简介这份资源面向物流、仓储与制造业优化方向的学习者及MATLAB使用者提供遗传算法求解三维装箱问题的完整实现。三维装箱属NP难组合优化问题目标是在有限容器内最大化空间利用率或最小化箱数遗传算法凭借全局搜索与并行性成为有效求解工具。压缩包共7个文件约92KB含3个m脚本、2个xlsx数据表、1个jpg与1个doc文档脚本分别承担适应度评估、种群初始化与选择交叉变异、主流程控制表格记录物品尺寸、箱子规格及每代最优解与适应度文档补充算法步骤与参数说明。已有1064人学习下载。读者可据此掌握从建模、编码到迭代收敛的完整流程理解选择、交叉、变异策略对性能的影响并直接运行调试为物流规划与仓库设计提供决策参考。1. 三维装箱遇上遗传算法一个能跑出装箱方案的完整工程包如果你接触过物流调度、集装箱配载或者仓储优化大概率听过三维装箱问题3D Bin Packing。它的核心诉求很朴素给定一批尺寸各异的货物和一个固定容积的箱子怎么摆放才能让空间利用率最高、装载数量最多。听起来简单但货物数量一多组合方案就是指数级爆炸穷举根本不现实。这份资源给的是一个用遗传算法求解三维装箱问题的完整工程包里面包含可运行的代码、测试数据和结果输出逻辑。它适合两类人一类是想搞懂遗传算法怎么落地到实际组合优化问题的开发者另一类是手头有装箱需求、想找个能改能跑的基础框架的工程人员。我拿到之后先把项目跑了一遍确认它能输出具体的装箱方案和空间利用率然后才开始拆代码。2. 项目结构与技术选型先搞清楚代码怎么组织2.1 目录结构一览拿到一个压缩包我习惯先看目录结构这比读文档快得多。这个项目的组织方式比较典型按职责拆成了几个部分├── main.py # 程序入口配置参数并启动求解 ├── ga_core.py # 遗传算法核心种群初始化、选择、交叉、变异 ├── box_model.py # 三维装箱的数学模型箱子、货物、空间划分 ├── fitness.py # 适应度函数评估每个个体的装箱方案优劣 ├── utils.py # 工具函数数据加载、结果可视化、日志输出 ├── data/ │ └── sample_goods.csv # 示例货物数据编号、长、宽、高、数量 └── output/ └── result.json # 求解结果每个货物的摆放坐标和朝向这个结构的好处是职责清晰。box_model.py只管空间和几何逻辑ga_core.py只管进化流程fitness.py单独定义评价标准。你改任何一块都不会牵一发动全身。常见做法是把这些揉在一个文件里跑是能跑但改起来就是血泪经验了。2.2 技术栈与依赖项目用的是 Python没有引入额外的优化求解库遗传算法的选择、交叉、变异全部手写。依赖只有几个基础库# 核心依赖 numpy # 矩阵运算和空间坐标计算 pandas # 读取CSV货物数据 matplotlib # 可选用于绘制装箱结果三维图安装方式很直接pip install numpy pandas matplotlib没有用到 DEAP、PyGAD 这类遗传算法框架原因是三维装箱的编码方式比较特殊通用框架反而不好适配。手写虽然代码量大一些但每个算子都能按装箱场景定制比如交叉操作要考虑货物序列的合法性变异要保证不产生重叠摆放。2.3 三维装箱的编码方式这是整个项目最核心的设计决策。遗传算法要跑起来首先得把「装箱方案」编码成染色体。这个项目用的是一种基于货物排列顺序加旋转状态的编码# box_model.py 中的个体编码结构 # 每个个体是一个列表列表元素为 (货物ID, 旋转类型) # 旋转类型 0-5 表示货物六种不同的朝向 individual [ (0, 0), # 货物0不旋转 (3, 2), # 货物3旋转类型2 (1, 5), # 货物1旋转类型5 # ... ]逻辑说明这个编码的巧妙之处在于它不直接编码每个货物的三维坐标而是编码「放置顺序 朝向」。解码时按照顺序依次把货物放进箱子放置位置由空间划分算法自动决定。这样做的好处是染色体长度固定等于货物总数交叉变异操作简单不会产生坐标越界或重叠的非法解。参数说明旋转类型 0-5 对应长方体的六种朝向组合。比如一个 2×3×4 的货物旋转类型 0 是 (2,3,4)类型 1 是 (2,4,3)以此类推。如果你的货物有朝向限制比如不能倒放可以在box_model.py里限制旋转类型的取值范围。3. 遗传算法核心流程从种群初始化到收敛输出3.1 种群初始化策略种群初始化看起来简单但对收敛速度影响很大。这个项目用了混合策略一部分个体随机生成一部分用贪心算法生成。# ga_core.py import random import numpy as np def init_population(goods_list, pop_size): 初始化种群 goods_list: 货物列表每个元素为 (长, 宽, 高) pop_size: 种群大小 population [] num_goods len(goods_list) # 策略1完全随机初始化占30% for _ in range(int(pop_size * 0.3)): individual [] for i in range(num_goods): rotation random.randint(0, 5) # 随机朝向 individual.append((i, rotation)) random.shuffle(individual) # 打乱放置顺序 population.append(individual) # 策略2按体积降序 随机朝向占70% # 大件先放小件填充这是装箱的常见经验 sorted_indices sorted(range(num_goods), keylambda i: goods_list[i][0]*goods_list[i][1]*goods_list[i][2], reverseTrue) for _ in range(pop_size - int(pop_size * 0.3)): individual [] for i in sorted_indices: rotation random.randint(0, 5) individual.append((i, rotation)) # 在排序基础上做小扰动避免种群过于单一 for _ in range(random.randint(1, 3)): idx1, idx2 random.sample(range(num_goods), 2) individual[idx1], individual[idx2] individual[idx2], individual[idx1] population.append(individual) return population逻辑说明完全随机的个体负责探索解空间的多样性贪心排序的个体负责快速逼近可行解。70% 的个体按体积降序排列是因为大件货物先放更容易找到合理的空间划分小件后放可以填充缝隙。小扰动操作是为了防止种群在初始化阶段就失去多样性。参数说明pop_size一般取 50-200。货物数量少于 20 时取 50 就够了超过 50 个货物建议取 100-200。初始化比例 30/70 是我调过几次后觉得比较稳的你可以根据货物尺寸差异程度调整——差异大就多给随机初始化一些比例。3.2 适应度函数设计适应度函数直接决定算法往哪个方向进化。三维装箱的适应度通常由两部分组成空间利用率和放置合法性惩罚。# fitness.py def calculate_fitness(individual, goods_list, bin_size): 计算个体适应度 individual: [(货物ID, 旋转类型), ...] goods_list: 货物尺寸列表 bin_size: (箱子长, 箱子宽, 箱子高) bin_l, bin_w, bin_h bin_size placed_boxes [] # 已放置的货物 [(x, y, z, l, w, h), ...] total_volume 0 for goods_id, rotation in individual: l, w, h get_rotated_size(goods_list[goods_id], rotation) # 寻找可行放置点 position find_position(placed_boxes, (l, w, h), bin_size) if position is None: # 放不下跳过该货物惩罚项在最后统一计算 continue x, y, z position placed_boxes.append((x, y, z, l, w, h)) total_volume l * w * h # 空间利用率 bin_volume bin_l * bin_w * bin_h utilization total_volume / bin_volume # 未放置货物的惩罚 unplaced_ratio 1 - len(placed_boxes) / len(individual) # 综合适应度利用率为主未放置率为辅 fitness utilization * 0.7 (1 - unplaced_ratio) * 0.3 return fitness, placed_boxes逻辑说明find_position是空间划分的核心函数它遍历已放置货物产生的候选放置点通常是每个已放置货物的右、上、前三个方向的角点找到第一个不重叠且不越界的位置。如果找不到该货物就被跳过。适应度用加权和的方式综合了空间利用率和装载率权重 0.7 和 0.3 可以根据实际需求调整——如果你更在意装得多而不是装得满可以把装载率权重调高。参数说明bin_size是箱子的三维尺寸单位要和货物数据保持一致。get_rotated_size根据旋转类型返回货物的实际占用尺寸。注意这里没有做货物承重和堆叠限制如果你的场景有这些约束需要在find_position里加判断。3.3 选择、交叉与变异算子这三个算子是遗传算法的进化引擎。项目里用的是锦标赛选择、顺序交叉和交换变异。# ga_core.py def tournament_select(population, fitness_values, k3): 锦标赛选择随机选k个取适应度最高的 selected random.sample(range(len(population)), k) best_idx max(selected, keylambda i: fitness_values[i]) return population[best_idx] def order_crossover(parent1, parent2): 顺序交叉保持货物ID不重复 适用于排列编码的经典交叉方式 n len(parent1) start, end sorted(random.sample(range(n), 2)) # 子代1继承parent1的片段其余按parent2顺序填充 child1 [None] * n child1[start:end] parent1[start:end] inherited_ids {gene[0] for gene in child1[start:end]} fill_idx 0 for gene in parent2: if gene[0] not in inherited_ids: while child1[fill_idx] is not None: fill_idx 1 child1[fill_idx] gene return child1 def swap_mutation(individual, mutation_rate0.05): 交换变异随机交换两个位置的货物 individual individual.copy() for i in range(len(individual)): if random.random() mutation_rate: j random.randint(0, len(individual) - 1) individual[i], individual[j] individual[j], individual[i] return individual逻辑说明锦标赛选择的k值控制选择压力k3是比较温和的设置既能保证优秀个体被选中又不会让种群过快同质化。顺序交叉保证了子代中每个货物 ID 只出现一次这是排列编码必须满足的约束。交换变异以 5% 的概率交换两个位置的货物这个概率不能太高否则会破坏已经形成的优良排列。参数说明mutation_rate建议在 0.01-0.1 之间。货物数量多的时候取小值因为单个基因的影响被稀释了货物少的时候可以取大值增加探索能力。交叉率一般固定为 0.8-0.9项目里是在主循环中控制的。4. 运行与调参怎么让算法跑出可用结果4.1 数据准备与格式要求项目的数据输入是 CSV 文件格式很简洁id,length,width,height,quantity 0,30,20,15,2 1,50,40,25,1 2,20,20,20,3字段说明id是货物编号length/width/height是货物三维尺寸quantity是数量。如果同一种货物有多件会在加载时展开成多条记录。单位没有限制但必须和箱子尺寸保持一致。我一般用厘米箱子尺寸设成 100×80×60 这种量级数值太小容易在浮点比较时出问题。加载数据的代码在utils.py里import pandas as pd def load_goods(csv_path): 加载货物数据展开数量字段 df pd.read_csv(csv_path) goods_list [] for _, row in df.iterrows(): for _ in range(int(row[quantity])): goods_list.append((row[length], row[width], row[height])) return goods_list4.2 主循环与参数配置main.py是整个程序的入口所有可调参数都集中在这里# main.py from ga_core import init_population, tournament_select, order_crossover, swap_mutation from fitness import calculate_fitness from utils import load_goods # 参数配置区 BIN_SIZE (100, 80, 60) # 箱子尺寸 POP_SIZE 100 # 种群大小 MAX_GENERATIONS 500 # 最大迭代代数 CROSSOVER_RATE 0.85 # 交叉概率 MUTATION_RATE 0.05 # 变异概率 ELITE_COUNT 5 # 精英保留数量 # goods_list load_goods(data/sample_goods.csv) population init_population(goods_list, POP_SIZE) best_fitness_history [] for gen in range(MAX_GENERATIONS): # 计算适应度 fitness_results [calculate_fitness(ind, goods_list, BIN_SIZE) for ind in population] fitness_values [f[0] for f in fitness_results] # 记录最优 best_idx max(range(len(fitness_values)), keylambda i: fitness_values[i]) best_fitness_history.append(fitness_values[best_idx]) # 精英保留 sorted_idx sorted(range(len(fitness_values)), keylambda i: fitness_values[i], reverseTrue) new_population [population[i] for i in sorted_idx[:ELITE_COUNT]] # 生成新一代 while len(new_population) POP_SIZE: parent1 tournament_select(population, fitness_values) parent2 tournament_select(population, fitness_values) if random.random() CROSSOVER_RATE: child order_crossover(parent1, parent2) else: child parent1.copy() child swap_mutation(child, MUTATION_RATE) new_population.append(child) population new_population # 每50代输出一次进度 if gen % 50 0: print(f第{gen}代最优适应度{fitness_values[best_idx]:.4f}) # 输出最终方案 best_individual population[0] final_fitness, final_placement calculate_fitness(best_individual, goods_list, BIN_SIZE) print(f最终空间利用率{final_fitness:.4f})逻辑说明主循环的结构很标准——评估、选择、交叉、变异、精英保留。精英保留确保每代最优的几个个体不会因为交叉变异被破坏这对收敛稳定性很重要。每 50 代输出一次进度方便你判断算法是否还在改进。参数说明MAX_GENERATIONS设 500 是保守值实际跑的时候可以观察输出如果 200 代之后适应度基本不变就可以提前停了。ELITE_COUNT一般取种群大小的 5% 左右太大容易早熟太小收敛慢。4.3 结果输出与可视化跑完之后output/result.json会记录每个货物的最终摆放坐标import json def save_result(placement, bin_size, output_pathoutput/result.json): 保存装箱方案 result { bin_size: bin_size, placements: [ {x: p[0], y: p[1], z: p[2], l: p[3], w: p[4], h: p[5]} for p in placement ], utilization: sum(p[3]*p[4]*p[5] for p in placement) / (bin_size[0]*bin_size[1]*bin_size[2]) } with open(output_path, w) as f: json.dump(result, f, indent2)如果你装了 matplotlib还可以画个三维图直观看看装箱效果。不过说实话货物一多图就很乱我一般只看利用率和未装载列表。5. 避坑与常见问题那些我踩过的坑5.1 算法不收敛或适应度震荡现象跑了几十代最优适应度一直在某个值附近上下跳没有明显提升。原因最常见的是种群多样性不足或者变异率太低导致陷入局部最优。另一个可能是适应度函数设计有问题比如惩罚项权重过大导致算法只顾着减少未放置货物忽略了空间利用率。解决先把变异率从 0.05 提到 0.1 试试如果还不行检查初始化策略里随机个体的比例是不是太低了。我一般会在主循环里加一个多样性监控——如果连续 30 代最优值不变就随机重新初始化 20% 的个体。5.2 货物放不进去但箱子明明有空间现象空间利用率只有 60% 多但看输出发现有些货物被跳过了而箱子里还有明显空隙。原因这是三维装箱的经典问题——空间划分方式太粗糙。项目里用的是角点放置法只能把货物放在已放置货物的角点位置如果空隙不在角点就利用不上。解决可以在find_position里增加候选点比如两个已放置货物之间的缝隙中点。另一个办法是允许货物在放置时做微小偏移但这样会增加计算量。我一般先调大种群规模和迭代代数让算法自己找到更好的排列顺序来规避这个问题。5.3 运行速度太慢现象货物数量超过 50 个之后跑一代要好几秒500 代跑完要半小时以上。原因适应度计算里find_position是 O(n²) 的复杂度每个货物都要和所有已放置货物做重叠检测。种群大、代数多的时候计算量是乘起来的。解决最直接的办法是减少种群规模和迭代代数先跑个粗结果看看趋势。如果确实需要精细求解可以把find_position里的重叠检测用空间索引优化比如把已放置货物按 x 坐标排序只检测相邻的几个。另一个思路是用多进程并行计算适应度multiprocessing.Pool几行代码就能搞定。5.4 结果每次跑都不一样现象同样的数据跑两次结果不同有时候利用率差好几个百分点。原因遗传算法本身是随机算法没有设随机种子的情况下每次初始化、选择、交叉的随机序列都不同。解决在main.py开头加random.seed(42)和np.random.seed(42)这样每次跑的结果就可复现了。如果你要做参数对比实验这个很重要否则你分不清是参数改了还是随机波动。5.5 货物尺寸单位不统一导致结果异常现象程序能跑但空间利用率极低或者所有货物都放不进去。原因CSV 里的货物尺寸和代码里设置的箱子尺寸单位不一致。比如货物是毫米箱子是厘米那货物就全放不下了。解决在load_goods里加一个单位换算参数或者在数据加载后打印一下最大货物尺寸和箱子尺寸的比值心里有个数。我一般会在加载数据后加一行断言assert max(goods) bin_size不满足就直接报错省得跑半天才发现问题。6. 二次开发与改造思路从能跑到好用这个项目作为一个基础框架是合格的但真要放到实际业务里用还有几个方向值得改。我按优先级从高到低说。第一支持多箱装箱。现在的逻辑是单箱所有货物往一个箱子里塞塞不下的就跳过。实际场景往往是多个箱子目标是用的箱子数最少。改造思路是在个体编码里增加一个「箱子编号」字段适应度函数改成「已用箱子数的倒数 × 平均利用率」。这个改动量不大但实用性提升明显。第二增加承重和堆叠约束。现在的find_position只检查几何重叠不检查重量。实际装箱里重货不能压轻货底层货物承重有限制。改造方式是在box_model.py里给每个货物加一个weight属性在放置时检查下方货物的累计承重是否超限。这个改动会显著增加计算量建议配合空间索引一起做。第三把适应度计算改成增量式。现在每评估一个个体都要从头解码一遍计算量很大。实际上交叉变异只改变了局部基因可以只重新计算受影响的部分。这个优化能把运行速度提升 3-5 倍但代码改动比较大适合对性能有硬要求的场景。第四加一个结果校验模块。跑完之后的方案最好能自动校验一遍有没有重叠、有没有越界、利用率算得对不对。我一般会写一个独立的validator.py用暴力两两检测的方式验证结果合法性。虽然慢但只跑一次能避免把错误方案交给下游。第五参数自适应。现在交叉率和变异率是固定的实际上前期需要大变异率探索后期需要小变异率收敛。可以改成随代数线性递减或者根据种群多样性动态调整。这个改动代码量很小但效果往往比调固定参数好。最后说一个我自己的习惯每次改完算子或者适应度函数我都会先用 10 个货物的小数据集跑 50 代确认逻辑没问题再上大规模数据。直接拿几百个货物去调参跑一次等半天出了问题都不知道是代码 bug 还是参数不合适。从那以后我每次改核心逻辑都强制走一遍小数据快速验证希望这个习惯也能帮到你。本文还有配套的精品资源点击获取