Fhq-Treap

FHQ Treap

FHQ Treapfhq(范浩强)大神发明的非常好用非常有思想的函数式二叉搜索树.

其主要操作分为两个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了, 维护的方法就是在splitmerge进行的同时维护.

如果有递归的思维, 很容易就知道在那里使用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;
}

基本操作就是这些

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值