数据结构-二叉搜索树 详解
😊 | Powered By HeartFireY | Binary Search Tree |
一、二叉搜索树 简介
二叉搜索树(BST, Binary Search Tree)是一种基于二叉树的树形数据结构,定义(满足的性质)如下
- 空树是二叉搜索树
- 若二叉搜索树的左子树不为空,则其左子树上所有点的权值均小于其根节点的权值;
- 若二叉搜索树的右子树不为空,则其右子树上所有点的权值均大于其根节点的权值;
- 二叉搜索树的左右子树均为二叉搜索树
不难发现,二叉搜索树是采用递归进行定义的。
我们可以通过例子来具体了解二叉搜索树的结构,如图所示是一颗二叉搜索树:
二叉搜索树的基本操作所花费的时间与树的高度成正比。对于一个有个节点的二叉搜索树:
- 最优时间复杂度为,此时左右平衡
- 最差时间复杂度为,此时二叉搜索树退化为链
二、二叉搜索树-基本操作
我们采用数组表示法来储存一棵搜索树:
为了说明方便,我们定义表示二叉树的高度。
1.插入操作
二叉搜索树的建树逻辑是比较简单的,假设我们需要向以为根节点的二叉搜索树中插入一个值为的新节点。
定义该操作为,则步骤总结如下:
- 判断是否为空树,如果为空树则建立根节点直接存放数据;
- 如果的权值等于,则该节点对应出现的次数自增次;
- 如果的权值大于,则递归向的左子树中插入;
- 如果的权值小于,则递归向的右子树中插入。
根据步骤,我们可以写出插入操作的代码:
根据上述步骤的描述可以知道:插入操作的时间复杂度为,其中为这棵二叉搜索树的高度。
2.删除操作
删除操作涉及的情况较多,需要进行分类讨论:
假设我们已经拥有一颗根节点为的二叉搜索树,现在我们需要删除一个权值为的节点。
定义该操作为delete(root, v)
,则步骤总结如下:
- 查找:首先需要在节点为的二叉搜索树上找到该节点;
- 判断删除方式:
- 不完全删除:如果该节点的值大于,则只需要对该节点的做减运算;
- 完全删除:如果该节点的值为,则需要对该节点进行完全删除:
- 如果为叶子节点,则直接删除该节点即可;
- 如果为链节点(只有一个孩子的节点),则返回该节点的孩子;
- 如果具有两个非空的子节点,则一般用其左子树的最大值或右子树的最小值代替它,然后将它删除。
与插入操作相同,时间复杂度为。
3.遍历操作
由二叉搜索树的递归定义可知:对二叉搜索树进行中序遍历,得到的序列是非下降序列。
定义该操作为travel(int root)
,则代码如下(实际上与普通二叉树的中序遍历区别不大)
时间复杂度
4.查找最大值/最小值
对二叉搜索树查询最大值最小值是非常简单操作:我们根据二叉搜索树的定义可以知道:二叉搜索树的最小值为二叉搜索树左链的端点,与之相反,二叉搜索树的最大值为右链的端点。
定义getmin(int root)
为寻找最小值的操作,getmax(int root)
为寻找最大值的操作,则代码如下:
与插入删除操作相同,时间复杂度为。
5.查询给定元素的排名
排名定义为将数组元素排序后第一个相同元素之前的数的个数加一。
我们在开始时定义并维护,用于储存每个根节点的子树大小。查找一个子树的大小,首先从根节点跳到这个元素:
- 如果向右跳,则答案加上左子树节点个数()、和当前节点重复的数的个数();
- 如果向左跳,则不需要额外附加;
- 最后答案加上终点的左儿子子树大小+1
定义queryrnk(int root, int v)
,则代码如下:
时间复杂度。
6.查询排名为的元素
我们需要熟悉一条结论:在一棵子树中,根节点的排名取决于其左子树的大小。
- 若其左子树的大小,则该元素位于左子树中;
- 若其左子树的大小中,则该元素为子树的根节点;
- 若其左子树的大小,则该元素位于右子树中。