【问题标题】:Why does Dijkstra's Algorithm use a heap (priority queue)?为什么 Dijkstra 算法使用堆(优先队列)?
【发布时间】:2012-09-10 23:13:33
【问题描述】:

我尝试在循环加权图上使用 Djikstra 算法,而不使用优先级队列(堆),并且成功了。

维基百科指出该算法的原始实现不使用优先级队列,运行时间为 O(V2)。

现在,如果我们只是删除优先级队列并使用普通队列,运行时间是线性的,即 O(V+E)。

谁能解释为什么我们需要优先队列?

【问题讨论】:

  • 不,它与运行时相反。使用优先级队列它在 O(|E| + |V|.|logV|) 中运行,没有优先级队列它在 O(V^2) 中运行。它在维基百科上说得很清楚。最佳时间是使用 fib 堆的 O(|E| + |V|.|logV|)。你在哪里读到你可以在 O(V+E) 中得到它?
  • 我没读过...我在删除优先级队列的概念并使用普通队列之后运行代码...是的,我知道它需要 O(E+ |V|log| V|) 具有优先级队列。因此,当我们不使用优先级队列时,logV 不再存在,因此运行时间变为 O(E+V) 只需查看代码即可。只有一个 for 循环。
  • 但是您如何根据最小距离对条目进行排序?排序将通过优先队列“廉价”完成 - 您是否明确排序 - 这将更昂贵。
  • hackerrank.com/challenges/dijkstrashortreach 我尝试对这个问题使用 FIFO 队列,但它没有通过一些测试。我想关键是要确保你得到队列中的最小值。虽然我没有知道在什么特定情况下正常队列会失败..

标签: graph-algorithm priority-queue dijkstra


【解决方案1】:

我也有同样的疑问,发现了一个测试用例,没有priority_queue的算法不起作用。

假设我有一个 Graph 对象 g,一个方法 addEdge(a,b,w) 将边从顶点 a 添加到顶点 b,权重 w

现在,让我定义下图:-

   Graph g 
   g.addEdge(0,1,5) ; 
   g.addEdge(1,3,1) ; 
   g.addEdge(0,2,2) ; 
   g.addEdge(2,1,1) ; 
   g.addEdge(2,3,7) ; 

现在,假设我们的队列包含以下顺序的节点{0,1,2,3 } 因此,首先访问节点 0,然后访问节点 1。

此时,使用路径 0->1->3 计算 dist b/w 0 和 3 为 6,并将 1 标记为已访问。

现在节点 2 已被访问,并且 dist b/w 0 和 1 使用路径 0->2->1 更新为值 3,但由于节点 1 被标记为已访问,因此您无法更改 b/w 0 和 3 的距离(使用最优路径) (`0->2->1->3) 是 4.

所以,如果不使用 priority_queue,您的算法就会失败。

它报告 dist b/w 0 和 3 为 6,而实际上它应该是 4。

现在,这是我用于实现算法的代码:-

            class Graph
        {
            public: 
                vector<int> nodes ; 
                vector<vector<pair<int,int> > > edges ; 
                void addNode() 
                {
                    nodes.push_back(nodes.size()) ; 
                    vector<pair<int,int> > temp ; edges.push_back(temp);
                }
                void addEdge(int n1, int n2, int w)
                {
                    edges[n1].push_back(make_pair(n2,w)) ; 
                }
                pair<vector<int>, vector<int> > shortest(int source) // shortest path djkitra's
                {
                    vector<int> dist(nodes.size()) ; 
                    fill(dist.begin(), dist.end(), INF) ; dist[source] = 0 ; 
                    vector<int> pred(nodes.size()) ; 
                    fill(pred.begin(), pred.end(), -1) ; 
                    for(int i=0; i<(int)edges[source].size(); i++)
                    {
                        dist[edges[source][i].first] = edges[source][i].second ; 
                        pred[edges[source][i].first] = source  ; 
                    }
                    set<pair<int,int> > pq ; 
                    for(int i=0; i<(int)nodes.size(); i++)
                        pq.insert(make_pair(dist[i],i)) ; 
                    while(!pq.empty())
                    {
                        pair<int,int> item = *pq.begin() ; 
                        pq.erase(pq.begin()) ; 
                        int v = item.second ; 
                        for(int i=0; i<(int)edges[v].size(); i++)
                        {
                            if(dist[edges[v][i].first] > dist[v] + edges[v][i].second)
                            {
                                pq.erase(std::find(pq.begin(), pq.end(),make_pair(dist[edges[v][i].first],edges[v][i].first))) ; 
                                pq.insert(make_pair(dist[v] + edges[v][i].second,edges[v][i].first)) ; 
                                dist[edges[v][i].first] = dist[v] + edges[v][i].second ; 
                                pred[i] = edges[v][i].first ; 
                            }
                        }
                    }
                    return make_pair(dist,pred) ; 
                }
    
    pair<vector<int>, vector<int> > shortestwpq(int source) // shortest path djkitra's without priority_queue 
            {
                vector<int> dist(nodes.size()) ; 
                fill(dist.begin(), dist.end(), INF) ; dist[source] = 0 ; 
                vector<int> pred(nodes.size()) ; 
                fill(pred.begin(), pred.end(), -1) ; 
                for(int i=0; i<(int)edges[source].size(); i++)
                {
                    dist[edges[source][i].first] = edges[source][i].second ; 
                    pred[edges[source][i].first] = source  ; 
                }
                vector<pair<int,int> > pq ; 
                for(int i=0; i<(int)nodes.size(); i++)
                    pq.push_back(make_pair(dist[i],i)) ; 
                while(!pq.empty())
                {
                    pair<int,int> item = *pq.begin() ; 
                    pq.erase(pq.begin()) ; 
                    int v = item.second ; 
                    for(int i=0; i<(int)edges[v].size(); i++)
                    {
                        if(dist[edges[v][i].first] > dist[v] + edges[v][i].second)
                        {
                            dist[edges[v][i].first] = dist[v] + edges[v][i].second ; 
                            pred[i] = edges[v][i].first ; 
                        }
                    }
                }
                return make_pair(dist,pred) ; 
            }

不出所料,结果如下:-

使用优先队列
0
3
2
4

现在使用没有优先队列
0
3
2
6

【讨论】:

  • 我知道这很旧,但是您的代码是错误的,因为您首先计算当前节点的所有邻居的距离,然后再标记任何已访问的节点,然后您继续使用该节点最低距离(0 后 2)。 Dijkstra 被证明可以找到最佳结果。问题是你不能有负边权重。因此,您引入了队列。现在你仍然有负面圈子的限制。
【解决方案2】:

就像 Moataz Elmasry 所说,你可以期待的最好的结果是 O(|E| + |V|.|logV|) 和 fib 队列。至少在涉及到大 oh 值时。

它背后的想法是,对于您当前正在处理的每个顶点(节点),您已经找到了最短路径。如果顶点不是最小的,(距离+边权重)不一定是真的。这就是允许您在扩展(?)从初始顶点可到达的每个顶点后立即停止算法的原因。如果您不扩展最小顶点,则不能保证找到最短路径,因此您必须测试每条路径,而不仅仅是一条。因此,您不必遍历一条路径中的每条边,而是遍历每条路径中的每条边。

您对 O(E + V) 的估计可能是正确的,但另一方面,您确定的路径和成本不正确。如果我没记错的话,只有当你从每个顶点经过的第一条边恰好是最小的一条时,这条路径才会是最短的。

所以 Dijkstra 的没有优先队列的最短路径算法就是 Dijkstra 的路径算法 ;)

【讨论】:

  • 无论是否使用优先级队列,Dijksta 算法的正确实现总是返回最短路径。
  • @brthornbury 是对的。这种算法的结果仍然是一条短路径,即它可能是局部最优而不是全局最优,但它仍然保证存在更长的路径。它不仅仅是一个路径算法。
  • 我认为从高层次上看,这部分解释是为什么堆和优先级队列与 Dijkstra 算法一起使用的关键:'如果你不扩展最小的顶点,你不能保证寻找最短路径,因此您必须测试每条路径,而不仅仅是一条。'
【解决方案3】:

对于稀疏图,如果用二进制最小堆运行时是(E*logV),但是如果你用斐波那契堆实现它,运行时是(VlogV+E)。

【讨论】:

  • 欢迎来到 StackOverflow。有参考资料吗?
  • 请参阅算法介绍第三版,第 23.2 章。
【解决方案4】:

堆是此任务的最佳选择,因为它保证 O(log(n)) 可以将边添加到我们的队列并删除顶部元素。优先级队列的任何其他实现都会牺牲添加到我们的队列或从中删除以在其他地方获得性能提升。根据图的稀疏程度,您可能会发现使用不同的优先级队列实现会获得更好的性能,但一般来说最小堆是最好的,因为它可以平衡两者。

不是最大的来源,但:http://en.wikipedia.org/wiki/Heap_(data_structure)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-14
    • 2013-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 1970-01-01
    相关资源
    最近更新 更多