前序非递归
1.结点不空则先访问后入栈,而后沿着此结点的左孩子,
依次访问后并入栈,直到左孩子为空//说明左子树的根已经访问结束
2.栈顶元素出栈并访问右孩子,
若右孩子为空,则继续执行2 ;
若右孩子不空,则继续执行1 ;
void PreOrderNonR(BiTree T){
LinkStack S;
InitStack(S);
BiTree p=T;//初始化栈,p是遍历指针
while(p || !StackEmpty(S)){//栈不空或p不为空时循环
if(p){ //一路向左
visit(p);Push(S,p);//访问当前结点,并入栈
p=p->lchild;//左孩子不空,一直向左走
}
else{
//printf("# ");//p为空
Pop(S,p);//栈顶元素出栈,并转向出栈结点的右孩子
p=p->rchild;//向右子树走,p赋值为当前结点的右孩子
//返回while循环继续进行if-else语句
}
}
}
中序非递归
1.沿着根的孩子,依次入栈,直到左孩子为空,说明已经找到可以输出端 结点
2.栈顶元素出栈并访问:
若其右孩子为空,继续执行;
若其右孩子不空,将右子树转执行1.
void InOrderNonR(BiTree T){
LinkStack S;
InitStack(S);//初始化栈
BiTree p=T;//p是遍历指针
while(p || !StackEmpty(S)){//栈不空或p不空时循环
if(p){
Push(S,p);//当前结点入栈
p=p->lchild;//左孩子不空,一直向左走
}
else{
//printf("# ");//p为空
Pop(S,p);//出栈并访问,转向出栈结点的右子树
visit(p);//栈顶元素出栈,访问出栈结点
p=p->rchild;//向右子树走,p赋值为当前结点的右孩子
}
}
/*//不用销毁,因为Push过程动态分配,Pop过程动态销毁
if(StackEmpty(S)){
printf("\n\n栈空,那我要销毁了栈\n");
DestroyStack(S);
}*/
}
后序非递归
1.从根节点开始,将其入栈,然后沿着其左子树一直往下搜索,直到搜索到没有左孩子的结点。
但是此时不能出栈并访问,因为如果其有右子树,还需按相同的规则对其右子树进行处理
2.直至上述操作进行不下去,
若栈顶元素想要出栈被访问,要么右子树为空,要么右子树刚被访问过(此时左子树早已访问过了)
后序遍历:后序遍历较前两种遍历方法比较难实现,原因在于需要遍历完左子树,遍历完右子树,最后才去访问根节点。这样栈顶结点可能会从他的左子树返回,也有可能从他的右子树返回,需要区分这种情况,如果是第一次从左子树返回,那么还需要去遍历其右子树,如果是从右子树返回,那么直接返回该结点就可以了。这里使用辅助指针来区分来源。
要注意:在 visit( p ) 后
要将辅助指针r指向刚刚被访问过的结点,并将 p 赋值为NULL,因为既然p能被访问,说明p的左右结点均已被访问过了,则对于p来说,p是其父节点的右孩子,所以p访问过后,要从栈中取出下一个或许被访问的结点,故应将p = NULL
非递归后序遍历二叉树时,访问一个结点p时,栈中结点恰好是p结点的所有祖先,从栈底到栈顶再加上结点p,刚好构成从根节点到p结点的一条路径。在很多算法设计中都可以利用这一思路来求解
如:求根到某结点的路径,求两个结点的最近公共祖先等。
void PostOrderNonR(BiTree T){
LinkStack S;
InitStack(S);
BiTree p=T;//p是遍历指针
BiTree r=NULL;//辅助指针r,指向刚刚被访问过的结点
while(p || !StackEmpty(S)){
if(p){
Push(S,p);//左节点入栈
p=p->lchild;//沿着其左子树一直往下搜索,直到没有左孩子的结点
}
else{
GetTop(S,p);//@@@取栈顶,!!!注意,不是出栈!
if(p->rchild && p->rchild != r ){//@@@
//右子树还没有访问 且 右子树非空
p=p->rchild;//进入右子树
}
else{//右子树已经访问过 或 右子树为空,则出栈并访问结点
Pop(S,p);
visit(p);
r=p;//指向访问过的结点
p=NULL;//@@@ 使p为空,继续访问栈顶@@@
}
}
}
}
完整代码测试
完整代码如下所示:
#include<stdio.h>
#include<stdlib.h>
#define Elemtype BiTree
#define ElemType char
typedef struct BiTNode{//栈的链式存储
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
typedef struct LSNode{//链结点
Elemtype data;//数据域
struct LSNode *next;//指针域
}LSNode,*LinkStack;
//这里规定链栈没有头结点
void InitStack(LinkStack &s){
s=NULL;
}
bool StackEmpty(LinkStack s){
return s==NULL;
}
bool Push(LinkStack &s,Elemtype x){
if(s==NULL){//空栈 则此时入栈即为第一个元素
s=(LinkStack)malloc(sizeof(LSNode));
s->data=x;
s->next=NULL;
return true;
}
LSNode *p=(LSNode *)malloc(sizeof(LSNode));
if(p==NULL){
printf("p==NULL\n");
return false;
}
//偷天换日 用后插的方法来实现前插,即实现入栈
p->data=s->data;
p->next=s->next;
s->next=p;
s->data=x;
return true;
}
bool Pop(LinkStack &s,Elemtype &x){
if(s==NULL){
printf("s==NULL\n");
return false;
}
LSNode *p=s;
s=s->next;
x=p->data;
free(p);
p=NULL;
return true;
}
bool GetTop(LinkStack s, Elemtype &x){
if(s==NULL){
printf("空栈\n");
return false;
}
x=s->data;
return true;
}
bool DestroyStack(LinkStack &s){
if(s==NULL){
printf("栈不存在,无法销毁\n");
return false;
}
LSNode*p=s;
while(p!=NULL){
s=s->next;
free(p);
p=s;
}
p=NULL;
return true;
}
bool PrintStack(LinkStack s){
if(s==NULL){
printf("空栈,无法print\n");
return false;
}
printf("\nStack is: ");
LSNode *p=s;
while(p){
printf("%d ",p->data);
p=p->next;//@@@
}
printf("\n");
return true;
}
void visit(BiTree T){
printf("%c ",T->data);
}
//前序(先根遍历)非递归 Non-Recurrence
/*
1.结点不空则先访问后入栈,而后沿着此结点的左孩子,
依次访问后并入栈,直到左孩子为空//说明左子树的根已经访问结束
2.栈顶元素出栈并访问右孩子,
若右孩子为空,则继续执行2 ;
若右孩子不空,则继续执行1 ;
*/
void PreOrderNonR(BiTree T){
LinkStack S;
InitStack(S);
BiTree p=T;//初始化栈,p是遍历指针
while(p || !StackEmpty(S)){//栈不空或p不为空时循环
if(p){ //一路向左
visit(p);Push(S,p);//访问当前结点,并入栈
p=p->lchild;//左孩子不空,一直向左走
}
else{
//printf("# ");//p为空
Pop(S,p);//栈顶元素出栈,并转向出栈结点的右孩子
p=p->rchild;//向右子树走,p赋值为当前结点的右孩子
//返回while循环继续进行if-else语句
}
}
}
//中序非递归Non-Recurrence
/*
1.沿着根的孩子,依次入栈,直到左孩子为空,说明已经找到可以输出端 结点
2.栈顶元素出栈并访问:
若其右孩子为空,继续执行;
若其右孩子不空,将右子树转执行1.
*/
void InOrderNonR(BiTree T){
LinkStack S;
InitStack(S);//初始化栈
BiTree p=T;//p是遍历指针
while(p || !StackEmpty(S)){//栈不空或p不空时循环
if(p){
Push(S,p);//当前结点入栈
p=p->lchild;//左孩子不空,一直向左走
}
else{
//printf("# ");//p为空
Pop(S,p);//出栈并访问,转向出栈结点的右子树
visit(p);//栈顶元素出栈,访问出栈结点
p=p->rchild;//向右子树走,p赋值为当前结点的右孩子
}
}
/*//不用销毁,因为Push过程动态分配,Pop过程动态销毁
if(StackEmpty(S)){
printf("\n\n栈空,那我要销毁了栈\n");
DestroyStack(S);
}*/
}
//后序非递归 Non-Recurrence
/*左右根 要弄两个指针
1.从根节点开始,将其入栈,然后沿着其左子树一直往下搜索,直到搜索到没有左孩子的结点。
但是此时不能出栈并访问,因为如果其有右子树,还需按相同的规则对其右子树进行处理
2.直至上述操作进行不下去,
若栈顶元素想要出栈被访问,要么右子树为空,要么右子树刚被访问过(此时左子树早已访问过了)
*/
void PostOrderNonR(BiTree T){
LinkStack S;
InitStack(S);
BiTree p=T;//p是遍历指针
BiTree r=NULL;//辅助指针r,指向刚刚被访问过的结点
while(p || !StackEmpty(S)){
if(p){
Push(S,p);//左节点入栈
p=p->lchild;//沿着其左子树一直往下搜索,直到没有左孩子的结点
}
else{
GetTop(S,p);//@@@取栈顶,!!!注意,不是出栈!
if(p->rchild && p->rchild != r ){//@@@
//右子树还没有访问 且 右子树非空
p=p->rchild;//进入右子树
}
else{//右子树已经访问过 或 右子树为空,则出栈并访问结点
Pop(S,p);
visit(p);
r=p;//指向访问过的结点
p=NULL;//@@@ 使p为空,继续访问栈顶@@@
}
}
}
}
//按前序输入二叉树中节点的值(一个字符)
// ' '表示空树, 构造二叉链表表示二叉树T
bool CreateBiTree(BiTree &T){
ElemType ch;
scanf("%c",&ch);//@@@
if(ch == '#'){
//printf("您要创建一棵空树吗?\n");
T=NULL;//
return false;
}
else{
T=(BiTree)malloc(sizeof(BiTNode));
if(!T){
printf("malloc failure\n");
return false;
}
T->data=ch;//生成根节点
CreateBiTree(T->lchild);//构造左子树
CreateBiTree(T->rchild);//构造右子树
return true;
}
}
bool DestroyBiTree(BiTree T){//@@@
if(T == NULL){
//printf("空结点#\n");
return false;
}
DestroyBiTree(T->lchild);
DestroyBiTree(T->rchild);
printf("销毁%c\n",T->data);
free(T);//@@@
T=NULL;//防止产生野指针
return true;
}
int main(){
BiTree T=NULL;//@@@
printf("按前序输入二叉树中节点的值(输入#表示空节点)\n");
CreateBiTree(T);
printf("NonRecurrence前序遍历结果为:\n");
PreOrderNonR(T);
printf("\n");
printf("NonRecurrence非递归中序遍历结果为:\n");
InOrderNonR(T);
printf("\n");
printf("NonRecurrence非递归后序遍历结果为:\n");
PostOrderNonR(T);
printf("\n\n开始destroy二叉树(按后序遍历来销毁):\n\n");
DestroyBiTree(T);
return 0;
}
在输入窗口输入: AB#D##C##