最小生成树算法

一、Prim算法

概论

适合稠密图,不进行堆优化的时间复杂度是\(O(n^2)\),进行堆优化则是\(O(mlogn)\)
每次将离连通部分最近的点点对应的边加入的连通部分,连通部分逐渐扩大,最后将整个图连通起来,并且边长之和最小,基于一种贪心的策略。
证明的引理
对于任意切割 (S, V-S),其中 S 是生成树 T 的节点集合,V-S 是未包含在 T 中的节点集合,存在一条横切边连接 S 和 V-S 中的顶点,并且该边的权重是最小的。

image
礼貌拿图:@Hasity【https://www.acwing.com/solution/content/38312/】
如果像记录生成树的边的话,可以用一个pre数组,在更新时记录前驱节点!
image

代码

# Prim算法求最小生成树

from math import inf
n,m = map(int,input().split())
g = [[inf] * (n + 1) for _ in range(n + 1)]
for _ in range(m):
    u,v,w = map(int,input().split())
    g[u][v] = g[v][u] = min(g[u][v],w)

vis = [False] * (n + 1)
dis = [inf] * (n + 1)

ans = 0
# dis[i]保存的是节点i与已连通树的最短距离
# 首先将1节点作为生成树开始的起点
dis[1] = 0

for _ in range(n):
    t = -1
    # 寻找距离连通部分距离最短的节点将其纳入生成树的范围
    for i in range(1,n + 1):
        if not vis[i] and (t == -1 or dis[t] > dis[i]):
            t = i
    vis[t] = True
    # 如果一个点距离已结成的生成树的距离为inf,那么说明不存在一棵树将所有点串起来
    if dis[t] == inf:
        print("impossible")
        exit()
    # 加 上 那 条 边
    ans += dis[t]
    # 用新加入t点去更新剩余的点到生成树的最小距离!
    for j in range(1,n + 1):
        if dis[j] > g[t][j]:
            dis[j] = g[t][j]
print(ans)

Kruskal算法

概论

适用于稀疏图,时间复杂度为max(O(mlogm),O((m + n)logu)
通过贪心策略逐步添加边,确保最终得到的生成树是最小的。
image

代码

import sys

input = lambda:sys.stdin.readline()
read = lambda: map(int, input().split())
edges = []
n, m = read()
fa = [i for i in range(n + 1)]

# 并查集
def find(x):
    if x != fa[x]:
        fa[x] = find(fa[x])
    return fa[x]

# 存边
for _ in range(m):
    u, v, w = read()
    edges.append([w, u, v])
# 对边进行排序,每次选取不在已生成生成树中最短的边
edges.sort()
cnt = 0
res = 0
for w, u, v in edges:
    fu, fv = find(u), find(v)
    if fu != fv:
        res += w
        fa[fu] = fv
        cnt += 1
# 如果小于 n - 1条边,那么说明没有最小生成树
if cnt < n - 1:
    print("impossible")
else:
    print(res)
posted @ 2024-04-08 21:03  gebeng  阅读(34)  评论(0编辑  收藏  举报