0/1 Knapsack (medium)
Dry-run templates
This is the implementation of the solution provided for the problem posed in the 0/1 Knapsack (medium) lesson using Top-down Dynamic Programming with Memoization appraoch:
def solve_knapsack(profits, weights, capacity):# create a two dimensional array for Memoization, each element is initialized to '-1'dp = [[-1 for x in range(capacity+1)] for y in range(len(profits))]return knapsack_recursive(dp, profits, weights, capacity, 0)def knapsack_recursive(dp, profits, weights, capacity, currentIndex):# base checksif capacity <= 0 or currentIndex >= len(profits):return 0# if we have already solved a similar problem, return the result from memoryif dp[currentIndex][capacity] != -1:return dp[currentIndex][capacity]# recursive call after choosing the element at the currentIndex# if the weight of the element at currentIndex exceeds the capacity, we# shouldn't process thisprofit1 = 0if weights[currentIndex] <= capacity:profit1 = profits[currentIndex] + knapsack_recursive(dp, profits, weights, capacity - weights[currentIndex], currentIndex + 1)# recursive call after excluding the element at the currentIndexprofit2 = knapsack_recursive(dp, profits, weights, capacity, currentIndex + 1)dp[currentIndex][capacity] = max(profit1, profit2)return dp[currentIndex][capacity]def main():print(solve_knapsack([1, 6, 10, 16], [1, 2, 3, 5], 7))print(solve_knapsack([1, 6, 10, 16], [1, 2, 3, 5], 6))main()
Students may be encouraged to run through the provided solution with the following sample inputs:
Sample input #1
profits: [1, 2, 5, 7] weights: [1, 2, 3, 4] capacity: 5
Recursive Call | Line No. | currentIndex | capacity | profit1 | profit2 |
4 | 0 | 5 | |||
knapsack_recursive -------------------------- Computing profit1 at index 1 | 22 | 1 | 4 | - | - |
knapsack_recursive -------------------------- Computing profit1 at index 2 | 22 | 2 | 2 | ||
Got profit 1 at index 2 | 22 | 2 | 2 | 0 | - |
knapsack_recursive -------------------------- Computing profit1 at index 3 | 22 | 3 | 2 | ||
Got profit 1 at index 3 | 22 | 3 | 2 | 0 | - |
knapsack_recursive -------------------------- Computing profit2 at index 4 | 26 | 4 | 2 | ||
knapsack_recursive -------------------------- Computing profit2 at index 3 | 26 | 3 | 2 | 0 | 0 |
knapsack_recursive -------------------------- Computing profit2 at index 2 | 26 | 2 | 2 | 0 | 0 |
Got profit1 at index 1 | 22 | 1 | 4 | 2 | - |
knapsack_recursive -------------------------- Computing profit2 at index 2 | 26 | 2 | 4 | ||
knapsack_recursive -------------------------- Computing profit1 at index 3 | 22 | 3 | 1 | ||
Got profit1 at index 3 | 22 | 3 | 1 | 0 | |
knapsack_recursive -------------------------- Computing profit2 at index 4 | 26 | 4 | 1 | 0 | |
Got profit2 at index 3 | 26 | 3 | 1 | 0 | 0 |
Got profit1 at index 2 | 22 | 2 | 4 | 5 | |
knapsack_recursive -------------------------- Computing profit1 at index 3 | 22 | 3 | 4 | ||
knapsack_recursive -------------------------- Computing profit1 at index 4 | 22 | 4 | 0 | 0 | |
Got profit1 at index 3 | 22 | 3 | 4 | 7 | |
knapsack_recursive -------------------------- Computing profit2 at index 4 | 26 | 4 | 4 | ||
Got profit2 at index 3 | 26 | 3 | 4 | 7 | 0 |
Got profit2 at index 2 | 26 | 2 | 4 | 5 | 7 |
Got profit2 at index 1 | 26 | 1 | 4 | 2 | 7 |
Got profit1 at index 0 | 22 | 0 | 5 | 8 | |
knapsack_recursive -------------------------- Computing profit2 at index 1 | 26 | 1 | 5 | ||
knapsack_recursive -------------------------- Computing profit2 at index 2 | 26 | 2 | 3 | ||
knapsack_recursive -------------------------- Computing profit2 at index 3 | 26 | 3 | 0 | ||
Got profit1 at index 2 | 22 | 2 | 3 | 5 | |
knapsack_recursive -------------------------- Computing profit2 at index 3 | 26 | 3 | 3 | ||
Got profit1 at index 3 | 26 | 3 | 3 | 0 | |
knapsack_recursive -------------------------- Computing profit2 at index 4 | 26 | 4 | 3 | ||
Got profit2 at index 3 | 26 | 3 | 3 | 0 | 0 |
Got profit2 at index 2 | 26 | 2 | 3 | 5 | 0 |
Got profit1 at index1 | 22 | 1 | 5 | 7 | |
knapsack_recursive -------------------------- Computing profit2 at index 1 | 26 | 1 | 5 | 7 | |
knapsack_recursive -------------------------- Computing profit2 at index 2 | 26 | 2 | 5 | ||
knapsack_recursive -------------------------- Computing profit1 at index 2 | 22 | 2 | 2 | ||
Got profit1 at index 2 | 22 | 2 | 2 | 5 | |
knapsack_recursive -------------------------- Computing profit2 at index 3 | 26 | 3 | 5 | ||
knapsack_recursive -------------------------- Computing profit2 at index 4 | 26 | 4 | 1 | ||
Got profit 1 at index 3 | 22 | 3 | 5 | 7 | |
knapsack_recursive -------------------------- Computing profit2 at index 4 | 26 | 4 | 5 | ||
Got profit2 at index 3 | 26 | 3 | 5 | 7 | 0 |
Got profit2 at index 2 | 26 | 2 | 2 | 5 | 7 |
Got profit2 at index 1 | 26 | 1 | 5 | 7 | 7 |
Got profit2 at index 2 | 26 | 0 | 5 | 8 | 7 |
Sample input #2
Students may be encouraged to complete the dry-run with this input:
profits: [1, 2, 3] weights: [1, 2, 3] capacity: 4
Recursive Call | Line No. | currentIndex | capacity | profit1 | profit2 |
- | 4 | 0 | 4 | - | - |
knapsack_recursive -------------------------- Computing profit1 at index 0 | 22 | 1 | 3 | - | - |
knapsack_recursive -------------------------- Computing profit1 at index 1 | 22 | 2 | 1 | - | - |
... | ... | ... | ... | ... | ... |
Step-by-step solution construction
We have three main parts of the solution:
- Writing the base conditions for the recursive code
- Recursively computing profit in two alternative ways:
- While including the profit of the current item
- While excluding the profit of the current item
- Utilizing the profits computed earlier for a given
capacity
andweight
.
In the code below, on lines 40-42, we define the base conditions:
- There is still capacity in the knapsack
- The capacity will eventually reach a negative value as we will be subtracting
weights[current_index]
fromcapacity
at each recursive call. - There are items still left to consider
We will be calling the function recursively by incrementing the current_index
, so we will eventually reach the end of the profits
array.
On lines 45-47, we implement the memoization check: if we have already computed the profit for the given current_index
and capacity
, we simply return the value stored in the matrix dp.
This works because, at the end of each recursive call, we store the profit for the input current_index
and capacity
(see line 62).”
On lines 53-57, we start to implement the second part of the solution:
- If the
weight
of the current item is less than the remainingcapacity
of the knapsack, we issue the recursive command to calculate theprofit
with the current item included (lines 56-57). Note that in making the call, we reduce the capacity of the knapsack by the weight of the current item (capacity - weights[current_index]
) and move to the next item in the list (current_index + 1
).
Note: The function
print_matrix
has been added to print the matrixdp
that stores the profits already calculated. This function does not contribute to the solution itself.
def solve_knapsack(profits, weights, capacity):# create a two dimensional array for Memoization, each element is initialized to '-1'dp = [[-1 for x in range(capacity+1)] for y in range(len(profits))]print("Initialized a two dimensional array dp: ")print_matrix(dp,0)result = knapsack_recursive(dp, profits, weights, capacity, 0)return resultdef print_matrix(dp,current_index):print()print("\t"*current_index," ",end=" ")for k in range(len(dp[0])):print(k,end=" ")print()print("\t"*current_index,"-"*40)for i in range(len(dp)):print("\t"*current_index,i,end= " | ")for j in range(len(dp[i])):if(j == len(dp[i])-1):if(dp[i][j]<0 or dp[i][j]>9):print(dp[i][j])else:print ("",dp[i][j])else:if(dp[i][j]<0 or dp[i][j]>9):print(dp[i][j],end=" ")else:print ("",dp[i][j],end=" ")# print("\n")def knapsack_recursive(dp, profits, weights, capacity, current_index):print("\t"*current_index, "Method knapsack_recursive called with parameters:",end=" ")print("current_index: ",current_index,end=" ")print(" ","capacity: ",capacity)# base checksif capacity <= 0 or current_index >= len(profits):print("\t"*current_index, "failed base checks")return 0# if we have already solved a similar problem, return the result from memoryif dp[current_index][capacity] != -1:print("\t"*current_index,"We already know the profit:", dp[current_index][capacity])return dp[current_index][capacity]# recursive call after choosing the element at the current_index# if the weight of the element at current_index exceeds the capacity, we# shouldn't process thisprofit1 = 0if weights[current_index] <= capacity:print("\t"*current_index, " Computing profit while including item at index", current_index)#print("\t"*current_index,"profits[current_index]: ",profits[current_index])profit1 = profits[current_index] + knapsack_recursive(dp, profits, weights, capacity - weights[current_index], current_index + 1)print("\t"*current_index, " Profit when capacity is", capacity, "and item at index", current_index, "is included:", profit1)else:print("\t"*current_index, " Current item is too heavy (weight: ", weights[current_index], ") for the knapsack. Hence the profit is 0.", sep="")dp[current_index][capacity] = profit1print("\t"*current_index,"dp, after updating: ", end="")print_matrix(dp, current_index)return dp[current_index][capacity]def main():knapsacks = [[[1, 2, 5, 7], [1, 2, 3, 4], 5],[[1,2,3],[1,2,3],4], [[1, 6, 10, 16], [1, 2, 3, 5], 7]]for knapsack in knapsacks:print("Solving for the following parameters: \nProfits: ",knapsack[0]," Weights: ",knapsack[1]," Capacity: ",knapsack[2])result = solve_knapsack(knapsack[0], knapsack[1], knapsack[2])print("\nMaximum profit: ",result)print("-"*100+"\n")main()
Clearly, our solution is not complete and we are not finding the combinations of items that give us the most profit. So, let’s complete the second part of the solution:
- Computing the profit while excluding the current item. This is done through a second recursive call (see lines 64–65) where the capacity of the knapsack remains unchanged, though we do still move to the next item.
Since we have two profits, profit1
and profit2
, arising from two mutually exclusive possibilities, we will choose the greater of the two and update the dp
matrix (line 73).
def solve_knapsack(profits, weights, capacity):# create a two dimensional array for Memoization, each element is initialized to '-1'dp = [[-1 for x in range(capacity+1)] for y in range(len(profits))]print("Initialized a two dimensional array dp: ")print_matrix(dp,0)result = knapsack_recursive(dp, profits, weights, capacity, 0)return resultdef print_matrix(dp,current_index):print()print("\t"*current_index," ",end=" ")for k in range(len(dp[0])):print(k,end=" ")print()print("\t"*current_index,"-"*40)for i in range(len(dp)):print("\t"*current_index,i,end= " | ")for j in range(len(dp[i])):if(j == len(dp[i])-1):if(dp[i][j]<0 or dp[i][j]>9):print(dp[i][j])else:print ("",dp[i][j])else:if(dp[i][j]<0 or dp[i][j]>9):print(dp[i][j],end=" ")else:print ("",dp[i][j],end=" ")# print("\n")def knapsack_recursive(dp, profits, weights, capacity, current_index):print("\t"*current_index,"Method knapsack_recursive called with parameters:",end=" ")print("current_index: ",current_index,end=" ")print(" ","capacity: ",capacity)# base checksif capacity <= 0 or current_index >= len(profits):print("\t"*current_index, "failed base checks")return 0# if we have already solved a similar problem, return the result from memoryif dp[current_index][capacity] != -1:print("\t"*current_index,"We already know the profit:", dp[current_index][capacity])return dp[current_index][capacity]# recursive call after choosing the element at the current_index# if the weight of the element at current_index exceeds the capacity, we# shouldn't process thisprofit1 = 0if weights[current_index] <= capacity:print("\t"*current_index, " Computing profit while including item at index", current_index)#print("\t"*current_index,"profits[current_index]: ",profits[current_index])profit1 = profits[current_index] + knapsack_recursive(dp, profits, weights, capacity - weights[current_index], current_index + 1)print("\t"*current_index, " Profit when capacity is", capacity, "and item at index", current_index, "is included:", profit1)else:print("\t"*current_index, " Current item is too heavy (weight: ", weights[current_index], ") for the knapsack. Hence the profit from it is 0.", sep="")# recursive call after excluding the element at the current_indexprint("\t"*current_index," Computing profit while excluding item at index", current_index)profit2 = knapsack_recursive(dp, profits, weights, capacity, current_index + 1)print("\t"*current_index, " Profit when capacity is", capacity, "and item at index", current_index, "is excluded:", profit2)in_ex = ""if profit1 > profit2:in_ex = "in"else:in_ex = "ex"print("\t"*current_index, " With capacity at ", capacity, ", we make more profit by ", in_ex, "cluding the item at index ", current_index, sep="")dp[current_index][capacity] = max(profit1, profit2)print("\t"*current_index,"dp, after updating: ")print_matrix(dp, current_index)return dp[current_index][capacity]def main():knapsacks = [[[1, 2, 5, 7], [1, 2, 3, 4], 5],[[1,2,3],[1,2,3],4], [[1, 6, 10, 16], [1, 2, 3, 5], 7]]for knapsack in knapsacks:print("Solving for the following parameters: \nProfits: ",knapsack[0]," Weights: ",knapsack[1]," Capacity: ",knapsack[2])result = solve_knapsack(knapsack[0], knapsack[1], knapsack[2])print("\nMaximum profit: ",result)print("-"*100+"\n")main()