【题目】图的操作和应用之景区信息管理系统
现有一个景区,景区里面有若干个景点,景点之间满足以下条件:
(1) 某些景点之间铺设了道路(相邻)
(2) 这些道路都是可以双向行驶的(无向图)
(3) 从任意一个景点出发都可以游览整个景区(连通图)
开发景区信息管理系统,对景区的信息进行管理。使用图的数据结构来保存景区景点信息,为用户提供创建图、查询景点信息、旅游景点导航、搜索最短路径、铺设电路规划等功能。
(1) 景点信息:景点编号、名字和介绍
编号 |
名字 |
介绍 |
0 |
A区 |
…… |
1 |
B区 |
…… |
2 |
C区 |
…… |
3 |
D区 |
…… |
4 |
E区 |
…… |
5 |
F区 |
…… |
6 |
G区 |
…… |
(2) 道路信息:景点1,景点2、两个景点之间的距离。
景点1 |
景点2 |
距离(m) |
A |
C |
700 |
A |
E |
1000 |
A |
F |
600 |
B |
C |
1000 |
B |
G |
1000 |
C |
D |
400 |
D |
E |
300 |
D |
G |
400 |
E |
F |
600 |
F |
G |
500 |
(1) 读文件创建图
输入:从Vex.txt文件中读取景点信息,从Edge.txt文件中读取道路信息。
处理:根据读取的景区信息创建景区景点图。
(2) 查询景点
输入:想要查询的景点的编号。
处理:根据输入的景点编号,查询该景点及相邻景点的信息。
输出:
① 景点名字
② 景点介绍
③ 相邻景区的名字
④ 到达相邻景区的路径长度
(3) 旅游景点导航
输入:起始景点的编号。
处理:使用深度优先搜索(DFS)算法,查询以该景点为起点,无回路游览整个景区的路线。
输出:所有符合要求的导航路线。
(4) 搜索最短路径
输入:
① 起始景点的编号
② 终点的编号。
处理:使用迪杰斯特拉(Dijkstra)算法,求得从起始景点到终点之间的最短路径,计算路径总长度。
输出:
① 最短路线
② 路径总长度
(5) 铺设电路规划
处理:根据景区景点图使用普里姆(Prim)算法构造最小生成树,设计出一套铺设线路最短,但能满足每个景点都能通电的方案。
输出:
① 需要铺设电路的道路
② 每条道路铺设电路的长度
③ 铺设电路的总长度
(6) 修改图保存文件
插入、删除、修改顶点、边的信息,注意顶点和边的关系,之后保存文件,重新读取文件建立图的存储结构并显示。
重点注意顶点和边的关系,考虑边是否重复?顶点是否存在?……
【题目】图的操作和应用之景区信息管理系统
现有一个景区,景区里面有若干个景点,景点之间满足以下条件:
(1) 某些景点之间铺设了道路(相邻)
(2) 这些道路都是可以双向行驶的(无向图)
(3) 从任意一个景点出发都可以游览整个景区(连通图)
开发景区信息管理系统,对景区的信息进行管理。使用图的数据结构来保存景区景点信息,为用户提供创建图、查询景点信息、旅游景点导航、搜索最短路径、铺设电路规划等功能。
(1) 景点信息:景点编号、名字和介绍
编号 |
名字 |
介绍 |
0 |
A区 |
…… |
1 |
B区 |
…… |
2 |
C区 |
…… |
3 |
D区 |
…… |
4 |
E区 |
…… |
5 |
F区 |
…… |
6 |
G区 |
…… |
(2) 道路信息:景点1,景点2、两个景点之间的距离。
景点1 |
景点2 |
距离(m) |
A |
C |
700 |
A |
E |
1000 |
A |
F |
600 |
B |
C |
1000 |
B |
G |
1000 |
C |
D |
400 |
D |
E |
300 |
D |
G |
400 |
E |
F |
600 |
F |
G |
500 |
(1) 读文件创建图
输入:从Vex.txt文件中读取景点信息,从Edge.txt文件中读取道路信息。
处理:根据读取的景区信息创建景区景点图。
(2) 查询景点
输入:想要查询的景点的编号。
处理:根据输入的景点编号,查询该景点及相邻景点的信息。
输出:
① 景点名字
② 景点介绍
③ 相邻景区的名字
④ 到达相邻景区的路径长度
(3) 旅游景点导航
输入:起始景点的编号。
处理:使用深度优先搜索(DFS)算法,查询以该景点为起点,无回路游览整个景区的路线。
输出:所有符合要求的导航路线。
(4) 搜索最短路径
输入:
① 起始景点的编号
② 终点的编号。
处理:使用迪杰斯特拉(Dijkstra)算法,求得从起始景点到终点之间的最短路径,计算路径总长度。
输出:
① 最短路线
② 路径总长度
(5) 铺设电路规划
处理:根据景区景点图使用普里姆(Prim)算法构造最小生成树,设计出一套铺设线路最短,但能满足每个景点都能通电的方案。
输出:
① 需要铺设电路的道路
② 每条道路铺设电路的长度
③ 铺设电路的总长度
(6) 修改图保存文件
插入、删除、修改顶点、边的信息,注意顶点和边的关系,之后保存文件,重新读取文件建立图的存储结构并显示。
重点注意顶点和边的关系,考虑边是否重复?顶点是否存在?……
-------------------------------------------------------我是一条分割线-----------------------------------------------------------
以下为本人创建的两个文件截图和cpp代码,注意cpp文件与文件必须放在一个文件夹中,不然会显示文件不存在.
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
#define ERROR 0
#define OK 1
#define MAX_VERTEX_NUM 20 //图的最大定点数
#define MAX_INFO 200 //每条弧附带信息最大长度
typedef struct ArcCell{
int adj;//两点之间是否相邻,0为不相邻
int info;//两点之间权值是多少,0为无权值
}ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];//一个结点,结点组成的二维数组(即邻接矩阵)
typedef struct scenery{
int num; //编号
char name;//名称
char introduce[MAX_INFO];//介绍
}scen[MAX_VERTEX_NUM];//景区的集合
typedef struct{
scen S;
AdjMatrix arcs;
int vexnum;//当前顶点数
int arcnum;//边的数量
}Graph;
int visited[MAX_VERTEX_NUM];//判断是否遍历过
int LocateVex(Graph G,char x){
for(int i=0;i<G.vexnum;i++){
if(G.S[i].name == x)
return i;
}
return -1;
}
void Scen_Information(scen S,int num,FILE *fp){
int i;
char intro[200];
for(int i=0;i<num;i++){
S->num = i;
fscanf(fp,"%c%*c",&S[i].name);
fscanf(fp,"%s",&intro);
fgetc(fp);//吸收光标
strcpy(S[i].introduce,intro);
}
}
void CreateGraph(Graph *G){
char v1,v2;
int i,j,k,z;
FILE *fp;
fp = fopen("Vex_new.txt","r");
if(fp == NULL){
printf("该文件不存在");
exit(-1);
}
fscanf(fp,"%d%*c",&(G->vexnum));
Scen_Information(G->S,G->vexnum,fp);
fclose(fp);
for( k=0;k<G->vexnum;k++)
for(z=0;z<G->vexnum;z++)
{ //初始化全部不相邻和无权值
G->arcs[k][z].adj = 0;
G->arcs[k][z].info = 0;
}
fp = fopen("Edge_new.txt","r");
if(fp == NULL){
printf("该文件不存在");
exit(-1);
}
fscanf(fp,"%d%*c",&G->arcnum);
for(k=0;k<G->arcnum;k++){
fscanf(fp,"%c%c%*c",&v1,&v2);
i = LocateVex(*G,v1);
j = LocateVex(*G,v2);
//cout<<i<<j<<" ";
if(i>=0 && j>=0){
G->arcs[i][j].adj = 1;
G->arcs[j][i].adj = 1;
}
fscanf(fp,"%d%*c",&G->arcs[i][j].info);
//G->arcs[i][j].info= G->arcs[j][i].info;//错误的写法
G->arcs[j][i].info = G->arcs[i][j].info;
}
fclose(fp);
printf("该图的邻接矩阵如下:\n");
for(k=0;k<G->vexnum;k++){
for(z=0;z<G->vexnum;z++){
printf("%d",G->arcs[k][z].adj);
cout<<" ";
}
printf("\n");
}
}
void Search_infor(Graph G){
cout<<"查询景区功能:\n请输入您要查询的景区编号(0-6)\n";
int x;
cin>>x;
if(x<0||x>6)
{
cout<<"输入错误,请重新输入(0-6)\n";
cin>>x;
}
cout<<"您查询的是"<<G.S[x].name<<"区,该区有以下特点:"<<G.S[x].introduce<<"\n";
int n=0;
for(int i=0;i<G.vexnum;i++)
if(G.arcs[x][i].adj == 1){
n++;
}
cout<<"该景区有"<<n<<"个相邻景区,分别为:\n";
for(int i=0;i<G.vexnum;i++)
if(G.arcs[x][i].adj == 1){
cout<<G.S[i].name<<"区,距离"<<G.S[x].name<<"区"<<G.arcs[x][i].info<<"米";
cout<<"\n";
}
cout<<"\n";
}
void Print(char x){
cout<<x<<"景区 -> ";
}
void Print_Last(char x){
cout<<x<<"景区\n";
}
int Find_Next_Vex(Graph G,int v){
int i;
if (v > G.vexnum || v < 0)
return -1;
for(i=0;i<G.vexnum;i++){
if(G.arcs[v][i].adj == 1)
return i;
}
return -1;
}
int Find_After_Next_Vex(Graph G,int v,int w){
int i;
if(G.arcs[v][w].adj == 0){
return -1;
}
for(i=w+1;i<G.vexnum;i++){
if(G.arcs[v][i].adj == 1)
return i;
}
return -1;
}
int Traverse(Graph G,int i,int j,char pass[MAX_VERTEX_NUM],void (*Print)(char)){
int n,m = 0;
visited[i] = 1;
pass[j] = G.S[i].name;
j++;
//cout<<"测试函数\n";
for(int w = Find_Next_Vex(G,i);w>=0;w = Find_After_Next_Vex(G,i,w)){
if(!visited[w]){
m = Traverse(G,w,j,pass,Print);
if(m == 1 && j==G.vexnum - 1){
for( n=0;n < j;n++)
Print(pass[n]);
Print_Last(pass[n]);
}
visited[w] = 0;
}
}
return ++m;
}
void DFS(Graph G){
cout<<"浏览所有景区服务:\n请输入您的起点位置编号(0-6):\n";
int x;
cin>>x;
if(x<0||x>6)
{
cout<<"输入错误,请重新输入(0-6)\n";
cin>>x;
}
cout<<"从该起点无回路地游览所有景区有如下方案:\n";
char pass[MAX_VERTEX_NUM];
int j = 0;
Traverse(G,x,j,pass,Print);
cout<<"\n";
}
#define INFINITY 99999
void Dijkstra(Graph G){
cout<<"计算两景区间最短距离服务:\n";
int Dis[MAX_VERTEX_NUM],Path[MAX_VERTEX_NUM][MAX_VERTEX_NUM],Visit[MAX_VERTEX_NUM],Path_Through_Num[MAX_VERTEX_NUM];
int start,end,i,j,w,v,min;
cout<<"请输入起点位置编号(0-6):";cin>>start;
for(i=0;i<G.vexnum;i++){
Visit[i] = 0;
Path_Through_Num[i] = 1;
if(G.arcs[start][i].info){
Dis[i] = G.arcs[start][i].info;
}
else
Dis[i] = INFINITY;
for(j=0;j<G.vexnum;j++){
Path[i][j] = start;
}
}
Visit[start] = 1;
cout<<"请输入终点位置编号(0-6):"; cin>>end;
while(end == start) {cout<<"您输入的起点与终点一样,请重新输入:"; cin>>end;}
for(i=1;i<G.vexnum;i++){
min = INFINITY;
for(w=0;w<G.vexnum;w++){
if(!Visit[w]){
if(Dis[w]<min){
v = w;
min = Dis[w];
}
}
}
Path[v][Path_Through_Num[v]] = v;
Path_Through_Num[v] = Path_Through_Num[v] + 1;
Visit[v] = 1;
if(v == end){
cout<<"正在为您计算两点间的最短距离...";
cout<<"小天为你计算的最短路径为:\n";
for(w=0;w<Path_Through_Num[end];w++){
// cout<<Path_Through_Num[end]<<" "<<Path[end][w]<<endl;
cout<<G.S[Path[end][w]].name<<"区 ";
}
cout<<",路径的总距离为"<<min<<"米\n";
break;
}
for(w=0;w<G.vexnum;w++){
if(!Visit[w] && (min+G.arcs[v][w].info < Dis[w]) && G.arcs[v][w].adj == 1){
Dis[w] = min + G.arcs[v][w].info;
for(j=0;j<Path_Through_Num[v];j++)
Path[w][j] = Path[v][j];
Path_Through_Num[w] = Path_Through_Num[v];
}
}
}
}
#define MAX_WEIGHT 99999
void Prim(Graph G){
cout<<"本功能为计算铺设电线的最小化生成树\n";
int graph[MAX_VERTEX_NUM][MAX_VERTEX_NUM];//存放图的信息
int Weight_Edge[MAX_VERTEX_NUM];//权值边
int Weight_Edge_Start[MAX_VERTEX_NUM];//存放权值边的起点
int i,j,min,minid,sum=0;
for(i=0;i<G.vexnum;i++)
for(j=0;j<G.vexnum;j++){
if(G.arcs[i][j].info)
graph[i][j] = G.arcs[i][j].info;
else
graph[i][j] = MAX_WEIGHT;
}
for(i=0;i<G.vexnum;i++){
Weight_Edge[i] = graph[0][i];
Weight_Edge_Start[i] = 0;
}
Weight_Edge[0] = 0;
for(i=1;i<G.vexnum;i++){
min = MAX_WEIGHT;
minid = 0;
for(j=1;j<G.vexnum;j++){
if(Weight_Edge[j] < min && Weight_Edge[j] != 0){
min = Weight_Edge[j];
minid = j;
}
}
if(G.S[Weight_Edge_Start[minid]].name != G.S[minid].name)
cout<<"从"<<G.S[Weight_Edge_Start[minid]].name<<"到"<<G.S[minid].name<<"有"<<min<<"米\n";
if(min != MAX_WEIGHT)
sum += min;
Weight_Edge[minid] = 0;
for(j=1;j<G.vexnum;j++){
if(graph[minid][j] < Weight_Edge[j]){
Weight_Edge[j] = graph[minid][j];
Weight_Edge_Start[j] = minid;
}
}
}
cout<<"需要铺设电线"<<sum<<"米"<<endl;
}
char Vex[MAX_VERTEX_NUM];
int See_about_Vex(char Vex[MAX_VERTEX_NUM],char s,int n){
int i;
for(i=0;i<n;i++){
if(Vex[i] == s){
return i;
}
}
return -1;
}
int Add_Vex(Graph G,char Vex[MAX_VERTEX_NUM],int vex_num){
int i,j;
FILE *fp;
char v,Str[MAX_INFO];
cout<<"请输入您所要添加的顶点名称及景点信息:\n";
scanf("%c%*c%s",&v,&Str);
getchar();
i = LocateVex(G,v);
while(i>=0){
cout<<"您输入的顶点已经有了,请重新输入:\n";
scanf("%c%*c%s",&v,&Str);
getchar();
i = LocateVex(G,v);
}
fp = fopen("Vex_new.txt","r+");
if(fp == NULL){
cout<<"该文件不存在";
exit(-1);
}
fscanf(fp,"%d",&j);
rewind(fp);
fprintf(fp,"%d",++j);
fseek(fp,0,SEEK_END);
fprintf(fp,"\n%c %s",v,Str);
fclose(fp);
Vex[j-1] = v;
return j;
}
void Add_Arcs(Graph G,char Vex[MAX_VERTEX_NUM],int x){
FILE *fp;
int v,w,info,m,n,sum;
char s1,s2;
fp = fopen("Edge_new.txt","r+");
if(fp == NULL){
printf("该文件不存在");
exit(-1);
}
printf("请输入你所需要加入的边的条数:\n");
scanf("%d",&w);
getchar();
fscanf(fp,"%d",&v);
fgetc(fp);
fseek(fp,0,SEEK_END);
for(int i=0;i<w;i++){
cout<<"请输入你所要加入的第"<<v+i+1<<"条边的信息(边两端的顶点以及边的权值)"<<endl;
cin>>s1>>s2>>info;
getchar();
m = See_about_Vex(Vex,s1,x);
n = See_about_Vex(Vex,s2,x);
while(m<0||n<0){
cout<<"您所要插入的边的两端顶点不存在,请重新输入\n";
cin>>s1>>s2>>info; getchar();
m = See_about_Vex(Vex,s1,x);
n = See_about_Vex(Vex,s2,x);
}
while(G.arcs[m][n].adj==1){
cout<<"该边两边已经有端点,请重新输入\n";
cin>>s1>>s2>>info; getchar();
m = See_about_Vex(Vex,s1,x);
n = See_about_Vex(Vex,s2,x);
}
fprintf(fp,"\n%c%c %d",s1,s2,info);
}
sum = v + w;
rewind(fp);
fprintf(fp, "%d",sum);//将总边数写入到文件Edge的规定地点
fclose(fp);
}
int DeleteVex(char Vex[MAX_VERTEX_NUM]){
int i,n,m;
FILE *fp,*Fp;
char v,s,str[MAX_INFO];
fp = fopen("Vex_new.txt","r+");
if(fp == NULL){
cout<<"该文件不存在";
exit(-1);
}
fscanf(fp,"%d",&n);//将文件Vex中的顶点数存储到n中
getc(fp);
printf("请输入所要删除的顶点:\n");
scanf("%c", &s);//将所删除的顶点输入到s中
getchar();
m = See_about_Vex(Vex, s, n);//与当前顶点序列匹配定位,顶点序号存储在m中,如果新的边的顶点不在顶点序列中,则值为-1
while (m < 0)//如果值小于0,则重新输入
{
printf("当前无所要删除的顶点,请重新输入:");
scanf("%c", &s);
getchar();
m = See_about_Vex(Vex, s, n);
}
Fp = fopen("linshi.txt", "w");//创建一个临时只写文件
fprintf(Fp, "%d", --n);//将新的顶点数写入到临时文件中
for ( i = 0; i < n + 1 ; i++)//按格式将除要删除的顶点所在的行之外的其他所有信息写入到临时文件中
{
if (i==m)//如果是顶点所在行,则跳过
{
fscanf(fp, "%c%*c", &v);
fscanf(fp, "%s", &str);
fgetc(fp);
}
else//否则将整行写入到临时文件中
{
fscanf(fp, "%c%*c", &v);
fscanf(fp, "%s", &str);
fgetc(fp);
fprintf(Fp, "\n%c %s", v, str);
}
}
fclose(fp);
fclose(Fp);
remove("Vex_new.txt");//移除原文件Vex
rename("linshi.txt", "Vex_new.txt");//将临时文件重命名为Vex
return s;//返回删除的顶点
}
void DeleteArc(char c)
{
FILE *fp, *Fp;
int v, w, i, info;
char s1, s2;
fp = fopen("Edge_new.txt", "r");//以只读方式打开文件Edge
Fp = fopen("linshi.txt", "w");//创建临时只写文件
if (fp == NULL)
{
printf("该文件不存在");
exit(-1);
}
fprintf(Fp, "\n");//空出临时文件中的第一行,从第二行开始写入
fscanf(fp, "%d", &v);//将文件Edge中的边数存到v中
fgetc(fp);
w = v;//将v的边数存到w中
for (i = 0; i < v; i++)
{
fscanf(fp, "%c%c%*c", &s1, &s2);//将文件Edge中的当前边存储到s1、s2中
if (s1 == c || s2 == c)//如果s1或者s2有一个是已经删除的顶点,则跳过该行并边数w - 1
{
fscanf(fp, "%d", &info);
fgetc(fp);
w--;
}
else//否则将整行写入到临时文件中
{
fscanf(fp, "%d", &info);
fgetc(fp);
fprintf(Fp, "\n%c%c %d", s1, s2, info);
}
}
rewind(Fp);//临时文件指针返回文件首部
fprintf(Fp, "%d", w);//将新的边数写入到临时文件中
fclose(fp);
fclose(Fp);
remove("Edge_new.txt");//移除原文件
rename("linshi.txt", "Edge_new.txt");//重命名临时文件
printf("已删除与该顶点相关的边!");
}
void Modify(Graph G){//修改图标信息
int i;
int n = G.vexnum;
int nn = G.vexnum;
char c;
for(i=0;i<G.vexnum;i++)
{
Vex[i] = G.S[i].name;
}
cout<<"接下来要进行的操作有:\n"<<"1.添加顶点\n"<<"2.添加边\n"<<"3.删除顶点\n"<<"4.删除边\n";
getchar();
n=Add_Vex(G,Vex,G.vexnum);
Add_Arcs(G,Vex,n);
nn=DeleteVex(Vex);
DeleteArc(nn);
}
int main(){
Graph G;
printf("导航助手小天为您服务(*^_^*)\n");
CreateGraph(&G);
Search_infor(G);
DFS(G);
Dijkstra(G);
Prim(G);
Modify(G);
CreateGraph(&G);
cout<<endl<<"点任意键退出...\n";
}
实验心得:
本次实验是考察我们对于无向连通图的使用,其中包括邻接矩阵中判断两点是否相邻,是否边有权值,对于顶点添加相关信息,以及根据顶点查询其相关信息,还考察了DFS的变相实现,计算两点最短距离的迪杰斯特拉算法,生成最小生成树的Prim算法,到最后的添加和删除顶点和边,通过这次实验我掌握了文件输入输出的方法以及对DFS算法加深了理解,将迪杰斯特拉算法和Prim算法实现,也加强了自己对于代码问题的测试能力.