kruskal算法
将一个连通图的边全部删除,剩下一个个顶点。通过选择最短路径连接对应的顶点,直至构成一棵生成树,则这棵生成树一定是最小生成树。Kruskal算法就是通过此方法来建立一棵最小生成树的。其通过不断添加边的过程构成最小生成树。区别于Kruskal算法,Prim算法则是通过添加点来构造最小生成树。
构造过程:
- 保存各顶点间边的连通情况及其对应权值,按对应权值排序(选择最短路径/最小权值连接对应顶点)
- 将连通图划分成一个个单一顶点(删除全部边)
- 选择最短路径(最小权值),将边添加到相应的两个顶点间
- 生成树中是没有回路的,则重构过程如何避免回路?分析发现,如果选中边的对应顶点的连通域相同,则一定会产生回路,因此这样的两个顶点不能连接
- 直至所有顶点在同一个连通域中,否则重复步骤3
关于步骤四的分析:
如上图,因为边权值1、2、3、4最小,所以优先连接相应的边。当权值取5时,发现顶点1和4可以连接,顶点3和4可以连接,顶点2和3也可以连接。倘若连接顶点1和4,则顶点3、4、6构成回路(不是生成树)。这时候我们则可以通过连通域来判断选择哪条边。当顶点1和3连接时构成连通域1,顶点4和6连接时构成连通域4,顶点3和6连接,此时连通域不同(一个是1,一个是4。1,4可以随便选择,这里只是为了区别不同的连通域)因此可以连接,连接后构成了一个大的连通域,我们不妨称其为连通域1。倘若选择顶点3和4连接,但它们属于同一连通域,则会产生回路,因此选择下一条边。当选择顶点1和4时,同样属于一个连通域也不能选择,则再选择下一条边。即连通域相同的两个顶点不能相互连接。
算法步骤:
- 如构造过程步骤1,需要存储连通图中边的关系(起始顶点,终止顶点,权值),因此我们声明一个结构体保存
- 单一顶点(每一个顶点即一个连通域,连通域内仅自己)可以用一维数组存储,表示顶点所属的连通域
- 按连通图中边的权重大小排序(升序)
- 选择权值最小的边,查看其相应的顶点所属连通域是否相同,如果相同则不能连接(产生回路),否则连接相应的边(记录相应的权值)
- 直至所有顶点在同一个连通域中,否则重复步骤4
代码示例:
#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_SIZE 100
struct Edge{
int h, t;
int w;
};
int con[MAX_SIZE];
Edge edge[MAX_SIZE];
bool cmp(Edge x, Edge y){
return x.w < y.w;
}
void init_graph(int m){
for(int i = 0; i < m; i++){
cin >> edge[i].h >> edge[i].t >> edge[i].w;
}
}
int kruskal(int n, int m){
int sum = 0;
sort(edge, edge+m, cmp);
for(int i = 1; i <= n; i++){//表示每个顶点都是单一顶点,无连通
con[i] = i;
}
for(int i = 0; i < m; i++){
int h = con[edge[i].h];
int t = con[edge[i].t];
if(h != t){
cout << edge[i].h << " " << edge[i].t << endl;
sum += edge[i].w;
for(int j = 1; j <= n; j++){
if(con[j] == t){
con[j] = h;
}
}
}
}
return sum;
}
int main(){
int n, m;
cin >> n >> m;
init_graph(m);
cout << kruskal(n, m);
return 0;
}