【数据结构和算法】实现带头双向循环链表(最复杂的链表)(下)

简介: 【数据结构和算法】实现带头双向循环链表(最复杂的链表)(下)

7.删除指定pos结点

如图所示:

代码如下:

1. 
2. //删除指针
3. void ListEarse(DSLNode* pos) {
4.  assert(pos);
5.  DSLNode* cur = pos->prev;
6.  DSLNode* tail = pos->next;
7.  cur->next = tail;//两者相互指向,最后释放pos空间
8.  tail->prev = cur;
9.  free(pos);
10.   pos = NULL;
11. }

8.摧毁链表

两种方式,可以直接使用二级指针,也可以使用一级指针一个一个free和NULL

1. //摧毁链表
2. void ListDestory(DSLNode* ps) {
3.  //可以设计二级指针直接将ps地址滞空为NULL
4.  //这里还是使用一级指针
5.  assert(ps);
6.  DSLNode* cur = ps->next;
7.  while (cur != ps) {
8.    DSLNode* tail = cur->next;//这个地方就是一个精彩的地方
9.    free(cur);//使用临时变量tail得到cur的下一个地址,然后再free cur
10.     cur = tail;//tail这个时候就是cur 的下一个结点
11.   }
12.   free(ps);
13.   ps = NULL;
14. 
15. }
16. void ListDestory2(DSLNode** ps) {
17.   assert(ps);
18.   free(ps);//二级指针直接free
19.   *ps = NULL;
20. }

三、完整代码

1.DSLinkList.h

1. #define _CRT_SECURE_NO_WARNINGS
2. 
3. #include<stdio.h>
4. #include<stdlib.h>
5. #include<malloc.h>
6. #include<assert.h>
7. #include<stdbool.h>
8. 
9. typedef struct DSLDataType;
10. //定义双向链表的结构体
11. 
12. //双向链表每一个节点都有一个前驱节点和后继节点,可以根据节点获得其前后的节点
13. typedef struct DSListNode {
14.   struct DSListNode* prev;//前驱节点
15.   struct DSListNode* next;//后继节点
16.   int value;//数据域
17. }DSLNode;//重定义
18. 
19. //创建头节点,并将tail和head都指向第一个节点
20. struct DSList {
21.   struct DSListNode* tail;
22.   struct DSListNode* head;
23.   unsigned int len;//表示链表的长度
24. };
25. //初始化
26. DSLNode*ListInit();
27. //打印链表
28. void ListPrint(DSLNode* ps);
29. //尾部插入
30. void ListPushBack(DSLNode* ps, int data);
31. //头部插入
32. void ListPushFront(DSLNode* ps, int data);
33. //尾部删除
34. void ListPopBack(DSLNode* ps);
35. //头部删除
36. void ListPopFront(DSLNode* ps);
37. //判空
38. bool ListEmpty(DSLNode* ps);
39. //返回链表节点个数
40. int Listsize(DSLNode* ps);
41. //寻找指定元素,返回对应的节点
42. DSLNode* ListFind(DSLNode* ps, int data);
43. //在pos之前插入节点
44. void ListInsert(DSLNode* pos, int data);
45. //删除pos位置结点
46. void ListEarse(DSLNode* pos);
47. //摧毁链表
48. void ListDestory(DSLNode* ps);
49.

2.DSLinkList.c

1. #define _CRT_SECURE_NO_WARNINGS
2. 
3. #include"DSLinkList.h"
4. 
5. //对于函数的实现
6. //初始化
7. DSLNode* ListInit() {
8.  //初始化创建链表
9.  //创建新节点
10.   DSLNode* num = (DSLNode*)malloc(sizeof(DSLNode));
11.   //判断是否创建成功
12.   if (num == NULL) {
13.     perror("malloc fail\n");
14.     exit(-1);
15.   }
16.   num->prev = num;
17.   num->next = num;
18.   return num;
19. }
20. 
21. //打印链表
22. void ListPrint(DSLNode* ps) {
23.   assert(ps);//断言
24.   DSLNode* cur = ps->next;
25.   printf("phead->");
26.   while (cur != ps) {//双向链表,回到ps,就表示转了一圈
27.     printf("%d->", cur->value);
28.     cur = cur->next;
29.   }
30.   printf("NULL\n");
31. }
32. 
33. DSLNode* CreatNode(int data) {//创建新节点
34.   DSLNode* cur = (DSLNode*)malloc(sizeof(DSLNode));
35.   if (cur == NULL) {
36.     perror("malloc fail\n");
37.     exit(-1);
38.   }
39.   cur->next = cur->prev = NULL;
40.   cur->value = data;
41. }
42. //尾部插入
43. void ListPushBack(DSLNode* ps, int data) {
44.   assert(ps);//断言
45.   DSLNode* newnode = CreatNode(data);
46.   DSLNode* tail = ps->prev;//把头节点之前的那个地址给tail
47.   tail->next = newnode;//将ps之前的那个数值,实际上是这个双向链表的最后一个元素的位置,的next指针指向新节点
48.   newnode->prev = tail;//新节点前后为 tail和ps
49.   newnode->next = ps;
50.   ps->prev = newnode;//tail的两个指针域都指向完成,就只剩下ps的前驱指针
51. 
52. }
53. 
54. //头部插入
55. void ListPushFront(DSLNode* ps, int data) {
56.   assert(ps);
57.   //头部插入就是将ps之后的一个地址给临时变量tail
58.   DSLNode* tail = ps->next;
59.   DSLNode* newnode = CreatNode(data);//创建新节点
60.   ps->next->prev = newnode;
61.   newnode->next = ps->next;
62.     newnode->prev = ps;
63.     ps->next = newnode;
64. }
65. 
66. //判空
67. bool ListEmpty(DSLNode* ps) {
68.   assert(ps);//断言
69.   return ps->next == ps;//如果是只有一个ps头节点,则表示为空,返回true,否则返回false
70. 
71. }
72. 
73. //返回链表节点个数
74. int Listsize(DSLNode* ps) {
75.   assert(ps);
76.   int count = 0;
77.   DSLNode* cur = ps->next;
78.   while (cur != ps) {
79.     count++;
80.     cur = cur->next;
81.   }
82.   printf("\n");
83.   return count;
84. }
85. //尾部删除
86. void ListPopBack(DSLNode* ps) {
87. 
88.   assert(ps);//断言
89.   assert(!ListEmpty(ps));//先判断是否为空
90.   DSLNode* cur = ps->prev;
91.   //将cur的next值为NULL
92.   ps->prev = ps->prev->prev;
93.   ps->prev->next = ps;
94.   //这是直接略过尾部最后一个元素
95.   //跳过ps之前的一个节点,先让其左右节点相连,然后释放这个地址
96.   free(cur);
97.   cur = NULL;
98. }
99. //头部删除
100. void ListPopFront(DSLNode* ps) {
101.  assert(ps);
102.  assert(!ListEmpty(ps));
103.  DSLNode* cur = ps->next;
104.  //头删  是将头节点之后的第一个节点删除释放空间
105.  ps->next = ps->next->next;
106.  ps->next->prev = ps;
107.  free(cur);
108.  cur = NULL;
109. }
110. //查找
111. DSLNode* ListFind(DSLNode* ps, int data) {
112.  assert(ps);
113.  DSLNode* cur = ps->next;
114.  while (cur != ps) {
115.    if (cur->value == data) {
116.      return cur;
117.    }
118.  }
119.  return NULL;//NULL表示不存在
120. }
121. //插入节点
122. void ListInsert(DSLNode* pos, int data) {
123. 
124.  assert(pos);
125.  //pos三种可能
126.  //1.空链表
127.  //2.只有一个节点
128.  DSLNode* cur = pos->prev;
129.  //双向链表可以直接找到pos之前的位置,单向链表只能进行循环
130.  DSLNode* newnode = CreatNode(data);
131.  pos->prev = newnode;//把新节点newnode的位置给pos的prev
132.  newnode->prev = cur;//cur表示的是创建new节点之前的时候pos之前的结点
133.  cur->next = newnode;
134.  newnode->next = pos;
135.  //另一种不使用cur的方法
136.  //newnode->next = pos;
137.  //pos->prev->next = newnode;
138.  //newnode->prev = pos->prev;
139.  //pos->prev = newnode;
140. }
141. 
142. 
143. //删除指针
144. void ListEarse(DSLNode* pos) {
145.  assert(pos);
146.  DSLNode* cur = pos->prev;
147.  DSLNode* tail = pos->next;
148.  cur->next = tail;//两者相互指向,最后释放pos空间
149.  tail->prev = cur;
150.  free(pos);
151.  pos = NULL;
152. }
153. //摧毁链表
154. void ListDestory(DSLNode* ps) {
155.  //可以设计二级指针直接将ps地址滞空为NULL
156.  //这里还是使用一级指针
157.  assert(ps);
158.  DSLNode* cur = ps->next;
159.  while (cur != ps) {
160.    DSLNode* tail = cur->next;
161.    free(cur);
162.    cur = tail;
163.  }
164.  free(ps);
165.  ps = NULL;
166. 
167. }
168. void ListDestory2(DSLNode** ps) {
169.  assert(ps);
170.  free(ps);
171.  *ps = NULL;
172. }

3.test.c

1. #define _CRT_SECURE_NO_WARNINGS
2. #include"DSLinkList.h"
3. 
4. void test()
5. {
6.  DSLNode* phead = ListInit();//初始化
7. 
8.  ListPushBack(phead, 1);
9.  ListPushBack(phead, 2);
10.   ListPushBack(phead, 3);
11.   ListPushBack(phead, 4);
12.   ListPushBack(phead, 5);//检验尾插
13.   ListPrint(phead);//打印
14. 
15.   ListPushFront(phead, 10);
16.   ListPushFront(phead, 20);
17.   ListPushFront(phead, 30);
18.   ListPushFront(phead, 40);
19.   ListPushFront(phead, 50);//检验头插
20.   ListPrint(phead);//打印
21.   printf("%d\n", Listsize(phead));//检验返回结点个数
22. 
23.   ListPopBack(phead);
24.   ListPopBack(phead);
25.   ListPopBack(phead);
26.   ListPopBack(phead);
27.   ListPopBack(phead);//尾删
28.   ListPopFront(phead);
29.   ListPopFront(phead);
30.   ListPopFront(phead);
31.   ListPopFront(phead);
32.   ListPopFront(phead);//头删
33.   ListPrint(phead);//打印
34.   ListInsert(phead->next, 10);//pos指定结点之前插入newnode新节点
35.   ListPrint(phead);//打印
36. }
37. int main()
38. {
39.   test();
40.   return 0;
41. }

总结

我们本文主要讲解了,链表中最为复杂的带头双向循环链表的使用和代码实现,实现了头插尾插,头删尾删,初始化、打印、指定pos位置插入结点或者删除结点、寻找结点、摧毁链表等函数。并做了板书图示解释相应函数的流程,附带完整代码,以便帮助大家更好的理解。

接下来我们就要进行栈和队列的学习,本文不足之处,欢迎大佬指导一二,让我们开始新章节的学习!!!

相关文章
|
1月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
69 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
18天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
45 4
|
19天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
19天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
32 4
|
1月前
|
存储 缓存 算法
经典算法之链表篇(三)
经典算法之链表篇(三)
|
18天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
38 0
|
1月前
|
存储
[数据结构] -- 双向循环链表
[数据结构] -- 双向循环链表
22 0
|
5月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
|
5月前
|
存储 SQL 算法
LeetCode 题目 86:分隔链表
LeetCode 题目 86:分隔链表