def printtree(T):
print(T[0])
for child in range(1, len(T)):
printtree(T[child])
T = ['c', ['a', ['p'], ['n'], ['t']], ['o', ['n']]]
print(T)
printtree(T)['c', ['a', ['p'], ['n'], ['t']], ['o', ['n']]]
c
a
p
n
t
o
n
April 7, 2025
def printtree(T):
print(T[0])
for child in range(1, len(T)):
printtree(T[child])
T = ['c', ['a', ['p'], ['n'], ['t']], ['o', ['n']]]
print(T)
printtree(T)['c', ['a', ['p'], ['n'], ['t']], ['o', ['n']]]
c
a
p
n
t
o
n
def printtree_iterator(T):
iterator = iter(T)
print(next(iterator))
for child in iterator:
printtree_iterator(child)
T = ['c', ['a', ['p'], ['n'], ['t']], ['o', ['n']]]
print(T); printtree_iterator(T)['c', ['a', ['p'], ['n'], ['t']], ['o', ['n']]]
c
a
p
n
t
o
n
Tree!int valuesTree will leverage the previously created Queueclass ListQueueSimple:
def __init__(self):
self._L = []
def enqueue(self, item):
self._L.append(item)
def dequeue(self):
return self._L.pop(0)
def peek(self):
return self._L[0]
def __len__(self):
return len(self._L)
def isempty(self):
return len(self) == 0
class ListQueueFakeDelete:
def __init__(self):
self._head = 0
self._L = []
def enqueue(self, item):
self._L.append(item)
def peek(self):
return self._L[self._head]
def dequeue(self):
item = self.peek()
self._head += 1
return item
def __len__(self):
return len(self._L) - self._head
def isempty(self):
return len(self) == 0
class ListQueue(ListQueueFakeDelete):
def dequeue(self):
item = self._L[self._head]
self._head += 1
if self._head > len(self._L)//2:
self._L = self._L[self._head:]
self._head = 0
return itemclass Tree:
def __init__(self, L):
iterator = iter(L)
self.data = next(iterator)
self.children = [Tree(c) for c in iterator]
def _listwithlevels(self, level, trees):
trees.append(" " * level + str(self.data))
for child in self.children:
child._listwithlevels(level + 1, trees)
def __str__(self):
trees = []
self._listwithlevels(0, trees)
return "\n".join(trees)
def __eq__(self, other):
return self.data == other.data and self.children == other.children
def height(self):
if len(self.children) == 0:
return 0
else:
return 1 + max(child.height() for child in self.children)
def __contains__(self, k):
return self.data == k or any(k in ch for ch in self.children)
def preorder(self):
yield self.data
for child in self.children:
for data in child.preorder():
yield data
__iter__ = preorder
def _postorder(self):
node, childiter = self, iter(self.children)
stack = [(node, childiter)]
while stack:
node, childiter = stack[-1]
try:
child = next(childiter)
stack.append((child, iter(child.children)))
except StopIteration:
yield node
stack.pop()
def postorder(self):
return (node.data for node in self._postorder())
def _layerorder(self):
node, childiter = self, iter(self.children)
queue = ListQueue()
queue.enqueue((node, childiter))
while queue:
node, childiter = queue.peek()
try:
child = next(childiter)
queue.enqueue((child, iter(child.children)))
except StopIteration:
yield node
queue.dequeue()
def layerorder(self):
return (node.data for node in self._layerorder())Tree API__init__(L): Initialize a new tree given a list of lists.
height(): Return the height of the tree.
__str__(): Return a string representing the entire tree.
__eq__(other): Return True if the tree is equal to other.
__contains__(k): Return True if and only if the tree contains the data k either at the root or at one of its descendants. Return False otherwise.
__contains__)height method gives maximal depth of the treeTree APIpreorder(): Return an iterator over the data in the tree that yields values according to the pre-order traversal of the tree.
postorder(): Return an iterator over the data in the tree that yields values according to the post-order traversal of the tree.
layerorder(): Return an iterator over the data in the tree that yields values according to the layer-order traversal of the tree.
__iter__(): An alias for the default traversal, preorder.
__iter__ method allows for iteration over the treeTree class!Tree instanceTree constructorclass Tree:
def __init__(self, L):
iterator = iter(L)
self.data = next(iterator)
self.children = [Tree(c) for c in iterator]data attribute stores the current node’s valuechildren attribute stores the children of current node[1, [2], [3, [4]]]1 with children 2 and 3printtree functiondef printtree(T):
print(T.data)
for child in T.children:
printtree(child)
T = Tree(['a', ['b', ['c', ['d']]],['e',['f'], ['g']]]); printtree(T)a
b
c
d
e
f
g
printtree function now accepts a Tree instanceTreedef __str__(self, level = 0):
treestring = " " * level + str(self.data)
for child in self.children:
treestring += "\n" + child.__str__(level + 1)
return treestring
__str__ method now accepts an optional level parameterTree?Tree!Treedef preorder(self):
yield self.data
for child in self.children:
for data in child.preorder():
yield data
__iter__ = preorder
a
b
c
d
e
f
g
yield to generate nodes according to pre-order approachTreedef _postorder(self):
node, childiter = self, iter(self.children)
stack = [(node, childiter)]
while stack:
node, childiter = stack[-1]
try:
child = next(childiter)
stack.append((child, iter(child.children)))
except StopIteration:
yield node; stack.pop()
def postorder(self):
return (node.data for node in self._postorder())stack structure to store nodes during traversal_postorder methodTreedef _layerorder(self):
node, childiter = self, iter(self.children)
queue = ListQueue()
queue.enqueue((node, childiter))
while queue:
node, childiter = queue.peek()
try:
child = next(childiter)
queue.enqueue((child, iter(child.children)))
except StopIteration:
yield node; queue.dequeue()
def layerorder(self):
return (node.data for node in self._layerorder())ListQueue structure to store nodes during traversal_layerorder methodget(k): Return the value associated to the key k. Raise the KeyError if the given key k is not present.
put(k,v): Add the key-value pair (k,v) to the mapping.
floor(k): Return a tuple (k,v) that is the key-value pair in the mapping with the largest key that is less than or equal to k. If there is no such tuple, it returns (None, None).
remove(k) - Remove the key-value pair with key k from the ordered mapping. Raise KeyError if the given key not present.
BSTNode classclass BSTNode:
def __init__(self, key, value):
self.key = key
self.value = value
self.left = None
self.right = None
self._length = 1
def __len__(self):
return self._length
def __str__(self):
return str(self.key) + " : " + str(self.value)
def get(self, key):
if key == self.key:
return self
elif key < self.key and self.left:
return self.left.get(key)
elif key > self.key and self.right:
return self.right.get(key)
else:
raise KeyError
def put(self, key, value):
if key == self.key:
self.value = value
elif key < self.key:
if self.left:
self.left.put(key, value)
else:
self.left = BSTNode(key, value)
elif key > self.key:
if self.right:
self.right.put(key, value)
else:
self.right = BSTNode(key, value)
self._updatelength()
def _updatelength(self):
len_left = len(self.left) if self.left else 0
len_right = len(self.right) if self.right else 0
self._length = 1 + len_left + len_right
def floor(self, key):
if key == self.key:
return self
elif key < self.key:
if self.left is not None:
return self.left.floor(key)
else:
return None
elif key > self.key:
if self.right is not None:
floor = self.right.floor(key)
return floor if floor is not None else self
else:
return self
def __iter__(self):
if self.left is not None:
yield from self.left
yield self
if self.right is not None:
yield from self.right
def _swapwith(self, other):
self.key, other.key = other.key, self.key
self.value, other.value = other.value, self.value
def maxnode(self):
return self.right.maxnode() if self.right else self
def remove(self, key):
if key == self.key:
if self.left is None: return self.right
if self.right is None: return self.left
self._swapwith(self.left.maxnode())
self.left = self.left.remove(key)
elif key < self.key and self.left:
self.left = self.left.remove(key)
elif key > self.key and self.right:
self.right = self.right.remove(key)
else: raise KeyError
self._updatelength()
return selfBSTNodeMapping interfaceclass Mapping:
def get(self, key):
raise NotImplementedError
def put(self, key, value):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
def _entryiter(self):
raise NotImplementedError
def __iter__(self):
return (e.key for e in self._entryiter())
def values(self):
return (e.value for e in self._entryiter())
def items(self):
return ((e.key, e.value) for e in self._entryiter())
def __contains__(self, key):
try:
self.get(key)
except KeyError:
return False
return True
def __getitem__(self, key):
return self.get(key)
def __setitem__(self, key, value):
self.put(key, value)
def __str__(self):
return "{" + ", ".join(str(e) for e in self._entryiter()) + "}"Mapping interface for the binary search treeBSTMapping classclass BSTMapping(Mapping):
def __init__(self):
self._root = None
def get(self, key):
if self._root:
return self._root.get(key).value
raise KeyError
def put(self, key, value):
if self._root:
self.root = self._root.put(key, value)
else:
self._root = BSTNode(key, value)
def __len__(self):
return len(self._root) if self._root is not None else 0
def _entryiter(self):
if self._root:
yield from self._root
def floor(self, key):
if self._root:
floornode = self._root.floor(key)
if floornode is not None:
return floornode.key, floornode.value
return None, None
def remove(self, key):
if self._root:
self._root = self._root.remove(key)
else:
raise KeyError
def __delitem__(self, key):
self.remove(key)floorBSTMapping0
1
2
3
4
5
6
BSTMapping exactly like a dict!None valuesBSTMapping0
1
2
3
4
5
6
iter function performs an in-order traversalfloor method of BSTMappingT_two = BSTMapping()
for i in [1,3,0,2,4,5,6]:
T_two[i] = None
floor_t_two = T_two.floor(1)
print(floor_t_two)
floor_t_two = T_two.floor(3)
print(floor_t_two)
floor_t_two = T_two.floor(100)
print(floor_t_two)
floor_t_two = T_two.floor(-100)
print(floor_t_two)
print(T_two)(1, None)
(3, None)
(6, None)
(None, None)
{0 : None, 1 : None, 2 : None, 3 : None, 4 : None, 5 : None, 6 : None}
BSTMappingdef remove(self, key):
if key == self.key:
if self.left is None: return self.right
if self.right is None: return self.left
self._swapwith(self.left.maxnode())
self.left = self.left.remove(key)
elif key < self.key and self.left:
self.left = self.left.remove(key)
elif key > self.key and self.right:
self.right = self.right.remove(key)
else: raise KeyError
self._updatelength()
return selfA BST with \(n\) nodes can have height \(O(n)\)
This means that removal may not always be efficient!
Can we make a binary search tree more balanced?
We will say that a BST with \(n\) nodes is balanced if the height of the tree is at most some constant times \(\log n\). A balanced tree will better ensure methods are more predictably efficient.
Balancing the tree must preserve the BST property!
Refer to chapter 19 for more details about tree rotations
TreeBSTMappingThese hierarchical data structures demonstrate the combination of data structures to achieve a goal! For instance, the Tree uses a Stack and a Queue while the BSTMapping is a Dictionary.
Algorithmology