CS410, Summer 1998 Lecture 5 Outline Dan Grossman Goals: * Polymorphism, when to use it, and how to fake it in Java, fake it even more in C, sort of do it right in C++, and how to do it right in languages you've never heard of. * Trees: How why build them, why we build them. * ADT: Dynamic set * Binary Search Tree: Breaking the O(n) barrier (if we're balanced). Quizzes this week: Tuesday, Wednesday, Friday Reading: * The polymorphism part isn't in CLR. Casting in Java should be covered in any Java book. * Trees are in CLR 5.5. Binary search trees are in CLR 13. * Polymorphism We've learned Stacks, Queues, etc. and a lot more are coming. But I always glossed over what we were actually stacking and queueing. I don't want to make a new class every time I need a Stack for a new kind of thing. Polymorphism is abstracting in exactly this way. In the OO world, we take advantage that everything is an Object. Pain: Wrapper classes and associated inefficiency Pain: Casting in and out -- compiler doesn't know you're getting out what you're putting in. Pain: No way to require all objects in same stack be a particular kind of thing (so casts may fail without programmer discipline). But it's better than cut-and-paste -- the great programming crime. Personally, this is the biggest shortcoming of Java. How do other languages do it? C++ has templates. They look like exactly what you want. In reality, the compiler does the cut and paste for you. This is bad because it doesn't check anything until instantiation. Silly eg.: class Foo { T x; int bar () { return x; } } Hard to sell code you don't know will compile (but the C++ world lives with it). C (and C++) has void *. Kind of like Object without the safety. You're the programmer -- you must know what you're doing. Do any languages get the "what you put in you get out" thing right? Yep, they have methods saying "gimme a _____ and I'll give you back an _____". Remember, I'm biased. ========= * Trees Basic notation: we draw them upside-down, parents, children, siblings, root, leaves, etc. Pointers only as necessary (cf. singly vs. doubly linked) Fixed vs. arbitrary degree For arbitrary: * list of children * leftmost child and sibling Many data definitions are really some sort of tree structure (like on your homework) but we usually mean where all nodes are the same exact type. Trees are never cyclic (else we don't call them trees) Notice that lists are trees. * ADT: Dictionary (with keys and vals) Operations: Insert, Delete, Lookup (Lookup might return null or throw an exception, depending on application, not our present concern) Many other possible operations. * With lists or arrays: your choice between slow insert and slow lookup depending on sortedness and data structure. Array List Sorted Unsorted Sorted Unsorted Insert n 1 (but) n 1 Lookup log n n n n Delete n (or logn + zombie) n n n Min/Max 1 n 1 n (This is an in-your-sleep table!!) (Aside: Never trust average case unless you have guarantees about your inputs.) A main use of trees is to do everything in O(log n). That's worse than 1 and better than n. If n is large, I'd rather have log n for everything than 1 for some and n for some (unless the operations taking O(1) time are exponentially more common). * Binary search trees (BSTs) A binary search tree is a binary (max degree 2) tree with the property that for _ALL_ nodes x: * The nodes in the left subtree have keys less than x's key * The nodes in the right subtree have keys greater than x's key No duplicate keys (see homework). * search, min, max, predecessor, successor, insert, delete all in time O(height) T.Search(key): Make all the correct turns in a walk down the tree. while (x != null) { if (x.key == key) return x.val; else if (x.key < key) x = x.right; else if (x.key > key) x = x.left; } return null; // or throw exception (Recursive version too) T.min():Go left, go left, go left. x = root; while (x != null) { x = x.left; } return x; T.max():Go right, go right, go right. x = root; while (x != null) { x = x.right; } return x; Predecessor(x): Max in left subtree, else closest ancestor for which x is a right descendant. Successor(x): Min in right subtree, else closest ancestor for which x is a left descendant. Insert(x): Walk to the bottom such that x can be a correctly-placed leaf. if tree is empty make x the root Node curr = root; Node parent; while (curr != null) { parent = curr; if (x.key < curr.key) curr = curr.left; if (x.key > curr.key) curr = curr.right; else throw new RepeatedKeyBSTException(); } x.parent = parent; if (x.key < parent.key) parent.left = x; else parent.right = x; Delete(x): If 0 or 1 children replace with child, else replace with successor and delete successor. Notice the successor has 0 or 1 children because we're in the else case. Code is in the book.