Kruskal算法的思路在于从图中最小权值的路径开始连接,只要不形成回路,那就继续连接当前图中未连接的权值最小的路径。
直至n个点的图被连接了n-1次,那么该图的最小生成树便构建成功。
完整代码和示例图在后边,我先演示下我的思路构建过程。
首先我先画个界面,基于一共有四种图,我就在首界面罗列出四个选项。
上图所应用代码:
system("title 请选择");
while (true) {
system("cls");
printf("请选择生成何种带权图的最小生成树:\n");
printf("1.带权有向图\n");
printf("2.带权无向图\n");
printf("3.有向图\n");
printf("4.无向图\n");
printf("0.退出");
switch (_getch()) {
case '0':return 0;
case '1':system("title 带权有向图"); Tree(1, 1);
system("title 请选择"); break;
case '2':system("title 带权无向图"); Tree(0, 1);
system("title 请选择"); break;
case '3':system("title 有向图"); Tree(1, 0);
system("title 请选择"); break;
case '4':system("title 无向图"); Tree(0, 0);
system("title 请选择"); break;
}
}
然后进去相关部分,我选择构建一个Tree函数来显示子界面
第一个参数是有无方向(isTo),第二个参数是是否带权(isTake)
这里我对带权的处理是,带权就是每条路径都有各自的权值,不带权就是所有路径的权值都为1。
然后在子界面那里,我先让用户输入点数,从而知晓该图的点数和最小生成树会有的边数。
后面的动态生成一个已使用点的集合数组和一些判断会用到他们。
这次我们用一个结构体来表示路径,这将构成一个有头结点的单向链表
为什么这么做?
因为这让我们避免去写一个复杂的动态多维数组来存储两端点和其路劲权值。
而且由于我们会有最短路径排序,也就是存在多次插入动作,所以链表是最好的选择。
查询可以不用管,因为我们必然是从小到大一路尝试过去连接路径的,这就不用数组的随机查询了,顺序的单链表就完全够用了。
struct Line
{
int start;
int end;
int val;
Line* nextp;
};
int num;//点的个数
int i, j, k;//临时变量
char *haveChoose;//已经被使用的点
struct Line lineHead, *lineNow, *lineNew;//头结点
lineHead.nextp = NULL;
system("cls");
while (true) {
printf("请输入所有点的个数(不大于100):");
scanf_s("%d", &num);
if (num > 1 && num <= 100)
break;
printf("输入数字有误,请重新输入!\n");
}
haveChoose = (char*)malloc(sizeof(char)*num);
for (i = 0; i < num; ++i)
haveChoose[i] = 0;
printf("==============\n点已经录入完毕\n==============\n");
printf("以1号点为开始,最大为%d号点\n", num);
if (isTake) {
printf("请输入从几号点到几号点,以构建路径,并附带权值(使用0号点以结束录入)\n");
printf("输入示例:1,2,25\n");
}
else {
printf("请输入从几号点到几号点,以构建路径(使用0号点以结束录入)\n");
printf("输入示例:1,2\n");
}
while (true) {
printf("请输入:");
if (isTake)
scanf_s("%d,%d,%d", &i, &j, &k);
else {
scanf_s("%d,%d", &i, &j);
k = 1;
}
if (i == 0 || j == 0)
break;
if (i == j);
else if (i > 0 && i <= num && j > 0 && j <= num && k >= 0) {
lineNow = &lineHead;
haveChoose[0] = 0;
while (lineNow->nextp) {
if ((lineNow->nextp->start == i && lineNow->nextp->end == j) || (!isTo&&lineNow->nextp->start == j && lineNow->nextp->end == i)) {
lineNow->nextp->val = k;
haveChoose[0] = 1;
break;
}
if (lineNow->nextp->val >= k)
break;
lineNow = lineNow->nextp;
}
if (!haveChoose[0]) {
lineNew = (Line*)malloc(sizeof(Line));
lineNew->start = i;
lineNew->end = j;
lineNew->val = k;
lineNew->nextp = lineNow->nextp;
lineNow->nextp = lineNew;
}
}
else
printf("请输入合理的点号以及权值!\n");
}
printf("===============\n边已记录完毕\n==============\n");
haveChoose[0] = 0;
if (isTake)
printf("已将录入的边以从小到大的权值排列如下:");
else
printf("已录入的边有:\n");
for (lineNow = lineHead.nextp; lineNow; lineNow = lineNow->nextp)
if (isTake)
printf(" %d->%d(%d);", lineNow->start, lineNow->end, lineNow->val);
else {
printf(" %d->%d;", lineNow->start, lineNow->end);
}
printf("\b \n");
图的构建和路径的录入代码如上
录入了一个图和相应的路径之后,我就开始从最小的边开始试图连接路径了,这个从小到大的排序是在录入的时候顺便就做好的,不需要我们后面再专门写一个排序。
由于可能两次连接的边不属于同一个集合
如:
1跟2点相连
3跟4点相连
所以这里我们用haveChoose数组中的值来表示所在集合,haveChoose中的每个元素都是点,0是没有连接过的意思,其余的值就代表着所属集合。
因此,haveChoose中的值全都一样并且都非0就代表着最小生成树已经生成完毕,当然这里我们不用对他进行这样的判断,我们用k记录当前已连接边数,就能更快地知道最小生成树有没有生成完毕。
i的作用在这里是作为当前已有的最大集合号,来使得点可以有不一样的集合,而不用专门写一个数组表示集合这么麻烦。
上图所显示结果用的代码如下:
printf("所构建的最小生成树的边生成中:\n");
i = 0;
k = 0;//当前已经连了几条边
lineNow = lineHead.nextp;
while (k != num - 1) {//如果不够
if (!lineNow) {//如果已经没边了
printf("\n该图无法构建最小生成树\n");
break;
}
if (haveChoose[lineNow->start - 1] != haveChoose[lineNow->end - 1] || haveChoose[lineNow->start - 1] == 0) {
++k;
if (haveChoose[lineNow->start - 1] == haveChoose[lineNow->end - 1] && haveChoose[lineNow->start - 1] == 0)
haveChoose[lineNow->start - 1] = haveChoose[lineNow->end - 1] = ++i;
else if (haveChoose[lineNow->end - 1] == 0)
haveChoose[lineNow->end - 1] = haveChoose[lineNow->start - 1];
else
haveChoose[lineNow->start - 1] = haveChoose[lineNow->end - 1];
if (isTake)
printf(" %d->%d(%d);", lineNow->start, lineNow->end, lineNow->val);
else
printf(" %d->%d;", lineNow->start, lineNow->end);
}
lineNow = lineNow->nextp;
}
if (k == num - 1)
printf("\n最小生成树构建过程如上\n");
printf("按任意键返回...\n");
lineNew = lineHead.nextp;
while (lineNow = lineNew) {
lineNew = lineNow->nextp;
free(lineNow);
}
free(haveChoose);
_getch();
最终该算法的可视化处理效果就是:
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
struct Line
{
int start;
int end;
int val;
Line* nextp;
};
void Tree(char isTo, char isTake);
void printLine(int num);
int main(int args, char *argc[]) {
system("title 请选择");
while (true) {
system("cls");
printf("请选择生成何种带权图的最小生成树:\n");
printf("1.带权有向图\n");
printf("2.带权无向图\n");
printf("3.有向图\n");
printf("4.无向图\n");
printf("0.退出");
switch (_getch()) {
case '0':return 0;
case '1':system("title 带权有向图"); Tree(1, 1);
system("title 请选择"); break;
case '2':system("title 带权无向图"); Tree(0, 1);
system("title 请选择"); break;
case '3':system("title 有向图"); Tree(1, 0);
system("title 请选择"); break;
case '4':system("title 无向图"); Tree(0, 0);
system("title 请选择"); break;
}
}
}
void Tree(char isTo, char isTake) {
int num;//点的个数
int i, j, k;//临时变量
char *haveChoose;//已经被使用的点
struct Line lineHead, *lineNow, *lineNew;//头结点
lineHead.nextp = NULL;
system("cls");
while (true) {
printf("请输入所有点的个数(不大于100):");
scanf_s("%d", &num);
if (num > 1 && num <= 100)
break;
printf("输入数字有误,请重新输入!\n");
}
haveChoose = (char*)malloc(sizeof(char)*num);
for (i = 0; i < num; ++i)
haveChoose[i] = 0;
printf("==============\n点已经录入完毕\n==============\n");
printf("以1号点为开始,最大为%d号点\n", num);
if (isTake) {
printf("请输入从几号点到几号点,以构建路径,并附带权值(使用0号点以结束录入)\n");
printf("输入示例:1,2,25\n");
}
else {
printf("请输入从几号点到几号点,以构建路径(使用0号点以结束录入)\n");
printf("输入示例:1,2\n");
}
while (true) {
printf("请输入:");
if (isTake)
scanf_s("%d,%d,%d", &i, &j, &k);
else {
scanf_s("%d,%d", &i, &j);
k = 1;
}
if (i == 0 || j == 0)
break;
if (i == j);
else if (i > 0 && i <= num && j > 0 && j <= num && k >= 0) {
lineNow = &lineHead;
haveChoose[0] = 0;
while (lineNow->nextp) {
if ((lineNow->nextp->start == i && lineNow->nextp->end == j) || (!isTo&&lineNow->nextp->start == j && lineNow->nextp->end == i)) {
lineNow->nextp->val = k;
haveChoose[0] = 1;
break;
}
if (lineNow->nextp->val >= k)
break;
lineNow = lineNow->nextp;
}
if (!haveChoose[0]) {
lineNew = (Line*)malloc(sizeof(Line));
lineNew->start = i;
lineNew->end = j;
lineNew->val = k;
lineNew->nextp = lineNow->nextp;
lineNow->nextp = lineNew;
}
}
else
printf("请输入合理的点号以及权值!\n");
}
printf("===============\n边已记录完毕\n==============\n");
haveChoose[0] = 0;
if (isTake)
printf("已将录入的边以从小到大的权值排列如下:");
else
printf("已录入的边有:\n");
for (lineNow = lineHead.nextp; lineNow; lineNow = lineNow->nextp)
if (isTake)
printf(" %d->%d(%d);", lineNow->start, lineNow->end, lineNow->val);
else {
printf(" %d->%d;", lineNow->start, lineNow->end);
}
printf("\b \n");
printf("所构建的最小生成树的边生成中:\n");
i = 0;
k = 0;//当前已经连了几条边
lineNow = lineHead.nextp;
while (k != num - 1) {//如果不够
if (!lineNow) {//如果已经没边了
printf("\n该图无法构建最小生成树\n");
break;
}
if (haveChoose[lineNow->start - 1] != haveChoose[lineNow->end - 1] || haveChoose[lineNow->start - 1] == 0) {
++k;
if (haveChoose[lineNow->start - 1] == haveChoose[lineNow->end - 1] && haveChoose[lineNow->start - 1] == 0)
haveChoose[lineNow->start - 1] = haveChoose[lineNow->end - 1] = ++i;
else if (haveChoose[lineNow->end - 1] == 0)
haveChoose[lineNow->end - 1] = haveChoose[lineNow->start - 1];
else
haveChoose[lineNow->start - 1] = haveChoose[lineNow->end - 1];
if (isTake)
printf(" %d->%d(%d);", lineNow->start, lineNow->end, lineNow->val);
else
printf(" %d->%d;", lineNow->start, lineNow->end);
}
lineNow = lineNow->nextp;
}
if (k == num - 1)
printf("\n最小生成树构建过程如上\n");
printf("按任意键返回...\n");
lineNew = lineHead.nextp;
while (lineNow = lineNew) {
lineNew = lineNow->nextp;
free(lineNow);
}
free(haveChoose);
_getch();
}
void printLine(int num) {
printf("=======");
for (int i = 0; i < num; ++i)
printf("============");
printf("\n");
}