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 }

 

posted @ 2017-05-14 15:36  MJT12044  阅读(754)  评论(0编辑  收藏  举报