Click here to Skip to main content
15,881,516 members
Articles / Programming Languages / C++

Shortest Path Algorithm

Rate me:
Please Sign up or sign in to vote.
3.06/5 (19 votes)
27 Feb 2019CPOL6 min read 39.2K   616   41   25
This explains the working of Bellman Ford algorithm.

Introduction

Shortest path algorithms are used in many real life applications, especially applications involving maps and artificial intelligence algorithms which are NP in nature. A graph is a collection of nodes \(V\) connected by edges \(E\) and can be expressed as \(G(V,E)\). Stands for vertices. These vertices and the connecting edges together can be imagined to form a three dimensional geometrical structure. In some cases, for each \((u,v)\) \(\epsilon E\) (here \(u,v,\) \(\epsilon V\) are vertices which are connected), there is a weight value assigned to it. This can also be called as "distance" or "cost" of connecting the two nodes. The shortest path algorithm traces the minimum distance (or cost) between two nodes \((u,v)\) which are either directly or indirectly connected. The weight values along each possible paths to the destination node from the source node are summed up, and the path with the minimum summation value is chosen as the shortest path.

We may represent a weighted graph \(G(V,E,w)\) as where the extra parameter represents the set of weight values across each edge. \(w:E \rightarrow \mathbb{R} - {0},w(e)\) Weight of the edge. Usually, shortest path algorithms are of time complexity \(\Omega (|V|)\) because every node needs to be visited at least once. In case of AI algorithms, the vertices are generated based on specific rules, and usually it’s a function of depth; because most nodes have more than one connection, we may also have non-polynomial relation between the depth and the number of nodes.

Algorithms like Dijkstra’s algorithm’s time complexity is \(O(|V|^{2})\) (note, \(|V|\) represent number of nodes and \(|E|\) represents number of edges). Note that the definition is actually a relation between the edge and a real number other than zero. This also shows that the weight values can be negative. So a modified version of the algorithm which also solves problems with negative weight edges is required. But doing this increases the time complexity; Dijkstra’s algorithm is a greedy algorithm, which selects the best possible path in the graph originating from a node, and later choosing the node with the least distance from the set of nodes yet to be visited, to further repeat the process with the newly selected node. Such an algorithm which solves graphs with negative weight values is Bellman Ford algorithm and its runtime complexity is \(O(|V|E|)\). Generally, \(|E| \geq 1\) unless there are disjoint nodes or graphs. Unless stated, the graphs discussed here are always assumed to not contain any disjoint sets or nodes.

Dijkstra’s Algorithm

This is a greedy algorithm which keeps selecting the node with the minimum weight from the list of nodes, to begin with.

Algorithm I

C++
Dijkstra (G=(V,E,w),s,d),
// V stands for the  vertices
// E stands for edges.         
// w is the weight value for each
// s is the source node. 
// d is the destination node.

     // Q can be a min-priority queue to improve the                
     // runtime complexity of the algorithm which changes 
     // to.

               For each // is the minimum value in the set 
                       Let 
                     Be a node that can immediately reached by the   
                           Node 
                Then for each 
                    The distance from the source to the node   
                    Is modified as, 
                     If then    
                          
                            Else 
                         No change in distance value.
                            //is the parent node of 
       
                    If v = d then 
                                End search returning  and 
 
                // this ensures that the node is not
                // visited again
               End For loop // For each 
 End function Dijkstra (G (V, E, w), s, d)

In the above algorithm, pre and dis(s,u) refer to "previous link" and "distance from s to u" respectively. We can see that for each node that is at minimum among the other unvisited nodes, are selected first. So the time complexity is \(O(|V|^{2})\), because in the worst case scenario every node is visited, and again, if every node is connected to every other node, then both pairs of nodes are visited each time the algorithm iterates. So this ends up visiting \(|V|\) nodes for each node in the graph. But we don’t always consider the worst case, as its occurrence is rare, but we rather rely on the average case or the amortised analysis of the algorithm.

The above described algorithm shows the shortest path between two nodes, given that even negative weight values can be included.

Bellman Ford Algorithm

When there are negative weights in the graph, each path needs to be visited for times on the worse case. This shows that the time complexity is \(O(|V|E|)\). The reason that every edge is compulsorily visited is, if the algorithm misses a negative edge, the shortest path itself changes; and moreover the greedy approach fails because, once the final destination node is reached before visiting each edge, we cannot conclude that to be the shortest path. Let’s say that the Dijkstra’s algorithm returns the shortest path to the destination to be \(a_{source}\rightarrow b\rightarrow c\rightarrow e_{destination}\) in a graph with negative weight values. Let’s further consider that that path is of length \(x_{1}\).

And another path \(a_{source}\rightarrow b\rightarrow l\rightarrow m\) to be of length \(x_{2} > x_{1}\). Then there can be a negative edge, if when visited reduces the length to a value less than \(x_{1}\). From the algorithm, we can say that the node at the shortest distance to the destination is returned as the answer. But for the case involving negative weight values, a path that’s considered to be "longer" at that instant might not be as long as expected. Because, if it has a connecting path, that travels through a negative edge, its distance might reduce beyond the currently chosen shortest path. Let’s consider there exists a path \(m\rightarrow f\rightarrow e_{source}\) with a negative distance \(w\). So the new path \(a_{source}\rightarrow b\rightarrow l\rightarrow m\rightarrow f\rightarrow e_{destination}\) will be of length \(x_{2}+w\) which could also be \(x_{2}+w < x_{1}\). So we cannot conclude the shortest path until we have visited each path in the graph. Even if we have visited each path in the graph, the shortest path might be something else unless the process is carried over for \(N \leq |V|E|\) times. But not lesser than \(|V|\) times.

Algorithm II

C++
BellmanFord(G(V, E, W),s,d), 
// V stands for the vertices.
// E stands for edges.
// w is the weight value for each.
// s is the source node. 
// d is the destination node.

    For i= 1 to  do 
                   // Q can be a min-priority queue to improve the
                   // runtime complexity of the algorithm which changes 
                   // to.
         
               For each // is the minimum value in the set 
                       Let 
                     Be a node that can immediately reached by the   
                           Node 
                Then for each 
                    The distance from the source to the node   
                    Is modified as, 
                     If then    
                          
                            Else 
                         No change in distance value.
                            //is the parent node of        
                    
                 // this ensures that the node is not
                 // visited again
               End For loop // For each 
        End For loop        // For i= 1 to  do
   Return Pre and Dis 
End function Dijkstra (G (V, E, w), s, d)

The above depicts the Bellman Ford algorithm. This algorithm, as we can see, always runs at the worst case of \(O(|V|E|)\). But we can modify it to run faster, by inserting an operation to check if there is any change after calling the ‘For each u∈Q_min’ loop, we can determine if we could terminate the process earlier. If there was no modification, then the function returns immediately.

Negative Cycles

There are many cases that are to be considered before the direct implementation of Bellman Ford algorithm. Because of involving negative edges, there are chances of having the shortest path of a graph’s loop undeterminable. For example, consider the case of having a graph with every edge having a negative weight value. Let’s consider that the Bellman Ford algorithm updates the distance of each node from the source node, by updating each edge for times. If every edge was negative, the shortest path that was obtained keeps becoming shorter each time we follow the same procedure (refer the Bellman Ford algorithm given in this article).

Definition

A negative edge cycle is defined as a cyclic path within that graph, which contains negative weight values and the net sum of each edge along the cyclic path sums up to a value less than zero. This causes the algorithm to never find the shortest path in the graph, and if such a scenario exists, then the solution becomes undefined.

This problem does not just arise when each edge in the graph has negative weight value, but also occurs when any arbitrary cyclic path in the graph has its edge values summing up to a value less than zero.

Reposting From

History

  • 3rd October, 2015: First written

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
India India
I like programming and the only languages I use as of now are C/C++. I am doing my second year computer science engineering and my favorite topics in computer science are: Data structures, algorithm analysis, Artificial Intelligence and I also like to develop learning algorithms. Apart from computer science, I also have a great interest in mathematics and physics. I like solving problems in these areas. I also like writing programs that solve problems in these areas.

I maintain a blog. I don't update it much. Here is it's URL: www.creativitymaximized.com

And my place in github: https://github.com/sreramk/

Comments and Discussions

 
QuestionFormatting PinPopular
Rick York27-Feb-19 5:44
mveRick York27-Feb-19 5:44 
AnswerRe: Formatting Pin
Philippe Verdy28-Feb-19 9:23
Philippe Verdy28-Feb-19 9:23 
I do agree, but the initial original article was on a blog (at the URL given by the author in 2015) which is no longer available (domain name has expired and has been cybersquatted).
So as is, the article is now unusable.
It is a known fact that the "shortest" path is a NP problem and has no satisfactory solution with bounded time or resource.
The best solutions (used for example for routing in geographic applications, or in electronics) is to use heuristics. By not using the weight very strictly but splitting the graph into subgraphs using some reasonable thresholds (based on simple statistics like average and standard deviation), so that all nodes in a subgraph are within a given distance range, then recompute the thresholds on each subgraph recursively. You get a first solution on which you'll compute the actual weights between all nodes/vertices of each minimal subgraph (on which you have an algorithmically exact solution).
Then you'll sort the subgraphs by their standard deviation and join them by pair, starting by the two subgraphs that have the largest deviation to recompute the estimated statistics (average and standard deviation: notice that these are estimate because you have not tested all pairs, but you can perform an algorithmic optimization by using a random half of sets of vertices/nodes from the two subgraphs, and knowing that these statistics are only representative of a total estimation of a quarter of the full join, you need to compensate the standard deviation because it is a sample of the population and not the full population). On this new subgraph find the shortest paths, quick-sort the vertices/nodes and then restart it once by selecting half the population.
This done your new joined subgraph should now sort in the list of other subgraphs at a lower position because it should now have a lower standard deviation value.

Any way the article focuses on the problem with negative costs: this is a theoretical problem, but generally comes from an incorrect modelisation of the problem where you've not properly isolated the variables of your problem, or you have chosen the incorrect metric for evaluating the weights by a direct sum: you should still be able to change the weighting function, by transforming it to a positive function. And the simple arithmertic sum of weight is not necessarily the best way to evaluate the total costs of traversing two edges. Such cases exist in financial applications (e.g. purchase costs vs. sell revenues), but the simple balance is not representative because it is often first benefitable to maximize the number of transactions to minize the risks for the same final balance, and what is then important is no really the sign but the abolute value of individual transactions which should be minimized. But then you have fixed costs per transaction and fixed revenues for prcessing them and their sum is easily predictable and you can make the total highly profitable if you maximize again these transactions (wchich can be largely automated for their processing).
If you can't avoid the negative costs, at least you can offset all of them by an arbitrary value, so that the sum of positive costs in your graph is at least 50% higher than the absolute of negative weights: traversing your graph should then converge and not diverge, and you can easily split the graph into subgraphs that will "break" the negative cycles into separate parts in distinct subgraphs.
So the optimization and sorting can still work on these subgraphs.
Once you have made these splits, you just have to make sure that each subgraph is fully connected and rearrange the population of vertices between pairs of subgraphs.
Now you can join the subgraphs together by trying to reconnect them by pair using random selection in each subgraph and testing only a few nodes (the other links to complete the traversal are already part of each resolved/optimized subgraph): instead of joining distinct nodes, you just replace the inital large graph by a small graph whose nodes have weights computed from average and deviation, on which an algorithmic solution provides a good solution.
Then in your graph traversal you can lookup into each subgraph to find a few nearby nodes that could help reduce the actual distance between subgraphs.
Basically all this works like a "quick-sort" algorithm, and uses the wellknown "divide-and-conquer" strategy. You get a first satisfactory solution on which you can perform "local" optimizations to swap algorithmically nodes in a window with a bounded size.
You can then use the satisfactory graph you got by reuysing it to recuirsively split the graph again but now you'll get weights with lower standard deviation (if not, your heuristic is terminated), and can perform it again and again until you reach your execution time limit: you can propose the solution to the user, who can then decide to continue the processing for another time slot.
There are interesting adjustments you can do to the algorithm to make it converge faster: instead of using weights directly, you can also add a random weight within about 67% of the standard deviation of weights at start of each loop; and then reduice the random offsets to 50%, 33%, 15%, using a a decaying threshold, because it is very helpful to avoid very local extremums that block the convergence to a much better solution: this is like adding noise or temperature to the data and lowering the temperature. This strategy works extremely well for finding geograghic routing paths, or for solding printed circuits routing in electronics. This worlks also when you want to minimize just one type of weights but also several ones: starting with a zero-tremperatur does not help because you need to cycle between each type of weight and covering too fast on one criteria would cause other types of weights/costs to be largely ignored.
These kind of problems are also very suitable for being implemented with neural networks, which are naturally miassively parallelized and use the divide-and-conquer strategy: their interest is that they no longer need to use any "sort" (this works as long as they are fully parallelized; iof the neural network is partially parallelized, and needs sequencing to perform each step, the sorting is still helpful to determine the cutting steps): it is for now very difficult to implement neural networks with more than about 200-500 neurones, but most path-finding practial problems have to work with graphs containing many thousands nodes, possibly millions
GeneralRe: Formatting Pin
David Crow28-Feb-19 14:30
David Crow28-Feb-19 14:30 
GeneralRe: Formatting Pin
Super Lloyd28-Feb-19 15:57
Super Lloyd28-Feb-19 15:57 
GeneralRe: Formatting Pin
Philippe Verdy1-Mar-19 8:22
Philippe Verdy1-Mar-19 8:22 
GeneralRe: Formatting Pin
yafan1-Mar-19 3:18
yafan1-Mar-19 3:18 
GeneralRe: Formatting Pin
Philippe Verdy1-Mar-19 9:56
Philippe Verdy1-Mar-19 9:56 
GeneralRe: Formatting Pin
#realJSOP1-Mar-19 8:01
mve#realJSOP1-Mar-19 8:01 
GeneralRe: Formatting Pin
Philippe Verdy1-Mar-19 8:27
Philippe Verdy1-Mar-19 8:27 
GeneralRe: Formatting Pin
#realJSOP1-Mar-19 8:34
mve#realJSOP1-Mar-19 8:34 
GeneralRe: Formatting Pin
Philippe Verdy1-Mar-19 8:40
Philippe Verdy1-Mar-19 8:40 
GeneralRe: Formatting Pin
#realJSOP1-Mar-19 8:49
mve#realJSOP1-Mar-19 8:49 
GeneralRe: Formatting Pin
Philippe Verdy1-Mar-19 9:56
Philippe Verdy1-Mar-19 9:56 
GeneralMy vote of 4 Pin
KarstenK5-Nov-15 2:41
mveKarstenK5-Nov-15 2:41 
GeneralRe: My vote of 4 Pin
Sreram K5-Nov-15 8:02
Sreram K5-Nov-15 8:02 
QuestionWhat about two way weighting? Pin
Member 1159380830-Oct-15 6:51
Member 1159380830-Oct-15 6:51 
AnswerRe: What about two way weighting? Pin
Sreram K5-Nov-15 7:55
Sreram K5-Nov-15 7:55 
GeneralRe: What about two way weighting? Pin
Member 115938086-Nov-15 5:06
Member 115938086-Nov-15 5:06 
QuestionSource code is missing Pin
Sten Hjelmqvist9-Oct-15 0:16
Sten Hjelmqvist9-Oct-15 0:16 
AnswerRe: Source code is missing Pin
Sreram K9-Oct-15 2:04
Sreram K9-Oct-15 2:04 
Questionlike these articles on combinatorics Pin
Midnight4898-Oct-15 6:31
Midnight4898-Oct-15 6:31 
AnswerRe: like these articles on combinatorics Pin
Sreram K11-Oct-15 5:21
Sreram K11-Oct-15 5:21 
QuestionI need help editing! Pin
Sreram K7-Oct-15 3:27
Sreram K7-Oct-15 3:27 
AnswerRe: I need help editing! Pin
Sreram K8-Oct-15 5:24
Sreram K8-Oct-15 5:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.