23

I have tried using Djikstra's Algorithm on a cyclic weighted graph without using a priority queue (heap) and it worked.

Wikipedia states that the original implementation of this algorithm does not use a priority queue and runs in O(V2) time.

Now if we just removed the priority queue and used normal queue, the run time is linear, i.e. O(V+E).

Can someone explain why we need the priority queue?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Anshu Kandhari
  • 369
  • 1
  • 3
  • 9
  • 1
    no its the other way around with runtime. using priority queues it runs in O(|E| + |V|.|logV|), without priority queues it runs in O(V^2). it says so clearly on wikipedia. The best time is O(|E| + |V|.|logV|) using fib heaps. where did u read that you can get it in O(V+E)? – Moataz Elmasry Sep 18 '12 at 16:50
  • 2
    I did not read...I run the code just after removing the concept of priority queue and using normal queue.... and yes I know it will take O(E+ |V|log|V|) with priority queue. Therefore when we do not use priority queue the logV is not there anymore and hence the running time becomes O(E+V) Just see the code. There is only one for loop. – Anshu Kandhari Sep 18 '12 at 18:09
  • 1
    But how are you sorting the entries according to the minimal distance? Sorting will be done 'cheaply' via prio queue - are you explicitely sorting it - this will be more expensive. – Karussell Sep 20 '12 at 19:11
  • Not every loop happens in your code. I think you aren't considering the cost of maintaining and searching a queue. – Barry Fruitman Feb 28 '13 at 08:54
  • 2
    https://www.hackerrank.com/challenges/dijkstrashortreach I tried to use a FIFO queue for this question and it failed some tests.. I guess the key is to make sure you get the min in the queue.. though I don't know in what particular scenario a normal queue would fail.. – xialin Apr 01 '16 at 06:47
  • 1
    Similar to this question? [graph - Why does Dijkstra's algorithm need a priority queue when this regular queue version is also correct? - Stack Overflow](https://stackoverflow.com/questions/36920817/why-does-dijkstras-algorithm-need-a-priority-queue-when-this-regular-queue-vers/62517884#62517884) – Kevin Chou Jun 22 '20 at 15:27

4 Answers4

15

I had the exact same doubt and found a test case where the algorithm without a priority_queue would not work.

Let's say I have a Graph object g, a method addEdge(a,b,w) which adds edge from vertex a to vertex b with weight w.

Now, let me define the following graph :-

   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) ; 

Now, say our queue contains the nodes in the following order {0,1,2,3 } So, node 0 is visited first then node 1 is visited.

At this point of time the dist b/w 0 and 3 is computed as 6 using the path 0->1->3, and 1 is marked as visited.

Now node 2 is visited and dist b/w 0 and 1 is updated to the value 3 using the path 0->2->1, but since node 1 is marked visited, you cannot change the distance b/w 0 and 3 which (using the optimal path) (`0->2->1->3) is 4.

So, your algorithm fails without using the priority_queue.

It reports dist b/w 0 and 3 to be 6 while in reality it should be 4.

Now, here is the code which I used for implementing the algorithm :-

            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) ; 
            }

As expected the results were as follows :-

With priority_queue
0
3
2
4

Now using without priority queue
0
3
2
6

21rw
  • 1,016
  • 1
  • 12
  • 26
Pratik Singhal
  • 6,283
  • 10
  • 55
  • 97
  • 3
    I know this is old, but your code is wrong in the sense that you first compute the distances for all neighbors of the current node before you mark any node visited und then you continue with the node with the lowest distance (2 after 0). Dijkstra is proven to find the optimal result. The catch is that you can't have negative edge weights. Therefore you introduce the queue. Now you still have the limitation of negative circles. – Matthias Brandt Jun 28 '18 at 07:43
4

Like Moataz Elmasry said the best you can expect is O(|E| + |V|.|logV|) with a fib queue. At least when it comes to big oh values.

The idea behind it is, for every vertex(node) you are currently working on, you already found the shortest path to. If the vertex isn't the smallest one, (distance + edge weight) that isn't necessarily true. This is what allows you to stop the algorithm as soon as you have expanded(?) every vertex that is reachable from your initial vertex. If you aren't expanding the smallest vertex, you aren't guaranteed to be finding the shortest path, thus you would have to test every single path, not just one. So instead of having to go through every edge in just one path, you go through every edge in every path.

Your estimate for O(E + V) is probably correct, the path and cost you determined on the other hand, are incorrect. If I'm not mistaken the path would only be the shortest if by any chance the first edge you travel from every vertex just happens to be the smallest one.

So Dijkstra's shortest path algorithm without a queue with priority is just Dijkstra's path algorithm ;)

Patric Cunha
  • 97
  • 1
  • 5
  • 8
    A correct implementation of Dijksta's Algorithm always returns a shortest path regardless of a priority queue is used or not. – brthornbury Mar 12 '14 at 23:49
  • @brthornbury is right. The result of such an algorithm is still a short path, i.e. it might be a local optima instead of the global optima, but it is still guarantees that longer paths exist. It is not just a _path_ algorithm. – alelom Jun 06 '20 at 16:36
  • I think from a high level this part of the explanation is key to why heaps and priority queues are used with Dijkstra’s Algorithm: ‘ If you aren't expanding the smallest vertex, you aren't guaranteed to be finding the shortest path, thus you would have to test every single path, not just one.’ – Dan Oct 13 '21 at 12:09
2

For sparse graph, if implement with binary min heap runtime is(E*logV), however if you implement it with Fibonacci heap, runtime would be(VlogV+E).

Linghua Jin
  • 570
  • 2
  • 6
  • 22
0

A heap is the best choice for this task as it guarantees O(log(n)) for adding edges to our queue and to remove the top element. Any other implementation of priority queue would sacrifice in either adding to our queue or removing from it to gain a performance boost somewhere else. Depending on how sparse the graph, then you might find better performance using a different implementation of priority queue, but generally speaking a min-heap is best since it balances the two.

Not the greatest source but: http://en.wikipedia.org/wiki/Heap_(data_structure)

Daeden
  • 481
  • 1
  • 6
  • 20