最小生成树
定义:
给定一张带边权的无向图G = (V, E), V中有n个顶点,由n个顶点和n-1条边构成的连通块就是G的一颗生成树,边权值最小的生成树就是最小生成树。
定理:任意一颗最小生成树一定含有无向图中权值最小的边e(x, y, z);
因为如果不含e,把e加进最小生成树中时会产生一个环,用e代替环中任意一条边,都能得到一条权值和更小的生成树。
图中权值为1的边一定在最小生成树中。
对于Kruskal和prim算法,我感觉都是要一条一条地找出必须加入最小生成树的边/点
Kruskal:
1.建立并查集表示连通块,初始状态每个点都是一个连通块,即fa[i] = i;
2.将所有边按权值由大到小排序,依次扫描每条边(x, y, z);
3.若x,y属于同一连通块,那么不连这条边,continue;
4.若x,y不属于一个连通块,合并两个连通块 fa[y] = x,把z累加进ans
5.扫描所有边
为什么4时一定要加入这条边呢?因为假设这条边是连接两个连通块1,2的边,最小生成树中一定要有
连通1,2的边,而这条边是其中权值最小的(因为我们经过排序了)
时间复杂度是o(mlogm)
#include <bits/stdc++.h>
using namespace std;
struct rec{
int x, y, z;
}edge[500010];
int fa[100010], n, m, ans;
bool compare(rec a, rec b) {
return a.z < b.z;
}
int find (int x) {
if (fa[x] == x) return x;
else return find (fa[x]);
}
int main () {
cin >> n >> m;
for (int i = 1; i <= m; i++) scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].z);
sort(edge + 1, edge + m + 1, compare);
for (int i = 1; i <= m; i++) fa[i] = i;
for (int i = 1; i <= m; i++) {
int x = find(edge[i].x), y = find(edge[i].y);
if (x == y) continue;
fa[x] = y;
ans += edge[i]. z;
}
cout << ans << endl;
}
prim:
prim致力于维护最小生成树的一部分。维护一个最小生成树的节点集合T,剩余的节点集合为S,开始时T中只有一个点1,我们不断的找到一边(x, y, z),使得x,y分别处于S,T集合且是这一类边中权值最小的边,把x从S中删去,加入T集合,把z累加到ans中。
稠密图适用,时间复杂度o(n^2);
可以看出,prim算法很像朴素版dijkstra
和朴素版dijkstra的区别是:prim中 d[i] 是i到连通块的距离,不用加上之前走的路径,所以prim是=, dijkstra是 +=
#include <bits/stdc++.h>
using namespace std;
int n, m, ans = 0;
int a[3010][3010], d[3010];
bool v[3010];
void prim () {
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[1] = 0;
for (int i = 1; i < n; i++) {
int x = 0;
for (int j = 1; j <= n; j++) {
if (!v[j] && (x == 0 || d[j] < d[x])) x = j;
}
v[x] = 1;
for (int y = 1; y <= n; y++)
if (!v[y]) d[y] = min(d[y], a[x][y]);
}
}
int main () {
cin >> n >> m;
memset(a, 0x3f, sizeof a);
for (int i = 1; i <= n; i++) a[i][i] = 0;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
a[y][x] = a[x][y] = min(a[x][y], z);
}
prim();
for (int i = 2; i <= n; i++) ans += d[i];
cout << ans << endl;
return 0;
}
一道prim的板子题
农夫约翰被选为他们镇的镇长!
他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。
约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。
约翰的农场的编号是1,其他农场的编号是 2∼n。
为了使花费最少,他希望用于连接所有的农场的光纤总长度尽可能短。
你将得到一份各农场之间连接距离的列表,你必须找出能连接所有农场并使所用光纤最短的方案。
输入格式
第一行包含一个整数 n,表示农场个数。
接下来 n 行,每行包含 n 个整数,输入一个对角线上全是0的对称矩阵。
其中第 x+1 行 y 列的整数表示连接农场 x 和农场 y 所需要的光纤长度。
输出格式
输出一个整数,表示所需的最小光纤长度。
数据范围
3≤n≤100
每两个农场间的距离均是非负整数且不超过100000。
#include <bits/stdc++.h>
using namespace std;
int n, ans = 0;
int a[110][110], d[110];
bool v[110];
void prim () {
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[1] = 0;
for (int i = 1; i < n; i++) {
int x = 0;
for (int j = 1; j <= n; j++) {
if (!v[j] &&(x == 0 || d[j] < d[x])) x = j;
}
v[x] = 1;
for (int y = 1; y <= n; y++)
if (!v[y]) d[y] = min(d[y], a[x][y]);
}
}
int main () {
cin >> n;
memset(a, 0x3f, sizeof a);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) cin >> a[i][j];
prim();
for (int i = 1; i <= n; i++) ans += d[i];
cout << ans << endl;
return 0;
}
本文作者:misasteria
本文链接:https://www.cnblogs.com/misasteria/p/16174398.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步