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
nApril 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
ndef 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
nTree!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
gprinttree 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
gyield 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
6BSTMapping exactly like a dict!None valuesBSTMapping0
1
2
3
4
5
6iter 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