二維矩形裝箱演算法之二叉樹
我們要解決兩個問題:
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幾乎總是最好的選擇。意思是,結果大致是正方形的(不是長而細的),並且有最少的空白。