該算法也是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]。
完成對子樹的搜索和詢問后,需將子樹根結點並入父結點所在集合,這也是整個算法的精妙所在。