Unit 10 Dynamic Programming - 1: Structure
Unit 10 Dynamic Programming - 1: Structure
10.1 Introduction
By now you must be familiar with the different algorithms with various
concepts and their space and time tradeoffs. This unit introduces you to the
concepts of dynamic programming.
Dynamic programming is a general algorithm design technique. It was
introduced in the 1950’s by American mathematician Richard Bellman to
solve optimization problems.
Objectives:
After studying this unit you should be able to:
calculate the nth Fibonacci number
explain the dynamic programming approach to compute binomial
coefficients
explain Warshall’s and Floyd’s algorithms
Sikkim Manipal University B1480 Page No. 207
Analysis and Design of Algorithms Unit 10
F0= 0
F1= 1
Fn= Fn-1+ Fn-2 Eq: 10.2
We will now try to create an algorithm for finding the nth Fibonacci-number.
Let's begin with the naive algorithm exactly coding the mathematical
definition:
Naive algorithm to calculate the nth Fibonacci number:
// fib -- compute Fibonacci(n)
function fib(integer n): integer
assert (n ≥ 0)
if n == 0: return 0 fi
if n == 1: return 1 fi
return fib(n - 1) + fib(n - 2)
end
Let us now trace the naive algorithm to calculate the nth Fibonacci number
Trace of the naive algorithm to calculate the nth Fibonacci number
n=2
function fib( integer 2) //n should be an integer
assert (2≥0)
if n == 0: return 0 fi
if n == 1: return 1 fi
return fib(2 - 1) + fib(2 - 2)=1 // the 2nd Fibonacci number
end
n (1 ) n
F ( n)
5 Eq: 10.3
where
1 5
2 (Golden Ratio) Eq: 10.4
Hence using the equation F(n) we can calculate the nth Fibonacci number
efficiently when n is small. However when n is large, this method is very
inefficient.
In the equation Eq: 10.4, Φ is known as the Golden Ratio. It is an irrational
mathematical constant which is approximately 1.6180339887. This unique
ratio has served as an inspiration to thinkers of all discipline be it art,
mathematics, architecture, physiology, biology etc.
Fib(6)
Fib(5)
To analyze the running time of Fibonacci sequence we will look at a call tree
for the sixth Fibonacci number:
In the Figure 10.1 every leaf of the tree has the value 0 or 1, and the sum of
these values is the final result. Thus for any n, the number of leaves in the
call tree is Fibn itself. The closed form therefore tells us that the number of
leaves in fib(n) is approximately equal to equation Eq: 10.5
n
1 5 n
1.618 n 2log(1.618 ) 2n log(1.618 ) 20.69 n
2
Eq: 10.5
This means that there are far too many leaves, considering the repeated
patterns found in the figure 10.1 tree. (The algebraic manipulation used in
equation Eq: 10.5 to make the base of the exponent as 2 should be duly
noted.)
We can use a recursive memoization algorithm that can be turned bottom-
up into an iterative algorithm that would fill in a table of solutions to sub-
problems. Here some of the sub-problems solved might not be needed at
the end while computing the result (and this is where dynamic programming
differs from memoization), but dynamic programming can be very efficient
because it can use the result stored in a better manner and have less call
overhead.
The pseudocode to compute the nth Fibonacci number is given as follows:
Pseudocode to compute the nth Fibonacci number:
function fib(integer n): integer
if n == 0 or n == 1:
return n
else-if f[n] != -1:
return f[n]
else
f[n] = fib(n - 1) + fib(n - 2)
return f[n]
fi
end
In the above code if the value of fib(n) already has been calculated it's
stored in fib[n] and then returned instead of calculating it again. That
means all the copies of the sub-call trees can be exempted from the
calculation.
Fib(6)
Fib(5)
Fib(2)
Fib(2) Fib(3) Fib(1) Fib(2) Fib(3)
In the Figure 10.2 the values in the boxes are values that already have been
calculated and the calls can thus be skipped. Hence it is a lot faster than the
straightforward recursive algorithm. Here every value less than n is
calculated once only. Therefore the first time you execute it, the asymptotic
running time is O(n). Any other calls to it will take O(1) time since the values
have been pre-calculated (assuming each subsequent call's argument is
less than n).
However the algorithm does consume a lot of memory. When we calculate
fib(n), the values of fib(0) to fib(n) are stored in main memory, though this
can be improved, there is no need as the memory cost has fallen drastically.
The O(1) running time of subsequent calls is lost since the values aren't
stored. The value of fib(n) only depends on fib(n-1) and fib(n-2) hence we
can discard the other values by going bottom-up. If we want to calculate
fib(n), we first have to calculate fib(2) = fib(0) + fib(1). We then need to
calculate fib(3) by adding fib(1) and fib(2). After this we can discard the
values of fib(0) and fib(1), since we no longer need them to calculate any
further values. Now we can calculate fib(4) from fib(2) and fib(3) and discard
fib(2), then we can calculate fib(5) and discard fib(3) and so on. The
pseudocode to do this is given below.
Pseudocode to calculate nth Fibonacci number (Dynamic
programming approach):
function fib(integer n): integer
if n == 0 or n == 1:
return n
fi
let u := 0
let v := 1
for i := 2 to n:
let t := u + v
u := v
v := t
repeat
return v
end
We can rework the code to store the values in an array for subsequent calls,
but, we don't have to. This method is typical for dynamic programming in
which we first identify the sub-problems that we need to solve in order to
solve the entire problem. Then using iterative process on bottom-up we can
calculate the values.
Activity 1
Find the various instances in the natural world where you find the
presence of the Fibonacci number and discuss it with your friends.
Pascal’s triangle
Pascal’s triangle helps you determine the coefficients which arise in
binomial expansion. For example consider equation Eq: 10.7
(x+y)2 = x2 + 2xy + y2 = 1x2y0 + 2x1y1 + 1x0y2 Eq: 10.7
You might notice that the coefficients are numbers in the row of Pascal’s
triangle shown in the Figure 10.3.
0: 1
1: 1 1
2: 1 2 1
3: 1 3 3 1
4: 1 4 6 4 1
5: 1 5 10 10 5 1
6: 1 6 15 20 15 6 1
7: 1 7 21 35 35 21 7 1
8: 1 8 28 56 70 56 28 8 1
Hence in general when x+y is raised to a positive integer we can give the
following equation Eq: 10.8
(x y) n a 0 x n a 1 x n 1 y a 2 x n 2 y 2 a n 1 xy n 1 a n y n
Eq: 10.8
where the coefficients ai in this expansion are exactly the numbers on row n
of Pascal's triangle and are written as equation Eq: 10.9, which is the
binomial theorem.
n
ai
i
Eq: 10.9
You will notice that the entire right diagonal of Pascal's triangle is the
coefficient of yn in the binomial expansions, while the next diagonal is the
coefficient of xyn−1 and so on.
Dynamic programming approach
Computing a binomial coefficient is a standard example of applying dynamic
programming to a non-optimization problem. You may recall from your
studies of elementary combinatorics that the binomial coefficient, denoted
n
by C (n,k) or k is the number of combinations (subsets) of k elements from
an n-element set (0 ≤ k ≤ n). The name “binomial coefficients” comes from
the participation of these numbers in the binomial formula given in equation
Eq: 10.10
(a b) n C (n,0)a n C (n.k )a nk b k C (n, n)b n Eq: 10.10
Of the many properties of binomial coefficients, we concentrate on two given
by equation Eq: 10.11
C (n, k ) C (n 1, k 1) C(n 1, k ) for n > k > 0
and Eq: 10.11
C (n,0) C (n, n) 1
The nature of recurrence, which expresses the problem of computing C(n,k)
in terms of smaller and overlapping problems of computing C(n-1,k-1) and
C(n-1,k), lends itself to solving by dynamic programming technique. To do
this, we record the values of the binomial coefficients in a table of n+1 rows
and k+1 columns, numbered from 0 to n and from 0 to k respectively.
To compute C(n,k), we fill the table in the Figure 10.4 row by row, starting
with row 0 and ending with row n. Each row i (0 ≤ i ≤ n) is filled left to right,
starting with 1 because C(n, 0)= 1. Rows 0 through k also end with 1 on the
table’s main diagonal: C(i,i)=1 for 0 ≤ i ≤ k . We compute the other entries by
the formula to calculate C(n,k), by adding the contents of the cells in the
preceding row and the previous column and in the preceding row and the
same column. It is precisely the implementation of Pascal’s triangle.
Algorithm to find binomial coefficient (n, k)
//Computes C(n,k) by the dynamic programming algorithm
//Input: A pair of nonnegative integers n ≥ k ≥ 0
for i ← 0 to n do
for j ← 0 to min (i,k) do
if j = 0 or j = i
C[i,j] ← 1
else C[i,j] ← C[i-1 , j-1 ] + C[i-1 , j]
return C[n , k]
We can clearly see that the algorithm’s basic operation is addition, so let A
(n,k) be the total number of additions made by the this algorithm in
computing C (n,k) . Note that computing each entry by formula of C (n,k)
requires just one addition. Also note that because the first k+1 rows of the
table form a triangle while the remaining n-k rows form a rectangle, we have
to split the sum expressing A (n,k) into two parts as shown by the equation
Eq: 10.13.
k i 1 n k k k
A(n, k ) 1 1 (i 1) k
i 1 j 1 i k 1 j 1 i 1 i k 1
(k 1)k
k (n k ) (nk )
2 Eq: 10.13
0 1 2 … k-1 k
0 1
1 1 1
2 1 2 1
:
k 1 1
:
n-1 1 C(n-1, k) C(n-1, k-1) C(n,k)
N 1
Figure 10.4: Table for Computing Binomial Coefficient C(n,k)
The figure 10.4 shows the table for computing binomial coefficient.
a b c d
a b c d
a b
a 0 1 0 0 a 1 1 1 1
b 0 0 0 1 b 1 1 1 1
A T
c d c 0 0 0 0 c 0 0 0 0
d 1 0 1 0 d 1 1 1 1
(a) (b) (c)
Figure 10.5: (a) Digraph (b) Its Adjacency Matrix (c) Its Transitive Closure
We can generate transitive closure of the digraph with the help of depth-first
search or breadth-first search. Performing either traversal starting at the ith
vertex gives the information about the vertices reachable from the ith vertex
and hence the columns that contain ones in the ith row of the transitive
closure. Thus by doing such a traversal for every vertex as a starting point
we obtain the transitive closure in its eternity.
has one more vertex to use as intermediate for its paths than its
predecessor and hence may, but does not necessarily have to, contain
more ones. R(4) being the final matrix in the series, reflects paths that can
use all n vertices of the digraph as intermediate and hence is the digraph’s
transitive closure.
a b
c d
a b c d
a 1 1 1 1
b1 1 1 1
R ( 4)
c
0 0 0 0
d
1 1 1 1
Figure 10.7: Application of Warshall’s Algorithm to the Digraph
The central part of the algorithm is that we can compute all the elements of
each matrix R(k) from its immediate predecessor R(k-1) in the series, like in
(k )
the example we can compute R(4) from R(3). Let rij , the element in the ith
row and the jth column of matrix R(k) , be equal to 1. This means that there
exists a path from the ith row and the jth vertex vj with each intermediate
vertex numbered not higher than k.
vi, a list of intermediate vertices each numbered not higher than k, vj.
Two situations regarding this path are possible. In the first, the list of its
intermediate vertices does not contain kth vertex. Then this path from vi to vj
( k 1)
has intermediate vertices numbered not higher than k-1, and therefore rij
is equal to 1 as well.
3 6 7
c d
1
(a)
a b c d
a b c d
a 0 3 a 0 10 3 4
b b2 0 5 6
W 2 0 D
c 1 c 1
7
7 0 7 0
d 6 0 d
6 16 9 0
(b) (c)
Figure 10.8: (a) Digraph (b) Its Weight Matrix (c) its Distance Matrix
The all-pairs-shortest paths problem asks to find the distances from each
vertex to all other vertices in a weighted connected graph. For our
convenience to record the lengths of the shortest paths we use an n x n
matrix D called the distance matrix. The element dij in the ith row and the jth
column of this matrix indicates the length of the shortest path from the i th
vertex to the jth vertex (1≤ i, j ≤ n). An example of this is shown in Figure
10.8 where 10.8 (a) is the digraph, 10.8 (b) the weight matrix, and 10.8
(c) the distance matrix.
Floyd’s algorithm computes the distance matrix of a weighted graph with a
series of n x n matrices as given in equation Eq: 10.14
D0,…..D(k-1),D(k),…..,D(n) Eq: 10.14
Each of these matrices contains the length of the shortest paths with certain
constraints on the paths considered for the matrix in question. Specifically,
Sikkim Manipal University B1480 Page No. 223
Analysis and Design of Algorithms Unit 10
(k )
the element d ij in the ith row and the jth column of matrix D(k) (k=0,1,…,n) is
equal to the length of the shortest path among all paths from the ith vertex to
the jth vertex with each intermediate vertex, if any, are not numbered higher
than k. In particular, the series starts with D(0) which does not allow any
intermediate vertices in its path; hence D(0) is nothing but the weight matrix
of the graph, like in the example in the figure 10.9. The last matrix in the
series, D(n) contains the length of the shortest paths among all the paths that
can use all n vertices as immediate. This is nothing but the distance matrix
being sought.
As in Warshall’s algorithm, we can compute all the elements of each matrix
(k )
D(k) from its immediate predecessor D(k-1) in series. Let d ij be the element
(k )
in the ith row and the jth column of matrix D(k) . Hence d ij is the length of the
shortest path among all the paths from the ith vertex vi to the jth vertex vj with
their intermediate vertices numbered not higher than k.
vi, a list of intermediate vertices each numbered not higher than k, vj.
We can partition all such paths into two disjoint subsets, those that do not
use the kth vertex vk as intermediate and those that do. Since the paths of
the first subset have their intermediate vertices numbered not higher than
k-1, the shortest of them is by definition of our matrices of length d ik( k 1) .
If the graph does not contain a cycle of a negative length, our attention gets
focused only to the paths in the second subset that uses vertex vk as its
intermediate vertex exactly once. All such paths have the following form.
vi, vertices numbered ≤ k-1, vk, vertices numbered ≤ k-1, vj.
In other words, each of the paths is made up of a path from vi to vk with each
intermediate vertex numbered not higher than k-1 and a path from vk to vj
with each intermediate vertex numbered not higher than k-1.
Since the length of the shortest path from vi to vk among the paths that use
the intermediate vertices numbered not higher than k-1 and a path from vk to
vj with each intermediate vertex numbered not higher than k-1 is equal to
d kj( k 1) and the length of the shortest path from vk to vj among the paths that
use intermediate vertices numbered not higher than k-1 is equal to
( k 1)
d ik( k 1) + d kj . Taking into account the lengths of the shortest paths in both
subsets, lead to the recurrence shown by equation Eq: 10.15.
d ij( k 1) = min { d ij( k 1) , d ik( k 1) + d ikj( k 1) } for k≥ 1, d ij0 =wij Eq: 10.15
Putting it another way, the element in the ith row and the jth column of the
current distance matrix D(k-1) is replaced by the sum of elements in the same
row i and the kth column and in the same column j and the kth column if and
only if the latter sum is smaller than its existing value.
Floyd’s algorithm: W[1…n,1…n]
//Implements Floyd’s algorithm for all-pairs shortest-path problem
//Input: The weight matrix W of a graph with no negative-length cycle
//Output: The distance matrix of the shortest paths’ lengths
D ← W //is not necessary if W can be over written
for k ←1 to n do
for i ← 1 to n do
For j ← 1 to n do
D[i , j] ← min{ D[i,j], D[i,k]+D[k,j]}
return D
Activity 2
Write a pseudocode to find the weight matrix and the distance matrix for
a digraph.
10.6 Summary
In this unit we have learned about the dynamic programming technique
which is a widely acclaimed tool used in applied mathematics and in
computer science wherein it is regarded as a general algorithm design.
In dynamic programming we have learned the technique to solve
overlapping problems by solving the sub-problem only once, and storing the
result in a table as we have seen in the case of Fibonacci numbers.
Similarly we have also learnt to apply the concept to find the binomial
coefficient and find solutions to the transitive closure and shortest path
problems utilizing Warshall’s and Floyd’s algorithms respectively.
The remaining concepts like principle of optimality, optimal binary search,
Knapsack problem and memory functions will be covered in the next unit,
which will help you to broaden your horizon of dynamic programming.
10.7 Glossary
Term Description
Adjacency matrix Representation of the vertices of a graph adjacent to the
other vertices.
Combinatorics Combinatorics is a branch of mathematics concerning the
study of finite or countable discrete structures.
Digraph A digraph is short for directed graph, and it is a diagram
composed of points called vertices (nodes) and arrows called
arcs going from a vertex to a vertex.
10.9 Answers
Self Assessment Questions
1. Top-down, bottom-up
2. Memoization
3. Divide and conquer
n (1 ) n
4. F (n)
5
5. O(n)
6. Bottom-up
n n!
7. Ck
n
k (n k )!k!
8. Complex n and k
9. Combinatorics
10. θ(n3)
11. Transitive closure
12. Shortest path
Terminal Questions
1. Refer to10.2.3 – Dynamic programming Vs divide and conquer
2. Refer to 10.3.2 – Algorithm to find nth Fibonacci number
3. Refer to 10.4.2 – Computation of binomial coefficients
4. Refer to 10.5.2 – Warshall’s algorithm
5. Refer to 10.5.3 – Floyd’s algorithm
References
Anany V. Levetin (2002). Introduction to the analysis and design of
algorithms. Addison-Wesley Longman Publishing Co.
Cormen, Thomas H., &Charles E. Leiserson., &Ronald L Rivest.,
&Clifford Stein (2006). Introduction to algorithms, 2nd Edition, PHI
E-References
http://mathworld.wolfram.com/BinomialCoefficient.html
http://students.ceid.upatras.gr/%7Epapagel/project/kef5_6.htm