浅谈 平衡树-treap

…之前仿佛讲得很水…现在稍微详细一些讲一下…


树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。

意思就是给每个节点分配一个随机权值,使得该数据结构在满足随机权值满足堆的性质的情况下,在保存数据满足BST性质…所以说既然是一棵BST,所以说我们可以通过维护BST性质来维护这棵平衡树…既然大家会堆…我们就rand权值给堆…


大概是这么个操作…
数据结构定义:

struct Treap{
   int lson,rson,val,rnd,size,wei;
}treap[MAXN];

我们来看insert函数

insert(root,x);

操作是这样的:(请大家先不要管其中关于Rotate的操作[没错之前的单词打错了])

void insert(int &k,int x){
    if(k==0){
        k=++sz;treap[k].size=treap[k].wei=1;
        treap[k].val=x;treap[k].rnd=rand_num();return;
    }
    treap[k].size++;
    if(treap[k].val==x) treap[k].wei++;
    else if(treap[k].val<x){
        insert(treap[k].rson,x);
        if(treap[treap[k].rson].rnd<treap[k].rnd)
        Left_Ratote(k);
    }else{
        insert(treap[k].lson,x);
        if(treap[treap[k].lson].rnd<treap[k].rnd)
        Right_Ratote(k);
    }
}

首先从根节点开始,向下进行查找,按照BST的性质,如果数据大了往右查找,小于往左查找,等于的话就给当前节点的wei权值++,wei权值存储的是有多少个相同的数据…
在往下查找的时候,把当前点的权重size++,表示他的多了一个…子孙

这里写图片描述


比如我们要insert一个权值为4的点,那么我们的查找方式是这样的

这里写图片描述
把这个点插入在val=4的地方的时候,进行rand,随机给其分配一个权值

inline int rand_num(){
   static int seed=520;
   return seed=int(seed*48271LL%2147483647);
}

如果访问到一个k==0的地方,就是一个空的节点了

treap[k].rnd=rand_num();

现在来讲解旋转,从代码可以看出,如果我们分配的节点的随机值不满足堆的性质的话,我们可以进行调整,旋转是什么呢…(好像大家都知道)
这里写图片描述
所以显然可以得知左旋右旋的规律,右旋就是把左儿子旋转到上面去,比如说图中的X是Y的父亲,这个时候右旋Y,右旋以顺时针旋转将Y旋转到X的上方,这个时候X就认Y做爹

左旋也是一个意思,只不过是把X逆时针旋转到Y上方,Y认X做爹

所以这个旋转的方式我们懂了…意义何在啊…
这里写图片描述

如果我们规定旋转前的BST长左边那个样子,我们会惊奇地发现,旋转到右边之后,竟然还是满足BST性质,所以我们进行旋转之后,BST的性质是不会改变的


那么我们为什么要旋转呢…

试想一棵BST,我们如果需要查询其中点的信息,期望复杂度是O(logn)啊…但是如果出现这种情况呢

这里写图片描述
现在我们进行如图右所示的操作,不断插入一些有单调性的值
现在我要查询值10的位置怎么办…显然已经退化成一条链了,BST性质好像没啥用?都O(n)的查询复杂度了
所以我们进行旋转,把一些节点进行旋转之后,保持一种矮矮胖胖的好身材,也就是我们熟悉的堆的模样,这样一来可以在数学期望上使得一棵BST尽量地平衡,也就是size balance


右旋操作:

void Right_Ratote(int &k){
    int t=treap[k].lson;treap[k].lson=treap[t].rson;treap[t].rson=k;
    treap[t].size=treap[k].size;update(k);k=t;
}

左旋操作:

void Left_Ratote(int &k){
    int t=treap[k].rson;treap[k].rson=treap[t].lson;treap[t].lson=k;
    treap[t].size=treap[k].size;update(k);k=t;
}

以及点信息维护pushup函数(旋转了之后size会发生变化)

void update(int k){
    treap[k].size=treap[treap[k].lson].size+treap[treap[k].rson].size+treap[k].wei;
}

删除操作(BST的删除操作一般是最难的,除了splay(还有spaly233)的区间翻转,然而区间翻转只是利用了splay灵活的维护根的方式,而已经并不满足BST性质了)

void del(int &k,int x){
    if(k==0) return;//如果寻址的时候寻到空址,说明treap里没有这个值,直接return即可 
    if(treap[k].val==x){//如果找到这个值 
        if(treap[k].wei>1){//如果这个值的数量大于1,则直接把数量减一即可 
            treap[k].wei--;
            treap[k].size--;//改点的size也会减少 
            return;
        }
        if(treap[k].lson*treap[k].rson==0){//这句话的含义是,如果左子和右子有一个空 
            k=treap[k].lson+treap[k].rson;//就找出有的那个孩子的标号 
        }else if(treap[treap[k].lson].rnd<treap[treap[k].rson].rnd){//然后进行对rnd值的判定 
            Right_Ratote(k);//然后进行旋转操作 
            del(k,x);//并且递归地去删除 
        }else{
            Left_Ratote(k);
            del(k,x);
        }
    }else if(treap[k].val<x){//按照BST性质寻址 
        treap[k].size--;
        del(treap[k].rson,x);//递归删除 
    }else{
        treap[k].size--;
        del(treap[k].lson,x);//至此删除删除的操作已经梳理完毕 
    }
}

接下来的几个函数大家应该看一遍就可以懂了…其实也可以自己敲一遍


查询rank

int query_rank(int k,int x){
    if(!k) return 0;
    if(treap[k].val==x) return treap[treap[k].lson].size+1;
    else if(treap[k].val<x) return treap[treap[k].lson].size+treap[k].wei+query_rank(treap[k].rson,x);
    else return query_rank(treap[k].lson,x);
}

查询某rank的值

int query_num(int k,int x){
    if(!k) return 0;
    if(x<=treap[treap[k].lson].size) return query_num(treap[k].lson,x);
    else if(x>treap[treap[k].lson].size+treap[k].wei) return query_num(treap[k].rson,x-treap[treap[k].lson].size-treap[k].wei);
    else return treap[k].val;
}

类似STL的lower_bound();操作

void query_LB(int k,int x){
    if(!k) return;
    if(treap[k].val<x){
        ans=k;
        query_LB(treap[k].rson,x);
    }else{
        query_LB(treap[k].lson,x);
    }
}

类似STL的upper_bound();操作

void query_UB(int k,int x){
    if(!k) return;
    if(treap[k].val>x){
        ans=k;
        query_UB(treap[k].lson,x);
    }else{
        query_UB(treap[k].rson,x);
    }
}

至此,treap的基本操作已经讲解完毕(当然还有一些奇奇怪怪的搞法,比如无旋treap,可以实现区间翻转[感到害怕])

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
红黑树和平衡二叉树都是用于保持二叉搜索树平衡的数据结构,但它们在某些方面有所不同。 优点: 1. 平衡性:红黑树和平衡二叉树都能够在插入和删除操作后自动调整树的结构,保持树的平衡,从而保证了在最坏情况下的查找效率为O(log n)。 2. 动态性:红黑树和平衡二叉树都支持高效的动态插入和删除操作,适用于频繁更新的应用场景。 3. 操作简单:相比其他平衡树结构,红黑树和平衡二叉树的操作相对简单,实现起来较为容易。 差异: 1. 结构性:红黑树是一种特殊的二叉搜索树,它在每个节点上增加了一个额外的颜色属性,并通过一些规则来保持树的平衡。而平衡二叉树是一种更广义的概念,可以有多种实现方式,如AVL树、Treap等。 2. 调整频率:红黑树的调整操作相对较少,仅在插入和删除时需要进行调整。而平衡二叉树可能需要更频繁地进行调整,因为它要保持每个节点的左右子树高度差不超过1。 3. 空间利用:红黑树需要额外的颜色属性来维持平衡,并且每个节点还需要存储其颜色信息。而平衡二叉树只需要存储节点值和指向左右子树的指针,相对而言空间利用更加紧凑。 综上所述,红黑树相对于平衡二叉树在实现和调整操作上更简单,但在空间利用上稍逊一筹。选择使用哪种结构取决于具体应用场景和需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值