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");
}