引入
在之前的介绍Treap中,只有实现一般的BST的功能,相当于是一个STL的set,但是STL的set过度封装,所以对Treap稍加修改还可以实现一些其他的功能。
比如说Rank Tree(名次树)
基本概念
Rank Tree在这里是建立在Treap的基础上写的。主要功能包含了Treap的所有功能,以及查询某一个排名是哪个数字(kth操作),以及查询某一个数字的排名(rank操作)
实现
对于Treap来说,只需要在原来的点的定义中多加一个变量,s,表示以这个节点为根的子树的节点的数量。维护s也很容易,只需要一个函数maintain,每一次调用时重新自底向上地计算一下s。
那么代码大概就是这样的。
struct Node {
Node *ch[2];
int r, v;
int s; // 节点个数
int cmp(int x) const {
if(x == v) return -1;
return x < v ? 0 : 1;
}
void maintain() { // 根据子树重新计算s
s = 1;
if(ch[0] != NULL) {
s += ch[0] -> s;
}
if(ch[1] != NULL) {
s += ch[1] -> s;
}
}
} *root;
当然在发生旋转操作之后,s也有可能会改变,所以要去重新计算s
主要就是在旋转前k是o的儿子,旋转之后o成了k的儿子,所以重新计算时应该先算o再算k
代码如下
void rotate(Node* &o, int d) {
Node* k = o -> ch[d ^ 1];
o -> ch[d ^ 1] = k -> ch[d];
k -> ch[d] = o;
o -> maintain(); // o在k之前先被修改
k -> maintain();
o = k;
}
最后就是实现rank和kth操作了。
rank操作比较简单,如果是从小到大,就是相当于去找这个节点,往左走的时候就往左访问就好了,往右的时候加上左节点的s再加一1(包括当前的节点)再向右节点访问,如果刚好是根节点就直接加上左节点的s
kth操作类似,就是往左时不理,往右时减去左边的s再减1,直到k刚好等于左边的s加1时,就是这个根节点了。
具体的操作可以画图来理解。
代码如下
int kth(Node* o, int k) {
if(o == NULL || k > o -> s || k <= 0) {
return 0;
}
int s = (o -> ch[0] == NULL ? 0 : o -> ch[0] -> s);
if(k == s + 1) {
return o -> v;
} else {
if(k <= s) {
return kth(o -> ch[0], k);
} else {
return kth(o -> ch[1], k - s - 1);
}
}
}
int rank(Node* o, int x) {
if(o == NULL) {
return 0;
}
int res = 0;
int s = (o -> ch[0] == NULL ? 0 : o -> ch[0] -> s);
if(x <= o -> v) {
res += rank(o -> ch[0], x);
res += x == o -> v;
} else {
res += s + 1;
res += rank(o -> ch[1], x);
}
return res;
}
最终的代码其实就是在treap的基础上加了一些东西。
在做rank操作时,如果是一个没有在这棵treap里的点,那就需要先加上这个点,然后再做rank,最后把这个点删掉,这样的做法的时间也只是接近于O(logN)的三倍的。