【每日算法】力扣144. 二叉树的前序遍历 & 94. 二叉树的中序遍历 & 145. 二叉树的后序遍历

224 阅读5分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」。

144. 二叉树的前序遍历

描述

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

image.png

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

输出:[1,2,3]

示例 2:

输入:root = []

输出:[]

示例 3:

输入:root = [1]

输出:[1]

示例 4:

image.png

输入:root = [1,2]

输出:[1,2]

示例 5:

image.png

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

输出:[1,2]  

提示:

树中节点数目在范围 [0, 100] 内 -100 <= Node.val <= 100  

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

做题

前序遍历

image.png

前序遍历又称为先序遍历,遍历的方式是:从一个节点开始,先遍历自己,然后再是左孩子节点,最后是右孩子节点。

像上面的图,如果只有 1,2,3 这三个节点,那前序遍历就是:1,2,3。

全部节点的遍历顺序就是:1,2,4,5,3,6,7。

递归法

递归法做起来比较简单,我们需要有一个思路:我们走到那个节点,就把这个节点的 val 存储 list 中,这个节点的左右子节点就交给递归去处理。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList();
        ergodic(root,result);
        return result;
    }
    private void ergodic(TreeNode root,List<Integer> result){
        if(root == null){
            return;
        }
        //因为是前序,所以要把当前节点的 val 存储 result
        result.add(root.val);
        ergodic(root.left,result);
        ergodic(root.right,result);
    }

}

image.png

速度非常快,内存消耗有点大。

迭代法

这个方法跟递归法的性能差不多,只是使用了栈去代替了递归操作。

需要注意!因为栈是先进后出的,所以我们要先把右子节点先放进去。

class Solution {
    //迭代法
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList();
        Stack<TreeNode> stack = new Stack();
        stack.push(root);

        while(!stack.empty()){
            TreeNode node = stack.pop();
            if(node==null){
                continue;
            }
            result.add(node.val);
            if(node.right!=null){
                stack.push(node.right);
            }
            if(node.left!=null){
                stack.push(node.left);
            }
            
        }
        
        return result;
    }
}

image.png

94. 二叉树的中序遍历

描述

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

示例 1:

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

输出:[1,3,2]

示例 2:

输入:root = []

输出:[]

示例 3:

输入:root = [1]

输出:[1]

示例 4:

输入:root = [1,2]

输出:[2,1]

示例 5:

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

输出:[1,2]  

做题

image.png

想分清楚二叉树前中后序三种遍历,我们只需要关注根节点是在那个位置的就可以了。

前序遍历,根-左-右。

中序遍历,左-根-右。

后序遍历,左-右-根。

左和右子节点是不会改变顺序的,根节点可以在它们俩的左边、中间和右边。

上图的中序遍历:4,2,5,1,6,3,7。

递归法

思路和前序遍历的差不多,只需要把当前节点的 val 添加到 result 的动作放在两个方法的中间就可以了。

class Solution {
    //递归
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList(); 
        ergodic(root,result);
        return result;
    }
    private void ergodic(TreeNode root,List<Integer> result){
        if(root == null){
            return;
        }
        ergodic(root.left,result);
        result.add(root.val);
        ergodic(root.right,result);
    }
}

image.png

迭代法

中序遍历的迭代法就不能拿前序遍历的来改改就完了

image.png

主要的思路就是使用 stack 去模拟递归时系统帮我们维护的栈。

class Solution {
    //迭代
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList();
        // 模拟递归的栈
        Stack<TreeNode> stack = new Stack();
        while(!stack.empty() || root !=null){
            while(root !=null){
                //因为是中序遍历,所以每次到达一个新的节点都要往它的左子节点移动,并且把当前的节点压入栈中
                stack.push(root);
                root = root.left;
            }
            //遍历到叶子节点后,我们就要开始出栈了
            root = stack.pop();
            result.add(root.val);
            // 左-根-右
            // 如果把自己当成根节点,那么遍历完自己之后,就要去遍历右子节点了,所以这里 root.right 是否为空,我们每次遍历都需要去判断,所以直接赋值就行
            root = root.right;
        }
        
        return result;
    }
}

image.png

145. 二叉树的后序遍历

描述

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

示例:

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

输出: [3,2,1] 进阶: 递归算法很简单,你可以通过迭代算法完成吗?

做题

递归法

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList(); 
        ergodic(root,result);
        return result;
    }

    private void ergodic(TreeNode root,List<Integer> result){
        if(root == null){
            return;
        }
        ergodic(root.left,result);
        ergodic(root.right,result);
        result.add(root.val);
    }
}

image.png

迭代法

后续遍历的迭代法同样也是使用栈去模拟递归时系统帮我们维护的栈。

后序遍历,左-右-根。

因为遍历的第一个节点是左子节点,所以每次遍历一个节点时,第一步要一路遍历下去,把路上遇到的节点压入栈中,最后找到是叶子节点的左节点。

然后记录下这个节点,开始往回递归,出栈的节点呢就是根节点,我们还不能记录它,再次把它压入栈中,然后去遍历它的右子节点

基本上流程就是上面所描述的,我们还需要确定什么情况下,根节点和右子节点可以被记录下来。

  1. 当前节点没有右子节点时。没有右子节点,说明就不要再走下一趟去遍历它的右子节点了。这个条件同样也是判断是否找到是叶子节点的左节点的条件。
  2. 当前节点的右子节点被记录过了。说明是时候记录这个根节点了。

因为除了叶子节点之外,任何节点都可以被当作根节点(我这里理解的是有左子节点或者右子节点的都可以被当作根节点),所以这两种情况可以让我们记录下所有节点。

第一种情况很简单,只需要每次遍历时,判断当前节点的右子节点是否为空就行。

第二种情况就需要我们记录下上一次遍历的节点,我们创建一个引用对象来引用上次记录的节点就行。

public List<Integer> postorderTraversal(TreeNode root) {
	List<Integer> result = new ArrayList(); 
	Stack<TreeNode> stack = new Stack();
	// 记录上一次记录的节点
	TreeNode pre = null;
	while(root != null || !stack.empty()){
		while(root != null){
			// 找是叶子节点的左子节点,经过这个遍历,从出栈的第一个节点就是叶子节点的左子节点
			stack.push(root);
			root = root.left;
		}
		root = stack.pop();
		if(root.right == null || root.right == pre){
			// 满足条件,记录这个节点
			result.add(root.val);
			pre = root;
			// 赋值 root 为空,直接跳过找叶子节点的左子节点的循环
			root = null;
		}else{
			// 不满足。因为还有右子节点没有遍历,压入栈中,赋值 root 为 右子节点,找右子节点的叶子节点的左子节点
			stack.push(root);
			root = root.right;
		}
	}
	return result;
}

image.png

最后

其实还有更加复杂的Morris 遍历解法,但奈何点赞数不高,动力不足,刚好又有其他事情想做,只好先搁置一下了,看到这还不点赞?!

今天就到这里了。

这里是程序员徐小白,【每日算法】是我新开的一个专栏,在这里主要记录我学习算法的日常,也希望我能够坚持每日学习算法,不知道这样的文章风格您是否喜欢,不要吝啬您免费的赞,您的点赞、收藏以及评论都是我下班后坚持更文的动力。