数学建模竞赛实战:Python实现2021年C题完整代码与优化求解

发布时间:2026/10/26 2:05:55

数学建模竞赛实战:Python实现2021年C题完整代码与优化求解 简介这份资料面向备战全国大学生数学建模竞赛的参赛者与指导教师聚焦2021年C题提供从解题思路、模型构建到算法实现的完整参考适合需要复盘赛题、学习建模方法或提升MATLAB编程能力的学习者。压缩包共27个文件约996KB以13个.m脚本、10个.mat数据文件和4个.xlsx表格为主脚本对应各小问的求解实现数据文件保存中间结果便于复现表格则涵盖供应商与转运商原始数据及订购、转运方案结果。目前已有2310人学习。资料完整呈现了理解问题、建立模型、求解验证到结果分析的全过程读者可对照代码理解数学理论如何转化为实际计算借助数据文件复现结果并通过方案表格体会模型在实际场景中的应用对系统掌握建模流程具有较高参考价值。1. 从一份“能跑通”的建模资料说起数学建模比赛里最让人心里没底的时刻不是拿到题目看不懂而是看懂题目、也定了思路打开编辑器却不知道第一行代码该写什么。2021 年 C 题那道关于原材料订购与转运的问题本质是一个多目标优化加决策排序的组合题光靠手算根本推不动必须落到代码上。这份资料把整道题的完整代码、数据处理脚本和结果输出都打包在一起适合两类人一是正在准备建模竞赛、想拿一道真题从头到尾跑一遍练手的同学二是带队伍、需要一份能直接改参数复用的代码底子的指导者。它不是一个只放论文的压缩包而是能让你把模型真正跑出数字来的工程化资料。2. 项目结构与技术选型这道题为什么用 Python 而不是 MATLAB2.1 目录组织与文件职责拿到这份资料第一件事不是急着运行而是先看清目录。常见的组织方式是按“数据—模型—结果”三层拆开这样二次开发时改动范围可控不会牵一发动全身。典型的目录结构大致如下C2021/ ├── data/ │ ├── raw/ # 原始附件数据保持只读 │ │ ├── supply.csv # 供应商供货能力与价格 │ │ └── transport.csv # 转运商运力与损耗 │ └── processed/ # 清洗后的中间数据 ├── src/ │ ├── preprocess.py # 数据清洗与特征构造 │ ├── model_q1.py # 第一问供应商重要性排序 │ ├── model_q2.py # 第二问最少供应商方案 │ ├── model_q3.py # 第三问转运方案优化 │ └── utils.py # 公共函数读数据、写结果 ├── output/ │ ├── q1_result.csv │ ├── q2_result.csv │ └── q3_result.csv ├── requirements.txt └── run_all.py # 一键跑通入口这个结构的关键在于raw目录只读、processed目录可重建。我一般会强制自己遵守这条原始数据永远不覆盖所有清洗结果都能从raw重新生成。这样一旦模型结果对不上可以先把processed删掉重跑排除数据污染这个变量。run_all.py作为入口把三个问题的脚本按顺序串起来避免手动一个个执行时漏掉某一步。2.2 技术栈选择numpy pandas scipy 的组合逻辑这道题的核心计算是线性规划、整数规划和排序评价技术栈上最省事的是 Python 的numpy做矩阵运算、pandas做表格处理、scipy.optimize做规划求解。为什么不用 MATLAB不是 MATLAB 不行而是这份资料面向的是要交付、要复现的场景Python 的依赖可以用requirements.txt锁死别人拿到手pip install -r就能还原环境而 MATLAB 的版本和工具箱差异经常让复现翻车。依赖清单大致是numpy1.21 pandas1.3 scipy1.7 matplotlib3.4 openpyxl3.0scipy.optimize.linprog负责连续变量规划milp较新版本或pulp负责整数规划。如果资料里用的是pulp那requirements.txt里会多一行pulp2.4。这里有个血泪经验linprog在旧版本里默认方法是单纯形法新版本改成了内点法同一个模型在不同版本下可能给出不同精度的解所以锁版本不是形式主义是保证结果可复现的后悔药。2.3 数据流从原始附件到模型输入的转换原始附件通常是 Excel 或 CSV字段名带中文、单位不统一、还有空值。preprocess.py的职责就是把这些脏数据变成模型能直接吃的数值矩阵。核心逻辑一般是这样import pandas as pd import numpy as np def load_supply(path): # 读取供应商数据统一列名去掉单位字符 df pd.read_csv(path, encodingutf-8) df.columns [supplier_id, capacity, price, loss_rate] # 供货能力转数值异常值置为 0 df[capacity] pd.to_numeric(df[capacity], errorscoerce).fillna(0) df[price] pd.to_numeric(df[price], errorscoerce).fillna(df[price].median()) return df def build_matrix(df): # 构造 供应商 x 周次 的供货能力矩阵 weeks sorted(df[week].unique()) mat np.zeros((df[supplier_id].nunique(), len(weeks))) for _, row in df.iterrows(): mat[row[supplier_id], weeks.index(row[week])] row[capacity] return matload_supply里用errorscoerce把无法解析的字符串变成NaN再用fillna兜底这是处理脏数据的标准动作。build_matrix把长表转成宽矩阵行是供应商、列是周次后续规划模型直接拿这个矩阵当约束系数。参数上要注意supplier_id如果是字符串得先映射成 0 到 n-1 的整数索引否则mat[row[supplier_id]]会直接报错。这一步看着简单但实际数据里供应商编号经常带前缀字母不处理就是运行即崩。3. 核心模型拆解三问的代码实现与参数含义3.1 第一问供应商重要性排序的熵权法与 TOPSIS第一问要求对供应商的供货能力做重要性排序常见做法是熵权法确定指标权重再用 TOPSIS 算综合得分。熵权法的逻辑是某个指标在各供应商间差异越大说明它区分度越高权重就该越大。代码实现分两步先算权重再算贴近度import numpy as np def entropy_weight(matrix): # matrix: 供应商 x 指标已正向化 m, n matrix.shape # 归一化避免 0 值导致 log 无意义 p matrix / matrix.sum(axis0, keepdimsTrue) p np.clip(p, 1e-12, None) e -np.sum(p * np.log(p), axis0) / np.log(m) d 1 - e # 差异系数 w d / d.sum() # 权重归一化 return w def topsis(matrix, weights): # 加权规范化 norm matrix / np.sqrt((matrix ** 2).sum(axis0, keepdimsTrue)) weighted norm * weights best weighted.max(axis0) worst weighted.min(axis0) d_best np.sqrt(((weighted - best) ** 2).sum(axis1)) d_worst np.sqrt(((weighted - worst) ** 2).sum(axis1)) score d_worst / (d_best d_worst) return scoreentropy_weight里np.clip(p, 1e-12, None)是防止某个供应商某指标为 0 时log(0)变成负无穷这个细节不处理权重会直接变成NaN。topsis的score越接近 1 表示越接近理想解排序时降序即可。参数上matrix必须提前正向化——供货能力越大越好是正向指标价格越低越好是负向指标负向指标要先取倒数或做极差变换否则排序结果会完全反过来。这一步是新手最容易翻车的地方指标方向搞错整张排序表就是废的。3.2 第二问最少供应商方案的整数规划建模第二问要在满足供货需求的前提下选最少的供应商这是典型的 0-1 整数规划。决策变量x[i]表示是否选第 i 个供应商目标是最小化sum(x)约束是选中供应商的总供货能力不低于需求。用pulp写出来是这样import pulp def min_suppliers(capacity, demand): # capacity: 每个供应商的供货能力列表 # demand: 总需求 n len(capacity) prob pulp.LpProblem(min_suppliers, pulp.LpMinimize) x [pulp.LpVariable(fx{i}, catBinary) for i in range(n)] # 目标最小化选中数量 prob pulp.lpSum(x) # 约束选中供应商的供货能力之和 需求 prob pulp.lpSum(capacity[i] * x[i] for i in range(n)) demand prob.solve() selected [i for i in range(n) if pulp.value(x[i]) 1] return selectedcatBinary声明 0-1 变量lpSum做线性求和。这里有个建模陷阱如果只写“能力之和 需求”求解器可能给出一个数学上最优但业务上不合理的解比如选了供货能力刚好卡线的供应商实际执行时一点波动就不够用。稳妥做法是给需求乘一个安全系数比如demand * 1.05或者在约束里加最小单家供货量限制。参数demand的单位要和capacity对齐原始数据里如果一个是吨一个是箱不换算直接算结果会差出几个数量级。3.3 第三问转运方案的多目标优化与损耗控制第三问要同时考虑转运商的运力限制和损耗率本质是多目标既要满足供货又要让损耗尽量小。常见做法是把损耗折算成成本合并成单目标或者用加权和法。核心代码结构是构造一个“供应商—转运商—周次”的三维决策变量约束包括运力上限和供货平衡def transport_plan(supply, capacity_trans, loss_rate, weeks): # supply: 供应商周供货量 # capacity_trans: 转运商周运力 # loss_rate: 转运商损耗率 prob pulp.LpProblem(transport, pulp.LpMinimize) # y[s][t][w]: 供应商 s 在周 w 交给转运商 t 的量 y pulp.LpVariable.dicts(y, (range(len(supply)), range(len(capacity_trans)), range(weeks)), lowBound0) # 目标最小化总损耗 prob pulp.lpSum(loss_rate[t] * y[s][t][w] for s in range(len(supply)) for t in range(len(capacity_trans)) for w in range(weeks)) # 约束1每个供应商每周供货必须全部转运出去 for s in range(len(supply)): for w in range(weeks): prob pulp.lpSum(y[s][t][w] for t in range(len(capacity_trans))) supply[s][w] # 约束2每个转运商每周运力不超限 for t in range(len(capacity_trans)): for w in range(weeks): prob pulp.lpSum(y[s][t][w] for s in range(len(supply))) capacity_trans[t] prob.solve() return ylowBound0保证转运量非负约束 1 是供货平衡约束 2 是运力上限。这里变量维度是供应商数 × 转运商数 × 周数如果供应商 50 家、转运商 8 家、周数 24 周变量接近一万个求解时间会明显上升。我一般会先跑一个小规模子集验证模型逻辑再上全量数据否则等十分钟才发现约束写错时间全浪费。另外loss_rate如果是百分比字符串记得先转成小数直接拿2%去乘会报类型错误。4. 跑起来环境搭建与一键复现的完整步骤4.1 环境准备与依赖安装先把 Python 版本对齐这份资料一般要求 3.8 以上。建议用虚拟环境隔离避免和系统里其他项目的包版本打架python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt装完之后验证一下关键包能不能正常导入尤其是scipy和pulp这两个装失败最常见的原因是网络或编译依赖缺失。跑一句python -c import scipy, pulp; print(scipy.__version__, pulp.__version__)能打印出版本号就说明环境没问题。如果pulp报找不到求解器通常是它自带的 CBC 求解器没随包安装重装一次pulp一般能解决。4.2 数据路径与配置项检查运行前必须确认src/utils.py里的数据路径是相对路径还是绝对路径。很多资料里写死了作者本机的绝对路径换台机器直接FileNotFoundError。稳妥做法是统一用pathlib基于脚本位置拼路径from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent RAW_DIR BASE_DIR / data / raw OUTPUT_DIR BASE_DIR / output OUTPUT_DIR.mkdir(exist_okTrue)这样不管从哪个目录执行脚本路径都能正确解析。OUTPUT_DIR.mkdir(exist_okTrue)保证输出目录不存在时自动创建省得手动建文件夹。检查完路径再扫一眼model_q2.py里的demand和model_q3.py里的capacity_trans是不是从配置文件或数据文件读的如果是硬编码改成读文件后面调参才不用改代码。4.3 分步执行与结果校验不建议一上来就跑run_all.py先按问题逐个跑每跑完一个检查输出。执行顺序是preprocess.py→model_q1.py→model_q2.py→model_q3.py。每步跑完看output目录下有没有生成对应 CSV再用 pandas 读出来看行数和关键列import pandas as pd df pd.read_csv(output/q1_result.csv) print(df.shape) print(df.head()) print(df[score].describe())shape对得上供应商数量、score落在 0 到 1 之间、没有大面积NaN基本说明这步没问题。如果score全是 1 或者全是 0多半是指标正向化没做对回去查preprocess.py里的方向处理。三个问题都单独跑通后再执行run_all.py做端到端验证确认串起来也不报错。5. 避坑与排查五个真实会卡住你的问题5.1 求解器返回 infeasible 但约束看起来没问题现象是pulp求解状态显示Infeasible但逐条检查约束都觉得合理。原因通常是某条约束的右端项单位和其他约束不一致比如需求按吨算、供货能力按箱算导致约束在数值上根本不可能满足。解决办法是把所有量纲统一到同一单位在preprocess.py里加一步单位换算并在注释里写清换算系数。排查时可以先把约束逐条注释掉看放开哪条之后变得可行问题就定位在那条上。5.2 熵权法权重出现 NaN现象是entropy_weight返回的权重里有NaN。原因是某个指标在某供应商上的值为 0归一化后log(0)产生负无穷。解决办法就是前面代码里的np.clip(p, 1e-12, None)给概率加一个极小下界。另一个容易忽略的点是如果某列全为 0matrix.sum(axis0)也是 0除法直接产生NaN这种情况要在调用前先剔除全零列。5.3 中文列名读取后变成乱码现象是pd.read_csv读出来的列名是乱码。原因是文件编码不是 UTF-8常见的是 GBK。解决办法是显式指定encodinggbk或者用chardet先探测编码。稳妥做法是在utils.py里封装一个read_csv_safe函数先试 UTF-8失败再试 GBK避免每个脚本都写一遍编码判断。5.4 整数规划跑太久不出结果现象是model_q2.py执行后长时间无输出。原因是变量规模大加上约束紧分支定界搜索空间爆炸。解决办法是给求解器设时间上限prob.solve(pulp.PULP_CBC_CMD(timeLimit60))先拿到一个可行解再根据结果调整约束松紧。另外可以先用贪心算法算一个初始可行解作为上界传进去能明显缩短求解时间。5.5 结果 CSV 每次跑都不一样现象是同一份数据跑两次q3_result.csv的数值有细微差异。原因是求解器内部有随机种子或并行计算导致浮点顺序不同。解决办法是在脚本开头固定随机种子np.random.seed(42)并在求解器参数里关闭多线程。对于需要严格复现的场景还可以在输出时对数值做四舍五入到固定小数位消除浮点尾差。6. 二次开发把这份代码改成你自己的建模底子这份资料最大的价值不是那三道题的答案而是它提供了一个可以拆开重组的骨架。二次开发的第一步是抽公共层把utils.py里的读数据、写结果、路径管理抽成一个独立的小包以后换题目只改model_*.py公共层不动。第二步是参数外置把demand、安全系数、求解时间上限这些从代码里挪到config.yaml或config.py调参不用碰业务逻辑。第三步是加一层结果校验每个模型输出后自动检查关键约束是否满足比如第二问选中的供应商总能力是否真的覆盖需求不满足就抛异常而不是静默写文件。具体改造时我习惯先写一个validate.py把每问的业务约束翻译成断言def validate_q2(selected, capacity, demand): total sum(capacity[i] for i in selected) assert total demand, f供货不足: {total} {demand} assert len(selected) len(set(selected)), 供应商重复选中这样每次改完模型跑一遍约束不满足立刻暴露不用等到写论文时才发现数字对不上。改造的验证方法也很直接拿原始数据跑一遍结果和资料自带的结果对比差异在可接受范围内再动结构结构改完再跑一遍确认输出格式没变。从那以后我每次接手一份建模代码都强制先跑通原始版本、存一份基准结果再开始改不然改到一半分不清是代码错了还是数据错了。希望这份拆解能帮你把这道题真正跑起来也把这份代码变成你自己的东西。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/26 2:00:55

C语言学习笔记——数据类型和常量变量

C语言学习笔记——数据类型和常量变量 今天是 Mii 学习 C 语言的第三天。这两天 Mii 学习了 C 语言的数据类型和常量变量,话不多说,现在就把这两天学到的知识整理一下吧٩(๑•̀ω•́๑)۶ 下面来盘点一下 C 语言的各种数据类型。不过 Mii 这两天学习的…

2026/10/26 2:00:55

角色设定工程化:角色卡、系统提示词与人设一致性回归

先明确一件事:“S级厉鬼:梦中人”不是一个能直接 clone 下来跑 demo 的仓库,它是一份角色设定——杨间鬼梦在平行世界形成的变种,并且拥有成年人智慧。放到技术博客的场景里,真正值得写的不是设定本身有多离奇&#xf…

2026/10/26 3:10:58

Block Copy内存布局优化:从原理到性能调优实践

1. Block Copy 内存布局解析:从原理到实践在系统级编程和性能优化领域,内存操作的高效性直接决定了程序性能的上限。Block Copy(块拷贝)作为内存操作的基础单元,其背后的内存布局设计直接影响着拷贝操作的效率。不同于…

2026/10/26 3:10:58

ICMP与ARP协议详解:从ping不通到抓包定位的排障思路

1. 从两个"看不见的协议"说起:为什么ping通了不代表网络就通了很多人学网络协议时有个典型误区:把TCP/IP四层模型背得滚瓜烂熟,知道IP负责寻址、TCP负责可靠传输,却对ICMP和ARP这两个"打杂"的协议不屑一顾。结…

2026/10/26 3:10:58

慢SQL排查误区与优化实战指南

1. 慢SQL排查的常见误区与正确思路那天下午3点,系统监控突然报警,一个核心接口响应时间从平时的200ms飙升到8秒。团队第一反应是数据库出了问题,DBA立即被拉进群聊。但经过半小时的检查,数据库各项指标完全正常——CPU使用率不到3…

2026/10/26 3:10:58

HDFS架构设计与生产环境部署实战指南

1. HDFS核心架构解析:理解分布式文件系统的设计哲学HDFS(Hadoop Distributed File System)作为Hadoop生态的基石存储系统,其设计处处体现着"移动计算比移动数据更划算"的核心思想。我在实际生产环境中部署过多个PB级HDF…

2026/10/26 3:10:58

FS4412 U-Boot移植:SPL双阶段启动与DDR时序调试实战

简介:本资源是面向嵌入式Linux开发初学者与进阶工程师的FS4412平台U-Boot 2013版本移植实战代码包,聚焦ARM架构SoC引导程序定制化开发,解决从零启动、硬件适配、安全加密集成等核心问题。压缩包含2000个文件,主体为2879个C源码&am…

2026/10/26 3:05:58

Vue.js与ECharts实现价格趋势分析图实战

1. 项目概述:价格趋势分析图的技术选型价格趋势分析是金融、电商、能源等领域的核心需求之一。作为前端开发者,我们经常需要将复杂的数据变化趋势直观呈现给用户。Vue.js ECharts的组合是目前实现这类需求的最佳实践方案之一。为什么选择这个技术栈&…

2026/10/26 0:20:20

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/26 1:53:47

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/26 0:20:17

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/26 0:00:48

Nested Learning:解决AI灾难性遗忘的分层记忆架构

1. 为什么AI需要"记忆"能力?在传统深度学习模型中,我们常遇到一个根本性问题:模型在处理新任务时,往往会完全遗忘之前学到的知识。这种现象被业界称为"灾难性遗忘"(Catastrophic Forgetting)。想象一下&#…

2026/10/26 0:00:48

企业级大模型应用:OCR预处理、Dify编排与DeepSeek落地实战

1. 这不是“用大模型”,而是重构工作流的起点“大模型的应用和工具”——这八个字现在被贴在无数技术分享、招聘JD、内部立项书的标题栏里,但绝大多数人点进去后看到的,是零散的API调用示例、几个开源UI截图、或者一句“接入LLM就能降本增效”…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑