CS302 Final Exam - December 3, 2020

This was a long exam, so you needed to be studied up, so you wouldn't linger on questions. Grades:


Explanations of your scores from Q17 to Q23

I have emailed you the explanation of your scores for these questions, because it's too much of a pain to put them on Canvas.


Questions

The exam was on Canvas, and I used question banks for a lot of questions, so I won't have the exact exam here, but I will show example questions when one was chosen from a bank.

Questions 1-6

These were Big-O questions, worth two points each. You got them in a random order. If the answer was O(X+Y), then you also received credit for O(Y). No credit for O(X).

Also, you'll see that they are all "regraded". That's because I had a canvas misunderstang which had them originally at 4 points each, and not the two that they should have been.


Questions 7-12

These were true-false questions on NP-completeness, worth 1 point each. They were given to you in a random order:


Questions 13 and 14

These were "identify the sorting algorithm" questions, randomly chosen from a question bank. Four points each. Here are two examples:

  1. Suppose I am sorting the following vector:
     69 33 22 77 56 3 79 55 25 14
    
    After two passes of my sorting algorithm, the vector is:
     22 33 69 77 56 3 79 55 25 14
    
    Which sorting algorithm am I using?

    Answer: Insertion sort: The first pass sorts the first two elements, and the second pass sorts the first three elements.

  2. Suppose I am sorting the following vector:
     73 61 20 84 81 37 57 88 77 1
    
    After two passes of my sorting algorithm, the vector is:
     1 20 61 84 81 37 57 88 77 73
    
    Which sorting algorithm am I using?

    Answer: Selection sort: The first pass swaps the minimum element (1) with the first element. The second pass swaps the second-minimum element (20) with the second element.


Question 15

Quicksort Pivot Selection - 4 points. Pulled from a bank. Here's an example:

Suppose I am sorting the following vector using quicksort:

 43 24 35 15 90 98 74 47 89 66 54
If I am using the median-of-three pivot selection algorithm, what is the pivot? Answer: the median of 43, 98 and 54: 54.


Question 16

Quicksort Partition - 8 points. Pulled from a bank. Here's an example:

You are sorting the following string using quicksort (those are lower-case L's, not ones):

l p g s v l f f f e b w l
If you use the first character as a pivot, what will the string be right before you make the first recursive call?

Answer: f l g b e f f l l v s w p. Remember, when you see an 'l', it needs to be swapped into the other set. You got 6 points if you forgot to swap the first element into place at the end (l l g b e f f f l v s w p). You got 5 points if you didn't swap the 'l' characters into the other set: (f b g e f l f l v s p w l). And you got 3 points if you didn't swap the 'l' characters, and you forgot to swap the first element: (l b g e f l f f v s p w l).

Canvas will show that this question has been "regraded'. That's because I originally forgot to set the "correct" answer. You received the correct number of points, but it wasn't showing the correct answer. The regrade fixed that.


Question 17

Minimum Spanning Tree - 8 points. Pulled from a bank. Here's an example:

The following list specifies the edges in an undirected, weighted graph with 10 nodes labeled A through J. In the answer box, please enter the edges in the minimum spanning tree of the graph. Specify an edge from X to Y as "XY" (but no quotes). You can separate your edges with spaces or put them on separate lines. Please don't put anything else in your answer except the edges.

Edge: AF -- weight  1
Edge: AJ -- weight  2
Edge: EH -- weight  4
Edge: FJ -- weight  5
Edge: EG -- weight  6
Edge: GH -- weight  8
Edge: EF -- weight  9
Edge: BI -- weight 10
Edge: CD -- weight 12
Edge: GJ -- weight 14
Edge: FH -- weight 15
Edge: AE -- weight 17
Edge: HJ -- weight 18
Edge: AG -- weight 20
Edge: EJ -- weight 21
Edge: CI -- weight 22
Edge: BD -- weight 23
Edge: FG -- weight 25
Edge: AH -- weight 26
Edge: BC -- weight 27
Edge: DI -- weight 29
Edge: DF -- weight 31
Edge: AI -- weight 32
Edge: DE -- weight 33
Edge: HI -- weight 34
Edge: CE -- weight 36
Edge: AC -- weight 37
Edge: AD -- weight 38
Edge: FI -- weight 39
Edge: EI -- weight 40
Answer: Since the edges are sorted, Kruskal's algorithm is straightforward. Here are the edges and the components. If a node is not listed, it's in its own component:
AF -- AF 
AJ -- AFJ
EH -- AFJ EH
FJ -- skip
EG -- AFJ EGH
GH -- skip
EF -- AEFGHJ
BI -- AEFGHJ BI
CD -- AEFGHJ BI CD
GJ -- skip
FH -- skip
AE -- skip
HJ -- skip
AG -- skip
EJ -- skip
CI -- AEFGHJ BCDI
BD -- skip
FG -- skip
AH -- skip
BC -- skip
DI -- skip
DF -- ABCDEFGHIJ -- we're done and and skip the rest.
So the answer is: AF AJ EH EG EF BI CD CI DF
.

Grading: I counted the number of "wrong" answers. This starts with the size of your answer minus 9 (minimum value = 0). To this, I added the number of correct answers that weren't in your answer. Perfect answer is 8 points. If there were any wrong, then you got 9 - wrong*2. Minimum score is zero.


Question 18

Topological Sort - 8 points. Pulled from a bank. Here's an example:

Below is a specification of a directed, unweighted graph using adjacency lists. In the answer box, please give a listing of the nodes in the order of a valid topological sort. There may be many valid answers -- you only have to give one valid answer. Please answer by simply listing the nodes. You don't need spaces or commas or newlines between the nodes, but it's ok to do to. In fact, if the browser lets you, I'd recommend cutting and pasting the adjacency lists into your answer and then working through it from there. But that's me.

Please don't put any other stray stuff like numbers or comments in your answer. Remember that you can resize your entry window if it makes your life easier.

A: D C
B: D H A
C:
D:
E: C
F: D B
G: H C B
H:
I: D H C A
J: A I G E F
Answer: Make yourself a vector of incoming edges, and then keep removing nodes with no incoming edges from the graph. I'll mark a node with x when it's done.
Incoming-Edges             Node-To-Remove
A B C D E F G H I J        
3 2 4 4 1 1 1 3 1 0              J
2 2 4 4 0 0 0 3 0 x              E (You can remove any of E, F, G, I)
2 2 3 4 x 0 0 3 0 x              F (You can remove any of F, G, I)
2 0 2 3 x x 0 2 0 x              G (You can remove any of B, G, I)
2 0 2 3 x x x 2 0 x              I (You can remove any of B, I)
1 0 1 2 x x x 1 x x              B
0 x 1 1 x x x 0 x x              A (You can remove any of A, H)
x x 0 0 x x x 0 x x              H (You can remove any of C, D)
x x 0 0 x x x x x x              C (You can remove any of C, D)
x x x 0 x x x x x x              D (You can remove any of C, D)
So a valid answer is JEFGIBAHCD. Any legal answer got full credit.

Grading: As above, I counted the number of "wrong" answers. This starts 10 minus the number of nodes that you gave. If you gave more than 10 nodes, then it started at zero. Then I performed the topological sorting calculation on your nodes, and whenever you gave an illegal node, I incremented the number of "wrong" answers. For example, suppose above you started with "A". That would be a wrong answer, because "A" has incoming edges. Regardless of whether your answer was right or wrong, I then deleted it from the tree.

Perfect answer is 8 points. If there were any wrong, then you got 9 - wrong*2. Minimum score is zero.


Questions 19-20

Network Flow - 6 points each. Pulled from a bank. Here's an example:

You are looking to find the max flow and min cut in the following graph:

After you have processed it, the final residual graph has the following edges:

In the box below, enter the maximum flow on the first line (just a number). And then enter the minimum cut on the next lines, one edge per line. No spaces; no punctuation. Use, for example, SA to represent the edge from S to A.

Answer: First do a DFS from S to identify the nodes that are reachable from S. Those are nodes S, B and D. The minimum cut is composed of the edges from those nodes to the others in the original graph: BA and CE. The maximum flow is equal to the sum of the weights of those two edges: 7+5 = 12.

Grading: Two points if you got an edge right. Two points if you got the flow right. Two points if you got the edges exactly right.


Question 21

Dijkstra's Algorithm - 7 points. Pulled from a bank. Here's an example: You are working with a directed, weighted graph with the following adjacency matrix:
      A  B  C  D  E  F  G  H
A |   0 67 57 44 59 11 42 36
B |  68  0 50 13  2 67 55 30
C |  53 62  0  3 49 34  5 23
D |  55 18 46  0 13 25 30 26
E |  27 57 23 69  0 42  8 55
F |  59 50 61 65 45  0 66 39
G |  44  2 66 34 44 61  0 60
H |  63 25 38 10 68 11 18  0
You are in the middle of doing a shortest path calculation with Dijkstra's algorithms. You are maintaining a vector of the best known shortest paths, and the multimap that is central to Dijkstra's algorithm. The values of these data structures are as follows:
         A   B   C   D   E   F   G   H
Best:   68   0  50  13   2  67  55  30

Multimap:

key:   2    val: E
key:  13    val: D
key:  30    val: H
key:  50    val: C
key:  55    val: G
key:  67    val: F
key:  68    val: A
Your job is to tell me what the state of these two data structures is after the next pass of Dijkstra's algorithm. What you should do is copy-and paste the two data structures above into your answer window, and then modify them to reflect the next pass of Dijkstra's algorithm. If the cut-and-paste doesn't have your answer be in a fixed-width font, set the "Paragraph" field of your entry box to "preformatted".

Answer: You are adding E to your "known" set of nodes. You remove it from the multimap, and then you process its edges to see if you improve any "best" paths to other nodes. E's row of the adjacency matrix is:

      A  B  C  D  E  F  G  H
E |  27 57 23 69  0 42  8 55
So the paths to all of these nodes by going through E are:
      A  B  C  D  E  F  G  H
E |  29 59 25 71  - 44 10 54
The paths to A, C, F and G are all improved. So the "Best" vector becomes:
         A   B   C   D   E   F   G   H
Best:   29   0  25  13   2  44  10  30
And the multimap becomes:
key:  10    val: G
key:  13    val: D
key:  25    val: C
key:  30    val: A
key:  30    val: H
key:  44    val: F

Grading: In the "best" vector, I subtracted your number of mismatches from 6 and added half of that to your score (it couldn't be negative). Then with the multimap, if yours was too big, I set w to be the difference between yours and the correct one. Otherwise w was zero. I added one to w for every correct entry in the multimap that was not in yours. I divided w by the size of the correct multimap, and multiplied that by four, and added that to your score.


Question 22

Programming BFS - 12 points:

The GF(64K) graph is an unweighted, directed graph, where every node is labeled by a number from 0 to 0xffff. Every node with a label of i has two outgoing edges:

  1. There is an edge to (i+1)%0x10000.
  2. If i is less than 0x8000, then there is an edge to i << 1; otherwise, there is an edge to (i << 1) ^ 0x1100b. (in C++, the carat is XOR). You'll note that in either case, the edge is to a number that is less than or equal to 0xffff.
The graph does have two multi-edges. (Node 1 has two edges to 2, and Node 0xf006 has two edges to 0xf007). It's not really important, but I mention it in case you noticed it and were confused.

Write a procedure with the following prototype:

int shortest_path(int from, int to);

You don't have to error check -- you will be guaranteed that from and to are numbers between 0 and 0xffff. Your program should return the length of the shortest path from from to to in the GF(64K) graph. You'll note that there is always a path between every pair of nodes, so you don't need to worry about from and to being disconnected.

Now, you can design this program as you'd like, but were this me, I'd implement this in one of two ways:

  1. Using a deque of ints and a vector of ints.
  2. Using a deque of ints and a map whose keys and vals are both ints.
I used the first way, and my code was 27 lines -- this does not have to be a killer program.

In your answer, set the "paragraph" field to "preformatted", which makes your code more readable. Also, you can resize that window so that you can see more than three lines.

Answer>: This is a BFS where you simply create the edges on the fly. You start by putting the "from" node onto a deque. And then you process the deque from the front until you process the "to" node. In the vector or map, you maintain the shortest known paths. Since there are only 64K entries, the vector doesn't take much space. Here's my solution:

#include <vector>
#include <deque>
#include <iostream>
using namespace std;

int shortest_path(int from , int to)
{
  vector <int> paths;     // This is where I store the best paths so far.
  deque <int> bfsq;       // This is my breadth-first search queue
  int eto;                // This is where I calculate the "to" node of edges.
  int i, n;

  /* Initialization: Set the best path length for each node to -1.  Then set the
     path length for the first node to 0, and put the first node on the queue. */

  paths.resize(1 << 16, -1);
  paths[from] = 0;
  bfsq.push_back(from);

  /* Process the queue. */

  while (!bfsq.empty()) {

    /* Remove the first node on the queue, and if it's the destination, we're done. */

    n = bfsq[0];
    bfsq.pop_front();
    if (n == to) return paths[n];

    /* Generate the edges -- the "to" node of the two edges is the variable eto. */

    for (i = 0; i < 2; i++) {
      switch (i) {
        case 0: eto = (n + 1) & 0xffff; break;
        case 1: eto = n << 1;  if (eto & 0x10000) eto ^= 0x1100b; break;
      }

      /* If the "to" node is not on the queue yet, put it there with the proper
         path length */

      if (paths[eto] == -1) {
        paths[eto] = paths[n] + 1;
        bfsq.push_back(eto);
      }
    }
  }

  /* This won't happen, but may as well keep the compiler quiet. */

  return -1;
}

int main()
{
  int from, to;

  cin >> from >> to;

  cout << shortest_path(from, to) << endl;
  return 0;
}

Grading: If you had the right structure for a BFS, you started with 12 points and had deductions. If you didn't do a BFS, you typically started with 6 points and had deductions. One deduction that was very frequent was "exponential blow up", which is what happens when you don't check to see if a node is already on the queue.


Question 23

Programming DP - 15 points:

The following is a definition for a B-String:

So, for example, here are some B-Strings:
[]
[()]
[[][]]
[([]({}))]
These are not B-strings:
[][]         -- You can only have two concatenated strings inside a {, [ or (
[()()()]     -- You can't have three strings inside a []
[[]]         -- If you have one string inside a [], it cannot start with [
Write a dynamic program (obviously, in C++) that reads n from standard input, and prints the total number of B-Strings that are n characters in length. You only need to go to "step 2" in the four steps of dynamic programming.

Some examples:

UNIX> echo 1 | a.out
0
UNIX> echo 2 | a.out
3
UNIX> echo 3 | a.out
0
UNIX> echo 4 | a.out
6                   # These are ([]), ({}), [()], [{}], {[]}, {()}
UNIX> echo 6 | a.out
39                  # 13 begin with [, 13 begin with {, 13 begin with (
UNIX> 

Please feel free to cut and paste the following to get you started:

#include <string>
#include <vector>
#include <iostream>
using namespace std;

class Bstring {
  public:
    vector < vector <long long> > Cache;
    long long bs(int len, int potential_starting_chars);
};

long long Bstring::bs(int len, int psc)
{
// Do base cases.
// Create the cache if you need to.
// Check the cache
// You have two cases to count: strings like { T }, and strings like { TS }.
}

int main()
{
  int len;
  Bstring b;

  cin >> len;
  cout << b.bs(len, 3) << endl;
  return 0;
}
Answer: This is a straightforward dynamic program that treats the two cases recursively. Please see the comments inline:

#include <vector>
#include <iostream>
using namespace std;

class Bstring {
  public:
    vector < vector <long long> > Cache;
    long long bs(int len, int potential_starting_chars);
};

/* My main recursive method is bs(len, psc), where psc is either 3 or 2,
   depending on whether there are 3 or 2 potential starting characters.
   It returns the number of bstrings of length len that has that many
   potential starting characters.
 
   On second thought -- this really makes the problem harder than it needs
   to be.  You can instead just have bs(len), and then if you only have two
   potential starting characters, you multiply the answer by 2/3.

   Still, this is the first answer that came to me.  My DP cache is indexed 
   on len and psc-2. */

long long Bstring::bs(int len, int psc)
{
  long long total;
  size_t i;

  /* If there's no cache, create it and set all of the entries to -1. */

  if (len > Cache.size()) {
    Cache.resize(len+1);
    for (i = 0; i < Cache.size(); i++) Cache[i].resize(2, -1);
  }

  /* Handle the base cases.  I handle two explicitly, because the recursion
     won't work with two, since bs(0,x) returns 0. */

  if (len <= 0) return 0;
  if (len == 2) return psc;
  if (len % 2 == 1) return 0;

  /* Check the cache and return it if it's there. */

  if (Cache[len][psc-2] != -1) return Cache[len][psc];

  /* Do the recursive calculation. */
  total = 0;

  total += (psc * bs(len-2, 2));      // This handles the {T}/[T]/(T) case.

  for (i = 2; i < len-2; i += 2) {    // This handles the {TS}/[TS](TS) case.
   total += (psc * bs(i, 3) * bs(len-i-2, 3));
  }

  /* Put the answer into the cache and return it. */

  Cache[len][psc-2] = total;
  return total;
}

int main()
{
  int len;
  Bstring b;

  cin >> len;
  cout << b.bs(len, 3) << endl;
  return 0;
}

Grading: You typically started with 8 points for having the base cases right, cache management right and some recursion. There were deductions when you got the base cases incorrect, or you didn't initialize the cache.

If you calculated "{T}" correctly, but not "{TS}", you received 5 more points.

The rest of the points were deductions or additions, and there are too many to list.