学习笔记第六节:伸展树splay

正文

      说到伸展树,大家想到的可能是复杂,代码冗长,很容易打错,思路却很好理解。

      没错,伸展树的确是这么一个东西,如果你做多了题,学会用它之后,你就会觉得很得心应手,轻松面对许多高级数据结构题。这个当然也是一个锻炼人的耐心的东西,有一份代码调了4天emm

      伸展树是一棵二叉搜索树,就是说,它是一棵单一关键字排序的二叉树。

      也就是说,比您小的都在您的左边,比您大的都在您的右边,这样平衡,可以使得我们很快的访问一个区间。

重点1

      向上翻飞。

      别想太多,就是让您可以往上走的操作,比如说下面这棵树,2想到4的位置怎么办emm


      这时候我们就要想出一定的方法:

     1.首先,考虑的旋转方式肯定是包含我,我的父亲。

     2.其次,如果我是我的父亲的左儿子,那么我的父亲肯定是当我的右儿子(因为比我大)。


      就有上面的两种情况。

     3.我们同时要考虑他们的儿子的继承,包括爷爷是谁的爸爸也要分清。


      其他情况自己可以推一推哦~

      明显发现,除了绿绿的点之外,其他点所连接的关系都有发生变化,那么该如何考虑这种状态呢?

      下面给出函数通式

void rotate(int x,int w){//w等于1意味着我是我爸的左儿子,否则为我爸的右儿子
	int f=s[x].f,ff=s[f].f;//代表我的爸爸和爷爷哦~
	s[f].son[1-w]=s[x].son[w];//这里的son[0]是左儿子,son[1]是右儿子,如果我是我爸的右儿子,那么说明我的整棵子树都比他大,所以我的左儿子
	if(s[x].son[w]!=0) s[s[x].son[w]].f=f;//我爸的右儿子,否则我的左儿子来当我爸的右儿子,这个关系很明显。
	if(s[ff].son[1]==f) s[ff].son[1]=x;//如果我爸比我爷爷大,那么说明我比我爷爷大
	else s[ff].son[0]=x;//否则我比我爷爷小
	s[x].f=ff;//我认我爷做爸爸
	s[x].son[w]=f;//认爸爸做儿子
	s[f].f=x;//我爸爸的爸爸是我
	update(f);//更新底层
	update(x);//更新当前层
}

      希望理解一下父亲与我的关系和操作有什么关系emm

      不断的上旋即可达到目标。。

void splay(int x,int tar){//使得x做tar的儿子
	while(s[x].f!=tar){
		int f=s[x].f,ff=s[f].f;
		if(ff==tar) {//如果只差一步。
			if(s[f].son[0]==x) rotate(x,1);
			else rotate(x,0);
		}
		else{//否则就利用双旋
			if(s[ff].son[0]==f){
				if(s[f].son[0]==x) {rotate(f,1);rotate(x,1);}
				else {rotate(x,0);rotate(x,1);}
			}
			else{
				if(s[f].son[0]==x) {rotate(x,1);rotate(x,0);}
				else {rotate(f,0);rotate(x,0);}
			}
		}
	}
	if(tar==0) root=x;
}

双旋这个东西很曼妙。

后面的操作就很简单啊。

比如说,以维护关键字来找元素:

int findip(int x){
    int now=root;
    while(x!=s[now].v){
        if(x<s[now].v){
            if(s[now].son[0]==0) break;
            now=s[now].son[0];
        }
        else{
            if(s[now].son[1]==0) break;
            now=s[now].son[1];
        }
    }
    return now;
}

找某个数的前驱:

你可以找到与之相近的点,再找他的前驱(即为左儿子的最右边儿子)

int find_next(int x){
    int ip=findip(x);
    splay(ip,0);
    if(s[ip].v<=x && s[ip].son[1]!=0){
        ip=s[ip].son[1];
        while(s[ip].son[0]!=0) ip=s[ip].son[0];
    }
    if(s[ip].v<=x) return 0;
    return s[ip].v;
}

找后继也是如此哦~:

int find_next(int x){
    int ip=findip(x);
    splay(ip,0);
    if(s[ip].v<=x && s[ip].son[1]!=0){
        ip=s[ip].son[1];
        while(s[ip].son[0]!=0) ip=s[ip].son[0];
    }
    if(s[ip].v<=x) return 0;
    return s[ip].v;
}

找某个数的排名,相当于把它旋到根之后的左子树大小+1(比他小的数的后一个)

int find_rank(int x){
    int ip=findip(x);splay(ip,0);
    return s[s[ip].son[0]].c+1;
}

找排名为k的数,根据左右子树的大小往下找:

int find_num(int x){
    int now=root;
    while(1){
        if(x<=s[s[now].son[0]].c) now=s[now].son[0];
        else if(x>s[now].same+s[s[now].son[0]].c){
            x-=s[now].same+s[s[now].son[0]].c;
            now=s[now].son[1];
        }
        else break;
    }
    return s[now].v;
}

删除某个节点,可以分情况讨论:

1.没有左右儿子,直接清空整棵树。

2.只有左儿子,让左儿子当根。

3.只有右儿子,让右儿子当根。

4.左右儿子都有,要找这个数的前驱来代替他,因为在左子树它是最大的,所以将它旋到x的儿子后,直接将右儿子继承给他即可。。

void del(int x){
    int ip=findip(x);
    splay(ip,0);
    if(s[ip].v!=x) return ; 
    if(s[ip].same>1) s[ip].same--,update(ip);
    else if(s[ip].son[0]==0 && s[ip].son[1]==0) root=0,tot=0;
    else if(s[ip].son[0]!=0 && s[ip].son[1]==0) {root=s[ip].son[0];s[s[ip].son[0]].f=0;}
    else if(s[ip].son[0]==0 && s[ip].son[1]!=0) {root=s[ip].son[1];s[s[ip].son[1]].f=0;}
    else{
        int p=s[ip].son[0];
        while(s[p].son[1]!=0) p=s[p].son[1];
		splay(p,ip);
        root=p;s[p].f=0;
        s[s[ip].son[1]].f=p;
        s[p].son[1]=s[ip].son[1];
        update(p);
    }
}

      好像写的不怎么样,有点赶吧,对不起了,有什么问题或者想吐槽博客的,都可以在下面登陆之后,耐心的解说大骂楼主。

      再次感谢阅读我的博客,希望你们个个IOI金牌









评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值