> 文档中心 > 【数据结构】二叉树的遍历(非递归)

【数据结构】二叉树的遍历(非递归)

我们已经对二叉树的基本概念有了一定的了解,也对它的递归版本的前、中、后序遍历掌握的很好了,那么接下来就要介绍二叉树中遍历的非递归版本
>【数据结构】二叉树详解< 先看本篇文章效果更佳哦

目录

  • 1、非递归遍历解析
  • 2、前序遍历(非递归)
  • 3、中序遍历(非递归)
  • 4、后序遍历(非递归)

1、非递归遍历解析

我们知道深度优先遍历是需要用到栈的
在前序遍历的递归的写法中我们是将树分为 根【左子树的前序】【右子树的前序】 这样的视角来进行的,貌似在递归写法中我们没有用到栈,但实际上是在递归调用的时候使用了调用栈进行回溯了
【数据结构】二叉树的遍历(非递归)
可是在我们非递归的视角中是没有办法使用调用栈来进行回溯,所以我们要 手动的构建一个栈 来进行回溯操作

2、前序遍历(非递归)

非递归的前序遍历就是在每个结点第一次被访问到的时候进行打印,下图中红色的线就代表第一次访问某一个结点,紫色的线就是访问所有结点的完整路径

注意:叶子结点或只有一个孩子的结点,在访问时即使孩子为 null 也要对其左右孩子进行访问

在这里插入图片描述

就好比是有 4 条线依次往左走,最后就有了我们的前序序列 A B C D E F
那么怎么实现这 4 条线呢? 其实就是进行遍历,一直朝根结点的左孩子遍历直到遇到 null

TreeNode cur = root;while (cur != null) {  打印(cur.val);  cur = cur.left;}

这样就可以一直向左进行遍历,但是此时会有一个问题:假如第 1 条线向左走遇到 null 了,那么怎么继续走第 2 条线呢?
在这里插入图片描述
这时候我们就需要用到回溯的,我们先走第 1 条线,遇到 null 则证明 C 的左子树已经访问完,所以要按照先访问 C 的右子树,紧接着访问 B 的右子树,然后访问 A 的右子树的顺序进行,这里就需要使用到栈来为我们进行回溯操作了
所以我们的想法就是在遍历结点的同时将该结点入栈,当一条线 走完之后就取出栈顶元素,然后像栈顶元素的右边进行遍历

TreeNode cur = root;while(cur != null || 栈不为空) {  while (cur != null) {    打印(cur.val);    入栈(cur);    cur = cur.left;  }  //此时已经一条线走到底了  top = 栈顶元素;  cur = cur.right;//还需开启一个循环来继续走下一条线}

简单的对前两条线的遍历进行图解
在这里插入图片描述
其实就是当一条线走完后取出栈顶元素并访问其右孩子,然后继续之前的操作
完整代码

public static void preOrder(TreeNode root) { Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) {     while (cur != null) {  System.out.printf("%c ", cur.val);  stack.push(cur);//入栈cur  cur = cur.left;     }     TreeNode top = stack.pop();//取出栈顶元素     cur = top.right; }    }

3、中序遍历(非递归)

有了前序遍历的思想和基础,那么对于中序遍历来说就好理解了很多
前序遍历是第一次访问结点的时候进行打印输出,那么中序遍历就是在第二次访问该结点的时候进行打印输出
图中红色的线就是第二次访问结点的情况
在这里插入图片描述
那么什么时候是第二次经过结点呢?
第一次经过某结点是向左遍历时访问的结点是第一次经过该结点,那么第二次经过该结点就是从它的左孩子回来时经过就是第二次经过,也就是说当该元素从栈中弹出时就是第二次经过

TreeNode cur = root;while (cur != null) {  入栈(cur);  cur = cur.left;}  top = 栈顶元素;//此时为第二次经过该结点

有了这个思路那我们就可以很快的写出中序遍历的非递归代码
完整代码

public static void inorder(TreeNode root){ Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()){     while (cur != null){  stack.push(cur);  cur = cur.left;     }     TreeNode top = stack.pop();//第二次经过该结点     System.out.printf("%c ", top.val);//马上打印该结点     cur = top.right;//继续向右走 }    }

4、后序遍历(非递归)

后序遍历相较于前序和中序遍历来说考虑的方面较多一些
后序遍历的非递归视角就是第三次访问该结点时进行打印输出
图中红色的线就是第三次访问该结点的情况
在这里插入图片描述

但是在中序遍历的基础上,如果第二次访问将结点弹出栈后,再从该结点的右孩子回来时是第三次访问,但是结点已经被弹出栈了,所以会无法第三次访问结点
在这里插入图片描述
所以我们先在要着手解决一个问题:不要在第二次访问结点时将结点弹出栈

我们将 top = stack.pop() 变为 top = stack.peek()

并且我们就应该区分一下什么时候是第二次经过该结点,什么时候是第三次经过

第一次经过结点(就是从他的双亲过来时的情况)
第二次经过结点(就是从他的左孩子过来的情况)
第三次经过结点(就是从他的右孩子过来的情况)

那么就会有以下几种情况:
1.该结点的右孩子为空的情况,虽然从左孩子过来后是第二次经过该结点,但因为没有右孩子,就没有必要去访问它的右孩子,那么也就可以看作是第三次经过该结点
在这里插入图片描述
2.如果该结点的右孩子就是上一个访问过的结点,那么必然是从它右孩子过来的,就一定是第三次访问该结点
在这里插入图片描述
3.剩下的情况就证明不是第三次访问该结点,所以就像中序遍历一样,继续向栈顶元素的
完整代码

 public static void postorder(TreeNode root) { Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; TreeNode last = null;//用来记录上一个被访问的结点 while (cur != null || !stack.isEmpty()) {     while (cur != null) {  stack.push(cur);  cur = cur.left;     }     TreeNode top = stack.peek();//第二次访问,只查看栈顶元素但不弹出     if (top.right == null) {//右孩子为空直接视为第三次访问  System.out.printf("%c ", top.val);  last = top;  stack.pop();//第三次访问,弹出栈顶元素     } else if (top.right == last) {//右孩子就是上一个被访问的结点就是第三次访问  System.out.printf("%c ", top.val);  last = top;  stack.pop();//第三次访问,弹出栈顶元素     } else {//否则就像右孩子方向走  cur = top.right;     } }    }

ZDfans