深入探讨Go语言中的双向链表
简介
双向链表是链表家族中的一种高级结构,每个节点不仅指向下一个节点,还指向上一个节点。今天,我们将学习如何在Go语言中实现和操作这种灵活的数据结构。
双向链表的优缺点
-
优点:
- 可以从任一方向遍历链表,灵活性高。
- 插入和删除操作非常高效,因为可以轻松地调整前后节点的链接。
- 可以在不遍历整个链表的情况下访问特定位置的节点。
-
缺点:
- 需要额外的内存来存储两个指针,占用内存较大。
- 比单链表实现复杂,代码量更多。
代码实现
下面是我们在Go中实现双向链表的完整代码:
// -------------------------------------------
// @file : DoubleLinkedList.go
// @author : Serein
// @contact : xming9523@gmail.com
// @blog : https://blog.childish.vip
// @time : 2024/12/3 20:55:58 星期二
// -------------------------------------------package mainimport ("fmt"
)// 双链表// DoubleListNode 节点结构体定义
// 每个节点包含一个值(Val),以及指向前后节点的指针(Prev 和 Next)
type DoubleListNode struct {Val int // 节点的值Prev *DoubleListNode // 指向前一个节点的指针Next *DoubleListNode // 指向后一个节点的指针
}// DoubleLinkList 双链表结构体定义
// 双链表包含头节点(Head)和尾节点(Tail)
type DoubleLinkList struct {Head *DoubleListNode // 链表头部指针Tail *DoubleListNode // 链表尾部指针
}// NewDoubleLinkList 初始化双链表函数
// 创建一个空链表,返回其指针
func NewDoubleLinkList() *DoubleLinkList {return &DoubleLinkList{Head: nil, Tail: nil} // 初始化头尾指针为nil,表示空链表
}// insertNode 通用的插入节点函数,既可以在头部插入,也可以在尾部插入
// 该函数可以在头部或尾部插入节点,atHead参数控制插入位置
func (l *DoubleLinkList) insertNode(val int, atHead bool) {newNode := &DoubleListNode{Val: val}// 如果链表为空,新节点就是头部也是尾部if l.Head == nil {newNode.Prev = nil // 新节点没有前驱newNode.Next = nil // 新节点没有后继l.Head = newNode // 头部指向新节点l.Tail = newNode // 尾部指向新节点return}// 如果在头部插入if atHead {newNode.Next = l.Head // 新节点的 Next 指向当前头节点newNode.Prev = nil // 新节点没有前驱节点l.Head.Prev = newNode // 当前节点的前驱指向新节点l.Head = newNode // 跟新头部节点为新指针} else {// 在尾部插入newNode.Prev = l.Tail // 新节点的 Prev 指向当前节点newNode.Next = nil // 新节点没有后继节点l.Tail.Next = newNode // 当前尾节点的 Next 指向新节点l.Tail = newNode // 跟新尾节点尾新节点}
}// InsertAtHead 在头部插入节点操作
// 通过调用通用的插入函数,指定在头部插入
func (l *DoubleLinkList) InsertAtHead(val int) {l.insertNode(val, true) // 在头部插入节点
}// InsertAtTail 在尾部插入节点
// 通过调用通用的插入函数,指定在尾部插入
func (l *DoubleLinkList) InsertAtTail(val int) {l.insertNode(val, false) // 在尾部插入节点
}// InsertAtRandomPosition 在随机位置插入节点函数
// 如果位置超出了链表长度,则在尾部插入
func (l *DoubleLinkList) InsertAtRandomPosition(pos int, val int) {// 如果位置小于0,或者链表为空,直接在头部插入if pos <= 0 || l.Head == nil {l.InsertAtHead(val)return}// 计算链表的长度length := 0current := l.Headfor current != nil {length++ // 遍历链表,计算长度current = current.Next}// 如果位置超出了链表的长度,就在尾部插入if pos >= length {l.InsertAtTail(val)return}// 遍历链表,找到指定位置的前一个节点current = l.Headfor i := 0; i < pos-1; i++ {current = current.Next}// 在指定位置插入节点newNode := &DoubleListNode{Val: val} // 创建新节点newNode.Next = current.Next // 新节点的 Next 指向当前节点的 NextnewNode.Prev = current // 新节点的 Prev 指向当前节点current.Next.Prev = newNode // 当前节点的 Next 节点的Prev指向信介蒂纳current.Next = newNode // 当前节点的 Next 指向新节点
}// DeleteAtPosition 删除指定位置的节点
// 如果链表为空或者位置无效(小于0),则不进行任何操作
func (l *DoubleLinkList) DeleteAtPosition(pos int) {// 如果链表为空或删除位置无效,直接返回if l.Head == nil || pos < 0 {return}// 如果删除的是头节点(位置为0)if pos == 0 {l.Head = l.Head.Next // 更新头节点为当前节点的下一个节点if l.Head != nil { // 如果新的头节点不为空l.Head.Prev = nil // 清空新的头节点的前驱节点}return}// 初始化当前节点为头节点current := l.Head// 遍历链表,找到要删除节点的位置for i := 0; current != nil && i < pos; i++ {current = current.Next // 移动到下一个节点}// 如果当前节点为空,说明位置超出了链表的范围,直接驳回if current == nil {return}// 如果删除的是尾节点if current.Next == nil {// 将当前节点的前一个节点的 Next 指向nil,断开尾节点current.Prev.Next = nil} else {// 删除的是中间节点或非尾节点,调整前后节点的指针current.Prev.Next = current.Next // 当前节点的前一个节点指向当前节点的下一个节点current.Next.Prev = current.Prev // 当前节点的下一个节点的前驱指向当前节点的前一个节点}// 清理当前节点的指针(帮助垃圾回收)current.Next = nil // 当前节点的 Next 指针指向 nilcurrent.Prev = nil // 当前节点的 Prev 指针指向 nil
}// traverseList 遍历链表的函数
// 根据reverse的值决定遍历方向,并执行给定的操作action
// reverse为true时,从尾到头遍历;为false时,从头到尾遍历
func (l *DoubleLinkList) traverseList(reverse bool, action func(*DoubleListNode)) {// 如果链表为空,打印提示信息if l.Head == nil {fmt.Println("链表为空")return}// 定义当前节点,初始化为头部节点current := l.Head// 如果 reverse 为 true ,说明要尾部遍历if reverse {current = l.Tail // 从尾部遍历for {action(current) // 执行传入的操作current = current.Prev // 移动到前一个节点if current == nil { // 如果当前节点为nil,表示遍历结束break}}} else {// 从头部开始遍历for {action(current) // 执行传入的操作current = current.Next // 移动到下一个节点if current == nil { // 如果当前节点为nil,便是遍历结束break}}}
}// PrintListForward 打印双链表元素(从前向后)操作
// 调用traverseList遍历链表,并打印每个节点的值
func (l *DoubleLinkList) PrintListForward() {l.traverseList(false, func(node *DoubleListNode) {fmt.Println(node.Val) // 打印节点的值})
}// PrintListBackward 打印双链表元素(从后向前)操作
// 调用traverseList遍历链表,并打印每个节点的值
func (l *DoubleLinkList) PrintListBackward() {l.traverseList(true, func(node *DoubleListNode) {fmt.Println(node.Val) // 打印节点的值})
}func main() {doubleList := NewDoubleLinkList()doubleList.InsertAtHead(1)doubleList.InsertAtHead(2)doubleList.InsertAtTail(3)doubleList.InsertAtTail(4)doubleList.InsertAtRandomPosition(2, 5)doubleList.PrintListForward()fmt.Println("-------------")doubleList.PrintListBackward()doubleList.DeleteAtPosition(2)fmt.Println("----------------")doubleList.PrintListForward()}
关键点解析
- 节点结构体:
DoubleListNode
定义了每个节点的结构,包含一个值Val
,一个指向前节点的指针Prev
和一个指向后节点的指针Next
。 - 链表结构体:
DoubleLinkList
使用Head
和Tail
指针来管理整个链表,这使得对头尾的操作变得简便。 - 初始化:
NewDoubleLinkList
函数返回一个空的双向链表。 - 插入操作:
InsertAtHead
和InsertAtTail
方法简化了在链表的头部或尾部插入新节点的操作。InsertAtRandomPosition
允许我们在链表的指定位置插入节点,提供了高度的灵活性。
- 删除操作:
DeleteAtPosition
展示了如何删除指定位置的节点,并处理了头节点、尾节点以及中间节点的删除逻辑。 - 遍历:
traverseList
函数可以根据reverse
参数决定遍历方向,并通过回调函数action
执行指定操作。PrintListForward
和PrintListBackward
方法分别用于打印链表的正向和反向节点值。
运行和测试
在main
函数中,我们展示了如何创建一个双向链表,插入节点,删除节点并从两个方向遍历打印链表:
func main() {// ... (代码)
}
总结
通过这个实现,我们学习了如何在Go中构建和操作双向链表。双向链表在很多情况下优于单向链表,因为它提供了双向遍历的能力以及更灵活的插入和删除操作。Go语言的简单语法使得这些操作的实现变得直观和高效。
实际应用
- LRU缓存:双向链表可以有效地实现LRU(最近最少使用)缓存策略。
- 文件系统:操作系统中文件系统的目录结构可以使用双向链表来实现。
- 浏览器历史记录:浏览器的前进和后退功能可以用双向链表实现。
相关文章:
深入探讨Go语言中的双向链表
简介 双向链表是链表家族中的一种高级结构,每个节点不仅指向下一个节点,还指向上一个节点。今天,我们将学习如何在Go语言中实现和操作这种灵活的数据结构。 双向链表的优缺点 优点: 可以从任一方向遍历链表,灵活性高…...

Fastapi + vue3 自动化测试平台---移动端App自动化篇
概述 好久写文章了,专注于新框架,新UI界面的实践,废话不多说,开搞 技术架构 后端: Fastapi Airtest multiprocessing 前端: 基于 Vue3、Vite、TypeScript、Pinia、Pinia持久化插件、Unocss 和 Elemen…...
ElasticSearch easy-es 聚合函数 group by 混合写法求Top N 词云 分词
1.将用户访问记录表数据同步到ES,并且分词,获取用户访问最多前十条词语。 Elasticsearch、Easy-es 快速入门 SearchAfterPage分页 若依前后端分离 Ruoyi-Vue SpringBoot 使用结巴分词器 <!-- 分词器--><dependency><groupId>com.hua…...

在 ASP.NET C# Web API 中实现 Serilog 以增强请求和响应的日志记录
介绍 日志记录是任何 Web 应用程序的关键方面。它有助于调试、性能监控和了解用户交互。在 ASP.NET C# 中,集成 Serilog 作为记录请求和响应(包括传入和传出的数据)的中间件可以显著提高 Web API 的可观察性和故障排除能力。 在过去的几周里&…...

2024年顶级小型语言模型前15名
本文,我们将深入了解2024年备受瞩目的十五款小型语言模型(SLMs),它们分别是Llama 3.1 8B、Gemma2、Qwen 2、Mistral Nemo、Phi-3.5等。这些SLMs以其精巧的体积和高效率著称,它们不需要依赖庞大的服务器资源,…...
精通 Python 网络安全(一)
前言 最近,Python 开始受到越来越多的关注,最新的 Python 更新添加了许多可用于执行关键任务的包。我们的主要目标是帮助您利用 Python 包来检测和利用漏洞,并解决网络挑战。 本书将首先带您了解与网络和安全相关的 Python 脚本和库。然后&…...

【python自动化二】pytest集成allure生成测试报告
pytest本身不会直接生成测试报告,而allure是一种生成测试报告的公共插件,可与多种测试框架配合生成测试报告,本文介绍下如何集成allure生成测试报告。 1.allure安装 1.安装allure-pytest 先安装allure的pytest插件,用于在pytes…...

网络版本的通讯录青春版(protobuf)
环境搭建 Protobuf 还常⽤于通讯协议、服务端数据交换场景。 因为我们主要目的只是为了学习protobuf,因此对于客户端,原本应该具备: 新增⼀个联系⼈ ◦ 删除⼀个联系⼈ ◦ 查询通讯录列表 ◦ 查询⼀个联系⼈的详细信息 这样四个功能。 …...
开源模型应用落地-安全合规篇-用户输入价值观判断(三)
一、前言 在深度合规功能中,对用户输入内容的价值观判断具有重要意义。这一功能不仅仅是对信息合法性和合规性的简单审核,更是对信息背后隐含的伦理道德和社会责任的深刻洞察。通过对价值观的判断,系统能够识别可能引发不当影响或冲突的内容,从而为用户提供更安全、更和谐的…...

神经网络入门实战:(十四)pytorch 官网内置的 CIFAR10 数据集,及其网络模型
(一) pytorch 官网内置的网络模型 图像处理: Models and pre-trained weights — Torchvision 0.20 documentation (二) CIFAR10数据集的分类网络模型(仅前向传播): 下方的网络模型图片有误,已做修改,具…...

【Rust在WASM中实现pdf文件的生成】
Rust在WASM中实现pdf文件的生成 前言概念和依赖问题描述分步实现pdf转Blob生成URL两种方式利用localstorage传递参数处理图片Vec<u8>到pdf格式的Vec<u8>使用rust创建iframe显示pdf的Blob最后 前言 实现了一个通用的前端jpg转pdf的wasm,因为动态响应框架无法直接打…...
在MySQL中执行sum case when报错:SUM does not exist
1. 报错 在pgsql中能正常运行的一段SQL在MySQL中运行的时候报错了: SELECT DATE( hr.handle_time ) AS statsDate,SUM ( CASE WHEN hma.app_type IN ( 2, 5 ) THEN ch_money ELSE 0 END ) AS aliPayAmt,SUM ( CASE WHEN hma.app_type IN ( 1, 4 ) THEN ch_money EL…...
【openssl】相关指令
熟悉下相关概念 x509:证书标准pem和der:两种(包括公私钥、证书签名请求、证书等内容的)的格式,前者是文本形式,linux常用,后者是二进制形式,windows常用,仅仅是格式&…...
实例分割详解
实例分割详解 引言 实例分割是计算机视觉领域的一项复杂任务,它要求模型能够识别图像中不同类别的对象,并对每个单独的对象进行像素级别的分类。与语义分割不同的是,实例分割不仅要区分不同的类别,还要识别同一类别中的不同个体…...

D87【python 接口自动化学习】- pytest基础用法
day87 pytest运行参数 -m -k 学习日期:20241203 学习目标:pytest基础用法 -- pytest运行参数-m -k 学习笔记: 常用运行参数 pytest运行参数-m -k pytest -m 执行特定的测试用例,markers最好使用英文 [pytest] testpaths./te…...

浅谈MySQL路由
华子目录 mysql-router介绍下载mysql-router安装mysql-router实验 mysql-router介绍 mysql-router是一个对应用程序透明的InnoDB Cluster连接路由服务,提供负载均衡、应用连接故障转移和客户端路由利用路由器的连接路由特性,用户可以编写应用程序来连接到…...

matlab中disp,fprintf,sprintf,display,dlmwrite输出函数之间的区别
下面是他们之间的区别: disp函数与fprintf函数的区别 输出格式的灵活性 disp函数:输出格式相对固定。它会自动将变量以一种比较直接的方式显示出来。对于数组,会按照行列形式展示;对于字符串,直接原样输出并换行。例如…...

30.100ASK_T113-PRO 用QT编写视频播放器(一)
1.再buildroot中添加视频解码库 X264, 执行 make menuconfig Target packages -->Libraries --> Multimedia --> X264 CLI 还需要添加 FFmpeg 2. 保存,重新编译 make all 3.将镜像下载开发板...

Linux-GPIO应用编程
本章介绍应用层如何控制 GPIO,譬如控制 GPIO 输出高电平、或输出低电平。 只要是用到GPIO的外设,都有可能用得到这些操作方法。 照理说,GPIO的操作应该是由驱动层去做的,使用寄存器操作或者GPIO子系统之类的框架。 但是࿰…...

opencvocr识别手机摄像头拍摄的指定区域文字,文字符合规则就语音报警
安装python,pycharm,自行安装。 Python下安装OpenCv 2.1 打开cmd,先安装opencv-python pip install opencv-python --user -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 再安装opencv-contrib-python pip install opencv-contrib-python --user …...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...