为什么不通过插入O(n)来构建二进制堆的时间复杂度?

时间:2013-01-20 05:31:20

标签: algorithm data-structures complexity-theory binary-heap

背景

According to Wikipedia以及我发现的其他来源,通过从空二进制堆开始并将 n 元素插入到 n 元素中构建二进制堆它是O( n log n ),因为二进制堆插入是O(log n )而你正在做 n 次。我们称之为插入算法。

它还提供了一种替代方法,您可以从中间元素开始并以第一个元素结束,向下/向下/向下/向下/向下堆积/向下向下/向下压缩元素的第一个/上半部分,并且这是O( n ),这是一个更好的复杂性。这种复杂性的证明取决于每个元素的接收器复杂性取决于它在二进制堆中的高度的洞察力:如果它接近底部,它将很小,可能为零;如果它靠近顶部,它可能很大,可能是log n 。关键是对于此过程中沉没的每个元素,复杂性不是log n ,因此整体复杂性远小于O( n log n < / em>),实际上是O( n )。我们称之为 sink 算法。

问题

为什么插入算法的复杂性与sink算法的复杂度不同,原因相同?

考虑插入算法中前几个元素的实际工作。第一次插入的成本不是log n ,它是零,因为二进制堆是空的!第二次插入的成本最差的是一次交换,第四次的成本最差是两次交换,依此类推。插入元素的实际复杂性取决于二进制堆的当前深度,因此大多数插入的复杂性小于O(log n )。插入成本甚至在技术上达不到O(log n ),直到 所有 n 元素插入之后[它是O(log(< em> n - 1))为最后一个元素]!

这些节省听起来就像接收器算法所节省的成本一样,那么为什么两种算法的计算结果不一样呢?

4 个答案:

答案 0 :(得分:5)

实际上,当n = 2 ^ x - 1(最低级别已满)时,n / 2个元素可能需要插入算法中的log(n)交换(成为叶子节点)。因此,您只需要(n / 2)(log(n))交换叶子,这已经使它成为O(nlogn)。

在另一个算法中,只有一个元素需要log(n)交换,2个需要log(n)-1个交换,4个需要log(n)-2交换等。维基百科显示了一个证明它导致了一个系列收敛于常数而不是对数。

答案 1 :(得分:1)

虽然log(n-1)确实小于log(n),但它并不足以产生差异。

数学上:插入第i个元素的最坏情况成本是ceil(log i)。因此,插入元素1到n的最坏情况成本是sum(i = 1..n,ceil(log i))&gt; sum(i = 1..n,log i)= log 1 + log 1 + ... + log n = log(1×2×...×n)= log n! = O(n log n)。

答案 2 :(得分:1)

直觉是接收器算法只移动一些东西(堆/树顶部的小层中的那些)距离log(n),而插入算法移动很多东西(那些在大层中的东西)堆的底部)距离log(n)。

为什么接收器算法的直觉可以使得插入算法也满足额外的(不错的)要求:如果我们在任何点停止插入,则部分形成的堆具有是(并且是)有效的堆。对于接收器算法,我们得到的只是堆的奇怪的畸形底部。有点像松树,顶部被切掉。

另外,总结和等等等等。最好是逐渐考虑插入一些任意大小的n元素的最后 half 时会发生什么。

答案 3 :(得分:0)

昨天遇到了同样的问题。我试着拿出某种形式的证据来满足自己。这有什么意义吗?

如果从底部开始插入,叶子将有恒定的时间插入 - 只需将其复制到数组中。

叶子以上水平的最坏情况运行时间是:

K *(名词 / <子> 2 ħ )* H

其中h是高度(叶子为0,顶部为log(n))k是常数(仅用于良好测量)。所以( n / 2 h )是每个级别的节点数,h是“沉没”的最大数量。每个插入操作

有log(n)级别, 因此,总运行时间将是

h从1到log(n)的总和:n * k *( h / 2 h

哪个是k * n * SUM h = [1,log(n)] :( h / 2 h

总和是一个简单的算术几何级数,它是2。 所以你得到k * n * 2的运行时间,即O(n)

每个级别的运行时间并不是我所说的严格,但是它严格要小于那个。任何陷阱?