前言
本篇博客以管理学生的姓名学号为背景,大致归纳了单链表的几种基本操作,由于主要目的是归纳基本操作,并未做很全面的优化,暂时只支持数据数在三组及以上的操作。注释中列出的是博主自己在敲代码的时候遇到并解决的小坑还有一些大概解释。
讲正事
准备工作——各种结构体的定义
定义结点
typedef struct student {
int serial_number; //序号
char name[10]; //学生姓名
char number[10]; //学生学号
struct student* next;
}Student;
定义专用于储存链表信息的结构体
typedef struct linklist {
Student* head;
Student* tail;
}Linklist;
还是要解释一下,使用结构体单独存放每一个链表信息,是为了在多个链表同时存在时,对链表的调用更加有序。
创建链表
Linklist Initlist() {
Linklist linklist;
//创建一个头结点,并让头指针和尾指针指向该结点
Student* p = (Student*)malloc(sizeof(Student));
p->next = NULL;
linklist.head = p;
linklist.tail = p;
return linklist;
}
增
头插法
void add_head(Linklist* linklist) {
int judge;//用于判断是否要继续插入新结点
while (1) {
Student* p = (Student*)malloc(sizeof(Student));
printf("请输入学生姓名:");
scanf("%s", p->name);
printf("请输入该生编号:");
scanf("%d", &p->serial_number);
printf("请输入学生学号:");
scanf("%s", p->number);
p->next = NULL;
if (linklist->head->next) {
//如果该结点不是链表的第一个有效结点,直接插就好了
p->next = linklist->head->next;
//↑令新结点与原来的第一个有效结点相连防丢失
linklist->head ->next= p;
//再令头结点与新结点相连
}
else {
//如果该结点是链表的第一个有效结点,那么它同时是链表的尾
linklist->head->next = p;
linklist->tail = p;
}
printf("继续输入请按1,输入完毕请按0:");
scanf("%d", &judge);
if (0 == judge) {
break;
}
}
}
尾插法
void add_tail(Linklist* linklist) {
int judge;
while (1) {
Student* p = (Student*)malloc(sizeof(Student));
printf("请输入学生姓名:");
scanf("%s", p->name);
printf("请输入该生编号:");
scanf("%d", &p->serial_number);
printf("请输入学生学号:");
scanf("%s", p->number);
p->next = NULL;
if (linklist->head->next) {
//如果不是第一个有效结点,直接尾插
linklist->tail->next = p;
linklist->tail = p;
}
else {
linklist->head->next = p;
linklist->tail = p;
}
printf("继续输入请按1,输入完毕请按0:");
scanf("%d", &judge);
if (0 == judge) {
break;
}
}
}
删
void delete_sth(Linklist* linklist) {
int n;
Student* p = linklist->head;
//指向头结点是考虑到被删结点可能是第一个有效结点的情况
Student* q = NULL;
printf("请输入要删除的学生编号:");
scanf("%d", &n);
while (p) {
if (p->next->serial_number != n) {
p = p->next;
}
else if (p->next->serial_number == n) {
//p会停在要删结点的前一个
q = p->next;//q指向要删结点
p->next = q->next;
free(q);
break;
}
}
}
这里没有处理输入的序号不在链表中的情况,可自行补充
改
void amend(Linklist* linklist) {
int n;
Student* p = linklist->head->next;
printf("请输入要修改的学生编号:");
scanf("%d", &n);
while (p) {
if (p->serial_number != n) {
p = p->next;
}
else if (p->serial_number == n) {
break;
}
}
if (!p) {
printf("您输入的编号不存在");
}
else {
printf("请输入要改正的学生姓名和学号");
scanf("%s %s", p->name, p->number);
}
}
逆置(三指针)
void reverse(Linklist* linklist) {
Student* p = linklist->head->next;
Student* q = p, * r = p->next;
//先对第一个结点做特殊处理
p->next = NULL;//先处理第一个结点的指针域
linklist->tail = p;//第一个结点是逆置后的尾
while (r) {
/*如果先连接再挪,可能会导致r为空时不再进入循环,
但最后两个结点还没有连接起来,要做特殊处理,就很麻烦*/
q = p;
p = r;
r = r->next;
p->next = q;//连接
}
//出循环后,p恰好指向最后一个链表,把他和头结点连在一起
linklist->head->next = p;
}
遍历并输出整个链表
void traversal(Linklist* linklist) {
Student* p = linklist->head->next;
//令p指向第一个有效结点
while (p) {
printf("%d\t%s\t%s\n", p->serial_number,p->name, p->number);
p = p->next;
}
}