数据结构课程设计
一:选用的语言
在本次课程设计任务中,我采用的是C++作为编程语言。对此我主要的考虑如下:C++具有如下优势:
-
近乎零开销抽象
-
极高的性能及运行效率
-
极高的可底层可控性
-
极高的成熟度
-
极高的兼容性
最关键的是,结合本人自身的学习情况,比较熟悉C++,所以采用C++作为编程语言。
二:任务
1 任务描述:
- 能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
- 从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
- 能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
- 为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
- 赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。
2 任务分析:
- 对于上述五个任务,对于任务1到任务4,首先要实现的就是对team.txt文件的读取与写入,基于这个读取与写入操作的完成,才能完成后续任务。
三:任务实现
1 文件的写入:
首先要包含头文件:
#include <fstream>
之后就可以进行创建文件流对象等操作了
ofstream o; //创建文件输出流对象,用于写入文件
o.open("team.txt", ios::app); // ios::app append 往后加
o << '\n'; //输入换行,很重要,主要是为了之后的读取操作
最后就可以根据提示进行相应就输入,以选手编号为例:
string Number; //添加参赛队伍编号
cout << "请输入参赛队伍编号:" << endl;
cin >> Number;
o << Number;
o << "#"; //根据情况判断是否要输入#号
最后的最后,把这个操作变成—Add函数,方便后续使用。
2 文件的读取:
我注意到team.txt文件中,每项数据都有空格,这不利于之后数据的使用,而C++中又没有去除字符串空格的内置函数,所以我先定义了Remove函数,去除读取的数据所有空格:
void Remove(string &s) //去除字符串中的空格
{
int index = 0;
if( !s.empty())
{
while( (index = s.find('\t',index)) != string::npos) // 需要用'\t' ,不然无法清除
{
s.erase(index,1);
}
}
}
之后,按照任务书中的要求,按照”#“来对数据进行分割读取
ifstream i; //创建文件输入流对象,用于读取文件
i.open("team.txt"); // 读取team.txt文件
string Temp; //按行读取后储存在Temp字符串
while (getline(i, Temp)) //按行读取
{
Remove(Temp); //按行读取,每一行都记录到Temp
for (int j = 0; j < 6; j++) //每行六项数据
{
int Position = Temp.find('#'); //寻找#,记录#出现的位置
string s = Temp.substr(0,Position); //字符串s记录所需要的数据
Temp = Temp.substr(Position + 1);
cout << s<<endl;
}
}
i.close(); //关闭文件
最后,可以将读取操作变成一个函数—Read,方便调用。
以下是部分的运行结果(结果太长无法全部展示):
3 二叉排序树的创建:
创建二叉排序树之前,要先把从文件中读取的数据先储存起来,对此于**2 文件的读取**中的代码第12行中的内容要做一下修改:
Team.push_back(s); //将队伍信息存入容器
在这之前要先定义一个容器Team:
vector<string> Team; //储存队伍信息的容器
然后就到了二叉排序树的创建:
定义所需的结构体 :
typedef struct
{
long long Number; //参赛作品编号
string Production; //添加参赛作品名称
string Campus; //参赛学校
string Category; //参赛类别
string StudentName; //参赛学生姓名
string TeacherName; //指导老师姓名
} ElemType;
typedef struct BSTNode
{
ElemType data; //每个结点的数据域包括参赛作品编号和其他数据项
struct BSTNode *lchild, *rchild; //左右孩子指针
} BSTNode, *BSTree;
定义创建二叉排序树的函数插入:InsertBST 和创建:CreatBST:
void InsertBST(BSTree &T, ElemType e)
{
if (!T)
{
BSTNode *S = new BSTNode; //生成新结点*S
S->data = e; //新结点*S的数据域置为e
S->lchild = S->rchild = NULL; //新结点*S作为叶子结点
T = S; //把新结点*S链接到已找到的插入位置
}
else if (e.Number < T->data.Number)
{
InsertBST(T->lchild, e);
} //将*S插入左子树
else if (e.Number > T->data.Number)
{
InsertBST(T->rchild, e);
} //将*S插入右子树
}
int Flag = 6; //全局变量,前六个为无用信息
void CreatBST(BSTree &T)
{
//依次读入一个关键字为Number的结点,将此结点插入二叉排序树T中
T = NULL; //将二叉排序树T初始化为空树
ElemType e; //容器中对应的信息放入树中
e.Number = stod(Team.at(Flag++)); // stod函数转化为整形
e.Production = Team.at(Flag++);
e.Campus = Team.at(Flag++);
e.Category = Team.at(Flag++);
e.StudentName = Team.at(Flag++);
e.TeacherName = Team.at(Flag++);
while (Flag < Team.size()-6) //前六个为无用信息
{
InsertBST(T, e);
//容器中对应的信息放入树中
e.Number = stod(Team.at(Flag++));
e.Production = Team.at(Flag++);
e.Campus = Team.at(Flag++);
e.Category = Team.at(Flag++);
e.StudentName = Team.at(Flag++);
e.TeacherName = Team.at(Flag++);
}
InsertBST(T, e);
}
验证是否成功创建二叉排序树,可以依据中序遍历,输出参赛队伍编号查看其是否为升序,对此定义了一个采用递归方法的InorderTree函数:
void InorderTree(BSTree T)
{
if (T)
{
InorderTree(T->lchild);
cout << T->data.Number << " ";
InorderTree(T->rchild);
}
}
主函数中进行相关函数调用后,输出结果如下:
成功!
4 按参赛队伍编号查找:
基于二叉排序树的查找,定义Find函数来进行依据参赛队伍编号进行查找,对于输出ASL查找成功时的平均查找长度,要创建ASL函数进行统计所有结点的深度和节点数:
首先是定义所需要的全局变量:
int VertexSum = 0; //节点数
然后是ASL函数:
int ASL(BSTree T, int depth)
{ //利用递归计算所有节点深度和
if (T)
{
VertexSum++;
return depth + ASL(T->rchild, depth + 1) + ASL(T->lchild, depth + 1);
}
else
return 0;
}
最后是Find函数:
void Find(BSTree T)
{
cout << "请输入参赛队伍的编号:" << endl;
long long Number;
cin >> Number;
BSTree TempT1; //暂时创建一颗树储存找到的信息
TempT1 = SearchBST(T, Number);
if (TempT1)
{ //找到了
cout << "参赛队伍编号:" << endl;
cout << TempT1->data.Number << endl;
cout << "参赛作品名称:" << endl;
cout << TempT1->data.Production << endl;
cout << "参赛学校:" << endl;
cout << TempT1->data.Campus << endl;
cout << "赛事类别:" << endl;
cout << TempT1->data.Category << endl;
cout << "参赛者:" << endl;
cout << TempT1->data.StudentName << endl;
cout << "指导老师:" << endl;
cout << TempT1->data.TeacherName << endl;
cout << "当前二叉树的查找成功时的平均查找长度为: " << endl;
int DepthSum = ASL(T, 0);
double ASL2 = (double)DepthSum / VertexSum;
cout << ASL2 << endl;
;
}
else
{
cout << "查找错误!请检查队伍编号是否正确!" << endl;
}
cout<<"请重新选择功能!"<<endl;
}
运行结果如下:
5 增加参赛队伍的信息:
参考1 文件的写入,补全其余代码即可。以下为实现操作的Add函数:
void Add()
{ //往txt文件添加操作
ofstream o; //创建文件输出流对象,用于写入文件
o.open("team.txt", ios::app); // ios::app append 往后加
o << '\n'; //换行,很重要
string Number; //添加参赛队伍编号
cout << "请输入参赛队伍编号:" << endl;
cin >> Number;
o << Number;
o << "#";
string Production; //添加参赛作品名称
cout << "请输入参赛作品名称:" << endl;
cin >> Production;
o << Production;
o << "#";
string Campus; //添加参赛学校
cout << "请输入参赛学校:" << endl;
cin >> Campus;
o << Campus;
o << "#";
string Category; //添加参赛类别
cout << "请输入参赛类别:" << endl;
cin >> Category;
o << Category;
o << "#";
string StudentName; //添加参赛学生姓名
cout << "请输入参赛学生姓名:" << endl;
cin >> StudentName;
o << StudentName;
o << "#";
string TeacherName; //添加指导老师姓名
cout << "请输入指导老师姓名:" << endl;
cin >> TeacherName;
o << TeacherName;
o.close(); //关闭文件
}
6 删除参赛队伍信息:
要想删除一个参赛队伍的参赛信息,首先要根据这支队伍的编号来找到这支队伍,然后要对team.txt文件中所对应的信息。我使用的操作是,先删除Team容器里的信息(Team容器在3 二叉排序树的创建首次出现),然后根据Team容器创建一个新的team1.txt,再讲原先的team.txt文件删除,最后将team1.txt改名为team.txt。实现该操作首先需要一个能根据容器创建新team1.txt,并改名的函数CreatNewTxt:
void CreatNewTxt()
{ //创建新的txt文件,用于参赛信息的修改和删除
ofstream o1; //创建文件输出流对象,用于写入文件
o1.open("team1.txt"); // 创建一个team1.txt文件
for (int i = 0; i < Team.size(); i++)
{
o1 << Team.at(i);
if (i % 6 == 5)
{
o1 << '\n'; //换行,很重要
}
else
{
o1 << "#";
}
}
o1.close(); //关闭文件
}
然后再根据编号,创建删除Team容器信息的DeleteProj函数,其中要调用CreatNewTxt函数:
int Exisit = 0; // DeleteProjet函数中,判断是否存在的标志
void DeleteProject()
{
long long DeleteNumber;
cout << "请输入要删除的参赛项目编号:" << endl;
cin >> DeleteNumber;
for (int i = 6; i < Team.size(); i += 6)
{
if (DeleteNumber == stoll(Team.at(i))) //找到要删除的编号
{
cout << "你要删除的参赛信息为:" << endl; //输出移除参赛编号以及后续的信息
cout << "参赛编号:" << endl;
cout << Team.at(i) << endl;
Team.erase(Team.begin() + i);
cout << "参赛作品名称:" << endl;
cout << Team.at(i) << endl;
Team.erase(Team.begin() + i);
cout << "参赛学校:" << endl;
cout << Team.at(i) << endl;
Team.erase(Team.begin() + i);
cout << "赛事类别:" << endl;
cout << Team.at(i) << endl;
Team.erase(Team.begin() + i);
cout << "参赛者:" << endl;
cout << Team.at(i) << endl;
Team.erase(Team.begin() + i);
cout << "指导老师:" << endl;
cout << Team.at(i) << endl;
Team.erase(Team.begin() + i);
CreatNewTxt(); //创建新的team1文件储存信息
Exisit = 1;
remove("team.txt"); // 删除原始team文件
cout << "删除成功!" << endl;
break;
}
}
if (Exisit == 0)
{
cout << "输入的编号有误!" << endl;
}
}
运行结果如下:
然后查看team.txt文件,要删除的编号为2021009290的参赛队伍已经删除:
删除前(要删除的队伍在第一行):
删除后:
7 修改参赛队伍信息:
要修改一个参赛队伍的信息,首先要找到这个参赛队伍,然后对之前定义的Team容器里的要修改的相关信息进行修改,我认为这一步的操作与6 删除参赛队伍信息的操作很类似,具体操作如下:
CreatNewTxt函数(6 删除参赛队伍信息中出现):
void CreatNewTxt()
{ //创建新的txt文件,用于参赛信息的修改和删除
ofstream o1; //创建文件输出流对象,用于写入文件
o1.open("team1.txt"); // 创建一个team1.txt文件
for (int i = 0; i < Team.size(); i++)
{
o1 << Team.at(i);
if (i % 6 == 5)
{
o1 << '\n'; //换行,很重要
}
else
{
o1 << "#";
}
}
o1.close(); //关闭文件
}
最重要的ChangeProject函数:
void ChangeProject()
{ //更改信息的函数
cout << "请输入要修改参赛编号:" << endl;
long long FindNumber;
cin >> FindNumber;
for (int i = 6; i < Team.size(); i += 6)
{
if (FindNumber == stoll(Team.at(i))) //找到要修改的编号
{
cout << "你要修改的参赛信息为:" << endl; //输出要修改信息
cout << "1 参赛编号:" << endl;
cout << Team.at(i) << endl;
cout << "2 参赛作品名称:" << endl;
cout << Team.at(i + 1) << endl;
cout << "3 参赛学校:" << endl;
cout << Team.at(i + 2) << endl;
cout << "4 赛事类别:" << endl;
cout << Team.at(i + 3) << endl;
cout << "5 参赛者:" << endl;
cout << Team.at(i + 4) << endl;
cout << "6 指导老师:" << endl;
cout << Team.at(i + 5) << endl;
Exisit = 1;
cout << "你要修改哪一项:(输入要修改目标的编号1-6)" << endl;
int Selection2;
cin >> Selection2;
switch (Selection2)
{
case 1:
{
cout << "请输入修改后的队伍编号:" << endl;
string Modification;
cin >> Modification;
Team.at(i) = Modification;
cout << "修改完成!" << endl;
break;
}
case 2:
{
cout << "请输入修改后的参赛队伍名称:" << endl;
string Modification;
cin >> Modification;
Team.at(i + 1) = Modification;
cout << "修改完成!" << endl;
break;
}
case 3:
{
cout << "请输入修改后的参赛学校:" << endl;
string Modification;
cin >> Modification;
Team.at(i + 2) = Modification;
cout << "修改完成!" << endl;
break;
}
case 4:
{
cout << "请输入修改后的赛事类别:" << endl;
string Modification;
cin >> Modification;
Team.at(i + 3) = Modification;
cout << "修改完成!" << endl;
break;
}
case 5:
{
cout << "请输入修改后的参赛者:" << endl;
string Modification;
cin >> Modification;
Team.at(i + 4) = Modification;
cout << "修改完成!" << endl;
break;
}
case 6:
{
cout << "请输入修改后的指导老师:" << endl;
string Modification;
cin >> Modification;
Team.at(i + 5) = Modification;
cout << "修改完成!" << endl;
break;
}
}
cout << "修改后参赛信息为:" << endl; //输出修改后信息
cout << "参赛编号:" << endl;
cout << Team.at(i) << endl;
cout << "参赛作品名称:" << endl;
cout << Team.at(i + 1) << endl;
cout << "参赛学校:" << endl;
cout << Team.at(i + 2) << endl;
cout << "赛事类别:" << endl;
cout << Team.at(i + 3) << endl;
cout << "参赛者:" << endl;
cout << Team.at(i + 4) << endl;
cout << "指导老师:" << endl;
cout << Team.at(i + 5) << endl;
CreatNewTxt(); //创建新的team1文件储存信息
remove("team.txt"); // 删除原始team文件
break;
}
}
if (Exisit == 0)
{
cout << "输入的编号有误!" << endl;
}
}
具体运行结果:
修改后的team.txt文件(第二行的参赛者):
8 按照参赛学校查找队伍:
首先要定义一个Campus函数,对上述定义的Team容器进行遍历,寻找符合寻找要求的大学名称,之后将其对应的选手编号等信息存入定义的E1结构体数组。代码如下:
void CampusFind()
{
int j = 0; //计数器
string FindCampus;
cout << "请输入要找寻的学校:" << endl;
cin >> FindCampus;
ElemType E1[999]; //包含结构体数组
for (int i = 2; i < Team.size(); i += 6)
{
if (Team.at(i) == FindCampus)
{
//对各种信息赋值
E1[j].Number = stod(Team.at(i - 2));
E1[j].Production = Team.at(i - 1);
E1[j].Campus = Team.at(i);
E1[j].Category = Team.at(i + 1);
E1[j].StudentName = Team.at(i + 2);
E1[j].TeacherName = Team.at(i + 3);
j++;
}
}
//对结构体数字E1按照作品编号进行直接插入排序(升序):
for (int i = 1; i < j; ++i)
{
E1[j] = E1[i];
int k = i - 1;
while (k >= 0 && E1[k].Number > E1[j].Number)
{
E1[k + 1] = E1[k];
--k;
}
E1[k + 1] = E1[j];
}
//进行输出
for (int i = 0; i < j; i++)
{
cout << "参赛队伍编号:" << endl;
cout << E1[i].Number << endl;
cout << "参赛作品名称:" << endl;
cout << E1[i].Production << endl;
cout << "参赛学校:" << endl;
cout << E1[i].Campus << endl;
cout << "赛事类别:" << endl;
cout << E1[i].Category << endl;
cout << "参赛者:" << endl;
cout << E1[i].StudentName << endl;
cout << "指导老师:" << endl;
cout << E1[i].TeacherName << endl;
}
}
其中,对符合要求的参赛队伍,我选择的是按照参赛编号升序排序。我采用的算法是直接插入排序,理由如下:
- 实现简单:直接插入排序算法实现起来相对简单,容易理解和编写。
- 稳定性好:在排序过程中,如果两个元素的值相等,不会改变它们之间的相对位置。因此,直接插入排序是一种稳定的排序算法。
- 对于小规模数据集表现良好:当待排序数组规模较小时,直接插入排序效率比较高。
- 部分有序情况下效率较高:如果待排序数组已经部分有序,则直接插入排序算法的时间复杂度可以降至O(n)。
以下是程序运行结果: