【算法学习笔记】30.动态规划 01背包和完全背包的关系

首先先说明一下01背包和完全背包问题的区别

01背包:有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的费用是 Ci,得到的价值是 Wi。求解将哪些物品装入背包可使价值总和最大。(可以不装满)

完全背包:有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。放入第 i 种物品 的费用是 Ci,价值是 Wi。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。

先说结论:

两个问题的最优解都要用DP来解决,实现的过程也非常像只是在内层循环中 完全背包是顺序,01是逆序。

原因在于 01背包要保证每次计算前i个物品时,分析第i个物品放还是不放时,要根据的是前i-1个物品的值,因为不能让i进入两次背包。

    而完全背包则必须要在前i个物品的基础上再考虑第i个物品是否加入对当前容量下的w的影响。

01背包问题:

状态转移方程:F[i,v]表示的是前i个物体中选若干个,在容量为V的背包里可以达到的最大价值。

        F[i,v] = max{F[i − 1,v],F[i − 1,v − Ci] + Wi}

可见计算F[i,v]时,是从i-1阶段的两个状态里转移过来的,一个是如果不放第i个物体时,则前i-1个物体在背包为V的最大价值,一个是如果放第i个物体时,在背包为V-c[i]的最大价值加上Wi。两个里面较大的那个就是结果。

这里是用二维数组表示的,非常容易理解,但是空间复杂度过大,另外也不容易统一,如果用一维数组来进行表示,则在每一个阶段时F[v]如果是左值则表示时当前阶段的,即F[i,v],如果是右值的话要分两种情况,如果已经更新(重新赋值)过则表示当前阶段的,如果还没有被覆盖过则表示的是上一阶段的,即F[i-1,v]。

如果用一维数组来表示的话,F[v]在左值时表示的是这一阶段的,要保证右面的F[v]是上一阶段的,而F[v-ci]也是上一阶段的,所以~逆序计算!!

再仔细观察这个转移方程,因为在计算F[i,v]时,用到的是F[i-1,v-ci]要想在这时用一维数组F[v-ci]取到这个值,必须保证它没有被更新过,即它暂时还没有被计算,而v-ci < v 所以要逆序进行计算!

这个图片可以说明这个问题。 要注意在每一个阶段(每一行)计算时,都是从C[10]开始到C[0]的。

伪代码:

F [0..V ] ←0

for i ← 1 to N

for v ← V to Ci

F[v] ←max{F[v],F[v−Ci]+Wi}

注意内层循环的顺序和边界。因为在第i阶段,如果v小于第i个物品的大小,肯定放不进去i,所以一定不用更新,不必检测。

完全背包问题:

和01背包最大的区别就在于可以每个无限重复。

所以它的状态转移方程可以写成

            F[i,v] = max{F[i − 1,v],F[i,v − Ci] + Wi}

表示如果不放第i个物品则沿用上一阶段的F[v],如果用了它,则要从包含它的这一阶段的v-ci中累加。

如果用一维数组来表示的话,F[v]在左值时表示的是这一阶段的,要保证右面的F[v]是上一阶段的,而F[v-ci]是这一阶段的,所以~顺序计算!!

伪代码:

F [0..V ] ←0

for i ←1 to N

  for v  ←Ci to V

    F[v] ←max(F[v],F[v−Ci]+Wi)

另外,如果把两层循环的顺序颠倒,我们还可以得到一个理解:

F [0..V ] ←0

for v  ←Ci to V

  tmp = 0

  for i ←1 to N

    tmp ←max(tmp,F[v−Ci]+Wi)

  F[v]=tmp

在这种情形下,我们可以认为对于每一个v来说,F[v]存的是针对容量v时n个物品的最大价值方案,在计算每个v时,我们循环试着填入每个物体,找到使F[v]最大的那个物体,然后放入进去。

所以此时的状态转移方程应该写成

  F[v] = max(i:1~N){F[v-Ci]+Wi}  也就是说每个状态是由它的前N个状态转移而来,这N个状态分别就是v-ci  这样也同时可以保证一个物品可以放多次。

-----------

但是为了记忆上的方便可以把01背包和完全背包都用i-v的双层循环,完全背包顺序,01背包逆序,即可。

时间: 2024-10-28 23:30:23

【算法学习笔记】30.动态规划 01背包和完全背包的关系的相关文章

由LCS到编辑距离—动态规划入门—算法学习笔记

一切计算机问题,解决方法可以归结为两类:分治和封装.分治是减层,封装是加层. 动态规划问题同样可以用这种思路,分治. 它可以划分为多个子问题解决,那这样是不是用简单的递归就完成了?也许是的,但是这样会涉及太多的不便的操作.因为子问题有重叠! 针对这种子问题有重叠的情况的解决,就是提高效率的关键. 所以动态规划问题可以总结为:最优子结构和重叠子问题. 解决这个子问题的方式的关键就是:memoization,备忘录. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方

算法导论三剑客之 动态规划 0-1背包问题

1 #include "iostream" 2 using namespace std; 3 4 float MAX(float m1,float m2){ 5 if(m1>=m2) 6 return m1; 7 else 8 return m2; 9 } 10 11 float bag_Zero_One(int n,float v,float p[],float w[]){ 12 if(n==0||v==0) 13 return 0; 14 else{ 15 float m2;

算法学习笔记 最短路

图论中一个经典问题就是求最短路,最为基础和最为经典的算法莫过于 Dijkstra 和 Floyd 算法,一个是贪心算法,一个是动态规划,这也是算法中的两大经典代表.用一个简单图在纸上一步一步演算,也是很好理解的,理解透自己多默写几次即可记住,机试时主要的工作往往就是快速构造邻接矩阵了. 对于平时的练习,一个很厉害的 ACMer  @BenLin_BLY 说:"刷水题可以加快我们编程的速度,做经典则可以让我们触类旁通,初期如果遇见很多编不出,不妨就写伪代码,理思路,在纸上进行整体分析和一步步的演算

算法学习笔记 递归之 快速幂、斐波那契矩阵加速

递归的定义 原文地址为:http://blog.csdn.net/thisinnocence 递归和迭代是编程中最为常用的基本技巧,而且递归常常比迭代更为简洁和强大.它的定义就是:直接或间接调用自身.经典问题有:幂运算.阶乘.组合数.斐波那契数列.汉诺塔等.其算法思想: 原问题可分解子问题(必要条件): 原与分解后的子问题相似(递归方程): 分解次数有限(子问题有穷): 最终问题可直接解决(递归边界): 对于递归的应用与优化,直接递归时要预估时空复杂度,以免出现用时过长或者栈溢出.优化递归就是以

EM算法学习笔记2:深入理解

文章<EM算法学习笔记1:简介>中介绍了EM算法的主要思路和流程,我们知道EM算法通过迭代的方法,最后得到最大似然问题的一个局部最优解.本文介绍标准EM算法背后的原理. 我们有样本集X,隐变量Z,模型参数θ,注意他们3个都是向量,要求解的log似然函数是lnp(X|θ),而这个log似然函数难以求解,我们假设隐变量Z已知,发现lnp(X,Z|θ) 的最大似然容易求解. 有一天,人们发现引入任意一个关于隐变量的分布q(Z),对于这个log似然函数,存在这样一个分解: lnp(X|θ)=L(q,θ

C++学习笔记30,指针的引用(2)

可以创建任何类型的引用,包括指针类型. 看一个简单的指针的引用的例子.例如: #include <iostream> using namespace std; int main(){ int x=10; int y=20; int z=30; int* ptx=&x; int* ptz=&z; //指针的引用,声明从右往左看,rtp与&结合, //剩余的符号和左边结合 //引用一旦创建,不能改变其指向,只能改变其值 int* &rtp=ptx; cout<

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前

[算法学习笔记]直接插入排序笔记

直接插入排序概念: 带排元素放在elem[0...n-1]中,初始化时,elem[0]自成1个有序区,无序区为elem[1...n-1],从i=1起,到i=n-1,依次将elem[i]插入有序区[0...n-1]中 直接插入排序算法步骤: 1.在当前有序区域R[1,i-1]中查找R[i]的正确插入位置K(1<=K<=i-1) 2.将R[K,i-1]中的记录均向后移动 3.移动后腾出K位置,插入R[i] (最坏)时间复杂度:O(n^2) 空间复杂度:O(1) /// <summary>

八大排序算法学习笔记:冒泡排序

冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法. 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端. 算法原理: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 针对所有

八大排序算法学习笔记:插入排序(一)

插入排序     包括:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序).属于稳定排序的一种(通俗地讲,就是两个相等的数不会交换位置) . 直接插入排序: 1.算法的伪代码(这样便于理解):     INSERTION-SORT (A, n)             A[1 . . n] for j ←2 to n do key ← A[ j] i ← j – 1 while i > 0 and A[i] > key do A[i+1] ← A[i]