数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树

Posted 晚风(●•σ )

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树相关的知识,希望对你有一定的参考价值。

目录

一、由遍历恢复二叉树

(一)由先序遍历和中序遍历

1、二叉树的先序遍历中,首先是根结点,遍历完根结点的左子树,然后再遍历完根结点的右子树,依次下去至所有结点都遍历到;
2、二叉树的中序遍历中,首先是遍历完根结点的左子树,然后是根结点,最后遍历完根结点的右子树,依次下去至所有结点都遍历到。

  • 由于先序遍历首先是根结点,所以可以根据先序遍历确定所求二叉树的根结点,然后再通过中序遍历来确定左、右子树,其思路也是分别寻找左子树和右子树的根结点,并将左、右子树的根结点连在双亲结点后,依次进行下去。

例如,已知先序序列为ABDECFG,中序序列为DBEAFCG,由先序遍历和中序遍历恢复二叉树。
从先序序列确定二叉树的根结点:A,如下:

通过中序遍历来确定左、右子树,其中结点A之前的所有结点都是根结点左子树结点,其后都是根结点右子树结点,如下:

然后继续对先序序列和中序序列中的左、右子树进行相应进一步的分解:

可得到如下二叉树:

#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 100
int front=0,rear=0;
/*1、二叉树的定义*/
typedef struct BNode 
	int data;		//数据域
	struct BNode *lchild,*rchild;		//左孩子、右孩子指针
 *BTree,BNode;

/*2、二叉树的建立*/
BTree CreateTree() 
	BTree T;
	char ch;
	scanf("%c",&ch);
	getchar();	//getchar()用于接收每次输入字符结点后的回车符,从而以便输入下一个字符结点
	if(ch=='0')	//当为0时,将结点置空
		T=NULL;
	else 
		T=(BTree)malloc(sizeof(BTree));	//分配一个新的结点
		T->data=ch;
		printf("请输入%c结点的左孩子结点:",T->data);
		T->lchild=CreateTree();		//通过递归建立左孩子结点
		printf("请输入%c结点的右孩子结点:",T->data);
		T->rchild=CreateTree();		//通过递归建立右孩子结点
	
	return T;


/*3、广义表输出二叉树*/
void ShowTree(BTree T) 
	if(T!=NULL) 
		//当二叉树不为空时
		printf("%c",T->data);	//输入出该结点的数据域
		if(T->lchild!=NULL) 		//若该结点的左子树不为空
			printf("(");	//输出一个左括号
			ShowTree(T->lchild);	//通过递归继续输出结点的左子树结点下的各结点
			if(T->rchild!=NULL) 	//若该结点右子树不为空
				printf(",");	//输出一个逗号
				ShowTree(T->rchild);	//通过递归继续输出结点的右子树结点下的各结点
			
			printf(")");	//输出一个右括号
		 else 	//若左子树为空,右子树不为空
			if(T->rchild!=NULL) 
				printf("(");	//输出一个左括号
				ShowTree(T->lchild);	//通过递归继续输出结点的左子树结点下的各结点
				if(T->rchild!=NULL) 		//若该结点的右子树不为空
					printf(",");	//输出一个逗号
					ShowTree(T->rchild);	//通过递归继续输出结点的右子树结点下的各结点
				
				printf(")");	//输出一个右括号
			
		
	


/*4、先序遍历二叉树*/
bool ProTree(BTree T) 
	if(T==NULL)
		return false;			//递归结束
	else 
		printf("%c ",T->data);	//输出当前结点的数据域
		ProTree(T->lchild);		//递归继续遍历该结点的左子树
		ProTree(T->rchild);		//递归继续遍历该结点的右子树
		return true;
	


/*5、中序遍历二叉树*/
bool InTree(BTree T) 
	if(T==NULL)
		return false;			//递归结束
	else 
		InTree(T->lchild);		//递归继续遍历该结点的左子树
		printf("%c ",T->data);	//输出当前结点的数据域
		InTree(T->rchild);		//递归继续遍历该结点的右子树
		return true;
	


/*6、后序遍历二叉树*/
bool PostTree(BTree T) 
	if(T==NULL)
		return false;				//递归结束
	else 
		PostTree(T->lchild);		//递归继续遍历该结点的左子树
		PostTree(T->rchild);		//递归继续遍历该结点的右子树
		printf("%c ",T->data);		//输出当前结点的数据域
		return true;
	


/*主函数*/
int main() 
	BTree T;
	T=NULL;
	printf("请输入二叉树的根结点:");
	T=CreateTree();		//建立二叉树
	printf("建立的二叉树如下:\\n");
	ShowTree(T);		//通过广义表显示二叉树
	printf("\\n");
	printf("先序:\\n"); 
	ProTree(T);
	printf("\\n");
	printf("中序:\\n"); 
	InTree(T);
	printf("\\n");
	printf("后序:\\n"); 
	PostTree(T);

通过代码验证如下,正确:

(二)由中序遍历和后序遍历

1、二叉树的中序遍历中,首先是遍历完根结点的左子树,然后是根结点,最后遍历完根结点的右子树,依次下去至所有结点都遍历到;
2、二叉树的后序遍历中,首先是遍历完根结点的左子树,然后遍历完根结点的右子树,最后是根结点,依次下去至所有结点都遍历到,也就是从二叉树的底层往上层依次遍历。

  • 根据后序遍历,找到最后一个结点为根结点,然后由中序遍历确定左、右子树,然后再通过中序遍历来确定左、右子树,其思路也是分别寻找左子树和右子树的根结点,并将左、右子树的根结点连在双亲结点后,依次进行下去。

例如,已知中序序列为DBEAFCG,后序序列为DEBFGCA,由先序遍历和中序遍历恢复二叉树。
从后序序列的最后一个结点确定二叉树的根结点:A,如下:

通过中序遍历来确定左、右子树,其中结点A之前的所有结点都是根结点左子树结点,其后都是根结点右子树结点,如下:

然后继续对中序序列和后序序列中的左、右子树进行相应进一步的分解:

可得到如下二叉树:

  • ✨结论:由先序序列和中序序列后序序列和中序序列层次序列和中序序列可以唯一确定一棵二叉树,但不能由先序序列和后序序列唯一确定一棵二叉树。

例、已知某二叉树的后序遍历序列为DABEC,中序遍历序列为DEBAC,求其先序遍历序列。

由后序遍历序列可知C为二叉树的根结点,然后在中序遍历序列中,C结点之前为根结点的左子树,由于C后面无结点,则说明C结点只有左子树而无右子树:

然后再对后序遍历序列DABE和中序遍历序列DEAB,

此时还未确定结点A、B,根据后序遍历序列AB,可知B为根结点,在中序遍历序列中,结点A在B的右子树,所以二叉树如下:

由上图所恢复的二叉树可得,先序遍历序列为CEDBA
通过代码验证,正确:

二、非递归先、中、后序遍历二叉树

之前,在先序、中序、后序遍历二叉树中,都采用了递归算法,由于递归算法中的每一次函数调用都会在内存栈中分配空间,且要做保护现场和恢复现场等一系列操作,会产生溢出,且很低效会影响效率,所以我们可以对遍历二叉树进行进一步改进,即通过非递归的算法来完成,其思路是不使用系统内部的栈来完成遍历,而是使用用户自定义的栈算法来代替,从而提升效率。

系统栈处理记录访问过的结点信息之外,还有其他信息需要记录,以实现函数的递归调用,而用户自定义的栈仅保存了遍历所需的结点信息。

例如以下图二叉树为例:

(一)先序遍历二叉树的非递归算法

二叉树的先序遍历(DLR)
二叉树的先序遍历中,首先是根结点,遍历完根结点的左子树,然后再遍历完根结点的右子树,依次下去至所有结点都遍历到。

分析一下具体的实现步骤:
由于我们要自己定义一个栈,初始时,栈为空。首先,结点A入栈,然后结点A再出栈,输出遍历序列为A;然后将输出结点A的左、右孩子结点入栈,由于二叉树的性质,先排左子树,所以对左孩子的遍历优先级大于右孩子,将结点A的左、右孩子结点(左孩子B和右孩子C)入栈,其中右孩子C先入栈,左孩子B再入栈,因为后入栈的结点会先出栈(栈的性质)被遍历,当前栈内序列由下至上为CB,此时结点B出栈,输出遍历序列为AB,此时将结点B的左、右孩子结点入栈(左孩子D和右孩子E);继续执行出栈,先将右孩子E先入栈,左孩子D再入栈,当前栈内序列由下至上为CED,此时结点D出栈,输出遍历序列为ABD,由于D结点无左、右孩子结点,然后再看结点E,也无,结点E出栈,输出遍历序列为ABDE;回到结点C,结点C出栈,输出遍历序列为ABDEC;然后将输出结点C的左、右孩子结点入栈,先将右孩子G入栈,然后再是左孩子F,当前栈内序列由下至上为GF,此时结点F出栈,输出遍历序列为ABDECF,由于F结点无左、右孩子结点,然后再看结点G,也无,结点G出栈,最终栈为空,输出遍历序列为ABDECFG

先序遍历二叉树的非递归算法代码如下:

/*先序遍历二叉树【非递归】*/
void ProTree1(BTree T) 
	SqStack *S;
	S=InitStack();		//初始化栈S 
	BNode *p=T;			//p指针从二叉树的根结点开始 
	while(p!=NULL||!StackEmpty(*S)) 	//当栈S不为空或遍历指针p不为空时循环一直进行下去 
		if(p!=NULL) 	//左子树遍历 
			printf("%c ",p->data);	//访问当前结点 
			StackPush(S,p);			//当前结点入栈 
			p=p->lchild;			//继续向左孩子 
		 else 		//右子树遍历 
			StackPop(S,p);			//栈顶元素出栈 
			p=p->rchild;			//继续向右孩子 
		
	

完整代码:

#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 100
/*1、二叉树的定义*/
typedef struct BNode 
	char data;		//数据域,类型为char
	struct BNode *lchild,*rchild;		//左孩子、右孩子指针
 *BTree;

/*2、顺序栈的定义*/
typedef struct SqStack 
	BTree data[MAXSIZE];	//存放栈中元素 ,使用数组,数组类型为BTree
	int top;			//栈顶指针 ,记录栈顶元素的位置
 *Stack;	//顺序栈的类型定义

/*3、判断顺序栈是否为空*/
bool StackEmpty(Stack S) 
	if(S->top==-1)	//当top=-1时,栈为空,而并不是S.top=0(它表示的是栈中有一个元素).
		return true;
	else
		return false;


/*4、顺序栈的初始化,初始化一个空栈*/
Stack InitStack() 
	Stack S;
	S=(Stack)malloc(sizeof(SqStack));	//定义一个根结点S
	S->top=-1;	//顺序栈为空
	return S;


/*5、进栈*/
bool StackPush(Stack S,BTree p) 
	if(S->top==MAXSIZE-1)	//若栈已满,则报错
		return false;
	++S->top;				//top指针始终指向栈顶,新的元素进栈,所以指针先加1
	S->data[S->top]=p;		//将进栈元素的值传入并入栈
	//S->data[++S->top]=p;	//也可以用这一句代码替换上面两行代码
	return true;


/*6、出栈*/
bool StackPop(Stack S,BTree &p) 
	if(S->top==-1)			//若栈为空,则报错
		return false;
	p=S->data[S->top];		//出栈
	S->top--;				//指针减1
	//p=S->data[S->top--];	//也可以用这一句代码替换上面两行代码
	return true;


/*7、二叉树的建立*/
BTree CreateTree() 
	BTree T;
	char ch;
	scanf("%c",&ch);
	getchar();	//getchar()用于接收每次输入字符结点后的回车符,从而以便输入下一个字符结点
	if(ch=='0')	//当为0时,将结点置空
		T=NULL;
	else 
		T=(BTree)malloc(sizeof(BTree));	//分配一个新的结点
		T->data=ch;
		printf("请输入%c结点的左孩子结点:",T->data);
		T->lchild=CreateTree();		//通过递归建立左孩子结点
		printf("请输入%c结点的右孩子结点:",T->data);
		T->rchild=CreateTree();		//通过递归建立右孩子结点
	
	return T;


/*8、广义表输出二叉树*/
void ShowTree(BTree T) 
	if(T!=NULL) 
		//当二叉树不为空时
		printf("%c",T->data);	//输入出该结点的数据域
		if(T->lchild!=NULL) 		//若该结点的左子树不为空
			printf("(");	//输出一个左括号
			ShowTree(T->lchild);	//通过递归继续输出结点的左子树结点下的各结点
			if(T->rchild!=NULL) 	//若该结点右子树不为空
				printf(",");	//输出一个逗号
				ShowTree(T->rchild);	//通过递归继续输出结点的右子树结点下的各结点
			
			printf(")");	//输出一个右括号
		 else 	//若左子树为空,右子树不为空
			if(T->rchild!=NULL) 
				printf("(");	//输出一个左括号
				ShowTree(T->lchild);	//通过递归继续输出结点的左子树结点下的各结点
				if(T->rchild!=NULL) 		//若该结点的右子树不为空
					printf(",");	//输出一个逗号
					ShowTree(T->rchild);	//通过递归继续输出结点的右子树结点下的各结点
				
				printf(")");	//输出一个右括号
			
		
	


/*9、先序遍历二叉树【非递归】*/
void ProTree1(BTree T) 
	SqStack *S;
	S=InitStack();		//初始化栈S
	BNode *p=T;			//p指针从二叉树的根结点开始
	while(p!=NULL||!StackEmpty(S)) 	//当栈S不为空或遍历指针p不为空时循环一直进行下去
		if(p!=NULL) 	//左子树遍历
			printf("%c ",p->data);	//访问当前结点
			StackPush(S,p);			//当前结点入栈
			p=p->lchild;			//继续向左孩子
		 else 		//右子树遍历
			StackPop(S,p);			//栈顶元素出栈
			p=p->rchild;			//继续向右孩子
		
	


/*主函数*/
int main() 
	BTree T;
	T=NULL;
	printf("请输入二叉树的根结点:");
	T=CreateTree();		//建立二叉树
	printf("建立的二叉树如下:\\n");
	ShowTree(T);		//通过广义表显示二叉树
	printf("\\n");
	printf("先序:\\n");
	ProTree1(T);		//非递归先序遍历二叉树

运行结果如下:

(二)中序遍历二叉树的非递归算法

二叉树的中序遍历(LDR)
二叉树的中序遍历中,首先是遍历完根结点的左子树,然后是根结点,最后遍历完根结点的右子树,依次下去至所有结点都遍历到。

分析一下具体的实现步骤:
由于我们要自己定义一个栈,初始时,栈为空。首先,结点A入栈,看根结点A的左孩子,左孩子存在为结点B,结点B入栈,当前栈内序列由下至上为AB;然后看结点B的左孩子,左孩子存在为结点D,结点D入栈,当前栈内序列由下至上为ABD;由于结点D的左孩子不存在,此时栈顶元素出栈,即结点D出栈,当前输出遍历序列为D;结点D无右孩子结点,此时栈顶元素出栈,即结点B出栈,当前输出遍历序列为DB;然后遍历结点B的右子树,由于结点B的右孩子存在为结点E,结点E入栈,当前栈内序列由下至上为AE,由于结点E的左孩子不存在,此时栈顶元素出栈,当前输出遍历序列为DBE;结点E无右孩子结点,此时栈顶元素出栈,即结点A出栈,当前输出遍历序列为DBEA,根结点A的左子树和根结点遍历完成,此时遍历右子树。看根结点A的右孩子,右孩子存在为结点C,结点C入栈,当前栈内序列为C,看结点C的左孩子,左孩子存在为结点F,结点F入栈,当前栈内序列由下至上为CF,然后看结点F的左孩子;由于结点F的左孩子不存在,此时栈顶元素出栈,即结点F出栈,当前输出遍历序列为DBEAF;结点F无右孩子结点,此时栈顶元素出栈,即结点C出栈,当前输出遍历序列为DBEAFC;然后遍历结点C的右子树,由于结点C的右孩子存在为结点G,结点G入栈,由于结点G的左孩子不存在,此时栈顶元素出栈,即结点G出栈,其右孩子不存在,最终栈为空,输出遍历序列为DBEAFCG

由于中序遍历是先遍历左子树,依次根结点,然后右子树,所以在当前结点的左孩子为空时,栈顶元素出栈,输出当前结点,只需对先序遍历的非递归代码进行部分改动,中序遍历二叉树的非递归算法代码如下:

/*中序遍历二叉树【非递归】*/
void InTree1(BTree T) 
	SqStack *S;
	S=InitStack();		//初始化栈S
	BNode *p=T;			//p指针从二叉树的根结点开始
	while(p!=NULL||!StackEmpty(*S)) 	//当栈S不为空或遍历指针p不为空时循环一直进行下去
		if(p!=NULL) 	//左子树遍历
			StackPush(S,p);			//当前结点入栈
			p=p->lchild;			//继续向左孩子
		 else 		//右子树遍历
			StackPop(S,p);			//栈顶元素出栈
			printf("%c ",p->data);	//访问当前结点
			p=p->rchild;			//继续向右孩子
		
	

完整代码:

#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 100
/*1、二叉树的定义*/
typedef struct BNode 
	char data;		//数据域,类型为char
	struct BNode *lchild,*rchild;		//左孩子、右孩子指针
 *BTree;

/*2、顺序栈的定义*/
typedef struct SqStack 
	BTree data[MAXSIZE];	//存放栈中元素 ,使用数组,数组类型为BTree
	int top;			//栈顶指针 ,记录栈顶元素的位置
 *Stack;	//顺序栈的类型定义

/*3、判断顺序栈是否为空*/
bool StackEmpty(Stack S) 
	if(S->top==-1)	//当top=-1时,栈为空,而并不是S.top=0(它表示的是栈中有一个元素).
		return true;
	else
		return false;


/*4、顺序栈的初始化,初始化一个空栈*/
Stack InitStack() 数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树

学习笔记非递归实现先后根遍历二叉树

二叉树几种遍历算法的非递归实现

五分钟C语言数据结构 之 二叉树后序遍历(非递归很重要)

数据结构学习笔记(二叉树)总结与整理

数据结构学习笔记(二叉树)总结与整理