1.实验目的
熟练掌握图的操作,掌握 Dijkstra 算法的原理。
2.实验要求
输入:通过命令行参数输入起点和终点的位置名称。
输出: (1)命令行参数不正确输出ERROR_01;
(2)获取最短路径失败时输出ERROR_02;
(3)获取最短路径成功时输出路径长度。
实际地图:
1 右图为校内知名建筑物示意平面图(其中“传送门”用于增加网络复杂度),以边表示建筑物间的路径,各条路径上方的数字表示路径长度。
2 针对该图进行构建数据结构和算法,通过命令行参数输入任意两建筑物的名称,可查询建筑物间的最短路径长度,并输出最短路径。
代码质量要求:
1、优选C语言,禁止直接调用C++ STL库;
2、除循环变量外,其它变量命名使用有明确含义的单词或缩写,不建议使用拼音;
3、禁止出现魔鬼数字; 4、添加必要的程序注释;
5、统一代码格式,例如:{}和空行; 6、变量初始化,不要依赖默认赋值;
7、入参检查,“外部输入输入不可靠”,指针判空(一级指针、二级指针……),循环变量上下限;
8、malloc与free配对; 9、尽量少用全局变量;
10、编译错误解决,从前往后处理,提示出错的行不一定是错误的根因;
3.实验原理
图的存储和头文件的引入:
为了使得代码可以被重复使用,即代码被二次利用时只需要更改图的模式识别代码,同时改变宏定义之中的最大次数,本次将图的文件和宏定义的部分单独的写成一个头文件,在书写主程序时对他进行引用即可。
#ifndef _COMMON_H//和文件名一致,将.换成_;前面加_。
#define _COMMON_H//避免重复声明,使用if no define,define......endif
#include<stdio.h>//添加程序所需要的所有头文件
#include<string.h>
#define Status int//添加宏定义部分
static int a = 0 ;//可以定义静态变量,在所有.c文件中使用。
extern int test(char* p,int n)//声明所有子函数
#endif
迪杰特斯拉算法思想:
设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。
具体步骤:
(1) 初始时,S只包含起点s;U包含除s外的其他顶点,且U中顶点的距离为"起点s到该顶点的距离"[例如,U中顶点v的距离为(s,v)的长度,然后s和v不相邻,则v的距离为∞]。
(2) 从U中选出"距离最短的顶点k",并将顶点k加入到S中;同时,从U中移除顶点k。
(3) 更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(s,v)的距离可能大于(s,k)+(k,v)的距离。
(4) 重复步骤(2)和(3),直到遍历完所有顶点。
简明实例:
4.实验结果
5.问题与思考
思考:
对于Git的使用和一些其他的东西了解的不够深刻,对于编译器编译过程的理解依然不够深刻,这些都需要后续的学习去加以提升。
#.h文件
#pragma once
#ifndef _HEAD_H_
#define _HEAD_H_
#define InAccessible 100000 //无法抵达时的总长度
#define Status int
#define OK 2
#define TRUE 1
#define change 1 //用于扩大1位,进行占位和存放
#define FALSE 0
#define PLen 0
#define none -1
#define ERROR -1 //用于判定是否出现错误
#define OVERFLOW -2 //用于判断是否分配成功
#define ERROR_01 -3 //入参不正确
#define ERROR_02 -4 //无法生成虽短路径
#define MaxNum 20 //最大支持的存放的节点数目
//图的数据结构
typedef struct GRAPH
{
int vexs[MaxNum]; //定义结点矩阵,用于存放所有的结点
int arcs[MaxNum][MaxNum]; //定义邻接矩阵,用于之后的计算和分析
int vexnum; //总的节点数目
int arcnum; //总的边的数目
}Graph; //图的结构体的名称
//所有的地点名称及其所对应的在图中顶点的脚标
extern const char* PlaceName[13] = { "北门","饮水思源","传送门3","传送门1","腾飞塔","图书馆","传送门2","传送门4","教学主楼","宪梓堂","西迁馆","活动中心","南门" };
//0 1 2 3 4 5 6 7 8 9 10 11 12
//对图片进行初始化
extern Status InitMap(Graph* map)
{
if (!map) return ERROR; //鲁棒性检测
map->vexnum = 13; //所有结点的个数
map->arcnum = 18; //所有边的条数
//初始化所有的结点和边
int i = 0, j = 0;
for (i = 0; i < map->vexnum; ++i) {
map->vexs[i] = i;
for (j = 0; j < map->vexnum; ++j) //对角线上初始化为0
if (i == j) map->arcs[i][j] = 0;
else {
map->arcs[i][j] = InAccessible; //全部先初始化为不连通
}
}
//初始化邻接矩阵
map->arcs[0][2] = map->arcs[2][0] = 22;
map->arcs[0][1] = map->arcs[1][0] = 18;
map->arcs[1][3] = map->arcs[3][1] = 27;
map->arcs[1][4] = map->arcs[4][1] = 19;
map->arcs[4][2] = map->arcs[2][4] = 4;
map->arcs[4][7] = map->arcs[7][4] = 32;
map->arcs[3][5] = map->arcs[5][3] = 4;
map->arcs[5][6] = map->arcs[6][5] = 4;
map->arcs[4][5] = map->arcs[5][4] = 23;
map->arcs[5][8] = map->arcs[8][5] = 15;
map->arcs[8][11] = map->arcs[11][8] = 21;
map->arcs[11][12] = map->arcs[12][11] = 30;
map->arcs[7][9] = map->arcs[9][7] = 4;
map->arcs[8][9] = map->arcs[9][8] = 8;
map->arcs[6][12] = map->arcs[12][6] = 21;
map->arcs[6][10] = map->arcs[10][6] = 43;
map->arcs[10][12] = map->arcs[12][10] = 20;
map->arcs[9][12] = map->arcs[12][9] = 14;
return OK;
}
extern Graph;
#endif
#.cpp文件
/*寻找图片中的最小路径的问题,张文硕-2206110686-自动化03*/
#include<stdio.h> //调用内部包
#include<malloc.h> //调用内存分配文件包
#include<string.h>
#include "map.h" //调用map文件,其中存放了图的基本信息和宏定义信息,方便之后对图片文件进行跟换,只需更改InitMap即可。
typedef int PathMatrix[MaxNum][MaxNum+change]; //定义路径矩阵
typedef struct {
int weight[MaxNum]; //定义最短路径长度和
Status Visited[MaxNum]; //定义列表标记是否被访问过
}ShortPathList;
//判断入参数量是否正确
//如果参数为3, 则认为没有错误
//如果参数不为3, 则认为有错误
Status IfError_01(int argc)
{
if (argc == 3) return FALSE;
else
{
printf("ERROR_01");
return TRUE;
}
}
//寻找输入的地点在地点列表中的位置
//如果找到了,则返回下标
//如果找不到,则返回ERROR_02
int FindPlace(const char* PlaceName[], char* name,Graph* map)
{
if (!PlaceName || !name|| !map) return ERROR; //鲁棒性检测
int i = 0;
while (i < map->vexnum) //进行寻址
{
if (strcmp(PlaceName[i], name) == 0) return i;
i++;
}
printf("ERROR_02"); //当跳出循环还没返回答案时,认为出错了
return ERROR_02;
}
//定义函数初始化最短路径列表
//所有visit状态更新为FALSE
//所有权重进行处理,除自己到自己之外全部为无穷大,方便之后修改
Status InitList(ShortPathList* List,Graph map,int start)
{
if (!List) return ERROR;
for (int i = 0; i < map.vexnum; i++)
{
List->Visited[i] = FALSE;
List->weight[i] = InAccessible;
}
List->weight[start] = 0;
return OK;
}
//功能:让path矩阵的每行第一列表示路径长度.
//每行第一列全部初始化为FALSE
Status InitPath(PathMatrix& path,Graph map,int start)
{
if (!path) return ERROR;
for (int i = 0; i < map.vexnum; i++)
{
for (int j = 0; j < map.vexnum + change; j++)
{
path[i][j] = none; //将所有的长度初始化为-1
}
}
path[start][0] = 1;
path[start][1] = start;
return OK;
}
//功能:更新路径,使得路径更小
//输入:使得路径更新为到起点距离更小的路径,更小路径结点横坐标为now,原先的路径横坐标结点为before
//输出:状态
Status UpdatePath(PathMatrix &path,int before,int now,Graph map)
{
if (!path) return ERROR;
for (int i = 0; i < map.vexnum; i++)
{
path[before][i] = path[now][i]; //将小的直接抄过来
}
return OK;
}
//功能:修改结点到起点的路径长度最小值,并调用函数UpdatePath更新每个结点的最短路径。
//结果:如果新的长度小于原有长度,则进行更新。跟新后最后一个结点路径指向被更新的结点。
// 总的长度进行加一运算,同时对权重也进行更新。
Status ChangeWeight(Graph map, ShortPathList* list, int curnode, PathMatrix& path)
{
if (!list||!path) return ERROR;
for (int i = 0; i < map.vexnum; i++)
{
if (list->Visited[i] == FALSE)
{
if (list->weight[i] > list->weight[curnode] + map.arcs[curnode][i])
{
UpdatePath(path, i, curnode,map); //当满足条件时更新路径
path[i][PLen]++; //总的路径长度path[i][0]加一
path[i][path[i][PLen]] = i; //最后一步补上当前的位置
list->weight[i] = list->weight[curnode] + map.arcs[curnode][i]; //将权重更新为较小的值
}
}
}
return OK;
}
//功能:寻找最短的路径的结点并修改访问状态
//输入:list存放有每个节点到起点的距离;map用于存放图的信息
//输出:最小的所处的位置;
int FindShort(ShortPathList* list,Graph map)
{
if (!list) return ERROR;
int shortest=none;
int flag = TRUE;
int i = 0;
while ( i < map.vexnum && flag) //寻找第一个未被访问的结点
{
if (list->Visited[i] == FALSE)
{
shortest = i;
flag =FALSE;
}
i++;
}
for (int i = 0; i < map.vexnum; i++) //寻找所有节点中未被访问的最小长度结点
{
if (list->Visited[i] == FALSE)
{
if (list->weight[i] < list->weight[shortest])
{
shortest = i;
}
}
}
//if (shortest != none) list->Visited[shortest] = TRUE; //更改访问状态
return shortest; //返回角标,如果角标是none,认为所有都访问完了,可以给结果了
}
//函数功能:求出指定起点到终点的最短路径,并且存放最短路径
/*输入:map 已经初始化完成的有向图
start 起点序号
end 终点序号
list 存放最短路径长度的数组
path 用于记录脚步的
返回值:返回从start到end的最短路径权值*/
int DJT_ShortPath(Graph map, PathMatrix& path, ShortPathList* list, int start, int end)
{
if (!path || ! list) return ERROR;
InitList(list,map,start);
InitPath(path, map,start);
int cursor;
while (FindShort(list, map) != none)
{
cursor = FindShort(list, map);
list->Visited[cursor] = TRUE;
ChangeWeight(map, list, cursor, path);
}
return list->weight[end];
}
Status PrintPath(PathMatrix path, int end,const char** PlaceName)
{
if (!PlaceName) return ERROR;
int flag = TRUE;
for (int i = 0; i < path[end][0]; i++)
{
if (flag)
{
printf("\n%s", PlaceName[path[end][i + change]]);
flag = FALSE;
}
else
{
printf("->%s", PlaceName[path[end][i + change]]);
}
}
return OK;
}
int main(int argc, char* argv[])
{
if(IfError_01(argc)) return ERROR_01;
Graph* map = (Graph*)malloc(sizeof(Graph));
if (!map) return OVERFLOW;
InitMap(map);
PathMatrix path = { none }; //定义记录路径的矩阵
ShortPathList* list=(ShortPathList*) malloc(sizeof(ShortPathList)); //定义所有结点到起点的路径长度,
if (!list) return OVERFLOW;
int start = FindPlace(PlaceName, argv[1], map); //起始地点的坐标
if (start == ERROR_02) return ERROR_02;
int end = FindPlace(PlaceName, argv[2], map); //终止地点的坐标
if (end == ERROR_02) return ERROR_02;
int shortestdistance = 0;
shortestdistance=DJT_ShortPath(*map, path, list, start, end); //寻找最小路径长度并将其存放进shortestdistance
printf("%d", shortestdistance);
//PrintPath(path,end,PlaceName);
free(map); //释放map指针
free(list); //释放list指针
return 0;
}