当前位置:   article > 正文

数据结构——单向循环链表

数据结构——单向循环链表

文章目录

1. 概念

2. 区别

2.1 结构区别

2.2 访问方式区别

2.3 优缺点对比

3. 流程

4. 基本操作

5. 代码示例


1. 概念

单向循环链表是一种特殊的单链表,其中最后一个节点的后继指针指向头节点,形成一个环。单向循环链表适合用于需要循环访问数据的场景,如约瑟夫环问题。

节点定义

每个节点包含两个部分:数据域和指针域。

  1. typedef struct Node {
  2. int data; // 数据域,存储节点的值
  3. struct Node *next; // 指针域,指向下一个节点
  4. } Node;

结构定义

单向循环链表包含一个头指针和链表中的节点个数。

  1. typedef struct {
  2. Node *head; // 指向单向循环链表的头节点
  3. size_t size; // 单向循环链表中的节点个数
  4. } CircularLinkedList;

2. 区别

单向链表和单向循环链表的区别

2.1 结构区别

单向链表(Singly Linked List)

  • 每个节点包含一个数据域和一个指向下一个节点的指针。
  • 最后一个节点的指针指向 NULL,表示链表的结束。
  • 只能单向遍历,从头节点开始到尾节点结束。

单向循环链表(Singly Circular Linked List)

  • 每个节点也包含一个数据域和一个指向下一个节点的指针。
  • 最后一个节点的指针指向头节点,形成一个循环。
  • 可以从链表中的任何一个节点开始遍历,最终会回到该节点。

2.2 访问方式区别

单向链表

  • 从头节点开始遍历,直到找到目标节点或者到达链表末尾。

单向循环链表

  • 从任意节点开始遍历,最终会回到该节点,因此在循环中进行操作时更加方便。

2.3 优缺点对比

单向链表的优缺点

优点

  1. 实现简单:实现和维护相对简单,每个节点只包含一个指针。
  2. 内存开销小:每个节点只包含一个指针,内存占用较少。
  3. 插入和删除操作效率较高:在已知前驱节点的情况下,插入和删除节点的时间复杂度为 O(1)。

缺点

  1. 只能单向遍历:不能从尾节点向前遍历到头节点,灵活性差。
  2. 查找效率较低:从头节点遍历到目标节点的时间复杂度为 O(n)。

单向循环链表的优缺点

优点

  1. 循环访问方便:适合需要循环访问数据的应用场景,如约瑟夫环问题。
  2. 无需处理链表末尾:由于最后一个节点指向头节点,不需要特殊处理链表末尾的情况。

缺点

  1. 实现和维护复杂:插入和删除操作需要特别注意尾节点和头节点之间的关系。
  2. 查找效率较低:和单向链表一样,需要遍历链表才能找到特定节点,时间复杂度为 O(n)。

3. 流程

初始化单向循环链表

  • 设置头指针为NULL。
  • 设置节点个数为0。

插入新节点

  • 判断插入位置是否是0。
  • 如果是0,进一步判断链表是否为空:
    • 如果链表为空,新节点指向自己,头指针指向新节点。
    • 如果链表不为空,找到尾节点,新节点指向头节点,头指针指向新节点,尾节点指向新节点。
  • 如果插入位置不是0,找到插入位置的前一个节点,新节点指向前一个节点的后继节点,前一个节点指向新节点。
  • 节点个数加1。

删除节点

  • 判断删除位置是否是0。
  • 如果是0,保存头节点,进一步判断链表是否只有一个节点:
    • 如果链表只有一个节点,头指针置为NULL。
    • 如果链表有多个节点,找到尾节点,头指针指向头节点的后继节点,尾节点指向新头节点。
  • 如果删除位置不是0,找到删除位置前一个节点,保存要删除的节点,前一个节点指向要删除节点的后继节点。
  • 释放被删除的节点,节点个数减1。

获取指定位置的元素

  • 判断索引是否合法,如果不合法返回无效索引。
  • 从头节点开始遍历,遍历到目标位置节点,返回节点数据。

修改指定位置的元素

  • 判断索引是否合法,如果不合法忽略位置。
  • 从头节点开始遍历,遍历到目标位置节点,修改节点数据。

释放单向循环链表内存

  • 判断链表是否为空,如果为空直接返回。
  • 从头节点开始遍历,保存下一个节点,释放当前节点,继续遍历,直到回到头节点。
  • 头指针置为NULL,节点个数置为0。

 

4. 基本操作

初始化单向循环链表

  1. void initCircularLinkedList(CircularLinkedList *list) {
  2. list->head = NULL; // 初始化头节点为空
  3. list->size = 0; // 初始化节点个数为0
  4. }
  • 功能:初始化一个空的单向循环链表。
  • 参数CircularLinkedList *list,指向需要初始化的链表结构。
  • 操作
    • 将链表的头节点指针 head 设置为 NULL
    • 将链表的节点个数 size 设置为 0

 

插入节点

在单向循环链表的指定位置插入新节点。

  1. void insertAt(CircularLinkedList *list, size_t index, int element) {
  2. if (index > list->size) {
  3. return; // 忽略无效的插入位置
  4. }
  5. Node *newNode = (Node *)malloc(sizeof(Node)); // 创建新节点
  6. newNode->data = element;
  7. if (index == 0) { // 插入位置是头节点
  8. if (list->head == NULL) { // 如果链表为空
  9. newNode->next = newNode;
  10. list->head = newNode;
  11. } else {
  12. Node *tail = list->head;
  13. while (tail->next != list->head) {
  14. tail = tail->next;
  15. }
  16. newNode->next = list->head;
  17. list->head = newNode;
  18. tail->next = newNode;
  19. }
  20. } else {
  21. Node *prevNode = list->head;
  22. for (size_t i = 0; i < index - 1; i++) {
  23. prevNode = prevNode->next;
  24. }
  25. newNode->next = prevNode->next;
  26. prevNode->next = newNode;
  27. }
  28. list->size++; // 更新节点个数
  29. }
  • 功能:在指定位置插入新节点。
  • 参数
    • CircularLinkedList *list,指向链表结构。
    • size_t index,插入位置的索引。
    • int element,插入节点的值。
  • 操作
    • 检查插入位置是否合法,如果不合法则直接返回。
    • 创建一个新节点并赋值。
    • 如果插入位置是头节点:
      • 如果链表为空,直接将新节点的 next 指针指向自己,并将 head 指针指向新节点。
      • 如果链表不为空,找到尾节点,更新尾节点和新节点的指针,使新节点成为头节点。
    • 如果插入位置不是头节点,找到插入位置的前一个节点,更新前一个节点和新节点的指针,使新节点插入到指定位置。
    • 更新链表的节点个数。

 

删除节点

删除指定位置的节点并返回被删除的元素。

  1. int deleteAt(CircularLinkedList *list, size_t index) {
  2. if (index >= list->size) {
  3. return -1; // 忽略无效的删除位置
  4. }
  5. int deletedElement;
  6. if (index == 0) { // 删除位置是头节点
  7. Node *temp = list->head;
  8. if (list->head->next == list->head) { // 如果链表只有一个节点
  9. list->head = NULL;
  10. } else {
  11. Node *tail = list->head;
  12. while (tail->next != list->head) {
  13. tail = tail->next;
  14. }
  15. list->head = temp->next;
  16. tail->next = list->head;
  17. }
  18. deletedElement = temp->data;
  19. free(temp); // 释放被删除节点的内存
  20. } else {
  21. Node *prevNode = list->head;
  22. for (size_t i = 0; i < index - 1; i++) {
  23. prevNode = prevNode->next;
  24. }
  25. Node *temp = prevNode->next;
  26. prevNode->next = temp->next;
  27. deletedElement = temp->data;
  28. free(temp); // 释放被删除节点的内存
  29. }
  30. list->size--; // 更新节点个数
  31. return deletedElement;
  32. }
  • 功能:删除指定位置的节点并返回被删除的节点的值。
  • 参数
    • CircularLinkedList *list,指向链表结构。
    • size_t index,删除位置的索引。
  • 操作
    • 检查删除位置是否合法,如果不合法则返回 -1
    • 如果删除位置是头节点:
      • 如果链表只有一个节点,将 head 指针置为 NULL
      • 如果链表有多个节点,找到尾节点,更新尾节点和头节点的指针,使头节点指向下一个节点。
      • 释放被删除节点的内存,返回被删除节点的值。
    • 如果删除位置不是头节点,找到删除位置的前一个节点,更新前一个节点和下一个节点的指针,删除指定位置的节点。
    • 更新链表的节点个数,返回被删除节点的值。

获取节点

  1. // 获取指定位置的元素
  2. int getElementAt(const CircularLinkedList *list, size_t index) {
  3. if (index >= list->size) {
  4. return -1; // 返回无效的索引
  5. }
  6. Node *currentNode = list->head;
  7. for (size_t i = 0; i < index; i++) {
  8. currentNode = currentNode->next;
  9. }
  10. return currentNode->data; // 返回指定位置的元素
  11. }
  • 功能:获取指定位置的节点值。
  • 参数
    • const CircularLinkedList *list,指向链表结构。
    • size_t index,目标位置的索引。
  • 操作
    • 检查索引是否合法,如果不合法则返回 -1
    • 从头节点开始遍历,直到找到目标位置的节点。
    • 返回目标位置节点的值。

修改节点

  1. // 修改指定位置的元素
  2. void modifyAt(CircularLinkedList *list, size_t index, int newValue) {
  3. if (index >= list->size) {
  4. return; // 忽略无效的修改位置
  5. }
  6. Node *currentNode = list->head;
  7. for (size_t i = 0; i < index; i++) {
  8. currentNode = currentNode->next;
  9. }
  10. currentNode->data = newValue; // 修改节点的值
  11. }
  • 功能:修改指定位置的节点值。
  • 参数
    • CircularLinkedList *list,指向链表结构。
    • size_t index,目标位置的索引。
    • int newValue,新值。
  • 操作
    • 检查索引是否合法,如果不合法则直接返回。
    • 从头节点开始遍历,直到找到目标位置的节点。
    • 修改目标位置节点的值。

打印所有元素

  1. void printCircularLinkedList(const CircularLinkedList *list) {
  2. if (list->head == NULL) {
  3. printf("链表为空\n");
  4. return;
  5. }
  6. Node *currentNode = list->head;
  7. do {
  8. printf("%d ", currentNode->data);
  9. currentNode = currentNode->next;
  10. } while (currentNode != list->head);
  11. printf("\n");
  12. }
  • 功能:打印单向循环链表中的所有节点值。
  • 参数const CircularLinkedList *list,指向需要打印的链表结构。
  • 操作
    • 如果链表为空,打印“链表为空”并返回。
    • 从头节点开始遍历,每次打印当前节点的值。
    • 继续遍历,直到回到头节点。

 

释放单向循环链表的内存

  1. void destroyCircularLinkedList(CircularLinkedList *list) {
  2. if (list->head == NULL) {
  3. return;
  4. }
  5. Node *currentNode = list->head;
  6. Node *nextNode;
  7. do {
  8. nextNode = currentNode->next;
  9. free(currentNode);
  10. currentNode = nextNode;
  11. } while (currentNode != list->head);
  12. list->head = NULL;
  13. list->size = 0;
  14. }
  • 功能:释放单向循环链表中所有节点的内存。
  • 参数CircularLinkedList *list,指向需要释放的链表结构。
  • 操作
    • 如果链表为空,直接返回。
    • 从头节点开始遍历,每次保存当前节点的下一个节点的指针,释放当前节点的内存。
    • 继续遍历,直到回到头节点。
    • 将链表的头节点指针 head 置为 NULL,将链表的节点个数 size 置为 0

5. 代码示例

以下是一个完整的单向循环链表实现代码,包括初始化、插入、删除、获取和修改元素,以及释放链表的内存的所有基本操作:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. // 单向循环链表节点结构定义
  4. typedef struct Node {
  5. int data; // 节点存储的数据
  6. struct Node *next; // 指向下一个节点的指针
  7. } Node;
  8. // 单向循环链表结构定义
  9. typedef struct {
  10. Node *head; // 单向循环链表头节点指针
  11. size_t size; // 单向循环链表中的节点个数
  12. } CircularLinkedList;
  13. // 初始化单向循环链表
  14. void initCircularLinkedList(CircularLinkedList *list) {
  15. list->head = NULL; // 初始化头节点为空
  16. list->size = 0; // 初始化节点个数为0
  17. }
  18. // 在指定位置插入元素
  19. void insertAt(CircularLinkedList *list, size_t index, int element) {
  20. if (index > list->size) {
  21. return; // 忽略无效的插入位置
  22. }
  23. Node *newNode = (Node *)malloc(sizeof(Node)); // 创建新节点
  24. newNode->data = element;
  25. if (index == 0) { // 插入位置是头节点
  26. if (list->head == NULL) { // 如果链表为空
  27. newNode->next = newNode;
  28. list->head = newNode;
  29. } else {
  30. Node *tail = list->head;
  31. while (tail->next != list->head) {
  32. tail = tail->next;
  33. }
  34. newNode->next = list->head;
  35. list->head = newNode;
  36. tail->next = newNode;
  37. }
  38. } else {
  39. Node *prevNode = list->head;
  40. for (size_t i = 0; i < index - 1; i++) {
  41. prevNode = prevNode->next;
  42. }
  43. newNode->next = prevNode->next;
  44. prevNode->next = newNode;
  45. }
  46. list->size++; // 更新节点个数
  47. }
  48. // 删除指定位置的元素并返回被删除的元素
  49. int deleteAt(CircularLinkedList *list, size_t index) {
  50. if (index >= list->size) {
  51. return -1; // 忽略无效的删除位置
  52. }
  53. int deletedElement;
  54. if (index == 0) { // 删除位置是头节点
  55. Node *temp = list->head;
  56. if (list->head->next == list->head) { // 如果链表只有一个节点
  57. list->head = NULL;
  58. } else {
  59. Node *tail = list->head;
  60. while (tail->next != list->head) {
  61. tail = tail->next;
  62. }
  63. list->head = temp->next;
  64. tail->next = list->head;
  65. }
  66. deletedElement = temp->data;
  67. free(temp); // 释放被删除节点的内存
  68. } else {
  69. Node *prevNode = list->head;
  70. for (size_t i = 0; i < index - 1; i++) {
  71. prevNode = prevNode->next;
  72. }
  73. Node *temp = prevNode->next;
  74. prevNode->next = temp->next;
  75. deletedElement = temp->data;
  76. free(temp); // 释放被删除节点的内存
  77. }
  78. list->size--; // 更新节点个数
  79. return deletedElement;
  80. }
  81. // 获取指定位置的元素
  82. int getElementAt(const CircularLinkedList *list, size_t index) {
  83. if (index >= list->size) {
  84. return -1; // 返回无效的索引
  85. }
  86. Node *currentNode = list->head;
  87. for (size_t i = 0; i < index; i++) {
  88. currentNode = currentNode->next;
  89. }
  90. return currentNode->data; // 返回指定位置的元素
  91. }
  92. // 修改指定位置的元素
  93. void modifyAt(CircularLinkedList *list, size_t index, int newValue) {
  94. if (index >= list->size) {
  95. return; // 忽略无效的修改位置
  96. }
  97. Node *currentNode = list->head;
  98. for (size_t i = 0; i < index; i++) {
  99. currentNode = currentNode->next;
  100. }
  101. currentNode->data = newValue; // 修改节点的值
  102. }
  103. // 释放单向循环链表内存
  104. void destroyCircularLinkedList(CircularLinkedList *list) {
  105. if (list->head == NULL) {
  106. return;
  107. }
  108. Node *currentNode = list->head;
  109. Node *nextNode;
  110. do {
  111. nextNode = currentNode->next;
  112. free(currentNode);
  113. currentNode = nextNode;
  114. } while (currentNode != list->head);
  115. list->head = NULL;
  116. list->size = 0;
  117. }
  118. // 打印单向循环链表中的所有元素
  119. void printCircularLinkedList(const CircularLinkedList *list) {
  120. if (list->head == NULL) {
  121. printf("链表为空\n");
  122. return;
  123. }
  124. Node *currentNode = list->head;
  125. do {
  126. printf("%d ", currentNode->data);
  127. currentNode = currentNode->next;
  128. } while (currentNode != list->head);
  129. printf("\n");
  130. }
  131. // 主函数测试单向循环链表操作
  132. int main() {
  133. CircularLinkedList myList; // 声明单向循环链表
  134. initCircularLinkedList(&myList); // 初始化单向循环链表
  135. printf("初始化单向循环链表成功!\n");
  136. insertAt(&myList, 0, 1); // 在索引0处插入元素1
  137. insertAt(&myList, 1, 2); // 在索引1处插入元素2
  138. insertAt(&myList, 2, 3); // 在索引2处插入元素3
  139. printf("向单向循环链表插入了3个元素\n");
  140. printCircularLinkedList(&myList); // 打印链表中的元素
  141. printf("单向循环链表长度为: %zu\n", myList.size); // 获取单向循环链表长度
  142. insertAt(&myList, 1, 4); // 在索引1处插入元素4
  143. printf("在索引1处插入元素4\n");
  144. printCircularLinkedList(&myList); // 打印链表中的元素
  145. printf("单向循环链表长度为: %zu\n", myList.size); // 再次获取单向循环链表长度
  146. printf("删除索引1处的元素,该元素值是: %d\n", deleteAt(&myList, 1)); // 删除索引1处的元素
  147. printCircularLinkedList(&myList); // 打印链表中的元素
  148. destroyCircularLinkedList(&myList); // 销毁单向循环链表
  149. printf("单向循环链表销毁成功!\n");
  150. return 0;
  151. }

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/802923
推荐阅读
相关标签
  

闽ICP备14008679号