树与二叉树的奥秘全解析
目录
树
定义
基本性质
性质一
性质二
性质三
二叉树
定义
基本形态
性质
性质一
性质二
性质三
真题
存储结构
顺序结构
链式结构
遍历
前序遍历
中序遍历
后序遍历
性质
真题
特殊二叉树
满二叉树
完全二叉树
性质
线索二叉树
存储结构
中序遍历线索化
真题
哈夫曼树
计算WPL
定义
构造哈夫曼树
哈夫曼编码
真题
树与二叉树的转换
树转换成二叉树
二叉树转换成树
森林与二叉树的转换
二叉树转换为森林
森林转换为二叉树
遍历
树的遍历
森林的遍历
树、森林与二叉树的遍历关系
树
定义

树是一个或多个结点的有限集合
存在一个称为根的特定结点其余的结点被分为 n 个互不相交的集合 T1, T2, …, Tn, 其中的每个集合都是一棵树。T1, T2, …, Tn称为根节点的子树。
结点:树中的一个独立单元。
结点的度:结点拥有的子树数称为结点的度。
树的度:树内各结点度的最大值。
叶子:度为 0 的结点或终端结点。
非终端结点:度不为 0 的结点。
双亲和孩子:结点的子树的根称为该结点的孩子,相应地,该结点称为孩子的双亲。
层次:结点的层次从根开始定义,根为第一层,根的孩子为第二层,以此类推。
基本性质
性质一
树中所有结点数等于所有结点的度数之和加 1。

例题:

解析:

答案:
B
性质二
性质三
二叉树
定义

二叉树(Binary Tree)是 n(n ⩾ 0 )个结点所构成的集合,它或为空树(n=0),或为非空树。
对于非空树T:
(1)有且仅有一个 称为根的结点
(2)除根结点以外的其余结点分为两个互不相交的子集 T1 和 T2 ,分别称为 T 的左子树和右子树,且 T1 和 T2 本身又都是二叉树。
(3)二叉树每个结点至多只有两棵子树。
(4)二叉树的子树有左右之分,其次序不能任意颠倒。
基本形态

性质
性质一
性质二
深度为 k 的二叉树最多有2 ^ k - 1(k ⩾ 1)个结点。
性质三
对于任何非空的二叉树T,如果叶子结点的个数为n0,而度为2的结点数为n2,则 n0=n2+1
真题
题1

解析

答案
C
题2

解析:

答案:
C
题3

解析:

答案:
A
存储结构
顺序结构

链式结构

typedef char ElemType; typedef struct TreeNode
{ ElemType data; TreeNode *lchild; TreeNode *rchild;
}TreeNode; typedef TreeNode* BiTree;
遍历

前序遍历
先访问根结点,然后访问左分支上遇到的每一个结点,持续这一过程,直到遇到空结点为止。这时,返回到最近的有右孩子的祖先结点,并从该结点的右孩子开始继续遍历。
void preOrder(BiTree T)
{ if (T == NULL) { return; } printf("%c ", T->data); preOrder(T->lchild); preOrder(T->rchild);
}
中序遍历
先访问根结点,向树的左下方移动,直到遇到空结点为止,然后访问空结点的父结点。接着继续遍历该结点的右子树,如果右子树没的子树可以遍历,那么继续遍历上一层最后一个未被访问的结点
void inOrder(BiTree T)
{ if (T == NULL) { return; } inOrder(T->lchild); printf("%c ", T->data); inOrder(T->rchild);
}
后序遍历
从根结点开始先访问结点的左右儿子,再对该结点进行访问。这就意味着结点的儿子将在该结点之前输出。
void postOrder(BiTree T)
{ if (T == NULL) { return; } postOrder(T->lchild); postOrder(T->rchild); printf("%c ", T->data);
}
输出内容:K H D E B I F J G C A
下列树的遍历结果是?

前序:A B D H E I C F G J K
中序:H D B E I A F C J G K
后序:H D I E B F J K G C A
下列树的遍历结果是?

前序:A B D E G H C F I
中序:D B G E H A F I C
后序:D G H E B I F C A
性质
已知前序遍历和中序遍历,可以唯一确定一棵二叉树。
已知中序遍历和后序遍历,可以唯一确定一棵二叉树。
已知前序遍历和后序遍历,是不能确定一棵二叉树的。
例:前序序列是ABC,后序序列是CBA

真题
题1

解析:
D.
A.
B. 
答案:
C
题2

答案:
B
题3


解析:

特殊二叉树
满二叉树

深度为 k 且含有 2 ^ k - 1个结点的二叉树
所有的叶子结点只能出现在最后一层
对于同样深度的二叉树,满二叉树的结点个数最多,叶子结点的数量也是最多的。
如果对满二叉树进行编号,根结点从1开始,从上到,下从左到右,对于编号为i的结点,若存在左孩子, 则左孩子的编号为2i,右孩子为2i+1。
完全二叉树

深度为 k 的、有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应时,称之为完全二叉树。
完全二叉树的特点是:
(1)叶子结点只可能在层次最大的两层上出现;
(2)对任一结点,若其右分支下的子孙的最大层次为 l,则其左分支下的子孙的最大层次必为 l 或 l+1。
没有左子树,不能有右子树,上一层没有铺满,不能有下一层
性质
性质四:具有 n 个结点的完全二叉树的深度为 log2n+1(向下取整)
性质五:如果对一棵有 n 个结点的完全二叉树(其深度为 log2n+1(向下取整))的结点按层序编号(从第 1 层到第 log2n+1 (向下取整层),每层左到右),则对任一结点 i (1 ⩽ i ⩽ n),以下结论成立。
(1)如果 i=1,则结点 i 是二叉树的根,无双亲;如果结点 i>1则其双亲是结点 i/2(向下取整)
(2)如果 2i>n,则结点 i 无左孩子(结点 i 为叶子结点);否则其左孩子是结点2i
(3)如果 2i+1>n,则结点 i 无右孩子;否则其右孩子的结点是 2i+1。
线索二叉树

通过遍历可以得到二叉树的线性排列 ,但这样线性序列只有在遍历时才能得到
将二叉树线索化得到线索二叉树可以解决这个问题
线索化:利用叶节点的空余空间记录前驱、后继

存储结构
typedef char ElemType; typedef struct ThreadNode
{ ElemType data; struct ThreadNode *lchild; struct ThreadNode *rchild; int ltag; int rtag;
}ThreadNode; typedef ThreadNode* ThreadTree;
ltag为 0 时,指向该结点左孩子,为 1 时,指向该结点的前驱
rtag为 0 时,指向该结点右孩子,为 1 时,指向该结点的后继
中序遍历线索化

1. 头结点的 lchild 指向二叉树的根

2. 头结点的 rchild 指向遍历的最后一个结点(指中序遍历的最后一个节点)


真题
题1

解析:
d b c a
答案:
D
题2

解析:
答案:
D
哈夫曼树

路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径
路径长度:路径上的分支数目
树的路径长度:从树根到每一个结点的路径长度之和
结点的权:在实际应用中,给树中的结点赋予代表某种含义的数值
结点的带权路径长度:从该节点到树根之间的路径长度与该结点权的乘积
树的带权路径长度(WPL):树中所有叶节点的带权路径长度之和
计算WPL
WPL = 5 * 1 + 15 * 2 + 40 * 3 + 30 * 4 + 10 * 4 = 315
定义
构造哈夫曼树








哈夫曼编码


对 BADCADFEED 进行编码
哈夫曼编码进制串:1001 01 00 101 01 00 1000 11 11 00
原编码二进制串:001 000 011 010 000 011 101 100 100 011
真题
题1

题2

题3

题4

解析:


答案:
A
树与二叉树的转换
树转换成二叉树

1. 加线,在所有兄弟结点之间加一条线

2. 去线,对树中的每一个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线。

3. 层次调整,以树的根结点为轴心,将整棵数顺时针旋转一定角度,使之层次分明。注意第一个
孩子是二叉树结点的左孩子。兄弟转过来的孩子是结点的右孩子。

二叉树转换成树

1. 加线,若某个结点的左孩子存在,则将这个左孩子的所有右孩子结点都作为此结点的孩子,将该结点与这些右孩子结点用线连起来。

2. 去线,删除二叉树中所有结点与其右孩子结点的连线

3. 调整,转一下

森林与二叉树的转换
二叉树转换为森林
第一棵二叉树不动,从第二棵树开始,依次把后一棵二叉树的根结点作为前一棵二 叉树的根结点的右孩子,然后用线连起来



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

拆

二叉树转换成树






遍历
树的遍历

森林的遍历

树、森林与二叉树的遍历关系

