Dijkstra(迪杰斯特拉)算法详解

目录

一、算法核心原理

[1. 适用场景](#1. 适用场景)

[2. 核心思想(贪心策略)](#2. 核心思想(贪心策略))

[3. 松弛操作(Relaxation)](#3. 松弛操作(Relaxation))

[4. 两种实现方式对比](#4. 两种实现方式对比)

二、方式一:邻接矩阵实现(O(n2),入门首选)

场景说明

[Java 完整代码 + 详细注释](#Java 完整代码 + 详细注释)

运行结果

[三、方式二:邻接表 + 优先队列(堆优化,工程常用 O(MlogN))](#三、方式二:邻接表 + 优先队列(堆优化,工程常用 O(MlogN)))

思路

[Java 完整代码(堆优化版)](#Java 完整代码(堆优化版))

关键点说明

四、面试高频总结


一、算法核心原理

1. 适用场景

单源最短路径算法 :计算一个起点 到图中所有其他节点的最短路径。

  • 要求:图中边权值必须非负(负数权边会导致算法失效)
  • 图类型:有向图 / 无向图均可

2. 核心思想(贪心策略)

  1. 维护两个集合:
    • 已确定最短路径集合 S:存放已经算出最短距离的节点
    • 未确定集合 U:剩余节点
  2. 初始化:起点距离为 0,其余节点距离为无穷大
  3. 循环贪心选择:
    • U 中选出当前距离起点最近 的节点 u,加入集合 S
    • u 为中转节点,松弛操作 :更新起点到 u 邻接节点的最短距离。
  4. 直到所有节点都加入 S,算法结束。

3. 松弛操作(Relaxation)

设:

  • dist[v]:起点到节点 v 当前最短距离
  • dist[u]:起点到节点 u 最短距离
  • w(u,v):边 u→v 的权重

公式: distv=min(distv, distu+w(u,v)) 含义:走「起点→u→v」是否比原来直接到 v 更近,是则更新距离。

4. 两种实现方式对比

实现方式 时间复杂度 特点
邻接矩阵 + 暴力遍历找最小节点 O(n2) 简单直观,适合节点数少的稠密图
邻接表 + 优先队列 (堆) O(MlogN) 效率更高,适合边数多的稀疏图

二、方式一:邻接矩阵实现(O(n2),入门首选)

场景说明

节点数量少,用二维数组存储图,逻辑最简单,便于理解流程。

Java 完整代码 + 详细注释

复制代码
import java.util.Arrays;

public class DijkstraMatrix {
    // 无穷大(代表两点不可达)
    private static final int INF = Integer.MAX_VALUE / 2; // 防溢出

    /**
     * @param graph 邻接矩阵图 graph[i][j] 表示 i→j 的边权
     * @param start 起始节点下标
     * @return 起点到每个节点的最短距离数组
     */
    public static int[] dijkstra(int[][] graph, int start) {
        int n = graph.length;
        // dist[]:起点到各节点最短距离
        int[] dist = new int[n];
        // visited[]:标记节点是否已确定最短路径(集合S)
        boolean[] visited = new boolean[n];

        // 1. 初始化距离数组
        Arrays.fill(dist, INF);
        dist[start] = 0; // 起点到自身距离为0

        // 遍历 n 轮,每轮确定一个节点的最短路径
        for (int i = 0; i < n; i++) {
            // 2. 第一步:从未访问节点中,找到距离起点最近的节点 u
            int u = -1;
            int minDist = INF;
            for (int j = 0; j < n; j++) {
                if (!visited[j] && dist[j] < minDist) {
                    minDist = dist[j];
                    u = j;
                }
            }

            if (u == -1) break; // 剩余节点均不可达,提前退出
            visited[u] = true; // 加入已确定集合

            // 3. 第二步:松弛操作,更新 u 所有邻接节点的距离
            for (int v = 0; v < n; v++) {
                // v 未访问 且 u→v 有边
                if (!visited[v] && graph[u][v] != INF) {
                    if (dist[v] > dist[u] + graph[u][v]) {
                        dist[v] = dist[u] + graph[u][v];
                    }
                }
            }
        }
        return dist;
    }

    public static void main(String[] args) {
        // 构建邻接矩阵:5个节点 0~4
        int[][] graph = {
                {INF, 2, 4, INF, INF},
                {2, INF, 1, 7, INF},
                {4, 1, INF, 1, 5},
                {INF, 7, 1, INF, 2},
                {INF, INF, 5, 2, INF}
        };

        int start = 0;
        int[] res = dijkstra(graph, start);

        System.out.println("起点 " + start + " 到各节点最短距离:");
        for (int i = 0; i < res.length; i++) {
            System.out.println("节点" + i + ":" + res[i]);
        }
    }
}

运行结果

复制代码
起点 0 到各节点最短距离:
节点0:0
节点1:2
节点2:3
节点3:4
节点4:6

三、方式二:邻接表 + 优先队列(堆优化,工程常用 O(MlogN))

节点 / 边数量大时,暴力找最小节点效率极低 ,用最小堆(优先队列) 快速取出当前距离最小的节点,是面试 / 开发主流写法。

思路

  1. 邻接表 List<List<Edge>> 存储图,节省空间。
  2. 优先队列(小顶堆)存放 (节点编号, 当前距离),自动按距离升序排序。
  3. 堆中会存在旧的无效记录(同一节点多条距离记录),遇到已确定最短路径的节点直接跳过。

Java 完整代码(堆优化版)

复制代码
import java.util.*;

public class DijkstraHeap {
    private static final int INF = Integer.MAX_VALUE / 2;

    // 边类:目标节点 + 边权重
    static class Edge {
        int to;
        int weight;
        Edge(int to, int weight) {
            this.to = to;
            this.weight = weight;
        }
    }

    // 堆元素:节点 + 起点到该节点的距离
    static class Node implements Comparable<Node> {
        int id;
        int dist;
        Node(int id, int dist) {
            this.id = id;
            this.dist = dist;
        }
        // 小顶堆:距离小的排在前面
        @Override
        public int compareTo(Node o) {
            return this.dist - o.dist;
        }
    }

    /**
     * 堆优化 Dijkstra
     * @param adj 邻接表
     * @param start 起点
     * @return 最短距离数组
     */
    public static int[] dijkstra(List<List<Edge>> adj, int start) {
        int n = adj.size();
        int[] dist = new int[n];
        boolean[] visited = new boolean[n];
        Arrays.fill(dist, INF);
        dist[start] = 0;

        // 最小优先队列
        PriorityQueue<Node> heap = new PriorityQueue<>();
        heap.add(new Node(start, 0));

        while (!heap.isEmpty()) {
            // 取出当前距离最小的节点
            Node cur = heap.poll();
            int u = cur.id;

            // 该节点已确定最短路径,跳过旧数据
            if (visited[u]) continue;
            visited[u] = true;

            // 遍历 u 的所有邻接边,执行松弛
            for (Edge edge : adj.get(u)) {
                int v = edge.to;
                int w = edge.weight;
                if (!visited[v] && dist[v] > dist[u] + w) {
                    dist[v] = dist[u] + w;
                    heap.add(new Node(v, dist[v])); // 新距离入堆
                }
            }
        }
        return dist;
    }

    public static void main(String[] args) {
        // 构建邻接表,5个节点 0~4
        int n = 5;
        List<List<Edge>> adj = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            adj.add(new ArrayList<>());
        }
        // 添加边(无向图,双向添加)
        adj.get(0).add(new Edge(1, 2));
        adj.get(0).add(new Edge(2, 4));

        adj.get(1).add(new Edge(0, 2));
        adj.get(1).add(new Edge(2, 1));
        adj.get(1).add(new Edge(3, 7));

        adj.get(2).add(new Edge(0, 4));
        adj.get(2).add(new Edge(1, 1));
        adj.get(2).add(new Edge(3, 1));
        adj.get(2).add(new Edge(4, 5));

        adj.get(3).add(new Edge(1, 7));
        adj.get(3).add(new Edge(2, 1));
        adj.get(3).add(new Edge(4, 2));

        adj.get(4).add(new Edge(2, 5));
        adj.get(4).add(new Edge(3, 2));

        int start = 0;
        int[] res = dijkstra(adj, start);
        System.out.println("起点 " + start + " 到各节点最短距离:");
        for (int i = 0; i < res.length; i++) {
            System.out.println("节点" + i + ":" + res[i]);
        }
    }
}

关键点说明

  1. 为什么不删除堆中旧数据? 堆不支持高效删除,因此重复入队,用 visited 标记过滤失效数据,这是标准优化写法。
  2. 负数权边问题 若存在负权边,贪心策略不再成立,Dijkstra 失效,需改用 Bellman-Ford / SPFA
  3. 不能求负权环 负权环会让路径无限变短,所有单源最短路径算法都无法处理。

四、面试高频总结

  1. 原理:贪心 + 松弛操作,单源最短路径,边权非负。
  2. 两种实现:
    • 邻接矩阵:O(n2),适合小图、稠密图。
    • 堆优化 + 邻接表:O(MlogN),工程 / 面试主流,适合大图、稀疏图。
  3. 核心步骤:选最近节点 → 标记已确定 → 松弛更新邻接点距离。
  4. 局限性:不支持负权边、负权环
相关推荐
x***r1511 小时前
burpsuite-1.4.07.jar 使用步骤详解(附Java环境配置与Burp Suite抓包教程)
java·开发语言·jar
Cosmoshhhyyy1 小时前
《Effective Java》解读第54条:返回零长度的数组或者集合,而不是null
java·开发语言·python
jsl_jsl_jsl1 小时前
☕ Java 高并发进阶(二):无锁并发与数据隔离——CAS、Unsafe 与 ThreadLocal 深度内核解密
java
kTR2hD1qb1 小时前
Keepalived 学习总结
java·服务器·学习
GIOTTO情1 小时前
智能舆情处置系统技术方案:基于NLP语义算法的全链路风险处置落地
人工智能·算法·自然语言处理
土狗TuGou1 小时前
SQL内功笔记 · 第9篇:UPDATE FROM 进阶——告别逐行子查询,拥抱集合更新
java·数据库·笔记·sql·mysql
郝学胜_神的一滴1 小时前
力扣 144:二叉树前序遍历的优雅实现
数据结构·算法
小谢小哥1 小时前
63-Gradle构建详解
java·后端·架构
Sam_Deep_Thinking1 小时前
一个业务场景只需要一个ThreadLocal实例
java·面试
zyl837211 小时前
Python 四大核心数据结构:列表、字典、元组、集合
数据结构·windows·python