当前位置: 首页 > news >正文

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 来编译上面的代码。

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

相关文章:

Rust 双向链表 LinkedList 和安全删除元素的方法

一、LinkedList 基本用法 在Rust中&#xff0c;LinkedList 是标准库中 std::collections 模块提供的一个双向链表实现。这个双向链表在每个节点中都保存了其前一个和后一个节点的引用&#xff0c;允许在链表的任一端进行有效的添加和移除操作。 以下是一个简单的示例&#xf…...

Android 开发中 Gradle 使用详解:构建、配置与优化技巧

文章目录 1. 基本概念2. 配置构建脚本2.1 项目级构建脚本2.2 模块级构建脚本 3. 自定义构建变体和应用 flavorDimensions4. 多模块项目4.1 创建模块4.2 配置模块依赖 5. 使用 Gradle 插件6. 使用 Gradle 命令 Gradle 是一种先进的构建工具&#xff0c;它被广泛应用于 Android 开…...

聚道云助力:易快报CDP无缝对接,登录同步一步到位!

一、客户介绍 某企业咨询有限公司是一家专注于为企业提供全方位、高质量咨询服务的领先机构。该公司致力于将先进的管理理念和实践经验与企业实际需求相结合&#xff0c;助力企业实现可持续发展。无论是战略规划、组织优化、人力资源管理&#xff0c;还是市场营销、财务管理等…...

Java解决幸运数字

Java解决幸运数字 01 题目 哈沙德数是指在某个固定的进位制当中&#xff0c;可以被各位数字之和整 除的正整数。 例如 126 是十进制下的一个哈沙德数&#xff0c;因为 (126)10 mod (1 2 6) 0; 126 也是8进制下的哈沙德 数&#xff0c;因为(126)10 (176)8&#xff0c;(126)10…...

将一个nextjs项目部署到vercel

注&#xff1a;下面均为AI创作&#xff08;本人已验证该流程可行&#xff09; 将一个 Next.js 项目部署到 Vercel 是一个相对直接的过程&#xff0c;因为 Vercel 是由同一个团队开发的&#xff0c;专门为 Next.js 优化。以下是部署一个 Next.js 项目到 Vercel 的基本步骤&…...

RocketMQ学习笔记:分布式事务

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育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款热门的!

红岩思维导图的制作软件&#xff0c;分享4款热门的&#xff01; 在当今信息爆炸的时代&#xff0c;思维导图作为一种有效的知识整理和思维拓展工具&#xff0c;受到了广大用户的青睐。红岩思维导图以其独特的风格和实用性&#xff0c;成为了许多人学习和工作中的得力助手。那么…...

es 集群开机自动启动

前面搭建了 es 集群&#xff0c;但是每次机器重启 都需要手动启动&#xff0c;很麻烦&#xff0c;所以这里介绍一下开机自动启动 首先使用 root 用户 es &#xff1a; 执行以下命令 vim /etc/init.d/elasticsearch 将以下内容 cv 进去 #!/bin/bash #chkconfig: 345 63 …...

使用JMeter从JSON响应的URL参数中提取特定值

在使用Apache JMeter进行API测试时&#xff0c;我们经常需要从JSON格式的响应中提取特定字段的值。这可以通过使用JMeter内置的JSON提取器和正则表达式提取器来完成。以下是一个具体的例子&#xff0c;展示了如何从一个JSON响应中提取rowId的值&#xff0c;同时处理字符串终止符…...

汽车电子行业知识:自动驾驶系统结构和各模块功能

文章目录 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、参数文件的作用 参数文件用于存放实例所需要的初始化参数&#xff0c;因为多数初始化参数都具有默认值&#xff0c;所以参数文件实际存放了非默认的初始化参数。 2、参数文件类型 1&#xff09;服务端参数文件&#xff0c;又称为 spfile 二进制的文件&#xff0c;命名规则…...

鸿蒙(HarmonyOS)Navigation如何实现多场景UI适配?

场景介绍 应用在不同屏幕大小的设备上运行时&#xff0c;往往有不同的UI适配&#xff0c;以聊天应用举例&#xff1a; 在窄屏设备上&#xff0c;联系人和聊天区在多窗口中体现。在宽屏设备上&#xff0c;联系人和聊天区在同一窗口体现。 要做好适配&#xff0c;往往需要开发…...

PTGui图像拼接实验

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

C++|类封装、类的分文件编写练习:设计立方体类、点和圆的关系

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

大数据开发扩展shell--尚硅谷shell笔记

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

考研数学|《1800》《1000》《880》《660》最佳搭配使用方法

直接说结论&#xff1a;基础不好先做1800、强化之前660&#xff0c;强化可选880/1000题。 首先&#xff0c;传统习题册存在的一个问题是题量较大&#xff0c;但难度波动较大。《汤家凤1800》和《张宇1000》题量庞大&#xff0c;但有些题目难度不够平衡&#xff0c;有些过于简单…...

【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&#xff08;请求&#xff09; & Response&#xff08;响应&#xff09; 浏览器会向服务器发送请求数据&#xff0c;服务器也需要返回响应数据给浏览器&#xff0c;因此我们需要设置对应的类来代表请求数据和响应数据&#xff0c;且Servlet中的service方法就需…...

How to convert .py to .ipynb in Ubuntu 22.04

How to convert .py to .ipynb in Ubuntu 22.04 jupyter nbconvertp2j 最近看到大家在用jupyter notebook&#xff0c;我也试了一下&#xff0c;感觉还不错&#xff0c;不过&#xff0c;也遇到了一些问题&#xff0c;比方说&#xff0c;我有堆的.py文件&#xff0c;如果要一个一…...

【prometheus-operator】k8s监控集群外redis

1、部署exporter GitHub - oliver006/redis_exporter: Prometheus Exporter for Redis Metrics. Supports Redis 2.x, 3.x, 4.x, 5.x, 6.x, and 7.x redis_exporter-v1.57.0.linux-386.tar.gz # 解压 tar -zxvf redis_exporter-v1.57.0.linux-386.tar.gz # 启动 nohup ./redi…...

MySQL索引(图文并茂)

目录 一、索引的概念 二、索引的作用 三、创建索引的原则依据 四、索引的分类和创建 1、索引的分类 2、索引的创建 2.1 普通索引 2.1.1 直接创建索引 2.1.2 修改表方式创建 2.1.3 创建表的时候指定索引 2.2 唯一索引 2.2.1 直接创建唯一索引 2.2.2 修改表方式创建 …...

Redis 教程系列之Redis PHP 使用 Redis(十二)

PHP 使用 Redis 安装 开始在 PHP 中使用 Redis 前&#xff0c; 我们需要确保已经安装了 redis 服务及 PHP redis 驱动&#xff0c;且你的机器上能正常使用 PHP。 接下来让我们安装 PHP redis 驱动&#xff1a;下载地址为:https://github.com/phpredis/phpredis/releases。 P…...

JavaScript语法和数据类型

基础 JavaScript 借鉴了 Java 的大部分语法&#xff0c;但同时也受到 Awk、Perl 和 Python 的影响。 JavaScript 是区分大小写的&#xff0c;并使用 Unicode 字符集。举个例子&#xff0c;可以将单词 Frh&#xff08;在德语中意思是“早”&#xff09;用作变量名。 var Frh …...

解决华为云服务器宝塔面板无法访问显示“此站点的连接不安全”问题

已经配置好安全组以及初始化宝塔面板&#xff0c;还是无法访问镜像管理页面&#xff0c;提示此站点的连接不安全。 解决方案 将地址https改为http即可进入。 成功登录后&#xff0c;开启面板SSL即可。...

【Python】 Python脚本实现某平台视频流下载

亲爱的玛丽 我会想念着你 我是多么的讨厌分离 加油站旁的海鸥 机场路上的松柏 挥挥手眼泪就落下来 我多想和那些光阴永远住下来 我不能 我不能 &#x1f3b5; 赵雷《玛丽》 在视频内容的分发上&#xff0c;m3u8格式的视频流越来越常见。它将视频切分成多个…...

LangChain核心模块 Model I/O——Prompts

Prompts ​ 语言模型的提示是用户提供的一组指令或输入&#xff0c;用于指导模型的响应&#xff0c;帮助模型理解上下文并生成相关且连贯的基于语言的输出&#xff0c;例如回答问题、完成句子或参与某项活动。对话。 关键问题 如何在LLMs中使用少量示例(few-shot examples)—…...

关于Docker守护程序未运行导致的错误

01 在启动Docker之前&#xff0c;确保你已经安装了Docker并且Docker服务是运行的。以下是一些步骤可以帮助你解决这个问题&#xff1a; 首先&#xff0c;确保Docker已经正确安装在你的系统上。你可以通过运行以下命令来检查Docker是否已安装&#xff1a; docker --version如果…...

Unity中关于SendMessage方法

在Unity中&#xff0c;SendMessage 方法用于在游戏对象及其所有子对象上调用指定名称的方法。这种方法可以用于在不需要知道接收方的确切类型的情况下&#xff0c;向游戏对象发送消息。 基本语法如下&#xff1a; void SendMessage(string methodName, object value null, S…...