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

有一个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 using namespace std;bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, list& path){if(pRoot == pNode)return true;path.push_back(pRoot);bool found = false;vector::iterator i = pRoot->vec_children.begin();while(!found && ivec_children.end()){found = GetNodePath(*i, pNode, path);++i;}if(!found)path.pop_back();return found;
}TreeNode* GetLastCommonNode(const list& path1, const list& path2){list::const_iterator iterator1 = path1.begin();list::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 path1;GetNodePath(pRoot, pNode1, path1);list 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: 
", testName);TreeNode* pResult = GetLastCommonParent(pRoot, pNode1, pNode2);if((pExpected == NULL && pResult == NULL) ||(pExpected != NULL && pResult != NULL && pResult->value == pExpected->value))printf("Passed.
");elseprintf("Failed.
");
}// 形状普通的树
//                     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、什么是环?在图论中,环(英语:cycle)是一条只有第一个和最后一个顶点重复的非空路径。在有向图中,一个结点经过两种路线到达另一个结点,未必形成环。1、拓扑排序1.1、无向图使用拓扑排序可以判断一个无向图中是否存在环,具体步骤如下:求出图中所有结点的度。将所有度 <= 1 的结点入队。(独立结点的度为 0)当队列不空时,弹出队首元...

  • 已知一个长度为11的线性表List=(12, 24, 36, 90, 52, 30, 41, 8, 10, 38, 61),试回答下面问题 (1)将线性表元素依次插入一个空的平衡二叉树,画出所得平衡二叉树,如果假设每个元素查找概率相同,则平均查找长度为多少?   (1) 插入12, 这是第一个结点,是根结点.   (2) 插入...

  • 哗我看了一下好像没有很详细专门讲分治的blog?那就主要先学一下点分治吧,其他的……等我记得把C++一本通带到机房来再说吧先咕着啦 写在前面 刷题进度 入门题(0/3) 好题(0/9) 问题解决进度 Q1 Q2  正文 淀粉质 点分治 点分治就是在一棵树上,对具有某些限定条件的途径静态地进行统计的算法。 ——《算法竞赛 进阶指南》...

  • B树        即二叉搜索树:        1.所有非叶子结点至多拥有两个儿子(Left和Right);        2.所有结点存储一个关键字;        3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;        如:               ...

  • 题目:从上到下打印二叉树 II 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。 例如: 给定二叉树: [3,9,20,null,null,15,7],     3    /   9  20     /      15   7 返回其层次遍历结果: [   [3],   [9,20],   [...

  • 题目:对称的二叉树 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。     1    /   2   2  / / 3  4 4  3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:...

  • 题目:二叉树的镜像 请完成一个函数,输入一个二叉树,该函数输出它的镜像。 例如输入:      4    /      2     7  /   / 1   3 6   9 镜像输出:      4    /      7     2  /   / 9   6 3   1 示例 1: 输入:root =...

  • 题目:树的子结构 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构, 即 A中有出现和B相同的结构和节点值。 例如: 给定的树 A:      3     /    4   5   /  1   2 给定的树 B:    4    /  1 返回 true,因为 B...

  • 下面是树类型题目需要用到的头文件tree.h,请包含在cpp文件中编译,而不是放在c文件中编译,比如查找树中两个节点的最低公共父结点的题common_parent_in_tree.cpp,编译它的方法是: g++ -g common_parent_in_tree.cpp -o common_parent_in_tree 下...

  • 大牛们应该对路径都很了解了,这篇文章主要给像我这样的入门小白普及常识用的,啊哈下面的路径介绍针对windows,其他平台的暂时不是很了解。在编写的py文件中打开文件的时候经常见到下面其中路径的表达方式:open('aaa.txt')open('/data/bbb.txt')open('D:\user\ccc.txt')这三种表达式...

  • 1)绝对路径:绝对路径是指目录下的绝对位置,直接到达目标位置,通常是从盘符开始的路径。例如:C:windowssystem32cmd.exe  注意: 在不同系统的情况系 windows下是“”,linux和unix下是“/” ,但在win中没有本质区别。linux和unix系统中绝对路径 以“/”为起始 例:/home/us...

  •     最终运行效果 当然,这个Application context路径可以直接删掉不需要最终访问路径就会变成http://localhost:8080/...

  •     1、在js代码里面 或者 html里面用"v-bind:"或":属性名"绑定路径的时候使用 require('@/assets/home/imgName.png') 2、在css或者scss或者html里面的src中引入图片使用(注意如果是:src=后面用第1种方式引入路径) ~@/assets/components...

  • 寻路算法大总结! 交换机生成树采用的是完全不同的D-V(distance vector)距离矢量算法,并不是很可靠. 并不是任意两点之间的最短路径,因为任意两点之间取最短路径可能有环路:总权更大 交换机STP不一定是最小生成树!!!举例论证 因为它只是所有交换机到根桥最短 贪心算法的味...