一、需求与规格说明
本次实验涉及实际问题,实现公交线路系统的增删改查功能,建立公交线路网,用户可以在威海市范围内实用。
本次实验要求分为:
1.实现根据给定的数据公交数据建立图的存储结构。
2.实现公交站点的增删改。
3.实现公交线路的增删改。
4.实现公交路线图的显示,在我的程序中,由于水平有限,在显示时仅输出邻接矩阵,没有以图的方式显示出来。
5.实现用户给定两个站点可以实现如果有直达线路,可以输出直达线路,如果没有直达线路,则输出最优的换乘线路。
二、设计
2.1设计思想
1.存储结构
其实本题用邻接矩阵来存储该无向网效果并不理想,但是由于使用和建立都比较简单,因此用邻接矩阵存储权值,即公交线路号。用一个一维的字符串数组来存储定点向量。其中在定点数组中各个定点的位置要和邻接矩阵中个定点的位置对应,方便于后续操作。
2.读入数据,建立无向网
(1)由于程序中涉及字符串的操作,明显用string类型操作较为方便,但是在读文件时若用C的文件操作函数会涉及到类型转化的问题。开始时并没有意识到这个问题,在调试时才解决。
(2)首先建立定点数组的存储结构,定点数组需要逐个读取字符串,另外,当遇到表示公交线路的公交线路号时要跳过。建立好定点数组的存储结构后,编写一个函数用于定位某定点在该数组中的下标,方便后续操作。
(3)然后建立邻接矩阵的存储结构,利用定点定位函数,再次度文件,遇到公交线路号时要将其保存,存入后续站点之间的矩阵元素中去,直到遇到另外一个数值时将先前保存的数据覆盖,表示现在读的是另外一条公交线路。在涉及一个元素需要保存多条公交线路的时候,注意一定要存储到数组指定的位置,不能将先前的数据覆盖掉。依次读取文件的信息,填充整个公交线路的邻接矩阵。
3.公交站点的增删改
(1)增加:在增加时应当在定点的数组中增加该定点信息,同时在邻接矩阵中添加定点信息,同时对相关信息初始化。
(2)删除:在删除时同样要在定点数组中删除该定点信息,但是在邻接矩阵中为了操作方便,保留该定点信息,但是将该定点的邻接矩阵中关于边的部分还原初值,这样避免了移动很多元素,同样可以完成功能。
(3)改动:较为简单,将字符串替换为新的名称即可。
4.公交线路的增删改
(1)增加:增加线路的实际应用可能性较小,需要依次输入站点,如过没有出现该站点则新增,如果出现则添加新的线路号的信息。
(2)删除:删除线路需要将该条线路的所有站点都删除,实际可能性很小。
(3)改动:包括对线路号进行改动,在该条线路上新增站点,删除站点等。
5.站点直达线路的查询
站点直达线路查询时较为方便,由于在同一条线路上面,因此没有使用邻接矩阵的遍历方法。而是建立数组存储每个线路的站点信息,然后根据下标指示的线路号来查询对应线路上的公交站点,如果两个站点都在线路上,则表示可以直达,输出两条线路之间的所有站点,并计算路长。
6.站点之间换乘车的查询
实现效果不佳,利用图的DFS算法实现,深度优先遍历图,记录每个经过的站点,当遍历到要求的站点时遍历结束。
7.主菜单的显示
显示菜单方便用户选择功能,显示功能,实现界面友好。
2.2设计表示
1.数据处理
对文件中的数据进行处理,文件中的数据格式不一,需要将文件中的数据处理成便于程序读取的文本结构。结尾以#号结束。处理后的部分示意如下:
2.无向带权图的存储结构
本程序中无向带权图用邻接矩阵进行存储,矩阵用结构体表示,包含三个元素:
①定点用字符串数组来进行存储
②边用邻接矩阵进行存储,arcs表示表示一个500*500的结构体,该结构体中包含两项元素,权值用数组来记录,由于两站点之间可以有很多条线路相通,因此用一维数组记录。同时定义一个辅助变量adj_loc来存储权值的总数,这是非常必要的,否则在后续增加权值的时候还要遍历该一维数组应该增删的具体位置,这里增加一个变量使得后续操作变得方便。
③存储图的特征信息,定点的数目和边的数目。
//无向带权图的存储结构
typedef struct map
{
string vex[500]; //顶点字符串数组
cell arcs; //邻接矩阵
int vexnum,arcnum; //顶点数量和边的数量
}graph;
graph G;
typedef struct Cell
{
int adj[100]; //存储权值
//记录每个站点所经过的公交线路数,在程序中用作标记量
int adj_loc;
}cell[500][500];
3.无向带权图的建立
(1)辅助函数的编写
有一些功能简单但是调用次数较为频繁的函数应当编写一些辅助函数实现,从而简化程序执行的流程。这里编写的函数有:
①判断顶点是否重复
int vex_repeat(string &point)
如果是重复则返回0,否则返回1。
②判断是否读到数字,如果读到数字返回0,否则返回1.
③确定某个定点在图G中的位置,返回在数组中的下标
(2)建立顶点的存储结构
首先将定点的数组初始化,然后读取文件,读到字符数组里的字符串转化为string 类型的,然后调用函数判断如果不重复则保存,如果遇到数字跳过,遇到字符串进行存储。读取文件后关闭文件即可。函数没有参数,直接对全局变量进行操作即可。实现代码如下:
(3)建立边的存储结构
首先打开文件,逐个对字符串进行读取,如果读取的是数字,则保存到line变量中,在读到下一个数组之前均为这个线路号。注意在读取下一个字符串之前需要将先前的字符串进行保存,然后在矩阵中进行操作,将两个点之间的元素增加该条线路号,同时修改adj_loc的值,便于下次操作。
注意在读到数字时应当将数字由string 类型转化为int 类型存储到line变量中。省略打开文件和初始化矩阵后的实现代码为:
4、输出图建立后的顶点和邻接矩阵并写入文件
(1)输出图G的顶点并写入文件vex.txt中
将G.vex中存储的内容输出并且写入文件即可。
(2)将邻接矩阵写入文件
由于邻接矩阵太过庞大,因此不再屏幕上显示,而是将其写入adj.txt文件中。
5.关于站点的增删操作
(1)新增站点信息
用户输入该站点为记录公交车,然后输入该站点的前面站点和后面站点,就可以将该站点添加到邻接矩阵中去。通过给它的前驱站点和后继站点赋给权值使该站点插入两站之间。
同时需要考虑用户的需求,做好用户界面。
(2)删除现存的站点
首先要用户输入站点的名称,如果没有该站点则输出出错信息。
如果存在该站点,应当在顶点的数组中将该站点清除,同时在邻接矩阵中将与该站点的所有邻接的元素之间的路赋值为空。则表示该站点的所有信息都被删除。
(3)修改站点名称
如果该站点存在则将该站点的名称按照用户要求做修改。
6.公交线路的增删
(1)用户手动增加新的公交路线
用户手动增加路线,需要输入站点名称以及要增加的线路号。
然后在邻接矩阵中操作,将该条线路添加。
(2)调整站点间的线路号
用户输入站点名称查找该两站之间是否存在线路号,如果存在该条线路,则手动添加。代码较多,参看源代码。但操作思路很简单。
7.直达线路的搜索
对公交站点按照线路的不同进行整理,分别存放在二维数组的不同位置,当执行搜索命令后。首先顺序查找,查找两个站点是否均位于一天线路上,如果位于同一条线路上面,则输出该线路上两站之间的所有站点。
站点用 string str[100][100]; 进行存储。
检索该数组,用两个站点的字符串进行匹配。
如果两个标志量都为真,则表示在同一条公交线路上,下面就可以对它进行输出。
如果没有路线则执行下一个函数显示换乘车的路线
8.换乘线路的搜索
(1)这里实用深度优先遍历算法。对图进行深度优先搜索遍历。
(2)需要定义函数 int FirstAdj_vex(int loc) 求某个定点的第一个邻接点,函数int Nextadj_vex(int loc1,int loc2)求某个定点相对与另外一个定点的下一个邻接点。
(3)编写DFS函数对图进行深度优先搜索遍历,直到搜索到需要的终止点位置。在遍历过程中对经过的定点进行记录。
(4)编写void Indirect_path()函数实现换乘车站的查询,调用DFS函数,在此函数中主要做用户界面,同时对某些数值作预处理。
代码较多,参见代码部分。
2.3实现注释
1.存储结构
//邻接矩阵的存储结构
typedef struct Cell
{
int adj[100]; //存储权值
//记录每个站点所经过的公交线路数,在程序中用作标记量
int adj_loc;
}cell[500][500];
//无向带权图的存储结构
typedef struct map
{
string vex[500]; //顶点字符串数组
cell arcs; //邻接矩阵
int vexnum,arcnum; //顶点数量和边的数量
}graph;
graph G;
2.函数注释
int vex_repeat(string &point) //判断顶点是否重复
int is_digit(string &str) //判断是否读到数字,若是字符串则返回1
int Locate_vex(string v) //确定某个顶点 v 在图 G 中的位置
void print_vex() //输出图G的顶点
void print_arcs() //输出图G的邻接矩阵
void Create_vex() //建立顶点的存储结构
void Creat_arcs() //建立邻接矩阵的存储结构
void Creat_graph() //建立图的存储结构
void add_vex() //用户手动录入新的站点
void dele_vex() //删除现存的站点
void modify_vex() //修改站点名称
void add_line() //用户手动增加新的公交线路
int modify_adj() //调整站点间的线路号
bool direct_path()//建立链表结构存储一条线上的顶点完成直达检索
int FirstAdj_vex(int loc)
//求点的第一个邻接点的函数,返回值为相邻顶点的序号
int Nextadj_vex(int loc1,int loc2)
//求loc1相对于loc2的下一个邻接点,返回值为下一个邻接点的下标
void DFS(int v) //深度优先遍历算法
void Indirect_path() //换乘车的线路
int main() //主函数
2.4详细设计表示
1.主函数:
2.void Create_vex()建立定点的存储结构
3.void Creat_arcs()建立邻接矩阵
4.bool direct_path()直达搜索
5、void Indirect_path()换乘搜索
3.用户手册
用户只需要按照系统提示的信息输入即可,注意输入是要准确,否则将会提示出错信息。输入后程序会显示执行结构。
4.调试报告
(1)开始编写程序时函数用传递参数的形式进行调用,后来发现会有一些问题,而且大多数函数的参数都是已经建好的图,因此将图G定义为全局变量。这样做避免了很多不必要的麻烦。
(2)C语言操作函数不能操作C++的对象string,但是程序并不报错,在调试时才发现这个问题,后来在中间步骤用char的数组转化一下就解决了这个问题。
(3)递归函数。在DFS函数的递归过程中当完成想要的步骤时想要跳出递归函数,但是如果用return的话只会返回调用函数的上一层。因此在递归函数中设置标志量,当达到想要的结果时,就将要的结果保护起来,不再执行修改其值的操作。等递归函数执行完成之后再返回,但是这样必须等到递归完成后再返回,内存开销较大。目前还没有找到理想的解决办法。
(4)由于全部站点的信息从文件中读取的时候都是string 类型的,对应公交线路的信息,要转化成int 类型的变量进行存储,开始没有注意到这个问题,在调试时发现并改正。
5.总结
(1)通过这次实验,对图的知识有了更加深层次的掌握,对图的存储结构,各种图的遍历和最短路径的算法有了较为深入的认识。
(2) 经过调试程序,锻炼了自己的调试程序的本领。一些之前没有注意到的细节得到了重视。
(3)意识到了程序应该写的有条理,不同功能的函数应该写在不同的函数中,这样在调试的时候可以清楚的知道是那个函数处理问题。
(4)感受到了数据结构的重要性,能够将看似杂乱无章的数据变得有条理,实现想要的功能。
6.源程序清单和结果
/*
威海市公交路线图
Author by 白辰甲
*/
#include
#include
#include
#include
#include
#include
using namespace std;
//邻接矩阵的存储结构
typedef struct Cell
{
int adj[100]; //存储权值
//记录每个站点所经过的公交线路数,在程序中用作标记量
int adj_loc;
}cell[500][500];
//无向带权图的存储结构
typedef struct map
{
string vex[500]; //顶点字符串数组
cell arcs; //邻接矩阵
int vexnum,arcnum; //顶点数量和边的数量
}graph;
graph G;
//判断顶点是否重复
int vex_repeat(string &point)
{
int i = 0;
for(i = 0;i<500;i++)
{
if(point == G.vex[i])
return 0;
}
return 1;
}
//判断是否读到数字,若是字符串则返回1
int is_digit(string &str)
{
int len = str.size();
for(int i = 0;i < len;i++)
{
if ((str[i] > '0') && (str[i] < '9'))
{
return 0;
}
}
return 1;
}
//确定某个顶点 v 在图 G 中的位置
int Locate_vex(string v)
{
int i;
for(i = 0;i < 500;i++)
{
if(G.vex[i] == v)
return i;
}
return 0;
}
//输出图G的顶点
void print_vex()
{
ofstream f1("vex.txt",ios::out);
if(!f1)
exit(1);
int num;
for(num = 0;num < G.vexnum;num++)
{
cout< f1< } f1.close(); } //输出图G的邻接矩阵 void print_arcs() { FILE *fp; char ch = ' '; if((fp = fopen("arcs.txt","a")) == NULL) { cout<<"open file error!"< exit(1); } int num1,num2,num3; for(num1 = 0;num1 < G.vexnum;num1++) { for(num2 = 0;num2 < G.vexnum;num2++) { for(num3 = 0;num3 < G.arcs[num1][num2].adj_loc;num3++) { cout< fprintf(fp,"%d,",G.arcs[num1][num2].adj[num3]); } cout<<" "; fputc(ch,fp); } cout< fputc('\n',fp); } } //建立顶点的存储结构 void Create_vex() { int sum_vex = 0; for(int k = 0;k < 500;k++) { G.vex[k] = "0"; } //读文件构造顶点向量 FILE *fp; if((fp = fopen("data.txt","r")) == NULL) { cout<<"open file error!"< exit(1); } char Point[100]; string point; //记录站点的名称 while(fscanf(fp,"%s",Point)) { point = Point; if(point == "#") break; for(int y = 0; y < 100; y++) { Point[y] = '0'; //对字符数组重新初始化 } if(is_digit(point)) { if(vex_repeat(point)) { G.vex[sum_vex] = point; sum_vex++; } } } G.vexnum = sum_vex; fclose(fp); } //建立邻接矩阵的存储结构 void Creat_arcs() { int sum_arcs = 0; //记录边的总数 //对邻接矩阵进行初始化 for(int i = 0;i<500;i++) { for(int j = 0;j < 500;j++) { G.arcs[i][j].adj_loc = 0; for(int c = 0;c < 100;c++) { G.arcs[i][j].adj[c] = 0; } } } FILE *fp; if((fp = fopen("data.txt","r+")) == NULL) { cout<<"open file error!"< exit(1); } char point1[100],point2[100]; string Point1,Point2; int loc1,loc2; int line; while(fscanf(fp,"%s",point1)) { Point1 = point1; if(Point1 == "#") break; if(!is_digit(Point1)) //如果是数字则先进行处理 { //将point1中的字符转为数字放入line中 int lent; lent = Point1.size(); if(lent == 1) { line = Point1[0] - '0'; } else { line = (Point1[0] - '0') * 10 + (Point1[1] - '0'); } fscanf(fp,"%s",point2); Point2 = point2; continue; } loc1 = Locate_vex(Point1); loc2 = Locate_vex(Point2); G.arcs[loc1][loc2].adj[G.arcs[loc1][loc2].adj_loc++] = line; G.arcs[loc2][loc1].adj[G.arcs[loc2][loc1].adj_loc++] = line; Point2 = Point1; } fclose(fp); } //建立图的存储结构 void Creat_graph() { Create_vex(); Creat_arcs(); } //用户手动录入新的站点 void add_vex() { string str,str1,str2; int num; int loca1,loca2; cout<<"请您输入需要增加的新的站点:"< cin>>str; cout<<"请您输入该站点属于几路公交车"< cin>>num; cout<<"请您输入该站点的前一站是:"< cin>>str1; cout<<"请您输入该站点的后一站是:"< cin>>str2; G.vex[G.vexnum] = str; //将该站点的信息增加到vex的数组中 loca1 = Locate_vex(str1); loca2 = Locate_vex(str2); //定位str1和str2的位置 //对新增线路增加权值 G.arcs[loca1][G.vexnum].adj[G.arcs[loca1][G.vexnum].adj_loc++] = num; G.arcs[G.vexnum][loca1].adj[G.arcs[G.vexnum][loca1].adj_loc++] = num; G.arcs[loca2][G.vexnum].adj[G.arcs[loca2][G.vexnum].adj_loc++] = num; G.arcs[G.vexnum][loca2].adj[G.arcs[G.vexnum][loca2].adj_loc++] = num; G.vexnum++; //站的数量增加 cout<<"该站点录入成功!"< } //删除现存的站点 void dele_vex() { string str; int locale = -1; cout<<"请您输入要删除的站点名称:"< cin>>str; locale = Locate_vex(str); if(locale == -1) { cout<<"删除现存站点失败!"<<" "<<"该站点不存在!"< exit(1); } G.vex[locale] = "0"; //将该站点名称清空 for(int i = 0;i < G.vexnum;i++) { for(int j = 0;j < 100;j++) { G.arcs[locale][i].adj[j] = 0; G.arcs[i][locale].adj[j] = 0; //将存储站点权值的数组清空 } G.arcs[locale][i].adj_loc = 0; G.arcs[i][locale].adj_loc = 0; //将表示该站点的权值数量的变量清零 } G.vexnum--; cout<<"删除站点成功!"< } //修改站点名称 void modify_vex() { string str; string str1; int loca = -1; cout<<"请输入您要修改的站点名称 : "< cin>>str; loca = Locate_vex(str); if(loca == -1) { cout<<"修改站点失败! "<<"该站点不存在! "< exit(1); } cout<<"请输入修改后的名称:"< cin>>str1; G.vex[loca] = str; cout<<"站点修改成功!"< } //用户手动增加新的公交线路 void add_line() { string st1,st2; int linenum; int locate1,locate2; cout<<"请您输入要在哪两个站点之间增加线路:"< cout<<"请您先输入第一个站点"< cin>>st1; cout<<"请您再输入第二个站点"< cin>>st2; cout<<"请您输入要增加的线路号数:"< cin>>linenum; locate1 = Locate_vex(st1); locate2 = Locate_vex(st2); G.arcs[locate1][locate2].adj[G.arcs[locate2][locate1].adj_loc++] = linenum; G.arcs[locate1][locate2].adj[G.arcs[locate2][locate1].adj_loc++] = linenum; cout<<"该站点录入成功!"< } //调整站点间的线路号 int modify_adj() { string str1,str2; int lint1,lint2; int line1,line2; cout<<"请输入想要修改线路号的站点"< cout<<"请输入第一个站点:"< cin>>str1; cout<<"请输入第二个站点:"< cin>>str2; lint1 = Locate_vex(str1); lint2 = Locate_vex(str2); //找到这两个站点在矩阵中的位置 cout<<"该站点间已经存在的线路号是:"; int flag = 0; //作为标记,检查这两站之间是否有线路相通 for(int i = 0;i < G.arcs[lint1][lint2].adj_loc;i++) //输出两站点间已经存在的线路号 { cout< flag = 1; } if(flag == 0) { cout<<"您输入的两站之间不存在线路,请您手动增加线路"< return 0; } cout< cout<<"请输入想要改动的站点间的线路号"< cin>>line1; cout<<"请输入修改后的线路号"< cin>>line2; for(int j = 0;j < G.arcs[lint1][lint2].adj_loc;j++) { if(G.arcs[lint1][lint2].adj[j] == line1) { G.arcs[lint1][lint2].adj[i] = line2; G.arcs[lint2][lint1].adj[i] = line2; cout<<"站点的线路号修改成功!"< break; } } if(j == G.arcs[lint1][lint2].adj_loc) cout<<"您的输入有误!"< return 1; } //建立链表结构存储一条线上的顶点完成直达检索 bool direct_path() { string str[100][100]; int i = 0; int j = 0; string str1; string str2; //起始站 string str3; //终点站 int loca1,loca2,loca3,loca4,temp; bool flag1,flag2; bool flag = false; ifstream f1; f1.open("data.txt"); f1>>str1; //第一个数字先处理 str[i][j++] = str1; while(f1>>str1) { if(str1 == "#") //如果读到文件结尾 break; if(!is_digit(str1)) { i++; j = 0; } str[i][j++] = str1; } cout<<"-------最优路线选择------"< cout<<"请输入您的起始站:"; cin>>str2; cout<<"请输入您的终点站:"; cin>>str3; //查找直达线路并作记录 for(i = 0;i < 100;i++) { flag1 = false; flag2 = false; for(j = 0;j < 100; j++) { if(str2 == str[i][j]) { loca1 = i; loca2 = j; flag1 = true; } if(str3 == str[i][j]) { loca3 = i; loca4 = j; flag2 = true; } } if((flag1 == true)&&(flag2 == true))//表示直达线路查找成功 { flag = true; cout<<"该两站点之间存在直达线路"< cout<<"该线路号为 "<
cout<<"具体路线为: "; if(loca2 > loca4) //如果后者处于线路的前方则交换数值 { temp = loca2; loca2 = loca4; loca4 = temp; } for(int k = loca2;k <=loca4;k ++) { cout< } cout< cout<<"路长是"< cout< } } if(flag == false) cout<<"该两站点之间没有直达路线! 即将为您显示换乘公交路线!"< return flag; //若falg = false,表示没有直达路线 } //对图进行深度优先遍历搜索完成换乘搜索 //全局变量的定义 bool visit[500]; //标志站点有没有被访问过 int num[500]; //记录已经访问过的站点 int num_loc = 0; //num数组中元素的个数 bool num_flag = false; int end_vex; //求点的第一个邻接点的函数,返回值为相邻顶点的序号 int FirstAdj_vex(int loc) { int adj_vex; int i; for(i = 0;i < G.vexnum;i++) { if(G.arcs[loc][i].adj_loc != 0) { adj_vex = loc; break; } } return adj_vex; } //求loc1相对于loc2的下一个邻接点,返回值为下一个邻接点的下标 int Nextadj_vex(int loc1,int loc2) { int i; for(i = 0;i < G.vexnum;i++) { if((i > loc2)&&(G.arcs[loc1][i].adj_loc != 0)) { return i; } } return -1; } int bb = 0; //深度优先遍历算法 void DFS(int v) { int w; visit[v] = true; if(bb == 0) { num[num_loc++] = v; if(num[num_loc - 1] == end_vex) { num_flag = true; bb = 1; } } for(w = FirstAdj_vex(v);w >= 0;w = Nextadj_vex(v,w)) { if(!visit[w]) DFS(w); } } //换乘车的线路 void Indirect_path() { int i,j,v; int loc1,loc2; string str1,str2; for(j = 0;j < 500;j++) //初始化数组 num[j] = 0; cout<<"换乘公交路线显示:"< cout<<"请输入起始站点: "; cin>>str1; cout<<"请输入终点站: "; cin>>str2; loc1 = Locate_vex(str1); loc2 = Locate_vex(str2); end_vex = loc2; for(i = 0;i < G.vexnum;i++) //访问标志数组的初始化 { visit[i] = false; } v = loc1; DFS(v); if(num_flag == true) { cout<<"前往站点"< for(i = 1;i < num_loc;i++) { cout<<"前往站点"< cout<<" 公交线路号为 "< } cout< } } int main() { int choose; bool choose_flag; Creat_graph(); cout<<"-------欢迎使用威海市公交查询系统-----------"< cout<<"-------制作者:110410413 白辰甲-------------"< cout<<"--------1.显示所有站点信息------------------"< cout<<"--------2.将公交线路矩阵写入文件------------"< cout<<"--------3.新增站点信息----------------------"< cout<<"--------4.删除站点信息----------------------"< cout<<"--------5.修改站点名称----------------------"< cout<<"--------6.手动增加公交路线------------------"< cout<<"--------7.调整站点间的线路------------------"< cout<<"--------8.搜索两站之间的路线----------------"< cout<<"------------请选择--------------------------"< cin>>choose; switch(choose) { case 1:print_vex();break; case 2:print_arcs();break; case 3:add_vex();break; case 4:dele_vex;break; case 5:modify_vex();break; case 6:add_line();break; case 7:modify_adj();break; case 8: { choose_flag = direct_path(); if(choose == false) Indirect_path(); else break; } default:cout<<"您的选择有误!"< } return 0; } 结果截图 1.显示所有站点信息 2.增加站点信息 3.删除站点信息 4.修改站点名称 5.增加公交路线 6.调整两站之间的线路号 7.最优线路选择