目录
[1 链表的概念及结构](#1 链表的概念及结构)
[2 链表的增删改查实现](#2 链表的增删改查实现)
[2.1 定义节点的结构](#2.1 定义节点的结构)
[2.2 申请一个新节点](#2.2 申请一个新节点)
[2.3 链表打印](#2.3 链表打印)
[2.4 链表的尾插](#2.4 链表的尾插)
[2.5 链表的头插](#2.5 链表的头插)
[2.6 链表的尾删](#2.6 链表的尾删)
[2.7 链表的头删](#2.7 链表的头删)
[2.8 链表数据的查找](#2.8 链表数据的查找)
[2.9 在指定位置之前插入数据](#2.9 在指定位置之前插入数据)
[2.10 在指定位置之后插入数据](#2.10 在指定位置之后插入数据)
[2.11 删除指定位置的节点](#2.11 删除指定位置的节点)
[2.12 删除pos之后的节点](#2.12 删除pos之后的节点)
[2.13 链表的销毁](#2.13 链表的销毁)
[3 链表实现完整代码](#3 链表实现完整代码)
[3.1 SList.h文件](#3.1 SList.h文件)
[3.2 SList.c文件](#3.2 SList.c文件)
[3.3 test.c文件](#3.3 test.c文件)
1 链表的概念及结构
链表是一种物理存储上非连续 、非顺序的存储结构。数据元素的逻辑顺序 是通过链表中的指针链接次序 实现的。链表是线性表的一种。链表在物理结构上(实际)不是线性的,逻辑结构上(逻辑层面)是线性的。
与顺序表不同的是,链表里的每个"模块"都是独立申请下来的空间,我们称之为"节点"。
节点有两个组成部分:当前节点要保存的数据(1、2、3...)和保存下一个节点的地址(指针变量)。图中指针变量plist保存的是第一个节点的地址,我们称plist此时指向第一个节点,如果我们希望plist指向第二个节点时,只需要修改plist保存的内容为0x0012FFA0。每一个节点都是独立申请的,我们需要通过指针变量来保存下一个节点的位置,才能从当前节点找到下一个节点。
2 链表的增删改查实现
2.1 定义节点的结构
根据结构体来定义节点的结构:
//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;//方便修改数据类型
typedef struct SListNode//定义节点
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
假设当我们想要通过链表来保存整型数据时,实际是先向操作系统申请一块内存,这块内存不仅要保存整型数据,也需要保存下一个节点的地址。
2.2 申请一个新节点
//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
当我们申请一个新节点时,利用malloc去开辟一个节点大小的空间,然后将数据赋值为x,当前节点储存下一个节点的地址初始化为NULL。
2.3 链表打印

打印链表的所有数据时,定义一个临时变量pcur来储存链表头节点的地址,然后打印当前节点的数据 ,然后走到下一个节点,直到打印完为止,代码如下:
//链表打印
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
2.4 链表的尾插
链表的尾插就是在当前链表最后一个节点处插入一个新的节点。

尾插函数的参数要进行传址调用,原因在于要实际改变链表的数据时,就需要传地址。想要改变节点,就要创建一个结构体指针变量,例如SLTNode* plist = NULL;调用尾插函数时,就要传递一级指针变量的地址,函数的参数自然就需要二级指针来接收 。后续的不同插入和删除操作函数的参数都需要二级指针,代码如下:
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);//创建新节点
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
2.5 链表的头插
链表的头插就是在链表第一个节点前插入一个新的节点。

代码如下:
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);//创建新节点
newnode->next = *pphead;
*pphead = newnode;
}
2.6 链表的尾删
链表的尾删就是删除链表的最后一个节点,这里需要两种情况讨论:如果链表只有一个节点,直接释放这个节点的地址,如果有两个或两个以上的节点,释放掉最后一个节点后,倒数第二个节点因为存储着最后一个节点的地址,它存储的地址也要置为NULL。

代码如下:
//链表尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead&&*pphead);
if ((*pphead)->next == NULL)//链表如果只有一个节点
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
2.7 链表的头删
链表的头删是删除链表的第一个节点,起始节点的地址要指向第二个节点的地址。

代码如下:
//链表头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* ptail = *pphead;
SLTNode* next = (*pphead)->next;
free(ptail);
ptail = NULL;
*pphead = next;
}
2.8 链表数据的查找
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
else
{
pcur = pcur->next;
}
}
//找不到
return NULL;
}
2.9 在指定位置之前插入数据
在指定节点之前插入一个新的节点,找到pos节点及其之前的节点prev,在两个节点之间插入新的节点。

代码如下:
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* prev = *pphead;
if (*pphead == pos)
{
SLTPushFront(pphead, x);
}
else
{
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = SLTBuyNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
2.10 在指定位置之后插入数据
在指定位置之后(pos)插入新的节点,新节点存储pos下一个节点的地址。

代码如下:
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.11 删除指定位置的节点
如果链表只有一个节点,直接调用头删函数;链表有多个节点,找到pos之前的节点和pos之后的节点,将它们连接起来。

代码如下:
/删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;
if (pos = *pphead)
{
//头删
SLTPopFront(pphead);
}
else
{
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
2.12 删除pos之后的节点
创建一个临时标量del,提前存储好pos下一个节点的地址,然后先将pos存储好del后的地址,再释放del。

代码如下:
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
2.13 链表的销毁
链表使用完后要进行销毁,将空间还给操作系统:
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
3 链表实现完整代码
从头创建链表,并实现对链表的增删改查实现,我们需要创建一个头文件:SList.h,一个源文件SList.h,用来实现增删改查功能,一个测试文件test.c用来测试代码功能:

3.1 SList.h文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//定义节点的结构
//数据+指向下一个节点的指针
typedef int SLTDataType;//方便修改数据类型
typedef struct SListNode//定义节点
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x);
//打印链表
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);
3.2 SList.c文件
#include "SList.h"
//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//链表打印
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);//创建新节点
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);//创建新节点
newnode->next = *pphead;
*pphead = newnode;
}
//链表尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead&&*pphead);
if ((*pphead)->next == NULL)//链表如果只有一个节点
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
//链表头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* ptail = *pphead;
SLTNode* next = (*pphead)->next;
free(ptail);
ptail = NULL;
*pphead = next;
}
//链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
else
{
pcur = pcur->next;
}
}
//找不到
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* prev = *pphead;
if (*pphead == pos)
{
SLTPushFront(pphead, x);
}
else
{
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = SLTBuyNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
SLTNode* prev = *pphead;
if (pos = *pphead)
{
//头删
SLTPopFront(pphead);
}
else
{
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
3.3 test.c文件
#include "SList.h"
void SListTest01()
{
//创建几个链表的节点测试
SLTNode* node1=SLTBuyNode(1);
SLTNode* node2=SLTBuyNode(2);
SLTNode* node3=SLTBuyNode(3);
SLTNode* node4=SLTBuyNode(4);
//将节点链接起来
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//链表打印测试
/*SLTNode* plist = node1;
SLTPrint(plist);*/
}
void SListTest02()
{
//链表尾插测试
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//链表头插测试
//SLTPushFront(&plist, 5);
//SLTPushFront(&plist, 6);
//SLTPushFront(&plist, 7);
//SLTPushFront(&plist, 8);
//SLTPrint(plist);
//链表尾删测试
//SLTPopBack(&plist);
//SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
//链表头删测试
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
//链表查找测试
/*SLTNode* find=SLTFind(plist, 3);
if (find == NULL)
{
printf("找不到\n");
}
else
{
printf("找到了\n");
}*/
//在指定位置之前插入数据测试
//SLTNode* find = SLTFind(plist, 1);
//SLTInsert(&plist,find, 11);
//SLTPrint(plist);
//在指定位置之后插入数据测试
//SLTNode* find = SLTFind(plist, 1);
//SLTInsertAfter(find, 11);
//SLTPrint(plist);
//删除pos节点测试
//SLTNode* find = SLTFind(plist, 1);
//SLTErase(&plist, find);
//SLTPrint(plist);
//find = SLTFind(plist, 2);
//SLTErase(&plist, find);
//SLTPrint(plist);
//find = SLTFind(plist, 3);
//SLTErase(&plist, find);
//SLTPrint(plist);
//find = SLTFind(plist, 4);
//SLTErase(&plist, find);
//SLTPrint(plist);
//find = SLTFind(plist, 5);
//SLTErase(&plist, find);
//SLTPrint(plist);
//删除pos之后的节点测试
/*SLTNode* find = SLTFind(plist, 2);
SLTEraseAfter(find);
SLTPrint(plist);*/
//销毁链表测试
SListDesTroy(&plist);
}
int main()
{
SListTest01();
SListTest02();
return 0;
}
以上就是有关单链表的所有内容了,如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力,后续会不断地分享知识。