C言語のきほん|代表的なアルゴリズム

探す・並べる・計算するの定番セット!代表的アルゴリズムを知ると、問題の解き方が見えてくるよ。

アルゴリズムは「問題を解く手順書」でしたね。実は、よく出る問題には“よく出る解き方”があって、定番のアルゴリズムをいくつか知っているだけで、プログラム作りがすごく楽になります。

たとえば、

  • 配列から値を探したい → 探索
  • データを小さい順に並べたい → ソート
  • 数学っぽい計算を効率よくやりたい → 互除法や数列

みたいに、用途ごとに「強い手順」があります。

ここでは初心者が最初に押さえたい代表例として、探索・ソート・計算系のアルゴリズムをやさしく整理していきます。

代表的アルゴリズム一覧(まずは全体像)

分野アルゴリズム何ができる?特徴
探索線形探索先頭から順に探す簡単、でも遅くなりやすい
探索二分探索半分ずつ絞って探す速い、ただし整列が必要
ソートバブルソート隣同士を交換して並べる簡単、遅い
ソートセレクションソート最小を選んで前へ簡単、遅い
ソートインサーションソート途中に差し込むある程度整列済みだと速い
ソートクイックソート分割して再帰で並べる速いことが多く大規模向き
計算ユークリッドの互除法最大公約数を求めるとても有名で強い
数列フィボナッチ数列前2つの和で進む数列再帰の練習題材

ここからは、それぞれを「どういう考え方か」「いつ使うか」を、実例を交えて説明します。

線形探索法(最初から順番に探す)

考え方

配列の先頭から順に見ていって、目的の値が見つかったら終了する方法です。

先頭 → 次 → 次 → ...
見つかったら終了
最後まで無ければ「見つからない」

良いところと弱点

  • 良いところ:実装が簡単、ソートされていなくてもOK
  • 弱点:データが多いと時間がかかりやすい

サンプルプログラム

「商品コードを探す」プログラム例です。

ファイル名:7_6_1.c

#include <stdio.h>

int main(void)
{
    int codes[] = {103, 205, 150, 330, 290};
    int n = 5;
    int target;
    int i;
    int found = 0;

    printf("探したい商品コードを入力してください > ");
    scanf("%d", &target);

    /* 線形探索:先頭から順番に探す */
    for (i = 0; i < n; i++) {
        if (codes[i] == target) {
            found = 1;
            break;
        }
    }

    if (found) {
        printf("見つかりました。位置は%d番目です。\n", i);
    } else {
        printf("見つかりませんでした。\n");
    }

    printf("メッセージ: 先頭から順番に探すのが線形探索だよ。\n");
    return 0;
}

二分探索法(半分ずつ絞って探す)

考え方

データが整列されているときに、中央を見て「左半分か右半分か」を決めて範囲を半分にしていく方法です。

中央を見る
  ↓
小さいなら右側へ / 大きいなら左側へ
  ↓
範囲を半分にして繰り返す

重要な条件:並んでいる必要がある

昇順・降順にソートされていないと使えません。
ここが線形探索との大きな違いです。

サンプルプログラム:整列済み配列で探す

ファイル名:7_6_2.c

#include <stdio.h>

int main(void)
{
    int nums[] = {2, 5, 8, 12, 20, 35, 50};
    int n = 7;
    int target;
    int left = 0, right = n - 1;
    int mid;
    int found = 0;

    printf("探したい数値を入力してください > ");
    scanf("%d", &target);

    /* 二分探索:範囲を半分ずつ絞る */
    while (left <= right) {
        mid = (left + right) / 2;

        if (nums[mid] == target) {
            found = 1;
            break;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }

    if (found) {
        printf("見つかりました。位置は%d番目です。\n", mid);
    } else {
        printf("見つかりませんでした。\n");
    }

    printf("メッセージ: 並んだデータなら二分探索はすごく速いよ。\n");
    return 0;
}

ソート系アルゴリズム(並べ替え)

探索とセットで覚えると強いです。なぜなら二分探索は「整列されていること」が前提だからです。

バブル・セレクション・インサーションの違い(ざっくり)

名前イメージ向き不向き
バブル隣同士を交換して大きいのを右へ学習向き、実務では遅い
セレクション最小を探して前へ置く学習向き、交換は少なめ
インサーション手札にカードを差し込むほぼ整列済みなら速い

クイックソートは“分割して並べる”高速タイプです。

インサーションソート(基本挿入法)

考え方

左側を「並んだ領域」として育てていき、次の要素を適切な位置に差し込みます。

左側は整列済み
次の値を取り出す
整列済みの中へ差し込む

サンプルプログラム:配列を整列

ファイル名:7_6_3.c

#include <stdio.h>

int main(void)
{
    int a[] = {6, 1, 4, 9, 2};
    int n = 5;
    int i, j, key;

    printf("並べ替え前: ");
    for (i = 0; i < n; i++) printf("%d ", a[i]);
    printf("\n");

    /* インサーションソート:適切な位置に挿入する */
    for (i = 1; i < n; i++) {
        key = a[i];
        j = i - 1;

        while (j >= 0 && a[j] > key) {
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = key;
    }

    printf("並べ替え後: ");
    for (i = 0; i < n; i++) printf("%d ", a[i]);
    printf("\n");

    printf("メッセージ: 手札に差し込む感覚がインサーションソートだよ。\n");
    return 0;
}

クイックソート(高速な分割統治)

考え方(雰囲気)

基準値(ピボット)を1つ決めて、

  • 小さいグループ
  • 大きいグループ

に分け、同じことを再帰的に繰り返します。

クイックソートは速いことが多いですが、再帰が出てくるので、最初は「こういう発想があるんだな」と知るだけでもOKです。

ユークリッドの互除法(最大公約数)

考え方

最大公約数は、余りを使ってどんどん小さくしていくと求まります。

a を b で割った余りを r とする
a = b, b = r に置き換える
余りが 0 になったら b が最大公約数

サンプルプログラム

ファイル名:7_6_4.c

#include <stdio.h>

int main(void)
{
    int a, b, r;

    printf("2つの整数を入力してください > ");
    scanf("%d %d", &a, &b);

    /* ユークリッドの互除法 */
    while (b != 0) {
        r = a % b;
        a = b;
        b = r;
    }

    printf("最大公約数は%dです。\n", a);
    printf("メッセージ: 余りを使って小さくしていくのが互除法だよ。\n");
    return 0;
}

フィボナッチ数列(再帰の入口)

定義

次の数が前2つの和になる数列です。

0, 1, 1, 2, 3, 5, 8, …

再帰でも書けますが、再帰は計算回数が増えやすいので、学習の最初は「繰り返しで作る」方が安心です。

サンプルプログラム:繰り返しでn番目まで表示

ファイル名:7_6_5.c

#include <stdio.h>

int main(void)
{
    int n, i;
    int a = 0, b = 1, next;

    printf("何個表示しますか > ");
    scanf("%d", &n);

    printf("フィボナッチ数列: ");
    for (i = 0; i < n; i++) {
        printf("%d ", a);
        next = a + b;
        a = b;
        b = next;
    }
    printf("\n");

    printf("メッセージ: 前2つの和で進む数列だよ。\n");
    return 0;
}

探索アルゴリズムをもう少し深掘り(どれくらい速いの?)

探索は「見つけるまでに何回くらい調べるか」がポイントになります。ここを感覚で掴むと、アルゴリズム選びが上手くなります。

線形探索と二分探索の比較(回数のイメージ)

データ数を n とすると、ざっくりこう考えられます。

探し方調べる回数のイメージ例:n=1000 のとき
線形探索最悪 n 回最悪 1000 回
二分探索最悪 log2(n) 回くらい約 10 回

二分探索が速いのは「半分に切る」を繰り返して、範囲が一気に小さくなるからです。

1000個
→ 500個
→ 250個
→ 125個
→ ...
→ 1個

ただし、二分探索は「整列済み」が条件なので、整列していないなら線形探索の方が素直です。

ソートが重要な理由(探索とセットで考える)

「二分探索が速いなら、いつも二分探索を使えばいいじゃん」と思うんですが、現実はこうなります。

  • データが最初から並んでいる → 二分探索でOK
  • データがバラバラ → まずソートする必要がある

ここで大事なのは、1回だけ探すならソートの手間が重いけど、何度も探すならソートしておく価値が出てくる、という考え方です。

例:会員番号を何度も検索するシステム

  • 毎回線形探索:毎回たくさん比較する
  • 最初にソートして二分探索:最初に整えるコストはあるが、その後の検索が速い

この「前処理して速くする」発想は、アルゴリズム全般でよく出ます。

バブル・セレクション・インサーションの“動き”を比べる

ソートは「同じ結果になるのに、手順が違う」のが面白いところです。
ここでは、動きの違いが分かるように整理します。

アルゴリズム何を繰り返す?どこが確定していく?特徴
バブル隣を比べて交換右端に最大が確定交換が多くなりがち
セレクション最小を探して交換左端に最小が確定交換は少なめ
インサーション取り出して挿入左側が整列済みとして育つほぼ整列済みだと速い

どれを覚えればいい?(学習の順番のおすすめ)

覚える順番は、こうすると無理が少ないです。

  1. 線形探索(配列とforに慣れる)
  2. バブルソート(交換の基本に慣れる)
  3. セレクションソート(最小を探す練習)
  4. インサーションソート(挿入の発想)
  5. 二分探索(整列済み前提の探索)
  6. ユークリッドの互除法(whileの良い練習)
  7. フィボナッチ(繰り返し→再帰の入口)
  8. クイックソート(分割統治と再帰の応用)