0、回顾
在前面的几篇内容中,已经对单向链表和双向链表的基础知识有了一定的了解,如果观察仔细的话,应该会注意到,前面内容的单向、双向链表中,第一个节点的prev指针或最后一个节点的next指针都是指向为NULL的,设计的结构并没有被使用起来,而是直接浪费掉了。
那么如何将这些个指向为空的指针利用起来呢?因此就引入了这篇博客笔记中需要复习梳理的循环链表了。
数据结构复习 | 双向链表-CSDN博客
https://blog.csdn.net/weixin_49337111/article/details/157843906?spm=1001.2014.3001.5502数据结构复习 | 单向链表-CSDN博客
https://blog.csdn.net/weixin_49337111/article/details/157516926?spm=1001.2014.3001.5502
1、什么是循环链表
循环链表,指的是首节点和尾节点连接起来,形成头尾相连形式的链表。
在循环链表中,主要分为两种类型,一种是单向循环链表,另一种是双向循环链表。
下面的图中,展示了单向循环链表和双向循环链表的结构示意。可以非常清晰直观的看到,相较于原先的单向链表,单向循环链表的实现主要是将链表中的尾节点的next指针指向了链表中的首节点。
相较于原先的双向链表,双向循环链表的实现主要是将链表中尾节点的next指针指向了链表中的首节点,而首节点的prev指针指向了尾节点。

在一些研究生入学考试及企业招聘的笔试题中,也经常会考察链表的时空复杂度。当然除了准备考试,学习掌握这个内容,对于日常程序设计开发时,综合考虑代码内部数据结构设计,优化程序代码也是非常有帮助的。
循环链表中,单向循环链表和双向循环链表的时间复杂度和空间复杂度与其非循环结构前是一致的。但有一点不一样的是,非循环链表并不能从尾到头遍历。

假设循环链表的长度为 n时,循环链表的空间复杂度与普通链表完全相同,为 O(n)。区别在于最后一个结点的next指针指向由 NULL 变为 Head,第一个结点的prev指针指向由NULL变成了tail,但是这并没有增加存储负担。
循环链表的时间复杂度(增删查改)与非循环链表的也是一致的。
循环链表的优势在于从头到尾的连续性(可以从任意点出发遍历全部结点)
2、循环与非循环链表代码差异
无论是单向循环链表和单向非循环链表,还是双向循环链表与双向非循环链表,他们的数据结构体设计是保持一致的,没有区别。
cpp
// 设计单向链表的数据结点
typedef int ElemType_t;
typedef struct node{
ElemType_t data;
struct node *next;
}Node_t;
typedef struct list{
Node_t *head;
Node_t *tail;
int nodeNumber;
}List_t;
---------------------------
// 设计双向链表的数据结点
typedef int ElemType_t;
typedef struct node{
ElemType_t data;
struct node *prev;
struct node *next;
}Node_t;
struct list{
Node_t *head;
Node_t *tail;
int nodeNumber;
};
循环和非循环链表的主要区别是在如下几点:
(1)、初始化与结构定义
普通链表:头指针指向第一个结点,尾结点的 next 指向 NULL,头结点的prev指向NULL。
循环链表:头指针指向第一个结点,尾结点的 next 指向头结点,头结点的prev指针指向尾结点。如果是空链表,头指针的 prev和next 通常指向自己。
(2)、遍历/查找
这是循环链表改动最大的地方,也是最容易出错的地方------死循环。
普通链表:
cpp
// 伪代码:从头遍历到尾
p = head;
while (p != NULL) // 以NULL作为终止条件
{
// 处理 p->data
p = p->next;
}
循环链表:链表没有 NULL,如果沿用普通链表的处理逻辑,p = p->next 从尾结点会跳到头结点,永远不为 NULL,会导致程序死循环。因此需要记录起点,遍历一圈后结束。
cpp
// 伪代码:从头遍历一圈
p = head;
// 情况1:链表为空
if (p == NULL)
return;
// 情况2:非空链表,使用 do-while 确保至少执行一次
do {
// 处理 p->data
p = p->next;
} while (p != head); // 判断是否回到了头结点
(3)、插入操作
循环链表中,插入操作的核心逻辑(申请结点、连接指针)与非循环链表的操作基本不变,变动的依然是尾结点的特殊处理。
如果是双向循环链表,那么除了尾结点,就还需要变动其头结点的处理。下面以尾插进行说明。
单向循环链表的尾部插入时 ,相较于单向非循环链表,其操作不再是找 null,而是需要找 p->next == head 的p结点(即尾结点),然后将尾结点的 next 指向新结点,新结点的 next 指向 head。
双向循环链表尾部插入时 ,tail->next = newNode; newNode->prev = tail; newNode->next = head; head->prev = newNode;(相比于双向非循环链表,多了两步维护与头部的循环)
(4)、删除操作
和前面提到的插入操作一样,进行删除操作时,同样在边界结点(头结点和尾结点)的处理上有较大差异。下面以尾删进行说明。
单向循环链表的尾部删除操作时 ,需要找到尾结点的前驱,即 p->next->next == head 的p结点,然后将其 next 指向 head。
双向循环链表尾部删除操作时 ,newTail = tail->prev; newTail->next = head; head->prev = newTail;(因为尾结点删除了,新的尾结点必须和头结点连起来)
3、程序代码实现示例
(1)、单向循环链表
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int ElemType_t;
typedef struct node{
ElemType_t data;
struct node *next;
} Node_t;
typedef struct list{
Node_t *head;
Node_t *tail;
int nodeNumber;
} List_t;
List_t *initList(void)
{
List_t *list = (List_t *)malloc(sizeof(List_t));
if (list == NULL)
return NULL;
list->head = NULL;
list->tail = NULL;
list->nodeNumber = 0;
return list;
}
Node_t *createNode(ElemType_t data)
{
Node_t *newNode = (Node_t *)malloc(sizeof(Node_t));
if (newNode == NULL)
return NULL;
newNode->data = data;
newNode->next = NULL;
return newNode;
}
bool isEmpty(List_t *list)
{
return (list->head == NULL || list->nodeNumber == 0);
}
bool insertAtTail(List_t *list, ElemType_t data)
{
if (list == NULL) return false;
Node_t *newNode = createNode(data);
if (newNode == NULL) return false;
if (isEmpty(list))
{
list->head = newNode;
list->tail = newNode;
newNode->next = newNode;
} else
{
newNode->next = list->head;
list->tail->next = newNode;
list->tail = newNode;
}
list->nodeNumber++;
return true;
}
bool insertAtHead(List_t *list, ElemType_t data)
{
if (list == NULL)
return false;
Node_t *newNode = createNode(data);
if (newNode == NULL)
return false;
if (isEmpty(list))
{
list->head = newNode;
list->tail = newNode;
newNode->next = newNode;
} else
{
newNode->next = list->head;
list->head = newNode;
list->tail->next = list->head;
}
list->nodeNumber++;
return true;
}
bool deleteFromTail(List_t *list)
{
if (list == NULL || isEmpty(list))
return false;
if (list->nodeNumber == 1)
{
free(list->head);
list->head = NULL;
list->tail = NULL;
} else
{
Node_t *current = list->head;
while (current->next != list->tail)
{
current = current->next;
}
free(list->tail);
current->next = list->head;
list->tail = current;
}
list->nodeNumber--;
return true;
}
bool deleteFromHead(List_t *list)
{
if (list == NULL || isEmpty(list))
return false;
if (list->nodeNumber == 1)
{
free(list->head);
list->head = NULL;
list->tail = NULL;
} else
{
Node_t *temp = list->head;
list->head = list->head->next;
list->tail->next = list->head;
free(temp);
}
list->nodeNumber--;
return true;
}
void printList(List_t *list)
{
if (list == NULL || isEmpty(list))
{
printf("链表为空\n");
return;
}
printf("循环链表: ");
Node_t *current = list->head;
do {
printf("%d ", current->data);
current = current->next;
} while (current != list->head);
printf("\r\n");
}
void destroyList(List_t *list)
{
if (list == NULL)
return;
if (!isEmpty(list))
{
Node_t *current = list->head;
Node_t *temp;
do {
temp = current;
current = current->next;
free(temp);
} while (current != list->head);
}
free(list);
}
int main(void)
{
printf("=== 单向循环链表 ===\n\n");
List_t *list = initList();
printf("1. 尾部插入 10, 20, 30\n");
insertAtTail(list, 10);
insertAtTail(list, 20);
insertAtTail(list, 30);
printList(list);
printf("\n2. 头部插入 5\n");
insertAtHead(list, 5);
printList(list);
printf("\n3. 尾部删除\n");
deleteFromTail(list);
printList(list);
printf("\n4. 头部删除\n");
deleteFromHead(list);
printList(list);
destroyList(list);
return 0;
}
测试结果:

(2)、双向循环链表
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int ElemType_t;
typedef struct node{
ElemType_t data;
struct node *prev;
struct node *next;
} Node_t;
typedef struct list{
Node_t *head;
Node_t *tail;
int nodeNumber;
} List_t;
List_t *initList(void)
{
List_t *list = (List_t *)malloc(sizeof(List_t));
if (list == NULL)
return NULL;
list->head = NULL;
list->tail = NULL;
list->nodeNumber = 0;
return list;
}
Node_t *createNode(ElemType_t data)
{
Node_t *newNode = (Node_t *)malloc(sizeof(Node_t));
if (newNode == NULL)
return NULL;
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
bool isEmpty(List_t *list)
{
return (list->head == NULL || list->nodeNumber == 0);
}
bool insertAtTail(List_t *list, ElemType_t data)
{
if (list == NULL)
return false;
Node_t *newNode = createNode(data);
if (newNode == NULL)
return false;
if (isEmpty(list))
{
list->head = newNode;
list->tail = newNode;
newNode->prev = newNode;
newNode->next = newNode;
} else
{
list->tail->next = newNode;
newNode->prev = list->tail;
newNode->next = list->head;
list->head->prev = newNode;
list->tail = newNode;
}
list->nodeNumber++;
return true;
}
bool insertAtHead(List_t *list, ElemType_t data)
{
if (list == NULL)
return false;
Node_t *newNode = createNode(data);
if (newNode == NULL)
return false;
if (isEmpty(list))
{
list->head = newNode;
list->tail = newNode;
newNode->prev = newNode;
newNode->next = newNode;
} else
{
newNode->next = list->head;
list->head->prev = newNode;
newNode->prev = list->tail;
list->tail->next = newNode;
list->head = newNode;
}
list->nodeNumber++;
return true;
}
bool deleteFromTail(List_t *list)
{
if (list == NULL || isEmpty(list)) return false;
if (list->nodeNumber == 1)
{
free(list->head);
list->head = NULL;
list->tail = NULL;
} else
{
Node_t *temp = list->tail;
Node_t *newTail = list->tail->prev;
newTail->next = list->head;
list->head->prev = newTail;
list->tail = newTail;
free(temp);
}
list->nodeNumber--;
return true;
}
bool deleteFromHead(List_t *list)
{
if (list == NULL || isEmpty(list))
return false;
if (list->nodeNumber == 1)
{
free(list->head);
list->head = NULL;
list->tail = NULL;
} else
{
Node_t *temp = list->head;
Node_t *newHead = list->head->next;
newHead->prev = list->tail;
list->tail->next = newHead;
list->head = newHead;
free(temp);
}
list->nodeNumber--;
return true;
}
void printForward(List_t *list)
{
if (list == NULL || isEmpty(list))
{
printf("链表为空\n");
return;
}
printf("正向: ");
Node_t *current = list->head;
do {
printf("%d ", current->data);
current = current->next;
} while (current != list->head);
printf("\n");
}
void printBackward(List_t *list)
{
if (list == NULL || isEmpty(list))
{
printf("链表为空\n");
return;
}
printf("反向: ");
Node_t *current = list->tail;
do {
printf("%d ", current->data);
current = current->prev;
} while (current != list->tail);
printf("\n");
}
void verifyCircular(List_t *list) {
if (list == NULL || isEmpty(list)) return;
printf("\n=== 循环特性验证 ===\n");
printf("头结点: %d\n", list->head->data);
printf("尾结点: %d\n", list->tail->data);
printf("头结点的前驱: %d\n", list->head->prev->data);
printf("头结点的后继: %d\n", list->head->next->data);
printf("尾结点的前驱: %d\n", list->tail->prev->data);
printf("尾结点的后继: %d\n", list->tail->next->data);
printf("===================\n");
}
// 销毁链表
void destroyList(List_t *list)
{
if (list == NULL) return;
if (!isEmpty(list)) {
Node_t *current = list->head;
Node_t *temp;
do {
temp = current;
current = current->next;
free(temp);
} while (current != list->head);
}
free(list);
}
// 测试
int main(void)
{
printf("=== 双向循环链表 ===\n\n");
List_t *list = initList();
printf("1. 尾部插入 10, 20, 30\n");
insertAtTail(list, 10);
insertAtTail(list, 20);
insertAtTail(list, 30);
printForward(list);
printBackward(list);
verifyCircular(list);
printf("\n2. 头部插入 5\n");
insertAtHead(list, 5);
printForward(list);
printBackward(list);
printf("\n3. 尾部删除\n");
deleteFromTail(list);
printForward(list);
printBackward(list);
verifyCircular(list);
printf("\n4. 头部删除\n");
deleteFromHead(list);
printForward(list);
printBackward(list);
destroyList(list);
return 0;
}
测试结果
