tarjan算法求最近公共祖先

tarjian算法

LCA: LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点。也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点。还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离。

LCA算法有在线算法也有离线算法,所谓的在线算法就是实时性的,而离线算法则是要求一次性读入所有的请求,然后在统一得处理。而在处理的过程中不一定是按照请求的输入顺序来处理的。说不定后输入的请求在算法的执行过程中是被先处理的。

tarjan算法。这个算法是基于并查集和DFS的。离线算法。

现在我们来观察正在处理与x结点关联的询问时并查集的情况。由于一个结点处理完毕后,它就被归到其父结点所在的集合,所以在已经处理过的结点中(包括 x本身),x结点本身构成了与x的LCA是x的集合,x结点的父结点及以x的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[x]的集合,x结点的父结点的父结点及以x的父结点的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[father[x]]的集合……(上面这几句话如果看着别扭,就分析一下句子成分,也可参照右面的图)假设有一个询问(x,y)(y是已处理的结点),在并查集中查到y所属集合的根是z,那么z 就是x和y的LCA,x到y的路径长度就是 lv[x]+lv[y]-lv[z]*2 。累加所有经过的路径长度就得到答案。  现在还有一个问题:上面提到的询问(x,y)中,y是已处理过的结点。那么,如果y尚未处理怎么办?其实很简单,只要在询问列表中加入两个询问(x, y)、(y,x),那么就可以保证这两个询问有且仅有一个被处理了(暂时无法处理的那个就pass掉)。而形如(x,x)的询问则根本不必存储。  如果在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将可以认为是常数级的,整个算法也就是线性的了。

Tarjan作为离线off-line算法,在程序开始前,需要将所有等待询问的节点对提前存储,然后程序从树根开始执行TarjanLCA()。假如有如下一棵多叉树

 

根据TarjanLCA的实现算法可以看出,只有当某一棵子树全部遍历处理完成后,才将该子树的根节点标记为黑色(初始化是白色),假设程序按上面的树形结构进行遍历,首先从节点1开始,然后递归处理根为2的子树,当子树2处理完毕后,节点2, 5, 6均为黑色;接着要回溯处理3子树,首先被染黑的是节点7(因为节点7作为叶子不用深搜,直接处理),接着节点7就会查看所有询问(7, x)的节点对,假如存在(7, 5),因为节点5已经被染黑,所以就可以断定(7, 5)的最近公共祖先就是find(5).ancestor,即节点1(因为2子树处理完毕后,子树2和节点1进行了union,find(5)返回了合并后的树的根1,此时树根的ancestor的值就是1)。    有人会问如果没有(7, 5),而是有(5, 7)询问对怎么处理呢?我们可以在程序初始化的时候做个技巧,将询问对(a, b)和(b, a)全部存储,这样就能保证完整性。

 

 

如果要按顺序输出,在输入询问时打一个标记,假设开一个数组 f[][] , f[x][] = "顺序",

que[x][i]=y 是询问x,y,与x有关的第 i 个数是y,f[x][i] = "顺序" ,与x有关的第 i 个询问是的几个顺序,所以就可以表示输出的顺序了。

 模板

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<vector>
 5 #include<cstring>
 6 using namespace std;
 7 
 8 const int N = 10010;
 9 
10 vector<int>node[N];    //相连的边 
11 vector<int>que[N];  //询问 
12 int n,m,x,y;
13 int far[N],ance[N];    //祖先,集合 
14 int rank[N],ru[N];    //树的秩,入度 
15 bool vis[N];        //判断是否访问过 
16 
17 void init()  
18 {  
19     for(int i=0;i<=n;i++)  
20     {  
21         node[i].clear();  
22         que[i].clear();  
23         far[i] = i;
24         rank[i] = 1;
25     }
26 }  
27 
28 int find(int x)
29 {
30     return far[x]==x?x:far[x]=find(far[x]);    
31 }
32 
33 void unio(int a,int b)
34 {
35     a=find(a);  
36     b=find(b);  
37     if(a==b) return ;  
38     if(rank[a]<=rank[b])  
39     {  
40         far[a] = b;  
41         rank[b] += rank[a];  
42     }  
43     else   
44     {  
45         far[b] = a;
46         rank[a] += rank[b];  
47     }  
48 }
49 
50 void LCA(int root)  
51 {      
52     ance[root] = root;         //首先自成一个集合  
53     for(int i=0;i<node[root].size();i++)  
54     {
55         LCA(node[root][i]);        //递归子树  
56         unio(root,node[root][i]);    //将子树节点与根节点root的集合合并 
57         ance[find(root)] = root;    //合并后的集合的祖先为root 
58     }
59     vis[root] = true;
60     for(int i=0;i<que[root].size();i++)  
61     {  
62         if(vis[que[root][i]])    //如果另一节点已经访问过 
63         printf("%d和%d的最近公共祖先:%d\n", root,que[root][i],ance[find(que[root][i])]);
64     }
65 }  
66 
67 int main()
68 {
69     scanf("%d%d",&n,&m);
70     init();      //调试了好久,原来这里的初始化要放在输入n下面 
71     for(int i=1;i<n;i++)  
72     {  
73         scanf("%d%d",&x,&y);   //x->y有一条边
74         node[x].push_back(y);
75         //node[y].push_back(x);  
76         ru[y]++;  
77     }  
78     for(int i=1;i<=m;++i)
79     {
80         scanf("%d%d",&x,&y);  
81         que[x].push_back(y); 
82         que[y].push_back(x); 
83     }
84     for(int i=0;i<=n;++i)
85         if(ru[i]==0) LCA(i);    //寻找根节点 
86     return 0;
87 }

 

转载于:https://www.cnblogs.com/mjtcn/p/6852646.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tarjan算法是一种用于解最近公共祖(Least Common Ancestors,LCA)问题的离线算法算法的核心思想是利用深度优先搜索(DFS)和并查集(Union Find)来解决问题。 首先,我们从根节点开始遍历每一个节点,并将节点分为三类,用st[]数组表示。0代表还未被遍历,1代表正在遍历这个点,2代表已经遍历完这个点并且回溯回来了。这样的划分有助于确定节点的最近公共祖先。 在Tarjan算法中,我们一边遍历一边回应查询。每当遍历到一个节点时,我们查找与该节点相关的所有查询。如果查询中的节点已经被遍历完(即st[]值为2),我们可以利用已经计算好的信息来计算它们的最近公共祖先最近公共祖先的距离可以通过两个节点到根节点的距离之和减去最近公共祖先节点到根节点的距离来计算。 在Tarjan算法中,我们可以通过深度优先搜索来计算dist[]数组,该数组表示每个节点到根节点的距离。我们可以利用父节点到根节点的距离加上边的权值来计算每个节点到根节点的距离。 最后,我们可以通过并查集来操作st[]数组。当遍历完一个节点的所有子树后,将子树中的节点放入该节点所在的集合。这样,每个子树的节点的最近公共祖先都是该节点。 综上所述,Tarjan算法利用DFS和并查集来最近公共祖先问题。它的时间复杂度为O(n+m),其中n是节点数,m是查询次数。通过该算法,我们可以高效地解决最近公共祖先的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [最近公共祖先tarjan](https://blog.csdn.net/qq_63092029/article/details/127737575)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【模版】Tarjan离线算法最近公共祖先(LCA)](https://blog.csdn.net/weixin_43359312/article/details/100823178)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Tarjan算法最近公共祖先问题](https://blog.csdn.net/Yeluorag/article/details/48223375)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值