前言:在上一篇中介绍了二叉树的基础知识,这一篇介绍遍历二叉树的实现
一、二叉树的存储结构:
二叉树的存储结构可以采用顺序存储,也可以采用链式存储,其中链式存储更加灵活。
在链式存储结构中,与线性链表类似,二叉树的每个结点采用结构体表示,结构体包含三个域:数据域、左指针、右指针。
二、二叉树的遍历:
二叉树实现需要用到的数据结构代码如下:
struct BiTNode{ //定义二叉树
char data; //每个结点的数据
BiTNode *lchild, *rchild; //左右孩子指针
};
struct Stack{ //定义栈
int top; //栈顶指针
BiTNode *stacksize[100]; //栈的容量
};
void InitStack(Stack &S) //初始化一个空栈
{
S.top = -1;
}
int Push(Stack &S, BiTNode *pt) //元素进栈
{
S.stacksize[++S.top] = pt;
return 1;
}
BiTNode * Pop(Stack &S) //元素出栈
{
BiTNode *pt;
pt = S.stacksize[S.top--];
return pt;
}
BiTNode * GetTop(Stack S) //获取栈顶元素
{
BiTNode *pt;
if (S.top == -1)
return 0;
else
{
pt = S.stacksize[S.top];
return pt;
}
}
int IsEmptyStack(Stack S) //判断是否空栈,空栈返回1,否则返回0
{
return S.top == -1;
}
二叉树遍历通常借用“栈”这种数据结构实现,有两种方式:递归方式及非递归方式。
(1)在递归方式中,栈是由操作系统维护的,用户不必关心栈的细节操作,用户只需关心“访问顺序”即可。因而,采用递归方式实现二叉树的遍历比较容易理解,算法简单,容易实现。
(2)借用“栈”采用非递归方式,也能实现遍历。但是,这时的栈操作(push、pop等)是由用户进行的,因而实现起来会复杂一些,而且也不容易理解,但有助于我们对树结构的遍历有一个深刻、清晰的理解。(a)非递归先序遍历:
在遍历某一个二叉(子)树时,以一当前指针记录当前要处理的二叉(左子)树,以一个栈保存当前树之后处理的右子树。首先访问当前树的根结点数据,接下来应该依次遍历其左子树和右子树,然而程序的控制流只能处理其一,所以考虑将右子树的根保存在栈里面,当前指针则指向需先处理的左子树,为下次循环做准备;若当前指针指向的树为空,说明当前树为空树,不需要做任何处理,直接弹出栈顶的子树,为下次循环做准备,代码如下
int PreOrderTraverse(BiTNode *T) //二叉树先序遍历的非递归算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
cout << pt->data;
Push(S, pt->rchild); //右子树进栈,让当前指针指向左子树,为下一个循环做准备
pt = pt->lchild;
}
else
pt=Pop(S); //左子树为空,不做任何处理,直接弹出右子树,为下一个循环准备
}
return 1; //遍历完成
}
相对于非递归先序遍历,非递归的中序/后序遍历稍复杂一点。
(b)非递归中序遍历:
若当前树不为空树,则访问其根结点之前应先访问其左子树,因而先将当前根节点入栈,然后考虑其左子树,不断将非空的根节点入栈,直到左子树为一空树;当左子树为空时,不需要做任何处理,弹出并访问栈顶结点,然后指向其右子树,为下次循环做准备。代码如下:
int InOrderTraverse(BiTNode *T) //二叉树中序非递归遍历
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
Push(S, pt); //访问根节点之前先访问左结点,所以现将根节点进栈
pt = pt->lchild;
}
else //根指针退栈,访问根节点,遍历右子树
{
pt=Pop(S);
cout << pt->data;
pt = pt->rchild; //访问根节点之后,访问右子树
}
}
return 1;
}
(c)后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问,并且左孩子在右孩子之前访问才能访问根结f点,这就为流程控制带来了难题。下面介绍一种思路。
要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点p,先将其入栈。若p不存在左孩子和右孩子,则可以直接访问它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已经被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将p的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子之前别访问,左孩子和右孩子都在根结点前面被访问。代码如下:
int PostOrderTraverse(BiTNode *T) //二叉树的后序非递归算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
BiTNode *pre, *cur;
pre = NULL;
if (!pt) //空树
{
cout << "the tree is null" << endl;
return 0;
}
Push(S, pt);
while (IsEmptyStack(S)!=1)
{
cur =GetTop(S); //获取栈顶元素
if ((cur->lchild == NULL&&cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
{
//如果当前结点没有孩子或者左右孩子都已经访问过,就直接访问该结点
cout << cur->data;
Pop(S);
pre = cur; //标识它成为上一个被访问的元素
}
else
{
if (cur->rchild != NULL) //当前结点既有左右孩子没有被访问,那么就该结点就进栈
Push(S, cur->rchild);
if (cur->lchild != NULL)
Push(S, cur->lchild);
}
}
return 1;
}
三、完整程序代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
struct BiTNode{ //定义二叉树
char data; //每个结点的数据
BiTNode *lchild, *rchild; //左右孩子指针
};
struct Stack{ //定义栈
int top; //栈顶指针
BiTNode *stacksize[100]; //栈的容量
};
void InitStack(Stack &S) //初始化一个空栈
{
S.top = -1;
}
int Push(Stack &S, BiTNode *pt) //元素进栈
{
S.stacksize[++S.top] = pt;
return 1;
}
BiTNode * Pop(Stack &S) //元素出栈
{
BiTNode *pt;
pt = S.stacksize[S.top--];
return pt;
}
BiTNode * GetTop(Stack S) //获取栈顶元素
{
BiTNode *pt;
if (S.top == -1)
return 0;
else
{
pt = S.stacksize[S.top];
return pt;
}
}
int IsEmptyStack(Stack S) //判断是否空栈,空栈返回1,否则返回0
{
return S.top == -1;
}
int PreOrderTraverse(BiTNode *T) //二叉树先序遍历的非递归算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
cout << pt->data;
Push(S, pt->rchild); //右子树进栈,让当前指针指向左子树,为下一个循环做准备
pt = pt->lchild;
}
else
pt=Pop(S); //左子树为空,不做任何处理,直接弹出右子树,为下一个循环准备
}
return 1; //遍历完成
}
int InOrderTraverse(BiTNode *T) //二叉树中序非递归遍历
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
Push(S, pt); //访问根节点之前先访问左结点,所以现将根节点进栈
pt = pt->lchild;
}
else //根指针退栈,访问根节点,遍历右子树
{
pt=Pop(S);
cout << pt->data;
pt = pt->rchild; //访问根节点之后,访问右子树
}
}
return 1;
}
int PostOrderTraverse(BiTNode *T) //二叉树的非递归算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
BiTNode *pre, *cur;
pre = NULL;
if (!pt)
{
cout << "the tree is null" << endl;
return 0;
}
Push(S, pt);
while (IsEmptyStack(S)!=1)
{
cur =GetTop(S); //获取栈顶元素
if ((cur->lchild == NULL&&cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
{
//如果当前结点没有孩子或者左右孩子都已经访问过,就直接访问该结点
cout << cur->data;
Pop(S);
pre = cur; //标识它成为上一个被访问的元素
}
else
{
if (cur->rchild != NULL) //当前结点既有左右孩子没有被访问,那么就该结点就进栈
Push(S, cur->rchild);
if (cur->lchild != NULL)
Push(S, cur->lchild);
}
}
return 1;
}
int CreateBiTree(BiTNode *&T) //采用先序遍历法创建二叉树
{
char c;
c = getchar();
if (c == '#') // 空树
T = NULL;
else
{
T = new BiTNode;
T->data = c;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return 1;
}
int _tmain(int argc, _TCHAR* argv[])
{
BiTNode *T = NULL;
cout << "先序输入字符创建一个二叉树:" << endl;
CreateBiTree(T);
cout << "前序遍历二叉树序列为:" << endl;
PreOrderTraverse(T);
cout << endl;
cout << "中序遍历二叉树序列为:" << endl;
InOrderTraverse(T);
cout << endl;
cout << "后序遍历二叉树序列为:" << endl;
PostOrderTraverse(T);
cout << endl;
return 0;
}
参考:严蔚敏《数据结构》(c语言版)