Test 2, Spring 2010 Answers Problem 1 Parts A and B Adding to a queue should be an O(1) operation. If linked lists are used, either single or double, you can add after the last node and update the pointer (below is single-link, double-link requires setting prev as well) myLast.next = new Node(...); myLast = myLast.next; You can remove from the front of the list too in O(1) time regardless of whether singly or doubly linked lists are used Node toRemove = myFront; myFront = myFront.next; toRemove.next = null; // no access to rest So adding N elements is O(N) and removing N/2 elements is O(N) so total is O(N). Part C: 4 5 6 + * operand: push operator: pop, pop, operate, push 4 11 * = 44 <--- answer Problem 2: Part A: Recurrence is T(n) = T(n-1) + O(1) where T(n) is the time for rmax to run when a.length-firstIndex = n, i.e., the range of elements being considered has size n. since there is one recursive call with the range decreased by 1 and the other operations are O(1). The solution to this recurrence is O(n) Part B: Average case search trees are bushy, so N nodes means the height of tree/depth of the root is O(log N) so average case the loop executes O(log N) times. Worst case is every node has a right child and no left child, like a linked list, loop is O(N) Part C: Max in a heap must be in a leaf. The code finds the last non-leaf value in the heap and looks at all other values (all leaf values) to find the max. As is evident from the code this is half the values in the array so half the N values are examined, the code runs in O(N) time for an N-element heap. This makes sense since in a complete binary tree (heap is complete except for last level, nodes added left-to-right) half the nodes are leaves. Part D: The method max is O(1), it simply compares two strings. The method trmax has the following recurrences in average and worst cases: average: T(n) = 2T(n/2) + O(1) ==> solution is O(n) both recursive calls are on trees with half the nodes, hence n/2 worst: T(n) = T(n-1) + T(0) + O(1) = T(n-1) + O(1) ===> solution is O(n) in worst case all nodes on one side of tree, e.g., right side This makes sense, this is doing O(1) work at every node of an n-node tree, hence complexity is O(n) regardless of the shape of the tree. Part E: The work in this method is the line below. pq.addAll(Arrays.asList(list)); The single remove operation is O(log n) in an n-element, heap-based PQ. This will disappear in the overall runtime because of the addAll time. In a naive implementation, this would be O(n log n) to add n-elements because each add is O(log n) in a heap-based priority queue. Technically the total runtime would be O(log(1)) + O(log(2)) + ... + O(log n) = O(log(n!)) which is O(n log n) as discussed in class (by Sterling's formula/approximation). So O(n log n) is acceptable as an answer --- In fact, it's possible to add n-elements to a heap-based priority queue in O(n) time, worst case. This is essentially because half the elements are leaves, they're all "heaps". Then to ensure that their parents are heaps requires constant time for each of their n/2 parents. Then for their parents, etc., so total time is 1 + n/2 + n/4 + .. which is O(n) This is because you re-heap only based on children, not all the way down. The Java PQ implementation does this, so it's actually O(n) time for the method pmax. Part F for(int i=0; i < k-1; i++) pq.remove(); return pq.remove(); ---- Problem C: Part A: falcon Part B: buzzard, eagle, harrier Part C: kestrel is right child of hawk condor is right child of buzzard ostrich is left child of owl Part D: Left subtree of hawk has four nodes, the number of binary search trees with four nodes is 14: the 4th Catalan number (first is 0-nodes) For right subtree there are 5 nodes, there are five values bigger than hawk: kite, vulture, osprey, owl, wren That's 42 trees. total # trees is 14*42 = 588 Part E: public void printLevel(TreeNode root, int level){ if (root == null) return; if (level == 0) System.out.println(root.info); printLevel(root.left,level-1); printLevel(root.right,level-1); } public int levelCount(TreeNode root, int level) { if (root == null) return 0; if (level == 0) return 1; return levelCount(root.left,level-1) + levelCount(root.right,level-1); } ===== Problem 4 Part A: 010 Part B: badfeed Part C: public void loadMap(TreeNode root, String path) { if (root.left == null && root.right == null) { myMap.put(root.myValue, path); } else { loadMap(root.left, path+"0"); loadMap(root.left, path+"1"); } } Part D private TreeNode helper(TreeNode root,int chunk, String path){ TreeNode current = root; for(int k=0; k < path.length(); k++){ // you fill in code here char ch = path.charAt(k); if (ch == '0'){ if (current.left == null) { current.left = new TreeNode(0,0,null,null); } current = current.left; } else { if (current.right == null) { current.right = new TreeNode(0,0,null,null); } current = current.right; } } current.myValue = chunk; return root; } ==== Problem 5: A: 10 / \ 6 19 \ 2 B: 7 / \ 12 8 C: average: T(n) = 2T(n/2) + O(n) ==> O(n log n) worst: T(n) = T(n-1) + O(n) ==> O(n^2) This is because getMax and getMin are both O(n) as given! Part D: Average case getMax is log(n), worst it's O(n), see problem problem 2 part B for an explanation. So new recurrences: average: T(n) = 2T(n/2) + O(log n) (you didn't have to solve this, it's solution is O(n)) worst case is the same as previous answer Part E: The code will work (typos from in-class fixed and null-references in getMax fixed, see the online copy of the test). isSearchTree checked recursively will "do the right thing" if the max value of the right subtree isn't all-the-way right.