1.链表解决的问题和概念
1.1在上一篇文章中介绍的顺序表很明显存在一些问题:①顺序表中间和头部插入数据时需要遍历数组,时间复杂度为O(N)②realloc在栈区上扩容时底层逻辑是:如果需要扩容的空间后面有足够的空间在原空间的基础上接上,如果空间不够,则需要重新开辟一块空间,再将原空间的数据复制上,再释放原空间,会有内存和时间上的消耗③顺序表在后期以两倍的大小扩容时,容易造成空间的浪费。
1.2这里我们引入链表解决问题,链表由一个个节点构成,节点可以看成是由数据和存储下一个数据的指针封装形成的数据块。当我们需要增加数据时,只需要将链表末尾节点中的指针指向新开辟的数据即可,这样链表就串联起来。下面这张图可以帮助我们理解.
1.3链表的创建:由上图我们可以用结构体构建一个链表架构(我们用int类型作为示例)
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType x;
struct SListNode *next;
}SLTNode;
2 链表功能的实现
2.1链表的尾插(尾插时需要考虑链表是否有节点)
我们可以先封装一个扩容函数,方便后续用到:
SLTNode* SLBuyNode(SLTDataType x)
{
SLTNode* ps = (SLTNode*)malloc(sizeof(SLTNode));
if (ps == NULL)
{
perror("malloc fail!");
exit(1);
}
ps->x = x;
ps->next = NULL;
return ps;
}
尾插实现函数(注意:先要判断节点存不存在。先要找到尾结点,可以用循环遍历找)
void SLPushBack(SLTNode** pphead , SLTDataType x)
{
assert(pphead);
if (*pphead == NULL)
{
*pphead = SLBuyNode(x);
}
else
{
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = SLBuyNode(x);
}
}
2.2链表的头插(注意不要忘记将头节点地址更换成新创建的节点)
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.3链表数据的打印(这里以整形为例子)
void SLPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d ", pcur->x);
pcur = pcur->next;
}
printf("\n");
}
2.4链表的尾删(注意需要区分只有一个节点和多个节点两种情况)
只有一个节点时直接释放掉就完事,当多个节点时需要找到尾节点的上一个节点,将尾二节点中的next指针置为空,再将尾节点释放掉
void SLPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* pcur = *pphead;
SLTNode* pcurback_1 = (*pphead)->next;
while (pcurback_1->next)
{
pcurback_1 = pcurback_1->next;
pcur = pcur->next;
}
pcur->next = NULL;
free(pcurback_1);
pcurback_1 = NULL;
}
}
2.5链表的头删(现将头结点的地址存储在oldphead中,再将头结点置为头节点的下一个节点,再将oldphead节点即原节点释放)
void SLPopFront(SLTNode** pphead)
{
//多链条 &&单链条
assert(pphead && *pphead);
SLTNode* oldphead = *pphead;
*pphead = (*pphead)->next;
free(oldphead);
oldphead = NULL;
}
2.6节点的查找(只需遍历节点中的data数据)
SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->x == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
2.6在指定位置之前插入(先要找到pos尾前的一个位置,如果只有一个节点则可以直接调用前插函数)
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* pcur = *pphead;
if ((*pphead)->next == NULL)
{
SLTNode* newnode = SLBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
else
{
while (pcur->next != pos)
{
pcur = pcur->next;
}
SLTNode* newnode = SLBuyNode(x);
pcur->next = newnode;
newnode->next = pos;
}
}
2.7在指定pos位置之后插入数据(注意需要按照顺序执行)
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.8删除pos位置(需要找到pos前面的位置,如果只有一个节点直接释放掉)
void SLErase(SLTNode** pphead,SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
pos = NULL;
}
else
{
SLTNode* pcur = *pphead;
while (pcur->next != pos)
{
pcur = pcur->next;
}
pcur->next = pos->next;
free(pos);
pos = NULL;
}
}
2.9删除pos位置之后的
void SLEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
pos->next = pos->next->next;
free(pos->next);
pos->next = NULL;
}
2.10销毁链表
void SLDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* del = *pphead;
while (del)
{
SLTNode* next = del->next;
free(del);
del = next;
}
*pphead = NULL;
}
3链表的代码链接
链表的.c和.h文件可以访问我的gitee链接获取8.24/SList · 向豪/数据结构学习 - 码云 - 开源中国 (gitee.com)
谢谢!