用 PriorityQueue<telement tpriority></telement>
实现 Dijkstra 的核心逻辑
.NET 6+ 自带的
PriorityQueue<telement tpriority></telement>是实现 Dijkstra 最自然的选择,它替代了手动维护最小堆或排序列表的麻烦。关键不是“怎么写循环”,而是确保每次出队的是当前已知最短距离的节点——这依赖于入队时传入的
TPriority(即距离值)严格单调非负,且更新时**不修改已入队节点的优先级**(
PriorityQueue不支持降键操作)。
所以标准做法是:允许同一节点多次入队,但第一次出队即为最短路径,后续再遇到该节点直接跳过。
图用Dictionary<int list to int weight>></int>表示,节点用整数 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 成立时,除了更新距离,还要执行 <code>previous[v] = u从终点倒推回起点:用
Stack<int></int>收集节点,避免递归或反转数组 注意:若
previous[end] == -1,说明不可达,别强行构造路径
稀疏图用邻接表,稠密图考虑是否值得换 Array
手写堆
PriorityQueue平均时间复杂度是 O((V+E) log V),对稀疏图足够好;但若边数 E 接近 V²(比如全连接网格),log V 开销会明显,而手写二叉堆(用
int[]存储)可减少对象分配和泛型开销。 99% 的业务场景直接用
PriorityQueue,可读性和维护性远胜微小性能差异 只有在高频调用(如每秒数百次)、节点数 > 10⁵、且 profiling 确认堆操作是瓶颈时,才考虑替换 别用
SortedSet模拟优先队列——它不支持重复距离,且删除任意元素是 O(n)
实际编码中最容易被忽略的是溢出处理和过期节点过滤——这两个点一旦出错,结果既不报异常也不提示,只是静默返回错误路径。
