一、动态规划
状态转移方程:
从前往后:
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
从后往前:
if(j>=w[i])
m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
else
m[i][j]=m[i+1][j];
算法:
从前往后:
for(int i=1;i<=n;i++)
for(int j=1;j<=c;j++)
{
if(j>=w[i])
{
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
}
else//这里没有考虑j<0的情况,因为算法中j取不到
{
m[i][j]=m[i-1][j];
}
}
从后往前:
for(int i=n;i>=1;i--)
for(int j=1;j<=c;j++)
{
if(j>=w[i])
{
m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
}
else
{
m[i][j]=m[i+1][j];
}
}
例子:
例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制
重量数组w = {4, 6, 2, 2, 5, 1},
价值数组v = {8, 10, 6, 3, 7, 2},
背包容量C = 12时对应的m[i][j]数组。(从前往后)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
1 | 0 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 |
2 | 0 | 0 | 0 | 8 | 8 | 10 | 10 | 10 | 10 | 18 | 18 | 18 |
3 | 0 | 6 | 6 | 8 | 8 | 14 | 14 | 16 | 16 | 18 | 18 | 24 |
4 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 19 | 24 |
5 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 21 | 24 |
6 | 2 | 6 | 8 | 9 | 11 | 14 | 16 | 17 | 19 | 19 | 21 | 24 |
例题代码 :
#include<iostream>
#include<cmath>
#include<cstring>
#define N 20
using namespace std;
int main()
{
int w[N]={0,4,6,2,2,5,1},v[N]={0,8,10,6,3,7,2};
int m[N][N];
memset(m,0,sizeof(m));
int n=6,c=12; //n,c均要小于N
for(int i=1;i<=n;i++)
for(int j=1;j<=c;j++)
{
if(j>=w[i])
{
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
}
else
{
m[i][j]=m[i-1][j];
}
}
cout<<m[n][c]<<endl; //从前往后
/*
for(int i=n;i>=1;i--)
for(int j=1;j<=c;j++)
{
if(j>=w[i])
{
m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
}
else
{
m[i][j]=m[i+1][j];
}
}
cout<<m[1][c]<<endl;//从后往前
*/
return 0;
}
二、回溯法
1进入左子树条件:cw+w[i]<=c //cw为当前重量
2进入右子树条件(减枝函数):cp+r>bestp //cp为当前价值,bestp为当前最优价值,r为当前剩余物品价值总和。cp+r由函数 Bound计算。
3需要先将物品按单位重量价值从大到小排序,按序进入左子树;进入右子树时,由函数Bound计算当前节点上界,只有其上界大于当前最优价值bestp时,才进入右子树,否则减去。
算法:
void Backtrack(int i)
{
if(i>n) //到达叶节点
{
bestp=cp;
return;
}
if(cw+w[i]<=c) //进入左子树
{
cw+=w[i];
cp+=v[i];
Backtrack(i+1);
cw-=w[i];
cp-=v[i];
}
if(Bound(i+1)>bestp) //进入右子树
{
Backtrack(i+1);
}
}
int Bound(int i) //计算上界
{
int cleft=c-cw;
int b=cp;
while(i<=n&&w[i]<=cleft) //以物品单位重量价值递减序装入物品
{
cleft-=w[i];
b+=v[i];
i++;
}
if(i<=n)//装满背包
{
b+=v[i]*(cleft/w[i]);
}
return b;
}
例子代码:
#include<iostream>
#define N 20
using namespace std;
int w[N]={0,4,6,2,2,5,1},v[N]={0,8,10,6,3,7,2};
int n=6,c=12;
int cp=0,cw=0,bestp=0;
int Bound(int i) //计算上界
{
int cleft=c-cw;
int b=cp;
while(i<=n&&w[i]<=cleft) //以物品单位重量价值递减序装入物品
{
cleft-=w[i];
b+=v[i];
i++;
}
if(i<=n)//装满背包
{
b+=v[i]*(cleft/w[i]);
}
return b;
}
void Backtrack(int i)
{
if(i>n) //到达叶节点
{
bestp=cp;
return;
}
if(cw+w[i]<=c) //进入左子树
{
cw+=w[i];
cp+=v[i];
Backtrack(i+1);
cw-=w[i];
cp-=v[i];
}
if(Bound(i+1)>bestp) //进入右子树
{
Backtrack(i+1);
}
}
int main()
{
Backtrack(1);
cout<<bestp<<endl;
return 0;
}