剑指Offer-62-把二叉树打印成多行

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/83422841

项目地址:https://github.com/SpecialYy/Sword-Means-Offer

问题

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

解析

这道题看似考察是层次遍历,其实不然,该题添加了按行打印的需求。其实这道题与这一题非常类似,我也在那一题中给出本题的解题思路。换句话说,上一题应该算是这一题的变形和拓展。

这里,首先阐明一下层次遍历的思路:

层级遍历:依次打印二叉树每一层的节点。先打印根节点,然后打印根节点的左右孩子,然后再打印这些孩子的左右孩子,直到最后一层为止。

屏幕快照 2018-10-26 00.16.02

如上图,层次遍历的顺序且带换行的形式为:

1

2,3

4,5,6,7

思路一

思路一主要通过确保每次循环时队列中节点集合即为当前层的节点数来打印每一行的。我们首先观察层次遍历的形式,发现右孩子总是先与左孩子出现,我们一般遍历时也是先左孩子后右孩子,所以符合先进先出的要求,很明显这里采用队列的数据结构比较合适。还有个问题我们如何控制换行呢,也就是我们怎么能做到打印出的形式符合原先树的层级关系呢?显然我们只要保证在出队时只出当前行的节点即可,一种做法时在出队列时首先获取当前队列的长度,因为这时队列里的节点就是当前行的节点。由于队列的先入先出的特性,我们对于出队的节点的左孩子和右孩子进队的行为不会影响获取当前层的节点的操作,因为我们事先获取了长度,所以不会出现任何差错。

老规矩,举个例子,拿上图为例:

  1. 首先根节点1入队列
  2. 获取队列的长度,长度为1,说明当前层的节点数为1,打印1,然后把1的左孩子2和右孩子3入队列
  3. 获取队列的长度,长度为2,说明当前层的节点数为2,依次出队2,3,同时把他们的左右孩子4,5,6和7入队
  4. 获取队列的长度,长度为4,说明当前层的节点数为4,依次出队4,5,6和7,因为他们都没有孩子,所以没有入队操作
  5. 队列为空,结束

基于以上的算法步骤,即可确保层次遍历且自带换行操作,保证了打印的序列与二叉树形式一致。

	/**
     * 方法一:每次循环时确保队列中仅含当前层节点
     * @param pRoot
     * @return
     */
    ArrayList<ArrayList<Integer>> Print1(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        if (pRoot == null) {
            return null;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        while (!queue.isEmpty()) {
            ArrayList<Integer> row = new ArrayList<>();
            int length = queue.size();
            for (int i = 0; i < length; i++) {
                TreeNode node = queue.poll();
                row.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            result.add(row);
        }
        return result;
    }

方法二

我们可以通过判断当前是否遍历到每一层的末尾节点来决定是否换行。具体做法,用2个指针:

  • last: 表示当前层的末尾节点
  • nLast: 表示下一层的末尾节点

我们同样使用队列来辅助层级遍历的过程,在每一次遍历中,我们都会判断当前节点是否等于last (是否走到了当前的末尾) 来决定换行。如果当前节点确实是层的末尾节点,则更新last为nLast,因为我们即将开始遍历下一层,所以要更新新的末尾节点。而nLast的更新过程就简单多了,对于每一个出队的节点,令其左右孩子来更新nLast。左右孩子位于下一层,借助先进先出的队列数据结构,新得到nLast必然是目前下一层最末尾的节点,直到当前层走到末尾为止。

上图栗子走起来,好饿~~~~~今晚百度的猪扒饭没有学校的好吃,sad!

  1. 首先last,nLast都指向节点1
  2. 节点1出队列,利用其左右孩子更新nLast,此时nLast指向下一层的3节点。判断节点1是否是末尾节点 (是否等于last),是,则打印换行,同时更新last = nLast,此时last指向3节点。
  3. 节点2出队列,利用其左右孩子更新nLast,此时nLast指向下一层的4节点。判断节点2是否是末尾节点 (是否等于last),不是,继续出队操作
  4. 节点3出队列,利用其左右孩子更新nLast,此时nLast指向下一层的7节点。判断节点3是否是末尾节点 (是否等于last),是,则打印换行,同时更新last = nLast,此时last指向7节点。
  5. 节点4出队列,无左右孩子,不更新nLast,不是末尾节点,继续出队操作。
  6. 同步骤5一样,直到节点7出队,判断节点7是否是末尾节点 (是否等于last),是,则打印换行。
  7. 队列为空,结束
	/**
     * 双指针法
     * @param pRoot
     * @return
     */
    ArrayList<ArrayList<Integer>> Print2(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        if (pRoot == null) {
            return null;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        TreeNode last = pRoot, nLast = pRoot;
        ArrayList<Integer> row = new ArrayList<>();
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            row.add(node.val);
            if (node.left != null) {
                queue.offer(node.left);
                nLast = node.left;
            }
            if (node.right != null) {
                queue.offer(node.right);
                nLast = node.right;
            }
            if (node == last) {
                result.add(row);
                row = new ArrayList<>();
                last = nLast;
            }

        }
        return result;
    }

总结

熟练掌握二叉树的遍历算法,同时还要学会融会贯通,结合其他数据结构,开阔思维。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/83422841