劍指Offer面試題:33.二叉樹的深度


一、題目一:二叉樹的深度

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指針)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM