二叉搜索树

二叉搜索树学习笔记

这篇文章是学习平衡树的铺垫

什么是二叉搜索树

二叉搜索树,又名二叉查找树、二叉排序树,是满足以下性质的一种树形结构

  • 每一个节点的左子树里的所有节点上的权值,都比这个节点的权值
  • 每一个节点的右子树里的所有节点上的权值,都比这个节点的权值

举个例子,就长这样:

其中每个节点都有编号和权值(还有一些其它玩意儿),所以定义结构体存节点

struct Node{
	int l,r;//左右儿子的编号
	int val;//这个节点的权值 
	int cnt;//计数器,有用的 
}tr[N]; 

一个简单的结论

有了刚才那样特殊的性质,我们只要稍微思考一下就能得出以下结论:

  • 一颗二叉排序树的中序遍历的顺序是从小到大排好序的

为什么哩?

所谓中序遍历,就是说按照左根右的顺序遍历一棵树

先遍历左子树,再遍历根,再遍历右子树

加上刚才的二叉搜索树的定义,左子树都小,右子树都大,很容易得出结论

例如:刚才上面那棵树的中序遍历就是 \(1 2 3 5 7 8 9\)

简易代码

void Medium_order(int u)
{
	if(tr[u].l)
		Medium_order(tr[u].l);
	cout<<tr[u].val<<' ';
	if(tr[u].r)
		Medium_order(tr[u].r);
}

And...then?

所以,这个结论到底有用嘛……

当然有用!有了杰个结论,二叉搜索树就能动态维护一个有序的数列,并支持以下操作

支持的操作

  • \(insert\) 插入一个节点
  • \(remove\) 删除一个节点

以上是没有技术含量的基本操作

插入 insert

插入的基本思路

就是从根节点开始看

如果要插入的结点的值比当前考虑的节点的值小,递归到左子树插入

否则如果比当前的值大,递归到右子树插入

到最后如果找到了一个和待插入的节点的值相同的节点,把当前节点的计数器 \(cnt\)++ 就行

如果到最后也没有找到相同的节点,说明这样的结点之前并不存在,新开一个节点即可

图示

# 1 插入一个节点,值为 \(4\)(原先不存在)

# 2 插入一个节点,值为 \(9\)(原先已存在)

代码

先写一个开新节点的函数

int newnode(int x)
{
	idx++;
	tr[idx].val=x;
	return idx;
}

\(insert\)

void insert(int p,int x)
{
	if(x<tr[p].val)
	{
		if(tr[p].l)
			insert(tr[p].l,x);
		else
			tr[p].l=newnode(x);
	}else if(x>tr[p].val){
		if(tr[p].r)
			insert(tr[p].r,x);
		else
			tr[p].r=newnode(x);
	}else tr[p].cnt++;
}

删除 remove

删除叶节点非常好办,直接找到之后 \(×\) 掉就行

但是不是叶子的话就麻烦了,对于我们学习平衡树也没有太大作用

所以这一部分就咕咕掉了(平衡树删除跟它一点关系都没,也挺好办的)


基本操作到此结束,下面来一些高级点的

  • \(get\_max\) 查询最大值
  • \(get\_min\) 查询最小值

最大值 & 最小值

思路非常简单

每一个点的右子树里所有点权值都比这个点大

所以从根节点开始找,一直往右走,不撞南墙不回头

走到的最后一个点就是最大值

最小值同理,一直向左走,走到不能走为止

图示

代码

代码咕咕咕喽 ψ(._. )>

嘿嘿(毕竟是平衡树为平衡树的铺垫,扯得太多了)


二阶操作结速,看看最高级操作

  • \(get\_prev\) 求一个节点的前驱

  • \(get\_next\) 求一个节点的后继

某个节点的前驱和后继就是中序遍历中这个节点前面和后面的节点

换句话说就是

权值小于该节点的点中权值最大的

and

权值大于该节点的点中权值最小的

怎么求呢?

求前驱 get_prev

分类讨论

如果一个点,他有左子树,那么显然他的前驱就在左子树里,而且还是左子树里的最大的那个值

那么我们只要先向左走一步,走到到左儿子

然后按照刚才说的找最大值的思路,在左子树中一直向右走,走到不能走为止就行辣;

那……如果这个点的左子树为空呢?

那就又要分类讨论了

第一种情况:

如果该点是其父节点的右儿子,那么父节点就是它的前驱

证明:

没有左子树又是父亲的右儿子,就说明,除了 父亲节点父亲的左子树里的所有点 之外,没有其他点的权值比他小了

父节点 的权值又比 父节点的左子树里的所有点的权值们 都要大

所以它的父节点就是他的前驱

第二种情况

如果是父节点的左儿子,就一路直着往上跳,跳到第一次拐弯就停

拐弯的意思就是原来都是从左儿子往上跳,直到有一次,是从右儿子向上跳了

也就是这样

图示

刚才的图太简陋了,包含的情况不全,再重新画一颗复杂一点的树

为了方便检验答案的正确性,我们直接把权值置为连续的自然数 \(1~26\)

# 1 首先找 \(21\) 的前驱,对应我们分析的第一种情况

# 2 再找 \(23\) 的前驱,对应第二种

# 3 最后找 \(4\) 的前驱,对应最后一种情况

求后继 get_next

思路

刚好和求前驱反过来

有右儿子的话,就是右子树里的最小值

没有的话,如果是父亲的左儿子,那后继就是父亲,父亲就是后继

如果是右儿子,就一直往上跳,直到拐弯为止

应该很好理解吧……

图示?

和上面求前驱的图一样,就是把箭头反过来……

因为一个点一定是 这个点的前驱 的后继(废话文学)

尾声

二叉搜索树 到这里就讲完了

其实还没有结束

因为二叉搜索树有一个致命的缺点!

他需要改进

这里我卖个关子,等到下一篇平衡树里再说

(白白bye/

posted @ 2022-08-26 14:11  OrangeStar*  阅读(75)  评论(0编辑  收藏  举报