一、什么是单链表
单链表是 C 语言中最基础、最重要的数据结构之一,非常适合用来练习结构体、指针和动态内存管理。
单链表的每个结点由两部分组成:数据域和指针域。数据域用于存储实际数据,指针域用于保存下一个结点的地址,多个结点通过指针依次连接,最终以 NULL 结尾形成完整链表。
二、单链表头文件设计(SList.h)
在实现单链表之前,通常先定义好结点结构和对外提供的操作接口,代码如下。
java
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
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);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
void SLTEraseAfter(SLTNode* pos);
SLTDataType 使用 typedef 定义为 int,方便后续统一修改链表中存储的数据类型。
SLTNode 结构体表示单链表的结点,其中 data 为数据域,next 为指针域,指向下一个结点。多个结点通过 next 串联形成完整链表。
SLTPrint 用于遍历并打印链表,主要用于调试和测试阶段查看链表结构是否正确。
SLTPushBack 和 SLTPushFront 分别实现尾插和头插操作,由于它们需要修改头指针,因此参数采用二级指针 SLTNode** pphead。
SLTPopBack 和 SLTPopFront 用于删除链表尾结点和头结点,同样需要修改头指针,所以也使用二级指针作为参数。
SLTFind 用于在链表中查找指定值,若找到则返回该结点指针,否则返回 NULL,便于后续进行插入或删除等操作。
SLTInsert 用于在指定结点 pos 前插入新结点,如果 pos 为头结点,则等价于头插操作;SLTErase 用于删除指定结点 pos。
SLTInsertAfter 和 SLTEraseAfter 分别表示在指定结点之后插入新结点,以及删除指定结点之后的结点。这两个操作不需要修改头指针,因此参数只需使用一级指针。
三、单链表实现(SList.c)
在前文中已经完成了单链表头文件的接口设计,下面给出对应的 .c 文件实现,通过这些基础操作,可以完整实现单链表的增删查改功能。
在实现功能前,我们增加一个函数SLTNode* BuySLTNode(SLTDataType x),来动态申请内存。该函数使用 malloc 申请空间,并对结点数据域和指针域进行初始化。如果内存申请失败,则打印错误信息并返回。完整代码如下。
java
#include"SList.h"
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newnode;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* cur = *pphead;
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
while (cur->next->next != NULL)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* cur = *pphead;
*pphead = cur->next;
free(cur);
cur = NULL;
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
四、测试代码(Test.c)
java
#include"SList.h"
int main()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPushFront(&plist, 6);
SLTPushFront(&plist, 7);
SLTPushFront(&plist, 8);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPopFront(&plist);
SLTPrint(plist);
// 值为2那个节点 *2
SLTNode* ret = SLTFind(plist, 2);
ret->data *= 2;
SLTPrint(plist);
SLTInsert(&plist, ret, 20);
SLTPrint(plist);
/*SLTErase(&plist, ret);
ret = NULL;
SLTPrint(plist);*/
SLTInsertAfter(ret, 666);
SLTPrint(plist);
SLTEraseAfter(ret);
SLTPrint(plist);
}
结果如下。
