当前位置: 首页 > news >正文

【数据结构初阶】--单链表(一)

🔥个人主页:@草莓熊Lotso

🎬作者简介:C++研发方向学习者

📖个人专栏: 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。  

前言:前面我们完成了顺序表的学习以及代码实现,大家一定要自己去实现一遍试试看并且画图理解。那么这篇博客我将会继续为大家分享单链表的定义以及其中打印,尾插,头擦,尾删,头删等接口的实现。还是一样,先分部分实现,最后展式总的代码。


目录

一.单链表的概念

二.单链表的打印

三.单链表的尾插

四.单链表的头插 

五.单链表的尾删

六.单链表的头删

七.代码展现

SList.h:

SList.c:

 test.c:


一.单链表的概念

--在之前我们学习了逻辑结构和物理结构都是线性的顺序表,但是我们会发现顺序表有以下3个比较明显的缺陷

  1. 中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间,有不小的消耗
  3. 增容一般呈两倍的增长,会有一定的空间浪费

那么我们的链表刚好可以使头部插入删除的时间复杂度为O(1),不需要增容也不存在空间的浪费。但是这里需要声明一下,每个数据结构都是有它的优势和缺陷的。

链表:链表是一种是一种物理存储结构上非连续,非顺序的存储结构,但它的逻辑结构还是线性的。我们可以把链表想象成火车的一节节车厢链接在一起,具体形式如同所示这里不过多的去讲解了,这里的图是逻辑结构,正常来说链表不一定是这样连续的,而是在堆区中随意存放的,通过存储的地址找到下一个结点。

我们今天一起来实现一个单链表,链表是由一个一个节点组成的,它的节点由两个组成部分

  1. 保存的数据
  2. 指针,存放的是下一个结点的地址 

我们先在.h文件中定义一个单链表,分3个文件以及重命名的好处啥的在顺序表中都讲过了,这里就不说了。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

二.单链表的打印

 --为了方便后续的观察,我们先来实现一个单链表的打印

SList.c:

//打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;//移动pcur,保证头指针phead位置不变while (pcur!= NULL){printf("%d -> ", pcur->data);pcur = pcur->next;//pcur往前走}printf("NULL\n");
}

重点提示:

 打印的实现还是很简单的,这里先用一个pcur指针存下头指针,移动它去打印,打印完一个数据后就利用pcur->next往前走,直到pcur==NULL。

--我们在test.c文件中创建几个节点,并且把它们打印出来看看吧,一定注意看注释 

test.c: 

#include"SList.h"int test1()
{//创建节点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()
{test1();return 0;
}

--测试完成没问题,能正确打印出想要的链表 ,退出码为0。


三.单链表的尾插

--在实现单链表的尾插之前,我们先封装一个申请新节点的函数,后续头插等接口的实现也会用上

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newcode = (SLTNode*)malloc(sizeof(SLTNode));newcode->data = x;newcode->next = NULL;return newcode;
}

SList.c:

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//申请新节点SLTNode* newcode =SLTBuyNode(x);//如果头节点为空if (*pphead == NULL){*pphead = newcode;}else {SLTNode* ptail = *pphead;//找到尾节点while (ptail->next != NULL){ptail = ptail->next;}//找到了之后链接新节点ptail->next = newcode;}
}

重点提示: 

  • 我们先判断当头结点为空时,是无法直接尾插的,我们直接令新节点为头结点就行了
  • 再就是我们需要找到尾结点,利用ptail从头开始往后找,如图所示,直到ptail->next==NULL时结束
  • 找到尾结点后,将新节点链接上去就行了
  • 还有就是传参数的时候,一定要传的是plist的地址,用二级指针接收。这样形参的修改才能影响实参的修改

test.c:

#include"SList.h"int test1()
{//创建节点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);
}void test2()
{//头结点SLTNode* plist =NULL;//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);
}
int main()
{//test1();test2();return 0;
}

--测试完成,打印没有问题,退出码为0。 


四.单链表的头插 

SList.c:

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请新节点SLTNode* newcode = SLTBuyNode(x);newcode->next = *pphead;*pphead = newcode;
}

重点提示:

先断言一下pphead不能为空,再就是先用申请的新节点链接上原来的头指针,再令新节点成为头指针就可以了

test.c: 

#include"SList.h"int test1()
{//创建节点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);
}void test2()
{//头结点SLTNode* plist =NULL;//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//头插SLTPushFront(&plist, 5);SLTPrint(plist);
}
int main()
{//test1();test2();return 0;
}

--测试完成,打印没有问题,退出码为0


五.单链表的尾删

SList.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 != NULL){prev = ptail;ptail = ptail->next;}//prev ptailprev->next = NULL;free(ptail);ptail = NULL;}
}

重点提示: 

  • 首先是断言,pphead不能为空,链表也不可以为空
  • 特别判断只有一个节点的情况,这题我们还需要找到尾结点的前一个结点,如果只有一个节点的话直接释放掉就好了
  • 找尾节点的同时找到尾节点的前一个节点,每次尾节点向前走之前,先让prev指向其原来的位置
  • 最后直接让prev->next=NULL,释放掉ptail就好了

test.c:

#include"SList.h"int test1()
{//创建节点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);
}void test2()
{//头结点SLTNode* plist =NULL;//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//头插SLTPushFront(&plist, 5);SLTPrint(plist);//尾删SLTPopBack(&plist);SLTPrint(plist);
}
int main()
{//test1();test2();return 0;
}

--测试完成,删掉了4,打印没有问题,退出码为0


六.单链表的头删

SList.c:

//头删
void SLTPopFront(SLTNode** pphead)
{//链表不能为空assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

重点提示: 

断言和尾删一样,这里主要就是先定义一个next记录phead的下一个节点,再直接free掉*pphead。最后让next成为新的头节点就可以了

test.c:

#include"SList.h"int test1()
{//创建节点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);
}void test2()
{//头结点SLTNode* plist =NULL;//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//头插SLTPushFront(&plist, 5);SLTPrint(plist);//尾删SLTPopBack(&plist);SLTPrint(plist);//头删SLTPopFront(&plist);SLTPrint(plist);}
int main()
{//test1();test2();return 0;
}

--测试完成,删掉了头的5打印没问题,退出码为0


七.代码展现

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;//打印
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode * *pphead);
//头删
void SLTPopFront(SLTNode * *pphead);

SList.c:

#include"SList.h"//打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;//移动pcur,保证头指针phead位置不变while (pcur!= NULL){printf("%d -> ", pcur->data);pcur = pcur->next;}printf("NULL\n");
}
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newcode = (SLTNode*)malloc(sizeof(SLTNode));newcode->data = x;newcode->next = NULL;return newcode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//申请新节点SLTNode* newcode =SLTBuyNode(x);//如果头节点为空if (*pphead == NULL){*pphead = newcode;}else {SLTNode* ptail = *pphead;//找到尾节点while (ptail->next != NULL){ptail = ptail->next;}//找到了之后链接新节点ptail->next = newcode;}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//申请新节点SLTNode* newcode = SLTBuyNode(x);newcode->next = *pphead;*pphead = newcode;
}
//尾删
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 != NULL){prev = ptail;ptail = ptail->next;}//prev ptailprev->next = NULL;free(ptail);ptail = NULL;}
}
//头删
void SLTPopFront(SLTNode** pphead)
{//链表不能为空assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

 test.c:

#include"SList.h"int test1()
{//创建节点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);
}void test2()
{//头结点SLTNode* plist =NULL;//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//头插SLTPushFront(&plist, 5);SLTPrint(plist);//尾删SLTPopBack(&plist);SLTPrint(plist);//头删SLTPopFront(&plist);SLTPrint(plist);}
int main()
{//test1();test2();return 0;
}

往期回顾: 

【数据结构初阶】--算法复杂度的深度解析

【数据结构初阶】--顺序表(一)

【数据结构初阶】--顺序表(二)

【数据结构初阶】--顺序表(三)

结语:在这篇博客中,我们完成了单链表的打印,尾/头插,尾/头删这些接口的实现,但是我们单链表还有部分接口没有实现,由于篇幅原因,博主将在下一篇中继续带大家实现单链表指定位置插入,删除,查找,修改等接口。如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

http://www.lryc.cn/news/586449.html

相关文章:

  • ICCV2025 特征点检测 图像匹配 RIPE
  • dify 用postman调试参数注意
  • 【数据结构初阶】--顺序表(二)
  • Compose笔记(三十四)--LayoutModifier
  • Raft-领导者选举
  • 基于YOLO11的垃圾分类AI模型训练实战
  • C语言课程设计--电子万年历
  • 第十八天,7月12日,八股
  • Agent 设计模式
  • 卫星通信终端天线的5种对星模式之二:DVB跟踪
  • Mamba架构的模型 (内容由deepseek辅助汇总)
  • 关于赛灵思的petalinux zynqmp.dtsi文件的理解
  • C++ Primer(第5版)- Chapter 7. Classes -001
  • Web学习笔记3
  • Windows环境下JS计时器精度差异揭秘
  • Qt:QCustomPlot类介绍
  • LangChain极速入门:用Python构建AI应用的新范式
  • zcbus使用数据抽取相当数据量实况
  • Scrapy爬虫中间件核心技术解析:定制化爬虫的神经中枢
  • CCS-MSPM0G3507-2-基础篇-定时器中断
  • 69 局部变量的空间分配
  • 通俗范畴论13 鸡与蛋的故事番外篇
  • C++类模板继承部分知识及测试代码
  • Golang操作MySQL json字段优雅写法
  • Hap包引用的Hsp报签名错误怎么解决
  • 【数据分析】03 - Matplotlib
  • LangChain 内存(Memory)
  • Java 大视界 -- Java 大数据机器学习模型在电商用户复购行为预测与客户关系维护中的应用(343)
  • C语言基础知识--动态内存管理
  • AD芯片(模数转换器)的有效位数(ENOB)