Depth of a tree using DFS - python

I'm trying to write code that returns the depth of the deepest leaf in a tree with arbitrary number of children per nodes, in Python, using DFS rather than BFS. It seeems I'm close, but the following code still has some bug that I can't figure out (i.e. the returned depth is not correct). Any help?
A test tree would be simply: [[1,2,3],[4,5],[6],[7],[8],[],[],[],[]]
def max_depth_dfs(tree): # DOESN'T WORK
max_depth, curr_depth, Q = 0,0, [0]
visited = set()
while Q != []:
n = Q[0]
more = [v for v in tree[n] if v not in visited]
if not more:
visited.add(n)
curr_depth -= 1
Q = Q[1:]
else:
curr_depth += 1
max_depth = max(max_depth, curr_depth)
Q = more + Q
return max_depth

I found the bug!
if not more:
visited.add(n)
curr_depth -= 1
Q = Q[1:]
When you visit the node 4, curr_depth is equal to 2. Node 4 has no children, so you decrease the curr_depth and curr_depth is equal to 1 now. However, the next node you will visit is node 5 and the depth of node 5 is 2 instead of 1. Therefore, curr_depth doesn't record the correct depth of the node in the tree.
The following solution may be helpful.
def max_depth_dfs(tree):
max_depth, curr_depth, Q = 0, 0, [0]
visited = set()
while Q != []:
n = Q[0]
max_depth = max(max_depth, curr_depth)
if n in visited:
curr_depth -= 1
Q = Q[1:]
continue
#print n, curr_depth #show the node and its depth in the tree
visited.add(n)
more = [v for v in tree[n]]
if not more:
Q = Q[1:]
else:
curr_depth += 1
Q = more + Q
return max_depth

I used try .. catch to distinguish branches from leafs. update No more exceptions :)
from collections import Iterable
tree = [[1,2,3],[4,5, [1, 6]],[6],[7],[8],[],[],[],[]]
def max_depth(tree, level=0):
if isinstance(tree, Iterable):
return max([ max_depth(item, level+1) for item in tree])
else: # leaf
return level
print max_depth(tree)

Here is the non-recurison version:
from collections import Iterable
def max_depth_no_recur(tree):
max_depth, node = 0, iter(tree)
stack = [node]
while stack:
try:
n = node.next()
except StopIteration:
if len(stack) > max_depth:
max_depth = len(stack)
node = stack.pop()
continue
if isinstance(n, Iterable):
stack.append(node)
node = iter(n)
return max_depth

After taking into account all the good feedback I got from Alex and Adonis and refining the code, I currently have the current version:
def max_depth_dfs(tree): # correct
max_depth, curr_depth, Q = 0, 0, [0]
visited = set()
while Q != []:
n = Q[0]
if n in visited:
Q = Q[1:]
curr_depth -= 1
visited.remove(n) # won't go back, save memory
print 'backtrack from', n
continue
# proper place to print depth in sync with node id
print 'visiting', n, 'children=', tree[n], 'curr_depth=', curr_depth, 'Q=', Q,
print visited # only current path, instead of visited part of tree
if tree[n]:
visited.add(n) # if leaf, won't ever try to revisit
Q = tree[n] + Q
curr_depth += 1
max_depth = max(max_depth, curr_depth) # no need to check if depth decreases
else:
Q = Q[1:] # leaf: won't revisit, will go to peer, if any, so don't change depth
print 'no children for', n
return max_depth

Related

How do I return the length of the path found by Breadth First Search?

I'm trying to augment the typical BFS algorithm to also return the length of the path it found. Here's what I've written so far:
from collections import deque
length = 1
visited = set()
q = deque()
visited.add("Start")
while q:
v = q.popleft()
length += 1
if v == "End":
return length
else:
for neighbor in graph[v]:
if neighbor not in visited:
visited.add(neighbor)
q.append(neighbor)
return 0
Here, I'm assuming a graph of strings. An example graph could be
graph = { "Start" : ["A"],
"A" : ["B"],
"B" : ["End"],
"End" : []
}
I understand that this is wrong as it will count the entire size of graph and not just the length of the path to the goal. How can I modify it to return the length of the path to the goal it found?
Based on the advice of #Karl Knechtel, I was able to write it!
from collections import deque
visited = set()
q = deque([("Start", 0)])
visited.add("Start")
while q:
v, length = q.popleft()
if v == "End":
return length + 1
else:
for neighbor in graph[v]:
if neighbor not in visited:
visited.add(neighbor)
q.append((neighbor, length + 1))
return 0

Shortest path Graph BFS python

Trying to return the int for shortest path in a graph, using BFS. The idea is to use a q, append into the q as [node,distance] and then when we traverse increase distance and keep track of the count and when we hit our destination first time that means we found shortest path so we return that. But I got error " currNode,distance = q.popleft()
ValueError: not enough values to unpack (expected 2, got 1)"
def shortestPath(graph,nodeA,nodeB):
q = deque((nodeA,0))
visited = set(nodeA)
while q:
currNode,distance = q.popleft()
if currNode == nodeB:
return distance
for neighbor in graph[currNode]:
if neighbor not in visited:
visited.add(neighbor)
q.append([neighbor,distance+1])
return -1
graph_three = {
'w':['x','v'],
'x':['w','y'],
'y':['x','z'],
'z':['y','v'],
'v':['z','w']
}
print(shortestPath(graph_three,'w','z'))
Deque takes an iterable of elements as input, you gave it a tuple so your deque will contains two elements instead of the expected one tuple of two elements.
fix line 2 into:
q = deque([(nodeA,0)])
also here is a cleaner implementation of BFS:
def shortestPath(graph, root, target):
if root == target: return 0
q = collections.deque(root)
visited = set(root)
distance = 0
while q:
for _ in range(len(q)):
node = q.popleft()
for neighbor in graph[node]:
if neighbor == target:
return distance + 1
elif neighbor not in visited:
visited.add(neighbor)
q.append(neighbor)
distance += 1
return -1

Converting BFS to Djikstra to find the shortest path

How can I convert the following BFS algorithm to find the shortest path using Djikstra? I know that I need to update distances of neighbors, but I am confused on how exactly to extend the following BFS with it. The constraint is we can move only along L shaped paths between two nodes.
from collections import deque
N = 8
board_p = [[(-1,-1) for f in range(0,N)] for i in range(0,N)]
def Adjacents(u):
adj = []
for e in [(-2,-1),(-2,1),(2,1),(2,-1),(-1,-2),(1,-2),(-1,2),(1,2)]:
v = (u[0] + e[0], u[1] + e[1])
if v[0] >= 0 and v[0] < N and v[1] >= 0 and v[1] < N: adj.append(v)
return adj;
def Moves(s,t):
q = deque()
q.append(s)
board_p[s[0]][s[1]] = s # "root" of BFS-traversal points to it self (avoid loop over "back-edge" to s)
while q:
u = q.popleft()
if u == t: break
for v in Adjacents(u):
if board_p[v[0]][v[1]] == (-1,-1):
board_p[v[0]][v[1]] = u
q.append(v)
# walk the path back (using parent "pointers")
path = [(t)]
while t != s:
t = board_p[t[0]][t[1]]
path.append(t)
path.reverse()
return path
print(Moves((1,1),(5,5)))

Set list next to None

When I try to make the next attribute of a node, p, of a linked list point to None, I use p.next = None. But what if I want to make the node corresponding to p.next to None?
An example would be when try to rotate a linked list, which ends with a node's next equal to None, I want to make the new list's last element's next point to None but I think I keep deleting the element that it pointed to.
Here's my code for rotating the list by k positions. If you want to see the full description of the problem see here
def rotate(head, k):
'''
head is pointer to the head, k is the number of positions to rotate
'''
if not head or k == 0:
return head
p = head
d = head
counter = 1
while p.next != None:
counter += 1
p = p.next
out = ListNode(0)
if k % counter == 0:
return head
if counter < k:
counter = counter % k
for _ in range(counter):
p.next = d
d = d.next
p = p.next
out = p
d.next.next = None
return out
Sounds like you want to take the last k values and append them to the front.
p.next is the next node. In general when we want to change p's next we need to grab temp = p.next p.next = newNode and then we can continue.
In this case though, I'd find the length of the list, set tail.next = head, subtract k (accounting for wraparound), then walk forward till N-k, and set that node's p.next = None
Something like:
p, len, prev = head, 0, None
while p:
prev = p
p = p.next
len += 1
# prev is tail, set it's next to head
prev.next = head
# find the node to detach
p = head
for i in xrange(len):
p = p.next
p.next = None
You'll need to figure out corner cases

printing K lowest values in Binary search tree

I am trying to figure out how to print the lowest k values in a binary search tree. I an having trouble stopping the method
code:
def kthSmallestBST(node,k, count):
if node == None or count == k:
return
else:
kthSmallestBST(node.left, k, count)
count += 1
print node.data
kthSmallestBST(node.right, k, count)
count += 1
kthSmallestBST(BST, 3, 0)
Currently my output simply printed the whole tree inorder
It's a rather "functional programming" solution, but one way is to generate (lazily) the nodes in the tree in order, and then using itertools to just take the first k.
def inorder(tree):
if not tree: return
for node in inorder(tree.left): yield node
yield tree
for node in inorder(tree.right): yield node
def least(tree, k):
return itertools.islice(inorder(tree), k)
If you're using Python 3, you can use "yield from" to make this solution shorter.
Changes to the value of count don't propagate back up to the caller. You need to return the new count:
def kthSmallestBST(node,k, count):
if node is None or count >= k:
return 0
else:
count += kthSmallestBST(node.left, k, count)
if count < k:
print node.data
count += 1
count += kthSmallestBST(node.right, k, count)
return count
Also note you don't need both k and count. You can get rid of count, decrement k instead of incrementing count, and compare k against 0 (instead of against count). Here's what you get:
def kthSmallestBST(node, k):
if node is None or k <= 0:
return 0
k = kthSmallestBST(node.left, k)
if k > 0:
print node.data
k -= 1
k = kthSmallestBST(node.right, k)
return k
You need to change things around a bit so you know how many elements were found during the recursive call. Have the function return the number of elements it found an add them up. Also you need to check the count between the recursive calls and the current node's element.
Something like:
def kthSmallestBST(node, k, count):
if node == None or count == k:
return 0
else:
count += kthSmallestBST(node.left, k, count)
if(count == k)
return count
print node.data
count += 1
count += kthSmallestBST(node.right, k, count)
return count
import unittest
class BST(object):
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def get_kth_smallest_keys(node, k):
"""Return, as a list, `k` smallest keys in a binary search tree rooted at `node`.
"""
smallest = []
_get_kth_smallest_keys(node, k, smallest)
return smallest
def _get_kth_smallest_keys(node, k, smallest):
"""A helper function. Appends nodes to the given list, `smallest`, and stop
when k reaches 0.
Returns the number of nodes appended to said list.
"""
if node is None or k == 0:
return 0
# first, recurse left, and we get the number of nodes appended to the list by that call
nk = _get_kth_smallest_keys(node.left, k, smallest)
# if that number already reduces our counter to zero, we fail fast, returning that same number
if k - nk <= 0:
return nk
# otherwise, we can still append this node's key to the list
smallest.append(node.key)
# then we recurse right, with a counter that is less 1 (our append) and less nk (appended by the left recurse)
nnk = _get_kth_smallest_keys(node.right, k - 1 - nk, smallest)
# our return value is the sum of our append (1) and the appends from both recurse calls
return nk + 1 + nnk
class BSTTest(unittest.TestCase):
def test_smallest_keys(self):
root = BST(10)
root.left = BST(6)
root.right = BST(15)
root.left.right = BST(8)
root.right.right = BST(20)
self.assertEquals(get_kth_smallest_keys(root, 0), [])
self.assertEquals(get_kth_smallest_keys(root, 1), [6])
self.assertEquals(get_kth_smallest_keys(root, 3), [6, 8, 10])
self.assertEquals(get_kth_smallest_keys(root, 5), [6, 8, 10, 15, 20])
self.assertEquals(get_kth_smallest_keys(root, 6), [6, 8, 10, 15, 20])
if __name__ == '__main__':
unittest.main()

Categories