Splay基础操作

本文介绍了Splay树的基本概念,包括旋转操作及Splay操作,并详细阐述了如何利用Splay树进行插入、删除、查询等操作。

Splay

Splay是一类二叉查找树,与其他平衡树相比,也是运用旋转保证复杂度
其最重要的操作便是rotaterotaterotate andandand spalyspalyspaly
先来谈旋转,我们都知道,旋转是这样的
在这里插入图片描述
仔细观察后,我们会发现,旋转操作可以拆解为三步,设xxxyyy的父亲,kkk表示yyyxxx的儿子(左0右1),现在要将yyy旋转

  1. xxx的父节点的子节点从xxx改为yyy
  2. xxxkkk儿子换成yyy1−k1-k1k儿子
  3. yyy1−k1-k1k儿子变成xxx
    写成代码即为:
void rotate(int x){
	int y=t[x].f,z=t[y].f,k=t[y].ch[1]==x;
    t[y].ch[k]=t[x].ch[k^1]; 
	t[t[x].ch[k^1]].f=y;
	t[z].ch[t[z].ch[1]==y]=x; 
	t[x].f=z;
    t[x].ch[k^1]=y;
	t[y].f=x;
    pushup(y);
	pushup(x);//不要忘记旋转后更新信息
}

接着便是SplaySplaySplay操作,对于使用到的一个节点xxx,我们将其一直旋转至树根,便是SplaySplaySplay操作,在旋转过程中,不能一直单旋转,我们发现x和father(x)x和father(x)xfather(x)都是自己父亲的kkk儿子的情况下,先旋转xxx的父节点,再旋转xxx会使得树变得更加均衡,会更加优秀

void splay(int x,int goal=0){
	while(t[x].f!=goal){
		int y=t[x].f,z=t[y].f;
		if(z!=goal){
			t[z].ch[1]==y ^ t[y].ch[1]==x?rotate(x):rotate(y);
		}
		rotate(x);
	}
	if(!goal)root=x;
}

然后便是其他平衡树的常规操作了

  1. 插入 xxx
  2. 删除 xxx 数(若有多个相同的数,因只删除一个)
  3. 查询 xxx 数的排名(排名定义为比当前数小的数的个数 +1+1+1 )
  4. 查询排名为 xxx 的数
  5. xxx 的前驱(前驱定义为小于 xxx,且最大的数)
  6. xxx 的后继(后继定义为大于 xxx,且最小的数)

我们来分别考虑几种操作,为了方便操作,我们往树中插入两个无穷大和无穷小的哨兵
1.插入操作
在树中查找xxx,并记录其父节点ppp,若找到了xxx节点,则将其计数的cntcntcnt加上1,若没有找到xxx节点,此时ppp一定是一个叶子节点,直接判断xxx应是其的哪一个儿子,直接加上关系即可,最后还得SplaySplaySplay一下

void insert(int x){
	int u=root,p=0;
	while(u&&t[u].val!=x){
		p=u;
		u=t[u].ch[x>t[u].val];
	}
	if(!u){
		u=++tot;
		t[u].cnt=t[u].siz=1;
		t[u].f=p,t[u].val=x;
		if(p)t[p].ch[x>t[p].val]=tot;
	}
	else{
		t[u].cnt++;
	}
	splay(u);
}

2.删除操作
因为普普通通的删除很麻烦,所以我们采用一种简单的方式:
xxx在树中的严格前驱和严格后继找到,将严格前驱旋转到树根,将严格后继旋转为严格前驱的儿子,此时xxx一定位于t[t[root].ch[1]].ch[0]t[t[root].ch[1]].ch[0]t[t[root].ch[1]].ch[0],执行即可

void Delete(int x){
	int last=nxt(x,0);
	int nex=nxt(x,1);
	splay(last,0);
	splay(nex,last);
	if(t[t[nex].ch[0]].cnt>1){t[t[nex].ch[0]].cnt--;splay(t[nex].ch[0]);}
	else  t[nex].ch[0]=0;
	pushup(nex),pushup(last);
}

3.查询排名
我们可以对于每一个节点记录sizsizsiz,表示节点xxx及其子树中有多少个节点,这样我们就可以找到节点xxx后将其旋转至树根,求树根左儿子的sizsizsiz再加一即可,但由于哨兵无穷小的存在,无需加一

int rak(int x){
	find(x);//查找到x所在节点,并将其旋转至根节点
	return t[t[root].ch[0]].siz;
} 

4.查找排名为kkk的数
我们可以从根节点开始查找,对于一个节点ppp,若k≤t[t[p].ch[0]].sizk\le t[t[p].ch[0]].sizkt[t[p].ch[0]].siz,那么这个节点就在ppp的左子树中,若t[t[p].ch[0]].siz<k≤t[t[p].ch[0]].siz+t[p].cntt[t[p].ch[0]].siz<k\le t[t[p].ch[0]].siz+t[p].cntt[t[p].ch[0]].siz<kt[t[p].ch[0]].siz+t[p].cnt,则说明当前节点ppp即为要找的节点,否则令k=k−t[p].cnt−t[t[p].ch[0]].sizk=k-t[p].cnt-t[t[p].ch[0]].sizk=kt[p].cntt[t[p].ch[0]].siz,然后再进入右子树

int kth(int u){
	int x=root;
	if(t[x].siz<u)return 0;
	while(1){
		if(ls&&u<=t[ls].siz){
			x=ls;
		}
		else if(t[ls].siz+t[x].cnt<u){
			u-=t[ls].siz+t[x].cnt;
			x=rs;
		}
		else {splay(x);return t[x].val;}
	}
}

5 and  6
前驱和后继的查找可以合成一段代码
xxx的前驱为xxx成为根节点之后,左子树中最大的(即左子树的最右子节点(一直往右跳即可))
后继类似

int nxt(int x,int f){
    find(x);
    if ((f==0&&t[root].val<x)||(f==1&&t[root].val>x))return root;//特判x不在树中的情况
    int u=t[root].ch[f];
    while (t[u].ch[!f])u=t[u].ch[!f];
    splay(u);
    return u;
}

还有就是findfindfind函数

void find(int x){
	int u=root;
	while(t[u].ch[x>t[u].val]&&t[u].val!=x){
		u=t[u].ch[x>t[u].val];
	}
	splay(u);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值