最小生成树算法Prim与Kruskal详解
Prim算法
- 概述
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。 - 算法描述(一般用于解决稠密图的最小生成树)
初始化:一个加权连通图g[][],用于记录图中节点到树的距离的dist[],初始化为无穷大
主体:取图中任一点v作为起始点,进行n次循环,每次选取距离树最近的点,并用新选取的点更新其余点到树的距离
注意:如果选取的点(除第一次的点)到树的距离为无穷,则说明图不连通,也就不存在最小生成树
例题:
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
数据范围
1≤n≤500,
1≤m≤105,
图中涉及边的边权的绝对值均不超过 10000。
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
N = 510
INF = 1000010
g = [[INF] * N for _ in range(N)]
st = [False] * N
dist = [INF] * N
def prim() :
res = 0
for i in range(1, n + 1) : ###循环n次
t = -1
for j in range(1, n + 1) :
if not st[j] and (t == -1 or dist[t] > dist[j]) :
t = j
st[t] = True
if t - 1 and dist[t] == INF : ####除第一次选择的外不用判断
return INF
if t - 1 :####除第一次选择的外不用判断
res += dist[t]
for j in range(1, n + 1) :
if dist[j] > g[t][j] :#####到树的距离就是图的权值
dist[j] = g[t][j]
return res
n, m = map(int, input().split())
for i in range(m) :
x, y, z = map(int, input().split())
g[x][y] = min(g[x][y], z)
g[y][x] = min(g[y][x], z)
res = prim()
if res == INF :
print("impossible")
else :
print(res)
Kruskal算法
Kruskal算法中用到了并查集,那我们就讲一下吧
并查集
-
定义:
并查集,是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。 -
应用:
- 快速的将两个集合合并
- 询问两个元素是否在一个集合当中
-
主要操作
- 初始化
把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为。 - 查找
查找元素所在的集合,即根节点。 - 合并
将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
- 初始化
-
基本原理:
每个集合用一棵树来表示,树根编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点
判断树根:
if p[x] == x
x的集合编号 :while p[x] != x : x = p[x]
路径压缩优化:while p[x] != x : x = p[x]
如何合并两个集合:p[find(x)] = find(y)
例题:
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m 个操作,操作共有两种:
M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。
输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。
每个结果占一行。
数据范围
1≤n,m≤10^5
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
N = 100010
p = [i for i in range(N)]
def find(x) :
if x != p[x] :
p[x] = find(p[x])
return p[x]
def join(a, b) :
fa, fb = find(a), find(b)
if fa != fb :
p[fa] = fb
n, m = map(int, input().split())
for i in range(m) :
cmd = input().split()
if cmd[0] == 'M' :
join(int(cmd[1]), int(cmd[2]))
else :
if find(int(cmd[1])) == find(int(cmd[2])) :
print("Yes")
else :
print("No")
给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
Q2 a,询问点 a 所在连通块中点的数量;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。
输出格式
对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No。
对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤10^5
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
难度:简单
时/空限制:1s / 64MB
N = 100010
p = [i for i in range(N)]
size = [1] * N
def find(x) :
if x != p[x] :
p[x] = find(p[x])
return p[x]
def join(a, b) :
fa, fb = find(a), find(b)
if fa != fb :
size[fb] += size[fa]
p[fa] = fb
n, m = map(int, input().split())
for i in range(m) :
cmd = input().split()
if cmd[0] == 'C' :
join(int(cmd[1]), int(cmd[2]))
elif cmd[0] == 'Q1' :
if find(int(cmd[1])) == find(int(cmd[2])) :
print("Yes")
else :
print("No")
else :
print(size[find(int(cmd[1]))])
介绍完并查集,我们就正式开始我们Kruskal的学习吧!
- 定义:
克鲁斯卡尔算法是求连通网的最小生成树的另一种方法。与普里姆算法不同,它的时间复杂度为O(eloge)(e为网中的边数),所以,适合于求边稀疏的网的最小生成树。 - 基本思想:
克鲁斯卡尔(Kruskal)算法从另一途径求网的最小生成树。其基本思想是:假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),概述图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点分别在T中不同的连通分量上,则将此边加入到T中;否则,舍去此边而选择下一条代价最小的边。依此类推,直至T中所有顶点构成一个连通分量为止 。 - 图像示意
例题:
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
数据范围
1≤n≤10^5,
1≤m≤2∗10^5,
图中涉及边的边权的绝对值均不超过 1000。
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
N = 100010
INF = 1000010
p = [i for i in range(N)]
E = []
def find(x) :
if x != p[x] :
p[x] = find(p[x])
return p[x]
def kruskal() :
E.sort(key = lambda x : x[2])
res = 0
cnt = 0
for i in range(m) :
a, b, w = E[i][0], E[i][1], E[i][2]
a, b = find(a), find(b)
if a != b :
p[a] = b
res += w
cnt += 1
if cnt < n - 1 : return INF
return res
n, m = map(int, input().split())
for i in range(m) :
u, v, w = map(int, input().split())
E.append([u, v, w])
res = kruskal()
if res == INF :
print("impossible")
else :
print("res")
总结
最小生成树结束,注意并查集的使用哦,最值得注意的是合并时的一些操作
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!