Leetcode 261 Graph Valid Tree: second test fails - python

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

Related

Graph recursive DFS when cycles exist?

I'm interested in handling DFS in undirected (or directed) graphs where cycles exist, such that the risk of entering an infinite-loop is non-trivial.
Note: This question is not about the cycle-detection problem(s) on LeetCode. Below is an iterative approach:
g = {'a':['b','c'],
'b':['a','f'],
'c':['a','f','d'],
'd':['c','e'],
'e':['d'],
'f':['c','b'],
'g':['h'],
'h':['g']
}
def dfs(graph, node, destination):
stack = [node]
visited = []
while stack:
current = stack.pop()
if current == destination:
return True
visited.append(current)
next_nodes = list(filter(lambda x: x not in visited + stack, graph[current]))
stack.extend(next_nodes)
return False
dfs(g,'h', 'g')
>>> True
dfs(g,'a', 'g')
>>> False
My question is, does such a recursive approach exist? And if so, how can it be defined in python?
If you're not interested in detecting if there are any loops or not and just interested in avoiding infinite loops (if any), then something like the following recursive implementation would work for you:
def dfs(graph, node, destination, visited=None):
if visited is None:
visited = set()
if node == destination:
return True
visited.add(node)
return any(
dfs(graph, neighbor, destination, visited=visited)
for neighbor in graph[node]
if neighbor not in visited
)
Note that a generator expression is used inside any, so it's evaluated in a lazy manner (one by one), and the whole any(...) expression returns True early as soon as a solution (i.e. a path to the destination) is found without checking the other neighbors and paths, so no extra recursive calls are made.

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.

Uniform Cost Search in 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
...

Retrieve Graph Lowest Height Node with Filter

Given a Tree T, sometimes binary or not, I need to retrieve the lowest Node that matches a criteria in each branch.
So, I need to retrieve a list (array) of those red marked nodes, where they label is equal to "NP" node.label() == 'NP'.
Actually I'm using NLTK Tree (nltk.tree.Tree) data structure, but you can post the pseudocode only, and I can implement it.
Here is the code that I've tried:
def traverseTree(tree):
if not isinstance(tree, nltk.Tree): return []
h = []
for subtree in tree:
if type(subtree) == nltk.tree.Tree:
t = traverseTree(subtree)
if subtree.label() == 'NP' and len(t) == 0: h.append(subtree)
return h
you have a conditional that if the there are no better candidates for your specification then append subtree, but what if len(t)>0? in that case you want to keep the nodes found in sub calls:
def traverseTree(tree):
if not isinstance(tree, nltk.Tree): return []
h = []
for subtree in tree:
if type(subtree) == nltk.tree.Tree:
t = traverseTree(subtree)
#RIGHT HERE!! need to extend by t or the other found nodes are thrown out
h.extend(t)
if subtree.label() == 'NP' and len(t) == 0:
h.append(subtree)
return h
Keep in mind that if t is always empty you would append all the valid nodes one level below, but any end-of-branch "NP" nodes will be found and returned in t so you want to pass them up a level in the recursion.
Edit: the only case where this would fail is if the top level node is "NP" and there are no sub-nodes of "NP" in which case tree should be added to h:
#after for loop has finished
if len(h) == 0 and tree.label() == "NP":
h.append(tree)
return h
edit2: if you add tree to h then the check for subtrees will never actually come true since they are checking the same node with the same conditionals just in differnt levels of recursion, so you can actually just write the function like this:
def traverseTree(tree):
if not isinstance(tree, nltk.Tree): return []
h = []
for subtree in tree:
#no need to check here as well as right inside the call
h.extend(traverseTree(subtree))
if tree.label() == 'NP' and len(h) == 0:
h.append(tree)
return h

Returning value when condition met in recursion

I am trying to find tours in a graph. I have written the following code, this seems to be printing tours correctly. I want it to stop once it have found the first tour and return the tour as a list. However, the recursion stack seems to finish to completion and I am not getting the desired result. How can I return a value and fully stop the recursion when I find the first tour i.e. my condition is met? Thanks.
def get_tour(start, graph, path):
if path==[]:
from_node=start
else:
from_node=path[-1][1]
if graph==[]:
if start in path[-1]:
print "Tour Found"
return path
else:
edges=[node for node in graph if from_node in node]
for edge in edges:
to_node=[i for i in edge if i<> from_node][0]
p=list(path)
p.append((from_node,to_node))
g=list(graph)
g.remove(edge)
get_tour(start, g,p)
g=[(1,2), (1,3), (2,3)]
get_tour(1, graph=g, path=[])
When using recursion you need to pass back the return value up to the whole call stack. Normally this isn't the best way to use recursion.
Without going in the details of your code, here is a quick suggestion:
def get_tour(start, graph, path):
ret_val = None
# Some code..
if graph==[]:
# Some code..
else:
edges=[node for node in graph if from_node in node]
for edge in edges:
# Some more code..
ret_val = get_tour(start, g,p)
if ret_val:
break
return ret_val
The reason the code continues to execute after finding the tour and returning the path is because it returns it to the call that was made within the iteration through the edges. If there is no break or return condition there then the iterations continue (and more recursive calls are followed).
Here is an amended version of your code that returns to the original call (as well as the recursive call) as soon as the conditions are satisfied, I have added some debug information to try to make the process clearer:
#!/usr/bin/python
# globals
verbose = True
def get_tour(start, graph, path):
if path==[]:
from_node=start
else:
from_node=path[-1][1]
if verbose:
print '\nfrom_node:\t', from_node
print 'start:\t', start
print 'graph:\t', graph
print 'path:\t', path
if graph==[]:
if start in path[-1]:
print "Tour Found"
return path
else:
edges=[node for node in graph if from_node in node]
for edge in edges:
to_node=[i for i in edge if i <> from_node][0]
p=list(path)
p.append((from_node,to_node))
g=list(graph)
g.remove(edge)
path = get_tour(start, g, p)
if path:
return path
g=[(1,2), (1,3), (2,3)]
get_tour(1, graph=g, path=[])

Categories

Resources