循环链表是一种特殊的链表结构,其中链表的最后一个节点的 `next` 指针指向链表的第一个节点,从而形成一个环。循环链表可以是单向的(循环单链表)或双向的(循环双链表)。这种结构的一个显著特点是它没有明确的起点和终点,任何一个节点都可以作为起点。
### 循环单链表
**特点:**
- 最后一个节点的 `next` 指针指向头节点(第一个节点),形成环。
- 可以从链表中的任何一个节点开始遍历。
**结构示例:**
```c
#include <stdio.h>
#include <stdlib.h>
// 定义节点结构
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建一个循环单链表
Node* createCircularList(int data[], int size) {
if (size == 0) return NULL;
Node* head = (Node*)malloc(sizeof(Node));
head->data = data[0];
Node* current = head;
for (int i = 1; i < size; i++) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data[i];
current->next = newNode;
current = newNode;
}
current->next = head; // 形成循环
return head;
}
// 打印循环链表
void printCircularList(Node* head) {
if (head == NULL) return;
Node* current = head;
do {
printf("%d ", current->data);
current = current->next;
} while (current != head);
printf("\n");
}
```
### 循环双链表
**特点:**
- 每个节点有两个指针:`next` 和 `prev`。
- `next` 指向后继节点,`prev` 指向前驱节点。
- 最后一个节点的 `next` 指向头节点,第一个节点的 `prev` 指向最后一个节点。
**结构示例:**
```c
#include <stdio.h>
#include <stdlib.h>
// 定义节点结构
typedef struct Node {
int data;
struct Node* next;
struct Node* prev;
} Node;
// 创建一个循环双链表
Node* createCircularDoublyList(int data[], int size) {
if (size == 0) return NULL;
Node* head = (Node*)malloc(sizeof(Node));
head->data = data[0];
Node* current = head;
for (int i = 1; i < size; i++) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data[i];
newNode->prev = current;
current->next = newNode;
current = newNode;
}
current->next = head; // 形成循环
head->prev = current; // 形成循环
return head;
}
// 打印循环双链表
void printCircularDoublyList(Node* head) {
if (head == NULL) return;
Node* current = head;
do {
printf("%d ", current->data);
current = current->next;
} while (current != head);
printf("\n");
}
```
### 使用场景
循环链表在需要循环遍历数据结构的情况下非常有用,例如:
- 实现循环队列。
- 需要一个循环访问的缓冲区。
- 游戏中的玩家循环轮流机制。
循环链表的设计可以避免在处理链表尾部和头部时需要特殊处理,因为它们自然连接在一起。选择使用单链表还是双链表取决于具体需求,比如查找效率和内存使用效率等。
循环链表是一种特殊的链表结构,其中链表的最后一个节点指向链表的第一个节点,使链表形成一个环。循环链表可以是单向的(单循环链表)或双向的(双循环链表),这里主要讨论单循环链表的插入和删除操作。
### 单循环链表的插入
**在给定节点后插入新节点**
假设我们要在节点 `p` 后面插入一个新节点 `s`。
**步骤:**
1. **创建新节点 `s`:**
- 为新节点分配内存并设置数据。
2. **调整指针:**
- 将 `s` 的 `next` 指针指向 `p->next`。
- 将 `p` 的 `next` 指针指向 `s`。
**代码示例:**
```c
#include <stdio.h>
#include <stdlib.h>
// 定义节点结构
typedef struct Node {
int data;
struct Node* next;
} Node;
// 在节点 p 后插入新节点 s
void insertAfter(Node* p, int data) {
if (p == NULL) return;
Node* s = (Node*)malloc(sizeof(Node));
s->data = data;
s->next = p->next;
p->next = s;
}
```
**在单循环链表的末尾插入新节点**
1. **创建新节点 `s`:**
- 为新节点分配内存并设置数据。
2. **调整指针:**
- 将 `s` 的 `next` 指针指向头节点(如果有头节点)。
- 找到最后一个节点(使其 `next` 指向头节点),并将其 `next` 指向 `s`。
**代码示例:**
```c
// 在单循环链表的末尾插入新节点
void insertAtEnd(Node** head, int data) {
Node* s = (Node*)malloc(sizeof(Node));
s->data = data;
if (*head == NULL) {
*head = s;
s->next = *head;
} else {
Node* temp = *head;
while (temp->next != *head) {
temp = temp->next;
}
temp->next = s;
s->next = *head;
}
}
```
### 单循环链表的删除
**删除给定节点后的节点**
假设我们要删除节点 `p` 后的节点。
**步骤:**
1. **检查 `p` 的 `next` 是否不为头节点(或不为 `p` 本身):**
- 如果 `p->next` 不等于 `p`,则找到要删除的节点。
2. **调整指针和释放内存:**
- 用临时指针 `temp` 指向 `p->next`。
- 将 `p->next` 指向 `temp->next`。
- 释放 `temp` 的内存。
**代码示例:**
```c
// 删除节点 p 后的节点
void deleteAfter(Node* p) {
if (p == NULL || p->next == p) return;
Node* temp = p->next;
p->next = temp->next;
free(temp);
}
```
**删除头节点**
删除头节点需要找到最后一个节点,并调整其 `next` 指向新的头节点。
**步骤:**
1. **检查链表是否为空或只有一个节点:**
- 如果为空或只有一个节点,则直接处理。
2. **调整指针和释放内存:**
- 找到最后一个节点。
- 将头节点的 `next` 赋给 `head`。
- 将最后一个节点的 `next` 指向新的头节点。
- 释放旧头节点的内存。
**代码示例:**
```c
// 删除循环链表的头节点
void deleteHead(Node** head) {
if (*head == NULL) return;
Node* temp = *head;
if (temp->next == *head) { // 只有一个节点
free(temp);
*head = NULL;
} else {
Node* last = *head;
while (last->next != *head) {
last = last->next;
}
*head = temp->next;
last->next = *head;
free(temp);
}
}
```
### 注意事项
- **循环结构**:在处理循环链表时,注意循环条件,防止无限循环。
- **边界条件**:特别是在插入和删除操作中处理单节点或空链表的情况。
- **内存管理**:确保在删除节点时正确释放内存,以避免内存泄漏。
循环链表在实现队列等需要循环访问的结构时非常有用。这些操作的实现可以根据具体需求进行调整和优化。