一、算法原理
Kruskal 算法用于求最小生成树,它的主要思路是基于并查集,算法的主要原理如下:
假设图中有 n 个点,则:
-
step 1:Kruskal 算法假定初始时每个点都只属于自己所在的并查集(即初始时每个点都只属于一个独立的集合,各集合之间没有任何交集)。代码实现如下:
// p[a] = S表示点a属于集合S for (int i = 1; i <= n; i++) p[i] = i;
-
step 2:将所有的边(假设图中共有 m 条)按权重从小到大进行排序(因为 Kruskal 算法是按照边的权重从小到大进行遍历的,排序的目的是为了保证在向当前的生成树中加入边时,加入的始终都是权重最小的边(其实也有点类似于贪心),这样当所有的边遍历完后,最终生成树中的所有边之和必然是全局最小的)。代码实现如下:
// 重载Edge对象的"<",使之能够进行小于运算 struct Edge {int a, b, w;bool operator<(const Edge& e) const{return w < e.w;} }edges[M];sort(edges, edges + m);
-
step 3:按边的权重,从小到大遍历所有边,取出边的两个顶点,然后进行判断:
如果边的两个顶点不在同一个并查集中,则将这两个顶点加入同一个并查集(相当于将这两个顶点和它们之间的连边加入最小生成树),然后将边的长度累加至最终结果,并记录当前生成树中又加入了一条边(这个记录是为了判断图中有没有可能不存在最小生成树,如果最终得到的生成树中的边数不到 n − 1 n-1 n−1 条( n n n 个点总共需有 n − 1 n-1 n−1 条边),则说明图中不存在最小生成树;如果最终得到的边数等于 n − 1 n-1 n−1 条,则说明图中存在最小生成树)。代码实现如下:int kruskal() {int res = 0, cnt = 0; // res是最终最小生成树的长度,cnt是全部循环结束以后所得生成树的边数for (int i = 0; i < m; i++) // 图中总共有m条边{auto e = edges[i]; // 先把这条边取出来int a = e.a, b = e.b, w = e.w; // 找到边的两个顶点和边的权重// 看这两个顶点是不是在同一个并查集中if (find(a) != find(b)) // 如果不在{p[find(a)] = find(b); // 把a所在的并查集加入b所在的并查集(相当于将两个并查集合并)res += w; // 累加上当前这条边的权重cnt++; // 记录当前生成树中又多了一条边}}// 根据最终cnt的取值判断图中到底存不存在最小生成树if (cnt < n - 1) return INF;else return res; }
二、完整代码实现
#include <iostream>
#include <cstring>
#include <algorithm> // sort()函数using namespace std;const int N = 1e5 + 10, M = 2e5 + 10, INF = 0x3f3f3f3f;struct Edge
{int a, b, w;bool operator<(const Edge& e) const{return w < e.w;}
}edges[M];int n, m;
int p[N];int find(int x)
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}int kruskal()
{for (int i = 1; i <= n; i++) p[i] = i;sort(edges, edges + m);int res = 0, cnt = 0;for (int i = 0; i < m; i++){auto e = edges[i];int a = e.a, b = e.b, w = e.w;if (find(a) != find(b)){p[find(a)] = find(b);res += w;cnt++;}}if (cnt < n - 1) return INF;else return res;
}int main()
{cin >> n >> m;for (int i = 0; i < m; i++){int a, b, w;scanf("%d%d%d", &a, &b, &w);edges[i] = {a, b, w};}int res = kruskal();if (res == INF) puts("impossible");else cout << res << endl;return 0;
}
三、算法分析
- Kruskal 算法的主要时间瓶颈在于对全部 m m m 条边的排序操作,故该算法的时间复杂度为 O ( m log m ) O(m\log m) O(mlogm);
- Kruskal 算法主要用于求 稀疏图 的最小生成树问题( m < n 2 m<n^2 m<n2)。
【注】以上内容参考 AcWing。