给定一棵二叉树的先序遍历序列和中序遍历序列,要求这棵二叉树的后序序列。
首先要知道先序序列是 根->左孩子->右孩子,而当前访问的这个根一定存在于中序序列中,然后根据中序序列的特点,根在中序序列的位置设为 border(边界) , 左边就是左子树,右边就是右子树。递归重建二叉树,然后后序输出即可。所以整体思路就是,沿着先序序列不断移动,当前的关键字设为 pre[ index ] , 然后在中序序列的相应的区间内寻找这个关键字,也就是根,然后在中序序列的这段区间内,左边是左子树,右边是右子树。
#include <iostream>
#include <cstdio>
using namespace std ;
int n , pre[1001] , mid[1001] ;
struct Node{
int weight ;
Node *lchild , *rchild ;
Node( int _weight ) {
weight = _weight ;
lchild = rchild = NULL ;
}
void Destroy( Node *&root ){ // 释放内存
if( root->lchild ) Destroy( root->lchild ) ;
if( root->rchild ) Destroy( root->rchild ) ;
delete root ; root = NULL ;
}
} *root ;
void Post_visit( Node *cur ){ // 后序遍历
if( cur->lchild ) Post_visit( cur->lchild ) ;
if( cur->rchild ) Post_visit( cur->rchild ) ;
printf( cur == root ? "%d" : "%d " , cur->weight ) ;
}
void Re_Build( Node *&cur , int l , int r , int &index ){ // cur 当前建立的节点 ;index 当前前序序列移动到的关键字下标
if( l > r || index > n ) return ;
cur = new Node( pre[index] ) ;
int part = 0 ;
for( int i = l ; i <= r ; ++i ) // 在中序序列的 [ l , r ] 区间内找根
if( mid[i] == pre[index] ){
part = i ; ++index ; break ; // index 要传引用,在找到的时候++ , 表示前序字符串移动
}
Re_Build( cur->lchild , l , part-1 , index ) ; // 递归建立左子树
Re_Build( cur->rchild , part+1 , r , index ) ; // 递归建立右子树
}
int main(){
int i = 0 ;
while( ~scanf( "%d" , &n ) ){
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &pre[i] ) ;
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &mid[i] ) ;
int index = 1 ;
Re_Build( root , 1 , n , index ) ;
if( root ) Post_visit( root ) , root->Destroy( root ) ; // 如果树不为空 , 访问过后,释放内存
cout << endl ;
}
return 0 ;
}
我个人比较喜欢写指针的二叉树。这道题用数组来实现会变得更简单,而且,通过观察上面的 Re_Build 重建二叉树的函数,可以发现,递归和后序遍历很像,也就在这里,我们可以用一个数组 package 来储存访问的节点顺序,那这个顺序是什么呢?递归左子树的时候,左子树的节点先被储存,左边结束之后,返回,递归右子树,然后储存右子树,最后是 package[++top] = mid[border] , 储存当前的找到的根节点的位置。
代码简洁很多,而且不需要每次先释放内存,然后重建。
#include <iostream>
#include <cstdio>
using namespace std ;
int n , pre[1001] , mid[1001] ;
int package[1001] , top ;
void Re_Build( int l , int r , int &index ){
if( l > r || index > n ) return ;
int border = 0 ;
for( int i = l ; i <= r ; ++i )
if( mid[i] == pre[index] ){
border = i ; ++index ; break ;
}
Re_Build( l , border-1 , index ) ; // 先储存左子树
Re_Build( border+1 , r , index ) ; // 返回储存右子树
package[++top] = mid[border] ; // 储存当前根节点
}
int main(){
int i = 0 , index ;
while( ~scanf( "%d" , &n ) ){
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &pre[i] ) ;
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &mid[i] ) ;
top = 0 ;
Re_Build( 1 , n , index = 1 ) ;
for( int i = 1 ; i < top ; ++i )
printf( "%d " , package[i] ) ;
printf( "%d\n" , package[top] ) ;
}
return 0 ;
}
先序遍历序列+后序遍历序列是无法重建二叉树的,因为先序序列的当前根节点在后序序列中,无法确定左右子树相对于根的位置 。当只有一个孩子的时候,先序序列根最先访问,后序序列根最后访问,中间的孩子既可以是左孩子,又可以是右孩子,都不干扰这个先序和后序。所以只有一个孩子的时候,无法确定左右孩子的情况 。
但是,中序+后序是可以重建二叉树的,和上面不同,中序序列中,左孩子先于根被访问,右孩子晚于根被访问;后序序列中,左孩子和右孩子都先于根被访问;从后序序列倒过来,就是 根->右孩子->左孩子,除去根,就可以根据中序序列左右孩子出现的顺序来确定左右子树 。
#include <iostream>
#include <cstdio>
using namespace std ;
int n , post[1001] , mid[1001] ;
int package[1001] , top ;
void Re_Build( int l , int r , int &index ){
if( l > r || index < 1 ) return ;
int border = 0 ;
for( int i = l ; i <= r ; ++i )
if( mid[i] == post[index] ){
border = i ; index-- ; break ; // 后序序列倒过来是 根->右子树->左子树 index 传引用
}
Re_Build( border+1 , r , index ) ; // 倒过来先是右子树
Re_Build( l , border-1 , index ) ; // 然后是左子树
package[++top] = mid[border] ; // 储存当前点 , 储存顺序是 右->左->根
}
int main(){
int i = 0 , index ;
while( ~scanf( "%d" , &n ) ){
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &mid[i] ) ;
for( i = 1 ; i <= n ; ++i )
scanf( "%d" , &post[i] ) ;
top = 0 ;
Re_Build( 1 , n , index = n ) ;
for( int i = top ; i > 1 ; --i )
printf( "%d " , package[i] ) ;
printf( "%d\n" , package[1] ) ;
}
return 0 ;
}