Given two integer arrays to represent weights and profits of ‘N’ items, we need to find a subset of these items which will give us maximum profit such that their cumulative weight is not more than a given number ‘C’. We can assume an infinite supply of item quantities; therefore, each item can be selected multiple times.
Brute force Solution
for each item 'i' create a new set which includes one quantity of item 'i' if it does not exceed the capacity, and recursively call to process all items create a new set without item 'i', and recursively process the remaining items return the set from the above two sets with higher profit
using namespace std; #include <iostream> #include <vector> class Knapsack { public: int solveKnapsack(const vector<int> &profits, const vector<int> &weights, int capacity) { return this->knapsackRecursive(profits, weights, capacity, 0); } private: int knapsackRecursive(const vector<int> &profits, const vector<int> &weights, int capacity, int currentIndex) { // base checks if (capacity <= 0 || profits.empty() || weights.size() != profits.size() || currentIndex >= profits.size()) { return 0; } // recursive call after choosing the items at the currentIndex, note that we recursive call on // all items as we did not increment currentIndex int profit1 = 0; if (weights[currentIndex] <= capacity) { profit1 = profits[currentIndex] + knapsackRecursive(profits, weights, capacity - weights[currentIndex], currentIndex); } // recursive call after excluding the element at the currentIndex int profit2 = knapsackRecursive(profits, weights, capacity, currentIndex + 1); return max(profit1, profit2); } }; int main(int argc, char *argv[]) { Knapsack *ks = new Knapsack(); vector<int> profits = {15, 50, 60, 90}; vector<int> weights = {1, 3, 4, 5}; cout << ks->solveKnapsack(profits, weights, 8) << endl; cout << ks->solveKnapsack(profits, weights, 6) << endl; }
The time complexity of the above algorithm is exponential , where ‘N’ represents the total number of items. The space complexity will be to store the recursion stack.
Let’s try to find a better solution.
Top-down Dynamic Programming with Memoization
Once again, we can use memoization to overcome the overlapping sub-problems.
We will be using a two-dimensional array to store the results of solved sub-problems. As mentioned above, we need to store results for every sub-array and for every possible capacity. Here is the code:
using namespace std; #include <iostream> #include <vector> class Knapsack { public: int solveKnapsack(const vector<int> &profits, const vector<int> &weights, int capacity) { vector<vector<int>> dp(profits.size(), vector<int>(capacity + 1)); return this->knapsackRecursive(dp, profits, weights, capacity, 0); } private: int knapsackRecursive(vector<vector<int>> &dp, const vector<int> &profits, const vector<int> &weights, int capacity, int currentIndex) { // base checks if (capacity <= 0 || profits.empty() || weights.size() != profits.size() || currentIndex >= profits.size()) { return 0; } // check if we have not already processed a similar sub-problem if (!dp[currentIndex][capacity]) { // recursive call after choosing the items at the currentIndex, note that we // recursive call on all items as we did not increment currentIndex int profit1 = 0; if (weights[currentIndex] <= capacity) { profit1 = profits[currentIndex] + knapsackRecursive(dp, profits, weights, capacity - weights[currentIndex], currentIndex); } // recursive call after excluding the element at the currentIndex int profit2 = knapsackRecursive(dp, profits, weights, capacity, currentIndex + 1); dp[currentIndex][capacity] = max(profit1, profit2); } return dp[currentIndex][capacity]; } }; int main(int argc, char *argv[]) { Knapsack *ks = new Knapsack(); vector<int> profits = {15, 50, 60, 90}; vector<int> weights = {1, 3, 4, 5}; cout << ks->solveKnapsack(profits, weights, 8) << endl; cout << ks->solveKnapsack(profits, weights, 6) << endl; }
What is the time and space complexity of the above solution? Since our memoization array dp[profits.length][capacity+1]
stores the results for all the subproblems, we can conclude that we will not have more than subproblems (where ‘N’ is the number of items and ‘C’ is the knapsack capacity). This means that our time complexity will be .
The above algorithm will be using space for the memoization array. Other than that we will use space for the recursion call-stack. So the total space complexity will be , which is asymptotically equivalent to .
Bottom-up Dynamic Programming
Let’s try to populate our dp[][]
array from the above solution, working in a bottom-up fashion. Essentially, what we want to achieve is: “Find the maximum profit for every sub-array and for every possible capacity”.
So for every possible capacity ‘c’ (0 <= c <= capacity), we have two options:
- Exclude the item. In this case, we will take whatever profit we get from the sub-array excluding this item:
dp[index-1][c]
- Include the item if its weight is not more than the ‘c’. In this case, we include its profit plus whatever profit we get from the remaining capacity:
profit[index] + dp[index][c-weight[index]]
Finally, we have to take the maximum of the above two values:
dp[index][c] = max (dp[index-1][c], profit[index] + dp[index][c-weight[index]])
using namespace std; #include <iostream> #include <vector> class Knapsack { public: int solveKnapsack(const vector<int> &profits, const vector<int> &weights, int capacity) { // base checks if (capacity <= 0 || profits.empty() || weights.size() != profits.size()) { return 0; } int n = profits.size(); vector<vector<int>> dp(n, vector<int>(capacity + 1)); // populate the capacity=0 columns for (int i = 0; i < n; i++) { dp[i][0] = 0; } // process all sub-arrays for all capacities for (int i = 0; i < n; i++) { for (int c = 1; c <= capacity; c++) { int profit1 = 0, profit2 = 0; if (weights[i] <= c) { profit1 = profits[i] + dp[i][c - weights[i]]; } if (i > 0) { profit2 = dp[i - 1][c]; } dp[i][c] = profit1 > profit2 ? profit1 : profit2; } } // maximum profit will be in the bottom-right corner. return dp[n - 1][capacity]; } }; int main(int argc, char *argv[]) { Knapsack *ks = new Knapsack(); vector<int> profits = {15, 50, 60, 90}; vector<int> weights = {1, 3, 4, 5}; cout << ks->solveKnapsack(profits, weights, 8) << endl; cout << ks->solveKnapsack(profits, weights, 6) << endl; delete ks; }