数据结构课程设计
背景
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务,该系统能够为省级赛事管理解决以下问题:
信息登录
能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
信息查找
从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
详细内容
能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
赛事过程
为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
地图信息
赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
设计要求
- 赛事数据要求存入文件(txt或excel)并能读入查询;
- 赛地目的地查询,需提供目的地(建筑物)名称、代号、简介、两地之间路径长度等信息;
- 输入数据形式和范围:赛事相关数据可从键盘输入,或自文件导入。
- 界面要求:交互设计要合理,每个功能可以设计菜单,用户根据提示,完成相关功能的要求。
实现步骤提示
- 分析任务,进行模块划分。
- 定义数据结构,建议按照抽象数据类型的定义、表示和实现来描述,用类C语言(伪代码)来描述数据的存储表示和相应操作的具体实现过程。
- 设计合适的算法来实现其功能,并绘制函数调用关系图。
测试数据
要求使用全部合法数据,整体非法数据,局部非法数据。进行程序测试,以保证程序的健壮性。
基本构建
定义了一个名为Team的类,其中包含了6个私有属性:teamID、projectName、school、eventCategory、competitors和teacher。
class Team {
private String teamID;
private String projectName;
private String school;
private String eventCategory;
private String competitors;
private String teacher;
public Team(String ID, String project, String school, String category, String competitors, String teacher) {
this.teamID = ID;
this.projectName = project;
this.school = school;
this.eventCategory = category;
this.competitors = competitors;
this.teacher = teacher;
}
增加参赛队伍
private static void addTeam() {
Scanner input = new Scanner(System.in);
System.out.print("Enter team ID: ");
String teamID = input.nextLine();
System.out.print("Enter project name: ");
String projectName = input.nextLine();
System.out.print("Enter school: ");
String school = input.nextLine();
System.out.print("Enter event category: ");
String category = input.nextLine();
System.out.print("Enter competitors: ");
String competitors = input.nextLine();
System.out.print("Enter teacher: ");
String teacher = input.nextLine();
Team team = new Team(teamID, projectName, school, category, competitors, teacher);
teamList.add(team);
System.out.println(“Team added successfully.”);
}
删除参赛队伍
private static void deleteTeam() {
Scanner input = new Scanner(System.in);
System.out.print("Enter team ID: ");
String teamID = input.nextLine();
boolean found = false;
for (Team team : teamList) {
if (team.getTeamID().equals(teamID)) {
修改参赛队伍信息
teamList.remove(team);
found = true;
System.out.println(“Team deleted successfully.”);
break;
}
}
if (!found) {
System.out.println(“Team not found.”);
}
}
private static void updateTeam() {
Scanner input = new Scanner(System.in);
System.out.print("Enter team ID: ");
String teamID = input.nextLine();
boolean found = false;
for (Team team : teamList) {
if (team.getTeamID().equals(teamID)) {
System.out.println("Current project name: " + team.getProjectName());
System.out.print("Enter new project name (press ENTER to skip): ");
String projectName = input.nextLine();
if (!projectName.isEmpty()) {
team.setProjectName(projectName);
}
显示所有参赛队伍的赛事数据
private static void displayTeam() {
System.out.printf(“%-10s%-20s%-20s%-15s%-20s%-20s%n”, “Team ID”, “Project Name”, “School”, “Event Category”, “Competitors”, “Teacher”);
for (Team team : teamList) {
System.out.println(team);
}
}
public static void main(String… args) {
主函数,提供菜单选择和程序的控制流
Scanner input = new Scanner(System.in);
try {
do {
System.out.println(“\nEvent Management System Main Menu”);
System.out.println(“1. Add Team”);
System.out.println(“2. Delete Team”);
System.out.println(“3. Update Team”);
System.out.println(“4. Display All Teams”);
System.out.println(“5. Save and Exit”);
System.out.print("\nEnter your choice (1-5): ");
int choice = input.nextInt();
input.nextLine(); // consume the newline character
构建二叉排序树
构建一个二叉排序树,并将参赛队伍按照编号插入到该二叉排序树中。
具体实现如下:
public class TeamNode { // 参赛队伍节点类
private Team team;
private TeamNode left;
private TeamNode right;
public TeamNode(Team team) {
this.team = team;
}
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
public TeamNode getLeft() {
return left;
}
public void setLeft(TeamNode left) {
this.left = left;
}
public TeamNode getRight() {
return right;
}
public void setRight(TeamNode right) {
this.right = right;
}
}
public class BinarySearchTree { // 二叉排序树类
private TeamNode root;
public void insert(Team team) { // 将参赛队伍节点插入到二叉排序树中
TeamNode node = new TeamNode(team);
if (root == null) {
root = node;
} else {
TeamNode current = root;
while (true) {
if (team.getId() < current.getTeam().getId()) {
if (current.getLeft() == null) {
current.setLeft(node);
break;
} else {
current = current.getLeft();
}
} else {
if (current.getRight() == null) {
current.setRight(node);
break;
} else {
current = current.getRight();
}
}
}
}
}
}
定义一个 TeamNode 类表示参赛队伍节点,在其基础上构建了一个 BinarySearchTree 类表示二叉排序树。在 BinarySearchTree 类中,我们实现了 insert 方法将参赛队伍节点插入到二叉排序树中。
实现查找功能
接下来,我们实现根据参赛队编号在二叉排序树中查找对应参赛队伍的基本信息。具体实现如下:
public class BinarySearchTree {
// …
public TeamNode search(int teamId) { // 在二叉排序树中查找参赛队伍
TeamNode current = root;
int depth = 0; // 记录查找深度
while (current != null) {
depth++;
if (current.getTeam().getId() == teamId) {
System.out.println("参赛作品名称:" + current.getTeam().getName()); // 输出参赛作品名称
System.out.println("参赛学校:" + current.getTeam().getSchool()); // 输出参赛学校
System.out.println("赛事类别:" + current.getTeam().getCategory()); // 输出赛事类别
System.out.println("参赛者:" + current.getTeam().getMembers()); // 输出参赛者
System.out.println("指导老师:" + current.getTeam().getTeacher()); // 输出指导老师
System.out.println("查找成功!");
System.out.println("平均查找长度ASL:" + (double) depth / count); // 输出查找成功时的平均查找长度ASL
return current;
} else if (current.getTeam().getId() > teamId) {
current = current.getLeft();
} else {
current = current.getRight();
}
}
System.out.println("查找失败!");
return null;
}
}
定义 search 方法用于在二叉排序树中查找对应参赛队伍。在查找过程中,我们记录查找深度,并在查找成功时输出相应的参赛队伍基本信息和平均查找长度ASL。如果未找到对应参赛队伍,则输出“查找失败!”。
输出查找成功时的平均查找长度ASL
为了计算平均查找长度ASL,我们需要记录查找次数。在 BinarySearchTree 类中,我们添加了一个 count 属性表示查找次数:
private static Team insert(Team root, Team team, int level) {
if (root == null) {
team.setLevel(level + 1);
return team;
}
if (Integer.parseInt(team.getTeamId()) < Integer.parseInt(root.getTeamId())) {
root.setLeft(insert(root.getLeft(), team, level + 1), level + 1);
} else {
root.setRight(insert(root.getRight(), team, level + 1), level + 1);
}
return root;
}
private void setLevel(int level) {
this.level = level;
}
private static Team search(Team root, String teamId) {
if (root == null || root.getTeamId().equals(teamId)) {
return root;
}
if (Integer.parseInt(teamId) < Integer.parseInt(root.getTeamId())) {
return search(root.getLeft(), teamId);
} else {
return search(root.getRight(), teamId);
}
}
Stack stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node != null) {
// 访问根节点
levelSum += node.level;
// 累加查找过程中的层数
// 先将右子节点压入栈中,再将左子节点压入栈中,保证先遍历左子树
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
double aslSuccess = (double)levelSum / (double) this.nodeNum;
System.out.println("成功ASL =(double)levelSum / (double) nodeNum = "+aslSuccess);}
计算ASL的表达式为:ASL = (1 * n1 + 2 * n2 + 3 * n3 + … + k * nk) / N,其中ni表示查找长度为i的节点数,k表示最大查找长度,N表示总节点数。在查找过程中记录每个节点的查找长度,最后统计节点数和查找长度的乘积并除以总节点数即可得到ASL的值。
基本实现
参赛团队查询
可以将参赛学校名称作为键,将所有属于该学校的参赛团队作为值存储在列表中。然后,当用户输入要查询的学校名称时,我们可以在列表中查找该键对应的值,即该学校参赛的所有团队。在输出参赛团队时,我们需要按参赛队编号进行排序。选择排序是一种简单的排序算法,它的基本思想是每次从未排序的元素中选择最小的元素,放到已排序的元素末尾。选择排序的时间复杂度为O(n^2),因此它并不是一个高效的排序算法。但是,选择排序的实现简单,代码易于理解,因此在数据规模较小的情况下,选择排序是一种不错的选择。
选择排序:
searchAnsList.sort(new Comparator() {
@Override
public int compare(Team o1, Team o2) {
char[] e1 = new char[0];
char[] e2 = new char[0];
e1 = o1.getCategory().toCharArray();
e2 = o2.getCategory().toCharArray();
int len = e1.length;
int ret = -1;
if (e1.length > e2.length){
len = e2.length;
ret = 1;
}
for (int i = 0; i < len; i++) {
int i1 = e1[i];
int i2 = e2[i];
if (i1 == i2){
continue;
}else if (i1 > i2){
return 1;
}else {
return -1;
}
}
if (e1.length == e2.length){
return 0;
}
return ret;
}
决赛叫号系统
为了模拟决赛叫号系统,我们可以使用队列来管理参赛队伍的进场顺序。具体地,我们为每个决赛室都创建一个队列,每当一个参赛队伍被叫号时,就将其加入相应决赛室的队列中。然后,我们按照顺序遍历所有决赛室的队列,将队首的参赛队伍进场比赛。
基本分类
class Data{
public static final String[][] categoryList =
{
{“大数据实践”,“数据可视化”,“信息图形设计”,“交互信息设计”,“数据可视化”},
{“人工智能实践赛”,“计算机基础与应用类课程微课”,“虚拟实验平台”},
{“动态信息影像(MG动画)”,“动画”,“纪录片”,“数字短片”,“微电影”,“新媒体漫画”},
{“产品设计”,“环境设计”,“平面设计”,“交互媒体设计”,“游戏设计”,“虚拟现实VR与增强现实AR”},
{“Web应用与开发”,“管理信息系统”,“算法设计与应用”,“移动应用开发”,“移动应用开发(非游戏类)”},
{“医药卫生”,“数字生活”,“运动健身”,“城市管理”,“行业应用”},
{“汉语言文学”,“中、小学数学或自然科学课程微课”}
};
}
七个队列输出
public class Event {
public void run(List teamList) {
List<List> list = new ArrayList<>();
for (int i = 0; i < Data.categoryList.length; i++) {
list.add(new ArrayList<>());
}
for (Team team : teamList) {
String category = team.getCategory();
for (int i = 0; i < Data.categoryList.length; i++) {
for (int j = 0; j < Data.categoryList[i].length; j++) {
if (Data.categoryList[i][j].equals(category)){
list.get(i).add(team);
}
}
}
}
int time;
for (int i = 0; i < list.size(); i++) {
System.out.println(“--------------第”+(i+1)+“组进场模拟---------------”);
time = 3000000;
for (int j = 0; j < list.get(i).size(); j++) {
Team team = list.get(i).get(j);
System.out.print(“队号: “);
System.out.print(team.getNum());
System.out.print(”\t”);
System.out.print(team.getCategory());
System.out.print(“\t”);
System.out.print(team.getParticipants());
System.out.print(“\t准备进场\t”);
System.out.println(new SimpleDateFormat().format(new Date(time)).replace(“1970/1/1”,“”));
time+= 250000;
}
}
}
}
校园导游咨询
求解出两个顶点之间的最短路径。根据数据结构课程所学知识,有多种经典算法可以解决最短路径问题,包括Dijkstra算法,Floyd-Warshell算法,Bellman-Ford算法和深度优先遍历。不同是算法有不同的算法复杂度,考虑到校园中道路没有负权边,即算法均可解决最短路径问题。
我选择Dijkstra算法,即从一个结点出发到其它所有结点的最短路径,算法的时间复杂度为O(n2) ,在实现Dijkstra算法时,可以使用优先队列来维护未计算出最短路径的顶点。优先队列中的元素按照距离起点的距离排序,每次从队首取出距离起点最近的顶点进行计算。
初始化顶点常量
static {
nodeList[0] = new Node(1,"3号组团","学生宿舍");
nodeList[1] = new Node(2,"西苑食堂","学生食堂");
nodeList[2] = new Node(3,"明德楼","教学楼");
nodeList[3] = new Node(4,"文体中心","体育活动");
nodeList[4] = new Node(5,"西操场","体育活动");
nodeList[5] = new Node(6,"笃学楼","上课");
nodeList[6] = new Node(7,"东操场","体育活动");
nodeList[7] = new Node(8,"东苑食堂","学生食堂");
nodeList[8] = new Node(9,"图书馆","读书,自习");
nodeList[9] = new Node(10,"文理大楼","教学,办公综合大楼");
nodeList[10] = new Node(11,"计算机学院","学院楼");
nodeList[11] = new Node(12,"行政大楼","办公大楼");
for (int i = 0; i < graph.length; i++) {
for (int j = 0; j < graph[0].length; j++) {
//初始化为全不连通
graph[i][j] = MAX_INT;
}
}
linkEdge(0,1,110);
linkEdge(0,3,250);
linkEdge(1,3,200);
linkEdge(1,2,130);
linkEdge(1,4,120);
linkEdge(2,4,50);
linkEdge(2,9,100);
linkEdge(3,4,100);
linkEdge(3,6,210);
linkEdge(4,5,135);
linkEdge(4,9,100);
linkEdge(5,6,100);
linkEdge(5,7,190);
linkEdge(5,8,135);
linkEdge(5,9,120);
linkEdge(5,10,170);
linkEdge(6,7,125);
linkEdge(7,8,190);
linkEdge(8,11,210);
linkEdge(9,10,50);
linkEdge(10,11,250);
}
Dijkstra算法:
private void dijkstra(Integer source) {
HashMap<Integer, Way> s = new HashMap<>();
HashMap<Integer, Way> vs = new HashMap<>();
s.put(source,new Way(0,(source+1)+“->”));
for (int i = 0; i < vertexNum; i++) {
if(i == source){
continue;
}
vs.put(i,new Way(MapC.MAX_INT,“”));
}
校园无向图: