【发布时间】:2011-02-26 11:23:13
【问题描述】:
我有一个包含 N 个相同数字的数组。我正在对其应用快速排序。 这种情况下排序的时间复杂度应该是多少。
我仔细研究了这个问题,但没有得到确切的解释。
任何帮助将不胜感激。
【问题讨论】:
标签: c algorithm complexity-theory
我有一个包含 N 个相同数字的数组。我正在对其应用快速排序。 这种情况下排序的时间复杂度应该是多少。
我仔细研究了这个问题,但没有得到确切的解释。
任何帮助将不胜感激。
【问题讨论】:
标签: c algorithm complexity-theory
这取决于快速排序的实现。划分为 2 个(< 和 >=)部分的传统实现将在相同的输入上具有 O(n*n)。虽然不一定会发生 交换,但它会导致进行 n 递归调用 - 每个调用都需要与枢轴和 n-recursionDepth 元素进行比较。即O(n*n)需要进行比较
但是有一个简单的变体,它分为 3 个集合(<、= 和 >)。在这种情况下,此变体具有 O(n) 性能 - 而不是选择枢轴,交换然后在 0 到 pivotIndex-1 和 pivotIndex+1 到 n 上递归,它将交换所有等于枢轴的事物到“中间”分区(在所有相同输入的情况下总是意味着与自身交换,即无操作)意味着调用堆栈在这种特殊情况下只有 1 深 n 比较并且不会发生交换。我相信这个变种至少已经进入了 linux 上的标准库。
【讨论】:
<= 和 >= 部分,在它们之间平均分配相等的值。这在平均情况下(不同数据)没有成本,并且在数据相等的情况下保证 O(N log N) 时间
快速排序的性能取决于枢轴选择。选择的枢轴越接近中值元素,快速排序的性能就越好。
在这种特定情况下,您很幸运 - 您选择的枢轴将始终是 a 中位数,因为所有值都相同。因此,快速排序的分区步骤将永远不必交换元素,并且两个指针将恰好在中间相遇。因此,这两个子问题的大小正好是一半——给你一个完美的O(n log n)。
更具体地说,这取决于分区步骤的实施情况。循环不变式只需要确保较小的元素在左侧子问题中,而较大的元素在右侧子问题中。不能保证分区实现永远不会交换相等的元素。但这总是不必要的工作,所以没有聪明的实现应该这样做:left 和 right 指针永远不会检测到相应枢轴的反转(即你永远不会遇到*left > pivot && *right < pivot 的情况),所以@987654325 @指针会递增,right指针会每一步递减,最终在中间相遇,产生大小为n/2的子问题。
【讨论】:
n*n 性能 - 所有元素都相同。
< 和>= 进行分区,所以虽然不会发生交换,但它会递归n*n 次,并且每次都递归,但仍然会导致n*n 的性能
< 和>=,你甚至可以在等于元素的情况下获得无限递归。 (所有元素将始终在右侧,子问题永远不会缩小)。
这取决于特定的实现。
如果只有一种比较(≤或n em>2) 性能,因为每一步问题大小只会减少 1。
算法listed here 是一个示例(所附插图适用于不同的算法)。
如果有两种比较,例如 用于右侧元素,就像双指针实现中的情况一样,和逐步移动指针,那么您可能会获得完美的 O(n log n) 性能,因为一半相等的元素将在两个分区中平均分割。
上面链接中的插图使用了一种不会逐步移动指针的算法,因此您的性能仍然很差(查看“少数独特”的情况)。
所以这取决于你在实现算法时是否考虑到这种特殊情况。
实际的实现通常处理更广泛的特殊情况:如果在分区步骤中没有交换,他们假设数据几乎是排序的,并使用插入排序,这给出了更好的 O(n) 在所有相等元素的情况下。
【讨论】:
O(n) 而不是 O(n log n) - 每个指针都会递增直到结束,最大 n 比较
= 分区
< 和 >(而不是 < 和 >=)的 QS 不会分成 3 - 如果你正在做< 和>,=s 必须去某个地方...
tobyodavies 提供了正确的解决方案。当所有键都相等时,它确实会处理这种情况并在 O(n) 时间内完成。 这和我们在荷兰国旗问题中所做的划分是一样的
http://en.wikipedia.org/wiki/Dutch_national_flag_problem
分享普林斯顿的代码
http://algs4.cs.princeton.edu/23quicksort/Quick3way.java.html
【讨论】:
如果您实施 2 路分区算法,那么在每一步中,数组都会减半。这是因为当遇到相同的键时,扫描会停止。因此,在每个步骤中,分区元素将位于子数组的中心,从而在每个后续递归调用中将数组减半。现在,这种情况类似于使用 ~N lg N 比较对 N 个元素的数组进行排序的合并排序情况。因此,对于重复键,Quicksort 的传统 2 路分区算法使用 ~N lg N 比较,因此遵循线性方法。
【讨论】:
快速排序代码是使用“分区”和“快速排序”函数完成的。
基本上,有两种实现快速排序的最佳方法。
这两者的区别只是“分区”功能,
1.洛穆托
2.霍尔
使用诸如上述 Lomuto 分区方案的分区算法(即使是选择好的主元值),快速排序对于包含许多重复元素的输入表现出较差的性能。当所有输入元素都相等时,问题就很明显了:在每次递归时,左分区为空(没有输入值小于枢轴),而右分区仅减少了一个元素(枢轴被移除)。因此,Lomuto 分区方案需要二次时间来对相等值的数组进行排序。
因此,使用 Lomuto 分区算法需要 O(n^2) 时间。
通过使用 Hoare 分区算法,我们得到了所有数组元素相等的最佳情况。时间复杂度为 O(n)。
【讨论】: