中国大学生计算机设计大赛省级赛事管理系统
中国大学生计算机设计大赛是我国高校面向本科生的计算机应用设计大赛,大赛旨在激发学生学习计算机知识和技能的兴趣与潜能,提高学生运用信息技术解决实际问题的综合能力。通过大赛这种计算机教学实践形式,可展示师生的教与学成果,最终以赛促学,以赛促教,以赛促创。该赛事在历届学生中影响力较大,参与者众多,请结合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项;包括增加、删除、修改参赛队伍的信息。
1.1 问题分析
对参赛队伍进行增删查改的操作,我的思路是创建一个Team类,把参赛队的信息作为类的成员变量,进行存储。同时使用二叉排序树将各个Team对象存储,同时编写对参赛队的增删查改操作。
1.2算法设计
参赛队信息的数据结构:类——Team 用于存储各个参赛队的详细信息,如编号、作品名称等,该类还提供了相应的get,set方法;
public class Team{
Integer number;//参赛队的编号
String workName;//参赛作品名称
String school;//参赛学校
String category;//赛事类别
String name;//参赛者
String teacherName;//指导老师
public Team(Integer number, String workName, String school, String category, String name, String teacherName) {
this.number = number;
this.workName = workName;
this.school = school;
this.category = category;
this.name = name;
this.teacherName = teacherName;
}
}
1.3 算法实现
增加:
public void add(Node node) {
if (root == null) {
root = node;
root.value = 1;
Teams.sum++;
Teams.count++;
} else root.add(node);
}
删除:
//删除节点
public void delNode(int num) {
if (root == null) {
System.out.println("队伍为空,无法删除");
return;
} else {
//1.先找到需要删除的节点
Node target = search(num);
if (target == null) {
System.out.println("所删除的队伍不存在");
return;
}
//如果二叉排序树只有一个节点
if (root.left == null && root.right == null) {
root = null;
return;
}
//去找target的父节点
Node parent = searchParent(num);
//如果要删除的节点是叶子节点
if (target.left == null && target.right == null) {
//判断是左子节点还是右子节点
if (parent.left != null && parent.left.team.number == num) {
parent.left = null;
} else if (parent.right != null && parent.right.team.number == num) {
parent.right = null;
}
//删除有两个子树的节点
} else if (target.left != null && target.right != null) {
Team minTeam = delRightTreeMin(target.right);
target.team = minTeam;
} else { //删除只有一个子树的节点
//如果要删除的结点有左子节点
if (target.left != null) {
if (parent != null) {
//如果target是parent的左子节点
if (parent.left.team.number == num) {
parent.left = target.left;
} else {
parent.right = target.left;
}
} else root = target.left;
} else { //如果要删除的节点有右子结点
if (target.left != null) {
if (parent.left.team.number == num) {
parent.left = target.right;
} else {
parent.right = target.right;
}
} else root = target.right;
}
}
}
}
修改:
//修改队伍信息 输入需要删除的队伍编号,修改信息
public void update(int num, String workName, String school, String category, String name, String teacherName) {
Node target = search(num);
if (target == null) {
System.out.println("需修改的队伍不存在");
return;
}
target.team.workName = workName;
target.team.school = school;
target.team.category = category;
target.team.name = name;
target.team.teacherName = teacherName;
}
查询:
//查找队伍
public Node search(int number) {
if (this.team.number == number) {
return this;
} else if (this.team.number > number) {
//如果左子树为空
if (this.left == null) {
return null;
}
return this.left.search(number);
} else {
if (this.right == null) {
return null;
}
return this.right.search(number);
}
}
1.4 实验结果
查询:
增加:
修改:
删除:
2 基于二叉排序树的查找
从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
2.1 问题分析
这个问题就是讲参赛队伍添加到一个二叉排序树中,树的节点Node的成员变量包含一个Team的成员。根据二叉排序树,左子节点的值小于根节点,右子节点的值大于根节点这一特性,可以很快地查询到参赛队伍的信息。而对ASL的计算。我的思路是在节点Node中添加一个value变量,每次添加节点的时候,由于是递归添加,子节点的value就等于根节点value+1,然后遍历节点将所有value相加除以节点个数就得到了ASL。而从team.xlsx中导入数据,我使用的是阿帕奇的OPI,对xlsx表格进行读取,从而将其创建,并加入二叉排序树中。
2.2 算法设计
二叉排序树的数据结构:类——Teams 用于存储每个Node节点。以及对二叉排序树修改的各种方法。
public class Teams {
//用于存储表格中的队伍
ArrayList<Team> teamList = new ArrayList<>();
//存储节点查询的次数总和
static int sum;
static int count;
private Node root;
}
节点的数据结构:类——Node 用于存储参赛队,以及他的左右子节点。以及对节点进行修改的方法,二叉排序树的许多类就是Node类中封装的。
class Node {
Team team;
Node left;
Node right;
int value;
public Node(Team team, Node left, Node right) {
this.team = team;
this.left = left;
this.right = right;
}
}
OPI对xlsx文件的读取:
FileInputStream fip = new FileInputStream("F:\\hb\\team.xlsx");
//获取工作簿
XSSFWorkbook wb = new XSSFWorkbook(fip);
//获取目标工作表
XSSFSheet sheet = wb.getSheetAt(0);
for (int i = 1; i < 399; i++) {
//获取目标行
XSSFRow row = sheet.getRow(i);
int num = (int) row.getCell(0).getNumericCellValue();
String workName = row.getCell(2).getStringCellValue();
String school = row.getCell(4).getStringCellValue();
String category = row.getCell(6).getStringCellValue();
String name = row.getCell(8).getStringCellValue();
String teacherName = row.getCell(10).getStringCellValue();
teamList.add(new Team(num, workName, school, category, name, teacherName));
2.3 算法实现
将xlsx文件中的队伍全都写入teamlist中,存储起来。
public void write() {
try {
//创建输入流
FileInputStream fip = new FileInputStream("F:\\hb\\team.xlsx");
//获取工作簿
XSSFWorkbook wb = new XSSFWorkbook(fip);
//获取目标工作表
XSSFSheet sheet = wb.getSheetAt(0);
for (int i = 1; i < 399; i++) {
//获取目标行
XSSFRow row = sheet.getRow(i);
int num = (int) row.getCell(0).getNumericCellValue();
String workName = row.getCell(2).getStringCellValue();
String school = row.getCell(4).getStringCellValue();
String category = row.getCell(6).getStringCellValue();
String name = row.getCell(8).getStringCellValue();
String teacherName = row.getCell(10).getStringCellValue();
teamList.add(new Team(num, workName, school, category, name, teacherName));
}
} catch (IOException e) {
throw new RuntimeException();
}
}
向二叉排序树中添加node节点,同时将队伍的value进行赋值,从而可以计算ASL平均查找路径。
public void add(Node node) {
if (root == null) {
root = node;
root.value = 1;
Teams.sum++;
Teams.count++;
} else root.add(node);
}
//增加队伍
public void add(Node node) {
if (node == null) return;
//判断传入的节点值,和当前子树的根节点的值进行对别
if (node.team.number < this.team.number) {
//如果左子为空直接加入,如果左子树不为空,那就递归到左子树
if (this.left == null) {
this.left = node;
node.value = this.value + 1;
Teams.sum += node.value;
Teams.count++;
} else {
this.left.add(node);
}
} else {
//右子树同理
if (this.right == null) {
this.right = node;
node.value = this.value + 1;
Teams.sum += node.value;
Teams.count++;
} else {
this.right.add(node);
}
}
计算asl平均查找路径。
//每次递归添加的时候都给node的value加一
//sum+=node.value
//count++;
public double getASL() {
// System.out.println("sum=" + sum);
//System.out.println("count=" + count);
return (double) sum / count;
}
2.4 实验结果
3 参赛团队查询
能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
3.1 问题分析
对于这个问题我的思路是,创建一个ArrayList添加相同的学校的参赛队信息,同时给list进行排序,从而可以按照参赛队的编号,按序输出。
3.2 算法设计
相同学校队伍的的数据结构:类——list 用于存储相同的学校的队伍。
ArrayList<Node> teams = new ArrayList<>();
对list进行排序,
teams.sort(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.team.number.compareTo(o2.team.number);
}
});
3.3 算法实现
//按照学校名称来查找
public void searchAsSchool(String schoolName) {
ArrayList<Node> teams = new ArrayList<>();
if (root != null) root.infixSearch(schoolName, teams);
else {
System.out.println("队伍为空");
return;
}
teams.sort(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.team.number.compareTo(o2.team.number);
}
});
for (Node team : teams) {
System.out.println(team);
}
}
3.4 实验结果
4 决赛叫号模拟
为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
4.1 问题分析
很明显这是一个关于多线程的问题,由于类别不止9个,我的想法是将398个队伍分入九个线程,重写线程的run方法,每个线程中有一个队列queue对象,可以做到先进先出,这样就能让叫号顺序和进场秩序一致。同时创建九个线程,并且调用start方法使得线程同时进行,模拟九个叫号室的情况。
4.2 算法设计
线程的数据结构:类——myThread,继承Thread类,重写run方法。类中包含队列queue以二叉排序,用于添加参赛队伍。
class MyThread extends Thread {
int start;
int end;
public Teams teams;
Queue queue;
public MyThread() {
}
}
重写run方法,进行多线程。
@Override
public void run() {
while (queue.peek() != null) {
System.out.println("请编号" + queue.peek().number + "队伍前往" + getName() + "进行比赛");
try {
currentThread().sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("编号" + queue.poll().number + "队伍比赛结束");
this.yield();
}
}
4.3 算法实现
public void create(String name,int start,int end) {
MyThread t = new MyThread(name,start,end);
t.start();
}
class MyThread extends Thread {
int start;
int end;
public Teams teams;
Queue queue;
public MyThread() {
}
public MyThread(String name, int start, int end) {
super(name);
teams = new Teams();
queue = new Queue(end-start);
this.start = start;
this.end = end;
for (int i = start; i < end; i++) {
queue.add(teams.teamList.get(i));
}
}
@Override
public void run() {
while (queue.peek() != null) {
System.out.println("请编号" + queue.peek().number + "队伍前往" + getName() + "进行比赛");
try {
currentThread().sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("编号" + queue.poll().number + "队伍比赛结束");
this.yield();
}
}
}
4.4 实验结果
5 校园导航
赛事系统为参赛者提供赛地的校园导游程序。为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供图中任意目标地(建筑物)的问路查询,即查询任意两个目的地(建筑物)之间的一条最短的简单路径。
1.1 问题分析
这显然是一个图论问题,而且校园内道路一般是双向通行的,所以这是一个无向图。对于图的存储结构而言,图中各个景点的存储结构有邻接表和邻接矩阵两种存储结构,本题中选择使用邻接矩阵来表示图。
任务中要求求解出图中景点的问路查询,即为给定两个源点,求解出两个顶点之间的最短路径。根据数据结构课程所学知识,有多种经典算法可以解决最短路径问题,包括Dijkstra算法,Floyd-Warshell算法,Bellman-Ford算法和深度优先遍历。这里我使用弗洛伊德算法进行操作。
1.2算法设计
结点信息的数据结构:类——Building 用于存储各个结点的详细信息,如景点编号、名称和景点介绍,该类还提供了相应的setter和getter。
class Building {
String name;
int code;
String description;
public Building() {
}
public Building(String name, int code, String description) {
this.name = name;
this.code = code;
this.description = description;
}
}
下面是图的数据结构:
public class Map {
static final int N = 1024;
private ArrayList<Building> vertexList;//存储定点集合
private int[][] edges;//存储对应的邻接矩阵
private int[][] pre;//保存到达目标顶点的前驱顶点
//显示图对应的矩阵
public void showGraph() {
for (int[] link : edges) {
System.out.println(Arrays.toString(link));
}
}
}
下面给出校园景点的无向带权图:
图1 校园景点的无向带权图
5.3 算法实现
根据编号来查询建筑的信息:
//根据code查找代码
public void showBuilding(int i) {
for (int j = 0; j < vertexList.size(); j++) {
if (vertexList.get(j).code == i) {
Building target = vertexList.get(j);
System.out.println(target);
return;
}
}
System.out.println("未找到该建筑");
}
列出所有的节点信息
public void list(){
vertexList.forEach(System.out::println);
}
弗洛伊德算法:
public void floyd() {
int len = 0;
for (int k = 0; k < edges.length; k++) {
for (int i = 0; i < edges.length; i++) {
for (int j = 0; j < edges.length; j++) {
len = edges[i][k] + edges[k][j];//从i出发 经过k 到达j顶点距离
if (len < edges[i][j]) {
edges[i][j] = len;
pre[i][j] = pre[k][j];//更新前驱结点
}
}
}
}
}