2

Recently I was asking about the algorithm to solve The Gas Station Problem and I got useful answer. Unfortunately solution with transforming a graph to complete graph and then preparing another one to find the shortest path (as described in paragraph 4) is really slow in case of my constraints.

I figured out how to implement much faster version. It's way better, but has a bottleneck :-(. Let me first describe my implementation. It's really simple:

I use BFS and relaxation method to calculate D[destination][0]:

D[v][f] - minimum cost to get from u to v with remaining f units of fuel at this city.

minFuel - minimum remaining fuel at v = max(0, f1 - distance(u,v))
maxFuel - maximum remaining fuel at v = tankCapacity - distance(u,v)

D[source][0] = 0
D[v][f] = min((u,v), f1, f2 | u->v in Edges and f1 in 0..tankCapacity and f2 in minFuel..maxFuel)
             { D[u][f1] + (f2 - f1 + distance(u,v)) * fuelRate[u] }

The bottleneck is because of going through all possible values (f1, f2).

Pseudocode:

queue.push(source);
D[source][0] = 0
visited[source] = true

while(!queue.empty)
    current = queue.pop

    foreach (current,v) in Edges
       if !visited[v]
           queue.push(v)
           visited[v] = true

        for f1 in 0..tankCapacity
           if D[current][f1] == infinity
              continue

            minFuel = max(0, f1 - distance[current,v])
            maxFuel = tankCapacity - distance[current,v]

            for f2 in minFuel..maxFuel
                toRefill = f2 - f1 + distance[current,v]
                newCost =  D[current][f1] + toRefill * fuelRate[current]
                if (newCost < D[v][f2])
                   D[v][f2] = newCost


return D[destination][0]

Constraints (only integers):

maximum vertices:      1000
maximum edges:         10000
maximum distance:      100
maximum fuel rate:     100
maximum tank capacity: 100

Does anybody have an idea how to improve this algorithm to make it faster?

Wojciech Kulik
  • 223
  • 3
  • 10

2 Answers2

3

Your recurrence relationship isn't quite right -- you need to relate the input parameter f to f1 and f2 somehow. I think you can get rid of f2 completely:

checkTank(f) = if f <= U then f else INF
D[v][f] = min(u, f1 | u->v in Edges and f1 in 0..tankCapacity)
             { D[u][f1] + checkTank(f - f1 + distance(u, v)) * fuelRate[u] }

Entries D[v][f] for values of f which would imply putting more gas in the tank than it can hold will correctly get assigned INF by the above. (Here I assume that adding any nonnegative value to or multiplying any positive value with INF yields INF again; in an actual implementation you might want to specially check and avoid further processing of such values.)

But in any case, the algorithm in the paper is fast because they exploit a dominance rule (Lemma 2.1) that implies that an optimal solution can always be found even if you only look at O(n) particular different gas tank levels at each node, and you aren't making use of that here. If you don't make use of that same property, I think your algorithm will be at best pseudopolynomial (they say this in the paper; BTW that doesn't necessarily make it a terrible algorithm in practice) and in effect usable only with integer-weight fuel amounts.

Regarding adding the missing edges: For the case where you don't limit the number of stops, the running time is O(n^3), so it's safe to run Floyd-Warshall at the beginning to calculate the minimum path distance between all pairs of vertices that lack an edge, as it's also only O(n^3) and therefore doesn't increase the running time asymptotically.

At first glance it seems possible to accept an incomplete graph, and allow "fill stops" at which 0 fuel is filled. But this invalidates Lemma 2.1, since now there can be graphs in which an optimal path involves filling up less than the full amount at some node w, before travelling to some node u with higher cost, "filling up" with 0 fuel there, and then travelling on to reach (on an empty tank) some other node v with cheaper fuel than w. You can play around with Lemma 2.1 to allow arbitrarily long strings of special "0-fill" stops between "real" stops, and this works, but it doesn't buy you anything: for each vertex u, instead of having to consider all possible neighbours w of u and their distances d(w, u) to u, you'll have to consider all possible vertices w and the lengths of their shortest paths to u, which means that you might as well calculate them all at the start and make edges with their weights.

Finally you can discard/ignore/avoid calculating any edges longer than U or which violate the Triangle Inequality (in fact these constraints also apply to any edges already in the original graph).

j_random_hacker
  • 5,509
  • 1
  • 17
  • 22
1

Finally I had prepared a solution with good enough complexity. Friend from my college had given me some hints and I managed to implement it. Maybe it will help someone, so I decided to describe this solution here.

The idea is very simple, we use Dijkstra's Algorithm, but instead simple vertices we have pairs (v, f). v - vertex id, f - fuel in tank. This solution is much faster, because we don't need to go through all values of "f" and also when we process a vertex, it already has an optimal solution. So when we'll get to the destination vertex, we can stop further processing.

Dijkstra's Algorithm needs to get a minimum value in each iteration, so it's good to use heap structure for a priority queue.

Pseudocode:

D[v][f]        - minimum cost to get from source to v with f units of fuel
distance[u][v] - distance between u and v


relax(tankCapacity, u, f, newCost):
    if (f >= 0 and f <= tankCapacity and D[u][f] > newCost)
        D[u][f] = newCost
        heap_push_or_update(D[u][f])


solve(tankCapacity, source, destination):
    foreach (v, f)
        D[v][f] = infinity

    D[source][0] = 0
    heap_push(D[source][0])

    while not heap_empty()
        (v, f) = heap_pop_min();

        //don't go further than destination
        if (v == destination and f == 0)
            return D[v][0];

        //stay in the city and refill more fuel - it can improve D[v][f + 1]
        newCost = D[v][f] + fuelRate[v];
        relax(tankCapacity, v, f + 1, newCost);

        //go to the adjacent cities "u" without refilling - it can improve D[u][f - d]  
        foreach (v, u) in Edges
            relax(tankCapacity, u, f - distance[v][u], D[v][f]);
Wojciech Kulik
  • 223
  • 3
  • 10