堆的结构和性质
堆是一种完全二叉树,满足以下条件:
- 完全二叉树:所有层次除了最后一层都被完全填满,最后一层从左到右填充。
- 堆性质
- 最大堆:任意父节点的值 ≥ 子节点的值。
- 最小堆:任意父节点的值 ≤ 子节点的值。
堆在数组中的存储方式
存储方式:用一维数组:下标为1的点是根节点,下标为x的节点左子节点下标是2x,节点x的左子节点下标是2x+1 在数组中存储堆时,节点之间的关系为:
- 父节点的索引:
- 左子节点的索引:
- 右子节点的索引:
例如,对于数组:
Index: 1 2 3 4 5 6 7
Values: 5 3 8 2 1 6 4
- 节点
1是根节点,子节点是2和3。 - 节点
2的子节点是4和5。 - 节点
3的子节点是6和7。
相关操作
- 插入一个数
堆的大小加一,并将该元素插入至堆尾,进行上浮操作维护堆的性质 - 求集合当中最小值
堆顶元素即集合当中最小值 - 删除最小值
由于数组删除元素比较麻烦,删除操作可以等价于第一步用堆底的元素覆盖根节点,第二步删除尾节点,第三步下沉 - 删除任意一个元素
删除任意一个元素操作可以类比删除最小值,第一步用堆底的元素覆盖任意节点,第二步删除尾节点,第三步下沉上升(注意这里只会执行一次)维护堆的性质 - 修改任意一个元素
修改任意一个节点的值,然后下沉上升
代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int heap[N], siz;
void down(int x) {
int t = x;
if (2 * x <= siz && heap[t] > heap[2 * x]) t = 2 * x;
if (2 * x + 1 <= siz && heap[t] > heap[2 * x + 1]) t = 2 * x + 1;
if (t != x) {
swap(heap[t], heap[x]);
down(t);
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> heap[i];
siz = n;
// 建堆
for (int i = n / 2; i; i --) down(i);
while (m --) {
// 输出最小值
printf("%d ", heap[1]);
// 删除堆顶元素并更新堆
heap[1] = heap[siz];
siz --;
down(1);
}
return 0;
}
down函数解释
- 假设当前节点,下标为x的节点是最小节点,用t记录子树中最小节点的下标
- 检查左子节点是否存在:2 * x <= siz
如果左子节点存在,并且其值小于当前节点的值:heap[t] > heap[2 * x],更新t为左子节点的索引。 - 检查右子节点是否存在2 * x + 1 <= siz
如果右子节点存在,并且其值小于当前最小值:heap[t] > heap[2 * x + 1],更新t为右子节点的索引。 - 如果当前节点不是最小值x != t,则交换当前节点与最小节点的值 交换后,递归调用down(t),继续调整子树