You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
5.6 KiB

2 years ago
##[$AcWing$ $859$. $Kruskal$算法求最小生成树](https://www.acwing.com/problem/content/description/861/)
### 一、题目描述
给定一个 $n$ 个点 $m$ 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 `impossible`
给定一张边带权的无向图 $G=(V,E)$,其中 $V$ 表示图中点的集合,$E$ 表示图中边的集合,$n=|V|$$m=|E|$。
由 $V$ 中的全部 $n$ 个顶点和 $E$ 中 $n1$ 条边构成的无向连通子图被称为 $G$ 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 $G$ 的最小生成树。
**输入格式**
第一行包含两个整数 $n$ 和 $m$。
接下来 $m$ 行,每行包含三个整数 $u,v,w$,表示点 $u$ 和点 $v$ 之间存在一条权值为 $w$ 的边。
**输出格式**
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 `impossible`
**数据范围**
$1≤n≤10^5,1≤m≤210^5$,
图中涉及边的边权的绝对值均不超过 $1000$。
**输入样例:**
```cpp {.line-numbers}
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
```
**输出样例:**
```cpp {.line-numbers}
6
```
### 二、$Kruskal$算法
#### 1、基本思路
1 将所有边按权重从小到大排序
2 枚举每条边 $a \sim b$ ,权重是$c$
```c++
if a,b不在一个集合中
将这条边加入集合中
结束
```
#### 2、与$prim$算法的区别
* 克鲁斯卡尔算法的基本思想是以边为主导地位,普利姆算法是以点为主导的地位的。
* $prim$算法适合稠密图,$kruskal$算法适合稀疏图。理由也挺简单的,$kruskal$是按边存的,边少就合适,边多就不适合。稀疏图当然边少,稠密图是点少,但边多,边可能达到节点数的平方,即每个节点都与其它节点有边。
#### 3、算法模拟
假如有以下几个城市,之间都有相连的道路:
<center><img src='https://cdn.acwing.com/media/article/image/2021/02/25/64630_402245cc77-1.png'></center>
根据$kruskal$的原理,我们需要对边权$dis$进行排序,每次找出最小的边。
排序后,最小的边自然是第$8$条边,于是$4$和$6$相连。
<center><img src='https://cdn.acwing.com/media/article/image/2021/02/25/64630_4690dcc877-2.png'></center>
遍历继续,第二小的边是$1$号,$1$和$2$联通。
<center><img src='https://cdn.acwing.com/media/article/image/2021/02/25/64630_4e48e92477-3.png'></center>
再后来是边$3$连接$1$,$4$。
<center><img src='https://cdn.acwing.com/media/article/image/2021/02/25/64630_5467593477-4.png'></center>
$dis$也是$14$的还有边$5$,它连接$3$,$4$。
<center><img src='https://cdn.acwing.com/media/article/image/2021/02/25/64630_5ab3b12a77-5.png'></center>
其次是$dis$为$15$的边$4$,但是$2$和$4$已经相连了,$pass$。
然后是$dis$为$16$的两条边(边$2$和边$9$),边$2$连接$1$和$3$,边$9$连接$3$和$6$,它们都已经间接相连,$pass$。
再然后就是$dis$为$22$的边$10$,它连接$5$和$6$$5$还没有加入组织,所以使用这边。继续,发现此时已经连接了$n-1$条边,结束,最后图示如下:
<center><img src='https://cdn.acwing.com/media/article/image/2021/02/25/64630_63ad1c2277-6.png'></center>
本题与 [https://www.acwing.com/problem/content/839/](https://www.acwing.com/problem/content/839/) 是姊妹题,其实$Kruskal$算法就是一个并查集的应用。
不像$Prim$算法,不用考虑边界,考虑循环$N$次啊,计算最小值啊,还要用堆进行优化啊,这个就是一个并查集,思路简单。
#### 4、疑问与解答
**$Q$:只需要简单结构体即可,不需要邻接表或者邻接矩阵来存,为什么呢?**
$A$:之所以使用邻接表或邻接矩阵,其实说白了,是按点存的,记录$A$点和$B$点的关系。**用结构体存储,其实是按边存的**,就是题目说有一条$A-B$的边(权为$C$),我们就存了一个$A-B$权为$C$的边。
**按点存麻烦(邻接表或邻接矩阵),按边存(结构体数组)简单。**
2 years ago
#### $Code$
2 years ago
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
2 years ago
const int N = 100010, M = N << 1;
2 years ago
const int INF = 0x3f3f3f3f;
2 years ago
2 years ago
int n, m; // n条顶点,m条边
int res; // 最小生成树的权值和
2 years ago
int cnt; // 最小生成树的结点数
2 years ago
// Kruskal用到的结构体
struct Node {
2 years ago
int a, b, c;
bool const operator<(const Node &t) const {
return c < t.c; //
2 years ago
}
2 years ago
} edge[M]; // 数组长度为是边数
2 years ago
// 并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
2 years ago
2 years ago
// Kruskal算法
2 years ago
void kruskal() {
2 years ago
// 1、按边权由小到大排序
2 years ago
sort(edge, edge + m);
2 years ago
// 2、并查集初始化
2 years ago
for (int i = 1; i <= n; i++) p[i] = i;
2 years ago
// 3、迭代m次
2 years ago
for (int i = 0; i < m; i++) {
2 years ago
int a = edge[i].a, b = edge[i].b, c = edge[i].c;
2 years ago
a = find(a), b = find(b);
if (a != b)
2 years ago
p[a] = b, res += c, cnt++; // cnt是指已经连接上边的数量
2 years ago
}
2 years ago
// 4、特判是不是不连通
2 years ago
if (cnt < n - 1) res = INF;
2 years ago
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
edge[i] = {a, b, c};
}
2 years ago
kruskal();
if (res == INF)
2 years ago
puts("impossible");
else
2 years ago
printf("%d\n", res);
2 years ago
return 0;
}
```