二叉查找树是一种特殊的二叉树,它可以组织动态数据集合,可以支持数据的快速插入,删除和查找操作。之前我们讨论过哈希表,他的,查找,插如和删除的时间复杂度是O(1),既然哈希表这么高效,那么为什么还需要二叉查找树呢?

二叉查找树的定义和操作

二叉查找树是二叉树中一种常用的一种类型。二叉查找树是为了实现快速查找产生的。不过,它不仅支持快速查找,还支持快速插入和删除。这主要归功于二叉查找树的一个特性,那就是树中任一节点,这个节点的左子树的值总是小于这个节点的值,这个节点右子树的值总是大于这个节点的值,如上图所示。下面看看他的查找,插入和删除操作是怎么实现的。

1.查找操作

需要查找一个节点的话,我们先取他的根节点,如果根节点不为空,恰巧还等于需要查找的值,那么直接返回根节点。如果要查找的值小于根节点,那么根据二叉查找树的性质,需要像左子树进行递归。同理,如果大于根节点,那么就要向右子树进行递归。

下面来看一张图

接下来看看代码

public class BinarySearchTree{public class Node{private int data;private Node left;private Node right;public Node(int data){this.data = data;}   } private Node tree;public Node find(int data){Node p = tree;while(p != null){if(data > p.data) p = p.rightelse if(data < p.data) p = p.left;else return p;}return null;}}

2.插入操作

为了简化二叉查找树的插入操作,我们把新插入的数据放在叶子节点上。我们从根节点开始,依次比较要插入数据和二叉查找树中节点的大小,来找到合适的位置进行插入。

如果要插入的数据比当前节点大,并且当前节点的右子树为空,那么就直接将数据插入到右子节点的位置。如果右子树不为空,那么我们就再遍历右子树,直到找到插入的位置。反之亦然。

代码实现

public void insert(int data){if(tree == null){tree = new Node(data);return;}Node p = tree;while(p != null){if(data > p.data){if(p.right == null){p.right = new Node(data);return;}p = p.right;} else{if(p.left == null){p.left = new Node(data);retrun;}p = p.left;}}
}

3.删除操作

二叉查找树的删除有三种情况

第一种情况:要删除的节点没有子节点,那么我们直接就可以把父节点中指向要删除节点的指针置为null,如下图删除节点55

第二种情况:要删除的节点只有一个子结点,那么我们只需要更新父节点中指向要删除节点的指针,让他指向删除节点的子结点。如图删除节点13

第三种情况:要删除节点有两个子结点,这个时候需要找到这个删除节点的右子树的最小节点,把他替换到要删除节点上。然后删除最小节点,因为最小节点肯定没有左子节点。

public void delete(int data){Node p = tree;//p指向要删除的节点,初始化指向根节点Node pp = null;//pp记录的是p的父节点//先查找元素while(p != null && data != p.data){pp = p;if(data > p.data) p = p.right;else p = p.left;}if(p == null) return;//没有找到//要删除的节点有两个子节点while (p.left != null && p.right != null){//查找右子树Node minP = p.right;Node minPP = p;//minPP表示minP的父节点while(minP.left != null){minPP = minP;minP = minP.left;}p.data = minP.data;//数据交换p = minP;pp = minPP;}//删除节点是叶子节点或者仅有一个子节点Node child;//p的子结点if(p.left != null) child = p.left;else if(p.right != null) child = p.right;else child = null;if(pp == null) tree = child;//删除的是根节点else if(pp.left == p) pp.left = child;else pp.right = child;}

实际上,对于二叉查找树的删除操作,还有一个取巧的办法,就是将要删除的节点标记为“已删除”,但并不真正从树中将这个节点删除掉。带来的缺点是已经删除的节点还需要存储在内存中,比较浪费内存空间,而且查询效率也会变低。优点就是删除操作就会变得非常简单。

关于二叉查找树也叫二叉排序树的说法是因为中序遍历二叉查找树可以从小到大输出数据,并且时间复杂度为O(n),非常高效

支持重复数据的二叉查找树

之前我们默认二叉查找树中不存在数据值相同的节点。针对数据值相同的节点的二叉树,我们有两种存储方式。

第一种:在二叉查找树中,每一个节点存储的不是一个数据,而是一组数据。通过链表和支持动态扩容的数组等数据结构,把直接相同的数据存储在同一个节点上。

第二种:(可以看下图),每一个节点仍然只存储一个数据。当插入数据时,在查找插入位置的过程中,如果碰到一个节点的值要与插入的值相同,我们就将要插入的数据放到这个节点的右子树,也就是说,某个节点的右子树中存储的是大于或等于这个节点的值。

当查找数据时,遇到值相同的节点,我们并不立刻停止查找,而是继续在右子树中查找,直到遇到叶子节点才停止。这样就可以把值等于要查找值得所有节点都找出来。

删除和前面是差不多得,直接看看图

二叉查找树的性能分析

对于同一组数据,我们可以构建各种不同的二叉查找树。下图所示,他们是针对同一组数据构建的二叉查找树。由于结构不同,那么增删改的效率也不同,像第一个树,已经退化成了链表,查找的时间复杂度变为了O(n)。

这是一种极端的情况,二叉查找树的左右子树极度不平衡,退化成链表。一个理想的情况是

一个完全二叉树或者满二叉树,一个完全二叉树的高度小于或等于logN(树的高度等于最大层数减一),他的时间复杂度为O(logn)。

文章开头的问题

哈希表并不能代替二叉树的主要原因

1.哈希表中的数据是无序存储的,如果要输出有序数据序列,需要先进行排序,或者配合有序链表使用。而对于二叉查找树,我们只需要中序遍历,就可以在O(n)的时间复杂度内,输出有限序列。

2.哈希表扩容耗时很多,而且,当遇到哈希冲突时,性能不稳定。在工程中,用平衡二叉排序树的性能非常稳定,时间复杂度O(logn)。

3.笼统地来说,尽管哈希表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的 存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈 希函数的耗时,也不一定就比平衡二叉查找树的效率高。

4,哈希表的构造比二叉查找树要复杂,需要考虑的东西很多。比如哈希函数的设计、冲 突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。

上面说到二叉查找树在频繁的动态更新的时候,在极端的情况下可能会退化为链表,导致时间复杂度降为O(n),所以为了避免时间复杂度退化的问题,我们来看看平衡二叉查找树。

平衡二叉查找树的定义

平衡二叉查找树的定义是这样的:二叉树中任一节点的左右子树的高度相差不能大于1,所以像之前介绍的完全二叉树和满二叉树都是平衡二叉树,非完全二叉树可能是平衡二叉树,我们要着重注意一下“平衡“这两个字的意思,可以简单理解为节点左右子树的分量大约相同,下面看几张图来判别一下。

此图就不是平衡二叉树,因为节点60的左子树不是平衡二叉树。

此图也不是平衡二叉树,因为并不满足一个节点的左右子树的高度相差不能超过1

此图为平衡二叉树。

平衡二叉查找树不仅满足平衡二叉树的特点还满足二叉查找树的特点。像AVL树,是最先提出来的平衡二叉查找树,他严格符合平衡二叉查找树的定义,不过今天要讨论的却是红黑树,红黑树并没有那么严格遵守这个规则,红黑树从根节点到各个叶子节点的最长路径有可能会比最短路径长一倍,具体的内容一会再说。

下面再来说说”平衡“这两个字,提出平衡二叉树是为了解决二叉查找树频繁的插入,删除等动态更新导致性能退化的问题。因此,平衡二叉树中的”平衡“的意思是让整个树变得”矮胖“一些,而不是”高瘦“,左右看起来比较对称和均衡,避免出现左子树很高,右子树很矮的情况。

红黑树的定义

红黑树是最长提及的二叉查找树,是一种相对平衡的二叉查找树,不符合严格意义上平衡二叉查找树的定义。

 对于红黑树的节点,一类被标记为黑色,一类被标记为红色,除此之外,还需要满足四个要求。

1.根节点是黑色的

2.每个叶子系欸但都是黑色的空节点(NIL),也就是说,叶子节点并不存储数据

3.任何上下相邻的节点不能同时为红色,也就是说,红色节点被黑色节点隔开

4.对于每个节点,从该节点到其叶子节点的所以路径,都包含相同数据的黑色节点

红黑树的性能分析

平衡二叉查找树的初衷,是为了解决二叉查找树因为动态更新导致的性能 退化问题。所以,“平衡”的意思可以等价为性能不退化。“近似平衡”就等价为性能不会 退化的太严重
二叉查找树很多操作的性能都跟树的高度成正比。一棵极其平衡的二叉 树(满二叉树或完全二叉树)的高度大约是 logN,所以如果要证明红黑树是近似平衡的,我们只需要分析,红黑树的高度是否比较稳定地趋近 logN 就好了。
红黑树的高度不是很好分析,我带你一步一步来推导。
首先,我们来看,如果我们将红色节点从红黑树中去掉,那单纯包含黑色节点的红黑树的高
度是多少呢?
红色节点删除之后,有些节点就没有父节点了,它们会直接拿这些节点的祖父节点(父节点
的父节点)作为父节点。所以,之前的二叉树就变成了四叉树。前面红黑树的定义里有这么一条:从任意节点到可达的叶子节点的每个路径包含相同数目的 黑色节点。我们从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。
所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度还要小。
之前我们说,完全二叉树的高度近似 logN ,这里的四叉“黑树”的高度要低于完全二叉
树,所以去掉红色节点的“黑树”的高度也不会超过 log2n。
我们现在知道只包含黑色节点的“黑树”的高度,那我们现在把红色节点加回去,高度会变
成多少呢?
从上面我画的红黑树的例子和定义看,在红黑树中,红色节点不能相邻,也就是说,有一个
红色节点就要至少有一个黑色节点,将它跟其他红色节点隔开。红黑树中包含最多黑色节点
的路径不会超过 log2n,所以加入红色节点之后,最长路径不会超过 2logN ,也就是说,
红黑树的高度近似 2logN 
所以,红黑树的高度只比高度平衡的 AVL 树的高度(log​​​​​​​N )仅仅大了一倍,在性能上,  
下降得并不多。这样推导出来的结果不够精确,实际上红黑树的性能更好。

最后,其实红黑树只做到了近似平衡,并没有做到严格意义上的平衡,因此维护平衡性的成本比AVL树要底,但性能损失不大,所以一般用红黑树比较多。

一看就懂的二叉查找树和平衡二叉查找树相关推荐

  1. 数据结构与算法之美笔记——基础篇(中):树,二叉树,二叉查找树,平衡二叉查找树,红黑树,递归树,堆

    树: A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点.B.C.D 这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点.我们把没有父节点的节点叫作根节点,也就是图中的节点 E.我们 ...

  2. 红黑树、平衡二叉查找树

    红黑树.平衡二叉查找树 红黑树.平衡二叉查找树 平衡二叉查找树 红黑树 特点 红黑树效率 红黑树和AVL树的比较 红黑树的等价变换 红黑树的操作 旋转操作 左旋 右旋 插入操作 插入操作的所有情况 满 ...

  3. 为什么红黑树查询快_为什么工程中都喜欢用红黑树,而不是其他平衡二叉查找树呢?...

    前言 二叉查找树是最常用的一种二叉树,它支持快速插入.删除.查找操作,各个操作的时间复杂度跟树的高度成正比,理想情况下,时间复杂度是O(logn). 不过,二叉查找树在频繁的动态更新过程中,可能会出现 ...

  4. 二叉查找树的平衡(DSW)

    二叉查找树的平衡(DSW) 一.有序数组创建二叉查找树 二.DSW算法(Day–Stout–Warren algorithm) 2.1 第一阶段:右旋转形成主链 2.2 第二阶段:左旋转转换为平衡树 ...

  5. 只有程序员看的懂面试圣经

    技术圈 只有程序员看的懂面试圣经|如何拿下编程面试 http://www.epubit.com.cn/article/197 当我最初开始参加编程面试的时候,我所有最心仪的公司都忽视了我.现在回头看那 ...

  6. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】...

    平衡树初阶--AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  7. 只有程序员看的懂的面试圣经|如何拿下编程面试

    只有程序员看的懂的面试圣经|如何拿下编程面试 摘要:当我最初开始参加编程面试的时候,我所有最心仪的公司都忽视了我.现在回头看那个时候,我发现自己当时去参加面试都完全没做任何准备.虽然已经有许多博客文章 ...

  8. c++ 结构体赋值_《零基础看得懂的C语言入门教程》—(十二)结构体是这么回事

    一.学习目标 了解C语言的结构体的使用方法 了解C语言结构体的结构的赋值 了解多种C语言结构体变量的赋值方法和取值方法 目录 <零基础看得懂的C语言入门教程>--(二)简单带你了解流程 & ...

  9. 前端app调起摄像头 只显示在页面_猫也能看得懂的教程之一分钟使用Vue搭建简单Web页面...

    本教程适合人群: 已经了解过过html.js.css,想深入学习前端技术的小伙伴 有前端开发经验.但是没有使用过Vue的小伙伴 有过其他编程经验,对前端开发感兴趣的小伙伴 学习本教程之后你将会: 了解 ...

最新文章

  1. 漫画:什么是冒泡排序?
  2. ajax在项目中怎么使用,我如何添加项目在sql中使用jQuery(ajax)通过web服务
  3. mysql 中遍历查询_mysql中循环查询
  4. FastStone Capture
  5. python制作系统程序与html交互_python+html语音人物交互_flask后台与前端(html)交互的两种方法...
  6. garch模型python步骤_GARCH模型的建模步骤?
  7. uva 10771——Barbarian tribes
  8. 【干货】从数字化洞察新消费趋势看数字化如何赋能企业.pdf(附下载链接)
  9. python力导向图论文_力导向图(关系图) echarts的运用
  10. Linux上修改open files数目
  11. ASP.NET MVC应用中一个诡异错误的处理
  12. 生产环境 JDK6 升级 JDK8
  13. ubuntu安装openpose
  14. 只需简单一步,android自带的示例程序 BluetoothChat 变蓝牙串口助手
  15. 联想电脑如何修复计算机系统,Lenovo电脑如何用已备份的系统进行一键恢复
  16. ODC 3.4.0 现已上线,让数据库开发更简单
  17. UCF Local Programming Contest 2018题解
  18. LOJ #2155. 「POI2011 R1」同谋者 Conspiracy(深入性质分析)
  19. Mutual Supervision for Dense Object Detection(ICCV2021)阅读笔记
  20. 微信小程序实现文字识别-ocr插件

热门文章

  1. C#设计一个Windows应用程序,要求如下。 ①构造一个产品基类。 ②分别定义家电、日用百货、衣服等派生类,要求具有不同的特征和行为。 ③定义一个泛型货架类,约束参数类型为产品类。该泛型的货架类包
  2. QLExpress脚本语言技术讲解(3) -----QL的性能描述
  3. 养生之道——》五红汤、紫薯枸杞银耳汤、山楂红枣红糖水、大枣红糖姜水、红薯汤
  4. 2B2T服务器的现状
  5. 2020Android面试心得:斩获3个大厂offer后,大厂常考知识点 面试技巧出炉
  6. jdb java_java jdb命令详解
  7. 前端页面支持水印动态配置需求实现建议
  8. ssh 关系维护 inverse
  9. 怎么这么慢!:flask 和 node express 性能测试
  10. android手机防盗图片,android手机防盗措施介绍【图文】