C 语言数据结构(二):序次表和链表

[复制链接]
发表于 2025-10-19 16:10:27 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次
1. 线性表
2. 序次表
2.1 概念及结构
2.1.1 静态序次表(不常用)
2.1.2 动态序次表(常用)
​编辑
2.2 训练
2.2.1 移除元素
2.2.2 删除有序数组中的重复项
2.2.3 归并两个有序数组
2.3 序次表存在的标题
3. 链表
3.1 概念及结构
3.2 链表的分类
​编辑
3.2.1 常用链表
3.2.1.1 无头单向非循环链表
3.2.1.2 带头双向循环链表
3.3 无头单向非循环链表的实现
3.4 带头双向循环链表的实现
4. 对比序次表和链表

   💬 :如果你在阅读过程中有任何疑问或想要进一步探究的内容,接待在批评区各抒己见!我们一起学习、共同发展~!
  👍 :如果你以为这篇文章还不错,不妨顺手点个赞、到场收藏,并分享给更多的朋侪噢~!
  
1. 线性表

   线性表(linear list)是 n 个具有雷同特性的数据元素的有限序列。它是一种在实际中广泛利用的数据结构。
  常见的线性表包罗:序次表、链表、栈、队列、字符串等。
  线性表在逻辑上是线性结构,即一条连续的直线。但是在物理结构上并不愿定是连续的,线性表在物理上存储时,通常以数组和链式结构的情势存储。

2. 序次表

2.1 概念及结构

   序次表是用一段物理所在连续的存储单元依次存储数据元素的线性结构,一样寻常接纳数组存储。在数组上完成数据的增删查改。
  

序次表一样寻常可以分为:静态序次表,动态序次表。
2.1.1 静态序次表(不常用)

静态序次表利用定长数组存储元素。存储数据前,必要预先确定命组的长度,且在步伐运行过程中数组长度不能改变。
示例:
  1. #include <stdio.h>
  2. #define N 7
  3. // int 类型重命名为 SLDataType
  4. typedef int SLDataType;
  5. // 定义静态顺序表结构体
  6. typedef struct SeqList     // 此结构体类型起了一个别名 SeqList
  7. {
  8.     SLDataType array[N];  // 定长数组
  9.     size_t size;         
  10.     // size_t 用于表示无符号整数类型,通常用于表示数组的索引、对象的大小等
  11.     // size :当前有效数据的个数
  12. } SeqList;
  13. void SeqListInit(SeqList* psl)
  14. {
  15.     psl->size = 0;   
  16. }
  17. // 向静态顺序表尾部插入数据
  18. int SeqListPushBack(SeqList* psl, SLDataType x)
  19. {
  20.     if (psl->size >= N)
  21.     {
  22.         printf("顺序表已满,无法插入\n");
  23.         return 0;
  24.     }
  25.     psl->array[psl->size] = x;
  26.     psl->size++;   // 表示成功插入了一个数据
  27.     return 1;
  28. }
  29. // 打印静态顺序表
  30. void SeqListPrint(SeqList* psl)
  31. {
  32.     for (size_t i = 0; i < psl->size; i++)
  33.     {
  34.         printf("%d ", psl->array[i]);
  35.     }
  36.     printf("\n");
  37. }
  38. int main()
  39. {
  40.     SeqList sl;
  41.     // 声明一个SeqList类型的变量sl,用于表示一个静态顺序表
  42.     SeqListInit(&sl);
  43.     SeqListPushBack(&sl, 1);
  44.     SeqListPushBack(&sl, 2);
  45.     SeqListPushBack(&sl, 3);
  46.     SeqListPrint(&sl);
  47.     return 0;
  48. }
复制代码

2.1.2 动态序次表(常用)

动态序次表利用动态开辟的数组存储元素。可根据实际必要动态地增减数组的容量,以顺应数据量的变革。
示例:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <assert.h>
  4. typedef int SLDataType;
  5. // int 类型重命名为 SLDataType
  6. typedef struct SeqList   // 此结构体类型起了一个别名 SeqList
  7. {
  8.     SLDataType* array;  // 指向动态开辟的数组
  9.     size_t size;        // 顺序表中有效数据的个数
  10.     size_t capacity;    // 顺序表当前容量大小
  11. } SeqList;
  12. // 顺序表初始化
  13. void SeqListInit(SeqList* psl)
  14. {
  15.     // 检查传入的顺序表指针是否为空,若为空则终止程序
  16.     assert(psl);
  17.     // 初始化时先不分配空间,将数组指针置为 NULL
  18.     psl->array = NULL;
  19.    
  20.     psl->size = 0;
  21.     psl->capacity = 0;
  22. }
  23. // 检查空间,如果满了,进行增容
  24. void CheckCapacity(SeqList* psl)
  25. {
  26.     assert(psl);
  27.     // 当有效数据个数等于容量时,说明顺序表已满,需要增容
  28.     if (psl->size == psl->capacity)
  29.     {
  30.         // 若当前容量为 0,先给一个初始容量,这里设为 4
  31.         size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
  32.         // 重新分配内存空间,大小为新容量乘以每个数据元素的大小
  33.         SLDataType* tmp = (SLDataType*)realloc(psl->array, newCapacity * sizeof(SLDataType));
  34.         // 检查内存分配是否成功,若失败则终止程序
  35.         assert(tmp);
  36.         // 更新数组指针为新分配的内存地址
  37.         psl->array = tmp;
  38.         
  39.         psl->capacity = newCapacity;
  40.     }
  41. }
  42. // 顺序表尾插
  43. void SeqListPushBack(SeqList* psl, SLDataType x)
  44. {
  45.     assert(psl);
  46.     // 插入元素前先检查是否需要增容
  47.     CheckCapacity(psl);
  48.     // 在顺序表的末尾插入新元素
  49.     psl->array[psl->size] = x;
  50.    
  51.      // 有效数据个数加 1,表示插入一个元素
  52.     psl->size++;
  53. }
  54. // 顺序表尾删
  55. void SeqListPopBack(SeqList* psl)
  56. {
  57.     assert(psl);
  58.     // 检查顺序表中是否有数据,若没有则终止程序
  59.     assert(psl->size > 0);
  60.     // 表示删除一个元素
  61.     psl->size--;
  62. }
  63. // 顺序表头插
  64. void SeqListPushFront(SeqList* psl, SLDataType x)
  65. {
  66.     assert(psl);
  67.     // 插入元素前先检查是否需要增容
  68.     CheckCapacity(psl);
  69.     // 将顺序表中所有元素依次向后移动一位
  70.     for (int i = psl->size; i > 0; i--)
  71.     {
  72.         psl->array[i] = psl->array[i - 1];
  73.     }
  74.     psl->array[0] = x;
  75.    
  76.     psl->size++;
  77. }
  78. // 顺序表头删
  79. void SeqListPopFront(SeqList* psl)
  80. {
  81.     assert(psl);
  82.     // 检查顺序表中是否有数据,若没有则终止程序
  83.     assert(psl->size > 0);
  84.     // 将顺序表中所有元素依次向前移动一位
  85.     for (int i = 0; i < psl->size - 1; i++)
  86.     {
  87.         psl->array[i] = psl->array[i + 1];
  88.     }
  89.    
  90.     psl->size--;
  91. }
  92. // 顺序表查找
  93. int SeqListFind(SeqList* psl, SLDataType x)
  94. {
  95.     assert(psl);
  96.    
  97.     for (size_t i = 0; i < psl->size; i++)
  98.     {
  99.         // 若找到与目标元素相等的元素,返回其下标
  100.         if (psl->array[i] == x)
  101.         {
  102.             return (int)i;
  103.         }
  104.     }
  105.    
  106.     return -1;
  107. }
  108. // 顺序表在 pos 位置插入 x
  109. void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
  110. {
  111.     assert(psl);
  112.     // 检查插入位置是否合法,即不能小于 0 且不能大于当前有效数据个数
  113.     assert(pos >= 0 && pos <= psl->size);
  114.     // 插入元素前先检查是否需要增容
  115.     CheckCapacity(psl);
  116.     // 将 pos 位置及之后的元素依次向后移动一位
  117.     for (size_t i = psl->size; i > pos; i--)
  118.     {
  119.         psl->array[i] = psl->array[i - 1];
  120.     }
  121.    
  122.     psl->array[pos] = x;
  123.    
  124.     psl->size++;
  125. }
  126. // 顺序表删除 pos 位置的值
  127. void SeqListErase(SeqList* psl, size_t pos)
  128. {
  129.     assert(psl);
  130.     assert(pos >= 0 && pos < psl->size);
  131.    
  132.     for (size_t i = pos; i < psl->size - 1; i++)
  133.     {
  134.         psl->array[i] = psl->array[i + 1];
  135.     }
  136.    
  137.     psl->size--;
  138. }
  139. // 顺序表销毁
  140. void SeqListDestory(SeqList* psl)
  141. {
  142.     assert(psl);
  143.     // 释放动态分配的数组内存
  144.     free(psl->array);
  145.     // 将数组指针置为 NULL,防止悬空指针
  146.     psl->array = NULL;
  147.    
  148.     psl->size = 0;
  149.    
  150.     psl->capacity = 0;
  151. }
  152. void SeqListPrint(SeqList* psl)
  153. {
  154.     assert(psl);
  155.    
  156.     for (size_t i = 0; i < psl->size; i++)
  157.     {
  158.         printf("%d ", psl->array[i]);
  159.     }
  160.     printf("\n");
  161. }
  162. int main()
  163. {
  164.     SeqList sl;
  165.     // 声明一个SeqList类型的变量sl,用于表示一个动态顺序表
  166.     // 表尾插
  167.     SeqListInit(&sl);
  168.     SeqListPushBack(&sl, 1);
  169.     SeqListPushBack(&sl, 2);
  170.     SeqListPushBack(&sl, 3);
  171.     SeqListPrint(&sl);
  172.     // 表头插
  173.     SeqListPushFront(&sl, 0);
  174.     SeqListPrint(&sl);
  175.     // 查找
  176.     int index = SeqListFind(&sl, 2);
  177.     if (index != -1)
  178.     {
  179.         printf("元素 2 在顺序表中的下标是: %d\n", index);
  180.     }
  181.     else
  182.     {
  183.         printf("未找到元素 2\n");
  184.     }
  185.     // 表尾删
  186.     SeqListPopBack(&sl);
  187.     SeqListPrint(&sl);
  188.     // 表头删
  189.     SeqListPopFront(&sl);
  190.     SeqListPrint(&sl);
  191.     // 指定位置插入值
  192.     SeqListInsert(&sl, 1, 10);
  193.     SeqListPrint(&sl);
  194.     // 指定位置删除值
  195.     SeqListErase(&sl, 1);
  196.     SeqListPrint(&sl);
  197.     // 顺序表销毁
  198.     SeqListDestory(&sl);
  199.     return 0;
  200. }
复制代码


2.2 训练

2.2.1 移除元素

有一个数组和一个值 val,要求:


  • 原地移除全部便是 val 的元素
  • 返回数组中未便是 val (剩余)的元素的数目 k
  • 新数组前 k 个元素包罗未便是 val 的元素
   “原地”:仅利用常数级别的额外空间,即空间复杂度为 O(1)。其算法直接在原始数据存储的内存位置上举行修改,而不是创建一个与原始数据规模相当的新数据结构来存储处置惩罚后的结果。
  1. #include <stdio.h>
  2. int removeElement(int* nums, int numsSize, int val)
  3. {
  4.     int k = 0;
  5.    
  6.     for (int i = 0; i < numsSize; i++)
  7.     {
  8.         if (nums[i] != val)
  9.         {
  10.             // 如果不等于 val,则将该元素放到数组的前 k 个位置
  11.             nums[k] = nums[i];
  12.             
  13.             k++;
  14.         }
  15.     }
  16.    
  17.     return k;
  18. }
  19. int main()
  20. {
  21.     int nums[] = {3, 2, 2, 3};
  22.     int numsSize = sizeof(nums) / sizeof(nums[0]);
  23.     int val = 3;
  24.     int newSize = removeElement(nums, numsSize, val);
  25.     printf("剩余元素的数量: %d\n", newSize);
  26.    
  27.     printf("移除指定元素后的数组: ");
  28.     for (int i = 0; i < newSize; i++)
  29.     {
  30.         printf("%d ", nums[i]);
  31.     }
  32.     printf("\n");
  33.     return 0;
  34. }
复制代码

只遍历数组一次,以是时间复杂度 O(n) ,n 为数组长度。
2.2.2 删除有序数组中的重复项

原地删除一个非严格递增分列的数组 nums 中重复的元素,使每个元素只出现一次,保持元素的相对序次稳固,返回新数组的长度 k ,新数组前 k 个元素包罗唯一元素。
  1. #include <stdio.h>
  2. int removeDuplicates(int* nums, int numsSize)
  3. {
  4.     if (numsSize == 0)
  5.     {
  6.         return 0;
  7.     }
  8.     int slow = 0;
  9.     for (int fast = 1; fast < numsSize; fast++)
  10.     {
  11.         if (nums[fast] != nums[slow])
  12.         {
  13.             slow++;
  14.             nums[slow] = nums[fast];
  15.         }
  16.     }
  17.     return slow + 1;
  18. }
  19. int main()
  20. {
  21.     int nums[] = {1, 1, 2, 2, 2, 3, 4, 4, 5};
  22.     int numsSize = sizeof(nums) / sizeof(nums[0]);
  23.     int newLength = removeDuplicates(nums, numsSize);
  24.     printf("删除重复元素后数组的新长度: %d\n", newLength);
  25.     printf("删除重复元素后的数组: ");
  26.     for (int i = 0; i < newLength; i++)
  27.     {
  28.         printf("%d ", nums[i]);
  29.     }
  30.     printf("\n");
  31.     return 0;
  32. }
复制代码
只遍历数组一次,以是时间复杂度 O(n) ,n 为数组长度。
2.2.3 归并两个有序数组

给定两个非递减排序的整数数组 nums1 和 nums2,及整数 m 和 n。
要求:


  • nums1 初始长度是 m + n,前 m 个是待归并元素,后 n 个为 0 ;
  • nums2 初始长度为 n;
  • nums2 归并到 nums1 里,归并后数组保持非递减序次,归并后结果存于 nums1 中。
  1. #include <stdio.h>
  2. void merge(int* nums1, int m, int* nums2, int n)
  3. {
  4.     int p1 = m - 1;  // nums1 中最后一个“有效”元素的索引
  5.     int p2 = n - 1;  // nums2 中最后一个元素的索引
  6.     int p = m + n - 1;  // 合并后数组最后一个元素的索引
  7.     // 从后往前比较两个数组的元素
  8.     while (p1 >= 0 && p2 >= 0)
  9.     {
  10.         if (nums1[p1] > nums2[p2])
  11.         {
  12.             nums1[p] = nums1[p1];
  13.             p1--;
  14.         }
  15.         else
  16.         {
  17.             nums1[p] = nums2[p2];
  18.             p2--;
  19.         }
  20.         p--;
  21.     }
  22.     // 其中一个数组遍历完后,若 nums2 还有剩余元素,将其复制到 nums1 前面
  23.     while (p2 >= 0)
  24.     {
  25.         nums1[p] = nums2[p2];
  26.         p2--;
  27.         p--;
  28.     }
  29. }
  30. int main()
  31. {
  32.     int nums1[] = { 1, 2, 3, 0, 0, 0, 0, 0 };
  33.     int m = 3;
  34.     int nums2[] = { 0, 2, 5, 6, 7 };
  35.     int n = 5;
  36.     merge(nums1, m, nums2, n);
  37.     for (int i = 0; i < m + n; i++)
  38.     {
  39.         printf("%d ", nums1[i]);
  40.     }
  41.     printf("\n");
  42.     return 0;
  43. }
复制代码

对比nums1[p]每次比力后的数组
nums1[7] = 71,2,3,0,0,0,0,7
nums1[6] = 61,2,3,0,0,0,6,7
nums1[5] = 51,2,3,0,0,5,6,7
nums1[4] = 31,2,3,0,3,5,6,7
nums1[3] = 21,2,3,2,3,5,6,7
nums1[2] = 21,2,2,2,3,5,6,7
nums1[1] = 11,1,2,2,3,5,6,7 然后 p1 = -1,p2 = 0,p = 0,实行第二个 while 循环,nums1[0] = nums2[0] = 0 ,得到归并数组{0,1,2,2,3,5,6,7} 。
别的,只遍历数组 nums1 和 nums2 一次,以是时间复杂度为 O(m+n) 。
2.3 序次表存在的标题

序次表存在以下标题:


  • 举行中心或表头插入、删除操纵时,时间复杂度达 O (N)。
  • 增容要申请新空间、拷贝数据、开释旧空间,斲丧大。
  • 扩容一样寻常扩大为原来的 2 倍,若插入数据少,造成空间浪费。
为办理这些标题,下面先容链表结构。

3. 链表

3.1 概念及结构

   链表是一种物理存储结构上非连续、非序次的存储结构,数据元素的逻辑序次是通过链表中的指针链接序次实现的 。
  链表在逻辑上是连续的,但是——链表节点一样寻常从堆上申请空间,而堆上空间的分配依据肯定计谋举行,导致两次申请的空间大概连续,也大概不连续——以是链表的物理存储所在不愿定连续。

3.2 链表的分类



  • 单向/双向




  • 带头/不带头



  • 循环/非循环

3.2.1 常用链表

3.2.1.1 无头单向非循环链表


结构简朴,一样寻常不会单独用来存数据。实际中更多作为其他数据结构的子结构,如哈希桶、图的毗邻表等。别的这种结构在笔试口试中较常见。
3.2.1.2 带头双向循环链表


结构最复杂,一样寻常用来单独存储数据。实际中利用的链表数据结构多是带头双向循环链表。
3.3 无头单向非循环链表的实现

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef int SLTDateType;
  4. // 定义链表节点结构体
  5. typedef struct SListNode
  6. {
  7.     SLTDateType data;  // 节点存储的数据
  8.     struct SListNode* next;  // 指向下一个节点的指针
  9. }SListNode;
  10. // 动态申请一个节点
  11. SListNode* BuySListNode(SLTDateType x)
  12. {
  13.     // 为新节点分配内存空间
  14.     SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
  15.     if (newNode == NULL)
  16.     {
  17.         perror("malloc");
  18.         return NULL;
  19.     }
  20.     // 初始化新节点的数据
  21.     newNode->data = x;
  22.     // 新节点的下一个节点初始化为空
  23.     newNode->next = NULL;
  24.     return newNode;
  25. }
  26. void SListPrint(SListNode* plist)
  27. {
  28.     SListNode* cur = plist;
  29.     // plist 是指向链表头节点的指针
  30.     while (cur != NULL)
  31.     // 无头单向非循环链表的末尾节点的 next 指针始终被设置为 NULL
  32.     {
  33.         // 打印当前节点的数据
  34.         printf("%d -> ", cur->data);
  35.         // 移动到下一个节点
  36.         cur = cur->next;
  37.     }
  38.     // 打印链表结束标志
  39.     printf("NULL\n");
  40. }
  41. // 尾插
  42. void SListPushBack(SListNode** pplist, SLTDateType x)
  43. {
  44.     // 创建新节点
  45.     SListNode* newNode = BuySListNode(x);
  46.     if (*pplist == NULL)
  47.     {
  48.         // 如果链表为空,新节点就是链表的头节点
  49.         *pplist = newNode;
  50.     }
  51.     else
  52.     {
  53.         // 找到链表的尾节点
  54.         SListNode* tail = *pplist;
  55.         while (tail->next != NULL)
  56.         {
  57.             tail = tail->next;
  58.         }
  59.         
  60.         tail->next = newNode;
  61.     }
  62. }
  63. // 头插
  64. void SListPushFront(SListNode** pplist, SLTDateType x)
  65. {
  66.     SListNode* newNode = BuySListNode(x);
  67.     // 新节点的下一个节点指向原来的头节点
  68.     newNode->next = *pplist;
  69.    
  70.     *pplist = newNode;
  71. }
  72. // 尾删
  73. void SListPopBack(SListNode** pplist)
  74. {
  75.     if (*pplist == NULL)   // 若链表为空
  76.     {
  77.         return;   // 函数立即终止执行
  78.     }
  79.     if ((*pplist)->next == NULL)
  80.     {
  81.         // 如果链表只有一个节点,释放该节点并将头指针置空
  82.         free(*pplist);
  83.         *pplist = NULL;
  84.     }
  85.     else
  86.     {
  87.         SListNode* prev = NULL;
  88.         SListNode* tail = *pplist;
  89.         while (tail->next != NULL)
  90.         {
  91.             prev = tail;
  92.             tail = tail->next;
  93.         }
  94.         // 最后一次循环中,prev 被赋为 tail 的前一个节点,tail 移到了尾节点
  95.         
  96.         free(tail);
  97.         // 倒数第二个节点的下一个节点置空
  98.         prev->next = NULL;
  99.     }
  100. }
  101. // 头删
  102. void SListPopFront(SListNode** pplist)
  103. {
  104.     if (*pplist == NULL)
  105.     {
  106.         return;
  107.     }
  108.     // 保存原来的头节点
  109.     SListNode* first = *pplist;
  110.     // 更新头节点为原来头节点的下一个节点
  111.     *pplist = first->next;
  112.     // 释放原来的头节点
  113.     free(first);
  114. }
  115. // 查找
  116. SListNode* SListFind(SListNode* plist, SLTDateType x)
  117. {
  118.     // 遍历链表,查找值为x的节点
  119.     SListNode* cur = plist;
  120.     while (cur != NULL)
  121.     {
  122.         if (cur->data == x)
  123.         {
  124.             return cur;
  125.         }
  126.         
  127.         cur = cur->next;
  128.     }
  129.    
  130.     return NULL;
  131. }
  132. // 在pos的后一个位置插入值 x
  133. void SListInsertAfter(SListNode* pos, SLTDateType x)
  134. {
  135.     if (pos == NULL)
  136.     {
  137.         return;
  138.     }
  139.    
  140.     SListNode* newNode = BuySListNode(x);
  141.    
  142.     newNode->next = pos->next;
  143.    
  144.     pos->next = newNode;
  145. }
  146. // 删除pos后一个位置的值
  147. void SListEraseAfter(SListNode* pos)
  148. {
  149.     if (pos == NULL || pos->next == NULL)   // 如果pos为空或者pos是尾节点
  150.     {
  151.         return;
  152.     }
  153.     // 保存pos节点的下一个节点
  154.     SListNode* nextNode = pos->next;
  155.     // pos节点的下一个节点指向要删除节点的下一个节点
  156.     pos->next = nextNode->next;
  157.     free(nextNode);
  158. }
  159. int main()
  160. {
  161.     SListNode* list = NULL;   
  162.     // 声明并初始化一个指向 SListNode 类型结构体的指针 list
  163.     // 用于表示初始为空的链表
  164.     // 尾插
  165.     SListPushBack(&list, 1);
  166.     SListPushBack(&list, 2);
  167.     SListPushBack(&list, 3);
  168.     SListPrint(list);
  169.     // 头插
  170.     SListPushFront(&list, 0);
  171.     SListPrint(list);
  172.     // 尾删
  173.     SListPopBack(&list);
  174.     SListPrint(list);
  175.     // 头删
  176.     SListPopFront(&list);
  177.     SListPrint(list);
  178.     // 查找
  179.     SListNode* found = SListFind(list, 2);
  180.     if (found)
  181.     {
  182.         printf("Found: %d\n", found->data);
  183.     }
  184.     else
  185.     {
  186.         printf("Not found\n");
  187.     }
  188.     // 在pos的后一个位置插入值
  189.     SListInsertAfter(list, 4);
  190.     SListPrint(list);
  191.     // 删除pos后一个位置的值
  192.     SListEraseAfter(list);
  193.     SListPrint(list);
  194.     return 0;
  195. }
复制代码

“无头单向非循环链表的末端节点的 next 指针始终被设置为 NULL”:


  • 节点创建:新节点创建时,next 指针初始化为 NULL。
  • 尾插操纵:尾插新节点时,若链表为空,新节点成头节点,next 为 NULL;若不为空,找到尾节点,将其 next 指向新节点(新节点 next 已初始化为 NULL)。
  • 尾删操纵:尾删时,若链表仅一个节点,开释后链表为空;如有多个节点,开释尾节点,将倒数第二个节点的 next 置为 NULL。
思索:为什么在pos的后一个位置插入值,而非前一个位置?为什么不删除 pos 位置节点?
(1)单向链表只能从前向后遍历,没有指向前一节点的指针,无法直接获取 pos 节点的前一个节点,因此只能重新节点开始依次遍历链表,直到找到 pos 节点的前一个节点,时间复杂度达 O(n);而在 pos 后插入,只改两个指针,时间复杂度为 O(1)。
(2)删除 pos 位置节点需找到其前一节点,时间复杂度达 O(n);而删除 pos 后节点,只改一个指针,时间复杂度为 O(1)。
3.4 带头双向循环链表的实现

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef int LTDataType;
  4. typedef struct ListNode
  5. {
  6.     LTDataType _data;  // 节点存储的数据
  7.     struct ListNode* next;  // 指向下一个节点的指针
  8.     struct ListNode* prev;  // 指向前一个节点的指针
  9. }ListNode;
  10. // 创建返回链表的头结点
  11. ListNode* ListCreate()
  12. {
  13.     // 为头节点分配内存
  14.     ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
  15.     if (phead == NULL)
  16.     {
  17.         perror("malloc fail");
  18.         return NULL;
  19.     }
  20.     // 头节点的 next 和 prev 都指向自身,形成循环
  21.     phead->next = phead;
  22.     phead->prev = phead;
  23.     return phead;
  24. }
  25. // 双向链表销毁
  26. void ListDestory(ListNode* plist)
  27. {
  28.     // 从头节点的下一个节点开始遍历
  29.     ListNode* cur = plist->next;
  30.     while (cur != plist)
  31.     {
  32.         // 保存当前节点的下一个节点
  33.         ListNode* next = cur->next;
  34.         
  35.         free(cur);
  36.         cur = next;
  37.     }
  38.     // 最后释放头节点的内存
  39.     free(plist);
  40. }
  41. void ListPrint(ListNode* plist)
  42. {
  43.     ListNode* cur = plist->next;
  44.     while (cur != plist)
  45.     {
  46.         printf("%d ", cur->_data);
  47.         cur = cur->next;
  48.     }
  49.     printf("\n");
  50. }
  51. // 尾插
  52. void ListPushBack(ListNode* plist, LTDataType x)
  53. {
  54.     // 创建新节点
  55.     ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  56.     if (newnode == NULL)
  57.     {
  58.         perror("malloc fail");
  59.         return;
  60.     }
  61.     newnode->_data = x;
  62.     // 找到尾节点
  63.     ListNode* tail = plist->prev;
  64.     // 插入新节点
  65.     tail->next = newnode;
  66.     newnode->prev = tail;
  67.     newnode->next = plist;
  68.     plist->prev = newnode;
  69. }
  70. // 尾删
  71. void ListPopBack(ListNode* plist)
  72. {
  73.     // 检查链表是否为空
  74.     if (plist->next == plist)
  75.     {
  76.         return;
  77.     }
  78.     // 找到尾节点
  79.     ListNode* tail = plist->prev;
  80.     // 找到尾节点的前一个节点
  81.     ListNode* prev = tail->prev;
  82.    
  83.     free(tail);
  84.     // 更新指针
  85.     prev->next = plist;
  86.     plist->prev = prev;
  87. }
  88. // 头插
  89. void ListPushFront(ListNode* plist, LTDataType x)
  90. {
  91.     ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  92.     if (newnode == NULL)
  93.     {
  94.         perror("malloc fail");
  95.         return;
  96.     }
  97.     newnode->_data = x;
  98.     // 保存头节点的下一个节点
  99.     ListNode* first = plist->next;
  100.     // 插入新节点
  101.     plist->next = newnode;
  102.     newnode->prev = plist;
  103.     newnode->next = first;
  104.     first->prev = newnode;
  105. }
  106. // 头删
  107. void ListPopFront(ListNode* plist)
  108. {
  109.     // 检查链表是否为空
  110.     if (plist->next == plist)
  111.     {
  112.         return;
  113.     }
  114.     // 保存头节点的下一个节点
  115.     ListNode* first = plist->next;
  116.     // 保存头节点的下下一个节点
  117.     ListNode* second = first->next;
  118.     // 释放头节点的下一个节点的内存
  119.     free(first);
  120.     // 更新指针
  121.     plist->next = second;
  122.     second->prev = plist;
  123. }
  124. // 查找
  125. ListNode* ListFind(ListNode* plist, LTDataType x)
  126. {
  127.     // 从头节点的下一个节点开始遍历
  128.     ListNode* cur = plist->next;
  129.     while (cur != plist)
  130.     {
  131.         if (cur->_data == x)
  132.         {
  133.             return cur;
  134.         }
  135.         cur = cur->next;
  136.     }
  137.     return NULL;
  138. }
  139. // 在pos的前一个位置插入
  140. void ListInsert(ListNode* pos, LTDataType x)
  141. {
  142.     ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  143.     if (newnode == NULL)
  144.     {
  145.         perror("malloc fail");
  146.         return;
  147.     }
  148.     newnode->_data = x;
  149.     // 找到 pos 节点的前一个节点
  150.     ListNode* prev = pos->prev;
  151.     // 插入新节点
  152.     prev->next = newnode;
  153.     newnode->prev = prev;
  154.     newnode->next = pos;
  155.     pos->prev = newnode;
  156. }
  157. // 删除pos位置的节点
  158. void ListErase(ListNode* pos)
  159. {
  160.     ListNode* prev = pos->prev;
  161.    
  162.     ListNode* next = pos->next;
  163.    
  164.     free(pos);
  165.     // 更新指针
  166.     prev->next = next;
  167.     next->prev = prev;
  168. }
  169. int main()
  170. {
  171.     ListNode* plist = ListCreate();
  172.     // 调用 ListCreate 创建带头双向循环链表,并将返回的头节点指针赋值给 plist
  173.     ListPushBack(plist, 1);
  174.     ListPushBack(plist, 2);
  175.     ListPushBack(plist, 3);
  176.     ListPrint(plist);
  177.     ListPushFront(plist, 0);
  178.     ListPrint(plist);
  179.     ListPopBack(plist);
  180.     ListPrint(plist);
  181.     ListPopFront(plist);
  182.     ListPrint(plist);
  183.     ListNode* pos = ListFind(plist, 2);
  184.     if (pos)
  185.     {
  186.         ListInsert(pos, 4);
  187.         ListPrint(plist);
  188.     }
  189.     pos = ListFind(plist, 4);
  190.     if (pos)
  191.     {
  192.         ListErase(pos);
  193.         ListPrint(plist);
  194.     }
  195.     ListDestory(plist);
  196.     return 0;
  197. }
复制代码



4. 对比序次表和链表

对比维度序次表链表存储空间物理存储上肯定连续逻辑上数据元素连续,但物理内存中不愿定连续,节点通过指针相互毗连随机访问支持随机访问,通过下标可直接定位元素,时间复杂度为 O (1)需从表头开始,逐个通过指针遍历查找元素,时间复杂度为 O (N)插入和删除操纵在恣意位置插入或删除元素时,大概需移动后续元素,服从较低,时间复杂度为 O (N)插入或删除元素时,仅需修改干系节点的指针指向,操纵相对轻巧插入特性动态序次表空间不敷时,需举行扩容,涉及重新分配内存、数据拷贝等步调没有容量限定,可随时动态创建和删除节点应用场景恰当元素高效存储且频仍访问的场景,如数组实现的线性表实用于数据插入和删除操纵频仍的场景,如实现队列、栈等数据结构缓存利用率物理存储连续,符合局部性原理,读取数据时相邻数据易一同加载到缓存中,缓存利用率高节点物理位置分散,难以充实利用缓存,缓存利用率较低
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表