🌈个人主页 :@ꪔ小林Y
✨个人专栏 :《C++小白闯关日记》,《C语言小白闯关日记》《数据结构入门------从原理到实战》
🍀代码信条 :每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应
目录
学习完上节可知,顺序表的存储空间是静态分配的,在程序执行前必须明确规定它的存储规模,设定过大造成浪费,设定过小,造成溢出,且顺序表在头部/中间的插入操作所需的时间复杂度为O(N),可见若对线性表的长度或存储规模难以估计时,则不宜采用顺序表。那么有没有一种数据结构头部插入删除的 时间复杂度为O(1),且不需要增容且不存在空间浪费 呢?
因此就有了链表的学习。
链表
链表可分为单链表 ,循环单链表 和双向链表
单链表
1.逻辑结构:线性的 ;物理结构:不一定是线性的
2.概念:链表是一种物理存储结构上非连续 ,非顺序的存储结构,数据元素的逻辑顺序 是通过链表中的指针链接次序实现的。
链表由一个一个的节点组成,节点有两个组成部分:保存的数据 ;指针 (始终保存下一个节点的地址)
因而在定义链表的数据结构时就是定义结点的结构。
1.链表的定义
- 头文件"SList.h"
c
#include<stdio.h>
#include<stdlib.h>
//链表的结构
typedef int SLTDataType;
struct SListNode
{
SLTDataType data;
struct SListNode* next;//指向下一个结点的地址
};
typedef struct SListNode SLTNode;
//打印链表
void SLTPrint(SLTNode* phead);
- 实现文件"SList.c"
c
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
- 测试文件"test.c"
c
#include"SList.h"
int test01()
{
//创建一个链表
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
node2->data = 2;
node3->data = 3;
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印链表
SLTNode* plist = node1;
SLTPrint(plist);
}
int main()
{
test01();
return 0;
}
2.在链表中插入数据
(1)尾插:
phead始终指向头节点,创建一个新变量pcur循环遍历链表查找尾节点;如果链表为空,则不需要找尾巴,直接让phead指向newnode,所以这里需要判断一下链表是否为空
- 头文件"SList.h"
c
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
- 实现文件"SList.c"
c
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(1);
}
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 {
SLTNode* ptail = *pphead;
while (ptail->next != NULL)
{
ptail = ptail->next;
}
//找到了尾节点ptail newnode
ptail->next = newnode;
}
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
//形参的改变要改变实参
// 因而注意这里传的是地址,要加&
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
时间复杂度O(N)
(2)头插:

- 头文件"SList.h"
c
void SLTPushFront(SLTNode** pphead, SLTDataType x);
- 实现文件"SList.c"
c
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请新节点
SLTNode* newnode = SLTBuyNode(x);
newnode->next=*pphead;
*pphead = newnode;
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
SLTPushFront(&plist, 1);
SLTPrint(plist);
SLTPushFront(&plist, 2);
SLTPrint(plist);
SLTPushFront(&plist, 3);
SLTPrint(plist);
SLTPushFront(&plist, 4);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
时间复杂度O(1)
3.在链表中删除数据
(1)尾删
不仅要找尾节点用ptail标记,还要找尾节点的前一个指针用prev标记
尾删时只有一个节点则需要特殊处理,不需要要创建新指针,直接释放掉即可
- 头文件"SList.h"
c
void SLTPopBack(SLTNode** pphead);
- 实现文件"SList.c"
c
//尾删
void SLTPopBack(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
//链表只有一个结点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else {
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//尾删
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
}
int main()
{
//test01();
test02();
return 0;
}
时间复杂度O(N)

(2)头删
- 头文件"SList.h"
c
//头删
void SLTPopFront(SLTNode** pphead);
- 实现文件"SList.c"
c
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//头删
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
时间复杂度O(N)

4.在链表中查找数据
- 头文件"SList.h"
c
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
- 实现文件"SList.c"
c
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
//从第一个链表遍历至整个链表查找数据
SLTNode* pcur =phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//未找到
return NULL;
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//查找
SLTNode* pos=SLTFind(plist, 3);
if (pos)
{
printf("找到了\n");
}
else {
printf("未找到\n");
}
}
int main()
{
test02();
return 0;
}
5.在指定位置之前插入数据

若插入点在头节点前,直接头插,不需要前后建立联系:
- 实现文件"SList.c"
c
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos);
SLTNode* newnode = SLTBuyNode(x);
//pos指向头节点
if (pos == *pphead)
{
//头插
SLTPushFront(pphead, x);
}
else{
//找pos的前一个节点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev newnode pos
prev->next = newnode;
newnode->next = pos;
}
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//指定节点前插入
SLTNode* pos=SLTFind(plist, 3);
SLTInsert(&plist, pos, 100);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
运行:
时间复杂度O(N)
6.在指定位置之后插入数据

- 实现文件"SList.c"
c
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
pos->next = newnode;
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//指定节点后插入
SLTNode* pos=SLTFind(plist, 3);
SLTInsertAfter( pos, 100);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
运行结果:
时间复杂度O(1)
7.删除pos节点

若pos刚好是头节点,只需头删
- 实现文件"SList.c"
c
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && pos);
//pos刚好就是头节点------直接头删
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//删除pos节点
SLTNode* pos=SLTFind(plist, 3);
SLTErase(&plist,pos);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
运行结果:
8.删除pos之后的节点

- 实现文件"SList.c"
c
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
//注意pos的下一个节点也不能为空
//pos del del->next
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//删除pos后节点
SLTNode* pos=SLTFind(plist, 3);
SLTEraseAfter( pos);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
运行结果:
9.销毁链表
一个一个销毁:
next指针指向下一个节点,释放pcur指向的节点,释放完成,pcur继续往下走,跳出循环
next指针是在循环里定义的,跳出循环,自动销毁。函数执行结束,pcur自动销毁
- 实现文件"SList.c"
c
//销毁链表
void SListDestroy(SLTNode** pphead)
{
assert(pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
- 测试文件"test.c"
c
void test02()
{
//创建空链表
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//销毁链表
SLTNode* pos=SLTFind(plist, 3);
SListDestroy(&plist);
SLTPrint(plist);
}
int main()
{
test02();
return 0;
}
运行结果:
🎊本期数据结构的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流------ 技术路上多碰撞,才能更快进步
觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习,带你从理论走进实操。下期不见不散✌️