赞
踩
我们应明确,DLR的遍历法则需要运用到每一个结点上,即当用根结点访问到其左孩子结点后,其左孩子成为新的根结点,我们重新根据DLR顺序访问其左孩子的左孩子…如此重复,直到某左孩子结点没有它的左孩子,这才完成了DLR中对L的遍历访问。
此时此刻,我们停留在了二叉树最左端的叶子结点。
假设一位妈妈有两个孩子,开饭时妈妈能叫老大吃饭,必定也能叫老二吃饭。同理,能通过D结点遍历访问左孩子,必能通过D结点访问右孩子。
第一步结束后,我们对左孩子的访问结束,接下来我们通过该左孩子的D结点,去访问D结点的右孩子,对D结点的右孩子使用DLR法则进行左右子树的遍历,到这里,我们完成了三个结点的最小二叉树单元的前序遍历。接着,我们再找到当前D结点的D结点,再通过当前D结点的D结点找到右孩子,对D结点当前D结点的D结点的右孩子使用DLR法则进行左右子树的遍历…如是重复,从最左端的叶子结点处进行回溯
//先序递归
void PreOrder1(BTreeNode *b)
{
if(b!=NULL){
printf("%c",b->data); //访问根结点并打印
PreOrder1(b->lchild); //遍历处理左孩子
PreOrder1(b->rchild); //遍历处理左孩子
}
}
正所谓“大道至简,知易行难,知行合一,得到功成”。为了让读者深入理解前序遍历优美的C语言三行诗,Joe树皮建立了一棵A(B,C)的简单小树带读者一同把玩把玩。
调用PreOrder1(b)
当前b为A结点,当前实参为A结点
printf("%c",b->data);直接访问打印A结点的值
调用PreOrder1(b->lchild);访问到B结点
当前b为A结点,当前实参为B结点
printf("%c",b->data);直接访问打印B结点的值
调用PreOrder1(b->lchild);访问到NULL
当前b为B结点,当前实参为NULL
本次调用结束
调用PreOrder1(b->rchild);访问到NULL
当前b为B结点,当前实参为NULL
本次调用结束
完成对B的DLR
调用PreOrder1(b->rchild);访问到C结点
完成对A的DLR
当前b为A结点,当前实参为C结点
printf("%c",b->data);直接访问打印C结点的值
调用PreOrder1(b->lchild);访问到NULL
当前b为C结点,当前实参为NULL
本次调用结束
调用PreOrder1(b->rchild);访问到NULL
当前b为C结点,当前实参为NULL
本次调用结束
完成对C的DLR
1)重复相同的操作,毫无疑问,在代码中我们应使用while、for之类的循环语句实现。
2)而对于访问,通过之前分析我们不难发现,这实则是将D结点、L结点、R结点按照规定顺序依次存进一片连续的存储单元。3)还需明确一个关系:当前孩子结点是下一个的根结点,当前根结点是上一个孩子结点,将此关系映射到数组内发现这两个结点在数组中位于相邻存储单元,因此我们可借数组解决回溯的问题。
回忆之前学习的数据结构知识,什么是运用数组对元素进行基本操作?我们不妨大胆猜想------栈 !
按照这个思路继续分析下去,我们会发现:1)依次将元素存储进数组正是 入栈操作;2)当前访问的结点位于栈顶,我们将其打印后出栈,这正是取栈顶操作。3)每轮循环取栈顶操作之后,新的栈顶元素成为它按DLR顺序进栈的前一个结点,通过取栈顶操作,我们完成了对新栈顶元素R结点的回溯。4)除上述外,我们还需注意栈特有的先进后出的特点,在入栈操作时,先输出的应后入栈。
到这里,我想读者应该能明白:对二叉树的非递归操作,是通过栈的基本操作实现的。
//先序非递归 void PreOrder2(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 if(b!=NULL){ //(1)根结点进栈 //直接操作 /* s->top++; s->ST[s->top]=b; */ //Push函数操作 Push(&s,&b); //栈不为空循环进行 while(s->top>-1){ //(2)取栈顶元素(D结点) //直接操作 /* TopNode=s->ST[s->top]; s->top--; printf("%c",TopNode->data); */ //调用GetTop函数 printf("%c",GetTop(&s,&TopNode)->data); //因栈是先入后出 故DLR需先Push右子树再Push左子树 //(3)通过TopNode处理右子树 Push if(TopNode->rchild!=NULL){ //直接操作 /* s->top++; s->ST[s->top]=TopNode->rchild; */ //调用Push函数 PNode=TopNode->rchild; Push(&s,&PNode); } //(4)通过TopNode处理左子树 Push if(TopNode->lchild!=NULL){ //直接操作 /* s->top++; s->ST[s->top]=TopNode->lchild; */ //调用Push函数 PNode=TopNode->lchild; Push(&s,&PNode); } } } printf("\n"); StackDestory(&s); }
Step1:创建树A(B(D,E),C(,F))
(若无特殊声明,此后前中后序遍历都研究这棵树)
Step2:初始化虚拟栈(前中后序都需要初始化栈操作,故此操作在之后的中后序算法分析里省略)
Step3:手工操作实现先序遍历过程
打印结果:ABDECF
原理:先打印根结点的值,因栈是先入后出,根据DLR顺序得知需先右子树入栈再左子树入栈。
(1)根结点进栈
(栈Push 操作:top指针++,通过top指针控制ST数组下标存入根结点)
while(栈空)
{
(2)栈顶元素出栈并打印
(栈GetTop操作:当前栈顶元素出栈打印,top指针–)
(3)PNode指向当前栈顶元素以进行后续(4)(5)操作
(4)处理右子树
(栈Push 操作:右结点进栈,top指针++,通过top指针控制ST数组下标存入右结点。)
(5)处理左子树
(栈Push 操作:左结点进栈,top指针++,通过top指针控制ST数组下标存入左结点。)
}
}
(声明:栈操作的具体实现在中后序代码分析中省略)
//中序非递归 void MidOrder2(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 if(b!=NULL){ PNode=b; //栈不为空或者当前结点不为空循环继续 while(s->top>-1||PNode!=NULL){ //(1)左孩子遍历进栈 while(PNode!=NULL){ /* s->top++; s->ST[s->top]=PNode; */ Push(&s,&PNode); PNode=PNode->lchild; } //(2)栈顶结点的右孩子为空可直接出栈 /* TopNode=s->ST[s->top]; s->top--; */ printf("%c",GetTop(&s,&TopNode)->data); //(3)若不为空则访问右孩子 进入while循环进栈 PNode=TopNode->rchild; } } printf("\n"); StackDestory(&s); }
while(栈不为空||当前栈空但有右孩子未访问进栈)
{
每个结点根据LDR循环访问
while(当前根结点存在左(右)孩子){
(1)左(右)孩子遍历进栈
}
(2)栈顶结点直接出栈
(3)PNode保存当前栈顶结点以便后续执行(4)操作
(4)通过PNode访问栈顶结点右孩子,若右孩子存在则进栈
if(右孩子存在){
执行(1)操作,右孩子遍历进栈
}
}
//后序非递归1 void EndOrder2(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 BTreeNode *VisitedNode=NULL; //用于保存已访问结点 if(b!=NULL){ PNode=b; while(PNode!=NULL||s->top>-1){ //(1)左孩子遍历进栈 while(PNode!=NULL){ /* s->top++; s->ST[s->top]=PNode; */ Push(&s,&PNode); PNode=PNode->lchild; } //取栈顶 TopNode=s->ST[s->top]; //此处不能用GetTop因为top未变 //(2)栈顶元素若无右孩子或其右孩子已访问则可直接出栈 if(TopNode->rchild==NULL||VisitedNode==TopNode->rchild){ /* printf("%c",TopNode->data); s->top--; */ printf("%c",GetTop(&s,&TopNode)->data); VisitedNode=TopNode; //标记为已访问 便于下一轮D->rchild的判断 PNode=NULL; //无右孩子进栈PNode置空跳过while循环 } //(3)否则访问右孩子 到while循环进栈 else{ PNode=TopNode->rchild; } } } printf("\n"); StackDestory(&s); }
while(栈不为空||栈空但仍有右孩子未进栈)
{
每个结点根据LRD循环访问
while(当前根结点存在左(右)孩子 ){
(1)左(右)孩子遍历进栈
}
(2)TopNode指向栈顶元素(注意此处top未变)
(3)栈顶元素若无右孩子或其右孩子已访问则可直接出栈
if(栈顶元素若无右孩子||栈顶元素右孩子已访问){
栈顶元素直接出栈
将出栈结点标记为已访问结点
}
(4)否则访问右孩子 到while循环进栈
else{
执行(1)步骤操作,右孩子进栈
}
}
LRD可看成DRL的倒序,故我们可将结点按DLR遍历顺序先存入Traverse[MaxSize]数组 ,最后将数组元素倒序打印。
由此上述分析可知,我们对前序非递归遍历做出两处修改即可实现后序遍历
(1)LRD->DRL->DLR
根据栈先进后出的原理,在DLR我们应先访问进栈右孩子再进栈左孩子,但访问左右孩子具体操作代码实现与先序遍历完全一致,故我们只需要将访问左右孩子的两端代码交换顺序即可。
(2)输出->存储
在先序遍历时,各结点按DLR顺序进栈,每轮循环通过GetTop取栈顶元素问打印。会发现,与前序遍历不同,此时我们不需要打印,我们只需要将原来打印的元素存储起来,其余操作代码实现完全一致。
故我们可定义一个数组,去存储我们原来每轮循环需要打印结点,循环结束,该数组内元素便是按照DRL顺序进行存储,还原成LRD的顺序,我们只需要将此数组倒序输出即可。
//后序非递归2 倒序输出法 void EndOrder3(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 BTreeNode *Traverse[MaxSize]; //临时按顺序存储结点 int index=0 ; //用于记录Traverse数组下标 int i; //用于倒序输出 if(b!=NULL){ //(1)根结点进栈 Push(&s,&b); //栈不为空循环进行 while(s->top>-1){ //(2)取栈顶元素(D结点) Traverse[index++] =GetTop(&s,&TopNode); //因栈是先入后出 故DRl需先Push左子树再Push右子树 //(3)通过TopNode处理左子树 Push if(TopNode->lchild!=NULL){ PNode=TopNode->lchild; Push(&s,&PNode); } //(4)通过TopNode处理右子树 Push if(TopNode->rchild!=NULL){ PNode=TopNode->rchild; Push(&s,&PNode); } } } //倒序输出 for(i=index-1;i>=0;i--) { printf("%c",Traverse[i]->data); } printf("\n"); StackDestory(&s); }
#include<stdio.h> #include<malloc.h> #define MaxSize 50 //虚拟栈最大存储个数 #define MaxLength 50 //字符数组最大存储个数 typedef char ElemType;//定义数据类型 此处暂定为字符型 //二叉树存储结构 typedef struct node { ElemType data ;//数据域 struct node *lchild;//左孩子 struct node *rchild;//右孩子 }BTreeNode; //虚拟栈的存储结构 typedef struct { int top; //栈顶指针 BTreeNode*ST[MaxSize]; //结构体类型的指针数组BTreeNode* 保存指向结点的结构体指针 }SqStack; //创建新结点函数 void CreatNewNode(BTreeNode **b,ElemType data) { (*b)=(BTreeNode*)malloc(sizeof(BTreeNode)); (*b)->data=data; (*b)->lchild=NULL; (*b)->rchild=NULL; } //初始化虚拟栈 void StackInit(SqStack**s) { (*s)=(SqStack*)malloc(sizeof(SqStack)); (*s)->top=-1; } //取栈顶 返回TopNode结点 //传入&TopNode才能使 GetTop中的TopNode与外部调用函数中的TopNode关联 BTreeNode* GetTop(SqStack**s,BTreeNode **TopNode) { (*TopNode)=(*s)->ST[(*s)->top]; (*s)->top--; return (*TopNode); } //入栈 void Push(SqStack**s,BTreeNode **PNode) { (*s)->top++; (*s)->ST[(*s)->top]=(*PNode); } //销毁虚拟栈 void StackDestory(SqStack**s) { free(*s); } //初始化二叉树 void BTreeInit(BTreeNode **b) { (*b)=(BTreeNode*)malloc(sizeof(BTreeNode)); (*b)->lchild=NULL; (*b)->rchild=NULL; } //建立二叉树 void BTreeCreat(BTreeNode **b,char *str) { int k; //用于标记左右子树(k=1左子树,k=2右子树) BTreeNode*q; //用于生成新结点 int j=0; //用于字符串数组的遍历 ElemType ch; //用于接收传进的字符 ch=str[j]; SqStack *s; //用于存储结点的虚拟栈 StackInit(&s); //初始化虚拟栈 (*b)=NULL; //先将b置空以从根结点重新建立二叉树 while(ch!='\0')//字符串未扫描到结尾 { switch(ch){ //入栈 top++ , '('前的结点进栈 ,k=1 case '(': s->top++; s->ST[s->top]=q; k=1; break; //出栈 top-- case ')': s->top--; break; //处理右孩子 k=2 case ',': k=2; break; //生成新结点并建立逻辑关系 default: CreatNewNode(&q,ch); //生成新结点 //b为空,则p为二叉树根结点 if((*b)==NULL){ (*b)=q; } else{ switch(k){ case 1: s->ST[s->top]->lchild=q;//k=1 q为左孩子 break; case 2: s->ST[s->top]->rchild=q;//k=2 q为右孩子 break; } } } //扫描字符串下一个元素 j++; ch=str[j]; } //释放临时虚拟栈存储空间 StackDestory(&s); } //打印二叉树 void BTreePrint(BTreeNode *b) { if(b!=NULL){ //b不为空 打印根结点 printf("%c",b->data); //有孩子 打印"()" if(b->lchild!=NULL||b->rchild!=NULL){ printf("("); //递归处理左孩子 BTreePrint(b->lchild); //如果有右孩子打印"," if(b->rchild!=NULL){ printf(","); } //递归处理右孩子 BTreePrint(b->rchild); printf(")"); } } } //先序递归 void PreOrder1(BTreeNode *b) { if(b!=NULL){ printf("%c",b->data); //访问根结点并打印 PreOrder1(b->lchild); //遍历处理左孩子 PreOrder1(b->rchild); //遍历处理左孩子 } } //先序非递归 void PreOrder2(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 if(b!=NULL){ //(1)根结点进栈 //直接操作 /* s->top++; s->ST[s->top]=b; */ //Push函数操作 Push(&s,&b); //栈不为空循环进行 while(s->top>-1){ //(2)取栈顶元素(D结点) //直接操作 /* TopNode=s->ST[s->top]; s->top--; printf("%c",TopNode->data); */ //调用GetTop函数 printf("%c",GetTop(&s,&TopNode)->data); //因栈是先入后出 故DLR需先Push右子树再Push左子树 //(3)通过TopNode处理右子树 Push if(TopNode->rchild!=NULL){ //直接操作 /* s->top++; s->ST[s->top]=TopNode->rchild; */ //调用Push函数 PNode=TopNode->rchild; Push(&s,&PNode); } //(4)通过TopNode处理左子树 Push if(TopNode->lchild!=NULL){ //直接操作 /* s->top++; s->ST[s->top]=TopNode->lchild; */ //调用Push函数 PNode=TopNode->lchild; Push(&s,&PNode); } } } printf("\n"); StackDestory(&s); } //中序递归 void MidOrder1(BTreeNode *b) { if(b!=NULL){ MidOrder1(b->lchild); //遍历处理左孩子 printf("%c",b->data); //访问根结点并打印 MidOrder1(b->rchild); //遍历处理左孩子 } } //中序非递归 void MidOrder2(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 if(b!=NULL){ PNode=b; //栈不为空或者当前结点不为空循环继续 while(s->top>-1||PNode!=NULL){ //(1)左孩子遍历进栈 while(PNode!=NULL){ /* s->top++; s->ST[s->top]=PNode; */ Push(&s,&PNode); PNode=PNode->lchild; } //(2)栈顶结点的右孩子为空可直接出栈 /* TopNode=s->ST[s->top]; s->top--; */ printf("%c",GetTop(&s,&TopNode)->data); //(3)若不为空则访问右孩子 进入while循环进栈 PNode=TopNode->rchild; } } printf("\n"); StackDestory(&s); } //后序递归 void EndOrder1(BTreeNode *b) { if(b!=NULL){ EndOrder1(b->lchild); //遍历处理左孩子 EndOrder1(b->rchild); //遍历处理左孩子 printf("%c",b->data); //访问根结点并打印 } } //后序非递归1 void EndOrder2(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 BTreeNode *VisitedNode=NULL; //用于保存已访问结点 if(b!=NULL){ PNode=b; while(PNode!=NULL||s->top>-1){ //(1)左孩子遍历进栈 while(PNode!=NULL){ /* s->top++; s->ST[s->top]=PNode; */ Push(&s,&PNode); PNode=PNode->lchild; } //取栈顶 TopNode=s->ST[s->top]; //此处不能用GetTop因为top未变 //(2)栈顶元素若无右孩子或其右孩子已访问则可直接出栈 if(TopNode->rchild==NULL||VisitedNode==TopNode->rchild){ /* printf("%c",TopNode->data); s->top--; */ printf("%c",GetTop(&s,&TopNode)->data); VisitedNode=TopNode; //标记为已访问 便于下一轮D->rchild的判断 PNode=NULL; //无右孩子进栈PNode置空跳过while循环 } //(3)否则访问右孩子 到while循环进栈 else{ PNode=TopNode->rchild; } } } printf("\n"); StackDestory(&s); } /* 方法分析: LRD可看成DRL的倒序输出 故我们可将结点按DLR遍历顺序先存入Traverse[MaxSize]数组 最后将数值元素倒序打印 */ //后序非递归2 倒序输出法 void EndOrder3(BTreeNode *b) { SqStack *s; //创建虚拟栈 StackInit(&s); BTreeNode *TopNode; //用于读取栈顶元素结点 BTreeNode *PNode; //用于读取目标操作结点 BTreeNode *Traverse[MaxSize]; //临时按顺序存储结点 int index=0 ; //用于记录Traverse数组下标 int i; //用于倒序输出 if(b!=NULL){ //(1)根结点进栈 Push(&s,&b); //栈不为空循环进行 while(s->top>-1){ //(2)取栈顶元素(D结点) Traverse[index++] =GetTop(&s,&TopNode); //因栈是先入后出 故DRl需先Push左子树再Push右子树 //(3)通过TopNode处理左子树 Push if(TopNode->lchild!=NULL){ PNode=TopNode->lchild; Push(&s,&PNode); } //(4)通过TopNode处理右子树 Push if(TopNode->rchild!=NULL){ PNode=TopNode->rchild; Push(&s,&PNode); } } } //倒序输出 for(i=index-1;i>=0;i--) { printf("%c",Traverse[i]->data); } printf("\n"); StackDestory(&s); } int main() { BTreeNode*b; printf("(1)初始化二叉树\n"); BTreeInit(&b); printf("(2)创建二叉树A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))\n"); BTreeCreat(&b,"A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))"); printf("(3)打印二叉树\n"); printf("创建的二叉树为:"); BTreePrint(b); printf("\n"); //1为递归 2为非递归 printf("(4)前序遍历结果为:\n"); // PreOrder1(b); printf("\n"); PreOrder2(b); printf("(5)中序遍历结果为:\n"); // MidOrder1(b); printf("\n"); MidOrder2(b); printf("(6)后序遍历结果为:\n"); // EndOrder1(b); printf("\n"); EndOrder2(b); // EndOrder3(b); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。