如何在 Go 中实现各种类型的链表?

链表是动态内存分配中最常见的数据结构之一。它由一组有限的元素组成,每个元素(节点)至少占用两块内存:一块用于存放数据,另一块用于存放指向下一个节点的指针。本文教程将说明在 Go 语言中如何借助指针和结构体类型来实现各种链表

Go 中的数据结构

随机存取存储器(RAM)可以想象成一张由许多地址单元组成的表格、矩阵或网格。为了在这张表中存放数据,Go 程序员必须先把内存划分成可定位的结构,并为它们起一个便于识别的名字——变量名。需要注意的是,变量名只是为了方便程序员阅读;编译后,名字会被实际的内存引用(例如 0x78BA 这样的地址)替换

最简单的情况下,变量名只对应单个内存单元;复杂时,它可以代表一段连续空间,或是具有行和列的二维区域,也就是数组。数组可通过下标寻址,例如array_name[2][4]表示第二行第四列的元素。

再复杂一些,数据元素之间的结构关系可能并非连续存储,而是随机分布,比如用来表示层级关系的树、分支结构,或含有多重连接的复杂网络结构。

因此,为了存储这些结构化关系,Go 开发者必须根据具体需求自行设计内存布局与访问策略。

静态 vs. 动态内存分配

在内存分配中,有两个关键特性:静态和动态。

  • 静态数据结构的大小和存储位置在编译期就已确定;
  • 动态数据结构的大小和位置则未预定义,而是在运行时决定。

举例来说,当 Go 开发者声明一个数组时,需要提前给出固定长度,这样编译器才能在你使用下标时准确地定位内存地址。而在动态数据结构(例如链表)中,下一个数据节点的地址只有在程序执行、节点被创建时才会确定,因此整个结构可在运行期间自由增长或收缩。由于静态结构存放在连续内存中,元素呈线性排列;动态结构则无此限制。

众多动态数据结构的基础——虽然动态分配并不限于此——就是链表。链表的各数据节点散布在内存的任意位置,通过指针相互连接。因此,一个链表节点至少包含两部分:

  1. 存放实际数据的元素
  2. 指向下一节点的链接

顺序存储与链式存储对比

与顺序存储结构(如数组)不同,链式存储除了保存数据本身,还需要额外的内存来存放指向下一节点的链接。这在某些场景下会增加开销,但链式存储带来的灵活性通常更具优势。比如,数组的内存大小在创建时就固定,因此可能出现大量未被利用的空间;而链表只有在需要时才创建节点,不会浪费内存。

在链表中删除元素非常容易,而顺序存储往往要移动大量数据才能完成删除。同样,链表插入元素也很高效。不过,如果要随机访问某个位置的元素,顺序存储则更快。

两种存储方式各有利弊,Go 程序员应根据具体需求选择合适的数据结构。

链表的 4 种基本形态

链表在内存中的组织方式主要有四种:单向(线性)、循环、双向以及双向循环。

  • 单向(线性)链表:只有一个 next 指针指向下一个节点;最后一个节点的 nextnil。遍历时一旦遇到 nil 就表示到达链表末尾;
  • 循环链表:结构与单向链表相同,但最后一个节点的 next 指向头节点,因此尾部再向后访问就回到起点,可形成“环形”遍历;
  • 双向链表:每个节点同时拥有 prevnext 两个指针,分别指向前驱和后继节点。这样即可正向也可反向遍历,查找元素更灵活;
  • 双向循环链表:在双向链表的基础上,让尾节点的 next 指向头节点,头节点的 prev 指向尾节点,于是可以向前或向后进行环形遍历。

从单向到双向、从线性到循环,链表的灵活性依次增强。下面的示例将演示在 Go 中实现这几种链表(示例仅涵盖链表的创建与遍历,以保持简洁)。

一、单向链表示例

下面是一个在 Go 中创建单向链表的示例:

package mainimport ("fmt""math/rand"
)type Node struct {info interface{}next *Node
}type List struct {head *Node
}func (l *List) Insert(d interface{}) {node := &Node{info: d}if l.head == nil {l.head = nodereturn}p := l.headfor p.next != nil {p = p.next}p.next = node
}func Show(l *List) {for p := l.head; p != nil; p = p.next {fmt.Printf("-> %v ", p.info)}
}func main() {sl := List{}for i := 0; i < 5; i++ {sl.Insert(rand.Intn(100))}Show(&sl)
}

示例输出:

-> 81 -> 87 -> 47 -> 59 -> 81

二、循环单向链表

我们可以轻松地把单向链表转换为循环链表。无需修改上述代码,只需再添加两个函数:ConvertSinglyToCircularShowCircular,并在 main 函数中调用它们即可。以下是这两个函数:

func ConvertSinglyToCircular(l *List) {if l.head == nil {return}p := l.headfor p.next != nil {p = p.next}p.next = l.head
}func ShowCircular(l *List) {p := l.headfor {fmt.Printf("-> %v ", p.info)if p.next == l.head {break}p = p.next}
}

注意:虽然假设该链表已经是循环的(即 p.next 最终会指回 l.head),但如果 l.headnil(空链表),此函数将发生空指针解引用错误并崩溃。

现在,在 main 函数中按如下方式调用这两个函数:

func main() {sl := List{}for i := 0; i < 5; i++ {sl.Insert(rand.Intn(100))}ConvertSinglyToCircular(&sl)ShowCircular(&sl)
}

三、双向链表示例

下面是一个演示如何在 Go 中创建双向链表的代码示例:

package mainimport ("fmt""math/rand""time"
)type Node struct {info interface{}prev *Nodenext *Node
}type List struct {head *Nodetail *Node
}func (l *List) Insert(d interface{}) {node := &Node{info: d}if l.head == nil {l.head, l.tail = node, nodereturn}l.tail.next = nodenode.prev = l.taill.tail = node
}func Show(l *List) {for p := l.head; p != nil; p = p.next {fmt.Printf("-> %v ", p.info)}
}func ReverseShow(l *List) {for r := l.tail; r != nil; r = r.prev {fmt.Printf("-> %v ", r.info)}
}func main() {sl := List{}rnd := rand.New(rand.NewSource(time.Now().UnixNano()))for i := 0; i < 10; i++ {sl.Insert(rnd.Intn(100))}Show(&sl)fmt.Println("\n----------------------------")ReverseShow(&sl)
}

示例输出:

-> 11 -> 17 -> 56 -> 71 -> 39 -> 44 -> 18 -> 78 -> 25 -> 19 
----------------------------
-> 19 -> 25 -> 78 -> 18 -> 44 -> 39 -> 71 -> 56 -> 17 -> 11

四、双向循环链表

与循环链表类似,双向循环链表也可以很容易地由双向链表转换而来。我们只需在上述代码中再添加两个函数即可。其余代码保持不变,只需在 main 函数中进行轻微修改,就像在前面的循环链表示例中所做的那样:

func ConvertDoublyToDoublyCircular(l *List) {if l.head == nil || l.tail == nil {return}l.head.prev = l.taill.tail.next = l.head
}func ShowDoublyCircular(l *List) {p := l.headfor {fmt.Printf("-> %v ", p.info)if p.next == l.head {break}p = p.next}
}func ReverseShowDoublyCircular(l *List) {r := l.tailfor {fmt.Printf("-> %v ", r.info)if r.prev == l.tail {break}r = r.prev}
}

main 示例:

func main() {sl := List{}rnd := rand.New(rand.NewSource(time.Now().UnixNano()))for i := 0; i < 10; i++ {sl.Insert(rnd.Intn(100))}ConvertDoublyToDoublyCircular(&sl)ShowDoublyCircular(&sl)fmt.Println("\n----------------------------")ReverseShowDoublyCircular(&sl)
}

关于在 Go 中实现链表的最后思考

正如我们所见,在 Go 语言中实现链表相当简单。链式分配可以用来表示各种类型的数据——无论是单个值,还是拥有众多字段的复杂数据结构。

当配合指针进行顺序查找时,访问速度非常快。与链表相关的优化技巧也有不少。与单向链表相比,双向链表效率更高,而且能够在两个方向上快速遍历。

  • 知识星球:云原生AI实战营。10+ 高质量体系课( Go、云原生、AI Infra)、15+ 实战项目,P8 技术专家助你提高技术天花板,入大厂拿高薪;
  • 公众号:令飞编程,分享 Go、云原生、AI Infra 相关技术。回复「资料」免费下载 Go、云原生、AI 等学习资料;
  • 哔哩哔哩:令飞编程 ,分享技术、职场、面经等,并有免费直播课「云原生AI高新就业课」,大厂级项目实战到大厂面试通关;

例行海报

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

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

相关文章

新一代机载相控阵雷达的发展

相控阵雷达以其优越的性能在军事领域中有着广阔的应用前景&#xff0c;但由于复杂的技术、昂贵的造价使其应用范围还存在一定的局限性。然而&#xff0c;国内外对相控阵技术的研究非常重视&#xff0c;并取得了丰硕的成果。 军用相控阵雷达主要分为陆基、海基和空基几种类型。 …

多数元素题解(LC:169)

169. 多数元素 核心思想&#xff08;Boyer-Moore 投票算法&#xff09;&#xff1a; 解题思路&#xff1a;可以使用 Boyer-Moore 投票算法、该算法的核心思想是&#xff1a; 维护一个候选元素和计数器、初始时计数器为 0。 遍历数组&#xff1a; 当计数器为 0 时、设置当前元…

数据库 AI 助手测评:Chat2DB、SQLFlow 等工具如何提升开发效率?

一、引言:数据库开发的 “效率革命” 正在发生 在某互联网金融公司的凌晨故障现场,资深 DBA 正满头大汗地排查一条执行超时的 SQL—— 该语句涉及 7 张核心业务表的复杂关联,因索引缺失导致全表扫描,最终引发交易系统阻塞。这类场景在传统数据库开发中屡见不鲜:据 Gartne…

【中间件】bthread效率为什么高?

bthread效率为什么更高&#xff1f; 1 基本概念 bthread是brpc中的用户态线程&#xff08;也可称为M:N线程库&#xff09;&#xff0c;目的是&#xff1a;提高程序的并发度&#xff0c;同时降低编码难度&#xff0c;在多核cpu上提供更好的scalability和cache locality。其采用…

DeepSeek V2:引入MLA机制与指令对齐

长上下文革命:Multi-Head Latent Attention(MLA)机制 传统 Transformer 的多头注意力需要缓存所有输入token的 Key 和 Value,这对长文本推理时的内存开销极为庞大。DeepSeek V2 针对这一难题提出了“Multi-Head Latent Attention”(MLA)机制。MLA 的核心思想是对多头注意…

Druid监控sql导致的内存溢出--内存分析工具MemoryAnalyzer(mat)

问题 druid监控sql在网页端显示&#xff0c;我的服务插入sql比较大&#xff0c;druid把执行过的sql保存在DruidDataSource类的成员变量JdbcDataSourceStat dataSourceStat&#xff1b; JdbcDataSourceStat类中的LinkedHashMap<String, JdbcSqlStat> sqlStatMap中&#…

《Python实战进阶》No45:性能分析工具 cProfile 与 line_profiler

Python实战进阶 No45&#xff1a;性能分析工具 cProfile 与 line_profiler 摘要 在AI模型开发中&#xff0c;代码性能直接影响训练效率和资源消耗。本节通过cProfile和line_profiler工具&#xff0c;实战演示如何定位Python代码中的性能瓶颈&#xff0c;并结合NumPy向量化操作…

计算机操作系统知识集合

主要来自小林coding 硬件结构 cpu位宽 如果用 32 位 CPU 去加和两个 64 位大小的数字&#xff0c;就需要把这 2 个 64 位的数字分成 2 个低位 32 位数字和 2 个高位 32 位数字来计算&#xff0c;先加个两个低位的 32 位数字&#xff0c;算出进位&#xff0c;然后加和两个高位…

电机常用易混淆概念说明(伺服、舵机、多轮)

1. 概述 基础动力需求 &#xff1a;普通电机&#xff08;如水泵、风扇&#xff09;。 高精度控制 &#xff1a;优先伺服系统或伺服电机&#xff08;如数控机床&#xff09;。 微型化场景 &#xff1a;舵机&#xff08;如遥控模型&#xff09;。 移动底盘 &#xff1a;单舵轮成…

进程与线程:04 内核线程

内核级线程概述 上一讲我们学习了用户级线程&#xff0c;了解了其切换和创建方式。用户级线程切换核心在于从一个栈变为两个栈&#xff0c;每个线程有自己的栈和线程控制块&#xff08;tcb&#xff09;&#xff0c;切换时先切换tcb再切换栈&#xff0c;创建时将切换的pc指针放…

信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(六)

个人笔记整理---仅供参考 第六章项目管理概论 6.1PMBOK的发展 6.2项目基本要素 组织过程资产指的是项目上的&#xff0c;国产数据库的使用----安保和安全指的是环境因素 6.3项目经理的角色 6.4价值驱动的项目管理知识体系

[蓝桥杯 2023 国 Python B] 划分 Java

import java.util.*;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int[] arr new int[41];int sum 0;for (int i 1; i < 40; i) {arr[i] sc.nextInt();sum arr[i];}sc.close();int target sum / 2; // 最接近的两…

Redis05-进阶-主从

零、文章目录 Redis05-进阶-主从 1、搭建主从架构 &#xff08;1&#xff09;概述 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 &#xff08;2&#xff09;集群概况 我们搭建的主从…

小结:ipsec-ike

IPSec 手动配置与自动配置&#xff08;IKE动态协商&#xff09; 手动配置IPSec 逻辑图 #mermaid-svg-eNMnNEwnoTjF8fkV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-eNMnNEwnoTjF8fkV .error-icon{fill:#552222;}…

潇洒郎: 100% 成功搭建Docker私有镜像仓库并管理、删除镜像

1、Registry Web管理界面 2、拉取Registry-Web镜像 创建配置文件 tee /opt/zwx-registry/web-config.yml <<-EOF registry:url: http://172.28.73.90:8010/v2name: registryreadonly: falseauth:enabled: false EOF 拉取docker-registry-web镜像并绑定Registry仓库 …

《机器学习中的过拟合与模型复杂性:理解与应对策略》

《机器学习中的过拟合与模型复杂性&#xff1a;理解与应对策略》 摘要 在机器学习中&#xff0c;过拟合是模型在训练数据上表现良好但在新数据上泛化能力差的现象。本文深入探讨了过拟合与模型复杂性之间的关系&#xff0c;分析了复杂模型导致过拟合的原因&#xff0c;并介绍…

linux中sigint和sigterm的区别

SIGINT 和 SIGTERM 是在 Unix 及类 Unix 系统&#xff08;包括 Linux&#xff09;中用于进程间通信的信号&#xff0c;它们都可以用于请求进程终止&#xff0c;区别如下&#xff1a; 1、信号编号与定义 在信号机制里&#xff0c;每个信号都有对应的编号&#xff0c;这便于系统…

一套SaaS ERP管理系统源码,支持项目二开商用,SpringBoot+Vue+ElementUI+UniAPP

ERP管理系统源码&#xff0c;一款适用于小微企业的SaaS ERP管理系统源码, 采用最新的技术栈开发(SpringBootVueElementUIUniAPP)&#xff0c;让企业简单上云。 专注于小微企业的应用需求&#xff0c;如企业基本的进销存、询价&#xff0c;报价, 采购、销售、MRP生产制造、品质…

2025 新生 DL-FWI 培训

摘要: 本贴给出 8 次讨论式培训的提纲, 每次培训 1 小时. 1. Basic concepts 主动学习: 提问, 理解, 继续追问. 通过不断迭代, 逐步提升问题的质量, 加深理解. 1.1 Seismic exploration 问 DeepSeek (下同): 为什么进行地震勘探? 问: 地震勘探一般的深度是多少? 1.2 Sesmi…

mac电脑pytest生成测试报告

时隔了好久再写代码&#xff0c;感觉我之前的积累都白费了&#xff0c;全部忘记了&#xff0c;看来每一步都有记录对于我来说才是最好的。 最近又要重新搞接口自动化&#xff0c;然而是在mac电脑&#xff0c;对于我长期使用windows的人来说真的是个考验&#xff0c;对此次过程…