树的遍历

树的遍历方式主要分为四种,先序、中序、后序和层序,在这篇博客中我将仔细介绍一下树的这四种遍历方式。
在这里插入图片描述

先序遍历

先序遍历,也叫先根遍历、前序遍历,首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树,如果二叉树为空则返回。可以简记为根左右。
以上图为例,整体的遍历过程为:

  • 先遍历A节点
  • 然后遍历A的左子节点B节点
  • 接着遍历B节点的左子节点D节点
  • D节点没有左子节点和右子节点,然后我们回溯到B节点,遍历B节点的右子节点E节点
  • 同理E没有左子节点和右子节点,我们继续回溯到B节点,发现B节点的左右子节点都已被访问,再接着回溯到B的父节点A。
  • 遍历A节点的右节点C
  • 遍历C的左子节点F,F没有左右子节点,回溯到C节点,C节点没有右子树,回溯到A节点,此使所有的节点都已被访问完毕。
  • 遍历结果为:ABDECF

代码实现

树先序遍历的实现方式主要包括两种,递归和迭代,递归算法调用隐式栈,而迭代算法显式的调用栈。

递归实现
//根左右,先访问根节点,然后访问左子树,最后再访问右子树
public List<Integer> preorderTraversal(TreeNode root){
        List<Integer> res = new ArrayList<>();
        preorder(root,res);
        return res;
    }

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

先序遍历的迭代实现要借用数据结构栈,首先我们要知道栈这种数据结构是先进后出,所以我们入栈的时候要先压入最右的节点,然后依次从右往左压入节点,这样就能保证出栈的时候首先访问左节点。以上图为例整体的遍历流程如下:

  • 初始化一个列表res用来存放访问结果
  • 首先将根节点A压入栈中,此时栈中只有元素A,stack(A)
  • 我们将A节点出栈,存入到res中,将A的子节点依次从右到左压入栈中,此时栈顶元素为B,栈中的元素为stack(B,C)
  • 然后B节点出栈,存入res中,将B节点的子节点依次从右到左压入栈中,此使栈顶元素为D,栈中的元素为stack(D,E,C)
  • 然后我们将D节点出栈存入到res中,因为D节点为叶节点没有子节点,所以此时的栈顶元素为E,栈中元素为stack(E,C)
  • 重复上述过程,直到栈空位置,遍历结束,返回res列表,即为访问结果
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> res=new ArrayList<Integer>();
        if(root==null)
            return res;
        Stack<TreeNode> stack=new Stack<TreeNode>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode tree=stack.pop();      //先往栈中压入右节点,再压左节点,这样出栈就是先左节点后右节点了。
            if(tree.right!=null)
                stack.push(tree.right);
            if(tree.left!=null)
                stack.push(tree.left);
            res.add(tree.val);
        }
        return res;
    }

中序遍历

中序遍历,也叫中根遍历,遍历方式可以简记为左根右。首先访问左子树,然后访问根节点,最后遍历右子树,若二叉树为空结束访问,否则:

  • 中序遍历左子树
  • 访问根节点
  • 中序遍历右子树
    具体流程如下:
  • 初始化res列表存储访问结果
  • 首先访问根节点A的左子树BDE
  • 发现B也有左右子树,那么我们先访问B的左子树D
  • D没有左子树,所以我们先将D存入res列表中
  • 根据左根右的思想,然后我们访问D节点的根节点B,将B存入res列表中
  • 然后DB都访问过了,即左根都被访问了,接着我们访问B节点的右子树E
  • 因为E没有左子树,所以将E存入res列表中,判断发现E也没有右子树,我们回溯到B节点,发现B的左右节点都已被访问,我们回溯到A节点,将A节点存入列表中
  • 整棵树的左子树和根节点均已被访问,接着访问根节点A的右子树。
  • 重复上述过程,直到访问完所以的节点。

上图的访问结果为:DBEAFC

代码实现

递归实现
public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root,res);
        return res;

    }

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

中序遍历的迭代实现要依靠栈数据结构,有一个口诀就是中序遍历不忘“左链入栈”

public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        if(root == null) return res;
        /*退出循环的条件,root != null没有特殊含义,只是让循环得以进行下去,
        循环的终止条件只要是判断栈空不空所决定*/
        while (root != null || !stack.isEmpty())
        {
            while (root != null)    //左链入栈
            {
                stack.push(root);
                root = root.left;
            }
            TreeNode node = stack.pop();  //左子树为空将根节点出栈
            res.add(node.val);
            root = node.right;   //访问右子树

        }
        return res;

    }

后序遍历

后序遍历(LRD),也叫做后根遍历,可记做左右根。后序遍历有递归算法和非递归算法两种。在二叉树中,先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点。

  • 后序遍历左子树

  • 后序遍历右子树

  • 访问根结点
    以上图为例我们走一遍整体的遍历流程:

  • 初始化res列表存储遍历结果

  • 首先访问根节点A的左子树BDE

  • 发现节点B也有左右子树,那我们访问B节点的左子树D

  • D节点没有左右子树,所以将D节点存入到res列表中

  • 接着我们回溯到B节点,发现其存在右子树E,那我们先访问E节点

  • E节点没有左右子树,我们将E节点存入res列表中

  • 此使B节点的左右子树访问完毕,我们将B节点存入res中,此时节点A的左子树访问完毕,我们访问其右子树。

  • 右子树仍然按照左右跟的思想进行遍历。

  • 最后在访问根节点A,至此遍历结束

代码实现

代码实现主要包括两种:

  • 递归实现
  • 迭代实现
递归实现
public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        postorder(root, res);
        return res;
    }

    public void postorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        postorder(root.left, res);   //遍历左子树
        postorder(root.right, res);  //遍历右子树
        res.add(root.val);           //访问根节点
    }

迭代实现
public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode prev = null;
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            //pre用来标记上一次右子树是否已经访问过
            if (root.right == null || root.right == prev) {
                res.add(root.val);
                prev = root;
                root = null;
            } else {
                stack.push(root);
                root = root.right;
            }
        }
        return res;


    }
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐