目录
写在开头:我们站在巨人的肩膀上
前言
上一篇文章我们详细讲解了顺序表的增删查改,我们发现,在实现数据的增加和删除的时候,需要频繁的挪动数据,并且很多功能实现前需要检查容量并且考虑增容,而链表的数据不必连续,不用担心容量的问题,那究竟如何实现呢,让我们开始今天的内容吧
**本文先介绍链表中的单链表,之后会介绍双向带头循环链表
单链表的结构
与顺序表不同,链表不需要申请一块连续的空间,链表的空间并不连续,他是通过指针来链接在一起,我们将链表的一个成员称为一个节点
一个节点由两部分组成,第一部分是数据部分data,第二部分是指向下一个节点的指针部分next

这就是一个由四个节点构成的链表
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
这样我们就自定义一个链表的结构
单链表的节点,创建,打印
创建节点
和顺序表不同,我们链表并不能一口气申请很多空间,而是需要的时候就插入一个节点,因此我们需要一个函数能够申请一个节点
声明:
SLTNode* BuySListNode(SLTDataType x);
函数实现:
SLTNode* BuySListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail:::");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
创建
当然,如果你想像顺序表那样初始化一个简单的链表,可以再封装一个函数:
声明:
SLTNode* CreateSList(int n);
实现:
SLTNode* CreateSList(int n)
{
SLTNode* phead = NULL;
SLTNode* ptail = NULL;
for (int i = 0;i<n;i++)
{
SLTNode* newnode = BuySListNode(i);
if (phead == NULL)
{
phead = ptail = newnode;
}
else
{
ptail->next = newnode;
ptail = newnode;
}
}
}
要实现上述函数,有几点需要注意,如果是第一个节点(头节点),需要将第一个节点的地址给phead和ptail,并且要用ptail继续指向后续节点,不然找不到头节点
打印
打印需要我们对链表遍历
声明:
void SListPrint(SLTNode* plist)
实现:
void SListPrint(SLTNode* plist)
{
SLTNode* cur = plist;
while (cur)
{
printf("%d",cur->data);
cur = cur->next;
}
printf("\n");
}
打印的过程相对简单,需要注意的是,使用cur而不是plist来遍历数组,防止找不到头节点
单链表的增删查改销
增加
头插:
声明:
void SLTPushFront(SLTNode** pphead, SLTDataType x);
实现思路:

如果想要把newnode链接到链表最前面,只需要让newnode的next指向phead即可,但我们会发现,原来的头节点phead不再是头节点了,因此我们需要更改phead的值,所以我们在声明的时候,选择传递了一个二级指针pphead,这样就可以更改头节点了。
代码:
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
尾插:
声明:
void SLTPushBack(SLTNode** pphead, SLTDataType x);
实现思路:

我们需要先找到当前链表的尾部节点,然后将尾节点和新建的节点链接起来即可
代码实现:
void SListPushBack(SLTNode** pplist, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
SLTNode* ptail = *pplist;
if (*pplist == NULL)
{
*pplist = newnode;
}
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
后插:
声明:
void SListInsertAfter(SLTNode* pos, SLTDataType x);
思路:
先使用prev保存pos节点的下一个位置,再让pos的next指向新开辟的节点newnode,最后让newnode的next指向prev保存的节点即可
代码:
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
SLTNode* prev = pos->next;
pos->next = newnode;
newnode->next = prev;
}
前插:
声明:
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
实现思路:
先要找到pos前一个节点的位置,然后进行链接即可,需要注意的是,如果pos的位置就是头节点,我们直接调用之前写的头插即可
代码实现:
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
if (*pphead == pos)
{
SListPushFront(pphead, x);
}
else
{
SLTNode* cur = *pphead;
SLTNode* newnode = BuySListNode(x);
while (cur)
{
if (cur->next == pos)
{
break;
}
cur = cur->next;
}
newnode->next = pos;
cur->next = newnode;
}
}
删除
删除与增加的大致思路类似,在这里我们只实现后删和尾删
后删:
声明:
void SListEraseAfter(SLTNode* pos);
实现思路:

同样的,我们需要先记录要删除节点的下一个位置,再完成链接即可,这里同样要判断pos位置
的next是否为空,如果为空,那就无法删除下一个节点(因为并不存在下一个节点),直接返回即可。
代码实现:
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
else
{
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
}
尾删:
声明:
void SLTPopBack(SLTNode** pphead);
实现思路:

同样的,我们需要找到尾部的前一个位置,如果ptail的next不为空,就把ptail赋值给prev,然后ptail走向下一个,之后释放掉ptail指向的位置即可
代码实现:
void SListPopBack(SLTNode** pplist)
{
assert(*pplist);
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SLTNode* prve = NULL;
SLTNode* ptail = NULL;
while (ptail->next)
{
prve = ptail;
ptail = ptail->next;
}
prve->next = NULL;
free(ptail);
}
}
查找
其实在之前的某个位置插入或删除中需要使用查找来找到pos位置,所以我们需要实现一个函数来查找。
声明:
SLTNode* SListFind(SLTNode* plist, SLTDataType x);
也较为简单,这里直接贴上代码:
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
assert(plist);
SLTNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
更改
我们使用查找找到指针就可以访问其中的数据,并且可以对其修改,也较为简单,如果感兴趣,可以自行尝试一下修改
销毁
因为是动态开辟的空间,所以我们需要对其销毁,我们也通过一个函数来实现
声明:
void SLTDestroy(SLTNode** pphead);
实现思路:
我们让ptail从头节点开始,如果ptail不为NULL的话,就把ptail赋值给prev,然后释放prev即可
代码实现:
void SLTDestroy(SLTNode** pphead)
{
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail)
{
prev = ptail;
ptail = ptail->next;
free(prev);
}
*pphead = NULL;
}
**同样需要注意,最后要把头节点赋值为NULL,所以这里依然传递的是二级指针
链表和顺序表的对比
这样,我们就学完了顺序表和链表两种数据结构,我可以从以下几个角度来对比两者
- 存储空间上:顺序表在物理空间上连续,链表只在逻辑上连续
- 随机访问:顺序表能够实现随机访问,但链表不能
- 任意位置插入或删除元素:顺序表需要整体挪动,而链表只需要修改指针
- 容量:顺序表需要考虑容量,但链表不用考虑
- 应用场景:顺序表应用于频繁访问和高效存储,而链表频繁删除和任意位置删除
小结:
天生我材必有用,任何一种结构都有最适合应用的场景,我们在生活学习中也要不断扩展自己的知识面,精进自己的技术。
最后,如果你有所收获,请不要忘记给笔者关注点赞收藏,这是我更新最大的动力,我们下次再见。
1万+

被折叠的 条评论
为什么被折叠?



