你知道的C++中最快的Dijkstra实现是什么?

12

我最近为我的项目添加了Dijkstra算法的第三个版本,用于单源最短路径。

我意识到有很多不同的实现方式,在性能上和大型图中结果的质量上差异很大。在我的数据集(> 100,000个顶点)中,运行时间从20分钟到几秒钟不等。最短路径也会有1-2%的差异。

你知道哪种是最好的实现方式吗?

编辑: 我的数据是一个水力网络,每个节点有1到5个顶点,类似于街道地图。我对已经加速的算法做了一些修改(使用排序列表来处理所有剩余的节点),现在在更短的时间内得出相同的结果。我寻找这样的算法已经相当长时间了。我想知道是否已经存在这样一种实现。

我无法解释结果的轻微差异。我知道Dijkstra并非启发式算法,但所有的实现似乎都是正确的。速度更快的解决方案具有更短路径的结果。我只使用双精度数学。

编辑2: 我发现找到的路径中的差异确实是我的问题。我为某些顶点插入了特殊处理(仅在一个方向上有效),并在另一个实现中忘记了这一点。

但是我仍然非常惊讶于Dijkstra算法可以通过以下更改显著加速: 通常,Dijkstra算法包含类似以下循环的过程:

MyListType toDoList; // List sorted by smallest distance 
InsertAllNodes(toDoList);
while(! toDoList.empty())
{
    MyNodeType *node = *toDoList.first();
    toDoList.erase(toDoList.first());
    ...
}

如果您稍微改变一下这个代码,它仍然能够达到同样的效果,但性能会更好:

MyListType toDoList; // List sorted by smallest distance 
toDoList.insert(startNode);
while(! toDoList.empty())
{
    MyNodeType *node = *toDoList.first();
    toDoList.erase(toDoList.first());
    for(MyNeigborType *x = node.Neigbors; x != NULL; x++)
    {
        ...
        toDoList.insert(x->Node);
    }
}

看起来这个修改不仅减少了运行时间的一个数量级,而是一个指数级别。我的运行时间从30秒减少到不到2秒。我在任何文献中都找不到这种修改方法。很明显原因在于排序列表。插入/删除在具有10万个元素的情况下表现得比手头上少得多。

回答:

经过大量搜索我自己找到了答案。答案显然是:boost graph lib。真是太神奇了 - 我找了好一段时间都没有找到。如果你认为Dijkstra实现之间没有性能差异,请参见维基百科


34
我有一个朋友可以在大约3分钟内用C++实现Dijkstra算法。我没有听说过任何更快的实现方法。 - jbasko
5
建议您检查这些实现,如果它们正确的话,它们应该都会返回相同的最短路径... Dijkstra算法不是启发式算法... - jerryjvl
2
最短路径的差异可能是由于使用双精度数学计算引起的,因为在对一长串双精度数进行求和时会产生舍入误差。不同的求和顺序可能会产生不同的误差。你能否在整数上测试你的实现? - Yuval F
1
任何来到这里并阅读此文的人都应该意识到,第一个变体不是Dijkstra算法。如果toDoList的顺序是预先知道的,那么可以直接读出最短路径。关键是toDoList应该是一个优先队列,元素在发现时添加。目标是创建一个最佳优先搜索,而第一个版本无法实现这一点。 - Unapiedra
1
@Unapiedra 是的。我们只能猜测 InsertAllNodes 做了什么,但如果它将源节点插入到 0 优先级,其余节点插入到 Infinity,那么它可能会工作,尽管速度较慢,因为队列必须无谓地处理所有顶点(加上已发现的顶点)。这时候,大 O 复杂度就真正得到了“实现”,因为队列始终处于其最大渐近大小。如果源节点和汇节点之间没有路径,则可以弹出汇节点(由 InsertAllNodes 添加)并返回 Infinity 距离,而不是像第二个变体中那样耗尽队列。 - Adam
5个回答

12

目前已知的道路网络(>1百万节点)最佳实现具有以微秒为单位表示的查询时间。更多细节请参见第9届DIMACS实现挑战赛(2006年)。请注意,这当然不仅仅是Dijkstra算法,而整个重点在于更快地获得结果。


2
我发现你提到的挑战在http://www.dis.uniroma1.it/~challenge9/papers.shtml下非常令人印象深刻。谢谢。 - RED SOFT ADAIR
1
请参阅http://algo2.iti.uni-karlsruhe.de/english/1087.php了解收缩分层法。 - Ants Aasma

3
也许我没有回答你的问题。我的观点是,如果有更有效的算法可以解决你的问题,为什么要使用Dijkstra算法呢?如果你的图满足三角形不等式(它是一个欧几里得图),即: |ab| + |bc| > |ac|
(从节点a到节点b的距离加上从节点b到节点c的距离大于从节点a到节点c的距离),那么你可以应用A *算法。这个算法非常高效。否则,请考虑使用启发式算法。实现并不是主要问题,所使用的算法才是关键。

我认为你甚至不需要三角形属性。你只需要一个好的启发式函数。使用一个贫乏但有效的启发式函数,A*算法会退化成Dijkstra算法。 - MSalters
没错,你是对的。我所说的是一种可能可接受的启发式方法。 - Luixv

2
我想要强调两点: 1)Dijkstra与A* Dijkstra算法是一种动态规划算法,不是启发式算法。A*算法是启发式算法,因为它还使用启发式函数(比如h(x))来“估计”一个点x离终点有多近。这个信息被用于后续的决策中,以确定哪些节点应该被探索。
对于欧几里得图等情况,A*算法表现良好,因为启发式函数容易定义(例如可以直接使用欧几里得距离)。然而,对于非欧几里得图形,则可能更难定义启发式函数,错误的定义可能会导致非最优路径。
因此,Dijkstra算法具有优势,因为它适用于任何一般图形(除了在某些情况下A*更快)。某些实现可能会交替使用这些算法,导致不同的结果。
2)Dijkstra算法(以及其他算法如A*)使用优先队列来获取下一个要探索的节点。一个很好的实现可能会使用堆而不是队列,甚至更好的实现可能会使用斐波那契堆。这可能解释了不同的运行时间。

1
上次我检查时,迪杰斯特拉算法返回一个最优解。所有“真正的”迪杰斯特拉算法的实现每次都应该返回相同的结果。
类似地,渐近分析告诉我们,对特定实现进行微小优化不会随着输入规模的增加而显着影响性能。

当存在多个长度(成本)相同(在舍入误差范围内)的“最短路径”时,唯一的区别可能是。如果某些实现无法找到最短路径,则它是有缺陷的。除非您的节点距离比率非常高(如1e20或更高),否则即使使用浮点数,也绝对不应该看到1-2%的差异,更不用说双精度浮点数了。 - Suma

0

这将取决于很多因素。你对输入数据了解多少?它是密集的还是稀疏的?这将改变哪个版本的算法最快。

如果它是密集的,只需使用矩阵。如果它是稀疏的,您可能需要查看更有效的数据结构来查找下一个最接近的顶点。如果您对数据集的信息不仅限于图形连接性,则可以尝试使用其他算法,例如A*。

问题在于,并没有“最快”的算法版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接