01背包问题
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
cin>>w[i]>>val[i];
memset(dp,0,sizeof(dp));
for(i=1;i<=m;i++)
for(j=0;j<=T;j++)//j相当于上面说的V-c[i]
{
if(j>=w[i])
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+val[i]);//放还是不放的选择
else dp[i][j]=dp[i-1][j];
}
cout<<dp[m][T]<<endl;
return 0;
}
实例解析:
背包:容量10
3个物品:W1=3,V1=4
W2=4,V2=5
W3=5,V3=6
参考:http://wenku.baidu.com/link?url=4f7wckWrL4AljAhbmwEJDlmPbIwaR5XyQSRGAA6gLppo-MoX_OEvrXREU1ohKReJhvrJWksG1HwLDfAhkyNp1nEhxvdwduxApQ8mAqvQHJ7
if(i>n)
{
print();
if(currentPrice>bp)
{
bp=currentPrice;
for(int j=1;j<=n;j++)
bA[j]=x[j];
}
return;
}
if(currentWeight+weight[i]<=c)
{//将物品i放入背包,搜索左子树
x[i]=1;
currentWeight+=weight[i];
(2)f[i,j]=0(i=0 OR j=0)
f[i,j]=f[i-1,j]j<wi①
f[i,j]=max{f[i-1,j],f[i-1,j-wi]+vi } j>wi②
①式表明:如果第i个物品的重量大于背包的容量,则装人前i个物品得到的最大价值和装入前i-1个物品得到的最大价是相同的,即物品i不能装入背包;②式表明:如果第i个物品的重量小于背包的容量,则会有一下两种情况:(a)如果把第i个物品装入背包,则背包物品的价值等于第i-1个物品装入容量位j-wi的背包中的价值加上第i个物品的价值vi;
01背包问题
一、问题描述
一个正在抢劫商店的小偷发现了n个商品,第i个商品价值Vi美元,重Wi磅,Vi和Wi都是整数;这个小偷希望拿走价值尽量高的商品,但他的背包最多能容纳W磅的商品,W是一个整数。
我们称这个问题是01背包问题,因为对每个商品,小偷要么把它完整拿走,要么把它留下;他不能只拿走一个商品的一部分,或者把一个商品拿走多次。
特征:
1、问题存在最优子结构
2、问题的最优解需要在子问题中作出选择
3、通过查表解决重叠子问题,避免重复计算
动态规划的设计:
1.刻画一个最优解的结构特征;
2.递归地定义最优解的值;
3.计算最优解的值,通常采用自底向上的方法;
4.利用计算的信息构造一个最优解。
问题分析
最优子结构:
(1)问题分析:令f(i,j)表示在前i(0≤i<n)个物品中能够装入容量为j(0≤j≤W)的背包中的物品的最大价值,则可以得到如下的动态规划函数:
(2)考虑物品i不被选择,这种可能性仅当不包含物品i也有可能找到价值更大的方案;
3.3分支界限法
分支限界法按广度优先策略遍历问题的解空间树,在遍历过程中,对已经处理的每一个结点根据限界函数估算目标函数的可能取值,从中选取使目标函数取得极值的结点优先进行广度优先搜索,从而不断调整搜索方向,尽快找到问题的解。因为限界函数常常是基于问题的目标函数而确定的,所以,分支限界法适用于求解最优化问题;并且包含回溯和各种剪枝,性能优于回溯法。
scanf("%d",&c);
printf("请输入物品重量:");
for(i=1;i<=n;i++)
scanf("%d",&weight[i]);
printf("请输入物品价值:");
for(i=1;i<=n;i++)
scanf("%d",&price[i]);
Backtrack(1);
printf("最优解是:{");
{
int i;
printf("路径为:{");
for(i=1;i<n;i++)
printf("%d,",x[i]);
printf("%d}价值为%d\n",x[i],currentPrice);
}
int main()
{
int i;
printf("请输入物品数量:");
scanf("%d",&n);
printf("请输入背包容量:");
(b)如果第i个物品没有装入背包,则背包中物品价值就等于把前i-1个物品装入容量为j的背包中所取得的价值。显然,取二者中价值最大的作为把前i个物品装入容量为j的背包中的最优解.
时间复杂度为Байду номын сангаас(n*c),c为背包容量
伪代码:
f[0,j]=0
For i=1 to n
For j=0 to W
f[i,j]=max{f[i-1,j],f[i-1,j-wi]+vi }
u b=v+ (W-w) × (v【i+ 1】/w【i+ 1】 )
根据限界函数确定目标函数的界[ down , up],然后, 按照广度优先策略遍历问题的空间树。
时间复杂度O(2^n)
思想
借助大顶堆来实现优先队列,构造大顶堆(按优先队列中所能达到的最大价值来构造这个大顶堆),实现对堆中元素的插入和删除;在回溯法的基础上改进,首先对物品按单位权重递减排序,堆中存放的是每一个当前物品所能达到的情况:
剪枝二、将剩下的所有物品都选取,其总价值也没有目前所求得的总价值还大的话,就可以返回;
递归法:
#include<stdio.h>
int c;//背包容量
int n;//物品个数
int x[100]; //是否选取物品,为0或1
int weight[100];
int price[100];
int bp;//最大价值
int bA[100];//最优解
int times=0;//节点访问次数
int currentWeight=0; //当前总重量
int currentPrice=0;//当前总价值
void print();//输出每条有效路径及最大价值
void Backtrack(int i)
{
times+=1;
基本思想:
给定n种物品和一个容量为C的背包, 物品i的重量是Wi, 其价值为Vi, 0/ 1 背包问题是如何选择装入背包的物品(物品不可分割) , 使得装入背包中物品的总价值最大,一般情况下, 解空间树中第i层的每个结点, 都代表了对物品1~i做出的某种特定选择, 这个特定选择由从根结点到该结点的路径唯一确定: 左分支表示装入物品, 右分支表示不装入物品。对于第i层的某个结点, 假设背包中已装入物品的重量是w, 获得的价值是v, 计算该结点的目标函数上界的一个简单方法是把已经装入背包中的物品取得的价值v, 加上背包剩余容量W-w与剩下物品的最大单位重量价值vi+ 1/wi+ 1的积,于是,得到限界函数:
{
structNode * par;//指向此优先队列中所包涵的所有结点
double cp,cw;//当前达到的价值,重量
double up;//此优先队列所能达到的最大价值
int level;//它所处的层次
}*HP;
typedef struct Heap
{
int hsize;//堆中元素个数
HeapNode * heapnodes;//堆中存放可能的解
时间复杂度O(2^n)
问题分析
用回溯法实现就是要枚举其所有的解空间。
具体方法如下:
①对每一个物品i,对该物品只有选与不选两种决策,有n个物品,可以形成一棵深度为n的决策树;
②遍历这棵树,以枚举所以情况,最后进行判断,若重量不超过背包容量,且总价值最大,该方案就是最优的。
优化方法:
剪枝一:可以进行剪枝,因为很多情况是没有意义的,当重量大于背包的容量时,没有必要对剩下的物品进行决策;
C代码:
#include<iostream>
# include<cstring>
# define max(a,b) a>b?a:b
using namespace std;
int main()
{
int dp[101][1001],m,T,w[101],val[101],i,j;
cin>>T>>m;
for(i=1;i<=m;i++)
3.2回溯法
回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束;
currentPrice+=price[i];
Backtrack(i+1);
//完成上面的递归,返回上一节点,搜索右子树,及不选物品i
currentWeight-=weight[i];
currentPrice-=price[i];
}
x[i]=0;
Backtrack(i+1);
}
void print()
概述:
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
二、解决方案
背包问题作为NP完全问题,暂时不存在多项式时间算法
1.动态规划
2.回溯法
3.分支界限法
三、方案详解
3.1动态规划
动态规划(Dynamic programming,DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
3.重复步骤2.
实现代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=0x0fffff;
typedef struct Node
{
struct Node * par;
bool isLchild;
};
typedef struct HeapNode
1.将第一个单位权重最大的物品插入大顶堆中,并用回溯法中计算边界条件的函数计算出该物品放入背包时所能达到的最大价值,用这一队列最后一个结点记录下当前得到的队列的情况,它可以得到的最大值(double型),用它该取的下一层次号,所达到的最大价值,所占用背包的体积;
2.如果物品层次号(从0开始)小于总个数,取出堆顶元素,对将要遍历到的这一层次上的物品判断选择取与不取的情况.若已经占用的体积+该物品的体积不超过背包体积,则该物品可以放入,即可以是左儿子结点,isLchild=true,将这一结点加入优先队列并放入大顶堆中;若右子树可能存在最优解,isLchild=false,即不放该物品时可能达到的最大价值(double型变量)>=放入该物品时当前背包达到的最优解,则也将这一结点加入优先队列中并放入大顶堆中;
for(i=1;i<n;i++)
printf("%d,",bA[i]);
printf("%d}价值为%d\n",bA[i],bp);
printf("总共访问节点数为:%d",times);
return 0;
}
总结
递归的思想:
(1)考虑物品i被选择,这种可能性仅当包含它不超过背包总重量的限制才是可行的;选中后,继续递归去考虑其他物品的选择;
return F[n,W]
问题的解为f[n,W]
代码实现:
for(i=0;i<n;i++)
for(j=0;j<=W;j++)//j相当于上面说的W-wi
if(j>=w[i])
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+val[i]);//放还是不放的选择
elsef[i][j]=f[i-1][j]
memset(dp,0,sizeof(dp));
for(i=1;i<=m;i++)
for(j=0;j<=T;j++)//j相当于上面说的V-c[i]
{
if(j>=w[i])
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+val[i]);//放还是不放的选择
else dp[i][j]=dp[i-1][j];
}
cout<<dp[m][T]<<endl;
return 0;
}
实例解析:
背包:容量10
3个物品:W1=3,V1=4
W2=4,V2=5
W3=5,V3=6
参考:http://wenku.baidu.com/link?url=4f7wckWrL4AljAhbmwEJDlmPbIwaR5XyQSRGAA6gLppo-MoX_OEvrXREU1ohKReJhvrJWksG1HwLDfAhkyNp1nEhxvdwduxApQ8mAqvQHJ7
if(i>n)
{
print();
if(currentPrice>bp)
{
bp=currentPrice;
for(int j=1;j<=n;j++)
bA[j]=x[j];
}
return;
}
if(currentWeight+weight[i]<=c)
{//将物品i放入背包,搜索左子树
x[i]=1;
currentWeight+=weight[i];
(2)f[i,j]=0(i=0 OR j=0)
f[i,j]=f[i-1,j]j<wi①
f[i,j]=max{f[i-1,j],f[i-1,j-wi]+vi } j>wi②
①式表明:如果第i个物品的重量大于背包的容量,则装人前i个物品得到的最大价值和装入前i-1个物品得到的最大价是相同的,即物品i不能装入背包;②式表明:如果第i个物品的重量小于背包的容量,则会有一下两种情况:(a)如果把第i个物品装入背包,则背包物品的价值等于第i-1个物品装入容量位j-wi的背包中的价值加上第i个物品的价值vi;
01背包问题
一、问题描述
一个正在抢劫商店的小偷发现了n个商品,第i个商品价值Vi美元,重Wi磅,Vi和Wi都是整数;这个小偷希望拿走价值尽量高的商品,但他的背包最多能容纳W磅的商品,W是一个整数。
我们称这个问题是01背包问题,因为对每个商品,小偷要么把它完整拿走,要么把它留下;他不能只拿走一个商品的一部分,或者把一个商品拿走多次。
特征:
1、问题存在最优子结构
2、问题的最优解需要在子问题中作出选择
3、通过查表解决重叠子问题,避免重复计算
动态规划的设计:
1.刻画一个最优解的结构特征;
2.递归地定义最优解的值;
3.计算最优解的值,通常采用自底向上的方法;
4.利用计算的信息构造一个最优解。
问题分析
最优子结构:
(1)问题分析:令f(i,j)表示在前i(0≤i<n)个物品中能够装入容量为j(0≤j≤W)的背包中的物品的最大价值,则可以得到如下的动态规划函数:
(2)考虑物品i不被选择,这种可能性仅当不包含物品i也有可能找到价值更大的方案;
3.3分支界限法
分支限界法按广度优先策略遍历问题的解空间树,在遍历过程中,对已经处理的每一个结点根据限界函数估算目标函数的可能取值,从中选取使目标函数取得极值的结点优先进行广度优先搜索,从而不断调整搜索方向,尽快找到问题的解。因为限界函数常常是基于问题的目标函数而确定的,所以,分支限界法适用于求解最优化问题;并且包含回溯和各种剪枝,性能优于回溯法。
scanf("%d",&c);
printf("请输入物品重量:");
for(i=1;i<=n;i++)
scanf("%d",&weight[i]);
printf("请输入物品价值:");
for(i=1;i<=n;i++)
scanf("%d",&price[i]);
Backtrack(1);
printf("最优解是:{");
{
int i;
printf("路径为:{");
for(i=1;i<n;i++)
printf("%d,",x[i]);
printf("%d}价值为%d\n",x[i],currentPrice);
}
int main()
{
int i;
printf("请输入物品数量:");
scanf("%d",&n);
printf("请输入背包容量:");
(b)如果第i个物品没有装入背包,则背包中物品价值就等于把前i-1个物品装入容量为j的背包中所取得的价值。显然,取二者中价值最大的作为把前i个物品装入容量为j的背包中的最优解.
时间复杂度为Байду номын сангаас(n*c),c为背包容量
伪代码:
f[0,j]=0
For i=1 to n
For j=0 to W
f[i,j]=max{f[i-1,j],f[i-1,j-wi]+vi }
u b=v+ (W-w) × (v【i+ 1】/w【i+ 1】 )
根据限界函数确定目标函数的界[ down , up],然后, 按照广度优先策略遍历问题的空间树。
时间复杂度O(2^n)
思想
借助大顶堆来实现优先队列,构造大顶堆(按优先队列中所能达到的最大价值来构造这个大顶堆),实现对堆中元素的插入和删除;在回溯法的基础上改进,首先对物品按单位权重递减排序,堆中存放的是每一个当前物品所能达到的情况:
剪枝二、将剩下的所有物品都选取,其总价值也没有目前所求得的总价值还大的话,就可以返回;
递归法:
#include<stdio.h>
int c;//背包容量
int n;//物品个数
int x[100]; //是否选取物品,为0或1
int weight[100];
int price[100];
int bp;//最大价值
int bA[100];//最优解
int times=0;//节点访问次数
int currentWeight=0; //当前总重量
int currentPrice=0;//当前总价值
void print();//输出每条有效路径及最大价值
void Backtrack(int i)
{
times+=1;
基本思想:
给定n种物品和一个容量为C的背包, 物品i的重量是Wi, 其价值为Vi, 0/ 1 背包问题是如何选择装入背包的物品(物品不可分割) , 使得装入背包中物品的总价值最大,一般情况下, 解空间树中第i层的每个结点, 都代表了对物品1~i做出的某种特定选择, 这个特定选择由从根结点到该结点的路径唯一确定: 左分支表示装入物品, 右分支表示不装入物品。对于第i层的某个结点, 假设背包中已装入物品的重量是w, 获得的价值是v, 计算该结点的目标函数上界的一个简单方法是把已经装入背包中的物品取得的价值v, 加上背包剩余容量W-w与剩下物品的最大单位重量价值vi+ 1/wi+ 1的积,于是,得到限界函数:
{
structNode * par;//指向此优先队列中所包涵的所有结点
double cp,cw;//当前达到的价值,重量
double up;//此优先队列所能达到的最大价值
int level;//它所处的层次
}*HP;
typedef struct Heap
{
int hsize;//堆中元素个数
HeapNode * heapnodes;//堆中存放可能的解
时间复杂度O(2^n)
问题分析
用回溯法实现就是要枚举其所有的解空间。
具体方法如下:
①对每一个物品i,对该物品只有选与不选两种决策,有n个物品,可以形成一棵深度为n的决策树;
②遍历这棵树,以枚举所以情况,最后进行判断,若重量不超过背包容量,且总价值最大,该方案就是最优的。
优化方法:
剪枝一:可以进行剪枝,因为很多情况是没有意义的,当重量大于背包的容量时,没有必要对剩下的物品进行决策;
C代码:
#include<iostream>
# include<cstring>
# define max(a,b) a>b?a:b
using namespace std;
int main()
{
int dp[101][1001],m,T,w[101],val[101],i,j;
cin>>T>>m;
for(i=1;i<=m;i++)
3.2回溯法
回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束;
currentPrice+=price[i];
Backtrack(i+1);
//完成上面的递归,返回上一节点,搜索右子树,及不选物品i
currentWeight-=weight[i];
currentPrice-=price[i];
}
x[i]=0;
Backtrack(i+1);
}
void print()
概述:
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
二、解决方案
背包问题作为NP完全问题,暂时不存在多项式时间算法
1.动态规划
2.回溯法
3.分支界限法
三、方案详解
3.1动态规划
动态规划(Dynamic programming,DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
3.重复步骤2.
实现代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=0x0fffff;
typedef struct Node
{
struct Node * par;
bool isLchild;
};
typedef struct HeapNode
1.将第一个单位权重最大的物品插入大顶堆中,并用回溯法中计算边界条件的函数计算出该物品放入背包时所能达到的最大价值,用这一队列最后一个结点记录下当前得到的队列的情况,它可以得到的最大值(double型),用它该取的下一层次号,所达到的最大价值,所占用背包的体积;
2.如果物品层次号(从0开始)小于总个数,取出堆顶元素,对将要遍历到的这一层次上的物品判断选择取与不取的情况.若已经占用的体积+该物品的体积不超过背包体积,则该物品可以放入,即可以是左儿子结点,isLchild=true,将这一结点加入优先队列并放入大顶堆中;若右子树可能存在最优解,isLchild=false,即不放该物品时可能达到的最大价值(double型变量)>=放入该物品时当前背包达到的最优解,则也将这一结点加入优先队列中并放入大顶堆中;
for(i=1;i<n;i++)
printf("%d,",bA[i]);
printf("%d}价值为%d\n",bA[i],bp);
printf("总共访问节点数为:%d",times);
return 0;
}
总结
递归的思想:
(1)考虑物品i被选择,这种可能性仅当包含它不超过背包总重量的限制才是可行的;选中后,继续递归去考虑其他物品的选择;
return F[n,W]
问题的解为f[n,W]
代码实现:
for(i=0;i<n;i++)
for(j=0;j<=W;j++)//j相当于上面说的W-wi
if(j>=w[i])
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+val[i]);//放还是不放的选择
elsef[i][j]=f[i-1][j]