链表(链式存储)基本原理

简介: 本文介绍链表的基本概念与操作,对比力扣中的单链表与编程语言标准库中更复杂的双链表。链表通过指针连接分散的内存块,支持高效增删,无需连续空间和扩容,但不支持随机访问。文中详解单链表的创建、遍历、头尾及中间插入等操作,并简述双链表优势。

刷过力扣的读者肯定对单链表非常熟悉,力扣上的单链表节点定义如下:
这仅仅是一个最简单的单链表节点,方便力扣出算法题来考你。在实际的编程语言中,我们使用的链表节点会稍微复杂一点,类似这样:
主要区别有两个:
1、编程语言标准库一般都会提供泛型,即你可以指定 val 字段为任意类型,而力扣的单链表节点的 val 字段只有 int 类型。
2、编程语言标准库一般使用的都是双链表而非单链表。单链表节点只有一个 next 指针,指向下一个节点;而双链表节点有两个指针,prev 指向前一个节点,next 指向下一个节点。
有了 prev 前驱指针,链表支持双向遍历,但由于要多维护一个指针,增删查改时会稍微复杂一些,后面带大家实现双链表时会具体介绍。
为什么需要链表
前面介绍了 数组(顺序存储)的底层原理,说白了就是一块连续的内存空间,有了这块内存空间的首地址,就能直接通过索引计算出任意位置的元素地址。
链表不一样,一条链表并不需要一整块连续的内存空间存储元素。链表的元素可以分散在内存空间的天涯海角,通过每个节点上的 next, prev 指针,将零散的内存块串联起来形成一个链式结构。
这样做的好处很明显,首先就是可以提高内存的利用效率,链表的节点不需要挨在一起,给点内存 new 出来一个节点就能用,操作系统会觉得这娃好养活。
另外一个好处,它的节点要用的时候就能接上,不用的时候拆掉就行了,从来不需要考虑扩缩容和数据搬移的问题,理论上讲,链表是没有容量限制的(除非把所有内存都占满,这不太可能)。
当然,不可能只有好处没有局限性。数组最大的优势是支持通过索引快速访问元素,而链表就不支持。
这个不难理解吧,因为元素并不是紧挨着的,所以如果你想要访问第 3 个链表元素,你就只能从头结点开始往顺着 next 指针往后找,直到找到第 3 个节点才行。
上面是对链表这种数据结构的基本介绍,接下来我们就结合代码实现单/双链表的几个基本操作。
单链表的基本操作
如果觉得抽象,以下网站可以帮助完成:https://visualgo.net/zh/list
我先写一个工具函数,用于创建一条单链表,方便后面的讲解:
查/改
单链表的遍历/查找/修改
比方说,我想访问单链表的每一个节点,并打印其值,可以这样写:
类似的,如果是要通过索引访问或修改链表中的某个节点,也只能用 for 循环从头结点开始往后找,直到找到索引对应的节点,然后进行访问或修改。
增
在单链表头部插入新元素
直接看代码吧,很简单:
Java
运行代码
复制代码
1
2
3
4
5
6
7
8
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});

// 在单链表头部插入一个新节点 0
ListNode newHead = new ListNode(0);
newHead.next = head;
head = newHead;
// 现在链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5
在单链表尾部插入新元素
这个操作稍微复杂一点,因为我们要先从头结点开始遍历到链表的最后一个节点,然后才能在最后一个节点后面再插入新节点:
Java
运行代码
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});

// 在单链表尾部插入一个新节点 6
ListNode p = head;
// 先走到链表的最后一个节点
while (p.next != null) {
p = p.next;
}
// 现在 p 就是链表的最后一个节点
// 在 p 后面插入新节点
p.next = new ListNode(6);

// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6
当然,如果我们持有对链表尾节点的引用,那么在尾部插入新节点的操作就会变得非常简单,不用每次从头去遍历了。这个优化会在后面具体实现双链表时介绍
在单链表中间插入新元素
这个操作稍微有点复杂,我们还是要先找到要插入位置的前驱节点,然后操作前驱节点把新节点插入进去:
Java
运行代码
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});

// 在第 3 个节点后面插入一个新节点 66
// 先要找到前驱节点,即第 3 个节点
ListNode p = head;
for (int i = 0; i < 2; i++) {
p = p.next;
}
// 此时 p 指向第 3 个节点
// 组装新节点的后驱指针
ListNode newNode = new ListNode(66);
newNode.next = p.next;

// 插入新节点
p.next = newNode;

// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

相关文章
|
11月前
|
存储 人工智能 机器人
LangGraph 圣经:从0到1穿透 multi-agent多智能体 入门实战
LangGraph 圣经:从0到1穿透 multi-agent多智能体 入门实战
LangGraph 圣经:从0到1穿透 multi-agent多智能体 入门实战
|
3月前
|
人工智能 开发工具 数据库
基于阿里云 Milvus 构建短剧生产平台--生图生视频与内容搜索实践
本文用四个 Milvus AI Function 把 AIGC 短剧生产的完整链路串成一条「生成 → 检索」流水线,它的价值不只是"生成快",更在于把每一次生成都沉淀为可检索、可复用的素材资产,让内容团队从一次性消耗走向资产化经营。
|
8月前
|
数据采集 SQL 人工智能
不会写代码,也能用AI做数据分析?手把手教你
本文介绍如何用AI(如ChatGPT或文心一言、通义千问等)零代码完成日常数据分析:上传Excel,用自然语言提问,即可自动清洗数据、发现异常、计算指标、生成图表与汇报文案,大幅提升运营、销售、产品等岗位的数据处理效率。
|
11月前
|
XML Java 数据格式
springboot@Configuration
`@Configuration` 注解用于标记配置类,相当于 XML 配置文件,配合 `@Bean` 注解注册 Bean 到 Spring 容器。通过 `AnnotationConfigApplicationContext` 可加载配置类并启动 IOC 容器,实现组件的自动管理与注入。
|
11月前
|
缓存 网络协议 算法
核心原理:能否画张图解释下 RPC 的通信流程?
RPC(远程过程调用)是一种实现分布式系统间通信的技术,它让调用远程服务像调用本地方法一样简单。本文深入浅出地讲解了RPC的定义、核心目标、通信流程及在微服务架构中的关键作用,帮助开发者理解其底层原理,掌握如何通过动态代理、序列化、协议设计等机制屏蔽网络复杂性,提升开发效率与系统可维护性。
|
11月前
|
人工智能 自然语言处理 Cloud Native
概述篇
AI时代重塑软件开发,本课程填补DeepSeek+Cursor+Devbox融合教学空白。零基础也能通过自然语言实现“需求→代码→部署”全链路开发。掌握AI辅助设计数据库、生成代码、联调测试到云原生部署,3小时实战交付完整项目,助力开发者高效转型,抢占智能开发先机。(238字)
|
11月前
|
人工智能 Java 程序员
SpringAI+DeepSeek大模型应用开发
本教程以SpringAI为核心,讲解Java与大模型(如DeepSeek)融合开发,助力传统应用智能化升级。适合Java程序员入门AI开发,推动企业低成本拥抱AI变革。
|
11月前
|
人工智能 自然语言处理 Cloud Native
AI时代代码开发(DeepSeek+Cursor+Devbox)
AI时代重塑软件开发,本课程聚焦DeepSeek+Cursor+Devbox+Sealos工具链,实现自然语言到代码的零基础全栈开发。覆盖需求分析、数据库设计、编码测试至云部署全流程,助力开发者高效构建并上线项目,抢占智能开发先机。(238字)
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
存储 算法 安全
HashMap的实现原理,看这篇就够了
关注【mikechen的互联网架构】,10年+BAT架构经验分享。深入解析HashMap,涵盖数据结构、核心成员、哈希函数、冲突处理及性能优化等9大要点。欢迎交流探讨。
HashMap的实现原理,看这篇就够了

热门文章

最新文章