LCA-最小公共父节点

有一个普通二叉树,AB分别为两个子节点,求AB最近(深度最大)的公共父节点。

此题仍然是一个老题,有着多种解决方法,本文针对其中三种方法来进行分析总结。

这三种方法分别是:递归法,tarjan离线算法,RMQ在线算法。


递归法

递归法比较直观简单,思路如下:

  • 首先判定当前节点root是否是A节点或者B节点,若是的话直接返回该节点
  • 若不是,分别对root节点的左右子树进行递归查找最小公共父节点,若左右子树都返回了节点,那么表示当前节点就是最小公共父节点,若只有其中一个子树返回了结果,那么就返回该结果节点。

    参考代码如下:

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root)
            return NULL;

        if(root == p || root == q)
            return root;

        TreeNode* left = lowestCommonAncestor(root->left,p,q);
        TreeNode* right = lowestCommonAncestor(root->right,p,q);

        if(left == NULL) return right;
        if(right==NULL) return left;

        return root;
    }

如上图中我们要查找节点4,3的最小公共父节点,那么上述代码的执行过程如下

  • 判断1是否4,3节点不是,查询1的左子树和右子树
  • 1的左子树中先判断2是否4,3节点不是,查找2的左右子树
    • 2的左子树 显然返回结果4,2的右子树返回结果NULL,显然2的左右子树并没有都返回树中的节点,因此2不是4,3的最小公共父节点,因此以2位根节点的1的左子树调用方法返回的是4.
  • 1的右子树判断3节点是否是4,3节点,发现是的,直接返回3节点。
  • 1的左右子树的递归调用都返回了结果,因此1就是节点4,3的最小公共父节点。

Tarjan离线算法

对于该算法,常常会有一个给定的树T和一个给定的查询集合P={(u,v)},我们需要确定P中每对的最小公共祖先。

算法为何说是离线的?因为针对所有的查询,我们都是在算法的一次执行过程中找到。

算法思路:

  • 根节点root开始搜索,每次递归搜索所有的子树
  • 当搜索到节点u时,创建一个由u本身组成的集合,这个集合的祖先为u自己。然后递归搜索u的所有儿子节点。每个儿子节点递归完毕之后,将儿子节点所在的集合和u所在的集合合并,再把该集合的祖先设定为节点u。
  • 每个节点的所有孩子都递归完毕后,将该节点设定为已经访问过,开始遍历所有u的查询,若发现查询的另外一个节点v也是标示为访问过的话,那么节点u和v的最小公共父节点即为节点v所在集合的祖先节点。

上述所有操作都是使用并查集高效完成的。因此时间复杂度,O(n)深度优先搜索所有节点的时间,搜索每个节点时会遍历这个节点相关的所有查询。如果总的查询个数为q,则总的复杂度为O(n+q)。

还是上图的树,我们从节点1开始,创建集合{1},然后递归到1的第一个孩子节点2,创建集合{2},之后继续深度递归到节点4,创建集合{4},完成节点4的访问后,将4设定为已访问,集合{4}与集合{2}合并,得到{4,2},将他们的祖先设为2,然后访问节点5,创建集合{5},完成之后将集合{2,4}和{5}合并,得到{2,4,5}然后将该集合的祖先设为2,若此时(5,4)是一个查询,那么就可以得到他们的最小公共祖先为2。之后合并{1}和{2,4,5}得到{1,2,4,5},祖先为1,访问3,继续访问6,完成之后,若(6,2)是一个查询,那么其最小公共祖先为1。剩下的操作类似。

伪代码如下(参考算法导论):

LCA(u)
  MAKE-SET(u)
  Find-Set(u).ancestor = u
  for each child v of u in T
      LCA(u)
      Union(u,v)
      Find-Set(u).ancestor = v
  u.color= Black
  for each node v such that (u,v) belongs to P
            if v.color ==Black
               print "The LCA of "u "and" v"is" Find-Set(v).ancestor

RMQ在线算法

无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的。所以离线算法针对一次性较多查询的话比较实惠,但是每次只来一个查询,这时候多次调用离线算法就不值当了。在线算法处理这种情况比较合适。

之前文章里写过RMQ在线算法的原理了,这里就不加多说,本文主要介绍如何把RMQ算法和最小公共父节点问题结合起来。

算法思路:

LCA集合RMQ主要是通过DFS(深度优先搜索)完成。每次经过某一个点——无论是从它的父亲节点进入这个点,还是从它的儿子节点返回这个点,都按顺序记录下来,还是上面的树,DFS之后顺序为:1-2-4-2-5-2-1-3-6-3-1。那么要找到树上两个节点的最近公共祖先,无非就是找到这两个节点最后一次出现在数组中的位置所囊括的一段区间中深度最小的那个点,并且我们假定我们的树中每个父亲节点的标号都比孩子节点小,那么目标就转换为求该区间中标号最小的那个节点。

部分代码如下:

int ind[N];//用来存储每个节点在生成路径中最后一次出现的位置
vector<int> road;//存储DFS路径
void dfs(int s)
{
    road.push_back(s);
    for(int i = 0; i < V[s].size();i++){
        dfs(V[s][i]);
        road.push_back(s);
    }
}

得到路径之后,那么每个节点在生成路径中最后一次出现的位置可以用如下方式得到:

for(int i = 0; i < road.size();i++)
{
        ind[road[i]] = i + 1;
}

进行上述操作之后,对于每一对查询(u,v),我们再利用RMQ区间查询的算法输入ind[u],ind[v]就能得到其最小公共父节点了。


总结

本文总结了三种不同求解最小公共父节点的方法,本人曾在微软面试中被问及此题,答得并不是很好,若是能在面试中答出多种方法,那么一定会有大大的加分。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-26 12:46:14

LCA-最小公共父节点的相关文章

LCA最小公共父节点的解题思路

LCA最小公共父节点解法: 1.二叉搜索树: 中序遍历是升序,前序遍历即按序插入建树的序列. 二叉搜索树建树最好用前序+中序,如果用前序建树,最坏情况会退化为线性表,超时. 最近公共祖先甲级: A1143,1151 利用二叉搜索树的性质寻找结点u和v的最低公共祖先(递归解法) 1)如果根结点的值大于max(u,v),说明u和v均在根结点的左子树,则进入根结点的左子结点继续递归 2)如果根结点的值小于min(u,v),说明u和v均在根结点的右子树,则进入根结点的右子结点继续递归 3)剩下的情况就是

[ 题解 ] [ 最小公共父节点 ] E. Family Tree

http://codeforces.com/group/NVaJtLaLjS/contest/238202/problem/E 题意: 输入数字N,还有两只牛的名字: 输入N行父子关系(题中为母女)即两个名字,前者是父,后者是子: 问两只牛是什么亲属关系. 具体的输出规则(亲属关系)请见原题. 示例: Input: 7 AA BB MOTHER AA GGMOTHER BB MOTHER SISTER GMOTHER MOTHER GMOTHER AUNT AUNT COUSIN GGMOTHE

[LeetCode] 1123. Lowest Common Ancestor of Deepest Leaves 最深叶结点的最小公共父节点

Given a rooted binary tree, return the lowest common ancestor of its deepest leaves. Recall that: The node of a binary tree is a?leaf?if and only if it has no children The?depth?of the root of the tree is 0, and if the depth of a node is?d, the depth

输出二叉树中随机两个结点的最小公共父结点

思路:当遇到一个结点是返回1,当左右子树都返回1的时候,即最小公共父节点. //二叉树的数据结构 typedef struct MyStruct { char data; struct MyStruct *leftChild; struct MyStruct *rightChild; }Node, *Tree; //查找方法 int findFirstFather(Tree root, char first, char second,char &destination){ int i, j; i

二叉树中两个节点的最近公共父节点

这是京东周六的笔试题目   当时不在状态,现在想来肯定是笔试就被刷掉了,权当做个纪念吧.  这个问题可以分为三种情况来考虑: 情况一:root未知,但是每个节点都有parent指针此时可以分别从两个节点开始,沿着parent指针走向根节点,得到两个链表,然后求两个链表的第一个公共节点,这个方法很简单,不需要详细解释的. 情况二:节点只有左.右指针,没有parent指针,root已知思路:有两种情况,一是要找的这两个节点(a, b),在要遍历的节点(root)的两侧,那么这个节点就是这两个节点的最

二叉树---最近公共父节点(LCA)

题目: 给定一棵二叉树,找到两个节点的最近公共父节点(LCA). 最近公共祖先是两个节点的公共的祖先节点且具有最大深度. 4 / 3 7 / 5 6 LCA(3, 5) = 4 LCA(5, 6) = 7 LCA(6, 7) = 7 Java代码: public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) { // write your code here if(root == null||root =

[LeetCode] Lowest Common Ancestor of a Binary Tree 二叉树的最小共同父节点

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w

Geeks LCA最低公共单亲节点

给出一颗二叉树,找到两个值的最小公共节点. 假设两个值都会在树中出现.如果可能不会出现的话,也很简单,就查找一遍看两个值是否在树中就可以了.如果不在就直接返回NULL. 基本思想:就是在二叉树中比较节点值和两个值的大小,如果都在一边(左边或者右边)那么就往下继续查找,否则就是都在同一边了,那么就可以返回这个节点了,这个节点就是最低公共单亲节点了. 参考:http://www.geeksforgeeks.org/lowest-common-ancestor-in-a-binary-search-t

二叉树最近公共父节点

在二叉树中找最近公共父节点.分为两种情况,一种是有父指针,一种没有父指针. 1.有父指针 这种情况比较简单,计算两个结点的深度,再把深度大的向上移,移到同一深度.在同时向上移动,直到两个结点相同,这样便找到了父节点.这个算法时间复杂度为O(N). 代码实现: #include<iostream> struct Node { int data; Node* left; Node* right; Node* parent; Node() :left(NULL), right(NULL), pare