6.树与二叉树
二叉树的定义、性质和存储结构
定义
(见教材 P121 )
性质
第 \(i\) 层至多 \(2^{i-1}\) 个结点
深度为 \(k\) ,至多 \(2^{k} - 1\) 个结点
设 \(n_0\) 为终端(叶子)结点个数, \(n_2\) 为度为 2 的结点个数,则 \(n_0 = n_2 +1\)
具有 \(n\) 个结点的完全二叉树(每一个结点都与深度为 \(k\) 的满二叉树中的编号从 1 至 \(n\) 的结点一一对应)的深度为 \(\lfloor log_{2}n\rfloor + 1\) (普通二叉树不一定有此性质,下同)
具有 \(n\) 个结点的完全二叉树,按层序编号,对任意结点 \(i\) ,有
条件 | 结论 | 否则 |
---|---|---|
\(i = 1\) | \(i\) 是二叉树的根,无双亲 | PARENT( \(i\) ) = \(\lfloor i/2\rfloor\)(\(i>1\)) |
\(2i>n\) | 结点 \(i\) 无左孩子(为叶子节点) | LCHILD( \(i\) ) = \(2i\) |
\(2i+1>n\) | 结点 \(i\) 无右孩子 | RCHILD( \(i\) ) = \(2i+1\) |
存储结构
顺序存储结构
1 | // 顺序存储结构 |
链式存储结构
1 | // 链式存储结构 |
线索存储
1 | // 二叉线索存储结构 |
遍历二叉树
(以链式存储的二叉树为例,顺序存储的二叉树的对应操作只需将相应的找左右孩子部分依据前述性质修改即可)
先序遍历
若二叉树不空,则
- 访问根结点
- 先序遍历左子树
- 先序遍历右子树
- 递归实现
1 | void PreOrderTraversal( BiTree bt ) |
- 非递归实现(★)
1 | // 关键是利用栈暂存右孩子。 |
- 通过先序遍历,建立二叉树
1 | void CreateBiTree(BiTree *T) |
中序遍历
若二叉树不空,则
- 中序遍历左子树
- 访问根结点
- 中序遍历右子树
- 递归实现
1 | void InOrderTraversal( BiTree bt ) |
- 非递归实现(★)
1 | // 在遍历左子树之前,先把根结点入栈,当左子树遍历结束后,从栈中弹出,访问,再遍历右子树 |
### 后序遍历
若二叉树不空,则
- 后序遍历左子树
- 后序遍历右子树
- 访问根结点
- 递归实现
1 | void PostOrderTraversal( BiTree bt ) |
- 非递归实现(★)
1 | // 设定一个指针,指向 最近访问过的结点。 |
层次遍历
1 | // 先将根结点入队,然后出队,访问该结点, |
线索二叉树
- 经常遍历或查找结点在遍历所得线性序列中的前驱和后继,则应使用线索链表作为存储结构。
遍历线索二叉树
教材和参考书只给出了中序
中序
1 | Status InOrderTraverse_Thr(BiThrTree T,Status(*Visit)(TElemType)) |
二叉树的线索化
中序
1 | Status InOrderThreading(BiThrTree *Thrt,BiThrTree T) |
1 | void InThreading(BiThrTree p) |
树的定义和存储结构
定义
(见教材 P118 )
存储结构
双亲表示法
- 便于找爹(常数时间内),不易找孩子(可能需要遍历整个树)
1 | // 双亲存储表示 |
孩子表示法
- 便于找孩子,不易找爹
1 | // 孩子链表存储表示 |
变种:带双亲的孩子链表
1 | // 孩子链表存储表示 |
孩子兄弟表示法
- 便于实现各种操作
1 | // 孩子-兄弟(二叉链表)存储表示 |
赫夫曼编码
- 存储结构
1 | /* 赫夫曼树和赫夫曼编码的存储表示 */ |
- 求赫夫曼编码
1 | void HuffmanCoding(HuffmanTree *HT,HuffmanCode *HC,int *w,int n) |
1 | void HuffmanCoding(HuffmanTree *HT,HuffmanCode *HC,int *w,int n) |
解题技巧小结及结论补充
- 求解树结点与度之间的关系有(★★★★★)
- 总结点数 = \(n_0 +n_1+n_2+\cdots+n_m\) (其中 \(n_i\) 表示度为 \(i\) 的结点个数)
- 总分支数 = \(1n_1+2n_2+\cdots+mn_m\)
- 总结点数 = 总分支数 + 1
- 完全二叉树的一个重要特征:若有度为 1 的结点,则只可能有一个,且该结点只有左孩子而无右孩子
- 在含有\(n\) 个结点的二叉链表中,含有\(n+1\) 个空链域
- 由遍历序列构造二叉树(★★★★★)
- 先序 + 中序(唯一确定)
- 确定步骤
- 先序第一个为根,找其在中序中的位置,这个位置的左边为左子树的中序序列,右边为右子树中序序列
- 根据这上述两个子序列,找到在先序中的对应子序列。左子序列的第一个元素为左子树的根,右子序列的第一个元素为右子树的根。这样递归的找下去,即可
- 举例说明:
- 先序:A B C D E F G
- 中序:C B E D A F G
- A 为根,C B E D 为左,F G 为右
- C B E D 在先序中为 B C D E,B 为左子树的根;B 中序分割,其左为 C,右为 E D;F G 在先序中为 F G,F 为右子树的根;F 中序分割,其左为空,其右为 G
- 以此类推···
- 确定步骤
- 中序 + 后序(唯一确定)
- 确定步骤
- 后序最后一个为根,找其在中序中的位置,这个位置的左边为左子树的中序序列,右边为右子树中序序列
- 根据这上述两个子序列,找到在后序中的对应子序列。左子序列的最后一个元素为左子树的根,右子序列的最后一个元素为右子树的根。这样递归的找下去,即可
- 确定步骤
- 层序 + 中序(唯一确定)
- 确定步骤
- 层序第一个为根,找其在中序中的位置,这个位置的左边为左子树的中序序列,右边为右子树中序序列
- 当上述左右子树的中序序列都非空时,层序遍历第二个为左子树的根,第三个为右子树的根;当有一个子树为空时,层序第二个为对应非空子树的根;根据根在中序中找其左右子树。以此类推,即可。
- 确定步骤
- 先序 + 后序(不行)
- 但可确定二叉树中结点的祖先关系:当先序序列为 XY ,后序序列为 YX 时,则 X 为 Y 的祖先。
- 解题时,可任画一个满足的二叉树,对选项进行判断
- 先序 + 中序(唯一确定)
- 二叉树的先序、中序、后序序列,叶子结点的先后顺序完全相同
- 先序遍历和中序遍历的关系:先序序列为入栈次序,中序序列为出栈次序。对于 \(n\) 个不同元素进栈,出栈序列的个数为\(\frac{1}{n+1}C_{2n}^{n}\)
- 线索二叉树是一种物理结构
- 后序线索二叉树的遍历仍需要栈的支持(★★★★★,加深对线索二叉树和二叉树遍历的理解)
- 遍历不需要栈的关键:从当前访问的结点开始,通过左右指针,能够找到下一个结点
- 叶子结点:其左右指针都用于线索,故一定能找到下一个结点
- 非叶子节点:
- 先序情况
- 当前访问结点的左子树非空,下一个节点为当前左子树的根
- 当前访问结点的左子树为空(意味着右子树不空,否则该节点为叶子节点),下一个节点为当前右子树的根
- 中序情况
- 找当前结点右子树的最左结点
- 后序情况
- 一个特例:设根节点的右子树的根的左右子树非空(即根的右子树的根的两个指针域全部用于指向其左右孩子),当遍历到根结点的右子树的根时,下一个应该遍历的应该是根结点,而此时无法通过当前节点的左右指针找到该根结点,故后序线索二叉树的遍历需要栈。
- 先序情况
- 在 n 个结点的树中,有n-1 条边。那么对于每棵树,其结点数比边数多一。森林中,结点数比边数多 x ,则有 x 棵树。
参考资料
- 数据结构(C语言版).严蔚敏等
- 2020年数据结构考研复习指导.王道论坛