首页 > 其他分享 >LCA(最近公共祖先)求解方法

LCA(最近公共祖先)求解方法

时间:2022-09-26 00:23:54浏览次数:55  
标签:求解 祖先 child int fa LCA 100 节点

本文参考https://oi-wiki.org/graph/lca/

定义树上u,v两点的LCA(最近公共祖先)是从根节点dfs到上述两节点路径上距离上述两点最近的公共点。

LCA有如下性质:

1、u是v的祖先,当且仅当LCA(u,v)=u

2、d(u,v)=h(u)+h(v)-2h(LCA(U,V))。其中d为树上两点距离,h为某点到树根的距离。

LCA求解方法如下:

1、朴素算法:每次寻找深度较大节点控制其上跳,最终实现重合。这里需要预处理整棵树,获得深度数组。

代码如下:

 1 #include <iostream>
 2 using namespace std;
 3 int h[100];//深度存储
 4 int child[100];
 5 int sib[100];
 6 int parent[100];
 7 void insert(int p,int self){
 8     int p_c = child[p];
 9     child[p]=self;
10     sib[self]=p_c;
11     parent[self]=p;
12 }//左孩子右兄弟存储 
13 int l=1;
14 void dfs(int u){
15     h[u]=l;
16     for (int i=child[u];i;i=sib[i]){
17         l++;
18         dfs(i);
19         l--;
20     }
21 }//dfs遍历获取深度 
22 int find(int u,int v){
23     int l_1 = h[u];
24     int l_2 = h[v];
25     while(u!=v){
26         if (l_1>l_2){
27             l_1--;
28             u=parent[u];            
29         }
30         else if(l_1<l_2){
31             l_2--;
32             v=parent[v];
33         }
34         else{
35             u=parent[u];
36             v=parent[v];
37         }
38     }//当位于相同高度时就一起跳 
39     return u;
40 }
41 int main(){
42     insert(4,2);
43     insert(4,1);
44     insert(1,3);
45     insert(1,5);
46     dfs(4);//4是根节点 
47     cout<<find(3,2);
48     return 0;
49 }

2、倍增算法:对上述朴素算法的优化,把跳跃过程二进制化来减少跳跃次数。

示例代码如下:

 1 #include <iostream>
 2 #include <algorithm>
 3 using namespace std;
 4 int fa[100][100];
 5 int child[100];
 6 int sib[100];
 7 int h[100];
 8 void insert(int p,int self){
 9     int p_c = child[p];
10     child[p]=self;
11     sib[self]=p_c;
12 }//左孩子右兄弟存储 
13 void dfs(int u,int l){
14     h[u]=l;//存储深度 
15     for (int i=1;i<=20;i++)
16         fa[u][i]=fa[fa[u][i-1]][i-1];
17     for (int i=child[u];i;i=sib[i]){
18         fa[i][0]=u;
19         dfs(i,l+1);
20     }
21 }
22 int find (int u,int v){
23     int l_1 = h[u];
24     int l_2 = h[v];
25     if (l_1<l_2)
26         swap(u,v);//保证u的深度大于等于v 
27     for (int i=20;i>=0;i--){
28         if (h[fa[u][i]]>=h[v])//如果可以缩小深度就跳跃。同时为了简便我们选择从大到小跳 
29             u=fa[u][i];
30         if (u==v)
31             return u;
32     }
33     for (int i=20;i>=0;i--){
34         if (fa[u][i]!=fa[v][i]){//相同深度后一起倍增跳 
35             u=fa[u][i];
36             v=fa[v][i];
37         }
38     }
39     return fa[u][0];
40 }
41 int main(){
42     insert(4,2);
43     insert(4,1);
44     insert(1,3);
45     insert(1,5);
46     dfs(4,1);//4是根节点 
47     cout<<find(1,5);
48     return 0;
49 }

3、Tarjan算法(求解LCA):这个算法作为离线算法,其根源还是利用dfs进行遍历,不同的是其利用并查集实现了信息的存储。

其大致思路如下:

 

 

此处以二叉形式为例,圆圈为根节点,三角为根节点下属子树。这里运用数学归纳法实现思路推导:假设tarjan(n)可以解决以n为根的树的LCA求解问题,于是我们分别对左右子树使用tarjan算法,并将完成tarjan算法遍历的子树通过并查集与根节点连接,如下图所示:

 

 这里红色表示已经对子树使用tarjan算法,并且将子树与根通过并查集连接到了一起。这时,我们再对右子树重复上述过程。得到了左右子树都解决LCA问题的一个树。这个树可以实现两查找节点都在左子树或者都在右子树的LCA查找。现在考虑查找节点分别位于左右节点的情况。因为通过并查集将左右子树连接在一起,所以最终只需寻找其祖宗节点即可。

为了防止无法寻找到真正的LCA,这里采用visit数组来记录节点的访问情况。如果两节点其中一个未被访问,就证明我们还未获取另一节点的相对位置信息,暂时不需要对其使用tarjan算法。只有当两节点都被访问后我们才可以根据二者的相对位置信息来使用tarjan算法。

示例代码如下:

 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 typedef struct ask{
 5     int u,v;
 6     int ans;
 7 }ask;
 8 ask a[100];//询问 
 9 int parent[100];
10 int child[100];
11 int sib[100];
12 int visit[100];
13 void insert(int p,int s){
14     int p_c = child[p];
15     child[p]=s;
16     sib[s]=p_c;
17 }
18 void init(){
19     for (int i=0;i<100;i++)
20         parent[i]=i;
21 }
22 int find_root(int u){
23     if (parent[u]==u)
24         return u;
25     else
26         return find_root(parent[u]);
27 }
28 void Union(int u,int v){
29     int p_u = find_root(u);
30     int p_v = find_root(v);
31     parent[p_v]=p_u;//顺序不能反,保证是后面的以前面的为祖先节点 
32 }
33 void tarjan(int u){
34     visit[u]=1;
35     for (int i=child[u];i;i=sib[i]){
36         tarjan(i);
37         Union(u,i);
38     }
39     int v_1 = a[0].u;
40     int v_2 = a[0].v;
41     if (visit[v_1]&&visit[v_2])
42         a[0].ans=find_root(v_1);
43 }
44 int main(){
45     init();
46     insert(1,2);
47     insert(1,3);
48     insert(2,4);
49     insert(2,5);
50     insert(3,6);
51     a[0].u=4;
52     a[0].v=3;
53     tarjan(1);
54     cout<<a[0].ans;
55     return 0;
56 }

 

标签:求解,祖先,child,int,fa,LCA,100,节点
From: https://www.cnblogs.com/johnsonstar/p/16728379.html

相关文章

  • 6th 2022/6/8 算法总结·LCA·倍增
    开头的话这个算法对于大部分图论无环图题都是必备的,应多多复习大概是对于两个点的公共祖先倍增众所周知,为了找到公共祖先,最暴力的算法就莫过于一个个往上跳,直至相遇而......
  • 实例84 二分法求解方程
    #include<stdio.h>#include<math.h>#include<malloc.h>#include<stdlib.h>doubleFunc(double);intBisectRoot(double,double,double,double,double*,int,in......
  • 实例85 牛顿迭代法求解方程
    #include<stdio.h>#include<math.h>#include<stdlib.h>intFunction(double,double*,double*);intNewton(double*,double,int);intFunction(x,f,dy)dou......
  • LCA&DSU&MST
    目录$\text{LCA}$时间复杂度树上差分最小瓶颈路树上路径相交无根树$\text{LCA}$小结$\text{DSU}$时间复杂度带权并查集扩展域连通时间戳断边时间戳小结\(\text{M......
  • FullCalendar日程管理控件(二)
    1css#calendar{max-width:1100px;margin:20pxauto;}.fc-license-message{display:none;}......
  • Webrtc Video Simulcast
    titleWebRTCVideoSimulcastClient->PeerConnection:SetLocalDescriptionPeerConnection->SdpOfferAnswerHandler:SetLocalDescriptionSdpOfferAnswerHandler->SdpOffer......
  • LCA
    定义LCA(LeastCommonAncestors),即最近公共祖先,指对于有根树TT的两个结点uu、vv,最近公共祖先LCA(T,u,v)LCA(T,u,v)表示一个结点xx,满足xx是uu、vv的祖先且xx......
  • 神经网络求解RL
    神经网络解决连续状态空间(或者状态很多的情况)经验回放使得神经网络更拟合打乱状态之间的关联固定q多加一个q预测值的神经网络一段时间才会改变以此固定q让强化学......
  • FullCalendar日程管理控件
    1官网https://fullcalendar.io/2参考文档https://www.cnblogs.com/cnsyear/p/13915215.html   注:参考文档的版本可能有问题,尽量使用官网中的英文文档,相对比较准......
  • C++迷宫问题求解(用队列实现)
    C++迷宫问题求解(用队列实现)19、迷宫问题求解(用队列实现)【任务】以一个m*n的长方阵表示迷宫。0和1分别表示迷宫中的通路和障碍。解迷宫通常用的是“穷举求解”方法,即从入......