Uploaded by sabiha.sultana123987

CSC 505 Homework 2: Algorithms & Recursion

advertisement
CSC 505, Homework 2
Homework should be submitted using WolfWare Submit Admin in PDF, or plain text. To
avoid reduced marks, please submit word/latex-formated PDF file, NOT scanned
writing in pdf format. Scanned writing is hard to read, takes longer to grade, and
produces gigantic files. All assignments are due on 9 PM of the due date. Late submission
will result in 10%/40% point reduction on the first/second day after the due date. No
credit will be given to submission that are two or more days late. Please try out Submit
Admin well before the due date to make sure that it works for you.
All assignments for this course are intended to be individual work. Turning in an
assignment which is not your own work is cheating. The Internet is not an allowed
resource! Copying of text, code or other content from the Internet (or other sources) is
plagiarism. Any tool/resource must be approved in advance by the instructor and
identified and acknowledged clearly in any work turned in, anything else is plagiarism.
General instruction about how to “give/describe/...” an algorithm, taken from Erik
Demaine. Try to be concise, correct, and complete. To avoid deductions, you should
provide (1) a textual description of the algorithm, and, if helpful, pseudocode; (2) at least
one worked example or diagram to illustrate how your algorithm works; (3) a proof (or
other indication) of the correctness of the algorithm; and (4) an analysis of the time
complexity (and, if relevant, the space complexity) of the algorithm. Remember that,
above all else, your goal is to communicate. If a grader cannot understand your
solution, they cannot give you appropriate credit for it.
1. Purpose: Apply recursion to solve a problem, practice formulating and analyzing
algorithms. In the US, coins are minted with denominations of 50, 25, 10, 5, and 1 cent. An
algorithm for making change using the smallest possible number of coins repeatedly
returns the biggest coin smaller than the amount to be changed until it is zero. For
example, 17 cents will result in the series 10 cents, 5 cents, 1 cent, and 1 cent.
a) (4 points) Give a recursive algorithm for changing n cents.
b) (4 points) Give an O(1) (non-recursive!) algorithm to compute the number of returned
coins.
c) (1 point) Show that the above algorithm does not always give the minimum number of
coins in a country whose denominations are 1, 6, and 10 cents.
a) (4 points) Here is the pseudo code for this recursive changing algorithm:
int denomination[5] = {50, 25, 10, 5, 1};
MakeChange(int amount)
{
if(amount < 0)
return INVALID;
if(amount == 0)
return;
for(i = 0; i < 5; i++){ //assume the index starts from 0
if(amount >= denomination[i]){
print(denomination[i]);
MakeChange(amount – denomination[i]);
break;
}
}
}
Algorithm description: First, we examine if the input is negative or zero. Negative is an
invalid input, while 0 means no further work to do. For positive input, the algorithm tries
to find the largest denomination which is less than or equal to the amount. The algorithm
outputs one coin with that denomination. Now the amount that needs to make change
becomes original amount subtracting that denomination. Now the original problem
converts to a new problem which is to make change for the updated amount. Therefore,
the algorithm recursively calls itself with the updated amount. Since the smallest
denomination is one cent, this function will always be able to find the coin series.
Example: For the changing amount 57 cents, this algorithm first tests and finds that a 50
cent coin could be used. Then, a recursive call is executed on input of 7 cents. In this step
of recursion, 50, 25 and 10 cents are tested but all larger than 7. The algorithm keeps
going on until 5-cent which is the largest denomination less than or equal to 7. It takes
the 5-cent coin. Now there remains 7-5 = 2 cents. A recursive call with updated amount
which is 2. This time the algorithm will find 1 cent, because all other denominations are
larger than 2 cents. Similarly, Taking 1 cent coin, it recursively calls with 2 – 1 = 1 cent.
The same procedure as last call, the algorithm finds 1 cent, then it recursively calls with
1 – 1 = 0 cent. Since the updated value is 0, it means we have found the coin series
corresponds to 57 coins, thus algorithm returns.
Analysis of time complexity:
Time complexity depends on the amount to make change. Let’s assume the amount is n.
In every recursive call, n is reduced by at least 1 and at most 50. Therefore, the number
𝑛
of recursive calls is between 50 𝑡𝑜 𝑛. In every recursive call, the number of arithmetic
operation is 1 (not considering i++). Therefore, the time complexity is 𝑂(𝑛).
b) (4 points) Write an O(1) (non-recursive!) algorithm to compute the number of
returned coins.
The pseudo code is as following:
int denomination[5] = {50, 25, 10, 5, 1};
Changing(int amount)
{
if(amount < 0)
return INVALID;
noOfCoins = 0;
for (i=0; i<5; i++)
{
noOfCoins += floor ( amount / denomination[i] );
amount = amount % denomination[i];
}
print(noOfCoins);
return;
}
Algorithm description: First, we test for invalid input. Then, we compute in order of
decreasing denominations how many coins of a certain denomination value have to be
returned. We adjust the amount to change correspondingly, and iterate with the next
denomination. Since the smallest denomination is one cent, this procedure will always be
able to change a positive amount; it produces the same coin set as the previous recursive
algorithm.
Example: for the 57 cents example, in the for loop, when i equals 0, we get noOfCoins as
1 and amount as 7, meaning one 50-cent coin can be used, and another 7 cents need to be
changed. When i equals 1 or 2, since the denominations of 25-cent and 10-cent coins are
larger than 7 cents, noOfCoins keeps the same. When i equals 3, we get one 5-cent coin
and the remainder is 2 cents. When i equals to 4, 2 1-cent coins are used. Therefore, the
result is 4.
For positive input, the number of performed operations is always the same: 5 loop
iterations, all iterations have exactly the same number of basic operations. Therefore, the
time complexity is 𝑂(1).
c) (1 point) Show that the above greedy algorithm does not always give the minimum number of
coins in a country whose denominations are 1, 6, and 10 cents.
E.g. 12 cents need the changing of one 10-cent coin and two 1-cent coins using the above
algorithm; however, the minimum number of coins changing is with two 6-cent coins.
2. Purpose: Practice solving recurrences. For each of the following recurrences, use the
Master Theorem to derive asymptotic bounds for T(n), or indicate why the Master
Theorem does not apply. If not explicitly stated, please assume that small instances need
constant time c. Justify your answers, in particular, for case 3 of the Master Theorem
show that the regularity condition is satisfied. (2 points each)
(a) T(n)=8T(n/5) + n3.
We have 𝑎 = 8, 𝑏 = 5, 𝑓(𝑛) = 𝑛3 𝑎𝑛𝑑 𝑛log𝑏 𝑎 = 𝑛log5 8 .
Choosing  = 0.25 we have 𝑓(𝑛) = 𝛺(𝑛log5 8+ ). Hence, MT case 3 applies if we can
show that the regularity condition holds for 𝑓(𝑛).
𝑛
i.e. 𝑎𝑓 (𝑏 ) ≤ 𝑐𝑓(𝑛) for some constant 𝑐 < 1 and all sufficiently large n.
𝑛 3
=> 8 ∗ ( 5) ≤ 𝑐 ∗ 𝑛3 . Which is true for 1 > 𝑐 ≥ 8/53 .
So, 𝑇(𝑛) = Ѳ(𝑛3 ).
(b) T(n)=3T(n/3) + n+1/sqrt(n).
a=b=3, f(n)=n1 + n-0.5   ( n1 ) hence case 2, and T(n)=  (nlgn)
(c) T(n)=7T(n/3) + n11/5.
We have 𝑎 = 7, 𝑏 = 3, 𝑓(𝑛) = 𝑛2.2 𝑎𝑛𝑑 𝑛log𝑏 𝑎 = 𝑛log3 7.
Choosing  = 0.25 we have 𝑓(𝑛) = 𝛺(𝑛log3 7+ ). Hence, MT case 3 applies if we can
show that the regularity condition holds for 𝑓(𝑛).
𝑛
i.e. 𝑎𝑓 (𝑏 ) ≤ 𝑐𝑓(𝑛) for some constant 𝑐 < 1 and all sufficiently large n.
𝑛 2.2
=> 7 ∗ ( 3) ≤ 𝑐 ∗ 𝑛2.2 . Which is true for 1 > 𝑐 ≥ 7/32.2 .
So, 𝑇(𝑛) = Ѳ(𝑛2.2 ).
(d) T(n)=4T(n/2) + nsqrt(n) - 1000.
a=4, b=2, f(n)=n1.5 - 1000, for =0.25: f(n)=n1.5 - 1000 O( n2- ) hence case 1, and
T(n)=  (n2)
(e) ½T(2n/3) + n3.
MT does not apply since a = 0.5 < 1
3.
a)(4 points) Purpose: More practice in algorithm design and algorithm analysis. Describe
a non-recursive Θ( lg(n) ) algorithm which computes an, given a and n. Justify the
asymptotic running time of your algorithm. You may assume that n is a positive integer,
but do not assume that n is always a power of 2. (Here, lg := logarithm base 2)
The algorithm uses the method of exponentiation by squaring to compute 𝑎𝑛 . We first initialize a
return value by 1, and then iteratively replace a n/2 if n is even, or multiply the return value by a,
and replace a by a2 and n by (n-1)/2 if n is odd. The pseudocode and example below illustrate the
approach. Correctness follows from the fact that our approach implements the identity: 𝑎𝑛 = (a2)n/2
if n is even, a(a2)(n—1)/2 if n is odd.
Psuedocode:
1 POWER(a, n)
2
val  1
3
while n > 0
4
if n % 2 != 0
#n is odd
5
val  val * a
6
n  floor(n / 2)
7
aa*a
8 return val
Example:
POWER(2, 5)
a  2, n  5, val  1
while n > 0
if n is odd
val  val * a
nn/2
aa*a
5>0
5 is odd
val  1 * 2 = 2
n5/2=2
a2*2=4
2>0
2 is not odd
val  2
n2/2=1
a  4 * 4 = 16
1>0
1 is odd
val  2 * 16 =
32
n1/2=0
a  16 * 16
0=0
val  32 is returned.
Run-time analysis: The while-loop is executed 𝑓𝑙𝑜𝑜𝑟(log 2 𝑛) + 1 times. The execution of the
commands within the while loop are bounded by a constant number of basic operations. Hence, the
asymptotic run-time of the algorithm is Θ(log n).
(b)
Algorithm:
1 Call_multiplier(matrix, power)
2
temp_matrix  Diagonal matrix having 1 throughout the diagonal of same
dimension of input matrix
3
4
5
6
7
8
while power > 0
if power%2 != 0 #power is odd
temp_matrix  Multiply_matrices(temp_matrix, matrix)
matrix  Multiply_matrices(matrix, matrix)
power  floor(power / 2)
return temp_matrix
4. (5 points) Purpose: Practice algorithm design and the use of data structures. This
problem was an interview question! Consider a situation where your data is almost
sorted—for example you are receiving time-stamped stock quotes and earlier quotes may
arrive after later quotes because of differences in server loads and network traffic routes.
Focus only on the time-stamps. To simplify this problem assume that each time-stamp is
an integer, all time-stamps are different, and for any two time-stamps, the earlier timestamp corresponds to a smaller integer than the later time-stamp. The time-stamps arrive
in a stream that is too large to be kept in memory completely. The time-stamps in the
stream are not in their correct order, but you know that every time-stamp (integer) in the
stream is at most hundred positions away from its correctly sorted position. Design an
algorithm that outputs the time-stamps in the correct order and uses only a constant
amount of storage, i.e., the memory used should be independent of the number of timestamps processed. Tip: map the problem to a data structure covered in class.
Algorithm description: To solve this problem, a linked list of 101 stream elements in
ascending order of their time-stamps is continuously maintained.
First, our algorithm builds an ascendingly sorted linked list of the first 101 stream
elements. To accomplish this, the stream elements are inserted one by one from the
stream. Initially, the list is empty. Each time a new stream element comes, it is inserted in
the proper position so that the list remains sorted. Suppose, it is the turn of the i’th stream
element (1<=i<=101). Before inserting the i’th element, the linked list comprises the first
i-1 elements in sorted order. To determine the proper position of the i’th element, our
algorithm iterates over the list to find the first element greater than the new element. If
such an element is found, the i’th element is inserted before it by pointer manipulation;
otherwise, the new element is inserted at the end of the list. This way, a sorted order is
maintained and eventually a sorted list of the first 101 stream elements is built.
Second, our algorithm reports the head element of the list of size 101 and removes it.
Then, it inserts the next element of the stream into the list in its proper position; thus,
maintaining a sorted list of 101 elements. Our second step is repeated for the remaining
elements of the stream. This way, our algorithm uses only the (constant amount of) space
used by the list to maintain 101 elements and the necessary pointers.
Example:
As 100 elements is a big number, let us illustrate with 2.
Let the stream be, 3, 2, 1, 7, 4, …
Current Stream Element
3
2
1
7
4
…
Linked List
3
2 -> 3
1 -> 2 -> 3
2 -> 3 -> 7
3 -> 4 -> 7
Always a list of size 3
Output
1
1, 2
1, 2, 3, 4, 7, …
Proof of correctness: The proof can be divided into two parts. First, a proof of
correctness for the first part of our algorithm, namely, building a sorted linked list of 101
elements. Second, a proof of correctness for the second part, namely, reporting stream
elements in an ascendingly sorted order.
The proof for the first part is easy. This can be done via a loop invariant: after inserting
the i-1’st element, the list comprises the first i-1 elements of the stream in an ascendingly
sorted order. Initialization: When i=1, the invariant is trivially held, as a list of 0
elements is always sorted. Maintenance: At each iteration, the i’th element is inserted
before the first element of the list greater than it, or at the end of the list if no such element
exists. Since all elements are unique, the i’th element is thus inserted into its proper
position. Since before the iteration, the list consisted of the first i-1 elements in sorted
order, after inserting the i’th element in its proper position, the list remains sorted,
however, grows by size 1. Thus, at the end of the i’th iteration, the list comprises the first
i elements of the stream in an ascendingly sorted order. Termination: The loop
terminates after the 101’th element is inserted. Since the loop invariant is maintained at
each iteration, after the end of the loop, the linked list comprises the first 101 elements
in an ascendingly sorted order.
The second part of our algorithm is the main part. The proof of correctness of that part
can be conducted via a loop invariant. The main argument is that during the execution of
the loop, a linked list of 101 ascendingly sorted elements is maintained, and all written
elements are smaller than the current list elements as well as all future stream elements.
Initialization: Prior to removing the head element of the list for the first time, the list
comprises the first 101 stream elements in an ascendingly sorted order, and there is no
element written to the output file. Thus, initially, the loop invariant is maintained trivially.
Maintenance: At each iteration, the algorithm outputs the head element x, which is the
smallest element in the list. Thus, at the time an element x is written, it is the smallest
among all 101 list elements e1, …, e101. Therefore, at that time, at least 100 elements
larger than x have been observed. If at any future time, an element y with an even smaller
time-stamp than x is to be observed, y would be distorted by at least 101 positions (the
list elements e1, …, e101), in contradiction to the assumption that no stream element can
be more than 100 positions out of order. Therefore, x is smaller than all other list
elements and any future stream element, and can be written without violating the loop
invariant. After removing the head element x, the list shrinks to a size of 100. Then, we
insert the next element from the stream into the list in its proper position. By a similar
argument, as presented in the proof of the first part of our algorithm, an ascendingly
sorted order of elements is preserved in the list. Also, the list size becomes 101 again after
the insertion. Thus, the loop invariant is maintained after every iteration; this implicates
that each element is written in the correct order, maintaining a list of (constant) size 101.
Termination: The loop invariant is maintained throughout the execution of the
algorithm. Thus, the stream elements get written into the output file in the correctly
sorted order. However, since the stream is endless the loop never terminates.
Space Complexity: The proofs of the loop invariants above also prove that during the
execution of our algorithm, a linked list of maximum 101 elements is maintained. We
need storage for the elements, and the required pointers to maintain the linked list.
However, all these are proportional to the list size, which is a constant, namely, 101. Thus,
our algorithm uses O(1) (a constant amount of) storage.
Time Complexity: First, building a sorted linked list of n elements, by the procedure
described, requires O(n^2) time. However, in our case, the initial linked list comprises a
constant number (101) of elements. Thus, the time required to build the initial list is O(1).
Second, removing the head element requires O(1) time. Inserting a new element in a list
of size n, in proper position, requires O(n) time. However, the size of our list is constant
(100). Thus, the insertion requires O(1) time. Hence, each iteration of the main part of
our algorithm requires O(1) time. Therefore, processing the first n stream elements and
writing them to the output file in correctly sorted order requires O(n) time in total. The
amortized execution time for processing and writing each input element is thus O(1).
Download