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

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

在图论中,最短路径算法用于找到图中从一个顶点到另一个顶点的最短路径。常见的最短路径算法包括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)。

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

相关推荐
橙几几秒前
击败了90%的解法?Two Sum 从 O(n²) 到 O(n) 的优化之路
算法
大葱白菜1 分钟前
Java Set 集合详解:从基础语法到实战应用,彻底掌握去重与唯一性集合
java·后端
大葱白菜3 分钟前
Java Map 集合详解:从基础语法到实战应用,彻底掌握键值对数据结构
java·后端
添乱3 分钟前
「Java案例」判断是否是闰年的方法
java
FG.7 分钟前
Day22
java·面试
菜鸟的迷茫9 分钟前
Redis 缓存雪崩、穿透、击穿面试题深度解析与 Spring Boot 实战代码示例
java
叶子爱分享14 分钟前
经典排序算法之归并排序(Merge Sort)
算法·排序算法
珹洺20 分钟前
C++算法竞赛篇:DevC++ 如何进行debug调试
java·c++·算法
SHUIPING_YANG28 分钟前
根据用户id自动切换表查询
java·服务器·数据库
爱吃烤鸡翅的酸菜鱼40 分钟前
IDEA高效开发:Database Navigator插件安装与核心使用指南
java·开发语言·数据库·编辑器·intellij-idea·database