1020 Tree Traversals (25 分)
Suppose that all the keys in a binary tree are distinct positive integers. Given the postorder and inorder traversal sequences, you are supposed to output the level order traversal sequence of the corresponding binary tree.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤30), the total number of nodes in the binary tree. The second line gives the postorder sequence and the third line gives the inorder sequence. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in one line the level order traversal sequence of the corresponding binary tree. All the numbers in a line must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
Sample Output:
4 1 6 3 5 7 2
题目要求:
给出一棵二叉树的后序遍历以及中序遍历,输出这棵树的层序遍历
解题思路一(先构建二叉树再层序遍历,知道的直接跳到下一个解题思路):
这里我是先按照二叉树的后序遍历和中序遍历,将二叉树构建出来,然后按照层序遍历将这棵二叉树输出。
如何构建二叉树首先对后序遍历和中序遍历进行分析。
后序遍历:左右根
中序遍历:左根右
下面结合这个看起来很复杂而且有点丑的图来分析后序和中序遍历怎么构成一个二叉树。从原理上深入解释过程。
由于后序遍历始终按从左右根的顺序,最后一个一定是二叉树的根,这里为4,记做根1。
在中序中找到根1(即为4),由于中序按照左根右的顺序,可以根据根1将剩下分成两部分,在根1的左边为其左子树部分,在根1右边的为其右子树部分。
再找到根1左子树对应的左子树根,以及右子树对应的右子树根。后序左右根的顺序,所以在根1左子树部分出现在后序遍历顺序最后的就是左子树根,左子树1,2,3中在后序顺序中最后出现的是1,记为根2左。在根1右子树部分出现在后序遍历顺序最后的就是右子树根,右子树5,6,7中在后序顺序中最后出现的是6,记为根2右。
重复上述步骤,找到根2左(1)对应在中序中的位置,其左边部分为左子树,这里没有。其右边部分为右子树(2,3)。找到根2右(6)对应在中序中的位置,其左边部分为左子树(5)。其右边部分为右子树(7)。
大概就是这样的一种思路。首先在后序遍历中找到根,再根据这个根找到其在中序中的位置区分它的左子树和右子树部分。直到将所有的结点都遍历结束。
注意:
1.递归时创建左孩子以及右孩子有三种情况。
a.根在中序范围内的中间,这样根既有左孩子又有右孩子
b.根在中序范围内的最左边,这样根只有右孩子,没有左孩子
c.根在中序范围内的最右边,这样根只有左孩子,没有右孩子
2.递归的结束条件:
a.树完全创建完成
b.根既没有左孩子也没有右孩子,此时根满足根在中序范围内的最左边,但是找右子树的根的时候会找不到,所以需要再找右子树根找不到时结束递归。
完整代码:
#include<bits/stdc++.h>
using namespace std;
typedef struct Node{
int data;
struct Node* left;
struct Node* right;
}*BinTree;
int post[31];
int in[31];
int level[31];
int num = 0;
queue<BinTree> q;
int N;
void LevelOrder(Node * T){ //对树进行层序遍历
int i=0;
Node* p = T;
if(p){
q.push(p);
}
while(!q.empty()){
p = q.front();
level[i++] = p->data;
q.pop();
if(p->left){
q.push(p->left);
}
if(p->right){
q.push(p->right);
}
}
}
void create(Node* T,int px,int py,int ix,int iy,int proot){ //根结点,对应子树部分在后序的起点下标px,终点下标py,对应子树部分在中序的起点下标ix,终点下标iy
int i,j,k,iroot,lroot=-1,rroot=-1; //iroot为根proot在中序中的下标;lroot为对应左子树根在后序中的下标;rroot为对应右子树根在后序中的下标
num++;
if(num == N) return;
for(i=ix;i<=iy;i++){
if(post[proot] == in[i]){
iroot = i;
break;
}
}
if(iroot !=ix && iroot!=iy){ //根在中序范围的中间
for(i = py;i>=px;i--){ //找到根iroot左子树部分在后序中最后出现的为其左子树的根lroot
for(j=ix;j<iroot;j++){
if(lroot == -1 && post[i] == in[j]){
lroot = i;
}
}
for(k=iy;k>iroot;k--){//找到根iroot右子树部分在后序中最后出现的为其右子树的根rroot
if(rroot == -1 && post[i] == in[k]){
rroot = i;
}
}
}
Node* L =(Node*)malloc(sizeof(Node));
Node* R =(Node*)malloc(sizeof(Node));
L->data = post[lroot];
L->left = NULL;
L->right = NULL;
R->data = post[rroot];
R->left = NULL;
R->right = NULL;
T->left = L;
T->right = R;
create(L,px,lroot-1,ix,iroot-1,lroot); //对左子树部分继续创建
create(R,lroot+1,rroot-1,iroot+1,iy,rroot); //对右子树部分继续创建
}else if(iroot == ix){ //根在中序范围的最左边
for(i = py;i>=px;i--){
for(k=iy;k>iroot;k--){
if(rroot == -1 && post[i] == in[k]){
rroot = i;
}
}
}
if(rroot == -1) return;
Node* R =(Node*)malloc(sizeof(Node));
R->data = post[rroot];
R->left = NULL;
R->right = NULL;
T->left = NULL;
T->right = R;
create(R,px,rroot-1,iroot+1,iy,rroot);
}else if(iroot == iy){ //根在中序范围内的最右边
for(i = py;i>=px;i--){
for(j=ix;j<iroot;j++){
if(lroot == -1 && post[i] == in[j]){
lroot = i;
}
}
}
Node* L =(Node*)malloc(sizeof(Node));
L->data = post[lroot];
L->left = NULL;
L->right = NULL;
T->left = L;
T->right = NULL;
create(L,px,lroot-1,ix,iroot-1,lroot);
}
}
int main(){
int i;
cin>>N;
for(i=0;i<N;i++){
scanf("%d",&post[i]);
}
for(i=0;i<N;i++){
scanf("%d",&in[i]);
}
Node* T = (Node*)malloc(sizeof(Node));
T->data = post[N-1];
T->left = NULL;
T->right = NULL;
create(T,0,N-2,0,N-1,N-1);
LevelOrder(T);
for(i=0;i<N-1;i++){
printf("%d ",level[i]);
}
printf("%d",level[N-1]);
return 0;
}
非常开心自己摸索的写完之后。。看了大神的代码。。人家30行就解决了。。o(╥﹏╥)o,不过也的确让我对树有了更深刻的理解。过程还是非常快乐的。
解题思路二:
首先来个铺垫:根据后序和中序输出先序遍历
仍然是在中序中找到后序中的根以此区分左右子树。但是不同的是右孩子为后序中根下标-1,左孩子为后序中根下标-右子树结点个数-1(这里的理解是,中序中根就区分了左子树和右子树,而后序中左子树的根一定是左子树中最后的,后续中右子树的根也是右子树中最后的)
再根据对应根的下标以及所在的子树区域,左子树是原根范围的起始下标——根的中序下标-1;右子树是根的中序下标+1——原根范围的结束下标
void pre(int root, int start, int end) {
if(start > end) return ;
int i = start;
while(i < end && in[i] != post[root]) i++;
printf("%d ", post[root]);
pre(root - 1 - end + i, start, i - 1);
pre(root - 1, i + 1, end);
}
这样每次输出根即按照先序遍历输出。
进阶:根据后序和中序输出层序遍历
所以按照先序遍历的顺序只需要用一个数组按照下标顺序记录每个位置对应的结点的值即可。初始化-1表示结点不存在。这样之后按下标顺序输出所有不为-1的结点就可以了。
完整代码:
#include <iostream>
#include <vector>
using namespace std;
vector<int> post, in, level(100000, -1);
void pre(int root, int start, int end, int index) {
if(start > end) return ;
int i = start;
while(i < end && in[i] != post[root]) i++;
level[index] = post[root];
pre(root - 1 - end + i, start, i - 1, 2 * index + 1);
pre(root - 1, i + 1, end, 2 * index + 2);
}
int main() {
int n, cnt = 0;
scanf("%d", &n);
post.resize(n);
in.resize(n);
for(int i = 0; i < n; i++) scanf("%d", &post[i]);
for(int i = 0; i < n; i++) scanf("%d", &in[i]);
pre(n-1, 0, n-1, 0);
for(int i = 0; i < level.size(); i++) {
if (level[i] != -1) {
if (cnt != 0) printf(" ");
printf("%d", level[i]);
cnt++;
}
if (cnt == n) break;
}
return 0;
}