
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回だけ探すならソートの手間が重いけど、何度も探すならソートしておく価値が出てくる、という考え方です。
例:会員番号を何度も検索するシステム
- 毎回線形探索:毎回たくさん比較する
- 最初にソートして二分探索:最初に整えるコストはあるが、その後の検索が速い
この「前処理して速くする」発想は、アルゴリズム全般でよく出ます。
バブル・セレクション・インサーションの“動き”を比べる
ソートは「同じ結果になるのに、手順が違う」のが面白いところです。
ここでは、動きの違いが分かるように整理します。
| アルゴリズム | 何を繰り返す? | どこが確定していく? | 特徴 |
|---|---|---|---|
| バブル | 隣を比べて交換 | 右端に最大が確定 | 交換が多くなりがち |
| セレクション | 最小を探して交換 | 左端に最小が確定 | 交換は少なめ |
| インサーション | 取り出して挿入 | 左側が整列済みとして育つ | ほぼ整列済みだと速い |
どれを覚えればいい?(学習の順番のおすすめ)
覚える順番は、こうすると無理が少ないです。
- 線形探索(配列とforに慣れる)
- バブルソート(交換の基本に慣れる)
- セレクションソート(最小を探す練習)
- インサーションソート(挿入の発想)
- 二分探索(整列済み前提の探索)
- ユークリッドの互除法(whileの良い練習)
- フィボナッチ(繰り返し→再帰の入口)
- クイックソート(分割統治と再帰の応用)
