图论及其应用——连通性问题

  我们这篇文章将会开始讨论关于图的连通性的问题。
  对于图的连通性问题,有着怎样的实际应用呢?其实很明显的一点就是社交网络、朋友圈当中的应用,后台可以根据一个连通分量(在实际的模型可以认为有着某些共性)当中其他用户的一些数据,来推测该连通分量下某一个用户可能感兴趣的信息。
  首先,我们给出图的强连通性的定义,对于图G<V,E>,如果其子图G<V',E'>满足对于任意vi、vj∈V',vi、vj之间总有一条通路,那么G'则称作G的一个强连通分量。
  目前对于一个图的最大强连通分量的求解,比较常见的是Kosaraju算法、Tarjan算法和Garbow算法,它们统称为SCC(strongly connected components)算法。
  下面我们来介绍Kosaraju算法。
  首先,我们要知道Kosaraju算法基于一条很重要也很显然的定理。
  定义:给定一个有向图G,若将G中所有的边的方向逆置,得到的图GT成为G图的逆图。
  定理:图G的逆图GT的强连通分量的个数是一样的。
  基于这个定理,我们对于G图,通过dfs来遍历G中所有的点,并在遍历过程中进行标记,我们会得到这样一个序列:<v1、v2、v3、v4……vn>,我们假设G并不是连通的,在dfs遍历的时候,必然有vi、vi+1之间是没有直接的通路的,但是<v1、v2、……vi>却是通过dfs给出的一条真实存在的路径,也就是说,我们得到的点序列<v1、v2、v3……vn>中包含着多个连通分量,并且对于连通分量的路径<vi、vi+1……vj>中,取出任意两点,下标小的点到下标大的点是一定存在通路的(这取决于这条路径是由遍历图的dfs算法得到的)。
  基于对<v1、v2、v3……vn>含义的理解,我们现在将图进行转置(得到逆图GT),然后按照这样一个序列:<vn、……v3、v2、v1>的进行遍历,假设当前遍历到了vi点,则表明逆图GT中vn->vi可达,也就是说原图中vi->vn可达,那么我们现在只需证明原图中vn->vi可达,再根据强连通的定义,即可以说vn、vi属于一个强连通分量。
  我们利用反证法,如果原图中不存在vn->vi的通路,而存在vn->vi的通路,根据我们对<v1、v2、v3……vn>含义的理解,我们容易基于这种假设,i>n,这与事实是不符的,因此得证。
  那么我们从vn开始,遍历逆图GT上能够遍历到的点,那么就得到了vn所在的最大强连通分量,一次进行下去,我们就可以找到所有的最大强连通分量了。
  概括一下该算法,便会得到如下过程。
  (1)对原图G进行dfs,记录每个节点离开的时间num[i](实际上就是得到上面所说序列)。
  (2)选择具有最晚离开时间的顶点,对逆图GT进行dfs,能够遍历到的点与该点组成一个强连通分量。
  (3)如果点集中还有点剩余,重复第二步。
  我们通过一个题目来具体的实现该算法。(Problem source : hdu 1269)
 

Problem Description
为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间。Gardon需要请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间i。
 
Input
输入包含多组数据,输入的第一行有两个数:N和M,接下来的M行每行有两个数a和b,表示了一条通道可以从A房间来到B房间。文件最后以两个0结束。
 
Output
对于输入的每组数据,如果任意两个房间都是相互连接的,输出"Yes",否则输出"No"。


  数理分析:容易看到,这道题目是判断一个图是否具有强连通性,及判断给定图G是否有且仅有一个强连通分量。
  通过上文对算法的分析,我们利用c++中的stl中的一些工具对算法进行编程实现。(为防止储存图信息的二维数组爆掉内存限制,我们这里用到栈来储存图)
  参考代码如下。

 

#include <cstdio>
#include <stack>
#include <vector>

using namespace std;

const int N = 10005;

vector<int> adj[N], t_adj[N];
bool visit[N];
int n, m, cnt;

//利用非递归的DFS来防止MLE
//通过传入的是adj或者t_adj来进行两遍DFS
bool dfs(int rt, vector<int> *graph)
{
    stack<int> st;
    st.push(rt);
    visit[rt] = true;
    cnt = 0;//用来统计从rt能访问到图中节点的个数

    while (!st.empty())
    {
        int u = st.top();
        ++cnt;
        st.pop();
        
        for (int i = 0; i < graph[u].size(); ++i)
        {
            if (visit[graph[u][i]])continue;
            st.push(graph[u][i]);
            visit[graph[u][i]] = true;
        }

    }
    if (cnt == n)return true;
    return false;
}

//将图做转置
void transform()
{
    for (int i = 1; i <= n; ++i)
    {
        for (unsigned j = 0; j < adj[i].size(); ++j)
        {
            t_adj[adj[i][j]].push_back(i);
        }
    }
}

bool kosaraju()
{
    memset(visit, 0, sizeof(visit));

    bool ret = dfs(1, adj);
    if (!ret)return false;

    transform();
    memset(visit, 0, sizeof(visit));

    ret = dfs(1, t_adj);
    if (ret)return true;
    return false;
}

int main()
{
    int a, b;

    while (scanf("%d %d", &n, &m) != EOF && (m + n))
    {
        for (int i = 0; i < m; ++i)
        {
            scanf("%d %d", &a, &b);
            adj[a].push_back(b);
        }

        if (kosaraju())
        {
            printf("Yes\n");
        }
        else
        {
            printf("No\n");
        }

        for (int i = 1; i <= n; ++i)
        {
            adj[i].clear();
            t_adj[i].clear();
        }
    }
    return 0;
}

 


 
 

posted on 2016-04-06 18:33  在苏州的城边  阅读(1762)  评论(0编辑  收藏  举报

导航