问题描述:n 个不同的物品,每个物品只有一个,有价值 value 和重量 weight 两个属性;背包容量 w。求背包所能容纳的最大价值。

最优子结构:当我们把一件物品放进背包里时,会让总价值变高,并且让背包变重。对某一件物品来说,我们可以选择放或不放,然后移去这件物品所带来的影响。

c(n, w) = max( c(n-1, w), c(n-1, w-W[n]) + C[n] ) ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ 不放 -> 0 放 -> 1

n:第1个到第n个物品要放进背包内。 w:背包负重上限。 c(n, w):第1个到第n个物品尽量塞进负重限制为w的背包时,最大价值。 W[n]:第n个物品的重量。 C[n]:第n个物品的价值。

基本算法

Bottom UP 递推

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 物品总数上限
const int MaxN = 100;
 
// 背包负重上限
const int MaxW = 100000;
 
// 物品
struct Item {int cost, weight;} items[MaxN];
 
// DP矩阵
int c[MaxN + 1][MaxW + 1];
 
void knapsack(int n, int w)             // n 为物品数,w 为背包负重上限
{
    memset(c, 0, sizeof(c));
 
    for (int i = 0; i < n; ++i)         // 每个物品都试试看
        for (int j = 0; j <= w; ++j)    // 每个重量都试试看
            if (j - items[i].weight < 0)
                c[i+1][j] = c[i][j];    // 放不下
            else
                c[i+1][j] = max( c[i][j], c[i][j - items[i].weight] + items[i].value );
 
    cout << "Max Value: " << c[n][w] << endl;
}

Top Down 递归

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int knapsack(int W)
{
    int i, space, max, maxi, t;
    if (maxKnow[W] != -1) return maxKnown[W];
    
    for (i = 0, max = 0; i < N; i++)
        if ((sapce = W - items[i].weight) >= 0)
            if ((t = knapsack(space) + items[i].value) > max)
                { max = t; maxi = i; }
    maxKnown[W] = max; itemKnown[W] = items[maxi];
    return max
}

空间优化

因为计算时只需要用到上方和左上方的格子,所以只需要一个单行数组就可以了。不过计算次序需要改为由后向前,才不会覆盖掉需要用来计算的格子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const int MaxN = 100, MaxW = 100000;
struct Item {int cost, weight;} items[MaxN];
int c[MaxW + 1];
 
void knapsack(int n, int w)
{
    memset(c, 0, sizeof(c));
 
    for (int i = 0; i < n; ++i)
    {
        int weight = items[i].weight, cost = items[i].cost;
        for (int j = w; j - weight >= 0; --j)
            c[j] = max( c[j], c[j - weight] + cost );
    }
 
    cout << "Max Value: " << c[w] << endl;
}

Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from numpy import *

def knapsack_iter(W, items):
    A = zeros(2 * W).reshape(2, W)
    i = 0

    for (v, w) in items:
        A[(i+1)%2][:w] = A[i%2][:w]
        A[(i+1)%2][w:] = maximum(A[i%2][w:], A[i%2][:W-w] + v)
        i += 1
    return A[i%2][W-1]


def knapsack_recur(W, items):
    # top-down solution with memoization via hashtable
    N = len(items)
    A = {}
    def A_(i, j):
        if (str(i)+','+str(j)) in A: return A[str(i)+','+str(j)] # memoization
        if i == 0: return 0
        v, w = items[i]
        if j-w < 0:
            result = A_(i-1, j)
            A[str(i)+','+str(j)] = result # memoization
            return result
        else:
            result = max(A_(i-1, j), A_(i-1, j-w) + v)
            A[str(i)+','+str(j)] = result # memoization
            return result
    return A_(N-1, W-1)