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

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

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位置插入结点或者删除结点、寻找结点、摧毁链表等函数。并做了板书图示解释相应函数的流程,附带完整代码,以便帮助大家更好的理解。

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

相关文章
|
28天前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
95 4
|
1月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
57 4
|
2天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
41 20
|
26天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
1月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
99 23
|
26天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
52 5
|
25天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
56 1
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
49 0
|
1月前
|
算法 vr&ar 计算机视觉
数据结构之洪水填充算法(DFS)
洪水填充算法是一种基于深度优先搜索(DFS)的图像处理技术,主要用于区域填充和图像分割。通过递归或栈的方式探索图像中的连通区域并进行颜色替换。本文介绍了算法的基本原理、数据结构设计(如链表和栈)、核心代码实现及应用实例,展示了算法在图像编辑等领域的高效性和灵活性。同时,文中也讨论了算法的优缺点,如实现简单但可能存在堆栈溢出的风险等。
43 0
|
6月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表