Leetcode 133. Clone Graph with dfs understanding - python

Hi I have a problem understanding dfs. What I know is DFS has two versions; we mark visited before call and after call.
def solution1(start):
def dfs1(cur):
for nei in cur.neighbors:
if nei not in visited:
## mark visit before call
visited.add(nei)
dfs1(nei)
## drive dfs1
visited = set()
visited.add(start)
dfs1(start)
def solution2(start):
def dfs2(cur):
## mark visit after call
visited.add(cur)
for nei in cur.neighbors:
if nei not in visited:
dfs2(nei)
## drive dfs2
dfs2(start)
However, when I applied version1 (mark visited before a call) to the problem(https://leetcode.com/problems/clone-graph/), it complained and did not copy.
This is my solution:
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
"""
class Solution:
"""
def dfs1(cur):
for nei in cur.neighbors:
if nei in visited: continue
visited.add(nei)
dfs1(nei)
visited.add(start_node)
dfs1(start_node)
"""
def dfs(self, cur, visited):
new_node = Node(cur.val)
# visited[cur.val] = new_node
new_neighbors = []
for nei in cur.neighbors:
if nei.val not in visited:
visited[nei.val] = nei
new_neighbors.append(self.dfs(nei, visited))
else:
new_neighbors.append(visited[nei.val])
new_node.neighbors = new_neighbors
return new_node
def cloneGraph(self, node: 'Node') -> 'Node':
if node == None:
return None
visited = {}
visited[node.val] = node
return self.dfs(node, visited)
Let me know why this has problem. I don't understand why it does not work.

Main reason that your code doesn't work is because you store original node inside visited dictionary. You have to store processed DFS clone of a node inside visited. Why? Because inside new_neighbors.append(visited[nei.val]) you add visited node to neighbours of new cloned node. But any cloned node should have only cloned neighbours, not originals.
I decided to implement my own version of your algorithm/idea using DFS, next code is successfully accepted by LeetCode system (all tests pass):
class Solution:
def __init__(self):
self.visited = {}
def cloneGraph(self, node: 'Node') -> 'Node':
if node is None:
return None
if node.val in self.visited:
return self.visited[node.val]
nnode = Node(val = node.val)
self.visited[nnode.val] = nnode
nnode.neighbors = [self.cloneGraph(c) for c in node.neighbors]
return nnode

We use the hash map to create a map from original nodes to copied nodes. Then we use the hash map to check if we visit the node before or not.
def cloneGraph(self,node):
# I used closure instead of passing this object
visited={}
def dfs(node):
if node in visited:
# this returned value will be used inside for loop copy.neighbors.append(dfs(nei))
return visited[node]
#if node is not in visited we add it
# this is the first part of the copy. first copy the val
copy=Node(node.val)
visited[node]=copy
# we create the neighbors array of "copy" node
for nei in node.neighbors:
copy.neighbors.append(dfs(nei))
return copy
return dfs(node) if node else None

Related

Simple Artificial Intelligence Search Problem Infinite Loop Bug in Python

I am working on Project 0 for Harvard's CS50 Artificial Intelligence class and we had to write a function called shortest_path which is used in the program in finding the shortest path between two actors in a movie database based on shared movies they have worked on with other actors. I did write the function but am getting an infinite loop and can't figure out why I am getting it. I followed the directions from lecture where it says to only add a node to the frontier if the node does not exist in the frontier and also does not exist in previously explored nodes. Overlooking this is something that could lead to an infinite loop but I am still getting one. The test case I used is when the name of the source actor is Kevin and target actor is Kevin. I just pick two different Kevins from the list that's provided after entering Kevin as sources first and targets first name. I am posting my shortest_path function and a link where the rest of the project files can be downloaded with the relevant CSV files for the data being used to test the project.
Link to project files: Download the distribution code from https://cdn.cs50.net/ai/2020/x/projects/0/degrees.zip and unzip it.
The only function we had to modify was the shortest_path function in the distribution code provided so that's all I am posting, along with the classes for the node and Queuefrontier data structure being used. The rest of the code is in the distribution files in case you need to see how it all fits into place.
My code for shortest_path function:
def shortest_path(source, target):
"""
Returns the shortest list of (movie_id, person_id) pairs
that connect the source to the target.
If no possible path, returns None.
"""
path = []
# Keep track of number of states explored
num_explored = 0
# Initialize frontier to just the starting position
start = Node(state=source, parent=None, action=None)
frontier = QueueFrontier()
frontier.add(start)
# Initialize an empty explored set
explored = set()
# Keep looping until solution found
counter = 0
while True:
print(counter)
counter = counter + 1
# If nothing left in frontier, then no path
if frontier.empty():
return None
# Choose a node from the frontier
node = frontier.remove()
num_explored += 1
# If node is the goal, then we have a solution
if node.state == target:
actions = []
cells = []
path=[]
while node.parent is not None:
#actions.append(node.action)
#cells.append(node.state)
path.append(node.action,node.state)
node = node.parent
#actions.reverse()
#cells.reverse()
path.reverse()
#solution = (actions, cells)
return path
# Mark node as explored
explored.add(node.state)
# Add neighbors to frontier
for action, state in neighbors_for_person(node.state):
if not frontier.contains_state(state) and state not in explored:
child = Node(state=state, parent=node, action=action)
frontier.add(child)
Code for Class Node and QueueFrontier:
class Node():
def __init__(self, state, parent, action):
self.state = state
self.parent = parent
self.action = action
def toString(self):
print(f"State={self.state}, Parent={self.parent}")
class StackFrontier():
def __init__(self):
self.frontier = []
def add(self, node):
self.frontier.append(node)
def contains_state(self, state):
return any(node.state == state for node in self.frontier)
def empty(self):
return len(self.frontier) == 0
def remove(self):
if self.empty():
raise Exception("empty frontier")
else:
node = self.frontier[-1]
self.frontier = self.frontier[:-1]
return node
class QueueFrontier(StackFrontier):
def remove(self):
if self.empty():
raise Exception("empty frontier")
else:
node = self.frontier[0]
self.frontier = self.frontier[1:]
return node

How can I reference a list initialized in a function? (Python)

The error I'm receiving is local variable 'actions' referenced before assignment. I have a feeling that this is because I am creating the list within the function and then trying to reference it within. This is the specific code from which I receiving the error.
if node.state == target:
actions: []
cells: []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent
This is more context for the origin of the code I am writing.
def shortest_path(source, target):
"""
Returns the shortest list of (movie_id, person_id) pairs
that connect the source to the target.
If no possible path, returns None.
"""
# Create a frontier
frontier = QueueFrontier()
# Initialise explored set
explored = set()
# Create a start Node
node = Node(state=source, parent=None, action=None)
# Start with the source as a node (The initial state) and add it to the frontier
frontier.add(node)
# Start looping until we get a solution
while True:
# Check if Frontier is empty then no solution
if frontier.empty():
raise Exception("No path found")
# Remove node from the frontier
node = frontier.remove()
# Check if node contains the goal state
if node.state == target:
actions: []
cells: []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent
actions.reverse()
cells.reverse()
solution = (actions, cells)
return solution
I really appreciate any help on this.
use = to assign
if node.state == target:
actions = []
cells = []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent

Clone an undirected graph return deep copy

Given a reference of a node in a connected undirected graph, return a deep copy (clone) of the graph. Each node in the graph contains a val (int) and a list (List[Node]) of its neighbors. https://leetcode.com/problems/clone-graph/
Trying to solve this problem off of leetcode and not sure exactly where I'm stuck and how to get to the correct solution
Example:
class Solution(object):
def cloneGraph(self, node):
newNode = Node(node.val, [])
oldNode = node
dic = {}
dic[oldNode] = newNode
bfs = [oldNode]
while bfs:
oldNode = bfs.pop(0)
newNode = dic[oldNode]
neighbors = []
for item in oldNode.neighbors:
if item not in dic:
copy = Node(item.val, [])
dic[item] = copy
neighbors.append(copy)
bfs.append(item)
else:
copy = dic[item]
neighbors.append(copy)
newNode.neighbors = neighbors
return newNode
Change your last line to
return dic[node]
and it will work. You need to return the cloned node of the node sent to the function in the first place, and while newNode is that before going through your while bfs: loop, it gets changed during the loop.

Implementing DFS and BFS for binary tree

I'm trying to traverse a binary tree using depth first traversal and breadth first traversal, but I'm running into trouble. My node and tree implementation seems to be fine, I'm just not sure how to properly traverse the tree depth-wise and breadth-wise.
class Node:
def __init__(self, val):
self.l = None
self.r = None
self.v = val
class Tree:
def __init__(self):
self.root = None
def getRoot(self):
return self.root
def add(self, val):
if(self.root == None):
self.root = Node(val)
else:
self._add(val, self.root)
def _add(self, val, node):
if(val < node.v):
if(node.l != None):
self._add(val, node.l)
else:
node.l = Node(val)
else:
if(node.r != None):
self._add(val, node.r)
else:
node.r = Node(val)
def find(self, val):
if(self.root != None):
return self._find(val, self.root)
else:
return None
def _find(self, val, node):
if(val == node.v):
return node
elif(val < node.v and node.l != None):
self._find(val, node.l)
elif(val > node.v and node.r != None):
self._find(val, node.r)
def printTree(self):
if(self.root != None):
self._printTree(self.root)
def _printTree(self, node):
if(node != None):
self._printTree(node.l)
print(str(node.v) + ' ')
self._printTree(node.r)
# This doesn't work - graph is not subscriptable
def dfs(self, graph, start):
visited, stack = set(), [start]
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
stack.extend(graph[vertex] - visited)
return visited
# Haven't tried BFS. Would use a queue, but unsure of the details.
If it is a tree, visited can be a list since trees are non-circular, so there no need to check whether you have visited a node before and, more importantly, you want to maintain the order of your traversal.
def dfs(self, tree):
if tree.root is None:
return []
visited, stack = [], [tree.root]
while stack:
node = stack.pop()
visited.append(node)
stack.extend(filter(None, [node.r, node.l]))
# append right first, so left will be popped first
return visited
Your DFS implementation is slightly incorrect. As written, you've actually mimicked a queue, not a stack.
Your current code actually works fairly well for breadth-first search. It forces the siblings of a node to be evaluated before its children:
def bfs(self, graph, start):
visited, queue = set(), [start]
while queue:
vertex = queue.pop()
if vertex not in visited:
visited.add(vertex)
# new nodes are added to end of queue
queue.extend(graph[vertex] - visited)
return visited
The logic for DFS requires a stack to behave like this: when a new node comes, you need to add it to the left of the list, rather than the right. This way, you force the traversal of a node's descendants before the node's siblings.
def dfs(self, graph, start):
visited, stack = set(), [start]
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
# new nodes are added to the start of stack
stack = graph[vertex] - visited + stack
return visited
Other Issues
The specific issue you are facing beyond this is that you haven't specified what graph is.
If graph is an object that doesn't support lookup, then you could implement that using a __getitem__() method in the class definition.
Typically, people are content to use a dictionary to implement this. Something like {Node: [<list of node's children], ... } should more than suffice.

how to iterate through a binary search tree in python(no recursion)

So far I have
def tree_iterate():
parent, current = None, self.root
lst = []
while current is not None:
if current.left not None:
lst.append(current.item)
parent, current = current, current.left
if current.right not None:
lst.append(current.item)
parent, current = current, current.right
(sorry about spacing I'm quite new at this)
I'm not quite sure how to iterate on both sides of the tree when current has left and right, without using recursion. My main goal is to have a list of all the nodes in this BSTenter code here
To get a list of all nodes in the BST iteratively, use Breadth-First Search (BFS). Note that this won't give you the nodes in sorted order:
queue = [root]
result = []
while queue:
l = queue.pop(0)
result.append(l)
if l.left != None:
queue.append(l.left)
if l.right!= None:
queue.append(l.right)
If you want the nodes in sorted order, you will need to simulate inorder traversal using a stack:
result = []
stack = [root]
while stack:
stack[-1].visited = True
if stack[-1].left != None and not stack[-1].left.visited:
stack.append(stack[-1].left)
else:
node = stack.pop()
result.append(node)
if stack[-1].right != None:
stack.append(stack[-1].right)

Categories

Resources