我是从算法导论看起的:
定义:在一个有向图中,任意两个点都是互相可达的,则称为强连通图。
解法步骤:
1.先对每个节点dfs。计算出 每个节点的finishing time f[u]。
2.对图进行倒置处理。
3.对倒置图的每个节点,按照f【u】降序的顺序进行dfs。
4.输出在步骤3中dfs时建立的每棵树的节点。这些树即分别是强连通分支。
时间复杂度分析:
在给定图G的邻接表表示的情况下,建立G倒置图的时间复杂度为O(V+E)。
而dfs也是O(V+E),故整个时间复杂度是线性时间O(v+e).
对这个解法的理解关键在理解finishing time的特点,算法导论书上对其有关键推论。
根据上面算法写的粗糙代码:
数据:
8 14
1 2
2 5
2 6
2 3
3 7
3 4
4 3
4 8
5 1
5 6
6 7
7 6
7 8
8 8
代码:
#include<iostream> using namespace std; const int N=10,M=50; int n,m; int edgehead[N],edgeheadT[N]; struct Edge { int v,next; }edge[M],edgeT[M]; bool vis[N]; int d[N]; int f[N]; int k=1,kT=1; void addedge(int u,int v) { edge[k].v=v; edge[k].next=edgehead[u]; edgehead[u]=k++; } int stack[N]; int sp=0; void dfs(int now,int t)//第一次dfs { f[now]=t; for(int i=edgehead[now];i;i=edge[i].next) { int v=edge[i].v; if(!vis[v]) { vis[v]=true; dfs(v,f[now]+1); f[now]=f[v]+1; } } stack[sp++]=now; } void trans()//倒置 { for(int i=1;i<=n;i++) { for(int j=edgehead[i];j;j=edge[j].next) { int u=edge[j].v; edgeT[kT].v=i; edgeT[kT].next=edgeheadT[u]; edgeheadT[u]=kT++; } } } void dfs_print(int now)//第二次dfs输出 { for(int i=edgeheadT[now];i;i=edgeT[i].next) { int v=edgeT[i].v; if(!vis[v]) { vis[v]=true; dfs_print(v); } } printf("%d\n",now); } void solve() { int t=1; for(int i=1;i<=n;i++) { if(!vis[i]) { vis[i]=true; dfs(i,t); t=f[i]; } } trans(); memset(vis,0,sizeof(vis)); while(sp>0) { int now=stack[--sp]; if(!vis[now]) { vis[now]=true; dfs_print(now); printf("\n"); } } } int main() { freopen("in.txt","r",stdin); k=1; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int from,to; scanf("%d%d",&from,&to); addedge(from,to); } solve(); return 0; }
http://www.byvoid.com/blog/scc-tarjan/
推荐这个链接,是求强连通分量的tarjan算法。
下面是参照链接里的描述写的代码:
测试数据同上.
#include<iostream> #define min(a,b) (a<b?a:b) using namespace std; const int N=10,M=50; int n,m; int edgehead[N]; struct Edge { int v,next; }edge[M]; bool instack[N]; int dfn[N],low[N]; int k=1; void addedge(int u,int v) { edge[k].v=v; edge[k].next=edgehead[u]; edgehead[u]=k++; } int stack[N]; int sp=0; int index=0; void tarjan(int i) { dfn[i]=low[i]=++index;//这里的index是搜索每个顶点的时间点 instack[i]=true; stack[sp++]=i;//进栈 for(int j=edgehead[i];j;j=edge[j].next) { int v=edge[j].v; if(!dfn[v])//如果还未访问 { tarjan(v); low[i]=min(low[i],low[v]);//因为v是i的树枝子节点。如果low[v]<low[i]则说明low[v]可以追溯到i的树上层节点。否则low[i]值不变。 } else if(instack[v])//如果已经在栈中 { low[i]=min(low[i],dfn[v]);//这里我的理解是一种时间等价的思想,就是既然我是你的树枝节点,而且我又可以访问到你,则外界访问咱们这个树枝的时候访问到我和访问到你的时间就应该是相等。即low其实记录的是最小访问时间。 } } if(dfn[i]==low[i])//如果最小访问时间和访问到的时间相等,则自己就是自己这棵树的根节点。所以就可以开始输出了。 { int ans; do { ans=stack[--sp]; printf("%d ",ans); instack[ans]=false; }while(ans!=i); printf("\n"); } } void solve() { int i; memset(dfn,0,sizeof(dfn)); for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } } int main() { freopen("in.txt","r",stdin); k=1; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int from,to; scanf("%d%d",&from,&to); addedge(from,to); } solve(); return 0; }