2023-05-23-数据结构课程设计


数据结构课程设计

一:选用的语言

​ 在本次课程设计任务中,我采用的是C++作为编程语言。对此我主要的考虑如下:C++具有如下优势:

  • 近乎零开销抽象

  • 极高的性能及运行效率

  • 极高的可底层可控性

  • 极高的成熟度

  • 极高的兼容性

    最关键的是,结合本人自身的学习情况,比较熟悉C++,所以采用C++作为编程语言。

二:任务

1 任务描述:

  1. 能够管理各参赛队的基本信息(包含参赛队编号,参赛作品名称,参赛学校,赛事类别,参赛者,指导老师),赛事类别共11项(参见大赛官网jsjds.blcu.edu.cn);包括增加、删除、修改参赛队伍的信息。
  2. 从team.txt中读取参赛队伍的基本信息,实现基于二叉排序树的查找。根据提示输入参赛队编号,若查找成功,输出该赛事类别对应的基本信息(参赛作品名称、参赛学校、赛事类别、参赛者和指导老师信息),同时,输出查找成功时的平均查找长度ASL;否则,输出“查找失败!”。
  3. 能够提供按参赛学校查询参赛团队(或根据赛事类别查询参赛团队),即,根据提示输入参赛学校名称(赛事类别),若查找成功,输出该学校参赛的(该赛事类别的)所有团队的基本信息,输出的参赛团队按赛事类别有序输出。(排序算法可从选择排序、插入排序、希尔排序、归并排序、堆排序中任意选择,并为选择算法的原因做出说明。)
  4. 为省赛现场设计一个决赛叫号系统。所有参赛队按赛事组织文件中的赛事类别分到9个决赛室,决赛室按顺序叫号,被叫号参赛队进场,比赛结束后,下一参赛队才能进赛场。请模拟决赛叫号系统,演示省赛现场各决赛室的参赛队进场情况。(模拟时,要能直观展示叫号顺序与进场秩序一致)
  5. 赛事系统为参赛者提供赛地的校园导游程序,为参赛者提供各种路径导航的查询服务。以我校长山校区提供比赛场地为例,(请为参赛者提供不少于10个目标地的导航。可为参赛者提供校园地图中任意目标地(建筑物)相关信息的查询;提供任意两个目标地(建筑物)的导航查询,即查询任意两个目的地(建筑物)之间的一条最短路径。

2 任务分析:

  1. 对于上述五个任务,对于任务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);
  }
}

主函数中进行相关函数调用后,输出结果如下:

2

成功!

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;
  }
}

其中,对符合要求的参赛队伍,我选择的是按照参赛编号升序排序。我采用的算法是直接插入排序,理由如下:

  1. 实现简单:直接插入排序算法实现起来相对简单,容易理解和编写。
  2. 稳定性好:在排序过程中,如果两个元素的值相等,不会改变它们之间的相对位置。因此,直接插入排序是一种稳定的排序算法。
  3. 对于小规模数据集表现良好:当待排序数组规模较小时,直接插入排序效率比较高。
  4. 部分有序情况下效率较高:如果待排序数组已经部分有序,则直接插入排序算法的时间复杂度可以降至O(n)。

以下是程序运行结果:

4

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值