二叉搜索树 学习笔记

功能

二叉搜索树可以对一个序列支持以下内容(源自洛谷P3369):
在序列上增加一个数字 \(x\)

  1. 插入一个整数 \(x\)
  2. 删除一个整数 \(x\)(若有多个相同的数,只删除一个)。
  3. 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
  4. 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
  5. \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。

性质

首先,二叉搜索树是一棵二叉树。这不是废话吗
其次,任意一个节点的左儿子一定小于根节点的数值,右儿子一定大于根节点的数值。
换句话说,二叉搜索树的中序遍历有序(递增)
所以在查找的时候只需要左右递归就可以了。
删除时要分类讨论,当然因为麻烦所以这里的删除是错的

代码实现

首先我们要存下这棵树,这里用结构体

struct JTZ{
	int val,siz,cnt,ls,rs;
}a[maxn];
int root,n;

其中 val 表示该节点的大小, siz 代表以该节点为根的子树的数字个数, cnt 代表该节点的数字的个数, ls,rs 代表该节点的左右儿子。
当然也有用 ``ch[0],ch[1]``` 来表示左右儿子的方法,这里暂时不用。

一些细节

  1. 我们发现,我们可以再最初的二叉搜索树中插入两个数字:\(-\operatorname{INF},\operatorname{INF}\)
  2. 我们可以使用 & 这个符号来减小代码的分类讨论次数。
  3. 选用封装来减少代码长度。
#include<cstdio>
#define INF 0x3fffffff
#define maxn 100039
inline int max(int x,int y){ return x>y?x:y; }
inline int min(int x,int y){ return x<y?x:y; }
using namespace std;
//#define debug
typedef int Type;
inline Type read(){
	Type sum=0;
	int flag=0;
	char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar();
	if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){
		sum=(sum<<1)+(sum<<3)+(c^48);
		c=getchar();
	}
	if(flag) return -sum;
	return sum;
}
struct JTZ{
	int val,siz,cnt,ls,rs;
}a[maxn];
int root,n;
int T;
int op,x;
void up(int rt){
	a[rt].siz=a[rt].cnt+a[a[rt].ls].siz+a[a[rt].rs].siz;
	return;
}
int newnode(int x){
	a[++n].val=x;
	a[n].cnt=1;
	return n;
}
void insert(int &rt,int x){
	if(!rt) rt=newnode(x);
	else if(a[rt].val==x) a[rt].cnt++;
	else if(a[rt].val>x) insert(a[rt].ls,x);
	else insert(a[rt].rs,x);
	up(rt);
	return;
}
void del(int &rt,int x){
	if(a[rt].val==x){
		a[rt].cnt--;
	}
	else if(a[rt].val<x) del(a[rt].rs,x);
	else del(a[rt].ls,x);
	up(rt);
}
int findrk(int rt,int x){
	if(!rt) return 0;
	if(a[rt].val==x) return a[a[rt].ls].siz+1;
	else if(a[rt].val>x) return findrk(a[rt].ls,x);
	else return findrk(a[rt].rs,x)+a[rt].cnt+a[a[rt].ls].siz;
}
int findval(int rt,int x){
	if(a[a[rt].ls].siz<x&&x<=a[a[rt].ls].siz+a[rt].cnt) return a[rt].val;
	else if(x<=a[a[rt].ls].siz) return findval(a[rt].ls,x);
    else return findval(a[rt].rs,x-a[a[rt].ls].siz-a[rt].cnt);
}
int findpre(int rt,int x){
	if(!rt) return -INF;
	if(x>a[rt].val)
	    return max(a[rt].val,findpre(a[rt].rs,x));
	return findpre(a[rt].ls,x);
}
int findnex(int rt,int x){
	if(!rt) return INF;
	if(x<a[rt].val)
	    return min(a[rt].val,findnex(a[rt].ls,x));
	return findnex(a[rt].rs,x);	
}
int dfs(int rt){
	if(!rt) return 0;
	return max(dfs(a[rt].ls),dfs(a[rt].rs))+1;
}
int main(){
	//freopen("1.in","r",stdin);
    T=read();
    newnode(-INF); newnode(INF);
    a[1].rs=2; up(1); up(2); root=1;
    int i=0;
	while(T--){
    	i++; 
    	op=read(); x=read();
    	if(op==1) insert(root,x);
    	if(op==2) del(root,x);
    	if(op==3) printf("%d\n",findrk(root,x)-1);
    	if(op==4) printf("%d\n",findval(root,x+1));
    	if(op==5) printf("%d\n",findpre(root,x));
    	if(op==6) printf("%d\n",findnex(root,x));
	}
	//printf("%d",dfs(root));
	return 0;
}

缺点

二叉搜索树每次操作的期望复杂度是 \(O\left(\log_2N\right)\)
我们发现如果以此插入的是一个有序数列,例如:
\(1,2,3,4,5,6\)
那么我们会发现这棵树会变成这样:

那么就会退化成了一条链,复杂度就变成了 \(O\left(N\right)\) ,所以还是希望大家在比赛时写一个平衡树,不然......

posted @ 2021-02-25 16:18  jiangtaizhe001  阅读(85)  评论(0编辑  收藏  举报