[算法學習筆記]左偏樹的基本操作及其應用
阿新 • • 發佈:2019-03-25
swa merge 完全 span read har 步驟 建議 i++
簡介
左偏樹是一種可以快速支持合並等操作的堆, 是可並堆中代碼復雜度最低,也最容易理解的一種(註意左偏樹的每一棵子樹都為左偏樹)
性質
左偏樹是一種二叉樹, 除了有二叉樹的左右兒子,還有2個屬性,鍵和距離。下面是左偏樹的一些基本性質。
節點的鍵值小於或等於左右子節點的鍵值。這是左偏樹的堆性質。
節點的左子節點的距離不小於右子節點的距離。 這是左偏樹的左偏性質。
節點的距離等於它的右子節點距離加一。 若無則為0.
若左偏樹的距離為一定值,則節點數最少的左偏樹是完全二叉樹。(顯然啊)
基本操作
數組定義
struct Point{ int val,ls,rs,dis,fa; //val 為 權值 //ls,rs 為左右子樹 //dis為距離 //fa 為 father! }tree[maxn];
Merge
? 顧名思義, Merge操作就是把兩個左偏樹並起來, 註意並後的左偏樹一定要滿足上面的四條性質。現在我們令x, y為要合並的兩個堆的根, 下面即是步驟。
- 如果\(x\) 或 \(y\) 為空(即等於0) 返回 x + y(就是\(x\) 或 \(y\) 非零的那一個)
- 為了保持一定是val大的的並到小的上面, 所以\(if(tree[x].val > tree[y].val) swap(x, y);\)(大根堆則反之)
- 現在我們將\(y\)樹並到\(x\)的右子樹上.(因為\(x\)的val比\(y\)的小, 所以並到他的子樹中, 又因為這棵樹是左偏的,你就把它並到右子樹中吧)
- 為了維持上面的第二條性質, 若\(tree[tree[x].ls].dis > tree[tree[x].rs].dis\) 則 \(swap(tree[x].ls, tree[x].rs)\)
- 更新x的距離
int merge(int x, int y) { if (x == 0 || y == 0) return x+y; if ((tree[x].val > tree[y].val) || (tree[x].val == tree[y].val && x > y)) swap(x,y); tree[x].rs = merge(tree[x].rs,y); tree[tree[x].rs].fa = x; if(tree[tree[x].ls].dis < tree[tree[x].ls].dis) swap(tree[x].ls,tree[x].rs); tree[x].dis = tree[tree[x].rs].dis + 1; return x; }
Insert
? Insert操作為插入一個新的節點, 把這個節點看做一顆新的左偏樹即可
int New(int x, int y){
tree[++tot].val = y;
tree[tot].ls = tree[tot].rs = 0;
return merge(x, y);
}
Delete
? 把根提出來, 合並左右節點就好了
root[x]= merge(tree[x].ls, tree[x].rs);
Top
? emmm, \(Top = tree[x].val\) (x 為 堆的根)
?
例題
? 以洛谷p3377為例, 因為此題需要維護聯通性, 所以需要像並查集一樣暴力找父親(不能路徑壓縮)
? 代碼醜, 建議不看, 以後會更新的
#include <bits/stdc++.h>
using namespace std;
int n,m;
struct Point{
int val,ch[2],dis,fa;
}tree[1000010];
template<class T>
void read(T &a){
T s = 0, w = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') w = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
s = (s << 1) + (s << 3) + (c ^ 48);
c = getchar();
}
a = w*s;
}
int merge(int x, int y){
if (x == 0 || y == 0)
return x+y;
if ((tree[x].val > tree[y].val) || (tree[x].val == tree[y].val && x > y)) swap(x,y);
tree[x].rs = merge(tree[x].rs,y);
tree[tree[x].ls].fa = x;
if(tree[tree[x].ls].dis < tree[tree[x].rs].dis){
swap(tree[x].ls,tree[x].rs);
}
tree[x].dis = tree[tree[x].rs].dis + 1;
return x;
}
int find(int x){
if(tree[x].fa == 0) return x;
return find(tree[x].fa);
}
int main(){
read(n); read(m);
tree[0].dis = -1;
for (int i = 1; i <= n; i++){
int x;
cin>>x;
tree[i].val = x;
}
for (int i = 1; i <= m; i++){
int opx;
read(opx);
if(opx == 1){
int x,y;
read(x); read(y);
if(tree[x].val == -1 || tree[y].val == -1) continue;
int xx = find(x), yy = find(y);
if(xx == yy) continue;
merge(xx,yy);
}
if(opx == 2){
int x;
read(x);
int xx = find(x);
printf("%d\n",tree[xx].val);
if(tree[xx].val == -1) continue;
tree[xx].val = -1;
int ls = tree[xx].ls;, rs = tree[xx].rs;
tree[xx].ch[0] = tree[xx].ch[1] = 0;
tree[ls].fa = 0,tree[rs].fa = 0;
merge(ls,rs);
}
}
return 0;
}
[算法學習筆記]左偏樹的基本操作及其應用