C ++ 11标准保证std::sort
在最坏的情况下O(n logn) complexity 。这与C ++ 98/03中的 average-case 保证不同,其中std::sort
可以使用Quicksort实现(可能与小n的插入排序结合使用),其中包含O( n ^ 2)在最坏的情况下(对于某些特定的输入,例如排序的输入)。
不同STL库中的std::sort
实现是否有任何变化? C ++ 11的std::sort
如何在不同的STL中实现?
答案 0 :(得分:24)
问题是, STL 如何说std::sort
最坏情况是 O(N log(N)),即使它本质上是<强>快速排序即可。 STL的排序是 IntroSort 。 IntroSort本质上是一个QuickSort,引入的差异改变了最坏的情况复杂性。
您选择的分区中,存在QuickSort将在 O(N ^ 2)上运行的序列。您选择的分区只会降低最坏情况发生的概率。 (Random Pivot Selection,三人中位数,etc.)
编辑:感谢@ maxim1000的更正。具有数据透视选择算法Median of Medians的Quicksort具有 O(N log(N))最差情况的复杂性,但由于其引入的开销,它在实践中未被使用。它显示了良好的选择算法,从理论上可以通过枢轴选择改变最坏情况的复杂性。
IntroSort限制了QuickSort的分支。这是最重要的一点,该限制为 2 * (log N)。达到限制时,IntroSort可以使用任何具有最差情况复杂度O(N log(N))的排序算法。
当我们有O(log N)子问题时,分支停止。我们可以解决每个子问题O(n log n)。 (小写字母n代表子问题大小)。
现在,(n log n)的总和是我们最糟糕的情况。
对于最糟糕的QuickSort案例;假设我们已经有一个已排序的数组,我们总是选择此数组中的第一个元素作为数据透视表。在每次迭代中,我们只消除第一个元素。如果我们这样走到最后,那么显然会 O(N ^ 2)。使用IntroSort我们停止QuickSort,当我们达到深度 log(N)时,我们使用 HeapSort 来保留剩余的未排序数组。
16 -> 1 /**N**/
\
> 15 -> 1 /**N - 1**/
\
> 14 -> 1 /**N - 2**/
\
> 13 -> 1 /**N - log(N)**/
\
> 12 /**(HeapSort Now) (N - log(N)) log (N - log(N))**/
总结一下;
在分支停止之前,N + (N - 1) + ... + (N - log(N))
操作已完成。我们可以简单地说N + (N - 1) + ... + (N - log(N)) < N log(N)
。
HeapSort部分是(N - log(N)) log(N - log(N)) < N log(N)
整体复杂性< 2 N log(N)
。
由于常数可以省略, IntroSort 的最坏情况复杂度为 O(N log(N))。
已添加信息 GCC STL实施源代码为here。 Sort
功能位于 5461 。
更正: * Microsoft .NET * sort自2012年起,实施就是IntroSort。相关信息是here。
答案 1 :(得分:19)
浏览libstdc++和libc++的在线资源,可以看到两个图书馆都使用了来自intro-sort主循环的众所周知的排序算法:
对于std::sort
,insertion_sort
有一个辅助例程(一个O(N^2)
算法,但具有良好的缩放常数以使其对小序列具有竞争力),加上一些特殊的子套管 - 0,1,2和3个元素的序列。
对于std::partial_sort
,两个库都使用heap_sort
版本(通常为O(N log N)
),因为该方法具有良好的不变性,可以保留已排序的子序列(通常具有较大的子序列)缩放常数使其对于完全排序更加昂贵)。
对于std::nth_element
,selection_sort
有一个辅助例程(同样是一个具有良好sclaing常数的O(N ^ 2)算法,以使其与小序列竞争)。对于常规排序insertion_sort
通常支配selection_sort
,但对于nth_element
,具有最小元素的不变量与selection_sort
的行为完全匹配。