我查看了你在评论中发布的Dijkstra算法链接,我相信这是你效率低下的根源。在内部Dijkstra循环中,它使用了一种极其不优化的方法来确定下一个要探索的节点(即每一步都线性扫描每个节点)。问题代码位于两个位置。第一个就是这段代码,尝试找到下一个要操作的节点:
mini = -1
for (i = 1
if (!visited[i] && ((mini == -1) || (d[i] < d[mini])))
mini = i
由于此代码嵌套在访问每个节点的循环中,所以复杂度(如链接中所述)为O(|V|2),其中 |V| 是节点数。 在您的情况下,由于 |V| 为 30,000,这个循环总共将迭代九亿次。 这非常慢(正如您所见),但是没有理由必须做这么多的工作。
另一个麻烦点在于这里,它试图找到应使用哪个图中的边来减少其他节点的成本:
for (i = 1
if (dist[mini][i])
if (d[mini] + dist[mini][i] < d[i])
d[i] = d[mini] + dist[mini][i]
这段代码扫描整个邻接矩阵中的一行,查找要考虑的节点,这需要时间O(n),无论一个节点有多少出边。
虽然你可以尝试将此版本的Dijkstra算法改进为更优化的实现方式,但我认为在这里正确的选项是放弃这段代码,寻找更好的Dijkstra算法实现。例如,如果使用从维基百科文章
伪代码实现的
二叉堆,则可以使Dijkstra算法运行时间为O(|E| log |V|)。在您的情况下,该值仅略高于两百万,比当前方法快约450倍。这是一个巨大的差异,我相信通过更好的Dijkstra算法实现,您最终会使代码完成时间显著缩短。
此外,您可能还希望考虑并行运行所有Dijkstra搜索,正如Jacob Eggers所指出的那样。这对于每个处理器都可以为您提供额外的速度提升。结合上述(也更为关键)的修复措施,这应该能够极大地提高性能。
如果您计划在更密集的数据集上运行此算法(其中边的数量接近于|V|
2 / log |V|),则可能要考虑切换到
Floyd-Warshall算法。对每个节点运行Dijkstra算法一次(有时称为
Johnson算法)需要时间O(|V||E| log |V|),而使用Floyd-Warshall算法需要时间O(|V|
3)。但是,对于您提到的数据集来说,图足够稀疏,运行多个Dijkstra实例应该没有问题。
希望这能帮助到您!