Binary Trees The material we will cover in class is all found in the link to the Trees web reading. I will briefly summarize below, what we will cover, but this reading is pretty short and direct. Trees are one of the two most important data structure in Computer Science (probably the other is Graphs; in fact, trees are just a special kind of a graph: one with (a) every node but the root having one predecessor and (b) no cycles). Trees are useful for representing all kinds of interesting information (like the inheritance structure of classes in Java libraries, or the structure of a file system), and have been studied extensively. Technical terms discussed in the lecture will include parent and child nodes; root, internal and leaf nodes. We will also discuss the height and depth of a node. There are two metrics that we can apply to trees: computing their size (the number of nodes reachable from the root) and their height (the distance from the root to the deepest leaf). We examined the simplfied code below for computing the height of a tree (by coming to the interesting conclusion that the height of an empty tree was -1) Just as we examined the type LN (list node) we examine the type TN (tree node) used to represent the simplest kind of trees: binary trees (trees where each parent has at most two children). We then examined some simple recursive algorithms to compute the size and height of a binary tree. Unlike list processing methods, there are few iterative methods that operate on trees, unless we also use a collection class like a stack or queue. We will find that although it makes no intuitive sense to define an empty tree to have a height of -1, by using this definition we can define the height of any tree (null or not) and simplify the method that computes heights. Wit this observation, we simplfied the code to compute the height of a tree as public static int height (TN t) { if (t == null) return -1; else return 1+Math.max(height(t.left),height(t.right)) } Although it is not in the web reading, the way to compute the depth of a (unique) node containing value (note we are assuming a binary tree, not necessarily a binary SEARCH tree) in a tree with root t, uses a helper method. public static int depth (TN t, int value) {return depth(t,value,0);} public static int depth (TN t, int value, int currentDepth) { if (t == null) return -1; else if (t.value = value) return currentDepth; else { int tryLeft = depth(t.left, value, currentDepth+1); if (tryLeft != -1) return tryLeft; else return depth(t.right, value, currentDepth+1); } Although all 4 node linear linked lists have the same structure, there are 14 differently structured binary trees with 4 nodes (and 42 with 5 nodes). There is a formula using combinatorics to compute the number of different binary trees of size n: (2n)!/( n!(n+1)! ), which is closely approximated by 4^n/sqrt(pi*n^3) having about a 10% error for n=10 and less than a 1% error for n=100. There are Cn different n node trees (Cn is the nth Catalan number). Here is a method that is more intuitive for computing this value public static int numberOfBinaryTrees (int n) { if (n == 0 || n == 1) return 1; else { int all = 0; for (int leftN = 0; leftN < n; leftN++) all += numberOfBinaryTrees(leftN) * numberOfBinaryTrees(n-leftN-1) ; return all; } } Here the base cases shows that all empty or 1 node trees look the same. Otherwise, we sum the number of trees whose left subtree is of size 0, 1, 2, ... , n-1 and whose right subtree is the remaining size (minus 1 for the parent of the two subtrees). If there are y different trees on the left and z different trees on the right, for that size there are yz different trees. Binary SEARCH trees allow us to simultaneously (on average) achieve logarithmic behavior for adding, searching, and removing values to a data structure -not achievable with arrays or linked lists, where some operations can have this complexity class, but others will be O(N). For example, we can search a sorted array in O(Log N), but adding or removing values requires, in the worst case, shifting N values in the array, so that operation is O(N). Finally, we will look at the relationship between the two tree metrics: size and height. We will use N for the size and H for the height. It is simple to see that the maximum height of a tree with N nodes is N-1 (each parent node in the tree has one child). So H < N. By using < we eliminate having to write the -1. We will find that maximum size of a tree has N <= 2^(H+1) - 1 and prove this formula by induction, using diagrams. So H < N < 2^(H+1). Again, by using < instead of <= we can eliminate the -1 on each side. Thus, we can say that N is Omega(H) and O(2^H): N must grow at least as fast as the height, but no faster than 2 raised to the height power. Because N < 2^(H+1) we know Log2 N < H+1 or Log2 N -1 < H or rewritten is H > Log2 N - 1. Thus, we can say that H is Omega(Log2 N) and O(N): H must grow at least as fast as Log2 of the height, but no faster than than the height. So, here are two examples where we have a lower bound on N and H, so we can use big-Omega and big-O notation in a meaningful way. There is no big-Theta because these bounds are in different complexity classes.