二叉堆
堆是一棵树,其每个节点都有一个键值,且每个节点的键值都大于等于/小于等于其父亲的键值.
每个节点的键值都大于等于其父亲键值的堆叫做小根堆,否则叫做大根堆.STL中的priority_queue其实就是一个大根堆.
(小根)堆主要支持的操作有:插入一个数、查询最小值、删除最小值、合并两个堆、减小一个元素的值.
一些功能强大的堆(可并堆)还能(高效地)支持 merge 等操作.
一些功能更强大的堆还支持可持久化,也就是对任意历史版本进行查询或者操作,产生新的版本
| 操作 / 数据结构 |
配对堆 (Pairing Heap) |
二叉堆 (Binary Heap) |
左偏树 (Leftist Heap) |
二项堆 (Binomial Heap) |
斐波那契堆 (Fibonacci Heap) |
| 插入 (insert) |
O(1) |
O(log n) |
O(log n) |
O(log n) |
O(1) |
| 查询最小值 (find-min) |
O(1) |
O(1) |
O(1) |
O(1) |
O(1) |
| 删除最小值 (delete-min) |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
| 合并 (merge) |
O(1) |
O(n) |
O(log n) |
O(log n) |
O(1) |
| 减小键值 (decrease-key) |
o(log n)
下界 Ω(log log n),上界 O(22√(log log n))
|
O(log n) |
O(log n) |
O(log n) |
O(1) |
| 是否支持可持久化 |
✗ |
✓ |
✓ |
✓ |
✗ |
下面是题目
题目一
题目概述
给定一个初始为空的整数数列,需要支持以下三种操作:
- 插入操作:给定一个整数 ,将 插入到数列中。
- 查询操作:输出当前数列中的最小值。
- 删除操作:删除当前数列中的最小值。若最小值存在多个,仅删除其中一个。
要求按照输入顺序依次处理所有操作,并对每个查询操作输出结果。
题解
这是一道堆的模板题,会使用priority_queue就能做
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
priority_queue<ll,vector<ll>,greater<ll>> pd;
int n;
cin >> n;
while(n--)
{
int opt;
cin >> opt;
if(opt == 1)
{
ll x;
cin >> x;
pd.push(x);
}
else if(opt == 2)
{
int ans = pd.top();
cout << ans << "\n";
}
else{
pd.pop();
}
}
}
题目二
题目概述
在一个果园中,已将所有果子按种类划分为 堆,第i 堆果子的数量为 。每次可选择任意两堆果子合并为一堆,合并后新堆的数量为两堆之和,且该次操作的代价等于两堆果子数量之和。经过 次合并后最终得到一堆,定义总消耗为所有合并操作代价之和。求一种合并顺序,使总消耗最小,并输出该最小值。
题解
这是一道堆的应用,对于Huffman树问题,可以直接贪心每次合并最小的两个数就可以保证总消耗最小,可以证明。使用小根堆即可。、
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
priority_queue<int,vector<int>,greater<int>> pq;
for(int i = 0 ; i < n; i++)
{
int x;
cin >> x;
pq.emplace(x);
}
int ans = 0;
while(pq.size() != 1)
{
int x = pq.top(); pq.pop();
int y = pq.top(); pq.pop();
ans += x+y;
pq.push(x+y);
}
cout << ans << endl;
}
题目三、中位数
题目概述
给定一个长度为 N 的非负整数序列 A,依次考虑其前缀子序列。当只取前奇数个元素(即前 项)时,需要分别求出这些子序列的中位数并输出。中位数定义为将当前子序列排序后处于正中间位置的数。最终输出 个结果,每个对应一个奇数长度前缀的中位数。
题解
这道题目需要用两个堆来实现,一大大根堆一个小根堆。然后我们维护一个数mid,每次加入新的数的时候,判断和mid的大小,小于mid的值就push到大根堆,反之push小根堆。这样就可以保证小根堆的top大于mid并且是小根堆中最小的,大根堆也是同理。每2个循环判断一下大根堆和小根堆的数量,如果两边的数量不相等,就把哪一边top取出来并且pop(),将另一边push一个mid,然后mid复制top。这样就可以实现维护mid输出奇数长度的中位数了
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<ll> a(n+5);
priority_queue<ll,vector<ll>,greater<ll>> p1;
priority_queue<ll> p2;
for(int i = 0; i < n; i++) cin >> a[i];
ll mid = a[0];
cout << mid << endl;
for(int i = 1; i < n; i++)
{
ll temp = a[i];
if(temp > mid)
{
p1.push(temp);
}
else p2.push(temp);
if(i % 2 == 0)
{
while(p1.size()!=p2.size())
{
if(p1.size() > p2.size())
{
int t = p1.top();p1.pop();
p2.push(mid);
mid = t;
}
else{
int t = p2.top();p2.pop();
p1.push(mid);
mid = t;
}
}
cout << mid << "\n";
}
}
}
评论(0)
暂无评论