Kosaraju 算法
两遍DFS,两个vector数组(存边和反向边),一个栈
-
第一遍DFS:
从第一个点出发(其实是都可以,按顺序来呗,方便),进行正向边的DFS,当某一个点没有下一个点可去,或者所有下一个点都去过,那就将该点放入栈中并回溯,直到回到起始点,由于从1开始不一定可以DFS遍历完所有点,因此向后找没有走过的点,再次DFS,直到遍历完所有点。
(源自bilibili_up主Biosas)
第一遍DFS得到所有点的入栈顺序。 -
第二遍DFS:
从栈顶出发(取出),进行反向边DFS,和正向相似。 -
DFS第一遍找所有环,但这些环上可能“节外生枝”,第二遍就是一个减枝的过程,因为环可以出枝,但是反向变之后,环还是环,但是从环没办法到枝上了。
-
洛谷B3609
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
const int maxm=100010;
int vis[maxn],temp[maxn];
int n,m,cnt=0,num=0;
stack<int>stk;
vector<int>G[maxn];//正向边
vector<int>G_t[maxn];//反向边
vector<int>ans[maxn];//依据题目用到的,存储一个强连通分量的每一个节点
void dfs1(int x)
{
vis[x]=1;
for(int i=0;i<G[x].size();i++)
{
int y=G[x][i];
if(!vis[y])
{
dfs1(y);
}
}
stk.push(x);//回溯到x(for循环完了),入栈
}
void dfs2(int x)
{
vis[x]=1;
for(int i=0;i<G_t[x].size();i++)
{
int y=G_t[x][i];
if(!vis[y])
{
//temp[num++]=y;//忽略
ans[num].push_back(y);//存第num个强连通分量的点
dfs2(y);
}
}
}
void kosaraju()
{
int con=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
if(!vis[i])dfs1(i);
}
memset(vis,0,sizeof(vis));
while(!stk.empty())
{
// memset(temp,0,sizeof(temp));
// num=0;
int u=stk.top();
stk.pop();
if(!vis[u])
{
// temp[num++]=u;
num++;
ans[num].push_back(u);
dfs2(u);
con++;
// for(int i=0;i<num;i++)cout<<temp[i]<<" ";
// cout<<endl;
}
}
memset(vis,0,sizeof(vis));
cout<<con<<endl;
// for(int j=1;j<=num;j++)
// {
// sort(ans[j].begin(),ans[j].end());
// for(int i=0;i<ans[j].size();i++)
// {
// cout<<ans[j][i]<<" ";
// }
// cout<<endl;
// }
for(int i=1;i<=num;i++)sort(ans[i].begin(),ans[i].end());
//对vector容器的num行排序
for(int i=1;i<=n;i++)//题目的输出要求,按序按大小输出
{
int flag=0;
if(!vis[i])
{
for(int j=1;j<=num;j++)
{
for(int k=0;k<ans[j].size();k++)
{
if(i==ans[j][k])flag=1;
break;
}
if(flag)
{
for(int k=0;k<ans[j].size();k++)
{
vis[ans[j][k]]=1;
cout<<ans[j][k]<<" ";
}
cout<<endl;
break;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
G[x].push_back(y);
G_t[y].push_back(x);
}
kosaraju();
return 0;
}