有一个1亿结点的树,已知两个结点, 求它们的最低公共祖先!

对该问题,分为如下几种情形讨论:

情形一:

假如该树为二叉树,并且是二叉搜索树, 依据二叉搜索树是排过序的, 我们只需要从树的根结点开始,逐级往下,和两个输入的结点进行比较.

如果当前结点的值比两个结点的值都大,那么最低的公共祖先一定在当前结点的左子树中,下一步遍历当前结点的左子节点.

如果当前结点的值比两个结点的值都小,那么最低的公共祖先一定在当前结点的右子树中,下一步遍历当前结点的右子结点.

这样在树中从上到下找到的第一个在两个输入结点的值之间的结点,就是最低的公共祖先.


情形二:

如果是一棵普通的树,但是树的结点(除根结点外)中有指向父结点的指针:

这个问题可以转换为求两个链表的第一个公共结点.

假设树结点中指向父结点的指针是pParent,那么从树的每一个叶结点开始都一个由指针pParent串起来的链表,这些链表的尾指针都是树的根结点.

输入两个结点,这两个结点位于两个链表上,它们的最低公共祖先刚好是这两个链表的第一个公共结点. 比如输入的两个结点分别为F和H,那么F在链表F->D->B->A上, 

而H在链表H->E->B->A上, 这两个链表的第一个交点B 刚好也是它们的最低公共祖先.参见下面的图示



情形三:

仅是一棵普通的树,没有其它条件:

求出从根结点到指定结点的路径,这样我们就得到两条路径,比较这两条路径的最低公共祖先就可以了.

具体的思路还是,从树的根节点开始遍历,参见下面的图, 


假设还是输入结点F和H, 我们先判断A的子树中是否同时包含结点F和H, 得到的结果为true,接着我们再先后判断A的两个子结点B和C的子树是否同时包含F和H,结果是B的结果为true而C的结果是false, 接下来我们再判断B的两个子结点D和E,发现这两个结点得到的结果都是false,于是B是最后一个公共祖先,即是我们的输出.

这里需要注意的问题是, 如何避免对同一个结点重复遍历很多次的问题?

比如当我们判断以结点A为根的树中是否含有结点F的时候, 我们需要对D,E等结点遍历一遍;接下来判断以结点B为根的树中是否含有结点F时, 我们还是需要对D,E等结点再次遍历一遍.

解决这个问题的思路是:

在遍历的过程中使用辅助缓存,用两个链表分别保存从根结点到输入的两个结点之间的路径,然后把问题转换为两个链表的最后公共结点的问题.

比如我们用前序遍历的方法得到从根结点A到H的路径的过程如下:

(1)遍历到A,将A放入路径中,路径中只有一个结点A;

(2)遍历到B,把B存到路径中去,此时路径为A->B;

(3)遍历到D,把D放到路径中去,此时路径为A->B->D;

(4)遍历到F,把F放到路径中去,此时路径为A->B->D->F;

(5)F为叶结点,这条路径不可能到达节点H,我们需要回溯,把F从路径中删除,变为A->B->D;

(6)遍历到G,和结点F一样,这条路径无法到达H,遍历玩G后,这条路径仍是A->B->D;

(7)由于D的所有结点都遍历过了,不可能到达结点H, 因此D不在路径中,将D从路径中删除,变成A->B;

(8)遍历E,把E加入到路径中,此时路径变成A->B->E;

(9)遍历H,已经到达目标结点,A->B->E就是根结点开始到达H必须经过的路径.

按照上面的方法,我们同理可以得到从根结点A开始到F必须经过的路径是A->B->D.

接着,我们求出这两个路径的最后公共结点,也就是B,就是我们要求的F和H的最低公共祖先.


时间复杂度分析:

为了得到从根结点开始到输入的两个结点的两条路径,需要遍历两次树,每次遍历的时间复杂度是O(n),得到的两条路径的长度在最差的情况是O(n),通常情况下两条路径的长度是O(logn)


下面是情形三的实现代码,文件名为common_parent_in_tree.cpp

#include "tree.h"
#include <list>

using namespace std;

bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, list<TreeNode*>& path){
    if(pRoot == pNode)
        return true;

    path.push_back(pRoot);
    bool found = false;

    vector<TreeNode*>::iterator i = pRoot->vec_children.begin();
    while(!found && i<pRoot->vec_children.end()){
        found = GetNodePath(*i, pNode, path);
        ++i;
    }

    if(!found)
        path.pop_back();

    return found;
}

TreeNode* GetLastCommonNode(const list<TreeNode*>& path1, const list<TreeNode*>& path2){
    list<TreeNode*>::const_iterator iterator1 = path1.begin();
    list<TreeNode*>::const_iterator iterator2 = path2.begin();

    TreeNode* pLast = NULL;
    while(iterator1 != path1.end() && iterator2 != path2.end()){
        if(*iterator1 == *iterator2)
            pLast = *iterator1;

        iterator1++;
        iterator2++;
    }

    return pLast;
}

TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2){
    if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
        return NULL;

    list<TreeNode*> path1;
    GetNodePath(pRoot, pNode1, path1);

    list<TreeNode*> path2;
    GetNodePath(pRoot, pNode2, path2);

    return GetLastCommonNode(path1,path2);
}

//===========================测试代码==================================

void Test(const char* testName, TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2, TreeNode* pExpected){
    if(testName != NULL)
        printf("%s begins: \n", testName);

    TreeNode* pResult = GetLastCommonParent(pRoot, pNode1, pNode2);

    if((pExpected == NULL && pResult == NULL) ||
        (pExpected != NULL && pResult != NULL && pResult->value == pExpected->value))
        printf("Passed.\n");
    else
        printf("Failed.\n");
}

// 形状普通的树
//                     1
//                    / \
//                   2   3
//                  / \
//                 4   5
//                /\  /|\
//               6  78 9 10
void Test1(){
    TreeNode* pNode1 = CreateTreeNode(1);
    TreeNode* pNode2 = CreateTreeNode(2);
    TreeNode* pNode3 = CreateTreeNode(3);
    TreeNode* pNode4 = CreateTreeNode(4);
    TreeNode* pNode5 = CreateTreeNode(5);
    TreeNode* pNode6 = CreateTreeNode(6);
    TreeNode* pNode7 = CreateTreeNode(7);
    TreeNode* pNode8 = CreateTreeNode(8);
    TreeNode* pNode9 = CreateTreeNode(9);
    TreeNode* pNode10 = CreateTreeNode(10);

    ConnectTreeNodes(pNode1,pNode2);
    ConnectTreeNodes(pNode1,pNode3);

    ConnectTreeNodes(pNode2,pNode4);
    ConnectTreeNodes(pNode2,pNode5);

    ConnectTreeNodes(pNode4,pNode6);
    ConnectTreeNodes(pNode4,pNode7);

    ConnectTreeNodes(pNode5,pNode8);
    ConnectTreeNodes(pNode5,pNode9);
    ConnectTreeNodes(pNode5,pNode10);

    Test("Test1", pNode1, pNode6, pNode8, pNode2);
}

//树退化为一个链表
//                    1
//                   /
//                  2
//                 /
//                3
//               /
//              4
//             /
//            5
void Test2(){
    TreeNode* pNode1 = CreateTreeNode(1);
    TreeNode* pNode2 = CreateTreeNode(2);
    TreeNode* pNode3 = CreateTreeNode(3);
    TreeNode* pNode4 = CreateTreeNode(4);
    TreeNode* pNode5 = CreateTreeNode(5);

    ConnectTreeNodes(pNode1,pNode2);
    ConnectTreeNodes(pNode2,pNode3);
    ConnectTreeNodes(pNode3,pNode4);
    ConnectTreeNodes(pNode4,pNode5);

    Test("Test2",pNode1,pNode5,pNode4,pNode3);
}


//树退化为一个链表,一个结点不在树中
//                    1
//                   /
//                  2
//                 /
//                3
//               /
//              4
//             /
//            5         6
void Test3(){
    TreeNode* pNode1 = CreateTreeNode(1);
    TreeNode* pNode2 = CreateTreeNode(2);
    TreeNode* pNode3 = CreateTreeNode(3);
    TreeNode* pNode4 = CreateTreeNode(4);
    TreeNode* pNode5 = CreateTreeNode(5);

    ConnectTreeNodes(pNode1,pNode2);
    ConnectTreeNodes(pNode2,pNode3);
    ConnectTreeNodes(pNode3,pNode4);
    ConnectTreeNodes(pNode4,pNode5);

    TreeNode* pNode6 = CreateTreeNode(6);

    Test("Test3",pNode1,pNode5,pNode6,NULL);
}

//输入NULL
void Test4(){
    Test("Test4", NULL, NULL, NULL, NULL);
}

int main(int argc, char** argv){
    Test1();
    Test2();
    Test3();
    Test4();

    return 0;
}

下面是程序运行结果的截图:






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值