2024-02-19-物联网C语言(9-链表)

9.链表

9.1 概念

假如:做一个班级信息管理系统,统计班级学生的信息而我们事先不知道班级人数,或者知道人数,但是中间人员可能发生变化:比如有新同学加入,有同学请假,又或者我们需要统计班级的平均成绩等等

目标:要做一个类似 QQ、飞秋类似的通信软件,其中有一个功能,类似用户上下线检测、有新的用户上线、下线实时更新显示,可以实时查询在线状态、按姓名排序等以上问题如何使用学过的C语言知识处理呢?

使用数组远远不能达到我们的要求,因为数组只能存储同类型的数据,这个场景需要的是多种类型的数据存储。

  1. 数组不能实现动态的内存申请;
  2. malloc申请的空间也不能实现局部申请和释放

这就要链表登场了

定义: 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。

img

9.1.1 链表的构成

特点: 链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成(malloc),每个节点包括两个部分:

  1. 一个是存储数据元素的数据域
  2. 另一个是存储下一个节点地址的指针域
typedef struct student{
    int num;
    char name;    // 数据域
    struct student *next;  // 指针域,存放下一个节点的首地址
} STU;

9.2链表的操作

9.2.1 链表的创建

image-20240220085105683

#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
    int num;
    int score;
    char name[20];
    // 指针域
    struct student *next;
} STU;

void link_create_head(STU **p_head, STU *p_new)
{
    STU *p_mov = *p_head;

    if (*p_head == NULL)
    {                    // 当第一次加入链表为空
        *p_head = p_new; // 头节点保存新插入节点的地址
        p_new->next = NULL;
    }
    else // 第二次及以后加入链表
    {
        while (p_mov->next != NULL)
        {
            p_mov = p_mov->next; // 找到原有链表的最后一个节点
        }
        p_mov->next = p_new; // 将新申请的节点加入链表
        p_new->next = NULL;
    }
}

int main(int argc, char const *argv[])
{
    STU *head = NULL, *p_new = NULL;
    int num, i;
    printf("请输出链表的初始个数:\n");
    scanf("%d", &num);
    for (i = 0; i < num; i++)
    {
        p_new = (STU *)malloc(sizeof(STU)); // 申请一个新节点
        printf("请输入学号、分数、名字\n"); // 给新节点赋值
        scanf("%d %d %s", &p_new->num, &p_new->score, &p_new->name);
        link_create_head(&head, p_new); // 将新节点加入链表
    }
    return 0;
}

9.2.2 链表的遍历

image-20240220091308317

#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
    int num;
    int score;
    char name[20];
    // 指针域
    struct student *next;
} STU;

void link_create_head(STU **p_head, STU *p_new)
{
    STU *p_mov = *p_head;

    if (*p_head == NULL)
    {                    // 当第一次加入链表为空
        *p_head = p_new; // 头节点保存新插入节点的地址
        p_new->next = NULL;
    }
    else // 第二次及以后加入链表
    {
        while (p_mov->next != NULL)
        {
            p_mov = p_mov->next; // 找到原有链表的最后一个节点
        }
        p_mov->next = p_new; // 将新申请的节点加入链表
        p_new->next = NULL;
    }
}

void link_print(STU *head)
{
    STU *p_mov;
    // 定义新的指针保存链表,防止使用head改变原本链表
    p_mov = head;
    // 当循环到最后一个节点的指针域为NULL的时候,结束循环
    while (p_mov != NULL)
    {
        // 先打印当前指针保存节点的指针域
        printf("num = %d score = %d name: %s\n", p_mov->num, p_mov->score, p_mov->name);

        // 指针后移,指向下一个节点的地址
        p_mov = p_mov->next;
    }
}

int main(int argc, char const *argv[])
{
    STU *head = NULL, *p_new = NULL;
    int num, i;
    printf("请输出链表的初始个数:\n");
    scanf("%d", &num);
    for (i = 0; i < num; i++)
    {
        p_new = (STU *)malloc(sizeof(STU)); // 申请一个新节点
        printf("请输入学号、分数、名字\n"); // 给新节点赋值
        scanf("%d %d %s", &p_new->num, &p_new->score, &p_new->name);
        link_create_head(&head, p_new); // 将新节点加入链表
    }
    link_print(head);
    return 0;
}

执行输出结果

请输出链表的初始个数:
2
请输入学号、分数、名字
1 2 zhangsan
请输入学号、分数、名字
1 3 lisi 
num = 1 score = 2 name: zhangsan
num = 1 score = 3 name: lisi

9.2.3 链表的释放

image-20240220100439493

void link_free(STU **p_head){
    // 定义一个指针变量保存头结点的地址
    STU *pb = *p_head;
    while (*p_head != NULL){
        // 先保存p_head指向的结点地址
        pb = *p_head;
        // p_head 保存下一个结点的地址
        *p_head = (*p_head)->next;
        // 释放结点,防止野指针
        free(pb);
        pb = NULL;
    }
}

9.2.4 链表结点的查找

//链表的查找//按照学号查找
STU *link_search_num(STU *head,int num){
    STU *p_mov;
    //定义的指针变量保存第一个结点的地址
    p_mov=head;
    //当没有到达最后一个结点的指针域时循环继续
    while(p_mov!=NULL){
        // 对比,如果找到是当前需要的数据,则返回结点的地址
        if(p_mov->num == num){
            //找到了
            return p_mov;
        };
        p_mov=p_mov->next;
    }
    return NULL; //没有找到
}

9.2.5 链表结点的删除

  1. 如果链表为空,则不需要删除
  2. 如果删除的是第一个结点A,则需要将A的指针域保存其下一个节点B的地址
  3. 如果删除的是中间结点B,那么需要找到这个节点的前一个节点A,使前一个节点的指针域保存这个节点(B)后一个节点C的地址
void link_delete_num(STU **p_head,int num){
    STU *pb,*pf;
    pb = pf = *p_head;
    if (*p_head == NULL){ // 1. 链表为空,不用删
        printf("链表为空,没有需要删除的节点");
        return;
    }
    while (pb-> num != num && pb->next !=NULL){ // 循环找需要删除的节点
        pf = pbl;
        pb = pb->next;
    }
    if (pb->num ==num)
    { // 找到了一个节点的num和num相同
        if( pb ==*p_head){  //2. 如果是头结点,让保存头节点的指针保存后一个节点的地址
            *p_head = pb->next;
        }else{
            pf->next = pb->next; // 3. 如果是中间结点,其前一个节点指针保存其后一个节点地址
        }
        free(pb);
        pb = NULL;
    }else{
        printf("没有需要删除的节点\n");
    }

9.2.6 链表中插入节点

链表中插入节点,按照原链表顺序插入,找到合适的位置

image-20240220103519968

  1. 如果链表没有节点,则新插入的就是头结点
  2. 如果新插入的节点数值最小,则其就是头结点
  3. 如果新插入的结点数值在中间位置,则找到前一个,然后插入到他们中间
  4. 如果新插入的节点数值较大,则插入到最后
//链表的插入:按照学号的顺序插入
void link insert_num(STU **p_head,STU *p_new){
    STU *pb,*pf;
    pb=pf=*p_head;
    // 1. 链表为空链表,新插入的结点就是头结点
    if(*p_head == NULL){
        *p_head = p_new;
        p_new->next = NULL;
        return ;
    }

    while((p_new->num >= pb->num)&& (pb->next !=NULL)){ // 找到需要插入位置的前一个结点和后一个结点
        pf=pb;
        pb=pb->next;
    }
    // 2. 找到一个节点pb的num比新来的节点num大
    if (p_new->num < pb->num){
        if (pb == *p_head){
            p_new->next = *p_head; // 如果这个节点是头结点,那么新来的节点需要插到最前面,作为头结点
            *p_head = p_new;
        }else{
            pf->next = p_new; // 如果这个节点不是头节点,新来的节点插入到中间
            p_new->next = pb;
        }

    }else{ // 没有找到一个节点pb的num比新来的节点大,那么新来的节点就是最后一个结点
        pb->next = p_new;
        p_new->next = NULL;
    }
}

9.2.7 链表的排序

  1. 如果链表为空,不需要排序
  2. 如果链表只有一个结点,不需要排序
  3. 如果链表有多个节点,采用交换排序的方式。先将第一个结点与后面所有的结点依次对比数据域,只要有比第一个结点数据域小的,则交换位置交换之后,拿新的第一个结点的数据域与下一个结点再次对比,如果比他小,再次交换,依次类推第一个结点确定完毕之后,接下来再将第二个结点与后面所有的结点对比,直到最后一个结点也对比完毕为止
void link_order(STU *head){
    STU *pb,*pf,temp;
    pf = head;
    if(head == NULL){
        printf("链表为空,不用排序\n");
        return ;
    }
    if (head->next == NULL){
        printf("链表只有一个结点,不用排序\n");
        return ;
    }
    while (pf->next != NULL){
        pb = pf->next; //pd从基准元素的下个元素开始
        while (pb != NULL){  // 结点不为NULL,即不为最后一个节点
            if (pf->num > pb->num){  // 交换节点值和节点中的指针指向
                temp = *pb;
                *pb = *pf;
                *pf =temp;

                temp.next = pb ->next;
                pb->next = pf->next;
                pd->next = temp.next
            }
            pb = pb->next;
        }
        pf = pf->next;
    }
}

9.2.8 链表逆序

image-20240220110723687

//链表逆序
STU *link_reverse(STU *head){
    STU *pf,*pb,*r;
    pf=head;
    pb=pf->next;
    while(pb!=NULL){
        r=pb->next;
        pb->next=pf;
        pf=pb;
        pb=r;
    }
    head->next=NULL;
    head=pf;
    return head;
}

9.3 双向链表

image-20240220112731220

9.3.1 创建和遍历

  1. 创建一个节点为头结点,将两个指针都保存NULL
  2. 找到链表中最后一个结点A,A的指针保存新插入的结点B地址;B有两个指针域,一个保存A结点地址,另一个保存NULL
double link creat head(STU **p _head,STU *p_new){
    STU *p_mov=*p_head;
    if(*p_head==NULL){  //当第一次加入链表为空时
        *p_head= p_new;
        p_new->front = NULL;
        p_new->next = NULL;
    }else{
        //第二次及以后加入链表
        while(p_mov->next!=NULL){
            //找到原有链表的最后一个节点
            p_mov=p_mov->next;
        }
        //将新申请的节点加入链表
        p_mov->next= p_new;
        p_new->front=p_mov;
        p_new->next = NULL;
    }
}

9.3.2 结点删除

  1. 如果链表为空,则不需要删除
  2. 如果删除第一个结点A,则保存链表首地址的指针保存后一个结点B的地址,并且让结点B的front保存NULL
  3. 如果删除最后一个结点Z,只需要让最后一个结点的前一个结点Y的next保存NULL即可
  4. 如果删除中间结点M,则让中间结点的前后两个结点的指针域分别保存对方的地址即可
void doubel_link_delete_num(STU **p_head,int num){
    STU *pb,*pf;
    pb = *p_head;
    if (*p_head == NULL){ // 1. 链表为空
        printf("链表为空,没有需要删除的节点\n");
        return ;
    }
    
    while ((pb->num !=num) && (pb->next !=NULL)){  // 遍历需要删除的节点
        pb = pb->next;
    }
    if (pb->num == num){  // 找到了一个节点的num与num相同,删除pb指向的节点
        if (pb == *p_head){  // 2. 如果找到的是头节点
            if ((*p_head)->next ==NULL){
                *p_head = pb->next;
            }else{  // 如果有多个节点
                *p_head = pb->next; 
                (*p_head)->front = NULL;
            }
        }else{ //要删除的节点是其他节点
            if (pb->next !=NULL){ // 3.删除中间节点
                pf = pb->front;
                pf->next = pb->next;
                (pb->next)->front = pf;
            }else{ // 4.删除尾部节点
                pf =pb->front; 
                pf->next = NULL;
            }
            
        }
    }
}

9.3.3 插入节点

按照顺序插入节点

//链表的插入:按照学号的顺序插入
void link insert_num(STU **p_head,STU *p_new){
    STU *pb,*pf;
    pb=pf=*p_head;
    // 1. 链表为空链表,新插入的结点就是头结点
    if(*p_head == NULL){
        *p_head = p_new;
        p_new->next = NULL;
        return ;
    }

    while((p_new->num >= pb->num)&& (pb->next !=NULL)){ // 找到需要插入位置的前一个结点和后一个结点
        pf=pb;
        pb=pb->next;
    }
    // 2. 找到一个节点pb的num比新来的节点num大
    if (p_new->num < pb->num){
        if (pb == *p_head){
            p_new->next = *p_head; // 如果这个节点是头结点,那么新来的节点需要插到最前面,作为头结点
            (*p_head)->front = p_new; // 头结点的front指保存新插入的结点地址
            *p_new->front = NULL; // 新插入的节点front保存NULL
            *p_head = p_new; // 原本保存链表首地址的指针,保存新插入的节点地址
        }else{
            pf->next = p_new; // 如果这个节点不是头节点,新来的节点插入到中间
            p_new->front = pf;
            pf->next =p_new;
            pb->front = p_new;
        }

    }else{ // 没有找到一个节点pb的num比新来的节点大,那么新来的节点就是最后一个结点
        pb->next = p_new;
        p_new->front = pb;
        p_new->next = NULL
    }
}
posted @ 2024-02-20 13:56  Yasuo_Hasaki  阅读(13)  评论(0)    收藏  举报
//雪花飘落效果