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

来源:这里教程网 时间:2026-02-21 17:40:37 作者:

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)

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

相关推荐