Reputation: 4717
I have the following undirected graph, in which I am trying to reach node 5
destination node starting from node 0
via all the paths available and not just a single or the shortest one.
The graph is a dictionary as such:
graph = { "0" : ["2", "1"],
"2" : ["0", "3", "4"],
"3" : ["2"],
"1" : ["0", "5"],
"4" : ["2", "5"],
"5" : ["1", "4"]
}
My approach is to start with the start node, then iterate through each neighbor, and then through their neighbors until a destination is reached.
However, the issue I am facing is that, I can not change the main iterative in the for loop to a new one real-time (Not possible and highly not recommended).
For example, If I start to loop in the neighbors of 0
which are 2
and 1
, I would be looping through [2, 1]
initially, I would then loop between 3
and 4
as they are the neighbors of 2
, however, I can't change this in the for loop as the iterative will still remain [2, 1]
instead of becoming [3, 4]
. Also, [2, 1]
needs to be kept referenced in a way that after node 3
and node 4
and it's neighbors [5]
are traversed, the 2nd node in the first iterator 1
should be traversed alongside it's neighbor which is [1]
in this case.
Upvotes: 0
Views: 850
Reputation: 42143
You can do it iteratively using a stack structure that holds the list of next nodes to explore and the path to reach them.
for example:
def findPath(graph, origin,target):
stack = [(origin,[origin])] # Start from origin
seen = {origin} # Track where you've been so far
while stack:
node, path = stack.pop(0) # BFS (use pop(-1) for DFS)
for nextNode in graph[node]: # Go through neighbours
if nextNode in seen: continue # not seen yet
nextPath = path + [nextNode] # Record path to get there
if nextNode == target: return nextPath # Arrived!
seen.add(nextNode) # Avoid returning here
stack.append( (nextNode,nextPath) ) # Stack for more exploration
return [] # no path to target
output:
graph = { "0" : ["2", "1"],
"2" : ["0", "3", "4"],
"3" : ["2"],
"1" : ["0", "5"],
"4" : ["2", "5"],
"5" : ["1", "4"]
}
print(findPath(graph,"0","5"))
['0', '1', '5']
print(findPath(graph,"3","5"))
['3', '2', '4', '5']
print(findPath(graph,"1","3"))
['1', '0', '2', '3']
BFS (Breadth First Search) will find the shortest path. DFS (Depth First search) will consume less memory but may not find an optimal path.
To get all the possible paths, you can convert this function into a generator but then you can't use the 'seen' optimization as it would short circuit multiple partial paths reaching the same node. In this case you should use DFS because you're gonna explore all paths anyway and DFS will be more memory efficient.
def findPaths(graph, origin,target):
stack = [(origin,[origin])] # Start from origin
while stack:
node, path = stack.pop(-1) # DFS (use pop(0) for BFS)
for nextNode in graph[node]: # Go through neighbours
if nextNode in path: continue # don't loop inside paths
nextPath = path + [nextNode] # Record path to get there
if nextNode == target:
yield nextPath # Return this path,
continue # continue with others
stack.append( (nextNode,nextPath) ) # Stack for more exploration
ouput:
graph = { "0" : ["2", "1"],
"2" : ["0", "3", "4"],
"3" : ["2"],
"1" : ["0", "5"],
"4" : ["2", "5"],
"5" : ["1", "4"]
}
for path in findPaths(graph,"0","5"): print(path)
['0', '1', '5']
['0', '2', '4', '5']
for path in findPaths(graph,"3","5"): print(path)
['3', '2', '4', '5']
['3', '2', '0', '1', '5']
for path in findPaths(graph,"1","3"): print(path)
['1', '0', '2', '3']
['1', '5', '4', '2', '3']
Upvotes: 1
Reputation: 1
I'm not sure this answers your question of how to do iteratively but I would suggest you solve it in an recursive approach, therefor different nodes could be run iteratively without interfering with other nodes and could scale infinitely. what I mean is you'll take the loop of neighboring nodes and make it a function that receives destination node and current node and does the logic you've already implemented, for each node that isn't the destination node you would run the function on the node. GL
Upvotes: 0