1. 程式人生 > >模板:左偏樹

模板:左偏樹

git show ons www. print style getc strong ble

如果你知道priority_queue的話,那自然就知道左偏樹的目的了。

左偏樹的目的和優先隊列一致,就是求出當前所在堆中的最大(小)值。

但是我們作為高貴的C++選手,我們為什麽還要學習左偏樹呢。

當然是因為priority_queue太!慢!了!

————————————————————————————————————

概念引入:

對於左偏樹,我們引入兩個概念:

外節點:如果該節點的左子樹或右子樹為空,那麽該節點為外節點。

距離(dis):該節點到達最近的外節點經過的邊的個數。

我們同時將優先隊列的定義照搬過來:

鍵值(val):節點的權值。

同時將優先隊列的性質照搬過來:

性質1:節點的鍵值小於或等於它的左右子節點的鍵值。

對於左偏樹,賦予它一個性質:

性質2:節點的左子節點的距離不小於右子節點的距離。

同時可以推出:

性質3:節點的距離等於它的右子節點的距離加1。(顯然)

為了滿足性質3,我們規定空節點的dis為-1。

此外還有很多性質,不過都是跟推左偏樹復雜度相關的性質,有興趣可以百度。

——————————————————————

復雜度:

這裏直接給出左偏樹的復雜度:

合並:O(logN1+logN2)(N1和N2分別為待合並左偏樹的節點個數)

插入:O(logn)

刪除最小節點:O(logn)

建樹:O(n)

刪除已知編號節點:O(logn)

————————————————————

基本操作:

以建立大根堆為例,現將前三個操作說明。

合並:

  按照左偏樹的定義合並即可。

  設要合並的兩個堆的根節點為x,y。

  如果其中一個堆為空堆,則不需合並。

  否則比較它們的val,設x為val較小的堆根節點。

  則x顯然是新堆(子堆)的根節點,但是y在哪裏我們並不知道。

  為了滿足左偏樹的性質(不提供證明),我們將x的右子堆和y堆合並,最後按照它們的dis交換一下左右兒子即可。

插入:

  將插入元素看成堆的話,與合並相同。

刪除最小節點:

  將根節點刪除後,其左右子堆合並即可。

————————————————————————————————

學到這裏代碼實現不是很難,這裏提供板子題目:

洛谷P3377:[模板]左偏樹(可並堆):https://www.luogu.org/problemnew/show/3377

代碼如下:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cctype>
#include<queue>
using namespace std;
const int N=1e5+3;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch==-;ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int fa[N];
struct tree{
    int l,r;
    int dis,val;
}tr[N];
int merge(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);//讓x的價值比y的價值小,這樣x就在y上面
    tr[x].r=merge(tr[x].r,y);//合並x右樹和y
    fa[tr[x].r]=x;
    if(tr[tr[x].l].dis<tr[tr[x].r].dis)
    swap(tr[x].l,tr[x].r);//讓右兒子更接近外節點
    tr[x].dis=tr[tr[x].r].dis+1;//根據右兒子dis更新節點dis
    return x;
}
int get(int x){
    while(fa[x])x=fa[x];
    return x;
}
void del(int x){
    tr[x].val=-1;
    fa[tr[x].l]=fa[tr[x].r]=0;
    merge(tr[x].l,tr[x].r);
    return;
}
int main(){
    int n=read();
    int m=read();
    for(int i=1;i<=n;i++)tr[i].val=read();
    for(int i=1;i<=m;i++){
    int c=read();
    if(c==1){
        int x=read();
        int y=read();
        if(tr[x].val==-1||tr[y].val==-1||x==y)continue;
        int nx=get(x);
        int ny=get(y);
        merge(nx,ny);
    }else{
        int x=read();
        if(tr[x].val==-1){
        puts("-1");
        }else{
        int y=get(x);
        printf("%d\n",tr[y].val);
        del(y);
        }
    }
    }
    return 0;
}

模板:左偏樹