【題目】圖的操作和應用之景區信息管理系統
現有一個景區,景區裏面有若干個景點,景點之間滿足以下條件:
(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算法實現,也加強了自己對於代碼問題的測試能力.