前言
在初阶数据结构的篇章中,我向大家讲解了顺序表的内涵以及代码的实现,还带着大家用顺序表这个数据结构实现了一个通讯录的项目。那么在本文中,我们将继续学习一种新的数据结构——单链表。
1. 为什么要学习单链表
在学习一个新知识之前,我们都要问自己为什么要学习这个知识?
这里我就帮大家答疑解惑啦!
其实,在针对为什么要使用顺序表这个问题,我们也曾讨论过。(如果还没看过那篇文章的读者们可以建议去看看,绝对让你收货满满!)
我们当时是这样说的,顺序表的底层是数组,但是数组无法完全满足我们对于数据的一系列操作,比如:增、删、查、改以及插入数据等等。而且数组不够灵活,一旦大小确定了就无法更改了。
这里还带着大家回忆了一下,马上进入正题,sir~。
沿着这个思路,我们通过对顺序表的实现,也发现了其的不足之处:
- 在使用realloc函数开辟动态空间时,由于realloc开辟空间的特性,会使程序运行的效率变低。
- 在尾插数据或者是尾删数据时都得经过循环来查找,一旦数据量开始增多,代码的运行速度就会大打折扣。
基于上述的缺点,我们就发现了一种能够覆盖这两个缺点的数据结构——单链表。
所以我们可以这么说,单链表是对顺序表的一种优化。这个就是我们学习单链表意义所在了。
2. 什么是单链表
单链表是属于链表的一种。而链表是属于线性表。
我们在之前说过,线性表在逻辑结构上是连续的,在物理结构上不一定连续。
链表的概念:链表是一种逻辑结构上连续,但在物理结构上是非连续的,数据元素在逻辑结构上连续是通过链表上的指针次序来展示的。
链表是由一个个节点链接而成,而每一个节点又分为两大部分:数据域 + 指针域。(这个十分重要)
什么意思呢?下面我生活中的事物给大家做个比对:
相信大家对火车并不陌生,或火车上面的一节一节的车厢是通过每一节车厢后面的钩子和铁链连接起来的,最终它们通过火车的驱动一起驶向星辰大海。
那么我们可以想象到,把链表比作成火车,火车的车厢就是链表上一个一个的节点,车厢里的乘客和货物就相当于节点的数据,而每一个车厢的编号就是该节点的地址。
相信经过这么一说,你已经对单链表的形式已经有了具象化的认识。那么接下来,我们就来聊一聊链表是如何运作的。
链表是通过内部存储了下一个数据的地址并进行访问就能得到数据。就像是动漫里的男主,为了守护自己珍视的一切,不断地修炼自身,一步一步地实现自己的理想。反之如果有一步走错了,那就全盘皆输了。
再用例子做进一步的解释,就比如现在我是一个大公司的老板,我要连续抽查一批员工的业绩,我不可能记得住公司每一位员工的姓名,此时我就可以通过抽查连续的工号。我会叫秘书打印出要抽查的工号表,并叫他们依次来到办公室和喝茶聊天。而且是聊完天后的员工就会叫工号表下一位员工进来办公室,就这样进行,直至全部抽查完毕。
我此时就相当于来链表的第一个节点,我们也称作它为"头节点",每个员工的工号就相当于节点自身的地址,而每个员工的姓名就是节点中的数据。每个员工在叫下一位员工进来办公室喝茶聊天就相当于链表中的指针,其指向了下一个节点(下一个员工)。
OK,终于费尽心思地大家讲解完了什么是链表,其实单链表也是这样的。
3. 单链表 - 代码实现
这里我就不过多阐述代码的写法了,我会直接给出源码,望大家下来自己研究,不懂得可以评论区提问,看到了就会及时的回复。
代码:
//--------------------->SList.h里面的内容<-------------------
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表是由一个个节点构成的
//一个节点包括数据 + 指向下一个节点的指针
typedef int SLTDataType;
typedef struct SLTNode
{
SLTDataType data;
struct SLTNode* next;
}SLTNode;
//打印链表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead,SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x);
//查找指定数据
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos,SLTDataType x);
//在指定位置删除数据
void SLTErase(SLTNode** pphead,SLTNode* pos);
//在指定位置之后删除数据
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestory(SLTNode** pphead);
//--------------------->SList.c里面的内容<-------------------
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->",pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
//申请一个新节点
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(EXIT_FAILURE);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请一个新节点
SLTNode* newnode = SLTBuyNode(x);
//链表分为:空链表和非空链表
// 空链表:一个节点也没有
// 非空链表:至少存在一个节点
//尾插代码
//当链表为空链表时
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//步骤:
//1.先找到链表的尾节点
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//2.将刚申请的新节点与尾节点进行链接
ptail->next = newnode;
//3.检查代码在特殊情况下的正确性
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead && pphead);
SLTNode* ptail = *pphead;
SLTNode* pcur = *pphead;
//当链表只有一个节点时
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找到链表中的最后的一个节点
while (ptail->next)
{
ptail = ptail->next;
}
//还要找到链表中的倒数第二节点
while (pcur->next != ptail)
{
pcur = pcur->next;
}
//释放链表的最后一个节点
free(ptail);
ptail = NULL;
pcur->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
*pphead = pcur->next;
free(pcur);
pcur = NULL;
}
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
SLTNode* pcur = *pphead;
if (pcur == pos)
{
SLTPushFront(pphead,x);
}
else
{
while (pcur->next != pos)
{
pcur = pcur->next;
}
newnode->next = pos;
pcur->next = newnode;
}
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
SLTNode* pcur = *pphead;
//当链表只有一个节点时
if ((*pphead)->next == NULL)
{
SLTPopBack(*pphead);
}
else
{
while (pcur->next != pos)
{
pcur = pcur->next;
}
pcur->next = pos->next;
free(pos);
pos = NULL;
}
}
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* pcur = pos;
pos->next = pos->next->next;
free(pcur->next);
pcur->next = NULL;
}
void SLTDestory(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
OK,以上就是单链表的实现代码了。如果觉得本文还不错的话,麻烦大家给偶点个赞吧!