关于强连通分量浅谈

强连通分量由美国计算机科学家 Robert Tarjan 提出。

Tarjan

职业:计算机科学家

主要成就:设计了求解的应用领域的许多问题的广泛有效的算法和数据结构等,1986年获得图灵奖.

简介:Robert Tarjan,计算机科学家,以LCA、强连通分量等算法闻名。他拥有丰富的商业工作经验,1985年开始任教于普林斯顿大学。

事实上上述并没有什么用。如果真的想知道就上百度百科吧。

一个很好的图论工具

分割线


普及几个强连通分量的定义

强连通: 在一个有向图G里,设两个点 a b 发现,由a有一条路可以走到b,由b又有一条路可以走到a,我们就叫这两个顶点(a,b)强连通

强连通图: 如果 在一个有向图G中,每两个点都强连通,我们就叫这个图为强连通图。

强连通分量:在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就叫这个子图叫做 强连通分量


1.现在是有向图的Tarjan。

先来看个图。

48108.png

由上述定义知:

子图1,2,3构成一个强连通。

点4独立为一个强连通。

点5也是一个独立的强连通。

相信现在的你已经知道强连通分量究竟是什么了。

然而一个重要的问题产生了:

怎么求它呢???


dfs

那么怎么dfs呢?

首先需要三个数组:

1.dfn[Max];(存储时间戳)

2.low[Max];(存储在它之前的最小时间戳)

3.cast[Max];(标记它所在的强连通)

48108.png

还是刚才的图。

开始时dfn==low;

从1开始(入栈),先搜点2(入栈);

点2再搜点3(入栈),点3搜到了点1,这是点1正在栈中(更新点3的low值;

回溯更新点2的low,再回溯更新点1的low(当然还是原来的low,点1的dfn==low;

此时1搜到4(入栈),点4搜到点5;

点5开始纠结,因为他搜不到别人了,只好老老实实的被使用自己的low;

于是点5的dfn==low成为了一个强连通(出栈);cast[5]=1;

点4也由于点5的low影响不到他,dfn==low,也成为了一个强连通(出栈)。cast[4]=2;

回溯到点1,发现点5已经被访问,而且已经出栈,便不用理他。

此时dfn[1]==low[1]。

开始大批出栈

1出栈,2出栈,3出栈。cast全为3;

成功

那么看一段代码吧:

void tarjan(int x)
{
    s.push(x);
    ins[x]=1;
    inx++;
    dfn[x]=low[x]=inx;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        if(dfn[e[i].to]==0)
        {
            tarjan(e[i].to);
            low[x]=min(low[x],low[e[i].to]);
        }
        else if(ins[e[i].to]==1)low[x]=min(low[x],dfn[e[i].to]);
    }
    int v=100000;
    if(dfn[x]==low[x])
    {
        gs++;
        while(x!=v)
        {
            v=s.top();
            s.pop();
            ins[v]=0;
            cast[v]=gs;
        }
    }
}

无向图?

2.无向图的强连通分量。

由于无向图的边可以双向访问,那么只要访问一条边就行了。

看一下代码吧。

void tarjan(int x)
{
    int u;
    inx++;
    dfn[x]=low[x]=inx;
    s.push(x);
    ins[x]=1;
    for(int i=head[x];i!=-1;i=e[i].next)
    if(v[i]==0)
    {
        v[i]=v[i%2==0?i-1:i+1]=1;
        u=e[i].to;
        if(dfn[u]==0)
        {
            tarjan(u);
            low[x]=min(low[x],low[u]);
        }
        else if(ins[u]==1)low[x]=min(low[x],dfn[u]);
    }
    if(dfn[x]==low[x])
    {
        gs++;
        u=Max;
        while(u!=x)
        {
            u=s.top();
            s.pop();
            ins[u]=0;
            cast[u]=gs;
            //printf("%d %d\n",u,gs);
        }
        
    }
    
}

3.割桥。

定义:在一个无向图中,如果删掉边 x 后图的连通块数量增加,则称点 x 为图的割桥。

我的算法和别的博客不一样。如果有问题可以@我。

连接两个强连通的边是割桥。

貌似不用证明。

代码就同无向图强连通分量了。


4.割点!!!

定义:在一个无向图中,如果删掉点 x 后图的连通块数量增加,则称点 x 为图的割点。

洛谷题目P3388

17909.png

开始思路为割桥上的点为割点,后来证明的确正确。 不过可惜的是他的逆定理错了(gg了),不过数据很弱以至于得了90分。侥幸

看看下面恶毒地让我90的图吧

47800.png

图中无割桥,但点3却是割点,貌似无法解决。

怎样解决???

另一种思路诞生了: 如果u点的子节点为v,v点他能返回的最老祖先比u点年轻或一样(即dfn[u]值<=low[v]),那么如果删去u点,那么v以下的点就会与v以上的点失去联系,就会产生新的连通块(实质是在我的原来思路上多了一个判断

也就是说如果在我们的搜索树上有一个点只有树边与祖先相连,而没有反向边连回祖先节点的话,那么它就是割点。就是没有这样的边 轻微地客串的一个自己的题解,不要举报

代码完整的了

#include<cstdio>
#include<algorithm>
#include<stack>
#include<cstring>
#define Max 1000000+199
using namespace std;
int n,m,dfn[Max]={0},low[Max],cast[Max],ins[Max],inx=0,head[Max],v[Max]={0},cnt=0,gs=0,cd[Max]={0};
stack<int> s;
struct edge
{
    int c,to,next;
}e[Max];
void adde(int a,int b)
{
    cnt++;
    e[cnt].to=b;
    e[cnt].c=a;
    e[cnt].next=head[a];
    head[a]=cnt;
    cd[a]++;
}
int ans=0,gd[Max]={0};
void tarjan(int x,int fa)
{
    int u,sk=0;
    inx++;
    dfn[x]=low[x]=inx;
    s.push(x);
    ins[x]=1;
    for(int i=head[x];~i;i=e[i].next)
    {   
        u=e[i].to;
        if(dfn[u]==0)
        {
            tarjan(u,fa);
            if(low[u]>=dfn[x]&&x!=fa)gd[x]=1;
            v[i]=v[i%2==0?i-1:i+1]=1;
            low[x]=min(low[x],low[u]);
            if(x==fa)sk++;
        }
        else if(ins[u]==1&&v[i]==0)v[i]=v[i%2==0?i-1:i+1]=1,low[x]=min(low[x],dfn[u]);
    }
    if(dfn[x]==low[x])
    {
        gs++;
        u=Max;
        while(u!=x)
        {
            u=s.top();
            s.pop();
            ins[u]=0;
            cast[u]=gs;
            //printf("%d %d\n",u,gs);
        }

    }
    if(x==fa&&sk>=2)gd[x]=1;
}
int main()
{
    memset(cd,0,sizeof(cd));
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        adde(a,b);
        adde(b,a);
        //printf("%d",v[i]);
    }
    for(int i=1;i<=n;i++)
    if(dfn[i]==0)tarjan(i,i);
    for(int i=1;i<=n;i++)
    {
        if(gd[i]==1)ans++;
    }
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
    {
        if(gd[i]==1)printf("%d ",i);
    }
    return 0;
}

评论区可以表白


参考文献:

1.https://baike.baidu.com/item/tarjan/8970498?fr=aladdin

2.https://www.luogu.org/blog/zwp/solution-p3388
(真的是我的题解)

转载于:https://www.cnblogs.com/zwp2004/p/10346588.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值