Reputation: 55
I wrote a recursive function to find a simple path in a graph (there exists only one path), given list of graph edges as tuples. for example:
edges = [(0, 10),(2, 16),(4, 5),(6, 24),(7, 6),(8, 23),(9, 25),(10, 14),(11, 1),(12, 19),(13, 22),(14, 15),(15, 11),(16, 7),(17, 21),(18, 13),(19, 17),(20, 8),(21, 3),(22, 20),(23, 12),(24, 9),(25, 18)]
def get_path(sol, start, end):
out = []
for i,j in sol:
if i == start:
out.append(i)
out += get_path(sol, j, end)
if j == end:
out.append(j)
return out
However if I cannot get the end node correctly in my return list. If I remove line 7 and 8 the last node will not be present in my solution, and also if I have those lines in the code I get more than one end node in my solution. As an example:
get_path(edges, 2, 3)
path = [2, 16, 7, 11, 15, 14, 10, 22, 13, 18, 25, 21, 17, 19, 12, 9, 24, 6, 8, 20, 23]
As we can see end node is 3
which should be last element of the list. I highly appreciate your inputs. Thanks
Upvotes: 2
Views: 1528
Reputation: 350310
Each recursive call in your code will have one iteration where j == end
is true, and so you will indeed get it added multiple times: just as many as you have recursion depth.
It will work better if you put the end-condition outside of the loop, like this:
if start == end:
return [start]
So with that little modification your code would look like this:
def get_path(sol, start, end):
if start == end:
return [start]
out = []
for i,j in sol:
if i == start:
out.append(i)
out += get_path(sol, j, end)
return out
A few other remarks:
You can perform the two list operations in the if
block in one go:
out += [i] + get_path(sol, j, end)
It is very inefficient to iterate through the complete list in each recursive call. It would be better to first build a dictionary, keyed by the start number. That way you don't have to iterate to find the corresponding edge.
It seems that your code expects the graph to be a chain, without any
nodes where you have 2 or more outgoing edges. If that would happen, the if i == start:
block would execute multiple times resulting in an awkward output. It is not too hard to cover for such situations as well
Code:
from collections import defaultdict
def get_path(sol, start, end):
# Transform to dict
d = defaultdict(list)
for i,j in sol:
d[i] += [j]
def recur(start):
if start == end:
return [start]
for nxt in d[start]:
path = recur(nxt)
if path is not None:
return [start] + path
return recur(start)
edges = [(0, 10),(2, 16),(4, 5),(6, 24),(7, 6),(8, 23),(9, 25),(10, 14),
(11, 1),(12, 19),(13, 22),(14, 15),(15, 11),(16, 7),(17, 21),
(18, 13),(19, 17),(20, 8),(21, 3),(22, 20),(23, 12),(24, 9),(25, 18)]
path = get_path(edges, 2, 3)
print (path)
Your question is about acyclic graphs since you wrote:
there exists only one path
But for anyone coming here looking for a solution for cyclic graphs, the above solution would need to be extended so it does not get stuck in a cycle. This can be done by marking nodes as visited:
from collections import defaultdict
def get_path(sol, start, end):
# Transform to dict
d = defaultdict(list)
for i,j in sol:
d[i] += [j]
visited = set()
def recur(start):
if start == end:
return [start]
visited.add(start)
for nxt in d[start]:
if nxt not in visited:
path = recur(nxt)
if path is not None:
return [start] + path
visited.remove(start)
return recur(start)
edges = [(0,1),(1,0),(0,2),(2,0),(2,3),(3,2)]
path = get_path(edges, 2, 3)
print (path)
Note that neither solutions guarantee that the found path is the shortest. But if it is known that there is only one path, it is a memory efficient algorithm.
If there are multiple paths, then a depth-first search cannot stop the search when it finds a path, as there still might be shorter paths to be found. If on the other hand you use a breadth-first search, then you can stop as soon as you find a path.
Upvotes: 1
Reputation: 88236
You can use networkX
for that. You can define a network from the edges and obtain the simple paths between two given nodes. I'm using nx.shortest_simple_paths
, but you could use all_simple_paths
to obtain them all:
import networkx as nx
G=nx.Graph()
G.add_edges_from(edges)
next(nx.shortest_simple_paths(G, 2, 3))
# [2, 16, 7, 6, 24, 9, 25, 18, 13, 22, 20, 8, 23, 12, 19, 17, 21, 3]
Upvotes: 3