SBT笔记和模板

一、SBT维护平衡的方式
SBT靠维护size来维护平衡。
对于以r为根的子树,SBT必须满足以下性质:
1.r的左儿子的size大于等于r的右儿子的左右儿子的size
2.r的右儿子的size大于等于r的左儿子的左右儿子的size
SBT维护size很简单,就是插入++size,删除–size
维护平衡依靠maintain函数。具体看论文。
我想写这个和AVL的相似的地方。
首先给出maintain函数

void maintain(int &r,bool flag)
{
    if(!flag)
    {
        if(tree[tree[r].rc].sz<tree[tree[tree[r].lc].lc].sz)zig(r);
        else if(tree[tree[r].rc].sz<tree[tree[tree[r].lc].rc].sz)zagzig(r);
        else return;
    }
    else
    {
        if(tree[tree[r].lc].sz<tree[tree[tree[r].rc].rc].sz)zag(r);
        else if(tree[tree[r].lc].sz<tree[tree[tree[r].rc].lc].sz)zigzag(r);
        else return;
    }
    maintain(tree[r].lc,0);
    maintain(tree[r].rc,1);
    maintain(r,0);
    maintain(r,1);
}

然后AVL的maintain长这样:

void maintain(int &r)
{
    if(tree[tree[r].lc].h==tree[tree[r].rc].h+2)
    {
        int t=tree[r].lc;
        if(tree[tree[t].lc].h==tree[tree[r].rc].h+1)r=zig(r);
        else if(tree[tree[t].rc].h==tree[tree[r].rc].h+1)r=zagzig(r);
    }
    else if(tree[tree[r].rc].h==tree[tree[r].lc].h+2)
    {
        int t=tree[r].rc;
        if(tree[tree[t].rc].h==tree[tree[r].lc].h+1)r=zag(r);
        else if(tree[tree[t].lc].h==tree[tree[r].lc].h+1)r=zigzag(r);
    }
    tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1;
}

可以发现的是:
1.AVL和SBT都有旋转,单旋双旋都有,不过判断条件从h变成了size
2.AVL只maintain当前的r,而SBT要maintain好几次,尽管还是O(1)的
3.SBT的maintain有个flag是决定maintain左边还是右边,左边就是0,右边就是1。
那么,对于记忆来说,就只记住SBT要maintain好几次(根左右都要maintain,左儿子只maintain左边,右儿子右边),还有SBT的旋转判断条件。左儿子的左儿子大于右儿子,那么就右旋;左儿子的右儿子大于右儿子,就先左旋后右旋,这个和AVL的那个其实很类似。

二、insert
就是二叉查找树的insert,加上维护size,加上最后有个maintain。

void insert(int &r,int x)
{
    if(r==0)
    {
        tree[++cnt].sz=1;
        tree[cnt].v=x;
        r=cnt;
        return;
    }
    ++tree[r].sz;
    if(x<tree[r].v)insert(tree[r].lc,x);
    else insert(tree[r].rc,x);
    maintain(r,x>=tree[r].v);
}

三、del
就是二叉查找树的del,加上维护size。
可以不maintain,反而要快些。
因为这个地方就算不维护,让其随便删,删完了树的高度起码不变,二叉查找树的性质不变,时间复杂度起码不会增加,而且不maintain了反而节约常数时间。

int del(int &r,int x)
{
    int res;
    --tree[r].sz;
    if(tree[r].v==x||(x<tree[r].v&&tree[r].lc==0)||(x>tree[r].v&&tree[r].rc==0))
    {
        res=tree[r].v;
        if(0==tree[r].lc||0==tree[r].rc)r=tree[r].lc+tree[r].rc;
        else tree[r].v=del(tree[r].lc,x);
    }
    else
    {
        if(x<tree[r].v)res=del(tree[r].lc,x);
        else res=del(tree[r].rc,x);
    }
    return res;
}

四、可重SBT
其实上面那个insert就是一种可重SBT的实现。
让SBT可重有2个思路。
1.增加一个tot域,记录这个节点被插入了几次。
然后很多地方需要修改,比如插入时r!=0还要分3个情况
tree[r].v > x递归左子树,tree[r].v < x递归右子树,tree[r].v==x就tree[r].tot++
然后del的时候如果这个点的tot值 > 1,那么直接- -tot,如果tot==1,那么才删除点。并且del要返回一个二元组,记录tot和v。
求排名,第K大的时候也有所修改。
前驱后继不用修改。
总之实现比较麻烦。

int kth(int r,int k)
{
    if(k>=tree[tree[r].rc].sz+1&&k<=tree[tree[r].rc].sz+tree[r].tot)return tree[r].v;
    if(k<1+tree[tree[r].rc].sz)return kth(tree[r].rc,k);
    else return kth(tree[r].lc,k-tree[r].tot-tree[tree[r].rc].sz);
}

然后还有更坑爹的,就是size的二义性,SBT的size是节点个数,求第k大的size是插入的数个数,可重SBT这个不一样。需要分别维护。不过不维护好像也可以,就是不是特别平衡。
2.把相同的值当作不同的值插入
插入就是上文的insert,可以发现的是,如果x==tree[r].v,仍然把x插入右子树(当然左子树亦可)中。此时二叉查找树的意义发生了变化:这棵树的中序遍历得到的数列是不递减的,而非递增。
这样写不用额外开tot域,也不用维护2个意义不同的size。insert和delete也不用怎么改。
不过这样写,就要仔细考虑求前驱后继第k大和rank了,注意递归哪一边,注意取等。具体还是看代码吧。
普通平衡树代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct node{
    int lc,rc,sz,v;
}tree[500010];
int n,op,x,pred,succ,cnt,rt;
char c;
inline void GET(int &n)
{
    n=0;
    int f=1;
    do{c=getchar();if(c=='-')f=-1;}while(c>'9'||c<'0');
    while(c<='9'&&c>='0'){n=n*10+c-'0';c=getchar();}
    n*=f;
}
inline void zig(int &r)
{
    int t=tree[r].lc;
    tree[r].lc=tree[t].rc;
    tree[t].rc=r;
    tree[r].sz=tree[tree[r].lc].sz+tree[tree[r].rc].sz+1;
    tree[t].sz=tree[tree[t].lc].sz+tree[tree[t].rc].sz+1;
    r=t;
}
inline void zag(int &r)
{
    int t=tree[r].rc;
    tree[r].rc=tree[t].lc;
    tree[t].lc=r;
    tree[r].sz=tree[tree[r].lc].sz+tree[tree[r].rc].sz+1;
    tree[t].sz=tree[tree[t].lc].sz+tree[tree[t].rc].sz+1;
    r=t;
}
inline void zigzag(int &r)
{
    zig(tree[r].rc);
    zag(r);
}
inline void zagzig(int &r)
{
    zag(tree[r].lc);
    zig(r);
}
inline void maintain(int &r,bool flag)
{
    if(!flag)
    {
        if(tree[tree[r].rc].sz<tree[tree[tree[r].lc].lc].sz)zig(r);
        else if(tree[tree[r].rc].sz<tree[tree[tree[r].lc].rc].sz)zagzig(r);
        else return;
    }
    else
    {
        if(tree[tree[r].lc].sz<tree[tree[tree[r].rc].rc].sz)zag(r);
        else if(tree[tree[r].lc].sz<tree[tree[tree[r].rc].lc].sz)zigzag(r);
        else return;
    }
    maintain(tree[r].lc,0);
    maintain(tree[r].rc,1);
    maintain(r,0);
    maintain(r,1);
}
void insert(int &r,int x)
{
    if(r==0)
    {
        tree[++cnt].sz=1;
        tree[cnt].v=x;
        r=cnt;
        return;
    }
    ++tree[r].sz;
    if(x<tree[r].v)insert(tree[r].lc,x);
    else insert(tree[r].rc,x);
    maintain(r,x>=tree[r].v);
}
int del(int &r,int x)
{
    int res;
    --tree[r].sz;
    if(tree[r].v==x||(x<tree[r].v&&tree[r].lc==0)||(x>tree[r].v&&tree[r].rc==0))
    {
        res=tree[r].v;
        if(0==tree[r].lc||0==tree[r].rc)r=tree[r].lc+tree[r].rc;
        else tree[r].v=del(tree[r].lc,x);
    }
    else
    {
        if(x<tree[r].v)res=del(tree[r].lc,x);
        else res=del(tree[r].rc,x);
    }
    return res;
}
void predecessor(int r,int x)
{
    if(r==0)return;
    if(x<=tree[r].v)predecessor(tree[r].lc,x);
    else
    {
        if(x!=tree[r].v)pred=tree[r].v;
        predecessor(tree[r].rc,x);
    }
}
void successor(int r,int x)
{
    if(r==0)return;
    if(x<tree[r].v)
    {
        if(x!=tree[r].v)succ=tree[r].v;
        successor(tree[r].lc,x);
    }
    else successor(tree[r].rc,x);
}
int kth(int r,int x)
{
    if(x==tree[tree[r].lc].sz+1)return tree[r].v;
    if(x<tree[tree[r].lc].sz+1)return kth(tree[r].lc,x);
    return kth(tree[r].rc,x-tree[tree[r].lc].sz-1);
}
int rnk(int r,int x)
//返回最靠前的x的排名,如1 1 1 2 2 3 3,rnk(rt,2)=4
{
    if(r==0)return 1;
    if(x<=tree[r].v)return rnk(tree[r].lc,x);
    return rnk(tree[r].rc,x)+tree[tree[r].lc].sz+1;
}
int main()
{
    GET(n);
    while(n--)
    {
        GET(op);GET(x);
        if(op==1)insert(rt,x);
        else if(op==2)del(rt,x);
        else if(op==3)printf("%d\n",rnk(rt,x));
        else if(op==4)printf("%d\n",kth(rt,x));
        else if(op==5){predecessor(rt,x);printf("%d\n",pred);}
        else if(op==6){successor(rt,x);printf("%d\n",succ);}
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值