经典算法——二叉树遍历问题

2022-05-20  本文已影响0人  _冻茶

姓名:谭旭东

学号:19011210016

一、基本概念

二叉树有一般有三种遍历方法:根据遍历顺序不同而区分

二、前序遍历

1. 递归写法

    // 前序:递归方法遍历
    public List<Integer> preOrder1(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        preOrder_dfs(root, ans);

        return ans;
    }

    public void preOrder_dfs(TreeNode node, List<Integer> ans) {
        if (node == null)
            return;
        
        // 前序遍历:首次进入节点即获取值
        ans.add(node.val);
        preOrder_dfs(node.left, ans);
        preOrder_dfs(node.right, ans);
    }

2. 非递归写法

    public List<Integer> preOrder2(TreeNode root) {
        List<Integer> ans = new ArrayList<>();

        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);

        /*
            栈解法:前序遍历,按照根左右的顺序
            由于栈是先进后出,需要调整入栈顺序
            1.根节点(栈顶节点)直接出栈,加入队列
            2.我们要的顺序:先访问左节点(左子树)再访问右节点(右子树)
            3.入栈顺序:先加入右节点,再加入左节点
        */
        while (!stack.isEmpty()) {

            TreeNode curNode = stack.pop();

            ans.add(curNode.val);

            if (curNode.right != null)
                stack.push(curNode.right);

            if (curNode.left != null)
                stack.push(curNode.left);
        }
        return ans;

    }

三、中序遍历

1. 递归写法

    // 中序:递归方法遍历,左根右
    public List<Integer> inOrder1(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        inOrder_dfs(root, ans);

        return ans;
    }

    public void inOrder_dfs(TreeNode node, List<Integer> ans) {
        if (node == null)
            return;

        // 中序遍历:从左孩子节点返回时,获取节点值
        preOrder_dfs(node.left, ans);
        ans.add(node.val);
        preOrder_dfs(node.right, ans);
    }

2. 非递归写法

    public List<Integer> inOrder2(TreeNode root) {
        List<Integer> ans = new ArrayList<>();

        Stack<TreeNode> stack = new Stack<>();

        /*
            栈解法:中序遍历,需要按照左根右顺序入队
            对每一颗子树,我们都这样做
            1.需要先一直往左遍历,直到最左侧最深节点,同时还要记录节点路径
            2.最左最深节点可以直接获取值(子树为空,不需访问),然后返回上一层节点(栈中保存记录)
            3.上层节点此时可直接获取值(已经从左子树返回,属于第二次访问,该节点为左子树的根)
            4.在访问该上层节点的右节点,按照123步骤继续
         */

        TreeNode cur = root;
        while (!stack.isEmpty() || cur != null) {

            // 左
            while (cur != null) {       // 先往左走到最深
                stack.push(cur);        // 记录路径
                cur = cur.left;
            }

            TreeNode node = stack.pop();
            ans.add(node.val);          // 栈顶元素为该子树最左最深节点,直接获取其值

            // 右
            if (node.right != null) {   // 访问右子树
                cur = node.right;
            }
        }
        return ans;
    }

四、后序遍历

1. 递归写法

    // 后续:递归方法遍历,左右根
    public List<Integer> postOrder1(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        postOrder_dfs(root, ans);

        return ans;
    }

    public void postOrder_dfs(TreeNode node, List<Integer> ans) {
        if (node == null)
            return;

        // 后续遍历:从右孩子节点返回时,获取节点值
        postOrder_dfs(node.left, ans);
        postOrder_dfs(node.right, ans);
        ans.add(node.val);
    }

2. 非递归写法

    public List<Integer> postOrder2(TreeNode root) {
        List<Integer> ans = new ArrayList<>();

        Stack<TreeNode> stack = new Stack<>();
        TreeNode pre = root;        // 指针cur标记当前退出的节点
        stack.push(root);

        /*
            使用栈:后续遍历,要求顺序,左右根
            由于节点路径要保留,所以先用peek查看,而不用pop
            1.对每一颗子树,我们先一直访问直至其最左最深节点
            2.然后获取最左最深节点值,返回上一层
            3.在上一层需要进行判断,左子树和右子树是否访问过
                我们通过加入节点记录值pre,记录上次退出的节点
                 (1)如果上次从左子树/右子树退出,表明左子树不需再访问
                 (2)如果上次从右子树退出,表明右子树不需再访问
            4.如果满足条件,继续按照上述123步骤,访问其左子树/右子树
         */
        while (!stack.isEmpty()) {
            TreeNode peek = stack.peek();

            // cur = peek是记录上次退出的节点
            // 如果上次退出的节点和左/右子节点相同,则表示那边的子树已经访问过了,不需要再访问
            // 左子节点不为空时,需要判断左右节点是否需要再访问
            // 右节点不为空时,此时已经从左节点返回,不会再访问左节点,故只需判断右节点是否需要访问
            if (peek.left != null && peek.left != pre && peek.right != pre) {
                stack.push(peek.left);          // 往左遍历
            } else if (peek.right != null && peek.right != pre) {
                stack.push(peek.right);         // 往右遍历
            } else {
                ans.add(stack.pop().val);       // 左右都空
                pre = peek;
            }
        }
        return ans;
    }

五、层序遍历

1. 从上到小层序遍历

    // 层序遍历:从上往下
    public List<Integer> levelOrder(TreeNode root) {
        List<Integer> ans = new ArrayList<>();

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {

            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                ans.add(cur.val);
                if (cur.left != null)
                    queue.add(cur.left);
                if (cur.right != null)
                    queue.add(cur.right);
            }
        }

        return ans;
    }

2. 从下往上遍历

    // 层序遍历:从下往上
    public List<Integer> levelOrder_desc(TreeNode root) {
        List<List<Integer>> lists = new ArrayList<>();

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        // 分层记录
        while (!queue.isEmpty()) {

            int size = queue.size();
            List<Integer> temp = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                temp.add(cur.val);

                if (cur.left != null)
                    queue.add(cur.left);

                if (cur.right != null)
                    queue.add(cur.right);
            }
            lists.add(new ArrayList<>(temp));


        }

        List<Integer> ans = new ArrayList<>();
        for (int i = lists.size() - 1; i >= 0; i--) {
            List<Integer> l = lists.get(i);
            ans.addAll(l);
        }

        return ans;
    }


上一篇下一篇

猜你喜欢

热点阅读