一、设计内容
中国大学生计算机设计大赛是我国高校面向本科生的计算机应用设计大赛,大赛旨在激发学生学习计算机知识和技能的兴趣与潜能,提高学生运用信息技术解决实际问题的综合能力。通过大赛这种计算机教学实践形式,可展示师生的教与学成果,最终以赛促学,以赛促教,以赛促创。该赛事在历届学生中影响力较大,参与者众多,请结合2021届省赛参赛的数据,借助数据结构课程所学的相关知识,通过对数据的处理和分析,熟悉数据结构设计及数据处理在信息管理系统中应用的重要性。赛事相关数据存储在文本文件和excel文件中,相应的文件信息说明如表1所示。其中,各个文件中不同的数据项之间均使用#分隔,图1中给出了文件team.txt中参赛信息的对应数据示例。
图1. 参赛队基本信息
【问题描述】
本次课程设计要求协助中国大学生计算机设计大赛江苏省组委会,设计一款赛事管理系统,实现赛务相关的数据管理及信息服务,该系统能够为省级赛事管理解决以下问题:
(1)能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
(2)从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
(3)能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
(4)为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
(5)赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
二、设计要求
1)赛事数据要求存入文件(txt或excel)并能读入查询;
2)赛地目的地查询,需提供目的地(建筑物)名称、代号、简介、两地之间路径长度等信息;
3)输入数据形式和范围:赛事相关数据可从键盘输入,或自文件导入。
4)界面要求:交互设计要合理,每个功能可以设计菜单,用户根据提示,完成相关功能的要求。
三、实验过程
首先确定我们需要的头文件:
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <bits/stdc++.h>
定义参赛队伍结构体:
//节点
typedef struct TeamNode {
int number; //参赛队伍编号
string name; //作品名称
string school; //参赛队伍学校
string category; //赛事类别
string contestant; //参赛者
string teacher; //指导老师
struct TeamNode *lchild; //左孩子
struct TeamNode *rchild; //右孩子
} TeamNode;
定义地点节点结构体:
//地点节点
struct Site {
char name[10]; //地点名称
char introduce[100]; //地点介绍
} s[12];
需要用到的功能函数:
void Menu(); //菜单
void ReadTeams(); //读取参赛队伍信息
void AddTeam(); //增添参赛队伍信息
void DeleteTeam(); //删除参赛队伍信息
void EditTeam(); //修改参赛队伍信息
void Lookup_num(); //按参赛队编号查找
void Lookup_sch(); //按参赛学校查询
void Call(); //决赛叫号模拟
void Guide(); //校园导游服务
void PrintTeams(); //打印
void Save(); //保存
void Remove(string &s); //去除字符串中的空格
void Insert(TeamNode **T, int number, string name, string school, string category, string contestant,
string teacher); //插入二叉排序树
TeamNode *SearchParent(TeamNode *T, int number); //寻找父节点
TeamNode *Search(TeamNode *T, int number); //基于二叉排序树的查找
void Inorder(TeamNode *T); //中序遍历二叉排序树
void Inorder2(TeamNode *T, string school); //中序遍历二叉排序树进行有条件的输出
int Totalweight(TeamNode *T, int d); //计算总权值
int Node_num(TeamNode *T); // 计算总结点数
void Inorder3(TeamNode *T, ofstream &ofs); //中序遍历二叉排序树将数据写入文件
void Inorder4(TeamNode *T, int *t, int &index); //遍历生成存储参赛队编号的数组,并返回指向该数组的指针
void Siteslist(); //地点列表
void Store(); //存储景点信息、同时存图,各边信息
void Dijkstra(int v0, int t); //迪杰斯特拉求最短路径,并输出路线
将以上以上代码放入我们的头文件“ManageSystem.h”中,完整的头文件“ManageSystem.h”内容如下:
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <bits/stdc++.h>
#define inf 99999999
using namespace std;
int road_map[20][20], book[20], dis[20];
//节点
typedef struct TeamNode {
int number; //参赛队伍编号
string name; //作品名称
string school; //参赛队伍学校
string category; //赛事类别
string contestant; //参赛者
string teacher; //指导老师
struct TeamNode *lchild; //左孩子
struct TeamNode *rchild; //右孩子
} TeamNode;
TeamNode *T = NULL;
//地点节点
struct Site {
char name[10]; //地点名称
char introduce[100]; //地点介绍
} s[12];
void Menu(); //菜单
void ReadTeams(); //读取参赛队伍信息
void AddTeam(); //增添参赛队伍信息
void DeleteTeam(); //删除参赛队伍信息
void EditTeam(); //修改参赛队伍信息
void Lookup_num(); //按参赛队编号查找
void Lookup_sch(); //按参赛学校查询
void Call(); //决赛叫号模拟
void Guide(); //校园导游服务
void PrintTeams(); //打印
void Save(); //保存
void Remove(string &s); //去除字符串中的空格
void Insert(TeamNode **T, int number, string name, string school, string category, string contestant,
string teacher); //插入二叉排序树
TeamNode *SearchParent(TeamNode *T, int number); //寻找父节点
TeamNode *Search(TeamNode *T, int number); //基于二叉排序树的查找
void Inorder(TeamNode *T); //中序遍历二叉排序树
void Inorder2(TeamNode *T, string school); //中序遍历二叉排序树进行有条件的输出
int Totalweight(TeamNode *T, int d); //计算总权值
int Node_num(TeamNode *T); // 计算总结点数
void Inorder3(TeamNode *T, ofstream &ofs); //中序遍历二叉排序树将数据写入文件
void Inorder4(TeamNode *T, int *t, int &index); //遍历生成存储参赛队编号的数组,并返回指向该数组的指针
void Siteslist(); //地点列表
void Store(); //存储景点信息、同时存图,各边信息
void Dijkstra(int v0, int t); //迪杰斯特拉求最短路径,并输出路线
本系统是基于二叉排序树实现的参赛队伍信息管理系统。程序提供了多个功能,包括增加、删除、修改、查找等操作,并使用了文件存储来实现持久化存储。其中,插入和查找操作使用了递归函数进行实现,详细的遍历算法也得到了充分考虑。此外,还提供了校园导游服务和Dijkstra算法求最短路径的功能。程序考虑了复杂的图论算法,并优化了存储结构以提高效率。总的来说,本程序稳定性较高,具有比较完善的功能和良好的用户体验。
1.用户菜单界面
简单通过使用printf函数及‘\t’实现我们的菜单界面。
void Menu() {
printf("*****************************************\n");
printf("*\t欢迎使用赛事管理系统v1.0\t*\n");
printf("*****************************************\n");
printf("*\t请选择功能列表\t\t\t*\n");
printf("*****************************************\n");
printf("*\t1.读取参赛队伍信息\t\t*\n");
printf("*\t2.增添参赛队伍信息\t\t*\n");
printf("*\t3.删除参赛队伍信息\t\t*\n");
printf("*\t4.修改参赛队伍信息\t\t*\n");
printf("*\t5.按参赛队编号查找\t\t*\n");
printf("*\t6.按参赛学校查询\t\t*\n");
printf("*\t7.决赛叫号模拟\t\t\t*\n");
printf("*\t8.校园导游服务\t\t\t*\n");
printf("*\t9.保存\t\t\t\t*\n");
printf("*\tp.打印参赛队伍信息\t\t*\n");
printf("*\t0.退出系统\t\t\t*\n");
printf("*****************************************\n");
}
运行结果如图所示:
2.读取参赛队伍信息:
该程序目的是实现从team.txt文件中读取信息。其中,函数ReadTeams()是读取参赛队伍信息的核心函数,它能够从文本文件中读取每个参赛队伍的相关信息,并将其插入到二叉排序树中。以下是该函数的具体实现过程:
打开文件 首先,这段代码使用ifstream打开名为“team.txt”的文本文件。如果打开失败,则输出失败提示信息并返回。
逐行读取文件内容 然后,该函数使用getline()函数逐行读取文件内容,将每个参赛队伍的信息存储在一个string变量str中。
去除字符串中的空格和制表符 在读取每一行之前,函数Remove()会将字符串中的空格和制表符去除。这样可以确保正确读取每个字段的值。
提取每个字段的值并插入到二叉排序树中 接下来,该函数对每一行分别使用循环进行遍历,通过找到“#”字符将该行信息分成6个字段。然后,将各个字段的值(如参赛队伍编号、名称、所属学校等)分别存储在相应的变量中。
最后,调用Insert()函数,将该参赛队伍的信息以二叉排序树节点的形式插入到二叉排序树中。该节点的结构为TeamNode,其中包含参赛队伍编号、名称、所属学校、类别、参赛者、指导老师等信息。
输出读取成功信息并清空控制台屏幕读取文件完成后,函数输出“读取成功”的提示信息,并等待用户按下任意键继续下一步操作。同时,还会清空控制台屏幕,以便用户能够更清晰地看到每个功能的执行结果。
代码如下:
//去除字符串中的空格
void Remove(string &s) {
string::size_type index = 0;
if (!s.empty()) {
while ((index = s.find('\t', index)) != string::npos) { // 进行转行 ,不然无法清除
s.erase(index, 1);
}
}
}
//插入二叉排序树
void Insert(TeamNode **T, int number, string name, string school, string category, string contestant, string teacher) {
if (*T == NULL) {
*T = new TeamNode();
(*T)->number = number;
(*T)->name = name;
(*T)->school = school;
(*T)->category = category;
(*T)->contestant = contestant;
(*T)->teacher = teacher;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
} else if (number < (*T)->number) {
Insert(&((*T)->lchild), number, name, school, category, contestant, teacher);
} else if (number > (*T)->number) {
Insert(&((*T)->rchild), number, name, school, category, contestant, teacher);
}
}
//读取参赛队伍信息
void ReadTeams() {
//打开文件
ifstream fp;
fp.open("team.txt");
if (!fp) {
printf("打开文件失败!\n");
return;
}
//读文件
string str;
int number;
string name, school, category, contestant, teacher;
while (getline(fp, str)) {
if (str[0] != '2')
continue;
Remove(str);
for (int i = 0; i < 6; i++) {
int position = str.find('#');
string s = str.substr(0, position);
str = str.substr(position + 1);
switch (i) {
case 0:
number = stoi(s);
break;
case 1:
name = s;
break;
case 2:
school = s;
break;
case 3:
category = s;
break;
case 4:
contestant = s;
break;
case 5:
teacher = s;
break;
default:
break;
}
}
Insert(&T, number, name, school, category, contestant, teacher);
}
fp.close();
printf("读取成功!\n");
system("pause");
system("cls");
}
3.增添参赛队伍信息
核心函数为AddTeam(),其功能是增添一个参赛队伍到已有的参赛队伍信息列表中。该函数由以下几个步骤组成:
定义需要填写的参赛队伍信息变量 该函数在一开始定义了6个变量,分别代表参赛队伍编号、作品名称、学校、赛事类别、参赛者和指导老师的姓名。这些变量用于存储用户通过控制台输入的相应参赛队伍信息。
提示用户输入相关信息 该函数随后将逐条提醒用户输入参赛队伍的各项信息,包括参赛队编号、作品名称、学校、赛事类别、参赛者姓名和指导老师姓名等。
将信息插入到参赛队伍信息列表中 当用户输入完所有信息以后,Insert()函数会被调用,将用户刚输入的信息以二叉排序树节点的形式插入到参赛队伍信息列表中。插入完成后,该函数输出“参赛队信息增添完成”的提示信息。
等待并清空控制台屏幕 最后,该函数使用system()函数先暂停并等待用户任意键,然后再通过system("cls")清空控制台窗口,以便用户可以更清晰地看到后续执行的操作结果。
代码如下:
//增添参赛队伍信息
void AddTeam() {
int number; //参赛队伍编号
string name; //作品名称
string school; //参赛队伍学校
string category;//赛事类别
string contestant;//参赛者
string teacher; //指导老师
printf("请输入参赛队编号:\n");
cin >> number;
printf("请输入参赛作品名称:\n");
cin >> name;
printf("请输入参赛学校:\n");
cin >> school;
printf("请输入赛事类别:\n");
cin >> category;
printf("请输入参赛者姓名:\n");
cin >> contestant;
printf("请输入指导老师姓名:\n");
cin >> teacher;
Insert(&T, number, name, school, category, contestant, teacher);
printf("参赛队信息增添完成.\n");
system("pause");
system("cls");
}
3.删除参赛队伍信息
这段代码实现了一个参赛队伍信息的删除功能,包含了寻找父节点函数 SearchParent 和删除函数 Delete。具体流程如下:
首先在定义工作指针 P 的情况下,通过 while 循环扫描二叉搜索树 T 直到找到待删除的节点 P 或 P 不存在;如果没有找到则提示结点不存在。
当找到待删除的节点 P 后,有三种情况需要考虑:
- 当该结点是叶子结点时,直接删除(将其父节点的左/右孩子置空);
- 当该结点有一个左孩子或者一个右孩子时,让其孩子结点代替它的位置;
- 当左右孩子都存在时找中序遍历的上一个结点(即最大值的前继或最小值的后继)代替其位置。
第三种情况需要执行以下步骤:
- 定义工作指针 Q 指向 P 的左孩子;
- 找到 Q 的右子树中的最大值,并记录 Pre2 为最终遍历到的结点 Q 的前序结点,用于后期连接;
- 将 Q 的值复制到 P 中,并将 Q 节点删除。
最后,定义一个函数 DeleteTeam() 实现用户输入需要删除的参赛队编号,调用函数 Delete 进行删除操作,并输出删除成功信息。其中,cin 和 cout 分别实现用户输入和输出信息。系统函数 system("pause") 和 system("cls") 分别实现暂停并等待用户按任意键继续和清屏操作。
代码如下:
//寻找父节点
TeamNode *SearchParent(TeamNode *T, int number) {
if (T) {
if (T->lchild->number == number || T->rchild->number) {
return T;
} else if (number < T->number) {
return SearchParent(T->lchild, number);
} else {
return SearchParent(T->rchild, number);
}
} else {
return NULL;
}
}
void Delete(TeamNode *T, int number) {
//首先找到要删除的结点
TeamNode *Pre;
TeamNode *P = T; //定义工作指针
while (P != NULL && number != P->number) {
if (number > P->number) {
Pre = P;
P = P->rchild;
} else {
Pre = P;
P = P->lchild;
}
}
if (P == NULL) {
printf("要删除的结点不存在.\n" );
} else {
// 当该结点是叶子结点时,直接删除
if (P->lchild == NULL && P->rchild == NULL) {
if (P->number > Pre->number) {
Pre->rchild = NULL;
} else {
Pre->lchild = NULL;
}
}
//当该结点有一个左孩子或者一个右孩子时,让其孩子结点代替他的位置
if ((P->lchild != NULL && P->rchild == NULL) || (P->rchild != NULL && P->lchild == NULL)) {
if (P->number > Pre->number) {
if (P->lchild != NULL) {
Pre->rchild = P->lchild;
} else {
Pre->rchild = P->rchild;
}
}
if (P->number < Pre->number) {
if (P->lchild != NULL) {
Pre->lchild = P->lchild;
} else {
Pre->lchild = P->rchild;
}
}
}
//当左右孩子都存在时找中序遍历的上一个结点代替其位置
if (P->lchild != NULL && P->rchild != NULL) {
TeamNode *Q = P->lchild; //工作指针
TeamNode *Pre2; //用于记录最终遍历到的结点的前序结点,用于后期连接
while (Q->rchild) {
Pre2 = Q;
Q = Q->rchild;
}
P->number = Q->number;
P->name = Q->name;
P->school = Q->school;
P->category = Q->category;
P->contestant = Q->contestant;
P->teacher = Q->teacher;
if (P != Pre2) {
Pre2->rchild = Q->lchild;
} else {
Pre2->lchild = Q->lchild;
}
delete (Q);
}
}
}
//删除参赛队伍信息
void DeleteTeam() {
int number;
printf("请输入需要删除的参赛队编号:\n");
cin >> number;
Delete(T, number);
printf("该参赛队信息已删除.\n");
system("pause");
system("cls");
}
4.修改参赛队伍信息
核心函数为EditTeam(),其实现了一个参赛队伍信息的修改功能。主要流程如下:
- 首先,定义一个整型变量 number,需要用户输入参赛队编号;
- 调用 Search(T, number) 函数查找编号为 number 的参赛队,并将其返回值赋给指针变量 p。如果没查找到,则输出错误提示信息"查找不到此参赛队,请重试."。
- 如果查找到了,则输出提示信息让用户输入新的参赛队信息,并更新该节点的数据成员 name、school、category、contestant 和 teacher。最后输出一个提示"参赛队信息修改完成."。
- 在查找函数 Search(T, number) 中,参数 T 为根节点指针,number 为待查找的参赛队编号。在搜索过程中,若 T 为空,则说明未找到目标节点,返回 NULL。否则,比较待查找的编号 number 与当前节点 T 的编号大小,若相等,则返回当前节点;若 number 小于 T 的编号,则在左子树中继续查找;否则在右子树中继续查找。
- 在执行完修改操作后,通过 system("pause") 暂停并等待用户按任意键继续,通过 system("cls") 清屏操作来显示修改结果。
代码如下:
//修改参赛队伍信息
void EditTeam() {
int number;
string name; //作品名称
string school; //参赛队伍学校
string category;//赛事类别
string contestant;//参赛者
string teacher; //指导老师
printf("请输入需要修改信息的参赛队编号:\n");
cin >> number;
TeamNode *p = Search(T, number);
if (p) {
printf("请输入新信息\n");
printf("参赛作品名称:\n");
cin >> name;
printf("参赛学校:\n");
cin >> school;
printf("赛事类别:\n");
cin >> category;
printf("参赛者姓名:\n");
cin >> contestant;
printf("指导老师姓名:\n");
cin >> teacher;
p->name = name;
p->school = school;
p->category = category;
p->contestant = contestant;
p->teacher = teacher;
printf("参赛队信息修改完成.\n");
} else {
printf("查找不到此参赛队,请重试.\n");
}
system("pause");
system("cls");
}
//基于二叉排序树的查找
TeamNode *Search(TeamNode *T, int number) {
if (T) {
if (T->number == number) {
return T;
} else if (number < T->number) {
return Search(T->lchild, number);
} else {
return Search(T->rchild, number);
}
} else {
return NULL;
}
}
5.按参赛队编号查找
首先,定义了二叉排序树的节点数据结构 TeamNode,包括名称 name、编号 number、所属学校 school、赛事类别 category、参赛者 contestant 和指导老师 teacher 等信息。 数据结构TeamTree代表整棵二叉排序树,包含根节点指针head。
然后,定义了函数 Search(T, number),用于在二叉排序树中按照编号 number 查找对应的节点,并返回该节点的指针。具体实现是利用递归的方法,从根节点 T 开始,若当前节点为空,则返回 NULL;若相等就返回该节点;若待查找编号小于当前节点编号,则递归查找左子树;否则递归查找右子树。
接下来,定义了Totalweight(T, d) 和 Node_num(T) 两个函数,分别用于计算二叉排序树的总权值和总结点数。其中 Totalweight(T, d) 函数通过递归计算树的深度和每个节点的权值之和,以求得总权值;而Node_num(T) 函数通过递归遍历树的所有节点数,以求得总结点数。这两个函数可以用于后续统计和分析。
最后,定义了 Lookup_num() 函数,实现了按编号查找参赛队伍信息的功能。函数中,首先让用户输入待查找的参赛队编号 number,然后调用上述的 Search(T, number) 函数查找对应节点,并输出该节点的相关信息。
此外,Lookup_num() 函数还统计了二叉排序树的总权值和总结点数,并输出平均搜索长度ASL作为性能指标。最后,通过 system("pause") 暂停程序执行并等待用户按下任意键继续执行,然后通过 system("cls") 命令清空屏幕内容,以便于显示查找结果。
代码如下:
//基于二叉排序树的查找
TeamNode *Search(TeamNode *T, int number) {
if (T) {
if (T->number == number) {
return T;
} else if (number < T->number) {
return Search(T->lchild, number);
} else {
return Search(T->rchild, number);
}
} else {
return NULL;
}
}
//计算总权值
int Totalweight(TeamNode *T, int d) {
d++;
int a = d;
if (T->lchild)
d += Totalweight(T->lchild, a);
if (T->rchild)
d += Totalweight(T->rchild, a);
return d;
}
// 计算总结点数
int Node_num(TeamNode *T) {
if (T == NULL) { // 空节点判断
return 0;
}
int d = 1 + Node_num(T->lchild) + Node_num(T->rchild);
return d;
}
//按参赛队编号查找
void Lookup_num() {
int number, d1, d2;
printf("请输入需要查找的参赛队编号:\n");
cin >> number;
TeamNode *p = Search(T, number);
if (p != NULL) {
printf("参赛队编号:%d\n参赛作品名称:%s\n参赛学校:%s\n赛事类别:%s\n参赛者:%s\n指导老师:%s\n", p->number, p->name.c_str(),
p->school.c_str(), p->category.c_str(),
p->contestant.c_str(), p->teacher.c_str());
d1 = Totalweight(T, 0);
d2 = Node_num(T);
cout << "ASL=" << d1 << '/' << d2 << '=' << d1 * 1.0 / d2 << endl;
} else {
printf("查找失败!\n");
}
system("pause");
system("cls");
}
6.按参赛学校查询
按参赛学校查询的功能,使用中序遍历二叉排序树,在满足指定学校名的条件下输出符合要求的队伍信息。
具体来说,Inorder2(T, school) 函数实现了在二叉排序树 T 中,对所有节点进行中序遍历,并按照指定条件进行输出。函数首先通过递归方法遍历左子树,然后判断当前节点的所属学校是否与参数 school 相等,如果相等就将本节点的信息输出,否则忽略本节点。最后再通过递归方法遍历右子树。需要注意的是,由于我们采用了 C++ 里的 string 类型存储学校名称,因此需要使用 c_str() 方法将其转换成 char* 类型才能直接输出。
Lookup_sch() 函数本身很简单,它向用户输入待查询的参赛学校名,然后调用 Inorder2(T, school) 函数对所有符合条件的节点进行输出。最后,程序通过 system("pause") 和 system("cls") 命令暂停和清空屏幕,使用户能够看到查询结果。
代码如下:
//中序遍历二叉排序树进行有条件的输出
void Inorder2(TeamNode *T, string school) {
if (T) {
Inorder2(T->lchild, school);
if (school == T->school) {
printf("%d # %s # %s # %s # %s # %s\n", T->number, T->name.c_str(), T->school.c_str(), T->category.c_str(),
T->contestant.c_str(), T->teacher.c_str());
}
Inorder2(T->rchild, school);
}
}
//按参赛学校查询
void Lookup_sch() {
string school;
printf("请输入查询的参赛学校:\n");
cin >> school;
system("cls");
printf("参赛队编号 # 参赛作品名称 # 参赛学校 # 赛事类别 # 参赛者 # 指导老师\n");
Inorder2(T, school);
printf("查找完毕.\n");
system("pause");
system("cls");
}
7.打印输出
Inorder(T) 函数通过递归实现对二叉排序树 T 进行中序遍历。首先递归遍历左子树,并将其所有节点输出;然后输出本节点;再递归遍历右子树,并将其所有节点输出。由于本题所要处理的二叉排序树是按照参赛队编号从小到大排序的,因此该函数实际上就是按照编号从小到大地输出队伍信息。
PrintTeams() 函数的作用是输出前文中构建的二叉排序树中保存的所有队伍信息。它会调用 Inorder(T) 函数对整棵树进行中序遍历,并输出所有节点的内容。打印时会在每个项目之间加入制表符 "\t" 和换行符 "\n",以便于排版。最后,程序通过 system("pause") 和 system("cls") 命令暂停和清空屏幕,使用户能够看到完整的输出结果。
代码如下:
//中序遍历二叉排序树
void Inorder(TeamNode *T) {
if (T) {
Inorder(T->lchild);
printf("%d # %s # %s # %s # %s # %s\n", T->number, T->name.c_str(), T->school.c_str(), T->category.c_str(),
T->contestant.c_str(), T->teacher.c_str());
Inorder(T->rchild);
}
}
//打印
void PrintTeams() {
system("cls");
printf("参赛队编号\t参赛作品名称\t参赛学校\t赛事类别\t参赛者\t\t指导老师\n");
Inorder(T);
system("pause");
system("cls");
}
8.保存信息
Inorder3(T, ofs) 函数实现了对二叉排序树 T 进行中序遍历,并将节点信息保存到指定的文件流 ofs 中。其实现方式与 Inorder(T) 函数类似,只是输出内容的格式变成了用流对象进行输出。
Save() 函数的作用是将前文中构建的二叉排序树中保存的所有队伍信息写入到文件 "team.txt" 中。它首先会清空文件内容,然后使用 ofstream 打开并关闭同名文件,以确保文件存在。再次打开同名文件时需要以 ios::app 模式打开,以向文件追加新的内容。接着通过 Inorder3(T, i) 函数将整棵树的信息逐一保存到文件流 i 中。最后,程序通过 system("pause") 和 system("cls") 命令暂停和清空屏幕,使用户能够看到保存结果。需要注意的是,在调用 ofstream 的 open 方法时应该先检查相关文件是否打开成功,避免出现无法打开文件的错误。
代码如下:
//中序遍历二叉排序树将数据写入文件
void Inorder3(TeamNode *T, ofstream &ofs) {
if (T) {
Inorder3(T->lchild, ofs);
ofs << T->number << " # "
<< T->name << " # "
<< T->school << " # "
<< T->category << " # "
<< T->contestant << " # "
<< T->teacher << '\n';
Inorder3(T->rchild, ofs);
}
}
//保存
void Save() {
//清空文件内容
ofstream ofs("team.txt");
if (!ofs) {
printf("打开文件失败!\n");
return;
}
ofs << "参赛队编号 # 参赛作品名称 # 参赛学校 # 赛事类别 # 参赛者 # 指导老师\n";
ofs.close();
//重新加载数据到文件中
ofstream i("team.txt", ios::app);
if (!i) {
printf("打开文件失败!\n");
return;
}
Inorder3(T, i);
i.close();
printf("保存成功!\n");
system("pause");
system("cls");
}
9.模拟叫号系统
该代码段实现了一个名为 Call() 的函数用于模拟叫号系统,即依次输出所有参赛队伍的编号以及将其分配到8个决赛室中。
Inorder4(T, t, index) 函数用于遍历二叉排序树 T,并将存储队伍编号的数组 t 进行初始化。它采用了与 Inorder3(T, ofs) 函数类似的方式进行中序遍历,并使用参数 index 来记录已经存放的索引值,并在每经过一个节点时将其编号存放到数组 t 中。
Call() 函数首先清空屏幕和设置一些初值。然后定义一个大小为1000的动态整型数组t来存储所有队伍的编号。节点数通过 Node_num(T) 函数进行获取。接着,调用 Inorder4(T, t, index) 将所有队伍的编号复制到数组 t 中。最后,在 while 循环中对每个数组元素依次输出到不同的决赛室(共9个,索引从0-8),并在每个队伍编号之间加上对应的“决赛室”信息。这里采取了特判的方法输出,当已经输出完某个决赛室的队伍后,若还有剩余队伍,则跳转至下一个决赛室继续输出。如果当前时间的分钟小于等于8,则分钟加1;否则分钟归零,小时加1,然后输出下一个队伍。最后暂停程序并清空屏幕。
代码如下:
//遍历生成存储参赛队编号的数组,并返回指向该数组的指针
void Inorder4(TeamNode *T, int *t, int &index) {
if (T) {
Inorder4(T->lchild, t, index);
t[index++] = T->number;
Inorder4(T->rchild, t, index);
}
}
//模拟叫号系统
void Call() {
system("cls");
int index = 0;
int h = 8, m = 0, k = 0, i = 0;
int *t = new int[1000];
int d = Node_num(T);
Inorder4(T, t, index);
while (i < d) {
printf("%d:%d%d\n", h, k, m);
printf("1号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("2号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("3号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("4号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("5号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("6号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("7号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("8号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("9号决赛室:%d\n", t[i++]);
if (i == d)
break;
if (m < 9)
m++;
if (m == 9) {
m = 0;
if (k < 5)
k++;
if (k == 5) {
k = 0;
h++;
}
}
}
delete []t;
printf("模拟完成.\n");
system("pause");
system("cls");
}
10.校园导游服务
函数
Guide()
实现了一个校园导游服务。该函数首先展示了景点信息,然后让用户选择功能。当用户选择第一项时,程序要求用户输入一个地点编号,然后输出该地点名称和介绍等信息,并结束执行;当用户选择第二项时,程序要求用户输入起点和终点景点编号,接着使用 Dijkstra 算法求解从起点到终点的最短路径,并输出路径和距离。对于 Dijkstra 算法,它是一种基于贪心策略实现的单源最短路径算法。在代码中,Dijkstra 函数首先初始化距离数组
dis
和节点数组p
。其中,dis[i]
表示从起点到节点 i 的当前最短路径长度,p[i]
记录节点 i 在最短路径中的前一节点。这两个数组都有初始值,其中dis[v0]
的初始值为 0,表示起点到自己的距离为 0,其余部分都初始化为邻接矩阵中相应的距离,即road_map[v0][i]
。接下来,Dijkstra 函数使用一个 bool 类型的标记数组
book
来记录每个节点是否被遍历过。开始时,只有起点被标记,其他节点都未被标记。然后,Dijkstra 函数进入一个循环,在每次循环中,首先找到离起点最近的未标记节点 u,将其标记,并更新与 u 相邻的节点 v 的距离和前向星数组 p。若满足dis[v] > dis[u] + road_map[u][v]
,即从起点到 u 再到 v 的距离小于当前记录的起点到 v 的距离,就更新dis[v]
为更小值,并且将节点 u 记录为节点 v 在最短路径中的前一节点。最后,在 Dijkstra 函数中,使用前向星数组 p 和节点数组 s 来输出完整的最短路径,以及路径长度。具体地,首先由终点开始,按照前向星的方向逐个存储在一个中间节点数组
l
中,然后逆序输出,即得到完整的最短路径。同时,根据节点数组 s,输出每个节点的名称。
代码如下:
//地点列表
void Siteslist() {
printf("*****************************************\n");
printf("*\t\t地点列表\t\t*\n");
printf("*****************************************\n");
printf("*\t\t<1>三号组团\t\t*\n");
printf("*\t\t<2>西苑食堂\t\t*\n");
printf("*\t\t<3>明德园\t\t*\n");
printf("*\t\t<4>文体中心\t\t*\n");
printf("*\t\t<5>西田径场\t\t*\n");
printf("*\t\t<6>文理大楼\t\t*\n");
printf("*\t\t<7>北苑食堂\t\t*\n");
printf("*\t\t<8>求索园\t\t*\n");
printf("*\t\t<9>东苑食堂\t\t*\n");
printf("*\t\t<10>图书馆\t\t*\n");
printf("*****************************************\n");
}
//存储景点信息、同时存图,各边信息
void Store() {
int i, j;
strcpy(s[1].name, "三号组团");
strcpy(s[1].introduce, "三号组团是学生住宿楼,其中住着计算机学院的学子,他们都无比优秀。");
strcpy(s[2].name, "西苑食堂");
strcpy(s[2].introduce, "西苑食堂是一个供应学校师生员工用餐的场所,提供各种美食和饮品,是大家日常用餐的主要场所之一。");
strcpy(s[3].name, "明德园");
strcpy(s[3].introduce, "明德园环境优美、景色宜人,是人们度过悠闲时光、放松身心的理想场所。");
strcpy(s[4].name, "文体中心");
strcpy(s[4].introduce,
"文体中心是一个综合性场所,它不仅能促进社区文化教育和艺术交流、发展体育锻炼和竞技运动,同时也能让人们更好地享受文化和娱乐生活,为学生们打造出一处智慧、和谐、幸福、美好的生活环境。");
strcpy(s[5].name, "西田径场");
strcpy(s[5].introduce,
"田径场是一种专门用于田径运动比赛和训练的设施。通常由跑道、标准的跳远、三级跳、撑杆跳、铅球、铁饼、标枪、链球等各种田径比赛项目的比赛区域和其他辅助设施组成。");
strcpy(s[6].name, "文理大楼");
strcpy(s[6].introduce, "文理大楼作为江苏科技大学长山校区第一高楼,共21层,高103米,是江科大的标志性建筑。");
strcpy(s[7].name, "北苑食堂");
strcpy(s[7].introduce,
"北苑食堂提供多样化的饮食选择,如米饭、面条、馒头、馄饨、炒菜、烧烤等,同时还提供不同口味的汤、饮料和甜点等。");
strcpy(s[8].name, "求索园");
strcpy(s[8].introduce, "求索园是学生们接近自然、放松身心、锻炼身体的重要场所之一。");
strcpy(s[9].name, "东苑食堂");
strcpy(s[9].introduce,
"东苑食堂注重菜品的营养搭配和健康指导,通过设置标识或提供专门的食品展示区来提示餐品的营养成分,以便师生员工做出更加合理的用餐选择。");
strcpy(s[10].name, "图书馆");
strcpy(s[10].introduce,
"图书馆是一种公共设施,提供各种类型的书籍、期刊和其他资料,供读者借阅或在馆内阅读。图书馆也提供其他服务,如电子资源显微镜报告、计算机终端、学习空间和教育课程。");
for (i = 1; i <= 12; i++) {
for (j = 1; j <= 12; j++) {
if (i == j)
road_map[i][j] = 0;
else
road_map[i][j] = inf;
}
}
road_map[1][2] = 100;
road_map[1][4] = 200;
road_map[2][1] = 100;
road_map[2][3] = 80;
road_map[2][4] = 150;
road_map[3][2] = 80;
road_map[3][5] = 120;
road_map[3][6] = 110;
road_map[4][1] = 200;
road_map[4][2] = 150;
road_map[4][5] = 50;
road_map[5][3] = 120;
road_map[5][4] = 50;
road_map[5][8] = 150;
road_map[5][9] = 230;
road_map[6][3] = 110;
road_map[6][7] = 80;
road_map[6][8] = 60;
road_map[7][6] = 80;
road_map[7][10] = 100;
road_map[8][5] = 150;
road_map[8][6] = 60;
road_map[8][9] = 90;
road_map[8][10] = 70;
road_map[9][5] = 230;
road_map[9][8] = 90;
road_map[9][10] = 50;
road_map[10][7] = 100;
road_map[10][8] = 70;
road_map[10][9] = 50;
}
//迪杰斯特拉求最短路径,并输出路线
void Dijkstra(int v0, int t) {
int min, i, j, u, v;
int p[20], l[20];
memset(p, -1, sizeof(p)); //p 数组用于记录每个节点在最短路径中的前一节点
memset(l, 0, sizeof(l)); //l 数组用于存储最短路径的中间节点
memset(book, 0, sizeof(book)); //book 数组表示该节点是否被遍历过(0 表示未标记,1 表示已标记)
for (i = 1; i <= 10; i++) {
dis[i] = road_map[v0][i];
if (dis[i] < inf) //v0能直接到达,即上一站点为v0
p[i] = v0;
}
book[v0] = 1;
for (i = 1; i < 10; i++) {
min = inf;
for (j = 1; j <= 10; j++) { //每次找出距离v0最近点
if (book[j] == 0 && dis[j] < min) {
min = dis[j];
u = j;
}
}
book[u] = 1; //标记该点
for (v = 1; v <= 10; v++) {
if (book[v] == 0 && dis[v] > dis[u] + road_map[u][v]) { //通过最近点更新其他边
p[v] = u; //存储更新的边,即为路线
dis[v] = dis[u] + road_map[u][v];
}
}
}
v = t;
i = 1;
while (p[v] != v0) { //将路线存入栈中,正序输出
l[i++] = p[v];
v = p[v];
}
printf("\n");
u = i - 1;
printf("路线为:\n");
printf("%s--->", s[v0].name);
for (i = u; i >= 1; i--)
printf("%s--->", s[l[i]].name);
printf("%s\n", s[t].name);
printf("最短路径长度为:%d 米\n", dis[t]);
}
//校园导游服务
void Guide() {
system("cls");
Siteslist();
int opt, num, n, m;
printf("欢迎使用校园导航服务,请选择功能:\n1.查询目标地信息.\n2.问路查询.\n");
cin >> opt;
Store();
if (opt == 1) {
printf("请输入地点编号:");
cin >> num;
printf("地点名称:%s\n地点介绍:%s\n", s[num].name, s[num].introduce);
} else if (opt == 2) {
printf("请输入起点景点编号:\n");
scanf("%d", &n);
printf("\n请输入终点景点编号:\n");
scanf("%d", &m);
if (n >= 1 && n <= 10 && m >= 1 && m <= 10 && n != m)
Dijkstra(n, m);
else {
printf("输入错误,请重试!\n");
}
} else {
printf("输入错误,请重试!\n");
}
system("pause");
system("cls");
}
四、实验总结
历时两星期完成了此次数据结构课程设计,一开始感觉以自己的编程能力可能完成这个还是有一点难度,但经过我在网上不断查询、不断学习,以一种边学边做的状态也是完成了此时课程设计。整体上,本次课设我是基于二叉排序树实现的,总结功能如下:
读取参赛队伍信息:将参赛队伍信息从team.txt文件中读取到系统中,然后进行后续的操作。
添加参赛队伍信息:用户可以输入参赛队伍编号、作品名称、学校、赛事类别、参赛者及指导老师等信息,然后将其插入到二叉排序树中(按编号有序排列),以便后面的查找和修改。
删除参赛队伍信息:用户可以通过输入参赛队伍编号来寻找该队的节点位置,然后将其从二叉排序树中删除。当该队信息删除成功时,程序会提示“删除成功”,否则提示“未找到该队或者已被删除”。
修改参赛队伍信息:用户可以通过输入需要更改的参赛队伍编号来寻找该队的节点位置,并输入新的信息进行更新。当该队信息修改完成后,程序会提示“修改成功”,否则提示“未找到该队”。
按参赛队编号查找:用户可以通过输入参赛队伍编号查询该队的详细信息。
按参赛学校查询:用户可以输出指定学校的所有参赛队伍信息。
决赛叫号模拟:该功能在用户输入一个时间间隔后,会每隔一段时间就随机选取下一场比赛的参赛队伍,并进行打印操作。
校园导游服务:该功能提供多个景点的介绍,并在用户输入起点和终点之后,生成一条从起点到终点的最短路径。
打印:用户可以输出全部参赛队伍信息或者筛选某个学校的所有参赛队伍信息。
保存:该系统提供将参赛队伍数据保存到文件中的功能,采用中序遍历二叉排序树的方法逐行将数据写入指定文件中。
其它辅助函数:如节点插入、寻找父节点、中序遍历、计算总权值、计算总结点数等。同时该系统还包含了图论算法Dijkstra求解最短路径问题。
我对参赛队的数据存储采用二叉排序树(BST)结构。BST是一种有序的树形结构,在每个节点中存储着一个数据,利用每个节点的左子树小于当前节点数值、右子树大于当前节点数值的规则,可以进行快速的查找和插入/删除操作。因此,显然BST对于类似参赛队伍这样的“有序性”数据非常适合。
在具体实现过程中,为了避免出现把某些相等节点的判断失误,采用了两个与BTS相关的同类型数据的排序依据(即按照编号升序排列、但编号如果相同按照时间先后排列),从而严谨地保证了参赛队伍数据的有序性。
此外,该系统还引入了决赛叫号模拟和校园导游服务等功能。其中,决赛叫号模拟通过随机 选取参赛队伍来提高用户趣味性;而校园导游服务则通过Dijkstra最短路径算法,为用户提供导航指引。
在实现过程中,我还利用了一些经典的数据结构和算法。比如,在删除节点时,采取了三重判断的方法:如果当前节点有左右子树,则需要寻找到当前节点的后继结点,并替换当前节点的值,从而实现以保证树的有序性;如果当前节点只有左子树或者右子树,则直接用需要删除的节点的子树代替即可;最后如果当前节点为叶节点时则直接删除。
此外,在节点插入和访问操作时,我分别采用了递归和循环的不同实现方式,并通过限制二叉排序树高度,有效地避免了二叉平衡树旋转操作带来的复杂性,并兼顾空间和时间性能。
总的来说,该参赛管理系统功能基本全部实现,满足我们的课设任务要求。
五、源码
头文件"ManageSystem.h"源码:
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <bits/stdc++.h>
#define inf 99999999
using namespace std;
int road_map[20][20], book[20], dis[20];
//节点
typedef struct TeamNode {
int number; //参赛队伍编号
string name; //作品名称
string school; //参赛队伍学校
string category; //赛事类别
string contestant; //参赛者
string teacher; //指导老师
struct TeamNode *lchild; //左孩子
struct TeamNode *rchild; //右孩子
} TeamNode;
TeamNode *T = NULL;
//地点节点
struct Site {
char name[10]; //地点名称
char introduce[100]; //地点介绍
} s[12];
void Menu(); //菜单
void ReadTeams(); //读取参赛队伍信息
void AddTeam(); //增添参赛队伍信息
void DeleteTeam(); //删除参赛队伍信息
void EditTeam(); //修改参赛队伍信息
void Lookup_num(); //按参赛队编号查找
void Lookup_sch(); //按参赛学校查询
void Call(); //决赛叫号模拟
void Guide(); //校园导游服务
void PrintTeams(); //打印
void Save(); //保存
void Remove(string &s); //去除字符串中的空格
void Insert(TeamNode **T, int number, string name, string school, string category, string contestant,
string teacher); //插入二叉排序树
TeamNode *SearchParent(TeamNode *T, int number); //寻找父节点
TeamNode *Search(TeamNode *T, int number); //基于二叉排序树的查找
void Inorder(TeamNode *T); //中序遍历二叉排序树
void Inorder2(TeamNode *T, string school); //中序遍历二叉排序树进行有条件的输出
int Totalweight(TeamNode *T, int d); //计算总权值
int Node_num(TeamNode *T); // 计算总结点数
void Inorder3(TeamNode *T, ofstream &ofs); //中序遍历二叉排序树将数据写入文件
void Inorder4(TeamNode *T, int *t, int &index); //遍历生成存储参赛队编号的数组,并返回指向该数组的指针
void Siteslist(); //地点列表
void Store(); //存储景点信息、同时存图,各边信息
void Dijkstra(int v0, int t); //迪杰斯特拉求最短路径,并输出路线
系统功能源码:
#include "ManageSystem.h"
int main() {
while (1) {
Menu(); //菜单
char ch = getch(); //从键盘接受一个字符
switch (ch) {
case '1': //读取参赛队伍信息
ReadTeams();
break;
case '2': //增添参赛队伍信息
AddTeam();
break;
case '3': //删除参赛队伍信息
DeleteTeam();
break;
case '4': //修改参赛队伍信息
EditTeam();
break;
case '5': //按参赛队编号查找
Lookup_num();
break;
case '6': //按参赛学校查询
Lookup_sch();
break;
case '7': //决赛叫号模拟
Call();
break;
case '8': //校园导游服务
Guide();
break;
case '9': //保存
Save();
break;
case 'p': //打印
PrintTeams();
break;
case '0': //退出系统
printf("Bye Bye!\n");
return 0;
default:
printf("您的输入有误,请重新输入.\n");
system("pause");
system("cls");
}
}
return 0;
}
void Menu() {
printf("*****************************************\n");
printf("*\t欢迎使用赛事管理系统v1.0\t*\n");
printf("*****************************************\n");
printf("*\t请选择功能列表\t\t\t*\n");
printf("*****************************************\n");
printf("*\t1.读取参赛队伍信息\t\t*\n");
printf("*\t2.增添参赛队伍信息\t\t*\n");
printf("*\t3.删除参赛队伍信息\t\t*\n");
printf("*\t4.修改参赛队伍信息\t\t*\n");
printf("*\t5.按参赛队编号查找\t\t*\n");
printf("*\t6.按参赛学校查询\t\t*\n");
printf("*\t7.决赛叫号模拟\t\t\t*\n");
printf("*\t8.校园导游服务\t\t\t*\n");
printf("*\t9.保存\t\t\t\t*\n");
printf("*\tp.打印参赛队伍信息\t\t*\n");
printf("*\t0.退出系统\t\t\t*\n");
printf("*****************************************\n");
}
//去除字符串中的空格
void Remove(string &s) {
string::size_type index = 0;
if (!s.empty()) {
while ((index = s.find('\t', index)) != string::npos) { // 进行转行 ,不然无法清除
s.erase(index, 1);
}
}
}
//插入二叉排序树
void Insert(TeamNode **T, int number, string name, string school, string category, string contestant, string teacher) {
if (*T == NULL) {
*T = new TeamNode();
(*T)->number = number;
(*T)->name = name;
(*T)->school = school;
(*T)->category = category;
(*T)->contestant = contestant;
(*T)->teacher = teacher;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
} else if (number < (*T)->number) {
Insert(&((*T)->lchild), number, name, school, category, contestant, teacher);
} else if (number > (*T)->number) {
Insert(&((*T)->rchild), number, name, school, category, contestant, teacher);
}
}
//读取参赛队伍信息
void ReadTeams() {
//打开文件
ifstream fp;
fp.open("team.txt");
if (!fp) {
printf("打开文件失败!\n");
return;
}
//读文件
string str;
int number;
string name, school, category, contestant, teacher;
while (getline(fp, str)) {
if (str[0] != '2')
continue;
Remove(str);
for (int i = 0; i < 6; i++) {
int position = str.find('#');
string s = str.substr(0, position);
str = str.substr(position + 1);
switch (i) {
case 0:
number = stoi(s);
break;
case 1:
name = s;
break;
case 2:
school = s;
break;
case 3:
category = s;
break;
case 4:
contestant = s;
break;
case 5:
teacher = s;
break;
default:
break;
}
}
Insert(&T, number, name, school, category, contestant, teacher);
}
fp.close();
printf("读取成功!\n");
system("pause");
system("cls");
}
//增添参赛队伍信息
void AddTeam() {
int number; //参赛队伍编号
string name; //作品名称
string school; //参赛队伍学校
string category;//赛事类别
string contestant;//参赛者
string teacher; //指导老师
printf("请输入参赛队编号:\n");
cin >> number;
printf("请输入参赛作品名称:\n");
cin >> name;
printf("请输入参赛学校:\n");
cin >> school;
printf("请输入赛事类别:\n");
cin >> category;
printf("请输入参赛者姓名:\n");
cin >> contestant;
printf("请输入指导老师姓名:\n");
cin >> teacher;
Insert(&T, number, name, school, category, contestant, teacher);
printf("参赛队信息增添完成.\n");
system("pause");
system("cls");
}
//寻找父节点
TeamNode *SearchParent(TeamNode *T, int number) {
if (T) {
if (T->lchild->number == number || T->rchild->number) {
return T;
} else if (number < T->number) {
return SearchParent(T->lchild, number);
} else {
return SearchParent(T->rchild, number);
}
} else {
return NULL;
}
}
void Delete(TeamNode *T, int number) {
//首先找到要删除的结点
TeamNode *Pre;
TeamNode *P = T; //定义工作指针
while (P != NULL && number != P->number) {
if (number > P->number) {
Pre = P;
P = P->rchild;
} else {
Pre = P;
P = P->lchild;
}
}
if (P == NULL) {
printf("要删除的结点不存在.\n" );
} else {
// 当该结点是叶子结点时,直接删除
if (P->lchild == NULL && P->rchild == NULL) {
if (P->number > Pre->number) {
Pre->rchild = NULL;
} else {
Pre->lchild = NULL;
}
}
//当该结点有一个左孩子或者一个右孩子时,让其孩子结点代替他的位置
if ((P->lchild != NULL && P->rchild == NULL) || (P->rchild != NULL && P->lchild == NULL)) {
if (P->number > Pre->number) {
if (P->lchild != NULL) {
Pre->rchild = P->lchild;
} else {
Pre->rchild = P->rchild;
}
}
if (P->number < Pre->number) {
if (P->lchild != NULL) {
Pre->lchild = P->lchild;
} else {
Pre->lchild = P->rchild;
}
}
}
//当左右孩子都存在时找中序遍历的上一个结点代替其位置
if (P->lchild != NULL && P->rchild != NULL) {
TeamNode *Q = P->lchild; //工作指针
TeamNode *Pre2; //用于记录最终遍历到的结点的前序结点,用于后期连接
while (Q->rchild) {
Pre2 = Q;
Q = Q->rchild;
}
P->number = Q->number;
P->name = Q->name;
P->school = Q->school;
P->category = Q->category;
P->contestant = Q->contestant;
P->teacher = Q->teacher;
if (P != Pre2) {
Pre2->rchild = Q->lchild;
} else {
Pre2->lchild = Q->lchild;
}
delete (Q);
}
}
}
//删除参赛队伍信息
void DeleteTeam() {
int number;
printf("请输入需要删除的参赛队编号:\n");
cin >> number;
Delete(T, number);
printf("该参赛队信息已删除.\n");
system("pause");
system("cls");
}
//修改参赛队伍信息
void EditTeam() {
int number;
string name; //作品名称
string school; //参赛队伍学校
string category;//赛事类别
string contestant;//参赛者
string teacher; //指导老师
printf("请输入需要修改信息的参赛队编号:\n");
cin >> number;
TeamNode *p = Search(T, number);
if (p) {
printf("请输入新信息\n");
printf("参赛作品名称:\n");
cin >> name;
printf("参赛学校:\n");
cin >> school;
printf("赛事类别:\n");
cin >> category;
printf("参赛者姓名:\n");
cin >> contestant;
printf("指导老师姓名:\n");
cin >> teacher;
p->name = name;
p->school = school;
p->category = category;
p->contestant = contestant;
p->teacher = teacher;
printf("参赛队信息修改完成.\n");
} else {
printf("查找不到此参赛队,请重试.\n");
}
system("pause");
system("cls");
}
//基于二叉排序树的查找
TeamNode *Search(TeamNode *T, int number) {
if (T) {
if (T->number == number) {
return T;
} else if (number < T->number) {
return Search(T->lchild, number);
} else {
return Search(T->rchild, number);
}
} else {
return NULL;
}
}
//计算总权值
int Totalweight(TeamNode *T, int d) {
d++;
int a = d;
if (T->lchild)
d += Totalweight(T->lchild, a);
if (T->rchild)
d += Totalweight(T->rchild, a);
return d;
}
// 计算总结点数
int Node_num(TeamNode *T) {
if (T == NULL) { // 空节点判断
return 0;
}
int d = 1 + Node_num(T->lchild) + Node_num(T->rchild);
return d;
}
//按参赛队编号查找
void Lookup_num() {
int number, d1, d2;
printf("请输入需要查找的参赛队编号:\n");
cin >> number;
TeamNode *p = Search(T, number);
if (p != NULL) {
printf("参赛队编号:%d\n参赛作品名称:%s\n参赛学校:%s\n赛事类别:%s\n参赛者:%s\n指导老师:%s\n", p->number, p->name.c_str(),
p->school.c_str(), p->category.c_str(),
p->contestant.c_str(), p->teacher.c_str());
d1 = Totalweight(T, 0);
d2 = Node_num(T);
cout << "ASL=" << d1 << '/' << d2 << '=' << d1 * 1.0 / d2 << endl;
} else {
printf("查找失败!\n");
}
system("pause");
system("cls");
}
//中序遍历二叉排序树进行有条件的输出
void Inorder2(TeamNode *T, string school) {
if (T) {
Inorder2(T->lchild, school);
if (school == T->school) {
printf("%d # %s # %s # %s # %s # %s\n", T->number, T->name.c_str(), T->school.c_str(), T->category.c_str(),
T->contestant.c_str(), T->teacher.c_str());
}
Inorder2(T->rchild, school);
}
}
//按参赛学校查询
void Lookup_sch() {
string school;
printf("请输入查询的参赛学校:\n");
cin >> school;
system("cls");
printf("参赛队编号 # 参赛作品名称 # 参赛学校 # 赛事类别 # 参赛者 # 指导老师\n");
Inorder2(T, school);
printf("查找完毕.\n");
system("pause");
system("cls");
}
//中序遍历二叉排序树
void Inorder(TeamNode *T) {
if (T) {
Inorder(T->lchild);
printf("%d # %s # %s # %s # %s # %s\n", T->number, T->name.c_str(), T->school.c_str(), T->category.c_str(),
T->contestant.c_str(), T->teacher.c_str());
Inorder(T->rchild);
}
}
//打印
void PrintTeams() {
system("cls");
printf("参赛队编号\t参赛作品名称\t参赛学校\t赛事类别\t参赛者\t\t指导老师\n");
Inorder(T);
system("pause");
system("cls");
}
//中序遍历二叉排序树将数据写入文件
void Inorder3(TeamNode *T, ofstream &ofs) {
if (T) {
Inorder3(T->lchild, ofs);
ofs << T->number << " # "
<< T->name << " # "
<< T->school << " # "
<< T->category << " # "
<< T->contestant << " # "
<< T->teacher << '\n';
Inorder3(T->rchild, ofs);
}
}
//保存
void Save() {
//清空文件内容
ofstream ofs("team.txt");
if (!ofs) {
printf("打开文件失败!\n");
return;
}
ofs << "参赛队编号 # 参赛作品名称 # 参赛学校 # 赛事类别 # 参赛者 # 指导老师\n";
ofs.close();
//重新加载数据到文件中
ofstream i("team.txt", ios::app);
if (!i) {
printf("打开文件失败!\n");
return;
}
Inorder3(T, i);
i.close();
printf("保存成功!\n");
system("pause");
system("cls");
}
//遍历生成存储参赛队编号的数组,并返回指向该数组的指针
void Inorder4(TeamNode *T, int *t, int &index) {
if (T) {
Inorder4(T->lchild, t, index);
t[index++] = T->number;
Inorder4(T->rchild, t, index);
}
}
//模拟叫号系统
void Call() {
system("cls");
int index = 0;
int h = 8, m = 0, k = 0, i = 0;
int *t = new int[1000];
int d = Node_num(T);
Inorder4(T, t, index);
while (i < d) {
printf("%d:%d%d\n", h, k, m);
printf("1号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("2号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("3号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("4号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("5号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("6号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("7号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("8号决赛室:%d\n", t[i++]);
if (i == d)
break;
printf("9号决赛室:%d\n", t[i++]);
if (i == d)
break;
if (m < 9)
m++;
if (m == 9) {
m = 0;
if (k < 5)
k++;
if (k == 5) {
k = 0;
h++;
}
}
}
delete []t;
printf("模拟完成.\n");
system("pause");
system("cls");
}
//地点列表
void Siteslist() {
printf("*****************************************\n");
printf("*\t\t地点列表\t\t*\n");
printf("*****************************************\n");
printf("*\t\t<1>三号组团\t\t*\n");
printf("*\t\t<2>西苑食堂\t\t*\n");
printf("*\t\t<3>明德园\t\t*\n");
printf("*\t\t<4>文体中心\t\t*\n");
printf("*\t\t<5>西田径场\t\t*\n");
printf("*\t\t<6>文理大楼\t\t*\n");
printf("*\t\t<7>北苑食堂\t\t*\n");
printf("*\t\t<8>求索园\t\t*\n");
printf("*\t\t<9>东苑食堂\t\t*\n");
printf("*\t\t<10>图书馆\t\t*\n");
printf("*****************************************\n");
}
//存储景点信息、同时存图,各边信息
void Store() {
int i, j;
strcpy(s[1].name, "三号组团");
strcpy(s[1].introduce, "三号组团是学生住宿楼,其中住着计算机学院的学子,他们都无比优秀。");
strcpy(s[2].name, "西苑食堂");
strcpy(s[2].introduce, "西苑食堂是一个供应学校师生员工用餐的场所,提供各种美食和饮品,是大家日常用餐的主要场所之一。");
strcpy(s[3].name, "明德园");
strcpy(s[3].introduce, "明德园环境优美、景色宜人,是人们度过悠闲时光、放松身心的理想场所。");
strcpy(s[4].name, "文体中心");
strcpy(s[4].introduce,
"文体中心是一个综合性场所,它不仅能促进社区文化教育和艺术交流、发展体育锻炼和竞技运动,同时也能让人们更好地享受文化和娱乐生活,为学生们打造出一处智慧、和谐、幸福、美好的生活环境。");
strcpy(s[5].name, "西田径场");
strcpy(s[5].introduce,
"田径场是一种专门用于田径运动比赛和训练的设施。通常由跑道、标准的跳远、三级跳、撑杆跳、铅球、铁饼、标枪、链球等各种田径比赛项目的比赛区域和其他辅助设施组成。");
strcpy(s[6].name, "文理大楼");
strcpy(s[6].introduce, "文理大楼作为江苏科技大学长山校区第一高楼,共21层,高103米,是江科大的标志性建筑。");
strcpy(s[7].name, "北苑食堂");
strcpy(s[7].introduce,
"北苑食堂提供多样化的饮食选择,如米饭、面条、馒头、馄饨、炒菜、烧烤等,同时还提供不同口味的汤、饮料和甜点等。");
strcpy(s[8].name, "求索园");
strcpy(s[8].introduce, "求索园是学生们接近自然、放松身心、锻炼身体的重要场所之一。");
strcpy(s[9].name, "东苑食堂");
strcpy(s[9].introduce,
"东苑食堂注重菜品的营养搭配和健康指导,通过设置标识或提供专门的食品展示区来提示餐品的营养成分,以便师生员工做出更加合理的用餐选择。");
strcpy(s[10].name, "图书馆");
strcpy(s[10].introduce,
"图书馆是一种公共设施,提供各种类型的书籍、期刊和其他资料,供读者借阅或在馆内阅读。图书馆也提供其他服务,如电子资源显微镜报告、计算机终端、学习空间和教育课程。");
for (i = 1; i <= 12; i++) {
for (j = 1; j <= 12; j++) {
if (i == j)
road_map[i][j] = 0;
else
road_map[i][j] = inf;
}
}
road_map[1][2] = 100;
road_map[1][4] = 200;
road_map[2][1] = 100;
road_map[2][3] = 80;
road_map[2][4] = 150;
road_map[3][2] = 80;
road_map[3][5] = 120;
road_map[3][6] = 110;
road_map[4][1] = 200;
road_map[4][2] = 150;
road_map[4][5] = 50;
road_map[5][3] = 120;
road_map[5][4] = 50;
road_map[5][8] = 150;
road_map[5][9] = 230;
road_map[6][3] = 110;
road_map[6][7] = 80;
road_map[6][8] = 60;
road_map[7][6] = 80;
road_map[7][10] = 100;
road_map[8][5] = 150;
road_map[8][6] = 60;
road_map[8][9] = 90;
road_map[8][10] = 70;
road_map[9][5] = 230;
road_map[9][8] = 90;
road_map[9][10] = 50;
road_map[10][7] = 100;
road_map[10][8] = 70;
road_map[10][9] = 50;
}
//迪杰斯特拉求最短路径,并输出路线
void Dijkstra(int v0, int t) {
int min, i, j, u, v;
int p[20], l[20];
memset(p, -1, sizeof(p)); //p 数组用于记录每个节点在最短路径中的前一节点
memset(l, 0, sizeof(l)); //l 数组用于存储最短路径的中间节点
memset(book, 0, sizeof(book)); //book 数组表示该节点是否被遍历过(0 表示未标记,1 表示已标记)
for (i = 1; i <= 10; i++) {
dis[i] = road_map[v0][i];
if (dis[i] < inf) //v0能直接到达,即上一站点为v0
p[i] = v0;
}
book[v0] = 1;
for (i = 1; i < 10; i++) {
min = inf;
for (j = 1; j <= 10; j++) { //每次找出距离v0最近点
if (book[j] == 0 && dis[j] < min) {
min = dis[j];
u = j;
}
}
book[u] = 1; //标记该点
for (v = 1; v <= 10; v++) {
if (book[v] == 0 && dis[v] > dis[u] + road_map[u][v]) { //通过最近点更新其他边
p[v] = u; //存储更新的边,即为路线
dis[v] = dis[u] + road_map[u][v];
}
}
}
v = t;
i = 1;
while (p[v] != v0) { //将路线存入栈中,正序输出
l[i++] = p[v];
v = p[v];
}
printf("\n");
u = i - 1;
printf("路线为:\n");
printf("%s--->", s[v0].name);
for (i = u; i >= 1; i--)
printf("%s--->", s[l[i]].name);
printf("%s\n", s[t].name);
printf("最短路径长度为:%d 米\n", dis[t]);
}
//校园导游服务
void Guide() {
system("cls");
Siteslist();
int opt, num, n, m;
printf("欢迎使用校园导航服务,请选择功能:\n1.查询目标地信息.\n2.问路查询.\n");
cin >> opt;
Store();
if (opt == 1) {
printf("请输入地点编号:");
cin >> num;
printf("地点名称:%s\n地点介绍:%s\n", s[num].name, s[num].introduce);
} else if (opt == 2) {
printf("请输入起点景点编号:\n");
scanf("%d", &n);
printf("\n请输入终点景点编号:\n");
scanf("%d", &m);
if (n >= 1 && n <= 10 && m >= 1 && m <= 10 && n != m)
Dijkstra(n, m);
else {
printf("输入错误,请重试!\n");
}
} else {
printf("输入错误,请重试!\n");
}
system("pause");
system("cls");
}