【数学建模笔记05】数学建模的图与网络

05. 图与网络

基本概念

  • 无向图:一个非空有限集合 V ( G ) V(G) V(G) V ( G ) V(G) V(G) 中的某些元素的无序对集合 E ( G ) E(G) E(G)​ 构成的二元组,记
    G = ( V ( G ) , E ( G ) ) G=(V(G),E(G)) G=(V(G),E(G))
    其中
    V ( G ) = { v 1 , v 2 , … , v n } V(G)=\{v_1,v_2,\dots,v_n\} V(G)={v1,v2,,vn}
    称顶点集或结点集,
    E ( G ) = { e 1 , e 2 , … , e m } E(G)=\{e1,e2,\dots,e_m\} E(G)={e1,e2,,em}
    称边集, E ( G ) E(G) E(G)​ 中的每个元素 e k e_k ek​ 为 V ( G ) V(G) V(G)​ 中某两个元素 v i , v j v_i,v_j vi,vj 的无序对,记
    e k = ( v i , v j )   或   e k = v i v j = v j v i e_k=(v_i,v_j)\ 或\ e_k=v_iv_j=v_jv_i ek=(vi,vj)  ek=vivj=vjvi
    称从 v i v_i vi​ 到 v j v_j vj 的边;

    • 有限图:结点集和边集都有限的图;
    • 简单图:没有环也没有两条边连接同一对结点。
  • 有向图:一个有向图 G G G 由一个非空有限集合 V V V V V V 中的某些元素的有序对集合 A A A 构成二元组,记
    G = ( V , A ) G=(V,A) G=(V,A)
    其中
    V = { v 1 , v 2 , … , v n } V=\{v1,v2,\dots,v_n\} V={v1,v2,,vn}
    称顶点集或结点集,
    A = { a 1 , a 2 , … , a m } A=\{a_1,a_2,\dots,a_m\} A={a1,a2,,am}
    称弧集, A A A 中的每个元素 a k a_k ak V V V 中某两个元素的有序对,记
    a k = ( v i , v j ) a_k=(v_i,v_j) ak=(vi,vj)
    称从 v i v_i vi v j v_j vj 的弧;

  • 完全图:每一对不同的顶点都有一条边相连的简单图;

  • 子图:如果 V ( H ) ⊂ V ( G ) , E ( H ) ⊂ E ( G ) V(H)\sub V(G),E(H)\sub E(G) V(H)V(G),E(H)E(G)​,称 H H H​ 是 G G G​ 的子图, G G G H H H​ 的母图;

  • 结点的度:设 v ∈ V ( G ) v\in V(G) vV(G) G G G 中与 v v v 关联的边数称 v v v 的度,记 d ( v ) d(v) d(v)。​

问题求解

最短路

对于给定的赋权图 G = ( V , E , W ) G=(V,E,W) G=(V,E,W),其中 V V V 为结点集, E E E​ 为边集,邻接矩阵 KaTeX parse error: Undefined control sequence: \cp at position 14: W=(w_{ij})_{n\̲c̲p̲ ̲n},其中
w i j = { v i , v j 之间边的权值 , v i , v j 之间有边 , ∞ , v i , v j 之间无边 . w_{ij}=\left\{\begin{aligned} &v_i,v_j\text{之间边的权值},&v_i,v_j\text{之间有边}, \\ &\infty,&v_i,v_j\text{之间无边}. \end{aligned}\right. wij={vi,vj之间边的权值,,vi,vj之间有边,vi,vj之间无边.

w i i = 0 , i = 1 , 2 , … , n w_{ii}=0,i=1,2,\dots,n wii=0,i=1,2,,n

固定起点 Dijkstra 算法

u 0 u_0 u0 V V V 中某个固定起点,求 u 0 u_0 u0 V V V 中另一顶点 v 0 v_0 v0 的最短距离和最短路径。

Dijkstra 的步骤:

  1. 为每个结点维护:

    • l ( v ) l(v) l(v): ​表示起点到结点 v v v 的长度;
    • z ( v ) z(v) z(v): 表示结点 v v v 的父结点。
  2. l ( u 0 ) = 0 l(u_0)=0 l(u0)=0,对 v ≠ u 0 v\ne u_0 v=u0,令
    l ( v ) = ∞ , z ( v ) = u 0 , S 0 = u 0 , i = 0. l(v)=\infty,z(v)=u_0,S_0={u_0},i=0. l(v)=,z(v)=u0,S0=u0,i=0.

  3. 对每个 v ∉ S i v\notin S_i v/Si,令
    l ( v ) = min ⁡ u [ l ( v ) , l ( u ) + w ( u v ) ] , l(v)=\min_u[l(v),l(u)+w(uv)], l(v)=umin[l(v),l(u)+w(uv)],
    若此次迭代利用结点 u ^ \hat{u} u^​ 修改了 l ( v ) l(v) l(v),则 z ( v ) = u ^ z(v)=\hat{u} z(v)=u^,否则不变;

  4. 将结点 argmin v [ l ( v ) ] \underset{v}{\text{argmin}}[l(v)] vargmin[l(v)] 加入 S i S_i Si,构成新的集合;

  5. i = ∣ V ∣ − 1 i=|V|-1 i=V1 v 0 v_0 v0 进入 S i S_i Si​,算法终止;否则转步骤 3。

每对结点 Floyd 算法

Floyd 算法:

对于赋权图 G = ( V , E , A 0 ) G=(V,E,A_0) G=(V,E,A0)​​,对于邻接矩阵:
A 0 = ( a 11 a 12 … a 1 n a 21 a 22 … a 2 n ⋮ ⋮ ⋱ ⋮ a n 1 a n 2 … a n n ) A_0=\begin{pmatrix} a_{11}&a_{12}&\dots&a_{1n}\\ a_{21}&a_{22}&\dots&a_{2n}\\ \vdots&\vdots&\ddots&\vdots\\ a_{n1}&a_{n2}&\dots&a_{nn}\\ \end{pmatrix} A0=a11a21an1a12a22an2a1na2nann
有迭代公式:
a k ( i , j ) = min ⁡ [ a k − 1 ( i , j ) , a k − 1 ( i , k ) + a k − 1 ( k , j ) ] a_k(i,j)=\min[a_{k-1}(i,j),a_{k-1}(i,k)+a_{k-1}(k,j)] ak(i,j)=min[ak1(i,j),ak1(i,k)+ak1(k,j)]
其中 k k k 是迭代次数。

如果要求得最短路径,还需引入路由矩阵
KaTeX parse error: Undefined control sequence: \cp at position 19: …k=(r_k(i,j))_{n\̲c̲p̲ ̲n}

  • 初始时
    KaTeX parse error: Undefined control sequence: \cp at position 10: R_0=O_{n\̲c̲p̲ ̲n}

  • 迭代公式为
    KaTeX parse error: Undefined control sequence: \cp at position 19: …k=(r_k(i,j))_{n\̲c̲p̲ ̲n}
    其中
    r k ( i , j ) = { k , a k − 1 ( i , j ) > a k − 1 ( i , k ) + a k − 1 ( k , j ) , r k − 1 ( i , j ) , O t h e r s . r_k(i,j)=\left\{\begin{aligned} &k,a_{k-1}(i,j)>a_{k-1}(i,k)+a_{k-1}(k,j),\\ &r_{k-1}(i,j),Others. \end{aligned}\right. rk(i,j)={k,ak1(i,j)>ak1(i,k)+ak1(k,j),rk1(i,j),Others.

最小生成树

在赋权图 G G G 中,边权之和最小的生成树称最小生成树。

Kruskal 算法

步骤:

  1. 初始化 E ′ = ∅ E'=\varnothing E=​;
  2. 每次从 E − E ’ E-E’ EE​ 中选取一条边,加入 E ′ E' E,要求使得 E ′ E' E​​​​ 不构成圈的边中权值最小;
  3. 直到选取 n − 1 n-1 n1 个边为止 ( n 为 结 点 个 数 ) (n 为结点个数) (n)
Prime 算法

步骤:

  1. 初始化 P = { v 1 } , Q = ∅ P=\{v_1\},Q=\varnothing P={v1},Q=​​;

  2. 每次找最短边 p v pv pv,其中 p ∈ P , v ∈ V − P p\in P,v\in V-P pP,vVP,使
    P = P + { v } , Q = Q + { p v } . P=P+\{v\},Q=Q+\{pv\}. P=P+{v},Q=Q+{pv}.

  3. 直到 P P P 中包含所有结点。

最大流

给定有向图 D = ( V , A ) D=(V,A) D=(V,A)​​,在 V V V​ 中指定发点 (源) v s v_s vs​ 和收点 (汇) v t v_t vt​。对于每一条弧,对应有一个 c ( v i , v j ) ≥ 0 c(v_i,v_j)\ge0 c(vi,vj)0​ 称容量,构成一个网络记
D = ( V , A , C ) D=(V,A,C) D=(V,A,C)
其中 C = { c i j } C=\{c_{ij}\} C={cij}

满足下列条件的流 f f f 称可行流:

  • 容量限制:对每一弧 ( v i , v j ) ∈ A (v_i,v_j)\in A (vi,vj)A 0 ≤ f i j ≤ c i j 0\le f_{ij}\le c_{ij} 0fijcij

  • 平衡:
    ∑ j : ( v i , v j ) ∈ A f i j − ∑ j : ( v i , v j ) ∈ A f j i = { v , i = s , − v , i = t , 0 , i ≠ s , t . \sum_{j:(v_i,v_j)\in A}f_{ij}-\sum_{j:(v_i,v_j)\in A}f_{ji}=\left\{\begin{aligned} v,i=s,\\ -v,i=t,\\ 0,i\ne s,t. \end{aligned}\right. j:(vi,vj)Afijj:(vi,vj)Afji=v,i=s,v,i=t,0,i=s,t.
    即,发点的流出为 v v v,收点的流入为 v v v,其余点流出量=流入量。

根据上述条件,可以构造线性规划模型
max ⁡ v , \max v, maxv,

s . t . { ∑ j : ( v i , v j ) ∈ A f i j − ∑ j : ( v i , v j ) ∈ A f j i = { v , i = s , − v , i = t , 0 , i ≠ s , t , 0 ≤ f i j ≤ c i j . s.t.\left\{\begin{aligned} &\sum_{j:(v_i,v_j)\in A}f_{ij}-\sum_{j:(v_i,v_j)\in A}f_{ji}=\left\{\begin{aligned} v,i=s,\\ -v,i=t,\\ 0,i\ne s,t, \end{aligned}\right.\\ &0\le f_{ij}\le c_{ij}. \end{aligned}\right. s.t.j:(vi,vj)Afijj:(vi,vj)Afji=v,i=s,v,i=t,0,i=s,t,0fijcij.

不过对于图论模型,有效率更高的解决方法。

Ford-Fulkerson 算法

步骤:

  1. 初始化一个残差网络,与原网络相同,为每条弧 a i a_i ai​​​ 初始化空闲流量 (即未使用的流量) r ( a i ) = c ( a i ) r(a_i)=c(a_i) r(ai)=c(ai)​​​,其中 c ( a i ) c(a_i) c(ai)​​​​ 为容量;

  2. 找到一个从发点到收点的路径,记经过的弧集
    A ′ = { a 1 ′ , a 2 ′ , … , a m ′ } A'=\{a_1',a_2',\dots,a_m'\} A={a1,a2,,am}
    取空闲流量最小值作为路径的流量,对每条弧更新空闲流量
    r ( a i ′ ) = r ( a i ′ ) − min ⁡ i = 1 m r ( a i ′ ) . r(a_i')=r(a_i')-\min_{i=1}^m r(a_i'). r(ai)=r(ai)i=1minmr(ai).

  3. 对找到的路径生成反向路径,记经过的弧集
    A ′ ′ = { a m ′ ′ , a m − 1 ′ ′ , … , a 1 ′ ′ } A''=\{a_m'',a_{m-1}'',\dots,a_1''\} A={am,am1,,a1}
    其中 a i ′ ′ a_i'' ai 即为 a i ′ a_i' ai 的反向弧,每条弧的空闲流量即为之前的正向路径扣除的流量
    r ( a i ′ ′ ) = min ⁡ i = 1 m r ( a i ′ ) . r(a_i'')=\min_{i=1}^mr(a_i'). r(ai)=i=1minmr(ai).

  4. 合并相同方向的弧,使它们的空闲流量相加;

  5. 直到找不到发点到收点的路径,迭代结束,否则转步骤 2;

  6. 令初始网络的每条弧的容量 c ( a i ) c(a_i) c(ai) 与迭代后的残差网络对应的弧的闲流量 r ( a i ) r(a_i) r(ai) 相减,即为该条弧的流量
    f ( a i ) = c ( a i ) − r ( a i ) . f(a_i)=c(a_i)-r(a_i). f(ai)=c(ai)r(ai).

Networkx 包

networkx 是一个用 Python 开发的图论与复杂网络建模工具,内置常用的图与复杂网络的分析算法。

常用函数如下:

  • Graph(): 创建无向图;
  • DiGraph(): 创建有向图;
  • add_edge():加边;
  • add_edge_from():加多条边;
  • add_node():加结点;
  • add_node_from():加多个结点;
  • dijkstra_path():求最短路径;
  • dijkstra_path_length():求最短距离。

画图

对于邻接矩阵表示的无向图:
A = ( 0 9 2 4 7 9 0 3 4 0 2 3 0 8 4 4 4 8 0 6 7 0 4 6 0 ) A=\begin{pmatrix} 0&9&2&4&7\\ 9&0&3&4&0\\ 2&3&0&8&4\\ 4&4&8&0&6\\ 7&0&4&6&0 \end{pmatrix} A=0924790340230844480670460
画图代码如下:

# %%

import numpy as np
import networkx as nx

# %%

A = np.array([[0, 9, 2, 4, 7],
              [9, 0, 3, 4, 0],
              [2, 3, 0, 8, 4],
              [4, 4, 8, 0, 6],
              [7, 0, 4, 6, 0]])
G = nx.Graph(A)

# %%

pos=nx.spring_layout(G, iterations=20)

nx.draw_networkx(G, pos=pos)
nx.draw_networkx_edge_labels(G,pos=pos,
                             edge_labels=nx.get_edge_attributes(G, 'weight'))

输出如下:

求最短路

对下图:

3
2
4
6
2
8
1
3
2
5
7
3
v1
v2
v3
v4
v5
v6
v7
固定起点

求解 v 1 v_1 v1​ 到其余所有结点的最短路和最短距离,代码如下:

# %%

import numpy as np
import networkx as nx

# %%

G = nx.DiGraph()
G.add_weighted_edges_from([
    ('v1', 'v2', 3),
    ('v1', 'v3', 2),
    ('v2', 'v4', 4),
    ('v2', 'v5', 6),
    ('v2', 'v6', 2),
    ('v3', 'v2', 8),
    ('v3', 'v4', 1),
    ('v3', 'v6', 3),
    ('v4', 'v5', 2),
    ('v6', 'v5', 5),
    ('v5', 'v7', 7),
    ('v6', 'v7', 3),
])

# %%

path = nx.dijkstra_path(G, source='v1',
                        target='v7',
                        weight='weight')
dist = nx.dijkstra_path_length(G, source='v1',
                               target='v7',
                               weight='weight')

path, dist

输出如下:

(['v1', 'v3', 'v6', 'v7'], 8)

于是有,最短路径为 v 1 → v 3 → v 6 → v 7 v_1\to v_3\to v_6\to v_7 v1v3v6v7,最短距离为 8。

每对结点

对于上述问题,求解任意每对结点间的最短距离和路径,代码如下:

# %%

import numpy as np
import networkx as nx

# %%

G = nx.DiGraph()
G.add_weighted_edges_from([
    ('v1', 'v2', 3),
    ('v1', 'v3', 2),
    ('v2', 'v4', 4),
    ('v2', 'v5', 6),
    ('v2', 'v6', 2),
    ('v3', 'v2', 8),
    ('v3', 'v4', 1),
    ('v3', 'v6', 3),
    ('v4', 'v5', 2),
    ('v6', 'v5', 5),
    ('v5', 'v7', 7),
    ('v6', 'v7', 3),
])

# %%

path = nx.shortest_path(G, weight='weight')
length = nx.shortest_path_length(G, weight='weight')
dict(path), dict(length)

输出如下:

({'v1': {'v1': ['v1'],
   'v2': ['v1', 'v2'],
   'v3': ['v1', 'v3'],
   'v4': ['v1', 'v3', 'v4'],
   'v6': ['v1', 'v3', 'v6'],
   'v5': ['v1', 'v3', 'v4', 'v5'],
   'v7': ['v1', 'v3', 'v6', 'v7']},
  'v2': {'v2': ['v2'],
   'v4': ['v2', 'v4'],
   'v5': ['v2', 'v5'],
   'v6': ['v2', 'v6'],
   'v7': ['v2', 'v6', 'v7']},
  'v3': {'v3': ['v3'],
   'v2': ['v3', 'v2'],
   'v4': ['v3', 'v4'],
   'v6': ['v3', 'v6'],
   'v5': ['v3', 'v4', 'v5'],
   'v7': ['v3', 'v6', 'v7']},
  'v4': {'v4': ['v4'], 'v5': ['v4', 'v5'], 'v7': ['v4', 'v5', 'v7']},
  'v5': {'v5': ['v5'], 'v7': ['v5', 'v7']},
  'v6': {'v6': ['v6'], 'v5': ['v6', 'v5'], 'v7': ['v6', 'v7']},
  'v7': {'v7': ['v7']}},
 {'v1': {'v1': 0, 'v3': 2, 'v2': 3, 'v4': 3, 'v6': 5, 'v5': 5, 'v7': 8},
  'v2': {'v2': 0, 'v6': 2, 'v4': 4, 'v7': 5, 'v5': 6},
  'v3': {'v3': 0, 'v4': 1, 'v6': 3, 'v5': 3, 'v7': 6, 'v2': 8},
  'v4': {'v4': 0, 'v5': 2, 'v7': 9},
  'v5': {'v5': 0, 'v7': 7},
  'v6': {'v6': 0, 'v7': 3, 'v5': 5},
  'v7': {'v7': 0}})

可以发现,如 v 1 v_1 v1 v 7 v_7 v7​ 的最短距离为 8,最短路径为 v 1 → v 3 → v 6 → v 7 v_1\to v_3\to v_6\to v_7 v1v3v6v7​,与之前的结论相同,其余结点间也可以查阅。

求最小生成树

对画图示例的邻接矩阵求最小生成树,代码如下:

# %%

import numpy as np
import networkx as nx

# %%

A = np.array([[0, 9, 2, 4, 7],
              [9, 0, 3, 4, 0],
              [2, 3, 0, 8, 4],
              [4, 4, 8, 0, 6],
              [7, 0, 4, 6, 0]])
G = nx.Graph(A)

# %%

T = nx.minimum_spanning_tree(G)

# %%

pos=nx.spring_layout(T, iterations=20)

nx.draw_networkx(T, pos=pos)
nx.draw_networkx_edge_labels(T,pos=pos,
                             edge_labels=nx.get_edge_attributes(T, 'weight'))

输出如下:

求最大流

对于求最短路的示例,将权值看作容量,求 v 1 v_1 v1 v 7 v_7 v7 的最大流,代码如下:

# %%

import numpy as np
import networkx as nx

# %%

G = nx.DiGraph()

edge_list = [
    ('v1', 'v2', 3),
    ('v1', 'v3', 2),
    ('v2', 'v4', 4),
    ('v2', 'v5', 6),
    ('v2', 'v6', 2),
    ('v3', 'v2', 8),
    ('v3', 'v4', 1),
    ('v3', 'v6', 3),
    ('v4', 'v5', 2),
    ('v6', 'v5', 5),
    ('v5', 'v7', 7),
    ('v6', 'v7', 3),
]

for edge in edge_list:
    G.add_edge(edge[0], edge[1], capacity=edge[2])

# %%

value, flow_dic = nx.maximum_flow(G, 'v1', 'v7')

# %%

value, flow_dic

# %%

dic = {}
for s in flow_dic:
    for t in flow_dic[s]:
        dic[(s,t)] = flow_dic[s][t]

pos = nx.spring_layout(G)
nx.draw_networkx(G, pos=pos)
nx.draw_networkx_edge_labels(T,pos=pos,
                             edge_labels=dic)

输出如下:

(5,
 {'v1': {'v2': 3, 'v3': 2},
  'v2': {'v4': 0, 'v5': 3, 'v6': 0},
  'v3': {'v2': 0, 'v4': 0, 'v6': 2},
  'v4': {'v5': 0},
  'v5': {'v7': 3},
  'v6': {'v5': 0, 'v7': 2},
  'v7': {}})

最大流为 5,并给出具体流向和流量。
e, flow_dic

%%

dic = {}
for s in flow_dic:
for t in flow_dic[s]:
dic[(s,t)] = flow_dic[s][t]

pos = nx.spring_layout(G)
nx.draw_networkx(G, pos=pos)
nx.draw_networkx_edge_labels(T,pos=pos,
edge_labels=dic)


输出如下:

```python
(5,
 {'v1': {'v2': 3, 'v3': 2},
  'v2': {'v4': 0, 'v5': 3, 'v6': 0},
  'v3': {'v2': 0, 'v4': 0, 'v6': 2},
  'v4': {'v5': 0},
  'v5': {'v7': 3},
  'v6': {'v5': 0, 'v7': 2},
  'v7': {}})

[外链图片转存中…(img-CFykI3AE-1627132572853)]

最大流为 5,并给出具体流向和流量。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数学建模是一门重要而又有趣的学科,它是将数学的方法与现实问题相结合的过程。在进行数学建模的过程笔记的记录是非常重要的,可以帮助我们更好地理解问题、掌握建模方法,并且方便后期的复习和总结。 我想将我的数学建模笔记手写在CSDN上,主要出于以下几个原因。首先,手写笔记能够培养我对数学建模概念的理解和记忆能力。通过亲自动手书写数学模型、公式和解题步骤,我可以更好地掌握知识点,避免只是机械地复制粘贴或者直接照抄书的内容。 其次,通过手写笔记,我可以更好地记录自己在建模过程的思考和想法。数学建模是一个灵活而创造性的过程,每个人对问题的理解和解决方式不尽相同。在手写笔记,我可以更加自由地表达自己的思路和想法,将自己独特的见解与他人分享。 此外,手写笔记也可以提高我对数学建模问题的整体把握能力。在手写过程,我需要整理和提炼一些关键的概念和知识点,并将它们以更简洁、更清晰的方式呈现出来。这种整合和概括的过程可以帮助我更好地理解问题的本质和解决思路,并将其与其他相关知识进行联系,形成一个更完整的知识体系。 最后,将数学建模笔记手写在CSDN上,可以与其他同学和科研者进行交流与讨论。CSDN是一个专注于计算机科学与技术的知识分享平台,拥有众多对数学建模感兴趣的读者和作者。通过将自己的笔记分享在CSDN上,可以获得更多人的意见和建议,从而不断完善自己的建模能力。 总而言之,数学建模笔记的手写在CSDN上,不仅可以帮助我提升对数学建模的理解和记忆能力,还可以促进思考、整理和交流能力,对于提升自己的数学建模能力具有重要意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值