Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shortest Path in a Directed Acyclic Graph with two types of costs

I am given a directed acyclic graph G = (V,E), which can be assumed to be topologically ordered (if needed). The edges in G have two types of costs - a nominal cost w(e) and a spiked cost p(e).

The goal is to find the shortest path from a node s to a node t which minimizes the following cost: sum_e (w(e)) + max_e (p(e)), where the sum and maximum are taken over all edges in the path.

Standard dynamic programming methods show that this problem is solvable in O(E^2) time. Is there a more efficient way to solve it? Ideally, an O(E*polylog(E,V)) algorithm would be nice.

---- EDIT -----

This is the O(E^2) solution I found using dynamic programming.

First, order all costs p(e) in an ascending order. This takes O(Elog(E)) time.

Second, define the state space consisting of states (x,i) where x is a node in the graph and i is in 1,2,...,|E|. It represents "We are in node x, and the highest edge weight p(e) we have seen so far is the i-th largest".

Let V(x,i) be the length of the shortest path (in the classical sense) from s to x, where the highest p(e) encountered was the i-th largest. It's easy to compute V(x,i) given V(y,j) for any predecessor y of x and any j in 1,...,|E| (there are two cases to consider - the edge y->x is has the j-th largest weight, or it does not).

At every state (x,i), this computation finds the minimum of about deg(x) values. Thus the complexity is O(|E| * sum_(x\in V) deg(x)) = O(|E|^2), as each node is associated to |E| different states.

like image 795
ThatGuy Avatar asked Jul 31 '20 19:07

ThatGuy


2 Answers

I don't see any way to get the complexity you want. Here's an algorithm that I think would be practical in real life.

First, reduce the graph to only vertices and edges between s and t, and do a topological sort so that you can easily find shortest paths in O(E) time.

Let W(m) be the minimum sum(w(e)) cost of paths max(p(e)) <= m, and let P(m) be the smallest max(p(e)) among those shortest paths. The problem solution corresponds to W(m)+P(m) for some cost m. Note that we can find W(m) and P(m) simultaneously in O(E) time by finding a shortest W-cost path, using P-cost to break ties.

The relevant values for m are the p(e) costs that actually occur, so make a sorted list of those. Then use a Kruskal's algorithm variant to find the smallest m that connects s to t, and calculate P(infinity) to find the largest relevant m.

Now we have an interval [l,h] of m-values that might be the best. The best possible result in the interval is W(h)+P(l). Make a priority queue of intervals ordered by best possible result, and repeatedly remove the interval with the best possible result, and:

  • stop if the best possible result = an actual result W(l)+P(l) or W(h)+P(h)
  • stop if there are no p(e) costs between l and P(h)
  • stop if the difference between the best possible result and an actual result is within some acceptable tolerance; or
  • stop if you have exceeded some computation budget
  • otherwise, pick a p(e) cost t between l and P(h), find a shortest path to get W(t) and P(t), split the interval into [l,t] and [t,h], and put them back in the priority queue and repeat.

The worst case complexity to get an exact result is still O(E2), but there are many economies and a lot of flexibility in how to stop.

like image 109
Matt Timmermans Avatar answered Oct 18 '22 14:10

Matt Timmermans


This is only a 2-approximation, not an approximation scheme, but perhaps it inspires someone to come up with a better answer.

Using binary search, find the minimum spiked cost θ* such that, letting C(θ) be the minimum nominal cost of an s-t path using edges with spiked cost ≤ θ, we have C(θ*) = θ*. Every solution has either nominal or spiked cost at least as large as θ*, hence θ* leads to a 2-approximate solution.

Each test in the binary search involves running Dijkstra on the subset with spiked cost ≤ θ, hence this algorithm takes time O(|E| log2 |E|), well, if you want to be technical about it and use Fibonacci heaps, O((|E| + |V| log |V|) log |E|).

like image 20
David Eisenstat Avatar answered Oct 18 '22 15:10

David Eisenstat