CS 1501 Previous Midterm Exam Solutions

1)      Fill in the Blanks (20 points -- 2 points each). Complete the statements below with the MOST APPROPRIATE words/phrases.

a)         If a program's run-time can be modeled by the function   10N3 + N2(8N + 2N2), the program's Theta runtime growth rate is ____Theta(N4)__________.

b)         An algorithm that is known to run in time Theta(N2) requires 8 seconds to run on a problem of size K.  How long will the algorithm take to run on a problem of size 2K? _____32 seconds_______. 

c)         A _____digital search tree___________ is similar to a binary search tree, but branches to the left or right are made based on the current bit in the key (0 or 1).

d)         Assume that I have 256 32-bit keys in a multiway trie in which 4 bits are considered at a time.  The worst case height of this tree is _________8_________ and the average case height is ________2___________.

e)         The InsertionSort algorithm has a worst case asymptotic runtime of _______Theta(N2)____________ and a best case asymptotic runtime of _______Theta(N)______________.

f)          The recurrence relation for QuickSort in the best case is ___T(N) = 2T(N/2) + Theta(N)___.

g)         Assuming a pattern of length M is searched for within a text string of length N, the normal case run-time of the brute force (naοve) string matching algorithm is _____Theta(N)_________ and its worst case run-time is ______Theta(NM)___________.

h)         The simple divide and conquer integer multiplication algorithm has a run-time of ____Theta(N2)______ and Karatsuba's improved algorithm has a run-time of ____Theta(N1.58)____.

i)           Using the recursive, efficient GCD algorithm that we discussed in class, finish the equation sequence (show each recursive call and the result): GCD(108, 63) = _GCD(63,45) = GCD(45,18) = GCD(18,9) = GCD(9,0) = 9_.

j)           Given a substitution cipher on an alphabet with S characters, there are ____________S!________________ possible keys for the cipher.

2)      True/False (10 points -- 2 points each). Indicate whether each of the following is TRUE or FALSE, explaining why in an informative way for false answers.

a)         Pruning is a technique that improves exhaustive search algorithms by reducing the number of execution paths that are followed.   True

b)         If I am using hashing to store 1000 arbitrary students (some may graduate and some may enroll) based on their names, I can guarantee that no collisions occur as long as I keep my hash table at least twice the size of my data (or over 2000).  False – the table must be larger than the key space, which is the set of arbitrary names – very large!

c)         In the Rabin-Karp string-matching algorithm, we know two strings match when their hash values match.  False – they are likely to match but it could be a collision.

d)         If someone comes up with an efficient, polynomial-time factoring algorithm, the RSA encryption scheme will no longer be useful.  True

e)         The Miller-Rabin Witness algorithm is used to verify the authenticity of sent messages.  False – it is used to test primality of integers.

3)      (8 points) Consider an emtpy de la Briandais Tree (dlB) which uses the lower case letters (plus a string termination character) as its alphabet.  Also consider the following 6 strings:  walker  wall  walk  walnut  wilt  silt

Draw the dlB that results after inserting the strings shown in the order shown.

Answer: Show on board during class

4)      (6 points)  Consider the forest of single-node trees with frequencies shown below.  Draw the Huffman tree that would result from this forest of nodes.  Show your work.

Oval: G,4Oval: B,24Oval: A,6Oval: C,7Oval: D,5Oval: E,42Oval: F,20Oval: H,18Oval: 9Oval: 38Oval: 22Oval: 13Oval: 126Oval: 80Oval: 46

 

 

 

 

 

 

 

 

 

5)      (8 points)  Consider the chessboard below with the queens in the marked locations.  Assume that we are using the recursive algorithm discussed in class, and that the current recursive call is attempting to place a queen in column 6.  Explain in detail the sequence of steps that occur until the first instance in which a queen is actually placed onto the board, or until a queen is moved from a previous position to a new valid location (in other words, continue until a queen is placed somewhere new, but not necessarily in column 6).  In any case, indicate where the queen is finally placed and why.  Assume that I know nothing about the algorithm or the theory behind it, and that you are teaching it to me with your explanation, so be VERY detailed.

 

 

0

1

2

3

4

5

6

7

0

Q

 

 

 

 

 

 

 

1

 

 

 

Q

 

 

 

 

2

 

Q

 

 

 

 

 

 

3

 

 

 

 

 

 

 

 

4

 

 

 

 

 

Q

 

 

5

 

 

Q

 

 

 

 

 

6

 

 

 

 

Q

 

 

 

7

 

 

 

 

 

 

 

 

 

Answer:  The recursive call for column 6 iterates through the rows, trying to place the queen successfully in the column.  Due to queens on the rows and / or diagonals, this does not succeed, and the call terminates.   Since the call was recursive, at this point the algorithm backtracks to the previous call at column 5.  The queen there is removed from its current location and an attempt is made to re-place it in a successive row.  Based on other queens' locations, this again is not possible, so this call also terminates.  A similar situation occurs for the call at column 4, and we backtrack to column 3.  At this point the queen is removed and replaced at row 7, since there are no conflicts there.    Execution may now resume in the forward direction (to column 4).

 

 

6)      (8 points – 4 + 4) In Assignment 1, you implemented your dlB search() algorithm to return three possible values:

–1 indicates that the key is not in the dlB and it is not a prefix of any word in the dlB

0 indicates that the key is not in the dlB, but it IS a prefix of a word in the dlB

1 indicates that the key is in the dlB

Let's see if this really helps improve the searching of a Boggle board for valid words.  Let's take an extreme case and assume that in our 4x4 board of letters there are NO valid words, and, further, that there are not even any valid prefixes to words.  For example, all locations in the board could be the letter x, and we'll assume that no words in our dictionary begin with the letter x.  Also, to simplify things, assume we are only looking for words that are 1 or 2 letters in length.

a)         Given the search implementation above, how many calls to search() in the dlB will we have to do for our 4x4 board?  Justify your answer.

Answer: In this situation, each call to search() will return –1,  since no prefix is in the dictionary.  If a prefix of a word is not in the dictionary, there is no need to add any more characters, and we can stop as soon as –1 is returned.  Thus, we only need to make one call to search per board position, or 16 total calls.

b)         Assume now that we only have a two value search – 0 indicates not found and 1 indicates found.  Given the same extreme case for our board, how many calls to search() in the dlB will we have to do?  Justify your answer.

Answer: In this situation,  a 0 return value only indicates that the word was not found – we don't know anything about longer words.  For example, even though "x" is not in the dictionary, from this implementation we will not know that "xo" is not in the dictionary until we test for it.  Thus, from each starting position we must test all possible 2 letter strings.   The 4 corner locations require 2 tests each, the 8 edge locations require 3 tests each and the 4 interior locations require 4 tests each.  This total of 48 search() calls is added to the 16 single-character search() calls for a grand total of 64.  Note that if we allowed longer words this total would grow exponentially!

7)      (8 points) Consider the QuickSort algorithm that we discussed in class.  Assume that a separate function / method partition is already defined to partition the array as we discussed in lecture, and it also returns the index of the location of the pivot after partition has been completed.  Write the Java or C++ code for QuickSort, using one of the headers below.

       public static void QuickSort(int [] A, int left, int right)  // Java

  void QuickSort(int A[], int left, int right)                   // C++

   {

if (left < right)

{

    int i = partition(A, left, right);

    QuickSort(A, left, i-1);

    QuickSort(A, i+1, right);

}

   }

 

 

1)      (6 points) You would like to send a message to your friend such that only he/she can read it and such that he/she can verify that it is definitely from you and hasn't been tampered with.   Explain in detail how this can be done using only RSA.  Assume that any necessary keys have been authenticated to their appropriate owners.

Answer:  In this situation I must encrypt the message and use some type of digital signature to detect tampering.  To do this using only RSA,  I can decrypt the message with my private key, then encrypt it with my friend's public key.  Upon receiving the message, my friend decrypts it with his private key, then encrypts it with my public key.  If the message is not garbage, it likely has not been tampered with.  (Note that a digital signature was we discussed in lecture could also be used).

 

2)      (8 points)  Consider the SCTable class that you implemented in Assignment 2 (using separate chaining).  Your table might look something like the (small) one shown below.  Using either Java or C++, write the find() method (function) for this class.  Recall that find() will search the table for a key (an int), returning true if it is found and false otherwise.  Assume your table array variable is T and that the intnode objects have two fields – an int that is your data and a next that is a reference/pointer to the next node.  Also assume that the hash function for the table, h(x), has already been defined – so you can call it in your code without rewriting it here.  Use one of the two headers below:

public boolean find(int key) // Java

{

    int i = h(key);

    intnode curr = T[i];

    while (curr != null)

    {

             if (curr.data == key) return true;

             curr = curr.next;

    }

    return false;

}

 

bool find(int key) // C++

{

    int i = h(key);

    intnode * curr = T[i];

    while (curr != NULL)

    {

            if (curr -> data == key) return true;

            curr = curr -> next;

    }

    return false;

}

 

3)       (8 points – 4 + 4) Consider the Boyer Moore String Matching algorithm to find a pattern of length M within a text string of length N.

a)         Considering only the mismatched character (MC) heuristic that we discussed in lecture, state the best case and worst case number of comparisons to complete a search for a pattern.  For each case, give a pattern string and a text string that will produce that case.

Answer: Best case = N/M   Text = ABCDWABCDXABCDYABCDE  Pattern = ABCDE  In this case theW will be compared against the E, enabling a skip of 5 locations for only one comparison.  Similar for the X and the Y.

Worst case = Theta(NM)   Text = XXXXXXXXXXXXXXXX   Pattern = YXXXX  In this case, the mismatch does not occur until the leftmost character, requiring M comparisons per single shift.

b)         Give the skip array for the following string:

RADIOHEAD

Answer:  R=8, I=5,O=4, H=3, E=2, A=1, D=0, all other characters = 9


4)      (10 points – 6 + 4) Consider the recursive divide and conquer algorithm to calculate XY, where X and Y are (very large) N bit integers (potentially hundreds of bits).   Below are two possible implementations of this algorithm.  Assume that type xlong represents arbitrary length integers.

VERSION A

VERSION B

xlong pow(xlong base, xlong exp)

{

   if (exp == 0) return 1;

   xlong temp = pow(base, exp/2);

   if (exp % 2 == 0)

       return (temp * temp);

   else

       return (base * temp * temp);

}

xlong pow(xlong base, xlong exp)

{

   if (exp == 0) return 1;

   if (exp % 2 == 0)

       return (pow(base, exp/2) *

               pow(base, exp/2));

   else

       return (base * pow(base, exp/2) *

                      pow(base, exp/2));

}

a)         Assuming that base and exp are N-bit integers, in the worst case how many total function calls (in terms of N) will version A require until it completes execution?  Justify your answer.  Assuming that multiplication of N-bit integers takes Theta(N2), what is the overall Theta run-time for Version A in the worst case?

Answer: Examining the code we see that one recursive call is made each time exp is cut in half.  Continuing in this way leads to lg(exp) total function calls (since we know we can only divide a number by 2 lg times).  Since exp is an N-bit integer, we know it can have a value of up to »2N.  This yields lg(2N) = N total function calls.  Each call requires 1 or 2 multiplications, requiringTheta(N2) time, for a total runtime of Theta(N3).

b)         Again assuming that base and exp are N-bit integers, in the worst case how many total function calls (in terms of N) will version B require until it completes execution?  Thoroughly justify your answer.

Answer: Although this code functions in the same way as version A, its runtime is very different.  This is because now each time exp is cut in half, two recursive calls are generated.  Consider an execution tree for this implementation.  At the top (call it level 0) is a single call of size exp.  At level 1 are two calls of size exp/2.  At level 2 are 4 calls of size exp/4, and so on.  Each level has twice as many calls as the previous level, and we already know that there are lg(exp) levels.  Thus we get the sum 20 + 21 + 22 + … + 2k where k = lg(exp).  But we also already know that lg(exp) is N so now we have 20 + 21 + 22 + … + 2N = 2N+1-1.  Clearly this is an unacceptably high number of calls, and the benefit of divide and conquer is lost.