前言
本文作为我学习数据结构的笔记,若有不足请见谅或麻烦提出宝贵意见。
1.什么是二叉树的周游
周游是将二叉树的所有结点访问一遍,且每个结点只被访问一次,按照被访问的次序,得到结点的一组线性序列。
其种类可分为:深度优先遍历、广度优先遍历。这里主要讲深度优先遍历,其中,深度优先遍历按照根被访问的时间,可分为:
- DLR先根遍历:根左右
- LDR中根遍历:左根右
- LRD后根遍历:左右根
2.遍历算法
非递归的遍历算法主要依靠栈来实现。
2.1 先根遍历
2.1.1 算法思想:
1)从根p开始,访问p并让p进栈;置p为左子树,重复直到p为空;
2)置p为栈顶的右子树,栈顶退栈;
3)重复1、2,直到p空且栈空。
注意先根序列是边访问边进栈,这是和中根遍历的区别所在
2.1.2 程序实现
首先定义二叉树的数据结构:
// 二叉树存储结构
struct BinTreeNode; // 结点类型
typedef struct BinTreeNode * PBinTreeNode; // 结点指针类型
struct BinTreeNode
{
char info; // 结点信息
PBinTreeNode llink; // 左孩子
PBinTreeNode rlink; // 右孩子
} ;
typedef struct BinTreeNode * BinTree; // 二叉树类型
typedef BinTree * PBinTree; // 二叉树指针类型
辅助栈的数据结构和功能函数。
栈的数据结构:
// 栈的存储结构
struct SeqStack
{
int MaxNum; // 栈的容量
int top; // 栈顶下标
PBinTreeNode * s; // 结点数组
} ;
typedef struct SeqStack * PSeqStack; // 栈的指针
栈的功能函数:
// 栈的功能函数
// 建立空栈
PSeqStack createEmptyStack(int m)
{
PSeqStack p = (PSeqStack)malloc(sizeof(struct SeqStack)); // 给栈申请空间
if(p != NULL)
{
p->s = (PBinTreeNode*)malloc(sizeof(PBinTreeNode)*m);
if(p != NULL)
{
p->MaxNum = m;
p->top = -1;
return p;
}
else free(p); // 申请失败
}
printf("Out of space!\n");
return NULL;
}
// 判空
int isEmptyStack(PSeqStack p)
{
return (p->top == -1);
}
// 入栈
void push(PSeqStack p,PBinTreeNode x)
{
if(p->top >= p->MaxNum - 1) // 判满
printf("Overflow!\n");
else
{
p->top = p->top + 1;
p->s[p->top] = x;
}
return;
}
// 出栈
void pop(PSeqStack p)
{
if(isEmptyStack(p)) // 判空
return;
else
{
p->top = p->top - 1; // 出栈直接将栈顶下标减1,让栈直接丢弃被删除的元素
}
}
// 取栈顶
PBinTreeNode top(PSeqStack p)
{
if(isEmptyStack(p))
return NULL;
else
return (p->s[p->top]);
}
二叉树功能函数:
// 二叉树的相关函数
// 输入扩充二叉树的先序序列,递归建立二叉树
PBinTreeNode create_BTree()
{
PBinTreeNode t; // 指向树根的t
char x;
scanf("%c", &x); // 输入结点,左孩子或右孩子为空就输入$
if(x == '$') return NULL; // 递归出口
else
{
t = (PBinTreeNode)malloc(sizeof(struct BinTreeNode));
t->info = x;
t->llink = create_BTree();
t->rlink = create_BTree();
}
return t;
}
// 访问
void visit(PBinTreeNode p)
{
printf("%c", p->info);
return;
}
// 获得左孩子
PBinTreeNode leftchild(PBinTreeNode p)
{
return p->llink;
}
// 获得右孩子
PBinTreeNode rightchild(PBinTreeNode p)
{
return p->rlink;
}
先根遍历代码:
// 先序遍历
void PreOrder(BinTree p)
{
PSeqStack s = createEmptyStack(M); // 建立辅助栈
BinTree t;
t = p; // t指向树根
if(t == NULL) return;
do
{
while(t != NULL) // 直到二叉树最左下角的结点
{
visit(t);
push(s, t);
t = leftchild(t);
}
t = top(s); // 取栈顶
t = rightchild(t); // 置其为右孩子
pop(s); // 栈顶退栈
}while(!isEmptyStack(s) || t!=NULL); // 直到栈空且t空
}
}
2.2 中根遍历
2.2.1 算法思想
中根遍历和先根差不多,主要是什么时候访问的区别。
1)置树根为当前结点p;
2)若p不为空,p进栈,置p为左孩子;
3)重复2,直到p为空;
4)当栈不空,置p为栈顶的右孩子,访问栈顶,退栈,返回2;
5)若栈空且p为空,结束。
2.2.2 程序实现
// 中根遍历
void InOrder(BinTree p)
{
PSeqStack s = createEmptyStack(M); // 建立辅助栈
BinTree t;
t = p; // t指向树根
if(t == NULL) return;
do
{
while(t != NULL) // 直到二叉树最左下角的结点
{
push(s, t);
t = leftchild(t);
}
t = top(s); //取栈顶
visit(t);
t = rightchild(t); // 置其为右孩子
pop(s); // 栈顶退栈
}while(!isEmptyStack(s) || t!=NULL); // 直到栈空且t空
return;
}
2.3 后根遍历
这里后根遍历就跟先根和中根不太一样啦,它的实现要麻烦一些,具体如下:
当是中根遍历时,结点进栈不访问,向左下方走不动时再访问栈顶,去栈顶右孩子处,退栈;
而后根遍历是,结点进栈不访问,向左下方走不动时,求助栈顶,这时有两种情况:
1)若结点没有右子树或右子树被访问过了,则访问栈顶,栈顶出栈;
2)否则去右子树进行遍历,因为后根遍历需要先访问右子树,再访问根。
2.3.1 算法思想
1)p指向树根;
2)若p不为空,p进栈,置p为左孩子;
3)重复2,直到p为空;
4)当栈不空,求助栈顶,即p=栈顶;
a、栈顶无右孩子,或右孩子刚被访问,访问栈顶,退栈,令p=NULL(再次求助栈顶,避免再次向下遍历左孩子)
b、否则,置p为右孩子;
5)若栈空且p为空,结束。
2.3.2 程序实现
// 后根遍历
void PostOrder(BinTree p)
{
PSeqStack s = createEmptyStack(M); // 建立辅助栈
BinTree t,q=NULL; // q用来指向上一个访问的结点
t = p; // t指向树根
if(t == NULL) return;
do
{
while(t != NULL) // 直到二叉树最左下角的结点
{
push(s, t);
t = leftchild(t);
}
t = top(s); // 求助栈顶
if(rightchild(t) == NULL || rightchild(t) == q) // 没有右孩子或右孩子刚访问过了
{
visit(t);
q = t; // 保存上一个访问的结点
pop(s); // 栈顶退栈
t = NULL; // 避免重复向下访问左孩子,重新求助栈顶
}
else t = rightchild(t);
}while(!isEmptyStack(s) || t!=NULL); // 直到栈空且t空
return;
}
2.4 主函数测试
main函数如下:
int main(void)
{
BinTree t;
printf("请输入该二叉树的扩充二叉树的先序序列:\n");
t = create_BTree();
printf("该二叉树的先根序列为:\n");
PreOrder(t);
printf("\n该二叉树的中根序列为:\n");
InOrder(t);
printf("\n该二叉树的后根序列为:\n");
PostOrder(t);
return 0;
}
输出:
这里是二叉树的图
参考:
算法与数据结构——c语言描述(第3版)张乃孝等著
图片来源老师的课件