数据结构与算法之美学习笔记:24 | 二叉树基础(下):有了如此高效的散列表,为什么还需要二叉树?

目录

  • 前言
  • 二叉查找树(Binary Search Tree)
  • 二叉查找树的时间复杂度分析
  • 解答开篇
  • 内容小结

前言

在这里插入图片描述
本节课程思维导图:
在这里插入图片描述
二叉查找树最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。我们之前说过,散列表也是支持这些操作的,并且散列表的这些操作比二叉查找树更高效,时间复杂度是 O(1)。既然有了这么高效的散列表,使用二叉树的地方是不是都可以替换成散列表呢?有没有哪些地方是散列表做不了,必须要用二叉树来做的呢?

二叉查找树(Binary Search Tree)

二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。
二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
在这里插入图片描述
二叉查找树支持快速查找、插入、删除操作,现在我们就依次来看下,这三个操作是如何实现的。

  1. 二叉查找树的查找操作
    首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。
    在这里插入图片描述
    代码:
public class BinarySearchTree {private Node tree;public Node find(int data) {Node p = tree;while (p != null) {if (data < p.data) p = p.left;else if (data > p.data) p = p.right;else return p;}return null;}public static class Node {private int data;private Node left;private Node right;public Node(int data) {this.data = data;}}
}
  1. 二叉查找树的插入操作
    二叉查找树的插入过程有点类似查找操作。新插入的数据一般都是在叶子节点上,所以我们只需要从根节点开始,依次比较要插入的数据和节点的大小关系。如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。
    在这里插入图片描述
    代码:
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 { // data < p.dataif (p.left == null) {p.left = new Node(data);return;}p = p.left;}}
}
  1. 二叉查找树的删除操作
    针对要删除节点的子节点个数的不同,我们需要分三种情况来处理。
    第一种情况是,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。比如图中的删除节点 55。
    第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。比如图中的删除节点 13。
    第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。比如图中的删除节点 18。
    在这里插入图片描述
  2. 二叉查找树的其他操作
    除了插入、删除、查找操作之外,二叉查找树中还可以支持快速地查找最大节点和最小节点、前驱节点和后继节点。
    还有一个重要的特性,就是中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度是 O(n),非常高效。因此,二叉查找树也叫作二叉排序树。

二叉查找树的时间复杂度分析

我们来分析一下,二叉查找树的插入、删除、查找操作的时间复杂度。
实际上,二叉查找树的形态各式各样。它们的查找、插入、删除操作的执行效率都是不一样的。下图二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。
在这里插入图片描述
我们现在来分析一个最理想的情况,二叉查找树是一棵完全二叉树(或满二叉树)。这个时候,插入、删除、查找的时间复杂度是多少呢?

不管操作是插入、删除还是查找,时间复杂度其实都跟树的高度成正比,也就是 O(height)。既然这样,现在问题就转变成另外一个了,也就是,如何求一棵包含 n 个节点的完全二叉树的高度?

树的高度就等于最大层数减一,为了方便计算,我们转换成层来表示。从图中可以看出,包含 n 个节点的完全二叉树中,第一层包含 1 个节点,第二层包含 2 个节点,第三层包含 4 个节点,依次类推,下面一层节点个数是上一层的 2 倍,第 K 层包含的节点个数就是 2^(K-1)。不过,对于完全二叉树来说,最后一层的节点个数有点儿不遵守上面的规律了。它包含的节点个数在 1 个到 2^(L-1) 个之间(我们假设最大层数是 L)。如果我们把每一层的节点个数加起来就是总的节点个数 n。也就是说,如果节点的个数是 n,那么 n 满足这样一个关系:

n >= 1+2+4+8+...+2^(L-2)+1
n <= 1+2+4+8+...+2^(L-2)+2^(L-1)

借助等比数列的求和公式,我们可以计算出,L 的范围是[log2(n+1), log2n +1]。完全二叉树的层数小于等于 log2n +1,也就是说,完全二叉树的高度小于等于 log2n。
显然,极度不平衡的二叉查找树,它的查找性能肯定不能满足我们的需求。我们需要构建一种不管怎么删除、插入数据,在任何时候,都能保持任意节点左右子树都比较平衡的二叉查找树,一种特殊的二叉查找树,平衡二叉查找树。平衡二叉查找树的高度接近 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。

解答开篇

散列表的插入、删除、查找操作的时间复杂度可以做到常量级的 O(1),非常高效。而二叉查找树在比较平衡的情况下,插入、删除、查找操作时间复杂度才是 O(logn),相对散列表,好像并没有什么优势,那我们为什么还要用二叉查找树呢?
我认为有下面几个原因:
第一,散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。

第二,散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。

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

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

最后,为了避免过多的散列冲突,散列表装载因子不能太大,特别是基于开放寻址法解决冲突的散列表,不然会浪费一定的存储空间。综合这几点,平衡二叉查找树在某些方面还是优于散列表的,所以,这两者的存在并不冲突。我们在实际的开发过程中,需要结合具体的需求来选择使用哪一个。

内容小结

二叉查找树。它支持快速地查找、插入、删除操作。二叉查找树中,每个节点的值都大于左子树节点的值,小于右子树节点的值。

在二叉查找树中,查找、插入、删除等很多操作的时间复杂度都跟树的高度成正比。两个极端情况的时间复杂度分别是 O(n) 和 O(logn),分别对应二叉树退化成链表的情况和完全二叉树。为了避免时间复杂度的退化,针对二叉查找树,我们又设计了一种更加复杂的树,平衡二叉查找树,时间复杂度可以做到稳定的 O(logn)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/158318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

赛氪荣幸受邀参与中国联合国采购促进会第五次会员代表大会

11 月21 日 &#xff08;星期二&#xff09; 下午14:00&#xff0c;在北京市朝阳区定福庄东街1号中国传媒大学&#xff0c;赛氪荣幸参与中国联合国采购促进会第五次会员代表大会。 2022年以来&#xff0c;联合国采购杯全国大学生英语大赛已经走上了国际舞台&#xff0c;共有来自…

解析大型语言模型的训练、微调和推理的运行时性能

背景 这篇论文是截至目前为数不多的介绍大模型训练配套环境比对的论文&#xff0c;对于想要入门大模型训练同学是个不错的入门资料。比较了不同尺寸模型&#xff08;比较常用的7、13、70b&#xff09;&#xff0c;在不同型号gpu、训练框架、推理框架数据。结合自己实际工作需要…

C/C++数据结构之堆栈(Stack):理解、实现与运用

当我们讨论堆栈时&#xff0c;我们首先需要了解它的概念和基本原理。堆栈是一种后进先出&#xff08;Last In, First Out&#xff0c;LIFO&#xff09;的数据结构&#xff0c;它的操作主要包括压栈&#xff08;Push&#xff09;和弹栈&#xff08;Pop&#xff09;&#xff0c;以…

Git - 版本控制系统

目录 一、概述 配置用户信息 二、Git仓库 创建 本地仓库 git的三个区域 示例 Git文件状态 举例 三、区域使用 暂存区使用 版本库使用 文件忽略 四、分支 步骤 合并与删除 步骤 合并与提交 合并冲突 五、常用指令 六、Git远程仓库 使用步骤 克隆 同步 …

十七、SpringAMQP

目录 一、SpringAMQP的介绍&#xff1a; 二、利用SpringAMQP实现HelloWorld中的基础消息队列功能 1、因为publisher和consumer服务都需要amqp依赖&#xff0c;因此这里把依赖直接放到父工程mq-demo中 2、编写yml文件 3、编写测试类&#xff0c;并进行测试 三、在consumer…

HarmonyOS ArkTS 应用添加弹窗(八)

概述 在我们日常使用应用的时候&#xff0c;可能会进行一些敏感的操作&#xff0c;比如删除联系人&#xff0c;这时候我们给应用添加弹窗来提示用户是否需要执行该操作&#xff0c;如下图所示&#xff1a; 弹窗是一种模态窗口&#xff0c;通常用来展示用户当前需要的或用户必须…

去除表格中的网格线

去除表格中的网格线 在excel中如何得到下图2的效果 点开视图&#xff0c;去除网格线 最后输出为图片

「L2C」型行业从线索到成交,听懂客户之「声」是关键

存量经营时代下&#xff0c;营销变得越来越难。无论是稳流量&#xff0c;或是促活跃&#xff0c;转化率就是难以提升。 相比传统快消行业&#xff0c;线索型&#xff08;L2C&#xff0c;Leads to Cash&#xff09;行业因为客单价高、决策周期长、用户触点分散等特性&#xff0…

CRM系统中的联系人是什么?如何进行联系人管理?

上手CRM系统前掌握专业术语是必要的功课&#xff0c;在第一次使用CRM系统时小编和大家一样&#xff0c;分不清楚线索、联系人、客户、商机之间的关系&#xff0c;今天我们就来着重分享一下CRM中联系人是什么&#xff1f;如何进行联系人管理&#xff1f; CRM系统联系人是指能够…

2023年国自然植物科学相关面上项目信息公布(小麦、大麦、棉花、大豆、玉米)

2024年申报国自然项目基金撰写及技巧http://mp.weixin.qq.com/s?__bizMzA4NTAwMTY1NA&mid2247575761&idx1&sn32dbacd3393f3b76a1e0668e4b8b3c89&chksm9fdd7c08a8aaf51ec31d4790067bb57751a09947eeb7e728b8c008d26b89adba37e0cab32a62&scene21#wechat_redi…

梨花声音课堂,真诚和情感展现家庭生活场景,易使观众产生共鸣

在为家庭剧的配音工作时&#xff0c;配音员要能够传递出剧中角色在日常生活中所经历的情感波动&#xff0c;以及家庭关系中的温情、矛盾和解决问题的过程。家庭剧着重描绘亲情纽带和人物间的真挚交往&#xff0c;因此配音的真实感和情感表达尤为重要。以下是针对家庭剧配音的几…

毕业设计2049网上选课系统JSP【程序源码+文档+调试运行】

摘要 本文详细介绍了一个网上选课系统的设计与实现过程。该系统主要分为学生用户、管理员和教师用户三个模块&#xff0c;涵盖了用户登录、在线选课、信息管理、密码修改等功能。通过对系统功能的分析&#xff0c;进行了数据库设计和界面设计&#xff0c;并进行了测试和优化。…

Java线程的学习

本来我以为这可能只是Java里的一小块知识点&#xff0c;但当我搜索自己关注的Up主的网课时&#xff0c;觉得还是开一个系列来记录好了。我的记录绝不仅仅是照搬课程中的内容&#xff0c;我会带上自己的理解以及示例代码、并且是按照本人的专业课老师上课的节奏来记录&#xff0…

Redisson 分布式锁实战应用解析

文章目录 前言一、Redisson介绍二、Redisson的使用1.1 引入依赖1.2 编写配置1.3 示例测试_011.4 示例测试_02 三、Redisson源码分析2.1 加锁源码2.2 看门狗机制 前言 分布式锁主要是解决分布式系统下数据一致性的问题。在单机的环境下&#xff0c;应用是在同一进程下的&#x…

【报错记录】解决使用Kotlin写的SpringBoot项目使用Aspect切面无法生效的问题

前言 为了能在SpringBoot使用Kotlin&#xff0c;真的是各种坑都彩礼一遍&#xff0c;这次遇到的问题是Aspect无法对Kotlin代码生效。我这里的使用场景是使用切面切Controller中的方法&#xff0c;用来对接口进行一些初始化和收尾工作。 Aspect在Controller类还是Java代码的时…

迁新址 启新程|美创科技杭州总部乔迁仪式圆满举行

“迁新址 启新程” 2023年11月21日 美创科技杭州总部乔迁仪式隆重举行 杭州未来科技城管委会、余杭国投集团、浙江省网络空间安全协会、浙江鸿程、华睿投资、金艮投资、如山资本、赛伯乐投资、宽带资本、普华投资、国中创投、密码资本、东方富海、之江商学、阿里云、联通&…

cc linux用root用户执行chmod 777 -R ./提示 Operation not permitted怎么办?

如果你作为 root 用户执行 chmod 777 -R ./ 命令时收到 “Operation not permitted” 错误&#xff0c;可能有几个原因&#xff1a; 不可更改 (Immutable) 文件属性&#xff1a; 文件可能被设置为不可更改。即使是 root 用户也不能修改这些文件的权限。使用 lsattr 命令查看文件…

Oracle与Redis Enterprise协同,作为企业缓存解决方案

来源&#xff1a;虹科云科技 虹科干货丨Oracle与Redis Enterprise协同&#xff0c;作为企业缓存解决方案 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 单独使用Oracle作为企业缓存数据库时&#xff0c;会出现哪些问题呢&#xff1f;使用Redis Enterprise与Oracle共…

使用 Filebeat+Easysearch+Console 打造日志管理平台

近年来&#xff0c;日志管理平台越来越流行。使用日志管理平台可以实时地、统一地、方便地管理和查看日志&#xff0c;挖掘日志数据价值&#xff0c;驱动运维、运营&#xff0c;提升服务管理效率。 方案架构 Beats 是轻量级采集器&#xff0c;包括 Filebeat、Metricbeat 等。E…

基于vue的three.js学习

vue安装three.js&#xff0c;并创建第一个入门场景 安装three.js npm install --save three引入three.js import * as THREE from threethree.js结构 three.js坐标 创建一个场景 scene场景&#xff0c;camera相机&#xff0c;renderer渲染器 创建一个场景 this.scene new T…