图的连通性之强连通分量缩点建图

关 于 图 的 连 通 性


无向图:连通、连通图、连通分量、双连通分量

有向图:强连通、强连通图、强连通分量

 

 

 

 

    Tarjan算法求强连通分量:

本质上就是dfs,就是在 dfs 的过程维护两个数组 dfn[] 、low[] ,

其中,dfn[ x ] 表示 dfs 到 x 点的最小的时间,即时间戳,可知在一个dfs树的子树中,dfn[x] 值越小,则其越浅。

low[ x ]表示在 dfs 树中,x 点及其后代指出去的边,能返回到的最浅的点的时间戳,即使 x 能够指向的最浅的节点

 

 

右边就是左边这棵树的搜索树。

   5号点的 low 不能指向更浅的时间戳了,就指向自己,自己就是一个独立的强连通分量。

 

 

  6返回到了最浅的时间戳5(4号点),它的 low 值就是5

 

 

 

   3指向的最浅的dfs序就是1

  那么,就会发现,当一个节点的 dfn[x] 和 low[x] 相等时,以它为根的一些子树就是强连通分量。

 

 

接下来理解一下这个栈是怎么运行的

 一开始,先不断搜索,a、b、c、e分别入栈,这时还没开始回溯。

  

 

 

 

接下来,发现e 已经不能往下搜索了,就会判断这两个dfn[x] 和 low[x]是否相等,发现相等了,就把 e 节点弹出

 

 

 

接着,把 d 节点入栈,而 d 节点的下一条边指向一个更浅的节点 b ,其low[x]值 ( 以及 c 的low[x]值,因为它的儿子能指向更小的点 那么它也一定能指向更小的点 ) 就被更新为2 

 

 

 

 

然后,d不能往下搜索了,又开始回溯,发现 d、c 的 dfn[x] 和 low[x] 不相等,知道 b 才相等,则在栈中以 b为根的子树是一个强连通分量,

就从栈中一直弹出弹到根 b 为止,也就得到了由 b、c、d组成的强连通分量。

 

 

 

接下来就回溯到了 a,a再指向 f ,就把f入栈,f 又指向 g ,再回溯更新low[x]的值,最后就又得到了强连通分量

 

 这就是Tajan算法求强连通分量了。

 

 

得到强连通分量后,就可以缩点建图了。

 

 会缩成

 

 

 

 

 

 

遍历就是 1 - 2 边的两个顶点的SCC编号一样,那就不加边。而 3 - 4 边的两个顶点的SCC编号不同,那么就属于两个强连通分量,就要加上这条边。

例题:POJ-1236

https://vjudge.net/problem/POJ-1236

题目大意
问,对于一个DAG(有向无环图):
1.至少要选几个点,才能从这些点出发到达所有点
2.至少加入几条边,就能从图中任何一个点出发到达所有点

分析
先求DAG的强连通分量数,再缩点,可以用tarjan算法来做。
第一个问题:不难想到答案就是缩点之后入度为0的点的个数
第二个问题:设缩点后入度为0的个数是n,出度为0的个数是m,至少添加边的条数就是max(n,m)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 101
using namespace std;

int dfn[maxn],low[maxn],scc[maxn],stk[maxn],index=0,sccnum=0,top=0;

struct node{
    int to,next;
}edge[maxn*maxn];
int head[maxn*maxn];
int cnt=1; 
inline void add(int u,int v){
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++; 
}

inline void tarjan(int root){    //Tarjan算法求强连通分量缩点
    if( dfn[root] ) return;
    dfn[root] = low[root] = ++index;
    stk[++top] = root;
    for(int i=head[root]; i; i=edge[i].next){
        int v = edge[i].to;
        if( !dfn[v] ){   //如果v节点还没访问过
            tarjan(v);
            low[root] = min(low[root],low[v]);   //回溯回来更上儿子的进度(如果儿子能够
        }                                        //指向更浅的节点,那么它也一定行)
        else if( !scc[v] ){    //如果还在栈内
            low[root] = min(low[root],dfn[v]);
            // printf("low[%d]=%d\n", root,low[root]);

        }
    }    
    if(low[root]==dfn[root]){   //后代不能找到更浅的节点
        sccnum++;
        for(;;){
            int x = stk[top--];
            scc[x] = sccnum;
            // printf("%d %d\n", x,sccnum);
            if( x == root ) break;
        }
    }
}

int ind[maxn],out[maxn];

inline void init(){
    memset(head,-1,sizeof(head));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(scc,0,sizeof(scc));
    top=0,index=0,sccnum=0,cnt=1;
}
int main(){
    int n;
    init();
    scanf("%d",&n);
    int u,v;
    for(u=1; u<=n; u++){
        while(scanf("%d",&v) && v){
            add(u,v);
        }
    }
    for(int i=1; i<=n; i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    for(u=1; u<=n; u++){
        for(int i=head[u]; i; i=edge[i].next){
            v=edge[i].to;
            if(scc[u]!=scc[v]){
                // printf("%d %d \n", scc[v],scc[u]);
                ind[ scc[v] ]++;
                out[ scc[u] ]++;
            }
        }
    }
    int ans1=0,ans2=0;
    for(int i=1; i<=sccnum; i++){
        if(ind[i]==0) ans1++;
        if(out[i]==0) ans2++;
    }
    ans2 = max(ans1,ans2); 
    if(sccnum==1) printf("1\n0\n");
    else printf("%d\n%d\n", ans1,ans2);
}
View Code

 

posted @ 2019-08-30 23:51  *Zzz  阅读(483)  评论(0)    收藏  举报