我正在阅读Robert Sedwick算法和数据结构第1-4部分中的快速排序算法。
template <class item>
static void quicksort(item [] a, int l, int r)
{
if(r <= l) return;
int i = partition(a,l,r);
quicksort(a, l, i-1);
quicksort(a, i+1, r);
}
template <class item>
int partition(item a[], int l, int r)
{
int i = l-1; j = r; item v = a[r];
for(;;) {
while( a[++i] < v );
while( v < a[--j] )
if( j == l )
break;
if( i >= j)
break; // pointer crossing.
exch(a[i], a[j]);
}
exch(a[i], a[r]);
return i;
}
Book上面的算法有以下文字。
当文件中存在重复键时,指针交叉为 微妙。我们可以稍微改进分区过程 当i <1时终止扫描。 j,然后用j而不是i-1, 为第一个递归分隔左子文件的右端 呼叫。在这种情况下让循环迭代一次是一个 改进,因为,当扫描循环用j终止时 而且我指的是相同的元素,我们最终得到了两个元素 他们的最终位置:停止两次扫描的元素,必须 因此等于分区元素和分区 元素本身。这种变化可能是值得的,因为,在这 特殊情况下,程序会留下一个等于该键的记录 在[r]中分区键,这使得第一个分区成为 调用快速排序(a,i + 1,r)退化,因为它最右键是 最小的。
我的问题是
感谢您的时间和帮助。
答案 0 :(得分:6)
&gt;&gt;如果存在更多重复键,为什么上面的快速排序算法无效?
由于您的违规条件是:if(i >= j) break;
,因此效率低下
因此,当您使用i
和j
从两侧进行扫描时,很可能在 i == j 时而不是让{{1超越i
。
当存在许多重复键时1>}时,可能会发生错误
当你从第一个while循环中断j
时,你必须有i==j
和第二个while循环i==j;
但是因为我们正在考虑'{break}':{{1}所以,a[i] >= v
即a[j] <=v
与i==j
相同,您的枢轴元素。
在这种情况下,您最外面的a[i] = a[j] = v
将简单地将枢轴值交换给自己。
因此,在你的下一个递归调用a[i]
中,对于数组的右半部分,你将最小元素放在最右端。(你的枢轴选择策略就是v
),我们都知道它QuickSort选择一个相当于数组最小值或最大值的pivot元素是不好的。因此,您对右半部分的后续递归调用将是退化调用
这就是为什么作者建议不要为 i == j 打破,而是在发生这种情况之前抓住它们。
&gt;&gt;作者在这里贬义是什么意思?
这里的退化意味着递归树变得偏斜,即后续问题不会产生几乎相等的大小。
您将大小为exch(a[i], a[r]);
的问题划分为大小为quicksort(a, i+1, r);
和item v = a[r];
的问题而不是更平衡的问题,例如将其划分为大小为N
和{{}的问题1}}。
&gt;&gt;我们如何通过以下说明修改上述程序?
我们可以像下面这样实现它:
N-1
&gt;&gt;如果存在更多重复密钥,上述修改如何改进?
它通过让您不会生成退化案例的明显场景来提高性能。