固定容量的箱子最少用幾個:從搬家打包到雲端 VM 調度,以及「先放大件」的智慧

搬家打包:每個紙箱限重 10 公斤,物品重量已知。最少用幾個箱子?所有人的直覺——「大件先放,小件塞縫」——正是本篇主角 FFD,而且這個直覺有嚴格的品質保證。

給定 \(n\) 個物品,大小 \(s_1,\dots,s_n \in (0, C]\),箱子容量 \(C\)。把物品分進最少數量的箱子,每箱總和 \(\leq C\)。NP-Hard:Partition 直接歸約過來(兩堆能否各裝一箱?),所以連判斷「2 個箱子夠不夠」都是 NP-Complete——這也順帶證明:除非 P=NP,近似比不可能好於 \(3/2\)

顯然的下界

\[OPT \;\geq\; \left\lceil \frac{\sum_i s_i}{C} \right\rceil\]

體積總量除以箱容量,無條件進位。評估任何解時先算它,常常立刻知道「已經最優」。

三個線上/離線演算法

Next Fit(NF):只看手上這箱

裝不下就封箱開新的,永不回頭。\(O(n)\)、串流可用。保證 \(NF \leq 2\cdot OPT\):任兩個相鄰箱子的總和 \(> C\)(否則不會開新箱),所以箱數 \(< 2\sum s_i / C \leq 2\,OPT\)

First Fit(FF):掃全部箱子

放進第一個裝得下的箱子。\(O(n\log n)\)(用平衡樹找箱)。保證 \(FF \leq \lceil 1.7\,OPT\rceil\)

First Fit Decreasing(FFD):先排序再 FF

物品由大到小排序後跑 FF。

\(FFD \leq \frac{11}{9}\,OPT + \frac{6}{9}\),且此界不可再改進。

直覺:大件是「地形」,先擺定;小件是「沙子」,往縫裡流。反過來先撒沙子,地形就擺不進去了——Example 2 的實測正是這個劇本。

手算範例

物品 \(\{4,8,1,4,2,1\}\)\(C=10\)、下界 \(\lceil 20/10\rceil = 2\)

演算法 過程 箱數
NF \([4] \to [8,1] \to [4,2,1]\) 3
FF \([4,1,4,1]\)\([8,2]\) 2
FFD 排序 \(8,4,4,2,1,1 \to [8,2]\)\([4,4,1,1]\) 2

(NF 的細節:\(4\) 進箱 1;\(8\) 裝不下封箱開箱 2;\(1\) 進箱 2;下一個 \(4\) 裝不下箱 2(剩 1),開箱 3……NF 的痛點是封掉的箱子有縫也回不去。)

FFD 也有極限

FFD 不是精確演算法。本篇程式 Example 3 的實例(總和 300、\(C=100\)、下界 3)FFD 用了 4 箱、精確解 3 箱:FFD 把 \(51+30\) 裝在一起浪費了 19 的縫,而最優解需要「刻意不貪」的組合(如 \(51+27+22\)\(50+50\)\(26+23+21+30\))。要精確就得回溯搜尋(程式附帶剪枝版:大件先放 + 相同剩餘空間的箱只試一個 + 箱數超過已知最佳就砍)。

完整 C++ 程式

包含:NF、FF、FFD(含每箱內容輸出)、下界計算、回溯精確解(對稱剪枝)、100 隨機實例統計。編譯:

g++ -std=c++17 -O2 -Wall -Wextra -o bin_packing bin_packing.cpp

執行結果與解讀

=== Example 1: NF vs FF vs FFD ===
items {4,8,1,4,2,1}, capacity 10, lower bound = 2
NextFit : 3 bins   [4] [8,1] [4,2,1]
FirstFit: 2 bins   [4,1,4,1] [8,2]
FFD     : 2 bins   [8,2] [4,4,1,1]

=== Example 3: FFD is not optimal either ===
items sum=300, capacity 100, lower bound = 3
FFD   bins = 4     [51,30] [50,50] [27,26,23,22] [21]
exact bins = 3 (nodes=26)

=== Example 4: FFD quality on random instances ===
100 random instances (12 items, size 10-70, cap 100):
FFD optimal 91/100 times, avg FFD=5.52 vs avg OPT=5.43
theory: FFD <= (11/9) OPT + 6/9, in practice usually spot-on

Example 4 的統計把 FFD 的實務地位講完了:100 個隨機實例 91 次直接最優,平均只差 0.09 箱。\(11/9\approx 1.22\) 的理論界是「保險」,日常表現接近完美——這就是它成為雲端調度器預設演算法的原因。Example 3 則提醒你剩下的 9%長什麼樣。

實務應用

  • 雲端 VM 調度:把工作負載(CPU/RAM 需求)塞進最少的實體機——Kubernetes 調度器的 bin-packing 策略、AWS 的容量規劃。裝越密,電費省越多。

  • 物流裝櫃:貨櫃、卡車、棧板的裝載規劃(實務是二維/三維變形)。

  • 廣告排期:時段長度固定,廣告長短不一,最少用幾個時段播完。

  • 記憶體配置:allocator 的 slab/區塊管理本質是線上裝箱。

  • 裁切問題:鋼材、布料、玻璃的下料切割(cutting stock,同構問題)。

練習題

  1. 手算 \(\{7,5,6,4,2,3,7,2\}\)\(C=12\):下界、NF、FF、FFD 各多少?

  2. 實作 Best Fit(放進「剩餘空間最小且裝得下」的箱)與 Worst Fit,加入 Example 4 的統計擂台。

  3. 構造一個 FF 比 FFD 差至少 2 箱的實例。

  4. 線上情境(物品逐一到達、不可重排)NF 與 FF 都合法,FFD 不行——為什麼?線上演算法的競爭比下界是多少(查 1.54…)?

  5. 挑戰:把精確解加上「下界剪枝」(剩餘體積除以容量進位 + 已用箱數 \(\geq\) 已知最佳即砍),實測能解到幾件物品。

小結

演算法 時間 保證 特性
Next Fit \(O(n)\) \(\leq 2\,OPT\) 串流、只留一箱開著
First Fit \(O(n\log n)\) \(\leq 1.7\,OPT\) 線上可用
FFD \(O(n\log n)\) \(\leq \frac{11}{9}OPT+\frac69\) 離線之王,先排序
回溯精確 指數 精確 \(n\lesssim 25\)

延伸閱讀:Johnson (1973) 博士論文(FF/FFD 首次分析);Dósa (2007) FFD 緊界;Coffman, Garey & Johnson 的裝箱綜述;Karmarkar–Karp (1982) 漸近 PTAS。