【数据结构】最小生成树
最小生成树
前置知识
-
并查集
-
图论
概念
条件
最小生成树的满足条件为:
-
在无向图中选取总权值最少的边让所有点连通。
-
要求结果是一棵树,边数比点数少 。
当然,最小生成树的结果可能不唯一。
特性
- 图中任意一条非树边都会和树边构成一个环。
- 非树边一定是环中最大的边。否则可以替换掉最大的边,得到一个更小生成树。
其他
最小生成树有两种算法:Kruskal 和 Prim。
Kruskal 是在并查集的基础上展开。
Prim 是在最短路的基础上展开。
Kruskal
介绍
我们可以从选边的角度来构造生成树,通过点的连通性来对边的选取进行判断。
我们按权值由小到大选择不成环的边加入树中。(由树的特性可知,成环时该边一定是环中最大的边,不应该选取)。如果选出的边比点数少 ,说明最小生成树存在,否则不存在。
那么如何快速的判断是否成环?我们可以使用并查集来合并点及判断点的连通性。
步骤
-
读入所有边并初始化并查集()。
-
按权值排序。
-
合并所有边并计算答案。
-
输出。
Prim
可以从选点的角度来构造生成树,不断将新的点拉入生成树中。
步骤
-
读入所有边。
-
把所有点的距离设为 ,并将任意一点距离设为 。
-
寻找没有进入生成树且距离最小的点进入生成树并累加距离到答案。
-
更新相邻点距离。
-
重复 步直到所有点进入生成树。
-
输出。
证明
假定当前的生成树为 ,且 是离生成树最近的点。如果 与 的边在最终的生成树中,那么现在可以将 拉入生成树中。如果 与 相连的边不在最终的生成树中,那么 必然要通过另一个当前与 直接相连的点 ,间接与 相连。那么 构成环,且 比 更短,替换后可以得到更小生成树。
例题 1(洛谷 P3366 【模板】最小生成树)
给出一个无向图,求出最小生成树,如果该图不连通,则输出
orz
。
原题:https://www.luogu.com.cn/problem/P3366
【Kruskal】
代码是以前写的,可能不好看。
#include <bits/stdc++.h>
using namespace std;
#define qwq ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 200000 + 20, M = 5000 + 50;
struct node {
int x, y, z;
friend bool operator<(const node &a, const node &b) {
return a.z < b.z;
}
};
node edge[N];
int n, m, x, y, z, f[M], l[M], ans = 0;
bool b = 1;
int find(int i) {
if (f[i] == i)
return i;
return f[i] = find(f[i]);
}
void merge(int u, int v) {
u = find(u), v = find(v);
if (l[u] > l[v]) {
swap(l[u], l[v]);
}
f[u] = v;
l[v] += l[u];
}
int main() {
qwq;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
f[i] = i, l[i] = 1;
}
for (int i = 1; i <= m; ++i) {
cin >> edge[i].x >> edge[i].y >> edge[i].z;
}
sort(edge + 1, edge + m + 1);
for (int i = 1; i <= m; ++i) {
if (find(edge[i].x) == find(edge[i].y))
continue;
merge(edge[i].x, edge[i].y);
ans += edge[i].z;
if (l[find(edge[i].x)] == n || l[find(edge[i].y)] == n) {
b = 0;
break;
}
}
if (b)
cout << "orz";
else
cout << ans;
return 0;
}
【Prim】
(摘自洛谷题解)
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define R register int
using namespace std;
int k, n, m, cnt, sum, ai, bi, ci, head[5005], dis[5005], vis[5005];
struct Edge
{
int v, w, next;
} e[400005];
void add(int u, int v, int w)
{
e[++k].v = v;
e[k].w = w;
e[k].next = head[u];
head[u] = k;
}
typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii>> q;
void prim()
{
dis[1] = 0;
q.push(make_pair(0, 1));
while (!q.empty() && cnt < n)
{
int d = q.top().first, u = q.top().second;
q.pop();
if (vis[u])
continue;
cnt++;
sum += d;
vis[u] = 1;
for (R i = head[u]; i != -1; i = e[i].next)
if (e[i].w < dis[e[i].v])
dis[e[i].v] = e[i].w, q.push(make_pair(dis[e[i].v], e[i].v));
}
}
int main()
{
memset(dis, 127, sizeof(dis));
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for (R i = 1; i <= m; i++)
{
scanf("%d%d%d", &ai, &bi, &ci);
add(ai, bi, ci);
add(bi, ai, ci);
}
prim();
if (cnt == n)
printf("%d", sum);
else
printf("orz");
}
例题 2(洛谷 P1550 [USACO08OCT] Watering Hole G)
Farmer John 决定将水引入到他的 个农场。他准备通过挖若干井,并在各块田中修筑水道来连通各块田地以供水。在第 号田中挖一口井需要花费 元。连接 号田与 号田需要 ()元。
每次输入 时,把 和 连接,边权为 。
然后在输入 数组后,套一个双层循环,如果 ,把 和 连接
,边权为 。
存完边之后按照边权排序。排序后跑一遍 Kruskal 即可。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
using ll = long long;
#define mtest for (cin >> t; t; -- t)
const int kMaxN = 1e5 + 10, kMaxM = 1010, kInf = (((1 << 30) - 1) << 1) + 1;
const ll kLInf = 9.22e18;
int n, w[kMaxN], f[kMaxN], p[kMaxM][kMaxM], ans = 0;
struct node {
int u, v, w;
} e[kMaxN];
int F(int x) {
return f[x] == x? x : f[x] = F(f[x]);
}
bool cmp(node a, node b) {
return a.w < b.w; // 注意是 <
}
void U(int u, int v, int w) {
int x = F(u), y = F(v);
if (x != y) {
f[f[u]] = f[v];
ans += w;
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i - 1 <= n; ++ i) { // 初始化
f[i] = i;
}
int id = 0;
for (int i = 1; i <= n; ++ i) { // 村边
e[++ id].u = i;
e[id].v = n + 1;
// i & n + 1 连接
cin >> e[id].w;
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
cin >> p[i][j];
}
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
if (i == j) {
continue;
}
e[++ id].u = i;
e[id].v = j;
// i & j 连接
e[id].w = p[i][j];
}
}
sort(e + 1, e + id + 1, cmp); // 按边权排序
for (int i = 1; i <= id; ++ i) { // 跑 Kruskal
U(e[i].u, e[i].v, e[i].w);
}
cout << ans << '\n';
return 0;
}
关联习题
-
洛谷 P1194 / P1195 / P1396 / P2330 / P2700 / P4047。
-
CF / AT 上面关于最小生成树的题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!