1. 程式人生 > >[Treap][學習筆記]

[Treap][學習筆記]

rip section 及其 min 當前 imageview urn 其他 cimage

平衡樹

平衡樹就是一種可以在log的時間復雜度內完成數據的插入,刪除,查找第k大,查詢排名,查詢前驅後繼以及其他許多操作的數據結構。

Treap

treap是一種比較好寫,常數比較小,可以實現平衡樹基本操作的一種平衡樹。treap的平衡是基於隨機化。是將堆與二叉查找樹結合起來所得到的數據結構。

treap在插入數時,給每個數賦了一個新的隨機的值id。在以後的操作中,必須始終使得treap中的id構成一個堆的形態才可以。這樣就達到了平衡的目的。

定義

struct node {
    int ch[2],val,id,cnt,siz;
}TR[N];

ch[0/1]分別表示左右兒子。

val是插入的數

id是賦給這個數的一個隨機的值

cnt表示這個數字出現的次數

siz為在treap中以這個點為根的數字的個數

更新

void up(int cur) {
    TR[cur].siz = TR[ls].siz + TR[rs].siz + TR[cur].cnt;
}

就是維護一下siz

旋轉

f表示將左(0)還是右(1)兒子旋轉上來。

如圖

技術分享圖片

加入我們現在要把2旋轉到6這個位置,那麽旋轉之後6肯定成為了2的右兒子,那4去哪裏呢。我們就把4變成6的左兒子,這樣就完成了旋轉。然後在維護一下siz就行了。

就成了這樣

技術分享圖片

因為treap不存父親,所以這裏要取地址,把6換成2

void rotate(int &cur,int f) {
    int son = TR[cur].ch[f];
    TR[cur].ch[f] = TR[son].ch[f ^ 1];
    TR[son].ch[f ^ 1] = cur;
    up(cur);
    cur = son;
    up(cur);
}

插入

插入一個元素時我們只要按照與二叉查找樹相同的方法,找到一個合適的位置插入即可。如果這個元素以前已經插入過了。那麽只要把這個數的cnt++就行了。插入完成之後,還要維護id滿足堆的形態這個條件。所以如果插入之後的兒子的id比當前節點的id小了,那麽就將兒子旋轉上來就行了。

void insert(int &cur,int val) {
    if(!cur) {
        cur = ++tot;
        TR[cur].val = val;
        TR[cur].id = rand();
        TR[cur].cnt = TR[cur].siz = 1;
        return;
    }
    TR[cur].siz++;//!!
    if(TR[cur].val == val) { TR[cur].cnt++;return;}
    int d = val > TR[cur].val;
    insert(TR[cur].ch[d],val);
    if(TR[TR[cur].ch[d]].id < TR[cur].id) rotate(cur,d);
}

刪除

首先找到要刪除的節點,根據這個節點兒子的個數可以分為兩種情況。

情況1:這個節點有1個或者0個兒子。那麽直接將這個節點變為這個節點的兒子(沒有就是0)就行了

情況2:這個節點有2個兒子。那麽不斷的往下旋轉這個節點,知道滿足情況1。在旋轉的時候應該註意,因為要滿足堆這個條件。所以應該將兒子中id較小的那個旋轉上來。

void del(int &cur,int val) {
    if(!cur) return;
    if(val == TR[cur].val) {
        if(TR[cur].cnt > 1) {TR[cur].cnt--;TR[cur].siz--; return;}
        if(!ls || !rs) {cur = ls + rs;return;}
        int d = TR[rs].id < TR[ls].id;
        rotate(cur,d);
        del(cur,val);
    }
    else TR[cur].siz--,del(TR[cur].ch[val > TR[cur].val],val);
}

查找排名

這個就真的和二叉搜索樹一樣了。如果要找的那個數字比當前節點大。那麽就將排名加上左子樹大小,然後搜右子樹,否則搜左子樹。

int Rank(int cur,int val) {
    if(!cur) return 0;
    if(val == TR[cur].val) return TR[ls].siz + 1;
    if(val < TR[cur].val) return Rank(ls,val);
    return Rank(rs,val) + TR[ls].siz + TR[cur].cnt;
}

查找第k大(排名為k的元素)

和查找排名差不多。只要不斷的記錄下要查找當前子樹中的第幾大。如果比左子樹大小加上根節點大小還大,那麽就減去左子樹大小和根大小,並查找右子樹,否則查找左子樹

int kth(int cur,int now) {
    while(1) {
        if(TR[ls].siz >= now) cur = ls;
        else if(TR[ls].siz + TR[cur].cnt < now) now -=TR[ls].siz + TR[cur].cnt,cur = rs;
        else return TR[cur].val;
    }
}

前驅

還是從根往下搜索,如果要找的數比當前根節點要大,那麽就搜索右子樹,並將搜到的答案與當前根取max,否則就搜索左子樹

int pred(int cur,int val) {
    if(!cur) return -INF;
    if(val <= TR[cur].val) return pred(ls,val);//!!!
    return max(pred(rs,val),TR[cur].val);
}

後繼

跟前驅同理

int nex(int cur,int val) {
    if(!cur) return INF;
    if(val >= TR[cur].val) return nex(rs,val);//!!!
    return min(nex(ls,val),TR[cur].val);
}

一言

迷途經累劫,悟則剎那間。 ——六祖壇經

[Treap][學習筆記]