01背包问题

来源:互联网 发布:淘宝美工包包后期技巧? 编辑:程序博客网 时间:2024/06/17 09:50

01背包问题

n个重量和价值分别为wivi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

1 <= n <= 100

1 <= wi, vi <= 100

1 <= w <= 10000

int n, W;int w[MAX_n], v[MAX_n];//从第i个物品开始挑选总重小于j的部分int rec(int i, int j){    int res;    if (i == n) {        //已经没有剩余物品了        res = 0;    }    else if (j < w[i]) {        //无法挑选这个物品        res = rec(i + 1, j);    }    else {        res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);    }    return res;}

这种方法的搜索深度为n,且每一层搜索都需要两次分支,最坏需要O(2^n)的时间。

该算法中许多参数被重复计算,我们可以将第一次的时间结果记录下来,省略重复计算。



int dp[MAX_N+1][MAX_N+1];memset(dp, -1, sizeof(dp));int rec(int i, int j){    if (dp[i][j] >= 0) {        //已经计算过的话直接使用之间的结果        return dp[i][j];    }    int res;    if (i == n) {        res = 0;    }    else if (j < w[i]) {        rec(i + 1, j);    }    else {        res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);    }    //将结果记录在数组中    return dp[i][j] = res;}

对于同样的参数,只会在第一次被调用时执行递归部分,第二次之后都会直接返回。

参数组合不过nW种,而函数内只调用2次递归,所以只需要O(nW)的复杂度就能解决问题。



dp[i][j]为根据rec的定义,从第i个物品开始挑选总重小于j时,总价值的最大值,于是有如下递推式:

dp[n][j] = 0。

j < w[i]时,dp[i][j] = dp[i+1][j]。

其他情况下,dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]]+v[i])。

int dp[MAX_N+1][MAX_N+1];for (int i = n - 1; i >= 0; i--) {    for (int j = 0; j <= W; j++) {        if (j < w[i]) {            dp[i][j] = dp[i + 1][j];        }        else {            dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]] + v[i]);        }    }}printf("%d\n", dp[0][W]);


dp[i+1][j]=0ii+1个物品中选出总重量不超过j的物品时总价值的最大值。

dp[0][j] = 0。

j < w[i]时,dp[i+1][j] = dp[i][j]。

其他情况下,dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]] + v[i])。

for (int i = 0; i < n; i++) {    for (int j = 0; j <= W; j++) {        if (j < w[i]) {            dp[i+1][j] = dp[i][j];        }        else {            dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]] + v[i]);        }    }}printf("%d\n", dp[n][W]);


此外,除了运用递推方式逐项求解之外,还可以把状态转移想象成从i个物品中选取总重不超过j时的状态i+1个物品中选取总重不超过j”i+1个物品中选取总重不超过j+w[i]时的状态的转移。

for (int i = 0; i < n; i++) {    for (int j = 0; j <= w; j++) {        dp[i+1][j] = max(dp[i+1][j], dp[i][j]);        if (j + w[i] <= W) {            dp[i+1][j+w[i]] = max(dp[i+1][j+w[i]], dp[i][j] + v[i]);        }    }}printf("%d\n", dp[n][W]);



当背包问题的限制条件很大时,还可以改变DP的对象。之前的方法中,我们用DP针对不同的重量限制计算最大的价值。

这次不妨用DP针对不同的价值计算最小的重量。

dp[i+1][j]=i个物品中挑选出价值总和为j时总重量的最小值(不存在时就是一个充分大的数值INF)

由于前0个物品中什么都挑选不了,所以初始值为:

dp[0][0] =0。

dp[0][j] = INF。

此外,前i个物品中挑选出价值总和为j时,一定有:

i个物品中挑选价值总和为j的部分。

i-1个物品中挑选出价值总和为j-v[i]的部分,然后再选中第i个物品。

所以得到dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]]+w[i])。

最终的答案就对应于令dp[n][j] <= W的最大的j

int dp[MAX_N+1][MAX_N*MAX_V+1];for (int i = 1; i < MAX_N * MAX_V + 1; i++)dp[0][i] = INF;dp[0][0] = 0;for (int i = 0; i < n; i++) {    for (int j = 0; j <= MAX_N * MAX_V; j++) {        if (j < v[i]) {            dp[i+1][j] = dp[i][j];        }        else {            dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]] + w[i]);        }    }}int res = 0;for (int i = 0; i <= MAX_N*MAX_V; i++) {    if (dp[n][i] <= W)        res = i;        }printf("%d\n", res);
0 0
原创粉丝点击