图的最短路径算法:原理与实现

图的最短路径算法:原理与实现

在图论中,最短路径算法用于找到图中从一个顶点到另一个顶点的最短路径。常见的最短路径算法包括Dijkstra算法、Bellman-Ford算法和Floyd-Warshall算法。以下是每个算法的详细讲解及其Java实现。

1. Dijkstra算法

原理

Dijkstra算法是一种贪心算法,适用于无负权边的图。它通过逐步扩展从源节点到其他节点的最短路径,确保每次选择的路径都是当前最短的路径。

步骤

  1. 初始化:将源节点的距离设为0,其余节点的距离设为无穷大。
  2. 选择未处理节点中距离最小的节点作为当前节点。
  3. 更新当前节点的邻居节点的距离,如果通过当前节点到邻居节点的路径比原有路径更短,则更新邻居节点的距离。
  4. 标记当前节点为已处理。
  5. 重复步骤2-4,直到所有节点都被处理。

Java实现

java 复制代码
import java.util.*;

public class Dijkstra {
    public static Map<String, Integer> dijkstra(Map<String, Map<String, Integer>> graph, String start) {
        Map<String, Integer> distances = new HashMap<>();
        for (String node : graph.keySet()) {
            distances.put(node, Integer.MAX_VALUE);
        }
        distances.put(start, 0);

        PriorityQueue<Map.Entry<String, Integer>> priorityQueue = new PriorityQueue<>(Map.Entry.comparingByValue());
        priorityQueue.add(new AbstractMap.SimpleEntry<>(start, 0));

        while (!priorityQueue.isEmpty()) {
            Map.Entry<String, Integer> current = priorityQueue.poll();
            String currentNode = current.getKey();
            int currentDistance = current.getValue();

            if (currentDistance > distances.get(currentNode)) {
                continue;
            }

            Map<String, Integer> neighbors = graph.get(currentNode);
            for (Map.Entry<String, Integer> neighbor : neighbors.entrySet()) {
                int distance = currentDistance + neighbor.getValue();
                if (distance < distances.get(neighbor.getKey())) {
                    distances.put(neighbor.getKey(), distance);
                    priorityQueue.add(new AbstractMap.SimpleEntry<>(neighbor.getKey(), distance));
                }
            }
        }
        return distances;
    }

    public static void main(String[] args) {
        Map<String, Map<String, Integer>> graph = new HashMap<>();
        graph.put("A", Map.of("B", 1, "C", 4));
        graph.put("B", Map.of("A", 1, "C", 2, "D", 5));
        graph.put("C", Map.of("A", 4, "B", 2, "D", 1));
        graph.put("D", Map.of("B", 5, "C", 1));

        Map<String, Integer> distances = dijkstra(graph, "A");
        System.out.println(distances);
    }
}
2. Bellman-Ford算法

原理

Bellman-Ford算法适用于含有负权边的图,并且可以检测负权环。它通过逐步松弛边缘,确保每次选择的路径都是当前最短的路径。

步骤

  1. 初始化:将源节点的距离设为0,其余节点的距离设为无穷大。
  2. 重复图中边数 - 1 次:
    • 对每一条边 (u, v),如果通过 u 到 v 的距离比当前距离短,则更新距离。
  3. 再次检查所有边,如果仍然能找到更短路径,则说明存在负权环。

Java实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class BellmanFord {
    public static Map<String, Integer> bellmanFord(Map<String, Map<String, Integer>> graph, String start) throws Exception {
        Map<String, Integer> distances = new HashMap<>();
        for (String node : graph.keySet()) {
            distances.put(node, Integer.MAX_VALUE);
        }
        distances.put(start, 0);

        for (int i = 0; i < graph.size() - 1; i++) {
            for (String node : graph.keySet()) {
                for (Map.Entry<String, Integer> neighbor : graph.get(node).entrySet()) {
                    int newDist = distances.get(node) + neighbor.getValue();
                    if (newDist < distances.get(neighbor.getKey())) {
                        distances.put(neighbor.getKey(), newDist);
                    }
                }
            }
        }

        for (String node : graph.keySet()) {
            for (Map.Entry<String, Integer> neighbor : graph.get(node).entrySet()) {
                if (distances.get(node) + neighbor.getValue() < distances.get(neighbor.getKey())) {
                    throw new Exception("Graph contains a negative weight cycle");
                }
            }
        }
        return distances;
    }

    public static void main(String[] args) {
        Map<String, Map<String, Integer>> graph = new HashMap<>();
        graph.put("A", Map.of("B", 1, "C", 4));
        graph.put("B", Map.of("A", 1, "C", 2, "D", 5));
        graph.put("C", Map.of("A", 4, "B", 2, "D", 1));
        graph.put("D", Map.of("B", 5, "C", 1));

        try {
            Map<String, Integer> distances = bellmanFord(graph, "A");
            System.out.println(distances);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
3. Floyd-Warshall算法

原理

Floyd-Warshall算法用于计算所有节点对之间的最短路径,适用于加权图,包括负权边。它通过逐步扩展中间节点,确保每次选择的路径都是当前最短的路径。

步骤

  1. 初始化:将每个节点到自身的距离设为0,其余节点之间的距离设为无穷大(如果没有直接边)。
  2. 对每个节点 k 作为中间节点,更新所有节点对之间的距离:
    • 对每一对节点 (i, j),通过 k 更新距离,如果 i 到 k 再到 j 的距离比直接距离短,则更新距离。

Java实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class FloydWarshall {
    public static Map<String, Map<String, Integer>> floydWarshall(Map<String, Map<String, Integer>> graph) {
        Map<String, Map<String, Integer>> distances = new HashMap<>();
        for (String node : graph.keySet()) {
            distances.put(node, new HashMap<>());
            for (String target : graph.keySet()) {
                if (node.equals(target)) {
                    distances.get(node).put(target, 0);
                } else {
                    distances.get(node).put(target, graph.get(node).getOrDefault(target, Integer.MAX_VALUE));
                }
            }
        }

        for (String k : graph.keySet()) {
            for (String i : graph.keySet()) {
                for (String j : graph.keySet()) {
                    if (distances.get(i).get(k) != Integer.MAX_VALUE && distances.get(k).get(j) != Integer.MAX_VALUE) {
                        int newDist = distances.get(i).get(k) + distances.get(k).get(j);
                        if (newDist < distances.get(i).get(j)) {
                            distances.get(i).put(j, newDist);
                        }
                    }
                }
            }
        }
        return distances;
    }

    public static void main(String[] args) {
        Map<String, Map<String, Integer>> graph = new HashMap<>();
        graph.put("A", Map.of("B", 1, "C", 4));
        graph.put("B", Map.of("A", 1, "C", 2, "D", 5));
        graph.put("C", Map.of("A", 4, "B", 2, "D", 1));
        graph.put("D", Map.of("B", 5, "C", 1));

        Map<String, Map<String, Integer>> distances = floydWarshall(graph);
        System.out.println(distances);
    }
}

结论

  • Dijkstra算法:适用于没有负权边的图,时间复杂度为O(V^2)(使用邻接矩阵)或O(E + V log V)(使用优先队列和邻接表)。
  • Bellman-Ford算法:适用于含有负权边的图,可以检测负权环,时间复杂度为O(VE)。
  • Floyd-Warshall算法:用于计算所有节点对之间的最短路径,时间复杂度为O(V^3)。

合适的算法取决于图的特性(是否含有负权边、图的规模等)和具体的应用需求。

相关推荐
洛临_4 分钟前
【C语言】基础篇
c语言·算法
数据龙傲天12 分钟前
1688商品API接口:电商数据自动化的新引擎
java·大数据·sql·mysql
_.Switch24 分钟前
Python机器学习模型的部署与维护:版本管理、监控与更新策略
开发语言·人工智能·python·算法·机器学习
带带老表学爬虫41 分钟前
java数据类型转换和注释
java·开发语言
千里码aicood1 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
彭于晏6891 小时前
Android广播
android·java·开发语言
程序员-珍1 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
自由的dream2 小时前
0-1背包问题
算法
2401_857297912 小时前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
福大大架构师每日一题2 小时前
23.1 k8s监控中标签relabel的应用和原理
java·容器·kubernetes