C语言-单向循环链表不带头节点的基本操作(增、删、改、查)
C语言-单向循环链表不带头节点的基本操作(增、删、改、查)
前言
这篇博客将带你从零开始,逐步实现一个不带头节点的单向循环链表,并完成其创建、遍历、增、删、改、查等核心操作。我们将重点关注那些容易出错的边界条件,并用清晰的代码进行解析。
详细代码
1、所需要包含的头文件以及定义链表的节点结构
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node* next;
} Node;
2、创建新节点
Node* createNode(int data)
{
Node* newNode = (Node*)malloc(sizeof(Node));
if (!newNode)
{
printf("内存分配失败\n");
exit(1);
}
newNode->next = NULL;
newNode->data = data;
return newNode;
}
原理解释:创建的新节点的next先指向NULL
3、初始化空链表
Node* initList()
{
return NULL;
}
4、判断链表是否为空
int isEmpty(Node* head)
{
return head == NULL;
}
5、头插法
Node* insertAtHead(Node* head, int data)
{
Node* newNode = createNode(data);
//空链表,新节点指向自己
if (isEmpty(head))
{
newNode->next = newNode;
return newNode;
}
//非空链表,找到尾节点,插入到头部
Node* tail = head;
while (tail->next != head)
{
tail = tail->next;
}
newNode->next = head;
tail->next = newNode;
return newNode;
}
原理解释:这里分两种情况讨论。第一种如果链表为空,则新节点指向自己;第二种如果链表非空,则找到尾部节点,然后插入到头部
6、尾插法
Node* insertAtTail(Node* head, int data)
{
Node* newNode = createNode(data);
//空链表,新节点指向自己
if (isEmpty(head))
{
newNode->next = newNode;
return newNode;
}
//非空链表,找到尾节点,插入到尾部
Node* tail = head;
while (tail->next != head)
{
tail = tail->next;
}
tail->next = newNode;
newNode->next = head;
return head;
}
原理解释:这里分两种情况讨论。第一种如果链表为空,则新节点指向自己;第二种如果链表非空,则找到尾部节点,然后插入到尾部
7、在指定位置插入节点(位置从0开始)
Node* insertAtPosition(Node* head, int data, int position)
{
if (position < 0)
{
printf("位置不能为负的\n");
return head;
}
//如果位置为0,相当于头插
if (position == 0)
{
return insertAtHead(head, data);
}
//空链表但位置不为0
if (isEmpty(head))
{
printf("链表为空,但位置%d不为0,将插入到位置0\n", position);
return insertAtHead(head, data);
}
Node* newNode = createNode(data);
Node* current = head;
int index = 0;
//找到要插入位置的前一个节点
while (current->next != head && index < position - 1)
{
current = current->next;
index++;
}
//如果位置超过链表长度,插入到末尾
if (index < position - 1)
{
printf("位置%d超出链表长度,将插入到末尾\n", position);
}
newNode->next = current->next;
current->next = newNode;
return head;
}
原理解释:
首先我们要先判断3种特殊情况。第一,插入的位置小于0,这是不被允许的,代码不进行任何插入操作,直接返回链表的头部节点;第二,插入的位置等于0,相当于头插,直接调用上面的函数即可;第三,链表为空但位置不为0,也是相当于头插,调用头插函数即可;
接着我们找到要插入位置的前一个节点,顺带判断一下位置是否超过链表长度,若超过,则直接插入到末尾。
最后正常插入即可。
8、在指定值后插入节点
Node* insertAfterValue(Node* head, int targetValue, int newValue)
{
if (isEmpty(head))
{
printf("链表为空,无法插入\n");
return head;
}
Node* current = head;
//查找目标值
do
{
if (current->data == targetValue)
{
Node* newNode = createNode(newValue);
newNode->next = current->next;
current->next = newNode;
return head;
}
current = current->next;
} while (current != head);
printf("未找到值为%d的节点\n", targetValue);
return head;
}
原理解释:查找目标值这里选择do-while是因为,不管while有没有成立,都会至少执行一次do里面的代码
9、删除指定值的节点
Node* deleteByValue(Node* head, int data)
{
if (isEmpty(head))
{
printf("链表为空\n");
return NULL;
}
//如果链表只有一个节点
if (head->next == head)
{
if (head->data == data)
{
free(head);
return NULL;
}
else
{
printf("未找到数据为%d的节点\n", data);
return head;
}
}
Node* current = head;
Node* prev = NULL;
//查找要删除的节点
do
{
if (current->data == data)
{
break;
}
prev = current;
current = current->next;
} while (current != head);
//未找到
if (current->data != data)
{
printf("未找到数据为%d的节点\n", data);
return head;
}
//如果要删除的是头节点
if (current == head)
{
//找到尾节点
Node* tail = head;
while (tail->next != head)
{
tail = tail->next;
}
tail->next = head->next;
Node* newHead = head->next;
free(head);
return newHead;
}
//删除中间或尾部节点
prev->next = current->next;
free(current);
return head;
}
原理解释:依旧先处理链表为空以及链表只有一个节点的特殊情况。然后进行正常的查找删除操作。这里不管删除的是中间节点还是尾部节点都满足"前驱指向后继"的逻辑,所以可以共用同一条语句。
10、查找节点
Node* search(Node* head, int data)
{
if (isEmpty(head))
return NULL;
Node* current = head;
do
{
if (current->data == data)
return current;
current = current->next;
} while (current != head);
return NULL;
}
11、获取链表长度
int getLength(Node* head)
{
if (isEmpty(head))
return 0;
int length = 0;
Node* current = head;
do
{
length++;
current = current->next;
} while (current != head);
return length;
}
12、打印链表
void printList(Node* head)
{
if (isEmpty(head))
{
printf("链表为空\n");
return;
}
Node* current = head;
printf("链表内容: ");
do
{
printf("%d", current->data);
current = current->next;
if (current != head)
printf(" -> ");
} while (current != head);
printf(" -> ...(循环回到头部)\n");
}
13、销毁链表
void destroyList(Node* head)
{
if (isEmpty(head))
return;
Node* current = head->next;
Node* nextNode = NULL;
//释放除头节点外的所有节点
while (current != head)
{
nextNode = current->next;
free(current);
current = nextNode;
}
free(head);
}
14、测试函数
int main() {
Node* list = NULL;
printf("=== 不带头节点的单向循环链表测试 ===\n\n");
// 测试空链表
printf("1. 初始化空链表:\n");
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试尾插法
printf("2. 尾插法插入 1, 2, 3:\n");
list = insertAtTail(list, 1);
list = insertAtTail(list, 2);
list = insertAtTail(list, 3);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试头插法
printf("3. 头插法插入 0:\n");
list = insertAtHead(list, 0);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试指定位置插入
printf("4. 在位置2插入 99:\n");
list = insertAtPosition(list, 99, 2);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试在指定值后插入
printf("5. 在值2后插入 88:\n");
list = insertAfterValue(list, 2, 88);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试边界情况:位置超出
printf("6. 在位置10插入 77:\n");
list = insertAtPosition(list, 77, 10);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试查找
printf("7. 查找值为2的节点:\n");
Node* found = search(list, 2);
if (found) {
printf("找到节点,值为: %d\n\n", found->data);
}
else {
printf("未找到节点\n\n");
}
// 测试删除
printf("8. 删除值为99的节点:\n");
list = deleteByValue(list, 99);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 测试删除头节点
printf("9. 删除头节点(值为0):\n");
list = deleteByValue(list, 0);
printList(list);
printf("链表长度: %d\n\n", getLength(list));
// 销毁链表
printf("10. 销毁链表:\n");
destroyList(list);
list = NULL;
printList(list);
return 0;
}
应用场景
- 固定内存块管理(内存池)
- 轮询调度(任务/定时器)
- 循环缓冲区
- 嵌入式资源管理
浙公网安备 33010602011771号