简介这份资源面向需要解决带约束优化问题的MATLAB学习者与工程技术人员提供了一套改进遗传算法的完整实现代码。遗传算法模拟自然选择、遗传与变异过程进行全局寻优而本资源在基础框架上加入了约束处理与改进策略适合具备一定MATLAB基础、希望将进化算法落地到实际工程优化任务中的读者参考。压缩包共9个文件全部为m脚本整体约4KB涵盖算法主流程、种群初始化、适应度计算、竞赛选择、环境选择、交叉与变异等核心模块以及用于记录每代个体适应度与基因信息的辅助脚本结构紧凑、职责清晰。目前已有1712人学习下载。通过阅读与运行主入口脚本读者可以完整观察算法从初始化到迭代终止的全过程理解选择、交叉、变异各环节的配合方式并在此基础上调整参数、替换目标函数将其迁移到自己的约束优化问题中同时借鉴作者分享的改进思路与工程经验。1. 带约束的优化问题为什么标准遗传算法总是“差一口气”做过工程优化的人大多有过这种体验目标函数写好了约束条件也列清楚了标准遗传算法跑起来却总在边界附近反复横跳——要么早熟收敛到一个不可行解要么种群多样性崩掉之后再也跳不出来。带约束的优化问题难就难在“可行域”往往只占搜索空间很小一块遗传算法本身又没有梯度信息全靠交叉变异碰运气碰到非线性约束、等式约束、多峰目标翻车概率极高。这个标题讲的是用改进的遗传算法求解带约束的优化问题并且做成通用框架。所谓“通用”是指换一个目标函数、换一组约束代码结构不用大改调几个参数就能跑。适合谁做参数标定、结构优化、调度排程、控制器整定的工程师手头有 MATLAB不想从零推导算法但需要一套能复现、能改、能看中间过程的代码。下面按“先立住原理再动手复现最后讲坑”的顺序展开。2. 约束怎么处理罚函数、修复与可行性规则的选型逻辑2.1 三种主流约束处理方式的适用边界带约束优化问题的数学形式通常写成min f(x) s.t. g_i(x) 0, i 1,...,m h_j(x) 0, j 1,...,p x_L x x_U遗传算法本身是无约束搜索方法约束必须通过某种机制“编码”进适应度或种群操作里。常见做法有三类罚函数法把约束违反量乘上惩罚系数加到目标函数上把约束问题转成无约束问题。优点是实现简单、通用性强缺点是惩罚系数难调——太小了不可行解泛滥太大了可行域边界附近搜索能力骤降。我一般会先用动态罚函数让惩罚系数随进化代数递增前期允许探索后期收紧。修复法对违反约束的个体做投影或修复操作强行拉回可行域。适合约束结构清晰、修复操作代价低的场景比如线性约束、边界约束。等式约束用修复法要小心修复方向选不好会引入额外偏差。可行性规则在锦标赛选择时可行解优先于不可行解两个可行解比目标值两个不可行解比违反量。这是约束多目标优化里常用的支配关系移植到单目标遗传算法里效果也很稳不需要调惩罚系数代价是可行解比例极低时选择压力不够。选型建议约束数量少、可行域连续优先可行性规则约束数量多、可行域碎片化用动态罚函数加修复的混合策略。下面代码里我用的是一种改进思路——自适应罚函数配合可行性规则的混合选择兼顾探索和收敛。2.2 改进点在哪里自适应交叉变异与种群多样性维持标准遗传算法在带约束问题上最大的两个问题是早熟和边界震荡。改进方向通常落在三处第一自适应交叉变异概率。固定交叉概率在前期探索不足、后期破坏优良个体。常用做法是让交叉概率随个体适应度与种群平均适应度的关系动态调整优于平均的个体降低交叉概率保护它劣于平均的个体提高交叉概率加速淘汰。第二种群多样性维持。带约束问题可行域小种群容易挤在一起。可以在每代注入少量随机个体或者用拥挤度距离做小生境淘汰。我一般用“精英保留 随机移民”的组合每代保留最优的几个个体同时替换掉最差的几个为随机可行解。第三约束违反量的归一化。多个约束量纲不同直接相加会被大量纲约束主导。按每个约束的初始违反量做归一化再求和选择压力更均衡。function [c, ceq] constraints(x) % 通用约束接口c 为不等式约束0ceq 为等式约束0 % 示例一个二维非线性约束实际使用时替换为你的约束 c(1) x(1)^2 x(2)^2 - 1; % 圆形可行域边界 c(2) -x(1) - x(2) 0.5; % 线性约束 ceq []; % 无等式约束 end function viol constraint_violation(x) % 计算归一化约束违反量 [c, ceq] constraints(x); % 不等式约束只统计大于0的部分 viol_ineq sum(max(0, c)); % 等式约束取绝对值 viol_eq sum(abs(ceq)); viol viol_ineq viol_eq; end这段代码定义了约束接口和违反量计算。constraints函数返回不等式约束c和等式约束ceqMATLAB 优化工具箱的约定是c 0且ceq 0。constraint_violation把违反量归一化处理不等式约束只累加正值部分等式约束取绝对值。实际工程中如果约束量纲差异大可以在viol_ineq里对每个约束除以它的参考值再求和。2.3 适应度函数罚函数与可行性规则的混合写法适应度函数的设计直接决定选择压力。纯罚函数写法简单但惩罚系数敏感纯可行性规则不需要调参但可行解稀少时收敛慢。混合写法是可行解按目标值排序不可行解按违反量排序两者之间用可行性规则比较同时在适应度里加一个较小的动态惩罚项让不可行解也有梯度可循。function fitness evaluate_fitness(pop, penalty_base, gen, max_gen) % pop: 种群矩阵每行一个个体 % penalty_base: 惩罚系数基数 % gen/max_gen: 当前代数和最大代数用于动态惩罚 n size(pop, 1); fitness zeros(n, 1); % 动态惩罚系数随代数线性递增 penalty penalty_base * (1 gen / max_gen); for i 1:n x pop(i, :); obj objective(x); % 目标函数 viol constraint_violation(x); % 约束违反量 if viol 1e-6 % 可行解适应度为目标值 fitness(i) obj; else % 不可行解目标值加惩罚项 fitness(i) obj penalty * viol; end end end function f objective(x) % 示例目标函数Rastrigin 函数实际使用时替换 f 10 * length(x) sum(x.^2 - 10 * cos(2 * pi * x)); endevaluate_fitness里动态惩罚系数penalty随代数递增前期允许不可行解参与进化后期收紧约束。viol 1e-6是可行解判定阈值工程上根据约束精度要求调整。目标函数这里用 Rastrigin 做示例换成你的实际目标即可。注意惩罚系数基数penalty_base需要根据目标函数量级设置一般取目标函数初始种群目标值标准差的 1 到 10 倍。3. 从零跑通改进遗传算法的 MATLAB 实现步骤3.1 种群初始化与编码方式选择带约束问题的编码方式直接影响搜索效率。实数编码适合连续变量二进制编码适合离散变量混合编码适合既有连续又有离散的情况。通用框架我一般用实数编码因为工程优化问题大多是连续参数实数编码不需要解码交叉变异操作直接作用在实数上。种群初始化不能纯随机否则可行解比例可能为零。常用做法是先随机生成一批个体保留可行解对不可行解做修复或重新采样直到可行解数量达到种群规模的一定比例。如果可行域实在太小可以用拉丁超立方采样提高初始覆盖均匀性。function pop init_population(pop_size, n_var, lb, ub) % pop_size: 种群规模 % n_var: 变量维度 % lb/ub: 变量下界和上界 pop zeros(pop_size, n_var); for i 1:pop_size % 拉丁超立方采样每个维度分层随机 for j 1:n_var % 将 [0,1] 分成 pop_size 层每层随机取一点 layer (i - 1 rand) / pop_size; pop(i, j) lb(j) layer * (ub(j) - lb(j)); end end % 检查可行解比例不足则补充随机可行解 feasible_count 0; for i 1:pop_size if constraint_violation(pop(i, :)) 1e-6 feasible_count feasible_count 1; end end % 可行解比例低于 20% 时用随机采样补充 if feasible_count 0.2 * pop_size for i 1:pop_size if constraint_violation(pop(i, :)) 1e-6 % 尝试随机生成可行解最多尝试 100 次 for attempt 1:100 candidate lb rand(1, n_var) .* (ub - lb); if constraint_violation(candidate) 1e-6 pop(i, :) candidate; break; end end end end end endinit_population用拉丁超立方采样替代纯随机每个维度分层保证初始种群覆盖均匀。后面检查可行解比例低于 20% 时对不可行个体做随机重采样。attempt循环最多 100 次如果 100 次都找不到可行解说明可行域极小需要换约束处理策略或放宽初始可行解要求。lb和ub是变量上下界工程上根据参数物理意义设定。3.2 选择、交叉、变异算子的改进实现选择算子用锦标赛选择但比较规则改成可行性规则可行解优先都可行比目标值都不可行比违反量。交叉算子用模拟二进制交叉SBX变异用多项式变异这两个算子适合实数编码能产生局部搜索能力较强的子代。function [parent1, parent2] tournament_select(pop, fitness, viol_list, k) % k: 锦标赛规模 n size(pop, 1); idx randperm(n, k); % 可行性规则比较 best idx(1); for i 2:k cand idx(i); if viol_list(cand) 1e-6 viol_list(best) 1e-6 best cand; % 可行解优先 elseif viol_list(cand) 1e-6 viol_list(best) 1e-6 if fitness(cand) fitness(best) best cand; % 都可行比目标值 end elseif viol_list(cand) 1e-6 viol_list(best) 1e-6 if viol_list(cand) viol_list(best) best cand; % 都不可行比违反量 end end end parent1 pop(best, :); % 再选一个不同的 idx2 randperm(n, k); best2 idx2(1); for i 2:k cand idx2(i); if viol_list(cand) 1e-6 viol_list(best2) 1e-6 best2 cand; elseif viol_list(cand) 1e-6 viol_list(best2) 1e-6 if fitness(cand) fitness(best2) best2 cand; end elseif viol_list(cand) 1e-6 viol_list(best2) 1e-6 if viol_list(cand) viol_list(best2) best2 cand; end end end parent2 pop(best2, :); end function [child1, child2] sbx_crossover(parent1, parent2, eta_c, lb, ub) % eta_c: 交叉分布指数一般取 10-20 n length(parent1); child1 parent1; child2 parent2; for i 1:n if rand 0.5 if abs(parent1(i) - parent2(i)) 1e-10 if parent1(i) parent2(i) y1 parent1(i); y2 parent2(i); else y1 parent2(i); y2 parent1(i); end yl lb(i); yu ub(i); rand_val rand; beta 1 2 * (y1 - yl) / (y2 - y1); alpha 2 - beta^(-(eta_c 1)); if rand_val 1 / alpha betaq (rand_val * alpha)^(1 / (eta_c 1)); else betaq (1 / (2 - rand_val * alpha))^(1 / (eta_c 1)); end c1 0.5 * ((y1 y2) - betaq * (y2 - y1)); beta 1 2 * (yu - y2) / (y2 - y1); alpha 2 - beta^(-(eta_c 1)); if rand_val 1 / alpha betaq (rand_val * alpha)^(1 / (eta_c 1)); else betaq (1 / (2 - rand_val * alpha))^(1 / (eta_c 1)); end c2 0.5 * ((y1 y2) betaq * (y2 - y1)); c1 max(min(c1, yu), yl); c2 max(min(c2, yu), yl); if rand 0.5 child1(i) c2; child2(i) c1; else child1(i) c1; child2(i) c2; end end end end endtournament_select实现了可行性规则比较k是锦标赛规模一般取 2 到 4。sbx_crossover是模拟二进制交叉eta_c控制子代与父代的接近程度越大子代越接近父代。交叉后做了边界截断防止子代越界。这段代码里rand 0.5是每个维度独立决定是否交叉实际使用时可以改成整体交叉概率控制。3.3 主循环精英保留与收敛判据主循环把初始化、适应度评估、选择、交叉、变异、精英保留串起来。精英保留策略是每代把最优的几个个体直接复制到下一代防止最优解被交叉变异破坏。收敛判据用“最优适应度连续多代变化小于阈值”或“达到最大代数”。function [best_x, best_f, history] improved_ga(pop_size, n_var, lb, ub, max_gen) % 主函数改进遗传算法求解带约束优化问题 pop init_population(pop_size, n_var, lb, ub); history zeros(max_gen, 1); best_x []; best_f inf; eta_c 15; % 交叉分布指数 eta_m 20; % 变异分布指数 pm 1 / n_var; % 变异概率 elite_num 2; % 精英保留数量 for gen 1:max_gen % 评估适应度和约束违反量 fitness evaluate_fitness(pop, 100, gen, max_gen); viol_list zeros(pop_size, 1); for i 1:pop_size viol_list(i) constraint_violation(pop(i, :)); end % 记录最优 [min_f, min_idx] min(fitness); history(gen) min_f; if min_f best_f viol_list(min_idx) 1e-6 best_f min_f; best_x pop(min_idx, :); end % 精英保留 [~, sort_idx] sort(fitness); new_pop pop(sort_idx(1:elite_num), :); % 生成剩余个体 while size(new_pop, 1) pop_size [p1, p2] tournament_select(pop, fitness, viol_list, 3); [c1, c2] sbx_crossover(p1, p2, eta_c, lb, ub); % 多项式变异 c1 poly_mutation(c1, eta_m, pm, lb, ub); c2 poly_mutation(c2, eta_m, pm, lb, ub); new_pop [new_pop; c1; c2]; end pop new_pop(1:pop_size, :); % 随机移民每 10 代替换最差 5% 个体 if mod(gen, 10) 0 replace_num max(1, round(0.05 * pop_size)); for i 1:replace_num pop(end - i 1, :) lb rand(1, n_var) .* (ub - lb); end end end end function x poly_mutation(x, eta_m, pm, lb, ub) % 多项式变异 n length(x); for i 1:n if rand pm y x(i); yl lb(i); yu ub(i); delta1 (y - yl) / (yu - yl); delta2 (yu - y) / (yu - yl); rand_val rand; mut_pow 1 / (eta_m 1); if rand_val 0.5 xy 1 - delta1; val 2 * rand_val (1 - 2 * rand_val) * xy^(eta_m 1); deltaq val^mut_pow - 1; else xy 1 - delta2; val 2 * (1 - rand_val) 2 * (rand_val - 0.5) * xy^(eta_m 1); deltaq 1 - val^mut_pow; end y y deltaq * (yu - yl); y max(min(y, yu), yl); x(i) y; end end endimproved_ga是主循环elite_num控制精英保留数量eta_c和eta_m是交叉和变异分布指数pm是变异概率。随机移民每 10 代执行一次替换最差的 5% 个体维持种群多样性。poly_mutation是多项式变异eta_m越大变异幅度越小。注意evaluate_fitness里的惩罚系数基数 100 需要根据目标函数量级调整这里只是示例值。4. 避坑与排查带约束遗传算法最常见的 5 个翻车现场4.1 种群全部不可行算法原地踏步现象初始种群可行解比例为零进化多代后最优个体违反量仍然很大目标函数值几乎不变。原因可行域太小随机初始化根本碰不到可行解或者惩罚系数太小不可行解的适应度反而比可行解好选择压力方向反了。解决初始化用拉丁超立方加可行解重采样惩罚系数基数调大或者改用可行性规则做选择如果可行域确实极小先用修复法把不可行解投影到边界附近再让遗传算法在边界附近搜索。4.2 早熟收敛种群多样性在第 20 代就崩了现象最优适应度快速下降后停滞种群中个体几乎一模一样交叉变异产生不了新解。原因选择压力过大精英保留数量太多或者交叉概率太低。带约束问题可行域小种群容易挤在局部最优附近。解决降低精英保留数量到 1 到 2 个提高变异概率或者用自适应变异概率每代注入随机移民用拥挤度距离做小生境淘汰保持种群分散。4.3 等式约束永远不满足违反量卡在 0.01 下不去现象不等式约束都满足了等式约束违反量始终在 0.01 量级降不到 1e-6。原因等式约束在遗传算法里很难精确满足因为交叉变异是随机操作很难恰好落在等式超平面上。惩罚系数对等式约束不够大或者等式约束本身不可行。解决等式约束用修复法处理把违反等式约束的个体投影到等式超平面上或者把等式约束松弛成不等式约束允许一个小容差检查等式约束是否本身矛盾如果矛盾需要重新建模。4.4 惩罚系数调参玄学调大了不收敛调小了不可行现象惩罚系数取 10 时不可行解泛滥取 10000 时算法在可行域边界震荡目标函数降不下去。原因固定惩罚系数无法适应进化不同阶段的需求。前期需要小惩罚鼓励探索后期需要大惩罚收紧约束。解决用动态惩罚系数随代数递增或者用自适应惩罚根据种群可行解比例调整惩罚力度最稳的是可行性规则加小惩罚的混合策略不依赖惩罚系数调参。4.5 结果每次跑都不一样无法复现现象同样的代码和参数每次运行结果差异很大有时找到好解有时找不到。原因遗传算法是随机算法没有固定随机种子种群初始化、交叉变异、选择都依赖随机数。解决在代码开头固定随机种子rng(42)记录每次运行的最优解和收敛曲线用多次独立运行取最优的方式提高可靠性如果结果方差太大说明算法稳定性不够需要增加种群规模或代数。5. 进阶技巧用多次重启与参数扫描把通用框架用稳通用框架跑通之后真正决定工程可用性的是稳定性和调参效率。我一般会做两件事多次重启和参数扫描。多次重启的思路很简单用不同随机种子独立运行 N 次取最优解。N 一般取 10 到 30根据问题难度定。每次运行的种群规模和代数可以适当减小总计算量不变但找到全局最优的概率显著提高。代码上把improved_ga包一层循环记录每次的最优解和收敛代数。function [best_x, best_f, all_history] multi_restart_ga(n_run, pop_size, n_var, lb, ub, max_gen) % 多次重启遗传算法 best_f inf; best_x []; all_history zeros(max_gen, n_run); for run 1:n_run rng(run * 100); % 每次运行不同随机种子 [x, f, history] improved_ga(pop_size, n_var, lb, ub, max_gen); all_history(:, run) history; if f best_f best_f f; best_x x; end fprintf(Run %d: best_f %.6f\n, run, f); end endmulti_restart_ga里rng(run * 100)保证每次运行种子不同但可复现。all_history记录每次运行的收敛曲线方便后面画图分析。fprintf输出每次运行的最优值观察方差。如果 30 次运行里有 20 次以上找到相近的最优值说明算法稳定如果方差很大需要增加种群规模或调整算子参数。参数扫描是另一个实用技巧。带约束遗传算法的关键参数有种群规模、最大代数、交叉分布指数、变异分布指数、变异概率、精英保留数量、惩罚系数基数。全组合扫描计算量太大我一般用单因素扫描加局部组合。先固定其他参数扫描种群规模从 50 到 500看最优值和收敛代数怎么变再扫描变异概率从 0.01 到 0.1最后在较优区间做小范围组合。参数推荐范围影响种群规模100-300太小早熟太大计算慢最大代数200-1000看收敛曲线平稳后再跑 100 代交叉分布指数10-20越大子代越接近父代变异分布指数15-25越大变异幅度越小变异概率1/n_var 到 5/n_var太小探索不足太大破坏优良解精英保留数量1-3太多早熟太少不稳定惩罚系数基数目标值标准差的 1-10 倍太小不可行解泛滥太大边界震荡这张表是我做参数标定时的经验范围具体问题需要微调。扫描时每次只改一个参数记录最优值和收敛代数画成曲线看趋势。如果某个参数在很大范围内结果都差不多说明算法对这个参数不敏感可以固定一个中间值如果结果对某个参数很敏感说明这个参数需要精细调。最后说一个我踩过的坑有一次做结构优化约束是应力不超过许用值目标是最轻重量。标准遗传算法跑了 500 代最优解重量比初始设计还重。排查发现是惩罚系数基数设成了 1不可行解的适应度比可行解还好选择压力完全反了。把惩罚系数基数改成目标函数初始种群标准差的 5 倍同时加上可行性规则选择200 代就收敛到了可行最优解。从那以后我养成了一个习惯每次跑遗传算法之前先打印初始种群的可行解比例和目标函数值分布确认选择压力方向没反再开始正式运行。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取