FHQ Treap
FHQ Treap是fhq(範浩強)大神發明的非常好用非常有思想的函數式二叉搜索樹.
其主要操作分爲兩個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了, 維護的方法就是在split和merge進行的同時維護.
如果有遞歸的思維, 很容易就知道在那裏使用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;
}
基本操作就是這些