简介:面向数学建模竞赛(美赛、国赛)的Python算法实现合集,覆盖优化类、智能算法、统计机器学习、时间序列、综合评价、神经网络与图论等30类高频模型,适合需要快速搭建基线方法的参赛选手与科研人员。资源共39个文件,压缩包仅1.72MB,以txt代码脚本、docx说明文档、dat示例数据及部分py源文件为主,不少代码附带注释与样例数据,便于理解调用逻辑。目前已有63人浏览学习,内容体系完整,覆盖面广。通过这份资料,读者可直接获取线性/非线性规划、遗传算法、模拟退火、逻辑回归、SVM、随机森林、K-means、主成分分析、ARIMA、灰色预测、TOPSIS、层次分析法、CNN及最短路径等常见赛题方法的可运行Python代码,并参考文档中的建模思路快速迁移到自己的题目中,有效节省编码与调参时间,适合用作备赛阶段的速查与扩展工具。 不废话,直接说正事。这个合集是我过去几年带比赛、带毕设、自己接算法需求时,反复从代码仓库里翻出来改的东西。数学建模竞赛这东西,表面比的是论文和模型,实际上拼的是谁的代码落得快、结果算得稳。30个算法看着多,真正拆开看,无非就是插值拟合、规划优化、启发式搜索、图论网络、预测评价这几大门派。你把这几类吃透,比赛里90%的题都能找到对应的家伙什儿。
这篇东西不是给你抄一遍API文档,而是把我整理这30个算法时踩过的坑、总结的套路、以及每个算法在比赛里的适用场景都交代清楚。无论你是刚接触数模的大一新生,还是想系统补算法短板的参赛老手,照着这个思路去搭自己的工具库,绝对比临时抱佛脚翻CSDN强得多。
1. 算法合集的核心设计思路
1.1 为什么是这30个算法
数模竞赛的题型翻来覆去就那么几类:预测、评价、优化、分类、关联分析。我挑算法的标准很简单——在近五年国赛和美赛的赛题里出现过,或者是在优秀论文里频繁作为核心算法出现的。冷门但高逼格的算法比如小波分析、支持向量回归,我也收,但定位是备胎,用来给论文加分,不指望它扛大梁。
这30个算法不是平行关系,我把它们分成了五个梯队:数据处理与统计基础、规划与优化模型、启发式智能算法、图论与网络模型、预测与评价模型。这样分类的好处是,拿到一道题你不需要遍历30个算法,只需要根据题目类型定位到对应梯队,然后从中挑一到两个做对比分析就行。
1.2 代码模板的统一设计
整理代码时我给自己定了个规矩:每个算法的代码文件必须包含完整的输入输出示例、可视化代码、以及可直接调用的函数封装。比赛的时候没有时间让你现场调bug,所有代码必须拿过来改个参数就能跑。函数入口统一用DataFrame或numpy array,这样数据预处理完直接喂进去,不用来回转换格式。
2. 开发环境与代码工程结构
2.1 Python环境配置建议
我建议直接用Anaconda,Python版本3.9到3.11之间都行,别老追新,版本太新有些库还没适配。Dependencies这块,numpy、pandas、scipy、matplotlib、scikit-learn是必须的,另外建议装pulp做线性规划、statsmodels做时间序列、networkx做图论算法。深度学习相关的比赛场景不多,torch或keras装一个备用就行,别花太多时间在这上面。
2.2 目录结构设计
math_modeling_algorithms/ ├── data/ # 测试数据存放 ├── algorithms/ │ ├── data_processing/ # 数据处理类 │ ├── optimization/ # 优化与规划类 │ ├── heuristic/ # 启发式算法 │ ├── graph/ # 图论算法 │ └── prediction/ # 预测评价算法 ├── utils/ │ ├── data_loader.py │ └── visualization.py ├── notebooks/ # 调试用Jupyter └── main.py # 算法统一调用入口main.py里写了一个路由函数,你只要传入算法名称和参数,就能自动调起对应的模块。这样比赛时换算法做对比,只需要改一行代码。
3. 核心算法实现要点拆解
3.1 数据处理:插值拟合与数据标准化
数据处理是30个算法里看起来最简单但最容易出问题的部分。以三次样条插值为例,scipy.interpolate.CubicSpline虽然好用,但边界条件默认是not-a-knot,这个在很多实际数据里效果并不好。我习惯了传bc_type='natural'参数,强制两端二阶导数为零,能有效避免龙格现象。
数据标准化这块要注意一个坑:归一化和标准化的选择看模型需求。对距离敏感的算法(K-means、KNN)必须用Z-score标准化,也就是StandardScaler;对分布有要求的(神经网络、梯度下降类)可以用Min-Max归一化。标准化的时候记住一个原则:fit和transform必须用训练集的数据,不能拿全量数据去fit,否则会造成信息泄漏,这在预测类题目里是很致命的错误。
3.2 规划与优化:线性规划到整数规划
线性规划是国赛的常客,尤其是运输问题、生产计划这类题目。scipy.optimize.linprog很轻量,但只支持L1范数的目标函数,遇到绝对值、分段函数这类目标就需要用变量替换的技巧转化成标准形式。单纯形法和内点法的选择也有讲究:变量少约束少用单纯形法,速度快且结果可解释;变量多问题规模大用内点法,迭代稳定性更好。
整数规划和0-1规划推荐直接用pulp库,比scipy自带的好用太多。pulp的LpProblem定义问题,LpVariable定义变量时指定cat参数为'Integer'或'Binary'。个人经验是能用整数规划就不用穷举法,即便数据量看起来不大(比如20个变量的组合爆炸),穷举在竞赛环境里也会拖垮你的时间预算。
3.3 启发式算法:模拟退火和粒子群的实现细节
启发式算法是拿不到全局最优但能在合理时间内拿到近似最优的"野路子",美赛的优化题特别吃这套。模拟退火的核心在于温度衰减函数,我实测下来,指数衰减(T = T0 * 0.95**iter)比线性衰减稳定得多。初始温度T0设多少合适?看目标函数的量级,一般取目标函数初始值的5到10倍,这样能让算法在前期充分随机探索。
粒子群算法比模拟退火好用一点,因为不需要调那么多参数,但粒子群有个经典问题就是容易早熟,陷入局部最优。我的解决办法是引入惯性权重的线性递减策略,w从0.9线性降到0.4,同时限制粒子最大速度,避免粒子飞得太远导致震荡。
下面是我封装的粒子群算法核心代码,直接复制就能用:
import numpy as np def pso(func, dim, bounds, n_particles=30, max_iter=100): # func: 目标函数(最小化),接收一个1D数组 # bounds: 每个维度的取值范围, 形如[(low, high), ...] lb = np.array([b[0] for b in bounds]) ub = np.array([b[1] for b in bounds]) # 初始化粒子位置和速度 x = np.random.uniform(lb, ub, (n_particles, dim)) v = np.random.uniform(-1, 1, (n_particles, dim)) pbest = x.copy() gbest = x[0].copy() for i in range(max_iter): # 计算适应度 fitness = np.array([func(p) for p in x]) pbest_fitness = np.array([func(p) for p in pbest]) # 更新个体最优 mask = fitness < pbest_fitness pbest[mask] = x[mask] # 更新全局最优 best_idx = np.argmin(fitness) if fitness[best_idx] < func(gbest): gbest = x[best_idx].copy() # 惯性权重线性递减 w = 0.9 - 0.5 * (i / max_iter) # 更新速度和位置 r1, r2 = np.random.rand(dim), np.random.rand(dim) v = w * v + 1.5 * r1 * (pbest - x) + 1.5 * r2 * (gbest - x) v = np.clip(v, ub - lb, ub - lb) # 限制速度范围 x = x + v x = np.clip(x, lb, ub) return gbest, func(gbest)3.4 图论算法:Dijkstra、Floyd与最小生成树
图论在数模里多见于路径规划、网络优化、物流配送这类题目。Dijkstra算法的实现本身不复杂,关键是堆优化。不用heapq的Dijkstra在500个节点以上的图里就明显卡顿,比赛时一旦数据量大了会很被动。
Floyd算法虽然时间复杂度是O(n³),但胜在代码写起来简单,而且能一次性算出所有节点对之间的最短路径。我的建议是:节点在300以内用Floyd,300以上用Dijkstra+堆优化。这个经验值是拿真实路网数据测出来的。
最小生成树的Prim和Kruskal在实现难度上差不多,但注意Prim适合稠密图(邻接矩阵存储),Kruskal适合稀疏图(边集数组存储)。比赛里大多数情况是稀疏图,所以Kruskal一般够用。
下面是Kruskal算法的简洁实现:
def kruskal(n, edges): # n: 节点数(0~n-1) # edges: [(u, v, w), ...],u和v是节点编号,w是边权 parent = list(range(n)) def find(x): while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x def union(x, y): root_x, root_y = find(x), find(y) if root_x != root_y: parent[root_x] = root_y return True return False edges.sort(key=lambda e: e[2]) mst = [] total_weight = 0 for u, v, w in edges: if union(u, v): mst.append((u, v, w)) total_weight += w if len(mst) == n - 1: break return mst, total_weight3.5 预测与评价:灰色预测和模糊综合评价
灰色预测GM(1,1)在数据量少(4到10个样本)的时候特别好用,国赛很多预测题就是给你一两年的月数据,让你预测未来几个月。但GM(1,1)有个前提是数据序列的级比要落在可容覆盖区间内,不然累加序列的指数规律不明显,预测结果会偏差很大。我写了个辅助函数专门做级比检验,发现不满足就先用对数变换或开方变换让数据平滑掉。
模糊综合评价看着玄乎,其实本质就是权重矩阵和隶属度矩阵的乘法。关键难点在两层:第一是隶属度函数怎么选,三角形、梯形、正态型的选择直接影响结果;第二是权重怎么定,层次分析法(AHP)是配合模糊综合评价最常用的权重求解方式。这两个结合起来,在评价类题目里基本是通杀的存在。
4. 调试中的常见问题与踩坑记录
4.1 数值稳定性问题
做算法实现多了你会发现,很多bug不是逻辑错,而是数值问题。典型的就是log函数的参数为0、除法分母为0这类。比如在决策树或朴素贝叶斯里计算概率时,某个类别在训练集里没有出现,概率为0,log(0)直接崩溃,或者熵的计算直接出错。这时候你需要加平滑项,拉普拉斯平滑是最常用的手段,给分子分母同时加一个小常数。
另外在启发式算法里,如果目标函数计算过程中出现了NaN或inf,粒子群和模拟退火就会彻底跑飞。我习惯在写目标函数时强制做一次数值检查:
def safe_func(x): val = original_func(x) if not np.isfinite(val): return 1e10 # 给一个很大的惩罚值 return val这样做在求最小值时不光能防止崩溃,还能让算法自动避开不可行区域。
4.2 scipy版本API变化坑
三年前scipy.optimize.linprog把method='simplex'标记为deprecated,后来直接干掉了,现在默认用method='highs'。如果你去网上抄老代码,很可能跑不通。另外scipy.interpolate的interp2d也被标记为deprecated,推荐用RegularGridInterpolator或RectBivariateSpline。我整理这30个算法时,特意把每个算法跑通的scipy版本号写在文件头的注释里,避免以后环境升级代码废掉。
4.3 随机种子与结果可复现
数学建模竞赛最后是要提交论文的,算法跑出来的结果需要稳定。启发式算法每次运行结果都不一样,评委如果复现你的代码发现结果对不上,印象分会大打折扣。解决方案很简单,在算法入口处加上np.random.seed(42),或者把随机种子设为当前时间戳,然后将每次运行的种子记录在日志里。这样无论跑多少遍,结果都是可复现的,论文里也能明确写清楚"本实验随机种子固定为42"。
4.4 数据标准化的时机
前面提到过信息泄漏的问题,这里再强调一个相关的场景。如果题目里有训练集和测试集的划分,比如让你先对前80%的数据建模再预测后20%,那么标准化的参数(均值和标准差)必须只从训练集算出,然后同样应用到测试集上。直接调用StandardScaler.fit_transform(全部数据)是错的,这等于在建模前就偷看了测试集的信息。正确做法是:
scaler = StandardScaler() train_scaled = scaler.fit_transform(train_data) test_scaled = scaler.transform(test_data)比赛里我见过不少人在这里栽跟头,结果预测精度虚高,实际应用时完全变形。
5. 竞赛实战中的使用策略
5.1 如何快速判断用哪个算法
我总结了一个"选题—建库—匹配"三段式快速定位法。拿到题目先判断问题类型:数据给的是时间序列?那就是预测题,直接锁定灰色预测、ARIMA、LSTM这三件套。给的是指标表?评价题,用层次分析法加模糊综合评价,如果指标太多就先用主成分分析降维。给的是网络结构或坐标图?优化题,先试试线性规划,约束复杂就换遗传算法或粒子群。
这30个算法每一个我都标注了适用模型和最优场景,在算法仓库的README里做成了速查表。比赛进行到第20小时还剩一堆结果没算出来的时候,你绝对没有心情去翻算法导论,一张速查表能救命。
5.2 参数调优的通用方法
启发式算法的参数调优有些通用套路。我的做法是先跑一遍默认参数,然后对关键参数做网格搜索或随机搜索。比如遗传算法的种群规模和交叉概率、模拟退火的初始温度和降温速率、粒子群的学习因子和粒子数。不过比赛时间紧张,我一般只调一个最关键参数,其余用经验值。粒子群在多数问题上把粒子数设在30-50之间,迭代100-200次就够了,太大会拖慢速度,收益不明显。
5.3 代码复用与查重的平衡
不少同学担心比赛中大量复用自己整理的代码会不会被认定抄袭。实际上,数学建模竞赛看的是你的思路和模型创新点,基础代码框架的复用完全没问题。但有个细节要注意:论文附录里引用的核心代码,最好手动改一下变量命名风格,加一些自己定义的函数封装,这样既显得有自己的工作量,也让论文和代码的对应关系更清晰。
5.4 从算法到论文的完整路径
算法跑通只是完成了一半工作。我在整理30个算法时,每一个都配套了标准的论文描述段落模板,包括算法原理简述、参数设置表、结果分析图和对比实验表。这样在比赛的最后一个晚上,写论文的人只需要把实际跑出来的数据填进模板里就行,不用从零开始想怎么描述算法。图表统一用matplotlib导出的PDF格式,再在LaTeX里排版,整个论文质感会比临时画图好一个档次。
6. 踩坑总结与进阶方向
6.1 最常见的前五个坑
如果只能给一条建议,那一定是"不要只看算法理论,一定要亲手跑通代码"。十个数学建模新手有八个挂在环境配置上——不是numpy装不上,就是scikit-learn版本冲突。还有个常见问题是只会用jupyter notebook跑单文件,不懂怎么把代码组织成可复用的工程结构。另外就是过度依赖网上的现成代码,遇到题目稍微变形就不知道怎么改了。
6.2 这30个算法之外还该学什么
把这30个算法吃透,参加国赛和美赛已经够用。但如果想冲刺国一或者拿美赛的F奖,建议再补三个方向:深度学习的时间序列预测(LSTM、Transformer)、多目标优化算法(NSGA-II)、以及集成学习(XGBoost、LightGBM)。这三个是近年来优秀论文中高频出现的方向,也是区分"基础扎实"和"有亮点创新"的分水岭。
6.3 真正把算法库用起来的诀窍
我自己的习惯是每次比赛结束后,会把那一次比赛中实际用到的代码和数据重新整理到算法库里,把踩到的坑和临时补的优化都记录下来。这样每打一次比赛,我的算法库就会进化和成长一次,而不是比完就丢。到现在这个库已经从最初的30个算法涨到了50多个,而且每一个都经过真实赛题的检验。
从我的经验来看,算法库的整理不要追求大而全,而要追求稳而准。你熟练掌握10个算法的深度,比肤浅了解100个算法要有用得多。这套30算法的合集,定位就是帮你把数模竞赛中最核心的武器都摸透,这样上了赛场心里有底。
最后再分享一个小技巧:整理算法代码时,把每个算法的"适用条件"和"不适用条件"写在docstring第一行。比赛时不光要会用算法,更要知道什么时候不能用它,这是很多获奖论文评委最看重的能力。
本文还有配套的精品资源,点击获取