单链表总结提升

这篇博客讲解数据结构中的单链表,包括单链表的基础知识和我对链表实现的总结理解,希望可以帮助到正在学习的小伙伴,也希望得到小伙伴们的关注和支持哦~

目录

1.单链表的概念

1.2顺序表和链表的对比

顺序表:

链表: 

链表相对于顺序表:

2.单链表的结构

3.单链表的实现

总结单链表函数书写要点:

一、关于是否需要分支: 

二、关于删除操作:

三、关于为什么用二级指针 


1.单链表的概念

 在之前顺序表的博客中讲解过线性表

  • 线性表在逻辑上是线性结构,也就说是连续的一条直线。
  • 但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

顺序表在逻辑结构和物理结构上都是连续的(类似数组),而今天要说的链表在物理结构上是非连续的!

所以,链表到底是什么呢?它和顺序表到底有哪些不同呢?

1.2顺序表和链表的对比

顺序表:

实质上就是对数组的操作,相对于静态顺序表来说,动态顺序表可以更加灵活地申请使用空间,不过,在顺序表使用的后期,开辟的内存成倍增加,如果再次加入的数据不多,还是会导致空间的浪费,而且在每次非尾部插入和删除时,总要对数组元素进行大量移动,效率还是没有那么高。

再次看一下顺序表的结构:

typedef int SLDateType;
typedef struct SeqList {SLDateType* a;int size;//有效数据个数int capacity;//当前顺序表容量
}SL;

顺序表缺点小总结:

  1. 后期内存开辟成倍增加
  2. 插入删除元素对顺序表移动元素较多

 从中我们也可以猜测出链表的功能,开辟内存不会成倍增加且插入删除不必移动过多元素

链表: 

实质上是指针链接,就好比如图所示的火车:

链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只 需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。车厢是独立存在的,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾? 最简单的做法:每节车厢里都放一把下一节车厢的钥匙。 这里的钥匙就是指针。

链表相对于顺序表:

  1. 将数组这一整体变为一个一个的链表节点,从对整体开辟内存变为对单个节点开辟内存
  2. 将顺序表用于计数的size和capacity去除,不再有越界的问题,取而代之的是链接下一个节点的指针
  3. 链表缺点在于无法通过下标访问,只能从头一个一个去访问,原因是一个一个申请的内存空间可能连续也可能不连续
  4. 顺序表和链表都是基于结构体实现

2.单链表的结构

ps:节点和结点表示一个意思 

画出单链表的结构大致如下:

  • 所有的链表节点都使用链表结构体指针去访问,一个链表节点包含数据区和指针区,数据区用于存放数据;指针区用于存放下一个节点的地址 
  • plist相当于火车头,出于方便,习惯叫它头结点,不过这种叫法其实不符合逻辑,也是结构体指针类型,单链表也可以没有plist
  • 还有一种链表,叫带头链表,这种链表的头节点数据区没有值,这个头节点叫做哨兵位,区分一下这两种链表

单链表结构体用代码书写就是:

typedef int SLTDateType;
typedef struct SListNode {SLTDateType val;//存放有效值struct SListNode* next;//指向下一个节点
}SLTNode;

SList 代表 single list 

3.单链表的实现

 使用单链表实现数据的增删查改

SList.h存放链表结构体及相关函数声明:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDateType;
typedef struct SListNode {SLTDateType val;struct SListNode* next;
}SLTNode;
//头插
void SLTPushFront(SLTNode** pphead, SLTDateType x);//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x);//头删
void SLTPopFront(SLTNode** pphead);//尾删
void SLTPopBack(SLTNode** pphead);//申请内存
SLTNode* SLTBuyNode(SLTDateType x);
//打印链表
void SLTPrint(SLTNode* phead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDateType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x);//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos);//销毁链表
void SLTDestroy(SLTNode** pphead);

 SList.c存放相关函数定义

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//申请内存
SLTNode* SLTBuyNode(SLTDateType x)
{SLTNode* p = (SLTNode*)malloc(sizeof(SLTNode));if (p == NULL){perror("fail malloc");exit(1);}p->val = x;p->next = NULL;return p;
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDateType x)
{assert(pphead);SLTNode* pcur = SLTBuyNode(x);//不用找尾,不用对原先链表解引用,故不用考虑链表为空的情况pcur->next = *pphead;*pphead = pcur;}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{assert(pphead);SLTNode* ptail=*pphead;//链表为空if (*pphead == NULL)//如果链表为空,则找尾时不能对空指针解引用,故需要分支{*pphead = SLTBuyNode(x);}else{//找尾while (ptail->next != NULL){ptail = ptail->next;}//接入ptail->next = SLTBuyNode(x);}}
//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead&&*pphead);//只有一个节点和有多个节点可以使用相同方法//需要头结点后的位置的地址,若只有一个节点,其后面有位置,故不用分支SLTNode* pcur = *pphead;*pphead = (*pphead)->next;free(pcur);pcur = NULL;	
}
//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* ptail = *pphead;SLTNode* prev = *pphead;if (ptail->next == NULL)//只有一个节点(ptail prev未错开)//需要尾节点前一位的地址,若尾节点为头节点,头结点前无位置,故需要分支{free(*pphead);*pphead = NULL;}else//有多个节点(ptail prev错开){//找尾while (ptail->next != NULL){prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;}
}
//打印链表
void SLTPrint(SLTNode* phead)
{//assert(phead);while (phead != NULL){printf("%d->", phead->val);phead = phead->next;}printf("NULL\n");
}
//查找
SLTNode* SLTFind(SLTNode* phead,SLTDateType x)
{SLTNode* pcur = phead;while (phead){if (phead->val == x){return phead;}phead = phead->next;}return NULL;
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{assert(pphead && *pphead);assert(pos);SLTNode* pcur = *pphead;//第一个位置为指定位置if (pcur == pos)//需要指定位置之前的位置和指定位置的地址,头节点之前无位置,故需要分支{SLTPushFront(pphead, x);}//其他位置else{while (pcur->next != pos){pcur = pcur->next;}//插入SLTNode* pin = SLTBuyNode(x);pin->next = pos;pcur->next = pin;}}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{assert(pphead && *pphead);assert(pos);SLTNode* pcur = *pphead;//找到指定位置while (pcur != pos)//需要指定位置和指定位置之后位置的地址,尾节点后有位置,故不需要分支{pcur = pcur->next;}//插入(最后节点后插入与其他位置相同)SLTNode* pin = SLTBuyNode(x);pin->next = pcur->next;pcur->next = pin;
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);SLTNode* pcur = *pphead;//删除头结点if (pcur == pos)//需要pos前和pos后的位置,若pos为头结点,头结点前无位置,故需要分支{SLTPopFront(pphead);}else//非头结点{while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;//pos相当于尾删里面的prev,故不用再创建结构体指针来保存前一个位置的nextfree(pos);pos = NULL;}
}//删除pos之后的节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos && pos->next);SLTNode* pcur = *pphead;while (pcur!=pos)//需要pos和pos之后的之后的地址,若pos为尾节点,尾节点后有位置,故不需要分支{pcur = pcur->next;}SLTNode* pout = pos->next;//pos和pcur指向相同,pcur发生改变导致pos发生改变,保存pos地址用于删除pcur->next = pos->next->next;free(pout);pout = NULL;
}//销毁链表
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* pcur;while (*pphead){pcur = *pphead;*pphead = (*pphead)->next;free(pcur);pcur = NULL;}
}

总结单链表函数书写要点:

一、关于是否需要分支: 

1.传入链表可能为空的函数: 关键要清楚是否需要某个节点的next(也就是是否对需要某节点指针解引用)

  • 对于头插尾插:头插不需要原链表头结点的next尾插需要原链表尾节点的next用于链接,故尾插需要考虑链表为空的情况(不能对空指针解引用)。尾插需要分支,头插不用分支。 

2.传入链表不能为空的函数(讨论只有一个节点的情况):关键要确定需要找的节点是否存在(节点为NULL也是存在,不存在指找不到其地址)

  • 对于头删尾删:头删需要头结点后面的节点,当链表只有一个节点时,头结点后面的节点存在,故头删不用分支尾删需要尾节点前面的节点,当链表只有一个节点时,尾节点前面的节点不存在,故尾删需要分支
  • 对于任意位置插入:pos之前插入需要pos和pos前面的节点,当链表只有一个节点时,pos前面节点不存在,故前插需要分支pos之后插入需要pos和pos后面的节点,当链表只有一个节点时,pos后面节点存在,故后插不用分支
  • 对于任意位置删除:pos位置删除需要pos前面和pos后面的节点,当链表只有一个节点时,pos前面节点不存在,故pos删除需要分支pos之后删除需要pos和pos后面的后面的节点,当链表只有一个节点时,接收链表时就要断言报错,因为无法删除一个不存在的节点,故pos后删不用分支

总体来说,相对于参考节点,向前看的需要分支讨论,向后看的不需要分支讨论 

  • 向前看的(要分支的)都是用pcur->next 与某节点比较作为循环判断条件
  • 向后看的(不要分支的)都是用pcur与某节点比较作为循环判断条件

二、关于删除操作:

关键在于确定删除操作中链接前后节点所使用的指针是否使被删的节点指针指向发生改变 

  • 对于头删:需要头结点和头结点后的节点,头结点为被删节点,链接前后节点时,头指针指向发生了改变,故需要指针保存原来的头结点,进行后续删除。
  • 对于尾删:需要尾节点和尾节点前的节点,尾节点为被删节点,链接前后节点时,尾指针指向没有发生改变,故直接删除
  • 对于删pos节点:需要pos前和pos后的节点,pos为被删节点,链接前后节点时,pos指针指向没有发生改变,故直接删除
  • 对于删pos后的节点:需要pos和pos后面的后面的节点,pos->next为被删节点,链接前后节点时,pos指针指向发生改变,会导致pos->next指针指向发生改变,故需要指针保存原来的pos->next,进行后续删除。

也就是说,需要使用的指针与被删除的指针有关系时,需要指针保存节点(认为前一个节点与被删节点无关,因为被删节点无法向前找节点)  

  • 向前看的(有分支的)不需要指针保存被删节点
  • 向后看的(没有分支的)需要指针保存被删节点

三、关于为什么用二级指针 

首先:函数传参有传值,传址两种 

值传递不改变实参的值,按址传递在函数中通过解引用能改变实参的值

对于想要传入指针的函数(下面说两种情况,以结构体类型为例): 

传入一级指针,用一级指针接收:

 相当于传了一个结构体的址,结构体指针指向。此时我们可以通过解引用改变结构体的内容,但是不能改变指针的指向(指向该结构体的地址)        (ps:想想之前写题:数组传参用一级指针来接收 ,函数里面改变的是数组各元素的值而不是数组的地址)

传入一级指针的地址,用二级指针接收:

相当于传了结构体指针指向。此时我们可以通过解引用改变结构体指针的指向

  • 头插,头删操作明确要改变头结点的指针的指向,用到二级指针
  • 尾删,删除pos节点,pos前插入节点时,遇到一个节点的情况也要改变头指针指向,也要用到二级指针
  • 尾插操作是遇到空链表时,需要改变头指针指向,还要用到二级指针
  • 销毁操作需要将指针指向空,都要用到二级指针

所以,编写单链表的函数的时候,我们使用二级指针 

 --------------------------------------------------------------------------------------------------------------------------------

好啦,单链表的讲解就到这里啦,上面的总结部分真的是博主认为的精髓!!!(希望不是自以为是),看完的小伙伴是否能够留下你们的收藏关注呢(比心)(比心)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/305654.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sketch3D:用于草图到3D生成的样式一致性指南

Sketch3D: Style-Consistent Guidance for Sketch-to-3D Generation Sketch3D&#xff1a;用于草图到3D生成的样式一致性指南 Wangguandong Zheng 重试 错误原因 Southeast UniversityChina 重试 错误原因 wgdzhengseu.edu.cnHaifeng Xia 重试 错误原因 Southeast Universit…

C语言读取 .ico 文件并显示数据

原来是想做光标编辑器&#xff0c;自己把绘图板的内容导出为光标格式 鼠标指针文件格式解析——Windows&#xff08;一&#xff09; (qq.com) 代码来源自 Icons | Microsoft Learn 鄙人又补充些变量可以运行微软的代码 简单代码如下 #include <stdio.h> #include &l…

软考高级架构师:数据库模式概念和例题

一、AI 讲解 数据库模式分为三个层次&#xff1a;外模式、概念模式和内模式。这三个层次分别对应不同的抽象级别&#xff0c;帮助数据库管理员和用户以不同的视角理解数据库结构。 外模式&#xff08;用户级&#xff09;&#xff1a;是数据库用户的视图。每个用户可以通过外模…

最新Android Studio导入aar包的方法

以前的方式&#xff0c;目前看网上也大多数都是这种方式&#xff0c;导致我本地加的时候一直有问题 但是这样都无法sync以及编译通过&#xff0c;因为方式已经变了 1&#xff1a;将aar文件复制到MyApplication\app\libs下 2&#xff1a;在MyApplication\app\build.gradle下添加…

LeetCode-Java:6.Z字形变换

文章目录 题目解① 找规律 题目 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R之后&a…

独家原创 | Matlab实现INFO-BiTCN-BiGRU-Attention多输入单输出回归预测

独家原创 | Matlab实现INFO-BiTCN-BiGRU-Attention多输入单输出回归预测 目录 独家原创 | Matlab实现INFO-BiTCN-BiGRU-Attention多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现INFO-BiTCN-BiGRU-Attention向量加权算法优化双向时间卷积…

【无人机/平衡车/机器人】详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波——附3个算法源码

效果&#xff1a; MPU6050姿态解算-卡尔曼滤波四元数互补滤波 目录 基础知识详解 欧拉角 加速度计(Accelerometer)与姿态测量 陀螺仪(Gyroscope)与姿态测量 姿态解算算法1-互补滤波 姿态解算算法2-四元数法 姿态解算算法3-卡尔曼滤波 组成 1.预测状态方程 2. 预测协方…

10:00面试,10:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

浅谈网络安全威胁与防御策略

企业网络安全威胁概述 外部威胁&#xff1a;来自网络安全威胁&#xff0c;比如DDOS攻击&#xff0c;病毒&#xff0c;sql注入&#xff0c;木马&#xff0c;蠕虫&#xff0c;等网络入侵&#xff0c;网络扫描&#xff0c;垃圾邮件&#xff0c;钓鱼邮件&#xff0c;针对web的攻击…

计算机网络——NAT技术

目录 前言 前篇 引言 SNAT&#xff08;Source Network Address Translation&#xff09;源网络地址转换 SNAT流程 确定性标记 DNAT&#xff08;Destination Network Address Translation&#xff0c;目标网络地址转换&#xff09; NAT技术重要性 前言 本博客是博主用于…

科技论文和会议录制高质量Presentation Video视频方法

一、背景 机器人领域&#xff0c;许多高质量的期刊和会议&#xff08;如IEEE旗下的TRO&#xff0c;RAL&#xff0c;IROS&#xff0c;ICRA等&#xff09;在你的论文收录后&#xff0c;需要上传一个Presentation Video材料&#xff0c;且对设备兼容性和视频质量有较高要求&#…

Vue3基础语法

在这个章节中&#xff0c;简单的看下Vue3的基础语法&#xff0c;有了这些基础后&#xff0c;对写vue3单页也就没有什么问题了。 模板语法 在写html时&#xff0c;我们希望在某个节点绑定一个动态值时&#xff0c;是使用dom操作执行的&#xff0c;如下&#xff1a; <!DOCT…

Linux:动态库加载、编址

目录 一、库的概念 二、动静态库的加载 2.1绝对编址与相对编址 2.1一般程序的加载 三、动态库的加载 一、库的概念 库默认就是一个磁盘级文件&#xff0c;所以在执行代码时&#xff0c;库和可执行程序都会被加载到内存中&#xff0c;从原理上&#xff0c;库函数的调用依旧…

多态【C/C++复习版】

目录 一、多态是什么&#xff1f;如何实现&#xff1f; 二、 什么是重写&#xff1f;有什么特点&#xff1f; 三、什么是协变&#xff1f; 四、析构函数能实现多态吗&#xff1f;为什么要实现&#xff1f; 五、override和final的作用是什么&#xff1f; 六、 多态的原理是…

从 SQLite 3.4.2 迁移到 3.5.0(二十)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite---调试提示&#xff08;十九&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 ​ SQLite 版本 3.5.0 &#xff08;2007-09-04&#xff09; 引入了一个新的操作系统接口层&#xff0c; 与所有先前版本的 SQLi…

ELK 日志分析系统(一)

一、概念 二、详解 2.1 Elasticsearch 核心概念 2.1.1 接近实时(NRT) 2.1.2 cluster集群 2.1.3 Node节点 2.1.4 index索引 2.1.5 类型&#xff08;type&#xff09; 2.1.6 文档&#xff08;document) 2.1.7 分片和副本(shards & replicas) 2.2 Logstash主要组件 …

个人博客系统项目(SpringBoot+Linux部署上线)

在学完SpringBoot框架、MyBatis后&#xff0c;直接开始做第一个项目&#xff1a;博客系统 首先&#xff0c;该博客系统包含核心功能有&#xff1a; 一、登录、注册、退出登录功能。 二、没有登陆前可以查看博客首页以及博客展示的分页处理&#xff0c;以及点击查看博客可以…

windows下pycharm中配置conda虚拟环境

目录 一&#xff1a;背景 二&#xff1a;安装conda环境 三&#xff1a;pycharm配置环境 四&#xff1a;注意问题 一&#xff1a;背景 在使用python的过程中&#xff0c;我们可能需要在一个windows环境中创建多个版本的python和安装不同的库去做一些开发任务。 使用conda&a…

IDEA2023 开发环境配置

目录 1. 关闭IDEA自动更新1.2 IDEA 新版样式切换 2. Maven配置2.1本地仓库优先加载2.2 maven.config配置文件中 3. 全局配置JDK4. 配置文件编码:UTF-85. 开启自动编译&#xff08;全局配置&#xff09;6. 开启自动导包7. 开启鼠标悬浮&#xff08;提示文档信息&#xff09;8. 设…

10分钟1000台虚机 云安全效能双升 亚信安全新信舱无代理云平台快速适配版正式发布

新信舱 亚信安全新信舱无代理云平台快速适配版正式发布。在云平台依赖性、无代理部署速度、宿主机无代理AV防护和虚拟机缓存扫描性能等方面&#xff0c;新信舱无代理版本提供了无缝的可扩展性、低资源消耗并降低管理复杂性&#xff0c;让安全防护真正做到了 多快好省&#xff…