【二叉树】前、中、后 序遍历(递归+迭代)

215 篇文章 0 订阅

leetcode 练习题目

144. 二叉树的前序遍历
94. 二叉树的中序遍历
145. 二叉树的后序遍历

二叉树定义

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

1. 先序遍历

递归

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        dfs(root, res);
        return res;
    }

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

迭代

直接做法

用栈模拟递归。先序遍历比较简单,访问的顺序和添加到结果的顺序是一致的,先序是:中左右,访问二叉树也是先访问中间节点,所以在访问的同时将当前节点加入结果列表中即可。

唯一需要注意的是,因为是用栈模拟的递归,所以若希望以 中左右 的顺序加入结果,需要先把右节点加入栈中,再把左节点接入栈中。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        if (root != null) {
            stack.push(root);
        }
        while (!stack.isEmpty()) {
            TreeNode curr = stack.pop();
            res.add(curr.val);
            if (curr.right != null) {
                stack.push(curr.right);
            }
            if (curr.left != null) {
                stack.push(curr.left);
            }
        }
        return res;
    }
}
标记法

先序遍历是:中左右。由于是栈,访问到某一个节点,所以先把右节点入栈,再把左节点入栈,并且不把当前节点加入结果,把访问过的当前节点再次放入栈中,但是在其后再 push 到栈中一个标记 null。如果遇到栈顶是 null ,就把栈顶加入结果。

这样放的意义在于,用栈来维护结果的顺序,带 null 的节点再出栈时,访问顺序一定是 中左右。如果当前节点加入栈中,在下一次循环访问栈顶的时候,中间节点带 null 必然会加入结果,然后是左节点作为中间节点,左节点的右节点和左节点入栈,再把左节点入栈再在其后 push 一个标记 null,然后再下一次循环左节点带 null 必然会加入结果,以此类推,左子树都结束之后,再才是右节点作为中间节点。所以可以得到 [中, [左子树], [右子树]] 这样的效果。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        if (root != null) {
            stack.push(root);
        }
        while (!stack.isEmpty()) {
            TreeNode curr = stack.pop();
            if (curr != null) {
                if (curr.right != null) {
                    stack.push(curr.right);
                }
                if (curr.left != null) {
                    stack.push(curr.left);
                }
                stack.push(curr);
                stack.push(null);
            } else {
                curr = stack.pop();
                res.add(curr.val);
            }
        }
        return res;
    }
}

2. 中序遍历

递归

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        dfs(root, res);
        return res;
    }

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

迭代

直接做法

中序遍历是:左中右。所以需要先访问当前节点最左的节点。定义一个当前节点变量,使得它一直循环向左走直到变为 null,走到当前节点的最左,在走的过程中将路过的节点入栈记录下来。在走到最左后取栈顶,此时栈顶就是最左那个节点。将栈顶添加到结果(这相当于最左的中间节点),然后转向当前节点的右孩子继续(因为中间节点访问完,按顺序后面是右节点)

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode curr = root;
        while (curr != null || !stack.isEmpty()) {
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            curr = stack.pop();
            res.add(curr.val);
            curr = curr.right;
        }
        return res;
    }
}
标记法

中序遍历是:左中右。由于是栈,访问到某一个节点,所以先把右节点入栈,再中间节点,并在其后再 push 到栈中一个标记 null,再把左节点入栈,并且不把当前节点加入结果。之后如果遇到栈顶是 null ,就把栈顶加入结果。

这样放的意义在于,用栈来维护结果的顺序,带 null 的节点再出栈时,访问顺序一定是 [[左子树], 中, [右子树]]。如果当前节点加入栈中,在下一次循环访问栈顶的时候,左节点作为中间节点,左节点的右节点入栈,再把左节点入栈再在其后 push 一个标记 null,再把左节点的左节点入栈,以此类推,如此左子树都结束之后,再才是中间节点,中间节点带 null 加入结果,然后是右节点作为中间节点。所以总体来看即 [[左子树], 中, [右子树]]

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        if (root != null) {
            stack.push(root);
        }
        while (!stack.isEmpty()) {
            TreeNode curr = stack.pop();
            if (curr != null) {
                if (curr.right != null) {
                    stack.push(curr.right);
                }
                stack.push(curr);
                stack.push(null);
                if (curr.left != null) {
                    stack.push(curr.left);
                }
            } else {
                curr = stack.pop();
                res.add(curr.val);
            }
        }
        return res;
    }
}

3. 后序遍历

递归

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

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

迭代

直接做法

后序比较困难,因为后序遍历的顺序是:左右中,一直走左节点,走到头,还是不能访问中间节点,得把右节点再访问完,回来才能访问中间节点。那就需要判断,中间节点的右子树什么时候都访问完了?因为后序遍历的顺序是: [[左子树], [右子树]], 中,如果中间节点的右节点是结果集的最后一个元素,那么说明它的右子树都访问过了,这时可以访问中间节点了。

所以可以记录上一次的输出。首先一直循环向左走直到为 null,走到最左,在走的过程中将节点入栈记录下来,走到 null 之后取栈顶,当前栈顶就是最左的节点,如果栈顶右孩子为 null,或者栈顶右孩子是上一次的输出,添加栈顶到结果集,否则还没到这个中间节点,还得把它入栈,然后向右孩子继续探索,直到它的右子树都访问完,即上一次的输出是它的右孩子。这样即能保证 [[左子树], [右子树]], 中 的顺序。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode curr = root;
        TreeNode pre = null;
        while (curr != null || !stack.isEmpty()) {
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            curr = stack.pop();
            if (curr.right == null || curr.right == pre) {
                res.add(curr.val);
                pre = curr;
                curr = null;
            } else {
                stack.push(curr);
                curr = curr.right;
            }
        }
        return res;
    }
}
标记法

后序遍历是:左右中。由于是栈,访问到某一个节点,所以先把中间节点入栈,并在其后再 push 到栈中一个标记 null,再把右节点入栈,再左节点入栈,并且不把当前节点加入结果。之后如果遇到栈顶是 null ,就把栈顶加入结果。

这样放的意义在于,用栈来维护结果的顺序,带 null 的节点再出栈时,访问顺序一定是 [[左子树], [右子树], 中]。如果当前节点加入栈中,在下一次循环访问栈顶的时候,左节点作为中间节点,左节点入栈再在其后 push 一个标记 null,然后把左节点的右节点入栈,再把左节点的左节点入栈,以此类推,如此左子树都结束之后,再才是右节点作为中间节点,最后才是带 null 的中间节点,这时中间节点带 null 加入结果。所以总体来看即 [[左子树], [右子树], 中]

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        if (root != null) {
            stack.push(root);
        }
        while (!stack.isEmpty()) {
            TreeNode curr = stack.pop();
            if (curr != null) {
                stack.push(curr);
                stack.push(null);
                if (curr.right != null) {
                    stack.push(curr.right);
                }
                if (curr.left != null) {
                    stack.push(curr.left);
                }
            } else {
                curr = stack.pop();
                res.add(curr.val);
            }
        }
        return res;
    }
}
反转类前序遍历结果

前序遍历的顺序是:[中, [左子树], [右子树]],后序遍历是:[[左子树], [右子树], 中],将容易写的先序遍历的直接做法的写法改成 [中, [右子树], [左子树]],得到这样的类前序遍历的结果之后,最后再反转结果即是后序遍历的结果。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        if (root != null) {
            stack.push(root);
        }
        while (!stack.isEmpty()) {
            TreeNode curr = stack.pop();
            res.add(curr.val);
            if (curr.left != null) {
                stack.push(curr.left);
            }
            if (curr.right != null) {
                stack.push(curr.right);
            }
        }
        Collections.reverse(res);
        return res;
    }
}

Reference

  1. 代码随想录:二叉树:3. 二叉树的迭代遍历
  2. 代码随想录:二叉树:4. 二叉树的统一迭代法 (标记法)
  3. leetcode:145. 二叉树的后序遍历:官方题解 (后序迭代直接做法)
  4. leetcode:145. 二叉树的后序遍历:题解:Program120:JAVA 迭代法 (后序迭代直接做法)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值