在分析QS时,每个人总是指“几乎排序”的最坏情况。什么时候可以通过自然输入发生这种情况?
我想出的唯一例子是重新编制索引。
答案 0 :(得分:42)
我认为人们会把Quicksort混淆为基于分区的排序算法,并且“qsort”各种库实现。
我更倾向于将Quicksort算法视为具有可插入的枢轴选择算法,这在分析其行为时非常重要。
如果始终选择第一个元素作为数据透视表,则已经排序的列表是最坏的情况。通常,阵列很可能已经/几乎已经排序,因此这种实现方式相当差。
类似地,选择最后一个元素作为枢轴是不好的,原因相同。
某些实现尝试通过选择中间元素作为数据透视来避免此问题。这对已经/接近排序的数组执行起来不会那么严重,但是仍然可以构造一个输入来利用这个可预测的数据透视选择并使其在二次时间内运行。
因此,您获得了随机枢轴选择算法,但即便如此也不能保证O(N log N)
。
因此开发了其他算法,在选择枢轴之前使用序列中的一些信息。您当然可以扫描整个序列并找到中位数,并将其用作枢轴。这可以保证O(N log N)
,但在实践中当然会变慢。
因此,一些角落被削减,人们设计出3中值算法。当然,后来甚至可以通过所谓的3中位数“杀手”来利用它。
因此,在提出更多“智能”枢轴选择算法时会做出更多尝试,这些算法可以保证O(N log N)
渐近行为仍然足够快,实际上具有不同程度的成功。
实际上,除非指定Quicksort的特定实现,否则最坏情况发生的时间问题是不明确的。如果使用所谓的中位数中心枢轴选择算法,则不存在二次最坏情形。
然而,大多数库实现可能会放弃O(N log N)
保证在一般情况下更快的排序。一些非常古老的实现使用第一个元素作为支点,现在已经很好地理解为差,并且不再是广泛遵循的实践。
答案 1 :(得分:34)
我认为quicksort的最坏情况取决于每一步中枢轴元素的选择。如果枢轴可能是列表中的最小元素或最大元素(例如已排序列表的第一个或最后一个元素),则Quicksort的性能最差。
如果,例如你选择列表的中间元素,已排序的列表没有最差的运行时。
因此,如果您怀疑自己的情况可能是针对quicksort的错误案例,您可以简单地更改您对pivot元素的选择,以使quicksort更好地运行。
注意:我知道,这并没有给出快速排序最坏情况的真实世界场合的更多例子。此示例取决于您正在使用的实现。
答案 2 :(得分:8)
实际的问题是:“这种情况(几乎已经分类)何时能够以自然输入发生?”。
虽然所有答案都涉及“导致最坏情况表现的原因”,但没有一个涉及“导致数据遇到最坏情况性能的原因”。
程序员错误:基本上你要对列表进行两次排序。通常这是因为列表在代码中的一个位置排序。稍后在另一段代码中,您知道需要对列表进行排序,因此您需要对其进行排序。
使用几乎按时间顺序排列的数据:您的数据通常按时间顺序接收,但偶尔会有一些元素不在适当位置。 (考虑一个多线程环境,将时间戳元素添加到列表中。竞争条件可以使元素按照时间戳的不同顺序添加。)在这种情况下,如果需要排序数据,则必须重新排序-分类。因为无法保证数据的顺序。
将项目添加到列表中:如果您有一个已排序的列表,只需附加一些项目(即不使用二进制插入)。您需要重新排序几乎排序的列表。
来自外部源的数据:如果您从外部源接收数据,则可能无法保证其已排序。所以你自己排序。但是,如果外部源已排序,您将重新排序数据。
自然排序:这与计时数据类似。基本上,您收到的数据的自然顺序可能会被排序。考虑一家保险公司添加汽车注册。如果分配汽车注册的权限以可预测的顺序进行,则较新的汽车可能但不能保证具有更高的注册号。由于您无法保证它已经排序 - 您必须重新排序。
交错数据:如果您从具有重叠键的多个已排序源接收数据,则可以获得类似以下内容的键:1 3 2 5 4 7 6 9 8 11 10 13 12 15 14 17 16 19 18.尽管一半的元素与其邻居无序,但该列表“几乎已经排序”。当然使用在第一个元素上转动的QuickSort将表现出O(n^2)
性能。
因此,鉴于上述所有情况,实际上很容易找到排序几乎排序的数据。这正是为什么最好避免在第一个元素上转动的QuickSort的原因。 polygene提供了有关备用旋转注意事项的一些interesting信息。
作为旁注:通常性能最差的排序算法之一,实际上与“几乎排序”的数据相当好。在上面的交错数据中,冒泡排序只需要9次交换操作。它的表现实际上是
O(n)
。
答案 3 :(得分:7)
答案 4 :(得分:3)
快速排序的最坏情况:
答案 5 :(得分:1)
快速最坏的情况取决于选择枢轴元素。所以问题只发生在 1)数组已按相同顺序排序。 2)数组已按相反顺序排序。 3)所有元素相同(案例1和2的特例)