导言
在上一篇博客《[面试算法]Python实现二叉树三种遍历的递归与非递归形式》当中,我们详细讨论了二叉树深度优先遍历的各种方式。一道常见的面试题是,如何根据三种遍历的结果,还原出二叉树,或者说进行二叉树重建?还是拿是上一篇博客的二叉树举例,二叉树的示意图及其三种遍历的结果都贴在下面:
首先,根据某一种遍历的结果可以还原出二叉树吗?比如,仅仅有前序遍历的结果,我们只能知道A
是二叉树的根节点,但是除此之外,就什么也不知道,比如下面这棵二叉树,其前序遍历的结果,和上面的二叉树前序遍历结果是一样的:
因此,仅仅根据前序遍历的结果无法还原出唯一的二叉树。仅仅根据中序遍历的结果,我们什么都不知道,因为D B G E A C F
可能对应这样一棵二叉树:D (B G E A C F)
,其中D
是根节点,左子树为空,(B G E A C F)
为右子树,这也满足上述中序遍历的结果。根据后序遍历,同样不能唯一还原出二叉树,根据后序遍历D G E B F C A
我们只能知道:A
是根节点,其他的什么都不知道。所以,我们至少需要两种遍历结果,才能进行二叉树重建。
如果有了前序遍历和后序遍历的结果,我们可以还原出唯一的二叉树吗?不能。
已知前序遍历结果A B D E G C F
和后续遍历结果D G E B F C A
,由此可知,A
是根节点,那么去掉A
之后分别是:B D E G C F
和D G E B F C
,而这两个序列中,前者的第一个元素B
不等于后者的最后一个元素C
,说明这两个前序遍历和后序遍历,属于两棵子树,不属于一棵子树。
我们看一下:
前序:B D E G C F
后序:D G E B F C
可以肯定地是,B
是一棵子树的根节点,C
是另一棵子树的根节点。逐个扫描发现,B D E G
和D G E B
应当属于一棵子树,因为这两个序列中:前者的首元素=后者的尾元素,符合前序和后序遍历的特点。类似地,C F
和F C
属于同一棵子树。并且B D E G
应当属于左子树,因为在前序遍历中,这个序列在C F
的前面。看到这里,感觉有点希望可以将二叉树还原了,然而问题现在来了。
既然C F
和F C
是根节点A
的右子树的前序和中序遍历结果,那么这棵子树当中C
一定是根节点,那么F
节点是属于左子树还是右子树呢?如果F
是右子树,那么左子树为空,符合前序和中序遍历结果,也和原二叉树相同。如果F
属于左子树,也符合前序和中序遍历结果。至此,出现了不能唯一确定的问题,在B D E G C F
当中也有类似的问题。
因此,根据前序+后序遍历结果无法重建二叉树。剩下只有两种可能:前序+中序、后序+中序,这两种都可以进行二叉树重建。
根据前序遍历+中序遍历还原二叉树
根据前序遍历结果A B D E G C F
和中序遍历结果D B G E A C F
可知,A
是根节点,而中序遍历中,根节点可以分割左右子树,说明中序遍历当中,D B G E
是左子树,C F
是右子树。无论是什么遍历,某棵子树的节点个数总是相等的,因此根据左子树的中序遍历D B G E
,可以分割出这棵子树的前序遍历结果为B D E G
。右子树类似。
因此:
左子树的前序+中序结果为:B D E G
和D B G E
。
右子树的前序+中序的结果为:C F
和C F
。
剩下的事情,就是采用同样的方法了。
先来看左子树,根据前序遍历B D E G
可知,B
一定是根节点,放到中序遍历当中,可知分割的左右子树是D
和G E
。因此,B
的左子树对应着前序+中序结果为:D
和D
,只有一个节点,左子树重建完成。B
的右子树对应着前序+中序结果为:G E
和E G
,有两个节点,类似地,可知G
是根节点,E
是左子树,右子树为空,重建完成。
对于右子树的前序+后序:C F
和C F
,可知C
是根节点,F
属于右子树,右子树只有一个F
节点,左子树为空,重建完成!
上述过程是递归的,因此可以采用递归程序完成。递归的方法是:
- 根据前序遍历确定根节点
root
,根据root
在中序遍历当中的位置分割左右子树对应的中序遍历结果; - 根据左右子树中序遍历中节点的个数这个信息,结合前序遍历,确定左右子树的前序遍历结果;
- 现在知道左子树的前序+中序,可以进行递归;也知道右子树的前序+中序,可以进行递归;
- 递归的边界:如果某棵子树的节点个数为0,这棵子树重建完成,其根节点就是空,触及递归边界。
根据前序遍历+中序遍历还原二叉树的Python代码如下:
# 全局变量记录遍历的结果
result = []
# 前序
def dfs_before(root):
if root == None: # 遇到None,说明不用继续搜索下去
return
result.append(root)
dfs_before(root.left)
dfs_before(root.right)
# 中序遍历
def dfs_middle(root):
if root == None:
return
dfs_middle(root.left)
result.append(root)
dfs_middle(root.right)
# 后序遍历
def dfs_after(root):
if root == None