目录
一、问题定义
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务。
二、问题分析
【问题描述】
该系统能够为省级赛事管理解决以下问题:
(1)能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
(3)能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
(4)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
(5)赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
【设计要求】
1)赛事数据要求存入文件(txt或excel)并能读入查询;
2)赛地目的地查询,需提供目的地(建筑物)名称、代号、简介、两地之间路径长度等信息;
3)输入数据形式和范围:赛事相关数据可从键盘输入,或自文件导入。
4)界面要求:交互设计要合理,每个功能可以设计菜单,用户根据提示,完成相关功能的要求。
【实现步骤】
1)分析任务,进行模块划分。
2)定义数据结构,建议按照抽象数据类型的定义、表示和实现来描述,用类C语言(伪代码)来描述数据的存储表示和相应操作的具体实现过程。
3)设计合适的算法来实现其功能,并绘制函数调用关系图。
【测试数据】
要求使用全部合法数据,整体非法数据,局部非法数据。进行程序测试,以保证程序的健壮性。
三、概要设计
部分代码
#include<iostream>
#include <limits.h>
#include <fstream>
#include <string>
#include <queue>
using namespace std;
struct Team { //参赛队结构体
string teamid; //参赛队编号
string workname;//作品名称
string School;//参赛学校
string eventcategory;//赛事类别
string player;//参赛者
string teacher;//指导老师
};
struct Node { //节点结构体
Team data;
Node* left; //左节点
Node* right; //右节点
};
class BST {
private:
Node* c_Node(Team data) {
Node* n_Node = new Node();
n_Node->data = data;
n_Node->left = NULL;
n_Node->right = NULL;
return n_Node;
}
Node* insert_Node(Node* node, Team data) {
if (node == NULL) { // 若结点为空,将新结点作为根结点
node_number++; //结点数+1
return c_Node(data);
}
if (data.teamid < node->data.teamid) { // 如果新的节点编号小于当前结点,插到左子树
node->left = insert_Node(node->left, data);
}
else if (data.teamid > node->data.teamid) { // 如果新的结点编号小于当前结点,插到左子树
node->right = insert_Node(node->right, data);
}
return node;
}
/*基于二叉排序树的查找*/
Node* s_Node(Node* node, string tid, int& k) {
if (node == NULL) { // 若结点为空或没有找到,返回NULL
return NULL;
}
k++; // 查找次数加一
if (tid == node->data.teamid) { // 如果找到匹配的结点,返回该结点指针
return node;
}
if (tid < node->data.teamid) { // 若查找编号小于该结点编号,在左子树中查找
return s_Node(node->left, tid, k);
}
else { // 若查找编号大于该结点编号,在右子树中查找
return s_Node(node->right, tid, k);
}
}
/*中序遍历二叉排序树,输出所有的队伍信息*/
void in_order(Node* node) {
if (node == NULL) { // 如果结点是空的,则返回
return;
}
in_order(node->left); // 遍历左子树
printTeam(node->data); // 输出当前结点的队伍信息
cout << endl;
in_order(node->right); // 遍历右子树
}
public:
Node* root_node; // 根结点指针
int node_number; //结点数
int Search_time; // 查找次数
BST() { // 构造函數
root_node = NULL;
node_number = 0;
Search_time = 0;
}
~BST() { // 析构函数,释放所有动态分配的空间
delete_node(root_node); // 刪除二叉树中的所有结点
}
/*最小值结点*/
Node* min_Node(Node* node) {
if (node == NULL) return NULL; // 如果结点是空的,返回空
while (node->left != NULL) { // 若有左子树,继续寻找
node = node->left;
}
return node; // 返回最左边的结点
}
void delete_node(Node* node) {
if (node == NULL) {
return;
}
delete_node(node->left); // 刪除左子树
delete_node(node->right); // 刪除右子树
delete node; // 刪除当前结点
}
/*删除参赛队伍信息*/
Node* deleteteam(Node* root, string id) {
if (root == NULL) return root;
if (id < root->data.teamid) { // 如果要刪除的编号小于根结点的编号,在右子树返回删除
root->left = deleteteam(root->left, id);
}
else if (id > root->data.teamid) { // 如果要刪除的编号大于根结点的编号,在左子树返回删除
root->right = deleteteam(root->right, id);
}
else { //找到该结点
if (root->left == NULL && root->right == NULL) { // 该结点没有子结点,直接删除返回空
delete root;
return NULL;
}
else if (root->left == NULL) { // 该结点只有右子结点,替换成右子结点
Node* temp = root->right;
delete root;
return temp;
}
else if (root->right == NULL) { // 该结点只有左子结点,替换成左子结点
Node* temp = root->left;
delete root;
return temp;
}
else { // 若有两个结点,替换为左子树中最大值或右子树中最小值
Node* temp = min_Node(root->right); // 找到右子树的最小值
root->data = temp->data; // 最小值替换
root->right = deleteteam(root->right, temp->data.teamid); // 删除
}
}
return root; // 返回新的根结点
}
void insert(Team data) { //插入队伍信息
root_node = insert_Node(root_node, data);
}
Node* search(string id) { //查找该编号
int k = 0; // 记录查找次数
Node* node = s_Node(root_node, id, k);
Search_time += k; // 查找次数累加
return node; // 返回查找结点
}
/*平均查找长度ASL*/
double get_ASL() {
if (node_number == 0) { // 若树为空,返回0
return 0;
}
return (double)Search_time / node_number;
}
/*中序遍历二叉树*/
void in_order() {
in_order(root_node); //
}
/*添加队伍信息*/
void add_Team() {
Team s;
cout << "请输入参赛队编号: ";
cin >> s.teamid;
cout << "请输入参赛队作品名称: ";
cin >> s.workname;
cout << "请输入参赛队学校: ";
cin >> s.School;
cout << "请输入参赛类别: ";
cin >> s.eventcategory;
cout << "请输入参赛者: ";
cin >> s.player;
cout << "请输入指导老师: ";
cin >> s.teacher;
insert(s); //将输入的信息插入到新结点中
}
/*修改队伍信息*/
void modify_Team(string id) {
deleteteam(root_node, id); // 删除结点信息
Team s;
cout << "请输入参赛队编号: ";
cin >> s.teamid;
cout << "请输入参赛队作品名称: ";
cin >> s.workname;
cout << "请输入参赛队学校: ";
cin >> s.School;
cout << "请输入参赛类别: ";
cin >> s.eventcategory;
cout << "请输入参赛者: ";
cin >> s.player;
cout << "请输入指导老师: ";
cin >> s.teacher;
insert(s); // 插入到二叉树中
}
/*/找出学校的所有参赛队伍的信息*/
void printSchool(Node* root, string School) {
if (root == NULL) return; // 结点为空返回
printSchool(root->left, School); // 遍历左子树
if (root->data.School == School) { // 当前结点和输入学校相同,输出该结点的信息
printTeam(root->data);
cout << "********************************" << endl;
}
printSchool(root->right, School); // 遍历右子树
}
void printTeam(Team s) {
cout << "参赛队编号: " << s.teamid << endl;
cout << "参赛作品名称: " << s.workname << endl;
cout << "参赛队学校: " << s.School << endl;
cout << "参赛类型: " << s.eventcategory << endl;
cout << "参赛者: " << s.player << endl;
cout << "指导老师: " << s.teacher << endl;
}
/*将二叉树中的队伍分配到三个教室中,使用队列存储每个教室的队伍*/
void assignroom(Node* root, queue<Node*>& a1, queue<Node*>& a2, queue<Node*>& a3, queue<Node*>& a4, queue<Node*>& a5, queue<Node*>& a6, queue<Node*>& a7, queue<Node*>& a8, queue<Node*>& a9) {
if (root == NULL) return; //如果节点为空,直接返回
assignroom(root->left, a1, a2, a3, a4, a5, a6, a7, a8, a9); //分配左子树中的队伍
if (root->data.eventcategory == "大数据实践") {
a1.push(root);
}
else if (root->data.eventcategory == "信息图形设计") {
a2.push(root);
}
else if (root->data.eventcategory == "数据可视化") {
a3.push(root);
}
else if (root->data.eventcategory == "算法设计与应用") {
a4.push(root);
}
else if (root->data.eventcategory == "移动应用开发") {
a5.push(root);
}
else if (root->data.eventcategory == "医药卫生") {
a6.push(root);
}
else if (root->data.eventcategory == "数字生活") {
a7.push(root);
}
else if (root->data.eventcategory == "城市管理") {
a8.push(root);
}
else if (root->data.eventcategory == "行业应用") {
a9.push(root);
}
assignroom(root->right, a1, a2, a3, a3, a5, a6, a7, a8, a9); //分配右子树中的学生
}
/*模拟叫号系统*/
void Call(queue<Node*>& a1, queue<Node*>& a2, queue<Node*>& a3, queue<Node*>& a4, queue<Node*>& a5, queue<Node*>& a6, queue<Node*>& a7, queue<Node*>& a8, queue<Node*>& a9) {
cout << "**************************************" << endl; //打印分隔符
while (!a1.empty()) { //如果教室不为空,叫号并出队
Node* s = a1.front();
cout << "大数据实践叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入大数据实践教室比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a1.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a2.empty()) {
Node* s = a2.front();
cout << "信息图形设计叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入信息图形设计比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a2.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a3.empty()) {
Node* s = a3.front();
cout << "数据可视化叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入数据可视化比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a3.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a4.empty()) {
Node* s = a4.front();
cout << "算法设计与应用叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入算法设计与应用比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a4.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a5.empty()) {
Node* s = a5.front();
cout << "移动应用开发叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入移动应用开发比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a5.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a6.empty()) {
Node* s = a6.front();
cout << "医药卫生叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入医药卫生比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a6.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a7.empty()) {
Node* s = a7.front();
cout << "数字生活叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入数字生活比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a7.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a8.empty()) {
Node* s = a8.front();
cout << "城市管理叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入城市管理比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a8.pop();
}
cout << "**************************************" << endl; //打印分隔符
while (!a9.empty()) {
Node* s = a9.front();
cout << "行业应用叫号:" << s->data.teamid << " " << s->data.workname << endl;
cout << s->data.workname << "进入行业应用比赛" << endl;
cout << s->data.workname << "比赛结束" << endl;
a9.pop();
}
}
};
/*打印参赛信息*/
void printTeam(Team s) {
cout << "参赛队编号: " << s.teamid << endl;
cout << "参赛作品名称: " << s.workname << endl;
cout << "参赛队学校: " << s.School << endl;
cout << "参赛类别: " << s.eventcategory << endl;
cout << "参赛者: " << s.player << endl;
cout << "指导老师: " << s.teacher << endl;
}
/*读取信息,#为分隔*/
Team read(istream& in) {
Team s;
getline(in, s.teamid, '#');
getline(in, s.workname, '#');
getline(in, s.School, '#');
getline(in, s.eventcategory, '#');
getline(in, s.player, '#');
getline(in, s.teacher);
return s;
}
/*判断文件的结束*/
bool File(ifstream& file) {
char c = file.peek(); // 查看下一个字符
if (c == EOF) { // 若下一个字符为文件结束符,返回true
return true;
}
return false;
}
/*储存目的地信息*/
struct Destination {
string name; // 目的地名称
string code; // 目的地代号
string intro; // 目的地简介
};
const int INF = INT_MAX; //定义一个常量,表示无穷大
const int V = 10; //定义一个常量,表示图形中节点的个数
/*找到未被访问过的距离最小的节点*/
int min_Distance(int dist[], bool visited[]) {
int min = INF, min_index;
for (int v = 0; v < V; v++) {
if (visited[v] == false && dist[v] <= min) {
min = dist[v], min_index = v;
}
}
return min_index;
}
/*打印最短路径和长度*/
void print_Path(int parent[], int j, Destination destinations[]) {
if (parent[j] == -1) {
return;
}
print_Path(parent, parent[j], destinations);
cout << " -> " << destinations[j].code;
}
void printSolution(int dist[], int n, int parent[], int src, int dest, Destination destinations[]) {
cout << "Vertex\t\tDistance\tPath";
cout << "\n" << destinations[src].code << " -> " << destinations[dest].code << "\t\t" << dist[dest] << "\t\t" << destinations[src].code;
print_Path(parent, dest, destinations);
}
/*实现Dijkstra算法*/
void dijkstra(int graph[V][V], int src, int dest, Destination destinations[]) {
int dist[V]; // 储存源点到各个节点的距离
bool visited[V]; // 储存各个节点是否被访问过
int parent[V]; // 储存最短路径中各个节点的父节点
for (int i = 0; i < V; i++) {// 初始化各个数组
dist[i] = INF;
visited[i] = false;
parent[i] = -1;
}
dist[src] = 0;// 源点到自己的距离为0
for (int count = 0; count < V - 1; count++) {// 遍历所有节点
int u = min_Distance(dist, visited); // 找到未被访问过的距离最小的节点
visited[u] = true;// 标记该节点为已访问过
for (int v = 0; v < V; v++) { // 更新该节点相邻节点的距离和父节点
if (!visited[v] && graph[u][v] && dist[u] != INF && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
parent[v] = u;
}
}
}
printSolution(dist, V, parent, src, dest, destinations); // 打印最短路径和长度
}
int guide() {
int graph[V][V] = { // 一个二维数组,表示图形中各个节点之间的距离(权值)
{0, 144, 0, 0, 0, 0, 0, 148, 0},
{144, 0, 148, 0, 0, 0, 0, 151, 0},
{0, 148, 0, 147, 0, 144, 0, 0, 142},
{0, 0, 147, 0, 149, 154, 0, 0, 0},
{0, 0, 0, 149, 0, 150, 0, 0, 0},
{0, 0, 144, 154, 150, 0, 142, 0, 0},
{0, 0, 0, 0, 0, 142, 0, 141, 146},
{148, 151, 0, 0, 0, 0, 141, 0, 147},
{0, 0, 142, 0, 0, 0, 146,147, 0}
};
/*储存各个目的地的信息*/
Destination d[V] = { {"1号组团", "A", "学生宿舍"},
{"3号组团", "B", "学生宿舍"},
{"西苑食堂", "C", "食堂"},
{"笃学楼", "D", "教学楼"},
{"文理大楼", "E", "学校主楼"},
{"计算机学院", "F", "计算机学院办公楼"},
{"东苑食堂", "G", "食堂"},
{"体育馆", "H", "大学生活动中心"},
{"8号组团", "I", "学生宿舍"},
{"图书馆", "J", "图书馆和自习室"}
};
for (int i = 0; i < V; i++) {
cout << d[i].name << "(" << d[i].code << "):" << d[i].intro << endl;
}
char a, b;// 创建一个字符串变量,储存两个目的地代号
cout << "请输入目的地代号:"; // 提示两个目的地代号
cin >> a >> b;
int src = a - 'A';// 将输入的两个目的地代号转换为对应的节点编号
int dest = b - 'A';
if (src < 0 || src >= V || dest < 0 || dest >= V) {// 检查输入的两个目的地是否有效
cout << "输入错误,请重新输入!" << endl;
return -1;
}
if (src == dest) {// 检查输入的两个目的地是否相同
cout << "输入错误,请重新输入!" << endl;
return -1;
}
cout << "\n地点信息:" << endl;
cout << d[src].name << "(" << d[src].code << "):" << d[src].intro << endl;
cout << d[dest].name << "(" << d[dest].code << "):" << d[dest].intro << endl;
cout << "\n最短路径和长度是:" << endl;
dijkstra(graph, src, dest, d);//输出最短路径和长度
return 0;
}
void c_code() { //主程序代码
BST tree;
ifstream file;//导入参赛团队信息
file.open("team.txt");
while (!File(file)) { // 循环到文件结束
Team s = read(file); // 读取, 每一行作为一个队伍
tree.insert(s); // 将队伍信息插入二叉排序树
}
file.close();
int a;
string searchid, reviseid, deleteid, School1;
cout << "************1.添加参赛队伍信息************" << endl;
cout << "************2.修改参赛队伍信息************" << endl;
cout << "************3.删除参赛队伍信息************" << endl;
cout << "************4.查找参赛队伍信息************" << endl;
cout << "************5.查找学校参赛信息************" << endl;
cout << "************6.决赛叫号系统****************" << endl;
cout << "************7.校园导航系统****************" << endl;
cout << endl;
cout << "请选择你的操作:";
cin >> a;
switch (a)
{
case 1:
tree.add_Team();
cout << "添加成功!" << endl;
break;
case 2:
cout << "请输入你要修改的队伍编号:";
cin >> reviseid;
tree.modify_Team(reviseid);
cout << "修改成功!" << endl;
break;
case 3:
cout << "请输入你要删除的队伍编号:";
cin >> deleteid;
tree.deleteteam(tree.root_node, deleteid);
cout << "删除成功!" << endl;
break;
case 4:
while (true) { // 循环执行操作
cout << "请输入要查找的参赛队编号,或者输入0退出程序: ";
cin >> searchid;
if (searchid == "0") break; // 输入0跳出循环
Node* node = tree.search(searchid); // 在二叉树中搜索该编号对应的节点
if (node == NULL) { // 没有找到
cout << "查找失败" << endl;
}
else { // 输出队伍信息以及平均查找长度ASL
cout << "查找成功" << endl;
printTeam(node->data);
cout << "平均查找长度ASL: " << tree.get_ASL() << endl;
}
cout << endl;
}
cout << "退出查找" << endl;
break;
case 5:
cout << "请输入你要查找的学校名字:";
cin >> School1;
tree.printSchool(tree.root_node, School1);
break;
case 6:
{
queue<Node*>a1; queue<Node*>a2; queue<Node*>a3;
queue<Node*>a4; queue<Node*>a5; queue<Node*>a6;
queue<Node*>a7; queue<Node*>a8; queue<Node*>a9;
tree.assignroom(tree.root_node, a1, a2, a3, a4, a5, a6, a7, a8, a9);
cout << "**********开始模拟叫号系统**********" << endl;
tree.Call(a1, a2, a3, a4, a5, a6, a7, a8, a9);
break;
}
case 7:
guide();
break;
default:
break;
}
}
int main() {
c_code();
return 0;
}