二叉树遍历总结 & 剑指 Offer 07. 重建二叉树【M】

二叉树的遍历整理

契机:145. 二叉树的后序遍历题,用递归解决很简单,并且有统一的模板,但是用迭代方法就没有统一的模板,因此想总结一下4类遍历的解法。方便复习


前序遍历:1-2-4-5-8-3-6-9-7-10

中序遍历:4-2-8-5-1-6-9-3-10-7

后序遍历:4-8-5-2-9-6-10-7-3-1

1. 前序遍历

前序遍历:根 – 左 – 右

递归解法:

/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    private void dfs(TreeNode root, List<Integer> res){
        if(root == null) return;
        res.add(root.val);
        dfs(root.left, res);
        dfs(root.right, res);
    }
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null) return res;            // 排除特殊情况
        dfs(root, res);
        return res;
    }
}

迭代解法:

很明显,我们需要用到栈来模拟整个递归过程。栈顶指针就是我们当前循环的根节点,取出之后需要获得其左右结点,并且将非null的左右结点压入栈,由于栈是后进先出,所以需要先压右孩子、后压左孩子

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null) return res;            // 排除特殊情况
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur = stack.pop();
            res.add(cur.val);
            if(cur.right != null) stack.push(cur.right);
            if(cur.left != null) stack.push(cur.left);
        }
        return res;
    }
}

——前序遍历的迭代解法的关键:先压右孩子后压左孩子

2. 中序遍历

中序遍历的顺序:左 - 根 - 右

递归解法:

class Solution {
    private void dfs(TreeNode root, List<Integer> res){
        if(root == null) return;
        dfs(root.left, res);
        res.add(root.val);
        dfs(root.right, res);
    }
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null) return res;            // 排除特殊情况
        dfs(root, res);
        return res;
    }
}

可以发现和前序遍历的递归解法是统一的,只需要修改root的记录顺序即可

迭代解法:

实践发现,不能将前序遍历的迭代解法修改得到。原因是:前序遍历中,当前结点就是我们找的结点,可以将其从栈中弹出,并将左右孩子压入栈;但是中序遍历:当前节点需要在遍历完左孩子后才能从栈中弹出,所以当前结点获取之后不能出栈,而是要压左右孩子进栈,但是再次遍历到该结点的时候并不知道其已经遍历过了,所以会重复循环。

根据题解的提示:在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素

中序遍历:找最左结点,然后向上返回,然后找最近结点的右子树的最左,然后再向上,依次类推:

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null) return res;
        LinkedList<TreeNode> stack = new LinkedList<>();
        while(!stack.isEmpty() || root != null){
            while(root != null){			// 一直向左走,找到最左结点,期间碰到的所有结点均入栈
                stack.push(root);
                root = root.left;
            }			// 此时已经到底了,此时指向的是最左结点的左null,此时的栈顶就是最左结点
            root = stack.pop();			// 最左结点出栈,然后去找该结点的右节点(如果该右节点不为null,则找该右节点的左孩子则再次循环入栈....)
            res.add(root.val);
            root = root.right;
        }
        return res;
    }
}

——迭代的思想:循环找到最左结点,并且将路径上所有的结点均入栈(就是根结点),直到找到null。然后出栈,此时结点就是最左结点(其左子树已经遍历完成了,null),然后去看其右子树,然后对右子树再次循环入栈左孩子…,如果右子树不存在,即右节点为null,那么去找最近还未遍历的根节点(此时的栈顶)

3. 后序遍历

递归解法:

class Solution {
    private void dfs(TreeNode root, List<Integer> res){
        if(root == null) return;
        dfs(root.left, res);
        dfs(root.right, res);
        res.add(root.val);
    }
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null) return res;            // 排除特殊情况
        dfs(root, res);
        return res;
    }
}

迭代解法:

题解提供了一个很巧妙的方法

从前序遍历开始考虑:中左右 – 中右左 – 左右中,从第一步到第二步就是代码将左右压栈的顺序变换。第二步到第三步就是将结果翻转一下即可

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root == null) return res;            // 排除特殊情况
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode cur = stack.pop();
            res.add(cur.val);
            if(cur.left != null) stack.push(cur.left);		// 压栈顺序相反
            if(cur.right != null) stack.push(cur.right);
        }
        Collections.reverse(res);			// 全部翻转
        return res;
    }
}

——迭代解法:前序遍历和后序遍历可以统一,稍微修改一下就可以统一,并且完全根据栈的顺序来遍历

中序遍历稍微复杂一点,需要用一个指针来指示当前遍历的节点,主题思想是要找到最左结点然后再返回

4. 层序遍历

思想是:BFS,对每一层进行遍历,然后在遍历的过程中将其压入队列,先入先出

很多题都是基于层序遍历来实现的,所以记忆比较深:

/* TreeNode类,是树节点的属性和函数 */
public class TreeNode {
	int val;		// 节点的值
	TreeNode left;	// 左孩子节点
	TreeNode right;	// 右孩子节点
	TreeNode(int x) { val = x; }
 }

public void levelOrder(TreeNode root){
    if(root == null) return null;		// 处理特殊情况
    LinkedList<TreeNode> queue = new LinkedList<TreeNode>();		// 存放当前层级的节点
    queue.offer(root);		// 将根节点压入栈
    int depth = 0;			// 记录树的深度
    while(!queue.isEmpty()){		// 开始迭代——当前层存在需要遍历的节点
        int size = queue.size();
        while(size > 0){		// 遍历当前层的所有节点
            TreeNode current = queue.poll();		// 取出队首元素
            if(current.left != null) queue.offer(current.left);	// 将当前节点的左右孩子节点均加入到队尾
            if(current.right != null) queue.offer(current.right);
            size--;
        }
        depth++;
    }
}

ps:根据两个遍历来构造整棵二叉树

剑指 Offer 07. 重建二叉树

在这里插入图片描述

很惭愧的是,之前刷题的时候解出来了,而再次做这题的时候又犯了老错误,只构建了框架,而边界判断错误了。

首先,根据前序遍历和中序遍历的特点:

  • 前序遍历,第一个结点就是根节点;
  • 中序遍历,找到这个根节点(index),根结点的左右就是对应的左右子树;
  • 而根据该index,可以知道前序遍历的左子树的长度,而剩余的就是右子树的长度。

——注意,这边不能根据index,而直接推到prevorder的index,而是要根据in_left 和index计算出左子树的长度,而确定前序遍历的左右子树的分界线

eg:[1,2,3],[3,2,1]就无法推出正确的

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private int findIndex(int[]inorder, int target){        // 找到根节点target在中序遍历的位置
        for(int i = 0; i < inorder.length; i++){
            if(target == inorder[i]) return i;
        }
        return -1;
    }
    private TreeNode dfs(int[] preorder, int[] inorder, int pre_left, int pre_right, int in_left, int in_right){
        if(pre_left > pre_right){
            return null;
        }
        TreeNode root = new TreeNode(preorder[pre_left]);       // 前序遍历的第一个结点就是根节点
        int index = findIndex(inorder, preorder[pre_left]);     // 找到中序遍历中的根节点的index,就是分界线
        int left_length = index - in_left;			// 获得左子树的长度
        root.left = dfs(preorder, inorder, pre_left + 1, pre_left + left_length, in_left, index - 1);
        root.right = dfs(preorder, inorder, pre_left + left_length + 1, pre_right, index + 1, in_right);
        return root;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return dfs(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1);
    }
}

改进:可以将inOrder的数字存放在哈希表中,从而避免每次都需要查找,key=inOrder[i],value=i

参考:

  1. https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/bang-ni-dui-er-cha-shu-bu-zai-mi-mang-che-di-chi-t/
  2. https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by–22/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值