堆是一种拥有弱排序性质的数据结构。
堆是一种树,其中每棵子树的根节点都是这棵树中最小或最大的点,若是最小点,则称为最小堆;反之则称为最大堆。
处理堆的一组算法主要有插入,查询,删除,建堆,改变某个结点的值等。
二叉堆
结构
二叉堆是一种拥有堆性质的完全二叉树。下图展示了一个典型的二叉堆,节点中的数表示权值。
算法
在介绍二叉堆的算法前,需要先介绍两种核心操作:向上调整和向下调整。
向上调整与向下调整
原理
不满足条件时将叶节点与其根节点互换,实现叶节点的向上调整。向下调整正好相反。
注意
- 这两种算法传入的分别是要调整的叶节点和根节点。
- 在使用数组存储堆的时候,我们将下标为1的结点设为根节点,而下标为0的点可以设置为一个极小或极大的数,以防止在调整位置的时候不小心访问到负数下标,这种方法称为设置哨兵。
代码
void up(int x)//向上调整
{
while (x > 1&&heap[x] < heap[x / 2]) {//heap指表示堆的树
swap(heap[x], heap[x / 2]);
x /= 2;
}
}
void down(int x)//向下调整
{
while (x * 2 <= n) {
int t = x * 2;
if (t < n && heap[t]>heap[t + 1]) t++;
if (heap[x] >= heap[t]) {
swap(heap[x], heap[t]);
x = t;
}
}
}
插入
插入指的是将一个已知权值的点插入树中。
原理
先将点连到最右下角的叶节点上,若发现连上点后不满足堆的性质,则将该点与其根节点交换;若仍不满足则继续交换,直到满足为止。
图示
-
将点2.5连到点4上(图中的黄色结点和蓝色结点分别是连接后查看是否要调换的两个结点)
-
发现连上后不满足堆的性质,于是将4与2.5调换
-
此时满足堆的性质,插入操作完成(如果发现还不满足则要继续向上调整)
代码
void insert(int i)
{//i是插入的结点权值,n是树的结点总数
int x = ++n;
heap[x] = i;
up(n);
}
查询
原理
堆中的查询都很简单,返回最大值/最小值,即堆顶的元素。
代码
int inquire()
{
return heap[1];
}
删除
原理
删除,指的是从堆中删除根节点,但是如果直接删掉就变成了两个堆,难以处理。
我们采取将根节点与最后一个节点交换,删除最后一个结点(根节点),然后将根节点(最后一个节点)向下调整的方法来完成删除操作。
图示
- 原二叉堆(黄色和蓝色分别是两个要交换的点)
- 交换根节点和最后一个结点并将根节点删除
- 发现不满足堆的性质,于是向下调整
- 再次调整,满足堆的性质,调整完成
代码
void delete1(int i)
{
swap(heap[1], heap[n]);
n--;
down(1);
}
建堆
给定一个无序数组,建成一个二叉堆。有从根结点向上调整和从叶结点向下调整两个方法。
向上调整
从根结点向下一层一层遍历结点,不断向上调整即可。
代码
void build_heap_up()
{
for (int i = 1; i <= n; i++)up(i);
}
向下调整
从叶节点向上遍历结点,不断向下调整
代码
void build_heap_down()
{
for (int i = n; i >=1; i--)down(i);
}
改变某个结点的值
改变结点的值之后,要向上或向下调整。
代码
void change_node(int i, int a, bool x)//i是要改变的结点,a是增加或减小的值,x为0时表示减小,为1时表示增加
{
if (x == 0)i -= a;
else {
i += a;
}
up(i);
down(i);
}