トラック8・🗃️配列

集計・最大値・検索の定石

読む目安 約13分・ゴール:合計・最大値・線形探索の定型パターンを自力で書ける

アプリでこのトピックを学ぶ(無料・登録不要)

このトピックの要点

配列でやることは、実のところ数種類しかありません。ここで型(かた)として覚えてしまえば、あとは組み合わせるだけです。

毎回ゼロから考えると、初期値や範囲の判断でどこかを間違えます。型として手が覚えていれば、考える対象は「何を集計するか」だけになります。慣れた人がすばやくコードを書けるのは、頭の回転が速いからではなく、この引き出しを持っているからです。

定石1: 集計(合計と平均)

int sum = 0;                       // 合計は0から
for (int i = 0; i < n; i++) {
    sum += a[i];
}
double avg = (double)sum / n;      // 平均は必ずキャスト

平均で sum / n と書くと整数の割り算になり、350 ÷ 4 が 87 になってしまいます。(double) を1つ付けるだけで 87.5 が得られます。合計を求める変数は必ずループの外、初期値は 0 です。

定石2: 最大値・最小値

int max = a[0];                    // 先頭の要素で初期化するのが肝
for (int i = 1; i < n; i++) {      // 2番目から比べる
    if (a[i] > max) {
        max = a[i];
    }
}

// 位置も知りたいなら、更新のときに添字も一緒に保存する
int max2 = a[0], pos = 0;
for (int i = 1; i < n; i++) {
    if (a[i] > max2) { max2 = a[i]; pos = i; }
}

初期値を 0 にしてはいけません。全部マイナスの配列(気温など)では、答えが必ず 0 になってしまいます。先頭の要素を仮の王者にして、2番目から挑戦させる——これが安全な型です。最小値を求めるときは、比較を < に変えるだけで同じ骨格が使えます。

定石3: 線形探索

#include <stdio.h>

int find(const int a[], int n, int key)
{
    for (int i = 0; i < n; i++) {
        if (a[i] == key) {
            return i;              // 見つけたら添字を返す
        }
    }
    return -1;                     // 見つからなかった印
}

int main(void)
{
    int a[5] = {12, 45, 7, 90, 30};
    int pos = find(a, 5, 90);

    if (pos >= 0) {
        printf("%d 番目にありました\n", pos);
    } else {
        printf("見つかりません\n");
    }
    return 0;
}

見つかったら添字を返し、最後まで見つからなければ -1 を返します。「見つからなかったとき」に何を返すかを決めておくのが設計の要です。Cでは -1 を使うのが慣例で、呼び出し側は必ず戻り値を確認してから使います。この確認を省いて a[find(...)] と書くと、a[-1] を踏む重大事故になります。

組み合わせて使う

3つの型は組み合わせられます。「平均より高い点数が何個あるか」なら、集計(平均を出す)→ 走査(数える)の2段構えです。ループを1本にまとめたくなりますが、平均が確定するのは1本目が終わったあとなので、2本必要です。「いつ値が確定するか」を意識すると、こうした判断が自然にできるようになります。

罠・注意

ビット合計は0から、最大値は先頭から。ここだけは丸暗記でいいよ

この3つが書けるようになったら、次は「並べ替え」が視野に入ります。並べ替えも結局は、比較して入れ替えるという操作をループで繰り返すだけです。第4部で正面から扱いますが、そこで出てくるコードも、いま覚えた骨格の組み合わせでできています。

まとめ: 集計は0から、最大値は先頭要素から、探索は見つけたら即 return して -1 で不在を伝える。この3つの型が配列処理の土台です。

アプリでこのトピックを学ぶ(無料・登録不要)

確認問題

確認問題 1

最大値とその位置を求めるループを追って、空欄を埋めてください。max・pos欄はその周が終わった時点の値です。

#include <stdio.h>

int main(void)
{
    int t[5] = {12, 45, 7, 45, 30};
    int max = t[0];
    int pos = 0;

    for (int i = 1; i < 5; i++) {
        if (t[i] > max) {
            max = t[i];
            pos = i;
        }
    }
    printf("%d %d\n", max, pos);
    return 0;
}
it[i]maxpos
145?1
27451
34545?
430?1
答えを見る

空欄の答え(上から順に):45、1、45

max は先頭の 12 から始まり、i=1 で 45 に更新されて pos が 1 になります。i=3 の t[3] も 45 ですが、条件が > なので更新されず pos は 1 のままです。等しい値のときに更新するかどうかで「最初の1件」か「最後の1件」かが変わります。

確認問題 2

気温(すべて氷点下)の最高値を求めたいのに、必ず 0 と表示されます。原因の行はどれですか。

1#include <stdio.h>
2
3int main(void)
4{
5    int t[4] = {-3, -8, -1, -5};
6    int max = 0;
7
8    for (int i = 0; i < 4; i++) {
9        if (t[i] > max) {
10            max = t[i];
11        }
12    }
13    printf("%d\n", max);
14    return 0;
15}
答えを見る

バグのある行:6行目

6行目の初期値 0 が原因です。すべての要素が 0 より小さいため一度も更新されません。int max = t[0]; に直し、ループを i = 1 から始めます。

最大値の初期値は「配列に絶対に存在しない小さな値」ではなく、先頭の要素そのものにするのが安全な定石です。0で初期化すると、負の数だけのデータで必ず破綻します。

「配列」の目次

  1. 配列とは——箱の行列
  2. 添字と0始まりの理由
  3. 配列とループの黄金コンビ
  4. 境界外アクセスという重大事故
  5. 配列の初期化いろいろ
  6. 多次元配列
  7. 配列を関数に渡す——入口編
  8. 集計・最大値・検索の定石

もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。

完全版の販売ページは準備中です。

ほかのトラック

📚 姉妹教材:手を動かして覚える Linux 教材 — Linuxとインフラの仕組みを地図で学ぶ

Web版