web-dev-qa-db-ja.com

クイックソートとホアパーティション

Hoareパーティショニングを使用したQuickSortをCコードに変換するのに苦労しており、その理由がわかりません。私が使用しているコードを以下に示します。

void QuickSort(int a[],int start,int end) {
    int q=HoarePartition(a,start,end);
    if (end<=start) return;
    QuickSort(a,q+1,end);
    QuickSort(a,start,q);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);

        if  (i < j)
            swap(&a[i],&a[j]);
        else
            return j;
    }
}

また、なぜHoarePartitionが機能するのかよくわかりません。誰かがそれが機能する理由を説明できますか、または少なくとも私を機能する記事にリンクできますか?

パーティショニングアルゴリズムの段階的なワークスルーを見てきましたが、直感的に理解できません。私のコードでは、それも機能していないようです。たとえば、配列が与えられた場合

13 19  9  5 12  8  7  4 11  2  6 21

ピボット13を使用しますが、最終的には配列になります

 6  2  9  5 12  8  7  4 11 19 13 21 

そして、jを返します。これはa[j] = 11。その時点から始まり、それ以降の配列はすべてピボットよりも大きい値を持つべきであると私は思っていましたが、11 <13であるため、ここではそうではありません。

これが役立つ場合に備えて、Hoareパーティショニングの擬似コード(CLRS、第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
            exchange  A[i] ↔ A[j]
        else  return   j 

ありがとう!

編集:

この問題の正しいCコードは次のようになります。

void QuickSort(int a[],int start,int end) {
    int q;
    if (end-start<2) return;
    q=HoarePartition(a,start,end);
    QuickSort(a,start,q);
    QuickSort(a,q,end);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);
        if  (i < j) 
            swap(&a[i],&a[j]);
        else 
            return j+1;
    }
}
14
Ofek Ron

「Hoareパーティショニングが機能するのはなぜですか?」という質問に答えるには:

配列内の値を3種類に単純化してみましょう:[〜#〜] l [〜#〜]値(ピボット値よりも小さい値)、[〜#〜] e [〜#〜] 値(ピボット値に等しいもの)、および[〜#〜] g [〜#〜]値(ピボット値より大きいもの)。

また、配列内の1つの場所に特別な名前を付けます。この場所をsと呼びます。これは、プロシージャが終了したときにjポインタが存在する場所です。sがどこにあるかを事前に知っていますか?いいえ。ただし、一部の場所がその説明に一致することはわかっています。

これらの用語を使用すると、パーティショニング手順の目標をわずかに異なる用語で表すことができます。つまり、単一の配列を、相互に誤ってソートされていないである2つの小さなサブ配列に分割することです。次の条件が当てはまる場合、その「誤ってソートされていない」要件が満たされます。

  1. 配列の左端からsまでの「低」サブ配列には、[〜#〜] g [〜#〜]値は含まれません。
  2. sの直後に始まり、右端まで続く「高」サブ配列には、[〜#〜] l [〜#〜]値が含まれていません。

本当に必要なのはそれだけです。 [〜#〜] e [〜#〜]の値が特定のパスでどこに到達するかを心配する必要はありません。各パスがサブアレイを相互に正しく取得している限り、後のパスはサブアレイ内に存在するすべての障害を処理します。

それでは、反対側からの質問に対処しましょう。パーティショニング手順では、sまたはその左側に[〜#〜] g [〜#〜]値がないことをどのように確認しますか。 [〜#〜] l [〜#〜]sの右側の値はありませんか?

sの右側の値のセット」は、「jポインタがsに到達する前に移動するセルのセット」と同じです。また、「sを含む左側の値のセット」は、「jsに到達する前にiポインタが移動する値のセット」と同じです。 。

つまり、誤って配置されている値は、ループの一部の反復で、2つのポインターのいずれかの下にあります。 (便宜上、[〜#〜] l [〜#〜]値を指すjポインターであるとしましょう。ただし、iポインターを指す場合もまったく同じように機能します。 a [〜#〜] g [〜#〜] value。)jポインターが間違った値にある場合、iポインターはどこにありますか?私たちはそれが次のようになることを知っています:

  1. [〜#〜] l [〜#〜]値が問題なく移動できる「低」サブ配列内の場所。
  2. [〜#〜] e [〜#〜]または[〜#〜] g [〜#〜]値のいずれかである値を指すと、[〜 #〜] l [〜#〜]jポインタの下の値。 ([〜#〜] e [〜#〜]または[〜#〜] g [〜#〜]の値がなかった場合は、そこで停止しませんでした。 )

iおよびjポインタが実際には両方とも[〜#〜] e [〜#〜]値で停止する場合があることに注意してください。これが発生すると、値は必要ありませんが、値が切り替えられます。ただし、これによって害が生じることはありません。 [〜#〜] e [〜#〜]値を配置しても、サブ配列間で誤った並べ替えが発生することはありません。

したがって、要約すると、Hoareパーティショニングは次の理由で機能します。

  1. 配列を、相互に誤ってソートされない小さなサブ配列に分割します。
  2. それを続けてサブ配列を再帰的にソートすると、最終的にはソートされていない配列が残りません。
7
afeldspar

このコードには2つの問題があると思います。手始めに、クイックソート機能で、行を並べ替えたいと思います

 int q=HoarePartition(a,start,end);
 if (end<=start) return;

あなたがこれらをこのようにするように:

 if (end<=start) return;
 int q=HoarePartition(a,start,end);

ただし、これ以上のことを行う必要があります。特にこれは読むべきです

 if (end - start < 2) return;
 int q=HoarePartition(a,start,end);

これは、パーティション化しようとしている範囲のサイズが0または1の場合、Hoareパーティションが正しく機能しないためです。 CLRSの私の版では、これはどこにも言及されていません。これを見つけるには、本の正誤表ページに移動する必要がありました。これはほぼ間違いなく、「アクセス範囲外」エラーで発生した問題の原因です。不変条件が壊れていると、配列からすぐに実行される可能性があるためです。

Hoareパーティショニングの分析については、手作業でトレースすることから始めることをお勧めします。より詳細な分析もありますここ。直感的には、範囲の端から互いに向かって2つの範囲を拡大することで機能します。1つはピボットよりも小さい要素を含む左側にあり、もう1つはピボットよりも大きい要素を含む右側にあります。これをわずかに変更して、等しいキーを処理するために適切にスケーリングするBentley-McIlroyパーティショニングアルゴリズム(リンクで参照)を生成できます。

お役に立てれば!

5
templatetypedef

jの初期値はrではなく_r + 1_である必要があるため、最終的なコードは間違っています。それ以外の場合、パーティション関数は常に最後の値を無視します。

実際には、HoarePartitionは機能します。これは、少なくとも2つの要素(つまり、_A[p...r]_)を含む配列_p < r_の場合、_A[p...j]_のすべての要素が_<=_ _A[j+1...r]_のすべての要素であるためです。終了したとき。したがって、メインアルゴリズムが繰り返される次の2つのセグメントは、_[start...q]_と_[q+1...end]_です。

したがって、正しいCコードは次のとおりです。

_void QuickSort(int a[],int start,int end) {
    if (end <= start) return;
    int q=HoarePartition(a,start,end);
    QuickSort(a,start,q);
    QuickSort(a,q + 1,end);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r+1;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);
        if  (i < j) 
            swap(&a[i],&a[j]);
        else 
            return j;
    }
}
_

その他の説明:

  1. パーティション部分は、疑似コードの単なる翻訳です。 (戻り値はjであることに注意してください)

  2. 再帰部分については、基本ケースのチェックに注意してください(_end <= start_の代わりに_end <= start + 1_、そうでない場合は_[2 1]_サブ配列をスキップします)

3
VMatrix1900

まず、uは、ピボットをサブ配列の左端の要素と見なしたため、cの変換されたコードからわかるHoareの分割アルゴリズムを誤解しました。

左端の要素をピボットと見なしてuを説明します。

int HoarePartition (int a[],int p, int r) 

ここで、pとrは、パーティション化されるより大きな配列(サブ配列)の一部となることができる配列の下限と上限を表します。

したがって、最初に配列の終点の前後を指すポインター(マーカー)から始めます(do whileループを使用してbcozするだけです)。したがって、

i=p-1,

j=r+1;    //here u made mistake

ここで、パーティショニングに従って、ピボットの左側にあるすべての要素をピボット以下にし、ピボットの右側より大きくする必要があります。

したがって、ピボット以上の要素が得られるまで、「i」マーカーを移動します。同様に、ピボット以下の要素が見つかるまで「j」マーカー。

ここで、i <jの場合、要素bcozを交換します。両方の要素は、配列の間違った部分にあります。したがって、コードは

do  j--; while (a[j] <= x);                 //look at inequality sign
do  i++; while (a[i] >= x);
if  (i < j) 
    swap(&a[i],&a[j]);

'i'が 'j'以上の場合、スワップする要素が間にないことを意味するため、 'j'の位置を返します。

したがって、下半分を分割した後の配列は「startからj」になります。

上半分は「j + 1から終わりまで」

したがって、コードは次のようになります

void QuickSort(int a[],int start,int end) {
    int q=HoarePartition(a,start,end);
    if (end<=start) return;
    QuickSort(a,start,q);
    QuickSort(a,q+1,end);
}
0
aman khunt
  1. ピボットを最初に移動します。 (たとえば、中央値3を使用します。入力サイズが小さい場合は挿入ソートに切り替えます。)
  2. パーティション、
    • 現在左端の1と現在右端の0を繰り返し交換します。
      0-cmp(val、ピボット)== true、1--cmp(val、ピボット)== false。
      左<右でない場合は停止します。
    • その後、ピボットを右端の0と交換します。
0
qeatzy

最後のCコードは機能します。しかし、それは直感的ではありません。そして今、私は幸運にもCLRSを勉強しています。私の意見では、CLRSの擬似コードは間違っています。(2eで)ついに、場所を変更すれば正しいと思います。

 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
              exchange  A[i] ↔ A[j]
    else  
              exchnage  A[r] ↔ A[i]  
              return   i

はい、交換を追加しますA [r]↔A[i]はそれを機能させることができます。どうして? A [i]がA [r] OR i == rよりも大きくなっているためです。したがって、パーティションの機能を保証するために交換する必要があります。

0
longdeqidao

Javaでの簡単な実装。

public class QuickSortWithHoarePartition {

    public static void sort(int[] array) {
        sortHelper(array, 0, array.length - 1);
    }

    private static void sortHelper(int[] array, int p, int r) {
        if (p < r) {
            int q = doHoarePartitioning(array, p, r);
            sortHelper(array, p, q);
            sortHelper(array, q + 1, r);
        }
    }

    private static int doHoarePartitioning(int[] array, int p, int r) {
        int pivot = array[p];
        int i = p - 1;
        int j = r + 1;

        while (true) {

            do {
                i++;
            }
            while (array[i] < pivot);

            do {
                j--;
            }
            while (array[j] > pivot);

            if (i < j) {
                swap(array, i, j);
            } else {
                return j;
            }
        }

    }

    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}
0