在编程的世界里,数据结构是构建高效程序的核心基石之一。今天,我们将通过C语言实现一个简单的单链表,并探索其基本操作。从节点的创建到链表的遍历,从插入到删除,我们将一步步揭开单链表的神秘面纱。
一、单链表是什么?
单链表是一种线性数据结构,其中每个元素(称为节点)包含两部分:数据和指向下一个节点的指针。这种结构使得插入和删除操作非常高效,因为它们只需要修改指针,而不需要移动其他元素。
二、代码解析
(一)节点的创建
在我们的代码中,CLBuyNode 函数负责创建一个新的节点。它接收一个整数 x,并将其存储在节点的数据部分。如果内存分配失败,它会打印错误信息并返回 NULL。
CL* CLBuyNode(int x)
{
CL* New = (CL*)malloc(sizeof(CL));
if (New == NULL)
{
perror("malloc fail");
return NULL;
}
New->next = NULL;
New->data = x;
return New;
}
(二)链表的遍历
CLPrint 函数用于打印链表中的所有元素。它从头节点开始,依次访问每个节点,直到遇到 NULL,表示链表结束。
void CLPrint(CL* phead)
{
CL* cur = phead;
while(cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
(三)链表的插入操作
- 尾部插入
CLPushBack 函数将一个新节点插入到链表的尾部。如果链表为空,新节点直接成为头节点;否则,它会遍历链表,找到最后一个节点,并将新节点链接到其后面。
void CLPushBack(CL** pphead, int x)
{
assert(pphead);
CL* New = CLBuyNode(x);
if (*pphead == NULL)
{
*pphead = New;
}
else
{
CL* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = New;
}
}
- 头部插入
CLPushFront 函数将一个新节点插入到链表的头部。它将新节点的 next 指针指向当前的头节点,然后更新头指针。
void CLPushFront(CL** pphead, int x)
{
assert(pphead);
CL* New = CLBuyNode(x);
New->next = *pphead;
*pphead = New;
}
(四)链表的删除操作
- 删除尾部节点
CLPopBack 函数用于删除链表的尾部节点。它需要找到倒数第二个节点,并将其 next 指针设置为 NULL,然后释放尾部节点的内存。
void CLPopBack(CL** pphead)
{
assert(pphead);
assert(*pphead);
CL* cur = *pphead;
if (cur->next == NULL)
{
free(cur);
cur = NULL;
*pphead = NULL;
return;
}
while (cur->next->next)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
- 删除头部节点
CLPopFront 函数用于删除链表的头部节点。它将头指针移动到下一个节点,然后释放原头节点的内存。
void CLPopFront(CL** pphead)
{
assert(pphead);
assert(*pphead);
CL* cur = *pphead;
*pphead = cur->next;
free(cur);
cur = NULL;
}
(五)查找与插入
CLFind 函数用于在链表中查找一个特定值的节点。如果找到,返回该节点的指针;否则返回 NULL。
CL* CLFind(CL* phead, int x)
{
CL* cur = phead;
while (cur)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return cur;
}
CLInsert 函数允许我们在链表的指定位置插入一个新节点。它会找到目标位置的前一个节点,并将新节点插入其中。
void CLInsert(CL** pphead, CL* pos, int x)
{
assert(pos);
assert(pphead);
CL* New = CLBuyNode(x);
New->next = pos;
CL* cur = *pphead;
if (pos == cur)
{
*pphead = New;
return;
}
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = New;
}
(六)删除指定节点
CLErase 函数用于删除链表中的指定节点。它需要找到目标节点的前一个节点,并将其 next 指针跳过目标节点,然后释放目标节点的内存。
void CLErase(CL** pphead, CL* pos)
{
assert(pos);
assert(pphead);
CL* cur = *pphead;
if (pos == *pphead)
{
*pphead = cur->next;
free(cur);
return;
}
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
(七)在指定节点后插入或删除
CLInsertAfter 和 CLEraseAfter 函数分别用于在指定节点后插入一个新节点和删除指定节点后的节点。这些操作非常高效,因为它们不需要遍历整个链表。
void CLInsertAfter(CL* pos, int x)
{
assert(pos);
CL* New = CLBuyNode(x);
New->next = pos->next;
pos->next = New;
}
void CLEraseAfter(CL* pos)
{
assert(pos);
assert(pos->next);
CL* del = pos->next;
pos->next = pos->next->next;
free(del);
del == NULL;
}
三、测试代码
为了验证我们的链表操作是否正确,我们编写了两个测试函数 test1 和 test2。
(一)test1
test1 函数展示了如何使用 CLPushBack、CLPushFront、CLPopBack 和 CLPopFront 操作链表。
void test1()
{
CL* plist = NULL;
CLPushBack(&plist, 0);
CLPushBack(&plist, 1);
CLPushBack(&plist, 2);
CLPushBack(&plist, 3);
CLPopFront(&plist);
CLPushFront(&plist, 2);
CLPushFront(&plist, 5);
CLPopBack(&plist);
CLPopBack(&plist);
CLPrint(plist);
}
(二)test2
test2 函数进一步展示了如何使用 CLFind、CLInsert、CLErase、CLInsertAfter 和 CLEraseAfter 操作链表。
void test2()
{
CL* plist = NULL;
CLPushBack(&plist, 0);
CLPushBack(&plist, 1);
CLPushBack(&plist, 2);
CLPushBack(&plist, 3);
CLPopFront(&plist);
CLPushFront(&plist, 2);
CLPushFront(&plist, 5);
CLPopBack(&plist);
CLPopBack(&plist);
CLPrint(plist);
CL* pos = CLFind(plist, 2);
CLInsert(&plist, pos, 3);
CLPrint(plist);
CLErase(&plist, pos);
pos = CLFind(plist, 5);
CLPrint(plist);
CLEraseAfter(pos);
CLPrint(plist);
CLInsertAfter(pos, 2);
CLPrint(plist);
}

这是最后的答案,有彩蛋哦
四、总结
通过上述代码,我们成功实现了一个功能完善的单链表。从节点的创建到链表的遍历,从插入到删除,每一步都清晰明了。单链表作为一种灵活的数据结构,非常适合处理动态数据集合。希望这篇文章能帮助你更好地理解单链表的实现和应用。
在编程的世界里,数据结构是我们解决问题的得力助手。掌握它们,你将能够更高效地构建复杂的应用程序。如果你对单链表还有其他问题,欢迎在评论区留言,我们一起探讨!
希望这篇文章对你有所帮助!如果你喜欢,请点赞、分享并关注我,获取

1870

被折叠的 条评论
为什么被折叠?



