• 平衡树学习笔记(4)-------替罪羊树


    替罪羊树

    上一篇:平衡树学习笔记(3)-------Splay

    替罪羊树可以说是最暴力的平衡树

    但却跑的很快

    有多暴力?

    不是一条链影响复杂度吗?

    暴力给你拍到一个vector里去(没错,整棵树暴力拍扁)

    再重新建树,建出的树像线段树那样二分建来保证平衡

    可谓是要多暴力有多暴力

    在树套树上也有一些优势

    (color{#9900ff}{定义})

    const double eps=0.75;
    struct node
    {
    	int val,siz,cov;
    	bool exist;
    	node *ch[2];
    	void upd()
    	{
    		siz=ch[0]->siz+ch[1]->siz+exist;
    		cov=ch[0]->cov+ch[1]->cov+1;
    	}
    	bool nb(){ return ((ch[0]->cov>cov*eps+5)||(ch[1]->cov>cov*eps+5)); }
    	int rk() { return ch[0]->siz+exist;}
    };
    

    不同的是,替罪羊树的删除不是真正的删除,只是打个标记罢了

    但是,要注意的是,如果被标记(假装删除)的点在某个子树中,而现在要把那个子树拍扁

    则这个点就是真的被删除了qwq,重构的时候就不再把它加入新树了,因为没必要qwq

    所以,val记录权值,siz记录实际大小,cov记录节点个数

    exist就是标记,判断此节点是否存在

    刚刚说它维护平衡的方式就是拍扁重构

    但总不能每次都把所有拍扁重构

    条件就是,左/右子树大小大于自己大小*平衡因子

    这个平衡因子也是比较玄学,一般在0.7---0.85即可吧qwq

    nb的意思是。。。need-bong,判断是否需要拍扁,不,是被拍扁QAQ

    (color{#9900ff}{基本操作})

    1、travel

    travel,顾名思义,旅行

    让树上的节点去vector旅行qwq(<-----瞎bb)

    说难听点就是被拍扁。。。。。。

    不过,为了重构便捷,还记得第一节说的吗

    平衡树的中序遍历可是有序的!

    所以,按照中序遍历拍扁,就方便重构了qwq

    //vector要传地址(别告诉我不知道为什么)
    void travel(nod o,vector<nod> &v)
    {
        //到空节点不用管
    	if(o==null) return;
        //中序遍历左根右
    	travel(o->ch[0],v);
        //放进vector之前,判断是否存在, 不存在就不用管了,真正意义上把它删除
    	if(o->exist) v.push_back(o);
        //else这一行各位数组dalao可以忽略,它的左右就是回收删的节点放入内存池,节省空间利用
    	else ts[top++]=o;
    	travel(o->ch[1],v);
    }
    

    2、divide

    别问我为啥叫divide,也许是因为二分建树吧qwq

    nod divide(vector<nod> &v,int l,int r)
    {
        //二分边界(因为[l,r)左闭右开)
    	if(l>=r) return null;
    	int mid=(l+r)>>1;
        //mid给自己,[l,mid-1]给左孩子,[mid+1,r)给右孩子
    	nod o=v[mid];
    	o->ch[0]=divide(v,l,mid);
    	o->ch[1]=divide(v,mid+1,r);
        //别忘维护性质
    	o->upd();
    	return o;
    }
    

    3、rebuild

    这就是重构,说白了就是把前两个联系在一起

    void rebuild(nod &o)
    {
    	static vector<nod> v;
    	v.clear();
    	travel(o,v);
        //左闭右开不要忘
    	o=divide(v,0,v.size());
    }
    

    基本操作已经完成,够暴力吧qwq

    (color{#9900ff}{其它操作})

    1、插入

    由于涉及递归,而且有些不同,所以分成了两个函数来写

    插入节点毕竟会改变树的样子

    所以要考虑拍扁

    那么,现在问题来了

    我们既要保证树的平衡,又要保证复杂度,怎么办呢?

    我们找满足nb的最浅的那个重构

    这样既可以保证树的平衡,又不会因为太浅而影响复杂度(完美؏؏☝ᖗ乛◡乛ᖘ☝؏؏)

    //返回的是最浅的nb(牛逼)点的地址
    nod *ins(nod &o,int k)
    {
    	if(o==null)
    	{
            //如果为空则建立新节点
            //注意返回的是指针的地址
    		o=newnode(k);
    		return &null;
    	}
        //插入的点在当前点的子树内
        //而这个点是存在的
        //所以siz和cov都++
    	o->siz++,o->cov++;
        //递归像某个子树找
    	nod *p=ins(o->ch[k>=o->val],k);
        //先让p等于子树中的nb点
        //如果当前点是nb点,显然o比p浅,所以p=&o
    	if(o->nb()) p=&o;
    	return p;
    }
    void ins(int k)
    {
    	nod *p=ins(root,k);
        //只要不空说明有nb点,就拍扁重构
    	if(*p!=null) rebuild(*p);
    }
    
    

    2、删除

    因为我们只有拍扁重构的操作

    对于删除实在是不方便

    所以改了一下删除的方式

    改成删除第k大

    所以删除还得借助rnk qwq

    //删除第k大的点
    void del(nod &o,int k)
    {
        //沿途siz--,但cov不减,因为是假装删除qwq
    	o->siz--;
        //如果当前点存在并且就是要删的点,就删了好了qwq
    	if(o->exist&&o->rk()==k) o->exist=false;
    	else if(k<=o->rk()) del(o->ch[0],k);
    	else del(o->ch[1],k-o->rk());
        //否则向该去的方向递归,注意k的变化
    }
    //下面是删k这个数,通过rnk获取排名
    void del(int k)
    { 
    	del(root,rnk(k));
        //判断是否需要重构    
    	if(root->siz<root->cov*eps) rebuild(root);
    }
    

    3、查询数x的排名

    这个不解释了,比Splay的要简单不少了

    int rnk(int k)
    {
    	nod o=root;
    	int rank=1;
    	while(o!=null)
    	{
    		if(o->val>=k) o=o->ch[0];
    		else rank+=o->rk(),o=o->ch[1];
    	}
    	return rank;
    }
    

    4、查询第k大的数

    这个也差不多,平衡树都是相通的qwq

    唯一要注意的是,因为被删除的点有些是假装被删除的,所以要判断

    int kth(int k)
    {
    	nod o=root;
    	while(o!=null)
    	{
    		if(o->ch[0]->siz+1==k&&o->exist) return o->val;
    		else if(o->ch[0]->siz>=k) o=o->ch[0];
    		else k-=o->rk(),o=o->ch[1];
    	}
    }
    

    5,6、前驱,后继

    直接用rnk和kth嵌套就行了

    放上完整代码

    #include<cstdio>
    #include<queue>
    #include<vector>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cctype>
    #include<cmath>
    #define _ 0
    #define LL long long
    #define Space putchar(' ')
    #define Enter putchar('
    ')
    #define fuu(x,y,z) for(int x=(y);x<=(z);x++)
    #define fu(x,y,z)  for(int x=(y);x<(z);x++)
    #define fdd(x,y,z) for(int x=(y);x>=(z);x--)
    #define fd(x,y,z)  for(int x=(y);x>(z);x--)
    #define mem(x,y)   memset(x,y,sizeof(x))
    #ifndef olinr
     char getc()
    {
    	static char buf[100001],*p1=buf,*p2=buf;
    	return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100001,stdin),p1==p2)? EOF:*p1++;
    }
    #else
    #define getc() getchar()
    #endif
    template<typename T> void in(T &x)
    {
    	int f=1; char ch; x=0;
    	while(!isdigit(ch=getc()))(ch=='-')&&(f=-f);
    	while(isdigit(ch)) x=x*10+(ch^48),ch=getc();
    	x*=f;
    }
    const double eps=0.75;
    struct node
    {
    	int val,siz,cov;
    	bool exist;
    	node *ch[2];
    	void upd()
    	{
    		siz=ch[0]->siz+ch[1]->siz+exist;
    		cov=ch[0]->cov+ch[1]->cov+1;
    	}
    	bool nb(){ return ((ch[0]->cov>cov*eps+5)||(ch[1]->cov>cov*eps+5)); }
    	int rk() { return ch[0]->siz+exist;}
    };
    typedef node* nod;
    node st[1050500],*ts[1050500];
    nod tail=st,root,null;
    int top,n;
    using std::vector;
    nod newnode(int k)
    {
    	nod o=top? ts[--top]:tail++;
    	o->ch[0]=o->ch[1]=null;
    	o->cov=o->siz=o->exist=1;
    	o->val=k;
    	return o;
    }
    void travel(nod o,vector<nod> &v)
    {
    	if(o==null) return;
    	travel(o->ch[0],v);
    	if(o->exist) v.push_back(o);
    	else ts[top++]=o;
    	travel(o->ch[1],v);
    }
    nod divide(vector<nod> &v,int l,int r)
    {
    	if(l>=r) return null;
    	int mid=(l+r)>>1;
    	nod o=v[mid];
    	o->ch[0]=divide(v,l,mid);
    	o->ch[1]=divide(v,mid+1,r);
    	o->upd();
    	return o;
    }
    void rebuild(nod &o)
    {
    	static vector<nod> v;
    	v.clear();
    	travel(o,v);
    	o=divide(v,0,v.size());
    }
    nod *ins(nod &o,int k)
    {
    	if(o==null)
    	{
    		o=newnode(k);
    		return &null;
    	}
    	o->siz++,o->cov++;
    	nod *p=ins(o->ch[k>=o->val],k);
    	if(o->nb()) p=&o;
    	return p;
    }
    void del(nod &o,int k)
    {
    	o->siz--;
    	if(o->exist&&o->rk()==k) o->exist=false;
    	else if(k<=o->rk()) del(o->ch[0],k);
    	else del(o->ch[1],k-o->rk());
    }
    void init()
    {
    	null=tail++;
    	null->ch[0]=null->ch[1]=null;
    	null->siz=null->cov=null->val=0;
    	root=null;
    }
    void ins(int k)
    {
    	nod *p=ins(root,k);
    	if(*p!=null) rebuild(*p);
    }
    int rnk(int k)
    {
    	nod o=root;
    	int rank=1;
    	while(o!=null)
    	{
    		if(o->val>=k) o=o->ch[0];
    		else rank+=o->rk(),o=o->ch[1];
    	}
    	return rank;
    }
    int kth(int k)
    {
    	nod o=root;
    	while(o!=null)
    	{
    		if(o->ch[0]->siz+1==k&&o->exist) return o->val;
    		else if(o->ch[0]->siz>=k) o=o->ch[0];
    		else k-=o->rk(),o=o->ch[1];
    	}
    }
    void del(int k)
    { 
    	del(root,rnk(k));
    	if(root->siz<root->cov*eps) rebuild(root);
    }
    int main()
    {
    	init();
    	in(n);
    	int p,x;
    	while(n--)
    	{
    		in(p),in(x);
    		if(p==1) ins(x);
    		if(p==2) del(x);
    		if(p==3) printf("%d
    ",rnk(x));
    		if(p==4) printf("%d
    ",kth(x));
    		if(p==5) printf("%d
    ",kth(rnk(x)-1));
    		if(p==6) printf("%d
    ",kth(rnk(x+1)));
    	}
    	return ~~(0^_^0);
    }	
    

    下一篇:平衡树学习笔记(5)-------SBT

  • 相关阅读:
    java web分页查询初试
    SQL注入原理深度解析
    JS 清除IE缓存
    Android 代码混淆及第三方jar包不被混淆
    [leetcode]Unique Paths II
    ffmpeg API录制rtsp视频流
    HDU 2045 不容易系列之(3)—— LELE的RPG难题
    Ffmpeg和SDL创建线程(转)
    “富豪相亲大会”究竟迷失了什么?
    Ffmpeg和SDL如何同步视频(转)
  • 原文地址:https://www.cnblogs.com/olinr/p/10013217.html
Copyright © 2020-2023  润新知