Appearance
树与二叉树的定义与基本概念
2026 大纲 四(一)树的基本概念、四(二)1 二叉树的定义及其主要特性、四(二)2 二叉树的顺序存储结构和链式存储结构 三条,本篇同时承载。
树的定义为什么非递归不可
树是
定义里"子树本身又是一棵树"这句自我引用不是偷懒,是没有别的写法。树的层数事先不知道——用"根、孩子、孙子……"逐层列举来定义,就必须先固定层数,可树的高度是任意的。递归定义把"任意深"压成一句话,代价是后面所有关于树的算法与证明也天然是递归的:求高度、数结点、四种遍历,写出来几乎全是三行递归。根源就在这里。
"互不相交"这个词也删不得,它一次挡住两件事:
- 共享结点——若两棵子树共用一个结点,那个结点就有两个双亲,"从根到它的路径"不再唯一;
- 成环——若子树里含有自己的祖先,从根出发可以无限走下去,递归不终止。
删掉这个条件得到的结构就是图。图的遍历必须额外记"访问过没有",树的遍历不用——这个便利完全来自"互不相交"。换个说法:树是"连通且无回路"的图,
术语:先把三处约定钉死
A ← 根结点(第 1 层)
/ | \
B C D ← 第 2 层
/ \ / \
E F G H ← 第 3 层
/
I ← 第 4 层大部分术语看一眼就懂,先说三处不钉死就会算错的约定。
第一,层次从 1 起算。 本站与严蔚敏教材一致,根在第 1 层,所以高度为
第二,"路径长度"数的是边,不是点。
第三,"结点的高度"数的是结点数。 从该结点到最远叶子路径上的结点个数,所以叶子的高度是 1 不是 0。树的高度就是根的高度,也等于最大层次。
其余术语对着上图看一遍就够:
| 术语 | 定义(括号内为上图示例) |
|---|---|
| 结点的度 / 树的度 | 该结点的子树个数 / 全树结点度的最大值( |
| 叶子(终端)/ 分支结点 | 度为 0 / 度 |
| 孩子 / 双亲 / 兄弟 / 堂兄弟 | 子树的根是孩子、该结点是双亲;同双亲互称兄弟;双亲同层但不同的是堂兄弟( |
| 祖先 / 子孙 | 根到该结点路径上的所有结点是它的祖先,反之为子孙( |
| 带权路径长度 | 结点的权 × 它到根的路径长度,见 哈夫曼树 |
| 有序树 / 森林 | 子树从左到右有次序(交换两棵子树就是另一棵树)/ |
顺带把森林、树、结点三者的转化关系记住,它是后面"森林 ↔ 二叉树"转换的地基:一棵树删去根得到一个森林(原来的各棵子树);一个森林加一个公共根得到一棵树;一棵树本身就是只含一棵树的森林;空集是合法的森林(
一般树 / m 叉树的四条性质与推导(做"度为 m 的树"计算题时展开)
T1:结点数 = 所有结点的度之和 + 1,即
证明(边数双计数):树中每条边恰好由一个结点"射出"(从双亲指向孩子),所以边数
T2:度为
T3:高度为
T4:
边界提醒:"度为
的树"和" 叉树"不是一回事。 叉树要求每个结点至多 个孩子,允许所有结点的度都小于 ;度为 的树要求至少存在一个度恰为 的结点。求"最少结点数"时这个差别会直接改变答案。
二叉树不是"度为 2 的有序树"
二叉树:或为空集,或由一个根和两棵互不相交的左、右子树组成。三个要点——至多两棵子树、严格区分左右、允许为空。
空树 只有根 只有左子树 只有右子树 左右都有
● ● ● ●
/ \ / \
● ● ● ●五种基本形态里,第三种和第四种是同一个"根 + 一个孩子"的结构,只因为孩子挂左边还是挂右边就算成两棵不同的二叉树。这就是二叉树与"度为 2 的有序树"的分水岭:
| 二叉树 | 度为 2 的有序树 | |
|---|---|---|
| 子树数量 | 每个结点至多 2 棵子树 | 树的度恰为 2,即必须存在度为 2 的结点 |
| 空树 | 允许( | 不允许;度为 2 的树至少有 3 个结点 |
| 左右之分 | 严格区分:只有一个孩子时,"它是左孩子"和"它是右孩子"是两棵不同的二叉树 | 只有一棵子树时无左右可分,是同一棵树 |
判别办法就一条:拿"根 + 一个孩子"去试。 作为二叉树它有两种(左挂、右挂),作为有序树只有一种。
这个差别看着琐碎,其实是后面一条重要结论的根源:前序 + 后序不能唯一确定一棵二叉树——因为独生子的左右身份,前序和后序都表达不出来。
同样的措辞陷阱还有一对:"
满二叉树与完全二叉树:差别在"允不允许度 1"
满二叉树 完全二叉树 非完全二叉树
1 1 1
/ \ / \ / \
2 3 2 3 2 3
/ \ / \ / \ / / \
4 5 6 7 4 5 6 4 7满二叉树:高度
完全二叉树:每个结点都与高度
两者最要紧的差别:完全二叉树允许有一个度为 1 的结点,满二叉树一个都没有。 当
定义里的"与满二叉树编号一一对应"不好直接检查,实际判定用下面任一条:
- 层序编号法:按层序给结点编号,若编号
全部有结点、无空缺,就是完全二叉树。上图第三棵中编号 5、6 空缺而 7 有结点,故不是。 - 层序遍历法:做层序遍历,把空孩子也入队;一旦出队遇到空结点,此后不允许再出现非空结点。这是写代码判定的标准做法,见 层序遍历。
- 形态法:前
层是满的,第 层的结点从左到右连续排列,中间不许断。
两个结构特点顺带记住:叶子只可能出现在最后两层;对任一结点,若其右分支下子孙的最大层次为
另有按值组织的二叉排序树(左
五条性质,两条要会推
| 结论 | 适用范围 | |
|---|---|---|
| 性质 1 | 第 | 任意二叉树 |
| 性质 2 | 高度为 | 任意二叉树 |
| 性质 3 | 任意二叉树,与是否完全、是否满无关 | |
| 性质 4 | 完全二叉树高度 | 只对完全二叉树 |
| 性质 5 | 层序编号 | 只对完全二叉树,一般二叉树套用必错 |
性质 1、2 是逐层翻倍再求和,看一眼就明白。要真正会推的是性质 3 和性质 5——前者是所有计数题的入口,后者划定了顺序存储的边界。
性质 3: ,靠边数双计数
同一个量(结点总数)从两个角度各数一遍,然后让两式相等。
角度一,按度分类。 二叉树中结点的度只能是 0、1、2,所以
角度二,按边数。 除根之外每个结点头上恰有一条边射入,所以边数
联立
关键在于
性质 5:编号关系,以及它为什么只对完全二叉树成立
设结点
右孩子紧随其后是
推导里用到的"第
性质 1、2、4 的证明(想补全推导就展开)
性质 1(对
性质 2:把性质 1 逐层求和
取到等号的正是满二叉树。反过来,
性质 4:设高度为
两式为什么等价却写法不同:
在 恰为 (满二叉树)时更直观; 计算时不用先加 1,手算更快。 : , ,一致。
完全二叉树的三条导出结论
把性质 3 与性质 5 合起来用,能挤出三条直接可用的结论。
第一,
第二,叶子数
第三,最后一个分支结点的编号是
一个方向性的坑:正问单值,反问双值。 由
求 答案唯一;但已知 反求 , (奇)与 (偶)都成立。原因是第 号结点作为第 号结点的左孩子挂上去时,它自己是叶子、同时把双亲从叶子变成了分支结点,一进一出,叶子总数不变。
存储结构:顺序存储的代价藏在编号里
| 顺序存储 | 链式存储(二叉链表) | |
|---|---|---|
| 怎么定位 / 找双亲孩子 | 下标即层序编号,靠性质 5 的公式,均 | 靠指针;找孩子 |
| 适用形态 / 最坏空间 | 只适合完全二叉树(含满二叉树、堆);单支树 | 任意二叉树;空间与结点数成正比 |
| 空间开销 | 无指针开销,编号连续时恰好装满 | 每结点 2 个指针,空指针恰有 |
c
typedef struct BiTNode {
ElemType data; // 数据域
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;顺序存储用一维数组按完全二叉树的层序编号放结点:编号
一般二叉树 数组(0 表示该位置无结点)
A 下标 1 2 3 4 5 6 7
/ \ 值 A B C 0 0 0 D
B C
\
D这一补,最坏情形就出来了。 高度为
所以顺序存储不省空间,它只是用"编号占位"换来"下标算父子"的便利。 这也解释了堆为什么把"必须是完全二叉树"写进定义——不是为了好看,是为了让顺序存储成立,从而免掉全部指针开销。
链式存储这边有个数字要记住:
三叉链表与一般树的三种存储结构(想看结构定义与对照表就展开)
三叉链表在二叉链表上再加一个 parent 指针:
c
typedef struct TriTNode {
ElemType data;
struct TriTNode *lchild, *rchild, *parent; // 多一个双亲指针
} TriTNode, *TriTree;什么时候必须用三叉链表:需要自下而上回溯的场合。二叉链表里"找结点
一般树的三种存储结构(结点度不固定,不能照搬二叉链表;完整内容见 树与森林):
| 存储方式 | 结点结构 | 找双亲 | 找孩子 | 适用场景 |
|---|---|---|---|---|
| 双亲表示法 | data + parent(双亲下标) | 以"找双亲"为主,如并查集 | ||
| 孩子表示法 | 每个结点的孩子用单链表串联 | 以"找孩子"为主 | ||
| 孩子兄弟表示法 | firstChild + nextSibling | 不方便 | 第一个 | 树 ↔ 二叉树转换 |
树 ↔ 二叉树的具体转换规则见 树与森林的转换。
考点速记
三条会被反复调用的结论:
对任意二叉树成立,与是否完全、是否满无关;证法是边数双计数, 联立时自动消掉。 - 编号关系(双亲
、孩子 与 )只对完全二叉树成立,一般二叉树套用必错——顺序存储要先补空位,正是为了让它重新成立。 - 二叉链表的空指针域恰有
个( 个域减 条边),这正是线索二叉树要利用的空间。
这一节在真题里被考过的形式(下方「真题练习」逐题对应)。这是整门里出题最密的一节,考法分四类:
① 完全二叉树的结点计数。 两道典型:给"第 6 层有 8 个叶结点"问最多结点数——做法是逐层往下算,前 5 层满(31 个),第 6 层因为下面还有第 7 层所以也满(32 个),其中 8 个是叶子、剩下 24 个各生 2 个孩子,第 7 层最多 48 个,合计 111。给"768 个结点"问叶结点数——直接套
② 顺序存储的两问。 一问"至少要多少存储单元":题面强调"任意一棵高度为 5 且有 10 个结点的二叉树",就是要按最坏形态预留,答案是最大编号
③ 中序序列里的位置关系。 两道都建立在同一条性质上:有两个孩子的结点
④ 形态计数与判断题。 计数题考的是卡特兰数:先序序列固定时不同二叉树的形态数为
易错:把满二叉树的性质套到完全二叉树上。 完全二叉树可以有度为 1 的结点——
为偶数时必然恰有一个。
易错:以为"二叉树的分支结点比叶结点少"。 那只对满二叉树成立。反例是单支链:
个结点里 1 个叶子、 个分支结点。
易错:顺序存储把"存储单元数"当成"结点数"。 单元数取决于最大编号,高度
的最坏情形是 ;也别把它记成 。
易错:由
反求 时只答一个。 与 的完全二叉树叶子数相同。
易错:路径长度与比较次数混用。 前者数边、后者数结点,差 1;WPL 用边数口径,ASL 用结点数口径。
教材出处
- 树的递归定义("树是
个结点的有限集,它或为空树;或为非空树,对于非空树 :有且仅有一个称之为根的结点;除根结点以外的其余结点可分为 个互不相交的有限集,其中每一个集合本身又是一棵树,并且称为根的子树"):严蔚敏《数据结构(C 语言版)》(第 2 版)印刷 p111(5.1.1 节) - 树的基本术语逐条定义:结点的度、树的度、叶子(终端结点)、非终端结点(分支结点、内部结点)、双亲和孩子、兄弟、祖先、子孙、堂兄弟、结点的层次:印刷 p112;树的深度("树中结点的最大层次称为树的深度或高度")、有序树与无序树、森林("
棵互不相交的树的集合;对树中每个结点而言,其子树的集合即为森林"):印刷 p113(5.1.2 节) - 二叉树的递归定义、"二叉树与树的区别主要有两点:每个结点至多只有两棵子树;子树有左右之分,其次序不能任意颠倒",以及二叉树的 5 种基本形态:印刷 p113(5.2 节)
- 二叉树的性质 1、2、3 及其完整证明(含"设
为分支总数, , "的双计数推导):印刷 p118(5.4.1 节) - 满二叉树与完全二叉树的定义、完全二叉树的两个特点、性质 4 及其证明:印刷 p119
- 性质 5(编号与双亲、左右孩子的对应关系)、二叉树的顺序存储结构,以及"深度为
且只有 个结点的单支树需要长度为 的一维数组"这一空间浪费结论:印刷 p120(5.4.2 节) - 二叉链表与三叉链表的结点结构、"含有
个结点的二叉链表中有 个空链域"及其与线索链表的衔接:印刷 p121
相关知识
前序遍历|中序遍历|后序遍历|层序遍历|构造二叉树|线索二叉树(用掉那