
一.写题选哪个版本:
我怎么知道题目是节点少边少、节点少边多、节点多边少、节点多边多呢
这个问题问得非常实际!能问出这个问题,说明你已经完全不满足于"背代码"了。
我直接给你一套肉眼判断法,不用分析复杂度,扫一眼题目就能决定用哪个算法。
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)❌ 不成立,不会更新
四.刷题
两种方法都可以写:
只能用堆优化版:
1631. 最小体力消耗路径(因为该题是 节点多n*n 10^4 边少)
1514. 概率最大的路径(节点多边少)
复习:01dx课程🙂