目录
- 树与二叉树的定义和性质
- 二叉树的实现与遍历
- 哈夫曼树
- 树与森林
1.树与二叉树的定义和性质
1.1 树的定义

(1)树是一个或多个结点的有限集合(上图中一个圈代表一个结点),存在一个称为根的特定结点(A是根结点)。其余的结点被分为n个互不相交的集合T1,T2,···Tn,其中的每个集合都是一棵树,T1,T2,···Tn称为根结点的子树。
比如:B、C、D是A的子树,但E、F不是A的子树,且B、C、D各自又都是一颗独立的树。
(2)树中的概念:
- 结点:树中的一个独立单元(上图中一个圈代表一个结点)。
- 结点的度:结点拥有的子树数称为结点的度(A的度是3)。
- 叶子结点(简称叶子):度为0的结点或终端结点。
注:度为0的结点叫做终端结点。 - 非终端结点:度不为0的结点。
- 双亲和孩子:结点的子树的根称为该结点的孩子,相应的,该结点称为孩子的双亲。
注:一般不说A结点是BCD的双亲结点,而是说A是BCD的父节点。 - 层次:结点的层次从根开始定义,根为第一层,根的孩子为第二层,以此类推。
- 树的度:树内各结点度的最大值。
- 兄弟结点:同一个父节点,生出来的所有子节点,互相称为兄弟结点。
1.2 树的基本性质
(1)性质1:
-
树中所有结点数等于所有结点的度数之和加1。
-
例题:

(2)性质2:

(3)性质3:

注:性质2,3了解即可。
1.3 二叉树的定义
- 定义:

注:二叉树最多有两个分叉,可以有1个分叉,也可以有0个分叉。
- 二叉树基本形态:

1.4 二叉树的性质
(1)性质1:

(2)性质2:

(3)性质3:

1.5 特殊二叉树
(1)满二叉树


(2)完全二叉树
①口诀判断:左子树不是满二叉树,不能有右子树。上一层没铺满,不能有下一层。
注:完全二叉树中叶子结点只可能出现在倒数第一层和倒数第二层。
注:如果一个完全二叉树有偶数个结点,则一定在最后一层缺一个叉。
②例题1

③例题2

2.二叉树的实现与遍历
2.1 二叉树的存储结构---链式结构


注:顺序结构存储不是有多少个结点就有多少个存储单元,必须按照满二叉树来预留存储单元。比如说任意一颗高度为5且有10个结点的二叉树,按顺序结构存储,每个结点占一个存储单元,则至少要31个存储单元。
2.2 二叉树的遍历---前序遍历

注:前序遍历的意思就是将printf放在两个preOrder的前面,同理,中序遍历就是将printf放在两个preOrder的中间,后序遍历就是将printf放在两个prreOrder的后面。
如果二叉树是这样:

输出结果为:A B D H K E C F I G J。(形象理解见b站课程)
2.3 二叉树的遍历---中序遍历


输出结果为:H K D B E A I F C G J。
2.4 二叉树的遍历---后序遍历


输出结果:K H D E B I F J G C A。
2.5 根据遍历结果推导二叉树
已知一颗二叉树的前序遍历为ABCDEF,中序遍历为CBAEDF,则一定可以判断出:
A为根结点,且CB在A的左边,EDF在A的右边。
(1)已知前序遍历和中序遍历,可以唯一确定一颗二叉树。
(2)已知中序遍历和后序遍历,可以唯一确定一颗二叉树。
(3)已知前序遍历和后序遍历,是不能确定一颗二叉树的。
例:

2.6 线索二叉树
(1)线索化:利用结点的空余空间记录前驱、后继。
(2)线索二叉树可以不用递归,像遍历链表一样直接遍历整棵树。
(3)从代码实现的角度,如何判断结点中存放的是孩子还是线索?

(4)存储结构

(5)中序遍历线索化

注:这里第一个结点和最后一个结点是指中序遍历的第一个结点和最后一个结点。
(6)例题

3.哈夫曼树
(1)概念:

①路径:从树中的一个结点到另一个结点之间的分支构成这两个结点之间的路径。
②路径长度:路径上的分支数目(a<80到a<60的路径长度为2)。
③树的路径长度:从树根到每一个结点的路径长度之和。
④结点的权:在实际应用中,给树中的结点赋予代表某种含义的数值。
⑤结点的带权路径长度:从该结点到树根之间的路径长度与该结点权的乘积。
⑥树的带权路径长度(WPL):树中所有叶结点的带权路径长度之和。
注:WPL的值越小,这棵树的效率越高。
(2)定义:
带权路径长度(WPL)最小的二叉树称为哈夫曼树。
(3)构造哈夫曼树
①先把有权值的叶子结点按照从小到大的顺序排列成一个有序序列。

②取两个最小权值的结点作为一个新结点 N 1 N_1 N1的子结点。

注: N 1 N_1 N1的大小为两个子结点的大小之和。
③将结点 N 1 N_1 N1替换刚刚取出的两个结点,并加入到有序序列中。

注: N 1 N_1 N1也要按照从小到大的顺序进行排列。
④重复步骤②,将 N 1 N_1 N1和"及格"作为一个新结点 N 2 N_2 N2的子结点。

⑤将结点 N 2 N_2 N2替换刚刚取出的两个结点,并加入到有序序列中。

⑥重复步骤②,将 N 2 N_2 N2和"良好"作为一个新结点 N 3 N_3 N3的子结点。
⑦将结点 N 3 N_3 N3替换刚刚取出的两个结点,并加入到有序序列中。

⑧重复步骤②,将 N 3 N_3 N3和"中等"作为一个新结点 N 4 N_4 N4的子结点。

即构造出了哈夫曼树。
(4)哈夫曼编码
①假设六个字母的使用频率为:

②构造哈夫曼树:

③左边分叉放上0,右边分叉放上1(左0右1)

D的哈夫曼编码为00,C的哈夫曼编码为101。
④

可以看出哈夫曼编码效率更高。
(5)例题
①

4.树与森林
4.1 树与二叉树的转换
(1)树转化为二叉树:
①加线:
在所有兄弟结点之间加一条线。
②去线:
对树中的每一个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。

③调整层次:
以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之层次分明。转过来后:第一个孩子都变为左孩子,第一个孩子的兄弟都顺时针旋转45度。

(2)二叉树转化为树:
①加线:
若某个结点的左孩子存在,则将这个左孩子的所有右孩子都作为该结点的孩子。将该结点与这些右孩子用线连起来。

②去线:
删除二叉树中所有结点与其右孩子结点的连线。

③调整:

4.2 森林与二叉树的转换
注:多棵树即为森林(至少两棵)。
(1)森林转换为二叉树
①先将每棵树转化为二叉树。

②组合:
第一颗二叉树不动,从第二棵树开始,依次把后一颗二叉树的根结点作为前一棵二叉树的根结点的右孩子,然后用线连起来。

(2)二叉树转化为森林
①从根结点开始,若右孩子存在,则把右孩子结点的连线删除。

②将二叉树拆开。

③将拆开的二叉树转换为树。

4.3 树的遍历

先根和后根就是先访问根还是先访问孩子(规律自己找即可,很简单)。
注:先根遍历、后根遍历和前序遍历、中序遍历结果一样(前提是二叉树是由树转换来的)。
4.4 森林的遍历

前序遍历就是每棵树进行先根遍历,然后组合在一起。中序遍历就是每棵树进行后根遍历,组合到一起。
注:由森林转换来的二叉树所得的前序遍历和中序遍历与森林的前序、中序遍历相同。
4.5 树、森林与二叉树的遍历关系

4.6 层序遍历
(1)代码实现:
c
int maxDepth (TreeNode* root)
{
if (root == NULL)
{
return 0;
}
int depth = 0;
Queue* q = initQueue();
equeue(q, root);
while (!isEmpty(q))
{
int count = queueSize(q);//queueSize用来返回当前队列中元素的数量。
while (count > 0)
{
TreeNode* curr;
dequeue(q, &curr);
if (curr->lchild != NULL)
{
equeue(q, curr->lchild);
}
if (curr->rchild != NULL)
{
equeue(q, curr->rchild);
}
count--;
}
depth++;
}
return depth;
}
4.7 树、森林、二叉树 - 真题解析
(1)

思路:找一个最方便进行转换成二叉树的树,易得答案。

(2)

①第一问:

注:这里用到了总节点数=总度数+1。
②第二问:
- 最多


- 最少
