左神算法:二叉树的按层打印与ZigZag打印(Java版)
本题来自左神《程序员代码面试指南》“二叉树的按层打印与ZigZag打印”题目。
题目
给定一棵二叉树的头节点 head,分别实现 按层 和 ZigZag 打印 二叉树的函数。
例如,二叉树如图 3-29 所示。
按层打印时,输出格式必须如下:
Level 1 : 1 Level 2 : 2 3 Level 3 : 4 5 6 Level 4 : 7 8ZigZag 打印时,输出格式必须如下:
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版)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: leetcode 242. 有效的字母异
- 下一篇: leetcode 257. 二叉树的所有