tony9099
tony9099

Reputation: 4717

Reaching a destination node in a graph from a start node in Python using iterative approach

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"]
        }

enter image description here

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

Answers (2)

Alain T.
Alain T.

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

RonRud
RonRud

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

Related Questions