0-1背包问题(0-1 knapsack problem)c++实现

1 问题描述

有n个物品,它们有各自的重量和价值,现有一给定最大载重的背包,如何让背包里装入的物品具有最大的价值总和而又不超过最大载重?
比如:

商品12345
重量12345
价值34567

假设背包最大载重为8,可以求得以下两种最优方案

商品1 商品2 商品5
总价值:14
总重量:8 <= 最大载重

商品1 商品3 商品4
总价值:14
总重量:8 <= 最大载重

2 基本原理

定义数组V[0…n][0…maxWeight]

其中,n表示商品的总数,maxWeight表示背包的最大载重量。

定义V[i][w]为包含前i件商品最大载重为w时的子问题的最优方案的总价值

只需要讨论当前商品i是否可能在当前子问题的最优方案中便可以得到以下递推式:

若 weigth[i] > w, 即当前商品i太重了,故不可能出现在当前子问题的最优方案里,因此
V[i][w] = V[i-1][w]

//若 weigth[i] <= w ,则说明当前商品i可能在当前子问题的最优方案中,因此需要做比较选择,故有
V[i][w] = max( V[i-1][w] , V[i-1][w-weigth[i]] + value[i] )

3代码实现

利用上面的递推式便能使用动态规划算法解决问题!
c++代码如下:

//Albert Chow 2020.08.30
# include <iostream>
using namespace std;


/***********************************************************************************/
//用于动态创建二维数组
//利用了模板
template<typename T>
T** buildArray2D(int row, int column)
{
	T* p = new T[row * column]; //开辟一片临时空间
	T** a = new T * [row];       //创建行指针

	for (int i = 0; i < row; i++)
	{
		a[i] = p + column * i;     //给行指针赋值
	}

	return a;

};

template<typename T>
void deleteArray2D(T** a)
{
	delete[] * a;
	delete[] a;
}
/***********************************************************************************/


//0-1背包问题算法

//简单版本,用于理解算法
int knapsack_0_1_v1(int n, int* weigth, int* value, int maxWeight)
{
	int** V = buildArray2D<int>(n+1, maxWeight+1); //动态创建一个二维数组
	//其中A[i][w] 表示 包含前i件商品,最大重量为w时的子问题的最大价值
	//因此可分最优方案包不包含当前商品i而得到如下的递推式:

	//若 weigth[i] > w, 即当前商品i太重了,故不可能出现在当前子问题的最优方案里,因此
	//V[i][w] = V[i-1][w]

	//若 weigth[i] <= w ,则说明当前商品i可能在当前子问题的最优方案中,因此需要做比较选择
	//V[i][w] = max(  V[i-1][w]  ,  V[i-1][w-weigth[i]] + value[i]  )

	//根据以上的递推式设计自底向上的dp算法如下:
	int i,w;
	//对V做下初始化
	for (w = 0; w <= maxWeight; w++)
		V[0][w] = 0;
	for (i = 1; i <= n; i++)
		V[i][0] = 0;


	for(i = 1;i<=n;i++)
		for (w = 1; w <= maxWeight; w++)
		{
			if (weigth[i -1] > w)    //当前商品大于背包容量时的递推式
			{
				V[i][w] = V[i - 1][w];  
			}
			else     //当前商品可能出现在最优解里面的递推式
			{
				//V[i][w] = max(  V[i-1][w]  ,  V[i-1][w-weigth[i]] + value[i]  )
				//注意cpp中数组下标从0开始

				if ( V[i - 1][w] > (V[i - 1][w - weigth[i - 1]] + value[i - 1]) )
				{
					V[i][w] = V[i - 1][w];
				}
				else
				{
					V[i][w] = V[i - 1][w - weigth[i - 1]] + value[i - 1];
				}
			}
		}

	int res = V[n][maxWeight];
	deleteArray2D<int>(V);//手动释放内存
	return res;
}

//原理同v1,只是在内存上作了优化
int knapsack_0_1_v2(int n, int* weigth, int* value, int maxWeight)
{

	int* V = new int[maxWeight + 1];
	
	int i, w;
	//对V做下初始化
	for (w = 0; w <= maxWeight; w++)
		V[w] = 0;

	for (i = 1; i <= n; i++)
		//为了防止数据覆盖,这里w采用逆序
		//由于w < weigth[i - 1]时 无需更新数组,因此w的遍历只到weigth[i - 1]
		for (w = maxWeight; w >= weigth[i - 1]; w--)  
		{		
			if (V[w - weigth[i - 1]] + value[i - 1] > V[w])
			{
				V[w] = V[w - weigth[i - 1]] + value[i - 1];
			}
			
		}

	int res = V[maxWeight];
	delete[] V;
	return res;
}


//定义一个结构体,用于函数返回多个变量
typedef struct
{
	int val;
	bool** choice;
} RES;


//原理同v1,只是为重现最优解做了一些修改
RES knapsack_0_1_v3(int n, int* weigth, int* value, int maxWeight)
{
	int** V = buildArray2D<int>(n + 1, maxWeight + 1); //动态创建一个二维数组

	bool **choice = buildArray2D<bool>(n, maxWeight);

	int i, w;
	//对V做下初始化
	for (w = 0; w <= maxWeight; w++)
		V[0][w] = 0;
	for (i = 1; i <= n; i++)
		V[i][0] = 0;


	for (i = 1; i <= n; i++)
		for (w = 1; w <= maxWeight; w++)
		{
			if (weigth[i - 1] > w)    //当前商品大于背包容量时的递推式
			{
				V[i][w] = V[i - 1][w];
				choice[i -1][w -1] = 0;  //注意c语言下标从0开始
			}
			else     //当前商品可能出现在最优解里面的递推式
			{
				//V[i][w] = max(  V[i-1][w]  ,  V[i-1][w-weigth[i]] + value[i]  )
				//注意cpp中数组下标从0开始

				if (V[i - 1][w] > (V[i - 1][w - weigth[i - 1]] + value[i - 1]))
				{
					V[i][w] = V[i - 1][w];
					choice[i - 1][w - 1] = 0;
				}
				else
				{
					V[i][w] = V[i - 1][w - weigth[i - 1]] + value[i - 1];
					choice[i - 1][w - 1] = 1;
				}
			}
		}

	RES res;
	res.val = V[n][maxWeight];
	res.choice = choice;

	//手动释放内存
	deleteArray2D<int>(V);
	return res;
}

//原理同v2,只是为重现最优解做了一些修改,注意多与v3对比
RES knapsack_0_1_v4(int n, int* weigth, int* value, int maxWeight)
{

	int* V = new int[maxWeight + 1];
	bool** choice = buildArray2D<bool>(n, maxWeight);

	int i, w;
	//对V做下初始化
	for (w = 0; w <= maxWeight; w++)
		V[w] = 0;

	for (i = 1; i <= n; i++)
	{//为了防止数据覆盖,这里w采用逆序
		//由于w < weigth[i - 1]时 无需更新数组,因此w的遍历只到weigth[i - 1]
		for (w = maxWeight; w >= weigth[i - 1]; w--)
		{
			if (V[w - weigth[i - 1]] + value[i - 1] > V[w])
			{
				V[w] = V[w - weigth[i - 1]] + value[i - 1];
				choice[i - 1][w - 1] = 1;
			}
			else
				choice[i - 1][w - 1] = 0;

		}

		for (w = weigth[i - 1]-1; w>=1; w--)  //补零
			choice[i -1][w -1] = 0;
		
	}

	RES res;
	res.val = V[maxWeight];
	res.choice = choice;
	delete[] V;
	return res;
}




//仿照算法导论p225的程序得到的
void printKnapsack(bool** choice, int *weight,int i,int w)
{
	if (i == 0 || w == 0)
		return;

	if (choice[i -1][w -1] == 1)
	{
		
		printKnapsack(choice, weight,  i - 1,  w - weight[i - 1] );
		cout << "商品" << i << " ";
		
	}
	else
	{
		printKnapsack(choice, weight, i - 1, w);
	}
}


int main()
{
	//可以自己修改数据验证算法是否正确
	              // 1  2  3  4  5  6  7  8   9   10
	int weight[]  = {1, 2, 3, 4, 5};
	int value[]   = {3, 4, 5, 6, 7};

	int n = sizeof(weight)/sizeof(weight[0]);
	int maxWeight = 8;

	/************************************************************************/
	
	int r1 = knapsack_0_1_v1(n,weight, value, maxWeight);
	cout << r1 << endl;

	int r2 = knapsack_0_1_v2(n, weight, value, maxWeight);
	cout << r2 << endl;

	RES r3 = knapsack_0_1_v3(n, weight, value, maxWeight);
	cout << r3.val << endl;

	printKnapsack(r3.choice, weight, n, maxWeight);
	deleteArray2D<bool>(r3.choice);
	cout << endl;

	RES r4 = knapsack_0_1_v4(n, weight, value, maxWeight);
	cout << r4.val << endl;

	printKnapsack(r4.choice, weight, n, maxWeight);
	deleteArray2D<bool>(r4.choice);
	cout << endl;

	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
0-1背包问题是一个经典的动态规划问题,其目标是在给定一组物品的重量和价值以及一个背包的容量限制下,选择一些物品放入背包,以便在不超过背包容量的前提下,使得背包中物品的总价值最大化。 一种常见的C语言实现0-1背包问题的方法如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> int max(int a, int b) { return (a > b) ? a : b; } int knapSack(int W, int wt[], int val[], int n) { int i, w; int **K = (int **)malloc((n + 1) * sizeof(int *)); for (i = 0; i <= n; i++) { K[i] = (int *)malloc((W + 1) * sizeof(int)); } for (i = 0; i <= n; i++) { for (w = 0; w <= W; w++) { if (i == 0 || w == 0) { K[i][w] = 0; } else if (wt[i - 1] <= w) { K[i][w] = max(val[i - 1] + K[i - 1][w - wt[i - 1]], K[i - 1][w]); } else { K[i][w] = K[i - 1][w]; } } } int result = K[n][W]; for (i = 0; i <= n; i++) { free(K[i]); } free(K); return result; } int main() { int n, W; scanf("%d%d", &n, &W); int *wt = (int *)malloc(n * sizeof(int)); int *val = (int *)malloc(n * sizeof(int)); for (int i = 0; i < n; i++) { scanf("%d%d", &wt[i], &val[i]); } int ans = knapSack(W, wt, val, n); printf("%d\n", ans); free(wt); free(val); return 0; } ``` 这个实现中使用二维数组K来保存每个子问题的最优解。首先,我们初始化一个大小为(n+1) x (W+1)的二维数组K,并将所有元素初始化为0。然后,我们使用两个嵌套的循环依次计算K的每个元素的值,直到得到整个问题的最优解。 以上是一个基本的0-1背包问题的C语言实现。你可以根据需要对其进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值