本篇隨筆詳細講解一下一種隨機化資料結構——樹堆(\(treap\))。
首先給乙個字串等式:
\[treap=tree+heap
\]所以\(treap\)樹堆其實就是樹+堆。樹是二叉查詢樹\(bst\),堆是二叉堆,大根堆小根堆都可以。
關於\(bst\)的相關知識,請看官走這邊:
bst詳解
樹堆既是一棵二叉查詢樹,也是乙個二叉堆。但是這兩種資料結構貌似還是矛盾的存在,如果是二叉查詢樹,就不能是乙個堆,如果是乙個堆,那麼必然不是二叉查詢樹。
所以樹堆用了乙個很巧妙的方式解決這個問題:給每個鍵值乙個隨機附加的優先順序,讓鍵值滿足二叉查詢樹的結構,讓優先順序滿足二叉堆的結構。
其中大寫字母是鍵值,滿足\(bst\)結構。數是優先順序,滿足小根堆結構。
我們在\(bst\)中講過,普通的\(bst\)具有很強的不確定性,如果資料特殊,建樹的時候可能直接變成一條鏈。不僅如此,插入刪除的時候也很麻煩。因為如果插入或者刪除,整個樹原來的結構就會被打亂,這會為遍歷和查詢帶來災難性的後果。
所以我們推出了平衡樹。就是通過將樹旋轉來動態維護這個樹形態是平衡的,這樣查詢的複雜度就是\(o(log)\)級別的,是一種穩定的複雜度。
樹堆是一種平衡樹,它通過為鍵值(也就是我們需要維護成\(bst\)的)賦予優先順序,使之也滿足堆結構來進行旋轉,成為一棵平衡樹。
但是我們需要注意一點:樹堆的優先順序是隨機賦予的。也就是說,這個資料結構其實是乙個隨機化的資料結構。這不是樹堆的缺點,因為只有隨機化賦予優先順序,才有可能保證樹堆的複雜度是\(o(log)\)的級別。
那麼,上述性質也說明了,樹堆並不是乙個規則形態的二叉樹,更不是堆需要滿足的完全二叉樹。甚至它也不符合平衡樹的定義:每個節點左右子樹高度相差\(\le1\),所以我們說樹堆是近似實現平衡。
但是通過形態定義二叉樹的方式並不絕對。我們換一種方式來對平衡樹進行定義:
能夠保證時間複雜度的\(bst\),就是平衡樹。
首先當我們理解\(treap\)的操作的時候,需要先對旋轉這個事情有乙個大體的定義。
上圖:
光看這個箭頭的話,還是很容易理解什麼是旋轉的。但是可能給讀者造成困擾的是:這個b節點的父親怎麼變了?
原因是這樣的:我們在進行旋轉操作的時候,要保證\(bst\)的節點遍歷順序是一樣的,而\(bst\)的節點遍歷順序是中序遍歷(這個是按\(bst\)的定義來的),也就是說只有這樣才能保證遍歷序不變的情況下調換節點位置。
所以,針對這個圖,我們把「p/f」看成一對節點,就能很好地理解這個「左/右旋」的操作了。
首先我們了解一下\(bst\)的插入方式。其實很簡單,就是乙個新節點插進去,從根節點開始不停地與當前節點比大小,一直到這個節點成為葉子節點為止。
因為\(treap\)要同時維護\(bst\)和堆,所以我們還需要在裡面加上旋轉操作。
如果是右兒子的話要左旋,左兒子要右旋(旋轉操作請結合上圖理解)。
然後我們又多統計了乙個size的資料,這個資料表示子樹大小,在統計當前數x是第幾大的時候會很方便。
應該很簡單。
struct node
tree[maxn];
int tot;
void l_rotate(int &pos)
void r_rotate(int &pos)
void maintain(int pos)
void insert(int &pos,int x)
if(x刪除操作的大體思路和插入是一樣的。也是要保證刪除前後滿足\(treap\)的雙重結構。
大致是這樣的思路:首先找到這個點在哪。然後,如果這個點已經是葉子節點,就直接將其刪除,如果不是,就一層層地將它轉到底部,然後進行刪除。這和我們的插入操作有異曲同工之妙,就是進行操作的一定是葉子節點,如果不是葉子的話是不能粗暴刪除的。需要轉。
void remove(int &pos,int x)
else
pos=0;
}else
#define inf 1e9
int prev(int pos,int x)
treap的遍歷是中序遍歷,遵循先左後右的原則。
void dfs(int pos)
可旋轉Treap 樹堆 總結
樹堆,在資料結構中也稱treap,是指有乙個隨機附加域滿足堆的性質的二叉搜尋樹,其結構相當於以隨機資料插入的二叉搜尋樹。其基本操作的期望時間複雜度為o logn 相對於其他的平衡二叉搜尋樹,treap的特點是實現簡單,且能基本實現隨機平衡的結構。在深入了解treap之前,我們先來了解一下bst。bs...
Treap樹堆非旋轉演算法
核心操作 與合併 split 有兩種 按權值 與按位置 排名 如果按權值 split 那麼分出來兩棵樹的第一棵樹上的每個數的大小都小於 或者小於等於,視具體情況 x。如果按位置 split 那麼分出來兩棵樹的第一棵樹上恰好有x個結點。按權值 inline void split int k,int l...
線段樹套Treap
題目為bzoj1901.單點修改區間第k大,如果卡記憶體你要怎麼辦 hq說線段樹套平衡樹比樹狀陣列套線段樹好得多 所以我就寫了 然後就寫了 n log 3n 在zju上t掉了qaq 卡記憶體還卡時間真是有夠過分 其實就是外層線段樹存一下根然後按照正常的treap寫就好了,但是有乙個問題就是treap...