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

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

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

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

相关推荐
智慧老师21 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm22 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
古希腊掌管学习的神43 分钟前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca44 分钟前
洛谷 P1706 全排列问题 C语言
算法
浊酒南街1 小时前
决策树(理论知识1)
算法·决策树·机器学习
V+zmm101341 小时前
基于微信小程序的乡村政务服务系统springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
就爱学编程1 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
学术头条1 小时前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学
Oneforlove_twoforjob1 小时前
【Java基础面试题025】什么是Java的Integer缓存池?
java·开发语言·缓存
xmh-sxh-13141 小时前
常用的缓存技术都有哪些
java