次序表与链表·续

打印 上一主题 下一主题

主题 1023|帖子 1023|积分 3069

引言

本文承接上文(次序表与链表-CSDN博客),开始对链表的要点提炼。前文提到次序表得当需要频仍随机访问且数据量固定的场景,而链表得当需要频仍插入和删除且数据量动态厘革的场景。链表的引入弥补了次序表在动态性和操纵服从上的不敷,是线性表的告急实现方式之一。
链表

概念

   链表是一种  物理存储结构上非连续  、非次序的存储结构,数据元素的  逻辑次序  是通过链表     中的  指针链接  实现的 。            
         链表结构              容易发现链表结构在逻辑上连续,但物理上不一定连续,一样平常链表的节点从堆上申请的,每次申请的空间是否连续是不确定的。   分类

链表的分类可以根据以下维度进行:

  • 单向或双向:决定节点的指针数量和遍历方向。
  • 带头或不带头:决定是否有额外的头节点简化操纵。
  • 循环或不循环:决定链表是否形成环。
通过组合这些维度,可以设计出得当不同场景的链表结构。例如:


  • 带头单向循环链表:得当实现队列。
  • 带头双向循环链表:得当需要双向遍历且操纵简化的场景。
  而我们常遇到的主要是不带头单向非循环链表和带头双向循环链表(以下图例分别对应这两种链表)
      
       不带头单向非循环链表            
       带头双向循环链表        

实现

无头单向非循环链表的简单实现
  1. //"slist.h"
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. typedef int SLTDateType;
  7. typedef struct SListNode
  8. {
  9.         SLTDateType data;
  10.         struct SListNode* next;
  11. }SListNode;
  12. // 动态申请一个节点
  13. SListNode* BuySListNode(SLTDateType x)
  14. {
  15.         SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  16.         if (newnode == NULL)
  17.         {
  18.                 perror("malloc fail");
  19.                 exit(-1);
  20.         }
  21.         newnode->data = x;
  22.         newnode->next = NULL;
  23.         return newnode;
  24. }
  25. // 单链表打印
  26. void SListPrint(SListNode* plist)
  27. {
  28.         SListNode* cur = plist;
  29.         while (cur != NULL)
  30.         {
  31.                 printf("%d->", cur->data);
  32.                 cur = cur->next;
  33.         }
  34.         printf("NULL\n");
  35. }
  36. // 单链表尾插
  37. void SListPushBack(SListNode** pplist, SLTDateType x)
  38. {
  39.         SListNode* newnode = BuySListNode(x);
  40.         if (*pplist == NULL) {
  41.                 *pplist = newnode;
  42.         }
  43.         else
  44.         {
  45.                 SListNode* tail = *pplist;
  46.                 while (tail->next!=NULL)
  47.                 {
  48.                         tail = tail->next;
  49.                 }
  50.                 tail->next = newnode;
  51.                
  52.         }
  53. }
  54. // 单链表的头插
  55. void SListPushFront(SListNode** pplist, SLTDateType x)
  56. {
  57.         SListNode* newnode = BuySListNode(x);
  58.         newnode->next = *pplist;
  59.         *pplist = newnode;
  60. }
  61. // 单链表的尾删
  62. void SListPopBack(SListNode** pplist)
  63. {
  64.         assert(pplist);
  65.         assert(*pplist);
  66.         //一个节点
  67.         if ((*pplist)->next == NULL) {
  68.                 free(*pplist);
  69.                 *pplist = NULL;
  70.         }
  71.         //多个节点
  72.         SListNode* tail = *pplist;
  73.         while (tail->next->next!=NULL)
  74.         {
  75.                 tail = tail->next;
  76.         }
  77.         free(tail->next);
  78.         tail->next = NULL;
  79. }
  80. void SListPopFront(SListNode** pplist) {
  81.         // 防御性检查:拦截非法输入
  82.         if (pplist == NULL) {
  83.                 fprintf(stderr, "Error: Invalid pointer in SListPopFront\n");
  84.                 return;
  85.         }
  86.         // 业务逻辑检查:空链表直接返回
  87.         if (*pplist == NULL) {
  88.                 return;
  89.         }
  90.         SListNode* tmp = *pplist;
  91.         *pplist = tmp->next;
  92.         free(tmp);
  93. }
  94. // 单链表查找
  95. SListNode* SListFind(SListNode* plist, SLTDateType x)
  96. {
  97.         SListNode* cur = plist;
  98.         while (cur) {
  99.                 if (cur->data == x) {
  100.                         return cur;
  101.                 }
  102.                 cur = cur->next;
  103.         }
  104.         return NULL;
  105. }
  106. // 单链表在pos位置之后插入x
  107. // 分析思考为什么不在pos位置之前插入?  
  108. // 因为没有前置指针
  109. // 若要在 pos 之前插入,必须从头节点开始遍历链表找到 pos 的前驱节点,时间复杂度为 O(n)
  110. void SListInsertAfter(SListNode* pos, SLTDateType x)
  111. {
  112.         if (pos == NULL) return;
  113.         SListNode* newNode = BuySListNode(x);
  114.         newNode->next = pos->next;
  115.         pos->next = newNode;
  116. }
  117. // 单链表删除pos位置之后的值
  118. // 分析思考为什么不删除pos位置?
  119. //删除 pos 节点需要知道其前驱节点,而单链表无法直接获取前驱节点
  120. // 必须从头遍历链表,时间复杂度为O(n),删除 pos 之后的节点只需修改 pos->next,时间复杂度为O(1)。
  121. void SListEraseAfter(SListNode* pos)
  122. {
  123.         if (pos == NULL || pos->next == NULL) return;
  124.         SListNode* tmp = pos->next;
  125.         pos->next = tmp->next;
  126.         free(tmp);
  127. }
  128. // 在pos的前面插入
  129. void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
  130. {
  131.         assert(pphead);
  132.         assert(pos);
  133.         assert(*pphead);
  134.         if (*pphead == pos) SListPushFront(pphead,x);
  135.         else
  136.         {
  137.                 SListNode* prev = *pphead;
  138.                 while (prev->next!=pos)
  139.                 {
  140.                         prev = prev->next;
  141.                 }
  142.                 SListNode* newnode=BuySListNode(x);
  143.                 prev->next = newnode;
  144.                 newnode->next = pos;
  145.         }
  146. }
  147. // 删除pos位置
  148. void SLTErase(SListNode** pphead, SListNode* pos)
  149. {
  150.         assert(pphead);
  151.         assert(*pphead);
  152.         assert(pos);
  153.         if (*pphead == pos)
  154.         {
  155.                 // 头删
  156.                 SListPopFront(pphead);
  157.         }
  158.         else
  159.         {
  160.                 SListNode* prev = *pphead;
  161.                 while (prev->next != pos)
  162.                 {
  163.                         prev = prev->next;
  164.                 }
  165.                 prev->next = pos->next;
  166.                 free(pos);
  167.                 pos = NULL;
  168.         }
  169. }
  170. void SLTDestroy(SListNode** pphead)
  171. {
  172.         assert(pphead);
  173.         SListNode* cur = *pphead;
  174.         while (cur) {
  175.                 SListNode* tmp = cur;
  176.                 cur = cur->next;
  177.                 free(tmp);
  178.         }
  179.         *pphead = NULL;
  180. }
复制代码
关键点说明


  • 二级指针的利用

    • 修改头指针时(如插入/删除头节点),需通过二级指针 pplist 修改一级指针 *pplist。
    • 示例:SListPushFront 和 SListPopFront 直接修改头指针。

  • 边界条件处理惩罚

    • 空链表、单节点链表、尾节点/头节点操纵需特殊处理惩罚。
    • 例如 SListPopBack 中需处理惩罚链表只有一个节点的环境。

  • 时间复杂度

    • 头插/头删:O(1)/O(1)
    • 尾插/尾删:O(n)/O(n)
    • 插入/删除指定位置:平均 O(n)/O(n)(需遍历找到位置)

  • 内存管理

    • 每次 malloc 后需检查内存分配是否成功。
    • 删除节点后需及时 free 防止内存泄漏。

  若需要频仍在恣意位置前插入或删除,最好利用双向链表,它可以通过前驱指针直接操纵,时间复杂度为 O(1)/O(1)。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表