史上最強圖解Treap總結, 不是淺談!
大家都很強, 可與之共勉。
Treap = Tree + Heap.
樹堆,在資料結構中也稱Treap,是指有一個隨機附加域滿足堆的性質的二叉搜尋樹,其結構相當於以隨機資料插入的二叉搜尋樹。其基本操作的期望時間複雜度為O(logn)。相對於其他的平衡二叉搜尋樹,Treap的特點是實現簡單,且能基本實現隨機平衡的結構。
Treap 維護堆的性質的方法只用到了左旋和右旋, 程式設計複雜度比Splay小一點, 並且在兩者可完成的操作速度有明顯優勢
開始
每一個節點需要儲存至少四個資訊,當前節點的數值 ( val ), 優先順序 ( key ), 左右兒子 ( ls, rs )。 除此之外, 可能還會儲存以該節點為根的樹的大小 ( siz ), 以及該節點相同的數個數 ( same )。
typedef class TreapNode {
public:
int val;
int siz, key, same;
TreapNode *ls, *rs;
inline TreapNode ( ) { }
inline TreapNode ( int val, TreapNode* & node ) : val ( val ), key ( rand ( ) ), siz ( 1 ), same ( 1 ) { ls = rs = node; }
inline void update ( ) {
siz = ls -> siz + rs -> siz + same;
}
} Node;
當然, 博主是用的指標實現, 指標相比陣列有更多的細節需要注意。 在BZOJ-3224中大概比RBT慢2ms。
關於隨機函式
< cstdlib > 中的rand ( ) 速度比較慢, 而在資料結構中對於素的如果要求過高, 可以使用手寫 rand ( )。
inline int rand ( ) {
static int seed = 233;
return seed = ( int ) seed * 482711LL % 2147483647;
}
其中seed可以隨便取一個非零的數。 具體原理是什麼, 當然我也不會證明了。
首先是旋轉 操作
分為左旋和右旋, 我習慣用Zag, Zig來叫。
圖解一下
具體的程式碼如下
一定要記住的是當前節點的要旋轉節點為null時, 不轉。 否則root很有可能變成null, 將會影響一系列的操作。
inline void Zig ( Node* &nd ) {
tmp = nd -> ls;
if ( tmp == null ) return;
nd -> ls = tmp -> rs;
tmp -> rs = nd;
tmp -> siz = nd -> siz;
nd -> update ( );
nd = tmp;
}
inline void Zag ( Node* &nd ) {
tmp = nd -> rs;
if ( tmp == null ) return;
nd -> rs = tmp -> ls;
tmp -> ls = nd;
tmp -> siz = nd -> siz;
nd -> update ( );
nd = tmp;
}
接下來是插入操作
插入的過程中, 一定要滿足Treap的特點。
即左兒子的值比當前節點小, 右兒子的值比當前節點大。
與維護關於key值堆的性質
看完, 你一定會理解原理, 並且熟練掌握。
插入值為18,優先順序為20的結點後,違反了最小堆的定義,因此要進行調整,把優先順序小的往上提,也就是小的優先順序插入的是右子樹,那麼需要進行一次左旋轉( Zag ),這裡進行一次旋轉過後就OK了。
同樣,這種情況左旋轉,旋轉後發現還是不滿足最小堆的定義,並且小優先順序的結點在左子樹,所以還需要進行右旋轉 ( Zig ),如下圖所示:
右旋後,還是不滿足性質, 還需要左旋 ( Zag ):
當然這是在遞迴呼叫之中實現的。
如果該節點為null, 就新開一個。 用建構函式會很方便。
如果遇到插入兩個相同的值, 那麼該節點的same直接+1就好。
完成每一個節點的插入, 都要update
這個update()操作的優秀之處就在於null空節點, 若使用NULL系統自帶的空指標, 那麼直接指向左右兒子就會指出去, 報錯。。。
程式碼如下:
inline void Insert ( Node* &nd, int& val ) {
if ( nd == null ) {
nd = ++tail;
*nd = Node ( val, null );
return;
}
if ( nd -> val == val ) ++nd -> same;
else {
if ( val > nd -> val ) {
Insert ( nd -> rs, val );
if ( nd -> rs -> key < nd -> key )
Zag ( nd );
} else {
Insert ( nd -> ls, val );
if ( nd -> ls -> key < nd -> key )
Zig ( nd );
}
}
nd -> update ( );
}
之後是刪除操作
注意:
這是二叉樹刪除法
對比插入, 相對於刪除要複雜一些, 可以直接看程式碼明白。
不要忘了update()!!!
inline void Delete ( Node* &nd, int x ) {
if ( nd == null ) return;
if ( nd -> val == x ) {
if ( nd -> same > 1 ) {
--nd -> same; nd -> update ( ); return;
}
if ( nd -> ls == null && nd -> rs == null ) { nd = null; return; }
else if ( nd -> ls == null && nd -> rs ) nd = nd -> rs;
else if ( nd -> ls && nd -> rs == null ) nd = nd -> ls;
if ( nd -> ls -> key < nd -> rs -> key ) { Zig ( nd ); Delete ( nd -> rs, x ); }
else { Zag ( nd ); Delete ( nd -> ls, x ); }
} else if ( x > nd -> val ) Delete ( nd -> rs, x );
else Delete ( nd -> ls, x );
nd -> update ( );
}
主要的就是這兩個那麼還有前驅,後驅,排名,第K大。
那麼唯一注意的是前後驅在題目中的定義。
如BZOJ-1588
前後驅可以等於節點數值本身
那麼就應該這麼寫
inline int query_pre ( Treap* &nd, int val ) {
if ( nd == null ) return 0xefefefef;
if ( val < nd -> val )
return query_pre ( nd -> ls, val );
return max ( nd -> val, query_pre ( nd -> rs, val ) );
}
inline int query_post ( Treap* &nd, int val ) {
if ( nd == null ) return 0x7fffffff;
if ( val > nd -> val )
return query_post ( nd -> rs, val );
return min ( nd -> val, query_post ( nd -> ls, val ) );
}
如果是BZOJ-3224, 讀錯題了就Wa爽了。。。
inline void query_pre ( Node* &nd, int &ans, int val ) {
if ( nd == null ) return;
if ( val > nd -> val )
ans = nd -> val, query_pre ( nd -> rs, ans, val );
else query_pre ( nd -> ls, ans, val );
}
inline void query_post ( Node* &nd, int &ans, int val ) {
if ( nd == null ) return;
if ( val < nd -> val )
ans = nd -> val, query_post ( nd -> ls, ans, val );
else query_post ( nd -> rs, ans, val );
}
當然根據定義也可以用kth 與 rank 兩者一起求出前後驅。
具體程式碼及細節請讀者自己推敲
給出完整版參考程式碼
注意的是 Init( ) 中的 null 維護的節點資訊。
#include <bits/stdc++.h>
unsigned int seed, n, opt;
int ans, x;
inline int rand ( ) {
return seed = ( int ) seed * 482711LL % 2147483647;
}
typedef class TreapNode {
public:
int val;
int siz, key, same;
TreapNode *ls, *rs;
inline TreapNode ( ) { }
inline TreapNode ( int val, TreapNode* & node ) : val ( val ), key ( rand ( ) ), siz ( 1 ), same ( 1 ) { ls = rs = node; }
inline void update ( ) {
siz = ls -> siz + rs -> siz + same;
}
} Node;
Node *pool, *root, *tail, *null, *tmp;
inline void Init ( ) {
seed = 233;
pool = new Node [ n + 5 ];
root = null = tail = pool;
null -> siz = 0, null -> same = 0, null -> val = 0, null -> key = rand( ), null -> ls = null -> rs = null;
}
inline void Zig ( Node* &nd ) {
tmp = nd -> ls;
if ( tmp == null ) return;
nd -> ls = tmp -> rs;
tmp -> rs = nd;
tmp -> siz = nd -> siz;
nd -> update ( );
nd = tmp;
}
inline void Zag ( Node* &nd ) {
tmp = nd -> rs;
if ( tmp == null ) return;
nd -> rs = tmp -> ls;
tmp -> ls = nd;
tmp -> siz = nd -> siz;
nd -> update ( );
nd = tmp;
}
inline void Insert ( Node* &nd, int& val ) {
if ( nd == null ) {
nd = ++tail;
*nd = Node ( val, null );
return;
}
if ( nd -> val == val ) ++nd -> same;
else {
if ( val > nd -> val ) {
Insert ( nd -> rs, val );
if ( nd -> rs -> key < nd -> key )
Zag ( nd );
} else {
Insert ( nd -> ls, val );
if ( nd -> ls -> key < nd -> key )
Zig ( nd );
}
}
nd -> update ( );
}
inline int kth ( Node* &nd, int k ) {
int tmp = nd -> ls -> siz + nd -> same;
if ( nd -> ls -> siz < k && k <= tmp ) return nd -> val;
return nd -> ls -> siz >= k ? kth ( nd -> ls, k ) : kth ( nd -> rs, k - tmp );
}
inline void Delete ( Node* &nd, int x ) {
if ( nd == null ) return;
if ( nd -> val == x ) {
if ( nd -> same > 1 ) {
--nd -> same; nd -> update ( ); return;
}
if ( nd -> ls == null && nd -> rs == null ) { nd = null; return; }
else if ( nd -> ls == null && nd -> rs ) nd = nd -> rs;
else if ( nd -> ls && nd -> rs == null ) nd = nd -> ls;
if ( nd -> ls -> key < nd -> rs -> key ) { Zig ( nd ); Delete ( nd -> rs, x ); }
else { Zag ( nd ); Delete ( nd -> ls, x ); }
} else if ( x > nd -> val ) Delete ( nd -> rs, x );
else Delete ( nd -> ls, x );
nd -> update ( );
}
inline int query_rank ( Node* &nd, int x ) {
if ( nd == null ) return 0;
if ( nd -> val == x ) return nd -> ls -> siz + 1;
else return ( x > nd -> val ) ? nd -> ls -> siz + nd -> same + query_rank ( nd -> rs, x )
: query_rank ( nd -> ls, x );
}
inline void query_pre ( Node* &nd, int &ans, int val ) {
if ( nd == null ) return;
if ( val > nd -> val )
ans = nd -> val, query_pre ( nd -> rs, ans, val );
else query_pre ( nd -> ls, ans, val );
}
inline void query_post ( Node* &nd, int &ans, int val ) {
if ( nd == null ) return;
if ( val < nd -> val )
ans = nd -> val, query_post ( nd -> ls, ans, val );
else query_post ( nd -> rs, ans, val );
}
using std :: cin;
using std :: cout;
using std :: endl;
int main ( ) {
cin >> n;
Init ( );
register int i = 1;
loop :; {
cin >> opt >> x;
switch ( opt ) {
case 1:
Insert ( root, x ); break;
case 2:
Delete ( root, x ); break;
case 3:
cout << query_rank( root, x ) << endl; break;
case 4:
cout << kth ( root, x ) << endl; break;
case 5:
query_pre ( root, ans, x ); cout << ans << endl; break;
case 6:
query_post ( root, ans, x ); cout << ans << endl; break;
}
} if ( ++i <= n ) goto loop;
}