单/双链表代码实现

简介: 本文详解双链表与单链表的 MyLinkedList 实现,重点介绍三个关键优化:1)同时持有头尾节点引用,提升尾部操作效率;2)使用虚拟头尾节点简化边界处理;3)解析链表删除中的内存泄露误区,并强调指针置空的良好编程习惯。

几个关键点
下面我会分别用双链表和单链给出一个简单的 MyLinkedList 代码实现,包含了基本的增删查改功能。这里给出几个关键点,等会你看代码的时候可以着重注意一下。
关键点一、同时持有头尾节点的引用
在力扣做题时,一般题目给我们传入的就是单链表的头指针。但是在实际开发中,用的都是双链表,而双链表一般会同时持有头尾节点的引用。
因为在软件开发中,在容器尾部添加元素是个非常高频的操作,双链表持有尾部节点的引用,就可以在 O(1)的时间复杂度内完成尾部添加元素的操作。
对于单链表来说,持有尾部节点的引用也有优化效果。比如你要在单链表尾部添加元素,如果没有尾部节点的引用,你就需要遍历整个链表找到尾部节点,时间复杂度是 O(n);如果有尾部节点的引用,就可以在 O(1)的时间复杂度内完成尾部添加元素的操作。
细心的读者可能会说,即便如此,如果删除一次单链表的尾结点,那么之前尾结点的引用就失效了,还是需要遍历一遍链表找到尾结点。
是的,但你再仔细想想,删除单链表尾结点的时候,是不是也得遍历到倒数第二个节点(尾结点的前驱),才能通过指针操作把尾结点删掉?那么这个时候,你不就可以顺便把尾结点的引用给更新了吗?
关键点二、虚拟头尾节点
在上一篇文章 链表基础 中我提到过「虚拟头尾节点」技巧,它的原理很简单,就是在创建双链表时就创建一个虚拟头节点和一个虚拟尾节点,无论双链表是否为空,这两个节点都存在。这样就不会出现空指针的问题,可以避免很多边界情况的处理。
举例来说,假设虚拟头尾节点分别是 dummyHead 和 dummyTail,那么一条空的双链表长这样:
如果你添加 1,2,3 几个元素,那么链表长这样:
你以前要把在头部插入元素、在尾部插入元素和在中间插入元素几种情况分开讨论,现在有了头尾虚拟节点,无论链表是否为空,都只需要考虑在中间插入元素的情况就可以了,这样代码会简洁很多。
当然,虚拟头结点会多占用一点内存空间,但是比起给你解决的麻烦,这点空间消耗是划算的。
对于单链表,虚拟头结点有一定的简化作用,但虚拟尾节点没有太大作用。
虚拟节点是内部实现,对外不可见
虚拟节点是你内部实现数据结构的技巧,对外是不可见的。比如按照索引获取元素的 get(index) 方法,都是从真实节点开始计算索引,而不是从虚拟节点开始计算。
关键点三、内存泄露?
在前文 动态数组实现 中,我提到了删除元素时,要注意内存泄露的问题。那么在链表中,删除元素会不会也有内存泄露的问题呢?
尤其是这样的写法,你觉得有没有问题:
细心的读者可能认为这样写会有内存泄露的问题,因为原来的那个头结点 1 的 next 指针没有断开,依然指向着节点 2。
但实际上这样写是 OK 的,因为 Java 的垃圾回收的判断机制是看这个对象是否被别人引用,而并不会 care 这个对象是否还引用着别人。
那个节点 1 的 next 指针确实还指向着节点 2,但是并没有别的指针引用节点 1 了,所以节点 1 最终会被垃圾回收器回收释放。所以说这个场景和数组中删除元素的场景是不一样的,你可以再仔细思考一下。
不过呢,删除节点时,最好还是把被删除节点的指针都置为 null,这是个好习惯,不会有什么代价,还可能避免一些潜在的问题。所以在下面的实现中,无论是否有必要,我都会把被删除节点上的指针置为 null。
双链表代码实现
Java
运行代码
复制代码
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import java.util.NoSuchElementException;
if (size < 1) {
throw new NoSuchElementException();
}

    return tail.prev.val;
}

// ***** 改 *****

public E set(int index, E val) {
    checkElementIndex(index);
    // 找到 index 对应的 Node
    Node<E> p = getNode(index);

    E oldVal = p.val;
    p.val = val;

    return oldVal;
}

// ***** 其他工具函数 *****

public int size() {
    return size;
}

public boolean isEmpty() {
    return size == 0;
}

private Node<E> getNode(int index) {
    checkElementIndex(index);
    Node<E> p = head.next;
    // TODO: 可以优化,通过 index 判断从 head 还是 tail 开始遍历
    for (int i = 0; i < index; i++) {
        p = p.next;
    }
    return p;
}

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

// 检查 index 索引位置是否可以存在元素
private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}

// 检查 index 索引位置是否可以添加元素
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}

private void display() {
    System.out.println("size = " + size);
    for (Node<E> p = head.next; p != tail; p = p.next) {
        System.out.print(p.val + " <-> ");
    }
    System.out.println("null");
    System.out.println();
}

public static void main(String[] args) {
    MyLinkedList<Integer> list = new MyLinkedList<>();
    list.addLast(1);
    list.addLast(2);
    list.addLast(3);
    list.addFirst(0);
    list.add(2, 100);
相关文章
|
3月前
|
人工智能 Linux API
阿里云轻量应用服务器智能体专用型实例介绍:搭建agent更便宜更简单,2核2G服务器+2亿Token只需262.5元
对于广大开发者和中小企业而言,想要搭建一个完整的Agent运行环境,往往面临着“拼凑式”开发的痛点:既要买服务器,又要单独申请大模型API Key,还得手动配置网络和操作系统,不仅门槛高,成本也难以控制。针对这一行业难题,阿里云正式推出了轻量应用服务器智能体专用型实例。这是一款专为AI Agent场景打造的“开箱即用”的一体化解决方案,它将计算资源与大模型Tokens打包交付,极大地降低了AI应用的构建门槛。
|
11月前
|
弹性计算 运维 监控
运维编排OOS:自动化运维实战
运维编排OOS通过模板化、自动化方式,实现云上运维任务的高效、安全执行。本文详解OOS核心概念、系统与自定义模板、典型场景实践、监控联动及安全控制,并提供常用模板库,助力企业快速构建标准化、智能化的自动化运维体系,降本增效,保障业务稳定。
1094 0
|
4月前
|
人工智能 运维 安全
告别爬塔危险:AR眼镜如何重构电力高空巡检安全标准
在电力能源行业,高压输电线路与变电站设备的日常巡检是保障电网稳定运行的基石。然而,传统的人工登塔、近距离目视检查不仅效率低下,更伴随着极高的人身安全风险。随着增强现实(AR)、云计算、人工智能(AI)及5G通信技术的成熟,一种基于智能穿戴设备的“AR智慧运维系统”正在重塑这一领域的作业模式。本文将深入探讨基于云边端协同架构的AR智能巡检技术方案,分析其如何通过数字化手段实现作业标准化、远程专家协作及全流程数据闭环,从而从根本上提升电力高空巡检的安全性与效率。
|
机器学习/深度学习 人工智能 自然语言处理
AI Compass前沿速览:Cursor 2.0、Firefly Image5、Agent HQ 、LongCat-Video、Kimi-k2 Thinking
AI Compass前沿速览:Cursor 2.0、Firefly Image5、Agent HQ 、LongCat-Video、Kimi-k2 Thinking
AI Compass前沿速览:Cursor 2.0、Firefly Image5、Agent HQ 、LongCat-Video、Kimi-k2 Thinking
|
11月前
|
存储 运维 监控
日志服务SLS:日志采集与分析
日志服务SLS是阿里云提供的一站式日志解决方案,支持采集、存储、分析、投递全链路管理。通过Logtail、SDK、API实现多场景日志接入,结合查询语法、可视化图表与机器学习,助力运维监控、安全审计与成本优化,广泛应用于Nginx分析、错误排查及智能异常检测,提升企业数字化运营效率。(238字)
1006 0
|
8月前
|
人工智能 自然语言处理 监控
新手必看|两步部署OpenClaw(龙虾),轻松拥有专属AI助理!
OpenClaw(昵称“龙虾”)是一款开源、本地优先的AI智能体框架,支持零代码、两步部署,可直接执行文件整理、代码检查、邮件回复、价格监控等任务。数据本地处理,隐私安全,兼容微信/钉钉等50+平台,插件超1700个,真正实现“聊天即行动”。
1674 6
|
11月前
|
人工智能 自然语言处理 Cloud Native
AI时代代码开发(DeepSeek+Cursor+Devbox)
AI时代重塑软件开发,本课程聚焦DeepSeek+Cursor+Devbox+Sealos工具链,实现自然语言到代码的零基础全栈开发。覆盖需求分析、数据库设计、编码测试至云部署全流程,助力开发者高效构建并上线项目,抢占智能开发先机。(238字)
|
11月前
|
存储 Java Maven
服务端(DevBox)-项目创建
使用Sealos创建SpringBoot工程zxyf-management,配置Java语言、3.3.2版本,2核CPU、4G内存,通过Devbox在云端搭建开发环境。利用Cursor智能工具打开项目,自动识别Maven结构,一键启动运行,实现高效云端开发。
|
11月前
|
缓存 网络协议 算法
核心原理:能否画张图解释下 RPC 的通信流程?
RPC(远程过程调用)是一种实现分布式系统间通信的技术,它让调用远程服务像调用本地方法一样简单。本文深入浅出地讲解了RPC的定义、核心目标、通信流程及在微服务架构中的关键作用,帮助开发者理解其底层原理,掌握如何通过动态代理、序列化、协议设计等机制屏蔽网络复杂性,提升开发效率与系统可维护性。
|
11月前
|
存储 SQL 人工智能
AI时代代码开发(数据库设计)
本文介绍基于三范式与DDD的数据库设计流程,结合AI工具辅助分析页面原型,通过部门、员工及工作经历模块,演示表结构设计与优化过程,强调人工校验与调整的重要性,最终完成符合业务需求的数据库建模与测试数据构建。

热门文章

最新文章