Sorry if this is a common question but I haven't found an appropriate answer for my particular problem. I'm trying to implement a walk method that walks a binary tree from its root node to each of its leaf nodes, yielding the root-to-leaf path whenever I get to a leaf node. For example, walking the binary tree represented by:
__a__
/ \
b d
/ \ / \
- c - -
Would yield:
['a', 'b', 'c']
['a', 'd']
My idea is that BinaryTree.walk calls Node.traverse on the root node, which in turn calls the traversemethod of each child node recursively. BinaryTree.walk also creates an empty list which is passed around with each traverse call, appending the data of each node, yielding the list once a leaf node is reached and popping each element out of the list after visiting each node.
At some point, something is going wrong though. This is my code:
class Node:
def __init__(self, data=None, left=None, right=None):
self.data = data
self.left = left
self.right = right
def __repr__(self):
return f"{self.__class__.__name__}({self.data})"
#property
def children(self):
return self.left, self.right
def traverse(self, branch):
print('ON NODE:', self)
branch.append(self.data)
if self.left is None and self.right is None:
yield branch
else:
for child in self.children:
if child is not None:
print('ENTERING CHILD:', child)
child.traverse(branch=branch)
print('EXITING CHILD:', child)
branch.pop()
class BinaryTree:
def __init__(self, root=Node()):
if not isinstance(root, Node):
raise ValueError(f"Tree root must be Node, not {type(root)}")
self.root = root
def __repr__(self):
return f"{self.__class__.__name__}({self.root})"
def walk(self):
node = self.root
branch = []
yield from node.traverse(branch=branch)
if __name__ == '__main__':
# create root node
n0 = Node('A')
# create binary tree with root node
tree = BinaryTree(root=n0)
# create others nodes
n1 = Node(data='B')
n2 = Node(data='C')
n3 = Node(data='D')
# connect nodes
n0.left = n1
n0.right = n3
n1.right = n2
# walk tree and yield branches
for branch in tree.walk():
print(branch)
Expected output:
ON NODE: Node(A)
ENTERING CHILD: Node(B)
ON NODE: Node(B)
ENTERING CHILD: Node(C)
ON NODE: Node(C)
['A', 'B', 'C'] # yielded branch
EXITING CHILD: Node(C)
EXITING CHILD: Node(B)
ENTERING CHILD: Node(D)
ON NODE: Node(D)
['A', 'D'] # yielded branch
EXITING CHILD: Node(D)
Actual output:
ON NODE: Node(A)
ENTERING CHILD: Node(B)
EXITING CHILD: Node(B)
ENTERING CHILD: Node(D)
EXITING CHILD: Node(D)
IndexError: pop from empty list
I understand I'm doing something wrong with the list since it's trying to pop when it's empty, but I can't understand how it got to that. It should call pop once for each append call.
Also I can't figure out why the nodes are being entered and exited, but the ON NODE: message is not being printed... It's like my code just skips the child.traverse(branch=branch) line somehow?
Can anyone help me understand where I'm messing this up?
Thanks in advance for your help!
Here's a modified variant of your code.
code.py:
#!/usr/bin/env python3
import sys
class Node:
def __init__(self, data=None, left=None, right=None):
self.data = data
self.left = left
self.right = right
def __repr__(self):
return f"{self.__class__.__name__}({self.data})"
#property
def children(self):
if self.left:
yield self.left
if self.right:
yield self.right
#property
def is_leaf(self):
return self.left is None and self.right is None
def traverse_preord(self, accumulator=list()):
print(" On node:", self)
accumulator.append(self.data)
if self.is_leaf:
yield accumulator
else:
for child in self.children:
print(" Entering child:", child)
yield from child.traverse_preord(accumulator=accumulator)
accumulator.pop()
print(" Exiting child:", child)
def main():
root = Node(data="A",
left=Node(data="B",
right=Node(data="C")
),
right=Node(data="D",
#left=Node(data="E"),
#right=Node(data="F"),
)
)
for path in root.traverse_preord():
print("Found path:", path)
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Notes:
I refactored the code a bit (simplified, changed some identifier names, texts and other insignificant changes)
children property:
None for a node's left or right attribute, means that the node has no child, so no point including it in the returned result
Since the question is involving yield, I turned it into a generator (instead of returning a tuple or list, ...). As a consequence, I had to add is_leaf, since a generator doesn't evaluate to False (even if empty)
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q055424449]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32
On node: Node(A)
Entering child: Node(B)
On node: Node(B)
Entering child: Node(C)
On node: Node(C)
Found path: ['A', 'B', 'C']
Exiting child: Node(C)
Exiting child: Node(B)
Entering child: Node(D)
On node: Node(D)
Found path: ['A', 'D']
Exiting child: Node(D)
What is wrong with your code?
It's the traverse recurring call (child.traverse(branch=branch)). It created a generator, but since that wasn't used (iterated on) anywhere, the function didn't actually called itself, resulting in the attempt of removing more elements than added (only 1: which is the root node). So, it turns out that you were almost there. All you have to do, is adding a yield from in front of it :). More details on [Python]: PEP 380 -- Syntax for Delegating to a Subgenerator.
There is a great answer here
Copying their Python example:
"""
Python program to print all path from root to
leaf in a binary tree
"""
# binary tree node contains data field ,
# left and right pointer
class Node:
# constructor to create tree node
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# function to print all path from root
# to leaf in binary tree
def printPaths(root):
# list to store path
path = []
printPathsRec(root, path, 0)
# Helper function to print path from root
# to leaf in binary tree
def printPathsRec(root, path, pathLen):
# Base condition - if binary tree is
# empty return
if root is None:
return
# add current root's data into
# path_ar list
# if length of list is gre
if(len(path) > pathLen):
path[pathLen] = root.data
else:
path.append(root.data)
# increment pathLen by 1
pathLen = pathLen + 1
if root.left is None and root.right is None:
# leaf node then print the list
printArray(path, pathLen)
else:
# try for left and right subtree
printPathsRec(root.left, path, pathLen)
printPathsRec(root.right, path, pathLen)
# Helper function to print list in which
# root-to-leaf path is stored
def printArray(ints, len):
for i in ints[0 : len]:
print(i," ",end="")
print()
# Driver program to test above function
"""
Constructed binary tree is
10
/ \
8 2
/ \ /
3 5 2
"""
root = Node(10)
root.left = Node(8)
root.right = Node(2)
root.left.left = Node(3)
root.left.right = Node(5)
root.right.left = Node(2)
printPaths(root)
# This code has been contributed by Shweta Singh.
Gives:
10 8 3
10 8 5
10 2 2
You can give it letters like you have too:
root = Node("A")
root.left = Node("B")
root.right = Node("D")
root.left.right = Node("C")
printPaths(root)
Gives:
A B C
A D
Related
How to insert a node in a complete binary tree without using queue DS? I tried the following code:
class TreeNode:
def __init__(self, value=None) -> None:
self.left = None
self.value = value
self.right = None
class Tree:
def __init__(self, root=None) -> None:
self.__root = root if not root else TreeNode(root)
self.__len = 1 if root else 0
def append(self, data, root="_"):
if self.__root.value is None:
self.__root = TreeNode(data)
self.__len += 1
return
root = self.__root if root == "_" else root
if not root:
return False
if root.left is None and root.right is None:
root.left = TreeNode(data)
self.__len += 1
return True
elif root.left is not None and root.right is None:
root.right = TreeNode(data)
self.__len += 1
return True
elif root.left is not None and root.right is not None:
if self.append(data, root.left):
return
else:
self.append(data, root.right)
return
the recursive call of that function always add the new node on the left side of the tree, so what should I do to make it recursively checks the right side too?
First of all, the first line in your append code seems to give a special meaning to the value in the root node. When it is None it is not considered a real node, but to represent an empty tree. This is not a good approach. An empty tree is represented by a __root that is None -- nothing else. I would also suggest to remove the optional data argument from the constructor. Either the constructor should allow an arbitrary number of values or none at all. To allow one is odd and strengthens the idea that a tree could have a special None in its root node.
To the core of your question. There is nice attribute to complete binary trees. The paths from the root to a leaf can be represented by a bit pattern, where a 0 means "go left" and a 1 means "go right". And the path to the "slot" where a new node should be injected has a relationship with the size of the tree once that node has been added:
new size
binary representation
path to new node
1
1
[]
2
10
[left]
3
11
[right]
4
100
[left,left]
5
101
[left,right]
In general the path to the new node is defined by the bits in the binary representation of the new tree size, ignoring the leftmost 1.
This leads to the following code:
class Tree:
def __init__(self) -> None:
self.__root = None
self.__len = 0
def append(self, data):
self.__len += 1
if self.__len == 1: # First node
self.__root = TreeNode(data)
return
node = self.__root
# Iterate all the bits except the leftmost 1 and the final bit
for bit in bin(self.__len)[3:-1]:
node = [node.left, node.right][int(bit)] # Choose side
if self.__len & 1: # Use final bit to determine where child goes:
node.right = TreeNode(data)
else:
node.left = TreeNode(data)
I'm just getting started with binary trees and I have this task where I have to do a preorder iterative traversal search for a given binary tree '[1,null,2,3]'.
I tried to use a new binarytree module that I found, but it didn't worked and I saw a youtube video where some guy did it recursively but I just can't figure out.
#Input = [1,null, 2,3]
# 1
# \
# 2
# /
# 3
#Expected output = [1,2,3]
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
I'm just clueless, I wrote an algorithm but I can't turn it into actual functional code. Also I don't understand how the root: TreeNode works. Does it turn every element of the list into a TreeNode object? So far my best try had been this and it's obviously wrong in many ways.
def preorderTraversal(self, root: TreeNode) -> List[int]:
result = []
for i in root:
if i =! root[0] and root.left =! None:
root.left = i
if root.left =! null:
root.left.left = i
elif root.left == null:
root.right.left = i
elif root.left
result.append(i)
elif root.right == None:
root.right = i
else:
continue
A few points:
Function preorderTraversal should have no self parameter; it is not a method of a class.
I have modified the TreeNode class to make it more convenient to specify its children TreeNode objects.
Function preorderTraversal takes as an argument a TreeNode object. As I mentioned in a comment, your statement, #Input = [1,null, 2,3], is difficult to make sense of.
You need to keep a last in/first out (LIFO) stack of unvisited TreeNode objects to implement an iterative (rather than recursive solution). In the code below, variable nodes serves that purpose.
The code:
from typing import List
class TreeNode:
def __init__(self, val, left=None, right=None):
self.x = val
self.left = left
self.right = right
n3 = TreeNode(3)
n2 = TreeNode(2, left=n3)
n1 = TreeNode(1, right=n2)
def preorderTraversal(root: TreeNode) -> List[int]:
result = []
nodes = []
nodes.append(root) # initial node to visit
while len(nodes): # any nodes left top visit?
node = nodes.pop() # get topmost element, which is the next node to visit
result.append(node.x) # "output" its value before children are visited
if node.right is not None:
# show this node must be visited
nodes.append(node.right) # push first so it is popped after node.left
if node.left is not None:
# show this node must be visited
nodes.append(node.left)
return result
print(preorderTraversal(n1))
Prints:
[1, 2, 3]
Or a more complicated tree:
10
/ \
8 2
/ \ /
3 5 2
n3 = TreeNode(3)
n5 = TreeNode(5)
n8 = TreeNode(8, left=n3, right=n5)
n2a = TreeNode(2)
n2b = TreeNode(2, left=n2a)
n10 = TreeNode(10, left=n8, right=n2b)
print(preorderTraversal(n10))
Prints:
[10, 8, 3, 5, 2, 2]
You can use the queue data structure for it.
queue = []
result = []
queue.append(root)
while queue:
node = queue.pop()
result.append(node.val)
if node.left is not None:
queue.insert(0, node.left)
if node.right is not None:
queue.insert(0, node.right)
return result
This is a problem from leetcode platform, here is my solution. Runtime: 28 ms, faster than 81.08% of Python3 online submissions for Binary Tree Preorder Traversal.
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
I'm writing code to implement a Binary Search Tree in Python. I ran into problems in writing the insert function, which should add a new node at the correct in order location in the tree. I wrote it in two different ways: insertNode1 works fine (correctly performs an insert on the tree), but insertNode2 does not perform the insertion properly. When I Do InOrderTraversal for the tree using insertNode1 and insertNode2, it turned out that the 'insertNode1' yields the full tree while the 'insertNode2' only yields the root.
Why is it that insertNode1 succeeds while insertNode2 fails, and what are the meaningful differences between the two functions that cause this to be the case?
Here's my code:
def insert(self,val):
if not self.root:
self.root = TreeNode(val)
else:
self.insertNode2(self.root, val)
def insertNode1(self,node, val):
if val < node.val:
if not node.left:
node.left = TreeNode(val)
else:
self.insertNode1(node.left,val)
else:
if not node.right:
node.right = TreeNode(val)
else:
self.insertNode1(node.right, val)
def insertNode2(self, node, val):
if not node:
node = TreeNode(val)
else:
if node.val > val:
self.insertNode2(node.left, val)
else:
self.insertNode2(node.right, val)
insertNode2 doesn't correctly perform an insertion as expected because of the line node = TreeNode(val), which makes a purely local assignment to node. This new object is never set to its parent .left or .right property and is lost when the function returns. The root node will not be modified in any run of this function.
Either use the already-working insertNode1, or add a return node statement to insertNode2 and make an assignment in the parent function call scope to the new child.
Here's a snippet demonstrating how that might be done:
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
class BinarySearchTree:
#staticmethod
def p(root, depth=0):
if root:
print(" " * depth + str(root.val))
BinarySearchTree.p(root.left, depth + 2)
BinarySearchTree.p(root.right, depth + 2)
#staticmethod
def insert(node, val):
if not node:
return TreeNode(val)
elif node.val > val:
node.left = BinarySearchTree.insert(node.left, val)
else:
node.right = BinarySearchTree.insert(node.right, val)
return node
if __name__ == "__main__":
root = TreeNode(5)
for n in [2, 1, 3, 7, 9, 6]:
BinarySearchTree.insert(root, n)
BinarySearchTree.p(root)
Output:
5
2
1
3
7
6
9
Which amounts to:
In python 3
class BinaryTree:
"""
=== Private Attributes ===
#type _root: object | None
#type _left: BinaryTree | None
#type _right: BinaryTree | None
"""
def __init__(self, root, left, right):
if root is None:
# store an empty BinaryTree
self._root = None
self._left = None
self._right = None
else:
self._root = root
self._left = left
self._right = right
def is_empty(self):
return self._root is None
I know how to traverse this binary tree recursively, but I'm wondering how to do it without recursion
You can use stack method to do tree traversal without recursion.
I am giving example for inorder
def inOrder(root):
# Set current to root of binary tree
current = root
s = [] # initialze stack
done = 0
while(not done):
# Reach the left most Node of the current Node
if current is not None:
# Place pointer to a tree node on the stack
# before traversing the node's left subtree
s.append(current)
current = current.left
# BackTrack from the empty subtree and visit the Node
# at the top of the stack; however, if the stack is
# empty you are done
else:
if(len(s) >0 ):
current = s.pop()
print current.data,
# We have visited the node and its left
# subtree. Now, it's right subtree's turn
current = current.right
else:
done = 1
For more explanation you can consider https://www.youtube.com/watch?v=xLQKdq0Ffjg&t=755s tutorial
I'm new to Python thus the question,this is the implementation of my my BST
class BST(object):
def __init__(self):
self.root = None
self.size = 0
def add(self, item):
return self.addHelper(item, self.root)
def addHelper(self, item, root):
if root is None:
root = Node(item)
return root
if item < root.data:
root.left = self.addHelper(item, root.left)
else:
root.right = self.addHelper(item, root.right)
This is the Node object
class Node(object):
def __init__(self, data):
self.data = data
self.left = None
self.right = None
This is my implmentation of str
def __str__(self):
self.levelByLevel(self.root)
return "Complete"
def levelByLevel(self, root):
delim = Node(sys.maxsize)
queue = deque()
queue.append(root)
queue.append(delim)
while queue:
temp = queue.popleft()
if temp == delim and len(queue) > 0:
queue.append(delim)
print()
else:
print(temp.data, " ")
if temp.left:
queue.append(temp.left)
if temp.right:
queue.append(temp.right)
This is my calling client,
def main():
bst = BST()
bst.root = bst.add(12)
bst.root = bst.add(15)
bst.root = bst.add(9)
bst.levelByLevel(bst.root)
if __name__ == '__main__':
main()
Instead of the expected output of printing the BST level by level I get the following output,
9
9223372036854775807
When I look in the debugger it seems that the every time the add method is called it starts with root as None and then returns the last number as root. I'm not sure why this is happening.
Any help appreciated.
If the root argument of your addHelper is None, you set it to a newly-created Node object and return it. If it is not, then you modify the argument but return nothing, so you end up setting bst.root to None again. Try the following with your code above — it should help your understanding of what your code is doing.
bst = BST()
bst.root = bst.add(12)
try:
print(bst.root.data)
except AttributeError:
print('root is None')
# => 12
# `bst.addHelper(12, self.root)` returned `Node(12)`,
# which `bst.add` returned too, so now `bst.root`
# is `Node(12)`
bst.root = bst.add(15)
try:
print(bst.root.data)
except AttributeError:
print('root is None')
# => root is None
# `bst.addHelper(15, self.root)` returned `None`,
# which `bst.add` returned too, so now `bst.root`
# is `None`.
bst.root = bst.add(9)
try:
print(bst.root.data)
except AttributeError:
print('root is None')
# => 9
# `bst.addHelper(9, self.root)` returned `Node(9)`,
# which `bst.add` returned too, so now `bst.root`
# is `Node(9)`
So you should do two things:
make you addHelper always return its last argument — after the appropriate modifications —, and
have your add function take care of assigning the result to self.root (do not leave it for the class user to do).
Here is the code:
def add(self, item):
self.root = self.addHelper(item, self.root)
self.size += 1 # Otherwise what good is `self.size`?
def addHelper(self, item, node):
if node is None:
node = Node(item)
elif item < node.data:
node.left = self.addHelper(item, node.left)
else:
node.right = self.addHelper(item, node.right)
return node
Notice that I changed the name of the last argument in addHelper to node for clarity (there already is something called root: that of the tree!).
You can now write your main function as follows:
def main():
bst = BST()
bst.add(12)
bst.add(15)
bst.add(9)
bst.levelByLevel(bst.root)
(which is exactly what #AaronTaggart suggests — but you need the modifications in add and addHelper). Its output is:
12
9
15
9223372036854775807
The above gets you to a working binary search tree. A few notes:
I would further modify your levelByLevel to avoid printing that last value, as well as not taking any arguments (besides self, of course) — it should always print from the root of the tree.
bst.add(None) will raise an error. You can guard against it by changing your add method. One possibility is
def add(self, item):
try:
self.root = self.addHelper(item, self.root)
self.size += 1
except TypeError:
pass
Another option (faster, since it refuses to go on processing item if it is None) is
def add(self, item):
if item is not None:
self.root = self.addHelper(item, self.root)
self.size += 1
From the point of view of design, I would expect selecting a node from a binary search tree would give me the subtree below it. In a way it does (the node contains references to all other nodes below), but still: Node and BST objects are different things. You may want to think about a way of unifying the two (this is the point in #YairTwito's answer).
One last thing: in Python, the convention for naming things is to have words in lower case and separated by underscores, not the camelCasing you are using — so add_helper instead of addHelper. I would further add an underscore at the beginning to signal that it is not meant for public use — so _add_helper, or simply _add.
Based on the following, you can see that bst.root in None after the second call to add():
>>> bst.root = bst.add(12)
>>> bst.root
<__main__.Node object at 0x7f9aaa29cfd0>
>>> bst.root = bst.add(15)
>>> type(bst.root)
<type 'NoneType'>
Your addHelper isn't returning the root node. Try this:
def addHelper(self, item, root):
if root is None:
root = Node(item)
return root
if item < root.data:
root.left = self.addHelper(item, root.left)
else:
root.right = self.addHelper(item, root.right)
return root
And then it works as expected:
>>> bst.root = bst.add(12)
>>> bst.root = bst.add(15)
>>> bst.levelByLevel(bst.root)
(12, ' ')
()
(15, ' ')
(9223372036854775807, ' ')
>>> bst.root = bst.add(9)
>>> bst.levelByLevel(bst.root)
(12, ' ')
()
(9, ' ')
(15, ' ')
(9223372036854775807, ' ')
You're using the BST object basically only to hold a root Node and the add function doesn't really operate on the BST object so it's better to have only one class (BtsNode) and implement the add there. Try that and you'll see that the add function would be much simpler.
And, in general, when a member function doesn't use self it shouldn't be a member function (like addHelper), i.e., it shouldn't have self as a parameter (if you'd like I can show you how to write the BtsNode class).
I tried writing a class that uses your idea of how to implement the BST.
class BstNode:
def __init__(self):
self.left = None
self.right = None
self.data = None
def add(self,item):
if not self.data:
self.data = item
elif item >= self.data:
if not self.right:
self.right = BstNode()
self.right.add(item)
else:
if not self.left:
self.left = BstNode()
self.left.add(item)
That way you can create a BST the following way:
bst = BstNode()
bst.add(13)
bst.add(10)
bst.add(20)
The difference is that now the add function actually operates on the object without any need for the user to do anything. The function changes the state of the object by itself.
In general a function should do only what it's expected to do. The add function is expected to add an item to the tree so it shouldn't return the root. The fact that you had to write bst.root = bst.add() each time should signal that there's some fault in your design.
Your add method probably shouldn't return a value. And you most certainly shouldn't assign the root of the tree to what the add method returns.
Try changing your main code to something like this:
def main():
bst = BST()
bst.add(12)
bst.add(15)
bst.add(9)
bst.levelByLevel(bst.root)
if __name__ == '__main__':
main()