左偏樹 學習筆記
阿新 • • 發佈:2020-07-29
左偏樹
左偏樹到底是什麼呢??? 左偏樹實際上是可合併的堆。
他的節點不僅存了他的權值,還存了一個比較重要的資訊 \(dis\)。
dis 的定義: 一個節點到他的子樹中的葉子節點的最近距離。
維護\(dis\)有什麼用呢?
當我們合併兩個堆時,可能會導致堆退化成一條鏈,這樣我們的詢問操作的複雜度就會變成 O(n)。
這顯然不是我們想看到的。
而左偏樹就是靠 \(dis\) 來維護左右兩顆子樹的平衡。
性質
-
節點的權值小於他兒子節點的取值。
-
左偏樹的任意節點的左兒子的距離都要比右兒子大(
不然他怎麼叫左偏樹呢霧) -
左偏樹任意節點的距離等於右兒子的距離加一。
證明如下:
根據左偏性,可以得到他左兒子的距離要小於右兒子的距離,
而左偏樹中距離的定義是一個節點到離其最近的外節點的距離,故為Dis(RightSon)+1。
- 一個n個節點的左偏樹的距離最大為 log(n+1)-1(
不需要掌握)
證明如下:
若左偏樹的距離為一定值,則結點數最少的左偏樹是完全二叉樹。
結點最少的話,就是左右兒子距離一樣,這就是完全二叉樹了。
若一棵左偏樹的距離為k,則這棵左偏樹至少有 2^{k+1}-1個節點。
距離為k的完全二叉樹高度也是k,節點數就是 2^{k+1}-1個。
這樣就可以證明性質四了。
因為 n>=2^{k+1}-1,所以 k<=log(n+1)-1。
操作
一 合併操作
首先,當我們要合併堆時,讓要保持堆的性質,假設我們要維護小根堆,我們就要讓根節點權值較小的放上邊。
然後再讓另一個樹與他的右子樹合併。
那麼為什麼要合併右子樹呢???
因為左子樹的距離是大於右子樹的,合併左子樹顯然不優。
當我們合併完後,可能會出現這樣一種情況,左子樹的距離小於右子樹,這時我們直接交換一下就行了。
程式碼
int merage(int x,int y)//返回根節點 { if(x == 0 || y == 0) return x + y;//如果有一個為空,直接返回,不用合併 if(tr[x].val > tr[y].val) swap(x,y);//要滿足堆的性質 tr[x].rc = merage(tr[x].rc,y);//合併右子樹和另外一棵樹 fa[tr[x].rc] = x; if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);//滿足性質二 tr[x].dis = tr[tr[x].rc].dis + 1;//滿足性質三 return x; }
二 插入一個點
一個點就相當於一棵樹,直接合並就行了。
程式碼同上。
三 刪除根節點
我們要刪根節點,等於把根節點孤立出來,這時候我們只需要合併左右兩顆子樹就行了
程式碼
void del(int x)
{
tr[x].val = -1;//標記x已經被刪除
fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;//先把左右兒子的父親都設為他自己,方便以後合併
fa[x] = merage(tr[x].lc,tr[x].rc);//保持原有的父子關係不變
}
例題
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+7;
int n,m,opt,x,y;
int fa[N],dis[N];
struct node{
int val;
int lc,rc;
int fa,dis;
}tr[N];
int inline read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int find(int x){
if(tr[x].fa == x) return x;
else return tr[x].fa = find(tr[x].fa);
}
int merage(int x,int y){
if(x == 0 || y == 0) return x+y;
if(tr[x].val > tr[y].val || (tr[x].val == tr[y].val && x > y)){
swap(x,y);
}
tr[x].rc = merage(tr[x].rc,y);
tr[tr[x].rc].fa = x;
if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
tr[x].dis = tr[tr[x].rc].dis + 1;
return x;
}
void del(int x){
tr[x].val = -1;
tr[tr[x].lc].fa = tr[x].lc, tr[tr[x].rc].fa = tr[x].rc;
tr[x].fa = merage(tr[x].lc,tr[x].rc);
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; i++){
tr[i].fa = i;
tr[i].val = read();
}
while(m--){
opt = read();
if(opt == 1){
x = read(); y = read();
int fx = find(x) , fy = find(y);
if(tr[x].val == -1 || tr[y].val == -1) continue;
if(fx == fy) continue;
tr[fx].fa = tr[fy].fa = merage(fx,fy);
}
else{
x = read();
if(tr[x].val == -1) cout<<-1<<endl;
else{
int y = find(x);
printf("%d\n",tr[y].val);
del(y);
}
}
}
return 0;
}
左偏樹的裸題,和模板差不多
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
char opt;
int n,t,x,y,fa[N];
struct node{
int lc,rc;
int val,dis;
}tr[N];
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
return s * w;
}
int find(int x)
{
if(fa[x] == x) return x;
else return fa[x] = find(fa[x]);
}
int merage(int x,int y)
{
if(x == 0 || y == 0) return x + y;
if(tr[x].val > tr[y].val) swap(x,y);
tr[x].rc = merage(tr[x].rc,y);
fa[tr[x].rc] = x;
if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
tr[x].dis = tr[tr[x].rc].dis + 1;
return x;
}
void del(int x)
{
tr[x].val = -1;
fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;
fa[x] = merage(tr[x].lc,tr[x].rc);
}
int main()
{
n = read();
for(int i = 1; i <= n; i++)
{
fa[i] = i;
tr[i].val = read();
}
t = read();
while(t--)
{
cin>>opt;
if(opt == 'M')
{
x = read(); y = read();
int xx = find(x), yy = find(y);
if(tr[x].val == -1 || tr[y].val == -1) continue;
if(xx == yy) continue;
fa[xx] = fa[yy] = merage(xx,yy);
}
else if(opt == 'K')
{
x = read();
int xx = find(x);
if(tr[x].val == -1)
{
printf("%d\n",0);
continue;
}
printf("%d\n",tr[xx].val);
del(xx);
}
}
return 0;
}
暫時沒寫,先把坑占上。。。。