二叉堆
\(\Huge 堆\)
堆是一种能够维护 \(N\) 个元素的大小关系的树形数据结构。
堆主要分为二叉堆、左偏树、配对堆、斐波那契堆、二项堆等,其中,斐波那契堆以超高的常数复杂度及其巨复杂的实现方式,在此不讲解。
下面讲如何实现堆(以大根堆为例):
二叉堆
二叉堆是实现最简单的堆。
1.二叉堆的定义
二叉堆本质上是一棵完全二叉树。
2.二叉堆的实现
如下是一颗完全二叉树:
![](file:///C:/Users/HW/Pictures/R-C.gif?msec=1721108804003)
可以发现,每个左子节点编号都是其父节点的 \(2\) 倍,每个右子节点编号都是其父节点的 \(2\) 倍 \(+1\) 。
因此,我们可以用一个数组存储这个完全二叉树,并利用上述特征取出每个父节点与子节点的数值。
为了防止越界的情况,还要用一个数来保存编号最大的节点的编号。
int heap[N],n;
我们不妨保证根节点永远是二叉堆内部最大的数:
-
当插入新节点时更新上面的节点。
-
观察新节点的父节点(编号 \(\lfloor x\div2\rfloor\) )
-
若父节点小于子节点,则交换父节点与子节点的值:
void Up(int p){ while(p>1){ if(heap[p]>heap[p/2]){ swap(heap[p],heap[p/2]); p/=2; } else break; } } void Insert(int x){ heap[++n]=x; Up(n); }
-
-
当排出根节点时更新下面的节点。
-
观察左子节点与右子节点,取其较大者,与父节点比较,若父节点较小,则交换两数(左右子节点取较大值与父节点交换后必为三者中最大值)。
-
继续下调:
void Down(int p){ int s=p*2; while(s<=n){ if(s<n&&heap[s]<heap[s+1])s++; if(heap[s]>heap[p]){ swap(heap[s],heap[p]); p=s; s*=2; } else break; } } void Pop(){ heap[1]=heap[n--]; Down(1); }
-
接下来就可以放心输出堆顶了。
int GetTop(){
return heap[1];
}
当然,万能的 \(STL\) 也为我们提供了一个等价于堆的优先队列:
priority_queue<int,vector<int>,less<int> >q;//大根堆
priority_queue<int,vector<int>,greater<int> >q;//小根堆
但是相较于手写堆来说,优先队列缺少了随机删除操作,即删除任意一个任意编号的数:
注意:此时可能既需要下调,也可能上调,都需要执行一次:
void Remove(int k){
heap[k]=heap[n--];
Up(k);Down(k);
}
3.二叉堆的应用
例题一、堆
题意:给定一个数列,初始为空,请支持下面三种操作:
- 给定一个整数 \(x\) ,请将 \(x\) 加入到数列中。
- 输出数列中最小的数。
- 删除数列中最小的数(如果有多个数最小,只删除 \(1\) 个)。
此题是堆的板子,直接模拟。
#include<bits/stdc++.h>
using namespace std;
int heap[1000010],n,m;
void Up(int p){
while(p>1){
if(heap[p]>heap[p/2]){
swap(heap[p],heap[p/2]);
p/=2;
}
else break;
}
}
int GetTop(){
return heap[1];
}
void Insert(int x){
heap[++n]=x;
Up(n);
}
void Down(int p){
int s=p*2;
while(s<=n){
if(s<n&&heap[s]<heap[s+1])s++;
if(heap[s]>heap[p]){
swap(heap[s],heap[p]);
p=s;
s*=2;
}
else break;
}
}
void Pop(){
heap[1]=heap[n--];
Down(1);
}
int main(){
scanf("%d",&m);
while(m--){
int op,x;
scanf("%d",&op);
if(op==1){
scanf("%d",&x);
Insert(x);
}
if(op==2) printf("%d\n",GetTop());
if(op==3) Pop();
}
return 0;
}
例题二、合并果子
题意:给定 \(N\) 个数,不断取出两个数求和再放入并累加到答案中,输出最小的答案。
分析:将 \(N\) 个数放入小根堆,不断取出前两小的数,求和并累加至答案中且将和再次插入小根堆,直至堆内元素只剩一个:
#include<bits/stdc++.h>
using namespace std;
int heap[1000010],n,m,num,ans;
void Up(int p){
while(p>1){
if(heap[p]<heap[p/2]){
swap(heap[p],heap[p/2]);
p/=2;
}
else break;
}
}
int GetTop(){
return heap[1];
}
void Insert(int x){
heap[++n]=x;
num++;//记录堆中元素数
Up(n);
}
void Down(int p){
int s=p*2;
while(s<=n){
if(s<n&&heap[s]>heap[s+1])s++;
if(heap[s]<heap[p]){
swap(heap[s],heap[p]);
p=s;
s*=2;
}
else break;
}
}
void Pop(){
heap[1]=heap[n--];
num--;//记录堆中元素数
Down(1);
}
int main(){
scanf("%d",&m);
while(m--){
int x;
scanf("%d",&x);
Insert(x);
}
while(num>1){
int x1=GetTop();
Pop();
int x2=GetTop();
Pop();
ans+=x1;
ans+=x2;
Insert(x1+x2);
}
printf("%d\n",ans);
return 0;
}
posted on 2024-07-17 01:05 zengziquan 阅读(2) 评论(0) 编辑 收藏 举报