C语言-单向循环链表带头节点的基本操作(增、删、改、查)
C语言-单向循环链表带头节点的基本操作(增、删、改、查)
前言
带头节点的单向循环链表=链表成环+永远不用判断空头。如果你已经会单链表,那么会很快上手单向循环链表。
详细代码
1、所需要包含的头文件以及定义链表的节点结构
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
2、创建链表(带头节点)
Node* createList() {
Node* head = (Node*)malloc(sizeof(Node));
if (head == NULL) {
printf("内存分配失败!\n");
exit(1);
}
head->next = head; // 初始化为空链表
return head;
}
功能说明:创建一个带有头节点的链表,头节点不存放任何数据,初始化为空链表,头节点指向自己
3、判断是否为空
int isEmpty(Node* head) {
if (head == NULL) return 1;
return head->next == head;
}
功能说明:第一行判断链表是否有头节点,无头节点,自然链表为空;第二行判断链表是否只有头节点;
4、指定位置插入节点(从0开始)
void insert(Node* head, int value, int position) {
if (head == NULL) return;
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败,插入节点失败!\n");
return;
}
newNode->data = value;
Node* current = head;
int i = 0;
// 找到插入位置的前一个节点
while (i < position && current->next != head) {
current = current->next;
i++;
}
// 插入操作
newNode->next = current->next;
current->next = newNode;
}
功能说明:先找到插入位置的前一个节点再进行插入
5、查找节点
Node* find(Node* head, int value) {
if (head == NULL) {
return NULL;
}
Node* current = head->next;
while (current != head) {
if (current->data == value) {
return current;
}
current = current->next;
}
return NULL;
}
6、在指定值(第一个查找到的)后面插入节点
void insertAfterValue(Node* head, int targetValue, int newValue) {
if (head == NULL || head->next == head) {
printf("链表为空,无法插入!\n");
return;
}
// 查找目标节点
Node* target = find(head, targetValue);
if (target == NULL) {
printf("未找到值 %d,插入失败!\n", targetValue);
return;
}
// 创建新节点
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
return;
}
newNode->data = newValue;
// 插入到目标节点后面
newNode->next = target->next;
target->next = newNode;
printf("在值 %d 后面插入 %d 成功!\n", targetValue, newValue);
}
功能说明:
1、遍历链表找到链表中第一个和目标值相等的节点
2、创建新的节点并插入到目标值后面
7、删除指定位置的节点
int deleteAtPosition(Node* head, int position) {
if (head == NULL || head->next == head) {
printf("链表为空,删除失败!\n");
return 0;
}
if (position < 0) {
printf("位置不能为负数!\n");
return 0;
}
Node* prev = head;
Node* current = head->next;
int i = 0;
// 找到要删除的节点及其前驱
while (current != head && i < position) {
prev = current;
current = current->next;
i++;
}
if (current == head) {
printf("位置 %d 超出链表范围,删除失败!\n", position);
return 0;
}
// 执行删除
prev->next = current->next;
int deletedValue = current->data;
free(current);
printf("删除位置 %d 的节点(值:%d)成功!\n", position, deletedValue);
return 1;
}
功能说明:找到找到要删除的节点及其前驱,然后执行删除
8、删除指定值(第一个查找到的)的节点
int deleteNode(Node* head, int value) {
if (head == NULL || head->next == head) {
return 0; // 空链表
}
Node* prev = head;
Node* current = head->next;
while (current != head) {
if (current->data == value) {
prev->next = current->next;
free(current);
return 1;
}
prev = current;
current = current->next;
}
return 0; // 未找到
}
功能说明:遍历链表查找目标值的节点并记录其前驱节点,查找到后进行删除
9、修改节点
int modify(Node* head, int oldVal, int newVal) {
Node* node = find(head, oldVal);
if (node) {
node->data = newVal;
return 1;
}
return 0;
}
10、遍历打印
void traverse(Node* head) {
if (head == NULL) {
printf("链表为空指针!\n");
return;
}
printf("链表: ");
if (head->next == head) {
printf("空\n");
return;
}
Node* current = head->next;
int index = 0;
while (current != head) {
printf("[%d]=%d", index, current->data);
if (current->next != head) printf(" -> ");
current = current->next;
index++;
}
printf("\n");
}
11、获取链表长度
int getLength(Node* head) {
if (head == NULL) {
return 0;
}
int len = 0;
Node* current = head->next;
while (current != head) {
len++;
current = current->next;
}
return len;
}
12、销毁链表
void destroyList(Node* head) {
if (head == NULL) {
return;
}
Node* current = head->next;
while (current != head) {
Node* temp = current;
current = current->next;
free(temp);
}
free(head);
}
13、测试函数
void test() {
printf("=== 带头节点单向循环链表测试 ===\n");
// 1. 创建链表
Node* list = createList();
printf("1. 创建链表\n");
traverse(list);
// 2. 插入元素
printf("\n2. 插入元素\n");
insertAtPosition(list, 10, 0); // 位置0插入10
insertAtPosition(list, 20, 1); // 位置1插入20
insertAtPosition(list, 30, 2); // 位置2插入30
printf("插入10, 20, 30后: ");
traverse(list);
// 3. 在指定值后面插入节点
printf("\n3. 在指定值后面插入节点\n");
insertAfterValue(list, 10, 15); // 在10后面插入15
printf("在10后面插入15后: ");
traverse(list);
insertAfterValue(list, 20, 25); // 在20后面插入25
printf("在20后面插入25后: ");
traverse(list);
// 尝试在不存在的值后面插入
insertAfterValue(list, 100, 200);
// 4. 删除指定位置的节点
printf("\n4. 删除指定位置的节点\n");
printf("删除前: ");
traverse(list);
deleteAtPosition(list, 2); // 删除位置2的节点
printf("删除位置2后: ");
traverse(list);
deleteAtPosition(list, 0); // 删除位置0的节点
printf("删除位置0后: ");
traverse(list);
// 尝试删除超出范围的位置
deleteAtPosition(list, 5);
// 5. 边界测试
printf("\n5. 边界测试\n");
printf("当前链表长度: %d\n", getLength(list));
// 删除所有元素
printf("\n删除所有元素...\n");
int len = getLength(list);
for (int i = 0; i < len; i++) {
deleteAtPosition(list, 0); // 总是删除位置0
}
printf("清空后: ");
traverse(list);
// 6. 清理
destroyList(list);
list = NULL;
printf("\n链表已销毁\n");
}
int main() {
test(); // 运行测试
return 0;
}
总结
总体来说带头节点的单向循环链表比不带头节点的单向循环链表在边界处理上要简单许多
浙公网安备 33010602011771号