
个人主页:
wengqidaifeng
✨ 永远在路上,永远向前走
个人专栏:
数据结构
C语言
嵌入式小白启动!
重要OJ算法题详解
文章目录
- 前言---二叉树初探:计算机科学中的分叉树
- 一.树的概念及结构
-
- 1.树的概念及结构
-
- [1.1 什么是树?(用生活例子引入)](#1.1 什么是树?(用生活例子引入))
- [1.2 树的官方定义(用大白话翻译)](#1.2 树的官方定义(用大白话翻译))
- 2.树的相关概念
- 3.树的表示
- 4.树在实际中的运用(表示文件系统的目录树结构)
- 二.二叉树概念及结构
- 总结
前言---二叉树初探:计算机科学中的分叉树
前言
在计算机科学的世界里,有一类结构无处不在------文件夹的层层嵌套、网站的组织架构、甚至你正在阅读的这篇博客所在的DOM树,它们背后都藏着一个共同的身影:树。
而今天我们要聊的,是树家族中最重要、最基础的一员------二叉树。
你可能会好奇:为什么偏偏是"二叉"?为什么不是三叉、四叉?这得从计算机的"思维方式"说起。计算机本质上是一个只会判断"是/否"、"真/假"、"大/小"的机器,它最擅长的事情就是二选一。就像走迷宫时面对岔路口,每次只需要决定向左还是向右------这种"二分"的思维模式,恰好与二叉树的结构完美契合。
事实上,二叉树的诞生正是源于人类对"比较"和"排序"的需求。早在20世纪60年代,计算机科学家们就开始系统研究这种结构。1960年代初,著名计算机科学家艾兹赫尔·戴克斯特拉(Edsger Dijkstra)提出了基于二叉树的堆 (Heap)数据结构;1964年,乔治·柯林斯(George J. Collins)提出了AVL树------第一种自平衡的二叉搜索树。这些早期研究为后来的二叉树算法和应用奠定了基石。
更有趣的是,在数学上,两个分支和N个分支是等价的------任何多叉树都可以通过特定的方式转换为二叉树来处理。这意味着,掌握了二叉树,你就掌握了解构复杂层级关系的一把万能钥匙。
今天,我们就从零开始,揭开二叉树的神秘面纱。
一.树的概念及结构
1.树的概念及结构
1.1 什么是树?(用生活例子引入)
首先,你可以想象一下你电脑里的文件夹结构。
你打开"我的电脑",里面有一个叫"学习资料"的文件夹(这可以看作是根)。你双击打开它,里面又有几个文件夹:"数学"、"英语"、"Python编程"。你再打开"Python编程",里面又有"基础语法"和"项目实战"两个文件夹...
这种一层套一层、有清晰归属关系的结构,其实就是一种"树"形结构。
或者,再想一下你家中的家族族谱 :
最上面是爷爷(根 ),爷爷下面有爸爸和叔叔(分支 ),爸爸下面有你和你哥哥(叶子)。
1.2 树的官方定义(用大白话翻译)
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因
为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根结点没有前驱结点
除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、......、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
因此,树是递归定义的。
-
"树是一种非线性的数据结构"
- 人话解释 :我们之前可能学过"链表"、"数组",它们像一条线一样,一个挨着一个串起来。但树不一样,它是一个有分支的结构,一个点可以连接多个下面的点,就像文件夹里可以有多个子文件夹一样。
-
"它是由n(n>=0)个有限结点组成"
- 人话解释:这里的"结点"就是树里的每一个元素。比如文件夹里的每一个文件或文件夹,都可以称作一个"结点"。如果 n=0,那就代表这是一棵"空树",啥也没有。
-
"有一个特殊的结点,称为根结点,根结点没有前驱结点"
- 人话解释 :根就是整棵树最顶端的那个老大。
- 特点:它只有一个,而且它是唯一一个没有"上级"的节点。就像一家公司的CEO,或者一个国家的总统,没人管他。在我们刚才的例子中,"C盘"或者"学习资料"就是根。
-
"除根结点外,其余结点被分成M(M>0)个互不相交的集合...其中每一个集合...又是一棵子树"
- 人话解释 :这是树最核心、最神奇的地方------递归。
- 我们把根节点去掉,剩下的部分其实是由好几棵"小树"(子树)组成的。
- 比如,把"学习资料"这个根节点拿掉,剩下的"数学"文件夹本身,其实就是一棵"小树"。它下面还可以有"高等数学"、"线性代数"这些"叶子"。
- 这些"小树"之间是互不相交的,意思是"数学"文件夹里的东西不会跑到"英语"文件夹里去,它们是独立的。
-
"因此,树是递归定义的"
- 人话解释 :这个概念很有意思,就是树里面还有树。
- 一棵大树(比如整个文件系统),是由很多棵小树(各个子文件夹)组成的。而每一棵小树,它的结构又和大树一模一样(也有自己的根和分支)。这就叫递归,像俄罗斯套娃一样,一层套一层。


注意 :树形结构中,子树之间不能有交集,否则就不是树形结构

2.树的相关概念

结点的度 :一个结点含有的子树的个数称为该结点的度; 如上图:A的为6
叶结点或终端结点 :度为0的结点称为叶结点; 如上图:B、C、H、I...等结点为叶结点
非终端结点或分支结点 :度不为0的结点; 如上图:D、E、F、G...等结点为分支结点
双亲结点或父结点 :若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
孩子结点或子结点 :一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
兄弟结点 :具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6
结点的层次 :从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
树的高度或深度 :树中结点的最大层次; 如上图:树的高度为4
堂兄弟结点 :双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点
结点的祖先 :从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
子孙 :以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
森林 :由m(m>0)棵互不相交的树的集合称为森林;
以上转为人话:
- 根节点 (Root):老祖宗(唯一的一个,没有爸爸)。
- 叶子节点 (Leaf):家里最年轻的这一代,没有孩子了(比如你,你下面没文件夹了)。
- 父节点 (Parent):你的爸爸。如果B节点是A节点的上一级,A就是B的子节点,B就是A的父节点。
- 子节点 (Child):你的儿子。父节点的直接下级。
- 兄弟节点 (Sibling):你的亲兄弟姐妹。同一个父节点下的子节点。
- 节点的度 (Degree):一个人有几个儿子。比如你爸爸生了2个孩子,他的度就是2。
- 树的度:整个家族里,谁的儿子最多,那个数字就是树的度。
- 层次 (Level):你在这个家族里排第几辈。老祖宗是第1层,他儿子是第2层。
- 树的深度/高度:这个家族总共传了多少代(总共几层楼)。
3.树的表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系 ,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
c
typedef int DataType;
struct Node
{
struct Node* firstChild1; // 第一个孩子结点
struct Node* pNextBrother; // 指向其下一个兄弟结点
DataType data; // 结点中的数据域
};
在计算机里存储一棵树(比如那个文件夹结构)时,会碰到一个麻烦:每个节点的孩子数量是不固定的 。
有的节点(比如根目录)可能有几十个文件夹(孩子),有的节点(比如一个空文件夹)则一个孩子都没有。
如果用我们之前讲过的、每个节点都固定留一堆位置来存孩子的指针(就像C语言里的数组),会造成大量的内存浪费。因为大部分节点可能没那么多孩子。
为了解决这个问题,计算机科学家想出了一个很巧妙的办法,叫做**"孩子兄弟表示法"**。
核心思想:统一的视角
这个方法的核心思想只有一句话:
让树的每个节点,只记录两件事:
- 它的第一个孩子是谁。
- 它的下一个兄弟是谁。
通过这种方式,任何一棵复杂的、每个节点孩子数量都不一样的树,都可以被转换成一种每个节点只有两个指针 的结构。这实际上就是把一棵普通的树,变成了一棵二叉树(每个节点最多有两个分支)。
一个生活中的类比:家庭聚会
想象一下,你要组织一场超级大家族聚会,你需要记录所有人的关系,画一张人员关系图。
-
普通树画法:爷爷坐在中间,他下面围着爸爸、叔叔、姑姑。爸爸下面又围着你、你哥、你姐。这种画法很直观,但计算机存储起来麻烦,因为每个人下面围的人数都不一样。
-
"孩子兄弟"画法:你换一种方式组织大家排队拍照:
- 每个人只看着自己的大儿子(长子)。
- 每个儿子只看着自己的亲弟弟(下一个兄弟)。
这样排出来的队伍就很有规律了:
爷爷站在第一排,他只指着他的大儿子(爸爸)。
爸爸站在第二排,他一手牵着他的大儿子(你),另一手指着他的亲弟弟(叔叔)。
叔叔站在第二排爸爸的旁边,他指着他的大儿子(堂弟),同时手指着下一个兄弟(姑姑)...
这样一来,虽然关系没变,但整个结构变成了一个规整的、每个人最多只"指向"两个人的链条。
图解示例
假设我们有这样一棵树:
A
/ | \
B C D
/ \ |
E F G
- A 是根,有三个孩子:B、C、D。
- B 有两个孩子:E、F。
- C 是叶子,没孩子。
- D 有一个孩子:G。
用 孩子兄弟表示法 存储后,它会变成下面这个样子(想象每个方块都有两个指针):
- 节点A :
- 指向它的第一个孩子:B。
- 它是根节点,没有兄弟,所以指向兄弟的指针为空 (Null)。
- 节点B :
- 指向它的第一个孩子:E。
- 指向它的下一个兄弟:C(因为A的孩子中,B后面跟着C)。
- 节点C :
- 没有孩子,所以指向孩子的指针为空。
- 指向它的下一个兄弟:D。
- 节点D :
- 指向它的第一个孩子:G。
- 没有下一个兄弟了,所以指向兄弟的指针为空。
- 节点E :
- 没有孩子。
- 指向它的下一个兄弟:F(因为B的孩子中,E后面跟着F)。
- 节点F :
- 没有孩子。
- 没有兄弟,指向兄弟的指针为空。
- 节点G :
- 没有孩子。
- 没有兄弟,指向兄弟的指针为空。
- 操作变得简单 :无论原来的树有多复杂,经过这种变换,我们都可以用二叉树的操作方法来处理它。而二叉树是计算机科学里研究得最透彻、算法最成熟的数据结构之一。
- 结构统一:每个节点的结构是固定的(数据 + 孩子指针 + 兄弟指针),不像孩子数量不固定时那么难以管理。
- 节省空间:相比于为每个节点预留最大孩子数量的空间,这种方法只存储实际需要的指针,大大减少了内存浪费。
孩子兄弟表示法 ,你可以把它理解成一种"变形术"或"翻译器"。它把一棵"每个节点有任意多个孩子"的树,通过 "长子" 和 "兄弟"这两个视角,翻译成了一棵"每个节点最多有两个分支"的二叉树。

无论一个父亲节点有多少孩子,child总是指向左边开始的第一个孩子
4.树在实际中的运用(表示文件系统的目录树结构)
理解了树的结构(文件夹、族谱)之后,你会发现,计算机的世界里几乎到处都是树的影子。因为计算机最擅长的就是处理重复的事情,而"树"这种结构,完美地解决了管理、查找和排序的问题。
场景一:你的文件系统
- 实际运用 :当你打开"我的电脑",看到C盘、D盘,点进去看到一层一层的文件夹,这就是一棵标准的多叉树(每个节点可以有多个孩子)。
- 为什么用树?
- 管理方便:如果所有的文件都堆在一个列表里(就像数组一样),你想找一个文件,得翻半天。但通过文件夹分类(树的层次),你可以很快定位。
- 路径唯一 :在树里,从根节点到任何一个文件,只有一条路。这就是为什么文件的"路径"(比如
C:\用户\张三\桌面\照片.jpg)是唯一的。这个路径其实就是在树里从根走到那个叶子节点的路线图。

场景二:网页浏览器
你可能每天都在用,但没意识到它是树。
- 实际运用:当你在浏览器里看这篇文章时,你可以试着右键点击页面空白处,选择"检查"或"查看页面源代码"。
- 你会看到什么?
- 你会看到一堆像
<html>、<body>、<div>、<p>这样的标签。 - 这其实就是一个巨大的树,叫做 DOM 树。
<html>是根节点。<body>是<html>的子节点。<p>(段落)是<body>的子节点。- 如果
<p>里面有一段加粗的<b>文字,那么<b>就是<p>的子节点。
- 你会看到一堆像
- 为什么用树?
- 浏览器需要知道"这一段文字是在这个标题下面的,那个图片是在这个段落里面的"。树结构能完美表示这种嵌套关系 和层级关系。只有这样,浏览器才能知道把"张三"的名字显示在"联系人"板块下,而不是显示在页面顶部。
场景三:你公司的组织架构
- 实际运用:假设你在公司里,上面有部门经理,部门经理上面有总监,总监上面有CTO,CTO上面有CEO。
- 为什么用树?
- 这是一个典型的树结构(通常被称为组织树)。
- CEO是根节点。
- CTO、CFO是CEO的子节点。
- 这种结构清晰地定义了上下级关系 和汇报关系。软件里用来管理员工信息的模块,底层就是用树来存储这些层级数据的。
场景四:人工智能与棋类游戏
这是一个稍微进阶但非常有意思的应用。
- 实际运用:你玩过象棋、围棋或者五子棋的人机对战吗?电脑是怎么决定下一步走哪里的?
- 为什么用树? 电脑会使用一种叫博弈树 的数据结构。
- 当前的棋局是根节点。
- 你走一步棋,就产生了几个新的棋局(子节点)。
- 电脑针对你走的每一步,再计算它可能的回应(子节点的子节点)...
- 这样一层层往下推演,就形成了一棵巨大的树。电脑通过评估这棵树,找到那个最有可能让它赢的走法。
场景五:数据库索引
当你需要在海量数据里飞快地找到一条记录时,树就派上了大用场。
- 实际运用:你在淘宝搜一个商品,或者用身份证号查一个人的信息。
- 为什么用树?
- 如果数据是一条条排着队放着的(线性结构),你要找一条,最坏情况得从头翻到尾,几亿条数据就卡死了。
- 数据库会用一种特殊的树,比如 B-树 或 B+树。
- 这种树就像一本书的目录。它不会让你一页页翻,而是先告诉你你要找的数据大概在哪个章节,然后去那个章节里找哪个小节,最后精确到页码。这样经过几次查找就能定位数据,效率非常高。你现在能在毫秒级内打开网页、查到数据,很大程度上要感谢这种树形结构。
为什么计算机这么爱用树?
我们可以把树看作是计算机里的一种组织管理大师:
- 用来表示天然的分层:文件系统、网页结构、公司架构,世界本身就是分层的,所以用树来存最自然。
- 用来快速查找(二分搜索树):它可以让你每次比较都排除掉一半的可能性,像猜数字游戏一样,非常快。
- 用来做出决策(决策树):人工智能可以通过树来模拟各种可能性,选择最优路径。比如导航软件规划路线,或者AI下棋。
所以,下次当你打开文件夹、浏览网页或者玩人机游戏时,可以想想:哦,原来背后有一棵看不见的"树"在支撑着这一切的运行。
二.二叉树概念及结构
1.概念
棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 由一个根结点加上两棵别称为左子树和右子树的二叉树组成


从上图可以看出:
比特就业课 - 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:

2.现实中的二叉树


3.特殊的二叉树
- 满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是2^k-1 ,则它就是满二叉树。 - 完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。


4.二叉树的性质
- 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.
- 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 2^h - 1.
- 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为n2 ,则有n0 = n2+1
c
/*
* 假设二叉树有N个结点
* 从总结点数角度考虑:N = n0 + n1 + n2 ①
*
* 从边的角度考虑,N个结点的任意二叉树,总共有N-1条边
* 因为二叉树中每个结点都有双亲,根结点没有双亲,每个节点向上与其双亲之间存在一条边
* 因此N个结点的二叉树总共有N-1条边
*
* 因为度为0的结点没有孩子,故度为0的结点不产生边; 度为1的结点只有一个孩子,故每个度为1的结
点* * 产生一条边; 度为2的结点有2个孩子,故每个度为2的结点产生两条边,所以总边数为:
n1+2*n2
* 故从边的角度考虑:N-1 = n1 + 2*n2 ②
* 结合① 和 ②得:n0 + n1 + n2 = n1 + 2*n2 - 1
* 即:n0 = n2 + 1
*/
- 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2
为底,n+1为对数) - 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对
于序号为i的结点有: - 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
- 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
- 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
总结
二叉树是计算机科学中最基础、最重要、最优雅的数据结构之一。它用最简单的"二分"规则,构建出了一个充满可能性的世界。
通过本文,我们了解了:
二叉树的定义和基本形态
它的起源 ------源于计算机对"比较"和"二分"的需求
它的核心性质 ------节点数、深度、叶子节点关系等
它的特殊类型 ------满二叉树、完全二叉树、二叉搜索树、平衡二叉树
它在现实世界的广泛应用
掌握了二叉树,你就掌握了解构层级关系、优化查找效率的基本功。在后续的学习中,我们还会深入二叉树的遍历、存储实现,以及更高级的变种(如红黑树、B树)。
如果你能在下次打开文件夹、浏览网页或使用数据库时,想起背后那棵看不见的"二叉树",那么这篇文章的目的就达到了。
欢迎来到二叉树的世界------从这里开始,你将走进数据结构的更深处。
最后,感谢各位大佬的观看!