【数据结构】链表-C语言版(一)

简介: 【数据结构】链表-C语言版

顺序表缺点

顺序表随机访问很方便,但是也会有不足啊:

(1)挪动数据时间开销较大:头部/中间的插入删除,需要挪动后面的所有数据,时间复杂度为O(N)

(2)增容有代价:增容需要重新申请空间,拷贝数据,释放旧空间,系统消耗不小

(3)空间浪费:增容一般增至原来的2倍大空间,会有空间浪费,假如当前容量为100,满了以后增容到200,再继续插入了5个数据,后面没有插入数据了,这就浪费了95个空间。

不存在扩容代价、不存在空间浪费、按需申请空间、头部或者中间插入数据而不需要挪动数据的链表可以解决以上问题。


链表的概念和结构

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

注意:

(1)链式结构在逻辑上连续,逻辑上不一定连续。(逻辑结构是想象的,物理结构是内存中实际存储的)。

(2)结点一般从堆上申请。

(3)堆上申请的空间时按照一定策略分配的,两次申请的空间可能连续也可能不连续。


链表的分类

有8种链表结构 :

(1)单向和双向

(2)带头单链表和不带头单链表

(3)非循环链表和循环链表

以上3种分类的链表一共有2*2*2=8种组合,最实用的有两种:

(1)无头单向非循环链表:结构简单,一般不会用来单独存储数据。十几种更多是作为其他数据结构的子结构,如哈希桶,图的邻接表等。

(2)带头双向循环链表:结构最复杂,一般用来单独存储数据。实际中使用的链表数据结构都是带头双向循环链表。虽然结构复杂,但是使用代码实现以后会发现该结构会带来很多优势,实现反而简单。


链表的实现

如果需要交换两个int的值,那么swap函数的实参是两个int的地址,形参是两个int型指针。

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include<stdio.h>
3. 
4. void swap(int* pa, int* pb)//用两个指针作为形参接收两个地址
5. {
6.  int temp = *pa;
7.  *pa = *pb;
8.  *pb = temp;
9. }
10. 
11. int main()
12. {
13.   int a = 3;
14.   int b = 5;
15.   swap(&a, &b);//实参是两个int的地址
16.   printf("a = %d\n", a);
17.   printf("a = %d\n", b);
18.   return 0;
19. }

同理,对于链表的增删,如果需要改变指针指向,就要传指针的地址,即二级指针。

链表实现:

025-SList.h 链表函数定义

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #pragma once
3. 
4. typedef int SLTDataType;
5. 
6. typedef struct SListNode
7. {
8.  SLTDataType data;
9.  struct SListNode* next;
10. }SLTNode;
11. 
12. //单向+不带头+非循环
13. 
14. //打印
15. void SListPrint(SLTNode* plist);
16. 
17. //尾插(需要改变指针指向,就要传指针的地址,即二级指针,如需要交换两个int的值,那么swap函数的实参是两个int的地址,形参是两个int型指针)
18. void SListPushBack(SLTNode** plist,SLTDataType x);
19. 
20. //头插
21. void SListPushFront(SLTNode** plist, SLTDataType x);
22. 
23. //尾删
24. void SListPopBack(SLTNode** plist);
25. 
26. //头删
27. void SListPopFront(SLTNode** plist);
28. 
29. //单链表查找
30. SLTNode* SListFind(SLTNode* plist, SLTDataType x);
31. 
32. //在pos之后插入x
33. void SListInsertAfter(SLTNode* pos, SLTDataType x);
34. 
35. //在pos之前插入x
36. void SListInsertBefore(SLTNode** plist,SLTNode* pos, SLTDataType x);
37. 
38. //在pos之前插入x,没有头的情况:后插一个值为x的节点,再跟前面的结点值交换。
39. void SListInsertBeforeNoHead(SLTNode* pos, SLTDataType x);
40. 
41. //删除后一个节点
42. void SListEraseAfter(SLTNode* pos);
43. 
44. //删除当前位置
45. void SListEraseCur(SLTNode** plist, SLTNode* pos);

025-SList.c 链表函数实现

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #pragma once
3. #include<stdio.h>
4. #include<stdlib.h>
5. #include<assert.h>
6. 
7. #include "025-SList.h"
8. 
9. //打印
10. void SListPrint(SLTNode* plist)
11. {
12.   SLTNode* cur = plist;
13. 
14.   //找空节点,找到后不再打印
15.   while (cur != NULL)
16.   {
17.     printf("%d->", cur->data);
18. 
19.     //让cur指向下一个节点
20.     cur = cur->next;
21.   }
22. 
23.   printf("NULL\n");
24. }
25. 
26. //创建新节点
27. SLTNode* BuySLTNode(SLTDataType x)
28. {
29.   //申请空间
30.   SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
31.   if (node == NULL)
32.   {
33.     return NULL;
34.   }
35. 
36.   //数据赋值
37.   node->data = x;
38. 
39.   //指针赋值
40.   node->next = NULL;
41. 
42.   return node;
43. }

(1)尾插:

1. //尾插
2. void SListPushBack(SLTNode** pplist, SLTDataType x)
3. {
4.  SLTNode* newNode = BuySLTNode(x);
5. 
6.  //不能使用tail->next != NULL直接作为判断条件,因为有可能是空链表,对空指针解引用会造成非法访问,因此要分两种情况
7. 
8.  if (*pplist == NULL)//链表为空
9.  {
10.     *pplist = newNode;
11.   }
12.   else //链表不为空
13.   {
14.     SLTNode* tail = *pplist;
15. 
16.     //找尾节点
17.     while (tail->next != NULL)
18.     {
19.       tail = tail->next;
20.     }
21.     tail->next = newNode;
22.   }
23. }

(2)头插,不需要区分空链表和非空链表

1. //头插
2. void SListPushFront(SLTNode** pplist, SLTDataType x)
3. {
4.  SLTNode* newNode = BuySLTNode(x);
5.  newNode->next = *pplist;//新节点的下一个指向第一个节点
6.  *pplist = newNode;//链表指向新节点
7. }

(3)尾删

1. //尾删
2. void SListPopBack(SLTNode** pplist)
3. {
4.  //链表为空
5.  if (*pplist == NULL)
6.  {
7.    return;
8.  }
9.  //链表只有一个结点
10.   else if ((*pplist)->next == NULL)
11.   {
12.     free(*pplist);
13.     *pplist = NULL;
14.   }
15.   //链表有多个结点
16.   else
17.   {
18.     SLTNode* pre = NULL;//后面用该结点指向尾节点的前驱结点,因为删除为节点后,尾节点的前驱结点的后继结点要赋为空指针
19.     SLTNode* tail = *pplist;
20. 
21.     //寻找最后一个结点,循环结束后,tail为尾结点,pre为尾结点的前驱结点
22.     while (tail->next != NULL)
23.     {
24.       pre = tail;
25.       tail = tail->next;
26.     }
27. 
28. 
29.     free(tail);
30.     tail = NULL;
31. 
32.     pre->next = NULL;
33.   }
34. }

(4)头删

1. //头删
2. void SListPopFront(SLTNode** pplist)
3. {
4.  if (*pplist == NULL)
5.  {
6.    return;
7.  }
8.  else
9.  {
10.     SLTNode* next = (*pplist)->next;//保存后一个节点
11.     free(*pplist);
12.     *pplist = next;//链表指向下一个节点
13.   }
14. }

(5)单链表查找

1. //单链表查找
2. SLTNode* SListFind(SLTNode* plist, SLTDataType x)
3. {
4.  SLTNode* cur = plist;
5.  while (cur)//没找到就往后继续走
6.  {
7.    if (cur->data == x)
8.    {
9.      return cur;//找到了就返回
10.     }
11.     cur = cur->next;
12.   }
13.   return NULL;
14. }

(6) 在pos之后插入

1. //在pos之后插入
2. void SListInsertAfter(SLTNode* pos, SLTDataType x)
3. {
4.  assert(pos);
5. 
6.  SLTNode* newNode = BuySLTNode(x);//插入需要先创建结点
7.  newNode->next = pos->next;//新节点的下一个指向pos的下一个
8.  pos->next = newNode;//pos的下一个指向新节点
9. }

(7)在pos之前插入x

1. //在pos之前插入x
2. void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
3. {
4.  assert(pos);
5. 
6.  SLTNode* newNode = BuySLTNode(x);
7. 
8.  //第一个节点就是pos,即头插
9.  if (pos == *pplist)
10.   {
11.     newNode->next = pos;
12.     *pplist = newNode;
13.   }
14.   else
15.   {
16.     SLTNode* prev = NULL;//用来记录pos的前一个结点
17.     SLTNode* cur = *pplist;
18. 
19.     while (cur != pos)//找pos
20.     {
21.       prev = cur;
22.       cur = cur->next;
23.     }
24. 
25.     newNode->next = cur;//新节点的下一个指向pos,此时跳出while循环的cur=pos
26.     prev->next = newNode;//前一个结点的下一个指向新节点
27.   }
28. }

(8)在pos之前插入x,没有头(plist):后插一个值为x的节点,再跟前面的结点值交换

1. //在pos之前插入x,没有头(plist)的情况:后插一个值为x的节点,再跟前面的结点值交换。
2. void SListInsertBeforeNoHead(SLTNode* pos, SLTDataType x)
3. {
4.  assert(pos);
5. 
6.  SLTNode* newNode = BuySLTNode(x);
7. 
8.  //后插新节点
9.  newNode->next = pos->next;
10.   pos->next = newNode;
11. 
12.   //将新结点的值和pos的值进行交换
13.   SLTDataType temp = pos->data;
14.   newNode->data = temp;
15.   pos->data = x;
16. }

(9)删除后一个结点

1. //删除后一个节点
2. void SListEraseAfter(SLTNode* pos)
3. {
4.  assert(pos);
5. 
6.  if (pos->next == NULL)
7.  {
8.    return;
9.  }
10.   SLTNode* next = pos->next;
11.   pos->next = next->next;//pos的下一个指向pos的下一个结点的下一个结点
12.   free(next);
13.   next = NULL;
14. }

(10) 删除当前位置

1. //删除当前位置
2. void SListEraseCur(SLTNode** pplist, SLTNode* pos)
3. {
4.  assert(pos);
5.  SLTNode* prev = NULL;
6.  SLTNode* cur = *pplist;
7.  while (cur != pos)//寻找pos
8.  {
9.    prev = cur;
10.     cur = cur->next;
11.   }
12.   prev->next = cur->next;//前一个结点的下一个指向当前结点即pos的下一个
13.   free(cur);
14.   cur = NULL;
15. }

025-test.c 测试代码

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include<stdio.h>
3. #include<assert.h>
4. 
5. #include "025-SList.h"
6. 
7. void TestSList1()
8. {
9.  SLTNode* plist = NULL;
10.   SListPushBack(&plist, 1);
11.   SListPushBack(&plist, 2);
12.   SListPushBack(&plist, 3);
13.   SListPushBack(&plist, 4);
14. 
15.   SListPrint(plist);
16. 
17.   SListPushFront(&plist, 0);
18.   SListPrint(plist);
19. 
20.   SListPopBack(&plist);
21.   SListPrint(plist);
22.   SListPopBack(&plist);
23.   SListPrint(plist);
24.   SListPopBack(&plist);
25.   SListPrint(plist);
26.   SListPopBack(&plist);
27.   SListPrint(plist);
28. 
29. }
30. void TestSList2()
31. {
32.   SLTNode* plist = NULL;
33.   SListPushBack(&plist, 1);
34.   SListPushBack(&plist, 2);
35.   SListPushBack(&plist, 3);
36.   SListPushBack(&plist, 4);
37.   SListPrint(plist);
38. 
39.   SListPopFront(&plist);
40.   SListPopFront(&plist);
41.   SListPopFront(&plist);
42.   SListPopFront(&plist);
43.   SListPopFront(&plist);
44.   SListPrint(plist);
45. }
46. 
47. void TestSList3()
48. {
49.   SLTNode* plist = NULL;
50.   SListPushBack(&plist, 1);
51.   SListPushBack(&plist, 2);
52.   SListPushBack(&plist, 3);
53.   SListPushBack(&plist, 4);
54.   SListPrint(plist);
55. 
56.   SLTNode* pos = SListFind(plist, 3);
57.   if (pos)
58.   {
59.     //单链表查找兼具修改功能
60.     pos->data = 100;
61.     printf("找到了\n");
62.   }
63.   else
64.   {
65.     printf("没找到\n");
66.   }
67. 
68.   SListPrint(plist);
69. }
70. 
71. void TestSList4()
72. {
73.   SLTNode* plist = NULL;
74.   SListPushBack(&plist, 1);
75.   SListPushBack(&plist, 2);
76.   SListPushBack(&plist, 3);
77.   SListPushBack(&plist, 4);
78.   SListPrint(plist);
79. 
80.   SLTNode* pos = SListFind(plist, 3);
81.   SListInsertAfter(pos, 300);
82.   SListPrint(plist);
83. 
84.   SListInsertBefore(&plist, pos,400);
85.   SListPrint(plist);
86. 
87.   SListEraseCur(&plist, pos);
88.   SListPrint(plist);
89. }
90. 
91. int main()
92. {
93.   TestSList4();
94. 
95.   return 0;
96. }

执行结果:


相关文章
|
16天前
|
C语言
对链表使用插入排序的C语言实现示例
对链表使用插入排序的C语言实现示例
|
24天前
|
存储 编译器 C语言
【数据结构】C语言实现链队列(附完整运行代码)
【数据结构】C语言实现链队列(附完整运行代码)
35 0
|
24天前
|
存储 算法 程序员
【数据结构】C语言实现顺序表万字详解(附完整运行代码)
【数据结构】C语言实现顺序表万字详解(附完整运行代码)
38 0
|
26天前
|
存储 缓存 算法
数据结构-链表(一)
链表(Linked List)是一种常见的数据结构,用于存储和组织数据。与数组不同,链表的元素(节点)在内存中不必连续存储,而是通过指针链接在一起。 链表由多个节点组成,每个节点包含两部分:数据(存储实际的元素值)和指针(指向下一个节点的引用)。链表的第一个节点称为头节点,最后一个节点称为尾节点,尾节点的指针通常指向空值(null)。
31 1
数据结构—链表(超详细)(山东大学)(数据结构实验三)
数据结构—链表(超详细)(山东大学)(数据结构实验三)
数据结构|双向链表|带头结点|头插|尾插|尾删|头删
数据结构|双向链表|带头结点|头插|尾插|尾删|头删
|
11天前
数据结构--链表刷题(一)快慢指针(上)
数据结构--链表刷题(一)快慢指针
16 0
|
20天前
|
算法 C语言
【算法与数据结构】 C语言实现单链表队列详解2
【算法与数据结构】 C语言实现单链表队列详解
|
20天前
|
存储 算法 C语言
【算法与数据结构】 C语言实现单链表队列详解1
【算法与数据结构】 C语言实现单链表队列详解
|
20天前
|
缓存 算法 搜索推荐
【数据结构】链表(单链表与双链表实现+原理+源码)
【数据结构】链表(单链表与双链表实现+原理+源码)

热门文章

最新文章