最小生成树
(图片来源于:Acwing算法基础课)
最小生成树问题是什么?
官方解释:
给定一张边带权的无向图G=(V, E),
其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树
样例解释:
地图上有n个城市,需要在城市时间铺设公路,问铺设公路的总长度的最小值之和
在这里我们一般只求无向图的最小生成树
由于堆优化的prim不常用 我这里只罗列出Prim和Kruskal的算法思路和代码
稠密图和稀疏图的简单划分:
- 看边和点的平方是否接近,接近那么是稠密图
Prim朴素版(O n^2) 非常像dijkstra 稠密图
算法思路:
- 初始化dist[] = 0x3f
- 迭代n次 每次找到集合外距离最近的点(集合是已被用来更新的点的集合)
- 用该点来更新其他点到集合的距离
代码:
初始化dist
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ )
迭代n次 dij是迭代n-1次因为 他在第一次选定了一个点
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))找到不在集合中 且没有更新过的点
t = j;
if (i && dist[t] == INF) 如果不是第一个点 并且到每个点都是正无穷
return INF;这里用来判断是否存在最小生成树
if (i) 只要不是第一个点
res += dist[t]; 那么就加上每一个边权 因为n个点的边数是n-1 所以正好去掉第一个点
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}
Krukal 稀疏图 O mlogn
算法思路:
- 先将所有边 按权重从小到大排序
- 枚举每条边 a,b,w
- 如果a和b两个集合不连通,那么就将这条边加到集合
- ( 最开始每个点 都是单独的一个集合 )
存边方式
struct Edge
{
int a, b, w;
bool operator< (const Edge &W)const
{
return w < W.w;
}
重载运算符 就相当于sort(a,a+n,cmp)中的cmp 也可以自己定义cmp
}edges[M];
使用结构体存边的原因:
因为只用到边,而且需要给边排序所以就不用复杂的邻接表
代码实现:
int kruskal()
{
sort(edges, edges + m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
int res = 0, cnt = 0;
for (int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
如果a 和 b不在一个集合
if (a != b)
{
p[a] = b;
res += w;
cnt ++ ;
}
}
也就是存在 自己指向自己的边
if (cnt < n - 1)
这个cnt到底是什么
return INF;
return res;
}