CMSC 341 Binary Search Trees 2/23/2019
Binary Search Tree A Binary Search Tree is a Binary Tree in which, at every node v, the value stored in the left child node is less than the value at v and the value stored in the right child is greater. The elements in the BST must be comparable. Duplicates are not allowed. 2/23/2019
BST Implementation The SearchTree ADT A search tree is a binary search tree in which are stored homogeneous elements with no duplicates. It is dynamic. The elements are ordered in the following ways inorder -- as dictated by operator< preorder, postorder, levelorder -- as dictated by the structure of the tree Each BST maintains a simple object, known as ITEM_NOT_FOUND, that is guaranteed to not be an element of the tree. ITEM_NOT_FOUND is provided to the constructor. (author’s code) 2/23/2019
BinarySearchTree class template <class Comparable> class BinarySearchTree { public: BinarySearchTree(const Comparable ¬Fnd); BinarySearchTree (const BinarySearchTree &rhs); ~BinarySearchTree(); const Comparable &findMin() const; const Comparable &findMax() const; const Comparable &find(const Comparable &x) const; bool isEmpty() const; void printTree() const; void makeEmpty(); void insert (const Comparable &x); void remove (const Comparable &x); const BinarySearchTree &operator=(const BinarySearchTree &rhs); 2/23/2019
BinarySearchTree class (cont) private: BinaryNode<Comparable> *root; const Comparable ITEM_NOT_FOUND; const Comparable& elementAt(BinaryNode<Comparable> *t) const; void insert (const Comparable &x, BinaryNode<Comparable> * &t) const; void remove (const Comparable &x, BinaryNode<Comparable> * &t) const; BinaryNode<Comparable> *findMin(BinaryNode<Comparable> *t const; BinaryNode<Comparable> *findMax(BinaryNode<Comparable> *t)const; BinaryNode<Comparable> *find(const Comparable &x, BinaryNode<Comparable> *t) const; void makeEmpty(BinaryNode<Comparable> *&t) const; void printTree(BinaryNode<Comparable *t) const; BinaryNode<Comparable> *clone(BinaryNode<Comparable> *t)const; }; 2/23/2019
BinarySearchTree Implementation template <class Comparable> const Comparable &BinarySearchTree<Comparable> :: find(const Comparable &x) const; { return elementAt(find (x, root)); } elementAt(BinaryNode<Comparable> *t) const { return t == NULL ? ITEM_NOT_FOUND : t->element; BinaryNode<Comparable> *BinarySearchTree<Comparable> :: find(const Comparable &x, BinaryNode<Comparable> *t) const { if (t == NULL) return NULL; else if (x < t->element) return find(x, t->left); else if (t->element < x) return find(x, t->right); else return t; // Match Public find operation calls private find operation. The private find operation recursively searches the tree. If element is found, a pointer to its node is returned. Otherwise, NULL is returned. If NULL is returned to the public find method, it then returns Item_Not_Found. Otherwise, it returns the element of the node. What about strategy of returning Item_Not_Found? Alternative strategies mentioned in text: find throws an exception find has a bool reference paramenter to indicate success/failure Discuss pros/cons Another approach (like that for lists) Find returns an iterator that points to the found element. If the element is not found, the iterator is PastEnd.
Performance of find Search in randomly built BST is O(lg n) on average but generally, a BST is not randomly built Asymptotic performance is O(h) in all cases Elements inserted in the order: 50,40,30,20,10 avg search time: 0+1+2+3+4+5 = 10 Elements inserted in order: 30,20,10,40,50 avg search time: 0+1+2+1+2=6 How many permutations of 5 things? 5! = 120 must look at average IPL of the120 trees 2/23/2019
Predecessor in BST Predecessor of a node v in a BST is the node that holds the data value that immediately precedes the data at v in order. Finding predecessor v has a left subtree then predecessor must be the largest value in the left subtree (the rightmost node in the left subtree) v does not have a left subtree predecessor is the first node on path back to root thatdoes not have v in its left subtree 2/23/2019
Successor in BST Successor of a node v in a BST is the node that holds the data value that immediately follows the data at v in order. Finding Successor v has right subtree successor is smallest value in right subtree (the leftmost node in the right subtree) v does not have right subtree successor is first node on path back to root that does not have v in its right subtree 2/23/2019
The remove Operation } template <class Comparable> void BinarySearchTree<Comparable>:: remove(const Comparable &x, BinaryNode<Comparable> *&t) const { if (t == NULL) return; // item not found, do nothing if (x < t->element) remove(x, t->left); else if (t->element < x) remove(x, t->right); else if ((t->left != NULL) && (t->right != NULL)) { t->element = findMin( (t->right)->element ); remove (t->element, t->right);} else { BinaryNode<Comparable> *oldNode = t; t = (t->left != NULL) ? T->left : t->right; delete oldNode; } } Constraint: delete a node, maintain BST property 1. Deleting a leaf. Easy. Just do it. BST property is not affected. 2. Deleting a non-leaf node v a. v has no left child -- replace v by its right child b. v has no right child -- replace v by its left child c. v has both left and right children, either: 1. Replace data in v by data of predecessor and delete predecessor 2. Replace data in v by data in successor and delete successor Note that private remove method takes a reference to a pointer to a node. This allows the pointer to be changed to point to another node. If this were not a reference to a pointer, the pointer would be passed by value and could not be changed inside the method ie the last else clause t = (t->left != NULL) ? T->left : t->right; would change to t inside the method, but not cause any change after the method returns. Asymptotic performance of remove is similar to that for find. It’s O(n) worst case and O(lg n) best/average case. Remove traverses tree from root to some leaf.
The insert Operation template <class Comparable> void BinarySearchTree<Comparable>:: insert(const Comparable &x) // public insert( ) { insert (x, root); // calls private insert( ) } insert(const Comparable &x, BinaryNode<Comparable> *&t) const { if (t == NULL) t = new BinaryNode<Comparable>(x, NULL, NULL); else if (x < t->element) insert (x, t->left); else if (t->element < x) insert (x, t->right); else ; // Duplicate; do nothing Method: recursively traverses the tree looking for a null pointer at the point of insertion. If found, constructs a new node and stitches it into the tree. If duplicate found, simply returns with no insertion done. Question: Suppose Item_Not_Found object is inserted? The insert code allows it. This would mean the find operation could no longer indicate an unsuccessful search. Esoteric but possible -- and a reason not to use the text’s approach to indicating unsuccessful search. Asymptotic performance of insert = O(n) as usual.
Implementation of makeEmpty template <class Comparable> void BinarySearchTree<Comparable>:: makeEmpty() // public makeEmpty () { makeEmpty(root); // calls private makeEmpty ( ) } makeEmpty(BinaryNode<Comparable> *&t) const if (t != NULL) { // post order traversal makeEmpty (t->left); makeEmpty (t->right); delete t; t = NULL; Must hang onto each node until its children are deleted Postorder traversal is used in makeEmpty Asymptotic performance = O(n) Traversals of theentire tree (pre, post, in, level) require that each node be visited. Since n nodes are visited, operation is O(n) 2/23/2019
Tree Iterators Could provide separate iterators for each desired order Iterator<T> *GetInorderIterator(); Iterator<T> *GetPreorderIterator(); Iterator<T> *GetPostorderIterator (); Iterator<T> *GetLevelorderIterator (); Approach 1: Easy to code using existing list iterators. But: O(n) storage for the list (can do better) Difficult to free list storage when iterator is deleted (memory leak) 2/23/2019
Tree Iterator Implementation Approach 1: Store traversal in list. Return list iterator for list. Iterator<T> BinaryTree::GetInorderIterator() { List<T> *lst = new ArrayList<T>; FullListInorder(list, getRoot()); return list->GetIterator(); } void FillListInorder(ArrayList<T> *lst, Bnode<T> *node) { if (node == NULL) return; FillListInorder(list, node->left); lst->Append(node->data); FillListInorder(lst, node->right); 2/23/2019
Tree Iterators (cont) Approach 2: store traversal in stack to mimic recursive traversal template <class T> class InOrderIterator : public Iterator { private: Stack<T> _stack; BinaryTree<T> *_tree; public: InOrderIterator(BinaryTree<T> *t); bool hasNext() {return (!_stack.isEmpty()); } T Next(); }; 2/23/2019
Tree Iterators (cont’d) template <class T> InOrderIterator<T>::InOrderIterator(BinaryTree<T> *t) { _tree = t; Bnode<T> *v = t->getRoot(); while (v != NULL) { _stack.Push(v); // push root v = v->left; // and all left descendants } Example: 50 30 80 20 40 70 90 O(h) stoarage (=O(lg n) in best and avg cases) Easy to free stack (destroyed when iterator is destroyed) More difficult to code Similar code for re-order and post-order 2/23/2019
Tree Iterators (cont’d) template <class T> T InOrderIterator<T>::Next() { Bnode<T> *top = _stack.Top(); _stack.Pop(); Bnode<T> *v = top->right; while (v != NULL) { _stack.Push(v); // push right child v = v->left; // and all left descendants } return top->element; 2/23/2019