Uniform Cost Search in Python - python

I have implemented a simple graph data structure in Python with the following structure below. The code is here just to clarify what the functions/variables mean, but they are pretty self-explanatory so you can skip reading it.
# Node data structure
class Node:
def __init__(self, label):
self.out_edges = []
self.label = label
self.is_goal = False
def add_edge(self, node, weight = 0):
self.out_edges.append(Edge(node, weight))
# Edge data structure
class Edge:
def __init__(self, node, weight = 0):
self.node = node
self.weight = weight
def to(self):
return self.node
# Graph data structure, utilises classes Node and Edge
class Graph:
def __init__(self):
self.nodes = []
# some other functions here populate the graph, and randomly select three goal nodes.
Now I am trying to implement a uniform-cost search (i.e. a BFS with a priority queue, guaranteeing a shortest path) which starts from a given node v, and returns a shortest path (in list form) to one of three goal node. By a goal node, I mean a node with the attribute is_goal set to true.
This is my implementation:
def ucs(G, v):
visited = set() # set of visited nodes
visited.add(v) # mark the starting vertex as visited
q = queue.PriorityQueue() # we store vertices in the (priority) queue as tuples with cumulative cost
q.put((0, v)) # add the starting node, this has zero *cumulative* cost
goal_node = None # this will be set as the goal node if one is found
parents = {v:None} # this dictionary contains the parent of each node, necessary for path construction
while not q.empty(): # while the queue is nonempty
dequeued_item = q.get()
current_node = dequeued_item[1] # get node at top of queue
current_node_priority = dequeued_item[0] # get the cumulative priority for later
if current_node.is_goal: # if the current node is the goal
path_to_goal = [current_node] # the path to the goal ends with the current node (obviously)
prev_node = current_node # set the previous node to be the current node (this will changed with each iteration)
while prev_node != v: # go back up the path using parents, and add to path
parent = parents[prev_node]
path_to_goal.append(parent)
prev_node = parent
path_to_goal.reverse() # reverse the path
return path_to_goal # return it
else:
for edge in current_node.out_edges: # otherwise, for each adjacent node
child = edge.to() # (avoid calling .to() in future)
if child not in visited: # if it is not visited
visited.add(child) # mark it as visited
parents[child] = current_node # set the current node as the parent of child
q.put((current_node_priority + edge.weight, child)) # and enqueue it with *cumulative* priority
Now, after lots of testing and comparing with other alogrithms, this implementation seemed to work pretty well - up until I tried it with this graph:
For whatever reason, ucs(G,v) returned the path H -> I which costs 0.87, as opposed to the path H -> F -> I, costing 0.71 (this path was obtained by running a DFS). The following graph also gave an incorrect path:
The algorithm gave G -> F instead of G -> E -> F, obtained again by the DFS. The only pattern I can observe among these rare cases is the fact that the chosen goal node always has a loop. I can't figure out what is going wrong though. Any tips will be much appreciated.

Usually for searches, I tend to keep the path to a node part of the queue. This is not really memory efficient, but cheaper to implement.
If you want the parent map, remember that it is only safe to update the parent map when the child is on top of the queue. Only then has the algorithm determined the shortest path to the current node.
def ucs(G, v):
visited = set() # set of visited nodes
q = queue.PriorityQueue() # we store vertices in the (priority) queue as tuples
# (f, n, path), with
# f: the cumulative cost,
# n: the current node,
# path: the path that led to the expansion of the current node
q.put((0, v, [v])) # add the starting node, this has zero *cumulative* cost
# and it's path contains only itself.
while not q.empty(): # while the queue is nonempty
f, current_node, path = q.get()
visited.add(current_node) # mark node visited on expansion,
# only now we know we are on the cheapest path to
# the current node.
if current_node.is_goal: # if the current node is a goal
return path # return its path
else:
for edge in in current_node.out_edges:
child = edge.to()
if child not in visited:
q.put((current_node_priority + edge.weight, child, path + [child]))
Note: I haven't really tested this, so feel free to comment, if it doesn't work right away.

A simple check before expanding the node can save you duplicate visits.
while not q.empty(): # while the queue is nonempty
f, current_node, path = q.get()
if current_node not in visited: # check to avoid duplicate expansions
visited.add(current_node) # mark node visited on expansion,
# only now we know we are on the cheapest path to
# the current node.
if current_node.is_goal: # if the current node is a goal
return path # return its path
...

Related

Leetcode 261 Graph Valid Tree: second test fails

I am trying to write a solution for Leet Code problem 261. Graph Valid Tree:
Given n nodes labeled from 0 to n-1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.
Example 1:
Input: n = 5, and edges = [[0,1], [0,2], [0,3], [1,4]]
Output: true
Example 2:
Input: n = 5, and edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
Output: false
Here is my solution thus far. I believe that the goal is to detect cycles in the tree. I use dfs to do this.
class Node:
def __init__(self, val):
self.val = val
self.outgoing = []
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
visited = {}
for pre, end in edges:
if pre not in visited:
"we add a new node to the visited set"
visited[pre] = Node(pre)
if end not in visited:
visited[end] = Node(end)
"We append it to the list"
visited[pre].outgoing.append(visited[end])
def dfs(current, dvisit = set()):
if current.val in dvisit:
print("is the condition happening here")
return True
dvisit.add(current.val)
for nodes in current.outgoing:
dfs(nodes, dvisit)
return False
mdict = set()
for key in visited.keys():
mdict.clear()
if dfs(visited[key], mdict) == True:
return False
return True
It fails this test n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
It is supposed to return false but it returns true.
I placed some print statements in my dfs helper function and it does seem to be hitting the case where dfs is supposed to return true. However for some reason, the case in my for loop does not hit in the end, which causes the entire problem to return true for some reason. Can I receive some guidance on how I can modify this?
A few issues:
The given graph is undirected, so edges should be added in both directions when the tree data structure is built. Without doing this, you might miss cycles.
Once edges are made undirected, the algorithm should not travel back along the edge it just came from. For this purpose keep track of the parent node that the traversal just came from.
In dfs the returned value from the recursive call is ignored. It should not: when the returned value indicates there is a cycle, the loop should be exited and the same indication should be returned to the caller.
The main loop should not clear mdict. In fact, if after the first call to dfs, that loop finds another node that has not been visited, then this means the graph is not a tree: in a tree every pair of nodes is connected. No second call of dfs needs to be made from the main code, which means the main code does not need a loop. It can just call dfs on any node and then check that all nodes were visited by that call.
The function could do a preliminary "sanity" check, since a tree always has one less edge than it has vertices. If that is not true, then there is no need to continue: it is not a tree.
One boundary check could be made: when n is 1, and thus there are no edges, then there is nothing to call dfs on. In that case we can just return True, as this is a valid boundary case.
So a correction could look like this:
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
if n != len(edges) + 1: # Quick sanity check
return False
if n == 1: # Boundary case
return True
visited = {}
for pre, end in edges:
if pre not in visited:
visited[pre] = Node(pre)
if end not in visited:
visited[end] = Node(end)
visited[pre].outgoing.append(visited[end])
visited[end].outgoing.append(visited[pre]) # It's undirected
def dfs(parent, current, dvisit):
if current.val in dvisit:
return True # Cycle detected!
dvisit.add(current.val)
for node in current.outgoing:
# Avoid going back along the same edge, and
# check the returned boolean!
if node is not parent and dfs(current, node, dvisit):
return True # Quit as soon as cycle is found
return False
mdict = set()
# Start in any node:
if dfs(None, visited[pre], mdict):
return False # Cycle detected!
# After one DFS traversal there should not be any node that has not been visited
return len(mdict) == n
A tree is a special undirected graph. It satisfies two properties
It is connected
It has no cycle.
No cycle can be expressed as NumberOfNodes ==NumberOfEdges+1.
Based on this, given edges:
1- Create the graph
2- then traverse the graph and store the nodes in a set
3- Finally check if two conditions above are met
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
from collections import defaultdict
graph = defaultdict(list)
for src, dest in edges:
graph[src].append(dest)
graph[dest].append(src)
visited = set()
def dfs(root):
visited.add(root)
for node in graph[root]:
if node in visited:
# if you already visited before, means you alredy run dfs so do not run dfs again
continue
dfs(node)
dfs(0)
# this shows we have no cycle and connected
return len(visited) == n and len(edges)+1 == n
This question is locked in leetcode but you can test it here for now:
https://www.lintcode.com/problem/178/description

Uniform Cost Search In Romania Map problem

I have written this code. This is a code of Uniform COst search. I have to find the path between Arad and Bucharest. My problem is that my code is giving the correct total cost that is 418. But I can not figure out how to find the path that is giving this cost. Any help is appreciated.
from queue import PriorityQueue
class Graph:
def __init__(self):
self.edges={"Arad":["Zerind","Timisoara","Sibiu"],"Zerind":["Oradea"],"Oradea":["Sibiu"],"Timisoara":["Lugoj"],"Lugoj":["Mehadia"],"Mehadia":["Dobreta"],"Dobreta":["Craiova"],"Sibiu":["Fagaras","RimnicuVilcea"],"Craiova":["RimnicuVilcea","Pitesti"],"RimnicuVilcea":["Craiova","Pitesti"],"Fagaras":["Bucharest"],"Pitesti":["Bucharest"],"Bucharest":["Giurgiu","Urziceni"],"Urziceni":["Hirsova","Vaslui"],"Hirsova":["Eforie"],"Vaslui":["Lasi"],"Lasi":["Neamt"]}
self.weights={"AradZerind":75,"ZerindOradea":71,"AradTimisoara":118,"TimisoaraLugoj":111,"LugojMehadia":70,"MehadiaDobreta":75,"AradSibiu":140,"OradeaSibiu":151,"DobretaCraiova":120,"CraiovaRimnicuVilcea":146,"CraiovaPitesti":138,"SibiuFagaras":99,"SibiuRimnicuVilcea":80,"RimnicuVilceaPitesti":97,"RimnicuVilceaCraiova":146,"FagarasBucharest":211,"PitestiBucharest":101,"BucharestGiurgiu":90,"BucharestUrziceni":85,"UrziceniHirsova":98,"HirsovaEforie":86,"UrziceniVaslui":142,"VasluiLasi":92,"LasiNeamt":87}
def neighbors(self,node):
return self.edges[node]
def get_cost(self,from_node,to_node):
return self.weights[(from_node + to_node)]
def ucs(graph, start, goal):
global total_cost
visited = set()
path=[]
queue = PriorityQueue()
queue.put((0, start))
while queue:
cost, node = queue.get()
if node not in visited:
visited.add(node)
if node == goal:
return visited
for i in graph.neighbors(node):
if i not in visited:
total_cost = cost + graph.get_cost(node, i)
queue.put((total_cost, I)
graph=Graph()
s=ucs(graph,"Arad","Bucharest")
print(s)
You can use initialize your (priority) queue like this:
queue = PriorityQueue()
queue.put([0,[start]])
Here, start is a tuple representing the starting state or anything you want to represent in your way.
Then unpack it inside the while loop:
cost,path = queue.get()
x,y=path[-1]
You don't need to define the path var in advance.
When the goal state is reached, instead of returning the cost, just print(cost) or whatever var you want to print and return the path:
x,y=path[-1]
And, to update it when we are traversing the adjacency list of each node, you can do this:
queue.put([costx,path + [(x2, y2)]])
If you want to keep track of many things, you can keep it inside the 'priorityQueue() (your queue).
I can add the code if you want but maybe that won't be necessary.

Iterative postorder traversal of a binary tree with a single stack, how to approach the problem?

I have been studying up on algorithms and data structures and I wrote a post-order traversal for a binary tree without using recursion and using only one stack.
Here is the code:
def postorder_iterative(self):
current = self
s = []
current1 = None
done = 0
def peek(s):
return s[-1]
while(not done):
if current and (current != current1):
s.append(current)
current = current.leftChild
elif(len(s) > 0):
if peek(s).rightChild and peek(s).rightChild != current:
current = peek(s).rightChild
else:
current = current1 = s.pop()
print(current.key)
else:
done = 1
This code actually works but it took me forever to come up with it.
Can someone explain what is the intuitive way of thinking about this problem?
I'd like to be able to reproduce it using logic and not spend as much time as I did on it.
Post-order traversal requires that you only print the current node value after traversing both the left and right subtrees. You are using the stack to traverse the left tree only, and use the current1 variable (the last node printed) to know that you are now backing out of a right-hand side tree so you can print the current node.
I'd rename current to node, current1 to last (for last printed), remove the peek() function to just reference stack[-1] directly as tos (top of stack), and simplify your approach to:
def postorder_iterative(self):
node, last = self, None
stack = []
while True:
if node and node is not last:
# build up the stack from the left tree
stack.append(node)
node = node.leftChild
elif stack:
# no more left-hand tree to process, is there a right-hand tree?
tos = stack[-1]
if tos.rightChild and tos.rightChild is not node:
node = tos.rightChild
else:
# both left and right have been printed
node = last = stack.pop()
print(last.key)
else:
break
It is still hard to follow what is going on however, as the connection between last and the point where the left and right subtrees have been processed isn't all that clear.
I'd use a single stack with a state flag to track where in the process you are:
def postorder_iterative(self):
new, left_done, right_done = range(3) # status of node
stack = [[self, new]] # node, status
while stack:
node, status = stack[-1]
if status == right_done:
stack.pop()
print(node.key)
else:
stack[-1][1] += 1 # from new -> left_done and left_done -> right_done
# new -> add left child, left_done -> add right child
next_node = [node.leftChild, node.rightChild][status]
if next_node is not None:
stack.append((next_node, new))
Nodes go through three states, simply by incrementing the state flag. They start as new nodes, then progress to left, then right, and when the top of the stack is in that last state we remove it from the stack and print the node value.
When still in the new or left states, we add the left or right node, if present, to the stack as a new node.
Another approach pushes the right-hand tree onto the stack before the current node. Then later, when you return to the current node, having taken it from the stack, you can detect the case where you still need to process the right-hand side because the top of the stack will have the right-hand node. In that case you swap the top of the stack with the current node and continue from there; you'll later return to the same place and will no longer have that right-hand side node on the top of the stack so you can print:
def postorder_iterative(self):
stack = []
node = self
while node or stack:
while node:
# traverse to the left, but add the right to the stack first
if node.rightChild is not None:
stack.append(node.rightChild)
stack.append(node)
node = node.leftChild
# left-hand tree traversed, time to process right or print
node = stack.pop()
if stack and node.rightChild is stack[-1]:
# right-hand tree present and not yet done, swap tos and node
node, stack[-1] = stack[-1], node
else:
print(node.key)
node = None

How to determine last stack space in recursion

I'm implementing well-known depth first search by recursion. I wonder whether there may be a way to know the code within last stack space. Why I need is I don't want to put -> character at the end of output. If possible just '\n' in the last step.
def DFS(self, vertex=None, visited=None):
if vertex is None:
vertex = self.root
if visited is None:
visited = []
print(f"{vertex} -> ", end='')
visited.append(vertex)
for neighbor in self.getNeighbors(vertex):
if neighbor not in visited:
visited.append(neighbor)
print(f"{neighbor} -> ", end='')
self.DFS(neighbor, visited)
For example, it yields 1 -> 2 -> 4 -> 5 ->
Is there anyway to do within the same method? Moreover, I could write a helper function removing the last -> character.
#Edit: What I've done according to #Carcigenicate's comment follows
return visited # last line in DFS method
-- in main --
dfs = graph.DFS()
path = " -> ".join(str(vertex) for vertex in dfs)
print(path)
Rather than trying to special-case the last vertex, special-case the first. That is, don't try to figure out when not to append the "->", just don't do it for the first vertex:
def DFS(self, vertex=None, visited=None):
if vertex is None:
vertex = self.root
else:
# Not the first vertex, so need to add the separator.
print(f" ->", end='')
if visited is None:
visited = []
print(f"{vertex}", end='')
visited.append(vertex)
for neighbor in self.getNeighbors(vertex):
if neighbor not in visited:
# no need to append here, because it will be done in the recursive call.
# and the vertex will be printed in the recursive call, too.
# visited.append(neighbor)
# print(f"{neighbor} -> ", end='')
self.DFS(neighbor, visited)
This assumes that your initial call will always be DFS(root, None, visited). Which I think is a reasonable assumption.
On second thought, perhaps using the visited parameter as the condition is a better idea:
if vertex is None:
vertex = self.root
if visited is None:
visited = []
else:
# Not the first vertex, so need to add the separator.
print(f" ->", end='')
print(f"{vertex}", end='')
The whole point is that it's easier to special-case the first item than the last.

Breadth search algorithm implementation

I wrote a python code for Breadth search algorithm for 8 puzzle probelm. The states of the game are coded as a list of lists (two dimensional list). The goal is to reach [[1,2,3],[8,0,4],[7,6,5]] from a given state. The code output the path from a given state to the goal. It works fine but for this particular case [[0,1,2],[7,8,3],[6,5,4]] does not give the whole path rather it gives the last three states in the path.
Would you please try to point out the place of the bugs. Here is the code. Note The program starts from the statement:
"""
This program solves the 8 puzzel problems using Breadth First Search Algorithm.
"""
import copy
def successor(astate):
"""
This function takes an instance of the 8 puzzel problem and generates all the legal successors.
"""
up = copy.deepcopy(astate)
"""
important note:
I use [:] and list() to copy lists but it was not working fine, hence deecopy is used instead.
"""
down = copy.deepcopy(astate)
left = copy.deepcopy(astate)
right = copy.deepcopy(astate)
successors = []
for i in range(3):
for j in range(3):
if astate[i][j] == 0:
row = i
col = j
if row != 0:
dummy = up[row][col]
up[row][col] = up[row -1][col]
up[row-1][col] = dummy
successors.append(up)
if row != 2:
dummy = down[row][col]
down[row][col] = down[row+1][col]
down[row+1][col] = dummy
successors.append(down)
if col != 2:
dummy = right[row][col]
right[row][col] = right[row][col+1]
right[row][col+1] = dummy
successors.append(right)
if col != 0:
dummy = left[row][col]
left[row][col] = left[row][col-1]
left[row][col-1] = dummy
successors.append(left)
return successors
def puzzle(astate):
"""
This function takes a given instance of the 8 puzzel problem and returns the path to the goal where the goal is defined as below.
"""
goal = [[1,2,3],[8,0,4],[7,6,5]] #The goal state.
generation = [astate] #Nodes generated at each level.
tracking = {} #Track the path to the goal.
path = [] #The path from the root to the goal.
parent = generation.pop(0) #Takes the first element of the list!!
successors = successor(parent) #Generate successors.
key = str(parent) #keys odictionaries must be hashable and mutable. Lists are illegal keys.
tracking[key] = successors #Associate successors with their parent.
for asuccessor in successors:
generation.append(asuccessor) #Generate the first level.
if goal in generation: #if the goal is among the successors returns the path to it.
path.insert(0, key)
path.insert(0, goal)
return path
else:
while generation != []: #keep searching!
parent = generation.pop(0)
successors = successor(parent) #generate successors
key = str(parent)
tracking[key] = successors
if goal in [astate for astate in successors]: #if the goal is among the successors backtrack its path.
path.insert(0, str(goal)) #Just because path contains states as strings!!
path.insert(0, key)
for key in tracking.keys():
for value in tracking.get(key):
if str(parent) == str(value): #If the current (parent) is among the values of (key) then (key) is its parent.
path.insert(0, key)
parent = key
break
return path
else: #keep searching
for asuccessor in successors:
if asuccessor not in generation: #If the current successors is already generated do not add it.
if str(asuccessor) not in tracking.keys(): #If the successor is a previous parent do not add it.
generation.append(asuccessor)
return

Categories

Resources