【原创】/Restarting/ 二叉平衡树 & AVL树 (附普通平衡树 )

AVL

说在前面

好久没写博客了……
まさか,必须克服掉“必须先写知识点Blog才能写对应知识点Blog”的猫病吗。

我写知识点Blog实在是太慢了……
不然题目的Blog根本出不来啊。

二叉查找树

定义

首先,二叉查找树(Binary Search Tree)是一颗树。

首先,二叉查找树是一颗二叉树。

首先,BST是满足:

任意一个结点的所有的左儿子的权值都小于它,所有右儿子的权值都大于它。

的这样的一棵树。

还有对其进行中序遍历,得到的序列一定是单调递增的。

操作

因为不是重点所以暴力一点。
然后——我把要说的通通丢到namespace里面去了。
讲解啊代码啊吐槽啊什么的都在里面。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

namespace BST
{
	const int MAXN=102030;
	
	struct node
	{
		int lc,rc,val;
		//左右儿子和关键字
	}tree[MAXN];
	int n,root;
	
	
	void insert(int val,int &pos)//val是要插入的值,pos是当前访问的节点
	//我们只需要依照定义找到新插入的值的归宿即可
	{
		if(pos==0)
		//访问到了虚空结点!也就是说,沿着大小关系走到了不能再走了,就在此安息吧!
		{
			tree[++n].val=val,pos=n;
			return ;
		}
		insert(val,val<tree[pos].val?tree[pos].lc:tree[pos].rc);
		//如果相等的话倒是可以再在node结构体里开一个变量记录出现次数,这里默认了所有数据不相等(为了压行)
	}
	//解释一下为什么用取址符:因为有“pos=n”这一步,所以也就顺便把“tree[pos].lc”或者“tree[pos].rc”设为新增的结点n
	//如果不想用取址符,也可以再开一个局部变量记录父亲结点

	
	int find(int val,int pos)
	//在树里找val,找到返回下标,找不到返回-1
	{
		if(pos==0) return -1;
		if(tree[pos].val==val) return pos;
		
		return find(val,val<tree[pos].val?tree[pos].lc:tree[pos].rc);
	}
	
	int pre(int val) 
	//找前驱,默认的是找小于的那个,网上还有叫什么lower的。
	//对了,到底是前驱还是前趋啊?
	{
		int pos=root,ans=-2147483647;
		while(pos)
		{
			if(tree[pos].val<val) ans=max(ans,tree[pos].val);
			pos=val<tree[pos].val?tree[pos].lc:tree[pos].rc;
		}
		return ans;
	}
	//所谓前驱就是小于某个值的最大值,也可以说是这个点的左子树里面最靠右的那个端点。
	
	int nxt(int val)
	//后继后继,也有叫upper的,名字很多。
	{
		int pos=root,ans=2147483647;
		while(pos)
		{
			if(tree[pos].val>val) ans=min(ans,tree[pos].val);
			pos=val<tree[pos].val?tree[pos].lc:tree[pos].rc;
		}
		return ans;
	}
	//可以发现,find,pre,nxt几乎一样,只要愿意的话,都可以用循环、递归两种方案来解决……
	
	
	//删除操作———就连普通的BST的删除也不简单。
	/*
	稍微讲一下。
	如果删除的是叶子结点,那么直接注销,没问题的!
	如果删除的结点只有一个儿子,那么就把它的子树接到父亲结点的下面。
	如果删除的结点子女成群,有两个儿子,就找到它左子树里最右的儿子(就是前驱)来顶替它的位置。
	                           (↑女呢?)
	*/
	void deleted(int val,int &pos)
	{
		if(pos==0) return;
		if(tree[pos].val==val)//确认过眼神,是要删掉的人
		{
			if(tree[pos].lc==0 || tree[pos].rc==0) pos=tree[pos].lc+tree[pos].rc; 
			//此处偷大懒,反正有一个是零,那么和就是另一个值,如果两个都是零,那么和就是零。(我第一次看到要把点求和的时候真的被吓到了!)
			else 
			{
				int now=tree[pos].lc,cut=pos;
				while(tree[now].rc) cut=now,now=tree[now].rc;
				
				tree[pos].val=tree[now].val;
				if(cut==pos) tree[cut].lc=tree[now].lc;  
				else tree[cut].rc=tree[now].lc;
				//now这个结点被提拔了,但是now可能还是下有小,这只能交给上有老来抚养了。
				//为什么要分两类呢?因为这里不是重点(所以我用代码片所以我画不了图)所以我就不画图了,自己想一下吧。
			}   
			return ;
		}
		deleted(val,val<tree[pos].val?tree[pos].lc:tree[pos].rc);
	}
};
//总感觉什么都甩到代码片里看起来很乱呢?

int main()
{
	
}

时间复杂度分析

很容易发现,每个函数都时间复杂度都是 Θ ( 层 数 ) \Theta(层数) Θ()的。
层数最大多少层呢?

如果是随机数据,层数就期望在 Θ ( log ⁡ 2 n ) \Theta(\log_2{n}) Θ(log2n)级别。

可是,
通过超简单的构造(就是给的序列单调就行了),我们可以轻松地让这个二叉查找树以一条的形式出现。
也就是说,层数最大是 Θ ( n ) \Theta(n) Θ(n)级别的。

要优化。
计算机学家们如是想。

AVL算法

AV先生和L先生
1962年的这一天,上面的这两位先生(就是这位AV先生(…?)和L先生),发表了一篇论文。

他们说,他们想到一个办法,可以让一颗二叉查找树保持平衡,稳定在 Θ ( log ⁡ 2 n ) \Theta(\log_2{n}) Θ(log2n)层。
来康康。

平衡树和平衡因子

首先,怎么衡量一颗二叉查找树是否“平衡”。
对于每个节点,记录以之为根的子树的层数,即高度、深度之类的,记为 h h h吧。并记其左右儿子的 h h h值之差,叫做平衡因子,记为 w w w吧,我随便偷个小懒,就让w恒正吧。(用什么符号记录,加不加绝对值都无所谓吧)
(反正上课的时候w是从-2到+2的,但是左子树大还是右子树大不是很重要,所以我就偷懒用绝对值吧)
总之,如果某个点的平衡因子的超过1了,这棵树就不平衡了!

就是这么说的。
比方说:
举例子
(电脑上写字事倍功半的说)

这样子的一颗树叫做二叉平衡树Self-balancing binary search tree,就是会自平衡的二叉查找树,也有喊它平衡二叉树的来着。
因为是AV先生(…?)和L先生提出来的这种树的概念,也称这种树为AVL树。

AVL树的定义还能推出其它性质:

1 ◯   Θ ( 层 数 ) = Θ ( log ⁡ 2 n ) ⇒ 各 种 操 作 的 时 间 复 杂 度 = Θ ( log ⁡ 2 n ) 2 ◯   记 N h 为 一 颗 高 度 为 h 的 A V L 树 的 最 小 结 点 数 , F n 为 第 n 个 斐 波 那 契 数 , H n 为 一 颗 有 n 个 结 点 的 A V L 树 的 最 大 高 度 。 则 有 , N 0 = 0 , N 1 = 1 , N 2 = 2 , N h = N h − 1 + N h − 2 + 1 发 现 N h = F h + 2 − 1 。 ⇒ N h ≈ Φ h + 2 5 − 1 , Φ = 5 − 1 2 ⇒ H n ≈ log ⁡ 2 ( 5 ( n + 2 ) ) − 2 ⇒ H n 是 Θ ( log ⁡ 2 n ) 级 别 的 。 \begin{aligned} &amp;\text{\textcircled 1}~\Theta(层数)=\Theta(\log_2{n}) \Rightarrow 各种操作的时间复杂度=\Theta(\log_2{n})\\ &amp;\text{\textcircled 2}~ 记N_h为一颗高度为h的AVL树的最小结点数,F_n为第n个斐波那契数,H_n为一颗有n个结点的AVL树的最大高度。\\ &amp;则有,N_0=0,N_1=1,N_2=2,N_h=N_{h-1}+N_{h-2}+1 \\ &amp;发现N_h=F_{h+2}-1。\\ &amp;\Rightarrow N_h\approx \cfrac{\Phi^{h+2}}{\sqrt{5}}-1,\Phi=\frac{\sqrt{5}-1}{2} \\ &amp;\Rightarrow H_n\approx \log_2 \left( \sqrt{5}\left( n+2 \right) \right)-2\\ &amp;\Rightarrow H_n是\Theta(\log_2{n})级别的。 \end{aligned} 1 Θ()=Θ(log2n)=Θ(log2n)2 NhhAVLFnn,HnnAVLN0=0,N1=1,N2=2,Nh=Nh1+Nh2+1Nh=Fh+21Nh5 Φh+21,Φ=25 1Hnlog2(5 (n+2))2HnΘ(log2n)
我自已一字一句地敲了一些什么出来……

“自平衡”有很多种方法,下面介绍的就是一种叫做“AVL算法”的自平衡算法。

结点

所以说,我们的结点要在原来的基础上再加一个变量记录高度。
长这样:

struct node
{
	int lc,rc,val,h;
	//左右儿子和关键字和高度,平衡因子到时候算就是了
}tree[MAXN];

zig & zag

插入和删除的时候,如果一个结点失去平衡了,这个时候就需要“旋转”,来进行自平衡

是什么

左旋 z a g zag zag,即逆时针旋转,也有叫“ R 旋 转 R旋转 R”的。如图:
zag

图片从左往右看过去,这三次操作就是一次 z a g zag zag
因为之前紫点是蓝点的右儿子,说明紫>蓝,因此让蓝接到紫的左儿子上,是可行的。

右旋 z i g zig zig,即顺时针旋转,也有叫“ L 旋 转 L旋转 L”的。

zig

就是这样。

可以发现,“如果我是你的儿子,那么我就旋当你爸爸;我是你的儿子,我就旋当你爸爸。”
然后旋就是把左上边的爸爸拉到左下方当左儿子
诸如此类。

我是你的儿子?我当你爸爸? (⊙_⊙)?

有什么用

我们说过,旋转的目的是为了维护平衡。
首先,假设我们对一颗平衡树插入或删除了一个节点导致其不平衡,会出现什么状态?
ABCD
只有可能是这四个。
然后A和D,B和C分别是类似的。

A D 直线型

A
D
聪明的都看出来了,镜像翻转是个好东西。

把“/”和“\”类型的不平衡部分通过一次 z i g   o r   z a g zig~ or ~zag zig or zag,转成了“^”的平衡部分。
这样子,原本不平衡的黄点,现在变得平衡了。

B C 折线形

对于“”和“>”类型的东西,考虑先对下面那条边用 z a g   o r   z i g zag~or~zig zag or zig转换成“/”和“\”,再如上进行 z i g   o r   z a g zig~or~zag zig or zag
就像这样:
在这里插入图片描述

”的情况类似(实际上是不想画了)。

总之,

类型应对方案
/ z i g zig zig
\ z a g zag zag
z a g zag zag z i g zig zig,合称 z a g − z i g zag-zig zagzig
> z i g zig zig z a g zag zag,合称 z i g − z a g zig-zag zigzag

实践中会遇到的问题

首先,并不是每次旋转都是三个孤零零的小朋友在那里坐以待毙,往往都拖家带口。

因为不管 z i g z a g zigzag zigzag还是 z a g z i g zagzig zagzig都是由两个基本函数组成, z a g zag zag z i g zig zig,而它俩说实话照照镜子没差,所以我们以一次 z i g zig zig为例。

比方说:
在这里插入图片描述
三角上标的h代表这颗子树的高度,请不要在意大小。
然后绿点的另外半边就无视吧。

我是黄点。
大逆不孝的我把位于我右上角的父亲拉了下来当成我的右儿子。 那么会出现什么后果?

因为我们刚才旋转的时候没有考虑其他人,我们就假设红点的其它邻接边通通断掉,黄点和蓝三角的边也断掉。

也就是说,
我的右儿子会失去爸爸。
我的爸爸的右儿子会失去爸爸。
我的爷爷会失去某个儿子。
我会失去爸爸。
我的爸爸会失去所有儿子。

(这一家人好惨啊)

也就是上图的蓝三角、紫三角、黄点会失去父亲结点,绿点会失去左儿子(就假设它是左儿子吧),红点将没有儿子。

蓝三角,大于黄点并小于红点。
紫三角,大于红点黄点,小于绿点。
绿点,是在坐的各位最大的。

牵桥引线一下:
乱点鸳鸯谱

就这样牵起红线,不仅大小关系满足了,更是好好平衡了一下。

我的右儿子认我新的右儿子(之前的爸爸)为爸爸,成了我的孙子。 我的兄弟还是他爸爸的儿子,但是他也已经是我的孙子了。
我的前爷爷现在就是我的爸爸了。

总之,给出 z i g 和 z a g 等 等 zig和zag等等 zigzag的代码:

void zig(int &pos)
{
	int down=tree[pos].lc;
	tree[pos].lc=tree[down].rc;
	tree[down].rc=pos;
	tree[pos].h=max(tree[tree[pos].rc].h,tree[tree[pos].lc].h)+1;
	tree[down].h=max(tree[tree[down].rc].h,tree[tree[down].lc].h)+1;
	pos=down;
}
//这里给的pos指的是待旋转边的靠上的那个节点。
//然后,高度记得要随时更新。

void zag(int &pos)
{
	int down=tree[pos].rc;
	tree[pos].rc=tree[down].lc;
	tree[down].lc=pos;
	tree[pos].h=max(tree[tree[pos].rc].h,tree[tree[pos].lc].h)+1;
	tree[down].h=max(tree[tree[down].rc].h,tree[tree[down].lc].h)+1;
	pos=down;
}

void zagzig(int &pos)
{
	zag(tree[pos].lc),zig(pos);
}

void zigzag(int &pos)
{
	zig(tree[pos].rc),zag(pos);
}

第二个问题。
这个白痴博主一直说选三个点旋转选三个点摆成什么造型。可是——到底选哪三个点?!
如果说出现了上图的情况,那么我们插入点的时候岂不是要往上爬h层以后才找得到不平衡的点?!

好像是的。
毕竟本来我们的插入操作就是 Θ ( log ⁡ 2 n ) \Theta(\log_2{n}) Θ(log2n)的,要二分地先找到合适的位置插入。并且插入之后,刚刚我们二分地跑过的那些点的左右儿子的高度差(平衡因子)都会产生变动。
也就是说,先 Θ ( log ⁡ 2 n ) \Theta(\log_2{n}) Θ(log2n)地的找到该插入的地方,然后插入,然后我们 Θ ( log ⁡ 2 n ) \Theta(\log_2{n}) Θ(log2n) 地返回,同时更新高度,如果平衡因子不对头了,就旋转。

常数会大点。

插入



void insert(int val,int &pos)
//val是要插入的值,pos是当前访问的节点
//我们只需要依照定义找到新插入的值的归宿即可
//顺便看看是不是不平衡了
{
	if(pos==0)
	//访问到了虚空结点!也就是说,沿着大小关系走到了不能再走了,就在此安息吧!
	{
		tree[++n].val=val,tree[n].h=1,pos=n;
		return ;
	}
	if(val<tree[pos].val)//新增结点在当前节点的左边
	{
		insert(val,tree[pos].lc);
		if(tree[tree[pos].lc].h==tree[tree[pos].rc].h+2)//不平衡了!!(当然左边的高度高于右边了)
		{
			if(val<tree[tree[pos].lc].val) zig(pos);//“/”型
			else if(val>tree[tree[pos].lc].val) zagzig(pos);//“く”
		}
	}
	if(val>tree[pos].val)//新增结点在当前节点的左边
	{
		insert(val,tree[pos].rc);
		if(tree[tree[pos].rc].h==tree[tree[pos].lc].h+2)//不平衡了!!(当然右边的高度高于左边了)
		{
			if(val>tree[tree[pos].rc].val) zag(pos);//“\”型
			else if(val<tree[tree[pos].rc].val) zigzag(pos);//">"型
		}
	}
	//如果相等的话倒是可以再在node结构体里开一个变量记录出现次数,这里默认了所有数据不相等(为了压行)
	tree[pos].h=max(tree[tree[pos].lc].h,tree[tree[pos].rc].h)+1;//更新h
}
//再解释一下为什么用取址符:因为有“pos=n”这一步,所以也就顺便把“tree[pos].lc”或者“tree[pos].rc”设为新增的结点n
//如果不想用取址符,也可以再开一个局部变量记录父亲结点

删除

插入中,最多会有一次旋转。
然而,删除,并不总是在叶子结点处发生,而且可能会导致整棵树的平衡性坍塌,需要多次旋转来恢复平衡。
因此为了处理多次旋转,我们先把旋转写一个函数出来。

maintain函数
void maintain(int &pos)
{
	if(tree[tree[pos].lc].h==tree[tree[pos].rc].h+2)
	{
		if(tree[tree[tree[pos].lc].lc].h==tree[tree[pos].rc].h+1) zig(pos);
		else if(tree[tree[tree[pos].lc].rc].h==tree[tree[pos].rc].h+1) zag(tree[pos].lc),zig(pos);
	}
	if(tree[tree[pos].lc].h+2==tree[tree[pos].rc].h)
	{
		if(tree[tree[tree[pos].rc].rc].h==tree[tree[pos].lc].h+1) zag(pos);
		else if(tree[tree[tree[pos].rc].lc].h==tree[tree[pos].lc].h+1) zig(tree[pos].rc),zag(pos);
	}
	tree[pos].h=max(tree[tree[pos].lc].h,tree[tree[pos].rc].h)+1;
}

就是把insert里面那一堆删掉insert。

删除函数

首先要注意:如果要删的数不在树上,那么怎么处理?根据题目不同的要求,可能找不到就不删除,也可能删掉前驱或后继,这里一定要注意。

接着,参考我们之前写的普通删除:

如果删除的是叶子结点,那么直接注销,没问题的!
如果删除的结点只有一个儿子,那么就把它的子树接到父亲结点的下面。
如果删除的结点子女成群,有两个儿子(↑女呢?),就找到它左子树里最右的儿子(就是前驱)来顶替它的位置。

思路是基本相同的,详见代码:

int deleted(int &pos,int val)//在pos的子树中删除val,此处是如果没有则将小于val的最大的那个删除
{
	int tmp;//可能要上传val值
	if(val==tree[pos].val || (val<tree[pos].val && tree[pos].lc==0) || (val>tree[pos].val && tree[pos].rc==0))
	{//找到val了或者不是val但是是小于val的最大的那个
		if(tree[pos].lc==0 || tree[pos].rc==0)
		{//如果它没有左儿子或右儿子,就让另外一个儿子来顶替他;如果没有任何儿子,就很开心
			tmp=tree[pos].val,pos=tree[pos].lc+tree[pos].rc;
			return tmp;
		}
		else tree[pos].val=deleted(tree[pos].lc,val); 
		//如果子女都有,那就先往下走,找到小于它的最大值,再来顶替它
	}
	else tmp=deleted(val<tree[pos].val?tree[pos].lc:tree[pos].rc,val);
	//这个节点是安全的,就往下找
	maintain(pos);//随手关灯好习惯
	return tmp;
}

就是这样,最后引用一下:

AVL仍然可以采用惰性删除,而且比普通BST的惰性删除更加科学。
对于要删除的节点,只需要给它进行一个标记。树的高度仍然是log(N),这样并不会降低效率,而删除操作却要快速的多。而且,如果遇到那些删除过后还要恢复的节点,则惰性删除更优,不需要额外占用空间,将原来的节点恢复即可。如果遇到那种允许节点重复的AVL,惰性删除更可取,将删除标记设为整型变量,表示该节点出现的数量即可。

普通平衡树

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)

输入输出格式

输入格式:

第一行为nn,表示操作的个数,下面nn行每行有两个数optopt和xx,optopt表示操作的序号( 1 ≤ o p t ≤ 6 1 \leq opt \leq 6 1opt6)

输出格式:

对于操作3,4,5,6每行输出一个数,表示对应答案

输入输出样例

输入样例:

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出样例:

106465
84185
492737

说明

时空限制:1000ms,128M

1.n的数据范围: n ≤ 100000 n \leq 100000 n100000
2.每个数的数据范围: [ − 10 7 , 10 7 ] [-{10}^7, {10}^7] [107,107]
来源:Tyvj1728 原名:普通平衡树

我的回合

就是码了个模板敷衍了事。

解释一下:首先因为题目会有重复的数字出现所以结构体里新增了一个cnt来记录这个节点上重复了多少个同样的这个数。
所以现在删除就可以偷下懒了,要删除某个数的时候把它的cnt减一就是了,当然这样会浪费时间和空间……(不过可以节省maintain的时间)
然后是上文没有出现过的求排名和求排名上的数。
记为 g e t r a n k 和 r a n k g e t getrank和rankget getrankrankget吧。

对于给定数字来找排名,如果我们对于每个节点加一个值siz代表子树的大小(包括cnt),那么就很轻松了,(假设给定的数字在树上并且采用的是上文的偷懒删除法),我们就像find一样,找到了以后,它的排名就是它左子树的大小+1。

对于给定排名来找数字,思路和上一段差不多,看看代码吧(其实是因为不会描述)。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int MAXN=102030;

inline void Read(int &p)
{
	p=0;
	int f=1;
	char c=getchar();
	while(c<'0' || c>'9') 
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9')
		p=p*10+c-'0',c=getchar();
	p*=f;
}

struct node
{
	int lc,rc,val,h,cnt,siz;
}tree[MAXN];
int Q,opt,n,u,root,ans;

void zig(int &pos)
{
	int down=tree[pos].lc;
	tree[pos].lc=tree[down].rc;
	tree[down].rc=pos;
	tree[pos].h=max(tree[tree[pos].rc].h,tree[tree[pos].lc].h)+1;
	tree[down].h=max(tree[tree[down].rc].h,tree[tree[down].lc].h)+1;
	tree[pos].siz=tree[tree[pos].lc].siz+tree[tree[pos].rc].siz+tree[pos].cnt;
	tree[down].siz=tree[tree[down].lc].siz+tree[tree[down].rc].siz+tree[down].cnt;
	pos=down;
}

void zag(int &pos)
{
	int down=tree[pos].rc;
	tree[pos].rc=tree[down].lc;
	tree[down].lc=pos;
	tree[pos].h=max(tree[tree[pos].rc].h,tree[tree[pos].lc].h)+1;
	tree[down].h=max(tree[tree[down].rc].h,tree[tree[down].lc].h)+1;
	tree[pos].siz=tree[tree[pos].lc].siz+tree[tree[pos].rc].siz+tree[pos].cnt;
	tree[down].siz=tree[tree[down].lc].siz+tree[tree[down].rc].siz+tree[down].cnt;
	pos=down;
}

void zagzig(int &pos)
{
	zag(tree[pos].lc),zig(pos);
}

void zigzag(int &pos)
{
	zig(tree[pos].rc),zag(pos);
}

void maintain(int &pos)
{
	if(tree[tree[pos].lc].h==tree[tree[pos].rc].h+2)
	{
		if(tree[tree[tree[pos].lc].lc].h==tree[tree[pos].rc].h+1) zig(pos);
		else if(tree[tree[tree[pos].lc].rc].h==tree[tree[pos].rc].h+1) zag(tree[pos].lc),zig(pos);
	}
	if(tree[tree[pos].lc].h+2==tree[tree[pos].rc].h)
	{
		if(tree[tree[tree[pos].rc].rc].h==tree[tree[pos].lc].h+1) zag(pos);
		else if(tree[tree[tree[pos].rc].lc].h==tree[tree[pos].lc].h+1) zig(tree[pos].rc),zag(pos);
	}
	tree[pos].h=max(tree[tree[pos].lc].h,tree[tree[pos].rc].h)+1;
	tree[pos].siz=tree[tree[pos].lc].siz+tree[tree[pos].rc].siz+tree[pos].cnt;
}

void insert(int val,int &pos)
{
	if(pos==0)
	{
		tree[++n].val=val,tree[n].h=1,tree[n].siz=1,tree[n].cnt=1,pos=n;
		return ;
	}
	if(val<tree[pos].val) insert(val,tree[pos].lc);
	if(val>tree[pos].val) insert(val,tree[pos].rc);
	if(val==tree[pos].val) tree[pos].cnt++;
	maintain(pos);
}

void deleted(int &pos,int val)
{
	if(val==tree[pos].val) tree[pos].cnt--;
	else deleted(val<tree[pos].val?tree[pos].lc:tree[pos].rc,val);
	tree[pos].siz=tree[tree[pos].lc].siz+tree[tree[pos].rc].siz+tree[pos].cnt;
}

int find(int val,int pos)
{
	if(pos==0) return -2147483647;
	if(tree[pos].val==val) return tree[pos].cnt>0?pos:-2147483647;
	return find(val,val<tree[pos].val?tree[pos].lc:tree[pos].rc);
}

int getrank(int val,int pos)
{
	if(pos==0) return 2147483647;
	if(val==tree[pos].val) return tree[tree[pos].lc].siz+1;
	else  
	{
		if(val<tree[pos].val) return getrank(val,tree[pos].lc);
		else return getrank(val,tree[pos].rc)+tree[tree[pos].lc].siz+tree[pos].cnt;
	}
}

int rankget(int rak,int pos)
{
	if(pos==0) return -2147483647;
	if(rak>=tree[tree[pos].lc].siz+1 && rak<=tree[tree[pos].lc].siz+tree[pos].cnt) return tree[pos].val;
	else
	{
		if(rak<tree[tree[pos].lc].siz+1)  return rankget(rak,tree[pos].lc);
		return rankget(rak-tree[tree[pos].lc].siz-tree[pos].cnt,tree[pos].rc);
	}
}

void pre(int val,int pos)
{
	if(pos==0) return;
	if(tree[pos].val<val)
	{
		if(tree[pos].cnt) ans=max(ans,tree[pos].val);
		else pre(val,tree[pos].lc);
		if(tree[tree[pos].rc].siz) pre(val,tree[pos].rc);
	}
	else pre(val,tree[pos].lc);
}

void nxt(int val,int pos)
{
	if(pos==0) return;
	if(tree[pos].val>val)
	{
		if(tree[pos].cnt) ans=min(ans,tree[pos].val);
		else nxt(val,tree[pos].rc);
		if(tree[tree[pos].lc].siz) nxt(val,tree[pos].lc);
	}
	else nxt(val,tree[pos].rc);
}

int main()
{
	Read(Q);
	while(Q--)
	{
		Read(opt),Read(u);
		if(opt==1) insert(u,root);
		if(opt==2) deleted(root,u);
		if(opt==3 && find(u,root)!=-2147483647) printf("%d\n",getrank(u,root));
		if(opt==4) printf("%d\n",rankget(u,root));
		if(opt==5) ans=-2147483647,pre(u,root),printf("%d\n",ans);
		if(opt==6) ans=2147483647,nxt(u,root),printf("%d\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值