算法之动态规划(DP)求解01背包问题

本文主要介绍动态规划算法求解01背包问题。什么是01背包问题?什么是动态规划?动态规划怎么求解01背包问题?通过阅读本文能够解答上面的三个问题。

1、什么是01背包问题?

01背包问题是一种比较常见的问题,例如,你(或者你女盆友)有一个lv包包,出门的时候发现容量V固定,但是又要装口红,装防晒霜,装银行卡,装钥匙,装眼镜,装护手霜,装…,每种物品只能装一个,(是不是发现女人出门真麻烦?这也是女人最有智慧的地方,每次都能够快速的完成01背包问题的求解),需要装的这些东西呢只能选择装或者不装,每个物品的空间大小不一样,把这些物品的体积进行符号化,Ti (i=1,2…N)一共N个。但是每个物品在你心目中的重要程度不一样,比如,要出去购物,银行卡,手机是非常重要的,而口红可能重要性比较低,我们队每个物品Wi的权重符号化,设为 Wi. 将上面的符号汇总一下:

V:  lv包包的最大容量
N:  物品的个数
Ti: 物品i的体积
Wi: 物品的权重

那么问题来了,在LV包包容量V固定且能够装下,每种物品只能取一次的情况下,需要装哪些物品,才能是的总权重最大化呢?这就是我们说的01背包问题。

2、什么是动态规划?

大家经常以为动态规划算法不像排序算法,递归算法等比较容易理解,其实,只要你记住了动态规划的核心器件,你就能够很容易的理解它并且用来解决问题。
动态规划,首先它是动态的,那么它当前的状态就要依赖前一次的状态,即状态转移,对前面的依赖,因此就需要一个状态表,来记录不同情况下的状态。上面表黑的两个是动态规划最重要的两个因素,一个是状态表,一个是状态转移,下面对他们进行数学表达:

状态: f[i][j]  选择前i个物品,体积为j时的最优方案,即所选物品的最大权重和。
状态转移:f[i][j] = max(f[i-1][j-Ti]+Wi, f[i-1][j])  前i个物品,体积为j时的最优值= 最大值(包含i个物品的最优值 , 前i-1个物品体积为j时的最优值)

2.1 动态规划状态

f[i][j]为状态,i代表当前从前i个物品中选择,背包的最大容量为j时,选择出来的物品的权重和的最大值。给大家举个栗子:

LV包包容量:10, f[3][7]是多少?

物品 物品体积 物品权重
手机 5 10
银行卡 1 10
口红 2 1
车钥匙 3 10
护手霜 3 2

通过表格可以看出,容量为7时,选择手机和银行卡的最终的权重和最大是20,即f[3][6] = 20

2.2 状态转移方程

状态转移方程是动态规划最重要的一步,下面详细介绍一下状态转移方程的由来。

状态转移:f[i][j] = max(f[i-1][j-Ti]+Wi, f[i-1][j])

从前i-1个物品中体积最大为j时最优解为f[i-1][j], 从i-1状态转移到i时,只需要考虑f[i][j]的最优解中包不包含第i个物品两种情况(大家仔细琢磨一下这一句)。
1)包含第i个物品:表示当前物品比前i个物品性价比高,能够替换掉一个 表达式:f[i-1][j-Ti]+Wi 表示从j中除去Ti的空间时的最优值然后加上当前第i个物品的权重。
2)第i个物品定价比低,无法替换掉i-1个物品,依然保持i-1状态的值,表达式: f[i-1][j]

因此,转移方程为:f[i][j] = max(f[i-1][j-Ti]+Wi, f[i-1][j])

下面用事实说话,上栗子:

我们把i=3的所有状态计算出来。

f[3][1] f[3][2] f[3][3] f[3][4] f[3][5] f[3][6] f[3][7] f[3][8] f[3][9] f[3][10]
10 10 11 11 10 20 20 21 21 21

1)f[i][j]包含第i个物品的情况
计算f[4][10],从信息表中可以得出,选择手机,银行卡,车钥匙 最终值为30,包含第4个物品车钥匙
我们用状态转移方程:

f[4][10] = max(f[3][10], f[3][10 - 3] + 10)
         = max(21, f[3][7] + 10)
         = max(21, 20 + 10) = 30
  1. f[i][j]不包含第j个物品的情况
    f[3][7] ,从信息表中可以得出,选择手机,银行卡最终值为20,不包含第3个物品口红
    我们用状态转移方程:
f[3][7] = max(f[2][7], f[2][7 - 2] + 1)
         = max(20, f[2][5] + 1)       // f[2][5] = 10   f[2][7] = 20
         = max(20, 1) = 20

3、怎么求解01背包问题?

练习题:01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

代码:

#include<stdio.h>

int f[1050][1050];
int v[1050];
int w[1050];
int max(int a, int b)
{
    
    
    if (a > b) {
    
    
        return a;
    }
    return b;
}

int main()
{
    
    
    int n,m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
    
    
        scanf("%d %d", &v[i], &w[i]);
    }
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ ) {
    
    
            if (v[i] > j) {
    
      // 防止数组越界
                f[i][j] = f[i - 1][j];
                continue;
            }
            f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }
    printf("%d\n", f[n][m]);
}

代码优化

通过上面的分析可以发现,f[i][j]的值仅仅与依赖i-1时的状态,因此,代码中无需使用二维的f[i][j]进行存储,使用一维的f[j]进行存储,减少程序的空间。

使用一维f,可能会有一个疑问:如果j从小到大的顺序依次更新f[j]的值,因此,在计算f[7] = max(f[7], f[7 - 3] + 10)时,括号内的f[4]已经是i时刻的值了,而非i-1时刻的值了,这个问题怎么解决呢?

聪明的朋友已经想到了:f[j] 的值在更新的时候,按照j从大到小的顺序依次更新,这样在计算f[7] = max(f[7], f[7 - 3] + 10)时,f[4]还未更新,还是i-1时刻的状态值,因此,这个问题就解决了,上代码。

#include<stdio.h>

int f[1050];
int v[1050];
int w[1050];
int max(int a, int b)
{
    
    
    if (a > b) {
    
    
        return a;
    }
    return b;
}

int main()
{
    
    
    int n,m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
    
    
        scanf("%d %d", &v[i], &w[i]);
    }
    
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 1; j-- ) {
    
      //j从大到小的方向更新
            if (v[i] > j) {
    
      // 防止数组越界
                continue;
            }
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    printf("%d\n", f[m]);
}

代码是不是很简单呢?如果有疑问或者问题欢迎评论区留言,如果喜欢就点赞收藏吧_

猜你喜欢

转载自blog.csdn.net/PRML_MAN/article/details/114414230