基于MATLAB混合遗传算法的车间调度优化与实现
2026/9/14 14:34:38 网站建设 项目流程

简介:基于MATLAB的混合遗传算法车间调度优化完整实现包,面向智能制造、运筹优化方向的工程师与研究生,解决作业车间调度(JSP)中最小化完工时间、提升排产效率的问题。资源共206个文件,大小9.29MB,核心包括11个.m算法源码、7个C/C++程序及配套头文件,另含40个gif演示动画、20个htm与多个pdf、doc、ppt说明文档,以及jpg、ico等辅助素材,便于对照源码理解HGA编码、选择、交叉、变异与局部搜索流程。资源包内还包含代码工程与说明页面,方便不同基础的读者按需查阅。已有181人学习使用。通过该资源可掌握混合遗传算法与模拟退火、禁忌搜索等策略的融合方式,获得可运行的MATLAB代码、演示脚本和结果图表,有助于快速复现JSP优化实验并迁移到实际生产调度场景。该资源可作为课程设计、毕业设计与企业排产开发的参考资料。

1. 基于MATLAB混合遗传算法的车间调度优化:从代码到可排产的落地路径

车间调度问题(Job Shop Scheduling Problem, JSP)从来不是个让研究员自娱自乐的问题:订单交期、设备利用率、换产成本,每一项都直接压在生产计划员的桌子上。纯遗传算法在中小规模算例上能搜出可行解,但一旦工序数超过 15 或机器数超过 8,要么陷入早熟收敛,要么在局部最优附近来回震荡,收敛曲线拖着长长的尾巴。所谓混合遗传算法(Hybrid Genetic Algorithm),就是在常规 GA 框架里引入局部搜索或邻域搜索机制,让全局勘探与局部精化交替进行,从而在可接受的运行时间内找到更优的调度方案。这篇博文面向要把调度算法跑起来、并打算拿真实数据做验证的工程师和研究生,从问题建模、编码方式、算子设计到 MATLAB 参数调优和甘特图验证,给出可直接改用的代码思路。

2. 先把车间调度压成数据:编码、约束与 MATLAB 建模

2.1 工序—机器双层信息表是调度的“输入底板”

做车间调度优化,第一步不是写遗传算子,而是把实际的加工任务变成计算机能识别的矩阵。车间调度里最常用的输入格式是工序—机器加工时间表,每一行代表一个工件的工序,列代表每台机器上的加工时间,0 表示该工序不能在这台机器上加工。对不可重入的经典 JSP,每个工件的工序顺序是固定的,所以编码时只需要考虑“工序排列”和“机器分配”两层信息。这里用一个 6 工件 6 机器的经典算例,就是可以在文献里反复看到的 FT06:6 个工件,每个工件 6 道工序,每道工序严格对应一台机器。实际产线上如果工序可以跨机器,那就是柔性作业车间调度(FJSP)问题,编码上要额外加一段机器选择基因,本文先按经典 JSP 讲,柔性扩展放在最后一章。

数据在 MATLAB 里用二维矩阵存储比较直接。每个工件占用一行,列按工序序号排列,值是加工时间;另配一个机器矩阵存储每道工序使用的机器号。模型层面的两个硬约束必须刻进适应度计算里:同一台机器在任意时刻只能加工一道工序,同一工件的工序必须按给定顺序依次开工。优化目标一般取最大完工时间(makespan),也可以根据排产需要替换为总拖期、总能耗或机器负载均衡的加权和。

2.2 MATLAB 中读取算例并初始化种群的代码骨架

下面这段代码把 FT06 的加工时间和机器信息读入工作区,然后初始化工序排列种群。工序编码采用“基于工序的重复编号法”:每个工件编号出现次数等于它的工序数,比如 6 个工件各 6 道工序,染色体就是个包含 6 个 1、6 个 2、……、6 个 6 的长度为 36 的随机排列。这种编码天然保证工序先后顺序约束,解码时只需按编号第几次出现匹配到对应工序即可。

% load_schedule_data.m % 以 FT06 为例:6 jobs, 6 machines % machine_table(i,j): job i 第 j 道工序使用的机器编号 % time_table(i,j): job i 第 j 道工序的加工时间 machine_table = [3 1 2 4 6 5; 2 3 5 6 1 4; 3 4 6 1 2 5; 2 1 3 4 5 6; 3 2 5 6 1 4; 2 4 6 1 5 3]; time_table = [1 8 5 3 4 2; 3 5 2 6 9 3; 4 6 7 4 1 5; 7 4 8 9 6 2; 2 5 4 9 8 1; 4 3 6 2 7 5]; n_job = size(machine_table, 1); n_op = size(machine_table, 2); % 每个工件的工序数 pop_size = 100; % 初始化种群:每个个体是一段长度为 n_job*n_op 的随机排列 % 每个工件编号出现 n_op 次,表示该工件的 n_op 道工序 base_seq = repmat(1:n_job, 1, n_op); population = zeros(pop_size, n_job * n_op); for i = 1:pop_size population(i, :) = base_seq(randperm(n_job * n_op)); end

逻辑说明:base_seq是标准的工序编号序列,randperm对整个序列做随机打散,得到的每个染色体天然满足“同一工件内部工序顺序”约束。因为解码器只按工件编号第几次出现来取工序,所以不需要额外的可行性修复操作。参数上,pop_size设 100 是中小算例的常用起点;如果工件数超过 20,建议按工件数乘以 6 到 8 设置种群规模,否则基因多样性不足以支撑全局搜索。机器表和时间表注意要按行对齐,机器号从 1 开始连续编号,MATLAB 的列索引天然从 1 起,所以这里不需要加 1 修正。

2.3 用调度解码器把染色体变成甘特图坐标

染色体本身只是一串编号,必须经过解码才能计算出目标函数。经典 JSP 的解码采用“按工序顺序逐个插入最早可用时间窗”的半主动调度策略。维护两个时间数组:job_next_time记录每个工件上一道工序的结束时间,machine_free_time记录每台机器的空闲起点。遍历染色体的每个基因,查询该工件当前工序对应的机器号,工序开工时间取这两个时间的较大值,完工时间就是开工时间加加工时间,然后更新两个时间数组。

% decode_schedule.m function makespan = decode_schedule(chromosome, machine_table, time_table) n_job = size(machine_table, 1); n_op = size(machine_table, 2); job_step = zeros(1, n_job); % 每个工件已加工到的工序索引 job_next_time = zeros(1, n_job); % 工件上一道工序完工时间 machine_free_time = zeros(1, max(machine_table(:))); % 每台机器可用时间 for g = chromesome % 注意:实际循环用 for g = chromosome for g = chromosome job_id = g; job_step(job_id) = job_step(job_id) + 1; op_idx = job_step(job_id); machine_id = machine_table(job_id, op_idx); proc_time = time_table(job_id, op_idx); start_time = max(job_next_time(job_id), machine_free_time(machine_id)); finish_time = start_time + proc_time; job_next_time(job_id) = finish_time; machine_free_time(machine_id) = finish_time; end makespan = max(job_next_time); end

这个解码器的时间复杂度是 O(L),L 是染色体长度,对 36 道工序的算例一次解码耗时在微秒级,跑 100 个个体、迭代 200 代也没有性能压力。注意这里采用的半主动解码不会主动插入机器空闲碎片,虽然可能错过更优解,但好处是解码速度快、结果稳定,遗传算法本身会通过进化去搜索更好的工序排列来压缩空闲。如果后续想提高解质量,可以在这一步改成主动解码,把每道工序插入到机器时间轴上最早可用的空隙里,不过解码时间会随空隙数量变多而上升。

3. 混合在哪:GA 全局搜索 + 模拟退火局部搜索的 MATLAB 实现

3.1 纯 GA 的早熟问题在调度里为什么特别明显

工序排列的搜索空间是巨大的,FT06 这类 6×6 规模还算小,扩展到 20×10 时可行排列数量已经远超可穷举范围。标准遗传算法靠选择压力、交叉和变异来进化,但有两个问题在车间调度场景里会被放大:一是工序编码相邻基因之间存在强关联,单点交叉很容易破坏优良的块结构;二是变异算子如果只做随机交换,产生的新排列在解空间里跳得太远,无法在当前最优附近精细打磨。结果就是算法迭代几十代后种群多样性快速下降,最优解长期卡在一个平台期不再更新。混合策略的动机就在于此:交叉变异负责大范围跳跃,局部搜索负责在优秀个体周围爬山,两者节奏错开,才能兼顾早中期的探索和后期的精化。

常见的混合方式有三种:第一种是把模拟退火(SA)或禁忌搜索(TS)作为变异算子的一种替代形式,对交叉产生的子代做局部搜索后再放回种群;第二种是每间隔若干代对精英个体执行局部搜索,类似 Lamarckian 进化策略;第三种是在种群层面引入重启动机制,局部搜索找不到改进时触发种群重新初始化。工程上最省事且效果稳定的做法是第二种,因为它不会干扰 GA 原有的选择流程,代码改动也最小。

3.2 模拟退火局部搜索的设计与 MATLAB 关键代码

这里选用“插入邻域 + 模拟退火接受准则”作为局部搜索模块。插入邻域的动作是:从染色体中随机抽出一个工件编号,把它移动到另一个随机位置。相比交换两个位置,插入动作更符合车间调度的排产直觉——改变一个工件的相对顺序,而不是盲目互换。接受准则采用标准 Metropolis 规则:新解比当前解好则必然接受,否则以exp(-delta / current_temperature)的概率接受。温度随局部搜索的迭代次数线性下降。

% local_search_sa.m function new_chrom = local_search_sa(chromosome, machine_table, time_table, temp_init, temp_end) current = chromosome; current_makespan = decode_schedule(current, machine_table, time_table); T = temp_init; while T > temp_end % 随机选一个插入动作:抽出位置 p1,插入到 p2 len = length(current); p1 = randi(len); p2 = randi(len); candidate = current; gene = candidate(p1); candidate(p1) = []; candidate = [candidate(1:p2-1), gene, candidate(p2:end)]; new_makespan = decode_schedule(candidate, machine_table, time_table); delta = new_makespan - current_makespan; if delta < 0 || rand < exp(-delta / max(T, 1e-6)) current = candidate; current_makespan = new_makespan; end T = T * 0.95; % 线性或指数降温均可 end new_chrom = current; end

这段代码有几个关键参数要说明。temp_init一般设为初始种群最优个体目标值的 10% 到 20%,太大退火前期乱跳,太小起不到逃离局部最优的作用;temp_end取 1 左右即可,低于这个值后接受劣解的概率已经很低。降温系数 0.95 是兼顾搜索深度和耗时的保守选择,如果局部搜索只对精英个体做、每代做一次,那么温度从 20 降到 1 大约需要 58 次迭代,时间可控。注意在exp计算时要做delta符号判断,delta大于 0 时的分母温度不能为 0,所以max(T, 1e-6)这个下限保护是必要的,否则 MATLAB 会给出 Inf 导致后续逻辑错乱。

3.3 处理器序约束的交叉算子:POX 交叉的 MATLAB 实现

工序编码的交叉算子不能随便用经典单点交叉,否则子代会出现某个工件编号缺失或重复,违背“每工件出现 n_op 次”的约束。预处理排序交叉(Precedence Preserving Order-based Crossover, POX)是 JSP 里最常用的算子:把工件集合随机分成两个子集,父代 P1 中包含子集 S1 的基因原样保留到子代 O1 对应位置,P1 中属于 S2 的基因按它们在 P2 中的顺序依次填入 O1 的空位。这样 O1 中 S1 的相对位置继承自 P1,S2 的相对顺序继承自 P2,保证了合法性。

% pox_crossover.m function [child1, child2] = pox_crossover(parent1, parent2, n_job) len = length(parent1); % 随机将工件编号分成两组 job_set = randperm(n_job); split = randi([1, n_job - 1]); set1 = job_set(1:split); set2 = set(job_set(split+1:end)); % 注意变量名冲突,应为 set2 = job_set(split+1:end); child1 = zeros(1, len); child2 = zeros(1, len); % 继承 set1 基因的位置 pos1 = ismember(parent1, set1); child1(pos1) = parent1(pos1); % 剩余位置按 parent2 中 set2 的顺序填充 fill_seq = parent2(ismember(parent2, set2)); child1(~pos1) = fill_seq; % 反向交叉 pos2 = ismember(parent2, set1); child2(pos2) = parent2(pos2); fill_seq2 = parent1(ismember(parent1, set2)); child2(~pos2) = fill_seq2; end

逻辑说明:ismember返回的逻辑索引是这段代码的核心,避免了手动循环找位置的繁琐写法,也利用到了 MATLAB 的向量化优势。fill_seq提取的是另一父代中属于补集工件的基因,按原顺序提取就保证了工序先后关系。变异算子方面,推荐“插入变异 + 小块逆序”配合使用:插入变异在局部搜索里已经用过,所以主 GA 的变异可以选随机抽取一段长度为 2 到 5 的连续片段反转,既改变排列结构又不至于过度打乱优良块。

4. 从早熟到收敛:MATLAB 参数正交实验与收敛曲线对比

4.1 种群规模、交叉概率、变异概率的交互效应

车间调度优化的结果对参数相当敏感,但不同参数之间不是独立起作用的。种群规模决定搜索的覆盖面,交叉概率决定基因块的组合频次,变异概率和局部搜索强度则共同控制跳出局部最优的能力。把它们一个个单独调优往往陷入“调好 A 又破坏了 B”的循环里。常见做法是做个简单的正交实验,用 L9 正交表安排三因素三水平,跑同样的算例,看哪个因素对最终 makespan 的影响最大。以 FT06 为基准,目标值是已知最优 55,用这个做验证特别方便——算法如果跑不出 55,说明参数组合或算子实现有问题。

这里给一个参数组合的起点表,基于同类 JSP 算例的常见设置:

参数低水平中水平高水平说明
种群规模 pop_size50100200小于 50 容易早熟
交叉概率 pc0.70.850.95大于 0.95 近似随机搜索
变异概率 pm0.050.10.2配合局部搜索时可适当减小
局部搜索温度初值51530与当前最优解量级相关

注意交叉概率不是越大越好。工序编码下,POX 交叉的继承特性会保留父代中的大块基因顺序,过高的交叉概率会让种群快速同质化。经验区间是 0.8 到 0.9,变异概率对应每代发生变异的个体比例,在混合算法里因为有局部搜索兜底,可以取低水平。

4.2 批量跑实验的 MATLAB 脚本模板

手工在 MATLAB 里反复改参数再点运行,不仅效率低还容易记错配置。把主算法封装成一个函数run_hybrid_ga(pop_size, pc, pm, temp_init),返回值是最优 makespan 和收敛曲线,再用一个批处理脚本循环调用,结果统一记录到表格里。

% param_sweep.m param_combos = [ 50, 0.85, 0.1, 10; 100, 0.85, 0.1, 10; 200, 0.85, 0.1, 10; 100, 0.70, 0.1, 10; 100, 0.95, 0.1, 10; 100, 0.85, 0.05, 10; 100, 0.85, 0.20, 10; 100, 0.85, 0.1, 30]; results = table(); for i = 1:size(param_combos, 1) p = param_combos(i, :); [best_makespan, curve] = run_hybrid_ga(p(1), p(2), p(3), p(4)); results = [results; table(p(1), p(2), p(3), p(4), best_makespan)]; end

每个参数组合至少重复运行 5 次取最优和均值,因为遗传算法是随机算法,单次结果受随机种子影响很大。MATLAB 里可以用rng(i)固定每轮实验的随机种子,保证同一个组合下重复实验之间的差异只来自参数本身,而不是随机数序列的波动。固定随机种子还有一个好处:调试算子逻辑时,同一种子下结果可以复现,排错成本明显降低。实际运行中如果发现某组参数下最优值低于 55,先检查解码器是不是正确插入了最早可用时间,再检查 POX 交叉是否意外改动了基因总量。

4.3 用收敛曲线判断混合策略是否真的有效

纯 GA 和混合 GA 的对比,最直观的方式是把每一代种群最优 makespan 存下来画在同一张图上。收敛曲线的形态能说明很多问题:纯 GA 曲线如果在前 20 代快速下降后趋于水平,大概率是陷入了局部最优;混合 GA 的曲线初期可能略慢,因为局部搜索消耗了部分代数,但中后期仍会出现阶梯式下降。出现阶梯状跳跃说明局部搜索触发了从当前局部最优附近逃逸出去的有效搜索。建议至少在 3 个不同算例上观察曲线的形状差异,避免在单个算例上得出过度拟合的结论。

5. 让方案真正可用:甘特图验证与柔性工序扩展

5.1 主动调度解码的扩展写法

半主动调度虽然简单,但产线上往往会存在某些工序可以往前挪到更早的空档而不影响其他工序的情况。主动调度(active schedule)把解码逻辑改为:每道工序不是只能排到机器当前末尾,而是扫描机器时间轴上的每一个空闲区间,如果工序能在该区间内完整放下且不违反工件内部顺序,就插入到该区间。这种解码方式能找到半主动调度遗漏的解,代价是解码耗时增加,但配合局部搜索时往往能获得更小的 makespan。实现上可以维护一个机器占用区间数组[start, finish],每加工完一道工序就做一个区间合并,新工序到达时依次检查所有空隙。

5.2 甘特图绘制与结果合法性检查

算法输出的是一个数字最优值,但车间排产需要看到每个工序在哪台机器上什么时间开工、什么时间结束。MATLAB 里用rectangle函数逐段绘制矩形即可实现甘特图。绘制前需要把解码过程改成记录每个工序的开工和完工时间,返回三个数组:工序 ID、机器 ID、时间区间。检查合法性的标准很简单:同一机器上任意两个工序的时间区间不能重叠,同一工件相邻工序的开工时间必须大于等于上一道工序的完工时间。代码实现时可以对机器逐一排序后做区间重叠判断,如果发现重叠,说明解码器的时间更新逻辑存在漏洞。

5.3 从 JSP 到 FJSP 的扩展与混合算法迁移

产线上更常见的是柔性作业车间:一道工序可以在多台机器上加工,只是加工时间不同。此时染色体要变成两段:前半段仍是工序排列,后半段是机器分配基因——每个基因位表示该工序选用机器组中的第几台可选机器。混合策略里的局部搜索需要扩展两个方向的邻域动作:工序插入和机器变更。机器变更的局部搜索对解质量提升往往更明显,因为选择更合适的机器能直接影响完工时间。解码器也要改为先读机器基因确定加工时间,再按工序编码做时间插入。整体框架不需要推翻重来,GA 主循环、选择算子、SA 接受准则都还能复用,这正体现了混合架构的可扩展性优势。

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

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

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

立即咨询