Sei sulla pagina 1di 23

CS106X

Handout 36
November 30th, 2011

Autumn 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 jerry@cs.stanford.edu, or faxed to 650-7236092. 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 &.
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);
}
}

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:
mylist

mytail

10

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.

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

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

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<string> 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:
2

Dufty

Lee

Dufty

Herrera

Lee

Ting

Herrera

Ting

Lee

Lee

Ting

ballots

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:
2

Dufty

Lee

Dufty

Herrera

Lee

Herrera

Lee

1
0

Lee

ballots

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

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 rankone 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 int
called 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 maxheap 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 functions
compatible 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<int> is the simplest data structure that one could use to back an intspecific IntegerSet. But using a Vector<int> 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:

the IntegerSet (which contains an


integer count and a Vector<int *>)

11

12

15

16

16

10

11

12

14

15

allocated, but inactive

allocated, but inactive

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:

11

12

15

16

11

12

14

15

allocated, but inactive

10

11
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):

10

16

16

10

12

11

12

allocated, but inactive

allocated, but inactive

14

15

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:

11

10

16

12

14

15

13

15
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:
9 through 16 are off the page

11

12

14

15

10

16

13

16

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<int *> 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<int *>
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 <= rh) {
int mid = (lh + rh)/2;
if (array[mid] == key) return mid;
if (array[mid] < key) lh = mid + 1;
else rh = mid 1;
}
return -1;
}

Potrebbero piacerti anche