目录
一、前言
链表是一个强大且基础的数据结构。对于很多初学者来说,链表可能是一个令人望而生畏的主题,因为它涉及到了动态内存分配和指针操作等较为高级的概念。但是,一旦你掌握了链表的基本概念和操作,你会发现它在很多实际应用中都有着广泛的应用。
今天,我们就从C语言的角度,来探索一下单链表的基本概念和实现方法。在接下来的篇幅中,我将带你逐步构建一个单链表的简单实现,并解释其中的关键概念和代码逻辑。
首先,我们需要明确什么是单链表。单链表是一种线性数据结构,它的元素(在C语言中通常称为节点)按照线性的顺序排列,但每个节点并不是在内存中连续存储的。相反,每个节点都包含两个部分:一个是数据域(用于存储实际的数据),另一个是指针域(用于指向下一个节点)。通过这种方式,我们可以将多个节点连接起来,形成一个链状的数据结构。
单链表的定义:
cpp
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
//
}SLTNode;
二、实现链表的功能:
cpp
//打印
void SLTPrint(SLTNode* phead);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
打印
cpp
void SLTPrint(SLTNode* phead)
{
SLTNode* pucr = phead;
while (pucr != NULL)
{
printf("%d->", pucr->data);
pucr = pucr->next;
}
printf("NULL\n");
}
创建节点
cpp
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
if (newNode == NULL)
{
perror("malloc fail!");
exit(1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
尾插
cpp
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newNode = SLTBuyNode(x);
// 如果链表为空,直接让头指针指向新节点
if (*pphead == NULL) {
*pphead = newNode;
return;
}
// 否则,找到链表尾部并插入新节点
SLTNode* current = *pphead;
while (current->next != NULL)
{
current = current->next;
}
current->next = newNode;
}
尾删
cpp
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* prev = NULL;
SLTNode* current = *pphead;
while (current->next != NULL)
{
prev = current;
current = current->next;
}
free(current);
if (prev == NULL)
{
*pphead = NULL;
}
else
{
prev->next = NULL;
}
}
首先,检查传入的二级指针和其指向的头指针是否为空,以确保链表存在。
接着,定义了两个指针prev和current,用于遍历链表并找到尾节点。
在循环中,prev总是指向current的前一个节点,而current则遍历链表直到找到尾节点(即current->next为NULL的节点)。
找到尾节点后,释放其内存。
最后,根据prev是否为空来判断链表是否只有一个节点。如果是,则将头指针置为NULL;否则,将prev的next指针置为NULL,完成尾节点的删除。
头插
cpp
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newNode = SLTBuyNode(x);
newNode->next = *pphead;
*pphead = newNode;
}
我们首先通过断言确保传入的二级指针pphead不为空。接着,我们调用SLTBuyNode函数来根据给定的数据x创建一个新的节点newNode。然后,我们将新节点的next指针设置为当前的头节点*pphead,这样新节点就连接到了链表的开始位置。最后,我们更新头指针*pphead,使其指向新节点,从而完成了在链表头部插入新节点的操作。
头删
cpp
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* tmp = (*pphead)->next;
free(*pphead);
*pphead=tmp;
}
定义一个临时指针tmp,让它指向当前头节点的下一个节点。接着,我们释放当前头节点的内存。最后,我们更新头指针*pphead,使其指向tmp, 从而完成了头节点的删除操作。如果链表原本只有一个节点,那么删除后头指针*pphead将被设置为NULL,表示链表为空。
查找
cpp
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
使用一个指针pcur来遍历链表,从头节点开始,逐个检查每个节点的数据是否与要查找的数据x相等。如果找到了匹配的节点,就返回该节点的指针;如果遍历完整个链表都没有找到匹配的节点,则返回NULL表示未找到。
在指定位置之前插入数据
cpp
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
// 找到pos节点的前一个节点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
// 在pos之前插入新节点
newnode->next = pos;
prev->next = newnode;
}
}
指定位置删除
cpp
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
*pphead = pos->next;
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)// 遍历链表,找到要删除节点的前一个节点
{
prev = prev->next;
}
prev->next = pos->next;
}
}
在指定位置之后插入数据
cpp
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
设置新节点的next指针指向pos的下一个节点。最后,它将pos的next指针指向新节点,从而将新节点插入到pos之后。
打印
cpp
//打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
销毁
cpp
//销毁
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur != NULL)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
三、全部源码:
cpp
// 打印链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pucr = phead;
while (pucr != NULL)
{
printf("%d->", pucr->data);
pucr = pucr->next;
}
printf("NULL\n");
}
//初始化
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
if (newNode == NULL)
{
perror("malloc fail!");
exit(1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
// 尾部插入
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newNode = SLTBuyNode(x);
// 如果链表为空,直接让头指针指向新节点
if (*pphead == NULL) {
*pphead = newNode;
return;
}
// 否则,找到链表尾部并插入新节点
SLTNode* current = *pphead;
while (current->next != NULL)
{
current = current->next;
}
current->next = newNode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* prev = NULL;
SLTNode* current = *pphead;
while (current->next != NULL)
{
prev = current;
current = current->next;
}
free(current);
if (prev == NULL)
{
*pphead = NULL;
}
else
{
prev->next = NULL;
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newNode = SLTBuyNode(x);
newNode->next = *pphead;
*pphead = newNode;
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* tmp = (*pphead)->next;
free(*pphead);
*pphead=tmp;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
// 找到pos节点的前一个节点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
// 在pos之前插入新节点
newnode->next = pos;
prev->next = newnode;
}
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
*pphead = pos->next;
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos&&pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur != NULL)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
四、结语
总之,C语言中的单链表之旅是一次富有成效的学习经历。它不仅让我们掌握了链表的基本操作,还提高了我们的编程技能和问题解决能力。希望这个旅程能为你在数据结构和算法领域的进一步探索打下坚实的基础。