中国大学生计算机设计大赛省级赛事管理系统
中国大学生计算机设计大赛是我国高校面向本科生的计算机应用设计大赛,大赛旨在激发学生学习计算机知识和技能的兴趣与潜能,提高学生运用信息技术解决实际问题的综合能力。通过大赛这种计算机教学实践形式,可展示师生的教与学成果,最终以赛促学,以赛促教,以赛促创。该赛事在历届学生中影响力较大,参与者众多,请结合2021届省赛参赛的数据,借助数据结构课程所学的相关知识,通过对数据的处理和分析,全面了解赛事组织及管理的体系,以及数据结构设计及数据处理在信息管理系统中应用的重要性。赛事相关数据存储在文本文件和excel文件中,相应的文件信息说明如表1所示。其中,各个文件中不同的数据项之间均使用#分隔,图1中给出了文件team.txt中参赛信息的对应数据示例。
图1. 参赛队基本信息
【问题描述】
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务,该系统能够为省级赛事管理解决以下问题:
(1)从team.txt中读取参赛队伍的基本信息,能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。请输出ASL(成功)的计算公式和结果值。
(3)能够提供按参赛学校查询参赛团队,根据提示输入参赛学校名称,若查找成功,输出该学校参赛的所有团队的基本信息,输出的参赛团队需有序输出(按参赛队编号)。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
(4)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,各参赛队进场比赛时间可设为0.5秒)
(5)赛事系统为参赛者提供赛地的校园导游程序。为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供图中任意目标地(建筑物)的问路查询,即查询任意两个目的地(建筑物)之间的一条最短的简单路径。
【设计要求】
1)赛事数据要求存入文件(txt或excel)并能读入查询;
2)赛地目的地查询,需提供目的地(建筑物)名称、代号、简介等信息;最短路径的输出需包含途经地及最短路径值。
3)输入数据形式和范围:赛事相关数据可从键盘输入,或自文件导入。
4)界面要求:交互设计要合理,每个功能可以设计菜单,用户根据提示,完成相关功能的要求。
问题一:
从已给teme.txt读取信息,管理赛事信息,实现增加,删除,修改的基本操作
1.1:定义参赛队伍信息结构体
struct Team {//定义参赛队伍信息结构体
string id; // 参赛队编号
string workname; // 参赛作品名称
string school; // 参赛学校
string category; // 赛事类别
string participants; // 参赛者
string teacher; // 指导老师
};
注意最好将id用string类型进行定义防止报错
1.2:定义容器vector
vector<Team> teams; // 用容器vector储存队伍信息
1.3:实现添加操作
void addTeam()// 添加参赛队伍
{
bool isaddingteams = true;
while (isaddingteams) {
Team team;
cout << "请输入队伍信息" << endl;
cout << "队伍编号:" << endl;
cin >> team.id;
cout << "参赛作品名称:" << endl;
cin >> team.workname;
cout << "参赛学校:" << endl;
cin >> team.school;
cout << "赛事类别:" << endl;
cin >> team.category;
cout << "参赛者:" << endl;
cin >> team.participants;
cout << "指导老师:" << endl;
cin >> team.teacher;
teams.push_back(team);
定义一个addTeam函数,用户依次输入队伍编号,作品名称,参赛学校,赛事类别,参赛者,指导老师。
1.4:实现删除操作
if (findbyID(teamID, true).id != " ") {
std::vector<Team>::iterator it;
unordered_map<string, Team>::iterator iter;
for (it = teams.begin(); it != teams.end(); ) {
if (it->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->id == teamToDelete.id) {
teamVector.erase(iter2);
break; // 找到并删除元素后,可以提前退出循环
}
}
}
it = teams.erase(it);
//删除teamMap中对应元素
iter = teamMap.find(teamID);
iter = teamMap.erase(iter);
Delete(this->root, atoi(teamID.c_str()));
deleteinshu(teamID);
std::cout << "删除成功!" << std::endl;
在删除操作中使用了teams容器,这段代码的作用是删除参赛队伍。其中,findbyID 函数用于根据队伍编号查找对应的队伍信息,如果找到了,则返回该队伍信息,否则返回一个空的队伍信息。最外层的 if 语句用于判断是否找到了对应的队伍信息。
1.5:实现修改操作
void changeTeamInfo()
{
string teamID;
while (true) {
// 查找队伍是否存在
cout << "请输入要修改队伍的编号:" << endl;
cin >> teamID;
if (teamMap.find(teamID) != teamMap.end()) {
Team& team = teamMap[teamID];
cout << "请输入新的参赛作品名称:";
cin >> team.workname;
cout << "请输入新的参赛学校:";
cin >> team.school;
cout << "请输入新的赛事类别:";
cin >> team.category;
cout << "请输入新的参赛者:";
cin >> team.participants;
cout << "请输入新的指导老师:";
cin >> team.teacher;
1. 循环输入要修改的队伍编号,查找该队伍是否存在于 teamMap 中。
2. 如果找到了该队伍,则根据输入更新队伍的参赛作品名称、参赛学校、赛事类别、参赛者和指导老师等信息。
3. 更新 schoolMap 容器中相应学校对应的 vector<Team> 容器中该队伍信息。
4. 遍历容器 schoolTeams 中的所有队伍信息,如果找到了与当前队伍编号一致的队伍信息,则用新的队伍信息替换原有的队伍信息,并 break 循环。
1.6 运行结果:
问题二: 读取信息按编号查找并输出ASL
2.1:导入文件,读取并导入数据到vector
ifstream file("team.txt");
cout << "正在导入文件信息" << endl;
if (file.is_open()) {
string line;
getline(file, line);//跳过第一行,表格第一行无实际意义
while (getline(file, line)) {
Team team;
stringstream s(line);
string temp;
getline(s, team.id, '#');
delkonge(team.id);
getline(s, team.workname, '#');
delkonge(team.workname);
getline(s, team.school, '#');
delkonge(team.school);
getline(s, team.category, '#');
delkonge(team.category);
getline(s, team.participants, '#');
delkonge(team.participants);
getline(s, team.teacher, '#');
delkonge(team.teacher);
t.addTeam(team, 1);//将读取到的信息通过addTeam添加到系统中
}
file.close();
}
else {
cout << "无法打开文件" << endl; }
注意要跳过表格的第一行读取数据,因为第一行是title,然后每次都跳过“#”分别读取参赛编号,学校,类别,参赛选手,指导老师。
2.2:构建二叉树结构体
struct Node {//定义二叉树结构体
int data;
Node* left;
Node* right;
Node(int value) {
data = value;
left = nullptr;
right = nullptr;
}
};
分别包含了节点数据和左右子树节点指针,将节点的数据初始化为输入的参赛队编号,左右子节点指针初始化为nuiipter。
2.3 定义二叉排序树查找函数
Node* searchNode(Node* root, int value) {// 基于二叉排序树的查找(循环)
while (root) {
if (value < root->data) {
root = root->left;
}
else if (value > root->data) {
root = root->right;
}
else {
return root;
}
}
return nullptr;
}
Node* findMin(Node* root) {//找到以root为根节点的树最小节点
if (root == nullptr) {
return nullptr;
}
while (root->left != nullptr) {
root = root->left;
}
return root;
}
2.4:计算ASL
ASL为访问的路径总长度(所有节点到根节点路径长度之和)除以节点总数,先计算树的节点数和总搜索路径长度,然后将总搜索路径长度除以节点数,得到平均搜索长度ASL,作为函数的返回值。
2.5:运行结果
问题三:按参赛学校查找并有序输出
3.1 定义查找函数
void findbyschool() {
string schoolName;
cout << "请输入学校名称:" << endl;
cin >> schoolName;
auto it = schoolMap.find(schoolName);
if (it != schoolMap.end()) {
std::vector<Team> teams = it->second;
实现了根据输入的学校名称查找该学校参赛的所有团队信息,并将它们存储到一个名为 teams 的动态数组中。
3.2 堆排序
//heapify用于调整堆
void heapify(vector<Team>& teams, int n, int i) {// 堆排序,时间复杂度为 O(n log n),非常高效
int largest = i;//空间复杂度也小,只需要一个额外空间交换元素
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && teams[left].id > teams[largest].id) {
largest = left;
}
if (right < n && teams[right].id > teams[largest].id) {
largest = right;
}
if (largest != i) {
swap(teams[i], teams[largest]);
heapify(teams, n, largest);
}
}
void heapSort(vector<Team>& teams) {//对整个数组堆排序
int n = teams.size();
for (int i = (n / 2) - 1; i >= 0; i--) {
heapify(teams, n, i);
}
for (int i = n - 1; i >= 0; i--) {
swap(teams[0], teams[i]);
heapify(teams, i, 0);
}
}
在 heapSort 函数中,首先获取待排序数组的元素个数 n,并根据该值构建初始堆。然后,将堆顶元素与堆底元素交换,并排除当前的堆底元素,继续维护剩余元素的堆结构,直到完成整个堆排序的过程。
3.3 运行结果
问题四: 决赛叫号系统
4.1 模拟叫号系统
//模拟决赛叫号系统
void jiaohaosystem() {
unordered_map<string, queue<Team>> finalRooms;
for (const auto& team : teams) {// 将参赛队按赛事类别分配到决赛室
finalRooms[team.category].push(team);
}
// 模拟叫号与进场过程
int i = 1;
for (const auto& roomPair : finalRooms) {
auto& roomTeams = const_cast<queue<Team>&>(roomPair.second);
cout << roomPair.first << "决赛室:" << endl;
while (!roomTeams.empty()) {
cout << i << "号召集:" << endl;
const auto& team = roomTeams.front();
roomTeams.pop();
cout << " 队伍 " << team.id << " 进场" << endl;
// 其他比赛流程
i++;
cout << endl;
}
cout << endl;
}
}
};
代码通过循环召唤各个决赛室中的参赛队伍,这段代码实现的是一个叫号系统,将参赛队伍按照赛事类别分配到不同的决赛室,并模拟叫号和进场的过程。 接下来,代码通过循环召唤各个决赛室中的参赛队伍。在循环中,首先输出当前召唤的决赛室名称,然后依次召唤该决赛室中的每个参赛队伍。在召唤每个参赛队伍时,输出其队伍编号并模拟进场过程,最后更新召唤号码并输出换行符以进行格式化。
4.2 运行结果
问题五:校园导航系统
5.1 建筑物和道路结构体
// 建筑物结构体
class Building
{
public:
Building(int id, string name, int x, int y)
{
id_ = id;
name_ = name;
x_ = x;
y_ = y;
}
int getID()
{
return id_;
}
string getName()
{
return name_;
}
int getX()
{
return x_;
}
int getY()
{
return y_;
}
private:
int id_;
string name_;
int x_;
int y_;
};
// 道路类
class Road
{
public:
Road(int from, int to, int dist)
{
from_ = from;
to_ = to;
dist_ = dist;
}
int getFrom()
{
return from_;
}
int getTo()
{
return to_;
}
int getDist()
{
return dist_;
}
private:
int from_;
int to_;
int dist_;
};
这里定义了两个类,一个是 `Building` 类表示建筑物,另一个是 `Road` 类表示道路。`Building` 类包括建筑物编号、名称、横轴坐标以及纵轴坐标四个成员变量,并提供了相应的 getter 方法。`Road` 类包括起点、终点以及距离三个成员变量,同样提供了相应的 getter 方法。这两个类在路径导航程序中可以作为基本的数据结构来存储建筑物和道路信息,并与之前的无向加权图等数据结构配合使用实现路径导航功能。
5.2 Dijkstra算法查询最短路径
class PathFinder
{
public:
PathFinder(SchoolMap& map) : map_(map) {}
void findPath(int start, int end)
{
// 距离和前继节点的哈希表
unordered_map<int, int> prevNodes;
unordered_map<int, int> dist;
for (auto b : map_.getBuildings())
{
prevNodes[b.getID()] = -1; // 初始时前继节点都是 -1
dist[b.getID()] = INT_MAX; // 初始时距离为无穷大
}
dist[start] = 0;
// 使用小根堆存储候选节点
auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) -> bool { return a.second > b.second; };
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
pq.push(make_pair(start, 0));
while (!pq.empty())
{
auto p = pq.top();
pq.pop();
if (p.first == end)
break;
auto adjNodes = map_.getAdjacentNodes(p.first);
for (auto node : adjNodes)
{
int newDist = dist[p.first] + map_.getDistance(p.first, node);
if (newDist < dist[node])
{
prevNodes[node] = p.first;
dist[node] = newDist;
pq.push(make_pair(node, newDist));
}
}
}
// 根据前继节点哈希表回溯最短路径
int curNode = end;
vector<int> path;
path.push_back(curNode);
while (prevNodes[curNode] != -1)
{
path.push_back(prevNodes[curNode]);
curNode = prevNodes[curNode];
}
reverse(path.begin(), path.end());
// 输出查询结果
cout << "从 " << map_.getBuilding(start).getName() << " 到 " << map_.getBuilding(end).getName() << " 的最短路径是:\n";
for (auto node : path)
cout << map_.getBuilding(node).getName() << " ";
cout << "\n距离是:" << dist[end] << " 米" << endl;
}
private:
SchoolMap& map_;
};
算法的主要思想是通过小根堆来维护候选节点,每次取距离起点最近的节点进行更新,同时更新与其相邻节点的距离和前继节点,直到候选节点为空或者发现了终点。在算法过程中,使用哈希表 `prevNodes` 保存每个节点的前继节点,用于回溯最短路径;用哈希表 `dist` 保存每个节点到起点的距离,初始时距离都为无穷大,开始更新后即为当前已知的最短距离。最后,回溯最短路径时,根据前继节点哈希表 `prevNodes` 从终点向起点回溯,并将路径反转存储到 `path` 中,再根据 `path` 输出查询结果。
5.3 创建校园地图对象,添加建筑物对象和道路
// 添加建筑物
map.addBuilding(Building(1, "3号组图:学生住宿区", 0, 0));
map.addBuilding(Building(2, "西苑食堂:干饭区1", 100, 100));
map.addBuilding(Building(3, "笃学楼:大教室分布较多", 300, 100));
map.addBuilding(Building(4, "图书馆:学习圣地", 400, 100));
map.addBuilding(Building(5, "东苑食堂:干饭区2", 500, 80));
map.addBuilding(Building(6, "体育中心:锻炼区", 200, 20));
map.addBuilding(Building(7, "计算机学院楼:计算机专业学院实验地", 700, 700));
map.addBuilding(Building(8, "文理大楼:造型独特", 400, 400));
map.addBuilding(Building(9, "北苑:虾米餐馆多", 900, 900));
map.addBuilding(Building(10, "海韵湖:好湖", 1000, 1000));
// 添加道路
map.addRoad(Road(1, 2, 100));
map.addRoad(Road(2, 3, 100));
map.addRoad(Road(3, 4, 150));
map.addRoad(Road(4, 5, 100));
map.addRoad(Road(5, 6, 300));
map.addRoad(Road(6, 7, 250));
map.addRoad(Road(1, 6, 200));
map.addRoad(Road(2, 6, 100));
map.addRoad(Road(3, 5, 100));
map.addRoad(Road(2, 7, 300));
map.addRoad(Road(3, 8, 100));
map.addRoad(Road(4, 9, 500));
map.addRoad(Road(4, 10, 500));
map.addRoad(Road(8, 4, 200));
map.addRoad(Road(9, 10, 100));
这段代码是在创建 `SchoolMap` 对象时,向其中添加了若干个建筑物和道路,用于表示整个学校的地图信息。其中,建筑物通过 `addBuilding` 方法进行添加,其中传入了一个 `Building` 类型的参数,该类表示建筑物的信息,包括 ID、名称、坐标等信息。例如,第一行代码添加了 ID 为 1 的建筑物,名称为 "3号组图:学生住宿区",坐标为 (0,0)。道路通过 `addRoad` 方法进行添加,其中传入了一个 `Road` 类型的参数,该类表示道路的信息,包括起点 ID、终点 ID、距离等信息。例如,第二行代码添加了连接了 ID 为 1 和 2 的建筑物的道路,距离为 100 米。通过这些建筑物和道路的信息,就可以构建出整个学校的地图,并使用 `PathFinder` 类中的 `findPath` 方法来查询两个建筑物之间的最短路径。
5.4 运行结果及相关图