入门:链表的基本操作

入门:链表的基本操作

标签: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.建立链表完成后记得将最后一个结点的地址区间置空;同样的,指向倒置时,记得将第一个结点的地址赋给头指针。


以上内容皆为本人观点,欢迎大家提出意见,共同探讨!

  • 34
    点赞
  • 139
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值