C言語基礎|バブルソートの仕組み

「となり同士を比べるだけ!バブルソートの“動き”が見えると、ソートが一気に身近になる」

バブルソートってどんなソート?

ソートは、データを小さい順(昇順)や大きい順(降順)に並べ替えることでしたね。
その中でも バブルソート は、とても素朴で覚えやすい方法です。

やることはシンプルで、

  • となり同士を比べる
  • 順番が逆なら交換する
  • これを何度も繰り返す

これだけで、最終的に全体が整列します。
名前の由来は、交換を繰り返すうちに「正しい位置に値が泡(bubble)のように移動していく」イメージからです。

このページでは、元の「身長5人」ではなく、もっとシンプルに 5個の点数 を昇順に並べ替える例に変えて、
バブルソートがどの順番で比較・交換していくのかを、表と図で丁寧に追いかけます。

サンプルプログラム

「5個の点数を入力して、昇順にソートして表示する」プログラムです。

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

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

#include <stdio.h>

#define COUNT 5

//--- バブルソート(昇順) ---//
void bsort(int a[], int n)
{
    for (int i = 0; i < n - 1; i++) {          // パスは n-1 回
        for (int j = n - 1; j > i; j--) {      // 末尾から先頭へ走査
            if (a[j - 1] > a[j]) {             // 左が大きければ交換
                int tmp = a[j];
                a[j] = a[j - 1];
                a[j - 1] = tmp;
            }
        }
    }
}

int main(void)
{
    int score[COUNT];

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

    bsort(score, COUNT);

    puts("小さい順に並べました。");
    for (int i = 0; i < COUNT; i++) {
        printf("%d番目:%d\n", i + 1, score[i]);
    }

    return 0;
}

実行例

5個の点数を入力してください。
1番目:72
2番目:55
3番目:90
4番目:60
5番目:78
小さい順に並べました。
1番目:55
2番目:60
3番目:72
4番目:78
5番目:90

まず用語を整理:ソート・昇順・降順

用語

用語意味
ソート並べ替え点数を順番に並べる
昇順小さい→大きい55, 60, 72, 78, 90
降順大きい→小さい90, 78, 72, 60, 55

バブルソートの中身:パスとは何か

バブルソートは「パス」と呼ばれる作業を繰り返します。
1回のパスでは、配列の中を一定の方向に走査して、となり同士を比較し、必要なら交換します。

このサンプルでは 末尾から先頭へ走査 しています。

パスのイメージ(末尾→先頭)

末尾側から  (a[j-1], a[j])  を順に比較
必要なら入れ替える
1パス終わると、先頭側に小さい値が集まってくる

手で追ってみよう:実際の比較・交換の流れ

例として、入力が次の5個だったとします。

72  55  90  60  78

1パス目(i = 0):末尾から先頭へ

比較するペアは右から順に
(60, 78) → (90, 60) → (55, 90) → (72, 55)

1パス目の変化

比較する位置比較交換?配列の状態
(3,4)60 と 78しない72 55 90 60 78
(2,3)90 と 60する72 55 60 90 78
(1,2)55 と 60しない72 55 60 90 78
(0,1)72 と 55する55 72 60 90 78

表の説明
末尾から見ていき、逆順のペアだけ交換します。
1パスが終わった時点で、最小の 55 が先頭に移動しました。ここがバブルソートらしい動きです。

2パス目(i = 1):先頭1個はもう確定

2パス目では、先頭の 55 はもう動かさない前提になります。
つまり「未ソート部分」は index 1〜4 です。

ソート済み領域と未ソート領域

           ↓ここを並べる
     ________________
 55 | 72  60  90  78
 ↑ここは確定

2パス目の変化

開始状態
55 72 60 90 78

比較する位置比較交換?配列の状態
(3,4)90 と 78する55 72 60 78 90
(2,3)60 と 78しない55 72 60 78 90
(1,2)72 と 60する55 60 72 78 90

2パス目が終わると、先頭2個(55, 60)が確定します。

3パス目(i = 2)と 4パス目(i = 3)

3パス目:未ソートは index 2〜4
4パス目:未ソートは index 3〜4
と、だんだん比較範囲が狭くなります。

完成までのイメージ

1パス目:最小が先頭へ
2パス目:2番目に小さい値が2番目へ
3パス目:3番目が3番目へ
4パス目:残り2つを整えて完了

ポイント
要素数が n のとき、必要なパスは n-1 回です。
今回なら 5個なので 4回パスを行えば並び替えが完了します。

コードで理解:2重ループが何をしているか

ループ変数 i と j の役割

変数何を表す?どんな動き?
i何パス目か0 → 1 → 2 → … と増える。
jそのパスで走査する位置末尾(n-1) から i+1 まで減る。

表の説明

  • 外側 for(i):パスの回数を担当
  • 内側 for(j):そのパスでの比較・交換を担当
  • j > i になっているのは、「先頭 i 個は確定だから、そこまでは踏み込まない」という意味です

交換処理(swap)の仕組み

交換は一時変数 tmp を使って行います。

交換のイメージ

tmp に右を退避
右に左を入れる
左に tmp を戻す

交換の3手順

手順代入意味
1tmp = a[j]a[j] を一時退避
2a[j] = a[j-1]左の値を右へ移動
3a[j-1] = tmp退避した値を左へ

計算量の話(ざっくりでOK)

バブルソートは分かりやすい代わりに、データが多いと遅くなりがちです。

バブルソートの特徴

観点内容
比較回数だいたい n(n-1)/2 回
速さの傾向大きな n では遅め
良い点実装が簡単、動きが追いやすい
注意点交換が多いと時間がかかる

登場する命令・構文の書式と役割まとめ

#define の書式

#define 名前 値

何をする?
定数のように使える名前を作ります。今回は COUNT を 5 にしています。

配列の宣言

int score[COUNT];

何をする?
int を COUNT 個まとめて持てる箱を用意します。

関数の宣言(配列を受け取る)

void bsort(int a[], int n)

何をする?
配列 a を、要素数 n として受け取り、a の中身を並べ替えます。
配列は渡すときに「中身全部をコピー」ではなく「同じ配列を操作する形」になりやすいので、関数の中で並べ替えた結果が main 側にも反映されます。

for 文の書式

for (初期化; 条件; 更新) { ... }

何をする?
決まった回数や条件で繰り返します。
外側 for はパス回数、内側 for は比較・交換の走査担当でした。

if 文の書式

if (条件) { ... }

何をする?
条件が真のときだけ処理します。今回は a[j - 1] > a[j] のときに交換します。

printf / scanf / puts の書式

  • printf:表示(書式付き)
  • scanf:入力(変数のアドレスに格納)
  • puts:文字列を表示して改行

まとめ:バブルソートの“仕組み”はここだけ押さえればOK

  • となり同士を比べて、逆なら交換
  • 1パス終わると、先頭側に小さい値が集まり、確定していく
  • n 個ならパスは n-1 回
  • コードは 2重ループで表せる(外側=パス、内側=比較と交換)