基于DFS的连通性检测
理论基础
在图论中,连通分量是无向图的一个重要概念,特别是在处理图的结构和解析图的组成时。连通分组件表示图中的一个子图,在这个子图中任意两个顶点都是连通的,即存在一条路径可以从一个顶点到达另一个顶点,并且这个子图是最大的,即不能通过添加更多的顶点来增加连通性。对于有向图,这通常被称为强连通分量。
基于DFS的连通分量算法
书中4.1.6节提到的基于深度优先搜索(DFS)的连通分量算法用于识别和处理无向图中的连通分量。这个算法的基本思想是使用DFS遍历图中的每个顶点,同时记录哪些顶点是连通的。
算法步骤
- 初始化:为每个顶点准备一个标记数组
marked[]
来记录每个顶点是否被访问过,另外用一个数组id[]
来记录每个顶点所属的连通分量的标识符。还需要一个计数器count
来统计连通分量的数量。 - DFS遍历:从任意未被访问的顶点开始,执行DFS遍历。在遍历过程中,标记所有可达的顶点为已访问,同时将这些顶点的
id[]
设置为当前的连通分量标识符。 - 连通分量标识:每次在DFS遍历开始前增加连通分量计数器
count
,并将遍历过程中访问的所有顶点的连通分量标识设置为这个计数器的值。 - 重复执行:重复上述过程,直到图中的所有顶点都被访问过。
应用
- 图的结构分析:识别图中的独立部分或者紧密相关的群组。
- 网络设计:确定网络中的独立组件,以优化设计和提高稳定性。
- 社交网络:识别社交网络中的社区或者群组。
通过这种基于DFS的连通分量算法,可以有效地解析和处理图的结构,对于复杂网络的分析尤其有用。
数据结构
private boolean[] marked
private int[] id
private int count
myBag
myGraph
实验数据和算法流程
这里使用tinyG.txt
来构成实验用的无向图
注意算法流程中count
,marked[]
,id[]
的变化
代码实现
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
public class myCC {
private boolean[] marked;
private int[] id;
private int count;
public myCC(myGraph G){
marked = new boolean[G.V()];
id = new int[G.V()];
for(int s=0;s<G.V();s++){
if(!marked[s]){
dfs(G,s);
//这里是精髓所在,每次dfs回到这里就说明互相连通的一组顶点已经完成遍历,
//也就确定了一个连通分量
count++;
}
}
}
private void dfs(myGraph G, int v){
marked[v] = true;
id[v] = count;
for(int w:G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
}
public boolean connected(int v, int w){return id[v]==id[w];}
public int id(int v){return id[v];}
public int count(){return count;}
public static void main(String[] args){
myGraph G = new myGraph(new In(args[0]));
myCC cc = new myCC(G);
int M = cc.count();
StdOut.println(M + " components");
myBag<Integer>[] components = (myBag<Integer>[]) new myBag[M];
for(int i=0;i<M;i++){
components[i] = new myBag<Integer>();
}
for(int v=0;v<G.V();v++){
components[cc.id(v)].add(v);
}
for(int i=0;i<M;i++){
for(int v:components[i]) StdOut.print(v+" ");
StdOut.println();
}
}
}
代码详解
这段代码实现了一个基于深度优先搜索(DFS)的连通分量(CC)类 myCC
,用于确定无向图中所有的连通分量。下面是详细的代码解释:
类定义和变量
public class myCC {
private boolean[] marked; // 标记数组,用于标记每个顶点是否已经被访问过
private int[] id; // 每个顶点所属的连通分量标识
private int count; // 连通分量的数量
marked
数组用于记录图中的每个顶点是否已经被访问。id
数组用于存储每个顶点所属的连通分量的ID。count
用于计数图中连通分量的总数。
构造函数
public myCC(myGraph G){
marked = new boolean[G.V()];
id = new int[G.V()];
for(int s = 0; s < G.V(); s++) {
if (!marked[s]) {
dfs(G, s);
count++; // 完成一个连通分量的搜索后,增加连通分量的计数
}
}
}
构造函数遍历图中的所有顶点,对于每个未标记的顶点,执行DFS来标记和记录所有能从该顶点访问到的顶点,这些顶点构成一个连通分量。每次DFS调用结束后,连通分量数 count
加一。
DFS 方法
private void dfs(myGraph G, int v){
marked[v] = true;
id[v] = count;
for (int w : G.adj(v)) {
if (!marked[w]) {
dfs(G, w);
}
}
}
dfs
方法标记顶点 v
为已访问,并将其连通分量ID设置为当前的 count
。然后递归地访问所有与顶点 v
直接相连的未标记顶点。
连通分量的辅助方法
public boolean connected(int v, int w) { return id[v] == id[w]; }
public int id(int v) { return id[v]; }
public int count() { return count; }
这些方法提供了:
connected(v, w)
检查两个顶点是否属于同一个连通分量。id(v)
返回顶点v
的连通分量ID。count()
返回图中连通分量的总数。
主方法
public static void main(String[] args){
myGraph G = new myGraph(new In(args[0]));
myCC cc = new myCC(G);
int M = cc.count();
StdOut.println(M + " components");
myBag<Integer>[] components = (myBag<Integer>[]) new myBag[M];
for (int i = 0; i < M; i++) {
components[i] = new myBag<Integer>();
}
for (int v = 0; v < G.V(); v++) {
components[cc.id(v)].add(v);
}
for (int i = 0; i < M; i++) {
for (int v : components[i]) StdOut.print(v + " ");
StdOut.println();
}
}
主方法使用 myCC
类来处理一个从文件读取的图,并输出所有的连通分量。这里,连通分量被存储在一个 myBag
数组中,每个 myBag
对象存储一个连通分量的所有顶点,然后输出每个连通分量的顶点。
这段代码是一个完整的图连通分量识别实现,使用DFS作为基本的遍历策略。
实验
代码编译
javac myCC.java
运行代码
将实验数据tinyG.txt导入代码后,myCC可以检测到3个连通分量,并逐行将连通分量中的元素打印出来
java myCC ..\data\tinyG.txt
3 components
6 5 4 3 2 1 0
8 7
12 11 10 9
参考资料
算法(第4版)人民邮电出版社