文章目录
- 1.树概念及结构
-
- 1.1树的概念
- [1.2 树的相关概念](#1.2 树的相关概念)
- [1.3 树的表示](#1.3 树的表示)
- [2. 二叉树概念及结构](#2. 二叉树概念及结构)
-
- [2.1 概念](#2.1 概念)
- [2.2 特殊二叉树](#2.2 特殊二叉树)
- [2.3 二叉树的性质](#2.3 二叉树的性质)
- [2.4 二叉树的存储结构](#2.4 二叉树的存储结构)
-
- [1. 顺序存储](#1. 顺序存储)
- [2. 链式存储](#2. 链式存储)

1.树概念及结构
1.1树的概念
树是一种非线性的数据结构,它是由 n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
-
有一个特殊的结点,称为根结点,根结点没有前驱结点
-
除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、......、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
-
树可以被拆解为根和子树,子树又可以用类似的方法进行拆解,这种逻辑和递归的大问题拆解为小问题直到不能被拆解,因此树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构
如果子树之间相交了那就是一种更加复杂的数据结构------图。

1.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)棵互不相交的树的集合称为森林;
1.3 树的表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的左孩子右兄弟表示法。

1. 左孩子:
无论父节点有多少个孩子(0个、1个、甚至100个),leftchild 指针始终只指向这些孩子中最左边的那一个,也就是第一个孩子。
这个"最左边"是从树的图形或逻辑顺序上说的第一个孩子。因为孩子之间在物理上并不直接相连,所以必须通过这个指针找到孩子链表的"头"。
如果父节点没有孩子,leftchild 就是 NULL。
如果父节点有多个孩子,那么剩下的孩子会从左到右,通过前一个孩子的 rightBrother 指针依次串起来,形成一个单链表。
简单说:leftchild 是孩子链表的头指针,rightBrother 是链表内部的 next 指针。 这是该表示法能够"化多为二"的核心约定。
2. 右兄弟:
rightBrother 指向的是当前节点的下一个兄弟节点,也就是在链表结构中,紧挨着它右边的那个。
如果说 leftchild 是孩子链表的"头指针",那么 rightBrother 就是链表中的 "next" 指针。
可以这样理解父子兄弟的完整关系:
父亲的视角:父亲只抓着"老大"的手。
父亲->leftchild 永远等于第一个孩子(最左边的那个)。
兄弟的视角:从老大开始,大家手拉手。
老大->rightBrother 指向老二。
老二->rightBrother 指向老三。
...
老幺->rightBrother 指向 NULL(表示链表结束,后面没有兄弟了)。
一个例子说明:

2. 二叉树概念及结构
2.1 概念
一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成

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

2.2 特殊二叉树
1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2^k - 1 ,则它就是满二叉树。

2. 完全二叉树:是一种效率很高的数据结构,它是由满二叉树的概念引申而来的。对于一棵高度为 h 完全二叉树:它的前 h−1 层都是满的,即每一层的结点数都达到了该层的最大值。第 h 层的结点可以不满,但必须全部连续地集中在最左边,中间不能有空缺。满二叉树是特殊的完全二叉树------当第 h 层也恰好满时,这棵完全二叉树也就是满二叉树。

2.3 二叉树的性质
1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2^( i - 1) 个结点。
2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 2^h - 1。
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0 , 度为2的分支结点个数为 n2 ,则有 n0 = n2 + 1。
4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h = log2 (n+1)。
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对
于序号为i的结点有:
1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n,否则无左孩子
3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n,否则无右孩子
注意:结论5必须是完全二叉树才能使用
2.4 二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1. 顺序存储
顺序存储就是用数组来存储二叉树结点。
它特别适合完全二叉树,因为完全二叉树的结点编号和数组下标可以完美对应,空间利用率高;如果存储非完全二叉树,则必须为缺失的结点保留空位,会造成空间浪费。
实际应用中,堆是顺序存储二叉树最典型的例子,此外线段树等基于完全二叉树的结构也常采用数组存储。
从不同视角看:在物理内存上它是一个数组,在逻辑关系上它是一棵二叉树。

顺序存储的优缺点:
- 优点:
1. 随机访问极快,父子关系计算简单
用下标算父子关系:
假设父亲在数组中的下标是:i
那么左孩子在数组中的下标是:2 * i + 1
右孩子在数组中的下标是:2 * i + 2
假设孩子在数组中的下标是:j
那么父亲在数组中的下标是:( j - 1 ) / 2
2. 内存紧凑,无指针开销
对于完全二叉树,每个数组位置都存有有效数据,没有任何额外的指针占用。链式存储每个结点需要额外的两个指针(或三个),数组存储在这上面省了空间(在完全二叉树下)。
3. 缓存友好,遍历效率高
数组在内存中是连续存放的,对 CPU 缓存非常友好。在做堆排序、层序遍历等操作时,数据预取命中率高,速度往往比链式存储快得多。
- 缺点:
1. 非完全二叉树会严重浪费空间
如果树不是完全二叉树(例如倾斜的二叉树、退化成链表),中间会有很多"空位",数组大量位置只能闲置或存空标记。极端情况:一棵深度为 k 的右斜树,实际只有 k 个结点,却需要申请长度为 2^k - 1 的数组来按编号存储,几乎全空。

2. 插入和删除麻烦
若要保持完全二叉树的形态(如堆),插入或删除元素通常放在数组末尾,再通过上浮/下沉调整,这还算高效。但如果是一般二叉树的插入/删除,要维持顺序存储的紧凑性,可能需要移动大量元素来填补空位,复杂度高。对于非完全二叉树,插入新结点甚至可能要求数组扩容并重新整理位置。
总结:数组存储用空间换时间,对于完全二叉树(尤其是堆)是近乎完美的选择;但对于非完全二叉树或结构频繁变化的树,空间浪费和插入删除的代价会迅速暴露出来
2. 链式存储
二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。

1. 二叉树的链式存储结构
最常见的是二叉链表,每个结点有三个部分:
数据域:存数据 / 左指针:指向左孩子 / 右指针:指向右孩子。
一棵树,就是由这些结点指针串起来的逻辑结构。我们只需要记下根结点的指针,就能访问整棵树。有时候,为了能方便地找到一个结点的父结点,还会加一个父指针,这就变成了三叉链表。
如何表示"没有孩子"?很简单,如果某个孩子不存在,对应的指针就指向 NULL(空)。比如叶子结点,它的左右指针都是 NULL。
特点:
找孩子容易:从一个结点出发,能一步直接找到它的两个孩子。
找父亲很难:如果想从一个结点找到它的父结点,因为没有存储父结点的指针,你只能从整棵树的根结点开始,重新向下搜索。这就是它最大的"不方便"之处。
2. 三叉链表
三叉链表在二叉链表的基础上,专门解决"找父亲难"的问题。结点结构:比二叉链表多一个指针。data:存放数据 / left:指向左孩子 / right:指向右孩子 / parent:指向父结点。
有了 parent 指针,在任意结点都能一步找到父结点,极大方便了向上回溯的操作。代价是空间:每个结点都多出一个指针,当树非常大时,内存开销就上去了
3. 多叉树的链式存储:左孩子右兄弟
这就回到了我们最初讨论的左孩子右兄弟表示法。它本质也是链式存储。
对于任意一棵多叉树,结点结构是:data:数据 / leftChild:指向第一个孩子 / rightBrother:指向下一个兄弟
这完美解决了"孩子数量不定"的问题,用两个固定的指针,把所有孩子串成一条链表。这正是链式存储高度灵活性的体现。
一张图总结一下:

至此,《数据结构深度剖析二叉树·上篇》的内容已近尾声。我们从树的基本概念出发,厘清了二叉树与普通树的区别,重点剖析了满二叉树与完全二叉树的定义和性质------这两类特殊二叉树不仅是理论的基石,更是实际工程中高效存储与运算的前提。随后,我们围绕二叉树的物理存储展开,对比了顺序存储与链式存储的优劣:数组存储的简洁高效、指针存储的灵活自如,背后折射的正是计算机科学中经久不衰的"时间-空间"权衡。我们还深入讨论了二叉链表、三叉链表,以及面向多叉树的"左孩子右兄弟"表示法,试图让你看清,无论结构如何变化,存储的本质都是用最合理的方式去组织结点间的逻辑关系。
在中篇中,我们将把目光聚焦于二叉树最紧凑、最高效的存储形态------顺序结构,并在此基础上引出数据结构中最具代表性的应用之一:堆(Heap)。
保持好奇,我们下篇见。
