pull/677/head
krahets 1 year ago
parent c14ca74b8b
commit 4bc6b8af7b

@ -1,5 +1,5 @@
/** /**
* File: binary_search_edge.cpp * File: binary_search_insertion.cpp
* Created Time: 2023-08-04 * Created Time: 2023-08-04
* Author: Krahets (krahets@163.com) * Author: Krahets (krahets@163.com)
*/ */

@ -30,8 +30,8 @@
```python title="array.py" ```python title="array.py"
# 初始化数组 # 初始化数组
arr: List[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ]
nums: List[int] = [1, 3, 2, 5, 4] nums: list[int] = [1, 3, 2, 5, 4]
``` ```
=== "Go" === "Go"

@ -1,10 +1,10 @@
# 链表 # 链表
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。 内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。与数组相比,链表更具灵活性,它可以被存储在非连续的内存空间中。
「链表 Linked List」是一种线性数据结构其每个元素都是一个节点对象各个节点之间通过指针连接从当前节点通过指针可以访问到下一个节点。**由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性**,从而可以将各个节点分散存储在内存各处。 「链表 Linked List」是一种线性数据结构其每个元素都是一个节点对象各个节点之间通过指针连接从当前节点通过指针可以访问到下一个节点。**由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性**,从而可以将各个节点分散存储在内存各处。
链表「节点 Node」包含两项数据一是节点「值 Value」二是指向下一节点的「指针 Pointer」或称「引用 Reference」。 链表中的「节点 Node」包含两项数据一是节点「值 Value」二是指向下一节点的「指针 Pointer」或称「引用 Reference」。
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png) ![链表定义与存储方式](linked_list.assets/linkedlist_definition.png)
@ -169,13 +169,7 @@
``` ```
!!! question "尾节点指向什么?" 我们将链表的首个节点称为「头节点」,最后一个节点称为「尾节点」。尾节点指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。
我们将链表的最后一个节点称为「尾节点」,其指向的是“空”,在 Java, C++, Python 中分别记为 $\text{null}$ , $\text{nullptr}$ , $\text{None}$ 。在不引起歧义的前提下,本书都使用 $\text{None}$ 来表示空。
!!! question "如何称呼链表?"
在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个节点对象组成,我们通常将头节点当作链表的代称,例如头节点 `head` 和链表 `head` 实际上是同义的。
**链表初始化方法**。建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。完成后,即可以从链表的头节点(即首个节点)出发,通过指针 `next` 依次访问所有节点。 **链表初始化方法**。建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。完成后,即可以从链表的头节点(即首个节点)出发,通过指针 `next` 依次访问所有节点。
@ -372,9 +366,11 @@
``` ```
在编程语言中,数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` , `nums[1]` 等。而链表是由多个分散的节点对象组成,**我们通常将头节点当作链表的代称**,比如以上代码中的链表可被记做链表 `n0`
## 链表优点 ## 链表优点
**链表中插入与删除节点的操作效率高**。例如,如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。 **链表中插入与删除节点的操作效率高**。如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
![链表插入节点](linked_list.assets/linkedlist_insert_node.png) ![链表插入节点](linked_list.assets/linkedlist_insert_node.png)
@ -528,7 +524,7 @@
## 链表缺点 ## 链表缺点
**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意节点,这是因为系统需要从头节点出发,逐个向后遍历直至找到目标节点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的节点,则需要向后遍历 `index` 轮。 **链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而链表无法直接访问任意节点,因为程序需要从头节点出发,逐个向后遍历,直至找到目标节点。也就是说,如果想要访问链表中第 $i$ 个节点,则需要向后遍历 $i - 1$ 轮。
=== "Java" === "Java"
@ -602,7 +598,7 @@
[class]{}-[func]{access} [class]{}-[func]{access}
``` ```
**链表的内存占用较大**。链表以节点为单位,每个节点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。 **链表的内存占用较大**。链表以节点为单位,每个节点除了包含值,还需额外保存下一节点的引用(指针)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
## 链表常用操作 ## 链表常用操作

@ -35,9 +35,9 @@
```python title="list.py" ```python title="list.py"
# 初始化列表 # 初始化列表
# 无初始值 # 无初始值
list1: List[int] = [] list1: list[int] = []
# 有初始值 # 有初始值
list: List[int] = [1, 3, 2, 5, 4] list: list[int] = [1, 3, 2, 5, 4]
``` ```
=== "Go" === "Go"
@ -646,7 +646,7 @@
```python title="list.py" ```python title="list.py"
# 拼接两个列表 # 拼接两个列表
list1: List[int] = [6, 8, 7, 10, 9] list1: list[int] = [6, 8, 7, 10, 9]
list += list1 # 将列表 list1 拼接到 list 之后 list += list1 # 将列表 list1 拼接到 list 之后
``` ```

@ -26,17 +26,20 @@
!!! question "尾递归的空间复杂度是 $O(1)$ 吗?" !!! question "尾递归的空间复杂度是 $O(1)$ 吗?"
理论上,尾递归函数的空间复杂度可以被优化至 $O(1)$ 。不过绝大多数编程语言(例如 Java, Python, C++, Go, C# 等) 理论上,尾递归函数的空间复杂度可以被优化至 $O(1)$ 。不过绝大多数编程语言(例如 Java, Python, C++, Go, C# 等)都不支持自动优化尾递归,因此通常认为空间复杂度是 $O(n)$ 。
都不支持自动优化尾递归,因此一般来说空间复杂度是 $O(n)$ 。
!!! question "函数和方法这两个术语的区别是什么?" !!! question "函数和方法这两个术语的区别是什么?"
函数function可以独立被执行所有参数都以显式传递。 函数function可以独立被执行所有参数都以显式传递。方法method与一个对象关联方法被隐式传递给调用它的对象方法能够对类的实例中包含的数据进行操作。
方法method与一个对象关联方法被隐式传递给调用它的对象方法能够对类的实例中包含的数据进行操作。
因此C 和 Go 只有函数Java 和 C# 只有方法,在 C++, Python 中取决于它是否属于一个类。 以几个常见的编程语言为例:
- C 语言是过程式编程语言没有面向对象的概念所以只有函数。但我们可以通过创建结构struct来模拟面向对象编程与结构体相关联的函数就相当于其他语言中的方法。
- Java, C# 是面向对象的编程语言,代码块(方法)通常都是作为某个类的一部分。静态方法的行为类似于函数,因为它被绑定在类上,不能访问特定的实例变量。
- C++, Python 既支持过程式编程(函数)也支持面向对象编程(方法)。
!!! question "图片“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?" !!! question "图片“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?"
不是,该图片展示的是空间复杂度(即增长趋势),而不是占用空间的绝对大小。每个曲线都包含一个常数项,用来把所有曲线的取值范围压缩到一个视觉舒适的范围内。 不是,该图片展示的是空间复杂度(即增长趋势),而不是占用空间的绝对大小。每个曲线都包含一个常数项,用来把所有曲线的取值范围压缩到一个视觉舒适的范围内。
实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法;但相对地 $n = 8^5$ 就很好选了,这是复杂度占主导的情况。 实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法;但相对地 $n = 8^5$ 就很好选了,这是复杂度占主导的情况。

@ -151,7 +151,7 @@
is_empty: bool = not max_heap is_empty: bool = not max_heap
# 输入列表并建堆 # 输入列表并建堆
min_heap: List[int] = [1, 3, 2, 5, 4] min_heap: list[int] = [1, 3, 2, 5, 4]
heapq.heapify(min_heap) heapq.heapify(min_heap)
``` ```

@ -6,7 +6,7 @@
给定一个长度为 $n$ 的有序数组 `nums` ,数组可能包含重复元素。请返回数组中最左一个元素 `target` 的索引。若数组中不包含该元素,则返回 $-1$ 。 给定一个长度为 $n$ 的有序数组 `nums` ,数组可能包含重复元素。请返回数组中最左一个元素 `target` 的索引。若数组中不包含该元素,则返回 $-1$ 。
回忆二分查找插入点的方法,搜索完成后$i$ 指向最左一个 `target` **因此查找插入点本质上是在查找最左一个 `target` 的索引**。 回忆二分查找插入点的方法,搜索完成后 $i$ 指向最左一个 `target` **因此查找插入点本质上是在查找最左一个 `target` 的索引**。
考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 `target` ,此时有两种可能: 考虑通过查找插入点的函数实现查找左边界。请注意,数组中可能不包含 `target` ,此时有两种可能:

@ -81,7 +81,7 @@
```python title="stack.py" ```python title="stack.py"
# 初始化栈 # 初始化栈
# Python 没有内置的栈类,可以把 List 当作栈来使用 # Python 没有内置的栈类,可以把 List 当作栈来使用
stack: List[int] = [] stack: list[int] = []
# 元素入栈 # 元素入栈
stack.append(1) stack.append(1)

Loading…
Cancel
Save