教学维度
由于其简单性,Lomuto的分区方法可能更易于实现。乔恩·本特利(Jon Bentley)的《编程珍珠》中有一个很好的轶事:
“ Quicksort的大多数讨论都使用基于两个接近索引[即Hoare的]的分区方案。尽管该方案的基本思想很简单,但我总是发现细节很棘手-我曾经花了两天的大部分时间来寻找隐藏在短分区循环中的错误。初稿的读者抱怨说,标准的两索引方法实际上比Lomuto的方法更简单,并草绘了一些代码来阐明他的观点。我发现两个错误后就停止了寻找。”
性能维度
对于实际使用,为了效率可能会牺牲易于实现的程度。从理论上讲,我们可以确定元素比较的数量并交换以比较性能。此外,实际的运行时间将受到其他因素的影响,例如缓存性能和分支预测错误。
如下所示,除交换次数外,算法在随机排列上的行为非常相似。Lomuto需要的Hoare是Hoare的三倍!
比较数
两种方法都可以使用比较来实现,以对长度为的数组进行分区。这本质上是最佳的,因为我们需要将每个元素与枢轴进行比较以决定放置位置。nn−1n
掉期数
两种算法的交换次数都是随机的,具体取决于数组中的元素。如果我们假设随机排列,即所有元素都是不同的,并且元素的每个排列均具有相同的可能性,则可以分析预期的交换次数。
仅作为相对顺序计数,我们假设元素是数字。由于元素的等级与其值一致,因此下面的讨论更加容易。1,…,n
洛穆托法
索引变量扫描整个数组,并且每当找到小于数据透视点的元素,便进行交换。在元素,恰好个元素小于,因此,如果枢轴是,我们将得到交换。A [ j ] x 1 ,… ,n x − 1 x x − 1 xjA[j]x1,…,nx−1xx−1x
然后,通过对所有支点取平均值来得出总体期望。每个值都同样有可能成为枢轴(即,概率为),因此我们有1{1,…,n}1n
1n∑x=1n(x−1)=n2−12.
平均使用Lomuto方法交换长度为的数组。n
霍尔法
在这里,分析有些棘手:即使固定枢轴,交换的数量仍然是随机的。x
更准确地说:索引和彼此相向直到交叉为止,这总是在发生(通过Hoare分区算法的正确性!)。这有效地将数组分为两部分:左边的部分被扫描,右边的部分被扫描。Ĵijxij
现在,正好对每对 “错位”的元素进行交换,即当前位于左侧的一个大元素(大于,因此属于右侧分区)和一个位于右侧的小元素。请注意,这种配对形成总是可行的,即,最初在右侧的小元素数量等于在左侧的大元素数量。x
可以证明,这些对的数量是超几何 分布的:对于大元素,我们随机绘制它们在数组中的位置,并在中有位置。左部分。因此,假设枢轴为,则预期的对数为。Hyp(n−1,n−x,x−1)n−xx−1(n−x)(x−1)/(n−1)x
最后,我们再次对所有枢轴值取平均,以获得Hoare分区的总交换预期数量:
1n∑x=1n(n−x)(x−1)n−1=n6−13.
(更详细的描述可以在我的硕士论文第29页中找到。)
内存访问模式
两种算法都使用两个指针进入数组,并按顺序对其进行扫描。因此,两者的行为几乎都是最佳的wrt缓存。
相等元素和已排序列表
正如Wandering Logic所述,对于非随机排列的列表,算法的性能差异更大。
在已经排序的数组上,Hoare的方法从不交换,因为不存在错位的对(参见上文),而Lomuto的方法仍然进行大约交换!n/2
相等元素的存在在Quicksort中需要特别注意。(我自己闯入了这个陷阱;有关“过早优化的故事”,请参阅我的硕士论文,第36页)。考虑一个充满 s 的数组作为极端示例。在这样的数组上,Hoare的方法为每对元素执行一次交换-这是Hoare分区的最坏情况-但和始终在数组中间相遇。因此,我们具有最佳的分区,总运行时间保持在。i j O(n log n )0ijO(nlogn)
Lomuto的方法在全数组上表现得更加愚蠢:比较将始终为真,因此我们对每个元素进行一次交换!但更糟糕的是:循环之后,我们总是有,因此我们观察到最坏的情况下的分区,从而使整体性能降低到!我= Ñ Θ (Ñ 2)0A[j] <= x
i=nΘ(n2)
结论
Lomuto的方法简单且易于实现,但不应用于实现库排序方法。
A[i+1] <= x
。在排序数组中(并在合理选择的枢轴下),Hoare几乎不进行交换,而Lomuto进行一吨处理(一旦j变小,则所有A[j] <= x
。),我会缺少什么?