数据结构之链表(单链表)
目录
一、链表的概念
二、链表的分类
三、单链表的实现
1. 创建新的节点
2. 打印链表
3. 链表的头插和尾插
尾插:要注意第一次插入时链表为空的情况。
头插:
4. 单链表的头删和尾删
尾删:注意链表中只有一个元素的情况。且要保存尾节点的前一个节点。
头删:
5. 单链表的查找
一、链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的
指针链接
次序实现的 。链表实际上就像一列火车一样,每一个节点都是火车的一节车厢。

注意:1.从图上可以看出,链式结构在逻辑上是是连续的,但是在物理上不一定连续
2.现实中的节点一般都是从堆上申请出来的。
3.从堆上申请的空间是按照一定的策略来分配的,连续两次申请的空间可能连续也可能不连续。
二、链表的分类
从严格上来讲,链表有:单向/双向, 带头/不带头(哨兵位头节点), 循环/不循环 这些形式,这些形式组合起来共有8种结构,但是本文章只会讲两种基本链表:单向不带头不循环(以下简称单链表)和双向带头循环链表(以下简称双向链表)。掌握以上两种链表的结构以后剩下的结构也就顺其自然的掌握了。
三、单链表的实现
我们要知道单链表是一种数据结构,而提到数据结构,无非就是增删查改这些功能 。
以下是单链表的相关声明。
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
在进行增删查改之前,我们先写两个函数,一个是创建新节点,一个是打印链表从而观察操作是否实现。
1. 创建新的节点
如链表的概念所说,链表的节点是从堆上申请空间,所以我们要使用malloc函数来开辟一块空间,并将值存入data中。
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
// 检查开辟空间是否成功
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->data = x;
// 节点的next置空防止野指针
newnode->next = NULL;
return newnode;
}
2. 打印链表
// 单链表打印
void SListPrint(SListNode* plist)
{
// 定义一个指针来遍历链表
SListNode* cur = plist;
while (cur)
{
// 遍历过程
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL");
}
3. 链表的头插和尾插
尾插:要注意第一次插入时链表为空的情况。
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
// 因为要对链表进行修改,所以我们要使用二级指针
SListNode* head = *pplist;
if (head == NULL)
{
*pplist = newnode;
}
else
{
// 因为是尾插,所以要先找到尾
// 找尾
SListNode* tail = head;
while (tail->next)
{
tail = tail->next;
}
// 尾插
tail->next = newnode;
}
}
头插:
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
newnode->next = *pplist;
*pplist = newnode;
}
4. 单链表的头删和尾删
尾删:注意链表中只有一个元素的情况。且要保存尾节点的前一个节点。
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
SListNode* phead = *pplist;
if (phead->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* tail = phead;
SListNode* prev = NULL;
while (tail->next)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
头删:
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* phead = *pplist;
if (phead->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* newHead = phead->next;
free(phead);
phead = NULL;
*pplist = newHead;
}
}
5. 单链表的查找
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
单链表的修改就是在单链表的查找的基础上实现的,所以这里不会进行过多赘述。