0-1背包问题(回溯法)
Posted 没谱的曲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0-1背包问题(回溯法)相关的知识,希望对你有一定的参考价值。
前言
在之前的博客中,我写过用动态规划解决的0-1背包问题(具体可查看动态规划(0-1背包问题)),但是在最近刷软考题的时候,发现回溯法也可以解决0-1背包问题,对于回溯法我不是很了解,于是在学习了回溯法之后,我有用回溯法写了0-1背包的算法过程
0-1背包问题
给定n种物品和一背包。物品 i 的重量为 w[i],其价值为 v[i],背包的容量为 c。问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
回溯法
回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
我们可以将之理解成,把问题转换成一棵树,通过深度优先搜索的方式,将所有的叶子节点都搜索一遍,在搜索的过程中,我们添加一些限定条件,把不符合条件的节点直接排除,以达到最终的算法目的。当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
问题分析
对于有n种物品的 0-1 背包问题,其解空间由长度为n的 0-1 向量组成,该解空间包含了对变量的所有可能的0-1 赋值。当 n=3 时,其解空间是{ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1) }
对于n= 3的0-1 背包问题,其解空间可以用一棵完全二叉树表示,从树根到叶子结点的任意一条路径可表示解空间中的一个元素, 如从根结点A到结点J的路径对应于解空间中的一个元素(1, 0, 1)。
运用回溯法解题的关键要素有以下三点:
- 针对给定的问题,定义问题的解空间;
- 确定易于搜索的解空间结构;
- 以深度优先方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
建立二叉树
我们建立一个简单的二叉树模型
* A
* / \\
* Y N X[0]
* / \\ / \\
* Y N Y N X[1]
* /\\ /\\ /\\ /\\
* Y N Y N Y N Y N X[2]
* 8 7 6 5 4 3 2 1
- 通过上面的二叉树我们可以进行问题分析:X[i]表示第i件物品,Y/N代表是否选择该物品
- 当我们三件物品都不选择的时候就来到了1处,相应的我们就可以计算出1处对应的物品价值
- 这时我们回溯到X[1](第二件物品处),我们选择取第三件物品,这时我们就来到了2处,得出2处的价值后,我们就可以将两处比较,留下价值高的
- 然后再回溯到第一件物品处,选择取第二件物品不取第三件物品,就来到3处,相应的得出价值,进行比较
如此反复可以得到每一处所取物品对应的价值,我们再在其中添加相应的约束条件,所选物品重量相加不能超出背包容量,这样就可以进行剪枝操作
代码实现
通过上述的问题分析,我们就可以开始解决代码部分了
public class KnapsackProblem_2 {
//0-1背包问题(回溯算法)
//给定n种物品和一背包。物品i的重量是wi,其价值为pi,背包的容量为C。 问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
static int MaximumValue = 0; // 最大价值;当前的最大价值,初始化为0
static int CurWeight = 0; // 当前放入背包的物品总重量
static int CurValue = 0; // 当前放入背包的物品总价值
public static int backtrack(int t, int[] W, int[] v, int C) {
int N = W.length;// 物品数量
int[] x = new int[N];// x[i]=1代表物品i放入背包,0代表不放入
for (int i = 0; i < N; i++) {//背包置空
x[i] = 0;
}
// 如果是子节点 当前价值和最佳价值做判断 保存最佳价值
if (t > N - 1) {
if (CurValue > MaximumValue) {
MaximumValue = CurValue;
}
return MaximumValue;
}
// 如果不是子节点 对子节点进行遍历
else {
// 就两种情况 取或不取 用0/1表示
for (int i = 0; i <= 1; i++) {
x[t] = i;
if (i == 0) {
// 如果是不取 就不需要进行判断 直接到下一个节点
backtrack(t + 1, W, v, C);
} else
// 放入背包就进行约束条件 判断放入背包的东西是否合法
{
if (CurWeight + W[t] <= C) {
CurWeight += W[t];
CurValue += v[t];
// 当东西装进入背包后你可以进行对下个商品的判断了
backtrack(t + 1, W, v, C);
//能执行以下两个语句就说明你回溯到了上一个节点 所以你就需要恢复现场 把你刚刚拿的东西退出来 我们要冲上一个节点又要重新来遍历 如果不减你就会多加一遍
CurWeight -= W[t];
CurValue -= v[t];
}
}
}
}
return MaximumValue;
}
}
代码完成后我们现在来测试一下
public static void main(String[] args) {
int[] W = {1, 4, 3}; // 每个物品的重量
int[] v = {1500, 4000, 3000};// 每个物品的价值
int C = 4;//背包容量
backtrack(0, W, v, C);
System.out.println(MaximumValue);
}
运行结果:
结果正确!
结语:
学习算法的过程是很漫长的,当你解决了一个问题的时候,不妨多想想有没有别的方法可以解决这个问题,只有反复解决新的问题,勤加练习,多多思考,多向他人请教,才能有更多收获。
以上是关于0-1背包问题(回溯法)的主要内容,如果未能解决你的问题,请参考以下文章