【详解】二叉查找树BST与平衡树Treap

二叉查找树BST与平衡树Treap

一、二叉查找树BST

二叉查找树(简称BST),是满足以下条件的二叉树:

  1. 树上每一个节点都有一个权值;
  2. 对于树上任意一个节点u,若左子树不为空,则左子树上所有节点权值均小于u的权值;
  3. 对于树上任意一个节点u,若右子树不为空,则右子树上所有节点权值均大于u的权值;
    如下图:
    在这里插入图片描述

1. 二叉查找树的建立

struct BST		
{
    int l,r;	//左右子节点的编号
    int val;	//节点权值
}s[SIZE];		//数组模拟链表
int tot,root=1;	//tot记录当前节点数目,root为根节点
int INF=0x7fffffff;		//int的最大值
//主函数main()中
a[++tot].val=INF;	//添加一个无穷大的节点,就不需要判断BST是否为空,方便操作

二叉查找树的中序遍历,就是权值从小到大的排列顺序。

2. 二叉查找树的查找

BST中查找是否存在权值为x的节点,执行流程如下:
设变量p等于根节点root
(1) 若a[p].val==x,则查找成功。
(2) 若a[p].val>x
① 若p的左子节点为空,查找失败,说明不存在。
② 若p的左子节点不为空,则继续在p的左子树中递归查找。
(3) 若a[p].val<x
① 若p的右子节点为空,查找失败,说明不存在。
② 若p的右子节点不为空,则继续在p的右子树中递归查找。

int Find(int p,int x)	//当前节点为p,查找值为x的节点
{
	if(p==0)	return 0;	//节点为空,查找失败
	if(a[p].val==x)	return p;	//查找成功,返回x的位置
	return a[p].val<x ? Find(a[p].r,x) : Find(a[p].l,x);
}

3. 二叉查找树找最值

以找最小值为例,从根节点root开始,若左子节点不为空,访问左子节点,直到左子节点为空。

int Find_min(int p)
{
	if(p) while(a[p].l)	p=a[p].l;
	return a[p].val;	//返回最小值
}

找最大值方法类似。

4. 二叉查找树插入

BST中插入一个权值x,其实就是在BST中查找权值为x的节点:当权值x的节点存在时,添加一个域,记录重复个数;当权值x的节点不存在时,即找到了空节点,插入到空节点。

void Insert(int &p,int x)		//插入权值x,当前结点为p
{
	if(p==0)
	{
		a[++tot].val=x;			//添加一个新的节点
		p=tot;					//p是引用,其父节点的l或r值会被同时更新,指向节点tot
		return;
	}
	if(x==a[p].val)	return;		//已经存在,不插入,根据题意,也可以记录重复的个数
	return x<a[p].val ? Insert(a[p].l,x) : Insert(a[p].r,x);
}
引用

这里的&表示引用,引用在程序中相当于一个变量的别名,代表的都是同一个变量,只是名称不相同而已(类似每个人都有一个外号、小名等),对别名进行操作等同于对原变量进行操作,例如:

int a=10;
int &b=a;	//b是a的别名
b+=2;
cout<<a;	//输出答案为12

例如在下图BST中插入权值6
设插入的权值顺序依次为8、12、15、5、7、10、9、3,对应的节点编号依次为1~8.
在这里插入图片描述
程序执行流程:
(1) 首先调用函数Insert(root,6)root的别名为proot为根节点,root=1),接着递归调用函数Insert(a[root].l,6)
(2)a[root].l的别名为pa[root].l等于4,即执行函数Insert(4,6),接着递归调用函数Insert(a[4].r,6)
(3)a[4].r的别名为pa[4].r等于5,即执行函数Insert(5,6),接着递归调用函数Insert(a[5].l,6)
(4)a[5].l的别名为pp为空,添加一个新的节点,执行p=tot,相当于a[5].l=tot
这样,添加一个节点的同时,可以同时更新其父节点的lr

5. 二叉查找树的删除

BST中删除权值为x的节点,需要先查找是否存在。
若查找到权值为x的节点编号为p,需要分三种情况:
(1)p为叶子节点,则直接删除。
(2)p只有一个子节点,则让子节点代替p的位置,(1)的情况也可以按照此方式进行处理,叶节点的子节点就是空节点,空节点代替等同于删除。
(3)p既有左子树也有右子树,则在BST中寻找节点p的后继节点nxt来代替p的位置,后继节点就是大于x且权值最小的节点,后继节点nxt一定不存在左子树,所以可以直接删除后继节点nxt,并令后继节点nxt的右子树代替nxt的位置。
后继节点nxt一定不存在左子树,如果后继节点存在左子树,那么左子树的节点权值大于x,且小于nxt的权值,与nxt是后继节点矛盾。
后继节点的寻找方法:
找到权值为x的节点p时,令nxt=a[p].r,然后一直往节点nxt的左子节点递归往下找,最后一个非空的节点即为后继节点。

void Remove(int &p,int x)		//从子树p中删除权值为x的节点,p是引用,是父结点的l或r的别名
{
	if(p==0)	return;
	if(x==a[p].val)	//查找到x
	{
		if(a[p].l==0)	p=a[p].r;	//无左子树,右子节点代替
		else if(a[p].r==0)	p=a[p].l;	//无右子树,左子节点代替
		else							//既有左子树,又有右子树
		{
			//后继节点往p的右子节点的左子节点一直往下找
			int nxt=a[p].r;		
			while(a[nxt].l)	nxt=a[nxt].l;
			Remove(a[p].r,a[nxt].val);		//删除后继节点nxt
			a[nxt].l=a[p].l;				//nxt代替p的位置
			a[nxt].r=a[p].r;
			p=nxt;							//p是引用,是父结点的l或r的别名,指向添加的节点nxt
		}
		return;
	}
	if(x<a[p].val)	Remove(a[p].l,x);
	else	Remove(a[p].r,x);
}

在随机数据中,BST的每次操作时间复杂度为O(logN)。然而,如果按照权值顺序插入,则BST的形态是一条链。
依次插入权值:3、5、6、8、12、15BST形态如下:
在这里插入图片描述
时间复杂度会降低到O(N),为了使得BST的形态“平衡”,每次操作时间复杂度尽可能接近O(logN),从而产生了平衡树

样例

二叉查找树
【题目描述】
N个操作,分别对应插入一个数和删除一个数,如果插入的数已存在,输出has been,否则将这个数插入,并输出insert success,对于删除一个数,如果该数不存在,输出not exist,如果存在,删除它,并输出delete success
【输入】
第一行n(n<=100000),表示有n个操作数; 之后n行,每行一个字母(I表示插入,D表示删除),一个数字(数字为正整数,整型范围内)。
【输出】
n行,每行按描述的方式输出。
【样例输入】

10
D 1
I 3
I 5
I 3
I 4
D 4
D 5
D 3
I 2
D 1

【样例输出】

not exist
insert success
insert success
has been
insert success
delete success
delete success
delete success
insert success
not exist

【参考程序】

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct BST
{
	int l,r;
	int val;
}a[N];
int n,tot,root=1,INF=0x7fffffff;		//INF为int最大值 
int flag=0;								//标记是否删除 
void Insert(int &p,int x)				//插入权值x,当前结点为p
{
	if(p==0)
	{
		a[++tot].val=x;					//添加一个新的节点
		p=tot;							//p是引用,其父节点的l或r值会被同时更新,指向节点tot
		printf("insert success\n");
		return;
	}
	if(x==a[p].val)	{printf("has been\n");return;}		//已经存在,不插入
	return x<a[p].val ? Insert(a[p].l,x) : Insert(a[p].r,x);
}
void Remove(int &p,int x)				//从子树p中删除权值为x的节点,p是引用,是父结点的l或r的别名
{
	if(p==0)	{printf("not exist\n");return;} 	//不存在 
	if(x==a[p].val)						//查找到x
	{
		if(a[p].l==0)		p=a[p].r;	//无左子树,右子节点代替
		else if(a[p].r==0)	p=a[p].l;	//无右子树,左子节点代替
		else							//既有左子树,又有右子树
		{
			//后继节点往p的右子节点的左子节点一直往下找
			int nxt=a[p].r;		
			while(a[nxt].l)	nxt=a[nxt].l;
			Remove(a[p].r,a[nxt].val);		//删除后继节点nxt
			a[nxt].l=a[p].l;				//nxt代替p的位置
			a[nxt].r=a[p].r;
			p=nxt;							//p是引用,是父结点的l或r的别名,指向添加的节点nxt
		}
		flag=1;								//如果删除后继节点,会有两次删除,记录一次 
		return;
	}
	if(x<a[p].val)	Remove(a[p].l,x);
	else	Remove(a[p].r,x);
}
int main()
{
	a[++tot].val=INF;	//添加一个无穷大的节点,就不需要判断BST是否为空,方便操作 
	scanf("%d",&n);
	char ch[5];
	int x;
	for(int i=1;i<=n;i++)
	{
		flag=0;						 
		scanf("%s%d",ch,&x);	
		if(ch[0]=='I')	Insert(root,x);	
		else	Remove(root,x); 
		if(flag)	printf("delete success\n");
	}	
	return 0;
} 

二、平衡树Treap

Treap是一种简单的平衡树,在普通二叉查找树的基础上,赋予每个节点一个属性:优先级
对于Treap中的节点,除了权值满足二叉查找树的性质外,节点的优先级还要满足大根堆(小根堆)的性质。
简单来说,从权值上看,Treap是一棵二叉查找树,从优先级上看,Treap是一个堆(Heap)。所有Treap就是Tree+Heap,单词Treap就是TreeHeap的合成词。
BST不平衡是因为有序的数据会使查找的路径退化成链,而随机数据使其退化的概率非常小。因此,Treap中每个节点的优先级的值可以通过随机函数生成,这样Treap的结构就会趋于平衡了。

sconst int N=100010;
struct Treap
{
	int l,r;	//左右子节点下标
	int val;	//权值
	int pr;		//优先级
	int cnt;	//重复个数
	int size;	//子树大小
}a[N];
int n,tot,root=1,INF=0x3fffffff;
void Update(int p)		//更新子树大小
{
	a[p].size=a[a[p].l].size+a[a[p].r].size+a[p].cnt;	
}

1. Treap旋转

为了使Treap在满足BST的性质的同时,也同时满足大根堆的性质,需要对Treap的结构进行调整,而调整的方法为旋转,旋转分为左旋右旋,如下图所示:
在这里插入图片描述
注意:

  • 旋转操作是保证在BST性质的基础上,根据节点优先级调整。
  • 旋转不会改变BST的性质,只会改变节点优先级的位置。
    (1)若x的优先级大于y的优先级,如上左图,需要进行右旋:将x变为y的父节点,x原来的右子树变为y的左子树。
void Zig(int &p)	//	右旋,p的左子节点变为p的父节点
{
	int q=a[p].l;	//p的左子节点为q
	a[p].l=a[q].r;	//q的右子树变为p的左子树
	a[q].r=p;		//q变为p的父节点
	p=q;			//引用,p是其父节点的l或r的别名,指向q
	Update(a[p].r);Update(p);	//改变结构后,更新子树大小 
}

(2)若y的优先级大于x的优先级,如上右图,需要进行左旋:将y变为x的父节点,y原来的左子树变为x的右子树。

void Zag(int &p)	//左旋,p的右子节点变为p的父节点
{
	int q=a[p].r;	//p的右子节点为q
	a[p].r=a[q].l;	//q的左子树变为p的右子树
	a[q].l=p;		//q变为p的父节点
	p=q;			//引用,p是其父节点的l或r的别名,指向q
	Update(a[p].l);Update(p); 	//改变结构后,更新子树大小 
}

2. Treap插入

Treap在插入每个新节点时,在有权值属性的基础上,会给该节点生成一个随机值,作为优先级的属性。然后像堆的插入过程一样,自底向上依次检查,当某个节点不满足大根堆性质时,就执行旋转,使该节点与父节点交换
插入节点的过程,先按照BST性质,即权值大小插入到合适位置;再按照堆的性质,调整优先级,使其满足大根堆性质,步骤如下:
设变量p初始时等于root,插入权值为x的节点:
(1)若x==a[p].val,节点p的数量+1
(2)若x<a[p].val,递归往p的左子树a[p].l插入。
(3)若x>a[p].val,递归往p的右子树a[p].r插入。
(4)若p为空节点,插入到当前节点,生成一个随机值作为当前节点的优先级。
此时,是满足BST性质的,但是不一定满足大根堆的性质。因此在回溯的时候,若不满足大根堆性质,进行旋转。
(5)若p的左子节点优先级大于p,则进行右旋,交换左子节点与p的位置。
(6)若p的右子节点优先级大于p,则进行左旋,交换右子节点与p的位置。

void Insert(int &p,int x)	//插入 
{	
	if(x==a[p].val)		//存在权值为x的节点
	{
		a[p].cnt++;		//数量+1
		Update(p);
		return;
	}
	if(p==0)				//添加节点
	{
		a[++tot].val=x;			
		a[tot].pr=rand();	//rand()随机函数,优先级为随机值
		a[tot].cnt=1;
		a[tot].size=1;
		p=tot;				//引用,p是其父节点l或r的别名,指向tot
		return;
	}
	if(x<a[p].val)
	{
		Insert(a[p].l,x);					//递归插入
		if(a[p].pr<a[a[p].l].pr)	Zig(p);	//回溯,不满足堆的性质,右旋
	}
	if(x>a[p].val)
	{
		Insert(a[p].r,x);					//递归插入
		if(a[p].pr<a[a[p].r].pr)	Zag(p);//回溯,不满足堆的性质,左旋
	}
	Update(p);					//回溯,更新子树大小
}

3. Treap删除

如果删除的节点为叶节点,可以直接删除。对于非叶子节点,因为Treap支持旋转,可以通过旋转将其变为叶子节点。
(1)若删除的节点p是非叶子节点,选择左右子节点优先级大的和节点p交换:
(2)若左子节点优先级大,则右旋,保证优先级大的左子节点在右子节点上方。
(3)若右子节点优先级大,则左旋,保证优先级大的右子节点在左子节点上方。
如下图,删除节点15
在这里插入图片描述
首先查找到权值为15的位置,比较左右子节点优先级,左子节点优先级高,右旋:在这里插入图片描述
继续比较左右子节点优先级,右子节点优先级高,左旋:
在这里插入图片描述
继续比较左右子节点优先级,左子节点优先级高,右旋:
在这里插入图片描述
此时,删除的节点为叶子节点,可以直接删除。

void Remove(int &p,int x)			//删除权值x 
{
	if(p==0)	return;		//不存在
	if(x==a[p].val)
	{
		if(a[p].cnt>1)		//有重复元素,减少个数
		{
			a[p].cnt--;		
			Update(p);		//更新子树大小
			return;
		}
		if(a[p].l||a[p].r)	//非叶子节点
		{
			if(a[a[p].l].pr>a[a[p].r].pr)	Zig(p),Remove(a[p].r,x);	//右旋
			else	Zag(p),Remove(a[p].l,x);							//左旋
			a[p].size=a[a[p].l].size+a[a[p].r].size+a[p].cnt;	//回溯更新子树大小
		}
		else 				//叶子节点
			p=0;			//引用,p是父节点l或r的别名,指向0,即删除
		return;
	}
	if(x<a[p].val)	Remove(a[p].l,x);
	if(x>a[p].val)	Remove(a[p].r,x);
	Update(p);	//回溯更新子树大小
}

4. 求数值x的前驱与后继

前驱定义为小于x的最大数,后继定义为大于x的最小数。
求解前驱:
ans为当前最优解,令变量p等于根节点root,从p开始访问:
(1)若当前节点a[p].val<x,且a[p].val>ans,则更新最优解,继续寻找:若a[p].val<x,往右子节点a[p].r找,否则往左子节点a[p].l找。
(2)若a[p].val==x,则先找到p的左子节点,再从左子节点的右子节点一直往下找。
(3)若p为空,则停止寻找。

int Getpre(int x)						//前驱 
{
	int ans=-INF;
	int p=root;
	while(p)
	{
		if(x==a[p].val)					//找到相等
		{
			if(a[p].l)
			{
				p=a[p].l;
				while(a[p].r)	p=a[p].r;
				ans=a[p].val;
			}
			break;
		}
		if(a[p].val<x&&a[p].val>ans)	ans=a[p].val;	//更新最优解
		if(a[p].val<x)	p=a[p].r;						//选择合适的方向继续找
		else p=a[p].l;
	}
	return ans;
}

求解后驱方法类似:

int Getnxt(int x)						//后驱 
{
	int ans=INF;
	int p=root;
	while(p)
	{
		if(x==a[p].val)					//找到相等
		{
			if(a[p].r)
			{
				p=a[p].r;
				while(a[p].l)	p=a[p].l;
				ans=a[p].val;
			}
			break;
		}
		if(a[p].val>x&&a[p].val<ans)	ans=a[p].val;	//更新最优解
		if(a[p].val<x)	p=a[p].r;						//选择合适的方向继续找
		else p=a[p].l;
	}
	return ans;
}

5. 求排名为k的元素

求排名,需要维护每个节点为根的子树大小,即程序中的size属性。
一个节点的排名,取决于其左子树的大小。设当前访问的子树根节点p,重复元素个数为a[p].cntp在作为子树根节点,在子树中的排名介于:左子树节点总数+1 ~ 左子树节点总数+p的重复个数,即区间[ a[ a[p].l ].size + 1 , a[ a[p].l] ].size + a[p].cnt ]之间。因为左子树节点权值都小于p
(1)若 a[a[p].l].size +1<=k && k<=a[a[p].l]].size + a[p].cnt,则排名k的节点就是当前节点p
(2)若k <= a[a[p].l].size,则排名k的节点在左子树中。
(3)若k > a[a[p].l]].size + a[p].cnt,则排名k的节点子在右子树中,相当于在右子树中找排名为k-(a[a[p].l]].size + a[p].cnt)的节点。
在插入删除的时候,都需要从下往上更新size,一般采用递归的形式,以便于在回溯的时候更新size

int GetRank(int p,int k)		//在根节点p的子树中,查找排名为k的元素
{
	if(a[a[p].l].size+1<=k && k<=a[a[p].l].size+a[p].cnt)	return a[p].val;
	if(k<=a[a[p].l].size)	return GetRank(a[p].l,k);
	return GetRank(a[p].r,k-(a[a[p].l].size + a[p].cnt));
}

6. 求元素x的排名

求元素x的排名,类似于查找元素x的位置,在查找的过程中,统计比x小的元素个数。
设当前访问节点为p
(1)若x==a[p].val,则比x小的元素有a[a[p].l].size个。
(2)若x<a[p].val,则在左子树继续找。
(3)若x>a[p].val,则在右子树继续找,且比x小的元素有a[a[p].l].size+a[p].cnt个。

int Getx_Rank(int p,int x)		//当前访问节点p,查找x的排名
{
	if(p==0)	return 0;
	if(x==a[p].val)	return a[a[p].l].size+1;		//排名+1
	if(x<a[p].val)	return Getx_Rank(a[p].l,x);
	return Getx_Rank(a[p].r,x)+a[a[p].l].size+a[p].cnt;
}

Treap通过旋转,在维持权值满足BST性质之外,还能使节点的优先级满足大根堆的性质。
Treap的树的深度期望是O(logN),所以各个操作的期望时间复杂度为O(logN)

样例

普通平衡树
【题目描述】
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入x
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. x的前驱(前驱定义为小于x,且最大的数)
  6. x的后继(后继定义为大于x,且最小的数)
    ps:排名指的是从小到大排,且若有两个数相同则两个数均占名额。执行3操作时,输出排名最靠前的x数的排名)
    【输入】
    第一行为n,表示操作的个数,下面n行每行有两个数optxopt表示操作的序号(1<=opt<=6)
    【输出】
    对于操作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

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct Treap
{
	int l,r;	//左右子节点下标
	int val;	//权值
	int pr;		//优先级
	int cnt;	//重复个数
	int size;	//子树大小
}a[N];
int n,tot,root=1,INF=0x3fffffff;
void Update(int p)		//更新子树大小
{
	a[p].size=a[a[p].l].size+a[a[p].r].size+a[p].cnt;	
}
void Zig(int &p)	//	右旋,p的左子节点变为p的父节点
{
	int q=a[p].l;	//p的左子节点为q
	a[p].l=a[q].r;	//q的右子树变为p的左子树
	a[q].r=p;		//q变为p的父节点
	p=q;			//引用,p是其父节点的l或r的别名,指向q
	Update(a[p].r);Update(p);	//改变结构后,更新子树大小 
}
void Zag(int &p)	//左旋,p的右子节点变为p的父节点
{
	int q=a[p].r;	//p的右子节点为q
	a[p].r=a[q].l;	//q的左子树变为p的右子树
	a[q].l=p;		//q变为p的父节点
	p=q;			//引用,p是其父节点的l或r的别名,指向q
	Update(a[p].l);Update(p); 	//改变结构后,更新子树大小 
}
void Insert(int &p,int x)	//插入 
{	
	if(x==a[p].val)		//存在权值为x的节点
	{
		a[p].cnt++;		//数量+1
		Update(p);
		return;
	}
	if(p==0)				//添加节点
	{
		a[++tot].val=x;			
		a[tot].pr=rand();	//rand()随机函数,优先级为随机值
		a[tot].cnt=1;
		a[tot].size=1;
		p=tot;				//引用,p是其父节点l或r的别名,指向tot
		return;
	}
	if(x<a[p].val)
	{
		Insert(a[p].l,x);					//递归插入
		if(a[p].pr<a[a[p].l].pr)	Zig(p);	//回溯,不满足堆的性质,右旋
	}
	if(x>a[p].val)
	{
		Insert(a[p].r,x);					//递归插入
		if(a[p].pr<a[a[p].r].pr)	Zag(p);//回溯,不满足堆的性质,左旋
	}
	Update(p);					//回溯,更新子树大小
}
void Remove(int &p,int x)			//删除权值x 
{
	if(p==0)	return;		//不存在
	if(x==a[p].val)
	{
		if(a[p].cnt>1)		//有重复元素,减少个数
		{
			a[p].cnt--;		
			Update(p);		//更新子树大小
			return;
		}
		if(a[p].l||a[p].r)	//非叶子节点
		{
			if(a[a[p].l].pr>a[a[p].r].pr)	Zig(p),Remove(a[p].r,x);	//右旋
			else	Zag(p),Remove(a[p].l,x);							//左旋
			a[p].size=a[a[p].l].size+a[a[p].r].size+a[p].cnt;	//回溯更新子树大小
		}
		else 				//叶子节点
			p=0;			//引用,p是父节点l或r的别名,指向0,即删除
		return;
	}
	if(x<a[p].val)	Remove(a[p].l,x);
	if(x>a[p].val)	Remove(a[p].r,x);
	Update(p);	//回溯更新子树大小
}
int Getpre(int x)						//前驱 
{
	int ans=-INF;
	int p=root;
	while(p)
	{
		if(x==a[p].val)					//找到相等
		{
			if(a[p].l)
			{
				p=a[p].l;
				while(a[p].r)	p=a[p].r;
				ans=a[p].val;
			}
			break;
		}
		if(a[p].val<x&&a[p].val>ans)	ans=a[p].val;	//更新最优解
		if(a[p].val<x)	p=a[p].r;						//选择合适的方向继续找
		else p=a[p].l;
	}
	return ans;
}
int Getnxt(int x)						//后驱 
{
	int ans=INF;
	int p=root;
	while(p)
	{
		if(x==a[p].val)					//找到相等
		{
			if(a[p].r)
			{
				p=a[p].r;
				while(a[p].l)	p=a[p].l;
				ans=a[p].val;
			}
			break;
		}
		if(a[p].val>x&&a[p].val<ans)	ans=a[p].val;	//更新最优解
		if(a[p].val<x)	p=a[p].r;						//选择合适的方向继续找
		else p=a[p].l;
	}
	return ans;
}
int Getx_Rank(int p,int x)		//在根节点p的子树中,查找x的排名
{
	if(p==0)	return 0;
	if(x==a[p].val)	return a[a[p].l].size+1;		//排名+1
	if(x<a[p].val)	return Getx_Rank(a[p].l,x);
	return Getx_Rank(a[p].r,x)+a[a[p].l].size+a[p].cnt;
}
int GetRank(int p,int k)		//在根节点p的子树中,查找排名为k的元素
{
	if(a[a[p].l].size+1<=k && k<=a[a[p].l].size+a[p].cnt)	return a[p].val;
	if(k<=a[a[p].l].size)	return GetRank(a[p].l,k);
	return GetRank(a[p].r,k-(a[a[p].l].size + a[p].cnt));
}
int main()
{	
    a[++tot].val=INF;       //添加一个超级根节点,方便操作 
    a[tot].pr=INF;
    scanf("%d",&n);
    int x,opt;
    while(n--)
    {
        scanf("%d%d",&opt,&x);
        if(opt==1) Insert(root,x);
        else if(opt==2) Remove(root,x);
        else if(opt==3) printf("%d\n",Getx_Rank(root,x));    
        else if(opt==4) printf("%d\n",GetRank(root,x));
        else if(opt==5) printf("%d\n",Getpre(x));
        else if(opt==6) printf("%d\n",Getnxt(x)); 
    }       
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值