Questions in black, solutions in blue
Java provides a BigInteger
class for storing integers too long
for an int
or long
. Here we will look at the
complexity of
addition and multiplication of BigInteger
objects, and determine
whether the unusual multiplication algorithm of Levitin section 4.5 is
useful in this context.
We'll assume that a (non-negative)
BigInteger
is written in the form
"the sum for i from 0 to n of biri, where r is a
base such as MAXINT+1
. Each
bi is kept in an int
variable, and these
int
variables are kept in some sort of list. We can get
bi from b, for example, in O(1) time by the call
b.getTerm(i)
, and change bi to x in O(1) time by
b.setTerm(i,x)
.
BigInteger
class:
BigInteger add (BigInteger c)
{// returns sum of this and c
int n = max(this.size(), c.size());
BigInteger result = new BigInteger(n+1);
int carryBit = 0;
for (int i=0; i < n; i++) {
result.setTerm(i, this.getTerm(i) + c.getTerm(i) + carryBit);
carryBit = addCarry(this.getTerm(i), c.getTerm(i), carryBit);}
result.setTerm(n, carryBit);
return result;}
BigInteger multiply (int x)
{// returns this times x
int n = this.size();
BigInteger result = new BigInteger(n+1);
int carryInt = 0;
for (int i=0; i < n; i++) {
result.setTerm(i, this.getTerm(i) * x + carryInt);
carryInt = multCarry(this.getTerm(i), x, carryInt);}
result.setTerm(n,carryInt);
return result;}
Argue that each of these methods takes $O(n)$ time.
Each method has one loop that executes exactly n times. Inside that
loop we have basic int
operations plus methods that are
given to take O(1) time. (The methods addCarry
and
multCarry
operate on individual int
arguments.) So
each of the loops takes O(n) time. The code outside the loops in each
method also takes O(1) time except possibly for the constructor call, which
might take as much as O(n) depending on the implementation of the class.
But in any case the total time is O(n).
add
and multiply
methods
above to implement BigInteger multiply (BigInteger c)
using the
ordinary algorithm for multiplication. Analyze the running time.
The time is dominated by the loop, which runs m times. The inside
of the loop has an
BigInteger multiply (BigInteger c)
{//returns this times c
int n = this.size();
int m = c.size();
BigInteger(result) = new BigInteger(n+m); //starts as 0
for (int i=0; i < m; i++)
result.add((this.times(c.getTerm(i))).shift(i));
return result;}
add
, a times
with an
int
, and a shift
, each of which takes O(n+m) time.
So the entire time is O((n+m)2), or quadratic in the input size.
multiply
utilizing the trick from section 4.5, where it
is observed that if a = a1rn/2 + a0 and
b = b1rn/2 + b0, then ab =
(a1b1)rn +
((a1 + a0)(b1 + b0) -
a1b1 - a0b0)rn/2 +
(a0b0).
Assume you also
have a method shift
in the BigInteger
class, taking an int
argument, such that b.shift(i)
is b times ri if i is positive and b divided by ri
(with no remainder) if i is negative. There is also a method tail
such that b.tail(i)
gives the remainder when b is divided by
ri. Each of these methods takes Θ(i) time.
BigInteger multiply (BigInteger c)
{//returns this times c, using recursive multiplication
int n = max(this.size(), c.size());
BigInteger(result) = new BigInteger(2*n); //starts as 0
BigInteger a1 = this.shift(i);
BigInteger a0 = this.tail(i);
BigInteger b1 = c.shift(i);
BigInteger b0 = c.tail(i);
BigInteger r2 = a1.multiply(b1);
BigInteger r0 = a0.multiply(b0);
BigInteger r1 = (a1.add(a0)).multiply(b1.add(b0));
r1 = (r1.subtract(r2)).subtract(r0); //"subtract" is like "add"
result = result.add(r0);
result = result.add(r1.shift(n/2));
result = result.add(r2.shift(2*(n/2));
return result;}
Analyze the running time of your method.
A call with input size n makes three calls on the same procedure for input size n/2, and then does adds, shifts, and other operations taking O(n) total time. So our recurrence is T(n) = 3T(n/2) + Θ(n), with base case T(1) = Θ(1), and by the Master Theorem this gives us T(n) = Θ(nlog 3) = Θ(n1.5849...). As Levitin points out, given the constants concerned in the Θ's, n must be pretty large before this algorithm is better than that of Question 2 in practice.
Last modified 2 October 2003