For example, it is relatively straight forward to translate pre- and in-order traversal of a BST from recursive to iterative. But post-order is harder.
Here is the original recursive BST traversal function:
Python 3
def traverse_rec(node): # traversal of sub-tree at node.
# pre-order work here:
# print(node.val, end=' ')
if node.lft:
traverse_rec(node.lft)
# in-order work here:
print(node.val, end=' ')
# post-order work here:
# print(node.val, end=' ')
if node.rt:
traverse(node.rt),
I found some iterative versions of recursive functions (pre-, in-, post-order BST traverse) for example here, but I am looking for an iterative implementation that follows what the computer does with it's call stack so that I can just as easily convert post-order BST traversals, and, more generally convert recursive code to iterative. So a 'frame' should be pushed onto the frame stack each time a function is called which records where to continue execution on function return, as well as any variables needed by the calling function that might get changed by the called function. The frames are popped from the frame stack on function returns.
Here is my recursive to iterative translation using a frame_stack:
def traverse_iter(node):
# Iterative bst traversal. A (node, state) tuple is pushed onto frame_stack where a recursive call would occur.
# state defines where to pick up the processing on return (on pop).
# state: 0: Did not just return into this frame.
# 1: Just returned from left child.
# 2: Just returned from right child.
# Only states 1 and 2 get pushed onto the frame_stack.
# Generally, state would include any variables that are needed on return that get changed on the frame change
# in addition to the program counter (pc).
# Here, each node has all the data (val) needed, and state 1 or 2 acts as a pc to determine where to pick up
# on return.
frame_stack = []
state = 0 # Didn't just return from a child(function call).
while True:
if node is None:
if frame_stack: # Returning up recursion tree:
node, state = frame_stack.pop()
else: # or completed traversal.
break
if state == 0:
# Execute pre-order work here.
#print(node.val, end=' ')
if node.lft: # Emmulate recursive call into left child.
frame_stack.append((node, 1))
node = node.lft
continue
if state == 0 or state == 1:
# Execute in-order work here
print(node.val, end=' ')
if node.rt:
frame_stack.append((node, 2))
node = node.rt
state = 0 # State to descend into child.
continue
# Returning from a right child or there was none:
# Execute post-order work here.
# print(node.val, end=' ')
node = None # finished with this node (and all below it).
Once I 'got it', I found it fairly straightforward to develop and understand the above, and it seems the pattern I used is generally extensible to any recursive to iterative translation as it is based on what the computer does. I basically translate a recursive function call into an update of variables that get changed into the new function (node to a child node here), and push their original values onto the frame stack (node here) along with the pc (state here); minimal supporting logic was added as needed.
I am wondering if someone can supply an example where the frame payload requires more than (node, state) to pick up from function return where we left off, or simplify my BST traversal (while keeping it pre-, in-, and post-order general).
Related
so i guess you are all fimilliar with a binary heap data structure if not.. Brilliant. org say
i.e. a binary tree which obeys the property that the root of any tree is greater than or equal to (or smaller than or equal to) all its children (heap property). The primary use of such a data structure is to implement a priority queue.
will one of the properties of a binary heap is that it must be filled from top to bottom (from root) and from right to left
I coded this algorithm to find the next available spot to insert the next number I add (I hard coded the first nodes so I can track more further down the tree
this search method is inspired by BFS(Breadth First Search) algorithm
note that in this code I only care about finding the next empty node without the need to keep the heap property
I tested the code but I don't think I tested it enough so if you spot problems, bugs or suggest any ideas, every comment is welcomed
def insert(self, data):
if self.root.data == None:
self.root.data = data
print('root', self.root.data)
else:
self.search()
def search(self):
print('search..L31')
queue = [self.root]
while queue:
curr = queue.pop(0)
print(curr.data)
if curr.right_child == None:
print('made it')
return
else:
queue.append(curr.left_child)
queue.append(curr.right_child)
h = Min_heap(10)
h.insert(2)
h.root.left_child = Node(3)
h.root.right_child = Node(5)
h.root.left_child.left_child = Node(8)
h.root.left_child.right_child = Node(7)
h.root.right_child.left_child = Node(9)
# The tree I am building...
# __2__
# / \
# 3 5
# / \ / \
# 8 7 9 ⨂
# ↑
# what am
# looking for
h.search()
there is another way to figuring this out which is basically translating the tree into an array/list using special formulas and then we just assume that the next data we want to insert is the last element in the previous array and then work back through the same formulas but I already know that algorithm and I thought why not trying to solve it as a graph soooo...
You should better implement a binary heap as a list (array). But if you want to do it with node objects that have left/right attributes, then the position for the next node can be derived from the size of the tree.
So if you enrich your heap class instances with a size attribute and maintain that attribute to reflect the current number of nodes in the tree, then the following method will tell you where the next insertion point is, in O(logn) time:
Take the binary representation of the current size plus 1. So if the tree currently has 4 nodes, take the binary representation of 5, i.e. 101. Then drop the leftmost (most significant) bit. The bits that then remain are an encoding of the path towards the new spot: 0 means "left", 1 means "right".
Here is an implementation of a method that will return the parent node of where the new insertion spot is, and whether it would become the "left" or the "right" child of it:
def next_spot(self):
if not self.root:
raise ValueError("empty tree")
node = self.root
path = self.size + 1
sides = bin(path)[3:-1] # skip "0b1" and final bit
for side in sides:
if side == "0":
node = node.left
else:
node = node.right
# use final bit for saying "left" or "right"
return node, ("left", "right")[path % 2]
If you want to guarantee balanced, just add to each node how many items are there or below. Maintain that with the heap. And when placing an element, always go to where there are the fewest things.
If you just want a simple way to place, just randomly place it. You don't have to be perfect. You will still on average be O(log(n)) levels, just with a worse constant.
(Of course your constants are better with the array approach, but you say you know that one and are deliberately not implementing it.)
I'm trying to retake my Python exams and i've never really gotten the hang of binary trees.For one exercise i have to make a function that takes an int and a tree as args and returns a list of each node mark on the specified depth.
i have the basic
#dataclass
class Node:
mark: Any
left: Optional['Node']
right: Optional['Node']
to work with. I know how to determine max depth of a binary tree
def maxDepth(node:Node):
if node is None:
return -1
else :
lDepth = maxDepth(node.left)
rDepth = maxDepth(node.right)
if (lDepth > rDepth):
return lDepth+1
else:
return rDepth+1
and tried using that with a while loop to stop at a certain depth.
def layer(n:int,node:Node):
result=[]
depth=maxDepth(node)
while depth != n:
new_depth=maxDepth(node)
result.append(node)
return result
but my code makes no sense. I've also thought if i could make a recursive depth-finding function that also counts each time it's called, but have no idea how to implement that. Any help is welcome, i don't want the direct solution, but if you could point me in the right direction that would be great :)
I think that something like this might help:
from typing import Any, Optional
class Node:
mark: Any
left: Optional['Node']
right: Optional['Node']
def traverse_tree(node:Node,currentDepth:int,desiredDepth:int):
if node != None:
traverse_tree(node.left,currentDepth+1,desiredDepth)
traverse_tree(node.right,currentDepth+1,desiredDepth)
if currentDepth==desiredDepth:
print(node)
You just have to start off with currentDepth as 0 and this code will traverse the tree, printing those elements with the desired depth.
I am solving a question where we are asked to return the sum of the Depths of all the nodes in a Binary Tree.
For example:
Usually I would use a debugger to find the error in my code, but I don't know how to set up trees/binary tree in my IDE.
My code below passes most of the tests, but fails some. It fails the above test, and produces an output of 20 instead of 16.
def nodeDepths(root):
queue = [root]
sumOfDepths = 0
currentDepth = 0
while len(queue):
for _ in range(len(queue)):
currentNode = queue.pop()
sumOfDepths += currentDepth
if currentNode.left:
queue.append(currentNode.left)
if currentNode.right:
queue.append(currentNode.right)
currentDepth += 1
return sumOfDepths
Any suggestions where the code fails/is doing something unexpected.
I believe the source of your error is in your current_node = queue.pop() statement. According to the docs "The argument passed to the method is optional. If not passed, the default index -1 is passed as an argument (index of the last item)." Because you are pulling the last entry from the queue everything works okay until you have entries in the queue from different depths. To fix this problem use current_node = queue.pop(0), this will always pull the oldest entry from the queue.
I know this might be trivial but i just want to make sure. I believe it's runtime will be at most O(n). My reasoning is that every node will return a height value once throughout this recursive method. Or in other words we will visit every node in the tree once.
def height(self):
if self.is_empty():
return 0
else:
left_max = self._left.height()
right_max = self._right.height()
return max(left_max, right_max) + 1
You are performing DFS traversal on tree all nodes will be visited only one time.
So obviously it will take time of O(N) only.
I've hit a comprehension wall with respect to the following code. It is intended to search through a list of items that have both a personal utility and a cost, as if it is looking for an optimal solution in a binary search tree of possible items. Specifically, it contemplates the maximum utility that can be obtained by combining the items, subject to a weight constraint that is passed into the function.
The first recursive call under the else statement is where the trouble starts. Once the initial recursive call is made, and values are thrown into withVal and withToTake, does the code continue to recurse - piling up stack frames until a base case is hit - or does it continue to the bottom of the code and then consider the recursive calls? Print statements were of no help on this one. Any insight would be greatly appreciated!
def maxVal(toConsider, avail):
'''Assumes toConsider is a list of items (nodes higher up in the tree
corresponding to earlier calls in the recursive stack. That have not
yet been considered. avail a weight (amt of space still available).
Returns a tuple of the total value of a solution to the 0/1
knapsack problem and the items of that solution.
*Note we are NOT building the search tree. The local variable
result returns the best result found so far.To be used with
food class*
'''
#Base Cases: Nothing left to consider, or available weight is 0
if toConsider == [] or avail == 0:
result = (0, ())
#look at the first item's weight in toConsider & check against available space
#if the item's weight is > avail, recurse and slice off the first item
elif toConsider[0].getCost() > avail:
result = maxVal(toConsider[1:], avail)
else:
#look at left branch
nextItem = toConsider[0]
**withVal, withToTake = maxVal(toConsider[1:], avail - nextItem.getCost())**
withVal += nextItem.getValue()
#look at right branch
withoutVal, withoutToTake = maxVal(toConsider[1:], avail)
#tests whether left or right branch is better
if withVal > withoutVal:
result = (withVal, withToTake + (nextItem,))
else:
result = (withoutVal, withToTake)
return result
toCosider would take a list of Food objects with utility values & maxUnits is an arbitrary integer indicating the maximum amount of food one can carry.