前一篇文章描述了二叉树的遍历,这一章解决的问题是:当我们仅仅知道遍历结果的时候,如何恢复二叉树?
一、什么样的遍历结果可以恢复出唯一的二叉树?
通过前一章我们知道:二叉树前序遍历的第一个节点和后序遍历的最后一个节点一定是二叉树的根,二叉树中序遍历的情况下,根节点的左侧节点全部在左子树上,右节点全部在右子树上。因此我们可以用这样的递归逻辑写代码:
寻找并插入根节点(树的指针,中序结果,前序/后序结果)
{
根据前序的第一个节点/后序的最后一个节点找到当前树的根
插入到树的当前位置
根据根在中序结果的位置分离左右子树
在前序/后序结果中找出左右子树
将树的当前位置更改到左子节点
寻找并插入根节点(树的当前位置,中序结果(左子树),前序/后序结果(左子树))
将树的当前位置更改到右子节点
寻找并插入根节点(树的当前位置,中序结果(右子树),前序/后序结果(右子树))
}
因此,针对中序+前序,或者针对后序+中序,我们都可以恢复出唯一的二叉树。
而中序+后序的情况呢?
观察下面的两棵树:
A
\
B
\
C
A
/
B
\
C
这两棵树的前序结果都是A B C 后序结果都是C B A
综上所述,前序+后序不一定能恢复出唯一的二叉树。
那么,有没有前序+后序可以恢复出唯一的二叉树的情况呢?答案是:有。
当且仅当二叉树中所有节点的叶子节点为0个或2个时,前序+后序可以恢复出唯一的二叉树。
下面进行分析:
假设某棵树的左子树右子树均存在:
对于前序有:根节点 左子树的根节点 左子树的其他部分 右子树
对于后序有:左子树 右子树的其他部分 右子树的根节点 根节点
斜体部分是我们可以确定的信息。
这时,我们事实上可以同时知道根节点和左右子树的根节点,同时将左右子树分割出来。但是,如果某棵树只有左子树或只有右子树比如:
对于前序有:根节点 子树的根节点 子树的其他部分
对于后序有:子树的其他部分 子树的根节点 根节点
斜体部分是可以确定的信息。这时,我们虽然知道根节点和子树的根节点,并且可以分割出子树,但是我们不知道子树究竟是左子树还是右子树,这才是前序+后序不能恢复出唯一一颗二叉树的根本原因。
但是,我们这里给出的例子其实并不是二叉查找树(Binary Search Tree)
二叉查找树有以下特点:
1 所有左子树上的节点都小于根节点;
2 所有右子树上的节点都大于或等于根节点;
3 左子树和右子树也是二叉查找树。
如果一棵树是二叉查找树,前序和后序任意给出一种,就可以唯一确定地复原出来。原因在于,它不需要中序遍历的协助,只需要通过分析节点的大小关系,就能分割左右子树。如果只给出中序遍历,不一定能唯一确定地恢复出二叉查找树。原因在于,仅有中序遍历无法确定根节点,比如如果二叉查找树的中序遍历为:1 2 3 4 5 6,我们根本无法通过大小关系知道哪个节点是根节点,它们任何一个节点作为根节点都可以构建出二叉查找树。
二、恢复二叉树的代码实现
1 中序+后序恢复二叉树的实现:
这里直接贴出一道题的代码,该题的任务要求:给出二叉树的中序和后序遍历,要求输出层序遍历。
#include<iostream>
#include<queue>
using namespace std;
struct tree_node //二叉树的定义
{
int data = 0;
tree_node* left_child = NULL;
tree_node* right_child = NULL;
};
void find_insert_node(tree_node * &this_node_formal, int node_num, int* poststart, int* instart) //之所以使用指针引用,是因为分配空间时需要修改指针指向的位置,node_num表示当前(子)树中包含节点的总数。
{
if (node_num == 0)
{
return;
}
this_node_formal = new tree_node;
tree_node* this_node = this_node_formal;
int data_now = *(poststart + node_num - 1);
(*this_node).data = data_now;
int divide_place;
for (int i = 0; i < node_num; ++i)//找出当前(子)树根节点在中序遍历中的位置
{
if (*(instart + i) == data_now)
{
divide_place = i;
break;
}
}
find_insert_node((*this_node).left_child, divide_place, poststart, instart);
find_insert_node((*this_node).right_child, node_num - divide_place - 1, poststart + divide_place, instart + divide_place + 1);
}
queue<tree_node*> node_queue;
queue<int> data_save;
void level_out() //层序遍历
{
tree_node* this_node;
if (!node_queue.empty())
{
this_node = node_queue.front();
node_queue.pop();
if ( this_node == NULL)
{
level_out();
return;
}
}
else return;
data_save.push((*this_node).data);
node_queue.push((*this_node).left_child);
node_queue.push((*this_node).right_child);
level_out();
}
int main()
{
int postorder[30];
int inorder[30];
int num; cin >> num;
for (int i = 0; i < num; ++i) cin >> postorder[i];
for (int i = 0; i < num; ++i) cin >> inorder[i];
tree_node* tree_root = NULL;
find_insert_node(tree_root, num, postorder, inorder);
node_queue.push(tree_root);
level_out();
for (int i = 0; i < num; ++i)
{
cout << data_save.front();
data_save.pop();
if (i != num - 1) cout << " ";
}
getchar(); getchar();
return 0;
}
2 前序+中序恢复二叉树的实现
替换前面代码段中的find_insert_node函数就可以
void find_insert_node(tree_node * &this_node_formal, int node_num, int* prestart, int* instart)
{
if (node_num == 0)
{
return;
}
this_node_formal = new tree_node;
tree_node* this_node = this_node_formal;
int data_now = *prestart;
(*this_node).data = data_now;
int divide_place;
for (int i = 0; i < node_num; ++i)
{
if (*(instart + i) == data_now)
{
divide_place = i;
break;
}
}
find_insert_node((*this_node).left_child, divide_place, prestart + 1, instart);
find_insert_node((*this_node).right_child, node_num - divide_place - 1, prestart + divide_place + 1, instart + divide_place + 1);
}
3 前序遍历恢复二叉查找树的实现
void preorder_find_insert_node(tree_node* &this_node, int node_num, int* prestart)
{
if (node_num == 0) return;
this_node = new tree_node;
(*this_node).data = *prestart;
int divide;
for (divide = 1; divide < node_num; ++divide)
{
if (*(prestart + divide) >= *prestart) break;
}
preorder_find_insert_node((*this_node).left_child, divide-1, prestart + 1);
preorder_find_insert_node((*this_node).right_child, node_num - divide, prestart + divide);
}
4 后序遍历恢复二叉查找树的实现
void postorder_find_insert_node(tree_node* &this_node, int node_num, int* poststart)
{
if (node_num == 0) return;
this_node = new tree_node;
(*this_node).data = *(poststart + node_num - 1);
int divide;
for (divide = 0; divide < node_num; ++divide)
{
if (*(poststart + divide) >= *(poststart + node_num - 1)) break;
}
postorder_find_insert_node((*this_node).left_child, divide, poststart);
postorder_find_insert_node((*this_node).right_child, node_num - divide - 1, poststart + divide);
}