公园道路设计问题:数学建模竞赛中的几何网络优化与Steiner树求解 简介这份数学建模论文资源面向参加数学建模竞赛的学生及指导教师聚焦公园内道路设计的优化问题要求在任意两入口最短路径不超过直线距离1.4倍的前提下使道路总长度最小。论文完整给出三个递进问题的建模与求解过程问题一在给定交叉点下用Prim算法构建最小生成树结合Floyd算法求最短路径并做局部优化得到394.5米方案问题二在交叉点自由时引入Fermat点组合寻优最短路径为358.4米问题三考虑矩形湖障碍通过Fermat点与斯坦纳树方法优化最终路径为359.6米。资源包为1个doc文档约495KB内含摘要、问题重述、模型假设、符号说明、问题分析及Matlab程序附录结构完整。目前已有330人学习适合需要掌握图论算法与几何优化方法、参考完整赛题求解思路的读者研读。1. 公园道路设计问题从一张平面图到可提交的建模论文拿到“公园内道路设计问题”这个标题很多人第一反应是画几条线连起来就完事。真做过这道题的人知道它卡人的地方从来不是画线而是把“修路要花多少钱、游客走多远、绕不绕路”这三件事同时塞进一个数学模型里还要让评委相信你的解是合理的。这道题属于数学建模竞赛里典型的几何网络优化类问题核心是在给定景点坐标和边界约束下设计一组道路使总建设费用尽量低同时满足任意两个景点之间的通行需求。适合正在准备建模竞赛、需要一份能跑通代码和写清论文的读者。下面我按自己带队的实际流程把选型、建模、求解、写作和踩坑一次讲透。2. 先想清楚道路设计到底在优化什么2.1 三个目标函数的取舍逻辑公园道路设计问题通常给出若干景点坐标、公园边界多为矩形或不规则多边形要求修路连接这些景点。表面看是“连通图”问题但真正决定论文档次的是你怎么定义目标。常见的目标有三类。第一类是总路程最短等价于在景点之间找一棵最小生成树或者 Steiner 树。第二类是总建设费用最低费用往往和路段长度、道路等级主干道/次干道/小径挂钩不同等级单价不同。第三类是游客通行效率最高比如任意两景点间的最短路径之和最小这更接近网络上的最短路总和优化。我一般会先做一件事把题目里所有带数字的约束抄到一张纸上逐条标注它属于“硬约束”还是“软约束”。硬约束是必须满足的比如道路不能穿过湖面、不能超出公园边界、必须连通所有景点。软约束是可以折算进目标函数的比如“尽量少绕路”可以写成绕行系数的惩罚项。提示不要一上来就写目标函数。先把约束分类很多队伍翻车就翻在把硬约束当软约束处理最后解出来的路穿过水面整篇论文直接失去竞争力。选型上如果题目只要求连通且费用与长度成正比最小生成树MST就是基准解用 Prim 或 Kruskal 都能快速得到。但竞赛题很少这么仁慈通常会引入“允许在非景点位置设置道路交叉点”这时候就要考虑 Steiner 点问题从多项式时间变成 NP-hard需要用启发式或近似算法。2.2 从平面图到加权网络的建模步骤把几何问题转成图论问题是这道题最关键的一步。具体操作如下。第一步确定节点集合。节点包括所有景点以及可能的道路交叉点。初始阶段可以只把景点作为节点后续再考虑加入 Steiner 点。第二步确定边集合。任意两个节点之间是否可以直连取决于这条线段是否穿过障碍物、是否在公园边界内。这一步需要做几何相交判断。第三步给边赋权。权值可以是欧氏距离也可以是距离乘以该路段的单位造价。下面是一段用 Python 构建初始完全图并做障碍物过滤的代码假设障碍物用多边形表示。import math from shapely.geometry import LineString, Polygon, Point # 景点坐标实际使用时替换为题目给定数据 spots [(0, 0), (10, 0), (10, 8), (0, 8), (5, 4)] # 障碍物比如湖面用多边形顶点表示 obstacle Polygon([(3, 2), (7, 2), (7, 6), (3, 6)]) def dist(a, b): return math.hypot(a[0] - b[0], a[1] - b[1]) def edge_valid(a, b): 判断两点连线是否穿过障碍物 line LineString([a, b]) return not line.intersects(obstacle) # 构建邻接矩阵不可达的边权值设为无穷大 n len(spots) INF float(inf) adj [[INF] * n for _ in range(n)] for i in range(n): for j in range(i 1, n): if edge_valid(spots[i], spots[j]): w dist(spots[i], spots[j]) adj[i][j] w adj[j][i] w for row in adj: print([f{x:.2f} if x ! INF else INF for x in row])这段代码的逻辑是先定义景点和障碍物然后对每一对景点判断连线是否与障碍物相交。edge_valid用 Shapely 的intersects做几何判断返回 False 表示这条边不可用。adj是邻接矩阵无穷大代表两点之间不能直连。参数方面spots和obstacle需要根据题目实际数据替换障碍物如果是多个可以放进列表循环判断。注意Shapely 的intersects对边界接触也算相交如果题目允许道路贴着障碍物边缘走需要改用crosses或加一个微小的缓冲距离。2.3 最小生成树作为基准解的实现有了邻接矩阵最小生成树可以直接用 Prim 算法求。这一步的作用是给论文一个可对比的基准后面所有优化方案都要和它比。def prim(adj): n len(adj) visited [False] * n visited[0] True edges [] total 0 for _ in range(n - 1): best (None, None, float(inf)) for i in range(n): if not visited[i]: continue for j in range(n): if visited[j]: continue if adj[i][j] best[2]: best (i, j, adj[i][j]) if best[0] is None: return None, float(inf) # 图不连通 i, j, w best visited[j] True edges.append((i, j, w)) total w return edges, total edges, total prim(adj) print(MST 边:, edges) print(总长度:, round(total, 2))Prim 算法从任意一个节点出发每次选择连接已访问集合和未访问集合的最小权边。visited记录节点是否已加入生成树edges存结果total是总权值。如果返回的edges为 None说明图不连通需要检查障碍物是否把所有路径都堵死了。这个基准解在论文里通常放在“模型建立”之后作为“基础模型求解”部分。3. 引入 Steiner 点让道路在非景点处交叉3.1 Steiner 点为什么能缩短总长度最小生成树只允许在景点处连接但现实中道路可以在任意位置交叉。经典例子是三个景点构成等边三角形MST 总长是两条边而引入一个中心 Steiner 点后三条边从中心辐射出去总长反而更短。这个反直觉的结论是 Steiner 树问题的核心价值。在公园道路设计里Steiner 点的物理意义就是道路交叉口。允许在非景点位置设置交叉口可以显著降低总建设费用。但 Steiner 树是 NP-hard 问题景点数量超过 10 个以后精确求解基本不可行必须用启发式算法。我一般会先用“网格法”找近似 Steiner 点把公园区域划分成网格每个网格中心作为候选 Steiner 点然后在这个扩展后的节点集合上重新求 MST。网格越密解越接近最优但计算量也越大。3.2 网格法近似求解的完整代码下面这段代码在原有景点基础上加入网格候选点重新构建邻接矩阵并求 MST。import numpy as np def grid_candidates(xmin, xmax, ymin, ymax, step): 在矩形区域内生成网格候选点 xs np.arange(xmin, xmax step, step) ys np.arange(ymin, ymax step, step) return [(x, y) for x in xs for y in ys] # 假设公园边界为 (0,0) 到 (10,8) candidates grid_candidates(0, 10, 0, 8, 2.0) # 合并景点和候选点景点在前 all_nodes spots candidates n len(all_nodes) adj2 [[INF] * n for _ in range(n)] for i in range(n): for j in range(i 1, n): if edge_valid(all_nodes[i], all_nodes[j]): w dist(all_nodes[i], all_nodes[j]) adj2[i][j] w adj2[j][i] w edges2, total2 prim(adj2) print(加入网格点后总长度:, round(total2, 2)) print(使用的 Steiner 点数量:, len(set([e[1] for e in edges2 if e[1] len(spots)])))grid_candidates在给定矩形范围内按step生成均匀网格点。all_nodes把景点和候选点拼在一起景点索引在前方便后续区分。adj2是扩展后的邻接矩阵构建逻辑和之前一致。prim返回的边中如果终点索引大于等于景点数量说明用到了 Steiner 点。参数step是网格间距步长越小候选点越多解越优但计算越慢。实际比赛中我一般先用 step2 跑一遍看趋势再用 step1 或 0.5 精化。如果公园面积很大可以只在对角线附近或景点密集区域加密网格不必全局均匀。提示网格法得到的 Steiner 点位置是离散的论文里可以加一步“局部连续优化”用 scipy 的 minimize 对每个 Steiner 点坐标做微调通常还能再降 1% 到 3%。3.3 结果对比与论文中的呈现方式跑完两组结果后论文里需要一张对比表。我通常这样组织方案节点数总长度是否允许非景点交叉计算耗时MST 基准528.4否1s网格法 Steiner53024.7是3s局部优化后53024.1是12s表格里的数字必须来自你自己的代码运行结果不要照抄。呈现时先写“基础模型”再写“改进模型”最后写“模型对比与选择”逻辑链条要清晰。评委看的是你有没有意识到 MST 的局限性以及你用什么手段去突破。4. 避坑与常见问题排查4.1 障碍物判断把可行边误杀现象程序跑出来图不连通或者明显应该能连的景点之间权值是无穷大。原因通常是障碍物多边形定义得太“胖”或者intersects把边界接触也算成相交。解决方法是先用可视化把障碍物和所有候选边画出来肉眼确认哪些边被误杀。如果题目允许贴边修路把intersects换成crosses或者对障碍物做buffer(-0.01)收缩。4.2 网格步长选得太粗导致解质量差现象加入 Steiner 点后总长度几乎没降和 MST 差不多。原因是网格太稀疏候选点离真正的最优 Steiner 点太远。解决办法是逐步减小 step观察总长度变化曲线当曲线趋于平缓时停止。一般 step 降到公园短边的 1/20 左右就够用了。4.3 论文里目标函数和约束写反现象论文写完后自己检查发现某个硬约束被写进了目标函数或者某个优化目标被当成了约束。这种错误在评审时很致命。解决办法是画一张“约束-目标对照表”左边列题目原文右边列你的数学表达逐条核对。硬约束用等式或不等式单独列出软约束才进目标函数。4.4 代码结果和论文数字对不上现象论文里写的总长度是 24.7但代码重新跑一遍变成 25.3。原因可能是随机种子没固定、网格生成顺序变了、或者浮点数精度问题。解决办法是在代码开头固定np.random.seed(42)所有涉及随机的步骤都设种子。论文里的数字直接从代码输出复制不要手抄。4.5 忽略道路等级导致费用模型失真现象题目给了主干道和次干道的不同单价但代码里只用欧氏距离当权值。这样算出来的“最优解”在费用维度上完全错误。解决办法是在边权计算时乘以对应道路等级的单价。如果一条边同时可以修两种等级就拆成两条平行边让算法自己选。5. 论文写作与进阶技巧让评委一眼看到你的建模深度5.1 摘要里必须出现的三个数字数学建模论文的摘要是评委第一眼看到的东西。我一般要求摘要在 300 字以内但必须包含三个具体数字基准解总长度、改进解总长度、优化幅度百分比。比如“基础模型总长度 28.4 单位引入 Steiner 点后降至 24.7 单位优化幅度 13.0%”。这三个数字让评委立刻知道你的工作有量化结果不是空谈。摘要的结构可以这样组织第一句写问题背景和核心约束第二句写你用了什么模型图论Steiner树网格近似第三句写求解方法和关键结果第四句写模型检验和灵敏度分析。不要写“本文研究了……”这种套话直接上干货。5.2 灵敏度分析怎么做才有说服力灵敏度分析是区分省奖和国奖的关键。对公园道路设计问题我通常选三个参数做扰动景点坐标偏移 ±5%、障碍物面积变化 ±10%、道路单价变化 ±20%。每个参数扰动后重新求解记录总长度变化。import copy def sensitivity(spots, obstacle, delta0.05): 对景点坐标做扰动观察总长度变化 results [] for i in range(len(spots)): for dx, dy in [(delta, 0), (-delta, 0), (0, delta), (0, -delta)]: new_spots copy.deepcopy(spots) new_spots[i] (spots[i][0] * (1 dx), spots[i][1] * (1 dy)) # 重新构建邻接矩阵并求解 # 此处省略重复的建图代码实际使用时封装成函数 results.append((i, dx, dy, 总长度变化)) return results这段代码演示了扰动思路对每个景点在四个方向上做微小偏移重新求解并记录结果。实际论文里不需要贴这段代码而是把结果整理成折线图或表格说明“当景点位置存在测量误差时模型解的变化在可接受范围内”从而证明模型鲁棒性。5.3 模型评价怎么写才不空洞模型评价要分优点和缺点但每条都要具体。优点比如“将几何约束转化为图论中的边有效性判断处理障碍物灵活”缺点比如“网格法得到的 Steiner 点位置受步长影响无法保证全局最优”。不要写“模型具有较好的通用性”这种放之四海皆准的话。我自己的习惯是每写一条优点就问自己“这条优点在哪个具体步骤体现”然后把那个步骤的数字或方法名写进去。每写一条缺点就问自己“这个缺点会导致什么后果”然后把后果量化。比如“网格步长 2.0 时解与步长 0.5 的解相差 4.2%说明解的质量对步长敏感”。5.4 一个容易被忽略的进阶点多目标加权如果题目同时要求费用最低和通行效率最高可以构造加权目标函数min α * 总费用 β * 总绕行距离。α 和 β 的取值用层次分析法或熵权法确定。这一步能让论文从单目标优化升级到多目标决策评委通常会给更高分。具体操作是先分别求两个单目标的最优解得到费用最小值和绕行最小值然后归一化再让 α 从 0 到 1 步进画出帕累托前沿。论文里放一张帕累托曲线图说明“当 α0.6 时综合指标最优”。这个技巧不需要复杂代码但能显著提升论文的模型厚度。最后说一个我带队多年的习惯每次跑完代码先把所有中间结果存成 CSV再写论文。论文里的每个数字都能追溯到某个 CSV 文件评审问起来随时能复现。这个习惯帮我省过很多次“数字对不上”的后悔药。希望帮到你。本文还有配套的精品资源点击获取