联系数计加油站获取论文和代码等资源。
邮箱:MathComputerSharing@outlook.com
微信公众号:数计加油站
致谢:清风数学建模
关系的抽象化
人们很喜欢抽象。象可能无辜。但人确实能感到便捷。
让我们再一次地扬起鞭子,抽一次象:关系是什么?
关系,首先是两个对象的关系。让我们把这两个对象抽象为两个结点,更直白一点,两个圈。关系,是这两个结点所共有的某种性质。比如,“爸爸”结点和“妈妈”结点,他们的关系是“夫妻”。我们用一条直线来展示这种关系,俗称边。
通常,我们更关心数值关系,于是,关系被特定化为一个数字,即所谓权重。
将所有的两两关系都描绘出来,我们就得到了关系图。
图的具体化
最常见的图模型,就是交通道路。研究交通道路的问题,能帮助我们把图模型具体化。
结点,就是某个地点。
边,就是某两个地点之间的道路。
权重,就是某两个地点之间的路程,即道路长。
我们可以研究连通问题:两个地点相不相通。有没有哪个地方会绕圈。
我们可以研究最短路径问题:两个地点之间走哪条路线才能最近。
我们可以研究最小生成树问题: 要在路上铺电线,怎样才能兼顾各个地点,而且消耗最小?
图的算法
剪不断,理还乱。图论中的算法很多很多。理解它们的原理,对我们来说,是一种很好的"心智锻炼"。
可以在网络上搜索相关资料,尤其是算法可视化资料,可能会有很大的帮助。
图很复杂,我们的人生又何尝不是?或者说,更为复杂。
一篇关于图的简短论文
Matlab代码
Dijkstra.m
function [path, distance] = Dijkstra(G, u0, v0)
% Dijkstra算法求最短路径
% 初始化参数
n = G.numnodes;
nodes = 1:n;
i = 0;
d_now = ones(n, 1) * inf;
d_now(u0) = 0;
parents = ones(n, 1) * u0;
in_s = u0;
history = [];
% 遍历所有终点或v0进入最短路径集
while (i ~= n - 1) && ~(ismember(v0, in_s))
not_in_s = setdiff(nodes, in_s);
for v = not_in_s
for u = in_s
% 查找uv间的边
uv_index = G.findedge(u, v);
if uv_index
% 边存在则获取权
w = min(G.Edges.Weight(uv_index));
% 计算新的最短距离
d_now(v) = min([d_now(v), d_now(u) + w]);
if d_now(u) + w <= d_now(v)
% 距离发生改变
parents(v) = u;
% 记录变化
history = [history; [u, v]];
end
end
end
end
% 寻找最短距离的顶点
tmp = d_now;
tmp(in_s) = inf;
[~, new_u] = min(tmp);
new_u = new_u(1);
% 添加最短路径顶点
in_s = union(new_u, in_s);
i = i + 1;
end
% 倒溯最短路径
last = v0;
path = v0;
while last ~= u0
k = find(history(:, 2) == last);
last = history(k(end), 1);
path = [last, path];
end
distance = d_now(v0);
end
Floyd.m
function [distances, paths] = Floyd(G)
% Floyd算法求所有顶点对之间的最短路径
% 矩阵初始化
distances = G.adjacency('weighted');
n = length(distances);
distances(distances == 0) = inf;
distances(1:n + 1:end) = 0;
R = zeros(n);
for k = 1:n
for i = 1:n
for j = 1:n
% 插点更新
if distances(i, k) + distances(k, j) < distances(i, j)
distances(i, j) = distances(i, k) + distances(k, j);
R(i, j) = k;
end
end
end
end
distances = full(distances);
% 复原路径
paths = returnPaths(R);
for i = 1:n
for j = 1:n
if i ~= j
% 添加起点和终点
paths{i, j} = [i paths{i, j} j];
else
paths{i, j} = i;
end
end
end
end
function paths = returnPaths(R)
% 根据路由矩阵返回最短路径矩阵
n = length(R);
paths = cell(n);
for i = 1:n
for j = 1:n
paths{i, j} = parse(i, j);
end
end
% 递归推导最短路径
function path = parse(i, j)
p = R(i, j);
if p == 0
path = [];
else
left = parse(i, p);
right = parse(p, j);
path = [left, p, right];
end
end
end
Kruskal.m
function [resultgraph, minw] = Kruskal(G)
% Kruskal算法求最小生成树
% 初始化参数
W = full(G.adjacency("weighted"));
n = length(W);
W(W == 0) = inf;
minw = 0;
resultgraph = graph();
i = 1;
while i < n
% 寻找最小权值边
w_min = min(W, [], "all");
[index_x, index_y] = find(W == w_min, 1);
% 添加最小权值边
resultgraph = resultgraph.addedge(index_x, index_y, w_min);
% 判断添加该边后图是否有圈
if hascycles(resultgraph)
% 有圈则删除该边,并标记pass
resultgraph = resultgraph.rmedge(index_x, index_y);
W(index_x, index_y) = inf;
W(index_y, index_x) = inf;
else
% 无圈则保持,并标记pass
W(index_x, index_y) = inf;
W(index_y, index_x) = inf;
minw = minw + w_min;
i = i + 1;
end
end
end
Prim.m
function [resultgraph, minw] = Prim(G)
% Prim算法求解最小生成树
% 初始化参数
W = full(G.adjacency("weighted"));
n = length(W);
W(W == 0) = inf;
V = 1:n;
V_added = [1];
E_added = [];
minw = 0;
% 直至V和V_added相等
while ~isequal(V, sort(V_added))
tmp = W;
V_not_added = setdiff(V, V_added);
% 圈定p,v范围
tmp(V_not_added, :) = inf;
tmp(:, V_added) = inf;
w_min = min(tmp, [], "all");
minw = minw + w_min;
[p, v] = find(tmp == w_min, 1);
% pv边标记pass
W(p, v) = inf;
W(v, p) = inf;
% 更新
V_added = union(V_added, v);
E_added = [E_added; p, v, w_min];
end
s = E_added(:, 1);
t = E_added(:, 2);
w = E_added(:, 3);
resultgraph = graph(s, t, w);
end
GraphDemo.m
clc,clear
%% 无向赋权图通过邻接矩阵创建
figure(1);
% 输入邻接矩阵A
A = [ 0 0 10 60;
0 0 5 20;
10 5 0 1;
60 20 1 0];
% 输入结点名
nodes=["v_1","v_2","v_3","v_4"];
% 使用邻接矩阵A和结点名nodes创建赋权无向图
G = graph(A,nodes);
% plot函数显示图
plot(G,"EdgeLabel",G.Edges.Weight,"NodeFontSize",12);
%% 无向赋权图通过顶点对创建
figure(2);
% 输入一个顶点
s = [1 1 2 2 3];
% 输入另一个顶点
t = [3 4 3 4 4];
% 输入结点名
nodes = ["v_1","v_2","v_3","v_4"];
% 输入权值
weights = [10 60 5 20 1];
% 使用顶点对s,t和结点名nodes创建赋权无向图
G = graph(s,t,weights,nodes);
% plot函数显示图
plot(G,"EdgeLabel",G.Edges.Weight,"NodeFontSize",12)
%% 最短路径
% 输入图的顶点对
s = [1,1,1,1,2,2,2,3,3,4];
t = [2,3,4,5,3,4,5,4,5,5];
w = [4.5,1.3,3.3,5.6,3.2,1.1,0.9,1.9,4.2,2.2];
figure(3);
% 创建并绘制图
G=graph(s,t,w);
fig1=plot(G,"EdgeLabel",G.Edges.Weight,'EdgeLabelColor','green');
% 使用Dijkstra算法求得最短路径并在图中标出
[path,distance]=Dijkstra(G,1,5);
highlight(fig1,path,'EdgeColor','red','LineWidth',2,"EdgeLabelColor","red");
% 使用Floyd算法求所有最短路径
[distances,paths]=Floyd(G);
%% 最小生成树
s1 = [1,1,1,1,1,1,1,1,2,2,3,4,5,6,7,8];
t1 = [2,3,4,5,6,7,8,9,3,9,4,5,6,7,8,9];
w1 = [2,1,3,4,4,2,5,4,4,1,1,1,5,2,3,5];
G = graph(s1,t1,w1);
% Kruskal算法求解最小生成树
[min_tree, min_w] = Kruskal(G);
figure(4);
% 绘图并加粗最小生成树
fig2 = plot(G, "EdgeLabel", G.Edges.Weight, 'EdgeLabelColor', 'green');
highlight(fig2, min_tree, "EdgeColor", "red", "LineWidth", 4);
figure(5);
% Prim算法求解最小生成树
[tree_min,w_min] = Prim(G);
% 绘图并加粗最小生成树
fig3=plot(G,"EdgeLabel",G.Edges.Weight,'EdgeLabelColor','green');
highlight(fig3,tree_min,"EdgeColor","red","LineWidth",4);
Python代码
导库
import networkx as nx
import numpy as np
import pandas as pd
通过顶点对创建无向赋权图
# 输入边和权
s1 = [f"$v_{i}$" for i in [1, 1, 2, 2, 3]]
t1 = [f"$v_{i}$" for i in [3, 4, 3, 4, 4]]
w1 = [10, 60, 5, 20, 1]
edges = list(zip(s1, t1, w1))
# 创建空图并添加顶点和边权
G1 = nx.Graph()
G1.add_weighted_edges_from(edges)
# 计算顶点位置
pos1 = nx.spring_layout(G1)
# 绘制无权图
nx.draw(G1, pos1, with_labels=True, font_size=14)
# 追加绘制权
labels = nx.get_edge_attributes(G1, 'weight')
fig1 = nx.draw_networkx_edge_labels(G1, pos1, edge_labels=labels, font_color="red")
Dijkstra算法
def Dijkstra(G, u0, v0):
"""利用Dijkstra计算最短路径和距离"""
# 初始化参数
n = G.number_of_nodes()
nodes = list(range(1, n + 1))
d_now = np.ones(n) * np.inf
d_now[u0 - 1] = 0
parents = [u0 for i in range(n)]
s = set()
s.add(u0)
history = []
i = 0
# 遍历所有终点或v0进入最短路径顶点集
while (i != n - 1) or (v0 not in s):
not_s = set(nodes) - s
for v in not_s:
for u in s:
try:
# 检查顶点u,v间是否有边
w = G.edges[u, v]['weight']
except:
pass
else:
d_now[v - 1] = min([d_now[v - 1], d_now[u - 1] + w])
if d_now[u - 1] + w <= d_now[v - 1]:
# 顶点最短值改变,记录改变
parents[v - 1] = u
history.append([u, v])
# 寻找当前的最小距离点,并加入s集合
tmp = d_now.copy()
in_s_index = [i - 1 for i in s]
tmp[in_s_index] = np.inf
new_u = np.argmin(tmp) + 1
s.add(new_u)
i = i + 1
# 拼接最终的最短路径
last = v0
path = [v0]
history = np.array(history)
while last != u0:
k = np.where(history[:, 1] == last)
last = history[k[-1][-1], 0]
path.append(last)
path = path[-1::-1]
distance = d_now[v0 - 1]
return path, distance
# 输入边和权
edges = [(1, 2, 4.5), (1, 3, 1.3), (1, 4, 3.3), (1, 5, 5.6), (2, 3, 3.2), (2, 4, 1.1), (2, 5, 0.9), (3, 4, 1.9),
(3, 5, 4.2), (4, 5, 2.2)]
# 创建空图并添加顶点和边权
G2 = nx.Graph()
G2.add_weighted_edges_from(edges)
# 计算顶点位置
pos2 = nx.spring_layout(G2)
# 绘制无权图
nx.draw(G2, pos2, with_labels=True, font_size=14)
# 绘制最短路径
path, distance = Dijkstra(G2, 1, 5)
shortest_edges = []
for i in range(np.size(path) - 1):
shortest_edges.append((path[i], path[i + 1]))
nx.draw_networkx_edges(G2, pos2, edgelist=shortest_edges, edge_color='red')
# 追加绘制权
labels = nx.get_edge_attributes(G2, 'weight')
fig2 = nx.draw_networkx_edge_labels(G2, pos2, edge_labels=labels, font_color="green")
Floyd算法
def returnPaths(R):
"""根据路由矩阵还原路径"""
def parse(i, j):
"""递归解析i,j顶点最短路径"""
p = R[i, j]
if p == 0:
path = []
else:
path = [p]
left = parse(i, p - 1)
right = parse(p - 1, j)
path.extend(right)
for num in left[-1::-1]:
path.insert(0, num)
return path
n, n = np.shape(R)
paths = [[] for i in range(n)]
for i in range(n):
for j in range(n):
paths[i].append(parse(i, j))
return paths
def Floyd(G):
"""Floyd算法求所有顶点间的最短路径"""
# 初始化矩阵
distances = nx.to_numpy_matrix(G)
n, n = np.shape(distances)
distances[distances == 0] = np.inf
row, col = np.diag_indices_from(distances)
distances[row, col] = 0
R = np.zeros((n, n), dtype=int)
# 插点更新
for k in range(n):
for i in range(n):
for j in range(n):
if distances[i, k] + distances[k, j] < distances[i, j]:
distances[i, j] = distances[i, k] + distances[k, j]
R[i, j] = k + 1
# 复原路径
paths = returnPaths(R)
# 路径添加起点和终点
for i in range(n):
for j in range(n):
if i != j:
paths[i][j].append(j + 1)
paths[i][j].insert(0, i + 1)
else:
paths[i][j] = [i + 1]
return distances, paths
distances, paths = Floyd(G2)
# 最短路径长
n = G2.number_of_nodes()
d_df = pd.DataFrame(distances, index=range(1, n + 1), columns=range(1, n + 1))
d_df
# 最短路径集
p_df = pd.DataFrame(paths, index=range(1, n + 1), columns=range(1, n + 1))
p_df
Kruskal算法
def Kruskal(G):
"""Kruskal算法求解最小生成树"""
# 初始化参数
W = nx.to_numpy_matrix(G)
n, n = np.shape(W)
W[W == 0] = np.inf
minw = 0
resultgraph = nx.Graph()
i = 1
while i < n:
# 寻找最小权值边
w_min = np.min(W)
index_x, index_y = [
np.where(W == w_min)[0][0],
np.where(W == w_min)[1][0]
]
# 添加最小权值边
resultgraph.add_edge(index_x + 1,
index_y + 1,
weight=G.edges[index_x + 1,
index_y + 1]['weight'])
try:
# 判断添加该边后图是否有圈
nx.find_cycle(resultgraph)
except:
# 无圈则保持,并标记pass
W[index_x, index_y] = np.inf
W[index_y, index_x] = np.inf
minw = minw + w_min
i = i + 1
else:
# 有圈则删除该边,并标记pass
resultgraph.remove_edge(index_x + 1, index_y + 1)
W[index_x, index_y] = np.inf
W[index_y, index_x] = np.inf
return resultgraph, minw
# 输入边和权
edges = [(1, 2, 2), (1, 3, 1), (1, 4, 3), (1, 5, 4), (1, 6, 4), (1, 7, 2), (1, 8, 5), (1, 9, 4),
(2, 3, 4), (2, 9, 1), (3, 4, 1), (4, 5, 1), (5, 6, 5), (6, 7, 2), (7, 8, 3), (8, 9, 5)]
# 创建空图并添加顶点和边权
G3 = nx.Graph()
G3.add_weighted_edges_from(edges)
# 计算顶点位置
pos3 = nx.spring_layout(G3)
# 绘制无权图
nx.draw(G3, pos3, with_labels=True, font_size=14)
# Kruskal算法求解最小生成树
min_tree, wmin = Kruskal(G3)
# 绘制最小生成树
nx.draw_networkx_edges(G3, pos3, edgelist=min_tree.edges, edge_color="red", width=3)
# 追加绘制权
labels = nx.get_edge_attributes(G3, 'weight')
edges = nx.draw_networkx_edge_labels(G3,
pos3,
edge_labels=labels,
font_color="green")
Prim算法
def Prim(G):
"""Prim算法求解最小生成树"""
# 初始化参数
W = nx.to_numpy_matrix(G)
n, n = np.shape(W)
W[W == 0] = np.inf
V = set(range(1, n + 1))
V_added = {1}
E_added = []
minw = 0
# 直至V和V_added相等
while V != V_added:
tmp = W.copy()
V_not_added = V - V_added
# 圈定p,v范围
index_x = [i - 1 for i in V_not_added]
tmp[index_x, :] = np.inf
index_y = [i - 1 for i in V_added]
tmp[:, index_y] = np.inf
w_min = np.min(tmp)
minw = minw + w_min
p, v = np.where(tmp == w_min)[0][0] + 1, np.where(
tmp == w_min)[1][0] + 1
# pv边标记pass
W[p - 1, v - 1] = np.inf
W[v - 1, p - 1] = np.inf
# 更新
V_added.add(v)
E_added.append((p, v, w_min))
resultgraph = nx.Graph()
resultgraph.add_weighted_edges_from(E_added)
return resultgraph, minw
# 绘制无权图
nx.draw(G3, pos3, with_labels=True, font_size=14)
# Prim算法求解最小生成树
min_tree, minw = Prim(G3)
# 绘制最小生成树
nx.draw_networkx_edges(G3, pos3, edgelist=min_tree.edges, edge_color="red", width=3)
# 追加绘制权
labels = nx.get_edge_attributes(G3, 'weight')
p_edges = nx.draw_networkx_edge_labels(G3,
pos3,
edge_labels=labels,
font_color="green")