樹在計算機中佔着舉足輕重的地位,目前計算機中的文件系統,程序開發中的XML與HTML等都是樹這一數據結構的運用。甚至包括英雄聯盟的匹配系統也採用了樹,這章內容重點討論樹結構,並使用Java對一些常見的面試問題做個總結。由於關於樹的問題衆多,所以我準備分兩部分進行整理。
什麼是樹?樹的深度,高度,根節點,父節點,子節點等概念一張圖即可說明清楚
關於樹的編程問題有很多變形,但經過研究發現大部分解決思路的核心是遞歸思想
1. 樹的創建初始化
2. 求二叉樹的最大深度
3. 求二叉樹的最小深度
4. 求二叉樹中節點的個數
5. 求二叉樹中葉子節點的個數
6. 求二叉樹中第K層節點個數
7. 判斷二叉樹是否爲平衡二叉樹
8. 判斷二叉樹是否爲完全二叉樹
9. 判斷一個二叉樹是否爲鏡像
10. 求一個二叉樹的鏡像
11. 求兩個二叉樹最低公共祖先節點
12. 前序中序後序遍歷二叉樹
此處使用Java語言完成對上列各種問題的編程實現。
1.樹的創建初始化
//1. 樹的初始化
class TreeNode{
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value){
this.value = value;
}
}
2. 求二叉樹的最大深度
public int searchMaxDepth(TreeNode node){
if(node == null){
return 0;
}
int leftDepth = searchMaxDepth(node.left);
int rightDepth = searchMaxDepth(node.right);
return (leftDepth >= rightDepth ? leftDepth : rightDepth) + 1;
}
3. 求二叉樹的最小深度
public int searchMinDepth(TreeNode node){
if(node == null){
return 0;
}
if(node != null && node.left == null && node.right == null){
return 1;
}
//若左節點爲空那麼就返回右邊節點的深度
if(node.left == null){
return searchMinDepth(node.right) + 1;
}
if(node.right == null){
return searchMinDepth(node.left) + 1;
}
//若左右兩邊都存在節點則進入下一層進行比較
int leftDepth = searchMinDepth(node.left);
int rightDepth = searchMinDepth(node.right);
return (leftDepth <= rightDepth ? leftDepth : rightDepth) + 1;
}
4.求二叉樹中節點的個數
//思路:使用一種遍歷方式,創建一個計數器去統計
public int nodeNum(TreeNode node){
int count = 0;
if(node == null){
return 0;
}
//此處採用BFS的方式去遍歷,並且把節點存儲到一個隊列中
Queue<TreeNode> list = new LinkedList<TreeNode>();
list.add(node);
while(!list.isEmpty()){
if(node.left != null){
list.add(node.left);
}
if(node.right != null){
list.add(node.right);
}
list.poll();
count++;
node = list.peek();
}
return count;
}
5. 求二叉樹中葉子節點的個數
//思路:採用BFS遍歷樹,對每個節點的狀態進行判斷
public int leafNum(TreeNode node){
int count = 0;
if(node == null){
return 0;
}
if(node != null && node.left == null && node.right == null){
return 1;
}
//遍歷的同時對每個節點的左右進行判斷
Queue<TreeNode> list = new LinkedList<TreeNode>();
list.add(node);
while(!list.isEmpty()){
if(node.left != null){
list.add(node.left);
}
if(node.right != null){
list.add(node.right);
}
if(list.peek().left == null && list.peek().right == null){
count++;
}
list.poll();
node = list.peek();
}
return count;
}
6.求二叉樹第K層節點的個數
//思路:遞歸 K其實可以理解爲遞歸K次,node才得以從root更新到第K層的節點
public int KlevelNodeNum(TreeNode node, int k){
//若該節點不存在或者K不符合條件則返回0
if(node == null || k <= 0){
return 0;
}
if(node != null && k == 1){
return 1;
}
//要想得到第K層的節點數目就必須先知道第K-1層的節點是什麼狀態(即是否含有左右節點)
return KlevelNodeNum(node.left, k - 1) + KlevelNodeNum(node.right, k - 1);
}
7.判斷二叉樹是否爲平衡二叉樹(AVL樹)
//首先什麼條件下的二叉樹纔是平衡二叉樹?二叉樹中每個節點的左右子樹深度相差不超過1
//可以利用遞歸求每個節點的深度,將求得的深度進行比較即可
public boolean isBalanced(TreeNode node){
if(node == null){
return true;
}
int leftDepth = searchDepth(node.left);
int rightDepth = searchDepth(node.right);
if(Math.abs(leftDepth - rightDepth) > 1){
return false;
}
boolean leftBalanced = isBalanced(node.left);
boolean rightBalanced = isBalanced(node.right);
return leftBalanced && rightBalanced;
}
public int searchDepth(TreeNode node){
if(node == null){
return 0;
}
if(node != null && node.left == null && node.right == null){
return 1;
}
int leftDepth = searchDepth(node.left);
int rightDepth = searchDepth(node.right);
return (leftDepth >= rightDepth ? leftDepth : rightDepth) + 1;
}
8.判斷二叉樹是否爲完全二叉樹(CBT Complete binary tree)
//什麼是完全二叉樹?若設二叉樹的深度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大數,
//第 h 層所有的結點都連續集中在最左邊,這就是完全二叉樹。
public boolean isCBT(TreeNode node){
//通過BFS逐層遍歷樹,使用隊列進行存儲,對隊列中的每個節點進行判斷
//空樹也可以屬於完全二叉樹
if(node == null){
return true;
}
Queue<TreeNode> list = new LinkedList<TreeNode>();
list.add(node);
while(!list.isEmpty()){
if(node.left != null){
list.add(node.left);
}
if(node.right != null){
list.add(node.right);
}
//若左節點爲空,右節點不爲空,直接判定不是完全二叉樹
if(node.left == null && node.right != null){
return false;
}
//如果有左節點但沒有右節點,那麼該節點的所有子節點必須爲葉子節點,即該節點的子節點左右都爲空
if(node.left != null && node.right == null){
if(node.left.left != null || node.left.right != null){
return false;
}
}
list.poll();
node = list.peek();
}
return true;
}
9. 判斷一棵二叉樹是否爲鏡像
//思路:判斷方法其實是判斷二叉樹的子樹是否爲鏡像,可以使用遞歸的方式進行求解
/*
算法思想是:首先判斷這棵樹是否爲空樹,如果空樹則直接返回true
如果不爲空,則在進行分類:
case1:節點的左右子樹爲空,則直接返回true
case2:節點的左右子樹有一個爲空,則直接返回false
case3:節點的左右子樹均不爲空,則判斷節點的左右子節點的值是否相等
並且判斷左節點的子左節點和右節點的右子節點是否對稱
還有左節點的右子節點和右節點的左子節點是否對稱
*/
public boolean isSymmetric(TreeNode node){
if(node == null){
return true;
}
if(node.left == null || node.right == null){
return false;
}
//判斷兩棵子樹是否對稱
return symmetric(node.left, node.right);
}
//比較兩棵樹是否是鏡像的
public boolean symmetric(TreeNode left, TreeNode right){
//case 1
if(left == null && right == null){
return true;
}
//case 2
if(left == null || right == null){
return false;
}
//case 3
return left.value == right.value && symmetric(left.left, right.right) && symmetric(left.right, right.left);
}
10.給出一個二叉樹,求解其鏡像。
//思路:遞歸交換子樹的節點
public void mirror(TreeNode node){
if(node == null){
return;
}
//進行交換節點操作
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
mirror(node.left);
mirror(node.right);
}
11. 求二叉搜索樹最低公共祖先節點 lowest common ancestor(LCA)
//如果是二叉搜索樹,遞歸判斷
//case 1 如果兩個節點分別在根節點的左右,那麼該根節點就是所求節點
//case 2 兩個節點在根節點的同側,進行遞歸搜索同側的樹
public TreeNode findLCA(TreeNode node, TreeNode p, TreeNode q){
if(node == null){
return null;
}
if(node.value == p.value || node.value == q.value){
return node;
}
//當節點分別在node左右兩側,那麼根節點就是低公共祖先節點
if(p.value < node.value && q.value > node.value){
return node;
}
TreeNode treenode = null;
//若兩節點均小於node,說明兩個節點都是位於左子樹,同理兩節點都大於node,那麼就說明兩節點都位於右子樹
if(p.value < node.value && q.value < node.value){
treenode = findLCA(node.left, p, q);
}
if(p.value >= node.value){
treenode = findLCA(node.right, p, q);
}
return treenode;
}
12. 前序中序後序遍歷二叉樹 pre-order in-order post-order
//pre-order: root left right
public void preorder(TreeNode node){
if(node == null){
return;
}
if(node != null){
System.out.println(node.value);
preorder(node.left);
preorder(node.right);
}
}
//inorder: left root right
public void inorder(TreeNode node){
if(node == null){
return;
}
if(node != null){
inorder(node.left);
System.out.println(node.value);
inorder(node.right);
}
}
//post-order: left right root
public void postorder(TreeNode node){
if(node == null){
return;
}
if(node != null){
postorder(node.left);
postorder(node.right);
System.out.println(node.value);
}
}