Solving the Knapsack Problem with Code Examples

Solving the Knapsack Problem

The Knapsack Problem is a classic optimization problem in computer science and mathematics. The goal is to maximize the value of items placed in a knapsack without exceeding its weight capacity. This problem has many variations, but the most common are:

  1. 0/1 Knapsack Problem: Each item can either be included or excluded.
  2. Fractional Knapsack Problem: Items can be divided to maximize value.

This article focuses on the 0/1 Knapsack Problem and demonstrates its solution using dynamic programming.

Problem Background

The Knapsack Problem has its origins in combinatorial optimization, with applications in resource allocation, cryptography, and logistics. It was first formally defined in the early 20th century, but systematic methods for solving it, like dynamic programming, were developed by Richard Bellman in the 1950s. Bellman’s pioneering work laid the foundation for solving many optimization problems efficiently.

Problem Statement

Given:

  • n items, each with a weight w[i] and value v[i].
  • A knapsack with a maximum weight capacity W.

Find the maximum total value that can be obtained by selecting a subset of the items such that their total weight does not exceed W.

Dynamic Programming Approach

The idea is to solve the problem by breaking it into smaller subproblems and storing the results to avoid redundant calculations.

Recurrence Relation

Let dp[i][w] represent the maximum value attainable with the first i items and a knapsack capacity w.

  1. If the current item is not included:dp[i][w] = dp[i-1][w]The maximum value remains the same as when considering the first i-1 items.
  2. If the current item is included (provided its weight w[i-1] is less than or equal to w):dp[i][w] = max(dp[i-1][w], v[i-1] + dp[i-1][w – w[i-1]])The maximum value is the greater of:
    • Not including the current item.
    • Including the current item and adding its value to the maximum value obtainable with the remaining capacity.

Algorithm Steps

  1. Initialize a 2D array dp with dimensions (n+1) x (W+1) and set all values to 0.
  2. Iterate over items (i from 1 to n) and weights (w from 1 to W).
  3. For each combination, use the recurrence relation to update dp[i][w].
  4. The maximum value is stored in dp[n][W].

Implementation

#include <stdio.h>

// Function to solve the 0/1 Knapsack problem
int knapsack(int W, int n, int weights[], int values[]) {
    int dp[n + 1][W + 1];

    // Initialize the DP table
    for (int i = 0; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            if (i == 0 || w == 0)
                dp[i][w] = 0;
            else if (weights[i - 1] <= w)
                dp[i][w] = (values[i - 1] + dp[i - 1][w - weights[i - 1]] > dp[i - 1][w])
                              ? values[i - 1] + dp[i - 1][w - weights[i - 1]]
                              : dp[i - 1][w];
            else
                dp[i][w] = dp[i - 1][w];
        }
    }

    return dp[n][W];
}

int main() {
    int n = 4; // Number of items
    int values[] = {60, 100, 120, 50};
    int weights[] = {10, 20, 30, 5};
    int W = 50; // Knapsack capacity

    printf("Maximum value: %d\n", knapsack(W, n, weights, values));
    return 0;
}

Code Explanation

  1. Input:
    • values: An array of item values.
    • weights: An array of item weights.
    • W: The maximum weight the knapsack can carry.
  2. Output: Maximum value that fits within the knapsack capacity.
  3. Complexity:
    • Time Complexity: The time complexity of this algorithm is n multiplied by W, where n is the number of items, and W is the capacity of the knapsack.
    • Space Complexity: The space complexity of this algorithm is n multiplied by W because it uses a two-dimensional array to store intermediate results.

Knapsack Example

Input:

  • Items:
    • Values: {60, 100, 120, 50}
    • Weights: {10, 20, 30, 5}
  • Knapsack capacity: 50

Output:

Maximum value: 220

Optimized Space Complexity

The above implementation can be optimized to use only a single-dimensional array because the current state depends only on the previous row.

int knapsackOptimized(int W, int n, int weights[], int values[]) {
    int dp[W + 1];

    // Initialize DP array
    for (int w = 0; w <= W; w++) {
        dp[w] = 0;
    }

    // Fill DP array
    for (int i = 0; i < n; i++) {
        for (int w = W; w >= weights[i]; w--) {
            dp[w] = (values[i] + dp[w - weights[i]] > dp[w]) ? values[i] + dp[w - weights[i]] : dp[w];
        }
    }

    return dp[W];
}

Complexity

  1. Time Complexity: The time complexity of the optimized algorithm is n multiplied by W, where n is the number of items, and W is the capacity of the knapsack.
  2. Space Complexity: The space complexity of the optimized algorithm is W because it uses a single-dimensional array to store the results of the current state.

Key Takeaways

  • The Knapsack Problem demonstrates the power of dynamic programming for optimization problems.
  • A systematic approach to breaking down problems into subproblems ensures efficiency and correctness.
  • Space-optimized solutions can be crucial for handling large input sizes.

By understanding the algorithm and its origins you can apply the knapsack solution framework to various real-world problems.

References

M. Saqib: Saqib is Master-level Senior Software Engineer with over 14 years of experience in designing and developing large-scale software and web applications. He has more than eight years experience of leading software development teams. Saqib provides consultancy to develop software systems and web services for Fortune 500 companies. He has hands-on experience in C/C++ Java, JavaScript, PHP and .NET Technologies. Saqib owns and write contents on mycplus.com since 2004.
Related Post