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

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

前言

带头双向循环链表,是链表中最为复杂的一种结构,我们主要实现的功能为,头插尾插,头删尾删,初始化、打印、指定pos位置插入结点或者删除结点、寻找结点、摧毁链表等函数。


一、带头双向循环链表是什么?

如图所示:


二、实现带头双向循环链表

1.结构体和要实现函数

结构体如下:

1. typedef struct DSLDataType;
2. //定义双向链表的结构体
3. 
4. //双向链表每一个节点都有一个前驱节点和后继节点,可以根据节点获得其前后的节点
5. typedef struct DSListNode {
6.  struct DSListNode* prev;//前驱节点
7.  struct DSListNode* next;//后继节点
8.  int value;//数据域
9. }DSLNode;//重定义

相比单向链表,多一个指针域prev,指向前面的结点地址

实现功能的函数:

1. //初始化
2. DSLNode*ListInit();
3. //打印链表
4. void ListPrint(DSLNode* ps);
5. //尾部插入
6. void ListPushBack(DSLNode* ps, int data);
7. //头部插入
8. void ListPushFront(DSLNode* ps, int data);
9. //尾部删除
10. void ListPopBack(DSLNode* ps);
11. //头部删除
12. void ListPopFront(DSLNode* ps);
13. //判空
14. bool ListEmpty(DSLNode* ps);
15. //返回链表节点个数
16. int Listsize(DSLNode* ps);
17. //寻找指定元素,返回对应的节点
18. DSLNode* ListFind(DSLNode* ps, int data);
19. //在pos之前插入节点
20. void ListInsert(DSLNode* pos, int data);
21. //删除pos位置结点
22. void ListEarse(DSLNode* pos);
23. //摧毁链表
24. void ListDestory(DSLNode* ps);

2.初始化和打印链表

1. //对于函数的实现
2. //初始化
3. DSLNode* ListInit() {
4.  //初始化创建链表
5.  //创建新节点
6.  DSLNode* num = (DSLNode*)malloc(sizeof(DSLNode));
7.  //判断是否创建成功
8.  if (num == NULL) {
9.    perror("malloc fail\n");
10.     exit(-1);
11.   }
12.   num->prev = num;
13.   num->next = num;//作为头节点,前驱和后驱结点都指向自己
14.   return num;
15. }
16. 
17. //打印链表
18. void ListPrint(DSLNode* ps) {
19.   assert(ps);//断言
20.   DSLNode* cur = ps->next;
21.   printf("phead->");
22.   while (cur != ps) {//双向链表,回到ps,就表示转了一圈
23.     printf("%d->", cur->value);
24.     cur = cur->next;
25.   }
26.   printf("NULL\n");
27. }

3.头插和尾插

尾插如图所示:

代码如下:

1. 
2. DSLNode* CreatNode(int data) {//创建新节点
3.  DSLNode* cur = (DSLNode*)malloc(sizeof(DSLNode));
4.  if (cur == NULL) {
5.    perror("malloc fail\n");
6.    exit(-1);
7.  }
8.  cur->next = cur->prev = NULL;
9.  cur->value = data;
10. }
11. //尾部插入
12. void ListPushBack(DSLNode* ps, int data) {
13.   assert(ps);//断言
14.   DSLNode* newnode = CreatNode(data);
15.   DSLNode* tail = ps->prev;//把头节点之前的那个地址给tail
16.   tail->next = newnode;//将ps之前的那个数值,实际上是这个双向链表的最后一个元素的位置,的next指针指向新节点
17.   newnode->prev = tail;//新节点前后为 tail和ps
18.   newnode->next = ps;
19.   ps->prev = newnode;//tail的两个指针域都指向完成,就只剩下ps的前驱指针
20. 
21. }

头插如图所示:

代码如下:

1. 
2. //头部插入
3. void ListPushFront(DSLNode* ps, int data) {
4.  assert(ps);
5.  //头部插入就是将ps之后的一个地址给临时变量tail
6.  DSLNode* tail = ps->next;
7.  DSLNode* newnode = CreatNode(data);//创建新节点
8.  ps->next->prev = newnode;
9.  newnode->next = ps->next;
10.     newnode->prev = ps;
11.     ps->next = newnode;
12. }

4.头删和尾删

尾部删除如图所示:

代码如下:

1. //判空
2. bool ListEmpty(DSLNode* ps) {
3.  assert(ps);//断言
4.  return ps->next == ps;//如果是只有一个ps头节点,则表示为空,返回true,否则返回false
5. 
6. }
7. //尾部删除
8. void ListPopBack(DSLNode* ps) {
9. 
10.   assert(ps);//断言
11.   assert(!ListEmpty(ps));//先判断是否为空
12.   DSLNode* cur = ps->prev;
13.   //将cur的next值为NULL
14.   ps->prev = ps->prev->prev;
15.   ps->prev->next = ps;
16.   //这是直接略过尾部最后一个元素
17.   //跳过ps之前的一个节点,先让其左右节点相连,然后释放这个地址
18.   free(cur);
19.   cur = NULL;
20. }

头部删除如图所示:

代码如下:

1. //头部删除
2. void ListPopFront(DSLNode* ps) {
3.  assert(ps);
4.  assert(!ListEmpty(ps));
5.  DSLNode* cur = ps->next;
6.  //头删  是将头节点之后的第一个节点删除释放空间
7.  ps->next = ps->next->next;
8.  ps->next->prev = ps;
9.  free(cur);
10.   cur = NULL;
11. }

5.查找和返回结点个数

查找:返回一个结点,根据data数值域,来返回第一个遇到的结点cur,若没有返回NULL

1. //返回链表节点个数
2. int Listsize(DSLNode* ps) {
3.  assert(ps);
4.  int count = 0;
5.  DSLNode* cur = ps->next;
6.  while (cur != ps) {
7.    count++;
8.    cur = cur->next;
9.  }
10.   return count;
11. }
12. //查找
13. DSLNode* ListFind(DSLNode* ps, int data) {
14.   assert(ps);
15.   DSLNode* cur = ps->next;
16.   while (cur != ps) {
17.     if (cur->value == data) {
18.       return cur;
19.     }
20.   }
21.   return NULL;//NULL表示不存在
22. }

6.在pos位置之前插入结点

如图所示:

代码如下:

1. //插入节点
2. void ListInsert(DSLNode* pos, int data) {
3. 
4.  assert(pos);
5.  //pos三种可能
6.  //1.空链表
7.  //2.只有一个节点
8.  DSLNode* cur = pos->prev;
9.  //双向链表可以直接找到pos之前的位置,单向链表只能进行循环
10.   DSLNode* newnode = CreatNode(data);
11.   pos->prev = newnode;//把新节点newnode的位置给pos的prev
12.   newnode->prev = cur;//cur表示的是创建new节点之前的时候pos之前的结点
13.   cur->next = newnode;
14.   newnode->next = pos;
15.   //另一种不使用cur的方法
16.   //newnode->next = pos;
17.   //pos->prev->next = newnode;
18.   //newnode->prev = pos->prev;
19.   //pos->prev = newnode;
20. }


相关文章
|
25天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
38 1
|
28天前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
95 4
|
2天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
41 20
|
26天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
26天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
1月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
99 23
|
1月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
60 20
|
25天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
56 1
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
202 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
32 1