堆的定义与基本操作
堆是一棵完全二叉树,树中每个结点的值都不小于(或者不大于)其左右孩子结点的值
- 大顶堆:父亲结点的值大于或等于左右孩子的值
- 小顶堆:父亲结点的值小于或等于左右孩子的值
对于给定初始序列,如何建堆?
给定数组 [85, 55, 82, 57, 68, 92, 99, 98, 66, 56]
建堆过程如下
![1Snipaste_2020-04-15_18-47-34](https://tvax1.sinaimg.cn/large/005tpOh1gy1gdun9wwihij30m80egq64.jpg)
![2Snipaste_2020-04-15_18-47-51](https://tva1.sinaimg.cn/large/005tpOh1gy1gduna5g3gvj30ml0dx76t.jpg)
![3Snipaste_2020-04-15_18-48-09](https://tva4.sinaimg.cn/large/005tpOh1gy1gdunadzekpj30lp0cujtc.jpg)
![Snipaste_2020-04-15_18-54-43](https://tvax3.sinaimg.cn/large/005tpOh1gy1gdunfrt6rfj30l80g0n0d.jpg)
![Snipaste_2020-04-15_18-55-16](https://tvax4.sinaimg.cn/large/005tpOh1gy1gdunhoymooj30g30iwdip.jpg)
![Snipaste_2020-04-15_18-55-46](https://tva2.sinaimg.cn/large/005tpOh1gy1gduo1reoksj30db088my3.jpg)
建堆
利用数组来存储完全二叉树,这样结点就按层序存储与数组中,其中第一个结点将存储于数组的1号位,并且数组i号位
表示的结点的左孩子就是2i号位
,右孩子是2i+1号位
const int maxn = 100;
// heap为堆,n为元素个数
int heap[maxn], n = 10;
回顾之前的建堆过程会发现,每次调整都是把结点从上往下的调整。针对这种向下调整,调整方法是这样的:总是将当前结点V与它的左右孩子比较(如果有的话),假如孩子中存在权值比结点V的权值大的,就将其中权值最大的那个孩子结点与结点V交换;交换完毕后继续让结点V和孩子比较,直到结点V的孩子的权值都比结点V的权值小或是结点V不存在孩子结点。时间复杂度为O(logn)
// 对heap数组在[low, high]范围进行向下调整
// 其中low为欲调整结点的数组下标,high一般为堆的最后一个元素的数组下标
void downAdjust(int low, int high){
int i = low, j = 2 * i; // i为欲调整的结点,j为其左孩子
while(j <= high){ // 存在孩子结点
if(j + 1 <= high && heap[j+1] > heap[j]){// 当右孩子存在且右孩子大于左孩子
j = j + 1; // 此时j为右孩子
}
if(heap[j] > heap[i]){ // 孩子权值最大结点大于父亲结点
swap(heap[i], heap[j]); // 交换最大权值孩子与父亲结点
i = j; // 保持i为欲调整结点,j为i的左孩子
j = i * 2;
}else{
break; // 孩子的权值均比欲调整结点i小,调整结束
}
}
}
假设序列中元素的个数为n,由于完全二叉树的叶子结点个数为(left lceil frac{n}{2}
ight
ceil),因此数组下标在([1, left lfloor frac{n}{2}
ight
floor])范围内的结点都是非叶子结点,于是从(left lfloor frac{n}{2}
ight
floor)位置开始倒着枚举结点,对每个遍历到的结点i进行[i, n]
范围的调整。倒着枚举是因为每次调整完一个结点后,当前子树中权值最大的结点就会处在根结点的位置,这样当遍历到其父亲结点是,就可以直接使用这个结果,保证每个结点都是以其为根结点的子树中的权值最大的结点
建堆的时间复杂度为O(n)
void createHeap(){
for(int i = n / 2; i >= 1; i--){
downAdjust(i, n);
}
}
删除堆顶
只要最后一个元素覆盖堆顶元素,然后对根结点进行调整即可,时间复杂度为O(logn)
void deleteTop(){
heap[1] = heap[n];
n--;
downAdjust(1, n);
}
添加一个元素
把想要添加的元素放在数组最后,然后进行向上调整操作。向上调整总是把欲调整结点与父亲结点比较,如果权值比父亲结点大,那么就交换其与父亲结点,这样反复比较,直到到达堆顶或是父亲结点的权值较大为止。向上调整的时间复杂度为O(logn)
void upAdjust(int low, int high){
int i = high, j = i / 2; // i为待调整结点,j为其父亲结点
while(j >= low){
if(heap[j] < heap[i]){
swap(heap[i], heap[j]);
i = j;
j = i / 2;
}else{
break;
}
}
}
所以,添加元素代码:
void insert(int x){
heap[++n] = x; // 让元素个数为1,然后将数组末位赋值为x
upAdjust(1, n); // 向上调整新加入的结点n
}
堆排序
堆排序是指使用堆结构对一个序列进行排序
void heapSort(){
createHeap(); // 建堆
for(int i = n; i > 1; i--){ // 倒着枚举,直到堆中只有一个元素
swap(heap[i], heap[1]); // 交换heap[i]与堆顶
downAdjust(1, i - 1); // 调整堆顶
}
}
![Snipaste_2020-04-15_22-05-44](https://tvax4.sinaimg.cn/large/005tpOh1gy1gdusyj35mmj30ik0nqgpv.jpg)
![Snipaste_2020-04-15_22-06-18](https://tvax4.sinaimg.cn/large/005tpOh1gy1gdusz4x5bfj30j90ka77c.jpg)
![Snipaste_2020-04-15_22-07-35](https://tvax2.sinaimg.cn/large/005tpOh1gy1gdut0heaarj30ij0cdaby.jpg)
Write by Gqq