Splay之萌新版

【学习笔记】平衡树 - S p l a y \mathrm{Splay}

I \mathrm{I} 平衡树

  • 平衡树是什么?顾名思义就是左儿子与右儿子相平衡的 (即左儿子的权值小于根节点,右儿子大于根节点的权值)
  • 图1-1
  • s p l a y splay 也是利用这样的性质来进行旋转的

I I \mathrm{II} S p l a y \mathrm{Splay}

2.1 2.1 r o t a t e \mathrm{rotate} 操作

  • 这个操作比较重要,也比较麻烦。我们可以看图理解。

  • 首先我们建立一颗平衡树(下图
    图2-1

  • 我们设 10 10 那个电 z z 8 8 那个电 y y 7 7 那个电 x x 。那么 z , y z,y 分别是 y , x y,x 的父亲。我们现在想要把 x x 旋到 y y 上。那么我们会得到的图:

图2-2

  • 首先 x x 成为 z z 的左儿子,又发现 y y 成了 x x 的右儿子(依然合法,平衡树的性质)。如果 x x 本身有右儿子那么就会成为 y y 的左儿子。于是我们就有了下面的代码:
inline void rotate(int x)
{
	int y=t[x].fa;//x的父亲
	int z=t[y].fa;//y的父亲
	int k=t[y].ch[1]==x;//x是y的右儿子
	t[z].ch[t[z].ch[1]==y]=x;//如果z原来y变成x
	t[x].fa=z;
	t[y].ch[k]=t[x].ch[k^1];//x的与x原来在y的相对的那个儿子变成y的儿子
	t[t[x].ch[k^1]].fa=y;
	t[x].ch[k^1]=y;
	t[y].fa=x;//y的父亲是x
}

2.2 2.2 S p l a y \mathrm{Splay} 操作

  • 这个操作就是把一个节点旋到根节点的操作(难道只要简单地旋两次就行了
  • 这个操作我们分两种情况讨论
    • x , y x,y 分别为 y , z y,z 的同一方向上的儿子
    • x , y x,y 不为 y , z y,z 的同一方向上的儿子
  • 那么我们大力发现:对于情况一我们先旋 y y 再旋 x x ,对于情况二我们直接旋两次 x x (不理解可以手动滑稽一下
  • 于是我们就有了下面的代码:
inline void SPlay(int x,int p)
{
	while(t[x].fa!=p)
	{
		int y=t[x].fa;
		int z=t[y].fa;
		if(z!=p) 
			(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
			//讨论是不是同属一个方向上的儿子,然后讨论是那种旋转方式
		rotate(x);
	}
	if(!p) root=x;//如果旋到的是根那么就更行根
}

2.3 2.3 f i n d \mathrm{find} 操作

  • f i n d \mathrm{find} 顾名思义就是寻找那个元素具体的位置
  • 我们考虑平衡树的性质:越往左越小,越往右越大。于是我们只要不停地向左(如果要找的数更加小)向右递归找答案就行了。找到后把他 s p l a y splay 到根节点。有点类似与二分查找操作。
  • 复杂度 O ( log n ) O(\log n)
  • 于是我们就有了下面的代码:
inline void Find(int x)
{
	int u=root;
	if(!u) return;
	while(t[u].ch[x>t[u].val]&&x!=t[u].val) //一直找不到向左向右递归
		u=t[u].ch[x>t[u].val];
	SPlay(u,0);//再旋到根节点
}

2.4 2.4 I n s e r t \mathrm{Insert} 操作

  • 就是加入一个元素 x x 的操作。
  • 其实这个操作和上面那个差不多。就是先找在树上有没有这个节点,如果本身就有我们只要对那个点进行操作。否则新开一个节点来存储信息。最后再把这个点 s p l a y splay 到根就可以了。注意在向下寻找的过程中始终记录他的父亲是什么。
  • 于是我们就有了下面的代码:
inline void insert(int x)
{
	int u=root,ff=0;
	while(u&&t[u].val!=x)
	{
		ff=u;//记录父亲
		u=t[u].ch[t[u].val<x];//寻找
	}
	if(u) t[u].cnt++;//如果找到了那么就在原来基础上更新
	else 
	{
		u=++tot;//新开一个节点
		if(ff) t[ff].ch[x>t[ff].val]=u;
		t[tot].ch[1]=t[tot].ch[0]=0;
		t[tot].fa=ff;
		t[tot].siz=1;
		t[tot].cnt=1;
		t[tot].val=x;//对那个各个信息节点进行修改
	}
	SPlay(u,0);//再旋到根
}

2.5 2.5 N e x t \mathrm{Next} 操作

  • 其实就是寻找某个元素的前驱以及后继
  • 我们以前驱为例来介绍一下:先确定前驱比他小,所以在左子树上。然后他的前驱是左子树中最大的值所以一直跳右结点就可以了。
  • 后继同理不在赘述
  • 于是我们就有了下面的代码:
inline int Next(int x,int f)//前驱0
{
	Find(x);//先寻找
	int u=root;
	if(t[u].val>x&&f) return u;//是不是前驱/后继
	if(t[u].val<x&&!f) return u; 
	u=t[u].ch[f];
	while(t[u].ch[f^1]) u=t[u].ch[f^1];//要反过来,因为前缀是在右子树里寻找最大的,后继是在左子树寻找最小的
	return u;
}

2.6 2.6 d e l e t e \mathrm{delete} 操作

  • 删除操作吗。。。真的不太想说了。
  • 我们首先找到这个树的前驱把他旋到根,再找到后继节点旋到根节点下面。于是在右子树比后继小的且比前驱大的有且仅有当前数。然后再看这个节点的数量来处理就行了。如果只有 1 1 个直接根除。否则再把那个点(后继的左儿子)旋到根节点。
  • 于是我们就有了下面的代码:
inline void Del(int x)
{
	int las=Next(x,0);
	int nex=Next(x,1);//前驱后继
	SPlay(las,0);
	SPlay(nex,las);//旋丫旋
	int del=t[nex].ch[0];//那个点就是后继的左儿子
	if(t[del].cnt>1)
	{
		t[del].cnt--;
		SPlay(del,0);//旋到根节点
	}
	else t[nex].ch[0]=0;//直接删除
}

猜你喜欢

转载自blog.csdn.net/wangyiyang2/article/details/105740866
今日推荐