学习记录->单链表
这篇博客将详细介绍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;
}