Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding kth-shortest paths?

Finding the shortest path between two points in a graph is a classic algorithms question with many good answers (Dijkstra's algorithm, Bellman-Ford, etc.) My question is whether there is an efficient algorithm that, given a directed, weighted graph, a pair of nodes s and t, and a value k, finds the kth-shortest path between s and t. In the event that there are multiple paths of the same length that all tie for the kth-shortest, it's fine for the algorithm to return any of them.

I suspect that this algorithm can probably be done in polynomial time, though I'm aware that there might be a reduction from the longest path problem that would make it NP-hard.

Does anyone know of such an algorithm, or of a reduction that show that it is NP-hard?

like image 528
templatetypedef Avatar asked Aug 26 '11 17:08

templatetypedef


People also ask

How do you find the K shortest path?

Using the modified P(G), we can compute a heap Hv(G) of paths from each v to t, and compute the k smallest such paths in time O(k). Theorem 2. Given a source vertex s in a digraph G, we can find in time O(m + n logn + kn logk) an implicit representation of the k shortest paths from s to each other vertex in G.

What are k paths?

The k shortest path routing problem is a generalization of the shortest path routing problem in a given network. It asks not only about a shortest path but also about next k−1 shortest paths (which may be longer than the shortest path). A variation of the problem is the loopless k shortest paths.

How do I find the second shortest path?

To find second shortest path between given two vertices s and t , use Dijkstra algorithm to compute the shortest path from s to each vertex and compute the shortest path from each vertex to t .


3 Answers

The best (and basically optimal) algorithm is due to Eppstein.

like image 104
comestibles Avatar answered Oct 06 '22 04:10

comestibles


You're looking for Yen's algorithm for finding K shortest paths. The kth shortest path will then be the last path in that set.

Here's an implementation of Yen's algorithm.

And here's the original paper describing it.

like image 38
tskuzzy Avatar answered Oct 06 '22 04:10

tskuzzy


From the available Kth shortest path algorithms, Yen’s algorithm is the easiest to understand. Mostly this is because Yen’s algorithm first needs to compute all K-1 shortest paths before it can compute the Kth shortest path, so it can be broken down into sub-problems.

Furthermore, since each sub-problem uses a standard shortest path algorithm (e.g. Dijkstra’s algorithm), it is a more natural extension from the 1st shortest path problem to the Kth shortest path problem.

Here is how it works for finding the 3rd shortest path in an example graph with equal-weight edges.

1st shortest path (K=1)

If we are looking for the 1st shortest path between a start and a destination (here, between D and F), we can just run Dijkstra’s algorithm. The entire code for Yen’s algorithm at the first iteration is:

shortest_1 = Dijkstra(graph, D, F)

Given a starting graph, this gives the 1st shortest path (K=1).

First shortest path

2nd shortest path (K=2)

The intuition for finding the 2nd shortest path is to take the 1st shortest path but “force” Dijkstra’s algorithm along a different, slightly less optimal route. The way Yen’s algorithm “forces” Dijkstra’s algorithm along a different route, is by removing one of the edges that are part of the 1st shortest path.

But which of the edges do we remove to get the next shortest path? We need to try removing each edge, one by one, and see which edge removal gives us the next shortest path.

Second shortest path

First we try to remove edge D-E (the first edge in shortest_1), and then complete the shortest path by running Dijkstra(graph_1, D, F). We combine the shortest path already known from node D to D (which is just the node D itself), with the new shortest path from node D to F. This gives us an alternative path D->F.

Then we try to remove the edge E-F (the second edge in shortest_1), and then complete the shortest path by running Dijkstra(graph_2, E, F). We combine the shortest path already known from node D to E, with the new shortest path from node E to F. This gives us yet another alternative path D->F.

The procedure for the 2nd iteration thus looks like this:

// Try without edge 1
graph_1 = remove_edge(graph, D-E)
candidate_1 = shortest_1(D, D) + Dijkstra(graph_1, D, F)

// Try without edge 2
graph_2 = remove_edge(graph, E-F)
candidate_2 = shortest_1(D, E) + Dijkstra(graph_2, E, F)

shortest_2 = pick_shortest(candidate_1, candidate_2)

At the end, the shortest of the alternative new paths is chosen as the 2nd shortest path.

3rd shortest path (K=3)

Just as the 2nd shortest path was found by removing edges from the 1st shortest path, the 3rd shortest path is found by removing edges from the 2nd shortest path.

The new nuance this time, however, is that for when we remove edge D-A (the first edge in shortest_2), we also want to remove edge D-E. If we don’t do this, and remove only the edge D-A, then when we run Dijkstra on graph_3, we will find the 1st shortest path again (D-E-F), instead of the 3rd shortest path!

For graph_4 and graph_5, however, we do not need to remove any other edges, since those edges, when used, won’t give us previously seen shortest paths.

Third shortest path

Thus, the overall procedure looks similar to finding the 2nd shortest path, but with the nuance that we also want to remove some edges seen in the 1st shortest path in addition to the 2nd shortest path. The decision is made based on whether shortest_1 and shortest_2 share a subpath leading up to the edge which is being removed.

// Try without edge 1
edges_3 = [D-A]
if shortest_1 and shortest_2 share subpath up to node D: 
    // True because both shortest_1 and shortest_2 have D in shortest path
    // D-E is edge in shortest_1 that is connected from D, and so it is added
    edges_3.add(D-E)

graph_3 = remove_edges(graph, edges_3)
candidate_3 = shortest_e(D, D) + Dijkstra(graph_3, D, F) // returns infinity


// Try without edge 2
edges_4 = [A-E]
if shortest_1 and shortest_2 share subpath up to node A:
    // False because there are no edges in shortest_1 that go through A
    // So no edges added

graph_4 = remove_edges(graph, edges_4)
candidate_4 = shortest_2(D, A) + Dijkstra(graph_4, A, F) // returns infinity


// Try without edge 3
edges_5 = [E-F]
if shortest_1 and shortest_2 share subpath up to node E:
    // False because shortest_1 goes through D-E while shortest_2 goes through D-A-E
    // So no edges added

graph_5 = remove_edges(graph, edges_5)
candidate_5 = shortest_2(D, E) + Dijkstra(graph_5, E, F)


shortest_3 = pick_shortest(candidate_2, candidate_3, candidate_4, candidate_5)

Note how when we pick the shortest path this time, we take into account unused candidates from iteration 2 (i.e. candidate_2), and actually end up picking the shortest path found from graph_2. In the same way, on the next iteration (K=4), we will find that the 4th shortest path was actually found in iteration K=3. As you can see, this algorithm is doing work in advance, so while it is finding the Kth shortest path, it is also exploring some of the paths beyond the Kth shortest path. It is thus not the most optimal algorithm for the Kth shortest path problem. The Eppstein algorithm can do better, but it is more complex.

The above approach can be generalized by using several nested loops. The Wikipedia page on Yen’s algorithm already gives excellent pseudocode for the more generic implementation, so I will refrain from writing it here. Note that the Wikipedia code uses a list A to hold each shortest path, and a list B to hold each candidate path, and that candidate shortest paths persist across loop iterations.

Remarks

Due to the use of Dijkstra’s algorithm, Yen’s algorithm cannot have a Kth shortest path that contains a loop. This is not as noticable when un-weighted edges are used (as in the example above), but could occur if weights are added. For this reason, Eppstein’s algorithm works better as well, since it considers loops. This other answer includes a link to a good explanation of Eppstein’s algorithm.

like image 40
vasilyrud Avatar answered Oct 06 '22 04:10

vasilyrud