0-1背包问题(回溯算法)

问题描述

给定n种物品和一背包。物品i的重量为wi,其价值为vi, 背包容量为c。问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大?

完整代码

#include "stdafx.h"
int n;//物品的个数
int c;//背包的容量
int bestp;//最大价值(最优值)
int p[20],w[20];//存每个物品的价值和重量
int x[20],bestx[20];

int Bound(int i, int cp,int cw){//限界函数
	int cleft=c-cw;//剩余容量
	int b=cp;
	//以物品单位重量价值递减次序装入物品
	while(i<=n && w[i]<cleft)
	{	
		cleft-=w[i];
		b+=p[i];
		i++;
	}
	//装满背包
	if(i<=n){
        b+=p[i]*cleft/w[i];
     	return b;
	} 
}

void Backtrack(int i,int cp,int cw){   
	if(i>n){//回溯结束    
		if(cp>bestp){//cp当前包内物品价值     
	        bestp=cp;
            for(i=1;i<=n;i++){
               bestx[i]=x[i];
			}
        }
	   return;
    }
    if(cw+w[i]<=c){ //进入左子树
		x[i]=1; 
	    cw+=w[i];  
		cp+=p[i];
        Backtrack(i+1,cp,cw);
        cw-=w[i];
		cp-=p[i];
    }
    if(Bound(i+1,cp,cw)>bestp){ //进入右子树 
		x[i]=0;
		Backtrack(i+1,cp,cw);
	}
}


int main(int argc, char* argv[])
{
	int i;
    printf("请输入物品的数量:\n");
    scanf("%d",&n);
    printf("请输入每个物品的重量和价值:\n");
    for(i=1;i<=n;i++) {    
       scanf("%d%d",&w[i],&p[i]);
    }
    printf("请输入背包的容量:\n");
    scanf("%d",&c);
    Backtrack(1,0,0);
    printf("最大价值为:\n%d",bestp);
    printf("\n最优解为:\n");
    for(i=1;i<=n;i++)
    {
         printf("%d ",bestx[i]);
    }
	printf("\n");
	return 0;
}

测试

**测试过程:**运行程序,根据提示输入物品的数量,每个物品的重量和价值以及背包的容量,回车后系统显示最大的价值即最优值和最优解。
在这里插入图片描述

问题讨论

**讨论时间复杂度:**在最坏的情况下,所搜索的结果是一个满二叉树,此时相当于采用的就是穷举法,时间复杂度为在这里插入图片描述,而每次决定是否要讲n个物品放入背包都要进行比较,这一步的时间复杂度为n,所以最坏情况下时间复杂度为在这里插入图片描述

回溯法解0_1背包问题时,会用到状态空间树。在搜索状态空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。当右子树有可能包含最优解时才进入右子树搜索,否则将右子树剪去。设r是当前剩余物品价值总和;cp是当前价值;bestp是当前最优价值。当cp+r≤bestp时,可剪去右子树。计算右子树中解的上界可以用的方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。由此得到的价值是右子树中解的上界,用此值来剪枝。 为了便于计算上界,可先将物品依其单位重量价值从大到小排序,此后只要顺序考察各物品即可。在实现时,由MaxBoundary函数计算当前结点处的上界。它是类Knap的私有成员。Knap的其他成员记录了解空间树种的节点信息,以减少函数参数的传递以及递归调用时所需要的栈空间。在解空间树的当前扩展结点处,仅当要进入右子树时才计算上界函数MaxBoundary,以判断是否可以将右子树减去。进入左子树时不需要计算上界,因为其上界与父结点的上界相同。 在调用函数Knapsack之前,需要先将各物品依其单位重量价值从达到小排序。为此目的,我们定义了类Objiect。其中,运算符与通常的定义相反,其目的是为了方便调用已有的排序算法。在通常情况下,排序算法将待排序元素从小到大排序。 在搜索状态空间树时,由函数Backtrack控制。在函数中是利用递归调用的方法实现了空间树的搜索
0-1背包问题计算机科学中的一个经典动态规划问题,它涉及到在给定的一组物品中选择一些放入容量有限的背包,使得这些物品的总价值最大,每种物品只能取一次。这个问题可以用回溯算法求解,特别是当物品有无限多种可能性时。 在C++中,解决0-1背包问题回溯算法通常包括以下几个步骤: 1. 定义状态:用二维数组dp[i][j]表示前i件物品在容量为j的情况下能得到的最大价值。 2. 初始化:dp[0][j] = 0,表示没有物品时背包的价值为0;对于每个物品,dp[i][0] = val[i],表示单件物品的价值。 3. 动态规划:遍历所有物品和背包容量,如果当前物品可以放入背包(即其重量小于等于剩余容量),则更新dp[i][j]为当前物品价值加上dp[i-1][j-w[i]](w[i]为第i个物品的重量),否则dp[i][j] = dp[i-1][j]。 4. 回溯:当达到某个时刻,如果背包已满但仍有一个未考虑的物品,则说明当前路径不合适,需要回溯到上一步,尝试其他物品组合。 下面是简化的伪代码示例: ```cpp bool chooseItem(int i, int w[], int j, vector<int>& val, vector<vector<int>>& dp) { if (j < w[i]) // 当物品放不下时 return false; else { dp[i][j] = max(dp[i][j], val[i] + dp[i - 1][j - w[i]]); // 更新dp值 return true; // 继续尝试其他物品 } } vector<int> knapsack(int w[], int val[], int n, int W) { vector<vector<int>> dp(n+1, vector<int>(W+1)); for (int i = 1; i <= n; ++i) for (int j = 1; j <= W; ++j) chooseItem(i, w, j, val, dp); return dp[n][W]; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LongTermism

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值