本文參考自《劍指offer》一書,代碼采用Java語言。
題目
(一)從上往下打印出二叉樹的每個結點,同一層的結點按照從左到右的順序打印。
(二)從上到下按層打印二叉樹,同一層的結點按從左到右的順序打印,每一層打印到一行。
(三)請實現一個函數按照之字形順序打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右到左的順序打印,第三行再按照從左到右的順序打印,其他行以此類推。
思路
(一)不分行從上往下打印二叉樹:該題即為對二叉樹的層序遍歷,結點滿足先進先出的原則,采用隊列。每從隊列中取出頭部結點並打印,若其有子結點,把子結點放入隊列尾部,直到所有結點打印完畢。
/*
* 不分行從上往下打印二叉樹
*/
// 題目:從上往下打印出二叉樹的每個結點,同一層的結點按照從左到右的順序打印。
public void printTree1(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
TreeNode node = null;
while (queue.size()!=0) {
node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
System.out.println();
}
(二)分行從上到下打印二叉樹:同樣使用隊列,但比第一題增加兩個變量:當前層結點數目pCount,下一層結點數目nextCount。根據當前層結點數目來打印當前層結點,同時計算下一層結點數目,之后令pCount等於nextCount,重復循環,直到打印完畢。
/*
* 分行從上到下打印二叉樹
*/
// 題目:從上到下按層打印二叉樹,同一層的結點按從左到右的順序打印,每一層
// 打印到一行。
public void printTree2(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
TreeNode node = null;
int pCount = 0; //當前層結點數目
int nextCount = 1; //下一層結點數目
while (!queue.isEmpty()) {
pCount = nextCount;
nextCount = 0;
//打印當前層數字,並計算下一層結點數目
for (int i = 1; i <= pCount; i++) {
node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null) {
queue.offer(node.left);
nextCount++;
}
if (node.right != null) {
queue.offer(node.right);
nextCount++;
}
}
System.out.println();
}
}
(三)之字形打印二叉樹:
(1)自己開始想的方法:在(二)的基礎上,多定義一個表示當前層數的變量level。每層結點不直接打印,放入一個數組中,根據此時的層數level的奇偶來決定正向還是反向打印數組。
/*
* 之字形打印二叉樹
*/
// 題目:請實現一個函數按照之字形順序打印二叉樹,即第一行按照從左到右的順
// 序打印,第二層按照從右到左的順序打印,第三行再按照從左到右的順序打印,
// 其他行以此類推。
/**
* 自己開始想的方法,采用數組存儲每層的數字,根據當前層數確定正反向打印數組
*/
public void printTree3_1(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
TreeNode node = null;
int pCount = 0; //當前層結點數目
int nextCount = 1; //下一層結點數目
int level=1; //層數
int[] pNums=null; //用於存儲當前層的數字
while (!queue.isEmpty()) {
pCount = nextCount;
nextCount = 0;
pNums=new int[pCount];
//存儲當前層數字,並計算下一層結點數目
for (int i = 0; i < pCount; i++) {
node = queue.poll();
pNums[i]=node.val;
if (node.left != null) {
queue.offer(node.left);
nextCount++;
}
if (node.right != null) {
queue.offer(node.right);
nextCount++;
}
}
//根據當前層數確定正向或者反向打印數組
if((level&1)!=0 ) {
for(int i=0;i<pCount;i++) {
System.out.print(pNums[i]+" ");
}
}else {
for(int i=pCount-1;i>=0;i--) {
System.out.print(pNums[i]+" ");
}
}
level++;
System.out.println();
}
}
(2)書中提供的方法:采用兩個棧,對於不同層的結點,一個棧用於正向存儲,一個棧用於逆向存儲,打印出來就正好是相反方向。
/**
* 采用兩個棧進行操作的方法
*/
public void printTree3_2(TreeNode root) {
if (root == null)
return;
Stack<TreeNode> stack1 = new Stack<TreeNode>();
Stack<TreeNode> stack2 = new Stack<TreeNode>();
TreeNode node = null;
stack1.push(root);
while(!stack1.empty() || !stack2.empty()) {
while(!stack1.empty()) {
node=stack1.pop();
System.out.print(node.val + " ");
if (node.left != null)
stack2.push(node.left);
if (node.right != null)
stack2.push(node.right);
}
System.out.println();
while(!stack2.empty()) {
node=stack2.pop();
System.out.print(node.val + " ");
if (node.right != null)
stack1.push(node.right);
if (node.left != null)
stack1.push(node.left);
}
System.out.println();
}
}
測試算例
1.功能測試(完全二叉樹;左斜樹;右斜樹)
2.特殊測試(null;一個結點)
完整Java代碼
含測試代碼:
import java.util.LinkedList;
import java.util.Stack;
/**
*
* @Description 面試題32:從上往下打印二叉樹
*
* @author yongh
*/
public class PrintTreeFromTopToBottom {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
/*
* 不分行從上往下打印二叉樹
*/
// 題目:從上往下打印出二叉樹的每個結點,同一層的結點按照從左到右的順序打印。
public void printTree1(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
TreeNode node = null;
while (queue.size()!=0) {
node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
System.out.println();
}
/*
* 分行從上到下打印二叉樹
*/
// 題目:從上到下按層打印二叉樹,同一層的結點按從左到右的順序打印,每一層
// 打印到一行。
public void printTree2(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
TreeNode node = null;
int pCount = 0; //當前層結點數目
int nextCount = 1; //下一層結點數目
while (!queue.isEmpty()) {
pCount = nextCount;
nextCount = 0;
//打印當前層數字,並計算下一層結點數目
for (int i = 1; i <= pCount; i++) {
node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null) {
queue.offer(node.left);
nextCount++;
}
if (node.right != null) {
queue.offer(node.right);
nextCount++;
}
}
System.out.println();
}
}
/*
* 之字形打印二叉樹
*/
// 題目:請實現一個函數按照之字形順序打印二叉樹,即第一行按照從左到右的順
// 序打印,第二層按照從右到左的順序打印,第三行再按照從左到右的順序打印,
// 其他行以此類推。
/**
* 自己開始想的方法,采用數組存儲每層的數字,根據當前層數確定正反向打印數組
*/
public void printTree3_1(TreeNode root) {
if (root == null)
return;
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
TreeNode node = null;
int pCount = 0; //當前層結點數目
int nextCount = 1; //下一層結點數目
int level=1; //層數
int[] pNums=null; //用於存儲當前層的數字
while (!queue.isEmpty()) {
pCount = nextCount;
nextCount = 0;
pNums=new int[pCount];
//存儲當前層數字,並計算下一層結點數目
for (int i = 0; i < pCount; i++) {
node = queue.poll();
pNums[i]=node.val;
if (node.left != null) {
queue.offer(node.left);
nextCount++;
}
if (node.right != null) {
queue.offer(node.right);
nextCount++;
}
}
//根據當前層數確定正向或者反向打印數組
if((level&1)!=0 ) {
for(int i=0;i<pCount;i++) {
System.out.print(pNums[i]+" ");
}
}else {
for(int i=pCount-1;i>=0;i--) {
System.out.print(pNums[i]+" ");
}
}
level++;
System.out.println();
}
}
/**
* 采用兩個棧進行操作的方法
*/
public void printTree3_2(TreeNode root) {
if (root == null)
return;
Stack<TreeNode> stack1 = new Stack<TreeNode>();
Stack<TreeNode> stack2 = new Stack<TreeNode>();
TreeNode node = null;
stack1.push(root);
while(!stack1.empty() || !stack2.empty()) {
while(!stack1.empty()) {
node=stack1.pop();
System.out.print(node.val + " ");
if (node.left != null)
stack2.push(node.left);
if (node.right != null)
stack2.push(node.right);
}
System.out.println();
while(!stack2.empty()) {
node=stack2.pop();
System.out.print(node.val + " ");
if (node.right != null)
stack1.push(node.right);
if (node.left != null)
stack1.push(node.left);
}
System.out.println();
}
}
//============測試代碼==============
private void test(int testNum,TreeNode root) {
System.out.println("=========test"+testNum+"===========");
System.out.println("method1:");
printTree1(root);
System.out.println("method2:");
printTree2(root);
System.out.println("method3_1:");
printTree3_1(root);
System.out.println("method3_2:");
printTree3_2(root);
}
//null
private void test1() {
TreeNode node=null;
test(1, node);
}
//單個結點
private void test2() {
TreeNode node=new TreeNode(1);
test(2, node);
}
//左斜
private void test3() {
TreeNode node1=new TreeNode(1);
TreeNode node2=new TreeNode(2);
TreeNode node3=new TreeNode(3);
node1.left=node2;
node2.left=node3;
test(3, node1);
}
//右斜
private void test4() {
TreeNode node1=new TreeNode(1);
TreeNode node2=new TreeNode(2);
TreeNode node3=new TreeNode(3);
node1.right=node2;
node2.right=node3;
test(4, node1);
}
//完全二叉樹
private void test5() {
TreeNode[] nodes = new TreeNode[15];
for(int i=0;i<15;i++) {
nodes[i]= new TreeNode(i+1);
}
for(int i=0;i<7;i++) {
nodes[i].left=nodes[2*i+1];
nodes[i].right=nodes[2*i+2];
}
test(5, nodes[0]);
}
public static void main(String[] args) {
PrintTreeFromTopToBottom demo= new PrintTreeFromTopToBottom();
demo.test1();
demo.test2();
demo.test3();
demo.test4();
demo.test5();
}
}
=========test1=========== method1: method2: method3_1: method3_2: =========test2=========== method1: 1 method2: 1 method3_1: 1 method3_2: 1 =========test3=========== method1: 1 2 3 method2: 1 2 3 method3_1: 1 2 3 method3_2: 1 2 3 =========test4=========== method1: 1 2 3 method2: 1 2 3 method3_1: 1 2 3 method3_2: 1 2 3 =========test5=========== method1: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 method2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 method3_1: 1 3 2 4 5 6 7 15 14 13 12 11 10 9 8 method3_2: 1 3 2 4 5 6 7 15 14 13 12 11 10 9 8
收獲
1.層序遍歷時,一般都要用到隊列,可以用LinkedList類(方法:poll() 和 offer(Obj) )。
2.在分行打印時,定義的兩個int有明顯實際意義(當前層結點數目,下一層結點數目)。自己編程時,一開始只知道要設置兩個變量,但沒有去想這兩個變量的實際意義。當明白變量意義時,自己的思路會更清晰,而且代碼可讀性也更好。
