挿入ソートとは?トランプで理解するO(n²)と得意な場面

挿入ソートとは?トランプで理解するO(n²)と得意な場面 IT・コンピュータ基礎

トランプゲームで手札を整理するとき、あなたはどうしているだろうか。

1枚ずつ引いては、すでに並んでいる手札の中の正しい場所に差し込む。「3は1と4のあいだだな」「1が来た、先頭に入れよう」——これを繰り返すと、気づいたときには手札が昇順に並んでいる。この「1枚ずつ差し込む」動作がそのままアルゴリズムになったものが挿入ソートだ。

挿入ソートはバブルソートと同じO(n²)なので「遅いアルゴリズム」に分類される。ただし、「ほぼ整列済みのデータ」に対してだけは驚くほど速い。この特徴を知っておくと、実務でちょうどいい場面で使える。


この記事でわかること

  • 挿入ソートの動作をトランプの比喩でステップごとに理解する
  • O(n²)になる理由と、最良ケースがO(n)になる条件
  • バブルソートとの違いと、挿入ソートが得意な場面
  • Pythonでのシンプルな実装コード

1枚ずつ差し込む:動作をステップで追う

📌 要点:未整列部分から1要素を取り出し、整列済み部分の正しい位置に差し込む。これを繰り返すことで全体が整列される。「すでに並んでいる手札に1枚ずつ足していく」イメージ。

挿入ソートで要素が正しい位置に差し込まれるステップ図

配列 [4, 2, 5, 1, 3] を挿入ソートで並び替えてみよう。

最初の状態:

整列済み: [4]  未整列: [2, 5, 1, 3]

先頭の4は「1枚だけ」なので、すでに整列済みとして扱う。

ステップ1:2を取り出す

取り出した値: 2
整列済みを右から確認: 4 > 2 → 4を1つ右にずらす
差し込み位置が決まった → [2, 4]
結果: [2, 4, 5, 1, 3]

ステップ2:5を取り出す

取り出した値: 5
整列済みを右から確認: 4 < 5 → ここで止まる
差し込み位置が決まった → [2, 4, 5]
結果: [2, 4, 5, 1, 3]

ステップ3:1を取り出す

取り出した値: 1
整列済みを右から確認: 5 > 1 → ずらす、4 > 1 → ずらす、2 > 1 → ずらす
先頭まで来た → [1, 2, 4, 5]
結果: [1, 2, 4, 5, 3]

ステップ4:3を取り出す

取り出した値: 3
整列済みを右から確認: 5 > 3 → ずらす、4 > 3 → ずらす、2 < 3 → ここで止まる
差し込み位置が決まった → [1, 2, 3, 4, 5]
結果: [1, 2, 3, 4, 5] ✅ 完成
ステップ取り出した値整列済み部分配列全体
開始[4][4, 2, 5, 1, 3]
12[2, 4][2, 4, 5, 1, 3]
25[2, 4, 5][2, 4, 5, 1, 3]
31[1, 2, 4, 5][1, 2, 4, 5, 3]
43[1, 2, 3, 4, 5][1, 2, 3, 4, 5]

「整列済みの右端から左に向かって比較し、差し込む場所を探す」——これが挿入ソートの本質だ。

Pythonで実装すると

def insertion_sort(arr):
    for i in range(1, len(arr)):       # インデックス1番目から順番に取り出す
        key = arr[i]                   # 今から差し込む値を手に持つ
        j = i - 1                      # 整列済み部分の右端から確認を始める

        # 差し込む場所が見つかるまで、整列済み部分を右にずらしていく
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]        # 1つ右にずらす(空きを作る)
            j -= 1                     # 1つ左へ移動して確認を続ける

        arr[j + 1] = key               # 見つかった場所に差し込む
    return arr

# 実行例
cards = [4, 2, 5, 1, 3]
print(insertion_sort(cards))  # → [1, 2, 3, 4, 5]

コードの key = arr[i] が「1枚手に持つ」、while ループ内のずらし処理が「場所を空けながら差し込み位置を探す」動作に対応している。


計算量O(n²)とO(n):トランプの枚数と手間の関係

📌 要点:最悪ケース(完全逆順)はO(n²)。しかし最良ケース(整列済み)はO(n)になる。「1枚取るたびに何枚と比べるか」がケースごとに大きく変わるのが挿入ソートの特徴。

挿入ソートの計算量はケースによって大きく変わる。

ケース計算量トランプで言うと
最良(整列済み)O(n)1枚取るたびに「右端より大きい」ですぐ確定。比較1回で終わる
平均O(n²)ランダムな並びで、平均して半分くらいの枚数と比べる
最悪(完全逆順)O(n²)1枚取るたびに整列済みの全枚数と比べ続ける

最悪ケースをイメージすると:

手札が [5, 4, 3, 2, 1](完全逆順)のとき、

  • 4を取る → 5と比較:1回
  • 3を取る → 4、5と比較:2回
  • 2を取る → 3、4、5と比較:3回
  • 1を取る → 2、3、4、5と比較:4回
  • 合計:1+2+3+4 = 10回

5枚で10回。n枚では n(n-1)/2 回——これがO(n²)だ。

最良ケースをイメージすると:

手札が [1, 2, 3, 4, 5](すでに整列済み)のとき、

  • 2を取る → 1 < 2 → 即確定:1回
  • 3を取る → 2 < 3 → 即確定:1回
  • 4を取る → 3 < 4 → 即確定:1回
  • 5を取る → 4 < 5 → 即確定:1回
  • 合計:4回(=n-1回)

「整列済みのところに持ってくると、1回比べるだけで差し込み場所が確定する」——これがO(n)になる理由だ。


挿入ソートが本領を発揮する場面

📌 要点:「ほぼ整列済み」のデータへの強さが挿入ソートの最大の特徴。新しいデータを1件追加してすぐ再整列するような用途では、バブルソートより大幅に速い。

ほぼ整列済みデータでの挿入ソートとバブルソートの速度比較

挿入ソートが他のO(n²)アルゴリズムより優れている場面がある。「ほぼ整列済みのデータ」だ。

たとえば、ほぼ昇順に並んでいる100件のリストに、順序を乱すデータが2〜3件だけ混じっている場合を考えよう。

  • バブルソート:100件すべてを対象にn-1パスを実行する。フラグ最適化が効くが、それでも乱れた箇所のあるパスはフルに走る
  • 挿入ソート:整列済みの箇所は1回の比較でスキップできる。乱れた2〜3件だけ少し多く比較するだけで済む

挿入ソートが適している具体的な場面:

  • 毎日1件ずつデータを追加して再整列する処理
  • ほぼ正しい順で並んでいるリストの微調整
  • 少量データ(10〜20件程度)のソート
  • 他のソートアルゴリズムの「仕上げ処理」として使われる(後述)

情シス部門で、機器の点検記録を日次でCSVに追加していくシステムを扱ったことがある。毎朝1〜2件追加するたびに一覧を日付順に並び直す処理が必要だった。ほぼ整列済みの状態に毎日少数を追加するだけなので、挿入ソートを使うと処理が一瞬で終わる。クイックソートのような重いアルゴリズムを使う必要はなかった。


バブルソートとの違いを整理する

📌 要点:どちらもO(n²)だが「何を比べて何を動かすか」が違う。バブルソートは隣同士を交換し続け、挿入ソートは正しい場所を探してから1回で差し込む。交換回数は挿入ソートのほうが少ない。

比較項目バブルソート挿入ソート
動作イメージ隣同士がひたすら入れ替わる1枚取って正しい場所に差し込む
最悪計算量O(n²)O(n²)
最良計算量O(n)(フラグ最適化時)O(n)
交換(移動)回数多い少ない
安定ソート
ほぼ整列済みへの強さ△(フラグで改善可能)◎(構造的に速い)
実装のシンプルさ

実用上の差は「交換回数が少ない」点にある。バブルソートは隣同士を何度も交換しながら最大値を後ろへ運ぶが、挿入ソートは「差し込む場所が決まってから、まとめてずらして1回で差し込む」。データを書き換える回数が少なく、書き込みコストが高いメモリ(フラッシュメモリ等)では有利になることがある。


実はPythonのTimsortにも使われている

📌 要点:Pythonの組み込みソート(sorted/list.sort)はTimsortというアルゴリズムを使う。TimsortはマージソートとInsertionSortの組み合わせで、小さな部分配列には挿入ソートを使って高速化している。

「挿入ソートは遅いのに、なぜ現役で使われているのか」——答えは実装の仕組みにある。

Pythonの sorted()list.sort()Timsort というアルゴリズムを使っている。TimsortはマージソートとInsertionSortを組み合わせた設計で、小さな部分配列(32〜64要素程度)に対しては挿入ソートを使う

なぜか。少ない要素数ではO(n²)とO(n log n)の差が小さく、挿入ソートのほうがオーバーヘッドが少ない。「速いアルゴリズム」にも準備コストがかかる——それが小さいデータでは挿入ソートに負けることがある、というわけだ。

「使えないアルゴリズム」は実はなく、得意な場面がある。挿入ソートはその代表例だ。


FAQ

Q
挿入ソートの「挿入」とはどういう意味ですか?
A

「挿入(insertion)」は「差し込む」という意味です。未整列部分から取り出した値を、整列済み部分の正しい位置に差し込む(挿入する)動作に由来します。
トランプで手札を整理するときの「この1枚はここに入れよう」がそのままです。

Q
挿入ソートは安定ソートですか?
A

はい、安定ソートです。
等しい値の要素は「差し込み時に右端から比較して、等しければそこで止まる」ため、元の順序が維持されます。arr[j] > key(厳密な大なり)を使う限り、等しい値同士は入れ替わりません。

Q
バブルソートと挿入ソート、初心者はどちらから学ぶべきですか?
A

どちらから始めてもかまいません。バブルソートは「隣同士の交換」という動作が非常にシンプルで視覚的にわかりやすいです。挿入ソートはトランプの手札整理という日常的な比喩があり、動作の意味を理解しやすいという強みがあります。2つをセットで学ぶことで「ソートとは何か」の理解が深まります。

Q
挿入ソートで配列の「ずらし」と「交換」は違いますか?
A

違います。交換(swap)は2要素を入れ替える操作で、代入が2回必要です。ずらし(shift)は1要素を1つ右に移動する操作で、代入が1回で済みます。挿入ソートは「手に持った値(key)のための空きを作るために右にずらす」ため、交換より代入回数が少なくなります。

Q
挿入ソートと選択ソートの違いは何ですか?
A

どちらもO(n²)ですが、動作の方向が逆です。挿入ソートは「1枚取って正しい場所を探す(左側の整列済み部分に差し込む)」、選択ソートは「未整列の中から最小値を選んで先頭に置く」動作です。挿入ソートは整列済み部分がどんどん育ち、選択ソートは未整列部分がどんどん縮む、と覚えると区別しやすいです。

Q
どれくらいのデータ量まで挿入ソートを使えますか?
A

目安は50件以下、または「ほぼ整列済み」なら数百件程度まで実用的です。Pythonの組み込みTimsortも32〜64要素のサブ配列に挿入ソートを使っています。それ以上の件数でランダムなデータには、クイックソートやマージソートを選んでください。


まとめ

  • 挿入ソートはトランプを1枚ずつ手に取り、正しい場所に差し込む動作そのもの
  • 最悪・平均の計算量はO(n²)。完全逆順のデータが最も遅い
  • 最良ケースはO(n)。整列済みデータなら比較が1回ずつで済む
  • 「ほぼ整列済み」のデータへの強さがバブルソートより優れている
  • Pythonの Timsort は内部で挿入ソートを活用している。「遅い」だけのアルゴリズムではない

手札を整理するとき、あなたはすでに挿入ソートを使っている。「これはここに差し込もう」という判断を毎回繰り返すだけで、最終的にきれいに並ぶ——その単純な動作に、コンピュータサイエンスの美しさが詰まっている。


関連記事

バブルソートと比べながらソートの基礎を固めたい方はこちら。
バブルソートとは?エレベーターの入れ替えで理解するO(n²)

「分割して速くする」クイックソートの仕組みへ進む方はこちら。
クイックソートとデカルトの分割統治
“`

タイトルとURLをコピーしました