前面讲了0-1背包的回溯解决方法,它是穷举所有可能,复杂度是指数级别的,如何降低时间复杂度呢?
对于一组不同重量、不可分割的物品,我们需要选择一些装入背包,在满足背包最大重量限制的前提下,背包中物品总重量的最大值是多少呢?
假设背包的最大承载重量是9。有5个不同的物品,重量分别是2,2,4,6,3。把回溯求解过程,用递归树画出来,就是下面这个样子:
从上面图中可以看出有些函数被重复计算(之前递归中也讲到了),我们希望当计算到已经计算过的函数时,不要重复计算,直接拿来用,避免重复劳动。
还是前面的0-1背包问题,分别添加和不添加重复计算判断语句,查看效果
#include <iostream>
#define MaxWeight 9 //背包承载极限
using namespace std;
bool mem [5][9];
int counttimes;
void fill(int i, int curWeight, int *bag, int N, int &maxweightinbag)
{
cout << "调用次数: " << ++counttimes << endl;
if(curWeight == MaxWeight || i == N)//到达极限了,或者考察完所有物品了
{
if(curWeight > maxweightinbag)
maxweightinbag = curWeight;//记录历史最大装载量
return;
}
//-----注释掉以下3行查看效果-------
if(mem[i][curWeight])
return;
mem[i][curWeight] = true;
//---------------------------------
fill(i+1,curWeight,bag,N,maxweightinbag);//不选择当前i物品,cw不更新
if(curWeight+bag[i] <= MaxWeight)//选择当前i物品,cw更新
{//没有达到极限,继续装
fill(i+1,curWeight+bag[i],bag,N,maxweightinbag);
}
}
int main()
{
const int N = 5;
int bag[N] = {2,2,4,6,3};
int maxweightinbag = 0;
fill(0,0,bag,N,maxweightinbag);
cout << "最大可装进背包的重量是:" << maxweightinbag;
return 0;
}
bag[N] = {2,2,4,6,3}; MaxWeight = 9
/**
* @description: 0-1背包--dp应用
* @author: michael ming
* @date: 2019/7/9 1:13
* @modified by:
*/
#include <iostream>
#define MaxWeight 9 //背包承载极限
const int N = 5; //背包个数
using namespace std;
bool states[N][MaxWeight+1];//全局参数自动初始化,默认是false
int fill_dp(int *bag, int N)
{
states[0][0] = true;//第1个背包不放
if(bag[0] <= MaxWeight)
states[0][bag[0]] = true;//第1个背包放
for(int i = 1; i < N; ++i)//动态规划状态转移
{
for(int j = 0; j <= MaxWeight; ++j)//不把第i个物品放入背包
{
if(states[i-1][j] == true)
states[i][j] = states[i-1][j];//把上一行的状态复制下来(i不放物品)
}
for(int j = 0; j+bag[i] <= MaxWeight; ++j)
if(states[i-1][j] == true)
states[i][j+bag[i]] = true;//把第i个物品放入背包
}
for(int i = MaxWeight; i >= 0; --i)//把最后一行,从后往前找最重的背包
{
if(states[N-1][i] == true)
return i;//最大重量
}
return 0;
}
int main()
{
int bag[N] = {2,2,4,6,3};
cout << "最大可装进背包的重量是:" << fill_dp(bag,N);
return 0;
}
上面就是一种用动态规划解决问题的思路。把问题分解为多个阶段,每个阶段对应一个决策。记录每一个阶段可达的状态集合(去掉重复的),然后通过当前的状态集合,来推导下一阶段的状态集合,动态地往前推进。
/**
* @description:
* @author: michael ming
* @date: 2019/7/15 22:00
* @modified by:
*/
#include <iostream>
#define MaxWeight 9 //背包承载极限
const int N = 5; //背包个数
using namespace std;
bool states[MaxWeight+1];//全局参数自动初始化,默认是false
int fill_dp(int *bag, int N)
{
states[0] = true;//第1个背包不放
if(bag[0] <= MaxWeight)
states[bag[0]] = true;//第1个背包放
for(int i = 1; i < N; ++i)//动态规划状态转移
{
for(int j = MaxWeight-bag[i]; j >= 0; --j)//把第i个物品放入背包
{
if(states[j] == true)
states[j+bag[i]] = true;
}
}
for(int i = MaxWeight; i >= 0; --i)//输出结果
{
if(states[i] == true)
return i;//最大重量
}
return 0;
}
int main()
{
int bag[N] = {2,2,4,6,3};
cout << "最大可装进背包的重量是:" << fill_dp(bag,N);
return 0;
}
内层for循环,j 需要从大到小处理,如果从小到大,会出现重复计算
每个物品对应着一种价值,不超过背包载重极限,求可装入背包的最大总价值。
//--------回溯解法-------------------
int maxV = -1; // 最大价值放到 maxV 中
int weight[5] = {2,2,4,6,3}; // 物品的重量
int value[5] = {3,4,8,9,6}; // 物品的价值
int N = 5; // 物品个数
int MaxWeight = 9; // 背包承受的最大重量
void f(int i, int cw, int cv)
{ // 调用 f(0, 0, 0)
if (cw == MaxWeight || i == N)
{ // cw==MaxWeight 表示装满了,i==N 表示物品都考察完了
if (cv > maxV)
maxV = cv;
return;
}
f(i+1, cw, cv); // 选择不装第 i 个物品
if (cw + weight[i] <= w)
{
f(i+1, cw+weight[i], cv+value[i]); // 选择装第 i 个物品
}
}
对上面代码画出递归树,每个节点表示一个状态。现在我们需要3个变量(i,cw,cv)来表示一个状态。其中,i表示即将要决策第i个物品是否装入背包,cw表示当前背包中物品的总重量,cv表示当前背包中物品的总价值。
/**
* @description: 0-1背包带价值,dp解法
* @author: michael ming
* @date: 2019/7/16 0:07
* @modified by:
*/
#include <iostream>
#define MaxWeight 9 //背包承载极限
const int N = 5; //背包个数
using namespace std;
int fill_value_dp(int* weight, int* value, int N)
{
int (*states) [MaxWeight+1] = new int [N][MaxWeight+1];
for (int i = 0; i < N; ++i) // 初始化 states
{
for (int j = 0; j < MaxWeight+1; ++j)
states[i][j] = -1;
}
states[0][0] = 0;//第一个不放,价值0存入states
if (weight[0] <= MaxWeight)
{
states[0][weight[0]] = value[0];//第一个放入背包
}
for (int i = 1; i < N; ++i) // 动态规划,状态转移
{
for (int j = 0; j <= MaxWeight; ++j)
{ // 不选择第 i 个物品
if (states[i-1][j] >= 0)
states[i][j] = states[i-1][j];//直接复制上一层的状态
}
for (int j = 0; j+weight[i] <= MaxWeight; ++j)
{ // 选择第 i 个物品
if (states[i-1][j] >= 0)
{
int v = states[i-1][j] + value[i];
if (v > states[i][j+weight[i]])
{//只存价值最大的
states[i][j+weight[i]] = v;
}
}
}
}
// 找出最大值
int maxvalue = -1;// 最大价值放到 maxvalue 中
for (int j = 0; j <= MaxWeight; ++j)
{
if (states[N-1][j] > maxvalue)
maxvalue = states[N-1][j];
}
delete [] states;
return maxvalue;
}
int main()
{
int weight[5] = {2,2,4,6,3}; // 物品的重量
int value[5] = {3,4,8,9,6}; // 物品的价值
cout << "最大可装进背包的价值是:" << fill_value_dp(weight,value,N);
return 0;
}
时间和空间复杂度都是O(N * MaxWeight)
使用一维数组也可DP解题,空间复杂度减小为O(MaxWeight)
/**
* @description: 0-1背包带价值,dp解法(状态存储用一维数组)
* @author: michael ming
* @date: 2019/7/16 22:17
* @modified by:
*/
#include <iostream>
#define MaxWeight 9 //背包承载极限
const int N = 5; //背包个数
using namespace std;
int fill_value_dp(int* weight, int* value, int N)
{
int *states = new int [MaxWeight+1];
for (int i = 0; i < MaxWeight+1; ++i) // 初始化 states
{
states[i] = -1;
}
states[0] = 0;//第一个不放,价值0存入states
if (weight[0] <= MaxWeight)
{
states[weight[0]] = value[0];//第一个放入背包
}
for (int i = 1; i < N; ++i) // 动态规划,状态转移
{
for (int j = MaxWeight-weight[i]; j >= 0; --j)
// for (int j = 0; j <= MaxWeight-weight[i]; ++j)
{ // 选择第 i 个物品
if (states[j] >= 0)
{
int v = states[j] + value[i];
if (v > states[j+weight[i]])
{//只存价值最大的
states[j+weight[i]] = v;
}
}
}
}
// 找出最大值
int maxvalue = -1;// 最大价值放到 maxvalue 中
for (int i = 0; i <= MaxWeight; ++i)
{
if (states[i] > maxvalue)
maxvalue = states[i];
}
delete [] states;
return maxvalue;
}
int main()
{
int weight[N] = {2,2,4,6,3}; // 物品的重量
int value[N] = {3,4,8,9,6}; // 物品的价值
cout << "最大可装进背包的价值是:" << fill_value_dp(weight,value,N);
return 0;
}
最大可装进背包的价值是:18 一维数组变化过程如下: