Introsort(快速排序+堆排序)实现和复杂性

Introsort (quicksort + heapsort) implementation and complexity

本文关键字:实现 复杂性 堆排序 快速排序 Introsort      更新时间:2023-10-16

我读到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)。