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

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

一、由遍历恢复二叉树


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


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

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


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

例如,已知先序序列为ABDECFG,中序序列为DBEAFCG,由先序遍历和中序遍历恢复二叉树。

从先序序列确定二叉树的根结点:A,如下:

1667293084942.jpg

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

1667293099641.jpg

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

1667293107046.jpg

可得到如下二叉树:

1667293114174.jpg


#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);
}


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

1667293146592.jpg


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


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

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


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

例如,已知中序序列为DBEAFCG,后序序列为DEBFGCA,由先序遍历和中序遍历恢复二叉树。

从后序序列的最后一个结点确定二叉树的根结点:A,如下:

1667293169289.jpg

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

1667293179647.jpg

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

1667293191425.jpg

可得到如下二叉树:

1667293198834.jpg


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

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


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

1667293210776.jpg

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

1667293222331.jpg

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

1667293233901.jpg

由上图所恢复的二叉树可得,先序遍历序列为CEDBA。

通过代码验证,正确:

1667293242994.jpg


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


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


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


例如以下图二叉树为例:

1667293255373.jpg


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


二叉树的先序遍历(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。

1667293270308.jpg

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

/*先序遍历二叉树【非递归】*/
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);  //非递归先序遍历二叉树
}


运行结果如下:

1667293354069.jpg


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


二叉树的中序遍历(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。

1667293366382.jpg

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

/*中序遍历二叉树【非递归】*/
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() {
  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;    //继续向右孩子
  }
  }
}
/*10、中序遍历二叉树【非递归】*/
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;    //继续向右孩子
  }
  }
}
/*主函数*/
int main() {
  BTree T;
  T=NULL;
  printf("请输入二叉树的根结点:");
  T=CreateTree();  //建立二叉树
  printf("建立的二叉树如下:\n");
  ShowTree(T);  //通过广义表显示二叉树
  printf("\n");
  printf("先序:\n");
  ProTree1(T);  //非递归先序遍历二叉树
  printf("\n");
  printf("中序:\n");
  InTree1(T);   //非递归中序遍历二叉树
}


运行结果如下:

1667293403206.jpg


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


二叉树的后序遍历(LRD)

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


根据前面的思想,以及后序遍历的规则,应该从二叉树的根结点的左孩子开始,直到访问左孩子为空;然后读取当前栈顶元素,若当前根结点的右孩子不为空且未被访问,则开始转为右孩子,直到访问右孩子为空,否则栈顶元素出栈。

/*读取栈顶元素*/
bool StackGetTop(Stack S,BTree &p) {
  if(S->top==-1)    //若栈为空,则报错
  return false;
  p=S->data[S->top];  //p记录栈顶元素
  return true;
}


由于要记录当前返回的是左子树还是右子树,以下采用一个辅助指针t,指向最近访问过的结点,如下是后序遍历二叉树的非递归算法代码:

/*后序遍历二叉树【非递归】*/
void PostTree1(BTree T) {
  SqStack *S;
  S=InitStack();  //初始化栈S
  BNode *p=T;   //p指针从二叉树的根结点开始
  BNode *t=NULL;
  while(p!=NULL||!StackEmpty(S)) {  //当栈S不为空或遍历指针p不为空时循环一直进行下去
  if(p!=NULL) {    //左子树遍历
    StackPush(S,p);   //当前结点入栈
    p=p->lchild;    //继续向左孩子
  } else {      //右子树遍历
    StackGetTop(S,p);  //读取当前栈顶结点 
    if(p->rchild&&p->rchild!=t)  //若当前结点的右子树存在,且未被访问过 
    p=p->rchild;    //转向开始访问右子树 
    else {
    StackPop(S,p);    //栈顶元素出栈
    printf("%c ",p->data);  //访问当前结点
    t=p;      //t指针记录最近访问的结点 
    p=NULL;     //访问后,重置p指针 
    }
  }
  }
}


1667293440394.jpg

完整代码:

#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、读取栈顶元素*/
bool StackGetTop(Stack S,BTree &p) {
  if(S->top==-1)    //若栈为空,则报错
  return false;
  p=S->data[S->top];  //p记录栈顶元素
  return true;
}
/*8、二叉树的建立*/
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;
}
/*9、广义表输出二叉树*/
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(")");  //输出一个右括号
    }
  }
  }
}
/*10、先序遍历二叉树【非递归】*/
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;    //继续向右孩子
  }
  }
}
/*11、中序遍历二叉树【非递归】*/
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;    //继续向右孩子
  }
  }
}
/*12、后序遍历二叉树【非递归】*/
void PostTree1(BTree T) {
  SqStack *S;
  S=InitStack();  //初始化栈S
  BNode *p=T;   //p指针从二叉树的根结点开始
  BNode *t=NULL;
  while(p!=NULL||!StackEmpty(S)) {  //当栈S不为空或遍历指针p不为空时循环一直进行下去
  if(p!=NULL) {    //左子树遍历
    StackPush(S,p);   //当前结点入栈
    p=p->lchild;    //继续向左孩子
  } else {      //右子树遍历
    StackGetTop(S,p);  //读取当前栈顶结点 
    if(p->rchild&&p->rchild!=t)  //若当前结点的右子树存在,且未被访问过 
    p=p->rchild;    //转向开始访问右子树 
    else {
    StackPop(S,p);    //栈顶元素出栈
    printf("%c ",p->data);  //访问当前结点
    t=p;      //t指针记录最近访问的结点 
    p=NULL;     //访问后,重置p指针 
    }
  }
  }
}
/*主函数*/
int main() {
  BTree T;
  T=NULL;
  printf("请输入二叉树的根结点:");
  T=CreateTree();  //建立二叉树
  printf("建立的二叉树如下:\n");
  ShowTree(T);  //通过广义表显示二叉树
  printf("\n");
  printf("先序:\n");
  ProTree1(T);  //非递归先序遍历二叉树
  printf("\n");
  printf("中序:\n");
  InTree1(T);
  printf("\n");
  printf("后序:\n");
  PostTree1(T);
}

运行结果如下:

1667293010974.jpg

相关文章
|
6天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
15 1
|
14天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
63 8
|
1月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
19 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
1月前
|
存储 算法 搜索推荐
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
这篇文章主要介绍了顺序存储二叉树和线索化二叉树的概念、特点、实现方式以及应用场景。
20 0
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
|
1月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
15天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
8天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
11天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
13天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
41 4
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器

热门文章

最新文章