最小生成树(Kruskal & Prim)
最小生成树
定义
对于一个带权连通无向图G=(V,E),生成树不同,每棵树的权(树中所有边上的权值和)也不同,设R为G的所有生成树的集合,若T为R中权值和最小的生成树,则T称为G的最小生成树(Minimum-Spanning-Tree,MST)
注:
1、最小生成树可能有多个,但边的权值之和总是唯一且最小的
2、最小生成树的边数=定点数-1,砍掉一条则不连通,增加一条则会出现回路
3、若一个连通图本身就是一颗树,则其最小生成树就是它本身
4、只有连通图才有生成树,非连通图只有生成森林
如何求最小生成树?
两种算法:Kruskal算法和Prim算法。
Kruskal算法
在一个点集中,通过贪心的思想去找边权从小到大的边。这样,我们可以保证生成的树的边权之和最小。(证明请移步其他博客)
再通过并查集去查看两个点是否已经加入了生成树中(祖先是否相同)。如果已经在生成树中,则不加入这条边。这条边不仅仅是多余的,而且还不是最小值(前面加入最小生成树的边一定小)。
最后,判断是否能够生成一棵树。每一次合并两个点代表生成了一条边,故有n个点的点集应该有n-1条边。若没有,则说明这不是一棵有所有点的最小生成树。
由于Kruskal是使用的并查集,故对于稀疏图比较友好。
所以,它的时间复杂度为O(|n|log|n|)。
Prim算法
从一个点开始往外扩展,通过贪心的思想从小到大去扩展每一个点的连边。过程类似于BFS,每一次扩展一的点的所有边,找到权值最小的那一条。
写法也非常类似Dijkstra,甚至堆优化都和Dijkstra几乎一样。
由于时间复杂度较高:O(|n|^2),因此多用于稠密图。
Kruskal和Prim的区别
Kruskal算法适用于稀疏图(并查集有类似离散化的效果),而且时间复杂度相对较低,且是从点集中选取两点之间最短的边来建树。
Prim算法适用于稠密图,时间复杂度相对较高,且是从一个点开始用类似BFS的方式贪心扩展每一个点(边权值从小到大),直到扩展完所有的边。
代码
模板题:P3366 【模板】最小生成树
Prim算法:
因为是数据范围是5000,开个5000*5000的二维表问题不大。干就完事了。
朴素版Prim
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N = 5e3 + 10;
int g[N][N];
int dist[N];
bool st[N];
int n, m;
void prim()
{
memset(dist, inf, sizeof dist);
int res = 0;
dist[1] = 0;
for (int i = 1; i <= n; i++)
{
int t = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if (dist[t] == inf)
{
puts("impossible");
return;
}
res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j++)
if (!st[j] && dist[j] > g[t][j])
dist[j] = g[t][j];
}
cout << res << endl;
}
int main()
{
memset(g, inf, sizeof g);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
g[a][b] = g[b][a] = min(g[a][b], w);
}
prim();
return 0;
}
Kruskal算法
一定要记得初始化并查集fa[]
数组!排序是按边权从小到大排序!
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N = 2e5 + 10;
struct edge
{
int a, b, w;
bool operator < (const edge &W) const
{
return w < W.w;
}
};
edge e[N];
int fa[N];
int n, m, a, b, w;
int find(int x)
{
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx != fy)
fa[fx] = fy;
}
int kruskal()
{
for (int i = 1; i <= n; i++)
fa[i] = i;
sort(e + 1, e + 1 + m);
int res = 0, cnt = 0;
for (int i = 1; i <= m; i++)
{
int a = e[i].a, b = e[i].b, w = e[i].w;
a = find(a), b = find(b);
if (a != b)
{
fa[a] = b;
cnt++;
res += w;
}
}
if (cnt != n - 1)
return inf;
else
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &a, &b, &w);
e[i] = {a, b, w};
}
int t = kruskal();
if (t == inf)
puts("orz");
else
printf("%d\n", t);
return 0;
}