目录
一,链表的概念及结构
概念:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
注意:
- 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
- 现实中的节点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。
链表分类有单向或者双向,带头或不带头,循环或者不循环。但常用的有无头单向非循环链表和带头双向循环链表。
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶,图的邻接表等等。
2.带头双向循环链表:结构最复杂,一般用在单独存放数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但使用代码实现后会发现结构会带来很多优势。
二,单链表的实现
1.头文件
我们将库文件,单链表的定义,接口的声明,放在头文件。
//Slist.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
//动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
//单链表打印
void SListPrint(SListNode* phead);
//单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x);
//单链表头插
void SListPushFront(SListNode** pphead, SLTDateType x);
//单链表头删
void SListPopFront(SListNode** pphead);
//单链表尾删
void SListPopBack(SListNode** pphead);
//单链表查找,返回查找的地址
SListNode* SListFind(SListNode* pphead, SLTDateType x);
//pos位置前插入 ,pos是链表中的一个节点的地址。
void SListInsert(SListNode** pphead,SListNode* pos, SLTDateType x);
//pos位置删除
void SListErase(SListNode** pphead, SListNode* pos);
//pos后面插入,直接从pos代表的节点地址插
void SListInsert_after( SListNode* pos, SLTDateType x);
//pos后面删除
void SListErase_after( SListNode* pos);
2.接口的实现
接口的具体实现,我们在程序中注释,哪点有不懂的评论区可以解答。
2.1 结点的申请与打印
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode)); //用malloc申请一个结构体空间
if (newnode == NULL) //申请错误,提示
{
perror("malloc fail");
return NULL;
}
newnode->data = x; //存入结构体中的数据
newnode->next = NULL; //下个节点指向空
return newnode; //返回新创建的数组。
}
void SListPrint(SListNode* phead)
{
SListNode* cop = phead; //创建一个新空间cop,地址phead的数据赋值给cop
while (cop != NULL) //如果不为空,打印结构体中的数据,直到为空
{
printf("%d->", cop->data);
cop = cop->next;
}
printf("NULL\n"); //打印空
}
2.2单链表的尾插与尾删
void SListPushBack(SListNode** pphead, SLTDateType x) //一级指针地址用二级指针接收,*pphead指向结构体list的地址
{
SListNode* ccop = BuySListNode(x); //用ccop接收创建的结构体
if (*pphead == NULL) //如果list为空,直接将ccop地址的数据赋值给*pphead指向的结构体List
{
*pphead = ccop;
}
else {
SListNode* tail = *pphead; //创建一个指针tail,指向list地址
while (tail->next != NULL) //当tail下一个数据是空时,停止(找尾部)
{
tail = tail->next;
}
tail->next = ccop; //将list的下一个数据指向ccop的地址
}
}
void SListPopBack(SListNode** pphead) //尾删
{
assert(pphead);
assert(*pphead); //暴力检查
if ((*pphead)->next == NULL) //判断是否剩一个数据
{
free(*pphead);
*pphead = NULL;
}
else
{
SListNode* tail = *pphead;
while (tail->next->next) //找到尾部tail->next->next
{ //释放tail->next(最后一个节点)的地址
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.3单链表的头插与头删
void SListPushFront(SListNode** pphead, SLTDateType x)
{
SListNode* ccop = BuySListNode(x);//用ccop接收创建的结构体
ccop->next = *pphead; //直接将接收的结构体下一个数据指向list的头部
*pphead = ccop; //然后将list的头部换成ccop的头部
}
void SListPopFront(SListNode** pphead)
{
assert(*pphead);
SListNode* ccop = *pphead;
*pphead = (* pphead)->next; //注意:*pphead优先级,->比*的优先级高
}
2.4单链表的查找
SListNode* SListFind(SListNode* pphead, SLTDateType x) //查找
{
SListNode* tail = pphead;
int i = 0;
while (tail) //如果pphead 为空的话直接返回空,否则返回数据为x的地址
{
if (tail->data == x)
{
return tail;
}
tail= tail->next;
}
return NULL;
}
2.5 pos位置的节点前的插入与删除
void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x)//在pos点前插入
{
assert(pphead); //防止使用接口时要的目标链表地址传错
assert(*pphead); //检查是否为空链表
assert(pos); //检查链表中pos位置的节点是否为空
if (pos == *pphead) //如果在第一个节点插入,直接运用头插。
{
SListPushFront(pphead,x); //pphead里存储的是list的地址,*pphead指向的是list地址的数据
return;
}
else
{
SListNode* tail = *pphead;
while (tail->next != pos) //找到pos点前一个节点
{
tail = tail->next;
}
SListNode* ccop = BuySListNode(x);
ccop->next = tail->next; //让新申请的ccop指向下一个节点
tail->next = ccop; //pos前一个节点指向新申请的ccop
}
}
void SListErase(SListNode** pphead, SListNode* pos) //在pos位置删除
{
assert(pphead);
assert(pos);
assert(*pphead);
//如果pos点为第一个节点,直接调用头删
//否则,找到pos的位置,将(pos位置前一个节点)指向(pos节点指向的下个节点)。
//再释放pos位置的节点
if (*pphead == pos)
{
SListPopFront(pphead);
return;
}
else
{
SListNode* tail = *pphead;
while (tail->next != pos)
{
tail = tail->next;
}
tail->next = pos->next;
free(pos);
pos = NULL;
}
}
2.6 pos位置后的插入与删除
void SListInsert_after( SListNode* pos, SLTDateType x) //在pos位置后插入
{
assert(pos);
SListNode*tail = BuySListNode(x); //直接将(新申请的节点)指向(pos指向的下一个节点)
tail->next = pos->next; //然后将pos指向的下一个节点改为新申请的空间
pos->next = tail;
}
void SListErase_after(SListNode* pos) //在pos位置后删除
{
assert(pos);
//温柔的检查
if (pos->next == NULL)
{
return;
}
//暴力检查:assert(pos->next);
else
{
SListNode* tail = pos->next;
pos->next = tail->next;
free(tail);
}
}
三,总代码
总代码见码云:初阶数据结构/SListNode1 · 雨天code/优快云1 - 码云 - 开源中国 (gitee.com)