数据结构 | 单链表专题【详解】
数据结构 | 单链表专题【详解】
文章目录
- 数据结构 | 单链表专题【详解】
- 链表的概念及结构
- 单链表的实现
- 头文件
- 打印
- 尾插
- 头插
- 尾删
- 头删
- 查找
- 在指定位置之前插入数据
- 在指定位置之后插入数据
- 删除pos节点
- 删除pos之后的节点
- 销毁链表
顺序表遗留下来的问题
- 中间/头部的插⼊删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容⼀般是呈2倍的增长,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后⾯没有数据插入了,那么就浪费了95个数据空间。
那么如何解决以上问题呢?
那这个时候我们就要开始我们的链表专题了~~
链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
-
链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。
-
车厢是独立存在的,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?
最简单的做法:每节车厢里都放一把下一节车厢的钥匙。
在链表里,每节“车厢”是什么样的呢?我们来看下面:

-
与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点”
-
节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。
-
图中指针变量 plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。
为什么还需要指针变量来保存下一个节点的位置?
- 链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。
结合前面学到的结构体知识,我们可以给出每个节点对应的结构体代码:
假设当前保存的节点为整型:
struct SListNode
{int val;struct SListNode* next;
}
-
当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。
-
当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。
给定的链表结构中,如何实现节点从头到尾的打印?
思考:当我们想保存的数据类型为字符型、浮点型或者其他自定义的类型时,该如何修改?
补充说明:
1、链式机构在逻辑上是连续的,在物理结构上不一定连续
2、节点一般是从堆上申请的
3、从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续
单链表的实现
我们老样子,先来定义结构体,要用的头文件引入~~
头文件
SList
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int SLNDataType;typedef struct SListNode
{SLNDataType val;struct SListNode* next;
}SLNode;
我们要实现哪些功能呢?
//打印
void SLTPrint(SLNode* phead);//尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);//头插
void SLTPushFront(SLNode** pphead, SLNDataType x);//尾删
void SLTPopBack(SLNode** pphead);//头删
void SLTPopFront(SLNode** pphead);//查找
SLNode* SListFind(SLNode** phead, SLNDataType x);//在指定位置之前插入数据
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);//删除pos节点
void SLTErase(SLNode** pphead, SLNode* pos);//在指定位置之后插入数据
void SLTInsertAfter(SLNode* pos, SLNDataType x);//删除pos之后的节点
void SLTEraseAfter(SLNode* pos);//销毁链表
void SListDesTroy(SLNode** pphead);
好接下来我们开始实现~~
SList.c
打印
- 这个很好实现,
void SLTPrint(SLNode* phead)
{//将头节点的地址保存到cur中SLNode* cur = phead;while (cur != NULL){printf("%d-> ", cur->val);//cur是保存下一个节点的地址cur = cur->next;}printf("NULL\n");
}
- 我们来测试一下,这里链表中什么都没有,我们可以自己手动创造几个数据
slttest1()
{//测试打印SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));node1->val = 1;SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));node2->val = 2;SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));node3->val = 3;SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));node4->val = 4;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;SLNode* plist = node1;SLTPrint(plist);
}
- 可以看到是可以打印出来的~~

尾插
- 这里的尾插是不是需要先申请空间,然后再将申请出来的空间赋值
- 还需要先判断链表为不为,如果是空,就将新开辟的空间赋给头
下面是代码:
- 扩容我们后面可能还要用,所以我们就给他分装成一个函数
//开辟空间
SLNode* CreateNode(SLNDataType x)
{//malloc一个新的空间SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}//申请出来的空间直接赋值newnode->val = x;//下一个next赋值为空newnode->next = NULL;//返回一个新的空间return newnode;
}
void SLTPushBack(SLNode** pphead, SLNDataType x)
{//这里申请空间SLNode* newnode = CreateNode(x);//判断头是否为空,如果为空,就将新开辟的空间赋给头if (*pphead == NULL){*pphead = newnode;}else{//将头指向变量尾SLNode* tail = *pphead;//找尾while (tail->next != NULL){//找到了尾然后继续tail = tail->next;}//把那个返回的空间赋值给尾的nexttail->next = newnode;}
}

头插
- 这里先申请节点,然后让新的节点和头节点连接起来,最后再让新的节点成为头节点
- 这里如果链表为空也是可以完成任务的~~
void SLTPushFront(SLNode** pphead, SLNDataType x)
{//申请节点SLNode* newnode = CreateNode(x);//让新节点跟头节点连接起来newnode->next = *pphead;//让新的节点成为头节点*pphead = newnode;
}
- 可以看到,头插~~

尾删
- 首先找尾,然而找尾就要找到前一个节点,掷为空,然后再进行
free - 链表为空的时候不能尾删
void SLTPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//当前链表只有一个节点的时候if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//定义一个快慢指针SLNode* ptail = *pphead;SLNode* prev = NULL;//ptail的next不等于NULL就一直找while (ptail->next != NULL){//将ptail的地址赋给慢指针prevprev = ptail;//ptail继续往下找ptail = ptail->next;}free(ptail);prev->next = NULL;}
}

头删
- 使用临时节点指向头节点
- 然后将头节点指向新的头
- 把临时指针指向的节点释放掉
void SLTPopFront(SLNode** pphead)
{assert(pphead);assert(*pphead);//定义一个临时指针,将第二个节点赋值给临时指针SLNode* next = (*pphead)->next;//释放头节点free(*pphead);//将临时节点变成头节点*pphead = next;
}

查找
- 这里我们传地址就是要保持接口的一致性
- 所以我们这里写二级指针
- 这里很简单,不再介绍
SLNode* SListFind(SLNode** phead, SLNDataType x)
{assert(phead);SLNode* pcur = *phead;while (pcur != NULL){if (pcur->val == x){return pcur;}pcur = pcur->next;}return NULL;
}
在指定位置之前插入数据
- 在插入前,我们要向申请一块空间
- 先找到要插入的地方前一个节点
- 处理前一个和后一个的连接关系~~
- 链表不能为空,pos也不能为空
- 还要处理只有一个节点和只有一个节点的情况下,直接将新申请下来的节点赋给头
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);//链表不能为空,pos也不能为空assert(pos);assert(*pphead);SLNode* node = CreateNode(x);//处理只有一个节点和只有一个节点的情况下,直接将新申请下来的节点赋给头if ((*pphead)->next == NULL || pos == *pphead){node->next = *pphead;*pphead = node;return;}SLNode* prev = *pphead;//找pos的前一个节点while (prev->next != pos){prev = prev->next;}//连接node->next = pos;prev->next = node;
}

在指定位置之后插入数据
- 这里可以直接申请空间后赋值,然后直接连接~~
void SLTInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* node = CreateNode(x);//连接node->next = pos->next;pos->next = node;
}

删除pos节点
- 首先找到前一个节点,将next的指针指向下一个,再把pos的节点删除~~
- 当也要判断pos是不是头
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);//判断pos是不是头if (pos == *pphead){*pphead = (*pphead)->next;free(pos);return;}//找pos的前一个节点SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;
}

删除pos之后的节点
- 首先要将pos的节点保存下来,然后改变pos的指向,最后释放
void SLTEraseAfter(SLNode* pos)
{assert(pos && pos->next);SLNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

销毁链表
- 销毁节点之前,要把下一个节点保存起来,然后找下一个free,句许循环
void SListDesTroy(SLNode** pphead)
{assert(pphead);SLNode* pcur = *pphead;while (pcur != NULL){SLNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

好了,以上就是单链表的所有内容了,如果问题欢迎在评论区指正,一起交流~~
感谢大家的收看,希望我的文章可以帮助到正在阅读的你🌹🌹🌹
相关文章:
数据结构 | 单链表专题【详解】
数据结构 | 单链表专题【详解】 文章目录 数据结构 | 单链表专题【详解】链表的概念及结构单链表的实现头文件打印尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除pos节点删除pos之后的节点销毁链表 顺序表遗留下来的问题 中间/头部的插⼊删除ÿ…...
前端基础之BOM和DOM
目录 一、前戏 window对象 window的子对象 navigator对象(了解即可) screen对象(了解即可) history对象(了解即可) location对象 弹出框 计时相关 二、DOM HTML DOM 树 查找标签 直接查找 间…...
第23期 | GPTSecurity周报
GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区,集成了生成预训练 Transformer(GPT)、人工智能生成内容(AIGC)以及大型语言模型(LLM)等安全领域应用的知识。在这里,您可以…...
VSCode实用远程主机功能
作为嵌入式开发者,经常在各种系统平台或者开发工具之间切换,比如你的代码在Linux虚拟机上,如果不习惯在Linux下用IDE,那么我尝试将Linux的目录通过samba共享出来,在windows下用网络映射盘的方式映射出来,VS…...
并发编程: 2. 线程管控
给定一个线程,只要令std::thread对象与之关联,就能管控该线程的几乎每个细节。 2.1 线程的基本管控 2.1.1 发起线程 线程通过构建std::thread对象而启动,该对象指明线程要运行的任务(函数)。简单的任务,…...
使用 Python、XML 和 YAML 编写 ROS 2 Launch 文件
系列文章目录 ROS2 重要概念 ament_cmake_python 用户文档 ROS2 ament_cmake 用户文档 使用 rosdep 管理 ROS 2 依赖项 文章目录 系列文章目录前言一、Launch 文件示例1.1 Python 版本1.2 XML 版本1.3 YAML 版本 二、从命令行使用 Launch 文件1. Launching2. 设置参数3. 控制海…...
SpringCloud 微服务全栈体系(十)
第十章 RabbitMQ 一、初识 MQ 1. 同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得…...
[原创]Cadence17.4,win64系统,构建CIS库
目录 1、背景介绍 2、具体操作流程 3、遇到问题、分析鉴别问题、解决问题 4、借鉴链接并评论 1、背景介绍 CIS库,绘制原理图很方便,但是需要在Cadence软件与数据库之间建立联系,但是一直不成功,花费半天时间才搞明白如何建立关系并…...
Python 海龟绘图基础教学教案(一)
Python 海龟绘图——第 1 题 题目:绘制下面的图形 解析: 考察 turtle 基本命令,绘制直线,使用 forward,可缩写为 fd。 答案: import turtle as t t.fd(100) # 或者使用 t.forward(100) t.done() Python 海…...
JUC并发编程系列(一):Java线程
前言 JUC并发编程是Java程序猿必备的知识技能,只有深入理解并发过程中的一些原则、概念以及相应源码原理才能更好的理解软件开发的流程。在这篇文章中荔枝会梳理并发编程的基础,整理有关Java线程以及线程死锁的知识,希望能够帮助到有需要的小…...
双向链表相关代码
DLinkList.h // // DLinkList.hpp // FirstP // // Created by 赫赫 on 2023/10/31. // 双向链表相关代码:双向链表、循环双向链表#ifndef DLinkList_hpp #define DLinkList_hpp #include <stdio.h> #include <stdlib.h> #include <iostream>…...
[每周一更]-(第70期):常用的GIT操作命令
1、增删文件 # 添加当前目录的所有文件到暂存区 $ git add .# 添加指定文件到暂存区 $ git add <file1> <file2> ...# 添加指定目录到暂存区,包括其子目录 $ git add <dir># 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file…...
Leetcode-283 移动零
count记录0的个数,不为0的数取代0位置,最后把剩余位置置零 class Solution {public void moveZeroes(int[] nums) {int count 0;for(int i0;i<nums.length;i){if(nums[i]0){count;}else{nums[i-count]nums[i];}}for(int inums.length-count;i<nu…...
爱上C语言:函数递归,青蛙跳台阶图文详解
🚀 作者:阿辉不一般 🚀 你说呢:生活本来沉闷,但跑起来就有风 🚀 专栏:爱上C语言 🚀作图工具:draw.io(免费开源的作图网站) 如果觉得文章对你有帮助的话,还请…...
Pycharm 对容器中的 Python 程序断点远程调试
pycharm如何连接远程服务器的docker容器有两种方法: 第一种:pycharm通过ssh连接已在运行中的docker容器 第二种:pycharm连接docker镜像,pycharm运行代码再自动创建容器 本文是第一种方法的教程,第二种请点击以上的链接…...
自动驾驶行业观察之2023上海车展-----车企发展趋势(3)
合资\外资发展 宝马:i7、iX1新车亮相,未来将持续发力电动化、数字化(座舱) 宝马在本次车展重点展示了电动化产品,新发车型为i7 M70L、iX1、及i vision Dee概念车等车型。 • 展示重点:电动化数字化&#…...
day55【动态规划子序列】392.判断子序列 115.不同的子序列
文章目录 392.判断子序列115.不同的子序列 392.判断子序列 题目链接:力扣链接 讲解链接:代码随想录讲解链接 题意:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不…...
c语言中磁盘文件的分类
#include <stdio.h> /*磁盘文件的分类: * 一个文件通常是磁盘上一段命名的存储区计算机的存储在物理上是二进制的, * 所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储 * 从用户或者操作系统使用的角度(…...
Unity适配微信
使用的是微信开发的插件 GitHub - wechat-miniprogram/minigame-unity-webgl-transform 路径相关: Unity:Application.streamingAssetsPath --> 配置的cdn路径StreamingAssets...
虚拟机本地磁盘在线扩容
背景 虚拟机本地盘对于host物理机来说就是一个LVM卷,虚拟化(libvirt+kvm_qemu)已经支持虚拟机磁盘在线调整,配合物理机lvm管理工具可实现云场景下虚拟机磁盘在线扩容功能。环境检查 (1)虚拟机本地盘信息 <disk type=block device=disk><driver...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...



