Kosaraju处理强连通分量

强连通: 如果有向图中存在一个点集 使得其中任意两个点都可以相互到达, 那么这个集合就是强连通的

强连通分量: 如果一个强连通的集合是尽可能大的集合 那么可以称为强连通分量

任意有向图都可以分解成若干个不相干的强连通分量, 把分解之后的强连通分量缩成一个顶点, 就得到了一个有向无环图

Kosaraju

这个算法可以计算出一个有向图的强连通分量

基于两个原理

  • 对于任意有向图, 如果反转它的所有边的方向, 那么得到的新图的强连通分量不变
  • 对原图 G 和反图 rG 各做一次 DFS,可以确定 强连通分量的 数量.

过程:

  • 对原图 G 做一次 DFS 是为了确定点的先后顺序。在DFS过程中,把递归到最底层的那个点标记为最小,然后回退过程中,其他点的标记逐个递增。即优先级小的点先存,优先级大的点后存。
  • 对反图 rG 做一次 DFS ,顺序从标记最大的点开始到最小的点。为什么顺序要相反?因为在反图中,方向相对于原图都相反了,优先级大的点由出度多,变成了入度多,那么 DFS 过程中就会被反边堵住,也就是说,此时能搜索到的点都是同个强连通分量的点。按照这个顺序依次搜索下去
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 100010;
vector<int> G[N],rG[N];
vector<int> S;
// 存第一次dfs1()的结果,即标记点的先后顺序,优先级小的点先进
int vis[N];    // vis[i]标记第一次dfs1()点i是否访问过
int sccno[N];  // sccno[i]标记点i属于第几个强连通分量,同时记录dfs2()过程中点i是否访问过
int cnt;    //cnt表示强连通分量的个数
void dfs1(int u){
    if(vis[u])  return;
    vis[u] = 1;
    for(int i=0; i<G[u].size(); i++)
        dfs1(G[u][i]);
    S.push_back(u); //记录点的先后顺序,按照拓扑排序,优先级大的放在S的后面
}

void dfs2(int u){
    if(sccno[u])    return;
    sccno[u] = cnt;
    for(int i=0; i<rG[u].size(); i++)
        dfs2(rG[u][i]);
}
void Kosaraju(int n) {
    cnt = 0;
    S.clear();
    memset(vis,0,sizeof(vis));
    memset(sccno,0,sizeof(sccno));
    for(int i=1; i<=n; i++) //搜索所有点
        dfs1(i);
    for(int i=n-1; i>=0; i--){
        if(!sccno[S[i]]){
            cnt++;
            dfs2(S[i]);
        }
    }
}
int main() {
    int n,m,u,v;
    cin>>n>>m;
    for(int i=0; i<n; i++) {
        G[i].clear();
        rG[i].clear();
    }
    for(int i=0; i<m; i++) {
        cin >> u >> v;
        G[u].push_back(v);  // 原图
        rG[v].push_back(u); // 反图
    }
    Kosaraju(n);
    cnt==1? cout<<"Yes"<<endl:cout<<"No"<<endl;
    return 0;
}
posted @ 2025-08-11 20:11  Chen1098  阅读(15)  评论(0)    收藏  举报