平衡树

本文深入探讨了平衡树这一动态数据结构,包括Splay树的伸展操作和Treap的双旋特性,讲解了它们如何实现高效的操作,如查找、插入、删除等,以及在实际应用中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

平衡树是一类动态的数据结构。这是它最大的特点,它可以维护修改操作,删除操作,前驱后继操作,查找数k的排名,查找第k大等功能。

Splay
Spaly,意为伸展,所以Splay又有伸展树的别称。其核心思想就是伸展,用一句话概括就是在每次操作时将结点旋转到根,听起来复杂度很高。但是均摊复杂度是logn(树高)的。基于这种中心思想,就有了Splay这种数据结构。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
#include<stack>
#define root sp[0].ch[1]
using namespace std;
const int INF=2147480000;
struct node{
    int v,fa,ch[2],rp,sum;
}sp[100010]; 
void update(int x)
{
    sp[x].sum=sp[sp[x].ch[0]].sum+sp[sp[x].ch[1]].sum+sp[x].rp;
}
bool identify(int x)
{
    return x==sp[sp[x].fa].ch[1];
}
void connect(int x,int f,int son)
{
    sp[x].fa=f;
    sp[f].ch[son]=x;
}
void rotate(int x)
{
    int f=sp[x].fa;
    int gf=sp[f].fa;
    int son=identify(x);
    int fson=identify(f);
    int B=sp[x].ch[son^1];
    connect(B,f,son);
    connect(f,x,son^1);
    connect(x,gf,fson);
    update(f);
    update(x);
}
void splay(int x,int to)
{
    to=sp[to].fa;
    while(sp[x].fa!=to)
    {
        int up=sp[x].fa;
        if(sp[up].fa==to)
        {
            rotate(x);
        }
        else if(identify(x)==identify(up))
        {
            rotate(up),rotate(x);
        }
        else
        {
            rotate(x),rotate(x);
        }
    }
}

int tot;
void create(int x,int f,int son)
{
    sp[++tot].v=x;
    sp[tot].sum=sp[tot].rp=1;
    connect(tot,f,son);
}
void destroy(int x)
{
    sp[x].fa=sp[x].ch[0]=sp[x].ch[1]=sp[x].sum=sp[x].rp=sp[x].v=0;
}
int find(int now,int v)
{
    if(sp[now].v==v)
    {
        splay(now,root);
        return now;
    }
    int next=(v>sp[now].v);
    find(sp[now].ch[next],v);
}
void push(int v)
{
    if(tot==0)
    {
        root=1;
        create(v,0,1);
    }
    else
    {
        int now=root;
        while(1)
        {
            sp[now].sum++;
            if(v==sp[now].v)
            {
                sp[now].rp++;
                splay(now,root);
                return;	
            }	
            int next=(v>sp[now].v);
            if(!sp[now].ch[next])
            {
                create(v,now,next);
                splay(tot,root);
                return;
            }
            now=sp[now].ch[next];
        }	
    }
}
void pop(int v)
{
    int now=find(root,v);
    if(!now)
        return;
    if(sp[now].rp>1)
    {
        sp[now].rp--;
        sp[now].sum--;
    }
    else if(!sp[now].ch[0])
    {
        root=sp[now].ch[1];
        sp[root].fa=0;
        destroy(now);
    }
    else
    {
        int lef=sp[now].ch[0];
        while(sp[lef].ch[1])
        {
            lef=sp[lef].ch[1];
        }
        splay(lef,sp[now].ch[0]);
        int rig=sp[now].ch[1];
        connect(rig,lef,1);
        connect(lef,0,1);
        update(lef);
        destroy(now);
    }
} 
int rank(int v)
{
    sp[0].sum=0;
    int cur=0,now=root;
    while(1)
    {
        if(sp[now].v==v)
        {
            int res=cur+sp[sp[now].ch[0]].sum+1;
            splay(now,root);
            return res;
        }
        if(now==0)
        {
            return 0;
        }
        if(v<sp[now].v)
        {
            now=sp[now].ch[0];
        }
        else
        {
            cur+=sp[sp[now].ch[0]].sum+sp[now].rp;
            now=sp[now].ch[1];
        }
    }
}
int atrank(int x)
{
    int now=root;
    while(1)
    {
        int lefsum=sp[now].sum-sp[sp[now].ch[1]].sum;
        if(x>sp[sp[now].ch[0]].sum && x<=lefsum)
        {
            break;	
        } 
        if(x<lefsum)
        {
            now=sp[now].ch[0];
        }
        else
        {
            x-=lefsum;
            now=sp[now].ch[1];
        }
    }
    splay(now,root);
    return sp[now].v;
}
int lower(int x)
{
    push(x);
    int now=sp[root].ch[0];
    while(sp[now].ch[1])
        now=sp[now].ch[1];
    int res=sp[now].v;
    pop(x);
    return res;
}
int upper(int x)
{
    push(x);
    int now=sp[root].ch[1];
    while(sp[now].ch[0])
    {
        now=sp[now].ch[0];
    }
    int res=sp[now].v;
    pop(x);
    return res;
}
int main(void){
    int op,x,y,z,t;
    sp[0].v=-INF;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d",&x);
            push(x);		
        }
        else if(op==2)
        {
            scanf("%d",&x);
            pop(x);
        }
        else if(op==3)
        {
            scanf("%d",&x);
            printf("%d\n",rank(x));
        }
        else if(op==4)
        {
            scanf("%d",&x);
            printf("%d\n",atrank(x));
        }
        else if(op==5)
        {
            scanf("%d",&x);
            printf("%d\n",lower(x));
        }
        else
        {
            scanf("%d",&x);
            printf("%d\n",upper(x));
        }
    }
    return 0;
}

另一种平衡树treap,我采用的写法是Zig-Zag式的写法。就是双旋treap。

#include<bits/stdc++.h>
#define ls(x)  t[x].left_son
#define rs(x)  t[x].right_son
#define v(x)   t[x].value
#define p(x)   t[x].priority
#define rep(x) t[x].repeat_time
#define s(x)   t[x].size
#define inf    0x3f3f3f3f
using namespace std;
const int maxn=200100;
struct TREE{
    int left_son;
    int right_son;
    int value;
    int priority;
    int repeat_time;
    int size;
}t[maxn];
int num,rt;

inline void update(const int &x){
    s(x)=s(ls(x))+s(rs(x))+rep(x);
}

inline void right_rotate(int &k){
    int y=ls(k);
    ls(k)=rs(y);
    rs(y)=k;
    s(y)=s(k);
    update(k);
    k=y;
}

inline void left_rotate(int &k){
    int y=rs(k);
    rs(k)=ls(y);
    ls(y)=k;
    s(y)=s(k);
    update(k);
    k=y;
}

inline void insert(int &k ,const int &key){
    if(!k){
        k=++num;
        ls(k)=rs(k)=0;
        rep(k)=s(k)=1;
        v(k)=key;
        p(k)=rand();
        return;
    }
    else
        ++s(k);
    
    if(v(k)==key) ++rep(k);
    else if(key<v(k)){
        insert(ls(k),key);
        if(p(ls(k))<p(k)) right_rotate(k);
    }
    else if(key>v(k)){
        insert(rs(k),key);
        if(p(rs(k))<p(k)) left_rotate(k);
    }
    return;
} 

inline void del(int &k,const int &key){
    if(v(k)==key){
        if(rep(k)>1) --s(k),--rep(k);
        else if( !ls(k) || !rs(k) ){
            k=ls(k)+rs(k);
        }	
        else if(p(ls(k))<p(rs(k))){
            right_rotate(k);
            del(k,key);
        }
        else{
            left_rotate(k);
            del(k,key);
        }
        return;
    }
    
    --s(k);
    if(key<v(k)) del(ls(k),key);
    else del(rs(k),key);
    return;
}
inline int before(const int &key){
    int x=rt,res=-inf;
    while(x){
        if(v(x)<key){
            res=v(x);
            x=rs(x);
        }
        else {
            x=ls(x);
        }
    }
    return res;
}

inline int after(const int &key){
    int x=rt,res=-inf;
    while(x){
        if(v(x)>key){
            res=v(x);
            x=ls(x);
        }
        else {
            x=rs(x);
        }
    }
    return res;
}

inline int kth(int k){
    int x=rt;
    while(x){
        if(s(ls(x))<k && s(ls(x))+rep(x)>=k){
            return v(x);
        }
        if(s(ls(x))>=k) x=ls(x);
        else{
            k-=rep(x)+s(ls(x));
            x=rs(x);
        }  
    }
    return 0; 
}

inline int rank(const int &key){
    int x=rt,res=0;
    while(x){
        if(v(x)==key) return res+s(ls(x))+1;
        if(key<v(x)) x=ls(x);
        else{
            res+=rep(x)+s(ls(x));
            x=rs(x);
        }
    }
    return res;
}

int n;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int op,x;
        scanf("%d%d",&op,&x);
        if(op==1){
            insert(rt,x);
        }
        if(op==2){
            del(rt,x);
        }
        if(op==3){
            printf("%d\n",rank(x));
        }
        if(op==4){
            printf("%d\n",kth(x));
        }
        if(op==5){
            printf("%d\n",before(x));			
        }
        if(op==6){
            printf("%d\n",after(x));			
        }
    }	
}

自我感觉本人treap学的比splay懂。
如果说splay的核心在于伸展操作的话,那么treap的核心也在于它的名字中。tree+heap,既要满足bst(二叉搜索树)的性质,又要满足堆的性质,这样每个点除了它自带的权值之外,还有一个随机出的优先级。这样可以做到均摊复杂度logn,而且随机数据退化成链的可能很低。
在每一次插入一个新数的时候,若它插入之后,不满足堆的性质(因为本身是按bst性质插入的,所以只用考虑堆的性质)。将插入的结点,他的兄弟节点,他的父节点看作一个组,那么若左节点是最小的(以小根堆为例),那么就要把左节点旋转到根上去,这是右旋操作。
概括一下可以分为下列几步
1.获取根节点A的左节点,即要旋转到根的结点B。
2.将A的父节点信息更新为原B的父节点信息,并修改A父相对应的信息。
3.将根结点A的左节点改为原B的右儿子,对应修改。
4.将B的右儿子改为A

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值