图论 最小生成树算法 Kruskal‘s Algorithm (克鲁斯卡尔算法) Prim‘s Algrorithm(普利姆算法)原理以及python实现

在最小生成树算法中比较经典的算法有两个(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.每次将与当前树中顶点相连的权重最小的边加入,直至所有顶点都被选中

伪代码:

    \noindent \\1.Set\ U=\left \{ r \right \} for some node r\in V,and\ F=\o , \ T=(U,F))\\ 2.while\ U\neq V:\\ \indent \indent determine a min cost edge e \in \delta (U))\\ \indent \indent F=F\cup \left \{ e \right \}, \ U=U\cup \left \{W \right \}\ with e=\left \{ v,w \right \}, \ W=V \setminus U 

例子:(仍然以上图为例)

假设以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算法的输出是一致的。

程序存在许多不足,欢迎大伙看到的教教我怎么写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值