算法题---Tree系列(二叉树 )

1  题目: 二元树的深度

     输入一棵二元树的根结点,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。


例如:输入二元树:

                                            10
                                          /     \
                                        6        14
                                      /         /   \
                                    4         12     16

输出该树的深度3

二元树的结点定义如下:

struct SBinaryTreeNode// a node of the binary tree
{
      int               m_nValue; // value of node
      SBinaryTreeNode  *m_pLeft; // left child of node
      SBinaryTreeNode  *m_pRight;// right child of node
};

分析:这道题本质上还是考查二元树的遍历。

题目给出了一种树的深度的定义。当然,我们可以按照这种定义去得到树的所有路径,也就能得到最长路径以及它的长度。只是这种思路用来写程序有点麻烦。

我们还可以从另外一个角度来理解树的深度。如果一棵树只有一个结点,它的深度为1。如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。如果既有右子树又有左子树呢?那该树的深度就是其左、右子树深度的较大值再加1

上面的这个思路用递归的方法很容易实现,只需要对遍历的代码稍作修改即可。参考代码如下:

///
// Get depth of a binary tree
// Input: pTreeNode - the head of a binary tree
// Output: the depth of a binary tree
///
int TreeDepth(SBinaryTreeNode *pTreeNode)
{
      // the depth of a empty tree is 0
      if(!pTreeNode)
            return 0;

      // the depth of left sub-tree
      int nLeft = TreeDepth(pTreeNode->m_pLeft);
      // the depth of right sub-tree
      int nRight = TreeDepth(pTreeNode->m_pRight);

      // depth is the binary tree
      return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);

}

2  判断二叉树是不是平衡[数据结构]  

   题目:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。例如下图中的二叉树就是一棵平衡二叉树:

程序员面试题精选100题(60)-判断二叉树是不是平衡的 - 何海涛 - 微软、Google等面试题

有了求二叉树的深度的经验之后再解决这个问题,我们很容易就能想到一个思路:在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差都不超过1,按照定义它就是一棵平衡的二叉树。这种思路对应的代码如下:

bool IsBalanced(BinaryTreeNode* pRoot)

{

    if(pRoot == NULL)

        return true;

 

    int left = TreeDepth(pRoot->m_pLeft);

    int right = TreeDepth(pRoot->m_pRight);

    int diff = left - right;

    if(diff > 1 || diff < -1)

        return false;

 

    return IsBalanced(pRoot->m_pLeft) && IsBalanced(pRoot->m_pRight);

}

上面的代码固然简洁,但我们也要注意到由于一个节点会被重复遍历多次,这种思路的时间效率不高。例如在函数IsBalance中输入上图中的二叉树,首先判断根结点(值为1的结点)的左右子树是不是平衡结点。此时我们将往函数TreeDepth输入左子树根结点(值为2的结点),需要遍历结点457。接下来判断以值为2的结点为根结点的子树是不是平衡树的时候,仍然会遍历结点457。毫无疑问,重复遍历同一个结点会影响性能。接下来我们寻找不需要重复遍历的算法。

如果我们用后序遍历的方式遍历二叉树的每一个结点,在遍历到一个结点之前我们已经遍历了它的左右子树。只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶节点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡的。下面是这种思路的参考代码:

bool IsBalanced(BinaryTreeNode* pRoot, int* pDepth)

{

    if(pRoot == NULL)

    {

        *pDepth = 0;

        return true;

    }

 

    int left, right;

    if(IsBalanced(pRoot->m_pLeft, &left)

        && IsBalanced(pRoot->m_pRight, &right))

    {

        int diff = left - right;

        if(diff <= 1 && diff >= -1)

        {

            *pDepth = 1 + (left > right ? left : right);

            return true;

        }

    }

 

    return false;

}

我们只需要给上面的函数传入二叉树的根结点以及一个表示结点深度的整形变量就可以了:

bool IsBalanced(BinaryTreeNode* pRoot)

{

    int depth = 0;

    return IsBalanced(pRoot, &depth);

}

在上面的代码中,我们用后序遍历的方式遍历整棵二叉树。在遍历某结点的左右子结点之后,我们可以根据它的左右子结点的深度判断它是不是平衡的,并得到当前结点的深度。当最后遍历到树的根结点的时候,也就判断了整棵二叉树是不是平衡二叉树了。


3  树的子结构

    题目:二叉树的结点定义如下:

struct TreeNode

{

        int m_nValue;

        TreeNode* m_pLeft;

        TreeNode* m_pRight;

};

输入两棵二叉树AB,判断树B是不是A的子结构。

例如,下图中的两棵树AB,由于A中有一部分子树的结构和B是一样的,因此B就是A的子结构。

                 1                                                   8
               /    \                                               /    \
              8    7                                             9    2
            /    \
           9    2
                /  \
               4  7

分析:这是2010年微软校园招聘时的一道题目。二叉树一直是微软面试题中经常出现的数据结构。对微软有兴趣的读者一定要重点关注二叉树。

               回到这个题目的本身。要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:第一步在树A中找到和B的根结点的值一样的结点N,第二步再判断树A中以N为根结点的子树是不是包括和树B一样的结构。

               第一步在树A中查找与根结点的值一样的结点。这实际上就是树的遍历。对二叉树这种数据结构熟悉的读者自然知道我们可以用递归的方法去遍历,也可以用循环的方法去遍历。由于递归的代码实现比较简洁,面试时如果没有特别要求,我们通常都会采用递归的方式。下面是参考代码:

bool HasSubtree(TreeNode* pTreeHead1, TreeNode* pTreeHead2)

{

        if((pTreeHead1 == NULL && pTreeHead2 != NULL) ||

                (pTreeHead1 != NULL && pTreeHead2 == NULL))

                return false;

 

        if(pTreeHead1 == NULL && pTreeHead2 == NULL)

                return true;

 

        return HasSubtreeCore(pTreeHead1, pTreeHead2);

}

bool HasSubtreeCore(TreeNode* pTreeHead1, TreeNode* pTreeHead2)

{

        bool result = false;

        if(pTreeHead1->m_nValue == pTreeHead2->m_nValue)

        {

                result = DoesTree1HaveAllNodesOfTree2(pTreeHead1, pTreeHead2);

        }

 

        if(!result && pTreeHead1->m_pLeft != NULL)

                result = HasSubtreeCore(pTreeHead1->m_pLeft, pTreeHead2);

 

        if(!result && pTreeHead1->m_pRight != NULL)

                result = HasSubtreeCore(pTreeHead1->m_pRight, pTreeHead2);

 

        return result;

}

在上述代码中,我们递归调用hasSubtreeCore遍历二叉树A。如果发现某一结点的值和树B的头结点的值相同,则调用DoesTree1HaveAllNodeOfTree2,做第二步判断。

在面试的时候,我们一定要注意边界条件的检查,即检查空指针。当树A或树B为空的时候,定义相应的输出。如果没有检查并做相应的处理,程序非常容易崩溃,这是面试时非常忌讳的事情。由于没有必要在每一次递归中做边界检查(每一次递归都做检查,增加了不必要的时间开销),上述代码只在HasSubtree中作了边界检查后,在HasSubtreeCore中作递归遍历。

接下来考虑第二步,判断以树A中以N为根结点的子树是不是和树B具有相同的结构。同样,我们也可以用递归的思路来考虑:如果结点N的值和树B的根结点不相同,则以N为根结点的子树和树B肯定不具有相同的结点;如果他们的值相同,则递归地判断他们的各自的左右结点的值是不是相同。递归的终止条件是我们到达了树A或者树B的叶结点。参考代码如下:

bool DoesTree1HaveAllNodesOfTree2(TreeNode* pTreeHead1, TreeNode* pTreeHead2)

{

        if(pTreeHead2 == NULL)

                return true;

 

        if(pTreeHead1 == NULL)

                return false;

 

        if(pTreeHead1->m_nValue != pTreeHead2->m_nValue)

                return false;

 

        return DoesTree1HaveAllNodesOfTree2(pTreeHead1->m_pLeft, pTreeHead2->m_pLeft) &&

                DoesTree1HaveAllNodesOfTree2(pTreeHead1->m_pRight, pTreeHead2->m_pRight);

}

 

参考 :

   http://zhedahht.blog.163.com/blog/static/25411174201142733927831/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值