• 【数据结构】单链表的实现



    一、链表的特性

    1、概念

    2、单链表的优缺点

    二、SList.h

    三、Slist.c

    1、单链表的打印

    2、malloc一个新节点

    3、单链表的销毁

    4、单链表的头插、头删

    5、单链表的尾插、尾删

    6、单链表的查找(修改)

    7、在pos之前插入、删除pos节点

    8、pos后一个位置插入、删除

    四、pos之前插入、删除pos节点的巧妙解法

    1、pos之前插入一个节点

    2、删除pos节点


    一、链表的特性

    1、概念

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

    单链表的图解:

    单链表中,每一个节点存储一个数据和一个指向下一个节点的指针,为了方便观察,添加了虚拟的箭头。

    单链表中传入的实参是结构体头指针,当使用头插头删等接口时,需要改变头指针,所以要传入二级指针。

    但是修改头指针指向的next指针或者date值时,传值也行。

    2、单链表的优缺点

    优点:

    头插头删时间复杂度O(1)

    逐个申请、释放空间,没有空间浪费

    缺点:

    结构简单,通常作为其他数据结构的子结构

    中部、尾部插入删除需要遍历

    不支持随机访问

    需要额外的空间用于存储指针

    二、SList.h

    1. #pragma once
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include
    4. #include
    5. #include
    6. typedef int SLTDateType;
    7. typedef struct SLTNode
    8. {
    9. struct SLTNode* next;
    10. SLTDateType date;
    11. }SLTNode;
    12. void SListPrint(SLTNode* phead);//打印
    13. SLTNode* BuySListNode(SLTDateType x);//malloc一个新节点
    14. void SListDestroy(SLTNode** pphead);//销毁
    15. void SListPushFront(SLTNode** pphead,SLTDateType x);//头插
    16. void SListPopFront(SLTNode** pphead);//头删
    17. void SListPushBack(SLTNode** pphead, SLTDateType x);//尾插
    18. void SListPopBack(SLTNode** pphead);//尾删
    19. SLTNode* SListFind(SLTNode* phead, SLTDateType x);//查找,找到返回结构体地址,反之返回NULL
    20. void SListInsert(SLTNode** pphead, SLTNode* pos,SLTDateType x);//在结构体指针pos前一个节点插入
    21. void SlistEarse(SLTNode** pphead, SLTNode* pos);//删除结构体指针pos指向的节点
    22. void SListInsertAfter(SLTNode* pos, SLTDateType x);//在结构体指针pos后一个节点插入
    23. void SListEarseAfter(SLTNode* pos);//删除结构体指针pos后一个节点后一个节点插入
    24. void SListEarseAfter(SLTNode* pos);//删除结构体指针pos后一个节点

    三、Slist.c

    1、单链表的打印

    1. void SListPrint(SLTNode* phead)//打印
    2. {
    3. SLTNode* cur = phead;
    4. while (cur)
    5. {
    6. printf("%d->", cur->date);
    7. cur = cur->next;
    8. }
    9. printf("\n");
    10. }

    循环遍历

    2、malloc一个新节点

    1. SLTNode* BuySListNode(SLTDateType x)//malloc一个新节点
    2. {
    3. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    4. if (newnode == NULL)
    5. {
    6. perror("malloc fail:\n");
    7. return NULL;
    8. }
    9. newnode->date = x;
    10. newnode->next = NULL;
    11. return newnode;
    12. }

    3、单链表的销毁

    1. void SListDestroy(SLTNode** pphead)//销毁
    2. {
    3. assert(pphead);
    4. SLTNode* cur = *pphead;
    5. while (cur)
    6. {
    7. SLTNode* next = cur->next;
    8. free(cur);
    9. cur=next;
    10. }
    11. *pphead = NULL;
    12. }

    迭代释放链表节点

    4、单链表的头插、头删

    1. void SListPushFront(SLTNode** pphead, SLTDateType x)//头插
    2. {
    3. assert(pphead);
    4. SLTNode* newnode = BuySListNode(x);
    5. newnode->next = *pphead;
    6. *pphead = newnode;
    7. }
    8. void SListPopFront(SLTNode** pphead)//头删
    9. {
    10. assert(pphead&& *pphead);
    11. SLTNode* next = (*pphead)->next;
    12. free(*pphead);
    13. *pphead = next;
    14. }

    单链表的优势就是头插、头删

    对于头删,除了要断言二级指针外,还要断一下二级指针所指向的一级指针是否为空,如果为空,说明该链表没有元素,需要报断言错误。

    5、单链表的尾插、尾删

    1. void SListPushBack(SLTNode** pphead, SLTDateType x)//尾插
    2. {
    3. assert(pphead);
    4. SLTNode* newnode = BuySListNode(x);
    5. if (*pphead == NULL)//分别讨论*pphead是否为空的情况
    6. {
    7. *pphead = newnode;
    8. }
    9. else
    10. {
    11. SLTNode* tail = *pphead;
    12. while (tail->next)
    13. {
    14. tail = tail->next;
    15. }
    16. tail->next = newnode;
    17. }
    18. }
    19. void SListPopBack(SLTNode** pphead)//尾删
    20. {
    21. assert(pphead&&*pphead);//*pphead不能为空
    22. if ((*pphead)->next == NULL)//分别讨论仅剩一个节点和多节点的情况
    23. {
    24. SListPopFront(pphead);
    25. }
    26. else
    27. {
    28. SLTNode* prev = NULL;
    29. SLTNode* tail = *pphead;
    30. while (tail->next)
    31. {
    32. prev = tail;
    33. tail = tail->next;
    34. }
    35. free(tail);
    36. tail = NULL;
    37. prev->next = NULL;
    38. }
    39. }

    尾插时,需要分类讨论链表是否为空的情况

    尾删时,*pphead不能为空。同样需要分开讨论仅剩一个头结点或剩余多个节点的情况,再循环找到tail的prev,进行尾删操作。

    6、单链表的查找(修改)

    1. SLTNode* SListFind(SLTNode* phead, SLTDateType x)//查找,找到返回结构体地址,反之返回NULL
    2. {
    3. SLTNode* cur = phead;
    4. while (cur)
    5. {
    6. if (cur->date == x)
    7. return cur;
    8. cur = cur->next;
    9. }
    10. return NULL;
    11. }

    查找,找到返回结构体地址,反之返回NULL

    查找函数是配和后方pos节点插入、删除函数使用

    7、在pos之前插入、删除pos节点

    1. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)//在结构体指针pos前一个节点插入
    2. {
    3. assert(pphead&&pos);
    4. if ((*pphead)==pos)
    5. {
    6. SListPushFront(pphead, x);//头插
    7. }
    8. else
    9. {
    10. SLTNode* newnode = BuySListNode(x);
    11. SLTNode* prev = *pphead;
    12. while (prev->next != pos)
    13. {
    14. prev = prev->next;
    15. assert(prev);//pos不在链表中.prev为空,还没有找到pos,说明pos传错了
    16. }
    17. newnode->next = pos;
    18. prev->next = newnode;
    19. }
    20. }
    21. void SlistEarse(SLTNode** pphead, SLTNode* pos)//删除结构体指针pos指向的节点
    22. {
    23. assert(pphead && pos && *pphead);
    24. if (pos == *pphead)//要判断pos是不是头结点
    25. {
    26. *pphead = (* pphead)->next;
    27. free(pos);//pos需要在外部置空
    28. }
    29. else
    30. {
    31. SLTNode* prev = *pphead;
    32. while (prev->next != pos)
    33. {
    34. prev = prev->next;
    35. }
    36. prev->next = pos->next;
    37. free(pos);//pos需要在外部置空
    38. }
    39. }

    分类讨论pos是否是头节点

    对非头结点的插入,注意在循环中断言prev,防止pos传错,导致循环停不下来。

    此接口不实用,因为单链表找前一个节点不好找,算法效率低

    8、pos后一个位置插入、删除

    1. void SListInsertAfter(SLTNode* pos, SLTDateType x)//在结构体指针pos后一个节点插入
    2. {
    3. assert(pos);
    4. SLTNode* newnode = BuySListNode(x);
    5. newnode->next = pos->next;
    6. pos->next = newnode;
    7. }
    8. void SListEarseAfter(SLTNode* pos)//删除结构体指针pos后一个节点
    9. {
    10. assert(pos&&pos->next);
    11. SLTNode* next = pos->next;
    12. pos->next = next->next;
    13. free(next);
    14. next = NULL;
    15. }

    四、pos之前插入、删除pos节点的巧妙解法

    1、pos之前插入一个节点

    在pos之后插入一个节点,将pos指向的值拷贝给newnode,再将pos->date改成需要插入的那个数据即可。

    2、删除pos节点

    将pos和pos->next的值交换,free(pos->next)

    需要讨论尾删的情况

  • 相关阅读:
    Spring系列-SpringMvc父子容器启动原理解析
    【微信小程序】利用云函数获取小程序码(二维码)并上传到对象存储和获取临时图片url链接
    leetcode_39 组合总和
    windows下部署nginx+PHP环境,连接SQL Server
    ES(Elasticsearch)中文检索使用笔记(一)
    【故障公告】没有龙卷风,k8s集群翻船3次,投用双集群恢复
    maven-shade-plugin - 解决 Jar 包冲突新思路
    sqlite数据库整体迁移进mysql整个流程并解决中文异常问题
    TCP怎么实现可靠传输
    搭建一个自己的AI学术语音助手(一)
  • 原文地址:https://blog.csdn.net/gfdxx/article/details/126176873