总目录:https://blog.youkuaiyun.com/treesorshining/article/details/125726400
1.说明
相比于递归实现的遍历算法,非递归算法增加了一个辅助栈,根据栈后进先出的特点,来实现非递归遍历。
2.算法实现
2.1 先序遍历(非递归)
如果结点不为空就先访问结点,然后将结点压入栈中;然后进行遍历,直至存在左孩子为NULL的结点为止;此时,由于是先访问,此左节点之上的双亲节点,双亲结点的双亲节点(如果有)以及访问完毕了,即是运用了传统先序遍历的根左右思想。 之后弹出栈顶结点,若其右孩子不为NULL则进入其右子树进行上述操作,直至某个结点有孩子为NULL为止, 若其右孩子为NULL则继续弹出栈顶结点,然后继续上述操作,直至退出循环为止。
// 先序遍历(非递归)
void PreOrder(BiTree T) {
LinkStack S;
// 初始化栈
InitStack(S);
// 遍历指针
BiTree p = T;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
// 如果结点不为空就先访问结点,然后将结点压入栈中
// 然后进行遍历,直至存在左孩子为NULL的结点为止
// 此时,由于是先访问,此左节点之上的双亲节点,双亲结点的双亲节点(如果有)以及访问完毕了
// 即是运用了传统先序遍历的根左右思想
// 之后弹出栈顶结点
// 若其右孩子不为NULL则进入其右子树进行上述操作,直至某个结点有孩子为NULL为止
// 若其右孩子为NULL则继续弹出栈顶结点,然后继续上述操作,直至退出循环为止
if(p) {
visit(p);
Push(S, p);
p = p->lchild;
} else {
Pop(S, p);
p = p->rchild;
}
}
}
2.2 中序遍历(非递归)
如果结点不为空,则先将结点压入栈中, 然后先遍历此结点下所有左孩子,直至某一个左孩子为NULL,然后弹出栈顶结点并访问,若其右孩子不为NULL则进入其右子树进行上述操作,直至某个结点有孩子为NULL为止,若其右孩子为NULL则继续弹出栈顶结点进行访问,然后继续上述操作,直至退出循环为止。思路即是中序遍历传统思路,先访问左节点,再访问根节点,最后访问右节点。
// 中序遍历(非递归)
void InOrder(BiTree T) {
LinkStack S;
// 初始化栈
InitStack(S);
// 遍历指针
BiTree p = T;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
// 如果结点不为空,则先将结点压入栈中
// 然后先遍历此结点下所有左孩子,直至某一个左孩子为NULL
// 然后弹出栈顶结点并访问
// 若其右孩子不为NULL则进入其右子树进行上述操作,直至某个结点有孩子为NULL为止
// 若其右孩子为NULL则继续弹出栈顶结点进行访问,然后继续上述操作,直至退出循环为止
// 思路即是中序遍历传统思路,先访问左节点,再访问根节点,最后访问右节点
if(p) {
Push(S, p);
p = p->lchild;
} else {
Pop(S, p);
visit(p);
p = p->rchild;
}
}
}
2.3 后序遍历(非递归)
// 后序遍历(非递归)
void PostOrder(BiTree T) {
LinkStack S;
// 初始化栈
InitStack(S);
// 遍历指针
BiTree p = T;
// 辅助指针,用于记录最近访问过的结点
BiTree r = NULL;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
// 如果结点不为空就先访问结点,然后将结点压入栈中
// 然后进行遍历,直至存在左孩子为NULL的结点为止
// 然后读取栈顶结点,如果其右孩子不为NULL且未被访问过,则以上述方法访问右子树
// 否则栈顶结点出栈并进行访问
// 此思路主要就是由于后序需要先访问完左右结点后才访问根结点
// 所以不能类似于前序、中序一样,遍历到左子树最后一个左结点就可以开始出栈并访问
// 访问完左子树后,不能直接出栈,而是要获取栈顶结点,对其右子树进行上述处理
// 直至栈顶元素右子树为空或者右子树已经访问完毕,则可进行出栈访问
if(p) {
Push(S, p);
p = p->lchild;
} else {
// 注意:此处是读取栈顶结点,而并非出栈
GetTop(S, p);
// 若右子树存在且未被访问过
// 则以上述步骤开始访问其右子树
// 否则,将栈顶结点出栈并访问
if(p->rchild && p->rchild != r) {
p = p->rchild;
} else {
Pop(S, p);
visit(p);
// 记录最近访问过的结点
r = p;
// 结点访问完成后,重置p指针
// 由于每次出栈访问完一个结点就相当于遍历完以该结点为根的子树,所以需要将p重置
p = NULL;
}
}
}
}
2.4 后序遍历2(非递归)
可以以另一种较为巧妙的方式实现,可知后序遍历结果为左右根,先序遍历结果为根左右,可将先序遍历循环中左右全部互换,结果应为根右左,然后将结果全部取反,可得左右根,即后序遍历结果。但需要额外设立一个栈,增加了空间开销。
// 后序遍历(非递归)
// 可以以另一种较为巧妙的方式实现
// 可知后序遍历结果为左右根,先序遍历结果为根左右
// 可将先序遍历循环中左右全部互换,结果应为根右左
// 然后将结果全部取反,可得左右根,即后序遍历结果
// 但需要额外设立一个栈,增加了空间开销
void PostOrder1(BiTree T) {
LinkStack S;
// 额外设立一个辅助栈,用于存入将要输出结果的结点
// 由于栈的特性,根右左输入,应有左右根的输出
LinkStack temp;
// 初始化栈
InitStack(S);
InitStack(temp);
// 遍历指针
BiTree p = T;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
if(p) {
Push(temp, p);
Push(S, p);
p = p->rchild;
} else {
Pop(S, p);
p = p->lchild;
}
}
// 输出后序结果
while(!IsEmpty(temp)) {
Pop(temp, p);
visit(p);
}
}
3.完整代码
#include<stdio.h>
#include<stdlib.h>
typedef struct BiTNode {
// 数据域
char data;
// 左、右孩子指针
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 不带头结点
typedef struct LinkNode {
BiTNode* data; // 数据域
struct LinkNode *next; // 指针域
} LSNode, *LinkStack; // 栈类型定义
// 初始化
void InitTree(BiTree &T) {
// 初始化可将根节点置空,空树
T = NULL;
// 插入根节点
// 分配空间
T = (BiTree)malloc(sizeof(BiTree));
// 赋值
T->data = 'A';
// 初始无左右子树
T->lchild = NULL;
T->rchild = NULL;
}
// 左插入结点
bool InsertLeftTreeNode(BiTNode* &T, char x) {
// 分配空间
BiTNode *p = (BiTNode*)malloc(sizeof(BiTNode));
// 分配空间失败的情况
if(p == NULL) {
return false;
}
// 数据域赋值
p->data = x;
// 初始插入,无左右孩子,置空
p->lchild = NULL;
p->rchild = NULL;
// 作为指定插入结点的左孩子
T->lchild = p;
return true;
}
// 右插入结点
bool InsertRightTreeNode(BiTNode* &T, char x) {
// 分配空间
BiTNode *p = (BiTNode*)malloc(sizeof(BiTNode));
// 分配空间失败的情况
if(p == NULL) {
return false;
}
// 数据域赋值
p->data = x;
// 初始插入,无左右孩子,置空
p->lchild = NULL;
p->rchild = NULL;
// 作为指定插入结点的左孩子
T->rchild = p;
return true;
}
// 访问结点
void visit(BiTree T) {
printf("%c ", T->data);
}
// 树的深度
int treeDepth(BiTree T) {
// 根结点为空,则深度为0
if(T == NULL) {
return 0;
} else {
// 递归遍历左子树,获得左子树深度
int l = treeDepth(T->lchild);
// 递归遍历右子树,获得右子树深度
int r = treeDepth(T->rchild);
// 树的深度=Max{左子树深度,右子树深度} + 1
// 加1相当于访问根节点,包括根结点深度+1
return l > r ? l + 1 : r + 1;
}
}
// 初始化
void InitStack(LinkStack &S) {
// 此为不带头结点的栈,与单链表相似,初始将栈顶指针指向NULL即可
S = NULL;
}
// 判空
bool IsEmpty(LinkStack S) {
// 栈为空的条件即栈顶指针指向NULL
if(S == NULL) {
return true;
}
return false;
}
// 入栈操作
// 由于栈只能从一端操作,所以每次都应该是从栈顶入栈
bool Push(LinkStack &S, BiTNode* e) {
// 申请分配空间
LSNode *s = (LSNode*)malloc(sizeof(LSNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
s->data = e;
// 由于每次都是从栈顶指针处插入,所以无需特殊处理
// 令s指向S之前指向的地址
s->next = S;
// 使头指针指向s
S = s;
return true;
}
// 出栈操作
// 类似于单链表的删除操作,不过只能在栈顶进行出栈
bool Pop(LinkStack &S, BiTNode* &e) {
// 如果是空栈,则不可进行出栈操作
if(S == NULL) {
return false;
}
// 出栈结点即为栈顶指针指向的结点
// 将出栈结点值赋给e
e = S->data;
// 临时结点,用于之后释放空间
LSNode* p = S;
// 使头指针指向下一个结点
S = S->next;
// 释放空间
free(p);
return true;
}
// 获取栈顶元素
bool GetTop(LinkStack S, BiTNode* &e) {
// 栈为空的情况
if(S == NULL) {
return false;
}
e = S->data;
return true;
}
// 先序遍历(非递归)
void PreOrder(BiTree T) {
LinkStack S;
// 初始化栈
InitStack(S);
// 遍历指针
BiTree p = T;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
// 如果结点不为空就先访问结点,然后将结点压入栈中
// 然后进行遍历,直至存在左孩子为NULL的结点为止
// 此时,由于是先访问,此左节点之上的双亲节点,双亲结点的双亲节点(如果有)以及访问完毕了
// 即是运用了传统先序遍历的根左右思想
// 之后弹出栈顶结点
// 若其右孩子不为NULL则进入其右子树进行上述操作,直至某个结点有孩子为NULL为止
// 若其右孩子为NULL则继续弹出栈顶结点,然后继续上述操作,直至退出循环为止
if(p) {
visit(p);
Push(S, p);
p = p->lchild;
} else {
Pop(S, p);
p = p->rchild;
}
}
}
// 中序遍历(非递归)
void InOrder(BiTree T) {
LinkStack S;
// 初始化栈
InitStack(S);
// 遍历指针
BiTree p = T;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
// 如果结点不为空,则先将结点压入栈中
// 然后先遍历此结点下所有左孩子,直至某一个左孩子为NULL
// 然后弹出栈顶结点并访问
// 若其右孩子不为NULL则进入其右子树进行上述操作,直至某个结点有孩子为NULL为止
// 若其右孩子为NULL则继续弹出栈顶结点进行访问,然后继续上述操作,直至退出循环为止
// 思路即是中序遍历传统思路,先访问左节点,再访问根节点,最后访问右节点
if(p) {
Push(S, p);
p = p->lchild;
} else {
Pop(S, p);
visit(p);
p = p->rchild;
}
}
}
// 后序遍历(非递归)
void PostOrder(BiTree T) {
LinkStack S;
// 初始化栈
InitStack(S);
// 遍历指针
BiTree p = T;
// 辅助指针,用于记录最近访问过的结点
BiTree r = NULL;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
// 如果结点不为空就先访问结点,然后将结点压入栈中
// 然后进行遍历,直至存在左孩子为NULL的结点为止
// 然后读取栈顶结点,如果其右孩子不为NULL且未被访问过,则以上述方法访问右子树
// 否则栈顶结点出栈并进行访问
// 此思路主要就是由于后序需要先访问完左右结点后才访问根结点
// 所以不能类似于前序、中序一样,遍历到左子树最后一个左结点就可以开始出栈并访问
// 访问完左子树后,不能直接出栈,而是要获取栈顶结点,对其右子树进行上述处理
// 直至栈顶元素右子树为空或者右子树已经访问完毕,则可进行出栈访问
if(p) {
Push(S, p);
p = p->lchild;
} else {
// 注意:此处是读取栈顶结点,而并非出栈
GetTop(S, p);
// 若右子树存在且未被访问过
// 则以上述步骤开始访问其右子树
// 否则,将栈顶结点出栈并访问
if(p->rchild && p->rchild != r) {
p = p->rchild;
} else {
Pop(S, p);
visit(p);
// 记录最近访问过的结点
r = p;
// 结点访问完成后,重置p指针
// 由于每次出栈访问完一个结点就相当于遍历完以该结点为根的子树,所以需要将p重置
p = NULL;
}
}
}
}
// 后序遍历(非递归)
// 可以以另一种较为巧妙的方式实现
// 可知后序遍历结果为左右根,先序遍历结果为根左右
// 可将先序遍历循环中左右全部互换,结果应为根右左
// 然后将结果全部取反,可得左右根,即后序遍历结果
// 但需要额外设立一个栈,增加了空间开销
void PostOrder1(BiTree T) {
LinkStack S;
// 额外设立一个辅助栈,用于存入将要输出结果的结点
// 由于栈的特性,根右左输入,应有左右根的输出
LinkStack temp;
// 初始化栈
InitStack(S);
InitStack(temp);
// 遍历指针
BiTree p = T;
// 树和链表有一者不为空则循环继续进行
while(p || !IsEmpty(S)) {
if(p) {
Push(temp, p);
Push(S, p);
p = p->rchild;
} else {
Pop(S, p);
p = p->lchild;
}
}
// 输出后序结果
while(!IsEmpty(temp)) {
Pop(temp, p);
visit(p);
}
}
int main() {
BiTree T;
InitTree(T);
InsertLeftTreeNode(T, 'B');
InsertRightTreeNode(T, 'C');
InsertLeftTreeNode(T->lchild, 'D');
InsertRightTreeNode(T->lchild, 'E');
InsertLeftTreeNode(T->rchild, 'F');
InsertRightTreeNode(T->rchild, 'G');
printf("PreOrder\n");
PreOrder(T);
printf("\nInOrder\n");
InOrder(T);
printf("\nPostOrder\n");
PostOrder(T);
printf("\ntreeDepth:%d\n", treeDepth(T));
}