deepseek助手:数据结构课程设计(大作业):理解链表: 基于双向链表实现的学生管理系统
下面是一个基于双向链表实现的学生管理系统,采用C语言编写,严格按照模块化设计,包含详细注释,并在关键操作中描述了链表的指针变化过程。
---
## 系统设计说明
- **数据结构**:双向链表,每个节点包含学生信息(学号、姓名、成绩)以及指向前驱和后继的指针。
- **功能模块**:
- 添加学生(按学号自动升序插入,保证链表有序)
- 删除学生(按学号查找并删除,释放内存)
- 修改学生信息(先查找,后修改)
- 查找学生(按学号返回节点)
- 显示所有学生(遍历链表)
- 退出系统(释放整个链表)
- **模块划分**:
- `student.h`:头文件,定义结构体和函数原型。
- `student.c`:实现所有链表操作函数,内部包含详细注释说明指针变化。
- `main.c`:主程序,提供菜单交互,调用链表函数。
代码中每个关键步骤都附有注释,解释指针的重新连接过程,直观展示双向链表的“运动”方式。
---
## 代码实现
### 1. student.h
```c
#ifndef STUDENT_H
#define STUDENT_H
// 学生结构体,同时也是双向链表节点
typedef struct student {
int id; // 学号
char name[20]; // 姓名
float score; // 成绩
struct student *prev; // 指向前一个节点
struct student *next; // 指向后一个节点
} Student;
// 函数声明
Student* createStudent(int id, const char *name, float score);
void insertStudent(Student **head, Student *newNode);
void deleteStudent(Student **head, int id);
Student* findStudent(Student *head, int id);
void updateStudent(Student *head, int id, const char *newName, float newScore);
void displayStudents(Student *head);
void freeList(Student **head);
#endif
```
### 2. student.c
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "student.h"
/**
* 创建新学生节点
* 分配内存,初始化数据,前后指针置NULL
*/
Student* createStudent(int id, const char *name, float score) {
Student *newNode = (Student*)malloc(sizeof(Student));
if (newNode == NULL) {
printf("内存分配失败!\n");
return NULL;
}
newNode->id = id;
strcpy(newNode->name, name);
newNode->score = score;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
/**
* 按学号升序插入新节点(保证链表始终有序)
* 参数 head 为二级指针,因为可能修改头指针
* 插入过程详细注释描述了双向链表指针的变化
*/
void insertStudent(Student **head, Student *newNode) {
// 情况1:链表为空,新节点成为头结点
if (*head == NULL) {
*head = newNode;
printf("链表为空,新节点 %d 作为头结点插入。\n", newNode->id);
return;
}
// 情况2:新节点学号小于头结点,插入到头部
if (newNode->id < (*head)->id) {
// 新节点的next指向原头结点
newNode->next = *head;
// 原头结点的prev指向新节点
(*head)->prev = newNode;
// 更新头指针为新节点
*head = newNode;
printf("新节点 %d 学号最小,插入到链表头部。\n", newNode->id);
return;
}
// 情况3:寻找插入位置(在有序链表中,找到第一个大于新学号的节点的前一个位置)
Student *current = *head;
// 遍历,直到当前节点的下一个节点为空,或者下一个节点的学号大于新学号
while (current->next != NULL && current->next->id < newNode->id) {
current = current->next;
}
// 此时有两种可能:
// 1) current->next == NULL,说明新节点学号最大,插入到尾部
// 2) current->next->id >= newNode->id,插入到current之后,current->next之前
// 将新节点的next指向current->next
newNode->next = current->next;
// 如果current->next存在,则将其prev指向新节点
if (current->next != NULL) {
current->next->prev = newNode;
}
// 将current的next指向新节点
current->next = newNode;
// 新节点的prev指向current
newNode->prev = current;
if (newNode->next == NULL) {
printf("新节点 %d 学号最大,插入到链表尾部。\n", newNode->id);
} else {
printf("新节点 %d 插入到学号 %d 和学号 %d 之间。\n",
newNode->id, current->id, newNode->next->id);
}
}
/**
* 按学号删除节点
* 释放被删除节点的内存
* 注释说明了不同情况下指针的重连
*/
void deleteStudent(Student **head, int id) {
if (*head == NULL) {
printf("链表为空,无法删除。\n");
return;
}
Student *current = *head;
// 查找要删除的节点
while (current != NULL && current->id != id) {
current = current->next;
}
if (current == NULL) {
printf("未找到学号为 %d 的学生。\n", id);
return;
}
// 根据位置分情况处理
if (current == *head) {
// 删除头结点:头指针指向下一个节点,新头结点的prev置NULL
*head = current->next;
if (*head != NULL) {
(*head)->prev = NULL;
}
printf("删除头结点,学号 %d。\n", id);
}
else if (current->next == NULL) {
// 删除尾节点:将前一个节点的next置NULL
current->prev->next = NULL;
printf("删除尾节点,学号 %d。\n", id);
}
else {
// 删除中间节点:
// 前一个节点的next指向当前节点的下一个
current->prev->next = current->next;
// 下一个节点的prev指向当前节点的上一个
current->next->prev = current->prev;
printf("删除中间节点,学号 %d。\n", id);
}
free(current); // 释放内存
}
/**
* 根据学号查找节点
* 返回节点指针,若未找到返回NULL
*/
Student* findStudent(Student *head, int id) {
Student *current = head;
while (current != NULL) {
if (current->id == id) {
return current;
}
current = current->next;
}
return NULL;
}
/**
* 更新学生信息(姓名、成绩)
* 先查找,找到后直接修改数据,链表结构不变
*/
void updateStudent(Student *head, int id, const char *newName, float newScore) {
Student *stu = findStudent(head, id);
if (stu == NULL) {
printf("学号为 %d 的学生不存在,无法更新。\n", id);
return;
}
strcpy(stu->name, newName);
stu->score = newScore;
printf("学生信息更新成功。\n");
}
/**
* 显示所有学生信息(从头到尾遍历)
*/
void displayStudents(Student *head) {
if (head == NULL) {
printf("当前学生列表为空。\n");
return;
}
printf("学生列表(按学号升序):\n");
Student *current = head;
while (current != NULL) {
printf("学号: %d, 姓名: %s, 成绩: %.2f\n",
current->id, current->name, current->score);
current = current->next;
}
}
/**
* 释放整个链表,防止内存泄漏
*/
void freeList(Student **head) {
Student *current = *head;
while (current != NULL) {
Student *temp = current;
current = current->next;
free(temp);
}
*head = NULL;
printf("链表内存已释放。\n");
}
```
### 3. main.c
```c
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
int main() {
Student *head = NULL; // 链表头指针,初始为空
int choice, id;
char name[20];
float score;
do {
printf("\n========== 学生管理系统(双向链表) ==========\n");
printf("1. 添加学生\n");
printf("2. 删除学生\n");
printf("3. 修改学生信息\n");
printf("4. 查找学生\n");
printf("5. 显示所有学生\n");
printf("6. 退出系统\n");
printf("请输入你的选择: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入学号: ");
scanf("%d", &id);
// 检查学号是否已存在
if (findStudent(head, id) != NULL) {
printf("学号已存在,添加失败。\n");
break;
}
printf("请输入姓名: ");
scanf("%s", name);
printf("请输入成绩: ");
scanf("%f", &score);
Student *newStu = createStudent(id, name, score);
if (newStu != NULL) {
insertStudent(&head, newStu);
printf("学生添加成功。\n");
}
break;
case 2:
printf("请输入要删除的学号: ");
scanf("%d", &id);
deleteStudent(&head, id);
break;
case 3:
printf("请输入要修改的学号: ");
scanf("%d", &id);
printf("请输入新的姓名: ");
scanf("%s", name);
printf("请输入新的成绩: ");
scanf("%f", &score);
updateStudent(head, id, name, score);
break;
case 4:
printf("请输入要查找的学号: ");
scanf("%d", &id);
Student *stu = findStudent(head, id);
if (stu != NULL) {
printf("找到学生:学号: %d, 姓名: %s, 成绩: %.2f\n",
stu->id, stu->name, stu->score);
} else {
printf("未找到该学号的学生。\n");
}
break;
case 5:
displayStudents(head);
break;
case 6:
freeList(&head);
printf("感谢使用学生管理系统,再见!\n");
break;
default:
printf("无效选择,请重新输入。\n");
}
} while (choice != 6);
return 0;
}
```
---
## 链表实际运动过程描述
在双向链表的插入和删除操作中,指针的重新连接是核心。以插入操作为例,其过程如下(参考 `insertStudent` 函数中的注释):
- **插入到头部**:新节点的 `next` 指向原头结点,原头结点的 `prev` 指向新节点,然后头指针指向新节点。
- **插入到中间**:假设要在节点 `A` 和节点 `B` 之间插入新节点 `X`:
1. `X->next = B`(新节点指向后驱)
2. `B->prev = X`(后驱指回新节点)
3. `A->next = X`(前驱指向新节点)
4. `X->prev = A`(新节点指向前驱)
- **插入到尾部**:最后一个节点的 `next` 指向新节点,新节点的 `prev` 指向最后一个节点。
删除操作则是上述过程的逆过程,同时要注意释放被删除节点的内存,防止泄漏。
通过代码中的详细注释和运行时打印的位置信息,可以清晰观察每个操作如何改变链表结构。
更多推荐




所有评论(0)