左神算法:二叉树的按层打印与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. 二叉树的所有