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 8, 2024
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 Queue
class 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 item
class 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 3
printtree
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
instanceTree
def __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
!Tree
def 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 approachTree
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())
stack
structure to store nodes during traversal_postorder
methodTree
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())
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 self
BSTNode
Mapping
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)
floor
BSTMapping
0
1
2
3
4
5
6
BSTMapping
exactly like a dict
!None
valuesBSTMapping
0
1
2
3
4
5
6
iter
function performs an in-order traversalfloor
method of BSTMapping
T_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}
BSTMapping
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 self
A 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
Tree
BSTMapping
These 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