目录
前言
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务。
一、项目背景
中国大学生计算机设计大赛是我国高校面向本科生的计算机应用设计大赛,大赛旨在激发学生学习计算机知识和技能的兴趣与潜能,提高学生运用信息技术解决实际问题的综合能力。通过大赛这种计算机教学实践形式,可展示师生的教与学成果,最终以赛促学,以赛促教,以赛促创。该赛事在历届学生中影响力较大,参与者众多,请结合2021届省赛参赛的数据,借助数据结构课程所学的相关知识,通过对数据的处理和分析,熟悉数据结构设计及数据处理在信息管理系统中应用的重要性。赛事相关数据存储在文本文件和excel文件中,相应的文件信息说明如表1所示。其中,各个文件中不同的数据项之间均使用#分隔,图1中给出了文件team.txt中参赛信息的对应数据示例。
图1. 参赛队基本信息
二、项目分析
【问题描述】
该系统能够为省级赛事管理解决以下问题:
(1)从team.txt中读取参赛队伍的基本信息,能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
(3)能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
(4)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
(5)赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
【设计要求】
1)赛事数据要求存入文件(txt或excel)并能读入查询;
2)赛地目的地查询,需提供目的地(建筑物)名称、代号、简介、两地之间路径长度等信息;
3)输入数据形式和范围:赛事相关数据可从键盘输入,或自文件导入。
4)界面要求:交互设计要合理,每个功能可以设计菜单,用户根据提示,完成相关功能的要求。
【实现步骤提示】
1)分析任务,进行模块划分。
2)定义数据结构,建议按照抽象数据类型的定义、表示和实现来描述,用类C语言(伪代码)来描述数据的存储表示和相应操作的具体实现过程。
3)设计合适的算法来实现其功能,并绘制函数调用关系图。
【测试数据】
要求使用全部合法数据,整体非法数据,局部非法数据。进行程序测试,以保证程序的健壮性。
第(5)项内容以江苏科技大学长山校区为例,图例如下:
可制作如下示意图。
三、设计框架
首先,从整体框架上来看,这个赛事管理系统需要实现的功能比较多,将系统划分成多个模块,每个模块负责不同的功能实现。
下面是一个大致的模块划分:
1. 参赛队伍信息管理模块
从team.txt中读取参赛队伍的基本信息,实现参赛队伍的增加、删除、修改、查找等操作
// 参赛队伍的基本信息
struct TeamInfo {
};
//增加参赛队伍信息
void addTeamInteractive(){}
// 删除参赛队伍信息
void deleteTeamInteractive(){}
// 修改参赛队伍信息
void updateTeamInteractive(){}
2. 基于二叉排序树的查找模块
使用二叉排序树实现查找,并计算平均查找长度ASL。
// 二叉排序树的结点
struct TreeNode {
};
// 二叉排序树
class BinarySearchTree {
private:
TreeNode* root;
public:
BinarySearchTree() : root(nullptr) {}
// 插入结点
void insertNode(){}
// 查找结点
TeamInfo* findNode(){}
// 获取树的高度
int getHeight(){}
// 计算平均查找长度ASL
double calculateASL() {}
}
// 基于二叉排序树的查找
void binarySearchTreeSearch(){}
3. 参赛团队查询模块
实现按参赛学校查询参赛团队。
// 按参赛学校查询参赛团队
void searchBySchool(){}
4. 决赛叫号系统模块
模拟省赛现场的决赛叫号系统,按顺序叫号,被叫号参赛队进场,并展示叫号顺序与进场秩序一致。
// 决赛叫号系统
void finalsCallSystem(){
// 按照赛事类别将参赛队伍分组
unordered_map<string, vector<TeamInfo>> categoryMap;
// 按照赛事类别进行排序
vector<string> categories;
// 将参赛队伍按照顺序分配到决赛室
int numFinalRooms = 9; // 决赛室数量
vector<vector<TeamInfo>> finalRooms(numFinalRooms); // 决赛室队伍
// 模拟决赛叫号
}
5. 赛场导航模块
提供赛地的校园导游程序,实现任意两个建筑物之间的导航查询,需要提供建筑物的名称、代号、路径长度等信息。
void campusGuide() {
//目的地信息
unordered_map<int, string> destinationInfo = {}
//导航图
unordered_map<int, unordered_map<int, pair<int, int>>> navigationGraph = {}
//Dijkstra 算法来计算最短路径
}
四、数据结构的选择和概要设计
模块一:参赛队伍信息管理
1.1设计数据结构
使用指针来实现二叉排序树的存储。每个结点都是一个TreeNode
结构体,其中包含了队伍信息和指向左右子节点的指针。根节点root
是一个指向树的根结点的指针。
通过使用指针来连接各个结点,可以实现动态的插入,删除,修改等操作,并且可以根据结点之间的指针关系进行遍历和搜索。指针的使用使得二叉排序树可以高效地进行各种操作。
除了指针,队伍信息的存储使用了结构体TeamInfo
,它包含了参赛队伍的各种信息,例如队伍编号、作品名称、学校、赛事类别、参赛者和指导老师。
// 参赛队伍的基本信息
struct TeamInfo {
int teamNumber; //参赛队伍编号
string projectName; //参赛作品名称
string school; //参赛学校
string eventCategory; //赛事类别
string participants; //参赛者
string teacher; //指导老师
};
// 二叉排序树的结点
struct TreeNode {
TeamInfo teamInfo; //参赛队伍信息
TreeNode* left; //左子节点指针
TreeNode* right; //右子节点指针
int height; //节点的高度
TreeNode(const TeamInfo& info)
: teamInfo(info), left(nullptr), right(nullptr), height(1) {}
};
// 二叉排序树
class BinarySearchTree {
private:
TreeNode* root;
public:
BinarySearchTree() : root(nullptr) {}
// 插入结点
void insertNode(){}
// 查找结点
TeamInfo* findNode(){}
// 获取树的高度
int getHeight(){}
// 计算平均查找长度ASL
double calculateASL() {}
// 辅助函数:插入结点
TreeNode* insertNodeHelper(){}
// 辅助函数:查找结点
TreeNode* findNodeHelper(){}
// 辅助函数:计算ASL
void calculateASLHelper(){}
}
// 主函数中构建二叉排序树
BinarySearchTree bst;
for (const TeamInfo& info : teamInfos) {
bst.insertNode(info);
}
1.2设计操作函数
本模块需要实现以下功能:
增加参赛队伍信息
-
insertNodeHelper
函数:递归地插入新的节点到二叉排序树中。根据新节点的队伍编号与当前节点的队伍编号进行比较,决定将新节点插入左子树或右子树中。插入完成后,更新节点的高度,并进行平衡调整操作。 -
insertNode
函数:对insertNodeHelper
函数的包装,从根节点开始调用insertNodeHelper
插入新的参赛队伍信息。 -
addTeamInteractive
函数:接收用户输入的参赛队伍信息,调用insertNode
函数将队伍信息插入二叉排序树中。
// 辅助函数:插入结点
TreeNode* insertNodeHelper(TreeNode* node, const TeamInfo& info) {
if (node == nullptr) {
return new TreeNode(info);
}
if (info.teamNumber < node->teamInfo.teamNumber) {
node->left = insertNodeHelper(node->left, info);
}
else if (info.teamNumber > node->teamInfo.teamNumber) {
node->right = insertNodeHelper(node->right, info);
}
else {
// 结点已存在,进行更新
node->teamInfo = info;
return node;
}
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
int balance = getBalance(node);
// 平衡调整
if (balance > 1 && info.teamNumber < node->left->teamInfo.teamNumber) {
return rotateRight(node);
}
if (balance < -1 && info.teamNumber > node->right->teamInfo.teamNumber) {
return rotateLeft(node);
}
if (balance > 1 && info.teamNumber > node->left->teamInfo.teamNumber) {
node->left = rotateLeft(node->left);
return rotateRight(node);
}
if (balance < -1 && info.teamNumber < node->right->teamInfo.teamNumber) {
node->right = rotateRight(node->right);
return rotateLeft(node);
}
return node;
}
// 添加参赛队伍信息
void addTeamInteractive(BinarySearchTree& bst) {
TeamInfo info;
cout << "请输入参赛队伍编号: ";
cin >> info.teamNumber;
cin.ignore(); // 忽略换行符
cout << "请输入参赛作品名称: ";
getline(cin, info.projectName);
cout << "请输入参赛学校: ";
getline(cin, info.school);
cout << "请输入赛事类别: ";
getline(cin, info.eventCategory);
cout << "请输入参赛者: ";
getline(cin, info.participants);
cout << "请输入指导老师: ";
getline(cin, info.teacher);
bst.addTeam(info);
}
删除参赛队伍信息
-
deleteTeamHelper
函数:递归地删除指定队伍编号的节点,并进行平衡调整。根据指定的队伍编号与当前节点的队伍编号进行比较,决定向左子树或右子树递归删除节点。若找到要删除的节点后,根据节点的子节点情况进行删除操作。若节点为叶子节点,直接删除;若节点只有一个子树,用子树代替当前节点;若节点有两个子树,找到右子树中的最小节点,将其值替换到当前节点,并递归删除最小节点。完成删除后,更新节点的高度,并进行平衡调整。 -
deleteTeamInteractive
函数:接收用户输入的要删除的参赛队伍编号,调用deleteTeamHelper
函数从二叉排序树中删除对应的队伍信息。
// 辅助函数:删除结点
TreeNode* deleteTeamHelper(TreeNode* node, int teamNumber) {
if (node == nullptr) {
return nullptr;
}
if (teamNumber < node->teamInfo.teamNumber) {
node->left = deleteTeamHelper(node->left, teamNumber);
}
else if (teamNumber > node->teamInfo.teamNumber) {
node->right = deleteTeamHelper(node->right, teamNumber);
}
else {
// 找到要删除的结点
cout << "找到要删除的队伍信息,当前信息如下:" << endl;
displayTeamInfo(node->teamInfo);
if (node->left == nullptr && node->right == nullptr) {
// 结点为叶子结点,直接删除
delete node;
return nullptr;
}
else if (node->left == nullptr) {
// 结点只有右子树,用右子树代替当前结点
TreeNode* temp = node->right;
delete node;
return temp;
}
else if (node->right == nullptr) {
// 结点只有左子树,用左子树代替当前结点
TreeNode* temp = node->left;
delete node;
return temp;
}
else {
// 结点有两个子树,找到右子树中的最小结点,用该结点代替当前结点
TreeNode* minRight = findMinNode(node->right);
node->teamInfo = minRight->teamInfo;
node->right = deleteTeamHelper(node->right, minRight->teamInfo.teamNumber);
}
}
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
int balance = getBalance(node);
// 平衡调整
if (balance > 1 && getBalance(node->left) >= 0) {
return rotateRight(node);
}
if (balance > 1 && getBalance(node->left) < 0) {
node->left = rotateLeft(node->left);
return rotateRight(node);
}
if (balance < -1 && getBalance(node->right) <= 0) {
return rotateLeft(node);
}
if (balance < -1 && getBalance(node->right) > 0) {
node->right = rotateRight(node->right);
return rotateLeft(node);
}
return node;
}
// 删除参赛队伍信息
void deleteTeamInteractive(BinarySearchTree& bst) {
int teamNumber;
cout << "请输入要删除的参赛队伍编号: ";
cin >> teamNumber;
bst.deleteStudent(teamNumber);
}
修改参赛队伍信息
-
findTeamHelper
函数:通过递归查找二叉排序树中指定队伍编号的节点。从根节点开始,将指定队伍编号与当前节点的队伍编号进行比较,根据比较结果选择向左子树或右子树递归查找,直到找到匹配的节点或遍历到叶子节点为止。如果找到匹配的节点,则将节点中的队伍信息赋值给foundInfo
,并返回true
;如果遍历到叶子节点仍未找到匹配的节点,则返回false
。 -
findTeam
函数:调用findTeamHelper
函数从根节点开始查找指定队伍编号的节点,并将匹配的队伍信息存储到foundInfo
中。返回一个布尔值,表示是否找到匹配的队伍。 -
updateTeam
函数:根据指定队伍编号调用findTeam
函数查找对应的队伍信息。如果未找到匹配的队伍,输出提示信息并结束函数。如果找到匹配的队伍,先输出当前队伍信息,然后接收用户输入的新信息并存储到updatedInfo
中。最后调用updateInFile
函数将更新后的队伍信息写入文件。 -
updateTeamInteractive
函数:接收用户输入的要修改的参赛队伍编号,调用updateTeam
函数进行队伍信息的修改。
// 辅助函数:查找参赛队伍信息
bool findTeamHelper(TreeNode* node, int teamNumber, TeamInfo& foundInfo) {
if (node == nullptr) {
return false;
}
if (teamNumber < node->teamInfo.teamNumber) {
return findTeamHelper(node->left, teamNumber, foundInfo);
}
else if (teamNumber > node->teamInfo.teamNumber) {
return findTeamHelper(node->right, teamNumber, foundInfo);
}
else {
// 找到要修改的参赛队伍信息
foundInfo = node->teamInfo;
return true;
}
}
// 查找参赛队伍信息
bool findTeam(int teamNumber, TeamInfo& foundInfo) {
return findTeamHelper(root, teamNumber, foundInfo);
}
// 修改参赛队伍信息
void updateTeam(int teamNumber) {
TeamInfo foundInfo;
bool found = findTeam(teamNumber, foundInfo);
if (!found) {
cout << "未找到指定的队伍信息,无法修改。" << endl;
return;
}
cout << "找到要修改的队伍信息,当前信息如下:" << endl;
displayTeamInfo(foundInfo);
cout << endl;
TeamInfo updatedInfo;
cout << "请输入新的信息:" << endl;
cout << "请输入参赛作品名称: ";
cin.ignore(); // 忽略换行符
getline(cin, updatedInfo.projectName);
cout << "请输入参赛学校: ";
getline(cin, updatedInfo.school);
cout << "请输入赛事类别: ";
getline(cin, updatedInfo.eventCategory);
cout << "请输入参赛者: ";
getline(cin, updatedInfo.participants);
cout << "请输入指导老师: ";
getline(cin, updatedInfo.teacher);
updateInFile(teamNumber, updatedInfo);
}
// 修改参赛队伍信息
void updateTeamInteractive(BinarySearchTree& bst) {
int teamNumber;
cout << "请输入要修改的参赛队伍编号: ";
cin >> teamNumber;
bst.updateTeam(teamNumber);
}
模块二:基于二叉排序树的查找
2.1 设计数据结构
参赛队伍信息需要根据参赛队编号进行查询,并计算平均查找长度ASL,使用二叉排序树来存储参赛队伍信息。根节点指向树的根部,每个节点包含一个队伍信息对象,并根据队伍编号进行排序。左子节点的队伍编号小于父节点的队伍编号,右子节点的队伍编号大于父节点的队伍编号
2.2 设计操作函数
参赛队伍信息查询模块需要实现以下操作:
1.根据参赛队编号查找相应的参赛队伍信息。
2。计算平均查找长度ASL。
算法分析:
-
getHeight
函数:用于获取节点的高度。如果节点为空,则返回0;否则返回节点的高度。 -
calculateASL
函数:用于计算二叉排序树的平均查找长度(ASL)。通过调用辅助函数calculateASLHelper
递归计算每个节点的层级,并累加到总的查找长度totalASL
中。同时,记录节点的数量nodeCount
。最后,将总的查找长度除以节点数量得到平均查找长度。 -
calculateASLHelper
函数:辅助函数,用于递归计算每个节点的层级。通过前序遍历二叉排序树,对每个非空节点,将当前层级level
累加到总的查找长度totalASL
中,并递归调用左子节点和右子节点进行层级计算。 -
binarySearchTreeSearch
函数:基于二叉排序树的查找操作。用户输入要查找的参赛队编号,调用findNode
函数在二叉排序树中查找匹配的节点。如果找到匹配的节点,则输出队伍信息和平均查找长度(ASL)的计算结果。如果未找到匹配的节点,则输出查找失败的提示信息。
// 获取树的高度
int getHeight(TreeNode* node) {
if (node == nullptr) {
return 0;
}
return node->height;
}
// 计算平均查找长度ASL
double calculateASL() {
int totalASL = 0;
int nodeCount = 0;
calculateASLHelper(root, 1, totalASL, nodeCount);
return static_cast<double>(totalASL) / nodeCount;
}
// 辅助函数:计算ASL
void calculateASLHelper(TreeNode* node, int level, int& totalASL, int& nodeCount) {
if (node != nullptr) {
totalASL += level;
nodeCount++;
calculateASLHelper(node->left, level + 1, totalASL, nodeCount);
calculateASLHelper(node->right, level + 1, totalASL, nodeCount);
}
}
// 基于二叉排序树的查找
void binarySearchTreeSearch(BinarySearchTree& bst) {
int teamNumber;
cout << "请输入要查找的参赛队编号:";
cin >> teamNumber;
TeamInfo* teamInfo = bst.findNode(teamNumber);
if (teamInfo != nullptr) {
cout << "查找成功!" << endl;
cout << "参赛作品名称:" << teamInfo->projectName << endl;
cout << "参赛学校:" << teamInfo->school << endl;
cout << "赛事类别:" << teamInfo->eventCategory << endl;
cout << "参赛者:" << teamInfo->participants << endl;
cout << "指导老师:" << teamInfo->teacher << endl;
double asl = bst.calculateASL();
cout << "平均查找长度ASL:" << asl << endl;
cout << "ASL的计算公式:" << endl;
cout << "ASL = (h1 + h2 + ... + hn) / n" << endl;
cout << "其中,hi 表示查找到的参赛队所在的层级,n 表示节点的总数" << endl;
}
else {
cout << "查找失败!" << endl;
}
}
模块三:参赛团队信息查询
3.1 设计数据结构
参赛团队信息需要根据参赛学校名称进行查询,并需要按赛事类别有序输出,可以使来存储参赛队伍信息。
使用了向量(vector
)来存储队伍信息,通过遍历和选择排序来进行查找和排序操作。
选择排序是一种简单但效率较低的排序算法,它的主要思想是每次从未排序的元素中选择最小(或最大)的元素,然后将其放置在已排序序列的末尾。
选择排序被用于对匹配的队伍信息按队伍编号进行排序。虽然选择排序的时间复杂度为O(n^2),效率不如更高级的排序算法(如快速排序或归并排序),但在小规模数据集的情况下,选择排序是可以接受的。
选择排序的实现相对简单,代码量较少,易于理解和实现。这段代码的主要目的是演示按学校查询参赛队伍信息的功能,而不是关注排序算法的性能。因此,选择排序作为一个简单的排序方法可以满足排序需求。
3.2 设计操作函数
参赛团队信息查询模块需要实现以下操作:
读取参赛队信息文件数据
根据参赛学校名称查找相应的参赛团队信息,并按赛事类别有序输出。
searchTeamBySchool
函数的作用是遍历给定的参赛队伍信息,找到与指定学校匹配的队伍信息,并按队伍编号进行排序后输出。
searchTeamsBySchoolName
函数主要用于读取存储参赛队伍信息的文件(team.txt),将每行数据解析为队伍信息的结构体,并将其存储在名为team1
的向量中。然后,它会要求用户输入需要查找的学校名称,并调用searchTeamBySchool
函数来进行查询和输出。
// 按学校查询参赛队伍信息
void searchTeamBySchool(const string& school, const vector<TeamInfo>& teamInfos) {
// 遍历参赛队伍,找到匹配的队伍信息
vector<TeamInfo> matchedTeams;
for (const TeamInfo& team : teamInfos) {
if (team.school == school) {
matchedTeams.push_back(team);
}
}
if (matchedTeams.empty()) {
cout << "未找到匹配的队伍信息" << endl;
return;
}
// 使用选择排序按参赛队编号排序
for (int i = 0; i < matchedTeams.size() - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < matchedTeams.size(); j++) {
if (matchedTeams[j].teamNumber < matchedTeams[minIndex].teamNumber) {
minIndex = j;
}
}
if (minIndex != i) {
swap(matchedTeams[i], matchedTeams[minIndex]);
}
}
// 输出排序后的参赛团队信息
for (const TeamInfo& team : matchedTeams) {
cout << "队伍编号:" << team.teamNumber << endl;
cout << "项目名称:" << team.projectName << endl;
cout << "参赛学校:" << team.school << endl;
cout << "赛事类别:" << team.eventCategory << endl;
cout << "参赛人员:" << team.participants << endl;
cout << "指导教师:" << team.teacher << endl;
cout << "=================" << endl;
}
}
//按学校查询队伍信息
void searchTeamsBySchoolName(const vector<TeamInfo>& teamInfos) {
vector<TeamInfo> team1;
ifstream inFile("team.txt");//打开文件
if (!inFile) {
cout << "无法打开文件 team.txt\n";
}
//从输入流中读取一行文本,并将其存储到一个字符串变量中
string line;
getline(inFile, line); // 读取文件中的标题行,忽略
while (getline(inFile, line)) {
// 使用 stringstream 进行分割
stringstream ss(line);
string temp;
int teamNumber;
ss >> teamNumber;
ss.ignore();
getline(ss, temp, '\t'); // 跳过参赛作品名称的 #
string projectName;
getline(ss, projectName, '\t');
ss.ignore();
getline(ss, temp, '\t'); // 跳过参赛学校的 #
string school;
getline(ss, school, '\t');
ss.ignore();
getline(ss, temp, '\t'); // 跳过赛事类别的 #
string eventCategory;
getline(ss, eventCategory, '\t');
ss.ignore();
getline(ss, temp, '\t'); // 跳过参赛者的 #
string participants;
getline(ss, participants, '\t');
ss.ignore();
getline(ss, temp, '\t'); // 跳过指导教师的 #
string teacher;
getline(ss, teacher);
TeamInfo team;
team.teamNumber = teamNumber;
team.projectName = projectName;
team.school = school;
team.eventCategory = eventCategory;
team.participants = participants;
team.teacher = teacher;
team1.push_back(team);
}
cout << "请输入需要查找的学校名称:";
string school;
cin >> school;
searchTeamBySchool(school, team1);
}
模块四:决赛叫号模拟
4.1 设计数据结构
决赛叫号需要模拟9个决赛室,每个决赛室可以有多个参赛队伍进行比赛,可以使用队列来存储参赛队伍信息。
unordered_map<string, vector<TeamInfo>> categoryMap
:使用无序映射(unordered_map),以赛事类别为键,将参赛队伍按照赛事类别分组存储。vector<string> categories
:存储赛事类别,用于后续对赛事类别进行排序。vector<vector<TeamInfo>> finalRooms
:二维向量,表示决赛室队伍的分配情况。每个决赛室作为一维向量,包含分配到该决赛室的参赛队伍
4.2 设计操作函数
决赛叫号模拟模块需要实现以下操作:
根据赛事类别,将参赛队伍分配到9个决赛室中,每个决赛室均采用队列模拟参赛队伍进场顺序。
模拟决赛室中参赛队伍的进场顺序。
- 遍历给定的参赛队伍信息,将参赛队伍按照赛事类别分组存储在
categoryMap
中,使用无序映射的特性,可以快速根据赛事类别查找对应的队伍信息。 - 将赛事类别存储在
categories
向量中,并对其进行排序,以确保按照字典序输出。 - 根据决赛室的数量,创建一个二维向量
finalRooms
,表示决赛室队伍的分配情况。 - 依次遍历排好序的赛事类别
categories
,将每个赛事类别对应的队伍信息按顺序分配到决赛室中,通过取模运算实现循环分配。 - 模拟决赛叫号的过程,依次输出每个决赛室的编号和其中的参赛队伍信息。在每个参赛队伍进入赛场后,使用
this_thread::sleep_for
函数模拟比赛结束前的等待时间。
// 决赛叫号系统
void finalsCallSystem(const vector<TeamInfo>& teamInfos) {
// 按照赛事类别将参赛队伍分组
unordered_map<string, vector<TeamInfo>> categoryMap;
for (const TeamInfo& info : teamInfos) {
categoryMap[info.eventCategory].push_back(info);
}
// 按照赛事类别进行排序
vector<string> categories;
for (const auto& pair : categoryMap) {
categories.push_back(pair.first);
}
sort(categories.begin(), categories.end());
// 将参赛队伍按照顺序分配到决赛室
int numFinalRooms = 9; // 决赛室数量
vector<vector<TeamInfo>> finalRooms(numFinalRooms); // 决赛室队伍
int roomIndex = 0;
for (const string& category : categories) {
const vector<TeamInfo>& teams = categoryMap[category];
for (const TeamInfo& team : teams) {
finalRooms[roomIndex].push_back(team);
roomIndex = (roomIndex + 1) % numFinalRooms;
}
}
// 模拟决赛叫号
for (int i = 0; i < numFinalRooms; i++) {
cout << "决赛室编号:" << i + 1 << endl;
cout << "=================" << endl;
const vector<TeamInfo>& teams = finalRooms[i];
for (const TeamInfo& team : teams) {
cout << "参赛队编号:" << team.teamNumber << " 进入赛场" << endl;
this_thread::sleep_for(chrono::milliseconds(500)); // 比赛结束前等待0.5秒
}
cout<< " 比赛结束" << endl;
cout << endl;
}
}
模块五:赛地导游查询
5.1 设计数据结构
赛地导游查询模块需要提供校园导游程序和路径导航查询服务,可以使用图来存储校园地图信息和路径信息。
unordered_map<int, string> destinationInfo
:使用无序映射(unordered_map),将目的地的编号与目的地信息进行映射,方便根据编号查找目的地信息。unordered_map<int, unordered_map<int, pair<int, int>>> navigationGraph
:使用无序映射,表示导航图的数据结构。外层无序映射的键表示起点的编号,内层无序映射的键表示终点的编号,值是一个pair,包含路径长度和路径编号。
5.2 设计操作函数
赛地导游查询模块需要实现以下操作:
读取校园地图信息文件和路径信息文件,构建图。
根据参赛者的查询,提供相应的校园地图信息或路径导航查询服务。
- 使用Dijkstra算法来计算起点到终点的最短路径。
- 创建并初始化距离(distance)、前驱顶点(prev)和未访问顶点集合(unvisited)的数据结构。
- 设置起点到起点的距离为0,将其他顶点的距离初始化为无穷大,并将起点加入未访问集合。
- 在未访问集合中选择距离最小的顶点u,遍历u的邻居节点v,更新到v的距离和前驱顶点。
- 重复上述步骤,直到终点被访问到或者所有顶点都被访问完。
- 若终点的前驱顶点为-1,则表示无法找到最短路径,输出提示信息并返回。
- 若存在最短路径,则通过前驱顶点记录的路径信息构建路径,并输出路径信息和长度。
- 输出路径中每个目的地的详细信息。
//导航系统
void campusGuide() {
//目的地信息
unordered_map<int, string> destinationInfo = {
{1, "行政楼,用途:办公楼"},
{2, "海韵湖,用途:湖泊,风景优美"},
{3, "图书馆,用途:查阅资料,自习室"},
//其他的目的地信息
};
//导航图
unordered_map<int, unordered_map<int, pair<int, int>>> navigationGraph = {
{1, {{2, {300, 2}}, {10, {700, 10}}}},
{2, {{3, {600, 3}}, {1, {300, 1}}}},
{3, {{10, {400, 10}}, {8, {550, 8}}, {4, {100, 4}}, {2, {300, 2}}}},
//其他的
};
cout << "1.行政楼 " << "2.海韵湖 " << "3.图书馆 " << "4.东食堂 " << "5.东操场 \n"
<< "6.南门 " << "7.文体中心" << "8.西操场 " << "9.经世楼 " << "10.文理大楼" << "11.西食堂 " << "12.西宿舍区" << endl;
int start, end;
cout << "请输入起点编号:" << endl;
cin >> start;
cout << "请输入终点编号:" << endl;
cin >> end;
if (destinationInfo.find(start) == destinationInfo.end() || destinationInfo.find(end) == destinationInfo.end()) {
cout << "请输入有效的起点和终点编号!" << endl;
return;
}
//Dijkstra 算法来计算最短路径
unordered_map<int, int> distance; // 记录起点到各个顶点的最短距离
unordered_map<int, int> prev; // 记录最短路径中每个顶点的前驱顶点
vector<int> unvisited; // 未访问的顶点集合
for (const auto& pair : destinationInfo) {
const int& destination = pair.first;
distance[destination] = numeric_limits<int>::max(); // 初始距离设为无穷大
prev[destination] = -1; // 初始前驱顶点设为-1,表示不存在前驱顶点
unvisited.push_back(destination);
}
distance[start] = 0; // 起点到起点的距离为0
while (!unvisited.empty()) {
int u = unvisited[0];
int minDistance = distance[u];
for (const int& destination : unvisited) {
if (distance[destination] < minDistance) {
u = destination;
minDistance = distance[destination];
}
}
unvisited.erase(remove(unvisited.begin(), unvisited.end(), u), unvisited.end());
if (u == end) {
break;
}
for (const auto& neighbor : navigationGraph[u]) {
const int& v = neighbor.first;
int altDistance = distance[u] + neighbor.second.first;
if (altDistance < distance[v]) {
distance[v] = altDistance;
prev[v] = u;
}
}
}
if (prev[end] == -1) {
cout << "无法找到最短路径" << endl;
return;
}
vector<int> path;
int current = end;
while (current != start) {
path.push_back(current);
current = prev[current];
}
path.push_back(start);
reverse(path.begin(), path.end());
cout << "最短路径为:" << endl;
for (size_t i = 0; i < path.size(); ++i) {
cout << path[i];
if (i != path.size() - 1) {
cout << " -> ";
}
}
cout << endl;
cout << "路径长度为:" << distance[end] << " 米" << endl;
cout << "详细信息:" << endl;
for (const int& destination : path) {
cout << destination << ": " << destinationInfo[destination] << endl;
}
}
四结果演示:
总结
赛事管理系统通过多个功能模块实现了赛事管理的不同需求。查询参赛队伍信息模块可以按学校查询并展示参赛队伍的相关信息,帮助用户快速了解队伍情况。决赛叫号系统模块将参赛队伍按照赛事类别分组并分配到不同的决赛室,通过模拟叫号的方式让比赛顺利进行。导航系统模块使用Dijkstra算法计算最短路径,帮助用户在校园中快速找到目的地。
在数据结构方面,系统使用了unordered_map和vector等容器来存储参赛队伍信息、目的地信息和导航图等数据。这些数据结构提供了高效的查找和访问功能,确保系统的性能和可扩展性。
在算法方面,选择排序算法用于按队伍编号排序,Dijkstra算法用于计算最短路径。这些算法能够高效地处理大量数据,并保证结果的准确性和可靠性。
总之,这个赛事管理系统具备简洁明了的代码结构、完善的功能和稳定可靠的性能。它为赛事管理人员提供了强大的工具,提高了工作效率和管理水平,是一个不错的赛事管理解决方案。