From 9ed16db68e04f4094a3f954b66f0019fe4be4a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E6=8B=89?= Date: Mon, 7 Aug 2023 18:22:04 +0800 Subject: [PATCH] Complement to Rust code in the Chapter array and linked list / Time Complexity. (#657) * Complement to Rust code in the Chapter array and linked list * Complement to Rust code in the Time Complexity * Remove this Rust struct from 380 to 383. * Address the comments from @night-cruise * Add more comments in list and time complexity * Add more comments in linked list --- docs/chapter_array_and_linkedlist/array.md | 4 +- .../linked_list.md | 41 ++++++++++++++- docs/chapter_array_and_linkedlist/list.md | 42 +++++++++++++-- .../time_complexity.md | 52 ++++++++++++++++++- 4 files changed, 131 insertions(+), 8 deletions(-) diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 9b41e9255..fba6faa35 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -103,7 +103,9 @@ === "Rust" ```rust title="array.rs" - + /* 初始化数组 */ + let arr: Vec = vec![0; 5]; // [0, 0, 0, 0, 0] + let nums: Vec = vec![1, 3, 2, 5, 4]; ``` ## 数组优点 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 6d50f3efa..2bb2ce5c6 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -166,7 +166,14 @@ === "Rust" ```rust title="" - + use std::rc::Rc; + use std::cell::RefCell; + /* 链表节点类 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 节点值 + next: Option>>, // 指向下一节点的指针(引用) + } ``` 我们将链表的首个节点称为「头节点」,最后一个节点称为「尾节点」。尾节点指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。 @@ -363,7 +370,19 @@ === "Rust" ```rust title="linked_list.rs" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 + let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); + let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); + let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); + let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); + let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); + // 构建引用指向 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); ``` 在编程语言中,数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` , `nums[1]` 等。而链表是由多个分散的节点对象组成,**我们通常将头节点当作链表的代称**,比如以上代码中的链表可被记做链表 `n0` 。 @@ -858,7 +877,27 @@ === "Rust" ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* 双向链表节点类型 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 节点值 + next: Option>>, // 指向后继节点的指针(引用) + prev: Option>>, // 指向前驱节点的指针(引用) + } + + /* 构造函数 */ + impl ListNode { + fn new(val: i32) -> Self { + ListNode { + val, + next: None, + prev: None, + } + } + } ``` ![常见链表种类](linked_list.assets/linkedlist_common_types.png) diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 028097c17..2377af295 100755 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -119,7 +119,11 @@ === "Rust" ```rust title="list.rs" - + /* 初始化列表 */ + // 无初始值 + let list1: Vec = Vec::new(); + // 有初始值 + let list2: Vec = vec![1, 3, 2, 5, 4]; ``` **访问与更新元素**。由于列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问和更新元素,效率很高。 @@ -233,7 +237,10 @@ === "Rust" ```rust title="list.rs" - + /* 访问元素 */ + let num: i32 = list[1]; // 访问索引 1 处的元素 + /* 更新元素 */ + list[1] = 0; // 将索引 1 处的元素更新为 0 ``` **在列表中添加、插入、删除元素**。相较于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但插入和删除元素的效率仍与数组相同,时间复杂度为 $O(N)$ 。 @@ -447,7 +454,21 @@ === "Rust" ```rust title="list.rs" + /* 清空列表 */ + list.clear(); + + /* 尾部添加元素 */ + list.push(1); + list.push(3); + list.push(2); + list.push(5); + list.push(4); + + /* 中间插入元素 */ + list.insert(3, 6); // 在索引 3 处插入数字 6 + /* 删除元素 */ + list.remove(3); // 删除索引 3 处的元素 ``` **遍历列表**。与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。 @@ -620,7 +641,17 @@ === "Rust" ```rust title="list.rs" + /* 通过索引遍历列表 */ + let mut count = 0; + for (index, value) in list.iter().enumerate() { + count += 1; + } + /* 直接遍历列表元素 */ + let mut count = 0; + for value in list.iter() { + count += 1; + } ``` **拼接两个列表**。给定一个新列表 `list1` ,我们可以将该列表拼接到原列表的尾部。 @@ -717,7 +748,9 @@ === "Rust" ```rust title="list.rs" - + /* 拼接两个列表 */ + let list1: Vec = vec![6, 8, 7, 10, 9]; + list.extend(list1); ``` **排序列表**。排序也是常用的方法之一。完成列表排序后,我们便可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法。 @@ -801,7 +834,8 @@ === "Rust" ```rust title="list.rs" - + /* 排序列表 */ + list.sort(); // 排序后,列表元素从小到大排列 ``` ## 列表实现 * diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index b04cedda3..4cda8e97c 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -171,7 +171,16 @@ $$ === "Rust" ```rust title="" - + // 在某运行平台下 + fn algorithm(n: i32) { + let mut a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 循环 n 次 + for _ in 0..n { // 1 ns ,每轮都要执行 i++ + println!("{}", 0); // 5 ns + } + } ``` 然而实际上,**统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,因为算法需要在各种不同的平台上运行。其次,我们很难获知每种操作的运行时间,这给预估过程带来了极大的难度。 @@ -403,7 +412,22 @@ $$ === "Rust" ```rust title="" - + // 算法 A 时间复杂度:常数阶 + fn algorithm_A(n: i32) { + println!("{}", 0); + } + // 算法 B 时间复杂度:线性阶 + fn algorithm_B(n: i32) { + for _ in 0..n { + println!("{}", 0); + } + } + // 算法 C 时间复杂度:常数阶 + fn algorithm_C(n: i32) { + for _ in 0..1000000 { + println!("{}", 0); + } + } ``` ![算法 A, B, C 的时间增长趋势](time_complexity.assets/time_complexity_simple_example.png) @@ -571,7 +595,16 @@ $$ === "Rust" ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 循环 n 次 + for _ in 0..n { // +1(每轮都执行 i ++) + println!("{}", 0); // +1 + } + } ``` $T(n)$ 是一次函数,说明时间增长趋势是线性的,因此可以得出时间复杂度是线性阶。 @@ -816,7 +849,22 @@ $$ === "Rust" ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + + // +n(技巧 2) + for i in 0..(5 * n + 1) { + println!("{}", 0); + } + // +n*n(技巧 3) + for i in 0..(2 * n) { + for j in 0..(n + 1) { + println!("{}", 0); + } + } + } ``` ### 第二步:判断渐近上界