C# 狄克斯特拉算法方法 C#如何实现Dijkstra最短路径算法

3次阅读

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

C# 狄克斯特拉算法方法 C#如何实现Dijkstra最短路径算法

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)

实际编码中最容易被忽略的是溢出处理和过期节点过滤——这两个点一旦出错,结果既不报异常也不提示,只是静默返回错误路径。

text=ZqhQzanResources