算法思想很简单,比prim算法要清晰很多,难处可能还是在具体实现上面。
思想:既然要找最小生成树,让各个选中的边的权值加起来最小,那我们是不是可以很自然地想到我们先找到一个权值最小的边,然后找到第二小的边......这样就能出现最小的边的权值和了。就好像我们去迪士尼,排队很烦人,但是我们为了尽可能玩多的项目,就先去排队最小的项目然后去排队人数第二小的项目......
于是我们就可以从边出发建立最小生成树了。但是,哪有那么简单的算法呢哈哈哈,它还有一个难处就是,我们都知道图的定义和树的定义里面,我们树是不研究“环”的,我们在选择边的时候很可能就会选出一个环来,那么就不符合树的结构了,这就不对了,于是我们要在选的时候进行判断,如果这个边选入会构成环,那么就不要,我们看下一条边。
那么问题又来了,这个边怎么选呢?你怎么把最小权值的边找出来呢?这里就要用到边集数组,把边的信息全部放到一个数组里面,就可以了。
下面是图解:
先有一个无向图(画的丑勿怪.....)

然后我们按照上文所说,先找出最小两条的边选入最小生成树,用蓝色标起来。就是 5,7这两条

然后选第三条:

然后关键来了,我们选第四条,按照大小应该选9这一条,可是,我们会发现,如果选了9 ,那么7,9,5这三条边就构成了环,那就不行了,所以9这条不选,继续往下看,找到10,可以选

然后选15,然后选18,然后是21,诶,如果选了就有环,所以不选

然后选23,也不行。29也不行,所以就到此为止,我们的最小生成树就出来了。
下面给出具体实现,几乎每行代码都有注释,大家可以通过注释,可以更好理解他的实现。
PS:其中用到的邻接矩阵等图的储存知识,可以看看这个图(graph)的基础知识详解_chy响当当的博客-CSDN博客
#include"bits/stdc++.h"
using namespace std;
const int INFINITE = 1000;
//先定义边集数组结构体
typedef struct {
int begin;//边的起始,但是由于我们举的例子是无向图的邻接矩阵,所以起始末尾大家可以随意
int end;//边的末尾,不过如果你想做有向图的边集数组的话,需要处理一下
int data;//边的权值
}Edge;
//那么在开始之前,我们也需要一个把无向图变成边集数组的函数
//根据上面的讲解,边集数组是把边和边的权值存入,我们这里有需要对边的权值按大小排序
//所以无非就是循环然后找出最小的边存入,然后第二小的......
typedef struct
{
int vex[50];
int arc[50][50];
int numvex;
int numarc;
}Graph;//邻接矩阵,
//然后简单初识化一下,就直接确定相应的权值了
void CreateMGraph(Graph* G)
{
int i, j;
// printf("请输入边数和顶点数:");/
G->numarc = 15;
G->numvex = 9;
for (i = 0; i < G->numvex; i++)/* 初始化图 */
{
for (j = 0; j < G->numvex; j++)
{
if (i == j)
G->arc[i][j] = 0;
else
G->arc[i][j] = G->arc[j][i] = INFINITE;//没有边的就初始化为不可能值,
}
}
G->arc[0][1] = 10;
G->arc[0][5] = 11;
G->arc[1][2] = 18;
G->arc[1][8] = 12;
G->arc[1][6] = 16;
G->arc[2][8] = 8;
G->arc[2][3] = 22;
G->arc[3][8] = 21;
G->arc[3][6] = 24;
G->arc[3][7] = 16;
G->arc[3][4] = 20;
G->arc[4][7] = 7;
G->arc[4][5] = 26;
G->arc[5][6] = 17;
G->arc[6][7] = 19;
for (i = 0; i < G->numvex; i++)
{
for (j = i; j < G->numvex; j++)
{
G->arc[j][i] = G->arc[i][j];
}
}
}
然后处理边集数组:
//而循环又需要用到图的循环,可以使用图的广度优先遍历等等(以后有机会我可以专门做一个遍历·讲解)
// 交换权值 以及头和尾
void Swapn(Edge* edges, int i, int j)
{
int temp;
temp = edges[i].begin;
edges[i].begin = edges[j].begin;
edges[j].begin = temp;
temp = edges[i].end;
edges[i].end = edges[j].end;
edges[j].end = temp;
temp = edges[i].data;
edges[i].data = edges[j].data;
edges[j].data = temp;
}
void Create_Egde_array(Edge a[],Graph *G)//传递一个还未初始化的数组和无向图的邻接矩阵
{
//先把无向数据读入,他的矩阵是对称的,所以读上三角形
int p = 0;
for(int i=0;i<G->numvex;i++)
for (int j = i; j < G->numvex; j++)
{
if (G->arc[i][j] != INFINITE)//只要存在这条边
{
a[p].begin = i;
a[p].end = j;
a[p].data = G->arc[i][j];//这个就是权值了
p++;
}
}
//然后大小排序,就要用上面的交换了
for(int i=0;i<G->numvex;i++)
for (int j = i + 1; i < G->numvex; j++)
{
if (a[i].data > a[j].data)
{
Swapn(a, i, j);
}
}
//可以检查一下对不对
for (int i = 0; i < G->numarc; i++)
{
printf("(%d, %d) %d\n", a[i].begin, a[i].end, a[i].data);
}
}
接下来就是重点:KRUSKAL算法
int Find_com(int* parent, int vex);
void Mintree_Create_Kruskal(Graph G,Edge edge[])
{
int parent[50];//这个是用于判断是否形成回路的,我们可以想象它里面有一些传递的关系,如果你
//传递的两个顶点下标,这两个下标,在这个数组里面可以传递到一个数字,那说明就构成了一个环,
//具体可以看下面的Find_com函数
for (int i = 0; i < G.numvex; i++)
parent[i] = 0;//全部初识化为0,这个可以自己设置,只要能在自己心中,和顶点下标有区分即可
for (int i = 0; i < G.numarc; i++)
{
int n = Find_com(parent, edge[i].begin);
int m = Find_com(parent, edge[i].end);//因为edge这个边集数组已经按大小排好序了,从0开始就是从最小边开始
//然后根据Find_com的注释
if (n != m)
{
parent[n] = m;//parent[m]=n也一样,反正是无向的
//然后就可以把他添加到树里面,但是树这里要给还得添很多与今天重点无关的代码,
//所以大家只要知道在这里,就是把边填入最小生成树的时候就行了
//也可以Printf一下都可以,我这里就略过了
//.......
}
}
}
int Find_com(int* parent, int vex)//传递一个parent数组,和当前要选的边的一个下标
{
while (parent[vex] > 0)//parent存的是什么呢?就是对应的下标。比如顶点1和顶点2有边,也被选入了,
//那么我们就经过上面的处理得到一个parent[1]=2,就是说1和2有边,他们相连
//一次类推,2和3连那么,parent[2]=3,
//这样子如果我们在上面判断时,加入一条顶点4和顶点5连的边,parent[4]可以经过一个个传递得到3
//而parent[5]经过传递也得到了一个3,那么显然就构成一个环了,我们就得社区4,5这条边
{
vex = parent[vex];//
}
return vex;
}
好了,今天就到这里,希望大家能明白这个算法。
本文详细介绍了克鲁斯卡尔算法(Kruskal's Algorithm)的思想与实现过程,通过一个无向图实例展示了如何构造最小生成树。首先从边集数组结构体出发,通过邻接矩阵初始化图,接着按权值排序边集,最后通过判断环路避免原则逐步选择边构建最小生成树。代码中包含关键函数如Find_com用于查找顶点所属集合,以及Mintree_Create_Kruskal函数用于实际构建最小生成树。
9440

被折叠的 条评论
为什么被折叠?



