商店的货架上摆放着不同重量和价值的商品,一个小偷在商店行窃,他携带的背包只能装固定重量的商品。装哪些商品才能获得最大的收益呢?在限定条件内找到最佳的物品组合,这样的问题统称为背包问题。
根据限定的条件不同,背包问题还可以细分:
部分背包问题:所有物品是可再分的,即允许将某件物品的一部分(例如 1/3)放入背包;
0-1 背包问题:所有物品不可再分,要么整个装入背包,要么放弃,不允许出现“仅选择物品的 1/3 装入背包”的情况;
完全背包问题:不对每一件物品的数量做限制,同一件物品可以选择多个装入背包;
多重背包问题:每件物品的数量是有严格规定的,比如物品 A 有 2 件,物品 B 有 3 件。
前面章节中,我们学会了用贪心算法解决部分背包问题。本节,我们学习如何用动态规划算法解决 0-1 背包问题。
动态规划解决01背包问题
虚拟一个场景,商店中拥有 5 件商品,它们各自的重量和收益分别是:
商品 1:重量 1 斤,收益 1 元;
商品 2:重量 2 斤,收益 6 元;
商品 3:重量 5 斤,收益 18 元;
商品 4:重量 6 斤,收益 22 元;
商品 5:重量 7 斤,收益 28 元。
所有商品不可再分,顾客要么“整件”购买商品,要么放弃购买。一个小偷想窃取商品,他的背包只能装 11 斤商品,如何选择商品才能获得最大的收益呢?
动态规划算法解决此问题的核心思想是:背包承重 1 斤时所能获得的最大收益是很容易计算的,在此基础上,可以推算出背包承重 2 斤、3斤、…、14斤、15斤时所能获得的最大收益。建立如下这张表格,依次将各个商品装入不同承重的背包中,计算出它们所能获得的最大收益。
表 1 动态规划算法解决01背包问题

表格中,wi 表示第 i 件商品的重量,vi 表示第 i 件商品的收益值。承重不同的各个背包尚未装入商品时,对应的收益值都为 0。
表 2 动态规划算法解决01背包问题

我们用 f(n) 表示承重值为 n 的背包对应的最大收益。从算法的角度,各个背包收益值是这样计算的:f(1)=1+f(0)、f(2)=1+f(1)、…、f(11)=1+f(10),其中等号右侧表达式中的 1 指的是商品一的收益值,f(0)~f(10) 指的是不装任何商品时承重分别为 0~10 的背包对应的收益值,借助表格可以看到,它们的值都为 0。
f(2) = 6 + f(0) = 1
f(3) = 6 + f(1) = 6
f(4) = 6 + f(2) = 7
…
f(9) = 6 + f(7) = 7
f(10) = 6 + f(8) = 7
f(11) = 6 + f(9) = 7
等号右侧 f(0)~f(9) 的值是表 2 中装入商品一的各个背包对应的收益值。相比装入商品一统计的各个背包的收益值,装入商品二能使提高各个背包的收益。更新后的表格为:
表 3 动态规划算法解决01背包问题

f(5) = 18 + f(0) = 18
f(6) = 18 + f(1) = 19
f(7) = 18 + f(2) = 24
f(8) = 18 + f(3) = 25
f(9) = 18 + f(4) = 25
f(10) = 18 + f(5) = 25
f(11) = 18 + f(6) = 25
等号右侧 f(0)~f(6) 的值是表 2 中装入商品二的各个背包对应的收益值。和装入商品二时统计的各个背包的收益值相比,装入商品三能提高各个背包的收益。更新后的表格为:
表 4 动态规划算法解决01背包问题

表 5 动态规划算法解决01背包问题

注意,并不是每试图装入一个新商品,背包的收益一定会提高。举个例子,承重为 7 斤的背包装入商品四时的最大收益是:f(7) = 22+f(1) = 23,装入商品三时最大的收益值为:f(7) = 18+f(2) = 24。因此,表 5 中承重 7 斤的背包装入商品 4 时对应的收益值仍为 24,并未发生改变。
结合表 5,当背包承重为 11 斤时,所能获得的最大收益为 40 元。如下以伪代码的形式给大家总结了以上推理的整个过程:
输入 N // 指定商品种类
输入 W // 指定背包载重量
//w[] 记录各个商品的载重量,v[] 记录各个商品对应的收益
knapsack01(w[] , v[]):
//逐个遍历每个商品
for i <- 1 to N:
//求出从 1 到 W 各个载重量对应的最大收益
for j <- 1 to W:
//如果背包载重量小于商品总重量,则商品无法放入背包,收益不变
if j < w[i]:
result[i][j] = result[i-1][j]
else:
//比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值
result[i][j] = max(result[i-1][j] , v[i]+result[i-1][j-w[i]])
return result
01背包问题的具体实现
结合伪代码,如下是用动态规划算法解决 01 背包问题的 C 语言程序:
#include<stdio.h>
#define N 5 //商品的种类
#define W 11 //背包的最大承重
/*
动态规划算法解决01背包问题
result[N + 1][W + 1]:存储最终的结果
w[N + 1]:存储各商品的重量
v[N + 1]:存储各商品的价值
*/
void knapsack01(int result[N + 1][W + 1], int w[N + 1], int v[N + 1]) {
int i, j;
//逐个遍历每个商品
for (i = 1; i <= N; i++) {
//求出从 1 到 W 各个载重对应的最大收益
for (j = 1; j <= W; j++) {
//如果背包载重小于商品总重量,则该商品无法放入背包,收益不变
if (j < w[i])
result[i][j] = result[i - 1][j];
else
//比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值
result[i][j] = result[i - 1][j] > (v[i] + result[i - 1][j - w[i]]) ? result[i - 1][j] : (v[i] + result[i - 1][j - w[i]]);
}
}
}
//追溯选中的商品
void select(int result[N + 1][W + 1], int w[N + 1], int v[N + 1]) {
int n = N;
int bagw = W;
//逐个商品进行判断
while (n > 0) {
//如果在指定载重量下,该商品对应的收益和上一个商品对应的收益相同,则表明未选中
if (result[n][bagw] == result[n - 1][bagw]) {
n--;
}
else {
//输出被选用商品的重量和价值
printf("(%d,%d) ", w[n], v[n]);
//删除被选用商品的承重,以便继续遍历
bagw = bagw - w[n];
n--;
}
}
}
int main()
{
int w[N + 1] = { 0,1 , 2 , 5 , 6 , 7 }; //商品的承重
int v[N + 1] = { 0,1 , 6 , 18 , 22 , 28 }; //商品的价值
int result[N + 1][W + 1] = { 0 }; //记录统计数据
knapsack01(result, w, v);
printf("背包承重为 %d,最大收益为 %d\n", W, result[N][W]);
printf("选择了:");
select(result, w, v);
return 0;
}
如下为用动态规划算法解决 01 背包问题的 Java 程序:
public class Demo {
static int N = 5;//商品的种类
static int W = 11;//背包的承重
//动态规划算法解决01背包问题
public static void knapsack01(int [][] result , int [] w,int []v) {
//逐个遍历每个商品
for(int i=1;i<=N;i++) {
//求出从 1 到 W 各个承重对应的最大收益
for ( int j=1;j<=W;j++) {
//如果背包承重小于商品总重量,则该商品无法放入背包,收益不变
if(j<w[i]) {
result[i][j] = result[i-1][j];
}else {
//比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值
result[i][j] = result[i - 1][j] > (v[i] + result[i - 1][j - w[i]]) ? result[i - 1][j] : (v[i] + result[i - 1][j - w[i]]);
}
}
}
}
//追溯选中的商品
public static void select(int [][] result , int [] w,int []v) {
int n = N;
int bagw = W;
//逐个商品进行判断
while(n>0) {
//如果在指定承重下,该商品对应的收益和上一个商品对应的收益相同,则表明未选中
if (result[n][bagw] == result[n - 1][bagw]) {
n--;
}
else {
//输出被选用商品的重量和价值
System.out.print("("+w[n]+","+v[n]+") ");
//删除被选用商品的承重,以便继续遍历
bagw = bagw - w[n];
n--;
}
}
}
public static void main(String[] args) {
int [] w= {0,1 , 2 , 5 , 6 , 7}; //商品的重量
int [] v ={0,1 , 6 , 18 , 22 , 28}; //商品的价值
int [][] result = new int[N+1][W+1];
knapsack01(result, w, v);;
System.out.println("背包可容纳重量为 "+W+",最大收益为 "+result[N][W]);
System.out.print("选择了");
select(result, w,v);
}
}
如下为用动态规划算法解决 01 背包问题的 Python 程序:
N = 5 #商品的种类
W = 11 #背包的承重
w = [0,1,2,5,6,7] #商品的承重,不使用 w[0]
v = [0,1,6,18,22,28] #商品的价值,不使用 v[0]
#二维列表,记录统计数据
result = [[0]*(W+1),[0]*(W+1),[0]*(W+1),[0]*(W+1),[0]*(W+1),[0]*(W+1)]
#动态规划算法解决01背包问题
def knapsack01():
#逐个遍历每个商品
for i in range(1,N+1):
#求出从 1 到 W 各个承重对应的最大收益
for j in range(1,W+1):
#如果背包承重小于商品总重量,则该商品无法放入背包,收益不变
if j<w[i]:
result[i][j] = result[i-1][j];
else:
#比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值
result[i][j] = max(result[i-1][j],v[i]+result[i-1][j-w[i]])
knapsack01()
print("背包可容纳重量为 %d,最大收益为 %d"%(W, result[N][W]))
#追溯选中的商品
def select():
n = N
bagw = W
#逐个商品进行判断
while n > 0:
#如果在指定承重下,该商品对应的收益和上一个商品对应的收益相同,则表明未选中
if result[n][bagw] == result[n-1][bagw]:
n = n - 1
else:
#输出被选用商品的重量和价值
print("(%d,%d) "%(w[n],v[n]))
#删除被选用商品的承重,以便继续遍历
bagw = bagw - w[n]
n = n - 1
print("所选商品为:")
select()
以上程序的输出结果均为:
背包可容纳重量为 11,最大收益为 40 选择了(6,22) (5,18)
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有, 也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g