Implementing DFS and BFS for binary tree - python

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.

Related

Delete a branch in Binary tree( Linked List implementation)

I am trying to create a binary tree using a linked list. I want to delete a node in a binary tree, the way I implemented is to set the address(pointer) to the branch that I want to delete as None, but when I run the traversal methods, the branch still shows up.
here is my code
class tree:
def __init__(self,val):
self.val=val
self.left=None
self.right=None
def preorder(root):
if root is None:
return
print(root.val)
tree.preorder(root.left)
tree.preorder(root.right)
def inorder(root):
if root is None:
return
tree.inorder(root.left)
print(root.val)
tree.inorder(root.right)
def postorder(root):
if root is None:
return
tree.postorder(root.left)
tree.postorder(root.right)
print(root.val)
def levelorder(root):
if root is None:
return
Q=[]
Q.append(root)
while Q!=[]:
l=len(Q)
for i in range(l):
print(Q[i].val)
temp=[]
for i in Q:
if i.left is not None:
temp.append(i.left)
if i.right is not None:
temp.append(i.right)
Q.clear()
Q=temp.copy()
def search(root,value):
o=[]
o.append(tree.levelorder(root))
if value in o:
return "Found"
else:
return "Not Found"
def insert(root,value,where):
if root is None:
return
Q=[]
Q.append(root)
value=tree(value)
while Q!=[]:
for i in Q:
if i.val==where:
if i.left is not None:
i.right=value
else:
i.left=value
return
temp=[]
for i in Q:
if i.left is not None:
temp.append(i.left)
if i.right is not None:
temp.append(i.right)
Q.clear()
Q=temp.copy()
def delete(root,value):
if root is None:
return
Q=[]
Q.append(root)
value=tree(value)
while Q!=[]:
for i in Q:
if i.left is not None and i.left.val==value:
i.left.val=None
i.left=None
if i.right is not None and i.right.val==value:
i.right.val=None
i.right=None
return
temp=[]
for i in Q:
if i.left is not None:
temp.append(i.left)
if i.right is not None:
temp.append(i.right)
Q.clear()
Q=temp.copy()
and here is how I created the tree
base=tree("drinks")
L=tree("hot")
R=tree("cold")
LL=tree("coffe")
LR=tree("tea")
LRL=tree("w milk")
LRR=tree("wo milk")
base.left=L
base.right=R
L.left=LL
L.right=LR
LR.left=LRL
LR.right=LRR
Now when I run the delete method, the object that is supposed to be deleted still shows up.
tree.delete(base,"w milk")
tree.levelorder(base)
drinks
hot
cold
coffe
tea
ice cream
w milk
wo milk <=== the node i am trying to delete
The main issues:
delete method's while loop will exit immediately, because of the return statement
The value to compare with should not be turned into a node with value=tree(value)
Some remarks on your code:
You'll not be able to ever delete the root node of the tree, since you only check the values of child nodes. In order to allow the root to be deleted, and to represent an empty tree, you should really consider creating a second class. The current class could then be renamed to Node and the new class could become Tree.
It is a common habit to use a capital first letter for class names, while for variable names you would always start with a lower case letter. So not Q, but q.
It is not such good practice to print inside methods of such classes: these methods should just provide tools to iterate over nodes, but they should not print them. Use yield instead.
For instance, search should return the node when it is found and None otherwise. Don't print the result.
The insert method may actually delete node(s) when both the left and right child of the where node are already occupied. It would be better to reject such an insert.
In the OOP pattern, it is widely accepted to call the first argument of instance methods self. In OOP, you would call these methods with the dot notation: so instead of tree.postorder(root.left) you would do root.left.postorder().
You have quite some code repetition: mainly for traversals, and where you use the queue algorithm twice. Try to avoid that.
Here is how you could modify your code to align to these suggestions:
class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def traverse(self, kind=0, parent=None):
if kind == 0: # pre-order
yield (self, parent) # don't print in methods
if self.left:
yield from self.left.traverse(kind, self)
if kind == 1: # in-order
yield (self, parent)
if self.right:
yield from self.right.traverse(kind, self)
if kind == 2: # post-order
yield (self, parent)
class Tree:
def __init__(self):
self.root = None # Representing an empty tree
def traversevalues(self, kind=0):
if self.root:
for node, parent in self.root.traverse(kind):
yield node.val
def preorder(self):
yield from self.traversevalues(0)
def inorder(self):
yield from self.traversevalues(1)
def postorder(self):
yield from self.traversevalues(2)
def levelorder(self):
if self.root:
q = [self.root]
while q:
temp = []
for node in q:
yield node.val
if node.left:
temp.append(node.left)
if node.right:
temp.append(node.right)
q = temp
def search(self, value):
if self.root:
return next((edge for edge in self.root.traverse() if edge[0].val == value), None)
def insert(self, value, where=None):
if not where:
if self.root:
raise ValueError("Must call insert with second argument")
else:
self.root = Node(value)
else:
edge = self.search(where)
if edge:
parent = edge[0]
if not parent.left:
parent.left = Node(value)
elif not parent.right:
parent.right = Node(value)
else:
raise ValueError(f"Node {where} already has 2 children")
else:
raise ValueError(f"Node {where} not found")
def deletesubtree(self, value):
edge = self.search(value)
if edge:
node, parent = edge
if parent.left == node:
parent.left = None
else:
parent.right = None
else:
raise ValueError(f"Node {value} not found")
tree = Tree()
tree.insert("drinks")
tree.insert("hot", "drinks")
tree.insert("cold", "drinks")
tree.insert("coffee", "hot")
tree.insert("tea", "hot")
tree.insert("with milk", "tea")
tree.insert("without milk", "tea")
print(*tree.levelorder())
tree.deletesubtree("without milk")
print(*tree.levelorder())

Leetcode 133. Clone Graph with dfs understanding

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

python binary tree iterator - why returned node becomes None?

I'm scratching my head for an hour.
I implemented binary tree in-order traversal using the iterator & stack.
class Node:
def __init__(self, val, left, right):
self.val = val
self.left = left
self.right = right
class BTIterator:
def __init__(self, root):
self.stack = [] # Use a list as a stack by using pop() & append()
self.root = root
self.leftmost = None
def Run(self):
node = self.root
while (node.left != None):
self.stack.append(node)
node = node.left
print(self.stack)
self.leftmost = node
print(self.leftmost.val)
while (self.Next(node) != None):
node = self.Next(node)
print(node.val)
def Next(self, node):
if (node.left != None):
while (node.left != None):
self.stack.append(node.left)
node = node.left
return node
elif (node.right != None):
node = node.right
while (node.left != None):
self.stack.append(node)
node = node.left
return node
else:
if self.stack != []:
node = self.stack.pop()
node.left = None
return node
if __name__ == '__main__':
# Let's construct the tree.
n12 = Node(12, None, None)
n11 = Node(11, None, n12)
n9 = Node(9, None, None)
n10 = Node(10, n9, n11)
n15 = Node(15, None, None)
n13 = Node(13, n10, n15)
n5 = Node(5, None, None)
n3 = Node(3, None, n5)
n7 = Node(7, n3, n13)
bti = BTIterator(n7)
bti.Run()
I also ran above implementation on IDE. From debugging, I clearly see def Next(self, node) returns n7. However once node = self.Next(node) line gets the n7, it turns n7 into None. Why?!
I think this must be a python syntax that I do not know.
Any pointer will be appreciated.
in your implementation, there are three problems I want to point out:
the main problem is your implemtation in Next is incorrect. Because inorder traversal is left->root->right, if (node.left != None): part is correct, but here:
elif (node.right != None):
node = node.right
while (node.left != None):
self.stack.append(node)
node = node.left
return node
you should return node itself first before deal with right child.
else:
if self.stack != []:
node = self.stack.pop()
node.left = None
return node
and you should not only pop when node is leaf, you should push every node in stack, and pop every Next.
you can not put Next in both while condition and block, because it will call twice.
while (self.Next(node) != None):
node = self.Next(node)
!= None in if (node.left != None): is not needed in python, you can just use if node.left:
because Next has many problems, so I have to rewrited Next, here is the version I edited based on your code, and I have commented in details:
class BTIterator:
def __init__(self, root):
self.stack = [] # Use a list as a stack by using pop() & append()
self.root = root
def Run(self):
# find the left most node, which means then first node
node = self.root
while node.left:
self.stack.append(node)
node = node.left
# start from left most node
while node:
print(node.val)
node = self.Next(node)
def Next(self, node):
# find right node, if it is none, it's ok, we will deal with it afterwards
node = node.right
# reach the end
if not self.stack and not node:
return None
# push left child iteratively
while node:
self.stack.append(node)
node = node.left
# this is next node we want
return self.stack.pop()
Hope that helps you, and comment if you have further questions. : )

Inorder successor in a binary search tree

This is a common algorithm question. I'm trying to find the inorder successor in a binary search tree. Here's my code
def inorderSuccessor(self, root, p):
"""
:type root: TreeNode
:type p: TreeNode
:rtype: TreeNode
"""
# if Node has a right child, go all the way down
if p.right:
curr = p
while curr.left:
curr = curr.left
return curr
# first ancestor whose left child the node is
ans = None
curr = root
while curr is not p:
if curr.val < p.val:
curr = curr.right
else:
ans = curr
curr = curr.left
return ans
The problem is that this doesn't work when p is the highest node in the tree. I've been scratching my head over how to get this edge case going.
You can implement a method within your BST Class as below
def InOrderSucc(self,childNode):
if(self.search(childNode)):
if not (self.data == childNode):
return(self.right.InOrderSucc(childNode))
else:
if(self.right):
return(self.right.data)
else:
return None
else:
return False
where childNode is the Node you are requesting to find its InOrder Successor, Noting that the InOrder Successor will always be the right Child.

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