28

Here is the exercise:

Let v and w be two vertices in a directed graph G = (V, E). Design a linear-time algorithm to find the number of different shortest paths (not necessarily vertex disjoint) between v and w. Note: the edges in G are unweighted


For this excise, I summarise as follows:

  1. It is a directed graph
  2. It asks for the number of different shortest paths. First, the paths should be shortest, then there might be more than one such shortest paths whose length are the same.
  3. between v and w, so both from v to w and from w to v should be counted.
  4. linear-time.
  5. The graph is not weighted.

From the points above, I have the following thoughts:

  1. I don't need to use Dijkstra’s Algorithm because the graph is not weighted and we are try to find all shortest paths, not just single one.
  2. I maintain a count for the number of shortest paths
  3. I would like to use BFS from v first and also maintain a global level information
  4. I increase the global level by one each time then BFS reaches a new level
  5. I also maintain the shortest level info for shortest path to w
  6. The first time I meet w while traveling, I assign the global level to the shortest level and count++;
  7. as long as the global level equals to the shortest level, I increase count each time I met w again.
  8. if the global level becomes bigger than the shortest level, I terminate the travelling, because I am looking for shortest path not path.
  9. Then I do 2 - 8 again for w to v

Is my algorithm correct? If I do v to w and then w to v, is that still considered as linear-time?

nbro
  • 15,395
  • 32
  • 113
  • 196
Jackson Tale
  • 25,428
  • 34
  • 149
  • 271

8 Answers8

24

Here are some ideas on this.

  • There can only be multiple shortest paths from v->w through node x, either if there are multiple paths into x through the same vertice or if x is encountered multiple time at the same DFS level.

Proof: If there are multiple paths entering x through the same vertex there are obviously multiple ways through x. This is simple. Now let us assume there is only one way into x through each vertex going into x (at maximum).

If x has been encountered before, none of the current paths can contribute to another shortest path. Since x has been encountered before, all paths that can follow will be at least one longer than the previous shortest path. Hence none of these paths can contribute to the sum.

This means however we encounter each node at most once and are done. So a normal BFS is just fine.

  • We do not even need to know the level, instead we can get the final number once we encounter the final node.

This can be compiled into a very simple algorithm, which is mainly just BFS.

 - Mark nodes as visited as usual with BFS.
 - Instead of adding just nodes to the queue in the DFS add nodes plus number of incoming paths.
 - If a node that has been visited should be added ignore it.
 - If you find a node again, which is currently in the queue, do not add it again, instead add the counts together.
 - Propagate the counts on the queue when adding new nodes.
 - when you encounter the final, the number that is stored with it, is the number of possible paths.
Jay Taylor
  • 13,185
  • 11
  • 60
  • 85
LiKao
  • 10,408
  • 6
  • 53
  • 91
  • I have to mark qrqrq's answer as he first pointed out the flaw of my solution and lead to a good discussions among you guys. – Jackson Tale Apr 19 '12 at 14:05
  • Just to point out - @qrqrq was incorrect - your solution is not flawed; nor does it require exponential time in the worst case. You simply need to use a modified version of the BFS algorithm. – Dan Nissenbaum Apr 19 '12 at 14:06
  • @DanNissenbaum: The second part (it is possible and does not need EXPTIME) should be obvious from the presentation of some simple modification to BFS which achieves this in my answer. – LiKao Apr 19 '12 at 14:16
  • @JacksonTale: I am not sure wether your solution would be as flawed as qrqrq assumed. qrqrq based his assumptions on the (assumed) impossibility of the task at hand, but he was wrong on that count. So your algorithm might work as well. Just test it on his graph, if I understand your algorithm correctly it will not return 2 as he assumed, but I am not sure wether it is correct or if it returns something wrong still. – LiKao Apr 19 '12 at 14:19
  • @LiKao - I agree. I'm referring to the fact that JacksonTale selected qrqrq's answer as the solution, even though his answer is misleading, so this might confuse people who arrive at this question in the future. – Dan Nissenbaum Apr 19 '12 at 14:20
  • @DanNissenbaum ok, I will have a bit more study on these answers. I thought qrqrq's answer is correct, because my BFS really won't count the situation qrqrq proposed, because my solution intended to use a standard BFS which pass every edge and deal with every vertex just once. but to deal with the case qrqrq proposed, like you said, I have to modify my BFS a little. – Jackson Tale Apr 19 '12 at 14:25
  • @JacksonTale: It's enough to pass every edge and every vertex just once. You just have to keep the correct count when doing this. I have not verified that your algorithm for that. However this renders any argument given by qrqrq as incorrect. My solutions will also only pass every vertex and edge once and I have verified in my head it will return 16 on his example. – LiKao Apr 19 '12 at 14:38
  • This is a scenario where the algorithm in your question is *roughly* correct, in that the main ideas are correct (using the BFS approach is correct, and it works in linear time), but the algorithm doesn't literally succeed completely unchanged. Therefore, technically speaking, your algorithm, *exactly* as it is written, seems to fail, but the *main ideas* are correct. So, it's misleading to state that *your algorithm fails* when, in fact, the main ideas are correct - even though technically it is true that "the algorithm fails". – Dan Nissenbaum Apr 19 '12 at 15:11
  • both of you @DanNissenbaum are right. I will mark this answer – Jackson Tale Apr 20 '12 at 09:41
  • Even simpler, count(v) = 0, count(s) = 1. for every neighbour u of v, if(d(v) + 1 == d(u)), then count(u) += count(v). now reset everything and do the same from the end vertex. @LiKao – Ilan Aizelman WS Jul 27 '17 at 07:20
10

Your algorithm breaks on a graph like

  *   *   *   1
 / \ / \ / \ / \
v   *   *   *   w
 \ / \ / \ / \ /
  *   *   *   2

with all edges directed left to right. It counts two paths, one through 1 and the other through 2, but both 1 and 2 can be reached from v by eight different shortest paths, for a total of sixteen.

qrqrq
  • 134
  • 2
  • I think you are right. How about modify my algorithm in the way that when BFS, I remove the stats of `discovered`, instead, I use `processed` to check whether I go through a vertex or not? – Jackson Tale Apr 19 '12 at 13:05
  • The correct algorithm along those lines requires exponential time in the worst case -- each new constant-size "pleat" doubles the number of paths to be counted. – qrqrq Apr 19 '12 at 13:12
  • Actually, I think the exponential time can be avoided by associating a "success count" with each node that tracks the number of successful paths below that node, along with their lengths. A modified BFS algorithm updates the success count when the recursion exits (passing the success count up the chain). When the BFS algorithm hits a node that has previously been hit (this hit is be part of the BFS algorithm), it avoids the recursion and simply passes the success count result back up the chain. – Dan Nissenbaum Apr 19 '12 at 13:27
  • 1
    @qrqrq: that only holds, if you want to actually output all the shortest paths. If you only need the number I would think this could be done, because you do not have to follow all shortest paths. – LiKao Apr 19 '12 at 13:29
  • If you actually bothered to read what I wrote, that's not what I said. – qrqrq Apr 20 '12 at 04:35
  • 1
    @qrqrq: maybe the statement "the correct algorithm along those lines requires exponential time in the worst case" gave the wrong impression about what you meant. Because the correct algorithm along those lines (i.e. a modified bfs) does not require EXPTIME. A simple modified bfs can do this correctly in O(N). However it may unclear, what should be considered 'a correct algorithm along those lines'. – LiKao Apr 20 '12 at 11:09
4

As qrqrq shows, your algorithm fails on some graphs, but the idea of BFS is good. Instead, maintain an array z of size |V| which you initialize to zero; keep the number of shortest paths to a discovered vertex i at distance less than level in z[i]. Also maintain an array d of size |V| such that d[i] is the distance from v to vertex i if that distance is less than level. Initialize level to 0, d[v] to 0, and z[v] to 1 (there is a single path of length 0 from v to v), and set all other entries of d to -1 and of z to 0.

Now whenever you encounter an edge from i to j in your BFS, then:

  • If d[j] = -1, then set d[j] := level and z[j] := z[i].
  • If d[j] = level, then set z[j] := z[j] + z[i].
  • Otherwise, do nothing.

The reason is that for every shortest path from v to i, there is one shortest path from v to j. This will give the number of shortest paths from v to each vertex in linear time. Now do the same again but starting from w.

Erik P.
  • 1,577
  • 10
  • 18
  • How about modify my algorithm in the way that when BFS, I remove the stats of discovered, instead, I use processed to check whether I go through a vertex or not? – Jackson Tale Apr 19 '12 at 13:07
  • @JacksonTale Not sure I understand. You'll definitely need to maintain the number of shortest paths to each vertex you encounter - otherwise how will you distinguish between different vertices discovered at the same level that have different numbers of shortest paths to them? – Erik P. Apr 19 '12 at 13:12
  • @ErikP.: there is a simpler solution... see my answer above. – LiKao Apr 19 '12 at 13:51
  • 1
    @LiKao - it may be better to allow the questioner to decide which solution is simpler, even in cases where it's obvious. It's a bit humbler that way. – Dan Nissenbaum Apr 19 '12 at 13:59
  • @DanNissenbaum I know you are kind, but I prefer LiKao's way, lol – Jackson Tale Apr 19 '12 at 14:04
  • @LiKao that's actually just a different description of the same solution :) – Erik P. Apr 19 '12 at 16:27
  • @ErikP.: Oh, I assumed it was something different. In my solution I neither keep track of the levels, nor do any search from `w`. Just one BFS pass from `v` to `w` is sufficient. Maybe I just got confused by the explanation and was assuming somethig that you did not mean. – LiKao Apr 19 '12 at 16:33
  • @ErikP.: Ok, I see now... I misread your explanation. It is exactely the same as my, except that you are using the level array to build up your finished set. – LiKao Apr 19 '12 at 16:39
  • @JacksonTale: If you need to decide wether to take his or mine, take his, he was first... I just was to dumb to see, that he had the same idea. – LiKao Apr 19 '12 at 16:40
  • Even simpler, count(v) = 0, count(s) = 1. for every neighbour u of v, if(d(v) + 1 == d(u)), then count(u) += count(v). now reset everything and do the same from the end vertex. @ErikP. – Ilan Aizelman WS Jul 27 '17 at 07:20
2

This algorithm looks correct to me.

BFS, as you know, is a linear time (O(N)) search because the time T required to complete it is, at worst, T = C + a * N, where N is the number of nodes and C, a are any fixed constants.

In your case, performing the search twice - first from v to w, and then from w to v - is (at worst) 2T, or 2C + 2a * N, which also satisfies the linear time requirement O(N) if you define a new C' = 2C, and a new a' = 2a, because both C' and a' are also fixed constants.

Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
  • thanks. One question: so linear-time in Graph problems also means O(n)? Not just finish everything in one go? – Jackson Tale Apr 19 '12 at 11:16
  • @JacksonTale it usually means O(n) in any algorithm. In graph ones there's a catch though: n can mean either number of vertices or edges. – soulcheck Apr 19 '12 at 11:20
  • Dan, sorry to unmark your answer as correct one. qrqrq has found a flaw in my algorithm – Jackson Tale Apr 19 '12 at 13:06
  • 1
    I didn't go into details about the nature of the BFS algorithm that you would use, but focused on the linear time question. I believe a version of the BFS algorithm suffices to avoid multiple recursions and therefore maintain the `O(N)` nature of the BFS algorithm, avoiding the exponential recursion. See my comment above. I haven't programmed it to test, but I think it should work. – Dan Nissenbaum Apr 19 '12 at 13:29
2
int edgeCb( graphPT g, int x, int y )
{
    if ( dist[ y ] > dist[ x ] + 1 ) {
        dist[ y ] = dist[ x ] + 1; // New way
        ways[ y ] = ways[ x ]; // As many ways as it's parent can be reached
    } else if ( dist[ y ] == dist[ x ] + 1 ) {
        ways[ y ] += ways[ x ]; // Another way
    } else {
        // We already found a way and that is the best
        assert( dist[ y ] < g->nv );
    }
    return 1;
}

Above code is giving me proper results for all kind of graphs mentioned in this post. Basically it is a edge callback for the BFS traversal.

dist[ start ] = 0; ways[ start ] = 1;

for rest all vertices dist[ x ] = numberOfVertices; // This is beyond the max possible disatance

BFS( g, start );

If ways[ end ] is not zero then that represents the number of ways and dist[ end ] represents the shortest distance.

Incase ways[ end ] == 0 means end can't be reached from start.

Please let me know if there are any loop holes in this.

vamsi
  • 29
  • 3
2

Simplest solution by changing BFS:

count(v) = 0, count(s) = 1. for every neighbour u of v, if(d(v) + 1 == d(u)), then count(u) += count(v). now reset everything and do the same from the end vertex.

Community
  • 1
  • 1
Ilan Aizelman WS
  • 1,630
  • 2
  • 21
  • 44
0

Can i do it this way

  1. I traverse using BFS till I reach the destination vertex and maintain levels
  2. Once i reach the destination level, I use the level table as follows

From the level table, i start traversing back counting the number of parents to the vertex in our path(first time it would be the destination vertex).
At every step, I multiply the number of distinct parents found at that particular level to the the shortest paths I can have to the destination vertex.
I move up the levels, only considering the nodes that fall into my path and multiply the number of distinct parents found at each level till I reach the level 0.

Does this work?

Zulu
  • 8,765
  • 9
  • 49
  • 56
Rahul Krishna
  • 339
  • 1
  • 4
  • 7
0

Just check the good explanation given here:

https://www.geeksforgeeks.org/number-shortest-paths-unweighted-directed-graph/

Briefly, we can modify any shortest path algorithm, and when the update step comes increases a counter for the number of shortest path previous discovered when the current path proposal has the same length of the shortest path found until that moment.

In the particular case, when it's a graph unweighted or with constant weight for all edges, the easiest way is to modify a BFS.

Jonathan Prieto-Cubides
  • 2,577
  • 2
  • 18
  • 17