数据结构 - 树

article/2025/8/26 21:24:04

(1)相关概念

兄弟节点:节点的父节点是同一个节点,所以它们之间互称为兄弟节点。

根节点:没有父节点的节点叫作根节点

叶子节点:没有子节点的节点叫作叶子节点或者叶节点。

节点的高度:节点到叶子节点的最长路径(边数)。

节点的深度:根节点到这个节点所经历的边的个数。

节点的层数:节点的深度 + 1.

树的高度:根节点的高度。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G7MY1WWx-1585234305704)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1585227146916.png)]

记这几个概念,还有一个小窍门,就是类比“高度”、“深度”、“层”这几个名词在生活中的含义。

在生活中,“高度”这个概念,其实就是从下往上度量,比如要度量第 10 层楼的高度、第 13 层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是 0。

“深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是 0。

“层数”跟深度的计算类似,不过,计数起点是 1,也就是说根节点的位于第 1 层。

(2)二叉树(Binary Tree)

二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvvxBsdg-1585234305711)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1585228609837.png)]
编号 2 的二叉树中,叶子节点全都在最底层,除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫作满二叉树

编号 3 的二叉树中,叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫作完全二叉树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10nrulvD-1585234305731)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1585228784320.png)]
存储一棵二叉树,我们有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。

1. 链式存储法

每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。

只要拎住根节点,就可以通过左右子节点的指针,把整棵树都串起来。这种存储方式我们比较常用。大部分二叉树代码都是通过这种结构来实现的。
在这里插入图片描述

2. 顺序存储法

把根节点存储在下标 i = 1 的位置,那左子节点存储在下标 2 * i = 2 的位置,右子节点存储在 2 * i + 1 = 3 的位置。以此类推,B 节点的左子节点存储在 2 * i = 2 * 2 = 4 的位置,右子节点存储在 2 * i + 1 = 2 * 2 + 1 = 5 的位置。

如果节点 X 存储在数组中下标为 i 的位置,下标为 2 * i 的位置存储的就是左子节点,下标为 2 * i + 1 的位置存储的就是右子节点。反过来,下标为 i/2 的位置存储就是它的父节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S9tbQpEM-1585234305739)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1585229609566.png)]

不过,上面的例子是一棵完全二叉树,所以仅仅“浪费”了一个下标为 0 的存储位置。如果是非完全二叉树,其实会浪费比较多的数组存储空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yKrpZ94D-1585234305742)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1585229773296.png)]

所以,如果某棵二叉树是一棵完全二叉树,那用数组存储无疑是最节省内存的一种方式。因为数组的存储方式并不需要像链式存储法那样,要存储额外的左右子节点的指针。这也是为什么完全二叉树会单独拎出来的原因,也是为什么完全二叉树要求最后一层的子节点都靠左的原因。

3. 二叉树的遍历

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4xZpmmi-1585234305747)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1585232103561.png)]
经典的方法有三种,前序遍历中序遍历后序遍历

  • 前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。

    preOrderTraverse(callback) {const preOrderTraverseNode = (node, callback) => {if (node !== null) {callback(node.key)preOrderTraverseNode(node.left, callback)preOrderTraverseNode(node.right, callback)}}preOrderTraverseNode(this.root, callback)
    }
    tree.inOrderTraverse(value => { console.log(value) })
    
  • 中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。

    inOrderTraverse(callback) {const inOrderTraverseNode = (node, callback) => {if (node !== null) {inOrderTraverseNode(node.left, callback)callback(node.key)inOrderTraverseNode(node.right, callback)}}inOrderTraverseNode(this.root, callback)
    }
    tree.inOrderTraverse(value => { console.log(value) })
    
  • 后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。

    postOrderTraverse(callback) {const postOrderTraverseNode = (node, callback) => {if (node !== null) {postOrderTraverseNode(node.left, callback)postOrderTraverseNode(node.right, callback)callback(node.key)}}postOrderTraverseNode(this.root, callback)
    }
    

平时最常用的树就是二叉树。二叉树的每个节点最多有两个子节点,分别是左子节点和右子节点。二叉树中,有两种比较特殊的树,分别是满二叉树和完全二叉树。满二叉树又是完全二叉树的一种特殊情况。

二叉树既可以用链式存储,也可以用数组顺序存储。数组顺序存储的方式比较适合完全二叉树,其他类型的二叉树用数组存储会比较浪费存储空间。除此之外,二叉树里非常重要的操作就是前、中、后序遍历操作,遍历的时间复杂度是 O(n)。

(3)二叉查找树(Binary Search Tree)

二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。

顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。它是怎么做到这些的呢?这些都依赖于二叉查找树的特殊结构。

二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
在这里插入图片描述

1. 二叉查找树的查找操作

先取根节点,如果它等于我们要查找的数据,那就返回。

如果要查找的数据比根节点的值小,那就在左子树中递归查找;

如果要查找的数据比根节点的值大,那就在右子树中递归查找。
在这里插入图片描述

代码实现:

search(key) {const searchNode = (node, key) => {if (node === null) return falseif (node.key === key) return nodereturn searchNode((key < node.key) ? node.left : node.right, key)}return searchNode(root, key)
}
2. 二叉查找树的插入操作

二叉查找树的插入过程有点类似查找操作。新插入的数据一般都是在叶子节点上,所以我们只需要从根节点开始,依次比较要插入的数据和节点的大小关系。

如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。
在这里插入图片描述

代码实现:

class Node {constructor(key) {this.key = keythis.left = nullthis.right = null}
}class BinarySearchTree {constructor() {this.root = null}insert(key) {const newNode = new Node(key)const insertNode = (node, newNode) => {if (newNode.key < node.key) {if (node.left === null) {node.left = newNode} else {insertNode(node.left, newNode)}} else {if (node.right === null) {node.right = newNode} else {insertNode(node.right, newNode)}}}if (!this.root) {this.root = newNode} else {insertNode(this.root, newNode)}}
}
const tree = new BinarySearchTree()
tree.insert(11)
tree.insert(7)
tree.insert(5)
tree.insert(3)
3. 二叉查找树的删除操作

删除操作就比较复杂了 。针对要删除节点的子节点个数的不同,我们需要分三种情况来处理。

  • 第一种情况是,如果要删除的节点没有子节点,只需要直接将父节点中,指向要删除节点的指针置为 null。比如图中的删除节点 55。

  • 第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。比如图中的删除节点 13。

  • 第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。比如图中的删除节点 18。
    在这里插入图片描述

代码实现:

function deleteNode(root, key){if (!root){console.log("删除失败");return root;}if (root.value > key){  //若当前结点值大于删除值,则继续在左子树中寻找删除值root.left = deleteNode(root.left, key);}else if (root.value < key){  //若当前结点值小于删除值,则继续在右子树中寻找删除值root.right = deleteNode(root.right, key);}else{  //找到与删除中相等的结点if (root.left === null & root.right === null){  //叶子结点root = null;}else if (root.left === null){  //只有右子树root = root.right;}else if (root.right === null){  //只有左子树root = root.left;}else{  //同时具有左右子树let prevNode = root.left;while(prevNode.right){  //寻找不大于当前结点值的最大结点值prevNode = prevNode.right;}root.value = prevNode.value;  //替换值root.left = deleteNode(root.left, prevNode.value);  //递归左子树,删除重复值的结点}} return root;
}
4. 搜索最小值和最大值
min(node) {const minNode = node => {return node ? (node.left ? minNode(node.left) : node) : null}return minNode(node || this.root)
}max(node) {const maxNode = node => {return node ? (node.right ? maxNode(node.right) : node) : null}return maxNode(node || this.root)
}
5. 时间复杂度分析

实际上,二叉查找树的形态各式各样。比如这个图中,对于同一组数据,构造了三种二叉查找树。它们的查找、插入、删除操作的执行效率都是不一样的。
在这里插入图片描述

图中第一种二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。这是一种最糟糕的情况。

最理想的情况,二叉查找树是一棵完全二叉树(或满二叉树)。不管操作是插入、删除还是查找,时间复杂度其实都跟树的高度成正比,也就是 O(height)

(4)相对散列表,二叉树的优势

  • 散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。
  • 散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。
  • 笼统地来说,尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
  • 散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
  • 为了避免过多的散列冲突,散列表装载因子不能太大,特别是基于开放寻址法解决冲突的散列表,不然会浪费一定的存储空间。

学习于:

极客时间《数据结构与算法之美》

JavaScript中的数据结构与算法学习


http://chatgpt.dhexx.cn/article/MerF2uh6.shtml

相关文章

根节点、子节点、叶子节点是什么?

前言&#xff1a;这个属于数据结构&#xff1a;树。 下面给个例子图解释&#xff08;根节点、子节点、叶子节点&#xff09;。 上图数字 1、3、7是叶子节点&#xff1b;&#xff08;因为他们下面没有分叉出子节点&#xff0c;所以称为&#xff1a;叶子节点&#xff09;【度为0】…

弱网、2G、3G、4G测试

1.各项指标 教程指引&#xff1a;弱网测试教程 2.概念介绍 Bandwidth&#xff08;带宽&#xff09;、Utilistation&#xff08;利用百分比&#xff09;、Round-trip&#xff08;往返延迟&#xff09;、MTU&#xff08;最大传输单元&#xff09; 3G&#xff1a;300k-2Mbps左…

简单实用Chrome 日常开发功能详解,帮助你上班摸鱼

chrome是目前开发过程中一骑绝尘的浏览器&#xff0c;占有绝对领导地位。其强大的功能和生态圈&#xff0c;让很多开发者爱不释手。但很多的开发者使用chrome还是停留在F12打开控制台查看log、检查元素或者debug打断点阶段&#xff0c;其实chrome的强大的功能远远超过我们的想象…

小米路由器3G如何解决USB3.0 5G WiFi速度慢的问题

经常玩电脑&#xff0c;希望家里有个轻 nas&#xff0c;小米路由器是一个不错的选择&#xff0c;tbw买了一个小米路由器3G看重的是快速的速度&#xff08;usb3.0 5G Wifi&#xff09;&#xff0c;及小米的可拓展性&#xff0c;使用usb3.0的usb接口&#xff0c;且使用5gb网速&am…

浏览器通过f12来限制网速

浏览器可以使用F12开发者工具来模拟网速的快慢。 打开需要测试的网站&#xff0c;点击F12&#xff0c;再点击选项里的network-no throttling&#xff0c;展示的有几种&#xff0c;offline&#xff0c;快3g&#xff0c;慢3g&#xff0c;或者自定义 点击add可以自定义网速&#…

移动网速测试软件,网速测试大师APP

网速测试大师APP是一款专业的手机网络测试应用&#xff0c;支持一键测速&#xff0c;精准快速&#xff0c;还能全方位分析网速&#xff0c;Wifi和移动网络全检测&#xff0c;30秒测速当前网络状况&#xff0c;做随时随地的测速专家。 该软件整个测速过程精准又快速&#xff0c;…

android 显示网速,随着掌握联网状态 Android手机如何显示实时网速

很多时候手机信号栏明明显示正在联网而且图标也显示正在下载上传中&#xff0c;但就是打不开网页。实际上&#xff0c;此时可能正处于4G→3G切换状态而出现了短暂的断网。那么&#xff0c;如何才能准确掌握手机当前的联网状态呢&#xff1f; 答案很简单&#xff0c;就是通过手机…

Fiddler工具的弱网模拟2G/3G/4G

日常测试工作中&#xff0c;C端用户会因信号和设备网络原因&#xff0c;出现一些恶劣网络的状态&#xff0c;然后出现奇奇怪怪的丢包、重传、UI空白、UI绘制异常等问题&#xff0c;就需要测试人员去模拟这类弱网环境去重现用户反馈的问题&#xff0c;以方便开发同学解决和调优加…

网速测试大师的软件怎么回事,网速测试大师

手机网速测试大师是一款手机网络测试软件&#xff0c;通过手机网速测试大师你可以直接测试你手机的网络速度&#xff0c;无论是WIFI还是手机移动网络都可以检测&#xff0c;让你更加了解你的手机网络。 软件介绍 网速测试大师是一款热度仅次于Ookla Speedtest. net的网速测试工…

限制浏览器网速

需求&#xff1a;限制浏览器网速&#xff0c;拉长请求时间&#xff0c;方便验证请求loading状态是否添加成功。 ps&#xff1a;一般建议给弹窗/按钮增加loading状态&#xff0c;防止重复点击之后多次请求 解决方案&#xff1a;控制台(f12)-网络(Network) No throttling 无限制…

使用chrome进行慢网速测试

前言 在开发的时候&#xff0c;有这么一种情况&#xff0c;点击一个按钮调用一个接口生成数据&#xff0c;之后刷新页面&#xff0c;但是由于网络慢的原因&#xff0c;能够多次点击&#xff0c;但是自己的网络模拟不了很慢的情况&#xff0c;怎么办呢&#xff1f; 使用chrome…

Vue Chrome浏览器手动调节模拟网速

Chrome浏览器手动调节网速 1.首先打开chrome浏览器&#xff0c;按下键盘上的f12键&#xff0c;弹出开发者调试工具 2.点击network——>No throtting弹出选择项 3.选择需要模拟的选项就行了 Fast 3G&#xff1a;表示快的3g网速 Slow 3G&#xff1a;表示慢的3g网速 Offline…

3g显卡测试软件,3G网速测试细则

3G网速测试细则 3G网速测试细则 测试机型&#xff1a;小米手机2、小米手机2电信版、vivo X1st 测试机型 测试软件&#xff1a;speedtest 爱奇艺高清影视 测试软件 测试方法&#xff1a; ① 在同一时间段、同一地点使用Speedtest测试三台手机3G网络条件下的速度 ② 分别使用装有…

国内三大制式3G网络简介及比较

比比谁最快 随着中国电信近期3G网络试商用的开始&#xff0c;越来越多的用户开始关注国内3G网络上网速度&#xff0c;“传说”中的3G网络速度到底有多快呢&#xff1f;据最新的数据显示&#xff0c;中国电信的试商用测试信号可以达到近200KB/s的下载速度。这是怎样的一个概念&a…

python自动化介绍

首先我们得了解一下什么是自动化测试&#xff1f; Python自动化就是使用python语言来编写的脚本或者平台&#xff08;自动化运维平台、自动化测试平台-->devops&#xff09;&#xff0c;实现公司中重复业务的自动化流程。大体的方向分为 python自动化测试 python自动化运维 …

【Python自动化测试32】App自动化环境搭建

文章目录 一、前言二、安装与环境搭建教学2.1 环境依赖2.2 appium程序安装2.3 appium-python-client2.4 模拟器安装2.5 java jdk安装2.6 Android SDK环境 一、前言 本文章主要讲解App自动化测试的环境搭建&#xff0c;在App自动化测试环境搭建中&#xff0c;有非常多的坑&#…

Python自动化--1.Python环境安装-linux

python自动化更贴近运维自动化 Python自动化–1.Python环境安装-linux Python自动化–2.Python变量 Python自动化–3.Python数据类型 Python自动化–4. python类型转换 Python自动化–5. if判断语句 Python自动化–6. 写一个python程序 Python自动化–7. 函数的定义和调…

30道python自动化测试面试题

文章目录 1、什么项目适合做自动化测试&#xff1f;2、什么是 PO 模式&#xff1f;3、PO 模式的封装原则有哪些&#xff1f;4、 Python 中 *args 和 **kwargs 的作用&#xff1f;5、Python 中的垃圾回收机制是什么&#xff1f;6、selenium中隐藏元素如何定位&#xff1f;7、关闭…

python自动化测试-最常用的自动化测试框架

在开始学习python自动化测试之前&#xff0c;先了解目前市场上的自动化测试框架有哪些&#xff1f; 随着技术的不断迭代更新&#xff0c;优胜劣汰也同样发展下来。从一开始工具型自动化&#xff0c;到现在的框架型&#xff1b;从一开始的能用&#xff0c;到现在的不仅能用&…