1.主要写两个算法
1.1强连通分量之Korasaju
这个算法主要依赖的是图G与图G的转置强连通分量是一样的。
具体证明先不考虑了(既是强连通分量,正向可以到达,反向一定也可以到达)。
实现过程
1. dfs原图G,并且记录结束时间(后序)。后序越大,为相对的根节点。(先这样理解)
2. 给后序节点从大到小排序。
3. 依次取后序大的节点在G的转置图上进行dfs,跳过已标记的点。
每dfs一次,标记数+1
Hint:
说下遍历顺序的原因,G的转置图上,节点的相对关系变了,原图的相对根结点在新图上变成了相对子节点,由原图G可知,原图的相对根节点是一定可以搜到原图的相对子节点的。若是在G转置图上也能搜到(因为是反向),那么二者一定属于同一强连通分量。若两节点不属于同一强连通分量,则从当前的节点会搜到原图中的相对根节点却搜不回来。所以要从原图的相对根节点开始搜(即当前图的相对子节点)
4. 标记数即为强连通分量的个数,且标记数相同的为同一强连通分量。
模板:
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>edge[10010];
vector<int>rever[10010];
struct Node
{
int End,id;
}P[10010];//结束
int flag[10010];//着色
int vis[10010];//标记
int index,col;
int outdegree[10010];//出度
int indegree[10010];//出度
int num[10010];//压缩数量
bool cmp(Node a,Node b)
{
return a.End>b.End;
}
void dfs(int u)//原图G
{
index++;P[u].id=u;
vis[u]=1;
int l=edge[u].size();
for(int i=0;i<l;i++)
{
int k=edge[u][i];
if(!vis[k])
{
dfs(k);
index++;
}
}
P[u].End=index;
}
void reverdfs(int u)//G的转置
{
vis[u]=1;
flag[u]=col;
num[col]++;
int l=rever[u].size();
for(int i=0;i<l;i++)
{
int k=rever[u][i];
if(!vis[k])
{
reverdfs(k);
}
}
}
void init(int n)
{
for(int i=0;i<=n;i++)
{
edge[i].clear();
rever[i].clear();
}
memset(vis,0,sizeof(vis));
memset(flag,0,sizeof(flag));
memset(num,0,sizeof(num));
memset(outdegree,0,sizeof(outdegree));
memset(indegree,0,sizeof(indegree));
memset(P,0,sizeof(P));
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
init(n);
while(m--)//输入边
{
int a,b;
scanf("%d%d",&a,&b);
edge[a].push_back(b);
rever[b].push_back(a);
}
index=1,col=1;
for(int i=1;i<=n;i++)//防止图不连通
{
if(!vis[i])dfs(i);
}
sort(P+1,P+n+1,cmp);//按结束时间降序排序
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
if(!vis[P[i].id])
{
reverdfs(P[i].id);
col++;
}
}
for(int i=1;i<=n;i++)//缩图+统计出度入度
{
int l=edge[i].size();
for(int j=0;j<l;j++)
{
int a=i,b=edge[i][j];
if(flag[a]!=flag[b])
{
outdegree[flag[a]]++;
indegree[flag[b]]++;
}
}
}
//………………
}
return 0;
}
这个算法还有一个作用,就是求点的拓扑排序。反向dfs染色情况形成一种拓扑排序。
再说下拓扑排序;
假设把所有的强连通分量都各自看成一个点,假设从r 到s,r的后序大于s,有两种情况,(1) r与s