Hdu 1710 二叉树的重建 先序+中序 ;中序+后序

给定一棵二叉树的先序遍历序列和中序遍历序列,要求这棵二叉树的后序序列。

首先要知道先序序列是 根->左孩子->右孩子,而当前访问的这个根一定存在于中序序列中,然后根据中序序列的特点,根在中序序列的位置设为 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 ;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值