前言
⭐️感谢相遇,唤我沈七就好。
⭐️如果能和你一起进步那就太好啦。
定义
堆:是一种满足插入、删除、查询最值的数据结构。
其实是一颗满足堆性质的完全二叉树。
树中的任意一个结点的权值都小于等于父节点,则该二叉树满足"大根堆性质"
大根堆的最大值为根节点的权值。
树中的任意一个结点的权值都大于等于父节点,则该二叉树满足"小根堆性质"
小根堆的最小值为根节点的权值。
存储
层次序列储存方式,即用数组保存二叉堆,
逐层从左到右把树中的节点依次编号,把编号作为节点映射到数组的中下标
从1开始标号
2 * i 为左儿子
2 * i +1 为右儿子
所以父节点编号等于子节点编号除以2
核心操作
down(x)
小根堆(大根堆)
x与左右儿子做比较 ,如果找处最小(大)的儿子与之交换
void down(int u)
{
int t = u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u
if (u * 2 <= siz && h[u * 2] < h[t]) t = u * 2; // 左子节点存在并且小于当前结点,更新t的下标
if (u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//右子节点存在并且小于当前结点,更新t的下标
if (t != u)//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
{
swap(h[t], h[u]);
down(t); //交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)。u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
}
}
up
小根堆(大根堆)
x与父节点做比较 ,如果父节点小(大),x就与父节点交换
void up(int u)
{
while(u/2&&h[u/2]<h[u])//如果父节点小于 u
{
heap_swap(u/2,u);
u/=2;//变成它的父节点
}
}
五种操作(以小根堆为例)
- 插入一个数
heap[ ++ size] = x; up(size);
在作为插入,然后再不断up,直到移到合适位置 - 删除最小值
heap[1] = heap[size]; size – ;down(1);
先用尾部的叶节点 x 覆盖第一个值,即覆盖最小值
然后删除尾部的叶节点 x
最后不断down(1)(因为曾经覆盖最小值的 x 所在的节点编号就是1)
直到down到 x 回到正确位置 - 删除任意一个元素
heap[k] = heap[size]; size – ;up(k); down(k);
先用尾部的叶节点 x 覆盖要删除的元素 k
然后删除尾部的叶节点 x
因为不确定k的位置,所以 up(k); down(k);一起进行总会回到自己的位置 - 修改任意一个元素
heap[k] = x; up(k); down(k);
直接让要修改的元素赋值即可
然后 up(k); down(k);一起进行总会回到自己的位置 - 求最小值
h[1]
根据小根堆性质h[1]即为最小值
STL实现
map + priority_queue(优先队列)
实现可以修改任意位置的堆
在大多数情况下,我们只需要使用STL中的priority_queue即可,万不得已也可以手写左偏树
那么要修改任意位置,我自己瞎搞了一个优先级队列+map的方法,AC了本题,就是不知道有没有问题
希望各位小佬爷们帮忙康康
//ink代表第k个插入的是谁
//knum代表值为x的数有多少个
1.插入第cnt个数x, 常规操作聪明的你一定能看懂
ink[++cnt] = x;
knum[x] ++;
q.push(x);
2.删除集合中的最小值
问题在于我们集合中的最小值可能是已经被删除了的,那么我们需要找到一个最小值x,其knum[x] > 0,其余小于x的数弹出队列即可,然后找到之后
knum[x] --;
3.修改任意位置
直接把第k的插入的数x的个数减一
然后把k个插入的数改为x
然后把当前插入的数的个数加一
再把这个数插入优先级队列
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, cnt;
map<int, int> ink;
map<int, int> knum;
priority_queue<int, vector<int>, greater<int> > q;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++ ) {
string str;
int k, x;
cin >> str;
if (str == "I") {
cin >> x;
q.push(x);
ink[++cnt] = x;
knum[x] ++;
} else if (str == "PM") {
do {
x = q.top();
q.pop();
} while (!knum[x]);
q.push(x);
cout << x << '\n';
} else if (str == "DM") {
do {
x = q.top();
q.pop();
} while (!knum[x]);
knum[x] --;
} else if (str == "D") {
cin >> k;
x = ink[k];
knum[x] --;
} else {
cin >> k >> x;
knum[ink[k]] --;
ink[k] = x;
knum[x] ++;
q.push(x);
}
}
return 0;
}
合并果子
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n−1n-1n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 111 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 333 种果子,数目依次为 111 , 222 , 999 。可以先将 111 、 222 堆合并,新堆数目为 333 ,耗费体力为 333 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 121212 ,耗费体力为 121212 。所以多多总共耗费体力 =3+12=15=3+12=15=3+12=15 。可以证明 151515 为最小的体力耗费值。
输入格式
共两行。
第一行是一个整数 n(1≤n≤10000)n(1\leq n\leq 10000)n(1≤n≤10000) ,表示果子的种类数。
第二行包含 nnn 个整数,用空格分隔,第 iii 个整数 ai(1≤ai≤20000)a_i(1\leq a_i\leq 20000)ai(1≤ai≤20000) 是第 iii 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2312^{31}231 。
输入输出样例
输入 #1
3
1 2 9
输出 #1
#include<bits/stdc++.h>
using namespace std;
int ans,x;
priority_queue<int,vector<int>,greater<int>> heap;
int main(){
int n;
cin>>n;
for(int i = 1 ; i <= n ; i++)
cin>>x,heap.push({
x});
for(int i = 1 ; i <= n - 1; i ++)
{
int a = heap.top();heap.pop();
int b = heap.top();heap.pop();
ans+=a+b;
heap.push({
a+b});
}
cout<<ans;
return 0;
}
完结散花
ok以上就是对 模拟数据结构之堆 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。