实验三 通信网中的算法问题—最短路径算法实验
一. 引言
-
单源最短路问题: 给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径问题。
-
基本Dijkstra算法基本思想:
*1.*通过Dijkstra计算图G中的最短路径时,需要指定一个起点D(即从顶点D开始计算)。
*2.*此外,引进两个数组S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点D的距离)。
*3.*初始时,数组S中只有起点D;数组U中是除起点D之外的顶点,并且数组U中记录各顶点到起点D的距离。如果顶点与起点D不相邻,距离为无穷大。
*4.*然后,从数组U中找出路径最短的顶点K,并将其加入到数组S中;同时,从数组U中移除顶点K。接着,更新数组U中的各顶点到起点D的距离。
*5.*重复第4步操作,直到遍历完所有顶点。
二. 系统设计要求
实验要求:
-
给定任意带权重的连通图形,求出指定起点到所有点的最短路径和最短路径的长度,并打印出经过的路径及最短路径长度。
-
请把图形转化为数据,并通过文本/手动输入。
打印显示要求:
- 要求打印所有节点信息:
例如:节点1:重庆;
节点2:**;
**********;
- 要求打印所有边的信息:
例如:节点* 节点* 路径长度*
- 最短路径信息:
源节点到节点的最短路径长度*;
必经节点**。。。。。。;
三. 设计思路与方案
1.程序流程图设计
2.全局变量及结构体
#include <stdio.h>
#include <string.h>
#include <limits.h>
#define CITY_NUM 8
#define unconnect INT_MAX // 表示无连接
int graph[CITY_NUM][CITY_NUM]; // 城市之间的距离
int d[CITY_NUM]; // 距离数组,存储从起点到各顶点的最短距离
int p[CITY_NUM]; // 前驱节点数组,存储最短路径的前驱节点
int S[CITY_NUM]; // 标记已访问的顶点
char cities[CITY_NUM][20] = {
"重庆",
"北京",
"成都",
"上海",
"深圳",
"杭州",
"广州",
"武汉"
};
3.算法设计
3.1整体代码
#include <stdio.h>
#include <string.h>
#include <limits.h>
#define CITY_NUM 8
#define unconnect INT_MAX // 表示无连接
int graph[CITY_NUM][CITY_NUM]; // 城市之间的距离
int d[CITY_NUM]; // 距离数组,存储从起点到各顶点的最短距离
int p[CITY_NUM]; // 前驱节点数组,存储最短路径的前驱节点
int S[CITY_NUM]; // 标记已访问的顶点
char cities[CITY_NUM][20] = {
"重庆",
"北京",
"成都",
"上海",
"深圳",
"杭州",
"广州",
"武汉"
};
void fileop(void)//读文件构造图
{
FILE *fp;
int i, j;
int dis;
int from, to;
if ((fp = fopen("Dijkstra_input.txt", "r")) == NULL)
{
printf("文件打开失败");
}
else
{
for (i = 0; i < CITY_NUM; i++)
{
for (j = 0; j < CITY_NUM; j++)
{
graph[i][j] = unconnect;
}
}
while (fscanf(fp, "%d %d %d", &from, &to, &dis) == 3)
{
graph[from - 1][to - 1] = dis;
graph[to - 1][from - 1] = dis;
}
fclose(fp);
}
}
// 更新距离数组和前驱节点数组
void Update(int i)
{
for (int j = 0; j < CITY_NUM; j++)
{
if (!S[j] && graph[i][j] != unconnect)
{
int newDist = d[i] + graph[i][j];
if (newDist < d[j])
{
d[j] = newDist;
p[j] = i;
}
}
}
}
// 查找未访问顶点中距离最小的顶点
int FindMin()
{
int minDist = INT_MAX;
int minIndex = -1; // 记录未访问的顶点中距离最小的顶点
for (int i = 0; i < CITY_NUM; i++)
{
if (!S[i] && d[i] < minDist)
{
minDist = d[i];
minIndex = i;
}
}
return minIndex;
}
void DijkstraAlg(int s)
{
for (int i = 0; i < CITY_NUM; i++)
{
d[i] = INT_MAX; // 初始化距离为无穷大
p[i] = -1; // 初始化前驱节点为无效值
S[i] = 0; // 初始化未访问
}
d[s] = 0;
p[s] = -1;
while (1)
{
int i = FindMin();
if (i == -1)
{
break; // 所有顶点都已访问完毕
}
S[i] = 1; // 标记顶点i为已访问
Update(i);
}
}
void printCities()
{
printf("所有节点信息:\n");
for (int i = 0; i < CITY_NUM; i++)
{
printf("节点%d:%s;\n", i + 1, cities[i]);
}
printf("\n");
}
void printEdges()
{
printf("所有边的信息:\n");
for (int i = 0; i < CITY_NUM; i++)
{
for (int j = i + 1; j < CITY_NUM; j++)
{
if (graph[i][j] != unconnect)
{
printf("节点%d 到 节点%d 的路径长度:%d;\n", i + 1, j + 1, graph[i][j]);
}
}
}
printf("\n");
}
void printShortestPaths(int source)
{
printf("从节点%d到各个节点的最短路径长度和必经节点:\n", source + 1);
for (int i = 0; i < CITY_NUM; i++)
{
if (i != source)
{
printf("到节点%d的最短距离:%d\n", i + 1, d[i]);
printf("必经节点:");
int path[CITY_NUM]; // 用于存储路径
int current = i;
int pathLength = 0;
// 反向遍历前驱节点数组,将路径存入 path 数组
while (current != -1)
{
path[pathLength++] = current;
current = p[current];
}
// 打印路径,从起点到终点
for (int j = pathLength - 1; j >= 0; j--)
{
printf("%s", cities[path[j]]);
if (j > 0)
{
printf(" -> ");
}
}
printf("\n");
}
}
}
int main()
{
int ver1; // 定义起始节点
fileop();
printf("请输入起始节点(1~8):\n");
scanf("%d", &ver1);
if (ver1 < 1 || ver1 > CITY_NUM)
{
printf("无效的起始节点编号\n");
return 1;
}
DijkstraAlg(ver1 - 1);
printCities();
printEdges();
printShortestPaths(ver1 - 1);
return 0;
}
3.2 文件读取及图的构建
void fileop(void)//读文件构造图
{
FILE *fp;
int i, j;
int dis;
int from, to;
if ((fp = fopen("Dijkstra_input.txt", "r")) == NULL)
{
printf("文件打开失败");
}
else
{
//初始化图,将每个节点之间权值设为无穷大表示为未连接
for (i = 0; i < CITY_NUM; i++)
{
for (j = 0; j < CITY_NUM; j++)
{
graph[i][j] = unconnect;
}
}
//读取文件节点之间的权值并赋值
while (fscanf(fp, "%d %d %d", &from, &to, &dis) == 3)
{
graph[from - 1][to - 1] = dis;
graph[to - 1][from - 1] = dis;
}
fclose(fp);
}
}
3.3 Dijsktra算法
Dijkstra算法的主要步骤,包括初始化、选取距离最小的未访问节点、标记已访问、更新距离和前驱节点数组,从而找到从起点到各个节点的最短路径
// 查找未访问顶点中距离最小的顶点
int FindMin()
{
int minDist = INT_MAX;
int minIndex = -1; // 记录未访问的顶点中距离最小的顶点
for (int i = 0; i < CITY_NUM; i++)
{
if (!S[i] && d[i] < minDist)
{
minDist = d[i];
minIndex = i;
}
}
return minIndex;
}
// 更新距离数组和前驱节点数组
void Update(int i)
{
for (int j = 0; j < CITY_NUM; j++)
{
//若找到未访问节点
if (!S[j] && graph[i][j] != unconnect)
{
int newDist = d[i] + graph[i][j];
if (newDist < d[j])
{
d[j] = newDist;
p[j] = i;
}
}
}
}
void DijkstraAlg(int s)
{
for (int i = 0; i < CITY_NUM; i++)
{
d[i] = INT_MAX; // 初始化距离为无穷大
p[i] = -1; // 初始化前驱节点为无效值
S[i] = 0; // 初始化未访问
}
d[s] = 0;
p[s] = -1;
while (1)
{
int i = FindMin();
if (i == -1)//是否还有未访问的节点
{
break; // 所有顶点都已访问完毕
}
S[i] = 1; // 标记顶点i为已访问
Update(i);
}
}
3.4打印显示信息
在printShortestPaths
函数中,要实现源节点到目标节点的顺序打印必经路径,由于前驱节点数组 p
存储的是从源节点到每个节点的最短路径上的前一个节点,也就是说,p[i]
存储了从源节点到节点 i
的最短路径上的前一个节点的索引。
所以需要通过逆序遍历前驱节点数组 p
,从目标节点开始向源节点回溯,构建完整的最短路径。
void printCities()
{
printf("所有节点信息:\n");
for (int i = 0; i < CITY_NUM; i++)
{
printf("节点%d:%s;\n", i + 1, cities[i]);
}
printf("\n");
}
void printEdges()
{
printf("所有边的信息:\n");
for (int i = 0; i < CITY_NUM; i++)
{
for (int j = i + 1; j < CITY_NUM; j++)
{
if (graph[i][j] != unconnect)
{
printf("节点%d 到 节点%d 的路径长度:%d;\n", i + 1, j + 1, graph[i][j]);
}
}
}
printf("\n");
}
void printShortestPaths(int source)
{
printf("从节点%d到各个节点的最短路径长度和必经节点:\n", source + 1);
for (int i = 0; i < CITY_NUM; i++)
{
if (i != source)
{
printf("到节点%d的最短距离:%d\n", i + 1, d[i]);
printf("必经节点:");
int path[CITY_NUM]; // 用于存储路径
int current = i;
int pathLength = 0;
// 反向遍历前驱节点数组,将路径存入 path 数组
while (current != -1)
{
path[pathLength++] = current;
current = p[current];
}
// 打印路径,从起点到终点
for (int j = pathLength - 1; j >= 0; j--)
{
printf("%s", cities[path[j]]);
if (j > 0)
{
printf(" -> ");
}
}
printf("\n");
}
}
}
3.5 主函数
int main()
{
int ver1; // 定义起始节点
fileop();
printf("请输入起始节点(1~8):\n");
scanf("%d", &ver1);
if (ver1 < 1 || ver1 > CITY_NUM)
{
printf("无效的起始节点编号\n");
return 1;
}
DijkstraAlg(ver1 - 1);
printCities();
printEdges();
printShortestPaths(ver1 - 1);
return 0;
}
四.调试
首先测试读文件是否正常
打印结果丢头重尾,在循环中找到错误部分并修正
读文件无误后进行下一步,构建图结构
成功构建图结构,继续Dijsktra算法的编写和调试
五.心得体会
将程序分解成多个模块或函数,每个模块负责不同的功能。这使得代码更易于维护和理解。编写程序过程中还需掌握一些基本的异常处理,如文件打开失败或无效输入等,这提高了程序的健壮性。
六.意见
七.附录
结果展示