把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【学习笔记】splay入门(更新中)

声明:本博客所有随笔都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。

前言

终于学习了 spaly \(splay\) !听说了很久,因为dalao总是那这个开玩笑所以对它有深深的恐惧...但是学起来没有那么难啦,可能是因为提前学了替罪羊树?(学替罪羊树真的是痛苦555)

模板

P3369 【模板】普通平衡树

固定变量:

\(edge\) - 这个节点的值

\(tot\) - 这个节点重复的数

\(son[0/1]\) - 左儿子右儿子

\(fa\) - 节点的父亲

\(size\) - 子树的大小

struct ndoe{
	int tot, size, son[2], fa, edge;
}tree[N]; 

add(x)(添加一个点):

1.找到特殊点

2.判断 \(x\) 与特殊节点的值

\ \ 1)相等,则直接 \(++ \ tot\)

\ \ 2)不相等,则新建节点,更新变量

void add(int x)	
{
	int u = root, fa = 0;
	while(u && tree[u].edge != x)
		fa = u, u = tree[u].son[x > tree[u].edge];
	if(u) ++ tree[u].tot;
	else
	{
		u = ++ num;
		if(fa) tree[fa].son[x > tree[fa].edge] = u;
		tree[u].edge = x, tree[u].size = tree[u].tot = 1, tree[u].fa = fa;
	}
	splay(u, 0);
}

del(x)(删除一个点):

1.找到 \(x\) 的前驱后继

2.把前驱 \(splay\) 至根节点,把后继 \(splay\) 成前驱的儿子

3.此时后继的左儿子则为要删除的点,判断此节点的个数

\ \ 1)个数大于一则只用去掉一个,然后 \(splay\)

\ \ 2)个数为一则直接删掉

void del(int x)
{
	int xpre = next(x, 0), xnxt = next(x, 1);
	splay(xpre, 0), splay(xnxt, xpre);
	int u = tree[xnxt].son[0];
	if(tree[u].tot > 1) -- tree[u].tot, splay(u, 0);
	else tree[xnxt].son[0] = 0;
}

splay(x, goal)(将 \(x\) 旋成 \(goal\) 的儿子):

1.判断 \(x\) 是否是 \(goal\) 的儿子

\ \ 1)不是,则判断 \(x\) 和它的父亲、祖先是否在一条线上,进行不同的 \(splay\)

\ \ 2)是,判断 \(x\) 是否是根并更新

void splay(int x, int goal)
{
	while(tree[x].fa != goal)
	{
		int y = tree[x].fa, z = tree[y].fa;
		if(z != goal) ((tree[z].son[0] == y) ^ (tree[y].son[0] == x)) ? rotate(x) : rotate(y);
		rotate(x);
	} 
	if(! goal) root = x;
}

rotate(x)(单旋):

1.更新 \(x\)\(z\) 的父子关系

2.更新 \(y\) 和 ( \(x\) 原来和 \(y\) 对应的那个儿子)的父子关系

3.更新 \(x\)\(y\) 的父子关系

4.\(update \ x、y\)

void rotate(int x)
{
	int y = tree[x].fa, z = tree[y].fa, k = (tree[y].son[1] == x);
	tree[z].son[tree[z].son[1] == y] = x, tree[x].fa = z;
	tree[y].son[k] = tree[x].son[k ^ 1], tree[tree[x].son[k ^ 1]].fa = y;
	tree[x].son[k ^ 1] = y, tree[y].fa = x;
	update(y), update(x);
}

pre(x)(前驱):

1.将 \(x\) 变为树根

2.有左子树则进入左子树,没有则代表没有比它小的数了

3.进入左子树后一路往右子树上凑(找最大的)

nxt(x)(后继):

1.将 \(x\) 变为树根

2.有右子树则进入右子树,没有则代表没有比它大的数了

3.进入右子树后一路往左子树上凑(找最小的)

int next(int x, int f)//我把pre和nxt写到一起啦,实际上是一样的
{
	find(x);
	int u = root;
	if((tree[u].edge > x && f) || (tree[u].edge < x && (! f))) return u;
	u = tree[u].son[f];
	while(tree[u].son[f ^ 1]) u = tree[u].son[f ^ 1];
	return u;
}

find(x)(辅助函数):

1.找到值等于 \(x\) 的那个节点

2.将节点旋转至树根

void find(int x)
{
	int u = root;
	if(! u) return;
	while(tree[u].son[x > tree[u].edge] && tree[u].edge != x)
		u = tree[u].son[x > tree[u].edge];
	splay(u, 0);
}

query(x)(第k大的数):

1.先判断整个树的 \(size\) 是否大于等于 \(x\),若没有则不存在

2.分三种情况继续讨论:

\ \ 1)左子树 \(size\) 大于 \(x\) ,前往左子树

\ \ 2)左子树+这个节点的数的个数大于 \(x\),则直接返回这个节点的值

\ \ 3)更新 \(x\),前往右子树

int query(int x)
{
	int u = root;
	if(tree[u].size < x) return 0;
	while(1)
	{
		int y = tree[u].son[0];
		if(x <= tree[y].size) u = y;
		else if(x <= tree[y].size + tree[u].tot) return tree[u].edge;
		else x -= (tree[y].size + tree[u].tot), u = tree[u].son[1];
	}
}
posted @ 2020-02-08 12:24  Bn_ff  阅读(194)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end