首页 > 其他分享 >Splay学习笔记

Splay学习笔记

时间:2023-02-10 08:44:48浏览次数:60  
标签:tmp 学习 ch int father 笔记 Splay inline 节点

Splay学习笔记

说在前面:本文多参考这篇优秀博文,讲的十分详细,以下是我个人对Splay的理解。

一、普通Splay

例题传送门

(一)前置知识:

  1. 前驱:当前序列中小于目标数字的数的最大值
  2. 后继:当前序列中大于目标数字的数的最小值
  3. 二叉搜索树BST

(二)变量定义:

  1. ch[u][2]表示第u个节点的两个子节点,其中ch[u][0]表示左子节点,ch[u][1]表示右子节点;
  2. 解释如下的注释
struct Node
{
	int val;//当前编号节点的权值
	int siz;//以自己(包括自己)为根的树上的节点所存储的值的个数之和(即所有cnt之和)
	int cnt;//当前节点上存在数值相等的值的个数
	int father;//当前节点的父亲节点
}t[N];
  1. n表示操作数;
  2. tot表示平衡树节点总数;
  3. root表示根节点编号;

(三)各种操作:

操作一:Insert()[节点插入函数]

inline void Insert(int x)//插入一个值为x的节点
{
	int tmp=root,fa=0;
	while(tmp&&t[tmp].val!=x)//如果当前节点存在且值不为x
	{
		fa=tmp;//上一个节点赋值为父亲节点
		tmp=ch[tmp][x>t[tmp].val];//x>t[tmp].val可以快捷地判断左右儿子
	}
	if(tmp)t[tmp].cnt++;//如果存在一个这样的直接累加即可
	else
	{
		tmp=++tot;//添加一个新节点
		if(fa)ch[fa][x>t[fa].val]=tmp;//如果有父亲节点则将当前节点作为其子节点
		ch[tmp][0]=ch[tmp][1]=0;
		t[tmp].cnt=t[tmp].siz=1;
		t[tmp].val=x;t[tmp].father=fa;
	}
	splay(tmp);//将当前节点伸展至根节点
}

操作二:splay()[伸展函数]

inline void splay(int x,int goal=0)//将编号为x的节点伸展至goal的子节点(若不导入参数则goal默认为0即伸展至根节点)
{
	while(t[x].father!=goal)//如果还没有伸展至goal的子节点
	{
		int y=t[x].father;//父亲
		int z=t[y].father;//爷爷
		if(z!=goal)
		{
			if(Dir(x)==Dir(y))rotate(y);//一字旋先旋转父亲
			else rotate(x);//之字旋先旋转儿子
		}
		rotate(x);//再次旋转儿子
	}
	if(!goal)root=x;//如果goal为零则此时x已经是根节点了
}

操作三:rotate()[旋转函数]

大型换爹现场

inline void rotate(int x)//zig和zag的结合版(这部分建议画图理解)
{
	int y=t[x].father;//父亲
	int z=t[y].father;//爷爷
	int k=Dir(x); //1->zag 0->zig
	int w=ch[x][k^1];//将x准备用来接上父节点的子树转存
	ch[y][k]=w;t[w].father=y;//用转存的子树代替原先x在父节点中的子树
	ch[z][Dir(y)]=x;t[x].father=z;//将爷爷的子树赋值为自己
	ch[x][k^1]=y;t[y].father=x;//儿子当爹
	pushup(y);pushup(x);//上传
}

操作四:Dir()[判断左右儿子函数]

inline bool Dir(int x)//返回当前节点是其父节点的哪个儿子
{
	return ch[t[x].father][1]==x;
}

操作五:pushup()[上传函数]

inline void pushup(int k)//更新当前节点的siz值
{
	t[k].siz=t[ch[k][0]].siz+t[ch[k][1]].siz+t[k].cnt;
}

操作六:Find()[查找值对应节点的编号并伸展至根节点函数]

inline void Find(int x)
{
	int tmp=root;//从根节点开始往下
	while(ch[tmp][x>t[tmp].val]&&t[tmp].val!=x)
	tmp=ch[tmp][x>t[tmp].val];//没有值为x的节点时则采用其前驱或后继
	splay(tmp);
}

操作七:Delete()[删除函数]

inline void Delete(int x)//删除值为x的节点
{
	int L=query_pre(x);//找前驱
	int R=query_sur(x);//找后继
	splay(L);splay(R,L);//将前驱转至根节点,后继作为其右儿子则值为x的节点是后继的左儿子
	int del=ch[R][0];
	if(t[del].cnt>1)//如果有多个值为x的节点减一即可
	{
		t[del].cnt--;
		splay(del);
	}
	else ch[R][0]=0;
	pushup(R);pushup(root);//上传至祖先节点,注意有顺序性
}

操作八:query_pre()[查找前驱函数]

inline int query_pre(int x)
{
	Find(x);//将值为x的节点伸展至根节点
	if(t[root].val<x)return root;//若当前节点直接就是x的前驱
	int tmp=ch[root][0];
	while(ch[tmp][1])tmp=ch[tmp][1];//否则就为左子树中最右边的子节点
	splay(tmp);//将答案伸展至根节点
	return tmp;
}

操作九:query_sur()[查找后继函数]

inline int query_sur(int x)
{
	Find(x);//将值为x的节点伸展至根节点
	if(t[root].val>x)return root;//若当前节点直接就是x的后继
	int tmp=ch[root][1];
	while(ch[tmp][0])tmp=ch[tmp][0];//否则就为右子树中最左边的子节点
	splay(tmp);//将答案伸展至根节点
	return tmp;
}

操作十:query_rank()[查找排名函数]

inline int query_rank(int x)
{
	Find(x);//将值为x的节点伸展至根节点
	if(t[root].val<x)return t[ch[root][0]].siz+t[root].cnt;//如果根节点是前驱
	return t[ch[root][0]].siz;//如果根节点是自己或者后继
}

操作十一:query_kth()[查找排名为k的值的函数]

inline int query_kth(int k)//需要在主程序中事先加入一个正无穷节点和负无穷节点来规避死循环,所以询问排名为k+1的数实际上返回的是排名为k的数的编号
{
	int tmp=root;
	while(true)
	{
		if(k<=t[ch[tmp][0]].siz&&ch[tmp][0])//如果这个数在左子树中
		{
			tmp=ch[tmp][0];
		}
		else if(k>t[ch[tmp][0]].siz+t[tmp].cnt)//如果这个数在右子树中
		{
			k-=t[ch[tmp][0]].siz+t[tmp].cnt;
			tmp=ch[tmp][1];
		}
		else//已经找到
		{
			splay(tmp);//将当前节点伸展至根节点
			return tmp;
		}
	}
}

(四)完整代码:

#include<bits/stdc++.h>
#define init read()
using namespace std;
const int INF=0x7f7f7f7f;
const int N=100005;
int n,ch[N][2];
int root=0,tot=0;
struct Node
{
	int val;
	int siz;
	int cnt;
	int father;
}t[N];
inline int read()
{
	int mmm=0,ff=1;char xx=getchar();
	while((xx<'0'||xx>'9')&&xx!='-')xx=getchar();
	if(xx=='-')ff=-1,xx=getchar();
	while(xx>='0'&&xx<='9')
	mmm=mmm*10+xx-'0',xx=getchar();
	return mmm*ff;
}
inline void write(int x)
{
	if(x<0)
	{
		putchar('-');
		x=-x;
	}
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
inline bool Dir(int x)
{
	return ch[t[x].father][1]==x;
}
inline void pushup(int k)
{
	t[k].siz=t[ch[k][0]].siz+t[ch[k][1]].siz+t[k].cnt;
}
inline void rotate(int x)
{
	int y=t[x].father;
	int z=t[y].father;
	int k=Dir(x); //1->zag 0->zig
	int w=ch[x][k^1];
	ch[y][k]=w;t[w].father=y;
	ch[z][Dir(y)]=x;t[x].father=z;
	ch[x][k^1]=y;t[y].father=x;
	pushup(y);pushup(x);
}
inline void splay(int x,int goal=0)
{
	while(t[x].father!=goal)
	{
		int y=t[x].father;
		int z=t[y].father;
		if(z!=goal)
		{
			if(Dir(x)==Dir(y))rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
	if(!goal)root=x;
}
inline void Find(int x)
{
	int tmp=root;
	while(ch[tmp][x>t[tmp].val]&&t[tmp].val!=x)
	tmp=ch[tmp][x>t[tmp].val];
	splay(tmp);
}
inline void Insert(int x)
{
	int tmp=root,fa=0;
	while(tmp&&t[tmp].val!=x)
	{
		fa=tmp;
		tmp=ch[tmp][x>t[tmp].val];
	}
	if(tmp)t[tmp].cnt++;
	else
	{
		tmp=++tot;
		if(fa)ch[fa][x>t[fa].val]=tmp;
		ch[tmp][0]=ch[tmp][1]=0;
		t[tmp].cnt=t[tmp].siz=1;
		t[tmp].val=x;t[tmp].father=fa;
	}
	splay(tmp);
}
inline int query_pre(int x)
{
	Find(x);
	if(t[root].val<x)return root;
	int tmp=ch[root][0];
	while(ch[tmp][1])tmp=ch[tmp][1];
	splay(tmp);
	return tmp;
}
inline int query_sur(int x)
{
	Find(x);
	if(t[root].val>x)return root;
	int tmp=ch[root][1];
	while(ch[tmp][0])tmp=ch[tmp][0];
	splay(tmp);
	return tmp;
}
inline void Delete(int x)
{
	int L=query_pre(x);
	int R=query_sur(x);
	splay(L);splay(R,L);
	int del=ch[R][0];
	if(t[del].cnt>1)
	{
		t[del].cnt--;
		splay(del);
	}
	else ch[R][0]=0;
	pushup(R);pushup(root);
}
inline int query_rank(int x)
{
	Find(x);
	if(t[root].val<x)return t[ch[root][0]].siz+t[root].cnt;
	return t[ch[root][0]].siz;
}
inline int query_kth(int k)
{
	int tmp=root;
	while(true)
	{
		if(k<=t[ch[tmp][0]].siz&&ch[tmp][0])
		{
			tmp=ch[tmp][0];
		}
		else if(k>t[ch[tmp][0]].siz+t[tmp].cnt)
		{
			k-=t[ch[tmp][0]].siz+t[tmp].cnt;
			tmp=ch[tmp][1];
		}
		else
		{
			splay(tmp);
			return tmp;
		}
	}
}
inline void print(int x)
{
	write(x);putchar('\n');
}
int main()
{
	freopen("A2128(Splay).in","r",stdin);
	freopen("A2128(Splay).out","w",stdout);
	n=init;
	Insert(INF);
	Insert(-INF);
	for(int i=1;i<=n;i++)
	{
		int opt=init,x=init;
		if(opt==1)Insert(x);
		else if(opt==2)Delete(x);
		else if(opt==3)print(query_rank(x));
		else if(opt==4)print(t[query_kth(x+1)].val);
		else if(opt==5)print(t[query_pre(x)].val);
		else if(opt==6)print(t[query_sur(x)].val);
	}
	return 0;
}

标签:tmp,学习,ch,int,father,笔记,Splay,inline,节点
From: https://www.cnblogs.com/Dr-Glitch/p/17107714.html

相关文章

  • 读Java实战(第二版)笔记06_新的日期和时间API
    1. Java8之前的库对日期和时间的支持非常不理想2. TemporalField接口2.1. 定义了如何访问temporal对象某个字段的值的接口2.2. ChronoField枚举2.2.1. 实现Temp......
  • 第二十二天python3 classmethod、staticmethod、property装饰器学习笔记
    classmethod1、在类定义中,使用@classmethod装饰器修饰的方法;2、必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身;3、cls这个标识符可以是任意合法名......
  • 深度学习笔记——线性回归
    #导入相关包importtensorflowastfimportnumpyasnpimportpandasaspdimportmatplotlib.pyplotasplt%matplotlibinline#读取数据data=pd.read_csv('......
  • linux学习
    Linux是什么Linux是一个开源,免费的操作系统,具有稳定性,安全性,处理多并发。目前很多企业级的项目(c/c++/php/python/java/go)都会部署到linux/unix系统上linux的应用领域......
  • 蜗牛式学习Java--运行我的第一个程序
    安装jdk安装idea工具  packagecom.itheima;//java中最小组成单位是classpublicclassHelloWorld{//main是Java程序的入口publicstaticvoidmain......
  • python学习——【第五弹】
    前言上一篇文章​​python学习——【第四弹】​​不可变序列字符串和可变序列列表;今天这篇文章接着介绍不可变序列元组。元组元组是python内置的数据结构之一,有序,可多......
  • 网络最大流学习笔记
    网络最大流学习笔记形式化的定义网络流问题是指:给定一张有向图(称之为网络)\(G(V,E)\),每条边都有一个权值(称之为容量)\(c(u,v)\),对于\((u,v)\notinE\)有\(......
  • 自我介绍和学习记录
    这个作业属于哪个课程https://edu.cnblogs.com/campus/fzzcxy/2023learning这个作业要求在哪里https://edu.cnblogs.com/campus/fzzcxy/2023learning/homework/1......
  • kubernetes(k8s)基础学习-kubernetes是什么?有什么用?
    kubernetes(k8s)基础学习-kubernetes是什么?一、认识DockerDocker是什么先来看看Docker的图标:一条鲸鱼背上驮着四方形块的物品,就像一条海运船上装满集装箱,集装箱里......
  • C语言学习1
    常见的校招/社招要求:1.计算机语言(C/C++/JAVA等);2.数据结构和算法3.操作系统;4.计算机网络+网络编程;5.数据库;6.脚本语言。计算机语言的学习不要贪多而不精,选一门深入学......