淺談無旋treap(fhq_treap)


一、簡介

無旋Treap(fhq_treap),是一種不用旋轉的treap,其代碼復雜度不高,應用范圍廣(能代替普通treap和splay的所有功能),是一種極其強大的平衡樹。

無旋Treap是一個叫做范浩強的大佬發明的(快%啊!)

在我們一起學習無旋Treap之前,本蒟蒻有幾句活想說:

1.無旋Treap我個人認為是最容易理解的一種平衡樹,而且編程復雜度不高,功能還那么強大。

 我一開始學平衡樹的時候是先從普通的帶旋轉的Treap開始學的。那種Treap,我現在都沒搞懂什么左旋右旋究竟是怎么一回事。

 我當時真的以為我這輩子都不指望學會平衡樹了,直到djq大佬(快%!)告訴了我這種很美妙的數據結構。

 我真就不明白了,為什么那么多人願意去學普通的Treap而不學無旋Treap。無旋Treap更容易理解,最主要的是他與普通的Treap相比能可持久化!(盡管我不會

 所以,在大家學習完無旋Treap以后,本蒟蒻請諸君不妨推廣一下無旋Treap,造福更多的Oier。

 2.關於無旋Treap和其他平衡樹的比較:(這個建議大家學完無旋Treap再來看,可能感觸會更深刻一些)

  與AVL相比:旋轉操作真的很浪費時間,最壞情況下復雜度為O(log n),而且AVL樹難寫無比,不適合運用於算法競賽。

  與普通Treap相比:參見第一條

  與splay相比:基本上可以代替splay的所有功能,但是在處理LCT問題上沒有splay優秀

  與rbt(紅黑樹相比):紅黑樹特別難寫是眾所周知的。

  與sbt相比:sbt是我認為的最強大的平衡樹。但是無旋Treap中的merge、split操作的應用的廣泛(可持久化Treap維護Hash之類的)是sbt做不到的。

二、Treap

  什么是Treap?

  Treap=Tree+heap

  相信大家都知道二叉堆吧。父節點的權值比子節點都要大(或小)

  而Treap,則是在BST(二叉查找樹)的基礎上,添加二叉堆中的這個元素。

  Treap與heap的區別是,heap是完全二叉樹,而Treap不是。

  下面的

三、核心操作

我在前面說過,普通的Treap最煩人的地方便是旋轉。

而無旋Treap是如何做到無旋的呢?

關鍵就在兩個操作:merge和split

1.split

 split,顧名思義,就是把一個平衡樹分成兩棵樹。

 split有兩種:一種是按照權值split,一種是按照size來split。

 如果按照權值split,那么分出來兩棵樹的第一棵樹上的每一個數的大小都小於(或小於等於,視具體情況而定)x;

 如果按照size split,那么分出來兩棵樹的第一棵樹恰好有x個節點。

 我們可以結合具體代碼講解: 

 

inline void split(int k,int& l,int& r,int x){//理解時,我們可以把l當做是答案的第一個,r當做答案的第二個,這個函數的意義是:將以k為根的樹按照val分為以l為根的樹和以r為根的樹。注意引用的作用
    if(!k){
        l=r=0;//分到底了,返回
        return;
    }
    if(tree[k].val<x){//如果比它小
        l=k;//那么x肯定在k的右子樹里,先將k貼到第一個答案上
        split(tree[l].r,tree[l].r,r,x);//把第一個答案的右子樹按x分開,得到答案(這里自己理解一下,不難懂)
    }else{//反之亦然
        r=k;
        split(tree[r].l,l,tree[r].l,x);
    }
    push_up(k);
}

  而按size split的道理是一樣的:

inline void split(int k,int& l,int& r,int x){
    if(!k){
        l=r=0;
        return;
    }
    if(tree[tree[k].l].size+1<=x){
        l=k;
        split(tree[l].r,tree[l].r,r,x-tree[tree[k].l].size-1);//注意這里有些變化 
    }else{
        r=k;
        split(tree[r].l,l,tree[r].l,x);
    }
    push_up(k);
}

這樣就把一棵樹分開了。

2.merge

 merge就是把兩顆原本分開的樹合並在一起。

 我們仍然結合具體代碼講解

inline void merge(int& k,int l,int r){//函數名的意義是:把以l為根的樹和以r為根的樹合並為以k為根的樹 
    if(!l||!r){//合並到底,返回 
        k=l+r;
        return;
    }
    if(tree[l].p>tree[r].p){//默認大根堆 
        k=l;//先把l掛到k上 
        merge(tree[k].r,tree[k].r,r);//注意要滿足BST性質
    }else{//反之亦然 
        k=r;
        merge(tree[k].l,l,tree[k].l);
    }
    push_up(k);
} 

 有了merge和split,其他平衡樹的基本操作就好做多了

三、其他操作

inline void insert(int val){
    int x,y;
    split(root,x,y,val-1);
    merge(x,x,New(val));
    merge(root,x,y);
}
inline void Delete(int val){
    int x,y,z;
    split(root,x,y,val);
    split(x,x,z,val-1);
    merge(z,tree[z].l,tree[z].r);
    merge(x,x,z);
    merge(root,x,y);
}
inline int rnk(int val){
    int x,y;
    split(root,x,y,val-1);
    int ans=tree[x].size+1;
    merge(root,x,y);
    return ans;
}
inline int kth(int k){
    int x=root;
    while(true){
        if(k==tree[tree[x].l].size+1) return tree[x].val;
        if(k<=tree[tree[x].l]) x=tree[x].l;
        else k-=tree[tree[x].l].size+1,x=tree[x].r;
    }
}
inline int pre(int val){
    int x,y;
    split(root,x,y,val-1);
    int ans=tree[x].val;
    merge(root,x,y);
    return ans;
}
inline int suf(int val){
    int x,y;
    split(root,x,y,val);
    int ans=kth(tree[x].size+1);
    merge(root,x,y);
    return ans;
}

最后給大家放上一道模板題P3369 【模板】普通平衡樹

如有不足請指正,謝謝

  


免責聲明!

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



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