`
gaotong1991
  • 浏览: 91223 次
  • 来自: 北京
社区版块
存档分类
最新评论

寻找二叉树两个节点的最低公共祖先

 
阅读更多

给定一棵树,同时给出树中的两个结点(n1和n2),求它们的最低公共祖先。也就是常见的LCA(Lowest Common Ancestor )问题。

看下面的图就明白了:

 

lca

方法一

下面是一个简单的复杂度为 O(n) 的算法,解决LCA问题
1) 找到从根到n1的路径,并存储在一个向量或数组中。
2)找到从根到n2的路径,并存储在一个向量或数组中。
3) 遍历这两条路径,直到遇到一个不同的节点,则前面的那个即为最低公共祖先.

下面的C++的程序实现

01 // O(n) 解决 LCA
02 #include <iostream>
03 #include <vector>
04 using namespace std;
05  
06 //二叉树节点
07 struct Node
08 {
09     int key;
10     struct Node *left, *right;
11 };
12 //公用函数,生成一个节点
13 Node * newNode(int k)
14 {
15     Node *temp = new Node;
16     temp->key = k;
17     temp->left = temp->right = NULL;
18     return temp;
19 }
20 //找到从root到 节点值为key的路径,存储在path中。没有的话返回-1
21 bool findpath(Node * root,vector<int> &path,int key){
22     if(root == NULL) return false;
23     path.push_back(root->key);
24     if(root->key == key) return true;
25     //左子树或右子树 是否找到,找到的话当前节点就在路径中了
26     bool find =  ( findpath(root->left, path, key) || findpath(root->right,path ,key) );
27     if(find) return true;
28     //该节点下未找到就弹出
29     path.pop_back();
30     return false;
31 }
32  
33 int findLCA(Node * root,int key1,int key2){
34     vector<int> path1,path2;
35     bool find1 = findpath(root, path1, key1);
36     bool find2 = findpath(root, path2, key2);
37     if(find1 && find2){
38         int ans ;
39         for(int i=0; i<path1.size(); i++){
40             if(path1[i] != path2[i]){
41                 break;
42             }else
43                 ans = path1[i];
44         }
45         return ans;
46     }
47     return -1;
48 }
49  
50 // Driver program to test above functions
51 int main()
52 {
53     // 按照上面的图来创创建树
54     Node * root = newNode(1);
55     root->left = newNode(2);
56     root->right = newNode(3);
57     root->left->left = newNode(4);
58     root->left->right = newNode(5);
59     root->right->left = newNode(6);
60     root->right->right = newNode(7);
61     cout << "LCA(4, 5) = " << findLCA(root, 4, 5);
62     cout << "\nLCA(4, 6) = " << findLCA(root, 4, 6);
63     cout << "\nLCA(3, 4) = " << findLCA(root, 3, 4);
64     cout << "\nLCA(2, 4) = " << findLCA(root, 2, 4);
65     return 0;
66 }

输出:

1 LCA(4, 5) = 2
2 LCA(4, 6) = 1
3 LCA(3, 4) = 1
4 LCA(2, 4) = 2

时间复杂度: O(n), 树被遍历了两次,每次遍历复杂度不超过n,然后比较路径。

第二种方法(只遍历一次)

上面的方法虽然是O(n),但是操作依然繁琐了一点,并且需要额外的空间来存储路径。其实可以只遍历一次,利用递归的巧妙之处。学好二叉树,其实就是学好递归。

从root开始遍历,如果n1和n2中的任一个和root匹配,那么root就是LCA。 如果都不匹配,则分别递归左、右子树,如果有一个 key(n1或n2)出现在左子树,并且另一个key(n1或n2)出现在右子树,则root就是LCA.  如果两个key都出现在左子树,则说明LCA在左子树中,否则在右子树。

01 /* 只用一次遍历解决LCA */
02 #include <iostream>
03 using namespace std;
04 struct Node
05 {
06     struct Node *left, *right;
07     int key;
08 };
09 Node* newNode(int key)
10 {
11     Node *temp = new Node;
12     temp->key = key;
13     temp->left = temp->right = NULL;
14     return temp;
15 }
16  
17 // 返回n1和n2的 LCA的指针
18 // 假设n1和n2都出现在树中
19 struct Node *findLCA(struct Node* root, int n1, int n2)
20 {
21     if (root == NULL) return NULL;
22  
23     // 只要n1 或 n2 的任一个匹配即可
24     //  (注意:如果 一个节点是另一个祖先,则返回的是祖先节点。因为递归是要返回到祖先的 )
25     if (root->key == n1 || root->key == n2)
26         return root;
27     // 分别在左右子树查找
28     Node *left_lca  = findLCA(root->left, n1, n2);
29     Node *right_lca = findLCA(root->right, n1, n2);
30     // 如果都返回非空指针 Non-NULL, 则说明两个节点分别出现了在两个子树中,则当前节点肯定为LCA
31     if (left_lca && right_lca)  return root;
32     // 如果一个为空,在说明LCA在另一个子树
33     return (left_lca != NULL)? left_lca: right_lca;
34 }
35  
36 //测试
37 int main()
38 {
39     // 构造上面图中的树
40     Node * root = newNode(1);
41     root->left = newNode(2);
42     root->right = newNode(3);
43     root->left->left = newNode(4);
44     root->left->right = newNode(5);
45     root->right->left = newNode(6);
46     root->right->right = newNode(7);
47     cout << "LCA(4, 5) = " << findLCA(root, 4, 5)->key;
48     cout << "\nLCA(4, 6) = " << findLCA(root, 4, 6)->key;
49     cout << "\nLCA(3, 4) = " << findLCA(root, 3, 4)->key;
50     cout << "\nLCA(2, 4) = " << findLCA(root, 2, 4)->key;
51     return 0;
52 }

时间复杂度为O(n),但是上面的方法还是有所局限的,必须保证两个要查找的节点n1和n2都出现在树中。如果n1不在树中,则会返回n2为LCA,理想答案应该为NULL。要解决这个问题,可以先查找下 n1和n2是否出现在树中,然后加几个判断即可。

原文:http://www.acmerblog.com/lca-lowest-common-ancestor-5574.html

2
1
分享到:
评论

相关推荐

    寻找二叉树两结点最近的祖先

    (1)拟定合适的二叉树的输入形式和构造算法; (2)能够以直观的形式观察所建立的二叉树的结构;

    面试题68 – II. 二叉树的最近公共祖先

    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)” 例如,给定如下二叉树: root = [3...

    二叉树的最近公共祖先II1

    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节

    树的公共父节点.rar

    因为树的结构不同所以需要分情况...当树为二叉排序树,寻找给定两节点的最低公共祖先 当树为普通树,每个节点中有指针指向其父节点 当树为二叉树,每个节点仅有左右孩子指针 当树为普通树,每个节点仅有左右孩子指针

    Easay#JavascriptCoding#剑指68-2.二叉树的最近公共祖先1

    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节

    Cygra#Leet-Code-Solus#0236. 二叉树的最近公共祖先1

    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节

    二叉树的相关操作

    本程序包括二叉树的一些常见的相关操作,比如非递归建立二叉树,二叉树的非递归遍历,非递归求二叉树的叶子数目,层数,任意两个节点的公共祖先,跟到叶子节点的所有路径,根据中序和后序的遍历结果建立二叉树等等。

    floatLig#JavaLearning#236.二叉树的最近公共祖先1

    中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以

    二叉树的各种操作源代码c++

    //求二叉树的节点个数//前序打印叶子节点//二叉树的遍历//求二叉树的第k层节点的值//交换二叉树的左右子树//求二叉树中两个节点的最低公共祖先节点//求双亲节点//判断两棵二叉树是否相同//二叉树删除节点//二叉树...

    第五章 树与二叉树

    由于二叉树中每个结点都可能有两个子树,因此需要寻找一条合适的搜索路径。 1、前序遍历 前序遍历二叉树操作定义为: 若树为空,则空操作返回;否则 (1)访问根结点 (2)前序遍历根结点的左子树 (3)前序遍历根...

    common-father.rar_Father

    如何求二叉树的任意两个节点的公共祖先,可以通过编译

    为面试做准备的python经典编程题之4

    在排序数组中查找数字.py 二叉搜索树的第k大节点.py 二叉树的深度.py 数组中数字出现的次数.py 和为s的数字.py 翻转字符串.py 队列的最大值.py n个骰子的点数.py ...树中两个节点的最低公共祖先.py

    最大公共字符串leetcode-Binary-Tree:Swift中的二叉树

    直径或宽度(树中任意两个节点之间的最长距离) 二叉树节点的组成部分 它保存的数据类型,例如Int或String等 指向左子节点的指针 指向右子节点的指针 常见操作 遍历 插入 删除 二叉树节点 class BinaryTreeNode &lt;...

    二叉树家谱

    7、添加成员节点时,可以选择将新添加的节点作为整个家谱的上一代祖先, 或者将新添加的节点作为某个现有成员的孩子。 8、作为某个现有成员的孩子,根据给出的父节点的姓名将该结点添加到相 应位置,注意,针对某...

    目前最火最热门的python经典编程题之3

    50.第一个只出现一次的字符 50.字符流中第一个不重复的字符 51.数组中的逆序对 Array 常考 52.两个链表的第一个公共结点 Linked List 53.数字在排序数组中出现的次数 ...68.树中两个节点的最低公共祖先 常考

    《剑指Offer》题目及代码.zip

    50. 树中两个节点的最低公共祖先 51. 找出重复的数 52. 构建乘积数组 53. 正则表达式匹配 54. 表示数值的字符串 55. 字符流中第一个不重复的字符 56. 链表中环的入口节点 57. 删除链表中重复的节点 58. ...

    二叉排序树与平衡二叉树的实现

    (1)每当插入一个新结点,从该结点开始向上计算各结点的平衡因子,即计算该结点的祖先结点的平衡因子,若该结点的祖先结点的平衡因子的绝对值均不超过1,则平衡二叉树没有失去平衡,继续插入结点; (2)若插入结点的某...

    BinaryTree.rar

    1、任意输入前序 + 中序序列或者中序 + 后序序列,生成二叉树...6、输入两个节点值 找到共同祖先 7、检测输入的前序,中序,后续序列的有效性 例如当用户输入错误的序列时,程序应该有错误提示并构造二叉树至出错前状态

    leetcode跳跃-leetcoe:力扣刷题记录

    二叉树的最近公共祖先 路径总和II 打家劫舍III 没做出来。动态规划算法 二叉树的垂序遍历 删除二叉搜索树中的节点 移除链表元素 环形链表 回文链表 环形链表 II 两两交换链表中的节点 对...

    判别结点u是否为结点v的子孙

    判别结点u是否为结点v的子孙

Global site tag (gtag.js) - Google Analytics