设计一个控制台程序,使用图数据结构和算法,模拟城市公交系统,程序中保存了城市的公交线路和公交站点信息。开始运行时,输出菜单,供用户选择,具体实现的功能如下:
1、创建公交线路图
2、查询公交线路和站点信息
(1)查询公交线路
(2)查询站点信息
3、查询两站点之间的路线,找到至多换乘1次的路线,并输出结果。
4、附加文件存储功能,即站点消息、公交线路信息等数据不得写入程序中,要求保存在文件中。
(1)设计公交线路所需的存储结构,将文件中的数据读入内存。
(2)提供用户操作的菜单和界面实现添加、删除、修改公交、站点、线路信息。
(3)将修改后的信息保存回文件。
首先用多个文件存储数据保存站点信息将文件中的数据读入内存,建立图的存储结构。例如:在文件station.txt中保存站点信息:
在文件buses.txt中保存公交信息数组(格式为:公交编号 公交车号 起始站编号 终点站编号):
在文件名routes.txt中保存线路信息,(格式为:线路编号 站点编号 站点编号 距离):
当保存图结构时,既要保存顶点信息,也要保存边。图用数组来存储。用一维数组来保存顶点的集合,使用二维数组来保存边的集合。
公交路线图可以看作是一个带权的有向图,使用邻接表来保存,所有站点即为图的顶点;当两个站点之间设有公交时,表示两个顶点相连,为一条边;两个站点之间的距离,即为边的权值。
在model.h文件中定义结构体:
1.定义结构体Bus代表一个公交车线路:
typedef struct Bus {
char Name[40]; //公交名
int Start; //起点
int End; //终点
}Bus;
2.定义结构体Station代表一个站点:
typedef struct Station {
char StationName[40]; //站点名
struct Route* Routes; //从该站点出发的所有下行路线的链域
}Station;
3.定义结构体Route代表公交线路中的一个路段(邻接表结点):
typedef struct Route {
int IndexOfStation; //指向的站点索引号
int IndexOfBus; //公交索引号
int Distance; //两站之间的公路距离
struct Route* Next; //起始站点相同的,下一条下行路线
}Route;
4.定义结构体BusMap存储整个公交地图信息:
typedef struct BusMap {
Bus* Buses; //公交线路数组
Station* Stations; //站点数组
int Station_Num; //站点数
int Bus_Num; //公交线路数
int Route_Num;
}BusMap;
首先是对问题功能的的划分:
为了存取方便,我们将线路信息,公交信息,站点信息等数据存入txt文件中,再利用数据存取将文本文件中的数据导入程序中。其中要求数据都储存在txt.文件中,使用一下语句实现文件的读写:
ifstream infile;
infile.open("_Database\\stations.txt");
if (!infile.is_open()) {
cout << "stations.txt文件打开失败" << endl;
exit(0);
}
while (!infile.eof()) {
infile.getline(str1, 100);
if (strcmp(str1, "") == 0)break;
split_stations(str1, index, str2);
AddStation(g_sMap, str2);
}
infile.close();
另外,设置文件写入方式为覆盖写入以保存修改信息:
ofstream outfile; //打开文件,设置写入方式为覆盖写入
outfile.open("_Database\\stations.txt", ios::ate);
if (!outfile) {
cout << "stations.txt文件打开失败!" << endl;
exit(0);
}
for (int i = 0; i < g_sMap.Station_Num; i++)
outfile << i << " " << g_sMap.Stations[i].StationName << "\n";
outfile.close();
经过对题目问题的深入分析,首先发现函数之间的调用较为复杂,使用流程图对函数之间的关系作出一个简单的梳理。这里我使用了多文件编程便于划分各个模块的功能,首先创建main.cpp文件作为程序的入口;menu.cpp用于声明和实现功能菜单;map.cpp用于声明和实现站点图的相关功能。其次是数据结构设计,公交线路图可以看作是一个带权的有向图,使用邻接表来保存,所有站点即为图的顶点,当两个站点之间有公交线路时,表示两个顶点相连,为一条边,两个站点之间的距离为边的权值。在实际的调试过程中,并没有涉及到复杂的算法,只是在寻找所有站点信息时,使用函数Stretch_Bus进行排序;另外,在对公交线路图的初始化的时候,新增函数split来添加站点、公交、线路信息。
1.源代码:
void Initial() {
char str1[100], str2[100];
int index;
g_sMap.Bus_Num = 0;
g_sMap.Station_Num = 0;
g_sMap.Route_Num = 0;
g_sMap.Buses = NULL;
g_sMap.Stations = NULL;
cout << "调用txt文件初始化车站图..." << endl;
ifstream infile;
infile.open("_Database\\stations.txt");
if (!infile.is_open()) {
cout << "stations.txt文件打开失败" << endl;
exit(0);
}
while (!infile.eof()) {
infile.getline(str1, 100);
if (strcmp(str1, "") == 0)break;
split_stations(str1, index, str2);
AddStation(g_sMap, str2);
}
infile.close();
cout << "车站图初始化完毕!" << endl;
}
2.运行结果:
程序根据文件中的站点信息和线路信息创建公交线路图,实现线路的初始化,并且打印菜单显示不同的功能。
1.源代码:
void SearchOfBus(BusMap& g_sMap, char* Bus_Name) {
int IndexOfBus = GetBusIndex(g_sMap, Bus_Name);
int IndexOfStation1 = g_sMap.Buses[IndexOfBus].Start;
cout << g_sMap.Stations[IndexOfStation1].StationName;
Route* pRoute = g_sMap.Stations[IndexOfStation1].Routes;
while (pRoute != NULL) {
if (pRoute->IndexOfBus == IndexOfBus) {
cout << "--->" << g_sMap.Stations[pRoute->IndexOfStation].StationName;
cnt++;
length += pRoute->Distance;
pRoute = g_sMap.Stations[pRoute->IndexOfStation].Routes;
continue;
}
pRoute = pRoute->Next;
}
}
2.运行结果:
输入公交线路编号,系统通过公交编号查找到该路线途经的所有站点并输出
1.源代码:
int FindRoute(BusMap& g_sMap, bool* Visited, int IndexOfStation1, int IndexOfStation2, int* nStation, int* nBus, int Index, int& length) {
//寻路递归子函数,若寻到则输出路径
//试图从IndexOfStation1出发寻找路径,则将该结点的访问值定为已访问
Visited[IndexOfStation1] = true;
Route* pRoute = g_sMap.Stations[IndexOfStation1].Routes;
while (pRoute != NULL) {
//若该点出发的某一路径对应的目标结点未曾走过,则试触
if (Visited[pRoute->IndexOfStation] == false) {
nBus[Index - 1] = pRoute->IndexOfBus;//将确定走入的结点压栈,更新路径
nStation[Index++] = pRoute->IndexOfStation;
length += pRoute->Distance;
//要走入的结点是终点站
if (pRoute->IndexOfStation == IndexOfStation2) //找到可达的路径输出
else //要走入的结点不是终点站,则以走入的结点为起始点重新搜索
FindRoute(g_sMap, Visited, pRoute->IndexOfStation, IndexOfStation2, nStation, nBus, Index, length);
length -= pRoute->Distance;//将尝试过的到达站退栈
Index--;
}
pRoute = pRoute->Next;
}
//将出发站重新定为未访问状态
Visited[IndexOfStation1] = false;
return 0;
}
2.运行结果:
输入站点名,系统通过站点名查找到所有经过该站点的公交线路并输出。
1.源代码:
int SearchOfLIMITEDRoute(BusMap& g_sMap, char* Station1, char* Station2) {
//申请存储访问表的布尔空间,并初始化
bool* Visited = (bool*)malloc(sizeof(bool) * g_sMap.Station_Num);
ClearVisited(Visited, g_sMap.Station_Num);
//申请存储经过站点的数组空间,并将首结点定为起始站
int* nStation = (int*)malloc(sizeof(int) * g_sMap.Station_Num);
nStation[0] = IndexOfStation1;
//申请存储换乘车辆的数组空间
int* nBus = (int*)malloc(sizeof(int) * g_sMap.Bus_Num);
int length = 0;
Length = LENGTH_MAX; Times = 0; //初始化全局变量Length,Times
FindRoute(g_sMap, Visited, IndexOfStation1, IndexOfStation2, nStation, nBus, 1, length); //开始递归搜索
if (Length < LENGTH_MAX) //递归搜索结束,输出最短路线
free(Visited);
free(nStation);
return 0;
}
2.运行结果:
用户输入要查询的起点和终点,程序先判断两站点之间是否有一条路径(即两个站点之间是否连通)。若两个站点之间有路线:则找到所有最多换乘1次的路线,然后依次输出。
(1)提示共找到几条路线:从|起点站名|到|终点站名|共找到N条路线。
(2)循环依次输出每条路线,有路线编号和站点与公交信息。依次输出路线中经过的每一站,并在站点与站点之间输出两站点之间所坐的公交车名。
(3)输出两站点之间的最短路径,以及其换乘次数和路径长度。
(4)若两个站点之间没有可以找到的路线,则提示用户“两站之间没有公交线路!”。
1.源代码:
void SearchRoute_LessThanTwoBus(BusMap& g_sMap, char* Station_Name1, char* Station_Name2) {
if (IndexOfStation1 != IndexOfStation2) {
//理论上可找到至多不换乘的公交线路
Route_Array[0] = g_sMap.Stations[IndexOfStation1].Routes;
while (Route_Array[0] != NULL) { //假设pR[cnt]->Index就是直达公交
if (Route_Array[index]->IndexOfStation == IndexOfStation2) //找到到达站
//寻找下一条直达公交,找到公交的下一条路线,并赋值到R_A[++index];
Route* tpRoute = g_sMap.Stations[Route_Array[index]->IndexOfStation].Routes;
while (tpRoute != NULL) {
if (tpRoute->IndexOfBus == Route_Array[0]->IndexOfBus)break;
tpRoute = tpRoute->Next;}
Route_Array[0] = g_sMap.Stations[IndexOfStation1].Routes;
//找到至多换乘一次的公交线路,在选乘的公交中屏蔽可不换乘到达的公交线路
while (Route_Array[0] != NULL) {
//将路线推至最低端,并逆推换乘一次的公交线路
//从Route_Array[index]开始搜索换乘一次到达的线路
while (index >= 0) {
//寻找换乘一次的后一条公交,找不到则令index--,直至index < 0
int HCindex = index + 1;
Route_Array[index + 1] = g_sMap.Stations[Route_Array[index]->IndexOfStation].Routes;
//寻找与之前乘坐公交不同的线路
if (Route_Array[index + 1]->IndexOfBus == Route_Array[index]->IndexOfBus)
Route_Array[index + 1] = Route_Array[index + 1]->Next;
while (Route_Array[HCindex] != NULL) { //找到到达站
Route* tpRoute = g_sMap.Stations[Route_Array[HCindex]->IndexOfStation].Routes;
while (tpRoute != NULL) {
if (tpRoute->IndexOfBus == Route_Array[HCindex]->IndexOfBus)break;
tpRoute = tpRoute->Next;
}
2.运行结果:
系统根据输入的起点和终点,找到至多换乘一次的路线:
(1)首先输出不换乘的路线,若不存在,则提示“找不到不换乘可到达的路线!”。
(2)在寻找换乘一次的路线,并输出所有可能的路线,若不存在,则提示“找不到换乘一次可到达的路线!”
(1)源代码:
void CoutStation(BusMap& g_sMap) {
Route* pRoute;
for (int i = 0; i < g_sMap.Station_Num; i++) {
cout << g_sMap.Stations[i].StationName << endl;
cout << "经过公交有:";
pRoute = g_sMap.Stations[i].Routes;
while (pRoute != NULL) {
cout << "\t" << g_sMap.Buses[pRoute->IndexOfBus].Name;
pRoute = pRoute->Next;
}
cout << endl;
}
}
(1)源代码:
int AddStation(BusMap& g_sMap, char* name) {
//试图添加车站
int i=GetStationIndex(g_sMap, name);
if (i >= 0) {
cout << "该站点已经存在,无需添加!" << endl;
return i;
}
else {
//确定添加车站
g_sMap.Stations = (Station*)realloc(g_sMap.Stations, sizeof(Station) * (g_sMap.Station_Num + 1));
//初始化车站结点
Station* pStation = &g_sMap.Stations[g_sMap.Station_Num];
strcpy(pStation->StationName, name);
pStation->Routes = NULL; //尚不知晓从此站点出发的第一个路线
g_sMap.Station_Num++;
//返回新添加的车站下标
return g_sMap.Station_Num - 1;
}
}
(1)源程序:
case '2':
cout << "请输入要更改的站点名:";
cin >> str1;
temp1 = GetStationIndex(g_sMap, str1);
if (temp1 < 0)
cout << "目标站点不存在" << endl;
else {
cout << "请输入要替换的站点名:";
cin >> str2;
for (temp2 = 0; temp2 < g_sMap.Station_Num; temp2++)
if (strcmp(str2, g_sMap.Stations[temp2].StationName) == 0)
break;
if (temp2 >= g_sMap.Station_Num) {
strcpy(g_sMap.Stations[temp1].StationName, str2);
cout << "站点名" << "\"" << str1 << "\"" << "已成功更改为" << "\"" << str2 << "\"" << endl;
}
else
cout << "输入的新站点名不可与已有站点名相同" << endl;
}
break;
(1)源程序:
int DelStation(BusMap& g_sMap, char* Station_Name) {
//试图删除车站
int Station_Index;
for (Station_Index = 0; Station_Index < g_sMap.Station_Num; Station_Index++)
if (strcmp(g_sMap.Stations[Station_Index].StationName, Station_Name) == 0)
break;
if (Station_; i < DelBus_Num; i++)
DelBusIndex >= g_sMap.Station_Num)return -1;
//找到所有相关车辆信息
int Bus_Array[Max_Bus_Num], DelBus_Num;
All_Bus_gothrough_THE_Station(g_sMap, Bus_Array, DelBus_Num, Station_Index);
//删除线路、车辆
for (int i = 0(g_sMap, g_sMap.Buses[Bus_Array[i]].Name);
//删除车站
del_the_station(g_sMap, Station_Index);
DeleteNoUesStation(g_sMap);
return 0;
}
1.源代码
int DelStation(BusMap& g_sMap, char* Station_Name) {
//试图删除车站
int Station_Index;
for (Station_Index = 0; Station_Index < g_sMap.Station_Num; Station_Index++)
if (strcmp(g_sMap.Stations[Station_Index].StationName, Station_Name) == 0)
break;
if (Station_Index >= g_sMap.Station_Num)return -1;
//找到所有相关车辆信息
int Bus_Array[Max_Bus_Num], DelBus_Num;
All_Bus_gothrough_THE_Station(g_sMap, Bus_Array, DelBus_Num, Station_Index);
//删除线路、车辆
for (int i = 0; i < DelBus_Num; i++)
DelBus(g_sMap, g_sMap.Buses[Bus_Array[i]].Name);
//删除车站
del_the_station(g_sMap, Station_Index);
DeleteNoUesStation(g_sMap);
return 0;
}
2.运行结果:
对应实现显示所有车辆(路线),添加、修改、删除车辆(路线)的功能。
首先,复盘整个设计过程:1. 弄清楚程序所需要的实现的基本功能,设计模拟城市公交系统,也就是在文件中存储并调用公交站点信息,创建公交线路图,查询公交线路和站点信息,以及要求对站点以及公交线路词的修改、删除等功能。2. 其次是要设计针对特定功能的类,在本次设计中,在map.cpp文件中写入调用文件信息,添加、查找、删除公交线路信息的功能函数,model.h中定义站点信息和线路信息数据,另外在split.cpp中写入函数来实现从txt文件中修改公交线路信息,并且将修改后的信息保存回文件,在menu.cpp控制用户的操作显示界面。划分功能,保证每个类的功能清晰、简洁,在每个类内部实现的功能彼此联系。3. 对于程序具体实现,涉及到的算法问题其实并不复杂,主要是建立邻接表来储存线路图,实现对于图的顶点以及边的查找、删除、添加的工作。4. 程序的调试,针对测试出的问题不断进行改正,得到最终的代码,满足控制台的所有要求。在完成设计和调试后,开始着手实验报告,在不断地修改和改进中,对于整个题目有了更加深入的理解,最后也完成了所有的任务要求。
通过本次数据结构的课程设计,一方面锻炼了分析问题、类设计的能力,另一方面对于图的存储结构有了更加深刻的认识。本次设计基本实现模拟城市公交系统的功能,但仍然还有许多改进的余地。比如程序的简化,优化界面设置,投入到实际应用中等等这些构想尚未实现。总体起来,是受益良多的,体会到了数据结构在实际开发应用中的重要意义,也进一步体会到了C++作为一门程序设计语言在较大项目管理中的优势,清晰的结构增减代码的易读性,也非常有助于提高系统的可拓展性,特别是本次的设计任务是有关生活实际的问题,“公交系统”大家在出行过程中都会接触,可是在课设中才真正了解它的工作原理,原来并不是那么的复杂,让我们在生活中运用所学专业知识从而深刻地体会到了书本知识具体化。
[1] 数据结构(C语言版)/严蔚敏,吴伟民编著.—北京:清华大学出版社,2007
[2] C++语言程序设计/郑莉,董渊,何江舟编著.—4版.—北京:清华大学出版社,2010.7(2019.11重印)
[3] 数据结构实践教程(C语言版)/袁嵩主编.—武汉:华中科技大学出版社,2019.10