[C]可视化的Kruskal算法求最小生成树

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");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值