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

链表基础知识

一、什么是链表

链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
在这里插入图片描述
链表的结构是多式多样的,当时通常用的也就是两种:
(1)第一种是无头非循环单向链表
在这里插入图片描述
(2)第二种是带头循环双向链表
在这里插入图片描述

  • 无头单向非循环列表:结构简单,一般不会单独用来存放数据。实际中更多是作为其他数据结构的子结构,比如说哈希桶等等。
  • 带头双向循环链表:结构最复杂,一般单独存储数据。实际中经常使用的链表数据结构,都是带头双向循环链表。这个结构虽然复杂,但是使用代码实现后会发现这个结构会带来很多优势,实现反而简单了。

二、链表和数组对比

2.1 链表和数组的区别

  • 数组静态分配内存,链表动态分配内存。
  • 数组在内存中是连续的,链表是不连续的。
  • 数组利用下标定位,查找的时间复杂度是O(1),链表通过遍历定位元素,查找的时间复杂度是O(N)。
  • 数组插入和删除需要移动其他元素,时间复杂度是O(N),链表的插入或删除不需要移动其他元素,时间复杂度是O(1)。

2.2 数组的优缺点

数组的优点

  • 随机访问性比较强,可以通过下标进行快速定位。
  • 查找速度快

数组的缺点

  • 插入和删除的效率低,需要移动其他元素。
  • 会造成内存的浪费,因为内存是连续的,所以在申请数组的时候就必须规定七内存的大小,如果不合适,就会造成内存的浪费。
  • 内存空间要求高,创建一个数组,必须要有足够的连续内存空间。
  • 数组的大小是固定的,在创建数组的时候就已经规定好,不能动态拓展。

2.3 链表的优缺点

链表的优点

  • 插入和删除的效率高,只需要改变指针的指向就可以进行插入和删除。
  • 内存利用率高,不会浪费内存,可以使用内存中细小的不连续的空间,只有在需要的时候才去创建空间。大小不固定,拓展很灵活。

链表的缺点

  • 查找的效率低,因为链表是从第一个节点向后遍历查找。

三、单链表与双链表对比

单链表和双链表的区别
在这里插入图片描述

  • 单链表的每一个节点中只有指向下一个结点的指针,不能进行回溯,适用于节点的增加和删除。
  • 双链表的每一个节点给中既有指向下一个结点的指针,也有指向上一个结点的指针,可以快速的找到当前节点的前一个节点,适用于需要双向查找节点值的情况。

双链表相对于单链表的优点
  删除单链表中的某个节点时,一定要得到待删除节点的前驱,得到其前驱的方法一般是在定位待删除节点的时候一路保存当前节点的前驱,这样指针的总的的移动操作为2n次,如果是用双链表,就不需要去定位前驱,所以指针的总的的移动操作为n次。
  查找时也是一样的,可以用二分法的思路,从头节点向后和尾节点向前同时进行,这样效率也可以提高一倍,但是为什么市场上对于单链表的使用要超过双链表呢?从存储结构来看,每一个双链表的节点都比单链表的节点多一个指针,如果长度是n,就需要n*lenght(32位是4字节,64位是8字节)的空间,这在一些追求时间效率不高的应用下就不适用了,因为他占的空间大于单链表的1/3,所以设计者就会一时间换空间。

四、环形链表

4.1 判断是否有环

定义一个快指针和一个慢指针,快指针一次走两步,慢指针一次走两步,会出现两种情况,情况一指针走到了空的位置,那就说明这个链表不带环。情况二两个指针相遇,说明这个链表带环。

获得入环节点
如果不考虑空间复杂度,可以使用一个map来记录走过的节点,这个指针一直向后遍历如果遇到空,说明这个链表不带环,也就没有入环节点,如果没有遇到空,如果遇到第一个在map中存在的节点,就说明回到了出发点,这个节点就是环的入口节点。如果不建立额外的空间,先使用快慢指针判断这个链表是否有环,如果有环将相遇节点记录,然后一个指针从链表的起始位置开始一次走一步,另一个指针从记录的节点开始一次走一步,当两个节点再次相遇,这个相遇节点就是环的入口节点。在这里插入图片描述

五、链表的简单实现

5.1 单链表实现

package Listtype listNode struct {val  intnext *listNode
}func newNode(val int) *listNode {node := new(listNode)node.val = valnode.next = nilreturn node
}// NewList 初始化一个不带头结点的链表
func NewList(vals []int) *listNode {var fistNode *listNodevar curNode *listNodefor _, v := range vals {node := newNode(v)if curNode == nil {fistNode = nodecurNode = fistNodecontinue}curNode.next = nodecurNode = curNode.next}return fistNode
}// FistAdd 头插
func FistAdd(fistNode *listNode, val int) *listNode{if fistNode == nil {return fistNode}node := newNode(val)node.next = fistNodereturn node
}// LastAdd 尾插
func LastAdd(fistNode *listNode, val int)  {if fistNode == nil {return}curNode := fistNodefor curNode.next != nil {curNode = curNode.next}node := newNode(val)curNode.next = nodereturn
}// IndexValAdd 在第一个指定值后插入,若没有,在链表尾部插入
// fistNode 链表第一个节点, indexVal 目标节点值, val 新节点值
func IndexValAdd(fistNode *listNode, indexVal, val int) {if fistNode == nil {return}curNode := fistNodefor curNode.next != nil && curNode.val != indexVal {curNode = curNode.next}node := newNode(val)nextNode := curNode.nextnode.next = nextNodecurNode.next = nodereturn
}// ChangVal 更改目标节点值,若没有,不做改动
// fistNode 链表第一个节点, indexVal 目标节点值, val 目标值
func ChangVal (fistNode *listNode,  indexVal, tarVal int)  {if fistNode == nil {return}curNode := fistNodefor curNode != nil && curNode.val != indexVal {curNode = curNode.next}// 判断是走到最后都没有找到对应值还是找到对应值if curNode == nil{return}curNode.val = tarValreturn}// DelNode 删除指定节点,若没有则不删除
func DelNode(fistNode *listNode, indexVal int)  {if fistNode == nil {return}curNode := fistNode// 查找要删除节点的前一个节点for curNode.next != nil  {nextNode := curNode.nextif nextNode.val == indexVal {break}curNode = curNode.next}if curNode.next == nil {return}nextNode := curNode.nextcurNode.next = nextNode.next}// Show 不带头节点查
func Show(node *listNode) []int {if node == nil {return []int{}}valSlice := make([]int, 0, 4)curNode := nodefor curNode != nil {valSlice = append(valSlice, curNode.val)curNode = curNode.next}return valSlice
}

测试验证

package mainimport ("Data/List""fmt"
)func main() {// 初始化一个链表fistNode := List.NewList([]int{1, 2, 3, 4, 5})fmt.Println("初始化一个链表 :",List.Show(fistNode))// 对链表进行头插fistNode = List.FistAdd(fistNode, 0)fmt.Println("对链表进行头插 0 :",List.Show(fistNode))// 对链表进行尾插List.LastAdd(fistNode, 6)fmt.Println("对链表进行尾插 6 :",List.Show(fistNode))// 删除指定节点List.DelNode(fistNode, 3)fmt.Println("删除指定节点 3 :",List.Show(fistNode))List.DelNode(fistNode, 3)fmt.Println("删除指定节点 3 :",List.Show(fistNode))List.DelNode(fistNode, 3)fmt.Println("删除指定节点 7 :",List.Show(fistNode))// 在第一个指定值后插入List.IndexValAdd(fistNode, 4, 41)fmt.Println("在第一个指定值后插入,若没有,在链表尾部插入 4 41 :",List.Show(fistNode))List.IndexValAdd(fistNode, 7, 42)fmt.Println("在第一个指定值后插入,若没有,在链表尾部插入 7 42 :",List.Show(fistNode))// 更改目标节点值List.ChangVal(fistNode, 4, 40)fmt.Println("更改目标节点值 4 40 :",List.Show(fistNode))List.ChangVal(fistNode, 7, 43)fmt.Println("更改目标节点值 7 43 :",List.Show(fistNode))
}

5.2 双向循环链表的实现

package Listimport "fmt"func newCircleNode(val int) *listNode {node := new(listNode)node.val = valnode.next = nodereturn node
}func NewCircleList(vals []int) *listNode {var fistNode *listNodevar curNode *listNodefor _, v := range vals {if curNode == nil {fistNode = newCircleNode(v)curNode = fistNodecontinue}node := newCircleNode(v)node.next = fistNodecurNode.next = nodecurNode = curNode.next}return fistNode
}func isLastNode(fistNode *listNode, node *listNode) bool {return node.next == fistNode
}// CircleFistAdd 头插
func CircleFistAdd(fistNode **listNode, val int)  {if fistNode == nil {return}curNode := *fistNodefor !isLastNode(*fistNode, curNode) {curNode = curNode.next}node := newCircleNode(val)curNode.next = nodenode.next = *fistNode*fistNode = node
}// CircleLastAdd 尾插
func CircleLastAdd(fistNode *listNode, val int) {if fistNode == nil {return}curNode := fistNodefor !isLastNode(fistNode, curNode) {curNode = curNode.next}node := newCircleNode(val)node.next = fistNodecurNode.next = node}// CircleIndexValAdd 在第一个指定值后插入,若没有,在链表尾部插入
func CircleIndexValAdd(fistNode *listNode, indexVal,val int) {if fistNode == nil {return}curNode := fistNodefor !isLastNode(fistNode, curNode) && curNode.val != indexVal {curNode = curNode.next}node := newCircleNode(val)node.next = curNode.nextcurNode.next = node}// CircleChangVal 更改目标节点值,若没有,不做改动
func CircleChangVal(fistNode *listNode, indexVal, tarVal int) {if fistNode == nil {return}curNode := fistNodefor curNode.val != indexVal && !isLastNode(fistNode, curNode)  {curNode = curNode.next}if curNode.val == indexVal {curNode.val = tarValreturn}fmt.Printf("节点 %d 不存在\n", indexVal)
}// CircleDelNode 删除指定节点,若没有则不删除
func CircleDelNode(fistNode *listNode, indexVal int) {if fistNode == nil {return}curNode := fistNodefor curNode.next.val != indexVal && !isLastNode(fistNode, curNode)  {curNode = curNode.next}if curNode.next.val == indexVal {curNode.next = curNode.next.nextreturn}fmt.Printf("没有该节点 %d \n", indexVal)}// CircleShow 查看链表
func CircleShow(fistNode *listNode) {if fistNode == nil {return}curNode := fistNodefor {if isLastNode(fistNode, curNode) {fmt.Printf("val:%d next:%d", curNode.val, curNode.next.val)break}fmt.Printf("val:%d next:%d -> ", curNode.val, curNode.next.val)curNode = curNode.next}fmt.Println()
}

测试验证

package mainimport ("Data/List""fmt"
)func main() {// 初始化一个环形链表circleFistNode := List.NewCircleList([]int{1, 2, 3})fmt.Println("初始化一个链表 :")List.CircleShow(circleFistNode)// 对链表进行头插List.CircleFistAdd(&circleFistNode, 0)fmt.Println("对链表进行头插  0:")List.CircleShow(circleFistNode)// 对链表进行尾插List.CircleLastAdd(circleFistNode, 4)fmt.Println("对链表进行尾插 4 :")List.CircleShow(circleFistNode)// 删除指定节点fmt.Println("删除指定节点 3 :")List.CircleDelNode(circleFistNode, 3)List.CircleShow(circleFistNode)fmt.Println("删除指定节点 3 :")List.CircleDelNode(circleFistNode, 3)List.CircleShow(circleFistNode)fmt.Println("删除指定节点 7 :")List.CircleDelNode(circleFistNode, 7)List.CircleShow(circleFistNode)// 在第一个指定值后插入circleFistNode = List.NewCircleList([]int{1, 2, 3})fmt.Println("初始化一个链表 :")List.CircleShow(circleFistNode)List.CircleIndexValAdd(circleFistNode, 2, 41)fmt.Println("在第一个指定值后插入,若没有,在链表尾部插入 2 41 :")List.CircleShow(circleFistNode)List.CircleIndexValAdd(circleFistNode, 7, 42)fmt.Println("在第一个指定值后插入,若没有,在链表尾部插入 7 42 :")List.CircleShow(circleFistNode)// 更改目标节点值circleFistNode = List.NewCircleList([]int{1, 2, 3, 4})fmt.Println("初始化一个链表 :")List.CircleShow(circleFistNode)fmt.Println("更改目标节点值 3 40 :")List.CircleChangVal(circleFistNode, 3, 40)List.CircleShow(circleFistNode)fmt.Println("更改目标节点值 7 43 :")List.CircleChangVal(circleFistNode, 7, 43)List.CircleShow(circleFistNode)//
}

相关文章:

链表基础知识

一、什么是链表 链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表的结构是多式多样的,当时通常用的也就是两种: (1)第一种是无头非循环单向…...

process.env.npm_config_argv的值3个参数remain、cooked、original什么含义

在使用Webpack进行打包时,判断process.env.npm_config_argv的值通常是为了根据命令行参数来决定打包的行为。process.env.npm_config_argv是一个环境变量,保存了当前运行的npm命令和其参数。 具体而言,process.env.npm_config_argv的值是一个…...

【飞书】飞书导出md文档 | 飞书markdown文档导出 | 解决飞书只能导出pdf word

一、飞书导出markdown github地址:https://github.com/Wsine/feishu2md 这是一个下载飞书文档为 Markdown 文件的工具,使用 Go 语言实现。 请看这里:招募有需求和有兴趣的开发者,共同探讨开发维护,有兴趣请联系。 二、…...

零信任网络架构与实现技术的研究与思考

目前,国外已有较多有关零信任网络的研究与实践,包括谷歌的 BeyondCorp、BeyondProd,软件定义边界(Software Defined Perimeter,SDP) 及盖特提出的“持续自适应风险与信任评估”等。国内也有不少安全厂商积极…...

Unity 性能优化二:内存问题

目录 策略导致的内存问题 GFX内存 纹理资源 压缩格式 Mipmap 网格资源 Read/Write 顶点数据 骨骼 静态合批 Shader资源 Reserved Memory RenderTexture 动画资源 音频资源 字体资源 粒子系统资源 Mono堆内存 策略导致的内存问题 1. Assetbundle 打包的时候…...

JavaScript与TypeScript的区别

JavaScript和TypeScript是两种不同的编程语言,在一些方面有一些区别。 1. 类型系统:JavaScript是一种动态类型语言,变量的类型是在运行时确定的,并且可以随时更改。而TypeScript引入了静态类型系统,可以在编译时检查代…...

【NetCore】05-使用Autofac增强容器能力

文章目录 1.什么情况下需要引入第三方容器组件2.如何集成Autoface 1.什么情况下需要引入第三方容器组件 基于名称的注入属性注入子容器基于动态代理的AOP 核心扩展点:IServiceProviderFactory 第三方注入容器均使用这个类作为扩展点,将其注入到框架中…...

sparksql参数

Spark参数场景配置 参数类型 参数 参数说明 平台默认值 场景与建议 资源申请 spark.executor.memory Executor Java进程的堆内存大小 即Executor Java进程的Xmx值 2g 默认设置,或者同时等比例增大,最高不超过默认值的3倍,超过的单独拿出来看下 (注意作业是否数据倾斜&…...

STM32读写内部Flash

参考:https://blog.csdn.net/Caramel_biscuit/article/details/131925715 参考:https://blog.csdn.net/qq_36075612/article/details/124087574?spm1001.2014.3001.5502 目录 内存映射内部Flash的构成对内部Flash的写入过程查看工程内存的分布ROM加载空…...

golang文件锁,目录锁,syscall包的使用

先说结论 1. golang提供了syscall包来实现文件/目录的加锁,解锁 2. syscall包属于文件锁,是比较底层的技术,并不能在所有操作系统上完全实现,linux上实现了,windows下面就没有 3. 加锁时调用syscall.Flock(fd&#…...

数据库数据恢复-Syabse数据库存储页底层数据杂乱的数据恢复案例

数据库恢复环境: Sybase版本:SQL Anywhere 8.0。 数据库故障: 数据库所在的设备意外断电后,数据库无法启动。 错误提示: 使用Sybase Central连接后报错: 数据库故障分析: 经过北亚企安数据恢复…...

移远通信推出新一代高算力智能模组SG885G-WF,为工业和消费级IoT应用带来全新性能标杆

2023年7月24日,全球领先的物联网整体解决方案供应商移远通信宣布,正式推出其新一代旗舰级安卓智能模组SG885G-WF。该智能模组具有高达48 TOPS 的AI综合算力、强大性能及丰富的多媒体功能,非常适用于需要高处理能力和多媒体功能的工业和消费者…...

微信小程序开发,小程序类目符合,线上版本无权限申请wx.getLocation接口

我开发 的小程序类目符合wx.getLocation接口的申请标准 但是却还是显示无权限申请 后来研究好久才发现,小程序需要在发布线上版本时提交用户隐私保护指引 如未设置也可以在 设置-服务内容声明-用户隐私保护指引-声明处理用户信息项并补充填写后提交用户隐私协议审核…...

vue2企业级项目(五)

vue2企业级项目(五) 页面适配、主题切换 1、适配 项目下载插件 npm install --save-dev style-resources-loader vue-cli-plugin-style-resources-loader修改vue.config.js部分内容 const path require("path");module.exports {pluginOpt…...

【HTML5】拖放详解及实现案例

文章目录 效果预览代码实现 效果预览 代码实现 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>一颗不甘坠落的流星</title><style>#div1,#div2 {float: left;width: 100px;height: 27px;margin: 10px;paddin…...

Codeforces Round 888 (Div. 3)(视频讲解全部题目)

[TOC](Codeforces Round 888 (Div. 3)&#xff08;视频讲解全部题目&#xff09;) Codeforces Round 888 (Div. 3)&#xff08;A–G&#xff09;全部题目详解 A Escalator Conversations #include<bits/stdc.h> #define endl \n #define INF 0x3f3f3f3f using namesp…...

MySQL之深入InnoDB存储引擎——物理文件

文章目录 一、参数文件二、日志文件三、表结构定义文件四、InnoDB 存储引擎文件1、表空间文件2、重做日志文件 一、参数文件 当 MySQL 实例启动时&#xff0c;数据库会先去读一个配置参数文件&#xff0c;用来寻找数据库的各种文件所在位置以及指定某些初始化参数。在默认情况…...

Jquery操作html常用函数

1. text() 获取元素的文本内容&#xff1a;$("#element").text(); 设置元素的文本内容&#xff1a;$("#element").text("New Text"); 2. html() 获取元素的 HTML 内容&#xff1a;$("#element").html(); 设置元素的 HTML 内容&am…...

【Lua学习笔记】Lua进阶——Table,迭代器

文章目录 官方唯一指定数据结构--tabletable的一万种用法字典和数组 迭代器ipairs()pairs() 回到Table 在【Lua学习笔记】Lua入门中我们讲到了Lua的一些入门知识点&#xff0c;本文将补充Lua的一些进阶知识 官方唯一指定数据结构–table 在上篇文章的最后&#xff0c;我们指出…...

重庆市北斗新型智慧城市政府项目

技术栈&#xff1a;使用vue2JavaScriptElementUIvuexaxiosmapboxcesium 项目描述&#xff1a;重庆市北斗新型智慧城市政府项目是基于千寻孪界开发的一款智慧城市项目&#xff0c;包含车辆实时位置定位&#xff0c;智能设备的报警&#xff0c;基础设施的部设等等功能 工作内容&a…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

xmind转换为markdown

文章目录 解锁思维导图新姿势&#xff1a;将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件&#xff08;ZIP处理&#xff09;2.解析JSON数据结构3&#xff1a;递归转换树形结构4&#xff1a;Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

多元隐函数 偏导公式

我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式&#xff0c;给定一个隐函数关系&#xff1a; F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 &#x1f9e0; 目标&#xff1a; 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z​、 …...