单向循环链表详解:C语言实现与应用

单向循环链表详解:C语言实现与应用

引言

单向循环链表是一种特殊的链表结构,其中最后一个节点指向第一个节点,形成一个闭环。这种数据结构在需要循环访问元素的场景中非常有用,如轮询调度、游戏循环等。本文将详细讲解如何使用C语言实现单向循环链表,包括各种基本操作的实现原理和代码分析。

数据结构定义

typedef int TypData;

typedef struct SingleList {
    TypData data;            // 数据域
    struct SingleList *next; // 指向下一个节点的指针
} SList;

基本操作函数

1. 创建头节点

SList *CreHeadNode() {
    SList *Head = (SList *)calloc(1, sizeof(SList));
    if (NULL == Head) {
        perror("创建头节点失败");
        exit(EXIT_FAILURE);
    }
    Head->next = NULL;  // 初始为空链表
    return Head;
}

功能说明:创建链表的头节点,作为链表的入口点。头节点不存储实际数据,其next指针指向链表的第一个实际节点(首元节点)。

2. 创建新节点

SList *CreaOneNode(TypData data) {
    SList *Node = (SList *)calloc(1, sizeof(SList));
    if (NULL == Node) {
        perror("创建节点失败");
        exit(EXIT_FAILURE);
    }
    Node->data = data;
    Node->next = NULL;
    return Node;
}

功能说明:创建包含指定数据的新节点,初始化next指针为NULL。

3. 判断链表是否为空

bool IsEmpty(SList *List) {
    return (List->next == NULL);
}

功能说明:检查链表是否为空(即头节点的next指针是否为NULL)。

4. 头插法插入节点

bool InstHeadNode(SList *Head, TypData data) {
    SList *New = CreaOneNode(data);
    if (NULL == New) return false;
    
    if (IsEmpty(Head)) {  // 空链表情况
        New->next = New;  // 自环
        Head->next = New;
    } else { 
        // 定位尾节点
        SList *tail = Head->next;
        while (tail->next != Head->next) {
            tail = tail->next;
        }
        // 插入新节点并更新环
        New->next = Head->next;
        Head->next = New;
        tail->next = New;  // 尾节点指向新首节点
    }
    return true;
}

功能说明

  1. 在链表头部插入新节点
  2. 空链表时创建自环
  3. 非空链表时:
    • 新节点指向原首节点
    • 头节点指向新节点
    • 尾节点指向新节点(维护循环结构)

时间复杂度:O(n),需要遍历找到尾节点

5. 尾插法插入节点

bool InstTailNode(SList *Head, TypData data) {
    if (IsEmpty(Head)) {
        return InstHeadNode(Head, data);  // 复用头插法
    }
    
    SList *New = CreaOneNode(data);
    if (NULL == New) return false;

    // 定位尾节点
    SList *tail = Head->next;
    while (tail->next != Head->next) {
        tail = tail->next;
    }
    
    // 插入新节点
    New->next = Head->next;  // 新尾节点指向首节点
    tail->next = New;        // 原尾节点指向新节点
    return true;
}

功能说明

  1. 在链表尾部插入新节点
  2. 空链表时复用头插法
  3. 非空链表时:
    • 新节点指向首节点(形成环)
    • 原尾节点指向新节点

时间复杂度:O(n),需要遍历找到尾节点

6. 在指定数据后插入节点

bool InstDataNode(SList *Head, TypData NewData, TypData targetData) {
    if (IsEmpty(Head)) {
        printf("链表为空,插入失败\n");
        return false;
    }
    
    SList *p = Head->next;
    do {
        if (p->data == targetData) {
            SList *New = CreaOneNode(NewData);
            if (NULL == New) return false;
            
            New->next = p->next;
            p->next = New;
            return true;
        }
        p = p->next;
    } while (p != Head->next);
    
    printf("未找到数据 %d,插入失败\n", targetData);
    return false;
}

功能说明

  1. 在包含指定数据(targetData)的节点后插入新节点
  2. 使用do-while循环遍历整个链表
  3. 找到目标节点后插入新节点

时间复杂度:O(n)

7. 遍历并打印链表

void ListPrint(SList *Head) {
    if (IsEmpty(Head)) {
        printf("链表为空\n");
        return;
    }
    
    SList *p = Head->next;
    printf("链表内容: ");
    do {
        printf("%d -> ", p->data);
        p = p->next;
    } while (p != Head->next);
    printf("[循环回到%d]\n", Head->next->data);
}

功能说明

  1. 使用do-while循环遍历链表
  2. 打印每个节点的数据
  3. 显示循环回到的首节点数据

8. 删除头节点

bool DelHeadNode(SList *Head) {
    if (IsEmpty(Head)) {
        printf("链表为空,删除失败\n");
        return false;
    }
    
    SList *delNode = Head->next;
    if (delNode->next == delNode) {  // 单节点情况
        Head->next = NULL;
    } else {
        // 定位尾节点
        SList *tail = Head->next;
        while (tail->next != Head->next) {
            tail = tail->next;
        }
        // 更新指针
        Head->next = delNode->next;
        tail->next = delNode->next;
    }
    free(delNode);
    return true;
}

功能说明

  1. 删除链表的第一个节点
  2. 处理单节点情况:直接置空头节点指针
  3. 多节点情况:
    • 定位尾节点
    • 更新头节点和尾节点的指针
    • 释放被删除节点

时间复杂度:O(n),需要遍历找到尾节点

9. 删除尾节点

bool DelTailNode(SList *Head) {
    if (IsEmpty(Head)) {
        printf("链表为空,删除失败\n");
        return false;
    }
    
    // 单节点情况
    if (Head->next->next == Head->next) {
        free(Head->next);
        Head->next = NULL;
        return true;
    }

    // 定位尾节点及其前驱
    SList *prev = Head->next;
    SList *curr = prev->next;
    
    while (curr->next != Head->next) {
        prev = curr;
        curr = curr->next;
    }
    
    prev->next = Head->next;  // 前驱节点指向首节点
    free(curr);
    return true;
}

功能说明

  1. 删除链表的最后一个节点
  2. 单节点情况:直接删除并置空
  3. 多节点情况:
    • 定位尾节点(curr)及其前驱节点(prev)
    • 前驱节点指向首节点
    • 释放尾节点

时间复杂度:O(n)

10. 删除指定数据的节点

bool DelSpecNode(SList *Head, TypData data) {
    if (IsEmpty(Head)) {
        printf("链表为空,删除失败\n");
        return false;
    }
    
    SList *prev = NULL;        // 前驱节点
    SList *curr = Head->next;  // 当前节点
    
    do {
        if (curr->data == data) {
            if (curr == Head->next) {  // 目标为首节点
                return DelHeadNode(Head);
            } else {
                prev->next = curr->next;
                free(curr);
                return true;
            }
        }
        prev = curr;
        curr = curr->next;
    } while (curr != Head->next);
    
    printf("未找到数据 %d,删除失败\n", data);
    return false;
}

功能说明

  1. 删除包含指定数据(data)的节点
  2. 首节点情况:复用DelHeadNode()
  3. 其他节点:
    • 更新前驱节点的指针
    • 释放当前节点
  4. 遍历整个链表查找目标节点

时间复杂度:O(n)

11. 释放整个链表

void FreeList(SList *Head) {
    if (IsEmpty(Head)) {
        free(Head);
        return;
    }
    
    // 先断开循环
    SList *tail = Head->next;
    while (tail->next != Head->next) {
        tail = tail->next;
    }
    tail->next = NULL;  // 断开循环
    
    // 释放所有数据节点
    SList *current = Head->next;
    while (current != NULL) {
        SList *temp = current;
        current = current->next;
        free(temp);
    }
    
    // 释放头节点
    free(Head);
}

功能说明

  1. 安全释放整个链表占用的内存
  2. 关键步骤:
    • 先断开循环(避免死循环)
    • 释放所有数据节点
    • 最后释放头节点

单向循环链表的特点

  1. 循环结构:最后一个节点的next指针指向第一个节点
  2. 无NULL终止:遍历时需要特殊终止条件
  3. 高效循环访问:适合需要重复遍历的场景
  4. 插入/删除复杂度
    • 头插/头删:O(n)(需要找到尾节点)
    • 尾插/尾删:O(n)
    • 指定位置操作:O(n)

应用场景

  1. 轮询调度算法
  2. 多人游戏中的玩家轮流机制
  3. 循环缓冲区实现
  4. 约瑟夫环问题
  5. 周期性任务调度

完整测试示例

int main() {
    // 创建链表
    SList *list = CreHeadNode();
    
    // 插入操作
    InstHeadNode(list, 30);     // 头部插入30
    InstTailNode(list, 40);     // 尾部插入40
    InstHeadNode(list, 20);     // 头部插入20
    InstDataNode(list, 25, 20); // 在20后插入25
    InstTailNode(list, 50);     // 尾部插入50
    
    ListPrint(list);  // 输出: 20 -> 25 -> 30 -> 40 -> 50 -> [循环回到20]
    
    // 删除操作
    DelHeadNode(list);         // 删除头节点(20)
    ListPrint(list);  // 输出: 25 -> 30 -> 40 -> 50 -> [循环回到25]
    
    DelTailNode(list);         // 删除尾节点(50)
    ListPrint(list);  // 输出: 25 -> 30 -> 40 -> [循环回到25]
    
    DelSpecNode(list, 30);     // 删除节点30
    ListPrint(list);  // 输出: 25 -> 40 -> [循环回到25]
    
    // 释放链表
    FreeList(list);
    return 0;
}

总结

单向循环链表是一种重要的数据结构,特别适合需要循环访问元素的场景。本文详细讲解了C语言实现单向循环链表的各个关键操作,包括:

  1. 链表的基本结构和节点定义
  2. 各种插入操作(头插、尾插、指定位置插)
  3. 各种删除操作(头删、尾删、指定位置删)
  4. 链表遍历和内存释放
  5. 边界条件处理(空链表、单节点链表等)

通过合理使用单向循环链表,可以高效解决许多实际问题,如轮询调度、循环任务处理等。在实际应用中,需要特别注意循环结构的维护,确保在任何操作后链表都能保持正确的环状结构。

posted @ 2025-07-19 11:41  Rare_30  阅读(102)  评论(0)    收藏  举报