7

Problem 34 of creative problems from this page.

Monotonic shortest path. Given an edge-weighted digraph, find a monotonic shortest path from s to every other vertex. A path is monotonic if the weight of every edge on the path is either strictly increasing or strictly decreasing.

Partial solution: relax edges in ascending order and find a best path; then relax edges in descending order and find a best path.

My question:

Suppose we are relaxing edges in descending order and we have an option of more than 1 edge to take at a point. On what basis will we choose the next edge to take? Ideally we should choose the smaller edge as it will minimize the distance to that vertex. But doing this may result in no further paths from that vertex if all edges leaving it have a weight that is greater than current edge's weight.

So, how can we solve this problem?

Community
  • 1
  • 1
Nikunj Banka
  • 11,117
  • 16
  • 74
  • 112
  • Are you asking about arcs of equal length? It doesn't matter which order they are relaxed in, since they can't both belong to the same monotone path. – David Eisenstat Apr 05 '14 at 04:16
  • @DavidEisenstat No, I am talking about edges of different lengths. If the edges are of equal length, I will pick one arbitrarily. – Nikunj Banka Apr 05 '14 at 04:22
  • If you have more than 1 edge to take at a point, then you are forced to maintain a list of propositions, since there is insufficient information to choose. – user3386109 Apr 05 '14 at 06:05
  • @user3386109 Then is it impossible to achieve this in O(E logV) ? – Nikunj Banka Apr 05 '14 at 06:16
  • 1
    I'm afraid I can't quantify the complexity of the algorithm. On the one hand the list of propositions would apparently grow exponentially, but on the other hand the list is rapidly culled by the monotonicity requirement, and in fact the monotonicity requirement eliminates some edges from consideration altogether. Are you sure this problem has a solution that's provably O(E logV) time? – user3386109 Apr 05 '14 at 06:31
  • @user3386109 you can view the 1st question of the job interview questions on Shortest Paths given in this course https://www.coursera.org/course/algs4partII . They are indeed asking for a O(ElogE) algorithm. And O(ElogE) is same as O(ElogV). – Nikunj Banka Apr 05 '14 at 06:50

2 Answers2

6

This problem could be solved by modified Dijkstra’s algorithm. The main point is that relaxation should be done not with min operation in every graph node (as usual) but in the priority queue.

Here is the list of modifications for usual Dijkstra’s algorithm. I consider only edges' relaxation in ascending order, which results in strictly decreasing shortest path (to get increasing shortest path, alter items 2 and 4):

  1. Preprocess the graph by sorting outgoing edges from each node (by weight).
  2. Each node should contain position in the list of outgoing edges (initialized by position of the lightest edge).
  3. There is no need for priority queue to support "decrease" operation (so it could be implemented by simple min-heap). Each vertex is inserted into priority queue and then never altered until it appears at the top of queue (as a result each vertex may be represented in the queue several times). Queue entry consists of a key (which is path length, as usual), vertex, and weight of incoming edge. So we may assume priority queue to contain incoming edges instead of vertices.
  4. Relaxation procedure: pop edge (and hence vertex where this edge ends) from the queue; for all outgoing edges of the vertex in increasing order, starting from the position stored in graph node, and ending when outgoing edge's weight is greater or equal to the incoming edge's weight, push outgoing edge to priority queue and advance stored position.

This algorithm guarantees that each edge is processed at most once (or twice if we consider both strictly decreasing and strictly increasing paths), so its complexity is O(E log E).

C++11 implementation:

void getDecreasingSP(Vertices& vertices, Edges& edges, int src)
{
    for (auto& v: vertices)
        sort(begin(v.outEdges), end(v.outEdges),
             [&](int from, int to)
             {
                 return edges[from].weight < edges[to].weight;
             });

    PQ pq;
    auto& src_v = vertices[src];
    for (auto e: src_v.outEdges)
    {
        QEntry entry {edges[e].weight, e};
        pq.push(entry);
        ++src_v.pos;
    }

    while(!pq.empty())
    {
        QEntry top = pq.top();
        pq.pop();
        auto& v = vertices[edges[top.inEdge].to];

        while (v.pos < int(v.outEdges.size()) &&
            edges[v.outEdges[v.pos]].weight < edges[top.inEdge].weight)
        {
            auto e = v.outEdges[v.pos];
            edges[e].backPtr = top.inEdge;
            QEntry entry {top.pathWeight + edges[e].weight, e};
            pq.push(entry);
            ++v.pos;
        }

        if (v.backPtr == -1)
            v.backPtr = top.inEdge;
    }
}

See also working code on Ideone. And visualization of the graph (produced by this code with help of Graphviz) where one of strictly decreasing shortest paths is highlighted:

enter image description here

Evgeny Kluev
  • 24,287
  • 7
  • 55
  • 98
  • 4
    the algorithm is too complex to decipher, can you please illustrate it with an example. However, I have upvoted it. – Nikunj Banka Apr 06 '14 at 05:32
  • @NikunjBanka: It's not so easy to invent good examples for this problem. Instead I've added an implementation. BTW, it should be not difficult to understand it if you understand the concepts of original Dijkstra’s algorithm. – Evgeny Kluev Apr 06 '14 at 13:22
  • 4
    Why not use pseudocode? The C++11 clutter only makes it harder to understand it, especially with no comments referring back to the explanation. – Luke19 Feb 27 '20 at 14:24
3

I use modified Dijkstra algorithm to solve it : For example, if we want to find a best path in ascending order between source and every other vertex, use a Priority Queue PQ:

  1. PQ.add(source, 0)
  2. for other vertex, PQ.add(vertex, infinity)
  3. while PQ is not empty, let vertex i = PQ.removeSmallest(); relax all ascending edges from i;

relax edges from i to p with weight:

if (disTo[p] > disTo[i] + weight && weight > weight[i, edgeTo[i]) {
   disTo[p] =  disTo[i] + weight;
   edgeTo[p] = i;
   PQ.changePriority(p, disTo[p]);
}