[平衡树]Treap实现Rank Tree

引入

在之前的介绍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)的三倍的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值