Treap(樹堆)入門


  • 作者:zifeiy
  • 標簽:Treap

首先,我么要知道:Treap=Tree+Heap。
這里:

  • Tree指的是二叉排序樹;
  • Heap指的是堆。

所以在閱讀這篇文章之前需要大家對 二叉查找樹堆(Heap) 有一定的認識。

Treap支持如下操作:

  1. 插入x數
  2. 刪除x數(若有多個相同的數,應只刪除一個)
  3. 查詢x數的排名(排名定義為比當前數小的數的個數+1。若有多個相同的數,應輸出最小的排名)
  4. 查詢排名為x的數
  5. 求x的前驅(前驅定義為小於x,且最大的數)
  6. 求x的后繼(后繼定義為大於x,且最小的數)

二叉排序樹是這樣的一棵樹:

  • 它是一棵二叉樹;
  • 任意節點的左兒子(如果有)的權值都小於該節點的權值;
  • 任意節點的右兒子(如果有)的權值都大於該節點的權值。

二叉排序樹可以實現上述6個功能,但是最壞情況下每一步操作的時間復雜度都會達到 \(O(n)\)

所以我們需要在二叉查找樹的基礎上引入堆的性質,形成一個 \(\Rightarrow\) Treap。

Treap的基本內容

首先,我們需要開一些數組來保存信息:

  • size[i]:以i為根節點的子樹的節點總數;
  • v[i]:i節點的權值;
  • num[i]:由於可能有多個節點具有相同的權值,所以,我們將權值一樣的存在同一個節點里面,num[i]存放的是i節點存的數的個數(即:num[i]表示有多少個數的權值為v[i]);
    sum[i][2]:用於存儲i節點的兒子編號,其中:son[i][0]表示i節點的左兒子,son[i][1]表示i節點的右兒子;
  • rd[i]:i節點的隨機值。

那么,rd[i]的左右是什么呢?
每次創建一個新節點i的時候,都會為i節點分配一個隨機值 rd[i]
堆就是在這里派上用場的——我們要讓全部節點按照這個隨機值排成一個堆。
這就引出了平衡樹中最重要的一個概念——旋轉。

rotate操作——旋轉

旋轉分兩種:左旋和右旋,它們的共同特點是不改變Treap的二叉查找樹的性質,同時讓Treap更加平衡。

旋轉可以維護Treap堆的性質,然后巧妙地防止Treap退化成鏈,使得操作的時間復雜度趨於 \(O(\log n)\)

Treap的基本操作

void pushup(int p) {
    sz[p] = sz[ son[p][0] ] + sz[ son[p][1] ] + num[p];
}

用於重新統計以p為根節點的子樹中元素個數。

void rot(int &p, int d) {
    int k = son[p][d^1];
    son[p][d^1] = son[k][d];
    son[k][d] = p;
    pushup(p);
    pushup(k);
    p = k;
}

旋轉操作,d0:左旋;d1:右旋。

1. 插入一個數x

void ins(int &p, int x) {
    if (!p) {
        p = ++sum;
        sz[p] = num[p] = 1;
        v[p] = x;
        rd[p] = rand();
        return;
    }
    if (v[p] == x) {
        num[p] ++;
        sz[p] ++;
        return;
    }
    int d = (x > v[p]);
    ins(son[p][d], x);
    if (rd[p] < rd[ son[p][d] ]) rot(p, d^1);
    pushup(p);
}

如果 p==0 (即 !p),那么就說明當前節點是一個空節點,此時我們開辟一個新節點;
如果 v[p]==x,那么就說明當前要插入的位置p上面剛好存了一個x,直接放到這個點上面就OK了;
否則,我們需要遞歸的進一個子樹進行插入,當 x<v[p] 時, d=0,進左子樹;當 x>v[p] 時,d=1,進右子樹遞歸地插入。
如果進左兒子插入x后,p節點的rd值小於它左兒子的rd值(即:\(rd[p] \lt rd[son[p][0]]\)),則右旋;
如果進右兒子插入x后,p節點的rd值小於它右兒子的rd值(即:\(rd[p] \lt rd[son[p][1]]\)),則左旋。
重點,想一想,為什么這樣轉不破壞堆的性質 )(我暫時還沒有想明白~)

2. 刪除一個數x

void del(int &p, int x) {
    if (!p) return;
    if (x < v[p]) del(son[p][0], x);
    else if (x > v[p]) del(son[p][1], x);
    else {
        if (!son[p][0] && !son[p][1]) {
            num[p] --; sz[p] --;
            if (!num[p]) p = 0;
        }
        else if (!son[p][1]) {
            rot(p, 1);
            del(son[p][1], x);
        }
        else if (!son[p][0]) {
            rot(p, 0);
            del(son[p][0], x);
        }
        else {
            int d = (rd[ son[p][0] > rd[ son[p][1] ] ]);
            rot(p, d);
            del(son[p][d], x);
        }
    }
    pushup(p);
}

如果是空節點,則直接返回;
如果 xp[x] 不相等,直接去相應子樹遞歸刪除;
如果 x==v[p],則:

  • 如果x是葉子結點,直接扣掉個數,如果個數變為0則刪掉節點;
  • 如果x只有一個子節點,直接把子節點旋轉上來,然后去相應子樹解決;
  • 如果有兩個子節點,把大的那個轉上來,然后去另一個子樹解決。

3. 查詢x數的排名

int get_rank(int p, int x) {
    if (!p) return 0;
    if (v[p] == x) return sz[ son[p][0] ] + 1;
    if (v[p] < x) return sz[ son[p][0] ] + num[p] + get_rank(son[p][1], x);
    if (v[p] > x) return get_rank(son[p][0], x);
}

如果不存在這個節點(到達了一個空節點),直接返回0;
如果x==v[p],那么左子樹的全部節點都必定小於x,直接返回左子樹節點數+1;
如果x>v[p],則x位於右子樹,答案就是左子樹元素個數+該節點元素個數+右子樹中x的排名;
如果x<v[p],則x位於左子樹,答案就是x在左子樹的排名。

4. 查詢排名為x的數

int func_find(int p, int x) {
    if (!p) return 0;
    if (sz[ son[p][0] ] >= x) return func_find(son[p][0], x);
    else if (sz[ son[p][0] ] + num[p] < x)
        return func_find(son[p][1], x-num[p]-sz[ son[p][0] ]);
    else return v[p];
}

空節點沒有排名,直接返回0;
如果左子樹中節點個數大於x,則進左子樹找;
否則,如果左子樹加根節點的個數大於等於x,直接返回根節點的值v[p]
否則,說明左子樹加根節點的個數小於x,進右子樹找第 \(x-最節點和根節點元素個數\) 個元素。

5. 求x的前驅

int pre(int p, int x) {
    if (!p) return -INF;
    if (v[p] >= x) return pre(son[p][0], x);
    else return max(v[p], pre(son[p][1], x));
}

如果是空節點,則沒有前驅;
如果x是根或在右子樹,去左子樹找;
否則要么是根要么右子樹,取一個max就可以了(前驅定義為小於x,且最大的數)。

6. 求x的后綴

int suc(int p, int x) {
    if (!p) return INF;
    if (v[p] <= x) return suc(son[p][1], x);
    else return min(v[p], suc(son[p][0], x));
}

如果是空節點,則沒有后綴;
如果在根或者左子樹,去右子樹找;
否則要么根要么左子樹,取min就可以了(后繼定義為大於x,且最小的數)。
洛谷上面有一道題是專門用於練習左偏樹的題目:洛谷 P3369
實現代碼如下:

#include <bits/stdc++.h>
using namespace std;
#define INF INT_MAX
const int maxn = 100010;
int sum = 0, R = 0;
int sz[maxn], v[maxn], num[maxn], rd[maxn], son[maxn][2];
// 用於重新統計以p為根節點的子樹中元素個數
void pushup(int p) {
    sz[p] = sz[ son[p][0] ] + sz[ son[p][1] ] + num[p];
}
// 左旋(d==0時),右旋(d==1時)
void rot(int &p, int d) {
    int k = son[p][d^1];
    son[p][d^1] = son[k][d];
    son[k][d] = p;
    pushup(p);
    pushup(k);
    p = k;
}
// 插入一個數x
void ins(int &p, int x) {
    if (!p) {
        p = ++sum;
        sz[p] = num[p] = 1;
        v[p] = x;
        rd[p] = rand();
        return;
    }
    if (v[p] == x) {
        num[p] ++;
        sz[p] ++;
        return;
    }
    int d = (x > v[p]);
    ins(son[p][d], x);
    if (rd[p] < rd[ son[p][d] ]) rot(p, d^1);
    pushup(p);
}
// 刪除一個數x
void del(int &p, int x) {
    if (!p) return;
    if (x < v[p]) del(son[p][0], x);
    else if (x > v[p]) del(son[p][1], x);
    else {
        if (!son[p][0] && !son[p][1]) {
            num[p] --; sz[p] --;
            if (!num[p]) p = 0;
        }
        else if (!son[p][1]) {
            rot(p, 1);
            del(son[p][1], x);
        }
        else if (!son[p][0]) {
            rot(p, 0);
            del(son[p][0], x);
        }
        else {
            int d = (rd[ son[p][0] > rd[ son[p][1] ] ]);
            rot(p, d);
            del(son[p][d], x);
        }
    }
    pushup(p);
}
// 查詢x數的排名
int get_rank(int p, int x) {
    if (!p) return 0;
    if (v[p] == x) return sz[ son[p][0] ] + 1;
    if (v[p] < x) return sz[ son[p][0] ] + num[p] + get_rank(son[p][1], x);
    if (v[p] > x) return get_rank(son[p][0], x);
}
// 查詢排名為x的數
int func_find(int p, int x) {
    if (!p) return 0;
    if (sz[ son[p][0] ] >= x) return func_find(son[p][0], x);
    else if (sz[ son[p][0] ] + num[p] < x)
        return func_find(son[p][1], x-num[p]-sz[ son[p][0] ]);
    else return v[p];
}
// 求x的前驅
int pre(int p, int x) {
    if (!p) return -INF;
    if (v[p] >= x) return pre(son[p][0], x);
    else return max(v[p], pre(son[p][1], x));
}
// 求x的后綴
int suc(int p, int x) {
    if (!p) return INF;
    if (v[p] <= x) return suc(son[p][1], x);
    else return min(v[p], suc(son[p][0], x));
}

int T, op, x;
int main() {
    cin >> T;
    while (T --) {
        cin >> op >> x;
        if (op == 1) ins(R, x);
        else if (op == 2) del(R, x);
        else if (op == 3) cout << get_rank(R, x) << endl;
        else if (op == 4) cout << func_find(R, x) << endl;
        else if (op == 5) cout << pre(R, x) << endl;
        else if (op == 6) cout << suc(R, x) << endl;
    }
    return 0;
}

然后我又用類封裝了一下,C++類封裝的代碼如下:

#include <bits/stdc++.h>
using namespace std;
#define INF INT_MAX
const int maxn = 100010;
class Treap {
private:
    int sum, R, sz[maxn], v[maxn], num[maxn], rd[maxn], son[maxn][2];
    void pushup(int p) {
        sz[p] = sz[ son[p][0] ] + sz[ son[p][1] ] + num[p];
    }
    void rot(int &p, int d) {
        int k = son[p][d^1];
        son[p][d^1] = son[k][d];
        son[k][d] = p;
        pushup(p);
        pushup(k);
        p = k;
    }
    void ins(int &p, int x) {
        if (!p) {
            p = ++sum;
            sz[p] = num[p] = 1;
            v[p] = x;
            rd[p] = rand();
        }
        else if (v[p] == x) {
            num[p] ++;
            sz[p] ++;
        }
        else {
            int d = (x > v[p]);
            ins(son[p][d], x);
            if (rd[p] < rd[ son[p][d] ]) rot(p, d^1);
            pushup(p);
        }
    }
    void del(int &p, int x) {
        if (!p) return;
        if (x < v[p]) del(son[p][0], x);
        else if (x > v[p]) del(son[p][1], x);
        else {
            if (!son[p][0] && !son[p][1]) {
                num[p] --; sz[p] --;
                if (!num[p]) p = 0;
            }
            else if (!son[p][1]) {
                rot(p, 1);
                del(son[p][1], x);
            }
            else if (!son[p][0]) {
                rot(p, 0);
                del(son[p][0], x);
            }
            else {
                int d = (rd[ son[p][0] > rd[ son[p][1] ] ]);
                rot(p, d);
                del(son[p][d], x);
            }
        }
        pushup(p);
    }
    int get_rank(int p, int x) {
        if (!p) return 0;
        if (v[p] == x) return sz[ son[p][0] ] + 1;
        if (v[p] < x) return sz[ son[p][0] ] + num[p] + get_rank(son[p][1], x);
        if (v[p] > x) return get_rank(son[p][0], x);
    }
    int func_find(int p, int x) {
        if (!p) return 0;
        if (sz[ son[p][0] ] >= x) return func_find(son[p][0], x);
        else if (sz[ son[p][0] ] + num[p] < x)
            return func_find(son[p][1], x-num[p]-sz[ son[p][0] ]);
        else return v[p];
    }
    int pre(int p, int x) {
        if (!p) return -INF;
        if (v[p] >= x) return pre(son[p][0], x);
        else return max(v[p], pre(son[p][1], x));
    }
    int suc(int p, int x) {
        if (!p) return INF;
        if (v[p] <= x) return suc(son[p][1], x);
        else return min(v[p], suc(son[p][0], x));
    }
public:
    Treap() {}
    void Init() {
        sum = R = 0;
        memset(sz, 0, sizeof(sz));
        memset(v, 0, sizeof(v));
        memset(num, 0, sizeof(num));
        memset(rd, 0, sizeof(rd));
        memset(son, 0, sizeof(son));
    }
    void Insert(int x) { ins(R, x); }
    void Delete(int x) { del(R, x); }
    int GetRank(int x) { return get_rank(R, x); }
    int Find(int x) { return func_find(R, x); }
    int Pre(int x) { return pre(R, x); }
    int Suc(int x) { return suc(R, x); }
} treap;
int main() {
    treap.Init();
    int T, op, x;
    cin >> T;
    while (T --) {
        cin >> op >> x;
        switch (op) {
            case 1: treap.Insert(x); break;
            case 2: treap.Delete(x); break;
            case 3: cout << treap.GetRank(x) << endl; break;
            case 4: cout << treap.Find(x) << endl; break;
            case 5: cout << treap.Pre(x) << endl; break;
            case 6: cout << treap.Suc(x) << endl; break;
            default: break;
        }
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM