Unit 4
Unit 4
13-mark questions:
1. Explain about dynamic programming and its elements.
A dynamic programming algorithm will examine the previously solved sub problems
and will combine their solutions to give the best solution for the given problem.
Dynamic programming is used where we have problems, which can be divided into
similar sub-problems, so that their results can be re-used. Mostly, these algorithms are
used for optimization. Before solving the in-hand sub-problem, dynamic algorithm will
try to examine the results of the previously solved sub-problems. The solutions of sub-
problems are combined in order to achieve the best solution. It is used when the solution
can be recursively described in terms of solutions to subproblems (optimal
substructure).
The following computer problems can be solved using dynamic programming approach
–
• Fibonacci sequence
• 0-1 knapsack problem
• Longest Common Subsequence problem
• Matrix chain multiplication
• All pairs shortest path problem
• Subset-sum problem
• Optimal Binary Search Tree
In the case of any dynamic programming problem, when we divide the problem, the
subproblems are not independent, instead the solutions to these subproblems are used
to compute the values for the larger problems.
These repeating subproblems are used again and again and hence their results are
stored. If the results of subproblems are not stored, we would have to compute them
again and again, which will be very costly.
By storing the solutions of sub-problems in extra memory space, we thereby reduce the
time taken to calculate the complete solution for a problem. This method of storing the
answers to similar subproblems is called memoization.
Because of this, dynamic programming can solve a vast variety of problems involving
optimal solutions, which means finding the best case out of all. In these cases, also,
dynamic programming takes help of smaller subproblems to reach to the final optimal
solution.
In Dynamic Programming, we analyse all subproblems to find out the most optimal
solution, but because we store these values, no subproblem is recomputed. This
increases the efficiency of a Dynamic Programming solution and also assures a correct
solution as we are basically checking all the cases, but efficiently.
Two characteristics that a problem has for a Dynamic Programming solution to work
are:
1. Optimal Substructure
2. Overlapping Subproblems
Optimal Substructure
Taking the example of the Fibonacci Sequence again, the nth term is given by :
So, to find the nth term, we need to find the correct solution for the (n-1) th and
the (n-2)th term, which are smaller subproblems of the complete problem. And again
to find the (n-1)th term and the (n-2)th term, we need to find the solution to even
smaller subproblems.
Overlapping Subproblems
From this, it is clear that the subproblems F(n-2), F(n-3), and F(n-4) are used again and
again to find the final solution.
1. Stages
2. States and state variables
3. State Transition
4. Optimal Choice
1. Stages
When a complex problem is divided into several subproblems, each subproblem forms
a stage of the solution. After calculating the solution for each stage and choosing the
best ones we get to the final optimized solution.
Each subproblem can be associated with several states. States differ in their solutions
because of different choices. A state for a subproblem is therefore defined more clearly
based on a parameter, which is called the state variable. It is possible in some problems,
that one than one variable is needed to define a state distinctly. In these cases, there are
more than one state variables.
3. State Transition
State Transition simply refers to how one subproblem relates to other subproblems. By
using this state transition, we calculate our end solution.
4. Optimal Choice
At each stage, we need to choose the option which leads to the most desirable solution.
Choosing the most desirable option at every stage will eventually lead to an optimal
solution in the end.
Elements Of Dynamic Programming
1. Substructure
2. Table Structure
3. Bottom-Up Computation (Memoization)
• To solve a given complex problem and to find its optimal solution, it is broken
down into similar but smaller and easily computable problems called
subproblems. Hence, the complete solution depends on many smaller problems
and their solutions. We get to the final optimal solution after going through all
subproblems and selecting the most optimal ones. This is the substructure
element of any Dynamic Programming solution.
• Any Dynamic Programming solution involves storing the optimal solutions of
the subproblems so that they don't have to be computed again and again. To
store these solutions a table structure is needed. So, for example arrays in C++
or ArrayList in Java can be used. By using this structured table, the solutions of
previous subproblems are reused.
• The solutions to subproblems need to be computed first to be reused again. This
is called Bottom-Up Computation because we start storing values from the
bottom and then consequently upwards. The solutions to the smaller
subproblems are combined to get the final solution to the original problem.
2. Explain how to solve the following problems using dynamic programming with
example.
LCS Problem Statement: Given two sequences, find the length of longest
subsequence present in both of them. A subsequence is a sequence that appears in the
same relative order, but not necessarily contiguous. For example, “abc”, “abg”, “bdf”,
“aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”.
The naive solution for this problem is to generate all subsequences of both given
sequences and find the longest matching subsequence. This solution is exponential
in term of time complexity. Let us see how this problem possesses both important
properties of a Dynamic Programming (DP) Problem.
1) Optimal Substructure:
Let the input sequences be X[0..m-1] and Y[0..n-1] of lengths m and n respectively.
And let L(X[0..m-1], Y[0..n-1]) be the length of LCS of the two sequences X and Y.
Following is the recursive definition of L(X[0..m-1], Y[0..n-1]).
If last characters of both sequences match (or X[m-1] == Y[n-1]) then
L(X[0..m-1], Y[0..n-1]) = 1 + L(X[0..m-2], Y[0..n-2])
If last characters of both sequences do not match (or X[m-1] != Y[n-1]) then
L(X[0..m-1], Y[0..n-1]) = MAX ( L(X[0..m-2], Y[0..n-1]), L(X[0..m-1], Y[0..n-2]) )
Examples:
1) Consider the input strings “AGGTAB” and “GXTXAYB”. Last characters match
for the strings. So length of LCS can be written as:
L(“AGGTAB”, “GXTXAYB”) = 1 + L(“AGGTA”, “GXTXAY”)
2) Consider the input strings “ABCDGH” and “AEDFHR. Last characters do not
match for the strings. So length of LCS can be written as:
L(“ABCDGH”, “AEDFHR”) = MAX ( L(“ABCDG”, “AEDFHR”), L(“ABCDGH”,
“AEDFH”) )
So the LCS problem has optimal substructure property as the main problem can be
solved using solutions to subproblems.
2) Overlapping Subproblems:
Following is simple recursive implementation of the LCS problem. The
implementation simply follows the recursive structure mentioned above .
#include <bits/stdc++.h>
using namespace std;
/* Returns length of LCS for X[0..m-1], Y[0..n-1] */
int lcs( char *X, char *Y, int m, int n )
{
if (m == 0 || n == 0)
return 0;
if (X[m-1] == Y[n-1])
return 1 + lcs(X, Y, m-1, n-1);
else
return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n));
}
/* Driver code */
int main()
{
char X[] = "AGGTAB";
char Y[] = "GXTXAYB";
int m = strlen(X);
int n = strlen(Y);
cout<<"Length of LCS is "<< lcs( X, Y, m, n ) ;
return 0;
}
Output: Length of LCS is 4
Matrix Chain Multiplication
Given a sequence of matrices, find the most efficient way to multiply these matrices
together. The problem is not actually to perform the multiplications, but merely to
decide in which order to perform the multiplications.
We have many options to multiply a chain of matrices because matrix multiplication
is associative. In other words, no matter how we parenthesize the product, the result
will be the same. For example, if we had four matrices A, B, C, and D, we would
have:
(ABC)D = (AB)(CD) = A(BCD) = ....
However, the order in which we parenthesize the product affects the number of simple
arithmetic operations needed to compute the product, or the efficiency. For example,
suppose A is a 10 × 30 matrix, B is a 30 × 5 matrix, and C is a 5 × 60 matrix. Then,
(AB)C = (10×30×5) + (10×5×60) = 1500 + 3000 = 4500 operations
A(BC) = (30×5×60) + (10×30×60) = 9000 + 18000 = 27000 operations.
Clearly the first parenthesization requires less number of operations.
Given an array p[] which represents the chain of matrices such that the ith matrix Ai
is of dimension p[i-1] x p[i]. We need to write a function MatrixChainOrder() that
should return the minimum number of multiplications needed to multiply the chain.
Input: p[] = {10, 20, 30}
Output: 6000
There are only two matrices of dimensions 10x20 and 20x30. So there
is only one way to multiply the matrices, cost of which is 10*20*30
1) Optimal Substructure:
A simple solution is to place parenthesis at all possible places, calculate the cost for
each placement and return the minimum value. In a chain of matrices of size n, we
can place the first set of parenthesis in n-1 ways. For example, if the given chain is
of 4 matrices. let the chain be ABCD, then there are 3 ways to place first set of
parenthesis outer side: (A)(BCD), (AB)(CD) and (ABC)(D). So when we place a set
of parenthesis, we divide the problem into subproblems of smaller size. Therefore,
the problem has optimal substructure property and can be easily solved using
recursion.
Minimum number of multiplication needed to multiply a chain of size n = Minimum
of all n-1 placements (these placements create subproblems of smaller size)
2) Overlapping Subproblems
Following is a recursive implementation that simply follows the above optimal
substructure property.
Below is the implementation of the above idea:
#include <bits/stdc++.h>
using namespace std;
// Matrix Ai has dimension p[i-1] x p[i]
// for i = 1..n
int MatrixChainOrder(int p[], int i, int j)
{
if (i == j)
return 0;
int k;
int min = INT_MAX;
int count;
// place parenthesis at different places
// between first and last matrix, recursively
// calculate count of multiplications for
// each parenthesis placement and return the
// minimum count
for (k = i; k < j; k++)
{
count = MatrixChainOrder(p, i, k) + MatrixChainOrder(p, k + 1, j)+ p[i - 1] *
p[k] * p[j];
if (count < min)
min = count;
}
// Return minimum count
return min;
}
// Driver Code
int main()
{
int arr[] = { 1, 2, 3, 4, 3 };
int n = sizeof(arr) / sizeof(arr[0]);
• To construct the solution in an optimal way, this algorithm creates two sets where
one set contains all the chosen items, and another set contains the rejected items.
• A Greedy algorithm makes good local choices in the hope that the solution should
be either feasible or optimal.
Advantages
The biggest advantage that the Greedy algorithm has over others is that it is easy to
Greedy algorithm makes decisions based on the information available at each phase
without considering the broader problem. So, there might be a possibility that the
greedy solution does not give the best solution for every problem.
It follows the local optimum choice at each stage with a intend of finding the global
optimum.
Greedy Algorithm
2. At each step, an item is added to the solution set until a solution is reached.
If a Greedy Algorithm can solve a problem, then it generally becomes the best method
to solve that problem as the Greedy algorithms are in general more efficient than other
techniques like Dynamic Programming. But Greedy algorithms cannot always be
applied. For example, the Fractional Knapsack problem can be solved using Greedy,
but 0-1 Knapsack cannot be solved using Greedy.
Following are some standard algorithms that are Greedy algorithms.
The greedy algorithms are sometimes also used to get an approximation for Hard
optimization problems. For example, Traveling Salesman Problem is an NP-Hard
problem. A Greedy choice for this problem is to pick the nearest unvisited city from the
current city at every step. These solutions don’t always produce the best optimal
solution but can be used to get an approximately optimal solution.
Step 2: Extract two minimum frequency nodes from min heap. Add a new internal
node with frequency 5 + 9 = 14.
character Frequency
c 12
d 13
Internal Node 14
e 16
f 45
Step 3: Extract two minimum frequency nodes from heap. Add a new internal
node with frequency 12 + 13 = 25
Now min heap contains 4 nodes where 2 nodes are roots of trees with single
element each, and two heap nodes are root of tree with more than one nodes
character Frequency
Internal Node 14
e 16
Internal Node 25
f 45
Step 4: Extract two minimum frequency nodes. Add a new internal node with
frequency 14 + 16 = 30
Huffman Coding step 4
character Frequency
Internal Node 25
Internal Node 30
f 45
Step 5: Extract two minimum frequency nodes. Add a new internal node with
frequency 25 + 30 = 55
character Frequency
f 45
Internal Node 55
Step 6: Extract two minimum frequency nodes. Add a new internal node with
frequency 45 + 55 = 100
character code-word
f 0
c 100
d 101
a 1100
b 1101
e 111
5. Consider the given sequence {4, 10, 3, 12, 20, and 7}. The matrices have size 4 x
10, 10 x 3, 3 x 12, 12 x 20, 20 x 7. The objective is to compute M [i,j], 0 ≤ i, j≤ 5.
We know M [i, i] = 0 for all i.
It can be observed that the total entries in matrix 'C' is 'pr' as the matrix is of dimension
p x r Also each entry takes O (q) times to compute, thus the total time to compute all
possible entries for the matrix 'C' which is a multiplication of 'A' and 'B' is proportional
to the product of the dimension p q r.
It is also noticed that we can save the number of operations by reordering the
parenthesis.
Let Ai,j be the result of multiplying matrices i through j. It can be seen that the dimension
of Ai,j is pi-1 x pj matrix.
A1.....n=A1....k x Ak+1....n)
1
Thus we are left with two questions:
One possible answer to the first question for finding the best value of 'k' is to check all
possible choices of 'k' and consider the best among them. But that it can be observed
that checking all possibilities will lead to an exponential number of total possibilities. It
can also be noticed that there exists only O (n2 ) different sequence of matrices, in this
way do not reach the exponential growth
Let Ai....j where i≤ j denotes the matrix that results from evaluating the product
Ai Ai+1....Aj.
If i < j then any parenthesization of the product A i Ai+1 ......Aj must split that the product
between Ak and Ak+1 for some integer k in the range i ≤ k ≤ j. That is for some value of
k, we first compute the matrices Ai.....k & Ak+1....j and then multiply them together to
produce the final product Ai....j. The cost of computing Ai....k plus the cost of computing
Ak+1....j plus the cost of multiplying them together is the cost of parenthesization.
If i=j the chain consist of just one matrix Ai....i=Ai so no scalar multiplication are necessary
to compute the product. Thus m [i, j] = 0 for i= 1, 2, 3....n.
If i<j we assume that to optimally parenthesize the product we split it between A k and
Ak+1 where i≤ k ≤j. Then m [i,j] equals the minimum cost for computing the subproducts
Ai....k and Ak+1....j+ cost of multiplying them together. We know Ai has dimension pi-1 x pi,
so computing the product Ai....k and Ak+1....jtakes pi-1 pk pj scalar multiplication, we obtain
There are only (j-1) possible values for 'k' namely k = i, i+1.....j-1. Since the optimal
parenthesization must use one of these values for 'k' we need only check them all to
find the best.
Example: We are given the sequence {4, 10, 3, 12, 20, and 7}. The matrices have size 4 x
10, 10 x 3, 3 x 12, 12 x 20, 20 x 7. We need to compute M [i,j], 0 ≤ i, j≤ 5. We know M [i, i]
= 0 for all i.
3
In Dynamic Programming, initialization of every method done by '0'.So we initialize it by
'0'.It will sort out diagonally.
We have to sort out all the combination but the minimum output combination is taken
into consideration.
2. m (2, 3) = m2 x m3
= 10 x 3 x 3 x 12
= 10 x 3 x 12 = 360
3. m (3, 4) = m3 x m4
= 3 x 12 x 12 x 20
= 3 x 12 x 20 = 720
4. m (4,5) = m4 x m5
= 12 x 20 x 20 x 7
= 12 x 20 x 7 = 1680
o We initialize the diagonal element with equal i,j value with '0'.
4
o After that second diagonal is sorted out and we get all the values corresponded to
it
Now the third diagonal will be solved out in the same way.
M [1, 3] = M1 M2 M3
1. There are two cases by which we can solve this multiplication: ( M1 x M2) + M3, M1+
(M2x M3)
2. After solving both cases we choose the case in which minimum output is there.
M [1, 3] =264
As Comparing both output 264 is minimum in both cases so we insert 264 in table and (
M1 x M2) + M3 this combination is chosen for the output making.
M [2, 4] = M2 M3 M4
1. There are two cases by which we can solve this multiplication: (M2x M3)+M4, M2+(M3 x
M4 )
2. After solving both cases we choose the case in which minimum output is there.
M [2, 4] = 1320
As Comparing both output 1320 is minimum in both cases so we insert 1320 in table
and M2+(M3 x M4) this combination is chosen for the output making.
M [3, 5] = M3 M4 M5
1. There are two cases by which we can solve this multiplication: ( M3 x M4) + M5,
M3+ ( M4xM5)
2. After solving both cases we choose the case in which minimum output is there.
5
M [3, 5] = 1140
As Comparing both output 1140 is minimum in both cases so we insert 1140 in table
and ( M3 x M4) + M5this combination is chosen for the output making.
M [1, 4] = M1 M2 M3 M4
1. ( M1 x M2 x M3) M4
2. M1 x(M2 x M3 x M4)
3. (M1 xM2) x ( M3 x M4)
After solving these cases we choose the case in which minimum output is there
M [1, 4] =1080
As comparing the output of different cases then '1080' is minimum output, so we insert
1080 in the table and (M1 xM2) x (M3 x M4) combination is taken out in output making,
M [2, 5] = M2 M3 M4 M5
1. (M2 x M3 x M4)x M5
2. M2 x( M3 x M4 x M5)
6
3. (M2 x M3)x ( M4 x M5)
After solving these cases we choose the case in which minimum output is there
M [2, 5] = 1350
As comparing the output of different cases then '1350' is minimum output, so we insert
1350 in the table and M2 x( M3 x M4 xM5)combination is taken out in output making.
M [1, 5] = M1 M2 M3 M4 M5
1. (M1 x M2 xM3 x M4 )x M5
2. M1 x( M2 xM3 x M4 xM5)
3. (M1 x M2 xM3)x M4 xM5
4. M1 x M2x(M3 x M4 xM5)
After solving these cases we choose the case in which minimum output is there
M [1, 5] = 1344
As comparing the output of different cases then '1344' is minimum output, so we
insert 1344 in the table and M1 x M2 x(M3 x M4 x M5)combination is taken out in output
making.
7
Final Output is:
Step 3: Computing Optimal Costs: let us assume that matrix Ai has dimension pi-1x
pi for i=1, 2, 3....n. The input is a sequence (p0,p1,......pn) where length [p] = n+1. The
procedure uses an auxiliary table m [1....n, 1.....n] for storing m [i, j] costs an auxiliary
table s [1.....n, 1.....n] that record which index of k achieved the optimal costs in
computing m [i, j].
The algorithm first computes m [i, j] ← 0 for i=1, 2, 3.....n, the minimum costs for the
chain of length 1.
MATRIX-CHAIN-ORDER (p)
1. n length[p]-1
2. for i ← 1 to n
3. do m [i, i] ← 0
4. for l ← 2 to n // l is the chain length
5. do for i ← 1 to n-l + 1
6. do j ← i+ l -1
7. m[i,j] ← ∞
8. for k ← i to j-1
9. do q ← m [i, k] + m [k + 1, j] + pi-1 pk pj
10. If q < m [i,j]
11. then m [i,j] ← q
12. s [i,j] ← k
13. return m and s.
8
6. Let's try to trace the steps of above algorithm using an example:
In the table below, we have 6 activities with corresponding start and end time, the
objective is to compute an execution schedule having maximum number of non-
conflicting activities:
The Activity Selection Problem is an optimization problem which deals with the selection
of non-conflicting activities that needs to be executed by a single person or machine in
a given time frame.
Each activity is marked by a start and finish time. Greedy technique is used for finding
the solution since this is an optimization problem.
Let's consider that you have n activities with their start and finish times, the objective is
to find solution set having maximum number of non-conflicting activities that can be
executed in a single time frame, assuming that only one person or machine is available
for execution.
It might not be possible to complete all the activities, since their timings can
collapse.
Greedy approach can be used to find the solution since we want to maximize
the count of activities that can be executed. This approach will greedily choose an
activity with earliest finish time at every step, thus yielding an optimal solution.
9
Input Data for the Algorithm:
sol[] array refering to the solution set containing the maximum number of non-
conflicting activities.
Following are the steps we will be following to solve the activity selection problem,
Step 1: Sort the given activities in ascending order according to their finishing time.
Step 2: Select the first activity from sorted array act[] and add it to sol[] array.
Step 4: If the start time of the currently selected activity is greater than or equal to the
finish time of previously selected activity, then add it to the sol[] array.
In the table below, we have 6 activities with corresponding start and end time, the
objective is to compute an execution schedule having maximum number of non-
conflicting activities:
10
Start Time (s) Finish Time (f) Activity Name
5 9 a1
1 2 a2
3 4 a3
0 6 a4
5 7 a5
8 9 a6
A possible solution would be:
Step 1: Sort the given activities in ascending order according to their finishing time.
1 2 a2
3 4 a3
0 6 a4
5 7 a5
5 9 a1
8 9 a6
Step 2: Select the first activity from sorted array act[] and add it to the sol[] array,
thus sol = {a2}.
Step 3: Repeat the steps 4 and 5 for the remaining activities in act[].
Step 4: If the start time of the currently selected activity is greater than or equal to the
finish time of the previously selected activity, then add it to sol[].
Step 5: Select the next activity in act[]
For the data given in the above table,
A. Select activity a3. Since the start time of a3 is greater than the finish time
of a2 (i.e. s(a3) > f(a2)), we add a3 to the solution set. Thus sol = {a2, a3}.
B. Select a4. Since s(a4) < f(a3), it is not added to the solution set.
C. Select a5. Since s(a5) > f(a3), a5 gets added to solution set. Thus sol = {a2, a3,
a5}
D. Select a1. Since s(a1) < f(a5), a1 is not added to the solution set.
E. Select a6. a6 is added to the solution set since s(a6) > f(a5). Thus sol = {a2, a3,
a5, a6}.
Step 6: At last, print the array sol[]
Hence, the execution schedule of maximum number of non-conflicting activities will be:
11
(1,2)
(3,4)
(5,7)
(8,9)
In the above diagram, the selected activities have been highlighted in grey.
12