目录
一、课程设计内容
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务,该系统能够为省级赛事管理解决以下问题:
(1)能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
(3)能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按队伍编号有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
(4)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
(5)赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
【设计要求】
1)赛事数据要求存入文件(txt或excel)并能读入查询;
2)赛地目的地查询,需提供目的地(建筑物)名称、代号、简介、两地之间路径长度等信息;
3)输入数据形式和范围:赛事相关数据可从键盘输入,或自文件导入。
4)界面要求:交互设计要合理,每个功能可以设计菜单,用户根据提示,完成相关功能的要求。
二、问题分析
问题(1)(2): 能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
分析:能够管理参赛队基本信息,包括增加、删除、修改参赛队伍的信息。显而易见,我们需要定义一个team结构体类型对参赛队基本信息进行储存从而便于管理,在此基础上需要添加包括增加、删除、修改参赛队伍的信息的函数以实现这些功能。同时,我们需要定义一个赛事管理系统的类,并将这些变量与函数写进赛事管理类中,便于实现与调用,同时实现高聚合。为了实现从team.txt中读取信息,需要包含<fstream>头文件进行相关读取操作。对于基于二叉排序树的查找,需要定义节点结构,并写出相关操作如插入节点,删除节点,查找等。
问题(3)(4):能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按队伍编号有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。);为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到N个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)。
分析:为了便于实现按赛事类别查找队伍,我们需要定义一个findTeamsBySchool函数,集中实现这一功能。在解决按队伍编号查找参赛队伍时,我们需要定义unordered_map类型的teamMap作为参赛队伍映射,便于我们进行按队伍编号查找。同样的,在实现按参赛学校名称查找参赛团队时,我们同样定义了一个unordered_map类型的变量schoolMap作为学校映射。需要注意的是,每当我们执行一次添加、删除、修改队伍信息的操作时,不要忘记修改对应schoolMap、teamMap、二叉树排序树里的信息,实现信息同步。在输出参赛团队时需要按赛事类别进行有序输出,就需要我们用某种排序方法对参赛队伍进行排序。在这里,我们选用归并排序作为排序方法,用递归的方式实现。选用归并排序的原因如下:
• 归并排序的效率达到了巅峰:时间复杂度为O (nlogn),这是基于比较的排序算法所能达到的最高境界
• 归并排序是一种稳定的算法(即在排序过程中大小相同的元素能够保持排序前的顺序,3212升序排序结果是1223,排序前后两个2的顺序不变),这一点在某些场景下至关重要。
为省赛现场设计决赛叫号系统,同样需要定义一个相应函数simulateFinalCallingSystem实现此功能。由于按赛事类别分到决赛室,因此有多少个赛事类别,我们就需要设置多少决赛室。在实现决赛系统叫号时,为尽可能模拟现实情况,应实现多个决赛室并发进行叫号,若为简化设计,可以简单的实现顺序叫号:即按决赛室顺序,一个决赛室叫号完全结束才能进行下一个决赛室的叫号。同时,为演示省赛现场各决赛室的参赛队进场情况,需要把叫号过程打印出来。
问题(5):赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
分析:为了充分表示各目的地(建筑物)的信息与距离关系,我们需要重新定义两个数据结构,其中一个用来表示目的地(建筑物)的具体信息,另外一个用来表示边结构,用于表示两点的边关系。还需要定义一个图的类,用来储存图的相关变量与操作,如所有目的地(建筑物)的信息,目的地(建筑物)之间的关系(邻接表或邻接矩阵),导航功能等,便于复用。最后需要将校园地图的目的地先导入到该类中,便于后续操作。
三、概要设计
数据结构设计
参赛队伍结构:首先定义了一个结构体的数据结构Team——用来储存参赛队伍的信息
struct Team {
string team_id; // 参赛队编号
string project_name; // 参赛作品名称
string school; // 参赛学校
string category; // 赛事类别
string participants; // 参赛者
string guide_teacher; // 指导老师
};
在Team结构中,分别用team_id,project_name,school,category,participants,guide_teacher表示参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师。
赛事管理系统结构——之后定义一个赛事管理类,用于管理参赛队伍并对其进行相应操作。
// 定义比赛管理系统类
class CompetitionManagementSystem {
private:
vector<Team> teams; // 参赛队伍列表
unordered_map<string, Team> teamMap; // 参赛队伍映射,以队伍编号为键值
unordered_map<string, vector<Team>> schoolMap; // 学校映射,以学校名称为键值
Node* root = nullptr;//二叉排序树根节点
class CampusMap map;
public:
//打印参赛队伍信息
void print_team(const Team& team){}
// 添加参赛队伍
void addTeam(Team&team){}
// 根据队伍编号查找参赛队伍
Team findTeamByID(const string&teamID){}
//修改参赛队伍信息
void change_team(){}
// 根据学校名称查找参赛队伍
void findTeamsBySchool() {}
};
为实现二叉排序树的查找,需要定义节点结构以构建二叉树。
//定义树节点
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
为了便于表示校园地图中各目的地(建筑物)的信息,目的地之间的关系(边),我们需要定义两个数据结构 Building和Edge以充分表示。
// 建筑物结构体
struct Building {
string name; //建筑物名字
int code; //建筑物编号
string description; //建筑物信息描述
};
// 边结构体
struct Edge {
int destination; // 目的地建筑物的索引
int distance; // 路径长度
};
在Building中name,code,description分别表示建筑的名字,编号和信息描述,Edg中destination,distance分别表示目的地的索引和到目的地的路径长度。
定义一个图的类,用来储存图的相关变量与操作,如所有目的地(建筑物)的信息,目的地(建筑物)之间的关系(邻接表或邻接矩阵),导航功能等,便于复用。
class CampusMap {
private:
std::vector<Building> buildings; // 存储建筑物信息
std::unordered_map<std::string, int> buildingIndices; // 建筑物名称到索引的映射
std::vector<std::vector<Edge>> adjacencyList; // 邻接表表示的图
public:
//构造函数
CampusMap() {}
// 添加建筑物
void addBuilding(const Building& building) {}
// 添加路径
void addPath(const string& source, const string& destination, int distance{}
// 查询建筑物信息
void queryBuilding(const string& buildingName) {}
// 查询路径导航
void queryPath(const string& source, const string& destination) {}
// 递归打印最短路径
void printPath(const std::vector<int>&previous, int destination) {}
};
函数方法与成员变量设计
为了充分表示各比赛队伍,定义了vector容器teams用于存放Team类型的参赛队伍,用unordered_map类型的teamMap作为参赛队伍映射,以队伍编号为键值便于以队伍编号查找队伍信息,用unordered_map类型的变量schoolMap作为学校映射,用学校为键值便于以学校名称查找队伍,同时定义二叉排序树根节点,初始时用空指针进行初始化。再次强调,每当我们执行一次添加、删除、修改队伍信息的操作时,不要忘记修改对应schoolMap、teamMap、二叉树排序树里的信息,实现信息同步。
private:
vector<Team> teams; // 参赛队伍列表
unordered_map<string, Team> teamMap; // 参赛队伍映射,以队伍编号为键值
unordered_map<string, vector<Team>> schoolMap; // 学校映射,以学校名称为键值
Node* root = nullptr;//二叉排序树根节点
题设中要求从.txt文件直接读取赛事信息,所以需要包含 fstream和sstream头文件。
观察.txt文件中队伍信息的格式,我们会发现第一行是各数据项的标题,如参赛队编号,参赛作品名称等。从第二行往后就是各队伍的信息。对其中每一行进行分析,每个队伍信息中的一小项都用#号进行分隔。因此当我们在进行文件读取的时候,可以从第二行开始一行一行读取,对每一行再进行另外的处理——创建Team类型对象team,对每一行内容,用#当作分隔符,按照参赛队编号、参赛作品名称、参赛学校、赛事类别、参赛者、指导老师的顺序分别赋值给team_id,project_name,school,category,participants,guide_teacher.
相关代码如下:
CompetitionManagementSystem cms;
// 从team.txt中读取参赛队伍的基本信息并添加到系统中
ifstream file("team.txt");
if (file.is_open()) {
string line;
getline(file, line);//跳过第一行
while (getline(file, line)) {
Team team;
stringstream ss(line);
string temp;
// 解析并设置team的各项属性
getline(ss, team.team_id, '#');
removeSpaces(team.team_id);
getline(ss, team.project_name, '#');
removeSpaces(team.project_name);
getline(ss, team.school, '#');
removeSpaces(team.school);
getline(ss, team.category, '#');
removeSpaces(team.category);
getline(ss, team.participants, '#');
removeSpaces(team.participants);
getline(ss, team.guide_teacher, '#');
removeSpaces(team.guide_teacher);
cms.addTeam(team);
}
file.close();
}
else {
cout << "无法打开team.txt文件" << endl;
}
为实现赛事管理系统的基本功能,如打印、增加、删除、修改参赛队伍的信息、查找等,需要对赛事系统添加各种成员函数以实现相关功能。
//打印参赛队伍信息
void print_team(const Team& team)
// 添加参赛队伍
void addTeam(const Team& team)
// 根据队伍编号查找参赛队伍
Team findTeamByID(const string& teamID)
//删除参赛队伍
void delete_team()
//修改参赛队伍信息
void change_team()
//模拟决赛叫号系统
void simulateFinalCallingSystem()
在实现查找功能时,要实现基于二叉排序树的查找功能,同样需要定义相关函数进行操作。 其中finMin函数会在remove中用到。在查找成功后需要计算输出查找成功的ASL,为此需要再定义三个函数体,分别用于计算二叉排序树的总结点数,总搜索路径长,和平均搜索路径长(ASL)。
//建立二叉排序树
Node* insertNode(Node* root, int value)
//基于二叉排序树的查找
Node* searchNode(Node* root, int value)
//找到以root为根节点的树最小节点
Node* findMin(Node* root)
//二叉排序树删除节点
Node* remove(Node* root, int key)
// 计算二叉排序树的节点总数
int countNodes(Node* root){}
// 计算二叉排序树的总搜索路径长度
int totalSearchLength(Node* root, int level) {}
// 计算二叉排序树的平均搜索长度
double calculateASL(Node* root) {}
在模拟叫号系统simulateFinalCallingSystem()中,我们定义了一个unordered_map类型的变量finalRooms,该map以赛事类别为键,值为一个队列,用来储存以该键为赛事类别的队伍。一开始各队伍按顺序入队,每叫完一个号时,该队出队。
unordered_map<string,queue<Team>> finalRooms; // 各决赛室队列
在定义的CampusMap类中,我们定义了三个成员变量:buildings,buildingIndices,adjacencyList。其中buildings为一个Building类型的vector,用来储存所有建筑,buildingIndices为一个以建筑名称为键的map,其值为该建筑的索引,实现建筑名称到索引的映射。最后adjacencyList是一个以Edge为类型的容器为元素的容器,即容器套容器,用来表示邻接表。
std::vector<Building> buildings; // 存储建筑物信息
std::unordered_map<std::string, int> buildingIndices; // 建筑物名称到索引的映射
std::vector<std::vector<Edge>> adjacencyList; // 邻接表表示的图
在CampusMap类中,我们定义了以下函数:
public:
//构造函数
CampusMap() {}
// 添加建筑物
void addBuilding(const Building& building) {}
// 添加路径
void addPath(const string& source, const string& destination, int distance{}
// 查询建筑物信息
void queryBuilding(const string& buildingName) {}
// 查询路径导航
void queryPath(const string& source, const string& destination) {}
// 递归打印最短路径
void printPath(const std::vector<int>&previous, int destination) {}
};
其中CampusMap()为构造函数,用来将校园地图导入该类中,并进行相关初始化操作。addBuilding()为添加建筑物函数,在构造函数中通过此函数将建筑物添加进CampusMap类中。addPath()可以添加建筑物之间的路径,用来更新邻接表。
在本项目中,建筑物及其边关系如下图:
queryBuilding()用来查询建筑物的详细信息,即输出建筑物的名字、索引(代号)、介绍。queryPath()用来查询到达目的地的最短路径,并输出行走路线。
在queryPath(),对于最短路径的查找问题,根据数据结构课程所学知识,有多种经典算法可以解决最短路径问题,包括 Dijkstra算法,Floyd-Warshell 算法,Bellman-Ford 算法和深度优先遍历等。其中 Dijkstra 算法求的是单源最短路径:即从一个结点出发到其它所有结点的最短路径,
算法的时间复杂度为 O(n 2 ),但题目要求任意两个结点的最短路径,所以还是要在外层增加一个循环,以求得多源最短路径。而 Floyd 算法求的是多源最短路径:即从任意结点出发到其它所有结点的最短路径,算法的时间复杂度为 O(n 3 ),它可以一次性求得所有结点间的最短路径,且算法思想简单,便于理解。所以我们选用 Floyd 算法来求解最短路径。 为了实现这一算法,我们定义两个容器distance,previous分别用于储存路径长度和存储最短路径上的前驱节点。
std::vector<int> distance(buildings.size(), INT_MAX); // 存储路径长度
std::vector<int> previous(buildings.size(), -1); // 存储最短路径上的前驱节点
系统页面设置
在完成系统功能的大体结构设计后,最后对系统页面界面进行设计。要求系统界面能包含整个系统的完整功能,并简洁高效,便于操作。基于以上要求,对界面设计如下:
界面设计相关代码如下:
void display_menu() {
cout << "========== 计算机设计大赛赛事管理系统 ==========" << endl;
cout << "1. 增加队伍" << endl;
cout << "2. 删除队伍" << endl;
cout << "3. 更改队伍信息" << endl;
cout << "4. 按参赛队伍ID查找" << endl;
cout << "5. 按参赛学校名称查找" << endl;
cout << "6. 决赛室叫号" << endl;
cout << "7. 校园导航" << endl;
cout << "0. 退出系统" << endl;
cout << "================================================" << endl;
}
四、总结
本次课设是为某一赛事设计一个参赛系统,包括参赛队的管理和各项功能实现。通过本次课程设计,我们巩固了数据结构相关知识,并学习到很多额外知识:
-
数据结构的应用:课设涉及了多种数据结构的应用,如二叉排序树、排序算法等。学生可以深入理解和掌握这些数据结构的原理和实现方式,并学会将其应用于实际问题的解决中。
-
数据管理和操作:课设要求学生设计参赛队伍的基本信息管理系统,包括增加、删除和修改参赛队伍信息等操作。通过实践,学生可以学会如何有效地组织和管理数据,并实现相应的操作功能。
-
查找算法和效率分析:通过基于二叉排序树的参赛队伍查找功能,学生可以学习查找算法的实现原理和应用场景,并了解如何通过效率分析来评估算法的性能,以便进行算法选择和优化。
-
排序算法的比较与选择:课设要求学生为参赛团队查询功能选择排序算法,并说明选择的原因。学生需要了解不同排序算法的特点、时间复杂度等,并能根据实际需求选择适合的算法。
-
模拟系统的设计和实现:通过决赛叫号系统的设计和模拟,学生可以学会如何设计和实现一个模拟系统,模拟真实场景中的操作和流程,并保证系统的准确性和可视化效果。
-
地图导航算法的实现:通过校园导游程序的设计,学生可以学习如何使用图的算法来实现路径导航功能,包括最短路径算法等。这将培养学生在实际问题中应用图算法的能力。
附录
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <queue>
#include <algorithm>
#define INF INT_MAX // 无穷大
using namespace std;
// 定义参赛队伍结构
struct Team {
string team_id; // 参赛队编号
string project_name; // 参赛作品名称
string school; // 参赛学校
string category; // 赛事类别
string participants; // 参赛者
string guide_teacher; // 指导老师
};
//定义树节点
struct Node {
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
// 建筑物结构体
struct Building {
string name; //建筑物名字
int code; //建筑物编号
string description; //建筑物信息描述
};
// 边结构体
struct Edge {
int destination; // 目的地建筑物的索引
int distance; // 路径长度
};
//建立二叉排序树
Node* insertNode(Node* root, int value) {
if (root == nullptr) {
return new Node(value);
}
if (value < root->data) {
root->left = insertNode(root->left, value);
}
else if (value > root->data) {
root->right = insertNode(root->right, value);
}
return root;
}
//基于二叉排序树的查找
Node* searchNode(Node* root, int value) {
if (root == nullptr || root->data == value) {
return root;
}
if (value < root->data) {
return searchNode(root->left, value);
}
else {
return searchNode(root->right, value);
}
}
//找到以root为根节点的树最小节点
Node* findMin(Node* root) {
if (root == nullptr) {
return nullptr;
}
while (root->left != nullptr) {
root = root->left;
}
return root;
}
//二叉排序树删除节点
Node* remove(Node* root, int key) {
if (root == nullptr) {
return nullptr;
}
if (key < root->data) {
root->left = remove(root->left, key);
}
else if (key > root->data) {
root->right = remove(root->right, key);
}
else {
// 找到要删除的节点
if (root->left == nullptr) {
Node* temp = root->right;
delete root;
return temp;
}
else if (root->right == nullptr) {
Node* temp = root->left;
delete root;
return temp;
}
else {
// 当前节点有两个子节点
Node* minRight = findMin(root->right);
root->data = minRight->data;
root->right = remove(root->right, minRight->data);
}
}
return root;
}
// 计算二叉排序树的节点总数
int countNodes(Node* root) {
if (root == nullptr) {
return 0;
}
return 1 + countNodes(root->left) + countNodes(root->right);
}
// 计算二叉排序树的总搜索路径长度
int totalSearchLength(Node* root, int level) {
if (root == nullptr) {
return 0;
}
return level + totalSearchLength(root->left, level + 1) + totalSearchLength(root->right, level + 1);
}
// 计算二叉排序树的平均搜索长度
double calculateASL(Node* root) {
int nodeCount = countNodes(root);
int totalLength = totalSearchLength(root, 1);
if (nodeCount == 0) {
return 0.0;
}
return double(totalLength) / nodeCount;
}
//去除字符串的空格
void removeSpaces(string& input) {
string result;
for (char c : input) {
if (c != '\t') {
result += c;
}
}
input = result;
}
// 归并排序,按照编号对团队进行排序
void mergeSort(std::vector<Team>& teams) {
if (teams.size() <= 1) {
return;
}
int mid = teams.size() / 2;
std::vector<Team> left(teams.begin(), teams.begin() + mid);
std::vector<Team> right(teams.begin() + mid, teams.end());
mergeSort(left);
mergeSort(right);
int i = 0, j = 0, k = 0;
while (i < left.size() && j < right.size()) {
if (left[i].team_id < right[j].team_id){
teams[k++] = left[i++];
}
else {
teams[k++] = right[j++];
}
}
while (i < left.size()) {
teams[k++] = left[i++];
}
while (j < right.size()) {
teams[k++] = right[j++];
}
}
// 图类
class CampusMap {
private:
std::vector<Building> buildings; // 存储建筑物信息
std::unordered_map<std::string, int> buildingIndices; // 建筑物名称到索引的映射
std::vector<std::vector<Edge>> adjacencyList; // 邻接表表示的图
public:
CampusMap() {
Building building[12];
building[0] = { "行政楼",0,"行政研发中心,层高13层,用于日常办公。" };
building[1] = { "海韵湖",1,"位于北门附近,呈鸡爪型,在这里你可以看到天鹅。" };
building[2] = { "图书馆",2,"层高6层,具有智能借还书机器,十分智能。" };
building[3] = { "东苑食堂",3,"层高3层,蚕桑丝绸主题餐厅,环境具有蚕桑丝绸特色,菜品丰富。" };
building[4] = { "东操场",4,"内含足球场和各种锻炼器材,用于师生锻炼放松。" };
building[5] = { "南门",5,"校门进出口,需要扫脸进出。" };
building[6] = { "体育中心",6,"体育运动中心,内有室内篮球馆,足球馆,羽毛球馆,健美操馆等" };
building[7] = { "西操场",7,"内含足球场和各种锻炼器材,用于师生锻炼放松。" };
building[8] = { "经世楼",8,"层高5层,用于日常教学办公,取自校训“笃学明德,经世致用”" };
building[9] = { "文理大楼",9,"层高23层,造型独特,是江科大标志性建筑,用于日常教学和办公。" };
building[10] = { "西苑食堂",10,"层高3层,船海主题餐厅,环境具有船海特色,菜品丰富。" };
building[11] = { "西宿舍区",11,"西区学生住宿区,每间宿舍为四人间,有独立卫浴和阳台。" };
for (int i = 0; i < 12; i++) {
addBuilding(building[i]);
}
addPath("行政楼", "海韵湖", 300);
addPath("行政楼", "文理大楼", 700);
addPath("海韵湖", "行政楼", 300);
addPath("海韵湖", "图书馆", 600);
addPath("图书馆", "海韵湖", 600);
addPath("图书馆", "文理大楼", 400);
addPath("图书馆", "西操场", 550);
addPath("图书馆", "东苑食堂", 100);
addPath("东苑食堂", "图书馆", 100);
addPath("东苑食堂", "体育中心", 550);
addPath("东苑食堂", "东操场", 100);
addPath("东操场", "东苑食堂", 100);
addPath("东操场", "南门", 250);
addPath("南门", "东操场", 250);
addPath("南门", "体育中心", 250);
addPath("南门", "西宿舍区", 700);
addPath("体育中心", "南门", 250);
addPath("体育中心", "东苑食堂", 550);
addPath("体育中心", "西操场", 100);
addPath("体育中心", "西苑食堂", 300);
addPath("西操场", "体育中心", 100);
addPath("西操场", "图书馆", 550);
addPath("西操场", "经世楼", 100);
addPath("西操场", "西苑食堂", 250);
addPath("经世楼", "西操场", 100);
addPath("经世楼", "文理大楼", 100);
addPath("文理大楼", "经世楼", 100);
addPath("文理大楼", "行政楼", 700);
addPath("西苑食堂", "西操场", 250);
addPath("西苑食堂", "体育中心", 250);
addPath("西苑食堂", "西宿舍区", 200);
addPath("西宿舍区", "西苑食堂", 200);
addPath("西宿舍区", "南门", 700);
}
// 添加建筑物
void addBuilding(const Building& building) {
int index = buildings.size();
buildings.push_back(building);
buildingIndices[building.name] = index;
adjacencyList.emplace_back();
}
// 添加路径
void addPath(const std::string& source, const std::string& destination, int distance) {
int sourceIndex = buildingIndices[source];
int destinationIndex = buildingIndices[destination];
adjacencyList[sourceIndex].push_back({ destinationIndex, distance });
adjacencyList[destinationIndex].push_back({ sourceIndex, distance });
}
// 查询建筑物信息
void queryBuilding(const std::string& buildingName) {
if (buildingIndices.count(buildingName) > 0) {
int index = buildingIndices[buildingName];
Building building = buildings[index];
std::cout << "名称: " << building.name << std::endl;
std::cout << "代号: " << building.code << std::endl;
std::cout << "简介: " << building.description << std::endl;
}
else {
std::cout << "未找到该建筑物信息。" << std::endl;
}
}
// 查询路径导航(使用Floyd算法)
void queryPath(const std::string& source, const std::string& destination) {
if (buildingIndices.count(source) > 0 && buildingIndices.count(destination) > 0) {
int sourceIndex = buildingIndices[source];
int destinationIndex = buildingIndices[destination];
int numBuildings = buildings.size();
std::vector<std::vector<int>> distance(numBuildings, std::vector<int>(numBuildings, INT_MAX));
std::vector<std::vector<int>> next(numBuildings, std::vector<int>(numBuildings, -1));
// 初始化距离矩阵和下一个节点矩阵
for (int i = 0; i < numBuildings; ++i) {
distance[i][i] = 0;
for (const Edge& edge : adjacencyList[i]) {
distance[i][edge.destination] = edge.distance;
next[i][edge.destination] = edge.destination;
}
}
// Floyd算法计算最短路径和下一个节点
for (int k = 0; k < numBuildings; ++k) {
for (int i = 0; i < numBuildings; ++i) {
for (int j = 0; j < numBuildings; ++j) {
if (distance[i][k] != INT_MAX && distance[k][j] != INT_MAX && distance[i][k] + distance[k][j] < distance[i][j]) {
distance[i][j] = distance[i][k] + distance[k][j];
next[i][j] = next[i][k];
}
}
}
}
queryBuilding(destination);
// 输出最短路径和长度
if (distance[sourceIndex][destinationIndex] != INT_MAX) {
std::cout << "最短路径长度: " << distance[sourceIndex][destinationIndex] << std::endl;
std::cout << "路径导航: ";
printFloydPath(next, sourceIndex, destinationIndex);
cout << endl;
}
else {
std::cout << "无法找到从 " << source << " 到 " << destination << " 的路径。" << std::endl;
}
}
else {
std::cout << "建筑物信息不完整。" << std::endl;
}
}
// 打印Floyd算法计算的最短路径
void printFloydPath(const std::vector<std::vector<int>>& next, int source, int destination) {
if (source == destination) {
std::cout << buildings[source].name;
}
else if (next[source][destination] == -1) {
std::cout << "无法找到路径。";
}
else {
std::cout << buildings[source].name << " -> ";
printFloydPath(next, next[source][destination], destination);
}
}
};
// 定义比赛管理系统类
class CompetitionManagementSystem {
private:
vector<Team> teams; // 参赛队伍列表
unordered_map<string, Team> teamMap; // 参赛队伍映射,以队伍编号为键值
unordered_map<string, vector<Team>> schoolMap; // 学校映射,以学校名称为键值
Node* root = nullptr;//二叉排序树根节点
public:
class CampusMap map;
public:
//打印参赛队伍信息
void print_team(const Team& team)
{
cout << "队伍编号:" << team.team_id << endl << "作品名称:" << team.project_name << endl << "参赛学校:" << team.school << endl
<< "赛事类别:" << team.category << endl << "参赛者:" << team.participants << endl << "指导老师:" << team.guide_teacher << endl;
cout << endl;
}
// 添加参赛队伍
void addTeam()
{
bool conti = true;
while (conti) {
Team team;
cout << "请输入队伍信息" << endl;
cout << "队伍编号:" << endl;
cin >> team.team_id;
cout << "参赛作品名称:" << endl;
cin >> team.project_name;
cout << "参赛学校:" << endl;
cin >> team.school;
cout << "赛事类别:" << endl;
cin >> team.category;
cout << "参赛者:" << endl;
cin >> team.participants;
cout << "指导老师:" << endl;
cin >> team.guide_teacher;
teams.push_back(team);
teamMap[team.team_id] = team;
this->root = insertNode(root, atoi(team.team_id.c_str()));
schoolMap[team.school].push_back(team);
// 将队伍写入txt文件
std::ofstream file("team.txt", std::ios::app);
if (file.is_open()) {
file << team.team_id << "\t" << '#' << '\t' << team.project_name << "\t" << '#' << '\t' << team.school << "\t" << '#'
<< '\t' << team.category << "\t" << '#' << '\t' << team.participants << "\t" << '#' << '\t' << team.guide_teacher;
file << "\n";
file.close();
}
else {
// 文件打开失败
std::cout << "文件打开失败" << std::endl;
}
cout << "是否继续添加?(Y/N)" << endl;
char select;
cin >> select;
if (select != 'Y')
conti = false;
else if (select == 'Y')
conti = true;
}
}
void addTeam(Team& team,int flag)
{
teams.push_back(team);
teamMap[team.team_id] = team;
this->root = insertNode(root, atoi(team.team_id.c_str()));
schoolMap[team.school].push_back(team);
}
// 根据队伍编号查找参赛队伍
Team findTeamByID(const string&teamID,bool ISNOT_print)
{
double ASL;
Node* result = searchNode(this->root, atoi(teamID.c_str()));
//查找成功
if (result != nullptr) {
//cout << "查找成功!" << endl;
print_team(teamMap[teamID]);
if (!ISNOT_print) {
double ASL = calculateASL(root);
cout << "ASL为:" << ASL << endl;
}
return teamMap[teamID];
}
//查找失败
cout << "查找失败!" << endl;
return Team(); // 返回空的Team结构表示未找到
}
//删除参赛队伍
void delete_team()
{
string teamID;
bool conti = true;
while (conti) {
cout << "请输入要删除队伍编号:" << endl;
cin >> teamID;
//查找成功
if (findTeamByID(teamID,true).team_id != " ") {
vector<Team>::iterator it;
unordered_map<string, Team>::iterator iter;
for (it = teams.begin(); it != teams.end(); ) {
if (it->team_id == teamID) {
//删除schoolMap中对应元素
Team teamToDelete = *it;
auto ite = schoolMap.find(it->school);
if (ite != schoolMap.end()) {
std::vector<Team>& teamVector = ite->second;
// 遍历vector<Team>查找并删除指定的元素
for (auto iter2 = teamVector.begin(); iter2 != teamVector.end(); ++iter) {
if (iter2->team_id == teamToDelete.team_id) {
teamVector.erase(iter2);
break; // 找到并删除元素后,可以提前退出循环
}
}
}
//删除teams中对应元素
it = teams.erase(it);
//删除teamMap中对应元素
iter = teamMap.find(teamID);
iter = teamMap.erase(iter);
//删除二叉排序树中对应元素
remove(this->root, atoi(teamID.c_str()));
deleteTeamInfo(teamID);
cout << "删除成功!" << std::endl;
}
else {
++it;
}
}
cout << "是否继续删除?(Y/N)" << endl;
char select;
cin >> select;
if (select != 'Y')
conti = false;
else if (select == 'Y')
conti = true;
}
//查找失败
else
cout << "输入错误,请重试!" << endl;
}
}
void deleteTeamInfo( string& teamID)
{
std::ifstream file("team.txt");
std::vector<Team> teams;
// 读取原始文件内容到vector中
if (file.is_open()) {
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
Team team;
std::getline(iss, team.team_id, '#');
std::getline(iss, team.project_name, '#');
std::getline(iss, team.school, '#');
std::getline(iss, team.category, '#');
std::getline(iss, team.participants, '#');
std::getline(iss, team.guide_teacher, '#');
removeSpaces(team.team_id);
if (team.team_id != teamID) {
teams.push_back(team);
}
}
file.close();
}
else {
std::cout << "打开文件失败!" << std::endl;
return;
}
// 将更新后的队伍信息写回文件
std::ofstream outfile("team.txt");
if (outfile.is_open()) {
for (const auto& team : teams) {
outfile << team.team_id << '#' << team.project_name << '#' << team.school << '#'
<< team.category << '#' << team.participants << '#' << team.guide_teacher << '\n';
}
outfile.close();
}
else {
std::cout << "重新写入失败!" << std::endl;
}
}
//修改参赛队伍信息
void modifyTeamInfo()
{
string teamID;
bool conti = true;
while (conti) {
// 查找队伍是否存在
cout << "请输入要修改队伍编号:" << endl;
cin >> teamID;
if (teamMap.find(teamID) != teamMap.end()) {
Team& team = teamMap[teamID];
print_team(team); // 打印当前队伍信息
// 提示用户输入新的队伍信息
cout << "请输入新的参赛作品名称:";
cin >> team.project_name;
cout << "请输入新的参赛学校:";
cin >> team.school;
cout << "请输入新的赛事类别:";
cin >> team.category;
cout << "请输入新的参赛者:";
cin >> team.participants;
cout << "请输入新的指导老师:";
cin >> team.guide_teacher;
// 更新学校映射中的队伍信息
vector<Team>& schoolTeams = schoolMap[team.school];
for (auto& t : schoolTeams) {
if (t.team_id == teamID) {
t = team;
break;
}
}
// 更新vector<Team> teams中的队伍信息
for (auto& t : teams) {
if (t.team_id == teamID) {
t = team;
break;
}
}
std::ifstream inputFile("team.txt");
std::string firstLine;
if (inputFile.is_open()) {
std::getline(inputFile, firstLine);
inputFile.close();
}
else {
// 文件打开失败的处理逻辑
std::cout << "文件打开失败!" << std::endl;
return;
}
// 更新txt文件
std::ofstream file("team.txt");
if (file.is_open()) {
file << firstLine << std::endl; // 写入第一行文字
for (const auto& t : teams) {
file << t.team_id << "\t" << '#' << '\t' << t.project_name << "\t" << '#' << '\t' << t.school << "\t" << '#'
<< '\t' << t.category << "\t" << '#' << '\t' << t.participants << "\t" << '#' << '\t' << t.guide_teacher;
file << "\n";
}
file.close();
}
else {
// 文件打开失败的处理逻辑
std::cout << "文件打开失败!" << std::endl;
}
// 更新二叉排序树
root = remove(root, atoi(teamID.c_str()));
root = insertNode(root, atoi(team.team_id.c_str()));
cout << "队伍信息修改成功!" << endl;
cout << "是否继续修改?(Y/N)" << endl;
char select;
cin >> select;
if (select == 'N')
conti = false;
else if (select == 'Y')
conti = true;
}
else {
cout << "队伍不存在,无法修改信息!" << endl;
}
}
}
// 根据学校名称查找参赛队伍
void findTeamsBySchool() {
string schoolName;
cout << "请输入学校名称:" << endl;
cin >> schoolName;
auto it = schoolMap.find(schoolName);
if (it != schoolMap.end()) {
std::vector<Team> teams = it->second;
// 按赛事类别对团队进行排序
mergeSort(teams);
// 输出排序后的团队信息
for (const Team& team : teams) {
// 输出其他团队基本信息
print_team(team);
}
}
else {
cout << "没有找到该学校!" << std::endl;
}
}
//模拟决赛叫号系统
void simulateFinalCallingSystem() {
unordered_map<string,queue<Team>> finalRooms; // 各决赛室队列
//将参赛队按赛事类别分配到决赛室
for (Team team : teams) {
finalRooms[team.category].push(team);
}
//模拟叫号与进场过程
for (auto q : finalRooms) {
int i = 1;
while (!finalRooms[q.first].empty()) {
cout << q.first << "决赛室 " << i << " 号召集:" << endl;
string teamID = finalRooms[q.first].front().team_id;
finalRooms[q.first].pop();
cout << "队伍 " << teamID << " 进场" << endl;
cout << "正在进行比赛..." << endl;
system("pause");
cout << "队伍 " << teamID << " 比赛结束" << endl;
i++;
cout << endl;
}
cout << endl;
}
}
};
void display_menu() {
cout << "========== 计算机设计大赛赛事管理系统 ==========" << endl;
cout << "1. 增加队伍" << endl;
cout << "2. 删除队伍" << endl;
cout << "3. 更改队伍信息" << endl;
cout << "4. 按参赛队伍ID查找" << endl;
cout << "5. 按参赛学校名称查找" << endl;
cout << "6. 决赛室叫号" << endl;
cout << "7. 校园导航" << endl;
cout << "0. 退出系统" << endl;
cout << "================================================" << endl;
}
int main() {
CompetitionManagementSystem cms;
// 从team.txt中读取参赛队伍的基本信息并添加到系统中
ifstream file("team.txt");
cout << "导入文件信息....." << endl;
if (file.is_open()) {
string line;
getline(file, line);//跳过第一行
while (getline(file, line)) {
Team team;
stringstream ss(line);
string temp;
// 解析并设置team的各项属性
getline(ss, team.team_id, '#');
removeSpaces(team.team_id);
getline(ss, team.project_name, '#');
removeSpaces(team.project_name);
getline(ss, team.school, '#');
removeSpaces(team.school);
getline(ss, team.category, '#');
removeSpaces(team.category);
getline(ss, team.participants, '#');
removeSpaces(team.participants);
getline(ss, team.guide_teacher, '#');
removeSpaces(team.guide_teacher);
cms.addTeam(team,1);
}
file.close();
}
else {
cout << "无法打开team.txt文件" << endl;
}
int choice;
// 主循环
while (true) {
display_menu();
cout << "请选择操作:";
cin >> choice;
cout << endl;
switch (choice) {
case 1:
{
cout << "执行增加队伍操作......" << endl;
cms.addTeam();
cout << "添加成功!" << endl;
break;
}
case 2:
cout << "执行删除队伍操作......" << endl;
cms.delete_team();
break;
case 3:
cout << "执行更改队伍信息操作......" << endl;
cms.modifyTeamInfo();
break;
case 4:
{
string teamID;
cout << "执行按参赛队伍ID查找操作......" << endl;
cout << "请输入队伍编号:" << endl;
cin >> teamID;
cms.findTeamByID(teamID,false);
break;
}
case 5:
cout << "执行按参赛学校名称查找操作......" << endl;
cms.findTeamsBySchool();
break;
case 6:
{
cout << "执行叫号操作......" << endl;
cms.simulateFinalCallingSystem();
break;
}
case 7:
{
string source, destination;
cout << "执行校园导航操作......" << endl;
cout << "请输入起始地和目的地" << endl;
cin >> source >> destination;
cout << endl;
cms.map.queryPath(source, destination);
break;
}
case 0:
cout << "感谢使用,再见!" << endl;
return 0;
default:
cout << "无效的选项,请重新选择!" << endl;
break;
}
}
//cms.findTeamsBySchool("江苏科技大学");
//cms.map.queryBuilding("西宿舍区");
//cms.map.queryPath("行政楼", "经世楼");
return 0;
}