def test_items_pre_order_with_7_numbers(self): # Create a complete binary search tree of 7 items in level-order items = [4, 2, 6, 1, 3, 5, 7] tree = BinarySearchTree(items) # Ensure the pre-order traversal of tree items is ordered correctly print(tree.items_pre_order()) assert tree.items_pre_order() == [4, 2, 1, 3, 6, 5, 7]
def test_items_pre_order_with_3_strings(self): # Create a complete binary search tree of 3 strings in level-order items = ['B', 'A', 'C'] tree = BinarySearchTree(items) # Ensure the pre-order traversal of tree items is ordered correctly assert tree.items_pre_order() == ['B', 'A', 'C']
class TreeSet(object): """Initialize a new empty set structure, and add each element if a sequence is given""" def __init__(self, elements=None): self.tree = BinarySearchTree() self.element = BinaryTreeNode if elements is not None: for element in elements: self.add(element) @property def size(self): """Property that tracks the number of elements in constant time""" return self.tree.size def contains(self, element): """return a boolean indicating whether element is in this set Running time: O(log n) since we just search through a binary tree""" return self.tree.contains(element) def add(self, element): """add element to this set, if not present already Running time: O(log n) because we must binary search""" if self.tree.contains(element): raise KeyError(f'{element} is already in tree.') else: self.tree.insert(element) def remove(self, element): """remove element from this set, if present, or else raise KeyError Running time: Best Case: O(h) because we have to either scale the tree or move everything down once found. Worst Case: O(n) because the element could be the last we check in the tree.""" if self.tree.contains(element) != True: raise KeyError(f'No such element exists: {element}') else: self.tree.delete(element) def union(self, other_set): """return a new set that is the union of this set and other_set Best and worst case: (m + n) * log m because we add the length of each set to the time of the .add calls""" # m log m + n*2logm = (m + 2n) * log m = # (m + n) * log m new_set = TreeSet() for element in self.tree.items_pre_order(): # O(h(self)) new_set.add(element) # + O(m) # Adds remaining other_set elements for element in other_set.tree.items_pre_order(): if not new_set.contains(element): # O(log m) new_set.add(element) # +O(log m) return new_set def intersection(self, other_set): """return a new set that is the intersection of this set and other_set Best case/worst case: O(log n) due to binary search""" new_set = TreeSet() for element in self.tree.items_in_order(): if other_set.contains(element): new_set.add(element) return new_set def difference(self, other_set): """return a new set that is the difference of this set and other_set Best/Worst case: O(n) because we require checking all nodes""" new_set = TreeSet() for element in self.tree.items_in_order(): new_set.add(element) for element in other_set.tree.items_in_order(): if new_set.contains(element): new_set.remove(element) return new_set def is_subset(self, other_set): """return a boolean indicating whether other_set is a subset of this set Best case:e O(1), incase the size is bigger than other_set and we don't need to do anything. Worst case: O(n), if we must traverse through all nodes""" if self.size > other_set.size: return False for element in other_set.tree.items_in_order(): if not self.contains(element): return False return True
class TreeSet: def __init__(self, items=None): self.tree = BinarySearchTree() if items is not None: for item in items: self.add(item) def __len__(self): return len(self.tree) def __contains__(self, item): return item in self.tree def __iter__(self): for item in self.tree.items_in_order(): yield item def add(self, item): if item not in self.tree: self.tree.insert(item) def remove(self, item): self.tree.delete(item) def _smaller_larger_pair(self, tree_1, tree_2): if len(tree_1) < len(tree_2): return tree_1, tree_2 return tree_2, tree_1 def intersection(self, other_set): new_set = TreeSet() smaller, larger = self._smaller_larger_pair(self.tree, other_set.tree) for item in smaller.items_pre_order(): if item in larger: new_set.add(item) return new_set def _alternating_generator(self, tree1, tree2): items_1 = tree1.items_pre_order() items_2 = tree2.items_pre_order() for item_1, item_2 in zip(items_1, items_2): yield item_1 yield item_2 def union(self, other_set): new_set = TreeSet() for item in self._alternating_generator(self.tree, other_set.tree): new_set.add(item) return new_set def difference(self, other_set): new_set = TreeSet() for item in self.tree.items_pre_order(): if item not in other_set: new_set.add(item) return new_set def is_subset(self, other_set): if len(self) > len(other_set): return False for item in self.tree.items_pre_order(): if not other_set.contains(item): return False return True