最小生成树基础

最小生成树:

给定一个无向图,从中选择若干条边将所有节点连在一起,要求边权之和最小在图论中,称为求最小生成树


求最小生成树有 Prim和 Kruskal 两种算法,其中Prim算法适用于 稠密图,Kruskal算法适用于稀疏图

Prim算法

算法所需开辟空间:

  1. 布尔类型数组 st,表示某节点是否在集合(连通块)里
  2. dist数组,表示某节点到 集合(连通块)的距离
  3. 邻接矩阵,用于存储无向图

在这里插入图片描述

Prim算法中的集合,或者称为连通块,类似于 dijkstra 算法 当中的集合,一旦进入集合,就已经确定结果了。


算法流程:(设 res 为 最小边权之和)

将dist数组初始化为无穷大

循环 n 次下列步骤:

  1. 找到集合外距离集合最近的一个节点
  2. 将该最近节点加入到集合当中,res += 该节点到集合的距离
  3. 更新该节点连接的其他节点到集合的距离

题目

给定一个 n n n 个点 m m m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G = ( V , E ) G=(V, E) G=(V,E),其中 V V V 表示图中点的集合, E E E 表示图中边的集合, n = ∣ V ∣ n=|V| n=V m = ∣ E ∣ m=|E| m=E

V V V 中的全部 n n n 个顶点和 E E E n − 1 n-1 n1 条边构成的无向连通子图被称为 G G G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G G G 的最小生成树。

输入格式

第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含三个整数 u , v , w u,v,w u,v,w,表示点 u u u 和点 v v v 之间存在一条权值为 w w w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1 ≤ n ≤ 500 1 \le n \le 500 1n500,
1 ≤ m ≤ 1 0 5 1 \le m \le 10^5 1m105,
图中涉及边的边权的绝对值均不超过 10000 10000 10000

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

代码:


准备阶段:

包含可能需要包含的头文件,定义一些常量,N 为图的最大节点个数,M为图的最大边数,这两个常量是为了后面开辟的数组满足题目当中的所有情况

INF 为相对的无穷大,并非真正的无穷大

在这里插入图片描述

不管是用邻接矩阵或者邻接表存储 图时,都一定需要初始化

存储无向图只需要存两条有向边即可,min的作用是处理重边,当含有重边时,只存储短的一条边

在这里插入图片描述

prim 函数返回最小生成树边权之和,规定了当返回值为 INF时,最小生成树不存在(最小边权之和不会是INF,不会冲突)

在这里插入图片描述


prim函数体如下:

在这里插入图片描述
知识点:

  1. 求最近节点时 将 t 初始化为 -1的操作可以避免 t 的初始值是某个已存在的节点
  2. 若不是首次加入集合的节点,到集合的距离为正无穷大,则说明该点未与其他节点连通,则说明没有最小生成树

易错点:

  1. 判断最小生成树是否存在的语句 要在 加给res 之前
  2. 更新操作中,新的距离是 g[t][j] ,而不是 dist[t] + g[t][j]
  3. 外层循环的作用仅仅是循环 n 次,也可以写成 for 1 ~ n,而不是for 0 ~ n-1,但写成 1~n时,此时 i == 1为首次入节点,需要注意修改代码 改为 i != 1

Prim算法不会出现有回路的情况,因为入集合的点就不会是最近节点


总结:

算法所需空间:

  1. 布尔数组st,判断是否在集合里
  2. 整形数组dist,存储节点到集合的距离
  3. 邻接矩阵g,存储无向图

算法流程:

  1. 记res为最终最小边权之和结果
  2. 将所有节点到集合的距离设置为 无穷大
  3. 外层 n 次循环,可以for 0~n-1 或 for 1 ~ n(但需注意首入节点的 i 就不一样)
  4. 在循环内部,每次找到集合外距离集合最近的节点
  5. 将该节点加入集合当中,并判断是否有无最小生成树
  6. 如果不是独立的节点,则 res += 刚才的最近节点到集合的距离
  7. 更新 最近节点所连接节点到集合的距离

判断最小生成树是否存在:

  1. 若不是加入集合的首个节点,当集合外最近节点到集合的距离为 memset初始化的正无穷大时,则不存在最小生成树
  2. 不用考虑是否存在回路

易错点:

  1. 注意首次入集合的点时 i 是 1 还是 0
  2. 先判断,后res再加
  3. 更新是 g[t][j] 不是 dist[t] + g[t][j]

注意:

Prim算法不需要将dist[1] = 0,而最短路问题都需要将dist[1] = 0的原因是:

  • Prim算法更新的是到集合的距离不会用到dist[1]这个值,所以不用置为0
  • 而最短路问题更新1号节点的边的时候会用到dist[1]

注意外层要循环 n 次,不能是n-1次 dijkstra可以是n - 1,原因是:

  • dijkstra只需要更新dist[n]即可,n - 1次更新(若连通)则必定能够更新dist[n],也就少了一次循环
  • 而Prim算法需要 res += dist[t],只有当全部节点都作为“最近节点”加入到集合当中,才能加上他到集合的距离

在这里插入图片描述

演示动画

注释:

  1. 红色点为集合当中的点,代表st数组值为 true
  2. 蓝色线代表最小生成树的边
    在这里插入图片描述

Kruskal算法


Kruskal算法适用于稠密图

算法思路:

  1. 将所有边按照权重从小到大排序
  2. 依次从小的边开始遍历,若当前边不会产生回路,则选择该边作为最小生成树的一条边,反之则不选
  3. 直到选取 n - 1条边,则这些边组成了最小生成树
  4. 若选取的边数少于 n - 1条边,则最小生成树不存在

如何判断当前边是否产生回路?

答:利用并查集


并查集可以判断两个节点是否已经连通,若已经连通,则不选取该边,从而不会产生回路

刚开始每个节点各自为一个集合,在遍历边的时候,每次判断两个节点是否已经在一个集合,若不在一个集合,则合并两个集合,并此时该条边被选中,将边权加到结果当中


算法所需空间:

  1. 并查集(parent数组)
  2. 结构体数组,存储所有边的信息(由于需要排序、遍历且不需要知道某节点的连通节点,则必须使用结构体数组)

题目

给定一个 n n n 个点 m m m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G = ( V , E ) G=(V, E) G=(V,E),其中 V V V 表示图中点的集合, E E E 表示图中边的集合, n = ∣ V ∣ n=|V| n=V m = ∣ E ∣ m=|E| m=E

V V V 中的全部 n n n 个顶点和 E E E n − 1 n-1 n1 条边构成的无向连通子图被称为 G G G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G G G 的最小生成树。

输入格式

第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含三个整数 u , v , w u,v,w u,v,w,表示点 u u u 和点 v v v 之间存在一条权值为 w w w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105,
KaTeX parse error: Undefined control sequence: \* at position 14: 1 \le m \le 2\̲*̲10^5,
图中涉及边的边权的绝对值均不超过 1000 1000 1000

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

代码:

准备阶段:

  1. 在开始包含大概率可能需要的头文件,在后续使用函数时观察是否漏写,补上即可
  2. 定义常量,根据题目数据

在这里插入图片描述

开辟空间:

  1. 父亲数组p, 记录了每个节点的父节点(由于路径压缩同时也是他的祖宗)
  2. 结构体数组 edges,存储所有边的信息,代表 a点到b点的距离为c
  3. 由于后续需要排序需要使用sort函数,且排序的类型为自定义的类型,所以需要提供比较规则
  4. 定义一个运算符重载则可以提供比较规则

在这里插入图片描述


由于使用了并查集,所以需要写配套的 找祖宗的find函数

在这里插入图片描述

主函数:

在使用任意的数据结构时,都需要考虑是否需要初始化的问题,很关键

在这里插入图片描述

这里选择将kruskal封装成一个函数

在这里插入图片描述

规定当返回的结果为正无穷大时,说明无最小生成树,反之则为最终结果

同时在准备阶段记得补上正无穷大的定义

在这里插入图片描述


kruskal函数如下:

在这里插入图片描述

演示动画

在下面的无向图中,各个颜色代表了其初始各自为一个集合,相同颜色代表为同一集合

在这里插入图片描述

首先先将所有边排序
在这里插入图片描述
接着开始从最小的边开始遍历

在这里插入图片描述

由于此时 B 和 D 尚未连通,所以将B 和 D 连通 并且该边则为最小生成树的一条边

在这里插入图片描述
接着由于 D 和 T 之间 也未连通,则接着执行刚才的操作

在这里插入图片描述
接着是 A C

在这里插入图片描述
接着 C 和 D

在这里插入图片描述
当是 C B、B T、A B时,由于两节点已经连通,则无事发生

接着S A,然后 S C也无事发生

在这里插入图片描述


在这里插入图片描述

2 + 2 + 3 + 3 + 7 = 17,所以结果为17


动画:

在这里插入图片描述
总结:

开辟空间:

  1. 并查集,parent 数组
  2. 存储所有边的结构体数组

Kruskal 算法流程:

  1. 将所有边从小到大排序
  2. 遍历每条边
  3. 两个节点在并查集当中未连通则将其连通并作为最小生成树的一条边
  4. 最小生成树的边数小于 n - 1条边,则无最小生成树

易错点:

  1. 结构体需要运算符重载指定比较规则从而能够进行sort 排序

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值