ソート2:マージソートとクイックソート
100万個の値をすべての組を比べて並べると約5,000億(500 billion)回の比較が必要ですが、マージソートなら約2,000万(20 million)回ですみます。
問題を分けて、半分ずつ解く
前のユニットの方法はリスト全体を一度に扱い、その代わりに約 n² 回の比較がかかります。このユニットの2つの方法は、分割統治法と呼ばれる別の作戦を使います。リストを小さな部分に分け、それぞれを同じ方法で並べてから、整列した部分を組み合わせるのです。分割は部分の値が1個か0個になるまで続き、値が1個のリストはもう整列しています。この方法は小さなリストに対して自分自身を呼び出すので、再帰的です(再帰はユニット6でくわしく見ます)。
マージソート:仕事はマージ(併合)の中にある
マージソートはリストを半分に分け、左半分を並べ、右半分を並べ、整列した2つの半分を1つにマージします。かしこいのはマージの部分です。整列した2つのリストは、先頭の値だけを見て組み合わせられます。2つの先頭を比べ、小さいほうを書き出して、その先へ進みます。片方のリストが空になるまでくり返し、もう一方の残りをそのまま写します。[1, 4, 7] と [2, 3, 8] のマージは、1(1対2)、2(4対2)、3(4対3)、4(4対8)、7(7対8)と進み、最後に8を写します。6個の値で比較は5回です。合わせてn個の値を持つ2つのリストのマージでは、比較が n − 1 回を超えることはありません。
function merge(left, right) {
const out = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (right[j] < left[i]) out.push(right[j++]);
else out.push(left[i++]);
}
return out.concat(left.slice(i), right.slice(j));
}
function mergeSort(a) {
if (a.length <= 1) return a; // 整列済み
const mid = Math.floor(a.length / 2);
return merge(mergeSort(a.slice(0, mid)), mergeSort(a.slice(mid)));
}なぜこれが速いのでしょうか。半分にしていく様子を段として思い浮かべましょう。8個の値のリストは、4個のリスト2つ、2個のリスト4つ、1個のリスト8つと分かれます。8は3回半分にできるので、段は3つです。どの段でも、すべてのマージを合わせると各値に1回ずつふれるので、1つの段にかかる比較は多くても約n回です。段が log₂ n 個あるので、合計は約 n log₂ n 回です。100万個の値なら約2,000万(20 million)回で、組ごとに比べる方法の約5,000億(500 billion)回とは大違いです。ラボと同じように [38, 27, 43, 3] と [9, 82, 10] に分けると、リスト [38, 27, 43, 3, 9, 82, 10] の比較は14回です。上のコードは3個と4個に分けるので、13回です。
その代わりにメモリを使います。マージには出力を書く場所が必要なので、マージソートは入力と同じ大きさの2つ目のリストを使います。またマージソートは安定です。等しいと比べられる値は元の順番を保ちます。先頭どうしが等しいとき、マージは左のリストから取るからです。これは表をある列で並べたあとで別の列で並べるとき、たとえば市で並べてから名前で並べるときに大切になります。
ドラッグで回転、スクロールまたはピンチで拡大・縮小。
クイックソート:ピボットのまわりで分ける
クイックソートは別の方法でリストを分けます。ピボットと呼ぶ値を1つ選び、ピボットより小さいものはすべてその前に、ほかはすべてその後ろに来るようにリストを並べかえます。この手順を分割(パーティション)と言います。これでピボットは最終的な位置に入り、両側は同じ方法で別々に並べられます。左側の値はどれも右側のどの値より小さいので、あとでマージする必要はありません。
ラボでは、各部分の最後の値をピボットにする、教科書によくある版を使います。[5, 1, 4, 2, 8] では、ピボットの8をほかの4つの値と比べますが、どれも小さいので8は末尾に残ります。次の部分 [5, 1, 4, 2] はピボット2を使います。小さいのは1だけなので、2回交換するとリストは [1, 2, 4, 5, 8] になります。[4, 5] はあと1回の比較ですみます。合計は比較8回、交換2回です。
ピボットが真ん中近くに入れば、分けるたびに問題は半分になり、クイックソートの比較はマージソートと同じく約 n log₂ n 回です。しかも2つ目の写しを使わず、元のリストの中で作業できます。けれども悪いピボットは痛手です。リストがすでに整列していてピボットがいつも最後の値なら、分けるたびに値が1つはがれるだけで、クイックソートの比較は約 n²/2 回に逆戻りします。[1, 2, 3, 4, 5] では10回で、選択ソートと同じです。実際のライブラリは、たとえばランダムに選んだり、3つの見本の値の真ん中を選んだりして、ピボットをもっとよく選ぶことでこれを避けています。
| 方法 | 比較 | 追加のメモリ |
|---|---|---|
| 挿入ソート | ふつう約 n²/4、最悪約 n²/2 | なし。安定 |
| マージソート | いつも約 n log₂ n | n個の値の2つ目のリスト。安定 |
| クイックソート | ふつう約 n log₂ n、最悪約 n²/2(悪いピボット) | 少し。ふつうの版は安定でない |
| 分割統治法 | 問題を分け、各部分を同じ方法で解き、結果を組み合わせる |
| マージ | 小さいほうの先頭の値をくり返し取って、整列した2つのリストを組み合わせる |
| マージソート | 半分に分け、それぞれを並べ、マージする。比較は約 n log₂ n 回 |
| ピボット | クイックソートがリストを分けるときの基準になる値 |
| 分割(パーティション) | 小さい値がピボットの前に、残りが後ろに来るように並べかえること |
| クイックソート | ピボットのまわりで分割し、それぞれの側を並べる |
| 安定なソート | 等しい値が元の順番を保つ |
| インプレース | 少ない追加メモリで、元のリストの中で並べる |
- [1, 4, 7] と [2, 3, 8] のマージには何回の比較が必要ですか?
- [1, 2, 3] と [4, 5, 6] のマージには何回の比較が必要ですか?
- 16個の値のリストを、各部分が1個になるまで半分にしていきます。半分にする段はいくつありますか?
- 100万個の値に対して、マージソートはおおよそ何回比較しますか?
- 約20回
- 約2,000万(20 million)回
- 約5,000億(500 billion)回
- ちょうど100万回
- 分割の後、ピボットはどこにありますか?
- 整列後の最終的な位置
- 先頭
- 末尾
- どこでもよい。あとでまた動く
- 最後の値をピボットにするクイックソートを、すでに整列したリスト [1, 2, 3, 4, 5] で実行します。比較は何回ですか?
- 入力と同じ大きさの2つ目のリストが必要なのはどの方法ですか?
- マージソート
- クイックソート
- 挿入ソート
- 1945年にマージソートを考案したのはだれですか?
- トニー・ホーア
- ジョン・フォン・ノイマン
- エイダ・ラブレス
- ユークリッド
- トニー・ホーアがクイックソートを思いつくきっかけになった問題は何ですか?
- 磁気テープ上の辞書を引くために、文の単語を並べること
- 図書館の本を並べること
- ウェブページを順位づけすること
- 銀行の小切手を並べること
- マージソートが安定と呼ばれるのはなぜですか?
- 等しい値が元の順番を保つ
- 決してクラッシュしない
- いつも同じ時間がかかる
- 追加のメモリを使わない
- 10個の値の比較ソートで、最悪の場合の比較回数として考えられる最小の数はいくつですか(log₂ 10! を切り上げたもの)?
- 分割統治法:分けて、各部分を解き、組み合わせる。
- マージソート:比較はいつも約 n log₂ n 回、追加のメモリが必要、安定。
- クイックソート:ピボットのまわりで分割する。平均では速いが、悪いピボットでは約 n²/2 回。
- 100万個の値なら、比較は約5,000億(500 billion)回ではなく約2,000万(20 million)回。
- どんな比較ソートも、最悪の場合に約 n log₂ n より速くはできない。
このコースの出典 (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 1 Introduction, 3 Sorting, 9 Breadth-First Search, 10 Depth-First Search (ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes) and Fall 2011 lecture 13 BFS notes (ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011)
- Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (MIT Press)
- with the CLRS-based Khan Academy algorithms unit by Thomas Cormen and Devin Balkcom of Dartmouth (khanacademy.org/computing/computer-science/algorithms: binary search, selection sort analysis, merge sort, quicksort overview and analysis)
- MacTutor History of Mathematics, Al-Khwarizmi (mathshistory.st-andrews.ac.uk/Biographies/Al-Khwarizmi: word algorithm from the Latin title Algoritmi de numero Indorum)
- D. E. Joyce, Euclid's Elements Book VII Propositions 1 and 2 (mathcs.clarku.edu/~djoyce/elements/bookVII)
- Wolfram MathWorld, Merge Sort (mathworld.wolfram.com/MergeSort.html: proposed by John von Neumann in 1945, after Knuth 1998 p. 159)
- Stanford CS "Tony Hoare: Quicksort" (cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html: devised while a student at Moscow State University for Russian-English machine translation with a tape dictionary, programmed in Algol 60 and published 1961; the year 1959 from the Wikipedia Quicksort article and its cited sources)
- Bodleian Libraries, Ada Lovelace (blogs.bodleian.ox.ac.uk/adalovelace: 1843 paper, Note G Bernoulli numbers)
- Fourmilab text of Menabrea's Sketch with Lovelace's notes (fourmilab.ch/babbage/sketch.html)
- Seven Bridges of Konigsberg (Euler 1736) and Tower of Hanoi (Edouard Lucas, 1883) checked against the Wikipedia articles and their cited sources.
確認日: 2026-10-17