002 通过链表学习Rust笔记之链表布局

介绍

视频地址:https://www.bilibili.com/video/av78062009/
相关源码:https://github.com/anonymousGiga/Rust-link-list

Rust中最常见的链表

用函数式的语法定义一个链表如下:

List a  = Empty | Elem a (List a)

一个链表要么是空的类型,要么是一个值后面跟着一个链表,这种被称为递归定义类型,表示为和类型。Rust中的enum就是类型系统的和类型。所以最常见的Rust的链表的定义如下:

#[derive(Debug)]
enum List<T> {
    Cons(T, Box<List<T>>),
    Nil,
}

fn main() {
    let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
    println!("{:?}", list);
}

说明,上面的方式是通过枚举实现,实际上是:

pub enum List {
    Empty,
    Elem(i32, Box<List>),
}

存在的问题

但是,上面这种是一个非常不好的链表定义。
考虑一个包含两个元素的链表,如下:

//第一种布局,也是我们上面的布局
[] = stack //表示分配在栈上
() = heap  //表示分配在堆上
[Elem A, ptr] -> (Elem B, ptr) -> (Empty, junk)

这个方式存在两个问题:

  • 元素A是分配在栈上而不是分配在堆上的;
  • 最后的空元素需要分配空间。

1、占用多余的空间

我们再考虑下面的布局:

//第二种布局
[ptr] -> (Elem A, ptr) -> (Elem B, null)

后面这种布局的最后一个空没有分配一个节点的空间。

2、便于拆分

在一种布局中,第一个节点没有在堆上分配,这也是不太好的,可能在push、pop的时候没有太大影响,但是在拆分链表的时候影响就比较大了。

考虑以下例子的拆分:

第一种布局:

[Elem A, ptr] -> (Elem B, ptr) -> (Elem C, ptr) -> (Empty *junk*)

split off C:

[Elem A, ptr] -> (Elem B, ptr) -> (Empty *junk*)

[Elem C, ptr] -> (Empty *junk*)

第二种布局:

[ptr] -> (Elem A, ptr) -> (Elem B, ptr) -> (Elem C, *null*)

split off C:

[ptr] -> (Elem A, ptr) -> (Elem B. *null*)

[ptr] ->(Elem C, *null*)

布局2的拆分只需复制B在栈中存放的指针,然后null将旧的的值替换掉就可以了。布局1则必须将C从堆内存拷贝到栈内存。
链表的合并则是链表拆分的相反的过程。

**链表的一个比较好的特性就是,我们可以在节点本身进行构造元素,然后在不移动它(节点本身)的情况下,自由的在链表中移动它。**链表的特性如下面的示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aeyjQ9Gt-1630653384240)(F9EC93015194433981E8BB6FF5DF16BF)]
在上图中,我们删除中间的节点,我们对中间节点本身的数据并没有影响,只是修改了第一个节点的next指针指向的地址,即未移动节点本身而实现在节点在链表中的移动。

很明显,上面的第一种布局方式破坏了链表的此特性

更复杂的枚举

那我们是否可以定义成如下方式:

pub enum List {
    Empty,
    ElemThenEmpty(i32),
    ElemThenNotEmpty(i32, Box<List>),
}

通过此定义,我们可以节省最后一个空元素的空间。但是,这会更复杂,原因就是enum的本身的布局为如下:

//考虑如下枚举类型
enum Foo {
    A(u32),
    B(u64),
    C(u8),
}
//其布局如下
struct FooRepr {
    data: u64, // 根据tag的不同,这一项可以为u64,u32,或者u8
    tag: u8, // 0 = A, 1 = B, 2 = C
}

即使是使用上述的枚举类型表示空,因为tag的原因,不能使用空指针优化(参考Rust死灵书:如果一个枚举类型只包含一个单值变量(比如 None)和一个(级联的)非 null 指针变量(比如 &T),那么 tag 其实是不需要的)。

c风格的链表

通过上面的分析,我们会发现,我们其实需要的是一个类似于在c语言中实现的链表。
我们把List定义成如下:

#[derive(Debug)]
pub struct Node {
	elem: i32,
	next: List,
}

#[derive(Debug)]
pub enum List {
	Empty,
	More(Box<Node>),
}

fn main() {
	let node1 = Node { elem: 1, next: List::Empty};
	let list = Box::new(node1);
	println!("{:?}", list);
}

我们必须要将Node设置为public才能正确运行,但是在实际情况下我们更倾向于将Node设置private。因此我们进一步演进如下:

#[derive(Debug)]
pub struct List{
    head: Link,
}

#[derive(Debug)]
enum Link {
    Empty,
    More(Box<Node>),
}

#[derive(Debug)]
struct Node {
    elem: i32,
    next: Link,
}

fn main() {
	let node1 = Node { elem: 1, next: Link::Empty};
	let list = List {head: Link::More(Box::new(node1))};
	println!("{:?}", list);
}

至此,我们基本上得到了一个我们相对满意的链表的布局!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode 中链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode 中链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值