【问题标题】:Why does Dijkstra's algorithm need a priority queue when this regular queue version is also correct?当这个常规队列版本也正确时,为什么 Dijkstra 的算法需要优先级队列?
【发布时间】:2016-04-28 16:48:32
【问题描述】:

我已阅读以下内容,但请查看下面的代码。

Why Dijkstra's Algorithm uses heap (priority queue)?

我有两个版本的 dijkstra,一个带有 PQueue 的好版本,一个带有常规链表队列的坏版本。

public static void computeDijkstra(Vertex source) {
    source.minDistance = 0.;
    Queue<Vertex> vertexQueue = new PriorityQueue<Vertex>();
    // Queue<Vertex> vertexQueue = new LinkedList<Vertex>();
    vertexQueue.add(source);

    while (!vertexQueue.isEmpty()) {
        Vertex fromVertex = vertexQueue.poll();

        if (fromVertex.neighbors != null) {
            for (Edge currentEdge : fromVertex.neighbors) {
                Vertex toVertex = currentEdge.target;
                if (currentEdge.weight + fromVertex.minDistance < toVertex.minDistance) {
                    toVertex.minDistance = currentEdge.weight + fromVertex.minDistance;
                    toVertex.previous = fromVertex;
                    vertexQueue.add(toVertex);
                }
            }
        }
    }
}

public static void computeDijkstraBad(Vertex source) {
    source.minDistance = 0.;
    // Queue<Vertex> vertexQueue = new PriorityQueue<Vertex>();
    Queue<Vertex> vertexQueue = new LinkedList<Vertex>();
    vertexQueue.add(source);

    while (!vertexQueue.isEmpty()) {
        Vertex fromVertex = vertexQueue.poll();

        if (fromVertex.neighbors != null) {
            for (Edge currentEdge : fromVertex.neighbors) {
                Vertex toVertex = currentEdge.target;
                if (currentEdge.weight + fromVertex.minDistance < toVertex.minDistance) {
                    toVertex.minDistance = currentEdge.weight + fromVertex.minDistance;
                    toVertex.previous = fromVertex;
                    vertexQueue.add(toVertex);
                }
            }
        }
    }
}

我还使用如下文本文件创建图形

0, 1, 2, 3, 4, 5, 6 // vertices
0, 6 // from and to vertex
1, (2-5, 0-4, 4-6) // from vertex 1 it will have edge to 2 with weight 5 ...
0, (4-3, 3-7)
4, (2-11, 3-8)
3, (2-2, 5-5)
2, (6-2, 5-10)
5, (6-3)

两种实现都呈现以下[0, 3, 2, 6],它确实是从 0 到 6 的最短路径!

现在我们知道,如果使用 Simple BFS 来寻找正整数的最短距离,就会出现找不到最小路径的情况。那么,有人可以给我一个反例,我的 Bad 实现将无法打印图表的正确路径。随时以我使用的图形格式(示例文本文件格式)给我答案。

到目前为止,我所拥有的所有图表,两种实现都呈现了正确的结果。这不应该发生,因为糟糕的实现是运行时 (E+V),我们知道如果没有至少 E log V,我们就无法找到最短路径。

另一个例子,

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
0, 10
0, (1-9, 2-10, 3-11)
1, (4-1, 5-7)
2, (4-4, 5-3, 6-5)
3, (5-1, 6-4)
4, (7-9, 8-14, 5-3)
5, (7-4, 8-5, 9-9, 6-2)
6, (8-2, 9-2)
7, (10-3)
8, (10-2)
9, (10-5)

两种实现都渲染 [0, 3, 5, 6, 8, 10],这是从 0 到 10 的正确最短路径。

【问题讨论】:

  • 只是来这里说你的糟糕实现也是O(E logV)。实际上,Dijkstra 的实现并没有改善最坏的情况,所以它并没有改变 BigO 的情况,但它提高了普通情况的性能
  • 嗨,你能详细说明为什么坏的是E log v吗?这是一个简单的 BFS,具有恒定时间的链表插入和删除。
  • 您拥有与良好实现相同的循环结构。唯一的区别是测试的顺序。在更好的 Dijkstra 中,当您发现第一个完整的解决方案并确定它是最佳解决方案时,您可以停下来,这就是为什么在常见情况下它被认为更好的原因。在您的代码上,两者都具有相同的运行时间,因为它们都检查所有边缘(但这不是必需的)
  • 另外,您在顶点上有一个循环,对于每个顶点,边上都有一个循环。这绝对不是 E+V。

标签: algorithm graph dijkstra


【解决方案1】:

我相信你给出的算法是正确的,但它不如 Dijkstra 的算法高效。

在高层次上,您的算法通过找到一个“活动”节点(距离已降低的节点),然后扫描传出边以激活所有需要更新其距离的相邻节点。请注意,同一个节点可以多次激活 - 事实上,一个节点可能会在其候选距离下降时被激活一次,这可能在算法运行中发生多次。此外,如果候选距离多次下降,您实施的算法可能会将同一节点多次放入队列,因此可能不需要除第一个之外的所有出队。总体而言,我预计这会对大型图表造成相当大的运行时影响。

从某种意义上说,您实现的算法是最短路径算法,但它不是 Dijkstra 算法。主要区别在于,Dijkstra 的算法使用优先级队列来确保每个节点都出队并处理一次,并且只处理一次,从而带来更高的效率。

所以我想我能给出的最佳答案是“您的算法不是 Dijkstra 算法的实现,Dijkstra 算法使用优先级队列的原因是为了避免像您的算法那样多次重新计算距离。”

【讨论】:

  • 我同意你的评价。但是对于糟糕的实现,运行时不是仍然 O(E+V) 吗?随着图形变得越来越大,哪个比 E LOG(V) 更好?这是最困扰我的。换句话说,假设有 500 个边和 500 个顶点,那么坏的会做 E+V = 500+500 = 大约。 1000 次操作,其中优先级队列将执行 500*log(500) = 超过 4482 次操作!
  • @RichardAbraham 我不认为运行时评估是准确的。你能详细说明你是如何获得 O(E + V) 的运行时间的吗? (作为说明,我有很强的理论背景,并且据我所知,理论上不可能获得这样的运行时,除非您事先了解边缘权重)。
【解决方案2】:

您的算法会找到正确的结果,但您的方法正在做的是它会扼杀 Dijkstra 方法的效率。

例子:

考虑名为 A B C 的 3 个节点。

A->C :7 
A->B :2
B->C :3

在你的坏方法中,你首先将 A 到 C 的最短路径设置为 7,然后在遍历时将其修改为 5 (A->B-C)

在 Dijkstra 的方法中,根本不会遍历 A->C,因为在使用最小堆时,会先遍历 A->B,将 B 标记为“已遍历”,然后再将 B->C将被遍历,并且 C 将被标记为“遍历”。 现在,由于 C 已经被标记为“遍历”,路径 A->C(长度为 7)将永远不会被检查。

因此,如您所见,在您的错误方法中,您将到达 C 2 次(A->C 和 A->B->C),而使用 Dijkstra 的方法,您将只到达 C 一次。

这个例子应该证明使用 Dijkstra 算法的迭代次数更少。

【讨论】:

  • 我完全同意这个例子。但是对于糟糕的实现,运行时不是仍然 O(E+V) 吗?随着图形变得越来越大,哪个比 E LOG(V) 更好?这是最困扰我的。
  • @RichardAbraham 不,您的实现不是 O(V+E)。考虑一下 - 你正在做的是。每条边在两端都有两个顶点,这意味着每条边都被遍历两次。
【解决方案3】:

看了其他人的答案,他们是对的,总结是糟糕的实现方法是正确的,但是所有者索赔的复杂性(O(E + V))是不对的。还有一件事没有人提到,我会在这里补充。

这种糟糕的实现也对应一种算法,实际上是BFS的派生,正式名称为SPFA。查看SPFA algorithm。当作者在 1994 年发表这个算法时,他声称它的性能比 Dijkstra 的 O(E) 复杂度更好,这是错误的。

它在 ACM 学生中非常流行。由于其简单性和易于实施。至于性能,首选 Dijkstra。

类似的回复参考this post

【讨论】:

    【解决方案4】:

    所有答案都写得很好,所以我不会添加太多解释。 我添加了一个示例,这是我在 Striver 在他的一个图表播放列表视频中发表的评论中遇到的。希望它有助于清楚地看到这个问题。

    如果我们在为具有任意边权重的无向图实施 Dijkstra 算法时不使用优先队列,那么我们可能不得不一次又一次地访问同一个节点,它将无法考虑任意边权重。

    以下面的例子,通过优先队列试运行它,通过一个简单的队列,你会看到你必须访问node 6两次才能进行正常的队列试运行。因此,对于更大的图表,这将导致性能非常差。

    【讨论】:

      猜你喜欢
      • 2013-12-23
      • 1970-01-01
      • 1970-01-01
      • 2012-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多