当前位置:   article > 正文

数据结构线性表——带头双向循环链表_空闲分区采用带头结点的双向链表来管理,主函数、链表初始化函数和打印函数已实现,

空闲分区采用带头结点的双向链表来管理,主函数、链表初始化函数和打印函数已实现,

前言:小伙伴们好久不见啦,上篇文章我们一起学习了数据结构线性表其一的单链表,了解了单链表的不少好处,但是不可能有完美的数据结构,就算是单链表,也会有很多缺点。

那么今天这篇文章,我们就来学习单链表的promax版本——带头双向循环链表


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

关于带头双向循环链表,我们将它拆分为带头、双向、循环、链表四个部分,其中链表我们已经知道是怎么回事了,那我们就来一起结合下图分析前三个概念。

1.带头 

        所谓带头,也就是在链表的开头处,有一个不存放任何数据的头节点,我们通常称其为“哨兵位”。

        那么哨兵位存在的意义是什么呢???

        它可以帮助我们更方便的进行对链表的各种操作。具体好在哪里,我们结合后边实现链表的各种操作来进行展示。

2.双向

        我们前边学习过的单链表,它的每个节点之间只有一条链子相连,并且只能由前一个节点去找到后一个节点

        而双向链表,也就是两个节点之间有两条链子相连,不仅能从前一个找到后一个,也能从后一个去找到前一个

3.循环

        循环,顾名思义,就是将链表的头尾也进行连接,形成一个逻辑意义上的环形链表。

那么理解完带头双向循环链表的含义之后,我就就一起来看看到底来如何实现它吧。

此后我们将该链表的名字简化为双链表


二.双链表的实现

1.双链表的定义

  1. typedef int DLLDataType;
  2. //定义双链表
  3. typedef struct DLinkList
  4. {
  5. DLLDataType data;
  6. struct DLinkList* prev;//指向前一个节点
  7. struct DLinkList* next;//指向后一个节点
  8. }DLLNode;

双链表是在单链表的基础上,比它多出一个prev指针去指向前一个节点,还是比较容易理解的。


2.双链表的初始化

  1. //初始化双链表
  2. DLLNode* DLinkListInit()
  3. {
  4. DLLNode* phead = (DLLNode*)malloc(sizeof(DLLNode));
  5. if (phead == NULL)
  6. {
  7. perror("DLinkListInit->malloc");
  8. }
  9. phead->next = phead;
  10. phead->prev = phead;
  11. return phead;
  12. }

双链表的初始化需要先造出哨兵位考虑到链表为空,并且链表还要循环,所以我们将哨兵位的prev和next都指向自己

    DLLNode* dll = DLinkListInit();

创建一个双链表,我们习惯于运用上述方式。

因为如果用单链表的初始化方式,我们需要用到二级指针,但是我们后续双链表各种功能的操作,完全不和二级指针沾边

所以为了让我们的双链表全部由一级指针完成,选择采用接收函数返回值的方式来创建双链表


3.双链表节点的创建

  1. DLLNode* CreateNewNode(DLLDataType x)
  2. {
  3. DLLNode* newnode = (DLLNode*)malloc(sizeof(DLLNode));
  4. if (newnode == NULL)
  5. {
  6. perror("CreateNewNode->malloc");
  7. }
  8. newnode->data = x;
  9. newnode->next = NULL;
  10. newnode->prev = NULL;
  11. return newnode;
  12. }

双链表创建新节点就和单链表差不多啦,要注意的就是不要忘记两个指针置空,防止出现野指针

这样,我们就实现了一个基本的双链表框架,下面来实现双链表的各种基础操作。


 三.双链表的操作

1.双链表的打印

那么为了方便其他功能的测试,我们还是先来实现双链表的打印功能:

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

我们还是严格的进行一下assert断言如果phead为空,就说明双链表不存在

这里要注意两点:

1.cur为什么是phead->next???

        不难理解,我们在双链表初始化的时候,给到dll的返回值是哨兵位,但是哨兵位不存储数据,所以要从哨兵位的下一个节点开始。

2.while循环的判断条件

        因为我们是一个可循环的链表,所以并不存在cur为空的情况,但是cur最后会重新指向哨兵位,所以当cur == phead时,说明我们已经将双链表遍历一遍了

至于printf函数的内容,只是为了好看哈哈,展示一下:

这样能够让大家更形象的认识双链表。


2.双链表的尾插

双链表的尾插相较于单链表有什么优势呢???

单链表想尾插,首先要进行循环找尾时间复杂度就高了,但是双链表就好办,因为哨兵位的前一个节点就是尾,也就是phead->prev,尾找到之后,就好办了:

  1. //尾插
  2. void DLinkListPushBack(DLLNode* phead, DLLDataType x)
  3. {
  4. assert(phead);
  5. DLLNode* newnode = CreateNewNode(x);
  6. DLLNode* tail = phead->prev;
  7. tail->next = newnode;
  8. newnode->next = phead;
  9. newnode->prev = tail;
  10. phead->prev = newnode;
  11. }

用tail代替尾,接下来的一顿操作,就是:

旧尾的next指向新尾

新尾的next指向哨兵位

新尾的prev指向旧尾

哨兵位的prev指向新尾

看起来很简单,但是我们知道,单链表必须得考虑一下链表是否为空的特例,但是双链表不需要

因为双链表如果为空,那就只有哨兵位,哨兵位自己的头尾相连,带入上述代码操作之后,不会有任何错误。


 3.双链表的尾删

尾删就更简单了,只需要找到尾,再通过尾找到尾的前一个节点,再让此节点和哨兵位互连,再将尾free即可:

  1. //尾删
  2. void DLinkListPopBack(DLLNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead);
  6. DLLNode* tail = phead->prev;
  7. DLLNode* tailprev = tail->prev;
  8. phead->prev = tailprev;
  9. tailprev->next = phead;
  10. free(tail);
  11. tail = NULL;
  12. }

尾删要考虑只有一个节点的特例吗,依然不用,因为进行一顿操作之后,还是让哨兵位自己头尾相连

但是尾删要考虑空链表的情况,因为如果链表为空,free的就是哨兵位了,哨兵位一旦不存在了,我们就无法进行后续的操作了。所以要多进行一次assert断言。

到这里,小伙伴们是否已经感受到了哨兵位,以及双链表的强势之处啦


4.双链表的头插

头插就和尾插差不多了,这里我直接给出代码,希望小伙伴们可以自己理解掌握哦。

  1. //头插
  2. void DLinkListPushFront(DLLNode* phead, DLLDataType x)
  3. {
  4. assert(phead);
  5. DLLNode* head = phead->next;
  6. DLLNode* newnode = CreateNewNode(x);
  7. phead->next = newnode;
  8. newnode->next = head;
  9. head->prev = newnode;
  10. newnode->prev = phead;
  11. }

5.双链表的头删

头删也和尾删类似,要考虑空链表的情况:

  1. //头删
  2. void DLinkListPopFront(DLLNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead);
  6. DLLNode* head = phead->next;
  7. DLLNode* headnext = head->next;
  8. phead->next = headnext;
  9. headnext->prev = phead;
  10. free(head);
  11. head = NULL;
  12. }

6.双链表的查找

如果是查找的话,那我们还得老老实实的从头遍历:

  1. //查找
  2. DLLNode* DLinkListFind(DLLNode* phead,DLLDataType x)
  3. {
  4. assert(phead);
  5. DLLNode* cur = phead->next;
  6. while(cur)
  7. {
  8. if (cur->data == x)
  9. return cur;
  10. else
  11. cur = cur->next;
  12. }
  13. return NULL;
  14. }

还是要注意这里while循环的条件,和双链表的打印一样


7.双链表的任意插

双链表的任意位置的插入依然要和查找连用,因为只有查找才能得到pos位置的地址

但是我们这里规定一下,任意插就是pos位置前插

比如说我想在表的第四个位置插入新数据,那我就要把第四个位置空出来,让原来的第四位以及他后边的都老老实实往后退

这样一来,我们就需要找到pos节点的前一个节点,这样方便我们进行操作:

  1. //pos位置插
  2. void DLinkListInsert(DLLNode* pos, DLLDataType x)
  3. {
  4. assert(pos);
  5. DLLNode* newnode = CreateNewNode(x);
  6. DLLNode* posprev = pos->prev;
  7. posprev->next = newnode;
  8. newnode->prev = posprev;
  9. pos->prev = newnode;
  10. newnode->next = pos;
  11. }

8.双链表的任意删

任意删的形式就和任意插差不多,只是还需要另外记录pos的下一个节点

  1. //pos位置删
  2. void DLinkListEease(DLLNode* pos)
  3. {
  4. assert(pos);
  5. DLLNode* posprev = pos->prev;
  6. DLLNode* posnext = pos->next;
  7. posprev->next = posnext;
  8. posnext->prev = posprev;
  9. free(pos);
  10. pos = NULL;
  11. }

9.双链表的修改

想要修改数据,还是要用查找操作来找到要修改pos位置的地址,而后就简单了:

  1. //pos位置改
  2. void DLinkListAmend(DLLNode* pos, DLLDataType x)
  3. {
  4. assert(pos);
  5. pos->data = x;
  6. }

直接修改data即可。


10.双链表的销毁

双链表的销毁,同样是需要遍历对个个空间进行free,值得注意的是,哨兵位也需要销毁

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

四.完整代码展示

1.DLinkList.h

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <stdlib.h>
  4. typedef int DLLDataType;
  5. //定义双链表
  6. typedef struct DLinkList
  7. {
  8. DLLDataType data;
  9. struct DLinkList* prev;
  10. struct DLinkList* next;
  11. }DLLNode;
  12. //初始化双链表
  13. DLLNode* DLinkListInit();
  14. //打印双链表
  15. void DLinkListPrint(DLLNode* phead);
  16. //创造新节点
  17. DLLNode* CreateNewNode(DLLDataType x);
  18. //尾插
  19. void DLinkListPushBack(DLLNode* phead, DLLDataType x);
  20. //尾删
  21. void DLinkListPopBack(DLLNode* phead);
  22. //头插
  23. void DLinkListPushFront(DLLNode* phead, DLLDataType x);
  24. //头删
  25. void DLinkListPopFront(DLLNode* phead);
  26. //查找
  27. DLLNode* DLinkListFind(DLLNode* phead,DLLDataType x);
  28. //pos位置插
  29. void DLinkListInsert(DLLNode* pos, DLLDataType x);
  30. //pos位置删
  31. void DLinkListEease(DLLNode* pos);
  32. //pos位置改
  33. void DLinkListAmend(DLLNode* pos,DLLDataType x);
  34. //销毁
  35. void DLinkListDestroy(DLLNode* phead);

2.DLinkList.c

  1. #include "DLinkList.h"
  2. //初始化双链表
  3. DLLNode* DLinkListInit()
  4. {
  5. DLLNode* phead = (DLLNode*)malloc(sizeof(DLLNode));
  6. if (phead == NULL)
  7. {
  8. perror("DLinkListInit->malloc");
  9. }
  10. phead->next = phead;
  11. phead->prev = phead;
  12. return phead;
  13. }
  14. //打印双链表
  15. void DLinkListPrint(DLLNode* phead)
  16. {
  17. assert(phead);
  18. DLLNode* cur = phead->next;
  19. printf("phead<=>");
  20. while (cur != phead)
  21. {
  22. printf("%d<=>", cur->data);
  23. cur = cur->next;
  24. }
  25. printf("phead\n");
  26. }
  27. //创造新节点
  28. DLLNode* CreateNewNode(DLLDataType x)
  29. {
  30. DLLNode* newnode = (DLLNode*)malloc(sizeof(DLLNode));
  31. if (newnode == NULL)
  32. {
  33. perror("CreateNewNode->malloc");
  34. }
  35. newnode->data = x;
  36. newnode->next = NULL;
  37. newnode->prev = NULL;
  38. return newnode;
  39. }
  40. //尾插
  41. void DLinkListPushBack(DLLNode* phead, DLLDataType x)
  42. {
  43. assert(phead);
  44. DLLNode* newnode = CreateNewNode(x);
  45. DLLNode* tail = phead->prev;
  46. tail->next = newnode;
  47. newnode->next = phead;
  48. newnode->prev = tail;
  49. phead->prev = newnode;
  50. }
  51. //尾删
  52. void DLinkListPopBack(DLLNode* phead)
  53. {
  54. assert(phead);
  55. DLLNode* tail = phead->prev;
  56. DLLNode* tailprev = tail->prev;
  57. phead->prev = tailprev;
  58. tailprev->next = phead;
  59. free(tail);
  60. tail = NULL;
  61. }
  62. //头插
  63. void DLinkListPushFront(DLLNode* phead, DLLDataType x)
  64. {
  65. assert(phead);
  66. DLLNode* head = phead->next;
  67. DLLNode* newnode = CreateNewNode(x);
  68. phead->next = newnode;
  69. newnode->next = head;
  70. head->prev = newnode;
  71. newnode->prev = phead;
  72. }
  73. //头删
  74. void DLinkListPopFront(DLLNode* phead)
  75. {
  76. assert(phead);
  77. DLLNode* head = phead->next;
  78. DLLNode* headnext = head->next;
  79. phead->next = headnext;
  80. headnext->prev = phead;
  81. free(head);
  82. head = NULL;
  83. }
  84. //查找
  85. DLLNode* DLinkListFind(DLLNode* phead,DLLDataType x)
  86. {
  87. assert(phead);
  88. DLLNode* cur = phead->next;
  89. while(cur)
  90. {
  91. if (cur->data == x)
  92. return cur;
  93. else
  94. cur = cur->next;
  95. }
  96. return NULL;
  97. }
  98. //pos位置插
  99. void DLinkListInsert(DLLNode* pos, DLLDataType x)
  100. {
  101. assert(pos);
  102. DLLNode* newnode = CreateNewNode(x);
  103. DLLNode* posprev = pos->prev;
  104. posprev->next = newnode;
  105. newnode->prev = posprev;
  106. pos->prev = newnode;
  107. newnode->next = pos;
  108. }
  109. //pos位置删
  110. void DLinkListEease(DLLNode* pos)
  111. {
  112. assert(pos);
  113. DLLNode* posprev = pos->prev;
  114. DLLNode* posnext = pos->next;
  115. posprev->next = posnext;
  116. posnext->prev = posprev;
  117. free(pos);
  118. pos = NULL;
  119. }
  120. //pos位置改
  121. void DLinkListAmend(DLLNode* pos, DLLDataType x)
  122. {
  123. assert(pos);
  124. pos->data = x;
  125. }
  126. //销毁
  127. void DLinkListDestroy(DLLNode* phead)
  128. {
  129. assert(phead);
  130. DLLNode* cur = phead->next;
  131. while (cur != phead)
  132. {
  133. DLLNode* next = cur->next;
  134. free(cur);
  135. cur = next;
  136. }
  137. free(phead);
  138. phead = NULL;
  139. }

测试代码大家自行进行测试,这里就不在进行展示啦。


五.总结

双链表相比于单链表还是有很大优势的,建议大家在学习过单链表的基础上完全靠自己的写一写双链表,这将会让你对链表知识的掌握更上一层楼!

最后还是提醒大家不要忘记一键三连哦!!!

我们下期再见啦!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号