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);}
}

注意:

  1. 使用 push_back 在链表的尾部添加元素,使用 push_front 在链表的头部添加元素。
  2. 使用 frontback 方法来获取链表头部和尾部的引用。
  3. 可以直接迭代链表来访问它的每个元素。
  4. 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循环的构造中删除元素,你可以使用filterfold等迭代器方法,这些方法不会因删除元素而使迭代器失效。但请注意,这些方法实际上会创建一个新的链表,而不是在原地修改链表。

下面是一个使用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(),它允许你在遍历的同时修改链表。然而,直接删除当前迭代器指向的元素仍然是不安全的,因为这会使迭代器失效。

一种解决方法是使用 LinkedListcursors() 方法,它提供了对链表中元素的安全访问,并且允许你在遍历的同时删除元素。下面是一个使用 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 来编译上面的代码。

这种方法的好处是它允许你在遍历链表的同时安全地删除元素,而无需担心迭代器失效的问题。然而,它仍然不是最高效的方法,因为删除操作需要遍历整个链表。对于大型链表来说,这可能会成为性能瓶颈。在这种情况下,使用其他数据结构(如哈希表或向量)可能更为合适。

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

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

相关文章

Django之Celery篇(三)

一、任务交给Celery Django任务交给Celery的方法和普通使用Celery任务的调用基本无区别,只是将执行代码的放到到View视图中 而获取结果,往往并不能把结果和第1次请求一起响应,若想获取结果是通过第2次请求获取结果 代码如下: from django.http import HttpResponsefrom …

华为ensp中vrrp虚拟路由器冗余协议 原理及配置命令

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; ————前言————— VRRP&#xff08;Virtual Router Redundancy Protocol&#xff0c;虚拟路由器冗余协议&#xff0…

[数据集][目标检测]高质量铁路轨道缺陷检测数据集VOC+YOLO格式1050张6类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1050 标注数量(xml文件个数)&#xff1a;1050 标注数量(txt文件个数)&#xff1a;1050 标注…

【机器学习】基于变色龙算法优化的BP神经网络分类预测(SSA-BP)

目录 1.原理与思路2.设计与实现3.结果预测4.代码获取 1.原理与思路 【智能算法应用】智能算法优化BP神经网络思路【智能算法】变色龙优化算法&#xff08;CSA)原理及实现 2.设计与实现 数据集&#xff1a; 数据集样本总数2000 多输入多输出&#xff1a;样本特征24&#xff…

探秘ChatGPT:打造出色学术论文

ChatGPT无限次数:点击直达 探秘ChatGPT&#xff1a;打造出色学术论文 在当今信息爆炸的时代&#xff0c;如何借助ChatGPT这一先进技术创建卓越的学术论文成为了许多研究者关注的焦点。ChatGPT作为一种基于大规模预训练的生成式模型&#xff0c;具有强大的语言理解和生成能力&a…

集成学习 | 集成学习思想:Boosting思想 | XGBoost算法、LightGBM算法

目录 一. XGBoost 算法1. XGBoost 算法流程2. XGBoost 算法评价 二. LightGBM 算法2. LightGBM 算法优势 上一篇文章中&#xff0c;我们了解了Boosting思想的两种算法&#xff1a;Adboost和GBDT&#xff1b;其中对于GBDT算法&#xff0c;存在两种改进&#xff0c;即&#xff1a…

javaWeb奶茶商城前后台系统

一、简介 在当前数字化时代&#xff0c;电子商务已成为人们生活中不可或缺的一部分。为了满足用户对奶茶的需求&#xff0c;我设计并实现了一个基于JavaWeb的奶茶商城前后台系统。该系统涵盖了用户前台和管理员后台两大模块&#xff0c;包括登录注册、商品展示、购物车管理、订…

我的算法刷题笔记(3.18-3.22)

我的算法刷题笔记&#xff08;3.18-3.22&#xff09; 1. 螺旋矩阵1. total是总共走的步数2. int[][] directions {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};方位3. visited[row][column] true;用于判断是否走完一圈 2. 生命游戏1. 使用额外的状态22. 再复制一份数组 3. 旋转图像观…

PHP之CURL和Socket

文章目录 一、CURL1.基本流程&#xff08;1&#xff09;初始化&#xff08;2&#xff09;向服务器发送请求&#xff08;3&#xff09;向服务器发送请求&#xff08;4&#xff09;关闭curl 2.CURLOPT参数记得写一个文件curl上传的例子记得写一个json上传的例子3.CURL批处理 二、…

【ARXIV2402】MambaIR

这个工作首次将 Mamba 引入到图像修复任务&#xff0c;关于为什么 Mamba 可以用于图像修复&#xff0c;作者有非常详细的解释&#xff1a;一路向北&#xff1a;性能超越SwinIR&#xff01;MambaIR: 基于Mamba的图像复原基准模型 作者认为Mamba可以理解为RNN和CNN的结合&#xf…

Transformer模型

Transformer模型简介 Transformer模型自从2017年被提出以来&#xff0c;就以其优异的性能在自然语言处理(NLP)领域取得了巨大成功。它的设计哲学是完全基于自注意力机制&#xff08;Self-Attention Mechanism&#xff09;&#xff0c;这使得模型能够在处理序列数据时&#xff…

【测试开发学习历程】计算机编程语言

前言&#xff1a; 学习完数据库&#xff0c;我们便要进入到编程语言的内容当中了。 这里先对编程语言写出大致的分类&#xff0c; 在这之后&#xff0c;我们会以Python为重点&#xff0c; 开始测试开发为重点的编程语言学习。 目录 1 计算机编程语言的发展 2 语言的分类…

如何使用break和continue语句控制循环流程?

一、如何使用break和continue语句控制循环流程&#xff1f; 在编程中&#xff0c;break和continue是两个非常重要的控制流语句&#xff0c;它们可以帮助我们更精细地控制循环的执行流程。 break语句 break语句用于立即终止最内层的循环。无论是for循环还是while循环&#xf…

JAVA 学习记录(1)

1.函数 (1)String.join(";", messages); ";" 表示分隔符&#xff0c;输出的结果&#xff1a; message; (2) Double.parseDouble(valueString); 它返回由字符串参数表示的双精度值。 (3) Double.valueOf((Float) value; float 类型的数值转化为double类…

计数组合【2024蓝桥杯0基础】-学习笔记

文章目录 计数原理排列数组合数组合数性质例题分析代码复现 例题2状态分析代码复现 常见的排列组合问题圆排列代码复现 第二类斯特林数 感悟 计数原理 排列数 组合数 组合数性质 例题分析 代码复现 def ksm(a, b, c):ans 1%cwhile b ! 0:if b % 2 0:ans ans * a %ca a * …

java面向对象编程基础

对象&#xff1a; java程序中的对象&#xff1a; 本质上是一种特殊的数据结构 对象是由类new出来的&#xff0c;有了类就可以创建对象 对象在计算机的执行原理&#xff1a; student s1new student();每次new student(),就是在堆内存中开辟一块内存区域代表一个学生对象s1变…

力扣74---合并区间

题目描述&#xff1a; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间。 示例 1&#xff1a; 输入&#xff1…

K3 计划订单投放时,将“关联物料”传递到采购和生产订单的“组部件”字段

参考K/3 WISE 中MRP计算投放过程中 销售订单自定义字段怎么携带到任务单这篇文章&#xff0c;进行优化。 在表ICMrpDestBills下增加触发器&#xff0c;代码如下 CREATE TRIGGER [dbo].[ICMrpDestBills_update]ON [dbo].[ICMrpDestBills]AFTER INSERT,UPDATE AS BEGINSET NO…

I/O 多路复用是什么

核心概念&#xff1a; 批量提交&#xff0c;主动询问。 共用一个Selector的选择器概念。 I/O 多路复用 基本概念 Socket 套接字。对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。 例子&#xff1a;客户端将数据通过网线发送到服务端&#xff0c;客户端发送数据需…

【暴刷力扣】283. 移动零

283. 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nu…