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.
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.