二叉树的遍历真的很有意思,而且递归和非递归的实现的思想都很精妙。真心觉得创造出这种数据结构的人是多么炫酷。
二叉树的遍历就是把所有节点都走一遍,通常有三种遍历方式,先序遍历:中左右;中序遍历:左中右;后序遍历:左右中
这些原理当然没啥深究的,记住就行。
#include <stdio.h>
#include <stdlib.h >
#include <malloc.h >
//二叉树
typedef struct ErNode {
int data;//数据域
struct ErNode *lchild, *rchild;//左右孩子
}ErNode, *pErNode;
//链表节点
typedef struct ZanNode {
pErNode nodedata;//二叉节点
struct ZanNode *pNext;//指针域
}ZanNode, *pZanNode;
//栈
typedef struct stack {
pZanNode pTop;//栈顶
pZanNode pBottom;//栈底
}stack, *pStack;
//======================================
//栈的代码就不解释 之前写过
//初始栈
void InitStack(pStack pS) {
pS->pTop = (pZanNode)malloc(sizeof(ZanNode));
if(!pS->pTop )
exit(-1);
pS->pBottom = pS->pTop ;
pS->pBottom->pNext = NULL ;
}//InitStack
//========================================
//判断空
bool isEmpty(pStack pS) {
if(pS->pBottom == pS->pTop )
return true;
else
return false;
}//isEmpty
//=====================================
//入栈
void Push(pStack pS,pErNode item) {
pZanNode pNew = (pZanNode )malloc(sizeof(ZanNode ));
pNew->nodedata = item;//这里指的是二叉树的节点
pNew->pNext = pS->pTop ;
pS->pTop = pNew;
}//Push
//==============================================
//出栈
void Pop(pStack pS, pErNode *item) {
if(isEmpty(pS)) {
printf("栈已空!\n");
return ;
}
pZanNode p = pS->pTop ;
*item = p->nodedata;
pS->pTop = p->pNext;
free(p);
}//pop
//==========================
//栈长
int length(pStack pS) {
if(isEmpty(pS)) {
printf("栈空\n");
return 0;
}
int len = 0;
pZanNode p = pS->pTop ;
while(p != pS->pBottom ) {
len++;
p = p->pNext ;
}//while
return 0;
}//length
//======================================
//遍历栈
void TraverseStack(pStack pS) {
pZanNode p = pS->pTop ;
while(p != pS->pBottom ) {
printf("%d ",p->nodedata->data);
p = p->pNext ;
}//while
}//TraverseStack
//栈部分完毕
//=============================================
//创建一棵树
pErNode Create() {
pErNode tree;
int item;//数据域
printf("请输入节点值:");
scanf("%d",&item);
if(0 == item) {//当输入为0时,节点或孩子为空,
//其实应该输入一个较不好的数比如65535之类的,不过我嫌太长了,测的时候烦
tree = NULL ;
}//if
else {
tree = (pErNode )malloc(sizeof(ErNode ));
if(!tree)
exit(-1);
tree->data = item;//树的节点赋值
printf("创建%d的左孩子:",item);
tree->lchild = Create();
printf("创建%d的右孩子:",item);
tree->rchild = Create();
printf("%d节点创建完毕\n",item);
}//else
return tree;
}//Create
//===================================================
//清空树
void ClearTree(pErNode tree) {
if(tree) {
if(tree->lchild)
ClearTree(tree->lchild);//递归清空左孩子
if(tree->rchild)
ClearTree(tree->rchild );//递归清空右孩子
free(tree);//左右都被清了之后
}
}//ClearTree
//=======================================================
//先序遍历
void PreTraverse(pErNode tree) {
if(tree) {
printf("%d ",tree->data );
PreTraverse(tree->lchild );
PreTraverse(tree->rchild );
}
}//PreTraverse
//========================================================
//先序遍历非递归
/*
先序遍历的规则是中左右,递归的定义其实很有趣,当我们从根节点遍历时,根节点就是中,然后
遍历左孩子,那么左孩子便为中。也就是说我们遍历一棵二叉树的时候,先序遍历永远先遍历完所有左孩子,
然后才去右边。
1
/ \
2 5
/ \ / \
3 4 7 8
\ /
9 6
要知道理论上所有上非递归都可以用栈非递归实现的(书上说的),
其实程序思想很好理解,就是把左孩子一直入栈(同时一路上把她给打印出来),
然后获得栈顶(下面的p,temp其实都是栈顶)也就是3,并出栈,最后把3的
右孩子循环回去。
我觉得程序的核心是判断条件上,while(p)判断在于入栈的时候,一直到左孩子为
空为止,while(p || !isEmpty(s))判断在于左孩子遍历到空的时候,是否存在
右孩子,有的话重复一遍程序,没有的话就要出栈了。
其实最形象的是流程就如右斜45度的尺子,/
/
/
/
向右下方移动一样,斜一层一层似的。
*/
void PreTraverseNoDiGui(pErNode tree) {
pErNode p = tree;
stack n;
pStack s = &n;
InitStack(s);//初始化栈
pErNode temp;//存放出栈的节点
while(p || !isEmpty(s)) {
while(p) {
Push(s, p);//只要左孩子树不空,就一直把左孩子入栈
printf("%d ",p->data );//打印根节点
p = p->lchild;
}//while
p = s->pTop->nodedata;//获取栈顶的节点
Pop(s,&temp);//出栈
p = p->rchild;
}//while
}//PreTraverseNoDiGui
//============================================================
//中序遍历
void MidTraverse(pErNode tree) {
if(tree) {
MidTraverse(tree->lchild);
printf("%d ",tree->data);
MidTraverse(tree->rchild);
}//if
}//MidTraverse
//=============================================================
//中序遍历非递归
/*
中序遍历和先序遍历的非递归程序几乎一样。唯一不一样就在打印节点值的地方,
先序遍历是在遍历左孩子的过程中,打印出节点值,而中序遍历是在左孩子入栈完后,
打印出栈顶节点值。
二者的执行过程是一样的,只不过我们看到的节点值顺序不一样而已。
*/
void MidTraverseNoDiGui(pErNode tree) {
pErNode p = tree ;
stack n;
pStack s = &n;
InitStack(s);
pErNode temp;
while(p || !isEmpty(s)) {
while(p) {
Push(s,p);
p = p->lchild;
}//while
p = s->pTop->nodedata;//栈顶
printf("%d ",p->data);//打印左孩子
Pop(s,&temp);
p = p->rchild;
}//while
}//MidTraverseNoDiGui
//====================================================
//后序遍历
void PostTraverse(pErNode tree) {
if(tree) {
PostTraverse(tree->lchild);
PostTraverse(tree->rchild);
printf("%d ",tree->data);
}//tree
}//PostTraverse
//=======================================================
//后序遍历非递归
/*
感觉后序遍历的非递归直接提升了一个档次。我看了很多人写的,自己也琢磨了好久,才给理解了。
1
/ \
2 5
/ \ / \
3 4 7 8
\ /
9 6
如果我们仍用之前的方法,我们访问左右中,那么其实我们的中一开始其实之前已经访问过了,
这时候又访问回来了,这就陷入了死循环了(访问9后访问3,获得栈顶然后右孩子9是真,就又访问9)。所以我们要考虑,在访问9,然后访问3之后,怎么
判断9其实已经访问过了,告诉3你的右孩子已经访问了,你可以出栈了。所以我们要在再一次要访问
9时跳过这一步。(可以看完程序在回来看这里)这里我就设置了一个pNew节点(值为0)放在这循环的中间,
这样访问9后访问3,再循环回来栈顶就是pNew了,代表先前的9已经访问过了,不能死循环了,
直接俩次出栈到了2.
所以这里的pNew的设置就有要求,具体程序里也说了。带数据走一遍更好理解。
*/
void PostTraverseNoDiGui(pErNode tree) {
pErNode p = tree;
stack n;
pStack s = &n;
pErNode temp;
pErNode temp1;
/*temp1作为一个用作标志节点存放在栈中,记得之前我建树的时候,说过节点值
为0代表空的,那么这时候我就用这个0节点值,来表示已经访问过的右孩子
蛮叼的我自己觉着。
*/
while(p) {
Push(s,p);
p = p->lchild ;
}//while
while(!isEmpty(s)) {
temp = s->pTop->nodedata;//栈顶节点(开始是最左的节点)
if(!temp->rchild) {
/*没有右孩子。这里体现了标志节点的(在下面)孩子是不为空的,
如果标志节点孩子设为空的话则不能判断这里的空是标志节点的还是真实节点的。
判断右孩子空不空,如果是空的,那么她也不曾会有标志节点标志她
则这里只需pop一次(当前最左节点)就行了。
*/
Pop(s, &temp1);
printf("%d ",temp1->data);//没有右孩子就把最左孩子打印出来
}//if
else if(temp->data == 0) {//说明这个是标志节点,即右节点已经访问过了
//如果有右孩子,那么不知道这个右孩子是标志节点的还是真实节点的
//所以靠节点值来判断
Pop(s,&temp1);//把标志节点先给出栈掉
Pop(s,&temp1);//这个才是有用的真实节点
p = temp1;
printf("<%d> ",temp1->data);//打印出根节点
}//else if
else {//右孩子存在且未访问过,于是访问右孩子
//创建一个新节点入栈,她用来标志她的下一个节点是已经访问过了
pErNode pNew = (pErNode )malloc(sizeof(ErNode ));
pNew->data = 0;
pNew->lchild = pNew;
pNew->rchild = pNew;//让pNew的孩子不空,只有值是0
Push(s,pNew);
p = temp->rchild;
while(p) {//把右孩子的所有左孩子入栈
Push(s,p);
p = p->lchild;
}//while
}//else
}//while
}//PostTraverseNoDiGui
//主函数
int main() {
pErNode p = Create();
printf("\n先序遍历:\n");
PreTraverse(p);
printf("\n非递归先序遍历:\n");
PreTraverseNoDiGui (p);
printf("\n中序遍历:\n");
MidTraverse(p);
printf("\n非递归中序遍历:\n");
MidTraverseNoDiGui (p);
printf("\n后序遍历:\n");
PostTraverse(p);
printf("\n非递归后序遍历:\n");
PostTraverseNoDiGui(p);
ClearTree(p);
return 0;
}