数据结构-二叉树节点遍历

71 阅读7分钟

广度优先遍历(层次遍历)

对一棵二叉树进行广度优先遍历,就是对一棵二叉树中所有的节点,按照层次从上到下、每一层中节点从左到右的顺序,将二叉树中的所有节点进行遍历的操作。广度优先遍历方式只有一种,需要依赖于队列结构。

使用队列对二叉树进行层序遍历的步骤如下:

  • 步骤1:将二叉树的根节点加入队列
  • 步骤2:将队列头节点出队列
  • 步骤3.1:如果根节点具有左孩子,则根节点的左孩子入队列
  • 步骤3.2:如果根节点具有右孩子,则根节点的右孩子入队列
  • 然后重复上述的步骤2至步骤3.2,直至整个队列中不再具有元素为止。在步骤3.1至步骤3.2中,一定是左孩子先进入队列,然后才是右孩子进入队列。

深度优先遍历

深度优先遍历是二叉树遍历方式中比较重点的一种遍历方式,可以分为先序遍历中序遍历后序遍历3种遍历序列。和广度优先遍历方式不同的是,深度优先遍历是从根节点开始,一条路走到黑,一个分支一个分支的进行节点遍历,其中一个分支全部遍历结束之后,再去进行下一个分支的遍历。在深度优先遍历中,我们必须记住这样一个规则:如何遍历整个二叉树,就如何遍历左右子树

先序遍历

先序遍历处理节点的顺序是:根左右,即先访问根节点,然后访问左子树,最后访问右子树。

先序遍历二叉树节点的步骤是:

  • 步骤1:首先得到整个二叉树的根节点,并访问这个根节点。
  • 步骤2:如果根节点具有左孩子,则将左孩子及其子节点作为一个单独的二叉树进行先序遍历。
  • 步骤3:在遍历过程中,如果一个节点没有左孩子或者左孩子已经遍历完成,那么判断这个节点有没有右孩子。
  • 步骤4:如果这个节点具有右孩子,则将右孩子及其子节点作为一个单独的二叉树进行先序遍历。
  • 步骤5:如果这个节点的没有右孩子或者右孩子已经遍历完成,那么向上回溯到父节点,重复判断父节点是否具有右孩子。
  • 步骤6:重复上述步骤2-5,直到最终回溯到根节点为止,表示根节点及其左右子树已经全部遍历完成,得到完整的先序遍历序列。

中序遍历

中序遍历处理节点的顺序是:左根右,即先访问左子树,然后访问根节点,最后访问右子树。

中序遍历二叉树节点的步骤是:

  • 步骤1:在得到根节点的时候,首先不要对根节点进行访问,而是直接判断这个根节点有没有左孩子。
  • 步骤2:如果根节点存在左孩子,则对以左孩子为根的左子树进行中序遍历。
  • 步骤3:如果一个节点不存在左孩子,或者左孩子已经遍历完成,那么项父节点进行回溯,在回溯到父节点的时候,访问父节点。
  • 步骤4:在父节点访问完成后,判断父节点是否具有右孩子。
  • 步骤5:如果父节点具有右孩子,则对右孩子为根的右子树进行中序遍历。
  • 步骤6:如果父节点不具有右孩子或者右子树已经遍历完成,那么向上回溯的父节点。
  • 步骤7:重复上述步骤1-6,直到回溯到整个二叉树的根节点为止,遍历完成,得到完整的中序遍历序列。

后序遍历

后序遍历处理节点的顺序是:左右根,即先访问左子树,然后访问右子树,最后访问根节点。

后序遍历二叉树节点的步骤是:

  • 步骤1:得到整个二叉树的根节点,但是并不访问根节点,判断根节点是否具有左孩子。
  • 步骤2:如果根节点具有左孩子,则后序遍历以左孩子为根的左子树。
  • 步骤3:如果根节点不具有左孩子,或者左子树遍历完成,则回溯到根节点,但是依然不访问根节点,而是判断根节点是否具有右孩子。
  • 步骤4:如果根节点具有右孩子,则后序遍历以右孩子为根的右子树。
  • 步骤5:如果根节点不具有右孩子,或者右子树遍历完成,则回溯到根节点,此时访问根节点。
  • 步骤6:重复上述步骤1-5,直到回溯到整个二叉树的根节点并且根节点访问完毕为止。
public class MyTree {
    // 节点类
    class Node<E> {
        E data;
        Node<E> left;
        Node<E> right;
    }

    // 构建满二叉树
    public Node<Integer> buildTree(int level, int value) {
        Node<Integer> node = new Node<>();
        node.data = value;
        if (level == 1) {
            return node;
        }
        Node<Integer> leftNode = buildTree(level - 1, value * 2);
        node.left = leftNode;
        Node<Integer> rightNode = buildTree(level - 1, value * 2 + 1);
        node.right = rightNode;
        return node;
    }

    /**
     * 前序遍历(递归)
     * 先序遍历的顺序是:先依次访问每颗子树的根节点,然后再依次访问左子树和右子树。
     * 即:根-左-右
     */
    public <E> void preOrderTraverse(Node<E> root) {
        if (root == null) return;
        System.out.print(root.data + " ");// 先根节点
        preOrderTraverse(root.left);// 遍历左子树
        preOrderTraverse(root.right);// 遍历右子树
    }

    // 前序遍历(非递归,使用栈)
    public <E> void preOrderTraverseUseStack(Node<E> root) {
        if (root == null) return;
        Stack<Node<E>> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node<E> curNode = stack.pop();
            System.out.print(curNode.data + " ");// 先根节点
            // 先将右子树压入栈,再将左子树压入栈,这样出栈的时候会先出左子树
            if (curNode.right != null) {
                stack.push(curNode.right);
            }
            if (curNode.left != null) {
                stack.push(curNode.left);
            }
        }
    }

    /**
     * 中序遍历(递归)
     * 中序遍历的顺序是:先依次访问每颗子树的左孩子节点,然后访问子树的根节点,最后访问子树的右孩子节点。
     * 即:左-根-右。
     */
    public <E> void inOrderTraverse(Node<E> root) {
        if (root == null) return;
        inOrderTraverse(root.left);// 遍历左子树
        System.out.print(root.data + " ");// 先根节点
        inOrderTraverse(root.right);// 遍历右子树
    }

    // 中序遍历(非递归,使用栈)
    public <E> void inOrderTraverseUseStack(Node<E> root) {
        if (root == null) return;
        Stack<Node<E>> stack = new Stack<>();
        Node<E> curNode = root;
        while (curNode != null || !stack.isEmpty()) {
            // 一直循环到二叉排序树最左端的叶子结点(curNode是null)
            while (curNode != null) {
                stack.push(curNode);
                curNode = curNode.left;
            }
            curNode = stack.pop();
            System.out.print(curNode.data + " ");
            curNode = curNode.right;
        }
    }

    /**
     * 后序遍历(递归)
     * 后序遍历的顺序是:先依次访问每颗子树的左孩子节点,然后访问子树的右孩子节点,最后访问根节点。
     * 即:左-右-根。
     */
    public <E> void postOrderTraverse(Node<E> root) {
        if (root == null) return;
        postOrderTraverse(root.left);// 遍历左子树
        postOrderTraverse(root.right);// 遍历右子树
        System.out.print(root.data + " ");// 先根节点
    }

    // 后序遍历(非递归,使用栈)
    public <E> void postOrderTraverseUseStack(Node<E> root) {
        Stack<Node<E>> stack = new Stack<>();
        Node<E> curNode = root;
        Node<E> rightNode = null;
        while (curNode != null || !stack.isEmpty()) {
            // 一直循环到二叉排序树最左端的叶子结点(curNode是null)
            while (curNode != null) {
                stack.push(curNode);
                curNode = curNode.left;
            }
            curNode = stack.pop();
            // 当前结点没有右结点或上一个结点(已经输出的结点)是当前结点的右结点,则输出当前结点
            while (curNode.right == null || curNode.right == rightNode) {
                System.out.print(curNode.data + " ");
                rightNode = curNode;
                if (stack.isEmpty()) {
                    return; // root已输出,则遍历结束
                }
                curNode = stack.pop();
            }
            stack.push(curNode);// 还有节点没有遍历
            curNode = curNode.right;
        }
    }

    /**
     * 广度优先遍历(层次遍历)
     * 遍历顺序是:先从树的根节点开始,依次访问根节点的每一个直接子节点,然后再访问这些子节点的直接子节点,
     * 以此类推,直到到达叶节点。
     */
    public <E> void bfTraversal(Node<E> root) {
        if (root == null) return;
        LinkedList<Node<E>> queue = new LinkedList<>();// 创建一个队列来存放要访问的节点
        queue.offer(root);// 将根节点加入队列中
        while (!queue.isEmpty()) {
            Node<E> curNode = queue.poll();// 从队列头部取出一个节点进行处理
            System.out.print(curNode.data + " ");
            if (curNode.left != null) {
                queue.offer(curNode.left);// 左子节点不为空时将其加入队列尾部
            }
            if (curNode.right != null) {
                queue.offer(curNode.right);// 右子节点不为空时将其加入队列尾部
            }
        }
    }

    public static void main(String[] args) {
        MyTree tree = new MyTree();
        Node<Integer> root = tree.buildTree(3, 1);
        System.out.println("root:" + root.data);// 打印-->root:1

        System.out.print("广度优先遍历(层次遍历):");// 打印-->广度优先遍历(层次遍历):1 2 3 4 5 6 7
        tree.bfTraversal(root);
        System.out.println();

        System.out.print("前序遍历:");// 打印-->前序遍历:1 2 4 5 3 6 7
        tree.preOrderTraverse(root);
        System.out.println();

        System.out.print("使用栈前序遍历:");// 打印-->使用栈前序遍历:1 2 4 5 3 6 7
        tree.preOrderTraverseUseStack(root);
        System.out.println();

        System.out.print("中序遍历:");// 打印-->中序遍历:4 2 5 1 6 3 7
        tree.inOrderTraverse(root);
        System.out.println();

        System.out.print("使用栈中序遍历:");// 打印-->使用栈中序遍历:4 2 5 1 6 3 7
        tree.inOrderTraverseUseStack(root);
        System.out.println();

        System.out.print("后序遍历:");// 打印-->后序遍历:4 5 2 6 7 3 1 
        tree.postOrderTraverse(root);
        System.out.println();

        System.out.print("使用栈后序遍历:");// 打印-->使用栈后序遍历:4 5 2 6 7 3 1 
        tree.postOrderTraverseUseStack(root);
        System.out.println();
    }
}