Dynamic Programming Integer Knapsack
Integer Knapsack Problem
Welcome! This is part 10 in a series of articles on Dynamic Programming. You can refer to the first part (introduction) by clicking here. Today we are going to discuss the “Integer Knapsack Problem”. Please recall that our problem today is a little bit different from the “01 Knapsack Problem” in which duplicate items are not allowed. The later one is harder and requires more work so please keep that in mind. Now let us get started.
Problem Definition
We have (n) types of items. Each item (i) has a size (Si) and a value (Vi). Duplicate items are allowed. In other words we can use one or more items of the same type. We need to pack some items in a knapsack of capacity (C) such that we get the maximum value without exceeding the capacity of the knapsack.
Solution
Assume we have a knapsack with a capacity of (4). Assume we have (3) items with the following sizes (2, 3, 4) and values (3, 7, 1). Let us try to fill the knapsack hoping we can achieve maximum value:
- We can pack just the 1st item (2) and get a value of (3)
- We can pack just the 2nd item (3) and get a value of (7)
- We can pack just the 3nd item (4) and get a value of (1)
- We can pack two copies of the 1st item (2) and get a value of (6)
So the solution to our problem in this case is filling the knapsack with just the second item (3) yielding a maximum value of (7). Here is a schematic diagram to clarify that:
This problem can be solved using a brute force approach as we did in the example above but it is not going to be an efficient solution. On the other hand, this problem can be solved efficiently using Dynamic Programming as it satisfies all the conditions needed to employ a DP technique. So let us do that:
Characterize the structure of an optimal solution
Our goal is to maximize the total value we can possibly get from packing some items in a knapsack of the given capacity. We need to express this value in terms of smaller size problems of the same structure. Think of the knapsack as a container having (j <= C) empty slots each of size (1). Assume we have already filled the knapsack of capacity (j <= C) such that we achieved maximum value. We have two possibilities regarding position (j). It should be either empty or filled with some item. If position (j) is empty then the maximum value we can get is the same as that when filling a knapsack of size (j-1). If position (j) is filled with some item (i) then the maximum value we can get is the value of this item (Vi) plus the maximum value we can get by filling a knapsack of size (j-Si). Since we have multiple items to select from we choose the item that gives us the largest value. Here is a schematic diagram to clarify the second case:
Recursively define the value of an optimal solution
In the first step we characterized the structure of an optimal solution in plain English. Let us now do that formally. Let M(j) be the maximum value we can get from packing some items (subset of the (n) items) into a knapsack of size (j). M(j) can be expressed recursively as:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
M(j) = { //Trivial case. Zero size knapsack yields zero value 0 if j = 0 //We have two cases: //(1) when the jth slot is empty then M(j) = M(j-1) //(2) when the jth slot is occupied by some item (i) //in this case M(j) = MAX (Vi + M(j-Si)) for all (i) values //Note that (j) goes from (1) to (C) //Note that j-Si >= 0 because the size must not be negative MAX (M(j-1), MAX (Vi + M(j-Si))) if 1 <= j <= C and j-Si >= 0 } |
Let us try to apply the recursive formula above to our example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
S[i]: 2 3 4 V[i]: 3 7 1 C = 4 M(0) = 0 M(1) = Max (M(0), Max (Null because 1-2 or 1-3 or 1-4 all negative)) = M(0) = 0 M(2) = Max (M(1), Max (3 + M(0))) = Max (0, 3) = 3 M(3) = Max (M(2), Max (3 + M(1), 7 + M(0))) = Max (3, 7) = 7 M(4) = Max (M(3), Max (3 + M(2), 7 + M(1), 1 + M(0))) = Max (7, Max (6, 7, 1)) = Max (7, 7) = 7 |
As you can see the formula works just fine. The value we get by filling a knapsack of capacity (4) is the same as the value we get by filling a knapsack of capacity (3) because the last slot is kept empty. Note also that sub problems repeat but we have learned from this series that Dynamic Programming saves computed values in a table so that they are used again. In the next step we will compute these values and store them in a table by following a bottom up approach. Once the table M(j) is completely populated we can easily compute the maximum value by simply returning M(C). In order to know which items go to the knapsack we need to use back pointers so let us do that now. Let b(j) point to the previous slot with respect to position (j) that gives us the maximum value. Obviously b(j) is either (j-1) if the jth slot is empty and (j-Si) if the jth slot is occupied by some item (i).
Compute the value of an optimal solution in a bottom up fashion
The following pseudo code populates the values array M(j) and the back pointers array b(j) in a bottom up fashion starting at the initial case when (j=0) then building up all other values. The code is properly commented so take a look:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
//Assume the array V[i] //contains the values of the items //Assume the array S[i] //contains the sizes of the items //Trivial case when (j=0) //the value we get is also zero M[0] = 0; //For each slot (j) in the knapsack do for (int j = 1; j <= C; j++) { //M[j] will be max1 (or M[j-1]) //if the jth slot is empty int max1 = M[j-1]; //M[j] will be max2 if the jth //slot is occupied by some item //Initialize max2 to some small number int max2 = -999999; //This is used to mark the previous (j) //slot if the jth slot is occupied int mark = 0; //Search for an item to occupy the jth //slot such that it gives us maximum value for (int i = 1; i <= n; i++) { //For each item (i) calculate (V[i] + M[j - S[i]]) //then compare it to the current max. If it is greater //then update the current max. Only those items satisfying //the condition (j - S[i] >= 0) are checked because capacity //should not be negative if (V[i] + M[j - S[i]] > max2 && j - S[i] >= 0) { //Update the max max2 = V[i] + M[j - S[i]]; //Save the previous (j) position //that gives us the maximum value mark = j - S[i]; } } //Case1: jth slot is empty if (max1 > max2) { M[j] = max1; b[j] = j-1; } //Case 2: jth slot is occupied else { M[j] = max2; b[j] = mark; } } |
As you can see we have two nested loops. The first loop runs (n) times and the second loop runs (C) times so the running time of the algorithm is O(n x C) where (n) is the number of items and (C) is the knapsack capacity. The maximum value we can get by filling the knapsack is M(C) but in order to print out the individual items that we need to pack, we need to follow the back pointers saved in b(j). The following section explains how to do that.
Construct an optimal solution from the computed information
In order to print out the individual items that we need to pack in the knapsack we follow the back pointers stored in b(j) starting at (j = C). Just call the following recursive function as:
1 |
PrintPositions(b, C); |
The pseudo code below prints out the (j) positions at which the packed items start and end positions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//Function receives two parameters. //The first parameters is the back //pointers array. The second parameter //is the (j) value to start at PrintPositions(int b[], int j) { //Stopping point if (j == 0) { return; } else { //Print (j) value print("Position = ", j); //Call the function using the //previous (j) position stored //in b[j] PrintPosition(b, b[j]); } } |
We are done with part 10. In the next part I will explain another Dynamic Programming problem. Please use the comments section below for questions, corrections or feedback. Thanks for reading.
Awesome tutorials!!!
Loved them.Thanks a lot!
Logged in to just comment!!
😀
Sounds very good. that is actually the whole purpose of the blog. Have fun, learn and spread knowledge. I have allot to add but I do not have much spare time however I will be adding more stuff. Based on my experience, if you know something it is better to explain it to the others because it is going to be hard for first timers. Do please spread the news about this hidden blog on the net 🙂 that would give me the energy to add more. thanks for visiting.
Great job. it’s too much helpful.
g8
awesome keep it up…
Good Explanation..But optimal Solution can be better explained.
V[i] + M[j – S[i]] > max2 && j – S[i] >= 0 Following condition is incorrect. First check j – S[i] >= 0 in condition as it can also be negative which might result in negative index resulting in array index out of bounds exception.