1.问题定义
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务,该系统能够为省级赛事管理解决以下问题:
(1)能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
(3)能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
(4)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
(5)赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
2.问题分析
赛事管理系统的实现需要涉及到多个方面,包括数据结构设计、搜索和排序算法的应用、用户交互和可视化等方面的问题。
在学习过程中,需要熟练掌握二叉排序树的基本原理和实现,选择合适的排序算法对查询结果进行排序,同时需要使用JavaFX等工具对系统界面进行设计。
这显然是一个图论问题,而且校园内道路一般是双向通行的,所以这是一个无向图。对 于图的存储结构而言,图中各个景点的存储结构有邻接表和邻接矩阵两种存储结构,考虑到 顶点个数少于 50 个,所以邻接表和邻接矩阵的复杂度相同。本题中选择使用邻接矩阵来表 示图。
任务中要求求解出图中景点的问路查询,即为给定两个源点,求解出两个顶点之间的最 短路径。根据数据结构课程所学知识,有多种经典算法可以解决最短路径问题,包括 Dijkstra 算法,Floyd-Warshell 算法,Bellman-Ford 算法和深度优先遍历。不同是算法有不同的算法复 杂度,考虑到校园中道路没有负权边,即算法均可解决最短路径问题。
其中 Dijkstra 算法求的是单源最短路径:即从一个结点出发到其它所有结点的最短路径, 算法的时间复杂度为 O(n 2 ),但题目要求任意两个结点的最短路径,所以还是要在外层增加 一个循环,以求得多源最短路径。
而 Floyd 算法求的是多源最短路径:即从任意结点出发到其它所有结点的最短路径,算 法的时间复杂度为 O(n 3 ),它可以一次性求得所有结点间的最短路径,且算法思想简单,便 于理解。所以我们这一项目采用 Floyd 算法来求解最短路径
系统主要包括以下几个组成部分:
数据结构设计:采用二叉排序树进行参赛队伍的基本信息存储,使用链式前向星存储导航图信息和路径信息;
界面设计:使用JavaFX等工具进行设计,包括各种输入框、下拉框,以及部分交互式地图展示等
二叉排序树实现参赛队伍的查找:使用二叉排序树实现快速查找,同时计算ASL;
排序算法实现学校(或赛事类别)查询:采用高效的排序算法对查询结果进行排序;
决赛叫号系统:采用队列模拟决赛叫号程序,实现按赛事类别叫号和模拟参赛队进场;
赛地校园导游:将校园地图导入系统,同时实现不少于10个目标地的导航。采用Floyd算法求解最短路径,同时提供地点信息和路径导航查询。
3.概要设计
一.参赛队伍管理模块:
- 提供添加参赛队伍信息的功能,包括参赛队编号、参赛作品名称、参赛学校、赛事类别、参赛者和指导老师。提供删除参赛队伍信息的功能,根据参赛队编号删除相应的队伍信息。提供修改参赛队伍信息的功能,根据参赛队编号选择要修改的队伍信息进行更新。
-
// 参赛队伍信息的结构体定义 struct team { int team_id; // 参赛队编号 string team_name; // 参赛作品名称 string school_name; // 参赛学校 int competition_type; // 赛事类别代码 string participants; // 参赛者 string instructor; // 指导老师 struct team* next; // 指向下一个结点的指针 }; // 创建一个新结点并初始化它 struct team* create_new_node(int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) { struct team* new_node = new team; new_node->team_id = team_id; new_node->team_name = team_name; new_node->school_name = school_name; new_node->competition_type = competition_type; new_node->participants = participants; new_node->instructor = instructor; new_node->next = NULL; return new_node; } // 添加一个新结点到链表中 void add_team(struct team** head, int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) { struct team* new_node = create_new_node(team_id, team_name, school_name, competition_type, participants, instructor); // 如果链表还没创建,直接把新结点设为头结点 if ((*head) == NULL) { (*head) = new_node; return; } // 找到链表尾部, 插入新的结点 struct team* temp = (*head); while (temp->next != NULL) { temp = temp->next; } temp->next = new_node; } // 根据参赛队编号删除参赛队伍信息 void delete_team(struct team** head, int team_id) { struct team* temp = (*head); struct team* prev = NULL; // 找到要删除的结点,如果找到,把 prev 指针指向 prev->next,跳过该结点 while (temp != NULL && temp->team_id != team_id) { prev = temp; temp = temp->next; } // 如果没找到要删除的结点,则直接返回 if (temp == NULL) { return; } // 删除头结点 if (prev == NULL) { (*head) = temp->next; free(temp); return; } // 删除非头结点 prev->next = temp->next; free(temp); }二.参赛队伍查找模块:
- 从team.txt文件中读取参赛队伍的基本信息,构建二叉排序树。提供按参赛队编号查找的功能,根据输入的编号在二叉排序树中进行查找,如果查找成功,输出基本信息和平均查找长度(ASL);否则输出"查找失败!"。
-
// 参赛队伍信息的结构体定义 struct team { int team_id; // 参赛队编号 string team_name; // 参赛作品名称 string school_name; // 参赛学校 int competition_type; // 赛事类别代码 string participants; // 参赛者 string instructor; // 指导老师 struct team* next; // 指向下一个结点的指针 }; // 创建一个新结点并初始化它 struct team* create_new_node(int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) { struct team* new_node = new team; new_node->team_id = team_id; new_node->team_name = team_name; new_node->school_name = school_name; new_node->competition_type = competition_type; new_node->participants = participants; new_node->instructor = instructor; new_node->next = NULL; return new_node; } // 添加一个新结点到链表中 void add_team(struct team** head, int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) { struct team* new_node = create_new_node(team_id, team_name, school_name, competition_type, participants, instructor); // 如果链表还没创建,直接把新结点设为头结点 if ((*head) == NULL) { (*head) = new_node; return; } // 找到链表尾部, 插入新的结点 struct team* temp = (*head); while (temp->next != NULL) { temp = temp->next; } temp->next = new_node; } // 根据参赛队编号删除参赛队伍信息 void delete_team(struct team** head, int team_id) { struct team* temp = (*head); struct team* prev = NULL; // 找到要删除的结点,如果找到,把 prev 指针指向 prev->next,跳过该结点 while (temp != NULL && temp->team_id != team_id) { prev = temp; temp = temp->next; } // 如果没找到要删除的结点,则直接返回 if (temp == NULL) { return; } // 删除头结点 if (prev == NULL) { (*head) = temp->next; free(temp); return; } // 删除非头结点 prev->next = temp->next; free(temp); } // 根据参赛队编号修改参赛队伍信息 void update_team(struct team** head, int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) { struct team* temp = (*head); // 找到要修改的结点,并修改它的信息 while (temp != NULL && temp->team_id != team_id) { temp = temp->next; } if (temp != NULL) { temp->team_name = team_name; temp->school_name = school_name; temp->competition_type = competition_type; temp->participants = participants; temp->instructor = instructor; } }
三.参赛团队查询模块:
提供按参赛学校查询参赛团队的功能,根据输入的学校名称查询参赛的所有团队的基本信息,并按赛事类别进行排序输出。
提供按赛事类别查询参赛团队的功能,根据输入的赛事类别查询参赛的所有团队的基本信息,并按赛事类别进行排序输出。选择排序算法进行排序,因为在数据规模较小的情况下,选择排序简单且性能良好。
//定义二叉排序树节点类
class BSTNode {
public:
string teamName;
string schoolName;
string race;
//其他基本信息
BSTNode* left;
BSTNode* right;
BSTNode(string teamName, string schoolName, string race){
this->teamName = teamName;
this->schoolName = schoolName;
this->race = race;
//初始化其他信息
left = nullptr;
right = nullptr;
}
};
//定义二叉排序树类
class BST {
public:
BSTNode* root;
BST(){
root = nullptr;
}
void insert(BSTNode* node){
if(root == nullptr){
root = node;
return;
}
BSTNode* curr = root;
while(true){
if(node->teamName < curr->teamName){
if(curr->left == nullptr){
curr->left = node;
break;
}
else
curr = curr->left;
}
else{
if(curr->right == nullptr){
curr->right = node;
break;
}
else
curr = curr->right;
}
}
}
};
//读取team.txt并构建二叉排序树
BST readTeamsFromFile(string fileName){
BST bst;
ifstream fin(fileName);
string line;
while(getline(fin, line)){
//解析line并创建节点
BSTNode* node = new BSTNode(teamName, schoolName, race);
bst.insert(node);
}
fin.close();
return bst;
}
//根据学校名称查询团队
vector<BSTNode*> searchBySchool(BSTNode* node, string schoolName){
vector<BSTNode*> result;
if(node == nullptr)
return result;
if(node->schoolName == schoolName){
result.push_back(node);
}
vector<BSTNode*> subResultLeft = searchBySchool(node->left, schoolName);
vector<BSTNode*> subResultRight = searchBySchool(node->right, schoolName);
result.insert(result.end(), subResultLeft.begin(), subResultLeft.end());
result.insert(result.end(), subResultRight.begin(), subResultRight.end());
return result;
}
//根据赛事类别查询团队
vector<BSTNode*> searchByRace(BSTNode* node, string race){
vector<BSTNode*> result;
if(node == nullptr)
return result;
if(node->race == race){
result.push_back(node);
}
vector<BSTNode*> subResultLeft = searchByRace(node->left, race);
vector<BSTNode*> subResultRight = searchByRace(node->right, race);
result.insert(result.end(), subResultLeft.begin(), subResultLeft.end());
result.insert(result.end(), subResultRight.begin(), subResultRight.end());
return result;
}
//按赛事类别对查询结果进行归并排序并输出
void mergeSortByRace(vector<BSTNode*>& teams){
//实现归并排序并输出结果
}
//调用函数查询并输出结果
int main(){
BST bst = readTeamsFromFile("team.txt");
//根据学校名称查询并输出
vector<BSTNode*> result1 = searchBySchool(bst.root, "清华大学");
mergeSortByRace(result1);
//根据赛事类别查询并输出
vector<BSTNode*> result2 = searchByRace(bst.root, "篮球");
mergeSortByRace(result2);
return 0;
}
四.决赛叫号系统模块:
将参赛队伍按赛事类别分配到9个决赛室,按顺序叫号进场。
模拟决赛叫号过程,显示各决赛室的参赛队进场情况,确保叫号顺序与进场秩序一致。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int N_GROUP = 9; // 参赛队伍分组数
const int N_PER_GROUP = 20; // 每个组参赛队伍数量
const int N_ROOM = 5; // 决赛室数量
int main()
{
// 定义队列,存储各个决赛室的参赛队编号
queue<int> rooms[N_ROOM];
for (int i = 0; i < N_GROUP; i++) {
// 将每个组的参赛队编号平均放入到对应的决赛室队列中
for (int j = 0; j < N_PER_GROUP / N_ROOM; j++) {
for (int k = 0; k < N_ROOM; k++) {
rooms[k].push(i * N_PER_GROUP / N_ROOM + j + 1);
}
}
}
// 循环叫号进场
for (int i = 0; i < N_PER_GROUP / N_ROOM; i++) {
for (int j = 0; j < N_ROOM; j++) {
// 取出当前决赛室队列队首的参赛队编号,进场
int teamNo = rooms[j].front();
cout << "第" << teamNo << "支参赛队伍进入第" << (j + 1) << "个决赛室" << endl;
// 将该参赛队伍放到队列尾,等待下一次轮流进场
rooms[j].pop();
rooms[j].push(teamNo);
}
// 等待当前比赛结束
cout << "等待当前比赛结束..." << endl;
// 此处可以模拟比赛耗时
}
return 0;
}
五.校园导游程序模块:
提供校园地图中不少于10个目标地的导航查询服务,参赛者可以查询任意两个目的地之间的最短路径,并查询任意目标地的相关信息。
使用合适的图数据结构表示校园地图,例如图的邻接表表示法。
使用迪杰斯特拉算法计算最短路径,根据参赛者输入的起始点和目标点,输出最短路径和相关信息。
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
int get_index(vector<string>& nodes, string target) {
for (int i = 0; i < nodes.size(); i++) {
if (nodes[i] == target) {
return i;
}
}
return -1;
}
void build_map(vector<string>& nodes, vector<vector<int>>& map) {
// 建立地点名称和编号之间的映射关系
int node_count = nodes.size();
vector<vector<int>> new_map(node_count, vector<int>(node_count, INT_MAX));
for (int i = 0; i < node_count; i++) {
new_map[i][i] = 0;
}
nodes.push_back("出口"); // 添加一个出口节点
new_map.push_back(vector<int>(node_count+1, INT_MAX));
for (int i = 0; i < node_count+1; i++) {
new_map[i][node_count] = new_map[node_count][i] = INT_MAX;
}
// 添加建筑物和路径数据,建立地图
new_map[0][1] = new_map[1][0] = 300; // 添加1号楼到2号楼的路径
new_map[0][3] = new_map[3][0] = 500; // 添加1号楼到3号楼的路径
new_map[1][4] = new_map[4][1] = 150; // 添加2号楼到4号楼的路径
new_map[2][6] = new_map[6][2] = 200; // 添加教学楼到6号楼的路径
new_map[3][4] = new_map[4][3] = 400; // 添加3号楼到4号楼的路径
new_map[4][6] = new_map[6][4] = 350; // 添加4号楼到6号楼的路径
new_map[4][7] = new_map[7][4] = 450; // 添加4号楼到7号楼的路径
new_map[5][6] = new_map[6][5] = 150; // 添加图书馆到6号楼的路径
new_map[5][7] = new_map[7][5] = 300; // 添加图书馆到7号楼的路径
// 更新原图
nodes = nodes; // 后续还需要添加其他建筑物
map = new_map;
}
void dijkstra(vector<string>& nodes, vector<vector<int>>& map, int start, int end) {
int node_count = nodes.size();
// 初始化距离数组
vector<int> dist(node_count, INT_MAX);
dist[start] = 0;
// 初始化访问数组
vector<bool> visited(node_count, false);
// 初始化路径数组
vector<int> path(node_count, -1);
// 依次访问每个节点
for (int i = 0; i < node_count; i++) {
// 从未访问节点中选取距离起点最近的节点
int min_dist = INT_MAX;
int current = -1;
for (int j = 0; j < node_count; j++) {
if (!visited[j] && dist[j] < min_dist) {
min_dist = dist[j];
current = j;
}
}
if (current == -1) {
break; // 所有节点均已访问过,退出循环
}
visited[current] = true; // 标记当前节点已访问
// 以当前节点为中心,更新所有邻接节点的距离
for (int k = 0; k < node_count; k++) {
if (map[current][k] != INT_MAX) {
int new_dist = dist[current] + map[current][k];
if (new_dist < dist[k]) {
dist[k] = new_dist; // 更新到起点的最短距离
path[k] = current; // 通过哪个节点到达当前节点
}
}
}
}
// 根据path数组回溯路径
if (path[end] != -1) {
cout << nodes[end] << "<-";
int p = path[end];
while (p != start) { // 注意判断循环结束条件
cout << nodes[p] << "<-";
p = path[p];
}
// 输出起点
cout << nodes[start] << endl;
}
// 继续输出路径上的节点
if (p == start) {
cout << nodes[start] << endl;
break;
}
cout << nodes[p] << "<-";
p = path[p];
}
// 输出最短距离
cout << "最短距离为:" << dist[end] << endl;
}
int main() {
vector<string> nodes = {"1号楼", "2号楼", "3号楼", "4号楼", "5号楼", "图书馆", "教学楼"};
vector<vector<int>> map;
build_map(nodes, map);
// 查询任意两个建筑物之间的最短路径
int start = get_index(nodes, "1号楼");
int end = get_index(nodes, "教学楼");
cout << "1号楼到教学楼的最短路径为:";
dijkstra(nodes, map, start, end);
start = get_index(nodes, "4号楼");
end = get_index(nodes, "5号楼");
cout << "4号楼到5号楼的最短路径为:";
dijkstra(nodes, map, start, end);
start = get_index(nodes, "图书馆");
end = get_index(nodes, "出口");
cout << "图书馆到出口的最短路径为:";
dijkstra(nodes, map, start, end);
return 0;
}
下面给出校园景点的无向带权图:



266

被折叠的 条评论
为什么被折叠?



