Introsort(快速排序+堆排序)实现和复杂性
Introsort (quicksort + heapsort) implementation and complexity
我读到C++使用introsort(内省排序)作为其内置的std::sort,它从quicksort开始,当达到深度限制时切换到heapsort。
我还读到深度限制应该是2*log(2,N)。
这个值纯粹是实验性的吗?或者这背后有数学理论吗?
如果你有一个区间(范围或数组),在得到一个空(或一个元素)区间之前,你必须将区间一分为二的次数是log(2,N)
,这只是一个数学事实,如果你愿意,你可以很容易地计算出来。如果快速排序一切顺利,出于同样的原因,它应该递归log(2,N)
次(在每个递归级别,它必须处理区间的所有值,这会导致整个算法的O(N*log(2,N))
复杂性)。问题是,快速排序可能需要更多的递归(如果它在选择枢轴值时不断变得"不走运",这意味着它不会将间隔一分为二,而是以不平衡的方式)。更糟糕的是,quicksort最终可能会重复出现N次,这对于生产质量实现来说绝对是不可接受的。
在2*log(2,N)
切换到堆排序通常是一个很好的启发式方法,可以检测到太多的递归。
从技术上讲,您可以根据堆排序与快速排序的经验性能来确定什么限制是最好的。但这样的测试高度依赖于应用程序(你在排序什么?你如何比较元素?元素交换有多便宜?等等)。因此,大多数一刀切的实现,如std::sort
,只会选择一个合理的限制,如2*log(2,N)
。
@Mikael Persson关于深度限制为何为2*log(2,N)的说法部分正确。这不仅仅是一个好的启发式,或者一个合理的限制。
事实上,正如你可能已经猜到的(从第二个问题中描述的),这有一个重要的数学原因:在波浪号表示法(搜索波浪号表示法)中,quicksort平均进行~2*log(2,N)比较。在big oh表示法中,这相当于O(N*log(2,N))。
这就是为什么当递归深度超过2*log(2,N)时,introsort切换到heapsort(具有渐近O(N*log(2))复杂性)。你可以把它看作是一种不常见的事情,很可能意味着单凭枢轴选择和快速排序出现问题就会导致O(N^2)的复杂性。
你可以在这里找到一个简短的数学证明,快速排序的平均比较次数(幻灯片21)。
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 具有未知值时的时间复杂性
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 关联容器的下界复杂性:成员函数与非成员函数
- 在C 复杂性和实现中选择位向量
- 堆排序 - 实现的复杂性
- nth_element实现的复杂性
- Introsort(快速排序+堆排序)实现和复杂性
- 在 LLVM libc++ 中找到的 string::find 中实现的算法(及其复杂性)是什么?
- 我可以实现线性(或接近)复杂性连接字符串没有分配
- 如何使用CUDA实现2-for粒子交互循环,以及由此产生的复杂性是什么