【数据结构与算法】双向带头循环链表(附源码)

简介: 【数据结构与算法】双向带头循环链表(附源码)

一.前言

在前面的博客中,我们学习了顺序表和结构最简单的链表——单链表,但是单链表存在在着一些不足,比如单链表的插入和删除的操作,总是要找到指定节点的前驱或是后继,这样就会比较麻烦。

那么本篇文章所讲述的双向带头循环链表(以后简称双链表),就可以很好解决这个问题。

二.双向带头循环链表的结构

1.该链表有一个哨兵位节点,即头节点

2.每个节点都包含一个prev 指针和 next 指针,分别指向当前节点的前驱和后继;

3.头节点的 prev 指向的是尾节点,尾节点的next 指向的是头节点,这样就实现了循环

请看图示:

别看结构这么复杂,但其实它是一个很厉害的结构,代码实现会很简单。

三.接口实现

A.初始化 ListNodeinit 和销毁 Listdestroy

1.ListNodeinit

这个接口用来创建哨兵位头节点,并使该节点的 prev 和 next 都指向自己以实现循环的目的。

1. LNode* Buynewnode(DLdatatype x)   //定义一个 malloc 新节点的函数,以便后续需要
2. {
3.  LNode* newnode = (LNode*)malloc(sizeof(LNode));
4.  assert(newnode);
5.  newnode->prev = NULL;
6.  newnode->next = NULL;
7.  newnode->data = x;
8.  return newnode;
9. }
10. LNode* ListNodeinit()
11. {
12.   LNode* phead = (LNode*)malloc(sizeof(LNode));
13.   assert(phead);
14.   phead->prev = phead;  //使其都指向自己
15.   phead->next = phead;
16.   phead->data = -1;
17.   return phead;
18. }

2.Listdestroy

我们需要遍历链表,并定义一个next 来保存 cur 的下一个节点,在链表都free 完后,再销毁头节点;

注意:应该是从头节点的 next 开始遍历

1. void ListNodedestroy(LNode* phead)
2. {
3.  assert(phead);
4. 
5.  LNode* cur = phead->next;
6.  while (cur != phead)
7.  {
8.    LNode* next = cur->next;
9.    free(cur);
10.     cur = next;
11.   }
12. 
13.   free(phead);
14.   phead = NULL;
15. }

B.插入

1.头插  ListNodepushfront

1.这里要头插就太简单了,但是我们最好保存一下头节点的 next ,这样就不应考虑链接时的顺序问题;

2.使头节点的 next 指向新节点,然后新节点的 prev 指向头节点;

3.新节点的 next 指向保存的节点,保存的节点的 prev 指向新节点;

这里并不需要考虑链表为空的情况,这就是双链表的强大之处。

1. void ListNodepushfront(LNode* phead, DLdatatype x)
2. {
3.  assert(phead);
4. 
5.  LNode* newnode = Buynewnode(x);
6. 
7.  LNode* first = phead->next;  //保存头节点的下一个
8. 
9.  phead->next = newnode;
10.   newnode->prev = phead;
11.   newnode->next = first;
12.   first->prev = newnode;
13. }
14.

2.尾插 ListNodepushback

1.尾插就需要找尾;

2.这里的找尾很简单,就是头节点的 prev

3.将尾节点与新节点链接起来。

1. void ListNodepushback(LNode* phead, DLdatatype x)
2. {
3.  assert(phead);
4. 
5.  LNode* newnode = Buynewnode(x);
6.  LNode* tail = phead->prev;
7.  tail->next = newnode;
8.  newnode->prev = tail;
9.  newnode->next = phead;
10.   phead->prev = newnode;
11. 
12. }

3.插入 ListNodeinsert

1.再插入前需要写一个 find 函数,用来寻找指定的位置 pos

2.找到 pos 的前驱 prev ;

3.将前驱,pos,新节点链接起来。

1. LNode* ListNodefind(LNode* phead,DLdatatype x)
2. {
3.  assert(phead);
4. 
5.  LNode* cur = phead->next;
6.  while (cur != phead)
7.  {
8.    if (cur->data == x)
9.    {
10.       return cur;
11.     }
12. 
13.     cur = cur->next;
14. 
15.   }
16. 
17.   return NULL;
18. }
19. 
20. void ListNodeinsert(LNode* pos, LNode* phead, DLdatatype x)
21. {
22.   assert(phead);
23.   assert(pos);
24. 
25.   LNode* newnode = Buynewnode(x);
26.   LNode* prev = pos->prev;
27. 
28.   prev->next = newnode;
29.   newnode->prev = prev;
30.   newnode->next = pos;
31.   pos->prev = newnode;
32. }

总结:其实可以用一个插入接口就可以实现头插和尾插这两个接口。

C.删除

1.删除时要注意的点是不能把头节点也给删了,如果删了就破坏了双链表的结构;

2.如果是空链表也不能删除。

1.头删 ListNodepopfront

1.删除时保存其下一个节点;

2.链接头节点 phead 和 保存的下一个节点;

3.删除 phead 的next。

1. void ListNodepopfront(LNode* phead)
2. {
3.  assert(phead);
4.  assert(ListNodeempty(phead));
5.  if (phead->next == phead)
6.  {
7.    perror("ListNodepopfront");
8.    return;
9.  }
10. 
11.   LNode* first = phead->next;
12.   LNode* second = first->next;
13.   phead->next = second;
14.   second->prev = phead;
15. 
16.   free(first);
17.   first = NULL;
18. 
19. 
20. }

2.尾删 ListNodepopback

1.找尾,即:phead的prev;

2.找尾节点的前驱 prev ,使其next指向phead,phead的prev指向该前驱

1. void ListNodepopback(LNode* phead)
2. {
3.  assert(phead);
4.  assert(ListNodeempty(phead));
5. 
6.  if (phead->next == phead)
7.  {
8.    perror("ListNodepopback");
9.    exit(-1);
10.   }
11. 
12.   LNode* tail = phead->prev;
13.   LNode* prev = tail->prev;
14. 
15.   prev->next = phead;
16.   phead->prev = prev;
17.   free(tail);
18.   tail = NULL;
19. }

3.删除 ListNodeerase

1.调用 find 函数找到要删除的节点pos;

2.找到 pos 的前驱和后继

3.链接其前驱,后继

4.删除pos。

1. void ListNodeerase(LNode* pos, LNode* phead)
2. {
3.  assert(phead);
4.  assert(pos);
5. 
6.  if (phead->next == phead)
7.  {
8.    perror("ListNodeerase");
9.    exit(-1);
10.   }
11.   LNode* prev = pos->prev;
12.   LNode* next = pos->next;
13. 
14.   prev->next = next;
15.   next->prev = prev;
16. 
17.   free(pos);
18.   pos = NULL;
19. }

总结:这里可以复用删除函数去实现头删和尾删。

D.打印  ListNodeprint

注意是从头节点的下一个节点开始遍历,且循环终止条件是看是否等于 phead,因为双链表没有 NULL。

1. void ListNodeprint(LNode* phead)
2. {
3.  assert(phead);
4.  LNode* cur = phead->next;
5.  while (cur != phead)
6.  {
7.    printf("%d <=> ", cur->data);
8.    cur = cur->next;
9.  }
10. 
11.   printf("\n");
12. 
13. 
14. }

四.源码

List.h

1. #pragma once
2. 
3. 
4. #include <stdio.h>
5. #include <stdlib.h>
6. #include <assert.h>
7. #include <stdbool.h>
8. 
9. typedef int DLdatatype;
10. 
11. typedef struct ListNode
12. {
13.   struct ListNode* prev;
14.   struct ListNode* next;
15.   DLdatatype data;
16. }LNode;
17. 
18. LNode* ListNodeinit();
19. 
20. void ListNodeprint(LNode* phead);
21. 
22. void ListNodepushback(LNode*phead,DLdatatype x);
23. 
24. void ListNodepushfront(LNode* phead, DLdatatype x);
25. 
26. void ListNodepopback(LNode* phead);
27. 
28. void ListNodepopfront(LNode* phead);
29. 
30. LNode* ListNodefind(LNode* phead,DLdatatype x);
31. 
32. void ListNodeinsert(LNode* pos, LNode* phead, DLdatatype x);
33. 
34. void ListNodeerase(LNode* pos, LNode* phead);
35. 
36. void ListNodedestroy(LNode* phead);

List.c

1. #define _CRT_SECURE_NO_WARNINGS
2. 
3. #include "List.h"
4. 
5. LNode* Buynewnode(DLdatatype x)
6. {
7.  LNode* newnode = (LNode*)malloc(sizeof(LNode));
8.  assert(newnode);
9.  newnode->prev = NULL;
10.   newnode->next = NULL;
11.   newnode->data = x;
12.   return newnode;
13. }
14. 
15. LNode* ListNodeinit()
16. {
17.   LNode* phead = (LNode*)malloc(sizeof(LNode));
18.   assert(phead);
19.   phead->prev = phead;
20.   phead->next = phead;
21.   phead->data = -1;
22.   return phead;
23. }
24. 
25. void ListNodeprint(LNode* phead)
26. {
27.   assert(phead);
28.   LNode* cur = phead->next;
29.   while (cur != phead)
30.   {
31.     printf("%d <=> ", cur->data);
32.     cur = cur->next;
33.   }
34. 
35.   printf("\n");
36. 
37. 
38. }
39. 
40. void ListNodepushback(LNode* phead, DLdatatype x)
41. {
42.   assert(phead);
43. 
44.   /*LNode* newnode = Buynewnode(x);
45.   LNode* tail = phead->prev;
46.   tail->next = newnode;
47.   newnode->prev = tail;
48.   newnode->next = phead;
49.   phead->prev = newnode;*/
50. 
51.   ListNodeinsert(phead, phead, x);
52. 
53. 
54. }
55. 
56. void ListNodepushfront(LNode* phead, DLdatatype x)
57. {
58.   assert(phead);
59. 
60.   /*LNode* newnode = Buynewnode(x);
61. 
62.   LNode* first = phead->next;
63. 
64.   phead->next = newnode;
65.   newnode->prev = phead;
66.   newnode->next = first;
67.   first->prev = newnode;*/
68. 
69.   ListNodeinsert(phead->next, phead, x);
70. }
71. 
72. bool ListNodeempty(LNode* phead)
73. {
74.   assert(phead);
75. 
76.   return phead->next == phead;
77. }
78. 
79. void ListNodepopback(LNode* phead)
80. {
81.   assert(phead);
82.   //assert(ListNodeempty(phead));
83. 
84.   if (phead->next == phead)
85.   {
86.     perror("ListNodepopback");
87.     exit(-1);
88.   }
89. 
90.   /*LNode* tail = phead->prev;
91.   LNode* prev = tail->prev;
92. 
93.   prev->next = phead;
94.   phead->prev = prev;
95.   free(tail);
96.   tail = NULL;*/
97. 
98.   ListNodeerase(phead->prev, phead);
99. }
100. 
101. void ListNodepopfront(LNode* phead)
102. {
103.  assert(phead);
104.  //assert(ListNodeempty(phead));
105.  if (phead->next == phead)
106.  {
107.    perror("ListNodepopfront");
108.    return;
109.  }
110. 
111.  /*LNode* first = phead->next;
112.  LNode* second = first->next;
113.  phead->next = second;
114.  second->prev = phead;
115. 
116.  free(first);
117.  first = NULL;*/
118. 
119.  ListNodeerase(phead->next, phead);
120. }
121. 
122. LNode* ListNodefind(LNode* phead,DLdatatype x)
123. {
124.  assert(phead);
125. 
126.  LNode* cur = phead->next;
127.  while (cur != phead)
128.  {
129.    if (cur->data == x)
130.    {
131.      return cur;
132.    }
133. 
134.    cur = cur->next;
135. 
136.  }
137. 
138.  return NULL;
139. }
140. 
141. void ListNodeinsert(LNode* pos, LNode* phead, DLdatatype x)
142. {
143.  assert(phead);
144.  assert(pos);
145. 
146.  LNode* newnode = Buynewnode(x);
147.  LNode* prev = pos->prev;
148. 
149.  prev->next = newnode;
150.  newnode->prev = prev;
151.  newnode->next = pos;
152.  pos->prev = newnode;
153. }
154. 
155. void ListNodeerase(LNode* pos, LNode* phead)
156. {
157.  assert(phead);
158.  assert(pos);
159. 
160.  if (phead->next == phead)
161.  {
162.    perror("ListNodeerase");
163.    exit(-1);
164.  }
165.  LNode* prev = pos->prev;
166.  LNode* next = pos->next;
167. 
168.  prev->next = next;
169.  next->prev = prev;
170. 
171.  free(pos);
172.  pos = NULL;
173. }
174. 
175. void ListNodedestroy(LNode* phead)
176. {
177.  assert(phead);
178. 
179.  LNode* cur = phead->next;
180.  while (cur != phead)
181.  {
182.    LNode* next = cur->next;
183.    free(cur);
184.    cur = next;
185.  }
186. 
187.  free(phead);
188.  phead = NULL;
189. 
190. }

test.c

至于test.c 就不贴出来了,这里面主要是一些测试接口的代码,没必要贴出来。


😽本篇文章到此就结束了,若有错误或是建议,欢迎小伙伴们指出;😻

😍请多多支持博主哦~🥰

🤩谢谢你的阅读~😃


目录
相关文章
|
2月前
|
算法 数据可视化 数据挖掘
基于EM期望最大化算法的GMM参数估计与三维数据分类系统python源码
本内容展示了基于EM算法的高斯混合模型(GMM)聚类实现,包含完整Python代码、运行效果图及理论解析。程序使用三维数据进行演示,涵盖误差计算、模型参数更新、结果可视化等关键步骤,并附有详细注释与操作视频,适合学习EM算法与GMM模型的原理及应用。
|
5月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
100 0
|
6月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
193 30
|
7月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
296 25
|
7月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
1263 0
|
8月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
328 5
|
9月前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
660 5
|
9月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
10月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
273 5

热门文章

最新文章