一、实验目的与要求:
(一)实验目的
1、掌握图形结构的(邻接矩阵)输入方法
2、掌握图形结构的说明、创建以及图的存储表示
3、掌握最短路径算法原理
4、掌握最短路径的实现方法
(二)实验要求
1、熟悉C/C++语言编程
2、熟练使用C/C++语言实现图形结构的说明、创建以及图的存储表示
二、实验内容:
校园导航问题
(一)问题描述:
设计校园的平面图,至少包括8个以上的场所,每两个场所间可以有不同的路,且路长也可能不同。
1.(必做)找出从一个场所出发到达其它所有场所的最佳路径(最短路径)。
2.(选做)找出从任意场所到达另一场所的最佳路径(最短路径)。
(二) 基本要求
1、设计校园平面图,在校园景点选8个以上的景点。以图中顶点表示校园内各景点,存放景点名称、代号、简介等信息;以边表示路径,存放路径长度等有关信息。
2、为来访客人提供图中任意景点相关信息的查询。
3、为来访客人提供任意景点的问路查询。
(如:从学校1号门到各景点的最佳路线及距离;主楼到湖边餐厅的最佳路线及距离。)
4、界面要求:有合理提示;每个功能可以设立菜单;根据提示,可以完成相关的功能要求
3、熟练应用图形数据结构编程实现最短路径的算法
三、实验步骤与过程:
[介绍实验过程、算法描述及解题思路等]
[实验代码清单(要有注释语句)]
实验过程:
先对实验要求进行一个脑中的呈现。如:输出格式,菜单页面,所需算法等等。再根据所想的算法进行代码实现。最后,对输出格式进行修改及完善即可。
算法描述:
Dijkstra算法是一个基于「贪心」、「广度优先搜索」、「动态规划」求一个图中一个点到其他所有点的最短路径的算法,时间复杂度O(n^2)。 (可以解决问题一)
Floyd算法是一个基于「贪心」、「动态规划」求一个图中所有点到所有点最短路径的算法,时间复杂度O(n^3)。(可以解决问题二)
解题思路:
看到第一问和第二问就很容易想到了Dijkstra算法和Floyd算法去实现,所以就去实现这个两个算法即可。
代码呈现:
功能一:对各个场所进行简单描述
注:对于这个其实也可以创建一个顶点的结构体,包含char str[MAX]去存放场所信息。
//各个场所的功能函数
void feature()
{
hint();
printf("请输入场所编号: ");
int num;
scanf("%d",&num);
switch (num)
{
case 1:
printf("宿舍有公共厨房,公共阅览室,公共厕所\n\n");
break;
case 2:
printf("一食堂有丰富的菜品\n\n");
break;
case 3:
printf("湖滨餐厅有中西方菜品\n\n");
break;
case 4:
printf("图书馆设施好,学习氛围浓烈\n\n");
break;
case 5:
printf("实验楼有实验室以及机房\n\n");
break;
case 6:
printf("可以在里面举办一些晚会\n\n");
break;
case 7:
printf("老师办公区域\n\n");
break;
case 8:
printf("看主楼的最好位置\n\n");
break;
default:
printf("无该场所编号,请重新输入场所编号\n\n");
break;
}
}
功能二:找出从一个场所出发到达其它所有场所的最佳路径(dilkstra算法)
//取最小值的函数
int getMin(int* d,int* s,Graph* G)
{
int min=MAX;
int index;
for(int i=0;i<G->vexNum;i++)
{
if(!s[i] && d[i]<min) //比较权值
{
min=d[i];
index=i;
}
}
return index;
}
void dijkstra(Graph* G,int index) //index为目标节点
{
/*
最短路径(dijkstra算法):
准备辅助数组:
S数组:记录了目标顶点到其他顶点的最短路径是否求得
P数组:记录了目标顶点到其他顶点的最短路径的前驱节点
D数组:记录了目标顶点到其他顶点的最短路径长度
*/
int* s=(int*)malloc(sizeof(int)*G->vexNum);
int* p=(int*)malloc(sizeof(int)*G->vexNum);
int* d=(int*)malloc(sizeof(int)*G->vexNum);
//对辅助数组进行初始化
for(int i=0;i<G->vexNum;i++)
{
if(G->arcs[index][i]>0 && G->arcs[index][i]!=MAX)
{
d[i]=G->arcs[index][i];
p[i]=index;
}
else
{
d[i]=MAX;
p[i]=-1;
}
if(i==index)
{
s[i]=1;
d[i]=0;
}
else
{
s[i]=0;
}
}
for(int i=0;i<G->vexNum-1;i++)
{
int index=getMin(d,s,G);
s[index]=1; //代表已经找到了最小路径
for(int j=0;j<G->vexNum;j++)
{
if(!s[j] && d[index]+G->arcs[index][j]<d[j]) //判断是否要重新更新d数组中的最小路径
{
d[j]=d[index]+G->arcs[index][j];
p[j]=index; //更改
}
}
}
//输出到达个点的最短路径
for(int i=0;i<=G->vexNum-1;i++)
{
int j=i;
printf("%s <—— ",G->vexs[i].name); //最后打印所到场所
while(j!=index)
{
j=p[j];
if(j!=index)
{
printf("%s <—— ",G->vexs[j].name);
}
}
printf("%s",G->vexs[index].name);
printf("\n%s --> %s的最短路径大小为:%d分钟",G->vexs[index].name,G->vexs[i].name,d[i]);
printf("\n");
}
}
功能三:找出从一个场所出发到达其它所有场所的最佳路径(floyd算法)
/*
floyd算法(3 for:[n*n*n]):求每一对顶点之间的最短路径
d数组:保存了两点的最短路径长度(权值)
p数组:保存了两点之间最短路径前驱
核心:试探法,通过加入不同的点进行中转,选择出最优解
*/
void floyd(Graph* G,int f,int n)
{
int d[G->vexNum][G->vexNum];
int p[G->vexNum][G->vexNum];
//数组的初始化
for(int i=0;i<G->vexNum;i++)
{
for(int j=0;j<G->vexNum;j++)
{
d[i][j]=G->arcs[i][j];
if(G->arcs[i][j]>0 && G->arcs[i][j]!=MAX) //两个顶点之间有弧
{
p[i][j]=i; //有弧指向前继顶点
}
else
{
p[i][j]=-1;
}
}
}
/*
进行试探法:第一个循环代表着加入的顶点进行试探
后两个循环代表着每一对顶点
*/
for(int i=0;i<G->vexNum;i++)
{
for(int j=0;j<G->vexNum;j++)
{
for(int k=0;k<G->vexNum;k++)
{
if(d[j][i]+d[i][k]<d[j][k])
{
d[j][k]=d[j][i]+d[i][k];
p[j][k]=p[i][k];
}
}
}
}
printf("%s --> %s的最小路径为:%d分钟\n",G->vexs[f].name,G->vexs[n].name,d[f][n]);
int l=p[f][n]; // l作为中间变量用来接受最短路径中的父亲节点
printf("最短路径为: %s",G->vexs[f].name); // 输出最短路径
int temp=0; //创建一个中间变量,便于储存l值
while(l!=f)
{
printf(" --> %s",G->vexs[l].name);
if(l==temp) //当后一个l等于前一个l时,循环截至
break;
l=p[l][n]; // 不断更新l节点
temp=l;
}
printf(" --> %s\n\n",G->vexs[n].name);
}
完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<windows.h>
/*图顶点之间不通,那么邻接矩阵的值为MAX
如果顶点是自己本身,那么值为0*/
#define MAX 32767
int system(const char *command);//
//结构体定义顶点
typedef struct Vertex
{
int num; //顶点编号
char name[MAX]; //顶点名称
}Vertex;
typedef struct Graph{
Vertex vexs[9]; //顶点
int** arcs; //权重(使用**目的表示矩阵)
int vexNum;
int arcsNum;
}Graph;
//初始化一个图
Graph* initGraph(int vexNum)
{
Graph* G=(Graph*)malloc(sizeof(Graph));
G->arcs=(int**)malloc(sizeof(int*) *vexNum);
for(int i=0;i<vexNum;i++)
{
G->arcs[i]=(int*)malloc(sizeof(int) *vexNum);
}
for(int i=1;i<=G->vexNum;i++)
G->vexs[i].num=i; //初始化顶点编号为1-10
G->vexNum=vexNum;
G->arcsNum=0;
strcpy(G->vexs[0].name,"宿舍");
strcpy(G->vexs[1].name,"一食堂");
strcpy(G->vexs[2].name,"湖滨餐厅");
strcpy(G->vexs[3].name,"图书馆");
strcpy(G->vexs[4].name,"二号实验楼");
strcpy(G->vexs[5].name,"礼堂");
strcpy(G->vexs[6].name,"主楼");
strcpy(G->vexs[7].name,"一号门");
return G;
}
//构建一个图
void createGraph(Graph* G,int* arcs)
{
for(int i=0;i<G->vexNum;i++)
{
for(int j=0;j<G->vexNum;j++)
{
G->arcs[i][j]=*(arcs+i*G->vexNum+j);
if(G->arcs[i][j]!=0&&G->arcs[i][j]!=MAX)
{
G->arcsNum++;
}
}
}
G->arcsNum=G->arcsNum/2;
}
//取最小值的函数
int getMin(int* d,int* s,Graph* G)
{
int min=MAX;
int index;
for(int i=0;i<G->vexNum;i++)
{
if(!s[i] && d[i]<min) //比较权值
{
min=d[i];
index=i;
}
}
return index;
}
void dijkstra(Graph* G,int index) //index为目标节点
{
/*
最短路径(dijkstra算法):
准备辅助数组:
S数组:记录了目标顶点到其他顶点的最短路径是否求得
P数组:记录了目标顶点到其他顶点的最短路径的前驱节点
D数组:记录了目标顶点到其他顶点的最短路径长度
*/
int* s=(int*)malloc(sizeof(int)*G->vexNum);
int* p=(int*)malloc(sizeof(int)*G->vexNum);
int* d=(int*)malloc(sizeof(int)*G->vexNum);
//对辅助数组进行初始化
for(int i=0;i<G->vexNum;i++)
{
if(G->arcs[index][i]>0 && G->arcs[index][i]!=MAX)
{
d[i]=G->arcs[index][i];
p[i]=index;
}
else
{
d[i]=MAX;
p[i]=-1;
}
if(i==index)
{
s[i]=1;
d[i]=0;
}
else
{
s[i]=0;
}
}
for(int i=0;i<G->vexNum-1;i++)
{
int index=getMin(d,s,G);
s[index]=1; //代表已经找到了最小路径
for(int j=0;j<G->vexNum;j++)
{
if(!s[j] && d[index]+G->arcs[index][j]<d[j]) //判断是否要重新更新d数组中的最小路径
{
d[j]=d[index]+G->arcs[index][j];
p[j]=index; //更改
}
}
}
//输出到达个点的最短路径
for(int i=0;i<=G->vexNum-1;i++)
{
int j=i;
printf("%s <—— ",G->vexs[i].name); //最后打印所到场所
while(j!=index)
{
j=p[j];
if(j!=index)
{
printf("%s <—— ",G->vexs[j].name);
}
}
printf("%s",G->vexs[index].name);
printf("\n%s --> %s的最短路径大小为:%d分钟",G->vexs[index].name,G->vexs[i].name,d[i]);
printf("\n");
}
}
/*
floyd算法(3 for:[n*n*n]):求每一对顶点之间的最短路径
d数组:保存了两点的最短路径长度(权值)
p数组:保存了两点之间最短路径前驱
核心:试探法,通过加入不同的点进行中转,选择出最优解
*/
void floyd(Graph* G,int f,int n)
{
int d[G->vexNum][G->vexNum];
int p[G->vexNum][G->vexNum];
//数组的初始化
for(int i=0;i<G->vexNum;i++)
{
for(int j=0;j<G->vexNum;j++)
{
d[i][j]=G->arcs[i][j];
if(G->arcs[i][j]>0 && G->arcs[i][j]!=MAX) //两个顶点之间有弧
{
p[i][j]=i; //有弧指向前继顶点
}
else
{
p[i][j]=-1;
}
}
}
/*
进行试探法:第一个循环代表着加入的顶点进行试探
后两个循环代表着每一对顶点
*/
for(int i=0;i<G->vexNum;i++)
{
for(int j=0;j<G->vexNum;j++)
{
for(int k=0;k<G->vexNum;k++)
{
if(d[j][i]+d[i][k]<d[j][k])
{
d[j][k]=d[j][i]+d[i][k];
p[j][k]=p[i][k];
}
}
}
}
printf("%s --> %s的最小路径为:%d分钟\n",G->vexs[f].name,G->vexs[n].name,d[f][n]);
int l=p[f][n]; // l作为中间变量用来接受最短路径中的父亲节点
printf("最短路径为: %s",G->vexs[f].name); // 输出最短路径
int temp=0; //创建一个中间变量,便于储存l值
while(l!=f)
{
printf(" --> %s",G->vexs[l].name);
if(l==temp) //当后一个l等于前一个l时,循环截至
break;
l=p[l][n]; // 不断更新l节点
temp=l;
}
printf(" --> %s\n\n",G->vexs[n].name);
}
void menu()
{
printf("**************************************************************\n");
printf(" 深圳北理莫斯科大学校园导航 \n");
printf("**************************************************************\n");
printf("**** 1.查询找出从一个场所出发到达其它所有场所的最佳路径 ****\n");
printf("**** 2.查询找出从任意场所到达另一场所的最佳路径 ****\n");
printf("**** 3.查询各个场所的功能 ****\n");
printf("**** 4.清屏 ****\n");
printf("**** 0.退出程序 ****\n");
printf("**************************************************************\n");
}
void hint()
{
printf("1-宿舍 2-一食堂 3-湖滨餐厅 4-图书馆\n");
printf("5-二号实验楼 6-礼堂 7-主楼 8-一号门\n");
}
//各个场所的功能函数
void feature()
{
hint();
printf("请输入场所编号: ");
int num;
scanf("%d",&num);
switch (num)
{
case 1:
printf("宿舍有公共厨房,公共阅览室,公共厕所\n\n");
break;
case 2:
printf("一食堂有丰富的菜品\n\n");
break;
case 3:
printf("湖滨餐厅有中西方菜品\n\n");
break;
case 4:
printf("图书馆设施好,学习氛围浓烈\n\n");
break;
case 5:
printf("实验楼有实验室以及机房\n\n");
break;
case 6:
printf("可以在里面举办一些晚会\n\n");
break;
case 7:
printf("老师办公区域\n\n");
break;
case 8:
printf("看主楼的最好位置\n\n");
break;
default:
printf("无该场所编号,请重新输入场所编号\n\n");
break;
}
}
int main()
{
//打印学校图(注:需要把照片与代码放在用一个文件夹下)
char pic_name[80]="school.PNG";
char cmd[100];
sprintf(cmd,pic_name);
system(cmd);
Graph* G=initGraph(8);
int arcs[8][8]={ //邻接矩阵(权重(按走到该建筑的时间创立))
0,3,6,6,10,5,MAX,4,
3,0,4,MAX,MAX,MAX,MAX,MAX,
6,4,0,1,6,MAX,MAX,MAX,
6,MAX,1,0,5,MAX,MAX,MAX,
10,MAX,6,5,0,MAX,3,MAX,
5,MAX,MAX,MAX,MAX,0,1,2,
MAX,MAX,MAX,MAX,3,1,0,1,
4,MAX,MAX,MAX,MAX,2,1,0
};
printf("学校的平面图为:\n");
createGraph(G,(int*)arcs);
int input;
do
{
Sleep(1000); //延迟输出1s,方便查看
menu();
printf("请输入所需要的功能: ");
scanf("%d",&input);
switch (input) {
case 1:
{
int n;
hint();
printf("请输入场所编号: ");
scanf("%d",&n);
if(n<1||n>8)
{
printf("无该场所编号,请重新输入\n\n");
break;
}
printf("\n从该场所到各个场所的最短路径依次是:\n");
dijkstra(G,n-1);
printf("\n");
break;
}
case 2:
{
int a,b;
hint();
printf("请输入想从哪个场所到哪个场所的编号\n");
printf("开始场所: ");
scanf("%d",&a);
printf("目的地场所: ");
scanf("%d",&b);
if((a<1||a>8)&&(b<1||b>8))
{
printf("无该场所编号,请重新输入\n\n");
break;
}
floyd(G,a-1,b-1);
break;
}
case 3:
{
feature();
break;
}
case 4:
{
system("cls");
break;
}
case 0:
{
printf("退出程序\n");
break;
}
default:
{
printf("无该功能,请重新输入所需的功能\n\n");
break;
}
}
}while(input);
return 0;
}
四、实验结果及数据处理分析:
根据运行结果,发现此代码实现了该实验的功能。(为了更好的呈现结果这里将清屏功能删除,在实际代码中可实现清屏功能。)