关于Splay的学习感受

                                       

【关于Splay】

之前记得五月份听过一次外省金牌选手讲过一次,然后七月份又讲过一次,但本人脑子比较笨,当时完全听得一脸懵逼啊,练了两个月确实不一样,现在谈一下学习Splay的一些感受。

首先欲知Splay为何物,不得不先讲讲它的祖宗:二叉查找树,即BST(Binary Search Tree),关于二叉查找树,它不是一颗空树就是拥有以下几个性质的二叉树:

1.树中每个结点被赋予了一个权值;(下面假设不同结点的权值互不相同。)

2.若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

3.若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

4.左、右子树也分别为二叉查找树;

下图就是一个合法的BST(蜜汁画风【捂脸】):

通常二叉查找树支持如下操作:查找一个元素x在集合中是否存在;插入或删除一个元素,这些都可以通过递归实现。

但这也有个小问题,每个操作的复杂度都取决于树的高度,那么当这棵树退化成一条链的时候,复杂度就退化成O(n)的了,由于二叉查找树的形态不一,所以平衡树就出现了,它的每种操作最坏情况、均摊、期望的时间复杂度就是O(log n)的。而常用平衡树有两种:Splay和Treap,这里介绍Splay,下一篇文章介绍Treap。

(废话一堆然后转入正题)

Splay,即伸展树,是二叉查找树的一种改进,它与二叉查找树性质相似,与其不同的就是它可以自我调整。

关于Splay操作:伸展操作 Splay(x, S)是在保持伸展树有序性的前提下,通过一系列旋转将伸展树 S 中的元素 x调整至树的根部。在调整的过程中,要分以下三种情况分别处理:这个东西常数不能靠谱的可持久化,所以比起Treap而言,Splay的优势似乎仅在于求LCT。

1.节点x的父节点y是根节点:如果x是左儿子,那么就右旋一下,如果是右儿子,那么就左旋一下,一次操作后,x就变成根节点了,完成,下面是示意图:

2.节点x的父节点y不是根节点,y的父节点为z,且x,y均为父节点的左儿子或右儿子,那么就右旋或左旋两次,分别交换y,z和x,y,下面是示意图(灵魂画师再次上线):

3.节点x的父节点y不是根节点,y的父节点为z,x,y一个是左儿子,一个是右儿子,这时如果x是左儿子就先右旋再左旋,如果是右儿子就先左旋再右旋,注意两次都是转x,示意图:

 

所以由上图可以感性理解一下,经旋转的平衡树确实比之前要平衡很多,注意每次操作后都要进行伸展操作,然后这样下来各操作复杂度均摊为O(log n)(因为不会证所以我就直接跳了)。

Splay支持以下操作:

1.Find(x,S):判断元素x是否在伸展树S表示的有序集中。首先,访问根节点,如果x比根节点权值小则访问左儿子;如果x比根节点权值大则访问右儿子;如果权值相等,则说明x在树中;如果访问到空节点,则x不在树中。如果x在树中,则再执行Splay(x,S)调整伸展树。

2.Insert(x,S):将元素x插入伸展树S表示的有序集中。首先,访问根节点,如果x比根节点权值小则访问左儿子;如果x比根节点权值大则访问右儿子;如果访问到空节点t,则把x插入该节点,然后执行Splay(t,S)。

3.Merge(S1,S2):将两个伸展树S1与S2合并成为一个伸展树。其中S1的所有元素都小于S2的所有元素。首先,我们找到伸展树S1中最大的一个元素x,再通过Splay(x,S1)将x调整到伸展树S1的根。然后再将S2作为x节点的右子树。这样,就得到了新的伸展树S。如图所示:

4.Delete(x,S):把节点x从伸展树表示的有序集中删除。首先,执行Splay(x,S)将x旋转至根节点。然后取出x的左子树S1和右子树S2,执行Merge(S1,S2)把两棵子树合并成S。如图所示:

5.Split(x,S):以x为界,将伸展树S分离为两棵伸展树S1和S2,其中S1中所有元素都小于x,S2中的所有元素都大于x。首先执行Find(x,S),将元素x调整为伸展树的根节点,则x的左子树就是S1,而右子树为S2。图示同上。

除了以上操作,Splay还支持求最大值、最小值、前驱后继,同时,Splay还能进行区间操作,对于待操作区间[l,r],我们将l-1通过Splay旋至根节点处,再将r+1旋至根节点右儿子处,这时r+1的左儿子就是待操作区间,可以像线段树那样打lazy-tag标记,然后具体实现见下方代码(主要给Rotate(左旋右旋合并版,只用打一段即可,不必打一个Zig打一个Zag),Splay,build,lazy,find(int k)(寻找第k大数),getnext(求后继,前驱类似)):

(因为从来不打指针所以只能够打打数组)

结构体:

struct Num{                                //根据题目不同决定
	int val,id;
	bool operator<(const Num &a)const{
		if(val==a.val)
		  return id<a.id;
		return val<a.val;
	}
}So[MAXN/5];

struct Tr{                                 //平衡树
	int fa,sum;
	int val,c[2],lz;
}tr[MAXN];
int tot,root,n;

1.Rotate:

void Rotate(int x,int k)
{
	if(tr[x].fa==-1)
	  return ;
	int fa=tr[x].fa,w;
	lazy(fa);
	lazy(x);
	tr[fa].c[!k]=tr[x].c[k];
	if(tr[x].c[k]!=-1)
	  tr[tr[x].c[k]].fa=fa;
	tr[x].fa=tr[fa].fa,tr[x].c[k]=fa;
	if(tr[fa].fa!=-1)
	{
		w=tr[tr[fa].fa].c[1]==fa;
		tr[tr[fa].fa].c[w]=x;
	}
	tr[fa].fa=x;
	Push(fa);
	Push(x);
}

2.Splay

void Splay(int x,int goal)
{
	if(x==-1)
	  return ;
	lazy(x);
	while(tr[x].fa!=goal)
	{
		int y=tr[x].fa;
		lazy(tr[y].fa);
		lazy(y),lazy(x);
		bool w=x==tr[y].c[1];
		if(tr[y].fa!=goal&&w==(y==tr[tr[y].fa].c[1]))
		  Rotate(y,!w);
		Rotate(x,!w);
	}
	if(goal==-1)
	  root=x;
	Push(x);
}

3.build

int build(int l,int r,int f)          //返回根节点
{
	if(r<l)
	  return -1;
	int mid=l+r>>1;
	int ro=newtr(mid,f,mid);
	data[mid]=ro;
	tr[ro].c[0]=build(l,mid-1,ro);
	tr[ro].c[1]=build(mid+1,r,ro);
	Push(ro);
	return ro;
}

4.lazy

void lazy(int id)                  //此懒标记表示交换左右儿子,即区间反转
{
	if(tr[id].lz)
	{
		swap(lc,rc);
		tr[lc].lz^=1,tr[rc].lz^=1;
		tr[id].lz=0;
	}
}

5.find

int find(int k)
{
	int id=root;
	while(id!=-1)
	{
		lazy(id);
		int lsum=(lc==-1)?0:tr[lc].sum;
		if(lsum>=k)
		{
			id=lc;
		}
		else
		  if(lsum+1==k)
		    break;
		  else
		  {
		  	k=k-lsum-1;
		  	id=rc;
		  }
	}
	return id;
}

6.getnext

int Getnext(int id)
{
	lazy(id);
	int p=tr[id].c[1];
	if(p==-1)
	  return id;
	lazy(p);
	while(tr[p].c[0]!=-1)
	{
		p=tr[p].c[0];
		lazy(p);
	}
	return p;
}

关于Splay,在实现上还有许多细节,如每次操作后的Splay,此处不再赘述。

以上大概是我关于Splay的一些学习总结,以后可能还会填坑【PS:感谢wcr大佬的帮助】

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可持久化splay是一种数据结构,它是对splay树进行修改和查询的一种扩展。在传统的splay树中,对树的修改操作会破坏原有的树结构,而可持久化splay树则允许我们对树进行修改、查询,并且可以保存修改后的每个版本的树结构。 在可持久化splay树中,我们不会直接对原树进行修改,而是通过复制每个节点来创建新的版本。这样,每个版本都可以独立地修改和查询,保留了原有版本的结构和状态。每个节点保存了其左子树和右子树的引用,使得可以在不破坏原有版本的情况下进行修改和查询。 为了实现可持久化splay树,我们可以使用一些技巧,比如引用中提到的哨兵节点和假的父节点和孩子节点。这些技巧可以帮助我们处理根节点的旋转和其他操作。 此外,可持久化splay树还可以与其他数据结构相结合,比如引用中提到的可持久化线段树。这种结合可以帮助我们解决更复杂的问题,比如区间修改和区间查询等。 对于可持久化splay树的学习过程,可以按照以下步骤进行: 1. 理解splay树的基本原理和操作,包括旋转、插入、删除和查找等。 2. 学习如何构建可持久化splay树,包括复制节点、更新版本和保存历史版本等。 3. 掌握可持久化splay树的常见应用场景,比如区间修改和区间查询等。 4. 深入了解与可持久化splay树相关的其他数据结构和算法,比如可持久化线段树等。 在解决问题时,可以使用二分法来确定答案,一般称为二分答案。通过对答案进行二分,然后对每个答案进行检查,以确定最终的结果。这种方法可以应用于很多问题,比如引用中提到的在线询问问题。 综上所述,可持久化splay是一种对splay树进行修改和查询的扩展,可以通过复制节点来创建新的版本,并且可以与其他数据结构相结合解决更复杂的问题。学习过程中可以按照一定的步骤进行,并且可以使用二分法来解决一些特定的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [[学习笔记]FHQ-Treap及其可持久化](https://blog.csdn.net/weixin_34283445/article/details/93207491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [可持久化数据结构学习笔记](https://blog.csdn.net/weixin_30376083/article/details/99902410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值