☰
二维矩形排样算法解析:从组合优化到工程落地
2026/10/15 15:58:31 网站建设 项目流程

简介:二维矩形排样是典型的组合优化问题,核心目标是在固定宽度的板材上,以不重叠的方式放置矩形零件,最大化材料利用率。它统一了钣金下料、家具排版、PCB拼板、玻璃切割等众多工业场景的数学模型,其难点并非几何碰撞检测,而是随零件数量急剧膨胀的组合搜索空间。围绕该问题,业界形成了精确算法、启发式、元启发式以及学习型方法等不同技术路线。实际工程中,最低水平线算法以简单高效的骨架成为复现论文结果的理想基线,而排序策略、旋转开关、离散化粒度等参数显著影响最终利用率。理解约束假设、评价口径与数据集的隐性差异,是评估算法能否落地生产线的关键。本文从排样问题模型出发,梳理算法流派,并给出可复现的代码骨架与避坑指南,帮助工程师快速构建自己的排样原型系统。

1. 二维矩形排样相关论文打包:这叠资料真正值钱的是算法脉络,不是 PDF 数量

接过一份“二维矩形排样相关论文打包”时,多数人第一反应是收藏、归档、留着以后看。但我在排样项目里折腾过几轮之后,观点完全不同:二维矩形排样不是个几何题,而是个组合优化题,它把钣金下料、家具排版、PCB 拼板、玻璃切割这些听起来不相关的场景,统一成同一个数学模型——固定宽度的板材,放下一组互不重叠的矩形零件,让利用率最高。论文打包这件事,真正值钱的不在于你存了多少篇 PDF,而在于用一下午把“问题模型、算法流派、评价口径”三件事理清。立住这个坐标系之后,再读任何一篇新论文,你都能在五分钟内判断它属于哪条线、解决哪个子问题、值不值得追。适合谁?刚入行的工程师需要一条低成本入门路径,技术选型的人需要判断哪种算法能落进自己的产线,这两类人读这份打包,收益最大。

2. 先立住问题模型:矩形排样在优化什么,论文里反复出现的三类约束

2.1 正式定义与三种常见变体

先给一个能拿来当坐标系原点的定义:有一组矩形零件,每个零件宽 w_i、高 h_i 已知;有一块板材,宽度 W 固定,高度不限;要求所有零件正交放置(边与板材平行),彼此不重叠,不超出板材边界,目标通常是让最终占用高度最小,也就是让板材利用率最高。

这个定义看着简单,真正写代码时马上会遇到三种变体,而论文里几乎每一篇都会默认其中某一种。

第一种是装箱型(bin packing):不是一块无限高板,而是多块固定尺寸的板,目标是用最少块数装下所有矩形。第二种是下料型(cutting stock):零件数量多但种类少,同一尺寸可能重复几百次,目标是原料损耗最小。第三种是可旋转型:零件允许旋转 90° 后再放置,几何搜索空间直接翻倍,但很多论文默认允许,因为现实里的矩形零件大多没有纹理方向。你拿到的论文打包里,往深看会发现不同论文在“允许旋转”这件事上打架——有的明确标注旋转自由,有的只字不提却偷偷用了旋转,这类细节正是复现翻车的高发区。

还有一个容易被跳过但工程上要命的参数:刀缝间隙。实际激光切割或锯切,刀具路径有宽度,两块零件之间必须留缝,否则切完尺寸不对。排样论文里基本都假设零件紧密贴合,间距为零。落产线时你得在矩形尺寸外扩半个刀缝再做排样,这一步做不做,利用率差异能到几个百分点。所以读论文先别看算法,先看约束假设,这是我一直以来的习惯。

2.2 为什么难点不在几何而在组合爆炸

很多第一次接触排样的人,天然以为这题难在“怎么检测两个矩形是否重叠”。但做过一次就知道,几何碰撞检测在正交矩形场景下简单到不值一提:比较 x 区间和 y 区间是否有交集即可,O(1) 就判完。真正让这个领域变成 NP-hard 的,是组合爆炸。

一维装箱已经是 NP-hard,二维矩形排样只会更复杂。n 个矩形放进同一块板,粗略看放置顺序有 n! 种排列,每种排列里每个矩形还要决定是否旋转,那是额外的 2^n 分支。n=30 时,穷举所有排列不现实;n=100 以上,搜索空间大到任何精确方法都无能为力——这就是精确算法止步于小规模、启发式和元启发式成为工程主力的根本原因。

按规模选算法是读论文时最实用的分诊逻辑:n 小于 30 到 50,分支定界类精确算法能给出最优解或高质量近优解,适合做问题上界参考;n 在 50 到 200,遗传算法、模拟退火这类元启发式是主流;n 再大,或者要求秒级出方案的在线场景,就得回到简单启发式加局部搜索。论文打包里如果某一篇上来就宣称自己在大规模实例上性能卓越,先看它实验选用的 n 是多少,这是拆穿灌水论文的第一招。

2.3 三个高影响约束:一刀切、旋转、板材宽度

约束是排样论文里最容易被“觉得不重要”的部分,但恰恰是决定算法能不能落地的分水岭。我通常拿到一篇论文先翻 constraints 段落,重点核对三件事。

一刀切约束(guillotine cut)意味着所有切割线都必须从板材一侧切到另一侧,切完一块再把剩余部分分开切。玻璃切割、多层PCB板、部分家具板材切割,产线上就是这种切割方式,因为一刀切简单可靠、切割成本低。自由切割(non-guillotine)允许在板材中间任意位置开始切,排样利用率理论上更好,但产线设备不一定支持。论文里大量算法默认自由切割,这跟许多真实产线需求直接冲突。你复现一个自由切割算法拿到 95% 利用率,代到只能一刀切的设备上,可能只剩 88%,方案直接被否。

旋转约束前面提过,这里再补一个容易误判的点:允许旋转 90° 并不总是提升利用率。对于长条形的零件,旋转后的长边方向变了一个维度,某些排序策略反而会把排样格局搞乱,利用率下降的情况并不罕见。板材宽度也是暗坑——有的论文把 W 当成变量参与优化,有的固定 W 只优化高度,这两者的算法路径完全不同。读论文时把这三个约束记到每篇的卡片上,比背十个算法名词有用得多。

3. 按算法主线拆论文包:精确算法、启发式、元启发式与学习型方法

3.1 精确算法:分支定界与下界技术,小规模实例的标尺

论文包里最早的一批经典文献,多数是精确算法路线。分支定界(branch and bound)的基本逻辑很直白:把放置过程看成逐层决策树,每放一个矩形就产生新分支,同时用上界(当前最好解的占用高度)剪掉不可能优于上界的分支。但矩形排样的分支树太庞大,光靠剪枝不够,所以论文里真正值钱的部分是“下界技术”——退化的线性规划松弛、一维装箱下界、基于总面积的下界,这些下界用来评估当前分支还有没有希望。

精确算法适合的规模很小,n 超过 30 就非常吃力。那为什么还要读这条线?两个作用:第一,小规模实例上精确解可以作为其他算法的对照标尺,衡量遗传算法到底离最优解差多远;第二,下界技术是所有元启发式论文里“gap”指标的基础,你连下界都算不清,就不知道那些提升百分比是相对什么算出来的。

复现精确算法时我一般会建议别自己从零写分支定界,直接用现成的混合整数规划建模工具做一次松驰求解,能拿到小规模的可行解和上下界,比手写分支树效率高得多。论文里那些精妙的约束松弛,主要是为了摆脱通用求解器的黑匣子,换取在大规模上的可扩展性——这个trade-off,等你真正需要跑几千个零件时才会体会到。

3.2 启发式与元启发式:从 BL 到遗传算法的工程主力

工程里真正天天跑的,其实是这一条线。最早的启发式叫 Bottom-Left(BL)算法:按某个顺序逐一放置矩形,每个矩形尽量往低处放、往左靠,最终得到紧凑格局。BL 的问题很明显——先放的矩形会把后放的矩形卡死,优质位置被提前堵住。

后来改进成 Bottom-Left-Fill(BLF)/最低水平线系列:不再只看当前落点,而是在整个已排轮廓上找最低的、且能塞下当前矩形的空位。二维排样的核心难点瞬间被降维成“在一组水平线段里找可行空位”,几何问题转化成了区间问题,程序好写、速度快,于是它成了论文里出现频率最高的 baseline。再到后来的分层排样(shelf)、把板材分成若干层每层放一批矩形的做法,在“一刀切”约束下反而有天然优势,是玻璃切割场景的主力。

元启发式则在启发式之上加了一层搜索框架。遗传算法在这一领域最常见的编码方式是“序列编码”:一条染色体就是一个矩形放置顺序,适应度就是按这个顺序跑 BLF 后得到的利用率。交叉、变异操作交换序列片段,反复迭代。模拟退火类似,靠温度曲线控制接受差解的几率,跳出局部最优。禁忌搜索则用禁忌表记录最近动过的矩形,避免走回头路。这条线的论文套路高度一致:改进点集中在“初始解怎么生成”“邻域怎么定义”“评价函数要不要加速”,你读懂三篇,后面几十篇都能快速归类。

需要特别提醒的一点:元启发式的论文结果基本都强依赖参数。种群大小、交叉率、变异率、温度衰减系数、禁忌表长度,不同实例上最优参数差别明显。论文里给的那组参数,换个数据集大概率不是最优的。所以复现这类论文时,别把别人的参数表当圣旨,要把它当起点。

3.3 学习型方法:在线决策与打分模型,热度高但落地要谨慎

近几年的论文包里,强化学习和图神经网络的内容明显变多。这条线的核心思路是:把排样过程建模成序列决策——每一步选择下一个放哪个矩形、放在哪个位置,用策略网络给每个候选动作打分。训练时用带奖励的仿真环境迭代,奖励可能是“最终利用率”或“当前空位被填充的紧凑度”。

这类论文的优势是在线推理速度快,模型训练好后,单个实例求解在毫秒到秒级,比遗传算法动辄几十秒的迭代快一个量级。适合的场景是零件持续到达、需要快速给出排样方案的动态下料。但落地时要踩的坑也明显:训练数据和真实数据分布不一致时,模型会漂移;大多数学习方法针对自由切割场景,很少直接考虑一刀切;而且结果稳定性不足,同一个模型在同分布数据上跑,方差可能比传统元启发式还大。

我的建议很具体:学习型方法值得跟踪,但短期内别把它当产线主力。把它放在“在线预排样+人工兜底”的位置,或者作为混合方案里生成初始解的模块,比全量替换靠谱得多。读这类论文时重点看两处:训练分布是什么、和线上数据分布差多少;评分函数用的是启发式规则还是纯网络输出。这两处想清楚,论文是真能用还是实验室自嗨,判断会快很多。

4. 从论文到可复现基线:最低水平线算法的代码骨架与三个关键参数

4.1 用十几行实现一个能跑的最低水平线排样器

读一百篇排样论文,不如先跑通一个能出结果的基线。最低水平线(Lowest Horizontal Line)算法是公认的最省力起点:把板材宽度离散成一列列的单位格,维护每列当前的最高占用高度;放置矩形时,从左到右扫所有可能的位置,计算每个位置放下去后矩形底部的高度(取该区间所有列高度的最大值),选所有位置里底部最低的放。代码核心就是一个函数。

from dataclasses import dataclass from typing import List, Optional @dataclass class Rect: w: int # 宽度,按离散化后的单位格计 h: int area: int = 0 # 面积,用于排序 def __post_init__(self): self.area = self.w * self.h class LowestLinePacker: def __init__(self, cols: int): # cols:板材宽度被离散成多少个单位格 self.cols = cols self.heights = [0] * cols # 每列高度 self.placed = [] # 已放置的矩形 ((x, y, w, h)) self.max_height = 0 # 当前占用最高点 def place(self, w: int, h: int) -> bool: # 矩形宽度不能超过板材总宽 if w > self.cols: return False best_x = -1 best_y = 10 ** 9 # 扫描所有可能的起始列位置 for x in range(self.cols - w + 1): # 该区间内的最高列,就是矩形放下去的底部高度 y = max(self.heights[x:x + w]) if y < best_y or (y == best_y and x < best_x): best_y = y best_x = x if best_x < 0: return False # 把区间内所有列的高度更新为 best_y + h for j in range(best_x, best_x + w): self.heights[j] = best_y + h self.placed.append((best_x, best_y, w, h)) self.max_height = max(self.max_height, best_y + h) return True def pack(self, rects: List[Rect], allow_rotate: bool = True) -> float: # 按面积降序排序,这是经典且稳定的策略 seq = sorted(rects, key=lambda r: (r.area, r.w), reverse=True) for r in seq: rw, rh = r.w, r.h if not self.place(rw, rh) and allow_rotate: # 放不下就尝试旋转 90 度 self.place(rh, rw) return self.max_height

逻辑说明:heights数组把连续板材变成了离散列,放置一个矩形等价于在一段连续列上做“堆高”操作。place函数里最核心的一行是y = max(self.heights[x:x + w]),它把“矩形底部能落到多低”这个问题,化简成“区间内最高的一列决定底线”。这个思路本质上就是论文里讲的 lowest-line 策略,只不过用离散数组实现,代码量比维护天空线(skyline)数据结构少一大截。

复杂度得说清楚:place的扫描代价是 O(cols * w),放置 n 个矩形总代价 O(n * cols * w),在 cols 小于 1000、n 小于 200 时完全够用。如果你处理的零件尺寸跨度特别大,比如有的宽 500 格、有的宽 10 格,这个朴素实现的性能会明显下降,但作为基线验证和论文复现已经足够。真要追求性能,可以把max换成线段树或稀疏表,把区间查询降到 O(log cols),这一步是后续优化方向,不是现在的重点。

4.2 排序策略、旋转开关与离散化粒度:三个参数怎么定

同样是最低水平线算法,三个参数不同,利用率差距可以超过 5 个百分点。我做了几轮对比实验后的经验是:排序策略的影响最大,其次是旋转开关,离散化粒度影响最小。

参数常见选项经验判断
排序策略面积降序 / 宽度降序 / 高度降序 / 周长降序面积降序最稳,但对“宽而扁”的矩形效果差;宽度降序在零件宽度差距大时效果好;周长降序在混合场景里有惊喜
允许旋转 90°开 / 关一般强烈建议开;但长条形零件多时,开了可能让格局乱,两种都跑一下取最优
离散化粒度1 格 = 1mm / 0.5mm / 0.1mm粒度越细结果越精确,但扫描列数变大、耗时上升;0.5mm 粒度通常够用

排序策略为什么要单独讲?因为最低水平线的结果强烈依赖放置序列,这就像往盒子里装东西,先放大件还是先放小件,最终紧凑度完全不同。我见过最常翻车的配置是:默认按输入顺序排,结果先放了一堆小矩形,大矩形最后竖着卡在中间,利用率惨不忍睹。面积降序的价值在于大件先定格局,小件填缝,这是排样领域最朴素的智慧。

旋转开关别只听别人经验。我的习惯是写成可选参数,同一批数据开和关各跑一遍,取利用率高的结果。原因不复杂——旋转改变了矩形的宽高比,而最低水平线对宽高比敏感,同一个数据集在不同开关下结果可能差 3 个百分点。与其争论,不如跑两次,成本很低。

离散化粒度的选择要跟坐标精度联动。如果你的零件尺寸是浮点数,比如 37.5mm,就得先乘一个缩放因子把浮点转成整数格,再传入 packer。缩放因子越大越精确,但 cols 越大、扫描越慢。我一般先按 0.5mm 粒度跑,如果所有结果裕量充足、不需要进一步精细对比,就不再加大粒度了。

4.3 给基线加一层局部搜索,逼近论文里的“高级算法”

最低水平线只是一个单一解,遗传算法能胜出的原因在于它搜索了多个序列。但你不需要一上来就写完整遗传算法,有个性价比极高的中间步骤:迭代局部搜索(Iterated Local Search)。核心思路是:先用最低水平线生成一个序列和排样结果,然后不断微调序列——交换两个矩形的位置,重新跑一遍最低水平线,如果利用率提升了就接受这个新序列,否则回退。跑上几百轮,效果往往能从 90% 出头涨到 93% 上下。

import random def local_search(rects: List[Rect], cols: int, iterations: int = 500): best_seq = sorted(rects, key=lambda r: (r.area, r.w), reverse=True) best_height = LowestLinePacker(cols).pack(best_seq, allow_rotate=True) for i in range(iterations): # 复制当前最优序列并交换两个矩形位置 new_seq = best_seq.copy() a, b = random.sample(range(len(new_seq)), 2) new_seq[a], new_seq[b] = new_seq[b], new_seq[a] height = LowestLinePacker(cols).pack(new_seq, allow_rotate=True) if height < best_height: # 占用高度更低 = 利用率更高 best_height = height best_seq = new_seq return best_seq, best_height

参数说明:iterations是迭代轮数,500 轮在 n=100 时只需要几秒;best_seq初始用面积降序,之后的搜索都从这个解出发,保证最终结果不会差于单纯面积降序。交换操作取两个随机位置,这是最简单也最不引入偏差的邻域动作。实际跑的时候你会发现一个规律:前 100 轮利用率涨得很快,后 400 轮提升趋缓。这是局部搜索的正常现象,不必追求更多迭代,后续要再进一步,该上的是“交换+移动”的混合邻域,或者把单次尝试改成跑多个不同初始序列再合并结果。

这一段跑通之后,你就拥有了一个能复现、能改参、能加装各种搜索策略的排样实验台。后面读论文里的高级算法,本质上都是在往这个框架里追加模块——有人改初始解生成,有人改邻域结构,有人改接受准则,骨架没变。

5. 复现论文结果避坑指南:基准、口径与终止条件是最大的信息差

5.1 数据集的隐性差异:同名实例,不同结果

现象:同一个算法,论文里报告利用率 94.5%,你复现出来只有 91.2%。排除了代码 bug,你把论文里引用的公开基准实例找出来重新核对,发现实例文件里矩形的宽高跟你手头版本对不上。

原因:矩形排样领域的公开基准实例经过多轮转手,不同版本的实例文件在三个细节上经常出现差异——坐标单位(有的用整数像素,有的归一化到 0 到 1);是否包含外边框留白;矩形的宽高是否已经按 90° 旋转预处理过。少数版本连“板材宽度”定义都不同,比如把最大矩形宽度当板材宽度。

解决:复现前先统一数据表示。我一般会写一个转换脚本,把不同来源的实例全部转成内部统一格式:板材宽度、矩形列表(宽高浮点)、是否允许旋转标志位。转换之后跑一遍最低水平线基线,把基线利用率跟论文里报告的基线做对比——如果论文你引用的基线是 90%,你复现也是 90%,说明数据口径对齐了,再去追高级算法的差距才有意义。这一步省不得,省了就是拿自己的时间验证别人的笔误。

5.2 评价指标口径不一致时的五个核对点

现象:两篇论文都声称“比 BLF 提升 5%”,一篇的基准是随机排列起步的 BLF,另一篇的基准是面积降序起步的 BLF,两者起点不同,5% 的含义天差地别。你把 A 的方法和 B 的方法放在同一批数据上直接比结论,结果完全对不上。

原因:论文里“提升百分比”的分母不一致。有的用随机序列作 baseline,有的用面积降序作 baseline,有的用理论下界作分母,有的用板材总面积作分母。分母一换,数字水涨船高。

解决:制定一个固定的评价协议,每次对比按照三点来收口。第一,统一用“利用率”做主指标,利用率 = 矩形总面积 /(板材宽度 × 最终占用高度),这个口径在大多数论文里通用。第二,固定 baseline:你自己的所有对比实验,永远以“面积降序 + 最低水平线 + 允许旋转”的配置为基准,对外报告时明确写出这个配置,别人才能复现你的对比。第三,如果有人家的论文只报了“相对提升”没报绝对利用率,先按论文给的实例和约束自己跑出绝对利用率,再算相对值——不要直接用论文里数字做加减。

5.3 随机性与终止条件的常见误用

现象:复现某遗传算法,连续跑三次,三次结果差 2 个百分点;论文里只给了一张单次运行的最优图,你照着调参数,怎么都调不出那个数字。

原因:元启发式是随机算法,初始种群随机生成、交叉变异带随机性,偶数次运行结果有方差是常态。更隐蔽的是终止条件:论文写“运行 60 秒”或“迭代 500 代”,但没写这 60 秒是否包含数据加载和初始解生成,没写 500 代是每代跑完整个种群还是抽样——你拿自己的实现去对齐这个时间,永远对不上。

解决:给实验固定三件套。第一,固定随机种子,至少跑 10 次取均值±标准差,而不是单次最优;报告结果时写“均值 ± 标准差”,单次最优值只做参考。第二,统一终止条件:用“最长时间上限”时从计时器清零开始算,包括所有预处理;用“迭代代数”时以完整评估一整个种群为标准。第三,对比时记录“达到目标 gap 所需的时间”,而不是只记最终时间点——这能过滤掉双方实现在速度常数上的差异,留下算法本身的信息效率差异。这三件套立起来,你的复现结果才具备跟论文对话的资格。

6. 把论文读成系统:从打包资料到一套能自评的排样原型

论文打包里最后能沉淀下来的,不应该是一堆待读列表,而是一套属于你自己的验证流程。我现在的习惯是:任何新拿到手的排样论文,先不读算法细节,先做三件事——把它的约束假设记录下来,标注到实例上;用最低水平线基线跑一遍这些实例,拿到自己的基准利用率;再看论文报告的结果相对这个基准提升了多少。整个过程半天内能完成,但这套流程能过滤掉一半以上的“论文灌水”。

为什么是这个顺序?因为读论文最危险的读法是直奔实验结果,看到 95% 就觉得方法好。但 95% 相对什么来的?有的论文相对随机序列提升 20%,听起来吓人,换成面积降序基线可能只提升 2%。你有了自己的基线数据之后,论文里那些数字立刻就有了参照系:提升 10 个点以上的论文很可能是真突破,值得精读;提升 1 到 2 个点的,大概率只是在已有框架上加了个漂亮的旋转角,价值有限。

我再向后说一步:当你用这套流程跑了二十篇论文,会逐渐发现一个工程真相。很多 SOTA 方法在论文的宽松约束下确实领先,但一碰到真实产线——一刀切、允许旋转次数的限制、砂轮间隙、批量切换成本——领先优势就开始收窄。某开发者当初接一个板材切割项目,先花两周复现了一篇强化学习算法,排样效果和预期差不多,但产线只支持一刀切,而且每种零件必须成组切割,结果 RL 方案在满约束下连简单的分层排样都打不过。后来把分层排样和遗传算法组合,做了一版不那么“性感”的方案,上线后利用率反而高两个点。那次之后他养成一个习惯:论文的“先进”先放一边,约束匹配永远排在算法先进前面。

所以我的最终建议也很朴素:这份论文打包,你先把它读薄——把每篇论文归到“精确/启发式/元启发式/学习型”的一条主线上,记下约束假设和评价口径;再把它读厚——选三篇与你的场景约束最匹配的,用最低水平线做基线去复现,复现到你自己的数据能解释论文的数据为止。这套从打包到原型的路径反复走几轮,你对二维矩形排样这个方向的理解会超过大多数只看综述的同行。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询