本章目标
0.链表的基础结构
1.头插尾插
2.头删尾删
3.查找
4.在指定位置之前插入数据/在指定位置之后插入数据
5.删除pos结点的数据/删除pos结点之后的数据
6.销毁链表
0.链表的基础结构
对于链表来说,它是一种线性表,但是在物理空间上并不连续,我们可以把它想象成一辆火车
它一共有8种,但主要分为三大类,是否带头,是否循环,是否双向,在这里我们实现的单向不带头不循环链表,链表中最简单的一种.
它的结点结构定义如下
typedef int SLTDataType;
typedef struct SLTNote
{
SLTDataType data;
struct SLTNote* next;
}SN;
为了方便后面的实现在这里用typedef将数据类型和结构体名称进行typedef方便后续的书写以及为了更改后面的数据类型的便利.
对于链表的结点结构,它分为两部分
存储数据的数据域,和存储下一个结点的指针域.
1.头插尾插
1.1尾插
对于尾插一个结点,我们需要进行讨论,链表是否为空.若果为空,就需要对链表进行修改,需要传二级指针.相当于对头节点(非真正意义上的头节点)的地址进行修改.
如果链表不为空,我们就在后面直接插入一个新的结点即可
,为了方便后续的插入数据,我们可以将一个结点的创建进行封装
SN* SLTbuynode(SLTDataType y)
{
SN* newnode = (SN*)malloc(sizeof(SN));
if (newnode==NULL)
{
perror("malooc fail;");
exit(1);
}
newnode->data = y;
newnode->next = NULL;
return newnode;
}
对于链表中没有结点,我们可以直接让传过来的结点指针,直接用新创建的结点进行赋值
,对于链表不为空,我们只需要让最后一个结点的中的指针指向新创建的结点即可.
但又涉及另一个问题,我们需要遍历当前链表,寻找到最后一个节点中的指针为空的结点.
void SLTpushback(SN** pphead, SLTDataType x)
{
SN* newnode = SLTbuynode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SN* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
为了方便后续的测试我们可以将链表的打印,也封装成一个函数
void SLTPrint(SN* phead)
{
SN* pcur = phead;
while (pcur!=NULL)
{
printf("%d-> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
1.2头插
对于链表的头插来说,我们一定会对链表的头结点进行修改,我们只要将当前新创建的结点的下一个指针,指向当前头结点,然后让头结点变为下一个结点即可.
但需要判断当前传过来的结点,不能为空,因为需要对头结点进行解引用.
如果编译器过老不支持assert,可以选择用ifelse判断
void SLTpushfront(SN** pphead, SLTDataType x)
{
assert(pphead);
SN* newnode = SLTbuynode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.头删尾删
2.1头删
对于头删来说,我们需要保存头结点的下一个结点,释放当前头结点,然后让当前头结点变为我们保存的next结点.
void SLTpopfront(SN** pphead)
{
assert(pphead && *pphead);
SN* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
2.2尾删
与尾插相对称,尾删也可以分为两种情况,当前链表中是否只有一个结点
我们分情况进行讨论
当当前链表中,只有一个结点的时候,我们只需要释放当前结点,然后置空.
当不只有一个结点的时候,我们需要找到最后一个结点,然后保存最后一个结点的前一个结点,释放最后一个结点,然后让尾结点变为当前最后一个结点的前一个结点.
但需要判断,传过来的链表的结点不能为空,链表不能为空
void SLTpopback(SN** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next==NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SN* prev = NULL;
SN* ptail = *pphead;
while (ptail->next != NULL)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
3.查找
查找的逻辑很好盘,只需要遍历当前链表,与他们数据域的值进行比对即可
SN* SLTfind(SN* phead,SLTDataType x)
{
SN* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
SN* plist = NULL;
SLTpushback(&plist, 1);
SLTPrint(plist);
SLTpushback(&plist, 2);
SLTPrint(plist);
SLTpushback(&plist, 3);
SLTPrint(plist);
SLTpushback(&plist, 3);
SLTpushback(&plist, 3);
SLTpushback(&plist, 4);
SN* ret = SLTfind(plist, 2);
SLTInsert(&plist, ret, 555);
SLTInsertAfter(ret, 444444);
SLTErase(&plist, ret);
SN* ret1 = SLTfind(plist, 555);
SLTErase(&plist, plist);
SLTEraseAfter(plist);
SLTPrint(plist);
if (ret != NULL)
{
printf("找到了\n");
}
else
{
printf("没找到\n");
}
测试代码如上.
4.在指定位置之前插入数据/在指定位置之后插入数据
4.1在指定位置之前插入数据
仍然需要分成两种情况进行讨论
对于pos位置的结点如果与当前链表的结点相同即使头插逻辑,我们可以复用上面实现的函数.
如果不是需要遍历到pos结点的前一个位置.让它的下一个结点的指针指向新创建的结点,然后让新创建的结点的下一个结点的指针指向pos结点
void SLTInsert(SN** pphead, SN* pos, SLTDataType x)
{
assert(pphead && pos);
assert(*pphead);
if (pos == *pphead)
{
SN* newnode = SLTbuynode(x);
newnode->next = *pphead;
*pphead = newnode;
}
else
{
SN* newnode = SLTbuynode(x);
SN* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
4.2在指定位置之后插入数据
它的逻辑相对于上一个简单不少,只需要让新创建的结点的下一个结点的指针指向当前pos结点的下一个结点,然后让pos结点的下一个结点指向新创建的结点.
void SLTInsertAfter(SN* pos, SLTDataType x)
{
assert(pos);
SN* newnode = SLTbuynode(x);
newnode->next = pos->next;
pos->next = newnode;
}
5.删除pos结点的数据/删除pos结点之后的数据
5.1删除pos结点的数据
仍然分为两种情况讨论,如果当前pos结点等于链表的第一个结点,就是头删逻辑,可以复用上面的函数.
如果当前链表中pos结点不是头节点,我们需要找到pos结点的前一个结点,然后让这个结点的下一个结点的指针,指向pos结点的下一个结点.
然后释放pos结点
void SLTErase(SN** pphead, SN* pos)
{
assert(pphead && pos);
assert(*pphead);
if (*pphead == pos)
{
SLTpopfront(pphead);
}
else
{
SN* prev = *pphead;
while (prev ->next!= pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
5.2删除pos结点之后的数据
对于它来说需要保存pos结点的下一个结点,然后让pos结点的下一个结点的指针指向pos结点的下一个结点的下一个结点的指针,释放pos结点的下一个结点
void SLTEraseAfter(SN* pos)
{
assert(pos&&pos->next);
SN* del = pos->next;
pos->next = del->next;
//pos->next-next
free(del);
del = NULL;
}
6.销毁链表
对于链表的销毁来说
需要进行循环销毁,每一销毁前都需要保存下一个要销毁的结点.
void SLTdestory(SN** pphead)
{
SN* pcur = *pphead;
while (pcur)
{
SN* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}