本题来自左神《程序员代码面试指南》“二叉树的按层打印与ZigZag打印”题目。

题目

给定一棵二叉树的头节点 head,分别实现 按层ZigZag 打印 二叉树的函数。

例如,二叉树如图 3-29 所示。

按层打印时,输出格式必须如下:

Level 1 : 1
Level 2 : 2 3
Level 3 : 4 5 6
Level 4 : 7 8

ZigZag 打印时,输出格式必须如下:

Level 1 from left to right: 1
Level 2 from right to left: 3 2
Level 3 from left to right: 4 5 6
Level 4 from right to left: 8 7

题解

1、按层打印的实现

按层打印原本是十分基础的内容,对二叉树做简单的宽度优先遍历即可,但本题有额外的要求,那就是 同一层的节点必须打印在一行上,并且要求输出行号。这就需要我们在原来宽度优先遍历的基础上做一些改进,所以关键问题是如何知道该换行。

只需要用两个 node 类型的变量 last 和 nLast 就可以解决这个问题,last 变量表示正在打印的当前行的最右节点,nLast 表示下一行的最右节点

假设我们每一层都做从左到右的宽度优先遍历,如果发现遍历到的节点等于 last,则说明应该换行。换行之后,只要令 last=nLast,就可以继续下一行的打印过程,重复此过程,直到所有的节点都打印完。那么问题就变成了:如何更新 nLast?只需要让 nLast 一直跟踪记录宽度优先队列中的最新加入的节点即可。这是因为最新加入队列的节点一定是目前已经发现的下一行的最右节点。所以在当前行打印完时,nLast 一定是下一行所有节点中的最右节点

public static void printByLevel(Node head) {if (head == null) {return;}Queue<Node> queue = new LinkedList<Node>();int level = 1;Node last = head; // 当前行的最右节点Node nLast = null; // 下一行的最右节点queue.offer(head);System.out.print("Level " + (level++) + " : ");while (!queue.isEmpty()) { // 每一层都做从左到右宽度优先遍历head = queue.poll();System.out.print(head.value + " ");if (head.left != null) {queue.offer(head.left);nLast = head.left; // 让nLast一直跟踪记录宽度优先队列中的最新加入的节点即可,因为最新加入队列的节点一定是目前已经发现的下一行的最右节点}if (head.right != null) {queue.offer(head.right);nLast = head.right;}if (head == last && !queue.isEmpty()) { // 如果发现遍历到的节点等于last,则说明应该换行System.out.print("\nLevel " + (level++) + " : ");last = nLast; // 当前行打印完时,nLast一定是下一行所有节点中的最右节点,只要令last=nLast,就可以继续下一行的打印过程}}System.out.println();
}

2、ZigZag 打印的实现

使用一个双端队列,具体为 Java 中的 LinkedList 结构,这个结构的底层实现就是非常纯粹的双端队列结构,本方法也仅使用双端队列结构的基本操作。

原则1:如果是从左到右的过程,那么一律从 dq 的头部弹出节点

  • 如果弹出的节点没有孩子节点,当然不用放入任何节点到 dq 中
  • 如果当前节点有孩子节点,先让左孩子节点从尾部进入 dq,再让右孩子节点从尾部进入dq

原则2:如果是从右到左的过程,那么一律从 dq 的尾部弹出节点

  • 如果弹出的节点没有孩子节点,当然不用放入任何节点到 dq 中
  • 如果当前节点有孩子节点,先让右孩子从头部进入 dq,再让左孩子节点从头部进入 dq

原则1原则2 的过程切换,我们可以完成 ZigZag 的打印过程,所以现在只剩一个问题:如何确定切换原则1和原则2的时机?这其实还是 如何确定每一层最后一个节点的问题。具体方法见代码中的详细注释。

ZigZag 打印的全部过程请参看如下代码中的 printByZigZag 方法。


代码

含测试用例

package chapter_3_binarytreeproblem;import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;public class Problem_09_PrintBinaryTreeByLevelAndZigZag {public static class Node {public int value;public Node left;public Node right;public Node(int data) {this.value = data;}}/*** 二叉树的按层打印*/public static void printByLevel(Node head) {if (head == null) {return;}Queue<Node> queue = new LinkedList<Node>();int level = 1;Node last = head; // 当前行的最右节点Node nLast = null; // 下一行的最右节点queue.offer(head);System.out.print("Level " + (level++) + " : ");while (!queue.isEmpty()) { // 每一层都做从左到右宽度优先遍历head = queue.poll();System.out.print(head.value + " ");if (head.left != null) {queue.offer(head.left);nLast = head.left; // 让nLast一直跟踪记录宽度优先队列中的最新加入的节点即可,因为最新加入队列的节点一定是目前已经发现的下一行的最右节点}if (head.right != null) {queue.offer(head.right);nLast = head.right;}if (head == last && !queue.isEmpty()) { // 如果发现遍历到的节点等于last,则说明应该换行System.out.print("\nLevel " + (level++) + " : ");last = nLast; // 当前行打印完时,nLast一定是下一行所有节点中的最右节点,只要令last=nLast,就可以继续下一行的打印过程}}System.out.println();}/*** ZigZag 打印的实现*/public static void printByZigZag(Node head) {if (head == null) {return;}Deque<Node> dq = new LinkedList<Node>();int level = 1;boolean left2right = true; // 是否是从左到右打印过程Node last = head; // 当前行的最右节点Node nLast = null; // 下一行的最右节点dq.offerFirst(head);pringLevelAndOrientation(level++, left2right);while (!dq.isEmpty()) {if (left2right) { // 【原则1】:如果是从左到右的过程,那么一律从dq的头部弹出节点head = dq.pollFirst();// 如果当前节点有孩子节点,先让左孩子节点从尾部进入dq,再让右孩子节点从尾部进入dqif (head.left != null) {// 区别于"按层打印",此处的"zigzag打印"的特点为:下一层最后打印的节点是当前层有孩子节点的节点中最先进入dq的节点nLast = (nLast == null ? head.left : nLast); // 如果nlast非空,则保持其为"最先"进入dq的节点,不更新dq.offerLast(head.left);}if (head.right != null) {nLast = (nLast == null ? head.right : nLast);dq.offerLast(head.right);}} else { // 【原则2】:如果是从右到左的过程,那么一律从dq的尾部弹出节点head = dq.pollLast();// 如果当前节点有孩子节点,先让右孩子从头部进入dq,再让左孩子节点从头部进入dqif (head.right != null) {nLast = (nLast == null ? head.right : nLast);dq.offerFirst(head.right);}if (head.left != null) {nLast = (nLast == null ? head.left : nLast);dq.offerFirst(head.left);}}System.out.print(head.value + " ");// 如何确定切换【原则1】和【原则2】的时机?这其实还是如何确定每一层最后一个节点的问题// 下一层最后打印的节点是当前层有孩子节点的节点中最先进入dq的节点if (head == last && !dq.isEmpty()) {left2right = !left2right;last = nLast;nLast = null; // 换行之后置空nLast,与上面的非空判断配合,保证其为"最先"进入dq的节点System.out.println();pringLevelAndOrientation(level++, left2right);}}System.out.println();}public static void pringLevelAndOrientation(int level, boolean lr) {System.out.print("Level " + level + " from ");System.out.print(lr ? "left to right: " : "right to left: ");}// for test -- print treepublic static void printTree(Node head) {System.out.println("Binary Tree:");printInOrder(head, 0, "H", 17);System.out.println();}public static void printInOrder(Node head, int height, String to, int len) {if (head == null) {return;}printInOrder(head.right, height + 1, "v", len);String val = to + head.value + to;int lenM = val.length();int lenL = (len - lenM) / 2;int lenR = len - lenM - lenL;val = getSpace(lenL) + val + getSpace(lenR);System.out.println(getSpace(height * len) + val);printInOrder(head.left, height + 1, "^", len);}public static String getSpace(int num) {String space = " ";StringBuffer buf = new StringBuffer("");for (int i = 0; i < num; i++) {buf.append(space);}return buf.toString();}public static void main(String[] args) {Node head = new Node(1);head.left = new Node(2);head.right = new Node(3);head.left.left = new Node(4);head.right.left = new Node(5);head.right.right = new Node(6);head.right.left.left = new Node(7);head.right.left.right = new Node(8);printTree(head);System.out.println("===============");printByLevel(head);System.out.println("===============");printByZigZag(head);}
}
输出结果:
Binary Tree:v6v       v3v       v8v       ^5^       ^7^       H1H       ^2^       ^4^       ===============
Level 1 : 1
Level 2 : 2 3
Level 3 : 4 5 6
Level 4 : 7 8
===============
Level 1 from left to right: 1
Level 2 from right to left: 3 2
Level 3 from left to right: 4 5 6
Level 4 from right to left: 8 7

左神算法:二叉树的按层打印与ZigZag打印(Java版)相关推荐

  1. 左神算法:环形单链表的约瑟夫问题(Java版)

    本题来自左神<程序员面试代码指南>"环形单链表的约瑟夫问题"题目. 题目 据说,著名犹太历史学家 Josephus 有过以下故事: 在罗马人占领乔塔帕特后,39 个犹太 ...

  2. 左神算法:最大值减去最小值小于或等于num的子数组的数量(Java版)

    本题来自左神<程序员面试代码指南>"最大值减去最小值小于或等于num的子数组的数量"题目. 题目 给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况: ...

  3. 左神算法:求最大子矩阵的大小(Java版)

    本题来自左神<程序员面试代码指南>"求最大子矩阵的大小"题目. 题目 给定一个整型矩阵 map,其中的值只有0和1两种,求其中全是1的所有矩形区域中,最大的矩形区域为1 ...

  4. 左神算法:如何较为直观地打印二叉树(Java版)

    本题来自左神<程序员代码面试指南>"如何较为直观地打印二叉树"题目. 题目 二叉树可以用常规的三种遍历结果来描述其结构,但是不够直观,尤其是二叉树中有重复值的时候,仅通 ...

  5. 左神算法:在二叉树中找到累加和为指定值的最长路径长度(Java版)

    本题来自左神<程序员代码面试指南>"在二叉树中找到累加和为指定值的最长路径长度"题目. 题目 给定一棵二叉树的头节点 head 和一个 32 位整数 sum,二叉树节点 ...

  6. 左神算法:分别用递归和非递归方式实现二叉树先序、中序和后序遍历(Java版)

    本题来自左神<程序员代码面试指南>"分别用递归和非递归方式实现二叉树先序.中序和后序遍历"题目. 题目 用递归和非递归方式,分别按照二叉树先序.中序和后序打印所有的节点 ...

  7. 左神算法4.二叉树及相关习题理解

    题目1:实现二叉树的先序.中序.后序遍历[递归方式和非递归方式] 1.1递归实现 public class BinaryTreeWithRecur {public static class Node{ ...

  8. 左神算法:判断二叉树是否为平衡二叉树(树形dp套路,Java版)

    本题来自左神<程序员代码面试指南>"判断二叉树是否为平衡二叉树"题目. 题目 平衡二叉树的性质为:要么是一棵空树,要么任何一个节点的左右子树高度差的绝对值不超过 1. ...

  9. 左神算法:调整搜索二叉树中两个错误的节点(Java版)

    本题来自左神<程序员代码面试指南>"调整搜索二叉树中两个错误的节点"题目. 题目 原问题: 一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再 ...

最新文章

  1. vue如何输出一个值_怎么在控制台打印出来data里想要的数据? Vue
  2. Mysql 源码安装
  3. 原创,真正解决iframe高度自适应的问题.兼容各浏览器
  4. 【译】Linux系统和性能监控(2)
  5. JavaWeb:AJAX
  6. 【spark】SparkSession的API
  7. 前端--3、JavaScript
  8. Android官方开发文档Training系列课程中文版:高效显示位图之管理位图内存
  9. 线上安全大会还能这么玩 ISC 2020首创“3D立体云展馆”
  10. 快速了解Spring Cloud
  11. jquery自适应宽度轮播图
  12. TensorFlow基础:Session(会话)
  13. ​新手到底如何入门PLC?
  14. JS计算时间差;.net计算时间差
  15. 笔记本计算机风扇连线,机箱风扇电源怎么接线?机箱风扇接口知识及接法图解教程...
  16. 量化投资_期货日内交易几个问题的考证
  17. 智能人物画像综合分析系统——Day15
  18. 二级mysql刷题_2019年9月二级MySQL试题-快来刷题!
  19. ArcGIS pro/ArcGIS 10.6及以上版本的最强工具箱——“WhiteboxTools”(468新功能:GIS分析,水文分析,图像分析,激光雷达分析,数学和统计分析,数据流网络分析和)!
  20. html怎么把背景换成相片,怎么给照片换背景 图片后期处理把阴沉天空背景换成云彩背景...

热门文章

  1. 计蒜客 - Distance on the tree(LCA+主席树)
  2. 蓝桥杯 - 完美的代价(贪心+模拟)
  3. js:点击button后返回值
  4. HDU4382(特殊的矩阵连乘)
  5. QT乱码总结0.Qt乱码产生因素
  6. python3 Async/Await入门指南
  7. PE文件结构详解(三)PE导出表
  8. 对现有的所能找到的DDOS代码(攻击模块)做出一次分析----自定义攻击篇
  9. C++设计模式之工厂方法模式
  10. Linux 多线程(二)线程安全:线程安全、互斥与互斥锁、死锁、同步与条件变量