我认为我对quicksort的工作方式有了很好的理解,直到我在http://code.google.com/edu/algorithms/index.html上看到Jon Bentley推出他的“漂亮的快速排序代码”的视频,如下所示:
void quicksort(int l, int u){
int i, m;
if(l >= u) return;
m = l;
for(i = l+1; i<= u; i++)
if(x[i] < x[l])
swap(++m, i); //this is where I'm lost. Why the heck does it preincrement?
swap(l, m);
quicksort(l, m-1);
quicksort(m+1, u);
}
另一个令我困惑的算法是FOR循环后的最终交换。为什么这是必要的?让我们假设数组已经按顺序排列。如果这是真的,那么因为x [i]&gt;所以不会发生掉期。 X [1]。最后,我们用m交换l。这搞砸了订单。
我错过了什么吗?
感谢。
答案 0 :(得分:5)
在开头m
设置为l
,元素x[l]
被选为分区元素(pivot)。
接下来,算法迭代一个列表,只要它找到一个小于x[l]
的元素,它就会在当前 m
之后移动它。
这意味着,当m > l
时,l+1
到m
的所有位置上的元素都小于元素x[l]
。
例如:
3 5 4 2 1 l = 0, m = 0, i = 1
^
3 5 4 2 1 l = 0, m = 0, i = 2
^
3 2 4 5 1 l = 0, m = 1, i = 3
^
3 2 1 5 4 l = 0, m = 2, i = 4
^
最后,我们将最后一个较小的数字与第一个(分区)元素交换得到:
1 2 3 5 4
如果没有比第一个更小的元素,则交换不执行任何操作(因为m
等于l
)。
答案 1 :(得分:1)
x[l]
处的元素是选定的轴。 for循环的不变量是所有元素x[l+1]
到x[m]
都小于数据透视,而x[m]
到x[i]
的所有元素都大于或等于数据透视。
当找到小于枢轴的元素时,它会将其向下移动到条目m+1
,然后向上碰撞m
。 (m+1
处的条目大于枢轴,因此将其向上移动很好。)
最后一次交换是将枢轴从x[l]
移动到x[m]
,因为它需要在下部阵列和上部阵列之间结束。如果没有发生交换(排序数组示例),则m==l
和最终交换不会移动任何内容。
设置m = l + 1
并使用m++
代替++m
,教学代码更清晰。
答案 2 :(得分:1)
分区算法很好且易于记忆。它已经在ACM的通讯中或由Benltey多次重述。它也出现在Bentley的Programming Pearls一书中。我们的想法是跟踪否定后置条件的元素,即支点背后的元素越小,指数越高。但是,如果选择随机元素不是随机的,我们可能会得到最大(或最小)的元素,这将使我们与O(n)进行更多的交换。 java中的实现和解释是[blog]:http://harisankar-krishnaswamy.blogspot.in/2013/05/quick-sort-partition-algorithm.html“here”
哈
答案 3 :(得分:0)
如果数组已排序,则m永远不会从其初始值l更改,因此swap不执行任何操作。
答案 4 :(得分:0)
固定索引l
和u
指向要排序的子数组的第一个和最后一个元素。始终选择值x[l]
作为轴。
在循环的顶部,子数组(不包括枢轴x[l]
)划分如下:
l < index <= m
的元素,发现它们是< x[l]
m < index < i
的元素已经过测试,发现为>= x[l]
i <= index <= u
的元素尚未经过测试一个可能的混淆源是,在循环运行时,值大于枢轴的区域是中间部分,而不是上部部分。在未经测试的区域耗尽之前,它不会到达阵列的上部 - 通过重复交换有效地“移动”,以便为扩展的下部区域腾出空间。
具体地,每当下部区域需要扩展时,循环变量m
被预先递增:中间区域的第一个元素(如果有的话)必须移动到末尾,以便扩展下部区域。请注意,如果中间区域为空,则在预增量之后m == i
(对于这种情况,swap()
操作必须是无操作)。
最后,将枢轴x[l]
交换到下部区域的末尾,将其放置到递归步骤的位置。请注意,如果数组已按顺序排列,则下部区域为空m == l
,最后swap()
操作再次为无操作。