小言_互联网的博客

【数据结构】带头双向循环链表的实现

342人阅读  评论(0)

目录

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

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

              1、创建一个动态头结点

              2、双向链表初始化

              3、打印双向链表

              4、双向链表尾插

              5、双向链表尾删

              6、双向链表头插

              7、双向链表头删

              8、双向链表查找

              9、双向链表在pos的前面进行插入x

              10、双向链表删除pos位置的结点

              11、销毁链表

              12、双向链表的长度

              13、判空

        三、源代码

              1、DList.h

              2、DList.c

              3、test.c

        四、顺序表和链表的优缺点


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

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了。可以说是一种完美的链表,既可以向前也可以向后。


  
  1. 链表的声明
  2. typedef struct ListNode
  3. {
  4. struct ListNode* next;
  5. struct ListNode* prev;
  6. LTDataType data;
  7. }LTNode;

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

 1、创建一个动态头结点

 首先先创建一个动态节点以便后面在插入时候的使用。


  
  1. LTNode* BuyListNode(LTDataType x)
  2. {
  3. LTNode* node = (LTNode*)malloc( sizeof(LTNode));
  4. if (node == NULL)
  5. {
  6. perror( "malloc fail");
  7. exit( -1);
  8. }
  9. node->data = x;
  10. node->next = NULL;
  11. node->prev = NULL;
  12. return node;
  13. }

 2、双向链表初始化

带头双向循环链表是带有头节点的,所以开辟一个头节点,然后让这个头节点的前后指针域都指向自己,就实现了初始化。


  
  1. LTNode* ListInit()
  2. {
  3. LTNode* phead = BuyListNode( -1);
  4. phead->next = phead;
  5. phead->prev = phead;
  6. return phead;
  7. }

 3、打印双向链表

 这里需要另外一个指针cur去遍历一遍链表,当回到头节点的时候就结束。


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

 4、双向链表尾插

逻辑如下图,图只要画出来就比较清晰。 


  
  1. void ListPushBcak(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* newnode = BuyListNode(x);
  5. LTNode* tail = phead->prev;
  6. tail->next = newnode;
  7. newnode->prev = tail;
  8. newnode->next = phead;
  9. phead->prev = newnode;
  10. }

 5、双向链表尾删

逻辑如下,根据下图就可以表示出尾删。


  
  1. void ListPopBcak(LTNode* phead)
  2. {
  3. assert(phead);
  4. LTNode* tail = phead->prev;
  5. LTNode* tailPrev = tail->prev;
  6. tailPrev->next = phead;
  7. phead->prev = tailPrev;
  8. free(tail);
  9. }

 6、双向链表头插

 逻辑如下,根据下图就可以表示出头插。


  
  1. void ListPushFront(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* newnode = BuyListNode(x);
  5. LTNode* first = phead->next;
  6. //phead newnode first
  7. //顺序无关
  8. phead->next = newnode;
  9. newnode->prev = phead;
  10. newnode->next = first;
  11. first->prev = newnode;
  12. }

 7、双向链表头删

逻辑如下: 


  
  1. void ListPopFront(LTNode* phead)
  2. {
  3. assert(phead);
  4. assert(phead->next != phead); //是否为空
  5. LTNode* first = phead->next;
  6. LTNode* second = first->next;
  7. free(first);
  8. phead->next = second;
  9. second->prev = phead;
  10. }

 8、双向链表查找

从链表的头结点的后面结点开始逐一匹配,直到找到值相同的结点进行返回,若当查找结点一直后到头结点时意味着没有该结点,就返回NULL。


  
  1. LTNode* ListFind(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* 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. }

 9、双向链表在pos的前面进行插入x

逻辑如下图: 


  
  1. void ListInsert(LTNode* pos, LTDataType x)
  2. {
  3. assert(pos);
  4. LTNode* prev = pos->prev;
  5. LTNode* newnode = BuyListNode(x);
  6. //prev newnode pos
  7. prev->next = newnode;
  8. newnode->prev = prev;
  9. newnode->next = pos;
  10. pos->prev = newnode;
  11. }

 10、双向链表删除pos位置的结点

逻辑如下图: 


  
  1. void ListErase(LTNode* pos)
  2. {
  3. assert(pos);
  4. LTNode* prev = pos->prev;
  5. LTNode* next = pos->next;
  6. free(pos);
  7. prev->next = next;
  8. next->prev = prev;
  9. }

 11、销毁链表

 双向链表和单链表一样,逐个遍历,逐个销毁。


  
  1. void ListDestory(LTNode* phead)
  2. {
  3. assert(phead);
  4. LTNode* cur = phead->next;
  5. while (cur != phead)
  6. {
  7. LTNode* next = cur->next;
  8. free(cur);
  9. cur = next;
  10. }
  11. free(phead);
  12. }

 12、双向链表的长度


  
  1. size_t ListSize(LTNode* phead)
  2. {
  3. assert(phead);
  4. size_t size = 0;
  5. LTNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. ++size;
  9. cur = cur->next;
  10. }
  11. return size;
  12. }

 13、判空


  
  1. bool ListEmpty(LTNode* phead)
  2. {
  3. assert(phead);
  4. return phead->next == phead;
  5. }

三、源代码

 1、DList.h


  
  1. #pragma once
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include <stdlib.h>
  5. #include <stdbool.h>
  6. typedef int LTDataType;
  7. typedef struct ListNode
  8. {
  9. struct ListNode* next;
  10. struct ListNode* prev;
  11. LTDataType data;
  12. }LTNode;
  13. //创建一个动态头结点
  14. LTNode* BuyListNode(LTDataType x);
  15. //初始化
  16. LTNode* ListInit();
  17. //打印链表
  18. void ListPrint(LTNode* phead);
  19. //双向链表尾插
  20. void ListPushBcak(LTNode* phead, LTDataType x);
  21. //双向链表尾删
  22. void ListPopBcak(LTNode* phead);
  23. //双向链表头插
  24. void ListPushFront(LTNode* phead, LTDataType x);
  25. //双向链表头删
  26. void ListPopFront(LTNode* phead);
  27. //双向链表查找
  28. LTNode* ListFind(LTNode* phead, LTDataType x);
  29. //双向链表在pos的前面进行插入
  30. void ListInsert(LTNode* pos, LTDataType x);
  31. //双向链表删除pos位置的结点
  32. void ListErase(LTNode* pos);
  33. //判空
  34. bool ListEmpty(LTNode* phead);
  35. size_t ListSize(LTNode* phead);
  36. //销毁链表
  37. void ListDestory(LTNode* phead);

 2、DList.c


  
  1. #include "DList.h"
  2. //创建一个动态头结点
  3. LTNode* BuyListNode(LTDataType x)
  4. {
  5. LTNode* node = (LTNode*)malloc( sizeof(LTNode));
  6. if (node == NULL)
  7. {
  8. perror( "malloc fail");
  9. exit( -1);
  10. }
  11. node->data = x;
  12. node->next = NULL;
  13. node->prev = NULL;
  14. return node;
  15. }
  16. //初始化
  17. LTNode* ListInit()
  18. {
  19. LTNode* phead = BuyListNode( -1);
  20. phead->next = phead;
  21. phead->prev = phead;
  22. return phead;
  23. }
  24. //打印链表
  25. void ListPrint(LTNode* phead)
  26. {
  27. assert(phead);
  28. LTNode* cur = phead->next;
  29. while (cur != phead)
  30. {
  31. printf( "%d ", cur->data);
  32. cur = cur->next;
  33. }
  34. printf( "\n");
  35. }
  36. //双向链表尾插
  37. void ListPushBcak(LTNode* phead, LTDataType x)
  38. {
  39. assert(phead);
  40. LTNode* newnode = BuyListNode(x);
  41. LTNode* tail = phead->prev;
  42. tail->next = newnode;
  43. newnode->prev = tail;
  44. newnode->next = phead;
  45. phead->prev = newnode;
  46. }
  47. //双向链表尾删
  48. void ListPopBcak(LTNode* phead)
  49. {
  50. assert(phead);
  51. LTNode* tail = phead->prev;
  52. LTNode* tailPrev = tail->prev;
  53. tailPrev->next = phead;
  54. phead->prev = tailPrev;
  55. free(tail);
  56. }
  57. //双向链表头插
  58. void ListPushFront(LTNode* phead, LTDataType x)
  59. {
  60. assert(phead);
  61. LTNode* newnode = BuyListNode(x);
  62. LTNode* first = phead->next;
  63. //phead newnode first
  64. //顺序无关
  65. phead->next = newnode;
  66. newnode->prev = phead;
  67. newnode->next = first;
  68. first->prev = newnode;
  69. }
  70. //双向链表头删
  71. void ListPopFront(LTNode* phead)
  72. {
  73. assert(phead);
  74. assert(phead->next != phead); //是否为空
  75. LTNode* first = phead->next;
  76. LTNode* second = first->next;
  77. free(first);
  78. phead->next = second;
  79. second->prev = phead;
  80. }
  81. //双向链表查找
  82. LTNode* ListFind(LTNode* phead, LTDataType x)
  83. {
  84. assert(phead);
  85. LTNode* cur = phead->next;
  86. while (cur != phead)
  87. {
  88. if (cur->data == x)
  89. {
  90. return cur;
  91. }
  92. cur = cur->next;
  93. }
  94. return NULL;
  95. }
  96. //双向链表在pos的前面进行插入x
  97. void ListInsert(LTNode* pos, LTDataType x)
  98. {
  99. assert(pos);
  100. LTNode* prev = pos->prev;
  101. LTNode* newnode = BuyListNode(x);
  102. //prev newnode pos
  103. prev->next = newnode;
  104. newnode->prev = prev;
  105. newnode->next = pos;
  106. pos->prev = newnode;
  107. }
  108. //双向链表删除pos位置的结点
  109. void ListErase(LTNode* pos)
  110. {
  111. assert(pos);
  112. LTNode* prev = pos->prev;
  113. LTNode* next = pos->next;
  114. free(pos);
  115. prev->next = next;
  116. next->prev = prev;
  117. }
  118. //判空
  119. bool ListEmpty(LTNode* phead)
  120. {
  121. assert(phead);
  122. return phead->next == phead;
  123. }
  124. size_t ListSize(LTNode* phead)
  125. {
  126. assert(phead);
  127. size_t size = 0;
  128. LTNode* cur = phead->next;
  129. while (cur != phead)
  130. {
  131. ++size;
  132. cur = cur->next;
  133. }
  134. return size;
  135. }
  136. //销毁链表
  137. void ListDestory(LTNode* phead)
  138. {
  139. assert(phead);
  140. LTNode* cur = phead->next;
  141. while (cur != phead)
  142. {
  143. LTNode* next = cur->next;
  144. free(cur);
  145. cur = next;
  146. }
  147. free(phead);
  148. }

 3、test.c


  
  1. void TestList1()
  2. {
  3. LTNode* phead = ListInit();
  4. ListPushBcak(phead, 1);
  5. ListPushBcak(phead, 2);
  6. ListPushBcak(phead, 3);
  7. ListPushBcak(phead, 4);
  8. ListPushBcak(phead, 5);
  9. ListPrint(phead);
  10. ListPopBcak(phead);
  11. ListPrint(phead);
  12. ListPopBcak(phead);
  13. ListPrint(phead);
  14. ListPushFront(phead, 10);
  15. ListPushFront(phead, 20);
  16. ListPushFront(phead, 30);
  17. ListPrint(phead);
  18. ListPopFront(phead);
  19. ListPrint(phead);
  20. LTNode* pos = ListFind(phead, 2);
  21. if (pos)
  22. {
  23. pos->data *= 100;
  24. }
  25. ListPrint(phead);
  26. ListDestory(phead);
  27. phead = NULL;
  28. }
  29. int main()
  30. {
  31. TestList1();
  32. return 0;
  33. }

四、顺序表和链表的优缺点

1、顺序表优点:尾插,尾删方便,下标的随机访问快。缓存利用率高。

2、顺序表缺点:空间不足时需要扩容(扩容要付出相应代价),插入删除数据需要挪动数据。

3、链表优点:插入删除方便,按需申请释放小块结点内存。
4、链表缺点:不能随机访问下标。缓存利用率低。

不同点 顺序表 链表
存储空间上 物理上一定连续 逻辑上连续,但物理上不一定连续
随机访问 支持O(1) 不支持:O(N)
任意位置插入或者删除 元素 可能需要搬移元素,效率低 O(N) 只需修改指针指向
插入 动态顺序表,空间不够时需要扩容 没有容量的概念
应用场景 元素高效存储+频繁访问 任意位置插入和删除频繁
缓存利用率

 本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

  老铁们,记着点赞加关注哦!!!


转载:https://blog.csdn.net/m0_63198468/article/details/128526548
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场