克鲁斯卡尔Kruskal算法简介
前面我们已经讲过一种求最小生成树的算法,Prim算法
Prim算法是对点做操作,维护一个在最小生成树中的点的顶点集U,以及一个待处理点的顶点集V-U,每次找出连接这两个集合的最短边,构成最小生成树,并将顶点加入集合U,直到所有顶点都处理完毕。
Kruskal算法是对边做操作,每次选出一条最短边,如果它和当前最小生成树不构成回路就将其加入最小生成树,否则将其删除,直到所有边都处理完毕。
如果觉得上面的阐述过于抽象,那么请看下面这个例子。
例子引入
下面来实现Kruskal的具体操作:
1.找出最小权值的一条边,即B-E:12
2.继续找,即C-D:17
3.A-F:19
4.F-C:25
5.F-D:25,此时我们可以发现F-C-D三个顶点形成了回路,这种情况我们要舍去,即此时不进行任何“操作”
6.以此类推,直至边数为“顶点数-1”时结束
关键问题
在实现算法的过程中,我们要解决的问题有以下几个:
1.边的排序(从小到大)
2.如何判断有回路,即顶点有共同的祖先
对于问题1:我们只需根据边的权值对边进行排序,该排序算法的效率决定了整个Kruskal的效率,常见有冒泡排序、快速排序、堆排序等。
对于问题2:我们要找到两个顶点的祖先顶点,其实这就是并查集的内容,没接触过的可以观看视频并查集教程先理解一下并查集的思想
算法实现
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX_VERTEX 10//最多顶点数
#define MAX_EDGE 100//最多边数
typedef struct EdgeType
{
int from, to; //一条边上的两个顶点,from表示起点,to表示终点
int w; //权值
}EdgeType;
typedef struct Gragh
{
int vex[MAX_VERTEX]; //存放顶点
EdgeType edge[MAX_EDGE]; //存放边
int vexnum, arcnum; //顶点数、边数
}Gragh;
void CreatGragh(Gragh& G, int vexnum, int arcnum)
{
G.vexnum = vexnum;
G.arcnum = arcnum;
for (int i = 0; i < vexnum; i++) //顶点赋值
{
int x;
cin >> x;
G.vex[i] = x;
}
int v1, v2, w;
for (int i = 0; i < arcnum; i++) //边赋值
{
cin >> v1 >> v2 >> w;
G.edge[i].from = v1;
G.edge[i].to = v2;
G.edge[i].w = w;
}
}
void SortEdge_weigh(Gragh& G)
{
for (int i = 0; i < G.arcnum - 1; i++) //这里采用冒泡排序
{
for (int j = 0; j < G.arcnum - 1 - i; j++)
{
if (G.edge[j].w > G.edge[j + 1].w)
{
swap(G.edge[j], G.edge[j + 1]);
}
}
}
}
int findRoot(int* parent, int v) //寻祖先顶点
{
int t = v;
while (parent[t] > -1) //初始化为-1
{
t = parent[t];
}
return t;
}
void outputMST(EdgeType x)
{
printf("%d-%d:%d\n", x.from, x.to, x.w);
}
void Kruskal(Gragh& G, int* parent)
{
for (int i = 0; i < G.vexnum; i++)
parent[i] = -1; //初始化
int num=0, vex1, vex2;
for (int i = 0; i < G.arcnum; i++)
{
vex1 = findRoot(parent, G.edge[i].from);
vex2 = findRoot(parent, G.edge[i].to);
if (vex1 != vex2) //如果没有共同祖先,说明可以构成连通
{
outputMST(G.edge[i]);
parent[vex2] = vex1;
num++;
}
if (num == G.vexnum - 1) //但连通边达到了“顶点数-1”,最小生成树以形成,提前退出
return;
}
}
void print(Gragh G) {
printf(" 边 from to w\n");
for (int i = 0; i < G.arcnum; i++) {
printf(" Edge[%d] %d %d %d\n", i, G.edge[i].from, G.edge[i].to, G.edge[i].w);
}
}
int main()
{
int vexnum, arcnum;
printf("请输入顶点个数和边的个数:");
cin >> vexnum >> arcnum;
Gragh G;
CreatGragh(G, vexnum, arcnum);
SortEdge_weigh(G);
printf("输出边储存的图:\n");
print(G);
int parent[100];
printf("输出最小生成树:\n");
Kruskal(G, parent);
return 0;
}
运行结果
参考视频
【懒猫老师-数据结构-(44)最小生成树(Kruskal算法,克鲁斯卡尔算法)-哔哩哔哩】
【懒猫老师-数据结构-(45)最小生成树(Kruskal算法实现,克鲁斯卡尔算法)-哔哩哔哩】