一. 最小生成树
Ⅰ. 定义
给定一张边带权的无向图G = (V, E),其中V表示图中点的集合,E表示图中边的集合,n = |V|,m = |E|。由V中的全部n个顶点和E中n-1条边构成的无向图连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。
Ⅱ. 实际应用
在地图上有n个城市,已知城市的坐标,在城市之间修公路,让城市之间可连通(公路可相交),求铺设的公路的最短长度是多少。
Ⅲ. 分类
- 当图G为稠密图时,使用朴素版Prim算法;
- 当图G为稀疏图时,使用堆优化版Prim算法和Kruskal算法;
- 但因为Kruskal算法比堆优化版Prim算法思路简单,代码简单,所以堆优化版Prim算法不常用。
二. Prim算法(朴素版)
- Prim算法的代码和思路与Dijkstra算法比较像,都是找到集合外距离集合最近的一个点。
- 区别是:Prim算法是用t更新其他点到集合的距离,而Dijkstra算法是用t更新其他点到起点的距离。
Ⅰ. 算法思路
- 初始化:所有点到集合的距离都为+∞,即此时集合中一个点都没有,所以后续要选n个点,即要遍历n次。
- 选择:找到集合外距离集合最近的一个点,将这个点加入到集合中。
- 更新:看剩下的每一个点有没有一条边可以连向集合内部:若有,则这个点到集合内部的距离更新为这个点到集合内部的所有边当中长度最小的那个边的长度;若没有,则这个点到集合的距离依然为+∞。
Ⅱ. C++代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N]; //存储无向图G的每条边的长度,即权值
int dist[N]; //存储每个点到集合的距离
bool st[N]; //记录每个点是否在集合中
int n, m;
int prim()
{
//初始化
memset(dist, 0x3f, sizeof(dist));
int res = 0; //用res记录当前在集合中的所有边的权重之和
for(int i = 0; i < n; i++)
{
int t = -1; //t = -1表示没有找到任何一个点
//找到集合外距离集合最近的一个点,用t来表示
for(int j = 1; j <= n; j++)
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
//如果不是第一个点,且dist[t] = INF,表示当前集合外的点到集合的距离均为INF,即当前的图是不连通的,就不存在最小生成树,则返回INF
if(i && dist[t] == INF) return INF;
if(i) res += dist[t];
//更新所有点到集合的距离
for(int j = 1; j <= n; j++)
dist[j] = min(dist[j], g[t][j]);
//将t点加入到集合中
st[t] = true;
}
return res;
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof(g));
while(m --)
{
int u, v, w;
cin >> u >> v >> w;
//因为无向图为特殊的有向图,所以在存储无向图时,建立一条反向边
g[u][v] = g[v][u] = min(g[u][v], w);
}
int t = prim();
if(t == INF) puts("impossible");
else cout << t << endl;
return 0;
}
三. Kruskal算法
Ⅰ. 算法思路
- 将所有边按权重从小到大排序。时间复杂度:O(m*logm)
- 枚举每条边u -> v,权重为w。如果当前u,v不连通,则把u,v这条边加入到集合中。时间复杂度:O(m)
Ⅱ. C++代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200010;
int p[N]; //存储每个点所属的集合
int n, m;
struct Edge
{
int u, v, w;
//所有点按权重从小到大排序
bool operator< (const Edge &W) const
{
return w < W.w;
}
}edges[N];
//返回祖宗结点
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
edges[i] = {u, v, w};
}
sort(edges, edges + m);
for(int i = 1; i <= n; i++)
p[i] = i;
int res = 0, cnt = 0; //res:当前集合中所有边的权重之和。cnt:当前加了多少条边
for(int i = 0; i < m; i++)
{
int u = edges[i].u;
int v = edges[i].v;
int w = edges[i].w;
//找到u和v的祖先结点,即u和v所在的集合。
u = find(u);
v = find(v);
//若u和v不在一个集合中,就将u、v合并到一个集合中,把这条边加进去
if(u != v)
{
p[u] = v;
res += w;
cnt ++;
}
}
if(cnt < n - 1) puts("impossible");
else cout << res << endl;
return 0;
}