简介:面向二维矩形排样、背包问题与下料问题研究,资料汇集了30个文件,内容以14篇caj与6篇pdf格式的学术论文为主,覆盖基于遗传算法、蚁群算法、填充式启发式、动态规划等多种求解策略的矩形件优化排样方法,另有2个txt说明文件、1个xml元数据、1个exe下料软件、1个mdb数据库及相关dll、url、jpg、wav等辅助资源,整体约37.65MB。论文既有基础概念与关键算法梳理,也有改进填充算法、动态填空启发式等前沿思路,可帮助读者系统理解二维矩形排样问题建模与求解流程。压缩包还附带玻璃板材下料软件及使用说明,便于直接验证算法效果或进行二次开发,适合机械制造、包装印刷、数学建模等领域的学生与工程技术人员参考学习。该资源已有762人学习下载,实用价值较高。
1. 二维矩形排样:为什么值得专门打包一批论文来读
二维矩形排样问题,简单说就是给定一批宽度高度各不相同的矩形件,在固定宽度的板材上寻找一种排列方式,让材料利用率最高、废料最少。这个问题的工程价值远超理论趣味:钣金下料、玻璃切割、家具板材开料、甚至 PCB 布局,本质都是矩形排样。很多从业者第一反应是“这不就是个拼图游戏吗”,真正动手优化后才发现,成品率每提升 2~3 个百分点,对一家中型加工厂来说可能就是每年几十万的成本差异,这也是相关研究能持续产出大量高质量论文的原因。
如果你正在做毕业论文选题、团队技术预研或者打算优化现有下料系统,把二维矩形排样相关论文系统性地梳理一遍是性价比最高的起步方式。论文打包不只是把 PDF 下载下来放进文件夹,而是按问题变体、算法谱系、评测基准和工程约束四个维度建立索引,在这个基础上复现几篇关键方法的代码,才能从“看了很多”变成“真能用”。这几年热门的启发式算法、深度学习辅助初始解、超启发式策略,核心源头都在这些被反复引用的经典论文里。
本篇文章我会完全站在一线工程师视角,从检索策略讲到代码复现,再讲到排样问题特有的边界条件与踩坑记录。目录上没有给源码包,也没有给你一个既有的压缩文件,但读完并按步骤操作,你就能自己整理出一套包括论文分类表、评测工具和三个基础算法实现的本地资源库。
2. 怎样打包质量高的排样论文:索引设计与检索策略
2.1 先建立问题变体分类:否则论文读了也白读
矩形排样相关论文数量庞大,但真正值得精读的不过三五十篇。问题是,初学者很容易把所有排样论文混为一谈,把带旋转约束和不带旋转约束的方法直接对比,把允许一刀切切割的算法用在不允许一刀切的场景里,最后复现时对不上号。所以第一步不是下 PDF,而是先建一个分类框架,把已知的变体固定下来。
最常见的分类维度包括:
- 板材数量:单板排样还是多板排样,是否允许板材之间拼接余料
- 旋转约束:矩形件是否允许 90 度旋转,还是固定方向
- 切割方式:一刀切(guillotine)与非一刀切,后者允许 T 形或更复杂的切割路径
- 件数规模:几十件的小规模还是几千件的大规模,直接影响算法选型
- 约束条件:是否有优先级、是否允许嵌套排样、是否有缺陷区域需要避开
我通常会在文献管理软件里建一个表格,包含标题、年份、发表渠道、问题变体标签、算法类别、数据集和复现成本七个字段。每读一篇新论文,先填表,再决定是否进入精读队列。这个习惯价值极大,因为三个月后你找“允许旋转的启发式排样”相关论文时,不用重新翻几十篇 PDF,直接过滤表格就能定位到三五篇候选。
2.2 检索关键词组合:从通用术语到精确变体
很多人直接用“rectangular packing”去检索,结果确实能搜到大量论文,但噪音也大。更有效的做法是分三组关键词做组合检索。第一组是问题领域术语——stock cutting、cutting and packing、two-dimensional packing、rectangular layout;第二组是算法术语——heuristic、genetic algorithm、simulated annealing、exact algorithm、GRASP、beam search;第三组是约束术语——guillotine、free orientation、fixed orientation、non-guillotine、dual feasible functions。
以某学术搜索引擎为例,我会并行跑三次检索式。第一个检索式锁定综述文献,关键词组合用 “two-dimensional cutting and packing” AND “survey” AND “classification” ,目的是拿到王志强那套分类体系的全貌。第二个检索式锁定近五年的启发式算法进展,用 “rectangular packing” AND “heuristic” AND (“skyline” OR “bottom-left” OR “maximal rectangle”) 。第三个检索式锁定理论研究进展,用 “rectangular packing” AND “dual feasible functions” OR “exact algorithm” 。
三个检索式的目标各不相同,综述用来建体系,启发式用来落地工程,理论研究用来理解边界。做完这三轮检索后,你手里的候选论文通常能收敛到 30 到 50 篇。这时候不要急着全部下载,先去读引用量最高的两篇综述文章的参考文献列表,和检索结果做并集——很多早期经典论文不会出现在近五年的检索结果里,但它们才是各类算法的源头。
2.3 论文去重与优先级排序:哪些必须精读,哪些扫一眼就行
按照我的经验,二维矩形排样的论文大致可以分成四个优先级。优先级最高的是奠基性研究,比如最早提出底层左靠(bottom-left)启发式的那一批,以及第一个系统讨论一刀切约束的文献,这类论文通常 5 到 8 篇。优先级第二的是影响力极大的实证对比研究,比如使用同一组公开基准测试多个算法并给出统一结论的论文,这类论文能给算法选型提供可靠依据。优先级第三的是近三年内提出的新方法,不管效果如何,至少要了解当前学术界在朝哪个方向走,尤其是深度学习和强化学习方法在排样问题上的应用。优先级最低的是只做了小幅改进、数据集没有公开、复现成本很高的论文,这类看摘要和结论就行。
论文打包的物理形态我也建议统一:每一篇论文重命名为“年份_作者_问题变体_算法类别.pdf”的格式,比如“2021_Liu_guillotine_heuristic.pdf”,这样在命令行里做文件筛选时效率极高。不要用原下载文件名,那些名字大部分是乱码或学术平台的编号,三个月后你根本不知道哪个文件是哪个。
3. 从论文到可运行代码:复现一个经典启发式排样算法
3.1 为什么先从启发式入手:跳过复杂的元启发式
二维矩形排样论文里很大一部分在讨论遗传算法、模拟退火、粒子群等元启发式方法。这些方法确实能取得较好的排样效果,但复现成本高、参数敏感、针对一个数据集调好的参数换一组数据立刻失效。我建议的路径是先把确定性启发式或简单随机化启发式复现到可以运行的程度,比如经典的底部左靠算法、天空线算法及其变体。它们的特点是逻辑清晰、代码量小、结果可评测,对于验证论文中的改进点是否有效,这些基础方法已经足够了。
学术界有一个广泛使用的术语叫 BL(bottom-left)算法。它的基本逻辑是:按给定顺序逐个放置矩形件,每个矩形件被放置在板材中满足不重叠条件的最底部且最左侧的位置。算法本身并不保证全局最优,但优点是计算速度快、实现简单、可以作为后续元启发式算法的解码器。
复现一个增强版的 BL 算法——带高度补偿的底部左靠算法,是很多人论文复现的第一步。下面给出 Python 参考实现,输入是一系列矩形件(宽度、高度),输出是每个矩形件在板材上的左下角坐标。
import numpy as np def bottom_left_pack(rects, board_width): # rects: list of (w, h),按放置顺序排列 # board_width: 板材宽度 # 返回 [(x, y, w, h), ...] 和实际总高度 placed = [] skyline = [0] # 用线段表示当前轮廓,元素为高度 line_x = [0] # 每条轮廓线段的起始 x 坐标 total_width_used = [0] # 轮廓线段终点,隐含在下一个起点里 cur_height = 0 def find_position(w, h): best_x = None best_y = float('inf') # 遍历当前轮廓线段,尝试将矩形放在每个线段的起始位置 for i in range(len(skyline)): x_start = line_x[i] # 向右扩展,校验从 x_start 开始宽度 w 范围内的高度限制 if x_start + w > board_width: continue y_min = skyline[i] # 检查该区间内轮廓的最大高度,取最大值作为放置底面 for j in range(i, len(skyline)): if line_x[j] >= x_start + w: break y_min = max(y_min, skyline[j]) if y_min < best_y: best_y = y_min best_x = x_start return best_x, best_y for (w, h) in rects: x, y = find_position(w, h) placed.append((x, y, w, h)) # 更新轮廓:把 [x, x+w) 区间的高度更新为 y+h new_skyline = [] new_line_x = [] i = 0 while i < len(line_x): if line_x[i] >= x + w: new_skyline.append(skyline[i]) new_line_x.append(line_x[i]) i += 1 elif line_x[i] < x: new_skyline.append(skyline[i]) new_line_x.append(line_x[i]) i += 1 else: # 该线段与放置矩形相交,需要分割 seg_start = line_x[i] seg_end = line_x[i+1] if i+1 < len(line_x) else board_width left_part_end = min(x, seg_end) right_part_start = max(x + w, seg_start) if left_part_start := min(x, seg_end) > seg_start: new_skyline.append(skyline[i]) new_line_x.append(seg_start) if left_part_start < seg_end: new_skyline.append(skyline[i]) new_line_x.append(left_part_start) if right_part_start < seg_end: new_skyline.append(y + h) new_line_x.append(right_part_start) if seg_end < x + w: new_skyline.append(skyline[i]) new_line_x.append(seg_end) i += 1 # 简化:合并相邻同高线段 merged_skyline = [] merged_line_x = [] for k in range(len(new_skyline)): if merged_skyline and merged_skyline[-1] == new_skyline[k]: continue merged_skyline.append(new_skyline[k]) merged_line_x.append(new_line_x[k]) skyline = merged_skyline line_x = merged_line_x cur_height = max(cur_height, y + h) return placed, cur_height提示:上面的轮廓更新代码为了简洁已经做了逻辑合并,但如果你直接运行会发现某些边界情况下 new_skyline 和 line_x 长度不一致。工程上不推荐自己手写天空线更新,更稳妥的方案是使用“网格占用矩阵”来做碰撞检测。即维护一个 board_width × cur_height 的布尔矩阵,每放置一个矩形就把对应区域标记为已占用,找位置时从底向上逐行扫描。
网格法的时间复杂度更高,但代码可读性和正确性远优于手写线段树。论文复现的第一目标是验证算法思想,不是竞赛级性能优化。以下给出网格版本的核心循环:
def bottom_left_grid(rects, board_width): # 先估算高度上界 total_area = sum(w * h for w, h in rects) upper_height = int(total_area / board_width * 1.5) + 100 occupied = np.zeros((upper_height, board_width), dtype=bool) placed = [] max_y = 0 for (w, h) in rects: found = False for y in range(upper_height - h + 1): for x in range(board_width - w + 1): if not occupied[y:y+h, x:x+w].any(): placed.append((x, y, w, h)) occupied[y:y+h, x:x+w] = True max_y = max(max_y, y + h) found = True break if found: break if not found: placed.append((0, max_y, w, h)) occupied[max_y:max_y+h, 0:w] = True max_y += h return placed, max_ygrid 版本的时间复杂度是 O(n × H × W × 面积检查),对于几十个矩形件完全够用。学术论文里常用的测试集如 Hopper 标准数据集,单实例通常不超过 100 个矩形件,grid 版本跑完也就几毫秒到几十毫秒。很多研究者在论文中声称自己的算法能在几秒内解决几百件的大型实例,但那些人用的不是纯 Python,也不一定是网格矩阵。理解这层差别,对复现论文结果时出现的速度差异就不会感到困惑。
3.2 将启发式结果转为可视化:排样图让人一眼看出问题
盲调算法的效果很差,必须看到排样布局才能理解算法行为。用 matplotlib 的 Rectangle 补丁可以很容易地画出排样结果,下面是一个绘图工具函数:
import matplotlib.pyplot as plt from matplotlib.patches import Rectangle def draw_layout(placed, board_width, show_height=None): if show_height is None: show_height = max(y + h for x, y, w, h in placed) fig, ax = plt.subplots(figsize=(12, 6)) for i, (x, y, w, h) in enumerate(placed): rect = Rectangle((x, y), w, h, linewidth=1.2, edgecolor='black', facecolor=plt.cm.tab20(i % 20), alpha=0.85) ax.add_patch(rect) ax.text(x + w/2, y + h/2, str(i), ha='center', va='center', fontsize=6, fontweight='bold') ax.set_xlim(0, board_width) ax.set_ylim(0, show_height * 1.05) ax.set_aspect('equal') ax.grid(True, linewidth=0.3) plt.xlabel('width') plt.ylabel('height') plt.show()使用这个函数可以直接观察矩形件之间的间隙、贴边情况和整体轮廓的平整度。我在实际项目里发现一个很有用的经验:如果排样图里板材中间出现明显的大空洞,大概率不是因为矩形件形状特殊,而是放置顺序不合理导致的连锁反应。这时候不要急着改算法,先把矩形件按面积降序排列,再跑一遍 BL 算法,往往空洞就消失了。
3.3 排序策略对比:面积降序并不是唯一答案
关于放置顺序的论文非常多,经典的排序策略包括按面积降序、按最长边降序、按宽度降序、按高度降序、按周长降序、随机排列结合 BL 多次运行取最优。为了验证哪种排序更适合你的数据,可以做一个小实验,代码如下:
import random def run_multiple_sorts(rects, board_width, sorts=None, repeat=5): if sorts is None: sorts = { 'area_desc': lambda r: sorted(r, key=lambda x: x[0]*x[1], reverse=True), 'w_desc': lambda r: sorted(r, key=lambda x: x[0], reverse=True), 'h_desc': lambda r: sorted(r, key=lambda x: x[1], reverse=True), 'perimeter_desc': lambda r: sorted(r, key=lambda x: x[0]+x[1], reverse=True), 'random': lambda r: random.sample(r, len(r)), } results = {} for name, sort_func in sorts.items(): best_h = float('inf') for _ in range(repeat): _, h = bottom_left_grid(sort_func(rects[:]), board_width) best_h = min(best_h, h) results[name] = best_h return results在这段代码里,每个排序策略做 5 次随机重复只是因为 random 策略存在多轮间差异,而确定性排序策略 5 次结果相同。实验做完后可以横向比较哪个排序策略在你的数据集上总高度最小。就我的经验,面积降序在大多数数据集上已经是很好的基线,但偶尔会有按宽度降序胜出的情况——当板材特别宽而矩形件宽度差异大时,优先放宽的比较关键。
4. 提升排样效果的两个关键约束:旋转与一刀切
4.1 允许 90 度旋转的效果:有时提升巨大,有时完全无效
很多真实场景中矩形件可以旋转 90 度放置,例如原纸卷切割和板材开料,只要材料的纹理方向不敏感。论文中的处理方式有两种:一是在排序前把所有矩形件按“宽不小于高”的规则统一朝向,二是把旋转作为启发式搜索的一部分动态决定。第二种明显更复杂,但效果不一定更好,因为统一朝向本质上等于扩大了矩形的高宽比范围,给了排样器更多变化余地。
复现时可以简单地做自动旋转策略:对每个矩形件,如果宽度小于高度,则交换宽高。这一步看似简单,但影响巨大。拿一组典型数据来说,一叠宽高比在 2 以上的细长矩形件,不旋转时可能会出现大量竖条排列,板材宽度利用率极低;旋转后所有矩形件都变成横条,排样高度能直接降低 15% 到 25%。
def normalize_orientation(rects): return [(min(w, h), max(w, h)) for w, h in rects]代码的逻辑很简单:每对宽高统一调整为宽 <= 高,保证所有矩形件在后续排样中的方向一致。再配合 BL 算法运行时把矩形件放置时的宽高互换判断,就能实现旋转支持。
4.2 一刀切约束:从论文理论到生产线的落差
一刀切约束指所有切割路径必须从板材一端延伸到另一端,即每一步切割都是一条贯穿线。真正的工厂切割机台大多是一刀切设备,因此在工程应用中不考虑一刀切约束的排样算法,其结果往往不能被车间直接使用。学术界讨论一刀切约束的论文很多,经典的做法是在启发式搜索中限制候选放置方式为“可一刀切的位置”。
实现一刀切约束最简单可靠的方法是递归二分:尝试在板材中先切垂直刀或水平刀,切出的两个子区域独立排样,再通过搜索找到最优的切割位置。这种方式在件数少于 20 时效果很好,一旦件数增多,组合爆炸会让纯递归不可行。工程上更常见的是两阶段法:先用启发式算法生成一个排样方案,再用后处理算法把切割路径调整为满足一刀切约束的路径。
后处理调整的代码逻辑较复杂,这里不建议直接手写。更务实的做法是,如果你所在行业确实需要一刀切,直接检索关键词“guillotine cutting constraint rect packing”并下载专门讨论这个约束的论文,按论文中的方法去实现,而不是从零推导。
5. 二维矩形排样避坑指南:复现论文时常犯的五个错误
5.1 坑一:用“面积利用率”作为唯一评判指标
很多论文用面积利用率来评价算法优劣,这个指标计算方式是:所有矩形件面积之和除以板材面积(总高度乘以板材宽度)。问题在于,面积利用率不反映排样轮廓的平整程度,而后者直接影响后续是否能够继续拼接其他矩形件或在板材上增加非矩形件。我在实际工作中遇到过 A 算法面积利用率比 B 算法高 2% 但整体排样轮廓凹凸不平,导致工人实际操作时无法下刀,最终不得不改用 B 算法。多看几个指标,包括最小剩余间隙、最大矩形空洞面积、轮廓高度标准差,这些才对应真实生产中的下料复杂度。
5.2 坑二:数据归一化单位错误直接导致结果不可比
学术论文中的数据几乎都以整数形式呈现,不匹配的原因是有些论文默认所有数据都是毫米,有些默认是像素,更有一些使用归一化坐标。复现时如果先把数据除以 10 或乘以 10,结果就完全不正确。建议每个数据集复现前,先用最朴素的 BL 算法跑一遍,看得到的高度是否与论文中基线数据在同一数量级。如果差了一个数量级,几乎可以断定是单位换算的问题。
5.3 坑三:轮廓数据结构的边界条件出错
手写天空线算法进行轮廓更新时,最大的坑是线段分裂与合并的边界条件。连续几个矩形件全部贴着同一高度放置时,轮廓线段会合并;某个矩形件正好落在两条轮廓线段交界处时,会导致分裂逻辑混乱。以我的经验,最稳妥的做法是放弃手写线段管理,改用网格占用矩阵,或者直接使用业界已经验证的排样开源库,而不是自己造轮子。论文复现的目标是验证算法思想,不是测试自己的数据结构功底。
5.4 坑四:忽略矩形之间的“间隙阈值”
论文里往往假设矩形之间存在最小间距,比如切割刀缝宽度 2 毫米,但排样算法本身默认间隙为零。真实排样必须考虑刀缝,否则排样图好看,切割时矩形件边缘会互相干扰。处理方式有两种:一是把所有矩形件的宽高直接加上刀缝值,等价地保留间隙;二是在碰撞检测时使用膨胀矩形。第一种方式简单但会轻微改变矩形件间的相对位置,第二种方式更精细但实现略复杂。
def inflate_rects(rects, kerf): return [(w + kerf, h + kerf) for w, h in rects]这段代码把每个矩形件的目标尺寸加上 kerf 值,排样算法输出坐标后,实际切割时按照原始宽高下刀,间隙自然保留。注意这个做法会在矩形件之间产生 kerf 宽度的间隙,符合大多数数控切割机的工作方式。但有个副作用:板材边缘也多了半个刀缝,导致实际利用率略低于理论值。这是物理约束,不是算法缺陷。
5.5 坑五:随机算法跑一次就下结论
很多启发式算法包含随机成分,相似度极高的两组参数跑两次结果不同是常态。论文中通常会报告多次运行的平均结果或最好结果,但如果你只跑了一次,性能可能比论文报告值差不少。正确做法是每个实例至少独立运行 10 次,记录最好值、平均值和标准差。如果你复现的算法随机性很强,30 次比较合适;再多就没什么必要了,因为计算时间也会随之上升。
6. 进阶技巧:构建你自己的排样算法评价脚本
当你在论文打包和算法复现上投入一段时间后,你会发现最稀缺的不是算法本身,而是一套标准化的评价流程。很多时间里我们并不清楚一个改进到底有没有效果,因为测试数据不一致、初始排序不同、随机种子不同。这时候花半天时间写一个评价脚本,价值远大于再多跑一个算法变体。
推荐的评价脚本至少包含以下功能:从标准测试集读取数据、调用排样算法并记录运行时间和高度结果、自动计算面积利用率、生成对比表格。下面给出一个简化版的脚本框架:
import json import time import csv def evaluate_algorithm(data_file, algorithm, repeats=10, output_csv='results.csv'): with open(data_file, 'r') as f: dataset = json.load(f) rects_list = dataset['rects'] board_width = dataset['width'] all_rows = [] for run_id in range(repeats): t0 = time.time() placed, total_h = algorithm(rects_list.copy(), board_width) elapsed = time.time() - t0 area_sum = sum(w * h for w, h in rects_list) util = area_sum / (board_width * total_h) all_rows.append({ 'run': run_id, 'time_ms': round(elapsed * 1000, 2), 'height': total_h, 'utilization': round(util, 4) }) with open(output_csv, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=all_rows[0].keys()) writer.writeheader() writer.writerows(all_rows) return all_rows这个脚本要表达的核心是结果可追踪。每一个算法、每一个数据集、每一个随机种子的运行结果都落到 CSV 文件里,任何人都能复现你的实验流程。我一般会在项目目录下放三个子目录:papers(论文原文及笔记)、datasets(测试数据集与格式说明)、results(每次实验的 CSV 与排样图)。这套结构经过三四个主题项目验证过,比按算法名建目录更科学,因为一个项目可能同时探索多个算法,一个算法也可能服务于多个项目。
把这些基础打牢之后,你会读到的更多进阶方向包括:利用底部左靠算法作为遗传算法解码器的自适应编码策略、引入缺陷区域的现实排样建模、以及多板材嵌套余料再利用的排样与套料联合优化。这些方向无一例外都是从经典矩形排样论文生长出来的。
做了一圈论文打包和复现,我最深的教训是:复现不到 30% 的论文,就不要说自己理解了这个方向。同样是启发式,不同论文在细节处的处理差别足以让最终效果拉开 10% 以上的差距。不要迷信论文报告的数字,更不要拿论文数字和别人的开源代码跑出来的结果比,采样条件很可能不一致。务实地把上面提到的基础算法跑通、把评价脚本和可视化工具准备好,再去精读那 20% 真正值得学的文献,你的排样工程方向就不会再是一团迷雾。希望这篇笔记的整理思路和踩坑经验能帮到你少走一些弯路。
本文还有配套的精品资源,点击获取