クイックソートパーティション:Hoare vs. Lomuto


82

Cormenには、2つのクイックソートパーティション方式が記載されています。

Hoare-Partition(A, p, r)
x = A[p]
i = p - 1
j = r + 1
while true
    repeat
        j = j - 1
    until A[j] <= x
    repeat
        i = i + 1
    until A[i] >= x
    if i < j
        swap( A[i], A[j] )
    else
        return j

そして:

Lomuto-Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
    if A[j] <= x
        i = i + 1
        swap( A[i], A[j] )
swap( A[i +1], A[r] )
return i + 1

ピボットを選択する方法を無視して、どの状況で一方が他方よりも望ましいですか?たとえば、重複する値の割合が高い場合(つまり、配列の3分の2以上が同じ値である場合)にLomutoのプリフォームが比較的不十分であることを知っています。

他のどのような特別なケースが、あるパーティション方式を他のパーティション方式よりも大幅に改善しますか?


2
私は考えることはできません任意の Lomutoがホーアよりも優れている状況。Lomutoはいつでも追加のスワップを実行するようA[i+1] <= xです。ソートされた配列(および合理的に選択されたピボットが与えられた場合)では、Hoareはほとんどスワップを行わず、Lomutoは1トンを行います(jが十分に小さくなると、すべてのA[j] <= x。)。
さまようロジック

2
@WanderingLogic確かではありませんが、彼の本でLomutoパーティションを使用するというCormenの決定は教育学的かもしれません-それはかなり単純なループ不変を持っているようです。
ロバートS.バーンズ

2
これらの2つのアルゴリズムは同じことをしないことに注意してください。Hoareのアルゴリズムの最後では、ピボットは最終的な場所にありません。swap(A[p], A[j])Hoareの最後にを追加して、両方に同じ動作をさせることができます。
オーレリアンオーム14年

また、Hoareのパーティション分割のi < j2つの繰り返しループを確認する必要があります。
オーレリアンオーム14年

@AurélienOomsコードはブックから直接コピーされます。
ロバートS.バーンズ14年

回答:


92

教育的次元

Lomutoのパーティション分割方法は単純であるため、実装が簡単になる場合があります。ソートに関するJon Bentleyのプログラミングパールには素晴らしい逸話があります。

「Quicksortのほとんどの議論では、2つの接近するインデックスに基づいたパーティションスキームを使用しています[...] [ie Hoare's]。そのスキームの基本的な考え方は簡単ですが、詳細は常に注意が必要です。2日間の大部分を短いパーティションループに隠れているバグを追いかけてきました。予備ドラフトの読者は、標準の2インデックス方式が実際にはLomutoの方式よりも簡単であると不満を述べ、彼のポイントを示すためにいくつかのコードをスケッチしました。2つのバグを見つけた後、私は探していませんでした。」

パフォーマンス次元

実際の使用では、効率のために実装の容易さが犠牲になる場合があります。理論的には、パフォーマンスを比較するための要素比較とスワップの数を決定できます。さらに、実際の実行時間は、キャッシュのパフォーマンスや分岐の予測ミスなど、他の要因の影響を受けます。

以下に示すように、アルゴリズムは、スワップの数を除き、ランダムな順列で非常によく似た動作をします。ロムトはホアレと同じ数の三倍必要です!

比較の数

両方のメソッドは、長さ配列を分割するために比較を使用して実装できます。配置場所を決定するためにすべての要素をピボットと比較する必要があるため、これは本質的に最適です。n1n

スワップの数

スワップの数は、配列の要素に応じて、両方のアルゴリズムでランダムです。ランダムな順列、つまり、すべての要素が別個であり、要素のすべての順列が等しくなる可能性があると仮定した場合、予想されるスワップの数を分析できます。

相対的な順序カウントのみであるため、要素は数字と想定します。これにより、要素のランクとその値が一致するため、以下の説明が簡単になります。1,,n

ロムトの方法

インデックス変数は配列全体をスキャンし、ピボットよりも小さい要素を見つけるたびにスワップを行います。要素、厳密にの要素はより小さいため、ピボットが場合はスワップが得られます。jA[j]x1,,nx1xx1x

全体的な期待値は、すべてのピボットを平均することで得られます。各値は同様にピボットになる可能性が高い(つまりprob。)ので、{1,,n}1n

1nx=1n(x1)=n212.

Lomutoの方法で長さ配列を分割するために平均でスワップします。n

ホーアの方法

ここで、分析はもう少し複雑です。ピボット修正しても、スワップの数はランダムのままです。x

より正確に:インデックスとは、交差するまで相互に向かって実行されます。これは常に発生します(Hoareの分割アルゴリズムの正確さによる!)。これにより、配列は2つの部分に効果的に分割されます。左の部分はによってスキャンされ、右の部分はによってスキャンされます。ijxij

さて、スワップは「置き違い」の要素のペアごとに正確に行われます。すなわち、現在左部分にある大きな要素(より大きく、したがって右パーティションに属する)と右部分にある小さな要素に対して行われます。このペア形成は常に有効であることに注意してください。つまり、最初は右側の小さな要素の数が左側の大きな要素の数に等しくなります。x

一つは、これらの対の数であることを示すことができるhypergeometrically 分散のための:我々は、ランダム配列におけるそれらの位置を描画し、持っている大きな要素の位置を左の部分。したがって、ピボットが場合、予想されるペアの数はです。Hyp(n1,nx,x1)nxx1(nx)(x1)/(n1)x

最後に、すべてのピボット値を再度平均して、Hoareのパーティション分割のための全体的な予想スワップ数を取得します。

1nx=1n(nx)(x1)n1=n613.

(より詳細な説明は、私の修士論文、29ページにあります。)

メモリアクセスパターン

両方のアルゴリズムは、2つのポインターを使用して、配列を順番にスキャンます。したがって、両方ともほぼ最適なwrtキャッシングを行います。

等しい要素と既にソートされたリスト

Wandering Logicで既に述べたように、アルゴリズムのパフォーマンスは、ランダムな順列ではないリストの場合により大きく異なります。

既にソートされた配列では、Hoareのメソッド、ペアの配置ミスがないため(上記参照)、スワップしませんが、Lomutoのメソッドは、ほぼスワップを行います!n/2

等しい要素が存在するため、Quicksortでは特別な注意が必要です。(自分でこのtrapに足を踏み入れました。「早すぎる最適化の物語」については、36ページの修士論文を参照してください)極端な例として、で満たされた配列を考えてください。そのような配列では、Hoareのメソッドは要素のすべてのペアに対してスワップを実行します(これはHoareのパーティション分割の最悪のケースです)が、と常に配列の中央で一致します。したがって、最適なパーティション分割が行われ、合計実行時間はです。0ijO(nlogn)

Lomutoのメソッドは、すべて配列に対してはるかに愚かな動作をします。比較は常に真であるため、すべての要素に対してスワップを行います。さらに悪いことに、ループ後は常にであるため、最悪の場合のパーティショニングが観察され、全体のパフォーマンスが低下します!0A[j] <= xi=nΘ(n2)

結論

Lomutoのメソッドは実装が簡単で簡単ですが、ライブラリの並べ替えメソッドの実装には使用しないでください。


16
うわー、それは一つの詳細な答えです。うまくできました!
ラファエル

ラファエルに同意する必要があります、本当にいい答えです!
ロバートS.バーンズ

1
合計要素に対する一意の要素の比率が低くなると、Lomutoが行う比較の数がHoareの比較よりも大幅に速くなることを少し説明します。これは、Lomuto側のパーティション分割が不十分で、Hoare側の平均パーティション分割が良好であるためです。
ロバートS.バーンズ

2つの方法の素晴らしい説明!ありがとうございました!
Vのkouk

Lomutoメソッドのバリアントを簡単に作成して、ピボットに等しいすべての要素を抽出し、それらを再帰から除外することができますが、平均的なケースを助けるか妨げるかはわかりません。
ヤクブナルブスキ

5

セバスチャンの優れた回答にいくつかのコメントが追加されました。

一般的なパーティション再配置アルゴリズムについて説明しますが、Quicksortの特定の使用については説明しません。

安定

Lomutoのアルゴリズムは準安定です。述語を満たさない要素の相対的な順序は保持されます。Hoareのアルゴリズムは不安定です。

要素アクセスパターン

Lomutoのアルゴリズムは、単一リンクリストまたは同様の転送専用データ構造で使用できます。Hoareのアルゴリズムには双方向性が必要です。

比較の数

Lomutoのアルゴリズムは、長さシーケンスを分割する述語のアプリケーションを実行して実装できます。(ホアも)。n1n

ただし、これを行うには、2つのプロパティを犠牲にする必要があります。

  1. パーティション化するシーケンスは空にしないでください。
  2. アルゴリズムはパーティションポイントを返すことができません。

これらの2つのプロパティのいずれかが必要な場合、比較を行ってアルゴリズムを実装する以外に選択肢はありません。n

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.