最少背包问题

austinls 2012-04-26 12:40:12
最少背包问题:
假设有许多盒子,每个盒子能保存的总重量为1.0。有N个项i1,i2,…,iN,它们的重量分别是w1,w2,…,wN。
目的是用尽可能少的盒子放入所有的项,任何盒子的重量不能超过他的容量。
例如,如果想的重量为0.4, 0.4, 0.6和0.6,用两个盒子就能解决。
按如下策略解决此问题:按给定的次序扫描每一个项,把每一个项放入能够容纳他而不至于溢出的最满的盒子。用优先级队列选择要装入的盒子。


我试了一下,按照题目中给出的策略,根本解决不了。
不知道谁能给说个思路,不用上代码。

拜谢!
...全文
1802 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
lisiyang2006 2012-04-28
  • 打赏
  • 举报
回复
最重的货物放入第一个盒子固定不动。然后第二个重到最轻一个按0,1编码。做为2进制数从低位到高位。不断尝试,如果能够装满第一个盒子,就选用这个方法。如果没有装满。就选用空余最少,并且前面出现的方法。
再依次尝试装第2个盒子。
keeya0416 2012-04-27
  • 打赏
  • 举报
回复
呀 咋没看到自己的回复 貌似卡了

这个问题不能用回溯解决,数量级到100以上以现在的计算机也得算到地球灭亡才能算完
初步认为能用“贪心”结合“背包”解决这个
但我还不确定“贪心”得到的结果是不是最优的
这个需要证明下
keeya0416 2012-04-27
  • 打赏
  • 举报
回复
这个问题用回溯做的话 只要物品数达到100个以上
想得到结果得等到地球灭亡

我初步认为 “贪心” 加 “背包” 就可以解决这个问题
不过我还没细想证明下 “贪心” 是不是能得到最优的
dracularking 2012-04-27
  • 打赏
  • 举报
回复
嗯,这种方法就相当于在一定盒子(更少)数量的状态下,去测试遍历所有装球方法,能顺利装下就得解。
递归方法就是 拿一定数量的球去填装一定数量的盒子,返回成功与否,不成功就相当于回溯再计算
qybao 2012-04-27
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 的回复:]
这个不一定,呵呵
比如盒子容量是1.0
每个单项是6.0 20个。
需要20个盒子,但是那么计算是12个盒子。
[/Quote]
有道理,疏忽大意了。
那就从最少需要的盒子数开始到最大需要盒子数(也就是球的个数)为止循环,依次采用回溯法就可以了
也就是在上面的代码中多加一个for
for (int cnt=count; cnt<=balls.length; cnt++) { //加个for循环
if (cnt > boxes.size()) boxes.add(new LinkedList<Integer>());
//初始化loaded和used,weight

while (loaded.size() < balls.length) { //装载的球少于所有的球则循环
if (box == count) { //如果尝试到最后一个盒子结束
if (loaded.size() == 0) { //如果回退到最开始一个球也没装载的状态,则无解
//System.out.println("no result");
// return;
break; //这里用break而不是return
}
for (int i=used.size()-1; i>=loaded.size(); i--) {
used.get(i).clear(); //回退到某个状态时,该状态以后的状态清空(也就是该状态以后的状态初始化)
}
index = loaded.remove(loaded.size() - 1); //回退,取出最后一次装载的球
for (box=count-1; box>=0; box--) {//还原上一次装载状态
if (boxes.get(box).contains(index)) { //找到最后一次装载的盒子
weight[box] -= balls[index]; //去掉最后一次转载的球,并还原状态
index = boxes.get(box).indexOf(index);
boxes.get(box).remove(index);
break;
}
}
}

for (int i=0; i<balls.length; i++) { //遍历所有的球,尝试把球装到盒子里
if (loaded.contains(i)) continue; //如果球被装载了,则换下个球
if (used.get(loaded.size()).contains(i)) continue; //如果球装载尝试过了,则换下一个球
if (full-weight[box] >= balls[i]) { //如果盒子还能装球
weight[box] += balls[i];
used.get(loaded.size()).add(i); //记录装载顺序的球的下标
loaded.add(i);
boxes.get(box).add(i);
}
}
box++; //下一个盒子
}

if (loaded.size() == balls.length) break; //退出for循环
}
sdojqy1122 2012-04-27
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 的回复:]

这种问题,可以用回溯法来处理
思路,先计算出最少要用多少个盒子,也就是
盒子数 = 球的总总量 / 盒子的容量
[/Quote]
这个不一定,呵呵
比如盒子容量是1.0
每个单项是6.0 20个。
需要20个盒子,但是那么计算是12个盒子。
qybao 2012-04-27
  • 打赏
  • 举报
回复
这种问题,可以用回溯法来处理
思路,先计算出最少要用多少个盒子,也就是
盒子数 = 球的总总量 / 盒子的容量
如果刚好整除,盒子数就是最少盒子数,如果不能整除(说明有剩余),则盒子数+1
然后遍历所有球让每个盒子都尝试去装球,如果遍历下来,球更好能装完,则问题解决,如果不能装完,则会退到上一次装球的状态,换另一个球尝试,所有可能尝试结束,还是不能装完,则再会退到上上次装球的状态,如此循环,不断的回退,尝试新的装球,直到找出某个装球方法刚好满足盒子用完球也装完,否则,如果一直回退到最开始没有装球的状态,说明本题无解,也就是给定的盒子不能装完所有的球(如果球和盒子都是符合规则的,一般不会出现这种结果,只有球和盒子不符合规则,比如1个球的重量大于盒子的容量,没有盒子能装这个球,所以无解)。

给出一段代码例子
import java.util.*;
public class Test {
public static void main(String[] args) throws Throwable {
//double[] balls = {6, 3, 3, 2, 2, 2, 2};
//double[] balls = {1, 2, 2, 7, 8};
double[] balls = {6, 3, 3, 2, 2, 2, 2, 1, 2, 2, 7, 8}; //测试用球
double full = 10; //盒子容量
double sum = 0; //所有球的总重量
for (double d : balls) {
sum += d;
}
int count = (int)(sum / full); //计算最少需要的盒子数
if (count < sum / full) count++;

double[] weight = new double[count]; //每个盒子已装球的总重量
List<List<Integer>> boxes = new ArrayList<List<Integer>>();//盒子保存的球的下标
for (int i=0; i<count; i++) {
boxes.add(new LinkedList<Integer>());
}

List<Integer> loaded = new LinkedList<Integer>(); //已装裁过的球的下标
List<List<Integer>> used = new ArrayList<List<Integer>>(); //尝试过的装载
for (int i=0; i<balls.length; i++) { //也就是记录按装载顺序装载过的球的下标
used.add(new LinkedList<Integer>());
}
int box = 0, index = 0;

while (loaded.size() < balls.length) { //装载的球少于所有的球则循环
if (box == count) { //如果尝试到最后一个盒子结束
if (loaded.size() == 0) { //如果回退到最开始一个球也没装载的状态,则无解
System.out.println("no result");
return;
}
for (int i=used.size()-1; i>=loaded.size(); i--) {
used.get(i).clear(); //回退到某个状态时,该状态以后的状态清空(也就是该状态以后的状态初始化)
}
index = loaded.remove(loaded.size() - 1); //回退,取出最后一次装载的球
for (box=count-1; box>=0; box--) {//还原上一次装载状态
if (boxes.get(box).contains(index)) { //找到最后一次装载的盒子
weight[box] -= balls[index]; //去掉最后一次转载的球,并还原状态
index = boxes.get(box).indexOf(index);
boxes.get(box).remove(index);
break;
}
}
}

for (int i=0; i<balls.length; i++) { //遍历所有的球,尝试把球装到盒子里
if (loaded.contains(i)) continue; //如果球被装载了,则换下个球
if (used.get(loaded.size()).contains(i)) continue; //如果球装载尝试过了,则换下一个球
if (full-weight[box] >= balls[i]) { //如果盒子还能装球
weight[box] += balls[i];
used.get(loaded.size()).add(i); //记录装载顺序的球的下标
loaded.add(i);
boxes.get(box).add(i);
}
}
box++; //下一个盒子
}

System.out.println("-------分配结果---------");
for (List<Integer> b : boxes) {
System.out.print("{ ");
for (int i : b) {
System.out.printf("%.2f ", balls[i]);
}
System.out.println("}");
}
}
}
  • 打赏
  • 举报
回复
额条件看错了,for循环盒子之前,再加一个逻辑,对盒子按重量由大到小排序就可以了,这样,第一个取出来的重量最重的盒子,放不进去就换下一个稍微轻一点的。
这样就能满足条件:
把每一个项放入能够容纳他而不至于溢出的最满的盒子。
  • 打赏
  • 举报
回复
好像思路可以解决,就是不知道是不是最佳的。

初始化2个数组,
A用来放盒子,B用来放球

for(int i=0;i<B.size(),i++)
{
球 q = B.get(i);
boolean f = false;//判断是否放入了球
for(int j=0;j<A.size(),j++)
{
盒子 h = A.get(j);
int x = 0;//盒子里球的总重量
for(int k=0;k<盒子h中球的数量,k++)
{
x += h.get第k个球.get球的重量
}
if(x+q.重量<盒子最大容量)
{
把第i个球q,放入第j个盒子里。
f = true;
break;//放下一个球。
}
}
if(!f)
{
//说明现在的盒子都已经满了。或者是第一次循环的时候A里没盒子
//这个时候new 一个盒子放入到数组A里,然后把球q放入这个新盒子里
盒子 h1 = new 盒子();
h1.add(q);
A.add(h1);
}
}

思路基本就这样了
桃园闲人 2012-04-26
  • 打赏
  • 举报
回复
有项目会用到这个?还是去专研数学吧,表示不会。
austinls 2012-04-26
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 的回复:]

好像思路可以解决,就是不知道是不是最佳的。

初始化2个数组,
A用来放盒子,B用来放球
Java code

for(int i=0;i<B.size(),i++)
{
球 q = B.get(i);
boolean f = false;//判断是否放入了球
for(int j=0;j<A.size(),j++)
{
盒子 h = A.get(j);
……
[/Quote]

问题在于这种思路不是最优解。
比如考虑6,3,3,2,2,2,2这7个球,如果从大到小扫描,那么按照题目给出的思路,会得出{6,3}{3,2,2,2}{2},需要三个盒子。
但是最优解是{6,2,2}{3,3,2,2},只用了两个盒子。

在考虑1,2,2,7,8这5个球。如果从小到大扫描,按照题目思路,会得出{1,8}{2,7}{2}
但是最优解是{1,2,7}{2,8}


CHZiroy 2012-04-26
  • 打赏
  • 举报
回复
问题表述得好怪
cseu 2012-04-26
  • 打赏
  • 举报
回复
可以使用BFD(best fit decreasing)算法,但不一定是最优解。
已使用的盒子按剩余容量升序排列,物品按重量降序排列
将物品放入第一个可以放入的盒子,如果没有,拿一个空的放入。
放入后,对已使用的盒子再排序。

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧