0

I know that plain BFS search can be used for find shortest path in unweighted graph or graph with same edge weight, and Dijkstra should be used in weighted graph, and Dijkstra can be seen as a variant of BFS.

But I wonder if I push the node to the queue every time the dist[w] is updated, instead of only push once in plain BFS search, will this algorithm work for finding shortest path? I tried this algorithm on one leetcode problem, and it worked, but the leetcode problem only check limited testcase, so I cant prove the correctness of this algorithm. If the algorithm is correct, what's the time complexity?

   vector<int>dist(N + 1, INT_MAX);
        dist[start] = 0;
        queue<int>q;
        q.push(start);
        while(!q.empty()){
            int v = q.front(); q.pop();
            for(auto [w, cost] : g[v]){
              //  cout<<w<<" "<<cost<<endl;
                if(cost + dist[v] < dist[w]){
                    dist[w] = cost + dist[v];
                    q.push(w);
                }
            }
        }
codesavesworld
  • 587
  • 3
  • 10
  • This is a known variant of Dijkstra. It is not an actually a Dijkstra anymore it is no longer greedy, you will just turn it into a shortest path algorithm. You lose the performance benefits but it can work with negative values. – unlut Dec 10 '20 at 09:15
  • See answer by rontogiannis and comments of the answer: https://stackoverflow.com/questions/29715022/dijkstras-algorithm-for-negative-weights?rq=1 – unlut Dec 10 '20 at 09:18
  • @unlut I dont think this is a variant of Dijkstra, Dijkstra extracts the shortest dist from the unknown node set everytime, so it require a priotiry_queue or scan the whole dist array everytime. but this BFS-based version push node to queue when dist is updated... – codesavesworld Dec 10 '20 at 09:31

1 Answers1

0

I believe you have rediscovered the Bellman-Ford algorithm, minus the necessary handling for failing out on negative weight cycles. It should run in O(|V| * |E|) time (as opposed to the O((|V| + |E|) * lg(|V|)) of Dijkstra's) assuming no negative cycles, which will infinite loop on your implementation. The good part is that it handles negative edge weights, which Dijkstra's does not. The bad part is that it is much slower.

See https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm

dhakim
  • 107
  • 5
  • This is not bellman-ford, its just a modified Dijkstra. – unlut Dec 10 '20 at 09:04
  • Hi, do you mean the SPFA algorithm, which is an improvement of the Bellman-Ford? but from wikipedia, the SPFA will check if the updated node is already in the queue, if not, then push it to the queue, but mine does not check that, so is mine version still holds true? thanks – codesavesworld Dec 10 '20 at 09:09
  • Can't be dijkstra without a priority queue, the queue indicates a FIFO right? If so, I think it's Bellman Ford but without the clear separation of relaxation rounds you see in the canonical wikipedia implementation. – dhakim Dec 10 '20 at 09:16
  • Sure looks like SPFA as well yeah, which may be the same as Bellman-Ford-Moore, I'm not sure. That one does check if an element is already in the queue before re-adding it. – dhakim Dec 10 '20 at 09:18
  • You can't go wrong by writing to the queue multiple times- you can just make your implementation slower. You'll still get the right answer eventually. – dhakim Dec 10 '20 at 09:19
  • so does time complexity of O(V*E) still hold? – codesavesworld Dec 10 '20 at 09:33
  • The time complexity is at least exponential, since in some graphs it essentially explores every possible path between the source and some other node. – kaya3 Dec 10 '20 at 09:40
  • I *think* it might fall to O(V^2*E) without the check for redundant elements in the queue. Reverse engineering the SPFA analysis: in a fully connected graph, at most O(|E|) elements pass through the queue and O(|V|) work is performed for each element. In the worst case, by failing to check for duplicates, you push each element O(|V|) times for every time it is pushed in SPFA. However, while each of these redundant calls still performs O(|V|) work, they never trigger additional elements to be pushed, so I these redundant calls can be separated: O(V * E + V * V * E) – dhakim Dec 10 '20 at 10:28