In every company, the most essential task is managing the movement of money in and out in terms of income and expenditure, also known as cash flow. Such data is usually collected in tables and passed to the finance department. Afterward, the information is represented in a weighted graph where nodes stand for the economic activities performed by the company as shown below:
Negative weights indicate that the company earns money from the activity. The goal is to minimize the cost of every chain of activities. In other words, it is required to find the shortest paths between all pairs of nodes. The bigger the company, the larger the data graph. Johnson's algorithm is able to solve this task successfully in a reasonable amount of time.
Description of the algorithm
We are already familiar with a couple of algorithms that solve the SSSP problem. The fast way would be to apply Dijkstra's algorithm for every node and solve our all-pairs shortest path problem. However, as we know, we would have trouble with negative edges. Naturally, we might want to repeatedly apply the Bellman-Ford algorithm instead, but this is relatively slow.
We don't have all day, our aim is to be efficient. The main idea of Johnson's approach is to use both algorithms mentioned above as subroutines: we remove all the negative weights using Bellman-Ford, which allows Dijkstra's algorithm to work correctly. In more detail, the steps of Johnson's algorithm can be described as follows:
- Add a new node to the given graph . Connect to all other nodes using edges of weight
- Apply the Bellman-Ford algorithm to the new graph choosing as the source node. For each node , let denote the weight of the shortest path from to . If negative cycles are detected, notify about it and terminate.
- Reweight the edges of the initial graph eliminating negative edges. This can be accomplished by assigning a new nonnegative weight, namely , to every edge of weight .
- Remove . Apply Dijkstra's algorithm times, each time choosing a new starting node, in order to find the weights of the shortest paths between all pairs of nodes in the reweighted graph
- The original weights of the shortest paths can be obtained by adding to each distance computed during the previous step.
If you are an attentive reader, you must be asking yourself several questions now. Why are the new weights guaranteed to be nonnegative? What about the fifth step, isn't it an abstract assumption? The next paragraph is dedicated solely to these two questions.
Correctness of the algorithm
The correctness of Johnson's algorithm is mainly implied by the correctness of its subroutines. However, as we mentioned above, we have made two nontrivial statements:
- All edges in the reweighted graph have nonnegative weights.
- The shortest paths computed by Dijkstra's algorithm in remain shortest paths in the original graph .
First, let's notice that in , the weight of every path between two nodes, say and , differs from the original weight by . Indeed, consider the path . Its weight is equal to
as each is canceled. Therefore, every shortest path in the reweighted graph remains shortest in the original one and their weight can be calculated as we assumed in step . Still, we need to prove the first assumption that all the conditions to apply Dijkstra's algorithm have been met.
What if some edge has a negative weight after reweighting? Consider the shortest path from to . Based on what we proved above, its weight becomes after the reweighting process. Analogously, Besides, they still remain shortest paths. However, with being negative, we obtain a shorter path from to , namely, the blue path from to together with the red edge , which contradicts the fact that the shortest path from to costs
An example
Let's get down to business. Literally. Our company expects us to compute the all-pairs shortest paths in the cash flow graph. It is a perfect chance to apply Johnson's algorithm.
- Add a new node, say , and connect it to nodes , and using zero-weight edges as shown in picture 1.
- We already know how the Bellman-Ford algorithm works, hence we are going to immediately write the final weights of the shortest paths choosing node as the source.
- With these values calculated, we can easily reweight every edge of the initial graph. For example, the edge will cost After step , the reweighted graph will look like the one in picture 3.
- Remove the extra edge We end up with the graph in picture 4.0. Now we need to apply Dijkstra's algorithm times: first choosing as the starting node, then the source will be , and so on until we find all shortest paths between each pair of nodes. For obvious reasons, we are not going to describe how Dijkstra's algorithm works in detail. We shall simply display the final values for every instantiation of it in the pictures below.
- Now we need to reverse the reweighting process by adding the value to every shortest path weight . For instance, the cost of the shortest path from node to node becomes The final values are represented in table 5.
Based on the results above, the company is going to make decisions about the activities worth performing and the order of their completion, picking the ones that are more profitable. For example, doing the second activity in the beginning and the fourth one in the end is quite expensive; meanwhile, performing activity and then the second one is extremely beneficial for the company.
Time complexity
For a graph with nodes and edges, the time complexity of Johnson's algorithm is . Let's analyze every phase in more detail. Bellman-Ford takes . On the other hand, one can implement Dijkstra's algorithm in using a Fibonacci heap. We apply instantiations; therefore, step uses time. The first, third, and fifth steps can be implemented practically at no extra cost. Overall, Johnson's algorithm takes time.
Conclusion
Let's sum up what we have learned in this topic. Johnson's algorithm is a relatively fast algorithm for solving the all-pairs shortest path problem, and it is able to handle negative cycles as well. It can be easily implemented following these five steps:
- Add a new node. Connect it to every other node using zero-weight edges.
- Calculate the shortest paths from this node to each node using the Bellman-Ford algorithm.
- Reweight the graph so that no negative edges are left.
- Apply Dijkstra's algorithm times to find the shortest paths between each pair of nodes.
- Using the values found in step 4, calculate the weights of all shortest paths in the original graph.
In the case of sparse graphs, Johnson's algorithm works remarkably faster than many other all-pairs algorithms, as its time complexity depends on the number of edges.