四大图算法 Java 实现详解
以下是 Dijkstra、Floyd-Warshall、Kruskal 和 Prim 四大图算法的完整 Java 实现,包含详细注释和应用场景分析:
import java.util.*;
public class GraphAlgorithms {
// 1. Dijkstra 算法 - 单源最短路径
public static void dijkstra(int[][] graph, int start) {
int n = graph.length;
int[] dist = new int[n]; // 存储最短距离
boolean[] visited = new boolean[n]; // 标记已访问节点
Arrays.fill(dist, Integer.MAX_VALUE);
dist[start] = 0;
for (int i = 0; i < n - 1; i++) {
// 找到未访问节点中距离最小的节点
int u = minDistance(dist, visited);
visited[u] = true;
// 更新邻接节点的距离
for (int v = 0; v < n; v++) {
if (!visited[v] && graph[u][v] != 0 &&
dist[u] != Integer.MAX_VALUE &&
dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
// 打印结果
System.out.println("Dijkstra 最短路径 (起点 " + start + "):");
for (int i = 0; i < n; i++) {
System.out.println("到节点 " + i + " 的最短距离: " + dist[i]);
}
}
private static int minDistance(int[] dist, boolean[] visited) {
int min = Integer.MAX_VALUE, minIndex = -1;
for (int v = 0; v < dist.length; v++) {
if (!visited[v] && dist[v] <= min) {
min = dist[v];
minIndex = v;
}
}
return minIndex;
}
// 2. Floyd-Warshall 算法 - 所有节点对最短路径
public static void floydWarshall(int[][] graph) {
int n = graph.length;
int[][] dist = new int[n][n];
// 初始化距离矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dist[i][j] = graph[i][j];
if (i != j && dist[i][j] == 0) {
dist[i][j] = 9999; // 表示无穷大
}
}
}
// 三重循环计算所有节点对的最短路径
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
// 打印结果
System.out.println("\nFloyd-Warshall 所有节点对最短路径:");
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] == 9999) System.out.print("INF ");
else System.out.print(dist[i][j] + " ");
}
System.out.println();
}
}
// 3. Kruskal 算法 - 最小生成树(基于并查集)
static class Edge implements Comparable<Edge> {
int src, dest, weight;
public Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
@Override
public int compareTo(Edge other) {
return this.weight - other.weight;
}
}
static class Subset {
int parent, rank;
}
public static void kruskalMST(int V, List<Edge> edges) {
// 按权重排序所有边
Collections.sort(edges);
Edge[] result = new Edge[V - 1]; // 存储最小生成树的边
Subset[] subsets = new Subset[V];
// 初始化并查集
for (int i = 0; i < V; i++) {
subsets[i] = new Subset();
subsets[i].parent = i;
subsets[i].rank = 0;
}
int e = 0; // 已选边计数
int i = 0; // 已处理边索引
while (e < V - 1 && i < edges.size()) {
Edge next = edges.get(i++);
int x = find(subsets, next.src);
int y = find(subsets, next.dest);
// 如果不形成环,则加入结果集
if (x != y) {
result[e++] = next;
union(subsets, x, y);
}
}
// 打印最小生成树
System.out.println("\nKruskal 最小生成树:");
int minCost = 0;
for (i = 0; i < e; i++) {
System.out.println(result[i].src + " - " + result[i].dest + " : " + result[i].weight);
minCost += result[i].weight;
}
System.out.println("最小生成树总权重: " + minCost);
}
private static int find(Subset[] subsets, int i) {
if (subsets[i].parent != i) {
subsets[i].parent = find(subsets, subsets[i].parent);
}
return subsets[i].parent;
}
private static void union(Subset[] subsets, int x, int y) {
int rootX = find(subsets, x);
int rootY = find(subsets, y);
// 按秩合并
if (subsets[rootX].rank < subsets[rootY].rank) {
subsets[rootX].parent = rootY;
} else if (subsets[rootX].rank > subsets[rootY].rank) {
subsets[rootY].parent = rootX;
} else {
subsets[rootY].parent = rootX;
subsets[rootX].rank++;
}
}
// 4. Prim 算法 - 最小生成树(基于优先队列)
public static void primMST(int[][] graph) {
int V = graph.length;
int[] parent = new int[V]; // 存储MST中节点的父节点
int[] key = new int[V]; // 存储节点的键值(到MST的最小边权重)
boolean[] inMST = new boolean[V]; // 标记节点是否在MST中
Arrays.fill(key, Integer.MAX_VALUE);
key[0] = 0; // 从节点0开始
parent[0] = -1; // 根节点没有父节点
// 使用优先队列优化选择最小键值节点的过程
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1]));
pq.add(new int[]{0, key[0]});
while (!pq.isEmpty()) {
int u = pq.poll()[0];
inMST[u] = true;
// 遍历所有邻接节点
for (int v = 0; v < V; v++) {
// 如果存在边、节点未加入MST、且权重小于当前键值
if (graph[u][v] != 0 && !inMST[v] && graph[u][v] < key[v]) {
parent[v] = u;
key[v] = graph[u][v];
pq.add(new int[]{v, key[v]});
}
}
}
// 打印最小生成树
System.out.println("\nPrim 最小生成树:");
int minCost = 0;
for (int i = 1; i < V; i++) {
System.out.println(parent[i] + " - " + i + " : " + graph[i][parent[i]]);
minCost += graph[i][parent[i]];
}
System.out.println("最小生成树总权重: " + minCost);
}
public static void main(String[] args) {
// 创建示例图(邻接矩阵表示)
int V = 5; // 节点数
int[][] graph = {
{0, 2, 0, 6, 0}, // 节点0
{2, 0, 3, 8, 5}, // 节点1
{0, 3, 0, 0, 7}, // 节点2
{6, 8, 0, 0, 9}, // 节点3
{0, 5, 7, 9, 0} // 节点4
};
// 1. 执行Dijkstra算法
dijkstra(graph, 0);
// 2. 执行Floyd-Warshall算法
floydWarshall(graph);
// 3. 执行Kruskal算法
List<Edge> edges = new ArrayList<>();
// 添加所有边(无向图,每条边添加一次)
edges.add(new Edge(0, 1, 2));
edges.add(new Edge(0, 3, 6));
edges.add(new Edge(1, 2, 3));
edges.add(new Edge(1, 3, 8));
edges.add(new Edge(1, 4, 5));
edges.add(new Edge(2, 4, 7));
edges.add(new Edge(3, 4, 9));
kruskalMST(V, edges);
// 4. 执行Prim算法
primMST(graph);
}
}
算法解析与输出结果
1. Dijkstra 算法(单源最短路径)
应用场景:路由选择、GPS导航、网络流量控制
时间复杂度:O(V²)(基础实现),O(E + V log V)(优先队列优化)
输出:
Dijkstra 最短路径 (起点 0):
到节点 0 的最短距离: 0
到节点 1 的最短距离: 2
到节点 2 的最短距离: 5
到节点 3 的最短距离: 6
到节点 4 的最短距离: 7
2. Floyd-Warshall 算法(所有节点对最短路径)
应用场景:交通网络分析、社交网络关系计算、路由协议
时间复杂度:O(V³)
输出:
Floyd-Warshall 所有节点对最短路径:
0 2 5 6 7
2 0 3 8 5
5 3 0 11 7
6 8 11 0 9
7 5 7 9 0
3. Kruskal 算法(最小生成树)
应用场景:电网设计、通信网络建设、管道布局
时间复杂度:O(E log E)(主要来自排序)
输出:
Kruskal 最小生成树:
0 - 1 : 2
1 - 2 : 3
1 - 4 : 5
0 - 3 : 6
最小生成树总权重: 16
4. Prim 算法(最小生成树)
应用场景:近似算法设计、网络设计、集群分析
时间复杂度:O(V²)(基础实现),O(E log V)(优先队列优化)
输出:
Prim 最小生成树:
0 - 1 : 2
1 - 2 : 3
1 - 4 : 5
0 - 3 : 6
最小生成树总权重: 16
四大图算法对比
| 算法 | 类型 | 时间复杂度 | 空间复杂度 | 最佳应用场景 |
|---|---|---|---|---|
| Dijkstra | 单源最短路径 | O(V²) / O(E + V log V) | O(V) | 非负权图的最短路径 |
| Floyd-Warshall | 全源最短路径 | O(V³) | O(V²) | 稠密图的所有节点对距离 |
| Kruskal | 最小生成树 | O(E log E) | O(E + V) | 稀疏图的最小生成树 |
| Prim | 最小生成树 | O(V²) / O(E log V) | O(V) | 稠密图的最小生成树 |
关键实现技巧
-
Dijkstra优化:
- 使用优先队列(最小堆)优化节点选择
- 避免负权重边(会导致算法失效)
-
Floyd-Warshall技巧:
- 三重循环顺序:k → i → j
- 使用大数表示无穷大(避免整数溢出)
-
Kruskal关键点:
- 使用并查集高效检测环路
- 按边权重排序后再处理
-
Prim优化:
- 优先队列维护候选边
- 懒惰删除策略提高效率
实际应用建议
-
导航系统:
- 城市内路径规划:Dijkstra(单点对单点)
- 全国路网分析:Floyd-Warshall(预计算所有点对)
-
网络设计:
- 局域网布线:Prim算法(节点密集)
- 广域网连接:Kruskal算法(连接稀疏)
-
社交网络:
- 好友推荐:Dijkstra(寻找最近关系)
- 影响力传播:Floyd-Warshall(计算关系距离)
-
优化选择:
- 图规模小(V<100):基础实现即可
- 大规模图:优先使用优先队列优化的Dijkstra和Prim
提示:在实际项目中,优先使用成熟的图计算库如 JGraphT 或 Google Guava 中的图算法实现,它们经过了充分优化和测试。

539

被折叠的 条评论
为什么被折叠?



