二叉搜索树(简介)

定义和性质

二叉查找树或是空树,或是满足如下三个性质的二叉树:

1.若其左子树非空,则左子树上所有节点的值都小于根节点的值 若其右子树非空,则右子树上所有节点的值都大于根节点的值

2.其左右子树都是一棵二叉查找树

3.二叉查找树的特性:左子树<根<右子树,即二叉查找树的中序遍历是一个递增序列

二叉查找树的查询

算法步骤:

  1. 若二叉查找树为空,则查找失败,返回空指针
  2. 若二叉查找树非空,则将待查找关键字key与根节点的关键字T−>data进行比较:
  3. 如果x=T->data,则查找成功,返回查询到的当前节点T
  4. 如果x<T−>data,则递归查找左子树
  5. 如果x>T−>data,则递归查找右子树

时间复杂度:最好情况是O(logn),最坏情况是O(n) 空间复杂度:O(n)

查找最小值和最大值

由二叉搜索树的性质可得,二叉搜索树上的最小值为二叉搜索树左链的顶点,最大值为二叉搜索树右链的顶点。时间复杂度为O(h) 。

inline int findmin(int o){//查找最小值 
	if(!lc[o]) return o;//找到第一个没有左子树的节点,由二叉查找树的性质知,此时该节点值为最小值,返回节点编号 
	return findmin(lc[o]);//一直向左子树找 
}

inline int findmax(int o){//查找最大值 
	if(!rc[o]) return o;//找到第一个没有右子树的节点,由二叉查找树的性质知,此时节点值大于左子树所有节点值,该节点值即为最大值,返回节点编号 
	return findmax(rc[o]);//一直向右子树找 
}

插入一个元素

定义 insert(o,v) 为在以 o 为根节点的二叉搜索树中插入一个值为 v 的新节点。 分类讨论如下:

  • 若 o 为空,直接返回一个值为 v 的新节点。
  • 若 o 的权值等于v ,该节点的附加域该值出现的次数自增 。
  • 若 o 的权值大于v ,在 o 的左子树中插入权值为 v 的节点。
  • 若 o 的权值小于 v,在 o 的右子树中插入权值为 v 的节点。
inline void insert(int &o,int v){//在以o为根节点的二叉搜索树中插入一个值为v的节点 
	if(!o){//如果o没有在树上 
		val[o=++num]=v;//加入一个值为v,编号为++n的节点 
		cnt[o]=siz[o]=1;//初始化节点o的出现次数和子树大小 
		lc[o]=rc[o]=0;//将左右节点初始值赋为0 
		return;
	}
	siz[o]++;//o还是在树上,更新最终得到的o点的所有祖先节点的子树大小 
	if(val[o]>v) insert(lc[o],v);//如果节点o的值>v,说明v在此时的节点的左子树上,向左子树插入v 
	if(val[o]<v) insert(rc[o],v);//如果节点o的值<v,说明v在此时的节点的右子树上,向右子树插入v 
}

删除一个元素

定义 del(o,v) 为在以 o 为根节点的二叉搜索树中删除一个值为 v 的节点。 先在二叉搜索树中找到权值为 v 的节点,分类讨论如下:

  • 若该节点的附加 cnt 大于 1,只需要减少 cnt。
  • 若该节点的附加 cnt 为 1:
  • 若 o 为叶子节点,直接删除该节点即可。
  • 若 o 为链节点,即只有一个儿子的节点,返回这个儿子。
  • 若 o 有两个非空子节点,一般是用它左子树的最大值或右子树的最小值代替它,然后将它删除。

时间复杂度 O(h)。

inline int deletemin(int &o){//查询最小值 
	if(!lc[o]){//如果左子树为空 
		int u=o;
		o=rc[o];//找到最小值,更新节点,原本o点的值更新为最小值的右节点 
		return u;//返回最小值 
	}else{//如果左子树不为空 
		int u=deletemin(lc[o]);//继续查找 
		siz[o]-=cnt[u];//沿途的节点的子树大小-=u的个数 
		return u;
	}
}

inline void del(int &o,int v){//在以o为根节点的二叉查找树中删除一个值为v的节点 
	siz[o]--;//o节点的子树大小减一 
	if(val[o]==v){//如果o的值和v相同(找到了) 
		if(cnt[o]>1){//如果o点不止一个 
			cnt[o]--;//直接把cnt[o]-1 
			return;
		}
		if(lc[o]&&rc[o]) o=deletemin(rc[o]);//如果o节点左右子树均不为空,用左子树的最大值或右子树的最小值来替换o(此处用的是右子树的最小值) 
		else o=lc[o]+rc[o];//如果左子树为空或右子树为空或左右子树均为空(此处代码浓缩):当左子树为空时,lc[o]为0,o点被右节点替换;当右子树为空时,rc[o]为0,o被左节点替换;当左右子树均为空时lc[o]=rc[o]=0,o点变为0; 
		return;
	}
	if(val[o]>v) del(lc[o],v);//如果节点o的值>v,说明v在此时的节点的左子树上,向左子树搜索v 
	if(val[o]<v) del(rc[o],v);//如果节点o的值<v,说明v在此时的节点的右子树上,向右子树搜索v
}

求元素的排名

  • 排名定义为将数组元素排序后第一个相同元素之前的数的个数加一。
  • 查找一个元素的排名,首先从根节点跳到这个元素,若向右跳,答案加上左儿子节点个数加当前节点重复的数个数,最后答案加上终点的左儿子子树大小加一。 时间复杂度 O(h)。
inline int queryrnk(int o,int v){//求一个值为v的元素在以o为根节点的二叉搜索树中的排名 
	if(val[o]==v) return siz[lc[o]]+1;//如果val[o]==v,找到v了,返回该节点左节点的排名+1 
	if(val[o]>v) return queryrnk(lc[o],v);//如果节点o的值>v,说明v在此时的节点的左子树上,向左子树搜索v 
	if(val[o]<v) return queryrnk(rc[o],v)+siz[lc[o]]+cnt[o];//如果节点o的值<v,说明v在此时的节点的右子树上,向右子树搜索v,答案+=该节点左节点的排名+该节点重复的个数 
}

查找排名为k的元素

  • 在一棵子树中,根节点的排名取决于其左子树的大小。
  • 若其左子树的大小大于等于 k,则该元素在左子树中;
  • 若其左子树的大小在区间[k-cnt,k-1] ( cnt为当前结点的值的出现次数)中,则该元素为子树的根节点;
  • 若其左子树的大小小于 k-cnt,则该元素在右子树中。
  • 时间复杂度 O(h)。
inline int querykth(int o,int k){//查找在以o为根节点的二叉搜索树中排名为k的元素的值 
	if(siz[lc[o]]>=k) return querykth(lc[o],k);//如果o的左节点的子树大小>=k,k在o的左子树中 
	if(siz[lc[o]]<k-cnt[o]) return querykth(rc[o],k-siz[lc[o]]-cnt[o]);//如果o的左节点的子树的大小+o节点重复的个数(就是o的左子树的大小+cnt[o]-1)<k,说明该元素的排名>o节点排名,该元素在o右子树上,查询时k-=siz[lc[o]]+cnt[o],相当于在以rc[o]为根节点的二叉搜索数上重新查询 
	return val[o];//排名为k的节点是一颗树的根节点或叶子节点(此时o在它所在的树中的排名为k),返回val[o],即o的值(如果要求排名为k的节点的编号) 
}

二叉搜索树模板

#include<bits/stdc++.h>
using namespace std;
//二叉搜索树:左子树所有值小于根节点,右子树所有值大于根节点,且左子树和右子树均为根节点 
int lc[100005],rc[100005];//lc为左子树 rc为右子树,lc[i]表示i节点的左节点编号 rc[i] 表示i节点的右节点编号 

inline int findmin(int o){//查找最小值 
	if(!lc[o]) return o;//找到第一个没有左子树的节点,由二叉查找树的性质知,此时该节点值为最小值,返回节点编号 
	return findmin(lc[o]);//一直向左子树找 
}

inline int findmax(int o){//查找最大值 
	if(!rc[o]) return o;//找到第一个没有右子树的节点,由二叉查找树的性质知,此时节点值大于左子树所有节点值,该节点值即为最大值,返回节点编号 
	return findmax(rc[o]);//一直向右子树找 
}

int val[100005],cnt[100005],siz[100005];//val[i]表示节点i的值,cnt[i]表示节点i出现的次数,siz[i]表示节点i的子树的大小 
int num=0,o=1;
inline void insert(int &o,int v){//在以o为根节点的二叉搜索树中插入一个值为v的节点 
	if(!o){//如果o没有在树上 
		val[o=++num]=v;//加入一个值为v,编号为++n的节点 
		cnt[o]=siz[o]=1;//初始化节点o的出现次数和子树大小 
		lc[o]=rc[o]=0;//将左右节点初始值赋为0 
		return;
	}
	siz[o]++;//o还是在树上,更新最终得到的o点的所有祖先节点的子树大小 
	if(val[o]>v) insert(lc[o],v);//如果节点o的值>v,说明v在此时的节点的左子树上,向左子树插入v 
	if(val[o]<v) insert(rc[o],v);//如果节点o的值<v,说明v在此时的节点的右子树上,向右子树插入v 
}

inline int deletemin(int &o){//查询最小值 
	if(!lc[o]){//如果左子树为空 
		int u=o;
		o=rc[o];//找到最小值,更新节点,原本o点的值更新为最小值的右节点 
		return u;//返回最小值 
	}else{//如果左子树不为空 
		int u=deletemin(lc[o]);//继续查找 
		siz[o]-=cnt[u];//沿途的节点的子树大小-=u的个数 
		return u;
	}
}

inline void del(int &o,int v){//在以o为根节点的二叉查找树中删除一个值为v的节点 
	siz[o]--;//o节点的子树大小减一 
	if(val[o]==v){//如果o的值和v相同(找到了) 
		if(cnt[o]>1){//如果o点不止一个 
			cnt[o]--;//直接把cnt[o]-1 
			return;
		}
		if(lc[o]&&rc[o]) o=deletemin(rc[o]);//如果o节点左右子树均不为空,用左子树的最大值或右子树的最小值来替换o(此处用的是右子树的最小值) 
		else o=lc[o]+rc[o];//如果左子树为空或右子树为空或左右子树均为空(此处代码浓缩):当左子树为空时,lc[o]为0,o点被右节点替换;当右子树为空时,rc[o]为0,o被左节点替换;当左右子树均为空时lc[o]=rc[o]=0,o点变为0; 
		return;
	}
	if(val[o]>v) del(lc[o],v);//如果节点o的值>v,说明v在此时的节点的左子树上,向左子树搜索v 
	if(val[o]<v) del(rc[o],v);//如果节点o的值<v,说明v在此时的节点的右子树上,向右子树搜索v
}

inline int queryrnk(int o,int v){//求一个值为v的元素在以o为根节点的二叉搜索树中的排名 
	if(val[o]==v) return siz[lc[o]]+1;//如果val[o]==v,找到v了,返回该节点左节点的排名+1 
	if(val[o]>v) return queryrnk(lc[o],v);//如果节点o的值>v,说明v在此时的节点的左子树上,向左子树搜索v 
	if(val[o]<v) return queryrnk(rc[o],v)+siz[lc[o]]+cnt[o];//如果节点o的值<v,说明v在此时的节点的右子树上,向右子树搜索v,答案+=该节点左节点的排名+该节点重复的个数 
}

inline int querykth(int o,int k){//查找在以o为根节点的二叉搜索树中排名为k的元素的值 
	if(siz[lc[o]]>=k) return querykth(lc[o],k);//如果o的左节点的子树大小>=k,k在o的左子树中 
	if(siz[lc[o]]<k-cnt[o]) return querykth(rc[o],k-siz[lc[o]]-cnt[o]);//如果o的左节点的子树的大小+o节点重复的个数(就是o的左子树的大小+cnt[o]-1)<k,说明该元素的排名>o节点排名,该元素在o右子树上,查询时k-=siz[lc[o]]+cnt[o],相当于在以rc[o]为根节点的二叉搜索数上重新查询 
	return val[o];//排名为k的节点是一颗树的根节点或叶子节点(此时o在它所在的树中的排名为k),返回val[o],即o的值(如果要求排名为k的节点的编号) 
} 

int n,m; 
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		insert(o,x);
	}
	for(int i=1;i<=m;i++){
		int q;
		cin>>q;
		if(q==1){
			cout<<findmin(1)<<endl; 
		}
		else if(q==2) cout<<findmax(1)<<endl;
		else if(q==3){
			int v;
			cin>>v;
			del(o,v);
		}else if(q==4){
			int v;
			cin>>v;
			cout<<queryrnk(1,v);
		}else if(q==5){
			int v;
			cin>>v;
			cout<<querykth(1,v);
		}
	}
	return 0;
}

练习推荐:

https://www.luogu.com.cn/problem/P1864

萌新的第一篇文章,不喜勿喷。

给个免费的赞和关注再走吧求求了ヾ(≧▽≦*)o

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值