前言
本篇博客以管理学生的姓名学号为背景,大致归纳了单链表的几种基本操作,由于主要目的是归纳基本操作,并未做很全面的优化,暂时只支持数据数在三组及以上的操作。注释中列出的是博主自己在敲代码的时候遇到并解决的小坑还有一些大概解释
讲正事
准备工作——各种结构体的定义
定义结点
typedef struct student {
int serial_number;
char name[10];
char number[10];
struct student* next;
}Student;
定义专用于储存链表信息的结构体
typedef struct linklist {
Student* head;
Student* tail;
}Linklist;
如果使用结构体单独存放每一个链表的信息(头、尾指针),在多个链表同时存在时,对链表的调用会更加有序。
创建链表
因为本篇博客把创建链表的部分和“增”的部分放在一个函数里面了,没有单独写成一个函数,所以这部分就没有代码块啦
增
头插法
void add_head(Linklist* linklist) {
int judge = 1;
Student* p = NULL;
while (judge) {
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) {
//如果链表已经存在至少一个有效结点
p->next = linklist->head;
linklist->head = p;
}
else {
//链表当前无有效结点
linklist->head = p;
linklist->tail = p;
}
printf("继续输入请按1,结束输入请按0:");
scanf("%d", &judge);
}
}
尾插法
void add_tail(Linklist* linklist) {
int judge = 1;
Student* p = NULL;
while (judge) {
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) {
linklist->tail->next = p;
linklist->tail = p;
}
else {
linklist->head = p;
linklist->tail = p;
}
printf("继续输入请按1,结束输入请按0:");
scanf("%d", &judge);
}
}
无论是头插法还是尾插法,都要注意对空链表的处理进行分类
删
void delete_sth(Linklist* linklist) {
int n;
Student* p = linklist->head;
Student* q = NULL;
printf("请输入要删除的学生编号:");
scanf("%d", &n);
while (p) {
if (p->serial_number == n) {
//如果恰好要删除第一个结点,要移动头的位置,不然会丢失
linklist->head = p->next;
free(p);
break;
}
else 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;
}
}
if (!p) {
printf("您输入的编号不存在\n");
}
}
改
void amend(Linklist* linklist) {
int n;
Student* p = linklist->head;
printf("请输入要修改的学生编号:");
scanf("%d", &n);
while (p) {
if (p->serial_number != n) {
p = p->next;
}
else if (p->serial_number == n) {
break;
}
}
if (!p) {
printf("您输入的编号不存在\n");
}
else {
printf("请输入改正后的学生姓名和学号");
scanf("%s %s", p->name, p->number);
}
}
逆置(三指针)
void reverse(Linklist* linklist) {
Student* q = linklist->head;
Student* p = q;
Student* r = q->next;//因为r不为空才能进入循环,这里提前赋值了
//特殊处理第一个结点
q->next = NULL;
linklist->tail = q;
while (r) {
p = q;
q = r;//这里不能是 q = q->next 否则在从第一结点向第二结点移动的时候会出错
r = r->next;
q->next = p;
}
linklist->head = q;
}
遍历并输出整个链表
void traversal(Linklist* linklist) {
Student* p = linklist->head;
//令p指向第一个有效结点
while (p) {
printf("%d\t", p->serial_number);
printf("%s\t%s\n", p->name, p->number);
p = p->next;
}
}
在对带头结点和不带头结点的链表进行总结后发现,其实这两者的区别并不大,无非就是处理的时候注意头尾指针的处理和指向第一个有效结点的方式不太一样罢了。