C言語基礎|配列要素の最大値と最小値

配列に値を読み込めるようになると、次にやりたくなるのが「集計」系の処理です。
合計や平均も楽しいんですが、実務っぽさが一気に出るのがこれ:

  • 最大値(max) … いちばん大きい値
  • 最小値(min) … いちばん小さい値

たとえば、気温データの最高/最低、売上の最大/最小、センサー値のピーク…などなど。
この節では、配列を走査しながら max と min を同時に更新して求めるやり方を、やさしく整理していきますね。

最大値・最小値を求める基本アイデア

最大値を求める手順はすごくシンプルです。

  1. 「とりあえず最大値」を仮に決める(最初の要素を使うのが定番)
  2. 残りの要素を順番に見て、もっと大きいのがあったら更新する。

最小値も同じで、比較が逆になるだけです。

目的比較更新
最大値 max値 > maxmax = 値
最小値 min値 < minmin = 値

サンプルプログラム(別例に差し替え版)

ここでは「1週間の歩数」を入力して、最大と最小を表示してみます。

プロジェクト名:chap5-6-1 ソースファイル名:chap5-6-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 1週間の歩数を読み込んで最大値と最小値を表示
#include <stdio.h>

#define DAYS 7   // データ数(1週間)

int main(void)
{
    int steps[DAYS];
    int max, min;

    printf("%d日分の歩数を入力してください。\n", DAYS);
    for (int i = 0; i < DAYS; i++) {
        printf("%d日目:", i + 1);
        scanf("%d", &steps[i]);
    }

    min = max = steps[0];              // 多重代入(最初の値で両方初期化)
    for (int i = 1; i < DAYS; i++) {
        if (steps[i] > max) max = steps[i];
        if (steps[i] < min) min = steps[i];
    }

    printf("最も多い歩数:%d\n", max);
    printf("最も少ない歩数:%d\n", min);

    return 0;
}

どうして最初に min = max = steps[0]; なの?

配列の最大・最小を求めるときにありがちなミスが、「初期値を雑に 0 にしてしまう」ことです。
たとえば全てマイナスのデータだったら、最大値が 0 のままになって壊れますよね。

そこで安全なのが 最初の要素を基準にするやり方です。

初期化方法問題が起きやすい例
max = 0;全部マイナスなら max が更新されない。
min = 0;全部プラスなら min が更新されない可能性
min = max = steps[0];データの範囲に依存せず自然に正しく動く。

多重代入(min = max = steps[0];)の意味を図で理解

代入は 右から左へ行われます。
だから次のように解釈されます。

min = (max = steps[0]);

処理の流れ(steps[0] が 8000 だった例)

  1. max = steps[0]; で max が 8000 になる
  2. この代入式全体の評価結果は「代入後の max の値(8000)」
  3. min = 8000 が実行される

図イメージ:

重要:代入式を評価すると「左側の型と値」になる

ここ、地味に強いルールです。

例:int 型変数 n に 2.95 を代入すると…

代入式左側の型代入後の値代入式の評価結果
n = 2.95int2int の 2

小数は int に入らないので、小数点以下は切り捨てられます。

一方、double 型変数 x なら…

代入式左側の型代入後の値代入式の評価結果
x = 2.95double2.95double の 2.95

この「代入式の評価結果」を利用すると、多重代入がスムーズに書ける、というわけです。

最大・最小を求める処理を“開いて”見るとこうなる

ループが苦手でも大丈夫。
DAYS が 7 のとき、max を求める部分は実質こういうことです。

max = steps[0];
if (steps[1] > max) max = steps[1];
if (steps[2] > max) max = steps[2];
...
if (steps[6] > max) max = steps[6];

min も同じで、記号が逆になるだけ。

min = steps[0];
if (steps[1] < min) min = steps[1];
...

だから「配列を走査しながら更新する」という感覚が掴めると、自然に書けるようになります。

登場した命令・演算子の書式と役割

#define 指令(オブジェクト形式マクロ)

書式

#define 名前 値

何をする?
翻訳時に、名前を値へ置換します。今回なら DAYS を 7 に置換して、配列サイズやループ回数を一括管理できます。

for 文(配列走査)

書式

for (初期化; 条件式; 更新) {
    繰り返す処理
}

何をする?
添字 i を 0 から順に増やして、配列の要素を先頭から順番に処理します。

if 文(条件分岐)

書式

if (条件) 文;

または

if (条件) {
    文;
}

何をする?
条件が真のときだけ処理を行います。max/min の更新に使います。

比較演算子 > と <

役割

  • a > b:a が b より大きい
  • a < b:a が b より小さい

代入演算子 =

役割
右辺の値を左辺へ入れます。代入式自体も「評価結果」を持つのがポイントです。

printf / scanf

printf 書式

printf(書式文字列, 値...);

scanf 書式

scanf(書式文字列, 格納先アドレス);

入力は配列要素なので、格納先は &steps[i] になります。

演習問題

演習5-4

配列の並びを反転するプログラムで、要素数をオブジェクト形式マクロで定義するように変更せよ。
また、交換回数の規則性(何回交換すれば良いか)を説明せよ。

解答例(要素数 N を使う版)

プロジェクト名:chap5-6-2 ソースファイル名:chap5-6-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define N 7

int main(void)
{
    int x[N];

    printf("%d個の整数を入力してください。\n", N);
    for (int i = 0; i < N; i++) {
        printf("x[%d]:", i);
        scanf("%d", &x[i]);
    }

    for (int i = 0; i < N / 2; i++) {   // 交換回数は N/2(整数の切り捨て)
        int t = x[i];
        x[i] = x[N - 1 - i];
        x[N - 1 - i] = t;
    }

    puts("反転後の並びです。");
    for (int i = 0; i < N; i++)
        printf("x[%d] = %d\n", i, x[i]);

    return 0;
}

規則性の説明(ポイント)

  • 先頭と末尾、次とその次…のように「左右ペア」で入れ替える。
  • ちょうど半分まで交換すると反転が完成する。
  • 交換回数は N/2(N が奇数なら真ん中は動かない)

演習5-5

a が double 型、b が int 型であるとする。次の代入のあと、a と b はどうなるか説明せよ。

a = b = 1.5;

解答例(説明)

  1. 右から評価されるので、まず b = 1.5 が実行される。
  2. b は int 型なので 1.5 は 1 に切り捨てられ、b は 1 になる。
  3. 代入式 b = 1.5 の評価結果は「代入後の b の値」で、int の 1
  4. 次に a = 1 が実行され、a は double 型なので 1.0 になる。

結果

変数最終的な値
bint1
adouble1.0