01背包问题(两种方法),及数组访问冲突问题原因

大概的原理如下:

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]的这种情况,所以越界了。所以还是要多相信自己

出现了这种情况:



点击调试后类似下面这种情况,不用怀疑,有很大程度上都是越界错误



总结:还是自己思路要清晰,相信自己,不要迷信权威


猜你喜欢

转载自blog.csdn.net/changer_WE/article/details/80486341