Menu
Coddy logo textTech

Quick Sort

最終更新

クイックソートは「ピボット」を中心に並べ替える分割統治アルゴリズムです。ピボット要素を選び、それより小さいものはすべて前に、大きいものはすべて後ろに来るように配列を分割します。これによりピボットは最終的なソート済みの位置に固定されます。その後、左右の分割に対して再帰します。この可視化では、最後の要素をピボットとする Lomuto 方式を使用します。再生を押して、分割とピボットの配置を確認してください。

クイックソートは、良好なキャッシュ動作とインプレース分割のおかげで、実際には最速の汎用ソートであることが多く、平均は O(n log n) です。最悪計算量は O(n²) で(例えばピボットの選び方が悪いすでにソート済みの配列)、median-of-three やランダム化などの優れたピボット戦略でこれを回避できます。

時間計算量と空間計算量

ケース計算量備考
最良の場合O(n log n)均衡した分割
平均的な場合O(n log n)ランダムな順序
最悪の場合O(n²)常に不均衡なピボット
空間O(log n)再帰スタック(インプレース分割)
安定いいえ分割時のスワップが等しい要素を並べ替える

ステップごとの手順

ステップ何が起こるか
1ピボットを選ぶ(ここでは範囲の最後の要素)。
2分割:ピボットより小さい要素をすべてその左へ移動する。
3ピボットを境界にスワップする — これで最終的な位置に置かれる。
4左の分割に対して再帰的にクイックソートを適用する。
5右の分割に対して再帰的にクイックソートを適用する。

具体例による手順

Lomuto 方式(最後の要素をピボット)で [5, 2, 4, 1] をソートする:

パス配列操作
開始[5, 2, 4, 1]範囲全体を分割する。ピボットは 1(最後の要素)。
1[1, 2, 4, 5]1 より小さいものはないので、1 をインデックス 0 にスワップする。ピボット 1 はこれで確定。[2, 4, 5] に対して右へ再帰。
2[1, 2, 4, 5]ピボット 5[2, 4, 5] を分割する。24 も小さいので 5 は末尾に残り確定。[2, 4] に対して左へ再帰。
3[1, 2, 4, 5]ピボット 4[2, 4] を分割する。2 が小さいので 4 はそのままで確定。2 は要素が1つなのですでにソート済み。
完了[1, 2, 4, 5]すべてのピボットが所定の位置に固定され、配列はソート済み。

クイックソートを使うべきとき

使うべき場合避けるべき場合
定数係数が小さい、高速で汎用的なメモリ内ソートが必要なとき。最悪計算量 O(n log n) の保証が必要なとき(ヒープソートやマージソートを使う)。
メモリが限られているとき — 分割はインプレースで行われ、必要なスタックは O(log n) のみ。等しいキーの順序を保つ安定なソートが必要なとき。
データがランダムまたは不明な順序で、ランダムまたは median-of-three のピボットを使うとき。入力がすでにソート済みまたはほぼソート済みで、ピボットが固定されており O(n²) を引き起こすとき。
クイックソートはメモリに逐次アクセスするため、良好なキャッシュ局所性が重要なとき。連結リストをソートするとき。ここではマージソートがクイックソートの依存するランダムアクセスを避けられる。

Quick Sortのコード

Python, JavaScript, Java, C++, Cによるクリーンで実行可能なQuick Sortの実装です。言語を選んでコードをコピーするか、Coddyプレイグラウンドに読み込んだ状態で開けます。

PythonでのQuick Sortのコード

Python
1def quick_sort(a, low=0, high=None):2    if high is None:3        high = len(a) - 14    if low < high:5        p = partition(a, low, high)6        quick_sort(a, low, p - 1)7        quick_sort(a, p + 1, high)8    return a9
10
11def partition(a, low, high):12    # Lomuto partition: everything < pivot moves left of it13    pivot = a[high]14    i = low15    for j in range(low, high):16        if a[j] < pivot:17            a[i], a[j] = a[j], a[i]18            i += 119    a[i], a[high] = a[high], a[i]20    return i21
22
23nums = [10, 7, 8, 9, 1, 5]24print("Before:", nums)25quick_sort(nums)26print("After: ", nums)
このコードをPythonプレイグラウンドで実行

Quick Sort よくある質問

クイックソートの時間計算量はどれくらいですか?
クイックソートは平均 O(n log n)、最良の場合も O(n log n) ですが、分割が常に不均衡になる最悪の場合には O(n²) に劣化します。ランダムまたは median-of-three のピボットを使うと、最悪の場合はほとんど起こらなくなります。
クイックソートは安定ですか?
いいえ。標準的なインプレース分割は離れた要素をスワップするため、等しいキーの相対順序が変わることがあります。安定な変種も存在しますが、クイックソートのインプレースという利点を手放すことになります。
なぜクイックソートはマージソートより速いことが多いのですか?
クイックソートは優れたキャッシュ局所性を持ち、追加のバッファなしでインプレースに分割するため、定数係数が小さくなります。マージソートは同じ O(n log n) の上限を達成しますが、O(n) のバッファとより多くのデータ移動のコストを払います。
クイックソートとマージソート、どちらを使うべきですか?
メモリ内の配列を高速かつインプレースにソートするならクイックソートを選びましょう。小さな定数係数のおかげでたいてい勝ります。安定なソート、O(n log n) の最悪計算量保証、あるいは RAM に収まらない連結リストや外部データのソートが必要なときはマージソートを選びましょう。
なぜソート済みの配列でクイックソートは O(n²) になるのですか?
最初または最後の要素のような固定ピボットでは、すでにソート済みの入力に対して各分割が要素を1つだけ切り離すため、log n ではなく n 段の再帰が生じます。ピボットをランダムまたは median-of-three で選ぶとこのパターンが崩れ、O(n log n) の挙動が回復します。
Lomuto 方式と Hoare 方式の分割の違いは何ですか?
Lomuto 方式は左から右へ走査する単一のインデックスを使い、コードが単純なため、この可視化ではこれを採用しています。Hoare 方式は内側へ動く2つのポインタを使い、通常スワップが少なく実際には高速ですが、分割ステップの間にピボットを最終的な位置には置きません。
Coddy programming languages illustration

Coddy でアルゴリズムをマスターしよう

始める