树
定义
- 结点的度:一个结点的孩子个数
- 树的度:树中结点最大度数
- 分支节点:度大于0的结点
- 叶子结点:度为0的结点,也称终端结点
- 结点深度:从根节点开始自顶向下组成累加
- 结点高度:从叶节点开始自底向上逐层累加
性质
- 树中结点数等于所有结点度数加1
!一颗有n个结点的树有n-1条边
- 度为 \(m\) 的树中第 \(i\) 层上至多有 \(m^{i-1}\)个结点
- 高度为 \(h\) 的 \(m\) 叉树至多有 \((m^{h-1})(m-1)\)个结点
- 具有 \(n\) 个结点的 \(m\) 叉树的最小高度为 \(\lceil log_m(n(m-1)+1)\rceil\)
存储结构
顺序存储
- 双亲表示法(数组): 采用一组连续的空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲结点在数组中的位置
!该方法可以根据parent值查找双亲节点,时间复杂度为\(O(1)\)

| data | parent | |
|---|---|---|
| 0 | R | -1 |
| 1 | A | 0 |
| 2 | B | 0 |
| 3 | C | 0 |
| 4 | D | 1 |
| 5 | E | 1 |
| 6 | F | 3 |
| 7 | G | 6 |
| 8 | H | 6 |
| 9 | K | 6 |
链式存储
- 孩子表示法: 将每个结点的孩子结点都用单链表链接起来形成一个线性结构,此时n个结点就有n个孩子链表

- 孩子兄弟表示法: 每个结点包括三部分,结点值,指向第一个孩子结点的指针,指向结点下一个兄弟节点的指针

二叉树
存储结构
顺序存储结构
- 使用数组存储
- 添加一些不存在的空结点,让其每个结点与完全二叉树上的结点相对照,再存储到一维数组里去(用空值表示对应的完全二叉树中所缺的值)
链式存储结构
- 使用二叉链表存储
- 用链表节点来存储二叉树中的结点,二叉链表由至少包含三个域:数据域 data,左指针域 lchild 和右指针域 rchild
!树和线性表都可为空,图不可为空,图至少含有一个顶点
!使用二叉链表存储森林时应先将其转化为二叉树
性质
- 二叉树中叶结点数目等于度为 2 的结点数目加 1
- 二叉树中第 \(i\) 层上之多有 \(2^{i-1}\) 个结点
- 高度为 \(h\) 的二叉树中至多有 \(2^h-1\)个结点
- 具有 \(n\) 个结点的完全二叉树最小高度为 \(\lfloor log_2n\rfloor+1\)或 \(\lceil log_2(n+1)\rceil\)
特殊二叉树
满二叉树
- 含有 \(2^h-1\) 个结点的二叉树称为满二叉树
完全二叉树
- 高度为h,有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为 1~n 的结点一一对应时,称为完全二叉树
- 完全二叉树和相同高度的满二叉树的结点按层序编号是一一对应的
!满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树
!完全二叉树可以当作是一个堆
特点
- 叶子结点只可能在层次最大的两层中出现(最下两层),且对于最大两层的叶子结点,都依次排列在该层的最左边的位置上
- 度为1的结点要么为 0 个要么为 1 个,且度为 1 的结点只有左孩子
!哈夫曼树不存在度为1的结点
- 同样结点的二叉树,完全二叉树的深度是最小的
- 若一个有 n 个结点的树具有最小深度,则它一定为完全二叉树而不是满二叉树
!一棵只有 1 个度为 1 的完全二叉树,叶结点和非叶结点个数相同;若度为 1 的结点为 0 个,则非叶结点个数等于叶结点个数 - 1
- 当完全二叉树的最右非终结结点子树个数为 1 时,非叶结点数目 = 叶节点;当完全二叉树的最右非终结结点子树个数为 2 时,非叶结点数目 = 叶结点 + 1
树的遍历
二叉树的递归遍历
- 按某种次序依次访问树中结点,使得每个结点均且仅被访问一次
- NLR:先序遍历(先根遍历),LNR:中序遍历,LRN:后序遍历(后根遍历)
!递归效率不如非递归高,但能将问题转化为相同属性但规模小的问题
- 先序遍历: 根->左->右,打印输出在前
- 当遇到 NULL 时不执行递归式,它是递归边界,此时便开始归,即返回它的上一层,左孩子遍历结束,遍历该边界上一层的右孩子
- 右孩子的左右均是NULL逐级返回上一层直到上一层开始遍历其右孩子,为 NULL 再返回至上一层开始遍历其右孩子,之后操作一致
- 中序遍历: 左->根->右,打印输出在中
- 后序遍历: 左->右->根,打印输出在后
!若只知道先序序列和后序序列则不能唯一确定一棵二叉树
!树的后根遍历序列和其对应的二叉树的中序遍历序列相同
- 遍历结果相同的性质:
- 先序遍历和后序遍历结果相同的二叉树为空树或只有根结点的二叉树
- 先序遍历和中序遍历结果相同的二叉树为空树或缺左子树的单支二叉树
- 中序遍历和后序遍历结果相同的二叉树为空树或缺右子树的单支二叉树
- 遍历结果相反的性质:
4. 先序遍历和后序遍历结果相反的二叉树为高度等于结点数的单枝树
4. 先序遍历和中序遍历结果相反的二叉树为:
1. 空树或只有一个结点
1. 若非空树,则为没有右孩子的单枝树
E.G.
已知一棵二叉树的后序序列为 DABEC,中序序列为 DEBAC,则先序序列为 ( )
[A] ACBED [B] DECAB
[C] DEABC [D] CEDBA
解:
- 由后序序列知该树的根节点为 C,并由此结合中序序列知:以 C 为根节点的左子树有4个结点 DEBA,没有右子树结点;提取左子树的 DABE,结合后序序列知:该子树根节点为 E,并结合中序序列知:以 E 为根节点的子树有左子树结点 D 和右子树结点 BA;提取右子树结点AB作为新的子树结合后序序列知 B 为新子树根节点,并结合中序序列知 A 为右子树节点,即树构造完成。选 [D]
已知一棵二叉树的先序遍历结果为 ACDEF,中序遍历结果为 CBAEDF,则后续遍历结果为 ( )
[A] CBEFDA [B] FEDCBA
[C] CBEDFA [D] 不确定
解:
- 由上题知先序序列第一个结点确定该树的根节点,并根据上题方法结合中序序列逐步确定左右子树结点。再提取左右子树结点根据先序序列再确定该子树根节点,依次递推。选 [A]
已知一棵二叉树的层次序列为 ACBDEF,中序序列为 BADCFE,则先序序列为 ( )
[A] ACBEDF [B] ABCDEF
[C] BDFECA [D] FCEDBA
解:
- 举一反三得答案为 [B]
!一棵二叉树的先序序列的第一个值确定该树的根结点,后序序列的最后一个值确定该树的根节点,中序序列确定某个结点的左右子树结点
**
二叉树的非递归遍历
- 先序遍历(栈实现)
- 栈不为空时,输出指针所指结点(从头开始时为根节点)并将其压入栈,左孩子不为空时访问左孩子并输出该结点,将该结点压入栈
- 当左孩子为空时(指针所指的lchlid为NULL),将上一层结点弹出并将指针指向该结点,再将指针指向其右孩子,若不为空则入栈
- 当该结点左右孩子均为NULL时,弹出栈中结点并将指针指向该结点,后续操作相同直到树为空
void PreOrder(BitTree T) {
InitStack(S); // 初始化栈
BitTree p = T; // 初始化遍历指针p
while (p || !IsEmpty(S)) { // 栈或p不为空时循环
if (p) {
visit(p); // 打印结点
Push(S, p); // 结点入栈
p = p -> lchild; // 将指针转到左孩子
} else {
Pop(S, p); // 结点出栈
p = p -> rchild; // 将指针转到右孩子
}
}
}
- 中序遍历(栈实现)
void InOrder(BitTree T) {
InitStack(S);
BitTree p = T;
while (p || !IsEmpty(S)) {
if (p) {
Push(S, p);
p = p -> lchild;
} else {
Pop(S, p);
visit(p); // 打印出栈结点
p = p -> rchild;
}
}
}
- 层次遍历(队列实现)
思路:访问一个结点后,把它的孩子结点存起来,先访问的结点它的孩子也会被优先访问
void LevelOrder(BoeTree T) {
BitTree p = T;
InitQueue(Q);
EnQueue(Q, p); // 根结点入队
while(!IsEmpty(Q)) {
DeQueue(Q, p); // 根结点出队
visit(p); // 打印结点
if (p -> lchild != nullptr)
EnQueue(p -> lchild); // 左子树不为空入队
if (p -> rchild != nullptr)
EnQueue(p -> rchild); // 右子树不为空入队
}
}
线索二叉树
- 为了加快查找某一结点的前驱与后继的速度
- 是一种物理(存储)结构,而不是逻辑结构
- 指向结点前驱和后继的指针称为线索
- 对二叉树以某种次序遍历使其变为线索二叉树的过程称作线索化

- 线索二叉树在二叉链表的结构基础上增加两个标志位ltag和rtag
ltag == 1:lchild指向该结点前驱ltag == 0: lchild指向该节点左孩子rtag == 1:rchild指向该节点后继rtag == 0:rchild指向该节点右孩子
- 在含有n个结点的二叉树中,总共有 2n 个指针,有 n+1 条线索,有 n+1 个空指针,有 n-1 个使用的指针(除根节点外,每个节点都有且仅有一个射向自己的分支,所以 n 个节点的二叉树,需要 n-1 个指针域,空余 n+1 个)
- 非空二叉树中序线索化后空指针的个数为 2
- 左子树为空的二叉树先序线索化之后空的指针(链域)个数为 2
- 左右子树均不为空的二叉树先序或后序线索化后空的指针(链域)个数为 1
!二叉树线索化后仍不能有效解决的问题是先序线索树求先序前驱和后序线索树求后序后继
!后序线索树的遍历仍需要栈的支持,但若一棵单枝树只有左孩子则不需要用栈
考点
- 给一个树,找出对应的某种次序遍历的线索二叉树(选择题)
- 求二叉树中某一结点某种次序的线索化后的前驱或后继结点
- 如何区分指针是指向左孩子还是前驱或指向右孩子还是后继
树的转换
树转换为二叉树
- 将兄弟节点用线串起来并将兄弟结点作为他们第一个结点的右孩子
- 对于每个结点只保留它与原先的兄弟结点的连线,其余全部删除
!在树转化为二叉树的过程中,所有树中非叶结点在转化后均为二叉树中有左孩子的结点,叶结点转化为无左孩子的结点

二叉树转换为树
- 若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来
- 删除原二叉树中所有结点与其右孩子结点的连线

森林转化为二叉树
- 将森林中每棵树都转换成二叉树
- 将第二棵树作为第一棵树的根结点的右子树,将第三棵树作为第二棵树的根结点的右子树,以此类推

二叉树转化为森林
前提:这棵树的根节点有右孩子
- 从根节点开始, 若右孩子存在则把与右孩子结点的连线删除
- 分离后的二叉树若其根节点的右孩子存在,则将连线删除,以此类推
- 将每棵分离后的二叉树转换为树

哈夫曼树
定义
权与带权路径长度
- 权:树中结点常常被赋予一个表示某种意义的数值,称为该节点的权
- 带权路径长度 (WPL):从树的根节点到所有结点的路径长度与该结点上权值的乘积之和
哈夫曼树
- 在含有 n 个带权叶结点的二叉树中,其中带权路径长度最小的树称为哈夫曼树(最优二叉树)
!折半查找树称为最优二叉排序树
特点
- 每个初始结点均为叶节点,且权值越小的结点到根节点的路径长度越大
- 构造时共新建了 n-1 个双分支结点,因此哈夫曼树的结点总数为 2n-1
- 哈夫曼树不存在度为 1 的结点
!完全二叉树度为 1 的结点要么为 0 个要么为 1 个
构造过程
!选择俩小造新树,删除俩小添新人
E.G.
- 频率表A: 60, B: 45, C: 13, D: 69, E: 14, F: 5, G: 3
解:
- 找出字符中最小的两个,组成二叉树,并在频率表中删除这两个数,并加入这两个数之和F和G最小,因此如图,从字符串频率计数中删除F与G,并返回G与F的和 8给频率表

- 新频率表为A: 60, B: 45, C: 13, D: 69, E: 14, FG: 8, 最小的是 FG: 8与C: 13,因此如图,并返回FGC的和21给频率表。

- 重复第一步,略



哈夫曼编码
前缀编码
- 若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码(解决二义性问题)
!非前缀编码解码存在二义性
树的编码
- 将转向“左孩子”的边标记为 0,转向右“右孩子”的边标记为 1,如图所示,各字符编码为

A: 10 B: 01 C: 0011
**D: 11 E: 000 F: 00101 **
G: 00100
二叉排序树及平衡二叉树
- 见《查找》

浙公网安备 33010602011771号