以下是我编写的Hoare
分区算法,用于根据给定的数据块对数组进行分区(在这种情况下,它是数组的第一个元素,是一个相当糟糕的选择!)。但是,Bentley-McIlroy 3-way partitioning
Hoare
声称在许多密钥相等时可以提供更好的性能。任何人都可以简要解释第9页上的代码实现了什么,以及它为什么比<
算法表现更好?还有一个问题,分区根据=
,>
和def hoare(arr,start,end):
pivot = arr[start]
i,j = start,end
while i < j:
while i < j and arr[i] <= pivot:
i += 1
while j >= i and arr[j] > pivot:
j -= 1
if i < j:
arr[i],arr[j] = arr[j],arr[i]
arr[start],arr[j] = arr[j],arr[start]
return j
放置元素。如果多次出现的元素不是枢轴怎么办?
{{1}}
答案 0 :(得分:3)
我发现可以基于N. Lumoto的想法实现3路分区,这有点容易。 (正如Jon Bentley在他的书“编程珍珠”中提到的那样,Lumoto的方法很简单。)
这个想法是在扫描过程中保持不变,任何时候小于枢轴的元素都放在最左边的部分,而等于枢轴的元素则放在那个部分的旁边,大于枢轴放在最右边。剩下的部分是那些尚未经过检验的元素。
这些部分以i,k和j为界。当我们检查一个元素时,如果它等于pivot,我们只是前进到下一个元素,如果它小于pivot,我们可以将它与`equal'部分的第一个交换,这样不变量就可以了恢复。否则,我们需要与更大部分前面的最后一个交换它。
/*
* Another 3-way partition ternery quick sort based on N. Lomuto's method.
* Invariant: ... less ... | ... equal ... | ... ? ... | greater |
* i k j
*/
void qsort3(Key* xs, int l, int u) {
int i, j, k; Key pivot;
if (l < u - 1) {
i = l; j = u; pivot = xs[l];
for (k = l + 1; k < j; ++k) {
while (pivot < xs[k]) { --j; swap(xs[j], xs[k]); }
if (xs[k] < pivot) { swap(xs[i], xs[k]); ++i; }
}
qsort3(xs, l, i);
qsort3(xs, j, u);
}
}
我使用100,000个元素对标准库中的qsort API测试了这个程序。
答案 1 :(得分:2)
我认为,第9页上的代码很好地通过第8页上的图解释了:您首先执行分区,还将等于枢轴的元素交换到向量的边缘,因此它最终为:
[equals-left] [lesses] [greaters] [equals-right]
然后将相等的元素交换回中心:
[lesses] [equals-left] [equals-right] [greaters]
然后递归排序[lesses]
和[greaters]
塞奇威克的假设是,在数据集中有许多重复的元素。在这种情况下,重复枢轴是很常见的,如果是这样的话,你可以通过在任何一个快速排序递归中不包括任何重复的枢轴来获得一些好处,这样就可以得到总和的大小。两个分区将小于矢量的大小,即枢轴的重复次数(即使它只是它本身。)这减少了你需要递归的元素数量,这使得递归更快。
这样做的代价是每个元素进行一次或两次额外的比较,尽管它们都只是重复以前的比较,并且成功条件不同。在比较复杂的情况下,您可能希望使用显式三向比较函数,以便能够保存最后一个&lt;的结果。比较(在Sedgwick的代码中的while循环)。如果没有重复枢轴,那么这正是额外的成本:那些额外的比较。如果重复枢轴,则每个重复的枢轴元素有一个或两个额外的交换和两个或一个额外的比较(因此三个额外的操作,如果交换和比较花费相同的时间),加上每个重复的两个比较其他元素。
这值得吗?我很怀疑,但如果Sedgwick说的话,那么你应该听他而不是我。