什么是堆?
堆可以定义为一棵完全二叉树,且树的节点包含键值,用数组来实现的数据结构。
完全二叉树是指除了最底层,其他每一层都被完全填充,并且在最底层从左到右依次填充。
堆的类型
堆被分为两种类型:
大根堆:对于每个节点 i,父节点的值始终大于或等于其子节点的值。
小根堆:对于每个节点 i,父节点的值始终小于或等于其子节点的值。
这就保证了堆的第一个元素对于大根堆来说一定是所有数的最大值,对于小根堆来说一定是最小值
使得查询最大值(或最小值)的时间复杂度为O(1)。
堆的作用
1.插入一个值
2.输出最小值(小根堆)或最大值(大根堆)
3.删除最小值(小根堆)或最大值(大根堆)
4.删除任意一个数
5.修改任意一个数
如何建堆
对于一开始一堆散乱无序的数据来说,是还不形成堆的,所以要建堆。要建堆的话,就要掌握向下调整的操作了。
向下调整操作是指:如果是小根堆,当父亲节点比最小的孩子节点大时,应该要将父亲节点的值和最小的孩子节点进行交换,一直交换到父亲节点的值小于孩子节点为止(小根堆)
我们拿一组数据模拟一下该过程(在建小根堆的情况下,向下调整的前提是该父亲节点的子树都是小根堆)
1.对于4的节点来说,左子树和右子树都满足了小根堆(父节点大于两个孩子节点),但4大于2,找到最小的孩子节点也就是2,然后对其进行交换,进入第二个过程。
2.4的节点仍然没有满足大于两个孩子节点的条件,所以继续调整,到了最终状态,满足小根堆的条件
观察这张图,这既不符合小根堆,也不符合大根堆,如果我想让这些数据变成符合小根堆的性质,该如何操作呢。
我们知道堆是数组来实现的,同时堆又是完全二叉树,那就可以用数组下标关系来表示节点之间的父子关系
父亲节点*2=左孩子节点
父亲节点*2+1=右孩子节点(一定要下数据从下标为1开始,不然无法正常查找父子关系)
上面可知,如果要建堆,就要利用向下调整的操作,但无序的数据又不像上面的例子一样,除了根节点以外的左子树和右子树都形成了小根堆。应该要如何操作呢?从上到下的进行多次向下调整不行,那么我们可以先对最后一个父亲节点进行向下调整。
最后一个父亲节点=数据总数n/2
这组数据来说最后一个父亲节点是3=6(数据总数)/2,也就是23的位置。
接下来画图分析从最后一个父亲节点向下调整的过程。
从最后一个父母节点往后依次调整,最终将会形成小根堆。
堆的实现
接下来用代码实现建堆,实现一个堆排序的过程吧,这里使用递归和非递归两种方式进行向下调整操作
#include<iostream>
using namespace std;
const int N=1e5+10;
int h[N];//实现堆的数组
int nums;//记录堆中元素个数
int m;//操作次数
void down1(int u){
//递归做法
int t=u;
if(u*2<=nums&&h[t]>h[u*2]) t=u*2;
//如果左孩子存在,并且父亲节点存储值比左孩子存储值大,t记录左孩子节点
if(u*2+1<=nums&&h[t]>h[u*2+1]) t=u*2+1;
//如果右孩子存在,并且上面t节点的存储值比右孩子大,说明找到更小的值了,用t记录该节点
if(u!=t){
//说明有孩子并且是存储值最小的孩子节点
swap(