算法通关村——树的层次遍历

树的层次遍历

1、层次遍历概念

​ 树的广度优先搜索又叫层次遍历,层次遍历就是从根节点开始,先访问根节点下面一层全部元素,再访问之后的层次,类似金字塔一样一层层访问。

​ 基本过程如下所示:

算法通关村——树的层次遍历_第1张图片

​ 每次一个节点出去的时候就把该节点的子节点存入,借助队列来存储会很方便。

​ 在上面的图中:

  • 首先1入队
  • 然后1出队,之后将1的左节点2和右节点3入队
  • 然后2出队,之后将2的左节点4和右节点5入队
  • 然后3出队,之后将3的左节点6和右节点7入队
  • 之后4,5,6,7分别出队,此时都是叶子节点,只出队就行了

2、基本的层次遍历与变换

​ 关于树的层次遍历中最基本最简单的情况就是遍历并输出全部元素,方法就是上述的方法。以下是代码实现:

List<Integer> simpleLevelOrder(TreeNode root) {
    if (root == null) {
        return new ArrayList<Integer>();
    }
    List<Integer> res = new ArrayList<Integer>();
    LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
    //将根节点放入队列中,然后不断遍历队列
    queue.add(root);
    while(queue.size() > 0) {
        TreeNode t = queue.remove();
        res.add(t,val);
        if (t.left != null) {
            queue.add(t.left);
        }
        if (t.right != null) {
            queue.add(t.right);
        }
    }
    return res;
}

根据树的结构可以看到,一个节点在一层访问之后,其子节点都是在下层按照FIFO的顺序处理的,因此队列就是一个缓存的作用。

​ 基于最基本的层次遍历,可以衍生出很多相关的问题。

2.1、二叉树的层序遍历

​ LeetCode102. 给你一个二叉树,请你返回其按层序遍历得到的节点值。(即逐层地,从左到右访问所有节点)。

示例:

二叉树:[3, 9, 20, null, null, 15, 7]

​ 3

​ / \

9 20

​ / \

​ 15 7

返回其层序遍历结果:

[

​ [3],

​ [9, 20],

​ [15, 7]

]

​ 本题与基础版的不同之处就在于要去判断某一层已经访问完了。这里可以去使用一个变量size标记一下,size表示某一层的元素个数,只要出队,就将size - 1,减到0就说明该层元素访问完了。当size变为0之后,这时队列中剩余元素的个数恰好是下一层元素的个数,因此重新将size标记为下一层的元素个数就可以继续处理新的一行了。

​ 例如对于本文开始提到的那颗二叉树来说,首先拿根节点1,其左右子节点都不为空,就将其左右子节点放入队列中,此时1已经出队,剩余元素2和3恰好就是第二层的所有节点,此时size = 2。继续,将2从队列中拿走,size–变成1,并将其子节点4和5入队,之后再将3出队,3的子节点6和7入队,此时再次size–,变成0了。当size=0时,说明当前层已经处理完了,此时队列中有4个元素,恰好就是下一层的元素个数。最后,我们把每层遍历到的节点都放入到一个结果集中,将其返回就可以了。

​ 代码实现如下:

public List<List<Integer>> levelOrder(TreeNode root) {
    if (root == null) {
        return new ArrayList<List<Integer>>();
    }
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
    //将根节点放入队列中,然后不断遍历队列
    queue.add(root);
    while(queue.size() > 0) {
        //获取当前队列的长度,也就是当前这一层的元素个数
        int size = queue.size();
        ArrayList<Integer> tmp = new ArrayList<Integer>();
        //将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中
        //如果节点的左/右子节点不为空,也放入队列中
        for (int i = 0; i < size; i++) {
            TreeNode t = queue.remove();
            tmp.add(t.val);
            if(t.left != null) {
                queue.add(t.left);
            }
            if(t.right != null) {
                queue.add(t.right);
            }
        }
        //此时的tmp就是当前层的全部元素,用List类型的tmp保存,加入最终结果集中
        res.add(tmp);
    }
    return res;
} 

2.2、层序遍历-自底向上

​ LeetCode.107 给定一个二叉树,返回其节点值自底向上的层序遍历。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。

示例:

二叉树:[3, 9, 20, null, null, 15, 7]

​ 3

​ / \

9 20

​ / \

​ 15 7

返回其自底向上层序遍历结果:

[

​ [15, 7],

​ [9, 20],

​ [3]

]

​ 如果要求从上到下输出每一层的节点值,做法是很直观的,在遍历完一层的节点之后,将存储该层节点值的列表添加到结果列表的尾部。如果是从下到上输出的画,只需要将上述操作稍作修改即可。上述操作可看成是先进先出的队列,如果要逆序输出的话,使用先进后出的栈即可。

​ 具体的代码如下:

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
    if (root == null) {
        return levelOrder;
    }
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    queue.offer(root);
    while(!queue.isEmpty()){
        List<Integer> level = new ArrayList<Integer>();
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            TreeNode left = node.left;
            TreeNode right = node.right;
            if (left != null){
                queue.offer(left);
            }
            if (right != null){
                queue.offer(right);
            }
        }
        levelOrder.add(0,level);  //相当于栈,每次都在队列头部添加
    }
    return levelOrder;
}

2.3、锯齿形层序遍历

​ LeetCode103. 给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

示例:

二叉树:[3, 9, 20, null, null, 15, 7]

​ 3

​ / \

9 20

​ / \

​ 15 7

返回其锯齿形层序遍历结果:

[

​ [3],

​ [20, 9],

​ [15, 7]

]

​ 对于这一题,依然可以沿用之前的思想,为了满足题目要求的返回值为【先从左往右,再从右往左】交替输出的锯齿形,可以利用【双端队列】来维护当前层节点值输出的顺序。在广度优先搜索遍历当前层节点拓展下一层节点的时候我们仍然从左往右按顺序拓展,但是对当前层节点从存储我们维护一个变量isOrderLeft来记录是从左往右还是从右往左的。

​ 如果从左往右,我们每次将被遍历到的元素插入至双端队列的末尾。

​ 如果从右往左,我们每次将被遍历到的元素插入至双端队列的头部。

​ 具体代码如下:

public  List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ans = new LinkedList<List<Integer>>();
        if (root == null) {
            return ans;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        boolean isOrderLeft = true;
        while (!queue.isEmpty()) {
            //采用双端队列
            Deque<Integer> levelList = new LinkedList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; ++i) {
                TreeNode curNode = queue.poll();
                //根据isOrderLeft来决定将被遍历到的元素插入到哪
                if (isOrderLeft) {
                    levelList.offerLast(curNode.val);
                } else {
                    levelList.offerFirst(curNode.val);
                }
                if (curNode.left != null) {
                    queue.offer(curNode.left);
                }
                if (curNode.right != null) {
                    queue.offer(curNode.right);
                }
            }
            ans.add(new LinkedList<Integer>(levelList)); //此处在添加的时候需要做类型转换
            isOrderLeft = !isOrderLeft;
        }
        return ans;
    }

3、总结

​ 树的层次遍历的思路都是一样的,都是借助队列来做一个缓存,使得每次父节点出队的时候有对应的子节点入队。再处理好每一层节点的输出顺序即可。

你可能感兴趣的:(树,数据结构)