深圳幻海软件技术有限公司 欢迎您!

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

2023-04-13

 目录一.前言二.双向带头循环链表的结构三.接口实现A.初始化ListNodeinit和销毁Listdestroy1.ListNodeinit2.ListdestroyB.插入1.头插 ListNodepushfront2.尾插 ListNodepushback3.插入

 

目录

一.前言

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

三.接口实现

A.初始化 ListNodeinit 和销毁 Listdestroy

1.ListNodeinit

2.Listdestroy

B.插入

1.头插  ListNodepushfront

2.尾插 ListNodepushback

3.插入 ListNodeinsert

C.删除

1.头删 ListNodepopfront

2.尾删 ListNodepopback

3.删除 ListNodeerase

D.打印  ListNodeprint

四.源码

List.h

List.c

test.c


一.前言

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

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

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

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. LNode* cur = phead->next;
  5. while (cur != phead)
  6. {
  7. LNode* next = cur->next;
  8. free(cur);
  9. cur = next;
  10. }
  11. free(phead);
  12. phead = NULL;
  13. }

B.插入

1.头插  ListNodepushfront

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

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

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

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

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

2.尾插 ListNodepushback

1.尾插就需要找尾;

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

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

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

3.插入 ListNodeinsert

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

2.找到 pos 的前驱 prev ;

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

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

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

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. LNode* first = phead->next;
  11. LNode* second = first->next;
  12. phead->next = second;
  13. second->prev = phead;
  14. free(first);
  15. first = NULL;
  16. }

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. if (phead->next == phead)
  6. {
  7. perror("ListNodepopback");
  8. exit(-1);
  9. }
  10. LNode* tail = phead->prev;
  11. LNode* prev = tail->prev;
  12. prev->next = phead;
  13. phead->prev = prev;
  14. free(tail);
  15. tail = NULL;
  16. }

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. if (phead->next == phead)
  6. {
  7. perror("ListNodeerase");
  8. exit(-1);
  9. }
  10. LNode* prev = pos->prev;
  11. LNode* next = pos->next;
  12. prev->next = next;
  13. next->prev = prev;
  14. free(pos);
  15. pos = NULL;
  16. }

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

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. printf("\n");
  11. }

四.源码

List.h

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

List.c

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

test.c

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


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

😍请多多支持博主哦~🥰

🤩谢谢你的阅读~😃

 

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览43944 人正在系统学习中