Spring 2014 CS140 Final Exam - Answers and Grading

James S. Plank

Question 1 - Big O - 10 Points

This question asked you to memorize the definition of Big-O, and apply it to two concrete functions. If you're studying from this exam, what do you study for this? You make sure you've memorized the definition of Big-O, and then you know how to match T(n) and f(n) to a Big-O equation.

Let's recall the definition of Big-O from the lecture notes:

T(n) = O(f(n)) if there exists a constant c such that c*f(n) >= T(n).

Here, f(n) = n3 and T(n) = 5n3 + 500 log2 (n).

And recall how we define "greater than or equal" with functions:

f(n) is greater than or equal to g(n) if there is a value x0 so that for all x >= x0:

f(x) >= g(x)

Put them together, and you get I: For all n ≥ x0, cn3 ≥ 5n3 + 500 log2 (n).

Now, given that, I'd say choose the largest c and x0, and you're safe. That's answer J: c = 10, x0 = 1024.

We can prove it though -- for these two constants, you have:

10 (1024)3 = 10 (210)3 = 10 (230).

5 (1024)3 + log2(1024) = 5 (230) + 5,000.

It's pretty clear that the top number is bigger than the bottom, and the function will be growing faster.

As it turns out, none of the other sets of constants work:


Five points for Part A and Part B. I was very liberal with partial credit:

Question 2 - Linked Lists - 16 points

The point of this question was to test your understanding of how linked lists are implemented. There are detailed lecture notes for this, plus you implemented this exact data structure in lab 8, so my hope was that you would be prepared. How do you study for this? By reading through the lecture notes and making sure you understand them.

Part A: At the point of the first cout call, here is the state of the linked list:


The next two statements put the string "X" into the sentinel, and then corrupt p->flink so that it points to x->flink = p:


Part B: At the end of main(), the destructor is called on b. That's where the seg fault occurs. Specifially, the destructor tries to call delete on every node in the list, but when it gets to p, it follows p->flink and ends up deleting p twice. On a good day, that's a seg fault.

Some of you said stuff like "you can't access the sentinel because it's protected." That's a true statement in that my code cannot say "b.sentinel." However, if my code is passed a pointer to the sentinel (like p->blink), then my code is free to use it.


Question 3 - AVL Trees - 15 points

I solved these by putting the heights in the triangles, and then calculating the heights of the higher nodes. When I spotted an imbalance, I figured out whether the imbalance was a zig-zig or a zig-zag, and then did the right rotations:



3 points per scenario. Once again, I was liberal with extra credit:

Question 4 - Code Analysis - 16 points

The key here is to figure out the big-O complexity of the procedure, and then scale the time accordingly. The study material for this was the Big-O lecture notes, the recursion lecture notes, plus the myriad times in class that we explored how the running time of programs scaled when we doubled the size of input, or increased it by a factor of ten.

One sanity check for yourselves: if I'm doubling n, and there's a loop that goes from 0 to n-1, then unless there's something really crazy, the running time of the program is going to increase by at least a factor of two. There was nothing crazy here, so the answers of 10 and 11 seconds couldn't apply to any of the programs.


Question 5 - Sets and Maps - 15 points

I'll let you inhabit my perspective, and read a hand-written answer (mine - click on it to blow it up):

This is the proper way to solve this problem -- use a map, whose key is the last name, and whose val is a set of first names. It can be a pointer to a set (in fact, that's better, I think, than my answer above). When you're done creating the map, then you traverse it and maintain the last-name with the greatest set size. If you encounter a last-name whose set size is equal to the greatest, ignore it, because the last name is lexicographically greater than the current "best" one. When you're done, print out all the first names that correspond to the last name.

There were some common solutions that y'all gave that were not the way to solve the problem. I've grouped them into three categories:

STRING/INT MAPS: Quite a few of you tried a structure like the following:

This approach does not work, because you have to keep the association of certain first names with certain last names. Consider the following example:

{ "John Smith", "John Doe", "Binky Taylor", "Kathy Doe" }

The approach above will do the following:

At this point, I don't know what you're going to do with the set to make it right. The problem is that you've lost the association of first and last names.

STRING/STRING MAPS: Quite a few of you tried a map whose keys are last names and whose vals are first names. That won't work, because it only holds one first name per last name. Some of you added a map keyed on first name with vals as last names. That won't work for the same reason.

MULTIMAPS: Some of you tried to insert last-name/first-name pairs into multimaps, and then to cull duplicates while traversing the multimaps. That approach can work, but you need to be super-careful traversing the multimap. I was not leanient on grading this approach, because the extra care required to make it work is the reason why it is a poor approach.