图论及其应用——连通性问题
我们这篇文章将会开始讨论关于图的连通性的问题。
对于图的连通性问题,有着怎样的实际应用呢?其实很明显的一点就是社交网络、朋友圈当中的应用,后台可以根据一个连通分量(在实际的模型可以认为有着某些共性)当中其他用户的一些数据,来推测该连通分量下某一个用户可能感兴趣的信息。
首先,我们给出图的强连通性的定义,对于图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)
数理分析:容易看到,这道题目是判断一个图是否具有强连通性,及判断给定图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; }