目录
近来有空闲,把前几个学期做的实验报告上传上来。如有错误的地方欢迎大佬批评指正,有更好的方法也期待您的分享~
实验要求
编写一个实验程序实现以下功能:
有一个学生成绩文本文件xyz4.in,第一行为整数n,接下来为n行学生基本信息,包括学号、姓名和班号,接下来为整数m,然后为m行课程信息,包括课程编号和课程名,再接下来为整数k,然后为k行学生成绩,包括学号、课程编号和分数。编写一个程序按班号递增排序输出所有学生的成绩,相同班号按学号递增排序,同一个学生按课程编号递增排序,相邻的班号和学生信息不重复输出。
一、实验目的
1.熟悉线性表的逻辑结构、线性表抽象数据类型的描述方法;
2.熟悉存储结构以及算法设计方法;
3.熟悉线性表的建立、插入操作;
4.熟练 STL 中的 vector 容器及其应用;
5.复习文件读写的基本操作。
二、问题分析及数据结构设计
本次开发任务要求我们读取文件建立学生成绩线性表,并对其进行按某域值递增排序的操作。该任务涵盖功能要求有文件读取学生基本信息、课程信息和学生成绩以及建立线性表、按照班号递增排序输出所有学生的成绩,同时相同班号的学生按学号递增排序,同一个学生的成绩按课程编号递增排序。
从实验要求可知我们并不需要后续的交互, STL 标准库比我们自己实现的算法更健壮高效,在我们了解数据结构及其操作的基础上,直接使用标准库不用重复造轮子,可以提高工作效率,因此本次开发采用 STL 。 STL 组件包括容器、迭代器、算法三部分, STL 的基本观念是将数据与操作分离,数据由容器类管理,操作则由可定制的算法负责,迭代器在两者之间充当粘合剂,使任何算法都可以和任何容器交互运作。
首先我们需要选择容器,因为是线性表,自然选择顺序容器,有 vector 、 string 、 deque和 list ,因为不需要后续的插入与删除操作,故选择 vector 向量容器。
其次是数据结构设计,我们需要建立结构体方便储存从文件中读取的信息。根据实验要求,我们建立学生与课程两个结构体。课程信息结构体 Course 包含的数据项有课程编号、课程名和成绩,因为这3种都是字符串,所以使用 string 来分别定义为 id 、 name 和 score ;学生信息结构体Student包含的数据项有学号、姓名、班号和成绩,因为前三种数据都是字符串,所以使用 string 来分别定义为 id 、 name 和 classNum ,而成绩有多项数据,因此定义为课程信息结构体 Course 的 vector 容器 score 。同时为了保存学生信息和课程信息,创建包含 Student 结构体类型的 vector 向量容器 studentList 和包含 Course 结构体类型的 vector 向量容器 courseList 。
三、算法设计
1.读入文件功能
本实验要求从文本文件中读取学生成绩到线性表中,涉及的算法为 C++ 文件读取。
1.1第一步:写头文件 #include <fstream>
要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 <fstream> 。 iostream 标准库提供了 cin 和 cout 方法分别用于从标准输入读取流和向标准输出写入流。fstream 标准库定义了三个数据类型: ofstream 、 ifstream 、 fstream ,可以创建文件,向文件写入信息,从文件读取信息。
1.2第二步:采用open() 函数打开文件
本次实验只需要打开文件进行读操作,则使用 ifstream 对象。随后采用 open() 函数打开文件。open() 函数标准语法为:
void open(const char *filename, ios::openmode mode);
在这里, open() 成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式。
模式标志 | 描述 |
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
由于本次实验只需要打开文件进行读操作,因此定义文件被打开的模式为 ios::in。
1.3第三步:使用流提取运算符( >> )从文件读取信息
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里使用的是 ifstream 或 fstream 对象,而不是 cin 对象。本题使用的是fstream 对象。详细读取部分算法分析见2.3部分。
1.4第四步:使用close() 函数关闭文件
close() 函数是 fstream 、 ifstream 和 ofstream 对象的一个成员, close() 函数的标准语法为:
void close();
1.5读入文件功能的伪代码:
打开文件(file)
如果文件打开成功,则
创建一个缓冲区(buffer)来存储读取的数据
初始化数据计数器(count)为0
重复执行直到文件末尾:
从文件中读取数据到缓冲区
增加数据计数器
处理缓冲区中的数据
关闭文件
否则,
报告文件打开错误
2.vector 容器添加元素、获取迭代器等功能
本实验要求将学生基本信息、课程信息和学生成绩建立线性表来存储,涉及 C++ 顺序容器 vector 算法。
vector 是一个能够存放任意类型的动态数组,能够增加和压缩数据。因为是动态数组,当我们无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间的目的。 vector 也是一个类模板( class template )。模板允许我们编写单个类或函数定义,这个类和函数定义可用于不同的数据类型上。因此,我们可以定义保存 string 对象的 vector ,或保存 int 值的 vector ,又或是保存自定义的类类型对象的 vector 。
2.1第一步:写头文件 #include <vector> ,使用全局命名域方式 using namespace std
或者也可以使用 using std::vector; 。
2.2创建 vector 对象 studentList 和 courseList
必须说明 vector 保存何种对象的类型,通过将类型放在类模板名称后面的尖括号中来指定类型,例如 vector<int> vec; 。本题类型为我们自己定义的类型 Student 和 Course 。
2.3使用 vec.push_back(a) 添加元素
push_back() 函数将一个新的元素加到 vector 的最后面,位置为当前最后一个元素的下一个元素。
以本次实验的读取学生成绩为例,首先定义局部变量n为学生人数,读取学生人数。
随后定义一个结构体Student类型的student变量,使用流提取运算符( >> )从文件读取信息,然后采用 studentList.push_back(student) 添加成绩元素,以n--为条件进行while循环来循环初始化学生信息。
2.4结合获取迭代器使用sort函数对学生信息升序排列
vec.begin() 是返回vec的首元素; vec.end() 是返回尾元素之后元素的位置,即最后一个单元+1的指针,获取迭代器得到第一位学生与最后一位学生的指针,结合 sort() 函数来实现对学生信息升序排列。详细 sort() 排序函数见3.3部分。
2.5vector容器添加元素、获取迭代器等功能伪代码为:
声明一个vector容器
while循环初始化学生信息
定义一个结构体变量
读取变量信息
向容器中添加元素
定义一个vector容器的迭代器
for循环
使用迭代器遍历vector容器的所有元素
3.vector排序功能
在本次实验中,我们需要按照班号递增排序输出所有学生的成绩,同时相同班号的学生按学号递增排序,同一个学生的成绩按课程编号递增排序。对于 C++ 的排序, C++ 标准库里有排序函数。排序函数 sort() 就是最常用的。
3.1写头文件#include<algorithm>
在 C++ 中使用 sort() 函数需要使用 #include<algorithm> 头文件。 algorithm 意为"算法",是 C++ 的标准模版库(STL)中最重要的头文件之一,提供了大量基于迭代器的非成员模版函数。
3.2排序函数sort()基本使用方法
sort函数可以对给定区间所有元素进行排序。 sort() 函数标准语法为:
sort(begin, end, cmp)
其中 begin 为指向待 sort() 的数组的第一个元素的指针,end为指向待 sort() 的数组的最后一个元素的下一个位置的指针, cmp 参数为排序准则, cmp 参数可以不写,如果不写的话,默认从小到大进行排序。
3.3自定义排序准则
sort() 函数可以自定义排序准则,以便满足不同的排序情况。
在本次实验中,我们自定义3个辅助排序函数,分别是学号升序排序函数 num_sort(Student a, Student b) 、班号升序排序函数 cla_sort(Student a, Student b) 、课程编号升序排序函数cou_sort(Course a, Course b)。
在从文件读取信息时,使用 sort() 函数排序的准则就写我们自定义的3个辅助排序函数。
3.4 vector 排序功能伪代码:
自定义排序准则
声明一个 vector 容器
对容器进行自定义准则排序
四、功能模块程序流程图
五、实验结果
向txt文件中输入数据:
5
1 陈斌 101
3 王辉 102
5 李君 101
4 鲁明 101
2 张昂 102
3
2 数据结构
1 C程序设计
3 计算机导论
15
1 1 82
4 1 78
5 1 85
2 1 90
3 1 62
1 2 77
4 2 86
5 2 84
2 2 88
3 2 80
1 3 60
4 3 79
5 3 88
2 3 86
3 3 90
经过文件读取学生基本信息、课程信息和学生成绩以及建立线性表、按照班号递增排序输出所有学生的成绩,同时相同班号的学生按学号递增排序,同一个学生的成绩按课程编号递增排序的处理,得出实验结果如下:
六、算法分析
1.时间复杂度 O(nlogn) : 其中n是线性表的长度。 总遍历轮数为 logn ~ n 次。
2.空间复杂度 O(logn) : 主要为递归的空间开销。
七、操作说明
1.本次实验程序输入的数据为学生成绩的 txt 文件, txt 文件的输入规范为:
第一行为整数n,表示学生数,换行;
接下来为n行学生基本信息,包括学号、姓名和班号,3种数据中间以空格区分;
接下来为整数m,表示课程信息数,换行;
接下来为m行课程信息,包括课程编号和课程名,2种数据中间以空格区分;
再接下来为整数k,表示学生成绩数据量,换行;
然后为k行学生成绩,包括学号、课程编号和分数,3种数据中间以空格区分。
2.输入完学生成绩后保存 txt 文件,运行程序,程序自动读取 txt 文件并进行排序操作;
3.输出结果格式为:
第一行为按递增排序后第一位的班号,换行;
随后是学号按递增排序后第一位学生的学号、姓名,以及按课程编号递增排序的课程名及成绩,4种数据间以空格区分,不同课程换行;
接下来是同班学号排第二位学生的学号,接下来同理;
第一个班级的学生信息输出完后,显示按递增排序后第二位的班号,换行。
接下来同理。
以语句“退出成功,欢迎下次使用!”作为程序输出结束的标志。
4.按任意键退出程序。
八、源代码
#include <iostream> //io流输入输出
#include <iomanip> //setw场宽函数
#include <vector> //vector容器
#include <algorithm> // sort排序函数
#include <fstream> //读文件
using namespace std;
struct Course //课程信息结构体
{
string id; //课程编号
string name; //课程名
string score; //成绩
};
struct Student //学生信息结构体
{
string id; //学号
string name; //姓名
string classNum; //班号
vector<Course> score; //成绩,定义一个结构体变量的vector容器
};
//自定义函数
void read(); //将文件中的数据读入到结构体数组中
//容器
vector<Student> studentList;//创建包含Student结构体类型的向量studentList
vector<Course> courseList;//创建包含Course结构体类型的向量courseList
//三个辅助排序函数
//1_学号升序排序
bool num_sort(Student a, Student b)
{
return a.id < b.id; //学号升序排列
}
//2_班号升序排序
bool cla_sort(Student a, Student b)
{
if (a.classNum != b.classNum)
return a.classNum < b.classNum; //如果班号不同则返回班号小的,进行班号升序排序
else
return a.id < b.id; //如果班号相同,则返回学号小的,进行学号升序排序
}
//3_课程编号升序排序
bool cou_sort(Course a, Course b)
{
return a.id < b.id; //课程编号升序排列
}
//读取学生
//文件读
void read() {
ifstream fin;
fin.open("./exp1.txt", ios::in); //创建读取文件的流
if (!fin.is_open()) //若文件打开失败
{
cout << "无法找到这个文件!" << endl;//反馈打开文件失败
return;
}
puts(" 欢迎进入学生管理系统 "); //成功打开文件
//读取学生
int n; //学生人数
fin >> n; //读取学生人数
while (n--) //循环读取学生信息,初始化学生信息
{
Student student; //新建结构体对象
fin >> student.id; //读取学号
fin >> student.name; //读取姓名
fin >> student.classNum; //读取班号
studentList.push_back(student);//向向量中添加结构体对象
}
sort(studentList.begin(), studentList.end(), num_sort); // 对每一个初始化的学生信息从头到尾使用sort进行学号升序排列
//读取课程
int m; //课程数量
fin >> m; //读取课程数量
while (m--) //初始化课程信息
{
Course course; //新建结构体对象
fin >> course.id; //读取课程编号
fin >> course.name; //读取课程名
courseList.push_back(course); //向向量中添加结构体对象
}
sort(courseList.begin(), courseList.end(), cou_sort); //课程升序排序
//读取学生成绩
int k; //学生成绩数量
fin >> k; //读取学生成绩数量
while (k--) //初始化学生成绩信息
{
Course course; //新建结构体对象
int studentId; //定义局部变量学生学号
fin >> studentId >> course.id >> course.score; //读取学号、课程编号、成绩
studentList[studentId - 1].score.push_back(course); //别忘了索引值是从0开始的
}
//后续处理 使用迭代器遍历向量中的结构体对象,将每个学生的成绩按课程编号升序排好
for (auto x : studentList) {
sort(x.score.begin(), x.score.end(), cou_sort);
}
sort(studentList.begin(), studentList.end(), cla_sort); //重新按班号排序学生
//输出
string tag = "";
for (auto x : studentList)//使用迭代器遍历向量中的结构体对象,输出学生成绩信息
{
if (x.classNum != tag) //第一论:如果班号不为空,则输出班级的学生成绩信息;后面几轮,如果班号相同,则输出同一班级学生成绩信息
{
cout << "===============班号:" << x.classNum << "===============" << endl;
tag = x.classNum;
}
cout << x.id << " " << x.name << " " << endl;//输出学号、姓名
for (int i = 0; i < courseList.size(); i++)//输出课程成绩
{
cout << courseList[i].name;//输出课程名
cout << setiosflags(ios::right) << setw(20 - courseList[i].name.size()) << x.score[i].score << endl;//输出课程成绩
}
cout << endl;//输出完一个学生的成绩信息,换行
}
cout << "退出成功,欢迎下次使用!" << endl;//退出系统提示
fin.close(); //关闭文件
}
//主函数
int main()
{
read(); //将文件中的学生信息读取到容器中
return 0;
}