### Question 1

The easiest thing to do on these was to convert the vector to a binary tree, using the scratch sheets, and then perform the action on the tree, percolating up for Push(), and percolating down for Pop(). Then convert back to a vector. Here is each answer in pictoral form. You can click on each picture to blow it up.

Two points per part. You can click on the histograms to blow them up.

### Question 2

2A: Since 4's height is greater than 20's, it will be the parent, so Union(20,4), returns 4. Now, Find(17) will return 4, and Find(18) returns 11. The only part of links and ranks that changes is links[20] which becomes 4:

 ```Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 4 -1 -1 11 -1 11 4 4 12 -1 11 -1 4 -1 4 11 1 20 10 13 -1 4 13 13 Ranks: 1 2 1 1 3 1 1 1 1 1 2 3 2 2 1 1 1 1 1 1 2 1 1 1 Union(20,4) = 4 Find(17) = 4 Find(18) = 11 Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 4 Ranks: ```

2B: Since 7's size is greater than 22's, it will be the parent, so Union(7,22), returns 7. Links[22] will become 7 and Ranks[7] will become 8. The two Find() commands return 10 and 7 respectively.

 ```Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 21 21 7 -1 10 3 15 -1 21 21 -1 4 7 7 10 10 22 -1 2 3 -1 -1 -1 22 Ranks: 1 1 2 3 2 1 1 5 1 1 6 1 1 1 1 2 1 1 1 1 1 5 3 1 Union(7,22) = 7 Find(11) = 10 Find(23) = 7 Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 7 Ranks: 8 ```

2C: Now 7's rank is greater than 14's, so it becomes 14's parent. That changes links[14] to 7. The Find(10) command will now return 7, and because of path compression, links[10] is set to 7. Find(16) returns 19, and again because of path compression, links[16] is set to 19:

 ```Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 19 7 21 20 19 7 9 -1 5 -1 14 17 1 21 -1 7 0 -1 7 -1 7 -1 17 -1 Ranks: 2 2 1 1 1 2 1 3 1 2 1 1 1 1 2 1 1 2 1 3 2 2 1 1 Union(14,7) = 7 Find(10) = 7 Find(16) = 19 Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 7 7 19 Ranks: ```

2D: For the last one, the two sets have the same rank, so the tie is broken in favor of the higher set id: 22. Ranks[22] is incremented, because Union by Rank works like Union by Height, even though the ranks do not strictly represent heights. The Unions() operations also sets links[14] to 22. Find(13) now returns 22, and because of path compression, links[13] and links[20] are both set to 22. Find(1) also returns 22 and sets links[1] to 22.

 ```Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 22 0 15 5 22 -1 14 15 -1 8 11 -1 16 20 -1 -1 5 7 0 -1 14 7 -1 22 Ranks: 2 1 1 1 1 3 1 2 2 1 1 2 1 1 3 3 2 1 1 1 2 1 3 1 Union(14,22) = 22 Find(13) = 22 Find(1) = 22 Elts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Links: 22 22 22 22 Ranks: 4 ```

The Union()'s are worth 0.66 points, and the Find()'s are worth 0.67. Each correct setting of links is worth 0.72 and each correct setting of ranks is worth 0.76.

On part D, if you made 14 the parent instead of 22, I took two points off by giving you zero for the unions and finds. However, if you made the links equal 14, and 14's rank equal 4, you still got full credit for that part.

### Question 3:

The answers to all of these are in the lecture notes. Maps and multisets are equivalent to balanced binary trees.

 A: Sorting a vector using insertion sort. O(n2) B: Sorting a vector using heap sort. O(n log n) C: Calling Push() on a heap. O(log n) D: Creating a heap from a random vector. O(n) E: Calling Pop() on a heap. O(log n) F: Printing all elements of a map in order. O(n) G: Printing all subsets of a set. O(2n) H: Calling Union() on a disjoint set. O(1) I: Deleting an element from a map. O(log n) J: Printing all pairs of elements in a vector of integers. O(n2) K: Inserting an element into a map. O(log n) L: Sorting a vector using selection sort. O(n2) M: Sorting a vector using bubble sort. O(n2) N: Calling Find() on a disjoint set. O(α(n)) O: Sorting a vector using STL multisets. O(n log n) P: Enumerating all 2-disk failures in an n-disk system. O(n2)

Each question is worth a point.

### Question 4:

These are best to work by process of elimination, starting with the second line (1-indexing lines). With bubble sort, the largest element will end up as the last. With heap sort, the largest element will be the first. With selection and insertion-with-sentinel, the smallest element will be first. After that, you can narrow it down.
• Output A: The largest element is first on line 2. That eliminates everything but heap sort and insertion-no-sentinel. However, were this insertion-no-sentinel, then line 2 would start "6.92 7.30." This has to be heap sort.

• Output B: Here I said that the answers would be unique, and then I lied. My bad, but that means you get credit for either correct answer. This is insertion sort, with or without a sentinel. Either answer is fine.

• Output C: The smallest element is first at line 2, so this is either selection or insertion-with-sentienl. On line three, the 2.57 has been moved from position 6 to position 1. This has to be selection sort.

• Output D: The largest element is last on line 2. That implies bubble sort. Eyeballing lines 3 and 4, you can confirm bubble sort.

• Output E: On line 2, the smallest element isn't first, and the largest element isn't first or last. That eliminates everything but insertion-no-sentinel. Eyeballing the next few lines confirms this.

• Output F: As with output A, the largest element is moved to the beginning on line 2. That means heap sort or insertion-no-sentinel. However, were this insertion-no-sentinel, then line 2 would start with "8.37 1.15." This has to be heap sort.

• Output G: On line 2, the smallest element has been moved from position 2 to position 1. That could only mean selection sort or insertion-with-sentinel. On line two, the element in position 1 is not the second-smallest, which rules out selection sort. Eyeballing the next few lines confirms insertion-with-sentinel.

• Output H: The smallest element is first at line 2, so this is either selection or insertion-with-sentienl. On line three, the 2.59 has been moved from position 4 to position 1. This has to be selection sort.

Two points per part. Half credit for saying the wrong insertion sort on E and G.

### Question 5:

This turned out to be the hardest question on the test, because the answer was not straight from lecture notes and lab, but instead required you to reason about the enumeration techniques that you've learned to see how they apply. I can see two ways of solving this. The first is to realize that this is an enumeration of 9-bit binary numbers, mapping 1's to 'A' and 0's to 'B'. For each such number, there are ten ABC-10 strings with one 'C' in each of the ten potential positions. Here's code for that solution, in q5.cpp:

 ```#include #include using namespace std; main() { int i, j, k; string s; s.resize(10, ' '); for (i = 0; i < (1 << 9) ; i++) { for (j = 0; j < 9; j++) { if (i & (1 << j)) { s[j] = 'A'; } else { s[j] = 'B'; } } for (j = 0; j < 10; j++) { for (k = 0; k < j; k++) cout << s[k]; cout << 'C'; for (; k < 9; k++) cout << s[k]; cout << endl; } } } ```

The second solution is a "think less, but let recursion do the work" solution. You write a recursive procedure fill_next_digit(int index, int c_ok, string &s). What it does fill in the next digit with an 'A', a 'B', and a 'C' if c_ok is true. After each of these, it calls itself recursively to fill in the next digit. When index equals 10, it prints the string if c_ok is false (in other words, it doesn't print strings that don't have any 'C's). This one is in q5-recursive.cpp:

 ```#include #include using namespace std; void fill_in(int index, int c_ok, string &s) { if (index == 10) { if (!c_ok) cout << s << endl; return; } s[index] = 'A'; fill_in(index+1, c_ok, s); s[index] = 'B'; fill_in(index+1, c_ok, s); if (c_ok) { s[index] = 'C'; fill_in(index+1, 0, s); } } main() { int i, j, k; string s; s.resize(10, ' '); fill_in(0, 1, s); } ```

You could, of course, use a class so that you don't have to pass around the reference parameter.

Less good solutions: Some of you enumerated all 10-character strings with just A's and B's, and then for each of those, you printed out the ten strings that you get by replacing each character with 'C'. For that, you received a 2-point deduction, because you are generating a lot of duplicate strings.

Some of you inserted the above strings into a set and then traversed the set at the end. That's a nice way of cleaning up the solution, and you only lost a point for using an O(n log n) solution (where n is 2digits)) when an O(n) solution is available. I have that solution in q5-set.cpp.

Another approach was even less good was to enumerate all 310 ten-digit ABC-10 strings, and then only to print out those with exactly one C. That lost two points, because it is doing 115 times more work than it should (generating 310 strings and running through them to count C's, rather than generating 512 strings, and from them printing 10 strings each). I have that solution in q5-enum-3.cpp.

12 points. This grading is more subjective -- basically, I judge the reasonableness of your solution.

### Question 6

This is a nuts and bolts map/set problem. You need to read the input, create the criminals name and alias, then look up the criminal in a map, and insert the alias into a set. If you use the associative array way of inserting into a map, the code is very clean, not requiring any find() operations. My solution is in q6.cpp:

 ```#include #include #include #include using namespace std; main() { map > M; set ::iterator sit; string name, s, alias; while (cin >> name) { do { cin >> s; if (s != "XXX") { name += " "; name += s; } } while (s != "XXX"); cin >> alias; M[name].insert(alias); cout << name << ":"; for (sit = M[name].begin(); sit != M[name].end(); sit++) { cout << " " << *sit; } cout << endl; } } ```