Python recursive path finder for binary tree - python

I'm trying to write a recursive function in python that given a binary tree and a node returns a string containing directions to the node. I've got close but my final return statement gives me the path plus the node (I don't need the node) i.e LRLR4.
here is my code so far:
class Tree:
def __init__(self):
self.root = None
self.left = None
self.right = None
def join(item: object, left: Tree, right: Tree):
tree = Tree()
tree.root = item
tree.left = left
tree.right = right
return tree
def path(tree: Tree, node: str, out: str=""):
if not tree:
return ""
if tree.root == node:
return tree.root
res = path(tree.left, node)
if res:
return "L" + res
res = path(tree.right, node)
if res:
return "R" + res
Is there a way I can implement this without the node on the end of the string output?
Edit: added all actual code and the tree in question contains single letter strings for each node.

To write path -
def path(tree, target):
if not tree:
return ""
elif target < tree.root:
return "L" + path(tree.left, target)
elif target > tree.root:
return "R" + path(tree.right, target)
else:
return "" # tree.root equal to target; don't return node
We can make some more improvements to your Tree class though. See these assignments in join?
def join(item: object, left: Tree, right: Tree):
tree = Tree()
tree.root = item
tree.left = left
tree.right = right
return tree
It would be better if the Tree constructor takes these values as arguments -
class Tree:
def __init__(self, root, left = None, right = None):
self.root = root
self.left = left
self.right = right
def join(item, left, right):
return Tree(item, left, right) # pass as arguments
Now the join function is redundant and can be removed -
class Tree:
def __init__(self, root, left = None, right = None):
self.root = root
self.left = left
self.right = right
# no more need for `join`
Given mytree -
# g
# / \
# / \
# d m
# / \ / \
# b f j q
# / \
# a k
mytree = \
Tree("g",
Tree("d", Tree("b", Tree("a")), Tree("f")),
Tree("m", Tree("j", None, Tree("k")), Tree("q"))
)
print(path(mytree, "f")) # LR
print(path(mytree, "k")) # RLR
print(path(mytree, "q")) # RRR

Related

Trouble balancing a binary search tree using AVL

I'm unable to identify where I'm going wrong with my AVL implementation for balancing an existing binary search tree. I'm not getting any errors but my binary search tree does not come out to be properly balanced. After insertion, my binary search tree looks like (it would be prudent here to mention that my display_keys method gives us a visualization that is rotated by 90 degrees):
∅
The-Dreamers
∅
Saint-Laurent
∅
Pierrot-le-Fou
∅
Contempt
Cold-War
Before-Sunrise
∅
Basic-Instinct
∅
This is correct as it seems be to be following the rules for a BST.
But after calling the BalanceTree() method on my binary search tree, I seem to get:
∅
The-Dreamers
Saint-Laurent
Pierrot-le-Fou
Contempt
Cold-War
Before-Sunrise
Basic-Instinct
∅
which as you can see, is not Balanced. But wait, and here's the catch, if I call BalanceTree() again, the tree comes out to be perfectly balanced and isBSTBalanced returns True also. After the second call to BalanceTree(), our tree looks like:
∅
The-Dreamers
Saint-Laurent
Pierrot-le-Fou
Contempt
Cold-War
Before-Sunrise
Basic-Instinct
∅
I am adding the complete source code for my BST class for clarity and if somebody wants to execute the code, but I have added a comment (#Addition of new methods for AVL starts here) in the BST class to indicate where the methods for AVL start. You need only concern yourself with them. I would like for you help me pinpoint what exactly is going wrong in my code.
class BST:
class TreeNode:
def __init__(self, key, value, left=None, right=None, parent=None):
self.key = key
self.value = value
self.left = left
self.right = right
self.parent = parent
self.height = 1
def __init__(self):
self.root = None
self.size = 0
def __len__(self):
return self.size
def insert(self, key, value):
if self.root == None:
self.root = self.TreeNode(key, value)
else:
self._insert(key, value, self.root)
self.size += 1
def _insert(self, key, value, curr_node):
if key < curr_node.key:
if curr_node.left is not None:
self._insert(key, value, curr_node.left)
else:
curr_node.left = self.TreeNode(key, value, parent=curr_node)
elif key > curr_node.key:
if curr_node.right is not None:
self._insert(key, value, curr_node.right)
else:
curr_node.right = self.TreeNode(key, value, parent=curr_node)
def search(self, key):
if self.root:
found = self._search(key, self.root)
if found:
return found.value
else:
return None
else:
return None
def _search(self, key, curr_node):
if not curr_node:
return None
elif curr_node.key == key:
return curr_node
elif key < curr_node.key:
return self._search(key, curr_node.left)
else:
return self._search(key, curr_node.right)
def find_min(self):
curr = self.root
while curr.left is not None:
curr = curr.left
return curr
def find(self, node):
curr = node
while curr.left is not None:
curr = curr.left
return curr
def delete(self, key):
node_to_remove = self._search(key, self.root)
if node_to_remove.left is None and node_to_remove.right is None:
#Then we identify this as a leaf node
if node_to_remove is node_to_remove.parent.left:
#Setting the parent's reference to this to None
node_to_remove.parent.left = None
elif node_to_remove is node_to_remove.parent.right:
node_to_remove.parent.right = None
#2nd Case --> Two child
elif node_to_remove.left and node_to_remove.right:
minimum = self.find(node_to_remove.right)
self.delete(minimum.key) #We will still have a ref to this node afterwards
node_to_remove.key, node_to_remove.value = minimum.key, minimum.value
#3rd Case -> One child
else:
if node_to_remove.left:
node_to_remove.left.parent = node_to_remove.parent
node_to_remove.parent.left = node_to_remove.left
elif node_to_remove.right:
node_to_remove.right.parent = node_to_remove.parent
node_to_remove.parent.right = node_to_remove.right
def traversal(self, root):
res = []
if root:
res = self.traversal(root.left)
res.append(root)
res = res + self.traversal(root.right)
return res
def inorder_traversal(self, root):
if root:
self.inorder_traversal(root.left)
print(root.key)
self.inorder_traversal(root.right)
#Addition of new methods for AVL starts here
def display_keys(self, node, space='\t', level=0):
"""
Allows us to visualize the tree (albiet rotated by 90 degrees)
"""
# print(node.key if node else None, level)
# If the node is empty
if node is None:
print(space*level + '∅')
return
# If the node is a leaf
if node.left is None and node.right is None:
print(space*level + str(node.key))
return
# If the node has children
self.display_keys(node.right, space, level+1)
print(space*level + str(node.key))
self.display_keys(node.left,space, level+1)
def height(self):
return self._height(self.root)
def _height(self, curr_node):
if curr_node is None:
return -1 #since we are counting number of edges, we will return -1
else:
return 1 + max(self._height(curr_node.left), self._height(curr_node.right))
def isBSTBalanced(self):
return self._isBSTBalanced(self.root)
def _isBSTBalanced(self, curr_node):
if curr_node is None:
return True
hleft_subtree = self._height(curr_node.left)
hright_subtree = self._height(curr_node.right)
if hleft_subtree - hright_subtree in [-1,0,1]:
return self._isBSTBalanced(curr_node.left) and self._isBSTBalanced(curr_node.right)
else:
return False
def balance_factor(self):
if self.root is not None:
return self._balance_factor(self.root)
else:
return 0
def _balance_factor(self, curr_node):
if curr_node is None:
return
hleft_subtree = self._height(curr_node.left)
hright_subtree = self._height(curr_node.right)
b_factor = hleft_subtree - hright_subtree
return b_factor
def BalanceTree(self):
if self.isBSTBalanced() == False:
return self._rebalance(self.root)
def _rebalance(self, curr_node):
if curr_node is None:
return None
curr_node.left = self._rebalance(curr_node.left)
curr_node.right = self._rebalance(curr_node.right)
curr_node.height = 1 + max(self._height(curr_node.left), self._height(curr_node.right))
#print(curr_node.height)
if self._balance_factor(curr_node) > 1 and self._balance_factor(curr_node.left) >= 0:
#left heavy subtree
return self._rotate_right(curr_node)
if self._balance_factor(curr_node) < -1 and self._balance_factor(curr_node.right) <= 0:
#right heavy subtree
return self._rotate_left(curr_node)
if self._balance_factor(curr_node) < 0 and self._balance_factor(curr_node.right) > 0:
self._rotate_right(curr_node.right)
return self._rotate_left(curr_node)
if self._balance_factor(curr_node) > 0 and self._balance_factor(curr_node.left) < 0:
self._rotate_left(curr_node.left)
return self._rotate_right(curr_node)
return curr_node
def _rotate_left(self, oldRoot):
newRoot = oldRoot.right #the newRoot is the right child of the previous root
oldRoot.right = newRoot.left #replacing right child of the old root with the left child of the new
if newRoot.left is not None:
newRoot.left.parent = oldRoot
newRoot.parent = oldRoot.parent
if oldRoot == self.root:
self.root = newRoot
else:
if oldRoot.parent.left is oldRoot: #Checking isLeftChild
oldRoot.parent.left = newRoot
else:
oldRoot.parent.right = newRoot
newRoot.left = oldRoot
oldRoot.parent = newRoot
oldRoot.height = 1 + max(self._height(oldRoot.left), self._height(oldRoot.right))
newRoot.height = 1 + max(self._height(newRoot.left), self._height(newRoot.right))
return newRoot
def _rotate_right(self, oldRoot):
newRoot = oldRoot.left #the newRoot is the left child of the previous root
oldRoot.left = newRoot.right #replacing left child of the old root with the right child of the new
if newRoot.right is not None:
newRoot.right.parent = oldRoot
newRoot.parent = oldRoot.parent
if oldRoot == self.root:
self.root = newRoot
else:
if oldRoot.parent.right is oldRoot: #Checking isRightChild
oldRoot.parent.right = newRoot
else:
oldRoot.parent.left = newRoot
newRoot.right = oldRoot
oldRoot.parent = newRoot
oldRoot.height = 1 + max(self._height(oldRoot.left), self._height(oldRoot.right))
newRoot.height = 1 + max(self._height(newRoot.left), self._height(newRoot.right))
return newRoot
if __name__ == '__main__':
obj = BST()
obj.insert('Basic-Instinct', 0)
obj.insert('The-Dreamers', 1)
obj.insert('Saint-Laurent', 2)
obj.insert('Pierrot-le-Fou', 3)
obj.insert('Contempt', 4)
obj.insert('Before-Sunrise', 5)
obj.insert('Cold-War', 8)
obj.display_keys(obj.root) #displays a visual representation of our tree, albeit rotated by 90 degrees
print()
print("isBSTBalanced:", obj.isBSTBalanced())
obj.BalanceTree()
print("isBSTBalanced:", obj.isBSTBalanced()) #After executing BalanceTree(), isBSTBalanced still returns False
print()
obj.display_keys(obj.root)
Progress: Revamped _isBSTBalanced method so that it visits every node recursively and not just the root node. The final outcome, however, remains the same.
Progress: I was able to identify one of the major issues being that while I was calling _rotate_left and _rotate_right methods in the _rebalance method, I was not returning them. In addition to this, I was not recursively visiting the left and right subtrees of curr_node, which was initially set to the root of the tree, to be able to traverse the tree in a bottom up manner. I have resolved this too. I have added a display_keys method which allows us to visualize the tree, albeit rotated by 90 degrees. I'm updating the code and prompt in this post accordingly. The problem that still remains is that I have to call the BalanceTree() method more than once in some cases for isBSTBalanced to return True.

Return a node's parent that is exactly below the root

I have a general tree that might look like this:
I want to write a function MyFunc(tree,tree_element)
that will return the element's parent. But not the immediate parent, but a parent that is exactly one level below the root.
In case of the attached tree:
MyFunc(tree,'Dasani')
will return 'Coke', while:
MyFunc(tree,'Zero Sugar')
will return 'Pepsi'
I was able to write two functions.
First returns the immediate parent:
class tree:
def __init__(self, key):
self.data = key
self.left = None
self.right = None
def parent_search(self, root, child_node):
if root :
if root.left and root.left.data== child_node:
return root.data
if root.right and root.right.data== child_node:
return root.data
elif root:
return self.parent_search(root.left, child_node) or self.parent_search(root.right, child_node)
root = tree('Beverage')
root.left = tree('Pepsi')
root.right = tree('Coke')
root.left.left = tree('Zero Sugar')
root.left.right = tree('Cherry Pepsi')
root.right.left = tree('Sport Drinks')
root.right.left.left = tree('Powerade')
root.right.left.right = tree('Dasani')
root.right.left.left.left = tree('Powerade w/Sugar')
print(root.parent_search(root,'Dasani'))
The second returns the whole path all the way up to the root:
def printAncestors(root, target):
if root == None:
return False
if root.data == target:
return True
if (printAncestors(root.left, target) or
printAncestors(root.right, target)):
print root.data,
return True
return False
printAncestors(root, 'Dasani')
However, I need something in between that will return only one element that is exactly one level below the root.
I can calculate the height of the tree from a leaf in question to the root. Then I was thinking to return an element that is height-1 above the leaf but am not sure that it's the right approach.
One way to do that is to pass the path to root to each level of recursion, and then return the entire path, then you can extract whatever part of the path you need, like:
Code:
def parent_search(self, child_node, path_so_far=()):
path_so_far += self.data,
if (self.left and self.left.data == child_node or
self.right and self.right.data == child_node):
return path_so_far
return (
self.left and self.left.parent_search(child_node, path_so_far) or
self.right and self.right.parent_search(child_node, path_so_far)
)
Test Code:
class Tree:
def __init__(self, key):
self.data = key
self.left = None
self.right = None
def parent_search(self, child_node, path_so_far=()):
path_so_far += self.data,
if (self.left and self.left.data == child_node or
self.right and self.right.data == child_node):
return path_so_far
return (
self.left and self.left.parent_search(child_node, path_so_far) or
self.right and self.right.parent_search(child_node, path_so_far)
)
root = Tree('Beverage')
root.left = Tree('Pepsi')
root.right = Tree('Coke')
root.left.left = Tree('Zero Sugar')
root.left.right = Tree('Cherry Pepsi')
root.right.left = Tree('Sport Drinks')
root.right.left.left = Tree('Powerade')
root.right.left.right = Tree('Dasani')
root.right.left.left.left = Tree('Powerade w/Sugar')
print(root.parent_search('Dasani'))
print(root.parent_search('Dasani')[1])
Results:
('Beverage', 'Coke', 'Sport Drinks')
Coke

Python Trees: Modifying a tree

This is my python code to make an Ordered Binary Decision Diagram (not very relevant for the context). So I just have a tree of a particular height, and I need to set some of the leaf nodes to one. So I have a variable path which involves an array of "decisions", to go left or right from that particular node. But my code is by mistake modifying multiple roots. I am fairly new to Python and I used to rely on pointers when I used C.
def makeCubes(arr):
ans = []
for ar in arr:
ar2 = [ar[i:i + 2] for i in range(0, len(ar), 2)]
#splitting into segments of 2 each
if not '00' in ar2:
ans += [ar2]
return ans
class Node:
def __init__(self,key):
self.key = key
self.left = None
self.right = None
def addLeft(self,node):
self.left = node
def addRight(self,node):
self.right = node
def makeTree(size):
if(size == 1):
leaf = Node('x0')
leaf.addLeft(Node('zero'))
leaf.addRight(Node('zero'))
return leaf
else:
node = Node('x'+str(size-1))
childNode = makeTree(size-1)
node.addLeft(childNode)
node.addRight(childNode)
return node
def inOrder(root):
if(root != None):
return inOrder(root.left) + [root.key] + inOrder(root.right)
return []
def makeOBDD(array):
maxLen = max([len(word) for word in array])
tree = makeTree(maxLen)
for cube in array:
tree = makeOne(tree,cube)
return tree
def makeOne(root,cube):
print("cube",cube)
if(cube == []):
print("updated")
root.key = 'one'
else:
element = cube[0]
if(element == '01'):
root.addLeft(makeOne(root.left,cube[1:]))
elif(element == '10'):
root.addRight(makeOne(root.right,cube[1:]))
return root
# ab + a'b'
'''
Expected output
x1
/ \
x0 x0
/ \ / \
1 0 0 1
'''
cubeSet = ['1010','0101']
cubes = makeCubes(cubeSet)
print(cubes)
obdd = makeOBDD(cubes)
print(inOrder(obdd))

Can this code be improved to implement Binary Expression Tree?

For more basic information (for starters)... you may want to read this question:
How to Implement a Binary Tree?
For more information on how to implement a binary tree... you may want to open this site:
http://www.openbookproject.net/thinkcs/python/english2e/ch21.html
The binary operation tree is way different from its father... the Binary Tree. This is the expression tree for the string '(7+3)(5-2):Expression Tree for (7+3)(5-2).
The snippet way way below should do the following...
Read a string
When it encounters '(', then it creates a new node and goes to the left child of the current node
When it encounters any digit, it places the data then goes up
When it encounters an operator '+','-','*','/', then it places the data then goes to the right child of the current node
When it encounters ')', then it backtracks and goes up.
There are few problems with this code that need to be solved...
the int(strng) does not function well in this code: BinaryOperationTree instance has no attribute 'len'
Or maybe you may add your own code to answer this question... If you do this, then you may disregard the code below. Take note that it should print the nodes infix, prefix, and postfix...
class Node:
def __init__ (self, data, currNode):
self.data = data
self.left = None
self.right = None
self.parent = currNode
currNode = self.right
return currNode
class BinaryOperationTree():
def __init__(self):
strng = raw_input('Please enter the operation to turn it into a binary tree.\n')
print strng
def readCharacter(self):
for i in range(-1, str_len, -1):
k = k + 1
if (k >= 2):
j = j * 16
l = 0 #Counter
currNode = Node
while (l <= k - 1):
if isDigit(hexa[l]):
encntrDigit(hexa[l], currNode)
elif isRPar(hexa[l]):
enctrRpar(currNode)
elif isLPar(hexa[l]):
enctrLpar(currNode)
elif isOperator(hexa[l]):
enctrOperator(hexa[1], currNode)
def isDigit(x):
chars = ['0','1','2','3','4','5','6','7','8','9']
if chars in x:
return True
else:
return False
def isRPar(x):
if ')' in x:
return True
else:
return False
def isLPar(x):
if '(' in x:
return True
else:
return False
def isOperator(x):
chars = ['+','-','*','/']
if chars in x:
return True
else:
return False
def encntrDigit(x, currNode):
currNode.data = x
currNode = currNode.parent
return currNode
def enctrRpar(self, currNode):
currNode = currNode.parent
def enctrLPar(self, currNode):
currNode = Node()
def enctrOperator(self, x, currNode):
currNode.data = x
currNode = currNode.parent
#Prints the tree in Pre-Order Format
def preOrder (node):
if (node is not None):
print node.data, "",
preOrder(node.left)
preOrder(node.right)
#Prints the tree in Order Format
def inOrder (node):
if (node is not None):
inOrder (node.left)
print node.data, "",
inOrder (node.right)
#Prints the tree in Post-Order Format
def postOrder (node):
if (node is not None):
postOrder (node.left)
postOrder (node.right)
print node.data, "",
def main():
string = BinaryOperationTree()
string.readCharacter()
main()

Python Serializing/Deserializing a binary tree

I'm trying to implement a serializing/deserializing algorithm in python for binary trees.
Here's my code:
class Node:
count = 1
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
if self.value > value:
if self.left is None:
self.left = Node(value)
Node.count += 1
else:
self.left.insert(value)
else:
if self.right is None:
self.right = Node(value)
Node.count += 1
else:
self.right.insert(value)
# Using preorder
def serialize(root, serial):
if root != None:
serial.append(root.value)
serialize(root.left, serial)
serialize(root.right, serial)
else:
serial.append('x')
def deserialize(newRoot, serial):
if serial[0] == 'x':
serial.pop(0)
else:
if len(serial) > 0:
newRoot = Node(serial.pop(0))
print(newRoot.value)
deserialize(newRoot.left, serial)
deserialize(newRoot.right, serial)
print("This program serializes a tree\n")
root = Node(3)
root.insert(1)
root.insert(2)
root.insert(4)
root.insert(5)
root.insert(0)
# Serialize
serial = []
serialize(root, serial)
print(serial)
# Deserialize
newRoot = Node(None)
deserialize(newRoot, serial)
print(newRoot.value)
The problem is, newRoot doesn't get updated by deserialize because python passes it by value. How do I get around this, preferably in the most elegant way? In C/C++, I would just pass a pointer to newRoot and it should get updated accordingly. Thanks!
You can return the newly created nodes and assign them as left and right nodes. Also poping the first element of a list is more costly than poping the last element, so reverseing the list at the beginning and then using it in the recursion will be more performant in your case. So the code will become something like:
def deserialize(serial):
serial.reverse()
return _deserialize(serial)
def _deserialize(serial):
if not serial:
return None
node = None
value = serial.pop()
if value != 'x':
node = Node(value)
node.left = _deserialize(serial)
node.right = _deserialize(serial)
return node
root = deserialize(serial)
print(root.value)
You can create left and right subtree within deserialize function and return the root.
Here is my code:
node_list = []
MARKER = -1
class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def serialize(root):
if root is None:
node_list.append(MARKER)
return
node_list.append(root.val)
serialize(root.left)
serialize(root.right)
def deserialize(root, node_list):
if node_list:
val = node_list.pop(0)
else:
return
if val == MARKER:
return
# Create root, left and right recursively
root = Node(val)
root.left = deserialize(root.left, node_list)
root.right = deserialize(root.right, node_list)
return root
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.val, end=' ')
inorder_traversal(root.right)
if __name__=="__main__":
# Create tree
root = Node(20)
root.left = Node(8)
root.right = Node(22)
root.left.left = Node(4)
root.left.right = Node(12)
root.left.right.left = Node(10)
root.left.right.right = Node(14)
print("Inorder traversal before serialization..")
inorder_traversal(root)
print('')
# serialize the tree and insert elements into a list
serialize(root)
print(node_list)
root1 = None
root1 = deserialize(root1, node_list)
print("Inorder traversal after deserialization..")
inorder_traversal(root1)
print('')

Categories