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

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

前言

带头双向循环链表,是链表中最为复杂的一种结构,我们主要实现的功能为,头插尾插,头删尾删,初始化、打印、指定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. }


相关文章
|
6天前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
28 10
【数据结构】链表从实现到应用,保姆级攻略
|
24天前
|
算法
【初阶数据结构】复杂度算法题篇
该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 kmodn 个元素会移动至数组头部,其余元素向后移动 kmodn 个位置。
|
25天前
|
机器学习/深度学习 人工智能 算法
【人工智能】线性回归模型:数据结构、算法详解与人工智能应用,附代码实现
线性回归是一种预测性建模技术,它研究的是因变量(目标)和自变量(特征)之间的关系。这种关系可以表示为一个线性方程,其中因变量是自变量的线性组合。
37 2
|
22天前
|
存储 Java 程序员
"揭秘HashMap底层实现:从数组到链表,再到红黑树,掌握高效数据结构的秘密武器!"
【8月更文挑战第21天】HashMap是Java中重要的数据结构,采用数组+链表/红黑树实现,确保高效查询与更新。构造方法初始化数组,默认容量16,负载因子0.75触发扩容。`put`操作通过计算`hashCode`定位元素,利用链表或红黑树处理冲突。`get`和`remove`操作类似地定位并返回或移除元素。JDK 1.8优化了链表转红黑树机制,提升性能。理解这些原理能帮助我们更高效地应用HashMap。
29 0
|
23天前
|
算法
【初阶数据结构篇】二叉树算法题
二叉树是否对称,即左右子树是否对称.
|
24天前
|
算法 索引
【初阶数据结构篇】单链表算法题进阶
深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。
|
24天前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
|
24天前
|
存储 测试技术
【初阶数据结构篇】双向链表的实现(赋源码)
因为头结点的存在,plist指针始终指向头结点,不会改变。
|
24天前
|
存储 测试技术
【初阶数据结构篇】单链表的实现(附源码)
在尾插/尾删中,都需要依据链表是否为空/链表是否多于一个节点来分情况讨论,目的是避免对空指针进行解引用造成的错误。
|
26天前
|
存储 缓存 算法
深入解析B树:数据结构、存储结构与算法优势
深入解析B树:数据结构、存储结构与算法优势