Python小白的数学建模课-18.最小生成树问题

  • 最小生成树(MST)是图论中的基本问题,具有广泛的实际应用,在数学建模中也经常出现。
  • 路线设计、道路规划、官网布局、公交路线、网络设计,都可以转化为最小生成树问题,如要求总线路长度最短、材料最少、成本最低、耗时最小。
  • 最小生成树的典型算法有普里姆算法(Prim算法)和克鲁斯卡算法(Kruskal算法)。
  • 本文基于 NetworkX 工具包,通过例程详细介绍最小生成树问题的求解。
  • 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛达人。

1. 最小生成树

1.1 生成树

树是图论中的基本概念。连通的无圈图称为树(Tree),就是不包含循环的回路的连通图。

对于无向连通图,如下图所示,生成树(Spanning tree)是原图的极小连通子图,它包含原图中的所有 n 个顶点,并且有保持图连通的最少的边,即只有足以构成一棵树的 n-1 条边。

生成树满足:(1)包含连通图中所有的顶点;(2)任意两顶点之间有且仅有一条通路。因此,生成树中边的数量 = 顶点数 - 1。

对于非连通无向图, 遍历每个连通分量中的顶点集合所经过的边是多颗生成树,这些连通分量的生成树构成非连通图的生成森林 。

1.2 最小生成树和最大生成树

遍历连通图的方式通常有很多种,也就是说一张连通图可能有多种不同的生成树。

无向赋权图的生成树中,各条边的权重之和最小的生成树,称为最小生成树(minimum spanning tree,MST),也称最小权重生成树。

对应地,各条边的权重之和最大的生成树,称为最大生成树(maximum spanning tree)。

1.3 最小生成树问题

最小生成树(MST)是图论中的基本问题,具有广泛的实际应用,在数学建模中也经常出现。

例如,在若干城市之间铺设通信线路,使任意两个城市之间都可以通信,要使铺设线路的总费用最低,就需要找到最小生成树。类似地,路线设计、道路规划、官网布局、公交路线、网络设计,都可以转化为最小生成树问题,如要求总线路长度最短、材料最少、成本最低、耗时最小等。

在实际应用中,不仅要考虑网络连通,还要考虑连通网络的质量和效率,就形成了带有约束条件的最小生成树:

直径限制最小生成树(Bounded diameter minimum spanning tree):对给定的连通图,满足直径限制的生成树中,具有最小权的树,称为直径限制最小生成树。直径限制最小生成树问题在资源优化问题中应用广泛,如网络设计的网络直径影响到网络的传输速度、效率和能耗。

度限制最小生成树(Degree constrained minimum spanning tree):对给定的连通图,满足某个节点或全部节点的度约束(如入度不超过 k)的生成树中,具有最小权的树,称为度限制最小生成树。实际应用中,为了控制节点故障对整个系统的影响,需要对节点的度进行限制。


2. 最小生成树算法

构造最小生成树的算法很多,通常是从空树开始,按照贪心法逐步选择并加入 n-1 条安全边(不产生回路),最终得到最小生成树。

最小生成树的典型算法有普里姆算法(Prim算法)和克鲁斯卡算法(Kruskal算法)。

2.1 普里姆算法(Prim算法)

Prim 算法以顶点为基础构造最小生成树,每个顶点只与连通图连接一次,因此不用考虑在加入顶点的过程中是否会形成回路。

算法从某一个顶点 s 开始,每次选择剩余的代价最小的边所对应的顶点,加入到最小生成树的顶点集合中,逐步扩充直到包含整个连通网的所有顶点,可以称为“加点法”。

Prim 算法中图的存贮结构采用邻接矩阵,使用一个顶点集合 u 构造最小生成树。由于不断向集合u中加点,还需要建立一个辅助数组来同步更新最小代价边的信息。

Prim 算法每次选择顶点时,都需要进行排序,但每次都只需要对一部分边进行排序。Prim 算法的时间复杂度为 O(n*n),与边的数量无关,适用于边很多的稠密图。

采用堆实现优先队列来维护最小点,可以将Prim算法的时间复杂度降低到 O(mlogn),称为Prim_heap 算法,但该算法的空间消耗很大。

2.2 克鲁斯卡算法(Kruskal算法)

Kruskal 算法以边为基础构造最小生成树,利用避圈思想,每次找到不使图构成回路的代价最小的边。

算法初始边数为 0,每次选择一条满足条件的最小代价边,加入到边集合中,逐步扩充直到包含整个生成树,可以称为“加边法”。

Kruskal 算法中图的存贮结构采用边集数组,权值相等的边在数组中的排列次序是任意的。Kruskal算法开始就要对所有的边进行排序,之后还需要对所有边应用 Union-Find算法,但不再需要排序。

Kruskal 算法的时间复杂度为 O(mlogm),主要是对边排序的时间复杂度,适用于边较少的稀疏图。


3. NetworkX 的最小生成树算法

3.1 NetworkX 的最小/最大生成树函数

函数 功能
minimum_spanning_tree(G[, weight,…]) 计算无向图上的最小生成树
maximum_spanning_tree(G[, weight,…]) 计算无向图上的最大生成树
minimum_spanning_edges(G[, algorithm,…]) 计算无向加权图最小生成树的边
maximum_spanning_edges(G[, algorithm,…]) 计算无向加权图最大生成树的边

3.2 minimum_spanning_tree() 使用说明

minimum_spanning_tree(G, weight=‘weight’, algorithm=‘kruskal’, ignore_nan=False)

minimum_spanning_edges(G, algorithm=‘kruskal’, weight=‘weight’, keys=True, data=True, ignore_nan=False)

minimum_spanning_tree() 用于计算无向连通图的最小生成树(森林)。minimum_spanning_edges() 用于计算无向连通图的最小生成树(森林)的边。
对于连通无向图,计算最小生成树;对于非连通无向图,计算最小生成森林。

主要参数:

  • G(undirected graph):无向图。
  • weight(str):指定用作计算权重的边属性。
  • algorithm(string):计算最小生成树的算法,可选项为 ‘kruskal’、‘prim’ 或 ‘boruvka’。默认算法为 ‘kruskal’。
  • data(bool):指定返回值是否包括边的权值。
  • ignore_nan(bool) :在边的权重为 Nan 时产生异常。

返回值:

  • minimum_spanning_tree() 的返回值是由最小生成树构成的图,类型为 NetworkX Graph,需要用 T.edges() 获得对应的最小生成树的边。
  • minimum_spanning_edges() 的返回值是最小生成树的构成边,类型为<class ‘generator’>,需要用 list() 转换为列表数据。

3.3 案例:天然气管道铺设问题

问题描述:

某市区有 7个小区需要铺设天然气管道,各小区的位置及可能的管道路线、费用如图所示,要求设计一个管道铺设路线,使天然气能输送到各个小区,且铺设管道的总费用最小。

程序说明:

这是一个最小生成树问题,用 NetworkX 的 minimum_spanning_tree() 函数即可求出费用最小的管道路线。

  1. 图的输入。本例为稀疏的有权无向图,使用 G.add_weighted_edges_from() 函数以列表向图中添加多条赋权边,每个赋权边以元组 (node1,node2,weight) 表示。
  2. nx.minimum_spanning_tree() 和 nx.tree.minimum_spanning_edges() 都可以计算最小生成树,参数设置和属性也基本一致,区别主要在于返回值的格式和调用方式。

Python 例程:

# mathmodel18_v1.py
# Demo18 of mathematical modeling algorithm
# Demo of minimum spanning tree(MST) with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-10import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包# 1. 天然气管道铺设
G1 = nx.Graph()  # 创建:空的 无向图
G1.add_weighted_edges_from([(1,2,5),(1,3,6),(2,4,2),(2,5,12),(3,4,6),(3,6,7),(4,5,8),(4,7,4),(5,8,1),(6,7,5),(7,8,10)])  # 向图中添加多条赋权边: (node1,node2,weight)T = nx.minimum_spanning_tree(G1)  # 返回包括最小生成树的图
print(T.nodes)  # 最小生成树的 顶点
print(T.edges)  # 最小生成树的 边
print(sorted(T.edges)) # 排序后的 最小生成树的 边
print(sorted(T.edges(data=True)))  # data=True 表示返回值包括边的权重mst1 = nx.tree.minimum_spanning_edges(G1, algorithm="kruskal") # 返回最小生成树的边
print(list(mst1))  # 最小生成树的 边
mst2 = nx.tree.minimum_spanning_edges(G1, algorithm="prim",data=False)  # data=False 表示返回值不带权
print(list(mst2))# 绘图
pos={1:(1,5),2:(3,1),3:(3,9),4:(5,5),5:(7,1),6:(6,9),7:(8,7),8:(9,4)}  # 指定顶点位置
nx.draw(G1, pos, with_labels=True, node_color='c', alpha=0.8)  # 绘制无向图
labels = nx.get_edge_attributes(G1,'weight')
nx.draw_networkx_edge_labels(G1,pos,edge_labels=labels, font_color='m') # 显示边的权值
nx.draw_networkx_edges(G1,pos,edgelist=T.edges,edge_color='b',width=4)  # 设置指定边的颜色
plt.show()

程序运行结果:

[1, 2, 3, 4, 5, 6, 7, 8]
[(1, 2), (1, 3), (2, 4), (4, 7), (4, 5), (5, 8), (6, 7)]
[(1, 2), (1, 3), (2, 4), (4, 5), (4, 7), (5, 8), (6, 7)]
[(1, 2, {'weight': 5}), (1, 3, {'weight': 6}), (2, 4, {'weight': 2}), (4, 5, {'weight': 8}), (4, 7, {'weight': 4}), (5, 8, {'weight': 1}), (6, 7, {'weight': 5})]
[(5, 8, {'weight': 1}), (2, 4, {'weight': 2}), (4, 7, {'weight': 4}), (1, 2, {'weight': 5}), (6, 7, {'weight': 5}), (1, 3, {'weight': 6}), (4, 5, {'weight': 8})]
[(1, 2), (2, 4), (4, 7), (7, 6), (1, 3), (4, 5), (5, 8)]

4. 案例:建设通信网络

4.1 问题描述

在 n 个城市架设 n-1 条线路,建设通信网络。任意两个城市之间都可以建设通信线路,且单位长度的建设成本相同。求建设通信网络的最低成本的线路方案。

(1)城市数 n≥10n\geq10n≥10,由键盘输入;
(2)城市坐标 x, y 在(0~100)之间随机生成;
(3)输出线路方案的各段线路及长度。

4.1 程序说明

  1. 这是一个典型的最小生成树问题。n 个城市构成图的 n 个顶点,任意两个顶点之间都有连接边,边的权值是两个顶点的间距。
  2. 输入。
  3. 本例程对应案例中的各项约束条件: 必须经过图中的绿色节点;必须经过图中的两段绿色路段;必须避开图中的红色路段;尽可能以最少的花费到达终点。
  4. 本例程是一个遍历简单路径、判断约束条件的通用框架。
  5. all(n in path for n in (2,4,7,12,13,14)) 的作用,一是判断路径中是否包括必经点 N7、N12;二是判断路径中是否包括必经边 (2,4)、(13,14) 的各顶点,这不仅可以减小计算量,而且能确保下面使用 index() 查找顶点位置时不会发生错误。

4.2 Python 例程

# mathmodel18_v1.py
# Demo18 of mathematical modeling algorithm
# Demo of minimum spanning tree(MST) with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-10import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包
from scipy.spatial.distance import pdist, squareform# # 2. 城市通信网络建设
# nCities = input("Input number of cities (n>=10):")
# nCities = int(nCities)
nCities = 20
np.random.seed(1)
xPos = np.random.randint(0, 100, nCities)  # 生成 [0,100) 均匀分布的随机整数
yPos = np.random.randint(0, 100, nCities)  # 生成 Ncities 个城市坐标posCity = []
G2 = nx.complete_graph(nCities)  # 创建:全连接图
for node in G2.nodes():G2.add_node(node, pos=(xPos[node], yPos[node]))  # 向节点添加位置属性 posposCity.append(G2.nodes[node]["pos"])  # 获取节点位置属性 posdist = squareform(pdist(np.array(posCity)))  # 计算所有节点之间的距离
for u, v in G2.edges:G2.add_edge(u, v, weight=np.round(dist[u][v],decimals=1))  # 向边添加权值 dist(u,v)T = nx.minimum_spanning_tree(G2, algorithm='kruskal')  # 返回包括最小生成树的图
print("\n城市位置:\n",G2._node)
print("\n通信网络:\n",sorted(T.edges(data=True)))  # data=True 表示返回值包括边的权重
# mst = nx.tree.minimum_spanning_edges(G2, algorithm="kruskal") # 返回最小生成树的边
# for edge in sorted(list(mst)):
#     print(edge)fig, ax = plt.subplots(figsize=(8, 6))
node_pos = nx.get_node_attributes(G2, 'pos')  # 顶点位置
nx.draw(G2,node_pos,with_labels=True,node_color='c',edge_color='silver',node_size=300,font_size=10,font_color='r',alpha=0.8)  # 绘制无向图
# nx.draw_networkx_labels(G2, node_pos, labels=node_pos, font_size=6, horizontalalignment='left', verticalalignment='top')  # 绘制顶点属性:位置坐标 pos
# edge_col = ['red' if edge in T.edges() else 'silver' for edge in G2.edges()]  # 设置边的颜色
# nx.draw_networkx_edges(G2, node_pos, edge_color=edge_col, width=2)  # 设置指定边的颜色
nx.draw_networkx_edges(G2, node_pos, edgelist=T.edges, edge_color='r', width=2)  # 设置指定边的颜色
edge_weight = nx.get_edge_attributes(T, 'weight')  # 边的权值
nx.draw_networkx_edge_labels(T, node_pos, edge_labels=edge_weight, font_size=8, font_color='m', verticalalignment='top')  # 显示边的权值
plt.axis('on')  # Remove the axis
plt.xlim(-5, 100)
plt.ylim(-5, 100)
plt.show()

4.3 运行结果

城市位置:{0: {'pos': (37, 29)}, 1: {'pos': (12, 14)}, 2: {'pos': (72, 50)}, 3: {'pos': (9, 68)}, 4: {'pos': (75, 87)}, 5: {'pos': (5, 87)}, 6: {'pos': (79, 94)}, 7: {'pos': (64, 96)}, 8: {'pos': (16, 86)}, 9: {'pos': (1, 13)}, 10: {'pos': (76, 9)}, 11: {'pos': (71, 7)}, 12: {'pos': (6, 63)}, 13: {'pos': (25, 61)}, 14: {'pos': (50, 22)}, 15: {'pos': (20, 57)}, 16: {'pos': (18, 1)}, 17: {'pos': (84, 0)}, 18: {'pos': (11, 60)}, 19: {'pos': (28, 81)}}通信网络:[(0, 1, {'weight': 29.2}), (0, 14, {'weight': 14.8}), (0, 15, {'weight': 32.8}), (1, 9, {'weight': 11.0}), (1, 16, {'weight': 14.3}), (2, 4, {'weight': 37.1}), (2, 14, {'weight': 35.6}), (3, 8, {'weight': 19.3}), (3, 12, {'weight': 5.8}), (4, 6, {'weight': 8.1}), (4, 7, {'weight': 14.2}), (5, 8, {'weight': 11.0}), (8, 19, {'weight': 13.0}), (10, 11, {'weight': 5.4}), (10, 17, {'weight': 12.0}), (11, 14, {'weight': 25.8}), (12, 18, {'weight': 5.8}), (13, 15, {'weight': 6.4}), (15, 18, {'weight': 9.5})]


版权声明:

欢迎关注『Python小白的数学建模课 @ Youcans』 原创作品

原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/118566422)。

Copyright 2021 Youcans, XUPT

Crated:2021-07-12

欢迎关注 『Python小白的数学建模课 @ Youcans』 系列,持续更新
Python小白的数学建模课-01.新手必读
Python小白的数学建模课-02.数据导入
Python小白的数学建模课-03.线性规划
Python小白的数学建模课-04.整数规划
Python小白的数学建模课-05.0-1规划
Python小白的数学建模课-06.固定费用问题
Python小白的数学建模课-07.选址问题
Python小白的数学建模课-09.微分方程模型
Python小白的数学建模课-10.微分方程边值问题
Python小白的数学建模课-12.非线性规划
Python小白的数学建模课-15.图论的基本概念
Python小白的数学建模课-16.最短路径算法
Python小白的数学建模课-17.条件最短路径算法
Python小白的数学建模课-18.最小生成树问题
Python小白的数学建模课-19.网络流优化问题
Python小白的数学建模课-20.网络流优化案例
Python小白的数学建模课-21.关键路径法
Python小白的数学建模课-A1.国赛赛题类型分析
Python小白的数学建模课-21.关键路径法
Python小白的数学建模课-22.插值方法
Python小白的数学建模课-A1.国赛赛题类型分析
Python小白的数学建模课-A2.2021年数维杯C题探讨
Python小白的数学建模课-A3.12个新冠疫情数模竞赛赛题及短评
Python小白的数学建模课-B2. 新冠疫情 SI模型
Python小白的数学建模课-B3. 新冠疫情 SIS模型
Python小白的数学建模课-B4. 新冠疫情 SIR模型
Python小白的数学建模课-B5. 新冠疫情 SEIR模型
Python小白的数学建模课-B6. 新冠疫情 SEIR改进模型

Python小白的数学建模课-18.最小生成树问题相关推荐

  1. Python小白的数学建模课-22.插值方法

    Python小白的数学建模课-22.插值方法 插值.拟合.回归和预测,都是数学建模中经常提到的概念,也经常被混淆. 插值,是在离散数据的基础上补插连续函数,使得插值函数通过全部给定的离散数据点,多用于 ...

  2. Python小白的数学建模课-23.数据拟合全集

    拟合是用一个连续函数(曲线)靠近给定的离散数据,使其与给定的数据相吻合. 数据拟合的算法相对比较简单,但调用不同工具和方法时的函数定义和参数设置有所差异,往往使小白感到困惑. 本文基于 Scipy 工 ...

  3. Python小白的数学建模课-17.条件最短路径算法

    条件最短路径问题,指带有约束条件.限制条件的最短路径问题.例如: 顶点约束,包括必经点或禁止点的限制: 边的约束,包括必经路段.禁行路段和单向路段:无权路径长度的限制,如要求经过几步或不超过几步到达终 ...

  4. Python小白的数学建模课-15.图论的基本概念

    图论中所说的图,不是图形图像或地图,而是指由顶点和边所构成的图形结构. 图论不仅与拓扑学.计算机数据结构和算法密切相关,而且正在成为机器学习的关键技术. 本系列结合数学建模的应用需求,来介绍 Netw ...

  5. Python小白的数学建模课-10.微分方程边值问题

    小白往往听到微分方程就觉得害怕,其实数学建模中的微分方程模型不仅没那么复杂,而且很容易写出高水平的数模论文. 本文介绍微分方程模型边值问题的建模与求解,不涉及算法推导和编程,只探讨如何使用 Pytho ...

  6. Python小白的数学建模课-07.选址问题

    选址问题是要选择设施位置使目标达到最优,是数模竞赛中的常见题型. 小白不一定要掌握所有的选址问题,但要能判断是哪一类问题,用哪个模型. 进一步学习 PuLP工具包中处理复杂问题的字典格式快捷建模方法. ...

  7. Python小白的数学建模课-06.固定费用问题

    Python 实例介绍固定费用问题的建模与求解. 学习 PuLP工具包中处理复杂问题的快捷使用方式. 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛达人. 前文讲到几种典型 ...

  8. Python小白的数学建模课-05.0-1规划

    0-1 规划不仅是数模竞赛中的常见题型,也具有重要的现实意义. 双十一促销中网购平台要求二选一,就是互斥的决策问题,可以用 0-1规划建模. 小白学习 0-1 规划,首先要学会识别 0-1规划,学习将 ...

  9. Python小白的数学建模课-04.整数规划

    整数规划与线性规划的差别只是变量的整数约束. 问题区别一点点,难度相差千万里. 选择简单通用的编程方案,让求解器去处理吧. 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛达 ...

最新文章

  1. 根据表名如何查找使用它的程序名、接口等
  2. Windows下Nutch的配置
  3. 在linux下使用多个tomcat
  4. 360手机浏览器_360手机浏览器9.0新功能测评
  5. 现金贷平台倒闭后,借的钱是否可以不还?
  6. Sklearn之Ensemble 估计器
  7. UNIX高手的20个习惯
  8. 严重漏洞可导致 Juniper 设备遭劫持或破坏
  9. QT 异步函数和同步函数交换问题
  10. citrix终端linux,Citrix XenDesktop发布Centos 7.2桌面(六)--安装Linux VDA
  11. nvme SSD和sata SSD的对比
  12. Mysql根据出生日期计算年龄
  13. python util
  14. 四书《孟子》《论语》《中庸》《大学》五经《风》《雅》《颂》全文
  15. 渣基础:比照Hawstein学Cracking the coding interview(2)
  16. 立冬了,小伙伴们要注意保暖哦!
  17. 新媒体运营:2019年微信改版,裂变增长如何做? 黎想
  18. 百趣代谢组学资讯:项目文章Nature,揭示低温暴露抑制实体瘤生长机制,‘饿死’癌细胞
  19. wma转mp3怎么弄_wma转mp3格式转换器
  20. uniapp最全面的知识总结

热门文章

  1. 怎么在QQ浏览器上使用微信聊天?
  2. springboot前后端分离后权限原理浅谈
  3. springboot 使用 minio
  4. shiro框架,自定义realm注入service失败解决办法
  5. java获取当月有几天_腾讯程序员裸辞3个月,转行去送外卖,曝出当月收入网友:又骗我去送外卖...
  6. 世外桃源六python_六年匠心 桃花源记6月1日全民狂欢
  7. linux第三方模块参数,nginx 的第三方模块ngx_http_accesskey_module 来实现下载文件的防盗链步骤(linux系统下)...
  8. vs2019Linux守护,Visual Studio 2019将支援Ninja显着提升Linux专案建置效率
  9. MyBatis中or和and的使用问题
  10. Spring Boot 设置 ASCII banner 艺术字