Tarjan算法
Tarjan算法是一个很著名的算法,其主要的目的是用来求有向图的强连通分量,什么是强连通分量呢? 简单点说就是存在一个点 v 1 v_1 v1能到点 v 2 v_2 v2,且 v 2 v_2 v2也能到 v 1 v_1 v1,那么 v 1 , v 2 v_1, v_2 v1,v2就构成一个强连通分量; 具体的定义参见百度。
Tarjan算法步骤
Tarjan算法采用的深度优先遍历的方式(DFS),需要两个数组dfn[]和low[],dfn数组记录结点在dfs搜索下的访问顺序, low记录结点能能追溯的最早的祖先; 算法同时还需要一个栈来记录当前已经访问过的结点。
算法一开始指定一个结点为根结点,然后从这个结点用dfs进行遍历整个图。 每次遍历一个结点是记录结点的dfs序,刚开始low的值等于dfs的值,在回溯的过程我们更新low的值,更新过程为 l o w [ u ] = m i n ( l o w [ u ] , l o w [ v ] ) low[u]=min(low[u],low[v]) low[u]=min(low[u],low[v]), v v v为 u u u的子结点,若当前结点被访问过则判断其是否在栈中,若在栈中,则同时也按上面的步骤进行更新。若结点不在栈中,则其也被访问过,则说明结点 v v v无法到达结点 u u u.
用来判断是否为一个连通分量的标准为 d f n [ u ] = = l o w [ u ] dfn[u]==low[u] dfn[u]==low[u],此时从栈顶到结点u之间的结点为一个强连通分量. 因为每个结点记录的是它和它子结点所能追溯到的最早的祖先,再回溯过程中会不断更新每个结点能追溯到的最早访问的祖先,当遇到 d f n [ u ] = l o w [ u ] dfn[u]=low[u] dfn[u]=low[u]时,则表示这个点的 l o w [ u ] low[u] low[u]已经被其他结点更新过了,所有 l o w [ i ] = = l o w [ u ] low[i]==low[u] low[i]==low[u]的结点在同一个连通分量。然后只要对栈中的结点进行染色标记就可以找出连通分量了。
C++模版
写得不太好,欢迎指出错误。
/*
有向图的强连通分量-Tarjan算法;
*/
#include<bits/stdc++.h>
using namespaec std;
typedef long long ll;
const int maxn=1e5+5;
int n,m;
int dfn[maxn],low[maxn],vis[maxn],color[maxn],Stack[maxn];
int top,deep,sum;
inline int min(int x,int y){
return x<y?x:y;
}
void Tarjan(int u){
dfn[u]=++deep; //记录结点的dfs序;
low[u]=deep; //记录结点最早能追溯的祖先;
vis[u]=1;
Stack[++top]=u; //把结放入栈中;
for(int i=0;i<v[u].size();i++){
if(!dfn[v[u][i]]){ //判断结点是否被遍历过;
Tarjan(v[u][i]);
low[u]=min(low[u],low[v[u][i]]);
}else{
if(vis[v[u][i]]){ //判断结点是否栈了;
low[u]=min(low[u],dfn[v[u][i]]);
}
}
}
if(dfn[u]==low[u]){ //找到一个强连通;
color[u]=++sum; //对连通分量内的点进行染色;
vis[u]=0;
while(Stack[top]!=u){
color[Stack[top]]=sum;
vis[Stack[top--]]=0;
}
top--;
}
}
vector<int> v;
int main(){
int n,m;
scanf("%d %d",&n,&m);
int x,y;
while(m--){
scanf("%d %d",&x,&y); //x到y的有向边;
v[x].push_back(y);
}
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
top=deep=sum=0;
for(int i=0;i<n;i++){
if(!dfn[i]){
Tarjan(i);
}
}
return 0;
}
新的开始,每天都要快乐哈!