[C]可视化的Dijkstra标号算法求带权图的最短路径和距离

Dijkstra标号算法的思路在于选取一个开始点,遍历所有未被确定到达该点所需最短路径的点,对比所有点的当前距离,选择继承或更新成更短的距离,图有多少个点,就有多少步,每一步都将确立一个到达某点的最短路径,直至所有点的最短路径都被判断完毕(即该点当前路径已经是最短的路径了)。

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 带权有向图"); Map(1, 1);
			system("title 请选择"); break;
		case '2':system("title 带权无向图"); Map(0, 1);
			system("title 请选择"); break;
		case '3':system("title 有向图"); Map(1, 0);
			system("title 请选择"); break;
		case '4':system("title 无向图"); Map(0, 0);
			system("title 请选择"); break;
		}
	}

然后进去相关部分,我选择构建一个Map函数来显示子界面
第一个参数是有无方向(isTo),第二个参数是是否带权(isTake)
这里我对带权的处理是,带权就是每条路径都有各自的权值,不带权就是所有路径的权值都为1。

然后在子界面那里,我先让用户输入点数,从而知晓该图的点数和生成所有点的最短距离需要的次数。
后面的动态生成的多种数组和一些判断会用到他们。

这次我们用一个三维数组来存储点与点之间的关系和路径权值
0是没有关系,-1是权值为无穷大,也有着无最短路径的意思

为什么这么做?
因为后面主要做的操作是查询数值进行比较,所以我采用了具有随机读取能力的数组结构,虽然看起来构建得很麻烦,但其实也就几个循环用来构建和销毁,它使用起来是很顺手的。

为了展现推导过程,这里我构建了一个历史记录的数组,但其实这个数组只会关心当前行和上一行,而且我是一边判断新的最短距离点一边更新历史记录数组的,因此构造两行的历史记录数组就行了。

但这边我就不改了(懒),先用着一个点数*点数大小的历史记录数组,各位自己喜欢可以对这个数组进行空间复杂度的改进(你们也懒吗?)。

在这里插入图片描述

int num;//点的个数
	int i, j, k;//临时变量
	int fristPoint;//首选点
	int nowPoint;//当前点
	int *numStr;//存储字符串
	char *haveChoose;//已经是最短路径的点
	int ***historyPoint;//推导过程
	int ***point;//点矩阵
	system("cls");
	while (true) {
		printf("请输入所有点的个数(不大于100):");
		scanf_s("%d", &num);
		if (num > 1 && num <= 100)
			break;
		printf("输入数字有误,请重新输入!\n");
	}
	numStr = (int*)malloc(sizeof(int)*num);
	haveChoose = (char*)malloc(sizeof(char)*num);
	historyPoint = (int***)malloc(sizeof(int**)*num);
	point = (int***)malloc(sizeof(int)*num);
	for (i = 0; i < num; ++i) {
		haveChoose[i] = 0;
		historyPoint[i] = (int**)malloc(sizeof(int)*num);
		point[i] = (int**)malloc(sizeof(int)*num);
		for (j = 0; j < num; ++j) {
			historyPoint[i][j] = (int*)malloc(sizeof(int) * 2);
			historyPoint[i][j][0] = -1;
			historyPoint[i][j][1] = -1;
			point[i][j] = (int*)malloc(sizeof(int) * 2);
			point[i][j][0] = 0;
			point[i][j][1] = -1;
		}
	}
	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) {
			point[i - 1][j - 1][0] = 1;
			point[i - 1][j - 1][1] = k;
			if (!isTo) {
				point[j - 1][i - 1][0] = 1;
				point[j - 1][i - 1][1] = k;
			}
		}
		else
			printf("请输入合理的点号以及权值!\n");
	}
	printf("===============\n边已记录完毕\n==============\n");

	while (true) {
		printf("请选择开始的点号:");
		scanf_s("%d", &k);
		if (k > 0 && k <= num)
			break;
		printf("请重新选择合适的点号!\n");
	}
	historyPoint[0][k - 1][0] = 0;
	fristPoint = nowPoint = k;
	printf("===============\n点号选择完毕\n==============\n");

图的构建和路径的录入代码如上

录入了一个图和相应的点之间的联系(即路径与权值)并选取了一个开始点之后,就开始遍历所有未被确定到达该点所需最短路径的点,对比所有点的当前距离,选择继承或更新成更短的距离。

在这里插入图片描述

上图所显示结果用的代码如下:

printf("推导顺序如下:\n");
	printLine(num);
	printf("|  t  |");
	for (i = 0; i < num; ++i)
		printf("    %3d    |", i + 1);
	printf("\n");
	printLine(num);
	printf("|   1 |");
	for (j = 0; j < num; ++j)
		if (historyPoint[0][j][0] == 0)
			printf(" (  0, λ)%c|", nowPoint == j + 1 ? '*' : ' ');
		else
			printf(" (+∞, λ)%c|", nowPoint == j + 1 ? '*' : ' ');
	printf("\n");
	printLine(num);
	haveChoose[nowPoint - 1] = 1;
	for (i = 0; i < num - 1; ++i) {
		k = -1;
		for (j = 0; j < num; ++j) {
			if (!haveChoose[j]) {//如果没选中过
				if (k == -1)//k用来寻找当前最小路径的点
					k = j;
				if (point[nowPoint - 1][j][0]) {//如果存在路径
					if (i == 0) {
						historyPoint[i][j][0] = point[nowPoint - 1][j][1];
						historyPoint[i][j][1] = nowPoint;
						if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
							k = j;
					}
					else {
						if (historyPoint[i - 1][nowPoint - 1][0] + point[nowPoint - 1][j][1] < historyPoint[i - 1][j][0] || historyPoint[i - 1][j][0] == -1) {
							historyPoint[i][j][0] = historyPoint[i - 1][nowPoint - 1][0] + point[nowPoint - 1][j][1];
							historyPoint[i][j][1] = nowPoint;
							if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
								k = j;
						}
						else {
							historyPoint[i][j][0] = historyPoint[i - 1][j][0];
							historyPoint[i][j][1] = historyPoint[i - 1][j][1];
							if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
								k = j;
						}
					}
				}
				else if (i != 0) {//不存在路径,且不是第一次,就来继承上次的
					historyPoint[i][j][0] = historyPoint[i - 1][j][0];
					historyPoint[i][j][1] = historyPoint[i - 1][j][1];
					if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
						k = j;
				}
			}
			else if (i != 0) {//选中过了的,且不是第一次的,就来继承上次的
				historyPoint[i][j][0] = historyPoint[i - 1][j][0];
				historyPoint[i][j][1] = historyPoint[i - 1][j][1];
			}
		}
		nowPoint = k + 1;
		printf("| %3d |", i + 2);
		for (j = 0; j < num; ++j) {
			if (haveChoose[j])
				printf("           |");
			else
				if (historyPoint[i][j][1] == -1)
					printf(" (+∞, λ)%c|", nowPoint == j + 1 ? '*' : ' ');
				else
					printf(" (%3d,%3d)%c|", historyPoint[i][j][0], historyPoint[i][j][1], nowPoint == j + 1 ? '*' : ' ');
		}
		printf("\n");
		printLine(num);
		haveChoose[nowPoint - 1] = 1;
	}
	printf("由上可知:\n");
	for (i = 0; i < num; ++i) {
		if (i + 1 == fristPoint)
			continue;
		if (historyPoint[num - 2][i][0] == -1)
			printf("%d号点到%d号点无路径", fristPoint, i + 1);
		else {
			printf("%d号点到%d号点的距离是%d的最短路径为%d", fristPoint, i + 1, historyPoint[num - 2][i][0], fristPoint);
			j = i;
			k = 0;
			while ((numStr[k++] = historyPoint[num - 2][j][1]) != -1)
				j = historyPoint[num - 2][j][1] - 1;
			k -= 2;
			while (--k >= 0)
				printf("->%d", numStr[k]);
			printf("->%d", i + 1);
		}
		printf("\n");
	}
	printf("按任意键返回...\n");

	for (i = 0; i < num; ++i) {
		for (j = 0; j < num; ++j) {
			free(historyPoint[i][j]);
			free(point[i][j]);
		}
		free(historyPoint[i]);
		free(point[i]);
	}
	free(historyPoint);
	free(haveChoose);
	free(point);
	_getch();

最终该算法的可视化处理效果就是:
在这里插入图片描述

完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
void Map(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 带权有向图"); Map(1, 1);
			system("title 请选择"); break;
		case '2':system("title 带权无向图"); Map(0, 1);
			system("title 请选择"); break;
		case '3':system("title 有向图"); Map(1, 0);
			system("title 请选择"); break;
		case '4':system("title 无向图"); Map(0, 0);
			system("title 请选择"); break;
		}
	}
}

void Map(char isTo, char isTake) {
	int num;//点的个数
	int i, j, k;//临时变量
	int fristPoint;//首选点
	int nowPoint;//当前点
	int *numStr;//存储字符串
	char *haveChoose;//已经是最短路径的点
	int ***historyPoint;//推导过程
	int ***point;//点矩阵
	system("cls");
	while (true) {
		printf("请输入所有点的个数(不大于100):");
		scanf_s("%d", &num);
		if (num > 1 && num <= 100)
			break;
		printf("输入数字有误,请重新输入!\n");
	}
	numStr = (int*)malloc(sizeof(int)*num);
	haveChoose = (char*)malloc(sizeof(char)*num);
	historyPoint = (int***)malloc(sizeof(int**)*num);
	point = (int***)malloc(sizeof(int)*num);
	for (i = 0; i < num; ++i) {
		haveChoose[i] = 0;
		historyPoint[i] = (int**)malloc(sizeof(int)*num);
		point[i] = (int**)malloc(sizeof(int)*num);
		for (j = 0; j < num; ++j) {
			historyPoint[i][j] = (int*)malloc(sizeof(int) * 2);
			historyPoint[i][j][0] = -1;
			historyPoint[i][j][1] = -1;
			point[i][j] = (int*)malloc(sizeof(int) * 2);
			point[i][j][0] = 0;
			point[i][j][1] = -1;
		}
	}
	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) {
			point[i - 1][j - 1][0] = 1;
			point[i - 1][j - 1][1] = k;
			if (!isTo) {
				point[j - 1][i - 1][0] = 1;
				point[j - 1][i - 1][1] = k;
			}
		}
		else
			printf("请输入合理的点号以及权值!\n");
	}
	printf("===============\n边已记录完毕\n==============\n");

	while (true) {
		printf("请选择开始的点号:");
		scanf_s("%d", &k);
		if (k > 0 && k <= num)
			break;
		printf("请重新选择合适的点号!\n");
	}
	historyPoint[0][k - 1][0] = 0;
	fristPoint = nowPoint = k;
	printf("===============\n点号选择完毕\n==============\n");

	printf("推导顺序如下:\n");
	printLine(num);
	printf("|  t  |");
	for (i = 0; i < num; ++i)
		printf("    %3d    |", i + 1);
	printf("\n");
	printLine(num);
	printf("|   1 |");
	for (j = 0; j < num; ++j)
		if (historyPoint[0][j][0] == 0)
			printf(" (  0, λ)%c|", nowPoint == j + 1 ? '*' : ' ');
		else
			printf(" (+∞, λ)%c|", nowPoint == j + 1 ? '*' : ' ');
	printf("\n");
	printLine(num);
	haveChoose[nowPoint - 1] = 1;
	for (i = 0; i < num - 1; ++i) {
		k = -1;
		for (j = 0; j < num; ++j) {
			if (!haveChoose[j]) {//如果没选中过
				if (k == -1)//k用来寻找当前最小路径的点
					k = j;
				if (point[nowPoint - 1][j][0]) {//如果存在路径
					if (i == 0) {
						historyPoint[i][j][0] = point[nowPoint - 1][j][1];
						historyPoint[i][j][1] = nowPoint;
						if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
							k = j;
					}
					else {
						if (historyPoint[i - 1][nowPoint - 1][0] + point[nowPoint - 1][j][1] < historyPoint[i - 1][j][0] || historyPoint[i - 1][j][0] == -1) {
							historyPoint[i][j][0] = historyPoint[i - 1][nowPoint - 1][0] + point[nowPoint - 1][j][1];
							historyPoint[i][j][1] = nowPoint;
							if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
								k = j;
						}
						else {
							historyPoint[i][j][0] = historyPoint[i - 1][j][0];
							historyPoint[i][j][1] = historyPoint[i - 1][j][1];
							if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
								k = j;
						}
					}
				}
				else if (i != 0) {//不存在路径,且不是第一次,就来继承上次的
					historyPoint[i][j][0] = historyPoint[i - 1][j][0];
					historyPoint[i][j][1] = historyPoint[i - 1][j][1];
					if (historyPoint[i][j][0] != -1 && (historyPoint[i][k][0] > historyPoint[i][j][0] || historyPoint[i][k][0] == -1))//如果当前的比上次的大并且k所指的是无穷大
						k = j;
				}
			}
			else if (i != 0) {//选中过了的,且不是第一次的,就来继承上次的
				historyPoint[i][j][0] = historyPoint[i - 1][j][0];
				historyPoint[i][j][1] = historyPoint[i - 1][j][1];
			}
		}
		nowPoint = k + 1;
		printf("| %3d |", i + 2);
		for (j = 0; j < num; ++j) {
			if (haveChoose[j])
				printf("           |");
			else
				if (historyPoint[i][j][1] == -1)
					printf(" (+∞, λ)%c|", nowPoint == j + 1 ? '*' : ' ');
				else
					printf(" (%3d,%3d)%c|", historyPoint[i][j][0], historyPoint[i][j][1], nowPoint == j + 1 ? '*' : ' ');
		}
		printf("\n");
		printLine(num);
		haveChoose[nowPoint - 1] = 1;
	}
	printf("由上可知:\n");
	for (i = 0; i < num; ++i) {
		if (i + 1 == fristPoint)
			continue;
		if (historyPoint[num - 2][i][0] == -1)
			printf("%d号点到%d号点无路径", fristPoint, i + 1);
		else {
			printf("%d号点到%d号点的距离是%d的最短路径为%d", fristPoint, i + 1, historyPoint[num - 2][i][0], fristPoint);
			j = i;
			k = 0;
			while ((numStr[k++] = historyPoint[num - 2][j][1]) != -1)
				j = historyPoint[num - 2][j][1] - 1;
			k -= 2;
			while (--k >= 0)
				printf("->%d", numStr[k]);
			printf("->%d", i + 1);
		}
		printf("\n");
	}
	printf("按任意键返回...\n");

	for (i = 0; i < num; ++i) {
		for (j = 0; j < num; ++j) {
			free(historyPoint[i][j]);
			free(point[i][j]);
		}
		free(historyPoint[i]);
		free(point[i]);
	}
	free(historyPoint);
	free(haveChoose);
	free(point);
	_getch();
}

void printLine(int num) {
	printf("=======");
	for (int i = 0; i < num; ++i)
		printf("============");
	printf("\n");
}
  • 12
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: Dijkstra标号算法是一种用于求带权图最短路径算法。它的基本思想是从起点开始,每次选择当前距离起点最近的一个顶点,并更新与该顶点相邻的顶点的距离。通过这样的迭代,最终得到起点到所有顶点的最短路径距离。 具体实现时,可以使用一个数组来记录每个顶点的距离和是否已经被访问过。每次选择距离起点最近的未访问顶点,并更新与该顶点相邻的顶点的距离。重复这个过程,直到所有顶点都被访问过。 Dijkstra算法的时间复杂度为O(n^2),其中n为顶点数。如果使用堆优化可以将时间复杂度降为O(mlogn),其中m为边数。 ### 回答2: Dijkstra标号算法是一种用于求解带权最短路径算法,它是基于贪心思想的一种算法。该算法的主要思想是从起点出发,依次加入该节点能到达的邻接点及到达邻接点的权值,直到到达终点,得到两个矩阵:到起点的最短距离矩阵和最短路径矩阵。 算法步骤如下: 1. 初始化:设置起点为源点,将所有顶点的最短路径初始值设置为正无穷,将源点的最短距离设为0,将所有节点的状态设置为“未确定的最短路径”。 2. 标记源点:将源点标记为“已确定的最短路径”,更新源点能到达的邻接点的最短距离,并标记这些节点“下一步需要确定的最短路径”。 3. 寻找下一个确定节点:从“下一步需要确定的最短路径”中选择一个节点,该节点到源点的最短距离已经确定。 4. 标记该节点:将该节点标记为“已确定最短路径”,更新该节点的邻接点的最短距离,并标记这些节点“下一步需要确定的最短路径”。 5.重复步骤3和4,直到目标节点被标记为“已确定的最短路径”,或其他“未确定的最短路径”节点的最短路径无穷大。 在算法执行过程中,需要使用一个记录最短路径的数组和记录最短距离的数组,分别表示从源点到每个节点的最短路径和最短距离。同时还需要使用一个优先队列(最小堆)来存储未确定的最短路径,每次从中选取最小的节点进行操作,以保证算法的效率。 Dijkstra标号算法可以解决有向无环图和有向赋权图最短路径问题,但是不能处理负权边的图。在实际应用中,该算法被广泛应用于计算机网络路由算法和GPS等导航系统的路径规划。 ### 回答3: Dijkstra标号算法是求解带权图最短路径的一种经典算法。在算法执行过程中,首先需要确定起点,随后按照一定规则对节点进行标记,再通过不断更新标记,最终得到最短路径距离。 具体的执行过程如下:从起点开始,对所有邻居节点进行距离标记(即距离起点的距离),并将起点加入已访问节点集合。接着,在未访问节点中选取距离最小的节点,并将其加入已访问节点集合,同时更新与该节点相邻的节点的距离标记。不断重复该过程,直到所有节点被访问并标记后,即可得到最短路径距离。 需要注意的是,在执行过程中需要用一个数组或堆来存储每个节点的距离标记,用于寻找距离最小的节点。同时为了防止环的存在导致算法陷入死循环,需要在已访问节点集合中排除已经访问过的节点。 Dijkstra标号算法的时间复杂度为O(N^2),其中N为节点数。因此,对于大规模的图来说,该算法可能会带来较大的计算开销。为此,还有一些优化的算法,比如A*算法和Bellman-Ford算法等,可以在保证正确性的前提下减少计算时间,具体实现需要根据实际情况选择。 总之,Dijkstra标号算法是一种经典的求解带权图最短路径算法,具有计算简单、易于理解等优点。在实际应用中,需要结合具体问题特点进行选择,并注意算法的时间复杂度和空间复杂度。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值