+ All Categories
Home > Documents > 36 CS106X Midterm

36 CS106X Midterm

Date post: 06-Oct-2015
Category:
Upload: johndoe1204
View: 214 times
Download: 0 times
Share this document with a friend
Description:
Midterm Solutions
23
 CS106X Handout 36 Autumn 2011 November 30 th , 2011 CS106X Midterm Examination This is an open-note, open-reader exam. You can refer to any course handouts, the course reader, handwritten lecture notes, and print outs of any code relevant to a CS106X assignment. You may not use any laptops, cell phones, or handheld devices of any sort. Those taking the exam remotely should feel free to telephone me at 415-205-2242 while taking the exam. Completed exams should be scanned and emailed to [email protected], or faxed to 650-723- 6092. Hold on to the origi nal until you’ve gotte n your graded exam back. Good luck! Section Leader: _____________________ Last Name: First Name: _____________________ I accept the lette r and spirit of t he honor code. I’ve neither given nor received unauthorized ai d on this exam. I pledge to write more neatly than I ever have in my entire life. (signed) Score Grader 1. Phineas and Ferb (9) ______ ______ 2. append Two Ways (6) ______ ______ 3. Ranked Choice Voting (15) ______ ______ 4. Treaps and Tries (15) ______ ______ 5. The IntegerSet (20) ______ ______ Total (65) ______ ______
Transcript
  • CS106X Handout 36

    Autumn 2011 November 30th, 2011

    CS106X Midterm Examination

    This is an open-note, open-reader exam. You can refer to any course handouts, the course reader,

    handwritten lecture notes, and printouts of any code relevant to a CS106X assignment. You may not use any

    laptops, cell phones, or handheld devices of any sort.

    Those taking the exam remotely should feel free to telephone me at 415-205-2242 while taking the exam.

    Completed exams should be scanned and emailed to [email protected], or faxed to 650-723-6092. Hold on to the original until youve gotten your graded exam back.

    Good luck!

    Section Leader: _____________________

    Last Name: _____________________

    First Name: _____________________

    I accept the letter and spirit of the honor code. Ive neither given nor received unauthorized aid on this exam. I

    pledge to write more neatly than I ever have in my entire life.

    (signed) __________________________________________________________

    Score Grader

    1. Phineas and Ferb (9) ______ ______

    2. append Two Ways (6) ______ ______ 3. Ranked Choice Voting (15) ______ ______

    4. Treaps and Tries (15) ______ ______

    5. The IntegerSet (20) ______ ______ Total (65) ______ ______

  • 2

    Problem 1: Phineas and Ferb [9 points]

    Analyze the following code snippet, starting with a call to mobilemammal, and draw the state of memory at the point indicated. Be sure to differentiate between stack and heap memory, note values that have not been initialized, and identify if and where memory has been orphaned. Draw out your final diagram in the lower half of this page.

    struct squirrel { int spa; squirrel *duckymomo[3]; }; void mobilemammal() { squirrel phineas[2]; phineas[1].spa = 100; phineas->spa = 777; squirrel **ferb = &(phineas[0].duckymomo[1]); ferb[0] = ferb[1] = new squirrel; ferb[1] = NULL; squirrel *pants = *ferb; ferb = &(ferb[1]); phineas[0].duckymomo[0] = &(phineas[1]); phineas[1].duckymomo[0] = phineas[0].duckymomo[0]; phineas[1].duckymomo[1] = ferb[0]; phineas[1].duckymomo[2] = pants; *pants = phineas[1];

    Draw the state of memory just before mobilemammal returns. }

    In the space below, draw the state of memory just before mobilemammal returns.

  • 3

    Problem 2: append Two Ways [6 points] Assume the following node definition:

    struct node { int value; node *next; };

    and consider the following two recursive functions, which differ only by the placement of a single &.

    Presumably, each exists with the intent of tacking the node addressed by tail to the end of the full list addressed by list. append2 works, and append1 doesnt. Assume the following illustration captures exactly how variables mylist and mytail [each of type node *] have been initialized just prior to a call to one of the two append functions above:

    Over the next two pages, youre going to draw the full state of memory just before list = tail executes to detail exactly how it is that append1(mylist, mytail) fails and append2(mylist, mytail) succeeds.

    void append1(node *list, node *tail) { if (list == NULL) { list = tail; } else { append1(list->next, tail); } }

    void append2(node *& list, node *tail) { if (list == NULL) { list = tail; } else { append2(list->next, tail); } }

    mylist

    mytail

    1 3 6

    10

  • 4

    a. [3 points] Draw the state of memory as a primary call to append1(mylist, mytail) bottoms out, just before its list = tail statement executes. Youll want to draw all of the parameters for all four function calls, being clear what each of the parameters associated with each of the recursive calls contains.

    mylist

    mytail

    1 3 6

    10

  • 5

    b. [3 points] Do the same thing again, this time for a primary call to append2(mylist, mytail) [again, just before its list = tail statement executes]. Youll again want to draw all of the parameters for all function calls, being clear what each of the parameters associated with each of the recursive calls contains.

    mylist

    mytail

    1 3 6

    10

  • 6

    Problem 3: Ranked Choice Voting [15 points]

    Ranked choice votingalso knows as instant runoff votingis used in San Francisco for mayoral elections. Rather than voting for a single candidate, those casting ballots vote for up to three candidates, ranking them 1, 2, and 3 (or, in computer science speak: 0, 1, and 2.) Assume you are given the following to represent a single ballot:

    struct ballot { Vector votes; // of size 1, 2, or 3; sorted by preference ballot *next; ballot *prev; };

    and that the collection of all ballots is represented as a doubly linked list. The first five of what in practice would be tens of thousands of ballots in a real San Francisco election might look like this:

    Initially, only first place votes matter, and if a single candidate gets the majority of all first place votes, then that candidate wins. Often, no one gets a majority of all first place votes [There were, for instance, 16 official candidates in San Franciscos mayoral election on November 8th, and Ed Lee, who eventually won, only got only 31% of the first choice votes.] In that case, the candidate with the least number of first place votes is eliminated by effectively removing that candidate from all ballots everywhere and promoting all second and third place votes to be first and second place votes to close any gaps. If, after an analysis of the ballots list above its determined that Phil Ting received the smallest number of first place votes, the ballots list would be updated to look like this:

    The first two ballots were left alone, but the next three were updated to reflect Tings elimination. Note the one node that included a standalone vote for Phil Ting was removed from the list, since

    0

    1

    2 Dufty

    Lee

    Herrera

    0

    1 Dufty

    Lee

    0 Ting

    0

    1

    2 Herrera

    Ting

    Lee

    0

    1 Lee

    Ting

    0

    1

    2 Ting

    Dufty

    Lee

    ballots

    0

    1

    2 Dufty

    Lee

    Herrera

    0

    1 Dufty

    Lee

    0 0

    1 Herrera

    Lee

    0 Lee

    0

    1

    2 Ting

    Dufty

    Lee

    ballots

  • 7

    it no longer contains any valid votes. The two other impacted nodes each saw candidates Dennis Herrera and Ed Lee advance from third and second to second and first. The process is repeated over and over again until it leaves one candidate with a majority of rank-one votes. [On November 8th, this very process was applied 12 times before Ed Lee prevailed with 61% of all remaining first choice votes.] a. [7 points] Implement the identifyLeastPopular function that, given a doubly linked list

    of ballots called ballots, returns the name of the candidate receiving the smallest number of first-choice votes. You may assume all ballots include at least one vote, that no ballots ever include two votes for the same candidate, and that if two or more candidates are tied for least popular [maybe Phil Ting and Bevan Dufty, for instance, each get only two first-choice votes and no one got only one], then any one of them can be returned. Use this and the next page to present your implementation.

    string identifyLeastPopular(ballot *ballots) {

  • 8

    Problem 3: Ranked Choice Voting [continued]

  • 9

    b. [8 points] Now implement the eliminateLeastPopular function which, given a doubly linked list of ballots and the name of the candidate to be eliminated, removes all traces of the candidate from the list of ballots, removing and properly disposing of any ballots depleted of all votes. Ensure that you properly handle the case where the first or last ballot (or both) is removed.

    Use this and the next page for your implementation. void eliminateLeastPopular(ballot *& ballots, string name) {

  • 10

    Problem 3: Ranked Choice Voting [continued]

  • 11

    Problem 4: Treaps and Tries [15 points]

    a. [7 points] A treap is a binary tree structure where each node, in addition to its two child pointers, maintains two fieldsa string called value, and a second fieldof type intcalled count.

    struct node { string value; int count; node *left, *right; };

    The string values can be anything at all, but the counts are constrained to be non-negative [and in fact track the number of times the node has been accessed via a treapContains call. More on that in a few.] The treap organizes its nodes so its values respect the binary search tree property, but the counts respect a max-heap property [the idea of which should be a natural extension of your work with the heap in Assignment 4, adapted to support extractMax instead of extractMin.] When a value is inserted, it takes its place along the fringe of the tree [while respecting the binary search tree property], and is given a count of 0. Because the 0 is the smallest allowed count, no max-heap property violation could possibly be introduced by the insertion.] The treapInsert code I provide here is an obvious adaptation of the binary-tree-insertion code I presented in lecture:

    void treapInsert(node *& root, string value) { node **rootp = &root; while (*rootp != NULL) { int cmp = (*rootp)->value.compare(value); if (cmp == 0) return; // already present, choose to do nothing if (cmp > 0) rootp = &((*rootp)->left); else rootp = &((*rootp)->right); } node n = {value, 0, NULL, NULL}; // on the fly initialization of a node node *np = new node(n); // dynamically allocated clone of n *rootp = np; // hang persistent clone in proper spot }

    The treapContains code will be your responsibility, and leverages the contains code I wrote in lecture for binary search trees. One key difference is that, whenever contains succeeds in finding a particular string, it increments the companion count field by one as a side effect. Doing so, in essence, notes that the sought value is more popular than previously thoughtperhaps that more searches for it are coming very soonand that maybe it should be bubbled up the tree toward the root so that future searches for it take less time. Now, the act of incrementing may very well have introduced a max-heap violation if the impacted nodes count field just surpassed that of its parent (and possibly grandparents and

  • 12

    great-grandparents). The impacted node would need to be rotated up the treap until the max-heap property is restored. Given a reference to the root of the tree and the string key of interest, present your implementation of treapContains, which not only returns true if and only if the supplied key is present, but also increments the companion count of the key, if present, and bubbles the key-count pair up toward the root to the extent necessary so that everything continues to respect the binary search and max-heap properties. [Handouts 28 and 28S presented code to support left and right rotation, and those functionscompatible it turns out with the node definition were using for this problemare included at the end of the exam and can be used as is.] Use this and the next page for your implementation.

    bool treapContains(node *&root, string key) {

  • 13

    Problem 4: Treaps and Tries [continued]

  • 14

    b. [8 points] Recall the primary data structure we used to build a trie is defined as:

    struct node { bool isWord; node *children[26]; };

    Implement the mergeTries function, which recursively constructs and returns a third trie to be the logical union of the two incoming onesthat is, a word is present in the union if and only if its present in one or both of the incoming ones. Your implementation should synthesize the union out of the nodes hanging from one and two, cannibalizing and otherwise destroying one and two in the process and properly disposing of any redundant memory. In particular, your implementation cannot call new anywhere, and should run in time that is linear in the number of nodes making up the two incoming tries. Use this page and the next page for your implementation. node *mergeTries(node *one, node *two) {

  • 15

    Problem 4: Treaps and Tries [continued]

  • 16

    Problem 5: IntegerSet [20 points] The CS106 Vector is the simplest data structure that one could use to back an int-specific IntegerSet. But using a Vector means that either insertion is slow (because youre careful to keep all of the elements in sorted order) or that search is slow (because you wanted insertion to be fast and abandoned the sorted-order requirement.) If you want both insertion and search to be lickety-split, youd normally settle on a balanced binary search tree (100% guaranteed to be fast) or a hash table (which makes that guarantee with high probability, provided your hash function is good.) The lazy, pointer-phobic side of us wants to just use the Vector, but to improve the insertion time without sacrificing the search efficiency all that much. We can improve on insertion time without the headaches of binary search trees and hash tables by maintaining several sorted arrays behind the scenes. Specifically, we wish to support contains and add on a set of n elements. The internal representation of the IntegerSet maintains a Vector of dynamically allocated integer arrays. Each of these arrays is sorted from low to high, but the size of each varies, depending on where it is in the Vector. In particular, the size of the array at index k is 2k. Because each arrays size is some unique positive power of two, different subsets of the vectors can be turned on and off so that the proper number of integers can be stored. The manner in which elements are stored is best illustrated by example, so lets read through one. Suppose our IntegerSet instance stores 10 elements: 1, 3, 6, 7, 8, 11, 12, 14, 15, and 16. Because the binary representation of 10 is 1010, we expect some of these 10 elements to be housed in a sorted array of size 23 = 8 and the rest of the elements to be housed in a sorted array of size 21 = 2. If you think about it, youll understand thats it no coincidence that the collective size of these two arrays is 10each is sized according to the contribution the corresponding bit makes to the overall element count. 1000 is 8, 0010 is 2, so 1010 is 10. This binary digit decomposition is very similar to that we saw with binomial heaps in Assignment 4. How are these ten elements stored behind the scenes? There are several possibilities, but lets assume it looks like this:

    6 16

    1 3 7 8 11 12 14 15

    7 11 12 15

    16

    allocated, but inactive

    allocated, but inactive

    the IntegerSet (which contains an integer count and a Vector)

    10

  • 17

    If we continue on to insert the number 10, we just activate the array in the 0th slot and put the 10 right there. The number of stored elements increases to 11 = 1011, and because the least significant bit is now 1, we know that the 0th array of integers is now being used. Our IntegerSet now looks like this:

    If you think about it, every other insertion sees the set size go from even to odd, so insertion is quick and easy about 50% of the time. Thats why insertion is so fast. But inserting the 12th element requires a series of mergesmerges that render some smaller arrays to be inactive (although still allocated) and one large array to be active. Because the binary representation of 12 is 1100, we expect the arrays of size 8 and 4 to be active, and the arrays of size 2 and 1 to be inactive. The merge process is managed by the merge function, which Ive supplied on the last page of the exam. This is what our IntegerSet would look like after we insert the 12th element (assuming that element is 2):

    6 16

    1 3 7 8 11 12 14 15

    7 11 12 15

    10

    allocated, but inactive

    11

    6 16

    1 3 7 8 11 12 14 15

    2 6 10 16

    10

    allocated, but inactive

    12

    allocated, but inactive

  • 18

    Insertion of three more elements (say 4, 9 and 13) would be quick: Two of them would immediately drops into the 0th array, and just one of them would require a merge. If these three elements were inserted in that order, then our structure would look as follows: Occasionally, an insertion brings the total element count to a perfect power of 2. In those cases, there are a maximum number of merges and the introduction of a new array to the structure. For if at this point we insert the number 5, the 5 is merged with the 13, which is then merged with the 4 and 9, and so on, until the behind-the-scenes state looks like:

    4 9

    1 3 7 8 11 12 14 15

    2 6 10 16

    13

    15

    4 9

    1 3 7 8 11 12 14 15

    2 6 10 16

    13

    16

    1 2 3 4 5 6 7 8

    9 through 16 are off the page

  • 19

    Your job: Implement the interesting parts of the IntegerSet. You neednt worry about constructors or destructors, but you do need to make sure that contains and add work according to specification. Heres the IntegerSet interface:

    class IntegerSet { public: IntegerSet (); ~IntegerSet (); void add(int value); bool contains(int value); private: Vector arrays; int count; };

    Your job is to implement the add method (using the merge function provided on the last page of the exam) and the contains method (using the bsearch function provided on the last page of the exam). You have the next several pages to write your code.

  • 20

    a. [14 points] Implement the add method using this and the next page. The implementation details have been drawn out for you already, but you should properly manage your dynamically allocated memory while coding it up. In particular, you shouldnt bother freeing the arrays that become inactive, since theyll likely become active again at some point. You will occasionally need to dynamically allocate a new array to add on the Vector every once in a while, so be sure to do that as needed.

    /** * Method: IntegerSet::add * ----------------------- * Inserts the specified value into the set. * * Note that nothing gets returned. We just trust that the value * is inserted and that itll turn up if we ever look for it. Dont * worry about duplicate elements. Just assume they never happen. */ void IntegerSet::add(int value) {

  • 21

    Problem 5: IntegerSet [20 points]

  • 22

    b. [6 points] Now implement the contains method, which calls bsearch [supplied on the last page of the exam] on just the active arrays [in any order] and returns true if and only if it confirms the supplied value is present.

    /** * Method: IntegerSet::contains * ---------------------------- * Searches for the specified value and true if and only if the supplied * value is present. */ bool IntegerSet::contains(int value) {

  • 23

    Left and Right Rotation Code void rotateLeft(node **parentp) {

    node *parent = *parentp; node *rightChild = parent->right; parent->right = rightChild->left; rightChild->left = parent;

    *parentp = rightChild; } void rotateRight(node **parentp) {

    node *parent = *parentp; node *leftChild = parent->left; parent->left = leftChild->right; leftChild->right = parent;

    *parentp = leftChild; }

    Merging Sorted Integer Arrays // assume target is of length m + n, one is of length m, two is of length n // further assume one and two are sorted from low to high void merge(int target[], int one[], int m, int two[], int n) { int i = 0, j = 0; while (i < m && j < n) { if (one[i] < two[j]) { target[i + j] = one[i]; i++; } else { target[i + j] = two[j]; j++; } } while (i < m) { target[i + j] = one[i]; i++; } while (j < n) { target[i + j] = two[j]; j++; } }

    Integer Array Binary Search // assume array is sorted from low to high, and is of length n // assume were interested in whether or not key is present int bsearch(int key, int array[], int n) { int lh = 0; int rh = n 1; while (lh


Recommended