1. 程式人生 > >二叉樹 已知前序中序兩個序列,建立二叉樹(中序和後序也有)

二叉樹 已知前序中序兩個序列,建立二叉樹(中序和後序也有)

本文主要講二叉樹的建樹,具體的說就是,題目給出你二叉樹的前序和中序,你來建樹,還有一個題目是給出中序和後序來建樹

第一題:A binary tree is a finite set of vertices that is either empty or consists of a root r and two disjoint binary trees called the left and right subtrees. There are three most important ways in which the vertices of a binary tree can be systematically traversed or ordered. They are preorder, inorder and postorder. Let T be a binary tree with root r and subtrees T1,T2.

In a preorder traversal of the vertices of T, we visit the root r followed by visiting the vertices of T1 in preorder, then the vertices of T2 in preorder.

In an inorder traversal of the vertices of T, we visit the vertices of T1 in inorder, then the root r, followed by the vertices of T2 in inorder.

In a postorder traversal of the vertices of T, we visit the vertices of T1 in postorder, then the vertices of T2 in postorder and finally we visit r.

Now you are given the preorder sequence and inorder sequence of a certain binary tree. Try to find out its postorder sequence.   

InputThe input contains several test cases. The first line of each test case contains a single integer n (1<=n<=1000), the number of vertices of the binary tree. Followed by two lines, respectively indicating the preorder sequence and inorder sequence. You can assume they are always correspond to a exclusive binary tree.
OutputFor each test case print a single line specifying the corresponding postorder sequence.
Sample Input
9
1 2 4 7 3 5 8 9 6
4 7 2 1 8 5 9 3 6
Sample Output
7 4 2 8 9 5 6 3 1


這個題的意思呢就是給出二叉樹的前序和中序,要你求按 後序遍歷來輸出。

這道題對於初學二叉樹的同學來說還是比較難的,對於完全摸不著頭緒的同學,建議現在紙上根據兩個序列來畫出這個樹,這一步很重要!!!因為通過畫二叉樹,你就會發現其中的規律,說白了就是 遞迴  ,當然,你要畫很多個二叉樹才能自己獨立的寫出完整的程式碼,我就是這樣做的,感覺挺有效。

在這,我特意給了畫二叉樹的具體步驟,有助於大家理解。(見分析中的圖片)

分析:

這個題最容易想到的就是先建樹,然後再遍歷輸出,其實可以不用建樹,直接輸出,那麼我分別講一下兩種解法。

第一種 :建樹,然後輸出。

我們用兩個陣列把前序和中序存起來,便於查詢。

我們知道,前序是 根-左-右,就是先輸出根,再左子樹,右子樹,那麼前序的第一個數一定是根,而中序遍歷是 左 - 根 - 右 , 根在中間, 那麼根據前序來找根,用這個根的資料再到中序中找到下標,從而中序就被分為三部分了,就是 左-根-右,那麼再對左  和    右分別進行同樣的步驟,具體的就是,用前序的根來去中序中查詢下標,下標的左邊是左子樹,下標的右邊是右子樹,然後再分別對左右進行同樣的步驟。。。。。。很顯然,一定要用到遞迴。下面是我對案例的分析:


字有點醜,大家不要介意啊。

其實這個步驟只是有助於理解二叉樹怎麼建,對於寫演算法還是有一丁點差別的,而且這個題還有很多細節問題。

我先把程式碼給出來吧,然後再進行分析(這個題我給出幾個版本的解法,之間有細微的差別):

第一種:在找的時候,沒有範圍的去找。具體的說就是對於題目給的a,b 陣列,在b中找a的元素時,無範圍的找。

為什麼可以在b中 從0到n的找呢? 那是因為題目資料沒有重複的,a中的陣列和b中的陣列都是獨立的,而且這道題給的資料是  一定可以建樹的資料。


#include<iostream>
using namespace std;
#include<cstdio>
#include<queue>
#include<string.h>
int n;
int a[1001],b[1001];
int judge[1001];				//判斷b中的下標是否用過
struct node
{
	int index;
	node *left,*right;
	node()
	{
		index = 0;
		left = right = NULL;
	}
};
int cnt = 0;
node *build( node *root )			//這個函式的返回值有多種寫法
{
	int t = a[cnt++], i;						//從a中取數 去b中查詢
	for( i = 0 ; i < n ; i++ )
	{
		if( b[i] == t )
			break;
	}
	judge[i] = 1;								//被標記
	root = new node();
	root->index = t;
	if( i > 0 && i < n  &&  judge[i-1] != 1 )		//在b陣列中,如果一個數左相鄰的數被標記,則不能向左建樹
		root->left = build(root->left);							/******	這個性質不懂的  可以在紙上多模擬幾遍,找一下規律 ******/
	if( i >= 0 && i  < n-1 && judge[i+1] != 1 )//同樣,在b陣列中,如果一個數右相鄰的數被標記,則不能向右建樹
		root->right = build(root->right);
	return root;											//左右都建完,返回根結點
}
int counter = 0;
void postorder(node *root)		//這個函式沒啥好說的,  模板函式( 上個函式其實也是半個模板 )
{
	if( root->left )
		postorder(root->left);
	if( root->right )
		postorder(root->right);
	if( counter )					//用來調整輸出格式
		printf(" ");
	counter++;
	printf("%d",root->index);
}
int main()
{
	while( scanf("%d",&n)==1 && n )
	{
		counter = 0;
		cnt = 0;
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(judge,0,sizeof(judge));
		for( int i = 0 ; i < n ; i++ )
			scanf("%d",&a[i]);
		for( int i  = 0 ; i < n ; i++ )
			scanf("%d",&b[i]);
		node *root = NULL;
		root = build(root);
		postorder(root);
		printf("\n");
	}
	return 0;
}



再說一下有範圍的找吧,這個程式碼更具有普適性,(下次如果給你的資料有不能建樹的,你可以用上這個來判斷是否可以建樹),如果這個題資料再大一點,上一種方法估計就用不了了。

有範圍的查詢是怎麼樣的呢?我們先看一下案例,

a :

1 2 4 7 3 5 8 9 6
b :
4 7 2 1 8 5 9 3 6

在b中找到 1 後,自然的將b分為左右兩部分,那麼在向左建樹的時候,查詢的範圍就是左邊的那部分,向右建樹的時候,就是右邊的部分。

是不是很簡單!

那麼build()函式就要加上兩個引數了,左端點和右端點,有了端點就有了範圍,如果在這個範圍裡找不到,就說明不能建樹。

程式碼如下:

#include<iostream>
using namespace std;
#include<cstdio>
#include<queue>
#include<string.h>
int n;
int a[1001],b[1001];
struct node
{
	int index;
	node *left,*right;
	node()
	{
		index = 0;
		left = right = NULL;
	}
};
int cnt = 0;
node *build( node *root ,int left,int right)		//左右端點			
{
	int i;
	int t = a[cnt++];
	for(  i = left ; i < right ; i++ )
	{
		if( b[i] == t )										//		①
			break;
	}
	root = new node();	/***如需加上是否可以建樹的合法判斷,在①②③處加(本題不用)***/
	root->index = t;
	if( i > left && i < right )						//			②
		root->left = build(root->left,left,i);				//範圍一定要非常精確
	if( i >= left && i <right-1 )					//			③
		root->right = build(root->right,i+1,right);
	return root;											//左右都建完,返回根結點
}
int counter = 0;
void postorder(node *root)		//這個函式沒啥好說的,  模板函式( 上個函式其實也是半個模板 )
{
	if( root->left )
		postorder(root->left);
	if( root->right )
		postorder(root->right);
	if( counter )					//用來調整輸出格式
		printf(" ");
	counter++;
	printf("%d",root->index);
}
int main()
{
	while( scanf("%d",&n)==1 && n )
	{
		counter = 0;
		cnt = 0;
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		//memset(judge,0,sizeof(judge));
		for( int i = 0 ; i < n ; i++ )
			scanf("%d",&a[i]);
		for( int i  = 0 ; i < n ; i++ )
			scanf("%d",&b[i]);
		node *root = NULL;
		root = build(root,0,n);			//一開始為最大範圍
		postorder(root);
		printf("\n");
	}
	return 0;
}

第二種:不用先建樹,後輸出,直接  建的時候就輸出,感覺更快一點

這一種,連node的結構體都不用寫了,而且程式碼簡潔了許多,這就是遞迴的神祕之處,運用好的話就有這種效果

程式碼如下:

#include<iostream>
using namespace std;
#include<cstdio>
#include<queue>
#include<string.h>
int n;
int a[1001],b[1001];
int cnt = 0;
int counter = 0;
void build( int left,int right)		//左右端點			
{
	int i;
	int t = a[cnt++];
	for(  i = left ; i < right ; i++ )
	{
		if( b[i] == t )									
			break;
	}
	if( i > left && i < right )						
		build(left,i);							//範圍一定要非常精確
	if( i >= left && i <right-1 )					
		build(i+1,right);
	if( counter )
		printf(" ");
	counter++;
	printf("%d",t);
}
int main()
{
	while( scanf("%d",&n)==1 && n )
	{
		counter = 0;
		cnt = 0;
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		//memset(judge,0,sizeof(judge));
		for( int i = 0 ; i < n ; i++ )
			scanf("%d",&a[i]);
		for( int i  = 0 ; i < n ; i++ )
			scanf("%d",&b[i]);
		build(0,n);
		printf("\n");
	}
	return 0;
}

以上就是對於本題的幾個版本的解法,其實差別不太大

下面再看一道變形的題,稍微難一點:






這道題是給出中序和後序,讓你判斷是否可以建樹,並輸出前序。

那麼思路其實和上邊的題一模一樣。

再來看一下案例(把它變一下):

中序:(左根右)

4 7 2 1 8 5 9 3 6
後序:(左右根)
7 4 2 8 9 5 6 3 1

答案:(前序)

1 2 4 7 3 5 8 9 6
還記不記得在之前的題目中,我們是在前序中找根節點的資料,去中序中找。那麼在這就是在後序中給資料,然後去中序中找。有一點不同的是,之前的是中前序的第一個一直取到最後一個,而在這是從最後開始,取到第一個,因為後序是左右根,那麼最後一個一定是根節點的資料。而前序是根左右,第一個是根。 那麼意識到這一點就很簡單了,a陣列的訪問換一個方向就行了。 然後這個題還有一個不同點是,有可能不能建樹,那麼就用上我之間說的,如果在範圍內沒有找到,則說明無法建樹。OK,程式碼如下(順便把這道題的資料給一下): (這個題只能先建樹後輸出)
#include<cstdio>
#include<cstring>
#include<math.h>
#include<iostream>
#include<queue>
using namespace std;
char a[30],b[30];
struct node
{
	char s;
	node *left,*right;
	node()
	{
		left = right = NULL;
	}
};
int cnt,len,bigflag;
node *build(node *root,int r,int l)
{
	if( bigflag == 1 )			//MMP了  不用建了
		return NULL;
	root = new node();
	root->s = b[--cnt];
	int i ;
	int flag = 0;
	for( i = l ; i < r ; i++ )
		if( a[i] == root->s )
		{
			flag = 1;
			break;
		}
	if( flag == 0 )			//一個數據沒找到就 MMP
	{
		bigflag = 1;		//用來判斷是否MMP
		return NULL;
	}
	if( i < r-1 )														//右邊還有元素
		root->right = build(root->right,r,i+1);
	if( i > l )															//左邊還有元素
		root->left = build(root->left,i,l);
	return root;
}
void preorder(node *root)
{
	printf("%c",root->s);
	if( root->left )
		preorder(root->left);
	if( root->right )
		preorder(root->right);
}
int main()
{
	int T;
	scanf("%d",&T);
	while( T-- )
	{
		scanf("%s%s",a,b);
		bigflag = 0;
		cnt = strlen(b);
		len = cnt;
		node *root = NULL;
		root = build(root,len,0);
		if( bigflag == 0 )
		{
			preorder(root);
			printf("\n");
		}
		else
			printf("MMP!\n");
	}

	return 0;
}

/*



*/

輸入資料in: 45
A
A
BA
AB
ABC
CAB
BCA
ABC
BCA
BAC
ABDCEFG
ABCDEFG
DBEAFCG
DEBFGCA
EAZGFH
EZGHFA
ABCDEFG
ABCDEFG
LBNVPAECFGQWZUJRHKDTSOIYXM
LNPVBEFCAZWUQRJKHTSOYXMIDG
CBDAIFJEKG
CDBIJFKGEA
BDCA
CADB
DBCA
CABD
BDAC
ACDB
BDAC
ACBD
CEBDA
CEBDA
CAEBD
ACEDB
EBDAC
EBDAC
EBACD
ACEBD
BFACDE
ECDFAB
FABCDE
CDEFAB
BDFACEG
BEGDFAC
EGCBDFA
CEGBDFA
CEGABDF
ACEGBDF
ACFEGBD
FACEDGB
GACEBDFH
BDFHEGAC
EGACHBDF
HBDFCEGA
CEGAFHBD
FHBDACEG
ACEGDFHB
DFHBGACE
GACEHBDF
HBDFCEGA
IFCAGDBHE
BHEIFCGDA
GDAHEBFCI
FCIDAGEBH
ECIFAGDHB
IFCGDAEBH
EBHCIFDAG
DAGBHEIFC
AGDBHEIFC
IFDAGEBHC
DFHJBACEGI
ACEGIHJBDF
HJBDFEGIAC
EGIACBDFHJ
BDFHJIAEGC
EGIACHJBDF
HJBDFEGIAC
GIACEDFHJB
DFHJBACEGI
ACEIGJBDFH
IJKABCDEFGH
HIJKACDEFGB
BCDEFGHIJKA
ABCDEFGHIJK
KABCDFGHIJE
EFHIJKABCDG
GHIJKABCDEF
FGHIJKABCDE
EFGHIKABCDJ
JKBCDEFGHIA
輸出資料out: