web-dev-qa-db-ja.com

三値戦略の中央値

ピボット値をクイックソートで選択する3つの戦略の中央値は何ですか?

私はそれをウェブで読んでいますが、正確にそれが何であるかを理解できませんでしたか?また、ランダム化されたクイックソートよりも優れている点。

21
Abdul Samad

3つの中央値では、配列の最初、中間、および最後の要素を見て、これら3つの要素の中央値をピボットとして選択します。

中央値3の「完全な効果」を得るには、中央値をピボットとして使用するだけでなく、sortこれら3つの項目も重要です。これは、ピボットとして選択されたものに影響しません現在の反復ですが、次の再帰呼び出しでピボットとして使用されるものに影響を与える可能性があります/これは、いくつかの初期順序の悪い動作を制限するのに役立ちます(多くの場合、特に悪いことが判明したのは、並べ替えられた配列です配列の上端に最小要素(または下端に最大要素)がある場合を除きます。

ピボットをランダムに選択する場合と比較して:

  1. これにより、1つの一般的なケース(完全にソートされたデータ)が最適なままになります。
  2. 最悪のケースを与えるように操作することはより困難です。
  3. A PRNGは、多くの場合比較的低速です。

その2番目の点は、おそらくもう少し説明があります。明らかな(Rand())乱数ジェネレーターを使用した場合、だれかが要素を簡単に(多くの場合、)簡単に配置できるため、貧弱なピボットを継続的に選択できます。これは、潜在的な攻撃者によって入力されたデータを並べ替える可能性のあるWebサーバーのようなものにとって深刻な懸念事項になる可能性があります。このような場合、could本当にランダムなシードを使用するか、Rand()を使用する代わりに独自のPRNGを含めることができます-またはMedianを使用します3つのうち、前述した他の利点もあります。

一方、十分にランダムなジェネレーター(たとえば、カウンターモードでのハードウェアジェネレーターまたは暗号化)を使用する場合、3つの選択の中央値よりも悪いケースを強制することはおそらく困難ですmore同時に、そのレベルのランダム性を達成することは、通常、それ自体のかなりのオーバーヘッドを持っているので、このケースで実際に攻撃されることを期待しない限り、それはおそらく価値がありません(そして、もしそうなら、おそらく少なくとも検討する価値がありますマージソートやヒープソートなど、O(N log N)の最悪の場合を保証する代替手段。

30
Jerry Coffin

Median of Threeの実装は、クイックソートでうまく機能します。

(Python)
# Get the median of three of the array, changing the array as you do.
# arr = Data Structure (List)
# left = Left most index into list to find MOT on.
# right = Right most index into list to find MOT on

def MedianOfThree(arr, left, right):
    mid = (left + right)/2
    if arr[right] < arr[left]:
        Swap(arr, left, right)        
    if arr[mid] < arr[left]:
        Swap(arr, mid, left)
    if arr[right] < arr[mid]:
        Swap(arr, right, mid)
    return mid

# Generic Swap for manipulating list data.
def Swap(arr, left, right):
    temp = arr[left]
    arr[left] = arr[right]
    arr[right] = temp    
12
rahvin_t

この戦略は、3つの数値を決定論的またはランダムに選択し、それらの中央値をピボットとして使用することで構成されます。

これは、「悪い」ピボットを見つける確率を減らすため、より良いでしょう。

4
Simone

シンプルだと思う... Python example ....

 def bigger(a、b):#2つの数値のうち大きい方を検索します... 
 if a> b:
 return a 
 else:
 return b 
 
 def maximum(a、b、c):#3つの数字の最大値を検索... 
 return large(a、bigger(b、c) )
 
 def median(a、b、c):#ジャストダンス!
 x =最大(a、b、c)
 if x == a :
 return large(b、c)
 if x == b:
 return Large(a、c)
 else:
 return Large (a、b)
2
Spiro

Common/Vanilla quicksortは、右端の要素をピボットとして選択します。これは、多くの場合に病理学的性能O(N²)を示すという結果をもたらします。特に、ソートされたコレクションと逆ソートされたコレクション。どちらの場合も、右端の要素は、ピボットとして選択するのに最も悪い要素です。ピボットは、パーティション分割の途中で理想的に考えられます。パーティショニングは、ピボットを使用してデータを2つのセクション(低セクションと高セクション)に分割することになっています。低いセクションはピボットより低く、高いセクションは高くなります。

つの中央値ピボット選択:

  • 左端、中央、右端の要素を選択します
  • 左のパーティション、ピボット、右のパーティションに並べます。通常のクイックソートと同じ方法でピボットを使用します。

これにより、ソート済み/逆ソート入力の一般的な病理O(N²)が軽減されます。 まだ3の中央値への病理学的入力を作成するのは簡単です。しかし、これは構築された悪意のある使用です。自然な順序ではありません。

ランダム化ピボット:

  • ランダムピボットを選択します。これを通常のピボット要素として使用します。

ランダムな場合、これは病理学的なO(N²)の挙動を示しません。ランダムピボットは通常、一般的な並べ替えでは計算量が多くなるため、望ましくありません。ランダムでない場合(つまり、srand(0);、Rand()、上記と同じO(N²)エクスプロイトに対して予測可能で脆弱です。

ランダムピボットしないは、複数の要素を選択することの利点に注意してください。主に、中央値の効果は既に固有のものであり、ランダムな値は2つの要素の順序よりも計算量が多いためです。

2
Captain Giraffe

例によって中央値3の戦略を理解できます。配列が与えられたと仮定します。

[8, 2, 4, 5, 7, 1]

したがって、左端の要素は8、右端の要素は1。中央の要素は4、長さ2kの配列の場合、k番目の要素を選択します。

次に、この3つの要素を昇順または降順で並べ替えます。

[1, 4, 8]

したがって、中央値は4。そして、4ピボットとして。

実装側では、次のことができます。

// javascript
function findMedianOfThree(array) {
    var len = array.length;
    var firstElement = array[0];          
    var lastElement = array[len-1];
    var middleIndex = len%2 ? (len-1)/2 : (len/2)-1;
    var middleElement = array[middleIndex];
    var sortedArray = [firstElement, lastElement, middleElement].sort(function(a, b) {
        return a < b; //descending order in this case
    });
    return sortedArray[1];
}

それを実装する別の方法は、@ kwrlに触発されており、もう少し明確に説明したいと思います。

    // javascript
    function findMedian(first, second, third) {
        if ((second - first) * (third - first) < 0) { 
            return first;
        }else if ((first - second) * (third - second) < 0) {
            return second;
        }else if ((first - third)*(second - third) < 0) {
            return third;
        }
    }
    function findMedianOfThree(array) {
        var len = array.length;
        var firstElement = array[0];          
        var lastElement = array[len-1];
        var middleIndex = len%2 ? (len-1)/2 : (len/2)-1;
        var middleElement = array[middleIndex];
        var medianValue = findMedian(firstElement, lastElement, middleElement);
        return medianValue;
    }

関数findMedianを考えてください。最初の要素は、second Element > first Element > third Elementおよびthird Element > first Element > second Element、および両方の場合:(second - first) * (third - first) < 0、同じ推論が残りの2つのケースに適用されます。

2番目の実装を使用する利点は、実行時間を短縮できることです。

1
Leonard Ge

より速く考えてください... Cの例.

int medianThree(int a, int b, int c) {
    if ((a > b) != (a > c)) 
        return a;
    else if ((b > a) != (b > c)) 
        return b;
    else
        return c;
}

これは、XORのような演算子を使用します。だからあなたは読むだろう:

  • aは他のどれよりも大きいのですか? return a
  • bは他のどれよりも大きいのですか? return b
  • 上記のいずれでもない場合:return c

分割はピボット値に基づいているため、中央値アプローチは配列内でより均等に分割されるため、より高速です。

ランダムピックまたは固定ピックの最悪のシナリオでは、すべての配列をピボットのみを含む配列と残りの配列に分割し、O(n²)の複雑さをもたらします。

中央値アプローチを使用すると、それが起こらないことを確認できますが、代わりに中央値を計算するためのオーバーヘッドが発生します。

編集:

ベンチマーク 結果は、Biggerを少し最適化したにもかかわらず、XORBiggerより24倍速いことを示しています。

Plot demonstrating benchmarks

0
caiohamamura