入门:链表的基本操作
标签:C语言 链表
By 小威威
1.写这篇博文的原因
C语言有三大重要部分:流程控制、函数、指针。
对于指针,单单了解它的简单应用是不够的,最重要的还是学习链表。许多参考书对链表的基本操作的概括还是不大完整的,所以在此通过这篇博文介绍链表的几种常用操作,以帮助初学者入门链表。
2.链表的基本操作主要有哪一些
1)链表的建立;
2)链表的输出;
3)链表结点的插入;
4)链表结点的删除;
5)链表各结点指向的倒置;
6)链表的粉碎。
补充:入门基本操作以后,可以进行其他操作:如各种类型的排序,这里就不介绍了,在以后的博文可能会介绍。
3.链表基本操作的完整代码
# include <stdio.h>
# include <stdlib.h>
struct student { // 用结构体定义链表的结点
int num; // 学号
float score; // 分数
struct student *next; // 存储的是指向下一个结构体的地址
};
struct student *establish(void); // 建立链表的函数的声明
struct student *lessen(struct student *head, int num); // 删除结点的函数的声明
struct student *insert(struct student *head, int num, struct student *node); // 插入结点的函数的声明
struct student *reserve(struct student *head); // 链表指向倒置的函数的声明
void output(struct student *head); // 输出链表的函数的声明
void deletelist(struct student *head); // 粉碎链表的函数的声明
int n; // 定义全局变量,表示链表中结点的个数
int main(void) {
struct student *head; // 定义链表的头指针
int num, num2; // num表示要删去的结点
head = establish(); // 调用建立链表的函数
output(head); // 调用输出链表的函数
printf("请输入你要删除的结点:\n");
scanf("%d", &num);
head = lessen(head, num); // 调用删除结点的函数
output(head); // 调用输出链表的函数
struct student *node = malloc(sizeof(struct student)); // 定义一个新的结点,并为他开辟一个空间
printf("请输入需要插入新结点的数据:\n");
scanf("%d%f", &node->num, &node->score); // 给该新结点赋值
printf("请输入要插入的位置(哪个结点之后):\n");
scanf("%d", &num2); // 输入要插入的位置
head = insert(head, num2, node); // 调用插入结点的函数
output(head); // 调用输出的函数
printf("现在对该链表的指向进行倒置\n");
head = reserve(head); // 调用链表指向倒置的函数
output(head); // 调用输出链表的函数
deletelist(head); // 调用粉碎链表的函数
return 0;
}
struct student *establish(void) { // 定义建立链表的函数
struct student *p1, *p2, *head; // 定义三个指针变量,p1、p2可以对相邻结点进行操作,head为头指针
n = 0; // 给n初始化,即结点为0
p1 = p2 = malloc(sizeof(struct student)); // 给两个指针变量分配动态内存
head = NULL; // 一定要将头指针置空。(如果没有初始化,一旦没有执行循环,该函数就会直接返回没有初始化的头指针,即没用的头指针)
printf("请输入数据(0 0结束):\n");
scanf("%d%f", &p1->num, &p1->score); // 给结点赋值
while (p1->num != 0) { // 建立一个while循环,判断条件是检测p1->num是否为0,以决定是否终止循环
n += 1; // 若不为0,则可建立新结点,即结点数+1
if (n == 1) // 将第一个结点的地址赋给头指针
head = p1;
else // 将p1的地址赋给p2,如此p1,p2就能实现对相邻结点的操作
p2 = p1;
p1 = malloc(sizeof(struct student)); // 建立新结点
p2->next = p1; // 把新结点的位置赋给新结点前的结点存储的地址区间内(也就是p2对应的结点)
scanf("%d%f", &p1->num, &p1->score); // 给新结点赋值
}
p2->next = NULL; // 循环结束后,p2就是最后一个结点,故将其存储的地址区间置空
free(p1); // 此时的p1起到的是结束循环的作用,是一个没有作用的结点,应该删去,故清空其内存,并加以置空
p1 = NULL; // 将无作用的结点置空
return head; // 返回头指针
}
struct student *lessen(struct student *head, int num) { // 动态链表结点的删除函数
struct student *p1, *p2 = NULL;//初始化,避免p2没有被初始化(在本程序中确实有可能)
if (head == NULL) {
printf("List is NULL\n");//先判断是否为空链表,如果是,返回头指针而不做其他操作
return head;
}
p1 = head;//把头指针赋给p1
while (p1->num != num && p1->next != NULL) { //while循环条件:结构体的数值不等于num且结构体地址不为NULL(即不对最后一个结点操作)
p2 = p1; //用p2保存p1的地址,便于储存所要删除的结点的地址区间的内容
p1 = p1->next; //结点后移
}
if (p1->num == num) { //判断该结点是否符合要求
if (p1 == head) { //判断该结点是否为第一个结点
head = p1->next; //如果删除的是第一个结点,则把第一个结点的地址区间的内容赋给head
printf("Delete %d success\n", num);
} else { //如果删除的不是第一个结点,则将所删除结点的地址区间内容赋给p2的地址区间
p2->next = p1->next;
printf("Delete %d success\n", num);
}
free(p1); //清空p1,即清空删除结点所占的内存
p1 = NULL; //将p1置空,避免生成野指针
n -= 1; //结点数减少一个
} else {
printf("Can't find the num in the list\n");
}
return head; //返回头指针
}
struct student *insert(struct student *head, int num, struct student *node) { //定义动态链表插入结点的函数
struct student *p1; // 定义一个p1,相当于结构体指针的副本
if (head == NULL) { // 倘若这是一个空链表,则加一个结点它就不是空链表了
head = node; // 所以就把新结点的位置赋给头指针head
node->next = NULL; // 将新结点的地址区间清空
n += 1; // 结点数加1
} else { //如果不是空链表,则执行接下来的操作
p1 = head; // 将头指针赋给p1,防止接下来的操作改变头指针的值
while (p1->num != num && p1->next != NULL) // 用一个while循环对目标结点(新结点前面的结点)进行搜索
p1 = p1->next; // 将链表推进
}
if (p1->num == num) { // 判断是否存在目标结点
node->next = p1->next; // 将node插入p1与原p1下一个结点之间,把p1指向下一结点的地址区间赋给node的地址区间
p1->next = node; // 再把node的地址赋给p1的地址区间
n += 1; // 结点数加一
} else {
printf("Cannot find the figure\n"); // 如果没有找到目标结点,则输出提示信息
}
return head; // 返回头指针
}
struct student *reserve(struct student *head) { //定义一个将链表指向倒置的函数
struct student *p, *p1, *p2; // 定义三个结构体指针变量,p用于暂时储存,p1,p2用于对相邻结点的操作
p1 = NULL; // p1指前面的结点(原链表),刚开始赋NULL是为了让第一个结点(也就是倒置后的尾结点)的地址区间清空
p2 = head; // p2指后面的结点(原链表),刚开始赋head是要通过p2对该链表进行操作
while (p2 != NULL) { // 定义一个while循环,第一是判断该链表是否为空链表,第二是判断是否到达了原先链表的尾结点
p = p2->next; // 先用p暂时储存p2所存储的下一个结点地址区间,目的是为了实现链表操作时结点的后移
p2->next = p1; // 然后把前面结点的地址赋给后面结点的地址区间,目的是使后一个结点指向前面的一个结点,以实现指向的倒置
p1 = p2; // 将p2位置赋给p1,因为结点后移时p1,p2的指向也要跟着后移
p2 = p; // 将下一个结点的位置赋给p2,完成结点的后移
}
head = p1; // 循环结束时,p1对应的就是新链表的第一个结点,故将其地址赋给头指针
return head; // 返回头指针
}
void output(struct student *head) { // 定义一个链表输出的函数
struct student *p1; // 定义结构体指针变量p1,用于结点的后移,以实现输出操作
p1 = head; // 将head赋给p1,以实现对该链表的操作
while (p1 != NULL) { // 建立一个while循环,结束条件是到达尾结点
printf("%d\n%f\n", p1->num, p1->score); // 输出结点中的数值部分
p1 = p1->next; // 将下一个结点的位置赋给p1
}
return;
}
void deletelist(struct student *head) {
struct student *p1; // 定义结构体指针变量副本
while (head != NULL) { // 建立一个while循环,条件是head不为空
p1 = head->next; // 将head所指向地址赋给p1
free(head); // 然后释放head指向的结构体所占的内存
head = p1; // 再把下一个结构体的地址赋给head
}
}
4.各基本操作的函数
1)链表的建立
struct student *establish(void) { // 定义建立链表的函数
struct student *p1, *p2, *head; // 定义三个指针变量,p1、p2可以对相邻结点进行操作,head为头指针
n = 0; // 给n初始化,即结点为0
p1 = p2 = malloc(sizeof(struct student)); // 给两个指针变量分配动态内存
head = NULL; // 一定要将头指针置空。(如果没有初始化,一旦没有执行循环,该函数就会直接返回没有初始化的头指针,即没用的头指针)
printf("请输入数据(0 0结束):\n");
scanf("%d%f", &p1->num, &p1->score); // 给结点赋值
while (p1->num != 0) { // 建立一个while循环,判断条件是检测p1->num是否为0,以决定是否终止循环
n += 1; // 若不为0,则可建立新结点,即结点数+1
if (n == 1) // 将第一个结点的地址赋给头指针
head = p1;
else // 将p1的地址赋给p2,如此p1,p2就能实现对相邻结点的操作
p2 = p1;
p1 = malloc(sizeof(struct student)); // 建立新结点
p2->next = p1; // 把新结点的位置赋给新结点前的结点存储的地址区间内(也就是p2对应的结点)
scanf("%d%f", &p1->num, &p1->score); // 给新结点赋值
}
p2->next = NULL; // 循环结束后,p2就是最后一个结点,故将其存储的地址区间置空
free(p1); // 此时的p1起到的是结束循环的作用,是一个没有作用的结点,应该删去,故清空其内存,并加以置空
p1 = NULL; // 将无作用的结点置空
return head; // 返回头指针
}
2)链表的输出
void output(struct student *head) { // 定义一个链表输出的函数
struct student *p1; // 定义结构体指针变量p1,用于结点的后移,以实现输出操作
p1 = head; // 将head赋给p1,以实现对该链表的操作
while (p1 != NULL) { // 建立一个while循环,结束条件是到达尾结点
printf("%d\n%f\n", p1->num, p1->score); // 输出结点中的数值部分
p1 = p1->next; // 将下一个结点的位置赋给p1
}
return;
}
3)链表结点的插入
struct student *insert(struct student *head, int num, struct student *node) { //定义动态链表插入结点的函数
struct student *p1; // 定义一个p1,相当于结构体指针的副本
if (head == NULL) { // 倘若这是一个空链表,则加一个结点它就不是空链表了
head = node; // 所以就把新结点的位置赋给头指针head
node->next = NULL; // 将新结点的地址区间清空
n += 1; // 结点数加1
} else { //如果不是空链表,则执行接下来的操作
p1 = head; // 将头指针赋给p1,防止接下来的操作改变头指针的值
while (p1->num != num && p1->next != NULL) // 用一个while循环对目标结点(新结点前面的结点)进行搜索
p1 = p1->next; // 将链表推进
}
if (p1->num == num) { // 判断是否存在目标结点
node->next = p1->next; // 将node插入p1与原p1下一个结点之间,把p1指向下一结点的地址区间赋给node的地址区间
p1->next = node; // 再把node的地址赋给p1的地址区间
n += 1; // 结点数加一
} else {
printf("Cannot find the figure\n"); // 如果没有找到目标结点,则输出提示信息
}
return head; // 返回头指针
}
4)链表结点的删除
struct student *lessen(struct student *head, int num) { // 动态链表结点的删除函数
struct student *p1, *p2 = NULL;//初始化,避免p2没有被初始化(在本程序中确实有可能)
if (head == NULL) {
printf("List is NULL\n");//先判断是否为空链表,如果是,返回头指针而不做其他操作
return head;
}
p1 = head;//把头指针赋给p1
while (p1->num != num && p1->next != NULL) { //while循环条件:结构体的数值不等于num且结构体地址不为NULL(即不对最后一个结点操作)
p2 = p1; //用p2保存p1的地址,便于储存所要删除的结点的地址区间的内容
p1 = p1->next; //结点后移
}
if (p1->num == num) { //判断该结点是否符合要求
if (p1 == head) { //判断该结点是否为第一个结点
head = p1->next; //如果删除的是第一个结点,则把第一个结点的地址区间的内容赋给head
printf("Delete %d success\n", num);
} else { //如果删除的不是第一个结点,则将所删除结点的地址区间内容赋给p2的地址区间
p2->next = p1->next;
printf("Delete %d success\n", num);
}
free(p1); //清空p1,即清空删除结点所占的内存
p1 = NULL; //将p1置空,避免生成野指针
n -= 1; //结点数减少一个
} else {
printf("Can't find the num in the list\n");
}
return head; //返回头指针
}
5)链表结点指向的倒置
struct student *reserve(struct student *head) { //定义一个将链表指向倒置的函数
struct student *p, *p1, *p2; // 定义三个结构体指针变量,p用于暂时储存,p1,p2用于对相邻结点的操作
p1 = NULL; // p1指前面的结点(原链表),刚开始赋NULL是为了让第一个结点(也就是倒置后的尾结点)的地址区间清空
p2 = head; // p2指后面的结点(原链表),刚开始赋head是要通过p2对该链表进行操作
while (p2 != NULL) { // 定义一个while循环,第一是判断该链表是否为空链表,第二是判断是否到达了原先链表的尾结点
p = p2->next; // 先用p暂时储存p2所存储的下一个结点地址区间,目的是为了实现链表操作时结点的后移
p2->next = p1; // 然后把前面结点的地址赋给后面结点的地址区间,目的是使后一个结点指向前面的一个结点,以实现指向的倒置
p1 = p2; // 将p2位置赋给p1,因为结点后移时p1,p2的指向也要跟着后移
p2 = p; // 将下一个结点的位置赋给p2,完成结点的后移
}
head = p1; // 循环结束时,p1对应的就是新链表的第一个结点,故将其地址赋给头指针
return head; // 返回头指针
}
6)链表的粉碎
void deletelist(struct student *head) {
struct student *p1; // 定义结构体指针变量副本
while (head != NULL) { // 建立一个while循环,条件是head不为空
p1 = head->next; // 将head所指向地址赋给p1
free(head); // 然后释放head指向的结构体所占的内存
head = p1; // 再把下一个结构体的地址赋给head
}
5.需要注意的地方
1.分配动态内存要注意释放并清空;
2.某些指针变量的初始化;
3.建立链表完成后记得将最后一个结点的地址区间置空;同样的,指向倒置时,记得将第一个结点的地址赋给头指针。
以上内容皆为本人观点,欢迎大家提出意见,共同探讨!