最小生成树:
给定一个无向图,从中选择若干条边将所有节点连在一起,要求边权之和最小在图论中,称为求最小生成树
求最小生成树有 Prim和 Kruskal 两种算法,其中Prim算法适用于 稠密图,Kruskal算法适用于稀疏图
Prim算法
算法所需开辟空间:
- 布尔类型数组 st,表示某节点是否在集合(连通块)里
- dist数组,表示某节点到 集合(连通块)的距离
- 邻接矩阵,用于存储无向图
Prim算法中的集合,或者称为连通块,类似于 dijkstra 算法 当中的集合,一旦进入集合,就已经确定结果了。
算法流程:(设 res 为 最小边权之和)
将dist数组初始化为无穷大
循环 n 次下列步骤:
- 找到集合外距离集合最近的一个节点
- 将该最近节点加入到集合当中,res += 该节点到集合的距离
- 更新该节点连接的其他节点到集合的距离
题目
给定一个 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 n−1 条边构成的无向连通子图被称为 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
1≤n≤500,
1
≤
m
≤
1
0
5
1 \le m \le 10^5
1≤m≤105,
图中涉及边的边权的绝对值均不超过
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函数体如下:
知识点:
- 求最近节点时 将 t 初始化为 -1的操作可以避免 t 的初始值是某个已存在的节点
- 若不是首次加入集合的节点,到集合的距离为正无穷大,则说明该点未与其他节点连通,则说明没有最小生成树
易错点:
- 判断最小生成树是否存在的语句 要在 加给res 之前
- 更新操作中,新的距离是 g[t][j] ,而不是 dist[t] + g[t][j]
- 外层循环的作用仅仅是循环 n 次,也可以写成 for 1 ~ n,而不是for 0 ~ n-1,但写成 1~n时,此时 i == 1为首次入节点,需要注意修改代码 改为 i != 1
Prim算法不会出现有回路的情况,因为入集合的点就不会是最近节点
总结:
算法所需空间:
- 布尔数组st,判断是否在集合里
- 整形数组dist,存储节点到集合的距离
- 邻接矩阵g,存储无向图
算法流程:
- 记res为最终最小边权之和结果
- 将所有节点到集合的距离设置为 无穷大
- 外层 n 次循环,可以for 0~n-1 或 for 1 ~ n(但需注意首入节点的 i 就不一样)
- 在循环内部,每次找到集合外距离集合最近的节点
- 将该节点加入集合当中,并判断是否有无最小生成树
- 如果不是独立的节点,则 res += 刚才的最近节点到集合的距离
- 更新 最近节点所连接节点到集合的距离
判断最小生成树是否存在:
- 若不是加入集合的首个节点,当集合外最近节点到集合的距离为 memset初始化的正无穷大时,则不存在最小生成树
- 不用考虑是否存在回路
易错点:
- 注意首次入集合的点时 i 是 1 还是 0
- 先判断,后res再加
- 更新是 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],只有当全部节点都作为“最近节点”加入到集合当中,才能加上他到集合的距离
演示动画
注释:
- 红色点为集合当中的点,代表st数组值为 true
- 蓝色线代表最小生成树的边
Kruskal算法
Kruskal算法适用于稠密图
算法思路:
- 将所有边按照权重从小到大排序
- 依次从小的边开始遍历,若当前边不会产生回路,则选择该边作为最小生成树的一条边,反之则不选
- 直到选取 n - 1条边,则这些边组成了最小生成树
- 若选取的边数少于 n - 1条边,则最小生成树不存在
如何判断当前边是否产生回路?
答:利用并查集
并查集可以判断两个节点是否已经连通,若已经连通,则不选取该边,从而不会产生回路
刚开始每个节点各自为一个集合,在遍历边的时候,每次判断两个节点是否已经在一个集合,若不在一个集合,则合并两个集合,并此时该条边被选中,将边权加到结果当中
算法所需空间:
- 并查集(parent数组)
- 结构体数组,存储所有边的信息(由于需要排序、遍历且不需要知道某节点的连通节点,则必须使用结构体数组)
题目
给定一个 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 n−1 条边构成的无向连通子图被称为 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
1≤n≤105,
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
代码:
准备阶段:
- 在开始包含大概率可能需要的头文件,在后续使用函数时观察是否漏写,补上即可
- 定义常量,根据题目数据
开辟空间:
- 父亲数组p, 记录了每个节点的父节点(由于路径压缩同时也是他的祖宗)
- 结构体数组 edges,存储所有边的信息,代表 a点到b点的距离为c
- 由于后续需要排序需要使用sort函数,且排序的类型为自定义的类型,所以需要提供比较规则
- 定义一个运算符重载则可以提供比较规则
由于使用了并查集,所以需要写配套的 找祖宗的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
动画:
总结:
开辟空间:
- 并查集,parent 数组
- 存储所有边的结构体数组
Kruskal 算法流程:
- 将所有边从小到大排序
- 遍历每条边
- 若两个节点在并查集当中未连通,则将其连通并作为最小生成树的一条边
- 若最小生成树的边数小于 n - 1条边,则无最小生成树
易错点:
- 结构体需要运算符重载指定比较规则从而能够进行sort 排序