这个问题实际上有三个:
- 已知二叉树的前序和中序序列,如何计算二叉树的后续序列?
- 已知二叉树的中序和后续序列,如何计算二叉树的前序序列?
- 已知二叉树的前序和后续序列,如何计算二叉树的中序序列?(这是无法实现的)
更进一步,问题归为一个:
- 已知二叉树的任意两个遍历序列,如何建立完整的二叉树?
问题一:
已知二叉树的前序和中序序列:
1 2 4 5 8 3 6 9 7 10
4 2 8 5 1 6 9 3 7 10
-
由前序遍历的性质可知:
-
- 任意一个二叉树的前序序列第一个元素是该树的根节点
再由中序遍历的性质可知:
-
- 任意一个二叉树的中序序列中,位于根节点左边的元素构成根节点的左子树,位于根节点右边的元素构成根节点的右子树
那么由上面两条性质,可以知道:前序序列判父子关系,中序序列判左右子树,我们可以将前序序列和中序序列各分成三部分来看:第一部分,根节点元素;第二部分,根节点左子树;第三部分,根节点右子树。于是递归就可以进行了。
-
递归部分:
- 第一步:确认并记录根节点在PRE数组和IN数组中的位置。在PRE数组中第一个元素PRE[0]就是根节点,遍历IN数组找到等于PRE[0]的元素位置,并记录( 即为图中 i )。
- 第二步:用 i 的位置将IN分成两部分,记录左子树和右子树的前序和中序序列的开始和结束位置。
- 第三步:将左子树和右子树进行上述递归。
下面是实现代码:
int PRE[maxn];// La...Ra
int IN[maxn];// Lb...Rb
Node* build_tree(int La, int Ra, int Lb, int Rb) {
if (La > Ra || Lb > Rb)return NULL;
int n = PRE[La];
int i;
for (i = Lb;i <= Rb && IN[i] != n;i++);
int len = i - Lb;
Node* t = new Node();
t->data = n;
t->LChild = build_tree(La + 1, La + len, Lb, i - 1);
t->RChild = build_tree(La + len + 1, Ra, i + 1, Rb);
return t;
}
若要得到后续遍历序列,只需要在上面代码 return 语句之前加入:
printf("%d ", n);
当然也可以重新遍历整个二叉树:
void post_order(Node* root) {
if (root == NULL)return;
post_order(root->LChild);
post_order(root->RChild);
printf("%d ", root->data);
}
最终得到
下面是完整代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 10000 + 10;
int PRE[maxn], IN[maxn];
struct Node {
int data;
Node* LChild, * RChild;
};
Node* build_tree(int La, int Ra, int Lb, int Rb) {
if (La > Ra || Lb > Rb)return NULL;
int n = PRE[La];
int i;
for (i = Lb;i <= Rb && IN[i] != n;i++);
int len = i - Lb;
Node* t = new Node();
t->data = n;
t->LChild = build_tree(La + 1, La + len, Lb, i - 1);
t->RChild = build_tree(La + len + 1, Ra, i + 1, Rb);
return t;
}
void pre_order(Node* root) {
if (root == NULL)return;
printf("%d ", root->data);
pre_order(root->LChild);
pre_order(root->RChild);
}
void in_order(Node* root) {
if (root == NULL)return;
in_order(root->LChild);
printf("%d ", root->data);
in_order(root->RChild);
}
void post_order(Node* root) {
if (root == NULL)return;
post_order(root->LChild);
post_order(root->RChild);
printf("%d ", root->data);
}
int main() {
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
int N;scanf("%d", &N);
for (int i = 0;i < N;i++)scanf("%d", PRE + i);
for (int i = 0;i < N;i++)scanf("%d", IN + i);
Node* root = build_tree(0, N - 1, 0, N - 1);
pre_order(root);printf("\n");
in_order(root);printf("\n");
post_order(root);printf("\n");
return 0;
}
结果
1 2 4 5 8 3 6 9 7 10
4 2 8 5 1 6 9 3 7 10
4 8 5 2 9 6 10 7 3 1
问题二:
已知二叉树的中序和后续序列:
3 2 1 4 5 7 6
3 1 2 5 6 7 4
这个问题实质上和上边那个问题是一样的,不同的地方在于问题一的根节点位置在PRE序列的第一个元素,而这个问题里根节点位置在POST序列最后一个。核心思想并未改变。
int IN[maxn];// La...Ra
int POST[maxn];// Lb...Rb
Node* build_tree(int La, int Ra, int Lb, int Rb) {
if (La > Ra || Lb > Rb)return NULL;
int i;
int n = POST[Rb];
for (i = La;i <= Ra && n != IN[i];i++);
int len = i - La;
Node* t = new Node();
t->data = n;
t->LChild = build_tree(La, i - 1, Lb, Lb + len - 1);
t->RChild = build_tree(i + 1, Ra, Lb + len, Rb - 1);
return t;
}
下面是一个例题:(Tree UVA - 548 )
题意:
给出一颗带权树(权值各不相同,且都是小于10000的正整数)的中序和后续遍历方法,找一个叶子节点使从根节点到该叶子节点的路径权值总和最小,若符合条件的叶子节点有多个,输出权值最小的叶子节点。
Sample Input
3 2 1 4 5 7 6
3 1 2 5 6 7 4
7 8 11 3 5 16 12 18
8 3 11 7 16 18 12 5
255
255
Sample Output
1
3
255
AC代码:
#include <cstdio>
#include <iostream>
#include <string>
#include <sstream>
#include <cstring>
using namespace std;
const int maxn = 10000 + 5;
const int inf = 1000000000;
int IN[maxn];
int POST[maxn];
int N;
int ans, SUM;
struct Node {
int data;
Node* LChild;
Node* RChild;
};
Node* build_tree(int La, int Ra, int Lb, int Rb) {
if (La > Ra || Lb > Rb)return NULL;
int i;
int n = POST[Rb];
for (i = La;i <= Ra && n != IN[i];i++);
int len = i - La;
Node* t = new Node();
t->data = n;
t->LChild = build_tree(La, i - 1, Lb, Lb + len - 1);
t->RChild = build_tree(i + 1, Ra, Lb + len, Rb - 1);
return t;
}
bool read_data(int* a) {
string line;
if (!getline(cin, line))return false;
stringstream ss(line);
N = 0;
int x;
while (ss >> x)a[N++] = x;
return N > 0;
}
void pre_order(Node* root, int sum) {
if (root == NULL)return;
sum += root->data;
if (root->LChild == NULL && root->RChild == NULL) {
if (SUM > sum || (SUM == sum && root->data < ans)) { SUM = sum;ans = root->data; }
//printf("sum = %4d ans = %4d\n", sum, ans);
}
pre_order(root->LChild, sum);
pre_order(root->RChild, sum);
}
int main() {
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
while (read_data(IN)) {
read_data(POST);
//for (int i = 0;i < N;i++)printf("%d ", IN[i]);printf("\n");
//for (int i = 0;i < N;i++)printf("%d ", POST[i]);printf("\n");
SUM = inf;
Node* root = build_tree(0, N - 1, 0, N - 1);
pre_order(root, 0);
printf("%d\n", ans);
}
return 0;
}
问题三:
已知前序和后续序列,能否求出整个二叉树?
上图左图的前序和后续为:
1 2 3 4
4 3 2 1
右图的前序和后续为:
1 2 3 4
4 3 2 1
可见二者虽然不同,但前序和后续序列却是相同的,因此只凭前序和后续序列是无法求出整颗二叉树的。
另外,附上上题顺手写的java代码,java在处理字符串方面比C++好用很多:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.StringTokenizer;
public class Main{
static class Node{
int data;
Node LChild,RChild;
}
static int[] IN,POST;
static final int inf = Integer.MAX_VALUE;
static int maxn = 10000+10;
static int N;
static int SUM,ans;
public static void main(String[] args) throws IOException {
//System.setIn(new FileInputStream("in.txt"));
//System.setOut(new PrintStream("out.txt"));
Scanner sc = new Scanner(System.in);
String sa,sb;
IN = new int[maxn];
POST = new int[maxn];
while((sa = sc.nextLine())!=null) {
sb = sc.nextLine();
String[] str = sa.split(" +");
for(int i=0;i<str.length;i++)IN[i] = Integer.valueOf(str[i]);
str = sb.split(" ");
for(int i=0;i<str.length;i++)POST[i] = Integer.valueOf(str[i]);
N = str.length;
/*for(int i=0;i<N;i++)System.out.printf("%4d",IN[i]);
System.out.println();
for(int i=0;i<N;i++)System.out.printf("%4d",POST[i]);*/
SUM = inf;
Node root = buildTree(0,N-1,0,N-1);
preOrder(root,0);
System.out.println(ans);
}
}
static Node buildTree(int La, int Ra, int Lb, int Rb) {
if (La > Ra || Lb > Rb)return null;
int i;
int n = POST[Rb];
for (i = La;i <= Ra && n != IN[i];i++);
int len = i - La;
Node t = new Node();
t.data = n;
t.LChild = buildTree(La, i - 1, Lb, Lb + len - 1);
t.RChild = buildTree(i + 1, Ra, Lb + len, Rb - 1);
return t;
}
static void preOrder(Node root,int sum) {
if(root==null)return;
sum+=root.data;
if(root.LChild==null&&root.RChild==null) {
if(SUM>sum||(sum==SUM&&ans>root.data)) {SUM = sum;ans = root.data; }
return;
}
preOrder(root.LChild,sum);
preOrder(root.RChild,sum);
}
}
class Scanner
{
BufferedReader br;
StringTokenizer st;
Scanner(InputStream input)
{
br = new BufferedReader(new InputStreamReader (input));
st = null;
}
public String next() throws IOException
{
while(st==null || !st.hasMoreTokens())
{
String str = br.readLine();
if(str==null)
return null;
st = new StringTokenizer(str);
}
return st.nextToken();
}
public int nextInt() throws NumberFormatException, IOException
{
return Integer.parseInt(next());
}
public double nextDouble() throws NumberFormatException, IOException
{
return Double.parseDouble(next());
}
public Long nextLong() throws NumberFormatException, IOException
{
return Long.parseLong(next());
}
public String nextLine() throws IOException {
return br.readLine();
}
}