图论基础算法:DFS、BFS、并查集与拓扑排序的Java实现
图论是计算机科学中的核心领域,广泛应用于社交网络分析、路径规划、编译器设计等场景。本文将使用Java实现图论中的基础算法,包括深度优先搜索(DFS)、广度优先搜索(BFS)、并查集(Union-Find)和拓扑排序(Topological Sorting)。
图的表示方法
邻接表表示法
邻接表是表示稀疏图的高效方式:
java
import java.util.*;
public class GraphAlgorithms {
// 创建邻接表
public static List<List<Integer>> createGraph(int vertices) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < vertices; i++) {
graph.add(new LinkedList<>());
}
return graph;
}
// 添加无向边
public static void addUndirectedEdge(List<List<Integer>> graph, int v, int w) {
graph.get(v).add(w);
graph.get(w).add(v);
}
// 添加有向边
public static void addDirectedEdge(List<List<Integer>> graph, int v, int w) {
graph.get(v).add(w);
}
}
邻接矩阵表示法
邻接矩阵适合表示稠密图:
java
// 创建邻接矩阵
public static boolean[][] createAdjMatrix(int vertices) {
return new boolean[vertices][vertices];
}
// 添加边
public static void addMatrixEdge(boolean[][] matrix, int v, int w) {
matrix[v][w] = true;
matrix[w][v] = true; // 无向图
}
深度优先搜索(DFS)
DFS以深度优先方式遍历图:
java
// 迭代实现DFS
public static void dfsIterative(List<List<Integer>> graph, int start) {
boolean[] visited = new boolean[graph.size()];
Stack<Integer> stack = new Stack<>();
stack.push(start);
visited[start] = true;
while (!stack.isEmpty()) {
int current = stack.pop();
System.out.print(current + " ");
for (int neighbor : graph.get(current)) {
if (!visited[neighbor]) {
stack.push(neighbor);
visited[neighbor] = true;
}
}
}
System.out.println();
}
// 递归实现DFS
public static void dfsRecursive(List<List<Integer>> graph, int start) {
boolean[] visited = new boolean[graph.size()];
dfsRecursiveUtil(graph, visited, start);
System.out.println();
}
private static void dfsRecursiveUtil(List<List<Integer>> graph,
boolean[] visited, int current) {
visited[current] = true;
System.out.print(current + " ");
for (int neighbor : graph.get(current)) {
if (!visited[neighbor]) {
dfsRecursiveUtil(graph, visited, neighbor);
}
}
}
时间复杂度:O(V + E),其中V是顶点数,E是边数
广度优先搜索(BFS)
BFS按层次遍历图:
java
public static void bfs(List<List<Integer>> graph, int start) {
boolean[] visited = new boolean[graph.size()];
Queue<Integer> queue = new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int current = queue.poll();
System.out.print(current + " ");
for (int neighbor : graph.get(current)) {
if (!visited[neighbor]) {
queue.add(neighbor);
visited[neighbor] = true;
}
}
}
System.out.println();
}
应用场景:社交网络中的好友推荐、最短路径查找
并查集(Union-Find)
并查集高效处理不相交集合的合并与查询问题:
java
static class UnionFind {
int[] parent;
int[] rank;
public UnionFind(int size) {
parent = new int[size];
rank = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}
// 查找操作(带路径压缩)
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
// 合并操作(带按秩合并)
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return;
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
优化技术:
- 路径压缩:使树更平坦,加速后续查找
- 按秩合并:防止树过高,保持平衡
拓扑排序
拓扑排序对有向无环图(DAG)进行线性排序:
java
public static void topologicalSort(List<List<Integer>> graph) {
int n = graph.size();
int[] inDegree = new int[n];
Queue<Integer> queue = new LinkedList<>();
List<Integer> result = new ArrayList<>();
// 计算入度
for (int i = 0; i < n; i++) {
for (int neighbor : graph.get(i)) {
inDegree[neighbor]++;
}
}
// 将入度为0的节点加入队列
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
queue.add(i);
}
}
// 处理队列
while (!queue.isEmpty()) {
int current = queue.poll();
result.add(current);
for (int neighbor : graph.get(current)) {
if (--inDegree[neighbor] == 0) {
queue.add(neighbor);
}
}
}
// 检测环
if (result.size() != n) {
System.out.println("Graph contains a cycle!");
} else {
for (int node : result) {
System.out.print(node + " ");
}
System.out.println();
}
}
应用场景:
- 任务调度:确定任务执行顺序
- 课程安排:解决课程依赖关系
- 编译顺序:确定源代码编译顺序
完整示例与测试
java
public static void main(String[] args) {
// 创建无向图
List<List<Integer>> graph = createGraph(6);
addUndirectedEdge(graph, 0, 1);
addUndirectedEdge(graph, 0, 2);
addUndirectedEdge(graph, 1, 3);
addUndirectedEdge(graph, 1, 4);
addUndirectedEdge(graph, 2, 4);
addUndirectedEdge(graph, 3, 5);
addUndirectedEdge(graph, 4, 5);
System.out.print("DFS (iterative): ");
dfsIterative(graph, 0);
System.out.print("DFS (recursive): ");
dfsRecursive(graph, 0);
System.out.print("BFS: ");
bfs(graph, 0);
// 并查集测试
UnionFind uf = new UnionFind(5);
uf.union(0, 1);
uf.union(2, 3);
uf.union(1, 4);
uf.union(3, 4);
System.out.print("Union-Find sets: ");
for (int i = 0; i < 5; i++) {
System.out.print(uf.find(i) + " ");
}
System.out.println();
// 创建有向无环图
List<List<Integer>> dag = createGraph(6);
addDirectedEdge(dag, 2, 3);
addDirectedEdge(dag, 3, 1);
addDirectedEdge(dag, 4, 0);
addDirectedEdge(dag, 4, 1);
addDirectedEdge(dag, 5, 0);
addDirectedEdge(dag, 5, 2);
System.out.print("Topological sort: ");
topologicalSort(dag);
// 测试有环图
List<List<Integer>> cyclicGraph = createGraph(3);
addDirectedEdge(cyclicGraph, 0, 1);
addDirectedEdge(cyclicGraph, 1, 2);
addDirectedEdge(cyclicGraph, 2, 0);
System.out.print("Cyclic graph test: ");
topologicalSort(cyclicGraph);
}
输出示例:
DFS (iterative): 0 2 4 5 3 1
DFS (recursive): 0 1 3 5 4 2
BFS: 0 1 2 3 4 5
Union-Find sets: 1 1 3 1 1
Topological sort: 4 5 0 2 3 1
Cyclic graph test: Graph contains a cycle!
算法比较与选择指南
算法 | 时间复杂度 | 空间复杂度 | 最佳适用场景 |
---|---|---|---|
DFS | O(V+E) | O(V) | 连通性检测、拓扑排序(递归) |
BFS | O(V+E) | O(V) | 最短路径、层级遍历 |
并查集 | 接近O(1) | O(V) | 动态连通性问题 |
拓扑排序 | O(V+E) | O(V) | 任务调度、依赖分析 |
使用建议:
- 需要最短路径时优先选择BFS
- 处理动态连通性问题使用并查集
- 任务调度和依赖分析使用拓扑排序
- 一般遍历和连通性检测DFS和BFS均可
Java实现特点
- 集合框架 :使用
ArrayList
、LinkedList
等集合类简化实现 - 泛型:使用泛型确保类型安全
- 面向对象:并查集使用静态内部类封装
- 异常处理:Java自带异常处理机制增强健壮性
- 内存管理:Java自动垃圾回收简化内存管理
总结
本文使用Java实现了图论中四种基础算法:
- DFS:深度优先遍历,提供递归和迭代两种实现
- BFS:广度优先遍历,使用队列实现层级遍历
- 并查集:高效处理集合合并与查询,使用路径压缩和按秩合并优化
- 拓扑排序:Kahn算法实现,自动检测环
所有实现均遵循Java最佳实践,代码简洁高效。这些算法构成了图论的基础,掌握它们对于解决更复杂的图论问题至关重要。