
个人主页: 小则又沐风a
个人专栏:<C语言>
目录
[一 链表的概念及概念:](#一 链表的概念及概念:)
[二 链表的分类](#二 链表的分类)
[1. 单向和双向](#1. 单向和双向)
[2. 带头或者不带头](#2. 带头或者不带头)
[三 链表的实现](#三 链表的实现)
[1. 链表的结构](#1. 链表的结构)
[2. 动态申请一个结点](#2. 动态申请一个结点)
[6. 单链表的尾删](#6. 单链表的尾删)
[7. 链表的头删](#7. 链表的头删)
[8. 单链表查找](#8. 单链表查找)
一 链表的概念及概念:
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的结构.
这样的解释有点抽象我们来看一张图:

我们把每个数据看作每一个车厢,如果没有连接车厢的装置的话,那么每个车厢就是独立的.
链表也是如此.

我们通过上面的图可以知道我们的每组数据在逻辑上一定是连续的,但是在物理上不一定是连续的,
我们通过记录下一个节点的地址实现每个节点的链接.
二 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向和双向

2. 带头或者不带头

3.循环和非循环

纵使有这么多的类型但是使用率最高的是

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
三 链表的实现
下面我实现的是无头单向非循环的链表
在实现链表的各个接口之前我们先来定义一下链表的结构:
1. 链表的结构
cpp
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
data表示的这个节点存储的数据.
next存储的是下一个节点的地址.
2. 动态申请一个结点
通过malloc开辟一个节点的空间,并且检查是否开辟成功,然后给节点赋值.
cpp
SListNode* BuySListNode(SLTDateType x)
{
SListNode* temp = (SListNode*)malloc(sizeof(SListNode));
if (temp == NULL)
{
perror("malloc");
return;
}
temp->data = x;
temp->next = NULL;
return temp;
}
3.单链表打印
cpp
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
在这里我们通过
这样的方式来遍历链表直到遍历到NULL;
这样就实现了链表的打印.
4.单链表尾插

所以我们先要找尾节点.
cpp
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode=BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
SListNode* cur = *pplist;
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newnode;
newnode->next = NULL;
}
}
注意在这里我们要传入的是二级指针因为有可能我们传入的链表是一个空链表,此时我们需要让链表的第一个节点不再指向NULL让他指向newnode所以我们需要的是地址传递.
**5.**单链表的头插

头插只需要让newnode的next指向原来的头节点,然后将头节点更新为newnode即可
cpp
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode = BuySListNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
6. 单链表的尾删

我们需要找到指向尾节点的节点让他的next指向NULL然后释放尾节点.
cpp
void SListPopBack(SListNode** pplist)
{
assert(pplist&&*pplist);
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* cur = *pplist;
while (cur->next->next != NULL)
{
cur = cur->next;
}
SListNode* del = cur->next;
cur->next = NULL;
free(del);
del = NULL;
}
}

如果链表只有一个节点的话直接释放掉头节点,然后置空.
7. 链表的头删

如果我们先把头节点释放掉的话,我们就无法找到下一个节点,所以我们需要先记录下下一个节点,再释放空间.
cpp
void SListPopFront(SListNode** pplist)
{
assert(pplist && *pplist);
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* cur = (*pplist)->next;
free(*pplist);
*pplist = cur;
}
}
8. 单链表查找
遍历链表,如果找到直接返回节点,如果找不到就返回空指针.
cpp
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur = plist;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
9.单链表在pos位置之后插入x

如果我们直接将pos->next修改为我们创建的新节点的话,那么我们之前的pos之后的节点就找不到了,所以我们要先记录下来这个pos之后的节点.
cpp
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
SListNode* next = pos->next;
pos->next = newnode;
newnode->next = next;
}
10.单链表删除pos位置之后的值

同理的如果直接删除节点的话我们就无法访问删除节点的下一个节点了,所以我们要先记录下来这个节点,再删除.
cpp
void SListEraseAfter(SListNode* pos)
{
assert(pos && pos->next);
SListNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
至此,链表的接口实现全部的代码和讲解都展示在了上面,,我们通过画图就可以更快的理解这些接口,
希望这篇文章能够帮助到大家理解链表的知识.
谢谢大家的观看!!!