有人能解释一下构建堆的复杂性吗?

将项插入到堆中是O(logn),并且插入被重复n/2次(剩余的是叶子,不能违反堆属性)。所以,我认为这意味着复杂性应该是O(n log n)。

换言之,对于我们“heapify”的每个项目,它有可能必须为堆的每个级别(即logn级别)过滤(即筛选)一次。

我错过了什么?


当前回答

简短回答

使用Heapify()构建二进制堆需要O(n)时间。

当我们一个接一个地将元素添加到堆中,并在每一步都满足堆属性(最大堆或最小堆)时,总时间复杂度将为O(nlogn)。因为二进制堆的一般结构是一个完整的二进制树。因此,堆的高度为h=O(logn)。因此,元素在堆中的插入时间等于树的高度,即O(h)=O(logn)。对于n个元素,这将花费O(nlogn)时间。

现在考虑另一种方法。为了简单起见,我假设我们有一个最小堆。因此,每个节点都应该小于其子节点。

在完整的二叉树的骨架中添加所有元素。这需要O(n)时间。现在我们只需要满足min堆属性。由于所有叶元素都没有子元素,因此它们已经满足堆属性。叶元素的总数是ceil(n/2),其中n是树中存在的元素的总数。现在,对于每个内部节点,如果它大于其子节点,则以从下到上的方式将其与最小子节点交换。每个内部节点将花费O(1)时间。注意:我们不会像插入时那样将值交换到根。我们只需交换一次,使该节点上的子树成为一个合适的最小堆。在二进制堆的基于数组的实现中,我们有父级(i)=ceil((i-1)/2),i的子级由2*i+1和2*i+2给出。因此,通过观察,我们可以说数组中的最后一个ceil(n/2)元素将是叶节点。深度越大,节点的索引就越多。我们将对阵列[n/2]、阵列[n/2-1]重复步骤4。。。。。数组[0]。通过这种方式,我们确保我们以自下而上的方式完成这项工作。总的来说,我们最终将维护min堆属性。所有n/2元素的步骤4将花费O(n)时间。

因此,使用这种方法进行堆化的总时间复杂度将为O(n)+O(n)~O(n(n)。

其他回答

直观地:

“复杂性应该是O(nLog n)……对于我们“heapify”的每个项目,它有可能必须为堆的每个级别(即log n级别)过滤一次。”

不完全是。您的逻辑不会产生严格的界限——它会过度估计每个堆的复杂性。如果从下往上构建,插入(heapify)可以比O(log(n))小得多。流程如下:

(步骤1)前n/2个元素位于堆的底行。h=0,因此不需要heapify。

(步骤2)接下来的n/22个元素从底部向上排列在第1行。h=1,将过滤器向下堆1级。

(步骤i)接下来的n/2i元素从底部排i。h=i,将过滤器堆成i级。

(步骤log(n))最后一个n/2log2(n)=1元素从底部向上进入行log(n。h=log(n),heapify向下过滤log(n)级别。

注意:在第一步之后,1/2的元素(n/2)已经在堆中,我们甚至不需要调用heapify一次。此外,请注意,实际上只有一个元素,即根元素,会导致完整的log(n)复杂性。


理论上:

构建大小为N的堆的总步骤N可以用数学公式表示。

在高度i处,我们已经显示(上面)将有n/2i+1个元素需要调用heapify,并且我们知道高度i处的heapify是O(i)。这给出了:

最后求和的解可以通过取众所周知的几何级数方程两边的导数来找到:

最后,将x=1/2代入上式得到2。将其代入第一个方程得出:

因此,步骤总数大小为O(n)

已经有一些很好的答案,但我想补充一点直观的解释

现在,看看图片,有n/2^1个高度为0的绿色节点(此处23/2=12)n/2^2个高度为1的红色节点(此处23/4=6)n/2^3高度为2的蓝色节点(此处23/8=3)n/2^4个紫色节点,高度为3(此处23/16=2)因此高度h有n/2^(h+1)个节点要计算时间复杂度,可以计算每个节点完成的工作量或执行的最大迭代次数现在可以注意到,每个节点都可以执行(atmost)迭代==节点的高度

Green  = n/2^1 * 0 (no iterations since no children)  
red    = n/2^2 * 1 (heapify will perform atmost one swap for each red node)  
blue   = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)  
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)   

因此,对于高度为h的任何节点,所做的最大功为n/2^(h+1)*h

现在完成的总工作量为

->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)  
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

现在对于h的任何值,序列

-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

永远不会超过1因此,构建堆的时间复杂度永远不会超过O(n)

连续插入可通过以下方式描述:

T = O(log(1) + log(2) + .. + log(n)) = O(log(n!))

通过starling近似,n!=~O(n^(n+O(1))),因此T=~O(nlog(n))

希望这有帮助,O(n)的最佳方式是对给定集合使用构建堆算法(排序无关紧要)。

基本上,在构建堆时,只在非叶节点上完成工作。。。所做的工作是减少交换量以满足堆条件。。。换句话说(在最坏的情况下),数量与节点的高度成比例。。。总之,问题的复杂性与所有非叶节点的高度之和成正比。。即(2^h+1-1)-h-1=n--1=O(n)

“构建堆的线性时间界限可以通过计算堆中所有节点的高度之和来显示,这是虚线的最大数量。对于包含N=2^(h+1)–1个节点的高度为h的完美二叉树,节点高度之和为N–h–1。因此它是O(N)。"