平衡树学习笔记(一)二叉搜索树

✧<1>定义:

(1)空树为一棵二叉搜索树
(2)二叉搜索树左子树中所有点的权值均小于其根节点
(3)二叉搜索树右子树中所有点的权值均大于其根节点
(4)二叉搜索树的左右子树均为二叉搜索树

✧<2>变量的前置定义:

在接下来的代码块中,我们约定n为结点个数h为高度,val[x]为结点处存的数值,cnt[x]为结点x存的值所出现的次数,lc[x]和rc[x]分别为结点x的左子结点和右子结点。
sum为二叉搜索树中节点的个数,siz[x]为x所在的子树的大小(包括x自身,siz[x]的大小实则为x所在子树中维护的数的个数,而非节点数)。
另外,根据二叉搜索树的二叉性质,h=logn。

✧<3>基本操作:
✡✡✡(1)二叉搜索树的遍历:

前置知识:
先序遍历:依次遍历根、左、右
中序遍历:依次遍历左、根、右
后序遍历:依次遍历左、右、根
排列组合就对了
二叉搜索树一般使用中序遍历:即左、根、右。

由二叉搜索树的递归定义可知,二叉搜索树中序遍历生成的点权序列是单调不降的
由于每个点都只会被遍历到一次,因此时间复杂度为O(n)。

Code

void print(int o)
{
	if(!o) return;//如果子树为空树,则返回
	print(lc[o]);//遍历左子树
	for(int i=1;i<=cnt[o];++i) printf("%d\n",val[o]);
	//输出根节点信息
	print(rc[o]);//遍历右子树
}
✡✡✡(2)查询最大值&最小值:

根据二叉搜索树的性质,最小值为二叉搜索树左链的顶点,最大值为二叉搜索树右链的顶点。
因此查找最小值的方式为不断查询左儿子,查询最大值的方式为不断查询右儿子。

时间复杂度为O(logn)=O(h)
返回值为最大值和最小值所在的顶点。

Code

int Findmin(int o)
{
	if(!lc[o]) return o;
	return Findmin(lc[o]);
}
int Findmax(int o)
{
	if(!rc[o]) return o;
	return Findmax(rc[o]);
}
✡✡✡(3)向二叉搜索树中插入一个值v:

分类讨论:
(1)若o所在的树是空树,那么直接新建一个节点
(2)若o所在的树不为空树,那么分三种情况讨论:
✧✧✧✧✧{1}v的值等于val[o],则cnt[o]++;
✧✧✧✧✧{2}v的值大于val[o],则将v插入o的右子树中
✧✧✧✧✧{3}v的值小于val[o],则将v插入o的左子树中

时间复杂度为O(h)=O(logn)

Code
void insert(int o,int v)
{
	if(!o)
	{
		o=++sum;
		siz[o]=cnt[o]=1;
		val[o]=v;
		lc[o]=rc[o]=0;
	}
	siz[o]++;
	if(v==val[o])
	{
		cnt[o]++;
		return;
	}
	if(v<val[o]) insert(lc[o],v);
	if(v>val[o]) insert(rc[o],v);
	return;
}
✡✡✡(4)在二叉搜索树中删除一个值:

还是要分类讨论,这一操作的重点在于要维护二叉搜索树的性质。
先找到权值为v的节点o。
(1)若该节点cnt>1,直接cnt–即可
(2)若该节点cnt=1,则直接删除该节点
✧✧✧✧✧{1}若该节点为叶子节点,则直接删除该节点即可
✧✧✧✧✧{2}若该节点为链节点,即只有一个儿子的节点,则直接用儿子代替该节点
✧✧✧✧✧{3}若该节点的有两个非空子节点,则用左子树中的最大值或者右子树中的最小值代替它
时间复杂度为O(h)=O(logn)

Code
int deletemin(int &o)
{
	if(!lc[o])
	{
		int u=o;
		o=rc[o];
		return u;
	}
	else
	{
		int u=deletemin(o);
		siz[o]-=cnt[u];
		return u;
	}
}
void del(int &o,int v)
{
	siz[o]--;
	if(v==val[o])
	{
		if(cnt[o]>1)
		{
			cnt[o]--;
			return;
		}
		if(lc[o] && rc[o]) o=deletemin(rc[o]);
		else o=lc[o]+rc[o];
		return;
	}
	if(v<val[o]) del(lc[o],v);
	if(v>val[o]) del(rc[o],v);
}
✡✡✡(5)在二叉搜索树中查询一个元素的排名:

注意:这里指的排名是从小到大的排名
只要稍加考虑就可以知道解法,还是分类讨论。
(1)若v==val[o],则v在o所在的树中的排名为siz[lc[o]]+1
(2)若v>val[o],则排名为cnt[o]+siz[lc[o]]+v在o的右子树中的排名
(3)若v<val[o],则排名为v在o的左子树中的排名

时间复杂度O(h)=O(logn)

Code
int queryrnk(int o,int v)
{
	if(v==val[o]) return siz[lc[o]]+1;
	if(v<val[o]) return queryrnk(lc[o],v);
	if(v>val[o]) return queryrnk(rc[o],v)+siz[lc[o]]+cnt[o];
	
}
✡✡✡(5)在二叉搜索树中查询排名为k的元素:

在一棵二叉搜索树的子树中,根节点的排名由其左子树的大小决定。
依旧是分类讨论。
(1)若左子树大小siz[lc[o]]>=k,则排名为k的元素在左子树中
(2)若左子树大小siz[lc[o]]<=k-1且siz[lc[o]]>=k-cnt[o],则该元素为根节点o
(3)若子树大小siz[lc[o]]<k-cnt[o],则该元素在右子树中

Code
int querykth(int o,int k)
{
	if(siz[lc[o]]>=k) return querykth(lc[o],k);
	if(siz[lc[o]]<=k-1 && siz[lc[o]]>=k-cnt[o]) return val[o];
	if(siz[lc[o]]<k-cnt[o]) return querykth(rc[o],k-cnt[o]-siz[lc[o]]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值