本题来自左神《程序员代码面试指南》“二叉树的按层打印与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. ⒈Altiris cms 7.0 安装前准备工作
  2. 常考数据结构与算法:反转链表
  3. 文巾解题 1. 两数之和
  4. Linux 修改主机名 和 ip 映射关系
  5. ng-controller and ng-model的工作原理
  6. ux和ui_首先要做的— UX / UI案例研究
  7. MySQL 企业监控器 2.3.10 正式版发布
  8. 纯粹,极致!他用两个词阐释了UCloud
  9. KVM Tracing, perf_events
  10. TensorFlow tf.keras.backend.ctc_batch_cost
  11. 2018双十一苏宁20+篇技术干货全整理
  12. Bootstrap滚动监控器
  13. SQL基础面试题(五)
  14. 32位/64位机上常用数据类型字节数(C语言)
  15. RESTORE DATAFILE TO A NEW LOCATION
  16. 解决body设置height:100%无效问题
  17. 如何将Word转换成PDF的几种使用方法
  18. 能自行调节温度的新型织物 马里兰大学新发明引发讨论
  19. 南佛罗里达大学计算机科学硕士,去南佛罗里达大学读硕士好吗
  20. Kettle【实践 04】Java环境实现KJB和KTR脚本文件执行v9版本9.0.0.0-423相关依赖说明(云资源分享:依赖包+kjb+ktr+测试源码)

热门文章

  1. POJ - 3678 Katu Puzzle(2-SAT)
  2. c# Task.Factory.StartNew 传参数_C#/C++混合编程一二事
  3. Markdown-VScode打造Markdown编辑器
  4. c oracle帮助类,C#DbHelperOra,Oracle数据库帮助类
  5. python 读取mongodb,python操作MongoDB
  6. HDU4082(相似三角形的个数)
  7. POJ1220(高精度进制转换)
  8. 《TCP/IP详解:卷1》之TCP/UDP总结
  9. 无ARP欺骗嗅探技术
  10. 第03讲. 原理探究,了解爬虫的基本原理