学习记录->单链表

这篇博客将详细介绍C语言中单链表的实现方法,包括链表的创建、插入、删除和遍历等基本操作,并提供验证代码功能。

什么是单链表?

C语言中的单链表是一种数据结构,它由一系列称为“节点”的元素组成,这些节点依次链接在一起。每个节点都包含两部分信息:

  • 一部分用于存储数据;
  • 另一部分是一个指向列表中下一个节点的引用,通常称之为“指针”。

在C语言中,单链表可以通过结构体(struct)来实现。每个节点都是一个结构体,其中包含数据和一个指向相同结构体类型的指针。链表的开始我们称为“头节点”,它是访问链表的入口。通常情况下,链表的最后一个节点会指向一个空指针(NULL),标志着链表的结束。

以下是一个示例,展示了一个用来存储整数的单链表结构:

graph LR A((头节点 Head)) B[节点1] C[节点2] D[节点3] E((尾节点 Null)) A --> B --> C --> D --> E

单链表结构定义

单链表结构定义->设计说明

  • 基本结构:单链表由节点(Node)组成,每个节点至少包含两部分信息:

    • 数据域:用于存储数据。
    • 指针域:指向列表中下一个节点的指针。
  • 数据类型兼容性:为了使链表具备适应不同数据类型的能力,定义节点的数据域时考虑到了数据类型的兼容性。实践方法之一是:

    • 使用类型定义别名DataType_t。通过声明typedef int DataType_t;,我们初步将数据类型设置为int。不过,这种类型别名的设计允许开发者根据实际存储需求,灵活地修改int为任何其他所需的数据类型,以此适配多样化的数据存储方案。

单链表结构定义->类图

classDiagram class DataType_t { <<typedef>> int } class LinkedList { DataType_t data LinkedList *next } DataType_t -- LinkedList : use ->

单链表结构定义->代码块

// 定义数据类型为int的别名DataType_t,方便后面在节点中存储数据 
typedef int DataType_t;

// 链表节点结构体定义
typedef struct LinkedList {
    DataType_t data; // 节点存储的数据,这里的数据类型是int
    struct LinkedList *next; // 指向下一个节点的指针,用于链表中节点的串联
} LList_t; // 定义了结构体的别名LList_t,简化了结构体类型的使用

链表头节点创建

链表头节点创建->设计说明

  • 创建并初始化链表头节点:

    • 使用calloc进行内存分配以创建头节点,这样可以确保分配的内存区域的初始值为零。
    • calloc初始化内存为0,有助于避免未初始化内存所导致的随机数据问题。
  • 内存分配失败时的错误处理:

    • 对于headNode指针,需要检查是否为NULL,这可以判断内存是否成功分配。
    • 内存分配失败时,使用perror打印错误信息,并通过exit函数退出程序。
  • 初始化头节点:

    • 将新创建的头节点的next指针设置为NULL,表示链表初始状态为空。
  • 返回头节点:

    • 最终,函数返回初始化好的头节点指针,供后续链表操作使用。

链表头节点创建->流程图

graph TD start[开始] --> allocate{分配内存空间} allocate --> check[检查内存分配是否成功] check -- 失败 --> error[内存分配失败输出错误信息退出程序] check -- 成功 --> init[初始化头节点] init --> 返回初始化后的头节点指针

链表头节点创建->代码块

// 创建并初始化链表头节点
LList_t *CreateLinkedListHead(void) {
    // 分配内存空间给头节点,并初始化内存为0以预防未初始化内存导致的随机数据问题
    LList_t *headNode = (LList_t *)calloc(1, sizeof(LList_t));
    
    // 内存分配失败时的错误处理
    if (headNode == NULL) {
        perror("内存分配失败,无法创建头节点!");
        exit(EXIT_FAILURE);
    }

    // 初始化头节点的next指针为空,表明链表为空
    headNode->next = NULL;

    // 返回初始化好的头节点指针
    return headNode;
}

链表节点创建

链表节点创建->设计说明

  • 创建并初始化新的链表节点:

    • 使用calloc为新节点分配内存空间,并自动将内存初始化为0,防止节点字段包含随机值。
  • 内存分配失败时的处理:

    • 检查 element 是否为 NULL 来确认内存是否成功分配。
    • 如分配失败,使用 perror 输出错误信息并返回 NULL
  • 设置新节点存储的数据:

    • 将传入的 value 赋值给新节点的 data 字段。
  • 新节点的next指针初始化:

    • 初始化新节点的 next 指针为 NULL,表示该节点是链表的尾部。
  • 返回新节点:

    • 函数最终将返回初始化好的新节点,准备加入链表中使用。

链表节点创建->流程图

graph TD start[开始] --> allocate{分配内存空间} allocate --> check[检查内存分配是否成功] check -- 失败 --> error[内存分配失败输出错误信息返回空指针] check -- 成功 --> init[初始化新节点] init --> Endpoint[返回初始化后的新节点指针]

链表节点创建->代码块

// 创建并初始化新的链表节点
LList_t *CreateListElement(DataType_t value) {
    // 使用calloc为新节点分配内存空间,并将内存设置为0,确保字段没有随机值
    LList_t *element = (LList_t *)calloc(1, sizeof(LList_t));

    // 内存分配失败时的处理
    if (element == NULL) {
        perror("无法分配内存创建新节点!");
        return NULL;
    }

    // 设置新节点存储的数据
    element->data = value;
    // 新节点的next指针设为空,标志着列表末尾
    element->next = NULL;

    // 返回初始化好的新节点
    return element;
}

插入链表节点

插入链表节点->设计说明

  • 查找插入位置:

    • 确定节点应当插入的具体位置,这可能需要遍历链表直至到达目标点。
  • 修改前置节点的指针:

    • 修改前置节点的next指针,让其指向新插入的节点。
  • 更新新节点的指针:

    • 新节点的next指针需要更新,指向前置节点原本指向的下一个节点。
  • 考虑空链表情况:

    • 如果链表为空,新节点将成为第一个节点,需要更新链表的头指针。
  • 考虑表头插入情况:

    • 如果在链表的起始位置插入,也需要更新链表的头指针。
  • 检查内存分配:

    • 确保为新插入的节点正确分配了内存,防止出现空指针异常。
  • 维持链表完整性:

    • 插入操作完成后,检查链表的连续性,确保所有节点正确连接。
  • 插入链表总流程:

    graph LR A[开始插入节点] --> B[确定插入位置] B --> C{链表是否为空} C -->|是| D[更新头指针指向新节点] C -->|否| E[遍历找到插入点前置节点] E --> F[修改前置节点next指向新节点] F --> G[新节点next指向原先后续节点] D --> H[新节点next指为NULL] G --> I[检查链表完整性] H --> I I --> J[插入完成]

在链表头部插入新节点->流程图

graph TD start((开始)) --> input[输入链表头指针head和新节点的值val] input --> create[创建新节点,并将其值设为val] create --> link[将新节点的next指针指向原链表的起始位置] link --> update[将链表头指针head指向新节点] update --> 结束

在链表头部插入新节点->代码块

//在链表头部插入新节点
bool InsertNodeAtHead(LList_t *listHead, DataType_t dataValue) {
    // 首先检查listHead是否为NULL,如果是,则不能进行插入操作
    if (listHead == NULL) {
        perror("链表头指针为空,无法进行头部插入!");
        return false;
    }
    
    // 创建一个包含数据的新节点
    LList_t *nodeToInsert = CreateListElement(dataValue);
    // 如果新节点创建失败,则返回false
    if (nodeToInsert == NULL) {
        perror("无法在头部插入新节点,节点创建失败!");
        return false;
    }
    
    // 将新节点插入到链表头部
    nodeToInsert->next = listHead->next;
    listHead->next = nodeToInsert;
    return true;
}

在链表尾部插入新节点->流程图

graph TD start((开始)) --> input[输入链表头指针head和新节点的值val] input --> create[创建新节点,并将其值设为val] create --> check[检查当前节点是否为尾部节点] check -- 是 --> search[搜索链表尾部节点] search --> insert[将尾部节点的next指针指向新节点] insert --> 结束 check -- 否 --> move[移动到下一节点] move --> check

在链表尾部插入新节点->代码块

//在链表尾部插入新节点
bool InsertNodeAtTail(LList_t *listHead, DataType_t dataValue) {
    // 首先检查listHead是否为NULL,如果是,则不能进行插入操作
    if (listHead == NULL) {
        perror("链表头指针为空,无法进行尾部插入!");
        return false;
    }
    
    // 创建一个包含数据的新节点
    LList_t *nodeToInsert = CreateListElement(dataValue);
    // 如果新节点创建失败,则返回false
    if (nodeToInsert == NULL) {
        perror("无法在尾部插入新节点,节点创建失败!");
        return false;
    }
    
    // 遍历链表找到最后一个节点
    LList_t *currentNode = listHead;
    while (currentNode->next) {
        currentNode = currentNode->next;
    }
    
    // 在链表尾部插入新节点
    currentNode->next = nodeToInsert;
    return true;
}

指定数据的节点后插入新节点->流程图

graph TD start((开始)) --> input[输入链表头指针head,指定数据value和新节点的值newValue] input --> search[查找链表中指定数据为value的节点] search -- 存在 --> insert[创建新节点并将其值设为newValue] insert --> next[将新节点的next指针指向找到的节点的下一个节点] next --> update[更新找到的节点的next指针指向新节点] update --> 结束 search -- 不存在 --> notFound[未找到指定数据的节点操作失败] notFound --> 结束

指定数据的节点后插入新节点->代码块

//在指定数据的节点后插入新节点
bool InsertNodeAfterData(LList_t *listHead, DataType_t targetData, DataType_t dataValue) {
    // 首先检查listHead是否为NULL,如果是,则不能进行插入操作
    if (listHead == NULL) {
        printf("链表头指针为空,无法在指定数据后插入新节点!\n");
        return false;
    }

    // 创建一个包含数据的新节点
    LList_t *nodeToInsert = CreateListElement(dataValue);
    // 如果新节点创建失败,则返回false
    if (nodeToInsert == NULL) {
        printf("无法在指定的数据后插入新节点,节点创建失败!\n");
        return false;
    }
    
    // 查找含有目标数据的节点
    LList_t *currentNode = listHead;
    while (currentNode && currentNode->data != targetData) {
        currentNode = currentNode->next;
    }
    
    // 如果找到含有目标数据的节点,则在其后插入新节点
    if (currentNode) {
        nodeToInsert->next = currentNode->next;
        currentNode->next = nodeToInsert;
    } else { // 如果未找到含有目标数据的节点,释放新节点内存并返回false
        free(nodeToInsert);
        return false;
    }
    
    return true;
}

删除链表节点

删除链表节点->设计说明

  • 确定目标节点:

    • 先找到需要删除的节点,通常需要遍历链表直到找到目标节点。
  • 寻找前置节点:

    • 定位目标节点的前一个节点,以便于后续调整指针。
  • 调整指针关系:

    • 如果目标节点不是头节点,则需要将其前置节点的next指针指向目标节点的下一个节点。
  • 删除头节点特殊处理:

    • 如果删除的是头节点,需要特别处理,将头指针指向下一个节点。
  • 释放节点内存:

    • 删除节点后,应当释放其内存资源,以免造成内存泄露。
  • 考虑只有一个节点的链表:

    • 如果链表只有一个节点且正被删除,则需要将头指针置为NULL
  • 确保链表的连贯性:

    • 调整指针并释放内存后,确保链表的其他节点依然正确连结。
  • 注意链表为空的情况:

    • 如果链表为空,则无需任何操作,直接返回。
  • 删除链表节点总流程:

    graph LR A[开始删除节点] --> B{链表是否为空} B -->|是| B1[没有操作可执行] B -->|否| C{节点是否为头节点} C -->|是| D[头指针指向下一个节点] C -->|否| E[找到目标节点前置节点] E --> F[前置节点next指向目标节点的next] D --> G[释放目标节点内存] F --> G G --> H[检查链表完整性] H --> I[删除完成]

### 删除链表的头部节点->流程图

```mermaid
graph TD
   start((开始)) --> input[输入链表头指针head]
   input --> cond{head是否为空?}
   cond -- 是 --> empty[链表为空,无法删除头部节点]
   cond -- 否 --> remove(删除头部节点)
   remove --> next[将head指针移动到下一个节点]
   next --> free(释放原头部节点的内存空间)
   free --> 结束
   empty --> 结束

删除链表的头部节点->代码块

// 删除链表的头部节点
bool DeleteHeadNode(LList_t *listHead) {
    // 检查链表头指针是否为NULL
    if (listHead == NULL) {
        printf("删除失败,链表不存在!\n");
        return false;
    }
    // 检查链表是否为空(即是否有节点可以删除)
    if (listHead->next == NULL) {
        printf("删除失败,链表为空,没有节点可以删除!\n");
        return false;
    }

    LList_t *nodeToDelete = listHead->next;
    listHead->next = nodeToDelete->next;
    free(nodeToDelete);

    return true;
}

删除链表的尾部节点->流程图

graph TD start((开始)) --> input[输入链表头指针head] input --> cond{head是否为空? } cond -- 是 --> empty[链表为空, 无需删除] cond -- 否 --> traverse(遍历链表找到尾部节点prev) traverse --> |遍历| loop{当前节点的下一节点是否为空? } loop -- 是 --> remove(删除尾部节点) loop -- 否 --> next[下一节点不为空, 移动到下一节点] --> loop remove --> prev[将尾部节点的前一节点的next指针指向null] prev --> 结束 empty --> 结束

删除链表的尾部节点->代码块

// 删除链表的尾部节点
bool DeleteTailNode(LList_t *listHead) {
    // 检查链表头指针是否为NULL
    if (listHead == NULL) {
        printf("删除失败,链表不存在!\n");
        return false;
    }
    // 检查链表是否为空(即是否有节点可以删除)
    if (listHead->next == NULL) {
        printf("删除失败,链表为空,没有节点可以删除!\n");
        return false;
    }

    LList_t *currentNode = listHead;
    LList_t *previousNode = NULL;

    while (currentNode->next) {
        previousNode = currentNode;
        currentNode = currentNode->next;
    }
    if (previousNode) previousNode->next = NULL;
    free(currentNode);

    return true;
}

删除链表中指定值的的节点->流程图

graph TD start((开始)) --> input[输入链表头指针head和要删除的值val] input --> cond{head是否为空?} cond -- 是 --> empty[链表为空,无法删除] cond -- 否 --> traverse(遍历链表找到要删除节点prev) traverse --> |遍历| loop{当前节点值是否为val?} loop -- 是 --> found[找到匹配值,记录前驱节点prev] found --> remove(删除节点) remove --> prev[将前驱节点的next指针指向要删除节点的下一节点] prev --> free(释放要删除节点的内存空间) free --> 结束 loop -- 否 --> next[继续遍历下一节点] next --> traverse empty --> 结束

删除链表中指定值的的节点->代码块

//删除链表中指定值的的节点
bool DeleteNodeByValue(LList_t *listHead, DataType_t targetValue) {
    // 检查链表头指针是否为NULL
    if (listHead == NULL) {
        printf("删除失败,链表不存在!\n");
        return false;
    }
    // 检查链表是否为空(即是否有节点可以删除)
    if (listHead->next == NULL) {
        printf("删除失败,链表为空,没有节点可以删除!\n");
        return false;
    }

    LList_t *currentNode = listHead->next;
    LList_t *previousNode = listHead;

    while (currentNode != NULL && currentNode->data != targetValue) {
        previousNode = currentNode;
        currentNode = currentNode->next;
    }
    if (currentNode != NULL && currentNode->data == targetValue) {
        previousNode->next = currentNode->next;
        free(currentNode);
        return true;
    } else {
        printf("没有找到包含数据 %d 的节点。\n", targetValue);
        return false;
    }
}

删除最小节点

删除最小节点->设计说明

  • 遍历寻找最小节点:

    • 首先,需要遍历整个链表以确定最小的节点。
    • 维护一个指向当前最小节点的指针以及其前驱节点的指针,以便于后续删除。
  • 考虑空链表情况:

    • 如果链表为空(即链表的头节点指针为NULL),则没有节点可以删除。
    • 应当返回错误或特定的提示。
  • 处理头节点为最小节点的情况:

    • 如果最小的节点恰好是链表的头节点,则需要特殊处理。
    • 因为头节点的删除会改变链表的头指针。
  • 更新链表链接:

    • 确定了最小节点及其前驱节点后,需要正确更新链表中的链接。
    • 确保链表的连续性不被破坏。
  • 释放内存:

    • 在删除节点后,不要忘记释放该节点占用的内存空间。
    • 避免内存泄漏。

删除最小节点->流程图

graph TB Start(开始) --> CheckEmpty{链表是否为空? } CheckEmpty -- 是 --> Exit[退出] CheckEmpty -- 否 --> Assign[初始化节点和最小值] Assign --> Traverse{遍历链表} Traverse -- 完成 --> CheckMin{找到最小节点? } CheckMin -- 否 --> Traverse CheckMin -- 是 --> Delete[删除最小节点] Delete --> Free[释放内存] Free --> Endpoint[结束] Exit --> Endpoint

删除最小节点->代码块

// 删除单链表中的最小值节点
void DeleteMinNode(LListNode *head) {
    // 检查链表是否为空,如果为空,则不进行任何操作
    if (head == NULL) {
        printf("链表不存在。\n");
        return;
    }
    
    // 检查链表中是否只有头节点,如果是,则没有节点可以删除
    if (head->next == NULL) {
        printf("链表为空,没有最小节点可以删除。\n");
        return;
    }
    
    LListNode *current = head->next;      // 用于遍历的当前节点
    LListNode *prev = head;               // 当前节点的前一个节点
    LListNode *minNode = current;         // 假设最初的最小节点是第一个节点
    LListNode *minPrev = prev;            // 最小节点的前一个节点
    
    // 寻找最小值节点
    while (current != NULL) {
        if (current->data < minNode->data) {
            // 发现新的最小值节点
            minNode = current;
            minPrev = prev;
        }
        prev = current;                  // 更新前一个节点
        current = current->next;         // 移动到下一个节点
    }
    
    // 删除最小节点
    minPrev->next = minNode->next;       // 将最小节点的前一个节点连接到最小节点的下一个节点
    free(minNode);                       // 释放最小节点占用的内存
}

搜索元素节点

搜索元素节点->设计说明

  • 检查空链表:

    • 在开始搜索之前,应验证链表是否为空。空链表意味着没有节点可供搜索,因此应立即返回NULL或相应的搜索失败指示。
  • 迭代访问:

    • 必须通过链表的所有节点进行迭代,直到找到具有特定值的节点或到达链表的末尾。
  • 比较节点值:

    • 在迭代过程中,您需要比较每个节点的data与搜索值。
  • 返回节点指针:

    • 如果找到具有搜索值的节点,应返回该节点的指针。否则,如果遍历整个链表也没有找到,应返回NULL。

搜索元素节点->流程图

graph TB Start(开始) -->|检查listHead是否为NULL| IsListHeadNull{链表是否为空?} IsListHeadNull -- 是 --> Exit[返回NULL] IsListHeadNull -- 否 --> Initialize[初始化当前节点为链表头] Initialize --> Loop{是否到达链表末尾?} Loop -- 是 --> NotFound[返回NULL] Loop -- 否 --> IsValueMatch{当前节点数据是否等于searchValue?} IsValueMatch -- 是 --> Found[返回当前节点] IsValueMatch -- 否 --> NextNode[移动到下一个节点] NextNode --> Loop Found --> Endpoint[结束] NotFound --> Endpoint

搜索元素节点->代码块

// 搜索链表中是否存在具有特定值的节点
LList_t *SearchValue(LList_t *listHead, DataType_t searchValue) {
    // 检查链表头指针是否为NULL
    if (listHead == NULL) {
        return NULL; // 链表不存在
    }

    LList_t *currentNode = listHead;
    // 遍历链表查找值
    while (currentNode != NULL) {
        if (currentNode->data == searchValue) {
            return currentNode; // 找到了包含搜索值的节点
        }
        currentNode = currentNode->next;
    }
    return NULL; // 没有找到包含搜索值的节点
}

获取链表中倒数第k个节点的值

获取链表中倒数第k个节点的值->设计说明

  • 特殊情况处理: 链表为空时,即没有节点可供操作。
  • 输入有效性: k的值是否在合理的范围内,即不应该大于链表的长度。
  • 单次遍历实现: 使用快慢指针技巧,快指针先走k步,然后快慢指针一起走,当快指针到达尾部时,慢指针正好在倒数第k个节点。
  • 边界条件: 如果k值等于链表的长度,则返回的就是链表的首节点。
  • 内存管理: 如果语言环境下使用手动内存管理,应确保不会因为操作节点而破坏链表结构或引发内存泄漏。

获取链表中倒数第k个节点的值->流程图

graph TB Start(开始) -->|初始化ptr1和ptr2为listHead| Initialize[初始化指针] Initialize --> CountLoop{计数器小于k} CountLoop -- 是 --> MovePtr2[移动ptr2] MovePtr2 --> CheckPtr2Null{ptr2是否为空?} CheckPtr2Null -- 是 --> ReturnNull[返回NULL] CheckPtr2Null -- 否 --> IncrementCount[计数器加1] IncrementCount --> CountLoop CountLoop -- 否 --> CheckCountLessThanK{计数器小于k?} CheckCountLessThanK -- 是 --> ReturnNull CheckCountLessThanK -- 否 --> MoveBothPtrs{移动ptr1和ptr2} MoveBothPtrs --> CheckPtr2AtEnd{ptr2是否到达尾部?} CheckPtr2AtEnd -- 否 --> MoveBothPtrs CheckPtr2AtEnd -- 是 --> ReturnPtr1[返回ptr1]

获取链表中倒数第k个节点的值->代码块

// 查找链表中倒数第k个节点的值
LList_t *FindKthToLast(LList_t *listHead, int k) {
    LList_t *ptr1 = listHead;
    LList_t *ptr2 = listHead;
    int count = 0;
    
    // 移动ptr2,使得ptr1和ptr2之间相隔k个节点
    while (ptr2 != NULL && count < k) {
        ptr2 = ptr2->next;
        count++;
    }
    // 若链表长度小于k,则返回NULL
    if (count < k) return NULL;
    
    // 移动ptr1和ptr2直到ptr2到达链表末尾
    while (ptr2 != NULL) {
        ptr1 = ptr1->next;
        ptr2 = ptr2->next;
    }
    // 此时ptr1指向倒数第k个节点
    return ptr1;
}

遍历链表

遍历链表->设计说明

  • 检查空链表: 在开始遍历之前,确认链表不为空。
  • 循环终止条件: 确保循环的终止条件是访问到链表的末尾(通常是判断节点的next指针是否为NULL)。
  • 内存访问: 当遍历链表时,确保不会访问未定义的内存,比如越界访问节点的next指针。
  • 循环控制变量: 应正确使用循环控制变量,防止出现死循环的情况。

遍历链表->流程图

graph TB Start(开始) -->|检查链表不为空| IsEmpty{链表是否为空?} IsEmpty -- 是 --> PrintEmpty[打印''链表为空''] IsEmpty -- 否 --> SetCurrentNode[设置当前节点为listHead] SetCurrentNode --> TraverseLoop{当前节点是否为空?} TraverseLoop -- 否 --> PrintData[打印当前节点数据] PrintData --> MoveToNext[当前节点移至下一节点] MoveToNext --> TraverseLoop TraverseLoop -- 是 --> Endpoint[结束遍历]

遍历链表->代码块

// 遍历并打印链表中的所有值
void TraverseList(LList_t *listHead) {
    // 检查链表是否为空
    if (listHead == NULL) {
        printf("链表为空。\n");
        return;
    }
    
    LList_t *currentNode = listHead;
    // 逐节点打印数据
    while (currentNode != NULL) {
        printf("%d ", currentNode->data);
        currentNode = currentNode->next;
    }
    printf("\n");
}

逆转链表

逆转链表->设计说明

  • 检查空链表: 在开始逆转之前,确认链表不为空。
  • 更新指针: 正确更新节点的next指针,以防断链。
  • 临时节点: 使用临时节点存储下一个节点,以防在修改指针时丢失原始链表的其余部分。
  • 链表头: 更新链表头指针,以确保它指向新的第一个节点,即原链表的最后一个节点。
  • 内存管理: 如果使用了动态分配的节点,在逆转链表的过程中应确保不会造成内存泄露。
  • 测试: 在逆转链表后,确保进行测试以验证链表的所有节点都正确逆转,且没有丢失或重复的节点。

逆转链表->流程图

graph TB Start(开始逆转链表) -->|初始化prevNode为NULL| SetPrevNode SetPrevNode -->|设置currentNode为listHead| SetCurrentNode SetCurrentNode --> Loop{currentNode是否为空?} Loop -- 否 --> SaveNextNode[保存nextNode为currentNode.next] SaveNextNode --> ReversePointer[逆转当前节点的指针] ReversePointer --> UpdatePrevNode[更新prevNode为当前节点] UpdatePrevNode --> MoveToNext[当前节点移至nextNode] MoveToNext --> Loop Loop -- 是 --> Endpoint[结束逆转, 返回新头节点prevNode]

逆转链表->代码块

// 逆转整个链表
LList_t *ReverseList(LList_t *listHead) {
    LList_t *prevNode = NULL;
    LList_t *currentNode = listHead;
    LList_t *nextNode = NULL;
    
    while (currentNode != NULL) {
        nextNode = currentNode->next;   // 保存下一个节点
        currentNode->next = prevNode;   // 逆转当前节点的指针
        prevNode = currentNode;         // 更新prevNode以供下次迭代使用
        currentNode = nextNode;         // 移动到下一个节点
    }
    return prevNode; // 反转后链表的新头节点
}

清空链表

清空链表->设计说明

  • 内存管理:
    • 在删除节点时正确处理内存释放,避免内存泄漏。
  • 更新头节点:
    • 确保在清空链表后,更新链表的头节点指针为NULL。
  • 迭代删除:
    • 逐个遍历节点并删除,直至链表为空。
  • 数据清理:
    • 如果节点存储的是指针类型数据,则需要专门进行数据清理以避免数据泄露。

清空链表->流程图

graph LR Start(开始) --> |取得链表头节点| Node1(当前节点) Node1 --> |下一节点存在| Node2(下一节点) Node2 --> |释放当前节点| FreeNode(释放节点内存) FreeNode --> |当前节点赋值为下一节点| Node1 Node1 --> |无更多节点| Endpoint([更新链表头为NULL, 链表清空完毕])

清空链表->代码块

// 清空整个链表,释放所有节点的内存
void ClearList(LList_t **listHead) {
    LList_t *currentNode = *listHead;
    LList_t *tempNode;

    while (currentNode != NULL) {
        tempNode = currentNode->next; // 保存下一个节点
        free(currentNode);            // 释放当前节点的内存
        currentNode = tempNode;       // 移动到下一个节点
    }
    
    *listHead = NULL; // 更新链表头指针为空,以表明链表已被清空
}

测试函数

// 测试函数
void testLinkedListOperations() {
    // 初始化链表
    ListNode *list = initLinkedList();
    
    // 插入元素
    insertNode(&list, 4);
    insertNode(&list, 2);
    insertNode(&list, 1);
    insertNode(&list, 3);
    printf("初始链表: ");
    printLinkedList(list);
    
    // 删除指定元素
    deleteNode(&list, 2);
    printf("删除值2后的链表: ");
    printLinkedList(list);
    
    // 删除最小节点
    deleteMinNode(list);
    printf("删除最小节点后的链表: ");
    printLinkedList(list);
    
    // 搜索元素
    int valueToSearch = 3;
    ListNode *searchedNode = searchNode(list, valueToSearch);
    if(searchedNode) {
        printf("元素 %d 在链表中被找到。\n", valueToSearch);
    } else {
        printf("元素 %d 在链表中没有被找到。\n", valueToSearch);
    }
    
    // 查找倒数第 n 个元素
    int n = 2;
    ListNode *nthNodeFromEnd = findNthFromEnd(list, n);
    if(nthNodeFromEnd) {
        printf("倒数第 %d 个节点的值是 %d。\n", n, nthNodeFromEnd->data);
    } else {
        printf("链表中少于 %d 个元素。\n", n);
    }
    
    // 逆转链表
    reverseLinkedList(&list);
    printf("链表逆转后: ");
    printLinkedList(list);
    
    // 清空链表
    clearLinkedList(&list);
    printf("清空链表后: ");
    printLinkedList(list);
}

// 主函数
int main() {
    testLinkedListOperations();
    return 0;
}
posted @ 2024-04-23 02:03  {^-^}  阅读(12)  评论(0编辑  收藏  举报