前置知識:二叉排序樹,堆。
應用場景:平衡樹。
我們都知道,二叉排序樹就是滿足「\(lch」(左兒子小於根節點,右兒子大於根節點)的二叉樹,一般情況下插入、刪除和搜尋的時間複雜度都為 \(\theta(\log n)\) ,非常快。但在特殊情況下,二叉排序樹可能會退化成鏈,時間複雜度也會變為 \(\theta(n)\) 。只有當二叉排序樹平衡時速度最優。
堆也很簡單,就是根節點大於子節點的完全二叉樹。
當二叉排序樹退化成鏈時速度會大打折扣,因此很多毒瘤出題者都喜歡卡它。但是既然能有這種題目,那麼肯定就有能解決的方法,當然也不排除出題者自己都沒有做出來。二叉排序樹平衡時時間複雜度為 \(\theta(\log n)\) 。所以,若要保證二叉排序樹的最大時間複雜度為 \(\theta(\log n)\) ,就要保證該二叉排序樹平衡。這就是是平衡樹。
平衡樹有很多種,本文就不再贅述,僅討論treap
。
二叉排序樹和堆都是二叉樹。既然堆就是完全二叉樹,何不利用它這個性質來保證二叉排序樹平衡呢?
於是tree
(二叉排序樹)+heap
(堆)=treap
,treap
橫空出世!
這裡我定義了乙個結構體treap
來封裝,然後再treap
內又定義了結構體node
,表示節點。
struct treap
e[100005],*root,*cnt;
};
解讀:
e
:儲存所有節點。
root
:指向根節點的指標。
cnt
:指向最後乙個插入的節點的指標。
如果沒能看懂各變數的作用也沒有關係,在後面的操作中會為您一一解答。
2-0-1 建構函式
node(){}
// 插入節點時用
node(const int &x)
優化
c++
自帶的rand
函式速度較慢,我們可以自己寫乙個mrand
函式來取代:
inline unsigned mrand()
然後建構函式如下:
node(){}
// 插入節點時用
node(const int &x)
2-0-2 更新節點資訊
不解釋:
inline void pushup()
現在node
實現如下:
struct node
node(const int &x)
inline void pushup()
};
下面實現操作的函式均為treap
的成員函式。
附:除錯函式,輸出當前樹的詳細資訊:
// now 表示所操作子樹的根節點指標引用,indent 表示當前縮排長度
void output(node *&now,const int &indent)
// 初始函式(方便呼叫)
inline void output()
旋轉是很多平衡樹常見的操作,分為左旋和右旋。
左旋的操作如下:
右旋的操作與此類似,僅方向不同。事實上,右圖中的樹右旋後即可得到左圖。
實現的**也很簡單:
// now 表示所操作子樹的根節點指標引用,d 表示旋轉方向(0 表示右旋,1 表示左旋)
// 這裡 now 為引用型別,便於更改。
inline void rotate(node *&now,const bool &d)
那麼為什麼要旋轉呢?因為每個節點都會有乙個隨機優先值,而treap
的每個節點的優先值都比其子節點的大,利用堆的思想,使得treap
相對平衡。
treap
的插入其實就是在二叉排序樹的插入的基礎上通過旋轉保證treap
堆的性質。
// now 表示指向當前節點的指標的引用,x 表示要插入的值
// 這裡 now 為引用型別,便於更改。
void insert(node *&now,const int &x)
++(now->size); // 因為新節點在以 now 指向的節點為根節點的子樹內,所以當前子樹的節點數+1
// 如果當前節點的數值不等於 x
if(now->val!=x)
// 否則說明當前節點的數值等於 x
else ++(now->times); // 當前節點的數值的存在數量+1
now->pushup(); // 更新當前節點(之前我沒有加上,導致我調了好久的 bug)
}// 初始函式(方便呼叫)
inline void insert(const int &x)
首先找到要刪除的節點,然後通過旋轉將其下移,直到其沒有子節點時之間再將其直接刪除。
// now 表示指向當前節點的指標的引用,x 表示要刪除的值
// 這裡 now 為引用型別,便於更改。
void remove(node *&now,const int &x)
now->pushup();// 更新當前節點
}// 初始函式(方便呼叫)
inline void remove(const int &x)
這個與二叉排序樹的操作一樣。
// now 表示指向當前節點的指標的引用,x 表示要查詢的值
int getrank(node *&now,const int &x)
// 初始函式(方便呼叫)
inline int getrank(const int &x)
// now 表示指向當前節點的指標的引用,x 表示要查詢的排名
int getval(node *&now,const int x)
// 初始函式(方便呼叫)
inline int getval(const int &x)
// now 表示指向當前節點的指標的引用,x 表示要查詢的數值
int getprev(node *&now,const int &x)
// 初始函式(方便呼叫)
inline int getprev(const int &x)
與查詢前驅思路相同。
// now 表示指向當前節點的指標的引用,x 表示要查詢的數值
int getnext(node *&now,const int &x)
// 初始函式(方便呼叫)
inline int getnext(const int &x)
這是一道模板題,沒什麼好說的,直接上**:
Treap原理和實現方法
分類 資料結構 2013 09 07 14 11 320人閱讀收藏 舉報treap是一棵二叉搜尋樹,只是每個節點多了乙個優先順序fix,對於每個節點,該節點的優先順序小於等於其所有孩子的優先順序。當然,引入優先順序fix的目的就是防止bst退化成一條鏈,從而影響查詢效率。所以,這樣看來就是 trea...
Treap實現的名次樹
1.感覺之前邵叔叔教的做fib的那個支援刪除 其實刪除只是打標記,而且不支援插入。和查詢的排序二叉樹就是個靜態的名次樹嘛。我居然學到了這麼奇怪的資料結構。2.寫的時候坑了幾次 1 不要亂用引用,getkth裡那個引數o沒過腦子用了引用,結果把樹搞爛了,一堆ch x 賦值成null了。2 寫查詢的時候...
Treap的實現方法 BZOJ 3224
傳說,有一種排序二叉樹叫做treap。而 treap tree heap 所以,treap既具有樹,也具有堆的性質。它的基本操作和普通的樹相近,但也有一些差異。以上全部為亂講系列 如果要看詳細介紹,這裡給出lmy大神關於平衡樹的研究講解 詳細介紹之後會補充的 首先,它的儲存方式和其他的二叉樹類似,都...