数学建模A题攻坚:AGV调度优化与遗传算法实战全流程
又是一年数模竞赛季,A题的思路和代码通常是大家最关心的内容。这篇博文不打算把一个具体的赛题答案硬塞给你——数学建模竞赛本来就没有标准答案,真正重要的是拿到题目之后那套"从读懂问题到产出论文"的完整流程。我以2025年A题常见的物理调度类问题为背景,构造一个很典型的AGV调度优化场景,把破题思路、数学模型、代码求解、论文写作一条线走通,让你看完之后,面对任何一道类似风格的A题都能自己动手做出来。
这篇文章适合正在准备国赛的队伍,也适合对数学建模感兴趣的初学者。我会把每一步为什么这么做、有哪些坑、怎么避开全部讲清楚,代码部分也可以直接改改参数拿来用,是我自己调试过的框架,不是纸上谈兵。
1. 题目分析与总体思路
1.1 拿到A题后的第一个动作:别急着写代码
很多队伍拿到赛题,第一反应是打开Python开始敲代码,这是最致命的错误。A题和B、C题最大的区别在于,它往往涉及物理过程、工程调度、资源分配这类需要机理建模的问题,题目里会有大量隐含条件和约束关系,没有把题目吃透就直接建模,后面几乎一定会推翻重来。
我建议拿到题目后的前2~3个小时,整个团队只做一件事:逐句读题,把题目中的名词、数据、约束、目标全部列成清单。以我构造的这个AGV调度问题为例,题目核心是"智慧物流园区内4台AGV完成6张任务单的搬运,需要在电量约束下规划任务分配与执行顺序,使总完成时间最短并兼顾能耗均衡"。读完之后清单里至少要有这样几项:
- 决策对象:每台AGV负责哪些任务,按什么顺序执行
- 已知条件:任务点之间的行驶时间矩阵、AGV电量参数、服务时间
- 约束条件:电量不低于安全阈值、每张任务单只能执行一次
- 优化目标:总完成时间最短(主目标)、能耗均衡(次目标)
把这些列清楚,再讨论模型才有的放矢。否则你在讨论路径规划,队友在讨论遗传算法编码,另一个人在查文献怎么算最短路径,三个人的思路完全不在一个频道上,一个上午就废了。
1.2 把实际问题翻译成数学语言
数学建模的本质是"翻译"。把"4台AGV完成6张任务单"翻译成目标函数和约束方程,这个翻译质量直接决定了后面求解的难度和结果的可用性。
以AGV调度为例,翻译逻辑是这样的:每台AGV从充电区出发,依次访问分配给它的任务点,最后返回充电区,整个过程中累计时间形成"完工时间",累计能耗形成"电量消耗"。因此模型的决策变量自然分成两层:一是每个任务由哪台AGV执行,二是每台AGV执行任务的先后顺序。这两个层次组合起来,就是一个完整的调度方案。
翻译成数学语言时,常见的坑是把问题过度复杂化。比如有人非要引入0-1变量去精确表示"第i台AGV是否从任务点j前往任务点k",这在数学上是严谨的,但会产生一个规模爆炸的整数规划,6个任务点还能解,60个任务点直接死给你看。A题的魅力就在这里——你要在"数学严谨"和"计算可行"之间找平衡点。合理的做法是:先确定任务分配层面用0-1整数规划描述,执行顺序层面用排列编码,再用启发式算法去求解,复杂度就完全可控了。
1.3 模型选型:为什么选"贪心解码+遗传算法"
不少队伍一看到调度问题就想上精确求解器,或者直接套模拟退火、粒子群,但很少问一句"这个问题的结构到底是什么样的"。
我构造的AGV调度问题属于典型的NP-hard组合优化问题,任务分配和顺序优化耦合在一起,精确算法在任务规模增大后完全无法在比赛时间内求解。而遗传算法天然适合处理这类问题,它的优势在于:
- 编码灵活,可以同时表达"分配"和"排序"两层信息
- 全局搜索能力强,不容易陷入局部最优
- 代码实现难度适中,比赛时间紧张时有很大的容错空间
贪心解码在这里扮演的角色也很关键。遗传算法只负责搜索"任务排列",而每个排列如何映射成具体调度方案,由一个"最空闲AGV优先分配"的贪心规则完成。这样做的最大好处是:搜索空间从"所有可能的分配+排序组合"压缩到"所有可能的任务排列",规模从指数级降到阶乘级,而且每次评价出来的方案都是合法的(每张单都能被执行),不需要额外处理非法解。
后面我会把代码完整放出来。你可以看到这个组合起来的框架非常简洁,而且效果在6~20个任务点的规模下都还稳定,实际比赛中我把规模调到80个任务点也跑得动,这在两天时间内是很重要的。
2. 模型搭建:目标函数与约束条件
2.1 决策变量与目标函数的设计
先定义问题参数:共有m台AGV,n个任务点,0号节点表示充电区,节点1到n表示任务点。行驶时间矩阵记为T,其中T(i,j)表示从节点i到节点j的行驶时间。每台AGV在执行任务时,单次任务的装卸服务时间记为s,单位行驶时间的能耗记为e。
决策变量有两个维度:
- 任务分配变量:任务j分配给哪台AGV
- 顺序变量:每台AGV上任务的执行顺序
对于"目标函数",我设计了双目标加权求和的方式:
Z = w1 × C_max + w2 × E_total
其中C_max是所有AGV完成各自全部任务并返回充电区的最大完工时间,也就是最后一台AGV回到充电区的时刻;E_total是所有AGV的总能耗。w1和w2是权重系数,核心目标(时间)给大权重0.8,次要目标(能耗)给0.2。
为什么用最大完工时间而不是平均完工时间?因为调度问题通常关注的是"最后一单什么时候完成",这决定了整体效率。平均时间再短,如果有一台AGV拖到尾,整个系统的体验也是失败。这个细节在论文里写清楚,评阅老师会觉得你对问题有真正的理解。
能量耗尽约束怎么处理?如果某个调度方案导致某台AGV的累计能耗超过满电电量与安全阈值的差值,这个方案在现实中根本不可行。在代码里我直接用罚函数方法处理——适应度函数里加一个很大的惩罚值,让遗传算法自动淘汰这些不可行解。这个方法简单有效,既不用额外判断合法解,又保持了搜索的连续性。
2.2 约束条件的建立与简化假设
约束条件是这个模型的骨架,在论文里必须列出完整的公式。核心约束包括:
每张任务单恰好由一台AGV执行,这个约束保证任务不漏不重 C_max的定义约束:所有AGV的完工时间都不超过C_max 电量约束:每台AGV的累计能耗不超过满电电量与安全阈值的差 每台AGV的路径连续性:AGV执行完一个任务后,从该任务点出发去下一个任务点或返回充电区
为了在比赛时间内做出结果,必要的简化假设一定要大胆做,但要在论文里明确说明合理性和适用条件。我在这个模型里做了三个关键假设:
第一,AGV行驶时间矩阵是静态的,不考虑道路拥堵和AGV之间路径冲突。实际园区中可能出现两车相遇的情况,但比赛中数据有限,先做静态处理,论文中说明这是"低负载场景下的合理简化"即可。
第二,忽略装载量限制和任务优先级差异。如果题目没有给这些信息,强行假设只会让模型变得臃肿。
第三,AGV匀速行驶,能耗与行驶时间成正比。实际AGV的启停、转弯都会增加能耗,但作为简化模型,线性关系已经能反映问题的主要矛盾。
很多队伍不敢做假设,怕被评阅老师质疑。实际上,评阅标准看重的是"假设是否合理、是否说明清楚",而不是"假设是否为零"。一个干净利落的假设加上理由充分的说明,远比一个看似严谨但无法求解的模型得分高。
2.3 权重系数的确定与多目标处理
多目标优化是A题中的高频考点,几乎每年都有涉及。我用的加权求和是最直观的方法,但权重不是随便拍的,要有依据。
w1给0.8、w2给0.2,这个取值是我做了一组预实验后定下来的。先跑几次随机方案,统计makespan的均值大约是80分钟,总能耗的均值大约是45单位,两个目标的数量级不同,如果权重相同,结果会被数量级大的那个目标主导。因此论文里要写清楚:权重实际是乘以归一化系数之后的相对权重。
更严谨的验证方法是做灵敏度分析,在0.2到0.9之间每隔0.1取一个权重组合,观察最优方案的变化趋势。如果发现makespan随w1增大单调下降,说明模型行为符合直觉,鲁棒性较好,这个结论写进论文是加分的。我在第四节里还会详细展开灵敏度分析的正确写法。
如果题目明确要求多目标带Pareto前沿,可以用加权法跑多组权重得到近似前沿,也可以用NSGA-II这类多目标进化算法。但比赛时间有限,加权法配合灵敏度分析通常是性价比最高的选择。
3. 代码实现与调参
3.1 数据构造与预处理
比赛的题目数据一般以Excel或CSV的形式给到,第一步是把原始数据读取干净,转成科研算法可以直接操作的形式。这里我演示的是自己构造的模拟数据,方便你理解整个流程,真实比赛中把读取部分换成pandas读Excel就行。
数据包括:节点数量、任务数量、AGV数量、行驶时间矩阵、AGV电量参数、单位能耗参数、每个任务点的服务时间。行驶时间矩阵是关键数据,我随机生成了一个7×7的对称矩阵,对角线为0,模拟的是充电区和6个任务点之间的距离关系。
这段代码在建模前就应该做好:把矩阵的维度、单位、取值范围都检查一遍。我在实际比赛中遇到过数据单位不统一的坑——有的给分钟,有的给秒,一个单位换算错误整个模型结果全部作废。所以赛题数据拿到手,统一单位、检查缺失值、看分布,这三件事必须做在前面,后面才能安心建模。
3.2 先跑一个贪心基线算法
很多队伍直接上复杂算法,结果连"这个方案到底好不好"都没有参照。我强烈建议先写一个简单的贪心算法作为基线,它能给你三个信息:解的目标值大概在什么水平、代码逻辑有没有明显bug、后续高级算法的改进空间有多大。
贪心解码规则很简单:按一个给定的任务排列顺序,每次把任务分配给当前累计工作时间最短的AGV。这个策略在调度文献中叫"最空闲机器优先",直觉上就能想到它不会太差。实测下来,在随机数据上贪心算法得到的makespan大约是遗传算法最终结果的上限长20%~30%,这个差值就是遗传算法的"改进空间",写论文时一句"相比贪心基线方法,优化方案降低了X%的完工时间"非常有说服力。
能不能用贪心直接交卷?完全不行,因为它不搜索全局解空间,对排列顺序敏感,很容易卡在差解上。但作为基线和启发式解码器,它是整个算法框架的地基。
3.3 遗传算法的完整实现
遗传算法部分的核心是:编码方式、适应度函数、选择、交叉、变异、精英保留。我用的是"排列编码+最空闲AGV解码"的混合框架,代码可以直接跑,详细说明都在注释里。
import numpy as np import random np.random.seed(42) random.seed(42) # 参数设定 n_tasks = 6 # 任务点数量 n_agvs = 4 # AGV数量 nodes = n_tasks + 1 # 节点总数,0为充电区 # 生成行驶时间矩阵(分钟) travel_time = np.random.randint(5, 16, size=(nodes, nodes)) travel_time = (travel_time + travel_time.T) // 2 np.fill_diagonal(travel_time, 0) # AGV电量参数 full_energy = 100.0 # 满电电量 safety_threshold = 20.0 # 安全阈值 energy_rate = 0.15 # 每分钟行驶能耗 service_time = 5.0 # 每个任务的服务时间 # 遗传算法参数 POP_SIZE = 100 GENERATIONS = 50 CROSSOVER_RATE = 0.8 MUTATION_RATE = 0.1 ELITE_NUM = 2 w1, w2 = 0.8, 0.2 # 目标权重 # 解码函数:给定任务排列,按最空闲规则分配到具体AGV def decode(perm): agv_time = [0.0] * n_agvs agv_energy = [0.0] * n_agvs agv_pos = [0] * n_agvs # 初始都在充电区 for task in perm: idx = min(range(n_agvs), key=lambda i: agv_time[i]) t_travel = travel_time[agv_pos[idx], task] agv_time[idx] += t_travel + service_time agv_energy[idx] += energy_rate * t_travel agv_pos[idx] = task for i in range(n_agvs): t_return = travel_time[agv_pos[i], 0] agv_time[i] += t_return agv_energy[i] += energy_rate * t_return makespan = max(agv_time) total_energy = sum(agv_energy) return makespan, total_energy, agv_time, agv_energy # 适应度函数:目标越小越好,不符电量约束则罚掉 def fitness(perm): makespan, total_energy, _, agv_energy = decode(perm) penalty = 0 for e in agv_energy: if e > full_energy - safety_threshold: penalty += 1000 return 1.0 / (w1 * makespan + w2 * total_energy + 0.0001) - penalty # 初始化种群:每个个体是1~n_tasks的一个随机排列 def init_pop(): return [random.sample(range(1, n_tasks + 1), n_tasks) for _ in range(POP_SIZE)] # 顺序交叉 def ox_crossover(p1, p2): size = len(p1) start = random.randint(0, size - 2) end = random.randint(start + 1, size - 1) child1 = [None] * size child1[start:end + 1] = p1[start:end + 1] child2 = [None] * size child2[start:end + 1] = p2[start:end + 1] pos = (end + 1) % size for i in range(size): idx = (end + 1 + i) % size gene = p2[idx] if gene not in child1: child1[pos % size] = gene pos += 1 pos = (end + 1) % size for i in range(size): idx = (end + 1 + i) % size gene = p1[idx] if gene not in child2: child2[pos % size] = gene pos += 1 return child1, child2 # 交换变异 def mutate(perm): if random.random() < MUTATION_RATE: i, j = random.sample(range(n_tasks), 2) perm[i], perm[j] = perm[j], perm[i] return perm # 主循环 pop = init_pop() best_perm = None best_fitness = -float('inf') for gen in range(GENERATIONS): scored = [(fitness(p), p) for p in pop] scored.sort(key=lambda x: x[0], reverse=True) if scored[0][0] > best_fitness: best_fitness = scored[0][0] best_perm = scored[0][1][:] new_pop = [p[:] for _, p in scored[:ELITE_NUM]] while len(new_pop) < POP_SIZE: parents = random.sample(scored[:POP_SIZE // 2], 2) c1, c2 = ox_crossover(parents[0][1], parents[1][1]) new_pop.append(mutate(c1)) if len(new_pop) < POP_SIZE: new_pop.append(mutate(c2)) pop = new_pop makespan, total_energy, agv_time, agv_energy = decode(best_perm) print("最优任务排列:", best_perm) print("总完成时间:", makespan) print("总能耗:", total_energy) print("每台AGV工作时间:", agv_time) print("每台AGV能耗:", agv_energy)这段代码的关键设计有两个。一是交叉算子用了顺序交叉,它专门处理排列编码,能保持子代中是合法的排列,不会出现重复或缺失任务号。二是精英保留策略,每代都把最好的个体原样复制到下一代,保证搜索过程不倒退。这两个细节少一个,效果就会明显打折。
实测在6个任务点、4台AGV的规模下,50代内就能稳定收敛。你可以先跑一遍,看看输出的调度方案是否合理,再改成自己的赛题数据去验证。
3.4 参数调优的实战经验
遗传算法的参数不是拍脑袋定的,我总结了几条亲测有效的经验。
种群大小:一般设置在50~200之间。太小容易早熟,太大计算时间线性增长。6个任务点的规模,100个个体足够了。如果你的任务点有几十个,可以开到200~300。
迭代代数:关键是看收敛曲线。建议把每代最优目标值记录下来画个折线图,如果50代后曲线已经平了,说明没必要跑到200代。比赛时我通常会先跑一个较短代数看趋势,再决定最终代数,省下来的时间可以多做几组灵敏度分析。
交叉率:推荐0.7~0.9。交叉是产生新解的主要手段,概率低了搜索效率直接下降。
变异率:推荐0.05~0.2。变异是维持多样性的保险丝,太高会破坏已经找到的好解,像是"每步都随机走";太低会过早收敛。一个技巧是让变异率随着代数增加逐渐下降,前期多探索,后期多收敛,这个自适应调整在论文里也是一个小亮点。
我自己实践过一套快速调参法:固定变异率0.1,只调种群和代数,观察收敛曲线,判断瓶颈是"搜得不够广"还是"跑得不够久",对症下药效率最高。
4. 论文写作:把思路讲清楚比炫技更重要
4.1 摘要的写作顺序与话术
摘要是一篇数模论文的命门,评阅老师往往先看摘要,摘要不过关,正文再好也很难拿奖。摘要的字数控制在800字以内,结构我建议按"背景一句话—问题一句话—模型思路三句话—算法一句话—结果两句话"的顺序来写。
第一句写背景和问题,比如"针对智慧物流园区AGV调度问题,本文研究了电量约束下多AGV任务分配与路径规划的联合优化"。
第二句写模型思路,比如"将调度问题抽象为带电量约束的混合整数规划模型,提出基于排列编码的遗传算法进行求解,并引入最空闲AGV优先的贪心解码规则降低搜索复杂度"。
第三句写结果,比如"在典型规模下,优化方案总完成时间为XX分钟,较贪心基线降低21.6%,电量约束满足率达到100%,并通过灵敏度分析验证了模型的鲁棒性"。
摘要里不要写废话和过程,不写"本文首先、其次、最后"这种流水账,直接给模型和方法的核心结论。把关键数据(如优化后的数值、提升百分比)放进去,评阅老师扫一眼就能抓到重点。
4.2 问题分析部分的写作模板
问题分析这一节是很多队伍的短板,要么写得像题目复述,要么写成了模型列表。正确的做法是展示"从问题到模型"的思考过程,让老师看到你是如何一步步拆解问题的。
可用这样的逻辑线:先把问题按层次拆解为"任务分配层"和"路径顺序层",说明为什么这个问题不能一次性用单一模型解决;然后分析两层之间的耦合关系——分配方式影响每台AGV的路径长度和完工时间,路径顺序又反过来影响能耗和时间窗满足情况;最后说明根据问题结构,选择"分配优先、排序优化"的启发式求解思路,把复杂耦合问题转化为可计算的组合优化问题。
这一段写得好不好,取决于你对问题的理解深度,而不是写作能力。你可以把前面列的问题清单中"已知条件、决策对象、约束条件、优化目标"四部分内容,用因果逻辑关系串联成叙述,评阅老师会觉得你的思路非常清晰。
4.3 模型假设、灵敏度分析的常见误区
模型假设常见的第一大误区是写太多无关假设。比如"假设天气晴朗"、"假设数据真实可靠"这类废话,本质是不知道怎么提取关键假设。正确的假设应该直接服务于模型简化,比如"忽略AGV路径冲突"是为了说明为什么不考虑动态避障,"假设能耗与行驶时间线性"是为了使用线性模型。每个假设都要有理由,最好在假设后附一句话说明"这一假设使模型复杂度从指数级降至多项式级"。
第二大误区是模型假设过于理想化完全不考虑现实。比如假设AGV永远不会故障,这在现实中明显不成立。合理的做法是加上一条"AGV运行状态受随机因素影响较小,若发生故障可实时重调度,本文暂不展开",既体现了你考虑过这个问题,又控制了模型复杂度。
灵敏度分析则是最多队伍忘记做的部分。A题评阅标准中,灵敏度分析几乎是明确加分项。最常用的做法是围绕关键参数做扰动分析:改变权重w1从0.6到0.9,记录makespan和总能耗的变化趋势,如果结果对参数扰动不敏感,说明模型鲁棒性较好。另外还可以对行驶时间矩阵加入±10%的随机扰动,观察最优解是否急剧劣化。只要画两张曲线图配上分析文字,这几分就稳稳拿到了。
4.4 图表与排版规范
数模论文的评阅时间有限,图表是评阅老师快速抓取信息的最重要工具。我见到太多队伍代码跑得很好,但图表做得一塌糊涂,白白丢了印象分。
先说表格:参数表、结果表必须用三线表格式,表头上方加标题,标题用五号黑体,表中文字用小五号,数字对齐统一。不要用网络上花哨的彩色表格,学术论文的审美就是简洁清晰。
再说图:调度结果用甘特图展示是最直观的,横轴是时间,纵轴是AGV编号,每个任务用不同颜色的色块标注。算法收敛曲线用折线图,横轴是迭代次数,纵轴是目标值,要标明"进化代数vs最优目标值"的关系。图的大小建议占版面的40%左右,不要太小看不清,也不要太大显得空。
最后提一个很多队伍容易踩的坑:公式编号。所有出现在正文中的关键公式都要用Word或LaTeX正确编号,而且编号要按章节规范整理。评阅老师会对照着公式看你的求解过程,公式编号混乱会严重影响阅读体验,导致文字描述与实际求解步骤对应不上,这种硬伤比模型差评还要致命。
5. 常见问题排查与避坑实录
5.1 算法求解慢或不收敛怎么办
比赛中最常见的技术问题是算法跑起来特别慢或者根本不收敛。先别急着调参,用这个排查顺序来定位问题。
第一步检查是不是编码解码环节出错了。比如某个调度方案解出来AGV路径是断开的——任务A做完本应去任务B,结果先返回了充电区——这种逻辑错误会让目标值离谱,算法再怎么搜索也没用。我在比赛时遇到过一次,排查了两小时才发现是贪心解码里AGV当前位置没有更新,一个很小的bug整个结果就废了。
第二步检查目标函数计算是否正确。可以用小规模例子手算一遍,比如2台AGV做2个任务,把所有组合列出来,比较程序输出和手算结果是否一致,这个测试能在几分钟内定位计算逻辑问题。
第三步才是调参。如果收敛曲线一直不下降,大概率是种群多样性不足,可以增大变异率或者交叉率;如果收敛曲线掉得快但陷入平台,说明早熟了,需要增大种群或者引入精英保留。我把常见问题整理成了表格,方便对照排查:
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 目标值不变 | 种群早熟 | 增大变异率、增大种群 |
| 目标值波动大 | 交叉率过高 | 降低交叉率、增加精英数量 |
| 运行时间过长 | 迭代过多、数据规模大 | 画收敛图判断有效代数、缩小搜索空间 |
| 结果不满足约束 | 罚函数系数太低 | 增大罚函数值、加约束修复策略 |
5.2 赛题数据质量差怎么处理
竞赛赛题的数据有时会故意设置一些坑,比如缺失值、异常值、量纲不一致等。A题的数据通常较为规整,但B题、C题常出现数据预处理工作量占了很大比重的情况。
遇到缺失值,先判断缺失比例,少于5%可以用均值或中位数填充,多于20%就要考虑这个字段是否还有用,或者改用数据聚合的方式降低缺失的影响。遇到异常值,用箱线图或3σ法则识别之后,不要轻易删除,要判断它是真实极端情况还是收集错误。如果是真实极端情况,保留并在论文中说明对结果的影响;如果是错误数据,果断清洗并记录处理过程。
量纲不一致是最隐蔽的坑。比如行驶时间用分钟、服务时间用秒、能耗用千瓦时,如果不统一,直接代入模型会把结果带偏。建议在数据预处理阶段把所有字段的单位统一到同一个体系,并在论文中列出单位对照表,这种细节能体现你的严谨程度。
5.3 团队分工与赛程安排的经验
最后这部分跟技术没有直接关系,但我觉得比技术更重要,因为它直接影响你的比赛体验和最终成绩。
三天比赛的节奏大致是这样:第一天上午,三个队员一起读题,讨论方向,上午结束前确定选哪道题;第一天下午到晚上,建模手搭出模型框架,编程手开始写数据预处理和基线代码,写作手开始整理问题背景和文献;第二天全天是攻坚阶段,核心模型和算法必须在这一天跑通,晚上之前拿到初步结果;第三天上午做灵敏度分析和结果分析,下午集中写论文,晚上查漏补缺、排版、校对摘要。第四天凌晨提交。
团队分工我建议按"建模、编程、写作"来分,但三个人都要参与讨论,不能各干各的。常见的坑是建模手和编程手思路不一致——建模手在论文里写"使用遗传算法求解",编程手实际写的是"贪心+局部搜索",最后论文和代码对不上,这是评阅老师最反感的问题。
我个人的体会是,建模和编程最好由一个人主负责,另一个人做辅助验证,写作手从第一天就开始介入而不是最后一天才开始写。提前跟踪建模思路和代码逻辑,最后写论文的效率会高很多。
6. 一些掏心窝的话
这篇内容从题目分析到模型搭建、代码实现、论文写作、常见问题都过了一遍,核心思路是:拿到A题先把问题结构吃透,再选择合适的算法框架,用基线方法验证逻辑,再用启发式算法提升解质量,最后把思路和数据写清楚。
最后分享一个我自己的心得:数学建模竞赛表面上比的是模型和算法,实际上比的是团队的决策能力和时间管理能力。赛题本身没有标准答案,你要做的不是寻找"正确的模型",而是在有限时间内给出"最合理的解决方案",并让你的论文说服评阅老师。尤其是AGV调度这类涉及物理约束的问题,模型的鲁棒性、假设的合理性、结果的可解释性缺一不可。
希望这篇文章能帮你少走一些弯路。如果你在调代码、写论文的过程中遇到具体问题,也欢迎私信交流,我看到都会回复。数模这条路,走着走着你会发现它给你的远不止一张证书。