强连通分量


前置知识:链式前向星

概念:存储带权值边的的数据结构,是邻接表的数组实现;head[i]表示以i为起点的最后一条边的编号,next表示相同起点的上一条边的编号。edge[j]即存储边的结构体数组数组,其中结构体含有终点,权值、以及next。

具体代码实现:

#include<bits/stdc++.h>//链式前向星
#define ll long long
#define maxn 20000
#define INF 0x3f3f3f3f
using namespace std;
struct Edge{
    int to;
    int val;
    int next;
}edge[maxn];//next记录上一个有相同起点的边
int head[maxn];//head[i]记录起点为i的最后一条边
int tot=0,n,m;//tot是边的编号
void addedge(int u,int v,int w)
{
    edge[++tot].to=v;
    edge[tot].val=w;
    edge[tot].next=head[u];//记录head[u]更新前最新的一条边
    head[u]=tot;
}
int main()
{
    for(int i=1;i<=n;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
}

强连通分量:

  • 概念:在一个有向图中,若该有向图中的任意两点可以互达,则该图强连通强连通分量就是一个非强连通有向图中的最大强连通子图。

求强连通分量的方法:

kosaraju算法:

具体实现思路:

  1. 以任意一个顶点开始循环遍历每个顶点,每趟循环做一次dfs遍历走的到且未访问的点,每次dfs在回溯前将顶点压入栈内或给顶点一个靠前的编号。


反转图:即顶点不变,有向图的中的方向倒转后的图,如A->B变为B->A


  1. 第二次dfs,从点的栈顶开始遍历,其中从每个点做一次反转图的dfs遍历,每次dfs遍历能走到且未访问的点,每次dfs能访问到的点属于同一个强连通分量。

证明:
  • 该算法能得到强连通分量:

已知第一次dfs,以一个点x为起点,在x之后被遍历到的点y也即栈中x底下的点,要么和x属于同一趟dfs,要么属于前几趟dfs,如果是在在后几趟dfs中,y一定在x的顶端,故栈中在x底下的点只可能是和x同一趟dfs或前几趟的点的点;同时还能意会到,前几趟dfs过的点要么是毫不相干的点,要么是保证一定有一条路线从x点能遍历到栈中在x底下的点。

第二次dfs从栈顶往栈底遍历,保证先遍历的点x在栈中底下的点y一定要么互不相干,要么已经能从x到y有路线。则dfs遍历的时候,一定只有可能遍历到从x到y有路线的点,若遍历到了,说明y->x也有路线,那么一定是属于同一个强连通分量内的

假设有两个点强连通,但未被计入同一个强连通分量中,可知第一次dfs,假设x先被遍历到,那么y一定在x的栈底,那么第二次dfs,y一定没有被遍历到,已知dfs会遍历所有在x栈底且能与x互达的的点,结论不成立,则一定能求出强连通分量。

代码实现:

#include<bits/stdc++.h>//scc——强连通分量
#define ll long long
#define maxn 20000
#define INF 0x3f3f3f3f
using namespace std;
struct Edge{
    int to;
    int val;
    int next;
}edge1[maxn],edge2[maxn];//next记录上一个有相同起点的边
int head1[maxn],head2[maxn];//head[i]记录起点为i的最后一条边
int vis1[maxn]={0},vis2[maxn]={0};//方便dfs记录某个点是否已经遍历过
int trace[maxn];//按照搜索到的点先后顺序排列编号从1到n
int belong[maxn];//每个点属于哪个强连通分量编号从1到cnt2
int cnt1=0,cnt2=0,tot1=0,tot2=0,n,m;//tot是边的编号,cnt2保存强连通分量个数,cnt1保存边的个数
void addedge(int u,int v)
{
    edge1[++tot1].to=v;
    edge1[tot1].next=head1[u];//记录head[u]更新前最新的一条边
    head1[u]=tot1;//记录正图

    edge2[++tot2].to=u;
    edge2[tot2].next=head2[v];
    head2[v]=tot2;//记录反图
}
void dfs1(int u)
{
    vis1[u]=1;
    for(int i=head1[u];~i;i=edge1[i].next)
    if(!vis1[edge1[i].to])
    dfs1(edge1[i].to);

    trace[++cnt1]=u;
}
void dfs2(int u)
{
    vis2[u]=1;
    belong[u]=cnt2;
    for(int i=head2[u];~i;i=edge2[i].next)
    if(!vis2[edge2[i].to])
    dfs2(edge2[i].to);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++)
    head1[i]=head2[i]=-1;
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    memset(vis1,false,sizeof(vis1));
    memset(vis2,false,sizeof(vis2));
    for(int i=1;i<=n;i++)
    if(!vis1[i])
    dfs1(i);
    for(int i=cnt1;i>=1;i--)
    if(!vis2[trace[i]])
    {
        cnt2++;
        dfs2(trace[i]);
    }

    //输出
    printf("%d\n",cnt2);
    memset(vis1,0,sizeof(vis1));
    for(int i=1;i<=n;i++)
    if(!vis1[i])
    {
        for(int j=i;j<=n;j++)
        if(belong[j]==belong[i])
        {
            printf("%d ",j);
            vis1[j]=1;
        }
        puts("");
    }
    system("pause");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值