1. 程式人生 > 實用技巧 >洛谷題解P3378 【模板】堆 暨 堆淺析

洛谷題解P3378 【模板】堆 暨 堆淺析

原題傳送門

\(\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;
}