算法模版:模拟数据结构之堆【沈七】

前言

⭐️感谢相遇,唤我沈七就好。
⭐️如果能和你一起进步那就太好啦。

定义

堆:是一种满足插入、删除、查询最值的数据结构。

其实是一颗满足堆性质的完全二叉树。

树中的任意一个结点的权值都小于等于父节点,则该二叉树满足"大根堆性质"
大根堆的最大值为根节点的权值。

树中的任意一个结点的权值都大于等于父节点,则该二叉树满足"小根堆性质"
小根堆的最小值为根节点的权值。

存储

层次序列储存方式,即用数组保存二叉堆,

逐层从左到右把树中的节点依次编号,把编号作为节点映射到数组的中下标
从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;//变成它的父节点
    }
}

五种操作(以小根堆为例)

  1. 插入一个数
    heap[ ++ size] = x; up(size);
    在作为插入,然后再不断up,直到移到合适位置
  2. 删除最小值
    heap[1] = heap[size]; size – ;down(1);
    先用尾部的叶节点 x 覆盖第一个值,即覆盖最小值
    然后删除尾部的叶节点 x
    最后不断down(1)(因为曾经覆盖最小值的 x 所在的节点编号就是1)
    直到down到 x 回到正确位置
  3. 删除任意一个元素
    heap[k] = heap[size]; size – ;up(k); down(k);
    先用尾部的叶节点 x 覆盖要删除的元素 k
    然后删除尾部的叶节点 x
    因为不确定k的位置,所以 up(k); down(k);一起进行总会回到自己的位置
  4. 修改任意一个元素
    heap[k] = x; up(k); down(k);
    直接让要修改的元素赋值即可
    然后 up(k); down(k);一起进行总会回到自己的位置
  5. 求最小值
    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以上就是对 模拟数据结构之堆 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。

参考文献

https://www.acwing.com/activity/content/19/

猜你喜欢

转载自blog.csdn.net/m0_66139206/article/details/124200008