Rust 双向链表 LinkedList 和安全删除元素的方法
一、LinkedList 基本用法
在Rust中,LinkedList
是标准库中 std::collections
模块提供的一个双向链表实现。这个双向链表在每个节点中都保存了其前一个和后一个节点的引用,允许在链表的任一端进行有效的添加和移除操作。
以下是一个简单的示例,演示了如何使用 LinkedList
:
use std::collections::LinkedList;fn main() {// 创建一个新的空双向链表let mut list = LinkedList::new();// 在链表的尾部添加一个元素list.push_back("rust".to_string());// 在链表的头部添加一个元素list.push_front("learn".to_string());// 访问链表的头部和尾部元素println!("Front of the list: {:?}", list.front());println!("Back of the list: {:?}", list.back());// 遍历链表并打印元素for element in &list {println!("{}", element);}// 使用迭代器移除链表中的元素for element in list.iter_mut() {if element.contains("ust") {*element = "replaced!".to_string();}}// 展示修改后的链表println!("List after modification:");for element in &list {println!("{}", element);}
}
注意:
- 使用
push_back
在链表的尾部添加元素,使用push_front
在链表的头部添加元素。 - 使用
front
和back
方法来获取链表头部和尾部的引用。 - 可以直接迭代链表来访问它的每个元素。
iter_mut
方法返回一个可变迭代器,可以用来修改链表中的元素。
请记住,双向链表的性能特征意味着在链表的中间插入或删除元素可能比在数组的对应位置进行相同的操作更加低效,因为链表必须遍历节点直到找到所需的位置。然而,在链表的两端插入和删除则是非常高效的。
由于Rust的所有权模型,当您将元素添加到 LinkedList
中时,链表将取得该元素的所有权,这意味着您不再能直接访问原始对象(除非您从链表中移除了该对象或者以某种方式共享了所有权,比如使用引用计数)。这在设计链表和处理所有权转移时是需要特别注意的。
二、双向链表LinkedList删除中间的元素效率比 Vec 低吗?
是的,从双向链表(LinkedList
)中删除中间的元素通常比从向量(Vec
)中删除元素的效率低。这是由两者不同的数据结构和内存布局所决定的。
对于 Vec
(向量):
- 向量在内存中是一块连续的空间,这意味着访问任意元素(尤其是中间的元素)都非常快,因为你可以直接通过偏移来找到它。
- 删除一个元素(尤其是在中间)涉及移动该元素之后的所有元素来填补空位,这虽然有一定的开销,但由于内存是连续的,这个过程仍然相对高效。对于大型向量,这种开销可能会变得显著。
对于 LinkedList
(双向链表):
- 链表中的每个元素都单独存储在内存中,并通过指针链接在一起。这意味着访问链表中的任意元素(尤其是中间的元素)都需要从头部或尾部开始遍历,直到找到目标元素,这是一个 O(n) 操作。
- 删除链表中的一个元素不需要移动其他元素,你只需要调整相邻元素的指针来绕过被删除的元素。虽然指针的调整本身是常数时间的,但找到要删除的元素需要遍历,这使得整个操作变得低效,尤其是对于长链表。
总的来说,如果你经常需要随机访问或删除中间的元素,Vec
可能是更好的选择。另一方面,如果你主要进行的是在头部或尾部的插入和删除操作,那么 LinkedList
可能会提供更好的性能。在选择数据结构时,了解你的具体用例和数据访问模式是非常重要的。
三、双向链表LinkedList在迭代循环中删除元素的效率如何?
双向链表在迭代循环中删除元素的效率主要取决于删除操作的位置。
在双向链表的头部或尾部删除元素通常是一个相对高效的操作,因为这两个位置都保留了直接的指针引用,可以直接调整指针来移除元素,无需遍历整个链表。这种情况下,删除操作的时间复杂度是O(1)。
然而,如果在迭代循环中需要删除链表中间的某个元素,效率就会降低。因为你需要从链表的头部或尾部开始遍历,直到找到要删除的元素。这个遍历过程的时间复杂度是O(n),其中n是链表的长度。一旦找到要删除的元素,你可以通过调整相邻节点的指针来将其从链表中移除,这个过程的时间复杂度是O(1)。但是,由于需要遍历链表来找到要删除的元素,所以整体删除操作的时间复杂度是O(n)。
另外,值得注意的是,在迭代循环中删除元素时需要谨慎处理迭代器和链表的状态。一种常见的做法是使用“安全”的删除方法,比如在迭代过程中收集要删除的元素的索引或引用,然后在循环结束后再进行实际的删除操作。这样可以避免在迭代过程中修改链表结构导致的迭代器失效问题。
总的来说,双向链表在迭代循环中删除元素的效率取决于删除位置。在头部或尾部删除是高效的,而在中间位置删除则需要遍历链表,效率较低。因此,在选择使用双向链表时,需要根据具体的应用场景和数据访问模式来权衡其性能特点。
四、从双向链表LinkedList删除元素的例子
下面是一个Rust程序示例,它创建了一个包含1到100之间自然数的双向链表,并在一个迭代循环中删除了所有的素数。
use std::collections::LinkedList;// 判断一个数是否为素数
fn is_prime(n: u32) -> bool {if n <= 1 {return false;}if n <= 3 {return true;}if n % 2 == 0 || n % 3 == 0 {return false;}let mut i = 5;while i * i <= n {if n % i == 0 || n % (i + 2) == 0 {return false;}i += 6;}true
}fn main() {// 创建一个包含1到100的双向链表let mut list: LinkedList<u32> = (1..=100).collect();// 使用迭代器删除素数list.retain(|&x| !is_prime(x));// 打印剩余的元素println!("Remaining elements after removing primes:");for num in &list {println!("{}", num);}
}
在这个程序中,我们首先定义了一个is_prime
函数,用于判断一个数是否为素数。然后,我们使用LinkedList::retain
方法,它接受一个闭包(closure),并保留闭包返回true
的元素。在我们的例子中,闭包检查每个元素是否不是素数(!is_prime(x)
),因此所有素数都会被移除。
最后,我们遍历并打印出链表中剩余的元素,这些元素都是非素数。
注意:retain
方法是原地操作,它直接修改链表,而不是创建一个新的链表。这通常比创建一个新链表并复制非删除元素更高效。
五、可否在 for 循环中删除双向链表LinkedList中特定的元素?
在Rust中,直接在for
循环中删除双向链表(LinkedList
)的元素是困难的,因为当你删除一个元素时,它会使当前迭代器失效。因此,你不能简单地遍历链表并在循环体中删除元素。相反,你应该使用LinkedList
提供的安全方法来删除元素,比如retain
(正如之前所示),或者显式地处理迭代器失效的问题。
如果你确实需要在for
循环的上下文中删除元素(尽管这不是推荐的做法),你可以通过收集要删除的元素的索引或引用,然后在循环结束后再进行删除。但是,对于LinkedList
来说,这通常不是必要的,因为retain
方法提供了一种更简洁且安全的方式来删除元素。
然而,如果你坚持要在类似for
循环的构造中删除元素,你可以使用filter
或fold
等迭代器方法,这些方法不会因删除元素而使迭代器失效。但请注意,这些方法实际上会创建一个新的链表,而不是在原地修改链表。
下面是一个使用filter
方法的示例,它创建一个新链表,只包含原始链表中的非素数元素:
use std::collections::LinkedList;// 判断一个数是否为素数
fn is_prime(n: &u32) -> bool {if n <= &1 {return false;}if n <= &3 {return true;}if n % 2 == 0 || n % 3 == 0 {return false;}let mut i = 5;while i * i <= *n {if n % i == 0 || n % (i + 2) == 0 {return false;}i += 6;}true
}fn main() {// 创建一个包含1到100的双向链表let mut list: LinkedList<u32> = (1..=100).collect();// 使用filter方法创建一个新的链表,其中不包含素数let filtered_list: LinkedList<u32> = list.iter().filter(|x| !is_prime(x)).cloned().collect();// 打印剩余的元素println!("Remaining elements after removing primes:");for num in &filtered_list {println!("{}", num);}// 如果你确实需要修改原始链表,你可以将filtered_list重新赋值给list// 但请注意,这将替换原始链表,而不是在原地修改它// list = filtered_list;
}
请注意,上面的代码实际上并没有修改原始的list
链表,而是创建了一个新的filtered_list
链表。如果你需要修改原始链表,你可以将filtered_list
重新赋值给list
(取消注释最后一行代码),但请记住这不是原地修改。
对于双向链表来说,原地删除元素通常是通过直接操作链表节点和指针来实现的,而这在Rust的LinkedList
抽象中是隐藏的。因此,使用retain
方法是更符合Rust惯用法的做法。
六、其他在循环体内删除LinkedList元素的方法
当然,如果你想要在类似 for
循环的构造中直接修改双向链表(即原地删除元素),你可以使用 LinkedList
提供的内部迭代器,比如 iter_mut()
,它允许你在遍历的同时修改链表。然而,直接删除当前迭代器指向的元素仍然是不安全的,因为这会使迭代器失效。
一种解决方法是使用 LinkedList
的 cursors()
方法,它提供了对链表中元素的安全访问,并且允许你在遍历的同时删除元素。下面是一个使用 cursors()
方法在双向链表中删除素数的示例:
use std::collections::LinkedList;// 判断一个数是否为素数
fn is_prime(n: &u32) -> bool {if *n <= 1 {return false;}for i in 2..=(n.sqrt() as u32) {if *n % i == 0 {return false;}}true
}fn main() {// 创建一个包含1到100的双向链表let mut list: LinkedList<u32> = LinkedList::from(1..=100);// 使用cursors()方法删除素数let mut cur = list.cursors_front();while let Some(cursor) = cur.as_mut() {if is_prime(&cursor.current()) {cursor.remove();} else {cur.move_next();}}// 打印剩余的元素println!("Remaining elements after removing primes:");for num in &list {println!("{}", num);}
}
在这个例子中,我们使用 cursors_front()
方法来获取一个指向链表前端的游标(cursor)。然后,我们在循环中使用 as_mut()
方法来获取一个可变的游标引用,并检查当前游标指向的元素是否为素数。如果是素数,我们使用 remove()
方法删除它;否则,我们使用 move_next()
方法将游标移动到下一个元素。
请注意,cursors()
方法是在 Rust 1.61.0 版本中引入的,因此你需要使用至少这个版本的 Rust 来编译上面的代码。
这种方法的好处是它允许你在遍历链表的同时安全地删除元素,而无需担心迭代器失效的问题。然而,它仍然不是最高效的方法,因为删除操作需要遍历整个链表。对于大型链表来说,这可能会成为性能瓶颈。在这种情况下,使用其他数据结构(如哈希表或向量)可能更为合适。
相关文章:
Rust 双向链表 LinkedList 和安全删除元素的方法
一、LinkedList 基本用法 在Rust中,LinkedList 是标准库中 std::collections 模块提供的一个双向链表实现。这个双向链表在每个节点中都保存了其前一个和后一个节点的引用,允许在链表的任一端进行有效的添加和移除操作。 以下是一个简单的示例…...
Android 开发中 Gradle 使用详解:构建、配置与优化技巧
文章目录 1. 基本概念2. 配置构建脚本2.1 项目级构建脚本2.2 模块级构建脚本 3. 自定义构建变体和应用 flavorDimensions4. 多模块项目4.1 创建模块4.2 配置模块依赖 5. 使用 Gradle 插件6. 使用 Gradle 命令 Gradle 是一种先进的构建工具,它被广泛应用于 Android 开…...

聚道云助力:易快报CDP无缝对接,登录同步一步到位!
一、客户介绍 某企业咨询有限公司是一家专注于为企业提供全方位、高质量咨询服务的领先机构。该公司致力于将先进的管理理念和实践经验与企业实际需求相结合,助力企业实现可持续发展。无论是战略规划、组织优化、人力资源管理,还是市场营销、财务管理等…...
Java解决幸运数字
Java解决幸运数字 01 题目 哈沙德数是指在某个固定的进位制当中,可以被各位数字之和整 除的正整数。 例如 126 是十进制下的一个哈沙德数,因为 (126)10 mod (1 2 6) 0; 126 也是8进制下的哈沙德 数,因为(126)10 (176)8,(126)10…...
将一个nextjs项目部署到vercel
注:下面均为AI创作(本人已验证该流程可行) 将一个 Next.js 项目部署到 Vercel 是一个相对直接的过程,因为 Vercel 是由同一个团队开发的,专门为 Next.js 优化。以下是部署一个 Next.js 项目到 Vercel 的基本步骤&…...

RocketMQ学习笔记:分布式事务
这是本人学习的总结,主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、分布式事务的难题2、解决方式2.1、半事务消息和事务回查2.2、代码样例2.2.1、TransactionListener2.2.2、TransactionMQProducer2.2.3、MessageListenerConcurrently2.2.4、流程图 1、分布…...

单臂路由和三层交换机
目录 一.单臂路由 1.单臂路由的工作原理 2.单臂路由的配置 2.1画出拓扑图 2.2配置PC 2.3配置交换机 2.4配置路由器 2.5测试 二.三层交换机 1.三层交换机的概述 2.三层交换机的配置 2.1画出拓扑图 2.2配置PC 2.3配置二层交换机 2.4配置三层交换机 2.5测试 3.拓展 三.总结 一.…...

红岩思维导图的制作软件,分享4款热门的!
红岩思维导图的制作软件,分享4款热门的! 在当今信息爆炸的时代,思维导图作为一种有效的知识整理和思维拓展工具,受到了广大用户的青睐。红岩思维导图以其独特的风格和实用性,成为了许多人学习和工作中的得力助手。那么…...
es 集群开机自动启动
前面搭建了 es 集群,但是每次机器重启 都需要手动启动,很麻烦,所以这里介绍一下开机自动启动 首先使用 root 用户 es : 执行以下命令 vim /etc/init.d/elasticsearch 将以下内容 cv 进去 #!/bin/bash #chkconfig: 345 63 …...
使用JMeter从JSON响应的URL参数中提取特定值
在使用Apache JMeter进行API测试时,我们经常需要从JSON格式的响应中提取特定字段的值。这可以通过使用JMeter内置的JSON提取器和正则表达式提取器来完成。以下是一个具体的例子,展示了如何从一个JSON响应中提取rowId的值,同时处理字符串终止符…...

汽车电子行业知识:自动驾驶系统结构和各模块功能
文章目录 2.自动驾驶系统结构和各模块功能2.1.自动驾驶系统结构2.2.车载传感器2.2.1.激光雷达2.2.2.毫米波雷达2.2.3.超声波雷达2.2.4.摄像头2.2.5.GNSS2.2.6. IMU2.2.7.多传感器融合 2.3.各功能模块2.3.1.高精度地图2.3.2.定位2.3.3.感知2.3.4.决策2.3.5.规划2.3.6.控制2.3.7.…...

Oracle参数文件详解
1、参数文件的作用 参数文件用于存放实例所需要的初始化参数,因为多数初始化参数都具有默认值,所以参数文件实际存放了非默认的初始化参数。 2、参数文件类型 1)服务端参数文件,又称为 spfile 二进制的文件,命名规则…...

鸿蒙(HarmonyOS)Navigation如何实现多场景UI适配?
场景介绍 应用在不同屏幕大小的设备上运行时,往往有不同的UI适配,以聊天应用举例: 在窄屏设备上,联系人和聊天区在多窗口中体现。在宽屏设备上,联系人和聊天区在同一窗口体现。 要做好适配,往往需要开发…...

PTGui图像拼接实验
1 PTGui图像拼接实验 1.1 概述 图像拼接技术就是将数张有重叠部分的图像(可能是不同时间、不同视角或者不同传感器获得的)拼成一幅无缝的全景图或高分辨率图像的技术 图像配准(image alignment)和图像融合是图像拼接的两个关键…...

C++|类封装、类的分文件编写练习:设计立方体类、点和圆的关系
文章目录 练习案例1:设计立方体类CPP代码 练习案例2:点和圆的关系CPP代码 代码总结类的分文件编写 练习案例1:设计立方体类 设计立方体类(Cube) 求出立方体的面积和体积 分别用全局函数和成员函数判断两个立方体是否相等。 CPP代码 class Cube { pub…...

大数据开发扩展shell--尚硅谷shell笔记
大数据开发扩展shell 学习目标 1 熟悉shell脚本的原理和使用 2 熟悉shell的编程语法 第一节 Shell概述 1)Linux提供的Shell解析器有: 查看系统中可用的 shell [atguiguhadoop101 ~]$ cat /etc/shells /bin/sh/bin/bash/sbin/nologin/bin/dash/bin/t…...

考研数学|《1800》《1000》《880》《660》最佳搭配使用方法
直接说结论:基础不好先做1800、强化之前660,强化可选880/1000题。 首先,传统习题册存在的一个问题是题量较大,但难度波动较大。《汤家凤1800》和《张宇1000》题量庞大,但有些题目难度不够平衡,有些过于简单…...
【GameFramework框架内置模块】17、声音(Sound)
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录: https://blog.csdn.net/q764424567/article/details/1…...

视频记录历史播放位置效果
简介 每次打开页面视频从上一次的播放位置开始播放 利用lodash库做节流 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…...

Request Response
简介 Request(请求) & Response(响应) 浏览器会向服务器发送请求数据,服务器也需要返回响应数据给浏览器,因此我们需要设置对应的类来代表请求数据和响应数据,且Servlet中的service方法就需…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...