350 likes | 368 Views
Computer Science 112. Fundamentals of Programming II Binary Search Trees. Iterative Binary Search. def index (target, sortedList): left = 0 right = len(sortedList) – 1 while left <= right: mid = (left + right) // 2 # compute midpoint
E N D
Computer Science 112 Fundamentals of Programming II Binary Search Trees
Iterative Binary Search def index(target, sortedList): left = 0 right = len(sortedList) – 1 while left <= right: mid = (left + right) // 2 # compute midpoint if target == sortedList[mid]: return mid # found target elif target < sortedList[mid]: right = mid – 1 # go left else: left = mid + 1 # go right return -1 # target not there
Recursive Binary Search def index(target, sortedList): def recurse(left, right): if left > right: return -1 # target not there else: mid = (left + right) // 2 # compute midpoint if target == sortedList[mid]: return mid # found target elif target < sortedList[mid]: return recurse(left, mid – 1) # go left else: return recurse(mid + 1, right) # go right return recurse(0, len(sortedList) – 1)
34 41 56 63 72 89 95 Call Tree for Binary Search 0 1 2 3 4 5 6
34 72 89 56 41 95 34 89 72 63 56 41 95 Call Tree for Binary Search 0 1 2 3 4 5 6 4 5 6 0 1 2
56 34 95 34 41 56 72 89 63 95 34 41 95 56 72 89 72 Call Tree for Binary Search 0 1 2 3 4 5 6 4 5 6 0 1 2 4 6 0 2
63 41 89 34 56 72 95 Binary Search Tree (BST)
Binary Search Tree (BST) • Ordering principle: • Each item in the left subtree is less than the item in the parent • Each item in the right subtree is greater than the item in the parent
Recursive Search of a BST Node contains(target, node) If the node is None Return False Else if node.data equals the target Return True Else if the target is less than the data Return contains(target, node.left) Else Return contains(target, node.right)
Minimal Set of BST Operations t.isEmpty() Returns True if empty, False otherwise len(t) Returns the number of items in the tree str(t) Returns a string representation iter(t) Supports a for loop, visiting in preorder item in t True if item is in tree, False otherwise t1 + t2 Returns a new tree with items in t1 and t2 t1 == t2 Equality test for two trees t.add(item) Adds item to the tree t.remove(item) Removes the given item The precondition of remove is that the item is in the tree.
Tree-Specific Operations t.preorder() Returns an iterator for preorder traversal t.inorder() Returns an iterator for inorder traversal t.postorder() Returns an iterator for postorder traversal t.levelorder() Returns an iterator for levelorder traversal t.height() Returns the number of links from the root to the deepest leaf t.isBalanced() Returns True if t.height() < 2 * log2(len(t) + 1) - 1 or False otherwise t.rebalance() Rearranges the nodes to ensure that t.height() <= log2(len(t) + 1)
Using a Binary Search Tree tree = LinkedBST() tree.add("D") tree.add("B") tree.add("A") tree.add("C") tree.add("F") tree.add("E") tree.add("G") print(tree) print("F"in tree) for item in tree: print(item) for item in tree.inorder(): print(item) for item in tree.postorder(): print(item) for item in tree.levelorder(): print(item) for ch in ('A', 'B', 'C', 'D', 'E', 'F', 'G'): print(tree.remove(ch))
The LinkedBSTClass from abstractcollection import AbstractCollection from bstnode import BSTNode classLinkedBST(AbstractCollection): def __init__(self, sourceCollection = None): self._root = None AbstractCollection.__init__(self, sourceCollection) # Tree methods go here
Method __contains__ def __contains__(self, target): """Returns True if target is found or False otherwise.""” def recurse(node): if node isNone: returnFalse elif target == node.data: returnTrue elif target < node.data : return recurse(node.left) else: return recurse(node.right) return recurse(self._root) Several operations call nested helper functions to recurse on nodes
Method __contains__ def __contains__(self, target): """Returns True if target is found or False otherwise.""” def recurse(node): return node != None and \ (target == node.data or \ (target < node.data and recurse(node.left)) or \ recurse(node.right)) return recurse(self._root) Alternatively, for the logically intoxicated . . .
Preorder Traversal def preorder(self): """Supports a preorder traversal on a view of self.""" lyst = list() def recurse(node): if node != None: lyst.append(node.data) recurse(node.left) recurse(node.right) recurse(self._root) returniter(lyst) A traversal a list of items in the order in which they were visited
iter – First Attempt def __iter__(self): """Supports a preorder traversal on a view of self.""” self.preorder() The iterator captures a preorder traversal However, this implementation would incur linear running time and linear growth of memory before the caller ever accesses an item Moreover, mutations within the iterator are not detected
iter – Second Attempt If the tree is not empty Create a new stack Push the root node onto the stack While the stack is not empty Pop the top node and yield its data Check the mod count for mutation and respond if necessary If the node’s right subtree is not empty Push the right subtree onto the stack If the node’s left subtree is not empty Push the left subtree onto the stack Visits the nodes in preorder, yielding the datum in each node What is the running time and growth of memory?
Adding Items to a BST D New items are always added to the frontier of the tree, as leaf nodes B F G A
Case 1: The Tree is Empty • Set the root to a new node containing the item
Case 2: The Tree Is Not Empty • Search for the proper place for the new node D E B F G A
Case 2: The Tree Is Not Empty • Search for the proper place for the new node D E B F G A
Case 2: The Tree Is Not Empty • Search for the proper place for the new node D B F G E A
I like trees because they seem more resigned to the way they have to live than other things do. − Willa Cather, O Pioneers!
Method add def add(self, item): """Adds item to self.""" # Tree is empty, so new item goes at the root if self.isEmpty(): self._root = BSTNode(item) # Otherwise, search for the item's spot else: recurse(self._root) self._size += 1 Define a helper function recurse to perform the search for the new item’s place in a non-empty tree
Method add def add(self, item): """Adds item to self.""" def recurse(node): # New item is less, go left until spot is found if item < node.data: if node.left isNone: node.left = BSTNode(item) else: recurse(node.left) # New item is >=, go right until spot is found elif node.right isNone: node.right = BSTNode(item) else: recurse(node.right) # Tree is empty, so new item goes at the root if self.isEmpty(): self._root = BSTNode(item) # Otherwise, search for the item's spot else: recurse(self._root) self._size += 1
Method str def __str__(self): def recurse(node, level): s = "" if node != None: s += recurse(node.right, level + 1) s += "| " * level s += str(node.data) + "\n" s += recurse(node.left, level + 1) return s return recurse(self._root, 0) Displays the tree rotated 90° counterclockwise
Adding Items in Best Order tree = LinkedBST() print(">>>>Items in advantageous order:") tree.add("E") tree.add("C") tree.add("D") tree.add("A") tree.add("H") tree.add("F") tree.add("K") print(tree)
Adding Items in Worst Order print(">>>>Items in worst order:") tree = LinkedBST() for i inrange(1, 9): tree.add(i) print(tree)
Adding Items in Random Order print(">>>>Items in random order:") tree = LinkedBST() foriinrange(7): tree.add(randomString()) print(tree)
A A D G G D C B C B E E F F Performance Best Worst
The Shape of the Tree • As the tree becomes linear, searches and insertions degrade to linear • A BST can be rebalanced in linear time • Alternatively, a BST can be set up to maintain its balance during insertions
A Simple Rebalance Strategy • Use an inorder traversal to copy the tree’s items to a list • Clear the tree • Add the item at the list’s midpoint to the tree • Subdivide the list into segments around the midpoint, and repeat step #3 on each segment • Repeat step 4 until there are no subdivisions left
A Simple Tree Rebalancing Algorithm Create a list from an inorder traversal of the tree Clear the tree Define a function recurse(left, right) # indexes into the list if left <= right add the item at the midpoint of the list to the tree recurse(left, midpoint – 1) recurse(midpoint + 1, right) Call recurse with left = 0 and right = length of list - 1 What is the running time?
For Friday Recursive descent parsing