强联通分量: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;
}

posted on 2011-07-14 14:21  oa414  阅读(679)  评论(0编辑  收藏  举报

导航