一、題目一:二叉樹的深度
1.1 題目說明
題目一:輸入一棵二叉樹的根結點,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度為樹的深度。例如下圖中的二叉樹的深度為4,因為它從根結點到葉結點最長的路徑包含4個結點(從根結點1開始,經過結點2和結點5,最終到達葉結點7)。
二叉樹的結點定義如下,這里使用C#語言描述:
public class BinaryTreeNode { public int Data { get; set; } public BinaryTreeNode LeftChild { get; set; } public BinaryTreeNode RightChild { get; set; } public BinaryTreeNode(int data) { this.Data = data; } public BinaryTreeNode(int data, BinaryTreeNode left, BinaryTreeNode right) { this.Data = data; this.LeftChild = left; this.RightChild = right; } }
1.2 解題思路
①如果一棵樹只有一個結點,它的深度為1。
②如果根結點只有左子樹而沒有右子樹,那么樹的深度應該是其左子樹的深度加1;同樣如果根結點只有右子樹而沒有左子樹,那么樹的深度應該是其右子樹的深度加1。
③如果既有右子樹又有左子樹,那該樹的深度就是其左、右子樹深度的較大值再加1。
比如在上圖的二叉樹中,根結點為1的樹有左右兩個子樹,其左右子樹的根結點分別為結點2和3。根結點為2的左子樹的深度為3,而根結點為3的右子樹的深度為2,因此根結點為1的樹的深度就是4。
public static int GetTreeDepth(BinaryTreeNode root) { if (root == null) { return 0; } int left = GetTreeDepth(root.LeftChild); int right = GetTreeDepth(root.RightChild); return left >= right ? left + 1 : right + 1; }
1.3 單元測試
(1)測試用例
[TestClass] public class TreeDepthTest { private void SetSubTreeNode(BinaryTreeNode root, BinaryTreeNode lChild, BinaryTreeNode rChild) { if (root == null) { return; } root.LeftChild = lChild; root.RightChild = rChild; } private void ClearUpTreeNode(BinaryTreeNode root) { if (root != null) { BinaryTreeNode left = root.LeftChild; BinaryTreeNode right = root.RightChild; root = null; ClearUpTreeNode(left); ClearUpTreeNode(right); } } // 1 // / \ // 2 3 // /\ \ // 4 5 6 // / // 7 [TestMethod] public void GetDepthTest1() { BinaryTreeNode node1 = new BinaryTreeNode(1); BinaryTreeNode node2 = new BinaryTreeNode(2); BinaryTreeNode node3 = new BinaryTreeNode(3); BinaryTreeNode node4 = new BinaryTreeNode(4); BinaryTreeNode node5 = new BinaryTreeNode(5); BinaryTreeNode node6 = new BinaryTreeNode(6); BinaryTreeNode node7 = new BinaryTreeNode(7); SetSubTreeNode(node1, node2, node3); SetSubTreeNode(node2, node4, node5); SetSubTreeNode(node3, null, node6); SetSubTreeNode(node5, node7, null); int actual = TreeDepthHelper.GetTreeDepth(node1); Assert.AreEqual(actual, 4); ClearUpTreeNode(node1); } // 1 // / // 2 // / // 3 // / // 4 // / // 5 [TestMethod] public void GetDepthTest2() { BinaryTreeNode node1 = new BinaryTreeNode(1); BinaryTreeNode node2 = new BinaryTreeNode(2); BinaryTreeNode node3 = new BinaryTreeNode(3); BinaryTreeNode node4 = new BinaryTreeNode(4); BinaryTreeNode node5 = new BinaryTreeNode(5); SetSubTreeNode(node1, node2, null); SetSubTreeNode(node2, node3, null); SetSubTreeNode(node3, node4, null); SetSubTreeNode(node4, node5, null); int actual = TreeDepthHelper.GetTreeDepth(node1); Assert.AreEqual(actual, 5); ClearUpTreeNode(node1); } // 1 // \ // 2 // \ // 3 // \ // 4 // \ // 5 [TestMethod] public void GetDepthTest3() { BinaryTreeNode node1 = new BinaryTreeNode(1); BinaryTreeNode node2 = new BinaryTreeNode(2); BinaryTreeNode node3 = new BinaryTreeNode(3); BinaryTreeNode node4 = new BinaryTreeNode(4); BinaryTreeNode node5 = new BinaryTreeNode(5); SetSubTreeNode(node1, null, node2); SetSubTreeNode(node2, null, node3); SetSubTreeNode(node3, null, node4); SetSubTreeNode(node4, null, node5); int actual = TreeDepthHelper.GetTreeDepth(node1); Assert.AreEqual(actual, 5); ClearUpTreeNode(node1); } // 樹中只有1個結點 [TestMethod] public void GetDepthTest4() { BinaryTreeNode node1 = new BinaryTreeNode(1); int actual = TreeDepthHelper.GetTreeDepth(node1); Assert.AreEqual(actual, 1); ClearUpTreeNode(node1); } // 樹中沒有結點 [TestMethod] public void GetDepthTest5() { int actual = TreeDepthHelper.GetTreeDepth(null); Assert.AreEqual(actual, 0); } }
(2)測試結果
①測試通過情況
②代碼覆蓋率
二、題目二:判斷二叉樹是否是平衡二叉樹
2.1 題目說明
題目二:輸入一棵二叉樹的根結點,判斷該樹是不是平衡二叉樹。如果某二叉樹中任意結點的左右子樹的深度相差不超過1,那么它就是一棵平衡二叉樹。例如,下圖中的二叉樹就是一棵平衡二叉樹。
2.2 解題思路
(1)需要重復遍歷節點多次的解法
有了求二叉樹的深度的經驗之后再解決這個問題,我們很容易就能想到一個思路:在遍歷樹的每個結點的時候,調用函數TreeDepth得到它的左右子樹的深度。如果每個結點的左右子樹的深度相差都不超過1,按照定義它就是一棵平衡的二叉樹。
public static bool IsBalancedBinaryTree(BinaryTreeNode root) { if (root == null) { return true; } int left = GetTreeDepth(root.LeftChild); int right = GetTreeDepth(root.RightChild); int diff = left - right; if (diff > 1 || diff < -1) { return false; } return IsBalancedBinaryTree(root.LeftChild) && IsBalancedBinaryTree(root.RightChild); }
上面的代碼固然簡潔,但我們也要注意到由於一個結點會被重復遍歷多次,這種思路的時間效率不高。例如在IsBalancedBinaryTree方法中輸入上圖中的二叉樹,我們將首先判斷根結點(結點1)是不是平衡的。此時我們往函數TreeDepth輸入左子樹的根結點(結點2)時,需要遍歷結點4、5、7。接下來判斷以結點2為根結點的子樹是不是平衡樹的時候,仍然會遍歷結點4、5、7。毫無疑問,重復遍歷同一個結點會影響性能。
(2)每個節點只需遍歷一次的解法
換個角度來思考,如果我們用后序遍歷的方式遍歷二叉樹的每一個結點,在遍歷到一個結點之前我們就已經遍歷了它的左右子樹。只要在遍歷每個結點的時候記錄它的深度(某一結點的深度等於它到葉節點的路徑的長度),我們就可以一邊遍歷一邊判斷每個結點是不是平衡的。
public static bool IsBalancedBinaryTree(BinaryTreeNode root) { int depth = 0; return IsBalancedBinaryTreeCore(root, ref depth); } private static bool IsBalancedBinaryTreeCore(BinaryTreeNode root, ref int depth) { if (root == null) { depth = 0; return true; } int left = 0; int right = 0; if (IsBalancedBinaryTreeCore(root.LeftChild, ref left) && IsBalancedBinaryTreeCore(root.RightChild, ref right)) { int diff = left - right; if (diff >= -1 && diff <= 1) { depth = left >= right ? left + 1 : right + 1; return true; } } return false; }
在上面的代碼中,我們用后序遍歷的方式遍歷整棵二叉樹。在遍歷某結點的左右子結點之后,我們可以根據它的左右子結點的深度判斷它是不是平衡的,並得到當前結點的深度。當最后遍歷到樹的根結點的時候,也就判斷了整棵二叉樹是不是平衡二叉樹。
2.3 單元測試
此方法的單元測試和第一種方法的一致,這里就不再貼出。需要注意的就是在針對二叉樹的測試用例中,需要考慮兩種:功能測試(平衡的二叉樹,不是平衡的二叉樹,二叉樹中所有結點都沒有左/右子樹)。特殊輸入測試(二叉樹中只有一個結點,二叉樹的頭結點為NULL指針)。