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;
}
基本操作就是这些