循环链表是一种特殊的链表,其中最后一个节点的指针不是指向NULL,而是指向第一个节点,形成一个环。
- 普通双向链表:5 <-> 10 <-> 20 <-> NULL(到头就没了)
- 双向循环链表:5 <-> 10 <-> 20 <-> 5 <-> 10...(绕圈走,永远走不完,除非主动停)
(1)定义循环链表的 "车厢"(节点)
cpp
struct Node {
int data; // 车厢里的数字
struct Node* next; // 拉后面车厢的手
struct Node* prev; // 拉前面车厢的手
};
(2)造一节新车厢(createNode)
cpp
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
(3)给空链表插第一个车厢(insertInEmpty)
cpp
struct Node* insertInEmpty(struct Node* head, int data) {
struct Node* newNode = createNode(data);
newNode->next = newNode; // 自己的后手拉自己
newNode->prev = newNode; // 自己的前手拉自己
return newNode;
}
空链表插第一个车厢时,因为要形成循环,这节车厢只能 "自己拉自己"(就像一个人站成圈,左手拉右手)。比如插 10,就变成「10 <-> 10 <-> 10...」。
(4)往车头插车厢(insertAtBeginning)
cpp
struct Node* insertAtBeginning(struct Node* head, int data) {
if (head == NULL) { // 空链表,走上面的insertInEmpty
return insertInEmpty(head, data);
}
struct Node* newNode = createNode(data);
struct Node* last = head->prev; // 找到最后一节车厢(头节点的前手就是最后一节)
newNode->next = head; // 新车厢的后手拉拉原来的车头
newNode->prev = last; // 新车厢的前手拉最后一节车厢
last->next = newNode; // 最后一节车厢的后手拉拉新车厢
head->prev = newNode; // 原来的车头的前手拉新车厢
return newNode; // 新车厢变成新车头
}
原来循环链表是「10 <-> 10」(只有 10),插 5 到车头:
- 先找到最后一节车厢(还是 10);
- 5 的后手拉拉 10,5 的前手拉 10;
- 10 的后手拉拉 5,10 的前手拉 5;
- 最终变成「5 <-> 10 <-> 5 <-> 10...」,5 成了新车头。
(5)往车尾插车厢(insertAtEnd)
cpp
struct Node* insertAtEnd(struct Node* head, int data) {
if (head == NULL) {
return insertInEmpty(head, data);
}
struct Node* newNode = createNode(data);
struct Node* last = head->prev; // 找到最后一节车厢
newNode->next = head; // 新车厢的后手拉拉车头(形成循环)
newNode->prev = last; // 新车厢的前手拉原来的最后一节
last->next = newNode; // 原来的最后一节的后手拉拉新车厢
head->prev = newNode; // 车头的前手拉新车厢(新车厢变成新的最后一节)
return head; // 车头没变
}
原来链表是「5 <-> 10 <-> 5」,插 20 到车尾:
- 找到最后一节车厢(10);
- 20 的后手拉拉车头 5,20 的前手拉 10;
- 10 的后手拉拉 20,5 的前手拉 20;
- 最终变成「5 <-> 10 <-> 20 <-> 5...」,车头还是 5。
(6)在指定车厢后插新车厢(insertAfter)
cpp
struct Node* insertAfter(struct Node* head, int data, int key) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
struct Node* current = head;
do { // 注意:循环链表用do-while,先走一次再判断(避免漏了头节点)
if (current->data == key) { // 找到要插在后面的车厢
struct Node* newNode = createNode(data);
newNode->next = current->next; // 新车厢的后手拉拉当前车厢的后手
newNode->prev = current; // 新车厢的前手拉当前车厢
current->next->prev = newNode; // 当前车厢后手的前手拉新车厢
current->next = newNode; // 当前车厢的后手拉拉新车厢
return head;
}
current = current->next;
} while (current != head); // 回到车头就停(避免无限循环)
printf("未找到值为%d的节点!\n", key);
return head;
}
链表是「5 <-> 10 <-> 20 <-> 5」,要在 10 后面插 15:
- 找到 10 这个车厢;
- 15 的后手拉拉 20,15 的前手拉 10;
- 20 的前手拉 15,10 的后手拉拉 15;
- 最终变成「5 <-> 10 <-> 15 <-> 20 <-> 5...」。
(7)删除指定数字的车厢(deleteNode)
cpp
struct Node* deleteNode(struct Node* head, int key) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
// 如果只有1节车厢,且是要删的
if (head->next == head && head->data == key) {
free(head);
return NULL; // 删完就空了
}
struct Node* current = head;
struct Node* toDelete = NULL;
// 找要删的车厢
do {
if (current->data == key) {
toDelete = current;
break;
}
current = current->next;
} while (current != head);
if (toDelete == NULL) {
printf("未找到值为%d的节点!\n", key);
return head;
}
// 如果要删的是车头,新车头是下一节
if (toDelete == head) {
head = head->next;
}
// 让要删的车厢的前后车厢直接拉手
toDelete->prev->next = toDelete->next;
toDelete->next->prev = toDelete->prev;
free(toDelete); // 拆了这节车厢
return head;
}
链表是「5 <-> 10 <-> 15 <-> 20 <-> 5」,删 10:
- 找到 10 这个车厢;
- 5 的后手直接拉拉 15,15 的前手直接拉拉 5;
- 拆了 10,最终变成「5 <-> 15 <-> 20 <-> 5...」。
(8)打印循环链表(printList)
cpp
void printList(struct Node* head) {
if (head == NULL) {
printf("链表为空!\n");
return;
}
struct Node* current = head;
printf("双向循环链表内容: ");
do { // 先打印头节点,再往后走
printf("%d <-> ", current->data);
current = current->next;
} while (current != head); // 回到车头就停
printf("(回到头部)\n");
}
循环链表不能用while (current != NULL)(因为永远不为 NULL),必须用do-while,走到回到车头就停,不然会无限打印。
(9)释放循环链表(freeList)
cpp
void freeList(struct Node* head) {
if (head == NULL) {
return;
}
struct Node* current = head->next;
struct Node* temp;
// 第一步:先断开循环(把最后一节车厢的后手放空)
head->prev->next = NULL;
// 第二步:像普通链表一样一节节拆
while (current != NULL) {
temp = current->next;
free(current);
current = temp;
}
free(head); // 最后拆车头
}
(10)主函数(main):实际操作循环链表
cpp
int main() {
struct Node* head = NULL;
// 插第一个节点10 → 「10 <-> 10」
head = insertInEmpty(head, 10);
printf("创建双向循环链表:\n");
printList(head);
// 车头插5 → 「5 <-> 10 <-> 5」
head = insertAtBeginning(head, 5);
printf("\n在头部插入5后:\n");
printList(head);
// 车尾插20 → 「5 <-> 10 <-> 20 <-> 5」
head = insertAtEnd(head, 20);
printf("\n在尾部插入20后:\n");
printList(head);
// 10后面插15 → 「5 <-> 10 <-> 15 <-> 20 <-> 5」
head = insertAfter(head, 15, 10);
printf("\n在值为10的节点后插入15:\n");
printList(head);
// 删10 → 「5 <-> 15 <-> 20 <-> 5」
head = deleteNode(head, 10);
printf("\n删除值为10的节点后:\n");
printList(head);
// 找15 → 能找到
struct Node* found = searchNode(head, 15);
if (found != NULL) {
printf("\n找到值为15的节点\n");
} else {
printf("\n未找到值为15的节点\n");
}
// 查长度 → 3(5、15、20)
printf("\n双向循环链表长度: %d\n", getLength(head));
// 释放链表
freeList(head);
return 0;
}