挿入ソート(insertion sort) は未ソートの要素を1つずつ取り出し、ソート済み部分の適切な位置に挿入するアルゴリズム。トランプの手札を並べるイメージ。
| 計算量 | |
|---|---|
| 最悪時間計算量 | (逆順入力) |
| 平均時間計算量 | |
| 最良時間計算量 | (ほぼソート済み) |
| 空間計算量 | (in-place) |
特徴
安定ソート
in-place
ほぼソート済みのデータに非常に効率的
小さい配列()では定数係数が小さく実用的に速い(Timsort などの内部で使用)

アルゴリズム¶
インデックス
i = 1から開始し、arr[i]をkeyとして取り出すソート済み部分
arr[0:i]でkeyより大きい要素を1つ右にシフトする空いた位置に
keyを挿入するiを進めて繰り返す
def insertion_sort(arr: list) -> list:
a = arr.copy()
for i in range(1, len(a)):
key = a[i]
j = i - 1
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
return aimport random
data = [12, 11, 13, 5, 6]
print("入力:", data)
print("出力:", insertion_sort(data))
# ほぼソート済みデータで高速動作を確認
nearly_sorted = list(range(100))
nearly_sorted[50], nearly_sorted[51] = nearly_sorted[51], nearly_sorted[50]
assert insertion_sort(nearly_sorted) == list(range(100))
print("ほぼソート済み100件: OK")
random.seed(1)
large = random.sample(range(1000), 20)
assert insertion_sort(large) == sorted(large)
print("ランダム20件: OK")入力: [12, 11, 13, 5, 6]
出力: [5, 6, 11, 12, 13]
ほぼソート済み100件: OK
ランダム20件: OK
ステップ可視化¶
def insertion_sort_verbose(arr: list) -> list:
a = arr.copy()
for i in range(1, len(a)):
key = a[i]
j = i - 1
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
print(f"i={i}, key={key}: {a}")
return a
data = [5, 3, 8, 1, 4]
print("入力:", data)
insertion_sort_verbose(data)入力: [5, 3, 8, 1, 4]
i=1, key=3: [3, 5, 8, 1, 4]
i=2, key=8: [3, 5, 8, 1, 4]
i=3, key=1: [1, 3, 5, 8, 4]
i=4, key=4: [1, 3, 4, 5, 8]
[1, 3, 4, 5, 8]二分探索による改良¶
挿入位置を線形探索ではなく二分探索で求めることで比較回数を に削減できる。ただしシフト操作は のままなので、時間計算量全体は変わらない。
from bisect import bisect_left
def binary_insertion_sort(arr: list) -> list:
a = arr.copy()
for i in range(1, len(a)):
key = a[i]
pos = bisect_left(a, key, 0, i) # 挿入位置を二分探索
a[pos + 1:i + 1] = a[pos:i] # シフト
a[pos] = key
return a
data = [5, 3, 8, 1, 4]
print(binary_insertion_sort(data))
assert binary_insertion_sort(data) == sorted(data)[1, 3, 4, 5, 8]