Add tree reconstruction to chp5.md.

This commit is contained in:
Shine wOng
2019-06-02 16:46:21 +08:00
parent 0b396e1652
commit a36a30e092

View File

@@ -86,6 +86,8 @@ $$
设当前第k个结点至少有左子树高度为h所以本层在当前结点前面的结点有$k - 2^{h}$个,换算到下一层在当前结点左子结点前面的结点就有$2·(k - 2^{h})$个。本层在当前结点后面的结点有$2^{h+1} - 1 - k$个,这样在当前结点和子结点直接就相隔了$2·(k - 2^{h}) + 2^{h+1} - 1 - k = k - 1$个路人,所以当前结点的左结点位于第$2k$个,右节点在第$2k + 1$个。
通过这种性质,我们就可以方便地存储完全二叉树,例如可以直接使用一个`Vector`来实现对完全二叉树的存储了。
> 哈夫曼编码(Huffman)
上面的最优编码树其实存在一些问题。它考虑了叶节点的平均深度,然后并没有考虑各个字符出现的频率往往是不一样的。例如英文字母`t`会比`j`出现的频率高出很多。在这样的情况下,最优编码树显然并不是最优,这让我想起之前的二分查找,看起来是平衡的,实际上却并不平衡,而后面的斐波拉契查找,看起来是不平衡的算法,实际上却是平衡的。这里也是同样的情况。
@@ -306,3 +308,17 @@ void postOrder_It(BinNodePosi(T) x, VST &visit){
### 层序遍历
层序遍历是按照自上而下,自左而右的次序遍历,不涉及任何的逆序,延迟缓冲之类的操作。因此就不使用栈了,转而使用队列。代码略过不表。
## 树的重构
> 通过中序遍历 + 先序(/后序)遍历重构二叉树。
这简直就是废话。简单说来就是通过先序(/后序)遍历的头部(/尾部)找到根结点,然后去中序遍历中找到这个点,从而就可以通过中序遍历中的这个点把左子树和右子树分开。然后这个过程递归的进行下去。
> 是否可以通过先序遍历 + 后序遍历重构二叉树?
可以先分析一下。假设现在同时有先序遍历序列和后序遍历序列,先序遍历的第一个结点是头部,第二个结点是左子树的根节点。通过这个左子树的根节点在后序遍历序列中进行查找,左子树根节点在后序遍历序列中左子树序列的最后,从而可以将左子树和右子树给分开,然后递归地进行下去。
一切都挺完美的,但是我们来考虑一下特殊情况。假设某棵子树只有左子树而没有右子树,那么其先序遍历序列是`Root + LeftRoot + Left`,后序遍历序列是`Left + LeftRoot + Root`。再假定某棵子树只有右子树而没有左子树,那么其先序遍历序列是`Root + RightRoot + Right`,后序遍历序列是`Right + RightRoot + Root`。可以注意到,两种情况下的先序遍历与后序遍历序列是相同的,就是说通过这样的遍历序列,我们无法判断当前结点的唯一子树是左子树还是右子树,从而无法重构二叉树。
通过上面分析也可以看出,在一定条件下,还是可以通过先序遍历序列和后序遍历序列来重构二叉树的,只需要不发生上面提到的某个结点只有一个孩子的情况就可以了。所以,如果二叉树是一棵真二叉树,即只存在度为零或为二的结点,不存在度为一的结点,则可以通过先序 + 后序来实现树的重构。