kruskal算法:同样解决最小生成树的问题,和prim算法不同,kruskal算法采用了边贪心的策略,思想要比prim算法简单。
算法基本思想
- 在初始状态时隐去图中的所有边,这样图中每个顶点都自成一个连通块。之后执行下面的步骤:
(1)对所有的边 按边权从小到大 进行排序;
(2)按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条测试边加入当前最小生成树中;否则,将边舍弃;
(3)执行步骤(2),知道最小生成树中的边数等于总顶点数减1或者测试完所有的边时结束。当结束时,如果最小生成树的边数小于总顶点数减1,则说明该图不连通。
算法的重点 并查集的使用!
在于如何判断两个点是否在不同的连通块中.
- 把每个连通块当做一个集合,判断两个端点是否在同一个连通块中就可以转换为判断两个端点是否在同一个集合中,解决这个问题的方法是使用并查集。
- 并查集可以通过查询两个结点所在集合的根结点是否相同来判断它们是否在同一个集合中
如何将测试边加入最小生成树
- 加测试边合并就能解决这个问题
- 即只要把测试边的两个端点所在集合合并
具体代码实现
- kruskal算法需要对边排序.对边的定义:
struct edge {
int u, v; // 边的两个端点
int cost; // 权值
edge(int x, int y, int val) :u(x), v(y), cost(val){}
};
// 比较函数,排序时使用
bool cmp(edge a, edge b) {
return a.cost < b.cost;
}
- 具体代码
// 如何判断测试边的两个端点是否在不同的连通块中
int findFather(vector<int> father, int x) {
int a = x;
while (x != father[x]) {
x = father[x]; // 找到最远的父亲
}
// 把所有儿子全部挂到指向x上.
while (a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
// n顶点个数 m边数
int kruskal(int n,int m,vector<edge>& E) {
// 需要用到并查集
int ans = 0; // 所求边的权值之和
int NumEdge = 0; // 记录最小生成树的边数
// 初始化并查集
vector<int> father(n);
for (int i = 0; i < n; i++) {
father[i] = i; // 每个点自己一个组
}
// 将权值从小到大排序
sort(E.begin(), E.end(), cmp);
// 遍历所有的边
for (int i = 0; i < m; ++i) {
int father_U = findFather(father, E[i].u);
int father_V = findFather(father, E[i].v);
if (father_U != father_V) {
// 不在同一个集合中,就可以把边加入最小生成树中
father[father_U] = father_V;
ans += E[i].cost;
NumEdge++;
if (NumEdge == n - 1) {
break;
}
}
}
if (NumEdge != n - 1) {
return -1;
}
return ans;
}