左偏樹小結

1 左偏樹的定義和性質

左偏樹是一種優先隊列,它除了支持優先隊列的三個操作:插入,取得最小(最大)節點,刪除最小(最大)節點之外,還支持一個額外的操作:合併操作

左偏樹是一種可並堆,它以一棵二叉樹的形式存在.二叉樹中每一個節點保存有左右兒子(lc,rc) ,值(key) ,距離(dis)
在這裏的節點i距離指的是節點i 到他的後代中,最近的葉子節點的距離

性質1:節點i 的值小於等於節點lc[i],rc[i] 的值
這也就是一般二叉堆所滿足的性質,被稱爲堆性質

性質2:節點lc[i] 的距離大於等於節點rc[i] 的距離
這就是左偏樹所特有的性質,被稱爲左偏性質

性質1,性質2是構成左偏樹的基本性質,屬於定義類

性質3:節點i 的距離爲節點rc[i] 的距離+1
這個性質是由性質2推出,方便我們在之後的操作中維護節點的距離

2 左偏樹的操作

2.1 左偏樹的合併

merge 操作用來合併兩棵左偏樹,返回合併後新的左偏樹,包含原有左偏樹的所有元素
一棵左偏樹我們通常用其根節點指針表示

merge(x,y)
若某棵樹爲空,則直接返回另一棵樹即可

if(x==0 || y==0) return x+y;

x,y 均非空,我們使x 的根節點小於y 的根節點(否則swap(x,y) )

if(key[x]>key[y] || (key[x]==key[y] && x>y)) swap(x,y);

x 的根節點顯然就是新樹中最小的節點,我們將其看做新樹的根節點
接下來就只需要合併rc[x]y

ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;

在合併了rc[x]y 後,rc[x] 的距離可能變大,從而超過lc[x] 的距離
在這種情況下,交換lc[x],rc[x] 即可

if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][1],ch[x][1]);

同時,更新x 的距離

dis[x]=dis[ch[x][1]]+1;

最後,返回新樹,即x

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

時間複雜度爲O(log2Node(x)+log2Node(y))

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 例題

Luogu P3377 【模板】左偏樹(可並堆)

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章