Treap树概念及应用

概念

  • Treap一词是Tree和Heap的合成词,也就是这是一颗带有堆性质的树,即树堆,Treap树是一种排序二叉树(二叉搜索树、二分检索树 Binary Serach Tree),简称BST,也就是满足value值大小关系是左孩子<根<右孩子,这样就满足了排序二叉树,刚才讲过,Treap树还满足堆的性质,那么它的哪个值满足堆的性质呢,并不是value,而是一个优先级rank值,这个rank值是人为添加的,一般使用一个随机数,这个rank是为了维持二叉树的平衡而设定的,总的说来,对于键值来说,Treap是一棵排序二叉树,对于优先级来说,Treap是一个堆,当然根节点的优先级是最高的

实现

结构

  • 首先考虑树的组成,需要键值value,优先级Rank,为了方便统计排名,需要一个sz记录当前树的节点数,需要一个cnt记录当前数字出现了多少次,需要两个指针分别指向左孩子和右孩子,每次操作之后都需要更新当前树上节点数,所以写一个Update函数,为了方便判断当前元素和根节点value的大小关系,需要写一个cmp函数,如果小于则在左子树,大于在右子树,相等返回 − 1 -1 1
struct TreapNode{
   
   
    int value;
    int Rank;
    int cnt;
    int sz;
    TreapNode* son[2];
    int cmp(int x) const{
   
   
        if(x == value) return -1;
        return x < value ? 0 : 1;
    }
    void Update(){
   
   
        sz = 1;
        if(son[0] != NULL) sz += son[0]->sz;
        if(son[1] != NULL) sz += son[1]->sz;
    }
};
  • son[0]表示左孩子,son[1]表示右孩子

旋转操作

  • 为什么要旋转?考虑普通的排序二叉树,假设插入数字是有序的,那么二叉树就会退化为一条链,这样查询就和数组没有什么区别了,所以Treap树就制定了一个随机给的rank值,按照它的大小关系进行相应的旋转,使得二叉树保持平衡不退化为链

左旋和右旋

在这里插入图片描述

  • 上图针对于9进行了旋转,从左到右是右旋,从右到左是左旋,这里可以理解为右旋为顺时针,左旋为逆时针
右旋
  • 这个图,通俗一点讲,右旋要断三根线:待旋节点和它的父亲节点、待旋节点和左孩子、左孩子和它的右孩子,然后把左孩子的右孩子作为待旋节点的左孩子,待旋节点作为它左孩子的右孩子,再把待旋节点左孩子放在待旋节点的位置上,这里注意一点,我们要传的是待旋节点的引用,引用不改变地址,只改变值,这样我们才能让6到9的位置上而依然保持和9父亲之间的关系
左旋
  • 相对的,左旋是先向右,再向左,也就是找右孩子,再找右孩子的左孩子,让右孩子的左孩子作为待旋节点的右孩子,待旋节点作为它右孩子的左孩子,完成左旋操作

旋转总结

  • 可以发现,左旋和右旋是一种互逆的关系,右旋找左孩子,左旋找右孩子,这样可以通过 1 − x 1 - x 1x的操作来实现,更快的方法是取异或,程序如下
void Rotate(TreapNode* &o, int d){
   
   
    TreapNode* p = o->son[d ^ 1];
    o->son[d ^ 1] = p->son[d];
    p->son[d] = o;
    o->Update();
    p->Update();
    o = p;
}
  • 更新节点数要先更新旧根,再更新新根

插入操作

  • 解决了旋转,插入就简单的多了,每次插入一个数,首先根据value确定位置(排序二叉树),确定位置以后,随机给定Rank值,如果根节点Rank不是最大,则需要旋转
void Insert(TreapNode* &o, int k){
   
   
    if(o == NULL){
   
   
        o = new TreapNode();
        o->value = k;
        o->Rank = rand();
        o->son[0] = o->son[1] = NULL;
        o->sz = 1;
        o->cnt = 1;
    }else{
   
   
        int d = o->cmp(k);
        if(d != -1){
   
   
            Insert(o->son[d], k);
            if(o->Rank < o->son[d]->Rank) Rotate(o, d ^ 1);
        }else o->cnt++;
    }
    o->Update();
}

删除操作

  • 先看这个值在哪棵子树,然后要看这个值是否有多个,如果有多个, c n t − − cnt-- cnt即可,注意update;如果只有一个,那么需要删除该节点,但是不能直接删除,需要判断左右孩子的优先级保证树的平衡,如果左孩子优先级高,那么节点右旋,此时左孩子转到根,原来的根转到右子树,所以需要递归在右子树中删除根节点
  • 如果该节点不是左孩子右孩子都存在,那么直接把存在的那个孩子移动到根,然后直接删除原来的根节点即可,最后如果根节点不是空指针,那么需要update
void Remove(TreapNode* &o, int k){
   
   
    int d = o->cmp(k);
    if(d == -1){
   
   
        if(o->cnt > 1){
   
   
            o->cnt--;
            o->Update();
            return;
        }
        TreapNode* u = o;
        if(o->son[0] != NULL && o->son[1] != NULL){
   
   
            int d2 = o->son[0]->Rank > o->son[1]->Rank ? 1 : 0;
            Rotate(o, d2);
            Remove(o->son[d2], k);
        }else{
   
   
            if(o->son[0] == NULL) o = o->son[1];
            else o = o->son[0];
            delete u;
        }
    }else{
   
   
        Remove(o->son[d], k);
    }
    if(o != NULL) o->Update();
}

第K大

  • 在这里,我们设定的sz派上了场,从根节点出发,如果K小于右子树的节点数,那么就在右子树里面找第K大(排序二叉树);如果K在右子树节点值的个数范围内,那么就是根节点对应的value;否则就在左子树里面找第(K − - 右子树的sz − - 出现次数)大,当然需要再处理一些特殊的情况比如越界
int Get_Kth(TreapNode* o, int k){
   
   
    if(o == NULL || k <= 0 || k > o->sz) return 0;
    int s = (o->son[1] == NULL ? 0 : o->son[1]->sz);
    if(k >= s + 1 && k <= s + o->cnt) return o->value;
    else if(k <= s) return Get_Kth(o->son[1], k);
    else return Get_Kth(o->son[0], k - s - o->cnt);
}

K是第几大

  • 如果K不在树上,那么返回 − 1 -1 1(有些题目有特殊要求),否则递归查找即可
int Get_Xrank(TreapNode* o, int k){
   
   
    if(o == NULL) return -1;
    int d = o->cmp(k);
    if(d == -1) return o->son[1] == NULL ? 1 : o->son[1]->sz + 1;
    else if(d == 1) return Get_Xrank(o->son[
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值