算法步骤
注:当然前提还是得保证图是无向连通的图。
Python代码
import numpy as np
from numpy import inf
def kruskal(D):
# 因为在无向图中不想考虑对角元,将对角元化为无穷大。
t1, t2 = np.where(D == 0)
D[t1, t2] = inf
T = np.zeros([D.shape[0], D.shape[1]]) # 初始化最终的最小生成树带权矩阵
V = [] # 初始化最小生成树顶点集
# 算法迭代主体
while not (len(V) == 1 and len(V[0]) == D.shape[0]):
t1, t2 = np.where(D == D.min())
if len(V) == 0:
V.append([t1[0], t2[0]])
else:
# 找到现考虑的边的两个端点有无包含在已有子树内和在哪棵子树内
pos_1 = [pos for pos, value in enumerate(V) if t1[0] in value]
pos_2 = [pos for pos, value in enumerate(V) if t2[0] in value]
# 新的带权边不含于现存任何一棵子树内
if len(pos_1) == 0 and len(pos_2) == 0:
V.append([t1[0], t2[0]])
else:
# 新的带权边的一个顶点在某棵子树内, 另一个顶点不在任一棵现存子树内
if len(pos_1) == 0 and len(pos_2) == 1:
V[pos_2[0]].append(t1[0])
elif len(pos_1) == 1 and len(pos_2) == 0:
V[pos_1[0]].append(t2[0])
# 新的带权边的两个顶点在两棵不同子树内
elif pos_1[0] != pos_2[0]:
t = V[pos_1[0]] + V[pos_2[0]]
V = [value for pos, value in enumerate(V) if (pos != pos_1[0] and pos != pos_2[0])]
V.append(t)
# 新的带权边的两个顶点在同棵子树内
else:
D[t1[0], t2[0]] = D[t2[0], t1[0]] = inf
continue
T[t1[0], t2[0]] = D[t1[0], t2[0]]
D[t1[0], t2[0]] = D[t2[0], t1[0]] = inf
print(T)
Matlab代码
%% 输入
% % 方式一:输入距离矩阵
% D = [
% ]; % 在这里输入带权邻接矩阵
% % 计算图的顶点数
% [N,~] = size(D);
% % 生成D的边权数据edge,是一个n*3的矩阵,其中第1,2列是边两端顶点的编号,第3列是边权
% edge = [];
% for i = 1:(N-1)
% for j = (i+1):N
% edge = [edge;[i,j,D(i,j)]];
% end
% end
% 方式二:直接输入n*3的边权矩阵
edge = [
];
%% 整理,初始化数据
% 对生成的edge按第3列边权从上向下递增的顺序整理edge
edge = sortrows(edge,3);
NodeNum=max(max(edge(:,1:2))); % 网络顶点数
e1 = length(edge(:,1)); % 网络边数
p = 1; % p是当前连通子树的编号
TreeNode = [edge(1,1), p; edge(1,2), p]; % 当前子树的顶点集
TreeEdge = [edge(1,:)]; % 当前子树的边集
%% 算法迭代主体
for i=2:e1
if length(TreeEdge(:,1)) < (NodeNum-1) % 测试将当前边加入子树是否会形成圈
k1 = 0; k2 = 0;
for j = 1:length(TreeNode(:,1))
if edge(i,1) == TreeNode(j,1)
k1 = 1; p1 = TreeNode(j,2);
end
end
for j = 1:length(TreeNode)
if edge(i,2) == TreeNode(j,1)
k2 = 1; p2 = TreeNode(j,2);
end
end
% 当前边的2个顶点都不在已形成的子树中
if k1 + k2 == 0
TreeEdge = [TreeEdge ; edge(i,:)];
p = max(TreeNode(:,2)) + 1;
TreeNode = [TreeNode;edge(i,1),p;edge(i,2),p ];
end
% 当前边的其中一个顶点不在已形成的子树中
if k1 + k2 == 1
TreeEdge = [TreeEdge;edge(i,:)];
if k1 == 1
p = p1;
TreeNode = [TreeNode;edge(i,2),p];
else
p = p2;
TreeNode = [TreeNode;edge(i,1),p];
end
end
% 当前边的2个顶点分别属于已形成的不同的连通子树中
if k1 + k2 == 2 && p1 ~= p2
TreeEdge = [TreeEdge;edge(i,:)];
if p1 < p2
t = find(TreeNode(:,2) == p2);
TreeNode(t,2) = p1;
else
t = find(TreeNode(:,2) == p1);
TreeNode(t,2) = p2;
end
end
end
end
TreeEdge
例题(无向图)
运行下列代码生成无向图:
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
G.add_weighted_edges_from([('A', 'S', 2), ('A', 'B', 2), ('A', 'D', 7),
('S', 'B', 5), ('B', 'D', 5), ('D', 'T', 5),
('S', 'C', 4), ('B', 'C', 1), ('B', 'E', 3),
('D', 'E', 1), ('T', 'E', 7), ('C', 'E', 4)])
edge_labels = dict([((u, v), d['weight']) for u, v, d in G.edges(data=True)])
pos = nx.spring_layout(G)
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=15)
nx.draw_networkx(G, pos, node_size=400)
plt.show()
题目示意图:
其中各点代表村镇,它们之间的连线表明各村镇之间能否直接交通来往,连线旁边的数字代表道路的长度。现要求沿图中道路架设电线,使得上述村镇全部通上电,应该如何架设使得总的线路长度为最短(即求出最小生成树)。
Python代码求解
# 输入初始的带权邻接矩阵
D = np.array([[0, 2, inf, 7, inf, inf, 2],
[2, 0, 1, 5, 3, inf, 5],
[inf, 1, 0, inf, 4, inf, 4],
[7, 5, inf, 0, 1, 5, inf],
[inf, 3, 4, 1, 0, 7, inf],
[inf, inf, inf, 5, 7, 0, inf],
[2, 5, 4, inf, inf, inf, 0]])
kruskal(D)
结果:
[[0. 2. 0. 0. 0. 0. 2.]
[0. 0. 1. 0. 3. 0. 0.]
[0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 5. 0.]
[0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0.]]
其中非零项对应的边即为树枝。
Matlab代码求解
% 采用方式2的输入, 数据如下
edge = [
1 2 2
1 4 7
1 7 2
2 3 1
2 4 5
2 5 3
2 7 5
3 5 4
3 7 4
4 5 1
4 6 5
5 6 7
];
结果:
TreeEdge =
2 3 1
4 5 1
1 2 2
1 7 2
2 5 3
4 6 5
拓:Python-networkx包求解
T = nx.minimum_spanning_tree(G) # G来源于题目一开始生成无向图的代码
print(sorted(T.edges(data=True)))
结果:
[('A', 'B', {'weight': 2}), ('A', 'S', {'weight': 2}),
('B', 'C', {'weight': 1}), ('B', 'E', {'weight': 3}),
('D', 'E', {'weight': 1}), ('D', 'T', {'weight': 5})]