Dijkstra算法(朴素版&堆优化版)

一.写题选哪个版本:

我怎么知道题目是节点少边少、节点少边多、节点多边少、节点多边多呢

这个问题问得非常实际!能问出这个问题,说明你已经完全不满足于"背代码"了

我直接给你一套肉眼判断法,不用分析复杂度,扫一眼题目就能决定用哪个算法。


1、四种情况的判断标准(直接看输入范围)

题目一定会给出数据范围,这就是你的判断依据:

情况 V(节点数) E(边数) 特点 算法选择
节点少边少 ≤ 500 ≤ 5000 稀疏图 堆优化 Dijkstra / 朴素也行
节点少边多 ≤ 500 ≈ V²(完全图) 稠密图 朴素 Dijkstra(O(V²) 最快)
节点多边少 10⁴ ~ 10⁵ 10⁵ ~ 10⁶ 稀疏图 堆优化 Dijkstra(必须)
节点多边多 10⁴ ~ 10⁵ ≈ V²(几乎不可能出现) 稠密大图 基本不会出现(会爆内存)

2、具体题目对照

题目 V 范围 E 范围 类型 推荐算法
743 网络延迟 n ≤ 100 E ≤ 6000 节点少边多(稠密) 朴素 Dijkstra
1334 阈值距离 n ≤ 100 E 可能很多 节点少边多 朴素 Dijkstra
1631 最小体力 m×n ≤ 10⁴ ≈ 4V(网格图) 节点多边少 堆优化 Dijkstra
1584 连接点 n ≤ 1000 完全图(V²/2) 节点中等边极多 朴素 Prim(稠密)

3、更简单的判断口诀(面试够用)

V 小(≤ 500)→ 无脑朴素
V 大(> 1000)→ 无脑堆优化

为什么?

  • V 小的时候,O(V²) 最多 25 万,堆优化的常数反而吃亏

  • V 大的时候,O(V²) 会爆炸(1 亿+),必须用 O(E log V)

二.dijkstra朴素版

1.基础代码:

java 复制代码
package com.Dijkstra;

import java.util.Arrays;

public class Dijkstra {
    public static void main(String[] args) {
        int n=5;
        int INF=Integer.MAX_VALUE/2;
        int[][] graph={
                {0, 2, INF, 6, INF},
                {2, 0, 3, 8, 5},
                {INF, 3, 0, INF, 7},
                {6, 8, INF, 0, 9},
                {INF, 5, 7, 9, 0}
        };
        int start=0;
        int[] dist=dijkstra(n,graph,start);
        System.out.println("从0节点到其他所有节点的距离:");
        for (int i = 0; i < n; i++) {
            if(i==start) continue;
            System.out.println("0->"+i+": "+dist[i]);
        }
    }
    public static int[] dijkstra(int n,int[][] graph,int start){
        int[] dist=new int[n];
        boolean[] visited=new boolean[n];
        int[] parent=new int[n];
        //初始化
        Arrays.fill(dist,Integer.MAX_VALUE);
        Arrays.fill(parent,-1);
        dist[start]=0;
        for (int i = 0; i < n; i++) {
            int cur=-1;
            int min=Integer.MAX_VALUE;
            //找最小
            for (int j = 0; j < n; j++) {
                if(!visited[j]&&dist[j]<min){
                    min=dist[j];
                    cur=j;
                }
            }
            if(cur==-1) break;
            visited[cur]=true;
            //更新邻居路径
            for (int j = 0; j < n; j++) {
                if(!visited[j]){
                    int newDist=dist[cur]+graph[cur][j];
                    if(newDist<dist[j]){
                        dist[j]=newDist;
                        parent[j]=cur;
                    }
                }
            }
        }
        return dist;
    }
}

2.思路:

dist[]存的是七点到该节点的最短路径

visited[]是该节点是否激活(是否被访问过),true是激活,false是未激活

parent[]存的是得到该节点的最短路径的上一个节点,也就是父结点,看是谁使它更新的最短路径

先找最小节点并用cur记录,再更新邻居节点的最短路径

细节:

1.注意是有向图还是无向图,如果是无向图再更新邻居节点的时候就要分类讨论谁作为邻居和cur本身

2.newDist的计算是dist[cur]+该两点之间的路径长度,进行newDist和dist[邻居]比较

三.dijkstra堆优化版

1.基础代码

java 复制代码
package com.Dijkstra;

import java.util.*;

public class DijkstraHeap {
    /**
     * Dijkstra 堆优化版(求从 start 到所有点的最短距离)
     * @param n 节点个数(编号 0 ~ n-1)
     * @param graph 邻接表,graph[u] = List<int[]{v, w}> 表示 u->v 边权 w
     * @param start 起点
     * @return dist 数组,dist[i] 表示 start 到 i 的最短距离
     */
    public static int[] dijkstra(int n, List<int[]>[] graph, int start) {
        // 1. 距离数组,初始化为无穷大
        int[] dist = new int[n];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[start] = 0;

        // 2. 优先队列(小顶堆),存 [节点, 当前最短距离]
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
        pq.offer(new int[]{start, 0});

        // 3. 主循环
        while (!pq.isEmpty()) {
            int[] cur = pq.poll();
            int u = cur[0];
            int d = cur[1];

            // 如果队列里的距离比已经记录的远,跳过(重要优化)
            if (d > dist[u]) continue;

            // 遍历所有邻居
            for (int[] edge : graph[u]) {
                int v = edge[0];
                int w = edge[1];
                int newDist = dist[u] + w;
                if (newDist < dist[v]) {
                    dist[v] = newDist;
                    pq.offer(new int[]{v, newDist});
                }
            }
        }
        return dist;
    }
}

2.思路

利用堆每次poll最小的节点,然后更新邻居节点的dist,在更新的同时要进行pq.offer( );

解释:if (d > dist[u]) continue;

在操作的过程中会出现更新的情况,假如说节点2的现在的最短路径是dist[2]=5,然后在往下继续遍历的过程中节点2的最短路径现在是d=10, pq = {2[10], 2[8]},然后根据pq的性质是升序排列的pq就会先弹出的是 2[8],再弹出 2[10],如果不加if (d > dist[u]) continue;让 2[10]直接退出,会造成它会向下错误的改变邻居节点的dist

举例:

修改一点图结构,加一个节点 3:

text

复制代码
0 → 1 (5)
0 → 2 (10)
1 → 2 (3)
2 → 3 (1)

目标还是 0 → 2(顺便看一下 3 会不会被错误更新)。

正确最短路径:
  • 0 → 1 → 2 → 3

    5 + 3 + 1 = 9

如果没有 if (d > dist[u]) continue

初始状态:

text

复制代码
dist = [0, ∞, ∞, ∞]
pq = [0[0]]

第 1 步:弹出 [0,0]

  • 更新 [1,5]、[2,10]

    pq = [1[5], 2[10]]

第 2 步:弹出 [1,5]

  • 更新 [2,8]

    pq = [2[8], 2[10]]

第 3 步:弹出 [2,8] ✅

  • 更新 [3,9]

    pq = [2[10], 3[9]]

第 4 步弹出 [2,10]❌ 旧数据

  • 没有 continue 的话,会执行这一行:
    newDist = dist[2] + 1 = 10 + 1 = 11

  • 比较 11 < dist[3] (9) ❌ 不成立,不会更新

四.刷题

两种方法都可以写:

743. 网络延迟时间

1334. 阈值距离内邻居最少的城市

只能用堆优化版:

1631. 最小体力消耗路径(因为该题是 节点多n*n 10^4 边少)

1514. 概率最大的路径(节点多边少)

复习:01dx课程🙂

相关推荐
苏三说技术1 小时前
美团二面:高并发下如何保证接口幂等性?
java·数据库
星星码️1 小时前
LeetCode刷题简单篇之反转字母
c++·算法·leetcode
yaoxin5211231 小时前
402. Java 文件操作基础 - 读取二进制文件
java·开发语言·python
沐浴露z1 小时前
面试官:静态变量与非静态成员变量的区别?别再死记硬背了!
java·jvm
极创信息1 小时前
信创软件快速适配信创改造,实战落地思路
java·大数据·数据库·人工智能·mvc·软件工程·hibernate
摇滚侠2 小时前
Java 项目教程《尚庭公寓》标签管理、自定义 converter 14 - 18
java·elasticsearch·架构
程序员清风2 小时前
科普一下:大模型Token的收费逻辑!
java·后端·面试
Nyarlathotep01132 小时前
并发集合类(4):ArrayBlockingQueue
java·后端
naturerun2 小时前
螺旋形遍历奇数阶矩阵
c++·算法·矩阵