数据结构与算法——二叉树的遍历总结

258 阅读4分钟

树是常用的数据结构,树的遍历算法可以在多种许多情况中都有使用。接下来介绍树的遍历,常见的遍历右前序遍历、中序遍历、后序遍历、层序遍历。 树的遍历方式,会用到许多知识:

  • 递归
  • Stack(栈)
  • Queue(队列)

前序遍历(深度优先遍历)

前序遍历的访问顺序为:

  • 访问根节点
  • 前序遍历访问左子树
  • 前序遍历访问右子树 对于递归访问,我们先明确该函数表达的含义:按照前序遍历的顺序访问以root参数为根节点的树。那么根据前序遍历的定义我们得到如下的函数:
//递归版
private void selfPreorderTraversal(TreeNode root, List<Integer> list) {
    if (root == null) {
        return;
    }
    list.add(root.val);
    selfPreorderTraversal(root.left, list);
    selfPreorderTraversal(root.right, list);
}

对于前序遍历的非递归实现,我们先来思考前序遍历的过程 对树中的一个节点,对它的访问是:1)先访问当前节点;2)再访问其左子树;3)最后访问其右子树。现在的问题是:我们访问其左子树的结束后,如何继续访问其右子树。再递归的实现中,是利用系统分配的函数栈来记录还没有访问右子树的节点。那么在非递归实现中我们就需要Stack数据结构来辅助我们完成访问

具体的做法:

  • 从根节点开始访问
  • 对每一个节点访问属性,为了以后能访问其右子树,将该节点入栈,接着指向左孩子
  • 当指向的节点为空的时候表明前一个节点的左子树访问结束,此时从Stack中弹出元素,继续访问其右节点,进行第二个过程
private void selfPreorderTraversalIterSingle(TreeNode root, List<Integer> list) {
    if (root == null) {
        return;
    }

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

    while(iter != null || !stack.isEmpty()) {
        //从stack提出下一个遍历点
        if (iter == null) {
            iter = stack.pop();
            iter = iter.right;
        } else {
            //此时iter一定不为空
            //入栈的时候访问数据
            list.add(iter.val);
            stack.push(iter);
            iter = iter.left;
        }
    }
}

中序遍历

定义:

  • 中序遍历访问左子树
  • 访问根节点
  • 中序遍历访问右子树
    //递归版
    private void selfInorderTraversal(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }

        selfInorderTraversal(root.left, res);
        res.add(root.val);
        selfInorderTraversal(root.right, res);

    } 

中序遍历的非递归实现与前序类似,区别在于前序遍历是在入栈的时候访问数据,中序遍历是在出栈时候访问数据

//非递归
private void selfInorderTraversalIter(TreeNode root, List<Integer> list) {
        
        if (root == null) {
            return;
        }

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

        while(iter != null || !stack.isEmpty()) {
            if (iter == null) {
                //从stack提出下一个遍历点
                iter = stack.pop();
                //出栈的时候访问数据
                list.add(iter.val);
                iter = iter.right;
            } else {
                stack.push(iter);
                iter = iter.left;
            }
        }
    }

后序遍历

定义

  • 后序遍历访问左子树
  • 后序遍历访问右子树
  • 访问根节点
//递归
    private void selfRecursionPostorderTraversal(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        
        selfRecursionPostorderTraversal(root.left, res);
        selfRecursionPostorderTraversal(root.right, res);
        res.add(root.val);
    }
       

后序遍历的非递归实现:对于根节点的访问,后序遍历的访问顺序是:左-》右-》根,如果将这个顺序颠倒:根-》右-》左,这个顺序与前序遍历的根-》左-》右是否很相似。那么我们使用两个Stack,一个Stack用来实现后序遍历的逆序访问,一个栈(output)记录后序遍历逆序顺序,最后我们将output栈中的元素出栈,就是后序遍历的顺序。

//迭代
    private void selfIterPostorderTraversal(TreeNode root, List<Integer> res) {
        Stack<TreeNode> stack = new Stack<>();
        Stack<TreeNode> output = new Stack<>();
        TreeNode node = root;

        /*
        后序遍历对每一个节点的访问顺序是:左——》右——》中,其逆顺序就是中-》右-》左。
        则根据其逆顺序入栈(有点类似于前序遍历),在从栈输出
        */
        while (node != null || !stack.isEmpty()) {
            if (node != null) {
                stack.push(node);
                output.push(node);
                node = node.right;
            } else {
                node = stack.pop();
                node = node.left;
            }
        }

        while (output.size() > 0) {
            TreeNode n = output.pop();
            res.add(n.val);
        }
    }

层序遍历(广度优先遍历) 树的层序遍历就是广度优先遍历,可以利用Queue先进先出的特性

  • 先将根节点放入队列
  • 接下来开始对队列左迭代操作,对与每一个Queue弹出的元素,访问其属性,并将其左孩子、右孩子以此放入队尾。直到队列为空。
public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new LinkedList<>();
        LinkedList<TreeNode> fifo = new LinkedList<>();

        if (root == null) {
            return result;
        }

        TreeNode temp;
        fifo.add(root);

        while (fifo.size() != 0) {

            //每一回先将一层的结点放入队列,end作为每一层的结束标志
            TreeNode end = fifo.getLast();

            //存储每一层的结点值
            List<Integer> storage = new LinkedList();

            //以end作为上一层结点的结束标记,将队列中上一层的结点弹出,同时将下一层的结点放入队列
            do {
                temp = fifo.pop();
                if (temp.left != null)
                    fifo.add(temp.left);
                if (temp.right != null)
                    fifo.add(temp.right);

                //System.out.println("value of node" + temp.val);
                storage.add(temp.val);
            }while(end != temp);

            //将刚弹出的一层的结点存入结果
            result.add(storage);
        }

        return result;
    }