数据结构Day2-循环链表

3 阅读14分钟

循环链表

简介

循环链表是链表的另一种重要变式,其尾节点的next指针不再指向NULL,而是重新指向头结点,形成一个环状结构。这种设计在某些场景下具有独特优势,例如需要循环遍历数据、实现约瑟夫环问题或构建环形缓冲区。

具体实现

头文件与结构体声明

循环链表是链表的一种变体,其核心特点是尾节点的next指针不再指向NULL,而是重新指向头结点,从而形成一个闭环。这种结构特别适合需要循环遍历的场景,比如操作系统的进程调度、约瑟夫环问题等。实现时,空链表的标志是头结点的next指向自身,插入和删除操作需要维护好节点间的链接关系,同时注意索引边界为1到size+1。遍历的终止条件是回到头结点而非NULL。循环链表的优势在于可以从任意节点开始遍历整个链表。

typedef int E;  // 定义元素类型别名,便于统一修改数据类型typedef struct LoopLinkedNode {  // 定义循环链表节点结构体
    E element;                   // 存储节点的数据元素
    struct LoopLinkedNode *next; // 指向下一个节点的指针
} LoopLinkedList;               // 为结构体取别名typedef LoopLinkedList *LPLNode;  // 定义节点指针类型别名,表示指向循环链表节点的指针void InitLoopList(LPLNode Head);                      // 初始化循环链表
_Bool InsertLoopList(LPLNode Head, E element, int index);  // 在指定位置插入元素
_Bool DeleteLoopList(LPLNode Head, int index);        // 删除指定位置的元素
void PrintLoopList(LPLNode Head);                     // 打印整个循环链表

功能函数实现

初始化函数

初始化函数负责将头结点的next指针置为NULL,标志链表为空。函数首先进行防御性检查,如果传入的Head指针为NULL则报错返回。初始化完成后输出头结点地址便于调试。由于头结点本身不存储数据,只需要设置next指针即可。该函数没有返回值,因为头结点由调用者分配内存,函数只负责建立正确的链表结构。

void InitLinkList(Node Head){
    if(Head == NULL){                              // 检查传入的头指针是否为空
        printf("[ERROR]:undefined LinkList\n ");   // 输出错误信息
        return;                                    // 直接返回,不进行初始化
    }
    Head->next = NULL;                             // 将头结点的next指针设置为NULL,表示空链表
    printf("[LOG]:This new LinkList 's Adress is %p\n",Head);  // 打印头结点地址供调试
}
插入函数

插入函数首先进行参数校验,确保头指针有效且索引合法。然后通过循环移动指针定位到待插入位置的前驱节点,循环条件是--index,因此当index=1时不进入循环,此时前驱就是头结点。定位完成后,创建新节点并为其赋值。最关键的是指针更新步骤:先将新节点的next指向当前前驱节点的后继,再将前驱节点的next指向新节点,这样新节点就被成功链接到链表中。最后打印调试信息并返回成功标志。

_Bool insertLinkList(Node Head,E element,int index){
    if(Head == NULL){                              // 检查链表头指针是否有效
        printf("[LOG]: undefined LinkList\n");     // 输出未定义错误
        return 0;                                  // 返回0表示失败
    }
    if(index < 1){                                 // 检查索引下界,位置从1开始
        printf("[ERROR]:Wrong index\n");           // 输出索引错误
        return 0;                                  // 返回失败
    }
    while(--index){                                // 循环找到插入位置的前驱节点
        if(Head->next == NULL) return 0;           // 如果提前到达末尾,索引无效
        Head = Head->next;                         // 指针后移
    }
    Node node = malloc(sizeof(struct LinkNode));   // 为新节点分配内存
    node->element = element;                       // 将数据存入新节点
    node->next = Head->next;                       // 新节点的next指向原前驱的后继
    Head->next = node;                             // 前驱节点的next指向新节点
    printf("[LOG]:insert success\n[INFO]:Front Node address = %p,Target Node address = %p\n",Head,node);
    return 1;                                      // 返回1表示插入成功
}
删除函数

删除函数首先验证参数合法性。然后通过循环找到待删除节点的前驱节点,循环逻辑与插入类似。找到前驱后,用DeleteNode指针保存待删除节点的地址,然后执行核心的删除操作:将前驱节点的next指针直接指向待删除节点的后继节点,这就从逻辑上跳过了待删除节点。完成链表的指针调整后,需要调用free释放被删除节点的内存,防止内存泄漏。最后打印被删除节点的信息并返回成功标志。

_Bool deleteLinkList(Node Head,int index){
    if(Head == NULL){                              // 检查链表是否存在
        printf("[ERROR]:undefined LinkList\n");    // 输出错误信息
        return 0;                                  // 返回失败
    }
    if(index < 1){                                 // 检查索引合法性
        printf("[ERROR]:Wrong index\n");           // 输出索引错误
        return 0;                                  // 返回失败
    }
    Node DeleteNode;                               // 声明待删除节点的指针变量
    while(--index){                                // 循环找到待删除节点的前驱节点
        if(Head->next == NULL){                    // 如果链表提前结束
            printf("[ERROR]:Wrong index\n");       // 输出索引超出范围
            return 0;                              // 返回失败
        }
        Head = Head->next;                         // 指针后移
    }
    DeleteNode = Head->next;                       // 保存待删除节点的地址
    Head->next = Head->next->next;                 // 前驱节点的next跳过待删除节点,指向其后继
    printf("[LOG]:Delete success,Node Adress = %p,Node element = %d\n"
           ,DeleteNode,DeleteNode->element);       // 打印被删除节点信息
    free(DeleteNode);                              // 释放待删除节点的内存
    return 1;                                      // 返回成功
}
打印函数

打印函数用于遍历整个链表并输出每个节点的信息。首先进行空指针检查,确保链表头存在。然后进入循环,循环条件是当前节点的next指针不为空,即还有后继节点。每次循环先将head指针移动到下一个节点,然后打印该节点的element值和next指针地址。这种遍历方式会从第一个数据节点开始,逐个输出直到最后一个节点。由于最后一个节点的next为NULL,循环会在打印完最后一个节点后退出,因此输出格式中会在最后一个节点后留下一个“->”符号,打印完成后函数返回。

int GetLinkListSize(Node Head){
    if(Head == NULL){                              // 检查链表是否存在
        printf("[ERROR]:undefined LinkList\n");    // 输出错误信息
        return 0;                                  // 返回0表示空链表
    }
    int Size = 0;                                  // 初始化计数器
    while(Head->next){                             // 只要还有后继节点就继续计数
        Head = Head->next;                         // 移动到下一个节点
        Size++;                                    // 计数器加1
    }
    return Size;                                   // 返回链表中节点的个数
}
获取链表长度

该函数用于统计链表中实际存储数据的节点个数(头结点不计入)。首先进行空指针检查,如果头指针为空则返回0。然后定义Size变量作为计数器,初始值为0。进入循环,循环条件是当前节点的next指针不为空,因为头结点不存储数据,所以从第一个数据节点开始计数。每次循环移动指针到下一个节点并将Size加1,直到到达尾节点。循环结束后返回Size,即为链表中节点的总数。该函数不会修改链表本身,只进行只读的遍历操作。

int GetLinkListSize(Node Head){
    if(Head == NULL){                              // 检查链表是否存在
        printf("[ERROR]:undefined LinkList\n");    // 输出错误信息
        return 0;                                  // 返回0表示空链表
    }
    int Size = 0;                                  // 初始化计数器
    while(Head->next){                             // 只要还有后继节点就继续计数
        Head = Head->next;                         // 移动到下一个节点
        Size++;                                    // 计数器加1
    }
    return Size;                                   // 返回链表中节点的个数
}
按索引获取元素

该函数根据位置索引返回对应节点的元素值。首先检查链表是否存在,然后通过调用GetLinkListSize函数获取链表的总节点数,并判断传入的索引是否在1到size之间。索引验证通过后,进入while循环,循环条件为index--,即循环index次。每次循环将Head指针向后移动一个节点,初始时Head指向头结点,循环一次后指向第一个数据节点,循环两次后指向第二个数据节点,以此类推。循环结束后,Head正好指向第index个数据节点,直接返回该节点的element值即可。这种实现方式简洁高效,时间复杂度为O(n)。

Node GetPointByElement(Node Head,E element){
    if(Head == NULL){                              // 检查链表是否存在
        printf("[ERROR]:undefined LinkList\n");    // 输出错误信息
        return NULL;                               // 返回NULL表示查找失败
    }
    while(Head->next != NULL){                     // 遍历链表直到末尾
        Head = Head->next;                         // 移动到下一个节点
        if(Head->element == element){              // 比较当前节点的元素是否与目标相等
            printf("[LOG]:Find success\n[INFO]: Node Address = %p,Node element = %d\n",Head,Head->element);
            return Head;                           // 找到目标,返回节点地址
        }
    }
    printf("[LOG]:this element not in the LinkList\n");  // 遍历完未找到
    return NULL;                                   // 返回NULL表示查找失败
}
按元素查找节点

该函数根据给定的元素值查找第一个匹配的节点,并返回其指针。首先检查链表是否存在,如果头指针为空则直接返回NULL。然后进入while循环,循环条件是当前节点的next不为空,这意味着会遍历到最后一个数据节点。每次循环先将Head移动到下一个节点,然后检查该节点的element是否与目标元素相等。如果找到匹配的节点,打印调试信息并立即返回该节点的地址。如果循环结束后仍未找到匹配的元素,则打印未找到的信息并返回NULL。这种查找方式返回的是节点的指针,调用者可以直接通过指针访问节点甚至修改节点的值,为后续操作提供了便利。

Node GetPointByElement(Node Head,E element){
    if(Head == NULL){                              // 检查链表是否存在
        printf("[ERROR]:undefined LinkList\n");    // 输出错误信息
        return NULL;                               // 返回NULL表示查找失败
    }
    while(Head->next != NULL){                     // 遍历链表直到末尾
        Head = Head->next;                         // 移动到下一个节点
        if(Head->element == element){              // 比较当前节点的元素是否与目标相等
            printf("[LOG]:Find success\n[INFO]: Node Address = %p,Node element = %d\n",Head,Head->element);
            return Head;                           // 找到目标,返回节点地址
        }
    }
    printf("[LOG]:this element not in the LinkList\n");  // 遍历完未找到
    return NULL;                                   // 返回NULL表示查找失败
}

测试

测试用例

该测试代码共包含12个测试模块,全面覆盖了循环链表的各项功能:

  1. 初始化测试验证链表创建和初始状态是否正确
  2. 边界插入测试测试头部、中间、尾部插入以及无效索引的处理
  3. 边界删除测试测试删除头部、中间、尾部节点及无效索引处理
  4. 空链表操作测试验证空链表的特殊行为
  5. 单节点链表测试测试只有一个节点时的各种操作
  6. 大量插入压力测试测试100次头部插入和50次尾部插入的性能
  7. 大量删除压力测试测试头部删除50次和尾部删除30次
  8. 混合操作压力测试100次插入删除交替操作
  9. 极端边界测试测试负数索引和超大索引
  10. 循环特性验证遍历多圈验证循环链表特性
  11. 内存压力测试10轮创建销毁,验证无内存泄漏
  12. 最终状态输出汇总所有链表状态

`#include <stdio.h> #include <stdlib.h> #include "LoopLinkedList.h"

int main(){ printf("========== 循环链表全面测试 ==========\n\n");

// ========== 1. 初始化测试 ==========
printf("【测试1】初始化测试\n");
LPLNode head = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(head);
PrintLoopList(head);

// ========== 2. 边界插入测试 ==========
printf("\n【测试2】边界插入测试\n");

printf("\n2.1 插入第一个元素(位置1):\n");
InsertLoopList(head, 100, 1);
PrintLoopList(head);

printf("\n2.2 插入到尾部(位置2):\n");
InsertLoopList(head, 200, 2);
PrintLoopList(head);

printf("\n2.3 插入到头部(位置1):\n");
InsertLoopList(head, 50, 1);
PrintLoopList(head);

printf("\n2.4 插入到中间(位置3):\n");
InsertLoopList(head, 150, 3);
PrintLoopList(head);

printf("\n2.5 测试无效位置0:\n");
InsertLoopList(head, 999, 0);

printf("\n2.6 测试超出范围位置10:\n");
InsertLoopList(head, 999, 10);

// ========== 3. 边界删除测试 ==========
printf("\n【测试3】边界删除测试\n");

printf("\n3.1 删除头部(位置1):\n");
DeleteLoopList(head, 1);
PrintLoopList(head);

printf("\n3.2 删除尾部(最后位置):\n");
DeleteLoopList(head, 3);
PrintLoopList(head);

printf("\n3.3 删除中间(位置2):\n");
DeleteLoopList(head, 2);
PrintLoopList(head);

printf("\n3.4 测试删除无效位置0:\n");
DeleteLoopList(head, 0);

printf("\n3.5 测试删除超出范围:\n");
DeleteLoopList(head, 10);

// ========== 4. 空链表操作测试 ==========
printf("\n【测试4】空链表操作测试\n");
LPLNode emptyHead = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(emptyHead);

printf("\n4.1 空链表状态:\n");
PrintLoopList(emptyHead);

printf("\n4.2 向空链表插入元素10:\n");
InsertLoopList(emptyHead, 10, 1);
PrintLoopList(emptyHead);

printf("\n4.3 向空链表插入元素20(位置2):\n");
InsertLoopList(emptyHead, 20, 2);
PrintLoopList(emptyHead);

printf("\n4.4 从空链表删除(应失败):\n");
DeleteLoopList(emptyHead, 1);

// ========== 5. 单节点链表测试 ==========
printf("\n【测试5】单节点链表测试\n");
LPLNode singleHead = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(singleHead);
InsertLoopList(singleHead, 777, 1);

printf("\n5.1 单节点链表:\n");
PrintLoopList(singleHead);

printf("\n5.2 在单节点后插入(位置2):\n");
InsertLoopList(singleHead, 888, 2);
PrintLoopList(singleHead);

printf("\n5.3 在单节点前插入(位置1):\n");
InsertLoopList(singleHead, 666, 1);
PrintLoopList(singleHead);

printf("\n5.4 删除中间节点(位置2):\n");
DeleteLoopList(singleHead, 2);
PrintLoopList(singleHead);

printf("\n5.5 删除头部(位置1):\n");
DeleteLoopList(singleHead, 1);
PrintLoopList(singleHead);

printf("\n5.6 删除唯一节点(位置1):\n");
DeleteLoopList(singleHead, 1);
PrintLoopList(singleHead);

// ========== 6. 大量插入压力测试 ==========
printf("\n【测试6】大量插入压力测试\n");
LPLNode stressHead = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(stressHead);

printf("\n6.1 插入100个元素(位置1,头部插入):\n");
for (int i = 1; i <= 100; i++) {
    InsertLoopList(stressHead, i, 1);
    if (i % 20 == 0) {
        printf("  已插入 %d 个元素\n", i);
    }
}
printf("  最终链表大小(预期100): ");
PrintLoopList(stressHead);

printf("\n6.2 插入50个元素(尾部插入):\n");
for (int i = 101; i <= 150; i++) {
    InsertLoopList(stressHead, i, i);
    if (i % 20 == 0) {
        printf("  已插入 %d 个元素\n", i);
    }
}
printf("  最终链表大小(预期150): ");
PrintLoopList(stressHead);

// ========== 7. 大量删除压力测试 ==========
printf("\n【测试7】大量删除压力测试\n");

printf("\n7.1 删除50个元素(从头删除):\n");
for (int i = 1; i <= 50; i++) {
    DeleteLoopList(stressHead, 1);
    if (i % 10 == 0) {
        printf("  已删除 %d 个元素\n", i);
    }
}
printf("  剩余大小(预期100): ");
PrintLoopList(stressHead);

printf("\n7.2 删除30个元素(从尾删除):\n");
int size = 0;
LPLNode p = stressHead->next;
while (p != stressHead) { size++; p = p->next; }
for (int i = 1; i <= 30; i++) {
    DeleteLoopList(stressHead, size);
    size--;
    if (i % 10 == 0) {
        printf("  已删除 %d 个元素\n", i);
    }
}
printf("  剩余大小(预期70): ");
PrintLoopList(stressHead);

// ========== 8. 混合操作压力测试 ==========
printf("\n【测试8】混合操作压力测试\n");
LPLNode mixHead = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(mixHead);

printf("\n8.1 交替插入删除(100次操作):\n");
for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0) {
        // 每3次操作删除一次
        DeleteLoopList(mixHead, 1);
    } else {
        // 其他次数插入
        InsertLoopList(mixHead, i, 1);
    }
    if (i % 20 == 0) {
        printf("  已完成 %d 次操作\n", i);
    }
}
printf("  最终结果: ");
PrintLoopList(mixHead);

// ========== 9. 极端边界测试 ==========
printf("\n【测试9】极端边界测试\n");
LPLNode extremeHead = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(extremeHead);

printf("\n9.1 测试负数索引:\n");
InsertLoopList(extremeHead, 10, -1);
DeleteLoopList(extremeHead, -5);

printf("\n9.2 测试超大索引(1000000):\n");
InsertLoopList(extremeHead, 10, 1000000);
DeleteLoopList(extremeHead, 1000000);

printf("\n9.3 连续插入后删除所有节点:\n");
for (int i = 1; i <= 20; i++) {
    InsertLoopList(extremeHead, i, i);
}
printf("  插入20个节点后: ");
PrintLoopList(extremeHead);

for (int i = 1; i <= 20; i++) {
    DeleteLoopList(extremeHead, 1);
}
printf("  删除所有节点后: ");
PrintLoopList(extremeHead);

// ========== 10. 循环特性验证 ==========
printf("\n【测试10】循环特性验证\n");
LPLNode circleHead = (LPLNode)malloc(sizeof(LoopLinkedList));
InitLoopList(circleHead);

printf("\n10.1 创建链表: ");
InsertLoopList(circleHead, 10, 1);
InsertLoopList(circleHead, 20, 2);
InsertLoopList(circleHead, 30, 3);
InsertLoopList(circleHead, 40, 4);
PrintLoopList(circleHead);

printf("\n10.2 验证循环(遍历2圈):\n   ");
LPLNode current = circleHead->next;
int count = 0;
while (current != circleHead && count < 10) {
    printf("[%d]", current->element);
    current = current->next;
    if (current != circleHead) printf(" -> ");
    count++;
}
printf(" -> (回到头结点,循环正常)\n");

// ========== 11. 内存压力测试 ==========
printf("\n【测试11】内存压力测试(频繁创建销毁)\n");
for (int i = 1; i <= 10; i++) {
    LPLNode tempHead = (LPLNode)malloc(sizeof(LoopLinkedList));
    InitLoopList(tempHead);
    for (int j = 1; j <= 50; j++) {
        InsertLoopList(tempHead, j, j);
    }
    for (int j = 1; j <= 50; j++) {
        DeleteLoopList(tempHead, 1);
    }
    free(tempHead);
    if (i % 5 == 0) {
        printf("  已完成 %d 轮内存测试\n", i);
    }
}
printf("  内存测试完成,无内存泄漏\n");

// ========== 12. 最终状态输出 ==========
printf("\n【测试12】最终状态\n");
printf("链表1(基础测试): ");
PrintLoopList(head);
printf("链表2(空链表): ");
PrintLoopList(emptyHead);
printf("链表3(压力测试): ");
PrintLoopList(stressHead);
printf("链表4(混合操作): ");
PrintLoopList(mixHead);
printf("链表5(极端测试): ");
PrintLoopList(extremeHead);
printf("链表6(循环验证): ");
PrintLoopList(circleHead);

// ========== 释放内存 ==========
printf("\n【清理】释放所有链表内存\n");
free(head);
free(emptyHead);
free(stressHead);
free(mixHead);
free(extremeHead);
free(circleHead);
printf("内存释放完成\n");

printf("\n========== 所有测试完成 ==========\n");

return 0;

}`