应使用 long[] 距离数组和 long.MaxValue 初始化,避免 int 溢出;允许多次入队同一节点,出队时用 distances[current] != currentPriority 过滤过期条目。

用 PriorityQueue 实现 Dijkstra 的核心逻辑
.NET 6+ 自带的 PriorityQueue 是实现 Dijkstra 最自然的选择,它替代了手动维护最小堆或排序列表的麻烦。关键不是“怎么写循环”,而是确保每次出队的是当前已知最短距离的节点——这依赖于入队时传入的 TPriority(即距离值)严格单调非负,且更新时**不修改已入队节点的优先级**(PriorityQueue 不支持降键操作)。
所以标准做法是:允许同一节点多次入队,但第一次出队即为最短路径,后续再遇到该节点直接跳过。
- 图用
Dictionary表示,节点用整数 ID(如 0 ~ n-1)> - 初始化距离数组
distances全为int.MaxValue,起点设为 0 - 入队格式:
queue.Enqueue(nodeId, distances[nodeId]) - 出队后检查
if (distances[current] != currentPriority) continue;过滤过期条目
int.MaxValue 作为无穷大带来的溢出风险
当边权较大(比如接近 int.MaxValue / 2)且路径含多条边时,累加 distances[u] + weight 可能溢出为负数,导致错误松弛甚至死循环。这不是理论问题,真实数据中容易触发。
- 不要用
int.MaxValue做初始距离,改用long.MaxValue并把距离数组声明为long[] - 边权本身可以仍是
int,但所有比较和加法需提升到long - 如果必须用
int,至少加溢出检查:if (distances[u] != int.MaxValue && weight
重建最短路径而非只算距离
多数实际场景需要知道具体走哪几步,不只是一个数字。Dijkstra 本身不输出路径,但可以在松弛时同步记录前驱节点。
- 额外维护
int[] previous = new int[n],初始化为 -1 - 当
distances[u] + w 成立时,除了更新距离,还要执行previous[v] = u - 从终点倒推回起点:用
Stack收集节点,避免递归或反转数组 - 注意:若
previous[end] == -1,说明不可达,别强行构造路径
稀疏图用邻接表,稠密图考虑是否值得换 Array 手写堆
PriorityQueue 平均时间复杂度是 O((V+E) log V),对稀疏图足够好;但若边数 E 接近 V²(比如全连接网格),log V 开销会明显,而手写二叉堆(用 int[] 存储)可减少对象分配和泛型开销。
- 99% 的业务场景直接用
PriorityQueue,可读性和维护性远胜微小性能差异 - 只有在高频调用(如每秒数百次)、节点数 > 10⁵、且 profiling 确认堆操作是瓶颈时,才考虑替换
- 别用
SortedSet模拟优先队列——它不支持重复距离,且删除任意元素是 O(n)
实际编码中最容易被忽略的是溢出处理和过期节点过滤——这两个点一旦出错,结果既不报异常也不提示,只是静默返回错误路径。










