摘自计蒜客:http://www.jisuanke.com/course/35/7547
一、连通分量和FloodFill算法
首先,我们来介绍一个概念:子图(subgraph)。若一个图的顶点集和边集分别是另一图的顶点集的子集和边集的子集,则称该图为另一图的子图。
换句话说,从一个图里选出一部分顶点和边,只要确保选择的边对应的两个顶点也都被选择,那么所有选出的顶点和边组成的图就是原图的子图。
就像在一个社交网络中,同班同学的帐号之间的关系就组成了整个社交网络的一个子图。
接下来,我们介绍一个概念:连通。在无向图中,如果有从顶点 v 到顶点 w 的路径存在,则称 v 和 w 是连通的。若图 G 中任意两个顶点都是连通的,则称图 G 为连通图,否则成为非连通图。
若图 G 的子图 Gs 是连通的,我们就称子图 Gs 是图 G 的连通子图。如果对于图 G 的一个连通子图 Gs,不存在图 G 的其他连通子图 Gmax 满足:Gs是 Gmax的子图。则子图 Gs 是图 G 的极大连通子图,也就是图 G 的连通分量。
如何求解无向图的连通分量呢?这要用到我们本章介绍的第一个图论算法:FloodFill 算法。
FloodFill算法通常译作“洪水灌溉法”,算法通过给图中的顶点染色,最终使得同一个连通分量的顶点颜色相同,不同连通分量的顶点颜色不同。算法的描述如下:
1. 找到一个没有染色的顶点,将其染为新的颜色 Color_new,如果没有则算法结束。
2. 初始化一个空的队列,并将第一步的顶点插入队列。
3. 不断获得队首元素的值并弹出,将和队首元素相邻的未染色顶点染为Color_new,并将其加入队列。
4. 重复执行第一步,直到所有顶点都被染色,算法结束。
下图是FloodFill算法的演示:
可以看出,最终整张图被染成了三种颜色,也就是说图中有三个连通分量。
FloodFill 的时间复杂度是 O(V+E),其中广度优先遍历的部分可以替换成深度优先遍历,复杂度是一样的。通常考虑到递归调用的时间开销,往往广度优先遍历的效率要更高一些。
代码实现:
#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
class Graph {
private:
int n;
int * color;
vector<int> * edges;
public:
Graph(int input_n) {
n = input_n;
edges = new vector<int>[n];
color = new int[n];
memset(color, 0, n * sizeof(int));
}
~Graph() {
delete[] edges;
delete[] color;
}
void insert(int x, int y) {
edges[x].push_back(y);
edges[y].push_back(x);
}
void floodfill() {
//首先定义变量color_cnt表示当前染色的值
int color_cnt = 0;
//接下来,枚举所有未染色的顶点。顶点的染色结果记录在
//color数组中,未染色时结果为0,否则为一个正数。因为我们判断一个顶点是否
//染色时,只需要判断color数组中对应的值是否为0即可。
for (int i=0; i<n; i++) {
if (color[i] == 0) {
//接下来,要从当前顶点出发,对所在连通分量进行广度优先遍历。
//每次对所在连通分量的遍历,都会讲连通分量内的所有顶点染为一种
//新的颜色。我们只需要将这些颜色区分开就可以,这里我们不妨让每
//个连通分量染色的值和上次相比都加1.比如没一个连通分量染色的值
//为1.第二个染色的值为2,依次类推。
//在处理每一个连通分量时,第一步要做的就是将color_cnt的值加1,
//并将第i个顶点染为color_cnt这个颜色。
color_cnt++;
color[i] = color_cnt;
//进行广度优先遍历
queue<int> q;
q.push(i);
while(!q.empty()) {
int vertex = q.front();
//枚举和vertex相邻的所有顶点
for(int j: edges[vertex]) {
if(color[j] == 0) {
color[j] = color_cnt;
q.push(j);
}
}
q.pop();
}
}
}
//将color数组结果输出
for(int i=0; i<n; ++i) {
cout << i << " " << color[i] << endl;
}
}
};
int main() {
int n, m, k;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; ++i) {
int x, y;
cin >> x >> y;
g.insert(x, y);
}
g.floodfill();
return 0;
}