kosaraju算法求强连通分量

本文深入解析强连通分量的概念及其在图论中的应用,通过两个具体例子展示了Kosaraju算法求解强连通分量的过程,并详细说明了算法步骤与实现细节。此外,文章还探讨了如何利用此算法解决实际问题,如篮球队伍的分组与传话游戏的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是强连通分量?在这之前先定义一个强连通性(strong connectivity)的概念:有向图中,如果一个顶点s到t有一条路径,t到s也有一条路径,即s与t互相可达,那么我们说s与t是强连通的。那么在有向图中,由互相强连通的顶点构成的分量,称作强连通分量。

一:对于kosaraju算法,这是一个比tarjan较复杂的算法,但是因为一本通上介绍了这种算法,我就找几个题目练习了一下。

    kosaraju算法:可以求出强连通分量的个数,还可以对分属于不同强连通分量的点进行标记。   

                算法描述:(1):第一次对图G进行DFS遍历,并在遍历的过程中,记录每一个点的退出顺序。

                              (2):倒转每一条边的方向,构造一个反图G1,然后按照退出的顺序的逆序对反图进行第二次DFS遍历,

                              (3):每一次遍历得到的那些点属于同一个强连通分量。

 因为这就是一个不变的模板,所以直接背过就是最好了

例题:

1298. 通讯问题(来源:sojs.tk)

★   输入文件:jdltt.in   输出文件:jdltt.out   简单对比
时间限制:1 s   内存限制:128 MB

【题目描述】

一个篮球队有n个篮球队员,每个队员都有联系方式(如电话、电子邮件等)。但并不是每个队员的联系方式都公开,每个队员的联系方式只有一部分队员知道。问队员可以分成多少个小组,小组成员之间可以相互通知(包括一个队员一个组,表示自己通知自己)。

【输入格式】

 

输入文件有若干行

第一行,一个整数n,表示共有n个队员(2<=n<=100)

下面有若干行,每行2个数a、b,a、b是队员编号,表示a知道b的通讯方式。

 

【输出格式】

 

输出文件有若干行

第一行,1个整数m,表示可以分m个小组,下面有m行,每行有若干个整数,表示该小组成员编号,输出顺序按编号由小到大。

 

【样例输入】

 【样例输出】

8

1 2 3

4 6

5

7 8

9

10

11

12

 这道题目的输出顺序是很坑的。

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define N 120
struct Edge{
    int u,v,last;
}edge1[N],edge2[N];
int t=0;
stack<int>s;
int ans[N][N];
int pp=0;
int head1[N]={0},head2[N]={0};
bool visit[N]={0};
int n;
void input()
{
    scanf("%d",&n);
    int a,b;
    while(scanf("%d%d",&a,&b)==2)
    {
        ++t;
        edge1[t].u=a;
        edge1[t].v=b;
        edge1[t].last=head1[a];
        head1[a]=t;
        edge2[t].u=b;
        edge2[t].v=a;
        edge2[t].last=head2[b];
        head2[b]=t;
        
    }
    
}
void dfs1(int k)
{
    visit[k]=true;
    for(int l=head1[k];l;l=edge1[l].last)
    {
        if(!visit[edge1[l].v])
        {
            dfs1(edge1[l].v);
        }
    }
    s.push(k);
}
void dfs2(int k)
{
    ans[pp][0]++;
    ans[pp][ans[pp][0]]=k;
    visit[k]=true;
    for(int l=head2[k];l;l=edge2[l].last)
    {
        if(!visit[edge2[l].v])
        {
            dfs2(edge2[l].v);
        }
    }
}
int kosaraju()
{
    while(!s.empty())
    s.pop();
    memset(visit,false,sizeof(visit));
    for(int i=1;i<=n;++i)
    {
        if(!visit[i])
        dfs1(i);
    }
    memset(visit,false,sizeof(visit));
    while(!s.empty())
    {
        int k=s.top();
        s.pop();
        if(!visit[k])
        {
            pp++;
            dfs2(k);
        }
    }
    return pp;
}
int main()
{
    freopen("jdltt.in","r",stdin);
    freopen("jdltt.out","w",stdout);
    input();
    pp=0;
    printf("%d\n",kosaraju());
    for(int i=1;i<=pp;++i)
    sort(ans[i]+1,ans[i]+ans[i][0]+1);
    bool flag[N]={0};
    for(int i=1;i<=pp;++i)
    {
        int q=0;
        int maxx=(1<<30);
        for(int j=1;j<=pp;++j)
        {
            if(maxx>ans[j][1]&&!flag[j])
            {
                maxx=ans[j][1];
                q=j;
            }
        }
        flag[q]=true;
        for(int j=1;j<=ans[q][0];++j)
        printf("%d ",ans[q][j]);
        printf("\n");
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
View Code

 例题二:

619. [金陵中学2007] 传话(来源cojs.tk)

★☆   输入文件:messagez.in   输出文件:messagez.out   简单对比
时间限制:1 s   内存限制:128 MB

[问题描述]

兴趣小组的同学来自各个学校,为了增加友谊,晚会上又进行了一个传话游戏,如果 a 认识 b ,那么 a 收到某个消息,就会把这个消息传给 b ,以及所有 a 认识的人。

如果 a 认识 b , b 不一定认识 a 。

所有人从 1 到 n 编号,给出所有“认识”关系,问如果 i 发布一条新消息,那么会不会经过若干次传话后,这个消息传回给了 i , 1<=i<=n 。

[输入文件]

输入文件 message.in 中的第一行是两个数 n(n<1000) 和 m(m<10000) ,两数之间有一个空格,表示人数和认识关系数。

接下来的 m 行,每行两个数 a 和 b ,表示 a 认识 b 。 1<=a, b<=n 。认识关系可能会重复给出,但一行的两个数不会相同。

[输出文件]

输出文件 message.out 中一共有 n 行,每行一个字符 T 或 F 。第 i 行如果是 T ,表示 i 发出一条新消息会传回给 i ;如果是 F ,表示 i 发出一条新消息不会传回给 i 。

[输入样例]

4 6
1 2 
2 3 
4 1 
3 1 
1 3 
2 3

[输出样例]




F

思路:仍然是求强连通分量,只是要判断每一个人是否在一个人数>=2的强连通分量中,如果是就满足题目条件

代码:

#include<iostream>
using namespace std;
#include<cstdio>
int n,m,a,b;
struct Edge{
    int u,v,last;
};
int t=0;
#include<stack>
#define M 10020
Edge edge1[M],edge2[M];
#define N 1008
stack<int> s;
int head1[N],head2[N];
bool visit[N];
#include<algorithm>
#include<cstring>
bool flag[N]={0};
int ans[N][N];
int calc=0;
void input()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&a,&b);
        ++t;
        edge1[t].u=a;
        edge1[t].v=b;
        edge1[t].last=head1[a];
        head1[a]=t;
        edge2[t].u=b;
        edge2[t].v=a;
        edge2[t].last=head2[b];
        head2[b]=t;
        
    }
}
void dfs1(int k)
{
    visit[k]=true;
    for(int p=head1[k];p;p=edge1[p].last)
    {
        if(!visit[edge1[p].v])
        dfs1(edge1[p].v);
    }
    s.push(k);
}
void dfs2(int k)
{
    int flat=calc;
    visit[k]=true;
    ans[calc][0]++;
    ans[calc][ans[calc][0]]=k;
    for(int p=head2[k];p;p=edge2[p].last)
        if(!visit[edge2[p].v])
        {
            
            dfs2(edge2[p].v);
        }
    if(ans[flat][0]>1)/*判断每一个人是否在一个人数>=2的强连通分量*/
    flag[k]=true;
}
void kosaraju()
{
    while(!s.empty())
    s.pop();
    memset(visit,false,sizeof(visit));
    for(int i=1;i<=n;++i)
    {
        if(!visit[i])
        dfs1(i);
    }
    memset(visit,0,sizeof(visit));
    while(!s.empty())
    {
        int k=s.top();
        s.pop();
        if(!visit[k])
        {
            calc++;
            dfs2(k);
        }
    }
}
int main()
{
    freopen("messagez.in","r",stdin);
    freopen("messagez.out","w",stdout);
    input();
    kosaraju();
    for(int i=1;i<=n;++i)
    if(flag[i])
    printf("T\n");
    else printf("F\n");
    fclose(stdin);
    fclose(stdout);
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/c1299401227/p/5400344.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值