数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树
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() 数据结构学习笔记——由遍历恢复二叉树以及非递归遍历二叉树