最小生成树算法Prim与Kruskal详解

Prim算法

  1. 概述
    普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。
  2. 算法描述(一般用于解决稠密图的最小生成树)
    初始化:一个加权连通图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算法中用到了并查集,那我们就讲一下吧

并查集
  1. 定义:
    并查集,是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

  2. 应用:

    • 快速的将两个集合合并
    • 询问两个元素是否在一个集合当中
  3. 主要操作

    • 初始化
      把每个点所在集合初始化为其自身。
      通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为。
    • 查找
      查找元素所在的集合,即根节点。
    • 合并
      将两个元素所在的集合合并为一个集合。
      通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
  4. 基本原理:

    每个集合用一棵树来表示,树根编号就是整个集合的编号,每个节点存储它的父节点,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的学习吧!

  1. 定义:
    克鲁斯卡尔算法是求连通网的最小生成树的另一种方法。与普里姆算法不同,它的时间复杂度为O(eloge)(e为网中的边数),所以,适合于求边稀疏的网的最小生成树。
  2. 基本思想:
    克鲁斯卡尔(Kruskal)算法从另一途径求网的最小生成树。其基本思想是:假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),概述图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点分别在T中不同的连通分量上,则将此边加入到T中;否则,舍去此边而选择下一条代价最小的边。依此类推,直至T中所有顶点构成一个连通分量为止 。
  3. 图像示意
    在这里插入图片描述

例题:

给定一个 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")

总结

最小生成树结束,注意并查集的使用哦,最值得注意的是合并时的一些操作

posted @   chanxe  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示