1. 程式人生 > >[算法學習筆記]左偏樹的基本操作及其應用

[算法學習筆記]左偏樹的基本操作及其應用

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

[算法學習筆記]左偏樹的基本操作及其應用