二叉树的非递归遍历(Java实现)

78 阅读5分钟

二叉树的非递归遍历

image-20250315180335845

先序遍历

首先先将头节点入栈。

之后不断判断栈中是否为空,不为空就将栈顶元素弹出,打印节点的值。

然后将弹出节点的右节点先入栈,左节点后入栈。(注意不为空时才入栈)

代码

import java.util.Stack;

public class BinaryTreeTraversal {
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int v) {
            val = v;
        }
    }

    /**
     * 先序遍历
     * @param head
     */
    public static void preOrder(TreeNode head){
        if (head != null){
            Stack<TreeNode> stack = new Stack<>();
            stack.push(head);

            while (!stack.isEmpty()){
                TreeNode pop = stack.pop();
                System.out.println(pop.val);
                if (pop.right != null){
                    stack.push(pop.right);
                }
                if (pop.left != null) {
                    stack.push(pop.left);
                }

            }
        }
    }

    public static void main(String[] args) {
        /**
         *       1
         *      / \
         *     2   3
         *    / \ / \
         *   4  5 6 7
         */
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);
        root.right.left = new TreeNode(6);
        root.right.right = new TreeNode(7);

        preOrder(root);
    }
}

中序遍历

  1. 当前来到某一条子树的头节点,将子树的整条左边界进栈,直到左边界遍历完。
  2. 从栈里弹出节点打印,把弹出节点的右树重复步骤1(打印之后,就把节点的右树看作新的子树头,看看有没有左边界,有的话继续进栈,一直到左边界遍历完)。
  3. 直到没有子树,并且栈空了。

图解

image-20250315183111864

首先来到 1节点,将其整个左边界进栈。

image-20250315183321834

之后将 4节点弹出,把4节点的右树的整个左边界进栈。

image-20250315183515946

弹出6节点,之后应继续将6节点的右子树的左边界进栈,但6节点的右子树为空,所以继续弹出5节点。

image-20250315183812740

5节点弹出后,其右树依然为空。栈中再弹出2节点。

image-20250315183947839

2节点弹出之后,栈中压入2节点的右子树的左边界,7节点入栈。

image-20250315184233226

栈弹出7节点,由于7节点没有右树,之后再弹出1节点。

image-20250315184528190

将1节点的右树的整个左边界入栈,只有3节点入栈。

image-20250315184812408

将栈顶元素3节点弹出后,让3节点的右树头节点的左边界入栈。

由于8、9、10均没有右树,后续的操作我们也只是将其弹出。全部弹出后,栈为空,且没有子树,中序遍历结束。

代码

import java.util.Stack;

public class BinaryTreeTraversal {
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int v) {
            val = v;
        }
    }

	/**
     * 中序遍历
     * @param head
     */
    public static void inOrder(TreeNode head){
        if (head != null){
            Stack<TreeNode> stack = new Stack<>();
            while (!stack.isEmpty() || head != null){   
                if (head != null){  // 来到当前子树的头节点,头节点不为null,将其整个左边界压入栈中
                    stack.push(head);
                    head = head.left;
                }else { // 头节点为null,从head = 栈中弹出元素,之后让弹出元素的右树的左边界入栈
                    head = stack.pop();
                    System.out.print(head.val + " ");
                    head = head.right; // 先让head指向右树头节点,下一次循环判断head不为空,继续将其左边界入栈
                }
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        /**
         *       1
         *      / \
         *     2   3
         *    / \ / \
         *   4  5 6 7
         */
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);
        root.right.left = new TreeNode(6);
        root.right.right = new TreeNode(7);

        inOrder(root);
    }
}

后序遍历

先序遍历的顺序是 中左右。代码上我们的实现方式是先压入右节点,再压入左节点。

如果先压入左节点,再压入右节点,得到的顺序是中右左。

在此基础上,将这个顺序反转,就是左右中,也就是后序遍历。

两个栈实现

package study.tree;

import java.util.Stack;

public class BinaryTreeTraversal {
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int v) {
            val = v;
        }
    }

    /**
     * 后序遍历(两个栈实现)
     * @param head
     */
    public static void posOrder(TreeNode head) {
        if (head != null){
            Stack<TreeNode> stack = new Stack<>();
            Stack<TreeNode> collect = new Stack<>();

            stack.push(head);
            while (!stack.isEmpty()){
                /**
                 *  注意,整段代码中节点的入栈顺序是先压左,再压右
                 *  如果这里我们将弹出的元素打印,得到的顺序是中右左
                 *  现在我们不打印,而是将 pop 元素 压入collect栈中
                 *  这样原来先打印的元素,最后出栈,得到的顺序就是左右中了
                 */
                TreeNode pop = stack.pop();
                collect.push(pop);

                if (pop.left != null){
                    stack.push(pop.left);
                }
                if (pop.right != null){
                    stack.push(pop.right);
                }
            }

            while (!collect.isEmpty()){
                System.out.print(collect.pop().val + " ");
            }

        }
    }


    public static void main(String[] args) {
        /**
         *       1
         *      / \
         *     2   3
         *    / \ / \
         *   4  5 6 7
         */
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);
        root.right.left = new TreeNode(6);
        root.right.right = new TreeNode(7);

        posOrder(root);
    }
}

一个栈实现

一个栈实现版本引入了一个变量记录上一次打印的节点。h 就是这个变量。

如果始终没有打印过节点,h就一直是头节点。

一旦打印过节点,h就变成打印节点。

之后h的含义:上一次打印的节点。

图解

image-20250315193814334

我们先将1节点(h)入栈。然后将栈顶元素peek,并没有弹出。(此时的h等于peek)

条件判断分支的第一条,peek.left != null && h != peek.left && h != peek.right,成立,将peek的左孩子2节点压入栈。

之后进入下一次循环判断。

image-20250315193918544

将栈顶元素peek,即2节点。

条件判断分支的第一条,peek.left != null && h != peek.left && h != peek.right,成立,将peek的左孩子4节点压入栈。

image-20250315193738813

栈顶元素peek,即4节点。

4节点 既没有左树也没有右树,条件判断分支的第三条,直接打印4节点,并且将4节点弹出,h指向弹出的4节点。

image-20250315194433440

栈顶元素peek,即2节点

条件判断分支的第二条,cur.right != null && h != cur.right 成立,将2节点的右节点5压入栈。

image-20250315194337344

栈顶元素peek,即5节点。

条件判断分支的第三条成立,打印5,把5弹出,h指向5。

image-20250315203623758

栈顶元素peek,即2节点。

此时h指向5,peek.left != h,但是peek.right == h。

h来到peek的右节点,说明peek的左右子树都处理过了,现在就该处理peek了。

条件判断分支的第三条成立,打印2,把2弹出,h指向2。

image-20250315204041971

2弹出之后,peek栈顶元素为1。

条件判断分支的第二条成立,然后3入栈。(之后的流程与之前类似,不多赘述了)

image-20250315203951355

image-20250315204417558

image-20250315204528397

image-20250315204609925

image-20250315204631105

image-20250315204656673

代码
import java.util.Stack;

public class BinaryTreeTraversal {
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int v) {
            val = v;
        }
    }

     /**
     * 后序遍历(一个栈实现)
     * @param head
     */
    public static void posOrder2(TreeNode head){
        if (head != null){
            Stack<TreeNode> stack = new Stack<>();
            stack.push(head);

            while (!stack.isEmpty()){
                TreeNode peek = stack.peek();

                if (peek.left != null && peek.left != head && peek.right != head){
                    // 有左树先去处理左树
                    // peek.left != head 说明左树还没有被处理过
                    stack.push(peek.left);
                } else if (peek.right != null && peek.right != head) {
                    // 左树处理完再处理右树
                    // peek.right != head 说明右树还没有被处理过
                    stack.push(peek.right);
                }else {
                    //左右子树都处理完了,处理当前节点
                    System.out.print(peek.val + " ");
                    head = stack.pop();
                }
            }
        }
    }


    public static void main(String[] args) {
        /**
         *       1
         *      / \
         *     2   3
         *    / \ / \
         *   4  5 6 7
         */
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);
        root.right.left = new TreeNode(6);
        root.right.right = new TreeNode(7);

        posOrder2(root);
    }
}