C数据结构初阶详解——链表专题
中间/头部的插⼊删除,时间复杂度为O(N)增容需要申请新空间,拷⻉数据,释放旧空间。会有不⼩的消耗。增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。思考:如何解决以上问题呢?答案就是使用另一种数据结构——链表单链表实现分三个文件设计,实现功能模块化与代码解耦。SList.h 负责声
若文中出现疏漏、不足之处,还望广大读者批评指正
接下来我会持续更新C语言数据结构、Java语法、Java数据结构、MySQL、JavaEE、微服务、Redis等等内容的知识点整理。后续我也会精心制作算法解析、项目经验系列内容,内容绝对干货。相信这些文章能够成为我和大家的“葵花宝典”,喜欢的话就关注一下吧!敬请期待!
PS:下节预告——单链表经典算法OJ题目详解!
前言:顺序表的问题及思考
- 中间/头部的插⼊删除,时间复杂度为O(N)
- 增容需要申请新空间,拷⻉数据,释放旧空间。会有不⼩的消耗。
- 增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?
答案就是使用另一种数据结构——链表
一、链表的概念及结构
这部分了解的话就可以直接学习代码实现模块
概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表的结构跟⽕⻋⻋厢相似,淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉/加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。

- 与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点”
节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。
图中指针变量plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希
望plist“指向”第⼆个节点时,只需要修改plist保存的内容为0x0012FFA0。
补充说明:
1、链式结构在逻辑上是连续的,在物理结构上不⼀定连续
2、节点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
二、单链表的实现
概述
单链表实现分三个文件设计,实现功能模块化与代码解耦。
SList.h 负责声明接口, SList.c 负责实现逻辑, test.c 负责验证功能,结构清晰且便于维护。大家可以跟着注释梳理下代码结构脉络
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;//定义储存数据类型
typedef struct SListNode//定义单链表
{
SLTDataType data;//储存数据
struct SListNode* next;//储存下一个节点的地址
}SLTNode;
//创建节点并赋值
SLTNode* SLTBuyNode(SLTDataType x);
//打印链表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表(释放动态申请的内存)
void SListDesTroy(SLTNode** pphead);
SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//创建节点并赋值
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTDataType));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
//申请空间后赋值,next值后续据需求更改
newnode->data = x;
newnode->next = NULL;
return newnode;//把节点地址返回以便后续操作使用
}
//打印链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
if (pcur == NULL)//链表为空
{
printf("链表为空!\n");
return;
}
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;//每打印完一个数据就通过pcur->next访问到下一个节点
}
printf("NULL\n");
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);//pphead是用来访问指向节点的那个地址的指针,
//如果pphead都为NULL就无任何意义了
SLTNode* newnode = SLTBuyNode(x);//创建节点,并接受其地址
if (*pphead == NULL)//若链表为空,则直接将地址赋给头节点
{
*pphead = newnode;
}
else
{
//若链表不为空,则要找到尾节点
SLTNode* ptail = *pphead;
while (ptail->next)//ptail->next:尾节点的next值为NULL
{
ptail = ptail->next;
}
ptail->next = newnode;//将新节点地址赋给原尾结点的next,这样两个节点就链接成功
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;//头插时即使遇到空链表也正常运行
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);//删除数据时链表也不能为空
if ((*pphead)->next==NULL)//注意:->的优先级比*高,所以*pphead要加括号
{
//处理只有一个节点的情况
free(*pphead);
*pphead = NULL;
}
else
{
//找尾节点
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;//储存尾节点的前一个节点
while (ptail ->next)//若只有一个节点,ptail->就访问了空指针
{
prev = ptail;
ptail = ptail->next;//若此时遍历到了尾节点,while再判断时就为NULL(假)
} //而ptail也储存了前一个节点地址
free(ptail);
ptail = NULL;
prev->next = NULL;//此时ptail是尾节点,next值应置为空
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next ;
free(*pphead);
*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);//链表不能为空
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//遍历完后还没有就返回NULL
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos);
SLTNode* newnode = SLTBuyNode(x);
if ((*pphead)->next == NULL)//节点只有一个
{
SLTPushFront(pphead, x);//直接调用头插
}
SLTNode* prev = *pphead;
while (prev->next != pos)//找到pos之前的节点
{
prev = prev->next;
}
//将节点链接起来
prev->next = newnode;
newnode->next = pos;
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos是头结点/pos不是头结点
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)//找到pos之前的节点
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos&&pos->next);
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
SLTNode* next =NULL;
while (pcur)
{
next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//void SListTest01()
//{
// //链表是由一个一个的节点组成
// //创建几个节点
// SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
// node1->data = 1;
//
// SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
// node2->data = 2;
//
// SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
// node3->data = 3;
//
// SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
// node4->data = 4;
//
// //将四个节点连接起来
// node1->next = node2;
// node2->next = node3;
// node3->next = node4;
// node4->next = NULL;
//
// //调用链表的打印
// SLTNode* plist = node1;
// SLTPrint(plist);
//}
void SListTest02()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist); // 1->2->3->4->NULL
SLTPrint(plist);
//测试查找
SLTNode* find = SLTFind(plist, 1);
//SLTInsert(&plist, find, 11);
//SLTInsertAfter(find, 11);
////删除pos节点
//SLTErase(&plist, find);
//SLTEraseAfter(find);
//SLTPrint(plist);
if (find == NULL)
{
printf("没有找到!\n");
}
else {
printf("找到了!\n");
}
//SLTPushBack(NULL, 5);
//
////测试头插
//SLTPushFront(&plist, 6);
//SLTPrint(plist);
//SLTPushFront(&plist, 7);
//SLTPrint(plist);
//SLTPushFront(&plist, 8);
//SLTPrint(plist);
////测试头删
//SLTPopFront(&plist);
//SLTPrint(plist);// 2->3->4->NULL
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
//SLTPopFront(&plist);
//SLTPrint(plist);
SListDesTroy(&plist);
}
int main()
{
//SListTest01();
SListTest02();
return 0;
}
觉得文章对你有帮助的话就点个赞,收藏起来这份免费的资料吧!也欢迎大家在评论区讨论技术、经验。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)