该算法也是tarjan发现的,故也叫tarjan算法。
这个算法的主体还是dfs,先看算法框架:
void make_set(int i)
{
p[i]=i;
}
int find_set(int i)
{
if(i!=p[i]) p[i]=find_set(p[i]);
return p[i];
}
union_set(int i,int j)
{
i=find_set(i),j=find_set(j);
p[j]=i;
}
//tarjan算法主体
void dfs(int u)
{
int i,v;
make_set(u);
for(i=0;i<g[u].size();i++)
{
v=g[u][i];
if(p[v]==-1)
{
dfs(v);
union_set(u,v);
}
}
for(v=0;v<n;v++)
{
if(p[v]!=-1)
{
lca[u][v]=lca[v][u]=find_set(v);
}
}
}
前3个是并查集的函数,这里就不分析了,主要分析dfs:
当dfs到某个结点时,该结点自成一个集合,即p[u]=u,这个数组同时还能作为dfs的标记,当完成某棵子树的搜索后,假设该子树的根为u,任意其他已经标记过的结点v,
若v是u的祖先,则可以肯定以v为根的子树的搜索尚未完成,所以v仍然自成一个集合,此时lca(u,v)=p[v]=v;
若v是u的子孙结点,则可以肯定以v为根的子树的搜索已经完成,v已经被并入u所在的集合,所以lca(u,v)=p[v]=u;
若v是u的兄弟结点或其兄弟结点的子孙结点,设u的父亲结点为w,则可以肯定以v为根的子树的搜索已经完成,但以w为结点的子树的搜索尚未完成,所以v已经并入w所在集合,lca(u,v)=p[v]=p[w]=w;
终上所述,每次完成以u为根的子树的dfs时,对于其他已经标记过的结点v,lca(u,v)=p[v]。
完成对子树的搜索和询问后,需将子树根结点并入父结点所在集合,这也是整个算法的精妙所在。