二叉树的前中后序遍历的非递归方法

相信大家对二叉树的前中后序遍历的递归方法非常熟悉,其实就是一个类似的框架

if(p!=null){
    
    
	//前序遍历时访问结点
	if(p.left!=null){
    
    
		//递归进入左子节点遍历
	}
	//中序遍历时访问结点
	if(p.right!=null){
    
    
		//递归进入右子节点遍历
	}
	//后序遍历时访问结点
}

可以看到,写前中后序遍历的递归方法非常简单,但是这种方式对我们学习编程的话意义并不是很大,而是用迭代方法可以体现出一个人的思维能力,尤其是在面试中,那么如何写出二叉树的前中后序遍历的非递归方法的代码呢?

二叉树的前中后序遍历框架

前中后序遍历也有它们相应的框架:它们的核心代码如下:(来自leetcode郭郭)
前序核心代码

  while(root != null || !stack.isEmpty()){
    
    
    //go left down to the ground
    while(root != null){
    
    
      res.add(root.val);
      stack.push(root);
      root = root.left;
    }

    //if we reach to the leaf, go back to the parent right, and repeat the go left down.
    TreeNode cur = stack.pop();
    root = cur.right;
  }

中序核心代码

while (root != null || !stack.isEmpty()) {
    
    
    while (root != null) {
    
    
        stack.push(root);
        root = root.left;
    }
    root = stack.pop();
    res.add(root.val);
    root = root.right;
}

后序核心代码

while(root != null || !stack.isEmpty()){
    
    
    while(root != null){
    
    
        res.add(root.val);
        stack.push(root);
        root = root.right;
    }

    TreeNode cur = stack.pop();
  	root = cur.left;
}

二叉树的前序遍历

对应leetcode144题

问题描述

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
在这里插入图片描述
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

示例 4:
在这里插入图片描述

输入:root = [1,2]
输出:[1,2]

解题思路:

前序遍历的顺序是根左右
前序遍历的框架如下:

  while(root != null || !stack.isEmpty()){
    
    
    //go left down to the ground
    while(root != null){
    
    
      res.add(root.val);
      stack.push(root);
      root = root.left;
    }

    //if we reach to the leaf, go back to the parent right, and repeat the go left down.
    TreeNode cur = stack.pop();
    root = cur.right;
  }

在这里插入图片描述
对于前序遍历,首先是拿到根节点,然后一直往左走,而在框架中,我们首先访问相应的结点值,然后将该节点入栈,并一直往左走,即指向其右子节点。当while循环结束后,即发现左为空,然后就从栈中弹出一个元素,往其右子节点走,由于最开始3的右子节点为空,那么可以不管,接着下一趟外层循环的时候,由于root为空,不走内层while循环,就从栈中弹出元素2,然后访问2的右子节点。进入到了4,然后再一直往4的左边走,我们可以看到,前序遍历的过程中,总是尽可能的往左下走,当走到左下,已经无路可走的时候,我们就弹出元素,并走到当前节点的右边,然后又一直往左下走。也就是说,这里是有一个趋势的,也就是我一直往左下走,直到走到无路可走,这时候我再弹栈,看看右边是否有要走的路,如果右边还有,那么我们就到右边,然后再立刻往左下走。

实现代码

class Solution {
    
    
    List<Integer> list;         //用list集合来保存我们遍历过程中的元素
    public List<Integer> preorderTraversal(TreeNode root) {
    
    
        list=new ArrayList<>();
        //创建一个栈,模拟递归时的系统栈
        Stack<TreeNode> stack=new Stack<TreeNode>();
        //当结点不为空并且栈也不为空的时候,就说明还有元素待遍历
        while(root!=null || !stack.empty()){
    
    
            //如果可以一直往左走,就一直往左右,一直走到底
            while(root!=null){
    
    
                list.add(root.val);       //前序遍历先访问根节点
                stack.push(root);         //将结点入栈
                root=root.left;           //一直往走左
            }
            //如果不能再往左走,就弹出一个元素
            root=stack.pop();           //弹出栈顶元素
            //然后先往右走一步
            root=root.right;
        }
        return list;
    }
}

二叉树的后序遍历

问题描述

给定一个二叉树,返回它的 后序 遍历。
示例:

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [3,2,1]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

解题思路

后序遍历的顺序是左右根
在这里插入图片描述
后序遍历的序列是左右根,如果我们将其逆序,就变成了根右左,这与前序遍历有着异曲同工之妙,如果我们能拿到根右左,这个时候我们就可以通过reverse的方式拿到左右根,那根右左与根左右有什么区别呢,在前序遍历中我们是root=root.left,那么在这里,我们就是root=root,right,在前序遍历中我们是root=root.right,那么在这里,我们就是root=root,left,其它代码其实是惊人的相似。如果从图上来说,我们是尽可能的往右走,直到走投无路的时候,我们才会弹出元素,并且考虑这个元素的左节点。但是一旦拿到新节点以后,我们又会执着的往右走,直到走到无路可走,再往上走
最后,我们可以使用Java集合类提供的reverse函数进行逆序

实现代码

class Solution {
    
    
    private List<Integer> list;
	public List<Integer> postorderTraversal(TreeNode root) {
    
    
		list=new ArrayList<Integer>();
        Stack<TreeNode> stack=new Stack<TreeNode>();
        //后序遍历序列是左右根,我们遵循根右左的规则,它与前序遍历左右刚好相反
        while(root!=null || !stack.empty()){
    
    
            //我们选择一直往右走,直到走到底
            while(root!=null){
    
    
                list.add(root.val);     //根右左,同样是先访问根节点
                stack.push(root);       //入栈
                root=root.right;        //一直往右走
            }
            root=stack.pop();   //弹出一个元素
            //走到底之后,就向左走一步
            root=root.left;
        }
        //上面我们得到了一个根右左的序列,要想得到左右根,转置一下即可。
        Collections.reverse(list);
		return list;
        
    }
}

二叉树的中序遍历

问题描述

给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例 1:
在这里插入图片描述

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

示例 4:
在这里插入图片描述

输入:root = [1,null,2]
输出:[1,2]

解题思路:

中序遍历的序列为左根右
在这里插入图片描述

while (root != null || !stack.isEmpty()) {
    
    
    while (root != null) {
    
    
        stack.push(root);
        root = root.left;
    }
    root = stack.pop();
    res.add(root.val);
    root = root.right;
}

在前序遍历和后序遍历时,我们都是在内层while循环遍历的过程中访问结点(即将结点值添加到容器中),但是中序遍历在压栈的时候并没有把值放入。因为我们顺序是左根右,直到我们走到无路可走的时候,我们弹出的第一个元素放入到结果中,因为前序遍历和逆后序遍历都是先访问根节点,所以在内存while循环遍历的时候就已经访问了。这里的过程是,当我们一直往左走的时候,一直走到3这个重点,然后弹出,把3添加到结果集中,然后继续弹出,直到存在右节点的时候,就向右走一步,然后再继续之前的过程。

实现代码

class Solution {
    
    
    public List<Integer> inorderTraversal(TreeNode root) {
    
    
        List<Integer> list=new ArrayList<Integer>();
		Stack<TreeNode> stack=new Stack<TreeNode>();       //辅助栈
        while(root!=null || !stack.empty()){
    
    
            //一直往左走,直到走到底,但是我们不将中间拿到的结点值放入到容器中
            while(root!=null){
    
    
                stack.push(root);
                root=root.left;
            }
            //出栈一个结点,并将其放入到容器中
            root=stack.pop();
            list.add(root.val);
            root=root.right;
        }
        return list;
    }
}

总结:

对于前中后序遍历,他们的非递归遍历代码是极其相似的,我们返回的是一个list集合,这里一个重要的点是到底我们何时把拿到的结点值添加到容器中,这是我们需要重点考虑的。

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/114009466