先看头文件 SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType; //链表是linklist,这里是singlelinklist,单链表
typedef struct SListNode //这个太长了,typedef一下
{
SLTDataType data;
struct SListNode* next; //存在一个指向自己结构的指针(可以认为指向一个同类)
}SLTNode; //定义成STLNode,这样简单
//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x);
SLTNode* CreateSList(int n); //创建一个n个的链表 (注意:出了CreateSList这个函数的作用域,phead,ptail就销毁了,所以要返回phead)
//打印链表 注意:链表只需要一个头指针就可以了,而顺序表有指针还不行,还需要有size(记录存储多少个有效数据)和capacity(空间容量大小)
void SLTPrint(SLTNode* phead); //这里的phead和刚刚的phead不是一个phead,刚刚CreateSList函数的phead已经销毁了
void SLTPushBack(SLTNode** pphead, SLTDataType x);//链表尾插
void SLTPopBack(SLTNode** pphead);//链表尾删
void SLTPushFront(SLTNode** pphead,SLTDataType x); //链表头插
void SLTPopFront(SLTNode** pphead); //链表头删
SLTNode* SLTFind(SLTNode* phead, SLTDataType x); //查找数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//单链表在pos位置之后插入x
//分析思考为什么不在pos位置之前插入?
void SLTEraseAfter(SLTNode* pos);//单链表删除pos位置之后的值
//分析思考为什么不删除pos位置?
void SLTInsert(SLTNode** phead, SLTNode* pos, SLTDataType x); //在pos之前插入x
void SLTErase(SLTNode** phead, SLTNode* pos);//删除pos位置
void SLTDestroy(SLTNode** pphead); //销毁链表
下面是函数实现(SList.c)重点都在注释中,一行一行注释的
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //在堆上申请空间
if (newnode == NULL)
{
perror("malloc fail");//申请失败
exit(-1);
}
newnode->data = x; //把x的值存上
newnode->next = NULL; //一个节点一开始最好初始化为空
return newnode; //返回这个指针
}
SLTNode* CreateSList(int n) //创建一个n个的链表 (注意:出了CreateSList这个函数的作用域,phead,ptail就销毁了,所以要返回phead)
{
SLTNode* phead = NULL; //链表的第一个节点喜欢叫头节点,这个的目的是找到头
SLTNode* ptail = NULL; //这个的目的是用来找到尾部
for (int i = 0; i < n; ++i)
{
SLTNode* newnode = BuySLTNode(i); //创建一个节点,值为i
if (phead == NULL)
{
ptail = phead = newnode; //如果一开始是空的,那么把头和尾都指向newnode节点
}
else
{
ptail->next = newnode; //此时的ptail的next指针存newnode的地址
ptail = newnode; //ptail放后面,指向的就是新节点了(这里phead是不变的,而ptail是一直变化的,指向最新的newnode节点)
}
}
//ptail->next = NULL;
return phead;
}
void SLTPrint(SLTNode* phead) //这里的phead和刚刚的phead不是一个phead,刚刚CreateSList函数的phead已经销毁了
{
SLTNode* cur = phead; // 把phead的值给一个cur指针变量
while (cur != NULL)
{
printf("[%d|%p]->", cur->data, cur->next); //打印cur中的数据和下一个的地址都打印出来
cur = cur->next; //cur指向下一个节点
}
printf("NULL\n");
}
void SLTPushBack(SLTNode** pphead, SLTDataType x) //尾插
{
SLTNode* newnode = BuySLTNode(x); //创建一个节点(data为x,next为空)
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead; //给tail指针一个初值
//找尾
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPopBack(SLTNode** pphead) //尾删
{
//暴力的检查
assert(*pphead);
if ((*pphead)->next == NULL) //如果这个链表只有一个元素
{
free(*pphead); //释放这个指针指向的地址空间
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead; //跟上面一样,先给tail赋值头节点
while (tail->next->next) //一个一个往后面找,知道找到最后一个节点,其next为null
{
tail = tail->next;
}
free(tail->next); //释放这个节点
tail->next = NULL;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x) //前插
{
SLTNode* newnode = BuySLTNode(x); //创建一个新节点,data为x
newnode->next = *pphead; //新节点的next指针指向之前的头节点
*pphead = newnode; //让新节点的指针成为头节点
}
void SLTPopFront(SLTNode** pphead) //前删
{
assert(*pphead); //断言一下,头指针不能为空
SLTNode* next = (*pphead)->next; //保存一下头节点的下一个指针
free(*pphead); //释放头节点
//(pphead是一个二级指针,存放的是头节点的指针的地址,解引用之后是一个一级指针,指向的是头节点,而free函数括号里就是一个指针)
*pphead = next; //把刚刚保存的指针赋值给头节点
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) //寻找节点,需要两个参数(头节点和查找的数据x)
{
SLTNode* cur = phead; //用cur指针来查找这个数据,一开始赋值头节点指针
while (cur) //当cur指针不为空时,一直循环
{
if (cur->data == x) //如果找到这个数据了,返回cur指针
{
return cur;
}
cur = cur->next; //把链表中指向的下一个节点的地址赋值给cur
}
return NULL; //找不到返回空指针
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x) //在pos之后插入一个新节点
{
assert(pos); //断言pos不能为空指针
SLTNode* newnode = BuySLTNode(x) ; //创建一个新节点
newnode->next = pos->next; //让新节点的next指向前一个数据原本指向的节点
pos->next = newnode; //让前一个节点指向新创建的节点newnode
}
//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) //链表找不到前面的节点,只能从头找,所以要头节点指针
{
assert(pos); //pos不能为空指针
if (*pphead == pos) //如果要插入的位置是首节点,相当于头插
{
SLTPushFront(pphead, x); //直接调用头插函数
}
else
{
SLTNode* prev = *pphead; //保存一下头指针
while (prev->next != pos) //用循环找到pos指针,当next指向的的是pos节点时停止(链表找不到前面的节点,只能从头找)
{
prev = prev->next; //从下一个节点开始找
}
SLTNode* newnode = BuySLTNode(x); //找到位置后,就得创建一个新节点了
prev->next = newnode; //让pos节点前一个节点指向新创建的节点
newnode->next = pos; //让新节点指向pos节点
}
}
void SLTEraseAfter(SLTNode* pos) //删除pos节点后面的节点
{
assert(pos); //如果是空节点删个锤子
if (pos->next == NULL) //如果pos后面是空节点,那么不需要删除,直接返回即可
{
return;
}
else
{
SLTNode* nextNode = pos->next; //这里保存一下pos节点的下一个节点(想删除的目标节点)的地址
//pos->next = pos->next->next;
pos->next = nextNode->next; //让pos节点的next指向目标节点后面的节点(跳过想要删除的节点)
free(nextNode); //释放这个目标节点
//nextNode = NULL;
}
}
//删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos) //需要一个头指针(为了找到pos前面的节点),和一个pos目标指针
{
assert(pos); //pos是空指针还删啥
assert(*pphead); //这是个空链表还删除什么
if (pos == *pphead) //如果pos是首结点,那么问题就变成了一个头删
{
SLTPopFront(pphead); //调用头删函数
}
else
{
SLTNode* prev = *pphead; //设prev指针用来寻找pos前面的节点,一开始给它首节点的地址
while (prev->next != pos) //如果节点指向pos节点(找到了),终止循环
{
prev = prev->next; //指针指向下一个节点
}
prev->next = pos->next; //让pos前面的节点指向pos后面的节点
free(pos); //释放pos结点
//pos = NULL;
}
}
void SLTDestroy(SLTNode** pphead) //销毁链表
{
SLTNode* cur = *pphead; //把头节点的指针赋给cur指针
while (cur)
{
SLTNode* next = cur->next; //在删除节点之前,先把这个节点next指向的下一个节点地址存下来
free(cur); //再释放
cur = next; //让cur指针指向刚刚保存的下一个节点地址
}
*pphead = NULL;
}