文章目录

  • 二叉搜索树的定义
    • 二叉搜索树的结构特点
  • 二叉搜索树查询
    • 查找
    • 最大关键字元素和最小关键字元素
    • 后继和前驱
  • 二叉搜索树插入和删除
    • 插入
    • 删除

参考《算法导论(第三版)》第 12 章。

搜索树数据结构支持许多动态及和操作,包括 SEARCH、MINIMUM、MAXIMUM、PREDECESSOR、SUCCESSOR、INSERTDELETE 等。因此,我们使用一棵搜索树既可以作为一个字典,又可以作为一个优先队列。

二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势,所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。

二叉搜索树的定义

二叉搜索树(Binary Search Tree),也叫二叉查找树和二叉排序树(Binary Sort Tree)。

定义一

二叉搜索树或者是空树,或者是满足下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的关键字的值都小于根结点关键字的值;
  • 若它的右子树不空,则右子树上所有结点的关键字的值都大于根结点关键字的值;
  • 它的左、右子树本身又是一个二叉搜索树;
  • 没有键值相等的节点。

定义二

二叉搜索树或者是空树,或者是满足下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的关键字的值都小于根结点关键字的值;
  • 若它的右子树不空,则右子树上所有结点的关键字的值都大于或等于根结点关键字的值;
  • 它的左、右子树本身又是一个二叉搜索树;

定义三

二叉搜索树或者是空树,或者是满足下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的关键字的值都小于或等于根结点关键字的值;
  • 若它的右子树不空,则右子树上所有结点的关键字的值都大于根结点关键字的值;
  • 它的左、右子树本身又是一个二叉搜索树;

以上的三种定义在不同的数据结构教材中均有不同的定义方式,但是都是正确的,在开发时需要根据不同的需求进行选择。

下面使用定义一来对二叉搜索树进行介绍。

二叉搜索树的结构特点

(1)按中序遍历二叉搜索树所得的中序序列是一个递增的有序序列,因此,二叉查找树可以把无序序列变为有序序列。

如果 xxx 是一棵有 nnn 个结点子树的根,那么对其进行中序遍历得到递增的有序序列的时间复杂度为 O(n)O(n)O(n)。也就是说可以在 O(n)O(n)O(n) 的时间内按序输出树中的所有关键字。

Q: 二叉搜索树与最小堆有什么区别?能使用最小堆在 O(n)O(n)O(n) 时间内按序输出一棵有 nnn 个结点树的关键字吗?

二叉搜索树:根的左子树的所有节点的值都小于根,右子树的所有节点的值都大于根;最小堆:根的左右子树均大于根。

不能,最小堆所有根的左右子树根节点的值是无序的,所以不能按照树的前中后序遍历在 O(n)O(n)O(n) 时间内来有序地输出;而二叉查找树,按照中序遍历正好是 左 < 根 < 右,是有序的。

(2)同一个数据集合,可按关键字表示成不同的二叉搜索树,即同一数据集合的二叉搜索树不唯一,但中序序列相同。

如关键字集合 1,4,5,10,16,17,21,可以构建出多种二叉搜索树,其中两种如下所示:

二叉搜索树查询

我们经常需要查找一个存储在二叉搜索树中的关键字。除了 SEARCH 操作之外,二叉搜索树还能支持诸如 MINIMUM, MAXIMUM, SUCCESSOR, PREDECESSOR 查询操作。

二叉搜索树的高度为 hhh,这里介绍如何在 O(h)O(h)O(h) 的时间内执行完每个操作。这里按照《算法导论》中的介绍,每个树结点包含属性 left,right,pleft,right,pleft,right,p,分别指向结点的左孩子、右孩子和双亲。

查找

在一棵二叉搜索树中查找一个具有给定关键字的节点。这个过程从树根开始查找,并沿着这棵树中的一条简单路径向下进行,对于遇到的每个结点 xxx,比较关键字 kkk 与 x.keyx.keyx.key:

  • 如果两个关键字相等,查找就终止;
  • 如果 kkk 小于 x.keyx.keyx.key,查找在 xxx 的左子树中继续;
  • 如果 kkk 大于 x.keyx.keyx.key,查找在 xxx 的右子树中继续。

从树根开始递归期间遇到的结点就形成了一条向下的简单路径,所以该算法的时间复杂度为 O(h)O(h)O(h),其中 hhh 是这棵树的树高。

// 递归
TREE_SEARCH(x, k)if x == NULL or k == x.keyreturn xif k < x.keyreturn TREE_SEARCH(x.left, k)else return TREE_SEARCH(x.right, k)// 迭代
ITERATIVE_TREE_SEARCH(x, k)while x != NULL and k != x.keyif k < x.keyx = x.leftelse x = x.rightreturn x

最大关键字元素和最小关键字元素

通过从树根开始沿着 leftleftleft 孩子指针直到遇到一个 NULLNULLNULL,我们总能在一棵二叉搜索树中找到一个最小元素

// 迭代
TREE_MINIMUM(x)while x.left != NULLx = x.leftreturn x// 递归
RECURSIVE_TREE_MINIMUM(x)if x.left == NULLreturn xreturn RECURSIVE_TREE_MINIMUM(x.left)

二叉搜索树的性质保证了 TREE_MINIMUMTREE\_MINIMUMTREE_MINIMUM 是正确的。如果结点 xxx 没有左子树,那么由于 xxx 右子树的每个关键字都至少大于或等于 x.keyx.keyx.key,则以 xxx 为根的子树中的最小关键字是 x.keyx.keyx.key;如果结点 xxx 有左子树,那么由于其右子树中没有关键字小于 x.keyx.keyx.key,且在左子树中的每个关键字不大于 x.keyx.keyx.key,则以 xxx 为根的子树中的最小关键字一定在以 x.leftx.leftx.left 为根的子树中。

TREEMAXIMUMTREE_MAXIMUMTREEM​AXIMUM 的伪代码是对称的:

// 迭代
TREE_MAXIMUM(x)while x.right != NULLx = x.rightreturn x// 递归
RECURSIVE_TREE_MAXIMUM(x)if x.right == NULLreturn xreturn RECURSIVE_TREE_MINIMUM(x.right)

这两个过程在一棵高度为 hhh 的二叉搜索树上均能在 O(h)O(h)O(h) 的时间内执行完,因为它们所遇到的节点构成了一条从树根向下的简单路径。

后继和前驱

这里就需要用到结点中存储的其父节点的指针。

假定树中的所有关键字都不同,而且树中节点中包括一个指向其父节点的指针,其树中某节点的后继结点的查询过程:

  • 如果结点 xxx 的右子树非空,那么 xxx 的后继恰是 xxx 右子树中的最左节点,也就是以 x.rightx.rightx.right 为根的子树中的最小元素;
  • 如果结点 xxx 的右子树为空,那么简单地从 xxx 沿树向上搜,直到搜到当前根节点是其父节点的左子树的根为止,返回该父节点;
TREE_SUCCESSOR(x)if x.right != NULLreturn TREE_MINIMUM(x.right)y = x.pwhile y != NULL and x = y.rightx = yy = y.preturn y

如上图,关键字为 15 的结点的后继是关键字为 17 的结点,也就是 15 右子树的最左结点;关键字为 13 的节点的后继是关键字为 15 的结点。

在一棵高度为 hhh 的树上,TREE−SUCCESSORTREE-SUCCESSORTREE−SUCCESSOR的运行时间为 O(h)O(h)O(h),因为该过程或者遵从一条简单路径沿树向上或者遵从简单路径沿树向下。

过程 TREE−PREDECESSORTREE-PREDECESSORTREE−PREDECESSOR 与 TREE−SUCCESSORTREE-SUCCESSORTREE−SUCCESSOR 是对称点,运行时间也是 O(h)O(h)O(h):

TREE_PREDECESSOR(x)if x.left != NULLreturn TREE_MAXIMUM(x.left)y = x.pwhile y != NULL and x == y.leftx = yy = y.preturn y

再如上图,关键字为 15 的结点的前驱是关键字为 13 的结点,也就是 15 左子树的最右结点;关键字为 9 的结点的前驱是关键字为 7 的结点。

结论:在一棵高度为 hhh 的二叉搜索树上,动态集合上的查找、最小(大)元素、前驱和后继操作可以在 O(h)O(h)O(h) 时间内完成。

二叉搜索树插入和删除

插入

二叉查找树的插入操作:

  • 若二叉排序树为空树,则新插入的结点为根结点;
  • 否则,新插入的结点必为一个新的叶结点;
  • 新插入的结点一定是查找不成功时,查找路径上最后一个结点的左儿子或右儿子。
TREE_INSERT(T,z)        // 将结点 z 插入到二叉搜索树 T 中, z.left = z.right = NULLy = NULLx = T.rootwhile x != NULLy = xif z.key < x.keyx = x.leftelse x = x.rightz.p = yif y == NULL        // tree T was emptyT.root = zelseif z.key < y.keyy.left = zelseif z.key > y.keyy.right = z

递归实现

TREE_INSERT(T,z)        // 将结点 z 插入到以 x 为根的子树上x = T.rootif x == NULLT.root = zelseif z.key < x.keyTREE_INSERT(x.left, z)elseif z.key > x.keyTREE_INSERT(x.right, z)

与其他搜索树上的原始操作一样,过程 TREE−INSERTTREE-INSERTTREE−INSERT 在一棵高度为 h 的树上的运行时间为 O(h)O(h)O(h),只需要从根开始沿树向下搜索,直到找到新节点要插入的位置(某一个 NULLNULLNULL 的位置)。

删除

删除某结点,并保持二叉排序树特性,分三种情况处理

1)如果删除的是叶结点,则直接删除;

2)如果删除的结点只有一株左子树或右子树,则直接继承:将该子树移到被删结点位置;

3)如果删除的结点有两株子树,则用继承结点(后继)代替被删结点,这相当于删除继承结点——按 1) 或 2) 处理继承结点(因为继承节点肯定没有左子树,不然它就不会是继承节点)。

二叉查找树的删除操作的实现步骤(按《算法导论》中每个节点存储了父节点指针来实现的)

  • 若结点 zzz 是叶子,则直接删除结点 zzz;
  • 若结点 zzz 只有左子树,则只需重接 zzz 的左子树;若结点 zzz 只有右子树,则只需重接 zzz 的右子树;
  • 若结点 zzz 的左右子树均不空,则
    • 查找结点 zzz 的右子树上的最左下结点 yyy(yyy 肯定没有左孩子);

    • 将结点 yyy 数据域替换到被删结点 zzz 的数据域;

    • 如果 yyy 是 zzz 的右孩子,那么用 yyy 替换 zzz 成为 zzz 的双亲的一个孩子,然后用 zzz 的左孩子替换 yyy 的左孩子;

      如果 yyy 不是 zzz 的左孩子,那么用 yyy 的右孩子替换 yyy 并成为 yyy 的双亲的一个孩子,然后再将 zzz 的右孩子转变为 yyy 的右孩子,然后执行和上面一样的操作,用 yyy 替换 zzz 成为 zzz 的双亲的一个孩子,然后用 zzz 的左孩子替换 yyy 的左孩子;

为了在二叉搜索树内移动子树,定义一个子过程 TRANSPLANTTRANSPLANTTRANSPLANT,它是用一棵子树替换一棵子树并成为其双亲的孩子节点。当 TRANSPLANTTRANSPLANTTRANSPLANT 用一棵以 vvv 为根的子树来替换一棵以 uuu 为根的子树时,结点 uuu 的双亲就变为了节点 vvv 的双亲,并且最后 vvv 成为 uuu 的双亲的相应孩子。

TRANSPLANT(T,u,v)if u.p == NULLT.root = velseif u == u.p.leftu.p.left = velseif u == u.p.rightu.p.right = vif v != NULLv.p = u.p

利用现成的 TRANSPLANTTRANSPLANTTRANSPLANT 过程,下面是从二叉搜索树 TTT 中删除节点 zzz 的过程:

TREE_DELETE(T,z)if z.left == NULLTRANSPLANT(T,z,z.left)elseif z.right == NULLTRANSPLANT(T,z,z.right)elsey = TREE_MINIMUM(z.right)if y.p != zTRANSPLANT(T, y, y.right)y.right = z.righty.right.p = yTRANSPLANT(T, z, y)y.left = z.lefty.left.p = y

除了调用 TREE−MINIMUMTREE-MINIMUMTREE−MINIMUM 之外,TREE−DELETETREE-DELETETREE−DELETE 的每一行,包括调用 TRANSPLANTTRANSPLANTTRANSPLANT 都只花费常数时间,因此在一棵高度为 hhh 的二叉搜索树上,TREE−DELETETREE-DELETETREE−DELETE 的运行时间为 O(h)O(h)O(h)。

结论:在一棵高度为 hhh 的二叉搜索树上,动态集合上的查找、最小(大)元素、前驱和后继、插入和删除操作,均可以在 O(h)O(h)O(h) 时间内完成。

我们已经证明了高度为 hhh 的二叉搜索树上的每个基本操作都可以在 O(h)O(h)O(h) 时间内完成,然而随着元素的插入和删除,二叉搜索树的高度是变化的。例如,如果 nnn 个关键字按照严格递增的次序被插入,则这棵树一定是高度为 nnn 的一条链,在这样的树上进行操作的时间性能是比较低的。也就是说,如果搜索树的高度较低时,这些集合操作会执行得较快;然而如果树的高度较高时,这些集合操作可能并不比在链表上执行的快。也就有了各种各样的 “平衡”搜索树

二叉搜索树及其操作详解相关推荐

  1. 二叉搜索树的图文详解

    二叉搜索树的实现 二叉搜索树的结构 二叉搜素树具备以下性质: 左子树不为空,则左子树上所有节点值都小于根结点值 右子树不为空,则右子树上所有节点值都大于根结点值 左右子树依然具备二叉树的以上性质 例如 ...

  2. 数据结构 - 从二叉搜索树说到AVL树(一)之二叉搜索树的操作与详解(Java)

    二叉搜索树(Binary Search Tree),简称BST,顾名思义,一颗可以用于搜索的二叉树.BST在数据结构中占有很重要的地位,一些高级树结构都是其的变种,例如AVL树.红黑树等,因此理解BS ...

  3. 6-12 二叉搜索树的操作集

    6-12 二叉搜索树的操作集(30 分) 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); Bin ...

  4. 6-1 二叉搜索树的操作集 (30 分)

    大一下半期数据结构 二叉搜索树的操作集 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); BinT ...

  5. 04-树7 二叉搜索树的操作集(c语言实现)

    题目 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); BinTree Delete( BinTr ...

  6. 04-树7 二叉搜索树的操作集 (30 分)

    本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); BinTree Delete( BinTree ...

  7. 算法 树7 二叉搜索树的操作集

    全部每周作业和视频思考题答案和解析 见 浙江大学 数据结构 思考题+每周练习答案汇总 题目:本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree ...

  8. 6-12 二叉搜索树的操作集 (30 分)

    本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); BinTree Delete( BinTree ...

  9. LeetCode刷题笔记 二叉树 二叉搜索树的操作

    669 修剪二叉搜索树 ​ 给定一个二叉查找树和两个整数 L 和 R,且 L < R,试修剪此二叉查找树,使得修剪后所有节点的值都在 [L, R] 的范围内. ​ 输入是一个二叉查找树和两个整数 ...

最新文章

  1. Windows上安装MinGW(GCC),各个Package的作用
  2. C语言经典算法 21-30
  3. Python基础教程:用模块化来搭项目
  4. 基于Flask+Nginx+uWSGI实现CentOS服务端模型部署及预加载
  5. Mac下使用macdeployqt打包qt程序:
  6. linux关闭gvim命令,Linux 下 8 种退出 vim 编辑器的方法
  7. 童家旺:如何用分表存储来提高性能
  8. 程序包清单签名验证失败_数字世界的手写签名
  9. 使用abcpdf将html转换成pdf文件
  10. 看完就懂webpack打包原理
  11. slf4j+log4j在Java中实现日志记录
  12. sha2 替换sha1 时间表
  13. 删库跑路mini版!程序员写代码给自己转账21万!判了~
  14. 讲真,WiFi 6到底6在哪儿
  15. java中this关键字的作用
  16. luna sea - I For You,“我想为你拭去降临在你身上的所有痛楚I For You 。。。”
  17. 【笑小枫的SpringBoot系列】【四】SpringBoot返回统一结果包装
  18. 【2020.12】Aspose.words 20.12最新版Crack,word转pdf去水印方法
  19. iPhone自动旋转控制代码-IOS开发
  20. 阿里巴巴开源产品列表

热门文章

  1. Web前端入门(五)HTML常用特殊字符
  2. scrum敏捷项目管理_Scrum –敏捷项目的最好朋友
  3. 长连接Tcp协议分析工具
  4. MATLAB算法实战应用案例精讲-【智能优化算法】多目标蚁狮优化算法(MOALO)(附matlab代码实现)
  5. python结巴分词代码_python结巴分词SEO的应用详解
  6. 期末WEB大作业——做一个可视化大屏
  7. C/C++解析tar文件
  8. 【010】Python全栈日记-类
  9. 实验室管理利器——LIMS软件厂商巡礼
  10. 驰骋“数字+服务“杭州为什么能?跨境数智服贸发展论坛为你破题