<<

Algorithms and Data Structures 2014 Exercises and Solutions Week 12

1 Division

Give a division that computes both quotient and remainder, that is, given two numbers Pn+m−1 i Pn−1 i A = i=0 aiβ en B = i=0 biβ , your algorithm should return two numbers D en R such that A = D × B + R, of course with 0 ≤ R < B. Your algorithm should be based on the standard schoolbook method, also known as a ‘staartdeling’.

Solution 1: function DIVIDE(A, B) 2: D ← 0 3: R ← 0 4: i ← n + m − 1 5: while i ≥ 0 do 6: repeat 7: D ← βD 8: R ← βR + ai 9: i ← i − 1 10: until R ≥ B or i < 0 11: while R ≥ B do 12: D ← D + 1 13: R ← R − B 14: return (D,R) The inner while loop divides R by B by guessing that it fits zero times and increasing this guess by one as many times as possible. A more efficient method would be to guess that the number of times it fits is the most significant non-zero digit of R (provided that R is not zero) and to then decrease this guess while R is negative.

2 Fast

1. Give an algorithm that computes ak efficiently. Assume that both a and k are relatively small (i.e. they both fit in a single integer variable), but the result could be large. Hence you will need some big integer representation. Determine the time complexity of your algorithm as accurate as possible. You may assume that has complexity O(nα), α ≥ 1.

2. A standard implementation of the factorial function will involve n to com- pute n!. Again, if n is large enough, a 32 or 64 bits representation for the result will not be sufficient. Since you only need to be able to multiply a large number with a small num- ber, multiplication can be done in O(n) time. A more efficient method for computing n! can be obtained by using the prime factorization of 1 × 2 × · · · × n, that is, write n! as n2 n3 n5 2 × 3 × 5 × · · · , for some n2, n3, n5,..., and use the previous operation to compute the powers. Give an algorithm that computes n! in this way.

Solution

1. Instead of multiplying a by itself k times, we can do this (recursively) for half of k and square the result. If k is odd, we can halve k − 1 instead and multiply the squared subresult by a. 1: function EXP(a, k) 2: if k = 0 then 3: return 1 4: else if k is even then k 2 5: return EXP(a, 2 ) 6: else k−1 2 7: return a · EXP(a, 2 ) We know that the size of ak will be at most k (unless k = 0), so, except for the base case that runs in constant time, there are O(kα) non-recursive computation steps, and therefore the function is in k−1 α! k−1 ! X  k  X O = O kα2−iα 2i i=0 i=0 k−1 ! X = O kα 2−iα i=0 = O(kα).

Pk−1 −i In the last step we use the upper bound i=0 2 < 2. Because α ≥ 1, it also holds that Pk−1 −iα i=0 2 < 2. 2. We exhibit two auxiliary arrays: r[i] is the divisor of the number i that has not been factor- ized into prime powers yet, while p[i] in the end will be the number ni if i is prime and 0 otherwise. The idea is that if we traverse the numbers i in order, then i will be prime if and only if r[i] > 1 because if this holds, we loop over all multiples j of i and divide r[j] by i as many times as possible. 1: function FACTORIAL(n) 2: for i from 2 to n do 3: r[i] ← i 4: p[i] ← 0 5: for i from 2 to n do 6: if i > 1 then 7: j ← i 8: while j ≤ n do 9: while r[j] mod i = 0 do r[j] 10: r[j] ← i 11: p[i] ← p[i] + 1 12: j ← j + i 13: f ← 1 14: for i from 2 to n do 15: f ← f · EXP(i, p[i]) 16: return f

3 Karatsuba

A naive implementation of Karatsuba’s multiplication method uses 3 auxiliary variables to store the intermediate results during each recursive step in the computation. Modify this naive al- gorithm such that O(n) space is reserved beforehand which is then carefully used during the computation itself. Any additional space allocations should be avoided. k/2 k/2 Hint: Suppose you are multiplying two k-digit numbers A = a0 + a1 · β and B = b0 + b1 · β . The result will be a 2k-digit number. Instead of allocating an array with 2k elements for storing this result you should create an array, say D, of size 6k. Then you can use the following layout for D not only for the final result, but also for storing intermediate values.

D[0..k − 1] = a0 × b0 D[k..2k − 1] = a1 × b1 D[2k..3k − 1] = x1 × x2 D[3k..5k − 1] = temporary space for recursion D[5k..11k/2 − 1] = a0 + a1 = x1 D[11k/2..6k − 1] = b0 + b1 = x2

Solution We write D + x for (a reference to) the subarray that starts at the xth position of D. Note that the given layout is not static: the indicated recursion space is only used for the second and third recursive call. After recursing, the subresults will be combined into the actual result that is stored in D[0..2k − 1]. 1: function KARATSUBA(A, B, k, D) 2: if k = 1 then 3: return A · B 4: D[5k..11k/2 − 1] ← a0 + a1 5: D[11k/2..6k − 1] ← b0 + b1 k 6: KARATSUBA(a0, b0, 2 ,D) k 7: KARATSUBA(a1, b1, 2 ,D + k) k 8: KARATSUBA(D[5k..11k/2 − 1],D[11k/2..6k − 1], 2 ,D + 2k) 9: D[2k..3k − 1] ← D[2k..3k − 1] − D[0..k − 1] − D[k..2k − 1] 10: D[k/2..3k/2 − 1] ← D[k/2..3k/2 − 1] + D[2k..3k − 1] 11: return D[0..2k − 1]

To make things easier, we do not require intermediate P to satisfy < β, where β is the radix. In practice, this means we need another limit γ for the value of the coefficients, in such a way that we can perform elementary operations within γ. For example, we could take γ to be the maximum value of an integer while β could be a small number like 10. Note that this approach does pose another limitation on the applicability of the algorithm: the coefficients must never exceed γ. However, it postpones any other size and carrying issues to the final result. We leave out the final normalization algorithm here.

4 Toom-Cook3

Karatsuba’s method generalizes to what is known as Toom-Cook r-way multiplication. Divide both multiplication operands A and B into r parts, combine these parts in a clever way, apply the algorithm recursively to these combinations, and compose the final results out of these in- termediate results. The case r = 2 corresponds to Karatsuba’s algorithm, and the case r = 3 is known as Toom-Cook 3-way, sometimes simply called the Toom-Cook algorithm. For r = 3 you 2 2 k write A = a0 + a1 · x + a2 · x ,B = b0 + b1 · x + b2 · x with x = β . The first step is to combine the coefficients in the following way: x0 = a0 y0 = b0 x1 = a0 + a1 + a2 y1 = b0 + b1 + b2 x2 = a0 − a1 + a2 y2 = b0 − b1 + b2 x3 = a0 + 2 · a1 + 4 · a2 y3 = b0 + 2 · b1 + 4 · b2 x4 = a2 y4 = b2

Then you compute vi = xi × yi, for each i. 2 3 4 Finally, the result C = c0 + c1 · x + c2 · x + c3 · x + a4 · x is obtained as follows:

t1 = (3 · v0 + 2 · v2 + v3)/6 − 2 · v4 t2 = (v1 + v2)/2 c0 = v0 c1 = v1 − t1 c2 = t2 − v0 − v4 c3 = t1 − t2 c4 = v4

1. Verify that the division in the computation of t1 and t2 are exact. We call a division exact if the remainder is zero. 2. Show that the final result is correct.

3. Determine the time complexity of this method. 4. Give a based on this method.