一:前言
二叉树的遍历方法分四种:前序,中序,后序以及层次遍历。
其中,前中后遍历方法的实现分递归和非递归,非递归遍历的实现需要借助于栈。
实际上,递归的调用就是一种栈的实现,所以,非递归遍历就需要人工借助栈结构来实现。
而层次遍历需要借助队列。
二:前中后序遍历
递归遍历:
递归遍历的思想和方法很简单,通过调整输出语句来实现前,中,后三种遍历。
代码如下:
1 void show(BiTree T) 2 { 3 if(T) 4 { 5 printf("%c ",T->data);//修改这三个语句的顺序分别实现三种遍历 6 show(T->lchild); 7 show(T->rchild); 8 } 9 }
非递归遍历:
总结了5总不同的非递归遍历算法,四种思路,都需要借助栈来完成。
第一种遍历算法:
算法的思路:1.从根结点开始,若当前结点的左孩子不为空,则遍历左孩子并进栈。
2.结点的左孩子为空时,出栈并遍历当前结点的右孩子结点
3.以右孩子结点为根结点,又沿着左孩子不断遍历,重复1.2步骤
此算法为前序遍历
1 void preorder1(BiTree T)//前序遍历1 2 { 3 LinkStack s; 4 InitStack(&s); 5 BiTree temp; 6 temp = T; 7 while(temp || !EmptyStack(s)) //第一个条件temp是为了进入循环 8 { 9 if(temp) //一直遍历左子树到底部 10 { 11 Push(&s,temp); 12 printf("%c",temp->data); 13 temp = temp->lchild; 14 } 15 else //lchild为空,跳转到rchild 16 { 17 Pop(&s,&temp); 18 temp = temp->rchild; 19 } 20 } 21 }
第二种遍历算法:
算法思路与第一种遍历算法一致,只是改变了遍历语句的位置,不是在进栈的时候输出,而是在出栈的时候输出,前序遍历则为中序遍历
1.从根结点开始,当前结点左孩子不为空时,左孩子进栈。
2.当前结点左孩子为空时,出栈并输出当前结点,然后跳转该结点的右孩子
3.重复1,2步骤直到栈空
1 void inorder(BiTree T)//中序遍历 2 { 3 LinkStack s; 4 InitStack(&s); 5 BiTree temp = T; 6 while(temp || !EmptyStack(s)) 7 { 8 if(temp) 9 { 10 Push(&s,temp); 11 temp = temp->lchild; 12 } 13 else 14 { 15 Pop(&s,&temp); 16 printf("%c ",temp->data); //出栈时输出则为中序遍历 17 temp = temp->rchild; 18 } 19 } 20 }
第三种遍历算法:
此种算法为前序遍历
算法思路:1.先将根结点入栈
2.出栈栈顶并输出,先入栈当前结点的右孩子结点,在入栈当前结点的左孩子结点
3.重复第2步直到栈空
1 void preorder2(BiTree T) 2 { 3 LinkStack s; 4 InitStack(&s); 5 BiTree temp = T; 6 Push(&s,temp); 7 while(!EmptyStack(s)) 8 { 9 Pop(&s,&temp); //出栈顶并输出 10 printf("%c ",temp->data); 11 if(temp->rchild) //入栈右孩子 12 { 13 Push(&s,temp->rchild); 14 } 15 if(temp->lchild) //入栈左孩子 16 { 17 Push(&s,temp->lchild); 18 } 19 } 20 }
第四种遍历算法:
后序遍历的非递归实现比较复杂,要保证输出当前结点时左右孩子结点已经输出(或者左右孩子为空)
即每输出一个结点要满足以下两个条件之一:
1.此结点的左右孩子为空
2.此结点的左右孩子已经遍历
算法思路:1.先将根结点入栈,以栈空为循环停止的条件
2. 用结点指针pre记录上一个输出结点地址,cur记录当前结点地址
2.1 当前结点的左右孩子为空时,或者pre记录的结点为当前结点的左(右)孩子时,出栈并输出当前结点,并更新pre
2.2 否则按照右孩子,左孩子的顺序入栈
3.循环第2步直到栈空
1 void postorder(BiTree T) 2 { 3 BiTree pre,cur; 4 LinkStack s; 5 InitStack(&s); 6 cur = pre = T; 7 Push(&s,cur); 8 while(!EmptyStack(s)) 9 { 10 GetTop(s,&cur); //当前结点的左右孩子都为空,pre为cur的左孩子或者右孩子时,出栈并输出当前结点 11 if((cur->lchild == NULL && cur->rchild == NULL) || cur->lchild == pre || cur->rchild == pre) 12 { 13 Pop(&s,&cur); 14 printf("%c ",cur->data); 15 pre = cur; //更新当前结点 16 } 17 else 18 { 19 if(cur->rchild) //否则入栈右孩子,左孩子 20 { 21 Push(&s,cur->rchild); 22 } 23 if(cur->lchild) 24 { 25 Push(&s,cur->lchild); 26 } 27 } 28 } 29 }
第五种遍历算法:
此种非递归遍历算法思想可以像递归遍历一样通过修改部分的顺序做到:前中后序三种遍历。
不过便利往往带来的是代码结构上的复杂,此种算法所借助的栈和上面的遍历算法借助的栈不同。
上面的算法使用的栈存储的数据时一个二叉树结点指针,而此算法栈储存的是一个结构体
栈数据类型定义:
typedef struct { // 栈数据定义 BiTree ptr; int task; }Datatype;
其中,task有两个值,0表示访问,1表示遍历。所有结点的task的初始值为1
算法思路:
1.根结点先进栈,task初始化为1
2.出栈,若当前结点的task为0 则访问当前结点,task为1,则修改task为0,并按想要的遍历顺序入栈左右孩子结点。
例如:若中序遍历,则先入栈右孩子结点,当前结点,左孩子结点。(其他遍历顺序则改一下入栈顺序)
3.直到栈空,停止循环
此算法完整代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <conio.h> 4 typedef struct binode{ //二叉树结点定义 5 char data; 6 struct binode * lchild,*rchild; 7 }BiNode,*BiTree; 8 9 10 //非递归遍历所要用到的栈 11 #define MAX 50 12 typedef struct { // 栈数据定义 13 BiTree ptr; 14 int task; 15 }Datatype; 16 17 18 typedef struct { 19 Datatype * arr; 20 int top; 21 int stacksize; 22 }SqStack;//顺序栈 23 24 void InitStack(SqStack *S) //初始化字符栈 25 { 26 S->arr = (Datatype*)malloc(sizeof(Datatype)*MAX); 27 if(S->arr == NULL) 28 { 29 printf("初始化失败....\n"); 30 exit(0); 31 } 32 S->top = -1; 33 S->stacksize = MAX; 34 } 35 36 37 int Push(SqStack *S,Datatype ch)//进栈(字符栈) 38 { 39 Datatype *p; 40 int select; 41 if(S->top+1 == S->stacksize) //先判断栈是否已满 42 { 43 printf("栈的容量已满,无法进栈"); 44 return 0; 45 } 46 S->arr[++S->top] = ch; 47 return 1; 48 } 49 50 int Pop(SqStack *S,Datatype *ch)//出栈(字符栈) 51 { 52 if(S->top < 0) 53 { 54 printf("栈为空....\n"); 55 return 0; 56 } 57 *ch = S->arr[S->top]; 58 S->top--; 59 return 1; 60 } 61 62 int GetTopStack(SqStack S,Datatype *ch) //取字符栈顶 63 { 64 if(S.top < 0) 65 { 66 return 0; 67 } 68 *ch = S.arr[S.top]; 69 return 1; 70 } 71 72 int EmptyStack(SqStack S) //判断栈空 73 { 74 if(S.top < 0) 75 { 76 return 1; 77 } 78 return 0; 79 } 80 81 /******************************栈结束*********************************/ 82 83 void CreateBitree(BiTree *T) //前序创建二叉树 84 { 85 char ch; 86 scanf(" %c",&ch); 87 getchar(); 88 if(ch == ‘#‘) 89 { 90 *T = NULL; 91 } 92 else 93 { 94 *T =(BiTree)malloc(sizeof(BiNode)); 95 (*T)->data = ch; 96 CreateBitree(&((*T)->lchild)); 97 CreateBitree(&((*T)->rchild)); 98 } 99 } 100 101 102 void View(BiTree T)//非递归遍历 实现不同的遍历顺序修改第一个else语句的结点入栈顺序 103 { 104 Datatype temp; 105 BiTree p; 106 temp.task = 1; 107 temp.ptr = T; 108 SqStack S; 109 InitStack(&S); 110 if(T == NULL) return ; 111 Push(&S,temp); 112 while(!EmptyStack(S)) 113 { 114 Pop(&S,&temp); //先把根出栈 115 p = temp.ptr; //记录根地址 116 if(temp.task == 0) //task为0 则访问输出 117 { 118 printf("%c ",temp.ptr->data); 119 } 120 else //task为1 则将孩子进栈 121 { 122 if(p->rchild) //右孩子进栈 123 { 124 temp.task = 1; 125 temp.ptr = p->rchild; 126 Push(&S,temp); 127 } 128 temp.task = 0; //根的状态由遍历改为访问,然后进栈 129 temp.ptr = p; 130 Push(&S,temp); 131 if(p->lchild) //左孩子进栈 132 { 133 temp.ptr = p->lchild; 134 temp.task = 1; 135 Push(&S,temp); 136 } 137 } 138 } 139 printf("\n"); 140 } 141 142 int main()//二叉树创建:a b d # # e # # c f # # g # # 143 { 144 BiTree T; 145 CreateBitree(&T); 146 printf("中序遍历为:"); 147 View(T); 148 return 0; 149 }
三:层次遍历
层次遍历需要使用队列
算法思路比较结单,即对每个结点按左孩子,右孩子的顺序进队列即可,出队列时访问即为层次遍历。
此处就不赘述算法思路步骤了,直接上代码。
1 void DispLayer(BiTree T) //层次遍历 2 { 3 LinkQueue rear; //装二叉树的结点地址的队列 4 BiTree view = NULL; 5 InitQueue(&rear); 6 if(T == NULL) 7 { 8 return ; 9 } 10 EnQueue(&rear,T); //先将总树根地址装进去 11 while(!EmptyQueue(rear)) //队列为空则遍历完毕 12 { 13 DeQueue(&rear,&view); //小树根出队列 14 printf("%c ",view->data); //输出小树根 15 if(view->lchild) //小树根左右孩子存在则入队列 16 { 17 EnQueue(&rear,view->lchild); 18 } 19 if(view->rchild) 20 { 21 EnQueue(&rear,view->rchild); 22 } 23 } 24 printf("\n"); 25 }
以上即为二叉树的各种遍历算法。