一、思路分析

思考如何遍历二叉树的时候,不能从扁平化的代码入手,要从图形入手,直观而易于理解的图形让编码者能够快速找到建构程序的方法。

那么看下面这个二叉树的示意图

计算机不可能直接去遍历这个图形,它不像我们人类一样可以一眼望去识别图像,它必须按照某种顺序阅读这个图形。我们给他一个小箭头,让它用这个小箭头遍历这个图形。小箭头指到哪个结点,计算机程序就可以读哪个结点的数据。

如上图,现在箭头指向那个结点,计算机就可以读出这个结点的数据——38 。

如果对于每一个结点,我们让程序先读这个结点的数据,再读这个结点左子树的数据,再读这个结点右子树的数据,最终达到这个结点对应子树数据被全部遍历的效果。那么这种遍历方式被称为前序遍历。

好,如果用前序遍历的方式进行遍历,那么我们发现对于任何一个结点,小箭头会并且只会到达它三次,三次之后,小箭头再也不会访问这个结点了。

比方说,储存着38这个数据的结点(下面称为结点38),小箭头一共会到达它三次。

第一次:小箭头从结点50移到结点38,这之后小箭头会跳向结点30

第二次:小箭头从结点30返回结点38,这之后小箭头会跳向结点40

第三次:小箭头从结点40返回结点38,这之后小箭头会返回结点50

需要说明的是,如果这个结点的左孩子指针为空,那我们就理解成小箭头跳到了空域上又跳了回来;如果这个结点的右孩子指针为空,也理解为小箭头跳到了空域又跳了回来。在这个前提下,我们可以认为,所有结点都会到达三次。

这是一个非常重要的结论,我的代码架构就是根据这个结论展开的!

二、代码设计

结点是这样设计的,这一步乏善可陈。

typedef struct node
{int num;int count; struct node* left;//左孩子指针struct node* right;//右孩子指针struct node* father;//指向父亲的指针}Tree;

把一个递归问题转化为一个递归问题,大概率需要使用栈这一数据结构。我使用了两个栈,stack栈储存着从根结点到当前结点的所有地址,stack[0]即是根节点地址,以此类推。isread_stack栈储存着当前路径下各个结点的数据是否被遍历的信息,isread_stack[i]的值如果是1,就意味着这个结点小箭头已经到达过一次了,接下来小箭头就要按第二次到达的流程行动了;isread_stack[i]的值如果是2,就意味着这个结点已经到达过两次了,那么接下来小箭头要按第三次到达的流程行动。对于每一个结点,小箭头都会到达三次,设计一个数据结构去储存小箭头到达了几次是至关重要的!

Tree* stack[NUM];
int isread_stack[NUM];
int top=-1;

注意,这两栈用了一个栈顶指针,也就是说stack[i]是第i层的某个被访问结点的地址,而isread_stack[i]正好记录了它是第几次被访问。

其实你也可以开一个结构体数组来当栈,一次实现我这两个栈的功能。不过本质上,都是一样的!

现在我们可以写程序了,刚开始要把栈初始化,把根结点的地址和被访问状态(也就是被访问了几次了)信息压入两个栈中。这里以前序遍历代码为例:

void read_tree1(Tree* root)
{top=-1;stack[++top]=root;isread_stack[top]=0;//栈初始化,把根结点的地址和被访问状态信息压入两个栈中 //改写代码时,这两个栈一定要calloc或开全局变量,不然程序会崩溃!!! Tree* tmp;//一个临时变量指针,在这里显得突兀,其实不重要,先别管它 while(1){//如果是第一次到达 if (isread_stack[top]==0){printf("num=%d count=%d\n",stack[top]->num,stack[top]->count);isread_stack[top]=1;//遍历左子树if (stack[top]->left!=NULL){tmp=stack[top]->left;stack[++top]=tmp;continue;}}//如果是第二次到达 else if (isread_stack[top]==1){isread_stack[top]=2;//遍历右子树if (stack[top]->right!=NULL){tmp=stack[top]->right;stack[++top]=tmp;continue;} }//如果是第三次到达 else if (isread_stack[top]==2&&top!=0){isread_stack[top]=0;top--;}//如果是第三次到达且这个结点是根结点//意味着树遍历完了,函数该结束了,退出循环吧 else if (isread_stack[top]==2&&top==0){break;}}
}

如果是第一次到达某个结点,做三件事:

1、访问本结点的数据(因为我们以前序遍历为例子)

2、把该结点访问状态信息改为已经被访问过一次(也就是isread_stack[top]=1)

3、看看左孩子指针是不是非空,如果非空,进入左孩子进行访问,(把左孩子的地址压入栈中,然后continue);否则,跳过这一步,进入下一轮循环。

如果是第二次到达某个结点,做两件事:

1、把该结点访问状态信息改为已经被访问过两次(也就是isread_stack[top]=2)

2、看看左孩子指针是不是非空,如果非空,进入右孩子进行访问,(把右孩子的地址压入栈中,然后continue);否则,跳过这一步,进入下一轮循环。

如果是第三次到达某个结点,做两件事:

1、该结点的访问次数改为0(isread_stack[top]=0)。这看起来怪异,其实是因为要为下一次访问作准备。

2、top--

当然,如果回到的是根结点,退出就是了。

注意,如果你觉得第三步有点反人类,或许你可以这样设计:

第一次访问某结点,压入左孩子地址时,顺势把左孩子的访问次数改为0.我没试过,或许是可行。很大可能是可行的!

三、示例代码

#include <stdio.h>
#include <stdlib.h>
#define NUM 100typedef struct node
{int num;int count; struct node* left;struct node* right;struct node* father;}Tree;Tree* stack[NUM];
int isread_stack[NUM];
int top=-1;void build_tree(Tree** proot,int num);
void read_tree1(Tree* root);int main(void)
{int n;scanf("%d",&n);Tree* root;int i;int num;for (i=1;i<=n;i++){scanf("%d",&num);build_tree(&root,num);}read_tree1(root);return 0;
}void build_tree(Tree** proot,int num)
{Tree* c;Tree* tmp;top=-1;if (*proot==NULL){c=(Tree*)calloc(1,sizeof(Tree));c->left=NULL;c->right=NULL;c->num=num;c->count=1;*proot=c;}else{stack[++top]=*proot;while(1){if (num==stack[top]->num){(stack[top]->count)++;break;} else if (num<stack[top]->num){if (stack[top]->left==NULL){c=(Tree*)calloc(1,sizeof(Tree));c->left=NULL;c->right=NULL;c->num=num;c->count=1;stack[top]->left=c;break;}else{tmp=stack[top]->left;stack[++top]=tmp;continue;}}else{if (stack[top]->right==NULL){c=(Tree*)calloc(1,sizeof(Tree));c->left=NULL;c->right=NULL;c->num=num;c->count=1;stack[top]->right=c;break;}else{tmp=stack[top]->right;stack[++top]=tmp;continue;}}}}
}void read_tree1(Tree* root)
{top=-1;stack[++top]=root;isread_stack[top]=0;//栈初始化,把根结点的地址和被访问状态信息压入两个栈中 Tree* tmp;//一个临时变量指针,在这里显得突兀,其实不重要,先别管它 while(1){//如果是第一次到达 if (isread_stack[top]==0){printf("num=%d count=%d\n",stack[top]->num,stack[top]->count);isread_stack[top]=1;//遍历左子树if (stack[top]->left!=NULL){tmp=stack[top]->left;stack[++top]=tmp;continue;}}//如果是第二次到达 else if (isread_stack[top]==1){isread_stack[top]=2;//遍历右子树if (stack[top]->right!=NULL){tmp=stack[top]->right;stack[++top]=tmp;continue;} }//如果是第三次到达 else if (isread_stack[top]==2&&top!=0){isread_stack[top]=0;top--;}//如果是第三次到达且这个结点是根结点//意味着树遍历完了,函数该结束了,退出循环吧 else if (isread_stack[top]==2&&top==0){break;}}
}

这里的build_tree是一个构建二叉树的函数,我就不赘述了。

如何遍历一个二叉树——非递归实现相关推荐

  1. C++版二叉树非递归遍历

    C++版二叉树非递归遍历 文章目录 C++版二叉树非递归遍历 一.二叉树前序遍历 二.二叉树中序遍历 三.二叉树后序遍历 一.二叉树前序遍历 /*** Definition for a binary ...

  2. 一种二叉树非递归遍历的简单写法

    一种二叉树非递归遍历的简单写法 目录 一种二叉树非递归遍历的简单写法 先序遍历 中序遍历 后序遍历 二叉树的遍历是数据结构中非常基础的一个知识点,也是面试手撕代码环节的一个常见题目.这个问题的递归写法 ...

  3. 二叉树非递归dfs——简单思路搞定前中后序遍历

    前言:相信很多同学都被二叉树非递归dfs的前中后序遍历方法弄的头疼.网上的答案,什么前中后序遍历各有一套写法,还有什么一个栈的写法,两个栈的写法.看起来能理解,一闭眼自己写都记不住.今天介绍一种用一种 ...

  4. 二叉树非递归遍历(模版)

    读完本篇内容大约花费您7分钟时间 本文主要讲解二叉树非递归遍历,由于是非递归遍历,所以需要用到栈stack,我们如果仔细考虑递归遍历的代码,就能明白非递归种栈的应用. 由于几种遍历方式只是在处理中间节 ...

  5. 二叉树非递归后序遍历算法(C语言)

    二叉树非递归后序遍历算法(C语言) 二叉树后序遍历的规律:左右根 后序非递归遍历中,访问根(子根)结点有两种情况 ①:遍历完左子树,需要遍历右子树,需要从栈中访问最顶上的根(子根)结点从而得到右子树的 ...

  6. 图解二叉树非递归版的中序遍历算法

    你会学到什么 讨论的问题是什么 这个问题相关的概念和理论 非递归版中序遍历算法 代码思考 算法技巧 实现代码 快照 评价算法 总结 欢迎关注算法思考与应用公众号 你会学到什么? 树的递归遍历算法很容易 ...

  7. python实现二叉树非递归前中后序遍历

    python实现二叉树非递归前中后层序遍历 二叉树是数据结构中重要的一部分,本文简单介绍用python实现二叉树的前中后序遍历,包括递归和非递归思路算法. # -*- 二叉树 begin -*- # ...

  8. [Leetcode][第889题][JAVA][根据前序和后序遍历构造二叉树][分治][递归]

    [问题描述][中等] [解答思路] copyOfRange class Solution {public TreeNode constructFromPrePost(int[] pre, int[] ...

  9. 【数据结构笔记10】二叉树的先序、中序、后序遍历,中序遍历的堆栈/非递归遍历算法,层序遍历,确定一个二叉树,树的同构

    本次笔记内容: 3.3.1 先序中序后序遍历 3.3.2 中序非递归遍历 3.3.3 层序遍历 3.3.4 遍历应用例子 小白专场:题意理解及二叉树表示 小白专场:程序框架.建树及同构判别 文章目录 ...

最新文章

  1. ogg mysql的原理_OGG基础原理了解
  2. Leetcode 415. 字符串相加
  3. 长三角,也开始“东北化”了
  4. tomcat调优的几个方面
  5. String s = new String(xyz);创建了几个对象?
  6. koajs 项目实战(二)
  7. [代码] DataGrid GridView 使用区别
  8. reportng定制修改
  9. 企业微信应用设置可信域名_怎么设置企业微信朋友圈功能?企业微信朋友圈功能有哪些限制?...
  10. Ubantu 安装ftp 之诡异
  11. ubantu安装环境
  12. 计算历史区间的收益率,用前复权还是后复权?
  13. Python动态图见得多了?Excel:亦可赛艇!我可是身经百战了
  14. Plonky msm的改进版Yao算法
  15. Linux:rsyslog 日志丢失 messages lost due to rate-limiting
  16. Adobe 官方公布的 RTMP 规范+未公布的部分
  17. 什么是适配器模式,它有哪些应用场景
  18. 《GhostXP_SP2电脑公司特别版_v8.5》
  19. linux 下 搭建邮件邮件服务器(Postfix+Dovecot)(二)-基于mysql的虚拟账户登陆收发邮件...
  20. 哈佛凌晨四点半...

热门文章

  1. APP微信支付(java后台_统一下单和回调)
  2. 幽默笑话,哥们误会了,木子家原创
  3. 语音芯片JQ8400的使用心得
  4. 华为机器学习服务语音识别功能,让应用绘“声”绘色
  5. 在Linux系统搭建DNS服务器
  6. android 设置画布颜色,如何在颜色变化的画布上在Android上绘制渐变颜色?
  7. Windows应用程序设计
  8. 小话HTTP Authentication
  9. hive中关键字作为列名的方法
  10. python 字符串的输入和输出