洛谷題解P3378 【模板】堆 暨 堆淺析
阿新 • • 發佈:2020-10-16
原題傳送門
\(\text{Solution}\)
一道堆的模板題,藉此機會來講一下堆的概念及基本操作。
\(1\).概念
堆為一種資料結構,即用陣列來實現一棵 完全二叉樹
有小根堆和大根堆兩種。
- 大根堆 : 根節點點權最大
- 小根堆 : 根節點點權最小
- 所有的堆都滿足一下一條性質 : 堆中某個節點的值總是不大於或不小於其父節點的值
當然,在 \(\text{STL}\) 中有其對應的資料結構(優先佇列實現)
這裡主要來說手寫堆(用陣列實現)
以上分別是一張小根堆示意圖和一張大根堆示意圖。(圖中點內數字表示點權)
\(2\).操作
\((1).push\) (把一個元素 \(x\) 加入堆)
思想如下 :
- 把 \(x\) 放進堆尾
- 比較當前節點與其父節點的大小
- 如是小根堆,即噹噹前節點比其父節點點權小時,交換當前節點與其父節點,迴圈往復,直至滿足小根堆的要求為止。
- 如實大根堆,即噹噹前節點比其父節點點權大時,交換當前節點與其父節點,迴圈往復,直至滿足大根堆的要求為止。
則我們可以得到以下程式碼 :
inline void push(int p){ int fa,now; heap[++cnt]=p; //加入堆 now=cnt; //注意初始化為當前堆尾 while(now>1){ fa=now>>1; //取其父節點 if(heap[now]<heap[fa]){ //小根堆為 "<" ,大根堆為 ">" swap(heap[now],heap[fa]); now=fa; } else return; } }
\((2).delete\)(刪除堆中的最值節點)
對於這個問題,分兩種情況討論
- 小根堆
- 刪除最小值
- 把堆尾元素的值覆蓋到堆的根節點(小根堆最小值)上,相當於完成了刪除操作。
- 比較當前節點與其子節點的大小,噹噹前節點比其兒子大時,交換當前節點與其兒子的值,迴圈往復,直到滿足小根堆的要求為止。
- 特別地,當此節點有兩個子節點時,需要找出較小的那個,完成交換
- 刪除最大值
- 直接刪除堆尾元素
- 刪除最小值
- 大根堆
- 刪除最小值
- 直接刪除堆尾元素
- 刪除最大值
- 把堆尾元素的值覆蓋到堆的根節點(大根堆的最大值)上,相當於完成了刪除操作。
- 比較當前節點與其子節點的大小,噹噹前節點比其兒子小時,交換當前節點與其兒子的值,迴圈往復,直到滿足大根堆的要求為止。
- 特別地,當此節點有兩個子節點時,需要找出較大的那個,完成交換
- 刪除最小值
則我們可以得到以下程式碼 :
inline void deleted(){
heap[1]=heap[cnt--]; //覆蓋,相當於刪除,注意為 "cnt--"
int now=1,son; //覆蓋的是堆頭,從堆頭開始遍歷
while(now*2<=cnt){ //保證當前節點會有子節點
son=now*2; //定義左兒子
if(son<cnt && heap[son+1]<heap[son]) son++;
//找兩個子節點中的最值,在 "heap[son+1]<heap[son]" 中,小根堆為 "<" ,大根堆為 ">"
if(heap[son]<heap[now]){ //小根堆為 "<" ,大根堆為 ">"
swap(heap[son],heap[now]);
now=son;
}
else return;
}
}
本題 \(Code\)
#include<iostream>
#include<cstdio>
using namespace std;
const int Maxn = 1e6+10;
inline void read(int &x){
int f=1;
char ch=getchar();
x=0;
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
x*=f;
}
int n;
int op;
int x;
int heap[Maxn],cnt;
inline void push(int p){
int fa,now;
heap[++cnt]=p;
now=cnt;
while(now>1){
fa=now>>1;
if(heap[now]<heap[fa]){
swap(heap[now],heap[fa]);
now=fa;
}
else return;
}
}
inline void deleted(){
heap[1]=heap[cnt--];
int now=1,son;
while(now*2<=cnt){
son=now*2;
if(son<cnt && heap[son+1]<heap[son]) son++;
if(heap[son]<heap[now]){
swap(heap[son],heap[now]);
now=son;
}
else return;
}
}
inline void swap(int &a,int &b){int t=a;a=b;b=t;}
int main(){
read(n);
for(int i=1;i<=n;i++){
read(op);
if(op==1){
read(x);
push(x);
}
else if(op==2) printf("%d\n",heap[1]);
else deleted();
}
return 0;
}