Knapsack & Variants - CSU083 | Shoolini University

Knapsack & Variants

1. Prerequisites

Understanding the Knapsack problem and its variants requires knowledge of the following fundamental concepts:

2. What is the Knapsack Problem?

The Knapsack problem is a combinatorial optimization problem where we aim to maximize the value of items placed in a knapsack without exceeding its weight capacity.

2.1 Variants of Knapsack Problem

3. Why Does This Algorithm Exist?

The Knapsack problem models real-world scenarios where limited resources need to be allocated for maximum benefit:

4. When Should You Use It?

5. How Does It Compare to Alternatives?

5.1 Strengths

5.2 Weaknesses

6. Basic Implementation

6.1 0/1 Knapsack Problem (Dynamic Programming Approach)

The 0/1 Knapsack problem is solved using dynamic programming by storing results of subproblems in a table to avoid recomputation.


def knapsack(weights, values, capacity):
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    # Build DP table
    for i in range(1, n + 1):
        for w in range(capacity + 1):
            if weights[i - 1] <= w:
                dp[i][w] = max(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][capacity]

# Example usage
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 5
print(knapsack(weights, values, capacity))  # Output: 7

7. Dry Run

7.1 Input

Given:

7.2 Step-by-Step Execution

We construct a DP table of size (n+1) x (capacity+1) and fill it row-wise.

"
i \ w 0 1 2 3 4 5
0 0 0 0 0 0 0
1 (Item weight=2, value=3) 0 0 3 3 3 3
2 (Item weight=3, value=4) 0 0 3 4 4 7
3 (Item weight=4, value=5) 0 0 3 4 5 7
4 (Item weight=5, value=6) 0 0 3 4 5 7

7.3 Final Answer

The maximum value that can be obtained with a capacity of 5 is 7.

8. Time & Space Complexity Analysis

8.1 Worst-Case Complexity

8.2 Best-Case Complexity

8.3 Average-Case Complexity

9. Space Complexity Analysis

Space consumption depends on the approach used:

9.1 0/1 Knapsack

9.2 Fractional Knapsack

10. Trade-Offs

10.1 0/1 Knapsack

10.2 Fractional Knapsack

10.3 General Considerations

11. Optimizations & Variants

11.1 Common Optimizations

11.2 Different Versions of the Algorithm

11.3 Space-Optimized DP Implementation

This reduces the 2D DP table to a 1D array:


def knapsack_optimized(weights, values, capacity):
    dp = [0] * (capacity + 1)

    for i in range(len(weights)):
        for w in range(capacity, weights[i] - 1, -1):
            dp[w] = max(dp[w], values[i] + dp[w - weights[i]])

    return dp[capacity]

# Example usage
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 5
print(knapsack_optimized(weights, values, capacity))  # Output: 7

12. Comparing Iterative vs. Recursive Implementations

12.1 Recursive Implementation (Exponential Time Complexity)

A direct recursive approach follows a brute-force strategy:


def knapsack_recursive(weights, values, capacity, n):
    if n == 0 or capacity == 0:
        return 0
    
    if weights[n - 1] > capacity:
        return knapsack_recursive(weights, values, capacity, n - 1)
    
    include = values[n - 1] + knapsack_recursive(weights, values, capacity - weights[n - 1], n - 1)
    exclude = knapsack_recursive(weights, values, capacity, n - 1)
    
    return max(include, exclude)

# Example usage
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 5
print(knapsack_recursive(weights, values, capacity, len(weights)))  # Output: 7

12.2 Iterative Implementation (Polynomial Time Complexity)

The iterative approach uses dynamic programming to fill a DP table.


def knapsack_iterative(weights, values, capacity):
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for w in range(capacity + 1):
            if weights[i - 1] <= w:
                dp[i][w] = max(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][capacity]

# Example usage
print(knapsack_iterative(weights, values, capacity))  # Output: 7

12.3 Efficiency Comparison

"
Approach Time Complexity Space Complexity Pros Cons
Recursive O(2^n) O(n) (recursion stack) Simple implementation Highly inefficient for large inputs
Recursive (Memoized) O(nW) O(nW) Eliminates redundant computations High memory usage
Iterative (DP) O(nW) O(nW) Guaranteed optimal solution Consumes large space
Iterative (Space Optimized DP) O(nW) O(W) Memory-efficient Overwrites previous values

12.4 Key Takeaways

13. Edge Cases & Failure Handling

13.1 Common Pitfalls and Edge Cases

14. Writing Test Cases

14.1 Python Unit Tests


import unittest

class TestKnapsack(unittest.TestCase):
    def test_zero_capacity(self):
        self.assertEqual(knapsack_optimized([], [], 0), 0)

    def test_empty_items(self):
        self.assertEqual(knapsack_optimized([], [], 10), 0)

    def test_all_items_too_heavy(self):
        self.assertEqual(knapsack_optimized([10, 20, 30], [100, 200, 300], 5), 0)

    def test_single_item_fits_exactly(self):
        self.assertEqual(knapsack_optimized([5], [50], 5), 50)

    def test_multiple_items_same_weight_different_values(self):
        self.assertEqual(knapsack_optimized([2, 2, 2], [10, 20, 30], 2), 30)

    def test_all_zero_values(self):
        self.assertEqual(knapsack_optimized([1, 2, 3], [0, 0, 0], 5), 0)

    def test_normal_case(self):
        self.assertEqual(knapsack_optimized([2, 3, 4, 5], [3, 4, 5, 6], 5), 7)

if __name__ == '__main__':
    unittest.main()

15. Real-World Failure Scenarios

15.1 Financial Portfolio Optimization

Incorrect implementation in portfolio selection could lead to suboptimal asset allocation, reducing returns.

15.2 Cloud Resource Allocation

If not properly optimized, cloud service costs could exceed budget due to inefficient resource selection.

15.3 Logistics and Packing

Suboptimal packing algorithms may lead to wasted space in shipping containers, increasing transport costs.

15.4 Cybersecurity and Cryptographic Vulnerabilities

Incorrect implementations in cryptographic knapsack algorithms could compromise encryption security.

15.5 AI & Machine Learning Model Selection

Feature selection using a faulty knapsack implementation may lead to poor model accuracy.

15.6 Key Takeaways

16. Real-World Applications & Industry Use Cases

16.1 Finance & Investment Optimization

Knapsack is used in portfolio optimization where an investor must allocate a fixed budget across different assets to maximize returns.

16.2 Cloud Computing & Resource Allocation

Cloud platforms allocate virtual machines and storage based on cost and performance constraints, using knapsack-style optimization.

16.3 Logistics & Supply Chain Management

Knapsack helps determine the optimal selection of packages in trucks and shipping containers to maximize efficiency.

16.4 Machine Learning Feature Selection

ML models use knapsack to select the most relevant features within a computation budget to optimize performance.

16.5 Cryptography

Merkle-Hellman Knapsack Cryptosystem uses a variation of the knapsack problem for secure encryption.

16.6 Robotics Path Planning

Knapsack optimization is used to allocate limited energy and computational power in autonomous robots.

16.7 Ad Budget Allocation

Marketing teams optimize ad placements by selecting a subset of campaigns that fit within a budget while maximizing engagement.

17. Open-Source Implementations

17.1 Notable Open-Source Projects Using Knapsack

17.2 Exploring Open-Source Code

For hands-on experience, check out:

18. Practical Project: Optimizing Cloud Server Costs

18.1 Problem Statement

Cloud providers charge for instances with different CPU and memory capacities. Given a fixed budget, we must select the best combination of instances to maximize computing power.

18.2 Python Implementation


def cloud_knapsack(instances, prices, compute_power, budget):
    n = len(instances)
    dp = [0] * (budget + 1)

    for i in range(n):
        for cost in range(budget, prices[i] - 1, -1):
            dp[cost] = max(dp[cost], compute_power[i] + dp[cost - prices[i]])

    return dp[budget]

# Example usage
instances = ["t2.micro", "t2.medium", "t2.large"]
prices = [10, 20, 30]  # Cost per hour
compute_power = [1, 3, 5]  # CPU score

budget = 50
print(cloud_knapsack(instances, prices, compute_power, budget))  # Output: 8

18.3 Enhancements

18.4 Real-World Impact

19. Competitive Programming & System Design Integration

19.1 Knapsack in Competitive Programming

Knapsack problems frequently appear in coding contests. Mastering it can improve problem-solving skills and performance in competitions like Codeforces, LeetCode, and CodeChef.

19.2 Strategies for Competitive Programming

19.3 Knapsack in System Design

In system design, knapsack optimizes resource allocation under constraints.

19.4 Real-World System Design Use Cases

20. Assignments

20.1 Solve 10 Competitive Programming Problems

Try solving the following problems on platforms like LeetCode, Codeforces, and CodeChef:

  1. 0/1 Knapsack Problem (LeetCode: "Knapsack Problem")
  2. Fractional Knapsack (Greedy approach, CodeChef)
  3. Unbounded Knapsack (Codeforces)
  4. Subset Sum Problem (LeetCode: "Partition Equal Subset Sum")
  5. Multi-Dimensional Knapsack (Advanced DP problem, AtCoder)
  6. Knapsack with Item Counts (LeetCode)
  7. Knapsack with Multiple Constraints (Google Code Jam problem)
  8. Optimizing Advertisement Budget (Real-world application on Codeforces)
  9. Knapsack for Cloud Resource Optimization (GCP Hackathon problem)
  10. Cryptographic Knapsack Variant (Used in security-based coding challenges)

20.2 System Design Assignment

Design a system where a ride-sharing app optimally assigns passengers to vehicles based on available seats and distance.

20.3 Implement Under Time Constraints

Test your speed by implementing the 0/1 Knapsack solution within 15 minutes. Use:

20.4 Final Challenge

Implement an AI-based optimizer using Knapsack to allocate cloud storage resources dynamically.