强联通分量:Kosarajus算法

以前接触过,也花了不少时间弄懂,不过当时只是写了一道题,代码也不知道放哪了,几个月下来忘得差不多了。

今日回顾,有温故而知新的感觉。

算法的证明与理解如下,具体的网上/书上都有(摘自《数据结构与算法分析》)

由于V是X在Gr的深度优先搜索树中的一个后裔,因此存在Gr中一条从X到V的路径,从而存在G中中一条从V到X的路径。此外,由于X是根节点,因此X从第一次深度优先搜索得到更高的后续编号。于是,在第一次深度优先搜索期间所有处理V的工作都在X的工作结束前完成。既然存在一条从V到X的路径,因此V必然是X在G的生成树中的一个后裔——否则V将在X之后结束。这意味着G中从X到V有一条路径,证明完成。

我写的代码很丑,不知道怎么命名反向边及相关操作,于是就在后面加了一个r。

下面是POJ的三道入门题,基本一样,第一道用邻接矩阵存储,后两道用邻接表存储,都是最简单的缩点然后求出入度有关的。具体的写在注释里了。

POJ 1236

{

http://www.cppblog.com/mythit/archive/2009/05/25/85718.html

这里写得不错,就复制一下吧



题目大意:N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。



具体算法:先用Korasaju Algorithm求出有向图所有的强连通分量,然后将所有的强连通分量缩成一个点(缩点),这样原来的有向图就缩成了一个DAG图(有向无环图);用2个数组分别记录新生成的DAG图中的每个顶点(包括原来的顶点和强连通分量的缩点)是否有出边和入边,最后遍历每个顶点,如果没有入边,则ans1++;如果没有出边,ans2++。最后所求即为ans1和max(ans1,ans2)。



犯错:第二次DFS顺序弄反了,另外第一个问题不能用输出强联通分量个数,因为不同强联通分量之间是可以传递的。



2011-07-14 09:50

}











#include <stdio.h>

#define MAXN 200



int g[MAXN][MAXN];

int gr[MAXN][MAXN];

int in[MAXN],out[MAXN],order[MAXN];

int belong[MAXN];

int vis[MAXN];

int count, num, i, j, k, n;



int max(int a, int b)

{

    return a > b ? a : b;

}



void init()

{

    int i, j;

    scanf("%d", &n);

    for (i = 1; i <= n; i++)

        while (scanf("%d", &j) && j)

        {

            g[i][j] = 1;

            gr[j][i] = 1;

        }

}



void dfs(int v)

{

    vis[v] = 1;

    int i;

    for (i = 1; i <= n; i++)

        if ((!vis[i]) && g[v][i])

            dfs(i);

    order[++num] = v;

}



void dfsr(int v)

{

    vis[v] = 1;

    belong[v] = count;

    int i;

    for (i = 1; i <= n; i++)

        if ((!vis[i]) && gr[v][i])

            dfsr(i);

}



void kosaraju()

{

    for (i = 1; i <= n; i++)

        if (!vis[i])

            dfs(i);

    memset(vis, 0, sizeof(vis));

    for (i = n; i >= 1; i--)

        if (!vis[order[i]])

        {

            count ++;

            dfsr(order[i]);

        }

}



void calc()

{

    int i, j, leaf = 0, root = 0;



    for (i = 1; i <= n; i++)

        for (j = 1; j <= n; j++)

            if (g[i][j] && belong[i] != belong[j])

            {

                in[belong[j]]++;

                out[belong[i]]++;

            }

    for (i = 1; i <= count; i++)

    {

        if (!in[i])

            root++;

        if (!out[i])

            leaf++;

    }

    if (count == 1)

        printf("1\n0\n");

    else

        printf("%d\n%d\n", root, max(root, leaf));

}



int main()

{

    init();

    kosaraju();

    calc();

    return 0;

}

POJ 2186

/*

    来自http://www.cppblog.com/RyanWang/archive/2009/02/26/74984.html



    题目简述:

    n头奶牛,给出若干个欢迎关系a b,表示a欢迎b,

    欢迎关系是单向的,但是是可以传递的。

    另外每个奶牛都是欢迎他自己的。

    求出被所有的奶牛欢迎的奶牛的数目。



    模型转换:

    N个顶点的有向图,有M条边(N≤10000,M≤50000)。

    求一共有多少个点,满足这样的条件:

    所有其它的点都可以到达这个点。



    算法:求强连通分量,计算每个分量的出度,如果只有一个为0则该强联通分量的节点个数就是答案,否则无解。



*/







#include <stdio.h>

#define MAXN 100000

#define MAXM 1000000



int e[MAXM],next[MAXM];

int er[MAXM],nextr[MAXM];

int g[MAXN],gr[MAXN];

int size, sizer;



int n, m, i, j, k, x, y, count, number;

int out[MAXN];

int order[MAXN];

int vis[MAXN];

int belong[MAXN];

int num[MAXN];



void insert(int x, int y)

{

    e[++size] = y;

    next[size] = g[x];

    g[x] = size;

}



void insertr(int x, int y)

{

    er[++sizer] = y;

    nextr[sizer] = gr[x];

    gr[x] = sizer;

}





void init()

{

    scanf("%d%d", &n, &m);

    for (i = 1; i <= m; i++)

    {

        scanf("%d%d", &x, &y);

        insert(x, y);

        insertr(y, x);

    }

}



void dfs(int v)

{

    vis[v] = 1;

    int p;

    for (p = g[v]; p; p = next[p])

        if (!vis[e[p]])

            dfs(e[p]);

    order[++number] = v;

}



void dfsr(int v)

{

    vis[v] = 1;

    belong[v] = count;

    int p;

    for (p = gr[v]; p; p = nextr[p])

        if (!vis[er[p]])

            dfsr(er[p]);

}



void kosaraju()

{

    for (i = 1; i <= n; i++)

        if (!vis[i])

            dfs(i);

    memset(vis, 0, sizeof(vis));

    for (i = n; i >= 1; i--)

        if (!vis[order[i]])

        {

            count++;

            dfsr(order[i]);

        }

}



int calc()

{

    int i, j, k, p, ans = 0;

    for (i = 1; i <= n; i++)

        num[belong[i]]++;

    for (i = 1; i <= n; i++)

        for (p = g[i]; p; p = next[p])

            if (belong[e[p]] != belong[i])

                out[belong[i]]++;

    for (i = 1; i <= count; i++)

    {

        if (ans && !out[i])

            return 0;

        if (!out[i])

            ans = num[i];

    }

    return ans;

}





int main()

{

    init();

    kosaraju();

    printf("%d\n", calc());

    return 0;

}

POJ 2553

/*

    题目出得比较恶心,下了一大堆定义。总之就是求所有满足这个条件的点:

    它能到达的点也能到达它。于是可以进行强连通分量缩点,

    只要该点满足所属的强连通分量的出度为0即为所求点。



    细节的错误犯了一些,有时候打错一个变量真的很难找啊。



    2011年7月14日14:01:57

*/



#include <stdio.h>

#define MAXN 100000

#define MAXM 1000000



int e[MAXM],er[MAXM],next[MAXM],nextr[MAXM];

int g[MAXN],gr[MAXN];

int order[MAXN],vis[MAXN],out[MAXN],belong[MAXN];



int m, n, i, j, k, x, y;

int count, num;

int size, sizer;



void insert(int x, int y)

{

    e[++size] = y;

    next[size] = g[x];

    g[x] = size;

}



void insertr(int x, int y)

{

    er[++sizer] = y;

    nextr[sizer] = gr[x];

    gr[x]  = sizer;

}



void dfs(int v)

{

    vis[v] = 1;

    int p;

    for (p = g[v]; p; p = next[p])

        if (!vis[e[p]])

            dfs(e[p]);

    order[++num] = v;

}



void dfsr(int v)

{

    vis[v] = 1;

    belong[v] = count;

    int p;

    for (p = gr[v]; p; p = nextr[p])

        if (!vis[er[p]])

            dfsr(er[p]);

}



void kosaraju()

{

    for (i = 1; i <= n; i++)

        if (!vis[i])

            dfs(i);

    memset(vis, 0,sizeof(vis));

    for (i = n; i >= 1; i--)

        if (!vis[order[i]])

        {

            count++;

            dfsr(order[i]);

        }

}





void init()

{

    memset(g, 0, sizeof(g));

    memset(gr, 0, sizeof(gr));

    memset(belong, 0, sizeof(belong));

    memset(order, 0, sizeof(order));

    memset(vis, 0, sizeof(vis));

    memset(out, 0, sizeof(out));

    size =  0;

    sizer = 0;

    num = 0;



    for(i = 1; i <= m; i++)

    {

        scanf("%d%d", &x, &y);

        insert(x, y);

        insertr(y, x);

    }

}









void calc()

{

    int i, p;



 //   for (i = 1; i <= n; i++)

 //       printf("%d ",belong[i]);

    for (i = 1; i <= n; i++)

        for (p = g[i]; p; p = next[p])

            if (belong[i] != belong[e[p]])

                out[belong[i]]++;

    for (i = 1; i <= n; i++)

        if (!out[belong[i]])

            printf("%d ",i);





    printf("\n");

}



int main()

{

    scanf("%d", &n);

    while (n != 0)

    {

        scanf("%d", &m);

        init();

        kosaraju();

        calc();

        scanf("%d", &n);

    }

    return 0;

}

你可能感兴趣的:(sar)