Fhq-Treap

FHQ Treap

FHQ Treapfhq(範浩強)大神發明的非常好用非常有思想的函數式二叉搜索樹.

其主要操作分爲兩個merge, split.

fhq-treap的每個節點只存一個數字, 也就是說, n個相同元素會有n個點, 而用一個節點加一域count. 其實其他搜索樹也能這樣, 把相同的元素插入同元素節點的左子樹.

我們從頭寫Fhq-Treap, 首先定義結構體, 我使用指針版.

struct Treap{
    Treap * left, * right;
    int value, priority, size;
    Treap(){};
    Treap(Treap * l, Treap * r, int v, int p): left(l), right(r), value(v), priority(p){
        size = 1;// 初始節點的size 爲 1
    }
    void maintain(){
        size = left->size + right->size + 1;
    }
};
Treap * null = new Treap();
typedef pair<Treap *, Treap *> pTT;

注意我們自己創建一個節點null來作爲空指針, 我們要把null的size賦值爲0;

先無視maintain函數.

void init(){
    null->size = 0;
}
split

將一顆樹拆分成一顆所有節點的值均小於x的樹和一顆所有節點的值均大於x的樹.

空樹將被拆分成兩顆空樹(邊界條件).

在訪問過程中, 若訪問到一個節點的值大於x的節點, 則說明該節點右子樹中所有節點均比x大.

則將其左子樹拆分成一顆所有節點的值均小於x的樹和一顆所有元素均大於x的樹, 並將其左子樹拆分出的大於x的樹作爲該節點的左兒子.

則我們得到的兩顆樹爲, 以該節點爲根的所有節點的值均大於x的樹, 和該節點左子樹中部分節點構成的所有節點的值均小於x的樹.

反之, 若訪問到一個節點的值小於x的節點, 則說明該節點左子樹中所有節點的值均小於x.

則將其右子樹拆分成一顆所有元素的值均小於x的樹和一顆所有元素的值均大於x的樹, 並將其右子樹拆分出的那顆所有元素的值均小於x的樹作爲該節點的右子樹.

則我們得到的兩顆樹爲, 以該節點爲根的所有元素均小於x的樹, 和該節點右子樹中部分節點構成的所有元素均大於x的樹

其實還有一種split是拆分出一顆樹的前k個元素, 理解上下兩句話後就很好就寫出了.

pTT split(Treap * root, int k){
    if(root == null){
        return make_pair(null, null);
    }
    root->spread();
    pTT result;
    if(k <= root->left->size){
        result = split(root->left, k);
        root->left = result.second;
        root->update();
        result.second = root;
    }
    else{
        result = split(root->right, k - root->left->size - 1);
        root->right = result.first;
        root->update();
        result.first = root;
    }
    return result;
}

我們這裏寫出這麼兩個函數, 這兩個函數都是按值劃分的, 不同之處在於見註釋.

兩個劃分函數只有有名字和一個等號的區別, 先寫出一個, 直接粘貼另一個.

pTT split_l(Treap * o, int val){// 與val相同的節點, 在左邊的樹上
    if(o == null){
        return make_pair(null, null);
    }
    pTT result;
    if(o->value <= val){
        result = split_l(o->right, val);
        o->right = result.first;
        // o->maintain();// 此時result保存兩顆維護完成的樹, o的兩個孩子都知道具體size
        result.first = o;
    }
    else{
        result = split_l(o->left, val);
        o->left = result.second;
        // o->maintain();
        result.second = o;
    }
    return result;
}
pTT split_r(Treap * o, int val){// 與val相同的節點, 在右邊的樹上
    if(o == null){
        return make_pair(null, null);
    }
    pTT result;
    if(o->value < val){
        result = split_r(o->right, val);
        o->right = result.first;
        // o->maintain();
        result.first = o;
    }
    else{
        result = split_r(o->left, val);
        o->left = result.second;
        // o->maintain();
        result.second = o;
    }
    return result;
}
merge

兩顆treap的根中, priority較小的爲新的treap的根(小根堆屬性).

下面大小treap是指, 小treap中任一元素都小於大treap中任一元素.

若小treap的根爲新treap的根, 那麼大treap不會對其左子樹造成任何影響, 修改小treap的右子樹爲其右子樹與大treap合併的結果.

反之, 小treap不會對大treap的右子樹造成影響, 修改大treap的左子樹爲其左子樹與小treap合併後的結果.

Treap * merge(Treap * left, Treap * right){
    if(left == null){
        return right;
    }
    if(right == null){
        return left;
    }
    if(left->priority < right->priority){
        left->right = merge(left->right, right);
        // left->maintain();// left的兩顆子樹都已經維護完成
        return left;
    }
    else{
        right->left = merge(left, right->left);
        // right->maintain();
        return right;
    }
}
insert

所謂插入, 就是按照要插入的值val, 劃分兩顆樹, 在來兩次合併.

void insert(Treap * & root, int val){// 注意這裏我們要傳引用
    pTT tree = split_l(root, val);
    Treap * t = new Treap(null, null, val, rand());
    root = merge(merge(tree.first, t), tree.second);
}
remove

刪除操作這麼理解, 最後劃分三棵樹, 小於val的樹, 等於val的樹, 大於val的樹.

我們在等於val的樹裏, 隨便刪除一個節點即可. 這裏就是刪除根節點.

void remove(Treap * & root, int val){
    pTT tree = split_l(root, val);
    pTT left = split_r(tree.first, val);

    left.second = merge(left.second->left, left.second->right);
    root = merge(merge(left.first, left.second), tree.second);
}
precursor

所謂求前驅操作在無旋Treap中分外簡單, 通過一次split即可.

這裏沒有用findKth()函數.

int getMax(Treap * root){
    while(root->right != null){
        root = root->right;
    }
    return root->value;
}
int getPre(Treap * & root, int val){
    pTT tree = split_r(root, val);
    int rst = getMax(tree.first);
    root = merge(tree.first, tree.second);
    return rst;
}
successor

同前驅, 只是劃分函數選擇的不同.

int getMin(Treap * root){
    while(root->left != null){
        root = root->left;
    }
    return root->value;
}
int getSuc(Treap * & root, int val){
    pTT tree = split_l(root, val);
    int rst = getMin(tree.second);
    root = merge(tree.first, tree.second);
    return rst;
}
the first k

這個操作就需要維護以當前節點爲根的size了, 維護的方法就是在splitmerge進行的同時維護.

如果有遞歸的思維, 很容易就知道在那裏使用maintain函數了.

這裏的k是從1開始的, 比如第1小就是最小的了.

int findKth(Treap * root, int k){
    if(root == null){
        return 0;
    }
    if(k <= root->left->size){
        return findKth(root->left, k);
    }
    else if(k == root->left->size + 1){
        return root->value;
    }
    else{
        return findKth(root->right, k - root->left->size - 1);
    }
}
get rank

這個操作對於無旋Treap很簡單, 一次split即可.

int getRank(Treap * & root, int val){
    pTT tree = split_r(root, val);
    int rst = tree->size + 1;
    root = merge(tree.first, tree.second);
    return rst;
}

基本操作就是這些

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章