1. 程式人生 > >treap詳解

treap詳解

不能 是什麽 同時 src 插入 排序 getch ont void

一、二叉排序樹
因為只要是來講treap的,所以關於二叉排序樹的知識就不再贅述。
如果還不知道二叉排序樹,可以先到別處學學再來看。
在二叉排序樹中,我們將比該節點小的值放在該節點的左邊,將比該節點大的值放在該節點的右邊。
可是很顯然,這樣的話操作的時間復雜度就和樹的深度有很大的關系。當樹的形態為一條鏈的時候,就無法滿足操作的復雜度為\(nlog(n)\)
?
?
二、treap是什麽
treap = tree + heap
treap就是樹與堆的結合體。為了防止樹被單調的數據卡成一條鏈,可以給每個節點多加上一個隨機的優先值,讓樹同時也滿足堆的性質(一般為小根堆)。
由於優先值是隨機的,所以樹的深度平均為\(nlog(n)\)


?
?
三、treap實現
1、左旋(zip)與右旋(zap)
我們發現,在一個節點的優先值被隨機生成後,樹可能無法滿足我們所要求的堆的性質。
所以為了讓樹同時也具備這個性質,需要對樹進行一定的旋轉,使樹變成我們所希望的樣子。
?
左旋:將根節點的右兒子作為根,根節點作為右兒子的左兒子。
右旋:將根節點的左兒子作為根,根節點作為左兒子的右兒子。
技術分享圖片
由圖中可知左旋與右旋的操作方法。

void zig(int &k) { //左旋 
    int v = rson[k]; rson[k] = lson[v]; lson[v] = k;
    size[v] = size[k]; up(k); k = v;
}

void zag(int &k) { //右旋 
    int v = lson[k]; lson[k] = rson[v]; rson[v] = k;
    size[v] = size[k]; up(k); k = v;
}

?
?
2、插入操作
我們考慮在樹上插入一個節點。
先像二叉排序樹一樣找到應當插入節點的位置,插入該節點,並初始化該節點的值。
由於樹要滿足小根堆的性質,所以如果插入的節點的優先級小於它的根節點,就通過左旋/右旋進行調整。(不理解的可以看看上面那張圖)

void insert(int &k, int x) { //插入節點 
    if (k == 0) { //如果節點數為0,直接插入 
      k = ++ cnt;  
      size[k] = c[k] = 1; a[k] = x; pri[k] = rand();
      return;
    }
    size[k] ++;
    if (a[k] == x) c[k] ++;
    else if (x > a[k]) {
      insert(rson[k], x);
      if (pri[rson[k]] < pri[k]) zig(k);
    }
    else {
      insert(lson[k], x);
      if (pri[lson[k]] < pri[k]) zag(k);
    }
}

?
?
3、刪除操作
考慮在樹上刪除一個節點。
首先先在樹上找到該點。
對於這個點,有兩種情況:要麽它有一個或沒有兒子,要麽它有兩個兒子。
先考慮只有一個或沒有兒子的情況,顯然這個點可以直接刪除,將它的子樹接到它的父節點上即可。
對於有兩個兒子的情況,顯然是不能直接刪除這個點的。(因為無法確定左右子所應接在父節點上的位置)
所以對於有兩個兒子的情況,可以通過左旋/右旋將待刪除的點向葉子節點處旋轉,直到只有一個或沒有兒子為止。
在旋轉時,如果改點的左兒子的優先級比右兒子小,就右旋;如果大,就左旋。

void del(int &k, int x) { //刪除 
    if (k == 0) return; //沒有該節點就直接退出 
    if (a[k] == x) {
      if (c[k] > 1) c[k] --, size[k] --;
      else {
        if (!lson[k] || !rson[k]) k = lson[k] + rson[k];
        else if (pri[lson[k]] < pri[rson[k]]) zag(k), del(k, x);
        else zig(k), del(k, x);
      }
    }
    else if (x > a[k]) size[k] --, del(rson[k], x);
    else size[k] --, del(lson[k], x);
}

?
?
4、查詢一個數的排名
查詢排名,就是查詢該數是這些數中第幾小的。
查詢排名的方法和二叉排序樹一樣,這裏就不再說了。

int query_rank(int &k, int x) { //查詢排名 
    if (k == 0) return 2e9;
    if (a[k] == x) return size[lson[k]] + 1;
    if (x > a[k]) return size[lson[k]] + c[k] + query_rank(rson[k], x);
    else return query_rank(lson[k], x);
}

?
?
5、查詢排名第x的數是什麽
即是詢問這些數中從小到大排後第x大的數是什麽。
查詢方法也和二叉排序樹一樣,直接放代碼。

int query_num(int &k, int x) { //查詢排名為x的數 
    if (k == 0) return 2e9;
    if (x <= size[lson[k]]) return query_num(lson[k], x);
    x -= size[lson[k]];
    if (x <= c[k]) return a[k];
    x -= c[k];
    return query_num(rson[k], x);
}

?
?
6、查詢數x的前驅
即查詢比x小的最大的數。
和二叉排序樹一樣。如果x比該數大,就往右走;否則往左走。

int query_pre(int &k, int x) { //查詢前驅 
    if (k == 0) return -2e9;
    if (a[k] < x) return max(a[k], query_pre(rson[k], x));
    else return query_pre(lson[k], x);
}

?
?
7、查詢數x的後繼
即查詢比x大的最小的數。
也和二叉排序樹一樣。如果x比該數小,就往左走;否則往右走。

int query_next(int &k, int x) { //查詢後繼 
    if (k == 0) return 2e9;
    if (a[k] > x) return min(a[k], query_next(lson[k], x));
    else return query_next(rson[k], x);
}

?
?
四、treap模板
放一題treap的模板題。
bzoj3224

代碼如下:

#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 100005;
int n, cnt, root;
int lson[maxn], rson[maxn], size[maxn], a[maxn], c[maxn], pri[maxn];

int read(void) {
    char c; while (c = getchar(), (c < '0' || c > '9') && c != '-'); int x = 0, y = 1;
    if (c == '-') y = -1; else x = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; return x * y; 
}

int rand(){
    static int seed = 2333;
    return seed = (int)((((seed ^ 998244353) + 19260817ll) * 19890604ll) % 1000000007);
}

void up(int k) {
    size[k] = size[lson[k]] + size[rson[k]] + c[k];
}

void zig(int &k) { //左旋 
    int v = rson[k]; rson[k] = lson[v]; lson[v] = k;
    size[v] = size[k]; up(k); k = v;
}

void zag(int &k) { //右旋 
    int v = lson[k]; lson[k] = rson[v]; rson[v] = k;
    size[v] = size[k]; up(k); k = v;
}

void insert(int &k, int x) { //插入節點 
    if (k == 0) { //如果節點數為0,直接插入 
      k = ++ cnt;  
      size[k] = c[k] = 1; a[k] = x; pri[k] = rand();
      return;
    }
    size[k] ++;
    if (a[k] == x) c[k] ++;
    else if (x > a[k]) {
      insert(rson[k], x);
      if (pri[rson[k]] < pri[k]) zig(k);
    }
    else {
      insert(lson[k], x);
      if (pri[lson[k]] < pri[k]) zag(k);
    }
}

void del(int &k, int x) { //刪除 
    if (k == 0) return; //沒有該節點就直接退出 
    if (a[k] == x) {
      if (c[k] > 1) c[k] --, size[k] --;
      else {
        if (!lson[k] || !rson[k]) k = lson[k] + rson[k];
        else if (pri[lson[k]] < pri[rson[k]]) zag(k), del(k, x);
        else zig(k), del(k, x);
      }
    }
    else if (x > a[k]) size[k] --, del(rson[k], x);
    else size[k] --, del(lson[k], x);
}

int query_rank(int &k, int x) { //查詢排名 
    if (k == 0) return 2e9;  
    if (a[k] == x) return size[lson[k]] + 1;
    if (x > a[k]) return size[lson[k]] + c[k] + query_rank(rson[k], x);
    else return query_rank(lson[k], x);
}

int query_num(int &k, int x) { //查詢排名為x的數 
    if (k == 0) return 2e9;
    if (x <= size[lson[k]]) return query_num(lson[k], x);
    x -= size[lson[k]];
    if (x <= c[k]) return a[k];
    x -= c[k];
    return query_num(rson[k], x);
}

int query_pre(int &k, int x) { //查詢前驅 
    if (k == 0) return -2e9;
    if (a[k] < x) return max(a[k], query_pre(rson[k], x));
    else return query_pre(lson[k], x);
}

int query_next(int &k, int x) { //查詢後繼 
    if (k == 0) return 2e9;
    if (a[k] > x) return min(a[k], query_next(lson[k], x));
    else return query_next(rson[k], x);
}

int main() {
    n = read();
      while (n --) {
        int opt = read(), x = read();
          if (opt == 1) insert(root, x);
          else if (opt == 2) del(root, x);
          else if (opt == 3) printf("%d\n", query_rank(root, x));
          else if (opt == 4) printf("%d\n", query_num(root, x));
          else if (opt == 5) printf("%d\n", query_pre(root, x));
          else if (opt == 6) printf("%d\n", query_next(root, x));
      }
    return 0;
} 

treap詳解