最小生成树

问题

对于给出的一张图求一个生成子图使得最小,其中表示边的边权
这里用一个在线画图工具来作图:传送门

解析

最小生成树问题其实就是在一张图中扣出一棵树来,这棵树有最小的边权和
对于最小生成树问题有两种常用的方法,一是kurskal算法,二是prim算法,下面给出两种算法的推导与图示

Kruskal

算法流程

1.对图的所有边,按边权升序排序
2.从小到大遍历所有的边
3.对于边,若他的两个端点不在同一个集合内,将加入答案集合,并将他的两条边放入同一个集合,否则跳过
4.遍历完所有的边或答案集合中有条边时,算法结束,其中是图的边数

tips

1.可以用一个三元组来储存图中的边,其中是边的两个端点,是边权
2.对于图的联通性,可以用并查集维护,每次查询是否属于同一个集合

算法流程图示

1.给出一张图
图片说明
2.一开始是一张空图
图片说明
3.按照边权的升序,加入一条边,判断并不属于同一个集合,放心加入
图片说明
4.同理,加入边
图片说明
5.同理,加入边
图片说明
6.同理,加入边
图片说明
7.尝试加入边,发现在同一个集合内,不加入这条边
图片说明
8.边又是一条符合条件的边,加入,算法结束
图片说明

prim

算法流程

1.选定一个起点加入答案集合
2.遍历从这集合内的点连向未加入集合的所有点的边,选定一条边权最小的点,加入集合
3.重复操作2直到没有更多的点可以加入

tips

1.采用邻接表存图
2.用一个来表示点是否在集合内
3.采用优先队列可以提高算法的效率

算法流程图示

作为起点
1.一张空图
图片说明
2.以6为起点,将6加入答案集合,从集合连出的所有边中边权最小,将加入
图片说明
3.此时答案集合为,从集合连出的所有边中边权最小,将加入答案集合
图片说明
4.答案集合变为,从集合连出的所有边中边权最小,将加入答案集合
图片说明
6.答案集合变为,从集合连出的所有边中边权最小,将加入答案集合
图片说明
7.答案集合变为,从集合连出的所有边中边权最小,将加入答案集合
图片说明

设计(伪代码)

kruskal

tot=0
count_edge=0
sort(E,key=edge.weight)
for 所有集合E中的边:
    if edge的两个端点在同一个集合中:
        continue
    else:
        count_edge+=1
        tot+=edge.w
        合并edge的两个端点所在的集合
    if count_edge==V.size-1:
        break
return tot

在实际使用中如果边不多,可以不需要统计生成树内的边的数量,因为算法保证了不会出现环

prim

tot=0
set={1}
q={(v,w)|edge[i].u=1}
while set.size < n:
    cur=q中边权最小的点
    tot+=cur.w
    for 所有与cur.v相连的点t:
        if t在set中:
            continue
        else:
            向q中插入(q,W)
    在set中插入cur.v
return tot

可以按边权维护一个小顶堆作为优先队列,每次弹出队首的点加入集合,并访访问所有与该点相联的店,若不在集合中则加入优先队列

分析

在此约定图的点数为,边数为

kruskal

kruskal算法主要分为两部分,一是对边进行排序,二是枚举边同时对点进行集合的合并
对边进行排序可以使用快速排序,归并排序,堆排序等等较为优秀的排序算法,这部分复杂度为
枚举边的复杂度为对点进行集合合并使用并查集来完成,加上并查集后的复杂度很难计算,通过网上查询发现其复杂度大约,可以明确的是小于排序的复杂度
最终复杂度由最高次决定,因此整个算法的复杂度为

prim

prim算法的主要过程有枚举点和找到最短边
首先每个点只会被插入答案集合中一次,因此可以发现这部分的复杂度是
同时对于是否在集合中的判断可以通过数组标记来实现,这部分
对于每插入一个点,我们都需要找到此时联通内外的边,枚举所有点,这部分
最后复杂度为
但是我们可以用优先队列来优化找最小边的过程,优先队列插入的复杂度为,查询的复杂度为,整张图共有个点,所以这部分复杂度是
总的复杂度为

源码