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;
}
}
「Hoareパーティショニングが機能するのはなぜですか?」という質問に答えるには:
配列内の値を3種類に単純化してみましょう:[〜#〜] l [〜#〜]値(ピボット値よりも小さい値)、[〜#〜] e [〜#〜] 値(ピボット値に等しいもの)、および[〜#〜] g [〜#〜]値(ピボット値より大きいもの)。
また、配列内の1つの場所に特別な名前を付けます。この場所をsと呼びます。これは、プロシージャが終了したときにjポインタが存在する場所です。sがどこにあるかを事前に知っていますか?いいえ。ただし、一部の場所がその説明に一致することはわかっています。
これらの用語を使用すると、パーティショニング手順の目標をわずかに異なる用語で表すことができます。つまり、単一の配列を、相互に誤ってソートされていないである2つの小さなサブ配列に分割することです。次の条件が当てはまる場合、その「誤ってソートされていない」要件が満たされます。
本当に必要なのはそれだけです。 [〜#〜] e [〜#〜]の値が特定のパスでどこに到達するかを心配する必要はありません。各パスがサブアレイを相互に正しく取得している限り、後のパスはサブアレイ内に存在するすべての障害を処理します。
それでは、反対側からの質問に対処しましょう。パーティショニング手順では、sまたはその左側に[〜#〜] g [〜#〜]値がないことをどのように確認しますか。 [〜#〜] l [〜#〜]sの右側の値はありませんか?
「sの右側の値のセット」は、「jポインタがsに到達する前に移動するセルのセット」と同じです。また、「sを含む左側の値のセット」は、「jがsに到達する前にiポインタが移動する値のセット」と同じです。 。
つまり、が誤って配置されている値は、ループの一部の反復で、2つのポインターのいずれかの下にあります。 (便宜上、[〜#〜] l [〜#〜]値を指すjポインターであるとしましょう。ただし、iポインターを指す場合もまったく同じように機能します。 a [〜#〜] g [〜#〜] value。)jポインターが間違った値にある場合、iポインターはどこにありますか?私たちはそれが次のようになることを知っています:
iおよびjポインタが実際には両方とも[〜#〜] e [〜#〜]値で停止する場合があることに注意してください。これが発生すると、値は必要ありませんが、値が切り替えられます。ただし、これによって害が生じることはありません。 [〜#〜] e [〜#〜]値を配置しても、サブ配列間で誤った並べ替えが発生することはありません。
したがって、要約すると、Hoareパーティショニングは次の理由で機能します。
このコードには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パーティショニングアルゴリズム(リンクで参照)を生成できます。
お役に立てれば!
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;
}
}
_
その他の説明:
パーティション部分は、疑似コードの単なる翻訳です。 (戻り値はj
であることに注意してください)
再帰部分については、基本ケースのチェックに注意してください(_end <= start
_の代わりに_end <= start + 1
_、そうでない場合は_[2 1]
_サブ配列をスキップします)
まず、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);
}
最後の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よりも大きくなっているためです。したがって、パーティションの機能を保証するために交換する必要があります。
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;
}
}