[刷题之旅no40]P3369 【模板】普通平衡树&&[数据结构no4]splay树

直接看题解,毕竟还没学这种高级数据结构,真是有点难啊!
六种操作(按难度顺序排名)
1.查找(4种:查询x的排名,查询排名为x的数,查找前驱,查找后继)
2.修改(2种:插入,删除)
3.基础维护操作(2种:旋转,伸展)
最简单的:
1.查找类:
只需要根据当前给出的数据结构进行查找就可以了,利用到当前数据结构的特点,但是本身没有对数据结构的理解进行过多的考察,所以最简单
2.修改类:
插入和删除,涉及到对数据结构的理解,并且需要熟悉伸展和旋转操作,算是数据结构的进阶(比较难,需要学会运用一些该数据结构的操作)
3.基础维护操作:
修改类进行的基础,为重难点操作,同时是当前数据结构最具特色的一部分
逐一解决把
数据结构:
1.父亲结点下标
2.双子结点下标
3.当前结点加上当前结点的所有子节点的数量,可以称之为以当前结点作为根节点的树的总结点数
4.当前结点重复次数
5.当前节点的值
1.查找:
参数即为数值x
设置一个u为当前树的根节点
如果是0,那么整个树都是空的,找不到
如果u不是0,那么说明当前树不空
和当前u的val值进行比较
如果大于u的val值
判断当前u的右子树是不是空
如果不空,那么直接更新u为右子树
如果是空的,说明走投无路,找不到
如果小于u的val值
判断当前u的左子树是不是空的
如果不空,那么直接更新u为左子树
如果是空的,说明走投无路,找不到
如果等于
直接break
最终把u伸展到根节点上
2.查询x的排名
直接查找x(目的就是为了把x伸展到根节点上面)
直接输出根节点的左子树的孩子数
3.查询排名为x的数
令u为根节点
判断如果x比当前子树的所有结点总和还要大,直接-1
y为u的左子树
判断当前x的值
如果比左子树的儿子加上u自己的数量都要大
那么说明x的排名在u的后面
直接让x减去左子树儿子数量加上自己的数量,因为在更新u为右子树之后,x的排名数量就是相当于u的右子树来看了
此时更新u的值为右子树
如果小于等于左子树的儿子
说明在左子树中,此时更新u,但是不用更新x
如果二者都不满足,说明x就等于当前的u
直接return u.val即可
4.查询前驱后继:
前驱和后继
1.直接find(x),相当于伸展他的位置到根节点
如果没找到,那么就要判断此时找到的这个最近的结点是是否就是x的前驱后者后继//细节
2.如果是前驱,一开始直接取根节点的左子树
一直访问左子树的右子树,直到当前节点的右子树为空停止
3.如果是后继,那么一开始直接取根节点的右子树
一直访问右子树的左子树,直到当前结点的左子树为空停止
返回值应该是当前查找到符合条件的节点的位置,因为到时候删除操作里面会用到
2.修改
1.删除操作:
1.找到前驱
2.找到后继
3.伸展前驱到根节点
4.伸展后继到前驱的子节点
结果被寻找到结点就到了后继的左子节点
判断当前左子节点的数量是否大于1;
大于1直接减一
等于1则更新后继右子树为0
2.插入操作:
1.设置一u,用来当拨针
2.设置一个ff,用来时刻更新当前u的父节点
3.循环比较要插入的x,
如果u等于0,或者u对应的结点值等于x
直接braek
否则更新u为左子树或者右子树(取决于当前u结点对应的值和x的相对大小)
最终判断u是不是为空
如果不是空,那么相应节点的cnt值++;
如果是空,那么就需要新建结点
tot值++;把这个新建结点填入
左右子节点0;cnt=1;val=x,f=ff,son值等于1
最后把u伸展到子节点上
3.基础性操作
1.cntnum:
函数用来计算给以给定结点为根节点的树的总结点数
左子树的son值加上右子树的son值,最后加上自己的cnt值
2.旋转操作,
需要用if判断好几种不同的情况
取出要旋转结点的父节点y,爷爷结点z
然后判断当前三者的连接情况
说白了,就是先把爷爷结点和孙子节点链起来
如果父亲节点是爷爷节点的左儿子,
那孙子就代替左儿子位置
如果父亲节点是右儿子
那么孙子就代替右儿子的位置
孙子的父亲就变成爷爷
同时判断父亲节点和儿子结点关系
如果儿子是父亲结点的右儿子
那么父亲结点的右儿子变成儿子结点的左儿子
儿子结点的左儿子的父亲变成父亲结点
儿子结点的左儿子变成父亲
如果儿子是父亲结点的左儿子
那么儿子节点的右儿子变成父亲节点的左儿子
儿子节点的右儿子的父亲变成父亲节点
儿子节点的左儿子变成父亲
最后父亲节点的父亲变成儿子
更新儿子节点和父亲节点的总结点值
3.伸展操作:
需要参数:结点编号和伸展到的位置
当我要伸展到的编号的父亲结点不等于我要伸展到的位置时:
那么我就要判断一下伸展情
此时取x的父亲节点和爷爷结点,
当爷爷结点不是我要找到的结点时
判断三者此时的链接情况
三者同线:
那么此时旋转y、
三者不同线:
此时再旋转x
最后再判断的外面,旋转一次x
每次循环都更新父亲节点和爷爷结点
如果goal==0那么直接更新root等于x
结束

写了这么多,说实话我并不是很懂原理
哎。。只能慢慢学了,实在有点费时间啊
好的:
错误1:
旋转出错,逻辑错误,简而言之,转反了
错误2:
插入出错,当x的值在原树中不存在时,需要插入到一个新的空位,同时也要判断其父节点是不是空,如果父节点不是空的,那么我需要更新他的父节点的儿子结点,同时看看是左子树还是右子树
错误3:
最后打印结果,包括函数使用出现了错误
1.前驱后继函数,返回值为下标,而题目要求输出值,所以错误
2.排名函数,由于前面加上一个最大值,所以输入的排名要在x基础上面加一;

#include<stdio.h>
#define maxn 500500
typedef struct
{
	int f;
	int numN;
	int cnt;
	int ch[2];
	int val;
}TRE;
TRE tree[maxn];
int root=0,tot=0,n,opt,x;
void cntnode(int x)
{
	tree[x].numN=tree[tree[x].ch[0]].numN+tree[tree[x].ch[1]].numN+tree[x].cnt;
}
//OK
void rota(int x)
{
	int y=tree[x].f;
	int z=tree[y].f;
	if(tree[z].ch[0]==y)
	{
		tree[z].ch[0]=x;
	}
	else
	{
		tree[z].ch[1]=x;
	}
	tree[x].f=z;
	if(tree[y].ch[1]==x)
	{
		tree[y].ch[1]=tree[x].ch[0];
		tree[tree[x].ch[0]].f=y;
		tree[x].ch[0]=y;
	}
	else
	{
		tree[y].ch[0]=tree[x].ch[1];
		tree[tree[x].ch[1]].f=y;
		tree[x].ch[1]=y;
	}
	tree[y].f=x;
	cntnode(y);
	cntnode(x);
}
//ok
void splay(int x,int pos)
{
	while(tree[x].f!=pos)
	{
		int y=tree[x].f;
		int z=tree[y].f;
		if(z!=pos)
		{
			if(tree[z].ch[0]==y&&tree[y].ch[0]==x)
			{
				rota(y);
			}
			else if(tree[z].ch[0]!=y&&tree[y].ch[0]!=x)
			{
				rota(y);
			}
			else
			{
				rota(x);
			}
		}
		rota(x);
	}
	if(pos==0)
	{
		root=x;
	}
}

void find(int x)
{
	int u=root;
	if(u==0) return ;
	while(1)
	{
		if(x>tree[u].val)
		{
			if(tree[u].ch[1]!=0)
			{
				u=tree[u].ch[1];
			}
			else
			{
				break;
			}
		}
		else if(x<tree[u].val)
		{
			if(tree[u].ch[0]!=0)
			{
				u=tree[u].ch[0];
			}
			else
			{
				break;
			}
		}
		else
		{
			break;
		}
	}
	splay(u,0);
}

void insert(int x)
{
	int u=root;
	int ff=0;
	while(1)
	{
		ff=u;
		if(x>tree[u].val)
		{
			u=tree[u].ch[1];
			if(u==0)
			{
				break;
			}
		}
		else if(x<tree[u].val)
		{
			u=tree[u].ch[0];
			if(u==0)
			{
				break;
			}
		}
		else//相等,说明找到了这个值 
		{
			break;
		}
	}
	if(u==0)//需要添加新东西 
	{
		tot++;
		u=tot;
		tree[tot].val=x;
		tree[tot].ch[0]=0;
		tree[tot].ch[1]=0;
		tree[tot].f=ff;
		if(ff!=0)
		{
			if(x>tree[ff].val)
			{
				tree[ff].ch[1]=u;
			}
			else
			{
				tree[ff].ch[0]=u;
			}
		}
		tree[tot].cnt=1;
		tree[tot].numN=1;
	}
	else
	{
		tree[u].cnt++;
	}
	splay(u,0);//在这里会更新numN的值,不需要在上面加一次了 
}

int next(int x,int f)//返回的是找到结点的位置,因为删除操作会用到 
{
	find(x);
	int u=root;
	if(tree[u].val>x&&f==1)
	{
		return u;
	}
	else if(tree[u].val<x&&f==0)
	{
		return u;
	}
	if(f==0)//找前驱
	{
		u=tree[u].ch[0];
		while(tree[u].ch[1]!=0)
		{
			u=tree[u].ch[1];
		}
	}
	else
	{
		u=tree[u].ch[1];
		while(tree[u].ch[0]!=0)
		{
			u=tree[u].ch[0];
		}
	}
	return u;
}
void delet(int x)
{
	int last=next(x,0);
	int later=next(x,1);
	splay(last,0);
	splay(later,last);
	int del=tree[later].ch[0];
	if(tree[del].cnt>1)
	{
		tree[del].cnt--;
		splay(del,0);
	}
	else
	{
		tree[later].ch[0]=0;
	}
}
int check(int x)
{
	int u=root;
	int y;
	if(x>tree[u].numN||x<=0) return -1;
	while(1)
	{
		y=tree[u].ch[0];
		if(x>tree[u].cnt+tree[y].numN)
		{
			x=x-(tree[u].cnt+tree[y].numN);
			u=tree[u].ch[1];
		}
		else if(x<=tree[y].numN)
		{
			u=y;
		}
		else
		{
			return tree[u].val;
		}
	}
}
int main()
{
	insert(-100000000);
    insert(+100000000);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d %d",&opt,&x);
		if(opt==1)
		{
			insert(x);
		}
		else if(opt==2)
		{
			delet(x);
		}
		else if(opt==3)
		{
			find(x);
			printf("%d\n",tree[tree[root].ch[0]].numN);
		}
		else if(opt==4)
		{
			printf("%d\n",check(x+1));
		}
		else if(opt==5)
		{
			printf("%d\n",tree[next(x,0)].val);
		}
		else if(opt==6)
		{
			printf("%d\n",tree[next(x,1)].val);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值