目录点这里:【数据结构与算法】相关文章目录
Kruskal算法思想:
第一步:给所有边按照从小到大的顺序排列。
第二步:从小到大依次考查每条边(u,v):
情况1:u和v在同一个连通分量中,那么加入(u, v)后会形成环,因此不能选择。
情况2:如果u和v在不同的连通分量,那么加入(u, v)一定是最优的。为什么呢?下面用 反证法——如果不加这条边能得到一个最优解T,则T+(u, v)一定有且只有一个环,而且环中 至少有一条边(u' , v')的权值大于或等于(u,v)的权值。删除该边后,得到的新树T'=T+(u, v)-(u', v')不会比T更差。因此,加入(u, v)不会比不加入差。
Kruskai算法伪代码:
把所有边排序,记第i小的边为e[i](1<=i<m);//e用于存储
初始化MST为空;//MST:最优边的集合
初始化连通分量,让每个点自成一个独立的连通分量;
for(int i = 0; i < m; i++)
if(e[i].u和e[i].v不在同一个连通分量) {
把边e[i]加入MST;
合并e[i].u和e[i].v所在的连通分量;
}
Kruskai算法实现代码:(C++)
在伪代码中,“e[i].u和e[i].v不在同一个连通分量”是一个难以处理的地方,如果每次判断时都用一次BFS或者DFS,会导致算法的复杂度极高。所以我们这里可以考虑用并查集处理。
并查集:
简单的说,并查集是将元素按某种方法分类到若干集合里面,集合用树来表示,每棵树的根节点都不同(相当于树的身份),且元素的排列结构无关紧要(因为只需要知道元素所处的集合,所以能寻找到根节点即可)。每次根据元素往树跟遍历得到根节点信息之后,直接把遍历经过的所有节点直接和跟根节点相连,这样一来,下次判断这些元素时,只需往上一层便能知道其所处的集合了。
所以可以把寻找元素所在集合的Find函数写成简单的递归函数:
int find(int x){return p[x] == x?x:p[x] = find(p[x]);}
下面是实现图信息的录入及求出最小生成树的边集权值和的代码实例
#include <algorithm>
#include <iostream>
using namespace std;
int m,n;
int w[5005];//权值
int r[5005];//边序号
int p[105];//并查集
int u[5005];//边的左端点
int v[5005];//边的右端点
//用于寻找点所在并查集
//感觉此题用的并查集可以直接return p[x],因为最多只有一个父节点
int find(int x){return p[x] == x?x:p[x] = find(p[x]);}
int cmp(const int x, const int y){return w[x] < w[y];}
int Kruskal() {
scanf("%d %d",&n,&m);
//得到边的数据,初始化边序号
for(int i = 0; i < m; i++) {
scanf("%d %d %d", &u[i], &v[i], &w[i]);
r[i] = i;
}
//对权值进行排序
sort(r, r+m, cmp);
//初始化并查集
for(int i = 1; i <= n; i++)
p[i] = i;
//生成最小树
int lenth = 0;
for(int i = 0; i < m; i++) {
int e = r[i];
int x = find(u[e]); int y = find(v[e]);
if(x != y)
lenth += w[e],
p[x] = y;//如果不在同一个集合,合并
}
return lenth;
}
int main() {
printf("%d\n",Kruskal());
}