《数据结构课程设计》系列文章一(学生成绩档案管理系统)
《数据结构课程设计》系列文章一(学生成绩档案管理系统)
《数据结构课程设计》系列文章二(隐式图的搜索问题)
《数据结构课程设计》系列文章三(文本文件单词的检索与计数)
前言
本文主要是为学生成绩档案管理系统项目所做的预习准备,后期项目完成会附录源码和实现代码分析
一、实验内容
二、实验思路
1.功能图分析
2.算法与数据结构分析
1.数据结构与算法:
- 逻辑结构:采用线性结构
在本项目中,我采用的是线性结构中的线性表;
- 存储结构:采用链式存储结构
在本项目中,我采用的是非线性结构中的单链表结构;
- 算法::
在本项目中,我在进行学生成绩排序时,准备采用冒泡算法;
2.项目中的具体实现:
①定义数据结构
//定义学生节点
struct studentNode{
//学号
string id;
//姓名
string name;
...
struct studentNode*next;
}
②主要算法描述及预计实现(给出的均是算法部分代码,后期有可能会改动)
- 浏览学生信息方法:
第一步:定义一个指针,并让它指向头结点;
第二步:如果链表为空,则需要输出提示信息,反之,则一边向后遍历一边输出学生信息,一直到链表最后一个结点;
if (head == NULL) {
cout << "学生成绩管理档案库为空!" << endl;
}
//链表不为空
else {
while (p1) {
cout << p1->id << "\t" << p1->name << "\t" << p1->professional << "\t" << p1->java << "\t\t" << p1->cpp << "\t\t" << p1->sql << "\t\t" << p1->web << "\t\t" << p1->sumScore << endl;
p1 = p1->next;
}
}
- 增加学生信息方法:
第一步:定义三个节点p1,p2,s,其中p1指向当前处理节点,p2始终指向p1的前一个结点,s节点为新添加进来的结点;
第二步:如果链表为空,则需要输出提示信息,反之,则分两种情况进行插入新结点,其一是要新结点插入位置为头结点前,其二是要插入的结点为链表中间位置或者尾部位置(中间和尾部情况相似);
if (head == NULL) {
head = s;
//return head;
}
else {
p1 = p2 = head;//p1,p2均指向头结点
//根据学号大小进行排序,学号小在前,学号大在后
//p2是p1的前一个节点
while (p1 != NULL && s->id > p1->id) {
p2 = p1;
p1 = p1->next;
p2->next = p1;
}
//要插入的节点学号比头结点小,即插入位置为头结点前
if (p1 == head) {
s->next = p1;
// p1->next = NULL;
head = s;
}
//插入位置是链表中间和尾部
else {
p2->next = s;
s->next = p1;
}
- 删除学生信息:
第一步:确定检索项,我准备采用两种检索项,其一通过姓名+专业(添加专业,是为了尽可能减少重名率)进行检索,其二通过学号进行检索;
第二步:通过姓名加专业进行检索,定义两个结点p1,p2,其中p2始终指向p1的前一个结点,其中p1为要删除的结点,删除位置我预计会分为三种,其一,要删除的结点在头部,且链表长度为1,直接将头结点置空,删除p1即可;其二,要删除的结点在头部,且链表长度大于1,将头结点指向p1的下一个结点,删除p1即可;其三,要删除的结点在链表中间或者尾部,将p2的下一个结点指向p1的下一个结点,将p1删除即可;
第三步:通过学号进行删除的方法和通过姓名+专业的方法是类似的,区别仅仅是在检索名称的不同而已。
while (p1 != NULL) {
if (p1->name == deleteName && p1->professional == deleteProfessional) {
count++;
}
//要删除的节点在头部,且链表长度为1
if (p1 == head && index == count && p1->next == NULL) {
head = NULL;
delete p1;
break;
}
//删除的节点在头部,且链表长度大于1
else if (p1 == head && p1->next != NULL && index == count) {
head = p1->next;
delete p1;
break;
}
//要删除的节点在中间
else if (p1->next != NULL && index == count) {
p2->next = p1->next;
delete p1;
break;
}
//要删除的节点在尾部
else if (p1->next == NULL && index == count) {
p2->next = NULL;
delete p1;
break;
}
p2 = p1;
p1 = p1->next;
//没有符合条件的学生
if (count == 0) {
cout << "没有找到符合条件的学生!" << endl;
}
else {
cout << "删除成功!" << endl;
}
- 学生成绩排名:
① 冒泡排序
第一步:我暂时先实现的单向冒泡排序算法;
第二步:定义两个结点p1,p2,其中p1指向头结点,p2指向p1的下一个结点,在双层循环下,分为两种情况,其一,第一个结点和第二个结点就需要交换位置,让p1的下一个结点指向p2的下一个结点,让p2的下一个结点指向p1,交换完后,需要把三者的位置归位,其二,在中间或者尾部需要进行交换,要定义一个结点始终指向p1的前一个结点,让p1的下一个结点指向p2的下一个结点,p2的下一个结点指向p1,p3的下一个结点指向p2,然后再进行和第二一样的指针归位,自己画图会更清晰直白;
if (head == NULL) {
cout << "学生成绩档案管理库没有信息记录!" << endl;
}
else {
for (int i = 0;i < countOfStudent - 1;i++) {
p1 = head;
p2 = p1->next;
p3 = head;
for (int j = 0;j < countOfStudent - 1 - i;j++) {
p3 = head;
//在头结点就需要交换
if (p1->sumScore > p2->sumScore && p1 == head) {
p1->next = p2->next;
p2->next = p1;
head = p2;
//归位
prev = p1;
p1 = p2;
p2 = prev;
}
//前面的值比后面的值要大,则进行交换
else if (p1->sumScore > p2->sumScore) {
//p3指向p1的前一个结点
while (p3->next != p1) {
p3 = p3->next;
}
p1->next = p2->next;
p2->next = p1;
p3->next = p2;
//归位
prev = p1;
p1 = p2;
p2 = prev;
}
p1 = p1->next;
p2 = p2->next;
}
}
p1 = head;
while (p1 != NULL) {
cout << p1->sumScore << " ";
p1 = p1->next;
}
}
②双向冒泡排序
- 原理:
在基于冒泡排序的基础上,我们知道,无论是从前向后遍历交换,还是从后向前遍历交换,对程序的逻辑和性能的代价都是不影响的,那么我们就可以让一部分情况下从前向后遍历交换,另一部分情况从后向前遍历交换。 - 算法步骤:
1.比较相邻两个元素的大小。如果前一个元素比后一个元素大,则两元素位置交换
2.对数组中所有元素的组合进行第1步的比较
3.奇数趟时从左向右进行比较和交换
4.偶数趟时从右向左进行比较和交换
5.当从左端开始遍历的指针与从右端开始遍历的指针相遇时,排序结束
③希尔排序
-
原理:
希尔排序是将待排序的数组元素 按下标的一定增量分组 ,分成多个子序列,然后对各个子序列进行直接插入排序算法排序;然后依次缩减增量再进行排序,直到增量为1时,进行最后一次直接插入排序,排序结束。 -
算法步骤:
④堆排序
-
原理:
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。 -
算法步骤:
⑤快速排序 -
原理:
快速排序算法的思想非常简单,在待排序的数列中,我们首先要找一个数字作为基准数(这只是个专用名词)。为了方便,我们一般选择第 1 个数字作为基准数(其实选择第几个并没有关系)。接下来我们需要把这个待排序的数列中小于基准数的元素移动到待排序的数列的左边,把大于基准数的元素移动到待排序的数列的右边。这时,左右两个分区的元素就相对有序了;接着把两个分区的元素分别按照上面两种方法继续对每个分区找出基准数,然后移动,直到各个分区只有一个数时为止。 -
算法步骤:
三、编程语言及开发环境
编程语言:C++
开发环境:Visual Studio2019