用Java描述数据结构之二叉树,前序遍历,中序遍历,后序遍历这篇博客中我介绍了二叉树的相关概念和递归实现的二叉树的前中后序遍历。今天来介绍非递归迭代版遍历的思路及实现代码。
首先我们要明白所谓遍历就是集合中每个节点都要经过,就下面这棵树而言,我们从根节点开始要怎么走才能经过每个节点?


下面是上图树的构建代码:

public static TreeNode buildTree() {// 博客中所用到的树的构建TreeNode a = new TreeNode('a');TreeNode b = new TreeNode('b');TreeNode c = new TreeNode('c');TreeNode d = new TreeNode('d');TreeNode e = new TreeNode('e');TreeNode f = new TreeNode('f');TreeNode g = new TreeNode('g');TreeNode h = new TreeNode('h');a.left = b; a.right = c;b.left = d; b.right = e;c.left = f; c.right = g;e.right = h;return a;}

很明显我们要从根节点出发,左右都可以走,一般我们习惯往左走,我们一路向左走,直到走到没路可走,即cur == null,用代码实现这个是这样的:

//root为根节点
Treenode cur = root;while(cur != null){cur = cur.left;
}

直到我们走到空之后,下一步该怎么办?

每个节点都有左右两个方向可走,我们选择一路向左,走到头之后我们应该退一步,然后向右走,然后再向左走,再走到尽头,往回走,向右走一步。。。。。。。就这样去探路。拿例子来演示如下:

从A出发向左走,到尽头之后退一步到B往B的右边走一步到E,继续向左走,走到头之后退一步到E,再向右走到H,继续向左走,走到尽头之后退一步。。。。。。
我们把上述过程中的退一步称为回溯,想要实现回溯我们就必须从刚开始向左走的过程中记录经过的每个节点,而这个回溯很明显具有后入先出的特性,这让我们想到用栈去记录(递归版遍历实际也是通过栈回溯,不过是java虚拟机栈,每一个栈帧都是以此方法的调用)每走到尽头之后回溯一个节点,然后往这个节点的右边走,就这样直到栈空了,也就遍历完了。代码实现如下:

     TreeNode cur = root;Stack<TreeNode> stack = new Stack<>();//第一次进入时stack为空,但是cur != null //所以 cur != null 是为第一次能进入循环专门设置的条件//当stack为空时结束循环 while (cur != null || !stack.isEmpty()){while (cur != null){//入栈每个经过的节点,向左走到尽头时回溯stack.push(cur);//向左走cur = cur.left;}//回溯TreeNode pop = stack.pop();//向右走cur = pop.right;}

现在我们来说明前序遍历和中序遍历(后序遍历有点不同后面细说):
每个节点都会经过第一次经过然后向左走,回溯回来经过第二遍然后向右走,向右走完还会回来经过第三遍像下图:

我们吧第一次经过处理节点称为先序遍历或者前序遍历,第二次经过时处理称为中序遍历,第三次经过时处理称为后序遍历。
因为前序遍历是在第一次经过时处理,体现在代码中就是:
先序遍历代码:

//先序遍历
public static void preorder(TreeNode root){TreeNode cur = root;Stack<TreeNode> stack = new Stack<>();while (cur != null || !stack.isEmpty()){while (cur != null){stack.push(cur);System.out.print((char)cur.val + " ");cur = cur.left;}cur = stack.pop().right;}}

那么中序遍历就是第二次经过也就是左走到尽头回溯的时候处理节点

//中序遍历public static void inorder(TreeNode root){TreeNode cur = root;Stack<TreeNode> stack = new Stack<>();while (cur != null || !stack.isEmpty()) {while (cur != null) {stack.push(cur);cur = cur.left;}TreeNode pop = stack.pop();System.out.print((char)pop.val + " ");cur = pop.right;}}

理解了先序和中序遍历之后再来看后序遍历!!!


剩下就是后续遍历了,为什么说后序遍历相对前两种要麻烦一点呢。
我们来一步一步执行一下前序遍历:

  1. cur指向root,入栈

  2. 向左走,走到B,入栈

  3. 向左走到D,入栈

  4. 再向左走,走到 cur == null 了,回溯一个节点,并向右走,执行下面代码:

         TreeNode pop = stack.pop();cur = pop.right;

此时栈中是这样的

走到这里还没有发现问题,后续遍历第一个节点也恰巧是D。继续走

  1. 因为D的右边还是 null,所以需要继续回溯,继续执行那段代码:
         TreeNode pop = stack.pop();cur = pop.right;

这次从栈中出来的的B节点,但是后序遍历第二个节点不应该是B,此时的cur指向了E,并且因为E != null,E入栈,此时栈中是这样:

  1. 向E的左边走,发现E的左边是空,那么就要执行回溯操作
         TreeNode pop = stack.pop();cur = pop.right;

E会出栈,栈中如下:

7. 因为E的右边是H不为null,所以H入栈,此时栈中如下:

走到这里我们就可以停下来了,这棵树的后序遍历是:D H E B F G C A,但是上述的过程中,还没到第三次经过节点很多节点像B E 节点都已经出栈了,H节点走完之后就直接回溯到A节点了,如果要后续遍历,H节点完了应该先回到E, 回到B 再回到A才对。现在这个代码在第二次经过的时候就已经让节点出栈,所以这个代码对于后序遍历不适用。

所谓后序遍历就是在第三次经过节点的时候才对节点进行相应的处理,也就是遍历完右子树回来的时候才做处理,所以现在的关键是怎么才能知道当前是从节点左边回来还是从右边回来的问题。

我们需要定义一个引用last指向上一个出栈的节点,然后在每次回溯的时候判断last是不是该节点的右孩子,如果是右孩子则说明是从右边回溯的,如果不是就是左边回溯,左边回溯不用出栈,右边回溯才出栈,在出栈时执行打印,通过代码理解:

public static void postorder(TreeNode root){Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;//专门用来记录上一个出栈的节点TreeNode last = null;while (cur != null || !stack.isEmpty()){while (cur != null){stack.push(cur);cur = cur.left;}//这里用peek看last是否是右孩子//peek.right == null 是用来判断当前节点有没有右孩子//没有右孩子就没必要执行else里的代码让cur往右走了//就像D节点,在这里判断了没有右孩子就直接出栈了,没必要在往其右走再回来TreeNode peek = stack.peek();if (peek.right == null || peek.right == last){System.out.print((char)peek.val + " ");last = peek;stack.pop();}else {cur = peek.right;}}}

下面通过执行代码验证三个遍历:

public class Main {public static TreeNode buildTree() {TreeNode a = new TreeNode('a');TreeNode b = new TreeNode('b');TreeNode c = new TreeNode('c');TreeNode d = new TreeNode('d');TreeNode e = new TreeNode('e');TreeNode f = new TreeNode('f');TreeNode g = new TreeNode('g');TreeNode h = new TreeNode('h');a.left = b; a.right = c;b.left = d; b.right = e;c.left = f; c.right = g;e.right = h;return a;}public static void main(String[] args) {TreeNode root = buildTree();System.out.print("前序遍历:");Preorder.preorder(root);System.out.println();System.out.print("中序遍历:");Inorder.inorder(root);System.out.println();System.out.print("后序遍历:");Postorder.postorder(root);}
}

二叉树前序,中序,后序遍历的迭代实现,实现思路及代码相关推荐

  1. 二叉树的前序中序后序遍历

    二叉树的前序中序后序遍历 二叉树的遍历 前序遍历 中序遍历 后序遍历 总结 二叉树的遍历 二叉树的遍历有前序遍历,中序遍历,后序遍历三种. 今天我把二叉树的遍历方法给大家总结一下,也算对我自己学习的一 ...

  2. 【二叉树Java】二叉树遍历前序中序后序遍历的非递归写法

    本文主要介绍二叉树前序中序后序遍历的非递归写法 在探讨如何写出二叉树的前序中序后序遍历代码之前,我们先来明确一个问题,前序中序后序遍历根据什么区分? 二叉树的前序中序后序遍历,是相较根节点说的.最先遍 ...

  3. 二叉树的前序中序后序遍历java代码实现

    1.前序遍历概述 前序遍历(VLR) 是二叉树遍历的一种,也叫做先根遍历.先序遍历.前序周游,可记做根左右.前序遍历首先访问根结点然后遍历左子树,最后遍历右子树. 若二叉树为空则结束返回,否则: (1 ...

  4. 二叉树遍历(递归实现前序/中序/后序遍历)

    1. 准备工作 我们先定义一棵普通的二叉树,如下图 2. 前序遍历 通过递归进行遍历: 如果二叉树为空,则操作返回: 如果非空,否则从根结点开始,然后遍历左子树,再遍历右子树. 前序遍历的结果是:AB ...

  5. C++用类实现二叉树的创建,前序中序后序遍历(附完整代码)

    C++用类实现二叉树的创建,前序中序后序遍历(附完整代码) 前序.中序.后序遍历 直接上代码 前序.中序.后序遍历 二叉树的遍历分为前序遍历,中序遍历和后序遍历三种遍历方法.前序遍历的顺序为" ...

  6. 二叉树的前序中序后序三种遍历方式及递归算法介绍

    二叉树三种遍历方式 二叉树的遍历是整个二叉树的核心,二叉树的几本操作都要依赖于遍历,对于二叉树的遍历,递归是最简单也最容易理解的,本文详细介绍了二叉树的三种遍历方法,并用递归来实现: 完整的可调试代码 ...

  7. 二叉树前序中序后续线索树_二叉树的先序,中序,后序遍历以及线索二叉树的遍历...

    二叉树的先序,中序,后序遍历以及线索二叉树的遍历 (2008-05-04 17:52:49) 标签: 杂谈 C++ 二叉树的先序,中序,后序遍历以及线索二叉树的遍历 头文件 //*********** ...

  8. 用前序中序创建二叉树(用中序后序创建二叉树)

    定义二叉树结点 比如就拿这个二叉树 前序中序创建 因为前序遍历的顺序是 根 , 左 ,右. 中序的遍历是 左 根 右. 我们会很不好想,但我们可以用前序和中序把上面那个二叉树的遍历一边 前序遍历:AB ...

  9. 二叉树前序中序,后序中序,公共最近祖先的实现

    二叉树前序中序,后序中序,公共最近祖先的实现 注释中详细介绍了算法,故不再赘述. 无论是前序还是后序,一个节点的左子树和右子树都是可以看做是分开的,有一定规律可循,故可用递归进行实现. #includ ...

  10. java中二叉树_Java工程师面试1000题224-递归非递归实现二叉树前、中、后序遍历...

    224.使用递归和非递归实现二叉树的前.中.后序遍历 使用递归来实现二叉树的前.中.后序遍历比较简单,直接给出代码,我们重点讨论非递归的实现. class Node { public int valu ...

最新文章

  1. [UGUI]圆形Image
  2. C# 中类对象与JSON字符串互相转换的几种方法
  3. b tree和b+tree_B TREE实施
  4. 【论文阅读】SIR模型下网络中多信息源检测 2014-IEEE
  5. 4G、5G难配物联网 hold不住也得死撑
  6. 【推荐实践】阿里飞猪“猜你喜欢”推荐排序实践
  7. JavaScript基础和记背内容 Day1
  8. 计算机主机的三大部分,计算机系统的三大组成部分
  9. Angular 权威教程
  10. 密码学的安全性浅析-1
  11. html调用wmp,web页面中嵌入window media player,支持IE和Chrome
  12. 普通人创业,不开实体店或者网上店铺,你能干啥?
  13. 用Python完成 “Excel合并(拆分)” 的各种自动化操作!
  14. 基于OpenCV实现的灰度图幻影坦克
  15. HiveHive创建数据库与创建数据库表
  16. 车载网络测试 - 车载以太网 - Tester和DUT的IP、MAC、Logical addr设置
  17. 上篇:基于球面调和基的实时全局光照明
  18. Android4.0的StatusBar和NavigationBar
  19. 单片机数字滤波c语言程序,单片机系统中数字滤波的算法【C程序整理】
  20. 如何做好网站安全防护 防止网站被黑?

热门文章

  1. PHP错误:Warning: preg_replace() [function.preg-replace]: Unknown modifier '[' in
  2. Memory Notification: Library Cache Object loaded into SGA问题
  3. 机器学习实战6-sklearn训练决策树实现分类和回归
  4. rabbitmq 消息长度_Spring Boot教程(29) – RabbitMQ必备基础
  5. edit with idle 没反应_搬个家,猫咪不吃不喝甚至猝死?可能是你没做好“前戏”...
  6. 7-65 藏头诗 (15 分)
  7. PAT乙级 1094 谷歌的招聘(柳婼代码,测试点1、2、4、5分析)
  8. STM32(三)-------流水灯(标准库函数)
  9. java控制器_Java用户界面 模型-视图-控制器(MVC)模式
  10. 【输入一个数,判断是否为素数(质数)】