CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
UNIT - IV DYNAMIC PROGRAMMING AND GREEDY TECHNIQUE
Dynamic programming — Principle of optimality — Coin changing problem, Computing a Binomial
Coefficient — Floyd’s algorithm - Greedy Technique — Container loading problem — Prim’s algorithm
and Kruskal’s Algorithm.
DYNAMIC PROGRAMMING
• Dynamic programming is an optimization technique.
• Greedy vs. Dynamic Programming:
o Both techniques are optimization techniques, and both build solutions from a collection of
choices of individual elements.
o The greedy method computes its solution by making its choices in a serial forward fashion, never
looking back or revising previous choices.
o Dynamic programming computes its solution bottom up by synthesizing them from smaller
subsolutions, and by trying many possibilities and choices before it arrives at the optimal set of
choices.
o There is no a priori litmus test by which one can tell if the Greedy method will lead to an optimal
solution.
o By contrast, there is a litmus test for Dynamic Programming, called The Principle of Optimality
PRINCIPLE OF OPTIMALITY
Definition: A problem is said to satisfy the Principle of Optimality if the subsolutions of an
optimal solution of the problem are themesleves optimal solutions for their subproblems.
Examples:
The shortest path problem satisfies the Principle of Optimality. This is because if
a,x1,x2,...,xn,b is a shortest path from node a to node b in a graph, then the portion of xi to xj on
that path is a shortest path from xi to xj.
The longest path problem, on the other hand, does not satisfy the Principle of Optimality.
Steps of Dynamic Programming
• Dynamic programming design involves 4 major steps:
1. Develop a mathematical notation that can express any solution and subsolution for the problem
at hand.
2. Prove that the Principle of Optimality holds.
3. Develop a recurrence relation that relates a solution to its subsolutions, using the math notation
of step 1. Indicate what the initial values are for that recurrenec relation, and which term signifies
the final solution.
4. Write an algorithm to compute the recurrence relation.
• Steps 1 and 2 need not be in that order. Do what makes sense in each problem.
Prepared by JASPIN K, AP/CSE, SJIT 1
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
• Step 3 is the heart of the design process. In high level algorithmic design situations, one can stop at step
3. In this course, however, we will carry out step 4 as well.
• Without the Principle of Optimality, it won't be possible to derive a sensible recurrence relation in step
3.
• When the Principle of Optimality holds, the 4 steps of DP are guaranteed to yield an optimal solution.
No proof of optimality is needed.
Approaches of dynamic programming
• There are two approaches to dynamic programming:
1. Top-down approach
2. Bottom-up approach
1) Top-down approach
The top-down approach follows the memorization technique, while bottom-up approach follows
the tabulation method. Here memorization is equal to the sum of recursion and caching. Recursion means
calling the function itself, while caching means storing the intermediate results.
Let's understand this approach through an example.
Consider an example of the Fibonacci series. The following series is the Fibonacci series:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ,…
The numbers in the above series are not randomly calculated. Mathematically, we could write each of the
terms using the below formula:
F(n) = F(n-1) + F(n-2),
With the base values F(0) = 0, and F(1) = 1. To calculate the other numbers, we follow the above relationship.
For example, F(2) is the sum f(0) and f(1), which is equal to 1.
The following are the steps that the dynamic programming follows:
o It breaks down the complex problem into simpler subproblems.
o It finds the optimal solution to these sub-problems.
o It stores the results of subproblems (memoization). The process of storing the results of subproblems
is known as memorization.
o It reuses them so that same sub-problem is calculated more than once.
o Finally, calculate the result of the complex problem.
Prepared by JASPIN K, AP/CSE, SJIT 2
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
Suppose we want to calculate the fibonacci sequence of f (5). The Fibonacci sequence of f (5) is 0, 1, 1, 2, 3, 5.
There is a total 15 calls to be used to calculate f (15).
Let's understand dynamic programming through an example.
1. int fib(int n)
2. {
3. if(n<0)
4. error;
5. if(n==0)
6. return 0;
7. if(n==1)
8. return 1;
9. sum = fib(n-1) + fib(n-2);
10. }
In the above code, we have used the recursive approach to find out the Fibonacci series. When the value of 'n'
increases, the function calls will also increase, and computations will also increase. In this case, the time
complexity increases exponentially, and it becomes 2n.
When we apply the dynamic programming approach in the implementation of the Fibonacci series, then the code
would look like:
1. static int count = 0;
2. int fib(int n)
3. {
4. if(memo[n]!= NULL)
Prepared by JASPIN K, AP/CSE, SJIT 3
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
5. return memo[n];
6. count++;
7. if(n<0)
8. error;
9. if(n==0)
10. return 0;
11. if(n==1)
12. return 1;
13. sum = fib(n-1) + fib(n-2);
14. memo[n] = sum;
15. }
In the above code, we have used the memorization technique in which we store the results in an array to reuse
the values. This is also known as a top-down approach in which we move from the top and break the problem
into sub-problems.
Advantages
o It is very easy to understand and implement.
o It solves the subproblems only when it is required.
o It is easy to debug.
Disadvantages
It uses the recursion technique that occupies more memory in the call stack. Sometimes when the recursion is too
deep, the stack overflow condition will occur.
It occupies more memory that degrades the overall performance.
2) Bottom-Up approach
The bottom-up approach is also one of the techniques which can be used to implement the dynamic
programming. It uses the tabulation technique to implement the dynamic programming approach. It solves
the same kind of problems but it removes the recursion. If we remove the recursion, there is no stack overflow
issue and no overhead of the recursive functions. In this tabulation technique, we solve the problems and store
the results in a matrix.
The bottom-up is an algorithm that starts from the beginning, whereas the recursive algorithm starts from
the end and works backward. In the bottom-up approach, we start from the base case to find the answer for
the end. As we know, the base cases in the Fibonacci series are 0 and 1. Since the bottom approach starts from
the base cases, so we will start from 0 and 1.
Prepared by JASPIN K, AP/CSE, SJIT 4
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
Key points
o We solve all the smaller sub-problems that will be needed to solve the larger sub-problems then move
to the larger problems using smaller sub-problems.
o We use for loop to iterate over the sub-problems.
o The bottom-up approach is also known as the tabulation or table filling method.
Let's understand through an example.
Suppose we have an array that has 0 and 1 values at a[0] and a[1] positions, respectively shown as below:
Since the bottom-up approach starts from the lower values, so the values at a[0] and a[1] are added to find the
value of a[2] shown as below:
The value of a[3] will be calculated by adding a[1] and a[2], and it becomes 2 shown as below:
The value of a[4] will be calculated by adding a[2] and a[3], and it becomes 3 shown as below:
The value of a[5] will be calculated by adding the values of a[4] and a[3], and it becomes 5 shown as below:
Prepared by JASPIN K, AP/CSE, SJIT 5
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
The code for implementing the Fibonacci series using the bottom-up approach is given below:
1. int fib(int n)
2. {
3. int A[];
4. A[0] = 0, A[1] = 1;
5. for( i=2; i<=n; i++)
6. {
7. A[i] = A[i-1] + A[i-2]
8. }
9. return A[n];
10. }
In the above code, base cases are 0 and 1 and then we have used for loop to find other values of Fibonacci
series.
One solution to this problem is to use the dynamic programming approach. Rather than generating the
recursive tree again and again, we can reuse the previously calculated value. If we use the dynamic
programming approach, then the time complexity would be O(n).
The following is the list of differences between tabulation and memoization:
Tabulation Memoization
In tabulation, the state transition is difficult to create. State transition relation is easy to create.
Code becomes difficult when lots of conditions are Code is not complicated and it's not difficult
needed. to create.
It is fast as the result of the previously solved sub- It is slow because lots of recursive calls and
problems can be directly accessed from the table. return statements are required.
In a tabulated version, all the entries must be filled one by In a memoized version, entries in the table are
one. filled on demand.
Application: The All-Pairs Shortest Path Problem
• Input: A weighted graph, represented by its weight matrix W.
• Problem: find the distance between every pair of nodes.
• Dynamic programming Design:
o Notation: A(k)(i,j) = length of the shortest path from node i to node j where the label of every
intermediary node is ≤ k.
A(0)(i,j) = W[i,j].
Prepared by JASPIN K, AP/CSE, SJIT 6
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
o Principle of Optimality: We already saw that any sub-path of a shortest path is a shortest path
between its end nodes.
o recurrence relation:
o Divide the paths from i to j where every intermediary node is of label ≤k into two groups:
1. Those paths that do not go through node k
2. Those paths that do go through node k.
Warshall’s Algorithm
The adjacency matrix A = {aij } of a directed graph is the boolean matrix that has 1 in its ith row and j th
column if and only if there is a directed edge from the ith vertex to the j th vertex.
DEFINITION
The transitive closure of a directed graph with n vertices can be defined as the n × n boolean matrix T =
{tij }, in which the element in the ith row and the j th column is 1 if there exists a nontrivial path (i.e., directed
path of a positive length) from the ith vertex to the j th vertex; otherwise, tij is 0.
Prepared by JASPIN K, AP/CSE, SJIT 7
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
Floyd’s Algorithm for the All-Pairs Shortest-Paths Problem
Given a weighted connected graph (undirected or directed), the all-pairs shortestpaths problem asks to
find the distances—i.e., the lengths of the shortest paths— from each vertex to all other vertices.
It is convenient to record the lengths of shortest paths in an n × n matrix D called the distance matrix: the
element dij in the ith row and the j th column of this matrix indicates the length of the shortest path from the ith
vertex to the j th vertex. We can generate the distance matrix with an algorithm that is very similar to Warshall’s
algorithm. It is called Floyd’s algorithm after its co-inventor Robert W. Floyd.
Prepared by JASPIN K, AP/CSE, SJIT 8
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
The Floyd-Warshall algorithm is a dynamic programming algorithm used to discover the shortest paths
in a weighted graph, which includes negative weight cycles. The algorithm works with the aid of computing the
shortest direction between every pair of vertices within the graph, the usage of a matrix of intermediate vertices
Example 1:
Prepared by JASPIN K, AP/CSE, SJIT 9
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
Example 2:
Floyd-Warshall is an algorithm used to locate the shortest course between all pairs of vertices in a weighted graph.
It works by means of keeping a matrix of distances between each pair of vertices and updating this matrix
iteratively till the shortest paths are discovered.
Let's see at an example to illustrate how the Floyd-Warshall algorithm works:
Consider the following weighted graph:
Figure: A Weighted Graph
The set of rules works as follows:
1. Initialize a distance matrix D wherein D[i][j] represents the shortest distance between vertex i and vertex
j.
2. Set the diagonal entries of the matrix to 0, and all other entries to infinity.
3. For every area (u,v) inside the graph, replace the gap matrix to mirror the weight of the brink: D[u][v] =
weight(u,v).
Prepared by JASPIN K, AP/CSE, SJIT 10
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
4. For every vertex okay in the graph, bear in mind all pairs of vertices (i,j) and check if the path from i to j
through k is shorter than the current best path. If it is, update the gap matrix: D[i][j] = min(D[i][j], D[i][k]
D[k][j]).
5. After all iterations, the matrix D will contain the shortest course distances between all pairs of vertices.
In this graph, the vertices are represented by letters (A, B, C, D), and the numbers on the edges represent the
weights of those edges.
To follow the Floyd-Warshall algorithm to this graph, we start by way of initializing a matrix of distances among
every pair of vertices. If two vertices are immediately related by using a side, their distance is the load of that
edge. If there may be no direct edge among vertices, their distance is infinite.
In the first iteration of the set of rules, we keep in mind the possibility of the usage of vertex 1 (A) as an
intermediate vertex in paths among all pairs of vertices. If the space from vertex 1 to vertex 2 plus the space from
vertex 2 to vertex three is much less than the present-day distance from vertex 1 to vertex three, then we replace
the matrix with this new distance. We try this for each possible pair of vertices.
In the second iteration, we recollect the possibility to use of vertex 2 (B) as an intermediate vertex in paths among
all pairs of vertices. We replace the matrix in the same manner as earlier before.
Prepared by JASPIN K, AP/CSE, SJIT 11
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
In the third iteration, we consider the possibility of using vertex 3 (C) as an intermediate vertex in paths between
all pairs of vertices.
Finally, in the fourth and final iteration, we consider the possibility of using vertex 4 (D) as an intermediate vertex
in paths between all pairs of vertices.
After the fourth iteration, we have got the shortest path between every pair of vertices in the graph. For example,
the shortest path from vertex A to vertex D is 4, which is the value in the matrix at row A and column D.
After the fourth iteration, we have got the shortest path between every pair of vertices in the graph. For example,
the shortest path from vertex A to vertex D is 4, which is the value in the matrix at row A and column D.
COIN CHANGE PROBLEM
Given an integer array of coins[ ] of size N representing different types of denominations and an integer sum,
the task is to count all combinations of coins to make a given value sum.
Note: Assume that you have an infinite supply of each type of coin.
Examples:
Input: sum = 4, coins[] = {1,2,3},
Output: 4
Explanation: there are four solutions: {1, 1, 1, 1}, {1, 1, 2}, {2, 2}, {1, 3}.
Input: sum = 10, coins[] = {2, 5, 3, 6}
Output: 5
Explanation: There are five solutions:
{2,2,2,2,2}, {2,2,3,3}, {2,2,6}, {2,3,5} and {5,5}.
Prepared by JASPIN K, AP/CSE, SJIT 12
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
Prepared by JASPIN K, AP/CSE, SJIT 13
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
COMPUTING A BINOMIAL COEFFICIENT
Dynamic Programming Binomial Coefficients
Dynamic Programming was invented by Richard Bellman, 1950. It is a very general technique for solving
optimization problems. Using Dynamic Programming requires that the problem can be divided into overlapping
similar sub-problems. A recursive relation between the larger and smaller sub problems is used to fill out a table.
The algorithm remembers solutions of the sub-problems and so does not have to recalculate the solutions.
Also optimal problem must have the principle of optimality (phase coined by Bellman) meaning that the
optimal problem can be posed as the optimal solution among sub problems. This is not true for all optimal
problems.
Dynamic Programming requires:
1. Problem divided into overlapping sub-problems
2. Sub-problem can be represented by a table
3. Principle of optimality, recursive relation between smaller and larger problems
Prepared by JASPIN K, AP/CSE, SJIT 14
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
Compared to a brute force recursive algorithm that could run exponential, the dynamic programming
algorithm runs typically in quadratic time. (Recall the algorithms for the Fibonacci numbers.) The recursive
algorithm ran in exponential time while the iterative algorithm ran in linear time. The space cost does increase,
which is typically the size of the table. Frequently, the whole table does not have to store.
Space and time efficient Binomial Coefficient
Here the function takes two parameters n and k and returns the value of Binomial Coefficient C (n, k).
Example:
Input: n = 4 and k = 2
Output: 6
Explanation: 4 C 2 is 4!/(2!*2!) = 6
Input: n = 5 and k = 2
Output: 10
Explanation: 5 C 2 is 5!/(3!*2!) = 10
Complexity Analysis:
We have discussed O(n*k) time and O(k) extra space algorithm in this post. The value of C(n, k) can be
calculated in O(k) time and O(1) extra space.
C[i][j] = C[i-1][j-1] + C[i-1][j]
C[0][0] = 1
int binomialCoeff (int i, int j)
{
if(j == 0 || j == i) return 1;
return binomialCoeff (i-1, j-1) + binomialCoeff (i-1, j);
}
Recursion tree for the above relation would look something like,
Prepared by JASPIN K, AP/CSE, SJIT 15
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
It is evident that there is some re-computation going on, which will increase exponentially as we input greater
values. Therefore, we memoize the results of overlapping subproblems in-order to avoid the re-computation. A
simple bottom-up DP approach is shown below:
This C[i][j] gives us the number of ways to select j numbers from a set of i positions. Let’s visualize the dp table
for the above computation.
Notice that, the bottom-up approach pushes the values gradually towards the cell which we are looking for.
Although DP is intuitive to think about in terms of a recursive algorithm, the iterative approach is used more by
most programmers due to its simplicity.
Prepared by JASPIN K, AP/CSE, SJIT 16
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
The Complexity of Coin Change Problem
• The complexity of solving the coin change problem using recursive time and space will be:
Problems: Overlapping subproblems + Time complexity
O(2n) is the time complexity, where n is the number of coins
• Time and space complexity will be reduced by using dynamic programming to solve the coin change
problem:
O(numberOfCoins*TotalAmount) time complexity
O(numberOfCoins*TotalAmount) is the space complexity.
GREEDY TECHNIQUE (2 MARKS)
Solving optimization problem with Greedy Algorithm
Optimization Problem: Given an Optimization Function and a set of constraints, find an optimal
solution.
Feasible Solution: [Solution that satisfies the constraints] Solutions to such a problem that satisfy the
constraints are called feasible solutions.
Optimal Solution: A feasible solution for which the optimization function has the best possible value.
Optimization Problem
A problem in which some function (called the optimization or objective function) is to be optimized
(usually minimized or maximized) subject to some constraints.
Feasible And Optimal Solutions
A feasible solution is a solution that satisfies the constraints.
An optimal solution is a feasible solution that optimizes the objective/optimization function.
Greedy Method
Solve problem by making a sequence of decisions.
Decisions are made one by one in some order.
Each decision is made using a greedy criterion.
A decision, once made, is (usually) not changed later.
The greedy method
It is one way to construct a feasible solution for such optimization problems, and, sometimes, it leads
to an optimal one.
Prepared by JASPIN K, AP/CSE, SJIT 17
CS4401 Datastructures and Algorithms-II / Unit IV Department of CSE
When applying this method, we construct a solution in stages.
At each stage, we make a decision that appears to be the best at that time, according to a certain greedy
criterion.
Such a decision will not be changed in later stages. Hence, each decision should assume the feasibility.
Sometimes, such a locally optimal solution does lead to an overall optimal one.
CONTAINER LOADING PROBLEM
• A large ship is to be loaded with containers of cargos. Different containers, although of equal size, will
have different weights. Objective is to load the ship with maximum number of containers without
exceeding the cargo weight capacity.
• The constraint is that the container loaded have total weight ≤the cargo weight capacity, and the objective
function is to find a larget set of containersto load.
Difference between Prims and Kruskal Algorithm
S.No. Prim’s Algorithm Kruskal’s Algorithm
1 This algorithm begins to construct the This algorithm begins to construct the shortest
shortest spanning tree from any vertex spanning tree from the vertex having the lowest
in the graph. weight in the graph.
2 To obtain the minimum distance, it It crosses one node only one time.
traverses one node more than one time.
3 The time complexity of Prim’s algorithm The time complexity of Kruskal’s algorithm is O(E
is O(V2). log V).
4 In Prim’s algorithm, all the graph Kruskal’s algorithm may have disconnected
elements must be connected. graphs.
5 When it comes to dense graphs, the When it comes to sparse graphs, Kruskal’s
Prim’s algorithm runs faster. algorithm runs faster.
6 It prefers list data structure. It prefers the heap data structure.
Prepared by JASPIN K, AP/CSE, SJIT 18
Greedy Technique