在最小生成树算法中比较经典的算法有两个(1)Kruskal's Algorithm (克鲁斯卡尔算法)
(2) Prim's Algrorithm(普利姆算法)
(代码在文章最后)
图的最小生成数就是在图中提取出一个树状结构,包含图中所有的顶点,任意两个顶点之间都是可达的,但是不能有环存在,其中该树结构中所有边的权重和在所有其他的由图生成的树中最小
下面首先对两个算法进行介绍:
一、Kruskal's Algorithm (克鲁斯卡尔算法)
伪代码:1.首先将图中所有边按照权重从小到大进行排序
2. 按照排好的顺序依次加入,如果没有形成环路则保存,否则取消,继续下一条边,直至遍历完所有的边
(注:有的博主说,到所有的顶点都被选中就结束是不对的,比如只有选取下面图中三条垂直于X方向的边,会包含所有的顶点,但不是树结构)
例子:(图片来自于百度百科)
假设现在有这样一个图,我们要找到这个图的最小生成树,根据Kruskal's Algorithm (克鲁斯卡尔算法)
1.将边按照权重的大小从小到大进行排序 以(vi,vj)的形式表示两个顶点之间的边
(v1,v3)>(v4,v6)>(v2,v5)>(v3,v6)>(v2,v3)=(v3,v4)=(v1,v4)>(v3,v5)=(v5,v6)=(v1,v2)
2.按照排好的顺序依次加入,如果没有形成环路则保存,否则取消
(1)加入(v1,v3)显然不会形成环路,所以加入
(2)加入(v4,v6)不会形成环路,加入
(3)加入(v2,v5)不会形成环路,加入
(4)加入(v3,v6)不会形成环路,加入
(5)加入(v2,v3)不会形成环路,加入
(6)加入(v3,v4)会形成环路(v3--->v4--->v6--->v3)所以不加入,看下一条边
重复这样的过程,直至遍历所有的边
最后得到的最小生成树如下:
Kruskal算法python实现如下:(以上图为例进行实现)
# -*- coding: utf-8 -*-
"""
Created on Mon Dec 5 20:18:57 2022
@author: 机器不学习ing
"""
V=['V1','V2','V3','V4','V5','V6']
Linjie_mat=[[0,6,1,5,float('inf'),float('inf')],
[6,0,5,float('inf'),3,float('inf')],
[ 1 , 5 , 0 , 5 , 6 , 4 ],
[5,float('inf'),5,0,float('inf'),2],
[float('inf'),3,6,float('inf'),0,6],
[float('inf'),float('inf'),4,2,6,0]]#无向图的邻接矩阵表示,float('inf')表示无穷大,代表两点之间没有边连接
edge_dict={} #以字典的形式保存边和对应的权重
length=len(Linjie_mat)
for i in range(length):
for j in range(i+1,length): #因为是对称的,所以只看上半部分即可
if Linjie_mat[i][j]!=float('inf'): #表示两者之间有边相连
xx={(V[i],V[j]):Linjie_mat[i][j]}
edge_dict.update(xx)#,Linjie_mat[i][j])
print(edge_dict,'\n')
####################下面是对算法进行实现#########################
b_value =dict(sorted(edge_dict.items(),key = lambda item:item[1])) #按照权重进行排序
edge_list= [item for item in b_value]
print(edge_list,'\n')
node_list=[[node] for node in V] #将所有顶点分成不同的部分
MST=[] #最小生成树
for e in edge_list:
temp=1
for i in range(len(node_list)):
if e[1] in node_list[i] and e[0] in node_list[i]: #判断是否形成环
temp=0 #用来控制是否结束当前循环,因为直接实现不太好实现,通过这样一个中间变量以及下面的if temp==0来实现
break
if temp==0:
continue
else:
index=[]
for i in range(len(node_list)):
if e[0] in node_list[i] or e[1] in node_list[i]: #
index.append(i)
node_list[index[0]]=node_list[index[0]]+node_list[index[1]] #将顶点和终点所在的部分进行合并
node_list[index[1]]=[]
node_list=[node for node in node_list if node!=[]]
MST.append(e) #向当前树中加入边
print('最小生成树是',MST) #对最小生成树进行打印
打印结果为:最小生成树是 [('V1', 'V3'), ('V4', 'V6'), ('V2', 'V5'), ('V3', 'V6'), ('V2', 'V3')]
到这里可能会对代码中的node_list变量有一定疑问,下面进行解释:
在一开始,我们将所有的顶点各自看作一部分,有如下结果:
[['V1'], ['V2'], ['V3'], ['V4'], ['V5'], ['V6']]
如果一条边被选中,没有形成环,就将起点与终点所在的部分进行合并,下面是每一步node_list的变化,可以自行打印看一看
[ ['V1', 'V3'], ['V2'], ['V4'], ['V5'], ['V6'] ]
[ ['V1', 'V3'], ['V2'], ['V4', 'V6'], ['V5'] ]
[ ['V1', 'V3'], ['V2', 'V5'], ['V4', 'V6'] ]
[ ['V1', 'V3', 'V4', 'V6'], ['V2', 'V5'] ]
[ ['V1', 'V3', 'V4', 'V6', 'V2', 'V5'] ]
二、Prim's Algorithm (普利姆算法)
prim's Algorithm的流程如下:
流程:1.初始化起点(即随机选取一个顶点作为起点)
2.每次将与当前树中顶点相连的权重最小的边加入,直至所有顶点都被选中
伪代码:
例子:(仍然以上图为例)
假设以V5作为起点,
(1).首先选取V5作为起点,在V5的所有边中选择罪小的边,为(v2,v5),此时U={V2,V5}
F={(v2,v5)}
(2).在从v2和v5的相连的所有边中找权重最小的加入,即(v2,v3),此时U={V2,V5,V3}
F={(V2,V5),(V2,V3)}
(3).再找v2,v5,v3相连的所有边中权重最小的,即(v1,v3)
(4)以相同原则重复上述过程直至所有的顶点都被选入
最后会产生相同的结果:
需要注意的是:因为存在权重相等的边,所以最小生成树会有不同,但是生成树中所有边的权重和是不变的
Prim's Algorithm 的python实现:(以上图为例)
"""
Created on Tue Dec 6 20:45:18 2022
@author:机器不学习ing
"""
import numpy as np
V=['V1','V2','V3','V4','V5','V6']
Linjie_mat=[[0,6,1,5,float('inf'),float('inf')],
[6,0,5,float('inf'),3,float('inf')],
[ 1 , 5 , 0 , 5 , 6 , 4 ],
[5,float('inf'),5,0,float('inf'),2],
[float('inf'),3,6,float('inf'),0,6],
[float('inf'),float('inf'),4,2,6,0]]#无向图的邻接矩阵表示,float('inf')表示无穷大,代表两点之间没有边连接
def Prim(start,Linjie_mat):
index=int(start[1:])-1 #因为python的索引是从0开始,为了将角标与索引统一
distance=[] #保存当前顶点到所有顶点的距离
MST=[] #保存最小生成树
index_list=[] #保存每一步所选中的定点的索引
node_list=[] #保存顶点
while len(node_list)!=len(V)-1: #-1是因为生成树中边的数量比顶点少一
index_list.append(index) #加入这个index
node_list.append(V[index]) #顶点
for i in range(len(Linjie_mat[index])):
if Linjie_mat[index][i]==0:
Linjie_mat[index][i]=float('inf') #为了不选中自身,将0置为无穷大
distance.append(Linjie_mat[index])
distance=np.array(distance) #转换为array,便于找到下面一句找到最小值的索引
index1 ,index2= np.unravel_index(distance.argmin(), distance.shape)
distance[index1][index2]=float('inf')
Linjie_mat[index_list[index1]][index2]=float('inf') #这三句都是防止选中后被再次选中,因为对称,所以交换横纵坐标再次置为inf
Linjie_mat[index2][index_list[index1]]=float('inf')
MST.append((V[index_list[index1]],V[index2])) #把边添加到MST中
index=index2
distance=list(distance) #转换成list便于添加
return MST
MST=Prim('V2', Linjie_mat) #调用Prim() 以V2作为起点
print(MST) #打印最小生成树
输出结果:[('V2', 'V5'), ('V2', 'V3'), ('V3', 'V1'), ('V3', 'V6'), ('V6', 'V4')]
过程是:按照上面的顺序,每次添加一条边。
与Kruskal算法的输出是一致的。
程序存在许多不足,欢迎大伙看到的教教我怎么写。