参考了这位博主的文章:https://blog.youkuaiyun.com/Strive_Y/article/details/81660719
kruskal最小生成树主要思想就是用贪心策略每次加最小权重的边到最小生成树里面,同时保证不要形成环路。
以下代码花了一般的篇幅实现判断不相交集合,也就是判断某条边是否可以加入到生成树里面,上一张导论上的图,说明一下数据结构。
下面用到了一个C语言库函数里面的 qsort
函数原型:
void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ) );
下面用到了一个的那么大的数组,还要遍历实际上不太科学,只是为了方便存放数据,比如一个结点是100,就直接把100当做数组的下标了去用了,在findset的时候就比较方便,要是改善的话可以在定义一个position唯一标识一下每一个结点,然后按顺序存数组,有几个数存几个。这样也可以看出,还是结构体好用呀,虽然写起来麻烦了些~~
#include "stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 30000 //N定义这么大是为了将数组parent和rank的下标直接用里面Edge的start和final里面存的值表示
struct Edge //边的结构体
{
int start;
int final;
int weight;
}edge[N]; //定义一个边的数组,用他来存放边
int parent[N];
int rank[N];
int vertexNum, edgeNum; //顶点个数,边的个数
int i, j; //循环变量
void MAKE_SET() //make_set是用来建立一个不相交的集合
{
for (i = 0; i < N; i++) //初始化一个结点为一个集合,高度都为0,父节点都是自己,是一个自环
{
rank[i] = 0; //rank表示结点的高度
parent[i] = i; //parent表示该结点的父节点
}
}
int FIND_SET(int x) //寻找根节点,递归往上,最上面的就是根,因为是一层一层网上累加的
{
if (x != parent[x])
{
parent[x] = FIND_SET(parent[x]);
}
return parent[x];
}
void UNION(int x, int y) //将两个集合连接起来,维护rank
{
x = FIND_SET(x);
y=FIND_SET(y);
if (rank[x] > rank[y])
{
parent[y] = x;
}
else
{
parent[x] = y;
if (rank[x] == rank[y])
rank[y] = rank[y] + 1;
}
}
void Kruskal()
{
int sumWeight = 0;
int num=0; //最小树中选用的边的数目
int ch1,ch2; //边的两个结点,对应start和final
MAKE_SET(); //初始化集合
for (i = 0; i < edgeNum; i++)
{
ch1 = edge[i].start;
ch2 = edge[i].final;
if (FIND_SET(ch1) != FIND_SET(ch2)) //如果不是同一个根,那么他们就是不相交的集合
{
printf("加入边 %d , %d ,权值 %d\n", ch1, ch2, edge[i].weight);
sumWeight = sumWeight + edge[i].weight;
num++;
UNION(ch1, ch2);
}
}
printf("weight of MST is %d\n", sumWeight);
}
int cmp(const void *a, const void *b) //计算这个是为了用到C语言的函数库里面的qsort,函数里面有这个参数。
{
Edge *e1 = (Edge*)a;
Edge *e2 = (Edge*)b;
return e1->weight - e2->weight;
}
int main()
{
printf("请输入分别输入点的个数和边的条数:\n");
scanf_s("%d %d", &vertexNum, &edgeNum);
printf("请输入每条边的详细信息:分别输入 起点,终点,权重\n");
for(i=0;i<edgeNum;i++)
scanf_s("%d %d %d", &edge[i].start, &edge[i].final,&edge[i].weight);
qsort(edge, edgeNum, sizeof(Edge), cmp);
Kruskal();
return 0;
}
上面那个博主的代码是这样实现不相交集合的,我没看懂这个实现,先贴出来吧,但是他这样可以在简洁的同时用到一个很小的数组
void MAKE_SET()
{
for (i = 1; i <= vertexNum; i++)
parent[i] = -1;
}
int FIND_SET(int i) //查找结点x所属的集合的根节点
{
int t;
for (t = i; parent[t] >= 0; t = parent[t])
;
while (t != i)
{
int t = parent[i];
parent[i] = t;
i = t;
}
return t;
}
void UNION(int x, int y) //把xy连接起来成为一个集合
{
int r1 = FIND_SET(x),r2=FIND_SET(y);
int t = parent[r1] + parent[r2];
if (parent[r1] > parent[r2])
{
parent[r1] = r2;
parent[r2] = t;
}
else
{
parent[r2] = r1;
parent[r1] = t;
}
}
结果:
图是这样的,我把字母都改成数字了:a=1,b=2,c=3…这样的
最开始写的时候就去构造图啊,然后去构造树啊,用了三个结构体,还然后还想用一些简单的方法去实现集合的不相交判断,最后也不知道怎么去判断,又想着用前面的数据结构,但是感觉又得写一个结构体,给结点一个parent和rank。然后看了别的博主的代码,真的写的很简洁。这其实是写算法的过程中形成的思维方式,为什么我一上来就想着要去用结果头体去构造图,去initial去create,然后一个结构体就有几个专门围着他转的函数。写下来两百行应该是要的。脑子里面的思维方式都是以前写那些B树啊,斐波那契堆啊那种,一步一步构造来的。比较清晰倒是真的,但是真的感觉有些固化了,或许这是风格?明明可以简单一些的,见得多点应该会好一些吧~~~。
强烈建议大家关注一些代码风格很好的博主,会有潜移默化的学习的,风格也会渐渐形成。