1 左偏樹的定義和性質
左偏樹是一種優先隊列,它除了支持優先隊列的三個操作:插入,取得最小(最大)節點,刪除最小(最大)節點之外,還支持一個額外的操作:合併操作
左偏樹是一種可並堆,它以一棵二叉樹的形式存在.二叉樹中每一個節點保存有左右兒子 ,值 ,距離
在這裏的節點 的距離指的是節點 到他的後代中,最近的葉子節點的距離
性質1:節點 的值小於等於節點 的值
這也就是一般二叉堆所滿足的性質,被稱爲堆性質
性質2:節點 的距離大於等於節點 的距離
這就是左偏樹所特有的性質,被稱爲左偏性質
性質1,性質2是構成左偏樹的基本性質,屬於定義類
性質3:節點 的距離爲節點 的距離+1
這個性質是由性質2推出,方便我們在之後的操作中維護節點的距離
2 左偏樹的操作
2.1 左偏樹的合併
操作用來合併兩棵左偏樹,返回合併後新的左偏樹,包含原有左偏樹的所有元素
一棵左偏樹我們通常用其根節點指針表示
若某棵樹爲空,則直接返回另一棵樹即可
if(x==0 || y==0) return x+y;
若 均非空,我們使 的根節點小於 的根節點(否則 )
if(key[x]>key[y] || (key[x]==key[y] && x>y)) swap(x,y);
的根節點顯然就是新樹中最小的節點,我們將其看做新樹的根節點
接下來就只需要合併 和
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
在合併了 和 後, 的距離可能變大,從而超過 的距離
在這種情況下,交換 即可
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][1],ch[x][1]);
同時,更新 的距離
dis[x]=dis[ch[x][1]]+1;
最後,返回新樹,即
return x;
完整合並如下
int merge(int x,int y) {
if(x==0 || y==0) return x+y;
if(key[x]>key[y] || (key[x]==key[y] && x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][1],ch[x][1]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
時間複雜度爲
2.2 左偏樹的插入
顯然,單個節點就是一顆左偏樹
那麼將插入看做兩個左偏樹合併即可
2.3 左偏樹的刪除
刪除指的是刪除左偏樹中最大(最小)節點
通過性質1發現,左偏樹的最大(最小)節點爲其根節點
在刪除根節點後,合併左右子樹即可
void pop(int x) {
key[x]=-1;
f[ch[x][0]]=ch[x][0];
f[ch[x][1]]=ch[x][1];
merge(ch[x][0],ch[x][1]);
}
2.4 刪除某個特定值的節點
在這裏提供一個簡單的方法
因爲左偏樹內部的節點是無序的,只有其根節點體現其有序性(最大(最小))
那麼建立一個刪除堆,將需要刪除的值插入,若刪除堆的根節點劫左偏樹的根節點相同,則彈出兩者根節點即可
3 例題
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int dis[N],ch[N][2],f[N],key[N],n,m;
int read() {
int ans=0,flag=1;
char ch=getchar();
while( (ch>'9' || ch<'0') && ch!='-' ) ch=getchar();
if(ch=='-') {flag=-1;ch=getchar();}
while(ch>='0' && ch<='9') {ans=ans*10+ch-'0';ch=getchar();}
return ans*flag;
}
int merge(int x,int y) {
if(x==0 || y==0) return x+y;
if(key[x]>key[y] || (key[x]==key[y] && x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][1],ch[x][1]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
void pop(int x) {
key[x]=-1;
f[ch[x][0]]=ch[x][0];
f[ch[x][1]]=ch[x][1];
merge(ch[x][0],ch[x][1]);
}
int find(int x) {
while(x!=f[x]) {x=f[x];}
return x;
}
int main() {
n=read();m=read();
memset(dis,-1,sizeof(dis));
for(int i=1;i<=n;++i) {key[i]=read();f[i]=i;dis[i]=0;}
for(int i=1;i<=m;++i) {
int cpt=read();
if(cpt==1) {
int x=read(),y=read();
if(key[x]==-1 || key[y]==-1 || x==y) continue;
x=find(x);y=find(y);
if(x==y) continue;
merge(x,y);
}
else {
int x=read();
if(key[x]==-1) {puts("-1");continue;}
x=find(x);
printf("%d\n",key[x]);
pop(x);
}
}
return 0;
}