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;
}

应用场景

  1. 固定内存块管理(内存池)
  2. 轮询调度(任务/定时器)
  3. 循环缓冲区
  4. 嵌入式资源管理
posted @ 2026-01-22 08:53  tanyee  阅读(0)  评论(0)    收藏  举报