【为了保证严谨下面所提及的树均为二叉树】
创作不易,未经允许严禁转载,
本博客文章仅代表作者本,人的观点,不保证文章等内容的有效性。
对于非法转载者,原作者保留采用法律手段追究的权利。
1、众所周知二叉树的前序+中序可以构成一颗唯一的树,后序+中序亦可,他们都可以推出一个唯一的中序序列。
2、那么二叉树的后序加前序似乎就显得模糊多了,可能可以构成一唯一的树,也可能构成的树不唯一。
下面我自我总结一下,自己对此的拙见:
对于一颗有子树的树来说可以分为以下三种情况:
1、只有左子树 |
---|
2、只有右子树 |
3、有左右子树 |
1、对于只有左子树的情况可以发现
先序序列:1 2
后序序列:2 1
但是只要2是1的子树就可以满足上面的先序序列和后序序列,所以2可以是1的左子树也可以是1的右子树,这也就导致了后序加前序得出中序的不唯一性。(其中节点2可以是一个节点也可以是一颗子树,这个由实际情况决定)
2、对于只有右子树的情况
这种情况同1,求出的中序序列具有不唯一性。
3、对于有左右子树的情况
如图所示,其中②和③可以是一颗节点,也可以是一颗子树。
在这种情况下其
前序序列:1 2 3
后序序列:2 3 1
可以发现其中序序列只能是2 1 3,不可能有其他情况,因为①的左子树只能是②右子树只能是③.
总结
对于上面三种情况我们可以发现,前序+后序推导出来的中序序列不唯一性只可能是单子树(如前两种情况)节点导致的,下面我主要说说在比较复杂的树中我们如何判断其唯一性。
如何判断给定的先序序列和后序序列是否存在唯一的中序序列?
对于一颗比较复杂的二叉树先序序列和后序序列我们如何来判断其是否构成一颗唯一的树呢?
下面我来举一个小例子:
以下面这棵树为例(画的有点丑别介意哈)
大家从图中就可以发现节点②就是单子树节点,那么立即就可以知道这颗树的前序和后序推出的中序不唯一。
这颗树的前序序列:1 2 4 10 8 3 6 7 9 5
后序序列:10 8 4 2 7 9 6 5 3 1
由已知的图可以知道这颗树存在一个单子树节点,所以由上面的两个两个序列必定得不到这棵树的唯一中序序列,因为你把②的左子树放到②的右子树上同样可以得到相同的前序遍历序列和后序遍历序列。
下面我来讲讲具有双子树的节点和只有单子树的节点的先序序列和后序序列有何不同。这部分讲起来有点难(其实理解起来特别简单),但是我尽量让大家理解这个过程。
1、首先回到我们一开始讲的地方,我们同样分为两种情况来分析,一个就是有双子树的节点,一个就是只有单子树的节点。
- 对于节点①,它有两棵子树,那么它前序序列就是先节点①在左子树右子树、其后序序列就是先左子树右子树再①节点。
- 而在前序序列中前序序列的第二个节点就是第一个节点的左子树的根节点,我们暂且记该节点为p(也就如图中②是①的左子树根节点)。
- 同样在后序序列中后序序列的倒数第二节点就是倒数第一个节点的右子树的根节点 ,我们暂且记该节点为q(也就是如图中的③是①的右子树根节点)。
- 好那么问题来了,如果给定一颗节点root,如果它有双子树(存在左右子树)那么必定有p!=q。也就是先序序列的第二个节点不会等于后序序列的到数第二个节点。(如图中先序中的②!=后序中的③)
- 如果给定一颗节点root,如果它只有单子树(存在左或右子树)那么必定有p==q。也就是先序序列的第二个节点肯定等于后序序列的到数第二个节点(我们可以将图中以②为根节点的树分割出来看,也就是图中先序中的4=后序中的4)。
有了前面的一些知识后,现在我们只给出先序序列和后序序列;
pre[10] :1 2 4 10 8 3 6 7 9 5
post[10]:10 8 4 2 7 9 6 5 3 1
下面给出求解这棵树的部分过程,给出了处理单节点树①和双节点根②的方法:
- pre[0] = post[9] 以pre[0]为根节点
- 判断pre[0+1]!=post[9-1] 说明根节点pre[0]有两个子树,(假若pre[0+1]=post[9-1] 则说明节点pre[0]只有一颗子树,那么我们将其设置为左子树或者右子树,这样就可以求出其一种情况)
- 上面说到了先序序列的第二个节点是左子树的根节点,也就是上面序列中的2是1的左子树根节点,我们在后序序列中找到左子树根节点2。
- 这样我们就可以确定以①为根节点的左子树先序序列是:
2 4 10 8
后序序列是:10 8 4 2
,同样可以得到左子树的先序序列和后序序列。 - 对于单节点根② ,我们同样令pre_left[]为:
2 4 10 8
pre_right[]为:10 8 4 2
我们首先判断 pre_left[0] = pre_right[3] 说明②是根节点,发现pre_left[1] = pre_right[2] = 4,说明节点②只有一个根节点④,我们可以将④放在②的左子树上也可以放在右子树上。 - 下面我们同样可以得到一个以④为根节点的前序序列:
4 10 8
和后序序列:10 8 4
,下面的过程又是处理一个双子树的情况,同上。
经过上面的过程我们可以发现,这就是一个递归的过程,大家可以使用递归来实现这个过程。
下面给出我的递归实现函数(思想用的就是上面的思想):
int ToInOrder(int l1,int h1,int l2,int h2) {
if (l1 == h1) return pre[l1];//递归出口,如果自由一个节点了直接返回就好了,不要再进行更深入递归了
//if (pre[l1] == post[h2] && l1 != 0 && l1!=h1) flag = 0;//我出错的地方,大家也可以引以为戒
if (pre[l1+1] == post[h2-1]) flag = 0;//表示存在不唯一的树。
int root = pre[l1];
if (pre[l1 + 1] != post[h2 - 1]) {//说明有两个子树,此时建造的树具有唯一形态
int t = pre[l1 + 1];//t为左子树的根节点,因为是先序遍历并且具有双子树,那么先序遍历的l1+1个节点必定是l1节点的左子树根。
int m = l2;//
while (post[m] != t) m++;//找到后序遍历的左子树根
int len = m - l2 + 1;//求出左子树的节点个数。
BiTree[root].left = ToInOrder(l1+1,l1+len,l2,m);
BiTree[root].right = ToInOrder(l1+len+1,h1,m+1,h2-1);
}
else {//说明此时只有一个子树,可以是左子树,也可以是右子树,我们暂定其为右子树
BiTree[root].right = ToInOrder(l1+1,h1,l2,h2-1);
}
return root;
}
下面是我做的一个题目(【PAT】1119 Pre- and Post-order Traversals)的题解:
OJ入口:1119
题目大意就是给出一棵树的结点个数n,以及它的前序遍历和后序遍历,输出它的中序遍历,如果中序遍历不唯一就输出No,且随便输出其中一个中序即可,如果中序遍历唯一就输出Yes,并输出它的中序。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 31;
int N,pre[MAXN],post[MAXN];
struct BiNode {
int left, right;
BiNode() {
left = 0; right = 0;
}
}BiTree[100];
//l1 h1表示pre的上下限 l2 h2表示post的上下限
int flag = 1;//1表示是唯一的一棵树,0表示不是一颗唯一的树。
int ToInOrder(int l1,int h1,int l2,int h2) {
if (l1 == h1) return pre[l1];
//if (pre[l1] == post[h2] && l1 != 0 && l1!=h1) flag = 0;//表示存在不唯一的树。
if (pre[l1+1] == post[h2-1]) flag = 0;//表示存在不唯一的树。
int root = pre[l1];
if (pre[l1 + 1] != post[h2 - 1]) {//说明有两个子树,此时建造的树具有唯一形态
int t = pre[l1 + 1];//t为左子树的根节点,因为是先序遍历并且具有双子树,那么先序遍历的l1+1个节点必定是l1节点的左子树根。
int m = l2;//
while (post[m] != t) m++;//找到后序遍历的左子树根
int len = m - l2 + 1;//求出左子树的节点个数。
BiTree[root].left = ToInOrder(l1+1,l1+len,l2,m);
BiTree[root].right = ToInOrder(l1+len+1,h1,m+1,h2-1);
}
else {//说明此时只有一个子树,可以是左子树,也可以是右子树,我们暂定其为右子树
BiTree[root].right = ToInOrder(l1+1,h1,l2,h2-1);
}
return root;
}
vector<int> ans;
void Inorder(int root) {
if (root == 0) return;
Inorder(BiTree[root].left);
ans.push_back(root);
Inorder(BiTree[root].right);
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("Text.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d", &N);
for (int i = 0; i < N; i++) scanf("%d", &pre[i]);
for (int i = 0; i < N; i++) scanf("%d", &post[i]);
ToInOrder(0, N - 1, 0, N - 1);
Inorder(pre[0]);
if (flag == 1) cout << "Yes" << endl;
else cout << "No" << endl;
cout << ans[0];
for (int i = 1; i < N; i++) {
cout << " " << ans[i];
}
cout << endl;
}