关于平衡树的一些学习

听说平衡树种类很多,什么RBT,AVL,BST,Splay,跳表什么的,简单谈一谈。

(1) 红黑树 又叫RBT ,实现复杂 性能强悍,在OI中较少出现QAQ

(2)Splay 第一个接触的平衡树,一开始还以为Splay是唯一的平衡树(我是SB),splay的原理是:每插入一个点,就把它旋转到根节点,Splay不能保证每次旋转操作的复杂度的是O(log(n)),但是它的均摊复杂度确实是log(n),证明我也不会,听说splay会被卡,等到被卡了再说吧QAQ,编程复杂度也小,Splay的核心在于splay操作即是Splay(x,f)把x旋转到f的儿子上,特别的如果要旋转到根,那么f=0 Splay的旋转操作分为zig,zig-zig,zig-zag。

zig操作:当x的父亲已经是f的儿子时,直接旋转x。


zig-zig操作:当x与x的父亲位于同一侧时,即x是x的父亲的左儿子,x的父亲是x的父亲的父亲的左儿子,反之也成立。


zig-zag操作:当x与x的父亲位于不同侧时,直接旋转两次x。这里的旋转,就是指,如果x是它父亲的左儿子,那么就把他右旋,否则左旋。如图


Splay Tree可以方便的解决一些区间问题,根据不同形状二叉树先序遍历结果不变的特性,可以将区间按顺序建二叉查找树。
每次自下而上的一套splay都可以将x移动到根节点的位置,利用这个特性,可以方便的利用Lazy的思想进行区间操作。
对于每个节点记录size,代表子树中节点的数目,这样就可以很方便地查找区间中的第k小或第k大元素。
对于一段要处理的区间[x, y],首先splay x-1到root,再splay y+1到root的右孩子,这时root的右孩子的左孩子对应子树就是整个区间。————————从别的地方抄来的
贴上代码:
int father[N],key[N],ch[N][2],root,tot1;  //分别表示父结点,键值,左右孩子(0为左孩子,1为右孩子),根结点,结点数量 
void Rotate(int x,int kind) {  
    int y=father[x];  
    //把其中一个分支先给父节点   
    ch[y][!kind]=ch[x][kind];  
    father[ch[x][kind]]=y;  
    //如果父节点不是根结点,则要和父节点的父节点连接起来    
    if(father[y])  
        ch[father[y]][ch[father[y]][1]==y]=x;  
    father[x]=father[y];  
    ch[x][kind]=y;  
    father[y]=x;  
}  
//Splay调整,将根为r的子树调整为goal    
void Splay(int r,int goal) {  
    while(father[r]!=goal) {  
    //父节点即是目标位置,goal为0表示,父节点就是根结点    
        if(father[father[r]]==goal)  
            Rotate(r,ch[father[r]][0]==r);  
        else {  
            int y=father[r];  
            int kind=ch[father[y]][0]==y;  
            //两个方向不同,则先左旋再右旋    
            if(ch[y][kind]==r) {  
                Rotate(r,!kind);  
                Rotate(r,kind);  
            }  
            //两个方向相同,相同方向连续两次     
            else {  
                Rotate(y,kind);  
                Rotate(r,kind);  
            }  
        }  
    }  
    //更新根结点    
    if(goal==0) root=r;  
}
二. Treap
我来填坑了,先给一道模板题 BZOJ3224 普通平衡树 可以用Treap做
一些定义如下
#include<bits/stdc++.h>
using namespace std;
struct data  
{  
    int l , r , v , rnd , size , w ;  
};  
data tr[100001] ;  
int n , ans , size , root ;  
void update(int k)  
{  
    tr[k].size = tr[tr[k].l].size + tr[tr[k].r].size + tr[k].w ;  
}  
void lturn(int &k)  
{  
    int t = tr[k].r ;  
    tr[k].r = tr[t].l ;  
    tr[t].l = k ;  
    tr[t].size = tr[k].size ;  
    update(k) ;  
    k = t ;  
}  
void rturn(int &k)  
{  
    int t = tr[k].l ;  
    tr[k].l = tr[t].r ;  
    tr[t].r = k ;  
    tr[t].size = tr[k].size ;  
    update(k) ;  
    k = t ;  
}
左旋和右旋看着图理解一下吧,实在不行记住代码也行QAQ
插入代码:
void insert(int &k , int x)  
{  
    if(k == 0)  
    {  
        size ++ ;  
        k = size ;  
        tr[k].size = tr[k].w = 1 ;  
        tr[k].v = x ;  
        tr[k].rnd = rand() ;  
        return ;  
    }  
    tr[k].size ++ ;  
    if(tr[k].v == x) tr[k].w ++ ;  
    else if(x > tr[k].v)  
    {  
        insert(tr[k].r , x) ;  
        if(tr[tr[k].r].rnd < tr[k].rnd) lturn(k) ;  
    }else   
    {  
        insert(tr[k].l , x) ;  
        if(tr[tr[k].l].rnd < tr[k].rnd) rturn(k) ;  
    }  
} 

1.先讨论没有节点的情况,那就新加入一个节点
2.若当前节点的权值与插入的值相同,那么给当前节点w+1
3.与当前节点权值作比较,如果插入值大于当前节点权值则插入到右边,反之插入到左边,同时维护小根堆的特点

删除操作:
void del(int &k , int x)  
{  
    if(k == 0) return ;  
    if(tr[k].v == x)  
    {  
        if(tr[k].w > 1)  
        {  
            tr[k].w -- , tr[k].size -- ;  
            return ;  
        }  
        if(tr[k].l * tr[k].r == 0) k = tr[k].l + tr[k].r ;  
        else if(tr[tr[k].l].rnd < tr[tr[k].r].rnd)  
        {  
            rturn(k) ;  
            del(k , x) ;  
        }else  
        {  
            lturn(k) ;  
            del(k , x) ;  
        }  
    }else if(x > tr[k].v)  
    {  
        tr[k].size -- ;  
        del(tr[k].r , x) ;  
    }else  
    {  
        tr[k].size -- ;  
        del(tr[k].l , x) ;  
    }  
}
1.没有节点不删除
2.删除值与当前节点的值相同且当前节点的值的个数大于1则减一
3.若k处只有一个子节点,则把子节点提上来
4.如果左边rnd小于右边rnd,则右旋,把这个根节点旋到叶节点处删掉
4.如果右边rnd小于左边rnd,则左旋,把这个根节点旋到叶节点处删掉
5.如果比k处的值大,则将k的size--然后分到右子树操作
5.如果比k处的值小,则将k的size--然后分到左子树操作
查询排名操作:
int query_rank(int k , int x)  
{  
    if(k == 0) return 0 ;  
    if(tr[k].v == x) return tr[tr[k].l].size + 1 ;  
    else if(x > tr[k].v)  
    {  
        return tr[tr[k].l].size + tr[k].w + query_rank(tr[k].r , x) ;  
    }else return query_rank(tr[k].l , x) ;  
} 
1.讨论没有节点的情况
2.若当前节点的值与查询值相同,则返回左子树的节点数+1;
3.若当前节点的值小于被查询的值,则返回左子树的节点数+当前节点的数目+查询到右子树的返回值
4.若当前节点的值大于被查询的值,则返回查询到左子树的返回值

查询排名为x的数的操作:
int query_num(int k , int x)  
{  
    if(k == 0) return 0 ;  
    if(x <= tr[tr[k].l].size)  
    {  
        return query_num(tr[k].l , x) ;  
    }else if(x > tr[tr[k].l].size + tr[k].w)  
    {  
        return query_num(tr[k].r , x - tr[tr[k].l].size - tr[k].w) ;  
    }else  
    {  
        return tr[k].v ;  
    }  
}
1.讨论没有节点的情况
2.如果x小于等于左子树的节点数则递归询问
3.如果x比左子树的节点数加k处节点数还大,则返回对右子树的x-左子树节点数-k处节点数的递归询问。
4.除了2,3情况就一定是k处,返回k处的值就好了
求前驱:
前驱定义:该节点的前一个节点
void query_pro(int k , int x)  
{  
    if(k == 0) return ;  
    if(tr[k].v < x)  
    {  
        ans = k ;  
        query_pro(tr[k].r , x) ;  
    }else query_pro(tr[k].l , x) ;  
}
1.讨论没有节点的情况
2.如果x比k处的值大,则用ans记录k,然后递归到右子树
3.如果k处的值大于等于x,则递归到左子树
求后继:
后继定义:该节点的后一个节点
void query_sub(int k , int x)  
{  
    if(k == 0) return ;  
    if(tr[k].v > x)  
    {  
        ans = k ;  
        query_sub(tr[k].l , x) ;  
    }else query_sub(tr[k].r , x) ;  
} 
1.讨论没有节点的情况
2.如果x比k处的值小,则用ans记录k,然后递归到左子树
3.如果k处的值大于等于x,则递归到右子树
基本操作已经叙述完了,下面贴上主函数,大家就可以去把上面那到题水掉了QAQ
贴上全代码:
#include<bits/stdc++.h>
using namespace std;
struct data  
{  
    int l , r , v , rnd , size , w ;  
};  
data tr[100001] ;  
int n , ans , size , root ;  
void update(int k)  
{  
    tr[k].size = tr[tr[k].l].size + tr[tr[k].r].size + tr[k].w ;  
}  
void lturn(int &k)  
{  
    int t = tr[k].r ;  
    tr[k].r = tr[t].l ;  
    tr[t].l = k ;  
    tr[t].size = tr[k].size ;  
    update(k) ;  
    k = t ;  
}  
void rturn(int &k)  
{  
    int t = tr[k].l ;  
    tr[k].l = tr[t].r ;  
    tr[t].r = k ;  
    tr[t].size = tr[k].size ;  
    update(k) ;  
    k = t ;  
}
void insert(int &k , int x)  
{  
    if(k == 0)  
    {  
        size ++ ;  
        k = size ;  
        tr[k].size = tr[k].w = 1 ;  
        tr[k].v = x ;  
        tr[k].rnd = rand() ;  
        return ;  
    }  
    tr[k].size ++ ;  
    if(tr[k].v == x) tr[k].w ++ ;  
    else if(x > tr[k].v)  
    {  
        insert(tr[k].r , x) ;  
        if(tr[tr[k].r].rnd < tr[k].rnd) lturn(k) ;  
    }else   
    {  
        insert(tr[k].l , x) ;  
        if(tr[tr[k].l].rnd < tr[k].rnd) rturn(k) ;  
    }  
} 
void del(int &k , int x)  
{  
    if(k == 0) return ;  
    if(tr[k].v == x)  
    {  
        if(tr[k].w > 1)  
        {  
            tr[k].w -- , tr[k].size -- ;  
            return ;  
        }  
        if(tr[k].l * tr[k].r == 0) k = tr[k].l + tr[k].r ;  
        else if(tr[tr[k].l].rnd < tr[tr[k].r].rnd)  
        {  
            rturn(k) ;  
            del(k , x) ;  
        }else  
        {  
            lturn(k) ;  
            del(k , x) ;  
        }  
    }else if(x > tr[k].v)  
    {  
        tr[k].size -- ;  
        del(tr[k].r , x) ;  
    }else  
    {  
        tr[k].size -- ;  
        del(tr[k].l , x) ;  
    }  
}  
int query_rank(int k , int x)  
{  
    if(k == 0) return 0 ;  
    if(tr[k].v == x) return tr[tr[k].l].size + 1 ;  
    else if(x > tr[k].v)  
    {  
        return tr[tr[k].l].size + tr[k].w + query_rank(tr[k].r , x) ;  
    }else return query_rank(tr[k].l , x) ;  
} 
int query_num(int k , int x)  
{  
    if(k == 0) return 0 ;  
    if(x <= tr[tr[k].l].size)  
    {  
        return query_num(tr[k].l , x) ;  
    }else if(x > tr[tr[k].l].size + tr[k].w)  
    {  
        return query_num(tr[k].r , x - tr[tr[k].l].size - tr[k].w) ;  
    }else  
    {  
        return tr[k].v ;  
    }  
}
void query_pro(int k , int x)  
{  
    if(k == 0) return ;  
    if(tr[k].v < x)  
    {  
        ans = k ;  
        query_pro(tr[k].r , x) ;  
    }else query_pro(tr[k].l , x) ;  
}
void query_sub(int k , int x)  
{  
    if(k == 0) return ;  
    if(tr[k].v > x)  
    {  
        ans = k ;  
        query_sub(tr[k].l , x) ;  
    }else query_sub(tr[k].r , x) ;  
} 
int main()  
{  
    scanf("%d" , &n) ;  
    int opt , x ;  
    for(int i = 1 ; i <= n ; i++)  
    {  
        scanf("%d%d" , &opt , &x) ;  
        switch(opt)  
        {  
            case 1 :insert(root , x) ;break ;  
            case 2 :del(root , x) ;break ;  
            case 3 :printf("%d\n" , query_rank(root , x)) ;break ;  
            case 4 :printf("%d\n" , query_num(root , x)) ;break ;  
            case 5 :ans = 0;query_pro(root , x);printf("%d\n" , tr[ans].v);break ;  
            case 6 :ans = 0;query_sub(root , x);printf("%d\n" , tr[ans].v);break ;  
        }  
    }  
} 

三.SBT
挖坑待填
四.替罪羊树

什么都不会,挖坑待填QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值