int fib(int n) { if (n < 3) return 1; else return fib(n-1) + fib(n-2); }This is terrible, because its running time is exponential in n.
The more obvious loop computes F(n) in time which is linear in n (depending on your assumptions about multiplication):
int fib(int n) { int *f, answer; if (n < 3) return 1; f = new int[n+1]; f[1] = f[2] = 1; for (int i = 3; i <= n; i++) f[i] = f[i-1] + f[i-2]; answer = f[n]; delete f; return answer; }We tend to recoil from this one because it is doing a malloc (or "new"), and malloc/free is a very expensive operation.
However, it turns out to run MUCH faster than the recursive version, for anything but the smallest values of n. A change to the 'if' there could take care of the remaining few special cases where the recursive version might be faster, but isn't really necessary.
But we know that recursion is good. Especially in the case of things which are defined recursively, such as the Fibonacci sequence, we can write a much simpler program by using recursion.
However, we also can note that there is something of the recursive solution still present in the second version of fib(). What this is is the recurrence, the formula stating values of fib(n) in terms of fib(j) for one or more values of j<n.
"Dynamic programming" inverts the recursion. For some problems (not all, or necessarily even most), even if you have to malloc and free the array each time, it can be much faster to compute all the values in an array and then take the ending value.
obj | weight | value |
---|---|---|
0 | 3 | 2 |
1 | 4 | 3 |
2 | 5 | 4 |
3 | 7 | 5 |
The filled-up array will be:
index | M | L |
---|---|---|
0 | 0 | -1 |
1 | 0 | -1 |
2 | 0 | -1 |
3 | 2 | 0 |
4 | 3 | 1 |
5 | 4 | 2 |
6 | 4 | 2 |
7 | 5 | 0 |
8 | 6 | 0 |
9 | 7 | 1 |
10 | 8 | 2 |
11 | 8 | 2 |
Notes:
The answer will be: 2, 2
The next example is about matrix multiplication.
Suppose we have a bunch of matricies to multiply together.
A x B x C x D x E
Matrix multiplication is associative, but not commutative.
e.g. can multiply AxB first, then xC; or can multiply BxC first, then Ax that.
But we can't multiply CxB instead.
This is what it means for matrix multiplication not to be commutative.
Also that would often not be
what as computer programmers we would think of as "type-correct" -- in
multiplying two matrices, the number of columns in the left matrix must equal
the number of rows in the right matrix.
There's still substantial choice though, through the associativity.
And while the mathematical answer is the same, it does turn out to matter
considerably sometimes which way we do it.
The number of little multiplication operations of the matrix elements can be
very different.
An i by j matrix times a j by k matrix yields an i by k matrix, in a number of
operations proportional to i*j*k.
Example: If A is 10 by 100, B is 100 by 5, C is 5 by 50
How many ways are there to parenthesize a product like A x B x C x D x E ?
LOTS. It's proportional to 4n.
Dynamic programming to the rescue!
There is a good dynamic programming solution for figuring out the optimal
order for doing the multiplying.
Recurrence:
There must be some last multiplication; the optimal solution for multiplying
the n matrices together is to multiply A0 through
Ak-1 together, and multiply Ak through An-1
together, then multiply the two results.
These subproblems must in turn
be optimal for those lists of matrices, else we could get
a better overall result by a better subresult.
Let array 's' be such that matrices A0 through An-1 have row
dimension s0
through sn-1 and column dimension s1 through
sn, respectively.
That is,
the number of columns in, say, matrix A3, which is s4, must equal the number
of rows in matrix A4, which s4 is also defined as being. The only point of
mentioning the column dimensions at all is to define sn as the number of
columns in the rightmost matrix (which isn't equal to anything else, just like
the number of rows in the leftmost matrix).
Let cost[i][j] be the minimum cost of multiplying Ai * ... *
Aj-1
Base case: cost[i][i+1] = 0, for all i
(We don't use cost[i][i], because by that definition of the cost array, it
means nothing.)
cost[i][j] = MIN i<=k<j of cost[i][k] + cost[k][j] + s[i]*s[k]*s[j]
That is, take the recursive cost of multiplying up to but not including k,
plus the recursive cost of multiplying from k to j, and do the multiplication
of these two resulting matrices, one of which is s[i] by s[k], the other of
which is s[k] by s[j].
Recursive version (bad, but a guide to writing the dynamic-programming
version):
(i.e. two items of type 2)
Computing (A*B)*C takes time proportional to 5000 + 2500
Computing A*(B*C) takes time proportional to 25000 + 50000
Ten times the work!
The optimal order for multiplying A0 * A1 * ... *
An-1
ends with a multiplication of
- the optimal multiplication of A0 * ... * Ak-1
times
- the optimal multiplication of Ak * ... * An-1
, for some k.
// Cost of multiplying from i to j (semi-inclusive), given array of sizes 's'
int r_mat_prod(int i, int j, int *s)
{
int k, mincost;
if (j == i + 1)
return 0;
k = i + 1;
mincost = r_mat_prod(i, k, s) + r_mat_prod(k, j, s) + s[i]*s[k]*s[j];
for (k++; k < j; k++) {
int subcost = r_mat_prod(i, k, s) + r_mat_prod(k, j, s) + s[i]*s[k]*s[j];
if (subcost < mincost)
mincost = subcost;
}
return mincost;
}
Dynamic version:
cost[i][j] will be the cost of multiplying Ai * Ai+1
* ... * Aj-1
int i, len;
for (i = 0; i < n; i++)
cost[i][i+1] = 0;
for (len = 2; len <= n; len++) { (above loop took care of the len==1 cases)
for (i = 0; i + len <= n; i++) {
int j = i + len;
int k = i + 1;
cost[i][j] = cost[i][k] + cost[k][j] + s[i]*s[k]*s[j];
for (k++; k < j; k++)
if (cost[i][k] + cost[k][j] + s[i]*s[k]*s[j] < cost[i][j])
cost[i][j] = cost[i][k] + cost[k][j] + s[i]*s[k]*s[j];
}
}
As in the knapsack example above, this finds the optimal order but
doesn't actually tell us what it is! Fortunately,
also as in the knapsack example,
we can easily insert appropriate tracking so that we can tell what
the optimal order is.
int i, len;
for (i = 0; i < n; i++) {
cost[i][i+1] = 0;
best[i][i+1] = i;
}
for (len = 2; len <= n; len++) {
for (i = 0; i + len <= n; i++) {
int j = i + len;
int k = i + 1;
best[i][j] = k;
cost[i][j] = cost[i][k] + cost[k][j] + s[i]*s[k]*s[j];
for (k++; k < j; k++) {
if (cost[i][k] + cost[k][j] + s[i]*s[k]*s[j] < cost[i][j]) {
cost[i][j] = cost[i][k] + cost[k][j] + s[i]*s[k]*s[j];
best[i][j] = k;
}
}
}
}
Now, to output it we can use a recursive function:
void output_order(int i, int j)
{
int k = best[i][j];
if (i < k) {
printf("(");
output_order(i, k);
printf(") * ");
}
printf("A%d", k);
if (k + 1 < j) {
printf(" * (");
output_order(k + 1, j);
printf(")");
}
}
The initial call will be:
output_order(0, n)
[list of topics covered, evening section]
[course notes available (evening section)]
[main course page]