二维矩形装箱算法之二叉树

我们要解决两个问题:

1.如何将所有二维矩形块放入一个矩形框内。

2.在满足问题1的情况下,矩形框的最小宽度和高度是多少。

期望的效果图:

 

下面我们就来解决以上问题。

1. 把矩形块放在固定大小的框内

假设有一个固定大小的矩形框,比如1024x768,我们怎么把矩形块装在里面?答案:使用二叉树

首先在左上角放置第一个(最大的)块,然后将该矩形框剩余的空白区域分割成两个较小的矩形


以二叉树的形式递归地进行处理,最后得到一个填充的图像


程序实现非常简单。假设输入矩形已经按从大到小排序。

Packer = function(w, h) {
  this.root = { x: 0, y: 0, w: w, h: h };
};

Packer.prototype = {

  fit: function(blocks) {
    var n, node, block;
    for (n = 0; n < blocks.length; n++) {
      block = blocks[n];
      if (node = this.findNode(this.root, block.w, block.h))
        block.fit = this.splitNode(node, block.w, block.h);
    }
  },

  findNode: function(root, w, h) {
    if (root.used)
      return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    else if ((w <= root.w) && (h <= root.h))
      return root;
    else
      return null;
  },

  splitNode: function(node, w, h) {
    node.used = true;
    node.down  = { x: node.x,     y: node.y + h, w: node.w,     h: node.h - h };
    node.right = { x: node.x + w, y: node.y,     w: node.w - w, h: h          };
    return node;
  }

}
2. 选择最小宽度和高度

我们现在可以使用一棵二叉树来将矩形块放入一个固定大小的矩形。但是我们应该选择多大的尺寸来确保所有的矩形块都能以最优的方式放置

我考虑了很多启发式方法。其中一个例子是取平均宽度和平均高度,然后分别乘以sqrt(n),以生成一个正方形。n是矩形块的数量。因为使用了平均值,生成矩形块矩形框可能太大也可能太小。

那么有没有别的方法呢?下面来看我们提出的方法。

3. 将矩形块填充进一个不断增长的矩形框

我们不是试图猜测矩形框的最优宽度和高度,我们可以先建立一个小的目标:能容下第一个矩形块。然后在没有足够的空间来容纳下一个块的时候,扩充矩形框。

先来看下没有足够的空间来容纳下一矩形块的情况:


这时我们有两种选择,我们可以让矩形框向下生长,或者向右生长


这可以通过在原始程序中添加几行代码来实现:

fit: function(blocks) {
      var n, node, block;
+     this.root = { x: 0, y: 0, w: blocks[0].w, h: blocks[0].h };
      for (n = 0; n < blocks.length; n++) {
        block = blocks[n];
        if (node = this.findNode(this.root, block.w, block.h))
          block.fit = this.splitNode(node, block.w, block.h);
+       else
+         block.fit = this.growNode(block.w, block.h);
      }
    },

  • 确保根节点被初始化为与第一个矩形块相同的大小
  • 每当findNode返回null时,调用一个新的方法grownode扩充矩形框

实际上,实现grownode方法时需要一些条件判断:

growNode: function(w, h) {

1    var canGrowDown  = (w <= this.root.w);
1    var canGrowRight = (h <= this.root.h);

2    var shouldGrowRight = canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width
2    var shouldGrowDown  = canGrowDown  && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down  when width  is much greater than height

     if (shouldGrowRight)
       return this.growRight(w, h);
     else if (shouldGrowDown)
       return this.growDown(w, h);
     else if (canGrowRight)
      return this.growRight(w, h);
     else if (canGrowDown)
       return this.growDown(w, h);
     else
       return null; // need to ensure sensible root starting size to avoid this happening
   },

几点注意事项:

  • 如果矩形块比框宽,我们就不能支持矩形框向下生长
  • 如果矩形块比框高,我们就不能支持矩形框向右生长

这对算法有相当大的影响。如果一个块比矩形框的宽和高都大,那么我们就不能生长了!解决方案是确保我们的块首先被排序,这样所有后续的块都至少有一个边缘比矩形框小。

 这并不是说我们不能支持这个,但会添加额外的复杂性。

  • 如果框的高度大于它的宽度再加上块的宽度,那么为了保持近似方形,框向右生长
  • 如果框的宽度大于它的高度再加上块的高度,那么为了保持近似方形,框向下生长

这就阻止了我们不断地向右生长并创建一个水平的长条。这也阻止了我们不断的向下生长并创造一个狭窄的垂直地带。它的结果近似于正方形。

4.对矩形块排序

块被放入框的顺序对结果有很大的影响。让我们先看看专家们怎么说:

    理论和实证结果表明,‘first fit decreasing’是最好的启发式方法。按照从大到小的顺序排列对象,这样最大的对象第一放入,最小的最后一个放。将每个对象依次地插入到第一个有空间可以容纳它的箱子里。

这里的大小是什么意思?宽度?高度?面积吗?

通过从各种排序算法中进行选择

宽度
高度
面积
maxside——(宽度,高度)
随机-随机化顺序

 每一种主要排序也有二级(有时是第三级)排序标准,以避免在其他情况下是相等的。

结果表明,maxside几乎总是最好的选择。意思是,结果大致是正方形的(不是长而细的),并且有最少的空白。


demo的演示地址:https://codeincomplete.com/posts/bin-packing/demo/

  • 16
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值