1. 程式人生 > >【劍指offer】面試題32:從上到下列印二叉樹

【劍指offer】面試題32:從上到下列印二叉樹

題目1:不分行從上到下列印二叉樹。從上往下打印出二叉樹的每個節點,同一層的節點從左到右的順序列印。

牛客網連結:https://www.nowcoder.com/questionTerminal/7fe2212963db4790b57431d9ed259701

 例如輸入下圖的二叉樹,則依次打印出:8,6,10,5,7,9,11。

這道題實質上考察的就是樹的遍歷演算法,只是這種遍歷不是我們熟悉的前序、中序或者後序遍歷。由於我們不太熟悉這種按層遍歷的方法,可能一下子也想不清楚遍歷的過程。

因為按層列印的順序決定應該先列印的根節點,所以我們從樹的根節點開始分析。為了接下來能夠列印8的結點的兩個子節點,我們應該在遍歷到該結點時把值為6和10的兩個結點儲存到一個容器中,現在容器內就有兩個結點了。按照從左到右列印的要求,我們先取出值為6的結點。打印出6後把它的值分別為5和7的兩個結點放入資料容器。此時資料容器中有3個結點,值分別為10,5,7。接下來我們從資料容器中取出值為10的結點。注意到值為10的結點比值為5、7的結點先放入容器,此時又比這兩個結點先取出,這就是我們通常說的**先入先出**,因此不難看出這個容器應該是一個**佇列**。由於值為5,7,9,11的結點都沒有子節點,因此只要依次列印即可。

通過分析具體例子,我們可以找到從上到下列印二叉樹的規律:每次列印一個結點的時候,如果該節點有子節點,把該節點的子節點放到一個佇列的末尾。接下來到佇列的頭部取出最早進入佇列的節點,重複前面列印操作,直到佇列中所有的節點都被打印出來。

程式碼實現:

package com.zju.offer.tree;
import java.util.ArrayList;

/**
 * 不分行從上到下列印二叉樹
 * 從上往下打印出二叉樹的每個節點,同一層的節點從左到右的順序列印
 */
public class PrintFromTopToBottom {

	public class TreeNode {
	    int val = 0;
	    TreeNode left = null;
	    TreeNode right = null;

	    public TreeNode(int val) {
	        this.val = val;
	    }
	}
	
	public ArrayList<Integer> printFromTopToBottom(TreeNode root){
		ArrayList<Integer> list = new ArrayList<>();
		// 模擬一個佇列,也可以用LinkedList
		ArrayList<TreeNode> queue = new ArrayList<>();
		
		if(root == null){
			return list;
		}
		
		queue.add(root);
		while(queue.size() != 0){
			// 將queue中第一個元素移除
			TreeNode temp = queue.remove(0);
			// 如果將要移除的節點有左子節點,則加入queue
			if(temp.left != null){
				queue.add(temp.left);
			}
			// 如果將要移除的節點有右子節點,則加入queue
			if(temp.right != null){
				queue.add(temp.right);
			}
			// 將temp的值加入list中
			list.add(temp.val);
		}
		return list;
	}
}

本題實現程式碼中用的是一個ArrayList集合去模擬的佇列,每次移除下標為0的元素即為佇列的頭元素,也符合佇列“先進先出的特點”。

本題擴充套件:如何廣度優先遍歷一幅有向圖?同樣也可以基於佇列實現。樹是圖的一種特殊退化形式,從上到下按層遍歷二叉樹,從本質上來說就是廣度優先遍歷二叉樹。 

舉一反三:不管是廣度優先遍歷一幅有向圖還是一棵樹,都要用到佇列。首先把起始節點(對樹而言是根節點)放入佇列。接下來每次從佇列的頭部取出一個節點,遍歷這個節點之後把它能到達的節點(對樹而言是子節點)都依次放入到佇列。重複這個遍歷過程,直到佇列中的節點全部被遍歷為止。

題目2:分行從上到下列印二叉樹。從上到下按層列印二叉樹,同一層的節點按從左到右的順序列印,每一層列印到一行。

如下案例所示:

列印結果為:

8

6     10

5      7       9      11

這道題和前面的題類似,也可以用一個佇列來儲存將要列印的節點。為了把二叉樹的每一行單獨列印到一行裡,我們需要兩個變數:一個變量表示在當前層中還沒有列印的節點數;另外一個變量表示下一層節點的數目。

public class PrintFromTopToBottom {

	public class TreeNode {
	    int val = 0;
	    TreeNode left = null;
	    TreeNode right = null;

	    public TreeNode(int val) {
	        this.val = val;
	    }
	}
	
	public ArrayList<Integer> printFromTopToBottom_Method1(TreeNode root){
		ArrayList<Integer> list = new ArrayList<>();
		// 模擬一個佇列,也可以用LinkedList
		ArrayList<TreeNode> queue = new ArrayList<>();
		
		if(root == null){
			return list;
		}
		
		queue.add(root);
		int nextLevel = 0;
		int toBePrinted = 1;
		
		while(queue.size() != 0){
			// 將queue中第一個元素移除
			TreeNode temp = queue.remove(0);
			// 如果將要移除的節點有左子節點,則加入queue
			if(temp.left != null){
				queue.add(temp.left);
				nextLevel++;
			}
			// 如果將要移除的節點有右子節點,則加入queue
			if(temp.right != null){
				queue.add(temp.right);
				nextLevel++;
			}
			// 將temp的值加入list中
			list.add(temp.val);
			System.out.print(temp.val + "  ");
			--toBePrinted;
			
			if(toBePrinted == 0){
				System.out.println();
				toBePrinted = nextLevel;
				nextLevel = 0;
			}
		}
		return list;
	}
	
	// 測試
	public static void main(String[] args) {
		PrintFromTopToBottom p = new PrintFromTopToBottom();
		TreeNode root = p.new TreeNode(8);
		TreeNode node1 = p.new TreeNode(6);
		TreeNode node2 = p.new TreeNode(10);
		TreeNode node3 = p.new TreeNode(5);
		TreeNode node4 = p.new TreeNode(7);
		TreeNode node5 = p.new TreeNode(9);
		TreeNode node6 = p.new TreeNode(11);
		
		root.left = node1;
		root.right = node2;
		node1.left = node3;
	        node1.right = node4;
	        node2.left = node5;
	        node2.right = node6;
	    
	        p.printFromTopToBottom_Method1(root);
	}
}

變數 toBePrinted 表示在當前層中還沒有列印的節點數,而變數 nextLevel 表示下一層的節點數。如果一個節點有子節點,則每把一個子節點加入佇列,同時把變數 nextLevel 加1。每列印一個節點,toBePrinted 減 1。當 toBePrinted 變成 0 時,表示當前層的所有節點已經列印完畢,可以繼續列印下一層。

題目3:之字形列印二叉樹。

請實現一個函式按照之字形列印二叉樹,即第一行按照從左到右的順序列印,第二層按照從右至左的順序列印,第三行按照從左到右的順序列印,其他行以此類推。

按之字形順序列印二叉樹需要兩個棧。我們在列印某一層的節點時,把下一層的子節點儲存到相應的棧裡。如果當前列印的是奇數層(第一層、第三層等),則先儲存左子節點再儲存右子節點到第一個棧裡(因為棧的後進先出特點);如果當前列印的是偶數層(第二層、第四層等),則先儲存右子節點再儲存左子節點到第二個棧裡。接下來逐個把位於棧頂的節點彈出棧並列印即可。

使用巢狀集合型別來儲存 stack1 和 stack2 中彈出的資料,每一個內層小集合中儲存一層的資料。

package com.zju.offer.tree;
import java.util.ArrayList;
import java.util.Stack;

public class PrintFromTopToBottom {

	public class TreeNode {
	    int val = 0;
	    TreeNode left = null;
	    TreeNode right = null;

	    public TreeNode(int val) {
	        this.val = val;
	    }
	}
	
	/**
	 * 之字形列印:奇數從左往右列印、偶數從右往左列印
	 */
	public ArrayList<ArrayList<Integer>> Print(TreeNode root){
		// 層數索引
		int layer = 1;   
		// stack1 儲存奇數層節點
		Stack<TreeNode> stack1 = new Stack<TreeNode>();
		stack1.push(root);  // 因為根節點是奇數層,所以將根節點先儲存到stack1中
		// stack2 儲存偶數層節點
		Stack<TreeNode> stack2 = new Stack<TreeNode>();
		
		// 採用雙層集合,每一個內層集合儲存一層資料
		ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();

		while(!stack1.empty() || !stack2.empty()){
			if(layer % 2 != 0){
				// 列印奇數層
				ArrayList<Integer> temp = new ArrayList<Integer>();
				while(!stack1.empty()){
					TreeNode node = stack1.pop();
					if(node != null){
						temp.add(node.val);
						System.out.print(node.val + "  ");
						// 由於當前層是奇數層,所以它的下一層是要從右往左列印
						// 因此按照棧的"後進先出"的特性,先儲存左子節點,再儲存右子節點
						stack2.push(node.left);
						stack2.push(node.right);
					}
				}
				if(!temp.isEmpty()){
					list.add(temp);
					layer++;
					System.out.println();
				}
			}else{
				// 列印偶數層
				ArrayList<Integer> temp = new ArrayList<Integer>();
				while(!stack2.isEmpty()){
					TreeNode node = stack2.pop();
					if(node != null){
						temp.add(node.val);
						System.out.print(node.val + "  ");
						// 由於當前層是偶數層,所以它的下一層是要從左往右列印
						// 因此按照棧的"後進先出"的特性,先儲存右子節點,再儲存左子節點
						stack1.push(node.right);
						stack1.push(node.left);
					}
				}
				if(!temp.isEmpty()){
					list.add(temp);
					layer++;
					System.out.println();
				}
			}
		}
		return list;
	}
	
	// 測試
	public static void main(String[] args) {
		PrintFromTopToBottom p = new PrintFromTopToBottom();
		TreeNode root = p.new TreeNode(8);
		TreeNode node1 = p.new TreeNode(6);
		TreeNode node2 = p.new TreeNode(10);
		TreeNode node3 = p.new TreeNode(5);
		TreeNode node4 = p.new TreeNode(7);
		TreeNode node5 = p.new TreeNode(9);
		TreeNode node6 = p.new TreeNode(11);
		
		root.left = node1;
		root.right = node2;
		node1.left = node3;
	        node1.right = node4;
	        node2.left = node5;
	        node2.right = node6;
	    
	        p.Print(root);
	}
}