Java实现Dijkstra最短路径算法(邻接表 + 优先队列)

一、Dijkstra 算法简介

Dijkstra(迪杰斯特拉)算法是图论中最经典的单源最短路径算法,用于计算一个源节点到图中所有其他节点的最短路径长度。它由荷兰计算机科学家艾兹格·迪杰斯特拉于1956年提出,广泛用于路由选择、导航系统、网络通信等领域。

本代码使用邻接表存储图结构,配合优先队列(PriorityQueue)实现,时间复杂度为 O(E log V),其中 E 是边数、V 是顶点数,适合处理节点较多但边相对稀疏的图。

需要注意的是,Dijkstra 算法要求图中不存在负权边。若存在负权边,应改用 Bellman-Ford 算法或 SPFA。

二、核心概念

理解本代码前,需要先掌握以下几个关键概念:

  • 邻接表:用 Map<Integer, List<Edge>> 存储图,每个顶点对应其出边列表

  • 边(Edge):包含终点to和权重weight两个属性

  • 优先队列:每次取出当前距离最小的节点,保证贪心选择的正确性

  • dist数组:记录从起点到各节点的当前最短距离,初始为无穷大

  • 松弛操作:若通过当前节点能获得更短路径,则更新dist并加入队列

  • visited数组:标记节点是否已确定最短路径,避免重复处理

Dijkstra 的核心思想是贪心:每次从未处理的节点中选出距离起点最近的节点,确定其最短路径,然后通过它松弛其邻居节点。

三、代码整体架构

本代码由 Edge 边类、Node 节点类(实现 Comparable 接口用于优先队列排序)、dijkstra 核心算法和 main 主函数四部分组成。main 函数构造了一个包含0、1、2、3四个节点的无向图,计算从节点0出发到所有节点的最短距离并打印。

四、核心代码逐段解析

4.1 Edge 边类

Edge 类表示一条有向边,包含两个成员:to 表示边的终点顶点编号,weight 表示边的权重。在邻接表中,每个顶点对应的 List<Edge> 存储了从该顶点出发的所有边。

4.2 Node 节点类与优先队列

Node 类封装了顶点编号 vertex 和该顶点到起点的当前距离 dist,并实现 Comparable 接口。compareTo 方法按 dist 升序比较,这样 PriorityQueue 每次弹出(poll)的就是当前距离最小的节点,这正是 Dijkstra 贪心策略的关键。

4.3 dijkstra 核心算法

算法初始化 dist 数组为无穷大(Integer.MAX_VALUE),将起点距离设为0并加入优先队列。主循环中每次弹出距离最小的节点,若已访问则跳过,否则标记为已访问,并遍历其所有出边进行松弛:如果经过当前节点到达邻居的距离更短,就更新 dist 并重新加入队列。循环结束后,dist 数组即为起点到各节点的最短距离。

4.4 main 函数构建图与输出

main 函数通过 HashMap 构建邻接表表示的图,节点0、1、2、3之间由多条带权边相连,构成一个无向图。调用 dijkstra(graph, 0, 4) 计算从节点0出发到4个节点的最短距离,并按格式打印结果。

五、完整源代码

以下是完整的可运行代码,可直接复制编译运行。

java 复制代码
import java.util.*;
 
public class Dijkstra {
    static class Edge {
        int to, weight;
        Edge(int to, int weight) {
            this.to = to;
            this.weight = weight;
        }
    }
 
    static class Node implements Comparable<Node> {
        int vertex, dist;
        Node(int v, int d) {
            vertex = v;
            dist = d;
        }
        @Override
        public int compareTo(Node other) {
            return Integer.compare(this.dist, other.dist);
        }
    }
 
    public static int[] dijkstra(Map<Integer, List<Edge>> graph, int start, int n) {
        int[] dist = new int[n];
        int[] parent = new int[n];
        Arrays.fill(dist, Integer.MAX_VALUE);
        Arrays.fill(parent, -1);
 
        dist[start] = 0;
        PriorityQueue<Node> pq = new PriorityQueue<>();
        pq.offer(new Node(start, 0));
 
        boolean[] visited = new boolean[n];
 
        while (!pq.isEmpty()) {
            Node cur = pq.poll();
            if (visited[cur.vertex]) continue;
            visited[cur.vertex] = true;
 
            for (Edge e : graph.getOrDefault(cur.vertex, new ArrayList<>())) {
                int newDist = cur.dist + e.weight;
                if (newDist < dist[e.to]) {
                    dist[e.to] = newDist;
                    parent[e.to] = cur.vertex;
                    pq.offer(new Node(e.to, newDist));
                }
            }
        }
        return dist;
    }
 
    public static void main(String[] args) {
        Map<Integer, List<Edge>> graph = new HashMap<>();
        graph.put(0, Arrays.asList(new Edge(1, 1), new Edge(2, 4)));
        graph.put(1, Arrays.asList(new Edge(0, 1), new Edge(2, 2), new Edge(3, 5)));
        graph.put(2, Arrays.asList(new Edge(0, 4), new Edge(1, 2), new Edge(3, 1)));
        graph.put(3, Arrays.asList(new Edge(1, 5), new Edge(2, 1)));
 
        int[] dist = dijkstra(graph, 0, 4);
 
        System.out.println("最短距离:");
        for (int i = 0; i < dist.length; i++) {
            System.out.printf("  0 → %d: %d%n", i, dist[i]);
        }
    }
}

六、运行结果

在本机 JDK 11 环境实际编译运行,输出如下:

java 复制代码
最短距离:
  0 → 0: 0
  0 → 1: 1
  0 → 2: 3
  0 → 3: 4

从结果可以看出:从节点0出发,到自身距离为0;到节点1的最短路径为0→1(权重1);到节点2的最短路径为0→1→2(1+2=3,而非直接0→2的4);到节点3的最短路径为0→1→2→3(1+2+1=4,而非0→1→3的6)。结果验证了算法能够正确找到经过中转的更短路径,符合Dijkstra算法预期。

七、编译运行方法

将代码保存为 Dijkstra.java,在命令行编译运行:

java 复制代码
javac Dijkstra.java
java Dijkstra

也可以直接在 IntelliJ IDEA / Eclipse 中新建类,粘贴代码后运行 main 方法。需要 JDK 8 及以上版本。

八、注意事项与知识点总结

下表汇总了本实现的关键知识点与常见注意事项:

|---------------|-------------------------------------|
| 要点 | 说明 |
| 适用范围 | 求单源最短路径,要求图中无负权边 |
| 贪心策略 | 每次选出距离最小的节点确定其最短路径 |
| 优先队列 | Node实现Comparable按距离排序,poll取出最小值 |
| 松弛操作 | 发现更短路径时更新dist并重新入队 |
| visited标记 | 标记已确定最短路径的节点,避免重复处理 |
| 邻接表存储 | Map<顶点, List<Edge>>,适合稀疏图 |
| 时间复杂度 | O(E log V),E为边数、V为顶点数 |

九、拓展方向

本文实现了基于邻接表和优先队列的Dijkstra算法,后续可以在此基础上进一步扩展:

  • 输出最短路径本身(利用parent数组回溯路径节点)

  • 使用优先队列保存完整的路径信息,支持路径还原

  • 对比Floyd-Warshall全源最短路径算法

  • 在负权边场景改用Bellman-Ford或SPFA算法

  • 扩展到最小生成树算法(Prim与Dijkstra思想相近)

相关推荐
2601_970703831 小时前
工业除湿机租赁:把一份租金合同拆开看
大数据·数据结构·算法·数据库架构·推荐算法
龙亘川1 小时前
综合素质评价业务建模:从过程性评价到成长档案全链路闭环
数据库·智慧城市·数据可视化
数据库小组1 小时前
NineData 实战教程:MySQL 5.5 数据迁移同步到 MySQL 8.0(跨版本升级)
数据库·mysql·mysql优化·数据库开发·数据库迁移·版本升级·数据复制
步行cgn1 小时前
Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示
java·spring·mvc
zhangjw342 小时前
1-5 PostgreSQL 数据类型全集:基础类型、字符串、数值、时间类型
数据库·postgresql
ZhengEnCi2 小时前
同样的模型,为什么本地和云 API 差别那么大:量化精度、聊天模板与采样参数的三大元凶
人工智能·算法·llm
lgcgkCQ2 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
王中阳Go2 小时前
5.27 分钟干完后端和全部测试:一场三小时笔试的五个失分点,四个不在代码上
大数据·数据库·人工智能·后端·大语言模型
Wang's Blog2 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友