数据结构:两种遍历方式, 递归和非递归,各有优缺点,都需要掌握。
一、辅助工具
这里需要使用前面文章讲到的栈,利用栈的先进后出的原则,去控制结点的访问。下面就是栈的建立
//栈的建立和基本操作
typedef struct Stack
{
RTreeNode * sa[MAX]; //存储的元素都是结点
int top;
}RStack;
//生成一个栈
RStack * init()
{
RStack *SS;
SS = (struct Stack*)malloc(sizeof(struct Stack));
SS->top = 0;
return SS;
}
//进栈
void push(RStack *p, RTreeNode *x)
{
if(p->top == MAX)
{
printf("栈满");
exit(0);
}
p->sa[p->top] = x;
++p->top;
}
//判断是否栈空
bool isEmpty(RStack p)
{
if(p.top == 0)
{
return true;
}
return false;
}
//出栈
RTreeNode *pop(RStack *p)
{
if(isEmpty(*p))
{
printf("栈空");
exit(0);
}
return p->sa[--p->top];
}
//清空栈
void clear(RStack *p)
{
p->top = 0;
}
RTreeNode *GetTop(Stack *S)
{
if(!isEmpty(*S))
{
return S->sa[S->top - 1]; //这里取栈顶元素不需要对栈进行操作
}
return NULL;
}
二、递归和非递归的区别
递归:优点简洁明了,代码少。缺点占用计算机资源,执行效率低。 非递归:优点:执行效率高,扩展性强,能够解决一些常见的问题。 缺点:代码多,理解起来比较困难。
三、三种非递归实现过程
1.前序非递归遍历
代码如下(示例):
前序非递归遍历(先遍历根,在遍历左,把右结点的存储在栈中,
最后进行打印 满足了根 左 右)
void front_f_d_preorder(RTreeNode *root)
{
RStack *S = init();
push(S, root);
RTreeNode *p = root; //设置为根结点
while(!p || !isEmpty(*S))
{
while(p != NULL)
{
printf("->%c", p->data);
if(p->right_child != NULL)
{
push(S, p->right_child); //利用栈的先进后出原则,右子树先进去,反而后出来(保存右结点)
}
p = p->left_child;
} //遍历左子树的所以左结点 把右结点全部压进栈中
if(!isEmpty(*S))
{
p = pop(S); //右结点出栈
}
}
}
2.中序非递归遍历
代码如下(示例):
中序非递归遍历(左 根 右, 先将根 和 左结点压入栈中,
出栈, 然后在判断是否有右子树,有压入栈中,没有进行下一次循环)
void center_f_d_preorder(RTreeNode *root)
{
RStack *S = init();
RTreeNode *p = root; //移动结点
RTreeNode *q = NULL; //临时存储出栈结点
while(p || !isEmpty(*S))
{
while(p != NULL)
{
push(S,p);
p = p->left_child;
}
while(!isEmpty(*S))
{
q = pop(S);
printf("->%c", q->data);
if(q->right_child != NULL)
{
p = q->right_child; //右子树也需要压入栈中
}
}
}
}
3.后序非递归遍历
代码如下(示例):
后序非递归遍历(左 右 根, 对每个结点都判断是否有右结点,
有压入栈中,将指针赋值给左结点,在压入栈中,每个结点都进行判断)
void after_f_d_preorder(RTreeNode *root)
{
Stack *S = init();
RTreeNode *p, *r;
p = root; //移动指针
r = NULL; //上一次访问的记录(临时指针)
while(p || !isEmpty(*S))
{
if(p != NULL){
push(S,p);
p = p->left_child;
}else{
p = GetTop(S); //取栈顶元素
if(p->right_child != NULL && p->right_child != r) //判断每一个左结点是否右右子树
{
p = p->right_child; //右结点压入栈中
push(S,p);
p = p->left_child; //左结点压入栈中
}else{
p = pop(S);
printf("->%c", p->data);
r = p;
p = NULL; //结束循环
}
}
}
}
本次非递归遍历的图:
四、代码实现
代码如下(示例):
//
// Created by xoo on 2021/7/7.
//
//采用静态建立二叉树,非递归实现
#include<stdio.h>
#include<stdlib.h>
#include<string>
#define MAX 50
typedef struct TreeNode
{
struct TreeNode *left_child, *right_child;
char data;
}RTreeNode;
RTreeNode *init_node(char data, RTreeNode *left_child, RTreeNode *right_child)
{
RTreeNode *node;
node = (struct TreeNode*)malloc(sizeof(struct TreeNode));
node->left_child = left_child;
node->right_child = right_child;
node->data = data;
return node;
}
//生成一课二叉树
RTreeNode *create_tree()
{
RTreeNode *root, *b, *c, *d, *e, *f;
d = init_node('D', NULL, NULL);
e = init_node('E', NULL, NULL);
f = init_node('F', NULL, NULL);
b = init_node('B', d, NULL);
c = init_node('C', e, f);
root = init_node('A', b, c);
return root;
}
typedef struct Stack
{
RTreeNode * sa[MAX];
int top;
}RStack;
//生成一个栈
RStack * init()
{
RStack *SS;
SS = (struct Stack*)malloc(sizeof(struct Stack));
SS->top = 0;
return SS;
}
//进栈
void push(RStack *p, RTreeNode *x)
{
if(p->top == MAX)
{
printf("栈满");
exit(0);
}
p->sa[p->top] = x;
++p->top;
}
//判断是否栈空
bool isEmpty(RStack p)
{
if(p.top == 0)
{
return true;
}
return false;
}
//出栈
RTreeNode *pop(RStack *p)
{
if(isEmpty(*p))
{
printf("栈空");
exit(0);
}
return p->sa[--p->top];
}
//清空栈
void clear(RStack *p)
{
p->top = 0;
}
//取栈顶元素
RTreeNode *GetTop(Stack *S)
{
if(!isEmpty(*S))
{
return S->sa[S->top - 1]; //这里取栈顶元素不需要对栈进行操作
}
return NULL;
}
//前序非递归遍历(先遍历根,在遍历左,把右结点的存储在栈中,最后进行打印 满足了根 左 右)
void front_f_d_preorder(RTreeNode *root)
{
RStack *S = init();
push(S, root);
RTreeNode *p = root; //设置为根结点
while(!p || !isEmpty(*S))
{
while(p != NULL)
{
printf("->%c", p->data);
if(p->right_child != NULL)
{
push(S, p->right_child); //利用栈的先进后出原则,右子树先进去,反而后出来(保存右结点)
}
p = p->left_child;
} //遍历左子树的所以左结点 把右结点全部压进栈中
if(!isEmpty(*S))
{
p = pop(S); //右结点出栈
}
}
}
//中序非递归遍历(左 根 右, 先将根 和 左结点压入栈中,出栈, 然后在判断是否有右子树,有压入栈中,没有进行下一次循环)
void center_f_d_preorder(RTreeNode *root)
{
RStack *S = init();
RTreeNode *p = root; //移动结点
RTreeNode *q = NULL; //临时存储出栈结点
while(p || !isEmpty(*S))
{
while(p != NULL)
{
push(S,p);
p = p->left_child;
}
while(!isEmpty(*S))
{
q = pop(S);
printf("->%c", q->data);
if(q->right_child != NULL)
{
p = q->right_child; //右子树也需要压入栈中
}
}
}
}
//后序非递归遍历(左 右 根, 对每个结点都判断是否有右结点,有压入栈中,将指针赋值给左结点,在压入栈中,每个结点都进行判断)
void after_f_d_preorder(RTreeNode *root)
{
Stack *S = init();
RTreeNode *p, *r;
p = root; //移动指针
r = NULL; //上一次访问的记录(临时指针)
while(p || !isEmpty(*S))
{
if(p != NULL){
push(S,p);
p = p->left_child;
}else{
p = GetTop(S); //取栈顶元素
if(p->right_child != NULL && p->right_child != r) //判断每一个左结点是否右右子树
{
p = p->right_child; //右结点压入栈中
push(S,p);
p = p->left_child; //左结点压入栈中
}else{
p = pop(S);
printf("->%c", p->data);
r = p;
p = NULL; //结束循环
}
}
}
}
//先序遍历
void front_preorder(RTreeNode *p)
{
if(p != NULL){
printf("->%c", p->data);
front_preorder(p->left_child);
front_preorder(p->right_child);
}
}
//中序遍历
void center_preorder(RTreeNode *p)
{
if(p != NULL){
center_preorder(p->left_child);
printf("->%c", p->data);
center_preorder(p->right_child);
}
}
//后序遍历
void after_preorder(RTreeNode *p)
{
if(p != NULL){
after_preorder(p->left_child);
after_preorder(p->right_child);
printf("->%c", p->data);
}
}
int main() {
RTreeNode *tree;
tree = create_tree();
printf("非递归的前序遍历:");
front_f_d_preorder(tree);
printf("\n");
printf("递归的前序遍历:");
front_preorder(tree);
printf("\n");
printf("非递归的中序遍历:");
center_f_d_preorder(tree);
printf("\n");
printf("递归的中序遍历:");
center_preorder(tree);
printf("\n");
printf("非递归的后序遍历:");
after_f_d_preorder(tree);
printf("\n");
printf("递归的后序遍历:");
after_preorder(tree);
printf("\n");
system("pause");
return 0;
}
结果展示:
总结
非递归的运用有很多,所以一定好好掌握,前序和中序非递归是容易理解的,后序非递归比较难理解,所以一定需要注重画图,具体的思路可以去某站上搜索。