01背包问题的材料看下来,我总结了一句话,物品一件一件减少,背包一点一点变大。

01背包问题形容

  • 最根本的01背包问题形容是,有一个限重W的背包,有好几件分量为weight,价值为value的物品供你筛选,要在不超过背包限重的前提下,奇妙地抉择物品,使得背包外面的物品价值最大。
  • 面试口试遇到的是01背包问题的应用题,重点在于把原始问题转化为背包问题,就跟做数学题套公式有点像,把题目中的已知条件跟背包限重W,以及物品的分量weight和价值value对应起来,问题就会迎刃而解。

动归三部曲

因为01背包问题过于常见,递推公式很容易就能背下来,有人写dp时对初始化也比拟佛系,思考深度不够。然而深刻理解01背包问题的推导和初始化,对了解动静布局的实质意义重大。

题目举例:W = 4 weight=[1,3,4] value=[15,20,30]

1.定义dp数组

这时须要把物品一件一件减少,背包一点一点变大这句话放在心里。

  • 一共有三件物品能够选,所谓物品一件一件减少就是这样,第一次没物品能够挑,第二次1件物品能够挑,第三次2件物品能够挑... 每步都多出了一件物品供本人筛选,那本人就能够决定,放进去还是不放了。
  • 限重为4,所谓背包一点一点变大就是这样,4的限重太大,先从限重为0开始算起,直到达到4为止。

最终dp是一个3*5的数组,dp[i][j]的含意就是有0-i件物品供你筛选,放入容量为j的背包能取得的最大价值。

(对于动静布局问题的剖析,要始终牢记dp的含意)

那最初的后果就是有0-2件物品供你筛选,放入容量为4的背包能取得的最大价值,天然是数组右下角那个格子dp[2][4]了。

2.递推公式

递推公式怎么去想呢?外围就是每次多了件物品能够挑,要辨别这个物品到底要不要放进背包。

  • 这个物品要

dp[i][j] = dp[i-1][j] 比如说0-2件物品供你挑,限重j,那你决定不要第2件物品,那好,背包限重没变,这个时候背包的价值就是0-1件物品供你挑,限重j时计算的最大价值dpi-1(也能够了解成,这个物品要是不拿,就跟没他没什么区别,所以还是之前没他时候,即i-1时的最大价值)。

  • 这个物品不要

dp[i][j] = dp[i-1][j-weight[i]]+value[i] 比如说0-2件物品供你挑,限重j,那你决定要第2件物品,那好,这个时候背包装的价值必定先把这件物品的价值给算上,那剩下的价值呢,就给0-1件物品去实现吧,只不过因为我放了第2件物品,背包的限重变少了weight[i]。

  • 取最大价值 可挑可不挑,那就看看哪个价值大就选哪个作为最大价值。

递推的程序就是下图了

3. 初始化

要初始化的中央就是递推不到的地位,而后算别的dp[i][j]的时候须要用到的值。图中就是第0列和第0行。

  • 第0列 限重0,无论多少物品那都没的放,价值必定为0,所以第0列全0。
  • 第0行 有第0件物品能够挑,那如果限重>=这个物品,就代表物品能够放进去,价值就是这个物品的值。

C++代码实现 含具体正文

#include <iostream>#include <vector>using namespace std;int main(){    int W = 4;//限重    int weight[3] = {1,3,4};//物品分量    int value[3] = {15,20,30};//物品价值    vector<vector<int>> dp(3,vector<int>(W+1,0)); //3*5的dp    //初始化    for(int i = 0;i < 3;i++){        dp[i][0] = 0;    }    for(int j = 1;j <= W;j++){        if(j >= weight[0]) dp[0][j] = value[0];    }    //递推    for(int i = 1;i < 3;i++){        for(int j = 1;j <= 4;j++){            dp[i][j] = dp[i-1][j];//不拿这件物品            if(j >= weight[i])//下标不越界 或者说之后限重>=物品能力拿物品                //拿这件物品和不拿这件物品 找最大                dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);        }    }    //打印下dp    for(int i = 0; i < 3;i++){        for(int j = 0;j <=4;j++){            cout << dp[i][j] << " ";        }        cout << endl;    }    cout << dp[2][4];//最终后果    return 0;}

手推一次dp数组加深了解 为优化打基础

手推时,发现能够先把上一行的数据整体挪动下来,当限重>=该物品分量时再思考如果筛选这个物品价值是多少,选更大价值即可(要是限重小了那这个物品必定不能放,只能从上一行移下来了)。

手推二维dp数组能够为滚动数组优化做铺垫。

滚动数组优化

滚动数组要着重了解的是初始化和遍历程序的问题。

动归三部曲

1. 定义dp数组

依据手推dp数组中的教训咱们能够晓得,算下一行是先把上一行的数据给搬下来了。所以优化时能够只用一行格子,而不必全副的格子。

dp[j] 含意就是限重j时的最大价值。一件物品一件物品去迭代,返回的是最初一个格子的值。

2. 递推

!用一排格子就得思考一个问题,值笼罩了怎么办?

在二维dp中,显然咱们用到了上方和左上方的值,那一维dp就会用到原地和左方的值。那要是从左往右算,值变了咋整,不就不是上一轮完结的dp了吗。所以只能==倒着往前遍历==,就不会有这种事了,反正dp[i]算的时候又跟前面的值没关系。

下图所示的是迭代到第1件物品时要用到的其余值。(weight[2] = 3,所以只须要算限重3和4。绿色是橙色格子在递推时须要用到的值)

3. 初始化

当然能够像为二维dp初始化第一行一样初始化一维dp,而后从第1件物品开始迭代。但其实能够初始化为全0,从第0件物品开始迭代,成果一样。

测验全0的初始化对不对,次要看第0件物品的迭代是否正确。此时发现,依然是当限重>=weight[0]时,
dp[i]=max(dp[i],dp[i-weight[i]]+value[i])=max(0,value[i])=value[i]

C++代码加正文

#include <iostream>#include <vector>using namespace std;int main(){    int W = 4;//限重    int weight[3] = {1,3,4};//物品分量    int value[3] = {15,20,30};//物品价值    vector<int> dp(W+1,0); //5的dp    //初始化 全0    //递推    for(int i = 0;i < 3;i++){        for(int j = 4;j >= 0;j--){            if(j >= weight[i])                 dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);         }    }    cout << dp[4];//最终后果    return 0;}