1. 单链表的含义
1.1 含义
单链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。单链表中只能从头部开始遍历,每个节点只能访问下一个节点,而不能访问前一个节点。
2.0 单链表的代码讲解
2.1 节点
2.1.0 定义单链表的节点结构
根据单链表的组成设置结构体成员
1.type类型的变量
2.指向下一个节点的指针
2.1.1 创建节点的函数
节点的创建分三步
1.使用malloc、calloc去开辟动态内存空间
2.判断是否开辟成功
3.成功:赋值 失败:报错退出
2.1.2 展示节点的函数
展示节点:遍历各个节点,并输出type类型的数据
!展示的时候利用的指针一定不能是Phead,否则会丢失整个单链表(要创建一个新的变量)
2.2 头尾的插入删除数据
2.2.0 尾插数据
因为涉及动态内存开辟,所以外围的大框架就有判断生成是否成功这条语句,成功就接着进行下一步,失败则直接报错退出。
然后核心步骤就是找尾:
因为涉及访问节点的成员,所以要把链表分为空链表和非空链表。
空链表情况可以直接把头指针指向新节点
非空链表就使用while循环找到尾结点,然后让尾结点指向新节点
2.2.1 头插数据
这里不涉及头结点的成员访问,所以不一定要分空链表还是非空链表,我们可以写完非空链表之后看看是否可以兼容空链表,也就是说先完成普遍情况,再考虑特殊情况
普遍情况:这里只需要先让new节点指向原来的头结点,然后再把头指针指向新节点即可
特殊情况:当*phead为空时,则新节点下一个指向空,头节点指针也给到新节点
代入特殊情况进代码后,我们发现确实可以兼容
2.2.2 尾删数据
节点删除的前提是链表不为空,所以在开头要有关于*pphead的断言
若删除后为空链表,我们直接释放掉节点之后,需要把头结点也改为空
若删除后不为空链表,我们就利用快慢指针找到尾和尾前的节点,然后让尾前的节点指向空。
2.2.3 头删数据
头删数据就只需要把头结点指向的节点释放即可,但是为了不丢失整条链表,我们需要在删除节点之前把新的头结点地址给保存下来,然后将他的地址给到头结点指针
2.3 查找数据
查找数据与展示节点方法类似:
需要遍历链表,如果找到对应数据就返回当前地址,最后都没找到就返回NULL
2.4 指定位置前/后插入删除数据
2.4.0 指定位置前插入数据
这里可以分两种情况
第一种:改变头结点指针,也就是pos指向的是头结点
第二种:不改变头节点,pos指向的不是phead
第一种情况我们可以知道他相当于头插一个数据,所以直接调用前面的头插函数即可
第二种情况我们需要找到pos前一个节点的地址,然后让prev->new_node,new_node->pos。
2.4.1 指定位置后插入数据
这里我们不涉及改变头结点的指针,所以不用分开两种情况思考
我们需要的指针是pos->next和pos,为了不用多创建一个指针去保存pos->next,我们先让new_node指向pos->next,再让pos指向new_node。
2.4.2 删除pos节点
这里同样涉及头结点的改变
情况1:删除头结点
情况2:删除非头节点
1.相当于头删,直接引用头删函数
2.需要找到pos前的节点,利用while循环完成。然后让prev指向pos->next,完成新链表的链接,随后再释放pos处的动态内存空间
2.4.3 删除pos后的节点
pos后的节点绝对不可能是头结点,所以也不需要分类讨论了
这里为了防止出现较为复杂的形式(pos->next->next),所以创建了一个新的指针pdel指向pos的下一个节点,让pdel->next代替pos->next->next。
同理,先链接链表,然后销毁pos节点。
2.5 销毁数据
销毁链表:遍历链表,释放内存
!!!由于释放内存会让pcur指针变为野指针,如果不提前保存下一个节点的地址,链表会丢失(就是无法通过pcur找到下一个节点)
3. 完整代码分享
slist.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define type int
//定义单链表的节点结构
typedef struct slistnode
{
type x;//单链表里面装的数据
struct slistnode *next;//指向下一个节点的地址(此时还没完成重命名)
}slnode;
//创建节点的函数
slnode * BuyNode(type a);
//展示节点的函数
void SLTPrint(slnode * phead);
//尾插数据
void SLTPushBack(slnode** pphead, type a);
//??phead在哪里被初始化置为空了
//头插数据
void SLTPushFront(slnode** pphead,type a);
//尾删数据
void SLTPopBack(slnode** pphead);
//头删数据
void SLTPopFront(slnode** pphead);
//查找数据
slnode* SLTFind(slnode* phead, type a);
//指定位置前插入数据
void SLTInsert(slnode** pphead, slnode* pos, type a);
//在指定位置之后插入数据
void SLTInsertAfter(slnode* pos, type a);
//删除pos节点
void SLTErase(slnode** pphead, slnode* pos);
//删除pos之后的节点
void SLTEraseAfter(slnode* pos);
//销毁链表
void SListDesTroy(slnode** pphead);
slist.c
#include"slist.h"
slnode * BuyNode(type a)//节点创建
{
slnode *new_node =(slnode *) malloc(sizeof(slnode));
if (new_node)//进行创建是否成功的判断
{
new_node->x = a;
new_node->next= NULL;
}
else
{
perror("malloc");
exit(1);//不用return是因为return返回的是该函数外部,且返回值类型也不太对
}
return new_node;
}
void SLTPrint(slnode * phead)//节点展示(这里的头结点准确来说是有效节点第一位,而不是真正的头节点,因为头结点是哨兵位
{
slnode*pcur = phead;//防止phead被改变
while (pcur)
{
printf("%d->",pcur->x);
pcur = pcur->next;
}
printf("NULL");
}
//尾插
void SLTPushBack(slnode** pphead, type a)//分为空链表和非空链表
{
assert(pphead);
slnode* new_node =(slnode*) BuyNode(a);
if (new_node)
{//开辟成功
if (*pphead == NULL)//讲解指向phead的指针和phead指针和链表节点关系
{
//空链表
*pphead = new_node;
}
else
{
//非空链表
slnode*ptail = *pphead;
while (ptail->next != NULL)
{
ptail = ptail->next;
}
ptail->next = new_node;
}
}
else//开辟失败
{
perror("malloc");
exit (1);
}
}
void SLTPushFront(slnode** pphead,type a)//头插
{
assert(pphead);
slnode* new_node = (slnode*)malloc(sizeof(slnode));
if (new_node)
{
new_node->next = *pphead;
*pphead = new_node;
}
else
{
perror("malloc");
exit(1);
}
}
void SLTPopBack(slnode** pphead)//尾删
{
assert(pphead&&*pphead);//确保不是空链表删除
//分为单节点和多节点(区分是否要改变头节点)
if ((*pphead)->next == NULL)//->的优先级高于*
{
free(*pphead);
*pphead = NULL;//改变头结点
}
else
{
slnode*prev = *pphead;
slnode*ptail = *pphead;
while (ptail->next)//找到尾和尾前一位
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;//断掉链条
free(ptail);
ptail = NULL;
}
}
void SLTPopFront(slnode** pphead)//头删
{
assert(*pphead && pphead);
slnode*pnext = (*pphead)->next;//以免丢失下一节点的数据
free(*pphead);
*pphead = pnext;
}
slnode* SLTFind(slnode* phead, type a)//查找数据
{
slnode*pcur = phead;
while (pcur)
{
if (pcur->x == a)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLTInsert(slnode** pphead, slnode* pos, type a)//指定位置前插入数据
{
assert(pphead && *pphead);
assert(pos);//不能在NULL前插入数据
if ((*pphead)->next == NULL)//单节点
{
SLTPushFront( pphead, a);//相当于头插
}
else//多节点
{
slnode*new_node = (slnode*)BuyNode(a);
slnode*prev = *pphead;//为了拿到pos前的地址
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = new_node;
new_node->next = pos;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(slnode* pos, type a)
{
assert(pos);
slnode*new_node=(slnode*)BuyNode(a);
new_node->next = pos->next;
pos->next = new_node;
}
//删除pos节点
void SLTErase(slnode** pphead, slnode* pos)
{
assert(pos);
assert(*pphead && pphead);
if (pos == *pphead)//pos是头节点
{
SLTPopFront(* pphead);
}
else//非头节点
{
slnode*prev = *pphead;
while (prev->next != pos)//找到删除节点前一个数据地址
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos后的节点
void SLTEraseAfter(slnode* pos)
{
assert(pos && pos->next);//不能删除空节点
slnode*pdel = pos->next;
pos->next = pdel->next;
free(pdel);
pdel = NULL;
}
//销毁链表
void SListDesTroy(slnode** pphead)
{
assert(pphead);
slnode*pcur = *pphead;
slnode*pnext = *pphead;
while (pcur)
{
pnext = pcur->next;//保存下一个节点的地址
free(pcur);
pcur = pnext;
}
*pphead = NULL;
}