当前位置:   article > 正文

【数据结构】带头+双向+循环链表(DList)(增、删、查、改)详解

dlist

一、带头双向循环链表的定义和结构

1、定义

带头双向循环链表,有一个数据域两个指针域。一个是前驱指针,指向其前一个节点;一个是后继指针,指向其后一个节点。

  1. // 定义双向链表的节点
  2. typedef struct ListNode
  3. {
  4. LTDataType data; // 数据域
  5. struct ListNode* prev; // 前驱指针
  6. struct ListNode* next; // 后继指针
  7. }ListNode;

2、结构

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

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

1、创建文件

  • test.c(主函数、测试顺序表各个接口功能)
  • List.c(带头双向循环链表接口函数的实现)
  • List.h(带头双向循环链表的类型定义、接口函数声明、引用的头文件)


2、List.h 头文件代码 

  1. // List.h
  2. // 带头+双向+循环链表增删查改实现
  3. #pragma once
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <assert.h>
  7. typedef int LTDataType;
  8. // 定义双向链表的节点
  9. typedef struct ListNode
  10. {
  11. LTDataType data; // 数据域
  12. struct ListNode* prev; // 前驱指针
  13. struct ListNode* next; // 后继指针
  14. }ListNode;
  15. // 动态申请一个新节点
  16. ListNode* BuyListNode(LTDataType x);
  17. // 创建返回链表的头结点
  18. ListNode* ListCreate();
  19. // 双向链表销毁
  20. void ListDestory(ListNode* plist);
  21. // 双向链表打印
  22. void ListPrint(ListNode* plist);
  23. // 双向链表尾插
  24. void ListPushBack(ListNode* plist, LTDataType x);
  25. // 双向链表尾删
  26. void ListPopBack(ListNode* plist);
  27. // 双向链表头插
  28. void ListPushFront(ListNode* plist, LTDataType x);
  29. // 双向链表头删
  30. void ListPopFront(ListNode* plist);
  31. // 双向链表查找
  32. ListNode* ListFind(ListNode* plist, LTDataType x);
  33. // 双向链表在pos的前面进行插入
  34. void ListInsert(ListNode* pos, LTDataType x);
  35. // 双向链表删除pos位置的节点
  36. void ListErase(ListNode* pos);
  37. // 双向链表的判空
  38. bool ListEmpty(ListNode* phead);
  39. // 获取双向链表的元素个数
  40. size_t ListSize(ListNode* phead);

三、在 List.c 上是实现各个接口函数

1、动态申请一个新结点

  1. // 动态申请一个新节点
  2. ListNode* BuyListNode(LTDataType x)
  3. {
  4. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  5. newnode->data = x;
  6. newnode->prev = NULL;
  7. newnode->next = NULL;
  8. return newnode;
  9. }

2、创建返回链表的头结点(初始化头结点)

  1. // 创建返回链表的头结点
  2. ListNode* ListCreate()
  3. {
  4. ListNode* phead = (ListNode*)malloc(sizeof(ListNode)); // 哨兵位头结点
  5. phead->next = phead;
  6. phead->prev = phead;
  7. return phead;
  8. }

也可以用下面这个函数(道理一样):

  1. // 初始化链表
  2. void ListInit(ListNode** pphead)
  3. {
  4. *pphead = BuyListNode(-1); // 动态申请一个头节点
  5. (*pphead)->prev = *pphead; // 前驱指针指向自己
  6. (*pphead)->next = *pphead; // 后继指针指向自己
  7. }

头指针初始指向 NULL,初始化链表时,需要改变头指针的指向,使其指向头节点,所以这里需要传二级指针。 


初始化带头双向循环链表,首先动态申请一个头结点头结点的前驱和后继指针都指向自己,形成一个循环

image-20210903220045099


3、双向链表的销毁

  1. // 双向链表的销毁
  2. void ListDestroy(ListNode** pphead)
  3. {
  4. assert(pphead);
  5. assert(*pphead);
  6. ListNode* cur = (*pphead)->next;
  7. while (cur != *pphead)
  8. {
  9. ListNode* next = cur->next; // 记录cur的直接后继节点
  10. free(cur);
  11. cur = next;
  12. }
  13. free(*pphead); // 释放头节点
  14. *pphead = NULL; // 置空头指针
  15. }

销毁链表,最后要将头指针 plist 置空,所以用了二级指针来接收。这里也可以用一级指针,但要在函数外面置空 plist 。

一级指针写法:

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

4、双向链表的打印

  1. // 打印双向链表
  2. void ListPrint(ListNode* phead)
  3. {
  4. assert(phead);
  5. ListNode* cur = phead->next; // 记录第一个节点
  6. printf("head <-> ");
  7. while (cur != phead)
  8. {
  9. printf("%d <-> ", cur->data);
  10. cur = cur->next;
  11. }
  12. printf("head\n");
  13. }

5、双向链表的尾插

  1. // 双向链表尾插
  2. void ListPushBack(ListNode* phead, LTDataType x)
  3. {
  4. assert(phead); // 头指针不能为空
  5. /* ListNode* newnode = BuyListNode(x); // 动态申请一个节点
  6. ListNode* tail = phead->prev; // 记录尾节点
  7. tail->next = newnode; // 尾节点的后继指针指向新节点
  8. newnode->prev = tail; //2、新节点的前驱指针指向尾节点
  9. newnode->next = phead; // 新节点的后继指针指向头节点
  10. phead->prev = newnode; // 头节点的前驱指针指向新节点 */
  11. ListInsert(phead, x);
  12. }


6、双向链表的尾删

  1. // 双向链表的尾删
  2. void ListPopBack(ListNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除
  6. /* ListNode* tail = phead->prev; // 记录尾节点
  7. ListNode* tailPrev = tail->prev; // 记录尾节点的直接前驱
  8. tailPrev->next = phead; // 尾节点的前驱节点的next指针指向头节点
  9. phead->prev = tailPrev; // 头节点的prev指针指向尾节点的前驱节点
  10. free(tail); // 释放尾节点 */
  11. ListErase(pHead->prev);
  12. }


7、双向链表的头插

  1. // 双向链表的头插
  2. void ListPushFront(ListNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. /* ListNode* newnode = BuyListNode(x); // 申请新节点
  6. ListNode* pheadNext = phead->next; // 记录第一个节点
  7. // 头节点和新节点建立链接
  8. phead->next = newnode;
  9. newnode->prev = phead;
  10. // 新节点和第一个节点建立链接
  11. newnode->next = pheadNext;
  12. pheadNext->prev = newnode; */
  13. ListInsert(phead->next, x);
  14. }


8、双向链表的头删

  1. // 双向链表的头删
  2. void ListPopFront(ListNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除
  6. /* ListNode* pheadNext = phead->next; // 记录第一个节点
  7. // 头节点和第一个节点的后继节点建立链接
  8. phead->next = pheadNext->next;
  9. pheadNext->next->prev = phead;
  10. free(pheadNext); // 头删 */
  11. ListErase(phead->next);
  12. }


9、查找双向链表中的元素

  1. // 在双向链表中查找元素,并返回该元素的地址
  2. ListNode* ListFind(ListNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. ListNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. if (cur->data == x)
  9. {
  10. return cur; //找到了 返回该元素的地址
  11. }
  12. cur = cur->next;
  13. }
  14. return NULL; //没找到 返回NULL
  15. }

10、在指定pos位置之前插入元素

  1. // 在指定pos位置之前插入元素
  2. void ListInsert(ListNode* pos, LTDataType x)
  3. {
  4. assert(pos);
  5. ListNode* newnode = BuyListNode(x); // 申请一个节点
  6. ListNode* posPrev = pos->prev; // 记录pos的直接前驱
  7. // pos的直接前驱和新节点建立链接
  8. posPrev->next = newnode;
  9. newnode->prev = posPrev;
  10. // 新节点和pos建立链接
  11. newnode->next = pos;
  12. pos->prev = newnode;
  13. }

实现了该函数后,可以尝试改进头插函数(pos相当于链表的第一个节点)和尾插函数(pos相当于链表的头节点),这样写起来更简便


11、删除指定pos位置的元素

  1. // 删除指定pos位置的元素
  2. void ListErase(ListNode* pos)
  3. {
  4. assert(pos);
  5. ListNode* posPrev = pos->prev; // 记录pos的直接前驱
  6. ListNode* posNext = pos->next; // 记录pos的直接后继
  7. // pos的直接前驱和直接后继建立链接
  8. posPrev->next = posNext;
  9. posNext->prev = posPrev;
  10. free(pos); // 释放pos位置的元素
  11. //pos = NULL;
  12. }

实现了该函数后,可以尝试改进函数(pos相当于链表的第一个节点)和尾删函数(pos相当于链表的最后一个节点),这样写起来更简便


12、双向链表的判空

  1. // 双向链表的判空
  2. bool ListEmpty(ListNode* phead)
  3. {
  4. assert(phead);
  5. return phead->next == phead; //为空 返回ture 否则返回false
  6. }

13、获取双向链表的元素个数

  1. // 获取双向链表的元素个数
  2. size_t ListSize(ListNode* phead)
  3. {
  4. assert(phead);
  5. size_t size = 0;
  6. ListNode* cur = phead->next; // 记录第一个节点
  7. while (cur != phead)
  8. {
  9. size++;
  10. cur = cur->next;
  11. }
  12. return size;
  13. }

四、代码整合

  1. // List.c
  2. #include "List.h"
  3. // 动态申请一个新节点
  4. ListNode* BuyListNode(LTDataType x)
  5. {
  6. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  7. newnode->data = x;
  8. newnode->prev = NULL;
  9. newnode->next = NULL;
  10. return newnode;
  11. }
  12. // 创建返回链表的头结点
  13. ListNode* ListCreate()
  14. {
  15. ListNode* phead = (ListNode*)malloc(sizeof(ListNode)); // 哨兵位头结点
  16. phead->next = phead;
  17. phead->prev = phead;
  18. return phead;
  19. }
  20. // 双向链表的销毁
  21. void ListDestroy(ListNode** pphead)
  22. {
  23. assert(pphead);
  24. assert(*pphead);
  25. ListNode* cur = (*pphead)->next;
  26. while (cur != *pphead)
  27. {
  28. ListNode* next = cur->next; // 记录cur的直接后继节点
  29. free(cur);
  30. cur = next;
  31. }
  32. free(*pphead); // 释放头节点
  33. *pphead = NULL; // 置空头指针
  34. }
  35. // 打印双向链表
  36. void ListPrint(ListNode* phead)
  37. {
  38. assert(phead);
  39. ListNode* cur = phead->next; // 记录第一个节点
  40. printf("head <-> ");
  41. while (cur != phead)
  42. {
  43. printf("%d <-> ", cur->data);
  44. cur = cur->next;
  45. }
  46. printf("head\n");
  47. }
  48. // 双向链表尾插
  49. void ListPushBack(ListNode* phead, LTDataType x)
  50. {
  51. assert(phead); // 头指针不能为空
  52. ListInsert(phead, x);
  53. }
  54. // 双向链表的尾删
  55. void ListPopBack(ListNode* phead)
  56. {
  57. assert(phead);
  58. assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除
  59. ListErase(pHead->prev);
  60. }
  61. // 双向链表的头插
  62. void ListPushFront(ListNode* phead, LTDataType x)
  63. {
  64. assert(phead);
  65. ListInsert(phead->next, x);
  66. }
  67. // 双向链表的头删
  68. void ListPopFront(ListNode* phead)
  69. {
  70. assert(phead);
  71. assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除
  72. ListErase(phead->next);
  73. }
  74. // 在双向链表中查找元素,并返回该元素的地址
  75. ListNode* ListFind(ListNode* phead, LTDataType x)
  76. {
  77. assert(phead);
  78. ListNode* cur = phead->next;
  79. while (cur != phead)
  80. {
  81. if (cur->data == x)
  82. {
  83. return cur; //找到了 返回该元素的地址
  84. }
  85. cur = cur->next;
  86. }
  87. return NULL; //没找到 返回NULL
  88. }
  89. // 在指定pos位置之前插入元素
  90. void ListInsert(ListNode* pos, LTDataType x)
  91. {
  92. assert(pos);
  93. ListNode* newnode = BuyListNode(x); // 申请一个节点
  94. ListNode* posPrev = pos->prev; // 记录pos的直接前驱
  95. // pos的直接前驱和新节点建立链接
  96. posPrev->next = newnode;
  97. newnode->prev = posPrev;
  98. // 新节点和pos建立链接
  99. newnode->next = pos;
  100. pos->prev = newnode;
  101. }
  102. // 删除指定pos位置的元素
  103. void ListErase(ListNode* pos)
  104. {
  105. assert(pos);
  106. ListNode* posPrev = pos->prev; // 记录pos的直接前驱
  107. ListNode* posNext = pos->next; // 记录pos的直接后继
  108. // pos的直接前驱和直接后继建立链接
  109. posPrev->next = posNext;
  110. posNext->prev = posPrev;
  111. free(pos); // 释放pos位置的元素
  112. //pos = NULL;
  113. }
  114. // 双向链表的判空
  115. bool ListEmpty(ListNode* phead)
  116. {
  117. assert(phead);
  118. return phead->next == phead; //为空 返回ture 否则返回false
  119. }
  120. // 获取双向链表的元素个数
  121. size_t ListSize(ListNode* phead)
  122. {
  123. assert(phead);
  124. size_t size = 0;
  125. ListNode* cur = phead->next; // 记录第一个节点
  126. while (cur != phead)
  127. {
  128. size++;
  129. cur = cur->next;
  130. }
  131. return size;
  132. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/655476
推荐阅读
相关标签
  

闽ICP备14008679号