堆 其实就是一棵完全二叉树(见下图)在数组中的实现
堆分为大根堆和小根堆
大根堆:1.根节点最大 2.子节点必须大于等于父节点
小跟堆:1.根节点最小 2.子节点必须小于等于父节点
在数组中存储这种结构时,总是习惯将根节点存储在数组下标为1的位置
每一个下标为k的节点的父节点的下标就是k/2
每一个下标为k的节点的子节点的下标就是k*2和 k * 2+1
在对堆进行操作(插入或删除了一个元素)的时候,有时会打破堆的有序性,使得子节点和父节点之间的有序性被打破,这时候就需要重新恢复堆的有序性。
恢复堆的有序性需要两个核心的方法
一、上浮(以大根堆为例)
void swim(int x)//x为要进行上浮操作的元素在数组中的下标
{
//当发现这个元素比它的父节点要大的时候,就说明堆的有序性已经被打破了
//此时就需要将这个节点与它的父节点的值进行交换
//然后重复此操作,直到无法上浮为止
while(x>1&&a[x]>a[x/2])
{
exch(x,x/2);
x/=2;
}
}
二、下沉 (以大根堆为例)
void sink(int x)
{
//因为每一个节点最多可以有两个子节点,因此下沉操作显得比上浮操作更加繁琐
//x*2<=n和x*2+1<=n这个判断不可缺少,并且需要放在逻辑表达式的前面
//因为只有当这个下标在合法的范围内,才能根据这个下标去访问元素
//这里的两个逻辑表达式是说当前节点小于任何一个子节点的时候,就进行下沉
while(x*2<=n&&a[x]<a[x*2]||x*2+1<=n&&a[x]<a[x*2+1])
{
//能进入到这里,说明上面两个逻辑表达式至少有一个满足
//现在考虑这样一种情况:x*2下标是合法的
//但是x*2+1就超出了此时堆的最大下标,所以需要加一个判断
if(x*2+1<=n)
{
//如果两个子节点都存在,那么就取两个之中大的那一个与当前节点交换
//因为当进入到这里面的时候,其实是不知道当前节点到底是比哪一个节点小
//所以选择相对大的那个节点
if(a[x*2]>a[x*2+1]){
exch(x,x*2);
x*=2;
}else
{
exch(x,x*2+1);
x=x*2+1;
}
}else
{
exch(x,x*2);
x*=2;
}
}
}
三、插入元素(从0开始构建一个堆)
假设要从键盘输入N个数,然后以这N个数构建一个大根堆
构建堆的基本思想就是将要插入的元素插入到数组的末尾
然后对这个元素进行上浮操作
代码如下
//n用来表示堆的最大下标
int N,n=0,a[10];
cin>>N;
int t;
while(N--)
{
n++;
cin>>a[n];
swim(n);
{
四、删除根节点
以大根堆为例,删除根节点就是删除最大元素
基本思想是将根节点与数组最后一个元素进行交换,然后对交换后的根节点实施下沉操作
void exch(int i,int j)
{
int t=a[i];
a[i]=a[j];
a[j]=t;
}
exch(1,n);
n--;
sink(1)