文章目录
前言
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
一、什么是单链表?
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
1.单链表的结构
特点:
1.从上图可以看出,链式结构在逻辑上是连续的,在物理上则不一定。
2.现实中的节点大多是malloc出来的,即位于堆上。
3.从堆上申请的空间则不一定连续。
二、实现过程
单链表,此处的是无头,单向,非循环链表,用来存储非连续空间的数据的结构,
其实现同顺序表,分为SList.h头文件,SList.c源文件,Test.c文件。
SList.h头文件:存放函数声明以及结构定义,
SList.c源文件:存放函数定义,即实现过程,
Test.c文件:则是存放其测试代码。
1.SList.h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
//单链表--节点连接
typedef int SLDataType;
typedef struct SListNode
{
SLDataType val;
struct SListNode* next;
}SListNode;
//ppst 存放的是链表头节点的地址
//开辟一个新节点
SListNode* CreateSListNode(SLDataType x);
//尾插
void SLPushBack(SListNode** ppst, SLDataType x);
//头插
void SLPushFront(SListNode** ppst, SLDataType x);
//尾删
void SLPopBack(SListNode** ppst);
//头删
void SLPopFront(SListNode** ppst);
//打印链表
void PrintSList(SListNode* pst);
//查找指定节点
SListNode* SLFind(SListNode* pst, SLDataType x);
//删除pos位置的节点
void SLErase(SListNode** ppst, SLDataType x);
//在pos前插入
void SLInsert(SListNode** ppst, SLDataType val, SLDataType x);
//销毁
void SLDestroy(SListNode** ppst);
节点定义原因讲解:单链表地址上的不连续,则代表其需要指针存放下一个节点的地址,以便进行链接,同时需要一个变量存放数值,对于多个变量,需要使用结构体来保存。
2.SList.c
#include "SList.h"
//开辟一个新节点
SListNode* CreateSListNode(SLDataType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->next = NULL;
newnode->val = x;
return newnode;
}
//单链表打印
void PrintSList(SListNode* pst)
{
SListNode* cur = pst;
while (cur != NULL)
{
printf("%d=-> ", cur->val);
cur = cur->next;
}
if (cur == NULL)
printf("NULL");
printf("\n");
}
//尾插--插入到指向NULL的节点的后一个节点位置
void SLPushBack(SListNode** ppst, SLDataType x)
{
assert(ppst);
SListNode* newnode = CreateSListNode(x);
//尾插的话需要找尾
if (*ppst != NULL)
{
SListNode* tail = *ppst;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
else
{
//传的是空指针--链表的节点为空
*ppst = newnode;
}
}
//头插
void SLPushFront(SListNode** ppst, SLDataType x)
{
assert(ppst);
//新的节点成为链表的头
SListNode* newnode = CreateSListNode(x);
newnode->next = *ppst;
*ppst = newnode;
}
//尾删
void SLPopBack(SListNode** ppst)
{
assert(ppst);
assert(*ppst);
//代表链表有多个节点
if ((*ppst)->next != NULL)
{
//找到删除节点的前一个位置,更改链接
//找尾
SListNode* cur = *ppst;
SListNode* prev = cur;
while (cur->next != NULL)
{
//prev保存的是cur的前一个位置
prev = cur;
cur = cur->next;
}
//cur->next==NULL
prev->next = NULL;
free(cur);
cur = NULL;
}
else
{
free(*ppst);
*ppst = NULL;
}
}
//头删
void SLPopFront(SListNode** ppst)
{
assert(ppst);
//空链表不用删除
assert(*ppst);
SListNode* head = *ppst;
//保留头节点的下一个节点位置
SListNode* nextnode = head->next;
//更改链接
*ppst = nextnode;
free(head);
head = NULL;
}
//查找指定节点
SListNode* SLFind(SListNode* pst, SLDataType x)
{
assert(pst);
SListNode* cur = pst;
while (cur)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//删除pos位置的节点--配合查找函数使用
void SLErase(SListNode** ppst, SLDataType x)
{
assert(ppst);
assert(*ppst);
SListNode* pos = SLFind(*ppst,x);
if (pos == *ppst)
{
//头删
SLPopFront(ppst);
return;
}
SListNode* cur = *ppst;
SListNode* prev = cur;
//找到pos前面的节点
while (cur!= pos)
{
prev = cur;
cur = cur->next;
}
//更改链接,prev---cur(pos)---pos->next
prev->next = pos->next;
free(cur);
cur = NULL;
}
//在pos前插入--pos节点数值为val,配合查找函数使用
void SLInsert(SListNode** ppst, SLDataType val, SLDataType x)
{
assert(ppst);
//先找到pos位置
SListNode* pos = SLFind(*ppst, val);
if (*ppst==pos)
{
//头插
SLPushFront(ppst, x);
return;
}
SListNode* newnode = CreateSListNode(x);
SListNode* cur = *ppst;
SListNode* prev = cur;
//找到pos前面的节点
while (cur!= pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newnode;
newnode->next = pos;
}
//销毁
void SLDestroy(SListNode** ppst)
{
assert(ppst);
assert(*ppst);
SListNode* cur = *ppst;
while (cur)
{
SListNode* nextnode = cur->next;
free(cur);
cur = nextnode;
}
free(*ppst);
free(ppst);
}
函数讲解
3.Test.c
#include "SList.h"
int main()
{
SListNode* n1 = CreateSListNode(1);
SListNode* n2 = CreateSListNode(2);
SListNode* n3 = CreateSListNode(3);
SListNode* n4 = CreateSListNode(4);
SListNode* n5 = CreateSListNode(5);
SListNode* n6 = CreateSListNode(6);
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = n6;
n6->next = NULL;
//中间省略掉即是使用相关的函数进行操作
//...
return 0;
}
注:该测试代码同样使用于Leetcode链表OJ测试,单链表构建较为麻烦,可直接手动构建一份测试用例,保存于桌面,便于后期调试。
总结
单链表,数据结构的基本数据存储结构,其实现较顺序表复杂些许,因其结构中存放有指针,其访问下一个节点使用的是指针的"->", 而非寻常元素访问,但其整体难度较低,无需赘述,实现者需要注意其参数,因为对于一级指针的修改需要二级指针,这一点对于单链表实现至关重要。