这是C++算法基础-数据结构专栏的第三十二篇文章,专栏详情请见此处。
引入
今天我们要学习的是堆,这个数据结构可以在存储的时候维护一些数据的大小关系,它的重要用途是堆排序。
下面我们就来讲堆的实现。
定义
堆,一般指二叉堆,它是一棵完全二叉树,分为大根堆和小根堆。小根堆中,父亲节点的值不大于儿子节点的值,根节点的值最小,大根堆反之。堆重要用途是堆排序。
过程
这里我们用讲解小根堆来理解堆,大根堆的过程和代码可通过小根堆得出,不再详细讲解。
存储
对于堆的存储,我们可以用一个数组存储,若父亲节点编号为
,则它的左右儿子节点分别为
和
。此外,还需要一个变量
表示根数组的大小。
这种用一维数组存储,通过数组下标辨识父亲节点和儿子节点的方式是存储树很常见也很方便的方式。
从定义中我们得知,小根堆中,不合法的情况是父亲节点的值大于儿子节点的值,对于此种情况,我们可以用两种方式处理:一是将父亲节点下移,二是将儿子节点上移。这两者虽然看上去区别不大,但侧重点不同。
移动函数:down()和up()
父亲节点下移我们用一个函数。首先用一个变量
存储三个结点(该父亲节点和它的左右儿子节点)中存在的最小的结点的下标,初始化为当前结点
;然后,若左右儿子节点存在并且小于
,就依次更新
,最后
就是三个结点中拥有最小值的结点下标;接着,若
,则说明需要交换数值,交换后父亲节点就成功下移了;最后,交换数值后,
存储原来
的值,可能需要调整,所以继续调用
函数,直到它比左右儿子节点都小。
儿子节点上移我们用一个函数。
函数比
函数实现简单,因为父亲节点最多有两个儿子节点,但儿子节点只有一个父亲节点,所以我们只需判断,若当前节点不是根节点且当前节点的父亲节点大于它,则交换数值;最后,交换后可能需要调整,所以继续调用函数
。
有了上面两个函数,操作堆就方便多了。堆的常见操作有五个:插入数据,求最小值,删除最小值,删除元素和修改元素。我们依次讲解上述操作。
插入数据
插入的时候,我们一般在数组最后插入数据,然后将其上移。
求最小值
由于小根堆中,根节点最小的缘故,只需输出根节点即可。
删除最小值
删除最小值并不是把根节点删除,而是需要先把根节点和位置最后的节点交换,再将,最后将其下移。
删除元素
和删除最小值差不多,都是先把该节点和位置最后的节点交换,再将,唯一不同的是需要
和
都调用,因为我们不知道它与其父亲节点和儿子节点的关系。
修改元素
重新赋值,然后和
。
代码
下面给出堆的实现代码:
注:operation 操作
int h[N],siz;
void down(int u){
int t=u;
if(u*2<=siz&&h[u*2]<h[t])
t=u*2;
if(u*2+1<=siz&&h[u*2+1]<h[t])
t=u*2+1;
if(u!=t){
swap(u,t);
down(t);
}
}
void up(int u){
while(u/2&&h[u]<h[u/2]){
swap(u,u/2);
u>>=1;
}
}
//operation 1
h[++siz]=x;
up(siz);
//operation 2
cout<<h[1];
//operation 3
h[1]=h[siz];
siz--;
down(1);
//operation 4
h[k]=h[siz];
siz--;
up(k);
down(k);
//operation 5
h[k]=x;
up(k);
down(k);
代码解释
第一行是存储堆的数组和该数组的长度;down()函数的作用是将父亲节点下移,up()函数的作用是将儿子节点上移;第一个操作的作用是插入数据;第二个操作的作用是求最小值;第三个操作的作用是删除最小值;第四个操作的作用是删除元素;第五个操作的作用是修改元素。
上一篇-并查集的实现(带权版)与食物链(NOI2001)问题 C++算法基础专栏文章 下一篇-堆排序的实现
每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容
点个赞,关注一下呗~