16

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;
    }
}
4

7 に答える 7

9

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

配列内の値を単純化して、 L値 (ピボット値より小さい値)、E値 (ピボット値に等しい値)、およびG値 (ピボット値より大きい値) の 3 種類だけにします。

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

これらの用語を使用して、分割手順の目的を少し異なる用語で表すことができます。つまり、単一の配列を、互いに並べ替えが間違っていない2 つの小さなサブ配列に分割することです。次の条件が真である場合、「誤ってソートされていない」という要件が満たされます。

  1. 配列の左端からsまでの「低」サブ配列には、 G値が含まれていません。
  2. s の直後に開始し、右端まで続く「高」サブ配列には、 L値が含まれていません。

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

それでは、反対側からの質問に対処しましょう: 分割手順では、 sまたはその左側にG値がなく、 sの右側にL値がないことをどのように保証するのでしょうか?

さて、「sの右側の値の集合」は、「 jポインタがsに到達する前に移動するセルの集合」と同じです。そして、「s の左側にあり、sを含む値のセット」は、「 jがsに到達する前にiポインターが移動する値のセット」と同じです。

つまり、誤って配置された値、ループの繰り返しで、2 つのポインターのいずれかの下に置かれます。(便宜上、 L値を指しているjポインターであるとしましょう。ただし、 G値を指しているiポインターとまったく同じように機能します。) jポインターが間違った値にある場合、iポインターはどこにありますか? 次のようになることがわかっています。

  1. L値が問題なく移動できる「低」サブアレイ内の位置。
  2. jポインターの下のL値を簡単に置き換えることができるEまたはG値のいずれかの値を指します。( E値またはG値になければ、そこで止まらなかったでしょう。)

iおよびjポインターが実際には両方ともE値で停止する場合があることに注意してください。これが発生すると、必要がなくても値が切り替わります。ただし、これは何の害もありません。前に、 E値の配置によって、サブ配列間で誤った並べ替えが発生することはないと述べました。

つまり、まとめると、ホーアのパーティショニングが機能する理由は次のとおりです。

  1. 配列を、互いに相対的に誤ってソートされていない小さなサブ配列に分割します。
  2. それを続けてサブ配列を再帰的にソートすると、最終的にソートされていない配列は何も残りません。
于 2014-07-26T15:34:41.253 に答える
5

このコードには 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 パーティショニング アルゴリズム (リンクで参照) を生成できます。

お役に立てれば!

于 2011-08-25T23:05:30.173 に答える
3

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

実際、HoarePartition が機能するのA[p...r]は、少なくとも 2 つの要素 (つまり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]

于 2016-05-23T16:38:42.090 に答える
1

あなたは最後のCコードが動作します。しかし、それは直感的ではありません。そして今、幸運にもCLRSを勉強しています。私の意見では、CLRSの疑似コードは間違っています。

 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 より大きくなったためです。そのため、パーティションの機能を保証するために交換する必要があります。

于 2013-10-18T14:08:39.263 に答える