学生成绩档案管理系统 实验准备
实验内容
建立一个学生成绩档案管理系统,系统满足以下要求:
- 学生信息录入,信息包括学号、姓名、专业、四门课成绩、总分、名次
- 系统可对学生信息浏览、增加、删除和修改
- 按学生成绩确定名次及信息输出,双向冒泡排序、希尔排序、快速排序、堆排序
- 要求可对学生信息查询,根据学号或姓名进行查找
- 信息修改仅可修改四门课成绩
- 文件存取学生信息
项目分析
- 读取学生信息(系统将磁盘文件中保存的学生成绩信息读取到内存中)
- 新增学生信息(学号、姓名、专业、四门课成绩)并保存
- 对学生总成绩进行排名(根据四门课成绩计算总分,通过四种方式:双向冒泡排序、希尔排序、快速排序、堆排序进行排序)
- 浏览学生信息(按排名输出信息:学号、姓名、专业、四门课成绩、总分、名次)
- 删除学生信息(删除学生)
- 修改学生信息(修改时只能修改四门课的成绩)
- 查找学生信息(根据学号或姓名进行查找)
编程语言的选择
- 编程语言:C++
- 开发工具:Visual Studio
项目思路
1.确定数据结构
(1)学生信息的结构体
代码如下:
struct student
{
char number[20]; //学号
char name[20]; //姓名
char major[20]; //专业
double mark1; //英语成绩
double mark2; //高数成绩
double mark3; //数据结构成绩
double mark4; //C++成绩
double sumMark; //总分
int rank; //排名
};
(2)链式存储结构的定义
struct Node
{
student data; //节点的数据域
Node* next; //节点的指针域
};
最初数据结构选择使用链表,但在程序的编写过程中,发现希尔排序和堆排序不太适合用于链表的排序,因此改为使用数组作为数据结构。
2.基本功能的实现
学生成绩档案管理系统的基本功能即为对学生信息的增删改查。
代码如下:
void insertNodeByHead(Node* listHeadNode, student data)
{
Node *newNode= createNode(data);
//连接
newNode->next = listHeadNode->next;
listHeadNode->next = newNode;
}
/*通过姓名查找并删除*/
void deleteNodeByAppoinName(Node* listHeadNode, char*name)
{
Node* posFrontNode = listHeadNode;
Node* posNode = listHeadNode->next;
if (posNode == nullptr)
{
cout << "无相关内容,无法删除!\n";
return;
}
else
{
while (strcmp(posNode->data.name,name))
{
posFrontNode = posNode;
posNode = posFrontNode->next;
if (posNode == nullptr)
{
cout<< "无相关内容,无法删除!\n";
return;
}
}
posFrontNode->next = posNode->next;
delete posNode;
}
}
/*通过学号查找*/
Node* searchNodeByAppoinNum(Node* listHeadNode, char* num)
{
Node* pMove = listHeadNode->next;
if (pMove == nullptr)
return pMove;
else
{
while (strcmp(pMove->data.number, num))
{
pMove = pMove->next;
if (pMove == nullptr)
break;
}
return pMove;
}
}
/*通过姓名查找*/
Node* searchNodeByAppoinName(Node* listHeadNode, char* name)
{
Node* pMove = listHeadNode->next;
if (pMove == nullptr)
return pMove;
else
{
while (strcmp(pMove->data.name, name))
{
pMove = pMove->next;
if (pMove == nullptr)
break;
}
return pMove;
}
}
3.排序的实现
(1)双向冒泡排序
算法原理
- 是传统冒泡气泡排序的双向进行,先让气泡排序由左向右进行,再来让气泡排序由右往左进行,如此完成一次排序的动作。
- 使用left与right两个旗标来记录左右两端已排序的元素位置。
算法步骤
- 比较相邻两个元素的大小。如果前一个元素比后一个元素大,则两元素位置交换
- 对数组中所有元素的组合进行第1步的比较
- 奇数趟时从左向右进行比较和交换
- 偶数趟时从右向左进行比较和交换
- 当从左端开始遍历的指针与从右端开始遍历的指针相遇时,排序结束
代码实现
(2)希尔排序
算法原理
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
算法步骤
- 根据初始序列的长度,选定一个递减增量序列t1,t2,t3…tk,其中ti>tj,tk = 1。
- 根据选定的增量序列元素个数k,对初始序列进行k趟排序。
- 根据增量序列的值ti,每趟排序将初始序列划分成若干个元素的子序列,对这些子序列使用插入排序。
(3) 快速排序
算法原理
快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立的排序,两个子数组都有序时,整个数组也就自然有序了。
算法步骤
- 从数组中选取一个主元
- 根据主元将数组切分成两部分,大于或等于主元的数组元素集中到数组右边,小于主元的数组元素集中到数组的左边
- 对左右两边的元素进行递归排序
(4)堆排序
算法原理
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
算法步骤
- 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
- 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
4.文件读写的实现
- 构造用于文件读、写的两个函数
- 通过调用链插入函数,将文件内容添加到链表中
- 通过将链表内容打印到文件内的方式,达到存放学生信息的目的
代码如下:
void readInformFile(const char* fileName, Node* listHeadNode)
{
FILE* fp = fopen(fileName, "r");
if (fp == nullptr)
{
fp = fopen(fileName, "w");
}
student tempData;
while (fscanf(fp,"%s\t%s\t%s\t%d\t%d\t%d\t%d\t%d\t%d\n", tempData.number , tempData.name ,tempData.major , &tempData.mark1 , &tempData.mark2 ,&tempData.mark3 , &tempData.mark4 , &tempData.sumMark , &tempData.rank)!=EOF)
{
insertNodeByHead(listHeadNode, tempData);
memset(&tempData, 0, sizeof(tempData));
}
fclose(fp);
}
void saveInfoToFile(const char* fileName, Node* listHeadNode)
{
FILE* fp = fopen(fileName, "w");
Node* pMove = listHeadNode->next;
while (pMove)
{
fprintf(fp, "%s\t%s\t%s\t%d\t%d\t%d\t%d\t%d\t%d\n", pMove->data.number, pMove->data.name, pMove->data.major, pMove->data.mark1, pMove->data.mark2, pMove->data.mark3, pMove->data.mark4, pMove->data.sumMark, pMove->data.rank);
pMove = pMove->next;
}
fclose(fp);
}