大概的原理如下:
w[]数组是重量,v[]数组是价值,它们都是从下标1开始的
(1). j < w[i] 的情况,剩余容量不能放下第 i 件物品,只能不拿
m[ i ][ j ] = m[ i-1 ][ j ]
(2). j>=w[i] 的情况可以放下,看拿与不拿谁获取更大的价值(value)。
如果拿,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。
如果不拿,m[ i ][ j ] = m[ i-1 ][ j ]
两种方法:
从左上到右下(可执行部分)和从左下到右上(main函数下边的注释部分)
#include<iostream>
#include<iomanip>
using namespace std;
//这是从左上到右下的做法
void Knapsack(int *v, int *w, int n, int content, int **m)//j表示现在的容量,v即value,w即weight,i表示现在的数组中的位置,n表示物品的数量
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= content; j++)
{
if (j<w[i])//装不下
{
m[i][j] = m[i - 1][j];
}
else
{
if (m[i - 1][j] > m[i - 1][j - w[i]] + v[i])//回到上一行不装的情况看一下,如果不装价值比较大
{
m[i][j] = m[i - 1][j];
}
else
{
m[i][j] = m[i - 1][j - w[i]] + v[i];
}
}
}
}
/*以下是两种回溯的方法,都可以用
void Trace(int j,int i, int *v, int *w, int *signal, int **m)//寻找解的组成
{
if (i > 0)//一直写成大于等于0导致越界,发生访问冲突
{
if (m[i][j] == m[i - 1][j])
{
signal[i] = 0;
return Trace(j,i - 1, v, w, signal, m);
}
else if (j >= w[i] && m[i][j] == m[i - 1][j - w[i]] + v[i])
{
signal[i] = 1;
return Trace(j - w[i],i - 1, v, w, signal, m);
}
}
}
void Trace(int c, int n, int *v, int *w, int *s, int **m)
{
for (int i = n; i >= 1; i--)//i=1也可以包括进来
{
if (m[i][c] == m[i - 1][c])//上面的一行的对应数如果相等,则表示没选
s[i] = 0;
else
{
s[i] = 1;
c -= w[i];//减掉选中的物品重量
}
}
//s[1] = (m[1][c]>0) ? 1 : 0;//i=1的情况
}
*/
void Trace(int c, int n, int *v, int *w, int *s, int **m)//寻找解的组成方式,c即capacity
{
int i;
for (i = 1; i<n; i++)
if (m[i][c] == m[i + 1][c]) s[i] = 0;
//可选择物品中有第i件物品或没有第i件物品,最优值都是一样的,故一定没有放入第 i件物品
else
{
s[i] = 1;
c = c - w[i];//选择了第i件物品,减去它的重量,
}
if (m[n][c]>0) //一定要单独判断是否选择了第 n件物品,如果放到循环中数组会越界
s[i] = 1;
else s[i] = 0;
}
int main()
{
int n, m;//n物品个数,m背包大小
cout << "please input the size of backpack:";
cin >> m;
cout << "please input the scale of choices:";
cin >> n;
int *v = new int[n + 1];//value
int *w = new int[n + 1];//weight
int *signal = new int[n + 1];//标志物品是否被选中
int **p;
p = new int*[n + 1];//记录迭代的过程
for (int i = 0; i <= n; i++)
{
p[i] = new int[m + 1];
memset(p[i], 0, sizeof(int)*(m+1));//p赋初值为0,sizeof一个指针返回的是指针大小,所以还是sizeof(int)
}
for (int i = 0; i <= n; i++)
signal[i] = 0;
cout << "please input the weight of choices:";
for (int i = 1; i <= n; i++)
cin >> w[i];
cout << "please input the values of choices:";
for (int i = 1; i <= n; i++)
cin >> v[i];
Knapsack(v, w, n, m, p);//调用
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= m; j++)
cout << setw(3) << p[i][j];
cout << endl;
}
Trace(m,n,v,w,signal,p);//调用
cout << "the best choice is:";
for (int i = 1; i <= n; i++)
cout << signal[i] << " ";
cout << endl << "the total value is" << p[n][m];
for (int i = 0; i <= n; i++)
delete[] p[i];
delete[] p;
delete[] v;
delete[] w;
return 0;
}
/*这是从左下到右上的做法
int Max(int a, int b)
{
int max = a>b ? a : b;
return max;
}
int Min(int a, int b)
{
int min = a<b ? a : b;
return min;
}
void Knapsack(int *v, int *w, int n, int c, int **m)//v是价值,w是所占空间c是总容量m是最优值
{
int jMax = Min(w[n] - 1, c);//w[n]-1的原因是下面的j<=jMax
for (int j = 0; j <= jMax; j++)//最下面一行赋值j(动态容量大小)不够,则赋值为0
m[n][j] = 0;
for (int j = w[n]; j <= c; j++)//赋值为相应的物品的价值
m[n][j] = v[n];
for (int i = n - 1; i >= 1; i--)//书上i>1,又将1的情况放到外边处理,好处是可以节省一定的计算空间不节省也可以
{
jMax = Min(w[i] - 1, c);//与上面一样,然后处理按照公式
for (int j = 0; j <= jMax; j++)
m[i][j] = m[i + 1][j];//赋值为下边一行的数
for (int j = w[i]; j <= c; j++)
m[i][j] = Max(m[i + 1][j], m[i + 1][j - w[i]] + v[i]);//加上第i个物品价值大的话就覆盖
}
}
void TraceBack(int c, int n, int *v, int *w, int **m)//*x记录选取情况,c即capacity,n物品多少
{
for (int i = 1; i<n; i++)
if (m[i][c] == m[i + 1][c])//与下边的一行值相同的话,表示不选
v[i] = 0;
else
{
v[i] = 1;
c -= w[i];//减掉选这个物品的重量
}
v[n] = (m[n][c]) ? 1 : 0;//最后的一个定要分出来,要不然会越界
}*/
这是从左上到右下的结果
我在做的时候,一开始觉得很不可思议,这个代码是一位大神写的,想着一定是对的,有一点不相信自己以为是,自己new开的二维数组有错,可事实是,他的代码有错,就错在Trace函数(递归的那个求法)的if(i>=0)而下面有访问二维数组m[i+1][j]的这种情况,所以越界了。所以还是要多相信自己
出现了这种情况:
点击调试后类似下面这种情况,不用怀疑,有很大程度上都是越界错误
总结:还是自己思路要清晰,相信自己,不要迷信权威