程序员面试100题之五:二叉树两个结点的最低共同父结点

来源:互联网 发布:网络销售合同 编辑:程序博客网 时间:2024/06/05 08:35
 题目:二叉树的结点定义如下:

    struct TreeNode

   {

              int m_nvalue;

             TreeNode* m_pLeft;

             TreeNode* m_pRight;

};

输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。

        分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。

        第一变种是二叉树是一种特殊的二叉树:查找二叉树。也就是树是排序过的,位于左子树上的结点都比父结点小,而位于右子树的结点都比父结点大。我们只需要从根结点开始和两个结点进行比较。如果当前结点的值比两个结点都大,则最低的共同父结点一定在当前结点的左子树中。如果当前结点的值比两个结点都小,则最低的共同父结点一定在当前结点的右子树中。

        第二个变种是树不一定是二叉树,每个结点都有一个指针指向它的父结点。于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表。因此这个问题转换为求两个单向链表的第一个公共结点。

       现在我们回到这个问题本身。所谓共同的父结点,就是两个结点都出现在这个结点的子树中。因此我们可以定义一函数,来判断一个结点的子树中是不是包含了另外一个结点。这不是件很难的事,我们可以用递归的方法来实现:

/*// If the tree with head pHead has a node pNode, return true.// Otherwise return false.*/bool HasNode(TreeNode* pHead, TreeNode* pNode){if(pHead == pNode)return true;bool has = false;if(pHead->m_pLeft != NULL)has = HasNode(pHead->m_pLeft, pNode);if(!has && pHead->m_pRight != NULL)has = HasNode(pHead->m_pRight, pNode);return has;}

  我们可以从根结点开始,判断以当前结点为根的树中左右子树是不是包含我们要找的两个结点。如果两个结点都出现在它的左子树中,那最低的共同父结点也出现在它的左子树中。如果两个结点都出现在它的右子树中,那最低的共同父结点也出现在它的右子树中。如果两个结点一个出现在左子树中,一个出现在右子树中,那当前的结点就是最低的共同父结点。基于这个思路,我们可以写出如下代码:

/*// Find the last parent of pNode1 and pNode2 in a tree with head pHead*/TreeNode* LastCommonParent_1(TreeNode* pHead, TreeNode* pNode1, TreeNode* pNode2){if(pHead == NULL || pNode1 == NULL || pNode2 == NULL)return NULL;// check whether left child has pNode1 and pNode2bool leftHasNode1 = false;bool leftHasNode2 = false;if(pHead->m_pLeft != NULL){leftHasNode1 = HasNode(pHead->m_pLeft, pNode1);leftHasNode2 = HasNode(pHead->m_pLeft, pNode2);} if(leftHasNode1 && leftHasNode2){if(pHead->m_pLeft == pNode1 || pHead->m_pLeft == pNode2)return pHead;return LastCommonParent_1(pHead->m_pLeft, pNode1, pNode2);}// check whether right child has pNode1 and pNode2bool rightHasNode1 = false;bool rightHasNode2 = false;if(pHead->m_pRight != NULL){if(!leftHasNode1)rightHasNode1 = HasNode(pHead->m_pRight, pNode1);if(!leftHasNode2)rightHasNode2 = HasNode(pHead->m_pRight, pNode2);}if(rightHasNode1 && rightHasNode2){if(pHead->m_pRight == pNode1 || pHead->m_pRight == pNode2)return pHead;return LastCommonParent_1(pHead->m_pRight, pNode1, pNode2);}if((leftHasNode1 && rightHasNode2) || (leftHasNode2 && rightHasNode1))return pHead; return NULL;}

   接着我们来分析一下这个方法的效率。函数HasNode的本质就是遍历一棵树,其时间复杂度是O(n)(n是树中结点的数目)。由于我们根结点开始,要对每个结点调用函数HasNode。因此总的时间复杂度是O(n^2)。

        我们仔细分析上述代码,不难发现我们判断以一个结点为根的树是否含有某个结点时,需要遍历树的每个结点。接下来我们判断左子结点或者右结点为根的树中是否含有要找结点,仍然需要遍历。第二次遍历的操作其实在前面的第一次遍历都做过了。由于存在重复的遍历,本方法在时间效率上肯定不是最好的。



0 0
原创粉丝点击