中国大学生计算机设计大赛省级赛事管理系统
中国大学生计算机设计大赛是我国高校面向本科生的计算机应用设计大赛,大赛旨在激发学生学习计算机知识和技能的兴趣与潜能,提高学生运用信息技术解决实际问题的综合能力。通过大赛这种计算机教学实践形式,可展示师生的教与学成果,最终以赛促学,以赛促教,以赛促创。该赛事在历届学生中影响力较大,参与者众多,请结合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 、参赛队伍管理
能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
这个信息管理是让我们建立一个结构体类型Info类型(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),然后读取team.txt文本,并实现对数据地增添、修改和删除。
建立一Info类型的数据,包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师等信息
struct Info
{
string id;
string works;
string school;
string category;
string student;
string coach;
};
//定义结构体类型Info
建立一个Info类型的容器来存放所有的信息
vector<Info> infos;
//建立一个Info类型的容器来储存文本信息
主要代码:
void file(vector<Info>& infos)//读取文件信息
void addteam(vector<Info>& infos)//添加队伍信息
{Info s;
cout << "请输入参赛队编号:";
cin >> s.id;
cout << "请输入参赛作品名称:";
cin >> s.works;
cout << "请输入参赛学校:";
cin >> s.school;
cout << "请输入赛事类别:";
cin >> s.category;
cout << "请输入参赛者:";
cin >> s.student;
cout << "请输入指导教师:";
cin >> s.coach;
infos.push_back(s);
cout << "已添加完成" << endl;
ofstream ofs("team.txt", ios::out | ios::trunc);
if (ofs.is_open()) {
for (const auto& info : infos) {
ofs << info.id << "#" << info.works << "#" << info.school
<< "#" << info.category << "#" << info.student
<< "#" << info.coach << endl;
}
ofs.close();
cout << "信息内容已更新" << endl;
}
else {
cout << "无法写入文件" << endl;
}}
void Delete(vector<Info>& infos)//删除队伍信息
{string filename = "team.txt";
ifstream file(filename);
string id;
cout << "请输入要删除的队伍编号:";
cin >> id;
cout << endl;
auto it = find_if(infos.begin(), infos.end(), [id](const Info& info) { return info.id == id; });
if (it != infos.end())
{
infos.erase(it);
}
else
cout << "您输入的编号有误!!" << endl;
ofstream ofs("team.txt", ios::out | ios::trunc);
if (ofs.is_open()) {
for (const auto& info : infos) {
ofs << info.id << "#" << info.works << "#" << info.school
<< "#" << info.category << "#" << info.student
<< "#" << info.coach << endl;
}
ofs.close();
cout << "信息内容已更新" << endl;
}
else {
cout << "无法写入文件" << endl;
}
void revise(vector<Info>& infos)//修改队伍信息
从team.txt中读取参赛队伍的基本信息,实现查找功能。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
数据结构:要实现从文件中读取数据并进行查找,我们可以使用vector作为容器来存储读取的信息,其中键为参赛队编号,值为参赛队的其他基本信息,然后我们可以通过查找键值来获取相应的值信息。
数据存储格式:我们需要考虑从文件中读取的数据格式,例如,文件中的每一行可能包含参赛队编号、参赛作品名称、参赛学校、赛事类别、参赛者和指导老师的信息,因此我们需要考虑如何适配这些信息并将其存储到vector容器中。
输入与输出:要实现查找功能,需要提示用户输入参赛队编号,并根据查找结果进行输出。当查找成功时,我们需要输出相关基本信息和平均查找长度(即遍历次数),否则输出“查找失败!”。
从team.txt文件中读取参赛队伍的基本信息,并将其存储在vector容器中,键为参赛队编号,值为一个结构体或其他类型的对象,存储参赛队的其他基本信息。
提示用户输入参赛队编号,通过键查找相应的值,并将其输出。
如果查找成功,计算平均查找长度ASL,即查找过程中的遍历次数,可通过记录每次遍历的次数并除以总查找次数得到,输出结果。
如果查找失败,输出“查找失败!
void file(vector<Info>& infos)//读取文件信息
{
ifstream file("team.txt");
if (!file.is_open())
{
// 判断文件是否打开成功
cout << "File open error!" << endl;
}
string line;
Info info;
getline(file, line);
while (getline(file, line))
{
line.erase(std::remove_if(line.begin(), line.end(),
[](unsigned char c) { return std::isspace(c); }),
line.end());
string field;
stringstream line_ss(line);
int field_index = 0;
while (getline(line_ss, field, '#'))
{
switch (field_index)
{
case 0:
info.id = field;
break;
case 1:
info.works = field;
break;
case 2:
info.school = field;
break;
case 3:
info.category = field;
break;
case 4:
info.student = field;
break;
case 5:
info.coach = field;
break;
default:
break;
}
field_index++;
}
infos.push_back(info);
}
file.close();
}
void NumFind(vector<Info>& infos)//根据参赛队伍编号查询队伍信息
{
cout << "请输入你需要查找的编号:" << endl;
string id;
cin >> id;
int search_count = 0;
auto it = find_if(infos.begin(), infos.end(), [id, &search_count](const Info& info) {
search_count++; // 统计比较次数
return info.id == id;
});
if (it != infos.end()) {
cout << it->id << " " << it->works << " " << it->school << " " << it->category << " " << it->student << " " << it->coach << " " << endl;
}
else {
cout << "您输入的编号有误!!" << endl;
}
double avg_search_len = static_cast<double>(search_count) / infos.size();
cout << "平均查找长度:" << avg_search_len << endl;
}
能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
数据结构:对于按参赛学校查询的情况,我们可以使用一个vector容器存储符合条件的参赛团队的指针,对于按赛事类别查询的情况,我们可以使用一个map容器存储符合条件的参赛团队。
数据存储格式:在从team.txt文件中读取数据时,我们需要根据输入提示选择读取参赛学校或者赛事类别的信息,并将其存储在对应容器中。在存储时,可以将参赛团队存储为一个结构体或其他类型的对象,存储其基本信息以及所对应的参赛学校或赛事类别等信息。
查找与排序:对于按参赛学校查询,我们需要遍历完整容器来寻找符合条件的参赛团队;对于按赛事类别查询,我们可以直接使用map容器根据键值查找符合条件的参赛团队,以提高查询效率。对于排序,我们可以选择各种排序算法,例如选择排序、插入排序、希尔排序、归并排序、堆排序等,其中选择排序和插入排序适合小规模数据,而希尔排序和归并排序适合中等数据规模,堆排序适合大规模数据。我们可以根据实际情况选择排序算法,并输出排序算法选择的原因。
输出结果:我们按赛事类别有序输出结果,可以使用排序算法将参赛团队按赛事类别排序后输出结果。
错误处理:需要考虑各种可能发生的错误情况并进行相应的异常处理,例如无法打开或读取文件、用户输入非法的参赛学校名称或赛事类别等情况需提示用户并进行相应的处理。
提示用户输入参赛学校名称(或赛事类别),遍历vector容器,查找该学校所有参赛团队的基本信息。
如果查找成功,将所有查找到的参赛团队的基本信息保存在一个新的vector容器中,并按赛事类别排序输出,输出格式与问题描述中要求的格式相同。使用选择排序(Selection Sort)。
如果查找失败,输出“查找失败!”。
void NameFind(vector <Info>& infos)
{
vector<Info> sum;
cout << "请输入你需要查找的学校参赛情况:" << endl;
string school;
cin >> school;
vector<Info> target_Infos;
copy_if(infos.begin(), infos.end(), back_inserter(target_Infos), [=](const Info& s) {
return s.school == school;
});
if (target_Infos.empty()) {
cout << "目前未检测到该学校有学生参赛" << endl;
}
else {
sort(target_Infos.begin(), target_Infos.end(), [](const Info& a, const Info& b) {
return a.id < b.id; // 按队伍编号从小到大排序
});
for (const auto& info : target_Infos) {
cout << info.id << '\t' << info.works << '\t' << info.school << '\t'
<< info.category << '\t' << info.student << '\t' << info.coach << endl;
}
}
}
为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
参赛队信息的存储:我们需要读取赛事组织文件中的参赛队信息,并将其存储在程序中。可以将参赛队信息存储为一个结构体或其他类型的对象,存储队伍名称、赛事类别等信息。
决赛室的模拟:为了模拟各决赛室的叫号和进场情况,我们可以使用一个队列来存储待叫号的参赛队的信息。对于每个决赛室,我们可以使用一个标记(如bool类型)来表示当前状态,即是否有队伍在进行比赛。
叫号顺序和进场秩序的模拟:可以使用循环来实现叫号和进场的模拟。每次循环,我们按顺序遍历每个决赛室,从待叫号的参赛队队列中取出一个队伍并进行叫号,如果当前决赛室没有参赛队在进行比赛,则进入场地进行比赛。进入场地后,当前决赛室的状态标记改为true,表示有队伍在进行比赛。当比赛结束后,该队伍离开场地,状态标记改为false,下一个等待进场的队伍才能进入。
读取赛事组织文件中的参赛队信息,将其按照赛事类别分到各个决赛室的待叫号队列中。
定义一个当前比赛进行状态的数组,初始化为false。
循环遍历每个决赛室(从1到9),每次处理一个决赛室。
如果当前决赛室的待叫号队列为空,跳过这个决赛室,继续处理下一个决赛室。
如果当前决赛室的待叫号队列不为空,取出队首队伍,进行叫号,并将其从待叫号队列中删除。
如果当前决赛室没有队伍在进行比赛,则将当前队伍进入场地,修改当前决赛室的比赛进行状态为true。
如果当前决赛室有队伍在进行比赛,则等待直到比赛结束,即等到比赛时长结束后。
比赛结束后,将当前队伍离开场地,修改当前决赛室的比赛进行状态为false。
重复执行步骤3到步骤8,直到所有决赛室的比赛都结束。
bool operator<(const Info& lhs, const Info& rhs) {
return lhs.category < rhs.category || (lhs.category == rhs.category && lhs.id < rhs.id);
}
// 将每个参赛队按照 category 分配到相应的决赛室中
#include <chrono>
#include <thread>
struct Room {
int num;
std::vector<Info> queue;
Room(int n) : num(n) {}
};
void radom(vector<Info>& infos)
{
const int num_rooms = 9;
std::vector<Room> rooms(num_rooms, Room(0));
map<std::string, int> category_to_room;
for (const auto& info : infos) {
auto it = category_to_room.find(info.category);
if (it == category_to_room.end()) {
// 如果此类别的决赛室尚未分配,分配给下一个编号的决赛室
it = category_to_room.emplace(info.category, category_to_room.size() % num_rooms).first;
}
int room_num = it->second;
rooms[room_num].queue.push_back(info);
}
// 对于每个决赛室,按照参赛队的顺序叫号,进入比赛场地
for (auto& room : rooms) {
auto& queue = room.queue;
int i = 1;
while (!queue.empty()) {
auto& info = queue.front();
cout << "决赛室 " << i << " - 队伍 " << info.id << " " << info.student << " " << info.category << " 正在比赛." << endl;
// 假设比赛时间为随机、常数的一段时间
std::this_thread::sleep_for(std::chrono::milliseconds(100 + (rand() % 100)));
cout << "决赛室 " << i << " - 队伍 " << info.id << " " << info.student << " " << info.category << " 比赛结束." << endl;
// 移除参赛队伍,接受下一个参赛队的比赛
queue.erase(queue.begin());
i++;
}
}
}
赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
需要提供不少于10个目标地 (建筑物) 的导航服务,以方便参赛者快速了解各个目标地在校园中的位置和基本信息,例如目标地的建筑名称、用途、周边环境等。
可以考虑利用校园地图等资源,将目标地点和参赛者当前位置进行匹配,并提供路径规划服务,以便参赛者尽快到达目标地点。
需要提供任意两个目标地 (建筑物) 之间的路径导航查询服务,以方便参赛者找到最近和最短的路径,例如从某个建筑物到达比赛场地。
可以考虑利用已有的地图数据和路径规划算法,计算出最短路径,并提供参赛者可视化的路径规划结果,例如地图上的路径线和关键节点指引。同时,需要实现多个目标地点之间的切换和导航的连续性.
收集和整理目标地点 (建筑物) 的地理位置和基本信息,例如建筑名称、用途、开放时间等等,存储到数据库中。
利用地图数据,获取参赛者当前的地理位置信息,并将其与目标地点信息匹配。
比对匹配结果后,利用路径规划算法计算出从起点到终点的最短路径,并展示给参赛者。同时,提供相关的建筑物信息供参赛者查看。
开始时提示参赛者输入起点和终点的目标地点 (建筑物) 名称或编号信息。
利用数据库中已有的建筑物位置数据,匹配起点和终点的地理位置,并计算出最短路径。可采用已有的路径规划算法,Dijkstra算法。
struct Edge
{
int to; // 表示该边连接的节点
int weight; // 表示该边的权重
};
const int MAX_N = 10; // 最大节点数
int dist[MAX_N]; // 存储起点到每个节点的距离
bool visited[MAX_N]; // 标记每个节点是否被访问过
int prevNode[MAX_N]; // 存储每个节点的前驱节点
vector<vector<Edge>> graph;
// 添加边的函数,这里默认无向图每个边的权重都为1
void addEdge(int from, int to, int s)
{
graph[from].push_back({ to, s });
graph[to].push_back({ from, s }); // 无向图要加上反向边
}
void buildmap()//创建无向图(地图)
void dijkstra(int start)//寻找最短路径
void printload(int start, int end)
{// 使用栈存储路径
stack<int> s;
int currentNode = end;
int distance = 0; // 初始化距离为0
// 从终点回溯到起点,将每个节点的前驱节点存入栈中
while (currentNode != start)
{
int prev = prevNode[currentNode];
for (auto edge : graph[prev]) {
if (edge.to == currentNode) {
distance += edge.weight;
// 累加每条边的权重
break;
}
}
s.push(currentNode);
currentNode = prev;
}
s.push(start);
// 输出路径和距离
cout << "从节点" << start << "到节点" << end << "的最短路径是:";
while (!s.empty())
{
cout << s.top();
if (s.top() != end)
{
cout << " -> ";
}
s.pop();
}
cout << ",距离为:" << distance << endl; // 输出距离
}
void guide()//实验导航系统