数学建模实战:基于最小生成树与网络优化的管道铺设方案设计
2026/8/28 6:29:29 网站建设 项目流程

1. 项目概述:当数学建模遇上城市“血管”规划

自来水管道铺设,听起来像是市政工程或给排水专业的活儿,怎么就成了数学建模的经典赛题?这恰恰是数学建模的魅力所在——它用抽象的数学工具,去解决现实世界中那些看似庞杂、充满约束的实际问题。我参加过不少数学建模竞赛,也带过学生团队,发现“管道铺设”这类问题几乎是各类赛事的“常客”,因为它完美融合了图论、优化算法和成本分析,是一个检验综合能力的绝佳场景。

简单来说,这个问题就是:给定一片区域(比如一个新开发区、一个工业园区或一个居民小区),里面有若干个需要供水的用户点(居民楼、工厂等),以及一个或多个水源点(水厂、加压站)。你的任务就是设计一套管道网络,用最经济的方式把水从水源送到每一个用户点。这里的“经济”,通常指管道建设总成本最低,而成本又和管道的长度、材质、直径等因素挂钩。这可不是随便画画连接线那么简单,它涉及到如何选择管道路径、如何确定管道规格、如何在多个水源和用户之间分配流量,是一个典型的网络优化问题

无论是参加“高教社杯”全国大学生数学建模竞赛,还是准备美赛(MCM/ICM),这类问题都极具代表性。它考察的不仅仅是数学公式的套用,更是将实际问题转化为数学模型的能力、对多种算法(如最小生成树、最短路径、线性/非线性规划)的灵活运用,以及利用编程工具(如MATLAB、Python)进行求解和结果可视化的综合技能。接下来,我就以一个资深建模者的视角,带你层层拆解这个问题的核心,分享从问题分析到论文成稿的全流程实战经验。

2. 问题拆解与模型构建思路

面对一个具体的管道铺设问题,第一步不是急着写代码或查公式,而是静下心来,把题目描述“翻译”成数学语言。这个过程决定了整个模型的走向和最终方案的优劣。

2.1 核心要素抽象化

首先,我们需要从工程问题中提取出数学模型的基本元素:

  1. 节点:将水源点、用户需求点抽象为网络中的“节点”。通常,水源点被视为供应点或“根”节点,用户点被视为需求点。有时,为了路径更优,我们还可以在道路交叉口或特定位置引入“虚拟节点”或“ Steiner点”(斯坦纳点),但这会大大增加问题复杂度,属于进阶玩法。
  2. :连接两个节点的管道,抽象为网络中的“边”。每条边有两个关键属性:长度成本系数。成本系数可能是一个简单的单位长度造价,也可能是一个与管道直径、材质、埋深相关的复杂函数。
  3. 权重:通常指边的成本,即铺设这条管道所需的费用。最基本的情况是,成本 = 单位长度造价 × 管道长度。但实际问题中,成本可能还与地形(平地、山地、穿越河流)、地质条件、是否需拆迁等因素有关,这就需要为不同的边赋予不同的单位成本。
  4. 约束条件:这是模型的血肉。常见的约束包括:
    • 连通性约束:必须保证从水源点到每一个用户点都存在通路(即网络是连通的)。
    • 流量约束(如果考虑水力):管道直径需满足用户节点的流量(或水压)需求。这会将问题从单纯的图论问题升级为网络流优化问题
    • 树状网络约束:在很多简化模型中,要求最终的管道网络是一棵树(即任意两点间只有唯一路径,无环)。这是因为环状管网虽然可靠性高,但初期建设成本通常更高。竞赛题中为简化问题,常默认采用树状结构。
    • 单/多水源:是一个水源供应所有点,还是多个水源协同供应。
  5. 目标函数:我们最终要优化的东西。99%的情况下是最小化管道网络的总建设成本。在考虑流量和管径时,目标函数可能是“最小化总投资(管道成本+泵站成本等)”。

2.2 模型类型选择与思路演进

根据问题的具体条件,我们可以选择不同复杂度的模型:

  • 基础版:不考虑流量和管径的静态最小成本网络这是最经典的版本。问题退化为:给定节点坐标和边的造价,求一个连接所有节点的网络,使得总成本最小。如果网络必须是树,那就是经典的最小生成树问题。常用的算法有:

    • Prim算法:从一个根节点(水源)开始,逐步生长出一棵树。非常适合单水源问题,能保证生成的树以水源为根。
    • Kruskal算法:对所有边按权重排序,从小到大选择不构成环的边加入。更适合无指定根节点,或需要快速理解的情况。

    注意:如果问题没有强制要求树状结构,那么最优解可能是“最小生成树”,因为增加任何边都会增加成本(在边权为正的前提下)。所以,即使题目没说,最小生成树也常常是一个优秀的候选解。

  • 进阶版:考虑流量需求的管径优化网络一旦引入用户节点的用水量需求,问题就变得复杂起来。管道直径的选择直接影响成本和输水能力。大管径成本高但水头损失小,小管径成本低但可能无法输送足够流量或导致末端水压不足。这时,模型变成一个混合整数非线性规划问题:决策变量包括哪些边被选中(0-1变量)、被选中边的管径(离散选择变量)、管道中的流量(连续变量)。目标函数是管道成本(与管径、长度有关)的最小化,约束包括节点流量平衡方程、管道压降方程等。求解这类问题需要用到专业的优化求解器(如LINGO、Gurobi)或设计启发式算法。

  • 高阶版:多阶段规划或带可靠性要求有些题目会考虑城市规划是分阶段的,或者要求管网系统具备一定的可靠性(如当某段管道损坏时,能有备用路径供水)。这就涉及到多阶段优化网络可靠性设计,可能需要用到更复杂的随机规划或鲁棒优化模型。

对于大多数竞赛场景,能把“基础版”做扎实,并尝试向“进阶版”做一些合理的探索和简化,就已经能产出非常出色的论文了。我的建议是:先完成,再完美。先用最小生成树给出一个可行解,作为论文的基准方案,然后再尝试加入流量因素进行优化,对比结果,展示你的思考深度。

3. 核心算法实现与编程实战

理论分析之后,就要动手实现。这里我以最经典的、基于坐标和欧氏距离的最小生成树问题为例,展示用Python(搭配networkxmatplotlib库)的完整求解和可视化流程。假设我们有1个水源点和9个用户点。

3.1 数据准备与问题描述

首先,我们定义节点。通常水源点编号为0。

import numpy as np import matplotlib.pyplot as plt import networkx as nx # 定义节点坐标 (水源点 + 用户点) # 节点0为水源,其余1-9为用户点 coordinates = { 0: (0, 0), # 水源 1: (2, 3), 2: (4, 1), 3: (5, 4), 4: (7, 2), 5: (8, 5), 6: (3, 6), 7: (6, 7), 8: (1, 8), 9: (9, 9) } # 定义节点类型和需求(为进阶模型准备) node_demand = {0: 0} # 水源供应,需求为负或0 for i in range(1, 10): node_demand[i] = np.random.randint(10, 30) # 随机生成用户点用水需求,单位:升/秒

3.2 构建完全图并计算边权

我们的目标是连接所有节点,因此先假设任意两点间都可以直接铺设管道(即构建一个完全图),边的权重就是两点间的欧氏距离乘以一个单位成本系数。这里为了简化,设单位成本系数为1,即权重等于距离。

# 创建完全无向图 G = nx.Graph() G.add_nodes_from(coordinates.keys()) # 计算所有节点对之间的欧氏距离作为边权(成本) for i in coordinates: for j in coordinates: if i < j: # 避免重复添加边 x1, y1 = coordinates[i] x2, y2 = coordinates[j] distance = np.sqrt((x2 - x1)**2 + (y2 - y1)**2) # 这里可以乘以一个成本系数,例如不同地形成本不同 cost = distance # 假设单位距离成本为1 G.add_edge(i, j, weight=cost) # 为节点添加坐标属性,用于绘图 for node, (x, y) in coordinates.items(): G.nodes[node]['pos'] = (x, y)

3.3 应用最小生成树算法求解

我们分别使用Prim算法(指定水源为根)和Kruskal算法求解,并对比结果。在单位成本一致的情况下,它们求出的总成本应该相同,但树的结构可能因算法而异(不过最小生成树的总权重唯一)。

# 使用Kruskal算法求最小生成树 (MST) mst_kruskal = nx.minimum_spanning_tree(G, algorithm='kruskal') total_cost_kruskal = sum(G.edges[u, v]['weight'] for u, v in mst_kruskal.edges()) # 使用Prim算法求最小生成树,指定根节点为0(水源) mst_prim = nx.minimum_spanning_tree(G, algorithm='prim') total_cost_prim = sum(G.edges[u, v]['weight'] for u, v in mst_prim.edges()) print(f"Kruskal算法得到的最小生成树总成本: {total_cost_kruskal:.2f}") print(f"Prim算法得到的最小生成树总成本: {total_cost_prim:.2f}")

3.4 结果可视化与方案展示

将原始节点、可能的连接(以浅色表示)以及最终选中的最小生成树管道(以粗线高亮)绘制出来,能让你的论文结果部分增色不少。

# 绘制图形 plt.figure(figsize=(12, 6)) # 子图1:原始完全图 plt.subplot(1, 2, 1) pos = nx.get_node_attributes(G, 'pos') nx.draw_networkx_nodes(G, pos, node_color='lightblue', node_size=300) nx.draw_networkx_nodes(G, pos, nodelist=[0], node_color='red', node_size=500, label='水源') # 高亮水源 nx.draw_networkx_edges(G, pos, alpha=0.2, edge_color='gray') # 浅色表示所有可能连接 nx.draw_networkx_labels(G, pos) plt.title("原始节点与所有可能连接(完全图)") plt.axis('equal') plt.legend() # 子图2:最小生成树方案(以Prim结果为例) plt.subplot(1, 2, 2) nx.draw_networkx_nodes(G, pos, node_color='lightblue', node_size=300) nx.draw_networkx_nodes(G, pos, nodelist=[0], node_color='red', node_size=500, label='水源') nx.draw_networkx_edges(G, pos, edgelist=mst_prim.edges(), width=3, edge_color='darkorange', label='铺设管道') nx.draw_networkx_labels(G, pos) plt.title(f"最小生成树管道铺设方案\n总成本: {total_cost_prim:.2f}") plt.axis('equal') plt.legend() plt.tight_layout() plt.show() # 输出详细的边信息 print("\n=== 最小生成树管道铺设详情 ===") print("起点-终点 | 长度(成本)") print("-" * 30) for u, v in mst_prim.edges(): length = G.edges[u, v]['weight'] print(f" {u:2d} - {v:2d} | {length:.2f}")

实操心得:在竞赛中,数据往往不是直接给坐标,而是给节点间的距离矩阵,或者给的是街区距离(曼哈顿距离)而非直线距离。这时,你需要根据题目描述,在构建图的时候正确计算边权。例如,如果是街区距离,边权cost = abs(x2-x1) + abs(y2-y1)。这个细节是很多新手容易忽略的失分点。

4. 模型进阶:引入流量与管径优化

基础模型假设所有管道成本只与长度有关,这显然不符合工程实际。管径越大,造价越高,但输水能力也越强。接下来,我们尝试在模型中加入流量因素,做一个简化版的管径优化。

4.1 问题简化与假设

为了在竞赛有限时间内使问题可解,我们通常需要做出合理简化:

  1. 管径离散化:假设市场上只有几种标准管径可供选择(如DN100, DN150, DN200),而不是连续变量。
  2. 水力模型简化:忽略复杂的水头损失计算,采用一个简化的规则,例如“某管径下最大允许流量”。或者,我们可以将“满足流量需求”转化为对管道“容量”的约束。
  3. 流向确定:在树状网络中,从水源到每个用户的路径是唯一的。因此,每条管道中的流量等于所有通过该管道供水的下游用户的需求量之和。这是一个自顶向下的确定过程。

4.2 建立混合整数规划模型思路

设:

  • x_{ij}^{d}为0-1变量,表示节点i到j的管道是否采用管径d。
  • f_{ij}为连续变量,表示从i流向j的流量(假设方向从i到j)。
  • c_{d}为单位长度、管径为d的管道成本。
  • L_{ij}为节点i到j的距离。
  • D为所有可选管径的集合。
  • Q_{max}^{d}为管径d的最大允许流量。

目标函数:最小化总成本Minimize Σ_{i,j} Σ_{d in D} (c_d * L_ij * x_{ij}^{d})

约束条件:

  1. 网络结构约束:选中的边构成一棵以水源为根的树。这可以用经典的“单商品流”约束或“Miller-Tucker-Zemlin”约束来刻画,但较为复杂。在编程求解时,一种实用的启发式方法是:先确定拓扑结构(如用最小生成树),再优化管径。即两阶段法。
  2. 流量平衡约束:对于每个非水源节点j,流入流量 - 流出流量 = 该节点需求。
  3. 管径容量约束:如果从i到j的管道被选中且管径为d,则流量f_{ij} <= Q_max^d。这可以表示为f_{ij} <= Σ_{d in D} (Q_max^d * x_{ij}^{d})
  4. 管径唯一性约束:对于每条可能被选中的边(i,j),只能选择一种管径:Σ_{d in D} x_{ij}^{d} <= 1。注意这里是<=1而不是=1,因为这条边可能不被选中。

4.3 两阶段启发式算法实现示例

由于完整的MIP模型求解较慢,我们可以采用一个高效的两阶段启发式方法:第一阶段:用最小生成树确定管道网络的拓扑结构(即哪些边需要铺设)。第二阶段:在固定的树状结构上,从叶子节点向水源根节点回溯,计算每条边需要承载的流量,然后根据流量为其选择能满足要求的最小标准管径(因为成本随管径增大而增加,所以选最小可行管径是最经济的)。

# 假设我们已通过第一阶段得到最小生成树 mst_prim (一个networkx的Graph对象) # 定义管径选项及其属性 pipe_options = { 'DN100': {'max_flow': 15, 'cost_per_unit_length': 10}, # 最大流量15,单位长度成本10 'DN150': {'max_flow': 30, 'cost_per_unit_length': 18}, 'DN200': {'max_flow': 50, 'cost_per_unit_length': 25} } # 将树转为以水源(节点0)为根的有向树,方便计算流量 directed_tree = nx.dfs_tree(mst_prim, source=0) # 计算每条边需要承载的流量(自底向上,后序遍历) # 首先,初始化所有节点的“子树总需求”,叶子节点就是自身需求 subtree_demand = node_demand.copy() # 开始时,每个节点的子树需求就是自身需求 # 按从叶子到根的顺序处理节点(逆拓扑序) # 获取一个逆拓扑序(从叶子到根) reverse_order = list(reversed(list(nx.topological_sort(directed_tree)))) # 有向树拓扑排序 for node in reverse_order: if node == 0: continue # 根节点(水源)不需要向上传递 # 找到该节点的父节点(在有向树中,入度为1) predecessors = list(directed_tree.predecessors(node)) if predecessors: parent = predecessors[0] # 将当前节点的子树总需求,加到父节点的子树总需求上 subtree_demand[parent] += subtree_demand[node] # 现在,对于有向树中的每条边 (parent -> child),其需要承载的流量就是 child 的 subtree_demand edge_flow = {} for u, v in directed_tree.edges(): # u是父,v是子 edge_flow[(u, v)] = subtree_demand[v] # 根据流量为每条边选择管径 edge_diameter = {} total_cost_advanced = 0 print("\n=== 考虑流量后的管径优化方案 ===") print("边(父->子) | 所需流量 | 选定管径 | 长度 | 该段成本") print("-" * 60) for (u, v), flow in edge_flow.items(): length = G.edges[u, v]['weight'] # 获取边的长度 # 选择能满足流量的最小成本管径 chosen_diam = None min_cost_for_edge = float('inf') for diam, props in pipe_options.items(): if flow <= props['max_flow']: cost = props['cost_per_unit_length'] * length if cost < min_cost_for_edge: min_cost_for_edge = cost chosen_diam = diam # 理论上,管径选项应覆盖所有流量范围,这里假设总能找到 edge_diameter[(u, v)] = chosen_diam total_cost_advanced += min_cost_for_edge print(f" {u:2d} -> {v:2d} | {flow:7.1f} | {chosen_diam:^8} | {length:4.2f} | {min_cost_for_edge:7.2f}") print(f"\n优化后管网总成本: {total_cost_advanced:.2f}") print(f"相较于仅考虑长度的基础方案(成本 {total_cost_prim:.2f}),成本变化: {total_cost_advanced - total_cost_prim:+.2f}")

注意:这个两阶段方法是启发式的,不一定得到全局最优解(因为拓扑结构是第一阶段单独决定的,可能不是考虑管径成本后的最优拓扑)。但在时间有限的竞赛中,这是一个非常有效且合理的策略。你可以在论文中明确指出这是“分解-优化”的启发式思路,并讨论其优缺点。

5. 论文写作要点与常见陷阱

数学建模竞赛,三分靠模型,七分靠表达。一个清晰、严谨、美观的论文是获胜的关键。

5.1 论文核心结构

  1. 摘要:重中之重!用300字左右概括问题、你的方法、模型、算法、主要结果和结论。要独立成文,让评委不看正文也能知道你们做了什么。模板:“针对XX问题,本文建立了……模型。首先,……;其次,……;然后,运用……算法求解;最后,……。结果表明,……。本文的特色在于……。”
  2. 问题重述与分析:不要照抄题目,要用自己的话梳理问题的背景、条件和目标。进行问题分析,指出问题的类型(优化、预测、评估等)、关键难点和解决思路。
  3. 模型假设与符号说明:列出所有为了简化问题而做出的合理假设。符号说明用三线表,清晰明了。
  4. 模型建立与求解:这是论文主体。分小节阐述你的模型:
    • 5.1 基础模型(如最小生成树模型)
    • 5.2 模型改进(如引入流量与管径的优化模型)
    • 5.3 算法设计(详细说明你用的算法步骤,最好配上流程图)
    • 5.4 求解过程(说明使用的软件、工具包、求解器参数等)
  5. 模型结果与分析
    • 表格和图表展示结果!总成本、管道明细表、网络图。
    • 进行灵敏度分析:改变某个参数(如单位成本、某个用户需求),观察结果如何变化。这能体现模型的稳健性和你的思考深度。例如:“将水源点坐标微调至(0.5, 0.5),总成本变化小于2%,表明模型对水源位置不敏感。”
    • 方案对比:如果你的模型有多个版本(如基础版vs进阶版),一定要对比结果,分析差异原因。
  6. 模型评价与推广:客观评价自己模型的优点(计算快、易于理解、结果合理)和缺点(忽略了地形起伏、假设需求恒定等)。提出模型的改进方向和在类似问题(如电网铺设、通信光缆布局)中的应用前景。
  7. 参考文献与附录:规范引用参考文献。将核心代码、大型数据表格放在附录。

5.2 常见陷阱与避坑指南

  • 陷阱一:模型与算法混淆。在论文中,“模型”是指你用数学公式、变量、约束描述的问题结构;“算法”是求解这个模型的具体计算步骤(如Prim算法、遗传算法)。一定要分开写。
  • 陷阱二:只有结果,没有分析。摆出总成本就完了?不行!要分析这个方案为什么好,管道走向有什么特点,成本主要花在哪里。灵敏度分析是拉开差距的关键。
  • 陷阱三:图表质量低下。用MATLAB或Python画图时,务必保证清晰度。坐标轴标签、图例、标题要齐全。网络图节点不要重叠,可以用nx.spring_layoutnx.kamada_kawai_layout进行布局优化。
  • 陷阱四:忽略单位。长度是米还是公里?成本是元还是万元?流量是升/秒还是立方米/天?全文必须统一,并在符号说明中明确标出。
  • 陷阱五:代码堆砌。附录里的代码要有重点,只放核心函数或算法主流程,并加上必要的注释。不要粘贴全部几十行代码。
  • 陷阱六:假设不合理。假设是为了简化,但不能改变问题的本质。例如,不能假设“所有用户需求为零”来简化问题。你的假设需要让人感觉是“在可控范围内对现实情况的合理近似”。

个人心得:管道铺设问题是一个非常好的练手项目,它像一把钥匙,能帮你打开运筹学、图论和优化算法的大门。在实战中,最关键的一步永远是“问题分析”。花半小时仔细读题、画示意图、列举已知和未知,远比一上来就套公式有效。当你拿到题目,看到那些散落的“点”,能在脑海里自动将它们连成一张“图”,并开始思考“权重”和“约束”时,你就已经成功了一半。剩下的,就是用严谨的数学和清晰的表达,将你的思考呈现出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询