堆排序中首先需要做的就是建堆,广为人知的是建堆复杂度才O(n),不过很少有人去了解过这个复杂度的证明过程,因为不是那么直观地可以一眼就看出来。本文不讲堆排序,只单纯讲建堆过程。
建堆代码
欲了解复杂度的计算过程,必先看懂建堆代码。先看这个建堆过程
// 将arr[n]向上调整至合适位置
void AdjustHeap(vector<int> &arr, int n)
{
if(n<=0) return ;
if(arr[(n-1)/2] > arr[n]) { //与父结点比较
swap(arr[(n-1)/2], arr[n]);
AdjustHeap(arr, (n-1)/2); //递归调整
}
}
// 小根堆
void BuildHeap(vector<int> &arr)
{
for(int i=1; i<arr.size(); i++) {
AdjustHeap(arr, i);
}
}
因为不讲堆排序,所以这里用的是向上调整即可,更加直观易懂。相信这么简单的代码你应该能看懂它的原理。
复杂度计算
从直观上看,AdjustHeap()
的调用深度最多为logn层,故复杂度上限为O(logn)。而BuildHead()
中的循环为n-1
次,故它的复杂度为O(nlogn),但这不是它的实际平均复杂度,而是一个估算的上界,它很可能永远达不到这个上界。下面来分析一下。
AdjustHeap(arr, 1)
比较次数最多为1
次,最少为1次。
AdjustHeap(arr, 2)
比较次数最多为1
次,最少为1次。
AdjustHeap(arr, 3)
比较次数最多为2
次,最少为1次。
AdjustHeap(arr, 4)
比较次数最多为2
次,最少为1次。
AdjustHeap(arr, 5)
比较次数最多为2
次,最少为1次。
AdjustHeap(arr, 6)
比较次数最多为2
次,最少为1次。
AdjustHeap(arr, 7)
比较次数最多为3
次,最少为1次。
...
AdjustHeap(arr, n-1)
比较次数最多为logn
次,最少为1次。
按最坏情况来算,将这些比较次数累加起来就是建堆的时间复杂度,显然最佳情况是O(n)。而最坏情况的比较次数复杂一些,为了方便计算,假设n是2的k次幂,则k = logn
。
\({1 + 1 + 2 + 2 + 2 + 2 + 3 + 3 + ... + k}\)
= \({1*2^1+2*2^2+3*2^3}+\cdots + {k*2^k}\)
=