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-10

import 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\geq10\),由鍵盤輸入;
(2)城市坐標 x, y 在(0~100)之間隨機生成;
(3)輸出線路方案的各段線路及長度。


4.2 程序說明

  1. 這是一個典型的最小生成樹問題。n 個城市構成圖的 n 個頂點,任意兩個頂點之間都有連接邊,邊的權值是兩個頂點的間距。
  2. nx.complete_graph(n) 可以創建一個全連接圖,即任意兩個頂點之間都有連接邊。

4.3 Python 例程

# mathmodel18_v1.py
# Demo18 of mathematical modeling algorithm
# Demo of minimum spanning tree(MST) with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-10

import 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]))  # 向節點添加位置屬性 pos
    posCity.append(G2.nodes[node]["pos"])  # 獲取節點位置屬性 pos

dist = 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.4 運行結果

城市位置:
 {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://www.cnblogs.com/youcans/category/1981091.html)。

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小白的數學建模課-B2.新冠疫情 SI模型
Python小白的數學建模課-B3.新冠疫情 SIS模型
Python小白的數學建模課-B4.新冠疫情 SIR模型
Python小白的數學建模課-B5.新冠疫情 SEIR模型
Python小白的數學建模課-B6.改進 SEIR疫情模型


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM