堆排序的第一步:建堆过程时间度是O(n)。
以升序为例,我们知道对于堆排序的第一步,是要先建立一个大顶堆。代码如下:
void adjustHeap(vector<int>& arr, int parent, int length){
int temp = arr[parent];//寻找temp最终应该放的位置
while(parent*2+1<length){
int left = parent*2+1;
if((left+1)<length&&arr[left]<arr[left+1])
left = left+1;
if(arr[left]>temp){
arr[parent] = arr[left];
//更新parent
parent = left;
}else break;
}
arr[parent] = temp;
}
for(int i=arr.size()/2-1;i>=0;i--){
adjustHeap(arr, i, arr.size());
}
给定一个固定的序列,将其从下到上建成一个大顶堆的过程如下:
假定堆以数组实现,建堆的过程,就是不断将一个完全二叉树进行调整使得该完全二叉树满足: 每一棵子树的根节点总是比其左子树和右子树的值大。这个调整过程主要是: 从第一个非叶子结点开始不断地从下往上调整(如果假定下标从0开始,则第一个非叶子结点的下标就是arr.length/2-1
),故外循环是O(n)。针对每次调整,调整函数ajustHeap之所以里面加一个while, 主要是因为,针对当前的根节点parent,当和其左孩子和右孩子交换之后,有可能破坏了其左子树或者右孩子的堆结构,因此还要往下继续调整,也就是找到最初的arr[parent]最终应该放置的位置。由于该while循环中,针对parent结点,它在往下调整找合适位置的时候,总是找某一半的子树(每一次要么往左子树走,要么是往右子树走),所以针对某一次调整,其调整的时间复杂度是树高: O(logn)。所以直观上从整体上来看,建堆的时间复杂度是外层的for循环乘内层的O(logn)的复杂度是:O(nlogn)。其实,这是不正确的。构建二叉堆是自下而上的构建,每一层的最大深度总是小于等于树的深度的,因此,该问题是叠加问题,不是递归问题。那么换个方式,假如我们自上而下建立二叉堆,那么插入每个节点都和树的深度有关,并且都是不断的把树折半来实现插入,因此是典型的递归,而非叠加。时间复杂度是O(nlogn)。
下面给出证明过程:
针对一棵完全二叉树,设树高为h, 则当前层元素最多
2
x
−
1
2^{x-1}
2x−1个,x为层数,由于从第一个非叶子结点进行调整,第一层的非叶子结点的层数也就是倒数第二层,因此开始调整的第一层的个数为
2
h
−
2
2^{h-2}
2h−2,对于该层只需要做一次线性比较就可以调整成功。依次往上考虑,对于倒数第二层,其结点的个数为
2
h
−
3
2^{h-3}
2h−3,其需要再往下调整的层数最多为2。故对于第倒数x层
,其元素的个数为
2
h
−
x
2^{h-x}
2h−x, 其往下的比较次数和处理时间复杂度也就是往下的高度减1,即
x
−
1
x-1
x−1。对于第1层,即倒数第h层,有1个元素,往下的调整的层数为
h
−
1
h-1
h−1。设总的时间复杂度为S:
S
=
1
∗
(
h
−
1
)
+
2
∗
(
h
−
2
)
+
4
∗
(
h
−
3
)
+
.
.
.
+
2
h
−
4
∗
3
+
2
h
−
3
∗
2
+
2
h
−
2
∗
1
S = 1* (h-1) + 2 * (h-2) + 4*(h-3) +...+ 2^{h-4} * 3 + 2^{h-3} *2+ 2^{h-2} *1
S=1∗(h−1)+2∗(h−2)+4∗(h−3)+...+2h−4∗3+2h−3∗2+2h−2∗1 (1)
观察上式,属于等差数列和等比数列的乘积的累加和,可以使用裂项错位相减。
将(1)
乘以2得:
2
S
=
2
∗
(
h
−
1
)
+
4
∗
(
h
−
2
)
.
.
.
+
2
h
−
3
∗
3
+
2
h
−
2
∗
2
+
2
h
−
1
∗
1
2S = 2*(h-1) +4*(h-2)...+ 2^{h-3} * 3 + 2^{h-2} *2+ 2^{h-1} *1
2S=2∗(h−1)+4∗(h−2)...+2h−3∗3+2h−2∗2+2h−1∗1 (2)
(2)-(1)
= S =
−
(
h
−
1
)
+
2
+
4
+
.
.
.
+
2
h
−
3
+
2
h
−
2
+
2
h
−
1
-(h-1)+2 + 4 +...+2^{h-3}+2^{h-2} + 2^{h-1}
−(h−1)+2+4+...+2h−3+2h−2+2h−1
后面是一个等比数列,使用等比数列的求和公式
a
1
−
a
n
∗
q
1
−
q
\frac{a_1-a_n * q}{1-q}
1−qa1−an∗q则
S
=
2
h
−
2
−
h
+
1
=
2
h
−
h
−
1
S = 2^h-2-h+1 = 2^h-h-1
S=2h−2−h+1=2h−h−1。将h = logn带入得S = n - logn -1,故最终的时间复杂度为O(n-logn-1) = O(n)。