Unbounded Knapsack Pattern

Introduction :

Given the weights and profits of ‘N’ items, put it in a knapsack of capacity ‘C’ such that we get the max profit.

Only difference between 0/1Knapsack and Unbounded knapsack is that here we are allowed to use an unlimited quantity of an item.

Example:
items: {Apple, Orange, Melon}
weights: {1, 2, 3}
profits: {15, 20, 50}
capacity: 5

Different Profit Combinations:
5 Apples (total weight 5) => 75 profit
1 Apple + 2 Oranges (total weight 5) => 55 profit
3 Apples + 1 Orange (total weight 5) => 65 profit
2 Apples + 1 Melon (total weight 5) => 80 profit
1 Orange + 1 Melon (total weight 5) => 70 profit

Best Profit Combination : 2 Apples + 1 Melon with 80 profit.


Problems following Unbounded Knapsack Pattern

1. Unbounded Knapsack

Problem:

Given the weights w and profits p of ‘N’ items, need to find a subset of these items which will give max profit with contstraint that their cumulative sum should not be greater than total knapsack capacity C.

Each item can only be selected multiple times.

Brute-Force : Recursive Solution

Code:

def unbounded_knapsack_recursion(weights, profits, capacity):
    item_index = 0
    return unbounded_knapsack_recursion_util(weights, profits, capacity, item_index)


def unbounded_knapsack_recursion_util(weights, profits, capacity, item_index):
    if (capacity <= 0 or item_index >= len(profits)):
        return 0

    current_included_profit, current_excluded_profit = 0, 0
    if(weights[item_index] <= capacity):
        current_included_profit = profits[item_index] + \
            unbounded_knapsack_recursion_util(weights, profits, capacity - weights[item_index], item_index)

    current_excluded_profit = unbounded_knapsack_recursion_util(weights, profits, capacity, item_index + 1)

    return max(current_included_profit, current_excluded_profit)


print("Recursive Method :")
print(unbounded_knapsack_recursion([1, 3, 4, 5], [15, 50, 60, 90], 8))
print(unbounded_knapsack_recursion([1, 3, 4, 5], [15, 50, 60, 90], 6))

Output:

140
105

Complexity:


DP : Recursion + Memoization (Top-Down) Solution

Code:

def unbounded_knapsack_dp_memoization(weights, profits, capacity):
    memory = [[None] * (capacity + 1) for i in range(len(weights))]
    item_index = 0
    return unbounded_knapsack_dp_memoization_util(weights, profits, capacity, item_index, memory)


def unbounded_knapsack_dp_memoization_util(weights, profits, capacity, item_index, memory):
    if (capacity <= 0 or item_index >= len(profits)):
        return 0

    if (memory[item_index][capacity]):
        return memory[item_index][capacity]

    current_included_profit, current_excluded_profit = 0, 0
    if(weights[item_index] <= capacity):
        current_included_profit = profits[item_index] + \
            unbounded_knapsack_dp_memoization_util(weights, profits, capacity - weights[item_index], item_index, memory)

    current_excluded_profit = unbounded_knapsack_dp_memoization_util(weights, profits, capacity, item_index + 1, memory)
    memory[item_index][capacity] = max(current_included_profit, current_excluded_profit)

    return memory[item_index][capacity]


print("\nDP -> Recursion + Memoization Method :")
print(unbounded_knapsack_dp_memoization([1, 3, 4, 5], [15, 50, 60, 90], 8))
print(unbounded_knapsack_dp_memoization([1, 3, 4, 5], [15, 50, 60, 90], 6))

Output:

140
105

Complexity:


DP : Iteration + Tabulation (Bottom-Up) Solution

Table Filling Formula:

table[index][c] = max(table[index-1][c], profits[item_index] + table[index][c-weights[item_index]])

Tabulation:

Final Table:

Code:

def unbounded_knapsack_dp_tabulation(weights, profits, capacity):
    n = len(profits)
    table = [[0] * (capacity + 1) for i in range(n)]

    # When capacity is 0, none of the item can be selected
    for i in range(n):
        table[i][0] = 0

    # When only 1 item is considered, get the profits using that item for every capapcity 'c'
    for c in range(capacity + 1):
        table[0][c] = (c // weights[0]) * profits[0]

    # For each item, check for every possible capacities
    for i in range(1, n):
        for c in range(1, capacity + 1):
            if (weights[i] <= c):
                table[i][c] = max(table[i - 1][c], profits[i] + table[i][c - weights[i]])
            else:
                table[i][c] = table[i - 1][c]

    return table[i][c]


print("\nDP -> Iteration + Tabulation Method :")
print(unbounded_knapsack_dp_tabulation([1, 3, 4, 5], [15, 50, 60, 90], 8))
print(unbounded_knapsack_dp_tabulation([1, 3, 4, 5], [15, 50, 60, 90], 6))

Output:

DP -> Iteration + Tabulation Method :
140
105

Complexity:


Finding Selected Items:

Code:

def unbounded_knapsack_dp_tabulation(weights, profits, capacity):
    n = len(profits)
    table = [[0] * (capacity + 1) for i in range(n)]

    # When capacity is 0, none of the item can be selected
    for i in range(n):
        table[i][0] = 0

    # When only 1 item is considered, get the profits using that item for every capapcity 'c'
    for c in range(capacity + 1):
        table[0][c] = (c // weights[0]) * profits[0]

    # For each item, check for every possible capacities
    for i in range(1, n):
        for c in range(1, capacity + 1):
            if (weights[i] <= c):
                table[i][c] = max(table[i - 1][c], profits[i] + table[i][c - weights[i]])
            else:
                table[i][c] = table[i - 1][c]

    pick_selected_items(table, weights, profits)

    return table[i][c]


def pick_selected_items(table, weights, profits):
    i, c = len(table)-1, len(table[0])-1
    total_profit = table[i][c]

    print("Selected items are : ")
    while (i-1 >= 0 and table[i][c] >= 0):
        if (table[i][c] != table[i - 1][c]):
            print(f"item - {i+1} with weight : {weights[i]} and profit : {profits[i]}")
            c -= weights[i]
            total_profit -= profits[i]
        else:
            i -= 1

    while (total_profit > 0):
        print(f"item - {1} with weight : {weights[0]} and profit : {profits[0]}")
        total_profit -= profits[0]


print("\nDP -> Iteration + Tabulation Method :")
print(unbounded_knapsack_dp_tabulation([1, 3, 4, 5], [15, 50, 60, 90], 8))
print(unbounded_knapsack_dp_tabulation([1, 3, 4, 5], [15, 50, 60, 90], 6))

Output:

Selected items are : 
item - 4 with weight : 5 and profit : 90
item - 2 with weight : 3 and profit : 50
140
Selected items are : 
item - 4 with weight : 5 and profit : 90
item - 1 with weight : 1 and profit : 15
105



2. Rod Cutting

3. Coin Change

4. Minimum Coin Change

5. Maximum Ribbon Cut




← Previous: 0/1 Knapsack Pattern

Next: Palindromic Subsequence Pattern →