diff --git a/build/.gitignore b/build/.gitignore deleted file mode 100644 index fd1014369..000000000 --- a/build/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -assets -*.assets - -*.png -*.jpg -*.gif diff --git a/build/chapter_array_and_linkedlist/array.md b/build/chapter_array_and_linkedlist/array.md deleted file mode 100755 index c4f0d920d..000000000 --- a/build/chapter_array_and_linkedlist/array.md +++ /dev/null @@ -1,910 +0,0 @@ ---- -comments: true ---- - -# 4.1. 数组 - -「数组 Array」是一种将 **相同类型元素** 存储在 **连续内存空间** 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。 - -![array_definition](array.assets/array_definition.png) - -

Fig. 数组定义与存储方式

- -!!! note - - 观察上图,我们发现 **数组首元素的索引为 $0$** 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 $1$ 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。 - -**数组有多种初始化写法**。根据实际需要,选代码最短的那一种就好。 - -=== "Java" - - ```java title="array.java" - /* 初始化数组 */ - int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } - int[] nums = { 1, 3, 2, 5, 4 }; - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 初始化数组 */ - int* arr = new int[5]; - int* nums = new int[5] { 1, 3, 2, 5, 4 }; - ``` - -=== "Python" - - ```python title="array.py" - """ 初始化数组 """ - arr = [0] * 5 # [ 0, 0, 0, 0, 0 ] - nums = [1, 3, 2, 5, 4] - ``` - -=== "Go" - - ```go title="array.go" - /* 初始化数组 */ - var arr [5]int - // 在 Go 中,指定长度时([5]int)为数组,不指定长度时([]int)为切片 - // 由于 Go 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 - // 为了方便实现扩容 extend() 方法,以下将切片(Slice)看作数组(Array) - nums := []int{1, 3, 2, 5, 4} - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 初始化数组 */ - var arr = new Array(5).fill(0); - var nums = [1, 3, 2, 5, 4]; - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 初始化数组 */ - let arr: number[] = new Array(5).fill(0); - let nums: number[] = [1, 3, 2, 5, 4]; - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 初始化数组 */ - int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } - int[] nums = { 1, 3, 2, 5, 4 }; - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 初始化数组 */ - let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] - let nums = [1, 3, 2, 5, 4] - ``` - -=== "Zig" - - ```zig title="array.zig" - // 初始化数组 - var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 } - var nums = [_]i32{ 1, 3, 2, 5, 4 }; - ``` - -## 4.1.1. 数组优点 - -**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 - -![array_memory_location_calculation](array.assets/array_memory_location_calculation.png) - -

Fig. 数组元素的内存地址计算

- -```java title="" -// 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引 -elementAddr = firtstElementAddr + elementLength * elementIndex -``` - -**为什么数组元素索引从 0 开始编号?** 根据地址计算公式,**索引本质上表示的是内存地址偏移量**,首个元素的地址偏移量是 $0$ ,那么索引是 $0$ 也就很自然了。 - -访问元素的高效性带来了许多便利。例如,我们可以在 $O(1)$ 时间内随机获取一个数组中的元素。 - -=== "Java" - - ```java title="array.java" - /* 随机返回一个数组元素 */ - int randomAccess(int[] nums) { - // 在区间 [0, nums.length) 中随机抽取一个数字 - int randomIndex = ThreadLocalRandom.current(). - nextInt(0, nums.length); - // 获取并返回随机元素 - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 随机返回一个数组元素 */ - int randomAccess(int* nums, int size) { - // 在区间 [0, size) 中随机抽取一个数字 - int randomIndex = rand() % size; - // 获取并返回随机元素 - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "Python" - - ```python title="array.py" - """ 随机访问元素 """ - def random_access(nums): - # 在区间 [0, len(nums)-1] 中随机抽取一个数字 - random_index = random.randint(0, len(nums) - 1) - # 获取并返回随机元素 - random_num = nums[random_index] - return random_num - ``` - -=== "Go" - - ```go title="array.go" - /* 随机返回一个数组元素 */ - func randomAccess(nums []int) (randomNum int) { - // 在区间 [0, nums.length) 中随机抽取一个数字 - randomIndex := rand.Intn(len(nums)) - // 获取并返回随机元素 - randomNum = nums[randomIndex] - return - } - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 随机返回一个数组元素 */ - function randomAccess(nums) { - // 在区间 [0, nums.length) 中随机抽取一个数字 - const random_index = Math.floor(Math.random() * nums.length); - // 获取并返回随机元素 - const random_num = nums[random_index]; - return random_num; - } - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 随机返回一个数组元素 */ - function randomAccess(nums: number[]): number { - // 在区间 [0, nums.length) 中随机抽取一个数字 - const random_index = Math.floor(Math.random() * nums.length); - // 获取并返回随机元素 - const random_num = nums[random_index]; - return random_num; - } - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 随机返回一个数组元素 */ - int RandomAccess(int[] nums) - { - Random random=new(); - // 在区间 [0, nums.Length) 中随机抽取一个数字 - int randomIndex = random.Next(nums.Length); - // 获取并返回随机元素 - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 随机返回一个数组元素 */ - func randomAccess(nums: [Int]) -> Int { - // 在区间 [0, nums.count) 中随机抽取一个数字 - let randomIndex = nums.indices.randomElement()! - // 获取并返回随机元素 - let randomNum = nums[randomIndex] - return randomNum - } - ``` - -=== "Zig" - - ```zig title="array.zig" - // 随机返回一个数组元素 - pub fn randomAccess(nums: []i32) i32 { - // 在区间 [0, nums.len) 中随机抽取一个整数 - var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len); - // 获取并返回随机元素 - var randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -## 4.1.2. 数组缺点 - -**数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 - -=== "Java" - - ```java title="array.java" - /* 扩展数组长度 */ - int[] extend(int[] nums, int enlarge) { - // 初始化一个扩展长度后的数组 - int[] res = new int[nums.length + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < nums.length; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 扩展数组长度 */ - int* extend(int* nums, int size, int enlarge) { - // 初始化一个扩展长度后的数组 - int* res = new int[size + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size; i++) { - res[i] = nums[i]; - } - // 释放内存 - delete[] nums; - // 返回扩展后的新数组 - return res; - } - ``` - -=== "Python" - - ```python title="array.py" - """ 扩展数组长度 """ - # 请注意,Python 的 list 是动态数组,可以直接扩展 - # 为了方便学习,本函数将 list 看作是长度不可变的数组 - def extend(nums, enlarge): - # 初始化一个扩展长度后的数组 - res = [0] * (len(nums) + enlarge) - # 将原数组中的所有元素复制到新数组 - for i in range(len(nums)): - res[i] = nums[i] - # 返回扩展后的新数组 - return res - ``` - -=== "Go" - - ```go title="array.go" - /* 扩展数组长度 */ - func extend(nums []int, enlarge int) []int { - // 初始化一个扩展长度后的数组 - res := make([]int, len(nums)+enlarge) - // 将原数组中的所有元素复制到新数组 - for i, num := range nums { - res[i] = num - } - // 返回扩展后的新数组 - return res - } - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 扩展数组长度 */ - function extend(nums, enlarge) { - // 初始化一个扩展长度后的数组 - const res = new Array(nums.length + enlarge).fill(0); - // 将原数组中的所有元素复制到新数组 - for (let i = 0; i < nums.length; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 扩展数组长度 */ - function extend(nums: number[], enlarge: number): number[] { - // 初始化一个扩展长度后的数组 - const res = new Array(nums.length + enlarge).fill(0); - // 将原数组中的所有元素复制到新数组 - for (let i = 0; i < nums.length; i++) { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 扩展数组长度 */ - int[] Extend(int[] nums, int enlarge) - { - // 初始化一个扩展长度后的数组 - int[] res = new int[nums.Length + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < nums.Length; i++) - { - res[i] = nums[i]; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 扩展数组长度 */ - func extend(nums: [Int], enlarge: Int) -> [Int] { - // 初始化一个扩展长度后的数组 - var res = Array(repeating: 0, count: nums.count + enlarge) - // 将原数组中的所有元素复制到新数组 - for i in nums.indices { - res[i] = nums[i] - } - // 返回扩展后的新数组 - return res - } - ``` - -=== "Zig" - - ```zig title="array.zig" - // 扩展数组长度 - pub fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 { - // 初始化一个扩展长度后的数组 - var res = try mem_allocator.alloc(i32, nums.len + enlarge); - std.mem.set(i32, res, 0); - // 将原数组中的所有元素复制到新数组 - std.mem.copy(i32, res, nums); - // 返回扩展后的新数组 - return res; - } - ``` - -**数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: - -- **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 -- **丢失元素**:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 -- **内存浪费**:我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。 - -![array_insert_remove_element](array.assets/array_insert_remove_element.png) - -

Fig. 在数组中插入与删除元素

- -=== "Java" - - ```java title="array.java" - /* 在数组的索引 index 处插入元素 num */ - void insert(int[] nums, int num, int index) { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = nums.length - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - void remove(int[] nums, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 在数组的索引 index 处插入元素 num */ - void insert(int* nums, int size, int num, int index) { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = size - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - void remove(int* nums, int size, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < size - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "Python" - - ```python title="array.py" - """ 在数组的索引 index 处插入元素 num """ - def insert(nums, num, index): - # 把索引 index 以及之后的所有元素向后移动一位 - for i in range(len(nums) - 1, index, -1): - nums[i] = nums[i - 1] - # 将 num 赋给 index 处元素 - nums[index] = num - - """ 删除索引 index 处元素 """ - def remove(nums, index): - # 把索引 index 之后的所有元素向前移动一位 - for i in range(index, len(nums) - 1): - nums[i] = nums[i + 1] - ``` - -=== "Go" - - ```go title="array.go" - /* 在数组的索引 index 处插入元素 num */ - func insert(nums []int, num int, index int) { - // 把索引 index 以及之后的所有元素向后移动一位 - for i := len(nums) - 1; i > index; i-- { - nums[i] = nums[i-1] - } - // 将 num 赋给 index 处元素 - nums[index] = num - } - - /* 删除索引 index 处元素 */ - func remove(nums []int, index int) { - // 把索引 index 之后的所有元素向前移动一位 - for i := index; i < len(nums)-1; i++ { - nums[i] = nums[i+1] - } - } - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 在数组的索引 index 处插入元素 num */ - function insert(nums, num, index) { - // 把索引 index 以及之后的所有元素向后移动一位 - for (let i = nums.length - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - function remove(nums, index) { - // 把索引 index 之后的所有元素向前移动一位 - for (let i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 在数组的索引 index 处插入元素 num */ - function insert(nums: number[], num: number, index: number): void { - // 把索引 index 以及之后的所有元素向后移动一位 - for (let i = nums.length - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - function remove(nums: number[], index: number): void { - // 把索引 index 之后的所有元素向前移动一位 - for (let i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 在数组的索引 index 处插入元素 num */ - void Insert(int[] nums, int num, int index) - { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = nums.Length - 1; i > index; i--) - { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - /* 删除索引 index 处元素 */ - void Remove(int[] nums, int index) - { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < nums.Length - 1; i++) - { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 在数组的索引 index 处插入元素 num */ - func insert(nums: inout [Int], num: Int, index: Int) { - // 把索引 index 以及之后的所有元素向后移动一位 - for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { - nums[i] = nums[i - 1] - } - // 将 num 赋给 index 处元素 - nums[index] = num - } - - /* 删除索引 index 处元素 */ - func remove(nums: inout [Int], index: Int) { - let count = nums.count - // 把索引 index 之后的所有元素向前移动一位 - for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { - nums[i] = nums[i + 1] - } - } - ``` - -=== "Zig" - - ```zig title="array.zig" - // 在数组的索引 index 处插入元素 num - pub fn insert(nums: []i32, num: i32, index: usize) void { - // 把索引 index 以及之后的所有元素向后移动一位 - var i = nums.len - 1; - while (i > index) : (i -= 1) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - // 删除索引 index 处元素 - pub fn remove(nums: []i32, index: usize) void { - // 把索引 index 之后的所有元素向前移动一位 - var i = index; - while (i < nums.len - 1) : (i += 1) { - nums[i] = nums[i + 1]; - } - } - ``` - -## 4.1.3. 数组常用操作 - -**数组遍历**。以下介绍两种常用的遍历方法。 - -=== "Java" - - ```java title="array.java" - /* 遍历数组 */ - void traverse(int[] nums) { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < nums.length; i++) { - count++; - } - // 直接遍历数组 - for (int num : nums) { - count++; - } - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 遍历数组 */ - void traverse(int* nums, int size) { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < size; i++) { - count++; - } - } - ``` - -=== "Python" - - ```python title="array.py" - """ 遍历数组 """ - def traverse(nums): - count = 0 - # 通过索引遍历数组 - for i in range(len(nums)): - count += 1 - # 直接遍历数组 - for num in nums: - count += 1 - ``` - -=== "Go" - - ```go title="array.go" - /* 遍历数组 */ - func traverse(nums []int) { - count := 0 - // 通过索引遍历数组 - for i := 0; i < len(nums); i++ { - count++ - } - // 直接遍历数组 - for range nums { - count++ - } - } - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 遍历数组 */ - function traverse(nums) { - let count = 0; - // 通过索引遍历数组 - for (let i = 0; i < nums.length; i++) { - count++; - } - // 直接遍历数组 - for (let num of nums) { - count += 1; - } - } - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 遍历数组 */ - function traverse(nums: number[]): void { - let count = 0; - // 通过索引遍历数组 - for (let i = 0; i < nums.length; i++) { - count++; - } - // 直接遍历数组 - for(let num of nums){ - count += 1; - } - } - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 遍历数组 */ - void Traverse(int[] nums) - { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < nums.Length; i++) - { - count++; - } - // 直接遍历数组 - foreach (int num in nums) - { - count++; - } - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 遍历数组 */ - func traverse(nums: [Int]) { - var count = 0 - // 通过索引遍历数组 - for _ in nums.indices { - count += 1 - } - // 直接遍历数组 - for _ in nums { - count += 1 - } - } - ``` - -=== "Zig" - - ```zig title="array.zig" - // 遍历数组 - pub fn traverse(nums: []i32) void { - var count: i32 = 0; - // 通过索引遍历数组 - var i: i32 = 0; - while (i < nums.len) : (i += 1) { - count += 1; - } - count = 0; - // 直接遍历数组 - for (nums) |_| { - count += 1; - } - } - ``` - -**数组查找**。通过遍历数组,查找数组内的指定元素,并输出对应索引。 - -=== "Java" - - ```java title="array.java" - /* 在数组中查找指定元素 */ - int find(int[] nums, int target) { - for (int i = 0; i < nums.length; i++) { - if (nums[i] == target) - return i; - } - return -1; - } - ``` - -=== "C++" - - ```cpp title="array.cpp" - /* 在数组中查找指定元素 */ - int find(int* nums, int size, int target) { - for (int i = 0; i < size; i++) { - if (nums[i] == target) - return i; - } - return -1; - } - ``` - -=== "Python" - - ```python title="array.py" - """ 在数组中查找指定元素 """ - def find(nums, target): - for i in range(len(nums)): - if nums[i] == target: - return i - return -1 - ``` - -=== "Go" - - ```go title="array.go" - /* 在数组中查找指定元素 */ - func find(nums []int, target int) (index int) { - index = -1 - for i := 0; i < len(nums); i++ { - if nums[i] == target { - index = i - break - } - } - return - } - ``` - -=== "JavaScript" - - ```javascript title="array.js" - /* 在数组中查找指定元素 */ - function find(nums, target) { - for (let i = 0; i < nums.length; i++) { - if (nums[i] == target) return i; - } - return -1; - } - ``` - -=== "TypeScript" - - ```typescript title="array.ts" - /* 在数组中查找指定元素 */ - function find(nums: number[], target: number): number { - for (let i = 0; i < nums.length; i++) { - if (nums[i] === target) { - return i; - } - } - return -1; - } - ``` - -=== "C" - - ```c title="array.c" - - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 在数组中查找指定元素 */ - int Find(int[] nums, int target) - { - for (int i = 0; i < nums.Length; i++) - { - if (nums[i] == target) - return i; - } - return -1; - } - ``` - -=== "Swift" - - ```swift title="array.swift" - /* 在数组中查找指定元素 */ - func find(nums: [Int], target: Int) -> Int { - for i in nums.indices { - if nums[i] == target { - return i - } - } - return -1 - } - ``` - -=== "Zig" - - ```zig title="array.zig" - // 在数组中查找指定元素 - pub fn find(nums: []i32, target: i32) i32 { - for (nums) |num, i| { - if (num == target) return @intCast(i32, i); - } - return -1; - } - ``` - -## 4.1.4. 数组典型应用 - -**随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 - -**二分查找**。例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。 - -**深度学习**。神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 diff --git a/build/chapter_array_and_linkedlist/linked_list.md b/build/chapter_array_and_linkedlist/linked_list.md deleted file mode 100755 index 271b0dcc7..000000000 --- a/build/chapter_array_and_linkedlist/linked_list.md +++ /dev/null @@ -1,979 +0,0 @@ ---- -comments: true ---- - -# 4.2. 链表 - -!!! note "引言" - - 内存空间是所有程序的公共资源,排除已占用的内存,空闲内存往往是散落在内存各处的。我们知道,存储数组需要内存空间连续,当我们需要申请一个很大的数组时,系统不一定存在这么大的连续内存空间。而链表则更加灵活,不需要内存是连续的,只要剩余内存空间大小够用即可。 - -「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(一般称为结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统不必保证内存地址的连续性。 - -链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。 - -![linkedlist_definition](linked_list.assets/linkedlist_definition.png) - -

Fig. 链表定义与存储方式

- -=== "Java" - - ```java title="" - /* 链表结点类 */ - class ListNode { - int val; // 结点值 - ListNode next; // 指向下一结点的指针(引用) - ListNode(int x) { val = x; } // 构造函数 - } - ``` - -=== "C++" - - ```cpp title="" - /* 链表结点结构体 */ - struct ListNode { - int val; // 结点值 - ListNode *next; // 指向下一结点的指针(引用) - ListNode(int x) : val(x), next(nullptr) {} // 构造函数 - }; - ``` - -=== "Python" - - ```python title="" - """ 链表结点类 """ - class ListNode: - def __init__(self, x): - self.val = x # 结点值 - self.next = None # 指向下一结点的指针(引用) - ``` - -=== "Go" - - ```go title="" - /* 链表结点结构体 */ - type ListNode struct { - Val int // 结点值 - Next *ListNode // 指向下一结点的指针(引用) - } - - // NewListNode 构造函数,创建一个新的链表 - func NewListNode(val int) *ListNode { - return &ListNode{ - Val: val, - Next: nil, - } - } - ``` - -=== "JavaScript" - - ```js title="" - /* 链表结点结构体 */ - class ListNode { - val; - next; - constructor(val, next) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.next = (next === undefined ? null : next); // 指向下一结点的引用 - } - } - ``` - -=== "TypeScript" - - ```typescript title="" - /* 链表结点结构体 */ - class ListNode { - val: number; - next: ListNode | null; - constructor(val?: number, next?: ListNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向下一结点的引用 - } - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - /* 链表结点类 */ - class ListNode - { - int val; // 结点值 - ListNode next; // 指向下一结点的引用 - ListNode(int x) => val = x; //构造函数 - } - ``` - -=== "Swift" - - ```swift title="" - /* 链表结点类 */ - class ListNode { - var val: Int // 结点值 - var next: ListNode? // 指向下一结点的指针(引用) - - init(x: Int) { // 构造函数 - val = x - } - } - ``` - -=== "Zig" - - ```zig title="" - // 链表结点类 - pub fn ListNode(comptime T: type) type { - return struct { - const Self = @This(); - - val: T = 0, // 结点值 - next: ?*Self = null, // 指向下一结点的指针(引用) - - // 构造函数 - pub fn init(self: *Self, x: i32) void { - self.val = x; - self.next = null; - } - }; - } - ``` - -**尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 - -**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 - -!!! tip - - 我们通常将头结点当作链表的代称,例如头结点 `head` 和链表 `head` 实际上是同义的。 - -=== "Java" - - ```java title="linked_list.java" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - ListNode n0 = new ListNode(1); - ListNode n1 = new ListNode(3); - ListNode n2 = new ListNode(2); - ListNode n3 = new ListNode(5); - ListNode n4 = new ListNode(4); - // 构建引用指向 - n0.next = n1; - n1.next = n2; - n2.next = n3; - n3.next = n4; - ``` - -=== "C++" - - ```cpp title="linked_list.cpp" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - ListNode* n0 = new ListNode(1); - ListNode* n1 = new ListNode(3); - ListNode* n2 = new ListNode(2); - ListNode* n3 = new ListNode(5); - ListNode* n4 = new ListNode(4); - // 构建引用指向 - n0->next = n1; - n1->next = n2; - n2->next = n3; - n3->next = n4; - ``` - -=== "Python" - - ```python title="linked_list.py" - """ 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """ - # 初始化各个结点 - n0 = ListNode(1) - n1 = ListNode(3) - n2 = ListNode(2) - n3 = ListNode(5) - n4 = ListNode(4) - # 构建引用指向 - n0.next = n1 - n1.next = n2 - n2.next = n3 - n3.next = n4 - ``` - -=== "Go" - - ```go title="linked_list.go" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - n0 := NewListNode(1) - n1 := NewListNode(3) - n2 := NewListNode(2) - n3 := NewListNode(5) - n4 := NewListNode(4) - - // 构建引用指向 - n0.Next = n1 - n1.Next = n2 - n2.Next = n3 - n3.Next = n4 - ``` - -=== "JavaScript" - - ```js title="linked_list.js" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - const n0 = new ListNode(1); - const n1 = new ListNode(3); - const n2 = new ListNode(2); - const n3 = new ListNode(5); - const n4 = new ListNode(4); - // 构建引用指向 - n0.next = n1; - n1.next = n2; - n2.next = n3; - n3.next = n4; - ``` - -=== "TypeScript" - - ```typescript title="linked_list.ts" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - const n0 = new ListNode(1); - const n1 = new ListNode(3); - const n2 = new ListNode(2); - const n3 = new ListNode(5); - const n4 = new ListNode(4); - // 构建引用指向 - n0.next = n1; - n1.next = n2; - n2.next = n3; - n3.next = n4; - ``` - -=== "C" - - ```c title="linked_list.c" - - ``` - -=== "C#" - - ```csharp title="linked_list.cs" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - ListNode n0 = new ListNode(1); - ListNode n1 = new ListNode(3); - ListNode n2 = new ListNode(2); - ListNode n3 = new ListNode(5); - ListNode n4 = new ListNode(4); - // 构建引用指向 - n0.next = n1; - n1.next = n2; - n2.next = n3; - n3.next = n4; - ``` - -=== "Swift" - - ```swift title="linked_list.swift" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 - let n0 = ListNode(x: 1) - let n1 = ListNode(x: 3) - let n2 = ListNode(x: 2) - let n3 = ListNode(x: 5) - let n4 = ListNode(x: 4) - // 构建引用指向 - n0.next = n1 - n1.next = n2 - n2.next = n3 - n3.next = n4 - ``` - -=== "Zig" - - ```zig title="linked_list.zig" - // 初始化链表 - // 初始化各个结点 - var n0 = inc.ListNode(i32){.val = 1}; - var n1 = inc.ListNode(i32){.val = 3}; - var n2 = inc.ListNode(i32){.val = 2}; - var n3 = inc.ListNode(i32){.val = 5}; - var n4 = inc.ListNode(i32){.val = 4}; - // 构建引用指向 - n0.next = &n1; - n1.next = &n2; - n2.next = &n3; - n3.next = &n4; - ``` - -## 4.2.1. 链表优点 - -**在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 - -![linkedlist_insert_remove_node](linked_list.assets/linkedlist_insert_remove_node.png) - -

Fig. 在链表中插入与删除结点

- -=== "Java" - - ```java title="linked_list.java" - /* 在链表的结点 n0 之后插入结点 P */ - void insert(ListNode n0, ListNode P) { - ListNode n1 = n0.next; - n0.next = P; - P.next = n1; - } - - /* 删除链表的结点 n0 之后的首个结点 */ - void remove(ListNode n0) { - if (n0.next == null) - return; - // n0 -> P -> n1 - ListNode P = n0.next; - ListNode n1 = P.next; - n0.next = n1; - } - ``` - -=== "C++" - - ```cpp title="linked_list.cpp" - /* 在链表的结点 n0 之后插入结点 P */ - void insert(ListNode* n0, ListNode* P) { - ListNode* n1 = n0->next; - n0->next = P; - P->next = n1; - } - - /* 删除链表的结点 n0 之后的首个结点 */ - void remove(ListNode* n0) { - if (n0->next == nullptr) - return; - // n0 -> P -> n1 - ListNode* P = n0->next; - ListNode* n1 = P->next; - n0->next = n1; - // 释放内存 - delete P; - } - ``` - -=== "Python" - - ```python title="linked_list.py" - """ 在链表的结点 n0 之后插入结点 P """ - def insert(n0, P): - n1 = n0.next - n0.next = P - P.next = n1 - - """ 删除链表的结点 n0 之后的首个结点 """ - def remove(n0): - if not n0.next: - return - # n0 -> P -> n1 - P = n0.next - n1 = P.next - n0.next = n1 - ``` - -=== "Go" - - ```go title="linked_list.go" - /* 在链表的结点 n0 之后插入结点 P */ - func insert(n0 *ListNode, P *ListNode) { - n1 := n0.Next - n0.Next = P - P.Next = n1 - } - - /* 删除链表的结点 n0 之后的首个结点 */ - func removeNode(n0 *ListNode) { - if n0.Next == nil { - return - } - // n0 -> P -> n1 - P := n0.Next - n1 := P.Next - n0.Next = n1 - } - ``` - -=== "JavaScript" - - ```js title="linked_list.js" - /* 在链表的结点 n0 之后插入结点 P */ - function insert(n0, P) { - let n1 = n0.next; - n0.next = P; - P.next = n1; - } - - /* 删除链表的结点 n0 之后的首个结点 */ - function remove(n0) { - if (!n0.next) - return; - // n0 -> P -> n1 - let P = n0.next; - let n1 = P.next; - n0.next = n1; - } - ``` - -=== "TypeScript" - - ```typescript title="linked_list.ts" - /* 在链表的结点 n0 之后插入结点 P */ - function insert(n0: ListNode, P: ListNode): void { - const n1 = n0.next; - n0.next = P; - P.next = n1; - } - - /* 删除链表的结点 n0 之后的首个结点 */ - function remove(n0: ListNode): void { - if (!n0.next) { - return; - } - // n0 -> P -> n1 - const P = n0.next; - const n1 = P.next; - n0.next = n1; - } - ``` - -=== "C" - - ```c title="linked_list.c" - - ``` - -=== "C#" - - ```csharp title="linked_list.cs" - // 在链表的结点 n0 之后插入结点 P - void Insert(ListNode n0, ListNode P) - { - ListNode n1 = n0.next; - n0.next = P; - P.next = n1; - } - - // 删除链表的结点 n0 之后的首个结点 - void Remove(ListNode n0) - { - if (n0.next == null) - return; - // n0 -> P -> n1 - ListNode P = n0.next; - ListNode n1 = P.next; - n0.next = n1; - } - ``` - -=== "Swift" - - ```swift title="linked_list.swift" - /* 在链表的结点 n0 之后插入结点 P */ - func insert(n0: ListNode, P: ListNode) { - let n1 = n0.next - n0.next = P - P.next = n1 - } - - /* 删除链表的结点 n0 之后的首个结点 */ - func remove(n0: ListNode) { - if n0.next == nil { - return - } - // n0 -> P -> n1 - let P = n0.next - let n1 = P?.next - n0.next = n1 - P?.next = nil - } - ``` - -=== "Zig" - - ```zig title="linked_list.zig" - // 在链表的结点 n0 之后插入结点 P - pub fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { - var n1 = n0.?.next; - n0.?.next = P; - P.?.next = n1; - } - - // 删除链表的结点 n0 之后的首个结点 - pub fn remove(n0: ?*inc.ListNode(i32)) void { - if (n0.?.next == null) return; - // n0 -> P -> n1 - var P = n0.?.next; - var n1 = P.?.next; - n0.?.next = n1; - } - ``` - -## 4.2.2. 链表缺点 - -**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 - -=== "Java" - - ```java title="linked_list.java" - /* 访问链表中索引为 index 的结点 */ - ListNode access(ListNode head, int index) { - for (int i = 0; i < index; i++) { - if (head == null) - return null; - head = head.next; - } - return head; - } - ``` - -=== "C++" - - ```cpp title="linked_list.cpp" - /* 访问链表中索引为 index 的结点 */ - ListNode* access(ListNode* head, int index) { - for (int i = 0; i < index; i++) { - if (head == nullptr) - return nullptr; - head = head->next; - } - return head; - } - ``` - -=== "Python" - - ```python title="linked_list.py" - """ 访问链表中索引为 index 的结点 """ - def access(head, index): - for _ in range(index): - if not head: - return None - head = head.next - return head - ``` - -=== "Go" - - ```go title="linked_list.go" - /* 访问链表中索引为 index 的结点 */ - func access(head *ListNode, index int) *ListNode { - for i := 0; i < index; i++ { - if head == nil { - return nil - } - head = head.Next - } - return head - } - ``` - -=== "JavaScript" - - ```js title="linked_list.js" - /* 访问链表中索引为 index 的结点 */ - function access(head, index) { - for (let i = 0; i < index; i++) { - if (!head) - return null; - head = head.next; - } - return head; - } - ``` - -=== "TypeScript" - - ```typescript title="linked_list.ts" - /* 访问链表中索引为 index 的结点 */ - function access(head: ListNode | null, index: number): ListNode | null { - for (let i = 0; i < index; i++) { - if (!head) { - return null; - } - head = head.next; - } - return head; - } - ``` - -=== "C" - - ```c title="linked_list.c" - - ``` - -=== "C#" - - ```csharp title="linked_list.cs" - // 访问链表中索引为 index 的结点 - ListNode Access(ListNode head, int index) - { - for (int i = 0; i < index; i++) - { - if (head == null) - return null; - head = head.next; - } - return head; - } - ``` - -=== "Swift" - - ```swift title="linked_list.swift" - /* 访问链表中索引为 index 的结点 */ - func access(head: ListNode, index: Int) -> ListNode? { - var head: ListNode? = head - for _ in 0 ..< index { - if head == nil { - return nil - } - head = head?.next - } - return head - } - ``` - -=== "Zig" - - ```zig title="linked_list.zig" - // 访问链表中索引为 index 的结点 - pub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) { - var head = node; - var i: i32 = 0; - while (i < index) : (i += 1) { - head = head.?.next; - if (head == null) return null; - } - return head; - } - ``` - -**链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 - -## 4.2.3. 链表常用操作 - -**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 - -=== "Java" - - ```java title="linked_list.java" - /* 在链表中查找值为 target 的首个结点 */ - int find(ListNode head, int target) { - int index = 0; - while (head != null) { - if (head.val == target) - return index; - head = head.next; - index++; - } - return -1; - } - ``` - -=== "C++" - - ```cpp title="linked_list.cpp" - /* 在链表中查找值为 target 的首个结点 */ - int find(ListNode* head, int target) { - int index = 0; - while (head != nullptr) { - if (head->val == target) - return index; - head = head->next; - index++; - } - return -1; - } - ``` - -=== "Python" - - ```python title="linked_list.py" - """ 在链表中查找值为 target 的首个结点 """ - def find(head, target): - index = 0 - while head: - if head.val == target: - return index - head = head.next - index += 1 - return -1 - ``` - -=== "Go" - - ```go title="linked_list.go" - /* 在链表中查找值为 target 的首个结点 */ - func find(head *ListNode, target int) int { - index := 0 - for head != nil { - if head.Val == target { - return index - } - head = head.Next - index++ - } - return -1 - } - ``` - -=== "JavaScript" - - ```js title="linked_list.js" - /* 在链表中查找值为 target 的首个结点 */ - function find(head, target) { - let index = 0; - while (head !== null) { - if (head.val === target) { - return index; - } - head = head.next; - index += 1; - } - return -1; - } - ``` - -=== "TypeScript" - - ```typescript title="linked_list.ts" - /* 在链表中查找值为 target 的首个结点 */ - function find(head: ListNode | null, target: number): number { - let index = 0; - while (head !== null) { - if (head.val === target) { - return index; - } - head = head.next; - index += 1; - } - return -1; - } - ``` - -=== "C" - - ```c title="linked_list.c" - - ``` - -=== "C#" - - ```csharp title="linked_list.cs" - // 在链表中查找值为 target 的首个结点 - int Find(ListNode head, int target) - { - int index = 0; - while (head != null) - { - if (head.val == target) - return index; - head = head.next; - index++; - } - return -1; - } - ``` - -=== "Swift" - - ```swift title="linked_list.swift" - /* 在链表中查找值为 target 的首个结点 */ - func find(head: ListNode, target: Int) -> Int { - var head: ListNode? = head - var index = 0 - while head != nil { - if head?.val == target { - return index - } - head = head?.next - index += 1 - } - return -1 - } - ``` - -=== "Zig" - - ```zig title="linked_list.zig" - // 在链表中查找值为 target 的首个结点 - pub fn find(node: ?*inc.ListNode(i32), target: i32) i32 { - var head = node; - var index: i32 = 0; - while (head != null) { - if (head.?.val == target) return index; - head = head.?.next; - index += 1; - } - return -1; - } - ``` - -## 4.2.4. 常见链表类型 - -**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 - -**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。 - -**双向链表**。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。 - -=== "Java" - - ```java title="" - /* 双向链表结点类 */ - class ListNode { - int val; // 结点值 - ListNode next; // 指向后继结点的指针(引用) - ListNode prev; // 指向前驱结点的指针(引用) - ListNode(int x) { val = x; } // 构造函数 - } - ``` - -=== "C++" - - ```cpp title="" - /* 链表结点结构体 */ - struct ListNode { - int val; // 结点值 - ListNode *next; // 指向后继结点的指针(引用) - ListNode *prev; // 指向前驱结点的指针(引用) - ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数 - }; - ``` - -=== "Python" - - ```python title="" - """ 双向链表结点类 """ - class ListNode: - def __init__(self, x): - self.val = x # 结点值 - self.next = None # 指向后继结点的指针(引用) - self.prev = None # 指向前驱结点的指针(引用) - ``` - -=== "Go" - - ```go title="" - /* 双向链表结点结构体 */ - type DoublyListNode struct { - Val int // 结点值 - Next *DoublyListNode // 指向后继结点的指针(引用) - Prev *DoublyListNode // 指向前驱结点的指针(引用) - } - - // NewDoublyListNode 初始化 - func NewDoublyListNode(val int) *DoublyListNode { - return &DoublyListNode{ - Val: val, - Next: nil, - Prev: nil, - } - } - ``` - -=== "JavaScript" - - ```js title="" - /* 双向链表结点类 */ - class ListNode { - val; - next; - prev; - constructor(val, next) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向后继结点的指针(引用) - this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用) - } - } - ``` - -=== "TypeScript" - - ```typescript title="" - /* 双向链表结点类 */ - class ListNode { - val: number; - next: ListNode | null; - prev: ListNode | null; - constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向后继结点的指针(引用) - this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用) - } - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - /* 双向链表结点类 */ - class ListNode { - int val; // 结点值 - ListNode next; // 指向后继结点的指针(引用) - ListNode prev; // 指向前驱结点的指针(引用) - ListNode(int x) => val = x; // 构造函数 - } - ``` - -=== "Swift" - - ```swift title="" - /* 双向链表结点类 */ - class ListNode { - var val: Int // 结点值 - var next: ListNode? // 指向后继结点的指针(引用) - var prev: ListNode? // 指向前驱结点的指针(引用) - - init(x: Int) { // 构造函数 - val = x - } - } - ``` - -=== "Zig" - - ```zig title="" - // 双向链表结点类 - pub fn ListNode(comptime T: type) type { - return struct { - const Self = @This(); - - val: T = 0, // 结点值 - next: ?*Self = null, // 指向后继结点的指针(引用) - prev: ?*Self = null, // 指向前驱结点的指针(引用) - - // 构造函数 - pub fn init(self: *Self, x: i32) void { - self.val = x; - self.next = null; - self.prev = null; - } - }; - } - ``` - -![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png) - -

Fig. 常见链表类型

diff --git a/build/chapter_array_and_linkedlist/list.md b/build/chapter_array_and_linkedlist/list.md deleted file mode 100755 index 76c521435..000000000 --- a/build/chapter_array_and_linkedlist/list.md +++ /dev/null @@ -1,1610 +0,0 @@ ---- -comments: true ---- - -# 4.3. 列表 - -**由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 - -为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。 - -## 4.3.1. 列表常用操作 - -**初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 - -=== "Java" - - ```java title="list.java" - /* 初始化列表 */ - // 无初始值 - List list1 = new ArrayList<>(); - // 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[]) - Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; - List list = new ArrayList<>(Arrays.asList(numbers)); - ``` - -=== "C++" - - ```cpp title="list.cpp" - /* 初始化列表 */ - // 需注意,C++ 中 vector 即是本文描述的 list - // 无初始值 - vector list1; - // 有初始值 - vector list = { 1, 3, 2, 5, 4 }; - ``` - -=== "Python" - - ```python title="list.py" - """ 初始化列表 """ - # 无初始值 - list1 = [] - # 有初始值 - list = [1, 3, 2, 5, 4] - ``` - -=== "Go" - - ```go title="list_test.go" - /* 初始化列表 */ - // 无初始值 - list1 := []int - // 有初始值 - list := []int{1, 3, 2, 5, 4} - ``` - -=== "JavaScript" - - ```js title="list.js" - /* 初始化列表 */ - // 无初始值 - const list1 = []; - // 有初始值 - const list = [1, 3, 2, 5, 4]; - ``` - -=== "TypeScript" - - ```typescript title="list.ts" - /* 初始化列表 */ - // 无初始值 - const list1: number[] = []; - // 有初始值 - const list: number[] = [1, 3, 2, 5, 4]; - ``` - -=== "C" - - ```c title="list.c" - - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 初始化列表 */ - // 无初始值 - List list1 = new (); - // 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[]) - int[] numbers = new int[] { 1, 3, 2, 5, 4 }; - List list = numbers.ToList(); - ``` - -=== "Swift" - - ```swift title="list.swift" - /* 初始化列表 */ - // 无初始值 - let list1: [Int] = [] - // 有初始值 - var list = [1, 3, 2, 5, 4] - ``` - -=== "Zig" - - ```zig title="list.zig" - // 初始化列表 - var list = std.ArrayList(i32).init(std.heap.page_allocator); - defer list.deinit(); - try list.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); - ``` - -**访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 - -=== "Java" - - ```java title="list.java" - /* 访问元素 */ - int num = list.get(1); // 访问索引 1 处的元素 - - /* 更新元素 */ - list.set(1, 0); // 将索引 1 处的元素更新为 0 - ``` - -=== "C++" - - ```cpp title="list.cpp" - /* 访问元素 */ - int num = list[1]; // 访问索引 1 处的元素 - - /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 - ``` - -=== "Python" - - ```python title="list.py" - """ 访问元素 """ - num = list[1] # 访问索引 1 处的元素 - - """ 更新元素 """ - list[1] = 0 # 将索引 1 处的元素更新为 0 - ``` - -=== "Go" - - ```go title="list_test.go" - /* 访问元素 */ - num := list[1] // 访问索引 1 处的元素 - - /* 更新元素 */ - list[1] = 0 // 将索引 1 处的元素更新为 0 - ``` - -=== "JavaScript" - - ```js title="list.js" - /* 访问元素 */ - const num = list[1]; // 访问索引 1 处的元素 - - /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 - ``` - -=== "TypeScript" - - ```typescript title="list.ts" - /* 访问元素 */ - const num: number = list[1]; // 访问索引 1 处的元素 - - /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 - ``` - -=== "C" - - ```c title="list.c" - - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 访问元素 */ - int num = list[1]; // 访问索引 1 处的元素 - - /* 更新元素 */ - list[1] = 0; // 将索引 1 处的元素更新为 0 - ``` - -=== "Swift" - - ```swift title="list.swift" - /* 访问元素 */ - let num = list[1] // 访问索引 1 处的元素 - - /* 更新元素 */ - list[1] = 0 // 将索引 1 处的元素更新为 0 - ``` - -=== "Zig" - - ```zig title="list.zig" - // 访问元素 - var num = list.items[1]; // 访问索引 1 处的元素 - - // 更新元素 - list.items[1] = 0; // 将索引 1 处的元素更新为 0 - ``` - -**在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 - -=== "Java" - - ```java title="list.java" - /* 清空列表 */ - list.clear(); - - /* 尾部添加元素 */ - list.add(1); - list.add(3); - list.add(2); - list.add(5); - list.add(4); - - /* 中间插入元素 */ - list.add(3, 6); // 在索引 3 处插入数字 6 - - /* 删除元素 */ - list.remove(3); // 删除索引 3 处的元素 - ``` - -=== "C++" - - ```cpp title="list.cpp" - /* 清空列表 */ - list.clear(); - - /* 尾部添加元素 */ - list.push_back(1); - list.push_back(3); - list.push_back(2); - list.push_back(5); - list.push_back(4); - - /* 中间插入元素 */ - list.insert(list.begin() + 3, 6); // 在索引 3 处插入数字 6 - - /* 删除元素 */ - list.erase(list.begin() + 3); // 删除索引 3 处的元素 - ``` - -=== "Python" - - ```python title="list.py" - """ 清空列表 """ - list.clear() - - """ 尾部添加元素 """ - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) - - """ 中间插入元素 """ - list.insert(3, 6) # 在索引 3 处插入数字 6 - - """ 删除元素 """ - list.pop(3) # 删除索引 3 处的元素 - ``` - -=== "Go" - - ```go title="list_test.go" - /* 清空列表 */ - list = nil - - /* 尾部添加元素 */ - list = append(list, 1) - list = append(list, 3) - list = append(list, 2) - list = append(list, 5) - list = append(list, 4) - - /* 中间插入元素 */ - list = append(list[:3], append([]int{6}, list[3:]...)...) // 在索引 3 处插入数字 6 - - /* 删除元素 */ - list = append(list[:3], list[4:]...) // 删除索引 3 处的元素 - ``` - -=== "JavaScript" - - ```js title="list.js" - /* 清空列表 */ - list.length = 0; - - /* 尾部添加元素 */ - list.push(1); - list.push(3); - list.push(2); - list.push(5); - list.push(4); - - /* 中间插入元素 */ - list.splice(3, 0, 6); - - /* 删除元素 */ - list.splice(3, 1); - ``` - -=== "TypeScript" - - ```typescript title="list.ts" - /* 清空列表 */ - list.length = 0; - - /* 尾部添加元素 */ - list.push(1); - list.push(3); - list.push(2); - list.push(5); - list.push(4); - - /* 中间插入元素 */ - list.splice(3, 0, 6); - - /* 删除元素 */ - list.splice(3, 1); - ``` - -=== "C" - - ```c title="list.c" - - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 清空列表 */ - list.Clear(); - - /* 尾部添加元素 */ - list.Add(1); - list.Add(3); - list.Add(2); - list.Add(5); - list.Add(4); - - /* 中间插入元素 */ - list.Insert(3, 6); - - /* 删除元素 */ - list.RemoveAt(3); - ``` - -=== "Swift" - - ```swift title="list.swift" - /* 清空列表 */ - list.removeAll() - - /* 尾部添加元素 */ - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) - - /* 中间插入元素 */ - list.insert(6, at: 3) // 在索引 3 处插入数字 6 - - /* 删除元素 */ - list.remove(at: 3) // 删除索引 3 处的元素 - ``` - -=== "Zig" - - ```zig title="list.zig" - // 清空列表 - list.clearRetainingCapacity(); - - // 尾部添加元素 - try list.append(1); - try list.append(3); - try list.append(2); - try list.append(5); - try list.append(4); - - // 中间插入元素 - try list.insert(3, 6); // 在索引 3 处插入数字 6 - - // 删除元素 - _ = list.orderedRemove(3); // 删除索引 3 处的元素 - ``` - -**遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 - -=== "Java" - - ```java title="list.java" - /* 通过索引遍历列表 */ - int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; - } - - /* 直接遍历列表元素 */ - count = 0; - for (int n : list) { - count++; - } - ``` - -=== "C++" - - ```cpp title="list.cpp" - /* 通过索引遍历列表 */ - int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; - } - - /* 直接遍历列表元素 */ - count = 0; - for (int n : list) { - count++; - } - ``` - -=== "Python" - - ```python title="list.py" - """ 通过索引遍历列表 """ - count = 0 - for i in range(len(list)): - count += 1 - - """ 直接遍历列表元素 """ - count = 0 - for n in list: - count += 1 - ``` - -=== "Go" - - ```go title="list_test.go" - /* 通过索引遍历列表 */ - count := 0 - for i := 0; i < len(list); i++ { - count++ - } - - /* 直接遍历列表元素 */ - count = 0 - for range list { - count++ - } - ``` - -=== "JavaScript" - - ```js title="list.js" - /* 通过索引遍历列表 */ - let count = 0; - for (let i = 0; i < list.length; i++) { - count++; - } - - /* 直接遍历列表元素 */ - count = 0; - for (const n of list) { - count++; - } - ``` - -=== "TypeScript" - - ```typescript title="list.ts" - /* 通过索引遍历列表 */ - let count = 0; - for (let i = 0; i < list.length; i++) { - count++; - } - - /* 直接遍历列表元素 */ - count = 0; - for (const n of list) { - count++; - } - ``` - -=== "C" - - ```c title="list.c" - - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 通过索引遍历列表 */ - int count = 0; - for (int i = 0; i < list.Count(); i++) - { - count++; - } - - /* 直接遍历列表元素 */ - count = 0; - foreach (int n in list) - { - count++; - } - ``` - -=== "Swift" - - ```swift title="list.swift" - /* 通过索引遍历列表 */ - var count = 0 - for _ in list.indices { - count += 1 - } - - /* 直接遍历列表元素 */ - count = 0 - for _ in list { - count += 1 - } - ``` - -=== "Zig" - - ```zig title="list.zig" - // 通过索引遍历列表 - var count: i32 = 0; - var i: i32 = 0; - while (i < list.items.len) : (i += 1) { - count += 1; - } - - // 直接遍历列表元素 - count = 0; - for (list.items) |_| { - count += 1; - } - ``` - -**拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 - -=== "Java" - - ```java title="list.java" - /* 拼接两个列表 */ - List list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); - list.addAll(list1); // 将列表 list1 拼接到 list 之后 - ``` - -=== "C++" - - ```cpp title="list.cpp" - /* 拼接两个列表 */ - vector list1 = { 6, 8, 7, 10, 9 }; - // 将列表 list1 拼接到 list 之后 - list.insert(list.end(), list1.begin(), list1.end()); - ``` - -=== "Python" - - ```python title="list.py" - """ 拼接两个列表 """ - list1 = [6, 8, 7, 10, 9] - list += list1 # 将列表 list1 拼接到 list 之后 - ``` - -=== "Go" - - ```go title="list_test.go" - /* 拼接两个列表 */ - list1 := []int{6, 8, 7, 10, 9} - list = append(list, list1...) // 将列表 list1 拼接到 list 之后 - ``` - -=== "JavaScript" - - ```js title="list.js" - /* 拼接两个列表 */ - const list1 = [6, 8, 7, 10, 9]; - list.push(...list1); // 将列表 list1 拼接到 list 之后 - ``` - -=== "TypeScript" - - ```typescript title="list.ts" - /* 拼接两个列表 */ - const list1: number[] = [6, 8, 7, 10, 9]; - list.push(...list1); // 将列表 list1 拼接到 list 之后 - ``` - -=== "C" - - ```c title="list.c" - - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 拼接两个列表 */ - List list1 = new() { 6, 8, 7, 10, 9 }; - list.AddRange(list1); // 将列表 list1 拼接到 list 之后 - ``` - -=== "Swift" - - ```swift title="list.swift" - /* 拼接两个列表 */ - let list1 = [6, 8, 7, 10, 9] - list.append(contentsOf: list1) // 将列表 list1 拼接到 list 之后 - ``` - -=== "Zig" - - ```zig title="list.zig" - // 拼接两个列表 - var list1 = std.ArrayList(i32).init(std.heap.page_allocator); - defer list1.deinit(); - try list1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); - try list.insertSlice(list.items.len, list1.items); // 将列表 list1 拼接到 list 之后 - ``` - -**排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 - -=== "Java" - - ```java title="list.java" - /* 排序列表 */ - Collections.sort(list); // 排序后,列表元素从小到大排列 - ``` - -=== "C++" - - ```cpp title="list.cpp" - /* 排序列表 */ - sort(list.begin(), list.end()); // 排序后,列表元素从小到大排列 - ``` - -=== "Python" - - ```python title="list.py" - """ 排序列表 """ - list.sort() # 排序后,列表元素从小到大排列 - ``` - -=== "Go" - - ```go title="list_test.go" - /* 排序列表 */ - sort.Ints(list) // 排序后,列表元素从小到大排列 - ``` - -=== "JavaScript" - - ```js title="list.js" - /* 排序列表 */ - list.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 - ``` - -=== "TypeScript" - - ```typescript title="list.ts" - /* 排序列表 */ - list.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 - ``` - -=== "C" - - ```c title="list.c" - - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 排序列表 */ - list.Sort(); // 排序后,列表元素从小到大排列 - ``` - -=== "Swift" - - ```swift title="list.swift" - /* 排序列表 */ - list.sort() // 排序后,列表元素从小到大排列 - ``` - -=== "Zig" - - ```zig title="list.zig" - // 排序列表 - std.sort.sort(i32, list.items, {}, comptime std.sort.asc(i32)); - ``` - -## 4.3.2. 列表简易实现 * - -为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: - -- **初始容量**:选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。 -- **数量记录**:需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。 -- **扩容机制**:插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。 - -本示例是为了帮助读者对如何实现列表产生直观的认识。实际编程语言中,列表的实现远比以下代码复杂且标准,感兴趣的读者可以查阅源码学习。 - -=== "Java" - - ```java title="my_list.java" - /* 列表类简易实现 */ - class MyList { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - public MyList() { - nums = new int[capacity]; - } - - /* 获取列表长度(即当前元素数量)*/ - public int size() { - return size; - } - - /* 获取列表容量 */ - public int capacity() { - return capacity; - } - - /* 访问元素 */ - public int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - public void set(int index, int num) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - public void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (size == capacity()) - extendCapacity(); - nums[size] = num; - // 更新元素数量 - size++; - } - - /* 中间插入元素 */ - public void insert(int index, int num) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size == capacity()) - extendCapacity(); - // 将索引 index 以及之后的元素都向后移动一位 - for (int j = size - 1; j >= index; j--) { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - size++; - } - - /* 删除元素 */ - public int remove(int index) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (int j = index; j < size - 1; j++) { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public void extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - nums = Arrays.copyOf(nums, capacity() * extendRatio); - // 更新列表容量 - capacity = nums.length; - } - - /* 将列表转换为数组 */ - public int[] toArray() { - int size = size(); - // 仅转换有效长度范围内的列表元素 - int[] nums = new int[size]; - for (int i = 0; i < size; i++) { - nums[i] = get(i); - } - return nums; - } - } - ``` - -=== "C++" - - ```cpp title="my_list.cpp" - /* 列表类简易实现 */ - class MyList { - private: - int* nums; // 数组(存储列表元素) - int numsCapacity = 10; // 列表容量 - int numsSize = 0; // 列表长度(即当前元素数量) - int extendRatio = 2; // 每次列表扩容的倍数 - - public: - /* 构造函数 */ - MyList() { - nums = new int[numsCapacity]; - } - - /* 析构函数 */ - ~MyList() { - delete[] nums; - } - - /* 获取列表长度(即当前元素数量)*/ - int size() { - return numsSize; - } - - /* 获取列表容量 */ - int capacity() { - return numsCapacity; - } - - /* 访问元素 */ - int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - void set(int index, int num) { - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (size() == capacity()) - extendCapacity(); - nums[size()] = num; - // 更新元素数量 - numsSize++; - } - - /* 中间插入元素 */ - void insert(int index, int num) { - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size() == capacity()) - extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 - for (int j = size() - 1; j >= index; j--) { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - numsSize++; - } - - /* 删除元素 */ - int remove(int index) { - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - int num = nums[index]; - // 索引 i 之后的元素都向前移动一位 - for (int j = index; j < size() - 1; j++) { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - numsSize--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - void extendCapacity() { - // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组 - int newCapacity = capacity() * extendRatio; - int* tmp = nums; - nums = new int[newCapacity]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size(); i++) { - nums[i] = tmp[i]; - } - // 释放内存 - delete[] tmp; - numsCapacity = newCapacity; - } - }; - ``` - -=== "Python" - - ```python title="my_list.py" - """ 列表类简易实现 """ - class MyList: - """ 构造函数 """ - def __init__(self): - self.__capacity = 10 # 列表容量 - self.__nums = [0] * self.__capacity # 数组(存储列表元素) - self.__size = 0 # 列表长度(即当前元素数量) - self.__extend_ratio = 2 # 每次列表扩容的倍数 - - """ 获取列表长度(即当前元素数量) """ - def size(self): - return self.__size - - """ 获取列表容量 """ - def capacity(self): - return self.__capacity - - """ 访问元素 """ - def get(self, index): - # 索引如果越界则抛出异常,下同 - assert index >= 0 and index < self.__size, "索引越界" - return self.__nums[index] - - """ 更新元素 """ - def set(self, num, index): - assert index >= 0 and index < self.__size, "索引越界" - self.__nums[index] = num - - """ 中间插入(尾部添加)元素 """ - def add(self, num, index=-1): - assert index >= 0 and index < self.__size, "索引越界" - # 若不指定索引 index ,则向数组尾部添加元素 - if index == -1: - index = self.__size - # 元素数量超出容量时,触发扩容机制 - if self.__size == self.capacity(): - self.extend_capacity() - # 索引 i 以及之后的元素都向后移动一位 - for j in range(self.__size - 1, index - 1, -1): - self.__nums[j + 1] = self.__nums[j] - self.__nums[index] = num - # 更新元素数量 - self.__size += 1 - - """ 删除元素 """ - def remove(self, index): - assert index >= 0 and index < self.__size, "索引越界" - num = self.nums[index] - # 索引 i 之后的元素都向前移动一位 - for j in range(index, self.__size - 1): - self.__nums[j] = self.__nums[j + 1] - # 更新元素数量 - self.__size -= 1 - # 返回被删除元素 - return num - - """ 列表扩容 """ - def extend_capacity(self): - # 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 - self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1) - # 更新列表容量 - self.__capacity = len(self.__nums) - - """ 返回有效长度的列表 """ - def to_array(self): - return self.__nums[:self.__size] - ``` - -=== "Go" - - ```go title="my_list.go" - /* 列表类简易实现 */ - type myList struct { - numsCapacity int - nums []int - numsSize int - extendRatio int - } - - /* 构造函数 */ - func newMyList() *myList { - return &myList{ - numsCapacity: 10, // 列表容量 - nums: make([]int, 10), // 数组(存储列表元素) - numsSize: 0, // 列表长度(即当前元素数量) - extendRatio: 2, // 每次列表扩容的倍数 - } - } - - /* 获取列表长度(即当前元素数量) */ - func (l *myList) size() int { - return l.numsSize - } - - /* 获取列表容量 */ - func (l *myList) capacity() int { - return l.numsCapacity - } - - /* 访问元素 */ - func (l *myList) get(index int) int { - // 索引如果越界则抛出异常,下同 - if index < 0 || index >= l.numsSize { - panic("索引越界") - } - return l.nums[index] - } - - /* 更新元素 */ - func (l *myList) set(num, index int) { - if index < 0 || index >= l.numsSize { - panic("索引越界") - } - l.nums[index] = num - } - - /* 尾部添加元素 */ - func (l *myList) add(num int) { - // 元素数量超出容量时,触发扩容机制 - if l.numsSize == l.numsCapacity { - l.extendCapacity() - } - l.nums[l.numsSize] = num - // 更新元素数量 - l.numsSize++ - } - - /* 中间插入元素 */ - func (l *myList) insert(num, index int) { - if index < 0 || index >= l.numsSize { - panic("索引越界") - } - // 元素数量超出容量时,触发扩容机制 - if l.numsSize == l.numsCapacity { - l.extendCapacity() - } - // 索引 i 以及之后的元素都向后移动一位 - for j := l.numsSize - 1; j >= index; j-- { - l.nums[j+1] = l.nums[j] - } - l.nums[index] = num - // 更新元素数量 - l.numsSize++ - } - - /* 删除元素 */ - func (l *myList) remove(index int) int { - if index < 0 || index >= l.numsSize { - panic("索引越界") - } - num := l.nums[index] - // 索引 i 之后的元素都向前移动一位 - for j := index; j < l.numsSize-1; j++ { - l.nums[j] = l.nums[j+1] - } - // 更新元素数量 - l.numsSize-- - // 返回被删除元素 - return num - } - - /* 列表扩容 */ - func (l *myList) extendCapacity() { - // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 - l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) - // 更新列表容量 - l.numsCapacity = len(l.nums) - } - ``` - -=== "JavaScript" - - ```js title="my_list.js" - /* 列表类简易实现 */ - class MyList { - #nums = new Array(); // 数组(存储列表元素) - #capacity = 10; // 列表容量 - #size = 0; // 列表长度(即当前元素数量) - #extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - constructor() { - this.#nums = new Array(this.#capacity); - } - - /* 获取列表长度(即当前元素数量)*/ - size() { - return this.#size; - } - - /* 获取列表容量 */ - capacity() { - return this.#capacity; - } - - /* 访问元素 */ - get(index) { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= this.#size) - throw new Error('索引越界'); - return this.#nums[index]; - } - - /* 更新元素 */ - set(index, num) { - if (index < 0 || index >= this.#size) - throw new Error('索引越界'); - this.#nums[index] = num; - } - - /* 尾部添加元素 */ - add(num) { - // 如果长度等于容量,则需要扩容 - if (this.#size === this.#capacity) { - this.extendCapacity(); - } - // 将新元素添加到列表尾部 - this.#nums[this.#size] = num; - this.#size++; - } - - /* 中间插入元素 */ - insert(index, num) { - if (index < 0 || index >= this.#size) - throw new Error('索引越界'); - // 元素数量超出容量时,触发扩容机制 - if (this.#size === this.#capacity) { - this.extendCapacity(); - } - // 将索引 index 以及之后的元素都向后移动一位 - for (let j = this.#size - 1; j >= index; j--) { - this.#nums[j + 1] = this.#nums[j]; - } - // 更新元素数量 - this.#nums[index] = num; - this.#size++; - } - - /* 删除元素 */ - remove(index) { - if (index < 0 || index >= this.#size) - throw new Error('索引越界'); - let num = this.#nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (let j = index; j < this.#size - 1; j++) { - this.#nums[j] = this.#nums[j + 1]; - } - // 更新元素数量 - this.#size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - this.#nums = this.#nums.concat( - new Array(this.capacity() * (this.#extendRatio - 1)) - ); - // 更新列表容量 - this.#capacity = this.#nums.length; - } - } - ``` - -=== "TypeScript" - - ```typescript title="my_list.ts" - /* 列表类简易实现 */ - class MyList { - private nums: Array; // 数组(存储列表元素) - private _capacity: number = 10; // 列表容量 - private _size: number = 0; // 列表长度(即当前元素数量) - private extendRatio: number = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - constructor() { - this.nums = new Array(this._capacity); - } - - /* 获取列表长度(即当前元素数量)*/ - public size(): number { - return this._size; - } - - /* 获取列表容量 */ - public capacity(): number { - return this._capacity; - } - - /* 访问元素 */ - public get(index: number): number { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= this._size) - throw new Error('索引越界'); - return this.nums[index]; - } - - /* 更新元素 */ - public set(index: number, num: number): void { - if (index < 0 || index >= this._size) - throw new Error('索引越界'); - this.nums[index] = num; - } - - /* 尾部添加元素 */ - public add(num: number): void { - // 如果长度等于容量,则需要扩容 - if (this._size === this._capacity) - this.extendCapacity(); - // 将新元素添加到列表尾部 - this.nums[this._size] = num; - this._size++; - } - - /* 中间插入元素 */ - public insert(index: number, num: number): void { - if (index < 0 || index >= this._size) - throw new Error('索引越界'); - // 元素数量超出容量时,触发扩容机制 - if (this._size === this._capacity) { - this.extendCapacity(); - } - // 将索引 index 以及之后的元素都向后移动一位 - for (let j = this._size - 1; j >= index; j--) { - this.nums[j + 1] = this.nums[j]; - } - // 更新元素数量 - this.nums[index] = num; - this._size++; - } - - /* 删除元素 */ - public remove(index: number): number { - if (index < 0 || index >= this._size) - throw new Error('索引越界'); - let num = this.nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (let j = index; j < this._size - 1; j++) { - this.nums[j] = this.nums[j + 1]; - } - // 更新元素数量 - this._size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public extendCapacity(): void { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - this.nums = this.nums.concat( - new Array(this.capacity() * (this.extendRatio - 1)) - ); - // 更新列表容量 - this._capacity = this.nums.length; - } - } - ``` - -=== "C" - - ```c title="my_list.c" - - ``` - -=== "C#" - - ```csharp title="my_list.cs" - class MyList - { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造函数 */ - public MyList() - { - nums = new int[capacity]; - } - - /* 获取列表长度(即当前元素数量)*/ - public int Size() - { - return size; - } - - /* 获取列表容量 */ - public int Capacity() - { - return capacity; - } - - /* 访问元素 */ - public int Get(int index) - { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= size) - throw new IndexOutOfRangeException("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - public void Set(int index, int num) - { - if (index < 0 || index >= size) - throw new IndexOutOfRangeException("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - public void Add(int num) - { - // 元素数量超出容量时,触发扩容机制 - if (size == Capacity()) - ExtendCapacity(); - nums[size] = num; - // 更新元素数量 - size++; - } - - /* 中间插入元素 */ - public void Insert(int index, int num) - { - if (index < 0 || index >= size) - throw new IndexOutOfRangeException("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size == Capacity()) - ExtendCapacity(); - // 将索引 index 以及之后的元素都向后移动一位 - for (int j = size - 1; j >= index; j--) - { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - size++; - } - - /* 删除元素 */ - public int Remove(int index) - { - if (index < 0 || index >= size) - throw new IndexOutOfRangeException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (int j = index; j < size - 1; j++) - { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - size--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public void ExtendCapacity() - { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - System.Array.Resize(ref nums, Capacity() * extendRatio); - // 更新列表容量 - capacity = nums.Length; - } - } - ``` - -=== "Swift" - - ```swift title="my_list.swift" - /* 列表类简易实现 */ - class MyList { - private var nums: [Int] // 数组(存储列表元素) - private var _capacity = 10 // 列表容量 - private var _size = 0 // 列表长度(即当前元素数量) - private let extendRatio = 2 // 每次列表扩容的倍数 - - /* 构造函数 */ - init() { - nums = Array(repeating: 0, count: _capacity) - } - - /* 获取列表长度(即当前元素数量)*/ - func size() -> Int { - _size - } - - /* 获取列表容量 */ - func capacity() -> Int { - _capacity - } - - /* 访问元素 */ - func get(index: Int) -> Int { - // 索引如果越界则抛出错误,下同 - if index < 0 || index >= _size { - fatalError("索引越界") - } - return nums[index] - } - - /* 更新元素 */ - func set(index: Int, num: Int) { - if index < 0 || index >= _size { - fatalError("索引越界") - } - nums[index] = num - } - - /* 尾部添加元素 */ - func add(num: Int) { - // 元素数量超出容量时,触发扩容机制 - if _size == _capacity { - extendCapacity() - } - nums[_size] = num - // 更新元素数量 - _size += 1 - } - - /* 中间插入元素 */ - func insert(index: Int, num: Int) { - if index < 0 || index >= _size { - fatalError("索引越界") - } - // 元素数量超出容量时,触发扩容机制 - if _size == _capacity { - extendCapacity() - } - // 将索引 index 以及之后的元素都向后移动一位 - for j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) { - nums[j + 1] = nums[j] - } - nums[index] = num - // 更新元素数量 - _size += 1 - } - - /* 删除元素 */ - @discardableResult - func remove(index: Int) -> Int { - if index < 0 || index >= _size { - fatalError("索引越界") - } - let num = nums[index] - // 将索引 index 之后的元素都向前移动一位 - for j in index ..< (_size - 1) { - nums[j] = nums[j + 1] - } - // 更新元素数量 - _size -= 1 - // 返回被删除元素 - return num - } - - /* 列表扩容 */ - func extendCapacity() { - // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 - nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) - // 更新列表容量 - _capacity = nums.count - } - } - ``` - -=== "Zig" - - ```zig title="my_list.zig" - // 列表类简易实现 - pub fn MyList(comptime T: type) type { - return struct { - const Self = @This(); - - nums: []T = undefined, // 数组(存储列表元素) - numsCapacity: usize = 10, // 列表容量 - numSize: usize = 0, // 列表长度(即当前元素数量) - extendRatio: usize = 2, // 每次列表扩容的倍数 - mem_arena: ?std.heap.ArenaAllocator = null, - mem_allocator: std.mem.Allocator = undefined, // 内存分配器 - - // 构造函数(分配内存+初始化列表) - pub fn init(self: *Self, allocator: std.mem.Allocator) !void { - if (self.mem_arena == null) { - self.mem_arena = std.heap.ArenaAllocator.init(allocator); - self.mem_allocator = self.mem_arena.?.allocator(); - } - self.nums = try self.mem_allocator.alloc(T, self.numsCapacity); - std.mem.set(T, self.nums, @as(T, 0)); - } - - // 析构函数(释放内存) - pub fn deinit(self: *Self) void { - if (self.mem_arena == null) return; - self.mem_arena.?.deinit(); - } - - // 获取列表长度(即当前元素数量) - pub fn size(self: *Self) usize { - return self.numSize; - } - - // 获取列表容量 - pub fn capacity(self: *Self) usize { - return self.numsCapacity; - } - - // 访问元素 - pub fn get(self: *Self, index: usize) T { - // 索引如果越界则抛出异常,下同 - if (index < 0 or index >= self.size()) @panic("索引越界"); - return self.nums[index]; - } - - // 更新元素 - pub fn set(self: *Self, index: usize, num: T) void { - // 索引如果越界则抛出异常,下同 - if (index < 0 or index >= self.size()) @panic("索引越界"); - self.nums[index] = num; - } - - // 尾部添加元素 - pub fn add(self: *Self, num: T) !void { - // 元素数量超出容量时,触发扩容机制 - if (self.size() == self.capacity()) try self.extendCapacity(); - self.nums[self.size()] = num; - // 更新元素数量 - self.numSize += 1; - } - - // 中间插入元素 - pub fn insert(self: *Self, index: usize, num: T) !void { - if (index < 0 or index >= self.size()) @panic("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (self.size() == self.capacity()) try self.extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 - var j = self.size() - 1; - while (j >= index) : (j -= 1) { - self.nums[j + 1] = self.nums[j]; - } - self.nums[index] = num; - // 更新元素数量 - self.numSize += 1; - } - - // 删除元素 - pub fn remove(self: *Self, index: usize) T { - if (index < 0 or index >= self.size()) @panic("索引越界"); - var num = self.nums[index]; - // 索引 i 之后的元素都向前移动一位 - var j = index; - while (j < self.size() - 1) : (j += 1) { - self.nums[j] = self.nums[j + 1]; - } - // 更新元素数量 - self.numSize -= 1; - // 返回被删除元素 - return num; - } - - // 列表扩容 - pub fn extendCapacity(self: *Self) !void { - // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组 - var newCapacity = self.capacity() * self.extendRatio; - var extend = try self.mem_allocator.alloc(T, newCapacity); - std.mem.set(T, extend, @as(T, 0)); - // 将原数组中的所有元素复制到新数组 - std.mem.copy(T, extend, self.nums); - self.nums = extend; - // 更新列表容量 - self.numsCapacity = newCapacity; - } - - // 将列表转换为数组 - pub fn toArray(self: *Self) ![]T { - // 仅转换有效长度范围内的列表元素 - var nums = try self.mem_allocator.alloc(T, self.size()); - std.mem.set(T, nums, @as(T, 0)); - for (nums) |*num, i| { - num.* = self.get(i); - } - return nums; - } - }; - } - ``` diff --git a/build/chapter_array_and_linkedlist/summary.md b/build/chapter_array_and_linkedlist/summary.md deleted file mode 100644 index 3b6ac2cb0..000000000 --- a/build/chapter_array_and_linkedlist/summary.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -comments: true ---- - -# 4.4. 小结 - -- 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。 -- 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。 -- 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。 -- 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。 - -## 4.4.1. 数组 VS 链表 - -

Table. 数组与链表特点对比

- -
- -| | 数组 | 链表 | -| ------------ | ------------------------ | ------------ | -| 存储方式 | 连续内存空间 | 离散内存空间 | -| 数据结构长度 | 长度不可变 | 长度可变 | -| 内存使用率 | 占用内存少、缓存局部性好 | 占用内存多 | -| 优势操作 | 随机访问 | 插入、删除 | - -
- -!!! tip - - 「缓存局部性(Cache locality)」涉及到了计算机操作系统,在本书不做展开介绍,建议有兴趣的同学 Google / Baidu 一下。 - -

Table. 数组与链表操作时间复杂度

- -
- -| 操作 | 数组 | 链表 | -| ------- | ------ | ------ | -| 访问元素 | $O(1)$ | $O(N)$ | -| 添加元素 | $O(N)$ | $O(1)$ | -| 删除元素 | $O(N)$ | $O(1)$ | - -
diff --git a/build/chapter_computational_complexity/performance_evaluation.md b/build/chapter_computational_complexity/performance_evaluation.md deleted file mode 100644 index 6c236c864..000000000 --- a/build/chapter_computational_complexity/performance_evaluation.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -comments: true ---- - -# 2.1. 算法效率评估 - -## 2.1.1. 算法评价维度 - -在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。 - -1. **找到问题解法**。算法需要能够在规定的输入范围下,可靠地求得问题的正确解。 -2. **寻求最优解法**。同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。 - -换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括: - -- **时间效率**,即算法的运行速度的快慢。 -- **空间效率**,即算法占用的内存空间大小。 - -数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 - -## 2.1.2. 效率评估方法 - -### 实际测试 - -假设我们现在有算法 A 和 算法 B ,都能够解决同一问题,现在需要对比两个算法之间的效率。我们能够想到的最直接的方式,就是找一台计算机,把两个算法都完整跑一遍,并监控记录运行时间和内存占用情况。这种评估方式能够反映真实情况,但是也存在很大的硬伤。 - -**难以排除测试环境的干扰因素**。硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。 - -**展开完整测试非常耗费资源**。随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。 - -### 理论估算 - -既然实际测试具有很大的局限性,那么我们是否可以仅通过一些计算,就获知算法的效率水平呢?答案是肯定的,我们将此估算方法称为「复杂度分析 Complexity Analysis」或「渐近复杂度分析 Asymptotic Complexity Analysis」。 - -**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势**。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 - -**复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 - -## 2.1.3. 复杂度分析重要性 - -复杂度分析给出一把评价算法效率的“标尺”,告诉我们执行某个算法需要多少时间和空间资源,也让我们可以开展不同算法之间的效率对比。 - -计算复杂度是个数学概念,对于初学者可能比较抽象,学习难度相对较高。从这个角度出发,其并不适合作为第一章内容。但是,当我们讨论某个数据结构或者算法的特点时,难以避免需要分析它的运行速度和空间使用情况。**因此,在展开学习数据结构与算法之前,建议读者先对计算复杂度建立起初步的了解,并且能够完成简单案例的复杂度分析**。 diff --git a/build/chapter_computational_complexity/space_complexity.md b/build/chapter_computational_complexity/space_complexity.md deleted file mode 100755 index 1d987ac6e..000000000 --- a/build/chapter_computational_complexity/space_complexity.md +++ /dev/null @@ -1,1513 +0,0 @@ ---- -comments: true ---- - -# 2.3. 空间复杂度 - -「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。 - -## 2.3.1. 算法相关空间 - -算法运行中,使用的内存空间主要有以下几种: - -- 「输入空间」用于存储算法的输入数据; -- 「暂存空间」用于存储算法运行中的变量、对象、函数上下文等数据; -- 「输出空间」用于存储算法的输出数据; - -!!! tip - - 通常情况下,空间复杂度统计范围是「暂存空间」+「输出空间」。 - -暂存空间可分为三个部分: - -- 「暂存数据」用于保存算法运行中的各种 **常量、变量、对象** 等。 -- 「栈帧空间」用于保存调用函数的上下文数据。系统每次调用函数都会在栈的顶部创建一个栈帧,函数返回时,栈帧空间会被释放。 -- 「指令空间」用于保存编译后的程序指令,**在实际统计中一般忽略不计**。 - -![space_types](space_complexity.assets/space_types.png) - -

Fig. 算法使用的相关空间

- -=== "Java" - - ```java title="" - /* 类 */ - class Node { - int val; - Node next; - Node(int x) { val = x; } - } - - /* 函数 */ - int function() { - // do something... - return 0; - } - - int algorithm(int n) { // 输入数据 - final int a = 0; // 暂存数据(常量) - int b = 0; // 暂存数据(变量) - Node node = new Node(0); // 暂存数据(对象) - int c = function(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 - } - ``` - -=== "C++" - - ```cpp title="" - /* 结构体 */ - struct Node { - int val; - Node *next; - Node(int x) : val(x), next(nullptr) {} - }; - - /* 函数 */ - int func() { - // do something... - return 0; - } - - int algorithm(int n) { // 输入数据 - const int a = 0; // 暂存数据(常量) - int b = 0; // 暂存数据(变量) - Node* node = new Node(0); // 暂存数据(对象) - int c = func(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 - } - ``` - -=== "Python" - - ```python title="" - """ 类 """ - class Node: - def __init__(self, x): - self.val = x # 结点值 - self.next = None # 指向下一结点的指针(引用) - - """ 函数 """ - def function(): - # do something... - return 0 - - def algorithm(n): # 输入数据 - b = 0 # 暂存数据(变量) - node = Node(0) # 暂存数据(对象) - c = function() # 栈帧空间(调用函数) - return a + b + c # 输出数据 - ``` - -=== "Go" - - ```go title="" - /* 结构体 */ - type node struct { - val int - next *node - } - - /* 创建 node 结构体 */ - func newNode(val int) *node { - return &node{val: val} - } - - /* 函数 */ - func function() int { - // do something... - return 0 - } - - func algorithm(n int) int { // 输入数据 - const a = 0 // 暂存数据(常量) - b := 0 // 暂存数据(变量) - newNode(0) // 暂存数据(对象) - c := function() // 栈帧空间(调用函数) - return a + b + c // 输出数据 - } - ``` - -=== "JavaScript" - - ```js title="" - /* 类 */ - class Node { - val; - next; - constructor(val) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = null; // 指向下一结点的引用 - } - } - - /* 函数 */ - function constFunc() { - // do something - return 0; - } - - function algorithm(n) { // 输入数据 - const a = 0; // 暂存数据(常量) - const b = 0; // 暂存数据(变量) - const node = new Node(0); // 暂存数据(对象) - const c = constFunc(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 - } - ``` - -=== "TypeScript" - - ```typescript title="" - /* 类 */ - class Node { - val: number; - next: Node | null; - constructor(val?: number) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = null; // 指向下一结点的引用 - } - } - - /* 函数 */ - function constFunc(): number { - // do something - return 0; - } - - function algorithm(n: number): number { // 输入数据 - const a = 0; // 暂存数据(常量) - const b = 0; // 暂存数据(变量) - const node = new Node(0); // 暂存数据(对象) - const c = constFunc(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - /* 类 */ - class Node - { - int val; - Node next; - Node(int x) { val = x; } - } - - /* 函数 */ - int function() - { - // do something... - return 0; - } - - int algorithm(int n) // 输入数据 - { - int a = 0; // 暂存数据(常量) - int b = 0; // 暂存数据(变量) - Node node = new Node(0); // 暂存数据(对象) - int c = function(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 - } - ``` - -=== "Swift" - - ```swift title="" - /* 类 */ - class Node { - var val: Int - var next: Node? - - init(x: Int) { - val = x - } - } - - /* 函数 */ - func function() -> Int { - // do something... - return 0 - } - - func algorithm(n: Int) -> Int { // 输入数据 - let a = 0 // 暂存数据(常量) - var b = 0 // 暂存数据(变量) - let node = Node(x: 0) // 暂存数据(对象) - let c = function() // 栈帧空间(调用函数) - return a + b + c // 输出数据 - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -## 2.3.2. 推算方法 - -空间复杂度的推算方法和时间复杂度总体类似,只是从统计“计算操作数量”变为统计“使用空间大小”。与时间复杂度不同的是,**我们一般只关注「最差空间复杂度」**。这是因为内存空间是一个硬性要求,我们必须保证在所有输入数据下都有足够的内存空间预留。 - -**最差空间复杂度中的“最差”有两层含义**,分别为输入数据的最差分布、算法运行中的最差时间点。 - -- **以最差输入数据为准**。当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; -- **以算法运行过程中的峰值内存为准**。程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; - -=== "Java" - - ```java title="" - void algorithm(int n) { - int a = 0; // O(1) - int[] b = new int[10000]; // O(1) - if (n > 10) - int[] nums = new int[n]; // O(n) - } - ``` - -=== "C++" - - ```cpp title="" - void algorithm(int n) { - int a = 0; // O(1) - vector b(10000); // O(1) - if (n > 10) - vector nums(n); // O(n) - } - ``` - -=== "Python" - - ```python title="" - def algorithm(n): - a = 0 # O(1) - b = [0] * 10000 # O(1) - if n > 10: - nums = [0] * n # O(n) - ``` - -=== "Go" - - ```go title="" - func algorithm(n int) { - a := 0 // O(1) - b := make([]int, 10000) // O(1) - var nums []int - if n > 10 { - nums := make([]int, n) // O(n) - } - fmt.Println(a, b, nums) - } - ``` - -=== "JavaScript" - - ```js title="" - function algorithm(n) { - const a = 0; // O(1) - const b = new Array(10000); // O(1) - if (n > 10) { - const nums = new Array(n); // O(n) - } - } - ``` - -=== "TypeScript" - - ```typescript title="" - function algorithm(n: number): void { - const a = 0; // O(1) - const b = new Array(10000); // O(1) - if (n > 10) { - const nums = new Array(n); // O(n) - } - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - void algorithm(int n) - { - int a = 0; // O(1) - int[] b = new int[10000]; // O(1) - if (n > 10) - { - int[] nums = new int[n]; // O(n) - } - } - ``` - -=== "Swift" - - ```swift title="" - func algorithm(n: Int) { - let a = 0 // O(1) - let b = Array(repeating: 0, count: 10000) // O(1) - if n > 10 { - let nums = Array(repeating: 0, count: n) // O(n) - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -**在递归函数中,需要注意统计栈帧空间**。例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 - -=== "Java" - - ```java title="" - int function() { - // do something - return 0; - } - /* 循环 O(1) */ - void loop(int n) { - for (int i = 0; i < n; i++) { - function(); - } - } - /* 递归 O(n) */ - void recur(int n) { - if (n == 1) return; - return recur(n - 1); - } - ``` - -=== "C++" - - ```cpp title="" - int func() { - // do something - return 0; - } - /* 循环 O(1) */ - void loop(int n) { - for (int i = 0; i < n; i++) { - func(); - } - } - /* 递归 O(n) */ - void recur(int n) { - if (n == 1) return; - return recur(n - 1); - } - ``` - -=== "Python" - - ```python title="" - def function(): - # do something - return 0 - - """ 循环 O(1) """ - def loop(n): - for _ in range(n): - function() - - """ 递归 O(n) """ - def recur(n): - if n == 1: return - return recur(n - 1) - ``` - -=== "Go" - - ```go title="" - func function() int { - // do something - return 0 - } - - /* 循环 O(1) */ - func loop(n int) { - for i := 0; i < n; i++ { - function() - } - } - - /* 递归 O(n) */ - func recur(n int) { - if n == 1 { - return - } - recur(n - 1) - } - ``` - -=== "JavaScript" - - ```js title="" - function constFunc() { - // do something - return 0; - } - /* 循环 O(1) */ - function loop(n) { - for (let i = 0; i < n; i++) { - constFunc(); - } - } - /* 递归 O(n) */ - function recur(n) { - if (n === 1) return; - return recur(n - 1); - } - ``` - -=== "TypeScript" - - ```typescript title="" - function constFunc(): number { - // do something - return 0; - } - /* 循环 O(1) */ - function loop(n: number): void { - for (let i = 0; i < n; i++) { - constFunc(); - } - } - /* 递归 O(n) */ - function recur(n: number): void { - if (n === 1) return; - return recur(n - 1); - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - int function() - { - // do something - return 0; - } - /* 循环 O(1) */ - void loop(int n) - { - for (int i = 0; i < n; i++) - { - function(); - } - } - /* 递归 O(n) */ - int recur(int n) - { - if (n == 1) return 1; - return recur(n - 1); - } - ``` - -=== "Swift" - - ```swift title="" - @discardableResult - func function() -> Int { - // do something - return 0 - } - - /* 循环 O(1) */ - func loop(n: Int) { - for _ in 0 ..< n { - function() - } - } - - /* 递归 O(n) */ - func recur(n: Int) { - if n == 1 { - return - } - recur(n: n - 1) - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -## 2.3.3. 常见类型 - -设输入数据大小为 $n$ ,常见的空间复杂度类型有(从低到高排列) - -$$ -\begin{aligned} -O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline -\text{常数阶} < \text{对数阶} < \text{线性阶} < \text{平方阶} < \text{指数阶} -\end{aligned} -$$ - -![space_complexity_common_types](space_complexity.assets/space_complexity_common_types.png) - -

Fig. 空间复杂度的常见类型

- -!!! tip - - 部分示例代码需要一些前置知识,包括数组、链表、二叉树、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解空间复杂度含义和推算方法上。 - -### 常数阶 $O(1)$ - -常数阶常见于数量与输入数据大小 $n$ 无关的常量、变量、对象。 - -需要注意的是,在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,即不会累积占用空间,空间复杂度仍为 $O(1)$ 。 - -=== "Java" - - ```java title="space_complexity.java" - /* 常数阶 */ - void constant(int n) { - // 常量、变量、对象占用 O(1) 空间 - final int a = 0; - int b = 0; - int[] nums = new int[10000]; - ListNode node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) { - function(); - } - } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 常数阶 */ - void constant(int n) { - // 常量、变量、对象占用 O(1) 空间 - const int a = 0; - int b = 0; - vector nums(10000); - ListNode node(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) { - func(); - } - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 常数阶 """ - def constant(n): - # 常量、变量、对象占用 O(1) 空间 - a = 0 - nums = [0] * 10000 - node = ListNode(0) - # 循环中的变量占用 O(1) 空间 - for _ in range(n): - c = 0 - # 循环中的函数占用 O(1) 空间 - for _ in range(n): - function() - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 常数阶 */ - func spaceConstant(n int) { - // 常量、变量、对象占用 O(1) 空间 - const a = 0 - b := 0 - nums := make([]int, 10000) - ListNode := newNode(0) - // 循环中的变量占用 O(1) 空间 - var c int - for i := 0; i < n; i++ { - c = 0 - } - // 循环中的函数占用 O(1) 空间 - for i := 0; i < n; i++ { - function() - } - fmt.Println(a, b, nums, c, ListNode) - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - /* 常数阶 */ - function constant(n) { - // 常量、变量、对象占用 O(1) 空间 - const a = 0; - const b = 0; - const nums = new Array(10000); - const node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (let i = 0; i < n; i++) { - const c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (let i = 0; i < n; i++) { - constFunc(); - } - } - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - /* 常数阶 */ - function constant(n: number): void { - // 常量、变量、对象占用 O(1) 空间 - const a = 0; - const b = 0; - const nums = new Array(10000); - const node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (let i = 0; i < n; i++) { - const c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (let i = 0; i < n; i++) { - constFunc(); - } - } - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 常数阶 */ - void constant(int n) - { - // 常量、变量、对象占用 O(1) 空间 - int a = 0; - int b = 0; - int[] nums = new int[10000]; - ListNode node = new ListNode(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) - { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) - { - function(); - } - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 常数阶 */ - func constant(n: Int) { - // 常量、变量、对象占用 O(1) 空间 - let a = 0 - var b = 0 - let nums = Array(repeating: 0, count: 10000) - let node = ListNode(x: 0) - // 循环中的变量占用 O(1) 空间 - for _ in 0 ..< n { - let c = 0 - } - // 循环中的函数占用 O(1) 空间 - for _ in 0 ..< n { - function() - } - } - ``` - -=== "Zig" - - ```zig title="space_complexity.zig" - // 常数阶 - fn constant(n: i32) void { - // 常量、变量、对象占用 O(1) 空间 - const a: i32 = 0; - var b: i32 = 0; - var nums = [_]i32{0}**10000; - var node = inc.ListNode(i32){.val = 0}; - var i: i32 = 0; - // 循环中的变量占用 O(1) 空间 - while (i < n) : (i += 1) { - var c: i32 = 0; - _ = c; - } - // 循环中的函数占用 O(1) 空间 - i = 0; - while (i < n) : (i += 1) { - _ = function(); - } - _ = a; - _ = b; - _ = nums; - _ = node; - } - ``` - -### 线性阶 $O(n)$ - -线性阶常见于元素数量与 $n$ 成正比的数组、链表、栈、队列等。 - -=== "Java" - - ```java title="space_complexity.java" - /* 线性阶 */ - void linear(int n) { - // 长度为 n 的数组占用 O(n) 空间 - int[] nums = new int[n]; - // 长度为 n 的列表占用 O(n) 空间 - List nodes = new ArrayList<>(); - for (int i = 0; i < n; i++) { - nodes.add(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - Map map = new HashMap<>(); - for (int i = 0; i < n; i++) { - map.put(i, String.valueOf(i)); - } - } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 线性阶 */ - void linear(int n) { - // 长度为 n 的数组占用 O(n) 空间 - vector nums(n); - // 长度为 n 的列表占用 O(n) 空间 - vector nodes; - for (int i = 0; i < n; i++) { - nodes.push_back(ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - unordered_map map; - for (int i = 0; i < n; i++) { - map[i] = to_string(i); - } - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 线性阶 """ - def linear(n): - # 长度为 n 的列表占用 O(n) 空间 - nums = [0] * n - # 长度为 n 的哈希表占用 O(n) 空间 - mapp = {} - for i in range(n): - mapp[i] = str(i) - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 线性阶 */ - func spaceLinear(n int) { - // 长度为 n 的数组占用 O(n) 空间 - _ = make([]int, n) - // 长度为 n 的列表占用 O(n) 空间 - var nodes []*node - for i := 0; i < n; i++ { - nodes = append(nodes, newNode(i)) - } - // 长度为 n 的哈希表占用 O(n) 空间 - m := make(map[int]string, n) - for i := 0; i < n; i++ { - m[i] = strconv.Itoa(i) - } - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - /* 线性阶 */ - function linear(n) { - // 长度为 n 的数组占用 O(n) 空间 - const nums = new Array(n); - // 长度为 n 的列表占用 O(n) 空间 - const nodes = []; - for (let i = 0; i < n; i++) { - nodes.push(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - const map = new Map(); - for (let i = 0; i < n; i++) { - map.set(i, i.toString()); - } - } - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - /* 线性阶 */ - function linear(n: number): void { - // 长度为 n 的数组占用 O(n) 空间 - const nums = new Array(n); - // 长度为 n 的列表占用 O(n) 空间 - const nodes: ListNode[] = []; - for (let i = 0; i < n; i++) { - nodes.push(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - const map = new Map(); - for (let i = 0; i < n; i++) { - map.set(i, i.toString()); - } - } - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 线性阶 */ - void linear(int n) - { - // 长度为 n 的数组占用 O(n) 空间 - int[] nums = new int[n]; - // 长度为 n 的列表占用 O(n) 空间 - List nodes = new(); - for (int i = 0; i < n; i++) - { - nodes.Add(new ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - Dictionary map = new(); - for (int i = 0; i < n; i++) - { - map.Add(i, i.ToString()); - } - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 线性阶 */ - func linear(n: Int) { - // 长度为 n 的数组占用 O(n) 空间 - let nums = Array(repeating: 0, count: n) - // 长度为 n 的列表占用 O(n) 空间 - let nodes = (0 ..< n).map { ListNode(x: $0) } - // 长度为 n 的哈希表占用 O(n) 空间 - let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) - } - ``` - -=== "Zig" - - ```zig title="space_complexity.zig" - // 线性阶 - fn linear(comptime n: i32) !void { - // 长度为 n 的数组占用 O(n) 空间 - var nums = [_]i32{0}**n; - // 长度为 n 的列表占用 O(n) 空间 - var nodes = std.ArrayList(i32).init(std.heap.page_allocator); - defer nodes.deinit(); - var i: i32 = 0; - while (i < n) : (i += 1) { - try nodes.append(i); - } - // 长度为 n 的哈希表占用 O(n) 空间 - var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); - defer map.deinit(); - var j: i32 = 0; - while (j < n) : (j += 1) { - const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); - defer std.heap.page_allocator.free(string); - try map.put(i, string); - } - _ = nums; - } - ``` - -以下递归函数会同时存在 $n$ 个未返回的 `algorithm()` 函数,使用 $O(n)$ 大小的栈帧空间。 - -=== "Java" - - ```java title="space_complexity.java" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - System.out.println("递归 n = " + n); - if (n == 1) return; - linearRecur(n - 1); - } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - cout << "递归 n = " << n << endl; - if (n == 1) return; - linearRecur(n - 1); - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 线性阶(递归实现) """ - def linear_recur(n): - print("递归 n =", n) - if n == 1: return - linear_recur(n - 1) - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 线性阶(递归实现) */ - func spaceLinearRecur(n int) { - fmt.Println("递归 n =", n) - if n == 1 { - return - } - spaceLinearRecur(n - 1) - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - /* 线性阶(递归实现) */ - function linearRecur(n) { - console.log(`递归 n = ${n}`); - if (n === 1) return; - linearRecur(n - 1); - } - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - /* 线性阶(递归实现) */ - function linearRecur(n: number): void { - console.log(`递归 n = ${n}`); - if (n === 1) return; - linearRecur(n - 1); - } - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 线性阶(递归实现) */ - void linearRecur(int n) - { - Console.WriteLine("递归 n = " + n); - if (n == 1) return; - linearRecur(n - 1); - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 线性阶(递归实现) */ - func linearRecur(n: Int) { - print("递归 n = \(n)") - if n == 1 { - return - } - linearRecur(n: n - 1) - } - ``` - -=== "Zig" - - ```zig title="space_complexity.zig" - // 线性阶(递归实现) - fn linearRecur(comptime n: i32) void { - std.debug.print("递归 n = {}\n", .{n}); - if (n == 1) return; - linearRecur(n - 1); - } - ``` - -![space_complexity_recursive_linear](space_complexity.assets/space_complexity_recursive_linear.png) - -

Fig. 递归函数产生的线性阶空间复杂度

- -### 平方阶 $O(n^2)$ - -平方阶常见于元素数量与 $n$ 成平方关系的矩阵、图。 - -=== "Java" - - ```java title="space_complexity.java" - /* 平方阶 */ - void quadratic(int n) { - // 矩阵占用 O(n^2) 空间 - int[][] numMatrix = new int[n][n]; - // 二维列表占用 O(n^2) 空间 - List> numList = new ArrayList<>(); - for (int i = 0; i < n; i++) { - List tmp = new ArrayList<>(); - for (int j = 0; j < n; j++) { - tmp.add(0); - } - numList.add(tmp); - } - } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 平方阶 */ - void quadratic(int n) { - // 二维列表占用 O(n^2) 空间 - vector> numMatrix; - for (int i = 0; i < n; i++) { - vector tmp; - for (int j = 0; j < n; j++) { - tmp.push_back(0); - } - numMatrix.push_back(tmp); - } - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 平方阶 """ - def quadratic(n): - # 二维列表占用 O(n^2) 空间 - num_matrix = [[0] * n for _ in range(n)] - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 平方阶 */ - func spaceQuadratic(n int) { - // 矩阵占用 O(n^2) 空间 - numMatrix := make([][]int, n) - for i := 0; i < n; i++ { - numMatrix[i] = make([]int, n) - } - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - /* 平方阶 */ - function quadratic(n) { - // 矩阵占用 O(n^2) 空间 - const numMatrix = Array(n).fill(null).map(() => Array(n).fill(null)); - // 二维列表占用 O(n^2) 空间 - const numList = []; - for (let i = 0; i < n; i++) { - const tmp = []; - for (let j = 0; j < n; j++) { - tmp.push(0); - } - numList.push(tmp); - } - } - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - /* 平方阶 */ - function quadratic(n: number): void { - // 矩阵占用 O(n^2) 空间 - const numMatrix = Array(n).fill(null).map(() => Array(n).fill(null)); - // 二维列表占用 O(n^2) 空间 - const numList = []; - for (let i = 0; i < n; i++) { - const tmp = []; - for (let j = 0; j < n; j++) { - tmp.push(0); - } - numList.push(tmp); - } - } - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 平方阶 */ - void quadratic(int n) - { - // 矩阵占用 O(n^2) 空间 - int[,] numMatrix = new int[n, n]; - // 二维列表占用 O(n^2) 空间 - List> numList = new(); - for (int i = 0; i < n; i++) - { - List tmp = new(); - for (int j = 0; j < n; j++) - { - tmp.Add(0); - } - numList.Add(tmp); - } - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 平方阶 */ - func quadratic(n: Int) { - // 二维列表占用 O(n^2) 空间 - let numList = Array(repeating: Array(repeating: 0, count: n), count: n) - } - ``` - -=== "Zig" - - ```zig title="space_complexity.zig" - // 平方阶 - fn quadratic(n: i32) !void { - // 二维列表占用 O(n^2) 空间 - var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); - defer nodes.deinit(); - var i: i32 = 0; - while (i < n) : (i += 1) { - var tmp = std.ArrayList(i32).init(std.heap.page_allocator); - defer tmp.deinit(); - var j: i32 = 0; - while (j < n) : (j += 1) { - try tmp.append(0); - } - try nodes.append(tmp); - } - } - ``` - -在以下递归函数中,同时存在 $n$ 个未返回的 `algorithm()` ,并且每个函数中都初始化了一个数组,长度分别为 $n, n-1, n-2, ..., 2, 1$ ,平均长度为 $\frac{n}{2}$ ,因此总体使用 $O(n^2)$ 空间。 - -=== "Java" - - ```java title="space_complexity.java" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) { - if (n <= 0) return 0; - // 数组 nums 长度为 n, n-1, ..., 2, 1 - int[] nums = new int[n]; - System.out.println("递归 n = " + n + " 中的 nums 长度 = " + nums.length); - return quadraticRecur(n - 1); - } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) { - if (n <= 0) return 0; - vector nums(n); - cout << "递归 n = " << n << " 中的 nums 长度 = " << nums.size() << endl; - return quadraticRecur(n - 1); - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 平方阶(递归实现) """ - def quadratic_recur(n): - if n <= 0: return 0 - # 数组 nums 长度为 n, n-1, ..., 2, 1 - nums = [0] * n - return quadratic_recur(n - 1) - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 平方阶(递归实现) */ - func spaceQuadraticRecur(n int) int { - if n <= 0 { - return 0 - } - // 数组 nums 长度为 n, n-1, ..., 2, 1 - nums := make([]int, n) - return spaceQuadraticRecur(n - 1) - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - /* 平方阶(递归实现) */ - function quadraticRecur(n) { - if (n <= 0) return 0; - const nums = new Array(n); - console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`); - return quadraticRecur(n - 1); - } - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - /* 平方阶(递归实现) */ - function quadraticRecur(n: number): number { - if (n <= 0) return 0; - const nums = new Array(n); - console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`); - return quadraticRecur(n - 1); - } - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) - { - if (n <= 0) return 0; - // 数组 nums 长度为 n, n-1, ..., 2, 1 - int[] nums = new int[n]; - return quadraticRecur(n - 1); - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 平方阶(递归实现) */ - func quadraticRecur(n: Int) -> Int { - if n <= 0 { - return 0 - } - // 数组 nums 长度为 n, n-1, ..., 2, 1 - let nums = Array(repeating: 0, count: n) - return quadraticRecur(n: n - 1) - } - ``` - -=== "Zig" - - ```zig title="space_complexity.zig" - // 平方阶(递归实现) - fn quadraticRecur(comptime n: i32) i32 { - if (n <= 0) return 0; - var nums = [_]i32{0}**n; - std.debug.print("递归 n = {} 中的 nums 长度 = {}\n", .{n, nums.len}); - return quadraticRecur(n - 1); - } - ``` - -![space_complexity_recursive_quadratic](space_complexity.assets/space_complexity_recursive_quadratic.png) - -

Fig. 递归函数产生的平方阶空间复杂度

- -### 指数阶 $O(2^n)$ - -指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的结点数量为 $2^n - 1$ ,使用 $O(2^n)$ 空间。 - -=== "Java" - - ```java title="space_complexity.java" - /* 指数阶(建立满二叉树) */ - TreeNode buildTree(int n) { - if (n == 0) return null; - TreeNode root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } - ``` - -=== "C++" - - ```cpp title="space_complexity.cpp" - /* 指数阶(建立满二叉树) */ - TreeNode* buildTree(int n) { - if (n == 0) return nullptr; - TreeNode* root = new TreeNode(0); - root->left = buildTree(n - 1); - root->right = buildTree(n - 1); - return root; - } - ``` - -=== "Python" - - ```python title="space_complexity.py" - """ 指数阶(建立满二叉树) """ - def build_tree(n): - if n == 0: return None - root = TreeNode(0) - root.left = build_tree(n - 1) - root.right = build_tree(n - 1) - return root - ``` - -=== "Go" - - ```go title="space_complexity.go" - /* 指数阶(建立满二叉树) */ - func buildTree(n int) *treeNode { - if n == 0 { - return nil - } - root := newTreeNode(0) - root.left = buildTree(n - 1) - root.right = buildTree(n - 1) - return root - } - ``` - -=== "JavaScript" - - ```js title="space_complexity.js" - /* 指数阶(建立满二叉树) */ - function buildTree(n) { - if (n === 0) return null; - const root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } - ``` - -=== "TypeScript" - - ```typescript title="space_complexity.ts" - /* 指数阶(建立满二叉树) */ - function buildTree(n: number): TreeNode | null { - if (n === 0) return null; - const root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } - ``` - -=== "C" - - ```c title="space_complexity.c" - - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 指数阶(建立满二叉树) */ - TreeNode? buildTree(int n) - { - if (n == 0) return null; - TreeNode root = new TreeNode(0); - root.left = buildTree(n - 1); - root.right = buildTree(n - 1); - return root; - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 指数阶(建立满二叉树) */ - func buildTree(n: Int) -> TreeNode? { - if n == 0 { - return nil - } - let root = TreeNode(x: 0) - root.left = buildTree(n: n - 1) - root.right = buildTree(n: n - 1) - return root - } - ``` - -=== "Zig" - - ```zig title="space_complexity.zig" - // 指数阶(建立满二叉树) - fn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) { - if (n == 0) return null; - const root = try mem_allocator.create(inc.TreeNode(i32)); - root.init(0); - root.left = try buildTree(mem_allocator, n - 1); - root.right = try buildTree(mem_allocator, n - 1); - return root; - } - ``` - -![space_complexity_exponential](space_complexity.assets/space_complexity_exponential.png) - -

Fig. 满二叉树下的指数阶空间复杂度

- -### 对数阶 $O(\log n)$ - -对数阶常见于分治算法、数据类型转换等。 - -例如「归并排序」,长度为 $n$ 的数组可以形成高度为 $\log n$ 的递归树,因此空间复杂度为 $O(\log n)$ 。 - -再例如「数字转化为字符串」,输入任意正整数 $n$ ,它的位数为 $\log_{10} n$ ,即对应字符串长度为 $\log_{10} n$ ,因此空间复杂度为 $O(\log_{10} n) = O(\log n)$ 。 diff --git a/build/chapter_computational_complexity/space_time_tradeoff.md b/build/chapter_computational_complexity/space_time_tradeoff.md deleted file mode 100755 index 552b8d085..000000000 --- a/build/chapter_computational_complexity/space_time_tradeoff.md +++ /dev/null @@ -1,388 +0,0 @@ ---- -comments: true ---- - -# 2.4. 权衡时间与空间 - -理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。 - -**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然**。我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。 - -大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。 - -## 2.4.1. 示例题目 * - -以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例。 - -!!! question "两数之和" - - 给定一个整数数组 `nums` 和一个整数目标值 `target` ,请你在该数组中找出“和”为目标值 `target` 的那两个整数,并返回它们的数组下标。 - - 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 - - 你可以按任意顺序返回答案。 - -「暴力枚举」和「辅助哈希表」分别为 **空间最优** 和 **时间最优** 的两种解法。本着时间比空间更宝贵的原则,后者是本题的最佳解法。 - -### 方法一:暴力枚举 - -时间复杂度 $O(N^2)$ ,空间复杂度 $O(1)$ ,属于「时间换空间」。 - -虽然仅使用常数大小的额外空间,但运行速度过慢。 - -=== "Java" - - ```java title="leetcode_two_sum.java" - /* 方法一:暴力枚举 */ - class SolutionBruteForce { - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; - } - } - return new int[0]; - } - } - ``` - -=== "C++" - - ```cpp title="leetcode_two_sum.cpp" - class SolutionBruteForce { - public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return { i, j }; - } - } - return {}; - } - }; - ``` - -=== "Python" - - ```python title="leetcode_two_sum.py" - """ 方法一:暴力枚举 """ - class SolutionBruteForce: - def twoSum(self, nums: List[int], target: int) -> List[int]: - # 两层循环,时间复杂度 O(n^2) - for i in range(len(nums) - 1): - for j in range(i + 1, len(nums)): - if nums[i] + nums[j] == target: - return i, j - return [] - ``` - -=== "Go" - - ```go title="leetcode_two_sum.go" - func twoSumBruteForce(nums []int, target int) []int { - size := len(nums) - // 两层循环,时间复杂度 O(n^2) - for i := 0; i < size-1; i++ { - for j := i + 1; i < size; j++ { - if nums[i]+nums[j] == target { - return []int{i, j} - } - } - } - return nil - } - ``` - -=== "JavaScript" - - ```js title="leetcode_two_sum.js" - function twoSumBruteForce(nums, target) { - const n = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - if (nums[i] + nums[j] === target) { - return [i, j]; - } - } - } - return []; - } - ``` - -=== "TypeScript" - - ```typescript title="leetcode_two_sum.ts" - function twoSumBruteForce(nums: number[], target: number): number[] { - const n = nums.length; - // 两层循环,时间复杂度 O(n^2) - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - if (nums[i] + nums[j] === target) { - return [i, j]; - } - } - } - return []; - }; - ``` - -=== "C" - - ```c title="leetcode_two_sum.c" - - ``` - -=== "C#" - - ```csharp title="leetcode_two_sum.cs" - class SolutionBruteForce - { - public int[] twoSum(int[] nums, int target) - { - int size = nums.Length; - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) - { - for (int j = i + 1; j < size; j++) - { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; - } - } - return new int[0]; - } - } - ``` - -=== "Swift" - - ```swift title="leetcode_two_sum.swift" - func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { - // 两层循环,时间复杂度 O(n^2) - for i in nums.indices.dropLast() { - for j in nums.indices.dropFirst(i + 1) { - if nums[i] + nums[j] == target { - return [i, j] - } - } - } - return [0] - } - ``` - -=== "Zig" - - ```zig title="leetcode_two_sum.zig" - const SolutionBruteForce = struct { - pub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 { - _ = self; - var size: usize = nums.len; - var i: usize = 0; - // 两层循环,时间复杂度 O(n^2) - while (i < size - 1) : (i += 1) { - var j = i + 1; - while (j < size) : (j += 1) { - if (nums[i] + nums[j] == target) { - return [_]i32{@intCast(i32, i), @intCast(i32, j)}; - } - } - } - return undefined; - } - }; - ``` - -### 方法二:辅助哈希表 - -时间复杂度 $O(N)$ ,空间复杂度 $O(N)$ ,属于「空间换时间」。 - -借助辅助哈希表 dic ,通过保存数组元素与索引的映射来提升算法运行速度。 - -=== "Java" - - ```java title="leetcode_two_sum.java" - /* 方法二:辅助哈希表 */ - class SolutionHashMap { - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 辅助哈希表,空间复杂度 O(n) - Map dic = new HashMap<>(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.containsKey(target - nums[i])) { - return new int[] { dic.get(target - nums[i]), i }; - } - dic.put(nums[i], i); - } - return new int[0]; - } - } - ``` - -=== "C++" - - ```cpp title="leetcode_two_sum.cpp" - class SolutionHashMap { - public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 辅助哈希表,空间复杂度 O(n) - unordered_map dic; - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.find(target - nums[i]) != dic.end()) { - return { dic[target - nums[i]], i }; - } - dic.emplace(nums[i], i); - } - return {}; - } - }; - ``` - -=== "Python" - - ```python title="leetcode_two_sum.py" - """ 方法二:辅助哈希表 """ - class SolutionHashMap: - def twoSum(self, nums: List[int], target: int) -> List[int]: - # 辅助哈希表,空间复杂度 O(n) - dic = {} - # 单层循环,时间复杂度 O(n) - for i in range(len(nums)): - if target - nums[i] in dic: - return dic[target - nums[i]], i - dic[nums[i]] = i - return [] - ``` - -=== "Go" - - ```go title="leetcode_two_sum.go" - func twoSumHashTable(nums []int, target int) []int { - // 辅助哈希表,空间复杂度 O(n) - hashTable := map[int]int{} - // 单层循环,时间复杂度 O(n) - for idx, val := range nums { - if preIdx, ok := hashTable[target-val]; ok { - return []int{preIdx, idx} - } - hashTable[val] = idx - } - return nil - } - ``` - -=== "JavaScript" - - ```js title="leetcode_two_sum.js" - function twoSumHashTable(nums, target) { - // 辅助哈希表,空间复杂度 O(n) - let m = {}; - // 单层循环,时间复杂度 O(n) - for (let i = 0; i < nums.length; i++) { - if (m[nums[i]] !== undefined) { - return [m[nums[i]], i]; - } else { - m[target - nums[i]] = i; - } - } - return []; - } - ``` - -=== "TypeScript" - - ```typescript title="leetcode_two_sum.ts" - function twoSumHashTable(nums: number[], target: number): number[] { - // 辅助哈希表,空间复杂度 O(n) - let m: Map = new Map(); - // 单层循环,时间复杂度 O(n) - for (let i = 0; i < nums.length; i++) { - let index = m.get(nums[i]); - if (index !== undefined) { - return [index, i]; - } else { - m.set(target - nums[i], i); - } - } - return []; - }; - ``` - -=== "C" - - ```c title="leetcode_two_sum.c" - - ``` - -=== "C#" - - ```csharp title="leetcode_two_sum.cs" - class SolutionHashMap - { - public int[] twoSum(int[] nums, int target) - { - int size = nums.Length; - // 辅助哈希表,空间复杂度 O(n) - Dictionary dic = new(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) - { - if (dic.ContainsKey(target - nums[i])) - { - return new int[] { dic[target - nums[i]], i }; - } - dic.Add(nums[i], i); - } - return new int[0]; - } - } - ``` - -=== "Swift" - - ```swift title="leetcode_two_sum.swift" - func twoSumHashTable(nums: [Int], target: Int) -> [Int] { - // 辅助哈希表,空间复杂度 O(n) - var dic: [Int: Int] = [:] - // 单层循环,时间复杂度 O(n) - for i in nums.indices { - if let j = dic[target - nums[i]] { - return [j, i] - } - dic[nums[i]] = i - } - return [0] - } - ``` - -=== "Zig" - - ```zig title="leetcode_two_sum.zig" - const SolutionHashMap = struct { - pub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 { - _ = self; - var size: usize = nums.len; - // 辅助哈希表,空间复杂度 O(n) - var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); - defer dic.deinit(); - var i: usize = 0; - // 单层循环,时间复杂度 O(n) - while (i < size) : (i += 1) { - if (dic.contains(target - nums[i])) { - return [_]i32{dic.get(target - nums[i]).?, @intCast(i32, i)}; - } - try dic.put(nums[i], @intCast(i32, i)); - } - return undefined; - } - }; - ``` diff --git a/build/chapter_computational_complexity/summary.md b/build/chapter_computational_complexity/summary.md deleted file mode 100644 index acd745f9f..000000000 --- a/build/chapter_computational_complexity/summary.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -comments: true ---- - -# 2.5. 小结 - -### 算法效率评估 - -- 「时间效率」和「空间效率」是算法性能的两个重要的评价维度。 -- 我们可以通过「实际测试」来评估算法效率,但难以排除测试环境的干扰,并且非常耗费计算资源。 -- 「复杂度分析」克服了实际测试的弊端,分析结果适用于所有运行平台,并且可以体现不同数据大小下的算法效率。 - -### 时间复杂度 - -- 「时间复杂度」统计算法运行时间随着数据量变大时的增长趋势,可以有效评估算法效率,但在某些情况下可能失效,比如在输入数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣性。 -- 「最差时间复杂度」使用大 $O$ 符号表示,即函数渐近上界,其反映当 $n$ 趋于正无穷时,$T(n)$ 处于何种增长级别。 -- 推算时间复杂度分为两步,首先统计计算操作数量,再判断渐近上界。 -- 常见时间复杂度从小到大排列有 $O(1)$ , $O(\log n)$ , $O(n)$ , $O(n \log n)$ , $O(n^2)$ , $O(2^n)$ , $O(n!)$ 。 -- 某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。时间复杂度分为「最差时间复杂度」和「最佳时间复杂度」,后者几乎不用,因为输入数据需要满足苛刻的条件才能达到最佳情况。 -- 「平均时间复杂度」可以反映在随机数据输入下的算法效率,最贴合实际使用情况下的算法性能。计算平均时间复杂度需要统计输入数据的分布,以及综合后的数学期望。 - -### 空间复杂度 - -- 与时间复杂度的定义类似,「空间复杂度」统计算法占用空间随着数据量变大时的增长趋势。 - -- 算法运行中相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间一般在递归函数中才会影响到空间复杂度。 -- 我们一般只关心「最差空间复杂度」,即统计算法在「最差输入数据」和「最差运行时间点」下的空间复杂度。 -- 常见空间复杂度从小到大排列有 $O(1)$ , $O(\log n)$ , $O(n)$ , $O(n^2)$ , $O(2^n)$ 。 diff --git a/build/chapter_computational_complexity/time_complexity.md b/build/chapter_computational_complexity/time_complexity.md deleted file mode 100755 index e2883be48..000000000 --- a/build/chapter_computational_complexity/time_complexity.md +++ /dev/null @@ -1,2837 +0,0 @@ ---- -comments: true ---- - -# 2.2. 时间复杂度 - -## 2.2.1. 统计算法运行时间 - -运行时间能够直观且准确地体现出算法的效率水平。如果我们想要 **准确预估一段代码的运行时间** ,该如何做呢? - -1. 首先需要 **确定运行平台** ,包括硬件配置、编程语言、系统环境等,这些都会影响到代码的运行效率。 -2. 评估 **各种计算操作的所需运行时间** ,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,打印操作需要 5 ns 等。 -3. 根据代码 **统计所有计算操作的数量** ,并将所有操作的执行时间求和,即可得到运行时间。 - -例如以下代码,输入数据大小为 $n$ ,根据以上方法,可以得到算法运行时间为 $6n + 12$ ns 。 - -$$ -1 + 1 + 10 + (1 + 5) \times n = 6n + 12 -$$ - -=== "Java" - - ```java title="" - // 在某运行平台下 - void algorithm(int n) { - int a = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns - // 循环 n 次 - for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - System.out.println(0); // 5 ns - } - } - ``` - -=== "C++" - - ```cpp title="" - // 在某运行平台下 - void algorithm(int n) { - int a = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns - // 循环 n 次 - for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - cout << 0 << endl; // 5 ns - } - } - ``` - -=== "Python" - - ```python title="" - # 在某运行平台下 - def algorithm(n): - a = 2 # 1 ns - a = a + 1 # 1 ns - a = a * 2 # 10 ns - # 循环 n 次 - for _ in range(n): # 1 ns - print(0) # 5 ns - ``` - -=== "Go" - - ```go title="" - // 在某运行平台下 - func algorithm(n int) { - a := 2 // 1 ns - a = a + 1 // 1 ns - a = a * 2 // 10 ns - // 循环 n 次 - for i := 0; i < n; i++ { // 1 ns - fmt.Println(a) // 5 ns - } - } - ``` - -=== "JavaScript" - - ```js title="" - // 在某运行平台下 - function algorithm(n) { - var a = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns - // 循环 n 次 - for(let i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - console.log(0); // 5 ns - } - } - ``` - -=== "TypeScript" - - ```typescript title="" - // 在某运行平台下 - function algorithm(n: number): void { - var a: number = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns - // 循环 n 次 - for(let i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - console.log(0); // 5 ns - } - } - ``` - -=== "C" - - ```c title="" - // 在某运行平台下 - void algorithm(int n) { - int a = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns - // 循环 n 次 - for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++ - printf("%d", 0); // 5 ns - } - } - ``` - -=== "C#" - - ```csharp title="" - // 在某运行平台下 - void algorithm(int n) - { - int a = 2; // 1 ns - a = a + 1; // 1 ns - a = a * 2; // 10 ns - // 循环 n 次 - for (int i = 0; i < n; i++) - { // 1 ns ,每轮都要执行 i++ - Console.WriteLine(0); // 5 ns - } - } - ``` - -=== "Swift" - - ```swift title="" - // 在某运行平台下 - func algorithm(_ n: Int) { - var a = 2 // 1 ns - a = a + 1 // 1 ns - a = a * 2 // 10 ns - // 循环 n 次 - for _ in 0 ..< n { // 1 ns - print(0) // 5 ns - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 - -## 2.2.2. 统计时间增长趋势 - -「时间复杂度分析」采取了不同的做法,其统计的不是算法运行时间,而是 **算法运行时间随着数据量变大时的增长趋势** 。 - -“时间增长趋势”这个概念比较抽象,我们借助一个例子来理解。设输入数据大小为 $n$ ,给定三个算法 `A` , `B` , `C` 。 - -- 算法 `A` 只有 $1$ 个打印操作,算法运行时间不随着 $n$ 增大而增长。我们称此算法的时间复杂度为「常数阶」。 -- 算法 `B` 中的打印操作需要循环 $n$ 次,算法运行时间随着 $n$ 增大成线性增长。此算法的时间复杂度被称为「线性阶」。 -- 算法 `C` 中的打印操作需要循环 $1000000$ 次,但运行时间仍与输入数据大小 $n$ 无关。因此 `C` 的时间复杂度和 `A` 相同,仍为「常数阶」。 - -=== "Java" - - ```java title="" - // 算法 A 时间复杂度:常数阶 - void algorithm_A(int n) { - System.out.println(0); - } - // 算法 B 时间复杂度:线性阶 - void algorithm_B(int n) { - for (int i = 0; i < n; i++) { - System.out.println(0); - } - } - // 算法 C 时间复杂度:常数阶 - void algorithm_C(int n) { - for (int i = 0; i < 1000000; i++) { - System.out.println(0); - } - } - ``` - -=== "C++" - - ```cpp title="" - // 算法 A 时间复杂度:常数阶 - void algorithm_A(int n) { - cout << 0 << endl; - } - // 算法 B 时间复杂度:线性阶 - void algorithm_B(int n) { - for (int i = 0; i < n; i++) { - cout << 0 << endl; - } - } - // 算法 C 时间复杂度:常数阶 - void algorithm_C(int n) { - for (int i = 0; i < 1000000; i++) { - cout << 0 << endl; - } - } - ``` - -=== "Python" - - ```python title="" - # 算法 A 时间复杂度:常数阶 - def algorithm_A(n): - print(0) - # 算法 B 时间复杂度:线性阶 - def algorithm_B(n): - for _ in range(n): - print(0) - # 算法 C 时间复杂度:常数阶 - def algorithm_C(n): - for _ in range(1000000): - print(0) - ``` - -=== "Go" - - ```go title="" - // 算法 A 时间复杂度:常数阶 - func algorithm_A(n int) { - fmt.Println(0) - } - // 算法 B 时间复杂度:线性阶 - func algorithm_B(n int) { - for i := 0; i < n; i++ { - fmt.Println(0) - } - } - // 算法 C 时间复杂度:常数阶 - func algorithm_C(n int) { - for i := 0; i < 1000000; i++ { - fmt.Println(0) - } - } - ``` - -=== "JavaScript" - - ```js title="" - // 算法 A 时间复杂度:常数阶 - function algorithm_A(n) { - console.log(0); - } - // 算法 B 时间复杂度:线性阶 - function algorithm_B(n) { - for (let i = 0; i < n; i++) { - console.log(0); - } - } - // 算法 C 时间复杂度:常数阶 - function algorithm_C(n) { - for (let i = 0; i < 1000000; i++) { - console.log(0); - } - } - - ``` - -=== "TypeScript" - - ```typescript title="" - // 算法 A 时间复杂度:常数阶 - function algorithm_A(n: number): void { - console.log(0); - } - // 算法 B 时间复杂度:线性阶 - function algorithm_B(n: number): void { - for (let i = 0; i < n; i++) { - console.log(0); - } - } - // 算法 C 时间复杂度:常数阶 - function algorithm_C(n: number): void { - for (let i = 0; i < 1000000; i++) { - console.log(0); - } - } - ``` - -=== "C" - - ```c title="" - // 算法 A 时间复杂度:常数阶 - void algorithm_A(int n) { - printf("%d", 0); - } - // 算法 B 时间复杂度:线性阶 - void algorithm_B(int n) { - for (int i = 0; i < n; i++) { - printf("%d", 0); - } - } - // 算法 C 时间复杂度:常数阶 - void algorithm_C(int n) { - for (int i = 0; i < 1000000; i++) { - printf("%d", 0); - } - } - ``` - -=== "C#" - - ```csharp title="" - // 算法 A 时间复杂度:常数阶 - void algorithm_A(int n) - { - Console.WriteLine(0); - } - // 算法 B 时间复杂度:线性阶 - void algorithm_B(int n) - { - for (int i = 0; i < n; i++) - { - Console.WriteLine(0); - } - } - // 算法 C 时间复杂度:常数阶 - void algorithm_C(int n) - { - for (int i = 0; i < 1000000; i++) - { - Console.WriteLine(0); - } - } - ``` - -=== "Swift" - - ```swift title="" - // 算法 A 时间复杂度:常数阶 - func algorithmA(_ n: Int) { - print(0) - } - - // 算法 B 时间复杂度:线性阶 - func algorithmB(_ n: Int) { - for _ in 0 ..< n { - print(0) - } - } - - // 算法 C 时间复杂度:常数阶 - func algorithmC(_ n: Int) { - for _ in 0 ..< 1000000 { - print(0) - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -![time_complexity_first_example](time_complexity.assets/time_complexity_first_example.png) - -

Fig. 算法 A, B, C 的时间增长趋势

- -相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足? - -**时间复杂度可以有效评估算法效率**。算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 - -**时间复杂度的推算方法更加简便**。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。 - -**时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 - -## 2.2.3. 函数渐近上界 - -设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 - -$$ -T(n) = 3 + 2n -$$ - -=== "Java" - - ```java title="" - void algorithm(int n) { - int a = 1; // +1 - a = a + 1; // +1 - a = a * 2; // +1 - // 循环 n 次 - for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - System.out.println(0); // +1 - } - } - ``` - -=== "C++" - - ```cpp title="" - void algorithm(int n) { - int a = 1; // +1 - a = a + 1; // +1 - a = a * 2; // +1 - // 循环 n 次 - for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - cout << 0 << endl; // +1 - } - } - ``` - -=== "Python" - - ```python title="" - def algorithm(n): - a = 1 # +1 - a = a + 1 # +1 - a = a * 2 # +1 - # 循环 n 次 - for i in range(n): # +1 - print(0) # +1 - ``` - -=== "Go" - - ```go title="" - func algorithm(n int) { - a := 1 // +1 - a = a + 1 // +1 - a = a * 2 // +1 - // 循环 n 次 - for i := 0; i < n; i++ { // +1 - fmt.Println(a) // +1 - } - } - ``` - -=== "JavaScript" - - ```js title="" - function algorithm(n){ - var a = 1; // +1 - a += 1; // +1 - a *= 2; // +1 - // 循环 n 次 - for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++) - console.log(0); // +1 - } - - } - ``` - -=== "TypeScript" - - ```typescript title="" - function algorithm(n: number): void{ - var a: number = 1; // +1 - a += 1; // +1 - a *= 2; // +1 - // 循环 n 次 - for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++) - console.log(0); // +1 - } - - } - ``` - -=== "C" - - ```c title="" - void algorithm(int n) { - int a = 1; // +1 - a = a + 1; // +1 - a = a * 2; // +1 - // 循环 n 次 - for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - printf("%d", 0); // +1 - } - } - ``` - -=== "C#" - - ```csharp title="" - void algorithm(int n) { - int a = 1; // +1 - a = a + 1; // +1 - a = a * 2; // +1 - // 循环 n 次 - for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) - Console.WriteLine(0); // +1 - } - } - ``` - -=== "Swift" - - ```swift title="" - func algorithm(n: Int) { - var a = 1 // +1 - a = a + 1 // +1 - a = a * 2 // +1 - // 循环 n 次 - for _ in 0 ..< n { // +1 - print(0) // +1 - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -$T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得时间复杂度是线性阶。 - -我们将线性阶的时间复杂度记为 $O(n)$ ,这个数学符号被称为「大 $O$ 记号 Big-$O$ Notation」,代表函数 $T(n)$ 的「渐近上界 asymptotic upper bound」。 - -我们要推算时间复杂度,本质上是在计算「操作数量函数 $T(n)$ 」的渐近上界。下面我们先来看看函数渐近上界的数学定义。 - -!!! abstract "函数渐近上界" - - 若存在正实数 $c$ 和实数 $n_0$ ,使得对于所有的 $n > n_0$ ,均有 - $$ - T(n) \leq c \cdot f(n) - $$ - 则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 - $$ - T(n) = O(f(n)) - $$ - -![asymptotic_upper_bound](time_complexity.assets/asymptotic_upper_bound.png) - -

Fig. 函数的渐近上界

- -本质上看,计算渐近上界就是在找一个函数 $f(n)$ ,**使得在 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别(仅相差一个常数项 $c$ 的倍数)**。 - -!!! tip - - 渐近上界的数学味儿有点重,如果你感觉没有完全理解,无需担心,因为在实际使用中我们只需要会推算即可,数学意义可以慢慢领悟。 - -## 2.2.4. 推算方法 - -推算出 $f(n)$ 后,我们就得到时间复杂度 $O(f(n))$ 。那么,如何来确定渐近上界 $f(n)$ 呢?总体分为两步,首先「统计操作数量」,然后「判断渐近上界」。 - -### 1) 统计操作数量 - -对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧: - -1. **跳过数量与 $n$ 无关的操作**。因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。 -2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。 -3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 - -根据以下示例,使用上述技巧前、后的统计结果分别为 - -$$ -\begin{aligned} -T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline -& = 2n^2 + 7n + 3 \newline -T(n) & = n^2 + n & \text{偷懒统计 (o.O)} -\end{aligned} -$$ - -最终,两者都能推出相同的时间复杂度结果,即 $O(n^2)$ 。 - -=== "Java" - - ```java title="" - void algorithm(int n) { - int a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (int i = 0; i < 5 * n + 1; i++) { - System.out.println(0); - } - // +n*n(技巧 3) - for (int i = 0; i < 2 * n; i++) { - for (int j = 0; j < n + 1; j++) { - System.out.println(0); - } - } - } - ``` - -=== "C++" - - ```cpp title="" - void algorithm(int n) { - int a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (int i = 0; i < 5 * n + 1; i++) { - cout << 0 << endl; - } - // +n*n(技巧 3) - for (int i = 0; i < 2 * n; i++) { - for (int j = 0; j < n + 1; j++) { - cout << 0 << endl; - } - } - } - ``` - -=== "Python" - - ```python title="" - def algorithm(n): - a = 1 # +0(技巧 1) - a = a + n # +0(技巧 1) - # +n(技巧 2) - for i in range(5 * n + 1): - print(0) - # +n*n(技巧 3) - for i in range(2 * n): - for j in range(n + 1): - print(0) - ``` - -=== "Go" - - ```go title="" - func algorithm(n int) { - a := 1 // +0(技巧 1) - a = a + n // +0(技巧 1) - // +n(技巧 2) - for i := 0; i < 5 * n + 1; i++ { - fmt.Println(0) - } - // +n*n(技巧 3) - for i := 0; i < 2 * n; i++ { - for j := 0; j < n + 1; j++ { - fmt.Println(0) - } - } - } - ``` - -=== "JavaScript" - - ```js title="" - function algorithm(n) { - let a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (let i = 0; i < 5 * n + 1; i++) { - console.log(0); - } - // +n*n(技巧 3) - for (let i = 0; i < 2 * n; i++) { - for (let j = 0; j < n + 1; j++) { - console.log(0); - } - } - } - ``` - -=== "TypeScript" - - ```typescript title="" - function algorithm(n: number): void { - let a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (let i = 0; i < 5 * n + 1; i++) { - console.log(0); - } - // +n*n(技巧 3) - for (let i = 0; i < 2 * n; i++) { - for (let j = 0; j < n + 1; j++) { - console.log(0); - } - } - } - ``` - -=== "C" - - ```c title="" - void algorithm(int n) { - int a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (int i = 0; i < 5 * n + 1; i++) { - printf("%d", 0); - } - // +n*n(技巧 3) - for (int i = 0; i < 2 * n; i++) { - for (int j = 0; j < n + 1; j++) { - printf("%d", 0); - } - } - } - ``` - -=== "C#" - - ```csharp title="" - void algorithm(int n) - { - int a = 1; // +0(技巧 1) - a = a + n; // +0(技巧 1) - // +n(技巧 2) - for (int i = 0; i < 5 * n + 1; i++) - { - Console.WriteLine(0); - } - // +n*n(技巧 3) - for (int i = 0; i < 2 * n; i++) - { - for (int j = 0; j < n + 1; j++) - { - Console.WriteLine(0); - } - } - } - ``` - -=== "Swift" - - ```swift title="" - func algorithm(n: Int) { - var a = 1 // +0(技巧 1) - a = a + n // +0(技巧 1) - // +n(技巧 2) - for _ in 0 ..< (5 * n + 1) { - print(0) - } - // +n*n(技巧 3) - for _ in 0 ..< (2 * n) { - for _ in 0 ..< (n + 1) { - print(0) - } - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -### 2) 判断渐近上界 - -**时间复杂度由多项式 $T(n)$ 中最高阶的项来决定**。这是因为在 $n$ 趋于无穷大时,最高阶的项将处于主导作用,其它项的影响都可以被忽略。 - -以下表格给出了一些例子,其中有一些夸张的值,是想要向大家强调 **系数无法撼动阶数** 这一结论。在 $n$ 趋于无穷大时,这些常数都是“浮云”。 - -
- -| 操作数量 $T(n)$ | 时间复杂度 $O(f(n))$ | -| ---------------------- | -------------------- | -| $100000$ | $O(1)$ | -| $3n + 2$ | $O(n)$ | -| $2n^2 + 3n + 2$ | $O(n^2)$ | -| $n^3 + 10000n^2$ | $O(n^3)$ | -| $2^n + 10000n^{10000}$ | $O(2^n)$ | - -
- -## 2.2.5. 常见类型 - -设输入数据大小为 $n$ ,常见的时间复杂度类型有(从低到高排列) - -$$ -\begin{aligned} -O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline -\text{常数阶} < \text{对数阶} < \text{线性阶} < \text{线性对数阶} < \text{平方阶} < \text{指数阶} < \text{阶乘阶} -\end{aligned} -$$ - -![time_complexity_common_types](time_complexity.assets/time_complexity_common_types.png) - -

Fig. 时间复杂度的常见类型

- -!!! tip - - 部分示例代码需要一些前置知识,包括数组、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解时间复杂度含义和推算方法上。 - -### 常数阶 $O(1)$ - -常数阶的操作数量与输入数据大小 $n$ 无关,即不随着 $n$ 的变化而变化。 - -对于以下算法,无论操作数量 `size` 有多大,只要与数据大小 $n$ 无关,时间复杂度就仍为 $O(1)$ 。 - -=== "Java" - - ```java title="time_complexity.java" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 常数阶 """ - def constant(n): - count = 0 - size = 100000 - for _ in range(size): - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 常数阶 */ - func constant(n int) int { - count := 0 - size := 100000 - for i := 0; i < size; i++ { - count ++ - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 常数阶 */ - function constant(n) { - let count = 0; - const size = 100000; - for (let i = 0; i < size; i++) count++; - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 常数阶 */ - function constant(n: number): number { - let count = 0; - const size = 100000; - for (let i = 0; i < size; i++) count++; - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - int i = 0; - for (int i = 0; i < size; i++) { - count ++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 常数阶 */ - int constant(int n) - { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 常数阶 */ - func constant(n: Int) -> Int { - var count = 0 - let size = 100000 - for _ in 0 ..< size { - count += 1 - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 常数阶 - fn constant(n: i32) i32 { - _ = n; - var count: i32 = 0; - const size: i32 = 100_000; - var i: i32 = 0; - while(i Int { - var count = 0 - for _ in 0 ..< n { - count += 1 - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 线性阶 - fn linear(n: i32) i32 { - var count: i32 = 0; - var i: i32 = 0; - while (i < n) : (i += 1) { - count += 1; - } - return count; - } - ``` - -「遍历数组」和「遍历链表」等操作,时间复杂度都为 $O(n)$ ,其中 $n$ 为数组或链表的长度。 - -!!! tip - - **数据大小 $n$ 是根据输入数据的类型来确定的**。比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。 - -=== "Java" - - ```java title="time_complexity.java" - /* 线性阶(遍历数组) */ - int arrayTraversal(int[] nums) { - int count = 0; - // 循环次数与数组长度成正比 - for (int num : nums) { - count++; - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 线性阶(遍历数组) */ - int arrayTraversal(vector& nums) { - int count = 0; - // 循环次数与数组长度成正比 - for (int num : nums) { - count++; - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 线性阶(遍历数组)""" - def array_traversal(nums): - count = 0 - # 循环次数与数组长度成正比 - for num in nums: - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 线性阶(遍历数组) */ - func arrayTraversal(nums []int) int { - count := 0 - // 循环次数与数组长度成正比 - for range nums { - count++ - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 线性阶(遍历数组) */ - function arrayTraversal(nums) { - let count = 0; - // 循环次数与数组长度成正比 - for (let i = 0; i < nums.length; i++) { - count++; - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 线性阶(遍历数组) */ - function arrayTraversal(nums: number[]): number { - let count = 0; - // 循环次数与数组长度成正比 - for (let i = 0; i < nums.length; i++) { - count++; - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 线性阶(遍历数组) */ - int arrayTraversal(int *nums, int n) { - int count = 0; - // 循环次数与数组长度成正比 - for (int i = 0; i < n; i++) { - count ++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 线性阶(遍历数组) */ - int arrayTraversal(int[] nums) - { - int count = 0; - // 循环次数与数组长度成正比 - foreach(int num in nums) - { - count++; - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 线性阶(遍历数组) */ - func arrayTraversal(nums: [Int]) -> Int { - var count = 0 - // 循环次数与数组长度成正比 - for _ in nums { - count += 1 - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 线性阶(遍历数组) - fn arrayTraversal(nums: []i32) i32 { - var count: i32 = 0; - // 循环次数与数组长度成正比 - for (nums) |_| { - count += 1; - } - return count; - } - ``` - -### 平方阶 $O(n^2)$ - -平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 $O(n)$ ,总体为 $O(n^2)$ 。 - -=== "Java" - - ```java title="time_complexity.java" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 平方阶 """ - def quadratic(n): - count = 0 - # 循环次数与数组长度成平方关系 - for i in range(n): - for j in range(n): - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 平方阶 */ - func quadratic(n int) int { - count := 0 - // 循环次数与数组长度成平方关系 - for i := 0; i < n; i++ { - for j := 0; j < n; j++ { - count++ - } - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 平方阶 */ - function quadratic(n) { - let count = 0; - // 循环次数与数组长度成平方关系 - for (let i = 0; i < n; i++) { - for (let j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 平方阶 */ - function quadratic(n: number): number { - let count = 0; - // 循环次数与数组长度成平方关系 - for (let i = 0; i < n; i++) { - for (let j = 0; j < n; j++) { - count++; - } - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count ++; - } - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 平方阶 */ - int quadratic(int n) - { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) - { - for (int j = 0; j < n; j++) - { - count++; - } - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 平方阶 */ - func quadratic(n: Int) -> Int { - var count = 0 - // 循环次数与数组长度成平方关系 - for _ in 0 ..< n { - for _ in 0 ..< n { - count += 1 - } - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 平方阶 - fn quadratic(n: i32) i32 { - var count: i32 = 0; - var i: i32 = 0; - // 循环次数与数组长度成平方关系 - while (i < n) : (i += 1) { - var j: i32 = 0; - while (j < n) : (j += 1) { - count += 1; - } - } - return count; - } - ``` - -![time_complexity_constant_linear_quadratic](time_complexity.assets/time_complexity_constant_linear_quadratic.png) - -

Fig. 常数阶、线性阶、平方阶的时间复杂度

- -以「冒泡排序」为例,外层循环 $n - 1$ 次,内层循环 $n-1, n-2, \cdots, 2, 1$ 次,平均为 $\frac{n}{2}$ 次,因此时间复杂度为 $O(n^2)$ 。 - -$$ -O((n - 1) \frac{n}{2}) = O(n^2) -$$ - -=== "Java" - - ```java title="time_complexity.java" - /* 平方阶(冒泡排序) */ - int bubbleSort(int[] nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 平方阶(冒泡排序) */ - int bubbleSort(vector& nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 平方阶(冒泡排序)""" - def bubble_sort(nums): - count = 0 # 计数器 - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(len(nums) - 1, 0, -1): - # 内循环:冒泡操作 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - tmp = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - count += 3 # 元素交换包含 3 个单元操作 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 平方阶(冒泡排序) */ - func bubbleSort(nums []int) int { - count := 0 // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := len(nums) - 1; i > 0; i-- { - // 内循环:冒泡操作 - for j := 0; j < i; j++ { - if nums[j] > nums[j+1] { - // 交换 nums[j] 与 nums[j + 1] - tmp := nums[j] - nums[j] = nums[j+1] - nums[j+1] = tmp - count += 3 // 元素交换包含 3 个单元操作 - } - } - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 平方阶(冒泡排序) */ - function bubbleSort(nums) { - let count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 平方阶(冒泡排序) */ - function bubbleSort(nums: number[]): number { - let count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 平方阶(冒泡排序) */ - int bubbleSort(int *nums, int n) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = n - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums [j + 1]) - { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 平方阶(冒泡排序) */ - int bubbleSort(int[] nums) - { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; i > 0; i--) - { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) - { - if (nums[j] > nums[j + 1]) - { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 平方阶(冒泡排序) */ - func bubbleSort(nums: inout [Int]) -> Int { - var count = 0 // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { - // 内循环:冒泡操作 - for j in 0 ..< i { - if nums[j] > nums[j + 1] { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - count += 3 // 元素交换包含 3 个单元操作 - } - } - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 平方阶(冒泡排序) - fn bubbleSort(nums: []i32) i32 { - var count: i32 = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - var i: i32 = @intCast(i32, nums.len ) - 1; - while (i > 0) : (i -= 1) { - var j: usize = 0; - // 内循环:冒泡操作 - while (j < i) : (j += 1) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - var tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -### 指数阶 $O(2^n)$ - -!!! note - - 生物学科中的“细胞分裂”即是指数阶增长:初始状态为 $1$ 个细胞,分裂一轮后为 $2$ 个,分裂两轮后为 $4$ 个,……,分裂 $n$ 轮后有 $2^n$ 个细胞。 - -指数阶增长得非常快,在实际应用中一般是不能被接受的。若一个问题使用「暴力枚举」求解的时间复杂度是 $O(2^n)$ ,那么一般都需要使用「动态规划」或「贪心算法」等算法来求解。 - -=== "Java" - - ```java title="time_complexity.java" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) { - for (int j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) { - for (int j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 指数阶(循环实现)""" - def exponential(n): - count, base = 0, 1 - # cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for _ in range(n): - for _ in range(base): - count += 1 - base *= 2 - # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 指数阶(循环实现)*/ - func exponential(n int) int { - count, base := 0, 1 - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for i := 0; i < n; i++ { - for j := 0; j < base; j++ { - count++ - } - base *= 2 - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 指数阶(循环实现) */ - function exponential(n) { - let count = 0, - base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (let i = 0; i < n; i++) { - for (let j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 指数阶(循环实现) */ - function exponential(n: number): number { - let count = 0, - base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (let i = 0; i < n; i++) { - for (let j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0; - int bas = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) { - for (int j = 0; j < bas; j++) { - count++; - } - bas *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 指数阶(循环实现) */ - int exponential(int n) - { - int count = 0, bas = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) - { - for (int j = 0; j < bas; j++) - { - count++; - } - bas *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 指数阶(循环实现) */ - func exponential(n: Int) -> Int { - var count = 0 - var base = 1 - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for _ in 0 ..< n { - for _ in 0 ..< base { - count += 1 - } - base *= 2 - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 指数阶(循环实现) - fn exponential(n: i32) i32{ - var count: i32 = 0; - var bas: i32 = 1; - var i: i32 = 0; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - while (i < n) : (i += 1) { - var j: i32 = 0; - while (j < bas) : (j += 1) { - count += 1; - } - bas *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } - ``` - -![time_complexity_exponential](time_complexity.assets/time_complexity_exponential.png) - -

Fig. 指数阶的时间复杂度

- -在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 $n$ 次后停止。 - -=== "Java" - - ```java title="time_complexity.java" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 指数阶(递归实现)""" - def exp_recur(n): - if n == 1: return 1 - return exp_recur(n - 1) + exp_recur(n - 1) + 1 - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 指数阶(递归实现)*/ - func expRecur(n int) int { - if n == 1 { - return 1 - } - return expRecur(n-1) + expRecur(n-1) + 1 - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 指数阶(递归实现) */ - function expRecur(n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 指数阶(递归实现) */ - function expRecur(n: number): number { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 指数阶(递归实现) */ - int expRecur(int n) - { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 指数阶(递归实现) */ - func expRecur(n: Int) -> Int { - if n == 1 { - return 1 - } - return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 指数阶(递归实现) - fn expRecur(n: i32) i32{ - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } - ``` - -### 对数阶 $O(\log n)$ - -对数阶与指数阶正好相反,后者反映“每轮增加到两倍的情况”,而前者反映“每轮缩减到一半的情况”。对数阶仅次于常数阶,时间增长得很慢,是理想的时间复杂度。 - -对数阶常出现于「二分查找」和「分治算法」中,体现“一分为多”、“化繁为简”的算法思想。 - -设输入数据大小为 $n$ ,由于每轮缩减到一半,因此循环次数是 $\log_2 n$ ,即 $2^n$ 的反函数。 - -=== "Java" - - ```java title="time_complexity.java" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 对数阶(循环实现)""" - def logarithmic(n): - count = 0 - while n > 1: - n = n / 2 - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 对数阶(循环实现)*/ - func logarithmic(n float64) int { - count := 0 - for n > 1 { - n = n / 2 - count++ - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 对数阶(循环实现) */ - function logarithmic(n) { - let count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 对数阶(循环实现) */ - function logarithmic(n: number): number { - let count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 对数阶(循环实现) */ - int logarithmic(float n) - { - int count = 0; - while (n > 1) - { - n = n / 2; - count++; - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 对数阶(循环实现) */ - func logarithmic(n: Int) -> Int { - var count = 0 - var n = n - while n > 1 { - n = n / 2 - count += 1 - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 对数阶(循环实现) - fn logarithmic(n: f32) i32 - { - var count: i32 = 0; - var n_var = n; - while (n_var > 1) - { - n_var = n_var / 2; - count +=1; - } - return count; - } - ``` - -![time_complexity_logarithmic](time_complexity.assets/time_complexity_logarithmic.png) - -

Fig. 对数阶的时间复杂度

- -与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 $\log_2 n$ 的递归树。 - -=== "Java" - - ```java title="time_complexity.java" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 对数阶(递归实现)""" - def log_recur(n): - if n <= 1: return 0 - return log_recur(n / 2) + 1 - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 对数阶(递归实现)*/ - func logRecur(n float64) int { - if n <= 1 { - return 0 - } - return logRecur(n/2) + 1 - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 对数阶(递归实现) */ - function logRecur(n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 对数阶(递归实现) */ - function logRecur(n: number): number { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 对数阶(递归实现) */ - int logRecur(float n) - { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 对数阶(递归实现) */ - func logRecur(n: Int) -> Int { - if n <= 1 { - return 0 - } - return logRecur(n: n / 2) + 1 - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 对数阶(递归实现) - fn logRecur(n: f32) i32 - { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -### 线性对数阶 $O(n \log n)$ - -线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 $O(\log n)$ 和 $O(n)$ 。 - -主流排序算法的时间复杂度都是 $O(n \log n )$ ,例如快速排序、归并排序、堆排序等。 - -=== "Java" - - ```java title="time_complexity.java" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 线性对数阶 """ - def linear_log_recur(n): - if n <= 1: return 1 - count = linear_log_recur(n // 2) + \ - linear_log_recur(n // 2) - for _ in range(n): - count += 1 - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 线性对数阶 */ - func linearLogRecur(n float64) int { - if n <= 1 { - return 1 - } - count := linearLogRecur(n/2) + - linearLogRecur(n/2) - for i := 0.0; i < n; i++ { - count++ - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 线性对数阶 */ - function linearLogRecur(n) { - if (n <= 1) return 1; - let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); - for (let i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 线性对数阶 */ - function linearLogRecur(n: number): number { - if (n <= 1) return 1; - let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); - for (let i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count ++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 线性对数阶 */ - int linearLogRecur(float n) - { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) - { - count++; - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 线性对数阶 */ - func linearLogRecur(n: Double) -> Int { - if n <= 1 { - return 1 - } - var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) - for _ in 0 ..< Int(n) { - count += 1 - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 线性对数阶 - fn linearLogRecur(n: f32) i32 - { - if (n <= 1) return 1; - var count: i32 = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - var i: f32 = 0; - while (i < n) : (i += 1) { - count += 1; - } - return count; - } - ``` - -![time_complexity_logarithmic_linear](time_complexity.assets/time_complexity_logarithmic_linear.png) - -

Fig. 线性对数阶的时间复杂度

- -### 阶乘阶 $O(n!)$ - -阶乘阶对应数学上的「全排列」。即给定 $n$ 个互不重复的元素,求其所有可能的排列方案,则方案数量为 - -$$ -n! = n \times (n - 1) \times (n - 2) \times \cdots \times 2 \times 1 -$$ - -阶乘常使用递归实现。例如以下代码,第一层分裂出 $n$ 个,第二层分裂出 $n - 1$ 个,…… ,直至到第 $n$ 层时终止分裂。 - -=== "Java" - - ```java title="time_complexity.java" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "C++" - - ```cpp title="time_complexity.cpp" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "Python" - - ```python title="time_complexity.py" - """ 阶乘阶(递归实现)""" - def factorial_recur(n): - if n == 0: return 1 - count = 0 - # 从 1 个分裂出 n 个 - for _ in range(n): - count += factorial_recur(n - 1) - return count - ``` - -=== "Go" - - ```go title="time_complexity.go" - /* 阶乘阶(递归实现) */ - func factorialRecur(n int) int { - if n == 0 { - return 1 - } - count := 0 - // 从 1 个分裂出 n 个 - for i := 0; i < n; i++ { - count += factorialRecur(n - 1) - } - return count - } - ``` - -=== "JavaScript" - - ```js title="time_complexity.js" - /* 阶乘阶(递归实现) */ - function factorialRecur(n) { - if (n == 0) return 1; - let count = 0; - // 从 1 个分裂出 n 个 - for (let i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "TypeScript" - - ```typescript title="time_complexity.ts" - /* 阶乘阶(递归实现) */ - function factorialRecur(n: number): number { - if (n == 0) return 1; - let count = 0; - // 从 1 个分裂出 n 个 - for (let i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "C" - - ```c title="time_complexity.c" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) { - if (n == 0) return 1; - int count = 0; - for (int i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) - { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) - { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 阶乘阶(递归实现) */ - func factorialRecur(n: Int) -> Int { - if n == 0 { - return 1 - } - var count = 0 - // 从 1 个分裂出 n 个 - for _ in 0 ..< n { - count += factorialRecur(n: n - 1) - } - return count - } - ``` - -=== "Zig" - - ```zig title="time_complexity.zig" - // 阶乘阶(递归实现) - fn factorialRecur(n: i32) i32 { - if (n == 0) return 1; - var count: i32 = 0; - var i: i32 = 0; - // 从 1 个分裂出 n 个 - while (i < n) : (i += 1) { - count += factorialRecur(n - 1); - } - return count; - } - ``` - -![time_complexity_factorial](time_complexity.assets/time_complexity_factorial.png) - -

Fig. 阶乘阶的时间复杂度

- -## 2.2.6. 最差、最佳、平均时间复杂度 - -**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论: - -- 当 `nums = [?, ?, ..., 1]`,即当末尾元素是 $1$ 时,则需完整遍历数组,此时达到 **最差时间复杂度 $O(n)$** ; -- 当 `nums = [1, ?, ?, ...]` ,即当首个数字为 $1$ 时,无论数组多长都不需要继续遍历,此时达到 **最佳时间复杂度 $\Omega(1)$** ; - -「函数渐近上界」使用大 $O$ 记号表示,代表「最差时间复杂度」。与之对应,「函数渐近下界」用 $\Omega$ 记号(Omega Notation)来表示,代表「最佳时间复杂度」。 - -=== "Java" - - ```java title="worst_best_time_complexity.java" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - int[] randomNumbers(int n) { - Integer[] nums = new Integer[n]; - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - Collections.shuffle(Arrays.asList(nums)); - // Integer[] -> int[] - int[] res = new int[n]; - for (int i = 0; i < n; i++) { - res[i] = nums[i]; - } - return res; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(int[] nums) { - for (int i = 0; i < nums.length; i++) { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (nums[i] == 1) - return i; - } - return -1; - } - ``` - -=== "C++" - - ```cpp title="worst_best_time_complexity.cpp" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - vector randomNumbers(int n) { - vector nums(n); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 使用系统时间生成随机种子 - unsigned seed = chrono::system_clock::now().time_since_epoch().count(); - // 随机打乱数组元素 - shuffle(nums.begin(), nums.end(), default_random_engine(seed)); - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(vector& nums) { - for (int i = 0; i < nums.size(); i++) { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (nums[i] == 1) - return i; - } - return -1; - } - ``` - -=== "Python" - - ```python title="worst_best_time_complexity.py" - """ 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """ - def random_numbers(n): - # 生成数组 nums =: 1, 2, 3, ..., n - nums = [i for i in range(1, n + 1)] - # 随机打乱数组元素 - random.shuffle(nums) - return nums - - """ 查找数组 nums 中数字 1 所在索引 """ - def find_one(nums): - for i in range(len(nums)): - # 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - # 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if nums[i] == 1: - return i - return -1 - ``` - -=== "Go" - - ```go title="worst_best_time_complexity.go" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - func randomNumbers(n int) []int { - nums := make([]int, n) - // 生成数组 nums = { 1, 2, 3, ..., n } - for i := 0; i < n; i++ { - nums[i] = i + 1 - } - // 随机打乱数组元素 - rand.Shuffle(len(nums), func(i, j int) { - nums[i], nums[j] = nums[j], nums[i] - }) - return nums - } - - /* 查找数组 nums 中数字 1 所在索引 */ - func findOne(nums []int) int { - for i := 0; i < len(nums); i++ { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if nums[i] == 1 { - return i - } - } - return -1 - } - ``` - -=== "JavaScript" - - ```js title="worst_best_time_complexity.js" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - function randomNumbers(n) { - const nums = Array(n); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (let i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - for (let i = 0; i < n; i++) { - const r = Math.floor(Math.random() * (i + 1)); - const temp = nums[i]; - nums[i] = nums[r]; - nums[r] = temp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - function findOne(nums) { - for (let i = 0; i < nums.length; i++) { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (nums[i] === 1) { - return i; - } - } - return -1; - } - ``` - -=== "TypeScript" - - ```typescript title="worst_best_time_complexity.ts" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - function randomNumbers(n: number): number[] { - const nums = Array(n); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (let i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - for (let i = 0; i < n; i++) { - const r = Math.floor(Math.random() * (i + 1)); - const temp = nums[i]; - nums[i] = nums[r]; - nums[r] = temp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - function findOne(nums: number[]): number { - for (let i = 0; i < nums.length; i++) { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (nums[i] === 1) { - return i; - } - } - return -1; - } - ``` - -=== "C" - - ```c title="worst_best_time_complexity.c" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - int *randomNumbers(int n) { - // 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int) - int *nums = (int *)malloc(n * sizeof(int)); - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) { - nums[i] = i + 1; - } - // 随机打乱数组元素 - for (int i = n - 1; i > 0; i--) { - int j = rand() % (i + 1); - int temp = nums[i]; - nums[i] = nums[j]; - nums[j] = temp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(int *nums, int n) { - for (int i = 0; i < n; i++) { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (nums[i] == 1) return i; - } - return -1; - } - ``` - -=== "C#" - - ```csharp title="worst_best_time_complexity.cs" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - int[] randomNumbers(int n) - { - int[] nums = new int[n]; - // 生成数组 nums = { 1, 2, 3, ..., n } - for (int i = 0; i < n; i++) - { - nums[i] = i + 1; - } - - // 随机打乱数组元素 - for (int i = 0; i < nums.Length; i++) - { - var index = new Random().Next(i, nums.Length); - var tmp = nums[i]; - var ran = nums[index]; - nums[i] = ran; - nums[index] = tmp; - } - return nums; - } - - /* 查找数组 nums 中数字 1 所在索引 */ - int findOne(int[] nums) - { - for (int i = 0; i < nums.Length; i++) - { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (nums[i] == 1) - return i; - } - return -1; - } - ``` - -=== "Swift" - - ```swift title="worst_best_time_complexity.swift" - /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - func randomNumbers(n: Int) -> [Int] { - // 生成数组 nums = { 1, 2, 3, ..., n } - var nums = Array(1 ... n) - // 随机打乱数组元素 - nums.shuffle() - return nums - } - - /* 查找数组 nums 中数字 1 所在索引 */ - func findOne(nums: [Int]) -> Int { - for i in nums.indices { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if nums[i] == 1 { - return i - } - } - return -1 - } - ``` - -=== "Zig" - - ```zig title="worst_best_time_complexity.zig" - // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 - pub fn randomNumbers(comptime n: usize) [n]i32 { - var nums: [n]i32 = undefined; - // 生成数组 nums = { 1, 2, 3, ..., n } - for (nums) |*num, i| { - num.* = @intCast(i32, i) + 1; - } - // 随机打乱数组元素 - const rand = std.crypto.random; - rand.shuffle(i32, &nums); - return nums; - } - - // 查找数组 nums 中数字 1 所在索引 - pub fn findOne(nums: []i32) i32 { - for (nums) |num, i| { - // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) - // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) - if (num == 1) return @intCast(i32, i); - } - return -1; - } - ``` - -!!! tip - - 我们在实际应用中很少使用「最佳时间复杂度」,因为往往只有很小概率下才能达到,会带来一定的误导性。反之,「最差时间复杂度」最为实用,因为它给出了一个“效率安全值”,让我们可以放心地使用算法。 - -从上述示例可以看出,最差或最佳时间复杂度只出现在“特殊分布的数据”中,这些情况的出现概率往往很小,因此并不能最真实地反映算法运行效率。**相对地,「平均时间复杂度」可以体现算法在随机输入数据下的运行效率,用 $\Theta$ 记号(Theta Notation)来表示**。 - -对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 $1$ 出现在任意索引的概率都是相等的,那么算法的平均循环次数则是数组长度的一半 $\frac{n}{2}$ ,平均时间复杂度为 $\Theta(\frac{n}{2}) = \Theta(n)$ 。 - -但在实际应用中,尤其是较为复杂的算法,计算平均时间复杂度比较困难,因为很难简便地分析出在数据分布下的整体数学期望。这种情况下,我们一般使用最差时间复杂度来作为算法效率的评判标准。 - -!!! question "为什么很少看到 $\Theta$ 符号?" - - 实际中我们经常使用「大 $O$ 符号」来表示「平均复杂度」,这样严格意义上来说是不规范的。这可能是因为 $O$ 符号实在是太朗朗上口了。
如果在本书和其他资料中看到类似 **平均时间复杂度 $O(n)$** 的表述,请你直接理解为 $\Theta(n)$ 即可。 diff --git a/build/chapter_data_structure/classification_of_data_structure.md b/build/chapter_data_structure/classification_of_data_structure.md deleted file mode 100644 index 1b8d7b739..000000000 --- a/build/chapter_data_structure/classification_of_data_structure.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -comments: true ---- - -# 3.2. 数据结构分类 - -数据结构主要可根据「逻辑结构」和「物理结构」两种角度进行分类。 - -## 3.2.1. 逻辑结构:线性与非线性 - -**「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 - -我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线性的(例如是网状或树状的),那么就是非线性数据结构。 - -- **线性数据结构**:数组、链表、栈、队列、哈希表; -- **非线性数据结构**:树、图、堆、哈希表; - -![classification_logic_structure](classification_of_data_structure.assets/classification_logic_structure.png) - -

Fig. 线性与非线性数据结构

- -## 3.2.2. 物理结构:连续与离散 - -!!! note - - 若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。 - -**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 - -![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png) - -

Fig. 连续空间存储与离散空间存储

- -**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 - -- **基于数组可实现**:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 $\geq 3$ 的数组)等; -- **基于链表可实现**:栈、队列、哈希表、树、堆、图等; - -基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度。 - -!!! tip - - 数组与链表是其他所有数据结构的“底层积木”,建议读者一定要多花些时间了解。 diff --git a/build/chapter_data_structure/data_and_memory.md b/build/chapter_data_structure/data_and_memory.md deleted file mode 100644 index c10980616..000000000 --- a/build/chapter_data_structure/data_and_memory.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -comments: true ---- - -# 3.1. 数据与内存 - -## 3.1.1. 基本数据类型 - -谈到计算机中的数据,我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但是有一个共同点,即都是由各种基本数据类型构成的。 - -**「基本数据类型」是 CPU 可以直接进行运算的类型,在算法中直接被使用。** - -- 「整数」根据不同的长度分为 byte, short, int, long ,根据算法需求选用,即在满足取值范围的情况下尽量减小内存空间占用; -- 「浮点数」代表小数,根据长度分为 float, double ,同样根据算法的实际需求选用; -- 「字符」在计算机中是以字符集的形式保存的,char 的值实际上是数字,代表字符集中的编号,计算机通过字符集查表来完成编号到字符的转换。占用空间与具体编程语言有关,通常为 2 bytes 或 1 byte ; -- 「布尔」代表逻辑中的 “是” 与 “否” ,其占用空间需要具体根据编程语言确定,通常为 1 byte 或 1 bit ; - -!!! note "字节与比特" - - 1 字节 (byte) = 8 比特 (bit) , 1 比特即最基本的 1 个二进制位 - -

Table. Java 的基本数据类型

- -
- -| 类别 | 符号 | 占用空间 | 取值范围 | 默认值 | -| ------ | ----------- | ----------------- | ---------------------------------------------- | -------------- | -| 整数 | byte | 1 byte | $-2^7$ ~ $2^7 - 1$ ( $-128$ ~ $127$ ) | $0$ | -| | short | 2 bytes | $-2^{15}$ ~ $2^{15} - 1$ | $0$ | -| | **int** | 4 bytes | $-2^{31}$ ~ $2^{31} - 1$ | $0$ | -| | long | 8 bytes | $-2^{63}$ ~ $2^{63} - 1$ | $0$ | -| 浮点数 | **float** | 4 bytes | $-3.4 \times 10^{38}$ ~ $3.4 \times 10^{38}$ | $0.0$ f | -| | double | 8 bytes | $-1.7 \times 10^{308}$ ~ $1.7 \times 10^{308}$ | $0.0$ | -| 字符 | **char** | 2 bytes / 1 byte | $0$ ~ $2^{16} - 1$ | $0$ | -| 布尔 | **boolean(bool)** | 1 byte / 1 bit | $\text{true}$ 或 $\text{false}$ | $\text{false}$ | - -
- -!!! tip - - 以上表格中,加粗项在「算法题」中最为常用。此表格无需硬背,大致理解即可,需要时可以通过查表来回忆。 - -**「基本数据类型」与「数据结构」之间的联系与区别** - -我们知道,数据结构是在计算机中 **组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 - -=== "Java" - - ```java title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int[] numbers = new int[5]; - float[] decimals = new float[5]; - char[] characters = new char[5]; - boolean[] booleans = new boolean[5]; - ``` - -=== "C++" - - ```cpp title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int numbers[5]; - float decimals[5]; - char characters[5]; - bool booleans[5]; - ``` - -=== "Python" - - ```python title="" - """ Python 的 list 可以自由存储各种基本数据类型和对象 """ - list = [0, 0.0, 'a', False] - ``` - -=== "Go" - - ```go title="" - // 使用多种「基本数据类型」来初始化「数组」 - var numbers = [5]int{} - var decimals = [5]float64{} - var characters = [5]byte{} - var booleans = [5]bool{} - ``` - -=== "JavaScript" - - ```js title="" - /* JavaScript 的数组可以自由存储各种基本数据类型和对象 */ - const array = [0, 0.0, 'a', false]; - ``` - -=== "TypeScript" - - ```typescript title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - const numbers: number[] = []; - const characters: string[] = []; - const booleans: boolean[] = []; - ``` - -=== "C" - - ```c title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int numbers[10]; - float decimals[10]; - char characters[10]; - bool booleans[10]; - - ``` - -=== "C#" - - ```csharp title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - int[] numbers = new int[5]; - float[] decimals = new float[5]; - char[] characters = new char[5]; - bool[] booleans = new bool[5]; - ``` - -=== "Swift" - - ```swift title="" - /* 使用多种「基本数据类型」来初始化「数组」 */ - let numbers = Array(repeating: Int(), count: 5) - let decimals = Array(repeating: Double(), count: 5) - let characters = Array(repeating: Character("a"), count: 5) - let booleans = Array(repeating: Bool(), count: 5) - ``` - -=== "Zig" - - ```zig title="" - - ``` - -## 3.1.2. 计算机内存 - -在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。 - -**算法运行中,相关数据都被存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 - -**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 - -![computer_memory_location](data_and_memory.assets/computer_memory_location.png) - -

Fig. 内存条、内存空间、内存地址

- -**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 diff --git a/build/chapter_data_structure/summary.md b/build/chapter_data_structure/summary.md deleted file mode 100644 index 5340746a3..000000000 --- a/build/chapter_data_structure/summary.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -comments: true ---- - -# 3.3. 小结 - -- 整数 byte, short, int, long 、浮点数 float, double 、字符 char 、布尔 boolean 是计算机中的基本数据类型,占用空间的大小决定了它们的取值范围。 -- 在程序运行时,数据存储在计算机的内存中。内存中每块空间都有独立的内存地址,程序是通过内存地址来访问数据的。 -- 数据结构主要可以从逻辑结构和物理结构两个角度进行分类。逻辑结构反映了数据中元素之间的逻辑关系,物理结构反映了数据在计算机内存中的存储形式。 -- 常见的逻辑结构有线性、树状、网状等。我们一般根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。根据实现方式的不同,哈希表可能是线性或非线性。 -- 物理结构主要有两种,分别是连续空间存储(数组)和离散空间存储(链表),所有的数据结构都是由数组、或链表、或两者组合实现的。 diff --git a/build/chapter_graph/graph.md b/build/chapter_graph/graph.md deleted file mode 100644 index 5f8d861e5..000000000 --- a/build/chapter_graph/graph.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -comments: true ---- - -# 9.1. 图 - -「图 Graph」是一种非线性数据结构,由「顶点 Vertex」和「边 Edge」组成。我们可将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。例如,以下表示一个包含 5 个顶点和 7 条边的图 - -$$ -\begin{aligned} -V & = \{ 1, 2, 3, 4, 5 \} \newline -E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline -G & = \{ V, E \} \newline -\end{aligned} -$$ - -![linkedlist_tree_graph](graph.assets/linkedlist_tree_graph.png) - -那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。**相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂**。 - -## 9.1.1. 图常见类型 - -根据边是否有方向,分为「无向图 Undirected Graph」和「有向图 Directed Graph」。 - -- 在无向图中,边表示两结点之间“双向”的连接关系,例如微信或 QQ 中的“好友关系”; -- 在有向图中,边是有方向的,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系; - -![directed_graph](graph.assets/directed_graph.png) - -根据所有顶点是否连通,分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。 - -- 对于连通图,从某个结点出发,可以到达其余任意结点; -- 对于非连通图,从某个结点出发,至少有一个结点无法到达; - -![connected_graph](graph.assets/connected_graph.png) - -我们可以给边添加“权重”变量,得到「有权图 Weighted Graph」。例如,在王者荣耀等游戏中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以使用有权图来表示。 - -![weighted_graph](graph.assets/weighted_graph.png) - -## 9.1.2. 图常用术语 - -- 「邻接 Adjacency」:当两顶点之间有边相连时,称此两顶点“邻接”。 -- 「路径 Path」:从顶点 A 到顶点 B 走过的边构成的序列,被称为从 A 到 B 的“路径”。 -- 「度 Degree」表示一个顶点具有多少条边。对于有向图,「入度 In-Degree」表示有多少条边指向该顶点,「出度 Out-Degree」表示有多少条边从该顶点指出。 - -## 9.1.3. 图的表示 - -图的常用表示方法有「邻接矩阵」和「邻接表」。以下使用「无向图」来举例。 - -### 邻接矩阵 - -设图的顶点数量为 $n$ ,「邻接矩阵 Adjacency Matrix」使用一个 $n \times n$ 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,使用 $1$ 或 $0$ 来表示两个顶点之间有边或无边。 - -![adjacency_matrix](graph.assets/adjacency_matrix.png) - -邻接矩阵具有以下性质: - -- 顶点不能与自身相连,因而邻接矩阵主对角线元素没有意义。 -- 「无向图」两个方向的边等价,此时邻接矩阵关于主对角线对称。 -- 将邻接矩阵的元素从 $1$ , $0$ 替换为权重,则能够表示「有权图」。 - -使用邻接矩阵表示图时,我们可以直接通过访问矩阵元素来获取边,因此增删查操作的效率很高,时间复杂度均为 $O(1)$ 。然而,矩阵的空间复杂度为 $O(n^2)$ ,内存占用较大。 - -### 邻接表 - -「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表结点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了所有与该顶点相连的顶点。 - -![adjacency_list](graph.assets/adjacency_list.png) - -邻接表仅存储存在的边,而边的总数往往远小于 $n^2$ ,因此更加节省空间。但是,因为在邻接表中需要通过遍历链表来查找边,所以其时间效率不如邻接矩阵。 - -观察上图发现,**邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率**。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 $O(n)$ 优化至 $O(\log n)$ ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 $O(1)$ ,。 - -## 9.1.4. 图常见应用 - -现实中的许多系统都可以使用图来建模,对应的待求解问题也可以被约化为图计算问题。 - -
- -| | 顶点 | 边 | 图计算问题 | -| -------- | ---- | -------------------- | ------------ | -| 社交网络 | 用户 | 好友关系 | 潜在好友推荐 | -| 地铁线路 | 站点 | 站点间的连通性 | 最短路线推荐 | -| 太阳系 | 星体 | 星体间的万有引力作用 | 行星轨道计算 | - -
diff --git a/build/chapter_graph/graph_operations.md b/build/chapter_graph/graph_operations.md deleted file mode 100644 index 6800193e0..000000000 --- a/build/chapter_graph/graph_operations.md +++ /dev/null @@ -1,681 +0,0 @@ ---- -comments: true ---- - -# 9.2. 图基础操作 - -图的基础操作分为对「边」的操作和对「顶点」的操作,在「邻接矩阵」和「邻接表」这两种表示下的实现方式不同。 - -## 9.2.1. 基于邻接矩阵的实现 - -设图的顶点总数为 $n$ ,则有: - -- **添加或删除边**:直接在邻接矩阵中修改指定边的对应元素即可,使用 $O(1)$ 时间。而由于是无向图,因此需要同时更新两个方向的边。 -- **添加顶点**:在邻接矩阵的尾部添加一行一列,并全部填 $0$ 即可,使用 $O(n)$ 时间。 -- **删除顶点**:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 $(n-1)^2$ 个元素“向左上移动”,从而使用 $O(n^2)$ 时间。 -- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。 - -=== "初始化邻接矩阵" - ![adjacency_matrix_initialization](graph_operations.assets/adjacency_matrix_initialization.png) - -=== "添加边" - ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_add_edge.png) - -=== "删除边" - ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_remove_edge.png) - -=== "添加顶点" - ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_add_vertex.png) - -=== "删除顶点" - ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_remove_vertex.png) - -以下是基于邻接矩阵表示图的实现代码。 - -=== "Java" - - ```java title="graph_adjacency_matrix.java" - /* 基于邻接矩阵实现的无向图类 */ - class GraphAdjMat { - List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” - - /* 构造函数 */ - public GraphAdjMat(int[] vertices, int[][] edges) { - this.vertices = new ArrayList<>(); - this.adjMat = new ArrayList<>(); - // 添加顶点 - for (int val : vertices) { - addVertex(val); - } - // 添加边 - // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - for (int[] e : edges) { - addEdge(e[0], e[1]); - } - } - - /* 获取顶点数量 */ - public int size() { - return vertices.size(); - } - - /* 添加顶点 */ - public void addVertex(int val) { - int n = size(); - // 向顶点列表中添加新顶点的值 - vertices.add(val); - // 在邻接矩阵中添加一行 - List newRow = new ArrayList<>(n); - for (int j = 0; j < n; j++) { - newRow.add(0); - } - adjMat.add(newRow); - // 在邻接矩阵中添加一列 - for (List row : adjMat) { - row.add(0); - } - } - - /* 删除顶点 */ - public void removeVertex(int index) { - if (index >= size()) - throw new IndexOutOfBoundsException(); - // 在顶点列表中移除索引 index 的顶点 - vertices.remove(index); - // 在邻接矩阵中删除索引 index 的行 - adjMat.remove(index); - // 在邻接矩阵中删除索引 index 的列 - for (List row : adjMat) { - row.remove(index); - } - } - - /* 添加边 */ - // 参数 i, j 对应 vertices 元素索引 - public void addEdge(int i, int j) { - // 索引越界与相等处理 - if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) - throw new IndexOutOfBoundsException(); - // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) - adjMat.get(i).set(j, 1); - adjMat.get(j).set(i, 1); - } - - /* 删除边 */ - // 参数 i, j 对应 vertices 元素索引 - public void removeEdge(int i, int j) { - // 索引越界与相等处理 - if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) - throw new IndexOutOfBoundsException(); - adjMat.get(i).set(j, 0); - adjMat.get(j).set(i, 0); - } - - /* 打印邻接矩阵 */ - public void print() { - System.out.print("顶点列表 = "); - System.out.println(vertices); - System.out.println("邻接矩阵 ="); - PrintUtil.printMatrix(adjMat); - } - } - ``` - -=== "C++" - - ```cpp title="graph_adjacency_matrix.cpp" - - ``` - -=== "Python" - - ```python title="graph_adjacency_matrix.py" - - ``` - -=== "Go" - - ```go title="graph_adjacency_matrix.go" - /* 基于邻接矩阵实现的无向图类 */ - type graphAdjMat struct { - // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - vertices []int - // 邻接矩阵,行列索引对应“顶点索引” - adjMat [][]int - } - - func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { - // 添加顶点 - n := len(vertices) - adjMat := make([][]int, n) - for i := range adjMat { - adjMat[i] = make([]int, n) - } - // 初始化图 - g := &graphAdjMat{ - vertices: vertices, - adjMat: adjMat, - } - // 添加边 - // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - for i := range edges { - g.addEdge(edges[i][0], edges[i][1]) - } - return g - } - - /* 获取顶点数量 */ - func (g *graphAdjMat) size() int { - return len(g.vertices) - } - - /* 添加顶点 */ - func (g *graphAdjMat) addVertex(val int) { - n := g.size() - // 向顶点列表中添加新顶点的值 - g.vertices = append(g.vertices, val) - // 在邻接矩阵中添加一行 - newRow := make([]int, n) - g.adjMat = append(g.adjMat, newRow) - // 在邻接矩阵中添加一列 - for i := range g.adjMat { - g.adjMat[i] = append(g.adjMat[i], 0) - } - } - - /* 删除顶点 */ - func (g *graphAdjMat) removeVertex(index int) { - if index >= g.size() { - return - } - // 在顶点列表中移除索引 index 的顶点 - g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) - // 在邻接矩阵中删除索引 index 的行 - g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) - // 在邻接矩阵中删除索引 index 的列 - for i := range g.adjMat { - g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) - } - } - - /* 添加边 */ - // 参数 i, j 对应 vertices 元素索引 - func (g *graphAdjMat) addEdge(i, j int) { - // 索引越界与相等处理 - if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { - fmt.Errorf("%s", "Index Out Of Bounds Exception") - } - // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) - g.adjMat[i][j] = 1 - g.adjMat[j][i] = 1 - } - - /* 删除边 */ - // 参数 i, j 对应 vertices 元素索引 - func (g *graphAdjMat) removeEdge(i, j int) { - // 索引越界与相等处理 - if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { - fmt.Errorf("%s", "Index Out Of Bounds Exception") - } - g.adjMat[i][j] = 0 - g.adjMat[j][i] = 0 - } - ``` - -=== "JavaScript" - - ```js title="graph_adjacency_matrix.js" - - ``` - -=== "TypeScript" - - ```typescript title="graph_adjacency_matrix.ts" - - ``` - -=== "C" - - ```c title="graph_adjacency_matrix.c" - - ``` - -=== "C#" - - ```csharp title="graph_adjacency_matrix.cs" - - ``` - -=== "Swift" - - ```swift title="graph_adjacency_matrix.swift" - /* 基于邻接矩阵实现的无向图类 */ - class GraphAdjMat { - private var vertices: [Int] // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - private var adjMat: [[Int]] // 邻接矩阵,行列索引对应“顶点索引” - - /* 构造函数 */ - init(vertices: [Int], edges: [[Int]]) { - self.vertices = [] - adjMat = [] - // 添加顶点 - for val in vertices { - addVertex(val: val) - } - // 添加边 - // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - for e in edges { - addEdge(i: e[0], j: e[1]) - } - } - - /* 获取顶点数量 */ - func size() -> Int { - vertices.count - } - - /* 添加顶点 */ - func addVertex(val: Int) { - let n = size() - // 向顶点列表中添加新顶点的值 - vertices.append(val) - // 在邻接矩阵中添加一行 - let newRow = Array(repeating: 0, count: n) - adjMat.append(newRow) - // 在邻接矩阵中添加一列 - for i in adjMat.indices { - adjMat[i].append(0) - } - } - - /* 删除顶点 */ - func removeVertex(index: Int) { - if index >= size() { - fatalError("越界") - } - // 在顶点列表中移除索引 index 的顶点 - vertices.remove(at: index) - // 在邻接矩阵中删除索引 index 的行 - adjMat.remove(at: index) - // 在邻接矩阵中删除索引 index 的列 - for i in adjMat.indices { - adjMat[i].remove(at: index) - } - } - - /* 添加边 */ - // 参数 i, j 对应 vertices 元素索引 - func addEdge(i: Int, j: Int) { - // 索引越界与相等处理 - if i < 0 || j < 0 || i >= size() || j >= size() || i == j { - fatalError("越界") - } - // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) - adjMat[i][j] = 1 - adjMat[j][i] = 1 - } - - /* 删除边 */ - // 参数 i, j 对应 vertices 元素索引 - func removeEdge(i: Int, j: Int) { - // 索引越界与相等处理 - if i < 0 || j < 0 || i >= size() || j >= size() || i == j { - fatalError("越界") - } - adjMat[i][j] = 0 - adjMat[j][i] = 0 - } - } - ``` - -=== "Zig" - - ```zig title="graph_adjacency_matrix.zig" - - ``` - -## 9.2.2. 基于邻接表的实现 - -设图的顶点总数为 $n$ 、边总数为 $m$ ,则有: - -- **添加边**:在顶点对应链表的尾部添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。 -- **删除边**:在顶点对应链表中查询与删除指定边,使用 $O(m)$ 时间。与添加边一样,需要同时删除两个方向的边。 -- **添加顶点**:在邻接表中添加一个链表即可,并以新增顶点为链表头结点,使用 $O(1)$ 时间。 -- **删除顶点**:需要遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(n + m)$ 时间。 -- **初始化**:需要在邻接表中建立 $n$ 个结点和 $2m$ 条边,使用 $O(n + m)$ 时间。 - -=== "初始化邻接表" - ![adjacency_list_initialization](graph_operations.assets/adjacency_list_initialization.png) - -=== "添加边" - ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_add_edge.png) - -=== "删除边" - ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_remove_edge.png) - -=== "添加顶点" - ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_add_vertex.png) - -=== "删除顶点" - ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_remove_vertex.png) - -基于邻接表实现图的代码如下所示。 - -=== "Java" - - ```java title="graph_adjacency_list.java" - /* 顶点类 */ - class Vertex { - int val; - public Vertex(int val) { - this.val = val; - } - } - - /* 基于邻接表实现的无向图类 */ - class GraphAdjList { - // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象 - Map> adjList; // 邻接表(使用哈希表实现) - - /* 构造函数 */ - public GraphAdjList(Vertex[][] edges) { - this.adjList = new HashMap<>(); - // 添加所有顶点和边 - for (Vertex[] edge : edges) { - addVertex(edge[0]); - addVertex(edge[1]); - addEdge(edge[0], edge[1]); - } - } - - /* 获取顶点数量 */ - public int size() { - return adjList.size(); - } - - /* 添加边 */ - public void addEdge(Vertex vet1, Vertex vet2) { - if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) - throw new IllegalArgumentException(); - // 添加边 vet1 - vet2 - adjList.get(vet1).add(vet2); - adjList.get(vet2).add(vet1); - } - - /* 删除边 */ - public void removeEdge(Vertex vet1, Vertex vet2) { - if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) - throw new IllegalArgumentException(); - // 删除边 vet1 - vet2 - adjList.get(vet1).remove(vet2); - adjList.get(vet2).remove(vet1); - } - - /* 添加顶点 */ - public void addVertex(Vertex vet) { - if (adjList.containsKey(vet)) - return; - // 在邻接表中添加一个新链表(即 HashSet) - adjList.put(vet, new HashSet<>()); - } - - /* 删除顶点 */ - public void removeVertex(Vertex vet) { - if (!adjList.containsKey(vet)) - throw new IllegalArgumentException(); - // 在邻接表中删除顶点 vet 对应的链表(即 HashSet) - adjList.remove(vet); - // 遍历其它顶点的链表(即 HashSet),删除所有包含 vet 的边 - for (Set set : adjList.values()) { - set.remove(vet); - } - } - - /* 打印邻接表 */ - public void print() { - System.out.println("邻接表 ="); - for (Map.Entry> entry : adjList.entrySet()) { - List tmp = new ArrayList<>(); - for (Vertex vertex : entry.getValue()) - tmp.add(vertex.val); - System.out.println(entry.getKey().val + ": " + tmp + ","); - } - } - } - ``` - -=== "C++" - - ```cpp title="graph_adjacency_list.cpp" - - ``` - -=== "Python" - - ```python title="graph_adjacency_list.py" - - ``` - -=== "Go" - - ```go title="graph_adjacency_list.go" - /* 顶点类 */ - type vertex struct { - val int - } - - func newVertex(val int) vertex { - return vertex{ - val: val, - } - } - - /* 基于邻接表实现的无向图类 */ - type graphAdjList struct { - // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象 - // 邻接表(使用哈希表实现), 使用哈希表模拟集合 - adjList map[vertex]map[vertex]struct{} - } - - /* 构造函数 */ - func newGraphAdjList(edges [][]vertex) *graphAdjList { - g := &graphAdjList{ - adjList: make(map[vertex]map[vertex]struct{}), - } - // 添加所有顶点和边 - for _, edge := range edges { - g.addVertex(edge[0]) - g.addVertex(edge[1]) - g.addEdge(edge[0], edge[1]) - } - return g - } - - /* 获取顶点数量 */ - func (g *graphAdjList) size() int { - return len(g.adjList) - } - - /* 添加边 */ - func (g *graphAdjList) addEdge(vet1 vertex, vet2 vertex) { - _, ok1 := g.adjList[vet1] - _, ok2 := g.adjList[vet2] - if !ok1 || !ok2 || vet1 == vet2 { - panic("error") - } - // 添加边 vet1 - vet2, 添加匿名 struct{}, - g.adjList[vet1][vet2] = struct{}{} - g.adjList[vet2][vet1] = struct{}{} - } - - /* 删除边 */ - func (g *graphAdjList) removeEdge(vet1 vertex, vet2 vertex) { - _, ok1 := g.adjList[vet1] - _, ok2 := g.adjList[vet2] - if !ok1 || !ok2 || vet1 == vet2 { - panic("error") - } - // 删除边 vet1 - vet2, 借助 delete 来删除 map 中的键 - delete(g.adjList[vet1], vet2) - delete(g.adjList[vet2], vet1) - } - - /* 添加顶点 */ - func (g *graphAdjList) addVertex(vet vertex) { - _, ok := g.adjList[vet] - if ok { - return - } - // 在邻接表中添加一个新链表(即 set) - g.adjList[vet] = make(map[vertex]struct{}) - } - - /* 删除顶点 */ - func (g *graphAdjList) removeVertex(vet vertex) { - _, ok := g.adjList[vet] - if !ok { - panic("error") - } - // 在邻接表中删除顶点 vet 对应的链表 - delete(g.adjList, vet) - // 遍历其它顶点的链表(即 Set),删除所有包含 vet 的边 - for _, set := range g.adjList { - // 操作 - delete(set, vet) - } - } - ``` - -=== "JavaScript" - - ```js title="graph_adjacency_list.js" - - ``` - -=== "TypeScript" - - ```typescript title="graph_adjacency_list.ts" - - ``` - -=== "C" - - ```c title="graph_adjacency_list.c" - - ``` - -=== "C#" - - ```csharp title="graph_adjacency_list.cs" - - ``` - -=== "Swift" - - ```swift title="graph_adjacency_list.swift" - /* 顶点类 */ - class Vertex: Hashable { - var val: Int - - init(val: Int) { - self.val = val - } - - static func == (lhs: Vertex, rhs: Vertex) -> Bool { - lhs.val == rhs.val - } - - func hash(into hasher: inout Hasher) { - hasher.combine(val) - } - } - - /* 基于邻接表实现的无向图类 */ - class GraphAdjList { - // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象 - private var adjList: [Vertex: Set] // 邻接表(使用哈希表实现) - - init(edges: [[Vertex]]) { - adjList = [:] - // 添加所有顶点和边 - for edge in edges { - addVertex(vet: edge[0]) - addVertex(vet: edge[1]) - addEdge(vet1: edge[0], vet2: edge[1]) - } - } - - /* 获取顶点数量 */ - func size() -> Int { - adjList.count - } - - /* 添加边 */ - func addEdge(vet1: Vertex, vet2: Vertex) { - if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { - fatalError("参数错误") - } - // 添加边 vet1 - vet2 - adjList[vet1]?.insert(vet2) - adjList[vet2]?.insert(vet1) - } - - /* 删除边 */ - func removeEdge(vet1: Vertex, vet2: Vertex) { - if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { - fatalError("参数错误") - } - // 删除边 vet1 - vet2 - adjList[vet1]?.remove(vet2) - adjList[vet2]?.remove(vet1) - } - - /* 添加顶点 */ - func addVertex(vet: Vertex) { - if adjList[vet] != nil { - return - } - // 在邻接表中添加一个新链表(即 HashSet) - adjList[vet] = [] - } - - /* 删除顶点 */ - func removeVertex(vet: Vertex) { - if adjList[vet] == nil { - fatalError("参数错误") - } - // 在邻接表中删除顶点 vet 对应的链表(即 HashSet) - adjList.removeValue(forKey: vet) - // 遍历其它顶点的链表(即 HashSet),删除所有包含 vet 的边 - for key in adjList.keys { - adjList[key]?.remove(vet) - } - } - } - ``` - -=== "Zig" - - ```zig title="graph_adjacency_list.zig" - - ``` - -## 9.2.3. 效率对比 - -设图中共有 $n$ 个顶点和 $m$ 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。 - -
- -| | 邻接矩阵 | 邻接表(链表) | 邻接表(哈希表) | -| ------------ | -------- | -------------- | ---------------- | -| 判断是否邻接 | $O(1)$ | $O(m)$ | $O(1)$ | -| 添加边 | $O(1)$ | $O(1)$ | $O(1)$ | -| 删除边 | $O(1)$ | $O(m)$ | $O(1)$ | -| 添加顶点 | $O(n)$ | $O(1)$ | $O(1)$ | -| 删除顶点 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | -| 内存空间占用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | - -
- -观察上表,貌似邻接表(哈希表)的时间与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需要一次数组访问或赋值操作即可。总结以上,**邻接矩阵体现“以空间换时间”,邻接表体现“以时间换空间”**。 diff --git a/build/chapter_hashing/hash_collision.md b/build/chapter_hashing/hash_collision.md deleted file mode 100644 index 269254c6c..000000000 --- a/build/chapter_hashing/hash_collision.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -comments: true ---- - -# 6.2. 哈希冲突 - -理想情况下,哈希函数应该为每个输入产生唯一的输出,使得 key 和 value 一一对应。而实际上,往往存在向哈希函数输入不同的 key 而产生相同输出的情况,这种情况被称为「哈希冲突 Hash Collision」。哈希冲突会导致查询结果错误,从而严重影响哈希表的可用性。 - -那么,为什么会出现哈希冲突呢?本质上看,**由于哈希函数的输入空间往往远大于输出空间**,因此不可避免地会出现多个输入产生相同输出的情况,即为哈希冲突。比如,输入空间是全体整数,输出空间是一个固定大小的桶(数组)的索引范围,那么必定会有多个整数同时映射到一个桶索引。 - -为了缓解哈希冲突,一方面,我们可以通过「哈希表扩容」来减小冲突概率。极端情况下,当输入空间和输出空间大小相等时,哈希表就等价于数组了,可谓“大力出奇迹”。 - -另一方面,**考虑通过优化数据结构以缓解哈希冲突**,常见的方法有「链式地址」和「开放寻址」。 - -## 6.2.1. 哈希表扩容 - -「负载因子 Load Factor」定义为 **哈希表中元素数量除以桶槽数量(即数组大小)**,代表哈希冲突的严重程度。 - -**负载因子常用作哈希表扩容的触发条件**。比如在 Java 中,当负载因子 $> 0.75$ 时则触发扩容,将 HashMap 大小扩充至原先的 $2$ 倍。 - -与数组扩容类似,**哈希表扩容操作的开销很大**,因为需要将所有键值对从原哈希表依次移动至新哈希表。 - -## 6.2.2. 链式地址 - -在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。**考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中**。 - -![hash_collision_chaining](hash_collision.assets/hash_collision_chaining.png) - -链式地址下,哈希表操作方法为: - -- **查询元素**:先将 key 输入到哈希函数得到桶内索引,即可访问链表头结点,再通过遍历链表查找对应 value 。 -- **添加元素**:先通过哈希函数访问链表头部,再将结点(即键值对)添加到链表头部即可。 -- **删除元素**:同样先根据哈希函数结果访问链表头部,再遍历链表查找对应结点,删除之即可。 - -链式地址虽然解决了哈希冲突问题,但仍存在局限性,包括: - -- **占用空间变大**,因为链表或二叉树包含结点指针,相比于数组更加耗费内存空间; -- **查询效率降低**,因为需要线性遍历链表来查找对应元素; - -为了缓解时间效率问题,**可以把「链表」转化为「AVL 树」或「红黑树」**,将查询操作的时间复杂度优化至 $O(\log n)$ 。 - -## 6.2.3. 开放寻址 - -「开放寻址」不引入额外数据结构,而是通过“多次探测”来解决哈希冲突。根据探测方法的不同,主要分为 **线性探测、平方探测、多次哈希**。 - -### 线性探测 - -「线性探测」使用固定步长的线性查找来解决哈希冲突。 - -**插入元素**:如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。 - -**查找元素**:若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况: - -1. 找到对应元素,返回 value 即可; -2. 若遇到空位,则说明查找键值对不在哈希表中; - -![hash_collision_linear_probing](hash_collision.assets/hash_collision_linear_probing.png) - -线性探测存在以下缺陷: - -- **不能直接删除元素**。删除元素会导致桶内出现一个空位,在查找其他元素时,该空位有可能导致程序认为元素不存在(即上述第 `2.` 种情况)。因此需要借助一个标志位来标记删除元素。 -- **容易产生聚集**。桶内被占用的连续位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促进这一位置的“聚堆生长”,最终导致增删查改操作效率的劣化。 - -### 多次哈希 - -顾名思义,「多次哈希」的思路是使用多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。 - -**插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。 - -**查找元素**:以相同的哈希函数顺序查找,存在两种情况: - -1. 找到目标元素,则返回之; -2. 到空位或已尝试所有哈希函数,说明哈希表中无此元素; - -相比于「线性探测」,「多次哈希」方法更不容易产生聚集,代价是多个哈希函数增加了额外计算量。 - -!!! note "工业界方案" - - Java 采用「链式地址」。在 JDK 1.8 之后,HashMap 内数组长度大于 64 时,长度大于 8 的链表会被转化为「红黑树」,以提升查找性能。 - - Python 采用「开放寻址」。字典 dict 使用伪随机数进行探测。 diff --git a/build/chapter_hashing/hash_map.md b/build/chapter_hashing/hash_map.md deleted file mode 100755 index 4943b4440..000000000 --- a/build/chapter_hashing/hash_map.md +++ /dev/null @@ -1,924 +0,0 @@ ---- -comments: true ---- - -# 6.1. 哈希表 - -哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 $O(1)$ 。 - -例如,给定一个包含 $n$ 个学生的数据库,每个学生有“姓名 `name` ”和“学号 `id` ”两项数据,希望实现一个查询功能:**输入一个学号,返回对应的姓名**,则可以使用哈希表实现。 - -![hash_map](hash_map.assets/hash_map.png) - -

Fig. 哈希表抽象表示

- -## 6.1.1. 哈希表效率 - -除了哈希表之外,还可以使用以下数据结构来实现上述查询功能: - -1. **无序数组**:每个元素为 `[学号, 姓名]` ; -2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序; -3. **链表**:每个结点的值为 `[学号, 姓名]` ; -4. **二叉搜索树**:每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树; - -使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 [二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/#_6))。无论是查找元素、还是增删元素,哈希表的时间复杂度都是 $O(1)$ ,全面胜出! - -
- -| | 无序数组 | 有序数组 | 链表 | 二叉搜索树 | 哈希表 | -| -------- | -------- | ----------- | ------ | ----------- | ------ | -| 查找元素 | $O(n)$ | $O(\log n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | -| 插入元素 | $O(1)$ | $O(n)$ | $O(1)$ | $O(\log n)$ | $O(1)$ | -| 删除元素 | $O(n)$ | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | - -
- -## 6.1.2. 哈希表常用操作 - -哈希表的基本操作包括 **初始化、查询操作、添加与删除键值对**。 - -=== "Java" - - ```java title="hash_map.java" - /* 初始化哈希表 */ - Map map = new HashMap<>(); - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); - map.put(13276, "小法"); - map.put(10583, "小鸭"); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - String name = map.get(15937); - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.remove(10583); - ``` - -=== "C++" - - ```cpp title="hash_map.cpp" - /* 初始化哈希表 */ - unordered_map map; - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map[12836] = "小哈"; - map[15937] = "小啰"; - map[16750] = "小算"; - map[13276] = "小法"; - map[10583] = "小鸭"; - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - string name = map[15937]; - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.erase(10583); - ``` - -=== "Python" - - ```python title="hash_map.py" - """ 初始化哈希表 """ - mapp = {} - - """ 添加操作 """ - # 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" - - """ 查询操作 """ - # 向哈希表输入键 key ,得到值 value - name = mapp[15937] - - """ 删除操作 """ - # 在哈希表中删除键值对 (key, value) - mapp.pop(10583) - ``` - -=== "Go" - - ```go title="hash_map.go" - /* 初始化哈希表 */ - mapp := make(map[int]string) - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - name := mapp[15937] - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - delete(mapp, 10583) - ``` - -=== "JavaScript" - - ```js title="hash_map.js" - /* 初始化哈希表 */ - const map = new ArrayHashMap(); - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.set(12836, '小哈'); - map.set(15937, '小啰'); - map.set(16750, '小算'); - map.set(13276, '小法'); - map.set(10583, '小鸭'); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - let name = map.get(15937); - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.delete(10583); - ``` - -=== "TypeScript" - - ```typescript title="hash_map.ts" - /* 初始化哈希表 */ - const map = new Map(); - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.set(12836, '小哈'); - map.set(15937, '小啰'); - map.set(16750, '小算'); - map.set(13276, '小法'); - map.set(10583, '小鸭'); - console.info('\n添加完成后,哈希表为\nKey -> Value'); - console.info(map); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - let name = map.get(15937); - console.info('\n输入学号 15937 ,查询到姓名 ' + name); - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.delete(10583); - console.info('\n删除 10583 后,哈希表为\nKey -> Value'); - console.info(map); - ``` - -=== "C" - - ```c title="hash_map.c" - - ``` - -=== "C#" - - ```csharp title="hash_map.cs" - /* 初始化哈希表 */ - Dictionary map = new (); - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.Add(12836, "小哈"); - map.Add(15937, "小啰"); - map.Add(16750, "小算"); - map.Add(13276, "小法"); - map.Add(10583, "小鸭"); - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - String name = map[15937]; - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.Remove(10583); - ``` - -=== "Swift" - - ```swift title="hash_map.swift" - /* 初始化哈希表 */ - var map: [Int: String] = [:] - - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map[12836] = "小哈" - map[15937] = "小啰" - map[16750] = "小算" - map[13276] = "小法" - map[10583] = "小鸭" - - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - let name = map[15937]! - - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.removeValue(forKey: 10583) - ``` - -=== "Zig" - - ```zig title="hash_map.zig" - - ``` - -遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。 - -=== "Java" - - ```java title="hash_map.java" - /* 遍历哈希表 */ - // 遍历键值对 key->value - for (Map.Entry kv: map.entrySet()) { - System.out.println(kv.getKey() + " -> " + kv.getValue()); - } - // 单独遍历键 key - for (int key: map.keySet()) { - System.out.println(key); - } - // 单独遍历值 value - for (String val: map.values()) { - System.out.println(val); - } - ``` - -=== "C++" - - ```cpp title="hash_map.cpp" - /* 遍历哈希表 */ - // 遍历键值对 key->value - for (auto kv: map) { - cout << kv.first << " -> " << kv.second << endl; - } - // 单独遍历键 key - for (auto key: map) { - cout << key.first << endl; - } - // 单独遍历值 value - for (auto val: map) { - cout << val.second << endl; - } - ``` - -=== "Python" - - ```python title="hash_map.py" - """ 遍历哈希表 """ - # 遍历键值对 key->value - for key, value in mapp.items(): - print(key, "->", value) - # 单独遍历键 key - for key in mapp.keys(): - print(key) - # 单独遍历值 value - for value in mapp.values(): - print(value) - ``` - -=== "Go" - - ```go title="hash_map_test.go" - /* 遍历哈希表 */ - // 遍历键值对 key->value - for key, value := range mapp { - fmt.Println(key, "->", value) - } - // 单独遍历键 key - for key := range mapp { - fmt.Println(key) - } - // 单独遍历值 value - for _, value := range mapp { - fmt.Println(value) - } - ``` - -=== "JavaScript" - - ```js title="hash_map.js" - /* 遍历哈希表 */ - // 遍历键值对 key->value - for (const entry of map.entries()) { - if (!entry) continue; - console.info(entry.key + ' -> ' + entry.val); - } - // 单独遍历键 key - for (const key of map.keys()) { - console.info(key); - } - // 单独遍历值 value - for (const val of map.values()) { - console.info(val); - } - ``` - -=== "TypeScript" - - ```typescript title="hash_map.ts" - /* 遍历哈希表 */ - console.info('\n遍历键值对 Key->Value'); - for (const [k, v] of map.entries()) { - console.info(k + ' -> ' + v); - } - console.info('\n单独遍历键 Key'); - for (const k of map.keys()) { - console.info(k); - } - console.info('\n单独遍历值 Value'); - for (const v of map.values()) { - console.info(v); - } - ``` - -=== "C" - - ```c title="hash_map.c" - - ``` - -=== "C#" - - ```csharp title="hash_map.cs" - /* 遍历哈希表 */ - // 遍历键值对 Key->Value - foreach (var kv in map) { - Console.WriteLine(kv.Key + " -> " + kv.Value); - } - // 单独遍历键 key - foreach (int key in map.Keys) { - Console.WriteLine(key); - } - // 单独遍历值 value - foreach (String val in map.Values) { - Console.WriteLine(val); - } - ``` - -=== "Swift" - - ```swift title="hash_map.swift" - /* 遍历哈希表 */ - // 遍历键值对 Key->Value - for (key, value) in map { - print("\(key) -> \(value)") - } - // 单独遍历键 Key - for key in map.keys { - print(key) - } - // 单独遍历值 Value - for value in map.values { - print(value) - } - ``` - -=== "Zig" - - ```zig title="hash_map.zig" - - ``` - -## 6.1.3. 哈希函数 - -哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。 - -最简单地,**我们可以仅用一个「数组」来实现哈希表**。首先,将所有 value 放入数组中,那么每个 value 在数组中都有唯一的「索引」。显然,访问 value 需要给定索引,而为了 **建立 key 和索引之间的映射关系**,我们需要使用「哈希函数 Hash Function」。 - -设数组为 `bucket` ,哈希函数为 `f(x)` ,输入键为 `key` 。那么获取 value 的步骤为: - -1. 通过哈希函数计算出索引,即 `index = f(key)` ; -2. 通过索引在数组中获取值,即 `value = bucket[index]` ; - -以上述学生数据 `key 学号 -> value 姓名` 为例,我们可以将「哈希函数」设计为 - -$$ -f(x) = x \% 100 -$$ - -![hash_function](hash_map.assets/hash_function.png) - -

Fig. 哈希函数

- -=== "Java" - - ```java title="array_hash_map.java" - /* 键值对 int->String */ - class Entry { - public int key; - public String val; - public Entry(int key, String val) { - this.key = key; - this.val = val; - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private List bucket; - public ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - bucket.add(null); - } - } - - /* 哈希函数 */ - private int hashFunc(int key) { - int index = key % 100; - return index; - } - - /* 查询操作 */ - public String get(int key) { - int index = hashFunc(key); - Entry pair = bucket.get(index); - if (pair == null) return null; - return pair.val; - } - - /* 添加操作 */ - public void put(int key, String val) { - Entry pair = new Entry(key, val); - int index = hashFunc(key); - bucket.set(index, pair); - } - - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 置为 null ,代表删除 - bucket.set(index, null); - } - - /* 获取所有键值对 */ - public List entrySet() { - List entrySet = new ArrayList<>(); - for (Entry pair : bucket) { - if (pair != null) - entrySet.add(pair); - } - return entrySet; - } - - /* 获取所有键 */ - public List keySet() { - List keySet = new ArrayList<>(); - for (Entry pair : bucket) { - if (pair != null) - keySet.add(pair.key); - } - return keySet; - } - - /* 获取所有值 */ - public List valueSet() { - List valueSet = new ArrayList<>(); - for (Entry pair : bucket) { - if (pair != null) - valueSet.add(pair.val); - } - return valueSet; - } - - /* 打印哈希表 */ - public void print() { - for (Entry kv: entrySet()) { - System.out.println(kv.key + " -> " + kv.val); - } - } - } - ``` - -=== "C++" - - ```cpp title="array_hash_map.cpp" - /* 键值对 int->String */ - struct Entry { - public: - int key; - string val; - Entry(int key, string val) { - this->key = key; - this->val = val; - } - }; - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private: - vector bucket; - public: - ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket= vector(100); - } - - /* 哈希函数 */ - int hashFunc(int key) { - int index = key % 100; - return index; - } - - /* 查询操作 */ - string get(int key) { - int index = hashFunc(key); - Entry* pair = bucket[index]; - return pair->val; - } - - /* 添加操作 */ - void put(int key, string val) { - Entry* pair = new Entry(key, val); - int index = hashFunc(key); - bucket[index] = pair; - } - - /* 删除操作 */ - void remove(int key) { - int index = hashFunc(key); - // 置为 nullptr ,代表删除 - bucket[index] = nullptr; - } - }; - ``` - -=== "Python" - - ```python title="array_hash_map.py" - """ 键值对 int->String """ - class Entry: - def __init__(self, key, val): - self.key = key - self.val = val - - """ 基于数组简易实现的哈希表 """ - class ArrayHashMap: - def __init__(self): - # 初始化一个长度为 100 的桶(数组) - self.bucket = [None] * 100 - - """ 哈希函数 """ - def hash_func(self, key): - index = key % 100 - return index - - """ 查询操作 """ - def get(self, key): - index = self.hash_func(key) - pair = self.bucket[index] - if pair is None: - return None - return pair.val - - """ 添加操作 """ - def put(self, key, val): - pair = Entry(key, val) - index = self.hash_func(key) - self.bucket[index] = pair - - """ 删除操作 """ - def remove(self, key): - index = self.hash_func(key) - # 置为 None ,代表删除 - self.bucket[index] = None - - """ 获取所有键值对 """ - def entry_set(self): - result = [] - for pair in self.bucket: - if pair is not None: - result.append(pair) - return result - - """ 获取所有键 """ - def key_set(self): - result = [] - for pair in self.bucket: - if pair is not None: - result.append(pair.key) - return result - - """ 获取所有值 """ - def value_set(self): - result = [] - for pair in self.bucket: - if pair is not None: - result.append(pair.val) - return result - - """ 打印哈希表 """ - def print(self): - for pair in self.bucket: - if pair is not None: - print(pair.key, "->", pair.val) - ``` - -=== "Go" - - ```go title="array_hash_map.go" - /* 键值对 int->String */ - type entry struct { - key int - val string - } - - /* 基于数组简易实现的哈希表 */ - type arrayHashMap struct { - bucket []*entry - } - - func newArrayHashMap() *arrayHashMap { - // 初始化一个长度为 100 的桶(数组) - bucket := make([]*entry, 100) - return &arrayHashMap{bucket: bucket} - } - - /* 哈希函数 */ - func (a *arrayHashMap) hashFunc(key int) int { - index := key % 100 - return index - } - - /* 查询操作 */ - func (a *arrayHashMap) get(key int) string { - index := a.hashFunc(key) - pair := a.bucket[index] - if pair == nil { - return "Not Found" - } - return pair.val - } - - /* 添加操作 */ - func (a *arrayHashMap) put(key int, val string) { - pair := &entry{key: key, val: val} - index := a.hashFunc(key) - a.bucket[index] = pair - } - - /* 删除操作 */ - func (a *arrayHashMap) remove(key int) { - index := a.hashFunc(key) - // 置为 nil ,代表删除 - a.bucket[index] = nil - } - ``` - -=== "JavaScript" - - ```js title="array_hash_map.js" - /* 键值对 Number -> String */ - class Entry { - constructor(key, val) { - this.key = key; - this.val = val; - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - #bucket; - constructor() { - // 初始化一个长度为 100 的桶(数组) - this.#bucket = new Array(100).fill(null); - } - - /* 哈希函数 */ - #hashFunc(key) { - return key % 100; - } - - /* 查询操作 */ - get(key) { - let index = this.#hashFunc(key); - let entry = this.#bucket[index]; - if (entry === null) return null; - return entry.val; - } - - /* 添加操作 */ - set(key, val) { - let index = this.#hashFunc(key); - this.#bucket[index] = new Entry(key, val); - } - - /* 删除操作 */ - delete(key) { - let index = this.#hashFunc(key); - // 置为 null ,代表删除 - this.#bucket[index] = null; - } - } - ``` - -=== "TypeScript" - - ```typescript title="array_hash_map.ts" - /* 键值对 Number -> String */ - class Entry { - public key: number; - public val: string; - - constructor(key: number, val: string) { - this.key = key; - this.val = val; - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - - private readonly bucket: (Entry | null)[]; - - constructor() { - // 初始化一个长度为 100 的桶(数组) - this.bucket = (new Array(100)).fill(null); - } - - /* 哈希函数 */ - private hashFunc(key: number): number { - return key % 100; - } - - /* 查询操作 */ - public get(key: number): string | null { - let index = this.hashFunc(key); - let entry = this.bucket[index]; - if (entry === null) return null; - return entry.val; - } - - /* 添加操作 */ - public set(key: number, val: string) { - let index = this.hashFunc(key); - this.bucket[index] = new Entry(key, val); - } - - /* 删除操作 */ - public delete(key: number) { - let index = this.hashFunc(key); - // 置为 null ,代表删除 - this.bucket[index] = null; - } - } - ``` - -=== "C" - - ```c title="array_hash_map.c" - - ``` - -=== "C#" - - ```csharp title="array_hash_map.cs" - /* 键值对 int->String */ - class Entry - { - public int key; - public String val; - public Entry(int key, String val) - { - this.key = key; - this.val = val; - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap - { - private List bucket; - public ArrayHashMap() - { - // 初始化一个长度为 100 的桶(数组) - bucket = new (); - for (int i = 0; i < 100; i++) - { - bucket.Add(null); - } - } - /* 哈希函数 */ - private int hashFunc(int key) - { - int index = key % 100; - return index; - } - /* 查询操作 */ - public String? get(int key) - { - int index = hashFunc(key); - Entry? pair = bucket[index]; - if (pair == null) return null; - return pair.val; - } - /* 添加操作 */ - public void put(int key, String val) - { - Entry pair = new Entry(key, val); - int index = hashFunc(key); - bucket[index]=pair; - } - /* 删除操作 */ - public void remove(int key) - { - int index = hashFunc(key); - // 置为 null ,代表删除 - bucket[index]=null; - } - } - ``` - -=== "Swift" - - ```swift title="array_hash_map.swift" - /* 键值对 int->String */ - class Entry { - var key: Int - var val: String - - init(key: Int, val: String) { - self.key = key - self.val = val - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private var bucket: [Entry?] = [] - - init() { - // 初始化一个长度为 100 的桶(数组) - for _ in 0 ..< 100 { - bucket.append(nil) - } - } - - /* 哈希函数 */ - private func hashFunc(key: Int) -> Int { - let index = key % 100 - return index - } - - /* 查询操作 */ - func get(key: Int) -> String? { - let index = hashFunc(key: key) - let pair = bucket[index] - return pair?.val - } - - /* 添加操作 */ - func put(key: Int, val: String) { - let pair = Entry(key: key, val: val) - let index = hashFunc(key: key) - bucket[index] = pair - } - - /* 删除操作 */ - func remove(key: Int) { - let index = hashFunc(key: key) - // 置为 nil ,代表删除 - bucket[index] = nil - } - } - ``` - -=== "Zig" - - ```zig title="array_hash_map.zig" - - ``` - -## 6.1.4. 哈希冲突 - -细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 $12836$ 和 $20336$ ,则有 - -$$ -f(12836) = f(20336) = 36 -$$ - -两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论。 - -![hash_collision](hash_map.assets/hash_collision.png) - -

Fig. 哈希冲突

- -综上所述,一个优秀的「哈希函数」应该具备以下特性: - -- 尽量少地发生哈希冲突; -- 时间复杂度 $O(1)$ ,计算尽可能高效; -- 空间使用率高,即“键值对占用空间 / 哈希表总占用空间”尽可能大; diff --git a/build/chapter_hashing/summary.md b/build/chapter_hashing/summary.md deleted file mode 100644 index a66b041a3..000000000 --- a/build/chapter_hashing/summary.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -comments: true ---- - -# 6.3. 小结 diff --git a/build/chapter_heap/heap.md b/build/chapter_heap/heap.md deleted file mode 100644 index efbac2365..000000000 --- a/build/chapter_heap/heap.md +++ /dev/null @@ -1,1032 +0,0 @@ ---- -comments: true ---- - -# 8.1. 堆 - -「堆 Heap」是一棵限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型: - -- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值; -- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值; - -![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png) - -## 8.1.1. 堆术语与性质 - -- 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。 -- 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。 -- 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。 - -## 8.1.2. 堆常用操作 - -值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 - -而恰好,**堆的定义与优先队列的操作逻辑完全吻合**,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构。因此,本文与代码对两者不做特别区分,统一使用「堆」来命名。 - -堆的常用操作见下表(方法命名以 Java 为例)。 - -

Table. 堆的常用操作

- -
- -| 方法 | 描述 | 时间复杂度 | -| --------- | ----------------------------------------- | ----------- | -| add() | 元素入堆 | $O(\log n)$ | -| poll() | 堆顶元素出堆 | $O(\log n)$ | -| peek() | 访问堆顶元素(大 / 小顶堆分别为最大 / 小值) | $O(1)$ | -| size() | 获取堆的元素数量 | $O(1)$ | -| isEmpty() | 判断堆是否为空 | $O(1)$ | - -
- -我们可以直接使用编程语言提供的堆类(或优先队列类)。 - -!!! tip - - 类似于排序中“从小到大排列”和“从大到小排列”,“大顶堆”和“小顶堆”可仅通过修改 Comparator 来互相转换。 - -=== "Java" - - ```java title="heap.java" - /* 初始化堆 */ - // 初始化小顶堆 - Queue minHeap = new PriorityQueue<>(); - // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) - Queue maxHeap = new PriorityQueue<>((a, b) -> { return b - a; }); - - /* 元素入堆 */ - maxHeap.add(1); - maxHeap.add(3); - maxHeap.add(2); - maxHeap.add(5); - maxHeap.add(4); - - /* 获取堆顶元素 */ - int peek = maxHeap.peek(); // 5 - - /* 堆顶元素出堆 */ - // 出堆元素会形成一个从大到小的序列 - peek = heap.poll(); // 5 - peek = heap.poll(); // 4 - peek = heap.poll(); // 3 - peek = heap.poll(); // 2 - peek = heap.poll(); // 1 - - /* 获取堆大小 */ - int size = maxHeap.size(); - - /* 判断堆是否为空 */ - boolean isEmpty = maxHeap.isEmpty(); - - /* 输入列表并建堆 */ - minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); - ``` - -=== "C++" - - ```cpp title="heap.cpp" - /* 初始化堆 */ - // 初始化小顶堆 - priority_queue, greater> minHeap; - // 初始化大顶堆 - priority_queue, less> maxHeap; - - /* 元素入堆 */ - maxHeap.push(1); - maxHeap.push(3); - maxHeap.push(2); - maxHeap.push(5); - maxHeap.push(4); - - /* 获取堆顶元素 */ - int peek = maxHeap.top(); // 5 - - /* 堆顶元素出堆 */ - // 出堆元素会形成一个从大到小的序列 - maxHeap.pop(); // 5 - maxHeap.pop(); // 4 - maxHeap.pop(); // 3 - maxHeap.pop(); // 2 - maxHeap.pop(); // 1 - - /* 获取堆大小 */ - int size = maxHeap.size(); - - /* 判断堆是否为空 */ - bool isEmpty = maxHeap.empty(); - - /* 输入列表并建堆 */ - vector input{1, 3, 2, 5, 4}; - priority_queue, greater> minHeap(input.begin(), input.end()); - ``` - -=== "Python" - - ```python title="heap.py" - - ``` - -=== "Go" - - ```go title="heap.go" - // Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆 - // 实现 heap.Interface 需要同时实现 sort.Interface - type intHeap []any - - // Push heap.Interface 的方法,实现推入元素到堆 - func (h *intHeap) Push(x any) { - // Push 和 Pop 使用 pointer receiver 作为参数 - // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。 - *h = append(*h, x.(int)) - } - - // Pop heap.Interface 的方法,实现弹出堆顶元素 - func (h *intHeap) Pop() any { - // 待出堆元素存放在最后 - last := (*h)[len(*h)-1] - *h = (*h)[:len(*h)-1] - return last - } - - // Len sort.Interface 的方法 - func (h *intHeap) Len() int { - return len(*h) - } - - // Less sort.Interface 的方法 - func (h *intHeap) Less(i, j int) bool { - // 如果实现小顶堆,则需要调整为小于号 - return (*h)[i].(int) > (*h)[j].(int) - } - - // Swap sort.Interface 的方法 - func (h *intHeap) Swap(i, j int) { - (*h)[i], (*h)[j] = (*h)[j], (*h)[i] - } - - // Top 获取堆顶元素 - func (h *intHeap) Top() any { - return (*h)[0] - } - - /* Driver Code */ - func TestHeap(t *testing.T) { - /* 初始化堆 */ - // 初始化大顶堆 - maxHeap := &intHeap{} - heap.Init(maxHeap) - /* 元素入堆 */ - // 调用 heap.Interface 的方法,来添加元素 - heap.Push(maxHeap, 1) - heap.Push(maxHeap, 3) - heap.Push(maxHeap, 2) - heap.Push(maxHeap, 4) - heap.Push(maxHeap, 5) - - /* 获取堆顶元素 */ - top := maxHeap.Top() - fmt.Printf("堆顶元素为 %d\n", top) - - /* 堆顶元素出堆 */ - // 调用 heap.Interface 的方法,来移除元素 - heap.Pop(maxHeap) - heap.Pop(maxHeap) - heap.Pop(maxHeap) - heap.Pop(maxHeap) - heap.Pop(maxHeap) - - /* 获取堆大小 */ - size := len(*maxHeap) - fmt.Printf("堆元素数量为 %d\n", size) - - /* 判断堆是否为空 */ - isEmpty := len(*maxHeap) == 0 - fmt.Printf("堆是否为空 %t\n", isEmpty) - } - ``` - -=== "JavaScript" - - ```js title="heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="heap.ts" - - ``` - -=== "C" - - ```c title="heap.c" - - ``` - -=== "C#" - - ```csharp title="heap.cs" - - ``` - -=== "Swift" - - ```swift title="heap.swift" - // Swift 未提供内置 heap 类 - ``` - -=== "Zig" - - ```zig title="heap.zig" - - ``` - -## 8.1.3. 堆的实现 - -下文实现的是「大顶堆」,若想转换为「小顶堆」,将所有大小逻辑判断取逆(例如将 $\geq$ 替换为 $\leq$ )即可,有兴趣的同学可自行实现。 - -### 堆的存储与表示 - -在二叉树章节我们学过,「完全二叉树」非常适合使用「数组」来表示,而堆恰好是一棵完全二叉树,**因而我们采用「数组」来存储「堆」**。 - -**二叉树指针**。使用数组表示二叉树时,元素代表结点值,索引代表结点在二叉树中的位置,**而结点指针通过索引映射公式来实现**。 - -具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。 - -![representation_of_heap](heap.assets/representation_of_heap.png) - -我们将索引映射公式封装成函数,以便后续使用。 - -=== "Java" - - ```java title="my_heap.java" - /* 获取左子结点索引 */ - int left(int i) { - return 2 * i + 1; - } - - /* 获取右子结点索引 */ - int right(int i) { - return 2 * i + 2; - } - - /* 获取父结点索引 */ - int parent(int i) { - return (i - 1) / 2; // 向下整除 - } - ``` - -=== "C++" - - ```cpp title="my_heap.cpp" - // 使用动态数组,这样无需考虑扩容问题 - vector maxHeap; - - /* 获取左子结点索引 */ - int left(int i) { - return 2 * i + 1; - } - - /* 获取右子结点索引 */ - int right(int i) { - return 2 * i + 2; - } - - /* 获取父结点索引 */ - int parent(int i) { - return (i - 1) / 2; // 向下取整 - } - ``` - -=== "Python" - - ```python title="my_heap.py" - - ``` - -=== "Go" - - ```go title="my_heap.go" - type maxHeap struct { - // 使用切片而非数组,这样无需考虑扩容问题 - data []any - } - - /* 构造函数,建立空堆 */ - func newHeap() *maxHeap { - return &maxHeap{ - data: make([]any, 0), - } - } - - /* 获取左子结点索引 */ - func (h *maxHeap) left(i int) int { - return 2*i + 1 - } - - /* 获取右子结点索引 */ - func (h *maxHeap) right(i int) int { - return 2*i + 2 - } - - /* 获取父结点索引 */ - func (h *maxHeap) parent(i int) int { - // 向下整除 - return (i - 1) / 2 - } - ``` - -=== "JavaScript" - - ```js title="my_heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="my_heap.ts" - - ``` - -=== "C" - - ```c title="my_heap.c" - - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - var maxHeap: [Int] - - /* 构造函数,建立空堆 */ - init() { - maxHeap = [] - } - - /* 获取左子结点索引 */ - func left(i: Int) -> Int { - 2 * i + 1 - } - - /* 获取右子结点索引 */ - func right(i: Int) -> Int { - 2 * i + 2 - } - - /* 获取父结点索引 */ - func parent(i: Int) -> Int { - (i - 1) / 2 // 向下整除 - } - ``` - -=== "Zig" - - ```zig title="my_heap.zig" - - ``` - -### 访问堆顶元素 - -堆顶元素是二叉树的根结点,即列表首元素。 - -=== "Java" - - ```java title="my_heap.java" - /* 访问堆顶元素 */ - int peek() { - return maxHeap.get(0); - } - ``` - -=== "C++" - - ```cpp title="my_heap.cpp" - /* 访问堆顶元素 */ - int peek() { - return maxHeap[0]; - } - ``` - -=== "Python" - - ```python title="my_heap.py" - - ``` - -=== "Go" - - ```go title="my_heap.go" - /* 访问堆顶元素 */ - func (h *maxHeap) peek() any { - return h.data[0] - } - ``` - -=== "JavaScript" - - ```js title="my_heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="my_heap.ts" - - ``` - -=== "C" - - ```c title="my_heap.c" - - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 访问堆顶元素 */ - func peek() -> Int { - maxHeap[0] - } - ``` - -=== "Zig" - - ```zig title="my_heap.zig" - - ``` - -### 元素入堆 - -给定元素 `val` ,我们先将其添加到堆底。添加后,由于 `val` 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,**因此需要修复从插入结点到根结点这条路径上的各个结点**,该操作被称为「堆化 Heapify」。 - -考虑从入堆结点开始,**从底至顶执行堆化**。具体地,比较插入结点与其父结点的值,若插入结点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个结点;直至越过根结点时结束,或当遇到无需交换的结点时提前结束。 - -=== "Step 1" - ![heap_push_step1](heap.assets/heap_push_step1.png) - -=== "Step 2" - ![heap_push_step2](heap.assets/heap_push_step2.png) - -=== "Step 3" - ![heap_push_step3](heap.assets/heap_push_step3.png) - -=== "Step 4" - ![heap_push_step4](heap.assets/heap_push_step4.png) - -=== "Step 5" - ![heap_push_step5](heap.assets/heap_push_step5.png) - -=== "Step 6" - ![heap_push_step6](heap.assets/heap_push_step6.png) - -设结点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。 - -=== "Java" - - ```java title="my_heap.java" - /* 元素入堆 */ - void push(int val) { - // 添加结点 - maxHeap.add(val); - // 从底至顶堆化 - siftUp(size() - 1); - } - - /* 从结点 i 开始,从底至顶堆化 */ - void siftUp(int i) { - while (true) { - // 获取结点 i 的父结点 - int p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 - if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) - break; - // 交换两结点 - swap(i, p); - // 循环向上堆化 - i = p; - } - } - ``` - -=== "C++" - - ```cpp title="my_heap.cpp" - /* 元素入堆 */ - void push(int val) { - // 添加结点 - maxHeap.push_back(val); - // 从底至顶堆化 - shifUp(size() - 1); - } - - /* 从结点 i 开始,从底至顶堆化 */ - void shifUp(int i) { - while (true) { - // 获取结点 i 的父结点 - int p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 - if (p < 0 || maxHeap[i] <= maxHeap[p]) - break; - // 交换两结点 - swap(maxHeap[i], maxHeap[p]); - // 循环向上堆化 - i = p; - } - } - ``` - -=== "Python" - - ```python title="my_heap.py" - - ``` - -=== "Go" - - ```go title="my_heap.go" - /* 元素入堆 */ - func (h *maxHeap) push(val any) { - // 添加结点 - h.data = append(h.data, val) - // 从底至顶堆化 - h.siftUp(len(h.data) - 1) - } - - /* 从结点 i 开始,从底至顶堆化 */ - func (h *maxHeap) siftUp(i int) { - for true { - // 获取结点 i 的父结点 - p := h.parent(i) - // 当“越过根结点”或“结点无需修复”时,结束堆化 - if p < 0 || h.data[i].(int) <= h.data[p].(int) { - break - } - // 交换两结点 - h.swap(i, p) - // 循环向上堆化 - i = p - } - } - ``` - -=== "JavaScript" - - ```js title="my_heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="my_heap.ts" - - ``` - -=== "C" - - ```c title="my_heap.c" - - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 元素入堆 */ - func push(val: Int) { - // 添加结点 - maxHeap.append(val) - // 从底至顶堆化 - siftUp(i: size() - 1) - } - - /* 从结点 i 开始,从底至顶堆化 */ - func siftUp(i: Int) { - var i = i - while true { - // 获取结点 i 的父结点 - let p = parent(i: i) - // 当“越过根结点”或“结点无需修复”时,结束堆化 - if p < 0 || maxHeap[i] <= maxHeap[p] { - break - } - // 交换两结点 - swap(i: i, j: p) - // 循环向上堆化 - i = p - } - } - ``` - -=== "Zig" - - ```zig title="my_heap.zig" - - ``` - -### 堆顶元素出堆 - -堆顶元素是二叉树根结点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有结点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤: - -1. 交换堆顶元素与堆底元素(即交换根结点与最右叶结点); -2. 交换完成后,将堆底从列表中删除(注意,因为已经交换,实际上删除的是原来的堆顶元素); -3. 从根结点开始,**从顶至底执行堆化**; - -顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根结点的值与其两个子结点的值,将最大的子结点与根结点执行交换,并循环以上操作,直到越过叶结点时结束,或当遇到无需交换的结点时提前结束。 - -=== "Step 1" - ![heap_poll_step1](heap.assets/heap_poll_step1.png) - -=== "Step 2" - ![heap_poll_step2](heap.assets/heap_poll_step2.png) - -=== "Step 3" - ![heap_poll_step3](heap.assets/heap_poll_step3.png) - -=== "Step 4" - ![heap_poll_step4](heap.assets/heap_poll_step4.png) - -=== "Step 5" - ![heap_poll_step5](heap.assets/heap_poll_step5.png) - -=== "Step 6" - ![heap_poll_step6](heap.assets/heap_poll_step6.png) - -=== "Step 7" - ![heap_poll_step7](heap.assets/heap_poll_step7.png) - -=== "Step 8" - ![heap_poll_step8](heap.assets/heap_poll_step8.png) - -=== "Step 9" - ![heap_poll_step9](heap.assets/heap_poll_step9.png) - -=== "Step 10" - ![heap_poll_step10](heap.assets/heap_poll_step10.png) - -与元素入堆操作类似,**堆顶元素出堆操作的时间复杂度为 $O(\log n)$** 。 - -=== "Java" - - ```java title="my_heap.java" - /* 元素出堆 */ - int poll() { - // 判空处理 - if (isEmpty()) - throw new EmptyStackException(); - // 交换根结点与最右叶结点(即交换首元素与尾元素) - swap(0, size() - 1); - // 删除结点 - int val = maxHeap.remove(size() - 1); - // 从顶至底堆化 - siftDown(0); - // 返回堆顶元素 - return val; - } - - /* 从结点 i 开始,从顶至底堆化 */ - void siftDown(int i) { - while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma - int l = left(i), r = right(i), ma = i; - if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) - ma = l; - if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) - ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if (ma == i) break; - // 交换两结点 - swap(i, ma); - // 循环向下堆化 - i = ma; - } - } - ``` - -=== "C++" - - ```cpp title="my_heap.cpp" - /* 从结点 i 开始,从顶至底堆化 */ - void shifDown(int i) { - while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma - int l = left(i), r = right(i), ma = i; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if (l < size() && maxHeap[l] > maxHeap[ma]) - ma = l; - if (r < size() && maxHeap[r] > maxHeap[ma]) - ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if (ma == i) - break; - swap(maxHeap[i], maxHeap[ma]); - // 循环向下堆化 - i = ma; - } - } - - /* 元素出堆 */ - void poll() { - // 判空处理 - if (empty()) { - cout << "Error:堆为空" << endl; - return; - } - // 交换根结点与最右叶结点(即交换首元素与尾元素) - swap(maxHeap[0], maxHeap[size() - 1]); - // 删除结点 - maxHeap.pop_back(); - // 从顶至底堆化 - shifDown(0); - } - ``` - -=== "Python" - - ```python title="my_heap.py" - - ``` - -=== "Go" - - ```go title="my_heap.go" - /* 元素出堆 */ - func (h *maxHeap) poll() any { - // 判空处理 - if h.isEmpty() { - fmt.Println("error") - return nil - } - // 交换根结点与最右叶结点(即交换首元素与尾元素) - h.swap(0, h.size()-1) - // 删除结点 - val := h.data[len(h.data)-1] - h.data = h.data[:len(h.data)-1] - // 从顶至底堆化 - h.siftDown(0) - - // 返回堆顶元素 - return val - } - - /* 从结点 i 开始,从顶至底堆化 */ - func (h *maxHeap) siftDown(i int) { - for true { - // 判断结点 i, l, r 中值最大的结点,记为 max - l, r, max := h.left(i), h.right(i), i - if l < h.size() && h.data[l].(int) > h.data[max].(int) { - max = l - } - if r < h.size() && h.data[r].(int) > h.data[max].(int) { - max = r - } - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if max == i { - break - } - // 交换两结点 - h.swap(i, max) - // 循环向下堆化 - i = max - } - } - ``` - -=== "JavaScript" - - ```js title="my_heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="my_heap.ts" - - ``` - -=== "C" - - ```c title="my_heap.c" - - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 元素出堆 */ - func poll() -> Int { - // 判空处理 - if isEmpty() { - fatalError("堆为空") - } - // 交换根结点与最右叶结点(即交换首元素与尾元素) - swap(i: 0, j: size() - 1) - // 删除结点 - let val = maxHeap.remove(at: size() - 1) - // 从顶至底堆化 - siftDown(i: 0) - // 返回堆顶元素 - return val - } - - /* 从结点 i 开始,从顶至底堆化 */ - func siftDown(i: Int) { - var i = i - while true { - // 判断结点 i, l, r 中值最大的结点,记为 ma - let l = left(i: i) - let r = right(i: i) - var ma = i - if l < size(), maxHeap[l] > maxHeap[ma] { - ma = l - } - if r < size(), maxHeap[r] > maxHeap[ma] { - ma = r - } - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if ma == i { - break - } - // 交换两结点 - swap(i: i, j: ma) - // 循环向下堆化 - i = ma - } - } - ``` - -=== "Zig" - - ```zig title="my_heap.zig" - - ``` - -### 输入数据并建堆 * - -如果我们想要直接输入一个列表并将其建堆,那么该怎么做呢?最直接地,考虑使用「元素入堆」方法,将列表元素依次入堆。元素入堆的时间复杂度为 $O(n)$ ,而平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。 - -然而,存在一种更加优雅的建堆方法。设结点数量为 $n$ ,我们先将列表所有元素原封不动添加进堆,**然后迭代地对各个结点执行「从顶至底堆化」**。当然,**无需对叶结点执行堆化**,因为其没有子结点。 - -=== "Java" - - ```java title="my_heap.java" - /* 构造函数,根据输入列表建堆 */ - MaxHeap(List nums) { - // 将列表元素原封不动添加进堆 - maxHeap = new ArrayList<>(nums); - // 堆化除叶结点以外的其他所有结点 - for (int i = parent(size() - 1); i >= 0; i--) { - siftDown(i); - } - } - ``` - -=== "C++" - - ```cpp title="my_heap.cpp" - /* 构造函数,根据输入列表建堆 */ - MaxHeap(vector nums) { - // 将列表元素原封不动添加进堆 - maxHeap = nums; - // 堆化除叶结点以外的其他所有结点 - for (int i = parent(size() - 1); i >= 0; i--) { - shifDown(i); - } - } - ``` - -=== "Python" - - ```python title="my_heap.py" - - ``` - -=== "Go" - - ```go title="my_heap.go" - /* 构造函数,根据切片建堆 */ - func newMaxHeap(nums []any) *maxHeap { - // 将列表元素原封不动添加进堆 - h := &maxHeap{data: nums} - // 堆化除叶结点以外的其他所有结点 - for i := len(h.data) - 1; i >= 0; i-- { - h.siftDown(i) - } - return h - } - ``` - -=== "JavaScript" - - ```js title="my_heap.js" - - ``` - -=== "TypeScript" - - ```typescript title="my_heap.ts" - - ``` - -=== "C" - - ```c title="my_heap.c" - - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 构造函数,根据输入列表建堆 */ - init(nums: [Int]) { - // 将列表元素原封不动添加进堆 - maxHeap = nums - // 堆化除叶结点以外的其他所有结点 - for i in stride(from: parent(i: size() - 1), through: 0, by: -1) { - siftDown(i: i) - } - } - ``` - -=== "Zig" - - ```zig title="my_heap.zig" - - ``` - -那么,第二种建堆方法的时间复杂度时多少呢?我们来做一下简单推算。 - -- 完全二叉树中,设结点总数为 $n$ ,则叶结点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶结点后,需要堆化结点数量为 $(n - 1)/2$ ,即为 $O(n)$ ; -- 从顶至底堆化中,每个结点最多堆化至叶结点,因此最大迭代次数为二叉树高度 $O(\log n)$ ; - -将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。然而,该估算结果仍不够准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质。 - -下面我们来尝试展开计算。为了减小计算难度,我们假设树是一个「完美二叉树」,该假设不会影响计算结果的正确性。设二叉树(即堆)结点数量为 $n$ ,树高度为 $h$ 。上文提到,**结点堆化最大迭代次数等于该结点到叶结点的距离,而这正是“结点高度”**。因此,我们将各层的“结点数量 $\times$ 结点高度”求和,即可得到所有结点的堆化的迭代次数总和。 - -$$ -T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1 -$$ - -![heapify_count](heap.assets/heapify_count.png) - -化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得 - -$$ -\begin{aligned} -T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{h-1}\times1 \newline -2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \cdots + 2^{h}\times1 \newline -\end{aligned} -$$ - -**使用错位相减法**,令下式 $2 T(h)$ 减去上式 $T(h)$ ,可得 - -$$ -2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \cdots + 2^{h-1} + 2^h -$$ - -观察上式,$T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为 - -$$ -\begin{aligned} -T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline -& = 2^{h+1} - h \newline -& = O(2^h) -\end{aligned} -$$ - -进一步地,高度为 $h$ 的完美二叉树的结点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 - -## 8.1.4. 堆常见应用 - -- **优先队列**。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 $O(\log n)$ ,建队操作为 $O(n)$ ,皆非常高效。 -- **堆排序**。给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序的序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并减小堆的长度即可。 -- **获取最大的 $k$ 个元素**。这既是一道经典算法题目,也是一种常见应用,例如选取热度前 10 的新闻作为微博热搜,选取前 10 销量的商品等。 diff --git a/build/chapter_introduction/algorithms_are_everywhere.md b/build/chapter_introduction/algorithms_are_everywhere.md deleted file mode 100644 index 980fba81e..000000000 --- a/build/chapter_introduction/algorithms_are_everywhere.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -comments: true ---- - -# 1.1. 算法无处不在 - -听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。 - -在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中**。接下来,我将介绍两个具体例子来佐证。 - -**例一:拼积木**。一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。 - -如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书」上的一系列步骤就是算法。 - -**例二:查字典**。在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做: - -1. 打开字典大致一半页数的位置,查看此页的首字母是什么(假设为 $m$ ); -2. 由于在英文字母表中 $r$ 在 $m$ 的后面,因此应排除字典前半部分,查找范围仅剩后半部分; -3. 循环执行步骤 1-2 ,直到找到拼音首字母为 $r$ 的页码时终止。 - -=== "Step 1" - ![look_up_dictionary_step_1](algorithms_are_everywhere.assets/look_up_dictionary_step_1.png) -=== "Step 2" - ![look_up_dictionary_step_2](algorithms_are_everywhere.assets/look_up_dictionary_step_2.png) -=== "Step 3" - ![look_up_dictionary_step_3](algorithms_are_everywhere.assets/look_up_dictionary_step_3.png) -=== "Step 4" - ![look_up_dictionary_step_4](algorithms_are_everywhere.assets/look_up_dictionary_step_4.png) -=== "Step 5" - ![look_up_dictionary_step_5](algorithms_are_everywhere.assets/look_up_dictionary_step_5.png) - -查字典这个小学生的标配技能,实际上就是大名鼎鼎的「二分查找」。从数据结构角度,我们可以将字典看作是一个已排序的「数组」;而从算法角度,我们可将上述查字典的一系列指令看作是「二分查找」算法。 - -小到烹饪一道菜、大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现,使我们可以通过编程将数据结构存储在内存中,也可以编写代码来调用 CPU, GPU 执行算法,从而将生活中的问题搬运到计算机中,更加高效地解决各式各样的复杂问题。 - -!!! tip - - 读到这里,如果你感到对数据结构、算法、数组、二分查找等此类概念一知半解,那么就太好了!因为这正是本书存在的价值,接下来,本书将会一步步地引导你进入数据结构与算法的知识殿堂。 diff --git a/build/chapter_introduction/what_is_dsa.md b/build/chapter_introduction/what_is_dsa.md deleted file mode 100644 index d104e4852..000000000 --- a/build/chapter_introduction/what_is_dsa.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -comments: true ---- - -# 1.2. 算法是什么 - -## 1.2.1. 算法定义 - -「算法 Algorithm」是在有限时间内解决特定问题的一组指令或操作步骤。算法具有以下特性: - -- 问题是明确的,需要拥有明确的输入和输出定义。 -- 解具有确定性,即给定相同输入时,输出一定相同。 -- 具有可行性,可在有限步骤、有限时间、有限内存空间下完成。 -- 独立于编程语言,即可用多种语言实现。 - -## 1.2.2. 数据结构定义 - -「数据结构 Data Structure」是在计算机中组织与存储数据的方式。为了提高数据存储和操作性能,数据结构的设计原则有: - -- 空间占用尽可能小,节省计算机内存。 -- 数据操作尽量快,包括数据访问、添加、删除、更新等。 -- 提供简洁的数据表示和逻辑信息,以便算法高效运行。 - -数据结构的设计是一个充满权衡的过程,这意味着如果获得某方面的优势,则往往需要在另一方面做出妥协。例如,链表相对于数组,数据添加删除操作更加方便,但牺牲了数据的访问速度;图相对于链表,提供了更多的逻辑信息,但需要占用更多的内存空间。 - -## 1.2.3. 数据结构与算法的关系 - -「数据结构」与「算法」是高度相关、紧密嵌合的,体现在: - -- 数据结构是算法的底座。数据结构为算法提供结构化存储的数据,以及操作数据的对应方法。 -- 算法是发挥数据结构优势的舞台。数据结构仅存储数据信息,结合算法才可解决特定问题。 -- 算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。 - -![relationship_between_data_structure_and_algorithm](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) - -

Fig. 数据结构与算法的关系

- -如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。 - -
- -| 数据结构与算法 | LEGO 乐高 | -| -------------- | ---------------------------------------- | -| 输入数据 | 未拼装的积木 | -| 数据结构 | 积木组织形式,包括形状、大小、连接方式等 | -| 算法 | 把积木拼成目标形态的一系列操作步骤 | -| 输出数据 | 积木模型 | - -
- -!!! tip "约定俗成的简称" - - 在实际讨论中,我们通常会将「数据结构与算法」直接简称为「算法」。例如,我们熟称的 LeetCode 算法题目,实际上同时考察了数据结构和算法两部分知识。 diff --git a/build/chapter_preface/about_the_book.md b/build/chapter_preface/about_the_book.md deleted file mode 100644 index 12e5b7e93..000000000 --- a/build/chapter_preface/about_the_book.md +++ /dev/null @@ -1,265 +0,0 @@ ---- -comments: true ---- - -# 0.1. 关于本书 - -五年前发生的一件事,成为了我职业生涯的重要转折点。当时的我在交大读研,对互联网求职一无所知,但仍然硬着头皮申请了 Microsoft 软件工程师实习。面试官让我在白板上写出“快速排序”代码,我畏畏缩缩地写了一个“冒泡排序”,并且还写错了` (ToT) ` 。从面试官的表情上,我看到了一个大大的 "GG" 。 - -此次失利倒逼我开始刷算法题。我采用“扫雷游戏”式的学习方法,两眼一抹黑刷题,扫到不会的“雷”就通过查资料把它“排掉”,配合周期性总结,逐渐形成了数据结构与算法的知识图景。幸运地,我在秋招斩获了多家大厂的 Offer 。 - -回想自己当初在“扫雷式”刷题中被炸的满头包的痛苦,思考良久,我意识到一本“前期刷题必看”的读物可以使算法小白少走许多弯路。写作意愿滚滚袭来,那就动笔吧: - -

Hello,算法!

- -## 0.1.1. 读者对象 - -!!! success "前置条件" - - 您需要至少具备任一语言的编程基础,能够阅读和编写简单代码。 - -如果您是 **算法初学者**,完全没有接触过算法,或者已经有少量刷题,对数据结构与算法有朦胧的理解,在会与不会之间反复横跳,那么这本书就是为您而写!本书能够带来: - -- 了解刷题所需的 **数据结构**,包括常用操作、优势和劣势、典型应用、实现方法等。 -- 学习各类 **算法**,介绍算法的设计思想、运行效率、优势劣势、实现方法等。 -- 可一键运行的 **配套代码**,包含详细注释,帮助你通过实践加深理解。 - -如果您是 **算法熟练工**,已经积累一定刷题量,接触过大多数题型,那么本书内容对你来说可能稍显基础,但仍能够带来以下价值: - -- 本书篇幅不长,可以帮助你提纲挈领地回顾算法知识。 -- 书中包含许多对比性、总结性的算法内容,可以帮助你梳理算法知识体系。 -- 源代码实现了各种经典数据结构和算法,可以作为“刷题工具库”来使用。 - -如果您是 **算法大佬**,请受我膜拜!希望您可以抽时间提出意见建议,或者[一起参与创作](https://www.hello-algo.com/chapter_preface/contribution/),帮助各位同学获取更好的学习内容,感谢! - -## 0.1.2. 内容结构 - -本书主要内容分为复杂度分析、数据结构、算法三个部分。 - -![mindmap](about_the_book.assets/mindmap.png) - -

Fig. 知识点思维导图

- -### 复杂度分析 - -首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。 - -接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度** 和 **空间复杂度**,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。 - -### 数据结构 - -首先介绍了常用的 **基本数据类型** 、以及它们是如何在内存中存储的。 - -接下来,介绍了两种 **数据结构分类方法**,包括逻辑结构与物理结构。 - -后续展开介绍了 **数组、链表、栈、队列、散列表、树、堆、图** 等数据结构,关心以下内容: - -- 基本定义:数据结构的设计来源、存在意义; -- 主要特点:在各项数据操作中的优势、劣势; -- 常用操作:例如访问、更新、插入、删除、遍历、搜索等; -- 常见类型:在算法题或工程实际中,经常碰到的数据结构类型; -- 典型应用:此数据结构经常搭配哪些算法使用; -- 实现方法:对于重要的数据结构,将给出完整的实现示例; - -### 算法 - -包括 **查找算法、排序算法、搜索与回溯、动态规划、分治算法**,内容包括: - -- 基本定义:算法的设计思想; -- 主要特点:使用前置条件、优势和劣势; -- 算法效率:最差和平均时间复杂度、空间复杂度; -- 实现方法:完整的算法实现,以及优化措施; -- 示例题目:结合例题加深理解; - -## 0.1.3. 配套代码 - -完整代码托管在 [GitHub 仓库](https://github.com/krahets/hello-algo) ,皆可一键运行。 - -!!! tip "前置工作" - - 1. [编程环境安装](https://www.hello-algo.com/chapter_preface/installation/) ,若有请跳过 - 2. 代码下载与使用方法请见 [如何使用本书](https://www.hello-algo.com/chapter_preface/suggestions/#_4) - -## 0.1.4. 风格约定 - -- 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。 -- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。 -- 重点内容、总起句、总结句会被 **加粗**,此类文字值得特别关注。 -- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。 -- 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 - -=== "Java" - - ```java title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "C++" - - ```cpp title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Python" - - ```python title="" - """ 标题注释,用于标注函数、类、测试样例等 """ - - # 内容注释,用于详解代码 - - """ - 多行 - 注释 - """ - ``` - -=== "Go" - - ```go title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "JavaScript" - - ```js title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "TypeScript" - - ```typescript title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "C" - - ```c title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "C#" - - ```csharp title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Swift" - - ```swift title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Zig" - - ```zig title="" - // 标题注释,用于标注函数、类、测试样例等 - - // 内容注释,用于详解代码 - - // 多行 - // 注释 - ``` - -## 0.1.5. 本书特点 * - -??? abstract "默认折叠,可以跳过" - - **以实践为主**。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。 - - 本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。 - - 实践部分主要由示例和代码组成。代码配有简要注释,复杂示例会尽可能地使用视觉化的形式呈现。我强烈建议读者对照着代码自己敲一遍,如果时间有限,也至少逐行读、复制并运行一遍,配合着讲解将代码吃透。 - - **视觉化学习**。信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。 - - 近两年,短视频成为最受欢迎的信息媒介,可以在短时间内将高密度的信息“灌”给我们,有着极其舒适的观看体验。阅读则不然,读者与书本之间天然存在一种“疏离感”,我们看书会累、会走神、会停下来想其他事、会划下喜欢的句子、会思考某一片段的含义,这种疏离感给了读者与书本之间对话的可能,拓宽了想象空间。 - - 本书作为一本入门教材,希望可以保有书本的“慢节奏”,但也会避免与读者产生过多“疏离感”,而是努力将知识完整清晰地推送到你聪明的小脑袋瓜中。我将采用视觉化的方式(例如配图、动画),尽我可能清晰易懂地讲解复杂概念和抽象示例。 - - **内容精简化**。大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。 - - 引入一些生活案例或趣味内容,非常适合作为知识点的引子或者解释的补充,但当融入过多额外元素时,内容会稍显冗长,也许反而使读者容易迷失、抓不住重点,这也是本书需要避免的。 - - 敲代码如同写字,“美”是统一的追求。本书力求美观的代码,保证规范的变量命名、统一的空格与换行、对齐的缩进、整齐的注释等。 - -## 0.1.6. 致谢 - -本书的成书过程中,我获得了许多人的帮助,包括但不限于: - -- 感谢我的女朋友泡泡担任本书的首位读者,从算法小白的视角为本书的写作提出了许多建议,使这本书更加适合算法初学者来阅读。 -- 感谢腾宝、琦宝、飞宝为本书起了个响当当的名字,好听又有梗,直接唤起我最初敲下第一行代码 "Hello, World!" 的回忆。 -- 感谢我的导师李博,在小酌畅谈时您告诉我“觉得适合、想做就去做”,坚定了我写这本书的决心。 -- 感谢苏潼为本书设计了封面和 LOGO ,我有些强迫症,前后多次修改,谢谢你的耐心。 -- 感谢 @squidfunk ,包括 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 顶级开源项目以及给出的写作排版建议。 - -在写作过程中,我阅读了许多与数据结构与算法的书籍材料,学习到了许多知识,感谢前辈们的精彩创作。 - -感谢父母,你们一贯的支持与鼓励给了我自由度来做这些有趣的事。 - -## 0.1.7. 作者简介 - -![profile](about_the_book.assets/profile.png){: .center} - -

Krahets

- -
大厂高级算法工程师、算法爱好者
- -

力扣(LeetCode)全网阅读量最高博主

-

分享近百道算法题解,累积回复数千读者的评论问题

-

创作 LeetBook《图解算法数据结构》,已免费售出 22 万本

diff --git a/build/chapter_preface/contribution.md b/build/chapter_preface/contribution.md deleted file mode 100644 index 073c3f5f4..000000000 --- a/build/chapter_preface/contribution.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -comments: true ---- - -# 0.4. 一起参与创作 - -!!! success "开源的魅力" - - 纸质书籍的两次印刷的间隔时间往往需要数年,内容更新非常不方便。
但在本开源 HTML 书中,内容更迭的时间被缩短至数日甚至几个小时。 - -由于作者水平有限,书中内容难免疏漏谬误,请您谅解。此外,期待您可以一同参与本书的创作。如果发现笔误、无效链接、内容缺失、文字歧义、解释不清晰、行文结构不合理等问题,烦请您修正内容,以帮助其他读者获取更优质的学习内容。所有 [撰稿人](https://github.com/krahets/hello-algo/graphs/contributors) 将被展示在仓库主页,以感谢您对开源社区的无私奉献。 - -## 0.4.1. 修改文字与代码 - -每个页面的右上角都有一个「编辑」按钮,你可以按照以下步骤修改文章: - -1. 点击编辑按钮,如果遇到提示“需要 Fork 此仓库”,请通过; -2. 修改 Markdown 源文件内容; -3. 在页面底部填写更改说明,然后单击“Propose file change”按钮; -4. 页面跳转后,点击“Create pull request”按钮发起拉取请求即可,我会第一时间查看处理并及时更新内容。 - -![edit_markdown](contribution.assets/edit_markdown.png) - -## 0.4.2. 修改图片与动画 - -书中的配图无法直接修改,需要通过以下途径提出修改意见: - -1. 新建一个 Issue ,将需要修改的图片复制或截图,粘贴在面板中; -2. 描述图片问题,应如何修改; -3. 提交 Issue 即可,我会第一时间重新画图并替换图片。 - -## 0.4.3. 创作新内容 - -如果您想要创作新内容,例如 **重写章节、新增章节、修改代码、翻译代码至其他编程语言** 等,那么需要实施 Pull Request 工作流程: - -1. 登录 GitHub ,并 Fork [本仓库](https://github.com/krahets/hello-algo) 至个人账号; -2. 进入 Fork 仓库网页,使用 `git clone` 克隆该仓库至本地; -3. 在本地进行内容创作(建议通过运行测试来验证代码正确性); -4. 将本地更改 Commit ,并 Push 至远程仓库; -5. 刷新仓库网页,点击“Create pull request”按钮发起拉取请求(Pull Request)即可; - -非常欢迎您和我一同来创作本书! - -## 0.4.4. 本地部署 hello-algo - -### Docker - -请确保 Docker 已经安装并启动,并根据如下命令离线部署。 - -稍等片刻,即可使用浏览器打开 `http://localhost:8000` 访问本项目。 - -```bash -git clone https://github.com/krahets/hello-algo.git -cd hello-algo - -docker-compose up -d -``` - -使用如下命令即可删除部署。 - -```bash -docker-compose down -``` - -(TODO:教学视频) diff --git a/build/chapter_preface/installation.md b/build/chapter_preface/installation.md deleted file mode 100644 index adab95b21..000000000 --- a/build/chapter_preface/installation.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -comments: true ---- - -# 0.3. 编程环境安装 - -(TODO 视频教程) - -## 0.3.1. 安装 VSCode - -本书推荐使用开源轻量的 VSCode 作为本地 IDE ,下载并安装 [VSCode](https://code.visualstudio.com/) 。 - -## 0.3.2. Java 环境 - -1. 下载并安装 [OpenJDK](https://jdk.java.net/18/)(版本需满足 > JDK 9)。 -2. 在 VSCode 的插件市场中搜索 `java` ,安装 Java Extension Pack 。 - -## 0.3.3. C/C++ 环境 - -1. Windows 系统需要安装 [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([配置教程](https://glj0.netlify.app/d-%E8%BD%AF%E4%BB%B6%E6%8A%80%E8%83%BD/windows%20%E4%B8%8B%E4%BD%BF%E7%94%A8%20vscode%20+%20mingw%20%E5%AE%8C%E6%88%90%E7%AE%80%E5%8D%95%20c%20%E6%88%96%20cpp%20%E4%BB%A3%E7%A0%81%E7%9A%84%E8%BF%90%E8%A1%8C%E4%B8%8E%E8%B0%83%E8%AF%95/)),MacOS 自带 Clang 无需安装。 -2. 在 VSCode 的插件市场中搜索 `c++` ,安装 C/C++ Extension Pack 。 - -## 0.3.4. Python 环境 - -1. 下载并安装 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) 。 -2. 在 VSCode 的插件市场中搜索 `python` ,安装 Python Extension Pack 。 - -## 0.3.5. Go 环境 - -1. 下载并安装 [go](https://go.dev/dl/) 。 -2. 在 VSCode 的插件市场中搜索 `go` ,安装 Go 。 -3. 快捷键 `Ctrl + Shift + P` 呼出命令栏,输入 go ,选择 `Go: Install/Update Tools` ,全部勾选并安装即可。 - -## 0.3.6. JavaScript 环境 - -1. 下载并安装 [node.js](https://nodejs.org/en/) 。 -2. 在 VSCode 的插件市场中搜索 `javascript` ,安装 JavaScript (ES6) code snippets 。 - -## 0.3.7. C# 环境 - -1. 下载并安装 [.Net 6.0](https://dotnet.microsoft.com/en-us/download) ; -2. 在 VSCode 的插件市场中搜索 `c#` ,安装 c# 。 - -## 0.3.8. Swift 环境 - -1. 下载并安装 [Swift](https://www.swift.org/download/); -2. 在 VSCode 的插件市场中搜索 `swift`,安装 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang)。 - -## 0.3.9. Rust 环境 - -1. 下载并安装 [Rust](https://www.rust-lang.org/tools/install); -2. 在 VSCode 的插件市场中搜索 `rust`,安装 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)。 diff --git a/build/chapter_preface/suggestions.md b/build/chapter_preface/suggestions.md deleted file mode 100644 index 7907db787..000000000 --- a/build/chapter_preface/suggestions.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -comments: true ---- - -# 0.2. 如何使用本书 - -## 0.2.1. 图文搭配学 - -视频和图片相比于文字的信息密度和结构化程度更高,更容易让人理解。在本书中,重点和难点知识会主要以动画、图解的形式呈现,而文字的作用则是作为动画和图的解释与补充。 - -在阅读本书的过程中,若发现某段内容提供了动画或图解,**建议你以图为主线**,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。 - -![animation](suggestions.assets/animation.gif) - -## 0.2.2. 代码实践学 - -!!! tip "前置工作" - - 如果没有本地编程环境,可以参照下节 [编程环境安装](https://www.hello-algo.com/chapter_preface/installation/) 。 - -### 下载代码仓 - -如果已经安装 [Git](https://git-scm.com/downloads) ,可以通过命令行来克隆代码仓。 - -```shell -git clone https://github.com/krahets/hello-algo.git -``` - -当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。 - -![download_code](suggestions.assets/download_code.png) - -### 运行源代码 - -本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件**。 - -![code_md_to_repo](suggestions.assets/code_md_to_repo.png) - -这些源文件中包含详细注释,配有测试样例,可以直接运行,帮助你省去不必要的调试时间,可以将精力集中在学习内容上。 - -![running_code](suggestions.assets/running_code.gif) - -!!! tip "代码学习建议" - - 若学习时间紧张,**请至少将所有代码通读并运行一遍**。若时间允许,**强烈建议对照着代码自己敲一遍**,逐渐锻炼肌肉记忆。相比于读代码,写代码的过程往往能带来新的收获。 - -## 0.2.3. 提问讨论学 - -阅读本书时,请不要“惯着”那些弄不明白的知识点。如果有任何疑惑,**可以在评论区留下你的问题**,小伙伴们和我都会给予解答(您一般 3 天内会得到回复)。 - -同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家一起加油与进步! - -![comment](suggestions.assets/comment.gif) - -## 0.2.4. 算法学习“三步走” - -**第一阶段,算法入门,也正是本书的定位**。熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。 - -**第二阶段,刷算法题**。可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。 - -**第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。 - -![learning_route](suggestions.assets/learning_route.png) diff --git a/build/chapter_reference/index.md b/build/chapter_reference/index.md deleted file mode 100644 index fcc5da56f..000000000 --- a/build/chapter_reference/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# 参考文献 - -[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). - -[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). - -[3] 程杰. 大话数据结构. - -[4] 王争. 数据结构与算法之美. - -[5] 严蔚敏. 数据结构( C 语言版). - -[6] 邓俊辉. 数据结构( C++ 语言版,第三版). - -[7] 马克·艾伦·维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版). - -[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). diff --git a/build/chapter_searching/binary_search.md b/build/chapter_searching/binary_search.md deleted file mode 100755 index 9ac254cec..000000000 --- a/build/chapter_searching/binary_search.md +++ /dev/null @@ -1,555 +0,0 @@ ---- -comments: true ---- - -# 10.2. 二分查找 - -「二分查找 Binary Search」利用数据的有序性,通过每轮缩小一半搜索区间来查找目标元素。 - -使用二分查找有两个前置条件: - -- **要求输入数据是有序的**,这样才能通过判断大小关系来排除一半的搜索区间; -- **二分查找仅适用于数组**,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。 - -## 10.2.1. 算法实现 - -给定一个长度为 $n$ 的排序数组 `nums` ,元素从小到大排列。数组的索引取值范围为 - -$$ -0, 1, 2, \cdots, n-1 -$$ - -使用「区间」来表示这个取值范围的方法主要有两种: - -1. **双闭区间 $[0, n-1]$** ,即两个边界都包含自身;此方法下,区间 $[0, 0]$ 仍包含一个元素; -2. **左闭右开 $[0, n)$** ,即左边界包含自身、右边界不包含自身;此方法下,区间 $[0, 0)$ 为空; - -### “双闭区间”实现 - -首先,我们先采用“双闭区间”的表示,在数组 `nums` 中查找目标元素 `target` 的对应索引。 - -=== "Step 1" - ![binary_search_step1](binary_search.assets/binary_search_step1.png) - -=== "Step 2" - ![binary_search_step2](binary_search.assets/binary_search_step2.png) - -=== "Step 3" - ![binary_search_step3](binary_search.assets/binary_search_step3.png) - -=== "Step 4" - ![binary_search_step4](binary_search.assets/binary_search_step4.png) - -=== "Step 5" - ![binary_search_step5](binary_search.assets/binary_search_step5.png) - -=== "Step 6" - ![binary_search_step6](binary_search.assets/binary_search_step6.png) - -=== "Step 7" - ![binary_search_step7](binary_search.assets/binary_search_step7.png) - -二分查找“双闭区间”表示下的代码如下所示。 - -=== "Java" - - ```java title="binary_search.java" - /* 二分查找(双闭区间) */ - int binarySearch(int[] nums, int target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C++" - - ```cpp title="binary_search.cpp" - /* 二分查找(双闭区间) */ - int binarySearch(vector& nums, int target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.size() - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Python" - - ```python title="binary_search.py" - """ 二分查找(双闭区间) """ - def binary_search(nums, target): - # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - i, j = 0, len(nums) - 1 - while i <= j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: # 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1 - elif nums[m] > target: # 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1 - else: - return m # 找到目标元素,返回其索引 - return -1 # 未找到目标元素,返回 -1 - ``` - -=== "Go" - - ```go title="binary_search.go" - /* 二分查找(双闭区间) */ - func binarySearch(nums []int, target int) int { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - i, j := 0, len(nums)-1 - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - for i <= j { - m := (i + j) / 2 // 计算中点索引 m - if nums[m] < target { // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1 - } else if nums[m] > target { // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1 - } else { // 找到目标元素,返回其索引 - return m - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "JavaScript" - - ```js title="binary_search.js" - /* 二分查找(双闭区间) */ - function binarySearch(nums, target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - let i = 0, j = nums.length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整 - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - else - return m; // 找到目标元素,返回其索引 - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search.ts" - /* 二分查找(双闭区间) */ - const binarySearch = function (nums: number[], target: number): number { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - let i = 0, j = nums.length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m - if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - } else { // 找到目标元素,返回其索引 - return m; - } - } - return -1; // 未找到目标元素,返回 -1 - } - ``` - -=== "C" - - ```c title="binary_search.c" - - ``` - -=== "C#" - - ```csharp title="binary_search.cs" - /* 二分查找(双闭区间) */ - int binarySearch(int[] nums, int target) - { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.Length - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) - { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Swift" - - ```swift title="binary_search.swift" - /* 二分查找(双闭区间) */ - func binarySearch(nums: [Int], target: Int) -> Int { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - var i = 0 - var j = nums.count - 1 - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while i <= j { - let m = (i + j) / 2 // 计算中点索引 m - if nums[m] < target { // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1 - } else if nums[m] > target { // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1 - } else { // 找到目标元素,返回其索引 - return m - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "Zig" - - ```zig title="binary_search.zig" - - ``` - -### “左闭右开”实现 - -当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。 - -=== "Java" - - ```java title="binary_search.java" - /* 二分查找(左闭右开) */ - int binarySearch1(int[] nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 - j = m; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C++" - - ```cpp title="binary_search.cpp" - /* 二分查找(左闭右开) */ - int binarySearch1(vector& nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.size(); - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 - j = m; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Python" - - ```python title="binary_search.py" - """ 二分查找(左闭右开) """ - def binary_search1(nums, target): - # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - i, j = 0, len(nums) - # 循环,当搜索区间为空时跳出(当 i = j 时为空) - while i < j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: # 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1 - elif nums[m] > target: # 此情况说明 target 在区间 [i, m) 中 - j = m - else: # 找到目标元素,返回其索引 - return m - return -1 # 未找到目标元素,返回 -1 - ``` - -=== "Go" - - ```go title="binary_search.go" - /* 二分查找(左闭右开) */ - func binarySearch1(nums []int, target int) int { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - i, j := 0, len(nums) - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - for i < j { - m := (i + j) / 2 // 计算中点索引 m - if nums[m] < target { // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1 - } else if nums[m] > target { // 此情况说明 target 在区间 [i, m) 中 - j = m - } else { // 找到目标元素,返回其索引 - return m - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "JavaScript" - - ```js title="binary_search.js" - /* 二分查找(左闭右开) */ - function binarySearch1(nums, target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - let i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整 - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 - j = m; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search.ts" - /* 二分查找(左闭右开) */ - const binarySearch1 = function (nums: number[], target: number): number { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - let i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m - if (nums[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - } else if (nums[m] > target) { // 此情况说明 target 在区间 [i, m) 中 - j = m; - } else { // 找到目标元素,返回其索引 - return m; - } - } - return -1; // 未找到目标元素,返回 -1 - } - ``` - -=== "C" - - ```c title="binary_search.c" - - ``` - -=== "C#" - - ```csharp title="binary_search.cs" - /* 二分查找(左闭右开) */ - int binarySearch1(int[] nums, int target) - { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.Length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) - { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 - j = m; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Swift" - - ```swift title="binary_search.swift" - /* 二分查找(左闭右开) */ - func binarySearch1(nums: [Int], target: Int) -> Int { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - var i = 0 - var j = nums.count - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while i < j { - let m = (i + j) / 2 // 计算中点索引 m - if nums[m] < target { // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1 - } else if nums[m] > target { // 此情况说明 target 在区间 [i, m) 中 - j = m - } else { // 找到目标元素,返回其索引 - return m - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "Zig" - - ```zig title="binary_search.zig" - - ``` - -### 两种表示对比 - -对比下来,两种表示的代码写法有以下不同点: - -
- -| 表示方法 | 初始化指针 | 缩小区间 | 循环终止条件 | -| ------------------- | ------------------- | ------------------------- | ------------ | -| 双闭区间 $[0, n-1]$ | $i = 0$ , $j = n-1$ | $i = m + 1$ , $j = m - 1$ | $i > j$ | -| 左闭右开 $[0, n)$ | $i = 0$ , $j = n$ | $i = m + 1$ , $j = m$ | $i = j$ | - -
- -观察发现,在“双闭区间”表示中,由于对左右两边界的定义是相同的,因此缩小区间的 $i$ , $j$ 处理方法也是对称的,这样更不容易出错。综上所述,**建议你采用“双闭区间”的写法。** - -### 大数越界处理 - -当数组长度很大时,加法 $i + j$ 的结果有可能会超出 `int` 类型的取值范围。在此情况下,我们需要换一种计算中点的写法。 - -=== "Java" - - ```java title="" - // (i + j) 有可能超出 int 的取值范围 - int m = (i + j) / 2; - // 更换为此写法则不会越界 - int m = i + (j - i) / 2; - ``` - -=== "C++" - - ```cpp title="" - // (i + j) 有可能超出 int 的取值范围 - int m = (i + j) / 2; - // 更换为此写法则不会越界 - int m = i + (j - i) / 2; - ``` - -=== "Python" - - ```py title="" - # Python 中的数字理论上可以无限大(取决于内存大小) - # 因此无需考虑大数越界问题 - ``` - -=== "Go" - - ```go title="" - // (i + j) 有可能超出 int 的取值范围 - m := (i + j) / 2 - // 更换为此写法则不会越界 - m := i + (j - i) / 2 - ``` - -=== "JavaScript" - - ```js title="" - // (i + j) 有可能超出 int 的取值范围 - let m = parseInt((i + j) / 2); - // 更换为此写法则不会越界 - let m = parseInt(i + (j - i) / 2); - ``` - -=== "TypeScript" - - ```typescript title="" - // (i + j) 有可能超出 Number 的取值范围 - let m = Math.floor((i + j) / 2); - // 更换为此写法则不会越界 - let m = Math.floor(i + (j - i) / 2); - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - // (i + j) 有可能超出 int 的取值范围 - int m = (i + j) / 2; - // 更换为此写法则不会越界 - int m = i + (j - i) / 2; - ``` - -=== "Swift" - - ```swift title="" - // (i + j) 有可能超出 int 的取值范围 - let m = (i + j) / 2 - // 更换为此写法则不会越界 - let m = i + (j - 1) / 2 - ``` - -=== "Zig" - - ```zig title="" - - ``` - -## 10.2.2. 复杂度分析 - -**时间复杂度 $O(\log n)$** :其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 - -**空间复杂度 $O(1)$** :指针 `i` , `j` 使用常数大小空间。 - -## 10.2.3. 优点与缺点 - -二分查找效率很高,体现在: - -- **二分查找时间复杂度低**。对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。 -- **二分查找不需要额外空间**。相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。 - -但并不意味着所有情况下都应使用二分查找,这是因为: - -- **二分查找仅适用于有序数据**。如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 -- **二分查找仅适用于数组**。由于在二分查找中,访问索引是 “非连续” 的,因此链表或者基于链表实现的数据结构都无法使用。 -- **在小数据量下,线性查找的性能更好**。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。 diff --git a/build/chapter_searching/hashing_search.md b/build/chapter_searching/hashing_search.md deleted file mode 100755 index 157e67a2e..000000000 --- a/build/chapter_searching/hashing_search.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -comments: true ---- - -# 10.3. 哈希查找 - -!!! question - - 在数据量很大时,「线性查找」太慢;而「二分查找」要求数据必须是有序的,并且只能在数组中应用。那么是否有方法可以同时避免上述缺点呢?答案是肯定的,此方法被称为「哈希查找」。 - -「哈希查找 Hash Searching」借助一个哈希表来存储需要的「键值对 Key Value Pair」,我们可以在 $O(1)$ 时间下实现“键 $\rightarrow$ 值”映射查找,体现着“以空间换时间”的算法思想。 - -## 10.3.1. 算法实现 - -如果我们想要给定数组中的一个目标元素 `target` ,获取该元素的索引,那么可以借助一个哈希表实现查找。 - -![hash_search_index](hashing_search.assets/hash_search_index.png) - -=== "Java" - - ```java title="hashing_search.java" - /* 哈希查找(数组) */ - int hashingSearchArray(Map map, int target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.getOrDefault(target, -1); - } - ``` - -=== "C++" - - ```cpp title="hashing_search.cpp" - /* 哈希查找(数组) */ - int hashingSearchArray(unordered_map map, int target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - if (map.find(target) == map.end()) - return -1; - return map[target]; - } - ``` - -=== "Python" - - ```python title="hashing_search.py" - """ 哈希查找(数组) """ - def hashing_search_array(mapp, target): - # 哈希表的 key: 目标元素,value: 索引 - # 若哈希表中无此 key ,返回 -1 - return mapp.get(target, -1) - ``` - -=== "Go" - - ```go title="hashing_search.go" - /* 哈希查找(数组) */ - func hashingSearchArray(m map[int]int, target int) int { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - if index, ok := m[target]; ok { - return index - } else { - return -1 - } - } - ``` - -=== "JavaScript" - - ```js title="hashing_search.js" - /* 哈希查找(数组) */ - function hashingSearchArray(map, target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.has(target) ? map.get(target) : -1; - } - ``` - -=== "TypeScript" - - ```typescript title="hashing_search.ts" - /* 哈希查找(数组) */ - function hashingSearchArray(map: Map, target: number): number { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.has(target) ? map.get(target) as number : -1; - } - ``` - -=== "C" - - ```c title="hashing_search.c" - - ``` - -=== "C#" - - ```csharp title="hashing_search.cs" - /* 哈希查找(数组) */ - int hashingSearchArray(Dictionary map, int target) - { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map.GetValueOrDefault(target, -1); - } - ``` - -=== "Swift" - - ```swift title="hashing_search.swift" - /* 哈希查找(数组) */ - func hashingSearchArray(map: [Int: Int], target: Int) -> Int { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - return map[target, default: -1] - } - ``` - -=== "Zig" - - ```zig title="hashing_search.zig" - - ``` - -再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 - -![hash_search_listnode](hashing_search.assets/hash_search_listnode.png) - -=== "Java" - - ```java title="hashing_search.java" - /* 哈希查找(链表) */ - ListNode hashingSearchLinkedList(Map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.getOrDefault(target, null); - } - ``` - -=== "C++" - - ```cpp title="hashing_search.cpp" - /* 哈希查找(链表) */ - ListNode* hashingSearchLinkedList(unordered_map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 nullptr - if (map.find(target) == map.end()) - return nullptr; - return map[target]; - } - ``` - -=== "Python" - - ```python title="hashing_search.py" - """ 哈希查找(链表) """ - def hashing_search_linkedlist(mapp, target): - # 哈希表的 key: 目标元素,value: 结点对象 - # 若哈希表中无此 key ,返回 -1 - return mapp.get(target, -1) - ``` - -=== "Go" - - ```go title="hashing_search.go" - /* 哈希查找(链表) */ - func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 nil - if node, ok := m[target]; ok { - return node - } else { - return nil - } - } - ``` - -=== "JavaScript" - - ```js title="hashing_search.js" - /* 哈希查找(链表) */ - function hashingSearchLinkedList(map, target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.has(target) ? map.get(target) : null; - } - ``` - -=== "TypeScript" - - ```typescript title="hashing_search.ts" - /* 哈希查找(链表) */ - function hashingSearchLinkedList(map: Map, target: number): ListNode | null { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.has(target) ? map.get(target) as ListNode : null; - } - ``` - -=== "C" - - ```c title="hashing_search.c" - - ``` - -=== "C#" - - ```csharp title="hashing_search.cs" - /* 哈希查找(链表) */ - ListNode? hashingSearchLinkedList(Dictionary map, int target) - { - - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map.GetValueOrDefault(target); - } - ``` - -=== "Swift" - - ```swift title="hashing_search.swift" - /* 哈希查找(链表) */ - func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 null - return map[target] - } - ``` - -=== "Zig" - - ```zig title="hashing_search.zig" - - ``` - -## 10.3.2. 复杂度分析 - -**时间复杂度 $O(1)$** :哈希表的查找操作使用 $O(1)$ 时间。 - -**空间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 - -## 10.3.3. 优点与缺点 - -在哈希表中,**查找、插入、删除操作的平均时间复杂度都为 $O(1)$** ,这意味着无论是高频增删还是高频查找场景,哈希查找的性能表现都非常好。当然,一切的前提是保证哈希表未退化。 - -即使如此,哈希查找仍存在一些问题,在实际应用中,需要根据情况灵活选择方法。 - -- 辅助哈希表 **需要使用 $O(n)$ 的额外空间**,意味着需要预留更多的计算机内存; -- 建立和维护哈希表需要时间,因此哈希查找 **不适合高频增删、低频查找的使用场景**; -- 当哈希冲突严重时,哈希表会退化为链表,**时间复杂度劣化至 $O(n)$** ; -- **当数据量很小时,线性查找比哈希查找更快**。这是因为计算哈希映射函数可能比遍历一个小型数组更慢; diff --git a/build/chapter_searching/linear_search.md b/build/chapter_searching/linear_search.md deleted file mode 100755 index 5a8bb05fa..000000000 --- a/build/chapter_searching/linear_search.md +++ /dev/null @@ -1,322 +0,0 @@ ---- -comments: true ---- - -# 10.1. 线性查找 - -「线性查找 Linear Search」是一种最基础的查找方法,其从数据结构的一端开始,依次访问每个元素,直到另一端后停止。 - -## 10.1.1. 算法实现 - -线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 `nums` 中查找目标元素 `target` 的对应索引,那么可以在数组中进行线性查找。 - -![linear_search](linear_search.assets/linear_search.png) - -=== "Java" - - ```java title="linear_search.java" - /* 线性查找(数组) */ - int linearSearchArray(int[] nums, int target) { - // 遍历数组 - for (int i = 0; i < nums.length; i++) { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C++" - - ```cpp title="linear_search.cpp" - /* 线性查找(数组) */ - int linearSearchArray(vector& nums, int target) { - // 遍历数组 - for (int i = 0; i < nums.size(); i++) { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Python" - - ```python title="linear_search.py" - """ 线性查找(数组) """ - def linear_search_array(nums, target): - # 遍历数组 - for i in range(len(nums)): - if nums[i] == target: # 找到目标元素,返回其索引 - return i - return -1 # 未找到目标元素,返回 -1 - ``` - -=== "Go" - - ```go title="linear_search.go" - /* 线性查找(数组) */ - func linearSearchArray(nums []int, target int) int { - // 遍历数组 - for i := 0; i < len(nums); i++ { - // 找到目标元素,返回其索引 - if nums[i] == target { - return i - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "JavaScript" - - ```js title="linear_search.js" - /* 线性查找(数组) */ - function linearSearchArray(nums, target) { - // 遍历数组 - for (let i = 0; i < nums.length; i++) { - // 找到目标元素,返回其索引 - if (nums[i] === target) { - return i; - } - } - // 未找到目标元素,返回 -1 - return -1; - } - - ``` - -=== "TypeScript" - - ```typescript title="linear_search.ts" - /* 线性查找(数组)*/ - function linearSearchArray(nums: number[], target: number): number { - // 遍历数组 - for (let i = 0; i < nums.length; i++) { - // 找到目标元素,返回其索引 - if (nums[i] === target) { - return i; - } - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C" - - ```c title="linear_search.c" - - ``` - -=== "C#" - - ```csharp title="linear_search.cs" - /* 线性查找(数组) */ - int linearSearchArray(int[] nums, int target) - { - // 遍历数组 - for (int i = 0; i < nums.Length; i++) - { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } - - ``` - -=== "Swift" - - ```swift title="linear_search.swift" - /* 线性查找(数组) */ - func linearSearchArray(nums: [Int], target: Int) -> Int { - // 遍历数组 - for i in nums.indices { - // 找到目标元素,返回其索引 - if nums[i] == target { - return i - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "Zig" - - ```zig title="linear_search.zig" - - ``` - -再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。 - -=== "Java" - - ```java title="linear_search.java" - /* 线性查找(链表) */ - ListNode linearSearchLinkedList(ListNode head, int target) { - // 遍历链表 - while (head != null) { - // 找到目标结点,返回之 - if (head.val == target) - return head; - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "C++" - - ```cpp title="linear_search.cpp" - /* 线性查找(链表) */ - ListNode* linearSearchLinkedList(ListNode* head, int target) { - // 遍历链表 - while (head != nullptr) { - // 找到目标结点,返回之 - if (head->val == target) - return head; - head = head->next; - } - // 未找到目标结点,返回 nullptr - return nullptr; - } - ``` - -=== "Python" - - ```python title="linear_search.py" - """ 线性查找(链表) """ - def linear_search_linkedlist(head, target): - # 遍历链表 - while head: - if head.val == target: # 找到目标结点,返回之 - return head - head = head.next - return None # 未找到目标结点,返回 None - ``` - -=== "Go" - - ```go title="linear_search.go" - /* 线性查找(链表)*/ - func linerSearchLinkedList(node *ListNode, target int) *ListNode { - // 遍历链表 - for node != nil { - // 找到目标结点,返回之 - if node.Val == target { - return node - } - node = node.Next - } - // 未找到目标元素,返回 nil - return nil - } - ``` - -=== "JavaScript" - - ```js title="linear_search.js" - /* 线性查找(链表)*/ - function linearSearchLinkedList(head, target) { - // 遍历链表 - while(head) { - // 找到目标结点,返回之 - if(head.val === target) { - return head; - } - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "TypeScript" - - ```typescript title="linear_search.ts" - /* 线性查找(链表)*/ - function linearSearchLinkedList(head: ListNode | null, target: number): ListNode | null { - // 遍历链表 - while (head) { - // 找到目标结点,返回之 - if (head.val === target) { - return head; - } - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "C" - - ```c title="linear_search.c" - - ``` - -=== "C#" - - ```csharp title="linear_search.cs" - /* 线性查找(链表) */ - ListNode? linearSearchLinkedList(ListNode head, int target) - { - // 遍历链表 - while (head != null) - { - // 找到目标结点,返回之 - if (head.val == target) - return head; - head = head.next; - } - // 未找到目标结点,返回 null - return null; - } - ``` - -=== "Swift" - - ```swift title="linear_search.swift" - /* 线性查找(链表) */ - func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { - var head = head - // 遍历链表 - while head != nil { - // 找到目标结点,返回之 - if head?.val == target { - return head - } - head = head?.next - } - // 未找到目标结点,返回 null - return nil - } - ``` - -=== "Zig" - - ```zig title="linear_search.zig" - - ``` - -## 10.1.2. 复杂度分析 - -**时间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 - -**空间复杂度 $O(1)$** :无需使用额外空间。 - -## 10.1.3. 优点与缺点 - -**线性查找的通用性极佳**。由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。 - -**线性查找的时间复杂度太高**。在数据量 $n$ 很大时,查找效率很低。 diff --git a/build/chapter_searching/summary.md b/build/chapter_searching/summary.md deleted file mode 100644 index 738255889..000000000 --- a/build/chapter_searching/summary.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -comments: true ---- - -# 10.4. 小结 - -- 线性查找是一种最基础的查找方法,通过遍历数据结构 + 判断条件实现查找。 -- 二分查找利用数据的有序性,通过循环不断缩小一半搜索区间来实现查找,其要求输入数据是有序的,并且仅适用于数组或基于数组实现的数据结构。 -- 哈希查找借助哈希表来实现常数阶时间复杂度的查找操作,体现以空间换时间的算法思想。 - -

Table. 三种查找方法对比

- -
- -| | 线性查找 | 二分查找 | 哈希查找 | -| ------------------------------------- | ------------------------ | ----------------------------- | ------------------------ | -| 适用数据结构 | 数组、链表 | 数组 | 数组、链表 | -| 输入数据要求 | 无 | 有序 | 无 | -| 平均时间复杂度
查找 / 插入 / 删除 | $O(n)$ / $O(1)$ / $O(n)$ | $O(\log n)$ / $O(n)$ / $O(n)$ | $O(1)$ / $O(1)$ / $O(1)$ | -| 最差时间复杂度
查找 / 插入 / 删除 | $O(n)$ / $O(1)$ / $O(n)$ | $O(\log n)$ / $O(n)$ / $O(n)$ | $O(n)$ / $O(n)$ / $O(n)$ | -| 空间复杂度 | $O(1)$ | $O(1)$ | $O(n)$ | - -
diff --git a/build/chapter_sorting/bubble_sort.md b/build/chapter_sorting/bubble_sort.md deleted file mode 100755 index e23edcaab..000000000 --- a/build/chapter_sorting/bubble_sort.md +++ /dev/null @@ -1,465 +0,0 @@ ---- -comments: true ---- - -# 11.2. 冒泡排序 - -「冒泡排序 Bubble Sort」是一种最基础的排序算法,非常适合作为第一个学习的排序算法。顾名思义,「冒泡」是该算法的核心操作。 - -!!! question "为什么叫“冒泡”" - - 在水中,越大的泡泡浮力越大,所以最大的泡泡会最先浮到水面。 - -「冒泡」操作则是在模拟上述过程,具体做法为:从数组最左端开始向右遍历,依次对比相邻元素大小,若 **左元素 > 右元素** 则将它俩交换,最终可将最大元素移动至数组最右端。 - -完成此次冒泡操作后,**数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素**。 - -=== "Step 1" - ![bubble_operation_step1](bubble_sort.assets/bubble_operation_step1.png) - -=== "Step 2" - ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) - -=== "Step 3" - ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) - -=== "Step 4" - ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) - -=== "Step 5" - ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) - -=== "Step 6" - ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) - -=== "Step 7" - ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) - -

Fig. 冒泡操作

- -## 11.2.1. 算法流程 - -1. 设数组长度为 $n$ ,完成第一轮「冒泡」后,数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素。 -2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。 -3. 以此类推…… **循环 $n - 1$ 轮「冒泡」,即可完成整个数组的排序**。 - -![bubble_sort](bubble_sort.assets/bubble_sort.png) - -

Fig. 冒泡排序流程

- -=== "Java" - - ```java title="bubble_sort.java" - /* 冒泡排序 */ - void bubbleSort(int[] nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - } - } - } - } - ``` - -=== "C++" - - ```cpp title="bubble_sort.cpp" - /* 冒泡排序 */ - void bubbleSort(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - // 这里使用了 std::swap() 函数 - swap(nums[j], nums[j + 1]); - } - } - } - } - ``` - -=== "Python" - - ```python title="bubble_sort.py" - """ 冒泡排序 """ - def bubble_sort(nums): - n = len(nums) - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(n - 1, 0, -1): - # 内循环:冒泡操作 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j + 1] = nums[j + 1], nums[j] - ``` - -=== "Go" - - ```go title="bubble_sort.go" - /* 冒泡排序 */ - func bubbleSort(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := len(nums) - 1; i > 0; i-- { - // 内循环:冒泡操作 - for j := 0; j < i; j++ { - if nums[j] > nums[j+1] { - // 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j+1] = nums[j+1], nums[j] - } - } - } - } - ``` - -=== "JavaScript" - - ```js title="bubble_sort.js" - /* 冒泡排序 */ - function bubbleSort(nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - } - } - } - } - ``` - -=== "TypeScript" - - ```typescript title="bubble_sort.ts" - /* 冒泡排序 */ - function bubbleSort(nums: number[]): void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - } - } - } - } - ``` - -=== "C" - - ```c title="bubble_sort.c" - /* 冒泡排序 */ - void bubbleSort(int nums[], int size) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = 0; i < size - 1; i++) - { - // 内循环:冒泡操作 - for (int j = 0; j < size - 1 - i; j++) - { - if (nums[j] > nums[j + 1]) - { - int temp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = temp; - } - } - } - } - ``` - -=== "C#" - - ```csharp title="bubble_sort.cs" - /* 冒泡排序 */ - void bubbleSort(int[] nums) - { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; i > 0; i--) - { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) - { - if (nums[j] > nums[j + 1]) - { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - } - } - } - } - ``` - -=== "Swift" - - ```swift title="bubble_sort.swift" - /* 冒泡排序 */ - func bubbleSort(nums: inout [Int]) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in stride(from: nums.count - 1, to: 0, by: -1) { - // 内循环:冒泡操作 - for j in stride(from: 0, to: i, by: 1) { - if nums[j] > nums[j + 1] { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - } - } - } - } - ``` - -=== "Zig" - - ```zig title="bubble_sort.zig" - - ``` - -## 11.2.2. 算法特性 - -**时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 - -**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 - -**原地排序**:指针变量仅使用常数大小额外空间。 - -**稳定排序**:不交换相等元素。 - -**自适应排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 - -## 11.2.3. 效率优化 - -我们发现,若在某轮「冒泡」中未执行任何交换操作,则说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 `flag` 来监听该情况,若出现则直接返回。 - -优化后,冒泡排序的最差和平均时间复杂度仍为 $O(n^2)$ ;而在输入数组 **已排序** 时,达到 **最佳时间复杂度** $O(n)$ 。 - -=== "Java" - - ```java title="bubble_sort.java" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(int[] nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.length - 1; i > 0; i--) { - boolean flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "C++" - - ```cpp title="bubble_sort.cpp" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - // 这里使用了 std::swap() 函数 - swap(nums[j], nums[j + 1]); - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "Python" - - ```python title="bubble_sort.py" - """ 冒泡排序(标志优化) """ - def bubble_sort_with_flag(nums): - n = len(nums) - # 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in range(n - 1, 0, -1): - flag = False # 初始化标志位 - # 内循环:冒泡操作 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j + 1] = nums[j + 1], nums[j] - flag = True # 记录交换元素 - if not flag: - break # 此轮冒泡未交换任何元素,直接跳出 - ``` - -=== "Go" - - ```go title="bubble_sort.go" - /* 冒泡排序(标志优化)*/ - func bubbleSortWithFlag(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := len(nums) - 1; i > 0; i-- { - flag := false // 初始化标志位 - // 内循环:冒泡操作 - for j := 0; j < i; j++ { - if nums[j] > nums[j+1] { - // 交换 nums[j] 与 nums[j + 1] - nums[j], nums[j+1] = nums[j+1], nums[j] - flag = true // 记录交换元素 - } - } - if flag == false { // 此轮冒泡未交换任何元素,直接跳出 - break - } - } - } - ``` - -=== "JavaScript" - - ```js title="bubble_sort.js" - /* 冒泡排序(标志优化)*/ - function bubbleSortWithFlag(nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - let flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "TypeScript" - - ```typescript title="bubble_sort.ts" - /* 冒泡排序(标志优化)*/ - function bubbleSortWithFlag(nums: number[]): void { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (let i = nums.length - 1; i > 0; i--) { - let flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (let j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "C" - - ```c title="bubble_sort.c" - /* 冒泡排序 */ - void bubbleSortWithFlag(int nums[], int size) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = 0; i < size - 1; i++) - { - bool flag = false; - // 内循环:冒泡操作 - for (int j = 0; j < size - 1 - i; j++) - { - if (nums[j] > nums[j + 1]) - { - int temp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = temp; - flag = true; - } - } - if(!flag) break; - } - } - ``` - -=== "C#" - - ```csharp title="bubble_sort.cs" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(int[] nums) - { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.Length - 1; i > 0; i--) - { - bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) - { - if (nums[j] > nums[j + 1]) - { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "Swift" - - ```swift title="bubble_sort.swift" - /* 冒泡排序(标志优化)*/ - func bubbleSortWithFlag(nums: inout [Int]) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in stride(from: nums.count - 1, to: 0, by: -1) { - var flag = false // 初始化标志位 - for j in stride(from: 0, to: i, by: 1) { - if nums[j] > nums[j + 1] { - // 交换 nums[j] 与 nums[j + 1] - let tmp = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - flag = true // 记录交换元素 - } - } - if !flag { // 此轮冒泡未交换任何元素,直接跳出 - break - } - } - } - ``` - -=== "Zig" - - ```zig title="bubble_sort.zig" - - ``` diff --git a/build/chapter_sorting/insertion_sort.md b/build/chapter_sorting/insertion_sort.md deleted file mode 100755 index 0c3cee929..000000000 --- a/build/chapter_sorting/insertion_sort.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -comments: true ---- - -# 11.3. 插入排序 - -「插入排序 Insertion Sort」是一种基于 **数组插入操作** 的排序算法。 - -「插入操作」原理:选定某个待排序元素为基准数 `base`,将 `base` 与其左侧已排序区间元素依次对比大小,并插入到正确位置。 - -回忆数组插入操作,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后再将 `base` 赋值给目标索引。 - -![insertion_operation](insertion_sort.assets/insertion_operation.png) - -

Fig. 插入操作

- -## 11.3.1. 算法流程 - -1. 第 1 轮先选取数组的 **第 2 个元素** 为 `base` ,执行「插入操作」后,**数组前 2 个元素已完成排序**。 -2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后,**数组前 3 个元素已完成排序**。 -3. 以此类推……最后一轮选取 **数组尾元素** 为 `base` ,执行「插入操作」后,**所有元素已完成排序**。 - -![insertion_sort](insertion_sort.assets/insertion_sort.png) - -

Fig. 插入排序流程

- -=== "Java" - - ```java title="insertion_sort.java" - /* 插入排序 */ - void insertionSort(int[] nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.length; i++) { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "C++" - - ```cpp title="insertion_sort.cpp" - /* 插入排序 */ - void insertionSort(vector& nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.size(); i++) { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "Python" - - ```python title="insertion_sort.py" - """ 插入排序 """ - def insertion_sort(nums): - # 外循环:base = nums[1], nums[2], ..., nums[n-1] - for i in range(1, len(nums)): - base = nums[i] - j = i - 1 - # 内循环:将 base 插入到左边的正确位置 - while j >= 0 and nums[j] > base: - nums[j + 1] = nums[j] # 1. 将 nums[j] 向右移动一位 - j -= 1 - nums[j + 1] = base # 2. 将 base 赋值到正确位置 - ``` - -=== "Go" - - ```go title="insertion_sort.go" - /* 插入排序 */ - func insertionSort(nums []int) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i := 1; i < len(nums); i++ { - base := nums[i] - j := i - 1 - // 内循环:将 base 插入到左边的正确位置 - for j >= 0 && nums[j] > base { - nums[j+1] = nums[j] // 1. 将 nums[j] 向右移动一位 - j-- - } - nums[j+1] = base // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "JavaScript" - - ```js title="insertion_sort.js" - /* 插入排序 */ - function insertionSort(nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (let i = 1; i < nums.length; i++) { - let base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "TypeScript" - - ```typescript title="insertion_sort.ts" - /* 插入排序 */ - function insertionSort(nums: number[]): void { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (let i = 1; i < nums.length; i++) { - const base = nums[i]; - let j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "C" - - ```c title="insertion_sort.c" - /* 插入排序 */ - void insertionSort(int nums[], int size) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < size; i++) - { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) - { - // 1. 将 nums[j] 向右移动一位 - nums[j + 1] = nums[j]; - j--; - } - // 2. 将 base 赋值到正确位置 - nums[j + 1] = base; - } - } - ``` - -=== "C#" - - ```csharp title="insertion_sort.cs" - /* 插入排序 */ - void insertionSort(int[] nums) - { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.Length; i++) - { - int bas = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > bas) - { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = bas; // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "Swift" - - ```swift title="insertion_sort.swift" - /* 插入排序 */ - func insertionSort(nums: inout [Int]) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for i in stride(from: 1, to: nums.count, by: 1) { - let base = nums[i] - var j = i - 1 - // 内循环:将 base 插入到左边的正确位置 - while j >= 0, nums[j] > base { - nums[j + 1] = nums[j] // 1. 将 nums[j] 向右移动一位 - j -= 1 - } - nums[j + 1] = base // 2. 将 base 赋值到正确位置 - } - } - ``` - -=== "Zig" - - ```zig title="insertion_sort.zig" - - ``` - -## 11.3.2. 算法特性 - -**时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 - -**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 - -**原地排序**:指针变量仅使用常数大小额外空间。 - -**稳定排序**:不交换相等元素。 - -**自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。 - -## 11.3.3. 插入排序 vs 冒泡排序 - -!!! question - - 虽然「插入排序」和「冒泡排序」的时间复杂度皆为 $O(n^2)$ ,但实际运行速度却有很大差别,这是为什么呢? - -回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换**,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值**,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。 - -插入排序运行速度快,并且具有原地、稳定、自适应的优点,因此很受欢迎。实际上,包括 Java 在内的许多编程语言的排序库函数的实现都用到了插入排序。库函数的大致思路: - -- 对于 **长数组**,采用基于分治的排序算法,例如「快速排序」,时间复杂度为 $O(n \log n)$ ; -- 对于 **短数组**,直接使用「插入排序」,时间复杂度为 $O(n^2)$ ; - -在数组较短时,复杂度中的常数项(即每轮中的单元操作数量)占主导作用,此时插入排序运行地更快。这个现象与「线性查找」和「二分查找」的情况类似。 diff --git a/build/chapter_sorting/intro_to_sort.md b/build/chapter_sorting/intro_to_sort.md deleted file mode 100644 index e4e1552ac..000000000 --- a/build/chapter_sorting/intro_to_sort.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -comments: true ---- - -# 11.1. 排序简介 - -「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。 - -- 待排序的列表的 **元素类型** 可以是整数、浮点数、字符、或字符串; -- 排序算法可以根据需要设定 **判断规则**,例如数字大小、字符 ASCII 码顺序、自定义规则; - -![sorting_examples](intro_to_sort.assets/sorting_examples.png) - -

Fig. 排序中的不同元素类型和判断规则

- -## 11.1.1. 评价维度 - -排序算法主要可根据 **稳定性 、就地性 、自适应性 、比较类** 来分类。 - -### 稳定性 - -- 「稳定排序」在完成排序后,**不改变** 相等元素在数组中的相对顺序。 -- 「非稳定排序」在完成排序后,相等元素在数组中的相对位置 **可能被改变**。 - -假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,**在多级排序中是必须的**。 - -```shell - # 输入数据是按照姓名排序好的 - # (name, age) - ('A', 19) - ('B', 18) - ('C', 21) - ('D', 19) - ('E', 23) - - # 假设使用非稳定排序算法按年龄排序列表, - # 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变, - # 输入数据按姓名排序的性质丢失 - ('B', 18) - ('D', 19) - ('A', 19) - ('C', 21) - ('E', 23) -``` - -### 就地性 - -- 「原地排序」无需辅助数据,不使用额外空间; -- 「非原地排序」需要借助辅助数据,使用额外空间; - -「原地排序」不使用额外空间,可以节约内存;并且一般情况下,由于数据操作减少,原地排序的运行效率也更高。 - -### 自适应性 - -- 「自适应排序」的时间复杂度受输入数据影响,即最佳 / 最差 / 平均时间复杂度不相等。 -- 「非自适应排序」的时间复杂度恒定,与输入数据无关。 - -我们希望 **最差 = 平均**,即不希望排序算法的运行效率在某些输入数据下发生劣化。 - -### 比较类 - -- 「比较类排序」基于元素之间的比较算子(小于、相等、大于)来决定元素的相对顺序。 -- 「非比较类排序」不基于元素之间的比较算子来决定元素的相对顺序。 - -「比较类排序」的时间复杂度最优为 $O(n \log n)$ ;而「非比较类排序」可以达到 $O(n)$ 的时间复杂度,但通用性较差。 - -## 11.1.2. 理想排序算法 - -- **运行快**,即时间复杂度低; -- **稳定排序**,即排序后相等元素的相对位置不变化; -- **原地排序**,即运行中不使用额外的辅助空间; -- **正向自适应性**,即算法的运行效率不会在某些输入数据下发生劣化; - -然而,**没有排序算法同时具备以上所有特性**。排序算法的选型使用取决于具体的列表类型、列表长度、元素分布等因素。 diff --git a/build/chapter_sorting/merge_sort.md b/build/chapter_sorting/merge_sort.md deleted file mode 100755 index 16ea46563..000000000 --- a/build/chapter_sorting/merge_sort.md +++ /dev/null @@ -1,465 +0,0 @@ ---- -comments: true ---- - -# 11.5. 归并排序 - -「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段: - -1. **划分阶段**:通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; -2. **合并阶段**:划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; - -![merge_sort_preview](merge_sort.assets/merge_sort_preview.png) - -

Fig. 归并排序两阶段:划分与合并

- -## 11.5.1. 算法流程 - -**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ; - -1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` ); -2. 递归执行 `1.` 步骤,直至子数组区间长度为 1 时,终止递归划分; - -**「回溯合并」** 从底至顶地将左子数组和右子数组合并为一个 **有序数组** ; - -需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的**。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组**。 - -=== "Step1" - ![merge_sort_step1](merge_sort.assets/merge_sort_step1.png) - -=== "Step2" - ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) - -=== "Step3" - ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) - -=== "Step4" - ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) - -=== "Step5" - ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) - -=== "Step6" - ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) - -=== "Step7" - ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) - -=== "Step8" - ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) - -=== "Step9" - ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) - -=== "Step10" - ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) - -观察发现,归并排序的递归顺序就是二叉树的「后序遍历」。 - -- **后序遍历**:先递归左子树、再递归右子树、最后处理根结点。 -- **归并排序**:先递归左子树、再递归右子树、最后处理合并。 - -=== "Java" - - ```java title="merge_sort.java" - /* 合并左子数组和右子数组 */ - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - void merge(int[] nums, int left, int mid, int right) { - // 初始化辅助数组 - int[] tmp = Arrays.copyOfRange(nums, left, right + 1); - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } - } - - /* 归并排序 */ - void mergeSort(int[] nums, int left, int right) { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "C++" - - ```cpp title="merge_sort.cpp" - /* 合并左子数组和右子数组 */ - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - void merge(vector& nums, int left, int mid, int right) { - // 初始化辅助数组 - vector tmp(nums.begin() + left, nums.begin() + right + 1); - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } - } - - /* 归并排序 */ - void mergeSort(vector& nums, int left, int right) { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "Python" - - ```python title="merge_sort.py" - """ 合并左子数组和右子数组 """ - # 左子数组区间 [left, mid] - # 右子数组区间 [mid + 1, right] - def merge(nums, left, mid, right): - # 初始化辅助数组 借助 copy模块 - tmp = nums[left:right + 1] - # 左子数组的起始索引和结束索引 - left_start, left_end = left - left, mid - left - # 右子数组的起始索引和结束索引 - right_start, right_end = mid + 1 - left, right - left - # i, j 分别指向左子数组、右子数组的首元素 - i, j = left_start, right_start - # 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k in range(left, right + 1): - # 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end: - nums[k] = tmp[j] - j += 1 - # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - elif j > right_end or tmp[i] <= tmp[j]: - nums[k] = tmp[i] - i += 1 - # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else: - nums[k] = tmp[j] - j += 1 - - """ 归并排序 """ - def merge_sort(nums, left, right): - # 终止条件 - if left >= right: - return # 当子数组长度为 1 时终止递归 - # 划分阶段 - mid = (left + right) // 2 # 计算中点 - merge_sort(nums, left, mid) # 递归左子数组 - merge_sort(nums, mid + 1, right) # 递归右子数组 - # 合并阶段 - merge(nums, left, mid, right) - ``` - -=== "Go" - - ```go title="merge_sort.go" - /* - 合并左子数组和右子数组 - 左子数组区间 [left, mid] - 右子数组区间 [mid + 1, right] - */ - func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy 模块 - tmp := make([]int, right-left+1) - for i := left; i <= right; i++ { - tmp[i-left] = nums[i] - } - // 左子数组的起始索引和结束索引 - leftStart, leftEnd := left-left, mid-left - // 右子数组的起始索引和结束索引 - rightStart, rightEnd := mid+1-left, right-left - // i, j 分别指向左子数组、右子数组的首元素 - i, j := leftStart, rightStart - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k := left; k <= right; k++ { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > leftEnd { - nums[k] = tmp[j] - j++ - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > rightEnd || tmp[i] <= tmp[j] { - nums[k] = tmp[i] - i++ - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j] - j++ - } - } - } - - func mergeSort(nums []int, left, right int) { - // 终止条件 - if left >= right { - return - } - // 划分阶段 - mid := (left + right) / 2 - mergeSort(nums, left, mid) - mergeSort(nums, mid+1, right) - // 合并阶段 - merge(nums, left, mid, right) - } - ``` - -=== "JavaScript" - - ```js title="merge_sort.js" - /* 合并左子数组和右子数组 */ - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - function merge(nums, left, mid, right) { - // 初始化辅助数组 - let tmp = nums.slice(left, right + 1); - // 左子数组的起始索引和结束索引 - let leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - let i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (let k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd || tmp[i] <= tmp[j]) { - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j++]; - } - } - } - - /* 归并排序 */ - function mergeSort(nums, left, right) { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - let mid = Math.floor((left + right) / 2); // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "TypeScript" - - ```typescript title="merge_sort.ts" - /* 合并左子数组和右子数组 */ - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - function merge(nums: number[], left: number, mid: number, right: number): void { - // 初始化辅助数组 - let tmp = nums.slice(left, right + 1); - // 左子数组的起始索引和结束索引 - let leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - let i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (let k = left; k <= right; k++) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd || tmp[i] <= tmp[j]) { - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j++]; - } - } - } - - /* 归并排序 */ - function mergeSort(nums: number[], left: number, right: number): void { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - let mid = Math.floor((left + right) / 2); // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "C" - - ```c title="merge_sort.c" - - ``` - -=== "C#" - - ```csharp title="merge_sort.cs" - /* 合并左子数组和右子数组 */ - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - void merge(int[] nums, int left, int mid, int right) - { - // 初始化辅助数组 - int[] tmp = nums[left..(right + 1)]; - // 左子数组的起始索引和结束索引 - int leftStart = left - left, leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - int rightStart = mid + 1 - left, rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - int i = leftStart, j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for (int k = left; k <= right; k++) - { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) - nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if (j > rightEnd || tmp[i] <= tmp[j]) - nums[k] = tmp[i++]; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else - nums[k] = tmp[j++]; - } - } - - /* 归并排序 */ - void mergeSort(int[] nums, int left, int right) - { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - int mid = (left + right) / 2; // 计算中点 - mergeSort(nums, left, mid); // 递归左子数组 - mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - merge(nums, left, mid, right); - } - ``` - -=== "Swift" - - ```swift title="merge_sort.swift" - /** - * 合并左子数组和右子数组 - * 左子数组区间 [left, mid] - * 右子数组区间 [mid + 1, right] - */ - func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { - // 初始化辅助数组 - let tmp = Array(nums[left ..< (right + 1)]) - // 左子数组的起始索引和结束索引 - let leftStart = left - left - let leftEnd = mid - left - // 右子数组的起始索引和结束索引 - let rightStart = mid + 1 - left - let rightEnd = right - left - // i, j 分别指向左子数组、右子数组的首元素 - var i = leftStart - var j = rightStart - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - for k in left ... right { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > leftEnd { - nums[k] = tmp[j] - j += 1 - } - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - else if j > rightEnd || tmp[i] <= tmp[j] { - nums[k] = tmp[i] - i += 1 - } - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - else { - nums[k] = tmp[j] - j += 1 - } - } - } - - /* 归并排序 */ - func mergeSort(nums: inout [Int], left: Int, right: Int) { - // 终止条件 - if left >= right { // 当子数组长度为 1 时终止递归 - return - } - // 划分阶段 - let mid = (left + right) / 2 // 计算中点 - mergeSort(nums: &nums, left: left, right: mid) // 递归左子数组 - mergeSort(nums: &nums, left: mid + 1, right: right) // 递归右子数组 - // 合并阶段 - merge(nums: &nums, left: left, mid: mid, right: right) - } - ``` - -=== "Zig" - - ```zig title="merge_sort.zig" - - ``` - -下面重点解释一下合并方法 `merge()` 的流程: - -1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并; -2. 初始化指针 `i` , `j` , `k` 分别指向左子数组、右子数组、原数组的首元素; -3. 循环判断 `tmp[i]` 和 `tmp[j]` 的大小,将较小的先覆盖至 `nums[k]` ,指针 `i` , `j` 根据判断结果交替前进(指针 `k` 也前进),直至两个子数组都遍历完,即可完成合并。 - -合并方法 `merge()` 代码中的主要难点: - -- `nums` 的待合并区间为 `[left, right]` ,而因为 `tmp` 只复制了 `nums` 该区间元素,所以 `tmp` 对应区间为 `[0, right - left]` ,**需要特别注意代码中各个变量的含义**。 -- 判断 `tmp[i]` 和 `tmp[j]` 的大小的操作中,还 **需考虑当子数组遍历完成后的索引越界问题**,即 `i > leftEnd` 和 `j > rightEnd` 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。 - -## 11.5.2. 算法特性 - -- **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 -- **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 -- **非原地排序**:辅助数组需要使用 $O(n)$ 额外空间。 -- **稳定排序**:在合并时可保证相等元素的相对位置不变。 -- **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。 - -## 11.5.3. 链表排序 * - -归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为: - -- 由于链表可仅通过改变指针来实现结点增删,因此“将两个短有序链表合并为一个长有序链表”无需使用额外空间,即回溯合并阶段不用像排序数组一样建立辅助数组 `tmp` ; -- 通过使用「迭代」代替「递归划分」,可省去递归使用的栈帧空间; - -> 详情参考:[148. 排序链表](https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/) diff --git a/build/chapter_sorting/quick_sort.md b/build/chapter_sorting/quick_sort.md deleted file mode 100755 index 1c4ab2a34..000000000 --- a/build/chapter_sorting/quick_sort.md +++ /dev/null @@ -1,880 +0,0 @@ ---- -comments: true ---- - -# 11.4. 快速排序 - -「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。 - -快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 **基准数**,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为: - -1. 以数组最左端元素作为基准数,初始化两个指针 `i` , `j` 指向数组两端; -2. 设置一个循环,每轮中使用 `i` / `j` 分别寻找首个比基准数大 / 小的元素,并交换此两元素; -3. 不断循环步骤 `2.` ,直至 `i` , `j` 相遇时跳出,最终把基准数交换至两个子数组的分界线; - -「哨兵划分」执行完毕后,原数组被划分成两个部分,即 **左子数组** 和 **右子数组**,且满足 **左子数组任意元素 < 基准数 < 右子数组任意元素**。因此,接下来我们只需要排序两个子数组即可。 - -=== "Step 1" - ![pivot_division_step1](quick_sort.assets/pivot_division_step1.png) - -=== "Step 2" - ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) - -=== "Step 3" - ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) - -=== "Step 4" - ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) - -=== "Step 5" - ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) - -=== "Step 6" - ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) - -=== "Step 7" - ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) - -=== "Step 8" - ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) - -=== "Step 9" - ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) - -

Fig. 哨兵划分

- -=== "Java" - - ```java title="quick_sort.java" - /* 元素交换 */ - void swap(int[] nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - int partition(int[] nums, int left, int right) { - // 以 nums[left] 作为基准数 - int i = left, j = right; - while (i < j) { - while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 - while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 元素交换 */ - void swap(vector& nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - int partition(vector& nums, int left, int right) { - // 以 nums[left] 作为基准数 - int i = left, j = right; - while (i < j) { - while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 - while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 哨兵划分 """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 - i, j = left, right - while i < j: - while i < j and nums[j] >= nums[left]: - j -= 1 # 从右向左找首个小于基准数的元素 - while i < j and nums[i] <= nums[left]: - i += 1 # 从左向右找首个大于基准数的元素 - # 元素交换 - nums[i], nums[j] = nums[j], nums[i] - # 将基准数交换至两子数组的分界线 - nums[i], nums[left] = nums[left], nums[i] - return i # 返回基准数的索引 - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 哨兵划分 */ - func partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 - i, j := left, right - for i < j { - for i < j && nums[j] >= nums[left] { - j-- // 从右向左找首个小于基准数的元素 - } - for i < j && nums[i] <= nums[left] { - i++ // 从左向右找首个大于基准数的元素 - } - //元素交换 - nums[i], nums[j] = nums[j], nums[i] - } - // 将基准数交换至两子数组的分界线 - nums[i], nums[left] = nums[left], nums[i] - return i // 返回基准数的索引 - } - ``` - -=== "JavaScript" - - ``` js title="quick_sort.js" - /* 元素交换 */ - function swap(nums, i, j) { - let tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - function partition(nums, left, right) { - // 以 nums[left] 作为基准数 - let i = left, j = right; - while (i < j) { - while (i < j && nums[j] >= nums[left]) { - j -= 1; // 从右向左找首个小于基准数的元素 - } - while (i < j && nums[i] <= nums[left]) { - i += 1; // 从左向右找首个大于基准数的元素 - } - // 元素交换 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 元素交换 */ - function swap(nums: number[], i: number, j: number): void { - let tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - function partition(nums: number[], left: number, right: number): number { - // 以 nums[left] 作为基准数 - let i = left, j = right; - while (i < j) { - while (i < j && nums[j] >= nums[left]) { - j -= 1; // 从右向左找首个小于基准数的元素 - } - while (i < j && nums[i] <= nums[left]) { - i += 1; // 从左向右找首个大于基准数的元素 - } - // 元素交换 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 元素交换 */ - void swap(int[] nums, int i, int j) - { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 哨兵划分 */ - int partition(int[] nums, int left, int right) - { - // 以 nums[left] 作为基准数 - int i = left, j = right; - while (i < j) - { - while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 - while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - /* 元素交换 */ - func swap(nums: inout [Int], i: Int, j: Int) { - let tmp = nums[i] - nums[i] = nums[j] - nums[j] = tmp - } - - /* 哨兵划分 */ - func partition(nums: inout [Int], left: Int, right: Int) -> Int { - // 以 nums[left] 作为基准数 - var i = left - var j = right - while i < j { - while i < j, nums[j] >= nums[left] { - j -= 1 // 从右向左找首个小于基准数的元素 - } - while i < j, nums[i] <= nums[left] { - i += 1 // 从左向右找首个大于基准数的元素 - } - swap(nums: &nums, i: i, j: j) // 交换这两个元素 - } - swap(nums: &nums, i: i, j: left) // 将基准数交换至两子数组的分界线 - return i // 返回基准数的索引 - } - ``` - -=== "Zig" - - ```zig title="quick_sort.zig" - - ``` - -!!! note "快速排序的分治思想" - - 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。 - -## 11.4.1. 算法流程 - -1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**; -2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」…… -3. 直至子数组长度为 1 时 **终止递归**,即可完成对整个数组的排序; - -观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。 - -![quick_sort](quick_sort.assets/quick_sort.png) - -

Fig. 快速排序流程

- -=== "Java" - - ```java title="quick_sort.java" - /* 快速排序 */ - void quickSort(int[] nums, int left, int right) { - // 子数组长度为 1 时终止递归 - if (left >= right) - return; - // 哨兵划分 - int pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 快速排序 */ - void quickSort(vector& nums, int left, int right) { - // 子数组长度为 1 时终止递归 - if (left >= right) - return; - // 哨兵划分 - int pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 快速排序 """ - def quick_sort(self, nums, left, right): - # 子数组长度为 1 时终止递归 - if left >= right: - return - # 哨兵划分 - pivot = self.partition(nums, left, right) - # 递归左子数组、右子数组 - self.quick_sort(nums, left, pivot - 1) - self.quick_sort(nums, pivot + 1, right) - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 快速排序 */ - func quickSort(nums []int, left, right int) { - // 子数组长度为 1 时终止递归 - if left >= right { - return - } - // 哨兵划分 - pivot := partition(nums, left, right) - // 递归左子数组、右子数组 - quickSort(nums, left, pivot-1) - quickSort(nums, pivot+1, right) - } - ``` - -=== "JavaScript" - - ```js title="quick_sort.js" - /* 快速排序 */ - function quickSort(nums, left, right) { - // 子数组长度为 1 时终止递归 - if (left >= right) return; - // 哨兵划分 - const pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 快速排序 */ - function quickSort(nums: number[], left: number, right: number): void { - // 子数组长度为 1 时终止递归 - if (left >= right) { - return; - } - // 哨兵划分 - const pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 快速排序 */ - void quickSort(int[] nums, int left, int right) - { - // 子数组长度为 1 时终止递归 - if (left >= right) - return; - // 哨兵划分 - int pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } - - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - /* 快速排序 */ - func quickSort(nums: inout [Int], left: Int, right: Int) { - // 子数组长度为 1 时终止递归 - if left >= right { - return - } - // 哨兵划分 - let pivot = partition(nums: &nums, left: left, right: right) - // 递归左子数组、右子数组 - quickSort(nums: &nums, left: left, right: pivot - 1) - quickSort(nums: &nums, left: pivot + 1, right: right) - } - ``` - -=== "Zig" - - ```zig title="quick_sort.zig" - - ``` - -## 11.4.2. 算法特性 - -**平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 - -**最差时间复杂度 $O(n^2)$** :最差情况下,哨兵划分操作将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 - -**空间复杂度 $O(n)$** :输入数组完全倒序下,达到最差递归深度 $n$ 。 - -**原地排序**:只在递归中使用 $O(\log n)$ 大小的栈帧空间。 - -**非稳定排序**:哨兵划分操作可能改变相等元素的相对位置。 - -**自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。 - -## 11.4.3. 快排为什么快? - -从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为: - -- **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,不如归并排序,但绝大部分情况下,快速排序可以达到 $O(n \log n)$ 的复杂度。 -- **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 -- **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 - -## 11.4.4. 基准数优化 - -**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 - -为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数**。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 - -进一步地,我们可以在数组中选取 3 个候选元素(一般为数组的首、尾、中点元素),**并将三个候选元素的中位数作为基准数**,这样基准数“既不大也不小”的概率就大大提升了。当然,如果数组很长的话,我们也可以选取更多候选元素,来进一步提升算法的稳健性。采取该方法后,时间复杂度劣化至 $O(n^2)$ 的概率极低。 - -=== "Java" - - ```java title="quick_sort.java" - /* 选取三个元素的中位数 */ - int medianThree(int[] nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - int partition(int[] nums, int left, int right) { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - int i = left, j = right; - while (i < j) { - while (i < j && nums[j] >= nums[left]) - j--; // 从右向左找首个小于基准数的元素 - while (i < j && nums[i] <= nums[left]) - i++; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 选取三个元素的中位数 */ - int medianThree(vector& nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - int partition(vector& nums, int left, int right) { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 选取三个元素的中位数 """ - def median_three(self, nums, left, mid, right): - # 使用了异或操作来简化代码 - # 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if (nums[left] < nums[mid]) ^ (nums[left] < nums[right]): - return left - elif (nums[mid] < nums[left]) ^ (nums[mid] > nums[right]): - return mid - return right - - """ 哨兵划分(三数取中值) """ - def partition(self, nums, left, right): - # 以 nums[left] 作为基准数 - med = self.median_three(nums, left, (left + right) // 2, right) - # 将中位数交换至数组最左端 - nums[left], nums[med] = nums[med], nums[left] - # 以 nums[left] 作为基准数 - i, j = left, right - while i < j: - while i < j and nums[j] >= nums[left]: - j -= 1 # 从右向左找首个小于基准数的元素 - while i < j and nums[i] <= nums[left]: - i += 1 # 从左向右找首个大于基准数的元素 - # 元素交换 - nums[i], nums[j] = nums[j], nums[i] - # 将基准数交换至两子数组的分界线 - nums[i], nums[left] = nums[left], nums[i] - return i # 返回基准数的索引 - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 选取三个元素的中位数 */ - func medianThree(nums []int, left, mid, right int) int { - if (nums[left] < nums[mid]) != (nums[left] < nums[right]) { - return left - } else if (nums[mid] > nums[left]) != (nums[mid] > nums[right]) { - return mid - } - return right - } - - /* 哨兵划分(三数取中值)*/ - func partition(nums []int, left, right int) int { - // 以 nums[left] 作为基准数 - med := medianThree(nums, left, (left+right)/2, right) - // 将中位数交换至数组最左端 - nums[left], nums[med] = nums[med], nums[left] - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "JavaScript" - - ```js title="quick_sort.js" - /* 选取三个元素的中位数 */ - function medianThree(nums, left, mid, right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - function partition(nums, left, right) { - // 选取三个候选元素的中位数 - let med = medianThree(nums, left, Math.floor((left + right) / 2), right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 选取三个元素的中位数 */ - function medianThree(nums: number[], left: number, mid: number, right: number): number { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if (Number(nums[left] < nums[mid]) ^ Number(nums[left] < nums[right])) { - return left; - } else if (Number(nums[mid] < nums[left]) ^ Number(nums[mid] < nums[right])) { - return mid; - } else { - return right; - } - } - - /* 哨兵划分(三数取中值) */ - function partition(nums: number[], left: number, right: number): number { - // 选取三个候选元素的中位数 - let med = medianThree(nums, left, Math.floor((left + right) / 2), right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 选取三个元素的中位数 */ - int medianThree(int[] nums, int left, int mid, int right) - { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } - - /* 哨兵划分(三数取中值) */ - int partition(int[] nums, int left, int right) - { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - /* 选取三个元素的中位数 */ - func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { - if (nums[left] < nums[mid]) != (nums[left] < nums[right]) { - return left - } else if (nums[mid] < nums[left]) != (nums[mid] < nums[right]) { - return mid - } else { - return right - } - } - - /* 哨兵划分(三数取中值) */ - func partition(nums: inout [Int], left: Int, right: Int) -> Int { - // 选取三个候选元素的中位数 - let med = medianThree(nums: nums, left: left, mid: (left + right) / 2, right: right) - // 将中位数交换至数组最左端 - swap(nums: &nums, i: left, j: med) - // 以 nums[left] 作为基准数 - // 下同省略... - } - ``` - -=== "Zig" - - ```zig title="quick_sort.zig" - - ``` - -## 11.4.5. 尾递归优化 - -**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 - -为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短的子数组长度不会超过 $\frac{n}{2}$ ,因此这样做能保证递归深度不超过 $\log n$ ,即最差空间复杂度被优化至 $O(\log n)$ 。 - -=== "Java" - - ```java title="quick_sort.java" - /* 快速排序(尾递归优化) */ - void quickSort(int[] nums, int left, int right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "C++" - - ```cpp title="quick_sort.cpp" - /* 快速排序(尾递归优化) */ - void quickSort(vector& nums, int left, int right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "Python" - - ```python title="quick_sort.py" - """ 快速排序(尾递归优化) """ - def quick_sort(self, nums, left, right): - # 子数组长度为 1 时终止 - while left < right: - # 哨兵划分操作 - pivot = self.partition(nums, left, right) - # 对两个子数组中较短的那个执行快排 - if pivot - left < right - pivot: - self.quick_sort(nums, left, pivot - 1) # 递归排序左子数组 - left = pivot + 1 # 剩余待排序区间为 [pivot + 1, right] - else: - self.quick_sort(nums, pivot + 1, right) # 递归排序右子数组 - right = pivot - 1 # 剩余待排序区间为 [left, pivot - 1] - ``` - -=== "Go" - - ```go title="quick_sort.go" - /* 快速排序(尾递归优化)*/ - func quickSort(nums []int, left, right int) { - // 子数组长度为 1 时终止 - for left < right { - // 哨兵划分操作 - pivot := partition(nums, left, right) - // 对两个子数组中较短的那个执行快排 - if pivot-left < right-pivot { - quickSort(nums, left, pivot-1) // 递归排序左子数组 - left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot+1, right) // 递归排序右子数组 - right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "JavaScript" - - ```js title="quick_sort.js" - /* 快速排序(尾递归优化) */ - function quickSort(nums, left, right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - let pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "TypeScript" - - ```typescript title="quick_sort.ts" - /* 快速排序(尾递归优化) */ - function quickSort(nums: number[], left: number, right: number): void { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - let pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "C" - - ```c title="quick_sort.c" - - ``` - -=== "C#" - - ```csharp title="quick_sort.cs" - /* 快速排序(尾递归优化) */ - void quickSort(int[] nums, int left, int right) - { - // 子数组长度为 1 时终止 - while (left < right) - { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) - { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } - else - { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "Swift" - - ```swift title="quick_sort.swift" - /* 快速排序(尾递归优化) */ - func quickSort(nums: inout [Int], left: Int, right: Int) { - var left = left - var right = right - // 子数组长度为 1 时终止 - while left < right { - // 哨兵划分操作 - let pivot = partition(nums: &nums, left: left, right: right) - // 对两个子数组中较短的那个执行快排 - if (pivot - left) < (right - pivot) { - quickSort(nums: &nums, left: left, right: pivot - 1) // 递归排序左子数组 - left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums: &nums, left: pivot + 1, right: right) // 递归排序右子数组 - right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "Zig" - - ```zig title="quick_sort.zig" - - ``` diff --git a/build/chapter_sorting/summary.md b/build/chapter_sorting/summary.md deleted file mode 100644 index 71cc82609..000000000 --- a/build/chapter_sorting/summary.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -comments: true ---- - -# 11.6. 小结 - diff --git a/build/chapter_stack_and_queue/deque.md b/build/chapter_stack_and_queue/deque.md deleted file mode 100644 index b49f3b20d..000000000 --- a/build/chapter_stack_and_queue/deque.md +++ /dev/null @@ -1,748 +0,0 @@ ---- -comments: true ---- - -# 5.3. 双向队列 - -对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。 - -![deque_operations](deque.assets/deque_operations.png) - -

Fig. 双向队列的操作

- -## 5.3.1. 双向队列常用操作 - -双向队列的常用操作见下表,方法名需根据特定语言来确定。 - -

Table. 双向队列的常用操作

- -
- -| 方法名 | 描述 | 时间复杂度 | -| ------------ | ---------------- | ---------- | -| pushFirst() | 将元素添加至队首 | $O(1)$ | -| pushLast() | 将元素添加至队尾 | $O(1)$ | -| pollFirst() | 删除队首元素 | $O(1)$ | -| pollLast() | 删除队尾元素 | $O(1)$ | -| peekFirst() | 访问队首元素 | $O(1)$ | -| peekLast() | 访问队尾元素 | $O(1)$ | -| size() | 获取队列的长度 | $O(1)$ | -| isEmpty() | 判断队列是否为空 | $O(1)$ | - -
- -相同地,我们可以直接使用编程语言实现好的双向队列类。 - -=== "Java" - - ```java title="deque.java" - /* 初始化双向队列 */ - Deque deque = new LinkedList<>(); - - /* 元素入队 */ - deque.offerLast(2); // 添加至队尾 - deque.offerLast(5); - deque.offerLast(4); - deque.offerFirst(3); // 添加至队首 - deque.offerFirst(1); - - /* 访问元素 */ - int peekFirst = deque.peekFirst(); // 队首元素 - int peekLast = deque.peekLast(); // 队尾元素 - - /* 元素出队 */ - int pollFirst = deque.pollFirst(); // 队首元素出队 - int pollLast = deque.pollLast(); // 队尾元素出队 - - /* 获取双向队列的长度 */ - int size = deque.size(); - - /* 判断双向队列是否为空 */ - boolean isEmpty = deque.isEmpty(); - ``` - -=== "C++" - - ```cpp title="deque.cpp" - /* 初始化双向队列 */ - deque deque; - - /* 元素入队 */ - deque.push_back(2); // 添加至队尾 - deque.push_back(5); - deque.push_back(4); - deque.push_front(3); // 添加至队首 - deque.push_front(1); - - /* 访问元素 */ - int front = deque.front(); // 队首元素 - int back = deque.back(); // 队尾元素 - - /* 元素出队 */ - deque.pop_front(); // 队首元素出队 - deque.pop_back(); // 队尾元素出队 - - /* 获取双向队列的长度 */ - int size = deque.size(); - - /* 判断双向队列是否为空 */ - bool empty = deque.empty(); - ``` - -=== "Python" - - ```python title="deque.py" - """ 初始化双向队列 """ - duque = deque() - - """ 元素入队 """ - duque.append(2) # 添加至队尾 - duque.append(5) - duque.append(4) - duque.appendleft(3) # 添加至队首 - duque.appendleft(1) - - """ 访问元素 """ - front = duque[0] # 队首元素 - rear = duque[-1] # 队尾元素 - - """ 元素出队 """ - pop_front = duque.popleft() # 队首元素出队 - pop_rear = duque.pop() # 队尾元素出队 - - """ 获取双向队列的长度 """ - size = len(duque) - - """ 判断双向队列是否为空 """ - is_empty = len(duque) == 0 - ``` - -=== "Go" - - ```go title="deque_test.go" - /* 初始化双向队列 */ - // 在 Go 中,将 list 作为双向队列使用 - deque := list.New() - - /* 元素入队 */ - deque.PushBack(2) // 添加至队尾 - deque.PushBack(5) - deque.PushBack(4) - deque.PushFront(3) // 添加至队首 - deque.PushFront(1) - - /* 访问元素 */ - front := deque.Front() // 队首元素 - rear := deque.Back() // 队尾元素 - - /* 元素出队 */ - deque.Remove(front) // 队首元素出队 - deque.Remove(rear) // 队尾元素出队 - - /* 获取双向队列的长度 */ - size := deque.Len() - - /* 判断双向队列是否为空 */ - isEmpty := deque.Len() == 0 - ``` - -=== "JavaScript" - - ```js title="deque.js" - /* 初始化双向队列 */ - // JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 - const deque = []; - - /* 元素入队 */ - deque.push(2); - deque.push(5); - deque.push(4); - // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) - deque.unshift(3); - deque.unshift(1); - console.log("双向队列 deque = ", deque); - - /* 访问元素 */ - const peekFirst = deque[0]; - console.log("队首元素 peekFirst = " + peekFirst); - const peekLast = deque[deque.length - 1]; - console.log("队尾元素 peekLast = " + peekLast); - - /* 元素出队 */ - // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) - const popFront = deque.shift(); - console.log("队首出队元素 popFront = " + popFront + ",队首出队后 deque = " + deque); - const popBack = deque.pop(); - console.log("队尾出队元素 popBack = " + popBack + ",队尾出队后 deque = " + deque); - - /* 获取双向队列的长度 */ - const size = deque.length; - console.log("双向队列长度 size = " + size); - - /* 判断双向队列是否为空 */ - const isEmpty = size === 0; - console.log("双向队列是否为空 = " + isEmpty); - ``` - -=== "TypeScript" - - ```typescript title="deque.ts" - /* 初始化双向队列 */ - // TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用 - const deque: number[] = []; - - /* 元素入队 */ - deque.push(2); - deque.push(5); - deque.push(4); - // 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n) - deque.unshift(3); - deque.unshift(1); - console.log("双向队列 deque = ", deque); - - /* 访问元素 */ - const peekFirst: number = deque[0]; - console.log("队首元素 peekFirst = " + peekFirst); - const peekLast: number = deque[deque.length - 1]; - console.log("队尾元素 peekLast = " + peekLast); - - /* 元素出队 */ - // 请注意,由于是数组,shift() 方法的时间复杂度为 O(n) - const popFront: number = deque.shift() as number; - console.log("队首出队元素 popFront = " + popFront + ",队首出队后 deque = " + deque); - const popBack: number = deque.pop() as number; - console.log("队尾出队元素 popBack = " + popBack + ",队尾出队后 deque = " + deque); - - /* 获取双向队列的长度 */ - const size: number = deque.length; - console.log("双向队列长度 size = " + size); - - /* 判断双向队列是否为空 */ - const isEmpty: boolean = size === 0; - console.log("双向队列是否为空 = " + isEmpty); - ``` - -=== "C" - - ```c title="deque.c" - - ``` - -=== "C#" - - ```csharp title="deque.cs" - /* 初始化双向队列 */ - // 在 C# 中,将链表 LinkedList 看作双向队列来使用 - LinkedList deque = new LinkedList(); - - /* 元素入队 */ - deque.AddLast(2); // 添加至队尾 - deque.AddLast(5); - deque.AddLast(4); - deque.AddFirst(3); // 添加至队首 - deque.AddFirst(1); - - /* 访问元素 */ - int peekFirst = deque.First.Value; // 队首元素 - int peekLast = deque.Last.Value; // 队尾元素 - - /* 元素出队 */ - deque.RemoveFirst(); // 队首元素出队 - deque.RemoveLast(); // 队尾元素出队 - - /* 获取双向队列的长度 */ - int size = deque.Count; - - /* 判断双向队列是否为空 */ - bool isEmpty = deque.Count == 0; - ``` - -=== "Swift" - - ```swift title="deque.swift" - /* 初始化双向队列 */ - // Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用 - var deque: [Int] = [] - - /* 元素入队 */ - deque.append(2) // 添加至队尾 - deque.append(5) - deque.append(4) - deque.insert(3, at: 0) // 添加至队首 - deque.insert(1, at: 0) - - /* 访问元素 */ - let peekFirst = deque.first! // 队首元素 - let peekLast = deque.last! // 队尾元素 - - /* 元素出队 */ - // 使用 Array 模拟时 pollFirst 的复杂度为 O(n) - let pollFirst = deque.removeFirst() // 队首元素出队 - let pollLast = deque.removeLast() // 队尾元素出队 - - /* 获取双向队列的长度 */ - let size = deque.count - - /* 判断双向队列是否为空 */ - let isEmpty = deque.isEmpty - ``` - -=== "Zig" - - ```zig title="deque.zig" - - ``` - -## 5.3.2. 双向队列实现 - -双向队列需要一种可以在两端添加、两端删除的数据结构。与队列的实现方法类似,双向队列也可以使用双向链表和循环数组来实现。 - -### 基于双向链表的实现 - -我们将双向链表的头结点和尾结点分别看作双向队列的队首和队尾,并且实现在两端都能添加与删除结点。 - -=== "LinkedListDeque" - ![linkedlist_deque](deque.assets/linkedlist_deque.png) - -=== "pushLast()" - ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_push_last.png) - -=== "pushFirst()" - ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_push_first.png) - -=== "pollLast()" - ![linkedlist_deque_poll_last](deque.assets/linkedlist_deque_poll_last.png) - -=== "pollFirst()" - ![linkedlist_deque_poll_first](deque.assets/linkedlist_deque_poll_first.png) - -以下是使用双向链表实现双向队列的示例代码。 - -=== "Java" - - ```java title="linkedlist_deque.java" - /* 双向链表结点 */ - class ListNode { - int val; // 结点值 - ListNode next; // 后继结点引用(指针) - ListNode prev; // 前驱结点引用(指针) - ListNode(int val) { - this.val = val; - prev = next = null; - } - } - - /* 基于双向链表实现的双向队列 */ - class LinkedListDeque { - private ListNode front, rear; // 头结点 front ,尾结点 rear - private int size = 0; // 双向队列的长度 - - public LinkedListDeque() { - front = rear = null; - } - - /* 获取双向队列的长度 */ - public int size() { - return size; - } - - /* 判断双向队列是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - - /* 入队操作 */ - private void push(int num, boolean isFront) { - ListNode node = new ListNode(num); - // 若链表为空,则令 front, rear 都指向 node - if (isEmpty()) - front = rear = node; - // 队首入队操作 - else if (isFront) { - // 将 node 添加至链表头部 - front.prev = node; - node.next = front; - front = node; // 更新头结点 - // 队尾入队操作 - } else { - // 将 node 添加至链表尾部 - rear.next = node; - node.prev = rear; - rear = node; // 更新尾结点 - } - size++; // 更新队列长度 - } - - /* 队首入队 */ - public void pushFirst(int num) { - push(num, true); - } - - /* 队尾入队 */ - public void pushLast(int num) { - push(num, false); - } - - /* 出队操作 */ - private Integer poll(boolean isFront) { - // 若队列为空,直接返回 null - if (isEmpty()) - return null; - int val; - // 队首出队操作 - if (isFront) { - val = front.val; // 暂存头结点值 - // 删除头结点 - ListNode fNext = front.next; - if (fNext != null) { - fNext.prev = null; - front.next = null; - } - front = fNext; // 更新头结点 - // 队尾出队操作 - } else { - val = rear.val; // 暂存尾结点值 - // 删除尾结点 - ListNode rPrev = rear.prev; - if (rPrev != null) { - rPrev.next = null; - rear.prev = null; - } - rear = rPrev; // 更新尾结点 - } - size--; // 更新队列长度 - return val; - } - - /* 队首出队 */ - public Integer pollFirst() { - return poll(true); - } - - /* 队尾出队 */ - public Integer pollLast() { - return poll(false); - } - - /* 访问队首元素 */ - public Integer peekFirst() { - return isEmpty() ? null : front.val; - } - - /* 访问队尾元素 */ - public Integer peekLast() { - return isEmpty() ? null : rear.val; - } - - /* 打印双向队列 */ - public void print() { - if (isEmpty()) { - System.out.println("[ ]"); - return; - } - List list = new ArrayList<>(); - ListNode head = front; - while (head != null) { - list.add(String.valueOf(head.val)); - head = head.next; - } - System.out.println("[" + String.join(", ", list) + "]"); - } - } - ``` - -=== "C++" - - ```cpp title="linkedlist_deque.cpp" - - ``` - -=== "Python" - - ```python title="linkedlist_deque.py" - - ``` - -=== "Go" - - ```go title="linkedlist_deque.go" - - ``` - -=== "JavaScript" - - ```js title="linkedlist_deque.js" - /* 双向链表结点 */ - class ListNode { - prev; // 前驱结点引用 (指针) - next; // 后继结点引用 (指针) - val; // 结点值 - - constructor(val) { - this.val = val; - this.next = null; - this.prev = null; - } - } - - /* 基于双向链表实现的双向队列 */ - class LinkedListDeque { - front; // 头结点 front - rear; // 尾结点 rear - len; // 双向队列的长度 - - constructor() { - this.front = null; - this.rear = null; - this.len = 0; - } - - /* 队尾入队操作 */ - pushLast(val) { - const node = new ListNode(val); - // 若链表为空,则令 front, rear 都指向 node - if (this.len === 0) { - this.front = node; - this.rear = node; - } else { - // 将 node 添加至链表尾部 - this.rear.next = node; - node.prev = this.rear; - this.rear = node; // 更新尾结点 - } - this.len++; - } - - /* 队首入队操作 */ - pushFirst(val) { - const node = new ListNode(val); - // 若链表为空,则令 front, rear 都指向 node - if (this.len === 0) { - this.front = node; - this.rear = node; - } else { - // 将 node 添加至链表头部 - this.front.prev = node; - node.next = this.front; - this.front = node; // 更新头结点 - } - this.len++; - } - - /* 队尾出队操作 */ - pollLast() { - if (this.len === 0) { - return null; - } - const value = this.rear.val; // 存储尾结点值 - // 删除尾结点 - let temp = this.rear.prev; - if (temp !== null) { - temp.next = null; - this.rear.prev = null; - } - this.rear = temp; // 更新尾结点 - this.len--; - return value; - } - - /* 队首出队操作 */ - pollFirst() { - if (this.len === 0) { - return null; - } - const value = this.front.val; // 存储尾结点值 - // 删除头结点 - let temp = this.front.next; - if (temp !== null) { - temp.prev = null; - this.front.next = null; - } - this.front = temp; // 更新头结点 - this.len--; - return value; - } - - /* 访问队尾元素 */ - peekLast() { - return this.len === 0 ? null : this.rear.val; - } - - /* 访问队首元素 */ - peekFirst() { - return this.len === 0 ? null : this.front.val; - } - - /* 获取双向队列的长度 */ - size() { - return this.len; - } - - /* 判断双向队列是否为空 */ - isEmpty() { - return this.len === 0; - } - - /* 打印双向队列 */ - print() { - const arr = []; - let temp = this.front; - while (temp !== null) { - arr.push(temp.val); - temp = temp.next; - } - console.log("[" + arr.join(", ") + "]"); - } - } - ``` - -=== "TypeScript" - - ```typescript title="linkedlist_deque.ts" - /* 双向链表结点 */ - class ListNode { - prev: ListNode; // 前驱结点引用 (指针) - next: ListNode; // 后继结点引用 (指针) - val: number; // 结点值 - - constructor(val: number) { - this.val = val; - this.next = null; - this.prev = null; - } - } - - /* 基于双向链表实现的双向队列 */ - class LinkedListDeque { - front: ListNode; // 头结点 front - rear: ListNode; // 尾结点 rear - len: number; // 双向队列的长度 - - constructor() { - this.front = null; - this.rear = null; - this.len = 0; - } - - /* 队尾入队操作 */ - pushLast(val: number): void { - const node: ListNode = new ListNode(val); - // 若链表为空,则令 front, rear 都指向 node - if (this.len === 0) { - this.front = node; - this.rear = node; - } else { - // 将 node 添加至链表尾部 - this.rear.next = node; - node.prev = this.rear; - this.rear = node; // 更新尾结点 - } - this.len++; - } - - /* 队首入队操作 */ - pushFirst(val: number): void { - const node: ListNode = new ListNode(val); - // 若链表为空,则令 front, rear 都指向 node - if (this.len === 0) { - this.front = node; - this.rear = node; - } else { - // 将 node 添加至链表头部 - this.front.prev = node; - node.next = this.front; - this.front = node; // 更新头结点 - } - this.len++; - } - - /* 队尾出队操作 */ - pollLast(): number { - if (this.len === 0) { - return null; - } - const value: number = this.rear.val; // 存储尾结点值 - // 删除尾结点 - let temp: ListNode = this.rear.prev; - if (temp !== null) { - temp.next = null; - this.rear.prev = null; - } - this.rear = temp; // 更新尾结点 - this.len--; - return value; - } - - /* 队首出队操作 */ - pollFirst(): number { - if (this.len === 0) { - return null; - } - const value: number = this.front.val; // 存储尾结点值 - // 删除头结点 - let temp: ListNode = this.front.next; - if (temp !== null) { - temp.prev = null; - this.front.next = null; - } - this.front = temp; // 更新头结点 - this.len--; - return value; - } - - /* 访问队尾元素 */ - peekLast(): number { - return this.len === 0 ? null : this.rear.val; - } - - /* 访问队首元素 */ - peekFirst(): number { - return this.len === 0 ? null : this.front.val; - } - - /* 获取双向队列的长度 */ - size(): number { - return this.len; - } - - /* 判断双向队列是否为空 */ - isEmpty(): boolean { - return this.len === 0; - } - - /* 打印双向队列 */ - print(): void { - const arr: number[] = []; - let temp: ListNode = this.front; - while (temp !== null) { - arr.push(temp.val); - temp = temp.next; - } - console.log("[" + arr.join(", ") + "]"); - } - } - ``` - -=== "C" - - ```c title="linkedlist_deque.c" - - ``` - -=== "C#" - - ```csharp title="linkedlist_deque.cs" - - ``` - -=== "Swift" - - ```swift title="linkedlist_deque.swift" - - ``` - -=== "Zig" - - ```zig title="linkedlist_deque.zig" - - ``` diff --git a/build/chapter_stack_and_queue/queue.md b/build/chapter_stack_and_queue/queue.md deleted file mode 100755 index c3a58d284..000000000 --- a/build/chapter_stack_and_queue/queue.md +++ /dev/null @@ -1,1319 +0,0 @@ ---- -comments: true ---- - -# 5.2. 队列 - -「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。 - -我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。 - -![queue_operations](queue.assets/queue_operations.png) - -

Fig. 队列的先入先出特性

- -## 5.2.1. 队列常用操作 - -队列的常用操作见下表,方法名需根据特定语言来确定。 - -

Table. 队列的常用操作

- -
- -| 方法名 | 描述 | 时间复杂度 | -| --------- | -------------------------- | -------- | -| push() | 元素入队,即将元素添加至队尾 | $O(1)$ | -| poll() | 队首元素出队 | $O(1)$ | -| front() | 访问队首元素 | $O(1)$ | -| size() | 获取队列的长度 | $O(1)$ | -| isEmpty() | 判断队列是否为空 | $O(1)$ | - -
- -我们可以直接使用编程语言实现好的队列类。 - -=== "Java" - - ```java title="queue.java" - /* 初始化队列 */ - Queue queue = new LinkedList<>(); - - /* 元素入队 */ - queue.offer(1); - queue.offer(3); - queue.offer(2); - queue.offer(5); - queue.offer(4); - - /* 访问队首元素 */ - int peek = queue.peek(); - - /* 元素出队 */ - int poll = queue.poll(); - - /* 获取队列的长度 */ - int size = queue.size(); - - /* 判断队列是否为空 */ - boolean isEmpty = queue.isEmpty(); - ``` - -=== "C++" - - ```cpp title="queue.cpp" - /* 初始化队列 */ - queue queue; - - /* 元素入队 */ - queue.push(1); - queue.push(3); - queue.push(2); - queue.push(5); - queue.push(4); - - /* 访问队首元素 */ - int front = queue.front(); - - /* 元素出队 */ - queue.pop(); - - /* 获取队列的长度 */ - int size = queue.size(); - - /* 判断队列是否为空 */ - bool empty = queue.empty(); - ``` - -=== "Python" - - ```python title="queue.py" - """ 初始化队列 """ - # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 - # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议 - que = collections.deque() - - """ 元素入队 """ - que.append(1) - que.append(3) - que.append(2) - que.append(5) - que.append(4) - - """ 访问队首元素 """ - front = que[0]; - - """ 元素出队 """ - pop = que.popleft() - - """ 获取队列的长度 """ - size = len(que) - - """ 判断队列是否为空 """ - is_empty = len(que) == 0 - ``` - -=== "Go" - - ```go title="queue_test.go" - /* 初始化队列 */ - // 在 Go 中,将 list 作为队列来使用 - queue := list.New() - - /* 元素入队 */ - queue.PushBack(1) - queue.PushBack(3) - queue.PushBack(2) - queue.PushBack(5) - queue.PushBack(4) - - /* 访问队首元素 */ - peek := queue.Front() - - /* 元素出队 */ - poll := queue.Front() - queue.Remove(poll) - - /* 获取队列的长度 */ - size := queue.Len() - - /* 判断队列是否为空 */ - isEmpty := queue.Len() == 0 - ``` - -=== "JavaScript" - - ```js title="queue.js" - /* 初始化队列 */ - // JavaScript 没有内置的队列,可以把 Array 当作队列来使用 - const queue = []; - - /* 元素入队 */ - queue.push(1); - queue.push(3); - queue.push(2); - queue.push(5); - queue.push(4); - - /* 访问队首元素 */ - const peek = queue[0]; - - /* 元素出队 */ - // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) - const poll = queue.shift(); - - /* 获取队列的长度 */ - const size = queue.length; - - /* 判断队列是否为空 */ - const empty = queue.length === 0; - ``` - -=== "TypeScript" - - ```typescript title="queue.ts" - /* 初始化队列 */ - // TypeScript 没有内置的队列,可以把 Array 当作队列来使用 - const queue: number[] = []; - - /* 元素入队 */ - queue.push(1); - queue.push(3); - queue.push(2); - queue.push(5); - queue.push(4); - - /* 访问队首元素 */ - const peek = queue[0]; - - /* 元素出队 */ - // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) - const poll = queue.shift(); - - /* 获取队列的长度 */ - const size = queue.length; - - /* 判断队列是否为空 */ - const empty = queue.length === 0; - ``` - -=== "C" - - ```c title="queue.c" - - ``` - -=== "C#" - - ```csharp title="queue.cs" - /* 初始化队列 */ - Queue queue = new(); - - /* 元素入队 */ - queue.Enqueue(1); - queue.Enqueue(3); - queue.Enqueue(2); - queue.Enqueue(5); - queue.Enqueue(4); - - /* 访问队首元素 */ - int peek = queue.Peek(); - - /* 元素出队 */ - int poll = queue.Dequeue(); - - /* 获取队列的长度 */ - int size = queue.Count(); - - /* 判断队列是否为空 */ - bool isEmpty = queue.Count() == 0; - ``` - -=== "Swift" - - ```swift title="queue.swift" - /* 初始化队列 */ - // Swift 没有内置的队列类,可以把 Array 当作队列来使用 - var queue: [Int] = [] - - /* 元素入队 */ - queue.append(1) - queue.append(3) - queue.append(2) - queue.append(5) - queue.append(4) - - /* 访问队首元素 */ - let peek = queue.first! - - /* 元素出队 */ - // 使用 Array 模拟时 poll 的复杂度为 O(n) - let pool = queue.removeFirst() - - /* 获取队列的长度 */ - let size = queue.count - - /* 判断队列是否为空 */ - let isEmpty = queue.isEmpty - ``` - -=== "Zig" - - ```zig title="queue.zig" - - ``` - -## 5.2.2. 队列实现 - -队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 - -### 基于链表的实现 - -我们将链表的「头结点」和「尾结点」分别看作是队首和队尾,并规定队尾只可添加结点,队首只可删除结点。 - -=== "LinkedListQueue" - ![linkedlist_queue](queue.assets/linkedlist_queue.png) - -=== "push()" - ![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png) - -=== "poll()" - ![linkedlist_queue_poll](queue.assets/linkedlist_queue_poll.png) - -以下是使用链表实现队列的示例代码。 - -=== "Java" - - ```java title="linkedlist_queue.java" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private ListNode front, rear; // 头结点 front ,尾结点 rear - private int queSize = 0; - - public LinkedListQueue() { - front = null; - rear = null; - } - - /* 获取队列的长度 */ - public int size() { - return queSize; - } - - /* 判断队列是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - - /* 入队 */ - public void push(int num) { - // 尾结点后添加 num - ListNode node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == null) { - front = node; - rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } else { - rear.next = node; - rear = node; - } - queSize++; - } - - /* 出队 */ - public int poll() { - int num = peek(); - // 删除头结点 - front = front.next; - queSize--; - return num; - } - - /* 访问队首元素 */ - public int peek() { - if (size() == 0) - throw new EmptyStackException(); - return front.val; - } - - /* 将链表转化为 Array 并返回 */ - public int[] toArray() { - ListNode node = front; - int[] res = new int[size()]; - for (int i = 0; i < res.length; i++) { - res[i] = node.val; - node = node.next; - } - return res; - } - } - ``` - -=== "C++" - - ```cpp title="linkedlist_queue.cpp" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private: - ListNode *front, *rear; // 头结点 front ,尾结点 rear - int queSize; - - public: - LinkedListQueue() { - front = nullptr; - rear = nullptr; - queSize = 0; - } - ~LinkedListQueue() { - delete front; - delete rear; - } - /* 获取队列的长度 */ - int size() { - return queSize; - } - /* 判断队列是否为空 */ - bool empty() { - return queSize == 0; - } - /* 入队 */ - void push(int num) { - // 尾结点后添加 num - ListNode* node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == nullptr) { - front = node; - rear = node; - } - // 如果队列不为空,则将该结点添加到尾结点后 - else { - rear->next = node; - rear = node; - } - queSize++; - } - /* 出队 */ - void poll() { - int num = peek(); - // 删除头结点 - ListNode *tmp = front; - front = front->next; - // 释放内存 - delete tmp; - queSize--; - } - /* 访问队首元素 */ - int peek() { - if (size() == 0) - throw out_of_range("队列为空"); - return front->val; - } - }; - ``` - -=== "Python" - - ```python title="linkedlist_queue.py" - """ 基于链表实现的队列 """ - class LinkedListQueue: - def __init__(self): - self.__front = None # 头结点 front - self.__rear = None # 尾结点 rear - self.__size = 0 - - """ 获取队列的长度 """ - def size(self): - return self.__size - - """ 判断队列是否为空 """ - def is_empty(self): - return not self.__front - - """ 入队 """ - def push(self, num): - # 尾结点后添加 num - node = ListNode(num) - # 如果队列为空,则令头、尾结点都指向该结点 - if self.__front is None: - self.__front = node - self.__rear = node - # 如果队列不为空,则将该结点添加到尾结点后 - else: - self.__rear.next = node - self.__rear = node - self.__size += 1 - - """ 出队 """ - def poll(self): - num = self.peek() - # 删除头结点 - self.__front = self.__front.next - self.__size -= 1 - return num - - """ 访问队首元素 """ - def peek(self): - if self.size() == 0: - print("队列为空") - return False - return self.__front.val - - """ 转化为列表用于打印 """ - def to_list(self): - queue = [] - temp = self.__front - while temp: - queue.append(temp.val) - temp = temp.next - return queue - ``` - -=== "Go" - - ```go title="linkedlist_queue.go" - /* 基于链表实现的队列 */ - type linkedListQueue struct { - // 使用内置包 list 来实现队列 - data *list.List - } - - // newLinkedListQueue 初始化链表 - func newLinkedListQueue() *linkedListQueue { - return &linkedListQueue{ - data: list.New(), - } - } - - // push 入队 - func (s *linkedListQueue) push(value any) { - s.data.PushBack(value) - } - - // poll 出队 - func (s *linkedListQueue) poll() any { - if s.isEmpty() { - return nil - } - e := s.data.Front() - s.data.Remove(e) - return e.Value - } - - // peek 访问队首元素 - func (s *linkedListQueue) peek() any { - if s.isEmpty() { - return nil - } - e := s.data.Front() - return e.Value - } - - // size 获取队列的长度 - func (s *linkedListQueue) size() int { - return s.data.Len() - } - - // isEmpty 判断队列是否为空 - func (s *linkedListQueue) isEmpty() bool { - return s.data.Len() == 0 - } - ``` - -=== "JavaScript" - - ```js title="linkedlist_queue.js" - /* 基于链表实现的队列 */ - class LinkedListQueue { - #front; // 头结点 #front - #rear; // 尾结点 #rear - #queSize = 0; - constructor() { - this.#front = null; - this.#rear = null; - } - /* 获取队列的长度 */ - get size() { - return this.#queSize; - } - /* 判断队列是否为空 */ - isEmpty() { - return this.size === 0; - } - /* 入队 */ - push(num) { - // 尾结点后添加 num - const node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (!this.#front) { - this.#front = node; - this.#rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } else { - this.#rear.next = node; - this.#rear = node; - } - this.#queSize++; - } - /* 出队 */ - poll() { - const num = this.peek(); - // 删除头结点 - this.#front = this.#front.next; - this.#queSize--; - return num; - } - /* 访问队首元素 */ - peek() { - if (this.size === 0) - throw new Error("队列为空"); - return this.#front.val; - } - } - ``` - -=== "TypeScript" - - ```typescript title="linkedlist_queue.ts" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private front: ListNode | null; // 头结点 front - private rear: ListNode | null; // 尾结点 rear - private queSize: number = 0; - constructor() { - this.front = null; - this.rear = null; - } - /* 获取队列的长度 */ - get size(): number { - return this.queSize; - } - /* 判断队列是否为空 */ - isEmpty(): boolean { - return this.size === 0; - } - /* 入队 */ - push(num: number): void { - // 尾结点后添加 num - const node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (!this.front) { - this.front = node; - this.rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } else { - this.rear!.next = node; - this.rear = node; - } - this.queSize++; - } - /* 出队 */ - poll(): number { - const num = this.peek(); - if (!this.front) - throw new Error("队列为空") - // 删除头结点 - this.front = this.front.next; - this.queSize--; - return num; - } - /* 访问队首元素 */ - peek(): number { - if (this.size === 0) - throw new Error("队列为空"); - return this.front!.val; - } - } - ``` - -=== "C" - - ```c title="linkedlist_queue.c" - - ``` - -=== "C#" - - ```csharp title="linkedlist_queue.cs" - /* 基于链表实现的队列 */ - class LinkedListQueue - { - private ListNode? front, rear; // 头结点 front ,尾结点 rear - private int queSize = 0; - public LinkedListQueue() - { - front = null; - rear = null; - } - /* 获取队列的长度 */ - public int size() - { - return queSize; - } - /* 判断队列是否为空 */ - public bool isEmpty() - { - return size() == 0; - } - /* 入队 */ - public void push(int num) - { - // 尾结点后添加 num - ListNode node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == null) - { - front = node; - rear = node; - // 如果队列不为空,则将该结点添加到尾结点后 - } - else if (rear != null) - { - rear.next = node; - rear = node; - } - queSize++; - } - /* 出队 */ - public int poll() - { - int num = peek(); - // 删除头结点 - front = front?.next; - queSize--; - return num; - } - /* 访问队首元素 */ - public int peek() - { - if (size() == 0 || front == null) - throw new Exception(); - return front.val; - } - } - ``` - -=== "Swift" - - ```swift title="linkedlist_queue.swift" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private var front: ListNode? // 头结点 - private var rear: ListNode? // 尾结点 - private var _size = 0 - - init() {} - - /* 获取队列的长度 */ - func size() -> Int { - _size - } - - /* 判断队列是否为空 */ - func isEmpty() -> Bool { - size() == 0 - } - - /* 入队 */ - func push(num: Int) { - // 尾结点后添加 num - let node = ListNode(x: num) - // 如果队列为空,则令头、尾结点都指向该结点 - if front == nil { - front = node - rear = node - } - // 如果队列不为空,则将该结点添加到尾结点后 - else { - rear?.next = node - rear = node - } - _size += 1 - } - - /* 出队 */ - @discardableResult - func poll() -> Int { - let num = peek() - // 删除头结点 - front = front?.next - _size -= 1 - return num - } - - /* 访问队首元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("队列为空") - } - return front!.val - } - } - ``` - -=== "Zig" - - ```zig title="linkedlist_queue.zig" - - ``` - -### 基于数组的实现 - -数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。 - -=== "ArrayQueue" - ![array_queue](queue.assets/array_queue.png) - -=== "push()" - ![array_queue_push](queue.assets/array_queue_push.png) - -=== "poll()" - ![array_queue_poll](queue.assets/array_queue_poll.png) - -细心的同学可能会发现一个问题,即在入队与出队的过程中,两个指针都在向后移动,**在到达尾部后则无法继续移动了**。 - -为了解决此问题,我们可以采取一个取巧方案,**即将数组看作是“环形”的**。具体做法是规定指针越过数组尾部后,再次回到头部接续遍历,这样相当于使数组“首尾相连”了。在环形数组的设定下,获取长度 `size()` 、入队 `push()` 、出队 `poll()` 方法都需要做相应的取余操作处理,使得当尾指针绕回数组头部时,仍然可以正确处理操作。 - -=== "Java" - - ```java title="array_queue.java" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private int[] nums; // 用于存储队列元素的数组 - private int front; // 队首指针,指向队首元素 - private int queSize; // 队列长度 - - public ArrayQueue(int capacity) { - nums = new int[capacity]; - front = queSize = 0; - } - - /* 获取队列的容量 */ - public int capacity() { - return nums.length; - } - - /* 获取队列的长度 */ - public int size() { - return queSize; - } - - /* 判断队列是否为空 */ - public boolean isEmpty() { - return queSize == 0; - } - - /* 入队 */ - public void push(int num) { - if (queSize == capacity()) { - System.out.println("队列已满"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - int rear = (front + queSize) % capacity(); - // 尾结点后添加 num - nums[rear] = num; - queSize++; - } - - /* 出队 */ - public int poll() { - int num = peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - queSize--; - return num; - } - - /* 访问队首元素 */ - public int peek() { - if (isEmpty()) - throw new EmptyStackException(); - return nums[front]; - } - - /* 返回数组 */ - public int[] toArray() { - // 仅转换有效长度范围内的列表元素 - int[] res = new int[queSize]; - for (int i = 0, j = front; i < queSize; i++, j++) { - res[i] = nums[j % capacity()]; - } - return res; - } - } - ``` - -=== "C++" - - ```cpp title="array_queue.cpp" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private: - int *nums; // 用于存储队列元素的数组 - int front; // 队首指针,指向队首元素 - int queSize; // 队列长度 - int queCapacity; // 队列容量 - - public: - ArrayQueue(int capacity) { - // 初始化数组 - nums = new int[capacity]; - queCapacity = capacity; - front = queSize = 0; - } - - ~ArrayQueue() { - delete[] nums; - } - - /* 获取队列的容量 */ - int capacity() { - return queCapacity; - } - - /* 获取队列的长度 */ - int size() { - return queSize; - } - - /* 判断队列是否为空 */ - bool empty() { - return size() == 0; - } - - /* 入队 */ - void push(int num) { - if (queSize == queCapacity) { - cout << "队列已满" << endl; - return; - } - // 计算队尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - int rear = (front + queSize) % queCapacity; - // 尾结点后添加 num - nums[rear] = num; - queSize++; - } - - /* 出队 */ - void poll() { - int num = peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % queCapacity; - queSize--; - } - - /* 访问队首元素 */ - int peek() { - if (empty()) - throw out_of_range("队列为空"); - return nums[front]; - } - }; - ``` - -=== "Python" - - ```python title="array_queue.py" - """ 基于环形数组实现的队列 """ - class ArrayQueue: - def __init__(self, size): - self.__nums = [0] * size # 用于存储队列元素的数组 - self.__front = 0 # 队首指针,指向队首元素 - self.__size = 0 # 队列长度 - - """ 获取队列的容量 """ - def capacity(self): - return len(self.__nums) - - """ 获取队列的长度 """ - def size(self): - return self.__size - - """ 判断队列是否为空 """ - def is_empty(self): - return self.__size == 0 - - """ 入队 """ - def push(self, num): - assert self.__size < self.capacity(), "队列已满" - # 计算尾指针,指向队尾索引 + 1 - # 通过取余操作,实现 rear 越过数组尾部后回到头部 - rear = (self.__front + self.__size) % self.capacity() - # 尾结点后添加 num - self.__nums[rear] = num - self.__size += 1 - - """ 出队 """ - def poll(self): - num = self.peek() - # 队首指针向后移动一位,若越过尾部则返回到数组头部 - self.__front = (self.__front + 1) % self.capacity() - self.__size -= 1 - return num - - """ 访问队首元素 """ - def peek(self): - assert not self.is_empty(), "队列为空" - return self.__nums[self.__front] - - """ 返回列表用于打印 """ - def to_list(self): - res = [0] * self.size() - j = self.__front - for i in range(self.size()): - res[i] = self.__nums[(j % self.capacity())] - j += 1 - return res - ``` - -=== "Go" - - ```go title="array_queue.go" - /* 基于环形数组实现的队列 */ - type arrayQueue struct { - nums []int // 用于存储队列元素的数组 - front int // 队首指针,指向队首元素 - queSize int // 队列长度 - queCapacity int // 队列容量(即最大容纳元素数量) - } - - // newArrayQueue 基于环形数组实现的队列 - func newArrayQueue(queCapacity int) *arrayQueue { - return &arrayQueue{ - nums: make([]int, queCapacity), - queCapacity: queCapacity, - front: 0, - queSize: 0, - } - } - - // size 获取队列的长度 - func (q *arrayQueue) size() int { - return q.queSize - } - - // isEmpty 判断队列是否为空 - func (q *arrayQueue) isEmpty() bool { - return q.queSize == 0 - } - - // push 入队 - func (q *arrayQueue) push(num int) { - // 当 rear == queCapacity 表示队列已满 - if q.queSize == q.queCapacity { - return - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - rear := (q.front + q.queSize) % q.queCapacity - // 尾结点后添加 num - q.nums[rear] = num - q.queSize++ - } - - // poll 出队 - func (q *arrayQueue) poll() any { - num := q.peek() - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - q.front = (q.front + 1) % q.queCapacity - q.queSize-- - return num - } - - // peek 访问队首元素 - func (q *arrayQueue) peek() any { - if q.isEmpty() { - return nil - } - return q.nums[q.front] - } - - // 获取 Slice 用于打印 - func (q *arrayQueue) toSlice() []int { - rear := (q.front + q.queSize) - if rear >= q.queCapacity { - rear %= q.queCapacity - return append(q.nums[q.front:], q.nums[:rear]...) - } - return q.nums[q.front:rear] - } - ``` - -=== "JavaScript" - - ```js title="array_queue.js" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - #nums; // 用于存储队列元素的数组 - #front = 0; // 队首指针,指向队首元素 - #queSize = 0; // 队列长度 - - constructor(capacity) { - this.#nums = new Array(capacity); - } - - /* 获取队列的容量 */ - get capacity() { - return this.#nums.length; - } - - /* 获取队列的长度 */ - get size() { - return this.#queSize; - } - - /* 判断队列是否为空 */ - empty() { - return this.#queSize == 0; - } - - /* 入队 */ - push(num) { - if (this.size == this.capacity) { - console.log("队列已满"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - const rear = (this.#front + this.size) % this.capacity; - // 尾结点后添加 num - this.#nums[rear] = num; - this.#queSize++; - } - - /* 出队 */ - poll() { - const num = this.peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - this.#front = (this.#front + 1) % this.capacity; - this.#queSize--; - return num; - } - - /* 访问队首元素 */ - peek() { - if (this.empty()) - throw new Error("队列为空"); - return this.#nums[this.#front]; - } - } - ``` - -=== "TypeScript" - - ```typescript title="array_queue.ts" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private nums: number[]; // 用于存储队列元素的数组 - private front: number; // 队首指针,指向队首元素 - private queSize: number; // 队列长度 - - constructor(capacity: number) { - this.nums = new Array(capacity); - this.front = this.queSize = 0; - } - - /* 获取队列的容量 */ - get capacity(): number { - return this.nums.length; - } - - /* 获取队列的长度 */ - get size(): number { - return this.queSize; - } - - /* 判断队列是否为空 */ - empty(): boolean { - return this.queSize == 0; - } - - /* 入队 */ - push(num: number): void { - if (this.size == this.capacity) { - console.log("队列已满"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - const rear = (this.front + this.queSize) % this.capacity; - // 尾结点后添加 num - this.nums[rear] = num; - this.queSize++; - } - - /* 出队 */ - poll(): number { - const num = this.peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - this.front = (this.front + 1) % this.capacity; - this.queSize--; - return num; - } - - /* 访问队首元素 */ - peek(): number { - if (this.empty()) - throw new Error("队列为空"); - return this.nums[this.front]; - } - } - ``` - -=== "C" - - ```c title="array_queue.c" - - ``` - -=== "C#" - - ```csharp title="array_queue.cs" - /* 基于环形数组实现的队列 */ - class ArrayQueue - { - private int[] nums; // 用于存储队列元素的数组 - private int front; // 队首指针,指向队首元素 - private int queSize; // 队列长度 - - public ArrayQueue(int capacity) - { - nums = new int[capacity]; - front = queSize = 0; - } - - /* 获取队列的容量 */ - public int capacity() - { - return nums.Length; - } - - /* 获取队列的长度 */ - public int size() - { - return queSize; - } - - /* 判断队列是否为空 */ - public bool isEmpty() - { - return queSize == 0; - } - - /* 入队 */ - public void push(int num) - { - if (queSize == capacity()) - { - Console.WriteLine("队列已满"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - int rear = (front + queSize) % capacity(); - // 尾结点后添加 num - nums[rear] = num; - queSize++; - } - - /* 出队 */ - public int poll() - { - int num = peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - queSize--; - return num; - } - - /* 访问队首元素 */ - public int peek() - { - if (isEmpty()) - throw new Exception(); - return nums[front]; - } - } - ``` - -=== "Swift" - - ```swift title="array_queue.swift" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private var nums: [Int] // 用于存储队列元素的数组 - private var front = 0 // 队首指针,指向队首元素 - private var queSize = 0 // 队列长度 - - init(capacity: Int) { - // 初始化数组 - nums = Array(repeating: 0, count: capacity) - } - - /* 获取队列的容量 */ - func capacity() -> Int { - nums.count - } - - /* 获取队列的长度 */ - func size() -> Int { - queSize - } - - /* 判断队列是否为空 */ - func isEmpty() -> Bool { - queSize == 0 - } - - /* 入队 */ - func push(num: Int) { - if size() == capacity() { - print("队列已满") - return - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - let rear = (front + queSize) % capacity() - // 尾结点后添加 num - nums[rear] = num - queSize += 1 - } - - /* 出队 */ - @discardableResult - func poll() -> Int { - let num = peek() - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity() - queSize -= 1 - return num - } - - /* 访问队首元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("队列为空") - } - return nums[front] - } - } - ``` - -=== "Zig" - - ```zig title="array_queue.zig" - - ``` - -以上代码仍存在局限性,即长度不可变。然而,我们可以通过将数组替换为列表(即动态数组)来引入扩容机制,有兴趣的同学可以尝试实现。 - -## 5.2.3. 两种实现对比 - -与栈的结论一致,在此不再赘述。 - -## 5.2.4. 队列典型应用 - -- **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 -- **各种待办事项**。例如打印机的任务队列、餐厅的出餐队列等等。 diff --git a/build/chapter_stack_and_queue/stack.md b/build/chapter_stack_and_queue/stack.md deleted file mode 100755 index 894c03552..000000000 --- a/build/chapter_stack_and_queue/stack.md +++ /dev/null @@ -1,1113 +0,0 @@ ---- -comments: true ---- - -# 5.1. 栈 - -「栈 Stack」是一种遵循「先入后出 first in, last out」数据操作规则的线性数据结构。我们可以将栈类比为放在桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。 - -“盘子”是一种形象比喻,我们将盘子替换为任意一种元素(例如整数、字符、对象等),就得到了栈数据结构。 - -我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。 - -![stack_operations](stack.assets/stack_operations.png) - -

Fig. 栈的先入后出特性

- -## 5.1.1. 栈常用操作 - -栈的常用操作见下表(方法命名以 Java 为例)。 - -

Table. 栈的常用操作

- -
- -| 方法 | 描述 | 时间复杂度 | -| --------- | ---------------------- | ---------- | -| push() | 元素入栈(添加至栈顶) | $O(1)$ | -| pop() | 栈顶元素出栈 | $O(1)$ | -| peek() | 访问栈顶元素 | $O(1)$ | -| size() | 获取栈的长度 | $O(1)$ | -| isEmpty() | 判断栈是否为空 | $O(1)$ | - -
- -我们可以直接使用编程语言实现好的栈类。 某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作。 - -=== "Java" - - ```java title="stack.java" - /* 初始化栈 */ - // 在 Java 中,推荐将 ArrayList 当作栈来使用 - List stack = new ArrayList<>(); - - /* 元素入栈 */ - stack.add(1); - stack.add(3); - stack.add(2); - stack.add(5); - stack.add(4); - - /* 访问栈顶元素 */ - int peek = stack.get(stack.size() - 1); - - /* 元素出栈 */ - int pop = stack.remove(stack.size() - 1); - - /* 获取栈的长度 */ - int size = stack.size(); - - /* 判断是否为空 */ - boolean isEmpty = stack.isEmpty(); - ``` - -=== "C++" - - ```cpp title="stack.cpp" - /* 初始化栈 */ - stack stack; - - /* 元素入栈 */ - stack.push(1); - stack.push(3); - stack.push(2); - stack.push(5); - stack.push(4); - - /* 访问栈顶元素 */ - int top = stack.top(); - - /* 元素出栈 */ - stack.pop(); - - /* 获取栈的长度 */ - int size = stack.size(); - - /* 判断是否为空 */ - bool empty = stack.empty(); - ``` - -=== "Python" - - ```python title="stack.py" - """ 初始化栈 """ - # Python 没有内置的栈类,可以把 List 当作栈来使用 - stack = [] - - """ 元素入栈 """ - stack.append(1) - stack.append(3) - stack.append(2) - stack.append(5) - stack.append(4) - - """ 访问栈顶元素 """ - peek = stack[-1] - - """ 元素出栈 """ - pop = stack.pop() - - """ 获取栈的长度 """ - size = len(stack) - - """ 判断是否为空 """ - is_empty = len(stack) == 0 - ``` - -=== "Go" - - ```go title="stack_test.go" - /* 初始化栈 */ - // 在 Go 中,推荐将 Slice 当作栈来使用 - var stack []int - - /* 元素入栈 */ - stack = append(stack, 1) - stack = append(stack, 3) - stack = append(stack, 2) - stack = append(stack, 5) - stack = append(stack, 4) - - /* 访问栈顶元素 */ - peek := stack[len(stack)-1] - - /* 元素出栈 */ - pop := stack[len(stack)-1] - stack = stack[:len(stack)-1] - - /* 获取栈的长度 */ - size := len(stack) - - /* 判断是否为空 */ - isEmpty := len(stack) == 0 - ``` - -=== "JavaScript" - - ```js title="stack.js" - /* 初始化栈 */ - // Javascript 没有内置的栈类,可以把 Array 当作栈来使用 - const stack = []; - - /* 元素入栈 */ - stack.push(1); - stack.push(3); - stack.push(2); - stack.push(5); - stack.push(4); - - /* 访问栈顶元素 */ - const peek = stack[stack.length-1]; - - /* 元素出栈 */ - const pop = stack.pop(); - - /* 获取栈的长度 */ - const size = stack.length; - - /* 判断是否为空 */ - const is_empty = stack.length === 0; - ``` - -=== "TypeScript" - - ```typescript title="stack.ts" - /* 初始化栈 */ - // Typescript 没有内置的栈类,可以把 Array 当作栈来使用 - const stack: number[] = []; - - /* 元素入栈 */ - stack.push(1); - stack.push(3); - stack.push(2); - stack.push(5); - stack.push(4); - - /* 访问栈顶元素 */ - const peek = stack[stack.length - 1]; - - /* 元素出栈 */ - const pop = stack.pop(); - - /* 获取栈的长度 */ - const size = stack.length; - - /* 判断是否为空 */ - const is_empty = stack.length === 0; - ``` - -=== "C" - - ```c title="stack.c" - - ``` - -=== "C#" - - ```csharp title="stack.cs" - /* 初始化栈 */ - Stack stack = new (); - - /* 元素入栈 */ - stack.Push(1); - stack.Push(3); - stack.Push(2); - stack.Push(5); - stack.Push(4); - - /* 访问栈顶元素 */ - int peek = stack.Peek(); - - /* 元素出栈 */ - int pop = stack.Pop(); - - /* 获取栈的长度 */ - int size = stack.Count(); - - /* 判断是否为空 */ - bool isEmpty = stack.Count()==0; - ``` - -=== "Swift" - - ```swift title="stack.swift" - /* 初始化栈 */ - // Swift 没有内置的栈类,可以把 Array 当作栈来使用 - var stack: [Int] = [] - - /* 元素入栈 */ - stack.append(1) - stack.append(3) - stack.append(2) - stack.append(5) - stack.append(4) - - /* 访问栈顶元素 */ - let peek = stack.last! - - /* 元素出栈 */ - let pop = stack.removeLast() - - /* 获取栈的长度 */ - let size = stack.count - - /* 判断是否为空 */ - let isEmpty = stack.isEmpty - ``` - -=== "Zig" - - ```zig title="stack.zig" - - ``` - -## 5.1.2. 栈的实现 - -为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 - -栈规定元素是先入后出的,因此我们只能在栈顶添加或删除元素。然而,数组或链表都可以在任意位置添加删除元素,因此 **栈可被看作是一种受约束的数组或链表**。换言之,我们可以“屏蔽”数组或链表的部分无关操作,使之对外的表现逻辑符合栈的规定即可。 - -### 基于链表的实现 - -使用「链表」实现栈时,将链表的头结点看作栈顶,将尾结点看作栈底。 - -对于入栈操作,将元素插入到链表头部即可,这种结点添加方式被称为“头插法”。而对于出栈操作,则将头结点从链表中删除即可。 - -=== "LinkedListStack" - ![linkedlist_stack](stack.assets/linkedlist_stack.png) - -=== "push()" - ![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png) - -=== "pop()" - ![linkedlist_stack_pop](stack.assets/linkedlist_stack_pop.png) - -以下是基于链表实现栈的示例代码。 - -=== "Java" - - ```java title="linkedlist_stack.java" - /* 基于链表实现的栈 */ - class LinkedListStack { - private ListNode stackPeek; // 将头结点作为栈顶 - private int stkSize = 0; // 栈的长度 - - public LinkedListStack() { - stackPeek = null; - } - - /* 获取栈的长度 */ - public int size() { - return stkSize; - } - - /* 判断栈是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - - /* 入栈 */ - public void push(int num) { - ListNode node = new ListNode(num); - node.next = stackPeek; - stackPeek = node; - stkSize++; - } - - /* 出栈 */ - public int pop() { - int num = peek(); - stackPeek = stackPeek.next; - stkSize--; - return num; - } - - /* 访问栈顶元素 */ - public int peek() { - if (size() == 0) - throw new EmptyStackException(); - return stackPeek.val; - } - - /* 将 List 转化为 Array 并返回 */ - public int[] toArray() { - ListNode node = stackPeek; - int[] res = new int[size()]; - for (int i = res.length - 1; i >= 0; i--) { - res[i] = node.val; - node = node.next; - } - return res; - } - } - ``` - -=== "C++" - - ```cpp title="linkedlist_stack.cpp" - /* 基于链表实现的栈 */ - class LinkedListStack { - private: - ListNode* stackTop; // 将头结点作为栈顶 - int stkSize; // 栈的长度 - - public: - LinkedListStack() { - stackTop = nullptr; - stkSize = 0; - } - ~LinkedListStack() { - freeMemoryLinkedList(stackTop); - } - /* 获取栈的长度 */ - int size() { - return stkSize; - } - /* 判断栈是否为空 */ - bool empty() { - return size() == 0; - } - /* 入栈 */ - void push(int num) { - ListNode* node = new ListNode(num); - node->next = stackTop; - stackTop = node; - stkSize++; - } - /* 出栈 */ - void pop() { - int num = top(); - ListNode *tmp = stackTop; - stackTop = stackTop->next; - // 释放内存 - delete tmp; - stkSize--; - } - /* 访问栈顶元素 */ - int top() { - if (size() == 0) - throw out_of_range("栈为空"); - return stackTop->val; - } - }; - ``` - -=== "Python" - - ```python title="linkedlist_stack.py" - """ 基于链表实现的栈 """ - class LinkedListStack: - def __init__(self): - self.__peek = None - self.__size = 0 - - """ 获取栈的长度 """ - def size(self): - return self.__size - - """ 判断栈是否为空 """ - def is_empty(self): - return not self.__peek - - """ 入栈 """ - def push(self, val): - node = ListNode(val) - node.next = self.__peek - self.__peek = node - self.__size += 1 - - """ 出栈 """ - def pop(self): - num = self.peek() - self.__peek = self.__peek.next - self.__size -= 1 - return num - - """ 访问栈顶元素 """ - def peek(self): - # 判空处理 - if not self.__peek: return None - return self.__peek.val - - """ 转化为列表用于打印 """ - def to_list(self): - arr = [] - node = self.__peek - while node: - arr.append(node.val) - node = node.next - arr.reverse() - return arr - ``` - -=== "Go" - - ```go title="linkedlist_stack.go" - /* 基于链表实现的栈 */ - type linkedListStack struct { - // 使用内置包 list 来实现栈 - data *list.List - } - - // newLinkedListStack 初始化链表 - func newLinkedListStack() *linkedListStack { - return &linkedListStack{ - data: list.New(), - } - } - - // push 入栈 - func (s *linkedListStack) push(value int) { - s.data.PushBack(value) - } - - // pop 出栈 - func (s *linkedListStack) pop() any { - if s.isEmpty() { - return nil - } - e := s.data.Back() - s.data.Remove(e) - return e.Value - } - - // peek 访问栈顶元素 - func (s *linkedListStack) peek() any { - if s.isEmpty() { - return nil - } - e := s.data.Back() - return e.Value - } - - // size 获取栈的长度 - func (s *linkedListStack) size() int { - return s.data.Len() - } - - // isEmpty 判断栈是否为空 - func (s *linkedListStack) isEmpty() bool { - return s.data.Len() == 0 - } - ``` - -=== "JavaScript" - - ```js title="linkedlist_stack.js" - /* 基于链表实现的栈 */ - class LinkedListStack { - #stackPeek; // 将头结点作为栈顶 - #stkSize = 0; // 栈的长度 - - constructor() { - this.#stackPeek = null; - } - - /* 获取栈的长度 */ - get size() { - return this.#stkSize; - } - - /* 判断栈是否为空 */ - isEmpty() { - return this.size == 0; - } - - /* 入栈 */ - push(num) { - const node = new ListNode(num); - node.next = this.#stackPeek; - this.#stackPeek = node; - this.#stkSize++; - } - - /* 出栈 */ - pop() { - const num = this.peek(); - if (!this.#stackPeek) { - throw new Error("栈为空!"); - } - this.#stackPeek = this.#stackPeek.next; - this.#stkSize--; - return num; - } - - /* 访问栈顶元素 */ - peek() { - if (!this.#stackPeek) { - throw new Error("栈为空!"); - } - return this.#stackPeek.val; - } - - /* 将链表转化为 Array 并返回 */ - toArray() { - let node = this.#stackPeek; - const res = new Array(this.size); - for (let i = res.length - 1; i >= 0; i--) { - res[i] = node.val; - node = node.next; - } - return res; - } - } - ``` - -=== "TypeScript" - - ```typescript title="linkedlist_stack.ts" - /* 基于链表实现的栈 */ - class LinkedListStack { - private stackPeek: ListNode | null; // 将头结点作为栈顶 - private stkSize: number = 0; // 栈的长度 - - constructor() { - this.stackPeek = null; - } - - /* 获取栈的长度 */ - get size(): number { - return this.stkSize; - } - - /* 判断栈是否为空 */ - isEmpty(): boolean { - return this.size == 0; - } - - /* 入栈 */ - push(num: number): void { - const node = new ListNode(num); - node.next = this.stackPeek; - this.stackPeek = node; - this.stkSize++; - } - - /* 出栈 */ - pop(): number { - const num = this.peek(); - if (!this.stackPeek) { - throw new Error("栈为空!"); - } - this.stackPeek = this.stackPeek.next; - this.stkSize--; - return num; - } - - /* 访问栈顶元素 */ - peek(): number { - if (!this.stackPeek) { - throw new Error("栈为空!"); - } - return this.stackPeek.val; - } - - /* 将链表转化为 Array 并返回 */ - toArray(): number[] { - let node = this.stackPeek; - const res = new Array(this.size); - for (let i = res.length - 1; i >= 0; i--) { - res[i] = node!.val; - node = node!.next; - } - return res; - } - } - ``` - -=== "C" - - ```c title="linkedlist_stack.c" - - ``` - -=== "C#" - - ```csharp title="linkedlist_stack.cs" - /* 基于链表实现的栈 */ - class LinkedListStack - { - private ListNode stackPeek; // 将头结点作为栈顶 - private int stkSize = 0; // 栈的长度 - public LinkedListStack() - { - stackPeek = null; - } - /* 获取栈的长度 */ - public int size() - { - return stkSize; - } - /* 判断栈是否为空 */ - public bool isEmpty() - { - return size() == 0; - } - /* 入栈 */ - public void push(int num) - { - ListNode node = new ListNode(num); - node.next = stackPeek; - stackPeek = node; - stkSize++; - } - /* 出栈 */ - public int pop() - { - int num = peek(); - stackPeek = stackPeek?.next; - stkSize--; - return num; - } - /* 访问栈顶元素 */ - public int peek() - { - if (size() == 0) - throw new Exception(); - return stackPeek.val; - } - } - ``` - -=== "Swift" - - ```swift title="linkedlist_stack.swift" - /* 基于链表实现的栈 */ - class LinkedListStack { - private var _peek: ListNode? // 将头结点作为栈顶 - private var _size = 0 // 栈的长度 - - init() {} - - /* 获取栈的长度 */ - func size() -> Int { - _size - } - - /* 判断栈是否为空 */ - func isEmpty() -> Bool { - size() == 0 - } - - /* 入栈 */ - func push(num: Int) { - let node = ListNode(x: num) - node.next = _peek - _peek = node - _size += 1 - } - - /* 出栈 */ - @discardableResult - func pop() -> Int { - let num = peek() - _peek = _peek?.next - _size -= 1 - return num - } - - /* 访问栈顶元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("栈为空") - } - return _peek!.val - } - } - ``` - -=== "Zig" - - ```zig title="linkedlist_stack.zig" - - ``` - -### 基于数组的实现 - -使用「数组」实现栈时,考虑将数组的尾部当作栈顶。这样设计下,「入栈」与「出栈」操作就对应在数组尾部「添加元素」与「删除元素」,时间复杂度都为 $O(1)$ 。 - -=== "ArrayStack" - ![array_stack](stack.assets/array_stack.png) - -=== "push()" - ![array_stack_push](stack.assets/array_stack_push.png) - -=== "pop()" - ![array_stack_pop](stack.assets/array_stack_pop.png) - -由于入栈的元素可能是源源不断的,因此可以使用支持动态扩容的「列表」,这样就无需自行实现数组扩容了。以下是示例代码。 - -=== "Java" - - ```java title="array_stack.java" - /* 基于数组实现的栈 */ - class ArrayStack { - private ArrayList stack; - public ArrayStack() { - // 初始化列表(动态数组) - stack = new ArrayList<>(); - } - - /* 获取栈的长度 */ - public int size() { - return stack.size(); - } - - /* 判断栈是否为空 */ - public boolean isEmpty() { - return size() == 0; - } - - /* 入栈 */ - public void push(int num) { - stack.add(num); - } - - /* 出栈 */ - public int pop() { - if (isEmpty()) - throw new EmptyStackException(); - return stack.remove(size() - 1); - } - - /* 访问栈顶元素 */ - public int peek() { - if (isEmpty()) - throw new EmptyStackException(); - return stack.get(size() - 1); - } - - /* 将 List 转化为 Array 并返回 */ - public Object[] toArray() { - return stack.toArray(); - } - } - ``` - -=== "C++" - - ```cpp title="array_stack.cpp" - /* 基于数组实现的栈 */ - class ArrayStack { - private: - vector stack; - - public: - /* 获取栈的长度 */ - int size() { - return stack.size(); - } - /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); - } - /* 入栈 */ - void push(int num) { - stack.push_back(num); - } - /* 出栈 */ - void pop() { - int oldTop = top(); - stack.pop_back(); - } - /* 访问栈顶元素 */ - int top() { - if(empty()) - throw out_of_range("栈为空"); - return stack.back(); - } - }; - ``` - -=== "Python" - - ```python title="array_stack.py" - """ 基于数组实现的栈 """ - class ArrayStack: - def __init__(self): - self.__stack = [] - - """ 获取栈的长度 """ - def size(self): - return len(self.__stack) - - """ 判断栈是否为空 """ - def is_empty(self): - return self.__stack == [] - - """ 入栈 """ - def push(self, item): - self.__stack.append(item) - - """ 出栈 """ - def pop(self): - assert not self.is_empty(), "栈为空" - return self.__stack.pop() - - """ 访问栈顶元素 """ - def peek(self): - assert not self.is_empty(), "栈为空" - return self.__stack[-1] - - """ 返回列表用于打印 """ - def to_list(self): - return self.__stack - ``` - -=== "Go" - - ```go title="array_stack.go" - /* 基于数组实现的栈 */ - type arrayStack struct { - data []int // 数据 - } - - func newArrayStack() *arrayStack { - return &arrayStack{ - // 设置栈的长度为 0,容量为 16 - data: make([]int, 0, 16), - } - } - - // size 栈的长度 - func (s *arrayStack) size() int { - return len(s.data) - } - - // isEmpty 栈是否为空 - func (s *arrayStack) isEmpty() bool { - return s.size() == 0 - } - - // push 入栈 - func (s *arrayStack) push(v int) { - // 切片会自动扩容 - s.data = append(s.data, v) - } - - // pop 出栈 - func (s *arrayStack) pop() any { - // 弹出栈前,先判断是否为空 - if s.isEmpty() { - return nil - } - val := s.peek() - s.data = s.data[:len(s.data)-1] - return val - } - - // peek 获取栈顶元素 - func (s *arrayStack) peek() any { - if s.isEmpty() { - return nil - } - val := s.data[len(s.data)-1] - return val - } - ``` - -=== "JavaScript" - - ```js title="array_stack.js" - /* 基于数组实现的栈 */ - class ArrayStack { - stack; - constructor() { - this.stack = []; - } - /* 获取栈的长度 */ - get size() { - return this.stack.length; - } - /* 判断栈是否为空 */ - empty() { - return this.stack.length === 0; - } - /* 入栈 */ - push(num) { - this.stack.push(num); - } - /* 出栈 */ - pop() { - if (this.empty()) - throw new Error("栈为空"); - return this.stack.pop(); - } - /* 访问栈顶元素 */ - top() { - if (this.empty()) - throw new Error("栈为空"); - return this.stack[this.stack.length - 1]; - } - }; - ``` - -=== "TypeScript" - - ```typescript title="array_stack.ts" - /* 基于数组实现的栈 */ - class ArrayStack { - private stack: number[]; - constructor() { - this.stack = []; - } - /* 获取栈的长度 */ - get size(): number { - return this.stack.length; - } - /* 判断栈是否为空 */ - empty(): boolean { - return this.stack.length === 0; - } - /* 入栈 */ - push(num: number): void { - this.stack.push(num); - } - /* 出栈 */ - pop(): number | undefined { - if (this.empty()) - throw new Error('栈为空'); - return this.stack.pop(); - } - /* 访问栈顶元素 */ - top(): number | undefined { - if (this.empty()) - throw new Error('栈为空'); - return this.stack[this.stack.length - 1]; - } - }; - ``` - -=== "C" - - ```c title="array_stack.c" - - ``` - -=== "C#" - - ```csharp title="array_stack.cs" - /* 基于数组实现的栈 */ - class ArrayStack - { - private List stack; - public ArrayStack() - { - // 初始化列表(动态数组) - stack = new(); - } - /* 获取栈的长度 */ - public int size() - { - return stack.Count(); - } - /* 判断栈是否为空 */ - public bool isEmpty() - { - return size() == 0; - } - /* 入栈 */ - public void push(int num) - { - stack.Add(num); - } - /* 出栈 */ - public int pop() - { - if (isEmpty()) - throw new Exception(); - var val = peek(); - stack.RemoveAt(size() - 1); - return val; - } - /* 访问栈顶元素 */ - public int peek() - { - if (isEmpty()) - throw new Exception(); - return stack[size() - 1]; - } - } - ``` - -=== "Swift" - - ```swift title="array_stack.swift" - /* 基于数组实现的栈 */ - class ArrayStack { - private var stack: [Int] - - init() { - // 初始化列表(动态数组) - stack = [] - } - - /* 获取栈的长度 */ - func size() -> Int { - stack.count - } - - /* 判断栈是否为空 */ - func isEmpty() -> Bool { - stack.isEmpty - } - - /* 入栈 */ - func push(num: Int) { - stack.append(num) - } - - /* 出栈 */ - @discardableResult - func pop() -> Int { - if isEmpty() { - fatalError("栈为空") - } - return stack.removeLast() - } - - /* 访问栈顶元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("栈为空") - } - return stack.last! - } - } - ``` - -=== "Zig" - - ```zig title="array_stack.zig" - - ``` - -## 5.1.3. 两种实现对比 - -### 支持操作 - -两种实现都支持栈定义中的各项操作,数组实现额外支持随机访问,但这已经超出栈的定义范畴,一般不会用到。 - -### 时间效率 - -在数组(列表)实现中,入栈与出栈操作都是在预先分配好的连续内存中操作,具有很好的缓存本地性,效率很好。然而,如果入栈时超出数组容量,则会触发扩容机制,那么该次入栈操作的时间复杂度为 $O(n)$ 。 - -在链表实现中,链表的扩容非常灵活,不存在上述数组扩容时变慢的问题。然而,入栈操作需要初始化结点对象并修改指针,因而效率不如数组。进一步地思考,如果入栈元素不是 `int` 而是结点对象,那么就可以省去初始化步骤,从而提升效率。 - -综上所述,当入栈与出栈操作的元素是基本数据类型(例如 `int` , `double` )时,则结论如下: - -- 数组实现的栈在触发扩容时会变慢,但由于扩容是低频操作,因此 **总体效率更高**; -- 链表实现的栈可以提供 **更加稳定的效率表现**; - -### 空间效率 - -在初始化列表时,系统会给列表分配“初始容量”,该容量可能超过我们的需求。并且扩容机制一般是按照特定倍率(比如 2 倍)进行扩容,扩容后的容量也可能超出我们的需求。因此,**数组实现栈会造成一定的空间浪费**。 - -当然,由于结点需要额外存储指针,因此 **链表结点比数组元素占用更大**。 - -综上,我们不能简单地确定哪种实现更加省内存,需要 case-by-case 地分析。 - -## 5.1.4. 栈典型应用 - -- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 -- **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 diff --git a/build/chapter_stack_and_queue/summary.md b/build/chapter_stack_and_queue/summary.md deleted file mode 100644 index 19cf6c8c3..000000000 --- a/build/chapter_stack_and_queue/summary.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -comments: true ---- - -# 5.4. 小结 - -- 栈是一种遵循先入后出的数据结构,可以使用数组或链表实现。 -- 在时间效率方面,栈的数组实现具有更好的平均效率,但扩容时会导致单次入栈操作的时间复杂度劣化至 $O(n)$ 。相对地,栈的链表实现具有更加稳定的效率表现。 -- 在空间效率方面,栈的数组实现会造成一定空间浪费,然而链表结点比数组元素占用内存更大。 -- 队列是一种遵循先入先出的数据结构,可以使用数组或链表实现。对于两种实现的时间效率与空间效率对比,与上述栈的结论相同。 -- 双向队列的两端都可以添加与删除元素。 diff --git a/build/chapter_tree/avl_tree.md b/build/chapter_tree/avl_tree.md deleted file mode 100755 index 0d4a36fea..000000000 --- a/build/chapter_tree/avl_tree.md +++ /dev/null @@ -1,1761 +0,0 @@ ---- -comments: true ---- - -# 7.4. AVL 树 * - -在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 $O(\log n)$ 劣化至 $O(n)$ 。 - -如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。 - -![degradation_from_removing_node](avl_tree.assets/degradation_from_removing_node.png) - -再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。 - -![degradation_from_inserting_node](avl_tree.assets/degradation_from_inserting_node.png) - -G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。 - -换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。 - -## 7.4.1. AVL 树常见术语 - -「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。 - -### 结点高度 - -在 AVL 树的操作中,需要获取结点「高度 Height」,所以给 AVL 树的结点类添加 `height` 变量。 - -=== "Java" - - ```java title="" - /* AVL 树结点类 */ - class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode left; // 左子结点 - public TreeNode right; // 右子结点 - public TreeNode(int x) { val = x; } - } - ``` - -=== "C++" - - ```cpp title="" - /* AVL 树结点类 */ - struct TreeNode { - int val{}; // 结点值 - int height = 0; // 结点高度 - TreeNode *left{}; // 左子结点 - TreeNode *right{}; // 右子结点 - TreeNode() = default; - explicit TreeNode(int x) : val(x){} - }; - ``` - -=== "Python" - - ```python title="" - """ AVL 树结点类 """ - class TreeNode: - def __init__(self, val=None, left=None, right=None): - self.val = val # 结点值 - self.height = 0 # 结点高度 - self.left = left # 左子结点引用 - self.right = right # 右子结点引用 - ``` - -=== "Go" - - ```go title="" - /* AVL 树结点类 */ - type TreeNode struct { - Val int // 结点值 - Height int // 结点高度 - Left *TreeNode // 左子结点引用 - Right *TreeNode // 右子结点引用 - } - ``` - -=== "JavaScript" - - ```js title="" - class TreeNode { - val; // 结点值 - height; //结点高度 - left; // 左子结点指针 - right; // 右子结点指针 - constructor(val, left, right, height) { - this.val = val === undefined ? 0 : val; - this.height = height === undefined ? 0 : height; - this.left = left === undefined ? null : left; - this.right = right === undefined ? null : right; - } - } - ``` - -=== "TypeScript" - - ```typescript title="" - class TreeNode { - val: number; // 结点值 - height: number; // 结点高度 - left: TreeNode | null; // 左子结点指针 - right: TreeNode | null; // 右子结点指针 - constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { - this.val = val === undefined ? 0 : val; - this.height = height === undefined ? 0 : height; - this.left = left === undefined ? null : left; - this.right = right === undefined ? null : right; - } - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - /* AVL 树结点类 */ - class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode? left; // 左子结点 - public TreeNode? right; // 右子结点 - public TreeNode(int x) { val = x; } - } - ``` - -=== "Swift" - - ```swift title="" - /* AVL 树结点类 */ - class TreeNode { - var val: Int // 结点值 - var height: Int // 结点高度 - var left: TreeNode? // 左子结点 - var right: TreeNode? // 右子结点 - - init(x: Int) { - val = x - height = 0 - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新结点的高度。 - -=== "Java" - - ```java title="avl_tree.java" - /* 获取结点高度 */ - int height(TreeNode node) { - // 空结点高度为 -1 ,叶结点高度为 0 - return node == null ? -1 : node.height; - } - - /* 更新结点高度 */ - void updateHeight(TreeNode node) { - // 结点高度等于最高子树高度 + 1 - node.height = Math.max(height(node.left), height(node.right)) + 1; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 获取结点高度 */ - int height(TreeNode* node) { - // 空结点高度为 -1 ,叶结点高度为 0 - return node == nullptr ? -1 : node->height; - } - - /* 更新结点高度 */ - void updateHeight(TreeNode* node) { - // 结点高度等于最高子树高度 + 1 - node->height = max(height(node->left), height(node->right)) + 1; - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 获取结点高度 """ - def height(self, node: Optional[TreeNode]) -> int: - # 空结点高度为 -1 ,叶结点高度为 0 - if node is not None: - return node.height - return -1 - - """ 更新结点高度 """ - def __update_height(self, node: Optional[TreeNode]): - # 结点高度等于最高子树高度 + 1 - node.height = max([self.height(node.left), self.height(node.right)]) + 1 - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 获取结点高度 */ - func height(node *TreeNode) int { - // 空结点高度为 -1 ,叶结点高度为 0 - if node != nil { - return node.Height - } - return -1 - } - - /* 更新结点高度 */ - func updateHeight(node *TreeNode) { - lh := height(node.Left) - rh := height(node.Right) - // 结点高度等于最高子树高度 + 1 - if lh > rh { - node.Height = lh + 1 - } else { - node.Height = rh + 1 - } - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 获取结点高度 */ - height(node) { - // 空结点高度为 -1 ,叶结点高度为 0 - return node === null ? -1 : node.height; - } - - /* 更新结点高度 */ - updateHeight(node) { - // 结点高度等于最高子树高度 + 1 - node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 获取结点高度 */ - height(node: TreeNode): number { - // 空结点高度为 -1 ,叶结点高度为 0 - return node === null ? -1 : node.height; - } - - /* 更新结点高度 */ - updateHeight(node: TreeNode): void { - // 结点高度等于最高子树高度 + 1 - node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 获取结点高度 */ - public int height(TreeNode? node) - { - // 空结点高度为 -1 ,叶结点高度为 0 - return node == null ? -1 : node.height; - } - - /* 更新结点高度 */ - private void updateHeight(TreeNode node) - { - // 结点高度等于最高子树高度 + 1 - node.height = Math.Max(height(node.left), height(node.right)) + 1; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 获取结点高度 */ - func height(node: TreeNode?) -> Int { - // 空结点高度为 -1 ,叶结点高度为 0 - node == nil ? -1 : node!.height - } - - /* 更新结点高度 */ - func updateHeight(node: TreeNode?) { - // 结点高度等于最高子树高度 + 1 - node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 - } - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -### 结点平衡因子 - -结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。 - -=== "Java" - - ```java title="avl_tree.java" - /* 获取平衡因子 */ - int balanceFactor(TreeNode node) { - // 空结点平衡因子为 0 - if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.left) - height(node.right); - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 获取平衡因子 */ - int balanceFactor(TreeNode* node) { - // 空结点平衡因子为 0 - if (node == nullptr) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node->left) - height(node->right); - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 获取平衡因子 """ - def balance_factor(self, node: Optional[TreeNode]) -> int: - # 空结点平衡因子为 0 - if node is None: - return 0 - # 结点平衡因子 = 左子树高度 - 右子树高度 - return self.height(node.left) - self.height(node.right) - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 获取平衡因子 */ - func balanceFactor(node *TreeNode) int { - // 空结点平衡因子为 0 - if node == nil { - return 0 - } - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.Left) - height(node.Right) - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 获取平衡因子 */ - balanceFactor(node) { - // 空结点平衡因子为 0 - if (node === null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return this.height(node.left) - this.height(node.right); - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 获取平衡因子 */ - balanceFactor(node: TreeNode): number { - // 空结点平衡因子为 0 - if (node === null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return this.height(node.left) - this.height(node.right); - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 获取平衡因子 */ - public int balanceFactor(TreeNode? node) - { - // 空结点平衡因子为 0 - if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node.left) - height(node.right); - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 获取平衡因子 */ - func balanceFactor(node: TreeNode?) -> Int { - // 空结点平衡因子为 0 - guard let node = node else { return 0 } - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node: node.left) - height(node: node.right) - } - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -!!! note - - 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 - -## 7.4.2. AVL 树旋转 - -AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 - -我们将平衡因子的绝对值 $> 1$ 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。 - -### Case 1 - 右旋 - -如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3**。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子结点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 - -=== "Step 1" - ![right_rotate_step1](avl_tree.assets/right_rotate_step1.png) - -=== "Step 2" - ![right_rotate_step2](avl_tree.assets/right_rotate_step2.png) - -=== "Step 3" - ![right_rotate_step3](avl_tree.assets/right_rotate_step3.png) - -=== "Step 4" - ![right_rotate_step4](avl_tree.assets/right_rotate_step4.png) - -进而,如果结点 `child` 本身有右子结点(记为 `grandChild` ),则需要在「右旋」中添加一步:将 `grandChild` 作为 `node` 的左子结点。 - -![right_rotate_with_grandchild](avl_tree.assets/right_rotate_with_grandchild.png) - -“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。 - -=== "Java" - - ```java title="avl_tree.java" - /* 右旋操作 */ - TreeNode rightRotate(TreeNode node) { - TreeNode child = node.left; - TreeNode grandChild = child.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 右旋操作 */ - TreeNode* rightRotate(TreeNode* node) { - TreeNode* child = node->left; - TreeNode* grandChild = child->right; - // 以 child 为原点,将 node 向右旋转 - child->right = node; - node->left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 右旋操作 """ - def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: - child = node.left - grand_child = child.right - # 以 child 为原点,将 node 向右旋转 - child.right = node - node.left = grand_child - # 更新结点高度 - self.__update_height(node) - self.__update_height(child) - # 返回旋转后子树的根结点 - return child - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 右旋操作 */ - func rightRotate(node *TreeNode) *TreeNode { - child := node.Left - grandChild := child.Right - // 以 child 为原点,将 node 向右旋转 - child.Right = node - node.Left = grandChild - // 更新结点高度 - updateHeight(node) - updateHeight(child) - // 返回旋转后子树的根结点 - return child - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 右旋操作 */ - rightRotate(node) { - const child = node.left; - const grandChild = child.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - this.updateHeight(node); - this.updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 右旋操作 */ - rightRotate(node: TreeNode): TreeNode { - const child = node.left; - const grandChild = child.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - this.updateHeight(node); - this.updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 右旋操作 */ - TreeNode? rightRotate(TreeNode? node) - { - TreeNode? child = node.left; - TreeNode? grandChild = child?.right; - // 以 child 为原点,将 node 向右旋转 - child.right = node; - node.left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 右旋操作 */ - func rightRotate(node: TreeNode?) -> TreeNode? { - let child = node?.left - let grandChild = child?.right - // 以 child 为原点,将 node 向右旋转 - child?.right = node - node?.left = grandChild - // 更新结点高度 - updateHeight(node: node) - updateHeight(node: child) - // 返回旋转后子树的根结点 - return child - } - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -### Case 2 - 左旋 - -类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。 - -![left_rotate](avl_tree.assets/left_rotate.png) - -同理,若结点 `child` 本身有左子结点(记为 `grandChild` ),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。 - -![left_rotate_with_grandchild](avl_tree.assets/left_rotate_with_grandchild.png) - -观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` ,即可得到「左旋」代码。 - -=== "Java" - - ```java title="avl_tree.java" - /* 左旋操作 */ - TreeNode leftRotate(TreeNode node) { - TreeNode child = node.right; - TreeNode grandChild = child.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 左旋操作 */ - TreeNode* leftRotate(TreeNode* node) { - TreeNode* child = node->right; - TreeNode* grandChild = child->left; - // 以 child 为原点,将 node 向左旋转 - child->left = node; - node->right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 左旋操作 """ - def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: - child = node.right - grand_child = child.left - # 以 child 为原点,将 node 向左旋转 - child.left = node - node.right = grand_child - # 更新结点高度 - self.__update_height(node) - self.__update_height(child) - # 返回旋转后子树的根结点 - return child - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 左旋操作 */ - func leftRotate(node *TreeNode) *TreeNode { - child := node.Right - grandChild := child.Left - // 以 child 为原点,将 node 向左旋转 - child.Left = node - node.Right = grandChild - // 更新结点高度 - updateHeight(node) - updateHeight(child) - // 返回旋转后子树的根结点 - return child - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 左旋操作 */ - leftRotate(node) { - const child = node.right; - const grandChild = child.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - this.updateHeight(node); - this.updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 左旋操作 */ - leftRotate(node: TreeNode): TreeNode { - const child = node.right; - const grandChild = child.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - this.updateHeight(node); - this.updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 左旋操作 */ - TreeNode? leftRotate(TreeNode? node) - { - TreeNode? child = node.right; - TreeNode? grandChild = child?.left; - // 以 child 为原点,将 node 向左旋转 - child.left = node; - node.right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 左旋操作 */ - func leftRotate(node: TreeNode?) -> TreeNode? { - let child = node?.right - let grandChild = child?.left - // 以 child 为原点,将 node 向左旋转 - child?.left = node - node?.right = grandChild - // 更新结点高度 - updateHeight(node: node) - updateHeight(node: child) - // 返回旋转后子树的根结点 - return child - } - - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -### Case 3 - 先左后右 - -对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。 - -![left_right_rotate](avl_tree.assets/left_right_rotate.png) - -### Case 4 - 先右后左 - -同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 `child` 执行「右旋」,然后对 `node` 执行「左旋」。 - -![right_left_rotate](avl_tree.assets/right_left_rotate.png) - -### 旋转的选择 - -下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 **右旋、左旋、先右后左、先左后右** 的旋转操作。 - -![rotation_cases](avl_tree.assets/rotation_cases.png) - -具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。 - -
- -| 失衡结点的平衡因子 | 子结点的平衡因子 | 应采用的旋转方法 | -| ------------------ | ---------------- | ---------------- | -| $>0$ (即左偏树) | $\geq 0$ | 右旋 | -| $>0$ (即左偏树) | $<0$ | 先左旋后右旋 | -| $<0$ (即右偏树) | $\leq 0$ | 左旋 | -| $<0$ (即右偏树) | $>0$ | 先右旋后左旋 | - -
- -为方便使用,我们将旋转操作封装成一个函数。至此,**我们可以使用此函数来旋转各种失衡情况,使失衡结点重新恢复平衡**。 - -=== "Java" - - ```java title="avl_tree.java" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode rotate(TreeNode node) { - // 获取结点 node 的平衡因子 - int balanceFactor = balanceFactor(node); - // 左偏树 - if (balanceFactor > 1) { - if (balanceFactor(node.left) >= 0) { - // 右旋 - return rightRotate(node); - } else { - // 先左旋后右旋 - node.left = leftRotate(node.left); - return rightRotate(node); - } - } - // 右偏树 - if (balanceFactor < -1) { - if (balanceFactor(node.right) <= 0) { - // 左旋 - return leftRotate(node); - } else { - // 先右旋后左旋 - node.right = rightRotate(node.right); - return leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode* rotate(TreeNode* node) { - // 获取结点 node 的平衡因子 - int _balanceFactor = balanceFactor(node); - // 左偏树 - if (_balanceFactor > 1) { - if (balanceFactor(node->left) >= 0) { - // 右旋 - return rightRotate(node); - } else { - // 先左旋后右旋 - node->left = leftRotate(node->left); - return rightRotate(node); - } - } - // 右偏树 - if (_balanceFactor < -1) { - if (balanceFactor(node->right) <= 0) { - // 左旋 - return leftRotate(node); - } else { - // 先右旋后左旋 - node->right = rightRotate(node->right); - return leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: Optional[TreeNode]) -> TreeNode: - # 获取结点 node 的平衡因子 - balance_factor = self.balance_factor(node) - # 左偏树 - if balance_factor > 1: - if self.balance_factor(node.left) >= 0: - # 右旋 - return self.__right_rotate(node) - else: - # 先左旋后右旋 - node.left = self.__left_rotate(node.left) - return self.__right_rotate(node) - # 右偏树 - elif balance_factor < -1: - if self.balance_factor(node.right) <= 0: - # 左旋 - return self.__left_rotate(node) - else: - # 先右旋后左旋 - node.right = self.__right_rotate(node.right) - return self.__left_rotate(node) - # 平衡树,无需旋转,直接返回 - return node - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 执行旋转操作,使该子树重新恢复平衡 */ - func rotate(node *TreeNode) *TreeNode { - // 获取结点 node 的平衡因子 - // Go 推荐短变量,这里 bf 指代 balanceFactor - bf := balanceFactor(node) - // 左偏树 - if bf > 1 { - if balanceFactor(node.Left) >= 0 { - // 右旋 - return rightRotate(node) - } else { - // 先左旋后右旋 - node.Left = leftRotate(node.Left) - return rightRotate(node) - } - } - // 右偏树 - if bf < -1 { - if balanceFactor(node.Right) <= 0 { - // 左旋 - return leftRotate(node) - } else { - // 先右旋后左旋 - node.Right = rightRotate(node.Right) - return leftRotate(node) - } - } - // 平衡树,无需旋转,直接返回 - return node - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 执行旋转操作,使该子树重新恢复平衡 */ - rotate(node) { - // 获取结点 node 的平衡因子 - const balanceFactor = this.balanceFactor(node); - // 左偏树 - if (balanceFactor > 1) { - if (this.balanceFactor(node.left) >= 0) { - // 右旋 - return this.rightRotate(node); - } else { - // 先左旋后右旋 - node.left = this.leftRotate(node.left); - return this.rightRotate(node); - } - } - // 右偏树 - if (balanceFactor < -1) { - if (this.balanceFactor(node.right) <= 0) { - // 左旋 - return this.leftRotate(node); - } else { - // 先右旋后左旋 - node.right = this.rightRotate(node.right); - return this.leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 执行旋转操作,使该子树重新恢复平衡 */ - rotate(node: TreeNode): TreeNode { - // 获取结点 node 的平衡因子 - const balanceFactor = this.balanceFactor(node); - // 左偏树 - if (balanceFactor > 1) { - if (this.balanceFactor(node.left) >= 0) { - // 右旋 - return this.rightRotate(node); - } else { - // 先左旋后右旋 - node.left = this.leftRotate(node.left); - return this.rightRotate(node); - } - } - // 右偏树 - if (balanceFactor < -1) { - if (this.balanceFactor(node.right) <= 0) { - // 左旋 - return this.leftRotate(node); - } else { - // 先右旋后左旋 - node.right = this.rightRotate(node.right); - return this.leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode? rotate(TreeNode? node) - { - // 获取结点 node 的平衡因子 - int balanceFactorInt = balanceFactor(node); - // 左偏树 - if (balanceFactorInt > 1) - { - if (balanceFactor(node.left) >= 0) - { - // 右旋 - return rightRotate(node); - } - else - { - // 先左旋后右旋 - node.left = leftRotate(node?.left); - return rightRotate(node); - } - } - // 右偏树 - if (balanceFactorInt < -1) - { - if (balanceFactor(node.right) <= 0) - { - // 左旋 - return leftRotate(node); - } - else - { - // 先右旋后左旋 - node.right = rightRotate(node?.right); - return leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 执行旋转操作,使该子树重新恢复平衡 */ - func rotate(node: TreeNode?) -> TreeNode? { - // 获取结点 node 的平衡因子 - let balanceFactor = balanceFactor(node: node) - // 左偏树 - if balanceFactor > 1 { - if self.balanceFactor(node: node?.left) >= 0 { - // 右旋 - return rightRotate(node: node) - } else { - // 先左旋后右旋 - node?.left = leftRotate(node: node?.left) - return rightRotate(node: node) - } - } - // 右偏树 - if balanceFactor < -1 { - if self.balanceFactor(node: node?.right) <= 0 { - // 左旋 - return leftRotate(node: node) - } else { - // 先右旋后左旋 - node?.right = rightRotate(node: node?.right) - return leftRotate(node: node) - } - } - // 平衡树,无需旋转,直接返回 - return node - } - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -## 7.4.3. AVL 树常用操作 - -### 插入结点 - -「AVL 树」的结点插入操作与「二叉搜索树」主体类似。不同的是,在插入结点后,从该结点到根结点的路径上会出现一系列「失衡结点」。所以,**我们需要从该结点开始,从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 - -=== "Java" - - ```java title="avl_tree.java" - /* 插入结点 */ - TreeNode insert(int val) { - root = insertHelper(root, val); - return root; - } - - /* 递归插入结点(辅助函数) */ - TreeNode insertHelper(TreeNode node, int val) { - if (node == null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) - node.left = insertHelper(node.left, val); - else if (val > node.val) - node.right = insertHelper(node.right, val); - else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 插入结点 */ - TreeNode* insert(int val) { - root = insertHelper(root, val); - return root; - } - - /* 递归插入结点(辅助函数) */ - TreeNode* insertHelper(TreeNode* node, int val) { - if (node == nullptr) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node->val) - node->left = insertHelper(node->left, val); - else if (val > node->val) - node->right = insertHelper(node->right, val); - else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 插入结点 """ - def insert(self, val) -> TreeNode: - self.root = self.__insert_helper(self.root, val) - return self.root - - """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: - if node is None: - return TreeNode(val) - # 1. 查找插入位置,并插入结点 - if val < node.val: - node.left = self.__insert_helper(node.left, val) - elif val > node.val: - node.right = self.__insert_helper(node.right, val) - else: - # 重复结点不插入,直接返回 - return node - # 更新结点高度 - self.__update_height(node) - # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 插入结点 */ - func (t *avlTree) insert(val int) *TreeNode { - t.root = insertHelper(t.root, val) - return t.root - } - /* 递归插入结点(辅助函数) */ - func insertHelper(node *TreeNode, val int) *TreeNode { - if node == nil { - return NewTreeNode(val) - } - /* 1. 查找插入位置,并插入结点 */ - if val < node.Val { - node.Left = insertHelper(node.Left, val) - } else if val > node.Val { - node.Right = insertHelper(node.Right, val) - } else { - // 重复结点不插入,直接返回 - return node - } - // 更新结点高度 - updateHeight(node) - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node) - // 返回子树的根结点 - return node - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 插入结点 */ - insert(val) { - this.root = this.insertHelper(this.root, val); - return this.root; - } - - /* 递归插入结点(辅助函数) */ - insertHelper(node, val) { - if (node === null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) node.left = this.insertHelper(node.left, val); - else if (val > node.val) node.right = this.insertHelper(node.right, val); - else return node; // 重复结点不插入,直接返回 - this.updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = this.rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 插入结点 */ - insert(val: number): TreeNode { - this.root = this.insertHelper(this.root, val); - return this.root; - } - - /* 递归插入结点(辅助函数) */ - insertHelper(node: TreeNode, val: number): TreeNode { - if (node === null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) { - node.left = this.insertHelper(node.left, val); - } else if (val > node.val) { - node.right = this.insertHelper(node.right, val); - } else { - return node; // 重复结点不插入,直接返回 - } - this.updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = this.rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 插入结点 */ - public TreeNode? insert(int val) - { - root = insertHelper(root, val); - return root; - } - - /* 递归插入结点(辅助函数) */ - private TreeNode? insertHelper(TreeNode? node, int val) - { - if (node == null) return new TreeNode(val); - /* 1. 查找插入位置,并插入结点 */ - if (val < node.val) - node.left = insertHelper(node.left, val); - else if (val > node.val) - node.right = insertHelper(node.right, val); - else - return node; // 重复结点不插入,直接返回 - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 插入结点 */ - @discardableResult - func insert(val: Int) -> TreeNode? { - root = insertHelper(node: root, val: val) - return root - } - - /* 递归插入结点(辅助函数) */ - func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { - var node = node - if node == nil { - return TreeNode(x: val) - } - /* 1. 查找插入位置,并插入结点 */ - if val < node!.val { - node?.left = insertHelper(node: node?.left, val: val) - } else if val > node!.val { - node?.right = insertHelper(node: node?.right, val: val) - } else { - return node // 重复结点不插入,直接返回 - } - updateHeight(node: node) // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node: node) - // 返回子树的根结点 - return node - } - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -### 删除结点 - -「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 - -=== "Java" - - ```java title="avl_tree.java" - /* 删除结点 */ - TreeNode remove(int val) { - root = removeHelper(root, val); - return root; - } - - /* 递归删除结点(辅助函数) */ - TreeNode removeHelper(TreeNode node, int val) { - if (node == null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) - node.left = removeHelper(node.left, val); - else if (val > node.val) - node.right = removeHelper(node.right, val); - else { - if (node.left == null || node.right == null) { - TreeNode child = node.left != null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child == null) - return null; - // 子结点数量 = 1 ,直接删除 node - else - node = child; - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode temp = getInOrderNext(node.right); - node.right = removeHelper(node.right, temp.val); - node.val = temp.val; - } - } - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根结点 - return node; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - TreeNode getInOrderNext(TreeNode node) { - if (node == null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left != null) { - node = node.left; - } - return node; - } - ``` - -=== "C++" - - ```cpp title="avl_tree.cpp" - /* 删除结点 */ - TreeNode* remove(int val) { - root = removeHelper(root, val); - return root; - } - - /* 递归删除结点(辅助函数) */ - TreeNode* removeHelper(TreeNode* node, int val) { - if (node == nullptr) return nullptr; - /* 1. 查找结点,并删除之 */ - if (val < node->val) - node->left = removeHelper(node->left, val); - else if (val > node->val) - node->right = removeHelper(node->right, val); - else { - if (node->left == nullptr || node->right == nullptr) { - TreeNode* child = node->left != nullptr ? node->left : node->right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child == nullptr) { - delete node; - return nullptr; - } - // 子结点数量 = 1 ,直接删除 node - else { - delete node; - node = child; - } - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode* temp = getInOrderNext(node->right); - node->right = removeHelper(node->right, temp->val); - node->val = temp->val; - } - } - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "Python" - - ```python title="avl_tree.py" - """ 删除结点 """ - def remove(self, val: int): - root = self.__remove_helper(self.root, val) - return root - - """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: - if node is None: - return None - # 1. 查找结点,并删除之 - if val < node.val: - node.left = self.__remove_helper(node.left, val) - elif val > node.val: - node.right = self.__remove_helper(node.right, val) - else: - if node.left is None or node.right is None: - child = node.left or node.right - # 子结点数量 = 0 ,直接删除 node 并返回 - if child is None: - return None - # 子结点数量 = 1 ,直接删除 node - else: - node = child - else: # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp = self.__get_inorder_next(node.right) - node.right = self.__remove_helper(node.right, temp.val) - node.val = temp.val - # 更新结点高度 - self.__update_height(node) - # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) - ``` - -=== "Go" - - ```go title="avl_tree.go" - /* 删除结点 */ - func (t *avlTree) remove(val int) *TreeNode { - root := removeHelper(t.root, val) - return root - } - - /* 递归删除结点(辅助函数) */ - func removeHelper(node *TreeNode, val int) *TreeNode { - if node == nil { - return nil - } - /* 1. 查找结点,并删除之 */ - if val < node.Val { - node.Left = removeHelper(node.Left, val) - } else if val > node.Val { - node.Right = removeHelper(node.Right, val) - } else { - if node.Left == nil || node.Right == nil { - child := node.Left - if node.Right != nil { - child = node.Right - } - // 子结点数量 = 0 ,直接删除 node 并返回 - if child == nil { - return nil - } else { - // 子结点数量 = 1 ,直接删除 node - node = child - } - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp := getInOrderNext(node.Right) - node.Right = removeHelper(node.Right, temp.Val) - node.Val = temp.Val - } - } - // 更新结点高度 - updateHeight(node) - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node) - // 返回子树的根结点 - return node - } - ``` - -=== "JavaScript" - - ```js title="avl_tree.js" - /* 删除结点 */ - remove(val) { - this.root = this.removeHelper(this.root, val); - return this.root; - } - - /* 递归删除结点(辅助函数) */ - removeHelper(node, val) { - if (node === null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) node.left = this.removeHelper(node.left, val); - else if (val > node.val) node.right = this.removeHelper(node.right, val); - else { - if (node.left === null || node.right === null) { - const child = node.left !== null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child === null) return null; - // 子结点数量 = 1 ,直接删除 node - else node = child; - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - const temp = this.getInOrderNext(node.right); - node.right = this.removeHelper(node.right, temp.val); - node.val = temp.val; - } - } - this.updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = this.rotate(node); - // 返回子树的根结点 - return node; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - getInOrderNext(node) { - if (node === null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left !== null) { - node = node.left; - } - return node; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - getInOrderNext(node) { - if (node === null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left !== null) { - node = node.left; - } - return node; - } - ``` - -=== "TypeScript" - - ```typescript title="avl_tree.ts" - /* 删除结点 */ - remove(val: number): TreeNode { - this.root = this.removeHelper(this.root, val); - return this.root; - } - - /* 递归删除结点(辅助函数) */ - removeHelper(node: TreeNode, val: number): TreeNode { - if (node === null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) { - node.left = this.removeHelper(node.left, val); - } else if (val > node.val) { - node.right = this.removeHelper(node.right, val); - } else { - if (node.left === null || node.right === null) { - const child = node.left !== null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child === null) { - return null; - } else { - // 子结点数量 = 1 ,直接删除 node - node = child; - } - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - const temp = this.getInOrderNext(node.right); - node.right = this.removeHelper(node.right, temp.val); - node.val = temp.val; - } - } - this.updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = this.rotate(node); - // 返回子树的根结点 - return node; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - getInOrderNext(node: TreeNode): TreeNode { - if (node === null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left !== null) { - node = node.left; - } - return node; - } - ``` - -=== "C" - - ```c title="avl_tree.c" - - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 删除结点 */ - public TreeNode? remove(int val) - { - root = removeHelper(root, val); - return root; - } - - /* 递归删除结点(辅助函数) */ - private TreeNode? removeHelper(TreeNode? node, int val) - { - if (node == null) return null; - /* 1. 查找结点,并删除之 */ - if (val < node.val) - node.left = removeHelper(node.left, val); - else if (val > node.val) - node.right = removeHelper(node.right, val); - else - { - if (node.left == null || node.right == null) - { - TreeNode? child = node.left != null ? node.left : node.right; - // 子结点数量 = 0 ,直接删除 node 并返回 - if (child == null) - return null; - // 子结点数量 = 1 ,直接删除 node - else - node = child; - } - else - { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode? temp = getInOrderNext(node.right); - node.right = removeHelper(node.right, temp.val); - node.val = temp.val; - } - } - updateHeight(node); // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根结点 - return node; - } - ``` - -=== "Swift" - - ```swift title="avl_tree.swift" - /* 删除结点 */ - @discardableResult - func remove(val: Int) -> TreeNode? { - root = removeHelper(node: root, val: val) - return root - } - - /* 递归删除结点(辅助函数) */ - func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { - var node = node - if node == nil { - return nil - } - /* 1. 查找结点,并删除之 */ - if val < node!.val { - node?.left = removeHelper(node: node?.left, val: val) - } else if val > node!.val { - node?.right = removeHelper(node: node?.right, val: val) - } else { - if node?.left == nil || node?.right == nil { - let child = node?.left != nil ? node?.left : node?.right - // 子结点数量 = 0 ,直接删除 node 并返回 - if child == nil { - return nil - } - // 子结点数量 = 1 ,直接删除 node - else { - node = child - } - } else { - // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - let temp = getInOrderNext(node: node?.right) - node?.right = removeHelper(node: node?.right, val: temp!.val) - node?.val = temp!.val - } - } - updateHeight(node: node) // 更新结点高度 - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node: node) - // 返回子树的根结点 - return node - } - ``` - -=== "Zig" - - ```zig title="avl_tree.zig" - - ``` - -### 查找结点 - -「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。 - -## 7.4.4. AVL 树典型应用 - -- 组织存储大型数据,适用于高频查找、低频增删场景; -- 用于建立数据库中的索引系统; - -!!! question "为什么红黑树比 AVL 树更受欢迎?" - - 红黑树的平衡条件相对宽松,因此在红黑树中插入与删除结点所需的旋转操作相对更少,结点增删操作相比 AVL 树的效率更高。 diff --git a/build/chapter_tree/binary_search_tree.md b/build/chapter_tree/binary_search_tree.md deleted file mode 100755 index 11d70a0d2..000000000 --- a/build/chapter_tree/binary_search_tree.md +++ /dev/null @@ -1,1081 +0,0 @@ ---- -comments: true ---- - -# 7.3. 二叉搜索树 - -「二叉搜索树 Binary Search Tree」满足以下条件: - -1. 对于根结点,左子树中所有结点的值 $<$ 根结点的值 $<$ 右子树中所有结点的值; -2. 任意结点的左子树和右子树也是二叉搜索树,即也满足条件 `1.` ; - -![binary_search_tree](binary_search_tree.assets/binary_search_tree.png) - -## 7.3.1. 二叉搜索树的操作 - -### 查找结点 - -给定目标结点值 `num` ,可以根据二叉搜索树的性质来查找。我们声明一个结点 `cur` ,从二叉树的根结点 `root` 出发,循环比较结点值 `cur.val` 和 `num` 之间的大小关系 - -- 若 `cur.val < num` ,说明目标结点在 `cur` 的右子树中,因此执行 `cur = cur.right` ; -- 若 `cur.val > num` ,说明目标结点在 `cur` 的左子树中,因此执行 `cur = cur.left` ; -- 若 `cur.val = num` ,说明找到目标结点,跳出循环并返回该结点即可; - -=== "Step 1" - ![bst_search_1](binary_search_tree.assets/bst_search_1.png) - -=== "Step 2" - ![bst_search_2](binary_search_tree.assets/bst_search_2.png) - -=== "Step 3" - ![bst_search_3](binary_search_tree.assets/bst_search_3.png) - -=== "Step 4" - ![bst_search_4](binary_search_tree.assets/bst_search_4.png) - -二叉搜索树的查找操作和二分查找算法如出一辙,也是在每轮排除一半情况。循环次数最多为二叉树的高度,当二叉树平衡时,使用 $O(\log n)$ 时间。 - -=== "Java" - - ```java title="binary_search_tree.java" - /* 查找结点 */ - TreeNode search(int num) { - TreeNode cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != null) { - // 目标结点在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 cur 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "C++" - - ```cpp title="binary_search_tree.cpp" - /* 查找结点 */ - TreeNode* search(int num) { - TreeNode* cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 目标结点在 cur 的右子树中 - if (cur->val < num) cur = cur->right; - // 目标结点在 cur 的左子树中 - else if (cur->val > num) cur = cur->left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "Python" - - ```python title="binary_search_tree.py" - """ 查找结点 """ - def search(self, num: int) -> Optional[TreeNode]: - cur = self.root - # 循环查找,越过叶结点后跳出 - while cur is not None: - # 目标结点在 cur 的右子树中 - if cur.val < num: - cur = cur.right - # 目标结点在 cur 的左子树中 - elif cur.val > num: - cur = cur.left - # 找到目标结点,跳出循环 - else: - break - return cur - ``` - -=== "Go" - - ```go title="binary_search_tree.go" - /* 查找结点 */ - func (bst *binarySearchTree) search(num int) *TreeNode { - node := bst.root - // 循环查找,越过叶结点后跳出 - for node != nil { - if node.Val < num { - // 目标结点在 cur 的右子树中 - node = node.Right - } else if node.Val > num { - // 目标结点在 cur 的左子树中 - node = node.Left - } else { - // 找到目标结点,跳出循环 - break - } - } - // 返回目标结点 - return node - } - ``` - -=== "JavaScript" - - ```js title="binary_search_tree.js" - /* 查找结点 */ - function search(num) { - let cur = root; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 目标结点在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 cur 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search_tree.ts" - /* 查找结点 */ - function search(num: number): TreeNode | null { - let cur = root; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - if (cur.val < num) { - cur = cur.right; // 目标结点在 cur 的右子树中 - } else if (cur.val > num) { - cur = cur.left; // 目标结点在 cur 的左子树中 - } else { - break; // 找到目标结点,跳出循环 - } - } - // 返回目标结点 - return cur; - } - ``` - -=== "C" - - ```c title="binary_search_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 查找结点 */ - TreeNode? search(int num) - { - TreeNode? cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 目标结点在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 目标结点在 cur 的左子树中 - else if (cur.val > num) cur = cur.left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - /* 查找结点 */ - func search(num: Int) -> TreeNode? { - var cur = root - // 循环查找,越过叶结点后跳出 - while cur != nil { - // 目标结点在 cur 的右子树中 - if cur!.val < num { - cur = cur?.right - } - // 目标结点在 cur 的左子树中 - else if cur!.val > num { - cur = cur?.left - } - // 找到目标结点,跳出循环 - else { - break - } - } - // 返回目标结点 - return cur - } - ``` - -=== "Zig" - - ```zig title="binary_search_tree.zig" - - ``` - -### 插入结点 - -给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步: - -1. **查找插入位置**:与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环; -2. **在该位置插入结点**:初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置 ; - -二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。 - -![bst_insert](binary_search_tree.assets/bst_insert.png) - -=== "Java" - - ```java title="binary_search_tree.java" - /* 插入结点 */ - TreeNode insert(int num) { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) { - // 找到重复结点,直接返回 - if (cur.val == num) return null; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 cur 的左子树中 - else cur = cur.left; - } - // 插入结点 val - TreeNode node = new TreeNode(num); - if (pre.val < num) pre.right = node; - else pre.left = node; - return node; - } - ``` - -=== "C++" - - ```cpp title="binary_search_tree.cpp" - /* 插入结点 */ - TreeNode* insert(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; - TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 找到重复结点,直接返回 - if (cur->val == num) return nullptr; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur->val < num) cur = cur->right; - // 插入位置在 cur 的左子树中 - else cur = cur->left; - } - // 插入结点 val - TreeNode* node = new TreeNode(num); - if (pre->val < num) pre->right = node; - else pre->left = node; - return node; - } - ``` - -=== "Python" - - ```python title="binary_search_tree.py" - """ 插入结点 """ - def insert(self, num: int) -> Optional[TreeNode]: - root = self.root - # 若树为空,直接提前返回 - if root is None: - return None - - # 循环查找,越过叶结点后跳出 - cur, pre = root, None - while cur is not None: - # 找到重复结点,直接返回 - if cur.val == num: - return None - pre = cur - # 插入位置在 cur 的右子树中 - if cur.val < num: - cur = cur.right - # 插入位置在 cur 的左子树中 - else: - cur = cur.left - - # 插入结点 val - node = TreeNode(num) - if pre.val < num: - pre.right = node - else: - pre.left = node - return node - ``` - -=== "Go" - - ```go title="binary_search_tree.go" - /* 插入结点 */ - func (bst *binarySearchTree) insert(num int) *TreeNode { - cur := bst.root - // 若树为空,直接提前返回 - if cur == nil { - return nil - } - // 待插入结点之前的结点位置 - var pre *TreeNode = nil - // 循环查找,越过叶结点后跳出 - for cur != nil { - if cur.Val == num { - return nil - } - pre = cur - if cur.Val < num { - cur = cur.Right - } else { - cur = cur.Left - } - } - // 插入结点 - node := NewTreeNode(num) - if pre.Val < num { - pre.Right = node - } else { - pre.Left = node - } - return cur - } - ``` - -=== "JavaScript" - - ```js title="binary_search_tree.js" - /* 插入结点 */ - function insert(num) { - // 若树为空,直接提前返回 - if (root === null) return null; - let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到重复结点,直接返回 - if (cur.val === num) return null; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 cur 的左子树中 - else cur = cur.left; - } - // 插入结点 val - let node = new Tree.TreeNode(num); - if (pre.val < num) pre.right = node; - else pre.left = node; - return node; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search_tree.ts" - /* 插入结点 */ - function insert(num: number): TreeNode | null { - // 若树为空,直接提前返回 - if (root === null) { - return null; - } - let cur = root, - pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - if (cur.val === num) { - return null; // 找到重复结点,直接返回 - } - pre = cur; - if (cur.val < num) { - cur = cur.right as TreeNode; // 插入位置在 cur 的右子树中 - } else { - cur = cur.left as TreeNode; // 插入位置在 cur 的左子树中 - } - } - // 插入结点 val - let node = new TreeNode(num); - if (pre!.val < num) { - pre!.right = node; - } else { - pre!.left = node; - } - return node; - } - ``` - -=== "C" - - ```c title="binary_search_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 插入结点 */ - TreeNode? insert(int num) - { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 找到重复结点,直接返回 - if (cur.val == num) return null; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 插入位置在 cur 的左子树中 - else cur = cur.left; - } - - // 插入结点 val - TreeNode node = new TreeNode(num); - if (pre != null) - { - if (pre.val < num) pre.right = node; - else pre.left = node; - } - return node; - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - /* 插入结点 */ - func insert(num: Int) -> TreeNode? { - // 若树为空,直接提前返回 - if root == nil { - return nil - } - var cur = root - var pre: TreeNode? - // 循环查找,越过叶结点后跳出 - while cur != nil { - // 找到重复结点,直接返回 - if cur!.val == num { - return nil - } - pre = cur - // 插入位置在 cur 的右子树中 - if cur!.val < num { - cur = cur?.right - } - // 插入位置在 cur 的左子树中 - else { - cur = cur?.left - } - } - // 插入结点 val - let node = TreeNode(x: num) - if pre!.val < num { - pre?.right = node - } else { - pre?.left = node - } - return node - } - ``` - -=== "Zig" - - ```zig title="binary_search_tree.zig" - - ``` - -为了插入结点,需要借助 **辅助结点 `pre`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 - -与查找结点相同,插入结点使用 $O(\log n)$ 时间。 - -### 删除结点 - -与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况: - -**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。 - -![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png) - -**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。 - -![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png) - -**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步: - -1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ; -2. 在树中递归删除结点 `nex` ; -3. 使用 `nex` 替换待删除结点; - -=== "Step 1" - ![bst_remove_case3_1](binary_search_tree.assets/bst_remove_case3_1.png) - -=== "Step 2" - ![bst_remove_case3_2](binary_search_tree.assets/bst_remove_case3_2.png) - -=== "Step 3" - ![bst_remove_case3_3](binary_search_tree.assets/bst_remove_case3_3.png) - -=== "Step 4" - ![bst_remove_case3_4](binary_search_tree.assets/bst_remove_case3_4.png) - -删除结点操作也使用 $O(\log n)$ 时间,其中查找待删除结点 $O(\log n)$ ,获取中序遍历后继结点 $O(\log n)$ 。 - -=== "Java" - - ```java title="binary_search_tree.java" - /* 删除结点 */ - TreeNode remove(int num) { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) { - // 找到待删除结点,跳出循环 - if (cur.val == num) break; - pre = cur; - // 待删除结点在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 cur 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur == null) return null; - // 子结点数量 = 0 or 1 - if (cur.left == null || cur.right == null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - TreeNode child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left == cur) pre.left = child; - else pre.right = child; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode nex = getInOrderNext(cur.right); - int tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - TreeNode getInOrderNext(TreeNode root) { - if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left != null) { - root = root.left; - } - return root; - } - ``` - -=== "C++" - - ```cpp title="binary_search_tree.cpp" - /* 删除结点 */ - TreeNode* remove(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; - TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 找到待删除结点,跳出循环 - if (cur->val == num) break; - pre = cur; - // 待删除结点在 cur 的右子树中 - if (cur->val < num) cur = cur->right; - // 待删除结点在 cur 的左子树中 - else cur = cur->left; - } - // 若无待删除结点,则直接返回 - if (cur == nullptr) return nullptr; - // 子结点数量 = 0 or 1 - if (cur->left == nullptr || cur->right == nullptr) { - // 当子结点数量 = 0 / 1 时, child = nullptr / 该子结点 - TreeNode* child = cur->left != nullptr ? cur->left : cur->right; - // 删除结点 cur - if (pre->left == cur) pre->left = child; - else pre->right = child; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode* nex = getInOrderNext(cur->right); - int tmp = nex->val; - // 递归删除结点 nex - remove(nex->val); - // 将 nex 的值复制给 cur - cur->val = tmp; - } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - TreeNode* getInOrderNext(TreeNode* root) { - if (root == nullptr) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root->left != nullptr) { - root = root->left; - } - return root; - } - ``` - -=== "Python" - - ```python title="binary_search_tree.py" - """ 删除结点 """ - def remove(self, num: int) -> Optional[TreeNode]: - root = self.root - # 若树为空,直接提前返回 - if root is None: - return None - - # 循环查找,越过叶结点后跳出 - cur, pre = root, None - while cur is not None: - # 找到待删除结点,跳出循环 - if cur.val == num: - break - pre = cur - if cur.val < num: # 待删除结点在 cur 的右子树中 - cur = cur.right - else: # 待删除结点在 cur 的左子树中 - cur = cur.left - # 若无待删除结点,则直接返回 - if cur is None: - return None - - # 子结点数量 = 0 or 1 - if cur.left is None or cur.right is None: - # 当子结点数量 = 0 / 1 时, child = null / 该子结点 - child = cur.left or cur.right - # 删除结点 cur - if pre.left == cur: - pre.left = child - else: - pre.right = child - # 子结点数量 = 2 - else: - # 获取中序遍历中 cur 的下一个结点 - nex = self.get_inorder_next(cur.right) - tmp = nex.val - # 递归删除结点 nex - self.remove(nex.val) - # 将 nex 的值复制给 cur - cur.val = tmp - return cur - - """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ - def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if root is None: - return root - # 循环访问左子结点,直到叶结点时为最小结点,跳出 - while root.left is not None: - root = root.left - return root - ``` - -=== "Go" - - ```go title="binary_search_tree.go" - /* 删除结点 */ - func (bst *binarySearchTree) remove(num int) *TreeNode { - cur := bst.root - // 若树为空,直接提前返回 - if cur == nil { - return nil - } - // 待删除结点之前的结点位置 - var pre *TreeNode = nil - // 循环查找,越过叶结点后跳出 - for cur != nil { - if cur.Val == num { - break - } - pre = cur - if cur.Val < num { - // 待删除结点在右子树中 - cur = cur.Right - } else { - // 待删除结点在左子树中 - cur = cur.Left - } - } - // 若无待删除结点,则直接返回 - if cur == nil { - return nil - } - // 子结点数为 0 或 1 - if cur.Left == nil || cur.Right == nil { - var child *TreeNode = nil - // 取出待删除结点的子结点 - if cur.Left != nil { - child = cur.Left - } else { - child = cur.Right - } - // 将子结点替换为待删除结点 - if pre.Left == cur { - pre.Left = child - } else { - pre.Right = child - } - // 子结点数为 2 - } else { - // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.getInOrderNext(cur) - temp := next.Val - // 递归删除结点 next - bst.remove(next.Val) - // 将 next 的值复制给 cur - cur.Val = temp - } - return cur - } - - /* 获取中序遍历的下一个结点(仅适用于 root 有左子结点的情况) */ - func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode { - if node == nil { - return node - } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - for node.Left != nil { - node = node.Left - } - return node - } - ``` - -=== "JavaScript" - - ```js title="binary_search_tree.js" - /* 删除结点 */ - function remove(num) { - // 若树为空,直接提前返回 - if (root === null) return null; - let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到待删除结点,跳出循环 - if (cur.val === num) break; - pre = cur; - // 待删除结点在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 cur 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur === null) return null; - // 子结点数量 = 0 or 1 - if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left === cur) pre.left = child; - else pre.right = child; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let nex = getInOrderNext(cur.right); - let tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - function getInOrderNext(root) { - if (root === null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left !== null) { - root = root.left; - } - return root; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_search_tree.ts" - /* 删除结点 */ - function remove(num: number): TreeNode | null { - // 若树为空,直接提前返回 - if (root === null) { - return null; - } - let cur = root, - pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 - while (cur !== null) { - // 找到待删除结点,跳出循环 - if (cur.val === num) { - break; - } - pre = cur; - if (cur.val < num) { - cur = cur.right as TreeNode; // 待删除结点在 cur 的右子树中 - } else { - cur = cur.left as TreeNode; // 待删除结点在 cur 的左子树中 - } - } - // 若无待删除结点,则直接返回 - if (cur === null) { - return null; - } - // 子结点数量 = 0 or 1 - if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur - if (pre!.left === cur) { - pre!.left = child; - } else { - pre!.right = child; - } - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let next = getInOrderNext(cur.right); - let tmp = next!.val; - // 递归删除结点 nex - remove(next!.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - function getInOrderNext(root: TreeNode | null): TreeNode | null { - if (root === null) { - return null; - } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left !== null) { - root = root.left; - } - return root; - } - ``` - -=== "C" - - ```c title="binary_search_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 删除结点 */ - TreeNode? remove(int num) - { - // 若树为空,直接提前返回 - if (root == null) return null; - TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 - while (cur != null) - { - // 找到待删除结点,跳出循环 - if (cur.val == num) break; - pre = cur; - // 待删除结点在 cur 的右子树中 - if (cur.val < num) cur = cur.right; - // 待删除结点在 cur 的左子树中 - else cur = cur.left; - } - // 若无待删除结点,则直接返回 - if (cur == null || pre == null) return null; - // 子结点数量 = 0 or 1 - if (cur.left == null || cur.right == null) - { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - TreeNode? child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur - if (pre.left == cur) - { - pre.left = child; - } - else - { - pre.right = child; - } - } - // 子结点数量 = 2 - else - { - // 获取中序遍历中 cur 的下一个结点 - TreeNode? nex = getInOrderNext(cur.right); - if (nex != null) - { - int tmp = nex.val; - // 递归删除结点 nex - remove(nex.val); - // 将 nex 的值复制给 cur - cur.val = tmp; - } - } - return cur; - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - private TreeNode? getInOrderNext(TreeNode? root) - { - if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left != null) - { - root = root.left; - } - return root; - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - /* 删除结点 */ - @discardableResult - func remove(num: Int) -> TreeNode? { - // 若树为空,直接提前返回 - if root == nil { - return nil - } - var cur = root - var pre: TreeNode? - // 循环查找,越过叶结点后跳出 - while cur != nil { - // 找到待删除结点,跳出循环 - if cur!.val == num { - break - } - pre = cur - // 待删除结点在 cur 的右子树中 - if cur!.val < num { - cur = cur?.right - } - // 待删除结点在 cur 的左子树中 - else { - cur = cur?.left - } - } - // 若无待删除结点,则直接返回 - if cur == nil { - return nil - } - // 子结点数量 = 0 or 1 - if cur?.left == nil || cur?.right == nil { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 - let child = cur?.left != nil ? cur?.left : cur?.right - // 删除结点 cur - if pre?.left === cur { - pre?.left = child - } else { - pre?.right = child - } - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - let nex = getInOrderNext(root: cur?.right) - let tmp = nex!.val - // 递归删除结点 nex - remove(num: nex!.val) - // 将 nex 的值复制给 cur - cur?.val = tmp - } - return cur - } - - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - func getInOrderNext(root: TreeNode?) -> TreeNode? { - var root = root - if root == nil { - return root - } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while root?.left != nil { - root = root?.left - } - return root - } - ``` - -=== "Zig" - - ```zig title="binary_search_tree.zig" - - ``` - -### 排序 - -我们知道,「中序遍历」遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历优先级,而二叉搜索树遵循“左子结点 $<$ 根结点 $<$ 右子结点”的大小关系。因此,在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小结点,从而得出一条重要性质:**二叉搜索树的中序遍历序列是升序的**。 - -借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,而无需额外排序,非常高效。 - -![bst_inorder_traversal](binary_search_tree.assets/bst_inorder_traversal.png) - -## 7.3.2. 二叉搜索树的效率 - -假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: - -- **查找元素**:由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间; -- **插入元素**:只需将元素添加至数组尾部即可,使用 $O(1)$ 时间; -- **删除元素**:先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素**:需要遍历数组来确定,使用 $O(n)$ 时间; - -为了得到先验信息,我们也可以预先将数组元素进行排序,得到一个「排序数组」,此时操作效率为: - -- **查找元素**:由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间; -- **插入元素**:先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间; -- **删除元素**:先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素**:数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间; - -观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。 - -
- -| | 无序数组 | 有序数组 | 二叉搜索树 | -| ------------------- | -------- | ----------- | ----------- | -| 查找指定元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | -| 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | -| 删除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | -| 获取最小 / 最大元素 | $O(n)$ | $O(1)$ | $O(\log n)$ | - -
- -## 7.3.3. 二叉搜索树的退化 - -理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 $\log n$ 轮循环内查找任意结点。 - -如果我们动态地在二叉搜索树中插入与删除结点,**则可能导致二叉树退化为链表**,此时各种操作的时间复杂度也退化之 $O(n)$ 。 - -!!! note - - 在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。 - -![bst_degradation](binary_search_tree.assets/bst_degradation.png) - -## 7.3.4. 二叉搜索树常见应用 - -- 系统中的多级索引,高效查找、插入、删除操作。 -- 各种搜索算法的底层数据结构。 -- 存储数据流,保持其已排序。 diff --git a/build/chapter_tree/binary_tree.md b/build/chapter_tree/binary_tree.md deleted file mode 100644 index c82750b45..000000000 --- a/build/chapter_tree/binary_tree.md +++ /dev/null @@ -1,578 +0,0 @@ ---- -comments: true ---- - -# 7.1. 二叉树 - -「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。 - -=== "Java" - - ```java title="" - /* 链表结点类 */ - class TreeNode { - int val; // 结点值 - TreeNode left; // 左子结点指针 - TreeNode right; // 右子结点指针 - TreeNode(int x) { val = x; } - } - ``` - -=== "C++" - - ```cpp title="" - /* 链表结点结构体 */ - struct TreeNode { - int val; // 结点值 - TreeNode *left; // 左子结点指针 - TreeNode *right; // 右子结点指针 - TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} - }; - ``` - -=== "Python" - - ```python title="" - """ 链表结点类 """ - class TreeNode: - def __init__(self, val=None, left=None, right=None): - self.val = val # 结点值 - self.left = left # 左子结点指针 - self.right = right # 右子结点指针 - ``` - -=== "Go" - - ```go title="" - /* 链表结点类 */ - type TreeNode struct { - Val int - Left *TreeNode - Right *TreeNode - } - /* 结点初始化方法 */ - func NewTreeNode(v int) *TreeNode { - return &TreeNode{ - Left: nil, - Right: nil, - Val: v, - } - } - ``` - -=== "JavaScript" - - ```js title="" - /* 链表结点类 */ - function TreeNode(val, left, right) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.left = (left === undefined ? null : left); // 左子结点指针 - this.right = (right === undefined ? null : right); // 右子结点指针 - } - ``` - -=== "TypeScript" - - ```typescript title="" - /* 链表结点类 */ - class TreeNode { - val: number; - left: TreeNode | null; - right: TreeNode | null; - - constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.left = left === undefined ? null : left; // 左子结点指针 - this.right = right === undefined ? null : right; // 右子结点指针 - } - } - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - /* 链表结点类 */ - class TreeNode { - int val; // 结点值 - TreeNode? left; // 左子结点指针 - TreeNode? right; // 右子结点指针 - TreeNode(int x) { val = x; } - } - ``` - -=== "Swift" - - ```swift title="" - /* 链表结点类 */ - class TreeNode { - var val: Int // 结点值 - var left: TreeNode? // 左子结点指针 - var right: TreeNode? // 右子结点指针 - - init(x: Int) { - val = x - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - -结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。 - -除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。 - -![binary_tree_definition](binary_tree.assets/binary_tree_definition.png) - -

Fig. 子结点与子树

- -## 7.1.1. 二叉树常见术语 - -二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。 - -- 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点; -- 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 $\text{null}$ ; -- 结点所处「层 Level」:从顶至底依次增加,根结点所处层为 1 ; -- 结点「度 Degree」:结点的子结点数量。二叉树中,度的范围是 0, 1, 2 ; -- 「边 Edge」:连接两个结点的边,即结点指针; -- 二叉树「高度」:二叉树中根结点到最远叶结点走过边的数量; -- 结点「深度 Depth」 :根结点到该结点走过边的数量; -- 结点「高度 Height」:最远叶结点到该结点走过边的数量; - -![binary_tree_terminology](binary_tree.assets/binary_tree_terminology.png) - -

Fig. 二叉树的常见术语

- -!!! tip "高度与深度的定义" - - 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。 - -## 7.1.2. 二叉树基本操作 - -**初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 - -=== "Java" - - ```java title="binary_tree.java" - // 初始化结点 - TreeNode n1 = new TreeNode(1); - TreeNode n2 = new TreeNode(2); - TreeNode n3 = new TreeNode(3); - TreeNode n4 = new TreeNode(4); - TreeNode n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1.left = n2; - n1.right = n3; - n2.left = n4; - n2.right = n5; - ``` - -=== "C++" - - ```cpp title="binary_tree.cpp" - /* 初始化二叉树 */ - // 初始化结点 - TreeNode* n1 = new TreeNode(1); - TreeNode* n2 = new TreeNode(2); - TreeNode* n3 = new TreeNode(3); - TreeNode* n4 = new TreeNode(4); - TreeNode* n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1->left = n2; - n1->right = n3; - n2->left = n4; - n2->right = n5; - ``` - -=== "Python" - - ```python title="binary_tree.py" - """ 初始化二叉树 """ - # 初始化结点 - n1 = TreeNode(val=1) - n2 = TreeNode(val=2) - n3 = TreeNode(val=3) - n4 = TreeNode(val=4) - n5 = TreeNode(val=5) - # 构建引用指向(即指针) - n1.left = n2 - n1.right = n3 - n2.left = n4 - n2.right = n5 - ``` - -=== "Go" - - ```go title="binary_tree.go" - /* 初始化二叉树 */ - // 初始化结点 - n1 := NewTreeNode(1) - n2 := NewTreeNode(2) - n3 := NewTreeNode(3) - n4 := NewTreeNode(4) - n5 := NewTreeNode(5) - // 构建引用指向(即指针) - n1.Left = n2 - n1.Right = n3 - n2.Left = n4 - n2.Right = n5 - ``` - -=== "JavaScript" - - ```js title="binary_tree.js" - /* 初始化二叉树 */ - // 初始化结点 - let n1 = new TreeNode(1), - n2 = new TreeNode(2), - n3 = new TreeNode(3), - n4 = new TreeNode(4), - n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1.left = n2; - n1.right = n3; - n2.left = n4; - n2.right = n5; - ``` - -=== "TypeScript" - - ```typescript title="binary_tree.ts" - /* 初始化二叉树 */ - // 初始化结点 - let n1 = new TreeNode(1), - n2 = new TreeNode(2), - n3 = new TreeNode(3), - n4 = new TreeNode(4), - n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1.left = n2; - n1.right = n3; - n2.left = n4; - n2.right = n5; - ``` - -=== "C" - - ```c title="binary_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree.cs" - /* 初始化二叉树 */ - // 初始化结点 - TreeNode n1 = new TreeNode(1); - TreeNode n2 = new TreeNode(2); - TreeNode n3 = new TreeNode(3); - TreeNode n4 = new TreeNode(4); - TreeNode n5 = new TreeNode(5); - // 构建引用指向(即指针) - n1.left = n2; - n1.right = n3; - n2.left = n4; - n2.right = n5; - ``` - -=== "Swift" - - ```swift title="binary_tree.swift" - // 初始化结点 - let n1 = TreeNode(x: 1) - let n2 = TreeNode(x: 2) - let n3 = TreeNode(x: 3) - let n4 = TreeNode(x: 4) - let n5 = TreeNode(x: 5) - // 构建引用指向(即指针) - n1.left = n2 - n1.right = n3 - n2.left = n4 - n2.right = n5 - ``` - -=== "Zig" - - ```zig title="binary_tree.zig" - - ``` - -**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 - -![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png) - -

Fig. 在二叉树中插入与删除结点

- -=== "Java" - - ```java title="binary_tree.java" - TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1.left = P; - P.left = n2; - // 删除结点 P - n1.left = n2; - ``` - -=== "C++" - - ```cpp title="binary_tree.cpp" - /* 插入与删除结点 */ - TreeNode* P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1->left = P; - P->left = n2; - // 删除结点 P - n1->left = n2; - ``` - -=== "Python" - - ```python title="binary_tree.py" - """ 插入与删除结点 """ - p = TreeNode(0) - # 在 n1 -> n2 中间插入结点 P - n1.left = p - p.left = n2 - # 删除结点 P - n1.left = n2 - ``` - -=== "Go" - - ```go title="binary_tree.go" - /* 插入与删除结点 */ - // 在 n1 -> n2 中间插入结点 P - p := NewTreeNode(0) - n1.Left = p - p.Left = n2 - // 删除结点 P - n1.Left = n2 - ``` - -=== "JavaScript" - - ```js title="binary_tree.js" - /* 插入与删除结点 */ - let P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1.left = P; - P.left = n2; - // 删除结点 P - n1.left = n2; - ``` - -=== "TypeScript" - - ```typescript title="binary_tree.ts" - /* 插入与删除结点 */ - const P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1.left = P; - P.left = n2; - // 删除结点 P - n1.left = n2; - ``` - -=== "C" - - ```c title="binary_tree.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree.cs" - /* 插入与删除结点 */ - TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P - n1.left = P; - P.left = n2; - // 删除结点 P - n1.left = n2; - ``` - -=== "Swift" - - ```swift title="binary_tree.swift" - let P = TreeNode(x: 0) - // 在 n1 -> n2 中间插入结点 P - n1.left = P - P.left = n2 - // 删除结点 P - n1.left = n2 - ``` - -=== "Zig" - - ```zig title="binary_tree.zig" - - ``` - -!!! note - - 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 - -## 7.1.3. 常见二叉树类型 - -### 完美二叉树 - -「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,所有结点的度 = 2 ;若树高度 $= h$ ,则结点总数 $= 2^{h+1} - 1$ ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。 - -!!! tip - - 在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。 - -![perfect_binary_tree](binary_tree.assets/perfect_binary_tree.png) - -### 完全二叉树 - -「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。 - -**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。 - -![complete_binary_tree](binary_tree.assets/complete_binary_tree.png) - -### 完满二叉树 - -「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。 - -![full_binary_tree](binary_tree.assets/full_binary_tree.png) - -### 平衡二叉树 - -「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。 - -![balanced_binary_tree](binary_tree.assets/balanced_binary_tree.png) - -## 7.1.4. 二叉树的退化 - -当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 - -- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势; -- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ; - -![binary_tree_corner_cases](binary_tree.assets/binary_tree_corner_cases.png) - -

Fig. 二叉树的最佳和最差结构

- -如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。 - -
- -| | 完美二叉树 | 链表 | -| ----------------------------- | ---------- | ---------- | -| 第 $i$ 层的结点数量 | $2^{i-1}$ | $1$ | -| 树的高度为 $h$ 时的叶结点数量 | $2^h$ | $1$ | -| 树的高度为 $h$ 时的结点总数 | $2^{h+1} - 1$ | $h + 1$ | -| 树的结点总数为 $n$ 时的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | - -
- -## 7.1.5. 二叉树表示方式 * - -我们一般使用二叉树的「链表表示」,即存储单位为结点 `TreeNode` ,结点之间通过指针(引用)相连接。本文前述示例代码展示了二叉树在链表表示下的各项基本操作。 - -那能否可以用「数组表示」二叉树呢?答案是肯定的。先来分析一个简单案例,给定一个「完美二叉树」,将结点按照层序遍历的顺序编号(从 0 开始),那么可以推导得出父结点索引与子结点索引之间的「映射公式」:**设结点的索引为 $i$ ,则该结点的左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$** 。 - -**本质上,映射公式的作用就是链表中的指针**。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。 - -![array_representation_mapping](binary_tree.assets/array_representation_mapping.png) - -然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 `null` ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。 - -![array_representation_without_empty](binary_tree.assets/array_representation_without_empty.png) - -为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,**即在序列中使用特殊符号来显式地表示“空位”**。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。 - -=== "Java" - - ```java title="" - /* 二叉树的数组表示 */ - // 使用 int 的包装类 Integer ,就可以使用 null 来标记空位 - Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; - ``` - -=== "C++" - - ```cpp title="" - /* 二叉树的数组表示 */ - // 为了符合数据类型为 int ,使用 int 最大值标记空位 - // 该方法的使用前提是没有结点的值 = INT_MAX - vector tree = { 1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15 }; - ``` - -=== "Python" - - ```python title="" - """ 二叉树的数组表示 """ - # 直接使用 None 来表示空位 - tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] - ``` - -=== "Go" - - ```go title="" - - ``` - -=== "JavaScript" - - ```js title="" - /* 二叉树的数组表示 */ - // 直接使用 null 来表示空位 - let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; - ``` - -=== "TypeScript" - - ```typescript title="" - /* 二叉树的数组表示 */ - // 直接使用 null 来表示空位 - let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; - ``` - -=== "C" - - ```c title="" - - ``` - -=== "C#" - - ```csharp title="" - /* 二叉树的数组表示 */ - // 使用 int? 可空类型 ,就可以使用 null 来标记空位 - int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; - ``` - -=== "Swift" - - ```swift title="" - /* 二叉树的数组表示 */ - // 使用 Int? 可空类型 ,就可以使用 nil 来标记空位 - let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] - ``` - -=== "Zig" - - ```zig title="" - - ``` - -![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) - -回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 - -![array_representation_complete_binary_tree](binary_tree.assets/array_representation_complete_binary_tree.png) - -数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。 diff --git a/build/chapter_tree/binary_tree_traversal.md b/build/chapter_tree/binary_tree_traversal.md deleted file mode 100755 index eb19b9451..000000000 --- a/build/chapter_tree/binary_tree_traversal.md +++ /dev/null @@ -1,520 +0,0 @@ ---- -comments: true ---- - -# 7.2. 二叉树遍历 - -非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 - -## 7.2.1. 层序遍历 - -「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 - -层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。 - -![binary_tree_bfs](binary_tree_traversal.assets/binary_tree_bfs.png) - -

Fig. 二叉树的层序遍历

- -广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。 - -=== "Java" - - ```java title="binary_tree_bfs.java" - /* 层序遍历 */ - List hierOrder(TreeNode root) { - // 初始化队列,加入根结点 - Queue queue = new LinkedList<>() {{ add(root); }}; - // 初始化一个列表,用于保存遍历序列 - List list = new ArrayList<>(); - while (!queue.isEmpty()) { - TreeNode node = queue.poll(); // 队列出队 - list.add(node.val); // 保存结点值 - if (node.left != null) - queue.offer(node.left); // 左子结点入队 - if (node.right != null) - queue.offer(node.right); // 右子结点入队 - } - return list; - } - ``` - -=== "C++" - - ```cpp title="binary_tree_bfs.cpp" - /* 层序遍历 */ - vector hierOrder(TreeNode* root) { - // 初始化队列,加入根结点 - queue queue; - queue.push(root); - // 初始化一个列表,用于保存遍历序列 - vector vec; - while (!queue.empty()) { - TreeNode* node = queue.front(); - queue.pop(); // 队列出队 - vec.push_back(node->val); // 保存结点值 - if (node->left != nullptr) - queue.push(node->left); // 左子结点入队 - if (node->right != nullptr) - queue.push(node->right); // 右子结点入队 - } - return vec; - } - ``` - -=== "Python" - - ```python title="binary_tree_bfs.py" - """ 层序遍历 """ - def hier_order(root: Optional[TreeNode]): - # 初始化队列,加入根结点 - queue = collections.deque() - queue.append(root) - # 初始化一个列表,用于保存遍历序列 - res = [] - while queue: - node = queue.popleft() # 队列出队 - res.append(node.val) # 保存结点值 - if node.left is not None: - queue.append(node.left) # 左子结点入队 - if node.right is not None: - queue.append(node.right) # 右子结点入队 - return res - ``` - -=== "Go" - - ```go title="binary_tree_bfs.go" - /* 层序遍历 */ - func levelOrder(root *TreeNode) []int { - // 初始化队列,加入根结点 - queue := list.New() - queue.PushBack(root) - // 初始化一个切片,用于保存遍历序列 - nums := make([]int, 0) - for queue.Len() > 0 { - // poll - node := queue.Remove(queue.Front()).(*TreeNode) - // 保存结点值 - nums = append(nums, node.Val) - if node.Left != nil { - // 左子结点入队 - queue.PushBack(node.Left) - } - if node.Right != nil { - // 右子结点入队 - queue.PushBack(node.Right) - } - } - return nums - } - ``` - -=== "JavaScript" - - ```js title="binary_tree_bfs.js" - /* 层序遍历 */ - function hierOrder(root) { - // 初始化队列,加入根结点 - let queue = [root]; - // 初始化一个列表,用于保存遍历序列 - let list = []; - while (queue.length) { - let node = queue.shift(); // 队列出队 - list.push(node.val); // 保存结点值 - if (node.left) - queue.push(node.left); // 左子结点入队 - if (node.right) - queue.push(node.right); // 右子结点入队 - } - return list; - } - ``` - -=== "TypeScript" - - ```typescript title="binary_tree_bfs.ts" - /* 层序遍历 */ - function hierOrder(root: TreeNode | null): number[] { - // 初始化队列,加入根结点 - const queue = [root]; - // 初始化一个列表,用于保存遍历序列 - const list: number[] = []; - while (queue.length) { - let node = queue.shift() as TreeNode; // 队列出队 - list.push(node.val); // 保存结点值 - if (node.left) { - queue.push(node.left); // 左子结点入队 - } - if (node.right) { - queue.push(node.right); // 右子结点入队 - } - } - return list; - } - ``` - -=== "C" - - ```c title="binary_tree_bfs.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree_bfs.cs" - /* 层序遍历 */ - public List hierOrder(TreeNode root) - { - // 初始化队列,加入根结点 - Queue queue = new(); - queue.Enqueue(root); - // 初始化一个列表,用于保存遍历序列 - List list = new(); - while (queue.Count != 0) - { - TreeNode node = queue.Dequeue(); // 队列出队 - list.Add(node.val); // 保存结点值 - if (node.left != null) - queue.Enqueue(node.left); // 左子结点入队 - if (node.right != null) - queue.Enqueue(node.right); // 右子结点入队 - } - return list; - } - - ``` - -=== "Swift" - - ```swift title="binary_tree_bfs.swift" - /* 层序遍历 */ - func hierOrder(root: TreeNode) -> [Int] { - // 初始化队列,加入根结点 - var queue: [TreeNode] = [root] - // 初始化一个列表,用于保存遍历序列 - var list: [Int] = [] - while !queue.isEmpty { - let node = queue.removeFirst() // 队列出队 - list.append(node.val) // 保存结点值 - if let left = node.left { - queue.append(left) // 左子结点入队 - } - if let right = node.right { - queue.append(right) // 右子结点入队 - } - } - return list - } - ``` - -=== "Zig" - - ```zig title="binary_tree_bfs.zig" - - ``` - -## 7.2.2. 前序、中序、后序遍历 - -相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 - -如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。 - -![binary_tree_dfs](binary_tree_traversal.assets/binary_tree_dfs.png) - -

Fig. 二叉树的前 / 中 / 后序遍历

- -
- -| 位置 | 含义 | 此处访问结点时对应 | -| ---------- | ------------------------------------ | ----------------------------- | -| 橙色圆圈处 | 刚进入此结点,即将访问该结点的左子树 | 前序遍历 Pre-Order Traversal | -| 蓝色圆圈处 | 已访问完左子树,即将访问右子树 | 中序遍历 In-Order Traversal | -| 紫色圆圈处 | 已访问完左子树和右子树,即将返回 | 后序遍历 Post-Order Traversal | - -
- -=== "Java" - - ```java title="binary_tree_dfs.java" - /* 前序遍历 */ - void preOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.add(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - void inOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.add(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - void postOrder(TreeNode root) { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.add(root.val); - } - ``` - -=== "C++" - - ```cpp title="binary_tree_dfs.cpp" - /* 前序遍历 */ - void preOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - vec.push_back(root->val); - preOrder(root->left); - preOrder(root->right); - } - - /* 中序遍历 */ - void inOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root->left); - vec.push_back(root->val); - inOrder(root->right); - } - - /* 后序遍历 */ - void postOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root->left); - postOrder(root->right); - vec.push_back(root->val); - } - ``` - -=== "Python" - - ```python title="binary_tree_dfs.py" - """ 前序遍历 """ - def pre_order(root: Optional[TreeNode]): - if root is None: - return - # 访问优先级:根结点 -> 左子树 -> 右子树 - res.append(root.val) - pre_order(root=root.left) - pre_order(root=root.right) - - """ 中序遍历 """ - def in_order(root: Optional[TreeNode]): - if root is None: - return - # 访问优先级:左子树 -> 根结点 -> 右子树 - in_order(root=root.left) - res.append(root.val) - in_order(root=root.right) - - """ 后序遍历 """ - def post_order(root: Optional[TreeNode]): - if root is None: - return - # 访问优先级:左子树 -> 右子树 -> 根结点 - post_order(root=root.left) - post_order(root=root.right) - res.append(root.val) - ``` - -=== "Go" - - ```go title="binary_tree_dfs.go" - /* 前序遍历 */ - func preOrder(node *TreeNode) { - if node == nil { - return - } - // 访问优先级:根结点 -> 左子树 -> 右子树 - nums = append(nums, node.Val) - preOrder(node.Left) - preOrder(node.Right) - } - - /* 中序遍历 */ - func inOrder(node *TreeNode) { - if node == nil { - return - } - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(node.Left) - nums = append(nums, node.Val) - inOrder(node.Right) - } - - /* 后序遍历 */ - func postOrder(node *TreeNode) { - if node == nil { - return - } - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(node.Left) - postOrder(node.Right) - nums = append(nums, node.Val) - } - ``` - -=== "JavaScript" - - ```js title="binary_tree_dfs.js" - /* 前序遍历 */ - function preOrder(root){ - if (root === null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.push(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - function inOrder(root) { - if (root === null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.push(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - function postOrder(root) { - if (root === null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.push(root.val); - } - ``` - -=== "TypeScript" - - ```typescript title="binary_tree_dfs.ts" - /* 前序遍历 */ - function preOrder(root: TreeNode | null): void { - if (root === null) { - return; - } - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.push(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - function inOrder(root: TreeNode | null): void { - if (root === null) { - return; - } - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.push(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - function postOrder(root: TreeNode | null): void { - if (root === null) { - return; - } - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.push(root.val); - } - ``` - -=== "C" - - ```c title="binary_tree_dfs.c" - - ``` - -=== "C#" - - ```csharp title="binary_tree_dfs.cs" - /* 前序遍历 */ - void preOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.Add(root.val); - preOrder(root.left); - preOrder(root.right); - } - - /* 中序遍历 */ - void inOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root.left); - list.Add(root.val); - inOrder(root.right); - } - - /* 后序遍历 */ - void postOrder(TreeNode? root) - { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root.left); - postOrder(root.right); - list.Add(root.val); - } - ``` - -=== "Swift" - - ```swift title="binary_tree_dfs.swift" - /* 前序遍历 */ - func preOrder(root: TreeNode?) { - guard let root = root else { - return - } - // 访问优先级:根结点 -> 左子树 -> 右子树 - list.append(root.val) - preOrder(root: root.left) - preOrder(root: root.right) - } - - /* 中序遍历 */ - func inOrder(root: TreeNode?) { - guard let root = root else { - return - } - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root: root.left) - list.append(root.val) - inOrder(root: root.right) - } - - /* 后序遍历 */ - func postOrder(root: TreeNode?) { - guard let root = root else { - return - } - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root: root.left) - postOrder(root: root.right) - list.append(root.val) - } - ``` - -=== "Zig" - - ```zig title="binary_tree_dfs.zig" - - ``` - -!!! note - - 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 diff --git a/build/chapter_tree/summary.md b/build/chapter_tree/summary.md deleted file mode 100644 index 8566eaa8e..000000000 --- a/build/chapter_tree/summary.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -comments: true ---- - -# 7.5. 小结 - -- 二叉树是一种非线性数据结构,代表着“一分为二”的分治逻辑。二叉树的结点包含「值」和两个「指针」,分别指向左子结点和右子结点。 -- 选定二叉树中某结点,将其左(右)子结点以下形成的树称为左(右)子树。 -- 二叉树的术语较多,包括根结点、叶结点、层、度、边、高度、深度等。 -- 二叉树的初始化、结点插入、结点删除操作与链表的操作方法类似。 -- 常见的二叉树类型包括完美二叉树、完全二叉树、完满二叉树、平衡二叉树。完美二叉树是理想状态,链表则是退化后的最差状态。 -- 二叉树可以使用数组表示,具体做法是将结点值和空位按照层序遍历的顺序排列,并基于父结点和子结点之间的索引映射公式实现指针。 - -- 二叉树层序遍历是一种广度优先搜索,体现着“一圈一圈向外”的层进式遍历方式,通常借助队列来实现。 -- 前序、中序、后序遍历是深度优先搜索,体现着“走到头、再回头继续”的回溯遍历方式,通常使用递归实现。 -- 二叉搜索树是一种高效的元素查找数据结构,查找、插入、删除操作的时间复杂度皆为 $O(\log n)$ 。二叉搜索树退化为链表后,各项时间复杂度劣化至 $O(n)$ ,因此如何避免退化是非常重要的课题。 -- AVL 树又称平衡二叉搜索树,其通过旋转操作,使得在不断插入与删除结点后,仍然可以保持二叉树的平衡(不退化)。 -- AVL 树的旋转操作分为右旋、左旋、先右旋后左旋、先左旋后右旋。在插入或删除结点后,AVL 树会从底至顶地执行旋转操作,使树恢复平衡。 diff --git a/build/index.md b/build/index.md deleted file mode 100644 index 0ab257690..000000000 --- a/build/index.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -comments: true -hide: - - footer ---- - -=== " " - -
- ![conceptual_rendering](index.assets/conceptual_rendering.png){ align=left width=350 } -




-

《 Hello,算法 》

-

动画图解、能运行、可提问的
数据结构与算法快速入门教程

-

[![github-stars](https://img.shields.io/github/stars/krahets/hello-algo?style=social)](https://github.com/krahets/hello-algo)

-
[@Krahets](https://leetcode.cn/u/jyd/)
-
- ---- - -

「清晰动画讲解」

- -

动画诠释重点,平滑学习曲线
电脑、平板、手机全终端阅读

- -![algorithm_animation](index.assets/animation.gif) - -!!! quote "" - -

"A picture is worth a thousand words."

-

“一图胜千言”

- ---- - -

「代码实践导向」

- -

提供经典算法的清晰实现与测试代码
多种语言,详细注释,皆可一键运行

- -![running_code](index.assets/running_code.gif) - -!!! quote "" - -

"Talk is cheap. Show me the code."

-

“少吹牛,看代码”

- ---- - -

「可讨论与提问」

- -

作者一般 72h 内回复评论问题
与小伙伴们一起讨论学习进步

- -![comment](index.assets/comment.gif) - -!!! quote "" - -

“追风赶月莫停留,平芜尽处是春山”

-

一起加油!

- ---- - -

推荐语

- -!!! quote - - “一本通俗易懂的数据结构与算法入门书,引导读者手脑并用地学习,强烈推荐算法初学者阅读。” - - **—— 邓俊辉,清华大学计算机系教授** - -

致谢

- -感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们是: - -

- - - -

- ---- diff --git a/build/overrides/partials/comments.html b/build/overrides/partials/comments.html deleted file mode 100755 index bac275a6e..000000000 --- a/build/overrides/partials/comments.html +++ /dev/null @@ -1,50 +0,0 @@ -{% if page.meta.comments %} -

{{ lang.t("meta.comments") }}

- - - - -{% endif %} \ No newline at end of file diff --git a/build/stylesheets/extra.css b/build/stylesheets/extra.css deleted file mode 100644 index 7171f2ed1..000000000 --- a/build/stylesheets/extra.css +++ /dev/null @@ -1,76 +0,0 @@ - -/* Color Settings */ -/* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ -/* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ -:root > * { - --md-primary-fg-color: #FFFFFF; - --md-primary-bg-color: #1D1D20; - - --md-accent-fg-color: #999; - - --md-typeset-color: #1D1D20; - --md-typeset-a-color: #2AA996; -} - -[data-md-color-scheme="slate"] { - --md-primary-fg-color: #2E303E; - --md-primary-bg-color: #FEFEFE; - - --md-accent-fg-color: #999; - - --md-typeset-color: #FEFEFE; - --md-typeset-a-color: #21C8B8; -} - -/* https://github.com/squidfunk/mkdocs-material/issues/4832#issuecomment-1374891676 */ -.md-nav__link[for] { - color: var(--md-default-fg-color) !important -} - -/* Center Markdown Tables (requires md_in_html extension) */ -.center-table { - text-align: center; -} - -.md-typeset .center-table :is(td,th):not([align]) { - /* Reset alignment for table cells */ - text-align: initial; -} - - -/* Markdown Header */ -/* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ -.md-typeset h1 { - font-weight: 400; - color: var(--md-default-fg-color); -} - -.md-typeset h2 { - font-weight: 400; -} - -.md-typeset h3 { - font-weight: 500; -} - -.md-typeset a { - text-decoration: underline; -} - -/* Image align center */ -.center { - display: block; - margin: 0 auto; -} - -/* font-family setting for Win10 */ -body { - --md-text-font-family: -apple-system,BlinkMacSystemFont,var(--md-text-font,_),Helvetica,Arial,sans-serif; - --md-code-font-family: var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,-apple-system,BlinkMacSystemFont,var(--md-text-font,_),monospace; -} - -/* max height of code block */ -/* https://github.com/squidfunk/mkdocs-material/issues/3444 */ -.md-typeset pre > code { - max-height: 30rem; -} diff --git a/codes/cpp/chapter_hashing/array_hash_map.cpp b/codes/cpp/chapter_hashing/array_hash_map.cpp index 8b92174e2..326cc2852 100644 --- a/codes/cpp/chapter_hashing/array_hash_map.cpp +++ b/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -37,9 +37,8 @@ public: string get(int key) { int index = hashFunc(key); Entry* pair = bucket[index]; - if (pair == nullptr) { - return "Not Found"; - } + if (pair == nullptr) + return nullptr; return pair->val; } @@ -141,4 +140,4 @@ int main() { } return 0; -} +} \ No newline at end of file diff --git a/codes/cpp/chapter_heap/my_heap.cpp b/codes/cpp/chapter_heap/my_heap.cpp index c0b550b26..faf2f5e88 100644 --- a/codes/cpp/chapter_heap/my_heap.cpp +++ b/codes/cpp/chapter_heap/my_heap.cpp @@ -28,7 +28,7 @@ private: } /* 从结点 i 开始,从底至顶堆化 */ - void shifUp(int i) { + void siftUp(int i) { while (true) { // 获取结点 i 的父结点 int p = parent(i); @@ -43,7 +43,7 @@ private: } /* 从结点 i 开始,从顶至底堆化 */ - void shifDown(int i) { + void siftDown(int i) { while (true) { // 判断结点 i, l, r 中值最大的结点,记为 ma int l = left(i), r = right(i), ma = i; @@ -68,7 +68,7 @@ public: maxHeap = nums; // 堆化除叶结点以外的其他所有结点 for (int i = parent(size() - 1); i >= 0; i--) { - shifDown(i); + siftDown(i); } } @@ -92,7 +92,7 @@ public: // 添加结点 maxHeap.push_back(val); // 从底至顶堆化 - shifUp(size() - 1); + siftUp(size() - 1); } /* 元素出堆 */ @@ -107,7 +107,7 @@ public: // 删除结点 maxHeap.pop_back(); // 从顶至底堆化 - shifDown(0); + siftDown(0); } /* 打印堆(二叉树)*/ diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp index 7b25a9e45..c7e76da2e 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -10,7 +10,7 @@ class LinkedListStack { private: ListNode* stackTop; // 将头结点作为栈顶 - int stkSize; // 栈的长度 + int stkSize; // 栈的长度 public: LinkedListStack() { diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 575213a02..778e97801 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -123,14 +123,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "C++" ```cpp title="array.cpp" - /* 随机返回一个数组元素 */ - int randomAccess(int* nums, int size) { - // 在区间 [0, size) 中随机抽取一个数字 - int randomIndex = rand() % size; - // 获取并返回随机元素 - int randomNum = nums[randomIndex]; - return randomNum; - } + [class]{}-[func]{randomAccess} ``` === "Python" @@ -238,19 +231,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "C++" ```cpp title="array.cpp" - /* 扩展数组长度 */ - int* extend(int* nums, int size, int enlarge) { - // 初始化一个扩展长度后的数组 - int* res = new int[size + enlarge]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size; i++) { - res[i] = nums[i]; - } - // 释放内存 - delete[] nums; - // 返回扩展后的新数组 - return res; - } + [class]{}-[func]{extend} ``` === "Python" @@ -383,23 +364,9 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "C++" ```cpp title="array.cpp" - /* 在数组的索引 index 处插入元素 num */ - void insert(int* nums, int size, int num, int index) { - // 把索引 index 以及之后的所有元素向后移动一位 - for (int i = size - 1; i > index; i--) { - nums[i] = nums[i - 1]; - } - // 将 num 赋给 index 处元素 - nums[index] = num; - } - - /* 删除索引 index 处元素 */ - void remove(int* nums, int size, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < size - 1; i++) { - nums[i] = nums[i + 1]; - } - } + [class]{}-[func]{insert} + + [class]{}-[func]{remove} ``` === "Python" @@ -567,14 +534,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "C++" ```cpp title="array.cpp" - /* 遍历数组 */ - void traverse(int* nums, int size) { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < size; i++) { - count++; - } - } + [class]{}-[func]{traverse} ``` === "Python" @@ -707,14 +667,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "C++" ```cpp title="array.cpp" - /* 在数组中查找指定元素 */ - int find(int* nums, int size, int target) { - for (int i = 0; i < size; i++) { - if (nums[i] == target) - return i; - } - return -1; - } + [class]{}-[func]{find} ``` === "Python" diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index af0edd8ba..bf4d88ed8 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -511,15 +511,7 @@ comments: true === "C++" ```cpp title="linked_list.cpp" - /* 访问链表中索引为 index 的结点 */ - ListNode* access(ListNode* head, int index) { - for (int i = 0; i < index; i++) { - if (head == nullptr) - return nullptr; - head = head->next; - } - return head; - } + [class]{}-[func]{access} ``` === "Python" @@ -640,17 +632,7 @@ comments: true === "C++" ```cpp title="linked_list.cpp" - /* 在链表中查找值为 target 的首个结点 */ - int find(ListNode* head, int target) { - int index = 0; - while (head != nullptr) { - if (head->val == target) - return index; - head = head->next; - index++; - } - return -1; - } + [class]{}-[func]{find} ``` === "Python" diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index a46deec09..3c043d21d 100755 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -722,106 +722,7 @@ comments: true === "C++" ```cpp title="my_list.cpp" - /* 列表类简易实现 */ - class MyList { - private: - int* nums; // 数组(存储列表元素) - int numsCapacity = 10; // 列表容量 - int numsSize = 0; // 列表长度(即当前元素数量) - int extendRatio = 2; // 每次列表扩容的倍数 - - public: - /* 构造函数 */ - MyList() { - nums = new int[numsCapacity]; - } - - /* 析构函数 */ - ~MyList() { - delete[] nums; - } - - /* 获取列表长度(即当前元素数量)*/ - int size() { - return numsSize; - } - - /* 获取列表容量 */ - int capacity() { - return numsCapacity; - } - - /* 访问元素 */ - int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - void set(int index, int num) { - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (size() == capacity()) - extendCapacity(); - nums[size()] = num; - // 更新元素数量 - numsSize++; - } - - /* 中间插入元素 */ - void insert(int index, int num) { - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (size() == capacity()) - extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 - for (int j = size() - 1; j >= index; j--) { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - numsSize++; - } - - /* 删除元素 */ - int remove(int index) { - if (index < 0 || index >= size()) - throw out_of_range("索引越界"); - int num = nums[index]; - // 索引 i 之后的元素都向前移动一位 - for (int j = index; j < size() - 1; j++) { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - numsSize--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - void extendCapacity() { - // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组 - int newCapacity = capacity() * extendRatio; - int* tmp = nums; - nums = new int[newCapacity]; - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size(); i++) { - nums[i] = tmp[i]; - } - // 释放内存 - delete[] tmp; - numsCapacity = newCapacity; - } - }; + [class]{MyList}-[func]{} ``` === "Python" diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 434b7fda9..57a2155f5 100755 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -588,22 +588,7 @@ $$ === "C++" ```cpp title="space_complexity.cpp" - /* 常数阶 */ - void constant(int n) { - // 常量、变量、对象占用 O(1) 空间 - const int a = 0; - int b = 0; - vector nums(10000); - ListNode node(0); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) { - func(); - } - } + [class]{}-[func]{constant} ``` === "Python" @@ -769,21 +754,7 @@ $$ === "C++" ```cpp title="space_complexity.cpp" - /* 线性阶 */ - void linear(int n) { - // 长度为 n 的数组占用 O(n) 空间 - vector nums(n); - // 长度为 n 的列表占用 O(n) 空间 - vector nodes; - for (int i = 0; i < n; i++) { - nodes.push_back(ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - unordered_map map; - for (int i = 0; i < n; i++) { - map[i] = to_string(i); - } - } + [class]{}-[func]{linear} ``` === "Python" @@ -933,12 +904,7 @@ $$ === "C++" ```cpp title="space_complexity.cpp" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - cout << "递归 n = " << n << endl; - if (n == 1) return; - linearRecur(n - 1); - } + [class]{}-[func]{linearRecur} ``` === "Python" @@ -1041,18 +1007,7 @@ $$ === "C++" ```cpp title="space_complexity.cpp" - /* 平方阶 */ - void quadratic(int n) { - // 二维列表占用 O(n^2) 空间 - vector> numMatrix; - for (int i = 0; i < n; i++) { - vector tmp; - for (int j = 0; j < n; j++) { - tmp.push_back(0); - } - numMatrix.push_back(tmp); - } - } + [class]{}-[func]{quadratic} ``` === "Python" @@ -1182,13 +1137,7 @@ $$ === "C++" ```cpp title="space_complexity.cpp" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) { - if (n <= 0) return 0; - vector nums(n); - cout << "递归 n = " << n << " 中的 nums 长度 = " << nums.size() << endl; - return quadraticRecur(n - 1); - } + [class]{}-[func]{quadraticRecur} ``` === "Python" @@ -1297,14 +1246,7 @@ $$ === "C++" ```cpp title="space_complexity.cpp" - /* 指数阶(建立满二叉树) */ - TreeNode* buildTree(int n) { - if (n == 0) return nullptr; - TreeNode* root = new TreeNode(0); - root->left = buildTree(n - 1); - root->right = buildTree(n - 1); - return root; - } + [class]{}-[func]{buildTree} ``` === "Python" diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md index e5d712fd0..3e669e20f 100755 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ b/docs/chapter_computational_complexity/space_time_tradeoff.md @@ -39,20 +39,7 @@ comments: true === "C++" ```cpp title="leetcode_two_sum.cpp" - class SolutionBruteForce { - public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 两层循环,时间复杂度 O(n^2) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return { i, j }; - } - } - return {}; - } - }; + [class]{SolutionBruteForce}-[func]{} ``` === "Python" @@ -193,22 +180,7 @@ comments: true === "C++" ```cpp title="leetcode_two_sum.cpp" - class SolutionHashMap { - public: - vector twoSum(vector& nums, int target) { - int size = nums.size(); - // 辅助哈希表,空间复杂度 O(n) - unordered_map dic; - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.find(target - nums[i]) != dic.end()) { - return { dic[target - nums[i]], i }; - } - dic.emplace(nums[i], i); - } - return {}; - } - }; + [class]{SolutionHashMap}-[func]{} ``` === "Python" diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 2a5c2860f..59e2d899e 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -801,14 +801,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 常数阶 */ - int constant(int n) { - int count = 0; - int size = 100000; - for (int i = 0; i < size; i++) - count++; - return count; - } + [class]{}-[func]{constant} ``` === "Python" @@ -927,13 +920,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 线性阶 */ - int linear(int n) { - int count = 0; - for (int i = 0; i < n; i++) - count++; - return count; - } + [class]{}-[func]{linear} ``` === "Python" @@ -1045,15 +1032,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 线性阶(遍历数组) */ - int arrayTraversal(vector& nums) { - int count = 0; - // 循环次数与数组长度成正比 - for (int num : nums) { - count++; - } - return count; - } + [class]{}-[func]{arrayTraversal} ``` === "Python" @@ -1175,17 +1154,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 平方阶 */ - int quadratic(int n) { - int count = 0; - // 循环次数与数组长度成平方关系 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - count++; - } - } - return count; - } + [class]{}-[func]{quadratic} ``` === "Python" @@ -1330,24 +1299,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 平方阶(冒泡排序) */ - int bubbleSort(vector& nums) { - int count = 0; // 计数器 - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - int tmp = nums[j]; - nums[j] = nums[j + 1]; - nums[j + 1] = tmp; - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } + [class]{}-[func]{bubbleSort} ``` === "Python" @@ -1543,19 +1495,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0, base = 1; - // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) - for (int i = 0; i < n; i++) { - for (int j = 0; j < base; j++) { - count++; - } - base *= 2; - } - // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 - return count; - } + [class]{}-[func]{exponential} ``` === "Python" @@ -1716,11 +1656,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } + [class]{}-[func]{expRecur} ``` === "Python" @@ -1822,15 +1758,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } + [class]{}-[func]{logarithmic} ``` === "Python" @@ -1958,11 +1886,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } + [class]{}-[func]{logRecur} ``` === "Python" @@ -2062,16 +1986,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 线性对数阶 */ - int linearLogRecur(float n) { - if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + - linearLogRecur(n / 2); - for (int i = 0; i < n; i++) { - count++; - } - return count; - } + [class]{}-[func]{linearLogRecur} ``` === "Python" @@ -2213,16 +2128,7 @@ $$ === "C++" ```cpp title="time_complexity.cpp" - /* 阶乘阶(递归实现) */ - int factorialRecur(int n) { - if (n == 0) return 1; - int count = 0; - // 从 1 个分裂出 n 个 - for (int i = 0; i < n; i++) { - count += factorialRecur(n - 1); - } - return count; - } + [class]{}-[func]{factorialRecur} ``` === "Python" diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 3a54103e6..fb6fc28e1 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -424,54 +424,9 @@ $$ === "C++" ```cpp title="array_hash_map.cpp" - /* 键值对 int->String */ - struct Entry { - public: - int key; - string val; - Entry(int key, string val) { - this->key = key; - this->val = val; - } - }; - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private: - vector bucket; - public: - ArrayHashMap() { - // 初始化一个长度为 100 的桶(数组) - bucket= vector(100); - } - - /* 哈希函数 */ - int hashFunc(int key) { - int index = key % 100; - return index; - } - - /* 查询操作 */ - string get(int key) { - int index = hashFunc(key); - Entry* pair = bucket[index]; - return pair->val; - } - - /* 添加操作 */ - void put(int key, string val) { - Entry* pair = new Entry(key, val); - int index = hashFunc(key); - bucket[index] = pair; - } - - /* 删除操作 */ - void remove(int key) { - int index = hashFunc(key); - // 置为 nullptr ,代表删除 - bucket[index] = nullptr; - } - }; + [class]{Entry}-[func]{} + + [class]{ArrayHashMap}-[func]{} ``` === "Python" diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index 864961565..86e8dafad 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -445,10 +445,7 @@ comments: true === "C++" ```cpp title="my_heap.cpp" - /* 访问堆顶元素 */ - int peek() { - return maxHeap[0]; - } + [class]{MaxHeap}-[func]{peek} ``` === "Python" @@ -548,28 +545,9 @@ comments: true === "C++" ```cpp title="my_heap.cpp" - /* 元素入堆 */ - void push(int val) { - // 添加结点 - maxHeap.push_back(val); - // 从底至顶堆化 - shifUp(size() - 1); - } - - /* 从结点 i 开始,从底至顶堆化 */ - void shifUp(int i) { - while (true) { - // 获取结点 i 的父结点 - int p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 - if (p < 0 || maxHeap[i] <= maxHeap[p]) - break; - // 交换两结点 - swap(maxHeap[i], maxHeap[p]); - // 循环向上堆化 - i = p; - } - } + [class]{MaxHeap}-[func]{push} + + [class]{MaxHeap}-[func]{siftUp} ``` === "Python" @@ -758,39 +736,9 @@ comments: true === "C++" ```cpp title="my_heap.cpp" - /* 从结点 i 开始,从顶至底堆化 */ - void shifDown(int i) { - while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma - int l = left(i), r = right(i), ma = i; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if (l < size() && maxHeap[l] > maxHeap[ma]) - ma = l; - if (r < size() && maxHeap[r] > maxHeap[ma]) - ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 - if (ma == i) - break; - swap(maxHeap[i], maxHeap[ma]); - // 循环向下堆化 - i = ma; - } - } - - /* 元素出堆 */ - void poll() { - // 判空处理 - if (empty()) { - cout << "Error:堆为空" << endl; - return; - } - // 交换根结点与最右叶结点(即交换首元素与尾元素) - swap(maxHeap[0], maxHeap[size() - 1]); - // 删除结点 - maxHeap.pop_back(); - // 从顶至底堆化 - shifDown(0); - } + [class]{MaxHeap}-[func]{poll} + + [class]{MaxHeap}-[func]{siftDown} ``` === "Python" @@ -993,15 +941,7 @@ comments: true === "C++" ```cpp title="my_heap.cpp" - /* 构造函数,根据输入列表建堆 */ - MaxHeap(vector nums) { - // 将列表元素原封不动添加进堆 - maxHeap = nums; - // 堆化除叶结点以外的其他所有结点 - for (int i = parent(size() - 1); i >= 0; i--) { - shifDown(i); - } - } + [class]{MaxHeap}-[func]{MaxHeap} ``` === "Python" diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 3cd9a8e37..a4d9d2ed5 100755 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -60,23 +60,7 @@ $$ === "C++" ```cpp title="binary_search.cpp" - /* 二分查找(双闭区间) */ - int binarySearch(vector& nums, int target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = nums.size() - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } + [class]{}-[func]{binarySearch} ``` === "Python" @@ -225,23 +209,7 @@ $$ === "C++" ```cpp title="binary_search.cpp" - /* 二分查找(左闭右开) */ - int binarySearch1(vector& nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.size(); - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = (i + j) / 2; // 计算中点索引 m - if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 - j = m; - else // 找到目标元素,返回其索引 - return m; - } - // 未找到目标元素,返回 -1 - return -1; - } + [class]{}-[func]{binarySearch1} ``` === "Python" diff --git a/docs/chapter_searching/hashing_search.md b/docs/chapter_searching/hashing_search.md index e3c6c2132..bcac44cd9 100755 --- a/docs/chapter_searching/hashing_search.md +++ b/docs/chapter_searching/hashing_search.md @@ -25,14 +25,7 @@ comments: true === "C++" ```cpp title="hashing_search.cpp" - /* 哈希查找(数组) */ - int hashingSearchArray(unordered_map map, int target) { - // 哈希表的 key: 目标元素,value: 索引 - // 若哈希表中无此 key ,返回 -1 - if (map.find(target) == map.end()) - return -1; - return map[target]; - } + [class]{}-[func]{hashingSearchArray} ``` === "Python" @@ -126,14 +119,7 @@ comments: true === "C++" ```cpp title="hashing_search.cpp" - /* 哈希查找(链表) */ - ListNode* hashingSearchLinkedList(unordered_map map, int target) { - // 哈希表的 key: 目标结点值,value: 结点对象 - // 若哈希表中无此 key ,返回 nullptr - if (map.find(target) == map.end()) - return nullptr; - return map[target]; - } + [class]{}-[func]{hashingSearchLinkedList} ``` === "Python" diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md index fa91589b0..6f446a85c 100755 --- a/docs/chapter_searching/linear_search.md +++ b/docs/chapter_searching/linear_search.md @@ -21,17 +21,7 @@ comments: true === "C++" ```cpp title="linear_search.cpp" - /* 线性查找(数组) */ - int linearSearchArray(vector& nums, int target) { - // 遍历数组 - for (int i = 0; i < nums.size(); i++) { - // 找到目标元素,返回其索引 - if (nums[i] == target) - return i; - } - // 未找到目标元素,返回 -1 - return -1; - } + [class]{}-[func]{linearSearchArray} ``` === "Python" @@ -151,18 +141,7 @@ comments: true === "C++" ```cpp title="linear_search.cpp" - /* 线性查找(链表) */ - ListNode* linearSearchLinkedList(ListNode* head, int target) { - // 遍历链表 - while (head != nullptr) { - // 找到目标结点,返回之 - if (head->val == target) - return head; - head = head->next; - } - // 未找到目标结点,返回 nullptr - return nullptr; - } + [class]{}-[func]{linearSearchLinkedList} ``` === "Python" diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index e229e450c..8a2bf4689 100755 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -56,20 +56,7 @@ comments: true === "C++" ```cpp title="bubble_sort.cpp" - /* 冒泡排序 */ - void bubbleSort(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - // 这里使用了 std::swap() 函数 - swap(nums[j], nums[j + 1]); - } - } - } - } + [class]{}-[func]{bubbleSort} ``` === "Python" @@ -235,23 +222,7 @@ comments: true === "C++" ```cpp title="bubble_sort.cpp" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(vector& nums) { - // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for (int i = nums.size() - 1; i > 0; i--) { - bool flag = false; // 初始化标志位 - // 内循环:冒泡操作 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - // 这里使用了 std::swap() 函数 - swap(nums[j], nums[j + 1]); - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } + [class]{}-[func]{bubbleSortWithFlag} ``` === "Python" diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 3cc791e10..37d2d3ad5 100755 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -33,19 +33,7 @@ comments: true === "C++" ```cpp title="insertion_sort.cpp" - /* 插入排序 */ - void insertionSort(vector& nums) { - // 外循环:base = nums[1], nums[2], ..., nums[n-1] - for (int i = 1; i < nums.size(); i++) { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到左边的正确位置 - while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 2. 将 base 赋值到正确位置 - } - } + [class]{}-[func]{insertionSort} ``` === "Python" diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index 8f694b8f1..b1bb17343 100755 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -259,17 +259,7 @@ comments: true === "C++" ```cpp title="quick_sort.cpp" - /* 快速排序 */ - void quickSort(vector& nums, int left, int right) { - // 子数组长度为 1 时终止递归 - if (left >= right) - return; - // 哨兵划分 - int pivot = partition(nums, left, right); - // 递归左子数组、右子数组 - quickSort(nums, left, pivot - 1); - quickSort(nums, pivot + 1, right); - } + [class]{QuickSort}-[func]{quickSort} ``` === "Python" @@ -415,27 +405,9 @@ comments: true === "C++" ```cpp title="quick_sort.cpp" - /* 选取三个元素的中位数 */ - int medianThree(vector& nums, int left, int mid, int right) { - // 使用了异或操作来简化代码 - // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 - if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right])) - return left; - else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) - return mid; - else - return right; - } + [class]{QuickSortMedian}-[func]{medianThree} - /* 哨兵划分(三数取中值) */ - int partition(vector& nums, int left, int right) { - // 选取三个候选元素的中位数 - int med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - // 下同省略... - } + [class]{QuickSortMedian}-[func]{partition} ``` === "Python" @@ -602,22 +574,7 @@ comments: true === "C++" ```cpp title="quick_sort.cpp" - /* 快速排序(尾递归优化) */ - void quickSort(vector& nums, int left, int right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] - } else { - quickSort(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] - } - } - } + [class]{QuickSortTailCall}-[func]{quickSort} ``` === "Python" diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 9860503bd..1ce554f53 100755 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -290,63 +290,7 @@ comments: true === "C++" ```cpp title="linkedlist_queue.cpp" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private: - ListNode *front, *rear; // 头结点 front ,尾结点 rear - int queSize; - - public: - LinkedListQueue() { - front = nullptr; - rear = nullptr; - queSize = 0; - } - ~LinkedListQueue() { - delete front; - delete rear; - } - /* 获取队列的长度 */ - int size() { - return queSize; - } - /* 判断队列是否为空 */ - bool empty() { - return queSize == 0; - } - /* 入队 */ - void push(int num) { - // 尾结点后添加 num - ListNode* node = new ListNode(num); - // 如果队列为空,则令头、尾结点都指向该结点 - if (front == nullptr) { - front = node; - rear = node; - } - // 如果队列不为空,则将该结点添加到尾结点后 - else { - rear->next = node; - rear = node; - } - queSize++; - } - /* 出队 */ - void poll() { - int num = peek(); - // 删除头结点 - ListNode *tmp = front; - front = front->next; - // 释放内存 - delete tmp; - queSize--; - } - /* 访问队首元素 */ - int peek() { - if (size() == 0) - throw out_of_range("队列为空"); - return front->val; - } - }; + [class]{LinkedListQueue}-[func]{} ``` === "Python" @@ -669,70 +613,7 @@ comments: true === "C++" ```cpp title="array_queue.cpp" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private: - int *nums; // 用于存储队列元素的数组 - int front; // 队首指针,指向队首元素 - int queSize; // 队列长度 - int queCapacity; // 队列容量 - - public: - ArrayQueue(int capacity) { - // 初始化数组 - nums = new int[capacity]; - queCapacity = capacity; - front = queSize = 0; - } - - ~ArrayQueue() { - delete[] nums; - } - - /* 获取队列的容量 */ - int capacity() { - return queCapacity; - } - - /* 获取队列的长度 */ - int size() { - return queSize; - } - - /* 判断队列是否为空 */ - bool empty() { - return size() == 0; - } - - /* 入队 */ - void push(int num) { - if (queSize == queCapacity) { - cout << "队列已满" << endl; - return; - } - // 计算队尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - int rear = (front + queSize) % queCapacity; - // 尾结点后添加 num - nums[rear] = num; - queSize++; - } - - /* 出队 */ - void poll() { - int num = peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % queCapacity; - queSize--; - } - - /* 访问队首元素 */ - int peek() { - if (empty()) - throw out_of_range("队列为空"); - return nums[front]; - } - }; + [class]{ArrayQueue}-[func]{} ``` === "Python" diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 4c39995b6..062ea6d13 100755 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -293,51 +293,7 @@ comments: true === "C++" ```cpp title="linkedlist_stack.cpp" - /* 基于链表实现的栈 */ - class LinkedListStack { - private: - ListNode* stackTop; // 将头结点作为栈顶 - int stkSize; // 栈的长度 - - public: - LinkedListStack() { - stackTop = nullptr; - stkSize = 0; - } - ~LinkedListStack() { - freeMemoryLinkedList(stackTop); - } - /* 获取栈的长度 */ - int size() { - return stkSize; - } - /* 判断栈是否为空 */ - bool empty() { - return size() == 0; - } - /* 入栈 */ - void push(int num) { - ListNode* node = new ListNode(num); - node->next = stackTop; - stackTop = node; - stkSize++; - } - /* 出栈 */ - void pop() { - int num = top(); - ListNode *tmp = stackTop; - stackTop = stackTop->next; - // 释放内存 - delete tmp; - stkSize--; - } - /* 访问栈顶元素 */ - int top() { - if (size() == 0) - throw out_of_range("栈为空"); - return stackTop->val; - } - }; + [class]{LinkedListStack}-[func]{} ``` === "Python" @@ -652,36 +608,7 @@ comments: true === "C++" ```cpp title="array_stack.cpp" - /* 基于数组实现的栈 */ - class ArrayStack { - private: - vector stack; - - public: - /* 获取栈的长度 */ - int size() { - return stack.size(); - } - /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); - } - /* 入栈 */ - void push(int num) { - stack.push_back(num); - } - /* 出栈 */ - void pop() { - int oldTop = top(); - stack.pop_back(); - } - /* 访问栈顶元素 */ - int top() { - if(empty()) - throw out_of_range("栈为空"); - return stack.back(); - } - }; + [class]{ArrayStack}-[func]{} ``` === "Python" diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 0a4b2d058..e8849ed47 100755 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -303,13 +303,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C++" ```cpp title="avl_tree.cpp" - /* 获取平衡因子 */ - int balanceFactor(TreeNode* node) { - // 空结点平衡因子为 0 - if (node == nullptr) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 - return height(node->left) - height(node->right); - } + [class]{AVLTree}-[func]{balanceFactor} ``` === "Python" @@ -436,19 +430,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C++" ```cpp title="avl_tree.cpp" - /* 右旋操作 */ - TreeNode* rightRotate(TreeNode* node) { - TreeNode* child = node->left; - TreeNode* grandChild = child->right; - // 以 child 为原点,将 node 向右旋转 - child->right = node; - node->left = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } + [class]{AVLTree}-[func]{rightRotate} ``` === "Python" @@ -581,19 +563,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C++" ```cpp title="avl_tree.cpp" - /* 左旋操作 */ - TreeNode* leftRotate(TreeNode* node) { - TreeNode* child = node->right; - TreeNode* grandChild = child->left; - // 以 child 为原点,将 node 向左旋转 - child->left = node; - node->right = grandChild; - // 更新结点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根结点 - return child; - } + [class]{AVLTree}-[func]{leftRotate} ``` === "Python" @@ -750,35 +720,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C++" ```cpp title="avl_tree.cpp" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode* rotate(TreeNode* node) { - // 获取结点 node 的平衡因子 - int _balanceFactor = balanceFactor(node); - // 左偏树 - if (_balanceFactor > 1) { - if (balanceFactor(node->left) >= 0) { - // 右旋 - return rightRotate(node); - } else { - // 先左旋后右旋 - node->left = leftRotate(node->left); - return rightRotate(node); - } - } - // 右偏树 - if (_balanceFactor < -1) { - if (balanceFactor(node->right) <= 0) { - // 左旋 - return leftRotate(node); - } else { - // 先右旋后左旋 - node->right = rightRotate(node->right); - return leftRotate(node); - } - } - // 平衡树,无需旋转,直接返回 - return node; - } + [class]{AVLTree}-[func]{rotate} ``` === "Python" diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 25f605580..e663d5300 100755 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -44,21 +44,7 @@ comments: true === "C++" ```cpp title="binary_search_tree.cpp" - /* 查找结点 */ - TreeNode* search(int num) { - TreeNode* cur = root; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 目标结点在 cur 的右子树中 - if (cur->val < num) cur = cur->right; - // 目标结点在 cur 的左子树中 - else if (cur->val > num) cur = cur->left; - // 找到目标结点,跳出循环 - else break; - } - // 返回目标结点 - return cur; - } + [class]{BinarySearchTree}-[func]{search} ``` === "Python" @@ -212,27 +198,7 @@ comments: true === "C++" ```cpp title="binary_search_tree.cpp" - /* 插入结点 */ - TreeNode* insert(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; - TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 找到重复结点,直接返回 - if (cur->val == num) return nullptr; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur->val < num) cur = cur->right; - // 插入位置在 cur 的左子树中 - else cur = cur->left; - } - // 插入结点 val - TreeNode* node = new TreeNode(num); - if (pre->val < num) pre->right = node; - else pre->left = node; - return node; - } + [class]{BinarySearchTree}-[func]{insert} ``` === "Python" @@ -465,53 +431,9 @@ comments: true === "C++" ```cpp title="binary_search_tree.cpp" - /* 删除结点 */ - TreeNode* remove(int num) { - // 若树为空,直接提前返回 - if (root == nullptr) return nullptr; - TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 - while (cur != nullptr) { - // 找到待删除结点,跳出循环 - if (cur->val == num) break; - pre = cur; - // 待删除结点在 cur 的右子树中 - if (cur->val < num) cur = cur->right; - // 待删除结点在 cur 的左子树中 - else cur = cur->left; - } - // 若无待删除结点,则直接返回 - if (cur == nullptr) return nullptr; - // 子结点数量 = 0 or 1 - if (cur->left == nullptr || cur->right == nullptr) { - // 当子结点数量 = 0 / 1 时, child = nullptr / 该子结点 - TreeNode* child = cur->left != nullptr ? cur->left : cur->right; - // 删除结点 cur - if (pre->left == cur) pre->left = child; - else pre->right = child; - } - // 子结点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个结点 - TreeNode* nex = getInOrderNext(cur->right); - int tmp = nex->val; - // 递归删除结点 nex - remove(nex->val); - // 将 nex 的值复制给 cur - cur->val = tmp; - } - return cur; - } + [class]{BinarySearchTree}-[func]{remove} - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ - TreeNode* getInOrderNext(TreeNode* root) { - if (root == nullptr) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root->left != nullptr) { - root = root->left; - } - return root; - } + [class]{BinarySearchTree}-[func]{getInOrderNext} ``` === "Python" diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index 895d4852a..f7eb1a80b 100755 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -27,24 +27,7 @@ comments: true === "C++" ```cpp title="binary_tree_bfs.cpp" - /* 层序遍历 */ - vector hierOrder(TreeNode* root) { - // 初始化队列,加入根结点 - queue queue; - queue.push(root); - // 初始化一个列表,用于保存遍历序列 - vector vec; - while (!queue.empty()) { - TreeNode* node = queue.front(); - queue.pop(); // 队列出队 - vec.push_back(node->val); // 保存结点值 - if (node->left != nullptr) - queue.push(node->left); // 左子结点入队 - if (node->right != nullptr) - queue.push(node->right); // 右子结点入队 - } - return vec; - } + [class]{}-[func]{hierOrder} ``` === "Python" @@ -218,32 +201,11 @@ comments: true === "C++" ```cpp title="binary_tree_dfs.cpp" - /* 前序遍历 */ - void preOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:根结点 -> 左子树 -> 右子树 - vec.push_back(root->val); - preOrder(root->left); - preOrder(root->right); - } - - /* 中序遍历 */ - void inOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 根结点 -> 右子树 - inOrder(root->left); - vec.push_back(root->val); - inOrder(root->right); - } - - /* 后序遍历 */ - void postOrder(TreeNode* root) { - if (root == nullptr) return; - // 访问优先级:左子树 -> 右子树 -> 根结点 - postOrder(root->left); - postOrder(root->right); - vec.push_back(root->val); - } + [class]{}-[func]{preOrder} + + [class]{}-[func]{inOrder} + + [class]{}-[func]{postOrder} ``` === "Python" diff --git a/docs/utils/build_markdown.py b/docs/utils/build_markdown.py index 41078af16..64a33636e 100755 --- a/docs/utils/build_markdown.py +++ b/docs/utils/build_markdown.py @@ -12,6 +12,7 @@ import glob import shutil from docs.utils.extract_code_python import ExtractCodeBlocksPython from docs.utils.extract_code_java import ExtractCodeBlocksJava +from docs.utils.extract_code_cpp import ExtractCodeBlocksCpp def build_markdown(md_path): @@ -22,10 +23,12 @@ def build_markdown(md_path): file_pattern = re.compile(r'\s*```(\w+)\s+title="(.+)"') src_pattern = re.compile(r'\s*\[class\]\{(.*?)\}-\[func\]\{(.*?)\}') - for i in range(len(lines)): + i = 0 + while i < len(lines): # Find the line target to the source codes src_match = src_pattern.match(lines[i]) if src_match is None: + i += 1 continue for j in range(i - 1, -1 ,-1): file_match = file_pattern.match(lines[j]) @@ -70,6 +73,8 @@ def build_markdown(md_path): func_block = class_dict["funcs"][func_label] for code_line in func_block["block"][::-1]: lines.insert(header_line, code_line) + + i += 1 with open(md_path.replace("docs/", "build/"), "w") as f: f.writelines(lines) @@ -79,6 +84,7 @@ def build_markdown(md_path): extractor_dict = { "java": ExtractCodeBlocksJava(), "python": ExtractCodeBlocksPython(), + "cpp": ExtractCodeBlocksCpp(), } diff --git a/docs/utils/extract_code_cpp.py b/docs/utils/extract_code_cpp.py new file mode 100644 index 000000000..9d0deff2e --- /dev/null +++ b/docs/utils/extract_code_cpp.py @@ -0,0 +1,28 @@ +""" +File: extract_code_python.py +Created Time: 2023-02-07 +Author: Krahets (krahets@163.com) +""" + +import re +import glob +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.dirname(osp.abspath(__file__))))) +from docs.utils.extract_code_java import ExtractCodeBlocksJava + + +class ExtractCodeBlocksCpp(ExtractCodeBlocksJava): + def __init__(self) -> None: + super().__init__() + + # Pattern to match function names and class names + self.func_pattern = r'(\s*)(static|)\s*(|\S+)\s*(\w+)(\(.*\))\s+\{' + self.class_pattern = r'(public|)\s*(class|struct)\s+(\w+)\s*\{' + + self.func_pattern_keys = ["total", "ind", "static", "return", "label", "args"] + self.class_pattern_keys = ["total", "scope", "type", "label"] + + +# for code_path in glob.glob("codes/cpp/chapter_*/my_heap.cpp"): +# ext = ExtractCodeBlocksCpp() +# ext.extract(code_path) diff --git a/docs/utils/extract_code_java.py b/docs/utils/extract_code_java.py index 739297dd9..f6be67990 100644 --- a/docs/utils/extract_code_java.py +++ b/docs/utils/extract_code_java.py @@ -5,19 +5,27 @@ Author: Krahets (krahets@163.com) """ import re -import os -import os.path as osp +import glob +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.dirname(osp.abspath(__file__))))) class ExtractCodeBlocksJava: def __init__(self) -> None: - self.langs = ["java"] + self.ind = 4 + # Pattern to match function names and class names - self.func_pattern = r'(\s+)(public|private|)\s*(static|)\s*(\S+)\s+(\w+)(\(.*\))\s+\{' + self.func_pattern = r'(\s*)(public|private|)\s*(static|)\s*(\S+)\s+(\w+)(\(.*\))\s+\{' self.class_pattern = r'(public|)\s*class\s+(\w+)\s*\{' + + self.func_pattern_keys = ["total", "ind", "scope", "static", "return", "label", "args"] + self.class_pattern_keys = ["total", "scope", "label"] + # Pattern to match the start and end of a block self.block_end_pattern = '^\s{ind}\}' self.block_start_pattern = '^\s{ind}\/\*.+\*\/' + self.block_start_shift = 0 + self.block_end_shift = 0 def extract(self, file_path): """ @@ -53,17 +61,17 @@ class ExtractCodeBlocksJava: # Search the code for i in range(header_line + 1, len(self.lines)): if re.match(block_end_pattern, self.lines[i]) is not None: - end_line = i + end_line = i + self.block_end_shift break # Search the header comment for i in range(header_line - 1, -1, -1): if re.search(block_start_pattern, self.lines[i]) is not None: - start_line = i + start_line = i + self.block_start_shift break return start_line, end_line, self.lines[start_line:end_line + 1] - def extract_function_blocks(self, indentation=4, start_line=-1, end_line=-1): + def extract_function_blocks(self, indentation=0, start_line=-1, end_line=-1): """ Extract all the functions with given indentation """ @@ -82,7 +90,7 @@ class ExtractCodeBlocksJava: if func_match is None: continue # The function should match the input indentation - if len(func_match.group(1)) != indentation: + if len(func_match.group(self.func_pattern_keys.index("ind"))) != indentation: continue header_line = line_num @@ -90,7 +98,7 @@ class ExtractCodeBlocksJava: start_line, end_line, func_block = self.search_block( header_line, indentation) # Construct the funcs dict - func_label = func_match.group(5) + func_label = func_match.group(self.func_pattern_keys.index("label")) funcs[func_label] = { "indentation": indentation, "line_number": { @@ -122,7 +130,7 @@ class ExtractCodeBlocksJava: start_line, end_line, class_block = self.search_block( header_line, 0) # Construct the classes dict - class_label = class_match.group(2) + class_label = class_match.group(self.class_pattern_keys.index("label")) classes[class_label] = { "indentation": 0, "line_number": { @@ -132,7 +140,7 @@ class ExtractCodeBlocksJava: }, "block": class_block, "funcs": self.extract_function_blocks( - indentation=4, start_line=start_line, end_line=end_line) + indentation=self.ind, start_line=start_line, end_line=end_line) } return classes @@ -144,7 +152,7 @@ class ExtractCodeBlocksJava: def remove_keyword(func): block = func["block"] header_line = func["line_number"]["header"] - \ - func["line_number"]["start"] + func["line_number"]["start"] block[header_line] = block[header_line] \ .replace("static ", "").replace("public ", "").replace("private ", "") for clas in classes.values(): diff --git a/docs/utils/extract_code_python.py b/docs/utils/extract_code_python.py index a44b50b7b..1618f87cf 100755 --- a/docs/utils/extract_code_python.py +++ b/docs/utils/extract_code_python.py @@ -5,130 +5,28 @@ Author: Krahets (krahets@163.com) """ import re -import os -import os.path as osp +import glob +import sys, os.path as osp +sys.path.append(osp.dirname(osp.dirname(osp.dirname(osp.abspath(__file__))))) +from docs.utils.extract_code_java import ExtractCodeBlocksJava -class ExtractCodeBlocksPython: + +class ExtractCodeBlocksPython(ExtractCodeBlocksJava): def __init__(self) -> None: - self.langs = ["python"] + super().__init__() + # Pattern to match function names and class names self.func_pattern = r'(\s*)def\s+(\w+)\s*\(' self.class_pattern = r'class\s+(\w+)' + + self.func_pattern_keys = ["total", "ind", "label"] + self.class_pattern_keys = ["total", "label"] + # Pattern to match the start and end of a block self.block_end_pattern = '^\s{0,ind}\S+.*\n' self.block_start_pattern = '^\s{ind}""".+' - - def extract(self, file_path): - """ - Extract classes and functions from a markdown document - """ - self.file_path = file_path - with open(file_path) as f: - self.lines = f.readlines() - self.content = "".join(self.lines) - - # Detect and extract all the classes and fucntions - classes = self.extract_class_blocks() - funcs = self.extract_function_blocks() - - self.post_process(classes, funcs) - - return { - "classes": classes, - "funcs": funcs, - } - - def search_block(self, header_line, indentation): - """ - Search class/function block given the header_line and indentation - """ - start_line, end_line = 0, len(self.lines) - - block_end_pattern = re.compile(self.block_end_pattern.replace("ind", str(indentation))) - block_start_pattern = re.compile(self.block_start_pattern.replace("ind", str(indentation))) - - # Search the code - for i in range(header_line + 1, len(self.lines)): - if re.match(block_end_pattern, self.lines[i]) is not None: - end_line = i - 1 - break - # Search the header comment - for i in range(header_line - 1, -1, -1): - if re.search(block_start_pattern, self.lines[i]) is not None: - start_line = i - break - - return start_line, end_line, self.lines[start_line:end_line + 1] - - - def extract_function_blocks(self, indentation=0, start_line=-1, end_line=-1): - """ - Extract all the functions with given indentation - """ - funcs = {} - - if start_line == -1: - start_line = 0 - if end_line == -1: - end_line = len(self.lines) - 1 - - func_pattern = re.compile(self.func_pattern) - - for line_num in range(start_line, end_line + 1): - # Search the function header - func_match = func_pattern.match(self.lines[line_num]) - if func_match is None: continue - # The function should match the input indentation - if len(func_match.group(1)) != indentation: continue - header_line = line_num - - # Search the block from the header line - start_line, end_line, func_block = self.search_block(header_line, indentation) - # Construct the funcs dict - func_label = func_match.group(2) - funcs[func_label] = { - "indentation": indentation, - "line_number": { - "start": start_line, - "end": end_line, - "header": header_line, - }, - "block": func_block, - } - - return funcs - - def extract_class_blocks(self): - """ - Extract all the classes with given indentation - """ - classes = {} - - class_pattern = re.compile(self.class_pattern) - - for line_num, line in enumerate(self.lines): - # Search the class header - class_match = class_pattern.match(line) - if class_match is None: continue - header_line = line_num - - # Search the block from the header line - start_line, end_line, class_block = self.search_block(header_line, 0) - # Construct the classes dict - class_label = class_match.group(1) - classes[class_label] = { - "indentation": 0, - "line_number": { - "start": start_line, - "end": end_line, - "header": header_line, - }, - "block": class_block, - "funcs": self.extract_function_blocks( - indentation=4, start_line=start_line, end_line=end_line) - } - - return classes + self.block_start_shift = 0 + self.block_end_shift = -1 def post_process(self, classes, funcs): """ @@ -154,4 +52,4 @@ class ExtractCodeBlocksPython: # ext = ExtractCodeBlocksPython() -# ext.extract("codes/python/chapter_array_and_linkedlist/my_list.py") \ No newline at end of file +# ext.extract("codes/python/chapter_array_and_linkedlist/my_list.py") diff --git a/site/404.html b/site/404.html deleted file mode 100644 index 3c6ca2b79..000000000 --- a/site/404.html +++ /dev/null @@ -1,1552 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- -

404 - Not found

- -
-
- - - - -
- - - -
- -
- - - - -
- -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/assets/images/favicon.png b/site/assets/images/favicon.png deleted file mode 100644 index 285fefc97..000000000 Binary files a/site/assets/images/favicon.png and /dev/null differ diff --git a/site/assets/images/logo.png b/site/assets/images/logo.png deleted file mode 100644 index 6fc6dab37..000000000 Binary files a/site/assets/images/logo.png and /dev/null differ diff --git a/site/assets/images/profile.png b/site/assets/images/profile.png deleted file mode 100644 index aeabdeb91..000000000 Binary files a/site/assets/images/profile.png and /dev/null differ diff --git a/site/assets/javascripts/bundle.6df46069.min.js b/site/assets/javascripts/bundle.6df46069.min.js deleted file mode 100644 index 02c8d5fb7..000000000 --- a/site/assets/javascripts/bundle.6df46069.min.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict";(()=>{var Hi=Object.create;var xr=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var $i=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Ii=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty,an=Object.prototype.propertyIsEnumerable;var on=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Er.call(t,r)&&on(e,r,t[r]);if(Ht)for(var r of Ht(t))an.call(t,r)&&on(e,r,t[r]);return e};var sn=(e,t)=>{var r={};for(var n in e)Er.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Ht)for(var n of Ht(e))t.indexOf(n)<0&&an.call(e,n)&&(r[n]=e[n]);return r};var Pt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Fi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $i(t))!Er.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=Pi(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Hi(Ii(e)):{},Fi(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var fn=Pt((wr,cn)=>{(function(e,t){typeof wr=="object"&&typeof cn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function f(T){var Ke=T.type,De=T.tagName;return!!(De==="INPUT"&&a[Ke]&&!T.readOnly||De==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function c(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&c(r.activeElement),n=!0)}function m(T){n=!1}function d(T){s(T.target)&&(n||f(T.target))&&c(T.target)}function h(T){s(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),B())}function B(){document.addEventListener("mousemove",z),document.addEventListener("mousedown",z),document.addEventListener("mouseup",z),document.addEventListener("pointermove",z),document.addEventListener("pointerdown",z),document.addEventListener("pointerup",z),document.addEventListener("touchmove",z),document.addEventListener("touchstart",z),document.addEventListener("touchend",z)}function ne(){document.removeEventListener("mousemove",z),document.removeEventListener("mousedown",z),document.removeEventListener("mouseup",z),document.removeEventListener("pointermove",z),document.removeEventListener("pointerdown",z),document.removeEventListener("pointerup",z),document.removeEventListener("touchmove",z),document.removeEventListener("touchstart",z),document.removeEventListener("touchend",z)}function z(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,ne())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),B(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var un=Pt(Sr=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},a=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(ne,z){d.append(z,ne)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+c+" due to "+T)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,B=!0,ne=this;["append","delete","set"].forEach(function(T){var Ke=h[T];h[T]=function(){Ke.apply(h,arguments),v&&(B=!1,ne.search=h.toString(),B=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var z=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==z&&(z=this.search,B&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},a=i.prototype,s=function(f){Object.defineProperty(a,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){s(f)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr)});var Qr=Pt((Lt,Kr)=>{/*! - * clipboard.js v2.0.11 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */(function(t,r){typeof Lt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ki}});var a=i(279),s=i.n(a),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var w=p()(O);return m("cut"),w},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",w=document.createElement("textarea");w.style.fontSize="12pt",w.style.border="0",w.style.padding="0",w.style.margin="0",w.style.position="absolute",w.style[O?"right":"left"]="-9999px";var k=window.pageYOffset||document.documentElement.scrollTop;return w.style.top="".concat(k,"px"),w.setAttribute("readonly",""),w.value=j,w}var B=function(O,w){var k=v(O);w.container.appendChild(k);var F=p()(k);return m("copy"),k.remove(),F},ne=function(O){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},k="";return typeof O=="string"?k=B(O,w):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?k=B(O.value,w):(k=p()(O),m("copy")),k},z=ne;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(w){return typeof w}:T=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},T(j)}var Ke=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},w=O.action,k=w===void 0?"copy":w,F=O.container,q=O.target,Le=O.text;if(k!=="copy"&&k!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(k==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(k==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Le)return z(Le,{container:F});if(q)return k==="cut"?h(q):z(q,{container:F})},De=Ke;function Fe(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(w){return typeof w}:Fe=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},Fe(j)}function Ti(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function nn(j,O){for(var w=0;w0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Fe(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var q=this;this.listener=c()(F,"click",function(Le){return q.onClick(Le)})}},{key:"onClick",value:function(F){var q=F.delegateTarget||F.currentTarget,Le=this.action(q)||"copy",kt=De({action:Le,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Le,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return yr("action",F)}},{key:"defaultTarget",value:function(F){var q=yr("target",F);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(F){return yr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return z(F,q)}},{key:"cut",value:function(F){return h(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof F=="string"?[F]:F,Le=!!document.queryCommandSupported;return q.forEach(function(kt){Le=Le&&!!document.queryCommandSupported(kt)}),Le}}]),w}(s()),ki=Ri},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,f){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(f))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof m=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(m))return c(m,d,h);if(a.nodeList(m))return u(m,d,h);if(a.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return s(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),a=f.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var f=this;function c(){f.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=s.length;for(f;f{"use strict";/*! - * escape-html - * Copyright(c) 2012-2013 TJ Holowaychuk - * Copyright(c) 2015 Andreas Lubbe - * Copyright(c) 2015 Tiancheng "Timothy" Gu - * MIT Licensed - */var is=/["'&<>]/;Jo.exports=as;function as(e){var t=""+e,r=is.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||s(m,d)})})}function s(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof Ze?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){s("next",m)}function u(m){s("throw",m)}function p(m,d){m(d),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof xe=="function"?xe(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,f){a=e[i](a),o(s,f,a.done,a.value)})}}function o(i,a,s,f){Promise.resolve(f).then(function(c){i({value:c,done:s})},a)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: -`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` - `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var je=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=xe(a),f=s.next();!f.done;f=s.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof It?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=xe(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{dn(h)}catch(v){i=i!=null?i:[],v instanceof It?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)dn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Or=je.EMPTY;function Ft(e){return e instanceof je||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function dn(e){A(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Or:(this.currentObservers=null,s.push(r),new je(function(){n.currentObservers=null,Ve(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new wn(r,n)},t}(U);var wn=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Or},t}(E);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,f=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Wt);var On=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Dt);var we=new On(Tn);var R=new U(function(e){return e.complete()});function Vt(e){return e&&A(e.schedule)}function kr(e){return e[e.length-1]}function Qe(e){return A(kr(e))?e.pop():void 0}function Se(e){return Vt(kr(e))?e.pop():void 0}function zt(e,t){return typeof kr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Nt(e){return A(e==null?void 0:e.then)}function qt(e){return A(e[ft])}function Kt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Yt=Ki();function Gt(e){return A(e==null?void 0:e[Yt])}function Bt(e){return ln(this,arguments,function(){var r,n,o,i;return $t(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,Ze(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,Ze(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,Ze(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Jt(e){return A(e==null?void 0:e.getReader)}function $(e){if(e instanceof U)return e;if(e!=null){if(qt(e))return Qi(e);if(pt(e))return Yi(e);if(Nt(e))return Gi(e);if(Kt(e))return _n(e);if(Gt(e))return Bi(e);if(Jt(e))return Ji(e)}throw Qt(e)}function Qi(e){return new U(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Yi(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?_(function(o,i){return e(o,i,n)}):de,Oe(1),r?Pe(t):zn(function(){return new Zt}))}}function Nn(){for(var e=[],t=0;t=2,!0))}function ue(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new E}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,f=s===void 0?!0:s;return function(c){var u,p,m,d=0,h=!1,v=!1,B=function(){p==null||p.unsubscribe(),p=void 0},ne=function(){B(),u=m=void 0,h=v=!1},z=function(){var T=u;ne(),T==null||T.unsubscribe()};return g(function(T,Ke){d++,!v&&!h&&B();var De=m=m!=null?m:r();Ke.add(function(){d--,d===0&&!v&&!h&&(p=jr(z,f))}),De.subscribe(Ke),!u&&d>0&&(u=new tt({next:function(Fe){return De.next(Fe)},error:function(Fe){v=!0,B(),p=jr(ne,o,Fe),De.error(Fe)},complete:function(){h=!0,B(),p=jr(ne,a),De.complete()}}),$(T).subscribe(u))})(c)}}function jr(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function V(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function rr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(He(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),N(e===_e()),G())}function Je(e){return{x:e.offsetLeft,y:e.offsetTop}}function Yn(e){return L(b(window,"load"),b(window,"resize")).pipe(Re(0,we),l(()=>Je(e)),N(Je(e)))}function nr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Re(0,we),l(()=>nr(e)),N(nr(e)))}var Bn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),xa?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ya.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Jn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Zn=typeof WeakMap!="undefined"?new WeakMap:new Bn,eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Ea.getInstance(),n=new Ra(t,r,this);Zn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){eo.prototype[e]=function(){var t;return(t=Zn.get(this))[e].apply(t,arguments)}});var ka=function(){return typeof or.ResizeObserver!="undefined"?or.ResizeObserver:eo}(),to=ka;var ro=new E,Ha=I(()=>H(new to(e=>{for(let t of e)ro.next(t)}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){return Ha.pipe(S(t=>t.observe(e)),x(t=>ro.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(()=>he(e)))),N(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function sr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var no=new E,Pa=I(()=>H(new IntersectionObserver(e=>{for(let t of e)no.next(t)},{threshold:0}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function cr(e){return Pa.pipe(S(t=>t.observe(e)),x(t=>no.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function oo(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),G())}var fr={drawer:V("[data-md-toggle=drawer]"),search:V("[data-md-toggle=search]")};function io(e){return fr[e].checked}function qe(e,t){fr[e].checked!==t&&fr[e].click()}function Ue(e){let t=fr[e];return b(t,"change").pipe(l(()=>t.checked),N(t.checked))}function $a(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ia(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(N(!1))}function ao(){let e=b(window,"keydown").pipe(_(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:io("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),_(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!$a(n,r)}return!0}),ue());return Ia().pipe(x(t=>t?R:e))}function Me(){return new URL(location.href)}function ot(e){location.href=e.href}function so(){return new E}function co(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)co(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)co(n,o);return n}function ur(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function fo(){return location.hash.substring(1)}function uo(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Fa(){return b(window,"hashchange").pipe(l(fo),N(fo()),_(e=>e.length>0),J(1))}function po(){return Fa().pipe(l(e=>ce(`[id="${e}"]`)),_(e=>typeof e!="undefined"))}function Nr(e){let t=matchMedia(e);return er(r=>t.addListener(()=>r(t.matches))).pipe(N(t.matches))}function lo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(N(e.matches))}function qr(e,t){return e.pipe(x(r=>r?t():R))}function pr(e,t={credentials:"same-origin"}){return pe(fetch(`${e}`,t)).pipe(fe(()=>R),x(r=>r.status!==200?Tt(()=>new Error(r.statusText)):H(r)))}function We(e,t){return pr(e,t).pipe(x(r=>r.json()),J(1))}function mo(e,t){let r=new DOMParser;return pr(e,t).pipe(x(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function lr(e){let t=M("script",{src:e});return I(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(x(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),C(()=>document.head.removeChild(t)),Oe(1))))}function ho(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function bo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(ho),N(ho()))}function vo(){return{width:innerWidth,height:innerHeight}}function go(){return b(window,"resize",{passive:!0}).pipe(l(vo),N(vo()))}function yo(){return Q([bo(),go()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function mr(e,{viewport$:t,header$:r}){let n=t.pipe(X("size")),o=Q([n,r]).pipe(l(()=>Je(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:a,size:s},{x:f,y:c}])=>({offset:{x:a.x-f,y:a.y-c+i},size:s})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(a=>{let s=document.createElement("script");s.src=i,s.onload=a,document.body.appendChild(s)})),Promise.resolve())}var r=class{constructor(n){this.url=n,this.onerror=null,this.onmessage=null,this.onmessageerror=null,this.m=a=>{a.source===this.w&&(a.stopImmediatePropagation(),this.dispatchEvent(new MessageEvent("message",{data:a.data})),this.onmessage&&this.onmessage(a))},this.e=(a,s,f,c,u)=>{if(s===this.url.toString()){let p=new ErrorEvent("error",{message:a,filename:s,lineno:f,colno:c,error:u});this.dispatchEvent(p),this.onerror&&this.onerror(p)}};let o=new EventTarget;this.addEventListener=o.addEventListener.bind(o),this.removeEventListener=o.removeEventListener.bind(o),this.dispatchEvent=o.dispatchEvent.bind(o);let i=document.createElement("iframe");i.width=i.height=i.frameBorder="0",document.body.appendChild(this.iframe=i),this.w.document.open(),this.w.document.write(` - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

4.1. 数组

-

「数组 Array」是一种将 相同类型元素 存储在 连续内存空间 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。

-

array_definition

-

Fig. 数组定义与存储方式

- -
-

Note

-

观察上图,我们发现 数组首元素的索引为 \(0\) 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 \(1\) 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。

-
-

数组有多种初始化写法。根据实际需要,选代码最短的那一种就好。

-
-
-
-
array.java
/* 初始化数组 */
-int[] arr = new int[5]; // { 0, 0, 0, 0, 0 }
-int[] nums = { 1, 3, 2, 5, 4 };
-
-
-
-
array.cpp
/* 初始化数组 */
-int* arr = new int[5];
-int* nums = new int[5] { 1, 3, 2, 5, 4 };
-
-
-
-
array.py
""" 初始化数组 """
-arr = [0] * 5  # [ 0, 0, 0, 0, 0 ]
-nums = [1, 3, 2, 5, 4]  
-
-
-
-
array.go
/* 初始化数组 */
-var arr [5]int
-// 在 Go 中,指定长度时([5]int)为数组,不指定长度时([]int)为切片
-// 由于 Go 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度
-// 为了方便实现扩容 extend() 方法,以下将切片(Slice)看作数组(Array)
-nums := []int{1, 3, 2, 5, 4}
-
-
-
-
array.js
/* 初始化数组 */
-var arr = new Array(5).fill(0);
-var nums = [1, 3, 2, 5, 4];
-
-
-
-
array.ts
/* 初始化数组 */
-let arr: number[] = new Array(5).fill(0);
-let nums: number[] = [1, 3, 2, 5, 4];
-
-
-
-
array.c

-
-
-
-
array.cs
/* 初始化数组 */
-int[] arr = new int[5]; // { 0, 0, 0, 0, 0 }
-int[] nums = { 1, 3, 2, 5, 4 };
-
-
-
-
array.swift
/* 初始化数组 */
-let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
-let nums = [1, 3, 2, 5, 4]
-
-
-
-
array.zig
// 初始化数组
-var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 }
-var nums = [_]i32{ 1, 3, 2, 5, 4 };
-
-
-
-
-

4.1.1. 数组优点

-

在数组中访问元素非常高效。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。

-

array_memory_location_calculation

-

Fig. 数组元素的内存地址计算

- -
// 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引
-elementAddr = firtstElementAddr + elementLength * elementIndex
-
-

为什么数组元素索引从 0 开始编号? 根据地址计算公式,索引本质上表示的是内存地址偏移量,首个元素的地址偏移量是 \(0\) ,那么索引是 \(0\) 也就很自然了。

-

访问元素的高效性带来了许多便利。例如,我们可以在 \(O(1)\) 时间内随机获取一个数组中的元素。

-
-
-
-
array.java
/* 随机返回一个数组元素 */
-int randomAccess(int[] nums) {
-    // 在区间 [0, nums.length) 中随机抽取一个数字
-    int randomIndex = ThreadLocalRandom.current().
-                      nextInt(0, nums.length);
-    // 获取并返回随机元素
-    int randomNum = nums[randomIndex];
-    return randomNum;
-}
-
-
-
-
array.cpp
/* 随机返回一个数组元素 */
-int randomAccess(int* nums, int size) {
-    // 在区间 [0, size) 中随机抽取一个数字
-    int randomIndex = rand() % size;
-    // 获取并返回随机元素
-    int randomNum = nums[randomIndex];
-    return randomNum;
-}
-
-
-
-
array.py
""" 随机访问元素 """
-def random_access(nums):
-    # 在区间 [0, len(nums)-1] 中随机抽取一个数字
-    random_index = random.randint(0, len(nums) - 1)
-    # 获取并返回随机元素
-    random_num = nums[random_index]
-    return random_num
-
-
-
-
array.go
/* 随机返回一个数组元素 */
-func randomAccess(nums []int) (randomNum int) {
-    // 在区间 [0, nums.length) 中随机抽取一个数字
-    randomIndex := rand.Intn(len(nums))
-    // 获取并返回随机元素
-    randomNum = nums[randomIndex]
-    return
-}
-
-
-
-
array.js
/* 随机返回一个数组元素 */
-function randomAccess(nums) {
-    // 在区间 [0, nums.length) 中随机抽取一个数字
-    const random_index = Math.floor(Math.random() * nums.length);
-    // 获取并返回随机元素
-    const random_num = nums[random_index];
-    return random_num;
-}
-
-
-
-
array.ts
/* 随机返回一个数组元素 */
-function randomAccess(nums: number[]): number {
-    // 在区间 [0, nums.length) 中随机抽取一个数字
-    const random_index = Math.floor(Math.random() * nums.length);
-    // 获取并返回随机元素
-    const random_num = nums[random_index];
-    return random_num;
-}
-
-
-
-
array.c

-
-
-
-
array.cs
/* 随机返回一个数组元素 */
-int RandomAccess(int[] nums)
-{
-    Random random=new();
-    // 在区间 [0, nums.Length) 中随机抽取一个数字
-    int randomIndex = random.Next(nums.Length);
-    // 获取并返回随机元素
-    int randomNum = nums[randomIndex];
-    return randomNum;
-}
-
-
-
-
array.swift
/* 随机返回一个数组元素 */
-func randomAccess(nums: [Int]) -> Int {
-    // 在区间 [0, nums.count) 中随机抽取一个数字
-    let randomIndex = nums.indices.randomElement()!
-    // 获取并返回随机元素
-    let randomNum = nums[randomIndex]
-    return randomNum
-}
-
-
-
-
array.zig
// 随机返回一个数组元素
-pub fn randomAccess(nums: []i32) i32 {
-    // 在区间 [0, nums.len) 中随机抽取一个整数
-    var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len);
-    // 获取并返回随机元素
-    var randomNum = nums[randomIndex];
-    return randomNum;
-}
-
-
-
-
-

4.1.2. 数组缺点

-

数组在初始化后长度不可变。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。

-
-
-
-
array.java
/* 扩展数组长度 */
-int[] extend(int[] nums, int enlarge) {
-    // 初始化一个扩展长度后的数组
-    int[] res = new int[nums.length + enlarge];
-    // 将原数组中的所有元素复制到新数组
-    for (int i = 0; i < nums.length; i++) {
-        res[i] = nums[i];
-    }
-    // 返回扩展后的新数组
-    return res;
-}
-
-
-
-
array.cpp
/* 扩展数组长度 */
-int* extend(int* nums, int size, int enlarge) {
-    // 初始化一个扩展长度后的数组
-    int* res = new int[size + enlarge];
-    // 将原数组中的所有元素复制到新数组
-    for (int i = 0; i < size; i++) {
-        res[i] = nums[i];
-    }
-    // 释放内存
-    delete[] nums;
-    // 返回扩展后的新数组
-    return res;
-}
-
-
-
-
array.py
""" 扩展数组长度 """
-# 请注意,Python 的 list 是动态数组,可以直接扩展
-# 为了方便学习,本函数将 list 看作是长度不可变的数组
-def extend(nums, enlarge):
-    # 初始化一个扩展长度后的数组
-    res = [0] * (len(nums) + enlarge)
-    # 将原数组中的所有元素复制到新数组
-    for i in range(len(nums)):
-        res[i] = nums[i]
-    # 返回扩展后的新数组
-    return res
-
-
-
-
array.go
/* 扩展数组长度 */
-func extend(nums []int, enlarge int) []int {
-    // 初始化一个扩展长度后的数组
-    res := make([]int, len(nums)+enlarge)
-    // 将原数组中的所有元素复制到新数组
-    for i, num := range nums {
-        res[i] = num
-    }
-    // 返回扩展后的新数组
-    return res
-}
-
-
-
-
array.js
/* 扩展数组长度 */
-function extend(nums, enlarge) {
-    // 初始化一个扩展长度后的数组
-    const res = new Array(nums.length + enlarge).fill(0);
-    // 将原数组中的所有元素复制到新数组
-    for (let i = 0; i < nums.length; i++) {
-        res[i] = nums[i];
-    }
-    // 返回扩展后的新数组
-    return res;
-}
-
-
-
-
array.ts
/* 扩展数组长度 */
-function extend(nums: number[], enlarge: number): number[] {
-    // 初始化一个扩展长度后的数组
-    const res = new Array(nums.length + enlarge).fill(0);
-    // 将原数组中的所有元素复制到新数组
-    for (let i = 0; i < nums.length; i++) {
-        res[i] = nums[i];
-    }
-    // 返回扩展后的新数组
-    return res;
-}
-
-
-
-
array.c

-
-
-
-
array.cs
/* 扩展数组长度 */
-int[] Extend(int[] nums, int enlarge)
-{
-    // 初始化一个扩展长度后的数组
-    int[] res = new int[nums.Length + enlarge];
-    // 将原数组中的所有元素复制到新数组
-    for (int i = 0; i < nums.Length; i++)
-    {
-        res[i] = nums[i];
-    }
-    // 返回扩展后的新数组
-    return res;
-}
-
-
-
-
array.swift
/* 扩展数组长度 */
-func extend(nums: [Int], enlarge: Int) -> [Int] {
-    // 初始化一个扩展长度后的数组
-    var res = Array(repeating: 0, count: nums.count + enlarge)
-    // 将原数组中的所有元素复制到新数组
-    for i in nums.indices {
-        res[i] = nums[i]
-    }
-    // 返回扩展后的新数组
-    return res
-}
-
-
-
-
array.zig
// 扩展数组长度
-pub fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 {
-    // 初始化一个扩展长度后的数组
-    var res = try mem_allocator.alloc(i32, nums.len + enlarge);
-    std.mem.set(i32, res, 0);
-    // 将原数组中的所有元素复制到新数组
-    std.mem.copy(i32, res, nums);
-    // 返回扩展后的新数组
-    return res;
-}
-
-
-
-
-

数组中插入或删除元素效率低下。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点:

-
    -
  • 时间复杂度高:数组的插入和删除的平均时间复杂度均为 \(O(N)\) ,其中 \(N\) 为数组长度。
  • -
  • 丢失元素:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。
  • -
  • 内存浪费:我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。
  • -
-

array_insert_remove_element

-

Fig. 在数组中插入与删除元素

- -
-
-
-
array.java
/* 在数组的索引 index 处插入元素 num */
-void insert(int[] nums, int num, int index) {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for (int i = nums.length - 1; i > index; i--) {
-        nums[i] = nums[i - 1];
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num;
-}
-
-/* 删除索引 index 处元素 */
-void remove(int[] nums, int index) {
-    // 把索引 index 之后的所有元素向前移动一位
-    for (int i = index; i < nums.length - 1; i++) {
-        nums[i] = nums[i + 1];
-    }
-}
-
-
-
-
array.cpp
/* 在数组的索引 index 处插入元素 num */
-void insert(int* nums, int size, int num, int index) {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for (int i = size - 1; i > index; i--) {
-        nums[i] = nums[i - 1];
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num;
-}
-
-/* 删除索引 index 处元素 */
-void remove(int* nums, int size, int index) {
-    // 把索引 index 之后的所有元素向前移动一位
-    for (int i = index; i < size - 1; i++) {
-        nums[i] = nums[i + 1];
-    }
-}
-
-
-
-
array.py
""" 在数组的索引 index 处插入元素 num """
-def insert(nums, num, index):
-    # 把索引 index 以及之后的所有元素向后移动一位
-    for i in range(len(nums) - 1, index, -1):
-        nums[i] = nums[i - 1]
-    # 将 num 赋给 index 处元素
-    nums[index] = num
-
-""" 删除索引 index 处元素 """
-def remove(nums, index):
-    # 把索引 index 之后的所有元素向前移动一位
-    for i in range(index, len(nums) - 1):
-        nums[i] = nums[i + 1]
-
-
-
-
array.go
/* 在数组的索引 index 处插入元素 num */
-func insert(nums []int, num int, index int) {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for i := len(nums) - 1; i > index; i-- {
-        nums[i] = nums[i-1]
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num
-}
-
-/* 删除索引 index 处元素 */
-func remove(nums []int, index int) {
-    // 把索引 index 之后的所有元素向前移动一位
-    for i := index; i < len(nums)-1; i++ {
-        nums[i] = nums[i+1]
-    }
-}
-
-
-
-
array.js
/* 在数组的索引 index 处插入元素 num */
-function insert(nums, num, index) {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for (let i = nums.length - 1; i > index; i--) {
-        nums[i] = nums[i - 1];
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num;
-}
-
-/* 删除索引 index 处元素 */
-function remove(nums, index) {
-    // 把索引 index 之后的所有元素向前移动一位
-    for (let i = index; i < nums.length - 1; i++) {
-        nums[i] = nums[i + 1];
-    }
-}
-
-
-
-
array.ts
/* 在数组的索引 index 处插入元素 num */
-function insert(nums: number[], num: number, index: number): void {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for (let i = nums.length - 1; i > index; i--) {
-        nums[i] = nums[i - 1];
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num;
-}
-
-/* 删除索引 index 处元素 */
-function remove(nums: number[], index: number): void {
-    // 把索引 index 之后的所有元素向前移动一位
-    for (let i = index; i < nums.length - 1; i++) {
-        nums[i] = nums[i + 1];
-    }
-}
-
-
-
-
array.c

-
-
-
-
array.cs
/* 在数组的索引 index 处插入元素 num */
-void Insert(int[] nums, int num, int index)
-{
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for (int i = nums.Length - 1; i > index; i--)
-    {
-        nums[i] = nums[i - 1];
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num;
-}
-/* 删除索引 index 处元素 */
-void Remove(int[] nums, int index)
-{
-    // 把索引 index 之后的所有元素向前移动一位
-    for (int i = index; i < nums.Length - 1; i++)
-    {
-        nums[i] = nums[i + 1];
-    }
-}
-
-
-
-
array.swift
/* 在数组的索引 index 处插入元素 num */
-func insert(nums: inout [Int], num: Int, index: Int) {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) {
-        nums[i] = nums[i - 1]
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num
-}
-
-/* 删除索引 index 处元素 */
-func remove(nums: inout [Int], index: Int) {
-    let count = nums.count
-    // 把索引 index 之后的所有元素向前移动一位
-    for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) {
-        nums[i] = nums[i + 1]
-    }
-}
-
-
-
-
array.zig
// 在数组的索引 index 处插入元素 num
-pub fn insert(nums: []i32, num: i32, index: usize) void {
-    // 把索引 index 以及之后的所有元素向后移动一位
-    var i = nums.len - 1;
-    while (i > index) : (i -= 1) {
-        nums[i] = nums[i - 1];
-    }
-    // 将 num 赋给 index 处元素
-    nums[index] = num;
-}
-
-// 删除索引 index 处元素
-pub fn remove(nums: []i32, index: usize) void {
-    // 把索引 index 之后的所有元素向前移动一位
-    var i = index;
-    while (i < nums.len - 1) : (i += 1) {
-        nums[i] = nums[i + 1];
-    }
-}
-
-
-
-
-

4.1.3. 数组常用操作

-

数组遍历。以下介绍两种常用的遍历方法。

-
-
-
-
array.java
/* 遍历数组 */
-void traverse(int[] nums) {
-    int count = 0;
-    // 通过索引遍历数组
-    for (int i = 0; i < nums.length; i++) {
-        count++;
-    }
-    // 直接遍历数组
-    for (int num : nums) {
-        count++;
-    }
-}
-
-
-
-
array.cpp
/* 遍历数组 */
-void traverse(int* nums, int size) {
-    int count = 0;
-    // 通过索引遍历数组
-    for (int i = 0; i < size; i++) {
-        count++;
-    }
-}  
-
-
-
-
array.py
""" 遍历数组 """
-def traverse(nums):
-    count = 0
-    # 通过索引遍历数组
-    for i in range(len(nums)):
-        count += 1
-    # 直接遍历数组
-    for num in nums:
-        count += 1
-
-
-
-
array.go
/* 遍历数组 */
-func traverse(nums []int) {
-    count := 0
-    // 通过索引遍历数组
-    for i := 0; i < len(nums); i++ {
-        count++
-    }
-    // 直接遍历数组
-    for range nums {
-        count++
-    }
-}
-
-
-
-
array.js
/* 遍历数组 */
-function traverse(nums) {
-    let count = 0;
-    // 通过索引遍历数组
-    for (let i = 0; i < nums.length; i++) {
-        count++;
-    }
-    // 直接遍历数组
-    for (let num of nums) {
-        count += 1;
-    }
-}
-
-
-
-
array.ts
/* 遍历数组 */
-function traverse(nums: number[]): void {
-    let count = 0;
-    // 通过索引遍历数组
-    for (let i = 0; i < nums.length; i++) {
-        count++;
-    }
-    // 直接遍历数组
-    for(let num of nums){
-        count += 1;
-    }
-}
-
-
-
-
array.c

-
-
-
-
array.cs
/* 遍历数组 */
-void Traverse(int[] nums)
-{
-    int count = 0;
-    // 通过索引遍历数组
-    for (int i = 0; i < nums.Length; i++)
-    {
-        count++;
-    }
-    // 直接遍历数组
-    foreach (int num in nums)
-    {
-        count++;
-    }
-}
-
-
-
-
array.swift
/* 遍历数组 */
-func traverse(nums: [Int]) {
-    var count = 0
-    // 通过索引遍历数组
-    for _ in nums.indices {
-        count += 1
-    }
-    // 直接遍历数组
-    for _ in nums {
-        count += 1
-    }
-}
-
-
-
-
array.zig
// 遍历数组
-pub fn traverse(nums: []i32) void {
-    var count: i32 = 0;
-    // 通过索引遍历数组
-    var i: i32 = 0;
-    while (i < nums.len) : (i += 1) {
-        count += 1;
-    }
-    count = 0;
-    // 直接遍历数组
-    for (nums) |_| {
-        count += 1;
-    }
-}
-
-
-
-
-

数组查找。通过遍历数组,查找数组内的指定元素,并输出对应索引。

-
-
-
-
array.java
/* 在数组中查找指定元素 */
-int find(int[] nums, int target) {
-    for (int i = 0; i < nums.length; i++) {
-        if (nums[i] == target)
-            return i;
-    }
-    return -1;
-}
-
-
-
-
array.cpp
/* 在数组中查找指定元素 */
-int find(int* nums, int size, int target) {
-    for (int i = 0; i < size; i++) {
-        if (nums[i] == target)
-            return i;
-    }
-    return -1;
-}
-
-
-
-
array.py
""" 在数组中查找指定元素 """
-def find(nums, target):
-    for i in range(len(nums)):
-        if nums[i] == target:
-            return i
-    return -1
-
-
-
-
array.go
/* 在数组中查找指定元素 */
-func find(nums []int, target int) (index int) {
-    index = -1
-    for i := 0; i < len(nums); i++ {
-        if nums[i] == target {
-            index = i
-            break
-        }
-    }
-    return
-}
-
-
-
-
array.js
/* 在数组中查找指定元素 */
-function find(nums, target) {
-    for (let i = 0; i < nums.length; i++) {
-        if (nums[i] == target) return i;
-    }
-    return -1;
-}
-
-
-
-
array.ts
/* 在数组中查找指定元素 */
-function find(nums: number[], target: number): number {
-    for (let i = 0; i < nums.length; i++) {
-        if (nums[i] === target) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-
-
-
array.c

-
-
-
-
array.cs
/* 在数组中查找指定元素 */
-int Find(int[] nums, int target)
-{
-    for (int i = 0; i < nums.Length; i++)
-    {
-        if (nums[i] == target)
-            return i;
-    }
-    return -1;
-}
-
-
-
-
array.swift
/* 在数组中查找指定元素 */
-func find(nums: [Int], target: Int) -> Int {
-    for i in nums.indices {
-        if nums[i] == target {
-            return i
-        }
-    }
-    return -1
-}
-
-
-
-
array.zig
// 在数组中查找指定元素
-pub fn find(nums: []i32, target: i32) i32 {
-    for (nums) |num, i| {
-        if (num == target) return @intCast(i32, i);
-    }
-    return -1;
-}
-
-
-
-
-

4.1.4. 数组典型应用

-

随机访问。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。

-

二分查找。例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。

-

深度学习。神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png b/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png deleted file mode 100644 index ec04fe46c..000000000 Binary files a/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png and /dev/null differ diff --git a/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png b/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png deleted file mode 100644 index 66d82b4f2..000000000 Binary files a/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png and /dev/null differ diff --git a/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_remove_node.png b/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_remove_node.png deleted file mode 100644 index 3ab4da38c..000000000 Binary files a/site/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_remove_node.png and /dev/null differ diff --git a/site/chapter_array_and_linkedlist/linked_list/index.html b/site/chapter_array_and_linkedlist/linked_list/index.html deleted file mode 100644 index e2dce0c8c..000000000 --- a/site/chapter_array_and_linkedlist/linked_list/index.html +++ /dev/null @@ -1,2626 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4.2. 链表(LinkedList) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

4.2. 链表

-
-

引言

-

内存空间是所有程序的公共资源,排除已占用的内存,空闲内存往往是散落在内存各处的。我们知道,存储数组需要内存空间连续,当我们需要申请一个很大的数组时,系统不一定存在这么大的连续内存空间。而链表则更加灵活,不需要内存是连续的,只要剩余内存空间大小够用即可。

-
-

「链表 Linked List」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(一般称为结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加灵活,系统不必保证内存地址的连续性。

-

链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。

-

linkedlist_definition

-

Fig. 链表定义与存储方式

- -
-
-
-
/* 链表结点类 */
-class ListNode {
-    int val;        // 结点值
-    ListNode next;  // 指向下一结点的指针(引用)
-    ListNode(int x) { val = x; }  // 构造函数
-}
-
-
-
-
/* 链表结点结构体 */
-struct ListNode {
-    int val;         // 结点值
-    ListNode *next;  // 指向下一结点的指针(引用)
-    ListNode(int x) : val(x), next(nullptr) {}  // 构造函数
-};
-
-
-
-
""" 链表结点类 """ 
-class ListNode:
-    def __init__(self, x):
-        self.val = x      # 结点值
-        self.next = None  # 指向下一结点的指针(引用)
-
-
-
-
/* 链表结点结构体 */
-type ListNode struct {
-    Val  int       // 结点值
-    Next *ListNode // 指向下一结点的指针(引用)
-}
-
-// NewListNode 构造函数,创建一个新的链表
-func NewListNode(val int) *ListNode {
-    return &ListNode{
-        Val:  val,
-        Next: nil,
-    }
-}
-
-
-
-
/* 链表结点结构体 */
-class ListNode {
-    val;
-    next;
-    constructor(val, next) {
-        this.val = (val === undefined ? 0 : val);       // 结点值
-        this.next = (next === undefined ? null : next); // 指向下一结点的引用
-    }
-}
-
-
-
-
/* 链表结点结构体 */
-class ListNode {
-    val: number;
-    next: ListNode | null;
-    constructor(val?: number, next?: ListNode | null) {
-        this.val = val === undefined ? 0 : val;        // 结点值
-        this.next = next === undefined ? null : next;  // 指向下一结点的引用
-    }
-}
-
-
-
-

-
-
-
-
/* 链表结点类 */
-class ListNode
-{
-    int val;         // 结点值
-    ListNode next;   // 指向下一结点的引用
-    ListNode(int x) => val = x;  //构造函数
-}
-
-
-
-
/* 链表结点类 */
-class ListNode {
-    var val: Int // 结点值
-    var next: ListNode? // 指向下一结点的指针(引用)
-
-    init(x: Int) { // 构造函数
-        val = x
-    }
-}
-
-
-
-
// 链表结点类
-pub fn ListNode(comptime T: type) type {
-    return struct {
-        const Self = @This();
-
-        val: T = 0, // 结点值
-        next: ?*Self = null, // 指向下一结点的指针(引用)
-
-        // 构造函数
-        pub fn init(self: *Self, x: i32) void {
-            self.val = x;
-            self.next = null;
-        }
-    };
-}
-
-
-
-
-

尾结点指向什么? 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 null / nullptr / None 。在不引起歧义下,本书都使用 null 来表示空。

-

链表初始化方法。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。

-
-

Tip

-

我们通常将头结点当作链表的代称,例如头结点 head 和链表 head 实际上是同义的。

-
-
-
-
-
linked_list.java
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点 
-ListNode n0 = new ListNode(1);
-ListNode n1 = new ListNode(3);
-ListNode n2 = new ListNode(2);
-ListNode n3 = new ListNode(5);
-ListNode n4 = new ListNode(4);
-// 构建引用指向
-n0.next = n1;
-n1.next = n2;
-n2.next = n3;
-n3.next = n4;
-
-
-
-
linked_list.cpp
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点 
-ListNode* n0 = new ListNode(1);
-ListNode* n1 = new ListNode(3);
-ListNode* n2 = new ListNode(2);
-ListNode* n3 = new ListNode(5);
-ListNode* n4 = new ListNode(4);
-// 构建引用指向
-n0->next = n1;
-n1->next = n2;
-n2->next = n3;
-n3->next = n4;
-
-
-
-
linked_list.py
""" 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """
-# 初始化各个结点 
-n0 = ListNode(1)
-n1 = ListNode(3)
-n2 = ListNode(2)
-n3 = ListNode(5)
-n4 = ListNode(4)
-# 构建引用指向
-n0.next = n1
-n1.next = n2
-n2.next = n3
-n3.next = n4
-
-
-
-
linked_list.go
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点
-n0 := NewListNode(1)
-n1 := NewListNode(3)
-n2 := NewListNode(2)
-n3 := NewListNode(5)
-n4 := NewListNode(4)
-
-// 构建引用指向
-n0.Next = n1
-n1.Next = n2
-n2.Next = n3
-n3.Next = n4
-
-
-
-
linked_list.js
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点
-const n0 = new ListNode(1);
-const n1 = new ListNode(3);
-const n2 = new ListNode(2);
-const n3 = new ListNode(5);
-const n4 = new ListNode(4);
-// 构建引用指向
-n0.next = n1;
-n1.next = n2;
-n2.next = n3;
-n3.next = n4;
-
-
-
-
linked_list.ts
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点
-const n0 = new ListNode(1);
-const n1 = new ListNode(3);
-const n2 = new ListNode(2);
-const n3 = new ListNode(5);
-const n4 = new ListNode(4);
-// 构建引用指向
-n0.next = n1;
-n1.next = n2;
-n2.next = n3;
-n3.next = n4;
-
-
-
-
linked_list.c

-
-
-
-
linked_list.cs
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点 
-ListNode n0 = new ListNode(1);
-ListNode n1 = new ListNode(3);
-ListNode n2 = new ListNode(2);
-ListNode n3 = new ListNode(5);
-ListNode n4 = new ListNode(4);
-// 构建引用指向
-n0.next = n1;
-n1.next = n2;
-n2.next = n3;
-n3.next = n4;
-
-
-
-
linked_list.swift
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
-// 初始化各个结点
-let n0 = ListNode(x: 1)
-let n1 = ListNode(x: 3)
-let n2 = ListNode(x: 2)
-let n3 = ListNode(x: 5)
-let n4 = ListNode(x: 4)
-// 构建引用指向
-n0.next = n1
-n1.next = n2
-n2.next = n3
-n3.next = n4
-
-
-
-
linked_list.zig
// 初始化链表
-// 初始化各个结点 
-var n0 = inc.ListNode(i32){.val = 1};
-var n1 = inc.ListNode(i32){.val = 3};
-var n2 = inc.ListNode(i32){.val = 2};
-var n3 = inc.ListNode(i32){.val = 5};
-var n4 = inc.ListNode(i32){.val = 4};
-// 构建引用指向
-n0.next = &n1;
-n1.next = &n2;
-n2.next = &n3;
-n3.next = &n4;
-
-
-
-
-

4.2.1. 链表优点

-

在链表中,插入与删除结点的操作效率高。例如,如果想在链表中间的两个结点 A , B 之间插入一个新结点 P ,我们只需要改变两个结点指针即可,时间复杂度为 \(O(1)\) ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。

-

linkedlist_insert_remove_node

-

Fig. 在链表中插入与删除结点

- -
-
-
-
linked_list.java
/* 在链表的结点 n0 之后插入结点 P */
-void insert(ListNode n0, ListNode P) {
-    ListNode n1 = n0.next;
-    n0.next = P;
-    P.next = n1;
-}
-
-/* 删除链表的结点 n0 之后的首个结点 */
-void remove(ListNode n0) {
-    if (n0.next == null)
-        return;
-    // n0 -> P -> n1
-    ListNode P = n0.next;
-    ListNode n1 = P.next;
-    n0.next = n1;
-}
-
-
-
-
linked_list.cpp
/* 在链表的结点 n0 之后插入结点 P */
-void insert(ListNode* n0, ListNode* P) {
-    ListNode* n1 = n0->next;
-    n0->next = P;
-    P->next = n1;
-}
-
-/* 删除链表的结点 n0 之后的首个结点 */
-void remove(ListNode* n0) {
-    if (n0->next == nullptr)
-        return;
-    // n0 -> P -> n1
-    ListNode* P = n0->next;
-    ListNode* n1 = P->next;
-    n0->next = n1;
-    // 释放内存
-    delete P;
-}
-
-
-
-
linked_list.py
""" 在链表的结点 n0 之后插入结点 P """
-def insert(n0, P):
-    n1 = n0.next
-    n0.next = P
-    P.next = n1
-
-""" 删除链表的结点 n0 之后的首个结点 """
-def remove(n0):
-    if not n0.next:
-        return
-    # n0 -> P -> n1
-    P = n0.next
-    n1 = P.next
-    n0.next = n1
-
-
-
-
linked_list.go
/* 在链表的结点 n0 之后插入结点 P */
-func insert(n0 *ListNode, P *ListNode) {
-    n1 := n0.Next
-    n0.Next = P
-    P.Next = n1
-}
-
-/* 删除链表的结点 n0 之后的首个结点 */
-func removeNode(n0 *ListNode) {
-    if n0.Next == nil {
-        return
-    }
-    // n0 -> P -> n1
-    P := n0.Next
-    n1 := P.Next
-    n0.Next = n1
-}
-
-
-
-
linked_list.js
/* 在链表的结点 n0 之后插入结点 P */
-function insert(n0, P) {
-    let n1 = n0.next;
-    n0.next = P;
-    P.next = n1;
-}
-
-/* 删除链表的结点 n0 之后的首个结点 */
-function remove(n0) {
-    if (!n0.next)
-        return;
-    // n0 -> P -> n1
-    let P = n0.next;
-    let n1 = P.next;
-    n0.next = n1;
-}
-
-
-
-
linked_list.ts
/* 在链表的结点 n0 之后插入结点 P */
-function insert(n0: ListNode, P: ListNode): void {
-    const n1 = n0.next;
-    n0.next = P;
-    P.next = n1;
-}
-
-/* 删除链表的结点 n0 之后的首个结点 */
-function remove(n0: ListNode): void {
-    if (!n0.next) {
-        return;
-    }
-    // n0 -> P -> n1
-    const P = n0.next;
-    const n1 = P.next;
-    n0.next = n1;
-}
-
-
-
-
linked_list.c

-
-
-
-
linked_list.cs
// 在链表的结点 n0 之后插入结点 P
-void Insert(ListNode n0, ListNode P)
-{
-    ListNode n1 = n0.next;
-    n0.next = P;
-    P.next = n1;
-}
-
-// 删除链表的结点 n0 之后的首个结点
-void Remove(ListNode n0)
-{
-    if (n0.next == null)
-        return;
-    // n0 -> P -> n1
-    ListNode P = n0.next;
-    ListNode n1 = P.next;
-    n0.next = n1;
-}
-
-
-
-
linked_list.swift
/* 在链表的结点 n0 之后插入结点 P */
-func insert(n0: ListNode, P: ListNode) {
-    let n1 = n0.next
-    n0.next = P
-    P.next = n1
-}
-
-/* 删除链表的结点 n0 之后的首个结点 */
-func remove(n0: ListNode) {
-    if n0.next == nil {
-        return
-    }
-    // n0 -> P -> n1
-    let P = n0.next
-    let n1 = P?.next
-    n0.next = n1
-    P?.next = nil
-}
-
-
-
-
linked_list.zig
// 在链表的结点 n0 之后插入结点 P
-pub fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void {
-    var n1 = n0.?.next;
-    n0.?.next = P;
-    P.?.next = n1;
-}
-
-// 删除链表的结点 n0 之后的首个结点
-pub fn remove(n0: ?*inc.ListNode(i32)) void {
-    if (n0.?.next == null) return;
-    // n0 -> P -> n1
-    var P = n0.?.next;
-    var n1 = P.?.next;
-    n0.?.next = n1;
-}
-
-
-
-
-

4.2.2. 链表缺点

-

链表访问结点效率低。上节提到,数组可以在 \(O(1)\) 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 index (即第 index + 1 个)的结点,那么需要 index 次访问操作。

-
-
-
-
linked_list.java
/* 访问链表中索引为 index 的结点 */
-ListNode access(ListNode head, int index) {
-    for (int i = 0; i < index; i++) {
-        if (head == null)
-            return null;
-        head = head.next;
-    }
-    return head;
-}
-
-
-
-
linked_list.cpp
/* 访问链表中索引为 index 的结点 */
-ListNode* access(ListNode* head, int index) {
-    for (int i = 0; i < index; i++) {
-        if (head == nullptr)
-            return nullptr;
-        head = head->next;
-    }
-    return head;
-}
-
-
-
-
linked_list.py
""" 访问链表中索引为 index 的结点 """
-def access(head, index):
-    for _ in range(index):
-        if not head:
-            return None
-        head = head.next
-    return head
-
-
-
-
linked_list.go
/* 访问链表中索引为 index 的结点 */
-func access(head *ListNode, index int) *ListNode {
-    for i := 0; i < index; i++ {
-        if head == nil {
-            return nil
-        }
-        head = head.Next
-    }
-    return head
-}
-
-
-
-
linked_list.js
/* 访问链表中索引为 index 的结点 */
-function access(head, index) {
-    for (let i = 0; i < index; i++) {
-        if (!head)
-            return null;
-        head = head.next;
-    }
-    return head;
-}
-
-
-
-
linked_list.ts
/* 访问链表中索引为 index 的结点 */
-function access(head: ListNode | null, index: number): ListNode | null {
-    for (let i = 0; i < index; i++) {
-        if (!head) {
-            return null;
-        }
-        head = head.next;
-    }
-    return head;
-}
-
-
-
-
linked_list.c

-
-
-
-
linked_list.cs
// 访问链表中索引为 index 的结点
-ListNode Access(ListNode head, int index)
-{
-    for (int i = 0; i < index; i++)
-    {
-        if (head == null)
-            return null;
-        head = head.next;
-    }
-    return head;
-}
-
-
-
-
linked_list.swift
/* 访问链表中索引为 index 的结点 */
-func access(head: ListNode, index: Int) -> ListNode? {
-    var head: ListNode? = head
-    for _ in 0 ..< index {
-        if head == nil {
-            return nil
-        }
-        head = head?.next
-    }
-    return head
-}
-
-
-
-
linked_list.zig
// 访问链表中索引为 index 的结点
-pub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) {
-    var head = node;
-    var i: i32 = 0;
-    while (i < index) : (i += 1) {
-        head = head.?.next;
-        if (head == null) return null;
-    }
-    return head;
-}
-
-
-
-
-

链表的内存占用多。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。

-

4.2.3. 链表常用操作

-

遍历链表查找。遍历链表,查找链表内值为 target 的结点,输出结点在链表中的索引。

-
-
-
-
linked_list.java
/* 在链表中查找值为 target 的首个结点 */
-int find(ListNode head, int target) {
-    int index = 0;
-    while (head != null) {
-        if (head.val == target)
-            return index;
-        head = head.next;
-        index++;
-    }
-    return -1;
-}
-
-
-
-
linked_list.cpp
/* 在链表中查找值为 target 的首个结点 */
-int find(ListNode* head, int target) {
-    int index = 0;
-    while (head != nullptr) {
-        if (head->val == target)
-            return index;
-        head = head->next;
-        index++;
-    }
-    return -1;
-}
-
-
-
-
linked_list.py
""" 在链表中查找值为 target 的首个结点 """
-def find(head, target):
-    index = 0
-    while head:
-        if head.val == target:
-            return index
-        head = head.next
-        index += 1
-    return -1
-
-
-
-
linked_list.go
/* 在链表中查找值为 target 的首个结点 */
-func find(head *ListNode, target int) int {
-    index := 0
-    for head != nil {
-        if head.Val == target {
-            return index
-        }
-        head = head.Next
-        index++
-    }
-    return -1
-}
-
-
-
-
linked_list.js
/* 在链表中查找值为 target 的首个结点 */
-function find(head, target) {
-    let index = 0;
-    while (head !== null) {
-        if (head.val === target) {
-            return index;
-        }
-        head = head.next;
-        index += 1;
-    }
-    return -1;
-}
-
-
-
-
linked_list.ts
/* 在链表中查找值为 target 的首个结点 */
-function find(head: ListNode | null, target: number): number {
-    let index = 0;
-    while (head !== null) {
-        if (head.val === target) {
-            return index;
-        }
-        head = head.next;
-        index += 1;
-    }
-    return -1;
-}
-
-
-
-
linked_list.c

-
-
-
-
linked_list.cs
// 在链表中查找值为 target 的首个结点
-int Find(ListNode head, int target)
-{
-    int index = 0;
-    while (head != null)
-    {
-        if (head.val == target)
-            return index;
-        head = head.next;
-        index++;
-    }
-    return -1;
-}
-
-
-
-
linked_list.swift
/* 在链表中查找值为 target 的首个结点 */
-func find(head: ListNode, target: Int) -> Int {
-    var head: ListNode? = head
-    var index = 0
-    while head != nil {
-        if head?.val == target {
-            return index
-        }
-        head = head?.next
-        index += 1
-    }
-    return -1
-}
-
-
-
-
linked_list.zig
// 在链表中查找值为 target 的首个结点
-pub fn find(node: ?*inc.ListNode(i32), target: i32) i32 {
-    var head = node;
-    var index: i32 = 0;
-    while (head != null) {
-        if (head.?.val == target) return index;
-        head = head.?.next;
-        index += 1;
-    }
-    return -1;
-}
-
-
-
-
-

4.2.4. 常见链表类型

-

单向链表。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 null

-

环形链表。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。

-

双向链表。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。

-
-
-
-
/* 双向链表结点类 */
-class ListNode {
-    int val;        // 结点值
-    ListNode next;  // 指向后继结点的指针(引用)
-    ListNode prev;  // 指向前驱结点的指针(引用)
-    ListNode(int x) { val = x; }  // 构造函数
-}
-
-
-
-
/* 链表结点结构体 */
-struct ListNode {
-    int val;         // 结点值
-    ListNode *next;  // 指向后继结点的指针(引用)
-    ListNode *prev;  // 指向前驱结点的指针(引用)
-    ListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // 构造函数
-};
-
-
-
-
""" 双向链表结点类 """ 
-class ListNode:
-    def __init__(self, x):
-        self.val = x      # 结点值
-        self.next = None  # 指向后继结点的指针(引用)
-        self.prev = None  # 指向前驱结点的指针(引用)
-
-
-
-
/* 双向链表结点结构体 */
-type DoublyListNode struct {
-    Val  int             // 结点值
-    Next *DoublyListNode // 指向后继结点的指针(引用)
-    Prev *DoublyListNode // 指向前驱结点的指针(引用)
-}
-
-// NewDoublyListNode 初始化
-func NewDoublyListNode(val int) *DoublyListNode {
-    return &DoublyListNode{
-        Val:  val,
-        Next: nil,
-        Prev: nil,
-    }
-}
-
-
-
-
/* 双向链表结点类 */
-class ListNode {
-    val;
-    next;
-    prev;
-    constructor(val, next) {
-        this.val = val  ===  undefined ? 0 : val;        // 结点值
-        this.next = next  ===  undefined ? null : next;  // 指向后继结点的指针(引用)
-        this.prev = prev  ===  undefined ? null : prev;  // 指向前驱结点的指针(引用)
-    }
-}
-
-
-
-
/* 双向链表结点类 */
-class ListNode {
-    val: number;
-    next: ListNode | null;
-    prev: ListNode | null;
-    constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
-        this.val = val  ===  undefined ? 0 : val;        // 结点值
-        this.next = next  ===  undefined ? null : next;  // 指向后继结点的指针(引用)
-        this.prev = prev  ===  undefined ? null : prev;  // 指向前驱结点的指针(引用)
-    }
-}
-
-
-
-

-
-
-
-
/* 双向链表结点类 */
-class ListNode {
-    int val;        // 结点值
-    ListNode next;  // 指向后继结点的指针(引用)
-    ListNode prev;  // 指向前驱结点的指针(引用)
-    ListNode(int x) => val = x;  // 构造函数
-}
-
-
-
-
/* 双向链表结点类 */
-class ListNode {
-    var val: Int // 结点值
-    var next: ListNode? // 指向后继结点的指针(引用)
-    var prev: ListNode? // 指向前驱结点的指针(引用)
-
-    init(x: Int) { // 构造函数
-        val = x
-    }
-}
-
-
-
-
// 双向链表结点类
-pub fn ListNode(comptime T: type) type {
-    return struct {
-        const Self = @This();
-
-        val: T = 0, // 结点值
-        next: ?*Self = null, // 指向后继结点的指针(引用)
-        prev: ?*Self = null, // 指向前驱结点的指针(引用)
-
-        // 构造函数
-        pub fn init(self: *Self, x: i32) void {
-            self.val = x;
-            self.next = null;
-            self.prev = null;
-        }
-    };
-}
-
-
-
-
-

linkedlist_common_types

-

Fig. 常见链表类型

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_array_and_linkedlist/list/index.html b/site/chapter_array_and_linkedlist/list/index.html deleted file mode 100644 index 5befbc019..000000000 --- a/site/chapter_array_and_linkedlist/list/index.html +++ /dev/null @@ -1,3221 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4.3. 列表(List) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

4.3. 列表

-

由于长度不可变,数组的实用性大大降低。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。

-

为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。

-

4.3.1. 列表常用操作

-

初始化列表。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。

-
-
-
-
list.java
/* 初始化列表 */
-// 无初始值
-List<Integer> list1 = new ArrayList<>();
-// 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[])
-Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
-List<Integer> list = new ArrayList<>(Arrays.asList(numbers));
-
-
-
-
list.cpp
/* 初始化列表 */
-// 需注意,C++ 中 vector 即是本文描述的 list
-// 无初始值
-vector<int> list1;
-// 有初始值
-vector<int> list = { 1, 3, 2, 5, 4 };
-
-
-
-
list.py
""" 初始化列表 """
-# 无初始值
-list1 = []
-# 有初始值
-list = [1, 3, 2, 5, 4]
-
-
-
-
list_test.go
/* 初始化列表 */
-// 无初始值
-list1 := []int
-// 有初始值
-list := []int{1, 3, 2, 5, 4}
-
-
-
-
list.js
/* 初始化列表 */
-// 无初始值
-const list1 = [];
-// 有初始值
-const list = [1, 3, 2, 5, 4];
-
-
-
-
list.ts
/* 初始化列表 */
-// 无初始值
-const list1: number[] = [];
-// 有初始值
-const list: number[] = [1, 3, 2, 5, 4];
-
-
-
-
list.c

-
-
-
-
list.cs
/* 初始化列表 */
-// 无初始值
-List<int> list1 = new ();
-// 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[])
-int[] numbers = new int[] { 1, 3, 2, 5, 4 };
-List<int> list = numbers.ToList();
-
-
-
-
list.swift
/* 初始化列表 */
-// 无初始值
-let list1: [Int] = []
-// 有初始值
-var list = [1, 3, 2, 5, 4]
-
-
-
-
list.zig
// 初始化列表
-var list = std.ArrayList(i32).init(std.heap.page_allocator);
-defer list.deinit();
-try list.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 });
-
-
-
-
-

访问与更新元素。列表的底层数据结构是数组,因此可以在 \(O(1)\) 时间内访问与更新元素,效率很高。

-
-
-
-
list.java
/* 访问元素 */
-int num = list.get(1);  // 访问索引 1 处的元素
-
-/* 更新元素 */
-list.set(1, 0);  // 将索引 1 处的元素更新为 0
-
-
-
-
list.cpp
/* 访问元素 */
-int num = list[1];  // 访问索引 1 处的元素
-
-/* 更新元素 */
-list[1] = 0;  // 将索引 1 处的元素更新为 0
-
-
-
-
list.py
""" 访问元素 """
-num = list[1]  # 访问索引 1 处的元素
-
-""" 更新元素 """
-list[1] = 0    # 将索引 1 处的元素更新为 0
-
-
-
-
list_test.go
/* 访问元素 */
-num := list[1]  // 访问索引 1 处的元素
-
-/* 更新元素 */
-list[1] = 0     // 将索引 1 处的元素更新为 0
-
-
-
-
list.js
/* 访问元素 */
-const num = list[1];  // 访问索引 1 处的元素
-
-/* 更新元素 */
-list[1] = 0;  // 将索引 1 处的元素更新为 0
-
-
-
-
list.ts
/* 访问元素 */
-const num: number = list[1];  // 访问索引 1 处的元素
-
-/* 更新元素 */
-list[1] = 0;  // 将索引 1 处的元素更新为 0
-
-
-
-
list.c

-
-
-
-
list.cs
/* 访问元素 */
-int num = list[1];  // 访问索引 1 处的元素
-
-/* 更新元素 */
-list[1] = 0;  // 将索引 1 处的元素更新为 0
-
-
-
-
list.swift
/* 访问元素 */
-let num = list[1] // 访问索引 1 处的元素
-
-/* 更新元素 */
-list[1] = 0 // 将索引 1 处的元素更新为 0
-
-
-
-
list.zig
// 访问元素
-var num = list.items[1]; // 访问索引 1 处的元素
-
-// 更新元素
-list.items[1] = 0; // 将索引 1 处的元素更新为 0  
-
-
-
-
-

在列表中添加、插入、删除元素。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 \(O(1)\) ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 \(O(N)\)

-
-
-
-
list.java
/* 清空列表 */
-list.clear();
-
-/* 尾部添加元素 */
-list.add(1);
-list.add(3);
-list.add(2);
-list.add(5);
-list.add(4);
-
-/* 中间插入元素 */
-list.add(3, 6);  // 在索引 3 处插入数字 6
-
-/* 删除元素 */
-list.remove(3);  // 删除索引 3 处的元素
-
-
-
-
list.cpp
/* 清空列表 */
-list.clear();
-
-/* 尾部添加元素 */
-list.push_back(1);
-list.push_back(3);
-list.push_back(2);
-list.push_back(5);
-list.push_back(4);
-
-/* 中间插入元素 */
-list.insert(list.begin() + 3, 6);  // 在索引 3 处插入数字 6
-
-/* 删除元素 */
-list.erase(list.begin() + 3);      // 删除索引 3 处的元素
-
-
-
-
list.py
""" 清空列表 """
-list.clear()
-
-""" 尾部添加元素 """
-list.append(1)
-list.append(3)
-list.append(2)
-list.append(5)
-list.append(4)
-
-""" 中间插入元素 """
-list.insert(3, 6)  # 在索引 3 处插入数字 6
-
-""" 删除元素 """
-list.pop(3)        # 删除索引 3 处的元素
-
-
-
-
list_test.go
/* 清空列表 */
-list = nil
-
-/* 尾部添加元素 */
-list = append(list, 1)
-list = append(list, 3)
-list = append(list, 2)
-list = append(list, 5)
-list = append(list, 4)
-
-/* 中间插入元素 */
-list = append(list[:3], append([]int{6}, list[3:]...)...) // 在索引 3 处插入数字 6
-
-/* 删除元素 */
-list = append(list[:3], list[4:]...) // 删除索引 3 处的元素
-
-
-
-
list.js
/* 清空列表 */
-list.length = 0;
-
-/* 尾部添加元素 */
-list.push(1);
-list.push(3);
-list.push(2);
-list.push(5);
-list.push(4);
-
-/* 中间插入元素 */
-list.splice(3, 0, 6);
-
-/* 删除元素 */
-list.splice(3, 1);
-
-
-
-
list.ts
/* 清空列表 */
-list.length = 0;
-
-/* 尾部添加元素 */
-list.push(1);
-list.push(3);
-list.push(2);
-list.push(5);
-list.push(4);
-
-/* 中间插入元素 */
-list.splice(3, 0, 6);
-
-/* 删除元素 */
-list.splice(3, 1);
-
-
-
-
list.c

-
-
-
-
list.cs
/* 清空列表 */
-list.Clear();
-
-/* 尾部添加元素 */
-list.Add(1);
-list.Add(3);
-list.Add(2);
-list.Add(5);
-list.Add(4);
-
-/* 中间插入元素 */
-list.Insert(3, 6);
-
-/* 删除元素 */
-list.RemoveAt(3);
-
-
-
-
list.swift
/* 清空列表 */
-list.removeAll()
-
-/* 尾部添加元素 */
-list.append(1)
-list.append(3)
-list.append(2)
-list.append(5)
-list.append(4)
-
-/* 中间插入元素 */
-list.insert(6, at: 3) // 在索引 3 处插入数字 6
-
-/* 删除元素 */
-list.remove(at: 3) // 删除索引 3 处的元素
-
-
-
-
list.zig
// 清空列表
-list.clearRetainingCapacity();
-
-// 尾部添加元素
-try list.append(1);
-try list.append(3);
-try list.append(2);
-try list.append(5);
-try list.append(4);
-
-// 中间插入元素
-try list.insert(3, 6); // 在索引 3 处插入数字 6
-
-// 删除元素
-_ = list.orderedRemove(3); // 删除索引 3 处的元素
-
-
-
-
-

遍历列表。与数组一样,列表可以使用索引遍历,也可以使用 for-each 直接遍历。

-
-
-
-
list.java
/* 通过索引遍历列表 */
-int count = 0;
-for (int i = 0; i < list.size(); i++) {
-    count++;
-}
-
-/* 直接遍历列表元素 */
-count = 0;
-for (int n : list) {
-    count++;
-}
-
-
-
-
list.cpp
/* 通过索引遍历列表 */
-int count = 0;
-for (int i = 0; i < list.size(); i++) {
-    count++;
-}
-
-/* 直接遍历列表元素 */
-count = 0;
-for (int n : list) {
-    count++;
-}
-
-
-
-
list.py
""" 通过索引遍历列表 """
-count = 0
-for i in range(len(list)):
-    count += 1
-
-""" 直接遍历列表元素 """
-count = 0
-for n in list:
-    count += 1
-
-
-
-
list_test.go
/* 通过索引遍历列表 */
-count := 0
-for i := 0; i < len(list); i++ {
-    count++
-}
-
-/* 直接遍历列表元素 */
-count = 0
-for range list {
-    count++
-}
-
-
-
-
list.js
/* 通过索引遍历列表 */
-let count = 0;
-for (let i = 0; i < list.length; i++) {
-    count++;
-}
-
-/* 直接遍历列表元素 */
-count = 0;
-for (const n of list) {
-    count++;
-}
-
-
-
-
list.ts
/* 通过索引遍历列表 */
-let count = 0;
-for (let i = 0; i < list.length; i++) {
-    count++;
-}
-
-/* 直接遍历列表元素 */
-count = 0;
-for (const n of list) {
-    count++;
-}
-
-
-
-
list.c

-
-
-
-
list.cs
/* 通过索引遍历列表 */
-int count = 0;
-for (int i = 0; i < list.Count(); i++)
-{
-    count++;
-}
-
-/* 直接遍历列表元素 */
-count = 0;
-foreach (int n in list)
-{
-    count++;
-}
-
-
-
-
list.swift
/* 通过索引遍历列表 */
-var count = 0
-for _ in list.indices {
-    count += 1
-}
-
-/* 直接遍历列表元素 */
-count = 0
-for _ in list {
-    count += 1
-}
-
-
-
-
list.zig
// 通过索引遍历列表
-var count: i32 = 0;
-var i: i32 = 0;
-while (i < list.items.len) : (i += 1) {
-    count += 1;
-}
-
-// 直接遍历列表元素
-count = 0;
-for (list.items) |_| {
-    count += 1;
-}
-
-
-
-
-

拼接两个列表。再创建一个新列表 list1 ,我们可以将其中一个列表拼接到另一个的尾部。

-
-
-
-
list.java
/* 拼接两个列表 */
-List<Integer> list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));
-list.addAll(list1);  // 将列表 list1 拼接到 list 之后
-
-
-
-
list.cpp
/* 拼接两个列表 */
-vector<int> list1 = { 6, 8, 7, 10, 9 };
-// 将列表 list1 拼接到 list 之后
-list.insert(list.end(), list1.begin(), list1.end());
-
-
-
-
list.py
""" 拼接两个列表 """
-list1 = [6, 8, 7, 10, 9]
-list += list1  # 将列表 list1 拼接到 list 之后
-
-
-
-
list_test.go
/* 拼接两个列表 */
-list1 := []int{6, 8, 7, 10, 9}
-list = append(list, list1...)  // 将列表 list1 拼接到 list 之后
-
-
-
-
list.js
/* 拼接两个列表 */
-const list1 = [6, 8, 7, 10, 9];
-list.push(...list1);  // 将列表 list1 拼接到 list 之后
-
-
-
-
list.ts
/* 拼接两个列表 */
-const list1: number[] = [6, 8, 7, 10, 9];
-list.push(...list1);  // 将列表 list1 拼接到 list 之后
-
-
-
-
list.c

-
-
-
-
list.cs
/* 拼接两个列表 */
-List<int> list1 = new() { 6, 8, 7, 10, 9 };
-list.AddRange(list1);  // 将列表 list1 拼接到 list 之后
-
-
-
-
list.swift
/* 拼接两个列表 */
-let list1 = [6, 8, 7, 10, 9]
-list.append(contentsOf: list1) // 将列表 list1 拼接到 list 之后
-
-
-
-
list.zig
// 拼接两个列表
-var list1 = std.ArrayList(i32).init(std.heap.page_allocator);
-defer list1.deinit();
-try list1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 });
-try list.insertSlice(list.items.len, list1.items); // 将列表 list1 拼接到 list 之后
-
-
-
-
-

排序列表。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。

-
-
-
-
list.java
/* 排序列表 */
-Collections.sort(list);  // 排序后,列表元素从小到大排列
-
-
-
-
list.cpp
/* 排序列表 */
-sort(list.begin(), list.end());  // 排序后,列表元素从小到大排列
-
-
-
-
list.py
""" 排序列表 """
-list.sort()  # 排序后,列表元素从小到大排列
-
-
-
-
list_test.go
/* 排序列表 */
-sort.Ints(list)  // 排序后,列表元素从小到大排列
-
-
-
-
list.js
/* 排序列表 */  
-list.sort((a, b) => a - b);  // 排序后,列表元素从小到大排列
-
-
-
-
list.ts
/* 排序列表 */
-list.sort((a, b) => a - b);  // 排序后,列表元素从小到大排列
-
-
-
-
list.c

-
-
-
-
list.cs
/* 排序列表 */
-list.Sort(); // 排序后,列表元素从小到大排列
-
-
-
-
list.swift
/* 排序列表 */
-list.sort() // 排序后,列表元素从小到大排列
-
-
-
-
list.zig
// 排序列表
-std.sort.sort(i32, list.items, {}, comptime std.sort.asc(i32));
-
-
-
-
-

4.3.2. 列表简易实现 *

-

为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点:

-
    -
  • 初始容量:选取一个合理的数组的初始容量 initialCapacity 。在本示例中,我们选择 10 作为初始容量。
  • -
  • 数量记录:需要声明一个变量 size ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。
  • -
  • 扩容机制:插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 extendRatio ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。
  • -
-

本示例是为了帮助读者对如何实现列表产生直观的认识。实际编程语言中,列表的实现远比以下代码复杂且标准,感兴趣的读者可以查阅源码学习。

-
-
-
-
my_list.java
/* 列表类简易实现 */
-class MyList {
-    private int[] nums;           // 数组(存储列表元素)
-    private int capacity = 10;    // 列表容量
-    private int size = 0;         // 列表长度(即当前元素数量)
-    private int extendRatio = 2;  // 每次列表扩容的倍数
-
-    /* 构造函数 */
-    public MyList() {
-        nums = new int[capacity];
-    }
-
-    /* 获取列表长度(即当前元素数量)*/
-    public int size() {
-        return size;
-    }
-
-    /* 获取列表容量 */
-    public int capacity() {
-        return capacity;
-    }
-
-    /* 访问元素 */
-    public int get(int index) {
-        // 索引如果越界则抛出异常,下同
-        if (index < 0 || index >= size)
-            throw new IndexOutOfBoundsException("索引越界");
-        return nums[index];
-    }
-
-    /* 更新元素 */
-    public void set(int index, int num) {
-        if (index < 0 || index >= size)
-            throw new IndexOutOfBoundsException("索引越界");
-        nums[index] = num;
-    }
-
-    /* 尾部添加元素 */
-    public void add(int num) {
-        // 元素数量超出容量时,触发扩容机制
-        if (size == capacity())
-            extendCapacity();
-        nums[size] = num;
-        // 更新元素数量
-        size++;
-    }
-
-    /* 中间插入元素 */
-    public void insert(int index, int num) {
-        if (index < 0 || index >= size)
-            throw new IndexOutOfBoundsException("索引越界");
-        // 元素数量超出容量时,触发扩容机制
-        if (size == capacity())
-            extendCapacity();
-        // 将索引 index 以及之后的元素都向后移动一位
-        for (int j = size - 1; j >= index; j--) {
-            nums[j + 1] = nums[j];
-        }
-        nums[index] = num;
-        // 更新元素数量
-        size++;
-    }
-
-    /* 删除元素 */
-    public int remove(int index) {
-        if (index < 0 || index >= size)
-            throw new IndexOutOfBoundsException("索引越界");
-        int num = nums[index];
-        // 将索引 index 之后的元素都向前移动一位
-        for (int j = index; j < size - 1; j++) {
-            nums[j] = nums[j + 1];
-        }
-        // 更新元素数量
-        size--;
-        // 返回被删除元素
-        return num;
-    }
-
-    /* 列表扩容 */
-    public void extendCapacity() {
-        // 新建一个长度为 size 的数组,并将原数组拷贝到新数组
-        nums = Arrays.copyOf(nums, capacity() * extendRatio);
-        // 更新列表容量
-        capacity = nums.length;
-    }
-
-    /* 将列表转换为数组 */
-    public int[] toArray() {
-        int size = size();
-        // 仅转换有效长度范围内的列表元素
-        int[] nums = new int[size];
-        for (int i = 0; i < size; i++) {
-            nums[i] = get(i);
-        }
-        return nums;
-    }
-}
-
-
-
-
my_list.cpp
/* 列表类简易实现 */
-class MyList {
-private:
-    int* nums;                // 数组(存储列表元素)
-    int numsCapacity = 10;    // 列表容量
-    int numsSize = 0;         // 列表长度(即当前元素数量)
-    int extendRatio = 2;      // 每次列表扩容的倍数
-
-public:
-    /* 构造函数 */
-    MyList() {
-        nums = new int[numsCapacity];
-    }
-
-    /* 析构函数 */
-    ~MyList() {
-        delete[] nums;
-    }
-
-    /* 获取列表长度(即当前元素数量)*/
-    int size() {
-        return numsSize;
-    }
-
-    /* 获取列表容量 */
-    int capacity() {
-        return numsCapacity;
-    }
-
-    /* 访问元素 */
-    int get(int index) {
-        // 索引如果越界则抛出异常,下同
-        if (index < 0 || index >= size())
-            throw out_of_range("索引越界");
-        return nums[index];
-    }
-
-    /* 更新元素 */
-    void set(int index, int num) {
-        if (index < 0 || index >= size())
-            throw out_of_range("索引越界");
-        nums[index] = num;
-    }
-
-    /* 尾部添加元素 */
-    void add(int num) {
-        // 元素数量超出容量时,触发扩容机制
-        if (size() == capacity())
-            extendCapacity();
-        nums[size()] = num;
-        // 更新元素数量
-        numsSize++;
-    }
-
-    /* 中间插入元素 */
-    void insert(int index, int num) {
-        if (index < 0 || index >= size())
-            throw out_of_range("索引越界");
-        // 元素数量超出容量时,触发扩容机制
-        if (size() == capacity())
-            extendCapacity();
-        // 索引 i 以及之后的元素都向后移动一位
-        for (int j = size() - 1; j >= index; j--) {
-            nums[j + 1] = nums[j];
-        }
-        nums[index] = num;
-        // 更新元素数量
-        numsSize++;
-    }
-
-    /* 删除元素 */
-    int remove(int index) {
-        if (index < 0 || index >= size())
-            throw out_of_range("索引越界");
-        int num = nums[index];
-        // 索引 i 之后的元素都向前移动一位
-        for (int j = index; j < size() - 1; j++) {
-            nums[j] = nums[j + 1];
-        }
-        // 更新元素数量
-        numsSize--;
-        // 返回被删除元素
-        return num;
-    }
-
-    /* 列表扩容 */
-    void extendCapacity() {
-        // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组
-        int newCapacity = capacity() * extendRatio;
-        int* tmp = nums;
-        nums = new int[newCapacity];
-        // 将原数组中的所有元素复制到新数组
-        for (int i = 0; i < size(); i++) {
-            nums[i] = tmp[i];
-        }
-        // 释放内存
-        delete[] tmp;
-        numsCapacity = newCapacity;
-    }
-};
-
-
-
-
my_list.py
""" 列表类简易实现 """
-class MyList:
-    """ 构造函数 """
-    def __init__(self):
-        self.__capacity = 10                 # 列表容量
-        self.__nums = [0] * self.__capacity  # 数组(存储列表元素)
-        self.__size = 0                      # 列表长度(即当前元素数量)
-        self.__extend_ratio = 2              # 每次列表扩容的倍数
-
-    """ 获取列表长度(即当前元素数量) """
-    def size(self):
-        return self.__size
-
-    """ 获取列表容量 """
-    def capacity(self):
-        return self.__capacity
-
-    """ 访问元素 """
-    def get(self, index):
-        # 索引如果越界则抛出异常,下同
-        assert index >= 0 and index < self.__size, "索引越界"
-        return self.__nums[index]
-
-    """ 更新元素 """
-    def set(self, num, index):
-        assert index >= 0 and index < self.__size, "索引越界"
-        self.__nums[index] = num
-
-    """ 中间插入(尾部添加)元素 """
-    def add(self, num, index=-1):
-        assert index >= 0 and index < self.__size, "索引越界"
-        # 若不指定索引 index ,则向数组尾部添加元素
-        if index == -1:
-            index = self.__size
-        # 元素数量超出容量时,触发扩容机制
-        if self.__size == self.capacity():
-            self.extend_capacity()
-        # 索引 i 以及之后的元素都向后移动一位
-        for j in range(self.__size - 1, index - 1, -1):
-            self.__nums[j + 1] = self.__nums[j]
-        self.__nums[index] = num
-        # 更新元素数量
-        self.__size += 1
-
-    """ 删除元素 """
-    def remove(self, index):
-        assert index >= 0 and index < self.__size, "索引越界"
-        num = self.nums[index]
-        # 索引 i 之后的元素都向前移动一位
-        for j in range(index, self.__size - 1):
-            self.__nums[j] = self.__nums[j + 1]
-        # 更新元素数量
-        self.__size -= 1
-        # 返回被删除元素
-        return num
-
-    """ 列表扩容 """
-    def extend_capacity(self):
-        # 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组
-        self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1)
-        # 更新列表容量
-        self.__capacity = len(self.__nums)
-
-    """ 返回有效长度的列表 """
-    def to_array(self):
-        return self.__nums[:self.__size]
-
-
-
-
my_list.go
/* 列表类简易实现 */
-type myList struct {
-    numsCapacity int
-    nums         []int
-    numsSize     int
-    extendRatio  int
-}
-
-/* 构造函数 */
-func newMyList() *myList {
-    return &myList{
-        numsCapacity: 10,              // 列表容量
-        nums:         make([]int, 10), // 数组(存储列表元素)
-        numsSize:     0,               // 列表长度(即当前元素数量)
-        extendRatio:  2,               // 每次列表扩容的倍数
-    }
-}
-
-/* 获取列表长度(即当前元素数量) */
-func (l *myList) size() int {
-    return l.numsSize
-}
-
-/*  获取列表容量 */
-func (l *myList) capacity() int {
-    return l.numsCapacity
-}
-
-/* 访问元素 */
-func (l *myList) get(index int) int {
-    // 索引如果越界则抛出异常,下同
-    if index < 0 || index >= l.numsSize {
-        panic("索引越界")
-    }
-    return l.nums[index]
-}
-
-/* 更新元素 */
-func (l *myList) set(num, index int) {
-    if index < 0 || index >= l.numsSize {
-        panic("索引越界")
-    }
-    l.nums[index] = num
-}
-
-/* 尾部添加元素 */
-func (l *myList) add(num int) {
-    // 元素数量超出容量时,触发扩容机制
-    if l.numsSize == l.numsCapacity {
-        l.extendCapacity()
-    }
-    l.nums[l.numsSize] = num
-    // 更新元素数量
-    l.numsSize++
-}
-
-/* 中间插入元素 */
-func (l *myList) insert(num, index int) {
-    if index < 0 || index >= l.numsSize {
-        panic("索引越界")
-    }
-    // 元素数量超出容量时,触发扩容机制
-    if l.numsSize == l.numsCapacity {
-        l.extendCapacity()
-    }
-    // 索引 i 以及之后的元素都向后移动一位
-    for j := l.numsSize - 1; j >= index; j-- {
-        l.nums[j+1] = l.nums[j]
-    }
-    l.nums[index] = num
-    // 更新元素数量
-    l.numsSize++
-}
-
-/* 删除元素 */
-func (l *myList) remove(index int) int {
-    if index < 0 || index >= l.numsSize {
-        panic("索引越界")
-    }
-    num := l.nums[index]
-    // 索引 i 之后的元素都向前移动一位
-    for j := index; j < l.numsSize-1; j++ {
-        l.nums[j] = l.nums[j+1]
-    }
-    // 更新元素数量
-    l.numsSize--
-    // 返回被删除元素
-    return num
-}
-
-/* 列表扩容 */
-func (l *myList) extendCapacity() {
-    // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组
-    l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...)
-    // 更新列表容量
-    l.numsCapacity = len(l.nums)
-}
-
-
-
-
my_list.js
/* 列表类简易实现 */
-class MyList {
-    #nums = new Array(); // 数组(存储列表元素)
-    #capacity = 10; // 列表容量
-    #size = 0; // 列表长度(即当前元素数量)
-    #extendRatio = 2; // 每次列表扩容的倍数
-
-    /* 构造函数 */
-    constructor() {
-        this.#nums = new Array(this.#capacity);
-    }
-
-    /* 获取列表长度(即当前元素数量)*/
-    size() {
-        return this.#size;
-    }
-
-    /* 获取列表容量 */
-    capacity() {
-        return this.#capacity;
-    }
-
-    /* 访问元素 */
-    get(index) {
-        // 索引如果越界则抛出异常,下同
-        if (index < 0 || index >= this.#size)
-            throw new Error('索引越界');
-        return this.#nums[index];
-    }
-
-    /* 更新元素 */
-    set(index, num) {
-        if (index < 0 || index >= this.#size)
-            throw new Error('索引越界');
-        this.#nums[index] = num;
-    }
-
-    /* 尾部添加元素 */
-    add(num) {
-        // 如果长度等于容量,则需要扩容
-        if (this.#size === this.#capacity) {
-            this.extendCapacity();
-        }
-        // 将新元素添加到列表尾部
-        this.#nums[this.#size] = num;
-        this.#size++;
-    }
-
-    /* 中间插入元素 */
-    insert(index, num) {
-        if (index < 0 || index >= this.#size)
-            throw new Error('索引越界');
-        // 元素数量超出容量时,触发扩容机制
-        if (this.#size === this.#capacity) {
-            this.extendCapacity();
-        }
-        // 将索引 index 以及之后的元素都向后移动一位
-        for (let j = this.#size - 1; j >= index; j--) {
-            this.#nums[j + 1] = this.#nums[j];
-        }
-        // 更新元素数量
-        this.#nums[index] = num;
-        this.#size++;
-    }
-
-    /* 删除元素 */
-    remove(index) {
-        if (index < 0 || index >= this.#size)
-            throw new Error('索引越界');
-        let num = this.#nums[index];
-        // 将索引 index 之后的元素都向前移动一位
-        for (let j = index; j < this.#size - 1; j++) {
-            this.#nums[j] = this.#nums[j + 1];
-        }
-        // 更新元素数量
-        this.#size--;
-        // 返回被删除元素
-        return num;
-    }
-
-    /* 列表扩容 */
-    extendCapacity() {
-        // 新建一个长度为 size 的数组,并将原数组拷贝到新数组
-        this.#nums = this.#nums.concat(
-            new Array(this.capacity() * (this.#extendRatio - 1))
-        );
-        // 更新列表容量
-        this.#capacity = this.#nums.length;
-    }
-}
-
-
-
-
my_list.ts
/* 列表类简易实现 */
-class MyList {
-    private nums: Array<number>; // 数组(存储列表元素)
-    private _capacity: number = 10; // 列表容量
-    private _size: number = 0; // 列表长度(即当前元素数量)
-    private extendRatio: number = 2; // 每次列表扩容的倍数
-
-    /* 构造函数 */
-    constructor() {
-        this.nums = new Array(this._capacity);
-    }
-
-    /* 获取列表长度(即当前元素数量)*/
-    public size(): number {
-        return this._size;
-    }
-
-    /* 获取列表容量 */
-    public capacity(): number {
-        return this._capacity;
-    }
-
-    /* 访问元素 */
-    public get(index: number): number {
-        // 索引如果越界则抛出异常,下同
-        if (index < 0 || index >= this._size)
-            throw new Error('索引越界');
-        return this.nums[index];
-    }
-
-    /* 更新元素 */
-    public set(index: number, num: number): void {
-        if (index < 0 || index >= this._size)
-            throw new Error('索引越界');
-        this.nums[index] = num;
-    }
-
-    /* 尾部添加元素 */
-    public add(num: number): void {
-        // 如果长度等于容量,则需要扩容
-        if (this._size === this._capacity)
-            this.extendCapacity();
-        // 将新元素添加到列表尾部
-        this.nums[this._size] = num;
-        this._size++;
-    }
-
-    /* 中间插入元素 */
-    public insert(index: number, num: number): void {
-        if (index < 0 || index >= this._size)
-            throw new Error('索引越界');
-        // 元素数量超出容量时,触发扩容机制
-        if (this._size === this._capacity) {
-            this.extendCapacity();
-        }
-        // 将索引 index 以及之后的元素都向后移动一位
-        for (let j = this._size - 1; j >= index; j--) {
-            this.nums[j + 1] = this.nums[j];
-        }
-        // 更新元素数量
-        this.nums[index] = num;
-        this._size++;
-    }
-
-    /* 删除元素 */
-    public remove(index: number): number {
-        if (index < 0 || index >= this._size)
-            throw new Error('索引越界');
-        let num = this.nums[index];
-        // 将索引 index 之后的元素都向前移动一位
-        for (let j = index; j < this._size - 1; j++) {
-            this.nums[j] = this.nums[j + 1];
-        }
-        // 更新元素数量
-        this._size--;
-        // 返回被删除元素
-        return num;
-    }
-
-    /* 列表扩容 */
-    public extendCapacity(): void {
-        // 新建一个长度为 size 的数组,并将原数组拷贝到新数组
-        this.nums = this.nums.concat(
-            new Array(this.capacity() * (this.extendRatio - 1))
-        );
-        // 更新列表容量
-        this._capacity = this.nums.length;
-    }
-}
-
-
-
-
my_list.c

-
-
-
-
my_list.cs
class MyList
-{
-    private int[] nums;           // 数组(存储列表元素)
-    private int capacity = 10;    // 列表容量
-    private int size = 0;         // 列表长度(即当前元素数量)
-    private int extendRatio = 2;  // 每次列表扩容的倍数
-
-    /* 构造函数 */
-    public MyList()
-    {
-        nums = new int[capacity];
-    }
-
-    /* 获取列表长度(即当前元素数量)*/
-    public int Size()
-    {
-        return size;
-    }
-
-    /* 获取列表容量 */
-    public int Capacity()
-    {
-        return capacity;
-    }
-
-    /* 访问元素 */
-    public int Get(int index)
-    {
-        // 索引如果越界则抛出异常,下同
-        if (index < 0 || index >= size)
-            throw new IndexOutOfRangeException("索引越界");
-        return nums[index];
-    }
-
-    /* 更新元素 */
-    public void Set(int index, int num)
-    {
-        if (index < 0 || index >= size)
-            throw new IndexOutOfRangeException("索引越界");
-        nums[index] = num;
-    }
-
-    /* 尾部添加元素 */
-    public void Add(int num)
-    {
-        // 元素数量超出容量时,触发扩容机制
-        if (size == Capacity())
-            ExtendCapacity();
-        nums[size] = num;
-        // 更新元素数量
-        size++;
-    }
-
-    /* 中间插入元素 */
-    public void Insert(int index, int num)
-    {
-        if (index < 0 || index >= size)
-            throw new IndexOutOfRangeException("索引越界");
-        // 元素数量超出容量时,触发扩容机制
-        if (size == Capacity())
-            ExtendCapacity();
-        // 将索引 index 以及之后的元素都向后移动一位
-        for (int j = size - 1; j >= index; j--)
-        {
-            nums[j + 1] = nums[j];
-        }
-        nums[index] = num;
-        // 更新元素数量
-        size++;
-    }
-
-    /* 删除元素 */
-    public int Remove(int index)
-    {
-        if (index < 0 || index >= size)
-            throw new IndexOutOfRangeException("索引越界");
-        int num = nums[index];
-        // 将索引 index 之后的元素都向前移动一位
-        for (int j = index; j < size - 1; j++)
-        {
-            nums[j] = nums[j + 1];
-        }
-        // 更新元素数量
-        size--;
-        // 返回被删除元素
-        return num;
-    }
-
-    /* 列表扩容 */
-    public void ExtendCapacity()
-    {
-        // 新建一个长度为 size 的数组,并将原数组拷贝到新数组
-        System.Array.Resize(ref nums, Capacity() * extendRatio);
-        // 更新列表容量
-        capacity = nums.Length;
-    }
-}
-
-
-
-
my_list.swift
/* 列表类简易实现 */
-class MyList {
-    private var nums: [Int] // 数组(存储列表元素)
-    private var _capacity = 10 // 列表容量
-    private var _size = 0 // 列表长度(即当前元素数量)
-    private let extendRatio = 2 // 每次列表扩容的倍数
-
-    /* 构造函数 */
-    init() {
-        nums = Array(repeating: 0, count: _capacity)
-    }
-
-    /* 获取列表长度(即当前元素数量)*/
-    func size() -> Int {
-        _size
-    }
-
-    /* 获取列表容量 */
-    func capacity() -> Int {
-        _capacity
-    }
-
-    /* 访问元素 */
-    func get(index: Int) -> Int {
-        // 索引如果越界则抛出错误,下同
-        if index < 0 || index >= _size {
-            fatalError("索引越界")
-        }
-        return nums[index]
-    }
-
-    /* 更新元素 */
-    func set(index: Int, num: Int) {
-        if index < 0 || index >= _size {
-            fatalError("索引越界")
-        }
-        nums[index] = num
-    }
-
-    /* 尾部添加元素 */
-    func add(num: Int) {
-        // 元素数量超出容量时,触发扩容机制
-        if _size == _capacity {
-            extendCapacity()
-        }
-        nums[_size] = num
-        // 更新元素数量
-        _size += 1
-    }
-
-    /* 中间插入元素 */
-    func insert(index: Int, num: Int) {
-        if index < 0 || index >= _size {
-            fatalError("索引越界")
-        }
-        // 元素数量超出容量时,触发扩容机制
-        if _size == _capacity {
-            extendCapacity()
-        }
-        // 将索引 index 以及之后的元素都向后移动一位
-        for j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) {
-            nums[j + 1] = nums[j]
-        }
-        nums[index] = num
-        // 更新元素数量
-        _size += 1
-    }
-
-    /* 删除元素 */
-    @discardableResult
-    func remove(index: Int) -> Int {
-        if index < 0 || index >= _size {
-            fatalError("索引越界")
-        }
-        let num = nums[index]
-        // 将索引 index 之后的元素都向前移动一位
-        for j in index ..< (_size - 1) {
-            nums[j] = nums[j + 1]
-        }
-        // 更新元素数量
-        _size -= 1
-        // 返回被删除元素
-        return num
-    }
-
-    /* 列表扩容 */
-    func extendCapacity() {
-        // 新建一个长度为 size 的数组,并将原数组拷贝到新数组
-        nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1))
-        // 更新列表容量
-        _capacity = nums.count
-    }
-}
-
-
-
-
my_list.zig
// 列表类简易实现
-pub fn MyList(comptime T: type) type {
-    return struct {
-        const Self = @This();
-
-        nums: []T = undefined,                        // 数组(存储列表元素)
-        numsCapacity: usize = 10,                     // 列表容量
-        numSize: usize = 0,                           // 列表长度(即当前元素数量)
-        extendRatio: usize = 2,                       // 每次列表扩容的倍数
-        mem_arena: ?std.heap.ArenaAllocator = null,
-        mem_allocator: std.mem.Allocator = undefined, // 内存分配器
-
-        // 构造函数(分配内存+初始化列表)
-        pub fn init(self: *Self, allocator: std.mem.Allocator) !void {
-            if (self.mem_arena == null) {
-                self.mem_arena = std.heap.ArenaAllocator.init(allocator);
-                self.mem_allocator = self.mem_arena.?.allocator();
-            }
-            self.nums = try self.mem_allocator.alloc(T, self.numsCapacity);
-            std.mem.set(T, self.nums, @as(T, 0));
-        }
-
-        // 析构函数(释放内存)
-        pub fn deinit(self: *Self) void {
-            if (self.mem_arena == null) return;
-            self.mem_arena.?.deinit();
-        }
-
-        // 获取列表长度(即当前元素数量)
-        pub fn size(self: *Self) usize {
-            return self.numSize;
-        }
-
-        // 获取列表容量
-        pub fn capacity(self: *Self) usize {
-            return self.numsCapacity;
-        }
-
-        // 访问元素
-        pub fn get(self: *Self, index: usize) T {
-            // 索引如果越界则抛出异常,下同
-            if (index < 0 or index >= self.size()) @panic("索引越界");
-            return self.nums[index];
-        }  
-
-        // 更新元素
-        pub fn set(self: *Self, index: usize, num: T) void {
-            // 索引如果越界则抛出异常,下同
-            if (index < 0 or index >= self.size()) @panic("索引越界");
-            self.nums[index] = num;
-        }  
-
-        // 尾部添加元素
-        pub fn add(self: *Self, num: T) !void {
-            // 元素数量超出容量时,触发扩容机制
-            if (self.size() == self.capacity()) try self.extendCapacity();
-            self.nums[self.size()] = num;
-            // 更新元素数量
-            self.numSize += 1;
-        }  
-
-        // 中间插入元素
-        pub fn insert(self: *Self, index: usize, num: T) !void {
-            if (index < 0 or index >= self.size()) @panic("索引越界");
-            // 元素数量超出容量时,触发扩容机制
-            if (self.size() == self.capacity()) try self.extendCapacity();
-            // 索引 i 以及之后的元素都向后移动一位
-            var j = self.size() - 1;
-            while (j >= index) : (j -= 1) {
-                self.nums[j + 1] = self.nums[j];
-            }
-            self.nums[index] = num;
-            // 更新元素数量
-            self.numSize += 1;
-        }
-
-        // 删除元素
-        pub fn remove(self: *Self, index: usize) T {
-            if (index < 0 or index >= self.size()) @panic("索引越界");
-            var num = self.nums[index];
-            // 索引 i 之后的元素都向前移动一位
-            var j = index;
-            while (j < self.size() - 1) : (j += 1) {
-                self.nums[j] = self.nums[j + 1];
-            }
-            // 更新元素数量
-            self.numSize -= 1;
-            // 返回被删除元素
-            return num;
-        }
-
-        // 列表扩容
-        pub fn extendCapacity(self: *Self) !void {
-            // 新建一个长度为 size * extendRatio 的数组,并将原数组拷贝到新数组
-            var newCapacity = self.capacity() * self.extendRatio;
-            var extend = try self.mem_allocator.alloc(T, newCapacity);
-            std.mem.set(T, extend, @as(T, 0));
-            // 将原数组中的所有元素复制到新数组
-            std.mem.copy(T, extend, self.nums);
-            self.nums = extend;
-            // 更新列表容量
-            self.numsCapacity = newCapacity;
-        }
-
-        // 将列表转换为数组
-        pub fn toArray(self: *Self) ![]T {
-            // 仅转换有效长度范围内的列表元素
-            var nums = try self.mem_allocator.alloc(T, self.size());
-            std.mem.set(T, nums, @as(T, 0));
-            for (nums) |*num, i| {
-                num.* = self.get(i);
-            }
-            return nums;
-        }
-    };
-}
-
-
-
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_array_and_linkedlist/summary/index.html b/site/chapter_array_and_linkedlist/summary/index.html deleted file mode 100644 index 60f953477..000000000 --- a/site/chapter_array_and_linkedlist/summary/index.html +++ /dev/null @@ -1,1801 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4.4. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

4.4. 小结

-
    -
  • 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。
  • -
  • 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。
  • -
  • 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。
  • -
  • 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。
  • -
-

4.4.1. 数组 VS 链表

-

Table. 数组与链表特点对比

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
数组链表
存储方式连续内存空间离散内存空间
数据结构长度长度不可变长度可变
内存使用率占用内存少、缓存局部性好占用内存多
优势操作随机访问插入、删除
-
-
-

Tip

-

「缓存局部性(Cache locality)」涉及到了计算机操作系统,在本书不做展开介绍,建议有兴趣的同学 Google / Baidu 一下。

-
-

Table. 数组与链表操作时间复杂度

- -
- - - - - - - - - - - - - - - - - - - - - - - - - -
操作数组链表
访问元素\(O(1)\)\(O(N)\)
添加元素\(O(N)\)\(O(1)\)
删除元素\(O(N)\)\(O(1)\)
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_computational_complexity/performance_evaluation/index.html b/site/chapter_computational_complexity/performance_evaluation/index.html deleted file mode 100644 index 2e5ddcb4a..000000000 --- a/site/chapter_computational_complexity/performance_evaluation/index.html +++ /dev/null @@ -1,1817 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.1. 算法效率评估 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

2.1. 算法效率评估

-

2.1.1. 算法评价维度

-

在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。

-
    -
  1. 找到问题解法。算法需要能够在规定的输入范围下,可靠地求得问题的正确解。
  2. -
  3. 寻求最优解法。同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。
  4. -
-

换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括:

-
    -
  • 时间效率,即算法的运行速度的快慢。
  • -
  • 空间效率,即算法占用的内存空间大小。
  • -
-

数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。

-

2.1.2. 效率评估方法

-

实际测试

-

假设我们现在有算法 A 和 算法 B ,都能够解决同一问题,现在需要对比两个算法之间的效率。我们能够想到的最直接的方式,就是找一台计算机,把两个算法都完整跑一遍,并监控记录运行时间和内存占用情况。这种评估方式能够反映真实情况,但是也存在很大的硬伤。

-

难以排除测试环境的干扰因素。硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。

-

展开完整测试非常耗费资源。随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。

-

理论估算

-

既然实际测试具有很大的局限性,那么我们是否可以仅通过一些计算,就获知算法的效率水平呢?答案是肯定的,我们将此估算方法称为「复杂度分析 Complexity Analysis」或「渐近复杂度分析 Asymptotic Complexity Analysis」。

-

复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。

-

复杂度分析克服了实际测试方法的弊端。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。

-

2.1.3. 复杂度分析重要性

-

复杂度分析给出一把评价算法效率的“标尺”,告诉我们执行某个算法需要多少时间和空间资源,也让我们可以开展不同算法之间的效率对比。

-

计算复杂度是个数学概念,对于初学者可能比较抽象,学习难度相对较高。从这个角度出发,其并不适合作为第一章内容。但是,当我们讨论某个数据结构或者算法的特点时,难以避免需要分析它的运行速度和空间使用情况。因此,在展开学习数据结构与算法之前,建议读者先对计算复杂度建立起初步的了解,并且能够完成简单案例的复杂度分析

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png b/site/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png deleted file mode 100644 index 65c79e98e..000000000 Binary files a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png and /dev/null differ diff --git a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png b/site/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png deleted file mode 100644 index c26da8461..000000000 Binary files a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png and /dev/null differ diff --git a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png b/site/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png deleted file mode 100644 index 951a4f4b5..000000000 Binary files a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png and /dev/null differ diff --git a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png b/site/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png deleted file mode 100644 index 457b583a6..000000000 Binary files a/site/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png and /dev/null differ diff --git a/site/chapter_computational_complexity/space_complexity.assets/space_types.png b/site/chapter_computational_complexity/space_complexity.assets/space_types.png deleted file mode 100644 index 713a510a8..000000000 Binary files a/site/chapter_computational_complexity/space_complexity.assets/space_types.png and /dev/null differ diff --git a/site/chapter_computational_complexity/space_complexity/index.html b/site/chapter_computational_complexity/space_complexity/index.html deleted file mode 100644 index 4b53a1a22..000000000 --- a/site/chapter_computational_complexity/space_complexity/index.html +++ /dev/null @@ -1,3171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.3. 空间复杂度 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

2.3. 空间复杂度

-

「空间复杂度 Space Complexity」统计 算法使用内存空间随着数据量变大时的增长趋势。这个概念与时间复杂度很类似。

-

2.3.1. 算法相关空间

-

算法运行中,使用的内存空间主要有以下几种:

-
    -
  • 「输入空间」用于存储算法的输入数据;
  • -
  • 「暂存空间」用于存储算法运行中的变量、对象、函数上下文等数据;
  • -
  • 「输出空间」用于存储算法的输出数据;
  • -
-
-

Tip

-

通常情况下,空间复杂度统计范围是「暂存空间」+「输出空间」。

-
-

暂存空间可分为三个部分:

-
    -
  • 「暂存数据」用于保存算法运行中的各种 常量、变量、对象 等。
  • -
  • 「栈帧空间」用于保存调用函数的上下文数据。系统每次调用函数都会在栈的顶部创建一个栈帧,函数返回时,栈帧空间会被释放。
  • -
  • 「指令空间」用于保存编译后的程序指令,在实际统计中一般忽略不计
  • -
-

space_types

-

Fig. 算法使用的相关空间

- -
-
-
-
/* 类 */
-class Node {
-    int val;
-    Node next;
-    Node(int x) { val = x; }
-}
-
-/* 函数 */
-int function() {
-    // do something...
-    return 0;
-}
-
-int algorithm(int n) {        // 输入数据
-    final int a = 0;          // 暂存数据(常量)
-    int b = 0;                // 暂存数据(变量)
-    Node node = new Node(0);  // 暂存数据(对象)
-    int c = function();       // 栈帧空间(调用函数)
-    return a + b + c;         // 输出数据
-}
-
-
-
-
/* 结构体 */
-struct Node {
-    int val;
-    Node *next;
-    Node(int x) : val(x), next(nullptr) {}
-};
-
-/* 函数 */
-int func() {
-    // do something...
-    return 0;
-}
-
-int algorithm(int n) {        // 输入数据
-    const int a = 0;          // 暂存数据(常量)
-    int b = 0;                // 暂存数据(变量)
-    Node* node = new Node(0);  // 暂存数据(对象)
-    int c = func();       // 栈帧空间(调用函数)
-    return a + b + c;         // 输出数据
-}
-
-
-
-
""" 类 """
-class Node:
-    def __init__(self, x):
-        self.val = x      # 结点值
-        self.next = None  # 指向下一结点的指针(引用)
-
-""" 函数 """
-def function():
-    # do something...
-    return 0
-
-def algorithm(n):     # 输入数据
-    b = 0             # 暂存数据(变量)
-    node = Node(0)    # 暂存数据(对象)
-    c = function()    # 栈帧空间(调用函数)
-    return a + b + c  # 输出数据
-
-
-
-
/* 结构体 */
-type node struct {
-    val  int
-    next *node
-}
-
-/* 创建 node 结构体  */
-func newNode(val int) *node {
-    return &node{val: val}
-}
-
-/* 函数 */
-func function() int {
-    // do something...
-    return 0
-}
-
-func algorithm(n int) int { // 输入数据
-    const a = 0             // 暂存数据(常量)
-    b := 0                  // 暂存数据(变量)
-    newNode(0)              // 暂存数据(对象)
-    c := function()         // 栈帧空间(调用函数)
-    return a + b + c        // 输出数据
-}
-
-
-
-
/* 类 */
-class Node {
-    val;
-    next;
-    constructor(val) {
-        this.val = val === undefined ? 0 : val; // 结点值
-        this.next = null;                       // 指向下一结点的引用
-    }
-}
-
-/* 函数 */
-function constFunc() {
-    // do something
-    return 0;
-}
-
-function algorithm(n) {       // 输入数据
-    const a = 0;              // 暂存数据(常量)
-    const b = 0;              // 暂存数据(变量)
-    const node = new Node(0); // 暂存数据(对象)
-    const c = constFunc();    // 栈帧空间(调用函数)
-    return a + b + c;         // 输出数据
-}
-
-
-
-
/* 类 */
-class Node {
-    val: number;
-    next: Node | null;
-    constructor(val?: number) {
-        this.val = val === undefined ? 0 : val; // 结点值
-        this.next = null;                       // 指向下一结点的引用
-    }
-}
-
-/* 函数 */
-function constFunc(): number {
-    // do something
-    return 0;
-}
-
-function algorithm(n: number): number { // 输入数据
-    const a = 0;                        // 暂存数据(常量)
-    const b = 0;                        // 暂存数据(变量)
-    const node = new Node(0);           // 暂存数据(对象)
-    const c = constFunc();              // 栈帧空间(调用函数)
-    return a + b + c;                   // 输出数据
-}
-
-
-
-

-
-
-
-
/* 类 */
-class Node
-{
-    int val;
-    Node next;
-    Node(int x) { val = x; }
-}
-
-/* 函数 */
-int function()
-{
-    // do something...
-    return 0;
-}
-
-int algorithm(int n)          // 输入数据
-{
-    int a = 0;                // 暂存数据(常量)
-    int b = 0;                // 暂存数据(变量)
-    Node node = new Node(0);  // 暂存数据(对象)
-    int c = function();       // 栈帧空间(调用函数)
-    return a + b + c;         // 输出数据
-}
-
-
-
-
/* 类 */
-class Node {
-    var val: Int
-    var next: Node?
-
-    init(x: Int) {
-        val = x
-    }
-}
-
-/* 函数 */
-func function() -> Int {
-    // do something...
-    return 0
-}
-
-func algorithm(n: Int) -> Int { // 输入数据
-    let a = 0 // 暂存数据(常量)
-    var b = 0 // 暂存数据(变量)
-    let node = Node(x: 0) // 暂存数据(对象)
-    let c = function() // 栈帧空间(调用函数)
-    return a + b + c // 输出数据
-}
-
-
-
-

-
-
-
-
-

2.3.2. 推算方法

-

空间复杂度的推算方法和时间复杂度总体类似,只是从统计“计算操作数量”变为统计“使用空间大小”。与时间复杂度不同的是,我们一般只关注「最差空间复杂度」。这是因为内存空间是一个硬性要求,我们必须保证在所有输入数据下都有足够的内存空间预留。

-

最差空间复杂度中的“最差”有两层含义,分别为输入数据的最差分布、算法运行中的最差时间点。

-
    -
  • 以最差输入数据为准。当 \(n < 10\) 时,空间复杂度为 \(O(1)\) ;但是当 \(n > 10\) 时,初始化的数组 nums 使用 \(O(n)\) 空间;因此最差空间复杂度为 \(O(n)\)
  • -
  • 以算法运行过程中的峰值内存为准。程序在执行最后一行之前,使用 \(O(1)\) 空间;当初始化数组 nums 时,程序使用 \(O(n)\) 空间;因此最差空间复杂度为 \(O(n)\)
  • -
-
-
-
-
void algorithm(int n) {
-    int a = 0;                   // O(1)
-    int[] b = new int[10000];    // O(1)
-    if (n > 10)
-        int[] nums = new int[n]; // O(n)
-}
-
-
-
-
void algorithm(int n) {
-    int a = 0;               // O(1)
-    vector<int> b(10000);    // O(1)
-    if (n > 10)
-        vector<int> nums(n); // O(n)
-}
-
-
-
-
def algorithm(n):
-    a = 0               # O(1)
-    b = [0] * 10000     # O(1)
-    if n > 10:
-        nums = [0] * n  # O(n)
-
-
-
-
func algorithm(n int) {
-    a := 0                      // O(1)
-    b := make([]int, 10000)     // O(1)
-    var nums []int
-    if n > 10 {
-        nums := make([]int, n)  // O(n)
-    }
-    fmt.Println(a, b, nums)
-}
-
-
-
-
function algorithm(n) {
-    const a = 0;                   // O(1)
-    const b = new Array(10000);    // O(1)
-    if (n > 10) {
-        const nums = new Array(n); // O(n)
-    }
-}
-
-
-
-
function algorithm(n: number): void {
-    const a = 0;                   // O(1)
-    const b = new Array(10000);    // O(1)
-    if (n > 10) {
-        const nums = new Array(n); // O(n)
-    }
-}
-
-
-
-

-
-
-
-
void algorithm(int n)
-{
-    int a = 0;                   // O(1)
-    int[] b = new int[10000];    // O(1)
-    if (n > 10)
-    {
-        int[] nums = new int[n]; // O(n)
-    }
-}
-
-
-
-
func algorithm(n: Int) {
-    let a = 0 // O(1)
-    let b = Array(repeating: 0, count: 10000) // O(1)
-    if n > 10 {
-        let nums = Array(repeating: 0, count: n) // O(n)
-    }
-}
-
-
-
-

-
-
-
-
-

在递归函数中,需要注意统计栈帧空间。例如函数 loop(),在循环中调用了 \(n\)function() ,每轮中的 function() 都返回并释放了栈帧空间,因此空间复杂度仍为 \(O(1)\) 。而递归函数 recur() 在运行中会同时存在 \(n\) 个未返回的 recur() ,从而使用 \(O(n)\) 的栈帧空间。

-
-
-
-
int function() {
-    // do something
-    return 0;
-}
-/* 循环 O(1) */
-void loop(int n) {
-    for (int i = 0; i < n; i++) {
-        function();
-    }
-}
-/* 递归 O(n) */
-void recur(int n) {
-    if (n == 1) return;
-    return recur(n - 1);
-}
-
-
-
-
int func() {
-    // do something
-    return 0;
-}
-/* 循环 O(1) */
-void loop(int n) {
-    for (int i = 0; i < n; i++) {
-        func();
-    }
-}
-/* 递归 O(n) */
-void recur(int n) {
-    if (n == 1) return;
-    return recur(n - 1);
-}
-
-
-
-
def function():
-    # do something
-    return 0
-
-""" 循环 O(1) """
-def loop(n):
-    for _ in range(n):
-        function()
-
-""" 递归 O(n) """
-def recur(n):
-    if n == 1: return
-    return recur(n - 1)
-
-
-
-
func function() int {
-    // do something
-    return 0
-}
-
-/* 循环 O(1) */
-func loop(n int) {
-    for i := 0; i < n; i++ {
-        function()
-    }
-}
-
-/* 递归 O(n) */
-func recur(n int) {
-    if n == 1 {
-        return
-    }
-    recur(n - 1)
-}
-
-
-
-
function constFunc() {
-    // do something
-    return 0;
-}
-/* 循环 O(1) */
-function loop(n) {
-    for (let i = 0; i < n; i++) {
-        constFunc();
-    }
-}
-/* 递归 O(n) */
-function recur(n) {
-    if (n === 1) return;
-    return recur(n - 1);
-}
-
-
-
-
function constFunc(): number {
-    // do something
-    return 0;
-}
-/* 循环 O(1) */
-function loop(n: number): void {
-    for (let i = 0; i < n; i++) {
-        constFunc();
-    }
-}
-/* 递归 O(n) */
-function recur(n: number): void {
-    if (n === 1) return;
-    return recur(n - 1);
-}
-
-
-
-

-
-
-
-
int function()
-{
-    // do something
-    return 0;
-}
-/* 循环 O(1) */
-void loop(int n)
-{
-    for (int i = 0; i < n; i++)
-    {
-        function();
-    }
-}
-/* 递归 O(n) */
-int recur(int n)
-{
-    if (n == 1) return 1;
-    return recur(n - 1);
-}
-
-
-
-
@discardableResult
-func function() -> Int {
-    // do something
-    return 0
-}
-
-/* 循环 O(1) */
-func loop(n: Int) {
-    for _ in 0 ..< n {
-        function()
-    }
-}
-
-/* 递归 O(n) */
-func recur(n: Int) {
-    if n == 1 {
-        return
-    }
-    recur(n: n - 1)
-}
-
-
-
-

-
-
-
-
-

2.3.3. 常见类型

-

设输入数据大小为 \(n\) ,常见的空间复杂度类型有(从低到高排列)

-
\[ -\begin{aligned} -O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline -\text{常数阶} < \text{对数阶} < \text{线性阶} < \text{平方阶} < \text{指数阶} -\end{aligned} -\]
-

space_complexity_common_types

-

Fig. 空间复杂度的常见类型

- -
-

Tip

-

部分示例代码需要一些前置知识,包括数组、链表、二叉树、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解空间复杂度含义和推算方法上。

-
-

常数阶 \(O(1)\)

-

常数阶常见于数量与输入数据大小 \(n\) 无关的常量、变量、对象。

-

需要注意的是,在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,即不会累积占用空间,空间复杂度仍为 \(O(1)\)

-
-
-
-
space_complexity.java
/* 常数阶 */
-void constant(int n) {
-    // 常量、变量、对象占用 O(1) 空间
-    final int a = 0;
-    int b = 0;
-    int[] nums = new int[10000];
-    ListNode node = new ListNode(0);
-    // 循环中的变量占用 O(1) 空间
-    for (int i = 0; i < n; i++) {
-        int c = 0;
-    }
-    // 循环中的函数占用 O(1) 空间
-    for (int i = 0; i < n; i++) {
-        function();
-    }
-}
-
-
-
-
space_complexity.cpp
/* 常数阶 */
-void constant(int n) {
-    // 常量、变量、对象占用 O(1) 空间
-    const int a = 0;
-    int b = 0;
-    vector<int> nums(10000);
-    ListNode node(0);
-    // 循环中的变量占用 O(1) 空间
-    for (int i = 0; i < n; i++) {
-        int c = 0;
-    }
-    // 循环中的函数占用 O(1) 空间
-    for (int i = 0; i < n; i++) {
-        func();
-    }
-}
-
-
-
-
space_complexity.py
""" 常数阶 """
-def constant(n):
-    # 常量、变量、对象占用 O(1) 空间
-    a = 0
-    nums = [0] * 10000
-    node = ListNode(0)
-    # 循环中的变量占用 O(1) 空间
-    for _ in range(n):
-        c = 0
-    # 循环中的函数占用 O(1) 空间
-    for _ in range(n):
-        function()
-
-
-
-
space_complexity.go
/* 常数阶 */
-func spaceConstant(n int) {
-    // 常量、变量、对象占用 O(1) 空间
-    const a = 0
-    b := 0
-    nums := make([]int, 10000)
-    ListNode := newNode(0)
-    // 循环中的变量占用 O(1) 空间
-    var c int
-    for i := 0; i < n; i++ {
-        c = 0
-    }
-    // 循环中的函数占用 O(1) 空间
-    for i := 0; i < n; i++ {
-        function()
-    }
-    fmt.Println(a, b, nums, c, ListNode)
-}
-
-
-
-
space_complexity.js
/* 常数阶 */
-function constant(n) {
-    // 常量、变量、对象占用 O(1) 空间
-    const a = 0;
-    const b = 0;
-    const nums = new Array(10000);
-    const node = new ListNode(0);
-    // 循环中的变量占用 O(1) 空间
-    for (let i = 0; i < n; i++) {
-        const c = 0;
-    }
-    // 循环中的函数占用 O(1) 空间
-    for (let i = 0; i < n; i++) {
-        constFunc();
-    }
-}
-
-
-
-
space_complexity.ts
/* 常数阶 */
-function constant(n: number): void {
-    // 常量、变量、对象占用 O(1) 空间
-    const a = 0;
-    const b = 0;
-    const nums = new Array(10000);
-    const node = new ListNode(0);
-    // 循环中的变量占用 O(1) 空间
-    for (let i = 0; i < n; i++) {
-        const c = 0;
-    }
-    // 循环中的函数占用 O(1) 空间
-    for (let i = 0; i < n; i++) {
-        constFunc();
-    }
-}
-
-
-
-
space_complexity.c

-
-
-
-
space_complexity.cs
/* 常数阶 */
-void constant(int n)
-{
-    // 常量、变量、对象占用 O(1) 空间
-    int a = 0;
-    int b = 0;
-    int[] nums = new int[10000];
-    ListNode node = new ListNode(0);
-    // 循环中的变量占用 O(1) 空间
-    for (int i = 0; i < n; i++)
-    {
-        int c = 0;
-    }
-    // 循环中的函数占用 O(1) 空间
-    for (int i = 0; i < n; i++)
-    {
-        function();
-    }
-}
-
-
-
-
space_complexity.swift
/* 常数阶 */
-func constant(n: Int) {
-    // 常量、变量、对象占用 O(1) 空间
-    let a = 0
-    var b = 0
-    let nums = Array(repeating: 0, count: 10000)
-    let node = ListNode(x: 0)
-    // 循环中的变量占用 O(1) 空间
-    for _ in 0 ..< n {
-        let c = 0
-    }
-    // 循环中的函数占用 O(1) 空间
-    for _ in 0 ..< n {
-        function()
-    }
-}
-
-
-
-
space_complexity.zig
// 常数阶
-fn constant(n: i32) void {
-    // 常量、变量、对象占用 O(1) 空间
-    const a: i32 = 0;
-    var b: i32 = 0;
-    var nums = [_]i32{0}**10000;
-    var node = inc.ListNode(i32){.val = 0};
-    var i: i32 = 0;
-    // 循环中的变量占用 O(1) 空间
-    while (i < n) : (i += 1) {
-        var c: i32 = 0;
-        _ = c;
-    }
-    // 循环中的函数占用 O(1) 空间
-    i = 0;
-    while (i < n) : (i += 1) {
-        _ = function();
-    }
-    _ = a;
-    _ = b;
-    _ = nums;
-    _ = node;
-}
-
-
-
-
-

线性阶 \(O(n)\)

-

线性阶常见于元素数量与 \(n\) 成正比的数组、链表、栈、队列等。

-
-
-
-
space_complexity.java
/* 线性阶 */
-void linear(int n) {
-    // 长度为 n 的数组占用 O(n) 空间
-    int[] nums = new int[n];
-    // 长度为 n 的列表占用 O(n) 空间
-    List<ListNode> nodes = new ArrayList<>();
-    for (int i = 0; i < n; i++) {
-        nodes.add(new ListNode(i));
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    Map<Integer, String> map = new HashMap<>();
-    for (int i = 0; i < n; i++) {
-        map.put(i, String.valueOf(i));
-    }
-}
-
-
-
-
space_complexity.cpp
/* 线性阶 */
-void linear(int n) {
-    // 长度为 n 的数组占用 O(n) 空间
-    vector<int> nums(n);
-    // 长度为 n 的列表占用 O(n) 空间
-    vector<ListNode> nodes;
-    for (int i = 0; i < n; i++) {
-        nodes.push_back(ListNode(i));
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    unordered_map<int, string> map;
-    for (int i = 0; i < n; i++) {
-        map[i] = to_string(i);
-    }
-}
-
-
-
-
space_complexity.py
""" 线性阶 """
-def linear(n):
-    # 长度为 n 的列表占用 O(n) 空间
-    nums = [0] * n
-    # 长度为 n 的哈希表占用 O(n) 空间
-    mapp = {}
-    for i in range(n):
-        mapp[i] = str(i)
-
-
-
-
space_complexity.go
/* 线性阶 */
-func spaceLinear(n int) {
-    // 长度为 n 的数组占用 O(n) 空间
-    _ = make([]int, n)
-    // 长度为 n 的列表占用 O(n) 空间
-    var nodes []*node
-    for i := 0; i < n; i++ {
-        nodes = append(nodes, newNode(i))
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    m := make(map[int]string, n)
-    for i := 0; i < n; i++ {
-        m[i] = strconv.Itoa(i)
-    }
-}
-
-
-
-
space_complexity.js
/* 线性阶 */
-function linear(n) {
-    // 长度为 n 的数组占用 O(n) 空间
-    const nums = new Array(n);
-    // 长度为 n 的列表占用 O(n) 空间
-    const nodes = [];
-    for (let i = 0; i < n; i++) {
-        nodes.push(new ListNode(i));
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    const map = new Map();
-    for (let i = 0; i < n; i++) {
-        map.set(i, i.toString());
-    }
-}
-
-
-
-
space_complexity.ts
/* 线性阶 */
-function linear(n: number): void {
-    // 长度为 n 的数组占用 O(n) 空间
-    const nums = new Array(n);
-    // 长度为 n 的列表占用 O(n) 空间
-    const nodes: ListNode[] = [];
-    for (let i = 0; i < n; i++) {
-        nodes.push(new ListNode(i));
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    const map = new Map();
-    for (let i = 0; i < n; i++) {
-        map.set(i, i.toString());
-    }
-}
-
-
-
-
space_complexity.c

-
-
-
-
space_complexity.cs
/* 线性阶 */
-void linear(int n)
-{
-    // 长度为 n 的数组占用 O(n) 空间
-    int[] nums = new int[n];
-    // 长度为 n 的列表占用 O(n) 空间
-    List<ListNode> nodes = new();
-    for (int i = 0; i < n; i++)
-    {
-        nodes.Add(new ListNode(i));
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    Dictionary<int, String> map = new();
-    for (int i = 0; i < n; i++)
-    {
-        map.Add(i, i.ToString());
-    }
-}
-
-
-
-
space_complexity.swift
/* 线性阶 */
-func linear(n: Int) {
-    // 长度为 n 的数组占用 O(n) 空间
-    let nums = Array(repeating: 0, count: n)
-    // 长度为 n 的列表占用 O(n) 空间
-    let nodes = (0 ..< n).map { ListNode(x: $0) }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") })
-}
-
-
-
-
space_complexity.zig
// 线性阶
-fn linear(comptime n: i32) !void {
-    // 长度为 n 的数组占用 O(n) 空间
-    var nums = [_]i32{0}**n;
-    // 长度为 n 的列表占用 O(n) 空间
-    var nodes = std.ArrayList(i32).init(std.heap.page_allocator);
-    defer nodes.deinit();
-    var i: i32 = 0;
-    while (i < n) : (i += 1) {
-        try nodes.append(i);
-    }
-    // 长度为 n 的哈希表占用 O(n) 空间
-    var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator);
-    defer map.deinit();
-    var j: i32 = 0;
-    while (j < n) : (j += 1) {
-        const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j});
-        defer std.heap.page_allocator.free(string);
-        try map.put(i, string);
-    }
-    _ = nums;
-}
-
-
-
-
-

以下递归函数会同时存在 \(n\) 个未返回的 algorithm() 函数,使用 \(O(n)\) 大小的栈帧空间。

-
-
-
-
space_complexity.java
/* 线性阶(递归实现) */
-void linearRecur(int n) {
-    System.out.println("递归 n = " + n);
-    if (n == 1) return;
-    linearRecur(n - 1);
-}
-
-
-
-
space_complexity.cpp
/* 线性阶(递归实现) */
-void linearRecur(int n) {
-    cout << "递归 n = " << n << endl;
-    if (n == 1) return;
-    linearRecur(n - 1);
-}
-
-
-
-
space_complexity.py
""" 线性阶(递归实现) """
-def linear_recur(n):
-    print("递归 n =", n)
-    if n == 1: return
-    linear_recur(n - 1)
-
-
-
-
space_complexity.go
/* 线性阶(递归实现) */
-func spaceLinearRecur(n int) {
-    fmt.Println("递归 n =", n)
-    if n == 1 {
-        return
-    }
-    spaceLinearRecur(n - 1)
-}
-
-
-
-
space_complexity.js
/* 线性阶(递归实现) */
-function linearRecur(n) {
-    console.log(`递归 n = ${n}`);
-    if (n === 1) return;
-    linearRecur(n - 1);
-}
-
-
-
-
space_complexity.ts
/* 线性阶(递归实现) */
-function linearRecur(n: number): void {
-    console.log(`递归 n = ${n}`);
-    if (n === 1) return;
-    linearRecur(n - 1);
-}
-
-
-
-
space_complexity.c

-
-
-
-
space_complexity.cs
/* 线性阶(递归实现) */
-void linearRecur(int n)
-{
-    Console.WriteLine("递归 n = " + n);
-    if (n == 1) return;
-    linearRecur(n - 1);
-}
-
-
-
-
space_complexity.swift
/* 线性阶(递归实现) */
-func linearRecur(n: Int) {
-    print("递归 n = \(n)")
-    if n == 1 {
-        return
-    }
-    linearRecur(n: n - 1)
-}
-
-
-
-
space_complexity.zig
// 线性阶(递归实现)
-fn linearRecur(comptime n: i32) void {
-    std.debug.print("递归 n = {}\n", .{n});
-    if (n == 1) return;
-    linearRecur(n - 1);
-}
-
-
-
-
-

space_complexity_recursive_linear

-

Fig. 递归函数产生的线性阶空间复杂度

- -

平方阶 \(O(n^2)\)

-

平方阶常见于元素数量与 \(n\) 成平方关系的矩阵、图。

-
-
-
-
space_complexity.java
/* 平方阶 */
-void quadratic(int n) {
-    // 矩阵占用 O(n^2) 空间
-    int[][] numMatrix = new int[n][n];
-    // 二维列表占用 O(n^2) 空间
-    List<List<Integer>> numList = new ArrayList<>();
-    for (int i = 0; i < n; i++) {
-        List<Integer> tmp = new ArrayList<>();
-        for (int j = 0; j < n; j++) {
-            tmp.add(0);
-        }
-        numList.add(tmp);
-    }
-}
-
-
-
-
space_complexity.cpp
/* 平方阶 */
-void quadratic(int n) {
-    // 二维列表占用 O(n^2) 空间
-    vector<vector<int>> numMatrix;
-    for (int i = 0; i < n; i++) {
-        vector<int> tmp;
-        for (int j = 0; j < n; j++) {
-            tmp.push_back(0);
-        }
-        numMatrix.push_back(tmp);
-    }
-}
-
-
-
-
space_complexity.py
""" 平方阶 """
-def quadratic(n):
-    # 二维列表占用 O(n^2) 空间
-    num_matrix = [[0] * n for _ in range(n)]
-
-
-
-
space_complexity.go
/* 平方阶 */
-func spaceQuadratic(n int) {
-    // 矩阵占用 O(n^2) 空间
-    numMatrix := make([][]int, n)
-    for i := 0; i < n; i++ {
-        numMatrix[i] = make([]int, n)
-    }
-}
-
-
-
-
space_complexity.js
/* 平方阶 */
-function quadratic(n) {
-    // 矩阵占用 O(n^2) 空间
-    const numMatrix = Array(n).fill(null).map(() => Array(n).fill(null));
-    // 二维列表占用 O(n^2) 空间
-    const numList = [];
-    for (let i = 0; i < n; i++) {
-        const tmp = [];
-        for (let j = 0; j < n; j++) {
-            tmp.push(0);
-        }
-        numList.push(tmp);
-    }
-}
-
-
-
-
space_complexity.ts
/* 平方阶 */
-function quadratic(n: number): void {
-    // 矩阵占用 O(n^2) 空间
-    const numMatrix = Array(n).fill(null).map(() => Array(n).fill(null));
-    // 二维列表占用 O(n^2) 空间
-    const numList = [];
-    for (let i = 0; i < n; i++) {
-        const tmp = [];
-        for (let j = 0; j < n; j++) {
-            tmp.push(0);
-        }
-        numList.push(tmp);
-    }
-}
-
-
-
-
space_complexity.c

-
-
-
-
space_complexity.cs
/* 平方阶 */
-void quadratic(int n)
-{
-    // 矩阵占用 O(n^2) 空间
-    int[,] numMatrix = new int[n, n];
-    // 二维列表占用 O(n^2) 空间
-    List<List<int>> numList = new();
-    for (int i = 0; i < n; i++)
-    {
-        List<int> tmp = new();
-        for (int j = 0; j < n; j++)
-        {
-            tmp.Add(0);
-        }
-        numList.Add(tmp);
-    }
-}
-
-
-
-
space_complexity.swift
/* 平方阶 */
-func quadratic(n: Int) {
-    // 二维列表占用 O(n^2) 空间
-    let numList = Array(repeating: Array(repeating: 0, count: n), count: n)
-}
-
-
-
-
space_complexity.zig
// 平方阶
-fn quadratic(n: i32) !void {
-    // 二维列表占用 O(n^2) 空间
-    var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator);
-    defer nodes.deinit();
-    var i: i32 = 0;
-    while (i < n) : (i += 1) {
-        var tmp = std.ArrayList(i32).init(std.heap.page_allocator);
-        defer tmp.deinit();
-        var j: i32 = 0;
-        while (j < n) : (j += 1) {
-            try tmp.append(0);
-        }
-        try nodes.append(tmp);
-    }
-}
-
-
-
-
-

在以下递归函数中,同时存在 \(n\) 个未返回的 algorithm() ,并且每个函数中都初始化了一个数组,长度分别为 \(n, n-1, n-2, ..., 2, 1\) ,平均长度为 \(\frac{n}{2}\) ,因此总体使用 \(O(n^2)\) 空间。

-
-
-
-
space_complexity.java
/* 平方阶(递归实现) */
-int quadraticRecur(int n) {
-    if (n <= 0) return 0;
-    // 数组 nums 长度为 n, n-1, ..., 2, 1
-    int[] nums = new int[n];
-    System.out.println("递归 n = " + n + " 中的 nums 长度 = " + nums.length);
-    return quadraticRecur(n - 1);
-}
-
-
-
-
space_complexity.cpp
/* 平方阶(递归实现) */
-int quadraticRecur(int n) {
-    if (n <= 0) return 0;
-    vector<int> nums(n);
-    cout << "递归 n = " << n << " 中的 nums 长度 = " << nums.size() << endl;
-    return quadraticRecur(n - 1);
-}
-
-
-
-
space_complexity.py
""" 平方阶(递归实现) """
-def quadratic_recur(n):
-    if n <= 0: return 0
-    # 数组 nums 长度为 n, n-1, ..., 2, 1
-    nums = [0] * n
-    return quadratic_recur(n - 1)
-
-
-
-
space_complexity.go
/* 平方阶(递归实现) */
-func spaceQuadraticRecur(n int) int {
-    if n <= 0 {
-        return 0
-    }
-    // 数组 nums 长度为 n, n-1, ..., 2, 1
-    nums := make([]int, n)
-    return spaceQuadraticRecur(n - 1)
-}
-
-
-
-
space_complexity.js
/* 平方阶(递归实现) */
-function quadraticRecur(n) {
-    if (n <= 0) return 0;
-    const nums = new Array(n);
-    console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`);
-    return quadraticRecur(n - 1);
-}
-
-
-
-
space_complexity.ts
/* 平方阶(递归实现) */
-function quadraticRecur(n: number): number {
-    if (n <= 0) return 0;
-    const nums = new Array(n);
-    console.log(`递归 n = ${n} 中的 nums 长度 = ${nums.length}`);
-    return quadraticRecur(n - 1);
-}
-
-
-
-
space_complexity.c

-
-
-
-
space_complexity.cs
/* 平方阶(递归实现) */
-int quadraticRecur(int n)
-{
-    if (n <= 0) return 0;
-    // 数组 nums 长度为 n, n-1, ..., 2, 1
-    int[] nums = new int[n];
-    return quadraticRecur(n - 1);
-}
-
-
-
-
space_complexity.swift
/* 平方阶(递归实现) */
-func quadraticRecur(n: Int) -> Int {
-    if n <= 0 {
-        return 0
-    }
-    // 数组 nums 长度为 n, n-1, ..., 2, 1
-    let nums = Array(repeating: 0, count: n)
-    return quadraticRecur(n: n - 1)
-}
-
-
-
-
space_complexity.zig
// 平方阶(递归实现)
-fn quadraticRecur(comptime n: i32) i32 {
-    if (n <= 0) return 0;
-    var nums = [_]i32{0}**n;
-    std.debug.print("递归 n = {} 中的 nums 长度 = {}\n", .{n, nums.len});
-    return quadraticRecur(n - 1);
-}
-
-
-
-
-

space_complexity_recursive_quadratic

-

Fig. 递归函数产生的平方阶空间复杂度

- -

指数阶 \(O(2^n)\)

-

指数阶常见于二叉树。高度为 \(n\) 的「满二叉树」的结点数量为 \(2^n - 1\) ,使用 \(O(2^n)\) 空间。

-
-
-
-
space_complexity.java
/* 指数阶(建立满二叉树) */
-TreeNode buildTree(int n) {
-    if (n == 0) return null;
-    TreeNode root = new TreeNode(0);
-    root.left = buildTree(n - 1);
-    root.right = buildTree(n - 1);
-    return root;
-}
-
-
-
-
space_complexity.cpp
/* 指数阶(建立满二叉树) */
-TreeNode* buildTree(int n) {
-    if (n == 0) return nullptr;
-    TreeNode* root = new TreeNode(0);
-    root->left = buildTree(n - 1);
-    root->right = buildTree(n - 1);
-    return root;
-}
-
-
-
-
space_complexity.py
""" 指数阶(建立满二叉树) """
-def build_tree(n):
-    if n == 0: return None
-    root = TreeNode(0)
-    root.left = build_tree(n - 1)
-    root.right = build_tree(n - 1)
-    return root
-
-
-
-
space_complexity.go
/* 指数阶(建立满二叉树) */
-func buildTree(n int) *treeNode {
-    if n == 0 {
-        return nil
-    }
-    root := newTreeNode(0)
-    root.left = buildTree(n - 1)
-    root.right = buildTree(n - 1)
-    return root
-}
-
-
-
-
space_complexity.js
/* 指数阶(建立满二叉树) */
-function buildTree(n) {
-    if (n === 0) return null;
-    const root = new TreeNode(0);
-    root.left = buildTree(n - 1);
-    root.right = buildTree(n - 1);
-    return root;
-}
-
-
-
-
space_complexity.ts
/* 指数阶(建立满二叉树) */
-function buildTree(n: number): TreeNode | null {
-    if (n === 0) return null;
-    const root = new TreeNode(0);
-    root.left = buildTree(n - 1);
-    root.right = buildTree(n - 1);
-    return root;
-}
-
-
-
-
space_complexity.c

-
-
-
-
space_complexity.cs
/* 指数阶(建立满二叉树) */
-TreeNode? buildTree(int n)
-{
-    if (n == 0) return null;
-    TreeNode root = new TreeNode(0);
-    root.left = buildTree(n - 1);
-    root.right = buildTree(n - 1);
-    return root;
-}
-
-
-
-
space_complexity.swift
/* 指数阶(建立满二叉树) */
-func buildTree(n: Int) -> TreeNode? {
-    if n == 0 {
-        return nil
-    }
-    let root = TreeNode(x: 0)
-    root.left = buildTree(n: n - 1)
-    root.right = buildTree(n: n - 1)
-    return root
-}
-
-
-
-
space_complexity.zig
// 指数阶(建立满二叉树)
-fn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) {
-    if (n == 0) return null;
-    const root = try mem_allocator.create(inc.TreeNode(i32));
-    root.init(0);
-    root.left = try buildTree(mem_allocator, n - 1);
-    root.right = try buildTree(mem_allocator, n - 1);
-    return root;
-}
-
-
-
-
-

space_complexity_exponential

-

Fig. 满二叉树下的指数阶空间复杂度

- -

对数阶 \(O(\log n)\)

-

对数阶常见于分治算法、数据类型转换等。

-

例如「归并排序」,长度为 \(n\) 的数组可以形成高度为 \(\log n\) 的递归树,因此空间复杂度为 \(O(\log n)\)

-

再例如「数字转化为字符串」,输入任意正整数 \(n\) ,它的位数为 \(\log_{10} n\) ,即对应字符串长度为 \(\log_{10} n\) ,因此空间复杂度为 \(O(\log_{10} n) = O(\log n)\)

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_computational_complexity/space_time_tradeoff/index.html b/site/chapter_computational_complexity/space_time_tradeoff/index.html deleted file mode 100644 index 82b733a12..000000000 --- a/site/chapter_computational_complexity/space_time_tradeoff/index.html +++ /dev/null @@ -1,2102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.4. 权衡时间与空间 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

2.4. 权衡时间与空间

-

理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。

-

降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然。我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。

-

大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,因此以空间换时间最为常用

-

2.4.1. 示例题目 *

-

以 LeetCode 全站第一题 两数之和 为例。

-
-

两数之和

-

给定一个整数数组 nums 和一个整数目标值 target ,请你在该数组中找出“和”为目标值 target 的那两个整数,并返回它们的数组下标。

-

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

-

你可以按任意顺序返回答案。

-
-

「暴力枚举」和「辅助哈希表」分别为 空间最优时间最优 的两种解法。本着时间比空间更宝贵的原则,后者是本题的最佳解法。

-

方法一:暴力枚举

-

时间复杂度 \(O(N^2)\) ,空间复杂度 \(O(1)\) ,属于「时间换空间」。

-

虽然仅使用常数大小的额外空间,但运行速度过慢。

-
-
-
-
leetcode_two_sum.java
/* 方法一:暴力枚举 */
-class SolutionBruteForce {
-    public int[] twoSum(int[] nums, int target) {
-        int size = nums.length;
-        // 两层循环,时间复杂度 O(n^2)
-        for (int i = 0; i < size - 1; i++) {
-            for (int j = i + 1; j < size; j++) {
-                if (nums[i] + nums[j] == target)
-                    return new int[] { i, j };
-            }
-        }
-        return new int[0];
-    }
-}
-
-
-
-
leetcode_two_sum.cpp
class SolutionBruteForce {
-public:
-    vector<int> twoSum(vector<int>& nums, int target) {
-        int size = nums.size();
-        // 两层循环,时间复杂度 O(n^2)
-        for (int i = 0; i < size - 1; i++) {
-            for (int j = i + 1; j < size; j++) {
-                if (nums[i] + nums[j] == target)
-                    return { i, j };
-            }
-        }
-        return {};
-    }
-};
-
-
-
-
leetcode_two_sum.py
""" 方法一:暴力枚举 """
-class SolutionBruteForce:
-    def twoSum(self, nums: List[int], target: int) -> List[int]:
-        # 两层循环,时间复杂度 O(n^2)
-        for i in range(len(nums) - 1):
-            for j in range(i + 1, len(nums)):
-                if nums[i] + nums[j] == target:
-                    return i, j
-        return []
-
-
-
-
leetcode_two_sum.go
func twoSumBruteForce(nums []int, target int) []int {
-    size := len(nums)
-    // 两层循环,时间复杂度 O(n^2)
-    for i := 0; i < size-1; i++ {
-        for j := i + 1; i < size; j++ {
-            if nums[i]+nums[j] == target {
-                return []int{i, j}
-            }
-        }
-    }
-    return nil
-}
-
-
-
-
leetcode_two_sum.js
function twoSumBruteForce(nums, target) {
-    const n = nums.length;
-    // 两层循环,时间复杂度 O(n^2)
-    for (let i = 0; i < n; i++) {
-        for (let j = i + 1; j < n; j++) {
-            if (nums[i] + nums[j] === target) {
-                return [i, j];
-            }
-        }
-    }
-    return [];
-}
-
-
-
-
leetcode_two_sum.ts
function twoSumBruteForce(nums: number[], target: number): number[] {
-    const n = nums.length;
-    // 两层循环,时间复杂度 O(n^2)
-    for (let i = 0; i < n; i++) {
-        for (let j = i + 1; j < n; j++) {
-            if (nums[i] + nums[j] === target) {
-                return [i, j];
-            }
-        }
-    }
-    return [];
-};
-
-
-
-
leetcode_two_sum.c

-
-
-
-
leetcode_two_sum.cs
class SolutionBruteForce
-{
-    public int[] twoSum(int[] nums, int target)
-    {
-        int size = nums.Length;
-        // 两层循环,时间复杂度 O(n^2)
-        for (int i = 0; i < size - 1; i++)
-        {
-            for (int j = i + 1; j < size; j++)
-            {
-                if (nums[i] + nums[j] == target)
-                    return new int[] { i, j };
-            }
-        }
-        return new int[0];
-    }
-}
-
-
-
-
leetcode_two_sum.swift
func twoSumBruteForce(nums: [Int], target: Int) -> [Int] {
-    // 两层循环,时间复杂度 O(n^2)
-    for i in nums.indices.dropLast() {
-        for j in nums.indices.dropFirst(i + 1) {
-            if nums[i] + nums[j] == target {
-                return [i, j]
-            }
-        }
-    }
-    return [0]
-}
-
-
-
-
leetcode_two_sum.zig
const SolutionBruteForce = struct {
-    pub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 {
-        _ = self;
-        var size: usize = nums.len;
-        var i: usize = 0;
-        // 两层循环,时间复杂度 O(n^2)
-        while (i < size - 1) : (i += 1) {
-            var j = i + 1;
-            while (j < size) : (j += 1) {
-                if (nums[i] + nums[j] == target) {
-                    return [_]i32{@intCast(i32, i), @intCast(i32, j)};
-                }
-            }
-        }
-        return undefined;
-    }
-};
-
-
-
-
-

方法二:辅助哈希表

-

时间复杂度 \(O(N)\) ,空间复杂度 \(O(N)\) ,属于「空间换时间」。

-

借助辅助哈希表 dic ,通过保存数组元素与索引的映射来提升算法运行速度。

-
-
-
-
leetcode_two_sum.java
/* 方法二:辅助哈希表 */
-class SolutionHashMap {
-    public int[] twoSum(int[] nums, int target) {
-        int size = nums.length;
-        // 辅助哈希表,空间复杂度 O(n)
-        Map<Integer, Integer> dic = new HashMap<>();
-        // 单层循环,时间复杂度 O(n)
-        for (int i = 0; i < size; i++) {
-            if (dic.containsKey(target - nums[i])) {
-                return new int[] { dic.get(target - nums[i]), i };
-            }
-            dic.put(nums[i], i);
-        }
-        return new int[0];
-    }
-}
-
-
-
-
leetcode_two_sum.cpp
class SolutionHashMap {
-public:
-    vector<int> twoSum(vector<int>& nums, int target) {
-        int size = nums.size();
-        // 辅助哈希表,空间复杂度 O(n)
-        unordered_map<int, int> dic;
-        // 单层循环,时间复杂度 O(n)
-        for (int i = 0; i < size; i++) {
-            if (dic.find(target - nums[i]) != dic.end()) {
-                return { dic[target - nums[i]], i };
-            }
-            dic.emplace(nums[i], i);
-        }
-        return {};
-    }
-};
-
-
-
-
leetcode_two_sum.py
""" 方法二:辅助哈希表 """
-class SolutionHashMap:
-    def twoSum(self, nums: List[int], target: int) -> List[int]:
-        # 辅助哈希表,空间复杂度 O(n)
-        dic = {}
-        # 单层循环,时间复杂度 O(n)
-        for i in range(len(nums)):
-            if target - nums[i] in dic:
-                return dic[target - nums[i]], i
-            dic[nums[i]] = i
-        return []
-
-
-
-
leetcode_two_sum.go
func twoSumHashTable(nums []int, target int) []int {
-    // 辅助哈希表,空间复杂度 O(n)
-    hashTable := map[int]int{}
-    // 单层循环,时间复杂度 O(n)
-    for idx, val := range nums {
-        if preIdx, ok := hashTable[target-val]; ok {
-            return []int{preIdx, idx}
-        }
-        hashTable[val] = idx
-    }
-    return nil
-}
-
-
-
-
leetcode_two_sum.js
function twoSumHashTable(nums, target) {
-    // 辅助哈希表,空间复杂度 O(n)
-    let m = {};
-    // 单层循环,时间复杂度 O(n)
-    for (let i = 0; i < nums.length; i++) {
-        if (m[nums[i]] !== undefined) {
-            return [m[nums[i]], i];
-        } else {
-            m[target - nums[i]] = i;
-        }
-    }
-    return [];
-}
-
-
-
-
leetcode_two_sum.ts
function twoSumHashTable(nums: number[], target: number): number[] {
-    // 辅助哈希表,空间复杂度 O(n)
-    let m: Map<number, number> = new Map();
-    // 单层循环,时间复杂度 O(n)
-    for (let i = 0; i < nums.length; i++) {
-        let index = m.get(nums[i]);
-        if (index !== undefined) {
-            return [index, i];
-        } else {
-            m.set(target - nums[i], i);
-        }
-    }
-    return [];
-};
-
-
-
-
leetcode_two_sum.c

-
-
-
-
leetcode_two_sum.cs
class SolutionHashMap
-{
-    public int[] twoSum(int[] nums, int target)
-    {
-        int size = nums.Length;
-        // 辅助哈希表,空间复杂度 O(n)
-        Dictionary<int, int> dic = new();
-        // 单层循环,时间复杂度 O(n)
-        for (int i = 0; i < size; i++)
-        {
-            if (dic.ContainsKey(target - nums[i]))
-            {
-                return new int[] { dic[target - nums[i]], i };
-            }
-            dic.Add(nums[i], i);
-        }
-        return new int[0];
-    }
-}
-
-
-
-
leetcode_two_sum.swift
func twoSumHashTable(nums: [Int], target: Int) -> [Int] {
-    // 辅助哈希表,空间复杂度 O(n)
-    var dic: [Int: Int] = [:]
-    // 单层循环,时间复杂度 O(n)
-    for i in nums.indices {
-        if let j = dic[target - nums[i]] {
-            return [j, i]
-        }
-        dic[nums[i]] = i
-    }
-    return [0]
-}
-
-
-
-
leetcode_two_sum.zig
const SolutionHashMap = struct {
-    pub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 {
-        _ = self;
-        var size: usize = nums.len;
-        // 辅助哈希表,空间复杂度 O(n)
-        var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator);
-        defer dic.deinit();
-        var i: usize = 0;
-        // 单层循环,时间复杂度 O(n)
-        while (i < size) : (i += 1) {
-            if (dic.contains(target - nums[i])) {
-                return [_]i32{dic.get(target - nums[i]).?, @intCast(i32, i)};
-            }
-            try dic.put(nums[i], @intCast(i32, i));
-        }
-        return undefined;
-    }
-};
-
-
-
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_computational_complexity/summary/index.html b/site/chapter_computational_complexity/summary/index.html deleted file mode 100644 index 98ce54c71..000000000 --- a/site/chapter_computational_complexity/summary/index.html +++ /dev/null @@ -1,1779 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.5. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

2.5. 小结

-

算法效率评估

-
    -
  • 「时间效率」和「空间效率」是算法性能的两个重要的评价维度。
  • -
  • 我们可以通过「实际测试」来评估算法效率,但难以排除测试环境的干扰,并且非常耗费计算资源。
  • -
  • 「复杂度分析」克服了实际测试的弊端,分析结果适用于所有运行平台,并且可以体现不同数据大小下的算法效率。
  • -
-

时间复杂度

-
    -
  • 「时间复杂度」统计算法运行时间随着数据量变大时的增长趋势,可以有效评估算法效率,但在某些情况下可能失效,比如在输入数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣性。
  • -
  • 「最差时间复杂度」使用大 \(O\) 符号表示,即函数渐近上界,其反映当 \(n\) 趋于正无穷时,\(T(n)\) 处于何种增长级别。
  • -
  • 推算时间复杂度分为两步,首先统计计算操作数量,再判断渐近上界。
  • -
  • 常见时间复杂度从小到大排列有 \(O(1)\) , \(O(\log n)\) , \(O(n)\) , \(O(n \log n)\) , \(O(n^2)\) , \(O(2^n)\) , \(O(n!)\)
  • -
  • 某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。时间复杂度分为「最差时间复杂度」和「最佳时间复杂度」,后者几乎不用,因为输入数据需要满足苛刻的条件才能达到最佳情况。
  • -
  • 「平均时间复杂度」可以反映在随机数据输入下的算法效率,最贴合实际使用情况下的算法性能。计算平均时间复杂度需要统计输入数据的分布,以及综合后的数学期望。
  • -
-

空间复杂度

-
    -
  • -

    与时间复杂度的定义类似,「空间复杂度」统计算法占用空间随着数据量变大时的增长趋势。

    -
  • -
  • -

    算法运行中相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间一般在递归函数中才会影响到空间复杂度。

    -
  • -
  • 我们一般只关心「最差空间复杂度」,即统计算法在「最差输入数据」和「最差运行时间点」下的空间复杂度。
  • -
  • 常见空间复杂度从小到大排列有 \(O(1)\) , \(O(\log n)\) , \(O(n)\) , \(O(n^2)\) , \(O(2^n)\)
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png b/site/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png deleted file mode 100644 index 59399bfcc..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png deleted file mode 100644 index f0108530f..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png deleted file mode 100644 index e4e839c90..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png deleted file mode 100644 index 3c9466574..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png deleted file mode 100644 index 7c86dfd3f..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_first_example.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_first_example.png deleted file mode 100644 index f2358b7cc..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_first_example.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png deleted file mode 100644 index a4df4527f..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png b/site/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png deleted file mode 100644 index 6f336f4b5..000000000 Binary files a/site/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png and /dev/null differ diff --git a/site/chapter_computational_complexity/time_complexity/index.html b/site/chapter_computational_complexity/time_complexity/index.html deleted file mode 100644 index df4e93d2a..000000000 --- a/site/chapter_computational_complexity/time_complexity/index.html +++ /dev/null @@ -1,4470 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.2. 时间复杂度 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

2.2. 时间复杂度

-

2.2.1. 统计算法运行时间

-

运行时间能够直观且准确地体现出算法的效率水平。如果我们想要 准确预估一段代码的运行时间 ,该如何做呢?

-
    -
  1. 首先需要 确定运行平台 ,包括硬件配置、编程语言、系统环境等,这些都会影响到代码的运行效率。
  2. -
  3. 评估 各种计算操作的所需运行时间 ,例如加法操作 + 需要 1 ns ,乘法操作 * 需要 10 ns ,打印操作需要 5 ns 等。
  4. -
  5. 根据代码 统计所有计算操作的数量 ,并将所有操作的执行时间求和,即可得到运行时间。
  6. -
-

例如以下代码,输入数据大小为 \(n\) ,根据以上方法,可以得到算法运行时间为 \(6n + 12\) ns 。

-
\[ -1 + 1 + 10 + (1 + 5) \times n = 6n + 12 -\]
-
-
-
-
// 在某运行平台下
-void algorithm(int n) {
-    int a = 2;  // 1 ns
-    a = a + 1;  // 1 ns
-    a = a * 2;  // 10 ns
-    // 循环 n 次
-    for (int i = 0; i < n; i++) {  // 1 ns ,每轮都要执行 i++
-        System.out.println(0);     // 5 ns
-    }
-}
-
-
-
-
// 在某运行平台下
-void algorithm(int n) {
-    int a = 2;  // 1 ns
-    a = a + 1;  // 1 ns
-    a = a * 2;  // 10 ns
-    // 循环 n 次
-    for (int i = 0; i < n; i++) {  // 1 ns ,每轮都要执行 i++
-        cout << 0 << endl;         // 5 ns
-    }
-}
-
-
-
-
# 在某运行平台下
-def algorithm(n):
-    a = 2      # 1 ns
-    a = a + 1  # 1 ns
-    a = a * 2  # 10 ns
-    # 循环 n 次
-    for _ in range(n):  # 1 ns
-        print(0)        # 5 ns
-
-
-
-
// 在某运行平台下
-func algorithm(n int) {
-    a := 2      // 1 ns
-    a = a + 1   // 1 ns
-    a = a * 2   // 10 ns
-    // 循环 n 次
-    for i := 0; i < n; i++ {    // 1 ns
-        fmt.Println(a)          // 5 ns
-    }
-}
-
-
-
-
// 在某运行平台下
-function algorithm(n) {
-    var a = 2; // 1 ns
-    a = a + 1; // 1 ns
-    a = a * 2; // 10 ns
-    // 循环 n 次
-    for(let i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++
-        console.log(0); // 5 ns
-    }
-}
-
-
-
-
// 在某运行平台下
-function algorithm(n: number): void {
-    var a: number = 2; // 1 ns
-    a = a + 1; // 1 ns
-    a = a * 2; // 10 ns
-    // 循环 n 次
-    for(let i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++
-        console.log(0); // 5 ns
-    }
-}
-
-
-
-
// 在某运行平台下
-void algorithm(int n) {
-    int a = 2;  // 1 ns
-    a = a + 1;  // 1 ns
-    a = a * 2;  // 10 ns
-    // 循环 n 次
-    for (int i = 0; i < n; i++) {   // 1 ns ,每轮都要执行 i++
-        printf("%d", 0);            // 5 ns
-    }
-}
-
-
-
-
// 在某运行平台下
-void algorithm(int n)
-{
-    int a = 2;  // 1 ns
-    a = a + 1;  // 1 ns
-    a = a * 2;  // 10 ns
-    // 循环 n 次
-    for (int i = 0; i < n; i++)
-    {  // 1 ns ,每轮都要执行 i++
-        Console.WriteLine(0);     // 5 ns
-    }
-}
-
-
-
-
// 在某运行平台下
-func algorithm(_ n: Int) {
-    var a = 2 // 1 ns
-    a = a + 1 // 1 ns
-    a = a * 2 // 10 ns
-    // 循环 n 次
-    for _ in 0 ..< n { // 1 ns
-        print(0) // 5 ns
-    }
-}
-
-
-
-

-
-
-
-
-

但实际上, 统计算法的运行时间既不合理也不现实。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。

-

2.2.2. 统计时间增长趋势

-

「时间复杂度分析」采取了不同的做法,其统计的不是算法运行时间,而是 算法运行时间随着数据量变大时的增长趋势

-

“时间增长趋势”这个概念比较抽象,我们借助一个例子来理解。设输入数据大小为 \(n\) ,给定三个算法 A , B , C

-
    -
  • 算法 A 只有 \(1\) 个打印操作,算法运行时间不随着 \(n\) 增大而增长。我们称此算法的时间复杂度为「常数阶」。
  • -
  • 算法 B 中的打印操作需要循环 \(n\) 次,算法运行时间随着 \(n\) 增大成线性增长。此算法的时间复杂度被称为「线性阶」。
  • -
  • 算法 C 中的打印操作需要循环 \(1000000\) 次,但运行时间仍与输入数据大小 \(n\) 无关。因此 C 的时间复杂度和 A 相同,仍为「常数阶」。
  • -
-
-
-
-
// 算法 A 时间复杂度:常数阶
-void algorithm_A(int n) {
-    System.out.println(0);
-}
-// 算法 B 时间复杂度:线性阶
-void algorithm_B(int n) {
-    for (int i = 0; i < n; i++) {
-        System.out.println(0);
-    }
-}
-// 算法 C 时间复杂度:常数阶
-void algorithm_C(int n) {
-    for (int i = 0; i < 1000000; i++) {
-        System.out.println(0);
-    }
-}
-
-
-
-
// 算法 A 时间复杂度:常数阶
-void algorithm_A(int n) {
-    cout << 0 << endl;
-}
-// 算法 B 时间复杂度:线性阶
-void algorithm_B(int n) {
-    for (int i = 0; i < n; i++) {
-        cout << 0 << endl;
-    }
-}
-// 算法 C 时间复杂度:常数阶
-void algorithm_C(int n) {
-    for (int i = 0; i < 1000000; i++) {
-        cout << 0 << endl;
-    }
-}
-
-
-
-
# 算法 A 时间复杂度:常数阶
-def algorithm_A(n):
-    print(0)
-# 算法 B 时间复杂度:线性阶
-def algorithm_B(n):
-    for _ in range(n):
-        print(0)
-# 算法 C 时间复杂度:常数阶
-def algorithm_C(n):
-    for _ in range(1000000):
-        print(0)
-
-
-
-
// 算法 A 时间复杂度:常数阶
-func algorithm_A(n int) {
-    fmt.Println(0)
-}
-// 算法 B 时间复杂度:线性阶
-func algorithm_B(n int) {
-    for i := 0; i < n; i++ {
-        fmt.Println(0)
-    }
-}
-// 算法 C 时间复杂度:常数阶
-func algorithm_C(n int) {
-    for i := 0; i < 1000000; i++ {
-        fmt.Println(0)
-    }
-}
-
-
-
-
// 算法 A 时间复杂度:常数阶
-function algorithm_A(n) {
-    console.log(0);
-}
-// 算法 B 时间复杂度:线性阶
-function algorithm_B(n) {
-    for (let i = 0; i < n; i++) {
-        console.log(0);
-    }
-}
-// 算法 C 时间复杂度:常数阶
-function algorithm_C(n) {
-    for (let i = 0; i < 1000000; i++) {
-        console.log(0);
-    }
-}
-
-
-
-
// 算法 A 时间复杂度:常数阶
-function algorithm_A(n: number): void {
-    console.log(0);
-}
-// 算法 B 时间复杂度:线性阶
-function algorithm_B(n: number): void {
-    for (let i = 0; i < n; i++) {
-        console.log(0);
-    }
-}
-// 算法 C 时间复杂度:常数阶
-function algorithm_C(n: number): void {
-    for (let i = 0; i < 1000000; i++) {
-        console.log(0);
-    }
-}
-
-
-
-
// 算法 A 时间复杂度:常数阶
-void algorithm_A(int n) {
-    printf("%d", 0);
-}
-// 算法 B 时间复杂度:线性阶
-void algorithm_B(int n) {
-    for (int i = 0; i < n; i++) {
-        printf("%d", 0);
-    }
-}
-// 算法 C 时间复杂度:常数阶
-void algorithm_C(int n) {
-    for (int i = 0; i < 1000000; i++) {
-        printf("%d", 0);
-    }
-}
-
-
-
-
// 算法 A 时间复杂度:常数阶
-void algorithm_A(int n)
-{
-    Console.WriteLine(0);
-}
-// 算法 B 时间复杂度:线性阶
-void algorithm_B(int n)
-{
-    for (int i = 0; i < n; i++)
-    {
-        Console.WriteLine(0);
-    }
-}
-// 算法 C 时间复杂度:常数阶
-void algorithm_C(int n)
-{
-    for (int i = 0; i < 1000000; i++)
-    {
-        Console.WriteLine(0);
-    }
-}
-
-
-
-
// 算法 A 时间复杂度:常数阶
-func algorithmA(_ n: Int) {
-    print(0)
-}
-
-// 算法 B 时间复杂度:线性阶
-func algorithmB(_ n: Int) {
-    for _ in 0 ..< n {
-        print(0)
-    }
-}
-
-// 算法 C 时间复杂度:常数阶
-func algorithmC(_ n: Int) {
-    for _ in 0 ..< 1000000 {
-        print(0)
-    }
-}
-
-
-
-

-
-
-
-
-

time_complexity_first_example

-

Fig. 算法 A, B, C 的时间增长趋势

- -

相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?

-

时间复杂度可以有效评估算法效率。算法 B 运行时间的增长是线性的,在 \(n > 1\) 时慢于算法 A ,在 \(n > 1000000\) 时慢于算法 C 。实质上,只要输入数据大小 \(n\) 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。

-

时间复杂度的推算方法更加简便。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。

-

时间复杂度也存在一定的局限性。比如,虽然算法 AC 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 BC 的时间复杂度要更高,但在输入数据大小 \(n\) 比较小时,算法 B 是要明显优于算法 C 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。

-

2.2.3. 函数渐近上界

-

设算法「计算操作数量」为 \(T(n)\) ,其是一个关于输入数据大小 \(n\) 的函数。例如,以下算法的操作数量为

-
\[ -T(n) = 3 + 2n -\]
-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +1
-    a = a + 1;  // +1
-    a = a * 2;  // +1
-    // 循环 n 次
-    for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++)
-        System.out.println(0);    // +1
-    }
-}
-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +1
-    a = a + 1;  // +1
-    a = a * 2;  // +1
-    // 循环 n 次
-    for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++)
-        cout << 0 << endl;    // +1
-    }
-}
-
-
-
-
def algorithm(n):
-    a = 1  # +1
-    a = a + 1  # +1
-    a = a * 2  # +1
-    # 循环 n 次
-    for i in range(n):  # +1
-        print(0)        # +1
-
-
-
-
func algorithm(n int) {
-    a := 1      // +1
-    a = a + 1   // +1
-    a = a * 2   // +1
-    // 循环 n 次
-    for i := 0; i < n; i++ {   // +1
-        fmt.Println(a)         // +1
-    }
-}
-
-
-
-
function algorithm(n){
-    var a = 1; // +1
-    a += 1; // +1
-    a *= 2; // +1
-    // 循环 n 次
-    for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++)
-        console.log(0); // +1
-    }
-
-}
-
-
-
-
function algorithm(n: number): void{
-    var a: number = 1; // +1
-    a += 1; // +1
-    a *= 2; // +1
-    // 循环 n 次
-    for(let i = 0; i < n; i++){ // +1(每轮都执行 i ++)
-        console.log(0); // +1
-    }
-
-}
-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +1
-    a = a + 1;  // +1
-    a = a * 2;  // +1
-    // 循环 n 次
-    for (int i = 0; i < n; i++) {   // +1(每轮都执行 i ++)
-        printf("%d", 0);            // +1
-    }
-}  
-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +1
-    a = a + 1;  // +1
-    a = a * 2;  // +1
-    // 循环 n 次
-    for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++)
-        Console.WriteLine(0);     // +1
-    }
-}
-
-
-
-
func algorithm(n: Int) {
-    var a = 1 // +1
-    a = a + 1 // +1
-    a = a * 2 // +1
-    // 循环 n 次
-    for _ in 0 ..< n { // +1
-        print(0) // +1
-    }
-}
-
-
-
-

-
-
-
-
-

\(T(n)\) 是个一次函数,说明时间增长趋势是线性的,因此易得时间复杂度是线性阶。

-

我们将线性阶的时间复杂度记为 \(O(n)\) ,这个数学符号被称为「大 \(O\) 记号 Big-\(O\) Notation」,代表函数 \(T(n)\) 的「渐近上界 asymptotic upper bound」。

-

我们要推算时间复杂度,本质上是在计算「操作数量函数 \(T(n)\) 」的渐近上界。下面我们先来看看函数渐近上界的数学定义。

-
-

函数渐近上界

-

若存在正实数 \(c\) 和实数 \(n_0\) ,使得对于所有的 \(n > n_0\) ,均有 -$$ -T(n) \leq c \cdot f(n) -$$ -则可认为 \(f(n)\) 给出了 \(T(n)\) 的一个渐近上界,记为 -$$ -T(n) = O(f(n)) -$$

-
-

asymptotic_upper_bound

-

Fig. 函数的渐近上界

- -

本质上看,计算渐近上界就是在找一个函数 \(f(n)\)使得在 \(n\) 趋向于无穷大时,\(T(n)\)\(f(n)\) 处于相同的增长级别(仅相差一个常数项 \(c\) 的倍数)

-
-

Tip

-

渐近上界的数学味儿有点重,如果你感觉没有完全理解,无需担心,因为在实际使用中我们只需要会推算即可,数学意义可以慢慢领悟。

-
-

2.2.4. 推算方法

-

推算出 \(f(n)\) 后,我们就得到时间复杂度 \(O(f(n))\) 。那么,如何来确定渐近上界 \(f(n)\) 呢?总体分为两步,首先「统计操作数量」,然后「判断渐近上界」。

-

1) 统计操作数量

-

对着代码,从上到下一行一行地计数即可。然而,由于上述 \(c \cdot f(n)\) 中的常数项 \(c\) 可以取任意大小,因此操作数量 \(T(n)\) 中的各种系数、常数项都可以被忽略。根据此原则,可以总结出以下计数偷懒技巧:

-
    -
  1. 跳过数量与 \(n\) 无关的操作。因为他们都是 \(T(n)\) 中的常数项,对时间复杂度不产生影响。
  2. -
  3. 省略所有系数。例如,循环 \(2n\) 次、\(5n + 1\) 次、……,都可以化简记为 \(n\) 次,因为 \(n\) 前面的系数对时间复杂度也不产生影响。
  4. -
  5. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 1.2. 技巧。
  6. -
-

根据以下示例,使用上述技巧前、后的统计结果分别为

-
\[ -\begin{aligned} -T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline -& = 2n^2 + 7n + 3 \newline -T(n) & = n^2 + n & \text{偷懒统计 (o.O)} -\end{aligned} -\]
-

最终,两者都能推出相同的时间复杂度结果,即 \(O(n^2)\)

-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +0(技巧 1)
-    a = a + n;  // +0(技巧 1)
-    // +n(技巧 2)
-    for (int i = 0; i < 5 * n + 1; i++) {
-        System.out.println(0);
-    }
-    // +n*n(技巧 3)
-    for (int i = 0; i < 2 * n; i++) {
-        for (int j = 0; j < n + 1; j++) {
-            System.out.println(0);
-        }
-    }
-}
-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +0(技巧 1)
-    a = a + n;  // +0(技巧 1)
-    // +n(技巧 2)
-    for (int i = 0; i < 5 * n + 1; i++) {
-        cout << 0 << endl;
-    }
-    // +n*n(技巧 3)
-    for (int i = 0; i < 2 * n; i++) {
-        for (int j = 0; j < n + 1; j++) {
-            cout << 0 << endl;
-        }
-    }
-}
-
-
-
-
def algorithm(n):
-    a = 1      # +0(技巧 1)
-    a = a + n  # +0(技巧 1)
-    # +n(技巧 2)
-    for i in range(5 * n + 1):
-        print(0)
-    # +n*n(技巧 3)
-    for i in range(2 * n):
-        for j in range(n + 1):
-            print(0)
-
-
-
-
func algorithm(n int) {
-    a := 1      // +0(技巧 1)
-    a = a + n  // +0(技巧 1)
-    // +n(技巧 2)
-    for i := 0; i < 5 * n + 1; i++ {
-        fmt.Println(0)
-    }
-    // +n*n(技巧 3)
-    for i := 0; i < 2 * n; i++ {
-        for j := 0; j < n + 1; j++ {
-            fmt.Println(0)
-        }
-    }
-}
-
-
-
-
function algorithm(n) {
-    let a = 1;  // +0(技巧 1)
-    a = a + n;  // +0(技巧 1)
-    // +n(技巧 2)
-    for (let i = 0; i < 5 * n + 1; i++) {
-        console.log(0);
-    }
-    // +n*n(技巧 3)
-    for (let i = 0; i < 2 * n; i++) {
-        for (let j = 0; j < n + 1; j++) {
-            console.log(0);
-        }
-    }
-}
-
-
-
-
function algorithm(n: number): void {
-    let a = 1;  // +0(技巧 1)
-    a = a + n;  // +0(技巧 1)
-    // +n(技巧 2)
-    for (let i = 0; i < 5 * n + 1; i++) {
-        console.log(0);
-    }
-    // +n*n(技巧 3)
-    for (let i = 0; i < 2 * n; i++) {
-        for (let j = 0; j < n + 1; j++) {
-            console.log(0);
-        }
-    }
-}
-
-
-
-
void algorithm(int n) {
-    int a = 1;  // +0(技巧 1)
-    a = a + n;  // +0(技巧 1)
-    // +n(技巧 2)
-    for (int i = 0; i < 5 * n + 1; i++) {
-        printf("%d", 0);
-    }
-    // +n*n(技巧 3)
-    for (int i = 0; i < 2 * n; i++) {
-        for (int j = 0; j < n + 1; j++) {
-            printf("%d", 0);
-        }
-    }
-}
-
-
-
-
void algorithm(int n)
-{
-    int a = 1;  // +0(技巧 1)
-    a = a + n;  // +0(技巧 1)
-    // +n(技巧 2)
-    for (int i = 0; i < 5 * n + 1; i++)
-    {
-        Console.WriteLine(0);
-    }
-    // +n*n(技巧 3)
-    for (int i = 0; i < 2 * n; i++)
-    {
-        for (int j = 0; j < n + 1; j++)
-        {
-            Console.WriteLine(0);
-        }
-    }
-}
-
-
-
-
func algorithm(n: Int) {
-    var a = 1 // +0(技巧 1)
-    a = a + n // +0(技巧 1)
-    // +n(技巧 2)
-    for _ in 0 ..< (5 * n + 1) {
-        print(0)
-    }
-    // +n*n(技巧 3)
-    for _ in 0 ..< (2 * n) {
-        for _ in 0 ..< (n + 1) {
-            print(0)
-        }
-    }
-}
-
-
-
-

-
-
-
-
-

2) 判断渐近上界

-

时间复杂度由多项式 \(T(n)\) 中最高阶的项来决定。这是因为在 \(n\) 趋于无穷大时,最高阶的项将处于主导作用,其它项的影响都可以被忽略。

-

以下表格给出了一些例子,其中有一些夸张的值,是想要向大家强调 系数无法撼动阶数 这一结论。在 \(n\) 趋于无穷大时,这些常数都是“浮云”。

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
操作数量 \(T(n)\)时间复杂度 \(O(f(n))\)
\(100000\)\(O(1)\)
\(3n + 2\)\(O(n)\)
\(2n^2 + 3n + 2\)\(O(n^2)\)
\(n^3 + 10000n^2\)\(O(n^3)\)
\(2^n + 10000n^{10000}\)\(O(2^n)\)
-
-

2.2.5. 常见类型

-

设输入数据大小为 \(n\) ,常见的时间复杂度类型有(从低到高排列)

-
\[ -\begin{aligned} -O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline -\text{常数阶} < \text{对数阶} < \text{线性阶} < \text{线性对数阶} < \text{平方阶} < \text{指数阶} < \text{阶乘阶} -\end{aligned} -\]
-

time_complexity_common_types

-

Fig. 时间复杂度的常见类型

- -
-

Tip

-

部分示例代码需要一些前置知识,包括数组、递归算法等。如果遇到看不懂的地方无需担心,可以在学习完后面章节后再来复习,现阶段先聚焦在理解时间复杂度含义和推算方法上。

-
-

常数阶 \(O(1)\)

-

常数阶的操作数量与输入数据大小 \(n\) 无关,即不随着 \(n\) 的变化而变化。

-

对于以下算法,无论操作数量 size 有多大,只要与数据大小 \(n\) 无关,时间复杂度就仍为 \(O(1)\)

-
-
-
-
time_complexity.java
/* 常数阶 */
-int constant(int n) {
-    int count = 0;
-    int size = 100000;
-    for (int i = 0; i < size; i++)
-        count++;
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 常数阶 */
-int constant(int n) {
-    int count = 0;
-    int size = 100000;
-    for (int i = 0; i < size; i++)
-        count++;
-    return count;
-}
-
-
-
-
time_complexity.py
""" 常数阶 """
-def constant(n):
-    count = 0
-    size = 100000
-    for _ in range(size):
-        count += 1
-    return count
-
-
-
-
time_complexity.go
/* 常数阶 */
-func constant(n int) int {
-    count := 0
-    size := 100000
-    for i := 0; i < size; i++ {
-        count ++
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 常数阶 */
-function constant(n) {
-    let count = 0;
-    const size = 100000;
-    for (let i = 0; i < size; i++) count++;
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 常数阶 */
-function constant(n: number): number {
-    let count = 0;
-    const size = 100000;
-    for (let i = 0; i < size; i++) count++;
-    return count;
-}
-
-
-
-
time_complexity.c
/* 常数阶 */
-int constant(int n) {
-    int count = 0;
-    int size = 100000;
-    int i = 0;
-    for (int i = 0; i < size; i++) {
-        count ++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 常数阶 */
-int constant(int n)
-{
-    int count = 0;
-    int size = 100000;
-    for (int i = 0; i < size; i++)
-        count++;
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 常数阶 */
-func constant(n: Int) -> Int {
-    var count = 0
-    let size = 100000
-    for _ in 0 ..< size {
-        count += 1
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 常数阶
-fn constant(n: i32) i32 {
-    _ = n;
-    var count: i32 = 0;
-    const size: i32 = 100_000;
-    var i: i32 = 0;
-    while(i<size) : (i += 1) {
-        count += 1;
-    }
-    return count;
-}
-
-
-
-
-

线性阶 \(O(n)\)

-

线性阶的操作数量相对输入数据大小成线性级别增长。线性阶常出现于单层循环。

-
-
-
-
time_complexity.java
/* 线性阶 */
-int linear(int n) {
-    int count = 0;
-    for (int i = 0; i < n; i++)
-        count++;
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 线性阶 */
-int linear(int n) {
-    int count = 0;
-    for (int i = 0; i < n; i++)
-        count++;
-    return count;
-}
-
-
-
-
time_complexity.py
""" 线性阶 """
-def linear(n):
-    count = 0
-    for _ in range(n):
-        count += 1
-    return count
-
-
-
-
time_complexity.go
/* 线性阶 */
-func linear(n int) int {
-    count := 0
-    for i := 0; i < n; i++ {
-        count++
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 线性阶 */
-function linear(n) {
-    let count = 0;
-    for (let i = 0; i < n; i++) count++;
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 线性阶 */
-function linear(n: number): number {
-    let count = 0;
-    for (let i = 0; i < n; i++) count++;
-    return count;
-}
-
-
-
-
time_complexity.c
/* 线性阶 */
-int linear(int n) {
-    int count = 0;
-    for (int i = 0; i < n; i++) {
-        count ++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 线性阶 */
-int linear(int n)
-{
-    int count = 0;
-    for (int i = 0; i < n; i++)
-        count++;
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 线性阶 */
-func linear(n: Int) -> Int {
-    var count = 0
-    for _ in 0 ..< n {
-        count += 1
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 线性阶
-fn linear(n: i32) i32 {
-    var count: i32 = 0;
-    var i: i32 = 0;
-    while (i < n) : (i += 1) {
-        count += 1;
-    }
-    return count;
-}
-
-
-
-
-

「遍历数组」和「遍历链表」等操作,时间复杂度都为 \(O(n)\) ,其中 \(n\) 为数组或链表的长度。

-
-

Tip

-

数据大小 \(n\) 是根据输入数据的类型来确定的。比如,在上述示例中,我们直接将 \(n\) 看作输入数据大小;以下遍历数组示例中,数据大小 \(n\) 为数组的长度。

-
-
-
-
-
time_complexity.java
/* 线性阶(遍历数组) */
-int arrayTraversal(int[] nums) {
-    int count = 0;
-    // 循环次数与数组长度成正比
-    for (int num : nums) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 线性阶(遍历数组) */
-int arrayTraversal(vector<int>& nums) {
-    int count = 0;
-    // 循环次数与数组长度成正比
-    for (int num : nums) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.py
""" 线性阶(遍历数组)"""
-def array_traversal(nums):
-    count = 0
-    # 循环次数与数组长度成正比
-    for num in nums:
-        count += 1
-    return count
-
-
-
-
time_complexity.go
/* 线性阶(遍历数组) */
-func arrayTraversal(nums []int) int {
-    count := 0
-    // 循环次数与数组长度成正比
-    for range nums {
-        count++
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 线性阶(遍历数组) */
-function arrayTraversal(nums) {
-    let count = 0;
-    // 循环次数与数组长度成正比
-    for (let i = 0; i < nums.length; i++) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 线性阶(遍历数组) */
-function arrayTraversal(nums: number[]): number {
-    let count = 0;
-    // 循环次数与数组长度成正比
-    for (let i = 0; i < nums.length; i++) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.c
/* 线性阶(遍历数组) */
-int arrayTraversal(int *nums, int n) {
-    int count = 0;
-    // 循环次数与数组长度成正比
-    for (int i = 0; i < n; i++) {
-        count ++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 线性阶(遍历数组) */
-int arrayTraversal(int[] nums)
-{
-    int count = 0;
-    // 循环次数与数组长度成正比
-    foreach(int num in nums)
-    {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 线性阶(遍历数组) */
-func arrayTraversal(nums: [Int]) -> Int {
-    var count = 0
-    // 循环次数与数组长度成正比
-    for _ in nums {
-        count += 1
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 线性阶(遍历数组)
-fn arrayTraversal(nums: []i32) i32 {
-    var count: i32 = 0;
-    // 循环次数与数组长度成正比
-    for (nums) |_| {
-        count += 1;
-    }
-    return count;
-}
-
-
-
-
-

平方阶 \(O(n^2)\)

-

平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 \(O(n)\) ,总体为 \(O(n^2)\)

-
-
-
-
time_complexity.java
/* 平方阶 */
-int quadratic(int n) {
-    int count = 0;
-    // 循环次数与数组长度成平方关系
-    for (int i = 0; i < n; i++) {
-        for (int j = 0; j < n; j++) {
-            count++;
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 平方阶 */
-int quadratic(int n) {
-    int count = 0;
-    // 循环次数与数组长度成平方关系
-    for (int i = 0; i < n; i++) {
-        for (int j = 0; j < n; j++) {
-            count++;
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.py
""" 平方阶 """
-def quadratic(n):
-    count = 0
-    # 循环次数与数组长度成平方关系
-    for i in range(n):
-        for j in range(n):
-            count += 1
-    return count
-
-
-
-
time_complexity.go
/* 平方阶 */
-func quadratic(n int) int {
-    count := 0
-    // 循环次数与数组长度成平方关系
-    for i := 0; i < n; i++ {
-        for j := 0; j < n; j++ {
-            count++
-        }
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 平方阶 */
-function quadratic(n) {
-    let count = 0;
-    // 循环次数与数组长度成平方关系
-    for (let i = 0; i < n; i++) {
-        for (let j = 0; j < n; j++) {
-            count++;
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 平方阶 */
-function quadratic(n: number): number {
-    let count = 0;
-    // 循环次数与数组长度成平方关系
-    for (let i = 0; i < n; i++) {
-        for (let j = 0; j < n; j++) {
-            count++;
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.c
/* 平方阶 */
-int quadratic(int n) {
-    int count = 0;
-    // 循环次数与数组长度成平方关系
-    for (int i = 0; i < n; i++) {
-        for (int j = 0; j < n; j++) {
-            count ++;
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 平方阶 */
-int quadratic(int n)
-{
-    int count = 0;
-    // 循环次数与数组长度成平方关系
-    for (int i = 0; i < n; i++)
-    {
-        for (int j = 0; j < n; j++)
-        {
-            count++;
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 平方阶 */
-func quadratic(n: Int) -> Int {
-    var count = 0
-    // 循环次数与数组长度成平方关系
-    for _ in 0 ..< n {
-        for _ in 0 ..< n {
-            count += 1
-        }
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 平方阶
-fn quadratic(n: i32) i32 {
-    var count: i32 = 0;
-    var i: i32 = 0;
-    // 循环次数与数组长度成平方关系
-    while (i < n) : (i += 1) {
-        var j: i32 = 0;
-        while (j < n) : (j += 1) {
-            count += 1;
-        }
-    }
-    return count;
-}   
-
-
-
-
-

time_complexity_constant_linear_quadratic

-

Fig. 常数阶、线性阶、平方阶的时间复杂度

- -

以「冒泡排序」为例,外层循环 \(n - 1\) 次,内层循环 \(n-1, n-2, \cdots, 2, 1\) 次,平均为 \(\frac{n}{2}\) 次,因此时间复杂度为 \(O(n^2)\)

-
\[ -O((n - 1) \frac{n}{2}) = O(n^2) -\]
-
-
-
-
time_complexity.java
/* 平方阶(冒泡排序) */
-int bubbleSort(int[] nums) {
-    int count = 0;  // 计数器
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.length - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3;  // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 平方阶(冒泡排序) */
-int bubbleSort(vector<int>& nums) {
-    int count = 0;  // 计数器
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.size() - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3;  // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.py
""" 平方阶(冒泡排序)"""
-def bubble_sort(nums):
-    count = 0  # 计数器
-    # 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i in range(len(nums) - 1, 0, -1):
-        # 内循环:冒泡操作
-        for j in range(i):
-            if nums[j] > nums[j + 1]:
-                # 交换 nums[j] 与 nums[j + 1]
-                tmp = nums[j]
-                nums[j] = nums[j + 1]
-                nums[j + 1] = tmp
-                count += 3  # 元素交换包含 3 个单元操作
-    return count
-
-
-
-
time_complexity.go
/* 平方阶(冒泡排序) */
-func bubbleSort(nums []int) int {
-    count := 0 // 计数器
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i := len(nums) - 1; i > 0; i-- {
-        // 内循环:冒泡操作
-        for j := 0; j < i; j++ {
-            if nums[j] > nums[j+1] {
-                // 交换 nums[j] 与 nums[j + 1]
-                tmp := nums[j]
-                nums[j] = nums[j+1]
-                nums[j+1] = tmp
-                count += 3 // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 平方阶(冒泡排序) */
-function bubbleSort(nums) {
-    let count = 0; // 计数器
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (let i = nums.length - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (let j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3; // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 平方阶(冒泡排序) */
-function bubbleSort(nums: number[]): number {
-    let count = 0; // 计数器
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (let i = nums.length - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (let j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3; // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.c
/* 平方阶(冒泡排序) */
-int bubbleSort(int *nums, int n) {
-    int count = 0;  // 计数器 
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = n - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums [j + 1]) 
-            {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3;  // 元素交换包含 3 个单元操作
-            }
-        }
-
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 平方阶(冒泡排序) */
-int bubbleSort(int[] nums)
-{
-    int count = 0;  // 计数器
-                    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.Length - 1; i > 0; i--)
-    {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++)
-        {
-            if (nums[j] > nums[j + 1])
-            {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3;  // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 平方阶(冒泡排序) */
-func bubbleSort(nums: inout [Int]) -> Int {
-    var count = 0 // 计数器
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) {
-        // 内循环:冒泡操作
-        for j in 0 ..< i {
-            if nums[j] > nums[j + 1] {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j]
-                nums[j] = nums[j + 1]
-                nums[j + 1] = tmp
-                count += 3 // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 平方阶(冒泡排序)
-fn bubbleSort(nums: []i32) i32 {
-    var count: i32 = 0;  // 计数器 
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    var i: i32 = @intCast(i32, nums.len ) - 1;
-    while (i > 0) : (i -= 1) {
-        var j: usize = 0;
-        // 内循环:冒泡操作
-        while (j < i) : (j += 1) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                var tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                count += 3;  // 元素交换包含 3 个单元操作
-            }
-        }
-    }
-    return count;
-}
-
-
-
-
-

指数阶 \(O(2^n)\)

-
-

Note

-

生物学科中的“细胞分裂”即是指数阶增长:初始状态为 \(1\) 个细胞,分裂一轮后为 \(2\) 个,分裂两轮后为 \(4\) 个,……,分裂 \(n\) 轮后有 \(2^n\) 个细胞。

-
-

指数阶增长得非常快,在实际应用中一般是不能被接受的。若一个问题使用「暴力枚举」求解的时间复杂度是 \(O(2^n)\) ,那么一般都需要使用「动态规划」或「贪心算法」等算法来求解。

-
-
-
-
time_complexity.java
/* 指数阶(循环实现) */
-int exponential(int n) {
-    int count = 0, base = 1;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for (int i = 0; i < n; i++) {
-        for (int j = 0; j < base; j++) {
-            count++;
-        }
-        base *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 指数阶(循环实现) */
-int exponential(int n) {
-    int count = 0, base = 1;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for (int i = 0; i < n; i++) {
-        for (int j = 0; j < base; j++) {
-            count++;
-        }
-        base *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}
-
-
-
-
time_complexity.py
""" 指数阶(循环实现)"""
-def exponential(n):
-    count, base = 0, 1
-    # cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for _ in range(n):
-        for _ in range(base):
-            count += 1
-        base *= 2
-    # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count
-
-
-
-
time_complexity.go
/* 指数阶(循环实现)*/
-func exponential(n int) int {
-    count, base := 0, 1
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for i := 0; i < n; i++ {
-        for j := 0; j < base; j++ {
-            count++
-        }
-        base *= 2
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count
-}
-
-
-
-
time_complexity.js
/* 指数阶(循环实现) */
-function exponential(n) {
-    let count = 0,
-        base = 1;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for (let i = 0; i < n; i++) {
-        for (let j = 0; j < base; j++) {
-            count++;
-        }
-        base *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 指数阶(循环实现) */
-function exponential(n: number): number {
-    let count = 0,
-        base = 1;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for (let i = 0; i < n; i++) {
-        for (let j = 0; j < base; j++) {
-            count++;
-        }
-        base *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}
-
-
-
-
time_complexity.c
/* 指数阶(循环实现) */
-int exponential(int n) {
-    int count = 0;
-    int bas = 1;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for (int i = 0; i < n; i++) {
-        for (int j = 0; j < bas; j++) {
-            count++;
-        }
-        bas *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 指数阶(循环实现) */
-int exponential(int n)
-{
-    int count = 0, bas = 1;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for (int i = 0; i < n; i++)
-    {
-        for (int j = 0; j < bas; j++)
-        {
-            count++;
-        }
-        bas *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 指数阶(循环实现) */
-func exponential(n: Int) -> Int {
-    var count = 0
-    var base = 1
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    for _ in 0 ..< n {
-        for _ in 0 ..< base {
-            count += 1
-        }
-        base *= 2
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count
-}
-
-
-
-
time_complexity.zig
// 指数阶(循环实现)
-fn exponential(n: i32) i32{
-    var count: i32 = 0;
-    var bas: i32 = 1;
-    var i: i32 = 0;
-    // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
-    while (i < n) : (i += 1) {
-        var j: i32 = 0;
-        while (j < bas) : (j += 1) {
-            count += 1;
-        }
-        bas *= 2;
-    }
-    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
-    return count;
-}       
-
-
-
-
-

time_complexity_exponential

-

Fig. 指数阶的时间复杂度

- -

在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 \(n\) 次后停止。

-
-
-
-
time_complexity.java
/* 指数阶(递归实现) */
-int expRecur(int n) {
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
time_complexity.cpp
/* 指数阶(递归实现) */
-int expRecur(int n) {
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
time_complexity.py
""" 指数阶(递归实现)"""
-def exp_recur(n):
-    if n == 1: return 1
-    return exp_recur(n - 1) + exp_recur(n - 1) + 1
-
-
-
-
time_complexity.go
/* 指数阶(递归实现)*/
-func expRecur(n int) int {
-    if n == 1 {
-        return 1
-    }
-    return expRecur(n-1) + expRecur(n-1) + 1
-}
-
-
-
-
time_complexity.js
/* 指数阶(递归实现) */
-function expRecur(n) {
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
time_complexity.ts
/* 指数阶(递归实现) */
-function expRecur(n: number): number {
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
time_complexity.c
/* 指数阶(递归实现) */
-int expRecur(int n) {
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
time_complexity.cs
/* 指数阶(递归实现) */
-int expRecur(int n)
-{
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
time_complexity.swift
/* 指数阶(递归实现) */
-func expRecur(n: Int) -> Int {
-    if n == 1 {
-        return 1
-    }
-    return expRecur(n: n - 1) + expRecur(n: n - 1) + 1
-}
-
-
-
-
time_complexity.zig
// 指数阶(递归实现)
-fn expRecur(n: i32) i32{
-    if (n == 1) return 1;
-    return expRecur(n - 1) + expRecur(n - 1) + 1;
-}
-
-
-
-
-

对数阶 \(O(\log n)\)

-

对数阶与指数阶正好相反,后者反映“每轮增加到两倍的情况”,而前者反映“每轮缩减到一半的情况”。对数阶仅次于常数阶,时间增长得很慢,是理想的时间复杂度。

-

对数阶常出现于「二分查找」和「分治算法」中,体现“一分为多”、“化繁为简”的算法思想。

-

设输入数据大小为 \(n\) ,由于每轮缩减到一半,因此循环次数是 \(\log_2 n\) ,即 \(2^n\) 的反函数。

-
-
-
-
time_complexity.java
/* 对数阶(循环实现) */
-int logarithmic(float n) {
-    int count = 0;
-    while (n > 1) {
-        n = n / 2;
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 对数阶(循环实现) */
-int logarithmic(float n) {
-    int count = 0;
-    while (n > 1) {
-        n = n / 2;
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.py
""" 对数阶(循环实现)"""
-def logarithmic(n):
-    count = 0
-    while n > 1:
-        n = n / 2
-        count += 1
-    return count
-
-
-
-
time_complexity.go
/* 对数阶(循环实现)*/
-func logarithmic(n float64) int {
-    count := 0
-    for n > 1 {
-        n = n / 2
-        count++
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 对数阶(循环实现) */
-function logarithmic(n) {
-    let count = 0;
-    while (n > 1) {
-        n = n / 2;
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 对数阶(循环实现) */
-function logarithmic(n: number): number {
-    let count = 0;
-    while (n > 1) {
-        n = n / 2;
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.c
/* 对数阶(循环实现) */
-int logarithmic(float n) {
-    int count = 0;
-    while (n > 1) {
-        n = n / 2;
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 对数阶(循环实现) */
-int logarithmic(float n)
-{
-    int count = 0;
-    while (n > 1)
-    {
-        n = n / 2;
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 对数阶(循环实现) */
-func logarithmic(n: Int) -> Int {
-    var count = 0
-    var n = n
-    while n > 1 {
-        n = n / 2
-        count += 1
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 对数阶(循环实现)
-fn logarithmic(n: f32) i32
-{
-    var count: i32 = 0;
-    var n_var = n;
-    while (n_var > 1)
-    {
-        n_var = n_var / 2;
-        count +=1;
-    }
-    return count;
-}
-
-
-
-
-

time_complexity_logarithmic

-

Fig. 对数阶的时间复杂度

- -

与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 \(\log_2 n\) 的递归树。

-
-
-
-
time_complexity.java
/* 对数阶(递归实现) */
-int logRecur(float n) {
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
time_complexity.cpp
/* 对数阶(递归实现) */
-int logRecur(float n) {
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
time_complexity.py
""" 对数阶(递归实现)"""
-def log_recur(n):
-    if n <= 1: return 0
-    return log_recur(n / 2) + 1
-
-
-
-
time_complexity.go
/* 对数阶(递归实现)*/
-func logRecur(n float64) int {
-    if n <= 1 {
-        return 0
-    }
-    return logRecur(n/2) + 1
-}
-
-
-
-
time_complexity.js
/* 对数阶(递归实现) */
-function logRecur(n) {
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
time_complexity.ts
/* 对数阶(递归实现) */
-function logRecur(n: number): number {
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
time_complexity.c
/* 对数阶(递归实现) */
-int logRecur(float n) {
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
time_complexity.cs
/* 对数阶(递归实现) */
-int logRecur(float n)
-{
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
time_complexity.swift
/* 对数阶(递归实现) */
-func logRecur(n: Int) -> Int {
-    if n <= 1 {
-        return 0
-    }
-    return logRecur(n: n / 2) + 1
-}
-
-
-
-
time_complexity.zig
// 对数阶(递归实现)
-fn logRecur(n: f32) i32
-{
-    if (n <= 1) return 0;
-    return logRecur(n / 2) + 1;
-}
-
-
-
-
-

线性对数阶 \(O(n \log n)\)

-

线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 \(O(\log n)\)\(O(n)\)

-

主流排序算法的时间复杂度都是 \(O(n \log n )\) ,例如快速排序、归并排序、堆排序等。

-
-
-
-
time_complexity.java
/* 线性对数阶 */
-int linearLogRecur(float n) {
-    if (n <= 1) return 1;
-    int count = linearLogRecur(n / 2) + 
-                linearLogRecur(n / 2);
-    for (int i = 0; i < n; i++) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 线性对数阶 */
-int linearLogRecur(float n) {
-    if (n <= 1) return 1;
-    int count = linearLogRecur(n / 2) +
-                linearLogRecur(n / 2);
-    for (int i = 0; i < n; i++) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.py
""" 线性对数阶 """
-def linear_log_recur(n):
-    if n <= 1: return 1
-    count = linear_log_recur(n // 2) + \
-            linear_log_recur(n // 2)
-    for _ in range(n):
-        count += 1
-    return count
-
-
-
-
time_complexity.go
/* 线性对数阶 */
-func linearLogRecur(n float64) int {
-    if n <= 1 {
-        return 1
-    }
-    count := linearLogRecur(n/2) +
-        linearLogRecur(n/2)
-    for i := 0.0; i < n; i++ {
-        count++
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 线性对数阶 */
-function linearLogRecur(n) {
-    if (n <= 1) return 1;
-    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);
-    for (let i = 0; i < n; i++) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 线性对数阶 */
-function linearLogRecur(n: number): number {
-    if (n <= 1) return 1;
-    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);
-    for (let i = 0; i < n; i++) {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.c
/* 线性对数阶 */
-int linearLogRecur(float n) {
-    if (n <= 1) return 1;
-    int count = linearLogRecur(n / 2) +
-                linearLogRecur(n / 2);
-    for (int i = 0; i < n; i++) {
-        count ++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 线性对数阶 */
-int linearLogRecur(float n)
-{
-    if (n <= 1) return 1;
-    int count = linearLogRecur(n / 2) +
-                linearLogRecur(n / 2);
-    for (int i = 0; i < n; i++)
-    {
-        count++;
-    }
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 线性对数阶 */
-func linearLogRecur(n: Double) -> Int {
-    if n <= 1 {
-        return 1
-    }
-    var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)
-    for _ in 0 ..< Int(n) {
-        count += 1
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 线性对数阶
-fn linearLogRecur(n: f32) i32
-{
-    if (n <= 1) return 1;
-    var count: i32 = linearLogRecur(n / 2) +
-                linearLogRecur(n / 2);
-    var i: f32 = 0;
-    while (i < n) : (i += 1) {
-        count += 1;
-    }
-    return count;
-}
-
-
-
-
-

time_complexity_logarithmic_linear

-

Fig. 线性对数阶的时间复杂度

- -

阶乘阶 \(O(n!)\)

-

阶乘阶对应数学上的「全排列」。即给定 \(n\) 个互不重复的元素,求其所有可能的排列方案,则方案数量为

-
\[ -n! = n \times (n - 1) \times (n - 2) \times \cdots \times 2 \times 1 -\]
-

阶乘常使用递归实现。例如以下代码,第一层分裂出 \(n\) 个,第二层分裂出 \(n - 1\) 个,…… ,直至到第 \(n\) 层时终止分裂。

-
-
-
-
time_complexity.java
/* 阶乘阶(递归实现) */
-int factorialRecur(int n) {
-    if (n == 0) return 1;
-    int count = 0;
-    // 从 1 个分裂出 n 个
-    for (int i = 0; i < n; i++) {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
time_complexity.cpp
/* 阶乘阶(递归实现) */
-int factorialRecur(int n) {
-    if (n == 0) return 1;
-    int count = 0;
-    // 从 1 个分裂出 n 个
-    for (int i = 0; i < n; i++) {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
time_complexity.py
""" 阶乘阶(递归实现)"""
-def factorial_recur(n):
-    if n == 0: return 1
-    count = 0
-    # 从 1 个分裂出 n 个
-    for _ in range(n):
-        count += factorial_recur(n - 1)
-    return count
-
-
-
-
time_complexity.go
/* 阶乘阶(递归实现) */
-func factorialRecur(n int) int {
-    if n == 0 {
-        return 1
-    }
-    count := 0
-    // 从 1 个分裂出 n 个
-    for i := 0; i < n; i++ {
-        count += factorialRecur(n - 1)
-    }
-    return count
-}
-
-
-
-
time_complexity.js
/* 阶乘阶(递归实现) */
-function factorialRecur(n) {
-    if (n == 0) return 1;
-    let count = 0;
-    // 从 1 个分裂出 n 个
-    for (let i = 0; i < n; i++) {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
time_complexity.ts
/* 阶乘阶(递归实现) */
-function factorialRecur(n: number): number {
-    if (n == 0) return 1;
-    let count = 0;
-    // 从 1 个分裂出 n 个
-    for (let i = 0; i < n; i++) {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
time_complexity.c
/* 阶乘阶(递归实现) */
-int factorialRecur(int n) {
-    if (n == 0) return 1;
-    int count = 0;
-    for (int i = 0; i < n; i++) {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
time_complexity.cs
/* 阶乘阶(递归实现) */
-int factorialRecur(int n)
-{
-    if (n == 0) return 1;
-    int count = 0;
-    // 从 1 个分裂出 n 个
-    for (int i = 0; i < n; i++)
-    {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
time_complexity.swift
/* 阶乘阶(递归实现) */
-func factorialRecur(n: Int) -> Int {
-    if n == 0 {
-        return 1
-    }
-    var count = 0
-    // 从 1 个分裂出 n 个
-    for _ in 0 ..< n {
-        count += factorialRecur(n: n - 1)
-    }
-    return count
-}
-
-
-
-
time_complexity.zig
// 阶乘阶(递归实现)
-fn factorialRecur(n: i32) i32 {
-    if (n == 0) return 1;
-    var count: i32 = 0;
-    var i: i32 = 0;
-    // 从 1 个分裂出 n 个
-    while (i < n) : (i += 1) {
-        count += factorialRecur(n - 1);
-    }
-    return count;
-}
-
-
-
-
-

time_complexity_factorial

-

Fig. 阶乘阶的时间复杂度

- -

2.2.6. 最差、最佳、平均时间复杂度

-

某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。举一个例子,输入一个长度为 \(n\) 数组 nums ,其中 nums 由从 \(1\)\(n\) 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 \(1\) 的索引。我们可以得出以下结论:

-
    -
  • nums = [?, ?, ..., 1],即当末尾元素是 \(1\) 时,则需完整遍历数组,此时达到 最差时间复杂度 \(O(n)\)
  • -
  • nums = [1, ?, ?, ...] ,即当首个数字为 \(1\) 时,无论数组多长都不需要继续遍历,此时达到 最佳时间复杂度 \(\Omega(1)\)
  • -
-

「函数渐近上界」使用大 \(O\) 记号表示,代表「最差时间复杂度」。与之对应,「函数渐近下界」用 \(\Omega\) 记号(Omega Notation)来表示,代表「最佳时间复杂度」。

-
-
-
-
worst_best_time_complexity.java
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-int[] randomNumbers(int n) {
-    Integer[] nums = new Integer[n];
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (int i = 0; i < n; i++) {
-        nums[i] = i + 1;
-    }
-    // 随机打乱数组元素
-    Collections.shuffle(Arrays.asList(nums));
-    // Integer[] -> int[]
-    int[] res = new int[n];
-    for (int i = 0; i < n; i++) {
-        res[i] = nums[i];
-    }
-    return res;
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-int findOne(int[] nums) {
-    for (int i = 0; i < nums.length; i++) {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (nums[i] == 1)
-            return i;
-    }
-    return -1;
-}
-
-
-
-
worst_best_time_complexity.cpp
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-vector<int> randomNumbers(int n) {
-    vector<int> nums(n);
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (int i = 0; i < n; i++) {
-        nums[i] = i + 1;
-    }
-    // 使用系统时间生成随机种子
-    unsigned seed = chrono::system_clock::now().time_since_epoch().count();
-    // 随机打乱数组元素
-    shuffle(nums.begin(), nums.end(), default_random_engine(seed));
-    return nums;
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-int findOne(vector<int>& nums) {
-    for (int i = 0; i < nums.size(); i++) {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (nums[i] == 1)
-            return i;
-    }
-    return -1;
-}
-
-
-
-
worst_best_time_complexity.py
""" 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """
-def random_numbers(n):
-    # 生成数组 nums =: 1, 2, 3, ..., n
-    nums = [i for i in range(1, n + 1)]
-    # 随机打乱数组元素
-    random.shuffle(nums)
-    return nums
-
-""" 查找数组 nums 中数字 1 所在索引 """
-def find_one(nums):
-    for i in range(len(nums)):
-        # 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        # 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if nums[i] == 1:
-            return i
-    return -1
-
-
-
-
worst_best_time_complexity.go
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-func randomNumbers(n int) []int {
-    nums := make([]int, n)
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for i := 0; i < n; i++ {
-        nums[i] = i + 1
-    }
-    // 随机打乱数组元素
-    rand.Shuffle(len(nums), func(i, j int) {
-        nums[i], nums[j] = nums[j], nums[i]
-    })
-    return nums
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-func findOne(nums []int) int {
-    for i := 0; i < len(nums); i++ {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if nums[i] == 1 {
-            return i
-        }
-    }
-    return -1
-}
-
-
-
-
worst_best_time_complexity.js
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-function randomNumbers(n) {
-    const nums = Array(n);
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (let i = 0; i < n; i++) {
-        nums[i] = i + 1;
-    }
-    // 随机打乱数组元素
-    for (let i = 0; i < n; i++) {
-        const r = Math.floor(Math.random() * (i + 1));
-        const temp = nums[i];
-        nums[i] = nums[r];
-        nums[r] = temp;
-    }
-    return nums;
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-function findOne(nums) {
-    for (let i = 0; i < nums.length; i++) {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (nums[i] === 1) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-
-
-
worst_best_time_complexity.ts
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-function randomNumbers(n: number): number[] {
-    const nums = Array(n);
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (let i = 0; i < n; i++) {
-        nums[i] = i + 1;
-    }
-    // 随机打乱数组元素
-    for (let i = 0; i < n; i++) {
-        const r = Math.floor(Math.random() * (i + 1));
-        const temp = nums[i];
-        nums[i] = nums[r];
-        nums[r] = temp;
-    }
-    return nums;
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-function findOne(nums: number[]): number {
-    for (let i = 0; i < nums.length; i++) {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (nums[i] === 1) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-
-
-
worst_best_time_complexity.c
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-int *randomNumbers(int n) {
-    // 分配堆区内存(创建一维可变长数组:数组中元素数量为n,元素类型为int)
-    int *nums = (int *)malloc(n * sizeof(int));
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (int i = 0; i < n; i++) {
-        nums[i] = i + 1;
-    }
-    // 随机打乱数组元素 
-    for (int i = n - 1; i > 0; i--) {
-        int j = rand() % (i + 1);
-        int temp = nums[i];
-        nums[i] = nums[j];
-        nums[j] = temp; 
-    }
-    return nums;
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-int findOne(int *nums, int n) {
-    for (int i = 0; i < n; i++) {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (nums[i] == 1) return i;
-    }
-    return -1;
-}
-
-
-
-
worst_best_time_complexity.cs
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-int[] randomNumbers(int n)
-{
-    int[] nums = new int[n];
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (int i = 0; i < n; i++)
-    {
-        nums[i] = i + 1;
-    }
-
-    // 随机打乱数组元素
-    for (int i = 0; i < nums.Length; i++)
-    {
-        var index = new Random().Next(i, nums.Length);
-        var tmp = nums[i];
-        var ran = nums[index];
-        nums[i] = ran;
-        nums[index] = tmp;
-    }
-    return nums;
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-int findOne(int[] nums)
-{
-    for (int i = 0; i < nums.Length; i++)
-    {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (nums[i] == 1)
-            return i;
-    }
-    return -1;
-}
-
-
-
-
worst_best_time_complexity.swift
/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */
-func randomNumbers(n: Int) -> [Int] {
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    var nums = Array(1 ... n)
-    // 随机打乱数组元素
-    nums.shuffle()
-    return nums
-}
-
-/* 查找数组 nums 中数字 1 所在索引 */
-func findOne(nums: [Int]) -> Int {
-    for i in nums.indices {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if nums[i] == 1 {
-            return i
-        }
-    }
-    return -1
-}
-
-
-
-
worst_best_time_complexity.zig
// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱
-pub fn randomNumbers(comptime n: usize) [n]i32 {
-    var nums: [n]i32 = undefined;
-    // 生成数组 nums = { 1, 2, 3, ..., n }
-    for (nums) |*num, i| {
-        num.* = @intCast(i32, i) + 1;
-    }
-    // 随机打乱数组元素
-    const rand = std.crypto.random;
-    rand.shuffle(i32, &nums);
-    return nums;
-}
-
-// 查找数组 nums 中数字 1 所在索引
-pub fn findOne(nums: []i32) i32 {
-    for (nums) |num, i| {
-        // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1)
-        // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n)
-        if (num == 1) return @intCast(i32, i);
-    }
-    return -1;
-}
-
-
-
-
-
-

Tip

-

我们在实际应用中很少使用「最佳时间复杂度」,因为往往只有很小概率下才能达到,会带来一定的误导性。反之,「最差时间复杂度」最为实用,因为它给出了一个“效率安全值”,让我们可以放心地使用算法。

-
-

从上述示例可以看出,最差或最佳时间复杂度只出现在“特殊分布的数据”中,这些情况的出现概率往往很小,因此并不能最真实地反映算法运行效率。相对地,「平均时间复杂度」可以体现算法在随机输入数据下的运行效率,用 \(\Theta\) 记号(Theta Notation)来表示

-

对于部分算法,我们可以简单地推算出随机数据分布下的平均情况。比如上述示例,由于输入数组是被打乱的,因此元素 \(1\) 出现在任意索引的概率都是相等的,那么算法的平均循环次数则是数组长度的一半 \(\frac{n}{2}\) ,平均时间复杂度为 \(\Theta(\frac{n}{2}) = \Theta(n)\)

-

但在实际应用中,尤其是较为复杂的算法,计算平均时间复杂度比较困难,因为很难简便地分析出在数据分布下的整体数学期望。这种情况下,我们一般使用最差时间复杂度来作为算法效率的评判标准。

-
-

为什么很少看到 \(\Theta\) 符号?

-

实际中我们经常使用「大 \(O\) 符号」来表示「平均复杂度」,这样严格意义上来说是不规范的。这可能是因为 \(O\) 符号实在是太朗朗上口了。
如果在本书和其他资料中看到类似 平均时间复杂度 \(O(n)\) 的表述,请你直接理解为 \(\Theta(n)\) 即可。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png b/site/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png deleted file mode 100644 index 7375e43c3..000000000 Binary files a/site/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png and /dev/null differ diff --git a/site/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png b/site/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png deleted file mode 100644 index 29ef8bb22..000000000 Binary files a/site/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png and /dev/null differ diff --git a/site/chapter_data_structure/classification_of_data_structure/index.html b/site/chapter_data_structure/classification_of_data_structure/index.html deleted file mode 100644 index 593a92a9c..000000000 --- a/site/chapter_data_structure/classification_of_data_structure/index.html +++ /dev/null @@ -1,1769 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3.2. 数据结构分类 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

3.2. 数据结构分类

-

数据结构主要可根据「逻辑结构」和「物理结构」两种角度进行分类。

-

3.2.1. 逻辑结构:线性与非线性

-

「逻辑结构」反映了数据之间的逻辑关系。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。

-

我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线性的(例如是网状或树状的),那么就是非线性数据结构。

-
    -
  • 线性数据结构:数组、链表、栈、队列、哈希表;
  • -
  • 非线性数据结构:树、图、堆、哈希表;
  • -
-

classification_logic_structure

-

Fig. 线性与非线性数据结构

- -

3.2.2. 物理结构:连续与离散

-
-

Note

-

若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。

-
-

「物理结构」反映了数据在计算机内存中的存储方式。从本质上看,分别是 数组的连续空间存储链表的离散空间存储。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。

-

classification_phisical_structure

-

Fig. 连续空间存储与离散空间存储

- -

所有数据结构都是基于数组、或链表、或两者组合实现的。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。

-
    -
  • 基于数组可实现:栈、队列、哈希表、树、堆、图、矩阵、张量(维度 \(\geq 3\) 的数组)等;
  • -
  • 基于链表可实现:栈、队列、哈希表、树、堆、图等;
  • -
-

基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度。

-
-

Tip

-

数组与链表是其他所有数据结构的“底层积木”,建议读者一定要多花些时间了解。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_data_structure/data_and_memory.assets/computer_memory_location.png b/site/chapter_data_structure/data_and_memory.assets/computer_memory_location.png deleted file mode 100644 index 900643191..000000000 Binary files a/site/chapter_data_structure/data_and_memory.assets/computer_memory_location.png and /dev/null differ diff --git a/site/chapter_data_structure/data_and_memory/index.html b/site/chapter_data_structure/data_and_memory/index.html deleted file mode 100644 index ca768005c..000000000 --- a/site/chapter_data_structure/data_and_memory/index.html +++ /dev/null @@ -1,1912 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3.1. 数据与内存 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

3.1. 数据与内存

-

3.1.1. 基本数据类型

-

谈到计算机中的数据,我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但是有一个共同点,即都是由各种基本数据类型构成的。

-

「基本数据类型」是 CPU 可以直接进行运算的类型,在算法中直接被使用。

-
    -
  • 「整数」根据不同的长度分为 byte, short, int, long ,根据算法需求选用,即在满足取值范围的情况下尽量减小内存空间占用;
  • -
  • 「浮点数」代表小数,根据长度分为 float, double ,同样根据算法的实际需求选用;
  • -
  • 「字符」在计算机中是以字符集的形式保存的,char 的值实际上是数字,代表字符集中的编号,计算机通过字符集查表来完成编号到字符的转换。占用空间与具体编程语言有关,通常为 2 bytes 或 1 byte ;
  • -
  • 「布尔」代表逻辑中的 “是” 与 “否” ,其占用空间需要具体根据编程语言确定,通常为 1 byte 或 1 bit ;
  • -
-
-

字节与比特

-

1 字节 (byte) = 8 比特 (bit) , 1 比特即最基本的 1 个二进制位

-
-

Table. Java 的基本数据类型

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
类别符号占用空间取值范围默认值
整数byte1 byte\(-2^7\) ~ \(2^7 - 1\) ( \(-128\) ~ \(127\) )\(0\)
short2 bytes\(-2^{15}\) ~ \(2^{15} - 1\)\(0\)
int4 bytes\(-2^{31}\) ~ \(2^{31} - 1\)\(0\)
long8 bytes\(-2^{63}\) ~ \(2^{63} - 1\)\(0\)
浮点数float4 bytes\(-3.4 \times 10^{38}\) ~ \(3.4 \times 10^{38}\)\(0.0\) f
double8 bytes\(-1.7 \times 10^{308}\) ~ \(1.7 \times 10^{308}\)\(0.0\)
字符char2 bytes / 1 byte\(0\) ~ \(2^{16} - 1\)\(0\)
布尔boolean(bool)1 byte / 1 bit\(\text{true}\)\(\text{false}\)\(\text{false}\)
-
-
-

Tip

-

以上表格中,加粗项在「算法题」中最为常用。此表格无需硬背,大致理解即可,需要时可以通过查表来回忆。

-
-

「基本数据类型」与「数据结构」之间的联系与区别

-

我们知道,数据结构是在计算机中 组织与存储数据的方式,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,则与所谓的数据的结构无关了

-
-
-
-
/* 使用多种「基本数据类型」来初始化「数组」 */
-int[] numbers = new int[5];
-float[] decimals = new float[5];
-char[] characters = new char[5];
-boolean[] booleans = new boolean[5];
-
-
-
-
/* 使用多种「基本数据类型」来初始化「数组」 */
-int numbers[5];
-float decimals[5];
-char characters[5];
-bool booleans[5];
-
-
-
-
""" Python 的 list 可以自由存储各种基本数据类型和对象 """
-list = [0, 0.0, 'a', False]
-
-
-
-
// 使用多种「基本数据类型」来初始化「数组」
-var numbers = [5]int{}
-var decimals = [5]float64{}
-var characters = [5]byte{}
-var booleans = [5]bool{}
-
-
-
-
/* JavaScript 的数组可以自由存储各种基本数据类型和对象 */
-const array = [0, 0.0, 'a', false];
-
-
-
-
/* 使用多种「基本数据类型」来初始化「数组」 */
-const numbers: number[] = [];
-const characters: string[] = [];
-const booleans: boolean[] = [];
-
-
-
-
/* 使用多种「基本数据类型」来初始化「数组」 */
-int numbers[10];
-float decimals[10];
-char characters[10];
-bool booleans[10];
-
-
-
-
/* 使用多种「基本数据类型」来初始化「数组」 */
-int[] numbers = new int[5];
-float[] decimals = new float[5];
-char[] characters = new char[5];
-bool[] booleans = new bool[5];
-
-
-
-
/* 使用多种「基本数据类型」来初始化「数组」 */
-let numbers = Array(repeating: Int(), count: 5)
-let decimals = Array(repeating: Double(), count: 5)
-let characters = Array(repeating: Character("a"), count: 5)
-let booleans = Array(repeating: Bool(), count: 5)
-
-
-
-

-
-
-
-
-

3.1.2. 计算机内存

-

在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。

-

算法运行中,相关数据都被存储在内存中。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。

-

系统通过「内存地址 Memory Location」来访问目标内存位置的数据。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。

-

computer_memory_location

-

Fig. 内存条、内存空间、内存地址

- -

内存资源是设计数据结构与算法的重要考虑因素。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_data_structure/summary/index.html b/site/chapter_data_structure/summary/index.html deleted file mode 100644 index b67ac7e24..000000000 --- a/site/chapter_data_structure/summary/index.html +++ /dev/null @@ -1,1686 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3.3. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

3.3. 小结

-
    -
  • 整数 byte, short, int, long 、浮点数 float, double 、字符 char 、布尔 boolean 是计算机中的基本数据类型,占用空间的大小决定了它们的取值范围。
  • -
  • 在程序运行时,数据存储在计算机的内存中。内存中每块空间都有独立的内存地址,程序是通过内存地址来访问数据的。
  • -
  • 数据结构主要可以从逻辑结构和物理结构两个角度进行分类。逻辑结构反映了数据中元素之间的逻辑关系,物理结构反映了数据在计算机内存中的存储形式。
  • -
  • 常见的逻辑结构有线性、树状、网状等。我们一般根据逻辑结构将数据结构分为线性(数组、链表、栈、队列)和非线性(树、图、堆)两种。根据实现方式的不同,哈希表可能是线性或非线性。
  • -
  • 物理结构主要有两种,分别是连续空间存储(数组)和离散空间存储(链表),所有的数据结构都是由数组、或链表、或两者组合实现的。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_graph/graph.assets/adjacency_list.png b/site/chapter_graph/graph.assets/adjacency_list.png deleted file mode 100644 index 24bd4a488..000000000 Binary files a/site/chapter_graph/graph.assets/adjacency_list.png and /dev/null differ diff --git a/site/chapter_graph/graph.assets/adjacency_matrix.png b/site/chapter_graph/graph.assets/adjacency_matrix.png deleted file mode 100644 index 24b10040c..000000000 Binary files a/site/chapter_graph/graph.assets/adjacency_matrix.png and /dev/null differ diff --git a/site/chapter_graph/graph.assets/connected_graph.png b/site/chapter_graph/graph.assets/connected_graph.png deleted file mode 100644 index d7a1031d1..000000000 Binary files a/site/chapter_graph/graph.assets/connected_graph.png and /dev/null differ diff --git a/site/chapter_graph/graph.assets/directed_graph.png b/site/chapter_graph/graph.assets/directed_graph.png deleted file mode 100644 index b5d6ea941..000000000 Binary files a/site/chapter_graph/graph.assets/directed_graph.png and /dev/null differ diff --git a/site/chapter_graph/graph.assets/linkedlist_tree_graph.png b/site/chapter_graph/graph.assets/linkedlist_tree_graph.png deleted file mode 100644 index eacfa9258..000000000 Binary files a/site/chapter_graph/graph.assets/linkedlist_tree_graph.png and /dev/null differ diff --git a/site/chapter_graph/graph.assets/weighted_graph.png b/site/chapter_graph/graph.assets/weighted_graph.png deleted file mode 100644 index 9eb4bcfc4..000000000 Binary files a/site/chapter_graph/graph.assets/weighted_graph.png and /dev/null differ diff --git a/site/chapter_graph/graph/index.html b/site/chapter_graph/graph/index.html deleted file mode 100644 index 4536d6ba9..000000000 --- a/site/chapter_graph/graph/index.html +++ /dev/null @@ -1,1889 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 9.1. 图(Graph) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

9.1. 图

-

「图 Graph」是一种非线性数据结构,由「顶点 Vertex」和「边 Edge」组成。我们可将图 \(G\) 抽象地表示为一组顶点 \(V\) 和一组边 \(E\) 的集合。例如,以下表示一个包含 5 个顶点和 7 条边的图

-
\[ -\begin{aligned} -V & = \{ 1, 2, 3, 4, 5 \} \newline -E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline -G & = \{ V, E \} \newline -\end{aligned} -\]
-

linkedlist_tree_graph

-

那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂

-

9.1.1. 图常见类型

-

根据边是否有方向,分为「无向图 Undirected Graph」和「有向图 Directed Graph」。

-
    -
  • 在无向图中,边表示两结点之间“双向”的连接关系,例如微信或 QQ 中的“好友关系”;
  • -
  • 在有向图中,边是有方向的,即 \(A \rightarrow B\)\(A \leftarrow B\) 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系;
  • -
-

directed_graph

-

根据所有顶点是否连通,分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。

-
    -
  • 对于连通图,从某个结点出发,可以到达其余任意结点;
  • -
  • 对于非连通图,从某个结点出发,至少有一个结点无法到达;
  • -
-

connected_graph

-

我们可以给边添加“权重”变量,得到「有权图 Weighted Graph」。例如,在王者荣耀等游戏中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以使用有权图来表示。

-

weighted_graph

-

9.1.2. 图常用术语

-
    -
  • 「邻接 Adjacency」:当两顶点之间有边相连时,称此两顶点“邻接”。
  • -
  • 「路径 Path」:从顶点 A 到顶点 B 走过的边构成的序列,被称为从 A 到 B 的“路径”。
  • -
  • 「度 Degree」表示一个顶点具有多少条边。对于有向图,「入度 In-Degree」表示有多少条边指向该顶点,「出度 Out-Degree」表示有多少条边从该顶点指出。
  • -
-

9.1.3. 图的表示

-

图的常用表示方法有「邻接矩阵」和「邻接表」。以下使用「无向图」来举例。

-

邻接矩阵

-

设图的顶点数量为 \(n\) ,「邻接矩阵 Adjacency Matrix」使用一个 \(n \times n\) 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,使用 \(1\)\(0\) 来表示两个顶点之间有边或无边。

-

adjacency_matrix

-

邻接矩阵具有以下性质:

-
    -
  • 顶点不能与自身相连,因而邻接矩阵主对角线元素没有意义。
  • -
  • 「无向图」两个方向的边等价,此时邻接矩阵关于主对角线对称。
  • -
  • 将邻接矩阵的元素从 \(1\) , \(0\) 替换为权重,则能够表示「有权图」。
  • -
-

使用邻接矩阵表示图时,我们可以直接通过访问矩阵元素来获取边,因此增删查操作的效率很高,时间复杂度均为 \(O(1)\) 。然而,矩阵的空间复杂度为 \(O(n^2)\) ,内存占用较大。

-

邻接表

-

「邻接表 Adjacency List」使用 \(n\) 个链表来表示图,链表结点表示顶点。第 \(i\) 条链表对应顶点 \(i\) ,其中存储了所有与该顶点相连的顶点。

-

adjacency_list

-

邻接表仅存储存在的边,而边的总数往往远小于 \(n^2\) ,因此更加节省空间。但是,因为在邻接表中需要通过遍历链表来查找边,所以其时间效率不如邻接矩阵。

-

观察上图发现,邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 \(O(n)\) 优化至 \(O(\log n)\) ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 \(O(1)\) ,。

-

9.1.4. 图常见应用

-

现实中的许多系统都可以使用图来建模,对应的待求解问题也可以被约化为图计算问题。

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
顶点图计算问题
社交网络用户好友关系潜在好友推荐
地铁线路站点站点间的连通性最短路线推荐
太阳系星体星体间的万有引力作用行星轨道计算
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_graph/graph_operations.assets/adjacency_list_add_edge.png b/site/chapter_graph/graph_operations.assets/adjacency_list_add_edge.png deleted file mode 100644 index 7261fb983..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_list_add_edge.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_list_add_vertex.png b/site/chapter_graph/graph_operations.assets/adjacency_list_add_vertex.png deleted file mode 100644 index 484f7c7a0..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_list_add_vertex.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_list_initialization.png b/site/chapter_graph/graph_operations.assets/adjacency_list_initialization.png deleted file mode 100644 index 4ccc52d6d..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_list_initialization.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_list_remove_edge.png b/site/chapter_graph/graph_operations.assets/adjacency_list_remove_edge.png deleted file mode 100644 index a9a3ff004..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_list_remove_edge.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_list_remove_vertex.png b/site/chapter_graph/graph_operations.assets/adjacency_list_remove_vertex.png deleted file mode 100644 index 24d10194f..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_list_remove_vertex.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_matrix_add_edge.png b/site/chapter_graph/graph_operations.assets/adjacency_matrix_add_edge.png deleted file mode 100644 index 6f753f9c2..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_matrix_add_edge.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_matrix_add_vertex.png b/site/chapter_graph/graph_operations.assets/adjacency_matrix_add_vertex.png deleted file mode 100644 index 6531e2700..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_matrix_add_vertex.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_matrix_initialization.png b/site/chapter_graph/graph_operations.assets/adjacency_matrix_initialization.png deleted file mode 100644 index 6a47786c4..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_matrix_initialization.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_matrix_remove_edge.png b/site/chapter_graph/graph_operations.assets/adjacency_matrix_remove_edge.png deleted file mode 100644 index e2f751777..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_matrix_remove_edge.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations.assets/adjacency_matrix_remove_vertex.png b/site/chapter_graph/graph_operations.assets/adjacency_matrix_remove_vertex.png deleted file mode 100644 index 692c938ac..000000000 Binary files a/site/chapter_graph/graph_operations.assets/adjacency_matrix_remove_vertex.png and /dev/null differ diff --git a/site/chapter_graph/graph_operations/index.html b/site/chapter_graph/graph_operations/index.html deleted file mode 100644 index 548311c4a..000000000 --- a/site/chapter_graph/graph_operations/index.html +++ /dev/null @@ -1,2434 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 9.2. 图基础操作 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

9.2. 图基础操作

-

图的基础操作分为对「边」的操作和对「顶点」的操作,在「邻接矩阵」和「邻接表」这两种表示下的实现方式不同。

-

9.2.1. 基于邻接矩阵的实现

-

设图的顶点总数为 \(n\) ,则有:

-
    -
  • 添加或删除边:直接在邻接矩阵中修改指定边的对应元素即可,使用 \(O(1)\) 时间。而由于是无向图,因此需要同时更新两个方向的边。
  • -
  • 添加顶点:在邻接矩阵的尾部添加一行一列,并全部填 \(0\) 即可,使用 \(O(n)\) 时间。
  • -
  • 删除顶点:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 \((n-1)^2\) 个元素“向左上移动”,从而使用 \(O(n^2)\) 时间。
  • -
  • 初始化:传入 \(n\) 个顶点,初始化长度为 \(n\) 的顶点列表 vertices ,使用 \(O(n)\) 时间;初始化 \(n \times n\) 大小的邻接矩阵 adjMat ,使用 \(O(n^2)\) 时间。
  • -
-
-
-
-

adjacency_matrix_initialization

-
-
-

adjacency_matrix_add_edge

-
-
-

adjacency_matrix_remove_edge

-
-
-

adjacency_matrix_add_vertex

-
-
-

adjacency_matrix_remove_vertex

-
-
-
-

以下是基于邻接矩阵表示图的实现代码。

-
-
-
-
graph_adjacency_matrix.java
/* 基于邻接矩阵实现的无向图类 */
-class GraphAdjMat {
-    List<Integer> vertices;     // 顶点列表,元素代表“顶点值”,索引代表“顶点索引”
-    List<List<Integer>> adjMat; // 邻接矩阵,行列索引对应“顶点索引”
-
-    /* 构造函数 */
-    public GraphAdjMat(int[] vertices, int[][] edges) {
-        this.vertices = new ArrayList<>();
-        this.adjMat = new ArrayList<>();
-        // 添加顶点
-        for (int val : vertices) {
-            addVertex(val);
-        }
-        // 添加边
-        // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引
-        for (int[] e : edges) {
-            addEdge(e[0], e[1]);
-        }
-    }
-
-    /* 获取顶点数量 */
-    public int size() {
-        return vertices.size();
-    }
-
-    /* 添加顶点 */
-    public void addVertex(int val) {
-        int n = size();
-        // 向顶点列表中添加新顶点的值
-        vertices.add(val);
-        // 在邻接矩阵中添加一行
-        List<Integer> newRow = new ArrayList<>(n);
-        for (int j = 0; j < n; j++) {
-            newRow.add(0);
-        }
-        adjMat.add(newRow);
-        // 在邻接矩阵中添加一列
-        for (List<Integer> row : adjMat) {
-            row.add(0);
-        }
-    }
-
-    /* 删除顶点 */
-    public void removeVertex(int index) {
-        if (index >= size())
-            throw new IndexOutOfBoundsException();
-        // 在顶点列表中移除索引 index 的顶点
-        vertices.remove(index);
-        // 在邻接矩阵中删除索引 index 的行
-        adjMat.remove(index);
-        // 在邻接矩阵中删除索引 index 的列
-        for (List<Integer> row : adjMat) {
-            row.remove(index);
-        }
-    }
-
-    /* 添加边 */
-    // 参数 i, j 对应 vertices 元素索引
-    public void addEdge(int i, int j) {
-        // 索引越界与相等处理
-        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)
-            throw new IndexOutOfBoundsException();
-        // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i)
-        adjMat.get(i).set(j, 1);
-        adjMat.get(j).set(i, 1);
-    }
-
-    /* 删除边 */
-    // 参数 i, j 对应 vertices 元素索引
-    public void removeEdge(int i, int j) {
-        // 索引越界与相等处理
-        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)
-            throw new IndexOutOfBoundsException();
-        adjMat.get(i).set(j, 0);
-        adjMat.get(j).set(i, 0);
-    }
-
-    /* 打印邻接矩阵 */
-    public void print() {
-        System.out.print("顶点列表 = ");
-        System.out.println(vertices);
-        System.out.println("邻接矩阵 =");
-        PrintUtil.printMatrix(adjMat);
-    }
-}
-
-
-
-
graph_adjacency_matrix.cpp

-
-
-
-
graph_adjacency_matrix.py

-
-
-
-
graph_adjacency_matrix.go
/* 基于邻接矩阵实现的无向图类 */
-type graphAdjMat struct {
-    // 顶点列表,元素代表“顶点值”,索引代表“顶点索引”
-    vertices []int
-    // 邻接矩阵,行列索引对应“顶点索引”
-    adjMat [][]int
-}
-
-func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {
-    // 添加顶点
-    n := len(vertices)
-    adjMat := make([][]int, n)
-    for i := range adjMat {
-        adjMat[i] = make([]int, n)
-    }
-    // 初始化图
-    g := &graphAdjMat{
-        vertices: vertices,
-        adjMat:   adjMat,
-    }
-    // 添加边
-    // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引
-    for i := range edges {
-        g.addEdge(edges[i][0], edges[i][1])
-    }
-    return g
-}
-
-/* 获取顶点数量 */
-func (g *graphAdjMat) size() int {
-    return len(g.vertices)
-}
-
-/* 添加顶点 */
-func (g *graphAdjMat) addVertex(val int) {
-    n := g.size()
-    // 向顶点列表中添加新顶点的值
-    g.vertices = append(g.vertices, val)
-    // 在邻接矩阵中添加一行
-    newRow := make([]int, n)
-    g.adjMat = append(g.adjMat, newRow)
-    // 在邻接矩阵中添加一列
-    for i := range g.adjMat {
-        g.adjMat[i] = append(g.adjMat[i], 0)
-    }
-}
-
-/* 删除顶点 */
-func (g *graphAdjMat) removeVertex(index int) {
-    if index >= g.size() {
-        return
-    }
-    // 在顶点列表中移除索引 index 的顶点
-    g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)
-    // 在邻接矩阵中删除索引 index 的行
-    g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)
-    // 在邻接矩阵中删除索引 index 的列
-    for i := range g.adjMat {
-        g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)
-    }
-}
-
-/* 添加边 */
-// 参数 i, j 对应 vertices 元素索引
-func (g *graphAdjMat) addEdge(i, j int) {
-    // 索引越界与相等处理
-    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {
-        fmt.Errorf("%s", "Index Out Of Bounds Exception")
-    }
-    // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i)
-    g.adjMat[i][j] = 1
-    g.adjMat[j][i] = 1
-}
-
-/* 删除边 */
-// 参数 i, j 对应 vertices 元素索引
-func (g *graphAdjMat) removeEdge(i, j int) {
-    // 索引越界与相等处理
-    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {
-        fmt.Errorf("%s", "Index Out Of Bounds Exception")
-    }
-    g.adjMat[i][j] = 0
-    g.adjMat[j][i] = 0
-}
-
-
-
-
graph_adjacency_matrix.js

-
-
-
-
graph_adjacency_matrix.ts

-
-
-
-
graph_adjacency_matrix.c

-
-
-
-
graph_adjacency_matrix.cs

-
-
-
-
graph_adjacency_matrix.swift
/* 基于邻接矩阵实现的无向图类 */
-class GraphAdjMat {
-    private var vertices: [Int] // 顶点列表,元素代表“顶点值”,索引代表“顶点索引”
-    private var adjMat: [[Int]] // 邻接矩阵,行列索引对应“顶点索引”
-
-    /* 构造函数 */
-    init(vertices: [Int], edges: [[Int]]) {
-        self.vertices = []
-        adjMat = []
-        // 添加顶点
-        for val in vertices {
-            addVertex(val: val)
-        }
-        // 添加边
-        // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引
-        for e in edges {
-            addEdge(i: e[0], j: e[1])
-        }
-    }
-
-    /* 获取顶点数量 */
-    func size() -> Int {
-        vertices.count
-    }
-
-    /* 添加顶点 */
-    func addVertex(val: Int) {
-        let n = size()
-        // 向顶点列表中添加新顶点的值
-        vertices.append(val)
-        // 在邻接矩阵中添加一行
-        let newRow = Array(repeating: 0, count: n)
-        adjMat.append(newRow)
-        // 在邻接矩阵中添加一列
-        for i in adjMat.indices {
-            adjMat[i].append(0)
-        }
-    }
-
-    /* 删除顶点 */
-    func removeVertex(index: Int) {
-        if index >= size() {
-            fatalError("越界")
-        }
-        // 在顶点列表中移除索引 index 的顶点
-        vertices.remove(at: index)
-        // 在邻接矩阵中删除索引 index 的行
-        adjMat.remove(at: index)
-        // 在邻接矩阵中删除索引 index 的列
-        for i in adjMat.indices {
-            adjMat[i].remove(at: index)
-        }
-    }
-
-    /* 添加边 */
-    // 参数 i, j 对应 vertices 元素索引
-    func addEdge(i: Int, j: Int) {
-        // 索引越界与相等处理
-        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {
-            fatalError("越界")
-        }
-        // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i)
-        adjMat[i][j] = 1
-        adjMat[j][i] = 1
-    }
-
-    /* 删除边 */
-    // 参数 i, j 对应 vertices 元素索引
-    func removeEdge(i: Int, j: Int) {
-        // 索引越界与相等处理
-        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {
-            fatalError("越界")
-        }
-        adjMat[i][j] = 0
-        adjMat[j][i] = 0
-    }
-}
-
-
-
-
graph_adjacency_matrix.zig

-
-
-
-
-

9.2.2. 基于邻接表的实现

-

设图的顶点总数为 \(n\) 、边总数为 \(m\) ,则有:

-
    -
  • 添加边:在顶点对应链表的尾部添加边即可,使用 \(O(1)\) 时间。因为是无向图,所以需要同时添加两个方向的边。
  • -
  • 删除边:在顶点对应链表中查询与删除指定边,使用 \(O(m)\) 时间。与添加边一样,需要同时删除两个方向的边。
  • -
  • 添加顶点:在邻接表中添加一个链表即可,并以新增顶点为链表头结点,使用 \(O(1)\) 时间。
  • -
  • 删除顶点:需要遍历整个邻接表,删除包含指定顶点的所有边,使用 \(O(n + m)\) 时间。
  • -
  • 初始化:需要在邻接表中建立 \(n\) 个结点和 \(2m\) 条边,使用 \(O(n + m)\) 时间。
  • -
-
-
-
-

adjacency_list_initialization

-
-
-

adjacency_list_add_edge

-
-
-

adjacency_list_remove_edge

-
-
-

adjacency_list_add_vertex

-
-
-

adjacency_list_remove_vertex

-
-
-
-

基于邻接表实现图的代码如下所示。

-
-
-
-
graph_adjacency_list.java
/* 顶点类 */
-class Vertex {
-    int val;
-    public Vertex(int val) {
-        this.val = val;
-    }
-}
-
-/* 基于邻接表实现的无向图类 */
-class GraphAdjList {
-    // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象
-    Map<Vertex, Set<Vertex>> adjList; // 邻接表(使用哈希表实现)
-
-    /* 构造函数 */
-    public GraphAdjList(Vertex[][] edges) {
-        this.adjList = new HashMap<>();
-        // 添加所有顶点和边
-        for (Vertex[] edge : edges) {
-            addVertex(edge[0]);
-            addVertex(edge[1]);
-            addEdge(edge[0], edge[1]);
-        }
-    }
-
-    /* 获取顶点数量 */
-    public int size() {
-        return adjList.size();
-    }
-
-    /* 添加边 */
-    public void addEdge(Vertex vet1, Vertex vet2) {
-        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)
-            throw new IllegalArgumentException();
-        // 添加边 vet1 - vet2
-        adjList.get(vet1).add(vet2);
-        adjList.get(vet2).add(vet1);
-    }
-
-    /* 删除边 */
-    public void removeEdge(Vertex vet1, Vertex vet2) {
-        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)
-            throw new IllegalArgumentException();
-        // 删除边 vet1 - vet2
-        adjList.get(vet1).remove(vet2);
-        adjList.get(vet2).remove(vet1);
-    }
-
-    /* 添加顶点 */
-    public void addVertex(Vertex vet) {
-        if (adjList.containsKey(vet))
-            return;
-        // 在邻接表中添加一个新链表(即 HashSet)
-        adjList.put(vet, new HashSet<>());
-    }
-
-    /* 删除顶点 */
-    public void removeVertex(Vertex vet) {
-        if (!adjList.containsKey(vet))
-            throw new IllegalArgumentException();
-        // 在邻接表中删除顶点 vet 对应的链表(即 HashSet)
-        adjList.remove(vet);
-        // 遍历其它顶点的链表(即 HashSet),删除所有包含 vet 的边
-        for (Set<Vertex> set : adjList.values()) {
-            set.remove(vet);
-        }
-    }
-
-    /* 打印邻接表 */
-    public void print() {
-        System.out.println("邻接表 =");
-        for (Map.Entry<Vertex, Set<Vertex>> entry : adjList.entrySet()) {
-            List<Integer> tmp = new ArrayList<>();
-            for (Vertex vertex : entry.getValue())
-                tmp.add(vertex.val);
-            System.out.println(entry.getKey().val + ": " + tmp + ",");
-        }
-    }
-}
-
-
-
-
graph_adjacency_list.cpp

-
-
-
-
graph_adjacency_list.py

-
-
-
-
graph_adjacency_list.go
/* 顶点类 */
-type vertex struct {
-    val int
-}
-
-func newVertex(val int) vertex {
-    return vertex{
-        val: val,
-    }
-}
-
-/* 基于邻接表实现的无向图类 */
-type graphAdjList struct {
-    // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象
-    // 邻接表(使用哈希表实现), 使用哈希表模拟集合
-    adjList map[vertex]map[vertex]struct{}
-}
-
-/* 构造函数 */
-func newGraphAdjList(edges [][]vertex) *graphAdjList {
-    g := &graphAdjList{
-        adjList: make(map[vertex]map[vertex]struct{}),
-    }
-    // 添加所有顶点和边
-    for _, edge := range edges {
-        g.addVertex(edge[0])
-        g.addVertex(edge[1])
-        g.addEdge(edge[0], edge[1])
-    }
-    return g
-}
-
-/* 获取顶点数量 */
-func (g *graphAdjList) size() int {
-    return len(g.adjList)
-}
-
-/* 添加边 */
-func (g *graphAdjList) addEdge(vet1 vertex, vet2 vertex) {
-    _, ok1 := g.adjList[vet1]
-    _, ok2 := g.adjList[vet2]
-    if !ok1 || !ok2 || vet1 == vet2 {
-        panic("error")
-    }
-    // 添加边 vet1 - vet2, 添加匿名 struct{},
-    g.adjList[vet1][vet2] = struct{}{}
-    g.adjList[vet2][vet1] = struct{}{}
-}
-
-/* 删除边 */
-func (g *graphAdjList) removeEdge(vet1 vertex, vet2 vertex) {
-    _, ok1 := g.adjList[vet1]
-    _, ok2 := g.adjList[vet2]
-    if !ok1 || !ok2 || vet1 == vet2 {
-        panic("error")
-    }
-    // 删除边 vet1 - vet2, 借助 delete 来删除 map 中的键
-    delete(g.adjList[vet1], vet2)
-    delete(g.adjList[vet2], vet1)
-}
-
-/* 添加顶点 */
-func (g *graphAdjList) addVertex(vet vertex) {
-    _, ok := g.adjList[vet]
-    if ok {
-        return
-    }
-    // 在邻接表中添加一个新链表(即 set)
-    g.adjList[vet] = make(map[vertex]struct{})
-}
-
-/* 删除顶点 */
-func (g *graphAdjList) removeVertex(vet vertex) {
-    _, ok := g.adjList[vet]
-    if !ok {
-        panic("error")
-    }
-    // 在邻接表中删除顶点 vet 对应的链表
-    delete(g.adjList, vet)
-    // 遍历其它顶点的链表(即 Set),删除所有包含 vet 的边
-    for _, set := range g.adjList {
-        // 操作
-        delete(set, vet)
-    }
-}
-
-
-
-
graph_adjacency_list.js

-
-
-
-
graph_adjacency_list.ts

-
-
-
-
graph_adjacency_list.c

-
-
-
-
graph_adjacency_list.cs

-
-
-
-
graph_adjacency_list.swift
/* 顶点类 */
-class Vertex: Hashable {
-    var val: Int
-
-    init(val: Int) {
-        self.val = val
-    }
-
-    static func == (lhs: Vertex, rhs: Vertex) -> Bool {
-        lhs.val == rhs.val
-    }
-
-    func hash(into hasher: inout Hasher) {
-        hasher.combine(val)
-    }
-}
-
-/* 基于邻接表实现的无向图类 */
-class GraphAdjList {
-    // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象
-    private var adjList: [Vertex: Set<Vertex>] // 邻接表(使用哈希表实现)
-
-    init(edges: [[Vertex]]) {
-        adjList = [:]
-        // 添加所有顶点和边
-        for edge in edges {
-            addVertex(vet: edge[0])
-            addVertex(vet: edge[1])
-            addEdge(vet1: edge[0], vet2: edge[1])
-        }
-    }
-
-    /* 获取顶点数量 */
-    func size() -> Int {
-        adjList.count
-    }
-
-    /* 添加边 */
-    func addEdge(vet1: Vertex, vet2: Vertex) {
-        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {
-            fatalError("参数错误")
-        }
-        // 添加边 vet1 - vet2
-        adjList[vet1]?.insert(vet2)
-        adjList[vet2]?.insert(vet1)
-    }
-
-    /* 删除边 */
-    func removeEdge(vet1: Vertex, vet2: Vertex) {
-        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {
-            fatalError("参数错误")
-        }
-        // 删除边 vet1 - vet2
-        adjList[vet1]?.remove(vet2)
-        adjList[vet2]?.remove(vet1)
-    }
-
-    /* 添加顶点 */
-    func addVertex(vet: Vertex) {
-        if adjList[vet] != nil {
-            return
-        }
-        // 在邻接表中添加一个新链表(即 HashSet)
-        adjList[vet] = []
-    }
-
-    /* 删除顶点 */
-    func removeVertex(vet: Vertex) {
-        if adjList[vet] == nil {
-            fatalError("参数错误")
-        }
-        // 在邻接表中删除顶点 vet 对应的链表(即 HashSet)
-        adjList.removeValue(forKey: vet)
-        // 遍历其它顶点的链表(即 HashSet),删除所有包含 vet 的边
-        for key in adjList.keys {
-            adjList[key]?.remove(vet)
-        }
-    }
-}
-
-
-
-
graph_adjacency_list.zig

-
-
-
-
-

9.2.3. 效率对比

-

设图中共有 \(n\) 个顶点和 \(m\) 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
邻接矩阵邻接表(链表)邻接表(哈希表)
判断是否邻接\(O(1)\)\(O(m)\)\(O(1)\)
添加边\(O(1)\)\(O(1)\)\(O(1)\)
删除边\(O(1)\)\(O(m)\)\(O(1)\)
添加顶点\(O(n)\)\(O(1)\)\(O(1)\)
删除顶点\(O(n^2)\)\(O(n + m)\)\(O(n)\)
内存空间占用\(O(n^2)\)\(O(n + m)\)\(O(n + m)\)
-
-

观察上表,貌似邻接表(哈希表)的时间与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需要一次数组访问或赋值操作即可。总结以上,邻接矩阵体现“以空间换时间”,邻接表体现“以时间换空间”

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_hashing/hash_collision.assets/hash_collision_chaining.png b/site/chapter_hashing/hash_collision.assets/hash_collision_chaining.png deleted file mode 100644 index c0f01e64b..000000000 Binary files a/site/chapter_hashing/hash_collision.assets/hash_collision_chaining.png and /dev/null differ diff --git a/site/chapter_hashing/hash_collision.assets/hash_collision_linear_probing.png b/site/chapter_hashing/hash_collision.assets/hash_collision_linear_probing.png deleted file mode 100644 index ce0467e65..000000000 Binary files a/site/chapter_hashing/hash_collision.assets/hash_collision_linear_probing.png and /dev/null differ diff --git a/site/chapter_hashing/hash_collision/index.html b/site/chapter_hashing/hash_collision/index.html deleted file mode 100644 index c6efaf915..000000000 --- a/site/chapter_hashing/hash_collision/index.html +++ /dev/null @@ -1,1846 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 6.2. 哈希冲突处理 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

6.2. 哈希冲突

-

理想情况下,哈希函数应该为每个输入产生唯一的输出,使得 key 和 value 一一对应。而实际上,往往存在向哈希函数输入不同的 key 而产生相同输出的情况,这种情况被称为「哈希冲突 Hash Collision」。哈希冲突会导致查询结果错误,从而严重影响哈希表的可用性。

-

那么,为什么会出现哈希冲突呢?本质上看,由于哈希函数的输入空间往往远大于输出空间,因此不可避免地会出现多个输入产生相同输出的情况,即为哈希冲突。比如,输入空间是全体整数,输出空间是一个固定大小的桶(数组)的索引范围,那么必定会有多个整数同时映射到一个桶索引。

-

为了缓解哈希冲突,一方面,我们可以通过「哈希表扩容」来减小冲突概率。极端情况下,当输入空间和输出空间大小相等时,哈希表就等价于数组了,可谓“大力出奇迹”。

-

另一方面,考虑通过优化数据结构以缓解哈希冲突,常见的方法有「链式地址」和「开放寻址」。

-

6.2.1. 哈希表扩容

-

「负载因子 Load Factor」定义为 哈希表中元素数量除以桶槽数量(即数组大小),代表哈希冲突的严重程度。

-

负载因子常用作哈希表扩容的触发条件。比如在 Java 中,当负载因子 \(> 0.75\) 时则触发扩容,将 HashMap 大小扩充至原先的 \(2\) 倍。

-

与数组扩容类似,哈希表扩容操作的开销很大,因为需要将所有键值对从原哈希表依次移动至新哈希表。

-

6.2.2. 链式地址

-

在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中

-

hash_collision_chaining

-

链式地址下,哈希表操作方法为:

-
    -
  • 查询元素:先将 key 输入到哈希函数得到桶内索引,即可访问链表头结点,再通过遍历链表查找对应 value 。
  • -
  • 添加元素:先通过哈希函数访问链表头部,再将结点(即键值对)添加到链表头部即可。
  • -
  • 删除元素:同样先根据哈希函数结果访问链表头部,再遍历链表查找对应结点,删除之即可。
  • -
-

链式地址虽然解决了哈希冲突问题,但仍存在局限性,包括:

-
    -
  • 占用空间变大,因为链表或二叉树包含结点指针,相比于数组更加耗费内存空间;
  • -
  • 查询效率降低,因为需要线性遍历链表来查找对应元素;
  • -
-

为了缓解时间效率问题,可以把「链表」转化为「AVL 树」或「红黑树」,将查询操作的时间复杂度优化至 \(O(\log n)\)

-

6.2.3. 开放寻址

-

「开放寻址」不引入额外数据结构,而是通过“多次探测”来解决哈希冲突。根据探测方法的不同,主要分为 线性探测、平方探测、多次哈希

-

线性探测

-

「线性探测」使用固定步长的线性查找来解决哈希冲突。

-

插入元素:如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。

-

查找元素:若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况:

-
    -
  1. 找到对应元素,返回 value 即可;
  2. -
  3. 若遇到空位,则说明查找键值对不在哈希表中;
  4. -
-

hash_collision_linear_probing

-

线性探测存在以下缺陷:

-
    -
  • 不能直接删除元素。删除元素会导致桶内出现一个空位,在查找其他元素时,该空位有可能导致程序认为元素不存在(即上述第 2. 种情况)。因此需要借助一个标志位来标记删除元素。
  • -
  • 容易产生聚集。桶内被占用的连续位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促进这一位置的“聚堆生长”,最终导致增删查改操作效率的劣化。
  • -
-

多次哈希

-

顾名思义,「多次哈希」的思路是使用多个哈希函数 \(f_1(x)\) , \(f_2(x)\) , \(f_3(x)\) , \(\cdots\) 进行探测。

-

插入元素:若哈希函数 \(f_1(x)\) 出现冲突,则尝试 \(f_2(x)\) ,以此类推……直到找到空位后插入元素。

-

查找元素:以相同的哈希函数顺序查找,存在两种情况:

-
    -
  1. 找到目标元素,则返回之;
  2. -
  3. 到空位或已尝试所有哈希函数,说明哈希表中无此元素;
  4. -
-

相比于「线性探测」,「多次哈希」方法更不容易产生聚集,代价是多个哈希函数增加了额外计算量。

-
-

工业界方案

-

Java 采用「链式地址」。在 JDK 1.8 之后,HashMap 内数组长度大于 64 时,长度大于 8 的链表会被转化为「红黑树」,以提升查找性能。

-

Python 采用「开放寻址」。字典 dict 使用伪随机数进行探测。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_hashing/hash_map.assets/hash_collision.png b/site/chapter_hashing/hash_map.assets/hash_collision.png deleted file mode 100644 index e94330064..000000000 Binary files a/site/chapter_hashing/hash_map.assets/hash_collision.png and /dev/null differ diff --git a/site/chapter_hashing/hash_map.assets/hash_function.png b/site/chapter_hashing/hash_map.assets/hash_function.png deleted file mode 100644 index b15d22515..000000000 Binary files a/site/chapter_hashing/hash_map.assets/hash_function.png and /dev/null differ diff --git a/site/chapter_hashing/hash_map.assets/hash_map.png b/site/chapter_hashing/hash_map.assets/hash_map.png deleted file mode 100644 index 5c36a2a18..000000000 Binary files a/site/chapter_hashing/hash_map.assets/hash_map.png and /dev/null differ diff --git a/site/chapter_hashing/hash_map/index.html b/site/chapter_hashing/hash_map/index.html deleted file mode 100644 index 8283a14ce..000000000 --- a/site/chapter_hashing/hash_map/index.html +++ /dev/null @@ -1,2649 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 6.1. 哈希表(HashMap) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

6.1. 哈希表

-

哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 \(O(1)\)

-

例如,给定一个包含 \(n\) 个学生的数据库,每个学生有“姓名 name ”和“学号 id ”两项数据,希望实现一个查询功能:输入一个学号,返回对应的姓名,则可以使用哈希表实现。

-

hash_map

-

Fig. 哈希表抽象表示

- -

6.1.1. 哈希表效率

-

除了哈希表之外,还可以使用以下数据结构来实现上述查询功能:

-
    -
  1. 无序数组:每个元素为 [学号, 姓名]
  2. -
  3. 有序数组:将 1. 中的数组按照学号从小到大排序;
  4. -
  5. 链表:每个结点的值为 [学号, 姓名]
  6. -
  7. 二叉搜索树:每个结点的值为 [学号, 姓名] ,根据学号大小来构建树;
  8. -
-

使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 二叉搜索树章节)。无论是查找元素、还是增删元素,哈希表的时间复杂度都是 \(O(1)\) ,全面胜出!

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
无序数组有序数组链表二叉搜索树哈希表
查找元素\(O(n)\)\(O(\log n)\)\(O(n)\)\(O(\log n)\)\(O(1)\)
插入元素\(O(1)\)\(O(n)\)\(O(1)\)\(O(\log n)\)\(O(1)\)
删除元素\(O(n)\)\(O(n)\)\(O(n)\)\(O(\log n)\)\(O(1)\)
-
-

6.1.2. 哈希表常用操作

-

哈希表的基本操作包括 初始化、查询操作、添加与删除键值对

-
-
-
-
hash_map.java
/* 初始化哈希表 */
-Map<Integer, String> map = new HashMap<>();
-
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-map.put(12836, "小哈");   
-map.put(15937, "小啰");   
-map.put(16750, "小算");   
-map.put(13276, "小法");
-map.put(10583, "小鸭");
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-String name = map.get(15937);
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-map.remove(10583);
-
-
-
-
hash_map.cpp
/* 初始化哈希表 */
-unordered_map<int, string> map;
-
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-map[12836] = "小哈";
-map[15937] = "小啰";
-map[16750] = "小算";
-map[13276] = "小法";
-map[10583] = "小鸭";
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-string name = map[15937];
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-map.erase(10583);
-
-
-
-
hash_map.py
""" 初始化哈希表 """
-mapp = {}
-
-""" 添加操作 """
-# 在哈希表中添加键值对 (key, value)
-mapp[12836] = "小哈"
-mapp[15937] = "小啰"
-mapp[16750] = "小算"
-mapp[13276] = "小法"
-mapp[10583] = "小鸭"
-
-""" 查询操作 """
-# 向哈希表输入键 key ,得到值 value
-name = mapp[15937]
-
-""" 删除操作 """
-# 在哈希表中删除键值对 (key, value)
-mapp.pop(10583)
-
-
-
-
hash_map.go
/* 初始化哈希表 */
-mapp := make(map[int]string)
-
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-mapp[12836] = "小哈"
-mapp[15937] = "小啰"
-mapp[16750] = "小算"
-mapp[13276] = "小法"
-mapp[10583] = "小鸭"
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-name := mapp[15937]
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-delete(mapp, 10583)
-
-
-
-
hash_map.js
/* 初始化哈希表 */
-const map = new ArrayHashMap();
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-map.set(12836, '小哈');
-map.set(15937, '小啰');
-map.set(16750, '小算');
-map.set(13276, '小法');
-map.set(10583, '小鸭');
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-let name = map.get(15937);
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-map.delete(10583);
-
-
-
-
hash_map.ts
/* 初始化哈希表 */
-const map = new Map<number, string>();
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-map.set(12836, '小哈');
-map.set(15937, '小啰');
-map.set(16750, '小算');
-map.set(13276, '小法');
-map.set(10583, '小鸭');
-console.info('\n添加完成后,哈希表为\nKey -> Value');
-console.info(map);
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-let name = map.get(15937);
-console.info('\n输入学号 15937 ,查询到姓名 ' + name);
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-map.delete(10583);
-console.info('\n删除 10583 后,哈希表为\nKey -> Value');
-console.info(map);
-
-
-
-
hash_map.c

-
-
-
-
hash_map.cs
/* 初始化哈希表 */
-Dictionary<int, String> map = new ();
-
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-map.Add(12836, "小哈");
-map.Add(15937, "小啰");
-map.Add(16750, "小算");
-map.Add(13276, "小法");
-map.Add(10583, "小鸭");
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-String name = map[15937];
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-map.Remove(10583);
-
-
-
-
hash_map.swift
/* 初始化哈希表 */
-var map: [Int: String] = [:]
-
-/* 添加操作 */
-// 在哈希表中添加键值对 (key, value)
-map[12836] = "小哈"
-map[15937] = "小啰"
-map[16750] = "小算"
-map[13276] = "小法"
-map[10583] = "小鸭"
-
-/* 查询操作 */
-// 向哈希表输入键 key ,得到值 value
-let name = map[15937]!
-
-/* 删除操作 */
-// 在哈希表中删除键值对 (key, value)
-map.removeValue(forKey: 10583)
-
-
-
-
hash_map.zig

-
-
-
-
-

遍历哈希表有三种方式,即 遍历键值对、遍历键、遍历值

-
-
-
-
hash_map.java
/* 遍历哈希表 */
-// 遍历键值对 key->value
-for (Map.Entry <Integer, String> kv: map.entrySet()) {
-    System.out.println(kv.getKey() + " -> " + kv.getValue());
-}
-// 单独遍历键 key
-for (int key: map.keySet()) {
-    System.out.println(key);
-}
-// 单独遍历值 value
-for (String val: map.values()) {
-    System.out.println(val);
-}
-
-
-
-
hash_map.cpp
/* 遍历哈希表 */
-// 遍历键值对 key->value
-for (auto kv: map) {
-    cout << kv.first << " -> " << kv.second << endl;
-}
-// 单独遍历键 key
-for (auto key: map) {
-    cout << key.first << endl;
-}
-// 单独遍历值 value
-for (auto val: map) {
-    cout << val.second << endl;
-}
-
-
-
-
hash_map.py
""" 遍历哈希表 """
-# 遍历键值对 key->value
-for key, value in mapp.items():
-    print(key, "->", value)
-# 单独遍历键 key
-for key in mapp.keys():
-    print(key)
-# 单独遍历值 value
-for value in mapp.values():
-    print(value)
-
-
-
-
hash_map_test.go
/* 遍历哈希表 */
-// 遍历键值对 key->value
-for key, value := range mapp {
-    fmt.Println(key, "->", value)
-}
-// 单独遍历键 key
-for key := range mapp {
-    fmt.Println(key)
-}
-// 单独遍历值 value
-for _, value := range mapp {
-    fmt.Println(value)
-}
-
-
-
-
hash_map.js
/* 遍历哈希表 */
-// 遍历键值对 key->value
-for (const entry of map.entries()) {
-    if (!entry) continue;
-    console.info(entry.key + ' -> ' + entry.val);
-}
-// 单独遍历键 key
-for (const key of map.keys()) {
-    console.info(key);
-}
-// 单独遍历值 value
-for (const val of map.values()) {
-    console.info(val);
-}
-
-
-
-
hash_map.ts
/* 遍历哈希表 */
-console.info('\n遍历键值对 Key->Value');
-for (const [k, v] of map.entries()) {
-    console.info(k + ' -> ' + v);
-}
-console.info('\n单独遍历键 Key');
-for (const k of map.keys()) {
-    console.info(k);
-}
-console.info('\n单独遍历值 Value');
-for (const v of map.values()) {
-    console.info(v);
-}
-
-
-
-
hash_map.c

-
-
-
-
hash_map.cs
/* 遍历哈希表 */
-// 遍历键值对 Key->Value
-foreach (var kv in map) {
-    Console.WriteLine(kv.Key + " -> " + kv.Value);
-}
-// 单独遍历键 key
-foreach (int key in map.Keys) {
-    Console.WriteLine(key);
-}
-// 单独遍历值 value
-foreach (String val in map.Values) {
-    Console.WriteLine(val);
-}
-
-
-
-
hash_map.swift
/* 遍历哈希表 */
-// 遍历键值对 Key->Value
-for (key, value) in map {
-    print("\(key) -> \(value)")
-}
-// 单独遍历键 Key
-for key in map.keys {
-    print(key)
-}
-// 单独遍历值 Value
-for value in map.values {
-    print(value)
-}
-
-
-
-
hash_map.zig

-
-
-
-
-

6.1.3. 哈希函数

-

哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。

-

最简单地,我们可以仅用一个「数组」来实现哈希表。首先,将所有 value 放入数组中,那么每个 value 在数组中都有唯一的「索引」。显然,访问 value 需要给定索引,而为了 建立 key 和索引之间的映射关系,我们需要使用「哈希函数 Hash Function」。

-

设数组为 bucket ,哈希函数为 f(x) ,输入键为 key 。那么获取 value 的步骤为:

-
    -
  1. 通过哈希函数计算出索引,即 index = f(key)
  2. -
  3. 通过索引在数组中获取值,即 value = bucket[index]
  4. -
-

以上述学生数据 key 学号 -> value 姓名 为例,我们可以将「哈希函数」设计为

-
\[ -f(x) = x \% 100 -\]
-

hash_function

-

Fig. 哈希函数

- -
-
-
-
array_hash_map.java
/* 键值对 int->String */
-class Entry {
-    public int key;
-    public String val;
-    public Entry(int key, String val) {
-        this.key = key;
-        this.val = val;
-    }
-}
-
-/* 基于数组简易实现的哈希表 */
-class ArrayHashMap {
-    private List<Entry> bucket;
-    public ArrayHashMap() {
-        // 初始化一个长度为 100 的桶(数组)
-        bucket = new ArrayList<>();
-        for (int i = 0; i < 100; i++) {
-            bucket.add(null);
-        }
-    }
-
-    /* 哈希函数 */
-    private int hashFunc(int key) {
-        int index = key % 100;
-        return index;
-    }
-
-    /* 查询操作 */
-    public String get(int key) {
-        int index = hashFunc(key);
-        Entry pair = bucket.get(index);
-        if (pair == null) return null;
-        return pair.val;
-    }
-
-    /* 添加操作 */
-    public void put(int key, String val) {
-        Entry pair = new Entry(key, val);
-        int index = hashFunc(key);
-        bucket.set(index, pair);
-    }
-
-    /* 删除操作 */
-    public void remove(int key) {
-        int index = hashFunc(key);
-        // 置为 null ,代表删除
-        bucket.set(index, null);
-    }
-
-    /* 获取所有键值对 */
-    public List<Entry> entrySet() {
-        List<Entry> entrySet = new ArrayList<>();
-        for (Entry pair : bucket) {
-            if (pair != null)
-                entrySet.add(pair);
-        }
-        return entrySet;
-    }
-
-    /* 获取所有键 */
-    public List<Integer> keySet() {
-        List<Integer> keySet = new ArrayList<>();
-        for (Entry pair : bucket) {
-            if (pair != null)
-                keySet.add(pair.key);
-        }
-        return keySet;
-    }
-
-    /* 获取所有值 */
-    public List<String> valueSet() {
-        List<String> valueSet = new ArrayList<>();
-        for (Entry pair : bucket) {
-            if (pair != null)
-                valueSet.add(pair.val);
-        }
-        return valueSet;
-    }
-
-    /* 打印哈希表 */
-    public void print() {
-        for (Entry kv: entrySet()) {
-            System.out.println(kv.key + " -> " + kv.val);
-        }
-    }
-}
-
-
-
-
array_hash_map.cpp
/* 键值对 int->String */
-struct Entry {
-public:
-    int key;
-    string val;
-    Entry(int key, string val) {
-        this->key = key;
-        this->val = val;
-    }
-};
-
-/* 基于数组简易实现的哈希表 */
-class ArrayHashMap {
-private:
-    vector<Entry*> bucket;
-public:
-    ArrayHashMap() {
-        // 初始化一个长度为 100 的桶(数组)
-        bucket= vector<Entry*>(100);
-    }
-
-    /* 哈希函数 */
-    int hashFunc(int key) {
-        int index = key % 100;
-        return index;
-    }
-
-    /* 查询操作 */
-    string get(int key) {
-        int index = hashFunc(key);
-        Entry* pair = bucket[index];
-        return pair->val;
-    }
-
-    /* 添加操作 */
-    void put(int key, string val) {
-        Entry* pair = new Entry(key, val);
-        int index = hashFunc(key);
-        bucket[index] = pair;
-    }
-
-    /* 删除操作 */
-    void remove(int key) {
-        int index = hashFunc(key);
-        // 置为 nullptr ,代表删除
-        bucket[index] = nullptr;
-    }
-};
-
-
-
-
array_hash_map.py
""" 键值对 int->String """
-class Entry:
-    def __init__(self, key, val):
-        self.key = key
-        self.val = val
-
-""" 基于数组简易实现的哈希表 """
-class ArrayHashMap:
-    def __init__(self):
-        # 初始化一个长度为 100 的桶(数组)
-        self.bucket = [None] * 100
-
-    """ 哈希函数 """
-    def hash_func(self, key):
-        index = key % 100
-        return index
-
-    """ 查询操作 """
-    def get(self, key):
-        index = self.hash_func(key)
-        pair = self.bucket[index]
-        if pair is None:
-            return None
-        return pair.val
-
-    """ 添加操作 """
-    def put(self, key, val):
-        pair = Entry(key, val)
-        index = self.hash_func(key)
-        self.bucket[index] = pair
-
-    """ 删除操作 """
-    def remove(self, key):
-        index = self.hash_func(key)
-        # 置为 None ,代表删除
-        self.bucket[index] = None
-
-    """ 获取所有键值对 """
-    def entry_set(self):
-        result = []
-        for pair in self.bucket:
-            if pair is not None:
-                result.append(pair)
-        return result
-
-    """ 获取所有键 """
-    def key_set(self):
-        result = []
-        for pair in self.bucket:
-            if pair is not None:
-                result.append(pair.key)
-        return result
-
-    """ 获取所有值 """
-    def value_set(self):
-        result = []
-        for pair in self.bucket:
-            if pair is not None:
-                result.append(pair.val)
-        return result
-
-    """ 打印哈希表 """
-    def print(self):
-        for pair in self.bucket:
-            if pair is not None:
-                print(pair.key, "->", pair.val)
-
-
-
-
array_hash_map.go
/* 键值对 int->String */
-type entry struct {
-    key int
-    val string
-}
-
-/* 基于数组简易实现的哈希表 */
-type arrayHashMap struct {
-    bucket []*entry
-}
-
-func newArrayHashMap() *arrayHashMap {
-    // 初始化一个长度为 100 的桶(数组)
-    bucket := make([]*entry, 100)
-    return &arrayHashMap{bucket: bucket}
-}
-
-/* 哈希函数 */
-func (a *arrayHashMap) hashFunc(key int) int {
-    index := key % 100
-    return index
-}
-
-/* 查询操作 */
-func (a *arrayHashMap) get(key int) string {
-    index := a.hashFunc(key)
-    pair := a.bucket[index]
-    if pair == nil {
-        return "Not Found"
-    }
-    return pair.val
-}
-
-/* 添加操作 */
-func (a *arrayHashMap) put(key int, val string) {
-    pair := &entry{key: key, val: val}
-    index := a.hashFunc(key)
-    a.bucket[index] = pair
-}
-
-/* 删除操作 */
-func (a *arrayHashMap) remove(key int) {
-    index := a.hashFunc(key)
-    // 置为 nil ,代表删除
-    a.bucket[index] = nil
-}
-
-
-
-
array_hash_map.js
/* 键值对 Number -> String */
-class Entry {
-    constructor(key, val) {
-        this.key = key;
-        this.val = val;
-    }
-}
-
-/* 基于数组简易实现的哈希表 */
-class ArrayHashMap {
-    #bucket;
-    constructor() {
-        // 初始化一个长度为 100 的桶(数组)
-        this.#bucket = new Array(100).fill(null);
-    }
-
-    /* 哈希函数 */
-    #hashFunc(key) {
-        return key % 100;
-    }
-
-    /* 查询操作 */
-    get(key) {
-        let index = this.#hashFunc(key);
-        let entry = this.#bucket[index];
-        if (entry === null) return null;
-        return entry.val;
-    }
-
-    /* 添加操作 */
-    set(key, val) {
-        let index = this.#hashFunc(key);
-        this.#bucket[index] = new Entry(key, val);
-    }
-
-    /* 删除操作 */
-    delete(key) {
-        let index = this.#hashFunc(key);
-        // 置为 null ,代表删除
-        this.#bucket[index] = null;
-    }
-}
-
-
-
-
array_hash_map.ts
/* 键值对 Number -> String */
-class Entry {
-public key: number;
-public val: string;
-
-    constructor(key: number, val: string) {
-        this.key = key;
-        this.val = val;
-    }
-}
-
-/* 基于数组简易实现的哈希表 */
-class ArrayHashMap {
-
-    private readonly bucket: (Entry | null)[];
-
-    constructor() {
-        // 初始化一个长度为 100 的桶(数组)
-        this.bucket = (new Array(100)).fill(null);
-    }
-
-    /* 哈希函数 */
-    private hashFunc(key: number): number {
-        return key % 100;
-    }
-
-    /* 查询操作 */
-    public get(key: number): string | null {
-        let index = this.hashFunc(key);
-        let entry = this.bucket[index];
-        if (entry === null) return null;
-        return entry.val;
-    }
-
-    /* 添加操作 */
-    public set(key: number, val: string) {
-        let index = this.hashFunc(key);
-        this.bucket[index] = new Entry(key, val);
-    }
-
-    /* 删除操作 */
-    public delete(key: number) {
-        let index = this.hashFunc(key);
-        // 置为 null ,代表删除
-        this.bucket[index] = null;
-    }
-}
-
-
-
-
array_hash_map.c

-
-
-
-
array_hash_map.cs
/* 键值对 int->String */
-class Entry
-{
-    public int key;
-    public String val;
-    public Entry(int key, String val)
-    {
-        this.key = key;
-        this.val = val;
-    }
-}
-
-/* 基于数组简易实现的哈希表 */
-class ArrayHashMap
-{
-    private List<Entry?> bucket;
-    public ArrayHashMap()
-    {
-        // 初始化一个长度为 100 的桶(数组)
-        bucket = new ();
-        for (int i = 0; i < 100; i++)
-        {
-            bucket.Add(null);
-        }
-    }
-    /* 哈希函数 */
-    private int hashFunc(int key)
-    {
-        int index = key % 100;
-        return index;
-    }
-    /* 查询操作 */
-    public String? get(int key)
-    {
-        int index = hashFunc(key);
-        Entry? pair = bucket[index];
-        if (pair == null) return null;
-        return pair.val;
-    }
-    /* 添加操作 */
-    public void put(int key, String val)
-    {
-        Entry pair = new Entry(key, val);
-        int index = hashFunc(key);
-        bucket[index]=pair;
-    }
-    /* 删除操作 */
-    public void remove(int key)
-    {
-        int index = hashFunc(key);
-        // 置为 null ,代表删除
-        bucket[index]=null;
-    }
-}
-
-
-
-
array_hash_map.swift
/* 键值对 int->String */
-class Entry {
-    var key: Int
-    var val: String
-
-    init(key: Int, val: String) {
-        self.key = key
-        self.val = val
-    }
-}
-
-/* 基于数组简易实现的哈希表 */
-class ArrayHashMap {
-    private var bucket: [Entry?] = []
-
-    init() {
-        // 初始化一个长度为 100 的桶(数组)
-        for _ in 0 ..< 100 {
-            bucket.append(nil)
-        }
-    }
-
-    /* 哈希函数 */
-    private func hashFunc(key: Int) -> Int {
-        let index = key % 100
-        return index
-    }
-
-    /* 查询操作 */
-    func get(key: Int) -> String? {
-        let index = hashFunc(key: key)
-        let pair = bucket[index]
-        return pair?.val
-    }
-
-    /* 添加操作 */
-    func put(key: Int, val: String) {
-        let pair = Entry(key: key, val: val)
-        let index = hashFunc(key: key)
-        bucket[index] = pair
-    }
-
-    /* 删除操作 */
-    func remove(key: Int) {
-        let index = hashFunc(key: key)
-        // 置为 nil ,代表删除
-        bucket[index] = nil
-    }
-}
-
-
-
-
array_hash_map.zig

-
-
-
-
-

6.1.4. 哈希冲突

-

细心的同学可能会发现,哈希函数 \(f(x) = x \% 100\) 会在某些情况下失效。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 \(12836\)\(20336\) ,则有

-
\[ -f(12836) = f(20336) = 36 -\]
-

两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论。

-

hash_collision

-

Fig. 哈希冲突

- -

综上所述,一个优秀的「哈希函数」应该具备以下特性:

-
    -
  • 尽量少地发生哈希冲突;
  • -
  • 时间复杂度 \(O(1)\) ,计算尽可能高效;
  • -
  • 空间使用率高,即“键值对占用空间 / 哈希表总占用空间”尽可能大;
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_hashing/summary/index.html b/site/chapter_hashing/summary/index.html deleted file mode 100644 index 95af35d0a..000000000 --- a/site/chapter_hashing/summary/index.html +++ /dev/null @@ -1,1679 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 6.3. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

6.3. 小结

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_heap/heap.assets/heap_poll_step1.png b/site/chapter_heap/heap.assets/heap_poll_step1.png deleted file mode 100644 index f1a8d9050..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step1.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step10.png b/site/chapter_heap/heap.assets/heap_poll_step10.png deleted file mode 100644 index 027c7e187..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step10.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step2.png b/site/chapter_heap/heap.assets/heap_poll_step2.png deleted file mode 100644 index abef7ca8c..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step2.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step3.png b/site/chapter_heap/heap.assets/heap_poll_step3.png deleted file mode 100644 index 4a2db29a5..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step3.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step4.png b/site/chapter_heap/heap.assets/heap_poll_step4.png deleted file mode 100644 index 64c4fd425..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step4.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step5.png b/site/chapter_heap/heap.assets/heap_poll_step5.png deleted file mode 100644 index 513d6fe8b..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step5.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step6.png b/site/chapter_heap/heap.assets/heap_poll_step6.png deleted file mode 100644 index 72f3ffac3..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step6.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step7.png b/site/chapter_heap/heap.assets/heap_poll_step7.png deleted file mode 100644 index 85263d5a4..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step7.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step8.png b/site/chapter_heap/heap.assets/heap_poll_step8.png deleted file mode 100644 index 9231963db..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step8.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_poll_step9.png b/site/chapter_heap/heap.assets/heap_poll_step9.png deleted file mode 100644 index 34b578f22..000000000 Binary files a/site/chapter_heap/heap.assets/heap_poll_step9.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_push_step1.png b/site/chapter_heap/heap.assets/heap_push_step1.png deleted file mode 100644 index 00489b07c..000000000 Binary files a/site/chapter_heap/heap.assets/heap_push_step1.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_push_step2.png b/site/chapter_heap/heap.assets/heap_push_step2.png deleted file mode 100644 index a38eef32b..000000000 Binary files a/site/chapter_heap/heap.assets/heap_push_step2.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_push_step3.png b/site/chapter_heap/heap.assets/heap_push_step3.png deleted file mode 100644 index 014912070..000000000 Binary files a/site/chapter_heap/heap.assets/heap_push_step3.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_push_step4.png b/site/chapter_heap/heap.assets/heap_push_step4.png deleted file mode 100644 index 4379a31c3..000000000 Binary files a/site/chapter_heap/heap.assets/heap_push_step4.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_push_step5.png b/site/chapter_heap/heap.assets/heap_push_step5.png deleted file mode 100644 index e810842ec..000000000 Binary files a/site/chapter_heap/heap.assets/heap_push_step5.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heap_push_step6.png b/site/chapter_heap/heap.assets/heap_push_step6.png deleted file mode 100644 index 66ea466a5..000000000 Binary files a/site/chapter_heap/heap.assets/heap_push_step6.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/heapify_count.png b/site/chapter_heap/heap.assets/heapify_count.png deleted file mode 100644 index 944aab543..000000000 Binary files a/site/chapter_heap/heap.assets/heapify_count.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/min_heap_and_max_heap.png b/site/chapter_heap/heap.assets/min_heap_and_max_heap.png deleted file mode 100644 index fe1720d94..000000000 Binary files a/site/chapter_heap/heap.assets/min_heap_and_max_heap.png and /dev/null differ diff --git a/site/chapter_heap/heap.assets/representation_of_heap.png b/site/chapter_heap/heap.assets/representation_of_heap.png deleted file mode 100644 index 5a9cac200..000000000 Binary files a/site/chapter_heap/heap.assets/representation_of_heap.png and /dev/null differ diff --git a/site/chapter_heap/heap/index.html b/site/chapter_heap/heap/index.html deleted file mode 100644 index b47090d67..000000000 --- a/site/chapter_heap/heap/index.html +++ /dev/null @@ -1,2777 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 8.1. 堆(Heap) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

8.1. 堆

-

「堆 Heap」是一棵限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型:

-
    -
  • 「大顶堆 Max Heap」,任意结点的值 \(\geq\) 其子结点的值;
  • -
  • 「小顶堆 Min Heap」,任意结点的值 \(\leq\) 其子结点的值;
  • -
-

min_heap_and_max_heap

-

8.1.1. 堆术语与性质

-
    -
  • 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。
  • -
  • 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。
  • -
  • 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。
  • -
-

8.1.2. 堆常用操作

-

值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,定义为具有出队优先级的队列

-

而恰好,堆的定义与优先队列的操作逻辑完全吻合,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构。因此,本文与代码对两者不做特别区分,统一使用「堆」来命名。

-

堆的常用操作见下表(方法命名以 Java 为例)。

-

Table. 堆的常用操作

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
方法描述时间复杂度
add()元素入堆\(O(\log n)\)
poll()堆顶元素出堆\(O(\log n)\)
peek()访问堆顶元素(大 / 小顶堆分别为最大 / 小值)\(O(1)\)
size()获取堆的元素数量\(O(1)\)
isEmpty()判断堆是否为空\(O(1)\)
-
-

我们可以直接使用编程语言提供的堆类(或优先队列类)。

-
-

Tip

-

类似于排序中“从小到大排列”和“从大到小排列”,“大顶堆”和“小顶堆”可仅通过修改 Comparator 来互相转换。

-
-
-
-
-
heap.java
/* 初始化堆 */
-// 初始化小顶堆
-Queue<Integer> minHeap = new PriorityQueue<>();
-// 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可)
-Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> { return b - a; });
-
-/* 元素入堆 */
-maxHeap.add(1);
-maxHeap.add(3);
-maxHeap.add(2);
-maxHeap.add(5);
-maxHeap.add(4);
-
-/* 获取堆顶元素 */
-int peek = maxHeap.peek(); // 5
-
-/* 堆顶元素出堆 */
-// 出堆元素会形成一个从大到小的序列
-peek = heap.poll();  // 5
-peek = heap.poll();  // 4
-peek = heap.poll();  // 3
-peek = heap.poll();  // 2
-peek = heap.poll();  // 1
-
-/* 获取堆大小 */
-int size = maxHeap.size();
-
-/* 判断堆是否为空 */
-boolean isEmpty = maxHeap.isEmpty();
-
-/* 输入列表并建堆 */
-minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));
-
-
-
-
heap.cpp
/* 初始化堆 */
-// 初始化小顶堆
-priority_queue<int, vector<int>, greater<int>> minHeap;
-// 初始化大顶堆
-priority_queue<int, vector<int>, less<int>> maxHeap;
-
-/* 元素入堆 */
-maxHeap.push(1);
-maxHeap.push(3);
-maxHeap.push(2);
-maxHeap.push(5);
-maxHeap.push(4);
-
-/* 获取堆顶元素 */
-int peek = maxHeap.top(); // 5
-
-/* 堆顶元素出堆 */
-// 出堆元素会形成一个从大到小的序列
-maxHeap.pop(); // 5
-maxHeap.pop(); // 4
-maxHeap.pop(); // 3
-maxHeap.pop(); // 2
-maxHeap.pop(); // 1
-
-/* 获取堆大小 */
-int size = maxHeap.size();
-
-/* 判断堆是否为空 */
-bool isEmpty = maxHeap.empty();
-
-/* 输入列表并建堆 */
-vector<int> input{1, 3, 2, 5, 4};
-priority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());
-
-
-
-
heap.py

-
-
-
-
heap.go
// Go 语言中可以通过实现 heap.Interface 来构建整数大顶堆
-// 实现 heap.Interface 需要同时实现 sort.Interface
-type intHeap []any
-
-// Push heap.Interface 的方法,实现推入元素到堆
-func (h *intHeap) Push(x any) {
-    // Push 和 Pop 使用 pointer receiver 作为参数
-    // 因为它们不仅会对切片的内容进行调整,还会修改切片的长度。
-    *h = append(*h, x.(int))
-}
-
-// Pop heap.Interface 的方法,实现弹出堆顶元素
-func (h *intHeap) Pop() any {
-    // 待出堆元素存放在最后
-    last := (*h)[len(*h)-1]
-    *h = (*h)[:len(*h)-1]
-    return last
-}
-
-// Len sort.Interface 的方法
-func (h *intHeap) Len() int {
-    return len(*h)
-}
-
-// Less sort.Interface 的方法
-func (h *intHeap) Less(i, j int) bool {
-    // 如果实现小顶堆,则需要调整为小于号
-    return (*h)[i].(int) > (*h)[j].(int)
-}
-
-// Swap sort.Interface 的方法
-func (h *intHeap) Swap(i, j int) {
-    (*h)[i], (*h)[j] = (*h)[j], (*h)[i]
-}
-
-// Top 获取堆顶元素
-func (h *intHeap) Top() any {
-    return (*h)[0]
-}
-
-/* Driver Code */
-func TestHeap(t *testing.T) {
-    /* 初始化堆 */
-    // 初始化大顶堆
-    maxHeap := &intHeap{}
-    heap.Init(maxHeap)
-    /* 元素入堆 */
-    // 调用 heap.Interface 的方法,来添加元素
-    heap.Push(maxHeap, 1)
-    heap.Push(maxHeap, 3)
-    heap.Push(maxHeap, 2)
-    heap.Push(maxHeap, 4)
-    heap.Push(maxHeap, 5)
-
-    /* 获取堆顶元素 */
-    top := maxHeap.Top()
-    fmt.Printf("堆顶元素为 %d\n", top)
-
-    /* 堆顶元素出堆 */
-    // 调用 heap.Interface 的方法,来移除元素
-    heap.Pop(maxHeap)
-    heap.Pop(maxHeap)
-    heap.Pop(maxHeap)
-    heap.Pop(maxHeap)
-    heap.Pop(maxHeap)
-
-    /* 获取堆大小 */
-    size := len(*maxHeap)
-    fmt.Printf("堆元素数量为 %d\n", size)
-
-    /* 判断堆是否为空 */
-    isEmpty := len(*maxHeap) == 0
-    fmt.Printf("堆是否为空 %t\n", isEmpty)
-}
-
-
-
-
heap.js

-
-
-
-
heap.ts

-
-
-
-
heap.c

-
-
-
-
heap.cs

-
-
-
-
heap.swift
// Swift 未提供内置 heap 类
-
-
-
-
heap.zig

-
-
-
-
-

8.1.3. 堆的实现

-

下文实现的是「大顶堆」,若想转换为「小顶堆」,将所有大小逻辑判断取逆(例如将 \(\geq\) 替换为 \(\leq\) )即可,有兴趣的同学可自行实现。

-

堆的存储与表示

-

在二叉树章节我们学过,「完全二叉树」非常适合使用「数组」来表示,而堆恰好是一棵完全二叉树,因而我们采用「数组」来存储「堆」

-

二叉树指针。使用数组表示二叉树时,元素代表结点值,索引代表结点在二叉树中的位置,而结点指针通过索引映射公式来实现

-

具体地,给定索引 \(i\) ,那么其左子结点索引为 \(2i + 1\) 、右子结点索引为 \(2i + 2\) 、父结点索引为 \((i - 1) / 2\) (向下整除)。当索引越界时,代表空结点或结点不存在。

-

representation_of_heap

-

我们将索引映射公式封装成函数,以便后续使用。

-
-
-
-
my_heap.java
/* 获取左子结点索引 */
-int left(int i) {
-    return 2 * i + 1;
-}
-
-/* 获取右子结点索引 */
-int right(int i) {
-    return 2 * i + 2;
-}
-
-/* 获取父结点索引 */
-int parent(int i) {
-    return (i - 1) / 2; // 向下整除
-}
-
-
-
-
my_heap.cpp
// 使用动态数组,这样无需考虑扩容问题
-vector<int> maxHeap;
-
-/* 获取左子结点索引 */
-int left(int i) {
-    return 2 * i + 1;
-}
-
-/* 获取右子结点索引 */
-int right(int i) {
-    return 2 * i + 2;
-} 
-
-/* 获取父结点索引 */
-int parent(int i) {
-    return (i - 1) / 2; // 向下取整
-}
-
-
-
-
my_heap.py

-
-
-
-
my_heap.go
type maxHeap struct {
-    // 使用切片而非数组,这样无需考虑扩容问题
-    data []any
-}
-
-/* 构造函数,建立空堆 */
-func newHeap() *maxHeap {
-    return &maxHeap{
-        data: make([]any, 0),
-    }
-}
-
-/* 获取左子结点索引 */
-func (h *maxHeap) left(i int) int {
-    return 2*i + 1
-}
-
-/* 获取右子结点索引 */
-func (h *maxHeap) right(i int) int {
-    return 2*i + 2
-}
-
-/* 获取父结点索引 */
-func (h *maxHeap) parent(i int) int {
-    // 向下整除
-    return (i - 1) / 2
-}
-
-
-
-
my_heap.js

-
-
-
-
my_heap.ts

-
-
-
-
my_heap.c

-
-
-
-
my_heap.cs

-
-
-
-
my_heap.swift
var maxHeap: [Int]
-
-/* 构造函数,建立空堆 */
-init() {
-    maxHeap = []
-}
-
-/* 获取左子结点索引 */
-func left(i: Int) -> Int {
-    2 * i + 1
-}
-
-/* 获取右子结点索引 */
-func right(i: Int) -> Int {
-    2 * i + 2
-}
-
-/* 获取父结点索引 */
-func parent(i: Int) -> Int {
-    (i - 1) / 2 // 向下整除
-}
-
-
-
-
my_heap.zig

-
-
-
-
-

访问堆顶元素

-

堆顶元素是二叉树的根结点,即列表首元素。

-
-
-
-
my_heap.java
/* 访问堆顶元素 */
-int peek() {
-    return maxHeap.get(0);
-}
-
-
-
-
my_heap.cpp
/* 访问堆顶元素 */
-int peek() {
-    return maxHeap[0];
-}
-
-
-
-
my_heap.py

-
-
-
-
my_heap.go
/* 访问堆顶元素 */
-func (h *maxHeap) peek() any {
-    return h.data[0]
-}
-
-
-
-
my_heap.js

-
-
-
-
my_heap.ts

-
-
-
-
my_heap.c

-
-
-
-
my_heap.cs

-
-
-
-
my_heap.swift
/* 访问堆顶元素 */
-func peek() -> Int {
-    maxHeap[0]
-}
-
-
-
-
my_heap.zig

-
-
-
-
-

元素入堆

-

给定元素 val ,我们先将其添加到堆底。添加后,由于 val 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,因此需要修复从插入结点到根结点这条路径上的各个结点,该操作被称为「堆化 Heapify」。

-

考虑从入堆结点开始,从底至顶执行堆化。具体地,比较插入结点与其父结点的值,若插入结点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个结点;直至越过根结点时结束,或当遇到无需交换的结点时提前结束。

-
-
-
-

heap_push_step1

-
-
-

heap_push_step2

-
-
-

heap_push_step3

-
-
-

heap_push_step4

-
-
-

heap_push_step5

-
-
-

heap_push_step6

-
-
-
-

设结点总数为 \(n\) ,则树的高度为 \(O(\log n)\) ,易得堆化操作的循环轮数最多为 \(O(\log n)\)因而元素入堆操作的时间复杂度为 \(O(\log n)\)

-
-
-
-
my_heap.java
/* 元素入堆 */
-void push(int val) {
-    // 添加结点
-    maxHeap.add(val);
-    // 从底至顶堆化
-    siftUp(size() - 1);
-}
-
-/* 从结点 i 开始,从底至顶堆化 */
-void siftUp(int i) {
-    while (true) {
-        // 获取结点 i 的父结点
-        int p = parent(i);
-        // 当“越过根结点”或“结点无需修复”时,结束堆化
-        if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))
-            break;
-        // 交换两结点
-        swap(i, p);
-        // 循环向上堆化
-        i = p;
-    }
-}
-
-
-
-
my_heap.cpp
/* 元素入堆 */
-void push(int val) {
-    // 添加结点
-    maxHeap.push_back(val);
-    // 从底至顶堆化
-    shifUp(size() - 1);
-}
-
-/* 从结点 i 开始,从底至顶堆化 */
-void shifUp(int i) {
-    while (true) {
-        // 获取结点 i 的父结点
-        int p =  parent(i);
-        // 当“越过根结点”或“结点无需修复”时,结束堆化
-        if (p < 0 || maxHeap[i] <= maxHeap[p])
-            break;
-        // 交换两结点
-        swap(maxHeap[i], maxHeap[p]);
-        // 循环向上堆化
-        i = p;
-    }
-}
-
-
-
-
my_heap.py

-
-
-
-
my_heap.go
/* 元素入堆 */
-func (h *maxHeap) push(val any) {
-    // 添加结点
-    h.data = append(h.data, val)
-    // 从底至顶堆化
-    h.siftUp(len(h.data) - 1)
-}
-
-/* 从结点 i 开始,从底至顶堆化 */
-func (h *maxHeap) siftUp(i int) {
-    for true {
-        // 获取结点 i 的父结点
-        p := h.parent(i)
-        // 当“越过根结点”或“结点无需修复”时,结束堆化
-        if p < 0 || h.data[i].(int) <= h.data[p].(int) {
-            break
-        }
-        // 交换两结点
-        h.swap(i, p)
-        // 循环向上堆化
-        i = p
-    }
-}
-
-
-
-
my_heap.js

-
-
-
-
my_heap.ts

-
-
-
-
my_heap.c

-
-
-
-
my_heap.cs

-
-
-
-
my_heap.swift
/* 元素入堆 */
-func push(val: Int) {
-    // 添加结点
-    maxHeap.append(val)
-    // 从底至顶堆化
-    siftUp(i: size() - 1)
-}
-
-/* 从结点 i 开始,从底至顶堆化 */
-func siftUp(i: Int) {
-    var i = i
-    while true {
-        // 获取结点 i 的父结点
-        let p = parent(i: i)
-        // 当“越过根结点”或“结点无需修复”时,结束堆化
-        if p < 0 || maxHeap[i] <= maxHeap[p] {
-            break
-        }
-        // 交换两结点
-        swap(i: i, j: p)
-        // 循环向上堆化
-        i = p
-    }
-}
-
-
-
-
my_heap.zig

-
-
-
-
-

堆顶元素出堆

-

堆顶元素是二叉树根结点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有结点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤:

-
    -
  1. 交换堆顶元素与堆底元素(即交换根结点与最右叶结点);
  2. -
  3. 交换完成后,将堆底从列表中删除(注意,因为已经交换,实际上删除的是原来的堆顶元素);
  4. -
  5. 从根结点开始,从顶至底执行堆化
  6. -
-

顾名思义,从顶至底堆化的操作方向与从底至顶堆化相反,我们比较根结点的值与其两个子结点的值,将最大的子结点与根结点执行交换,并循环以上操作,直到越过叶结点时结束,或当遇到无需交换的结点时提前结束。

-
-
-
-

heap_poll_step1

-
-
-

heap_poll_step2

-
-
-

heap_poll_step3

-
-
-

heap_poll_step4

-
-
-

heap_poll_step5

-
-
-

heap_poll_step6

-
-
-

heap_poll_step7

-
-
-

heap_poll_step8

-
-
-

heap_poll_step9

-
-
-

heap_poll_step10

-
-
-
-

与元素入堆操作类似,堆顶元素出堆操作的时间复杂度为 \(O(\log n)\)

-
-
-
-
my_heap.java
/* 元素出堆 */
-int poll() {
-    // 判空处理
-    if (isEmpty())
-        throw new EmptyStackException();
-    // 交换根结点与最右叶结点(即交换首元素与尾元素)
-    swap(0, size() - 1);
-    // 删除结点
-    int val = maxHeap.remove(size() - 1);
-    // 从顶至底堆化
-    siftDown(0);
-    // 返回堆顶元素
-    return val;
-}
-
-/* 从结点 i 开始,从顶至底堆化 */
-void siftDown(int i) {
-    while (true) {
-        // 判断结点 i, l, r 中值最大的结点,记为 ma
-        int l = left(i), r = right(i), ma = i;
-        if (l < size() && maxHeap.get(l) > maxHeap.get(ma))
-            ma = l;
-        if (r < size() && maxHeap.get(r) > maxHeap.get(ma))
-            ma = r;
-        // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出
-        if (ma == i) break;
-        // 交换两结点
-        swap(i, ma);
-        // 循环向下堆化
-        i = ma;
-    }
-}
-
-
-
-
my_heap.cpp
/* 从结点 i 开始,从顶至底堆化 */
-void shifDown(int i) {
-    while (true) {
-        // 判断结点 i, l, r 中值最大的结点,记为 ma
-        int l = left(i), r = right(i), ma = i;
-        // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出
-        if (l < size() && maxHeap[l] > maxHeap[ma]) 
-            ma = l;
-        if (r < size() && maxHeap[r] > maxHeap[ma])  
-            ma = r;
-        // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出
-        if (ma == i) 
-            break;
-        swap(maxHeap[i], maxHeap[ma]);
-        // 循环向下堆化
-        i = ma;
-    }
-}
-
-/* 元素出堆 */
-void poll() {
-    // 判空处理
-    if (empty()) {
-        cout << "Error:堆为空" << endl;
-        return;
-    }
-    // 交换根结点与最右叶结点(即交换首元素与尾元素)
-    swap(maxHeap[0], maxHeap[size() - 1]);
-    // 删除结点
-    maxHeap.pop_back();
-    // 从顶至底堆化
-    shifDown(0);
-}
-
-
-
-
my_heap.py

-
-
-
-
my_heap.go
/* 元素出堆 */
-func (h *maxHeap) poll() any {
-    // 判空处理
-    if h.isEmpty() {
-        fmt.Println("error")
-        return nil
-    }
-    // 交换根结点与最右叶结点(即交换首元素与尾元素)
-    h.swap(0, h.size()-1)
-    // 删除结点
-    val := h.data[len(h.data)-1]
-    h.data = h.data[:len(h.data)-1]
-    // 从顶至底堆化
-    h.siftDown(0)
-
-    // 返回堆顶元素
-    return val
-}
-
-/* 从结点 i 开始,从顶至底堆化 */
-func (h *maxHeap) siftDown(i int) {
-    for true {
-        // 判断结点 i, l, r 中值最大的结点,记为 max
-        l, r, max := h.left(i), h.right(i), i
-        if l < h.size() && h.data[l].(int) > h.data[max].(int) {
-            max = l
-        }
-        if r < h.size() && h.data[r].(int) > h.data[max].(int) {
-            max = r
-        }
-        // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出
-        if max == i {
-            break
-        }
-        // 交换两结点
-        h.swap(i, max)
-        // 循环向下堆化
-        i = max
-    }
-}
-
-
-
-
my_heap.js

-
-
-
-
my_heap.ts

-
-
-
-
my_heap.c

-
-
-
-
my_heap.cs

-
-
-
-
my_heap.swift
/* 元素出堆 */
-func poll() -> Int {
-    // 判空处理
-    if isEmpty() {
-        fatalError("堆为空")
-    }
-    // 交换根结点与最右叶结点(即交换首元素与尾元素)
-    swap(i: 0, j: size() - 1)
-    // 删除结点
-    let val = maxHeap.remove(at: size() - 1)
-    // 从顶至底堆化
-    siftDown(i: 0)
-    // 返回堆顶元素
-    return val
-}
-
-/* 从结点 i 开始,从顶至底堆化 */
-func siftDown(i: Int) {
-    var i = i
-    while true {
-        // 判断结点 i, l, r 中值最大的结点,记为 ma
-        let l = left(i: i)
-        let r = right(i: i)
-        var ma = i
-        if l < size(), maxHeap[l] > maxHeap[ma] {
-            ma = l
-        }
-        if r < size(), maxHeap[r] > maxHeap[ma] {
-            ma = r
-        }
-        // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出
-        if ma == i {
-            break
-        }
-        // 交换两结点
-        swap(i: i, j: ma)
-        // 循环向下堆化
-        i = ma
-    }
-}
-
-
-
-
my_heap.zig

-
-
-
-
-

输入数据并建堆 *

-

如果我们想要直接输入一个列表并将其建堆,那么该怎么做呢?最直接地,考虑使用「元素入堆」方法,将列表元素依次入堆。元素入堆的时间复杂度为 \(O(n)\) ,而平均长度为 \(\frac{n}{2}\) ,因此该方法的总体时间复杂度为 \(O(n \log n)\)

-

然而,存在一种更加优雅的建堆方法。设结点数量为 \(n\) ,我们先将列表所有元素原封不动添加进堆,然后迭代地对各个结点执行「从顶至底堆化」。当然,无需对叶结点执行堆化,因为其没有子结点。

-
-
-
-
my_heap.java
/* 构造函数,根据输入列表建堆 */
-MaxHeap(List<Integer> nums) {
-    // 将列表元素原封不动添加进堆
-    maxHeap = new ArrayList<>(nums);
-    // 堆化除叶结点以外的其他所有结点
-    for (int i = parent(size() - 1); i >= 0; i--) {
-        siftDown(i);
-    }
-}
-
-
-
-
my_heap.cpp
/* 构造函数,根据输入列表建堆 */
-MaxHeap(vector<int> nums) {
-    // 将列表元素原封不动添加进堆
-    maxHeap = nums;
-    // 堆化除叶结点以外的其他所有结点
-    for (int i = parent(size() - 1); i >= 0; i--) {
-        shifDown(i);
-    }
-}
-
-
-
-
my_heap.py

-
-
-
-
my_heap.go
/* 构造函数,根据切片建堆 */
-func newMaxHeap(nums []any) *maxHeap {
-    // 将列表元素原封不动添加进堆
-    h := &maxHeap{data: nums}
-    // 堆化除叶结点以外的其他所有结点
-    for i := len(h.data) - 1; i >= 0; i-- {
-        h.siftDown(i)
-    }
-    return h
-}
-
-
-
-
my_heap.js

-
-
-
-
my_heap.ts

-
-
-
-
my_heap.c

-
-
-
-
my_heap.cs

-
-
-
-
my_heap.swift
/* 构造函数,根据输入列表建堆 */
-init(nums: [Int]) {
-    // 将列表元素原封不动添加进堆
-    maxHeap = nums
-    // 堆化除叶结点以外的其他所有结点
-    for i in stride(from: parent(i: size() - 1), through: 0, by: -1) {
-        siftDown(i: i)
-    }
-}
-
-
-
-
my_heap.zig

-
-
-
-
-

那么,第二种建堆方法的时间复杂度时多少呢?我们来做一下简单推算。

-
    -
  • 完全二叉树中,设结点总数为 \(n\) ,则叶结点数量为 \((n + 1) / 2\) ,其中 \(/\) 为向下整除。因此在排除叶结点后,需要堆化结点数量为 \((n - 1)/2\) ,即为 \(O(n)\)
  • -
  • 从顶至底堆化中,每个结点最多堆化至叶结点,因此最大迭代次数为二叉树高度 \(O(\log n)\)
  • -
-

将上述两者相乘,可得时间复杂度为 \(O(n \log n)\) 。然而,该估算结果仍不够准确,因为我们没有考虑到 二叉树底层结点远多于顶层结点 的性质。

-

下面我们来尝试展开计算。为了减小计算难度,我们假设树是一个「完美二叉树」,该假设不会影响计算结果的正确性。设二叉树(即堆)结点数量为 \(n\) ,树高度为 \(h\) 。上文提到,结点堆化最大迭代次数等于该结点到叶结点的距离,而这正是“结点高度”。因此,我们将各层的“结点数量 \(\times\) 结点高度”求和,即可得到所有结点的堆化的迭代次数总和。

-
\[ -T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1 -\]
-

heapify_count

-

化简上式需要借助中学的数列知识,先对 \(T(h)\) 乘以 \(2\) ,易得

-
\[ -\begin{aligned} -T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{h-1}\times1 \newline -2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \cdots + 2^{h}\times1 \newline -\end{aligned} -\]
-

使用错位相减法,令下式 \(2 T(h)\) 减去上式 \(T(h)\) ,可得

-
\[ -2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \cdots + 2^{h-1} + 2^h -\]
-

观察上式,\(T(h)\) 是一个等比数列,可直接使用求和公式,得到时间复杂度为

-
\[ -\begin{aligned} -T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline -& = 2^{h+1} - h \newline -& = O(2^h) -\end{aligned} -\]
-

进一步地,高度为 \(h\) 的完美二叉树的结点数量为 \(n = 2^{h+1} - 1\) ,易得复杂度为 \(O(2^h) = O(n)\)。以上推算表明,输入列表并建堆的时间复杂度为 \(O(n)\) ,非常高效

-

8.1.4. 堆常见应用

-
    -
  • 优先队列。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 \(O(\log n)\) ,建队操作为 \(O(n)\) ,皆非常高效。
  • -
  • 堆排序。给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序的序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并减小堆的长度即可。
  • -
  • 获取最大的 \(k\) 个元素。这既是一道经典算法题目,也是一种常见应用,例如选取热度前 10 的新闻作为微博热搜,选取前 10 销量的商品等。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_1.png b/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_1.png deleted file mode 100644 index f05b05178..000000000 Binary files a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_1.png and /dev/null differ diff --git a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_2.png b/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_2.png deleted file mode 100644 index c4cc5d9d4..000000000 Binary files a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_2.png and /dev/null differ diff --git a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_3.png b/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_3.png deleted file mode 100644 index 828c62606..000000000 Binary files a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_3.png and /dev/null differ diff --git a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_4.png b/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_4.png deleted file mode 100644 index c13db8dac..000000000 Binary files a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_4.png and /dev/null differ diff --git a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_5.png b/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_5.png deleted file mode 100644 index ccd4f7ffa..000000000 Binary files a/site/chapter_introduction/algorithms_are_everywhere.assets/look_up_dictionary_step_5.png and /dev/null differ diff --git a/site/chapter_introduction/algorithms_are_everywhere/index.html b/site/chapter_introduction/algorithms_are_everywhere/index.html deleted file mode 100644 index 4fab59b5d..000000000 --- a/site/chapter_introduction/algorithms_are_everywhere/index.html +++ /dev/null @@ -1,1714 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.1. 算法无处不在 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

1.1. 算法无处不在

-

听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。

-

在正式介绍算法之前,我想告诉你一件有趣的事:其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中。接下来,我将介绍两个具体例子来佐证。

-

例一:拼积木。一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。

-

如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书」上的一系列步骤就是算法。

-

例二:查字典。在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 \(r\) 的字,一般我们会这样做:

-
    -
  1. 打开字典大致一半页数的位置,查看此页的首字母是什么(假设为 \(m\) );
  2. -
  3. 由于在英文字母表中 \(r\)\(m\) 的后面,因此应排除字典前半部分,查找范围仅剩后半部分;
  4. -
  5. 循环执行步骤 1-2 ,直到找到拼音首字母为 \(r\) 的页码时终止。
  6. -
-
-
-
-

look_up_dictionary_step_1

-
-
-

look_up_dictionary_step_2

-
-
-

look_up_dictionary_step_3

-
-
-

look_up_dictionary_step_4

-
-
-

look_up_dictionary_step_5

-
-
-
-

查字典这个小学生的标配技能,实际上就是大名鼎鼎的「二分查找」。从数据结构角度,我们可以将字典看作是一个已排序的「数组」;而从算法角度,我们可将上述查字典的一系列指令看作是「二分查找」算法。

-

小到烹饪一道菜、大到星际航行,几乎所有问题的解决都离不开算法。计算机的出现,使我们可以通过编程将数据结构存储在内存中,也可以编写代码来调用 CPU, GPU 执行算法,从而将生活中的问题搬运到计算机中,更加高效地解决各式各样的复杂问题。

-
-

Tip

-

读到这里,如果你感到对数据结构、算法、数组、二分查找等此类概念一知半解,那么就太好了!因为这正是本书存在的价值,接下来,本书将会一步步地引导你进入数据结构与算法的知识殿堂。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png b/site/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png deleted file mode 100644 index 1ab8721df..000000000 Binary files a/site/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png and /dev/null differ diff --git a/site/chapter_introduction/what_is_dsa/index.html b/site/chapter_introduction/what_is_dsa/index.html deleted file mode 100644 index a744e73a8..000000000 --- a/site/chapter_introduction/what_is_dsa/index.html +++ /dev/null @@ -1,1812 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.2. 算法是什么 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

1.2. 算法是什么

-

1.2.1. 算法定义

-

「算法 Algorithm」是在有限时间内解决特定问题的一组指令或操作步骤。算法具有以下特性:

-
    -
  • 问题是明确的,需要拥有明确的输入和输出定义。
  • -
  • 解具有确定性,即给定相同输入时,输出一定相同。
  • -
  • 具有可行性,可在有限步骤、有限时间、有限内存空间下完成。
  • -
  • 独立于编程语言,即可用多种语言实现。
  • -
-

1.2.2. 数据结构定义

-

「数据结构 Data Structure」是在计算机中组织与存储数据的方式。为了提高数据存储和操作性能,数据结构的设计原则有:

-
    -
  • 空间占用尽可能小,节省计算机内存。
  • -
  • 数据操作尽量快,包括数据访问、添加、删除、更新等。
  • -
  • 提供简洁的数据表示和逻辑信息,以便算法高效运行。
  • -
-

数据结构的设计是一个充满权衡的过程,这意味着如果获得某方面的优势,则往往需要在另一方面做出妥协。例如,链表相对于数组,数据添加删除操作更加方便,但牺牲了数据的访问速度;图相对于链表,提供了更多的逻辑信息,但需要占用更多的内存空间。

-

1.2.3. 数据结构与算法的关系

-

「数据结构」与「算法」是高度相关、紧密嵌合的,体现在:

-
    -
  • 数据结构是算法的底座。数据结构为算法提供结构化存储的数据,以及操作数据的对应方法。
  • -
  • 算法是发挥数据结构优势的舞台。数据结构仅存储数据信息,结合算法才可解决特定问题。
  • -
  • 算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。
  • -
-

relationship_between_data_structure_and_algorithm

-

Fig. 数据结构与算法的关系

- -

如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
数据结构与算法LEGO 乐高
输入数据未拼装的积木
数据结构积木组织形式,包括形状、大小、连接方式等
算法把积木拼成目标形态的一系列操作步骤
输出数据积木模型
-
-
-

约定俗成的简称

-

在实际讨论中,我们通常会将「数据结构与算法」直接简称为「算法」。例如,我们熟称的 LeetCode 算法题目,实际上同时考察了数据结构和算法两部分知识。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_preface/about_the_book.assets/mindmap.png b/site/chapter_preface/about_the_book.assets/mindmap.png deleted file mode 100644 index 131251da7..000000000 Binary files a/site/chapter_preface/about_the_book.assets/mindmap.png and /dev/null differ diff --git a/site/chapter_preface/about_the_book.assets/profile.png b/site/chapter_preface/about_the_book.assets/profile.png deleted file mode 100644 index aeabdeb91..000000000 Binary files a/site/chapter_preface/about_the_book.assets/profile.png and /dev/null differ diff --git a/site/chapter_preface/about_the_book/index.html b/site/chapter_preface/about_the_book/index.html deleted file mode 100644 index d09ae313f..000000000 --- a/site/chapter_preface/about_the_book/index.html +++ /dev/null @@ -1,2060 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - 0.1. 关于本书 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

0.1. 关于本书

-

五年前发生的一件事,成为了我职业生涯的重要转折点。当时的我在交大读研,对互联网求职一无所知,但仍然硬着头皮申请了 Microsoft 软件工程师实习。面试官让我在白板上写出“快速排序”代码,我畏畏缩缩地写了一个“冒泡排序”,并且还写错了(ToT) 。从面试官的表情上,我看到了一个大大的 "GG" 。

-

此次失利倒逼我开始刷算法题。我采用“扫雷游戏”式的学习方法,两眼一抹黑刷题,扫到不会的“雷”就通过查资料把它“排掉”,配合周期性总结,逐渐形成了数据结构与算法的知识图景。幸运地,我在秋招斩获了多家大厂的 Offer 。

-

回想自己当初在“扫雷式”刷题中被炸的满头包的痛苦,思考良久,我意识到一本“前期刷题必看”的读物可以使算法小白少走许多弯路。写作意愿滚滚袭来,那就动笔吧:

-

Hello,算法!

- -

0.1.1. 读者对象

-
-

前置条件

-

您需要至少具备任一语言的编程基础,能够阅读和编写简单代码。

-
-

如果您是 算法初学者,完全没有接触过算法,或者已经有少量刷题,对数据结构与算法有朦胧的理解,在会与不会之间反复横跳,那么这本书就是为您而写!本书能够带来:

-
    -
  • 了解刷题所需的 数据结构,包括常用操作、优势和劣势、典型应用、实现方法等。
  • -
  • 学习各类 算法,介绍算法的设计思想、运行效率、优势劣势、实现方法等。
  • -
  • 可一键运行的 配套代码,包含详细注释,帮助你通过实践加深理解。
  • -
-

如果您是 算法熟练工,已经积累一定刷题量,接触过大多数题型,那么本书内容对你来说可能稍显基础,但仍能够带来以下价值:

-
    -
  • 本书篇幅不长,可以帮助你提纲挈领地回顾算法知识。
  • -
  • 书中包含许多对比性、总结性的算法内容,可以帮助你梳理算法知识体系。
  • -
  • 源代码实现了各种经典数据结构和算法,可以作为“刷题工具库”来使用。
  • -
-

如果您是 算法大佬,请受我膜拜!希望您可以抽时间提出意见建议,或者一起参与创作,帮助各位同学获取更好的学习内容,感谢!

-

0.1.2. 内容结构

-

本书主要内容分为复杂度分析、数据结构、算法三个部分。

-

mindmap

-

Fig. 知识点思维导图

- -

复杂度分析

-

首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。

-

接下来,从 函数渐近上界 入手,分别介绍了 时间复杂度空间复杂度,包括推算方法、常见类型、示例等。同时,剖析了 最差、最佳、平均 时间复杂度的联系与区别。

-

数据结构

-

首先介绍了常用的 基本数据类型 、以及它们是如何在内存中存储的。

-

接下来,介绍了两种 数据结构分类方法,包括逻辑结构与物理结构。

-

后续展开介绍了 数组、链表、栈、队列、散列表、树、堆、图 等数据结构,关心以下内容:

-
    -
  • 基本定义:数据结构的设计来源、存在意义;
  • -
  • 主要特点:在各项数据操作中的优势、劣势;
  • -
  • 常用操作:例如访问、更新、插入、删除、遍历、搜索等;
  • -
  • 常见类型:在算法题或工程实际中,经常碰到的数据结构类型;
  • -
  • 典型应用:此数据结构经常搭配哪些算法使用;
  • -
  • 实现方法:对于重要的数据结构,将给出完整的实现示例;
  • -
-

算法

-

包括 查找算法、排序算法、搜索与回溯、动态规划、分治算法,内容包括:

-
    -
  • 基本定义:算法的设计思想;
  • -
  • 主要特点:使用前置条件、优势和劣势;
  • -
  • 算法效率:最差和平均时间复杂度、空间复杂度;
  • -
  • 实现方法:完整的算法实现,以及优化措施;
  • -
  • 示例题目:结合例题加深理解;
  • -
-

0.1.3. 配套代码

-

完整代码托管在 GitHub 仓库 ,皆可一键运行。

-
-

前置工作

-
    -
  1. 编程环境安装 ,若有请跳过
  2. -
  3. 代码下载与使用方法请见 如何使用本书
  4. -
-
-

0.1.4. 风格约定

-
    -
  • 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。
  • -
  • 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。
  • -
  • 重点内容、总起句、总结句会被 加粗,此类文字值得特别关注。
  • -
  • 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。
  • -
  • 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。
  • -
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
""" 标题注释,用于标注函数、类、测试样例等 """
-
-# 内容注释,用于详解代码
-
-"""
-多行
-注释
-"""
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
/* 标题注释,用于标注函数、类、测试样例等 */
-
-// 内容注释,用于详解代码
-
-/**
- * 多行
- * 注释
- */
-
-
-
-
// 标题注释,用于标注函数、类、测试样例等
-
-// 内容注释,用于详解代码
-
-// 多行
-// 注释
-
-
-
-
-

0.1.5. 本书特点 *

-
-默认折叠,可以跳过 -

以实践为主。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。

-

本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。

-

实践部分主要由示例和代码组成。代码配有简要注释,复杂示例会尽可能地使用视觉化的形式呈现。我强烈建议读者对照着代码自己敲一遍,如果时间有限,也至少逐行读、复制并运行一遍,配合着讲解将代码吃透。

-

视觉化学习。信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。

-

近两年,短视频成为最受欢迎的信息媒介,可以在短时间内将高密度的信息“灌”给我们,有着极其舒适的观看体验。阅读则不然,读者与书本之间天然存在一种“疏离感”,我们看书会累、会走神、会停下来想其他事、会划下喜欢的句子、会思考某一片段的含义,这种疏离感给了读者与书本之间对话的可能,拓宽了想象空间。

-

本书作为一本入门教材,希望可以保有书本的“慢节奏”,但也会避免与读者产生过多“疏离感”,而是努力将知识完整清晰地推送到你聪明的小脑袋瓜中。我将采用视觉化的方式(例如配图、动画),尽我可能清晰易懂地讲解复杂概念和抽象示例。

-

内容精简化。大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。

-

引入一些生活案例或趣味内容,非常适合作为知识点的引子或者解释的补充,但当融入过多额外元素时,内容会稍显冗长,也许反而使读者容易迷失、抓不住重点,这也是本书需要避免的。

-

敲代码如同写字,“美”是统一的追求。本书力求美观的代码,保证规范的变量命名、统一的空格与换行、对齐的缩进、整齐的注释等。

-
-

0.1.6. 致谢

-

本书的成书过程中,我获得了许多人的帮助,包括但不限于:

-
    -
  • 感谢我的女朋友泡泡担任本书的首位读者,从算法小白的视角为本书的写作提出了许多建议,使这本书更加适合算法初学者来阅读。
  • -
  • 感谢腾宝、琦宝、飞宝为本书起了个响当当的名字,好听又有梗,直接唤起我最初敲下第一行代码 "Hello, World!" 的回忆。
  • -
  • 感谢我的导师李博,在小酌畅谈时您告诉我“觉得适合、想做就去做”,坚定了我写这本书的决心。
  • -
  • 感谢苏潼为本书设计了封面和 LOGO ,我有些强迫症,前后多次修改,谢谢你的耐心。
  • -
  • 感谢 @squidfunk ,包括 Material-for-MkDocs 顶级开源项目以及给出的写作排版建议。
  • -
-

在写作过程中,我阅读了许多与数据结构与算法的书籍材料,学习到了许多知识,感谢前辈们的精彩创作。

-

感谢父母,你们一贯的支持与鼓励给了我自由度来做这些有趣的事。

-

0.1.7. 作者简介

-

profile

-

Krahets

- -
大厂高级算法工程师、算法爱好者
- -

力扣(LeetCode)全网阅读量最高博主

-

分享近百道算法题解,累积回复数千读者的评论问题

-

创作 LeetBook《图解算法数据结构》,已免费售出 22 万本

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_preface/contribution.assets/edit_markdown.png b/site/chapter_preface/contribution.assets/edit_markdown.png deleted file mode 100644 index 25037fce6..000000000 Binary files a/site/chapter_preface/contribution.assets/edit_markdown.png and /dev/null differ diff --git a/site/chapter_preface/contribution/index.html b/site/chapter_preface/contribution/index.html deleted file mode 100644 index 2e31a0460..000000000 --- a/site/chapter_preface/contribution/index.html +++ /dev/null @@ -1,1837 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.4. 一起参与创作 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

0.4. 一起参与创作

-
-

开源的魅力

-

纸质书籍的两次印刷的间隔时间往往需要数年,内容更新非常不方便。
但在本开源 HTML 书中,内容更迭的时间被缩短至数日甚至几个小时。

-
-

由于作者水平有限,书中内容难免疏漏谬误,请您谅解。此外,期待您可以一同参与本书的创作。如果发现笔误、无效链接、内容缺失、文字歧义、解释不清晰、行文结构不合理等问题,烦请您修正内容,以帮助其他读者获取更优质的学习内容。所有 撰稿人 将被展示在仓库主页,以感谢您对开源社区的无私奉献。

-

0.4.1. 修改文字与代码

-

每个页面的右上角都有一个「编辑」按钮,你可以按照以下步骤修改文章:

-
    -
  1. 点击编辑按钮,如果遇到提示“需要 Fork 此仓库”,请通过;
  2. -
  3. 修改 Markdown 源文件内容;
  4. -
  5. 在页面底部填写更改说明,然后单击“Propose file change”按钮;
  6. -
  7. 页面跳转后,点击“Create pull request”按钮发起拉取请求即可,我会第一时间查看处理并及时更新内容。
  8. -
-

edit_markdown

-

0.4.2. 修改图片与动画

-

书中的配图无法直接修改,需要通过以下途径提出修改意见:

-
    -
  1. 新建一个 Issue ,将需要修改的图片复制或截图,粘贴在面板中;
  2. -
  3. 描述图片问题,应如何修改;
  4. -
  5. 提交 Issue 即可,我会第一时间重新画图并替换图片。
  6. -
-

0.4.3. 创作新内容

-

如果您想要创作新内容,例如 重写章节、新增章节、修改代码、翻译代码至其他编程语言 等,那么需要实施 Pull Request 工作流程:

-
    -
  1. 登录 GitHub ,并 Fork 本仓库 至个人账号;
  2. -
  3. 进入 Fork 仓库网页,使用 git clone 克隆该仓库至本地;
  4. -
  5. 在本地进行内容创作(建议通过运行测试来验证代码正确性);
  6. -
  7. 将本地更改 Commit ,并 Push 至远程仓库;
  8. -
  9. 刷新仓库网页,点击“Create pull request”按钮发起拉取请求(Pull Request)即可;
  10. -
-

非常欢迎您和我一同来创作本书!

-

0.4.4. 本地部署 hello-algo

-

Docker

-

请确保 Docker 已经安装并启动,并根据如下命令离线部署。

-

稍等片刻,即可使用浏览器打开 http://localhost:8000 访问本项目。

-
git clone https://github.com/krahets/hello-algo.git
-cd hello-algo
-
-docker-compose up -d
-
-

使用如下命令即可删除部署。

-
docker-compose down
-
-

(TODO:教学视频)

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_preface/installation/index.html b/site/chapter_preface/installation/index.html deleted file mode 100644 index 7ea74de6a..000000000 --- a/site/chapter_preface/installation/index.html +++ /dev/null @@ -1,1881 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.3. 编程环境安装 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

0.3. 编程环境安装

-

(TODO 视频教程)

-

0.3.1. 安装 VSCode

-

本书推荐使用开源轻量的 VSCode 作为本地 IDE ,下载并安装 VSCode

-

0.3.2. Java 环境

-
    -
  1. 下载并安装 OpenJDK(版本需满足 > JDK 9)。
  2. -
  3. 在 VSCode 的插件市场中搜索 java ,安装 Java Extension Pack 。
  4. -
-

0.3.3. C/C++ 环境

-
    -
  1. Windows 系统需要安装 MinGW配置教程),MacOS 自带 Clang 无需安装。
  2. -
  3. 在 VSCode 的插件市场中搜索 c++ ,安装 C/C++ Extension Pack 。
  4. -
-

0.3.4. Python 环境

-
    -
  1. 下载并安装 Miniconda3
  2. -
  3. 在 VSCode 的插件市场中搜索 python ,安装 Python Extension Pack 。
  4. -
-

0.3.5. Go 环境

-
    -
  1. 下载并安装 go
  2. -
  3. 在 VSCode 的插件市场中搜索 go ,安装 Go 。
  4. -
  5. 快捷键 Ctrl + Shift + P 呼出命令栏,输入 go ,选择 Go: Install/Update Tools ,全部勾选并安装即可。
  6. -
-

0.3.6. JavaScript 环境

-
    -
  1. 下载并安装 node.js
  2. -
  3. 在 VSCode 的插件市场中搜索 javascript ,安装 JavaScript (ES6) code snippets 。
  4. -
-

0.3.7. C# 环境

-
    -
  1. 下载并安装 .Net 6.0
  2. -
  3. 在 VSCode 的插件市场中搜索 c# ,安装 c# 。
  4. -
-

0.3.8. Swift 环境

-
    -
  1. 下载并安装 Swift
  2. -
  3. 在 VSCode 的插件市场中搜索 swift,安装 Swift for Visual Studio Code
  4. -
-

0.3.9. Rust 环境

-
    -
  1. 下载并安装 Rust
  2. -
  3. 在 VSCode 的插件市场中搜索 rust,安装 rust-analyzer
  4. -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_preface/suggestions.assets/animation.gif b/site/chapter_preface/suggestions.assets/animation.gif deleted file mode 100644 index 60f53f20b..000000000 Binary files a/site/chapter_preface/suggestions.assets/animation.gif and /dev/null differ diff --git a/site/chapter_preface/suggestions.assets/code_md_to_repo.png b/site/chapter_preface/suggestions.assets/code_md_to_repo.png deleted file mode 100644 index 365ac4fe1..000000000 Binary files a/site/chapter_preface/suggestions.assets/code_md_to_repo.png and /dev/null differ diff --git a/site/chapter_preface/suggestions.assets/comment.gif b/site/chapter_preface/suggestions.assets/comment.gif deleted file mode 100644 index e7a74cf9e..000000000 Binary files a/site/chapter_preface/suggestions.assets/comment.gif and /dev/null differ diff --git a/site/chapter_preface/suggestions.assets/download_code.png b/site/chapter_preface/suggestions.assets/download_code.png deleted file mode 100644 index ae6f9aa29..000000000 Binary files a/site/chapter_preface/suggestions.assets/download_code.png and /dev/null differ diff --git a/site/chapter_preface/suggestions.assets/learning_route.png b/site/chapter_preface/suggestions.assets/learning_route.png deleted file mode 100644 index 1f9e39844..000000000 Binary files a/site/chapter_preface/suggestions.assets/learning_route.png and /dev/null differ diff --git a/site/chapter_preface/suggestions.assets/running_code.gif b/site/chapter_preface/suggestions.assets/running_code.gif deleted file mode 100644 index dfe175310..000000000 Binary files a/site/chapter_preface/suggestions.assets/running_code.gif and /dev/null differ diff --git a/site/chapter_preface/suggestions/index.html b/site/chapter_preface/suggestions/index.html deleted file mode 100644 index c27319eed..000000000 --- a/site/chapter_preface/suggestions/index.html +++ /dev/null @@ -1,1840 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.2. 如何使用本书 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

0.2. 如何使用本书

-

0.2.1. 图文搭配学

-

视频和图片相比于文字的信息密度和结构化程度更高,更容易让人理解。在本书中,重点和难点知识会主要以动画、图解的形式呈现,而文字的作用则是作为动画和图的解释与补充。

-

在阅读本书的过程中,若发现某段内容提供了动画或图解,建议你以图为主线,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。

-

animation

-

0.2.2. 代码实践学

-
-

前置工作

-

如果没有本地编程环境,可以参照下节 编程环境安装

-
-

下载代码仓

-

如果已经安装 Git ,可以通过命令行来克隆代码仓。

-
git clone https://github.com/krahets/hello-algo.git
-
-

当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。

-

download_code

-

运行源代码

-

本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 *.java , *.cpp , *.py ,则可在仓库 codes 文件夹中找到对应的 代码源文件

-

code_md_to_repo

-

这些源文件中包含详细注释,配有测试样例,可以直接运行,帮助你省去不必要的调试时间,可以将精力集中在学习内容上。

-

running_code

-
-

代码学习建议

-

若学习时间紧张,请至少将所有代码通读并运行一遍。若时间允许,强烈建议对照着代码自己敲一遍,逐渐锻炼肌肉记忆。相比于读代码,写代码的过程往往能带来新的收获。

-
-

0.2.3. 提问讨论学

-

阅读本书时,请不要“惯着”那些弄不明白的知识点。如果有任何疑惑,可以在评论区留下你的问题,小伙伴们和我都会给予解答(您一般 3 天内会得到回复)。

-

同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家一起加油与进步!

-

comment

-

0.2.4. 算法学习“三步走”

-

第一阶段,算法入门,也正是本书的定位。熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。

-

第二阶段,刷算法题。可以先从热门题单开刷,推荐 剑指 OfferLeetCode 热题 HOT 100 ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。

-

第三阶段,搭建知识体系。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。

-

learning_route

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_reference/index.html b/site/chapter_reference/index.html deleted file mode 100644 index 91d7c68f6..000000000 --- a/site/chapter_reference/index.html +++ /dev/null @@ -1,1611 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - 参考文献 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

参考文献

-

[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

-

[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

-

[3] 程杰. 大话数据结构.

-

[4] 王争. 数据结构与算法之美.

-

[5] 严蔚敏. 数据结构( C 语言版).

-

[6] 邓俊辉. 数据结构( C++ 语言版,第三版).

-

[7] 马克·艾伦·维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版).

-

[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_searching/binary_search.assets/binary_search_step1.png b/site/chapter_searching/binary_search.assets/binary_search_step1.png deleted file mode 100644 index 7748b3437..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step1.png and /dev/null differ diff --git a/site/chapter_searching/binary_search.assets/binary_search_step2.png b/site/chapter_searching/binary_search.assets/binary_search_step2.png deleted file mode 100644 index fba2f959a..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step2.png and /dev/null differ diff --git a/site/chapter_searching/binary_search.assets/binary_search_step3.png b/site/chapter_searching/binary_search.assets/binary_search_step3.png deleted file mode 100644 index 663ffcd26..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step3.png and /dev/null differ diff --git a/site/chapter_searching/binary_search.assets/binary_search_step4.png b/site/chapter_searching/binary_search.assets/binary_search_step4.png deleted file mode 100644 index 728b4cb1a..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step4.png and /dev/null differ diff --git a/site/chapter_searching/binary_search.assets/binary_search_step5.png b/site/chapter_searching/binary_search.assets/binary_search_step5.png deleted file mode 100644 index 1710f2433..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step5.png and /dev/null differ diff --git a/site/chapter_searching/binary_search.assets/binary_search_step6.png b/site/chapter_searching/binary_search.assets/binary_search_step6.png deleted file mode 100644 index b0f6ab1b8..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step6.png and /dev/null differ diff --git a/site/chapter_searching/binary_search.assets/binary_search_step7.png b/site/chapter_searching/binary_search.assets/binary_search_step7.png deleted file mode 100644 index 20bbcec0f..000000000 Binary files a/site/chapter_searching/binary_search.assets/binary_search_step7.png and /dev/null differ diff --git a/site/chapter_searching/binary_search/index.html b/site/chapter_searching/binary_search/index.html deleted file mode 100644 index 72ba522d7..000000000 --- a/site/chapter_searching/binary_search/index.html +++ /dev/null @@ -1,2326 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10.2. 二分查找 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
- -
- - - -
-
- - - - - - - - - - - - - -

10.2. 二分查找

-

「二分查找 Binary Search」利用数据的有序性,通过每轮缩小一半搜索区间来查找目标元素。

-

使用二分查找有两个前置条件:

-
    -
  • 要求输入数据是有序的,这样才能通过判断大小关系来排除一半的搜索区间;
  • -
  • 二分查找仅适用于数组,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。
  • -
-

10.2.1. 算法实现

-

给定一个长度为 \(n\) 的排序数组 nums ,元素从小到大排列。数组的索引取值范围为

-
\[ -0, 1, 2, \cdots, n-1 -\]
-

使用「区间」来表示这个取值范围的方法主要有两种:

-
    -
  1. 双闭区间 \([0, n-1]\) ,即两个边界都包含自身;此方法下,区间 \([0, 0]\) 仍包含一个元素;
  2. -
  3. 左闭右开 \([0, n)\) ,即左边界包含自身、右边界不包含自身;此方法下,区间 \([0, 0)\) 为空;
  4. -
-

“双闭区间”实现

-

首先,我们先采用“双闭区间”的表示,在数组 nums 中查找目标元素 target 的对应索引。

-
-
-
-

binary_search_step1

-
-
-

binary_search_step2

-
-
-

binary_search_step3

-
-
-

binary_search_step4

-
-
-

binary_search_step5

-
-
-

binary_search_step6

-
-
-

binary_search_step7

-
-
-
-

二分查找“双闭区间”表示下的代码如下所示。

-
-
-
-
binary_search.java
/* 二分查找(双闭区间) */
-int binarySearch(int[] nums, int target) {
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    int i = 0, j = nums.length - 1;
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    while (i <= j) {
-        int m = (i + j) / 2;       // 计算中点索引 m
-        if (nums[m] < target)      // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1;
-        else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1;
-        else                       // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.cpp
/* 二分查找(双闭区间) */
-int binarySearch(vector<int>& nums, int target) {
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    int i = 0, j = nums.size() - 1;
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    while (i <= j) {
-        int m = (i + j) / 2;       // 计算中点索引 m
-        if (nums[m] < target)      // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1;
-        else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1;
-        else                       // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.py
""" 二分查找(双闭区间) """
-def binary_search(nums, target):
-    # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    i, j = 0, len(nums) - 1
-    while i <= j:
-        m = (i + j) // 2        # 计算中点索引 m
-        if nums[m] < target:    # 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1
-        elif nums[m] > target:  # 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1
-        else:
-            return m            # 找到目标元素,返回其索引
-    return -1                   # 未找到目标元素,返回 -1
-
-
-
-
binary_search.go
/* 二分查找(双闭区间) */
-func binarySearch(nums []int, target int) int {
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    i, j := 0, len(nums)-1
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    for i <= j {
-        m := (i + j) / 2                // 计算中点索引 m
-        if nums[m] < target {           // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1
-        } else if nums[m] > target {    // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1
-        } else {                        // 找到目标元素,返回其索引
-            return m
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1
-}
-
-
-
-
binary_search.js
/* 二分查找(双闭区间) */
-function binarySearch(nums, target) {
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    let i = 0, j = nums.length - 1;
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    while (i <= j) {
-        let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整
-        if (nums[m] < target)          // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1;
-        else if (nums[m] > target)     // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1;
-        else
-            return m;                  // 找到目标元素,返回其索引
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.ts
/* 二分查找(双闭区间) */
-const binarySearch = function (nums: number[], target: number): number {
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    let i = 0, j = nums.length - 1;
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    while (i <= j) {
-        const m = Math.floor(i + (j - i) / 2);  // 计算中点索引 m
-        if (nums[m] < target) {                 // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1;
-        } else if (nums[m] > target) {          // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1;
-        } else {                                // 找到目标元素,返回其索引
-            return m;
-        }
-    }
-    return -1; // 未找到目标元素,返回 -1
-}
-
-
-
-
binary_search.c

-
-
-
-
binary_search.cs
/* 二分查找(双闭区间) */
-int binarySearch(int[] nums, int target)
-{
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    int i = 0, j = nums.Length - 1;
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    while (i <= j)
-    {
-        int m = (i + j) / 2;       // 计算中点索引 m
-        if (nums[m] < target)      // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1;
-        else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1;
-        else                       // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.swift
/* 二分查找(双闭区间) */
-func binarySearch(nums: [Int], target: Int) -> Int {
-    // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
-    var i = 0
-    var j = nums.count - 1
-    // 循环,当搜索区间为空时跳出(当 i > j 时为空)
-    while i <= j {
-        let m = (i + j) / 2 // 计算中点索引 m
-        if nums[m] < target { // 此情况说明 target 在区间 [m+1, j] 中
-            i = m + 1
-        } else if nums[m] > target { // 此情况说明 target 在区间 [i, m-1] 中
-            j = m - 1
-        } else { // 找到目标元素,返回其索引
-            return m
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1
-}
-
-
-
-
binary_search.zig

-
-
-
-
-

“左闭右开”实现

-

当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。

-
-
-
-
binary_search.java
/* 二分查找(左闭右开) */
-int binarySearch1(int[] nums, int target) {
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    int i = 0, j = nums.length;
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while (i < j) {
-        int m = (i + j) / 2;       // 计算中点索引 m
-        if (nums[m] < target)      // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1;
-        else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中
-            j = m;
-        else                       // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.cpp
/* 二分查找(左闭右开) */
-int binarySearch1(vector<int>& nums, int target) {
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    int i = 0, j = nums.size();
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while (i < j) {
-        int m = (i + j) / 2;       // 计算中点索引 m
-        if (nums[m] < target)      // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1;
-        else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中
-            j = m;
-        else                       // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.py
""" 二分查找(左闭右开) """
-def binary_search1(nums, target):
-    # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    i, j = 0, len(nums)
-    # 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while i < j:
-        m = (i + j) // 2        # 计算中点索引 m
-        if nums[m] < target:    # 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1
-        elif nums[m] > target:  # 此情况说明 target 在区间 [i, m) 中
-            j = m
-        else:                   # 找到目标元素,返回其索引
-            return m
-    return -1                   # 未找到目标元素,返回 -1
-
-
-
-
binary_search.go
/* 二分查找(左闭右开) */
-func binarySearch1(nums []int, target int) int {
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    i, j := 0, len(nums)
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    for i < j {
-        m := (i + j) / 2             // 计算中点索引 m
-        if nums[m] < target {        // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1
-        } else if nums[m] > target { // 此情况说明 target 在区间 [i, m) 中
-            j = m
-        } else {                     // 找到目标元素,返回其索引
-            return m
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1
-}
-
-
-
-
binary_search.js
/* 二分查找(左闭右开) */
-function binarySearch1(nums, target) {
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    let i = 0, j = nums.length;
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while (i < j) {
-        let m = parseInt((i + j) / 2); // 计算中点索引 m ,在 JS 中需使用 parseInt 函数取整
-        if (nums[m] < target)          // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1;
-        else if (nums[m] > target)     // 此情况说明 target 在区间 [i, m) 中
-            j = m;
-        else                           // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.ts
/* 二分查找(左闭右开) */
-const binarySearch1 = function (nums: number[], target: number): number {
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    let i = 0, j = nums.length;
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while (i < j) {
-        const m = Math.floor(i + (j - i) / 2);  // 计算中点索引 m
-        if (nums[m] < target) {                 // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1;
-        } else if (nums[m] > target) {          // 此情况说明 target 在区间 [i, m) 中
-            j = m;
-        } else {                                // 找到目标元素,返回其索引
-            return m;
-        }
-    }
-    return -1; // 未找到目标元素,返回 -1
-}
-
-
-
-
binary_search.c

-
-
-
-
binary_search.cs
/* 二分查找(左闭右开) */
-int binarySearch1(int[] nums, int target)
-{
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    int i = 0, j = nums.Length;
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while (i < j)
-    {
-        int m = (i + j) / 2;       // 计算中点索引 m
-        if (nums[m] < target)      // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1;
-        else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中
-            j = m;
-        else                       // 找到目标元素,返回其索引
-            return m;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
binary_search.swift
/* 二分查找(左闭右开) */
-func binarySearch1(nums: [Int], target: Int) -> Int {
-    // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
-    var i = 0
-    var j = nums.count
-    // 循环,当搜索区间为空时跳出(当 i = j 时为空)
-    while i < j {
-        let m = (i + j) / 2 // 计算中点索引 m
-        if nums[m] < target { // 此情况说明 target 在区间 [m+1, j) 中
-            i = m + 1
-        } else if nums[m] > target { // 此情况说明 target 在区间 [i, m) 中
-            j = m
-        } else { // 找到目标元素,返回其索引
-            return m
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1
-}
-
-
-
-
binary_search.zig

-
-
-
-
-

两种表示对比

-

对比下来,两种表示的代码写法有以下不同点:

-
- - - - - - - - - - - - - - - - - - - - - - - -
表示方法初始化指针缩小区间循环终止条件
双闭区间 \([0, n-1]\)\(i = 0\) , \(j = n-1\)\(i = m + 1\) , \(j = m - 1\)\(i > j\)
左闭右开 \([0, n)\)\(i = 0\) , \(j = n\)\(i = m + 1\) , \(j = m\)\(i = j\)
-
-

观察发现,在“双闭区间”表示中,由于对左右两边界的定义是相同的,因此缩小区间的 \(i\) , \(j\) 处理方法也是对称的,这样更不容易出错。综上所述,建议你采用“双闭区间”的写法。

-

大数越界处理

-

当数组长度很大时,加法 \(i + j\) 的结果有可能会超出 int 类型的取值范围。在此情况下,我们需要换一种计算中点的写法。

-
-
-
-
// (i + j) 有可能超出 int 的取值范围
-int m = (i + j) / 2;
-// 更换为此写法则不会越界
-int m = i + (j - i) / 2;
-
-
-
-
// (i + j) 有可能超出 int 的取值范围
-int m = (i + j) / 2;
-// 更换为此写法则不会越界
-int m = i + (j - i) / 2;
-
-
-
-
# Python 中的数字理论上可以无限大(取决于内存大小)
-# 因此无需考虑大数越界问题
-
-
-
-
// (i + j) 有可能超出 int 的取值范围
-m := (i + j) / 2
-// 更换为此写法则不会越界
-m := i + (j - i) / 2
-
-
-
-
// (i + j) 有可能超出 int 的取值范围
-let m = parseInt((i + j) / 2);
-// 更换为此写法则不会越界
-let m = parseInt(i + (j - i) / 2);
-
-
-
-
// (i + j) 有可能超出 Number 的取值范围
-let m = Math.floor((i + j) / 2);
-// 更换为此写法则不会越界
-let m = Math.floor(i + (j - i) / 2);
-
-
-
-

-
-
-
-
// (i + j) 有可能超出 int 的取值范围
-int m = (i + j) / 2;
-// 更换为此写法则不会越界
-int m = i + (j - i) / 2;
-
-
-
-
// (i + j) 有可能超出 int 的取值范围
-let m = (i + j) / 2
-// 更换为此写法则不会越界
-let m = i + (j - 1) / 2
-
-
-
-

-
-
-
-
-

10.2.2. 复杂度分析

-

时间复杂度 \(O(\log n)\) :其中 \(n\) 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 \(\log_2 n\) ,使用 \(O(\log n)\) 时间。

-

空间复杂度 \(O(1)\) :指针 i , j 使用常数大小空间。

-

10.2.3. 优点与缺点

-

二分查找效率很高,体现在:

-
    -
  • 二分查找时间复杂度低。对数阶在数据量很大时具有巨大优势,例如,当数据大小 \(n = 2^{20}\) 时,线性查找需要 \(2^{20} = 1048576\) 轮循环,而二分查找仅需要 \(\log_2 2^{20} = 20\) 轮循环。
  • -
  • 二分查找不需要额外空间。相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。
  • -
-

但并不意味着所有情况下都应使用二分查找,这是因为:

-
    -
  • 二分查找仅适用于有序数据。如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 \(O(n \log n)\) ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 \(O(n)\) ,也是非常昂贵的。
  • -
  • 二分查找仅适用于数组。由于在二分查找中,访问索引是 “非连续” 的,因此链表或者基于链表实现的数据结构都无法使用。
  • -
  • 在小数据量下,线性查找的性能更好。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 \(n\) 较小时,线性查找反而比二分查找更快。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_searching/hashing_search.assets/hash_search_index.png b/site/chapter_searching/hashing_search.assets/hash_search_index.png deleted file mode 100644 index ab9f0b6b4..000000000 Binary files a/site/chapter_searching/hashing_search.assets/hash_search_index.png and /dev/null differ diff --git a/site/chapter_searching/hashing_search.assets/hash_search_listnode.png b/site/chapter_searching/hashing_search.assets/hash_search_listnode.png deleted file mode 100644 index dd1ed8fab..000000000 Binary files a/site/chapter_searching/hashing_search.assets/hash_search_listnode.png and /dev/null differ diff --git a/site/chapter_searching/hashing_search/index.html b/site/chapter_searching/hashing_search/index.html deleted file mode 100644 index 865df19c5..000000000 --- a/site/chapter_searching/hashing_search/index.html +++ /dev/null @@ -1,1956 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10.3. 哈希查找 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

10.3. 哈希查找

-
-

Question

-

在数据量很大时,「线性查找」太慢;而「二分查找」要求数据必须是有序的,并且只能在数组中应用。那么是否有方法可以同时避免上述缺点呢?答案是肯定的,此方法被称为「哈希查找」。

-
-

「哈希查找 Hash Searching」借助一个哈希表来存储需要的「键值对 Key Value Pair」,我们可以在 \(O(1)\) 时间下实现“键 \(\rightarrow\) 值”映射查找,体现着“以空间换时间”的算法思想。

-

10.3.1. 算法实现

-

如果我们想要给定数组中的一个目标元素 target ,获取该元素的索引,那么可以借助一个哈希表实现查找。

-

hash_search_index

-
-
-
-
hashing_search.java
/* 哈希查找(数组) */
-int hashingSearchArray(Map<Integer, Integer> map, int target) {
-    // 哈希表的 key: 目标元素,value: 索引
-    // 若哈希表中无此 key ,返回 -1
-    return map.getOrDefault(target, -1);
-}
-
-
-
-
hashing_search.cpp
/* 哈希查找(数组) */
-int hashingSearchArray(unordered_map<int, int> map, int target) {
-    // 哈希表的 key: 目标元素,value: 索引
-    // 若哈希表中无此 key ,返回 -1
-    if (map.find(target) == map.end())
-        return -1;
-    return map[target];
-}
-
-
-
-
hashing_search.py
""" 哈希查找(数组) """
-def hashing_search_array(mapp, target):
-    # 哈希表的 key: 目标元素,value: 索引
-    # 若哈希表中无此 key ,返回 -1
-    return mapp.get(target, -1)
-
-
-
-
hashing_search.go
/* 哈希查找(数组) */
-func hashingSearchArray(m map[int]int, target int) int {
-// 哈希表的 key: 目标元素,value: 索引
-// 若哈希表中无此 key ,返回 -1
-    if index, ok := m[target]; ok {
-        return index
-    } else {
-        return -1
-    }
-}
-
-
-
-
hashing_search.js
/* 哈希查找(数组) */
-function hashingSearchArray(map, target) {
-    // 哈希表的 key: 目标元素,value: 索引
-    // 若哈希表中无此 key ,返回 -1
-    return map.has(target) ? map.get(target) : -1;
-}
-
-
-
-
hashing_search.ts
/* 哈希查找(数组) */
-function hashingSearchArray(map: Map<number, number>, target: number): number {
-    // 哈希表的 key: 目标元素,value: 索引
-    // 若哈希表中无此 key ,返回 -1
-    return map.has(target) ? map.get(target) as number : -1;
-}
-
-
-
-
hashing_search.c

-
-
-
-
hashing_search.cs
/* 哈希查找(数组) */
-int hashingSearchArray(Dictionary<int, int> map, int target)
-{
-    // 哈希表的 key: 目标元素,value: 索引
-    // 若哈希表中无此 key ,返回 -1
-    return map.GetValueOrDefault(target, -1);
-}
-
-
-
-
hashing_search.swift
/* 哈希查找(数组) */
-func hashingSearchArray(map: [Int: Int], target: Int) -> Int {
-    // 哈希表的 key: 目标元素,value: 索引
-    // 若哈希表中无此 key ,返回 -1
-    return map[target, default: -1]
-}
-
-
-
-
hashing_search.zig

-
-
-
-
-

再比如,如果我们想要给定一个目标结点值 target ,获取对应的链表结点对象,那么也可以使用哈希查找实现。

-

hash_search_listnode

-
-
-
-
hashing_search.java
/* 哈希查找(链表) */
-ListNode hashingSearchLinkedList(Map<Integer, ListNode> map, int target) {
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 null
-    return map.getOrDefault(target, null);
-}
-
-
-
-
hashing_search.cpp
/* 哈希查找(链表) */
-ListNode* hashingSearchLinkedList(unordered_map<int, ListNode*> map, int target) {
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 nullptr
-    if (map.find(target) == map.end())
-        return nullptr;
-    return map[target];
-}
-
-
-
-
hashing_search.py
"""  哈希查找(链表) """
-def hashing_search_linkedlist(mapp, target):
-    # 哈希表的 key: 目标元素,value: 结点对象
-    # 若哈希表中无此 key ,返回 -1
-    return mapp.get(target, -1)
-
-
-
-
hashing_search.go
/* 哈希查找(链表) */
-func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode {
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 nil
-    if node, ok := m[target]; ok {
-        return node
-    } else {
-        return nil
-    }
-}
-
-
-
-
hashing_search.js
/* 哈希查找(链表) */
-function hashingSearchLinkedList(map, target) {
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 null
-    return map.has(target) ? map.get(target) : null;
-}
-
-
-
-
hashing_search.ts
/* 哈希查找(链表) */
-function hashingSearchLinkedList(map: Map<number, ListNode>, target: number): ListNode | null {
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 null
-    return map.has(target) ? map.get(target) as ListNode : null;
-}
-
-
-
-
hashing_search.c

-
-
-
-
hashing_search.cs
/* 哈希查找(链表) */
-ListNode? hashingSearchLinkedList(Dictionary<int, ListNode> map, int target)
-{
-
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 null
-    return map.GetValueOrDefault(target);
-}
-
-
-
-
hashing_search.swift
/* 哈希查找(链表) */
-func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? {
-    // 哈希表的 key: 目标结点值,value: 结点对象
-    // 若哈希表中无此 key ,返回 null
-    return map[target]
-}
-
-
-
-
hashing_search.zig

-
-
-
-
-

10.3.2. 复杂度分析

-

时间复杂度 \(O(1)\) :哈希表的查找操作使用 \(O(1)\) 时间。

-

空间复杂度 \(O(n)\) :其中 \(n\) 为数组或链表长度。

-

10.3.3. 优点与缺点

-

在哈希表中,查找、插入、删除操作的平均时间复杂度都为 \(O(1)\) ,这意味着无论是高频增删还是高频查找场景,哈希查找的性能表现都非常好。当然,一切的前提是保证哈希表未退化。

-

即使如此,哈希查找仍存在一些问题,在实际应用中,需要根据情况灵活选择方法。

-
    -
  • 辅助哈希表 需要使用 \(O(n)\) 的额外空间,意味着需要预留更多的计算机内存;
  • -
  • 建立和维护哈希表需要时间,因此哈希查找 不适合高频增删、低频查找的使用场景
  • -
  • 当哈希冲突严重时,哈希表会退化为链表,时间复杂度劣化至 \(O(n)\)
  • -
  • 当数据量很小时,线性查找比哈希查找更快。这是因为计算哈希映射函数可能比遍历一个小型数组更慢;
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_searching/linear_search.assets/linear_search.png b/site/chapter_searching/linear_search.assets/linear_search.png deleted file mode 100644 index 01c26cbea..000000000 Binary files a/site/chapter_searching/linear_search.assets/linear_search.png and /dev/null differ diff --git a/site/chapter_searching/linear_search/index.html b/site/chapter_searching/linear_search/index.html deleted file mode 100644 index bb1feb3ec..000000000 --- a/site/chapter_searching/linear_search/index.html +++ /dev/null @@ -1,2025 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10.1. 线性查找 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

10.1. 线性查找

-

「线性查找 Linear Search」是一种最基础的查找方法,其从数据结构的一端开始,依次访问每个元素,直到另一端后停止。

-

10.1.1. 算法实现

-

线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 nums 中查找目标元素 target 的对应索引,那么可以在数组中进行线性查找。

-

linear_search

-
-
-
-
linear_search.java
/* 线性查找(数组) */
-int linearSearchArray(int[] nums, int target) {
-    // 遍历数组
-    for (int i = 0; i < nums.length; i++) {
-        // 找到目标元素,返回其索引
-        if (nums[i] == target)
-            return i;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
linear_search.cpp
/* 线性查找(数组) */
-int linearSearchArray(vector<int>& nums, int target) {
-    // 遍历数组
-    for (int i = 0; i < nums.size(); i++) {
-        // 找到目标元素,返回其索引
-        if (nums[i] == target)
-            return i;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
linear_search.py
""" 线性查找(数组) """
-def linear_search_array(nums, target):
-    # 遍历数组
-    for i in range(len(nums)):
-        if nums[i] == target:  # 找到目标元素,返回其索引
-            return i
-    return -1                  # 未找到目标元素,返回 -1
-
-
-
-
linear_search.go
/* 线性查找(数组) */
-func linearSearchArray(nums []int, target int) int {
-    // 遍历数组
-    for i := 0; i < len(nums); i++ {
-        // 找到目标元素,返回其索引
-        if nums[i] == target {
-            return i
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1
-}
-
-
-
-
linear_search.js
/* 线性查找(数组) */
-function linearSearchArray(nums, target) {
-    // 遍历数组
-    for (let i = 0; i < nums.length; i++) {
-        // 找到目标元素,返回其索引
-        if (nums[i] === target) {
-            return i;
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1; 
-}
-
-
-
-
linear_search.ts
/* 线性查找(数组)*/
-function linearSearchArray(nums: number[], target: number): number {
-    // 遍历数组
-    for (let i = 0; i < nums.length; i++) {
-        // 找到目标元素,返回其索引
-        if (nums[i] === target) {
-            return i;
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
linear_search.c

-
-
-
-
linear_search.cs
/* 线性查找(数组) */
-int linearSearchArray(int[] nums, int target)
-{
-    // 遍历数组
-    for (int i = 0; i < nums.Length; i++)
-    {
-        // 找到目标元素,返回其索引
-        if (nums[i] == target)
-            return i;
-    }
-    // 未找到目标元素,返回 -1
-    return -1;
-}
-
-
-
-
linear_search.swift
/* 线性查找(数组) */
-func linearSearchArray(nums: [Int], target: Int) -> Int {
-    // 遍历数组
-    for i in nums.indices {
-        // 找到目标元素,返回其索引
-        if nums[i] == target {
-            return i
-        }
-    }
-    // 未找到目标元素,返回 -1
-    return -1
-}
-
-
-
-
linear_search.zig

-
-
-
-
-

再比如,我们想要在给定一个目标结点值 target ,返回此结点对象,也可以在链表中进行线性查找。

-
-
-
-
linear_search.java
/* 线性查找(链表) */
-ListNode linearSearchLinkedList(ListNode head, int target) {
-    // 遍历链表
-    while (head != null) {
-        // 找到目标结点,返回之
-        if (head.val == target)
-            return head;
-        head = head.next;
-    }
-    // 未找到目标结点,返回 null
-    return null;
-}
-
-
-
-
linear_search.cpp
/* 线性查找(链表) */
-ListNode* linearSearchLinkedList(ListNode* head, int target) {
-    // 遍历链表
-    while (head != nullptr) {
-        // 找到目标结点,返回之
-        if (head->val == target)
-            return head;
-        head = head->next;
-    }
-    // 未找到目标结点,返回 nullptr
-    return nullptr;
-}
-
-
-
-
linear_search.py
""" 线性查找(链表) """
-def linear_search_linkedlist(head, target):
-    # 遍历链表
-    while head:
-        if head.val == target: # 找到目标结点,返回之
-            return head
-        head = head.next
-    return None                # 未找到目标结点,返回 None
-
-
-
-
linear_search.go
/* 线性查找(链表)*/
-func linerSearchLinkedList(node *ListNode, target int) *ListNode {
-    // 遍历链表
-    for node != nil {
-        // 找到目标结点,返回之
-        if node.Val == target {
-            return node
-        }
-        node = node.Next
-    }
-    // 未找到目标元素,返回 nil
-    return nil
-}
-
-
-
-
linear_search.js
/* 线性查找(链表)*/
-function linearSearchLinkedList(head, target) {
-    // 遍历链表
-    while(head) {
-        // 找到目标结点,返回之
-        if(head.val === target) {
-            return head;
-        }
-        head = head.next;
-    }
-    // 未找到目标结点,返回 null
-    return null;
-}
-
-
-
-
linear_search.ts
/* 线性查找(链表)*/
-function linearSearchLinkedList(head: ListNode | null, target: number): ListNode | null {
-    // 遍历链表
-    while (head) {
-        // 找到目标结点,返回之
-        if (head.val === target) {
-            return head;
-        }
-        head = head.next;
-    }
-    // 未找到目标结点,返回 null
-    return null;
-}
-
-
-
-
linear_search.c

-
-
-
-
linear_search.cs
/* 线性查找(链表) */
-ListNode? linearSearchLinkedList(ListNode head, int target)
-{
-    // 遍历链表
-    while (head != null)
-    {
-        // 找到目标结点,返回之
-        if (head.val == target)
-            return head;
-        head = head.next;
-    }
-    // 未找到目标结点,返回 null
-    return null;
-}
-
-
-
-
linear_search.swift
/* 线性查找(链表) */
-func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? {
-    var head = head
-    // 遍历链表
-    while head != nil {
-        // 找到目标结点,返回之
-        if head?.val == target {
-            return head
-        }
-        head = head?.next
-    }
-    // 未找到目标结点,返回 null
-    return nil
-}
-
-
-
-
linear_search.zig

-
-
-
-
-

10.1.2. 复杂度分析

-

时间复杂度 \(O(n)\) :其中 \(n\) 为数组或链表长度。

-

空间复杂度 \(O(1)\) :无需使用额外空间。

-

10.1.3. 优点与缺点

-

线性查找的通用性极佳。由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。

-

线性查找的时间复杂度太高。在数据量 \(n\) 很大时,查找效率很低。

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_searching/summary/index.html b/site/chapter_searching/summary/index.html deleted file mode 100644 index 4a350cca4..000000000 --- a/site/chapter_searching/summary/index.html +++ /dev/null @@ -1,1730 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10.4. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

10.4. 小结

-
    -
  • 线性查找是一种最基础的查找方法,通过遍历数据结构 + 判断条件实现查找。
  • -
  • 二分查找利用数据的有序性,通过循环不断缩小一半搜索区间来实现查找,其要求输入数据是有序的,并且仅适用于数组或基于数组实现的数据结构。
  • -
  • 哈希查找借助哈希表来实现常数阶时间复杂度的查找操作,体现以空间换时间的算法思想。
  • -
-

Table. 三种查找方法对比

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
线性查找二分查找哈希查找
适用数据结构数组、链表数组数组、链表
输入数据要求有序
平均时间复杂度
查找 / 插入 / 删除
\(O(n)\) / \(O(1)\) / \(O(n)\)\(O(\log n)\) / \(O(n)\) / \(O(n)\)\(O(1)\) / \(O(1)\) / \(O(1)\)
最差时间复杂度
查找 / 插入 / 删除
\(O(n)\) / \(O(1)\) / \(O(n)\)\(O(\log n)\) / \(O(n)\) / \(O(n)\)\(O(n)\) / \(O(n)\) / \(O(n)\)
空间复杂度\(O(1)\)\(O(1)\)\(O(n)\)
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png deleted file mode 100644 index 9b72415ed..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png deleted file mode 100644 index b4d9588d2..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png deleted file mode 100644 index bc44269ce..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png deleted file mode 100644 index 126f24db7..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png deleted file mode 100644 index 0d4335767..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png deleted file mode 100644 index 265b063f5..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png b/site/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png deleted file mode 100644 index 3899ca716..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort.assets/bubble_sort.png b/site/chapter_sorting/bubble_sort.assets/bubble_sort.png deleted file mode 100644 index 693d915a2..000000000 Binary files a/site/chapter_sorting/bubble_sort.assets/bubble_sort.png and /dev/null differ diff --git a/site/chapter_sorting/bubble_sort/index.html b/site/chapter_sorting/bubble_sort/index.html deleted file mode 100644 index 877680026..000000000 --- a/site/chapter_sorting/bubble_sort/index.html +++ /dev/null @@ -1,2172 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11.2. 冒泡排序 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

11.2. 冒泡排序

-

「冒泡排序 Bubble Sort」是一种最基础的排序算法,非常适合作为第一个学习的排序算法。顾名思义,「冒泡」是该算法的核心操作。

-
-

为什么叫“冒泡”

-

在水中,越大的泡泡浮力越大,所以最大的泡泡会最先浮到水面。

-
-

「冒泡」操作则是在模拟上述过程,具体做法为:从数组最左端开始向右遍历,依次对比相邻元素大小,若 左元素 > 右元素 则将它俩交换,最终可将最大元素移动至数组最右端。

-

完成此次冒泡操作后,数组最大元素已在正确位置,接下来只需排序剩余 \(n - 1\) 个元素

-
-
-
-

bubble_operation_step1

-
-
-

bubble_operation_step2

-
-
-

bubble_operation_step3

-
-
-

bubble_operation_step4

-
-
-

bubble_operation_step5

-
-
-

bubble_operation_step6

-
-
-

bubble_operation_step7

-
-
-
-

Fig. 冒泡操作

- -

11.2.1. 算法流程

-
    -
  1. 设数组长度为 \(n\) ,完成第一轮「冒泡」后,数组最大元素已在正确位置,接下来只需排序剩余 \(n - 1\) 个元素。
  2. -
  3. 同理,对剩余 \(n - 1\) 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 \(n - 2\) 个。
  4. -
  5. 以此类推…… 循环 \(n - 1\) 轮「冒泡」,即可完成整个数组的排序
  6. -
-

bubble_sort

-

Fig. 冒泡排序流程

- -
-
-
-
bubble_sort.java
/* 冒泡排序 */
-void bubbleSort(int[] nums) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.length - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.cpp
/* 冒泡排序 */
-void bubbleSort(vector<int>& nums) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.size() - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                // 这里使用了 std::swap() 函数
-                swap(nums[j], nums[j + 1]);
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.py
""" 冒泡排序 """
-def bubble_sort(nums):
-    n = len(nums)
-    # 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i in range(n - 1, 0, -1):
-        # 内循环:冒泡操作
-        for j in range(i):
-            if nums[j] > nums[j + 1]:
-                # 交换 nums[j] 与 nums[j + 1]
-                nums[j], nums[j + 1] = nums[j + 1], nums[j]
-
-
-
-
bubble_sort.go
/* 冒泡排序 */
-func bubbleSort(nums []int) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i := len(nums) - 1; i > 0; i-- {
-        // 内循环:冒泡操作
-        for j := 0; j < i; j++ {
-            if nums[j] > nums[j+1] {
-                // 交换 nums[j] 与 nums[j + 1]
-                nums[j], nums[j+1] = nums[j+1], nums[j]
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.js
/* 冒泡排序 */
-function bubbleSort(nums) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (let i = nums.length - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (let j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.ts
/* 冒泡排序 */
-function bubbleSort(nums: number[]): void {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (let i = nums.length - 1; i > 0; i--) {
-        // 内循环:冒泡操作
-        for (let j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.c
/* 冒泡排序 */
-void bubbleSort(int nums[], int size) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = 0; i < size - 1; i++)
-    {
-        // 内循环:冒泡操作
-        for (int j = 0; j < size - 1 - i; j++)
-        {
-            if (nums[j] > nums[j + 1])
-            {
-                int temp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = temp;
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.cs
/* 冒泡排序 */
-void bubbleSort(int[] nums)
-{
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.Length - 1; i > 0; i--)
-    {
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++)
-        {
-            if (nums[j] > nums[j + 1])
-            {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.swift
/* 冒泡排序 */
-func bubbleSort(nums: inout [Int]) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i in stride(from: nums.count - 1, to: 0, by: -1) {
-        // 内循环:冒泡操作
-        for j in stride(from: 0, to: i, by: 1) {
-            if nums[j] > nums[j + 1] {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j]
-                nums[j] = nums[j + 1]
-                nums[j + 1] = tmp
-            }
-        }
-    }
-}
-
-
-
-
bubble_sort.zig

-
-
-
-
-

11.2.2. 算法特性

-

时间复杂度 \(O(n^2)\) :各轮「冒泡」遍历的数组长度为 \(n - 1\) , \(n - 2\) , \(\cdots\) , \(2\) , \(1\) 次,求和为 \(\frac{(n - 1) n}{2}\) ,因此使用 \(O(n^2)\) 时间。

-

空间复杂度 \(O(1)\) :指针 \(i\) , \(j\) 使用常数大小的额外空间。

-

原地排序:指针变量仅使用常数大小额外空间。

-

稳定排序:不交换相等元素。

-

自适应排序:引入 flag 优化后(见下文),最佳时间复杂度为 \(O(N)\)

-

11.2.3. 效率优化

-

我们发现,若在某轮「冒泡」中未执行任何交换操作,则说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 flag 来监听该情况,若出现则直接返回。

-

优化后,冒泡排序的最差和平均时间复杂度仍为 \(O(n^2)\) ;而在输入数组 已排序 时,达到 最佳时间复杂度 \(O(n)\)

-
-
-
-
bubble_sort.java
/* 冒泡排序(标志优化)*/
-void bubbleSortWithFlag(int[] nums) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.length - 1; i > 0; i--) {
-        boolean flag = false; // 初始化标志位
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                flag = true;  // 记录交换元素
-            }
-        }
-        if (!flag) break;     // 此轮冒泡未交换任何元素,直接跳出
-    }
-}
-
-
-
-
bubble_sort.cpp
/* 冒泡排序(标志优化)*/
-void bubbleSortWithFlag(vector<int>& nums) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.size() - 1; i > 0; i--) {
-        bool flag = false; // 初始化标志位
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                // 这里使用了 std::swap() 函数
-                swap(nums[j], nums[j + 1]);
-                flag = true;  // 记录交换元素
-            }
-        }
-        if (!flag) break;     // 此轮冒泡未交换任何元素,直接跳出
-    }
-}
-
-
-
-
bubble_sort.py
""" 冒泡排序(标志优化) """
-def bubble_sort_with_flag(nums):
-    n = len(nums)
-    # 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i in range(n - 1, 0, -1):
-        flag = False  # 初始化标志位
-        # 内循环:冒泡操作
-        for j in range(i):
-            if nums[j] > nums[j + 1]:
-                # 交换 nums[j] 与 nums[j + 1]
-                nums[j], nums[j + 1] = nums[j + 1], nums[j]
-                flag = True  # 记录交换元素
-        if not flag:
-            break            # 此轮冒泡未交换任何元素,直接跳出
-
-
-
-
bubble_sort.go
/* 冒泡排序(标志优化)*/
-func bubbleSortWithFlag(nums []int) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i := len(nums) - 1; i > 0; i-- {
-        flag := false // 初始化标志位
-        // 内循环:冒泡操作
-        for j := 0; j < i; j++ {
-            if nums[j] > nums[j+1] {
-                // 交换 nums[j] 与 nums[j + 1]
-                nums[j], nums[j+1] = nums[j+1], nums[j]
-                flag = true // 记录交换元素
-            }
-        }
-        if flag == false { // 此轮冒泡未交换任何元素,直接跳出
-            break
-        }
-    }
-}
-
-
-
-
bubble_sort.js
/* 冒泡排序(标志优化)*/
-function bubbleSortWithFlag(nums) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (let i = nums.length - 1; i > 0; i--) {
-        let flag = false; // 初始化标志位
-        // 内循环:冒泡操作
-        for (let j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                flag = true;  // 记录交换元素
-            }
-        }
-        if (!flag) break;     // 此轮冒泡未交换任何元素,直接跳出
-    }
-}
-
-
-
-
bubble_sort.ts
/* 冒泡排序(标志优化)*/
-function bubbleSortWithFlag(nums: number[]): void {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (let i = nums.length - 1; i > 0; i--) {
-        let flag = false; // 初始化标志位
-        // 内循环:冒泡操作
-        for (let j = 0; j < i; j++) {
-            if (nums[j] > nums[j + 1]) {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                flag = true;  // 记录交换元素
-            }
-        }
-        if (!flag) break;     // 此轮冒泡未交换任何元素,直接跳出
-    }
-}
-
-
-
-
bubble_sort.c
/* 冒泡排序 */
-void bubbleSortWithFlag(int nums[], int size) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = 0; i < size - 1; i++)
-    {
-        bool flag = false;
-        // 内循环:冒泡操作
-        for (int j = 0; j < size - 1 - i; j++)
-        {
-            if (nums[j] > nums[j + 1])
-            {
-                int temp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = temp;
-                flag = true;
-            }
-        }
-        if(!flag) break;
-    }
-}
-
-
-
-
bubble_sort.cs
/* 冒泡排序(标志优化)*/
-void bubbleSortWithFlag(int[] nums)
-{
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for (int i = nums.Length - 1; i > 0; i--)
-    {
-        bool flag = false; // 初始化标志位
-        // 内循环:冒泡操作
-        for (int j = 0; j < i; j++)
-        {
-            if (nums[j] > nums[j + 1])
-            {
-                // 交换 nums[j] 与 nums[j + 1]
-                int tmp = nums[j];
-                nums[j] = nums[j + 1];
-                nums[j + 1] = tmp;
-                flag = true;  // 记录交换元素
-            }
-        }
-        if (!flag) break;     // 此轮冒泡未交换任何元素,直接跳出
-    }
-}
-
-
-
-
bubble_sort.swift
/* 冒泡排序(标志优化)*/
-func bubbleSortWithFlag(nums: inout [Int]) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i in stride(from: nums.count - 1, to: 0, by: -1) {
-        var flag = false // 初始化标志位
-        for j in stride(from: 0, to: i, by: 1) {
-            if nums[j] > nums[j + 1] {
-                // 交换 nums[j] 与 nums[j + 1]
-                let tmp = nums[j]
-                nums[j] = nums[j + 1]
-                nums[j + 1] = tmp
-                flag = true // 记录交换元素
-            }
-        }
-        if !flag { // 此轮冒泡未交换任何元素,直接跳出
-            break
-        }
-    }
-}
-
-
-
-
bubble_sort.zig

-
-
-
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_sorting/insertion_sort.assets/insertion_operation.png b/site/chapter_sorting/insertion_sort.assets/insertion_operation.png deleted file mode 100644 index cdaaa4e1a..000000000 Binary files a/site/chapter_sorting/insertion_sort.assets/insertion_operation.png and /dev/null differ diff --git a/site/chapter_sorting/insertion_sort.assets/insertion_sort.png b/site/chapter_sorting/insertion_sort.assets/insertion_sort.png deleted file mode 100644 index 61ee43dfe..000000000 Binary files a/site/chapter_sorting/insertion_sort.assets/insertion_sort.png and /dev/null differ diff --git a/site/chapter_sorting/insertion_sort/index.html b/site/chapter_sorting/insertion_sort/index.html deleted file mode 100644 index 442b10ad3..000000000 --- a/site/chapter_sorting/insertion_sort/index.html +++ /dev/null @@ -1,1946 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11.3. 插入排序 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

11.3. 插入排序

-

「插入排序 Insertion Sort」是一种基于 数组插入操作 的排序算法。

-

「插入操作」原理:选定某个待排序元素为基准数 base,将 base 与其左侧已排序区间元素依次对比大小,并插入到正确位置。

-

回忆数组插入操作,我们需要将从目标索引到 base 之间的所有元素向右移动一位,然后再将 base 赋值给目标索引。

-

insertion_operation

-

Fig. 插入操作

- -

11.3.1. 算法流程

-
    -
  1. 第 1 轮先选取数组的 第 2 个元素base ,执行「插入操作」后,数组前 2 个元素已完成排序
  2. -
  3. 第 2 轮选取 第 3 个元素base ,执行「插入操作」后,数组前 3 个元素已完成排序
  4. -
  5. 以此类推……最后一轮选取 数组尾元素base ,执行「插入操作」后,所有元素已完成排序
  6. -
-

insertion_sort

-

Fig. 插入排序流程

- -
-
-
-
insertion_sort.java
/* 插入排序 */
-void insertionSort(int[] nums) {
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for (int i = 1; i < nums.length; i++) {
-        int base = nums[i], j = i - 1;
-        // 内循环:将 base 插入到左边的正确位置
-        while (j >= 0 && nums[j] > base) {
-            nums[j + 1] = nums[j];  // 1. 将 nums[j] 向右移动一位
-            j--;
-        }
-        nums[j + 1] = base;         // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.cpp
/* 插入排序 */
-void insertionSort(vector<int>& nums) {
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for (int i = 1; i < nums.size(); i++) {
-        int base = nums[i], j = i - 1;
-        // 内循环:将 base 插入到左边的正确位置
-        while (j >= 0 && nums[j] > base) {
-            nums[j + 1] = nums[j];  // 1. 将 nums[j] 向右移动一位
-            j--;
-        }
-        nums[j + 1] = base;         // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.py
""" 插入排序 """
-def insertion_sort(nums):
-    # 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for i in range(1, len(nums)):
-        base = nums[i]
-        j = i - 1
-        # 内循环:将 base 插入到左边的正确位置
-        while j >= 0 and nums[j] > base:
-            nums[j + 1] = nums[j]  # 1. 将 nums[j] 向右移动一位
-            j -= 1
-        nums[j + 1] = base         # 2. 将 base 赋值到正确位置
-
-
-
-
insertion_sort.go
/* 插入排序 */
-func insertionSort(nums []int) {
-    // 外循环:待排序元素数量为 n-1, n-2, ..., 1
-    for i := 1; i < len(nums); i++ {
-        base := nums[i]
-        j := i - 1
-        // 内循环:将 base 插入到左边的正确位置
-        for j >= 0 && nums[j] > base {
-            nums[j+1] = nums[j]     // 1. 将 nums[j] 向右移动一位
-            j--
-        }
-        nums[j+1] = base            // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.js
/* 插入排序 */
-function insertionSort(nums) {
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for (let i = 1; i < nums.length; i++) {
-        let base = nums[i], j = i - 1;
-        // 内循环:将 base 插入到左边的正确位置
-        while (j >= 0 && nums[j] > base) {
-            nums[j + 1] = nums[j];  // 1. 将 nums[j] 向右移动一位
-            j--;
-        }
-        nums[j + 1] = base;         // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.ts
/* 插入排序 */
-function insertionSort(nums: number[]): void {
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for (let i = 1; i < nums.length; i++) {
-        const base = nums[i];
-        let j = i - 1;
-        // 内循环:将 base 插入到左边的正确位置
-        while (j >= 0 && nums[j] > base) {
-            nums[j + 1] = nums[j];  // 1. 将 nums[j] 向右移动一位
-            j--;
-        }
-        nums[j + 1] = base;         // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.c
/* 插入排序 */
-void insertionSort(int nums[], int size) {
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for (int i = 1; i < size; i++)
-    {
-        int base = nums[i], j = i - 1;
-        // 内循环:将 base 插入到左边的正确位置
-        while (j >= 0 && nums[j] > base)
-        {
-            // 1. 将 nums[j] 向右移动一位
-            nums[j + 1] = nums[j]; 
-            j--;
-        }
-        // 2. 将 base 赋值到正确位置
-        nums[j + 1] = base; 
-    }
-}
-
-
-
-
insertion_sort.cs
/* 插入排序 */
-void insertionSort(int[] nums)
-{
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for (int i = 1; i < nums.Length; i++)
-    {
-        int bas = nums[i], j = i - 1;
-        // 内循环:将 base 插入到左边的正确位置
-        while (j >= 0 && nums[j] > bas)
-        {
-            nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位
-            j--;
-        }
-        nums[j + 1] = bas;         // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.swift
/* 插入排序 */
-func insertionSort(nums: inout [Int]) {
-    // 外循环:base = nums[1], nums[2], ..., nums[n-1]
-    for i in stride(from: 1, to: nums.count, by: 1) {
-        let base = nums[i]
-        var j = i - 1
-        // 内循环:将 base 插入到左边的正确位置
-        while j >= 0, nums[j] > base {
-            nums[j + 1] = nums[j] // 1. 将 nums[j] 向右移动一位
-            j -= 1
-        }
-        nums[j + 1] = base // 2. 将 base 赋值到正确位置
-    }
-}
-
-
-
-
insertion_sort.zig

-
-
-
-
-

11.3.2. 算法特性

-

时间复杂度 \(O(n^2)\) :最差情况下,各轮插入操作循环 \(n - 1\) , \(n-2\) , \(\cdots\) , \(2\) , \(1\) 次,求和为 \(\frac{(n - 1) n}{2}\) ,使用 \(O(n^2)\) 时间。

-

空间复杂度 \(O(1)\) :指针 \(i\) , \(j\) 使用常数大小的额外空间。

-

原地排序:指针变量仅使用常数大小额外空间。

-

稳定排序:不交换相等元素。

-

自适应排序:最佳情况下,时间复杂度为 \(O(n)\)

-

11.3.3. 插入排序 vs 冒泡排序

-
-

Question

-

虽然「插入排序」和「冒泡排序」的时间复杂度皆为 \(O(n^2)\) ,但实际运行速度却有很大差别,这是为什么呢?

-
-

回顾复杂度分析,两个方法的循环次数都是 \(\frac{(n - 1) n}{2}\) 。但不同的是,「冒泡操作」是在做 元素交换,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 赋值,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。

-

插入排序运行速度快,并且具有原地、稳定、自适应的优点,因此很受欢迎。实际上,包括 Java 在内的许多编程语言的排序库函数的实现都用到了插入排序。库函数的大致思路:

-
    -
  • 对于 长数组,采用基于分治的排序算法,例如「快速排序」,时间复杂度为 \(O(n \log n)\)
  • -
  • 对于 短数组,直接使用「插入排序」,时间复杂度为 \(O(n^2)\)
  • -
-

在数组较短时,复杂度中的常数项(即每轮中的单元操作数量)占主导作用,此时插入排序运行地更快。这个现象与「线性查找」和「二分查找」的情况类似。

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_sorting/intro_to_sort.assets/sorting_examples.png b/site/chapter_sorting/intro_to_sort.assets/sorting_examples.png deleted file mode 100644 index 3423f9224..000000000 Binary files a/site/chapter_sorting/intro_to_sort.assets/sorting_examples.png and /dev/null differ diff --git a/site/chapter_sorting/intro_to_sort/index.html b/site/chapter_sorting/intro_to_sort/index.html deleted file mode 100644 index f4a6396ca..000000000 --- a/site/chapter_sorting/intro_to_sort/index.html +++ /dev/null @@ -1,1866 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11.1. 排序简介 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

11.1. 排序简介

-

「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。

-
    -
  • 待排序的列表的 元素类型 可以是整数、浮点数、字符、或字符串;
  • -
  • 排序算法可以根据需要设定 判断规则,例如数字大小、字符 ASCII 码顺序、自定义规则;
  • -
-

sorting_examples

-

Fig. 排序中的不同元素类型和判断规则

- -

11.1.1. 评价维度

-

排序算法主要可根据 稳定性 、就地性 、自适应性 、比较类 来分类。

-

稳定性

-
    -
  • 「稳定排序」在完成排序后,不改变 相等元素在数组中的相对顺序。
  • -
  • 「非稳定排序」在完成排序后,相等元素在数组中的相对位置 可能被改变
  • -
-

假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,在多级排序中是必须的

-
  # 输入数据是按照姓名排序好的
-  # (name, age)
-    ('A', 19)
-    ('B', 18)
-    ('C', 21)
-    ('D', 19)
-    ('E', 23)
-
-  # 假设使用非稳定排序算法按年龄排序列表,
-  # 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变,
-  # 输入数据按姓名排序的性质丢失
-    ('B', 18)
-    ('D', 19)
-    ('A', 19)  
-    ('C', 21)
-    ('E', 23)
-
-

就地性

-
    -
  • 「原地排序」无需辅助数据,不使用额外空间;
  • -
  • 「非原地排序」需要借助辅助数据,使用额外空间;
  • -
-

「原地排序」不使用额外空间,可以节约内存;并且一般情况下,由于数据操作减少,原地排序的运行效率也更高。

-

自适应性

-
    -
  • 「自适应排序」的时间复杂度受输入数据影响,即最佳 / 最差 / 平均时间复杂度不相等。
  • -
  • 「非自适应排序」的时间复杂度恒定,与输入数据无关。
  • -
-

我们希望 最差 = 平均,即不希望排序算法的运行效率在某些输入数据下发生劣化。

-

比较类

-
    -
  • 「比较类排序」基于元素之间的比较算子(小于、相等、大于)来决定元素的相对顺序。
  • -
  • 「非比较类排序」不基于元素之间的比较算子来决定元素的相对顺序。
  • -
-

「比较类排序」的时间复杂度最优为 \(O(n \log n)\) ;而「非比较类排序」可以达到 \(O(n)\) 的时间复杂度,但通用性较差。

-

11.1.2. 理想排序算法

-
    -
  • 运行快,即时间复杂度低;
  • -
  • 稳定排序,即排序后相等元素的相对位置不变化;
  • -
  • 原地排序,即运行中不使用额外的辅助空间;
  • -
  • 正向自适应性,即算法的运行效率不会在某些输入数据下发生劣化;
  • -
-

然而,没有排序算法同时具备以上所有特性。排序算法的选型使用取决于具体的列表类型、列表长度、元素分布等因素。

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_preview.png b/site/chapter_sorting/merge_sort.assets/merge_sort_preview.png deleted file mode 100644 index fa1844b71..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_preview.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step1.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step1.png deleted file mode 100644 index 2250b178e..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step1.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step10.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step10.png deleted file mode 100644 index a2d944a0b..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step10.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step2.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step2.png deleted file mode 100644 index 74d58b504..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step2.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step3.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step3.png deleted file mode 100644 index 4fc818342..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step3.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step4.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step4.png deleted file mode 100644 index f7b501d08..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step4.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step5.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step5.png deleted file mode 100644 index ee688f3a7..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step5.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step6.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step6.png deleted file mode 100644 index 18b54c175..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step6.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step7.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step7.png deleted file mode 100644 index 90d2a05e6..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step7.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step8.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step8.png deleted file mode 100644 index 4db8c0c66..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step8.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort.assets/merge_sort_step9.png b/site/chapter_sorting/merge_sort.assets/merge_sort_step9.png deleted file mode 100644 index 49e845708..000000000 Binary files a/site/chapter_sorting/merge_sort.assets/merge_sort_step9.png and /dev/null differ diff --git a/site/chapter_sorting/merge_sort/index.html b/site/chapter_sorting/merge_sort/index.html deleted file mode 100644 index aeb47c884..000000000 --- a/site/chapter_sorting/merge_sort/index.html +++ /dev/null @@ -1,2197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11.5. 归并排序 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

11.5. 归并排序

-

「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段:

-
    -
  1. 划分阶段:通过递归不断 将数组从中点位置划分开,将长数组的排序问题转化为短数组的排序问题;
  2. -
  3. 合并阶段:划分到子数组长度为 1 时,开始向上合并,不断将 左、右两个短排序数组 合并为 一个长排序数组,直至合并至原数组时完成排序;
  4. -
-

merge_sort_preview

-

Fig. 归并排序两阶段:划分与合并

- -

11.5.1. 算法流程

-

「递归划分」 从顶至底递归地 将数组从中点切为两个子数组,直至长度为 1 ;

-
    -
  1. 计算数组中点 mid ,递归划分左子数组(区间 [left, mid] )和右子数组(区间 [mid + 1, right] );
  2. -
  3. 递归执行 1. 步骤,直至子数组区间长度为 1 时,终止递归划分;
  4. -
-

「回溯合并」 从底至顶地将左子数组和右子数组合并为一个 有序数组

-

需要注意,由于从长度为 1 的子数组开始合并,所以 每个子数组都是有序的。因此,合并任务本质是要 将两个有序子数组合并为一个有序数组

-
-
-
-

merge_sort_step1

-
-
-

merge_sort_step2

-
-
-

merge_sort_step3

-
-
-

merge_sort_step4

-
-
-

merge_sort_step5

-
-
-

merge_sort_step6

-
-
-

merge_sort_step7

-
-
-

merge_sort_step8

-
-
-

merge_sort_step9

-
-
-

merge_sort_step10

-
-
-
-

观察发现,归并排序的递归顺序就是二叉树的「后序遍历」。

-
    -
  • 后序遍历:先递归左子树、再递归右子树、最后处理根结点。
  • -
  • 归并排序:先递归左子树、再递归右子树、最后处理合并。
  • -
-
-
-
-
merge_sort.java
/* 合并左子数组和右子数组 */
-// 左子数组区间 [left, mid]
-// 右子数组区间 [mid + 1, right]
-void merge(int[] nums, int left, int mid, int right) {
-    // 初始化辅助数组
-    int[] tmp = Arrays.copyOfRange(nums, left, right + 1);   
-    // 左子数组的起始索引和结束索引  
-    int leftStart = left - left, leftEnd = mid - left;
-    // 右子数组的起始索引和结束索引       
-    int rightStart = mid + 1 - left, rightEnd = right - left;
-    // i, j 分别指向左子数组、右子数组的首元素
-    int i = leftStart, j = rightStart;                
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for (int k = left; k <= right; k++) {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if (i > leftEnd)
-            nums[k] = tmp[j++];
-        // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        else if (j > rightEnd || tmp[i] <= tmp[j])
-            nums[k] = tmp[i++];
-        // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        else
-            nums[k] = tmp[j++];
-    }
-}
-
-/* 归并排序 */
-void mergeSort(int[] nums, int left, int right) {
-    // 终止条件
-    if (left >= right) return;       // 当子数组长度为 1 时终止递归
-    // 划分阶段
-    int mid = (left + right) / 2;    // 计算中点
-    mergeSort(nums, left, mid);      // 递归左子数组
-    mergeSort(nums, mid + 1, right); // 递归右子数组
-    // 合并阶段
-    merge(nums, left, mid, right);
-}
-
-
-
-
merge_sort.cpp
/* 合并左子数组和右子数组 */
-// 左子数组区间 [left, mid]
-// 右子数组区间 [mid + 1, right]
-void merge(vector<int>& nums, int left, int mid, int right) {
-    // 初始化辅助数组
-    vector<int> tmp(nums.begin() + left, nums.begin() + right + 1);   
-    // 左子数组的起始索引和结束索引  
-    int leftStart = left - left, leftEnd = mid - left;
-    // 右子数组的起始索引和结束索引       
-    int rightStart = mid + 1 - left, rightEnd = right - left;
-    // i, j 分别指向左子数组、右子数组的首元素
-    int i = leftStart, j = rightStart;                
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for (int k = left; k <= right; k++) {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if (i > leftEnd)
-            nums[k] = tmp[j++];
-        // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        else if (j > rightEnd || tmp[i] <= tmp[j])
-            nums[k] = tmp[i++];
-        // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        else
-            nums[k] = tmp[j++];
-    }
-}
-
-/* 归并排序 */
-void mergeSort(vector<int>& nums, int left, int right) {
-    // 终止条件
-    if (left >= right) return;       // 当子数组长度为 1 时终止递归
-    // 划分阶段
-    int mid = (left + right) / 2;    // 计算中点
-    mergeSort(nums, left, mid);      // 递归左子数组
-    mergeSort(nums, mid + 1, right); // 递归右子数组
-    // 合并阶段
-    merge(nums, left, mid, right);
-}
-
-
-
-
merge_sort.py
""" 合并左子数组和右子数组 """
-# 左子数组区间 [left, mid]
-# 右子数组区间 [mid + 1, right]
-def merge(nums, left, mid, right):
-    # 初始化辅助数组 借助 copy模块
-    tmp = nums[left:right + 1]
-    # 左子数组的起始索引和结束索引
-    left_start, left_end = left - left, mid - left
-    # 右子数组的起始索引和结束索引
-    right_start, right_end = mid + 1 - left, right - left
-    # i, j 分别指向左子数组、右子数组的首元素
-    i, j = left_start, right_start
-    # 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for k in range(left, right + 1):
-        # 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if i > left_end:
-            nums[k] = tmp[j]
-            j += 1
-        # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        elif j > right_end or tmp[i] <= tmp[j]:
-            nums[k] = tmp[i]
-            i += 1
-        # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        else:
-            nums[k] = tmp[j]
-            j += 1
-
-""" 归并排序 """
-def merge_sort(nums, left, right):
-    # 终止条件
-    if left >= right:
-        return                        # 当子数组长度为 1 时终止递归
-    # 划分阶段
-    mid = (left + right) // 2         # 计算中点
-    merge_sort(nums, left, mid)       # 递归左子数组
-    merge_sort(nums, mid + 1, right)  # 递归右子数组
-    # 合并阶段
-    merge(nums, left, mid, right)
-
-
-
-
merge_sort.go
/*
-    合并左子数组和右子数组
-    左子数组区间 [left, mid]
-    右子数组区间 [mid + 1, right]
-*/
-func merge(nums []int, left, mid, right int) {
-    // 初始化辅助数组 借助 copy 模块
-    tmp := make([]int, right-left+1)
-    for i := left; i <= right; i++ {
-        tmp[i-left] = nums[i]
-    }
-    // 左子数组的起始索引和结束索引
-    leftStart, leftEnd := left-left, mid-left
-    // 右子数组的起始索引和结束索引
-    rightStart, rightEnd := mid+1-left, right-left
-    // i, j 分别指向左子数组、右子数组的首元素
-    i, j := leftStart, rightStart
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for k := left; k <= right; k++ {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if i > leftEnd {
-            nums[k] = tmp[j]
-            j++
-            // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        } else if j > rightEnd || tmp[i] <= tmp[j] {
-            nums[k] = tmp[i]
-            i++
-            // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        } else {
-            nums[k] = tmp[j]
-            j++
-        }
-    }
-}
-
-func mergeSort(nums []int, left, right int) {
-    // 终止条件
-    if left >= right {
-        return
-    }
-    // 划分阶段
-    mid := (left + right) / 2
-    mergeSort(nums, left, mid)
-    mergeSort(nums, mid+1, right)
-    // 合并阶段
-    merge(nums, left, mid, right)
-}
-
-
-
-
merge_sort.js
/* 合并左子数组和右子数组 */
-// 左子数组区间 [left, mid]
-// 右子数组区间 [mid + 1, right]
-function merge(nums, left, mid, right) {
-    // 初始化辅助数组
-    let tmp = nums.slice(left, right + 1);   
-    // 左子数组的起始索引和结束索引  
-    let leftStart = left - left, leftEnd = mid - left;
-    // 右子数组的起始索引和结束索引       
-    let rightStart = mid + 1 - left, rightEnd = right - left;
-    // i, j 分别指向左子数组、右子数组的首元素
-    let i = leftStart, j = rightStart;                
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for (let k = left; k <= right; k++) {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if (i > leftEnd) {
-            nums[k] = tmp[j++];
-        // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        } else if (j > rightEnd || tmp[i] <= tmp[j]) {
-            nums[k] = tmp[i++];
-        // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        } else {
-            nums[k] = tmp[j++];
-        }
-    }
-}
-
-/* 归并排序 */
-function mergeSort(nums, left, right) {
-    // 终止条件
-    if (left >= right) return;       // 当子数组长度为 1 时终止递归
-    // 划分阶段
-    let mid = Math.floor((left + right) / 2);    // 计算中点
-    mergeSort(nums, left, mid);      // 递归左子数组
-    mergeSort(nums, mid + 1, right); // 递归右子数组
-    // 合并阶段
-    merge(nums, left, mid, right);
-}
-
-
-
-
merge_sort.ts
/* 合并左子数组和右子数组 */
-// 左子数组区间 [left, mid]
-// 右子数组区间 [mid + 1, right]
-function merge(nums: number[], left: number, mid: number, right: number): void {
-    // 初始化辅助数组
-    let tmp = nums.slice(left, right + 1);
-    // 左子数组的起始索引和结束索引
-    let leftStart = left - left, leftEnd = mid - left;
-    // 右子数组的起始索引和结束索引
-    let rightStart = mid + 1 - left, rightEnd = right - left;
-    // i, j 分别指向左子数组、右子数组的首元素
-    let i = leftStart, j = rightStart;
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for (let k = left; k <= right; k++) {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if (i > leftEnd) {
-            nums[k] = tmp[j++];
-            // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        } else if (j > rightEnd || tmp[i] <= tmp[j]) {
-            nums[k] = tmp[i++];
-            // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        } else {
-            nums[k] = tmp[j++];
-        }
-    }
-}
-
-/* 归并排序 */
-function mergeSort(nums: number[], left: number, right: number): void {
-    // 终止条件
-    if (left >= right) return; // 当子数组长度为 1 时终止递归
-    // 划分阶段
-    let mid = Math.floor((left + right) / 2); // 计算中点
-    mergeSort(nums, left, mid); // 递归左子数组
-    mergeSort(nums, mid + 1, right); // 递归右子数组
-    // 合并阶段
-    merge(nums, left, mid, right);
-}
-
-
-
-
merge_sort.c

-
-
-
-
merge_sort.cs
/* 合并左子数组和右子数组 */
-// 左子数组区间 [left, mid]
-// 右子数组区间 [mid + 1, right]
-void merge(int[] nums, int left, int mid, int right)
-{
-    // 初始化辅助数组
-    int[] tmp = nums[left..(right + 1)];
-    // 左子数组的起始索引和结束索引  
-    int leftStart = left - left, leftEnd = mid - left;
-    // 右子数组的起始索引和结束索引       
-    int rightStart = mid + 1 - left, rightEnd = right - left;
-    // i, j 分别指向左子数组、右子数组的首元素
-    int i = leftStart, j = rightStart;
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for (int k = left; k <= right; k++)
-    {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if (i > leftEnd)
-            nums[k] = tmp[j++];
-        // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        else if (j > rightEnd || tmp[i] <= tmp[j])
-            nums[k] = tmp[i++];
-        // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        else
-            nums[k] = tmp[j++];
-    }
-}
-
-/* 归并排序 */
-void mergeSort(int[] nums, int left, int right)
-{
-    // 终止条件
-    if (left >= right) return;       // 当子数组长度为 1 时终止递归
-    // 划分阶段
-    int mid = (left + right) / 2;    // 计算中点
-    mergeSort(nums, left, mid);      // 递归左子数组
-    mergeSort(nums, mid + 1, right); // 递归右子数组
-    // 合并阶段
-    merge(nums, left, mid, right);
-}
-
-
-
-
merge_sort.swift
/**
-* 合并左子数组和右子数组
-* 左子数组区间 [left, mid]
-* 右子数组区间 [mid + 1, right]
-*/
-func merge(nums: inout [Int], left: Int, mid: Int, right: Int) {
-    // 初始化辅助数组
-    let tmp = Array(nums[left ..< (right + 1)])
-    // 左子数组的起始索引和结束索引
-    let leftStart = left - left
-    let leftEnd = mid - left
-    // 右子数组的起始索引和结束索引
-    let rightStart = mid + 1 - left
-    let rightEnd = right - left
-    // i, j 分别指向左子数组、右子数组的首元素
-    var i = leftStart
-    var j = rightStart
-    // 通过覆盖原数组 nums 来合并左子数组和右子数组
-    for k in left ... right {
-        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
-        if i > leftEnd {
-            nums[k] = tmp[j]
-            j += 1
-        }
-        // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
-        else if j > rightEnd || tmp[i] <= tmp[j] {
-            nums[k] = tmp[i]
-            i += 1
-        }
-        // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
-        else {
-            nums[k] = tmp[j]
-            j += 1
-        }
-    }
-}
-
-/* 归并排序 */
-func mergeSort(nums: inout [Int], left: Int, right: Int) {
-    // 终止条件
-    if left >= right { // 当子数组长度为 1 时终止递归
-        return
-    }
-    // 划分阶段
-    let mid = (left + right) / 2 // 计算中点
-    mergeSort(nums: &nums, left: left, right: mid) // 递归左子数组
-    mergeSort(nums: &nums, left: mid + 1, right: right) // 递归右子数组
-    // 合并阶段
-    merge(nums: &nums, left: left, mid: mid, right: right)
-}
-
-
-
-
merge_sort.zig

-
-
-
-
-

下面重点解释一下合并方法 merge() 的流程:

-
    -
  1. 初始化一个辅助数组 tmp 暂存待合并区间 [left, right] 内的元素,后续通过覆盖原数组 nums 的元素来实现合并;
  2. -
  3. 初始化指针 i , j , k 分别指向左子数组、右子数组、原数组的首元素;
  4. -
  5. 循环判断 tmp[i]tmp[j] 的大小,将较小的先覆盖至 nums[k] ,指针 i , j 根据判断结果交替前进(指针 k 也前进),直至两个子数组都遍历完,即可完成合并。
  6. -
-

合并方法 merge() 代码中的主要难点:

-
    -
  • nums 的待合并区间为 [left, right] ,而因为 tmp 只复制了 nums 该区间元素,所以 tmp 对应区间为 [0, right - left]需要特别注意代码中各个变量的含义
  • -
  • 判断 tmp[i]tmp[j] 的大小的操作中,还 需考虑当子数组遍历完成后的索引越界问题,即 i > leftEndj > rightEnd 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。
  • -
-

11.5.2. 算法特性

-
    -
  • 时间复杂度 \(O(n \log n)\) :划分形成高度为 \(\log n\) 的递归树,每层合并的总操作数量为 \(n\) ,总体使用 \(O(n \log n)\) 时间。
  • -
  • 空间复杂度 \(O(n)\) :需借助辅助数组实现合并,使用 \(O(n)\) 大小的额外空间;递归深度为 \(\log n\) ,使用 \(O(\log n)\) 大小的栈帧空间。
  • -
  • 非原地排序:辅助数组需要使用 \(O(n)\) 额外空间。
  • -
  • 稳定排序:在合并时可保证相等元素的相对位置不变。
  • -
  • 非自适应排序:对于任意输入数据,归并排序的时间复杂度皆相同。
  • -
-

11.5.3. 链表排序 *

-

归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,空间复杂度可被优化至 \(O(1)\) ,这是因为:

-
    -
  • 由于链表可仅通过改变指针来实现结点增删,因此“将两个短有序链表合并为一个长有序链表”无需使用额外空间,即回溯合并阶段不用像排序数组一样建立辅助数组 tmp
  • -
  • 通过使用「迭代」代替「递归划分」,可省去递归使用的栈帧空间;
  • -
-
-

详情参考:148. 排序链表

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step1.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step1.png deleted file mode 100644 index 41f143c4e..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step1.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step2.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step2.png deleted file mode 100644 index ebb3d43f8..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step2.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step3.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step3.png deleted file mode 100644 index 8eb4f872c..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step3.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step4.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step4.png deleted file mode 100644 index b3e1fd129..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step4.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step5.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step5.png deleted file mode 100644 index 0fc46a4b9..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step5.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step6.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step6.png deleted file mode 100644 index 808db7e2b..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step6.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step7.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step7.png deleted file mode 100644 index cf9653021..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step7.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step8.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step8.png deleted file mode 100644 index acb723b77..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step8.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/pivot_division_step9.png b/site/chapter_sorting/quick_sort.assets/pivot_division_step9.png deleted file mode 100644 index c07b45749..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/pivot_division_step9.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort.assets/quick_sort.png b/site/chapter_sorting/quick_sort.assets/quick_sort.png deleted file mode 100644 index 7b71ecd36..000000000 Binary files a/site/chapter_sorting/quick_sort.assets/quick_sort.png and /dev/null differ diff --git a/site/chapter_sorting/quick_sort/index.html b/site/chapter_sorting/quick_sort/index.html deleted file mode 100644 index 2a0fcb4d6..000000000 --- a/site/chapter_sorting/quick_sort/index.html +++ /dev/null @@ -1,2576 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11.4. 快速排序 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
- -
- - - -
-
- - - - - - - - - - - - - -

11.4. 快速排序

-

「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。

-

快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 基准数,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为:

-
    -
  1. 以数组最左端元素作为基准数,初始化两个指针 i , j 指向数组两端;
  2. -
  3. 设置一个循环,每轮中使用 i / j 分别寻找首个比基准数大 / 小的元素,并交换此两元素;
  4. -
  5. 不断循环步骤 2. ,直至 i , j 相遇时跳出,最终把基准数交换至两个子数组的分界线;
  6. -
-

「哨兵划分」执行完毕后,原数组被划分成两个部分,即 左子数组右子数组,且满足 左子数组任意元素 < 基准数 < 右子数组任意元素。因此,接下来我们只需要排序两个子数组即可。

-
-
-
-

pivot_division_step1

-
-
-

pivot_division_step2

-
-
-

pivot_division_step3

-
-
-

pivot_division_step4

-
-
-

pivot_division_step5

-
-
-

pivot_division_step6

-
-
-

pivot_division_step7

-
-
-

pivot_division_step8

-
-
-

pivot_division_step9

-
-
-
-

Fig. 哨兵划分

- -
-
-
-
quick_sort.java
/* 元素交换 */
-void swap(int[] nums, int i, int j) {
-    int tmp = nums[i];
-    nums[i] = nums[j];
-    nums[j] = tmp;
-}
-
-/* 哨兵划分 */
-int partition(int[] nums, int left, int right) {
-    // 以 nums[left] 作为基准数
-    int i = left, j = right;
-    while (i < j) {
-        while (i < j && nums[j] >= nums[left])
-            j--;          // 从右向左找首个小于基准数的元素
-        while (i < j && nums[i] <= nums[left])
-            i++;          // 从左向右找首个大于基准数的元素
-        swap(nums, i, j); // 交换这两个元素
-    }
-    swap(nums, i, left);  // 将基准数交换至两子数组的分界线
-    return i;             // 返回基准数的索引
-}
-
-
-
-
quick_sort.cpp
/* 元素交换 */
-void swap(vector<int>& nums, int i, int j) {
-    int tmp = nums[i];
-    nums[i] = nums[j];
-    nums[j] = tmp;
-}
-
-/* 哨兵划分 */
-int partition(vector<int>& nums, int left, int right) {
-    // 以 nums[left] 作为基准数
-    int i = left, j = right;
-    while (i < j) {
-        while (i < j && nums[j] >= nums[left])
-            j--;          // 从右向左找首个小于基准数的元素
-        while (i < j && nums[i] <= nums[left])
-            i++;          // 从左向右找首个大于基准数的元素
-        swap(nums, i, j); // 交换这两个元素
-    }
-    swap(nums, i, left);  // 将基准数交换至两子数组的分界线
-    return i;             // 返回基准数的索引
-}
-
-
-
-
quick_sort.py
""" 哨兵划分 """
-def partition(self, nums, left, right):
-    # 以 nums[left] 作为基准数
-    i, j = left, right
-    while i < j:
-        while i < j and nums[j] >= nums[left]:
-            j -= 1  # 从右向左找首个小于基准数的元素
-        while i < j and nums[i] <= nums[left]:
-            i += 1  # 从左向右找首个大于基准数的元素
-        # 元素交换
-        nums[i], nums[j] = nums[j], nums[i]
-    # 将基准数交换至两子数组的分界线
-    nums[i], nums[left] = nums[left], nums[i]
-    return i  # 返回基准数的索引
-
-
-
-
quick_sort.go
/* 哨兵划分 */
-func partition(nums []int, left, right int) int {
-    // 以 nums[left] 作为基准数
-    i, j := left, right
-    for i < j {
-        for i < j && nums[j] >= nums[left] {
-            j-- // 从右向左找首个小于基准数的元素
-        }
-        for i < j && nums[i] <= nums[left] {
-            i++ // 从左向右找首个大于基准数的元素
-        }
-        //元素交换
-        nums[i], nums[j] = nums[j], nums[i]
-    }
-    // 将基准数交换至两子数组的分界线
-    nums[i], nums[left] = nums[left], nums[i]
-    return i // 返回基准数的索引
-}
-
-
-
-
quick_sort.js
/* 元素交换 */
-function swap(nums, i, j) {
-    let tmp = nums[i];
-    nums[i] = nums[j];
-    nums[j] = tmp;
-}
-
-/* 哨兵划分 */
-function partition(nums, left, right) {
-    // 以 nums[left] 作为基准数
-    let i = left, j = right;
-    while (i < j) {
-        while (i < j && nums[j] >= nums[left]) {
-            j -= 1; // 从右向左找首个小于基准数的元素
-        }
-        while (i < j && nums[i] <= nums[left]) {
-            i += 1; // 从左向右找首个大于基准数的元素
-        }
-        // 元素交换
-        swap(nums, i, j); // 交换这两个元素
-    }
-    swap(nums, i, left); // 将基准数交换至两子数组的分界线
-    return i; // 返回基准数的索引
-}
-
-
-
-
quick_sort.ts
/* 元素交换 */
-function swap(nums: number[], i: number, j: number): void {
-    let tmp = nums[i];
-    nums[i] = nums[j];
-    nums[j] = tmp;
-}
-
-/* 哨兵划分 */
-function partition(nums: number[], left: number, right: number): number {
-    // 以 nums[left] 作为基准数
-    let i = left, j = right;
-    while (i < j) {
-        while (i < j && nums[j] >= nums[left]) {
-            j -= 1; // 从右向左找首个小于基准数的元素
-        }
-        while (i < j && nums[i] <= nums[left]) {
-            i += 1; // 从左向右找首个大于基准数的元素
-        }
-        // 元素交换
-        swap(nums, i, j); // 交换这两个元素
-    }
-    swap(nums, i, left); // 将基准数交换至两子数组的分界线
-    return i; // 返回基准数的索引
-}
-
-
-
-
quick_sort.c

-
-
-
-
quick_sort.cs
/* 元素交换 */
-void swap(int[] nums, int i, int j)
-{
-    int tmp = nums[i];
-    nums[i] = nums[j];
-    nums[j] = tmp;
-}
-
-/* 哨兵划分 */
-int partition(int[] nums, int left, int right)
-{
-    // 以 nums[left] 作为基准数
-    int i = left, j = right;
-    while (i < j)
-    {
-        while (i < j && nums[j] >= nums[left])
-            j--;          // 从右向左找首个小于基准数的元素
-        while (i < j && nums[i] <= nums[left])
-            i++;          // 从左向右找首个大于基准数的元素
-        swap(nums, i, j); // 交换这两个元素
-    }
-    swap(nums, i, left);  // 将基准数交换至两子数组的分界线
-    return i;             // 返回基准数的索引
-}
-
-
-
-
quick_sort.swift
/* 元素交换 */
-func swap(nums: inout [Int], i: Int, j: Int) {
-    let tmp = nums[i]
-    nums[i] = nums[j]
-    nums[j] = tmp
-}
-
-/* 哨兵划分 */
-func partition(nums: inout [Int], left: Int, right: Int) -> Int {
-    // 以 nums[left] 作为基准数
-    var i = left
-    var j = right
-    while i < j {
-        while i < j, nums[j] >= nums[left] {
-            j -= 1 // 从右向左找首个小于基准数的元素
-        }
-        while i < j, nums[i] <= nums[left] {
-            i += 1 // 从左向右找首个大于基准数的元素
-        }
-        swap(nums: &nums, i: i, j: j) // 交换这两个元素
-    }
-    swap(nums: &nums, i: i, j: left) // 将基准数交换至两子数组的分界线
-    return i // 返回基准数的索引
-}
-
-
-
-
quick_sort.zig

-
-
-
-
-
-

快速排序的分治思想

-

哨兵划分的实质是将 一个长数组的排序问题 简化为 两个短数组的排序问题

-
-

11.4.1. 算法流程

-
    -
  1. 首先,对数组执行一次「哨兵划分」,得到待排序的 左子数组右子数组
  2. -
  3. 接下来,对 左子数组右子数组 分别 递归执行「哨兵划分」……
  4. -
  5. 直至子数组长度为 1 时 终止递归,即可完成对整个数组的排序;
  6. -
-

观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。

-

quick_sort

-

Fig. 快速排序流程

- -
-
-
-
quick_sort.java
/* 快速排序 */
-void quickSort(int[] nums, int left, int right) {
-    // 子数组长度为 1 时终止递归
-    if (left >= right)
-        return;
-    // 哨兵划分
-    int pivot = partition(nums, left, right);
-    // 递归左子数组、右子数组
-    quickSort(nums, left, pivot - 1);
-    quickSort(nums, pivot + 1, right);
-}
-
-
-
-
quick_sort.cpp
/* 快速排序 */
-void quickSort(vector<int>& nums, int left, int right) {
-    // 子数组长度为 1 时终止递归
-    if (left >= right)
-        return;
-    // 哨兵划分
-    int pivot = partition(nums, left, right);
-    // 递归左子数组、右子数组
-    quickSort(nums, left, pivot - 1);
-    quickSort(nums, pivot + 1, right);
-}
-
-
-
-
quick_sort.py
""" 快速排序 """
-def quick_sort(self, nums, left, right):
-    # 子数组长度为 1 时终止递归
-    if left >= right:
-        return
-    # 哨兵划分
-    pivot = self.partition(nums, left, right)
-    # 递归左子数组、右子数组
-    self.quick_sort(nums, left, pivot - 1)
-    self.quick_sort(nums, pivot + 1, right)
-
-
-
-
quick_sort.go
/* 快速排序 */
-func quickSort(nums []int, left, right int) {
-    // 子数组长度为 1 时终止递归
-    if left >= right {
-        return
-    }
-    // 哨兵划分
-    pivot := partition(nums, left, right)
-    // 递归左子数组、右子数组
-    quickSort(nums, left, pivot-1)
-    quickSort(nums, pivot+1, right)
-}
-
-
-
-
quick_sort.js
/* 快速排序 */
-function quickSort(nums, left, right) {
-    // 子数组长度为 1 时终止递归
-    if (left >= right) return;
-    // 哨兵划分
-    const pivot = partition(nums, left, right);
-    // 递归左子数组、右子数组
-    quickSort(nums, left, pivot - 1);
-    quickSort(nums, pivot + 1, right);
-}
-
-
-
-
quick_sort.ts
/* 快速排序 */
-function quickSort(nums: number[], left: number, right: number): void {
-    // 子数组长度为 1 时终止递归
-    if (left >= right) {
-        return;
-    }
-    // 哨兵划分
-    const pivot = partition(nums, left, right);
-    // 递归左子数组、右子数组
-    quickSort(nums, left, pivot - 1);
-    quickSort(nums, pivot + 1, right);
-}
-
-
-
-
quick_sort.c

-
-
-
-
quick_sort.cs
/* 快速排序 */
-void quickSort(int[] nums, int left, int right)
-{
-    // 子数组长度为 1 时终止递归
-    if (left >= right)
-        return;
-    // 哨兵划分
-    int pivot = partition(nums, left, right);
-    // 递归左子数组、右子数组
-    quickSort(nums, left, pivot - 1);
-    quickSort(nums, pivot + 1, right);
-}
-
-
-
-
quick_sort.swift
/* 快速排序 */
-func quickSort(nums: inout [Int], left: Int, right: Int) {
-    // 子数组长度为 1 时终止递归
-    if left >= right {
-        return
-    }
-    // 哨兵划分
-    let pivot = partition(nums: &nums, left: left, right: right)
-    // 递归左子数组、右子数组
-    quickSort(nums: &nums, left: left, right: pivot - 1)
-    quickSort(nums: &nums, left: pivot + 1, right: right)
-}
-
-
-
-
quick_sort.zig

-
-
-
-
-

11.4.2. 算法特性

-

平均时间复杂度 \(O(n \log n)\) :平均情况下,哨兵划分的递归层数为 \(\log n\) ,每层中的总循环数为 \(n\) ,总体使用 \(O(n \log n)\) 时间。

-

最差时间复杂度 \(O(n^2)\) :最差情况下,哨兵划分操作将长度为 \(n\) 的数组划分为长度为 \(0\)\(n - 1\) 的两个子数组,此时递归层数达到 \(n\) 层,每层中的循环数为 \(n\) ,总体使用 \(O(n^2)\) 时间。

-

空间复杂度 \(O(n)\) :输入数组完全倒序下,达到最差递归深度 \(n\)

-

原地排序:只在递归中使用 \(O(\log n)\) 大小的栈帧空间。

-

非稳定排序:哨兵划分操作可能改变相等元素的相对位置。

-

自适应排序:最差情况下,时间复杂度劣化至 \(O(n^2)\)

-

11.4.3. 快排为什么快?

-

从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 效率更高,这是因为:

-
    -
  • 出现最差情况的概率很低:虽然快速排序的最差时间复杂度为 \(O(n^2)\) ,不如归并排序,但绝大部分情况下,快速排序可以达到 \(O(n \log n)\) 的复杂度。
  • -
  • 缓存使用效率高:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。
  • -
  • 复杂度的常数系数低:在提及的三种算法中,快速排序的 比较赋值交换 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。
  • -
-

11.4.4. 基准数优化

-

普通快速排序在某些输入下的时间效率变差。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 左子数组长度为 \(n - 1\)、右子数组长度为 \(0\) 。这样进一步递归下去,每轮哨兵划分后的右子数组长度都为 \(0\) ,分治策略失效,快速排序退化为「冒泡排序」了。

-

为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 随机选取一个元素作为基准数。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。

-

进一步地,我们可以在数组中选取 3 个候选元素(一般为数组的首、尾、中点元素),并将三个候选元素的中位数作为基准数,这样基准数“既不大也不小”的概率就大大提升了。当然,如果数组很长的话,我们也可以选取更多候选元素,来进一步提升算法的稳健性。采取该方法后,时间复杂度劣化至 \(O(n^2)\) 的概率极低。

-
-
-
-
quick_sort.java
/* 选取三个元素的中位数 */
-int medianThree(int[] nums, int left, int mid, int right) {
-    // 使用了异或操作来简化代码
-    // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
-    if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))
-        return left;
-    else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))
-        return mid;
-    else
-        return right;
-}
-
-/* 哨兵划分(三数取中值) */
-int partition(int[] nums, int left, int right) {
-    // 选取三个候选元素的中位数
-    int med = medianThree(nums, left, (left + right) / 2, right);
-    // 将中位数交换至数组最左端
-    swap(nums, left, med);
-    // 以 nums[left] 作为基准数
-    int i = left, j = right;
-    while (i < j) {
-        while (i < j && nums[j] >= nums[left])
-            j--;          // 从右向左找首个小于基准数的元素
-        while (i < j && nums[i] <= nums[left])
-            i++;          // 从左向右找首个大于基准数的元素
-        swap(nums, i, j); // 交换这两个元素
-    }
-    swap(nums, i, left);  // 将基准数交换至两子数组的分界线
-    return i;             // 返回基准数的索引
-}
-
-
-
-
quick_sort.cpp
/* 选取三个元素的中位数 */
-int medianThree(vector<int>& nums, int left, int mid, int right) {
-    // 使用了异或操作来简化代码
-    // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
-    if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))
-        return left;
-    else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))
-        return mid;
-    else
-        return right;
-}
-
-/* 哨兵划分(三数取中值) */
-int partition(vector<int>& nums, int left, int right) {
-    // 选取三个候选元素的中位数
-    int med = medianThree(nums, left, (left + right) / 2, right);
-    // 将中位数交换至数组最左端
-    swap(nums, left, med);
-    // 以 nums[left] 作为基准数
-    // 下同省略...
-}
-
-
-
-
quick_sort.py
""" 选取三个元素的中位数 """
-def median_three(self, nums, left, mid, right):
-    # 使用了异或操作来简化代码
-    # 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
-    if (nums[left] < nums[mid]) ^ (nums[left] < nums[right]):
-        return left
-    elif (nums[mid] < nums[left]) ^ (nums[mid] > nums[right]):
-        return mid
-    return right
-
-""" 哨兵划分(三数取中值) """
-def partition(self, nums, left, right):
-    # 以 nums[left] 作为基准数
-    med = self.median_three(nums, left, (left + right) // 2, right)
-    # 将中位数交换至数组最左端
-    nums[left], nums[med] = nums[med], nums[left]
-    # 以 nums[left] 作为基准数
-    i, j = left, right
-    while i < j:
-        while i < j and nums[j] >= nums[left]:
-            j -= 1  # 从右向左找首个小于基准数的元素
-        while i < j and nums[i] <= nums[left]:
-            i += 1  # 从左向右找首个大于基准数的元素
-        # 元素交换
-        nums[i], nums[j] = nums[j], nums[i]
-    # 将基准数交换至两子数组的分界线
-    nums[i], nums[left] = nums[left], nums[i]
-    return i  # 返回基准数的索引
-
-
-
-
quick_sort.go
/* 选取三个元素的中位数 */
-func medianThree(nums []int, left, mid, right int) int {
-    if (nums[left] < nums[mid]) != (nums[left] < nums[right]) {
-        return left
-    } else if (nums[mid] > nums[left]) != (nums[mid] > nums[right]) {
-        return mid
-    }
-    return right
-}
-
-/* 哨兵划分(三数取中值)*/
-func partition(nums []int, left, right int) int {
-    // 以 nums[left] 作为基准数
-    med := medianThree(nums, left, (left+right)/2, right)
-    // 将中位数交换至数组最左端
-    nums[left], nums[med] = nums[med], nums[left]
-    // 以 nums[left] 作为基准数
-    // 下同省略...
-}
-
-
-
-
quick_sort.js
/* 选取三个元素的中位数 */
-function medianThree(nums, left, mid, right) {
-    // 使用了异或操作来简化代码
-    // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
-    if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))
-        return left;
-    else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))
-        return mid;
-    else
-        return right;
-}
-
-/* 哨兵划分(三数取中值) */
-function partition(nums, left, right) {
-    // 选取三个候选元素的中位数
-    let med = medianThree(nums, left, Math.floor((left + right) / 2), right);
-    // 将中位数交换至数组最左端
-    swap(nums, left, med);
-    // 以 nums[left] 作为基准数
-    // 下同省略...
-}
-
-
-
-
quick_sort.ts
/* 选取三个元素的中位数 */
-function medianThree(nums: number[], left: number, mid: number, right: number): number {
-    // 使用了异或操作来简化代码
-    // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
-    if (Number(nums[left] < nums[mid]) ^ Number(nums[left] < nums[right])) {
-        return left;
-    } else if (Number(nums[mid] < nums[left]) ^ Number(nums[mid] < nums[right])) {
-        return mid;
-    } else {
-        return right;
-    }
-}
-
-/* 哨兵划分(三数取中值) */
-function partition(nums: number[], left: number, right: number): number {
-    // 选取三个候选元素的中位数
-    let med = medianThree(nums, left, Math.floor((left + right) / 2), right);
-    // 将中位数交换至数组最左端
-    swap(nums, left, med);
-    // 以 nums[left] 作为基准数
-    // 下同省略...
-
-
-
-
quick_sort.c

-
-
-
-
quick_sort.cs
/* 选取三个元素的中位数 */
-int medianThree(int[] nums, int left, int mid, int right)
-{
-    // 使用了异或操作来简化代码
-    // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1
-    if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))
-        return left;
-    else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))
-        return mid;
-    else
-        return right;
-}
-
-/* 哨兵划分(三数取中值) */
-int partition(int[] nums, int left, int right)
-{
-    // 选取三个候选元素的中位数
-    int med = medianThree(nums, left, (left + right) / 2, right);
-    // 将中位数交换至数组最左端
-    swap(nums, left, med);
-    // 以 nums[left] 作为基准数
-    // 下同省略...
-}
-
-
-
-
quick_sort.swift
/* 选取三个元素的中位数 */
-func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {
-    if (nums[left] < nums[mid]) != (nums[left] < nums[right]) {
-        return left
-    } else if (nums[mid] < nums[left]) != (nums[mid] < nums[right]) {
-        return mid
-    } else {
-        return right
-    }
-}
-
-/* 哨兵划分(三数取中值) */
-func partition(nums: inout [Int], left: Int, right: Int) -> Int {
-    // 选取三个候选元素的中位数
-    let med = medianThree(nums: nums, left: left, mid: (left + right) / 2, right: right)
-    // 将中位数交换至数组最左端
-    swap(nums: &nums, i: left, j: med)
-    // 以 nums[left] 作为基准数
-    // 下同省略...
-}
-
-
-
-
quick_sort.zig

-
-
-
-
-

11.4.5. 尾递归优化

-

普通快速排序在某些输入下的空间效率变差。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 \(n - 1\) 的递归树,此时使用的栈帧空间大小劣化至 \(O(n)\)

-

为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短的子数组长度不会超过 \(\frac{n}{2}\) ,因此这样做能保证递归深度不超过 \(\log n\) ,即最差空间复杂度被优化至 \(O(\log n)\)

-
-
-
-
quick_sort.java
/* 快速排序(尾递归优化) */
-void quickSort(int[] nums, int left, int right) {
-    // 子数组长度为 1 时终止
-    while (left < right) {
-        // 哨兵划分操作
-        int pivot = partition(nums, left, right);
-        // 对两个子数组中较短的那个执行快排
-        if (pivot - left < right - pivot) {
-            quickSort(nums, left, pivot - 1);  // 递归排序左子数组
-            left = pivot + 1;  // 剩余待排序区间为 [pivot + 1, right]
-        } else {
-            quickSort(nums, pivot + 1, right); // 递归排序右子数组
-            right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.cpp
/* 快速排序(尾递归优化) */
-void quickSort(vector<int>& nums, int left, int right) {
-    // 子数组长度为 1 时终止
-    while (left < right) {
-        // 哨兵划分操作
-        int pivot = partition(nums, left, right);
-        // 对两个子数组中较短的那个执行快排
-        if (pivot - left < right - pivot) {
-            quickSort(nums, left, pivot - 1);  // 递归排序左子数组
-            left = pivot + 1;  // 剩余待排序区间为 [pivot + 1, right]
-        } else {
-            quickSort(nums, pivot + 1, right); // 递归排序右子数组
-            right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.py
""" 快速排序(尾递归优化) """
-def quick_sort(self, nums, left, right):
-    # 子数组长度为 1 时终止
-    while left < right:
-        # 哨兵划分操作
-        pivot = self.partition(nums, left, right)
-        # 对两个子数组中较短的那个执行快排
-        if pivot - left < right - pivot:
-            self.quick_sort(nums, left, pivot - 1)  # 递归排序左子数组
-            left = pivot + 1     # 剩余待排序区间为 [pivot + 1, right]
-        else:
-            self.quick_sort(nums, pivot + 1, right)  # 递归排序右子数组
-            right = pivot - 1    # 剩余待排序区间为 [left, pivot - 1]
-
-
-
-
quick_sort.go
/* 快速排序(尾递归优化)*/
-func quickSort(nums []int, left, right int) {
-    // 子数组长度为 1 时终止
-    for left < right {
-        // 哨兵划分操作
-        pivot := partition(nums, left, right)
-        // 对两个子数组中较短的那个执行快排
-        if pivot-left < right-pivot {
-            quickSort(nums, left, pivot-1)   // 递归排序左子数组
-            left = pivot + 1                 // 剩余待排序区间为 [pivot + 1, right]
-        } else {
-            quickSort(nums, pivot+1, right)  // 递归排序右子数组
-            right = pivot - 1                // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.js
/* 快速排序(尾递归优化) */
-function quickSort(nums, left, right) {
-    // 子数组长度为 1 时终止
-    while (left < right) {
-        // 哨兵划分操作
-        let pivot = partition(nums, left, right);
-        // 对两个子数组中较短的那个执行快排
-        if (pivot - left < right - pivot) {
-            quickSort(nums, left, pivot - 1);  // 递归排序左子数组
-            left = pivot + 1;  // 剩余待排序区间为 [pivot + 1, right]
-        } else {
-            quickSort(nums, pivot + 1, right); // 递归排序右子数组
-            right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.ts
/* 快速排序(尾递归优化) */
-function quickSort(nums: number[], left: number, right: number): void {
-    // 子数组长度为 1 时终止
-    while (left < right) {
-        // 哨兵划分操作
-        let pivot = partition(nums, left, right);
-        // 对两个子数组中较短的那个执行快排
-        if (pivot - left < right - pivot) {
-            quickSort(nums, left, pivot - 1); // 递归排序左子数组
-            left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right]
-        } else {
-            quickSort(nums, pivot + 1, right); // 递归排序右子数组
-            right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.c

-
-
-
-
quick_sort.cs
/* 快速排序(尾递归优化) */
-void quickSort(int[] nums, int left, int right)
-{
-    // 子数组长度为 1 时终止
-    while (left < right)
-    {
-        // 哨兵划分操作
-        int pivot = partition(nums, left, right);
-        // 对两个子数组中较短的那个执行快排
-        if (pivot - left < right - pivot)
-        {
-            quickSort(nums, left, pivot - 1);  // 递归排序左子数组
-            left = pivot + 1;  // 剩余待排序区间为 [pivot + 1, right]
-        }
-        else
-        {
-            quickSort(nums, pivot + 1, right); // 递归排序右子数组
-            right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.swift
/* 快速排序(尾递归优化) */
-func quickSort(nums: inout [Int], left: Int, right: Int) {
-    var left = left
-    var right = right
-    // 子数组长度为 1 时终止
-    while left < right {
-        // 哨兵划分操作
-        let pivot = partition(nums: &nums, left: left, right: right)
-        // 对两个子数组中较短的那个执行快排
-        if (pivot - left) < (right - pivot) {
-            quickSort(nums: &nums, left: left, right: pivot - 1) // 递归排序左子数组
-            left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right]
-        } else {
-            quickSort(nums: &nums, left: pivot + 1, right: right) // 递归排序右子数组
-            right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1]
-        }
-    }
-}
-
-
-
-
quick_sort.zig

-
-
-
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_sorting/summary/index.html b/site/chapter_sorting/summary/index.html deleted file mode 100644 index 1ec39ecfc..000000000 --- a/site/chapter_sorting/summary/index.html +++ /dev/null @@ -1,1679 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11.6. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

11.6. 小结

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_stack_and_queue/deque.assets/deque_operations.png b/site/chapter_stack_and_queue/deque.assets/deque_operations.png deleted file mode 100644 index 45ee775b4..000000000 Binary files a/site/chapter_stack_and_queue/deque.assets/deque_operations.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque.png b/site/chapter_stack_and_queue/deque.assets/linkedlist_deque.png deleted file mode 100644 index 2337972f7..000000000 Binary files a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_poll_first.png b/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_poll_first.png deleted file mode 100644 index db3d9a3ce..000000000 Binary files a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_poll_first.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_poll_last.png b/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_poll_last.png deleted file mode 100644 index c881168b5..000000000 Binary files a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_poll_last.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_push_first.png b/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_push_first.png deleted file mode 100644 index d1cfaeb9b..000000000 Binary files a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_push_first.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_push_last.png b/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_push_last.png deleted file mode 100644 index 408990870..000000000 Binary files a/site/chapter_stack_and_queue/deque.assets/linkedlist_deque_push_last.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/deque/index.html b/site/chapter_stack_and_queue/deque/index.html deleted file mode 100644 index 5950ebe86..000000000 --- a/site/chapter_stack_and_queue/deque/index.html +++ /dev/null @@ -1,2508 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5.3. 双向队列(Deque) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

5.3. 双向队列

-

对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。

-

deque_operations

-

Fig. 双向队列的操作

- -

5.3.1. 双向队列常用操作

-

双向队列的常用操作见下表,方法名需根据特定语言来确定。

-

Table. 双向队列的常用操作

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
方法名描述时间复杂度
pushFirst()将元素添加至队首\(O(1)\)
pushLast()将元素添加至队尾\(O(1)\)
pollFirst()删除队首元素\(O(1)\)
pollLast()删除队尾元素\(O(1)\)
peekFirst()访问队首元素\(O(1)\)
peekLast()访问队尾元素\(O(1)\)
size()获取队列的长度\(O(1)\)
isEmpty()判断队列是否为空\(O(1)\)
-
-

相同地,我们可以直接使用编程语言实现好的双向队列类。

-
-
-
-
deque.java
/* 初始化双向队列 */
-Deque<Integer> deque = new LinkedList<>();
-
-/* 元素入队 */
-deque.offerLast(2);   // 添加至队尾
-deque.offerLast(5);
-deque.offerLast(4);
-deque.offerFirst(3);  // 添加至队首
-deque.offerFirst(1);
-
-/* 访问元素 */
-int peekFirst = deque.peekFirst();  // 队首元素
-int peekLast = deque.peekLast();    // 队尾元素
-
-/* 元素出队 */
-int pollFirst = deque.pollFirst();  // 队首元素出队
-int pollLast = deque.pollLast();    // 队尾元素出队
-
-/* 获取双向队列的长度 */
-int size = deque.size();
-
-/* 判断双向队列是否为空 */
-boolean isEmpty = deque.isEmpty();
-
-
-
-
deque.cpp
/* 初始化双向队列 */
-deque<int> deque;
-
-/* 元素入队 */
-deque.push_back(2);   // 添加至队尾
-deque.push_back(5);
-deque.push_back(4);
-deque.push_front(3);  // 添加至队首
-deque.push_front(1);
-
-/* 访问元素 */
-int front = deque.front(); // 队首元素
-int back = deque.back();   // 队尾元素
-
-/* 元素出队 */
-deque.pop_front();  // 队首元素出队
-deque.pop_back();   // 队尾元素出队
-
-/* 获取双向队列的长度 */
-int size = deque.size();
-
-/* 判断双向队列是否为空 */
-bool empty = deque.empty();
-
-
-
-
deque.py
""" 初始化双向队列 """
-duque = deque()
-
-""" 元素入队 """
-duque.append(2)      # 添加至队尾
-duque.append(5)
-duque.append(4)
-duque.appendleft(3)  # 添加至队首
-duque.appendleft(1)
-
-""" 访问元素 """
-front = duque[0]  # 队首元素
-rear = duque[-1]  # 队尾元素
-
-""" 元素出队 """
-pop_front = duque.popleft()  # 队首元素出队
-pop_rear = duque.pop()       # 队尾元素出队
-
-""" 获取双向队列的长度 """
-size = len(duque)
-
-""" 判断双向队列是否为空 """
-is_empty = len(duque) == 0
-
-
-
-
deque_test.go
/* 初始化双向队列 */
-// 在 Go 中,将 list 作为双向队列使用
-deque := list.New()
-
-/* 元素入队 */
-deque.PushBack(2)      // 添加至队尾
-deque.PushBack(5)
-deque.PushBack(4)
-deque.PushFront(3)     // 添加至队首
-deque.PushFront(1)
-
-/* 访问元素 */
-front := deque.Front() // 队首元素
-rear := deque.Back()   // 队尾元素
-
-/* 元素出队 */
-deque.Remove(front)    // 队首元素出队
-deque.Remove(rear)     // 队尾元素出队
-
-/* 获取双向队列的长度 */
-size := deque.Len()
-
-/* 判断双向队列是否为空 */
-isEmpty := deque.Len() == 0
-
-
-
-
deque.js
/* 初始化双向队列 */
-// JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用
-const deque = [];
-
-/* 元素入队 */
-deque.push(2);
-deque.push(5);
-deque.push(4);
-// 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n)
-deque.unshift(3);
-deque.unshift(1);
-console.log("双向队列 deque = ", deque);
-
-/* 访问元素 */
-const peekFirst = deque[0];
-console.log("队首元素 peekFirst = " + peekFirst);
-const peekLast = deque[deque.length - 1];
-console.log("队尾元素 peekLast = " + peekLast);
-
-/* 元素出队 */
-// 请注意,由于是数组,shift() 方法的时间复杂度为 O(n)
-const popFront = deque.shift();
-console.log("队首出队元素 popFront = " + popFront + ",队首出队后 deque = " + deque);
-const popBack = deque.pop();
-console.log("队尾出队元素 popBack = " + popBack + ",队尾出队后 deque = " + deque);
-
-/* 获取双向队列的长度 */
-const size = deque.length;
-console.log("双向队列长度 size = " + size);
-
-/* 判断双向队列是否为空 */
-const isEmpty = size === 0;
-console.log("双向队列是否为空 = " + isEmpty);
-
-
-
-
deque.ts
/* 初始化双向队列 */
-// TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用
-const deque: number[] = [];
-
-/* 元素入队 */
-deque.push(2);
-deque.push(5);
-deque.push(4);
-// 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n)
-deque.unshift(3);
-deque.unshift(1);
-console.log("双向队列 deque = ", deque);
-
-/* 访问元素 */
-const peekFirst: number = deque[0];
-console.log("队首元素 peekFirst = " + peekFirst);
-const peekLast: number = deque[deque.length - 1];
-console.log("队尾元素 peekLast = " + peekLast);
-
-/* 元素出队 */
-// 请注意,由于是数组,shift() 方法的时间复杂度为 O(n)
-const popFront: number = deque.shift() as number;
-console.log("队首出队元素 popFront = " + popFront + ",队首出队后 deque = " + deque);
-const popBack: number = deque.pop() as number;
-console.log("队尾出队元素 popBack = " + popBack + ",队尾出队后 deque = " + deque);
-
-/* 获取双向队列的长度 */
-const size: number = deque.length;
-console.log("双向队列长度 size = " + size);
-
-/* 判断双向队列是否为空 */
-const isEmpty: boolean = size === 0;
-console.log("双向队列是否为空 = " + isEmpty);
-
-
-
-
deque.c

-
-
-
-
deque.cs
/* 初始化双向队列 */
-// 在 C# 中,将链表 LinkedList 看作双向队列来使用
-LinkedList<int> deque = new LinkedList<int>();
-
-/* 元素入队 */
-deque.AddLast(2);   // 添加至队尾
-deque.AddLast(5);
-deque.AddLast(4);
-deque.AddFirst(3);  // 添加至队首
-deque.AddFirst(1);
-
-/* 访问元素 */
-int peekFirst = deque.First.Value;  // 队首元素
-int peekLast = deque.Last.Value;    // 队尾元素
-
-/* 元素出队 */
-deque.RemoveFirst();  // 队首元素出队
-deque.RemoveLast();   // 队尾元素出队
-
-/* 获取双向队列的长度 */
-int size = deque.Count;
-
-/* 判断双向队列是否为空 */
-bool isEmpty = deque.Count == 0;
-
-
-
-
deque.swift
/* 初始化双向队列 */
-// Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用
-var deque: [Int] = []
-
-/* 元素入队 */
-deque.append(2) // 添加至队尾
-deque.append(5)
-deque.append(4)
-deque.insert(3, at: 0) // 添加至队首
-deque.insert(1, at: 0)
-
-/* 访问元素 */
-let peekFirst = deque.first! // 队首元素
-let peekLast = deque.last! // 队尾元素
-
-/* 元素出队 */
-// 使用 Array 模拟时 pollFirst 的复杂度为 O(n)
-let pollFirst = deque.removeFirst() // 队首元素出队
-let pollLast = deque.removeLast() // 队尾元素出队
-
-/* 获取双向队列的长度 */
-let size = deque.count
-
-/* 判断双向队列是否为空 */
-let isEmpty = deque.isEmpty
-
-
-
-
deque.zig

-
-
-
-
-

5.3.2. 双向队列实现

-

双向队列需要一种可以在两端添加、两端删除的数据结构。与队列的实现方法类似,双向队列也可以使用双向链表和循环数组来实现。

-

基于双向链表的实现

-

我们将双向链表的头结点和尾结点分别看作双向队列的队首和队尾,并且实现在两端都能添加与删除结点。

-
-
-
-

linkedlist_deque

-
-
-

linkedlist_deque_push_last

-
-
-

linkedlist_deque_push_first

-
-
-

linkedlist_deque_poll_last

-
-
-

linkedlist_deque_poll_first

-
-
-
-

以下是使用双向链表实现双向队列的示例代码。

-
-
-
-
linkedlist_deque.java
/* 双向链表结点 */
-class ListNode {
-    int val;       // 结点值
-    ListNode next; // 后继结点引用(指针)
-    ListNode prev; // 前驱结点引用(指针)
-    ListNode(int val) {
-        this.val = val;
-        prev = next = null;
-    }
-}
-
-/* 基于双向链表实现的双向队列 */
-class LinkedListDeque {
-    private ListNode front, rear; // 头结点 front ,尾结点 rear
-    private int size = 0;         // 双向队列的长度
-
-    public LinkedListDeque() {
-        front = rear = null;
-    }
-
-    /* 获取双向队列的长度 */
-    public int size() {
-        return size;
-    }
-
-    /* 判断双向队列是否为空 */
-    public boolean isEmpty() {
-        return size() == 0;
-    }
-
-    /* 入队操作 */
-    private void push(int num, boolean isFront) {
-        ListNode node = new ListNode(num);
-        // 若链表为空,则令 front, rear 都指向 node
-        if (isEmpty())
-            front = rear = node;
-        // 队首入队操作
-        else if (isFront) {
-            // 将 node 添加至链表头部
-            front.prev = node;
-            node.next = front;
-            front = node; // 更新头结点
-        // 队尾入队操作
-        } else {
-            // 将 node 添加至链表尾部
-            rear.next = node;
-            node.prev = rear;
-            rear = node;  // 更新尾结点
-        }
-        size++; // 更新队列长度
-    }
-
-    /* 队首入队 */
-    public void pushFirst(int num) {
-        push(num, true);
-    }
-
-    /* 队尾入队 */
-    public void pushLast(int num) {
-        push(num, false);
-    }
-
-    /* 出队操作 */
-    private Integer poll(boolean isFront) {
-        // 若队列为空,直接返回 null
-        if (isEmpty())
-            return null;
-        int val;
-        // 队首出队操作
-        if (isFront) {
-            val = front.val; // 暂存头结点值
-            // 删除头结点
-            ListNode fNext = front.next;
-            if (fNext != null) {
-                fNext.prev = null;
-                front.next = null;
-            }
-            front = fNext;   // 更新头结点
-        // 队尾出队操作
-        } else {
-            val = rear.val;  // 暂存尾结点值
-            // 删除尾结点
-            ListNode rPrev = rear.prev;
-            if (rPrev != null) {
-                rPrev.next = null;
-                rear.prev = null;
-            }
-            rear = rPrev;    // 更新尾结点
-        }
-        size--; // 更新队列长度
-        return val;
-    }
-
-    /* 队首出队 */
-    public Integer pollFirst() {
-        return poll(true);
-    }
-
-    /* 队尾出队 */
-    public Integer pollLast() {
-        return poll(false);
-    }
-
-    /* 访问队首元素 */
-    public Integer peekFirst() {
-        return isEmpty() ? null : front.val;
-    }
-
-    /* 访问队尾元素 */
-    public Integer peekLast() {
-        return isEmpty() ? null : rear.val;
-    }
-
-    /* 打印双向队列 */
-    public void print() {
-        if (isEmpty()) {
-            System.out.println("[ ]");
-            return;
-        }
-        List<String> list = new ArrayList<>();
-        ListNode head = front;
-        while (head != null) {
-            list.add(String.valueOf(head.val));
-            head = head.next;
-        }
-        System.out.println("[" + String.join(", ", list) + "]");
-    }
-}
-
-
-
-
linkedlist_deque.cpp

-
-
-
-
linkedlist_deque.py

-
-
-
-
linkedlist_deque.go

-
-
-
-
linkedlist_deque.js
/* 双向链表结点 */
-class ListNode {
-    prev;   // 前驱结点引用 (指针)
-    next;   // 后继结点引用 (指针)
-    val;    // 结点值
-
-    constructor(val) {
-        this.val = val;
-        this.next = null;
-        this.prev = null;
-    }
-}
-
-/* 基于双向链表实现的双向队列 */
-class LinkedListDeque {
-    front;  // 头结点 front
-    rear;   // 尾结点 rear
-    len;    // 双向队列的长度
-
-    constructor() {
-        this.front = null;
-        this.rear = null;
-        this.len = 0;
-    }
-
-    /* 队尾入队操作 */
-    pushLast(val) {
-        const node = new ListNode(val);
-        // 若链表为空,则令 front, rear 都指向 node
-        if (this.len === 0) {
-            this.front = node;
-            this.rear = node;
-        } else {
-            // 将 node 添加至链表尾部
-            this.rear.next = node;
-            node.prev = this.rear;
-            this.rear = node; // 更新尾结点
-        }
-        this.len++;
-    }
-
-    /* 队首入队操作 */
-    pushFirst(val) {
-        const node = new ListNode(val);
-        // 若链表为空,则令 front, rear 都指向 node
-        if (this.len === 0) {
-            this.front = node;
-            this.rear = node;
-        } else {
-            // 将 node 添加至链表头部
-            this.front.prev = node;
-            node.next = this.front;
-            this.front = node; // 更新头结点
-        }
-        this.len++;
-    }
-
-    /* 队尾出队操作 */
-    pollLast() {
-        if (this.len === 0) {
-            return null;
-        }
-        const value = this.rear.val; // 存储尾结点值
-        // 删除尾结点
-        let temp = this.rear.prev;
-        if (temp !== null) {
-            temp.next = null;
-            this.rear.prev = null;
-        }
-        this.rear = temp;   // 更新尾结点
-        this.len--;
-        return value;
-    }
-
-    /* 队首出队操作 */
-    pollFirst() {
-        if (this.len === 0) {
-            return null;
-        }
-        const value = this.front.val; // 存储尾结点值
-        // 删除头结点
-        let temp = this.front.next;
-        if (temp !== null) {
-            temp.prev = null;
-            this.front.next = null;
-        }
-        this.front = temp;   // 更新头结点
-        this.len--;
-        return value;
-    }
-
-    /* 访问队尾元素 */
-    peekLast() {
-        return this.len === 0 ? null : this.rear.val;
-    }
-
-    /* 访问队首元素 */
-    peekFirst() {
-        return this.len === 0 ? null : this.front.val;
-    }
-
-    /* 获取双向队列的长度 */
-    size() {
-        return this.len;
-    }
-
-    /* 判断双向队列是否为空 */
-    isEmpty() {
-        return this.len === 0;
-    }
-
-    /* 打印双向队列 */
-    print() {
-        const arr = [];
-        let temp = this.front;
-        while (temp !== null) {
-            arr.push(temp.val);
-            temp = temp.next;
-        }
-        console.log("[" + arr.join(", ") + "]");
-    }
-}
-
-
-
-
linkedlist_deque.ts
/* 双向链表结点 */
-class ListNode {
-    prev: ListNode;     // 前驱结点引用 (指针)
-    next: ListNode;     // 后继结点引用 (指针)
-    val: number;        // 结点值
-
-    constructor(val: number) {
-        this.val = val;
-        this.next = null;
-        this.prev = null;
-    }
-}
-
-/* 基于双向链表实现的双向队列 */
-class LinkedListDeque {
-    front: ListNode;    // 头结点 front
-    rear: ListNode;     // 尾结点 rear
-    len: number;        // 双向队列的长度
-
-    constructor() {
-        this.front = null;
-        this.rear = null;
-        this.len = 0;
-    }
-
-    /* 队尾入队操作 */
-    pushLast(val: number): void {
-        const node: ListNode = new ListNode(val);
-        // 若链表为空,则令 front, rear 都指向 node
-        if (this.len === 0) {
-            this.front = node;
-            this.rear = node;
-        } else {
-            // 将 node 添加至链表尾部
-            this.rear.next = node;
-            node.prev = this.rear;
-            this.rear = node; // 更新尾结点
-        }
-        this.len++;
-    }
-
-    /* 队首入队操作 */
-    pushFirst(val: number): void {
-        const node: ListNode = new ListNode(val);
-        // 若链表为空,则令 front, rear 都指向 node
-        if (this.len === 0) {
-            this.front = node;
-            this.rear = node;
-        } else {
-            // 将 node 添加至链表头部
-            this.front.prev = node;
-            node.next = this.front;
-            this.front = node; // 更新头结点
-        }
-        this.len++;
-    }
-
-    /* 队尾出队操作 */
-    pollLast(): number {
-        if (this.len === 0) {
-            return null;
-        }
-        const value: number = this.rear.val; // 存储尾结点值
-        // 删除尾结点
-        let temp: ListNode = this.rear.prev;
-        if (temp !== null) {
-            temp.next = null;
-            this.rear.prev = null;
-        }
-        this.rear = temp;   // 更新尾结点
-        this.len--;
-        return value;
-    }
-
-    /* 队首出队操作 */
-    pollFirst(): number {
-        if (this.len === 0) {
-            return null;
-        }
-        const value: number = this.front.val; // 存储尾结点值
-        // 删除头结点
-        let temp: ListNode = this.front.next;
-        if (temp !== null) {
-            temp.prev = null;
-            this.front.next = null;
-        }
-        this.front = temp;   // 更新头结点
-        this.len--;
-        return value;
-    }
-
-    /* 访问队尾元素 */
-    peekLast(): number {
-        return this.len === 0 ? null : this.rear.val;
-    }
-
-    /* 访问队首元素 */
-    peekFirst(): number {
-        return this.len === 0 ? null : this.front.val;
-    }
-
-    /* 获取双向队列的长度 */
-    size(): number {
-        return this.len;
-    }
-
-    /* 判断双向队列是否为空 */
-    isEmpty(): boolean {
-        return this.len === 0;
-    }
-
-    /* 打印双向队列 */
-    print(): void {
-        const arr: number[] = [];
-        let temp: ListNode = this.front;
-        while (temp !== null) {
-            arr.push(temp.val);
-            temp = temp.next;
-        }
-        console.log("[" + arr.join(", ") + "]");
-    }
-}
-
-
-
-
linkedlist_deque.c

-
-
-
-
linkedlist_deque.cs

-
-
-
-
linkedlist_deque.swift

-
-
-
-
linkedlist_deque.zig

-
-
-
-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_stack_and_queue/queue.assets/array_queue.png b/site/chapter_stack_and_queue/queue.assets/array_queue.png deleted file mode 100644 index afbecd915..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/array_queue.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue.assets/array_queue_poll.png b/site/chapter_stack_and_queue/queue.assets/array_queue_poll.png deleted file mode 100644 index 9a3c1087c..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/array_queue_poll.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue.assets/array_queue_push.png b/site/chapter_stack_and_queue/queue.assets/array_queue_push.png deleted file mode 100644 index cd164441d..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/array_queue_push.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue.assets/linkedlist_queue.png b/site/chapter_stack_and_queue/queue.assets/linkedlist_queue.png deleted file mode 100644 index cebfea13f..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/linkedlist_queue.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue.assets/linkedlist_queue_poll.png b/site/chapter_stack_and_queue/queue.assets/linkedlist_queue_poll.png deleted file mode 100644 index 83a102a5e..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/linkedlist_queue_poll.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue.assets/linkedlist_queue_push.png b/site/chapter_stack_and_queue/queue.assets/linkedlist_queue_push.png deleted file mode 100644 index 3e1075593..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/linkedlist_queue_push.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue.assets/queue_operations.png b/site/chapter_stack_and_queue/queue.assets/queue_operations.png deleted file mode 100644 index ad6347be8..000000000 Binary files a/site/chapter_stack_and_queue/queue.assets/queue_operations.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/queue/index.html b/site/chapter_stack_and_queue/queue/index.html deleted file mode 100644 index 63fe90ab6..000000000 --- a/site/chapter_stack_and_queue/queue/index.html +++ /dev/null @@ -1,3089 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5.2. 队列(Queue) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
- -
-
- - - -
-
- - - - - - - - - - - - - -

5.2. 队列

-

「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。

-

我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。

-

queue_operations

-

Fig. 队列的先入先出特性

- -

5.2.1. 队列常用操作

-

队列的常用操作见下表,方法名需根据特定语言来确定。

-

Table. 队列的常用操作

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
方法名描述时间复杂度
push()元素入队,即将元素添加至队尾\(O(1)\)
poll()队首元素出队\(O(1)\)
front()访问队首元素\(O(1)\)
size()获取队列的长度\(O(1)\)
isEmpty()判断队列是否为空\(O(1)\)
-
-

我们可以直接使用编程语言实现好的队列类。

-
-
-
-
queue.java
/* 初始化队列 */
-Queue<Integer> queue = new LinkedList<>();
-
-/* 元素入队 */
-queue.offer(1);
-queue.offer(3);
-queue.offer(2);
-queue.offer(5);
-queue.offer(4);
-
-/* 访问队首元素 */
-int peek = queue.peek();
-
-/* 元素出队 */
-int poll = queue.poll();
-
-/* 获取队列的长度 */
-int size = queue.size();
-
-/* 判断队列是否为空 */
-boolean isEmpty = queue.isEmpty();
-
-
-
-
queue.cpp
/* 初始化队列 */
-queue<int> queue;
-
-/* 元素入队 */
-queue.push(1);
-queue.push(3);
-queue.push(2);
-queue.push(5);
-queue.push(4);
-
-/* 访问队首元素 */
-int front = queue.front();
-
-/* 元素出队 */
-queue.pop();
-
-/* 获取队列的长度 */
-int size = queue.size();
-
-/* 判断队列是否为空 */
-bool empty = queue.empty();
-
-
-
-
queue.py
""" 初始化队列 """
-# 在 Python 中,我们一般将双向队列类 deque 看作队列使用
-# 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议
-que = collections.deque()
-
-""" 元素入队 """
-que.append(1)
-que.append(3)
-que.append(2)
-que.append(5)
-que.append(4)
-
-""" 访问队首元素 """
-front = que[0];
-
-""" 元素出队 """
-pop = que.popleft()
-
-""" 获取队列的长度 """
-size = len(que)
-
-""" 判断队列是否为空 """
-is_empty = len(que) == 0
-
-
-
-
queue_test.go
/* 初始化队列 */
-// 在 Go 中,将 list 作为队列来使用
-queue := list.New()
-
-/* 元素入队 */
-queue.PushBack(1)
-queue.PushBack(3)
-queue.PushBack(2)
-queue.PushBack(5)
-queue.PushBack(4)
-
-/* 访问队首元素 */
-peek := queue.Front()
-
-/* 元素出队 */
-poll := queue.Front()
-queue.Remove(poll)
-
-/* 获取队列的长度 */
-size := queue.Len()
-
-/* 判断队列是否为空 */
-isEmpty := queue.Len() == 0
-
-
-
-
queue.js
/* 初始化队列 */
-// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
-const queue = [];
-
-/* 元素入队 */
-queue.push(1);
-queue.push(3);
-queue.push(2);
-queue.push(5);
-queue.push(4);
-
-/* 访问队首元素 */
-const peek = queue[0];
-
-/* 元素出队 */
-// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
-const poll = queue.shift();
-
-/* 获取队列的长度 */
-const size = queue.length;
-
-/* 判断队列是否为空 */
-const empty = queue.length === 0;
-
-
-
-
queue.ts
/* 初始化队列 */
-// TypeScript 没有内置的队列,可以把 Array 当作队列来使用 
-const queue: number[] = [];
-
-/* 元素入队 */
-queue.push(1);
-queue.push(3);
-queue.push(2);
-queue.push(5);
-queue.push(4);
-
-/* 访问队首元素 */
-const peek = queue[0];
-
-/* 元素出队 */
-// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
-const poll = queue.shift();
-
-/* 获取队列的长度 */
-const size = queue.length;
-
-/* 判断队列是否为空 */
-const empty = queue.length === 0;
-
-
-
-
queue.c

-
-
-
-
queue.cs
/* 初始化队列 */
-Queue<int> queue = new();
-
-/* 元素入队 */
-queue.Enqueue(1);
-queue.Enqueue(3);
-queue.Enqueue(2);
-queue.Enqueue(5);
-queue.Enqueue(4);
-
-/* 访问队首元素 */
-int peek = queue.Peek();
-
-/* 元素出队 */
-int poll = queue.Dequeue();
-
-/* 获取队列的长度 */
-int size = queue.Count();
-
-/* 判断队列是否为空 */
-bool isEmpty = queue.Count() == 0;
-
-
-
-
queue.swift
/* 初始化队列 */
-// Swift 没有内置的队列类,可以把 Array 当作队列来使用
-var queue: [Int] = []
-
-/* 元素入队 */
-queue.append(1)
-queue.append(3)
-queue.append(2)
-queue.append(5)
-queue.append(4)
-
-/* 访问队首元素 */
-let peek = queue.first!
-
-/* 元素出队 */
-// 使用 Array 模拟时 poll 的复杂度为 O(n)
-let pool = queue.removeFirst()
-
-/* 获取队列的长度 */
-let size = queue.count
-
-/* 判断队列是否为空 */
-let isEmpty = queue.isEmpty
-
-
-
-
queue.zig

-
-
-
-
-

5.2.2. 队列实现

-

队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。

-

基于链表的实现

-

我们将链表的「头结点」和「尾结点」分别看作是队首和队尾,并规定队尾只可添加结点,队首只可删除结点。

-
-
-
-

linkedlist_queue

-
-
-

linkedlist_queue_push

-
-
-

linkedlist_queue_poll

-
-
-
-

以下是使用链表实现队列的示例代码。

-
-
-
-
linkedlist_queue.java
/* 基于链表实现的队列 */
-class LinkedListQueue {
-    private ListNode front, rear;  // 头结点 front ,尾结点 rear 
-    private int queSize = 0;
-
-    public LinkedListQueue() {
-        front = null;
-        rear = null;
-    }
-
-    /* 获取队列的长度 */
-    public int size() {
-        return queSize;
-    }
-
-    /* 判断队列是否为空 */
-    public boolean isEmpty() {
-        return size() == 0;
-    }
-
-    /* 入队 */
-    public void push(int num) {
-        // 尾结点后添加 num
-        ListNode node = new ListNode(num);
-        // 如果队列为空,则令头、尾结点都指向该结点
-        if (front == null) {
-            front = node;
-            rear = node;
-        // 如果队列不为空,则将该结点添加到尾结点后
-        } else {
-            rear.next = node;
-            rear = node;
-        }
-        queSize++;
-    }
-
-    /* 出队 */
-    public int poll() {
-        int num = peek();
-        // 删除头结点
-        front = front.next;
-        queSize--;
-        return num;
-    }
-
-    /* 访问队首元素 */
-    public int peek() {
-        if (size() == 0)
-            throw new EmptyStackException();
-        return front.val;
-    }
-
-    /* 将链表转化为 Array 并返回 */
-    public int[] toArray() {
-        ListNode node = front;
-        int[] res = new int[size()];
-        for (int i = 0; i < res.length; i++) {
-            res[i] = node.val;
-            node = node.next;
-        }
-        return res;
-    }
-}
-
-
-
-
linkedlist_queue.cpp
/* 基于链表实现的队列 */
-class LinkedListQueue {
-private:
-    ListNode *front, *rear;  // 头结点 front ,尾结点 rear 
-    int queSize;
-
-public:
-    LinkedListQueue() {
-        front = nullptr;
-        rear = nullptr;
-        queSize = 0;
-    }
-    ~LinkedListQueue() {
-        delete front;
-        delete rear;
-    }
-    /* 获取队列的长度 */
-    int size() {
-        return queSize;
-    }
-    /* 判断队列是否为空 */
-    bool empty() {
-        return queSize == 0;
-    }
-    /* 入队 */
-    void push(int num) {
-        // 尾结点后添加 num
-        ListNode* node = new ListNode(num);
-        // 如果队列为空,则令头、尾结点都指向该结点
-        if (front == nullptr) {
-            front = node;
-            rear = node;
-        }
-        // 如果队列不为空,则将该结点添加到尾结点后
-        else {
-            rear->next = node;
-            rear = node;
-        }
-        queSize++;
-    }
-    /* 出队 */
-    void poll() {
-        int num = peek();
-        // 删除头结点
-        ListNode *tmp = front;
-        front = front->next;
-        // 释放内存
-        delete tmp; 
-        queSize--;
-    }
-    /* 访问队首元素 */
-    int peek() {
-        if (size() == 0)
-            throw out_of_range("队列为空");
-        return front->val;
-    }
-};
-
-
-
-
linkedlist_queue.py
""" 基于链表实现的队列 """
-class LinkedListQueue:
-    def __init__(self):
-        self.__front = None  # 头结点 front
-        self.__rear = None   # 尾结点 rear
-        self.__size = 0
-
-    """ 获取队列的长度 """
-    def size(self):
-        return self.__size
-
-    """ 判断队列是否为空 """
-    def is_empty(self):
-        return not self.__front
-
-    """ 入队 """
-    def push(self, num):
-        # 尾结点后添加 num
-        node = ListNode(num)
-        # 如果队列为空,则令头、尾结点都指向该结点
-        if self.__front is None:
-            self.__front = node
-            self.__rear = node
-        # 如果队列不为空,则将该结点添加到尾结点后
-        else:
-            self.__rear.next = node
-            self.__rear = node
-        self.__size += 1
-
-    """ 出队 """
-    def poll(self):
-        num = self.peek()
-        # 删除头结点
-        self.__front = self.__front.next
-        self.__size -= 1
-        return num
-
-    """ 访问队首元素 """
-    def peek(self):
-        if self.size() == 0:
-            print("队列为空")
-            return False
-        return self.__front.val
-
-    """ 转化为列表用于打印 """
-    def to_list(self):
-        queue = []
-        temp = self.__front
-        while temp:
-            queue.append(temp.val)
-            temp = temp.next
-        return queue
-
-
-
-
linkedlist_queue.go
/* 基于链表实现的队列 */
-type linkedListQueue struct {
-    // 使用内置包 list 来实现队列
-    data *list.List
-}
-
-// newLinkedListQueue 初始化链表
-func newLinkedListQueue() *linkedListQueue {
-    return &linkedListQueue{
-        data: list.New(),
-    }
-}
-
-// push 入队
-func (s *linkedListQueue) push(value any) {
-    s.data.PushBack(value)
-}
-
-// poll 出队
-func (s *linkedListQueue) poll() any {
-    if s.isEmpty() {
-        return nil
-    }
-    e := s.data.Front()
-    s.data.Remove(e)
-    return e.Value
-}
-
-// peek 访问队首元素
-func (s *linkedListQueue) peek() any {
-    if s.isEmpty() {
-        return nil
-    }
-    e := s.data.Front()
-    return e.Value
-}
-
-// size 获取队列的长度
-func (s *linkedListQueue) size() int {
-    return s.data.Len()
-}
-
-// isEmpty 判断队列是否为空
-func (s *linkedListQueue) isEmpty() bool {
-    return s.data.Len() == 0
-}
-
-
-
-
linkedlist_queue.js
/* 基于链表实现的队列 */
-class LinkedListQueue {
-    #front;  // 头结点 #front
-    #rear;   // 尾结点 #rear
-    #queSize = 0;
-    constructor() {
-        this.#front = null;
-        this.#rear = null;
-    }
-    /* 获取队列的长度 */
-    get size() {
-        return this.#queSize;
-    }
-    /* 判断队列是否为空 */
-    isEmpty() {
-        return this.size === 0;
-    }
-    /* 入队 */
-    push(num) {
-        // 尾结点后添加 num
-        const node = new ListNode(num);
-        // 如果队列为空,则令头、尾结点都指向该结点
-        if (!this.#front) {
-            this.#front = node;
-            this.#rear = node;
-            // 如果队列不为空,则将该结点添加到尾结点后
-        } else {
-            this.#rear.next = node;
-            this.#rear = node;
-        }
-        this.#queSize++;
-    }
-    /* 出队 */
-    poll() {
-        const num = this.peek();
-        // 删除头结点
-        this.#front = this.#front.next;
-        this.#queSize--;
-        return num;
-    }
-    /* 访问队首元素 */
-    peek() {
-        if (this.size === 0)
-            throw new Error("队列为空");
-        return this.#front.val;
-    }
-}
-
-
-
-
linkedlist_queue.ts
/* 基于链表实现的队列 */
-class LinkedListQueue {
-    private front: ListNode | null; // 头结点 front
-    private rear: ListNode | null;  // 尾结点 rear
-    private queSize: number = 0;
-    constructor() {
-        this.front = null;
-        this.rear = null;
-    }
-    /* 获取队列的长度 */
-    get size(): number {
-        return this.queSize;
-    }
-    /* 判断队列是否为空 */
-    isEmpty(): boolean {
-        return this.size === 0;
-    }
-    /* 入队 */
-    push(num: number): void {
-        // 尾结点后添加 num
-        const node = new ListNode(num);
-        // 如果队列为空,则令头、尾结点都指向该结点
-        if (!this.front) {
-            this.front = node;
-            this.rear = node;
-            // 如果队列不为空,则将该结点添加到尾结点后
-        } else {
-            this.rear!.next = node;
-            this.rear = node;
-        }
-        this.queSize++;
-    }
-    /* 出队 */
-    poll(): number {
-        const num = this.peek();
-        if (!this.front)
-            throw new Error("队列为空")
-        // 删除头结点
-        this.front = this.front.next;
-        this.queSize--;
-        return num;
-    }
-    /* 访问队首元素 */
-    peek(): number {
-        if (this.size === 0)
-            throw new Error("队列为空");
-        return this.front!.val;
-    }
-}
-
-
-
-
linkedlist_queue.c

-
-
-
-
linkedlist_queue.cs
/* 基于链表实现的队列 */
-class LinkedListQueue
-{
-    private ListNode? front, rear;  // 头结点 front ,尾结点 rear 
-    private int queSize = 0;
-    public LinkedListQueue()
-    {
-        front = null;
-        rear = null;
-    }
-    /* 获取队列的长度 */
-    public int size()
-    {
-        return queSize;
-    }
-    /* 判断队列是否为空 */
-    public bool isEmpty()
-    {
-        return size() == 0;
-    }
-    /* 入队 */
-    public void push(int num)
-    {
-        // 尾结点后添加 num
-        ListNode node = new ListNode(num);
-        // 如果队列为空,则令头、尾结点都指向该结点
-        if (front == null)
-        {
-            front = node;
-            rear = node;
-            // 如果队列不为空,则将该结点添加到尾结点后
-        }
-        else if (rear != null)
-        {
-            rear.next = node;
-            rear = node;
-        }
-        queSize++;
-    }
-    /* 出队 */
-    public int poll()
-    {
-        int num = peek();
-        // 删除头结点
-        front = front?.next;
-        queSize--;
-        return num;
-    }
-    /* 访问队首元素 */
-    public int peek()
-    {
-        if (size() == 0 || front == null)
-            throw new Exception();
-        return front.val;
-    }
-}
-
-
-
-
linkedlist_queue.swift
/* 基于链表实现的队列 */
-class LinkedListQueue {
-    private var front: ListNode? // 头结点
-    private var rear: ListNode? // 尾结点
-    private var _size = 0
-
-    init() {}
-
-    /* 获取队列的长度 */
-    func size() -> Int {
-        _size
-    }
-
-    /* 判断队列是否为空 */
-    func isEmpty() -> Bool {
-        size() == 0
-    }
-
-    /* 入队 */
-    func push(num: Int) {
-        // 尾结点后添加 num
-        let node = ListNode(x: num)
-        // 如果队列为空,则令头、尾结点都指向该结点
-        if front == nil {
-            front = node
-            rear = node
-        }
-        // 如果队列不为空,则将该结点添加到尾结点后
-        else {
-            rear?.next = node
-            rear = node
-        }
-        _size += 1
-    }
-
-    /* 出队 */
-    @discardableResult
-    func poll() -> Int {
-        let num = peek()
-        // 删除头结点
-        front = front?.next
-        _size -= 1
-        return num
-    }
-
-    /* 访问队首元素 */
-    func peek() -> Int {
-        if isEmpty() {
-            fatalError("队列为空")
-        }
-        return front!.val
-    }
-}
-
-
-
-
linkedlist_queue.zig

-
-
-
-
-

基于数组的实现

-

数组的删除首元素的时间复杂度为 \(O(n)\) ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 front , rear 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 front / rear 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 \(O(1)\)

-
-
-
-

array_queue

-
-
-

array_queue_push

-
-
-

array_queue_poll

-
-
-
-

细心的同学可能会发现一个问题,即在入队与出队的过程中,两个指针都在向后移动,在到达尾部后则无法继续移动了

-

为了解决此问题,我们可以采取一个取巧方案,即将数组看作是“环形”的。具体做法是规定指针越过数组尾部后,再次回到头部接续遍历,这样相当于使数组“首尾相连”了。在环形数组的设定下,获取长度 size() 、入队 push() 、出队 poll() 方法都需要做相应的取余操作处理,使得当尾指针绕回数组头部时,仍然可以正确处理操作。

-
-
-
-
array_queue.java
/* 基于环形数组实现的队列 */
-class ArrayQueue {
-    private int[] nums;  // 用于存储队列元素的数组
-    private int front;   // 队首指针,指向队首元素
-    private int queSize; // 队列长度
-
-    public ArrayQueue(int capacity) {
-        nums = new int[capacity];
-        front = queSize = 0;
-    }
-
-    /* 获取队列的容量 */
-    public int capacity() {
-        return nums.length;
-    }
-
-    /* 获取队列的长度 */
-    public int size() {
-        return queSize;
-    }
-
-    /* 判断队列是否为空 */
-    public boolean isEmpty() {
-        return queSize == 0;
-    }
-
-    /* 入队 */
-    public void push(int num) {
-        if (queSize == capacity()) {
-            System.out.println("队列已满");
-            return;
-        }
-        // 计算尾指针,指向队尾索引 + 1
-        // 通过取余操作,实现 rear 越过数组尾部后回到头部
-        int rear = (front + queSize) % capacity();
-        // 尾结点后添加 num
-        nums[rear] = num;
-        queSize++;
-    }
-
-    /* 出队 */
-    public int poll() {
-        int num = peek();
-        // 队首指针向后移动一位,若越过尾部则返回到数组头部
-        front = (front + 1) % capacity();
-        queSize--;
-        return num;
-    }
-
-    /* 访问队首元素 */
-    public int peek() {
-        if (isEmpty())
-            throw new EmptyStackException();
-        return nums[front];
-    }
-
-    /* 返回数组 */
-    public int[] toArray() {
-        // 仅转换有效长度范围内的列表元素
-        int[] res = new int[queSize];
-        for (int i = 0, j = front; i < queSize; i++, j++) {
-            res[i] = nums[j % capacity()];
-        }
-        return res;
-    }
-}
-
-
-
-
array_queue.cpp
/* 基于环形数组实现的队列 */
-class ArrayQueue {
-private:
-    int *nums;       // 用于存储队列元素的数组
-    int front;       // 队首指针,指向队首元素
-    int queSize;     // 队列长度
-    int queCapacity; // 队列容量
-
-public:
-    ArrayQueue(int capacity) {
-        // 初始化数组
-        nums = new int[capacity];
-        queCapacity = capacity;
-        front = queSize = 0;
-    }
-
-    ~ArrayQueue() {
-        delete[] nums;
-    }
-
-    /* 获取队列的容量 */
-    int capacity() {
-        return queCapacity;
-    }
-
-    /* 获取队列的长度 */
-    int size() {
-        return queSize;
-    }
-
-    /* 判断队列是否为空 */
-    bool empty() {
-        return size() == 0;
-    }
-
-    /* 入队 */
-    void push(int num) {
-        if (queSize == queCapacity) {
-            cout << "队列已满" << endl;
-            return;
-        }
-        // 计算队尾指针,指向队尾索引 + 1
-        // 通过取余操作,实现 rear 越过数组尾部后回到头部
-        int rear = (front + queSize) % queCapacity;
-        // 尾结点后添加 num
-        nums[rear] = num;
-        queSize++;
-    }
-
-    /* 出队 */
-    void poll() {
-        int num = peek();
-        // 队首指针向后移动一位,若越过尾部则返回到数组头部
-        front = (front + 1) % queCapacity;
-        queSize--;
-    }
-
-    /* 访问队首元素 */
-    int peek() {
-        if (empty())
-            throw out_of_range("队列为空");
-        return nums[front];
-    }
-};
-
-
-
-
array_queue.py
""" 基于环形数组实现的队列 """
-class ArrayQueue:
-    def __init__(self, size):
-        self.__nums = [0] * size  # 用于存储队列元素的数组
-        self.__front = 0          # 队首指针,指向队首元素
-        self.__size = 0           # 队列长度
-
-    """ 获取队列的容量 """
-    def capacity(self):
-        return len(self.__nums)
-
-    """ 获取队列的长度 """
-    def size(self):
-        return self.__size
-
-    """ 判断队列是否为空 """
-    def is_empty(self):
-        return self.__size == 0
-
-    """ 入队 """
-    def push(self, num):
-        assert self.__size < self.capacity(), "队列已满"
-        # 计算尾指针,指向队尾索引 + 1
-        # 通过取余操作,实现 rear 越过数组尾部后回到头部
-        rear = (self.__front + self.__size) % self.capacity()
-        # 尾结点后添加 num
-        self.__nums[rear] = num
-        self.__size += 1
-
-    """ 出队 """
-    def poll(self):
-        num = self.peek()
-        # 队首指针向后移动一位,若越过尾部则返回到数组头部
-        self.__front = (self.__front + 1) % self.capacity()
-        self.__size -= 1
-        return num
-
-    """ 访问队首元素 """
-    def peek(self):
-        assert not self.is_empty(), "队列为空"
-        return self.__nums[self.__front]
-
-    """ 返回列表用于打印 """
-    def to_list(self):
-        res = [0] * self.size()
-        j = self.__front
-        for i in range(self.size()):
-            res[i] = self.__nums[(j % self.capacity())]
-            j += 1
-        return res
-
-
-
-
array_queue.go
/* 基于环形数组实现的队列 */
-type arrayQueue struct {
-    nums        []int // 用于存储队列元素的数组
-    front       int   // 队首指针,指向队首元素
-    queSize     int   // 队列长度
-    queCapacity int   // 队列容量(即最大容纳元素数量)
-}
-
-// newArrayQueue 基于环形数组实现的队列
-func newArrayQueue(queCapacity int) *arrayQueue {
-    return &arrayQueue{
-        nums:        make([]int, queCapacity),
-        queCapacity: queCapacity,
-        front:       0,
-        queSize:     0,
-    }
-}
-
-// size 获取队列的长度
-func (q *arrayQueue) size() int {
-    return q.queSize
-}
-
-// isEmpty 判断队列是否为空
-func (q *arrayQueue) isEmpty() bool {
-    return q.queSize == 0
-}
-
-// push 入队
-func (q *arrayQueue) push(num int) {
-    // 当 rear == queCapacity 表示队列已满
-    if q.queSize == q.queCapacity {
-        return
-    }
-    // 计算尾指针,指向队尾索引 + 1
-    // 通过取余操作,实现 rear 越过数组尾部后回到头部
-    rear := (q.front + q.queSize) % q.queCapacity
-    // 尾结点后添加 num
-    q.nums[rear] = num
-    q.queSize++
-}
-
-// poll 出队
-func (q *arrayQueue) poll() any {
-    num := q.peek()
-    // 队首指针向后移动一位,若越过尾部则返回到数组头部
-    q.front = (q.front + 1) % q.queCapacity
-    q.queSize--
-    return num
-}
-
-// peek 访问队首元素
-func (q *arrayQueue) peek() any {
-    if q.isEmpty() {
-        return nil
-    }
-    return q.nums[q.front]
-}
-
-// 获取 Slice 用于打印
-func (q *arrayQueue) toSlice() []int {
-    rear := (q.front + q.queSize)
-    if rear >= q.queCapacity {
-        rear %= q.queCapacity
-        return append(q.nums[q.front:], q.nums[:rear]...)
-    }
-    return q.nums[q.front:rear]
-}
-
-
-
-
array_queue.js
/* 基于环形数组实现的队列 */
-class ArrayQueue {
-    #nums;         // 用于存储队列元素的数组
-    #front = 0;    // 队首指针,指向队首元素
-    #queSize = 0;  // 队列长度
-
-    constructor(capacity) {
-        this.#nums = new Array(capacity);
-    }
-
-    /* 获取队列的容量 */
-    get capacity() {
-        return this.#nums.length;
-    }
-
-    /* 获取队列的长度 */
-    get size() {
-        return this.#queSize;
-    }
-
-    /* 判断队列是否为空 */
-    empty() {
-        return this.#queSize == 0;
-    }
-
-    /* 入队 */
-    push(num) {
-        if (this.size == this.capacity) {
-            console.log("队列已满");
-            return;
-        }
-        // 计算尾指针,指向队尾索引 + 1
-        // 通过取余操作,实现 rear 越过数组尾部后回到头部
-        const rear = (this.#front + this.size) % this.capacity;
-        // 尾结点后添加 num
-        this.#nums[rear] = num;
-        this.#queSize++;
-    }
-
-    /* 出队 */
-    poll() {
-        const num = this.peek();
-        // 队首指针向后移动一位,若越过尾部则返回到数组头部
-        this.#front = (this.#front + 1) % this.capacity;
-        this.#queSize--;
-        return num;
-    }
-
-    /* 访问队首元素 */
-    peek() {
-        if (this.empty())
-            throw new Error("队列为空");
-        return this.#nums[this.#front];
-    }
-}
-
-
-
-
array_queue.ts
/* 基于环形数组实现的队列 */
-class ArrayQueue {
-    private nums: number[];  // 用于存储队列元素的数组
-    private front: number;   // 队首指针,指向队首元素
-    private queSize: number; // 队列长度
-
-    constructor(capacity: number) {
-        this.nums = new Array(capacity);
-        this.front = this.queSize = 0;
-    }
-
-    /* 获取队列的容量 */
-    get capacity(): number {
-        return this.nums.length;
-    }
-
-    /* 获取队列的长度 */
-    get size(): number {
-        return this.queSize;
-    }
-
-    /* 判断队列是否为空 */
-    empty(): boolean {
-        return this.queSize == 0;
-    }
-
-    /* 入队 */
-    push(num: number): void {
-        if (this.size == this.capacity) {
-            console.log("队列已满");
-            return;
-        }
-        // 计算尾指针,指向队尾索引 + 1
-        // 通过取余操作,实现 rear 越过数组尾部后回到头部
-        const rear = (this.front + this.queSize) % this.capacity;
-        // 尾结点后添加 num
-        this.nums[rear] = num;
-        this.queSize++;
-    }
-
-    /* 出队 */
-    poll(): number {
-        const num = this.peek();
-        // 队首指针向后移动一位,若越过尾部则返回到数组头部
-        this.front = (this.front + 1) % this.capacity;
-        this.queSize--;
-        return num;
-    }
-
-    /* 访问队首元素 */
-    peek(): number {
-        if (this.empty())
-            throw new Error("队列为空");
-        return this.nums[this.front];
-    }
-}
-
-
-
-
array_queue.c

-
-
-
-
array_queue.cs
/* 基于环形数组实现的队列 */
-class ArrayQueue
-{
-    private int[] nums;  // 用于存储队列元素的数组
-    private int front;   // 队首指针,指向队首元素
-    private int queSize; // 队列长度
-
-    public ArrayQueue(int capacity)
-    {
-        nums = new int[capacity];
-        front = queSize = 0;
-    }
-
-    /* 获取队列的容量 */
-    public int capacity()
-    {
-        return nums.Length;
-    }
-
-    /* 获取队列的长度 */
-    public int size()
-    {
-        return queSize;
-    }
-
-    /* 判断队列是否为空 */
-    public bool isEmpty()
-    {
-        return queSize == 0;
-    }
-
-    /* 入队 */
-    public void push(int num)
-    {
-        if (queSize == capacity())
-        {
-            Console.WriteLine("队列已满");
-            return;
-        }
-        // 计算尾指针,指向队尾索引 + 1
-        // 通过取余操作,实现 rear 越过数组尾部后回到头部
-        int rear = (front + queSize) % capacity();
-        // 尾结点后添加 num
-        nums[rear] = num;
-        queSize++;
-    }
-
-    /* 出队 */
-    public int poll()
-    {
-        int num = peek();
-        // 队首指针向后移动一位,若越过尾部则返回到数组头部
-        front = (front + 1) % capacity();
-        queSize--;
-        return num;
-    }
-
-    /* 访问队首元素 */
-    public int peek()
-    {
-        if (isEmpty())
-            throw new Exception();
-        return nums[front];
-    }
-}
-
-
-
-
array_queue.swift
/* 基于环形数组实现的队列 */
-class ArrayQueue {
-    private var nums: [Int] // 用于存储队列元素的数组
-    private var front = 0 // 队首指针,指向队首元素
-    private var queSize = 0 // 队列长度
-
-    init(capacity: Int) {
-        // 初始化数组
-        nums = Array(repeating: 0, count: capacity)
-    }
-
-    /* 获取队列的容量 */
-    func capacity() -> Int {
-        nums.count
-    }
-
-    /* 获取队列的长度 */
-    func size() -> Int {
-        queSize
-    }
-
-    /* 判断队列是否为空 */
-    func isEmpty() -> Bool {
-        queSize == 0
-    }
-
-    /* 入队 */
-    func push(num: Int) {
-        if size() == capacity() {
-            print("队列已满")
-            return
-        }
-        // 计算尾指针,指向队尾索引 + 1
-        // 通过取余操作,实现 rear 越过数组尾部后回到头部
-        let rear = (front + queSize) % capacity()
-        // 尾结点后添加 num
-        nums[rear] = num
-        queSize += 1
-    }
-
-    /* 出队 */
-    @discardableResult
-    func poll() -> Int {
-        let num = peek()
-        // 队首指针向后移动一位,若越过尾部则返回到数组头部
-        front = (front + 1) % capacity()
-        queSize -= 1
-        return num
-    }
-
-    /* 访问队首元素 */
-    func peek() -> Int {
-        if isEmpty() {
-            fatalError("队列为空")
-        }
-        return nums[front]
-    }
-}
-
-
-
-
array_queue.zig

-
-
-
-
-

以上代码仍存在局限性,即长度不可变。然而,我们可以通过将数组替换为列表(即动态数组)来引入扩容机制,有兴趣的同学可以尝试实现。

-

5.2.3. 两种实现对比

-

与栈的结论一致,在此不再赘述。

-

5.2.4. 队列典型应用

-
    -
  • 淘宝订单。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。
  • -
  • 各种待办事项。例如打印机的任务队列、餐厅的出餐队列等等。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_stack_and_queue/stack.assets/array_stack.png b/site/chapter_stack_and_queue/stack.assets/array_stack.png deleted file mode 100644 index 4629ef667..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/array_stack.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack.assets/array_stack_pop.png b/site/chapter_stack_and_queue/stack.assets/array_stack_pop.png deleted file mode 100644 index afc87d382..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/array_stack_pop.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack.assets/array_stack_push.png b/site/chapter_stack_and_queue/stack.assets/array_stack_push.png deleted file mode 100644 index 098dbec35..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/array_stack_push.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack.assets/linkedlist_stack.png b/site/chapter_stack_and_queue/stack.assets/linkedlist_stack.png deleted file mode 100644 index 41d1bb5c6..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/linkedlist_stack.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack.assets/linkedlist_stack_pop.png b/site/chapter_stack_and_queue/stack.assets/linkedlist_stack_pop.png deleted file mode 100644 index 2afd3ca80..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/linkedlist_stack_pop.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack.assets/linkedlist_stack_push.png b/site/chapter_stack_and_queue/stack.assets/linkedlist_stack_push.png deleted file mode 100644 index 6b08e9833..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/linkedlist_stack_push.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack.assets/stack_operations.png b/site/chapter_stack_and_queue/stack.assets/stack_operations.png deleted file mode 100644 index 585af8eae..000000000 Binary files a/site/chapter_stack_and_queue/stack.assets/stack_operations.png and /dev/null differ diff --git a/site/chapter_stack_and_queue/stack/index.html b/site/chapter_stack_and_queue/stack/index.html deleted file mode 100644 index b538d5e20..000000000 --- a/site/chapter_stack_and_queue/stack/index.html +++ /dev/null @@ -1,2928 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5.1. 栈(Stack) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

5.1. 栈

-

「栈 Stack」是一种遵循「先入后出 first in, last out」数据操作规则的线性数据结构。我们可以将栈类比为放在桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。

-

“盘子”是一种形象比喻,我们将盘子替换为任意一种元素(例如整数、字符、对象等),就得到了栈数据结构。

-

我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。

-

stack_operations

-

Fig. 栈的先入后出特性

- -

5.1.1. 栈常用操作

-

栈的常用操作见下表(方法命名以 Java 为例)。

-

Table. 栈的常用操作

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
方法描述时间复杂度
push()元素入栈(添加至栈顶)\(O(1)\)
pop()栈顶元素出栈\(O(1)\)
peek()访问栈顶元素\(O(1)\)
size()获取栈的长度\(O(1)\)
isEmpty()判断栈是否为空\(O(1)\)
-
-

我们可以直接使用编程语言实现好的栈类。 某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作。

-
-
-
-
stack.java
/* 初始化栈 */
-// 在 Java 中,推荐将 ArrayList 当作栈来使用
-List<Integer> stack = new ArrayList<>();
-
-/* 元素入栈 */
-stack.add(1);
-stack.add(3);
-stack.add(2);
-stack.add(5);
-stack.add(4);
-
-/* 访问栈顶元素 */
-int peek = stack.get(stack.size() - 1);
-
-/* 元素出栈 */
-int pop = stack.remove(stack.size() - 1);
-
-/* 获取栈的长度 */
-int size = stack.size();
-
-/* 判断是否为空 */
-boolean isEmpty = stack.isEmpty();
-
-
-
-
stack.cpp
/* 初始化栈 */
-stack<int> stack;
-
-/* 元素入栈 */
-stack.push(1);
-stack.push(3);
-stack.push(2);
-stack.push(5);
-stack.push(4);
-
-/* 访问栈顶元素 */
-int top = stack.top();
-
-/* 元素出栈 */
-stack.pop();
-
-/* 获取栈的长度 */
-int size = stack.size();
-
-/* 判断是否为空 */
-bool empty = stack.empty();
-
-
-
-
stack.py
""" 初始化栈 """
-# Python 没有内置的栈类,可以把 List 当作栈来使用 
-stack = []
-
-""" 元素入栈 """
-stack.append(1)
-stack.append(3)
-stack.append(2)
-stack.append(5)
-stack.append(4)
-
-""" 访问栈顶元素 """
-peek = stack[-1]
-
-""" 元素出栈 """
-pop = stack.pop()
-
-""" 获取栈的长度 """
-size = len(stack)
-
-""" 判断是否为空 """
-is_empty = len(stack) == 0
-
-
-
-
stack_test.go
/* 初始化栈 */
-// 在 Go 中,推荐将 Slice 当作栈来使用
-var stack []int
-
-/* 元素入栈 */
-stack = append(stack, 1)
-stack = append(stack, 3)
-stack = append(stack, 2)
-stack = append(stack, 5)
-stack = append(stack, 4)
-
-/* 访问栈顶元素 */
-peek := stack[len(stack)-1]
-
-/* 元素出栈 */
-pop := stack[len(stack)-1]
-stack = stack[:len(stack)-1]
-
-/* 获取栈的长度 */
-size := len(stack)
-
-/* 判断是否为空 */
-isEmpty := len(stack) == 0
-
-
-
-
stack.js
/* 初始化栈 */
-// Javascript 没有内置的栈类,可以把 Array 当作栈来使用 
-const stack = [];
-
-/* 元素入栈 */
-stack.push(1);
-stack.push(3);
-stack.push(2);
-stack.push(5);
-stack.push(4);
-
-/* 访问栈顶元素 */
-const peek = stack[stack.length-1];
-
-/* 元素出栈 */
-const pop = stack.pop();
-
-/* 获取栈的长度 */
-const size = stack.length;
-
-/* 判断是否为空 */
-const is_empty = stack.length === 0;
-
-
-
-
stack.ts
/* 初始化栈 */
-// Typescript 没有内置的栈类,可以把 Array 当作栈来使用 
-const stack: number[] = [];
-
-/* 元素入栈 */
-stack.push(1);
-stack.push(3);
-stack.push(2);
-stack.push(5);
-stack.push(4);
-
-/* 访问栈顶元素 */
-const peek = stack[stack.length - 1];
-
-/* 元素出栈 */
-const pop = stack.pop();
-
-/* 获取栈的长度 */
-const size = stack.length;
-
-/* 判断是否为空 */
-const is_empty = stack.length === 0;
-
-
-
-
stack.c

-
-
-
-
stack.cs
/* 初始化栈 */
-Stack<int> stack = new ();
-
-/* 元素入栈 */
-stack.Push(1);
-stack.Push(3);
-stack.Push(2);
-stack.Push(5);
-stack.Push(4);
-
-/* 访问栈顶元素 */
-int peek = stack.Peek();
-
-/* 元素出栈 */
-int pop = stack.Pop();
-
-/* 获取栈的长度 */
-int size = stack.Count();
-
-/* 判断是否为空 */
-bool isEmpty = stack.Count()==0;
-
-
-
-
stack.swift
/* 初始化栈 */
-// Swift 没有内置的栈类,可以把 Array 当作栈来使用
-var stack: [Int] = []
-
-/* 元素入栈 */
-stack.append(1)
-stack.append(3)
-stack.append(2)
-stack.append(5)
-stack.append(4)
-
-/* 访问栈顶元素 */
-let peek = stack.last!
-
-/* 元素出栈 */
-let pop = stack.removeLast()
-
-/* 获取栈的长度 */
-let size = stack.count
-
-/* 判断是否为空 */
-let isEmpty = stack.isEmpty
-
-
-
-
stack.zig

-
-
-
-
-

5.1.2. 栈的实现

-

为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。

-

栈规定元素是先入后出的,因此我们只能在栈顶添加或删除元素。然而,数组或链表都可以在任意位置添加删除元素,因此 栈可被看作是一种受约束的数组或链表。换言之,我们可以“屏蔽”数组或链表的部分无关操作,使之对外的表现逻辑符合栈的规定即可。

-

基于链表的实现

-

使用「链表」实现栈时,将链表的头结点看作栈顶,将尾结点看作栈底。

-

对于入栈操作,将元素插入到链表头部即可,这种结点添加方式被称为“头插法”。而对于出栈操作,则将头结点从链表中删除即可。

-
-
-
-

linkedlist_stack

-
-
-

linkedlist_stack_push

-
-
-

linkedlist_stack_pop

-
-
-
-

以下是基于链表实现栈的示例代码。

-
-
-
-
linkedlist_stack.java
/* 基于链表实现的栈 */
-class LinkedListStack {
-    private ListNode stackPeek;  // 将头结点作为栈顶
-    private int stkSize = 0;   // 栈的长度
-
-    public LinkedListStack() {
-        stackPeek = null;
-    }
-
-    /* 获取栈的长度 */
-    public int size() {
-        return stkSize;
-    }
-
-    /* 判断栈是否为空 */
-    public boolean isEmpty() {
-        return size() == 0;
-    }
-
-    /* 入栈 */
-    public void push(int num) {
-        ListNode node = new ListNode(num);
-        node.next = stackPeek;
-        stackPeek = node;
-        stkSize++;
-    }
-
-    /* 出栈 */
-    public int pop() {
-        int num = peek();
-        stackPeek = stackPeek.next;
-        stkSize--;
-        return num;
-    }
-
-    /* 访问栈顶元素 */
-    public int peek() {
-        if (size() == 0)
-            throw new EmptyStackException();
-        return stackPeek.val;
-    }
-
-    /* 将 List 转化为 Array 并返回 */
-    public int[] toArray() {
-        ListNode node = stackPeek;
-        int[] res = new int[size()];
-        for (int i = res.length - 1; i >= 0; i--) {
-            res[i] = node.val;
-            node = node.next;
-        }
-        return res;
-    }
-}
-
-
-
-
linkedlist_stack.cpp
/* 基于链表实现的栈 */
-class LinkedListStack {
-private:
-    ListNode* stackTop; // 将头结点作为栈顶
-    int stkSize;        // 栈的长度
-
-public:
-    LinkedListStack() {
-        stackTop = nullptr;
-        stkSize = 0;
-    }
-    ~LinkedListStack() {
-        freeMemoryLinkedList(stackTop);
-    }
-    /* 获取栈的长度 */
-    int size() {
-        return stkSize;
-    }
-    /* 判断栈是否为空 */
-    bool empty() {
-        return size() == 0;
-    }
-    /* 入栈 */
-    void push(int num) {
-        ListNode* node = new ListNode(num);
-        node->next = stackTop;
-        stackTop = node;
-        stkSize++;
-    }
-    /* 出栈 */
-    void pop() {
-        int num = top();
-        ListNode *tmp = stackTop;
-        stackTop = stackTop->next;
-        // 释放内存
-        delete tmp;
-        stkSize--;
-    }
-    /* 访问栈顶元素 */
-    int top() {
-        if (size() == 0)
-            throw out_of_range("栈为空");
-        return stackTop->val;
-    }
-};
-
-
-
-
linkedlist_stack.py
""" 基于链表实现的栈 """
-class LinkedListStack:
-    def __init__(self):
-        self.__peek = None
-        self.__size = 0
-
-    """ 获取栈的长度 """
-    def size(self):
-        return self.__size
-
-    """ 判断栈是否为空 """
-    def is_empty(self):
-        return not self.__peek
-
-    """ 入栈 """
-    def push(self, val):
-        node = ListNode(val)
-        node.next = self.__peek
-        self.__peek = node
-        self.__size += 1
-
-    """ 出栈 """
-    def pop(self):
-        num = self.peek()
-        self.__peek = self.__peek.next
-        self.__size -= 1
-        return num
-
-    """ 访问栈顶元素 """
-    def peek(self):
-        # 判空处理
-        if not self.__peek: return None
-        return self.__peek.val
-
-    """ 转化为列表用于打印 """
-    def to_list(self):
-        arr = []
-        node = self.__peek
-        while node:
-            arr.append(node.val)
-            node = node.next
-        arr.reverse()
-        return arr
-
-
-
-
linkedlist_stack.go
/* 基于链表实现的栈 */
-type linkedListStack struct {
-    // 使用内置包 list 来实现栈
-    data *list.List
-}
-
-// newLinkedListStack 初始化链表
-func newLinkedListStack() *linkedListStack {
-    return &linkedListStack{
-        data: list.New(),
-    }
-}
-
-// push 入栈
-func (s *linkedListStack) push(value int) {
-    s.data.PushBack(value)
-}
-
-// pop 出栈
-func (s *linkedListStack) pop() any {
-    if s.isEmpty() {
-        return nil
-    }
-    e := s.data.Back()
-    s.data.Remove(e)
-    return e.Value
-}
-
-// peek 访问栈顶元素
-func (s *linkedListStack) peek() any {
-    if s.isEmpty() {
-        return nil
-    }
-    e := s.data.Back()
-    return e.Value
-}
-
-// size 获取栈的长度
-func (s *linkedListStack) size() int {
-    return s.data.Len()
-}
-
-// isEmpty 判断栈是否为空
-func (s *linkedListStack) isEmpty() bool {
-    return s.data.Len() == 0
-}
-
-
-
-
linkedlist_stack.js
/* 基于链表实现的栈 */
-class LinkedListStack {
-    #stackPeek;  // 将头结点作为栈顶
-    #stkSize = 0;   // 栈的长度
-
-    constructor() {
-        this.#stackPeek = null;
-    }
-
-    /* 获取栈的长度 */
-    get size() {
-        return this.#stkSize;
-    }
-
-    /* 判断栈是否为空 */
-    isEmpty() {
-        return this.size == 0;
-    }
-
-    /* 入栈 */
-    push(num) {
-        const node = new ListNode(num);
-        node.next = this.#stackPeek;
-        this.#stackPeek = node;
-        this.#stkSize++;
-    }
-
-    /* 出栈 */
-    pop() {
-        const num = this.peek();
-        if (!this.#stackPeek) {
-            throw new Error("栈为空!");
-        }
-        this.#stackPeek = this.#stackPeek.next;
-        this.#stkSize--;
-        return num;
-    }
-
-    /* 访问栈顶元素 */
-    peek() {
-        if (!this.#stackPeek) {
-            throw new Error("栈为空!");
-        }
-        return this.#stackPeek.val;
-    }
-
-    /* 将链表转化为 Array 并返回 */
-    toArray() {
-        let node = this.#stackPeek;
-        const res = new Array(this.size);
-        for (let i = res.length - 1; i >= 0; i--) {
-            res[i] = node.val;
-            node = node.next;
-        }
-        return res;
-    }
-}
-
-
-
-
linkedlist_stack.ts
/* 基于链表实现的栈 */
-class LinkedListStack {
-    private stackPeek: ListNode | null;  // 将头结点作为栈顶
-    private stkSize: number = 0;   // 栈的长度
-
-    constructor() {
-        this.stackPeek = null;
-    }
-
-    /* 获取栈的长度 */
-    get size(): number {
-        return this.stkSize;
-    }
-
-    /* 判断栈是否为空 */
-    isEmpty(): boolean {
-        return this.size == 0;
-    }
-
-    /* 入栈 */
-    push(num: number): void {
-        const node = new ListNode(num);
-        node.next = this.stackPeek;
-        this.stackPeek = node;
-        this.stkSize++;
-    }
-
-    /* 出栈 */
-    pop(): number {
-        const num = this.peek();
-        if (!this.stackPeek) {
-            throw new Error("栈为空!");
-        }
-        this.stackPeek = this.stackPeek.next;
-        this.stkSize--;
-        return num;
-    }
-
-    /* 访问栈顶元素 */
-    peek(): number {
-        if (!this.stackPeek) {
-            throw new Error("栈为空!");
-        }
-        return this.stackPeek.val;
-    }
-
-    /* 将链表转化为 Array 并返回 */
-    toArray(): number[] {
-        let node = this.stackPeek;
-        const res = new Array<number>(this.size);
-        for (let i = res.length - 1; i >= 0; i--) {
-            res[i] = node!.val;
-            node = node!.next;
-        }
-        return res;
-    }
-}
-
-
-
-
linkedlist_stack.c

-
-
-
-
linkedlist_stack.cs
/* 基于链表实现的栈 */
-class LinkedListStack
-{
-    private ListNode stackPeek;  // 将头结点作为栈顶
-    private int stkSize = 0;   // 栈的长度
-    public LinkedListStack()
-    {
-        stackPeek = null;
-    }
-    /* 获取栈的长度 */
-    public int size()
-    {
-        return stkSize;
-    }
-    /* 判断栈是否为空 */
-    public bool isEmpty()
-    {
-        return size() == 0;
-    }
-    /* 入栈 */
-    public void push(int num)
-    {
-        ListNode node = new ListNode(num);
-        node.next = stackPeek;
-        stackPeek = node;
-        stkSize++;
-    }
-    /* 出栈 */
-    public int pop()
-    {
-        int num = peek();
-        stackPeek = stackPeek?.next;
-        stkSize--;
-        return num;
-    }
-    /* 访问栈顶元素 */
-    public int peek()
-    {
-        if (size() == 0)
-            throw new Exception();
-        return stackPeek.val;
-    }
-}
-
-
-
-
linkedlist_stack.swift
/* 基于链表实现的栈 */
-class LinkedListStack {
-    private var _peek: ListNode? // 将头结点作为栈顶
-    private var _size = 0 // 栈的长度
-
-    init() {}
-
-    /* 获取栈的长度 */
-    func size() -> Int {
-        _size
-    }
-
-    /* 判断栈是否为空 */
-    func isEmpty() -> Bool {
-        size() == 0
-    }
-
-    /* 入栈 */
-    func push(num: Int) {
-        let node = ListNode(x: num)
-        node.next = _peek
-        _peek = node
-        _size += 1
-    }
-
-    /* 出栈 */
-    @discardableResult
-    func pop() -> Int {
-        let num = peek()
-        _peek = _peek?.next
-        _size -= 1
-        return num
-    }
-
-    /* 访问栈顶元素 */
-    func peek() -> Int {
-        if isEmpty() {
-            fatalError("栈为空")
-        }
-        return _peek!.val
-    }
-}
-
-
-
-
linkedlist_stack.zig

-
-
-
-
-

基于数组的实现

-

使用「数组」实现栈时,考虑将数组的尾部当作栈顶。这样设计下,「入栈」与「出栈」操作就对应在数组尾部「添加元素」与「删除元素」,时间复杂度都为 \(O(1)\)

-
-
-
-

array_stack

-
-
-

array_stack_push

-
-
-

array_stack_pop

-
-
-
-

由于入栈的元素可能是源源不断的,因此可以使用支持动态扩容的「列表」,这样就无需自行实现数组扩容了。以下是示例代码。

-
-
-
-
array_stack.java
/* 基于数组实现的栈 */
-class ArrayStack {
-    private ArrayList<Integer> stack;
-    public ArrayStack() {
-        // 初始化列表(动态数组)
-        stack = new ArrayList<>();
-    }
-
-    /* 获取栈的长度 */
-    public int size() {
-        return stack.size();
-    }
-
-    /* 判断栈是否为空 */
-    public boolean isEmpty() {
-        return size() == 0;
-    }
-
-    /* 入栈 */
-    public void push(int num) {
-        stack.add(num);
-    }
-
-    /* 出栈 */
-    public int pop() {
-        if (isEmpty())
-            throw new EmptyStackException();
-        return stack.remove(size() - 1);
-    }
-
-    /* 访问栈顶元素 */
-    public int peek() {
-        if (isEmpty())
-            throw new EmptyStackException();
-        return stack.get(size() - 1);
-    }
-
-    /* 将 List 转化为 Array 并返回 */
-    public Object[] toArray() {
-        return stack.toArray();
-    }
-}
-
-
-
-
array_stack.cpp
/* 基于数组实现的栈 */
-class ArrayStack {
-private:
-    vector<int> stack;
-
-public:
-    /* 获取栈的长度 */
-    int size() {
-        return stack.size();
-    }
-    /* 判断栈是否为空 */
-    bool empty() {
-        return stack.empty();
-    }
-    /* 入栈 */
-    void push(int num) {
-        stack.push_back(num);
-    }
-    /* 出栈 */
-    void pop() {
-        int oldTop = top();
-        stack.pop_back();
-    }
-    /* 访问栈顶元素 */
-    int top() {
-        if(empty())
-            throw out_of_range("栈为空");
-        return stack.back();
-    }
-};
-
-
-
-
array_stack.py
""" 基于数组实现的栈 """
-class ArrayStack:
-    def __init__(self):
-        self.__stack = []
-
-    """ 获取栈的长度 """
-    def size(self):
-        return len(self.__stack)
-
-    """ 判断栈是否为空 """
-    def is_empty(self):
-        return self.__stack == []
-
-    """ 入栈 """
-    def push(self, item):
-        self.__stack.append(item)
-
-    """ 出栈 """
-    def pop(self):
-        assert not self.is_empty(), "栈为空"
-        return self.__stack.pop()
-
-    """ 访问栈顶元素 """
-    def peek(self):
-        assert not self.is_empty(), "栈为空"
-        return self.__stack[-1]
-
-    """ 返回列表用于打印 """
-    def to_list(self):
-        return self.__stack
-
-
-
-
array_stack.go
/* 基于数组实现的栈 */
-type arrayStack struct {
-    data []int // 数据
-}
-
-func newArrayStack() *arrayStack {
-    return &arrayStack{
-        // 设置栈的长度为 0,容量为 16
-        data: make([]int, 0, 16),
-    }
-}
-
-// size 栈的长度
-func (s *arrayStack) size() int {
-    return len(s.data)
-}
-
-// isEmpty 栈是否为空
-func (s *arrayStack) isEmpty() bool {
-    return s.size() == 0
-}
-
-// push 入栈
-func (s *arrayStack) push(v int) {
-    // 切片会自动扩容
-    s.data = append(s.data, v)
-}
-
-// pop 出栈
-func (s *arrayStack) pop() any {
-    // 弹出栈前,先判断是否为空
-    if s.isEmpty() {
-        return nil
-    }
-    val := s.peek()
-    s.data = s.data[:len(s.data)-1]
-    return val
-}
-
-// peek 获取栈顶元素
-func (s *arrayStack) peek() any {
-    if s.isEmpty() {
-        return nil
-    }
-    val := s.data[len(s.data)-1]
-    return val
-}
-
-
-
-
array_stack.js
/* 基于数组实现的栈 */
-class ArrayStack {
-    stack;
-    constructor() {
-        this.stack = [];
-    }
-    /* 获取栈的长度 */
-    get size() {
-        return this.stack.length;
-    }
-    /* 判断栈是否为空 */
-    empty() {
-        return this.stack.length === 0;
-    }
-    /* 入栈 */
-    push(num) {
-        this.stack.push(num);
-    }
-    /* 出栈 */
-    pop() {
-        if (this.empty())
-            throw new Error("栈为空");
-        return this.stack.pop();
-    }
-    /* 访问栈顶元素 */
-    top() {
-        if (this.empty())
-            throw new Error("栈为空");
-        return this.stack[this.stack.length - 1];
-    }
-};
-
-
-
-
array_stack.ts
/* 基于数组实现的栈 */
-class ArrayStack {
-    private stack: number[];
-    constructor() {
-        this.stack = [];
-    }
-    /* 获取栈的长度 */
-    get size(): number {
-        return this.stack.length;
-    }
-    /* 判断栈是否为空 */
-    empty(): boolean {
-        return this.stack.length === 0;
-    }
-    /* 入栈 */
-    push(num: number): void {
-        this.stack.push(num);
-    }
-    /* 出栈 */
-    pop(): number | undefined {
-        if (this.empty())
-            throw new Error('栈为空');
-        return this.stack.pop();
-    }
-    /* 访问栈顶元素 */
-    top(): number | undefined {
-        if (this.empty())
-            throw new Error('栈为空');
-        return this.stack[this.stack.length - 1];
-    }
-};
-
-
-
-
array_stack.c

-
-
-
-
array_stack.cs
/* 基于数组实现的栈 */
-class ArrayStack
-{
-    private List<int> stack;
-    public ArrayStack()
-    {
-        // 初始化列表(动态数组)
-        stack = new();
-    }
-    /* 获取栈的长度 */
-    public int size()
-    {
-        return stack.Count();
-    }
-    /* 判断栈是否为空 */
-    public bool isEmpty()
-    {
-        return size() == 0;
-    }
-    /* 入栈 */
-    public void push(int num)
-    {
-        stack.Add(num);
-    }
-    /* 出栈 */
-    public int pop()
-    {
-        if (isEmpty())
-            throw new Exception();
-        var val = peek();
-        stack.RemoveAt(size() - 1);
-        return val;
-    }
-    /* 访问栈顶元素 */
-    public int peek()
-    {
-        if (isEmpty())
-            throw new Exception();
-        return stack[size() - 1];
-    }
-}
-
-
-
-
array_stack.swift
/* 基于数组实现的栈 */
-class ArrayStack {
-    private var stack: [Int]
-
-    init() {
-        // 初始化列表(动态数组)
-        stack = []
-    }
-
-    /* 获取栈的长度 */
-    func size() -> Int {
-        stack.count
-    }
-
-    /* 判断栈是否为空 */
-    func isEmpty() -> Bool {
-        stack.isEmpty
-    }
-
-    /* 入栈 */
-    func push(num: Int) {
-        stack.append(num)
-    }
-
-    /* 出栈 */
-    @discardableResult
-    func pop() -> Int {
-        if isEmpty() {
-            fatalError("栈为空")
-        }
-        return stack.removeLast()
-    }
-
-    /* 访问栈顶元素 */
-    func peek() -> Int {
-        if isEmpty() {
-            fatalError("栈为空")
-        }
-        return stack.last!
-    }
-}
-
-
-
-
array_stack.zig

-
-
-
-
-

5.1.3. 两种实现对比

-

支持操作

-

两种实现都支持栈定义中的各项操作,数组实现额外支持随机访问,但这已经超出栈的定义范畴,一般不会用到。

-

时间效率

-

在数组(列表)实现中,入栈与出栈操作都是在预先分配好的连续内存中操作,具有很好的缓存本地性,效率很好。然而,如果入栈时超出数组容量,则会触发扩容机制,那么该次入栈操作的时间复杂度为 \(O(n)\)

-

在链表实现中,链表的扩容非常灵活,不存在上述数组扩容时变慢的问题。然而,入栈操作需要初始化结点对象并修改指针,因而效率不如数组。进一步地思考,如果入栈元素不是 int 而是结点对象,那么就可以省去初始化步骤,从而提升效率。

-

综上所述,当入栈与出栈操作的元素是基本数据类型(例如 int , double )时,则结论如下:

-
    -
  • 数组实现的栈在触发扩容时会变慢,但由于扩容是低频操作,因此 总体效率更高
  • -
  • 链表实现的栈可以提供 更加稳定的效率表现
  • -
-

空间效率

-

在初始化列表时,系统会给列表分配“初始容量”,该容量可能超过我们的需求。并且扩容机制一般是按照特定倍率(比如 2 倍)进行扩容,扩容后的容量也可能超出我们的需求。因此,数组实现栈会造成一定的空间浪费

-

当然,由于结点需要额外存储指针,因此 链表结点比数组元素占用更大

-

综上,我们不能简单地确定哪种实现更加省内存,需要 case-by-case 地分析。

-

5.1.4. 栈典型应用

-
    -
  • 浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。
  • -
  • 程序内存管理。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_stack_and_queue/summary/index.html b/site/chapter_stack_and_queue/summary/index.html deleted file mode 100644 index 92cfcb0d5..000000000 --- a/site/chapter_stack_and_queue/summary/index.html +++ /dev/null @@ -1,1686 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5.4. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

5.4. 小结

-
    -
  • 栈是一种遵循先入后出的数据结构,可以使用数组或链表实现。
  • -
  • 在时间效率方面,栈的数组实现具有更好的平均效率,但扩容时会导致单次入栈操作的时间复杂度劣化至 \(O(n)\) 。相对地,栈的链表实现具有更加稳定的效率表现。
  • -
  • 在空间效率方面,栈的数组实现会造成一定空间浪费,然而链表结点比数组元素占用内存更大。
  • -
  • 队列是一种遵循先入先出的数据结构,可以使用数组或链表实现。对于两种实现的时间效率与空间效率对比,与上述栈的结论相同。
  • -
  • 双向队列的两端都可以添加与删除元素。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_tree/avl_tree.assets/degradation_from_inserting_node.png b/site/chapter_tree/avl_tree.assets/degradation_from_inserting_node.png deleted file mode 100644 index 60faa77fa..000000000 Binary files a/site/chapter_tree/avl_tree.assets/degradation_from_inserting_node.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/degradation_from_removing_node.png b/site/chapter_tree/avl_tree.assets/degradation_from_removing_node.png deleted file mode 100644 index 90f1d155c..000000000 Binary files a/site/chapter_tree/avl_tree.assets/degradation_from_removing_node.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/left_right_rotate.png b/site/chapter_tree/avl_tree.assets/left_right_rotate.png deleted file mode 100644 index a5e73acb6..000000000 Binary files a/site/chapter_tree/avl_tree.assets/left_right_rotate.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/left_rotate.png b/site/chapter_tree/avl_tree.assets/left_rotate.png deleted file mode 100644 index 4c07196dc..000000000 Binary files a/site/chapter_tree/avl_tree.assets/left_rotate.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png b/site/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png deleted file mode 100644 index 949c5c20f..000000000 Binary files a/site/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/right_left_rotate.png b/site/chapter_tree/avl_tree.assets/right_left_rotate.png deleted file mode 100644 index 3e178b894..000000000 Binary files a/site/chapter_tree/avl_tree.assets/right_left_rotate.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/right_rotate_step1.png b/site/chapter_tree/avl_tree.assets/right_rotate_step1.png deleted file mode 100644 index c79812632..000000000 Binary files a/site/chapter_tree/avl_tree.assets/right_rotate_step1.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/right_rotate_step2.png b/site/chapter_tree/avl_tree.assets/right_rotate_step2.png deleted file mode 100644 index 8cabc7121..000000000 Binary files a/site/chapter_tree/avl_tree.assets/right_rotate_step2.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/right_rotate_step3.png b/site/chapter_tree/avl_tree.assets/right_rotate_step3.png deleted file mode 100644 index 8a1386355..000000000 Binary files a/site/chapter_tree/avl_tree.assets/right_rotate_step3.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/right_rotate_step4.png b/site/chapter_tree/avl_tree.assets/right_rotate_step4.png deleted file mode 100644 index 5ede1ed20..000000000 Binary files a/site/chapter_tree/avl_tree.assets/right_rotate_step4.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png b/site/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png deleted file mode 100644 index ea07a8a2e..000000000 Binary files a/site/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree.assets/rotation_cases.png b/site/chapter_tree/avl_tree.assets/rotation_cases.png deleted file mode 100644 index 6cd6128d1..000000000 Binary files a/site/chapter_tree/avl_tree.assets/rotation_cases.png and /dev/null differ diff --git a/site/chapter_tree/avl_tree/index.html b/site/chapter_tree/avl_tree/index.html deleted file mode 100644 index f6672153e..000000000 --- a/site/chapter_tree/avl_tree/index.html +++ /dev/null @@ -1,3544 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 7.4. AVL 树 * - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

7.4. AVL 树 *

-

在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 \(O(\log n)\) 劣化至 \(O(n)\)

-

如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。

-

degradation_from_removing_node

-

再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。

-

degradation_from_inserting_node

-

G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化,进而使得各种操作的时间复杂度均能保持在 \(O(\log n)\) 级别。

-

换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。

-

7.4.1. AVL 树常见术语

-

「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。

-

结点高度

-

在 AVL 树的操作中,需要获取结点「高度 Height」,所以给 AVL 树的结点类添加 height 变量。

-
-
-
-
/* AVL 树结点类 */
-class TreeNode {
-    public int val; // 结点值
-    public int height; // 结点高度
-    public TreeNode left; // 左子结点
-    public TreeNode right; // 右子结点
-    public TreeNode(int x) { val = x; }
-}
-
-
-
-
/* AVL 树结点类 */
-struct TreeNode {
-    int val{};              // 结点值
-    int height = 0;         // 结点高度
-    TreeNode *left{};       // 左子结点
-    TreeNode *right{};      // 右子结点
-    TreeNode() = default;
-    explicit TreeNode(int x) : val(x){}
-};
-
-
-
-
""" AVL 树结点类 """
-class TreeNode:
-    def __init__(self, val=None, left=None, right=None):
-        self.val = val      # 结点值
-        self.height = 0     # 结点高度
-        self.left = left    # 左子结点引用
-        self.right = right  # 右子结点引用
-
-
-
-
/* AVL 树结点类 */
-type TreeNode struct {
-    Val    int       // 结点值
-    Height int       // 结点高度
-    Left   *TreeNode // 左子结点引用
-    Right  *TreeNode // 右子结点引用
-}
-
-
-
-
class TreeNode {
-    val; // 结点值
-    height; //结点高度
-    left; // 左子结点指针
-    right; // 右子结点指针
-    constructor(val, left, right, height) {
-        this.val = val === undefined ? 0 : val;
-        this.height = height === undefined ? 0 : height;
-        this.left = left === undefined ? null : left;
-        this.right = right === undefined ? null : right;
-    }
-}
-
-
-
-
class TreeNode {
-    val: number;            // 结点值
-    height: number;         // 结点高度
-    left: TreeNode | null;  // 左子结点指针
-    right: TreeNode | null; // 右子结点指针
-    constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {
-        this.val = val === undefined ? 0 : val;
-        this.height = height === undefined ? 0 : height; 
-        this.left = left === undefined ? null : left; 
-        this.right = right === undefined ? null : right; 
-    }
-}
-
-
-
-

-
-
-
-
/* AVL 树结点类 */
-class TreeNode {
-    public int val;          // 结点值
-    public int height;       // 结点高度
-    public TreeNode? left;   // 左子结点
-    public TreeNode? right;  // 右子结点
-    public TreeNode(int x) { val = x; }
-}
-
-
-
-
/* AVL 树结点类 */
-class TreeNode {
-    var val: Int // 结点值
-    var height: Int // 结点高度
-    var left: TreeNode? // 左子结点
-    var right: TreeNode? // 右子结点
-
-    init(x: Int) {
-        val = x
-        height = 0
-    }
-}
-
-
-
-

-
-
-
-
-

「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,叶结点的高度为 0 ,空结点的高度为 -1。我们封装两个工具函数,分别用于获取与更新结点的高度。

-
-
-
-
avl_tree.java
/* 获取结点高度 */
-int height(TreeNode node) {
-    // 空结点高度为 -1 ,叶结点高度为 0
-    return node == null ? -1 : node.height;
-}
-
-/* 更新结点高度 */
-void updateHeight(TreeNode node) {
-    // 结点高度等于最高子树高度 + 1
-    node.height = Math.max(height(node.left), height(node.right)) + 1;
-}
-
-
-
-
avl_tree.cpp
/* 获取结点高度 */
-int height(TreeNode* node) {
-    // 空结点高度为 -1 ,叶结点高度为 0
-    return node == nullptr ? -1 : node->height;
-}
-
-/* 更新结点高度 */
-void updateHeight(TreeNode* node) {
-    // 结点高度等于最高子树高度 + 1
-    node->height = max(height(node->left), height(node->right)) + 1;
-}
-
-
-
-
avl_tree.py
""" 获取结点高度 """
-def height(self, node: Optional[TreeNode]) -> int:
-    # 空结点高度为 -1 ,叶结点高度为 0
-    if node is not None:
-        return node.height
-    return -1
-
-""" 更新结点高度 """
-def __update_height(self, node: Optional[TreeNode]):
-    # 结点高度等于最高子树高度 + 1
-    node.height = max([self.height(node.left), self.height(node.right)]) + 1
-
-
-
-
avl_tree.go
/* 获取结点高度 */
-func height(node *TreeNode) int {
-    // 空结点高度为 -1 ,叶结点高度为 0
-    if node != nil {
-        return node.Height
-    }
-    return -1
-}
-
-/* 更新结点高度 */
-func updateHeight(node *TreeNode) {
-    lh := height(node.Left)
-    rh := height(node.Right)
-    // 结点高度等于最高子树高度 + 1
-    if lh > rh {
-        node.Height = lh + 1
-    } else {
-        node.Height = rh + 1
-    }
-}
-
-
-
-
avl_tree.js
/* 获取结点高度 */
-height(node) {
-    // 空结点高度为 -1 ,叶结点高度为 0
-    return node === null ? -1 : node.height;
-}
-
-/* 更新结点高度 */
-updateHeight(node) {
-    // 结点高度等于最高子树高度 + 1
-    node.height = Math.max(this.height(node.left), this.height(node.right)) + 1;
-}
-
-
-
-
avl_tree.ts
/* 获取结点高度 */
-height(node: TreeNode): number {
-    // 空结点高度为 -1 ,叶结点高度为 0
-    return node === null ? -1 : node.height;
-}
-
-/* 更新结点高度 */
-updateHeight(node: TreeNode): void {
-    // 结点高度等于最高子树高度 + 1
-    node.height = Math.max(this.height(node.left), this.height(node.right)) + 1;
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 获取结点高度 */
-public int height(TreeNode? node)
-{
-    // 空结点高度为 -1 ,叶结点高度为 0
-    return node == null ? -1 : node.height;
-}
-
-/* 更新结点高度 */
-private void updateHeight(TreeNode node)
-{
-    // 结点高度等于最高子树高度 + 1
-    node.height = Math.Max(height(node.left), height(node.right)) + 1;
-}
-
-
-
-
avl_tree.swift
/* 获取结点高度 */
-func height(node: TreeNode?) -> Int {
-    // 空结点高度为 -1 ,叶结点高度为 0
-    node == nil ? -1 : node!.height
-}
-
-/* 更新结点高度 */
-func updateHeight(node: TreeNode?) {
-    // 结点高度等于最高子树高度 + 1
-    node?.height = max(height(node: node?.left), height(node: node?.right)) + 1
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-

结点平衡因子

-

结点的「平衡因子 Balance Factor」是 结点的左子树高度减去右子树高度,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。

-
-
-
-
avl_tree.java
/* 获取平衡因子 */
-int balanceFactor(TreeNode node) {
-    // 空结点平衡因子为 0
-    if (node == null) return 0;
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return height(node.left) - height(node.right);
-}
-
-
-
-
avl_tree.cpp
/* 获取平衡因子 */
-int balanceFactor(TreeNode* node) {
-    // 空结点平衡因子为 0
-    if (node == nullptr) return 0;
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return height(node->left) - height(node->right);
-}
-
-
-
-
avl_tree.py
""" 获取平衡因子 """
-def balance_factor(self, node: Optional[TreeNode]) -> int:
-    # 空结点平衡因子为 0
-    if node is None:
-        return 0
-    # 结点平衡因子 = 左子树高度 - 右子树高度
-    return self.height(node.left) - self.height(node.right)
-
-
-
-
avl_tree.go
/* 获取平衡因子 */
-func balanceFactor(node *TreeNode) int {
-    // 空结点平衡因子为 0
-    if node == nil {
-        return 0
-    }
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return height(node.Left) - height(node.Right)
-}
-
-
-
-
avl_tree.js
/* 获取平衡因子 */
-balanceFactor(node) {
-    // 空结点平衡因子为 0
-    if (node === null) return 0;
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return this.height(node.left) - this.height(node.right);
-}
-
-
-
-
avl_tree.ts
/* 获取平衡因子 */
-balanceFactor(node: TreeNode): number {
-    // 空结点平衡因子为 0
-    if (node === null) return 0;
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return this.height(node.left) - this.height(node.right);
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 获取平衡因子 */
-public int balanceFactor(TreeNode? node)
-{
-    // 空结点平衡因子为 0
-    if (node == null) return 0;
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return height(node.left) - height(node.right);
-}
-
-
-
-
avl_tree.swift
/* 获取平衡因子 */
-func balanceFactor(node: TreeNode?) -> Int {
-    // 空结点平衡因子为 0
-    guard let node = node else { return 0 }
-    // 结点平衡因子 = 左子树高度 - 右子树高度
-    return height(node: node.left) - height(node: node.right)
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-
-

Note

-

设平衡因子为 \(f\) ,则一棵 AVL 树的任意结点的平衡因子皆满足 \(-1 \le f \le 1\)

-
-

7.4.2. AVL 树旋转

-

AVL 树的独特之处在于「旋转 Rotation」的操作,其可 在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。

-

我们将平衡因子的绝对值 \(> 1\) 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 右旋、左旋、先右旋后左旋、先左旋后右旋,接下来我们来一起来看看它们是如何操作的。

-

Case 1 - 右旋

-

如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 结点 3。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 node ,将其左子结点记为 child ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。

-
-
-
-

right_rotate_step1

-
-
-

right_rotate_step2

-
-
-

right_rotate_step3

-
-
-

right_rotate_step4

-
-
-
-

进而,如果结点 child 本身有右子结点(记为 grandChild ),则需要在「右旋」中添加一步:将 grandChild 作为 node 的左子结点。

-

right_rotate_with_grandchild

-

“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。

-
-
-
-
avl_tree.java
/* 右旋操作 */
-TreeNode rightRotate(TreeNode node) {
-    TreeNode child = node.left;
-    TreeNode grandChild = child.right;
-    // 以 child 为原点,将 node 向右旋转
-    child.right = node;
-    node.left = grandChild;
-    // 更新结点高度
-    updateHeight(node);
-    updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.cpp
/* 右旋操作 */
-TreeNode* rightRotate(TreeNode* node) {
-    TreeNode* child = node->left;
-    TreeNode* grandChild = child->right;
-    // 以 child 为原点,将 node 向右旋转
-    child->right = node;
-    node->left = grandChild;
-    // 更新结点高度
-    updateHeight(node);
-    updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.py
""" 右旋操作 """
-def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode:
-    child = node.left
-    grand_child = child.right
-    # 以 child 为原点,将 node 向右旋转
-    child.right = node
-    node.left = grand_child
-    # 更新结点高度
-    self.__update_height(node)
-    self.__update_height(child)
-    # 返回旋转后子树的根结点
-    return child
-
-
-
-
avl_tree.go
/* 右旋操作 */
-func rightRotate(node *TreeNode) *TreeNode {
-    child := node.Left
-    grandChild := child.Right
-    // 以 child 为原点,将 node 向右旋转
-    child.Right = node
-    node.Left = grandChild
-    // 更新结点高度
-    updateHeight(node)
-    updateHeight(child)
-    // 返回旋转后子树的根结点
-    return child
-}
-
-
-
-
avl_tree.js
/* 右旋操作 */
-rightRotate(node) {
-    const child = node.left;
-    const grandChild = child.right;
-    // 以 child 为原点,将 node 向右旋转
-    child.right = node;
-    node.left = grandChild;
-    // 更新结点高度
-    this.updateHeight(node);
-    this.updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.ts
/* 右旋操作 */
-rightRotate(node: TreeNode): TreeNode {
-    const child = node.left;
-    const grandChild = child.right;
-    // 以 child 为原点,将 node 向右旋转
-    child.right = node;
-    node.left = grandChild;
-    // 更新结点高度
-    this.updateHeight(node);
-    this.updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 右旋操作 */
-TreeNode? rightRotate(TreeNode? node)
-{
-    TreeNode? child = node.left;
-    TreeNode? grandChild = child?.right;
-    // 以 child 为原点,将 node 向右旋转
-    child.right = node;
-    node.left = grandChild;
-    // 更新结点高度
-    updateHeight(node);
-    updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.swift
/* 右旋操作 */
-func rightRotate(node: TreeNode?) -> TreeNode? {
-    let child = node?.left
-    let grandChild = child?.right
-    // 以 child 为原点,将 node 向右旋转
-    child?.right = node
-    node?.left = grandChild
-    // 更新结点高度
-    updateHeight(node: node)
-    updateHeight(node: child)
-    // 返回旋转后子树的根结点
-    return child
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-

Case 2 - 左旋

-

类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。

-

left_rotate

-

同理,若结点 child 本身有左子结点(记为 grandChild ),则需要在「左旋」中添加一步:将 grandChild 作为 node 的右子结点。

-

left_rotate_with_grandchild

-

观察发现,「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 left 替换为 right 、所有的 right 替换为 left ,即可得到「左旋」代码。

-
-
-
-
avl_tree.java
/* 左旋操作 */
-TreeNode leftRotate(TreeNode node) {
-    TreeNode child = node.right;
-    TreeNode grandChild = child.left;
-    // 以 child 为原点,将 node 向左旋转
-    child.left = node;
-    node.right = grandChild;
-    // 更新结点高度
-    updateHeight(node);
-    updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.cpp
/* 左旋操作 */
-TreeNode* leftRotate(TreeNode* node) {
-    TreeNode* child = node->right;
-    TreeNode* grandChild = child->left;
-    // 以 child 为原点,将 node 向左旋转
-    child->left = node;
-    node->right = grandChild;
-    // 更新结点高度
-    updateHeight(node);
-    updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.py
""" 左旋操作 """
-def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode:
-    child = node.right
-    grand_child = child.left
-    # 以 child 为原点,将 node 向左旋转
-    child.left = node
-    node.right = grand_child
-    # 更新结点高度
-    self.__update_height(node)
-    self.__update_height(child)
-    # 返回旋转后子树的根结点
-    return child
-
-
-
-
avl_tree.go
/* 左旋操作 */
-func leftRotate(node *TreeNode) *TreeNode {
-    child := node.Right
-    grandChild := child.Left
-    // 以 child 为原点,将 node 向左旋转
-    child.Left = node
-    node.Right = grandChild
-    // 更新结点高度
-    updateHeight(node)
-    updateHeight(child)
-    // 返回旋转后子树的根结点
-    return child
-}
-
-
-
-
avl_tree.js
/* 左旋操作 */
-leftRotate(node) {
-    const child = node.right;
-    const grandChild = child.left;
-    // 以 child 为原点,将 node 向左旋转
-    child.left = node;
-    node.right = grandChild;
-    // 更新结点高度
-    this.updateHeight(node);
-    this.updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.ts
/* 左旋操作 */
-leftRotate(node: TreeNode): TreeNode {
-    const child = node.right;
-    const grandChild = child.left;
-    // 以 child 为原点,将 node 向左旋转
-    child.left = node;
-    node.right = grandChild;
-    // 更新结点高度
-    this.updateHeight(node);
-    this.updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 左旋操作 */
-TreeNode? leftRotate(TreeNode? node)
-{
-    TreeNode? child = node.right;
-    TreeNode? grandChild = child?.left;
-    // 以 child 为原点,将 node 向左旋转
-    child.left = node;
-    node.right = grandChild;
-    // 更新结点高度
-    updateHeight(node);
-    updateHeight(child);
-    // 返回旋转后子树的根结点
-    return child;
-}
-
-
-
-
avl_tree.swift
/* 左旋操作 */
-func leftRotate(node: TreeNode?) -> TreeNode? {
-    let child = node?.right
-    let grandChild = child?.left
-    // 以 child 为原点,将 node 向左旋转
-    child?.left = node
-    node?.right = grandChild
-    // 更新结点高度
-    updateHeight(node: node)
-    updateHeight(node: child)
-    // 返回旋转后子树的根结点
-    return child
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-

Case 3 - 先左后右

-

对于下图的失衡结点 3 ,单一使用左旋或右旋都无法使子树恢复平衡,此时需要「先左旋后右旋」,即先对 child 执行「左旋」,再对 node 执行「右旋」。

-

left_right_rotate

-

Case 4 - 先右后左

-

同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 child 执行「右旋」,然后对 node 执行「左旋」。

-

right_left_rotate

-

旋转的选择

-

下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 右旋、左旋、先右后左、先左后右 的旋转操作。

-

rotation_cases

-

具体地,在代码中使用 失衡结点的平衡因子、较高一侧子结点的平衡因子 来确定失衡结点属于上图中的哪种情况。

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
失衡结点的平衡因子子结点的平衡因子应采用的旋转方法
\(>0\) (即左偏树)\(\geq 0\)右旋
\(>0\) (即左偏树)\(<0\)先左旋后右旋
\(<0\) (即右偏树)\(\leq 0\)左旋
\(<0\) (即右偏树)\(>0\)先右旋后左旋
-
-

为方便使用,我们将旋转操作封装成一个函数。至此,我们可以使用此函数来旋转各种失衡情况,使失衡结点重新恢复平衡

-
-
-
-
avl_tree.java
/* 执行旋转操作,使该子树重新恢复平衡 */
-TreeNode rotate(TreeNode node) {
-    // 获取结点 node 的平衡因子
-    int balanceFactor = balanceFactor(node);
-    // 左偏树
-    if (balanceFactor > 1) {
-        if (balanceFactor(node.left) >= 0) {
-            // 右旋
-            return rightRotate(node);
-        } else {
-            // 先左旋后右旋
-            node.left = leftRotate(node.left);
-            return rightRotate(node);
-        }
-    }
-    // 右偏树
-    if (balanceFactor < -1) {
-        if (balanceFactor(node.right) <= 0) {
-            // 左旋
-            return leftRotate(node);
-        } else {
-            // 先右旋后左旋
-            node.right = rightRotate(node.right);
-            return leftRotate(node);
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node;
-}
-
-
-
-
avl_tree.cpp
/* 执行旋转操作,使该子树重新恢复平衡 */
-TreeNode* rotate(TreeNode* node) {
-    // 获取结点 node 的平衡因子
-    int _balanceFactor = balanceFactor(node);
-    // 左偏树
-    if (_balanceFactor > 1) {
-        if (balanceFactor(node->left) >= 0) {
-            // 右旋
-            return rightRotate(node);
-        } else {
-            // 先左旋后右旋
-            node->left = leftRotate(node->left);
-            return rightRotate(node);
-        }
-    }
-    // 右偏树
-    if (_balanceFactor < -1) {
-        if (balanceFactor(node->right) <= 0) {
-            // 左旋
-            return leftRotate(node);
-        } else {
-            // 先右旋后左旋
-            node->right = rightRotate(node->right);
-            return leftRotate(node);
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node;
-}
-
-
-
-
avl_tree.py
""" 执行旋转操作,使该子树重新恢复平衡 """
-def __rotate(self, node: Optional[TreeNode]) -> TreeNode:
-    # 获取结点 node 的平衡因子
-    balance_factor = self.balance_factor(node)
-    # 左偏树
-    if balance_factor > 1:
-        if self.balance_factor(node.left) >= 0:
-            # 右旋
-            return self.__right_rotate(node)
-        else:
-            # 先左旋后右旋
-            node.left = self.__left_rotate(node.left)
-            return self.__right_rotate(node)
-    # 右偏树
-    elif balance_factor < -1:
-        if self.balance_factor(node.right) <= 0:
-            # 左旋
-            return self.__left_rotate(node)
-        else:
-            # 先右旋后左旋
-            node.right = self.__right_rotate(node.right)
-            return self.__left_rotate(node)
-    # 平衡树,无需旋转,直接返回
-    return node
-
-
-
-
avl_tree.go
/* 执行旋转操作,使该子树重新恢复平衡 */
-func rotate(node *TreeNode) *TreeNode {
-    // 获取结点 node 的平衡因子
-    // Go 推荐短变量,这里 bf 指代 balanceFactor
-    bf := balanceFactor(node)
-    // 左偏树
-    if bf > 1 {
-        if balanceFactor(node.Left) >= 0 {
-            // 右旋
-            return rightRotate(node)
-        } else {
-            // 先左旋后右旋
-            node.Left = leftRotate(node.Left)
-            return rightRotate(node)
-        }
-    }
-    // 右偏树
-    if bf < -1 {
-        if balanceFactor(node.Right) <= 0 {
-            // 左旋
-            return leftRotate(node)
-        } else {
-            // 先右旋后左旋
-            node.Right = rightRotate(node.Right)
-            return leftRotate(node)
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node
-}
-
-
-
-
avl_tree.js
/* 执行旋转操作,使该子树重新恢复平衡 */
-rotate(node) {
-    // 获取结点 node 的平衡因子
-    const balanceFactor = this.balanceFactor(node);
-    // 左偏树
-    if (balanceFactor > 1) {
-        if (this.balanceFactor(node.left) >= 0) {
-            // 右旋
-            return this.rightRotate(node);
-        } else {
-            // 先左旋后右旋
-            node.left = this.leftRotate(node.left);
-            return this.rightRotate(node);
-        }
-    }
-    // 右偏树
-    if (balanceFactor < -1) {
-        if (this.balanceFactor(node.right) <= 0) {
-            // 左旋
-            return this.leftRotate(node);
-        } else {
-            // 先右旋后左旋
-            node.right = this.rightRotate(node.right);
-            return this.leftRotate(node);
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node;
-}
-
-
-
-
avl_tree.ts
/* 执行旋转操作,使该子树重新恢复平衡 */
-rotate(node: TreeNode): TreeNode {
-    // 获取结点 node 的平衡因子
-    const balanceFactor = this.balanceFactor(node);
-    // 左偏树
-    if (balanceFactor > 1) {
-        if (this.balanceFactor(node.left) >= 0) {
-            // 右旋
-            return this.rightRotate(node);
-        } else {
-            // 先左旋后右旋
-            node.left = this.leftRotate(node.left);
-            return this.rightRotate(node);
-        }
-    }
-    // 右偏树
-    if (balanceFactor < -1) {
-        if (this.balanceFactor(node.right) <= 0) {
-            // 左旋
-            return this.leftRotate(node);
-        } else {
-            // 先右旋后左旋
-            node.right = this.rightRotate(node.right);
-            return this.leftRotate(node);
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node;
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 执行旋转操作,使该子树重新恢复平衡 */
-TreeNode? rotate(TreeNode? node)
-{
-    // 获取结点 node 的平衡因子
-    int balanceFactorInt = balanceFactor(node);
-    // 左偏树
-    if (balanceFactorInt > 1)
-    {
-        if (balanceFactor(node.left) >= 0)
-        {
-            // 右旋
-            return rightRotate(node);
-        }
-        else
-        {
-            // 先左旋后右旋
-            node.left = leftRotate(node?.left);
-            return rightRotate(node);
-        }
-    }
-    // 右偏树
-    if (balanceFactorInt < -1)
-    {
-        if (balanceFactor(node.right) <= 0)
-        {
-            // 左旋
-            return leftRotate(node);
-        }
-        else
-        {
-            // 先右旋后左旋
-            node.right = rightRotate(node?.right);
-            return leftRotate(node);
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node;
-}
-
-
-
-
avl_tree.swift
/* 执行旋转操作,使该子树重新恢复平衡 */
-func rotate(node: TreeNode?) -> TreeNode? {
-    // 获取结点 node 的平衡因子
-    let balanceFactor = balanceFactor(node: node)
-    // 左偏树
-    if balanceFactor > 1 {
-        if self.balanceFactor(node: node?.left) >= 0 {
-            // 右旋
-            return rightRotate(node: node)
-        } else {
-            // 先左旋后右旋
-            node?.left = leftRotate(node: node?.left)
-            return rightRotate(node: node)
-        }
-    }
-    // 右偏树
-    if balanceFactor < -1 {
-        if self.balanceFactor(node: node?.right) <= 0 {
-            // 左旋
-            return leftRotate(node: node)
-        } else {
-            // 先右旋后左旋
-            node?.right = rightRotate(node: node?.right)
-            return leftRotate(node: node)
-        }
-    }
-    // 平衡树,无需旋转,直接返回
-    return node
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-

7.4.3. AVL 树常用操作

-

插入结点

-

「AVL 树」的结点插入操作与「二叉搜索树」主体类似。不同的是,在插入结点后,从该结点到根结点的路径上会出现一系列「失衡结点」。所以,我们需要从该结点开始,从底至顶地执行旋转操作,使所有失衡结点恢复平衡

-
-
-
-
avl_tree.java
/* 插入结点 */
-TreeNode insert(int val) {
-    root = insertHelper(root, val);
-    return root;
-}
-
-/* 递归插入结点(辅助函数) */
-TreeNode insertHelper(TreeNode node, int val) {
-    if (node == null) return new TreeNode(val);
-    /* 1. 查找插入位置,并插入结点 */
-    if (val < node.val)
-        node.left = insertHelper(node.left, val);
-    else if (val > node.val)
-        node.right = insertHelper(node.right, val);
-    else
-        return node;     // 重复结点不插入,直接返回
-    updateHeight(node);  // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.cpp
/* 插入结点 */
-TreeNode* insert(int val) {
-    root = insertHelper(root, val);
-    return root;
-}
-
-/* 递归插入结点(辅助函数) */
-TreeNode* insertHelper(TreeNode* node, int val) {
-    if (node == nullptr) return new TreeNode(val);
-    /* 1. 查找插入位置,并插入结点 */
-    if (val < node->val)
-        node->left = insertHelper(node->left, val);
-    else if (val > node->val)
-        node->right = insertHelper(node->right, val);
-    else
-        return node;     // 重复结点不插入,直接返回
-    updateHeight(node);  // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.py
""" 插入结点 """
-def insert(self, val) -> TreeNode:
-    self.root = self.__insert_helper(self.root, val)
-    return self.root
-
-""" 递归插入结点(辅助函数)"""
-def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode:
-    if node is None:
-        return TreeNode(val)
-    # 1. 查找插入位置,并插入结点
-    if val < node.val:
-        node.left = self.__insert_helper(node.left, val)
-    elif val > node.val:
-        node.right = self.__insert_helper(node.right, val)
-    else:
-        # 重复结点不插入,直接返回
-        return node
-    # 更新结点高度
-    self.__update_height(node)
-    # 2. 执行旋转操作,使该子树重新恢复平衡
-    return self.__rotate(node)
-
-
-
-
avl_tree.go
/* 插入结点 */
-func (t *avlTree) insert(val int) *TreeNode {
-    t.root = insertHelper(t.root, val)
-    return t.root
-}
-/* 递归插入结点(辅助函数) */
-func insertHelper(node *TreeNode, val int) *TreeNode {
-    if node == nil {
-        return NewTreeNode(val)
-    }
-    /* 1. 查找插入位置,并插入结点 */
-    if val < node.Val {
-        node.Left = insertHelper(node.Left, val)
-    } else if val > node.Val {
-        node.Right = insertHelper(node.Right, val)
-    } else {
-        // 重复结点不插入,直接返回
-        return node
-    }
-    // 更新结点高度
-    updateHeight(node)
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node)
-    // 返回子树的根结点
-    return node
-}
-
-
-
-
avl_tree.js
/* 插入结点 */
-insert(val) {
-    this.root = this.insertHelper(this.root, val);
-    return this.root;
-}
-
-/* 递归插入结点(辅助函数) */
-insertHelper(node, val) {
-    if (node === null) return new TreeNode(val);
-    /* 1. 查找插入位置,并插入结点 */
-    if (val < node.val) node.left = this.insertHelper(node.left, val);
-    else if (val > node.val) node.right = this.insertHelper(node.right, val);
-    else return node; // 重复结点不插入,直接返回
-    this.updateHeight(node); // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = this.rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.ts
/* 插入结点 */
-insert(val: number): TreeNode {
-    this.root = this.insertHelper(this.root, val);
-    return this.root;
-}
-
-/* 递归插入结点(辅助函数) */
-insertHelper(node: TreeNode, val: number): TreeNode {
-    if (node === null) return new TreeNode(val);
-    /* 1. 查找插入位置,并插入结点 */
-    if (val < node.val) {
-        node.left = this.insertHelper(node.left, val);
-    } else if (val > node.val) {
-        node.right = this.insertHelper(node.right, val);
-    } else {
-        return node; // 重复结点不插入,直接返回
-    }
-    this.updateHeight(node); // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = this.rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 插入结点 */
-public TreeNode? insert(int val)
-{
-    root = insertHelper(root, val);
-    return root;
-}
-
-/* 递归插入结点(辅助函数) */
-private TreeNode? insertHelper(TreeNode? node, int val)
-{
-    if (node == null) return new TreeNode(val);
-    /* 1. 查找插入位置,并插入结点 */
-    if (val < node.val)
-        node.left = insertHelper(node.left, val);
-    else if (val > node.val)
-        node.right = insertHelper(node.right, val);
-    else
-        return node;     // 重复结点不插入,直接返回
-    updateHeight(node);  // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.swift
/* 插入结点 */
-@discardableResult
-func insert(val: Int) -> TreeNode? {
-    root = insertHelper(node: root, val: val)
-    return root
-}
-
-/* 递归插入结点(辅助函数) */
-func insertHelper(node: TreeNode?, val: Int) -> TreeNode? {
-    var node = node
-    if node == nil {
-        return TreeNode(x: val)
-    }
-    /* 1. 查找插入位置,并插入结点 */
-    if val < node!.val {
-        node?.left = insertHelper(node: node?.left, val: val)
-    } else if val > node!.val {
-        node?.right = insertHelper(node: node?.right, val: val)
-    } else {
-        return node // 重复结点不插入,直接返回
-    }
-    updateHeight(node: node) // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node: node)
-    // 返回子树的根结点
-    return node
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-

删除结点

-

「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡

-
-
-
-
avl_tree.java
/* 删除结点 */
-TreeNode remove(int val) {
-    root = removeHelper(root, val);
-    return root;
-}
-
-/* 递归删除结点(辅助函数) */
-TreeNode removeHelper(TreeNode node, int val) {
-    if (node == null) return null;
-    /* 1. 查找结点,并删除之 */
-    if (val < node.val)
-        node.left = removeHelper(node.left, val);
-    else if (val > node.val)
-        node.right = removeHelper(node.right, val);
-    else {
-        if (node.left == null || node.right == null) {
-            TreeNode child = node.left != null ? node.left : node.right;
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if (child == null)
-                return null;
-            // 子结点数量 = 1 ,直接删除 node
-            else
-                node = child;
-        } else {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            TreeNode temp = getInOrderNext(node.right);
-            node.right = removeHelper(node.right, temp.val);
-            node.val = temp.val;
-        }
-    }
-    updateHeight(node);  // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-TreeNode getInOrderNext(TreeNode node) {
-    if (node == null) return node;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (node.left != null) {
-        node = node.left;
-    }
-    return node;
-}
-
-
-
-
avl_tree.cpp
/* 删除结点 */
-TreeNode* remove(int val) {
-    root = removeHelper(root, val);
-    return root;
-}
-
-/* 递归删除结点(辅助函数) */
-TreeNode* removeHelper(TreeNode* node, int val) {
-    if (node == nullptr) return nullptr;
-    /* 1. 查找结点,并删除之 */
-    if (val < node->val)
-        node->left = removeHelper(node->left, val);
-    else if (val > node->val)
-        node->right = removeHelper(node->right, val);
-    else {
-        if (node->left == nullptr || node->right == nullptr) {
-            TreeNode* child = node->left != nullptr ? node->left : node->right;
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if (child == nullptr) {
-                delete node;
-                return nullptr;
-            }
-            // 子结点数量 = 1 ,直接删除 node
-            else {
-                delete node;
-                node = child;
-            }
-        } else {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            TreeNode* temp = getInOrderNext(node->right);
-            node->right = removeHelper(node->right, temp->val);
-            node->val = temp->val;
-        }
-    }
-    updateHeight(node);  // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.py
""" 删除结点 """
-def remove(self, val: int):
-    root = self.__remove_helper(self.root, val)
-    return root
-
-""" 递归删除结点(辅助函数) """
-def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]:
-    if node is None:
-        return None
-    # 1. 查找结点,并删除之
-    if val < node.val:
-        node.left = self.__remove_helper(node.left, val)
-    elif val > node.val:
-        node.right = self.__remove_helper(node.right, val)
-    else:
-        if node.left is None or node.right is None:
-            child = node.left or node.right
-            # 子结点数量 = 0 ,直接删除 node 并返回
-            if child is None:
-                return None
-            # 子结点数量 = 1 ,直接删除 node
-            else:
-                node = child
-        else:  # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            temp = self.__get_inorder_next(node.right)
-            node.right = self.__remove_helper(node.right, temp.val)
-            node.val = temp.val
-    # 更新结点高度
-    self.__update_height(node)
-    # 2. 执行旋转操作,使该子树重新恢复平衡
-    return self.__rotate(node)
-
-
-
-
avl_tree.go
/* 删除结点 */
-func (t *avlTree) remove(val int) *TreeNode {
-    root := removeHelper(t.root, val)
-    return root
-}
-
-/* 递归删除结点(辅助函数) */
-func removeHelper(node *TreeNode, val int) *TreeNode {
-    if node == nil {
-        return nil
-    }
-    /* 1. 查找结点,并删除之 */
-    if val < node.Val {
-        node.Left = removeHelper(node.Left, val)
-    } else if val > node.Val {
-        node.Right = removeHelper(node.Right, val)
-    } else {
-        if node.Left == nil || node.Right == nil {
-            child := node.Left
-            if node.Right != nil {
-                child = node.Right
-            }
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if child == nil {
-                return nil
-            } else {
-                // 子结点数量 = 1 ,直接删除 node
-                node = child
-            }
-        } else {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            temp := getInOrderNext(node.Right)
-            node.Right = removeHelper(node.Right, temp.Val)
-            node.Val = temp.Val
-        }
-    }
-    // 更新结点高度
-    updateHeight(node)
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node)
-    // 返回子树的根结点
-    return node
-}
-
-
-
-
avl_tree.js
/* 删除结点 */
-remove(val) {
-    this.root = this.removeHelper(this.root, val);
-    return this.root;
-}
-
-/* 递归删除结点(辅助函数) */
-removeHelper(node, val) {
-    if (node === null) return null;
-    /* 1. 查找结点,并删除之 */
-    if (val < node.val) node.left = this.removeHelper(node.left, val);
-    else if (val > node.val) node.right = this.removeHelper(node.right, val);
-    else {
-        if (node.left === null || node.right === null) {
-            const child = node.left !== null ? node.left : node.right;
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if (child === null) return null;
-            // 子结点数量 = 1 ,直接删除 node
-            else node = child;
-        } else {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            const temp = this.getInOrderNext(node.right);
-            node.right = this.removeHelper(node.right, temp.val);
-            node.val = temp.val;
-        }
-    }
-    this.updateHeight(node); // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = this.rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-getInOrderNext(node) {
-    if (node === null) return node;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (node.left !== null) {
-        node = node.left;
-    }
-    return node;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-getInOrderNext(node) {
-    if (node === null) return node;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (node.left !== null) {
-        node = node.left;
-    }
-    return node;
-}
-
-
-
-
avl_tree.ts
/* 删除结点 */
-remove(val: number): TreeNode {
-    this.root = this.removeHelper(this.root, val);
-    return this.root;
-}
-
-/* 递归删除结点(辅助函数) */
-removeHelper(node: TreeNode, val: number): TreeNode {
-    if (node === null) return null;
-    /* 1. 查找结点,并删除之 */
-    if (val < node.val) {
-        node.left = this.removeHelper(node.left, val);
-    } else if (val > node.val) {
-        node.right = this.removeHelper(node.right, val);
-    } else {
-        if (node.left === null || node.right === null) {
-            const child = node.left !== null ? node.left : node.right;
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if (child === null) {
-                return null;
-            } else {
-                // 子结点数量 = 1 ,直接删除 node
-                 node = child;
-            }
-        } else {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            const temp = this.getInOrderNext(node.right);
-            node.right = this.removeHelper(node.right, temp.val);
-            node.val = temp.val;
-        }
-    }
-    this.updateHeight(node); // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = this.rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-getInOrderNext(node: TreeNode): TreeNode {
-    if (node === null) return node;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (node.left !== null) {
-        node = node.left;
-    }
-    return node;
-}
-
-
-
-
avl_tree.c

-
-
-
-
avl_tree.cs
/* 删除结点 */
-public TreeNode? remove(int val)
-{
-    root = removeHelper(root, val);
-    return root;
-}
-
-/* 递归删除结点(辅助函数) */
-private TreeNode? removeHelper(TreeNode? node, int val)
-{
-    if (node == null) return null;
-    /* 1. 查找结点,并删除之 */
-    if (val < node.val)
-        node.left = removeHelper(node.left, val);
-    else if (val > node.val)
-        node.right = removeHelper(node.right, val);
-    else
-    {
-        if (node.left == null || node.right == null)
-        {
-            TreeNode? child = node.left != null ? node.left : node.right;
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if (child == null)
-                return null;
-            // 子结点数量 = 1 ,直接删除 node
-            else
-                node = child;
-        }
-        else
-        {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            TreeNode? temp = getInOrderNext(node.right);
-            node.right = removeHelper(node.right, temp.val);
-            node.val = temp.val;
-        }
-    }
-    updateHeight(node);  // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node);
-    // 返回子树的根结点
-    return node;
-}
-
-
-
-
avl_tree.swift
/* 删除结点 */
-@discardableResult
-func remove(val: Int) -> TreeNode? {
-    root = removeHelper(node: root, val: val)
-    return root
-}
-
-/* 递归删除结点(辅助函数) */
-func removeHelper(node: TreeNode?, val: Int) -> TreeNode? {
-    var node = node
-    if node == nil {
-        return nil
-    }
-    /* 1. 查找结点,并删除之 */
-    if val < node!.val {
-        node?.left = removeHelper(node: node?.left, val: val)
-    } else if val > node!.val {
-        node?.right = removeHelper(node: node?.right, val: val)
-    } else {
-        if node?.left == nil || node?.right == nil {
-            let child = node?.left != nil ? node?.left : node?.right
-            // 子结点数量 = 0 ,直接删除 node 并返回
-            if child == nil {
-                return nil
-            }
-            // 子结点数量 = 1 ,直接删除 node
-            else {
-                node = child
-            }
-        } else {
-            // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
-            let temp = getInOrderNext(node: node?.right)
-            node?.right = removeHelper(node: node?.right, val: temp!.val)
-            node?.val = temp!.val
-        }
-    }
-    updateHeight(node: node) // 更新结点高度
-    /* 2. 执行旋转操作,使该子树重新恢复平衡 */
-    node = rotate(node: node)
-    // 返回子树的根结点
-    return node
-}
-
-
-
-
avl_tree.zig

-
-
-
-
-

查找结点

-

「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。

-

7.4.4. AVL 树典型应用

-
    -
  • 组织存储大型数据,适用于高频查找、低频增删场景;
  • -
  • 用于建立数据库中的索引系统;
  • -
-
-

为什么红黑树比 AVL 树更受欢迎?

-

红黑树的平衡条件相对宽松,因此在红黑树中插入与删除结点所需的旋转操作相对更少,结点增删操作相比 AVL 树的效率更高。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_tree/binary_search_tree.assets/binary_search_tree.png b/site/chapter_tree/binary_search_tree.assets/binary_search_tree.png deleted file mode 100644 index b1c71f14e..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/binary_search_tree.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_degradation.png b/site/chapter_tree/binary_search_tree.assets/bst_degradation.png deleted file mode 100644 index f7ec99834..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_degradation.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png b/site/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png deleted file mode 100644 index 8153694e9..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_insert.png b/site/chapter_tree/binary_search_tree.assets/bst_insert.png deleted file mode 100644 index 8e64eac32..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_insert.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_remove_case1.png b/site/chapter_tree/binary_search_tree.assets/bst_remove_case1.png deleted file mode 100644 index fd9f5cde8..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_remove_case1.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_remove_case2.png b/site/chapter_tree/binary_search_tree.assets/bst_remove_case2.png deleted file mode 100644 index daf11618e..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_remove_case2.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_1.png b/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_1.png deleted file mode 100644 index ce70ba330..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_1.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_2.png b/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_2.png deleted file mode 100644 index 1f1a2b99b..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_2.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_3.png b/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_3.png deleted file mode 100644 index d9ef7f749..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_3.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_4.png b/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_4.png deleted file mode 100644 index 43da8c11b..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_remove_case3_4.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_search_1.png b/site/chapter_tree/binary_search_tree.assets/bst_search_1.png deleted file mode 100644 index 8257ef078..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_search_1.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_search_2.png b/site/chapter_tree/binary_search_tree.assets/bst_search_2.png deleted file mode 100644 index 3cab42e56..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_search_2.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_search_3.png b/site/chapter_tree/binary_search_tree.assets/bst_search_3.png deleted file mode 100644 index 48d019dcc..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_search_3.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree.assets/bst_search_4.png b/site/chapter_tree/binary_search_tree.assets/bst_search_4.png deleted file mode 100644 index a745e0a54..000000000 Binary files a/site/chapter_tree/binary_search_tree.assets/bst_search_4.png and /dev/null differ diff --git a/site/chapter_tree/binary_search_tree/index.html b/site/chapter_tree/binary_search_tree/index.html deleted file mode 100644 index 1e6b0f36a..000000000 --- a/site/chapter_tree/binary_search_tree/index.html +++ /dev/null @@ -1,2872 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 7.3. 二叉搜索树 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

7.3. 二叉搜索树

-

「二叉搜索树 Binary Search Tree」满足以下条件:

-
    -
  1. 对于根结点,左子树中所有结点的值 \(<\) 根结点的值 \(<\) 右子树中所有结点的值;
  2. -
  3. 任意结点的左子树和右子树也是二叉搜索树,即也满足条件 1.
  4. -
-

binary_search_tree

-

7.3.1. 二叉搜索树的操作

-

查找结点

-

给定目标结点值 num ,可以根据二叉搜索树的性质来查找。我们声明一个结点 cur ,从二叉树的根结点 root 出发,循环比较结点值 cur.valnum 之间的大小关系

-
    -
  • cur.val < num ,说明目标结点在 cur 的右子树中,因此执行 cur = cur.right
  • -
  • cur.val > num ,说明目标结点在 cur 的左子树中,因此执行 cur = cur.left
  • -
  • cur.val = num ,说明找到目标结点,跳出循环并返回该结点即可;
  • -
-
-
-
-

bst_search_1

-
-
-

bst_search_2

-
-
-

bst_search_3

-
-
-

bst_search_4

-
-
-
-

二叉搜索树的查找操作和二分查找算法如出一辙,也是在每轮排除一半情况。循环次数最多为二叉树的高度,当二叉树平衡时,使用 \(O(\log n)\) 时间。

-
-
-
-
binary_search_tree.java
/* 查找结点 */
-TreeNode search(int num) {
-    TreeNode cur = root;
-    // 循环查找,越过叶结点后跳出
-    while (cur != null) {
-        // 目标结点在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 目标结点在 cur 的左子树中
-        else if (cur.val > num) cur = cur.left;
-        // 找到目标结点,跳出循环
-        else break;
-    }
-    // 返回目标结点
-    return cur;
-}
-
-
-
-
binary_search_tree.cpp
/* 查找结点 */
-TreeNode* search(int num) {
-    TreeNode* cur = root;
-    // 循环查找,越过叶结点后跳出
-    while (cur != nullptr) {
-        // 目标结点在 cur 的右子树中
-        if (cur->val < num) cur = cur->right;
-        // 目标结点在 cur 的左子树中
-        else if (cur->val > num) cur = cur->left;
-        // 找到目标结点,跳出循环
-        else break;
-    }
-    // 返回目标结点
-    return cur;
-}
-
-
-
-
binary_search_tree.py
""" 查找结点 """
-def search(self, num: int) -> Optional[TreeNode]:
-    cur = self.root
-    # 循环查找,越过叶结点后跳出
-    while cur is not None:
-        # 目标结点在 cur 的右子树中
-        if cur.val < num:
-            cur = cur.right
-        # 目标结点在 cur 的左子树中
-        elif cur.val > num:
-            cur = cur.left
-        # 找到目标结点,跳出循环
-        else:
-            break
-    return cur
-
-
-
-
binary_search_tree.go
/* 查找结点 */
-func (bst *binarySearchTree) search(num int) *TreeNode {
-    node := bst.root
-    // 循环查找,越过叶结点后跳出
-    for node != nil {
-        if node.Val < num {
-            // 目标结点在 cur 的右子树中
-            node = node.Right
-        } else if node.Val > num {
-            // 目标结点在 cur 的左子树中
-            node = node.Left
-        } else {
-            // 找到目标结点,跳出循环
-            break
-        }
-    }
-    // 返回目标结点
-    return node
-}
-
-
-
-
binary_search_tree.js
/* 查找结点 */
-function search(num) {
-    let cur = root;
-    // 循环查找,越过叶结点后跳出
-    while (cur !== null) {
-        // 目标结点在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 目标结点在 cur 的左子树中
-        else if (cur.val > num) cur = cur.left;
-        // 找到目标结点,跳出循环
-        else break;
-    }
-    // 返回目标结点
-    return cur;
-}
-
-
-
-
binary_search_tree.ts
/* 查找结点 */
-function search(num: number): TreeNode | null {
-    let cur = root;
-    // 循环查找,越过叶结点后跳出
-    while (cur !== null) {
-        if (cur.val < num) {
-            cur = cur.right; // 目标结点在 cur 的右子树中
-        } else if (cur.val > num) {
-            cur = cur.left; // 目标结点在 cur 的左子树中
-        } else {
-            break; // 找到目标结点,跳出循环
-        }
-    }
-    // 返回目标结点
-    return cur;
-}
-
-
-
-
binary_search_tree.c

-
-
-
-
binary_search_tree.cs
/* 查找结点 */
-TreeNode? search(int num)
-{
-    TreeNode? cur = root;
-    // 循环查找,越过叶结点后跳出
-    while (cur != null)
-    {
-        // 目标结点在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 目标结点在 cur 的左子树中
-        else if (cur.val > num) cur = cur.left;
-        // 找到目标结点,跳出循环
-        else break;
-    }
-    // 返回目标结点
-    return cur;
-}
-
-
-
-
binary_search_tree.swift
/* 查找结点 */
-func search(num: Int) -> TreeNode? {
-    var cur = root
-    // 循环查找,越过叶结点后跳出
-    while cur != nil {
-        // 目标结点在 cur 的右子树中
-        if cur!.val < num {
-            cur = cur?.right
-        }
-        // 目标结点在 cur 的左子树中
-        else if cur!.val > num {
-            cur = cur?.left
-        }
-        // 找到目标结点,跳出循环
-        else {
-            break
-        }
-    }
-    // 返回目标结点
-    return cur
-}
-
-
-
-
binary_search_tree.zig

-
-
-
-
-

插入结点

-

给定一个待插入元素 num ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步:

-
    -
  1. 查找插入位置:与查找操作类似,我们从根结点出发,根据当前结点值和 num 的大小关系循环向下搜索,直到越过叶结点(遍历到 \(\text{null}\) )时跳出循环;
  2. -
  3. 在该位置插入结点:初始化结点 num ,将该结点放到 \(\text{null}\) 的位置 ;
  4. -
-

二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。

-

bst_insert

-
-
-
-
binary_search_tree.java
/* 插入结点 */
-TreeNode insert(int num) {
-    // 若树为空,直接提前返回
-    if (root == null) return null;
-    TreeNode cur = root, pre = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur != null) {
-        // 找到重复结点,直接返回
-        if (cur.val == num) return null;
-        pre = cur;
-        // 插入位置在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 插入位置在 cur 的左子树中
-        else cur = cur.left;
-    }
-    // 插入结点 val
-    TreeNode node = new TreeNode(num);
-    if (pre.val < num) pre.right = node;
-    else pre.left = node;
-    return node;
-}
-
-
-
-
binary_search_tree.cpp
/* 插入结点 */
-TreeNode* insert(int num) {
-    // 若树为空,直接提前返回
-    if (root == nullptr) return nullptr;
-    TreeNode *cur = root, *pre = nullptr;
-    // 循环查找,越过叶结点后跳出
-    while (cur != nullptr) {
-        // 找到重复结点,直接返回
-        if (cur->val == num) return nullptr;
-        pre = cur;
-        // 插入位置在 cur 的右子树中
-        if (cur->val < num) cur = cur->right;
-        // 插入位置在 cur 的左子树中
-        else cur = cur->left;
-    }
-    // 插入结点 val
-    TreeNode* node = new TreeNode(num);
-    if (pre->val < num) pre->right = node;
-    else pre->left = node;
-    return node;
-}
-
-
-
-
binary_search_tree.py
""" 插入结点 """
-def insert(self, num: int) -> Optional[TreeNode]:
-    root = self.root
-    # 若树为空,直接提前返回
-    if root is None:
-        return None
-
-    # 循环查找,越过叶结点后跳出
-    cur, pre = root, None
-    while cur is not None:
-        # 找到重复结点,直接返回
-        if cur.val == num:
-            return None
-        pre = cur
-        # 插入位置在 cur 的右子树中
-        if cur.val < num:
-            cur = cur.right
-        # 插入位置在 cur 的左子树中
-        else:
-            cur = cur.left
-
-    # 插入结点 val
-    node = TreeNode(num)
-    if pre.val < num:
-        pre.right = node
-    else:
-        pre.left = node
-    return node
-
-
-
-
binary_search_tree.go
/* 插入结点 */
-func (bst *binarySearchTree) insert(num int) *TreeNode {
-    cur := bst.root
-    // 若树为空,直接提前返回
-    if cur == nil {
-        return nil
-    }
-    // 待插入结点之前的结点位置
-    var pre *TreeNode = nil
-    // 循环查找,越过叶结点后跳出
-    for cur != nil {
-        if cur.Val == num {
-            return nil
-        }
-        pre = cur
-        if cur.Val < num {
-            cur = cur.Right
-        } else {
-            cur = cur.Left
-        }
-    }
-    // 插入结点
-    node := NewTreeNode(num)
-    if pre.Val < num {
-        pre.Right = node
-    } else {
-        pre.Left = node
-    }
-    return cur
-}
-
-
-
-
binary_search_tree.js
/* 插入结点 */
-function insert(num) {
-    // 若树为空,直接提前返回
-    if (root === null) return null;
-    let cur = root, pre = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur !== null) {
-        // 找到重复结点,直接返回
-        if (cur.val === num) return null;
-        pre = cur;
-        // 插入位置在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 插入位置在 cur 的左子树中
-        else cur = cur.left;
-    }
-    // 插入结点 val
-    let node = new Tree.TreeNode(num);
-    if (pre.val < num) pre.right = node;
-    else pre.left = node;
-    return node;
-}
-
-
-
-
binary_search_tree.ts
/* 插入结点 */
-function insert(num: number): TreeNode | null {
-    // 若树为空,直接提前返回
-    if (root === null) {
-        return null;
-    }
-    let cur = root,
-        pre: TreeNode | null = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur !== null) {
-        if (cur.val === num) {
-            return null; // 找到重复结点,直接返回
-        }
-        pre = cur;
-        if (cur.val < num) {
-            cur = cur.right as TreeNode; // 插入位置在 cur 的右子树中
-        } else {
-            cur = cur.left as TreeNode; // 插入位置在 cur 的左子树中
-        }
-    }
-    // 插入结点 val
-    let node = new TreeNode(num);
-    if (pre!.val < num) {
-        pre!.right = node;
-    } else {
-        pre!.left = node;
-    }
-    return node;
-}
-
-
-
-
binary_search_tree.c

-
-
-
-
binary_search_tree.cs
/* 插入结点 */
-TreeNode? insert(int num)
-{
-    // 若树为空,直接提前返回
-    if (root == null) return null;
-    TreeNode? cur = root, pre = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur != null)
-    {
-        // 找到重复结点,直接返回
-        if (cur.val == num) return null;
-        pre = cur;
-        // 插入位置在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 插入位置在 cur 的左子树中
-        else cur = cur.left;
-    }
-
-    // 插入结点 val
-    TreeNode node = new TreeNode(num);
-    if (pre != null)
-    {
-        if (pre.val < num) pre.right = node;
-        else pre.left = node;
-    }
-    return node;
-}
-
-
-
-
binary_search_tree.swift
/* 插入结点 */
-func insert(num: Int) -> TreeNode? {
-    // 若树为空,直接提前返回
-    if root == nil {
-        return nil
-    }
-    var cur = root
-    var pre: TreeNode?
-    // 循环查找,越过叶结点后跳出
-    while cur != nil {
-        // 找到重复结点,直接返回
-        if cur!.val == num {
-            return nil
-        }
-        pre = cur
-        // 插入位置在 cur 的右子树中
-        if cur!.val < num {
-            cur = cur?.right
-        }
-        // 插入位置在 cur 的左子树中
-        else {
-            cur = cur?.left
-        }
-    }
-    // 插入结点 val
-    let node = TreeNode(x: num)
-    if pre!.val < num {
-        pre?.right = node
-    } else {
-        pre?.left = node
-    }
-    return node
-}
-
-
-
-
binary_search_tree.zig

-
-
-
-
-

为了插入结点,需要借助 辅助结点 pre 保存上一轮循环的结点,这样在遍历到 \(\text{null}\) 时,我们也可以获取到其父结点,从而完成结点插入操作。

-

与查找结点相同,插入结点使用 \(O(\log n)\) 时间。

-

删除结点

-

与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况:

-

当待删除结点的子结点数量 \(= 0\),表明待删除结点是叶结点,直接删除即可。

-

bst_remove_case1

-

当待删除结点的子结点数量 \(= 1\),将待删除结点替换为其子结点即可。

-

bst_remove_case2

-

当待删除结点的子结点数量 \(= 2\),删除操作分为三步:

-
    -
  1. 找到待删除结点在 中序遍历序列 中的下一个结点,记为 nex
  2. -
  3. 在树中递归删除结点 nex
  4. -
  5. 使用 nex 替换待删除结点;
  6. -
-
-
-
-

bst_remove_case3_1

-
-
-

bst_remove_case3_2

-
-
-

bst_remove_case3_3

-
-
-

bst_remove_case3_4

-
-
-
-

删除结点操作也使用 \(O(\log n)\) 时间,其中查找待删除结点 \(O(\log n)\) ,获取中序遍历后继结点 \(O(\log n)\)

-
-
-
-
binary_search_tree.java
/* 删除结点 */
-TreeNode remove(int num) {
-    // 若树为空,直接提前返回
-    if (root == null) return null;
-    TreeNode cur = root, pre = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur != null) {
-        // 找到待删除结点,跳出循环
-        if (cur.val == num) break;
-        pre = cur;
-        // 待删除结点在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 待删除结点在 cur 的左子树中
-        else cur = cur.left;
-    }
-    // 若无待删除结点,则直接返回
-    if (cur == null) return null;
-    // 子结点数量 = 0 or 1
-    if (cur.left == null || cur.right == null) {
-        // 当子结点数量 = 0 / 1 时, child = null / 该子结点
-        TreeNode child = cur.left != null ? cur.left : cur.right;
-        // 删除结点 cur
-        if (pre.left == cur) pre.left = child;
-        else pre.right = child;
-    }
-    // 子结点数量 = 2
-    else {
-        // 获取中序遍历中 cur 的下一个结点
-        TreeNode nex = getInOrderNext(cur.right);
-        int tmp = nex.val;
-        // 递归删除结点 nex
-        remove(nex.val);
-        // 将 nex 的值复制给 cur
-        cur.val = tmp;
-    }
-    return cur;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-TreeNode getInOrderNext(TreeNode root) {
-    if (root == null) return root;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (root.left != null) {
-        root = root.left;
-    }
-    return root;
-}
-
-
-
-
binary_search_tree.cpp
/* 删除结点 */
-TreeNode* remove(int num) {
-    // 若树为空,直接提前返回
-    if (root == nullptr) return nullptr;
-    TreeNode *cur = root, *pre = nullptr;
-    // 循环查找,越过叶结点后跳出
-    while (cur != nullptr) {
-        // 找到待删除结点,跳出循环
-        if (cur->val == num) break;
-        pre = cur;
-        // 待删除结点在 cur 的右子树中
-        if (cur->val < num) cur = cur->right;
-        // 待删除结点在 cur 的左子树中
-        else cur = cur->left;
-    }
-    // 若无待删除结点,则直接返回
-    if (cur == nullptr) return nullptr;
-    // 子结点数量 = 0 or 1
-    if (cur->left == nullptr || cur->right == nullptr) {
-        // 当子结点数量 = 0 / 1 时, child = nullptr / 该子结点
-        TreeNode* child = cur->left != nullptr ? cur->left : cur->right;
-        // 删除结点 cur
-        if (pre->left == cur) pre->left = child;
-        else pre->right = child;
-    }
-    // 子结点数量 = 2
-    else {
-        // 获取中序遍历中 cur 的下一个结点
-        TreeNode* nex = getInOrderNext(cur->right);
-        int tmp = nex->val;
-        // 递归删除结点 nex
-        remove(nex->val);
-        // 将 nex 的值复制给 cur
-        cur->val = tmp;
-    }
-    return cur;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-TreeNode* getInOrderNext(TreeNode* root) {
-    if (root == nullptr) return root;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (root->left != nullptr) {
-        root = root->left;
-    }
-    return root;
-}
-
-
-
-
binary_search_tree.py
""" 删除结点 """
-def remove(self, num: int) -> Optional[TreeNode]:
-    root = self.root
-    # 若树为空,直接提前返回
-    if root is None:
-        return None
-
-    # 循环查找,越过叶结点后跳出
-    cur, pre = root, None
-    while cur is not None:
-        # 找到待删除结点,跳出循环
-        if cur.val == num:
-            break
-        pre = cur
-        if cur.val < num:  # 待删除结点在 cur 的右子树中
-            cur = cur.right
-        else:  # 待删除结点在 cur 的左子树中
-            cur = cur.left
-    # 若无待删除结点,则直接返回
-    if cur is None:
-        return None
-
-    # 子结点数量 = 0 or 1
-    if cur.left is None or cur.right is None:
-        # 当子结点数量 = 0 / 1 时, child = null / 该子结点
-        child = cur.left or cur.right
-        # 删除结点 cur
-        if pre.left == cur:
-            pre.left = child
-        else:
-            pre.right = child
-    # 子结点数量 = 2
-    else:
-        # 获取中序遍历中 cur 的下一个结点
-        nex = self.get_inorder_next(cur.right)
-        tmp = nex.val
-        # 递归删除结点 nex
-        self.remove(nex.val)
-        # 将 nex 的值复制给 cur
-        cur.val = tmp
-    return cur
-
-""" 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """
-def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
-    if root is None:
-        return root
-    # 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while root.left is not None:
-        root = root.left
-    return root
-
-
-
-
binary_search_tree.go
/* 删除结点 */
-func (bst *binarySearchTree) remove(num int) *TreeNode {
-    cur := bst.root
-    // 若树为空,直接提前返回
-    if cur == nil {
-        return nil
-    }
-    // 待删除结点之前的结点位置
-    var pre *TreeNode = nil
-    // 循环查找,越过叶结点后跳出
-    for cur != nil {
-        if cur.Val == num {
-            break
-        }
-        pre = cur
-        if cur.Val < num {
-            // 待删除结点在右子树中
-            cur = cur.Right
-        } else {
-            // 待删除结点在左子树中
-            cur = cur.Left
-        }
-    }
-    // 若无待删除结点,则直接返回
-    if cur == nil {
-        return nil
-    }
-    // 子结点数为 0 或 1
-    if cur.Left == nil || cur.Right == nil {
-        var child *TreeNode = nil
-        // 取出待删除结点的子结点
-        if cur.Left != nil {
-            child = cur.Left
-        } else {
-            child = cur.Right
-        }
-        // 将子结点替换为待删除结点
-        if pre.Left == cur {
-            pre.Left = child
-        } else {
-            pre.Right = child
-        }
-        // 子结点数为 2
-    } else {
-        // 获取中序遍历中待删除结点 cur 的下一个结点
-        next := bst.getInOrderNext(cur)
-        temp := next.Val
-        // 递归删除结点 next
-        bst.remove(next.Val)
-        // 将 next 的值复制给 cur
-        cur.Val = temp
-    }
-    return cur
-}
-
-/* 获取中序遍历的下一个结点(仅适用于 root 有左子结点的情况) */
-func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode {
-    if node == nil {
-        return node
-    }
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    for node.Left != nil {
-        node = node.Left
-    }
-    return node
-}
-
-
-
-
binary_search_tree.js
/* 删除结点 */
-function remove(num) {
-    // 若树为空,直接提前返回
-    if (root === null) return null;
-    let cur = root, pre = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur !== null) {
-        // 找到待删除结点,跳出循环
-        if (cur.val === num) break;
-        pre = cur;
-        // 待删除结点在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 待删除结点在 cur 的左子树中
-        else cur = cur.left;
-    }
-    // 若无待删除结点,则直接返回
-    if (cur === null) return null;
-    // 子结点数量 = 0 or 1
-    if (cur.left === null || cur.right === null) {
-        // 当子结点数量 = 0 / 1 时, child = null / 该子结点
-        let child = cur.left !== null ? cur.left : cur.right;
-        // 删除结点 cur
-        if (pre.left === cur) pre.left = child;
-        else pre.right = child;
-    }
-    // 子结点数量 = 2
-    else {
-        // 获取中序遍历中 cur 的下一个结点
-        let nex = getInOrderNext(cur.right);
-        let tmp = nex.val;
-        // 递归删除结点 nex
-        remove(nex.val);
-        // 将 nex 的值复制给 cur
-        cur.val = tmp;
-    }
-    return cur;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-function getInOrderNext(root) {
-    if (root === null) return root;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (root.left !== null) {
-        root = root.left;
-    }
-    return root;
-}
-
-
-
-
binary_search_tree.ts
/* 删除结点 */
-function remove(num: number): TreeNode | null {
-    // 若树为空,直接提前返回
-    if (root === null) {
-        return null;
-    }
-    let cur = root,
-        pre: TreeNode | null = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur !== null) {
-        // 找到待删除结点,跳出循环
-        if (cur.val === num) {
-            break;
-        }
-        pre = cur;
-        if (cur.val < num) {
-            cur = cur.right as TreeNode; // 待删除结点在 cur 的右子树中
-        } else {
-            cur = cur.left as TreeNode; // 待删除结点在 cur 的左子树中
-        }
-    }
-    // 若无待删除结点,则直接返回
-    if (cur === null) {
-        return null;
-    }
-    // 子结点数量 = 0 or 1
-    if (cur.left === null || cur.right === null) {
-        // 当子结点数量 = 0 / 1 时, child = null / 该子结点
-        let child = cur.left !== null ? cur.left : cur.right;
-        // 删除结点 cur
-        if (pre!.left === cur) {
-            pre!.left = child;
-        } else {
-            pre!.right = child;
-        }
-    }
-    // 子结点数量 = 2
-    else {
-        // 获取中序遍历中 cur 的下一个结点
-        let next = getInOrderNext(cur.right);
-        let tmp = next!.val;
-        // 递归删除结点 nex
-        remove(next!.val);
-        // 将 nex 的值复制给 cur
-        cur.val = tmp;
-    }
-    return cur;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-function getInOrderNext(root: TreeNode | null): TreeNode | null {
-    if (root === null) {
-        return null;
-    }
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (root.left !== null) {
-        root = root.left;
-    }
-    return root;
-}
-
-
-
-
binary_search_tree.c

-
-
-
-
binary_search_tree.cs
/* 删除结点 */
-TreeNode? remove(int num)
-{
-    // 若树为空,直接提前返回
-    if (root == null) return null;
-    TreeNode? cur = root, pre = null;
-    // 循环查找,越过叶结点后跳出
-    while (cur != null)
-    {
-        // 找到待删除结点,跳出循环
-        if (cur.val == num) break;
-        pre = cur;
-        // 待删除结点在 cur 的右子树中
-        if (cur.val < num) cur = cur.right;
-        // 待删除结点在 cur 的左子树中
-        else cur = cur.left;
-    }
-    // 若无待删除结点,则直接返回
-    if (cur == null || pre == null) return null;
-    // 子结点数量 = 0 or 1
-    if (cur.left == null || cur.right == null)
-    {
-        // 当子结点数量 = 0 / 1 时, child = null / 该子结点
-        TreeNode? child = cur.left != null ? cur.left : cur.right;
-        // 删除结点 cur
-        if (pre.left == cur)
-        {
-            pre.left = child;
-        }
-        else
-        {
-            pre.right = child;
-        }
-    }
-    // 子结点数量 = 2
-    else
-    {
-        // 获取中序遍历中 cur 的下一个结点
-        TreeNode? nex = getInOrderNext(cur.right);
-        if (nex != null)
-        {
-            int tmp = nex.val;
-            // 递归删除结点 nex
-            remove(nex.val);
-            // 将 nex 的值复制给 cur
-            cur.val = tmp;
-        }
-    }
-    return cur;
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-private TreeNode? getInOrderNext(TreeNode? root)
-{
-    if (root == null) return root;
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while (root.left != null)
-    {
-        root = root.left;
-    }
-    return root;
-}
-
-
-
-
binary_search_tree.swift
/* 删除结点 */
-@discardableResult
-func remove(num: Int) -> TreeNode? {
-    // 若树为空,直接提前返回
-    if root == nil {
-        return nil
-    }
-    var cur = root
-    var pre: TreeNode?
-    // 循环查找,越过叶结点后跳出
-    while cur != nil {
-        // 找到待删除结点,跳出循环
-        if cur!.val == num {
-            break
-        }
-        pre = cur
-        // 待删除结点在 cur 的右子树中
-        if cur!.val < num {
-            cur = cur?.right
-        }
-        // 待删除结点在 cur 的左子树中
-        else {
-            cur = cur?.left
-        }
-    }
-    // 若无待删除结点,则直接返回
-    if cur == nil {
-        return nil
-    }
-    // 子结点数量 = 0 or 1
-    if cur?.left == nil || cur?.right == nil {
-        // 当子结点数量 = 0 / 1 时, child = null / 该子结点
-        let child = cur?.left != nil ? cur?.left : cur?.right
-        // 删除结点 cur
-        if pre?.left === cur {
-            pre?.left = child
-        } else {
-            pre?.right = child
-        }
-    }
-    // 子结点数量 = 2
-    else {
-        // 获取中序遍历中 cur 的下一个结点
-        let nex = getInOrderNext(root: cur?.right)
-        let tmp = nex!.val
-        // 递归删除结点 nex
-        remove(num: nex!.val)
-        // 将 nex 的值复制给 cur
-        cur?.val = tmp
-    }
-    return cur
-}
-
-/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */
-func getInOrderNext(root: TreeNode?) -> TreeNode? {
-    var root = root
-    if root == nil {
-        return root
-    }
-    // 循环访问左子结点,直到叶结点时为最小结点,跳出
-    while root?.left != nil {
-        root = root?.left
-    }
-    return root
-}
-
-
-
-
binary_search_tree.zig

-
-
-
-
-

排序

-

我们知道,「中序遍历」遵循“左 \(\rightarrow\)\(\rightarrow\) 右”的遍历优先级,而二叉搜索树遵循“左子结点 \(<\) 根结点 \(<\) 右子结点”的大小关系。因此,在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小结点,从而得出一条重要性质:二叉搜索树的中序遍历序列是升序的

-

借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 \(O(n)\) 时间,而无需额外排序,非常高效。

-

bst_inorder_traversal

-

7.3.2. 二叉搜索树的效率

-

假设给定 \(n\) 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:

-
    -
  • 查找元素:由于数组是无序的,因此需要遍历数组来确定,使用 \(O(n)\) 时间;
  • -
  • 插入元素:只需将元素添加至数组尾部即可,使用 \(O(1)\) 时间;
  • -
  • 删除元素:先查找元素,使用 \(O(n)\) 时间,再在数组中删除该元素,使用 \(O(n)\) 时间;
  • -
  • 获取最小 / 最大元素:需要遍历数组来确定,使用 \(O(n)\) 时间;
  • -
-

为了得到先验信息,我们也可以预先将数组元素进行排序,得到一个「排序数组」,此时操作效率为:

-
    -
  • 查找元素:由于数组已排序,可以使用二分查找,平均使用 \(O(\log n)\) 时间;
  • -
  • 插入元素:先查找插入位置,使用 \(O(\log n)\) 时间,再插入到指定位置,使用 \(O(n)\) 时间;
  • -
  • 删除元素:先查找元素,使用 \(O(\log n)\) 时间,再在数组中删除该元素,使用 \(O(n)\) 时间;
  • -
  • 获取最小 / 最大元素:数组头部和尾部元素即是最小和最大元素,使用 \(O(1)\) 时间;
  • -
-

观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 \(n\) 很大时有巨大优势

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
无序数组有序数组二叉搜索树
查找指定元素\(O(n)\)\(O(\log n)\)\(O(\log n)\)
插入元素\(O(1)\)\(O(n)\)\(O(\log n)\)
删除元素\(O(n)\)\(O(n)\)\(O(\log n)\)
获取最小 / 最大元素\(O(n)\)\(O(1)\)\(O(\log n)\)
-
-

7.3.3. 二叉搜索树的退化

-

理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 \(\log n\) 轮循环内查找任意结点。

-

如果我们动态地在二叉搜索树中插入与删除结点,则可能导致二叉树退化为链表,此时各种操作的时间复杂度也退化之 \(O(n)\)

-
-

Note

-

在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。

-
-

bst_degradation

-

7.3.4. 二叉搜索树常见应用

-
    -
  • 系统中的多级索引,高效查找、插入、删除操作。
  • -
  • 各种搜索算法的底层数据结构。
  • -
  • 存储数据流,保持其已排序。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_tree/binary_tree.assets/array_representation_complete_binary_tree.png b/site/chapter_tree/binary_tree.assets/array_representation_complete_binary_tree.png deleted file mode 100644 index f369d5d58..000000000 Binary files a/site/chapter_tree/binary_tree.assets/array_representation_complete_binary_tree.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/array_representation_mapping.png b/site/chapter_tree/binary_tree.assets/array_representation_mapping.png deleted file mode 100644 index 5c51843bc..000000000 Binary files a/site/chapter_tree/binary_tree.assets/array_representation_mapping.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/array_representation_with_empty.png b/site/chapter_tree/binary_tree.assets/array_representation_with_empty.png deleted file mode 100644 index 7d1be8dd1..000000000 Binary files a/site/chapter_tree/binary_tree.assets/array_representation_with_empty.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/array_representation_without_empty.png b/site/chapter_tree/binary_tree.assets/array_representation_without_empty.png deleted file mode 100644 index d0f5d9bc1..000000000 Binary files a/site/chapter_tree/binary_tree.assets/array_representation_without_empty.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/balanced_binary_tree.png b/site/chapter_tree/binary_tree.assets/balanced_binary_tree.png deleted file mode 100644 index afc08723f..000000000 Binary files a/site/chapter_tree/binary_tree.assets/balanced_binary_tree.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/site/chapter_tree/binary_tree.assets/binary_tree_add_remove.png deleted file mode 100644 index d95c48f07..000000000 Binary files a/site/chapter_tree/binary_tree.assets/binary_tree_add_remove.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/binary_tree_corner_cases.png b/site/chapter_tree/binary_tree.assets/binary_tree_corner_cases.png deleted file mode 100644 index 6832f68c9..000000000 Binary files a/site/chapter_tree/binary_tree.assets/binary_tree_corner_cases.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/binary_tree_definition.png b/site/chapter_tree/binary_tree.assets/binary_tree_definition.png deleted file mode 100644 index 94252aff8..000000000 Binary files a/site/chapter_tree/binary_tree.assets/binary_tree_definition.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/binary_tree_terminology.png b/site/chapter_tree/binary_tree.assets/binary_tree_terminology.png deleted file mode 100644 index 08c0ccf20..000000000 Binary files a/site/chapter_tree/binary_tree.assets/binary_tree_terminology.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/complete_binary_tree.png b/site/chapter_tree/binary_tree.assets/complete_binary_tree.png deleted file mode 100644 index 8d26549a1..000000000 Binary files a/site/chapter_tree/binary_tree.assets/complete_binary_tree.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/full_binary_tree.png b/site/chapter_tree/binary_tree.assets/full_binary_tree.png deleted file mode 100644 index 37043df95..000000000 Binary files a/site/chapter_tree/binary_tree.assets/full_binary_tree.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree.assets/perfect_binary_tree.png b/site/chapter_tree/binary_tree.assets/perfect_binary_tree.png deleted file mode 100644 index 95f7194be..000000000 Binary files a/site/chapter_tree/binary_tree.assets/perfect_binary_tree.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree/index.html b/site/chapter_tree/binary_tree/index.html deleted file mode 100644 index 9563ec930..000000000 --- a/site/chapter_tree/binary_tree/index.html +++ /dev/null @@ -1,2341 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 7.1. 二叉树(Binary Tree) - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - - - - - -
-
- - - - - - - - - - - - - -

7.1. 二叉树

-

「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。

-
-
-
-
/* 链表结点类 */
-class TreeNode {
-    int val;         // 结点值
-    TreeNode left;   // 左子结点指针
-    TreeNode right;  // 右子结点指针
-    TreeNode(int x) { val = x; }
-}
-
-
-
-
/* 链表结点结构体 */
-struct TreeNode {
-    int val;          // 结点值
-    TreeNode *left;   // 左子结点指针
-    TreeNode *right;  // 右子结点指针
-    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
-};
-
-
-
-
""" 链表结点类 """
-class TreeNode:
-    def __init__(self, val=None, left=None, right=None):
-        self.val = val      # 结点值
-        self.left = left    # 左子结点指针
-        self.right = right  # 右子结点指针
-
-
-
-
/* 链表结点类 */
-type TreeNode struct {
-    Val   int
-    Left  *TreeNode
-    Right *TreeNode
-}
-/* 结点初始化方法 */
-func NewTreeNode(v int) *TreeNode {
-    return &TreeNode{
-        Left:  nil,
-        Right: nil,
-        Val:   v,
-    }
-}
-
-
-
-
/* 链表结点类 */
-function TreeNode(val, left, right) {
-    this.val = (val === undefined ? 0 : val); // 结点值
-    this.left = (left === undefined ? null : left); // 左子结点指针
-    this.right = (right === undefined ? null : right); // 右子结点指针
-}
-
-
-
-
/* 链表结点类 */
-class TreeNode {
-    val: number;
-    left: TreeNode | null;
-    right: TreeNode | null;
-
-    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
-        this.val = val === undefined ? 0 : val; // 结点值
-        this.left = left === undefined ? null : left; // 左子结点指针
-        this.right = right === undefined ? null : right; // 右子结点指针
-    }
-}
-
-
-
-

-
-
-
-
/* 链表结点类 */
-class TreeNode {
-    int val;          // 结点值
-    TreeNode? left;   // 左子结点指针
-    TreeNode? right;  // 右子结点指针
-    TreeNode(int x) { val = x; }
-}
-
-
-
-
/* 链表结点类 */
-class TreeNode {
-    var val: Int // 结点值
-    var left: TreeNode? // 左子结点指针
-    var right: TreeNode? // 右子结点指针
-
-    init(x: Int) {
-        val = x
-    }
-}
-
-
-
-

-
-
-
-
-

结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。

-

除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。

-

binary_tree_definition

-

Fig. 子结点与子树

- -

7.1.1. 二叉树常见术语

-

二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。

-
    -
  • 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点;
  • -
  • 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 \(\text{null}\)
  • -
  • 结点所处「层 Level」:从顶至底依次增加,根结点所处层为 1 ;
  • -
  • 结点「度 Degree」:结点的子结点数量。二叉树中,度的范围是 0, 1, 2 ;
  • -
  • 「边 Edge」:连接两个结点的边,即结点指针;
  • -
  • 二叉树「高度」:二叉树中根结点到最远叶结点走过边的数量;
  • -
  • 结点「深度 Depth」 :根结点到该结点走过边的数量;
  • -
  • 结点「高度 Height」:最远叶结点到该结点走过边的数量;
  • -
-

binary_tree_terminology

-

Fig. 二叉树的常见术语

- -
-

高度与深度的定义

-

值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。

-
-

7.1.2. 二叉树基本操作

-

初始化二叉树。与链表类似,先初始化结点,再构建引用指向(即指针)。

-
-
-
-
binary_tree.java
// 初始化结点
-TreeNode n1 = new TreeNode(1);
-TreeNode n2 = new TreeNode(2);
-TreeNode n3 = new TreeNode(3);
-TreeNode n4 = new TreeNode(4);
-TreeNode n5 = new TreeNode(5);
-// 构建引用指向(即指针)
-n1.left = n2;
-n1.right = n3;
-n2.left = n4;
-n2.right = n5;
-
-
-
-
binary_tree.cpp
/* 初始化二叉树 */
-// 初始化结点
-TreeNode* n1 = new TreeNode(1);
-TreeNode* n2 = new TreeNode(2);
-TreeNode* n3 = new TreeNode(3);
-TreeNode* n4 = new TreeNode(4);
-TreeNode* n5 = new TreeNode(5);
-// 构建引用指向(即指针)
-n1->left = n2;
-n1->right = n3;
-n2->left = n4;
-n2->right = n5;
-
-
-
-
binary_tree.py
""" 初始化二叉树 """
-# 初始化结点
-n1 = TreeNode(val=1)
-n2 = TreeNode(val=2)
-n3 = TreeNode(val=3)
-n4 = TreeNode(val=4)
-n5 = TreeNode(val=5)
-# 构建引用指向(即指针)
-n1.left = n2
-n1.right = n3
-n2.left = n4
-n2.right = n5
-
-
-
-
binary_tree.go
/* 初始化二叉树 */
-// 初始化结点
-n1 := NewTreeNode(1)
-n2 := NewTreeNode(2)
-n3 := NewTreeNode(3)
-n4 := NewTreeNode(4)
-n5 := NewTreeNode(5)
-// 构建引用指向(即指针)
-n1.Left = n2
-n1.Right = n3
-n2.Left = n4
-n2.Right = n5
-
-
-
-
binary_tree.js
/* 初始化二叉树 */
-// 初始化结点
-let n1 = new TreeNode(1),
-    n2 = new TreeNode(2),
-    n3 = new TreeNode(3),
-    n4 = new TreeNode(4),
-    n5 = new TreeNode(5);
-// 构建引用指向(即指针)
-n1.left = n2;
-n1.right = n3;
-n2.left = n4;
-n2.right = n5;
-
-
-
-
binary_tree.ts
/* 初始化二叉树 */
-// 初始化结点
-let n1 = new TreeNode(1),
-    n2 = new TreeNode(2),
-    n3 = new TreeNode(3),
-    n4 = new TreeNode(4),
-    n5 = new TreeNode(5);
-// 构建引用指向(即指针)
-n1.left = n2;
-n1.right = n3;
-n2.left = n4;
-n2.right = n5;
-
-
-
-
binary_tree.c

-
-
-
-
binary_tree.cs
/* 初始化二叉树 */
-// 初始化结点
-TreeNode n1 = new TreeNode(1);
-TreeNode n2 = new TreeNode(2);
-TreeNode n3 = new TreeNode(3);
-TreeNode n4 = new TreeNode(4);
-TreeNode n5 = new TreeNode(5);
-// 构建引用指向(即指针)
-n1.left = n2;
-n1.right = n3;
-n2.left = n4;
-n2.right = n5;
-
-
-
-
binary_tree.swift
// 初始化结点
-let n1 = TreeNode(x: 1)
-let n2 = TreeNode(x: 2)
-let n3 = TreeNode(x: 3)
-let n4 = TreeNode(x: 4)
-let n5 = TreeNode(x: 5)
-// 构建引用指向(即指针)
-n1.left = n2
-n1.right = n3
-n2.left = n4
-n2.right = n5
-
-
-
-
binary_tree.zig

-
-
-
-
-

插入与删除结点。与链表类似,插入与删除结点都可以通过修改指针实现。

-

binary_tree_add_remove

-

Fig. 在二叉树中插入与删除结点

- -
-
-
-
binary_tree.java
TreeNode P = new TreeNode(0);
-// 在 n1 -> n2 中间插入结点 P
-n1.left = P;
-P.left = n2;
-// 删除结点 P
-n1.left = n2;
-
-
-
-
binary_tree.cpp
/* 插入与删除结点 */
-TreeNode* P = new TreeNode(0);
-// 在 n1 -> n2 中间插入结点 P
-n1->left = P;
-P->left = n2;
-// 删除结点 P
-n1->left = n2;
-
-
-
-
binary_tree.py
""" 插入与删除结点 """
-p = TreeNode(0)
-# 在 n1 -> n2 中间插入结点 P
-n1.left = p
-p.left = n2
-# 删除结点 P
-n1.left = n2
-
-
-
-
binary_tree.go
/* 插入与删除结点 */
-// 在 n1 -> n2 中间插入结点 P
-p := NewTreeNode(0)
-n1.Left = p
-p.Left = n2
-// 删除结点 P
-n1.Left = n2
-
-
-
-
binary_tree.js
/* 插入与删除结点 */
-let P = new TreeNode(0);
-// 在 n1 -> n2 中间插入结点 P
-n1.left = P;
-P.left = n2;
-// 删除结点 P
-n1.left = n2;
-
-
-
-
binary_tree.ts
/* 插入与删除结点 */
-const P = new TreeNode(0);
-// 在 n1 -> n2 中间插入结点 P
-n1.left = P;
-P.left = n2;
-// 删除结点 P
-n1.left = n2;
-
-
-
-
binary_tree.c

-
-
-
-
binary_tree.cs
/* 插入与删除结点 */
-TreeNode P = new TreeNode(0);
-// 在 n1 -> n2 中间插入结点 P
-n1.left = P;
-P.left = n2;
-// 删除结点 P
-n1.left = n2;
-
-
-
-
binary_tree.swift
let P = TreeNode(x: 0)
-// 在 n1 -> n2 中间插入结点 P
-n1.left = P
-P.left = n2
-// 删除结点 P
-n1.left = n2
-
-
-
-
binary_tree.zig

-
-
-
-
-
-

Note

-

插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。

-
-

7.1.3. 常见二叉树类型

-

完美二叉树

-

「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,所有结点的度 = 2 ;若树高度 \(= h\) ,则结点总数 \(= 2^{h+1} - 1\) ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。

-
-

Tip

-

在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。

-
-

perfect_binary_tree

-

完全二叉树

-

「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。

-

完全二叉树非常适合用数组来表示。如果按照层序遍历序列的顺序来存储,那么空结点 null 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。

-

complete_binary_tree

-

完满二叉树

-

「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。

-

full_binary_tree

-

平衡二叉树

-

「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 \(\leq 1\)

-

balanced_binary_tree

-

7.1.4. 二叉树的退化

-

当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。

-
    -
  • 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势;
  • -
  • 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 \(O(n)\)
  • -
-

binary_tree_corner_cases

-

Fig. 二叉树的最佳和最差结构

- -

如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
完美二叉树链表
\(i\) 层的结点数量\(2^{i-1}\)\(1\)
树的高度为 \(h\) 时的叶结点数量\(2^h\)\(1\)
树的高度为 \(h\) 时的结点总数\(2^{h+1} - 1\)\(h + 1\)
树的结点总数为 \(n\) 时的高度\(\log_2 (n+1) - 1\)\(n - 1\)
-
-

7.1.5. 二叉树表示方式 *

-

我们一般使用二叉树的「链表表示」,即存储单位为结点 TreeNode ,结点之间通过指针(引用)相连接。本文前述示例代码展示了二叉树在链表表示下的各项基本操作。

-

那能否可以用「数组表示」二叉树呢?答案是肯定的。先来分析一个简单案例,给定一个「完美二叉树」,将结点按照层序遍历的顺序编号(从 0 开始),那么可以推导得出父结点索引与子结点索引之间的「映射公式」:设结点的索引为 \(i\) ,则该结点的左子结点索引为 \(2i + 1\) 、右子结点索引为 \(2i + 2\)

-

本质上,映射公式的作用就是链表中的指针。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。

-

array_representation_mapping

-

然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 null ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,即理论上存在许多种二叉树都符合该层序遍历序列。显然,这种情况无法使用数组来存储二叉树。

-

array_representation_without_empty

-

为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,即在序列中使用特殊符号来显式地表示“空位”。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。

-
-
-
-
/* 二叉树的数组表示 */
-// 使用 int 的包装类 Integer ,就可以使用 null 来标记空位
-Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
-
-
-
-
/* 二叉树的数组表示 */
-// 为了符合数据类型为 int ,使用 int 最大值标记空位
-// 该方法的使用前提是没有结点的值 = INT_MAX
-vector<int> tree = { 1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15 };
-
-
-
-
""" 二叉树的数组表示 """
-# 直接使用 None 来表示空位
-tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
-
-
-
-

-
-
-
-
/* 二叉树的数组表示 */
-// 直接使用 null 来表示空位
-let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
-
-
-
-
/* 二叉树的数组表示 */
-// 直接使用 null 来表示空位
-let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
-
-
-
-

-
-
-
-
/* 二叉树的数组表示 */
-// 使用 int? 可空类型 ,就可以使用 null 来标记空位
-int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
-
-
-
-
/* 二叉树的数组表示 */
-// 使用 Int? 可空类型 ,就可以使用 nil 来标记空位
-let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
-
-
-
-

-
-
-
-
-

array_representation_with_empty

-

回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”。因此,完全二叉树非常适合使用数组来表示。

-

array_representation_complete_binary_tree

-

数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。

- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png b/site/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png deleted file mode 100644 index 9d2a4d039..000000000 Binary files a/site/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png b/site/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png deleted file mode 100644 index 3104a0ba0..000000000 Binary files a/site/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png and /dev/null differ diff --git a/site/chapter_tree/binary_tree_traversal/index.html b/site/chapter_tree/binary_tree_traversal/index.html deleted file mode 100644 index d0d5d2876..000000000 --- a/site/chapter_tree/binary_tree_traversal/index.html +++ /dev/null @@ -1,2229 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 7.2. 二叉树遍历 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

7.2. 二叉树遍历

-

非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。

-

7.2.1. 层序遍历

-

「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。

-

层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。

-

binary_tree_bfs

-

Fig. 二叉树的层序遍历

- -

广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。

-
-
-
-
binary_tree_bfs.java
/* 层序遍历 */
-List<Integer> hierOrder(TreeNode root) {
-    // 初始化队列,加入根结点
-    Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
-    // 初始化一个列表,用于保存遍历序列
-    List<Integer> list = new ArrayList<>();
-    while (!queue.isEmpty()) {
-        TreeNode node = queue.poll();  // 队列出队
-        list.add(node.val);            // 保存结点值
-        if (node.left != null)
-            queue.offer(node.left);    // 左子结点入队
-        if (node.right != null)
-            queue.offer(node.right);   // 右子结点入队
-    }
-    return list;
-}
-
-
-
-
binary_tree_bfs.cpp
/* 层序遍历 */
-vector<int> hierOrder(TreeNode* root) {
-    // 初始化队列,加入根结点
-    queue<TreeNode*> queue;
-    queue.push(root);
-    // 初始化一个列表,用于保存遍历序列
-    vector<int> vec;
-    while (!queue.empty()) {
-        TreeNode* node = queue.front();
-        queue.pop();                 // 队列出队
-        vec.push_back(node->val);    // 保存结点值
-        if (node->left != nullptr)
-            queue.push(node->left);  // 左子结点入队
-        if (node->right != nullptr)
-            queue.push(node->right); // 右子结点入队
-    }
-    return vec;
-}
-
-
-
-
binary_tree_bfs.py
""" 层序遍历 """
-def hier_order(root: Optional[TreeNode]):
-    # 初始化队列,加入根结点
-    queue = collections.deque()
-    queue.append(root)
-    # 初始化一个列表,用于保存遍历序列
-    res = []
-    while queue:
-        node = queue.popleft()       # 队列出队
-        res.append(node.val)         # 保存结点值
-        if node.left is not None:
-            queue.append(node.left)  # 左子结点入队
-        if node.right is not None:
-            queue.append(node.right) # 右子结点入队
-    return res
-
-
-
-
binary_tree_bfs.go
/* 层序遍历 */
-func levelOrder(root *TreeNode) []int {
-    // 初始化队列,加入根结点
-    queue := list.New()
-    queue.PushBack(root)
-    // 初始化一个切片,用于保存遍历序列
-    nums := make([]int, 0)
-    for queue.Len() > 0 {
-        // poll
-        node := queue.Remove(queue.Front()).(*TreeNode)
-        // 保存结点值
-        nums = append(nums, node.Val)
-        if node.Left != nil {
-            // 左子结点入队
-            queue.PushBack(node.Left)
-        }
-        if node.Right != nil {
-            // 右子结点入队
-            queue.PushBack(node.Right)
-        }
-    }
-    return nums
-}
-
-
-
-
binary_tree_bfs.js
/* 层序遍历 */
-function hierOrder(root) {
-    // 初始化队列,加入根结点
-    let queue = [root];
-    // 初始化一个列表,用于保存遍历序列
-    let list = [];
-    while (queue.length) {
-        let node = queue.shift();   // 队列出队
-        list.push(node.val);        // 保存结点值
-        if (node.left)
-            queue.push(node.left);  // 左子结点入队
-        if (node.right)
-            queue.push(node.right); // 右子结点入队
-    }
-    return list;
-}
-
-
-
-
binary_tree_bfs.ts
/* 层序遍历 */
-function hierOrder(root: TreeNode | null): number[] {
-    // 初始化队列,加入根结点
-    const queue = [root];
-    // 初始化一个列表,用于保存遍历序列
-    const list: number[] = [];
-    while (queue.length) {
-        let node = queue.shift() as TreeNode; // 队列出队
-        list.push(node.val); // 保存结点值
-        if (node.left) {
-            queue.push(node.left); // 左子结点入队
-        }
-        if (node.right) {
-            queue.push(node.right); // 右子结点入队
-        }
-    }
-    return list;
-}
-
-
-
-
binary_tree_bfs.c

-
-
-
-
binary_tree_bfs.cs
/* 层序遍历 */
-public List<int?> hierOrder(TreeNode root)
-{
-    // 初始化队列,加入根结点
-    Queue<TreeNode> queue = new();
-    queue.Enqueue(root);
-    // 初始化一个列表,用于保存遍历序列
-    List<int> list = new();
-    while (queue.Count != 0)
-    {
-        TreeNode node = queue.Dequeue(); // 队列出队
-        list.Add(node.val);              // 保存结点值
-        if (node.left != null)
-            queue.Enqueue(node.left);    // 左子结点入队
-        if (node.right != null)
-            queue.Enqueue(node.right);   // 右子结点入队
-    }
-    return list;
-}
-
-
-
-
binary_tree_bfs.swift
/* 层序遍历 */
-func hierOrder(root: TreeNode) -> [Int] {
-    // 初始化队列,加入根结点
-    var queue: [TreeNode] = [root]
-    // 初始化一个列表,用于保存遍历序列
-    var list: [Int] = []
-    while !queue.isEmpty {
-        let node = queue.removeFirst() // 队列出队
-        list.append(node.val) // 保存结点值
-        if let left = node.left {
-            queue.append(left) // 左子结点入队
-        }
-        if let right = node.right {
-            queue.append(right) // 右子结点入队
-        }
-    }
-    return list
-}
-
-
-
-
binary_tree_bfs.zig

-
-
-
-
-

7.2.2. 前序、中序、后序遍历

-

相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。

-

如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。

-

binary_tree_dfs

-

Fig. 二叉树的前 / 中 / 后序遍历

- -
- - - - - - - - - - - - - - - - - - - - - - - - - -
位置含义此处访问结点时对应
橙色圆圈处刚进入此结点,即将访问该结点的左子树前序遍历 Pre-Order Traversal
蓝色圆圈处已访问完左子树,即将访问右子树中序遍历 In-Order Traversal
紫色圆圈处已访问完左子树和右子树,即将返回后序遍历 Post-Order Traversal
-
-
-
-
-
binary_tree_dfs.java
/* 前序遍历 */
-void preOrder(TreeNode root) {
-    if (root == null) return;
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    list.add(root.val);
-    preOrder(root.left);
-    preOrder(root.right);
-}
-
-/* 中序遍历 */
-void inOrder(TreeNode root) {
-    if (root == null) return;
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(root.left);
-    list.add(root.val);
-    inOrder(root.right);
-}
-
-/* 后序遍历 */
-void postOrder(TreeNode root) {
-    if (root == null) return;
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(root.left);
-    postOrder(root.right);
-    list.add(root.val);
-}
-
-
-
-
binary_tree_dfs.cpp
/* 前序遍历 */
-void preOrder(TreeNode* root) {
-    if (root == nullptr) return;
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    vec.push_back(root->val);
-    preOrder(root->left);
-    preOrder(root->right);
-}
-
-/* 中序遍历 */
-void inOrder(TreeNode* root) {
-    if (root == nullptr) return;
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(root->left);
-    vec.push_back(root->val);
-    inOrder(root->right);
-}
-
-/* 后序遍历 */
-void postOrder(TreeNode* root) {
-    if (root == nullptr) return;
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(root->left);
-    postOrder(root->right);
-    vec.push_back(root->val);
-}
-
-
-
-
binary_tree_dfs.py
""" 前序遍历 """
-def pre_order(root: Optional[TreeNode]):
-    if root is None:
-        return
-    # 访问优先级:根结点 -> 左子树 -> 右子树
-    res.append(root.val)
-    pre_order(root=root.left)
-    pre_order(root=root.right)
-
-""" 中序遍历 """
-def in_order(root: Optional[TreeNode]):
-    if root is None:
-        return
-    # 访问优先级:左子树 -> 根结点 -> 右子树
-    in_order(root=root.left)
-    res.append(root.val)
-    in_order(root=root.right)
-
-""" 后序遍历 """
-def post_order(root: Optional[TreeNode]):
-    if root is None:
-        return
-    # 访问优先级:左子树 -> 右子树 -> 根结点
-    post_order(root=root.left)
-    post_order(root=root.right)
-    res.append(root.val)
-
-
-
-
binary_tree_dfs.go
/* 前序遍历 */
-func preOrder(node *TreeNode) {
-    if node == nil {
-        return
-    }
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    nums = append(nums, node.Val)
-    preOrder(node.Left)
-    preOrder(node.Right)
-}
-
-/* 中序遍历 */
-func inOrder(node *TreeNode) {
-    if node == nil {
-        return
-    }
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(node.Left)
-    nums = append(nums, node.Val)
-    inOrder(node.Right)
-}
-
-/* 后序遍历 */
-func postOrder(node *TreeNode) {
-    if node == nil {
-        return
-    }
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(node.Left)
-    postOrder(node.Right)
-    nums = append(nums, node.Val)
-}
-
-
-
-
binary_tree_dfs.js
/* 前序遍历 */
-function preOrder(root){
-    if (root === null) return;
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    list.push(root.val);
-    preOrder(root.left);
-    preOrder(root.right);
-}
-
-/* 中序遍历 */
-function inOrder(root) {
-    if (root === null) return;
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(root.left);
-    list.push(root.val);
-    inOrder(root.right);
-}
-
-/* 后序遍历 */
-function postOrder(root) {
-    if (root === null) return;
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(root.left);
-    postOrder(root.right);
-    list.push(root.val);
-}
-
-
-
-
binary_tree_dfs.ts
/* 前序遍历 */
-function preOrder(root: TreeNode | null): void {
-    if (root === null) {
-        return;
-    }
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    list.push(root.val);
-    preOrder(root.left);
-    preOrder(root.right);
-}
-
-/* 中序遍历 */
-function inOrder(root: TreeNode | null): void {
-    if (root === null) {
-        return;
-    }
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(root.left);
-    list.push(root.val);
-    inOrder(root.right);
-}
-
-/* 后序遍历 */
-function postOrder(root: TreeNode | null): void {
-    if (root === null) {
-        return;
-    }
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(root.left);
-    postOrder(root.right);
-    list.push(root.val);
-}
-
-
-
-
binary_tree_dfs.c

-
-
-
-
binary_tree_dfs.cs
/* 前序遍历 */
-void preOrder(TreeNode? root)
-{
-    if (root == null) return;
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    list.Add(root.val);
-    preOrder(root.left);
-    preOrder(root.right);
-}
-
-/* 中序遍历 */
-void inOrder(TreeNode? root)
-{
-    if (root == null) return;
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(root.left);
-    list.Add(root.val);
-    inOrder(root.right);
-}
-
-/* 后序遍历 */
-void postOrder(TreeNode? root)
-{
-    if (root == null) return;
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(root.left);
-    postOrder(root.right);
-    list.Add(root.val);
-}
-
-
-
-
binary_tree_dfs.swift
/* 前序遍历 */
-func preOrder(root: TreeNode?) {
-    guard let root = root else {
-        return
-    }
-    // 访问优先级:根结点 -> 左子树 -> 右子树
-    list.append(root.val)
-    preOrder(root: root.left)
-    preOrder(root: root.right)
-}
-
-/* 中序遍历 */
-func inOrder(root: TreeNode?) {
-    guard let root = root else {
-        return
-    }
-    // 访问优先级:左子树 -> 根结点 -> 右子树
-    inOrder(root: root.left)
-    list.append(root.val)
-    inOrder(root: root.right)
-}
-
-/* 后序遍历 */
-func postOrder(root: TreeNode?) {
-    guard let root = root else {
-        return
-    }
-    // 访问优先级:左子树 -> 右子树 -> 根结点
-    postOrder(root: root.left)
-    postOrder(root: root.right)
-    list.append(root.val)
-}
-
-
-
-
binary_tree_dfs.zig

-
-
-
-
-
-

Note

-

使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。

-
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/chapter_tree/summary/index.html b/site/chapter_tree/summary/index.html deleted file mode 100644 index dbdc220c9..000000000 --- a/site/chapter_tree/summary/index.html +++ /dev/null @@ -1,1696 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 7.5. 小结 - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 跳转至 - - -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - -
-
-
- - - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -

7.5. 小结

-
    -
  • 二叉树是一种非线性数据结构,代表着“一分为二”的分治逻辑。二叉树的结点包含「值」和两个「指针」,分别指向左子结点和右子结点。
  • -
  • 选定二叉树中某结点,将其左(右)子结点以下形成的树称为左(右)子树。
  • -
  • 二叉树的术语较多,包括根结点、叶结点、层、度、边、高度、深度等。
  • -
  • 二叉树的初始化、结点插入、结点删除操作与链表的操作方法类似。
  • -
  • 常见的二叉树类型包括完美二叉树、完全二叉树、完满二叉树、平衡二叉树。完美二叉树是理想状态,链表则是退化后的最差状态。
  • -
  • -

    二叉树可以使用数组表示,具体做法是将结点值和空位按照层序遍历的顺序排列,并基于父结点和子结点之间的索引映射公式实现指针。

    -
  • -
  • -

    二叉树层序遍历是一种广度优先搜索,体现着“一圈一圈向外”的层进式遍历方式,通常借助队列来实现。

    -
  • -
  • 前序、中序、后序遍历是深度优先搜索,体现着“走到头、再回头继续”的回溯遍历方式,通常使用递归实现。
  • -
  • 二叉搜索树是一种高效的元素查找数据结构,查找、插入、删除操作的时间复杂度皆为 \(O(\log n)\) 。二叉搜索树退化为链表后,各项时间复杂度劣化至 \(O(n)\) ,因此如何避免退化是非常重要的课题。
  • -
  • AVL 树又称平衡二叉搜索树,其通过旋转操作,使得在不断插入与删除结点后,仍然可以保持二叉树的平衡(不退化)。
  • -
  • AVL 树的旋转操作分为右旋、左旋、先右旋后左旋、先左旋后右旋。在插入或删除结点后,AVL 树会从底至顶地执行旋转操作,使树恢复平衡。
  • -
- - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/index.assets/animation.gif b/site/index.assets/animation.gif deleted file mode 100644 index 60f53f20b..000000000 Binary files a/site/index.assets/animation.gif and /dev/null differ diff --git a/site/index.assets/comment.gif b/site/index.assets/comment.gif deleted file mode 100644 index e7a74cf9e..000000000 Binary files a/site/index.assets/comment.gif and /dev/null differ diff --git a/site/index.assets/conceptual_rendering.png b/site/index.assets/conceptual_rendering.png deleted file mode 100644 index 87c5181a3..000000000 Binary files a/site/index.assets/conceptual_rendering.png and /dev/null differ diff --git a/site/index.assets/running_code.gif b/site/index.assets/running_code.gif deleted file mode 100644 index dfe175310..000000000 Binary files a/site/index.assets/running_code.gif and /dev/null differ diff --git a/site/index.html b/site/index.html deleted file mode 100644 index 3a762a296..000000000 --- a/site/index.html +++ /dev/null @@ -1,1687 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - Hello 算法 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
- - - - -
- - -
- -
- - - - - - -
-
- - - - - -
-
-
- - - - -
-
-
- - - - - -
-
-
- - - -
-
-
- - - -
-
- - - - - - - - - - - - - -
-
-
-

-conceptual_rendering -




-

《 Hello,算法 》

-

动画图解、能运行、可提问的
数据结构与算法快速入门教程

-

github-stars

-
@Krahets
-

-
-
-
-
-

「清晰动画讲解」

- -

动画诠释重点,平滑学习曲线
电脑、平板、手机全终端阅读

- -

algorithm_animation

-
-

"A picture is worth a thousand words."

-

“一图胜千言”

-
-
-

「代码实践导向」

- -

提供经典算法的清晰实现与测试代码
多种语言,详细注释,皆可一键运行

- -

running_code

-
-

"Talk is cheap. Show me the code."

-

“少吹牛,看代码”

-
-
-

「可讨论与提问」

- -

作者一般 72h 内回复评论问题
与小伙伴们一起讨论学习进步

- -

comment

-
-

“追风赶月莫停留,平芜尽处是春山”

-

一起加油!

-
-
-

推荐语

- -
-

Quote

-

“一本通俗易懂的数据结构与算法入门书,引导读者手脑并用地学习,强烈推荐算法初学者阅读。”

-

—— 邓俊辉,清华大学计算机系教授

-
-

致谢

- -

感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们是:

-

- - - -

- -
- - - - - - - -

评论

- - - - - - -
-
- - - - -
- - - -
- -
- - - - -
- -
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/overrides/partials/comments.html b/site/overrides/partials/comments.html deleted file mode 100644 index bac275a6e..000000000 --- a/site/overrides/partials/comments.html +++ /dev/null @@ -1,50 +0,0 @@ -{% if page.meta.comments %} -

{{ lang.t("meta.comments") }}

- - - - -{% endif %} \ No newline at end of file diff --git a/site/search/search_index.json b/site/search/search_index.json deleted file mode 100644 index 351c13f32..000000000 --- a/site/search/search_index.json +++ /dev/null @@ -1 +0,0 @@ -{"config":{"lang":["ja"],"separator":"[\\s\\-\uff0c\u3002]+","pipeline":["stemmer"]},"docs":[{"location":"","title":"Home","text":"

\u300a Hello\uff0c\u7b97\u6cd5 \u300b

\u52a8\u753b\u56fe\u89e3\u3001\u80fd\u8fd0\u884c\u3001\u53ef\u63d0\u95ee\u7684\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u5feb\u901f\u5165\u95e8\u6559\u7a0b

@Krahets

\u300c\u6e05\u6670\u52a8\u753b\u8bb2\u89e3\u300d

\u52a8\u753b\u8be0\u91ca\u91cd\u70b9\uff0c\u5e73\u6ed1\u5b66\u4e60\u66f2\u7ebf\u7535\u8111\u3001\u5e73\u677f\u3001\u624b\u673a\u5168\u7ec8\u7aef\u9605\u8bfb

\"A picture is worth a thousand words.\"

\u201c\u4e00\u56fe\u80dc\u5343\u8a00\u201d

\u300c\u4ee3\u7801\u5b9e\u8df5\u5bfc\u5411\u300d

\u63d0\u4f9b\u7ecf\u5178\u7b97\u6cd5\u7684\u6e05\u6670\u5b9e\u73b0\u4e0e\u6d4b\u8bd5\u4ee3\u7801\u591a\u79cd\u8bed\u8a00\uff0c\u8be6\u7ec6\u6ce8\u91ca\uff0c\u7686\u53ef\u4e00\u952e\u8fd0\u884c

\"Talk is cheap. Show me the code.\"

\u201c\u5c11\u5439\u725b\uff0c\u770b\u4ee3\u7801\u201d

\u300c\u53ef\u8ba8\u8bba\u4e0e\u63d0\u95ee\u300d

\u4f5c\u8005\u4e00\u822c 72h \u5185\u56de\u590d\u8bc4\u8bba\u95ee\u9898\u4e0e\u5c0f\u4f19\u4f34\u4eec\u4e00\u8d77\u8ba8\u8bba\u5b66\u4e60\u8fdb\u6b65

\u201c\u8ffd\u98ce\u8d76\u6708\u83ab\u505c\u7559\uff0c\u5e73\u829c\u5c3d\u5904\u662f\u6625\u5c71\u201d

\u4e00\u8d77\u52a0\u6cb9\uff01

\u63a8\u8350\u8bed

Quote

\u201c\u4e00\u672c\u901a\u4fd7\u6613\u61c2\u7684\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u5165\u95e8\u4e66\uff0c\u5f15\u5bfc\u8bfb\u8005\u624b\u8111\u5e76\u7528\u5730\u5b66\u4e60\uff0c\u5f3a\u70c8\u63a8\u8350\u7b97\u6cd5\u521d\u5b66\u8005\u9605\u8bfb\u3002\u201d

\u2014\u2014 \u9093\u4fca\u8f89\uff0c\u6e05\u534e\u5927\u5b66\u8ba1\u7b97\u673a\u7cfb\u6559\u6388

\u81f4\u8c22

\u611f\u8c22\u672c\u5f00\u6e90\u4e66\u7684\u6bcf\u4e00\u4f4d\u64b0\u7a3f\u4eba\uff0c\u662f\u4ed6\u4eec\u7684\u65e0\u79c1\u5949\u732e\u8ba9\u8fd9\u672c\u4e66\u53d8\u5f97\u66f4\u597d\uff0c\u4ed6\u4eec\u662f\uff1a

"},{"location":"chapter_array_and_linkedlist/array/","title":"4.1. \u6570\u7ec4","text":"

\u300c\u6570\u7ec4 Array\u300d\u662f\u4e00\u79cd\u5c06 \u76f8\u540c\u7c7b\u578b\u5143\u7d20 \u5b58\u50a8\u5728 \u8fde\u7eed\u5185\u5b58\u7a7a\u95f4 \u7684\u6570\u636e\u7ed3\u6784\uff0c\u5c06\u5143\u7d20\u5728\u6570\u7ec4\u4e2d\u7684\u4f4d\u7f6e\u79f0\u4e3a\u5143\u7d20\u7684\u300c\u7d22\u5f15 Index\u300d\u3002

Fig. \u6570\u7ec4\u5b9a\u4e49\u4e0e\u5b58\u50a8\u65b9\u5f0f

Note

\u89c2\u5bdf\u4e0a\u56fe\uff0c\u6211\u4eec\u53d1\u73b0 \u6570\u7ec4\u9996\u5143\u7d20\u7684\u7d22\u5f15\u4e3a \\(0\\) \u3002\u4f60\u53ef\u80fd\u4f1a\u60f3\uff0c\u8fd9\u5e76\u4e0d\u7b26\u5408\u65e5\u5e38\u4e60\u60ef\uff0c\u9996\u4e2a\u5143\u7d20\u7684\u7d22\u5f15\u4e3a\u4ec0\u4e48\u4e0d\u662f \\(1\\) \u5462\uff0c\u8fd9\u4e0d\u662f\u66f4\u52a0\u81ea\u7136\u5417\uff1f\u6211\u8ba4\u540c\u4f60\u7684\u60f3\u6cd5\uff0c\u4f46\u8bf7\u5148\u8bb0\u4f4f\u8fd9\u4e2a\u8bbe\u5b9a\uff0c\u540e\u9762\u8bb2\u5185\u5b58\u5730\u5740\u8ba1\u7b97\u65f6\uff0c\u6211\u4f1a\u5c1d\u8bd5\u89e3\u7b54\u8fd9\u4e2a\u95ee\u9898\u3002

\u6570\u7ec4\u6709\u591a\u79cd\u521d\u59cb\u5316\u5199\u6cd5\u3002\u6839\u636e\u5b9e\u9645\u9700\u8981\uff0c\u9009\u4ee3\u7801\u6700\u77ed\u7684\u90a3\u4e00\u79cd\u5c31\u597d\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array.java
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n
array.cpp
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nint* arr = new int[5];\nint* nums = new int[5] { 1, 3, 2, 5, 4 };\n
array.py
\"\"\" \u521d\u59cb\u5316\u6570\u7ec4 \"\"\"\narr = [0] * 5  # [ 0, 0, 0, 0, 0 ]\nnums = [1, 3, 2, 5, 4]  \n
array.go
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nvar arr [5]int\n// \u5728 Go \u4e2d\uff0c\u6307\u5b9a\u957f\u5ea6\u65f6\uff08[5]int\uff09\u4e3a\u6570\u7ec4\uff0c\u4e0d\u6307\u5b9a\u957f\u5ea6\u65f6\uff08[]int\uff09\u4e3a\u5207\u7247\n// \u7531\u4e8e Go \u7684\u6570\u7ec4\u88ab\u8bbe\u8ba1\u4e3a\u5728\u7f16\u8bd1\u671f\u786e\u5b9a\u957f\u5ea6\uff0c\u56e0\u6b64\u53ea\u80fd\u4f7f\u7528\u5e38\u91cf\u6765\u6307\u5b9a\u957f\u5ea6\n// \u4e3a\u4e86\u65b9\u4fbf\u5b9e\u73b0\u6269\u5bb9 extend() \u65b9\u6cd5\uff0c\u4ee5\u4e0b\u5c06\u5207\u7247\uff08Slice\uff09\u770b\u4f5c\u6570\u7ec4\uff08Array\uff09\nnums := []int{1, 3, 2, 5, 4}\n
array.js
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nvar arr = new Array(5).fill(0);\nvar nums = [1, 3, 2, 5, 4];\n
array.ts
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nlet arr: number[] = new Array(5).fill(0);\nlet nums: number[] = [1, 3, 2, 5, 4];\n
array.c
\n
array.cs
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n
array.swift
/* \u521d\u59cb\u5316\u6570\u7ec4 */\nlet arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]\nlet nums = [1, 3, 2, 5, 4]\n
array.zig
// \u521d\u59cb\u5316\u6570\u7ec4\nvar arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 }\nvar nums = [_]i32{ 1, 3, 2, 5, 4 };\n
"},{"location":"chapter_array_and_linkedlist/array/#411","title":"4.1.1. \u6570\u7ec4\u4f18\u70b9","text":"

\u5728\u6570\u7ec4\u4e2d\u8bbf\u95ee\u5143\u7d20\u975e\u5e38\u9ad8\u6548\u3002\u8fd9\u662f\u56e0\u4e3a\u5728\u6570\u7ec4\u4e2d\uff0c\u8ba1\u7b97\u5143\u7d20\u7684\u5185\u5b58\u5730\u5740\u975e\u5e38\u5bb9\u6613\u3002\u7ed9\u5b9a\u6570\u7ec4\u9996\u4e2a\u5143\u7d20\u7684\u5730\u5740\u3001\u548c\u4e00\u4e2a\u5143\u7d20\u7684\u7d22\u5f15\uff0c\u5229\u7528\u4ee5\u4e0b\u516c\u5f0f\u53ef\u4ee5\u76f4\u63a5\u8ba1\u7b97\u5f97\u5230\u8be5\u5143\u7d20\u7684\u5185\u5b58\u5730\u5740\uff0c\u4ece\u800c\u76f4\u63a5\u8bbf\u95ee\u6b64\u5143\u7d20\u3002

Fig. \u6570\u7ec4\u5143\u7d20\u7684\u5185\u5b58\u5730\u5740\u8ba1\u7b97

// \u5143\u7d20\u5185\u5b58\u5730\u5740 = \u6570\u7ec4\u5185\u5b58\u5730\u5740 + \u5143\u7d20\u957f\u5ea6 * \u5143\u7d20\u7d22\u5f15\nelementAddr = firtstElementAddr + elementLength * elementIndex\n

\u4e3a\u4ec0\u4e48\u6570\u7ec4\u5143\u7d20\u7d22\u5f15\u4ece 0 \u5f00\u59cb\u7f16\u53f7\uff1f \u6839\u636e\u5730\u5740\u8ba1\u7b97\u516c\u5f0f\uff0c\u7d22\u5f15\u672c\u8d28\u4e0a\u8868\u793a\u7684\u662f\u5185\u5b58\u5730\u5740\u504f\u79fb\u91cf\uff0c\u9996\u4e2a\u5143\u7d20\u7684\u5730\u5740\u504f\u79fb\u91cf\u662f \\(0\\) \uff0c\u90a3\u4e48\u7d22\u5f15\u662f \\(0\\) \u4e5f\u5c31\u5f88\u81ea\u7136\u4e86\u3002

\u8bbf\u95ee\u5143\u7d20\u7684\u9ad8\u6548\u6027\u5e26\u6765\u4e86\u8bb8\u591a\u4fbf\u5229\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 \\(O(1)\\) \u65f6\u95f4\u5185\u968f\u673a\u83b7\u53d6\u4e00\u4e2a\u6570\u7ec4\u4e2d\u7684\u5143\u7d20\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array.java
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nint randomAccess(int[] nums) {\n// \u5728\u533a\u95f4 [0, nums.length) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nint randomIndex = ThreadLocalRandom.current().\nnextInt(0, nums.length);\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nint randomNum = nums[randomIndex];\nreturn randomNum;\n}\n
array.cpp
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nint randomAccess(int* nums, int size) {\n// \u5728\u533a\u95f4 [0, size) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nint randomIndex = rand() % size;\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nint randomNum = nums[randomIndex];\nreturn randomNum;\n}\n
array.py
\"\"\" \u968f\u673a\u8bbf\u95ee\u5143\u7d20 \"\"\"\ndef random_access(nums):\n# \u5728\u533a\u95f4 [0, len(nums)-1] \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nrandom_index = random.randint(0, len(nums) - 1)\n# \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nrandom_num = nums[random_index]\nreturn random_num\n
array.go
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nfunc randomAccess(nums []int) (randomNum int) {\n// \u5728\u533a\u95f4 [0, nums.length) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nrandomIndex := rand.Intn(len(nums))\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nrandomNum = nums[randomIndex]\nreturn\n}\n
array.js
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nfunction randomAccess(nums) {\n// \u5728\u533a\u95f4 [0, nums.length) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nconst random_index = Math.floor(Math.random() * nums.length);\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nconst random_num = nums[random_index];\nreturn random_num;\n}\n
array.ts
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nfunction randomAccess(nums: number[]): number {\n// \u5728\u533a\u95f4 [0, nums.length) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nconst random_index = Math.floor(Math.random() * nums.length);\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nconst random_num = nums[random_index];\nreturn random_num;\n}\n
array.c
\n
array.cs
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nint RandomAccess(int[] nums)\n{\nRandom random=new();\n// \u5728\u533a\u95f4 [0, nums.Length) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nint randomIndex = random.Next(nums.Length);\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nint randomNum = nums[randomIndex];\nreturn randomNum;\n}\n
array.swift
/* \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20 */\nfunc randomAccess(nums: [Int]) -> Int {\n// \u5728\u533a\u95f4 [0, nums.count) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6570\u5b57\nlet randomIndex = nums.indices.randomElement()!\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nlet randomNum = nums[randomIndex]\nreturn randomNum\n}\n
array.zig
// \u968f\u673a\u8fd4\u56de\u4e00\u4e2a\u6570\u7ec4\u5143\u7d20\npub fn randomAccess(nums: []i32) i32 {\n// \u5728\u533a\u95f4 [0, nums.len) \u4e2d\u968f\u673a\u62bd\u53d6\u4e00\u4e2a\u6574\u6570\nvar randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len);\n// \u83b7\u53d6\u5e76\u8fd4\u56de\u968f\u673a\u5143\u7d20\nvar randomNum = nums[randomIndex];\nreturn randomNum;\n}\n
"},{"location":"chapter_array_and_linkedlist/array/#412","title":"4.1.2. \u6570\u7ec4\u7f3a\u70b9","text":"

\u6570\u7ec4\u5728\u521d\u59cb\u5316\u540e\u957f\u5ea6\u4e0d\u53ef\u53d8\u3002\u7531\u4e8e\u7cfb\u7edf\u65e0\u6cd5\u4fdd\u8bc1\u6570\u7ec4\u4e4b\u540e\u7684\u5185\u5b58\u7a7a\u95f4\u662f\u53ef\u7528\u7684\uff0c\u56e0\u6b64\u6570\u7ec4\u957f\u5ea6\u65e0\u6cd5\u6269\u5c55\u3002\u800c\u82e5\u5e0c\u671b\u6269\u5bb9\u6570\u7ec4\uff0c\u5219\u9700\u65b0\u5efa\u4e00\u4e2a\u6570\u7ec4\uff0c\u7136\u540e\u628a\u539f\u6570\u7ec4\u5143\u7d20\u4f9d\u6b21\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\uff0c\u5728\u6570\u7ec4\u5f88\u5927\u7684\u60c5\u51b5\u4e0b\uff0c\u8fd9\u662f\u975e\u5e38\u8017\u65f6\u7684\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array.java
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nint[] extend(int[] nums, int enlarge) {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nint[] res = new int[nums.length + enlarge];\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor (int i = 0; i < nums.length; i++) {\nres[i] = nums[i];\n}\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res;\n}\n
array.cpp
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nint* extend(int* nums, int size, int enlarge) {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nint* res = new int[size + enlarge];\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor (int i = 0; i < size; i++) {\nres[i] = nums[i];\n}\n// \u91ca\u653e\u5185\u5b58\ndelete[] nums;\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res;\n}\n
array.py
\"\"\" \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 \"\"\"\n# \u8bf7\u6ce8\u610f\uff0cPython \u7684 list \u662f\u52a8\u6001\u6570\u7ec4\uff0c\u53ef\u4ee5\u76f4\u63a5\u6269\u5c55\n# \u4e3a\u4e86\u65b9\u4fbf\u5b66\u4e60\uff0c\u672c\u51fd\u6570\u5c06 list \u770b\u4f5c\u662f\u957f\u5ea6\u4e0d\u53ef\u53d8\u7684\u6570\u7ec4\ndef extend(nums, enlarge):\n# \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nres = [0] * (len(nums) + enlarge)\n# \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor i in range(len(nums)):\nres[i] = nums[i]\n# \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res\n
array.go
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nfunc extend(nums []int, enlarge int) []int {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nres := make([]int, len(nums)+enlarge)\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor i, num := range nums {\nres[i] = num\n}\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res\n}\n
array.js
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nfunction extend(nums, enlarge) {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nconst res = new Array(nums.length + enlarge).fill(0);\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor (let i = 0; i < nums.length; i++) {\nres[i] = nums[i];\n}\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res;\n}\n
array.ts
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nfunction extend(nums: number[], enlarge: number): number[] {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nconst res = new Array(nums.length + enlarge).fill(0);\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor (let i = 0; i < nums.length; i++) {\nres[i] = nums[i];\n}\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res;\n}\n
array.c
\n
array.cs
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nint[] Extend(int[] nums, int enlarge)\n{\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nint[] res = new int[nums.Length + enlarge];\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor (int i = 0; i < nums.Length; i++)\n{\nres[i] = nums[i];\n}\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res;\n}\n
array.swift
/* \u6269\u5c55\u6570\u7ec4\u957f\u5ea6 */\nfunc extend(nums: [Int], enlarge: Int) -> [Int] {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nvar res = Array(repeating: 0, count: nums.count + enlarge)\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor i in nums.indices {\nres[i] = nums[i]\n}\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res\n}\n
array.zig
// \u6269\u5c55\u6570\u7ec4\u957f\u5ea6\npub fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u6269\u5c55\u957f\u5ea6\u540e\u7684\u6570\u7ec4\nvar res = try mem_allocator.alloc(i32, nums.len + enlarge);\nstd.mem.set(i32, res, 0);\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nstd.mem.copy(i32, res, nums);\n// \u8fd4\u56de\u6269\u5c55\u540e\u7684\u65b0\u6570\u7ec4\nreturn res;\n}\n

\u6570\u7ec4\u4e2d\u63d2\u5165\u6216\u5220\u9664\u5143\u7d20\u6548\u7387\u4f4e\u4e0b\u3002\u5047\u8bbe\u6211\u4eec\u60f3\u8981\u5728\u6570\u7ec4\u4e2d\u95f4\u67d0\u4f4d\u7f6e\u63d2\u5165\u4e00\u4e2a\u5143\u7d20\uff0c\u7531\u4e8e\u6570\u7ec4\u5143\u7d20\u5728\u5185\u5b58\u4e2d\u662f\u201c\u7d27\u6328\u7740\u7684\u201d\uff0c\u5b83\u4eec\u4e4b\u95f4\u6ca1\u6709\u7a7a\u95f4\u518d\u653e\u4efb\u4f55\u6570\u636e\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u4e0d\u5f97\u4e0d\u5c06\u6b64\u7d22\u5f15\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u7136\u540e\u518d\u628a\u5143\u7d20\u8d4b\u503c\u7ed9\u8be5\u7d22\u5f15\u3002\u5220\u9664\u5143\u7d20\u4e5f\u662f\u7c7b\u4f3c\uff0c\u9700\u8981\u628a\u6b64\u7d22\u5f15\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\u3002\u603b\u4f53\u770b\u6709\u4ee5\u4e0b\u7f3a\u70b9\uff1a

  • \u65f6\u95f4\u590d\u6742\u5ea6\u9ad8\uff1a\u6570\u7ec4\u7684\u63d2\u5165\u548c\u5220\u9664\u7684\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u5747\u4e3a \\(O(N)\\) \uff0c\u5176\u4e2d \\(N\\) \u4e3a\u6570\u7ec4\u957f\u5ea6\u3002
  • \u4e22\u5931\u5143\u7d20\uff1a\u7531\u4e8e\u6570\u7ec4\u7684\u957f\u5ea6\u4e0d\u53ef\u53d8\uff0c\u56e0\u6b64\u5728\u63d2\u5165\u5143\u7d20\u540e\uff0c\u8d85\u51fa\u6570\u7ec4\u957f\u5ea6\u8303\u56f4\u7684\u5143\u7d20\u4f1a\u88ab\u4e22\u5931\u3002
  • \u5185\u5b58\u6d6a\u8d39\uff1a\u6211\u4eec\u4e00\u822c\u4f1a\u521d\u59cb\u5316\u4e00\u4e2a\u6bd4\u8f83\u957f\u7684\u6570\u7ec4\uff0c\u53ea\u7528\u524d\u9762\u4e00\u90e8\u5206\uff0c\u8fd9\u6837\u5728\u63d2\u5165\u6570\u636e\u65f6\uff0c\u4e22\u5931\u7684\u672b\u5c3e\u5143\u7d20\u90fd\u662f\u6211\u4eec\u4e0d\u5173\u5fc3\u7684\uff0c\u4f46\u8fd9\u6837\u505a\u540c\u65f6\u4e5f\u4f1a\u9020\u6210\u5185\u5b58\u7a7a\u95f4\u7684\u6d6a\u8d39\u3002

Fig. \u5728\u6570\u7ec4\u4e2d\u63d2\u5165\u4e0e\u5220\u9664\u5143\u7d20

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array.java
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nvoid insert(int[] nums, int num, int index) {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (int i = nums.length - 1; i > index; i--) {\nnums[i] = nums[i - 1];\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num;\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nvoid remove(int[] nums, int index) {\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (int i = index; i < nums.length - 1; i++) {\nnums[i] = nums[i + 1];\n}\n}\n
array.cpp
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nvoid insert(int* nums, int size, int num, int index) {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (int i = size - 1; i > index; i--) {\nnums[i] = nums[i - 1];\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num;\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nvoid remove(int* nums, int size, int index) {\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (int i = index; i < size - 1; i++) {\nnums[i] = nums[i + 1];\n}\n}\n
array.py
\"\"\" \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num \"\"\"\ndef insert(nums, num, index):\n# \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor i in range(len(nums) - 1, index, -1):\nnums[i] = nums[i - 1]\n# \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num\n\"\"\" \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 \"\"\"\ndef remove(nums, index):\n# \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor i in range(index, len(nums) - 1):\nnums[i] = nums[i + 1]\n
array.go
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nfunc insert(nums []int, num int, index int) {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor i := len(nums) - 1; i > index; i-- {\nnums[i] = nums[i-1]\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nfunc remove(nums []int, index int) {\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor i := index; i < len(nums)-1; i++ {\nnums[i] = nums[i+1]\n}\n}\n
array.js
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nfunction insert(nums, num, index) {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (let i = nums.length - 1; i > index; i--) {\nnums[i] = nums[i - 1];\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num;\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nfunction remove(nums, index) {\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (let i = index; i < nums.length - 1; i++) {\nnums[i] = nums[i + 1];\n}\n}\n
array.ts
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nfunction insert(nums: number[], num: number, index: number): void {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (let i = nums.length - 1; i > index; i--) {\nnums[i] = nums[i - 1];\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num;\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nfunction remove(nums: number[], index: number): void {\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (let i = index; i < nums.length - 1; i++) {\nnums[i] = nums[i + 1];\n}\n}\n
array.c
\n
array.cs
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nvoid Insert(int[] nums, int num, int index)\n{\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (int i = nums.Length - 1; i > index; i--)\n{\nnums[i] = nums[i - 1];\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num;\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nvoid Remove(int[] nums, int index)\n{\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (int i = index; i < nums.Length - 1; i++)\n{\nnums[i] = nums[i + 1];\n}\n}\n
array.swift
/* \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num */\nfunc insert(nums: inout [Int], num: Int, index: Int) {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) {\nnums[i] = nums[i - 1]\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num\n}\n/* \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20 */\nfunc remove(nums: inout [Int], index: Int) {\nlet count = nums.count\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) {\nnums[i] = nums[i + 1]\n}\n}\n
array.zig
// \u5728\u6570\u7ec4\u7684\u7d22\u5f15 index \u5904\u63d2\u5165\u5143\u7d20 num\npub fn insert(nums: []i32, num: i32, index: usize) void {\n// \u628a\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nvar i = nums.len - 1;\nwhile (i > index) : (i -= 1) {\nnums[i] = nums[i - 1];\n}\n// \u5c06 num \u8d4b\u7ed9 index \u5904\u5143\u7d20\nnums[index] = num;\n}\n// \u5220\u9664\u7d22\u5f15 index \u5904\u5143\u7d20\npub fn remove(nums: []i32, index: usize) void {\n// \u628a\u7d22\u5f15 index \u4e4b\u540e\u7684\u6240\u6709\u5143\u7d20\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nvar i = index;\nwhile (i < nums.len - 1) : (i += 1) {\nnums[i] = nums[i + 1];\n}\n}\n
"},{"location":"chapter_array_and_linkedlist/array/#413","title":"4.1.3. \u6570\u7ec4\u5e38\u7528\u64cd\u4f5c","text":"

\u6570\u7ec4\u904d\u5386\u3002\u4ee5\u4e0b\u4ecb\u7ecd\u4e24\u79cd\u5e38\u7528\u7684\u904d\u5386\u65b9\u6cd5\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array.java
/* \u904d\u5386\u6570\u7ec4 */\nvoid traverse(int[] nums) {\nint count = 0;\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor (int i = 0; i < nums.length; i++) {\ncount++;\n}\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor (int num : nums) {\ncount++;\n}\n}\n
array.cpp
/* \u904d\u5386\u6570\u7ec4 */\nvoid traverse(int* nums, int size) {\nint count = 0;\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor (int i = 0; i < size; i++) {\ncount++;\n}\n}  
array.py
\"\"\" \u904d\u5386\u6570\u7ec4 \"\"\"\ndef traverse(nums):\ncount = 0\n# \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor i in range(len(nums)):\ncount += 1\n# \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor num in nums:\ncount += 1\n
array.go
/* \u904d\u5386\u6570\u7ec4 */\nfunc traverse(nums []int) {\ncount := 0\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor i := 0; i < len(nums); i++ {\ncount++\n}\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor range nums {\ncount++\n}\n}\n
array.js
/* \u904d\u5386\u6570\u7ec4 */\nfunction traverse(nums) {\nlet count = 0;\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor (let i = 0; i < nums.length; i++) {\ncount++;\n}\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor (let num of nums) {\ncount += 1;\n}\n}\n
array.ts
/* \u904d\u5386\u6570\u7ec4 */\nfunction traverse(nums: number[]): void {\nlet count = 0;\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor (let i = 0; i < nums.length; i++) {\ncount++;\n}\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor(let num of nums){\ncount += 1;\n}\n}\n
array.c
\n
array.cs
/* \u904d\u5386\u6570\u7ec4 */\nvoid Traverse(int[] nums)\n{\nint count = 0;\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor (int i = 0; i < nums.Length; i++)\n{\ncount++;\n}\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nforeach (int num in nums)\n{\ncount++;\n}\n}\n
array.swift
/* \u904d\u5386\u6570\u7ec4 */\nfunc traverse(nums: [Int]) {\nvar count = 0\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nfor _ in nums.indices {\ncount += 1\n}\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor _ in nums {\ncount += 1\n}\n}\n
array.zig
// \u904d\u5386\u6570\u7ec4\npub fn traverse(nums: []i32) void {\nvar count: i32 = 0;\n// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u6570\u7ec4\nvar i: i32 = 0;\nwhile (i < nums.len) : (i += 1) {\ncount += 1;\n}\ncount = 0;\n// \u76f4\u63a5\u904d\u5386\u6570\u7ec4\nfor (nums) |_| {\ncount += 1;\n}\n}\n

\u6570\u7ec4\u67e5\u627e\u3002\u901a\u8fc7\u904d\u5386\u6570\u7ec4\uff0c\u67e5\u627e\u6570\u7ec4\u5185\u7684\u6307\u5b9a\u5143\u7d20\uff0c\u5e76\u8f93\u51fa\u5bf9\u5e94\u7d22\u5f15\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array.java
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nint find(int[] nums, int target) {\nfor (int i = 0; i < nums.length; i++) {\nif (nums[i] == target)\nreturn i;\n}\nreturn -1;\n}\n
array.cpp
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nint find(int* nums, int size, int target) {\nfor (int i = 0; i < size; i++) {\nif (nums[i] == target)\nreturn i;\n}\nreturn -1;\n}\n
array.py
\"\"\" \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 \"\"\"\ndef find(nums, target):\nfor i in range(len(nums)):\nif nums[i] == target:\nreturn i\nreturn -1\n
array.go
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nfunc find(nums []int, target int) (index int) {\nindex = -1\nfor i := 0; i < len(nums); i++ {\nif nums[i] == target {\nindex = i\nbreak\n}\n}\nreturn\n}\n
array.js
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nfunction find(nums, target) {\nfor (let i = 0; i < nums.length; i++) {\nif (nums[i] == target) return i;\n}\nreturn -1;\n}\n
array.ts
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nfunction find(nums: number[], target: number): number {\nfor (let i = 0; i < nums.length; i++) {\nif (nums[i] === target) {\nreturn i;\n}\n}\nreturn -1;\n}\n
array.c
\n
array.cs
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nint Find(int[] nums, int target)\n{\nfor (int i = 0; i < nums.Length; i++)\n{\nif (nums[i] == target)\nreturn i;\n}\nreturn -1;\n}\n
array.swift
/* \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20 */\nfunc find(nums: [Int], target: Int) -> Int {\nfor i in nums.indices {\nif nums[i] == target {\nreturn i\n}\n}\nreturn -1\n}\n
array.zig
// \u5728\u6570\u7ec4\u4e2d\u67e5\u627e\u6307\u5b9a\u5143\u7d20\npub fn find(nums: []i32, target: i32) i32 {\nfor (nums) |num, i| {\nif (num == target) return @intCast(i32, i);\n}\nreturn -1;\n}\n
"},{"location":"chapter_array_and_linkedlist/array/#414","title":"4.1.4. \u6570\u7ec4\u5178\u578b\u5e94\u7528","text":"

\u968f\u673a\u8bbf\u95ee\u3002\u5982\u679c\u6211\u4eec\u60f3\u8981\u968f\u673a\u62bd\u53d6\u4e00\u4e9b\u6837\u672c\uff0c\u90a3\u4e48\u53ef\u4ee5\u7528\u6570\u7ec4\u5b58\u50a8\uff0c\u5e76\u751f\u6210\u4e00\u4e2a\u968f\u673a\u5e8f\u5217\uff0c\u6839\u636e\u7d22\u5f15\u5b9e\u73b0\u6837\u672c\u7684\u968f\u673a\u62bd\u53d6\u3002

\u4e8c\u5206\u67e5\u627e\u3002\u4f8b\u5982\u524d\u6587\u67e5\u5b57\u5178\u7684\u4f8b\u5b50\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5b57\u5178\u4e2d\u7684\u6240\u6709\u5b57\u6309\u7167\u62fc\u97f3\u987a\u5e8f\u5b58\u50a8\u5728\u6570\u7ec4\u4e2d\uff0c\u7136\u540e\u4f7f\u7528\u4e0e\u65e5\u5e38\u67e5\u7eb8\u8d28\u5b57\u5178\u76f8\u540c\u7684\u201c\u7ffb\u5f00\u4e2d\u95f4\uff0c\u6392\u9664\u4e00\u534a\u201d\u7684\u65b9\u5f0f\uff0c\u6765\u5b9e\u73b0\u4e00\u4e2a\u67e5\u7535\u5b50\u5b57\u5178\u7684\u7b97\u6cd5\u3002

\u6df1\u5ea6\u5b66\u4e60\u3002\u795e\u7ecf\u7f51\u7edc\u4e2d\u5927\u91cf\u4f7f\u7528\u4e86\u5411\u91cf\u3001\u77e9\u9635\u3001\u5f20\u91cf\u4e4b\u95f4\u7684\u7ebf\u6027\u4ee3\u6570\u8fd0\u7b97\uff0c\u8fd9\u4e9b\u6570\u636e\u90fd\u662f\u4ee5\u6570\u7ec4\u7684\u5f62\u5f0f\u6784\u5efa\u7684\u3002\u6570\u7ec4\u662f\u795e\u7ecf\u7f51\u7edc\u7f16\u7a0b\u4e2d\u6700\u5e38\u4f7f\u7528\u7684\u6570\u636e\u7ed3\u6784\u3002

"},{"location":"chapter_array_and_linkedlist/linked_list/","title":"4.2. \u94fe\u8868","text":"

\u5f15\u8a00

\u5185\u5b58\u7a7a\u95f4\u662f\u6240\u6709\u7a0b\u5e8f\u7684\u516c\u5171\u8d44\u6e90\uff0c\u6392\u9664\u5df2\u5360\u7528\u7684\u5185\u5b58\uff0c\u7a7a\u95f2\u5185\u5b58\u5f80\u5f80\u662f\u6563\u843d\u5728\u5185\u5b58\u5404\u5904\u7684\u3002\u6211\u4eec\u77e5\u9053\uff0c\u5b58\u50a8\u6570\u7ec4\u9700\u8981\u5185\u5b58\u7a7a\u95f4\u8fde\u7eed\uff0c\u5f53\u6211\u4eec\u9700\u8981\u7533\u8bf7\u4e00\u4e2a\u5f88\u5927\u7684\u6570\u7ec4\u65f6\uff0c\u7cfb\u7edf\u4e0d\u4e00\u5b9a\u5b58\u5728\u8fd9\u4e48\u5927\u7684\u8fde\u7eed\u5185\u5b58\u7a7a\u95f4\u3002\u800c\u94fe\u8868\u5219\u66f4\u52a0\u7075\u6d3b\uff0c\u4e0d\u9700\u8981\u5185\u5b58\u662f\u8fde\u7eed\u7684\uff0c\u53ea\u8981\u5269\u4f59\u5185\u5b58\u7a7a\u95f4\u5927\u5c0f\u591f\u7528\u5373\u53ef\u3002

\u300c\u94fe\u8868 Linked List\u300d\u662f\u4e00\u79cd\u7ebf\u6027\u6570\u636e\u7ed3\u6784\uff0c\u5176\u4e2d\u6bcf\u4e2a\u5143\u7d20\u90fd\u662f\u5355\u72ec\u7684\u5bf9\u8c61\uff0c\u5404\u4e2a\u5143\u7d20\uff08\u4e00\u822c\u79f0\u4e3a\u7ed3\u70b9\uff09\u4e4b\u95f4\u901a\u8fc7\u6307\u9488\u8fde\u63a5\u3002\u7531\u4e8e\u7ed3\u70b9\u4e2d\u8bb0\u5f55\u4e86\u8fde\u63a5\u5173\u7cfb\uff0c\u56e0\u6b64\u94fe\u8868\u7684\u5b58\u50a8\u65b9\u5f0f\u76f8\u6bd4\u4e8e\u6570\u7ec4\u66f4\u52a0\u7075\u6d3b\uff0c\u7cfb\u7edf\u4e0d\u5fc5\u4fdd\u8bc1\u5185\u5b58\u5730\u5740\u7684\u8fde\u7eed\u6027\u3002

\u94fe\u8868\u7684\u300c\u7ed3\u70b9 Node\u300d\u5305\u542b\u4e24\u9879\u6570\u636e\uff0c\u4e00\u662f\u7ed3\u70b9\u300c\u503c Value\u300d\uff0c\u4e8c\u662f\u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u300c\u6307\u9488 Pointer\u300d\uff08\u6216\u79f0\u300c\u5f15\u7528 Reference\u300d\uff09\u3002

Fig. \u94fe\u8868\u5b9a\u4e49\u4e0e\u5b58\u50a8\u65b9\u5f0f

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nint val;        // \u7ed3\u70b9\u503c\nListNode next;  // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode(int x) { val = x; }  // \u6784\u9020\u51fd\u6570\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\nstruct ListNode {\nint val;         // \u7ed3\u70b9\u503c\nListNode *next;  // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode(int x) : val(x), next(nullptr) {}  // \u6784\u9020\u51fd\u6570\n};\n
\"\"\" \u94fe\u8868\u7ed3\u70b9\u7c7b \"\"\" \nclass ListNode:\ndef __init__(self, x):\nself.val = x      # \u7ed3\u70b9\u503c\nself.next = None  # \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n
/* \u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\ntype ListNode struct {\nVal  int       // \u7ed3\u70b9\u503c\nNext *ListNode // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n}\n// NewListNode \u6784\u9020\u51fd\u6570\uff0c\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u94fe\u8868\nfunc NewListNode(val int) *ListNode {\nreturn &ListNode{\nVal:  val,\nNext: nil,\n}\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\nclass ListNode {\nval;\nnext;\nconstructor(val, next) {\nthis.val = (val === undefined ? 0 : val);       // \u7ed3\u70b9\u503c\nthis.next = (next === undefined ? null : next); // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u5f15\u7528\n}\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\nclass ListNode {\nval: number;\nnext: ListNode | null;\nconstructor(val?: number, next?: ListNode | null) {\nthis.val = val === undefined ? 0 : val;        // \u7ed3\u70b9\u503c\nthis.next = next === undefined ? null : next;  // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u5f15\u7528\n}\n}\n
\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode\n{\nint val;         // \u7ed3\u70b9\u503c\nListNode next;   // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u5f15\u7528\nListNode(int x) => val = x;  //\u6784\u9020\u51fd\u6570\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nvar val: Int // \u7ed3\u70b9\u503c\nvar next: ListNode? // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\ninit(x: Int) { // \u6784\u9020\u51fd\u6570\nval = x\n}\n}\n
// \u94fe\u8868\u7ed3\u70b9\u7c7b\npub fn ListNode(comptime T: type) type {\nreturn struct {\nconst Self = @This();\nval: T = 0, // \u7ed3\u70b9\u503c\nnext: ?*Self = null, // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n// \u6784\u9020\u51fd\u6570\npub fn init(self: *Self, x: i32) void {\nself.val = x;\nself.next = null;\n}\n};\n}\n

\u5c3e\u7ed3\u70b9\u6307\u5411\u4ec0\u4e48\uff1f \u6211\u4eec\u4e00\u822c\u5c06\u94fe\u8868\u7684\u6700\u540e\u4e00\u4e2a\u7ed3\u70b9\u79f0\u4e3a\u300c\u5c3e\u7ed3\u70b9\u300d\uff0c\u5176\u6307\u5411\u7684\u662f\u300c\u7a7a\u300d\uff0c\u5728 Java / C++ / Python \u4e2d\u5206\u522b\u8bb0\u4e3a null / nullptr / None \u3002\u5728\u4e0d\u5f15\u8d77\u6b67\u4e49\u4e0b\uff0c\u672c\u4e66\u90fd\u4f7f\u7528 null \u6765\u8868\u793a\u7a7a\u3002

\u94fe\u8868\u521d\u59cb\u5316\u65b9\u6cd5\u3002\u5efa\u7acb\u94fe\u8868\u5206\u4e3a\u4e24\u6b65\uff0c\u7b2c\u4e00\u6b65\u662f\u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9\u5bf9\u8c61\uff0c\u7b2c\u4e8c\u6b65\u662f\u6784\u5efa\u5f15\u7528\u6307\u5411\u5173\u7cfb\u3002\u5b8c\u6210\u540e\uff0c\u5373\u53ef\u4ee5\u4ece\u94fe\u8868\u7684\u9996\u4e2a\u7ed3\u70b9\uff08\u5373\u5934\u7ed3\u70b9\uff09\u51fa\u53d1\uff0c\u8bbf\u95ee\u5176\u4f59\u6240\u6709\u7684\u7ed3\u70b9\u3002

Tip

\u6211\u4eec\u901a\u5e38\u5c06\u5934\u7ed3\u70b9\u5f53\u4f5c\u94fe\u8868\u7684\u4ee3\u79f0\uff0c\u4f8b\u5982\u5934\u7ed3\u70b9 head \u548c\u94fe\u8868 head \u5b9e\u9645\u4e0a\u662f\u540c\u4e49\u7684\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linked_list.java
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9 \nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.cpp
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9 \nListNode* n0 = new ListNode(1);\nListNode* n1 = new ListNode(3);\nListNode* n2 = new ListNode(2);\nListNode* n3 = new ListNode(5);\nListNode* n4 = new ListNode(4);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
linked_list.py
\"\"\" \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 \"\"\"\n# \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9 \nn0 = ListNode(1)\nn1 = ListNode(3)\nn2 = ListNode(2)\nn3 = ListNode(5)\nn4 = ListNode(4)\n# \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
linked_list.go
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9\nn0 := NewListNode(1)\nn1 := NewListNode(3)\nn2 := NewListNode(2)\nn3 := NewListNode(5)\nn4 := NewListNode(4)\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.Next = n1\nn1.Next = n2\nn2.Next = n3\nn3.Next = n4\n
linked_list.js
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.ts
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.c
\n
linked_list.cs
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9 \nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.swift
/* \u521d\u59cb\u5316\u94fe\u8868 1 -> 3 -> 2 -> 5 -> 4 */\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9\nlet n0 = ListNode(x: 1)\nlet n1 = ListNode(x: 3)\nlet n2 = ListNode(x: 2)\nlet n3 = ListNode(x: 5)\nlet n4 = ListNode(x: 4)\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
linked_list.zig
// \u521d\u59cb\u5316\u94fe\u8868\n// \u521d\u59cb\u5316\u5404\u4e2a\u7ed3\u70b9 \nvar n0 = inc.ListNode(i32){.val = 1};\nvar n1 = inc.ListNode(i32){.val = 3};\nvar n2 = inc.ListNode(i32){.val = 2};\nvar n3 = inc.ListNode(i32){.val = 5};\nvar n4 = inc.ListNode(i32){.val = 4};\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\nn0.next = &n1;\nn1.next = &n2;\nn2.next = &n3;\nn3.next = &n4;\n
"},{"location":"chapter_array_and_linkedlist/linked_list/#421","title":"4.2.1. \u94fe\u8868\u4f18\u70b9","text":"

\u5728\u94fe\u8868\u4e2d\uff0c\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9\u7684\u64cd\u4f5c\u6548\u7387\u9ad8\u3002\u4f8b\u5982\uff0c\u5982\u679c\u60f3\u5728\u94fe\u8868\u4e2d\u95f4\u7684\u4e24\u4e2a\u7ed3\u70b9 A , B \u4e4b\u95f4\u63d2\u5165\u4e00\u4e2a\u65b0\u7ed3\u70b9 P \uff0c\u6211\u4eec\u53ea\u9700\u8981\u6539\u53d8\u4e24\u4e2a\u7ed3\u70b9\u6307\u9488\u5373\u53ef\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(1)\\) \uff0c\u76f8\u6bd4\u6570\u7ec4\u7684\u63d2\u5165\u64cd\u4f5c\u9ad8\u6548\u5f88\u591a\u3002\u5728\u94fe\u8868\u4e2d\u5220\u9664\u67d0\u4e2a\u7ed3\u70b9\u4e5f\u5f88\u65b9\u4fbf\uff0c\u53ea\u9700\u8981\u6539\u53d8\u4e00\u4e2a\u7ed3\u70b9\u6307\u9488\u5373\u53ef\u3002

Fig. \u5728\u94fe\u8868\u4e2d\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linked_list.java
/* \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P */\nvoid insert(ListNode n0, ListNode P) {\nListNode n1 = n0.next;\nn0.next = P;\nP.next = n1;\n}\n/* \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 */\nvoid remove(ListNode n0) {\nif (n0.next == null)\nreturn;\n// n0 -> P -> n1\nListNode P = n0.next;\nListNode n1 = P.next;\nn0.next = n1;\n}\n
linked_list.cpp
/* \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P */\nvoid insert(ListNode* n0, ListNode* P) {\nListNode* n1 = n0->next;\nn0->next = P;\nP->next = n1;\n}\n/* \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 */\nvoid remove(ListNode* n0) {\nif (n0->next == nullptr)\nreturn;\n// n0 -> P -> n1\nListNode* P = n0->next;\nListNode* n1 = P->next;\nn0->next = n1;\n// \u91ca\u653e\u5185\u5b58\ndelete P;\n}\n
linked_list.py
\"\"\" \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P \"\"\"\ndef insert(n0, P):\nn1 = n0.next\nn0.next = P\nP.next = n1\n\"\"\" \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 \"\"\"\ndef remove(n0):\nif not n0.next:\nreturn\n# n0 -> P -> n1\nP = n0.next\nn1 = P.next\nn0.next = n1\n
linked_list.go
/* \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P */\nfunc insert(n0 *ListNode, P *ListNode) {\nn1 := n0.Next\nn0.Next = P\nP.Next = n1\n}\n/* \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunc removeNode(n0 *ListNode) {\nif n0.Next == nil {\nreturn\n}\n// n0 -> P -> n1\nP := n0.Next\nn1 := P.Next\nn0.Next = n1\n}\n
linked_list.js
/* \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P */\nfunction insert(n0, P) {\nlet n1 = n0.next;\nn0.next = P;\nP.next = n1;\n}\n/* \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunction remove(n0) {\nif (!n0.next)\nreturn;\n// n0 -> P -> n1\nlet P = n0.next;\nlet n1 = P.next;\nn0.next = n1;\n}\n
linked_list.ts
/* \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P */\nfunction insert(n0: ListNode, P: ListNode): void {\nconst n1 = n0.next;\nn0.next = P;\nP.next = n1;\n}\n/* \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunction remove(n0: ListNode): void {\nif (!n0.next) {\nreturn;\n}\n// n0 -> P -> n1\nconst P = n0.next;\nconst n1 = P.next;\nn0.next = n1;\n}\n
linked_list.c
\n
linked_list.cs
// \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P\nvoid Insert(ListNode n0, ListNode P)\n{\nListNode n1 = n0.next;\nn0.next = P;\nP.next = n1;\n}\n// \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9\nvoid Remove(ListNode n0)\n{\nif (n0.next == null)\nreturn;\n// n0 -> P -> n1\nListNode P = n0.next;\nListNode n1 = P.next;\nn0.next = n1;\n}\n
linked_list.swift
/* \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P */\nfunc insert(n0: ListNode, P: ListNode) {\nlet n1 = n0.next\nn0.next = P\nP.next = n1\n}\n/* \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunc remove(n0: ListNode) {\nif n0.next == nil {\nreturn\n}\n// n0 -> P -> n1\nlet P = n0.next\nlet n1 = P?.next\nn0.next = n1\nP?.next = nil\n}\n
linked_list.zig
// \u5728\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u63d2\u5165\u7ed3\u70b9 P\npub fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void {\nvar n1 = n0.?.next;\nn0.?.next = P;\nP.?.next = n1;\n}\n// \u5220\u9664\u94fe\u8868\u7684\u7ed3\u70b9 n0 \u4e4b\u540e\u7684\u9996\u4e2a\u7ed3\u70b9\npub fn remove(n0: ?*inc.ListNode(i32)) void {\nif (n0.?.next == null) return;\n// n0 -> P -> n1\nvar P = n0.?.next;\nvar n1 = P.?.next;\nn0.?.next = n1;\n}\n
"},{"location":"chapter_array_and_linkedlist/linked_list/#422","title":"4.2.2. \u94fe\u8868\u7f3a\u70b9","text":"

\u94fe\u8868\u8bbf\u95ee\u7ed3\u70b9\u6548\u7387\u4f4e\u3002\u4e0a\u8282\u63d0\u5230\uff0c\u6570\u7ec4\u53ef\u4ee5\u5728 \\(O(1)\\) \u65f6\u95f4\u4e0b\u8bbf\u95ee\u4efb\u610f\u5143\u7d20\uff0c\u4f46\u94fe\u8868\u65e0\u6cd5\u76f4\u63a5\u8bbf\u95ee\u4efb\u610f\u7ed3\u70b9\u3002\u8fd9\u662f\u56e0\u4e3a\u8ba1\u7b97\u673a\u9700\u8981\u4ece\u5934\u7ed3\u70b9\u51fa\u53d1\uff0c\u4e00\u4e2a\u4e00\u4e2a\u5730\u5411\u540e\u904d\u5386\u5230\u76ee\u6807\u7ed3\u70b9\u3002\u4f8b\u5982\uff0c\u5018\u82e5\u60f3\u8981\u8bbf\u95ee\u94fe\u8868\u7d22\u5f15\u4e3a index \uff08\u5373\u7b2c index + 1 \u4e2a\uff09\u7684\u7ed3\u70b9\uff0c\u90a3\u4e48\u9700\u8981 index \u6b21\u8bbf\u95ee\u64cd\u4f5c\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linked_list.java
/* \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 */\nListNode access(ListNode head, int index) {\nfor (int i = 0; i < index; i++) {\nif (head == null)\nreturn null;\nhead = head.next;\n}\nreturn head;\n}\n
linked_list.cpp
/* \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 */\nListNode* access(ListNode* head, int index) {\nfor (int i = 0; i < index; i++) {\nif (head == nullptr)\nreturn nullptr;\nhead = head->next;\n}\nreturn head;\n}\n
linked_list.py
\"\"\" \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 \"\"\"\ndef access(head, index):\nfor _ in range(index):\nif not head:\nreturn None\nhead = head.next\nreturn head\n
linked_list.go
/* \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 */\nfunc access(head *ListNode, index int) *ListNode {\nfor i := 0; i < index; i++ {\nif head == nil {\nreturn nil\n}\nhead = head.Next\n}\nreturn head\n}\n
linked_list.js
/* \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 */\nfunction access(head, index) {\nfor (let i = 0; i < index; i++) {\nif (!head)\nreturn null;\nhead = head.next;\n}\nreturn head;\n}\n
linked_list.ts
/* \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 */\nfunction access(head: ListNode | null, index: number): ListNode | null {\nfor (let i = 0; i < index; i++) {\nif (!head) {\nreturn null;\n}\nhead = head.next;\n}\nreturn head;\n}\n
linked_list.c
\n
linked_list.cs
// \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9\nListNode Access(ListNode head, int index)\n{\nfor (int i = 0; i < index; i++)\n{\nif (head == null)\nreturn null;\nhead = head.next;\n}\nreturn head;\n}\n
linked_list.swift
/* \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9 */\nfunc access(head: ListNode, index: Int) -> ListNode? {\nvar head: ListNode? = head\nfor _ in 0 ..< index {\nif head == nil {\nreturn nil\n}\nhead = head?.next\n}\nreturn head\n}\n
linked_list.zig
// \u8bbf\u95ee\u94fe\u8868\u4e2d\u7d22\u5f15\u4e3a index \u7684\u7ed3\u70b9\npub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) {\nvar head = node;\nvar i: i32 = 0;\nwhile (i < index) : (i += 1) {\nhead = head.?.next;\nif (head == null) return null;\n}\nreturn head;\n}\n

\u94fe\u8868\u7684\u5185\u5b58\u5360\u7528\u591a\u3002\u94fe\u8868\u4ee5\u7ed3\u70b9\u4e3a\u5355\u4f4d\uff0c\u6bcf\u4e2a\u7ed3\u70b9\u9664\u4e86\u4fdd\u5b58\u503c\u5916\uff0c\u8fd8\u9700\u989d\u5916\u4fdd\u5b58\u6307\u9488\uff08\u5f15\u7528\uff09\u3002\u8fd9\u610f\u5473\u7740\u540c\u6837\u6570\u636e\u91cf\u4e0b\uff0c\u94fe\u8868\u6bd4\u6570\u7ec4\u9700\u8981\u5360\u7528\u66f4\u591a\u5185\u5b58\u7a7a\u95f4\u3002

"},{"location":"chapter_array_and_linkedlist/linked_list/#423","title":"4.2.3. \u94fe\u8868\u5e38\u7528\u64cd\u4f5c","text":"

\u904d\u5386\u94fe\u8868\u67e5\u627e\u3002\u904d\u5386\u94fe\u8868\uff0c\u67e5\u627e\u94fe\u8868\u5185\u503c\u4e3a target \u7684\u7ed3\u70b9\uff0c\u8f93\u51fa\u7ed3\u70b9\u5728\u94fe\u8868\u4e2d\u7684\u7d22\u5f15\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linked_list.java
/* \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 */\nint find(ListNode head, int target) {\nint index = 0;\nwhile (head != null) {\nif (head.val == target)\nreturn index;\nhead = head.next;\nindex++;\n}\nreturn -1;\n}\n
linked_list.cpp
/* \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 */\nint find(ListNode* head, int target) {\nint index = 0;\nwhile (head != nullptr) {\nif (head->val == target)\nreturn index;\nhead = head->next;\nindex++;\n}\nreturn -1;\n}\n
linked_list.py
\"\"\" \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 \"\"\"\ndef find(head, target):\nindex = 0\nwhile head:\nif head.val == target:\nreturn index\nhead = head.next\nindex += 1\nreturn -1\n
linked_list.go
/* \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunc find(head *ListNode, target int) int {\nindex := 0\nfor head != nil {\nif head.Val == target {\nreturn index\n}\nhead = head.Next\nindex++\n}\nreturn -1\n}\n
linked_list.js
/* \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunction find(head, target) {\nlet index = 0;\nwhile (head !== null) {\nif (head.val === target) {\nreturn index;\n}\nhead = head.next;\nindex += 1;\n}\nreturn -1;\n}\n
linked_list.ts
/* \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunction find(head: ListNode | null, target: number): number {\nlet index = 0;\nwhile (head !== null) {\nif (head.val === target) {\nreturn index;\n}\nhead = head.next;\nindex += 1;\n}\nreturn -1;\n}\n
linked_list.c
\n
linked_list.cs
// \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9\nint Find(ListNode head, int target)\n{\nint index = 0;\nwhile (head != null)\n{\nif (head.val == target)\nreturn index;\nhead = head.next;\nindex++;\n}\nreturn -1;\n}\n
linked_list.swift
/* \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9 */\nfunc find(head: ListNode, target: Int) -> Int {\nvar head: ListNode? = head\nvar index = 0\nwhile head != nil {\nif head?.val == target {\nreturn index\n}\nhead = head?.next\nindex += 1\n}\nreturn -1\n}\n
linked_list.zig
// \u5728\u94fe\u8868\u4e2d\u67e5\u627e\u503c\u4e3a target \u7684\u9996\u4e2a\u7ed3\u70b9\npub fn find(node: ?*inc.ListNode(i32), target: i32) i32 {\nvar head = node;\nvar index: i32 = 0;\nwhile (head != null) {\nif (head.?.val == target) return index;\nhead = head.?.next;\nindex += 1;\n}\nreturn -1;\n}\n
"},{"location":"chapter_array_and_linkedlist/linked_list/#424","title":"4.2.4. \u5e38\u89c1\u94fe\u8868\u7c7b\u578b","text":"

\u5355\u5411\u94fe\u8868\u3002\u5373\u4e0a\u8ff0\u4ecb\u7ecd\u7684\u666e\u901a\u94fe\u8868\u3002\u5355\u5411\u94fe\u8868\u7684\u7ed3\u70b9\u6709\u300c\u503c\u300d\u548c\u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u300c\u6307\u9488\uff08\u5f15\u7528\uff09\u300d\u4e24\u9879\u6570\u636e\u3002\u6211\u4eec\u5c06\u9996\u4e2a\u7ed3\u70b9\u79f0\u4e3a\u5934\u7ed3\u70b9\uff0c\u5c3e\u7ed3\u70b9\u6307\u5411 null \u3002

\u73af\u5f62\u94fe\u8868\u3002\u5982\u679c\u6211\u4eec\u4ee4\u5355\u5411\u94fe\u8868\u7684\u5c3e\u7ed3\u70b9\u6307\u5411\u5934\u7ed3\u70b9\uff08\u5373\u9996\u5c3e\u76f8\u63a5\uff09\uff0c\u5219\u5f97\u5230\u4e00\u4e2a\u73af\u5f62\u94fe\u8868\u3002\u5728\u73af\u5f62\u94fe\u8868\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u4efb\u610f\u7ed3\u70b9\u770b\u4f5c\u662f\u5934\u7ed3\u70b9\u3002

\u53cc\u5411\u94fe\u8868\u3002\u5355\u5411\u94fe\u8868\u4ec5\u8bb0\u5f55\u4e86\u4e00\u4e2a\u65b9\u5411\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\uff0c\u5728\u53cc\u5411\u94fe\u8868\u7684\u7ed3\u70b9\u5b9a\u4e49\u4e2d\uff0c\u540c\u65f6\u6709\u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\uff08\u540e\u7ee7\u7ed3\u70b9\uff09\u548c\u4e0a\u4e00\u7ed3\u70b9\uff08\u524d\u9a71\u7ed3\u70b9\uff09\u7684\u300c\u6307\u9488\uff08\u5f15\u7528\uff09\u300d\u3002\u53cc\u5411\u94fe\u8868\u76f8\u5bf9\u4e8e\u5355\u5411\u94fe\u8868\u66f4\u52a0\u7075\u6d3b\uff0c\u5373\u53ef\u4ee5\u671d\u4e24\u4e2a\u65b9\u5411\u904d\u5386\u94fe\u8868\uff0c\u4f46\u4e5f\u9700\u8981\u5360\u7528\u66f4\u591a\u7684\u5185\u5b58\u7a7a\u95f4\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nint val;        // \u7ed3\u70b9\u503c\nListNode next;  // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode prev;  // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode(int x) { val = x; }  // \u6784\u9020\u51fd\u6570\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\nstruct ListNode {\nint val;         // \u7ed3\u70b9\u503c\nListNode *next;  // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode *prev;  // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // \u6784\u9020\u51fd\u6570\n};\n
\"\"\" \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b \"\"\" \nclass ListNode:\ndef __init__(self, x):\nself.val = x      # \u7ed3\u70b9\u503c\nself.next = None  # \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nself.prev = None  # \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\ntype DoublyListNode struct {\nVal  int             // \u7ed3\u70b9\u503c\nNext *DoublyListNode // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nPrev *DoublyListNode // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n}\n// NewDoublyListNode \u521d\u59cb\u5316\nfunc NewDoublyListNode(val int) *DoublyListNode {\nreturn &DoublyListNode{\nVal:  val,\nNext: nil,\nPrev: nil,\n}\n}\n
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nval;\nnext;\nprev;\nconstructor(val, next) {\nthis.val = val  ===  undefined ? 0 : val;        // \u7ed3\u70b9\u503c\nthis.next = next  ===  undefined ? null : next;  // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nthis.prev = prev  ===  undefined ? null : prev;  // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n}\n}\n
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nval: number;\nnext: ListNode | null;\nprev: ListNode | null;\nconstructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {\nthis.val = val  ===  undefined ? 0 : val;        // \u7ed3\u70b9\u503c\nthis.next = next  ===  undefined ? null : next;  // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nthis.prev = prev  ===  undefined ? null : prev;  // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n}\n}\n
\n
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nint val;        // \u7ed3\u70b9\u503c\nListNode next;  // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode prev;  // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nListNode(int x) => val = x;  // \u6784\u9020\u51fd\u6570\n}\n
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass ListNode {\nvar val: Int // \u7ed3\u70b9\u503c\nvar next: ListNode? // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nvar prev: ListNode? // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\ninit(x: Int) { // \u6784\u9020\u51fd\u6570\nval = x\n}\n}\n
// \u53cc\u5411\u94fe\u8868\u7ed3\u70b9\u7c7b\npub fn ListNode(comptime T: type) type {\nreturn struct {\nconst Self = @This();\nval: T = 0, // \u7ed3\u70b9\u503c\nnext: ?*Self = null, // \u6307\u5411\u540e\u7ee7\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\nprev: ?*Self = null, // \u6307\u5411\u524d\u9a71\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n// \u6784\u9020\u51fd\u6570\npub fn init(self: *Self, x: i32) void {\nself.val = x;\nself.next = null;\nself.prev = null;\n}\n};\n}\n

Fig. \u5e38\u89c1\u94fe\u8868\u7c7b\u578b

"},{"location":"chapter_array_and_linkedlist/list/","title":"4.3. \u5217\u8868","text":"

\u7531\u4e8e\u957f\u5ea6\u4e0d\u53ef\u53d8\uff0c\u6570\u7ec4\u7684\u5b9e\u7528\u6027\u5927\u5927\u964d\u4f4e\u3002\u5728\u5f88\u591a\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u4e8b\u5148\u5e76\u4e0d\u77e5\u9053\u4f1a\u8f93\u5165\u591a\u5c11\u6570\u636e\uff0c\u8fd9\u5c31\u4e3a\u6570\u7ec4\u957f\u5ea6\u7684\u9009\u62e9\u5e26\u6765\u4e86\u5f88\u5927\u56f0\u96be\u3002\u957f\u5ea6\u9009\u5c0f\u4e86\uff0c\u9700\u8981\u5728\u6dfb\u52a0\u6570\u636e\u4e2d\u9891\u7e41\u5730\u6269\u5bb9\u6570\u7ec4\uff1b\u957f\u5ea6\u9009\u5927\u4e86\uff0c\u53c8\u9020\u6210\u5185\u5b58\u7a7a\u95f4\u7684\u6d6a\u8d39\u3002

\u4e3a\u4e86\u89e3\u51b3\u6b64\u95ee\u9898\uff0c\u8bde\u751f\u4e86\u4e00\u79cd\u88ab\u79f0\u4e3a\u300c\u5217\u8868 List\u300d\u7684\u6570\u636e\u7ed3\u6784\u3002\u5217\u8868\u53ef\u4ee5\u88ab\u7406\u89e3\u4e3a\u957f\u5ea6\u53ef\u53d8\u7684\u6570\u7ec4\uff0c\u56e0\u6b64\u4e5f\u5e38\u88ab\u79f0\u4e3a\u300c\u52a8\u6001\u6570\u7ec4 Dynamic Array\u300d\u3002\u5217\u8868\u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\uff0c\u7ee7\u627f\u4e86\u6570\u7ec4\u7684\u4f18\u70b9\uff0c\u540c\u65f6\u8fd8\u53ef\u4ee5\u5728\u7a0b\u5e8f\u8fd0\u884c\u4e2d\u5b9e\u65f6\u6269\u5bb9\u3002\u5728\u5217\u8868\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u81ea\u7531\u5730\u6dfb\u52a0\u5143\u7d20\uff0c\u800c\u4e0d\u7528\u62c5\u5fc3\u8d85\u8fc7\u5bb9\u91cf\u9650\u5236\u3002

"},{"location":"chapter_array_and_linkedlist/list/#431","title":"4.3.1. \u5217\u8868\u5e38\u7528\u64cd\u4f5c","text":"

\u521d\u59cb\u5316\u5217\u8868\u3002\u6211\u4eec\u901a\u5e38\u4f1a\u4f7f\u7528\u5230\u201c\u65e0\u521d\u59cb\u503c\u201d\u548c\u201c\u6709\u521d\u59cb\u503c\u201d\u7684\u4e24\u79cd\u521d\u59cb\u5316\u65b9\u6cd5\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig list.java
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u65e0\u521d\u59cb\u503c\nList<Integer> list1 = new ArrayList<>();\n// \u6709\u521d\u59cb\u503c\uff08\u6ce8\u610f\u6570\u7ec4\u7684\u5143\u7d20\u7c7b\u578b\u9700\u4e3a int[] \u7684\u5305\u88c5\u7c7b Integer[]\uff09\nInteger[] numbers = new Integer[] { 1, 3, 2, 5, 4 };\nList<Integer> list = new ArrayList<>(Arrays.asList(numbers));\n
list.cpp
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u9700\u6ce8\u610f\uff0cC++ \u4e2d vector \u5373\u662f\u672c\u6587\u63cf\u8ff0\u7684 list\n// \u65e0\u521d\u59cb\u503c\nvector<int> list1;\n// \u6709\u521d\u59cb\u503c\nvector<int> list = { 1, 3, 2, 5, 4 };\n
list.py
\"\"\" \u521d\u59cb\u5316\u5217\u8868 \"\"\"\n# \u65e0\u521d\u59cb\u503c\nlist1 = []\n# \u6709\u521d\u59cb\u503c\nlist = [1, 3, 2, 5, 4]\n
list_test.go
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u65e0\u521d\u59cb\u503c\nlist1 := []int\n// \u6709\u521d\u59cb\u503c\nlist := []int{1, 3, 2, 5, 4}\n
list.js
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u65e0\u521d\u59cb\u503c\nconst list1 = [];\n// \u6709\u521d\u59cb\u503c\nconst list = [1, 3, 2, 5, 4];\n
list.ts
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u65e0\u521d\u59cb\u503c\nconst list1: number[] = [];\n// \u6709\u521d\u59cb\u503c\nconst list: number[] = [1, 3, 2, 5, 4];\n
list.c
\n
list.cs
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u65e0\u521d\u59cb\u503c\nList<int> list1 = new ();\n// \u6709\u521d\u59cb\u503c\uff08\u6ce8\u610f\u6570\u7ec4\u7684\u5143\u7d20\u7c7b\u578b\u9700\u4e3a int[] \u7684\u5305\u88c5\u7c7b Integer[]\uff09\nint[] numbers = new int[] { 1, 3, 2, 5, 4 };\nList<int> list = numbers.ToList();\n
list.swift
/* \u521d\u59cb\u5316\u5217\u8868 */\n// \u65e0\u521d\u59cb\u503c\nlet list1: [Int] = []\n// \u6709\u521d\u59cb\u503c\nvar list = [1, 3, 2, 5, 4]\n
list.zig
// \u521d\u59cb\u5316\u5217\u8868\nvar list = std.ArrayList(i32).init(std.heap.page_allocator);\ndefer list.deinit();\ntry list.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 });\n

\u8bbf\u95ee\u4e0e\u66f4\u65b0\u5143\u7d20\u3002\u5217\u8868\u7684\u5e95\u5c42\u6570\u636e\u7ed3\u6784\u662f\u6570\u7ec4\uff0c\u56e0\u6b64\u53ef\u4ee5\u5728 \\(O(1)\\) \u65f6\u95f4\u5185\u8bbf\u95ee\u4e0e\u66f4\u65b0\u5143\u7d20\uff0c\u6548\u7387\u5f88\u9ad8\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig list.java
/* \u8bbf\u95ee\u5143\u7d20 */\nint num = list.get(1);  // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist.set(1, 0);  // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.cpp
/* \u8bbf\u95ee\u5143\u7d20 */\nint num = list[1];  // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist[1] = 0;  // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.py
\"\"\" \u8bbf\u95ee\u5143\u7d20 \"\"\"\nnum = list[1]  # \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n\"\"\" \u66f4\u65b0\u5143\u7d20 \"\"\"\nlist[1] = 0    # \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list_test.go
/* \u8bbf\u95ee\u5143\u7d20 */\nnum := list[1]  // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist[1] = 0     // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.js
/* \u8bbf\u95ee\u5143\u7d20 */\nconst num = list[1];  // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist[1] = 0;  // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.ts
/* \u8bbf\u95ee\u5143\u7d20 */\nconst num: number = list[1];  // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist[1] = 0;  // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.c
\n
list.cs
/* \u8bbf\u95ee\u5143\u7d20 */\nint num = list[1];  // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist[1] = 0;  // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.swift
/* \u8bbf\u95ee\u5143\u7d20 */\nlet num = list[1] // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n/* \u66f4\u65b0\u5143\u7d20 */\nlist[1] = 0 // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0\n
list.zig
// \u8bbf\u95ee\u5143\u7d20\nvar num = list.items[1]; // \u8bbf\u95ee\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\n// \u66f4\u65b0\u5143\u7d20\nlist.items[1] = 0; // \u5c06\u7d22\u5f15 1 \u5904\u7684\u5143\u7d20\u66f4\u65b0\u4e3a 0  \n

\u5728\u5217\u8868\u4e2d\u6dfb\u52a0\u3001\u63d2\u5165\u3001\u5220\u9664\u5143\u7d20\u3002\u76f8\u5bf9\u4e8e\u6570\u7ec4\uff0c\u5217\u8868\u53ef\u4ee5\u81ea\u7531\u5730\u6dfb\u52a0\u4e0e\u5220\u9664\u5143\u7d20\u3002\u5728\u5217\u8868\u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(1)\\) \uff0c\u4f46\u662f\u63d2\u5165\u4e0e\u5220\u9664\u5143\u7d20\u7684\u6548\u7387\u4ecd\u4e0e\u6570\u7ec4\u4e00\u6837\u4f4e\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(N)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig list.java
/* \u6e05\u7a7a\u5217\u8868 */\nlist.clear();\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist.add(1);\nlist.add(3);\nlist.add(2);\nlist.add(5);\nlist.add(4);\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist.add(3, 6);  // \u5728\u7d22\u5f15 3 \u5904\u63d2\u5165\u6570\u5b57 6\n/* \u5220\u9664\u5143\u7d20 */\nlist.remove(3);  // \u5220\u9664\u7d22\u5f15 3 \u5904\u7684\u5143\u7d20\n
list.cpp
/* \u6e05\u7a7a\u5217\u8868 */\nlist.clear();\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist.push_back(1);\nlist.push_back(3);\nlist.push_back(2);\nlist.push_back(5);\nlist.push_back(4);\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist.insert(list.begin() + 3, 6);  // \u5728\u7d22\u5f15 3 \u5904\u63d2\u5165\u6570\u5b57 6\n/* \u5220\u9664\u5143\u7d20 */\nlist.erase(list.begin() + 3);      // \u5220\u9664\u7d22\u5f15 3 \u5904\u7684\u5143\u7d20\n
list.py
\"\"\" \u6e05\u7a7a\u5217\u8868 \"\"\"\nlist.clear()\n\"\"\" \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 \"\"\"\nlist.append(1)\nlist.append(3)\nlist.append(2)\nlist.append(5)\nlist.append(4)\n\"\"\" \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 \"\"\"\nlist.insert(3, 6)  # \u5728\u7d22\u5f15 3 \u5904\u63d2\u5165\u6570\u5b57 6\n\"\"\" \u5220\u9664\u5143\u7d20 \"\"\"\nlist.pop(3)        # \u5220\u9664\u7d22\u5f15 3 \u5904\u7684\u5143\u7d20\n
list_test.go
/* \u6e05\u7a7a\u5217\u8868 */\nlist = nil\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist = append(list, 1)\nlist = append(list, 3)\nlist = append(list, 2)\nlist = append(list, 5)\nlist = append(list, 4)\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist = append(list[:3], append([]int{6}, list[3:]...)...) // \u5728\u7d22\u5f15 3 \u5904\u63d2\u5165\u6570\u5b57 6\n/* \u5220\u9664\u5143\u7d20 */\nlist = append(list[:3], list[4:]...) // \u5220\u9664\u7d22\u5f15 3 \u5904\u7684\u5143\u7d20\n
list.js
/* \u6e05\u7a7a\u5217\u8868 */\nlist.length = 0;\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist.push(1);\nlist.push(3);\nlist.push(2);\nlist.push(5);\nlist.push(4);\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist.splice(3, 0, 6);\n/* \u5220\u9664\u5143\u7d20 */\nlist.splice(3, 1);\n
list.ts
/* \u6e05\u7a7a\u5217\u8868 */\nlist.length = 0;\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist.push(1);\nlist.push(3);\nlist.push(2);\nlist.push(5);\nlist.push(4);\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist.splice(3, 0, 6);\n/* \u5220\u9664\u5143\u7d20 */\nlist.splice(3, 1);\n
list.c
\n
list.cs
/* \u6e05\u7a7a\u5217\u8868 */\nlist.Clear();\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist.Add(1);\nlist.Add(3);\nlist.Add(2);\nlist.Add(5);\nlist.Add(4);\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist.Insert(3, 6);\n/* \u5220\u9664\u5143\u7d20 */\nlist.RemoveAt(3);\n
list.swift
/* \u6e05\u7a7a\u5217\u8868 */\nlist.removeAll()\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nlist.append(1)\nlist.append(3)\nlist.append(2)\nlist.append(5)\nlist.append(4)\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nlist.insert(6, at: 3) // \u5728\u7d22\u5f15 3 \u5904\u63d2\u5165\u6570\u5b57 6\n/* \u5220\u9664\u5143\u7d20 */\nlist.remove(at: 3) // \u5220\u9664\u7d22\u5f15 3 \u5904\u7684\u5143\u7d20\n
list.zig
// \u6e05\u7a7a\u5217\u8868\nlist.clearRetainingCapacity();\n// \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20\ntry list.append(1);\ntry list.append(3);\ntry list.append(2);\ntry list.append(5);\ntry list.append(4);\n// \u4e2d\u95f4\u63d2\u5165\u5143\u7d20\ntry list.insert(3, 6); // \u5728\u7d22\u5f15 3 \u5904\u63d2\u5165\u6570\u5b57 6\n// \u5220\u9664\u5143\u7d20\n_ = list.orderedRemove(3); // \u5220\u9664\u7d22\u5f15 3 \u5904\u7684\u5143\u7d20\n

\u904d\u5386\u5217\u8868\u3002\u4e0e\u6570\u7ec4\u4e00\u6837\uff0c\u5217\u8868\u53ef\u4ee5\u4f7f\u7528\u7d22\u5f15\u904d\u5386\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 for-each \u76f4\u63a5\u904d\u5386\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig list.java
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\nint count = 0;\nfor (int i = 0; i < list.size(); i++) {\ncount++;\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0;\nfor (int n : list) {\ncount++;\n}\n
list.cpp
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\nint count = 0;\nfor (int i = 0; i < list.size(); i++) {\ncount++;\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0;\nfor (int n : list) {\ncount++;\n}\n
list.py
\"\"\" \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 \"\"\"\ncount = 0\nfor i in range(len(list)):\ncount += 1\n\"\"\" \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 \"\"\"\ncount = 0\nfor n in list:\ncount += 1\n
list_test.go
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\ncount := 0\nfor i := 0; i < len(list); i++ {\ncount++\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0\nfor range list {\ncount++\n}\n
list.js
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\nlet count = 0;\nfor (let i = 0; i < list.length; i++) {\ncount++;\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0;\nfor (const n of list) {\ncount++;\n}\n
list.ts
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\nlet count = 0;\nfor (let i = 0; i < list.length; i++) {\ncount++;\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0;\nfor (const n of list) {\ncount++;\n}\n
list.c
\n
list.cs
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\nint count = 0;\nfor (int i = 0; i < list.Count(); i++)\n{\ncount++;\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0;\nforeach (int n in list)\n{\ncount++;\n}\n
list.swift
/* \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868 */\nvar count = 0\nfor _ in list.indices {\ncount += 1\n}\n/* \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20 */\ncount = 0\nfor _ in list {\ncount += 1\n}\n
list.zig
// \u901a\u8fc7\u7d22\u5f15\u904d\u5386\u5217\u8868\nvar count: i32 = 0;\nvar i: i32 = 0;\nwhile (i < list.items.len) : (i += 1) {\ncount += 1;\n}\n// \u76f4\u63a5\u904d\u5386\u5217\u8868\u5143\u7d20\ncount = 0;\nfor (list.items) |_| {\ncount += 1;\n}\n

\u62fc\u63a5\u4e24\u4e2a\u5217\u8868\u3002\u518d\u521b\u5efa\u4e00\u4e2a\u65b0\u5217\u8868 list1 \uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5176\u4e2d\u4e00\u4e2a\u5217\u8868\u62fc\u63a5\u5230\u53e6\u4e00\u4e2a\u7684\u5c3e\u90e8\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig list.java
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nList<Integer> list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));\nlist.addAll(list1);  // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list.cpp
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nvector<int> list1 = { 6, 8, 7, 10, 9 };\n// \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\nlist.insert(list.end(), list1.begin(), list1.end());\n
list.py
\"\"\" \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 \"\"\"\nlist1 = [6, 8, 7, 10, 9]\nlist += list1  # \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list_test.go
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nlist1 := []int{6, 8, 7, 10, 9}\nlist = append(list, list1...)  // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list.js
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nconst list1 = [6, 8, 7, 10, 9];\nlist.push(...list1);  // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list.ts
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nconst list1: number[] = [6, 8, 7, 10, 9];\nlist.push(...list1);  // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list.c
\n
list.cs
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nList<int> list1 = new() { 6, 8, 7, 10, 9 };\nlist.AddRange(list1);  // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list.swift
/* \u62fc\u63a5\u4e24\u4e2a\u5217\u8868 */\nlet list1 = [6, 8, 7, 10, 9]\nlist.append(contentsOf: list1) // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n
list.zig
// \u62fc\u63a5\u4e24\u4e2a\u5217\u8868\nvar list1 = std.ArrayList(i32).init(std.heap.page_allocator);\ndefer list1.deinit();\ntry list1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 });\ntry list.insertSlice(list.items.len, list1.items); // \u5c06\u5217\u8868 list1 \u62fc\u63a5\u5230 list \u4e4b\u540e\n

\u6392\u5e8f\u5217\u8868\u3002\u6392\u5e8f\u4e5f\u662f\u5e38\u7528\u7684\u65b9\u6cd5\u4e4b\u4e00\uff0c\u5b8c\u6210\u5217\u8868\u6392\u5e8f\u540e\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u4f7f\u7528\u5728\u6570\u7ec4\u7c7b\u7b97\u6cd5\u9898\u4e2d\u7ecf\u5e38\u8003\u5bdf\u7684\u300c\u4e8c\u5206\u67e5\u627e\u300d\u548c\u300c\u53cc\u6307\u9488\u300d\u7b97\u6cd5\u4e86\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig list.java
/* \u6392\u5e8f\u5217\u8868 */\nCollections.sort(list);  // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.cpp
/* \u6392\u5e8f\u5217\u8868 */\nsort(list.begin(), list.end());  // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.py
\"\"\" \u6392\u5e8f\u5217\u8868 \"\"\"\nlist.sort()  # \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list_test.go
/* \u6392\u5e8f\u5217\u8868 */\nsort.Ints(list)  // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.js
/* \u6392\u5e8f\u5217\u8868 */  list.sort((a, b) => a - b);  // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.ts
/* \u6392\u5e8f\u5217\u8868 */\nlist.sort((a, b) => a - b);  // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.c
\n
list.cs
/* \u6392\u5e8f\u5217\u8868 */\nlist.Sort(); // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.swift
/* \u6392\u5e8f\u5217\u8868 */\nlist.sort() // \u6392\u5e8f\u540e\uff0c\u5217\u8868\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\n
list.zig
// \u6392\u5e8f\u5217\u8868\nstd.sort.sort(i32, list.items, {}, comptime std.sort.asc(i32));\n
"},{"location":"chapter_array_and_linkedlist/list/#432","title":"4.3.2. \u5217\u8868\u7b80\u6613\u5b9e\u73b0 *","text":"

\u4e3a\u4e86\u5e2e\u52a9\u52a0\u6df1\u5bf9\u5217\u8868\u7684\u7406\u89e3\uff0c\u6211\u4eec\u5728\u6b64\u63d0\u4f9b\u4e00\u4e2a\u5217\u8868\u7684\u7b80\u6613\u7248\u672c\u7684\u5b9e\u73b0\u3002\u9700\u8981\u5173\u6ce8\u4e09\u4e2a\u6838\u5fc3\u70b9\uff1a

  • \u521d\u59cb\u5bb9\u91cf\uff1a\u9009\u53d6\u4e00\u4e2a\u5408\u7406\u7684\u6570\u7ec4\u7684\u521d\u59cb\u5bb9\u91cf initialCapacity \u3002\u5728\u672c\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u9009\u62e9 10 \u4f5c\u4e3a\u521d\u59cb\u5bb9\u91cf\u3002
  • \u6570\u91cf\u8bb0\u5f55\uff1a\u9700\u8981\u58f0\u660e\u4e00\u4e2a\u53d8\u91cf size \uff0c\u7528\u6765\u8bb0\u5f55\u5217\u8868\u5f53\u524d\u6709\u591a\u5c11\u4e2a\u5143\u7d20\uff0c\u5e76\u968f\u7740\u5143\u7d20\u63d2\u5165\u4e0e\u5220\u9664\u5b9e\u65f6\u66f4\u65b0\u3002\u6839\u636e\u6b64\u53d8\u91cf\uff0c\u53ef\u4ee5\u5b9a\u4f4d\u5217\u8868\u7684\u5c3e\u90e8\uff0c\u4ee5\u53ca\u5224\u65ad\u662f\u5426\u9700\u8981\u6269\u5bb9\u3002
  • \u6269\u5bb9\u673a\u5236\uff1a\u63d2\u5165\u5143\u7d20\u6709\u53ef\u80fd\u5bfc\u81f4\u8d85\u51fa\u5217\u8868\u5bb9\u91cf\uff0c\u6b64\u65f6\u9700\u8981\u6269\u5bb9\u5217\u8868\uff0c\u65b9\u6cd5\u662f\u5efa\u7acb\u4e00\u4e2a\u66f4\u5927\u7684\u6570\u7ec4\u6765\u66ff\u6362\u5f53\u524d\u6570\u7ec4\u3002\u9700\u8981\u7ed9\u5b9a\u4e00\u4e2a\u6269\u5bb9\u500d\u6570 extendRatio \uff0c\u5728\u672c\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u89c4\u5b9a\u6bcf\u6b21\u5c06\u6570\u7ec4\u6269\u5bb9\u81f3\u4e4b\u524d\u7684 2 \u500d\u3002

\u672c\u793a\u4f8b\u662f\u4e3a\u4e86\u5e2e\u52a9\u8bfb\u8005\u5bf9\u5982\u4f55\u5b9e\u73b0\u5217\u8868\u4ea7\u751f\u76f4\u89c2\u7684\u8ba4\u8bc6\u3002\u5b9e\u9645\u7f16\u7a0b\u8bed\u8a00\u4e2d\uff0c\u5217\u8868\u7684\u5b9e\u73b0\u8fdc\u6bd4\u4ee5\u4e0b\u4ee3\u7801\u590d\u6742\u4e14\u6807\u51c6\uff0c\u611f\u5174\u8da3\u7684\u8bfb\u8005\u53ef\u4ee5\u67e5\u9605\u6e90\u7801\u5b66\u4e60\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig my_list.java
/* \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 */\nclass MyList {\nprivate int[] nums;           // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nprivate int capacity = 10;    // \u5217\u8868\u5bb9\u91cf\nprivate int size = 0;         // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nprivate int extendRatio = 2;  // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n/* \u6784\u9020\u51fd\u6570 */\npublic MyList() {\nnums = new int[capacity];\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09*/\npublic int size() {\nreturn size;\n}\n/* \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\npublic int capacity() {\nreturn capacity;\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\npublic int get(int index) {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 || index >= size)\nthrow new IndexOutOfBoundsException(\"\u7d22\u5f15\u8d8a\u754c\");\nreturn nums[index];\n}\n/* \u66f4\u65b0\u5143\u7d20 */\npublic void set(int index, int num) {\nif (index < 0 || index >= size)\nthrow new IndexOutOfBoundsException(\"\u7d22\u5f15\u8d8a\u754c\");\nnums[index] = num;\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\npublic void add(int num) {\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (size == capacity())\nextendCapacity();\nnums[size] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nsize++;\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\npublic void insert(int index, int num) {\nif (index < 0 || index >= size)\nthrow new IndexOutOfBoundsException(\"\u7d22\u5f15\u8d8a\u754c\");\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (size == capacity())\nextendCapacity();\n// \u5c06\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (int j = size - 1; j >= index; j--) {\nnums[j + 1] = nums[j];\n}\nnums[index] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nsize++;\n}\n/* \u5220\u9664\u5143\u7d20 */\npublic int remove(int index) {\nif (index < 0 || index >= size)\nthrow new IndexOutOfBoundsException(\"\u7d22\u5f15\u8d8a\u754c\");\nint num = nums[index];\n// \u5c06\u7d22\u5f15 index \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (int j = index; j < size - 1; j++) {\nnums[j] = nums[j + 1];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nsize--;\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num;\n}\n/* \u5217\u8868\u6269\u5bb9 */\npublic void extendCapacity() {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nnums = Arrays.copyOf(nums, capacity() * extendRatio);\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\ncapacity = nums.length;\n}\n/* \u5c06\u5217\u8868\u8f6c\u6362\u4e3a\u6570\u7ec4 */\npublic int[] toArray() {\nint size = size();\n// \u4ec5\u8f6c\u6362\u6709\u6548\u957f\u5ea6\u8303\u56f4\u5185\u7684\u5217\u8868\u5143\u7d20\nint[] nums = new int[size];\nfor (int i = 0; i < size; i++) {\nnums[i] = get(i);\n}\nreturn nums;\n}\n}\n
my_list.cpp
/* \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 */\nclass MyList {\nprivate:\nint* nums;                // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nint numsCapacity = 10;    // \u5217\u8868\u5bb9\u91cf\nint numsSize = 0;         // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nint extendRatio = 2;      // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\npublic:\n/* \u6784\u9020\u51fd\u6570 */\nMyList() {\nnums = new int[numsCapacity];\n}\n/* \u6790\u6784\u51fd\u6570 */\n~MyList() {\ndelete[] nums;\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09*/\nint size() {\nreturn numsSize;\n}\n/* \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\nint capacity() {\nreturn numsCapacity;\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\nint get(int index) {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 || index >= size())\nthrow out_of_range(\"\u7d22\u5f15\u8d8a\u754c\");\nreturn nums[index];\n}\n/* \u66f4\u65b0\u5143\u7d20 */\nvoid set(int index, int num) {\nif (index < 0 || index >= size())\nthrow out_of_range(\"\u7d22\u5f15\u8d8a\u754c\");\nnums[index] = num;\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nvoid add(int num) {\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (size() == capacity())\nextendCapacity();\nnums[size()] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nnumsSize++;\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nvoid insert(int index, int num) {\nif (index < 0 || index >= size())\nthrow out_of_range(\"\u7d22\u5f15\u8d8a\u754c\");\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (size() == capacity())\nextendCapacity();\n// \u7d22\u5f15 i \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (int j = size() - 1; j >= index; j--) {\nnums[j + 1] = nums[j];\n}\nnums[index] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nnumsSize++;\n}\n/* \u5220\u9664\u5143\u7d20 */\nint remove(int index) {\nif (index < 0 || index >= size())\nthrow out_of_range(\"\u7d22\u5f15\u8d8a\u754c\");\nint num = nums[index];\n// \u7d22\u5f15 i \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (int j = index; j < size() - 1; j++) {\nnums[j] = nums[j + 1];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nnumsSize--;\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num;\n}\n/* \u5217\u8868\u6269\u5bb9 */\nvoid extendCapacity() {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size * extendRatio \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nint newCapacity = capacity() * extendRatio;\nint* tmp = nums;\nnums = new int[newCapacity];\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nfor (int i = 0; i < size(); i++) {\nnums[i] = tmp[i];\n}\n// \u91ca\u653e\u5185\u5b58\ndelete[] tmp;\nnumsCapacity = newCapacity;\n}\n};\n
my_list.py
\"\"\" \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 \"\"\"\nclass MyList:\n\"\"\" \u6784\u9020\u51fd\u6570 \"\"\"\ndef __init__(self):\nself.__capacity = 10                 # \u5217\u8868\u5bb9\u91cf\nself.__nums = [0] * self.__capacity  # \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nself.__size = 0                      # \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nself.__extend_ratio = 2              # \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n\"\"\" \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09 \"\"\"\ndef size(self):\nreturn self.__size\n\"\"\" \u83b7\u53d6\u5217\u8868\u5bb9\u91cf \"\"\"\ndef capacity(self):\nreturn self.__capacity\n\"\"\" \u8bbf\u95ee\u5143\u7d20 \"\"\"\ndef get(self, index):\n# \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nassert index >= 0 and index < self.__size, \"\u7d22\u5f15\u8d8a\u754c\"\nreturn self.__nums[index]\n\"\"\" \u66f4\u65b0\u5143\u7d20 \"\"\"\ndef set(self, num, index):\nassert index >= 0 and index < self.__size, \"\u7d22\u5f15\u8d8a\u754c\"\nself.__nums[index] = num\n\"\"\" \u4e2d\u95f4\u63d2\u5165\uff08\u5c3e\u90e8\u6dfb\u52a0\uff09\u5143\u7d20 \"\"\"\ndef add(self, num, index=-1):\nassert index >= 0 and index < self.__size, \"\u7d22\u5f15\u8d8a\u754c\"\n# \u82e5\u4e0d\u6307\u5b9a\u7d22\u5f15 index \uff0c\u5219\u5411\u6570\u7ec4\u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20\nif index == -1:\nindex = self.__size\n# \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif self.__size == self.capacity():\nself.extend_capacity()\n# \u7d22\u5f15 i \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor j in range(self.__size - 1, index - 1, -1):\nself.__nums[j + 1] = self.__nums[j]\nself.__nums[index] = num\n# \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nself.__size += 1\n\"\"\" \u5220\u9664\u5143\u7d20 \"\"\"\ndef remove(self, index):\nassert index >= 0 and index < self.__size, \"\u7d22\u5f15\u8d8a\u754c\"\nnum = self.nums[index]\n# \u7d22\u5f15 i \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor j in range(index, self.__size - 1):\nself.__nums[j] = self.__nums[j + 1]\n# \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nself.__size -= 1\n# \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num\n\"\"\" \u5217\u8868\u6269\u5bb9 \"\"\"\ndef extend_capacity(self):\n# \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a self.__size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nself.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1)\n# \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\nself.__capacity = len(self.__nums)\n\"\"\" \u8fd4\u56de\u6709\u6548\u957f\u5ea6\u7684\u5217\u8868 \"\"\"\ndef to_array(self):\nreturn self.__nums[:self.__size]\n
my_list.go
/* \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 */\ntype myList struct {\nnumsCapacity int\nnums         []int\nnumsSize     int\nextendRatio  int\n}\n/* \u6784\u9020\u51fd\u6570 */\nfunc newMyList() *myList {\nreturn &myList{\nnumsCapacity: 10,              // \u5217\u8868\u5bb9\u91cf\nnums:         make([]int, 10), // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nnumsSize:     0,               // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nextendRatio:  2,               // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n}\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09 */\nfunc (l *myList) size() int {\nreturn l.numsSize\n}\n/*  \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\nfunc (l *myList) capacity() int {\nreturn l.numsCapacity\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\nfunc (l *myList) get(index int) int {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif index < 0 || index >= l.numsSize {\npanic(\"\u7d22\u5f15\u8d8a\u754c\")\n}\nreturn l.nums[index]\n}\n/* \u66f4\u65b0\u5143\u7d20 */\nfunc (l *myList) set(num, index int) {\nif index < 0 || index >= l.numsSize {\npanic(\"\u7d22\u5f15\u8d8a\u754c\")\n}\nl.nums[index] = num\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nfunc (l *myList) add(num int) {\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif l.numsSize == l.numsCapacity {\nl.extendCapacity()\n}\nl.nums[l.numsSize] = num\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nl.numsSize++\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nfunc (l *myList) insert(num, index int) {\nif index < 0 || index >= l.numsSize {\npanic(\"\u7d22\u5f15\u8d8a\u754c\")\n}\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif l.numsSize == l.numsCapacity {\nl.extendCapacity()\n}\n// \u7d22\u5f15 i \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor j := l.numsSize - 1; j >= index; j-- {\nl.nums[j+1] = l.nums[j]\n}\nl.nums[index] = num\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nl.numsSize++\n}\n/* \u5220\u9664\u5143\u7d20 */\nfunc (l *myList) remove(index int) int {\nif index < 0 || index >= l.numsSize {\npanic(\"\u7d22\u5f15\u8d8a\u754c\")\n}\nnum := l.nums[index]\n// \u7d22\u5f15 i \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor j := index; j < l.numsSize-1; j++ {\nl.nums[j] = l.nums[j+1]\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nl.numsSize--\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num\n}\n/* \u5217\u8868\u6269\u5bb9 */\nfunc (l *myList) extendCapacity() {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a self.__size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nl.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...)\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\nl.numsCapacity = len(l.nums)\n}\n
my_list.js
/* \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 */\nclass MyList {\n#nums = new Array(); // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\n#capacity = 10; // \u5217\u8868\u5bb9\u91cf\n#size = 0; // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\n#extendRatio = 2; // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n/* \u6784\u9020\u51fd\u6570 */\nconstructor() {\nthis.#nums = new Array(this.#capacity);\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09*/\nsize() {\nreturn this.#size;\n}\n/* \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\ncapacity() {\nreturn this.#capacity;\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\nget(index) {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 || index >= this.#size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\nreturn this.#nums[index];\n}\n/* \u66f4\u65b0\u5143\u7d20 */\nset(index, num) {\nif (index < 0 || index >= this.#size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\nthis.#nums[index] = num;\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nadd(num) {\n// \u5982\u679c\u957f\u5ea6\u7b49\u4e8e\u5bb9\u91cf\uff0c\u5219\u9700\u8981\u6269\u5bb9\nif (this.#size === this.#capacity) {\nthis.extendCapacity();\n}\n// \u5c06\u65b0\u5143\u7d20\u6dfb\u52a0\u5230\u5217\u8868\u5c3e\u90e8\nthis.#nums[this.#size] = num;\nthis.#size++;\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\ninsert(index, num) {\nif (index < 0 || index >= this.#size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (this.#size === this.#capacity) {\nthis.extendCapacity();\n}\n// \u5c06\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (let j = this.#size - 1; j >= index; j--) {\nthis.#nums[j + 1] = this.#nums[j];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nthis.#nums[index] = num;\nthis.#size++;\n}\n/* \u5220\u9664\u5143\u7d20 */\nremove(index) {\nif (index < 0 || index >= this.#size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\nlet num = this.#nums[index];\n// \u5c06\u7d22\u5f15 index \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (let j = index; j < this.#size - 1; j++) {\nthis.#nums[j] = this.#nums[j + 1];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nthis.#size--;\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num;\n}\n/* \u5217\u8868\u6269\u5bb9 */\nextendCapacity() {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nthis.#nums = this.#nums.concat(\nnew Array(this.capacity() * (this.#extendRatio - 1))\n);\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\nthis.#capacity = this.#nums.length;\n}\n}\n
my_list.ts
/* \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 */\nclass MyList {\nprivate nums: Array<number>; // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nprivate _capacity: number = 10; // \u5217\u8868\u5bb9\u91cf\nprivate _size: number = 0; // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nprivate extendRatio: number = 2; // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n/* \u6784\u9020\u51fd\u6570 */\nconstructor() {\nthis.nums = new Array(this._capacity);\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09*/\npublic size(): number {\nreturn this._size;\n}\n/* \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\npublic capacity(): number {\nreturn this._capacity;\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\npublic get(index: number): number {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 || index >= this._size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\nreturn this.nums[index];\n}\n/* \u66f4\u65b0\u5143\u7d20 */\npublic set(index: number, num: number): void {\nif (index < 0 || index >= this._size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\nthis.nums[index] = num;\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\npublic add(num: number): void {\n// \u5982\u679c\u957f\u5ea6\u7b49\u4e8e\u5bb9\u91cf\uff0c\u5219\u9700\u8981\u6269\u5bb9\nif (this._size === this._capacity)\nthis.extendCapacity();\n// \u5c06\u65b0\u5143\u7d20\u6dfb\u52a0\u5230\u5217\u8868\u5c3e\u90e8\nthis.nums[this._size] = num;\nthis._size++;\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\npublic insert(index: number, num: number): void {\nif (index < 0 || index >= this._size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (this._size === this._capacity) {\nthis.extendCapacity();\n}\n// \u5c06\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (let j = this._size - 1; j >= index; j--) {\nthis.nums[j + 1] = this.nums[j];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nthis.nums[index] = num;\nthis._size++;\n}\n/* \u5220\u9664\u5143\u7d20 */\npublic remove(index: number): number {\nif (index < 0 || index >= this._size)\nthrow new Error('\u7d22\u5f15\u8d8a\u754c');\nlet num = this.nums[index];\n// \u5c06\u7d22\u5f15 index \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (let j = index; j < this._size - 1; j++) {\nthis.nums[j] = this.nums[j + 1];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nthis._size--;\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num;\n}\n/* \u5217\u8868\u6269\u5bb9 */\npublic extendCapacity(): void {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nthis.nums = this.nums.concat(\nnew Array(this.capacity() * (this.extendRatio - 1))\n);\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\nthis._capacity = this.nums.length;\n}\n}\n
my_list.c
\n
my_list.cs
class MyList\n{\nprivate int[] nums;           // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nprivate int capacity = 10;    // \u5217\u8868\u5bb9\u91cf\nprivate int size = 0;         // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nprivate int extendRatio = 2;  // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n/* \u6784\u9020\u51fd\u6570 */\npublic MyList()\n{\nnums = new int[capacity];\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09*/\npublic int Size()\n{\nreturn size;\n}\n/* \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\npublic int Capacity()\n{\nreturn capacity;\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\npublic int Get(int index)\n{\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 || index >= size)\nthrow new IndexOutOfRangeException(\"\u7d22\u5f15\u8d8a\u754c\");\nreturn nums[index];\n}\n/* \u66f4\u65b0\u5143\u7d20 */\npublic void Set(int index, int num)\n{\nif (index < 0 || index >= size)\nthrow new IndexOutOfRangeException(\"\u7d22\u5f15\u8d8a\u754c\");\nnums[index] = num;\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\npublic void Add(int num)\n{\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (size == Capacity())\nExtendCapacity();\nnums[size] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nsize++;\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\npublic void Insert(int index, int num)\n{\nif (index < 0 || index >= size)\nthrow new IndexOutOfRangeException(\"\u7d22\u5f15\u8d8a\u754c\");\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (size == Capacity())\nExtendCapacity();\n// \u5c06\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor (int j = size - 1; j >= index; j--)\n{\nnums[j + 1] = nums[j];\n}\nnums[index] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nsize++;\n}\n/* \u5220\u9664\u5143\u7d20 */\npublic int Remove(int index)\n{\nif (index < 0 || index >= size)\nthrow new IndexOutOfRangeException(\"\u7d22\u5f15\u8d8a\u754c\");\nint num = nums[index];\n// \u5c06\u7d22\u5f15 index \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor (int j = index; j < size - 1; j++)\n{\nnums[j] = nums[j + 1];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nsize--;\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num;\n}\n/* \u5217\u8868\u6269\u5bb9 */\npublic void ExtendCapacity()\n{\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nSystem.Array.Resize(ref nums, Capacity() * extendRatio);\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\ncapacity = nums.Length;\n}\n}\n
my_list.swift
/* \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0 */\nclass MyList {\nprivate var nums: [Int] // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nprivate var _capacity = 10 // \u5217\u8868\u5bb9\u91cf\nprivate var _size = 0 // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nprivate let extendRatio = 2 // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\n/* \u6784\u9020\u51fd\u6570 */\ninit() {\nnums = Array(repeating: 0, count: _capacity)\n}\n/* \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09*/\nfunc size() -> Int {\n_size\n}\n/* \u83b7\u53d6\u5217\u8868\u5bb9\u91cf */\nfunc capacity() -> Int {\n_capacity\n}\n/* \u8bbf\u95ee\u5143\u7d20 */\nfunc get(index: Int) -> Int {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u9519\u8bef\uff0c\u4e0b\u540c\nif index < 0 || index >= _size {\nfatalError(\"\u7d22\u5f15\u8d8a\u754c\")\n}\nreturn nums[index]\n}\n/* \u66f4\u65b0\u5143\u7d20 */\nfunc set(index: Int, num: Int) {\nif index < 0 || index >= _size {\nfatalError(\"\u7d22\u5f15\u8d8a\u754c\")\n}\nnums[index] = num\n}\n/* \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20 */\nfunc add(num: Int) {\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif _size == _capacity {\nextendCapacity()\n}\nnums[_size] = num\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\n_size += 1\n}\n/* \u4e2d\u95f4\u63d2\u5165\u5143\u7d20 */\nfunc insert(index: Int, num: Int) {\nif index < 0 || index >= _size {\nfatalError(\"\u7d22\u5f15\u8d8a\u754c\")\n}\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif _size == _capacity {\nextendCapacity()\n}\n// \u5c06\u7d22\u5f15 index \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nfor j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) {\nnums[j + 1] = nums[j]\n}\nnums[index] = num\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\n_size += 1\n}\n/* \u5220\u9664\u5143\u7d20 */\n@discardableResult\nfunc remove(index: Int) -> Int {\nif index < 0 || index >= _size {\nfatalError(\"\u7d22\u5f15\u8d8a\u754c\")\n}\nlet num = nums[index]\n// \u5c06\u7d22\u5f15 index \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nfor j in index ..< (_size - 1) {\nnums[j] = nums[j + 1]\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\n_size -= 1\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num\n}\n/* \u5217\u8868\u6269\u5bb9 */\nfunc extendCapacity() {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nnums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1))\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\n_capacity = nums.count\n}\n}\n
my_list.zig
// \u5217\u8868\u7c7b\u7b80\u6613\u5b9e\u73b0\npub fn MyList(comptime T: type) type {\nreturn struct {\nconst Self = @This();\nnums: []T = undefined,                        // \u6570\u7ec4\uff08\u5b58\u50a8\u5217\u8868\u5143\u7d20\uff09\nnumsCapacity: usize = 10,                     // \u5217\u8868\u5bb9\u91cf\nnumSize: usize = 0,                           // \u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\nextendRatio: usize = 2,                       // \u6bcf\u6b21\u5217\u8868\u6269\u5bb9\u7684\u500d\u6570\nmem_arena: ?std.heap.ArenaAllocator = null,\nmem_allocator: std.mem.Allocator = undefined, // \u5185\u5b58\u5206\u914d\u5668\n// \u6784\u9020\u51fd\u6570\uff08\u5206\u914d\u5185\u5b58+\u521d\u59cb\u5316\u5217\u8868\uff09\npub fn init(self: *Self, allocator: std.mem.Allocator) !void {\nif (self.mem_arena == null) {\nself.mem_arena = std.heap.ArenaAllocator.init(allocator);\nself.mem_allocator = self.mem_arena.?.allocator();\n}\nself.nums = try self.mem_allocator.alloc(T, self.numsCapacity);\nstd.mem.set(T, self.nums, @as(T, 0));\n}\n// \u6790\u6784\u51fd\u6570\uff08\u91ca\u653e\u5185\u5b58\uff09\npub fn deinit(self: *Self) void {\nif (self.mem_arena == null) return;\nself.mem_arena.?.deinit();\n}\n// \u83b7\u53d6\u5217\u8868\u957f\u5ea6\uff08\u5373\u5f53\u524d\u5143\u7d20\u6570\u91cf\uff09\npub fn size(self: *Self) usize {\nreturn self.numSize;\n}\n// \u83b7\u53d6\u5217\u8868\u5bb9\u91cf\npub fn capacity(self: *Self) usize {\nreturn self.numsCapacity;\n}\n// \u8bbf\u95ee\u5143\u7d20\npub fn get(self: *Self, index: usize) T {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 or index >= self.size()) @panic(\"\u7d22\u5f15\u8d8a\u754c\");\nreturn self.nums[index];\n}  // \u66f4\u65b0\u5143\u7d20\npub fn set(self: *Self, index: usize, num: T) void {\n// \u7d22\u5f15\u5982\u679c\u8d8a\u754c\u5219\u629b\u51fa\u5f02\u5e38\uff0c\u4e0b\u540c\nif (index < 0 or index >= self.size()) @panic(\"\u7d22\u5f15\u8d8a\u754c\");\nself.nums[index] = num;\n}  // \u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20\npub fn add(self: *Self, num: T) !void {\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (self.size() == self.capacity()) try self.extendCapacity();\nself.nums[self.size()] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nself.numSize += 1;\n}  // \u4e2d\u95f4\u63d2\u5165\u5143\u7d20\npub fn insert(self: *Self, index: usize, num: T) !void {\nif (index < 0 or index >= self.size()) @panic(\"\u7d22\u5f15\u8d8a\u754c\");\n// \u5143\u7d20\u6570\u91cf\u8d85\u51fa\u5bb9\u91cf\u65f6\uff0c\u89e6\u53d1\u6269\u5bb9\u673a\u5236\nif (self.size() == self.capacity()) try self.extendCapacity();\n// \u7d22\u5f15 i \u4ee5\u53ca\u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\nvar j = self.size() - 1;\nwhile (j >= index) : (j -= 1) {\nself.nums[j + 1] = self.nums[j];\n}\nself.nums[index] = num;\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nself.numSize += 1;\n}\n// \u5220\u9664\u5143\u7d20\npub fn remove(self: *Self, index: usize) T {\nif (index < 0 or index >= self.size()) @panic(\"\u7d22\u5f15\u8d8a\u754c\");\nvar num = self.nums[index];\n// \u7d22\u5f15 i \u4e4b\u540e\u7684\u5143\u7d20\u90fd\u5411\u524d\u79fb\u52a8\u4e00\u4f4d\nvar j = index;\nwhile (j < self.size() - 1) : (j += 1) {\nself.nums[j] = self.nums[j + 1];\n}\n// \u66f4\u65b0\u5143\u7d20\u6570\u91cf\nself.numSize -= 1;\n// \u8fd4\u56de\u88ab\u5220\u9664\u5143\u7d20\nreturn num;\n}\n// \u5217\u8868\u6269\u5bb9\npub fn extendCapacity(self: *Self) !void {\n// \u65b0\u5efa\u4e00\u4e2a\u957f\u5ea6\u4e3a size * extendRatio \u7684\u6570\u7ec4\uff0c\u5e76\u5c06\u539f\u6570\u7ec4\u62f7\u8d1d\u5230\u65b0\u6570\u7ec4\nvar newCapacity = self.capacity() * self.extendRatio;\nvar extend = try self.mem_allocator.alloc(T, newCapacity);\nstd.mem.set(T, extend, @as(T, 0));\n// \u5c06\u539f\u6570\u7ec4\u4e2d\u7684\u6240\u6709\u5143\u7d20\u590d\u5236\u5230\u65b0\u6570\u7ec4\nstd.mem.copy(T, extend, self.nums);\nself.nums = extend;\n// \u66f4\u65b0\u5217\u8868\u5bb9\u91cf\nself.numsCapacity = newCapacity;\n}\n// \u5c06\u5217\u8868\u8f6c\u6362\u4e3a\u6570\u7ec4\npub fn toArray(self: *Self) ![]T {\n// \u4ec5\u8f6c\u6362\u6709\u6548\u957f\u5ea6\u8303\u56f4\u5185\u7684\u5217\u8868\u5143\u7d20\nvar nums = try self.mem_allocator.alloc(T, self.size());\nstd.mem.set(T, nums, @as(T, 0));\nfor (nums) |*num, i| {\nnum.* = self.get(i);\n}\nreturn nums;\n}\n};\n}\n
"},{"location":"chapter_array_and_linkedlist/summary/","title":"4.4. \u5c0f\u7ed3","text":"
  • \u6570\u7ec4\u548c\u94fe\u8868\u662f\u4e24\u79cd\u57fa\u672c\u6570\u636e\u7ed3\u6784\uff0c\u4ee3\u8868\u4e86\u6570\u636e\u5728\u8ba1\u7b97\u673a\u5185\u5b58\u4e2d\u7684\u4e24\u79cd\u5b58\u50a8\u65b9\u5f0f\uff0c\u5373\u8fde\u7eed\u7a7a\u95f4\u5b58\u50a8\u548c\u79bb\u6563\u7a7a\u95f4\u5b58\u50a8\u3002\u4e24\u8005\u7684\u4f18\u70b9\u4e0e\u7f3a\u70b9\u5448\u73b0\u51fa\u6b64\u6d88\u5f7c\u957f\u7684\u5173\u7cfb\u3002
  • \u6570\u7ec4\u652f\u6301\u968f\u673a\u8bbf\u95ee\u3001\u5185\u5b58\u7a7a\u95f4\u5360\u7528\u5c0f\uff1b\u4f46\u63d2\u5165\u4e0e\u5220\u9664\u5143\u7d20\u6548\u7387\u4f4e\uff0c\u4e14\u521d\u59cb\u5316\u540e\u957f\u5ea6\u4e0d\u53ef\u53d8\u3002
  • \u94fe\u8868\u53ef\u901a\u8fc7\u66f4\u6539\u6307\u9488\u5b9e\u73b0\u9ad8\u6548\u7684\u7ed3\u70b9\u63d2\u5165\u4e0e\u5220\u9664\uff0c\u5e76\u4e14\u53ef\u4ee5\u7075\u6d3b\u5730\u4fee\u6539\u957f\u5ea6\uff1b\u4f46\u7ed3\u70b9\u8bbf\u95ee\u6548\u7387\u4f4e\u3001\u5360\u7528\u5185\u5b58\u591a\u3002\u5e38\u89c1\u7684\u94fe\u8868\u7c7b\u578b\u6709\u5355\u5411\u94fe\u8868\u3001\u5faa\u73af\u94fe\u8868\u3001\u53cc\u5411\u94fe\u8868\u3002
  • \u5217\u8868\u53c8\u79f0\u52a8\u6001\u6570\u7ec4\uff0c\u662f\u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u4e00\u79cd\u6570\u636e\u7ed3\u6784\uff0c\u5176\u4fdd\u5b58\u4e86\u6570\u7ec4\u7684\u4f18\u52bf\uff0c\u4e14\u53ef\u4ee5\u7075\u6d3b\u6539\u53d8\u957f\u5ea6\u3002\u5217\u8868\u7684\u51fa\u73b0\u5927\u5927\u63d0\u5347\u4e86\u6570\u7ec4\u7684\u5b9e\u7528\u6027\uff0c\u4f46\u526f\u4f5c\u7528\u662f\u4f1a\u9020\u6210\u90e8\u5206\u5185\u5b58\u7a7a\u95f4\u6d6a\u8d39\u3002
"},{"location":"chapter_array_and_linkedlist/summary/#441-vs","title":"4.4.1. \u6570\u7ec4 VS \u94fe\u8868","text":"

Table. \u6570\u7ec4\u4e0e\u94fe\u8868\u7279\u70b9\u5bf9\u6bd4

\u6570\u7ec4 \u94fe\u8868 \u5b58\u50a8\u65b9\u5f0f \u8fde\u7eed\u5185\u5b58\u7a7a\u95f4 \u79bb\u6563\u5185\u5b58\u7a7a\u95f4 \u6570\u636e\u7ed3\u6784\u957f\u5ea6 \u957f\u5ea6\u4e0d\u53ef\u53d8 \u957f\u5ea6\u53ef\u53d8 \u5185\u5b58\u4f7f\u7528\u7387 \u5360\u7528\u5185\u5b58\u5c11\u3001\u7f13\u5b58\u5c40\u90e8\u6027\u597d \u5360\u7528\u5185\u5b58\u591a \u4f18\u52bf\u64cd\u4f5c \u968f\u673a\u8bbf\u95ee \u63d2\u5165\u3001\u5220\u9664

Tip

\u300c\u7f13\u5b58\u5c40\u90e8\u6027\uff08Cache locality\uff09\u300d\u6d89\u53ca\u5230\u4e86\u8ba1\u7b97\u673a\u64cd\u4f5c\u7cfb\u7edf\uff0c\u5728\u672c\u4e66\u4e0d\u505a\u5c55\u5f00\u4ecb\u7ecd\uff0c\u5efa\u8bae\u6709\u5174\u8da3\u7684\u540c\u5b66 Google / Baidu \u4e00\u4e0b\u3002

Table. \u6570\u7ec4\u4e0e\u94fe\u8868\u64cd\u4f5c\u65f6\u95f4\u590d\u6742\u5ea6

\u64cd\u4f5c \u6570\u7ec4 \u94fe\u8868 \u8bbf\u95ee\u5143\u7d20 \\(O(1)\\) \\(O(N)\\) \u6dfb\u52a0\u5143\u7d20 \\(O(N)\\) \\(O(1)\\) \u5220\u9664\u5143\u7d20 \\(O(N)\\) \\(O(1)\\)"},{"location":"chapter_computational_complexity/performance_evaluation/","title":"2.1. \u7b97\u6cd5\u6548\u7387\u8bc4\u4f30","text":""},{"location":"chapter_computational_complexity/performance_evaluation/#211","title":"2.1.1. \u7b97\u6cd5\u8bc4\u4ef7\u7ef4\u5ea6","text":"

\u5728\u5f00\u59cb\u5b66\u4e60\u7b97\u6cd5\u4e4b\u524d\uff0c\u6211\u4eec\u9996\u5148\u8981\u60f3\u6e05\u695a\u7b97\u6cd5\u7684\u8bbe\u8ba1\u76ee\u6807\u662f\u4ec0\u4e48\uff0c\u6216\u8005\u8bf4\uff0c\u5982\u4f55\u6765\u8bc4\u5224\u7b97\u6cd5\u7684\u597d\u4e0e\u574f\u3002\u6574\u4f53\u4e0a\u770b\uff0c\u6211\u4eec\u8bbe\u8ba1\u7b97\u6cd5\u65f6\u8ffd\u6c42\u4e24\u4e2a\u5c42\u9762\u7684\u76ee\u6807\u3002

  1. \u627e\u5230\u95ee\u9898\u89e3\u6cd5\u3002\u7b97\u6cd5\u9700\u8981\u80fd\u591f\u5728\u89c4\u5b9a\u7684\u8f93\u5165\u8303\u56f4\u4e0b\uff0c\u53ef\u9760\u5730\u6c42\u5f97\u95ee\u9898\u7684\u6b63\u786e\u89e3\u3002
  2. \u5bfb\u6c42\u6700\u4f18\u89e3\u6cd5\u3002\u540c\u4e00\u4e2a\u95ee\u9898\u53ef\u80fd\u5b58\u5728\u591a\u79cd\u89e3\u6cd5\uff0c\u800c\u6211\u4eec\u5e0c\u671b\u7b97\u6cd5\u6548\u7387\u5c3d\u53ef\u80fd\u7684\u9ad8\u3002

\u6362\u8a00\u4e4b\uff0c\u5728\u53ef\u4ee5\u89e3\u51b3\u95ee\u9898\u7684\u524d\u63d0\u4e0b\uff0c\u7b97\u6cd5\u6548\u7387\u5219\u662f\u4e3b\u8981\u8bc4\u4ef7\u7ef4\u5ea6\uff0c\u5305\u62ec\uff1a

  • \u65f6\u95f4\u6548\u7387\uff0c\u5373\u7b97\u6cd5\u7684\u8fd0\u884c\u901f\u5ea6\u7684\u5feb\u6162\u3002
  • \u7a7a\u95f4\u6548\u7387\uff0c\u5373\u7b97\u6cd5\u5360\u7528\u7684\u5185\u5b58\u7a7a\u95f4\u5927\u5c0f\u3002

\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u8ffd\u6c42\u201c\u8fd0\u884c\u901f\u5ea6\u5feb\u3001\u5360\u7528\u5185\u5b58\u5c11\u201d\uff0c\u800c\u5982\u4f55\u53bb\u8bc4\u4ef7\u7b97\u6cd5\u6548\u7387\u5219\u662f\u975e\u5e38\u91cd\u8981\u7684\u95ee\u9898\uff0c\u56e0\u4e3a\u53ea\u6709\u77e5\u9053\u5982\u4f55\u8bc4\u4ef7\u7b97\u6cd5\uff0c\u624d\u80fd\u53bb\u505a\u7b97\u6cd5\u4e4b\u95f4\u7684\u5bf9\u6bd4\u5206\u6790\uff0c\u4ee5\u53ca\u4f18\u5316\u7b97\u6cd5\u8bbe\u8ba1\u3002

"},{"location":"chapter_computational_complexity/performance_evaluation/#212","title":"2.1.2. \u6548\u7387\u8bc4\u4f30\u65b9\u6cd5","text":""},{"location":"chapter_computational_complexity/performance_evaluation/#_1","title":"\u5b9e\u9645\u6d4b\u8bd5","text":"

\u5047\u8bbe\u6211\u4eec\u73b0\u5728\u6709\u7b97\u6cd5 A \u548c \u7b97\u6cd5 B \uff0c\u90fd\u80fd\u591f\u89e3\u51b3\u540c\u4e00\u95ee\u9898\uff0c\u73b0\u5728\u9700\u8981\u5bf9\u6bd4\u4e24\u4e2a\u7b97\u6cd5\u4e4b\u95f4\u7684\u6548\u7387\u3002\u6211\u4eec\u80fd\u591f\u60f3\u5230\u7684\u6700\u76f4\u63a5\u7684\u65b9\u5f0f\uff0c\u5c31\u662f\u627e\u4e00\u53f0\u8ba1\u7b97\u673a\uff0c\u628a\u4e24\u4e2a\u7b97\u6cd5\u90fd\u5b8c\u6574\u8dd1\u4e00\u904d\uff0c\u5e76\u76d1\u63a7\u8bb0\u5f55\u8fd0\u884c\u65f6\u95f4\u548c\u5185\u5b58\u5360\u7528\u60c5\u51b5\u3002\u8fd9\u79cd\u8bc4\u4f30\u65b9\u5f0f\u80fd\u591f\u53cd\u6620\u771f\u5b9e\u60c5\u51b5\uff0c\u4f46\u662f\u4e5f\u5b58\u5728\u5f88\u5927\u7684\u786c\u4f24\u3002

\u96be\u4ee5\u6392\u9664\u6d4b\u8bd5\u73af\u5883\u7684\u5e72\u6270\u56e0\u7d20\u3002\u786c\u4ef6\u914d\u7f6e\u4f1a\u5f71\u54cd\u5230\u7b97\u6cd5\u7684\u6027\u80fd\u8868\u73b0\u3002\u4f8b\u5982\uff0c\u5728\u67d0\u53f0\u8ba1\u7b97\u673a\u4e2d\uff0c\u7b97\u6cd5 A \u6bd4\u7b97\u6cd5 B \u8fd0\u884c\u65f6\u95f4\u66f4\u77ed\uff1b\u4f46\u6362\u5230\u53e6\u4e00\u53f0\u914d\u7f6e\u4e0d\u540c\u7684\u8ba1\u7b97\u673a\u4e2d\uff0c\u53ef\u80fd\u4f1a\u5f97\u5230\u76f8\u53cd\u7684\u6d4b\u8bd5\u7ed3\u679c\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u9700\u8981\u5728\u5404\u79cd\u673a\u5668\u4e0a\u5c55\u5f00\u6d4b\u8bd5\uff0c\u800c\u8fd9\u662f\u4e0d\u73b0\u5b9e\u7684\u3002

\u5c55\u5f00\u5b8c\u6574\u6d4b\u8bd5\u975e\u5e38\u8017\u8d39\u8d44\u6e90\u3002\u968f\u7740\u8f93\u5165\u6570\u636e\u91cf\u7684\u5927\u5c0f\u53d8\u5316\uff0c\u7b97\u6cd5\u4f1a\u5448\u73b0\u51fa\u4e0d\u540c\u7684\u6548\u7387\u8868\u73b0\u3002\u6bd4\u5982\uff0c\u6709\u53ef\u80fd\u8f93\u5165\u6570\u636e\u91cf\u8f83\u5c0f\u65f6\uff0c\u7b97\u6cd5 A \u8fd0\u884c\u65f6\u95f4\u77ed\u4e8e\u7b97\u6cd5 B \uff0c\u800c\u5728\u8f93\u5165\u6570\u636e\u91cf\u8f83\u5927\u65f6\uff0c\u6d4b\u8bd5\u7ed3\u679c\u622a\u7136\u76f8\u53cd\u3002\u56e0\u6b64\uff0c\u82e5\u60f3\u8981\u8fbe\u5230\u5177\u6709\u8bf4\u670d\u529b\u7684\u5bf9\u6bd4\u7ed3\u679c\uff0c\u90a3\u4e48\u9700\u8981\u8f93\u5165\u5404\u79cd\u4f53\u91cf\u6570\u636e\uff0c\u8fd9\u6837\u7684\u6d4b\u8bd5\u9700\u8981\u5360\u7528\u5927\u91cf\u8ba1\u7b97\u8d44\u6e90\u3002

"},{"location":"chapter_computational_complexity/performance_evaluation/#_2","title":"\u7406\u8bba\u4f30\u7b97","text":"

\u65e2\u7136\u5b9e\u9645\u6d4b\u8bd5\u5177\u6709\u5f88\u5927\u7684\u5c40\u9650\u6027\uff0c\u90a3\u4e48\u6211\u4eec\u662f\u5426\u53ef\u4ee5\u4ec5\u901a\u8fc7\u4e00\u4e9b\u8ba1\u7b97\uff0c\u5c31\u83b7\u77e5\u7b97\u6cd5\u7684\u6548\u7387\u6c34\u5e73\u5462\uff1f\u7b54\u6848\u662f\u80af\u5b9a\u7684\uff0c\u6211\u4eec\u5c06\u6b64\u4f30\u7b97\u65b9\u6cd5\u79f0\u4e3a\u300c\u590d\u6742\u5ea6\u5206\u6790 Complexity Analysis\u300d\u6216\u300c\u6e10\u8fd1\u590d\u6742\u5ea6\u5206\u6790 Asymptotic Complexity Analysis\u300d\u3002

\u590d\u6742\u5ea6\u5206\u6790\u8bc4\u4f30\u968f\u7740\u8f93\u5165\u6570\u636e\u91cf\u7684\u589e\u957f\uff0c\u7b97\u6cd5\u7684\u8fd0\u884c\u65f6\u95f4\u548c\u5360\u7528\u7a7a\u95f4\u7684\u589e\u957f\u8d8b\u52bf\u3002\u6839\u636e\u65f6\u95f4\u548c\u7a7a\u95f4\u4e24\u65b9\u9762\uff0c\u590d\u6742\u5ea6\u53ef\u5206\u4e3a\u300c\u65f6\u95f4\u590d\u6742\u5ea6 Time Complexity\u300d\u548c\u300c\u7a7a\u95f4\u590d\u6742\u5ea6 Space Complexity\u300d\u3002

\u590d\u6742\u5ea6\u5206\u6790\u514b\u670d\u4e86\u5b9e\u9645\u6d4b\u8bd5\u65b9\u6cd5\u7684\u5f0a\u7aef\u3002\u4e00\u662f\u72ec\u7acb\u4e8e\u6d4b\u8bd5\u73af\u5883\uff0c\u5206\u6790\u7ed3\u679c\u9002\u7528\u4e8e\u6240\u6709\u8fd0\u884c\u5e73\u53f0\u3002\u4e8c\u662f\u53ef\u4ee5\u4f53\u73b0\u4e0d\u540c\u6570\u636e\u91cf\u4e0b\u7684\u7b97\u6cd5\u6548\u7387\uff0c\u5c24\u5176\u662f\u53ef\u4ee5\u53cd\u6620\u5927\u6570\u636e\u91cf\u4e0b\u7684\u7b97\u6cd5\u6027\u80fd\u3002

"},{"location":"chapter_computational_complexity/performance_evaluation/#213","title":"2.1.3. \u590d\u6742\u5ea6\u5206\u6790\u91cd\u8981\u6027","text":"

\u590d\u6742\u5ea6\u5206\u6790\u7ed9\u51fa\u4e00\u628a\u8bc4\u4ef7\u7b97\u6cd5\u6548\u7387\u7684\u201c\u6807\u5c3a\u201d\uff0c\u544a\u8bc9\u6211\u4eec\u6267\u884c\u67d0\u4e2a\u7b97\u6cd5\u9700\u8981\u591a\u5c11\u65f6\u95f4\u548c\u7a7a\u95f4\u8d44\u6e90\uff0c\u4e5f\u8ba9\u6211\u4eec\u53ef\u4ee5\u5f00\u5c55\u4e0d\u540c\u7b97\u6cd5\u4e4b\u95f4\u7684\u6548\u7387\u5bf9\u6bd4\u3002

\u8ba1\u7b97\u590d\u6742\u5ea6\u662f\u4e2a\u6570\u5b66\u6982\u5ff5\uff0c\u5bf9\u4e8e\u521d\u5b66\u8005\u53ef\u80fd\u6bd4\u8f83\u62bd\u8c61\uff0c\u5b66\u4e60\u96be\u5ea6\u76f8\u5bf9\u8f83\u9ad8\u3002\u4ece\u8fd9\u4e2a\u89d2\u5ea6\u51fa\u53d1\uff0c\u5176\u5e76\u4e0d\u9002\u5408\u4f5c\u4e3a\u7b2c\u4e00\u7ae0\u5185\u5bb9\u3002\u4f46\u662f\uff0c\u5f53\u6211\u4eec\u8ba8\u8bba\u67d0\u4e2a\u6570\u636e\u7ed3\u6784\u6216\u8005\u7b97\u6cd5\u7684\u7279\u70b9\u65f6\uff0c\u96be\u4ee5\u907f\u514d\u9700\u8981\u5206\u6790\u5b83\u7684\u8fd0\u884c\u901f\u5ea6\u548c\u7a7a\u95f4\u4f7f\u7528\u60c5\u51b5\u3002\u56e0\u6b64\uff0c\u5728\u5c55\u5f00\u5b66\u4e60\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u4e4b\u524d\uff0c\u5efa\u8bae\u8bfb\u8005\u5148\u5bf9\u8ba1\u7b97\u590d\u6742\u5ea6\u5efa\u7acb\u8d77\u521d\u6b65\u7684\u4e86\u89e3\uff0c\u5e76\u4e14\u80fd\u591f\u5b8c\u6210\u7b80\u5355\u6848\u4f8b\u7684\u590d\u6742\u5ea6\u5206\u6790\u3002

"},{"location":"chapter_computational_complexity/space_complexity/","title":"2.3. \u7a7a\u95f4\u590d\u6742\u5ea6","text":"

\u300c\u7a7a\u95f4\u590d\u6742\u5ea6 Space Complexity\u300d\u7edf\u8ba1 \u7b97\u6cd5\u4f7f\u7528\u5185\u5b58\u7a7a\u95f4\u968f\u7740\u6570\u636e\u91cf\u53d8\u5927\u65f6\u7684\u589e\u957f\u8d8b\u52bf\u3002\u8fd9\u4e2a\u6982\u5ff5\u4e0e\u65f6\u95f4\u590d\u6742\u5ea6\u5f88\u7c7b\u4f3c\u3002

"},{"location":"chapter_computational_complexity/space_complexity/#231","title":"2.3.1. \u7b97\u6cd5\u76f8\u5173\u7a7a\u95f4","text":"

\u7b97\u6cd5\u8fd0\u884c\u4e2d\uff0c\u4f7f\u7528\u7684\u5185\u5b58\u7a7a\u95f4\u4e3b\u8981\u6709\u4ee5\u4e0b\u51e0\u79cd\uff1a

  • \u300c\u8f93\u5165\u7a7a\u95f4\u300d\u7528\u4e8e\u5b58\u50a8\u7b97\u6cd5\u7684\u8f93\u5165\u6570\u636e\uff1b
  • \u300c\u6682\u5b58\u7a7a\u95f4\u300d\u7528\u4e8e\u5b58\u50a8\u7b97\u6cd5\u8fd0\u884c\u4e2d\u7684\u53d8\u91cf\u3001\u5bf9\u8c61\u3001\u51fd\u6570\u4e0a\u4e0b\u6587\u7b49\u6570\u636e\uff1b
  • \u300c\u8f93\u51fa\u7a7a\u95f4\u300d\u7528\u4e8e\u5b58\u50a8\u7b97\u6cd5\u7684\u8f93\u51fa\u6570\u636e\uff1b

Tip

\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6\u7edf\u8ba1\u8303\u56f4\u662f\u300c\u6682\u5b58\u7a7a\u95f4\u300d+\u300c\u8f93\u51fa\u7a7a\u95f4\u300d\u3002

\u6682\u5b58\u7a7a\u95f4\u53ef\u5206\u4e3a\u4e09\u4e2a\u90e8\u5206\uff1a

  • \u300c\u6682\u5b58\u6570\u636e\u300d\u7528\u4e8e\u4fdd\u5b58\u7b97\u6cd5\u8fd0\u884c\u4e2d\u7684\u5404\u79cd \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61 \u7b49\u3002
  • \u300c\u6808\u5e27\u7a7a\u95f4\u300d\u7528\u4e8e\u4fdd\u5b58\u8c03\u7528\u51fd\u6570\u7684\u4e0a\u4e0b\u6587\u6570\u636e\u3002\u7cfb\u7edf\u6bcf\u6b21\u8c03\u7528\u51fd\u6570\u90fd\u4f1a\u5728\u6808\u7684\u9876\u90e8\u521b\u5efa\u4e00\u4e2a\u6808\u5e27\uff0c\u51fd\u6570\u8fd4\u56de\u65f6\uff0c\u6808\u5e27\u7a7a\u95f4\u4f1a\u88ab\u91ca\u653e\u3002
  • \u300c\u6307\u4ee4\u7a7a\u95f4\u300d\u7528\u4e8e\u4fdd\u5b58\u7f16\u8bd1\u540e\u7684\u7a0b\u5e8f\u6307\u4ee4\uff0c\u5728\u5b9e\u9645\u7edf\u8ba1\u4e2d\u4e00\u822c\u5ffd\u7565\u4e0d\u8ba1\u3002

Fig. \u7b97\u6cd5\u4f7f\u7528\u7684\u76f8\u5173\u7a7a\u95f4

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u7c7b */\nclass Node {\nint val;\nNode next;\nNode(int x) { val = x; }\n}\n/* \u51fd\u6570 */\nint function() {\n// do something...\nreturn 0;\n}\nint algorithm(int n) {        // \u8f93\u5165\u6570\u636e\nfinal int a = 0;          // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nint b = 0;                // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nNode node = new Node(0);  // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nint c = function();       // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c;         // \u8f93\u51fa\u6570\u636e\n}\n
/* \u7ed3\u6784\u4f53 */\nstruct Node {\nint val;\nNode *next;\nNode(int x) : val(x), next(nullptr) {}\n};\n/* \u51fd\u6570 */\nint func() {\n// do something...\nreturn 0;\n}\nint algorithm(int n) {        // \u8f93\u5165\u6570\u636e\nconst int a = 0;          // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nint b = 0;                // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nNode* node = new Node(0);  // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nint c = func();       // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c;         // \u8f93\u51fa\u6570\u636e\n}\n
\"\"\" \u7c7b \"\"\"\nclass Node:\ndef __init__(self, x):\nself.val = x      # \u7ed3\u70b9\u503c\nself.next = None  # \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u6307\u9488\uff08\u5f15\u7528\uff09\n\"\"\" \u51fd\u6570 \"\"\"\ndef function():\n# do something...\nreturn 0\ndef algorithm(n):     # \u8f93\u5165\u6570\u636e\nb = 0             # \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nnode = Node(0)    # \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nc = function()    # \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c  # \u8f93\u51fa\u6570\u636e\n
/* \u7ed3\u6784\u4f53 */\ntype node struct {\nval  int\nnext *node\n}\n/* \u521b\u5efa node \u7ed3\u6784\u4f53  */\nfunc newNode(val int) *node {\nreturn &node{val: val}\n}\n/* \u51fd\u6570 */\nfunc function() int {\n// do something...\nreturn 0\n}\nfunc algorithm(n int) int { // \u8f93\u5165\u6570\u636e\nconst a = 0             // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nb := 0                  // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nnewNode(0)              // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nc := function()         // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c        // \u8f93\u51fa\u6570\u636e\n}\n
/* \u7c7b */\nclass Node {\nval;\nnext;\nconstructor(val) {\nthis.val = val === undefined ? 0 : val; // \u7ed3\u70b9\u503c\nthis.next = null;                       // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u5f15\u7528\n}\n}\n/* \u51fd\u6570 */\nfunction constFunc() {\n// do something\nreturn 0;\n}\nfunction algorithm(n) {       // \u8f93\u5165\u6570\u636e\nconst a = 0;              // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nconst b = 0;              // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nconst node = new Node(0); // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nconst c = constFunc();    // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c;         // \u8f93\u51fa\u6570\u636e\n}\n
/* \u7c7b */\nclass Node {\nval: number;\nnext: Node | null;\nconstructor(val?: number) {\nthis.val = val === undefined ? 0 : val; // \u7ed3\u70b9\u503c\nthis.next = null;                       // \u6307\u5411\u4e0b\u4e00\u7ed3\u70b9\u7684\u5f15\u7528\n}\n}\n/* \u51fd\u6570 */\nfunction constFunc(): number {\n// do something\nreturn 0;\n}\nfunction algorithm(n: number): number { // \u8f93\u5165\u6570\u636e\nconst a = 0;                        // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nconst b = 0;                        // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nconst node = new Node(0);           // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nconst c = constFunc();              // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c;                   // \u8f93\u51fa\u6570\u636e\n}\n
\n
/* \u7c7b */\nclass Node\n{\nint val;\nNode next;\nNode(int x) { val = x; }\n}\n/* \u51fd\u6570 */\nint function()\n{\n// do something...\nreturn 0;\n}\nint algorithm(int n)          // \u8f93\u5165\u6570\u636e\n{\nint a = 0;                // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nint b = 0;                // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nNode node = new Node(0);  // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nint c = function();       // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c;         // \u8f93\u51fa\u6570\u636e\n}\n
/* \u7c7b */\nclass Node {\nvar val: Int\nvar next: Node?\ninit(x: Int) {\nval = x\n}\n}\n/* \u51fd\u6570 */\nfunc function() -> Int {\n// do something...\nreturn 0\n}\nfunc algorithm(n: Int) -> Int { // \u8f93\u5165\u6570\u636e\nlet a = 0 // \u6682\u5b58\u6570\u636e\uff08\u5e38\u91cf\uff09\nvar b = 0 // \u6682\u5b58\u6570\u636e\uff08\u53d8\u91cf\uff09\nlet node = Node(x: 0) // \u6682\u5b58\u6570\u636e\uff08\u5bf9\u8c61\uff09\nlet c = function() // \u6808\u5e27\u7a7a\u95f4\uff08\u8c03\u7528\u51fd\u6570\uff09\nreturn a + b + c // \u8f93\u51fa\u6570\u636e\n}\n
\n
"},{"location":"chapter_computational_complexity/space_complexity/#232","title":"2.3.2. \u63a8\u7b97\u65b9\u6cd5","text":"

\u7a7a\u95f4\u590d\u6742\u5ea6\u7684\u63a8\u7b97\u65b9\u6cd5\u548c\u65f6\u95f4\u590d\u6742\u5ea6\u603b\u4f53\u7c7b\u4f3c\uff0c\u53ea\u662f\u4ece\u7edf\u8ba1\u201c\u8ba1\u7b97\u64cd\u4f5c\u6570\u91cf\u201d\u53d8\u4e3a\u7edf\u8ba1\u201c\u4f7f\u7528\u7a7a\u95f4\u5927\u5c0f\u201d\u3002\u4e0e\u65f6\u95f4\u590d\u6742\u5ea6\u4e0d\u540c\u7684\u662f\uff0c\u6211\u4eec\u4e00\u822c\u53ea\u5173\u6ce8\u300c\u6700\u5dee\u7a7a\u95f4\u590d\u6742\u5ea6\u300d\u3002\u8fd9\u662f\u56e0\u4e3a\u5185\u5b58\u7a7a\u95f4\u662f\u4e00\u4e2a\u786c\u6027\u8981\u6c42\uff0c\u6211\u4eec\u5fc5\u987b\u4fdd\u8bc1\u5728\u6240\u6709\u8f93\u5165\u6570\u636e\u4e0b\u90fd\u6709\u8db3\u591f\u7684\u5185\u5b58\u7a7a\u95f4\u9884\u7559\u3002

\u6700\u5dee\u7a7a\u95f4\u590d\u6742\u5ea6\u4e2d\u7684\u201c\u6700\u5dee\u201d\u6709\u4e24\u5c42\u542b\u4e49\uff0c\u5206\u522b\u4e3a\u8f93\u5165\u6570\u636e\u7684\u6700\u5dee\u5206\u5e03\u3001\u7b97\u6cd5\u8fd0\u884c\u4e2d\u7684\u6700\u5dee\u65f6\u95f4\u70b9\u3002

  • \u4ee5\u6700\u5dee\u8f93\u5165\u6570\u636e\u4e3a\u51c6\u3002\u5f53 \\(n < 10\\) \u65f6\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(1)\\) \uff1b\u4f46\u662f\u5f53 \\(n > 10\\) \u65f6\uff0c\u521d\u59cb\u5316\u7684\u6570\u7ec4 nums \u4f7f\u7528 \\(O(n)\\) \u7a7a\u95f4\uff1b\u56e0\u6b64\u6700\u5dee\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \uff1b
  • \u4ee5\u7b97\u6cd5\u8fd0\u884c\u8fc7\u7a0b\u4e2d\u7684\u5cf0\u503c\u5185\u5b58\u4e3a\u51c6\u3002\u7a0b\u5e8f\u5728\u6267\u884c\u6700\u540e\u4e00\u884c\u4e4b\u524d\uff0c\u4f7f\u7528 \\(O(1)\\) \u7a7a\u95f4\uff1b\u5f53\u521d\u59cb\u5316\u6570\u7ec4 nums \u65f6\uff0c\u7a0b\u5e8f\u4f7f\u7528 \\(O(n)\\) \u7a7a\u95f4\uff1b\u56e0\u6b64\u6700\u5dee\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \uff1b
JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
void algorithm(int n) {\nint a = 0;                   // O(1)\nint[] b = new int[10000];    // O(1)\nif (n > 10)\nint[] nums = new int[n]; // O(n)\n}\n
void algorithm(int n) {\nint a = 0;               // O(1)\nvector<int> b(10000);    // O(1)\nif (n > 10)\nvector<int> nums(n); // O(n)\n}\n
def algorithm(n):\na = 0               # O(1)\nb = [0] * 10000     # O(1)\nif n > 10:\nnums = [0] * n  # O(n)\n
func algorithm(n int) {\na := 0                      // O(1)\nb := make([]int, 10000)     // O(1)\nvar nums []int\nif n > 10 {\nnums := make([]int, n)  // O(n)\n}\nfmt.Println(a, b, nums)\n}\n
function algorithm(n) {\nconst a = 0;                   // O(1)\nconst b = new Array(10000);    // O(1)\nif (n > 10) {\nconst nums = new Array(n); // O(n)\n}\n}\n
function algorithm(n: number): void {\nconst a = 0;                   // O(1)\nconst b = new Array(10000);    // O(1)\nif (n > 10) {\nconst nums = new Array(n); // O(n)\n}\n}\n
\n
void algorithm(int n)\n{\nint a = 0;                   // O(1)\nint[] b = new int[10000];    // O(1)\nif (n > 10)\n{\nint[] nums = new int[n]; // O(n)\n}\n}\n
func algorithm(n: Int) {\nlet a = 0 // O(1)\nlet b = Array(repeating: 0, count: 10000) // O(1)\nif n > 10 {\nlet nums = Array(repeating: 0, count: n) // O(n)\n}\n}\n
\n

\u5728\u9012\u5f52\u51fd\u6570\u4e2d\uff0c\u9700\u8981\u6ce8\u610f\u7edf\u8ba1\u6808\u5e27\u7a7a\u95f4\u3002\u4f8b\u5982\u51fd\u6570 loop()\uff0c\u5728\u5faa\u73af\u4e2d\u8c03\u7528\u4e86 \\(n\\) \u6b21 function() \uff0c\u6bcf\u8f6e\u4e2d\u7684 function() \u90fd\u8fd4\u56de\u5e76\u91ca\u653e\u4e86\u6808\u5e27\u7a7a\u95f4\uff0c\u56e0\u6b64\u7a7a\u95f4\u590d\u6742\u5ea6\u4ecd\u4e3a \\(O(1)\\) \u3002\u800c\u9012\u5f52\u51fd\u6570 recur() \u5728\u8fd0\u884c\u4e2d\u4f1a\u540c\u65f6\u5b58\u5728 \\(n\\) \u4e2a\u672a\u8fd4\u56de\u7684 recur() \uff0c\u4ece\u800c\u4f7f\u7528 \\(O(n)\\) \u7684\u6808\u5e27\u7a7a\u95f4\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
int function() {\n// do something\nreturn 0;\n}\n/* \u5faa\u73af O(1) */\nvoid loop(int n) {\nfor (int i = 0; i < n; i++) {\nfunction();\n}\n}\n/* \u9012\u5f52 O(n) */\nvoid recur(int n) {\nif (n == 1) return;\nreturn recur(n - 1);\n}\n
int func() {\n// do something\nreturn 0;\n}\n/* \u5faa\u73af O(1) */\nvoid loop(int n) {\nfor (int i = 0; i < n; i++) {\nfunc();\n}\n}\n/* \u9012\u5f52 O(n) */\nvoid recur(int n) {\nif (n == 1) return;\nreturn recur(n - 1);\n}\n
def function():\n# do something\nreturn 0\n\"\"\" \u5faa\u73af O(1) \"\"\"\ndef loop(n):\nfor _ in range(n):\nfunction()\n\"\"\" \u9012\u5f52 O(n) \"\"\"\ndef recur(n):\nif n == 1: return\nreturn recur(n - 1)\n
func function() int {\n// do something\nreturn 0\n}\n/* \u5faa\u73af O(1) */\nfunc loop(n int) {\nfor i := 0; i < n; i++ {\nfunction()\n}\n}\n/* \u9012\u5f52 O(n) */\nfunc recur(n int) {\nif n == 1 {\nreturn\n}\nrecur(n - 1)\n}\n
function constFunc() {\n// do something\nreturn 0;\n}\n/* \u5faa\u73af O(1) */\nfunction loop(n) {\nfor (let i = 0; i < n; i++) {\nconstFunc();\n}\n}\n/* \u9012\u5f52 O(n) */\nfunction recur(n) {\nif (n === 1) return;\nreturn recur(n - 1);\n}\n
function constFunc(): number {\n// do something\nreturn 0;\n}\n/* \u5faa\u73af O(1) */\nfunction loop(n: number): void {\nfor (let i = 0; i < n; i++) {\nconstFunc();\n}\n}\n/* \u9012\u5f52 O(n) */\nfunction recur(n: number): void {\nif (n === 1) return;\nreturn recur(n - 1);\n}\n
\n
int function()\n{\n// do something\nreturn 0;\n}\n/* \u5faa\u73af O(1) */\nvoid loop(int n)\n{\nfor (int i = 0; i < n; i++)\n{\nfunction();\n}\n}\n/* \u9012\u5f52 O(n) */\nint recur(int n)\n{\nif (n == 1) return 1;\nreturn recur(n - 1);\n}\n
@discardableResult\nfunc function() -> Int {\n// do something\nreturn 0\n}\n/* \u5faa\u73af O(1) */\nfunc loop(n: Int) {\nfor _ in 0 ..< n {\nfunction()\n}\n}\n/* \u9012\u5f52 O(n) */\nfunc recur(n: Int) {\nif n == 1 {\nreturn\n}\nrecur(n: n - 1)\n}\n
\n
"},{"location":"chapter_computational_complexity/space_complexity/#233","title":"2.3.3. \u5e38\u89c1\u7c7b\u578b","text":"

\u8bbe\u8f93\u5165\u6570\u636e\u5927\u5c0f\u4e3a \\(n\\) \uff0c\u5e38\u89c1\u7684\u7a7a\u95f4\u590d\u6742\u5ea6\u7c7b\u578b\u6709\uff08\u4ece\u4f4e\u5230\u9ad8\u6392\u5217\uff09

\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline \\text{\u5e38\u6570\u9636} < \\text{\u5bf9\u6570\u9636} < \\text{\u7ebf\u6027\u9636} < \\text{\u5e73\u65b9\u9636} < \\text{\u6307\u6570\u9636} \\end{aligned} \\]

Fig. \u7a7a\u95f4\u590d\u6742\u5ea6\u7684\u5e38\u89c1\u7c7b\u578b

Tip

\u90e8\u5206\u793a\u4f8b\u4ee3\u7801\u9700\u8981\u4e00\u4e9b\u524d\u7f6e\u77e5\u8bc6\uff0c\u5305\u62ec\u6570\u7ec4\u3001\u94fe\u8868\u3001\u4e8c\u53c9\u6811\u3001\u9012\u5f52\u7b97\u6cd5\u7b49\u3002\u5982\u679c\u9047\u5230\u770b\u4e0d\u61c2\u7684\u5730\u65b9\u65e0\u9700\u62c5\u5fc3\uff0c\u53ef\u4ee5\u5728\u5b66\u4e60\u5b8c\u540e\u9762\u7ae0\u8282\u540e\u518d\u6765\u590d\u4e60\uff0c\u73b0\u9636\u6bb5\u5148\u805a\u7126\u5728\u7406\u89e3\u7a7a\u95f4\u590d\u6742\u5ea6\u542b\u4e49\u548c\u63a8\u7b97\u65b9\u6cd5\u4e0a\u3002

"},{"location":"chapter_computational_complexity/space_complexity/#o1","title":"\u5e38\u6570\u9636 \\(O(1)\\)","text":"

\u5e38\u6570\u9636\u5e38\u89c1\u4e8e\u6570\u91cf\u4e0e\u8f93\u5165\u6570\u636e\u5927\u5c0f \\(n\\) \u65e0\u5173\u7684\u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u3002

\u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c\u5728\u5faa\u73af\u4e2d\u521d\u59cb\u5316\u53d8\u91cf\u6216\u8c03\u7528\u51fd\u6570\u800c\u5360\u7528\u7684\u5185\u5b58\uff0c\u5728\u8fdb\u5165\u4e0b\u4e00\u5faa\u73af\u540e\u5c31\u4f1a\u88ab\u91ca\u653e\uff0c\u5373\u4e0d\u4f1a\u7d2f\u79ef\u5360\u7528\u7a7a\u95f4\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6\u4ecd\u4e3a \\(O(1)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig space_complexity.java
/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nfinal int a = 0;\nint b = 0;\nint[] nums = new int[10000];\nListNode node = new ListNode(0);\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor (int i = 0; i < n; i++) {\nint c = 0;\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor (int i = 0; i < n; i++) {\nfunction();\n}\n}\n
space_complexity.cpp
/* \u5e38\u6570\u9636 */\nvoid constant(int n) {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nconst int a = 0;\nint b = 0;\nvector<int> nums(10000);\nListNode node(0);\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor (int i = 0; i < n; i++) {\nint c = 0;\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor (int i = 0; i < n; i++) {\nfunc();\n}\n}\n
space_complexity.py
\"\"\" \u5e38\u6570\u9636 \"\"\"\ndef constant(n):\n# \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\na = 0\nnums = [0] * 10000\nnode = ListNode(0)\n# \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor _ in range(n):\nc = 0\n# \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor _ in range(n):\nfunction()\n
space_complexity.go
/* \u5e38\u6570\u9636 */\nfunc spaceConstant(n int) {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nconst a = 0\nb := 0\nnums := make([]int, 10000)\nListNode := newNode(0)\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nvar c int\nfor i := 0; i < n; i++ {\nc = 0\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor i := 0; i < n; i++ {\nfunction()\n}\nfmt.Println(a, b, nums, c, ListNode)\n}\n
space_complexity.js
/* \u5e38\u6570\u9636 */\nfunction constant(n) {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nconst a = 0;\nconst b = 0;\nconst nums = new Array(10000);\nconst node = new ListNode(0);\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor (let i = 0; i < n; i++) {\nconst c = 0;\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor (let i = 0; i < n; i++) {\nconstFunc();\n}\n}\n
space_complexity.ts
/* \u5e38\u6570\u9636 */\nfunction constant(n: number): void {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nconst a = 0;\nconst b = 0;\nconst nums = new Array(10000);\nconst node = new ListNode(0);\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor (let i = 0; i < n; i++) {\nconst c = 0;\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor (let i = 0; i < n; i++) {\nconstFunc();\n}\n}\n
space_complexity.c
\n
space_complexity.cs
/* \u5e38\u6570\u9636 */\nvoid constant(int n)\n{\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nint a = 0;\nint b = 0;\nint[] nums = new int[10000];\nListNode node = new ListNode(0);\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor (int i = 0; i < n; i++)\n{\nint c = 0;\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor (int i = 0; i < n; i++)\n{\nfunction();\n}\n}\n
space_complexity.swift
/* \u5e38\u6570\u9636 */\nfunc constant(n: Int) {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nlet a = 0\nvar b = 0\nlet nums = Array(repeating: 0, count: 10000)\nlet node = ListNode(x: 0)\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nfor _ in 0 ..< n {\nlet c = 0\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\nfor _ in 0 ..< n {\nfunction()\n}\n}\n
space_complexity.zig
// \u5e38\u6570\u9636\nfn constant(n: i32) void {\n// \u5e38\u91cf\u3001\u53d8\u91cf\u3001\u5bf9\u8c61\u5360\u7528 O(1) \u7a7a\u95f4\nconst a: i32 = 0;\nvar b: i32 = 0;\nvar nums = [_]i32{0}**10000;\nvar node = inc.ListNode(i32){.val = 0};\nvar i: i32 = 0;\n// \u5faa\u73af\u4e2d\u7684\u53d8\u91cf\u5360\u7528 O(1) \u7a7a\u95f4\nwhile (i < n) : (i += 1) {\nvar c: i32 = 0;\n_ = c;\n}\n// \u5faa\u73af\u4e2d\u7684\u51fd\u6570\u5360\u7528 O(1) \u7a7a\u95f4\ni = 0;\nwhile (i < n) : (i += 1) {\n_ = function();\n}\n_ = a;\n_ = b;\n_ = nums;\n_ = node;\n}\n
"},{"location":"chapter_computational_complexity/space_complexity/#on","title":"\u7ebf\u6027\u9636 \\(O(n)\\)","text":"

\u7ebf\u6027\u9636\u5e38\u89c1\u4e8e\u5143\u7d20\u6570\u91cf\u4e0e \\(n\\) \u6210\u6b63\u6bd4\u7684\u6570\u7ec4\u3001\u94fe\u8868\u3001\u6808\u3001\u961f\u5217\u7b49\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig space_complexity.java
/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nint[] nums = new int[n];\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nList<ListNode> nodes = new ArrayList<>();\nfor (int i = 0; i < n; i++) {\nnodes.add(new ListNode(i));\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nMap<Integer, String> map = new HashMap<>();\nfor (int i = 0; i < n; i++) {\nmap.put(i, String.valueOf(i));\n}\n}\n
space_complexity.cpp
/* \u7ebf\u6027\u9636 */\nvoid linear(int n) {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nvector<int> nums(n);\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nvector<ListNode> nodes;\nfor (int i = 0; i < n; i++) {\nnodes.push_back(ListNode(i));\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nunordered_map<int, string> map;\nfor (int i = 0; i < n; i++) {\nmap[i] = to_string(i);\n}\n}\n
space_complexity.py
\"\"\" \u7ebf\u6027\u9636 \"\"\"\ndef linear(n):\n# \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nnums = [0] * n\n# \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nmapp = {}\nfor i in range(n):\nmapp[i] = str(i)\n
space_complexity.go
/* \u7ebf\u6027\u9636 */\nfunc spaceLinear(n int) {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\n_ = make([]int, n)\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nvar nodes []*node\nfor i := 0; i < n; i++ {\nnodes = append(nodes, newNode(i))\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nm := make(map[int]string, n)\nfor i := 0; i < n; i++ {\nm[i] = strconv.Itoa(i)\n}\n}\n
space_complexity.js
/* \u7ebf\u6027\u9636 */\nfunction linear(n) {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nconst nums = new Array(n);\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nconst nodes = [];\nfor (let i = 0; i < n; i++) {\nnodes.push(new ListNode(i));\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nconst map = new Map();\nfor (let i = 0; i < n; i++) {\nmap.set(i, i.toString());\n}\n}\n
space_complexity.ts
/* \u7ebf\u6027\u9636 */\nfunction linear(n: number): void {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nconst nums = new Array(n);\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nconst nodes: ListNode[] = [];\nfor (let i = 0; i < n; i++) {\nnodes.push(new ListNode(i));\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nconst map = new Map();\nfor (let i = 0; i < n; i++) {\nmap.set(i, i.toString());\n}\n}\n
space_complexity.c
\n
space_complexity.cs
/* \u7ebf\u6027\u9636 */\nvoid linear(int n)\n{\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nint[] nums = new int[n];\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nList<ListNode> nodes = new();\nfor (int i = 0; i < n; i++)\n{\nnodes.Add(new ListNode(i));\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nDictionary<int, String> map = new();\nfor (int i = 0; i < n; i++)\n{\nmap.Add(i, i.ToString());\n}\n}\n
space_complexity.swift
/* \u7ebf\u6027\u9636 */\nfunc linear(n: Int) {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nlet nums = Array(repeating: 0, count: n)\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nlet nodes = (0 ..< n).map { ListNode(x: $0) }\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nlet map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
space_complexity.zig
// \u7ebf\u6027\u9636\nfn linear(comptime n: i32) !void {\n// \u957f\u5ea6\u4e3a n \u7684\u6570\u7ec4\u5360\u7528 O(n) \u7a7a\u95f4\nvar nums = [_]i32{0}**n;\n// \u957f\u5ea6\u4e3a n \u7684\u5217\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nvar nodes = std.ArrayList(i32).init(std.heap.page_allocator);\ndefer nodes.deinit();\nvar i: i32 = 0;\nwhile (i < n) : (i += 1) {\ntry nodes.append(i);\n}\n// \u957f\u5ea6\u4e3a n \u7684\u54c8\u5e0c\u8868\u5360\u7528 O(n) \u7a7a\u95f4\nvar map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator);\ndefer map.deinit();\nvar j: i32 = 0;\nwhile (j < n) : (j += 1) {\nconst string = try std.fmt.allocPrint(std.heap.page_allocator, \"{d}\", .{j});\ndefer std.heap.page_allocator.free(string);\ntry map.put(i, string);\n}\n_ = nums;\n}\n

\u4ee5\u4e0b\u9012\u5f52\u51fd\u6570\u4f1a\u540c\u65f6\u5b58\u5728 \\(n\\) \u4e2a\u672a\u8fd4\u56de\u7684 algorithm() \u51fd\u6570\uff0c\u4f7f\u7528 \\(O(n)\\) \u5927\u5c0f\u7684\u6808\u5e27\u7a7a\u95f4\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig space_complexity.java
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\nSystem.out.println(\"\u9012\u5f52 n = \" + n);\nif (n == 1) return;\nlinearRecur(n - 1);\n}\n
space_complexity.cpp
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n) {\ncout << \"\u9012\u5f52 n = \" << n << endl;\nif (n == 1) return;\nlinearRecur(n - 1);\n}\n
space_complexity.py
\"\"\" \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 \"\"\"\ndef linear_recur(n):\nprint(\"\u9012\u5f52 n =\", n)\nif n == 1: return\nlinear_recur(n - 1)\n
space_complexity.go
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc spaceLinearRecur(n int) {\nfmt.Println(\"\u9012\u5f52 n =\", n)\nif n == 1 {\nreturn\n}\nspaceLinearRecur(n - 1)\n}\n
space_complexity.js
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction linearRecur(n) {\nconsole.log(`\u9012\u5f52 n = ${n}`);\nif (n === 1) return;\nlinearRecur(n - 1);\n}\n
space_complexity.ts
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction linearRecur(n: number): void {\nconsole.log(`\u9012\u5f52 n = ${n}`);\nif (n === 1) return;\nlinearRecur(n - 1);\n}\n
space_complexity.c
\n
space_complexity.cs
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nvoid linearRecur(int n)\n{\nConsole.WriteLine(\"\u9012\u5f52 n = \" + n);\nif (n == 1) return;\nlinearRecur(n - 1);\n}\n
space_complexity.swift
/* \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc linearRecur(n: Int) {\nprint(\"\u9012\u5f52 n = \\(n)\")\nif n == 1 {\nreturn\n}\nlinearRecur(n: n - 1)\n}\n
space_complexity.zig
// \u7ebf\u6027\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn linearRecur(comptime n: i32) void {\nstd.debug.print(\"\u9012\u5f52 n = {}\\n\", .{n});\nif (n == 1) return;\nlinearRecur(n - 1);\n}\n

Fig. \u9012\u5f52\u51fd\u6570\u4ea7\u751f\u7684\u7ebf\u6027\u9636\u7a7a\u95f4\u590d\u6742\u5ea6

"},{"location":"chapter_computational_complexity/space_complexity/#on2","title":"\u5e73\u65b9\u9636 \\(O(n^2)\\)","text":"

\u5e73\u65b9\u9636\u5e38\u89c1\u4e8e\u5143\u7d20\u6570\u91cf\u4e0e \\(n\\) \u6210\u5e73\u65b9\u5173\u7cfb\u7684\u77e9\u9635\u3001\u56fe\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig space_complexity.java
/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n// \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\nint[][] numMatrix = new int[n][n];\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nList<List<Integer>> numList = new ArrayList<>();\nfor (int i = 0; i < n; i++) {\nList<Integer> tmp = new ArrayList<>();\nfor (int j = 0; j < n; j++) {\ntmp.add(0);\n}\nnumList.add(tmp);\n}\n}\n
space_complexity.cpp
/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n) {\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nvector<vector<int>> numMatrix;\nfor (int i = 0; i < n; i++) {\nvector<int> tmp;\nfor (int j = 0; j < n; j++) {\ntmp.push_back(0);\n}\nnumMatrix.push_back(tmp);\n}\n}\n
space_complexity.py
\"\"\" \u5e73\u65b9\u9636 \"\"\"\ndef quadratic(n):\n# \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nnum_matrix = [[0] * n for _ in range(n)]\n
space_complexity.go
/* \u5e73\u65b9\u9636 */\nfunc spaceQuadratic(n int) {\n// \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\nnumMatrix := make([][]int, n)\nfor i := 0; i < n; i++ {\nnumMatrix[i] = make([]int, n)\n}\n}\n
space_complexity.js
/* \u5e73\u65b9\u9636 */\nfunction quadratic(n) {\n// \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\nconst numMatrix = Array(n).fill(null).map(() => Array(n).fill(null));\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nconst numList = [];\nfor (let i = 0; i < n; i++) {\nconst tmp = [];\nfor (let j = 0; j < n; j++) {\ntmp.push(0);\n}\nnumList.push(tmp);\n}\n}\n
space_complexity.ts
/* \u5e73\u65b9\u9636 */\nfunction quadratic(n: number): void {\n// \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\nconst numMatrix = Array(n).fill(null).map(() => Array(n).fill(null));\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nconst numList = [];\nfor (let i = 0; i < n; i++) {\nconst tmp = [];\nfor (let j = 0; j < n; j++) {\ntmp.push(0);\n}\nnumList.push(tmp);\n}\n}\n
space_complexity.c
\n
space_complexity.cs
/* \u5e73\u65b9\u9636 */\nvoid quadratic(int n)\n{\n// \u77e9\u9635\u5360\u7528 O(n^2) \u7a7a\u95f4\nint[,] numMatrix = new int[n, n];\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nList<List<int>> numList = new();\nfor (int i = 0; i < n; i++)\n{\nList<int> tmp = new();\nfor (int j = 0; j < n; j++)\n{\ntmp.Add(0);\n}\nnumList.Add(tmp);\n}\n}\n
space_complexity.swift
/* \u5e73\u65b9\u9636 */\nfunc quadratic(n: Int) {\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nlet numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
space_complexity.zig
// \u5e73\u65b9\u9636\nfn quadratic(n: i32) !void {\n// \u4e8c\u7ef4\u5217\u8868\u5360\u7528 O(n^2) \u7a7a\u95f4\nvar nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator);\ndefer nodes.deinit();\nvar i: i32 = 0;\nwhile (i < n) : (i += 1) {\nvar tmp = std.ArrayList(i32).init(std.heap.page_allocator);\ndefer tmp.deinit();\nvar j: i32 = 0;\nwhile (j < n) : (j += 1) {\ntry tmp.append(0);\n}\ntry nodes.append(tmp);\n}\n}\n

\u5728\u4ee5\u4e0b\u9012\u5f52\u51fd\u6570\u4e2d\uff0c\u540c\u65f6\u5b58\u5728 \\(n\\) \u4e2a\u672a\u8fd4\u56de\u7684 algorithm() \uff0c\u5e76\u4e14\u6bcf\u4e2a\u51fd\u6570\u4e2d\u90fd\u521d\u59cb\u5316\u4e86\u4e00\u4e2a\u6570\u7ec4\uff0c\u957f\u5ea6\u5206\u522b\u4e3a \\(n, n-1, n-2, ..., 2, 1\\) \uff0c\u5e73\u5747\u957f\u5ea6\u4e3a \\(\\frac{n}{2}\\) \uff0c\u56e0\u6b64\u603b\u4f53\u4f7f\u7528 \\(O(n^2)\\) \u7a7a\u95f4\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig space_complexity.java
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\nif (n <= 0) return 0;\n// \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\nint[] nums = new int[n];\nSystem.out.println(\"\u9012\u5f52 n = \" + n + \" \u4e2d\u7684 nums \u957f\u5ea6 = \" + nums.length);\nreturn quadraticRecur(n - 1);\n}\n
space_complexity.cpp
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n) {\nif (n <= 0) return 0;\nvector<int> nums(n);\ncout << \"\u9012\u5f52 n = \" << n << \" \u4e2d\u7684 nums \u957f\u5ea6 = \" << nums.size() << endl;\nreturn quadraticRecur(n - 1);\n}\n
space_complexity.py
\"\"\" \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 \"\"\"\ndef quadratic_recur(n):\nif n <= 0: return 0\n# \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\nnums = [0] * n\nreturn quadratic_recur(n - 1)\n
space_complexity.go
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc spaceQuadraticRecur(n int) int {\nif n <= 0 {\nreturn 0\n}\n// \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\nnums := make([]int, n)\nreturn spaceQuadraticRecur(n - 1)\n}\n
space_complexity.js
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction quadraticRecur(n) {\nif (n <= 0) return 0;\nconst nums = new Array(n);\nconsole.log(`\u9012\u5f52 n = ${n} \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}`);\nreturn quadraticRecur(n - 1);\n}\n
space_complexity.ts
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction quadraticRecur(n: number): number {\nif (n <= 0) return 0;\nconst nums = new Array(n);\nconsole.log(`\u9012\u5f52 n = ${n} \u4e2d\u7684 nums \u957f\u5ea6 = ${nums.length}`);\nreturn quadraticRecur(n - 1);\n}\n
space_complexity.c
\n
space_complexity.cs
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint quadraticRecur(int n)\n{\nif (n <= 0) return 0;\n// \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\nint[] nums = new int[n];\nreturn quadraticRecur(n - 1);\n}\n
space_complexity.swift
/* \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc quadraticRecur(n: Int) -> Int {\nif n <= 0 {\nreturn 0\n}\n// \u6570\u7ec4 nums \u957f\u5ea6\u4e3a n, n-1, ..., 2, 1\nlet nums = Array(repeating: 0, count: n)\nreturn quadraticRecur(n: n - 1)\n}\n
space_complexity.zig
// \u5e73\u65b9\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn quadraticRecur(comptime n: i32) i32 {\nif (n <= 0) return 0;\nvar nums = [_]i32{0}**n;\nstd.debug.print(\"\u9012\u5f52 n = {} \u4e2d\u7684 nums \u957f\u5ea6 = {}\\n\", .{n, nums.len});\nreturn quadraticRecur(n - 1);\n}\n

Fig. \u9012\u5f52\u51fd\u6570\u4ea7\u751f\u7684\u5e73\u65b9\u9636\u7a7a\u95f4\u590d\u6742\u5ea6

"},{"location":"chapter_computational_complexity/space_complexity/#o2n","title":"\u6307\u6570\u9636 \\(O(2^n)\\)","text":"

\u6307\u6570\u9636\u5e38\u89c1\u4e8e\u4e8c\u53c9\u6811\u3002\u9ad8\u5ea6\u4e3a \\(n\\) \u7684\u300c\u6ee1\u4e8c\u53c9\u6811\u300d\u7684\u7ed3\u70b9\u6570\u91cf\u4e3a \\(2^n - 1\\) \uff0c\u4f7f\u7528 \\(O(2^n)\\) \u7a7a\u95f4\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig space_complexity.java
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode buildTree(int n) {\nif (n == 0) return null;\nTreeNode root = new TreeNode(0);\nroot.left = buildTree(n - 1);\nroot.right = buildTree(n - 1);\nreturn root;\n}\n
space_complexity.cpp
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode* buildTree(int n) {\nif (n == 0) return nullptr;\nTreeNode* root = new TreeNode(0);\nroot->left = buildTree(n - 1);\nroot->right = buildTree(n - 1);\nreturn root;\n}\n
space_complexity.py
\"\"\" \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 \"\"\"\ndef build_tree(n):\nif n == 0: return None\nroot = TreeNode(0)\nroot.left = build_tree(n - 1)\nroot.right = build_tree(n - 1)\nreturn root\n
space_complexity.go
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunc buildTree(n int) *treeNode {\nif n == 0 {\nreturn nil\n}\nroot := newTreeNode(0)\nroot.left = buildTree(n - 1)\nroot.right = buildTree(n - 1)\nreturn root\n}\n
space_complexity.js
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunction buildTree(n) {\nif (n === 0) return null;\nconst root = new TreeNode(0);\nroot.left = buildTree(n - 1);\nroot.right = buildTree(n - 1);\nreturn root;\n}\n
space_complexity.ts
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunction buildTree(n: number): TreeNode | null {\nif (n === 0) return null;\nconst root = new TreeNode(0);\nroot.left = buildTree(n - 1);\nroot.right = buildTree(n - 1);\nreturn root;\n}\n
space_complexity.c
\n
space_complexity.cs
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nTreeNode? buildTree(int n)\n{\nif (n == 0) return null;\nTreeNode root = new TreeNode(0);\nroot.left = buildTree(n - 1);\nroot.right = buildTree(n - 1);\nreturn root;\n}\n
space_complexity.swift
/* \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09 */\nfunc buildTree(n: Int) -> TreeNode? {\nif n == 0 {\nreturn nil\n}\nlet root = TreeNode(x: 0)\nroot.left = buildTree(n: n - 1)\nroot.right = buildTree(n: n - 1)\nreturn root\n}\n
space_complexity.zig
// \u6307\u6570\u9636\uff08\u5efa\u7acb\u6ee1\u4e8c\u53c9\u6811\uff09\nfn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) {\nif (n == 0) return null;\nconst root = try mem_allocator.create(inc.TreeNode(i32));\nroot.init(0);\nroot.left = try buildTree(mem_allocator, n - 1);\nroot.right = try buildTree(mem_allocator, n - 1);\nreturn root;\n}\n

Fig. \u6ee1\u4e8c\u53c9\u6811\u4e0b\u7684\u6307\u6570\u9636\u7a7a\u95f4\u590d\u6742\u5ea6

"},{"location":"chapter_computational_complexity/space_complexity/#olog-n","title":"\u5bf9\u6570\u9636 \\(O(\\log n)\\)","text":"

\u5bf9\u6570\u9636\u5e38\u89c1\u4e8e\u5206\u6cbb\u7b97\u6cd5\u3001\u6570\u636e\u7c7b\u578b\u8f6c\u6362\u7b49\u3002

\u4f8b\u5982\u300c\u5f52\u5e76\u6392\u5e8f\u300d\uff0c\u957f\u5ea6\u4e3a \\(n\\) \u7684\u6570\u7ec4\u53ef\u4ee5\u5f62\u6210\u9ad8\u5ea6\u4e3a \\(\\log n\\) \u7684\u9012\u5f52\u6811\uff0c\u56e0\u6b64\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(\\log n)\\) \u3002

\u518d\u4f8b\u5982\u300c\u6570\u5b57\u8f6c\u5316\u4e3a\u5b57\u7b26\u4e32\u300d\uff0c\u8f93\u5165\u4efb\u610f\u6b63\u6574\u6570 \\(n\\) \uff0c\u5b83\u7684\u4f4d\u6570\u4e3a \\(\\log_{10} n\\) \uff0c\u5373\u5bf9\u5e94\u5b57\u7b26\u4e32\u957f\u5ea6\u4e3a \\(\\log_{10} n\\) \uff0c\u56e0\u6b64\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(\\log_{10} n) = O(\\log n)\\) \u3002

"},{"location":"chapter_computational_complexity/space_time_tradeoff/","title":"2.4. \u6743\u8861\u65f6\u95f4\u4e0e\u7a7a\u95f4","text":"

\u7406\u60f3\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u5e0c\u671b\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u548c\u7a7a\u95f4\u590d\u6742\u5ea6\u90fd\u80fd\u591f\u8fbe\u5230\u6700\u4f18\uff0c\u800c\u5b9e\u9645\u4e0a\uff0c\u540c\u65f6\u4f18\u5316\u65f6\u95f4\u590d\u6742\u5ea6\u548c\u7a7a\u95f4\u590d\u6742\u5ea6\u662f\u975e\u5e38\u56f0\u96be\u7684\u3002

\u964d\u4f4e\u65f6\u95f4\u590d\u6742\u5ea6\uff0c\u5f80\u5f80\u662f\u4ee5\u63d0\u5347\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a\u4ee3\u4ef7\u7684\uff0c\u53cd\u4e4b\u4ea6\u7136\u3002\u6211\u4eec\u628a\u727a\u7272\u5185\u5b58\u7a7a\u95f4\u6765\u63d0\u5347\u7b97\u6cd5\u8fd0\u884c\u901f\u5ea6\u7684\u601d\u8def\u79f0\u4e3a\u300c\u4ee5\u7a7a\u95f4\u6362\u65f6\u95f4\u300d\uff1b\u53cd\u4e4b\uff0c\u79f0\u4e4b\u4e3a\u300c\u4ee5\u65f6\u95f4\u6362\u7a7a\u95f4\u300d\u3002\u9009\u62e9\u54ea\u79cd\u601d\u8def\u53d6\u51b3\u4e8e\u6211\u4eec\u66f4\u770b\u91cd\u54ea\u4e2a\u65b9\u9762\u3002

\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u65f6\u95f4\u90fd\u662f\u6bd4\u7a7a\u95f4\u66f4\u5b9d\u8d35\u7684\uff0c\u53ea\u8981\u7a7a\u95f4\u590d\u6742\u5ea6\u4e0d\u8981\u592a\u79bb\u8c31\u3001\u80fd\u63a5\u53d7\u5c31\u884c\uff0c\u56e0\u6b64\u4ee5\u7a7a\u95f4\u6362\u65f6\u95f4\u6700\u4e3a\u5e38\u7528\u3002

"},{"location":"chapter_computational_complexity/space_time_tradeoff/#241","title":"2.4.1. \u793a\u4f8b\u9898\u76ee *","text":"

\u4ee5 LeetCode \u5168\u7ad9\u7b2c\u4e00\u9898 \u4e24\u6570\u4e4b\u548c \u4e3a\u4f8b\u3002

\u4e24\u6570\u4e4b\u548c

\u7ed9\u5b9a\u4e00\u4e2a\u6574\u6570\u6570\u7ec4 nums \u548c\u4e00\u4e2a\u6574\u6570\u76ee\u6807\u503c target \uff0c\u8bf7\u4f60\u5728\u8be5\u6570\u7ec4\u4e2d\u627e\u51fa\u201c\u548c\u201d\u4e3a\u76ee\u6807\u503c target \u7684\u90a3\u4e24\u4e2a\u6574\u6570\uff0c\u5e76\u8fd4\u56de\u5b83\u4eec\u7684\u6570\u7ec4\u4e0b\u6807\u3002

\u4f60\u53ef\u4ee5\u5047\u8bbe\u6bcf\u79cd\u8f93\u5165\u53ea\u4f1a\u5bf9\u5e94\u4e00\u4e2a\u7b54\u6848\u3002\u4f46\u662f\uff0c\u6570\u7ec4\u4e2d\u540c\u4e00\u4e2a\u5143\u7d20\u5728\u7b54\u6848\u91cc\u4e0d\u80fd\u91cd\u590d\u51fa\u73b0\u3002

\u4f60\u53ef\u4ee5\u6309\u4efb\u610f\u987a\u5e8f\u8fd4\u56de\u7b54\u6848\u3002

\u300c\u66b4\u529b\u679a\u4e3e\u300d\u548c\u300c\u8f85\u52a9\u54c8\u5e0c\u8868\u300d\u5206\u522b\u4e3a \u7a7a\u95f4\u6700\u4f18 \u548c \u65f6\u95f4\u6700\u4f18 \u7684\u4e24\u79cd\u89e3\u6cd5\u3002\u672c\u7740\u65f6\u95f4\u6bd4\u7a7a\u95f4\u66f4\u5b9d\u8d35\u7684\u539f\u5219\uff0c\u540e\u8005\u662f\u672c\u9898\u7684\u6700\u4f73\u89e3\u6cd5\u3002

"},{"location":"chapter_computational_complexity/space_time_tradeoff/#_1","title":"\u65b9\u6cd5\u4e00\uff1a\u66b4\u529b\u679a\u4e3e","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(N^2)\\) \uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff0c\u5c5e\u4e8e\u300c\u65f6\u95f4\u6362\u7a7a\u95f4\u300d\u3002

\u867d\u7136\u4ec5\u4f7f\u7528\u5e38\u6570\u5927\u5c0f\u7684\u989d\u5916\u7a7a\u95f4\uff0c\u4f46\u8fd0\u884c\u901f\u5ea6\u8fc7\u6162\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig leetcode_two_sum.java
/* \u65b9\u6cd5\u4e00\uff1a\u66b4\u529b\u679a\u4e3e */\nclass SolutionBruteForce {\npublic int[] twoSum(int[] nums, int target) {\nint size = nums.length;\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor (int i = 0; i < size - 1; i++) {\nfor (int j = i + 1; j < size; j++) {\nif (nums[i] + nums[j] == target)\nreturn new int[] { i, j };\n}\n}\nreturn new int[0];\n}\n}\n
leetcode_two_sum.cpp
class SolutionBruteForce {\npublic:\nvector<int> twoSum(vector<int>& nums, int target) {\nint size = nums.size();\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor (int i = 0; i < size - 1; i++) {\nfor (int j = i + 1; j < size; j++) {\nif (nums[i] + nums[j] == target)\nreturn { i, j };\n}\n}\nreturn {};\n}\n};\n
leetcode_two_sum.py
\"\"\" \u65b9\u6cd5\u4e00\uff1a\u66b4\u529b\u679a\u4e3e \"\"\"\nclass SolutionBruteForce:\ndef twoSum(self, nums: List[int], target: int) -> List[int]:\n# \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor i in range(len(nums) - 1):\nfor j in range(i + 1, len(nums)):\nif nums[i] + nums[j] == target:\nreturn i, j\nreturn []\n
leetcode_two_sum.go
func twoSumBruteForce(nums []int, target int) []int {\nsize := len(nums)\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor i := 0; i < size-1; i++ {\nfor j := i + 1; i < size; j++ {\nif nums[i]+nums[j] == target {\nreturn []int{i, j}\n}\n}\n}\nreturn nil\n}\n
leetcode_two_sum.js
function twoSumBruteForce(nums, target) {\nconst n = nums.length;\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor (let i = 0; i < n; i++) {\nfor (let j = i + 1; j < n; j++) {\nif (nums[i] + nums[j] === target) {\nreturn [i, j];\n}\n}\n}\nreturn [];\n}\n
leetcode_two_sum.ts
function twoSumBruteForce(nums: number[], target: number): number[] {\nconst n = nums.length;\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor (let i = 0; i < n; i++) {\nfor (let j = i + 1; j < n; j++) {\nif (nums[i] + nums[j] === target) {\nreturn [i, j];\n}\n}\n}\nreturn [];\n};\n
leetcode_two_sum.c
\n
leetcode_two_sum.cs
class SolutionBruteForce\n{\npublic int[] twoSum(int[] nums, int target)\n{\nint size = nums.Length;\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor (int i = 0; i < size - 1; i++)\n{\nfor (int j = i + 1; j < size; j++)\n{\nif (nums[i] + nums[j] == target)\nreturn new int[] { i, j };\n}\n}\nreturn new int[0];\n}\n}\n
leetcode_two_sum.swift
func twoSumBruteForce(nums: [Int], target: Int) -> [Int] {\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nfor i in nums.indices.dropLast() {\nfor j in nums.indices.dropFirst(i + 1) {\nif nums[i] + nums[j] == target {\nreturn [i, j]\n}\n}\n}\nreturn [0]\n}\n
leetcode_two_sum.zig
const SolutionBruteForce = struct {\npub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 {\n_ = self;\nvar size: usize = nums.len;\nvar i: usize = 0;\n// \u4e24\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n^2)\nwhile (i < size - 1) : (i += 1) {\nvar j = i + 1;\nwhile (j < size) : (j += 1) {\nif (nums[i] + nums[j] == target) {\nreturn [_]i32{@intCast(i32, i), @intCast(i32, j)};\n}\n}\n}\nreturn undefined;\n}\n};\n
"},{"location":"chapter_computational_complexity/space_time_tradeoff/#_2","title":"\u65b9\u6cd5\u4e8c\uff1a\u8f85\u52a9\u54c8\u5e0c\u8868","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(N)\\) \uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(N)\\) \uff0c\u5c5e\u4e8e\u300c\u7a7a\u95f4\u6362\u65f6\u95f4\u300d\u3002

\u501f\u52a9\u8f85\u52a9\u54c8\u5e0c\u8868 dic \uff0c\u901a\u8fc7\u4fdd\u5b58\u6570\u7ec4\u5143\u7d20\u4e0e\u7d22\u5f15\u7684\u6620\u5c04\u6765\u63d0\u5347\u7b97\u6cd5\u8fd0\u884c\u901f\u5ea6\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig leetcode_two_sum.java
/* \u65b9\u6cd5\u4e8c\uff1a\u8f85\u52a9\u54c8\u5e0c\u8868 */\nclass SolutionHashMap {\npublic int[] twoSum(int[] nums, int target) {\nint size = nums.length;\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nMap<Integer, Integer> dic = new HashMap<>();\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor (int i = 0; i < size; i++) {\nif (dic.containsKey(target - nums[i])) {\nreturn new int[] { dic.get(target - nums[i]), i };\n}\ndic.put(nums[i], i);\n}\nreturn new int[0];\n}\n}\n
leetcode_two_sum.cpp
class SolutionHashMap {\npublic:\nvector<int> twoSum(vector<int>& nums, int target) {\nint size = nums.size();\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nunordered_map<int, int> dic;\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor (int i = 0; i < size; i++) {\nif (dic.find(target - nums[i]) != dic.end()) {\nreturn { dic[target - nums[i]], i };\n}\ndic.emplace(nums[i], i);\n}\nreturn {};\n}\n};\n
leetcode_two_sum.py
\"\"\" \u65b9\u6cd5\u4e8c\uff1a\u8f85\u52a9\u54c8\u5e0c\u8868 \"\"\"\nclass SolutionHashMap:\ndef twoSum(self, nums: List[int], target: int) -> List[int]:\n# \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\ndic = {}\n# \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor i in range(len(nums)):\nif target - nums[i] in dic:\nreturn dic[target - nums[i]], i\ndic[nums[i]] = i\nreturn []\n
leetcode_two_sum.go
func twoSumHashTable(nums []int, target int) []int {\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nhashTable := map[int]int{}\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor idx, val := range nums {\nif preIdx, ok := hashTable[target-val]; ok {\nreturn []int{preIdx, idx}\n}\nhashTable[val] = idx\n}\nreturn nil\n}\n
leetcode_two_sum.js
function twoSumHashTable(nums, target) {\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nlet m = {};\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor (let i = 0; i < nums.length; i++) {\nif (m[nums[i]] !== undefined) {\nreturn [m[nums[i]], i];\n} else {\nm[target - nums[i]] = i;\n}\n}\nreturn [];\n}\n
leetcode_two_sum.ts
function twoSumHashTable(nums: number[], target: number): number[] {\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nlet m: Map<number, number> = new Map();\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor (let i = 0; i < nums.length; i++) {\nlet index = m.get(nums[i]);\nif (index !== undefined) {\nreturn [index, i];\n} else {\nm.set(target - nums[i], i);\n}\n}\nreturn [];\n};\n
leetcode_two_sum.c
\n
leetcode_two_sum.cs
class SolutionHashMap\n{\npublic int[] twoSum(int[] nums, int target)\n{\nint size = nums.Length;\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nDictionary<int, int> dic = new();\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor (int i = 0; i < size; i++)\n{\nif (dic.ContainsKey(target - nums[i]))\n{\nreturn new int[] { dic[target - nums[i]], i };\n}\ndic.Add(nums[i], i);\n}\nreturn new int[0];\n}\n}\n
leetcode_two_sum.swift
func twoSumHashTable(nums: [Int], target: Int) -> [Int] {\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nvar dic: [Int: Int] = [:]\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nfor i in nums.indices {\nif let j = dic[target - nums[i]] {\nreturn [j, i]\n}\ndic[nums[i]] = i\n}\nreturn [0]\n}\n
leetcode_two_sum.zig
const SolutionHashMap = struct {\npub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 {\n_ = self;\nvar size: usize = nums.len;\n// \u8f85\u52a9\u54c8\u5e0c\u8868\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6 O(n)\nvar dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator);\ndefer dic.deinit();\nvar i: usize = 0;\n// \u5355\u5c42\u5faa\u73af\uff0c\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nwhile (i < size) : (i += 1) {\nif (dic.contains(target - nums[i])) {\nreturn [_]i32{dic.get(target - nums[i]).?, @intCast(i32, i)};\n}\ntry dic.put(nums[i], @intCast(i32, i));\n}\nreturn undefined;\n}\n};\n
"},{"location":"chapter_computational_complexity/summary/","title":"2.5. \u5c0f\u7ed3","text":""},{"location":"chapter_computational_complexity/summary/#_1","title":"\u7b97\u6cd5\u6548\u7387\u8bc4\u4f30","text":"
  • \u300c\u65f6\u95f4\u6548\u7387\u300d\u548c\u300c\u7a7a\u95f4\u6548\u7387\u300d\u662f\u7b97\u6cd5\u6027\u80fd\u7684\u4e24\u4e2a\u91cd\u8981\u7684\u8bc4\u4ef7\u7ef4\u5ea6\u3002
  • \u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u300c\u5b9e\u9645\u6d4b\u8bd5\u300d\u6765\u8bc4\u4f30\u7b97\u6cd5\u6548\u7387\uff0c\u4f46\u96be\u4ee5\u6392\u9664\u6d4b\u8bd5\u73af\u5883\u7684\u5e72\u6270\uff0c\u5e76\u4e14\u975e\u5e38\u8017\u8d39\u8ba1\u7b97\u8d44\u6e90\u3002
  • \u300c\u590d\u6742\u5ea6\u5206\u6790\u300d\u514b\u670d\u4e86\u5b9e\u9645\u6d4b\u8bd5\u7684\u5f0a\u7aef\uff0c\u5206\u6790\u7ed3\u679c\u9002\u7528\u4e8e\u6240\u6709\u8fd0\u884c\u5e73\u53f0\uff0c\u5e76\u4e14\u53ef\u4ee5\u4f53\u73b0\u4e0d\u540c\u6570\u636e\u5927\u5c0f\u4e0b\u7684\u7b97\u6cd5\u6548\u7387\u3002
"},{"location":"chapter_computational_complexity/summary/#_2","title":"\u65f6\u95f4\u590d\u6742\u5ea6","text":"
  • \u300c\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u7edf\u8ba1\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\u968f\u7740\u6570\u636e\u91cf\u53d8\u5927\u65f6\u7684\u589e\u957f\u8d8b\u52bf\uff0c\u53ef\u4ee5\u6709\u6548\u8bc4\u4f30\u7b97\u6cd5\u6548\u7387\uff0c\u4f46\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u53ef\u80fd\u5931\u6548\uff0c\u6bd4\u5982\u5728\u8f93\u5165\u6570\u636e\u91cf\u8f83\u5c0f\u6216\u65f6\u95f4\u590d\u6742\u5ea6\u76f8\u540c\u65f6\uff0c\u65e0\u6cd5\u7cbe\u786e\u5bf9\u6bd4\u7b97\u6cd5\u6548\u7387\u7684\u4f18\u52a3\u6027\u3002
  • \u300c\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u4f7f\u7528\u5927 \\(O\\) \u7b26\u53f7\u8868\u793a\uff0c\u5373\u51fd\u6570\u6e10\u8fd1\u4e0a\u754c\uff0c\u5176\u53cd\u6620\u5f53 \\(n\\) \u8d8b\u4e8e\u6b63\u65e0\u7a77\u65f6\uff0c\\(T(n)\\) \u5904\u4e8e\u4f55\u79cd\u589e\u957f\u7ea7\u522b\u3002
  • \u63a8\u7b97\u65f6\u95f4\u590d\u6742\u5ea6\u5206\u4e3a\u4e24\u6b65\uff0c\u9996\u5148\u7edf\u8ba1\u8ba1\u7b97\u64cd\u4f5c\u6570\u91cf\uff0c\u518d\u5224\u65ad\u6e10\u8fd1\u4e0a\u754c\u3002
  • \u5e38\u89c1\u65f6\u95f4\u590d\u6742\u5ea6\u4ece\u5c0f\u5230\u5927\u6392\u5217\u6709 \\(O(1)\\) , \\(O(\\log n)\\) , \\(O(n)\\) , \\(O(n \\log n)\\) , \\(O(n^2)\\) , \\(O(2^n)\\) , \\(O(n!)\\) \u3002
  • \u67d0\u4e9b\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e0d\u662f\u6052\u5b9a\u7684\uff0c\u800c\u662f\u4e0e\u8f93\u5165\u6570\u636e\u7684\u5206\u5e03\u6709\u5173\u3002\u65f6\u95f4\u590d\u6742\u5ea6\u5206\u4e3a\u300c\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u548c\u300c\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6\u300d\uff0c\u540e\u8005\u51e0\u4e4e\u4e0d\u7528\uff0c\u56e0\u4e3a\u8f93\u5165\u6570\u636e\u9700\u8981\u6ee1\u8db3\u82db\u523b\u7684\u6761\u4ef6\u624d\u80fd\u8fbe\u5230\u6700\u4f73\u60c5\u51b5\u3002
  • \u300c\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u53ef\u4ee5\u53cd\u6620\u5728\u968f\u673a\u6570\u636e\u8f93\u5165\u4e0b\u7684\u7b97\u6cd5\u6548\u7387\uff0c\u6700\u8d34\u5408\u5b9e\u9645\u4f7f\u7528\u60c5\u51b5\u4e0b\u7684\u7b97\u6cd5\u6027\u80fd\u3002\u8ba1\u7b97\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u9700\u8981\u7edf\u8ba1\u8f93\u5165\u6570\u636e\u7684\u5206\u5e03\uff0c\u4ee5\u53ca\u7efc\u5408\u540e\u7684\u6570\u5b66\u671f\u671b\u3002
"},{"location":"chapter_computational_complexity/summary/#_3","title":"\u7a7a\u95f4\u590d\u6742\u5ea6","text":"
  • \u4e0e\u65f6\u95f4\u590d\u6742\u5ea6\u7684\u5b9a\u4e49\u7c7b\u4f3c\uff0c\u300c\u7a7a\u95f4\u590d\u6742\u5ea6\u300d\u7edf\u8ba1\u7b97\u6cd5\u5360\u7528\u7a7a\u95f4\u968f\u7740\u6570\u636e\u91cf\u53d8\u5927\u65f6\u7684\u589e\u957f\u8d8b\u52bf\u3002

  • \u7b97\u6cd5\u8fd0\u884c\u4e2d\u76f8\u5173\u5185\u5b58\u7a7a\u95f4\u53ef\u5206\u4e3a\u8f93\u5165\u7a7a\u95f4\u3001\u6682\u5b58\u7a7a\u95f4\u3001\u8f93\u51fa\u7a7a\u95f4\u3002\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u8f93\u5165\u7a7a\u95f4\u4e0d\u8ba1\u5165\u7a7a\u95f4\u590d\u6742\u5ea6\u8ba1\u7b97\u3002\u6682\u5b58\u7a7a\u95f4\u53ef\u5206\u4e3a\u6307\u4ee4\u7a7a\u95f4\u3001\u6570\u636e\u7a7a\u95f4\u3001\u6808\u5e27\u7a7a\u95f4\uff0c\u5176\u4e2d\u6808\u5e27\u7a7a\u95f4\u4e00\u822c\u5728\u9012\u5f52\u51fd\u6570\u4e2d\u624d\u4f1a\u5f71\u54cd\u5230\u7a7a\u95f4\u590d\u6742\u5ea6\u3002

  • \u6211\u4eec\u4e00\u822c\u53ea\u5173\u5fc3\u300c\u6700\u5dee\u7a7a\u95f4\u590d\u6742\u5ea6\u300d\uff0c\u5373\u7edf\u8ba1\u7b97\u6cd5\u5728\u300c\u6700\u5dee\u8f93\u5165\u6570\u636e\u300d\u548c\u300c\u6700\u5dee\u8fd0\u884c\u65f6\u95f4\u70b9\u300d\u4e0b\u7684\u7a7a\u95f4\u590d\u6742\u5ea6\u3002
  • \u5e38\u89c1\u7a7a\u95f4\u590d\u6742\u5ea6\u4ece\u5c0f\u5230\u5927\u6392\u5217\u6709 \\(O(1)\\) , \\(O(\\log n)\\) , \\(O(n)\\) , \\(O(n^2)\\) , \\(O(2^n)\\) \u3002
"},{"location":"chapter_computational_complexity/time_complexity/","title":"2.2. \u65f6\u95f4\u590d\u6742\u5ea6","text":""},{"location":"chapter_computational_complexity/time_complexity/#221","title":"2.2.1. \u7edf\u8ba1\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4","text":"

\u8fd0\u884c\u65f6\u95f4\u80fd\u591f\u76f4\u89c2\u4e14\u51c6\u786e\u5730\u4f53\u73b0\u51fa\u7b97\u6cd5\u7684\u6548\u7387\u6c34\u5e73\u3002\u5982\u679c\u6211\u4eec\u60f3\u8981 \u51c6\u786e\u9884\u4f30\u4e00\u6bb5\u4ee3\u7801\u7684\u8fd0\u884c\u65f6\u95f4 \uff0c\u8be5\u5982\u4f55\u505a\u5462\uff1f

  1. \u9996\u5148\u9700\u8981 \u786e\u5b9a\u8fd0\u884c\u5e73\u53f0 \uff0c\u5305\u62ec\u786c\u4ef6\u914d\u7f6e\u3001\u7f16\u7a0b\u8bed\u8a00\u3001\u7cfb\u7edf\u73af\u5883\u7b49\uff0c\u8fd9\u4e9b\u90fd\u4f1a\u5f71\u54cd\u5230\u4ee3\u7801\u7684\u8fd0\u884c\u6548\u7387\u3002
  2. \u8bc4\u4f30 \u5404\u79cd\u8ba1\u7b97\u64cd\u4f5c\u7684\u6240\u9700\u8fd0\u884c\u65f6\u95f4 \uff0c\u4f8b\u5982\u52a0\u6cd5\u64cd\u4f5c + \u9700\u8981 1 ns \uff0c\u4e58\u6cd5\u64cd\u4f5c * \u9700\u8981 10 ns \uff0c\u6253\u5370\u64cd\u4f5c\u9700\u8981 5 ns \u7b49\u3002
  3. \u6839\u636e\u4ee3\u7801 \u7edf\u8ba1\u6240\u6709\u8ba1\u7b97\u64cd\u4f5c\u7684\u6570\u91cf \uff0c\u5e76\u5c06\u6240\u6709\u64cd\u4f5c\u7684\u6267\u884c\u65f6\u95f4\u6c42\u548c\uff0c\u5373\u53ef\u5f97\u5230\u8fd0\u884c\u65f6\u95f4\u3002

\u4f8b\u5982\u4ee5\u4e0b\u4ee3\u7801\uff0c\u8f93\u5165\u6570\u636e\u5927\u5c0f\u4e3a \\(n\\) \uff0c\u6839\u636e\u4ee5\u4e0a\u65b9\u6cd5\uff0c\u53ef\u4ee5\u5f97\u5230\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\u4e3a \\(6n + 12\\) ns \u3002

\\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\] JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nvoid algorithm(int n) {\nint a = 2;  // 1 ns\na = a + 1;  // 1 ns\na = a * 2;  // 10 ns\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) {  // 1 ns \uff0c\u6bcf\u8f6e\u90fd\u8981\u6267\u884c i++\nSystem.out.println(0);     // 5 ns\n}\n}\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nvoid algorithm(int n) {\nint a = 2;  // 1 ns\na = a + 1;  // 1 ns\na = a * 2;  // 10 ns\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) {  // 1 ns \uff0c\u6bcf\u8f6e\u90fd\u8981\u6267\u884c i++\ncout << 0 << endl;         // 5 ns\n}\n}\n
# \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\ndef algorithm(n):\na = 2      # 1 ns\na = a + 1  # 1 ns\na = a * 2  # 10 ns\n# \u5faa\u73af n \u6b21\nfor _ in range(n):  # 1 ns\nprint(0)        # 5 ns\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nfunc algorithm(n int) {\na := 2      // 1 ns\na = a + 1   // 1 ns\na = a * 2   // 10 ns\n// \u5faa\u73af n \u6b21\nfor i := 0; i < n; i++ {    // 1 ns\nfmt.Println(a)          // 5 ns\n}\n}\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nfunction algorithm(n) {\nvar a = 2; // 1 ns\na = a + 1; // 1 ns\na = a * 2; // 10 ns\n// \u5faa\u73af n \u6b21\nfor(let i = 0; i < n; i++) { // 1 ns \uff0c\u6bcf\u8f6e\u90fd\u8981\u6267\u884c i++\nconsole.log(0); // 5 ns\n}\n}\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nfunction algorithm(n: number): void {\nvar a: number = 2; // 1 ns\na = a + 1; // 1 ns\na = a * 2; // 10 ns\n// \u5faa\u73af n \u6b21\nfor(let i = 0; i < n; i++) { // 1 ns \uff0c\u6bcf\u8f6e\u90fd\u8981\u6267\u884c i++\nconsole.log(0); // 5 ns\n}\n}\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nvoid algorithm(int n) {\nint a = 2;  // 1 ns\na = a + 1;  // 1 ns\na = a * 2;  // 10 ns\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) {   // 1 ns \uff0c\u6bcf\u8f6e\u90fd\u8981\u6267\u884c i++\nprintf(\"%d\", 0);            // 5 ns\n}\n}\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nvoid algorithm(int n)\n{\nint a = 2;  // 1 ns\na = a + 1;  // 1 ns\na = a * 2;  // 10 ns\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++)\n{  // 1 ns \uff0c\u6bcf\u8f6e\u90fd\u8981\u6267\u884c i++\nConsole.WriteLine(0);     // 5 ns\n}\n}\n
// \u5728\u67d0\u8fd0\u884c\u5e73\u53f0\u4e0b\nfunc algorithm(_ n: Int) {\nvar a = 2 // 1 ns\na = a + 1 // 1 ns\na = a * 2 // 10 ns\n// \u5faa\u73af n \u6b21\nfor _ in 0 ..< n { // 1 ns\nprint(0) // 5 ns\n}\n}\n
\n

\u4f46\u5b9e\u9645\u4e0a\uff0c \u7edf\u8ba1\u7b97\u6cd5\u7684\u8fd0\u884c\u65f6\u95f4\u65e2\u4e0d\u5408\u7406\u4e5f\u4e0d\u73b0\u5b9e\u3002\u9996\u5148\uff0c\u6211\u4eec\u4e0d\u5e0c\u671b\u9884\u4f30\u65f6\u95f4\u548c\u8fd0\u884c\u5e73\u53f0\u7ed1\u5b9a\uff0c\u6bd5\u7adf\u7b97\u6cd5\u9700\u8981\u8dd1\u5728\u5404\u5f0f\u5404\u6837\u7684\u5e73\u53f0\u4e4b\u4e0a\u3002\u5176\u6b21\uff0c\u6211\u4eec\u5f88\u96be\u83b7\u77e5\u6bcf\u4e00\u79cd\u64cd\u4f5c\u7684\u8fd0\u884c\u65f6\u95f4\uff0c\u8fd9\u4e3a\u9884\u4f30\u8fc7\u7a0b\u5e26\u6765\u4e86\u6781\u5927\u7684\u96be\u5ea6\u3002

"},{"location":"chapter_computational_complexity/time_complexity/#222","title":"2.2.2. \u7edf\u8ba1\u65f6\u95f4\u589e\u957f\u8d8b\u52bf","text":"

\u300c\u65f6\u95f4\u590d\u6742\u5ea6\u5206\u6790\u300d\u91c7\u53d6\u4e86\u4e0d\u540c\u7684\u505a\u6cd5\uff0c\u5176\u7edf\u8ba1\u7684\u4e0d\u662f\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\uff0c\u800c\u662f \u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\u968f\u7740\u6570\u636e\u91cf\u53d8\u5927\u65f6\u7684\u589e\u957f\u8d8b\u52bf \u3002

\u201c\u65f6\u95f4\u589e\u957f\u8d8b\u52bf\u201d\u8fd9\u4e2a\u6982\u5ff5\u6bd4\u8f83\u62bd\u8c61\uff0c\u6211\u4eec\u501f\u52a9\u4e00\u4e2a\u4f8b\u5b50\u6765\u7406\u89e3\u3002\u8bbe\u8f93\u5165\u6570\u636e\u5927\u5c0f\u4e3a \\(n\\) \uff0c\u7ed9\u5b9a\u4e09\u4e2a\u7b97\u6cd5 A , B , C \u3002

  • \u7b97\u6cd5 A \u53ea\u6709 \\(1\\) \u4e2a\u6253\u5370\u64cd\u4f5c\uff0c\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\u4e0d\u968f\u7740 \\(n\\) \u589e\u5927\u800c\u589e\u957f\u3002\u6211\u4eec\u79f0\u6b64\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a\u300c\u5e38\u6570\u9636\u300d\u3002
  • \u7b97\u6cd5 B \u4e2d\u7684\u6253\u5370\u64cd\u4f5c\u9700\u8981\u5faa\u73af \\(n\\) \u6b21\uff0c\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\u968f\u7740 \\(n\\) \u589e\u5927\u6210\u7ebf\u6027\u589e\u957f\u3002\u6b64\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u88ab\u79f0\u4e3a\u300c\u7ebf\u6027\u9636\u300d\u3002
  • \u7b97\u6cd5 C \u4e2d\u7684\u6253\u5370\u64cd\u4f5c\u9700\u8981\u5faa\u73af \\(1000000\\) \u6b21\uff0c\u4f46\u8fd0\u884c\u65f6\u95f4\u4ecd\u4e0e\u8f93\u5165\u6570\u636e\u5927\u5c0f \\(n\\) \u65e0\u5173\u3002\u56e0\u6b64 C \u7684\u65f6\u95f4\u590d\u6742\u5ea6\u548c A \u76f8\u540c\uff0c\u4ecd\u4e3a\u300c\u5e38\u6570\u9636\u300d\u3002
JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_A(int n) {\nSystem.out.println(0);\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nvoid algorithm_B(int n) {\nfor (int i = 0; i < n; i++) {\nSystem.out.println(0);\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_C(int n) {\nfor (int i = 0; i < 1000000; i++) {\nSystem.out.println(0);\n}\n}\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_A(int n) {\ncout << 0 << endl;\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nvoid algorithm_B(int n) {\nfor (int i = 0; i < n; i++) {\ncout << 0 << endl;\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_C(int n) {\nfor (int i = 0; i < 1000000; i++) {\ncout << 0 << endl;\n}\n}\n
# \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\ndef algorithm_A(n):\nprint(0)\n# \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\ndef algorithm_B(n):\nfor _ in range(n):\nprint(0)\n# \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\ndef algorithm_C(n):\nfor _ in range(1000000):\nprint(0)\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunc algorithm_A(n int) {\nfmt.Println(0)\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nfunc algorithm_B(n int) {\nfor i := 0; i < n; i++ {\nfmt.Println(0)\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunc algorithm_C(n int) {\nfor i := 0; i < 1000000; i++ {\nfmt.Println(0)\n}\n}\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunction algorithm_A(n) {\nconsole.log(0);\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nfunction algorithm_B(n) {\nfor (let i = 0; i < n; i++) {\nconsole.log(0);\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunction algorithm_C(n) {\nfor (let i = 0; i < 1000000; i++) {\nconsole.log(0);\n}\n}\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunction algorithm_A(n: number): void {\nconsole.log(0);\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nfunction algorithm_B(n: number): void {\nfor (let i = 0; i < n; i++) {\nconsole.log(0);\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunction algorithm_C(n: number): void {\nfor (let i = 0; i < 1000000; i++) {\nconsole.log(0);\n}\n}\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_A(int n) {\nprintf(\"%d\", 0);\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nvoid algorithm_B(int n) {\nfor (int i = 0; i < n; i++) {\nprintf(\"%d\", 0);\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_C(int n) {\nfor (int i = 0; i < 1000000; i++) {\nprintf(\"%d\", 0);\n}\n}\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_A(int n)\n{\nConsole.WriteLine(0);\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nvoid algorithm_B(int n)\n{\nfor (int i = 0; i < n; i++)\n{\nConsole.WriteLine(0);\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nvoid algorithm_C(int n)\n{\nfor (int i = 0; i < 1000000; i++)\n{\nConsole.WriteLine(0);\n}\n}\n
// \u7b97\u6cd5 A \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunc algorithmA(_ n: Int) {\nprint(0)\n}\n// \u7b97\u6cd5 B \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u7ebf\u6027\u9636\nfunc algorithmB(_ n: Int) {\nfor _ in 0 ..< n {\nprint(0)\n}\n}\n// \u7b97\u6cd5 C \u65f6\u95f4\u590d\u6742\u5ea6\uff1a\u5e38\u6570\u9636\nfunc algorithmC(_ n: Int) {\nfor _ in 0 ..< 1000000 {\nprint(0)\n}\n}\n
\n

Fig. \u7b97\u6cd5 A, B, C \u7684\u65f6\u95f4\u589e\u957f\u8d8b\u52bf

\u76f8\u6bd4\u76f4\u63a5\u7edf\u8ba1\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u5206\u6790\u7684\u505a\u6cd5\u6709\u4ec0\u4e48\u597d\u5904\u5462\uff1f\u4ee5\u53ca\u6709\u4ec0\u4e48\u4e0d\u8db3\uff1f

\u65f6\u95f4\u590d\u6742\u5ea6\u53ef\u4ee5\u6709\u6548\u8bc4\u4f30\u7b97\u6cd5\u6548\u7387\u3002\u7b97\u6cd5 B \u8fd0\u884c\u65f6\u95f4\u7684\u589e\u957f\u662f\u7ebf\u6027\u7684\uff0c\u5728 \\(n > 1\\) \u65f6\u6162\u4e8e\u7b97\u6cd5 A \uff0c\u5728 \\(n > 1000000\\) \u65f6\u6162\u4e8e\u7b97\u6cd5 C \u3002\u5b9e\u8d28\u4e0a\uff0c\u53ea\u8981\u8f93\u5165\u6570\u636e\u5927\u5c0f \\(n\\) \u8db3\u591f\u5927\uff0c\u590d\u6742\u5ea6\u4e3a\u300c\u5e38\u6570\u9636\u300d\u7684\u7b97\u6cd5\u4e00\u5b9a\u4f18\u4e8e\u300c\u7ebf\u6027\u9636\u300d\u7684\u7b97\u6cd5\uff0c\u8fd9\u4e5f\u6b63\u662f\u65f6\u95f4\u589e\u957f\u8d8b\u52bf\u7684\u542b\u4e49\u3002

\u65f6\u95f4\u590d\u6742\u5ea6\u7684\u63a8\u7b97\u65b9\u6cd5\u66f4\u52a0\u7b80\u4fbf\u3002\u5728\u65f6\u95f4\u590d\u6742\u5ea6\u5206\u6790\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u7edf\u8ba1\u300c\u8ba1\u7b97\u64cd\u4f5c\u7684\u8fd0\u884c\u65f6\u95f4\u300d\u7b80\u5316\u4e3a\u7edf\u8ba1\u300c\u8ba1\u7b97\u64cd\u4f5c\u7684\u6570\u91cf\u300d\uff0c\u8fd9\u662f\u56e0\u4e3a\uff0c\u65e0\u8bba\u662f\u8fd0\u884c\u5e73\u53f0\u8fd8\u662f\u8ba1\u7b97\u64cd\u4f5c\u7c7b\u578b\uff0c\u90fd\u4e0e\u7b97\u6cd5\u8fd0\u884c\u65f6\u95f4\u7684\u589e\u957f\u8d8b\u52bf\u65e0\u5173\u3002\u56e0\u800c\uff0c\u6211\u4eec\u53ef\u4ee5\u7b80\u5355\u5730\u5c06\u6240\u6709\u8ba1\u7b97\u64cd\u4f5c\u7684\u6267\u884c\u65f6\u95f4\u7edf\u4e00\u770b\u4f5c\u662f\u76f8\u540c\u7684\u201c\u5355\u4f4d\u65f6\u95f4\u201d\uff0c\u8fd9\u6837\u7684\u7b80\u5316\u505a\u6cd5\u5927\u5927\u964d\u4f4e\u4e86\u4f30\u7b97\u96be\u5ea6\u3002

\u65f6\u95f4\u590d\u6742\u5ea6\u4e5f\u5b58\u5728\u4e00\u5b9a\u7684\u5c40\u9650\u6027\u3002\u6bd4\u5982\uff0c\u867d\u7136\u7b97\u6cd5 A \u548c C \u7684\u65f6\u95f4\u590d\u6742\u5ea6\u76f8\u540c\uff0c\u4f46\u662f\u5b9e\u9645\u7684\u8fd0\u884c\u65f6\u95f4\u6709\u975e\u5e38\u5927\u7684\u5dee\u522b\u3002\u518d\u6bd4\u5982\uff0c\u867d\u7136\u7b97\u6cd5 B \u6bd4 C \u7684\u65f6\u95f4\u590d\u6742\u5ea6\u8981\u66f4\u9ad8\uff0c\u4f46\u5728\u8f93\u5165\u6570\u636e\u5927\u5c0f \\(n\\) \u6bd4\u8f83\u5c0f\u65f6\uff0c\u7b97\u6cd5 B \u662f\u8981\u660e\u663e\u4f18\u4e8e\u7b97\u6cd5 C \u7684\u3002\u5bf9\u4e8e\u4ee5\u4e0a\u60c5\u51b5\uff0c\u6211\u4eec\u5f88\u96be\u4ec5\u51ed\u65f6\u95f4\u590d\u6742\u5ea6\u6765\u5224\u5b9a\u7b97\u6cd5\u6548\u7387\u9ad8\u4f4e\u3002\u7136\u800c\uff0c\u5373\u4f7f\u5b58\u5728\u8fd9\u4e9b\u95ee\u9898\uff0c\u8ba1\u7b97\u590d\u6742\u5ea6\u4ecd\u7136\u662f\u8bc4\u5224\u7b97\u6cd5\u6548\u7387\u7684\u6700\u6709\u6548\u4e14\u5e38\u7528\u7684\u65b9\u6cd5\u3002

"},{"location":"chapter_computational_complexity/time_complexity/#223","title":"2.2.3. \u51fd\u6570\u6e10\u8fd1\u4e0a\u754c","text":"

\u8bbe\u7b97\u6cd5\u300c\u8ba1\u7b97\u64cd\u4f5c\u6570\u91cf\u300d\u4e3a \\(T(n)\\) \uff0c\u5176\u662f\u4e00\u4e2a\u5173\u4e8e\u8f93\u5165\u6570\u636e\u5927\u5c0f \\(n\\) \u7684\u51fd\u6570\u3002\u4f8b\u5982\uff0c\u4ee5\u4e0b\u7b97\u6cd5\u7684\u64cd\u4f5c\u6570\u91cf\u4e3a

\\[ T(n) = 3 + 2n \\] JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
void algorithm(int n) {\nint a = 1;  // +1\na = a + 1;  // +1\na = a * 2;  // +1\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) { // +1\uff08\u6bcf\u8f6e\u90fd\u6267\u884c i ++\uff09\nSystem.out.println(0);    // +1\n}\n}\n
void algorithm(int n) {\nint a = 1;  // +1\na = a + 1;  // +1\na = a * 2;  // +1\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) { // +1\uff08\u6bcf\u8f6e\u90fd\u6267\u884c i ++\uff09\ncout << 0 << endl;    // +1\n}\n}\n
def algorithm(n):\na = 1  # +1\na = a + 1  # +1\na = a * 2  # +1\n# \u5faa\u73af n \u6b21\nfor i in range(n):  # +1\nprint(0)        # +1\n
func algorithm(n int) {\na := 1      // +1\na = a + 1   // +1\na = a * 2   // +1\n// \u5faa\u73af n \u6b21\nfor i := 0; i < n; i++ {   // +1\nfmt.Println(a)         // +1\n}\n}\n
function algorithm(n){\nvar a = 1; // +1\na += 1; // +1\na *= 2; // +1\n// \u5faa\u73af n \u6b21\nfor(let i = 0; i < n; i++){ // +1\uff08\u6bcf\u8f6e\u90fd\u6267\u884c i ++\uff09\nconsole.log(0); // +1\n}\n}\n
function algorithm(n: number): void{\nvar a: number = 1; // +1\na += 1; // +1\na *= 2; // +1\n// \u5faa\u73af n \u6b21\nfor(let i = 0; i < n; i++){ // +1\uff08\u6bcf\u8f6e\u90fd\u6267\u884c i ++\uff09\nconsole.log(0); // +1\n}\n}\n
void algorithm(int n) {\nint a = 1;  // +1\na = a + 1;  // +1\na = a * 2;  // +1\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) {   // +1\uff08\u6bcf\u8f6e\u90fd\u6267\u884c i ++\uff09\nprintf(\"%d\", 0);            // +1\n}\n}  
void algorithm(int n) {\nint a = 1;  // +1\na = a + 1;  // +1\na = a * 2;  // +1\n// \u5faa\u73af n \u6b21\nfor (int i = 0; i < n; i++) { // +1\uff08\u6bcf\u8f6e\u90fd\u6267\u884c i ++\uff09\nConsole.WriteLine(0);     // +1\n}\n}\n
func algorithm(n: Int) {\nvar a = 1 // +1\na = a + 1 // +1\na = a * 2 // +1\n// \u5faa\u73af n \u6b21\nfor _ in 0 ..< n { // +1\nprint(0) // +1\n}\n}\n
\n

\\(T(n)\\) \u662f\u4e2a\u4e00\u6b21\u51fd\u6570\uff0c\u8bf4\u660e\u65f6\u95f4\u589e\u957f\u8d8b\u52bf\u662f\u7ebf\u6027\u7684\uff0c\u56e0\u6b64\u6613\u5f97\u65f6\u95f4\u590d\u6742\u5ea6\u662f\u7ebf\u6027\u9636\u3002

\u6211\u4eec\u5c06\u7ebf\u6027\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u8bb0\u4e3a \\(O(n)\\) \uff0c\u8fd9\u4e2a\u6570\u5b66\u7b26\u53f7\u88ab\u79f0\u4e3a\u300c\u5927 \\(O\\) \u8bb0\u53f7 Big-\\(O\\) Notation\u300d\uff0c\u4ee3\u8868\u51fd\u6570 \\(T(n)\\) \u7684\u300c\u6e10\u8fd1\u4e0a\u754c asymptotic upper bound\u300d\u3002

\u6211\u4eec\u8981\u63a8\u7b97\u65f6\u95f4\u590d\u6742\u5ea6\uff0c\u672c\u8d28\u4e0a\u662f\u5728\u8ba1\u7b97\u300c\u64cd\u4f5c\u6570\u91cf\u51fd\u6570 \\(T(n)\\) \u300d\u7684\u6e10\u8fd1\u4e0a\u754c\u3002\u4e0b\u9762\u6211\u4eec\u5148\u6765\u770b\u770b\u51fd\u6570\u6e10\u8fd1\u4e0a\u754c\u7684\u6570\u5b66\u5b9a\u4e49\u3002

\u51fd\u6570\u6e10\u8fd1\u4e0a\u754c

\u82e5\u5b58\u5728\u6b63\u5b9e\u6570 \\(c\\) \u548c\u5b9e\u6570 \\(n_0\\) \uff0c\u4f7f\u5f97\u5bf9\u4e8e\u6240\u6709\u7684 \\(n > n_0\\) \uff0c\u5747\u6709 $$ T(n) \\leq c \\cdot f(n) $$ \u5219\u53ef\u8ba4\u4e3a \\(f(n)\\) \u7ed9\u51fa\u4e86 \\(T(n)\\) \u7684\u4e00\u4e2a\u6e10\u8fd1\u4e0a\u754c\uff0c\u8bb0\u4e3a $$ T(n) = O(f(n)) $$

Fig. \u51fd\u6570\u7684\u6e10\u8fd1\u4e0a\u754c

\u672c\u8d28\u4e0a\u770b\uff0c\u8ba1\u7b97\u6e10\u8fd1\u4e0a\u754c\u5c31\u662f\u5728\u627e\u4e00\u4e2a\u51fd\u6570 \\(f(n)\\) \uff0c\u4f7f\u5f97\u5728 \\(n\\) \u8d8b\u5411\u4e8e\u65e0\u7a77\u5927\u65f6\uff0c\\(T(n)\\) \u548c \\(f(n)\\) \u5904\u4e8e\u76f8\u540c\u7684\u589e\u957f\u7ea7\u522b\uff08\u4ec5\u76f8\u5dee\u4e00\u4e2a\u5e38\u6570\u9879 \\(c\\) \u7684\u500d\u6570\uff09\u3002

Tip

\u6e10\u8fd1\u4e0a\u754c\u7684\u6570\u5b66\u5473\u513f\u6709\u70b9\u91cd\uff0c\u5982\u679c\u4f60\u611f\u89c9\u6ca1\u6709\u5b8c\u5168\u7406\u89e3\uff0c\u65e0\u9700\u62c5\u5fc3\uff0c\u56e0\u4e3a\u5728\u5b9e\u9645\u4f7f\u7528\u4e2d\u6211\u4eec\u53ea\u9700\u8981\u4f1a\u63a8\u7b97\u5373\u53ef\uff0c\u6570\u5b66\u610f\u4e49\u53ef\u4ee5\u6162\u6162\u9886\u609f\u3002

"},{"location":"chapter_computational_complexity/time_complexity/#224","title":"2.2.4. \u63a8\u7b97\u65b9\u6cd5","text":"

\u63a8\u7b97\u51fa \\(f(n)\\) \u540e\uff0c\u6211\u4eec\u5c31\u5f97\u5230\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(f(n))\\) \u3002\u90a3\u4e48\uff0c\u5982\u4f55\u6765\u786e\u5b9a\u6e10\u8fd1\u4e0a\u754c \\(f(n)\\) \u5462\uff1f\u603b\u4f53\u5206\u4e3a\u4e24\u6b65\uff0c\u9996\u5148\u300c\u7edf\u8ba1\u64cd\u4f5c\u6570\u91cf\u300d\uff0c\u7136\u540e\u300c\u5224\u65ad\u6e10\u8fd1\u4e0a\u754c\u300d\u3002

"},{"location":"chapter_computational_complexity/time_complexity/#1","title":"1) \u7edf\u8ba1\u64cd\u4f5c\u6570\u91cf","text":"

\u5bf9\u7740\u4ee3\u7801\uff0c\u4ece\u4e0a\u5230\u4e0b\u4e00\u884c\u4e00\u884c\u5730\u8ba1\u6570\u5373\u53ef\u3002\u7136\u800c\uff0c\u7531\u4e8e\u4e0a\u8ff0 \\(c \\cdot f(n)\\) \u4e2d\u7684\u5e38\u6570\u9879 \\(c\\) \u53ef\u4ee5\u53d6\u4efb\u610f\u5927\u5c0f\uff0c\u56e0\u6b64\u64cd\u4f5c\u6570\u91cf \\(T(n)\\) \u4e2d\u7684\u5404\u79cd\u7cfb\u6570\u3001\u5e38\u6570\u9879\u90fd\u53ef\u4ee5\u88ab\u5ffd\u7565\u3002\u6839\u636e\u6b64\u539f\u5219\uff0c\u53ef\u4ee5\u603b\u7ed3\u51fa\u4ee5\u4e0b\u8ba1\u6570\u5077\u61d2\u6280\u5de7\uff1a

  1. \u8df3\u8fc7\u6570\u91cf\u4e0e \\(n\\) \u65e0\u5173\u7684\u64cd\u4f5c\u3002\u56e0\u4e3a\u4ed6\u4eec\u90fd\u662f \\(T(n)\\) \u4e2d\u7684\u5e38\u6570\u9879\uff0c\u5bf9\u65f6\u95f4\u590d\u6742\u5ea6\u4e0d\u4ea7\u751f\u5f71\u54cd\u3002
  2. \u7701\u7565\u6240\u6709\u7cfb\u6570\u3002\u4f8b\u5982\uff0c\u5faa\u73af \\(2n\\) \u6b21\u3001\\(5n + 1\\) \u6b21\u3001\u2026\u2026\uff0c\u90fd\u53ef\u4ee5\u5316\u7b80\u8bb0\u4e3a \\(n\\) \u6b21\uff0c\u56e0\u4e3a \\(n\\) \u524d\u9762\u7684\u7cfb\u6570\u5bf9\u65f6\u95f4\u590d\u6742\u5ea6\u4e5f\u4e0d\u4ea7\u751f\u5f71\u54cd\u3002
  3. \u5faa\u73af\u5d4c\u5957\u65f6\u4f7f\u7528\u4e58\u6cd5\u3002\u603b\u64cd\u4f5c\u6570\u91cf\u7b49\u4e8e\u5916\u5c42\u5faa\u73af\u548c\u5185\u5c42\u5faa\u73af\u64cd\u4f5c\u6570\u91cf\u4e4b\u79ef\uff0c\u6bcf\u4e00\u5c42\u5faa\u73af\u4f9d\u7136\u53ef\u4ee5\u5206\u522b\u5957\u7528\u4e0a\u8ff0 1. \u548c 2. \u6280\u5de7\u3002

\u6839\u636e\u4ee5\u4e0b\u793a\u4f8b\uff0c\u4f7f\u7528\u4e0a\u8ff0\u6280\u5de7\u524d\u3001\u540e\u7684\u7edf\u8ba1\u7ed3\u679c\u5206\u522b\u4e3a

\\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{\u5b8c\u6574\u7edf\u8ba1 (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{\u5077\u61d2\u7edf\u8ba1 (o.O)} \\end{aligned} \\]

\u6700\u7ec8\uff0c\u4e24\u8005\u90fd\u80fd\u63a8\u51fa\u76f8\u540c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u7ed3\u679c\uff0c\u5373 \\(O(n^2)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
void algorithm(int n) {\nint a = 1;  // +0\uff08\u6280\u5de7 1\uff09\na = a + n;  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor (int i = 0; i < 5 * n + 1; i++) {\nSystem.out.println(0);\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor (int i = 0; i < 2 * n; i++) {\nfor (int j = 0; j < n + 1; j++) {\nSystem.out.println(0);\n}\n}\n}\n
void algorithm(int n) {\nint a = 1;  // +0\uff08\u6280\u5de7 1\uff09\na = a + n;  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor (int i = 0; i < 5 * n + 1; i++) {\ncout << 0 << endl;\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor (int i = 0; i < 2 * n; i++) {\nfor (int j = 0; j < n + 1; j++) {\ncout << 0 << endl;\n}\n}\n}\n
def algorithm(n):\na = 1      # +0\uff08\u6280\u5de7 1\uff09\na = a + n  # +0\uff08\u6280\u5de7 1\uff09\n# +n\uff08\u6280\u5de7 2\uff09\nfor i in range(5 * n + 1):\nprint(0)\n# +n*n\uff08\u6280\u5de7 3\uff09\nfor i in range(2 * n):\nfor j in range(n + 1):\nprint(0)\n
func algorithm(n int) {\na := 1      // +0\uff08\u6280\u5de7 1\uff09\na = a + n  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor i := 0; i < 5 * n + 1; i++ {\nfmt.Println(0)\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor i := 0; i < 2 * n; i++ {\nfor j := 0; j < n + 1; j++ {\nfmt.Println(0)\n}\n}\n}\n
function algorithm(n) {\nlet a = 1;  // +0\uff08\u6280\u5de7 1\uff09\na = a + n;  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor (let i = 0; i < 5 * n + 1; i++) {\nconsole.log(0);\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor (let i = 0; i < 2 * n; i++) {\nfor (let j = 0; j < n + 1; j++) {\nconsole.log(0);\n}\n}\n}\n
function algorithm(n: number): void {\nlet a = 1;  // +0\uff08\u6280\u5de7 1\uff09\na = a + n;  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor (let i = 0; i < 5 * n + 1; i++) {\nconsole.log(0);\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor (let i = 0; i < 2 * n; i++) {\nfor (let j = 0; j < n + 1; j++) {\nconsole.log(0);\n}\n}\n}\n
void algorithm(int n) {\nint a = 1;  // +0\uff08\u6280\u5de7 1\uff09\na = a + n;  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor (int i = 0; i < 5 * n + 1; i++) {\nprintf(\"%d\", 0);\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor (int i = 0; i < 2 * n; i++) {\nfor (int j = 0; j < n + 1; j++) {\nprintf(\"%d\", 0);\n}\n}\n}\n
void algorithm(int n)\n{\nint a = 1;  // +0\uff08\u6280\u5de7 1\uff09\na = a + n;  // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor (int i = 0; i < 5 * n + 1; i++)\n{\nConsole.WriteLine(0);\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor (int i = 0; i < 2 * n; i++)\n{\nfor (int j = 0; j < n + 1; j++)\n{\nConsole.WriteLine(0);\n}\n}\n}\n
func algorithm(n: Int) {\nvar a = 1 // +0\uff08\u6280\u5de7 1\uff09\na = a + n // +0\uff08\u6280\u5de7 1\uff09\n// +n\uff08\u6280\u5de7 2\uff09\nfor _ in 0 ..< (5 * n + 1) {\nprint(0)\n}\n// +n*n\uff08\u6280\u5de7 3\uff09\nfor _ in 0 ..< (2 * n) {\nfor _ in 0 ..< (n + 1) {\nprint(0)\n}\n}\n}\n
\n
"},{"location":"chapter_computational_complexity/time_complexity/#2","title":"2) \u5224\u65ad\u6e10\u8fd1\u4e0a\u754c","text":"

\u65f6\u95f4\u590d\u6742\u5ea6\u7531\u591a\u9879\u5f0f \\(T(n)\\) \u4e2d\u6700\u9ad8\u9636\u7684\u9879\u6765\u51b3\u5b9a\u3002\u8fd9\u662f\u56e0\u4e3a\u5728 \\(n\\) \u8d8b\u4e8e\u65e0\u7a77\u5927\u65f6\uff0c\u6700\u9ad8\u9636\u7684\u9879\u5c06\u5904\u4e8e\u4e3b\u5bfc\u4f5c\u7528\uff0c\u5176\u5b83\u9879\u7684\u5f71\u54cd\u90fd\u53ef\u4ee5\u88ab\u5ffd\u7565\u3002

\u4ee5\u4e0b\u8868\u683c\u7ed9\u51fa\u4e86\u4e00\u4e9b\u4f8b\u5b50\uff0c\u5176\u4e2d\u6709\u4e00\u4e9b\u5938\u5f20\u7684\u503c\uff0c\u662f\u60f3\u8981\u5411\u5927\u5bb6\u5f3a\u8c03 \u7cfb\u6570\u65e0\u6cd5\u64bc\u52a8\u9636\u6570 \u8fd9\u4e00\u7ed3\u8bba\u3002\u5728 \\(n\\) \u8d8b\u4e8e\u65e0\u7a77\u5927\u65f6\uff0c\u8fd9\u4e9b\u5e38\u6570\u90fd\u662f\u201c\u6d6e\u4e91\u201d\u3002

\u64cd\u4f5c\u6570\u91cf \\(T(n)\\) \u65f6\u95f4\u590d\u6742\u5ea6 \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)"},{"location":"chapter_computational_complexity/time_complexity/#225","title":"2.2.5. \u5e38\u89c1\u7c7b\u578b","text":"

\u8bbe\u8f93\u5165\u6570\u636e\u5927\u5c0f\u4e3a \\(n\\) \uff0c\u5e38\u89c1\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u7c7b\u578b\u6709\uff08\u4ece\u4f4e\u5230\u9ad8\u6392\u5217\uff09

\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline \\text{\u5e38\u6570\u9636} < \\text{\u5bf9\u6570\u9636} < \\text{\u7ebf\u6027\u9636} < \\text{\u7ebf\u6027\u5bf9\u6570\u9636} < \\text{\u5e73\u65b9\u9636} < \\text{\u6307\u6570\u9636} < \\text{\u9636\u4e58\u9636} \\end{aligned} \\]

Fig. \u65f6\u95f4\u590d\u6742\u5ea6\u7684\u5e38\u89c1\u7c7b\u578b

Tip

\u90e8\u5206\u793a\u4f8b\u4ee3\u7801\u9700\u8981\u4e00\u4e9b\u524d\u7f6e\u77e5\u8bc6\uff0c\u5305\u62ec\u6570\u7ec4\u3001\u9012\u5f52\u7b97\u6cd5\u7b49\u3002\u5982\u679c\u9047\u5230\u770b\u4e0d\u61c2\u7684\u5730\u65b9\u65e0\u9700\u62c5\u5fc3\uff0c\u53ef\u4ee5\u5728\u5b66\u4e60\u5b8c\u540e\u9762\u7ae0\u8282\u540e\u518d\u6765\u590d\u4e60\uff0c\u73b0\u9636\u6bb5\u5148\u805a\u7126\u5728\u7406\u89e3\u65f6\u95f4\u590d\u6742\u5ea6\u542b\u4e49\u548c\u63a8\u7b97\u65b9\u6cd5\u4e0a\u3002

"},{"location":"chapter_computational_complexity/time_complexity/#o1","title":"\u5e38\u6570\u9636 \\(O(1)\\)","text":"

\u5e38\u6570\u9636\u7684\u64cd\u4f5c\u6570\u91cf\u4e0e\u8f93\u5165\u6570\u636e\u5927\u5c0f \\(n\\) \u65e0\u5173\uff0c\u5373\u4e0d\u968f\u7740 \\(n\\) \u7684\u53d8\u5316\u800c\u53d8\u5316\u3002

\u5bf9\u4e8e\u4ee5\u4e0b\u7b97\u6cd5\uff0c\u65e0\u8bba\u64cd\u4f5c\u6570\u91cf size \u6709\u591a\u5927\uff0c\u53ea\u8981\u4e0e\u6570\u636e\u5927\u5c0f \\(n\\) \u65e0\u5173\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u5c31\u4ecd\u4e3a \\(O(1)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u5e38\u6570\u9636 */\nint constant(int n) {\nint count = 0;\nint size = 100000;\nfor (int i = 0; i < size; i++)\ncount++;\nreturn count;\n}\n
time_complexity.cpp
/* \u5e38\u6570\u9636 */\nint constant(int n) {\nint count = 0;\nint size = 100000;\nfor (int i = 0; i < size; i++)\ncount++;\nreturn count;\n}\n
time_complexity.py
\"\"\" \u5e38\u6570\u9636 \"\"\"\ndef constant(n):\ncount = 0\nsize = 100000\nfor _ in range(size):\ncount += 1\nreturn count\n
time_complexity.go
/* \u5e38\u6570\u9636 */\nfunc constant(n int) int {\ncount := 0\nsize := 100000\nfor i := 0; i < size; i++ {\ncount ++\n}\nreturn count\n}\n
time_complexity.js
/* \u5e38\u6570\u9636 */\nfunction constant(n) {\nlet count = 0;\nconst size = 100000;\nfor (let i = 0; i < size; i++) count++;\nreturn count;\n}\n
time_complexity.ts
/* \u5e38\u6570\u9636 */\nfunction constant(n: number): number {\nlet count = 0;\nconst size = 100000;\nfor (let i = 0; i < size; i++) count++;\nreturn count;\n}\n
time_complexity.c
/* \u5e38\u6570\u9636 */\nint constant(int n) {\nint count = 0;\nint size = 100000;\nint i = 0;\nfor (int i = 0; i < size; i++) {\ncount ++;\n}\nreturn count;\n}\n
time_complexity.cs
/* \u5e38\u6570\u9636 */\nint constant(int n)\n{\nint count = 0;\nint size = 100000;\nfor (int i = 0; i < size; i++)\ncount++;\nreturn count;\n}\n
time_complexity.swift
/* \u5e38\u6570\u9636 */\nfunc constant(n: Int) -> Int {\nvar count = 0\nlet size = 100000\nfor _ in 0 ..< size {\ncount += 1\n}\nreturn count\n}\n
time_complexity.zig
// \u5e38\u6570\u9636\nfn constant(n: i32) i32 {\n_ = n;\nvar count: i32 = 0;\nconst size: i32 = 100_000;\nvar i: i32 = 0;\nwhile(i<size) : (i += 1) {\ncount += 1;\n}\nreturn count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#on","title":"\u7ebf\u6027\u9636 \\(O(n)\\)","text":"

\u7ebf\u6027\u9636\u7684\u64cd\u4f5c\u6570\u91cf\u76f8\u5bf9\u8f93\u5165\u6570\u636e\u5927\u5c0f\u6210\u7ebf\u6027\u7ea7\u522b\u589e\u957f\u3002\u7ebf\u6027\u9636\u5e38\u51fa\u73b0\u4e8e\u5355\u5c42\u5faa\u73af\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u7ebf\u6027\u9636 */\nint linear(int n) {\nint count = 0;\nfor (int i = 0; i < n; i++)\ncount++;\nreturn count;\n}\n
time_complexity.cpp
/* \u7ebf\u6027\u9636 */\nint linear(int n) {\nint count = 0;\nfor (int i = 0; i < n; i++)\ncount++;\nreturn count;\n}\n
time_complexity.py
\"\"\" \u7ebf\u6027\u9636 \"\"\"\ndef linear(n):\ncount = 0\nfor _ in range(n):\ncount += 1\nreturn count\n
time_complexity.go
/* \u7ebf\u6027\u9636 */\nfunc linear(n int) int {\ncount := 0\nfor i := 0; i < n; i++ {\ncount++\n}\nreturn count\n}\n
time_complexity.js
/* \u7ebf\u6027\u9636 */\nfunction linear(n) {\nlet count = 0;\nfor (let i = 0; i < n; i++) count++;\nreturn count;\n}\n
time_complexity.ts
/* \u7ebf\u6027\u9636 */\nfunction linear(n: number): number {\nlet count = 0;\nfor (let i = 0; i < n; i++) count++;\nreturn count;\n}\n
time_complexity.c
/* \u7ebf\u6027\u9636 */\nint linear(int n) {\nint count = 0;\nfor (int i = 0; i < n; i++) {\ncount ++;\n}\nreturn count;\n}\n
time_complexity.cs
/* \u7ebf\u6027\u9636 */\nint linear(int n)\n{\nint count = 0;\nfor (int i = 0; i < n; i++)\ncount++;\nreturn count;\n}\n
time_complexity.swift
/* \u7ebf\u6027\u9636 */\nfunc linear(n: Int) -> Int {\nvar count = 0\nfor _ in 0 ..< n {\ncount += 1\n}\nreturn count\n}\n
time_complexity.zig
// \u7ebf\u6027\u9636\nfn linear(n: i32) i32 {\nvar count: i32 = 0;\nvar i: i32 = 0;\nwhile (i < n) : (i += 1) {\ncount += 1;\n}\nreturn count;\n}\n

\u300c\u904d\u5386\u6570\u7ec4\u300d\u548c\u300c\u904d\u5386\u94fe\u8868\u300d\u7b49\u64cd\u4f5c\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u4e3a \\(O(n)\\) \uff0c\u5176\u4e2d \\(n\\) \u4e3a\u6570\u7ec4\u6216\u94fe\u8868\u7684\u957f\u5ea6\u3002

Tip

\u6570\u636e\u5927\u5c0f \\(n\\) \u662f\u6839\u636e\u8f93\u5165\u6570\u636e\u7684\u7c7b\u578b\u6765\u786e\u5b9a\u7684\u3002\u6bd4\u5982\uff0c\u5728\u4e0a\u8ff0\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u76f4\u63a5\u5c06 \\(n\\) \u770b\u4f5c\u8f93\u5165\u6570\u636e\u5927\u5c0f\uff1b\u4ee5\u4e0b\u904d\u5386\u6570\u7ec4\u793a\u4f8b\u4e2d\uff0c\u6570\u636e\u5927\u5c0f \\(n\\) \u4e3a\u6570\u7ec4\u7684\u957f\u5ea6\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int[] nums) {\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor (int num : nums) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.cpp
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(vector<int>& nums) {\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor (int num : nums) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.py
\"\"\" \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09\"\"\"\ndef array_traversal(nums):\ncount = 0\n# \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor num in nums:\ncount += 1\nreturn count\n
time_complexity.go
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunc arrayTraversal(nums []int) int {\ncount := 0\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor range nums {\ncount++\n}\nreturn count\n}\n
time_complexity.js
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunction arrayTraversal(nums) {\nlet count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor (let i = 0; i < nums.length; i++) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.ts
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunction arrayTraversal(nums: number[]): number {\nlet count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor (let i = 0; i < nums.length; i++) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.c
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int *nums, int n) {\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor (int i = 0; i < n; i++) {\ncount ++;\n}\nreturn count;\n}\n
time_complexity.cs
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nint arrayTraversal(int[] nums)\n{\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nforeach(int num in nums)\n{\ncount++;\n}\nreturn count;\n}\n
time_complexity.swift
/* \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09 */\nfunc arrayTraversal(nums: [Int]) -> Int {\nvar count = 0\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor _ in nums {\ncount += 1\n}\nreturn count\n}\n
time_complexity.zig
// \u7ebf\u6027\u9636\uff08\u904d\u5386\u6570\u7ec4\uff09\nfn arrayTraversal(nums: []i32) i32 {\nvar count: i32 = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u6b63\u6bd4\nfor (nums) |_| {\ncount += 1;\n}\nreturn count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#on2","title":"\u5e73\u65b9\u9636 \\(O(n^2)\\)","text":"

\u5e73\u65b9\u9636\u7684\u64cd\u4f5c\u6570\u91cf\u76f8\u5bf9\u8f93\u5165\u6570\u636e\u5927\u5c0f\u6210\u5e73\u65b9\u7ea7\u522b\u589e\u957f\u3002\u5e73\u65b9\u9636\u5e38\u51fa\u73b0\u4e8e\u5d4c\u5957\u5faa\u73af\uff0c\u5916\u5c42\u5faa\u73af\u548c\u5185\u5c42\u5faa\u73af\u90fd\u4e3a \\(O(n)\\) \uff0c\u603b\u4f53\u4e3a \\(O(n^2)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor (int i = 0; i < n; i++) {\nfor (int j = 0; j < n; j++) {\ncount++;\n}\n}\nreturn count;\n}\n
time_complexity.cpp
/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor (int i = 0; i < n; i++) {\nfor (int j = 0; j < n; j++) {\ncount++;\n}\n}\nreturn count;\n}\n
time_complexity.py
\"\"\" \u5e73\u65b9\u9636 \"\"\"\ndef quadratic(n):\ncount = 0\n# \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor i in range(n):\nfor j in range(n):\ncount += 1\nreturn count\n
time_complexity.go
/* \u5e73\u65b9\u9636 */\nfunc quadratic(n int) int {\ncount := 0\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor i := 0; i < n; i++ {\nfor j := 0; j < n; j++ {\ncount++\n}\n}\nreturn count\n}\n
time_complexity.js
/* \u5e73\u65b9\u9636 */\nfunction quadratic(n) {\nlet count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor (let i = 0; i < n; i++) {\nfor (let j = 0; j < n; j++) {\ncount++;\n}\n}\nreturn count;\n}\n
time_complexity.ts
/* \u5e73\u65b9\u9636 */\nfunction quadratic(n: number): number {\nlet count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor (let i = 0; i < n; i++) {\nfor (let j = 0; j < n; j++) {\ncount++;\n}\n}\nreturn count;\n}\n
time_complexity.c
/* \u5e73\u65b9\u9636 */\nint quadratic(int n) {\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor (int i = 0; i < n; i++) {\nfor (int j = 0; j < n; j++) {\ncount ++;\n}\n}\nreturn count;\n}\n
time_complexity.cs
/* \u5e73\u65b9\u9636 */\nint quadratic(int n)\n{\nint count = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor (int i = 0; i < n; i++)\n{\nfor (int j = 0; j < n; j++)\n{\ncount++;\n}\n}\nreturn count;\n}\n
time_complexity.swift
/* \u5e73\u65b9\u9636 */\nfunc quadratic(n: Int) -> Int {\nvar count = 0\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nfor _ in 0 ..< n {\nfor _ in 0 ..< n {\ncount += 1\n}\n}\nreturn count\n}\n
time_complexity.zig
// \u5e73\u65b9\u9636\nfn quadratic(n: i32) i32 {\nvar count: i32 = 0;\nvar i: i32 = 0;\n// \u5faa\u73af\u6b21\u6570\u4e0e\u6570\u7ec4\u957f\u5ea6\u6210\u5e73\u65b9\u5173\u7cfb\nwhile (i < n) : (i += 1) {\nvar j: i32 = 0;\nwhile (j < n) : (j += 1) {\ncount += 1;\n}\n}\nreturn count;\n}   

Fig. \u5e38\u6570\u9636\u3001\u7ebf\u6027\u9636\u3001\u5e73\u65b9\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6

\u4ee5\u300c\u5192\u6ce1\u6392\u5e8f\u300d\u4e3a\u4f8b\uff0c\u5916\u5c42\u5faa\u73af \\(n - 1\\) \u6b21\uff0c\u5185\u5c42\u5faa\u73af \\(n-1, n-2, \\cdots, 2, 1\\) \u6b21\uff0c\u5e73\u5747\u4e3a \\(\\frac{n}{2}\\) \u6b21\uff0c\u56e0\u6b64\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n^2)\\) \u3002

\\[ O((n - 1) \\frac{n}{2}) = O(n^2) \\] JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int[] nums) {\nint count = 0;  // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.length - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3;  // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
time_complexity.cpp
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(vector<int>& nums) {\nint count = 0;  // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.size() - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3;  // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
time_complexity.py
\"\"\" \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09\"\"\"\ndef bubble_sort(nums):\ncount = 0  # \u8ba1\u6570\u5668\n# \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i in range(len(nums) - 1, 0, -1):\n# \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j in range(i):\nif nums[j] > nums[j + 1]:\n# \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\ntmp = nums[j]\nnums[j] = nums[j + 1]\nnums[j + 1] = tmp\ncount += 3  # \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\nreturn count\n
time_complexity.go
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunc bubbleSort(nums []int) int {\ncount := 0 // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i := len(nums) - 1; i > 0; i-- {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j := 0; j < i; j++ {\nif nums[j] > nums[j+1] {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\ntmp := nums[j]\nnums[j] = nums[j+1]\nnums[j+1] = tmp\ncount += 3 // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count\n}\n
time_complexity.js
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunction bubbleSort(nums) {\nlet count = 0; // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (let i = nums.length - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (let j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
time_complexity.ts
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunction bubbleSort(nums: number[]): number {\nlet count = 0; // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (let i = nums.length - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (let j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3; // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
time_complexity.c
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int *nums, int n) {\nint count = 0;  // \u8ba1\u6570\u5668 \n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = n - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums [j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3;  // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
time_complexity.cs
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nint bubbleSort(int[] nums)\n{\nint count = 0;  // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.Length - 1; i > 0; i--)\n{\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++)\n{\nif (nums[j] > nums[j + 1])\n{\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3;  // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
time_complexity.swift
/* \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09 */\nfunc bubbleSort(nums: inout [Int]) -> Int {\nvar count = 0 // \u8ba1\u6570\u5668\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j in 0 ..< i {\nif nums[j] > nums[j + 1] {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j]\nnums[j] = nums[j + 1]\nnums[j + 1] = tmp\ncount += 3 // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count\n}\n
time_complexity.zig
// \u5e73\u65b9\u9636\uff08\u5192\u6ce1\u6392\u5e8f\uff09\nfn bubbleSort(nums: []i32) i32 {\nvar count: i32 = 0;  // \u8ba1\u6570\u5668 \n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nvar i: i32 = @intCast(i32, nums.len ) - 1;\nwhile (i > 0) : (i -= 1) {\nvar j: usize = 0;\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nwhile (j < i) : (j += 1) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nvar tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\ncount += 3;  // \u5143\u7d20\u4ea4\u6362\u5305\u542b 3 \u4e2a\u5355\u5143\u64cd\u4f5c\n}\n}\n}\nreturn count;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#o2n","title":"\u6307\u6570\u9636 \\(O(2^n)\\)","text":"

Note

\u751f\u7269\u5b66\u79d1\u4e2d\u7684\u201c\u7ec6\u80de\u5206\u88c2\u201d\u5373\u662f\u6307\u6570\u9636\u589e\u957f\uff1a\u521d\u59cb\u72b6\u6001\u4e3a \\(1\\) \u4e2a\u7ec6\u80de\uff0c\u5206\u88c2\u4e00\u8f6e\u540e\u4e3a \\(2\\) \u4e2a\uff0c\u5206\u88c2\u4e24\u8f6e\u540e\u4e3a \\(4\\) \u4e2a\uff0c\u2026\u2026\uff0c\u5206\u88c2 \\(n\\) \u8f6e\u540e\u6709 \\(2^n\\) \u4e2a\u7ec6\u80de\u3002

\u6307\u6570\u9636\u589e\u957f\u5f97\u975e\u5e38\u5feb\uff0c\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\u4e00\u822c\u662f\u4e0d\u80fd\u88ab\u63a5\u53d7\u7684\u3002\u82e5\u4e00\u4e2a\u95ee\u9898\u4f7f\u7528\u300c\u66b4\u529b\u679a\u4e3e\u300d\u6c42\u89e3\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u662f \\(O(2^n)\\) \uff0c\u90a3\u4e48\u4e00\u822c\u90fd\u9700\u8981\u4f7f\u7528\u300c\u52a8\u6001\u89c4\u5212\u300d\u6216\u300c\u8d2a\u5fc3\u7b97\u6cd5\u300d\u7b49\u7b97\u6cd5\u6765\u6c42\u89e3\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\nint count = 0, base = 1;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor (int i = 0; i < n; i++) {\nfor (int j = 0; j < base; j++) {\ncount++;\n}\nbase *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}\n
time_complexity.cpp
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\nint count = 0, base = 1;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor (int i = 0; i < n; i++) {\nfor (int j = 0; j < base; j++) {\ncount++;\n}\nbase *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}\n
time_complexity.py
\"\"\" \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\"\"\"\ndef exponential(n):\ncount, base = 0, 1\n# cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor _ in range(n):\nfor _ in range(base):\ncount += 1\nbase *= 2\n# count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count\n
time_complexity.go
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09*/\nfunc exponential(n int) int {\ncount, base := 0, 1\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor i := 0; i < n; i++ {\nfor j := 0; j < base; j++ {\ncount++\n}\nbase *= 2\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count\n}\n
time_complexity.js
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction exponential(n) {\nlet count = 0,\nbase = 1;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor (let i = 0; i < n; i++) {\nfor (let j = 0; j < base; j++) {\ncount++;\n}\nbase *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}\n
time_complexity.ts
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction exponential(n: number): number {\nlet count = 0,\nbase = 1;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor (let i = 0; i < n; i++) {\nfor (let j = 0; j < base; j++) {\ncount++;\n}\nbase *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}\n
time_complexity.c
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n) {\nint count = 0;\nint bas = 1;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor (int i = 0; i < n; i++) {\nfor (int j = 0; j < bas; j++) {\ncount++;\n}\nbas *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}\n
time_complexity.cs
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint exponential(int n)\n{\nint count = 0, bas = 1;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor (int i = 0; i < n; i++)\n{\nfor (int j = 0; j < bas; j++)\n{\ncount++;\n}\nbas *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}\n
time_complexity.swift
/* \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunc exponential(n: Int) -> Int {\nvar count = 0\nvar base = 1\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nfor _ in 0 ..< n {\nfor _ in 0 ..< base {\ncount += 1\n}\nbase *= 2\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count\n}\n
time_complexity.zig
// \u6307\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\nfn exponential(n: i32) i32{\nvar count: i32 = 0;\nvar bas: i32 = 1;\nvar i: i32 = 0;\n// cell \u6bcf\u8f6e\u4e00\u5206\u4e3a\u4e8c\uff0c\u5f62\u6210\u6570\u5217 1, 2, 4, 8, ..., 2^(n-1)\nwhile (i < n) : (i += 1) {\nvar j: i32 = 0;\nwhile (j < bas) : (j += 1) {\ncount += 1;\n}\nbas *= 2;\n}\n// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\nreturn count;\n}       

Fig. \u6307\u6570\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6

\u5728\u5b9e\u9645\u7b97\u6cd5\u4e2d\uff0c\u6307\u6570\u9636\u5e38\u51fa\u73b0\u4e8e\u9012\u5f52\u51fd\u6570\u3002\u4f8b\u5982\u4ee5\u4e0b\u4ee3\u7801\uff0c\u4e0d\u65ad\u5730\u4e00\u5206\u4e3a\u4e8c\uff0c\u5206\u88c2 \\(n\\) \u6b21\u540e\u505c\u6b62\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.cpp
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.py
\"\"\" \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\ndef exp_recur(n):\nif n == 1: return 1\nreturn exp_recur(n - 1) + exp_recur(n - 1) + 1\n
time_complexity.go
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09*/\nfunc expRecur(n int) int {\nif n == 1 {\nreturn 1\n}\nreturn expRecur(n-1) + expRecur(n-1) + 1\n}\n
time_complexity.js
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction expRecur(n) {\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.ts
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction expRecur(n: number): number {\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.c
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n) {\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.cs
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint expRecur(int n)\n{\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.swift
/* \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc expRecur(n: Int) -> Int {\nif n == 1 {\nreturn 1\n}\nreturn expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
time_complexity.zig
// \u6307\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn expRecur(n: i32) i32{\nif (n == 1) return 1;\nreturn expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#olog-n","title":"\u5bf9\u6570\u9636 \\(O(\\log n)\\)","text":"

\u5bf9\u6570\u9636\u4e0e\u6307\u6570\u9636\u6b63\u597d\u76f8\u53cd\uff0c\u540e\u8005\u53cd\u6620\u201c\u6bcf\u8f6e\u589e\u52a0\u5230\u4e24\u500d\u7684\u60c5\u51b5\u201d\uff0c\u800c\u524d\u8005\u53cd\u6620\u201c\u6bcf\u8f6e\u7f29\u51cf\u5230\u4e00\u534a\u7684\u60c5\u51b5\u201d\u3002\u5bf9\u6570\u9636\u4ec5\u6b21\u4e8e\u5e38\u6570\u9636\uff0c\u65f6\u95f4\u589e\u957f\u5f97\u5f88\u6162\uff0c\u662f\u7406\u60f3\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u3002

\u5bf9\u6570\u9636\u5e38\u51fa\u73b0\u4e8e\u300c\u4e8c\u5206\u67e5\u627e\u300d\u548c\u300c\u5206\u6cbb\u7b97\u6cd5\u300d\u4e2d\uff0c\u4f53\u73b0\u201c\u4e00\u5206\u4e3a\u591a\u201d\u3001\u201c\u5316\u7e41\u4e3a\u7b80\u201d\u7684\u7b97\u6cd5\u601d\u60f3\u3002

\u8bbe\u8f93\u5165\u6570\u636e\u5927\u5c0f\u4e3a \\(n\\) \uff0c\u7531\u4e8e\u6bcf\u8f6e\u7f29\u51cf\u5230\u4e00\u534a\uff0c\u56e0\u6b64\u5faa\u73af\u6b21\u6570\u662f \\(\\log_2 n\\) \uff0c\u5373 \\(2^n\\) \u7684\u53cd\u51fd\u6570\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\nint count = 0;\nwhile (n > 1) {\nn = n / 2;\ncount++;\n}\nreturn count;\n}\n
time_complexity.cpp
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\nint count = 0;\nwhile (n > 1) {\nn = n / 2;\ncount++;\n}\nreturn count;\n}\n
time_complexity.py
\"\"\" \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\"\"\"\ndef logarithmic(n):\ncount = 0\nwhile n > 1:\nn = n / 2\ncount += 1\nreturn count\n
time_complexity.go
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09*/\nfunc logarithmic(n float64) int {\ncount := 0\nfor n > 1 {\nn = n / 2\ncount++\n}\nreturn count\n}\n
time_complexity.js
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction logarithmic(n) {\nlet count = 0;\nwhile (n > 1) {\nn = n / 2;\ncount++;\n}\nreturn count;\n}\n
time_complexity.ts
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunction logarithmic(n: number): number {\nlet count = 0;\nwhile (n > 1) {\nn = n / 2;\ncount++;\n}\nreturn count;\n}\n
time_complexity.c
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n) {\nint count = 0;\nwhile (n > 1) {\nn = n / 2;\ncount++;\n}\nreturn count;\n}\n
time_complexity.cs
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nint logarithmic(float n)\n{\nint count = 0;\nwhile (n > 1)\n{\nn = n / 2;\ncount++;\n}\nreturn count;\n}\n
time_complexity.swift
/* \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09 */\nfunc logarithmic(n: Int) -> Int {\nvar count = 0\nvar n = n\nwhile n > 1 {\nn = n / 2\ncount += 1\n}\nreturn count\n}\n
time_complexity.zig
// \u5bf9\u6570\u9636\uff08\u5faa\u73af\u5b9e\u73b0\uff09\nfn logarithmic(n: f32) i32\n{\nvar count: i32 = 0;\nvar n_var = n;\nwhile (n_var > 1)\n{\nn_var = n_var / 2;\ncount +=1;\n}\nreturn count;\n}\n

Fig. \u5bf9\u6570\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6

\u4e0e\u6307\u6570\u9636\u7c7b\u4f3c\uff0c\u5bf9\u6570\u9636\u4e5f\u5e38\u51fa\u73b0\u4e8e\u9012\u5f52\u51fd\u6570\u3002\u4ee5\u4e0b\u4ee3\u7801\u5f62\u6210\u4e86\u4e00\u4e2a\u9ad8\u5ea6\u4e3a \\(\\log_2 n\\) \u7684\u9012\u5f52\u6811\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
time_complexity.cpp
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
time_complexity.py
\"\"\" \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\ndef log_recur(n):\nif n <= 1: return 0\nreturn log_recur(n / 2) + 1\n
time_complexity.go
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09*/\nfunc logRecur(n float64) int {\nif n <= 1 {\nreturn 0\n}\nreturn logRecur(n/2) + 1\n}\n
time_complexity.js
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction logRecur(n) {\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
time_complexity.ts
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction logRecur(n: number): number {\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
time_complexity.c
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n) {\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
time_complexity.cs
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint logRecur(float n)\n{\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
time_complexity.swift
/* \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc logRecur(n: Int) -> Int {\nif n <= 1 {\nreturn 0\n}\nreturn logRecur(n: n / 2) + 1\n}\n
time_complexity.zig
// \u5bf9\u6570\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn logRecur(n: f32) i32\n{\nif (n <= 1) return 0;\nreturn logRecur(n / 2) + 1;\n}\n
"},{"location":"chapter_computational_complexity/time_complexity/#on-log-n","title":"\u7ebf\u6027\u5bf9\u6570\u9636 \\(O(n \\log n)\\)","text":"

\u7ebf\u6027\u5bf9\u6570\u9636\u5e38\u51fa\u73b0\u4e8e\u5d4c\u5957\u5faa\u73af\u4e2d\uff0c\u4e24\u5c42\u5faa\u73af\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u5206\u522b\u4e3a \\(O(\\log n)\\) \u548c \\(O(n)\\) \u3002

\u4e3b\u6d41\u6392\u5e8f\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u662f \\(O(n \\log n )\\) \uff0c\u4f8b\u5982\u5feb\u901f\u6392\u5e8f\u3001\u5f52\u5e76\u6392\u5e8f\u3001\u5806\u6392\u5e8f\u7b49\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\nif (n <= 1) return 1;\nint count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\nfor (int i = 0; i < n; i++) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.cpp
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\nif (n <= 1) return 1;\nint count = linearLogRecur(n / 2) +\nlinearLogRecur(n / 2);\nfor (int i = 0; i < n; i++) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.py
\"\"\" \u7ebf\u6027\u5bf9\u6570\u9636 \"\"\"\ndef linear_log_recur(n):\nif n <= 1: return 1\ncount = linear_log_recur(n // 2) + \\\n            linear_log_recur(n // 2)\nfor _ in range(n):\ncount += 1\nreturn count\n
time_complexity.go
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunc linearLogRecur(n float64) int {\nif n <= 1 {\nreturn 1\n}\ncount := linearLogRecur(n/2) +\nlinearLogRecur(n/2)\nfor i := 0.0; i < n; i++ {\ncount++\n}\nreturn count\n}\n
time_complexity.js
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunction linearLogRecur(n) {\nif (n <= 1) return 1;\nlet count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\nfor (let i = 0; i < n; i++) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.ts
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunction linearLogRecur(n: number): number {\nif (n <= 1) return 1;\nlet count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\nfor (let i = 0; i < n; i++) {\ncount++;\n}\nreturn count;\n}\n
time_complexity.c
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n) {\nif (n <= 1) return 1;\nint count = linearLogRecur(n / 2) +\nlinearLogRecur(n / 2);\nfor (int i = 0; i < n; i++) {\ncount ++;\n}\nreturn count;\n}\n
time_complexity.cs
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nint linearLogRecur(float n)\n{\nif (n <= 1) return 1;\nint count = linearLogRecur(n / 2) +\nlinearLogRecur(n / 2);\nfor (int i = 0; i < n; i++)\n{\ncount++;\n}\nreturn count;\n}\n
time_complexity.swift
/* \u7ebf\u6027\u5bf9\u6570\u9636 */\nfunc linearLogRecur(n: Double) -> Int {\nif n <= 1 {\nreturn 1\n}\nvar count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\nfor _ in 0 ..< Int(n) {\ncount += 1\n}\nreturn count\n}\n
time_complexity.zig
// \u7ebf\u6027\u5bf9\u6570\u9636\nfn linearLogRecur(n: f32) i32\n{\nif (n <= 1) return 1;\nvar count: i32 = linearLogRecur(n / 2) +\nlinearLogRecur(n / 2);\nvar i: f32 = 0;\nwhile (i < n) : (i += 1) {\ncount += 1;\n}\nreturn count;\n}\n

Fig. \u7ebf\u6027\u5bf9\u6570\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6

"},{"location":"chapter_computational_complexity/time_complexity/#on_1","title":"\u9636\u4e58\u9636 \\(O(n!)\\)","text":"

\u9636\u4e58\u9636\u5bf9\u5e94\u6570\u5b66\u4e0a\u7684\u300c\u5168\u6392\u5217\u300d\u3002\u5373\u7ed9\u5b9a \\(n\\) \u4e2a\u4e92\u4e0d\u91cd\u590d\u7684\u5143\u7d20\uff0c\u6c42\u5176\u6240\u6709\u53ef\u80fd\u7684\u6392\u5217\u65b9\u6848\uff0c\u5219\u65b9\u6848\u6570\u91cf\u4e3a

\\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\cdots \\times 2 \\times 1 \\]

\u9636\u4e58\u5e38\u4f7f\u7528\u9012\u5f52\u5b9e\u73b0\u3002\u4f8b\u5982\u4ee5\u4e0b\u4ee3\u7801\uff0c\u7b2c\u4e00\u5c42\u5206\u88c2\u51fa \\(n\\) \u4e2a\uff0c\u7b2c\u4e8c\u5c42\u5206\u88c2\u51fa \\(n - 1\\) \u4e2a\uff0c\u2026\u2026 \uff0c\u76f4\u81f3\u5230\u7b2c \\(n\\) \u5c42\u65f6\u7ec8\u6b62\u5206\u88c2\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig time_complexity.java
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\nif (n == 0) return 1;\nint count = 0;\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor (int i = 0; i < n; i++) {\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n
time_complexity.cpp
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\nif (n == 0) return 1;\nint count = 0;\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor (int i = 0; i < n; i++) {\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n
time_complexity.py
\"\"\" \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\"\"\"\ndef factorial_recur(n):\nif n == 0: return 1\ncount = 0\n# \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor _ in range(n):\ncount += factorial_recur(n - 1)\nreturn count\n
time_complexity.go
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc factorialRecur(n int) int {\nif n == 0 {\nreturn 1\n}\ncount := 0\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor i := 0; i < n; i++ {\ncount += factorialRecur(n - 1)\n}\nreturn count\n}\n
time_complexity.js
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction factorialRecur(n) {\nif (n == 0) return 1;\nlet count = 0;\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor (let i = 0; i < n; i++) {\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n
time_complexity.ts
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunction factorialRecur(n: number): number {\nif (n == 0) return 1;\nlet count = 0;\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor (let i = 0; i < n; i++) {\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n
time_complexity.c
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n) {\nif (n == 0) return 1;\nint count = 0;\nfor (int i = 0; i < n; i++) {\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n
time_complexity.cs
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nint factorialRecur(int n)\n{\nif (n == 0) return 1;\nint count = 0;\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor (int i = 0; i < n; i++)\n{\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n
time_complexity.swift
/* \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09 */\nfunc factorialRecur(n: Int) -> Int {\nif n == 0 {\nreturn 1\n}\nvar count = 0\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nfor _ in 0 ..< n {\ncount += factorialRecur(n: n - 1)\n}\nreturn count\n}\n
time_complexity.zig
// \u9636\u4e58\u9636\uff08\u9012\u5f52\u5b9e\u73b0\uff09\nfn factorialRecur(n: i32) i32 {\nif (n == 0) return 1;\nvar count: i32 = 0;\nvar i: i32 = 0;\n// \u4ece 1 \u4e2a\u5206\u88c2\u51fa n \u4e2a\nwhile (i < n) : (i += 1) {\ncount += factorialRecur(n - 1);\n}\nreturn count;\n}\n

Fig. \u9636\u4e58\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6

"},{"location":"chapter_computational_complexity/time_complexity/#226","title":"2.2.6. \u6700\u5dee\u3001\u6700\u4f73\u3001\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6","text":"

\u67d0\u4e9b\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e0d\u662f\u6052\u5b9a\u7684\uff0c\u800c\u662f\u4e0e\u8f93\u5165\u6570\u636e\u7684\u5206\u5e03\u6709\u5173\u3002\u4e3e\u4e00\u4e2a\u4f8b\u5b50\uff0c\u8f93\u5165\u4e00\u4e2a\u957f\u5ea6\u4e3a \\(n\\) \u6570\u7ec4 nums \uff0c\u5176\u4e2d nums \u7531\u4ece \\(1\\) \u81f3 \\(n\\) \u7684\u6570\u5b57\u7ec4\u6210\uff0c\u4f46\u5143\u7d20\u987a\u5e8f\u662f\u968f\u673a\u6253\u4e71\u7684\uff1b\u7b97\u6cd5\u7684\u4efb\u52a1\u662f\u8fd4\u56de\u5143\u7d20 \\(1\\) \u7684\u7d22\u5f15\u3002\u6211\u4eec\u53ef\u4ee5\u5f97\u51fa\u4ee5\u4e0b\u7ed3\u8bba\uff1a

  • \u5f53 nums = [?, ?, ..., 1]\uff0c\u5373\u5f53\u672b\u5c3e\u5143\u7d20\u662f \\(1\\) \u65f6\uff0c\u5219\u9700\u5b8c\u6574\u904d\u5386\u6570\u7ec4\uff0c\u6b64\u65f6\u8fbe\u5230 \u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \uff1b
  • \u5f53 nums = [1, ?, ?, ...] \uff0c\u5373\u5f53\u9996\u4e2a\u6570\u5b57\u4e3a \\(1\\) \u65f6\uff0c\u65e0\u8bba\u6570\u7ec4\u591a\u957f\u90fd\u4e0d\u9700\u8981\u7ee7\u7eed\u904d\u5386\uff0c\u6b64\u65f6\u8fbe\u5230 \u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 \\(\\Omega(1)\\) \uff1b

\u300c\u51fd\u6570\u6e10\u8fd1\u4e0a\u754c\u300d\u4f7f\u7528\u5927 \\(O\\) \u8bb0\u53f7\u8868\u793a\uff0c\u4ee3\u8868\u300c\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u3002\u4e0e\u4e4b\u5bf9\u5e94\uff0c\u300c\u51fd\u6570\u6e10\u8fd1\u4e0b\u754c\u300d\u7528 \\(\\Omega\\) \u8bb0\u53f7\uff08Omega Notation\uff09\u6765\u8868\u793a\uff0c\u4ee3\u8868\u300c\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig worst_best_time_complexity.java
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint[] randomNumbers(int n) {\nInteger[] nums = new Integer[n];\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (int i = 0; i < n; i++) {\nnums[i] = i + 1;\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nCollections.shuffle(Arrays.asList(nums));\n// Integer[] -> int[]\nint[] res = new int[n];\nfor (int i = 0; i < n; i++) {\nres[i] = nums[i];\n}\nreturn res;\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int[] nums) {\nfor (int i = 0; i < nums.length; i++) {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (nums[i] == 1)\nreturn i;\n}\nreturn -1;\n}\n
worst_best_time_complexity.cpp
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nvector<int> randomNumbers(int n) {\nvector<int> nums(n);\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (int i = 0; i < n; i++) {\nnums[i] = i + 1;\n}\n// \u4f7f\u7528\u7cfb\u7edf\u65f6\u95f4\u751f\u6210\u968f\u673a\u79cd\u5b50\nunsigned seed = chrono::system_clock::now().time_since_epoch().count();\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nshuffle(nums.begin(), nums.end(), default_random_engine(seed));\nreturn nums;\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(vector<int>& nums) {\nfor (int i = 0; i < nums.size(); i++) {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (nums[i] == 1)\nreturn i;\n}\nreturn -1;\n}\n
worst_best_time_complexity.py
\"\"\" \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a: 1, 2, ..., n \uff0c\u987a\u5e8f\u88ab\u6253\u4e71 \"\"\"\ndef random_numbers(n):\n# \u751f\u6210\u6570\u7ec4 nums =: 1, 2, 3, ..., n\nnums = [i for i in range(1, n + 1)]\n# \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nrandom.shuffle(nums)\nreturn nums\n\"\"\" \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 \"\"\"\ndef find_one(nums):\nfor i in range(len(nums)):\n# \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n# \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif nums[i] == 1:\nreturn i\nreturn -1\n
worst_best_time_complexity.go
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunc randomNumbers(n int) []int {\nnums := make([]int, n)\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor i := 0; i < n; i++ {\nnums[i] = i + 1\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nrand.Shuffle(len(nums), func(i, j int) {\nnums[i], nums[j] = nums[j], nums[i]\n})\nreturn nums\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunc findOne(nums []int) int {\nfor i := 0; i < len(nums); i++ {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif nums[i] == 1 {\nreturn i\n}\n}\nreturn -1\n}\n
worst_best_time_complexity.js
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunction randomNumbers(n) {\nconst nums = Array(n);\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (let i = 0; i < n; i++) {\nnums[i] = i + 1;\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nfor (let i = 0; i < n; i++) {\nconst r = Math.floor(Math.random() * (i + 1));\nconst temp = nums[i];\nnums[i] = nums[r];\nnums[r] = temp;\n}\nreturn nums;\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunction findOne(nums) {\nfor (let i = 0; i < nums.length; i++) {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (nums[i] === 1) {\nreturn i;\n}\n}\nreturn -1;\n}\n
worst_best_time_complexity.ts
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunction randomNumbers(n: number): number[] {\nconst nums = Array(n);\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (let i = 0; i < n; i++) {\nnums[i] = i + 1;\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nfor (let i = 0; i < n; i++) {\nconst r = Math.floor(Math.random() * (i + 1));\nconst temp = nums[i];\nnums[i] = nums[r];\nnums[r] = temp;\n}\nreturn nums;\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunction findOne(nums: number[]): number {\nfor (let i = 0; i < nums.length; i++) {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (nums[i] === 1) {\nreturn i;\n}\n}\nreturn -1;\n}\n
worst_best_time_complexity.c
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint *randomNumbers(int n) {\n// \u5206\u914d\u5806\u533a\u5185\u5b58\uff08\u521b\u5efa\u4e00\u7ef4\u53ef\u53d8\u957f\u6570\u7ec4\uff1a\u6570\u7ec4\u4e2d\u5143\u7d20\u6570\u91cf\u4e3an\uff0c\u5143\u7d20\u7c7b\u578b\u4e3aint\uff09\nint *nums = (int *)malloc(n * sizeof(int));\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (int i = 0; i < n; i++) {\nnums[i] = i + 1;\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20 \nfor (int i = n - 1; i > 0; i--) {\nint j = rand() % (i + 1);\nint temp = nums[i];\nnums[i] = nums[j];\nnums[j] = temp; }\nreturn nums;\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int *nums, int n) {\nfor (int i = 0; i < n; i++) {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (nums[i] == 1) return i;\n}\nreturn -1;\n}\n
worst_best_time_complexity.cs
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nint[] randomNumbers(int n)\n{\nint[] nums = new int[n];\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (int i = 0; i < n; i++)\n{\nnums[i] = i + 1;\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nfor (int i = 0; i < nums.Length; i++)\n{\nvar index = new Random().Next(i, nums.Length);\nvar tmp = nums[i];\nvar ran = nums[index];\nnums[i] = ran;\nnums[index] = tmp;\n}\nreturn nums;\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nint findOne(int[] nums)\n{\nfor (int i = 0; i < nums.Length; i++)\n{\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (nums[i] == 1)\nreturn i;\n}\nreturn -1;\n}\n
worst_best_time_complexity.swift
/* \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71 */\nfunc randomNumbers(n: Int) -> [Int] {\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nvar nums = Array(1 ... n)\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nnums.shuffle()\nreturn nums\n}\n/* \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15 */\nfunc findOne(nums: [Int]) -> Int {\nfor i in nums.indices {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif nums[i] == 1 {\nreturn i\n}\n}\nreturn -1\n}\n
worst_best_time_complexity.zig
// \u751f\u6210\u4e00\u4e2a\u6570\u7ec4\uff0c\u5143\u7d20\u4e3a { 1, 2, ..., n }\uff0c\u987a\u5e8f\u88ab\u6253\u4e71\npub fn randomNumbers(comptime n: usize) [n]i32 {\nvar nums: [n]i32 = undefined;\n// \u751f\u6210\u6570\u7ec4 nums = { 1, 2, 3, ..., n }\nfor (nums) |*num, i| {\nnum.* = @intCast(i32, i) + 1;\n}\n// \u968f\u673a\u6253\u4e71\u6570\u7ec4\u5143\u7d20\nconst rand = std.crypto.random;\nrand.shuffle(i32, &nums);\nreturn nums;\n}\n// \u67e5\u627e\u6570\u7ec4 nums \u4e2d\u6570\u5b57 1 \u6240\u5728\u7d22\u5f15\npub fn findOne(nums: []i32) i32 {\nfor (nums) |num, i| {\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 O(1)\n// \u5f53\u5143\u7d20 1 \u5728\u6570\u7ec4\u5c3e\u90e8\u65f6\uff0c\u8fbe\u5230\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 O(n)\nif (num == 1) return @intCast(i32, i);\n}\nreturn -1;\n}\n

Tip

\u6211\u4eec\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\u5f88\u5c11\u4f7f\u7528\u300c\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6\u300d\uff0c\u56e0\u4e3a\u5f80\u5f80\u53ea\u6709\u5f88\u5c0f\u6982\u7387\u4e0b\u624d\u80fd\u8fbe\u5230\uff0c\u4f1a\u5e26\u6765\u4e00\u5b9a\u7684\u8bef\u5bfc\u6027\u3002\u53cd\u4e4b\uff0c\u300c\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u6700\u4e3a\u5b9e\u7528\uff0c\u56e0\u4e3a\u5b83\u7ed9\u51fa\u4e86\u4e00\u4e2a\u201c\u6548\u7387\u5b89\u5168\u503c\u201d\uff0c\u8ba9\u6211\u4eec\u53ef\u4ee5\u653e\u5fc3\u5730\u4f7f\u7528\u7b97\u6cd5\u3002

\u4ece\u4e0a\u8ff0\u793a\u4f8b\u53ef\u4ee5\u770b\u51fa\uff0c\u6700\u5dee\u6216\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6\u53ea\u51fa\u73b0\u5728\u201c\u7279\u6b8a\u5206\u5e03\u7684\u6570\u636e\u201d\u4e2d\uff0c\u8fd9\u4e9b\u60c5\u51b5\u7684\u51fa\u73b0\u6982\u7387\u5f80\u5f80\u5f88\u5c0f\uff0c\u56e0\u6b64\u5e76\u4e0d\u80fd\u6700\u771f\u5b9e\u5730\u53cd\u6620\u7b97\u6cd5\u8fd0\u884c\u6548\u7387\u3002\u76f8\u5bf9\u5730\uff0c\u300c\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u300d\u53ef\u4ee5\u4f53\u73b0\u7b97\u6cd5\u5728\u968f\u673a\u8f93\u5165\u6570\u636e\u4e0b\u7684\u8fd0\u884c\u6548\u7387\uff0c\u7528 \\(\\Theta\\) \u8bb0\u53f7\uff08Theta Notation\uff09\u6765\u8868\u793a\u3002

\u5bf9\u4e8e\u90e8\u5206\u7b97\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u7b80\u5355\u5730\u63a8\u7b97\u51fa\u968f\u673a\u6570\u636e\u5206\u5e03\u4e0b\u7684\u5e73\u5747\u60c5\u51b5\u3002\u6bd4\u5982\u4e0a\u8ff0\u793a\u4f8b\uff0c\u7531\u4e8e\u8f93\u5165\u6570\u7ec4\u662f\u88ab\u6253\u4e71\u7684\uff0c\u56e0\u6b64\u5143\u7d20 \\(1\\) \u51fa\u73b0\u5728\u4efb\u610f\u7d22\u5f15\u7684\u6982\u7387\u90fd\u662f\u76f8\u7b49\u7684\uff0c\u90a3\u4e48\u7b97\u6cd5\u7684\u5e73\u5747\u5faa\u73af\u6b21\u6570\u5219\u662f\u6570\u7ec4\u957f\u5ea6\u7684\u4e00\u534a \\(\\frac{n}{2}\\) \uff0c\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(\\Theta(\\frac{n}{2}) = \\Theta(n)\\) \u3002

\u4f46\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\uff0c\u5c24\u5176\u662f\u8f83\u4e3a\u590d\u6742\u7684\u7b97\u6cd5\uff0c\u8ba1\u7b97\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u6bd4\u8f83\u56f0\u96be\uff0c\u56e0\u4e3a\u5f88\u96be\u7b80\u4fbf\u5730\u5206\u6790\u51fa\u5728\u6570\u636e\u5206\u5e03\u4e0b\u7684\u6574\u4f53\u6570\u5b66\u671f\u671b\u3002\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u4e00\u822c\u4f7f\u7528\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u6765\u4f5c\u4e3a\u7b97\u6cd5\u6548\u7387\u7684\u8bc4\u5224\u6807\u51c6\u3002

\u4e3a\u4ec0\u4e48\u5f88\u5c11\u770b\u5230 \\(\\Theta\\) \u7b26\u53f7\uff1f

\u5b9e\u9645\u4e2d\u6211\u4eec\u7ecf\u5e38\u4f7f\u7528\u300c\u5927 \\(O\\) \u7b26\u53f7\u300d\u6765\u8868\u793a\u300c\u5e73\u5747\u590d\u6742\u5ea6\u300d\uff0c\u8fd9\u6837\u4e25\u683c\u610f\u4e49\u4e0a\u6765\u8bf4\u662f\u4e0d\u89c4\u8303\u7684\u3002\u8fd9\u53ef\u80fd\u662f\u56e0\u4e3a \\(O\\) \u7b26\u53f7\u5b9e\u5728\u662f\u592a\u6717\u6717\u4e0a\u53e3\u4e86\u3002\u5982\u679c\u5728\u672c\u4e66\u548c\u5176\u4ed6\u8d44\u6599\u4e2d\u770b\u5230\u7c7b\u4f3c \u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \u7684\u8868\u8ff0\uff0c\u8bf7\u4f60\u76f4\u63a5\u7406\u89e3\u4e3a \\(\\Theta(n)\\) \u5373\u53ef\u3002

"},{"location":"chapter_data_structure/classification_of_data_structure/","title":"3.2. \u6570\u636e\u7ed3\u6784\u5206\u7c7b","text":"

\u6570\u636e\u7ed3\u6784\u4e3b\u8981\u53ef\u6839\u636e\u300c\u903b\u8f91\u7ed3\u6784\u300d\u548c\u300c\u7269\u7406\u7ed3\u6784\u300d\u4e24\u79cd\u89d2\u5ea6\u8fdb\u884c\u5206\u7c7b\u3002

"},{"location":"chapter_data_structure/classification_of_data_structure/#321","title":"3.2.1. \u903b\u8f91\u7ed3\u6784\uff1a\u7ebf\u6027\u4e0e\u975e\u7ebf\u6027","text":"

\u300c\u903b\u8f91\u7ed3\u6784\u300d\u53cd\u6620\u4e86\u6570\u636e\u4e4b\u95f4\u7684\u903b\u8f91\u5173\u7cfb\u3002\u6570\u7ec4\u548c\u94fe\u8868\u7684\u6570\u636e\u6309\u7167\u987a\u5e8f\u4f9d\u6b21\u6392\u5217\uff0c\u53cd\u6620\u4e86\u6570\u636e\u95f4\u7684\u7ebf\u6027\u5173\u7cfb\uff1b\u6811\u4ece\u9876\u81f3\u5e95\u6309\u5c42\u7ea7\u6392\u5217\uff0c\u53cd\u6620\u4e86\u7956\u5148\u4e0e\u540e\u4ee3\u4e4b\u95f4\u7684\u6d3e\u751f\u5173\u7cfb\uff1b\u56fe\u7531\u7ed3\u70b9\u548c\u8fb9\u7ec4\u6210\uff0c\u53cd\u6620\u4e86\u590d\u6742\u7f51\u7edc\u5173\u7cfb\u3002

\u6211\u4eec\u4e00\u822c\u5c06\u903b\u8f91\u7ed3\u6784\u5206\u4e3a\u300c\u7ebf\u6027\u300d\u548c\u300c\u975e\u7ebf\u6027\u300d\u4e24\u79cd\u3002\u201c\u7ebf\u6027\u201d\u8fd9\u4e2a\u6982\u5ff5\u5f88\u76f4\u89c2\uff0c\u5373\u8868\u660e\u6570\u636e\u5728\u903b\u8f91\u5173\u7cfb\u4e0a\u662f\u6392\u6210\u4e00\u6761\u7ebf\u7684\uff1b\u800c\u5982\u679c\u6570\u636e\u4e4b\u95f4\u7684\u903b\u8f91\u5173\u7cfb\u662f\u975e\u7ebf\u6027\u7684\uff08\u4f8b\u5982\u662f\u7f51\u72b6\u6216\u6811\u72b6\u7684\uff09\uff0c\u90a3\u4e48\u5c31\u662f\u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784\u3002

  • \u7ebf\u6027\u6570\u636e\u7ed3\u6784\uff1a\u6570\u7ec4\u3001\u94fe\u8868\u3001\u6808\u3001\u961f\u5217\u3001\u54c8\u5e0c\u8868\uff1b
  • \u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784\uff1a\u6811\u3001\u56fe\u3001\u5806\u3001\u54c8\u5e0c\u8868\uff1b

Fig. \u7ebf\u6027\u4e0e\u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784

"},{"location":"chapter_data_structure/classification_of_data_structure/#322","title":"3.2.2. \u7269\u7406\u7ed3\u6784\uff1a\u8fde\u7eed\u4e0e\u79bb\u6563","text":"

Note

\u82e5\u611f\u5230\u9605\u8bfb\u56f0\u96be\uff0c\u5efa\u8bae\u5148\u770b\u5b8c\u4e0b\u4e2a\u7ae0\u8282\u300c\u6570\u7ec4\u4e0e\u94fe\u8868\u300d\uff0c\u518d\u56de\u8fc7\u5934\u6765\u7406\u89e3\u7269\u7406\u7ed3\u6784\u7684\u542b\u4e49\u3002

\u300c\u7269\u7406\u7ed3\u6784\u300d\u53cd\u6620\u4e86\u6570\u636e\u5728\u8ba1\u7b97\u673a\u5185\u5b58\u4e2d\u7684\u5b58\u50a8\u65b9\u5f0f\u3002\u4ece\u672c\u8d28\u4e0a\u770b\uff0c\u5206\u522b\u662f \u6570\u7ec4\u7684\u8fde\u7eed\u7a7a\u95f4\u5b58\u50a8 \u548c \u94fe\u8868\u7684\u79bb\u6563\u7a7a\u95f4\u5b58\u50a8\u3002\u7269\u7406\u7ed3\u6784\u4ece\u5e95\u5c42\u4e0a\u51b3\u5b9a\u4e86\u6570\u636e\u7684\u8bbf\u95ee\u3001\u66f4\u65b0\u3001\u589e\u5220\u7b49\u64cd\u4f5c\u65b9\u6cd5\uff0c\u5728\u65f6\u95f4\u6548\u7387\u548c\u7a7a\u95f4\u6548\u7387\u65b9\u9762\u5448\u73b0\u51fa\u6b64\u6d88\u5f7c\u957f\u7684\u7279\u6027\u3002

Fig. \u8fde\u7eed\u7a7a\u95f4\u5b58\u50a8\u4e0e\u79bb\u6563\u7a7a\u95f4\u5b58\u50a8

\u6240\u6709\u6570\u636e\u7ed3\u6784\u90fd\u662f\u57fa\u4e8e\u6570\u7ec4\u3001\u6216\u94fe\u8868\u3001\u6216\u4e24\u8005\u7ec4\u5408\u5b9e\u73b0\u7684\u3002\u4f8b\u5982\u6808\u548c\u961f\u5217\uff0c\u65e2\u53ef\u4ee5\u4f7f\u7528\u6570\u7ec4\u5b9e\u73b0\u3001\u4e5f\u53ef\u4ee5\u4f7f\u7528\u94fe\u8868\u5b9e\u73b0\uff0c\u800c\u4f8b\u5982\u54c8\u5e0c\u8868\uff0c\u5176\u5b9e\u73b0\u540c\u65f6\u5305\u542b\u4e86\u6570\u7ec4\u548c\u94fe\u8868\u3002

  • \u57fa\u4e8e\u6570\u7ec4\u53ef\u5b9e\u73b0\uff1a\u6808\u3001\u961f\u5217\u3001\u54c8\u5e0c\u8868\u3001\u6811\u3001\u5806\u3001\u56fe\u3001\u77e9\u9635\u3001\u5f20\u91cf\uff08\u7ef4\u5ea6 \\(\\geq 3\\) \u7684\u6570\u7ec4\uff09\u7b49\uff1b
  • \u57fa\u4e8e\u94fe\u8868\u53ef\u5b9e\u73b0\uff1a\u6808\u3001\u961f\u5217\u3001\u54c8\u5e0c\u8868\u3001\u6811\u3001\u5806\u3001\u56fe\u7b49\uff1b

\u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6570\u636e\u7ed3\u6784\u4e5f\u88ab\u79f0\u4e3a\u300c\u9759\u6001\u6570\u636e\u7ed3\u6784\u300d\uff0c\u8fd9\u610f\u5473\u7740\u8be5\u6570\u636e\u7ed3\u6784\u5728\u5728\u88ab\u521d\u59cb\u5316\u540e\uff0c\u957f\u5ea6\u4e0d\u53ef\u53d8\u3002\u76f8\u53cd\u5730\uff0c\u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6570\u636e\u7ed3\u6784\u88ab\u79f0\u4e3a\u300c\u52a8\u6001\u6570\u636e\u7ed3\u6784\u300d\uff0c\u8be5\u6570\u636e\u7ed3\u6784\u5728\u88ab\u521d\u59cb\u5316\u540e\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u5728\u7a0b\u5e8f\u8fd0\u884c\u4e2d\u4fee\u6539\u5176\u957f\u5ea6\u3002

Tip

\u6570\u7ec4\u4e0e\u94fe\u8868\u662f\u5176\u4ed6\u6240\u6709\u6570\u636e\u7ed3\u6784\u7684\u201c\u5e95\u5c42\u79ef\u6728\u201d\uff0c\u5efa\u8bae\u8bfb\u8005\u4e00\u5b9a\u8981\u591a\u82b1\u4e9b\u65f6\u95f4\u4e86\u89e3\u3002

"},{"location":"chapter_data_structure/data_and_memory/","title":"3.1. \u6570\u636e\u4e0e\u5185\u5b58","text":""},{"location":"chapter_data_structure/data_and_memory/#311","title":"3.1.1. \u57fa\u672c\u6570\u636e\u7c7b\u578b","text":"

\u8c08\u5230\u8ba1\u7b97\u673a\u4e2d\u7684\u6570\u636e\uff0c\u6211\u4eec\u80fd\u591f\u60f3\u5230\u6587\u672c\u3001\u56fe\u7247\u3001\u89c6\u9891\u3001\u8bed\u97f3\u30013D \u6a21\u578b\u7b49\u7b49\uff0c\u8fd9\u4e9b\u6570\u636e\u867d\u7136\u7ec4\u7ec7\u5f62\u5f0f\u4e0d\u540c\uff0c\u4f46\u662f\u6709\u4e00\u4e2a\u5171\u540c\u70b9\uff0c\u5373\u90fd\u662f\u7531\u5404\u79cd\u57fa\u672c\u6570\u636e\u7c7b\u578b\u6784\u6210\u7684\u3002

\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u662f CPU \u53ef\u4ee5\u76f4\u63a5\u8fdb\u884c\u8fd0\u7b97\u7684\u7c7b\u578b\uff0c\u5728\u7b97\u6cd5\u4e2d\u76f4\u63a5\u88ab\u4f7f\u7528\u3002

  • \u300c\u6574\u6570\u300d\u6839\u636e\u4e0d\u540c\u7684\u957f\u5ea6\u5206\u4e3a byte, short, int, long \uff0c\u6839\u636e\u7b97\u6cd5\u9700\u6c42\u9009\u7528\uff0c\u5373\u5728\u6ee1\u8db3\u53d6\u503c\u8303\u56f4\u7684\u60c5\u51b5\u4e0b\u5c3d\u91cf\u51cf\u5c0f\u5185\u5b58\u7a7a\u95f4\u5360\u7528\uff1b
  • \u300c\u6d6e\u70b9\u6570\u300d\u4ee3\u8868\u5c0f\u6570\uff0c\u6839\u636e\u957f\u5ea6\u5206\u4e3a float, double \uff0c\u540c\u6837\u6839\u636e\u7b97\u6cd5\u7684\u5b9e\u9645\u9700\u6c42\u9009\u7528\uff1b
  • \u300c\u5b57\u7b26\u300d\u5728\u8ba1\u7b97\u673a\u4e2d\u662f\u4ee5\u5b57\u7b26\u96c6\u7684\u5f62\u5f0f\u4fdd\u5b58\u7684\uff0cchar \u7684\u503c\u5b9e\u9645\u4e0a\u662f\u6570\u5b57\uff0c\u4ee3\u8868\u5b57\u7b26\u96c6\u4e2d\u7684\u7f16\u53f7\uff0c\u8ba1\u7b97\u673a\u901a\u8fc7\u5b57\u7b26\u96c6\u67e5\u8868\u6765\u5b8c\u6210\u7f16\u53f7\u5230\u5b57\u7b26\u7684\u8f6c\u6362\u3002\u5360\u7528\u7a7a\u95f4\u4e0e\u5177\u4f53\u7f16\u7a0b\u8bed\u8a00\u6709\u5173\uff0c\u901a\u5e38\u4e3a 2 bytes \u6216 1 byte \uff1b
  • \u300c\u5e03\u5c14\u300d\u4ee3\u8868\u903b\u8f91\u4e2d\u7684 \u201c\u662f\u201d \u4e0e \u201c\u5426\u201d \uff0c\u5176\u5360\u7528\u7a7a\u95f4\u9700\u8981\u5177\u4f53\u6839\u636e\u7f16\u7a0b\u8bed\u8a00\u786e\u5b9a\uff0c\u901a\u5e38\u4e3a 1 byte \u6216 1 bit \uff1b

\u5b57\u8282\u4e0e\u6bd4\u7279

1 \u5b57\u8282 (byte) = 8 \u6bd4\u7279 (bit) \uff0c 1 \u6bd4\u7279\u5373\u6700\u57fa\u672c\u7684 1 \u4e2a\u4e8c\u8fdb\u5236\u4f4d

Table. Java \u7684\u57fa\u672c\u6570\u636e\u7c7b\u578b

\u7c7b\u522b \u7b26\u53f7 \u5360\u7528\u7a7a\u95f4 \u53d6\u503c\u8303\u56f4 \u9ed8\u8ba4\u503c \u6574\u6570 byte 1 byte \\(-2^7\\) ~ \\(2^7 - 1\\) ( \\(-128\\) ~ \\(127\\) ) \\(0\\) short 2 bytes \\(-2^{15}\\) ~ \\(2^{15} - 1\\) \\(0\\) int 4 bytes \\(-2^{31}\\) ~ \\(2^{31} - 1\\) \\(0\\) long 8 bytes \\(-2^{63}\\) ~ \\(2^{63} - 1\\) \\(0\\) \u6d6e\u70b9\u6570 float 4 bytes \\(-3.4 \\times 10^{38}\\) ~ \\(3.4 \\times 10^{38}\\) \\(0.0\\) f double 8 bytes \\(-1.7 \\times 10^{308}\\) ~ \\(1.7 \\times 10^{308}\\) \\(0.0\\) \u5b57\u7b26 char 2 bytes / 1 byte \\(0\\) ~ \\(2^{16} - 1\\) \\(0\\) \u5e03\u5c14 boolean(bool) 1 byte / 1 bit \\(\\text{true}\\) \u6216 \\(\\text{false}\\) \\(\\text{false}\\)

Tip

\u4ee5\u4e0a\u8868\u683c\u4e2d\uff0c\u52a0\u7c97\u9879\u5728\u300c\u7b97\u6cd5\u9898\u300d\u4e2d\u6700\u4e3a\u5e38\u7528\u3002\u6b64\u8868\u683c\u65e0\u9700\u786c\u80cc\uff0c\u5927\u81f4\u7406\u89e3\u5373\u53ef\uff0c\u9700\u8981\u65f6\u53ef\u4ee5\u901a\u8fc7\u67e5\u8868\u6765\u56de\u5fc6\u3002

\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u4e0e\u300c\u6570\u636e\u7ed3\u6784\u300d\u4e4b\u95f4\u7684\u8054\u7cfb\u4e0e\u533a\u522b

\u6211\u4eec\u77e5\u9053\uff0c\u6570\u636e\u7ed3\u6784\u662f\u5728\u8ba1\u7b97\u673a\u4e2d \u7ec4\u7ec7\u4e0e\u5b58\u50a8\u6570\u636e\u7684\u65b9\u5f0f\uff0c\u5b83\u7684\u4e3b\u8bed\u662f\u201c\u7ed3\u6784\u201d\uff0c\u800c\u4e0d\u662f\u201c\u6570\u636e\u201d\u3002\u6bd4\u5982\uff0c\u6211\u4eec\u60f3\u8981\u8868\u793a\u201c\u4e00\u6392\u6570\u5b57\u201d\uff0c\u81ea\u7136\u5e94\u8be5\u4f7f\u7528\u300c\u6570\u7ec4\u300d\u8fd9\u4e2a\u6570\u636e\u7ed3\u6784\u3002\u6570\u7ec4\u7684\u5b58\u50a8\u65b9\u5f0f\u4f7f\u4e4b\u53ef\u4ee5\u8868\u793a\u6570\u5b57\u7684\u76f8\u90bb\u5173\u7cfb\u3001\u5148\u540e\u5173\u7cfb\u7b49\u4e00\u7cfb\u5217\u6211\u4eec\u9700\u8981\u7684\u4fe1\u606f\uff0c\u4f46\u81f3\u4e8e\u5176\u4e2d\u5b58\u50a8\u7684\u662f\u6574\u6570 int \uff0c\u8fd8\u662f\u5c0f\u6570 float \uff0c\u6216\u662f\u5b57\u7b26 char \uff0c\u5219\u4e0e\u6240\u8c13\u7684\u6570\u636e\u7684\u7ed3\u6784\u65e0\u5173\u4e86\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d */\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nboolean[] booleans = new boolean[5];\n
/* \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d */\nint numbers[5];\nfloat decimals[5];\nchar characters[5];\nbool booleans[5];\n
\"\"\" Python \u7684 list \u53ef\u4ee5\u81ea\u7531\u5b58\u50a8\u5404\u79cd\u57fa\u672c\u6570\u636e\u7c7b\u578b\u548c\u5bf9\u8c61 \"\"\"\nlist = [0, 0.0, 'a', False]\n
// \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d\nvar numbers = [5]int{}\nvar decimals = [5]float64{}\nvar characters = [5]byte{}\nvar booleans = [5]bool{}\n
/* JavaScript \u7684\u6570\u7ec4\u53ef\u4ee5\u81ea\u7531\u5b58\u50a8\u5404\u79cd\u57fa\u672c\u6570\u636e\u7c7b\u578b\u548c\u5bf9\u8c61 */\nconst array = [0, 0.0, 'a', false];\n
/* \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d */\nconst numbers: number[] = [];\nconst characters: string[] = [];\nconst booleans: boolean[] = [];\n
/* \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d */\nint numbers[10];\nfloat decimals[10];\nchar characters[10];\nbool booleans[10];\n
/* \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d */\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nbool[] booleans = new bool[5];\n
/* \u4f7f\u7528\u591a\u79cd\u300c\u57fa\u672c\u6570\u636e\u7c7b\u578b\u300d\u6765\u521d\u59cb\u5316\u300c\u6570\u7ec4\u300d */\nlet numbers = Array(repeating: Int(), count: 5)\nlet decimals = Array(repeating: Double(), count: 5)\nlet characters = Array(repeating: Character(\"a\"), count: 5)\nlet booleans = Array(repeating: Bool(), count: 5)\n
\n
"},{"location":"chapter_data_structure/data_and_memory/#312","title":"3.1.2. \u8ba1\u7b97\u673a\u5185\u5b58","text":"

\u5728\u8ba1\u7b97\u673a\u4e2d\uff0c\u5185\u5b58\u548c\u786c\u76d8\u662f\u4e24\u79cd\u4e3b\u8981\u7684\u5b58\u50a8\u786c\u4ef6\u8bbe\u5907\u3002\u300c\u786c\u76d8\u300d\u4e3b\u8981\u7528\u4e8e\u957f\u671f\u5b58\u50a8\u6570\u636e\uff0c\u5bb9\u91cf\u8f83\u5927\uff08\u901a\u5e38\u53ef\u8fbe\u5230 TB \u7ea7\u522b\uff09\u3001\u901f\u5ea6\u8f83\u6162\u3002\u300c\u5185\u5b58\u300d\u7528\u4e8e\u8fd0\u884c\u7a0b\u5e8f\u65f6\u6682\u5b58\u6570\u636e\uff0c\u901f\u5ea6\u8f83\u5feb\uff0c\u4f46\u5bb9\u91cf\u8f83\u5c0f\uff08\u901a\u5e38\u4e3a GB \u7ea7\u522b\uff09\u3002

\u7b97\u6cd5\u8fd0\u884c\u4e2d\uff0c\u76f8\u5173\u6570\u636e\u90fd\u88ab\u5b58\u50a8\u5728\u5185\u5b58\u4e2d\u3002\u4e0b\u56fe\u5c55\u793a\u4e86\u4e00\u4e2a\u8ba1\u7b97\u673a\u5185\u5b58\u6761\uff0c\u5176\u4e2d\u6bcf\u4e2a\u9ed1\u8272\u65b9\u5757\u90fd\u5305\u542b\u4e00\u5757\u5185\u5b58\u7a7a\u95f4\u3002\u6211\u4eec\u53ef\u4ee5\u5c06\u5185\u5b58\u60f3\u8c61\u6210\u4e00\u4e2a\u5de8\u5927\u7684 Excel \u8868\u683c\uff0c\u5176\u4e2d\u6bcf\u4e2a\u5355\u5143\u683c\u90fd\u53ef\u4ee5\u5b58\u50a8 1 byte \u7684\u6570\u636e\uff0c\u5728\u7b97\u6cd5\u8fd0\u884c\u65f6\uff0c\u6240\u6709\u6570\u636e\u90fd\u88ab\u5b58\u50a8\u5728\u8fd9\u4e9b\u5355\u5143\u683c\u4e2d\u3002

\u7cfb\u7edf\u901a\u8fc7\u300c\u5185\u5b58\u5730\u5740 Memory Location\u300d\u6765\u8bbf\u95ee\u76ee\u6807\u5185\u5b58\u4f4d\u7f6e\u7684\u6570\u636e\u3002\u8ba1\u7b97\u673a\u6839\u636e\u7279\u5b9a\u89c4\u5219\u7ed9\u8868\u683c\u4e2d\u6bcf\u4e2a\u5355\u5143\u683c\u7f16\u53f7\uff0c\u4fdd\u8bc1\u6bcf\u5757\u5185\u5b58\u7a7a\u95f4\u90fd\u6709\u72ec\u7acb\u7684\u5185\u5b58\u5730\u5740\u3002\u81ea\u6b64\uff0c\u7a0b\u5e8f\u4fbf\u901a\u8fc7\u8fd9\u4e9b\u5730\u5740\uff0c\u8bbf\u95ee\u5185\u5b58\u4e2d\u7684\u6570\u636e\u3002

Fig. \u5185\u5b58\u6761\u3001\u5185\u5b58\u7a7a\u95f4\u3001\u5185\u5b58\u5730\u5740

\u5185\u5b58\u8d44\u6e90\u662f\u8bbe\u8ba1\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u91cd\u8981\u8003\u8651\u56e0\u7d20\u3002\u5185\u5b58\u662f\u6240\u6709\u7a0b\u5e8f\u7684\u516c\u5171\u8d44\u6e90\uff0c\u5f53\u5185\u5b58\u88ab\u67d0\u7a0b\u5e8f\u5360\u7528\u65f6\uff0c\u4e0d\u80fd\u88ab\u5176\u5b83\u7a0b\u5e8f\u540c\u65f6\u4f7f\u7528\u3002\u6211\u4eec\u9700\u8981\u6839\u636e\u5269\u4f59\u5185\u5b58\u8d44\u6e90\u7684\u60c5\u51b5\u6765\u8bbe\u8ba1\u7b97\u6cd5\u3002\u4f8b\u5982\uff0c\u82e5\u5269\u4f59\u5185\u5b58\u7a7a\u95f4\u6709\u9650\uff0c\u5219\u8981\u6c42\u7b97\u6cd5\u5360\u7528\u7684\u5cf0\u503c\u5185\u5b58\u4e0d\u80fd\u8d85\u8fc7\u7cfb\u7edf\u5269\u4f59\u5185\u5b58\uff1b\u82e5\u8fd0\u884c\u7684\u7a0b\u5e8f\u5f88\u591a\u3001\u7f3a\u5c11\u5927\u5757\u8fde\u7eed\u7684\u5185\u5b58\u7a7a\u95f4\uff0c\u5219\u8981\u6c42\u9009\u53d6\u7684\u6570\u636e\u7ed3\u6784\u5fc5\u987b\u80fd\u591f\u5b58\u50a8\u5728\u79bb\u6563\u7684\u5185\u5b58\u7a7a\u95f4\u5185\u3002

"},{"location":"chapter_data_structure/summary/","title":"3.3. \u5c0f\u7ed3","text":"
  • \u6574\u6570 byte, short, int, long \u3001\u6d6e\u70b9\u6570 float, double \u3001\u5b57\u7b26 char \u3001\u5e03\u5c14 boolean \u662f\u8ba1\u7b97\u673a\u4e2d\u7684\u57fa\u672c\u6570\u636e\u7c7b\u578b\uff0c\u5360\u7528\u7a7a\u95f4\u7684\u5927\u5c0f\u51b3\u5b9a\u4e86\u5b83\u4eec\u7684\u53d6\u503c\u8303\u56f4\u3002
  • \u5728\u7a0b\u5e8f\u8fd0\u884c\u65f6\uff0c\u6570\u636e\u5b58\u50a8\u5728\u8ba1\u7b97\u673a\u7684\u5185\u5b58\u4e2d\u3002\u5185\u5b58\u4e2d\u6bcf\u5757\u7a7a\u95f4\u90fd\u6709\u72ec\u7acb\u7684\u5185\u5b58\u5730\u5740\uff0c\u7a0b\u5e8f\u662f\u901a\u8fc7\u5185\u5b58\u5730\u5740\u6765\u8bbf\u95ee\u6570\u636e\u7684\u3002
  • \u6570\u636e\u7ed3\u6784\u4e3b\u8981\u53ef\u4ee5\u4ece\u903b\u8f91\u7ed3\u6784\u548c\u7269\u7406\u7ed3\u6784\u4e24\u4e2a\u89d2\u5ea6\u8fdb\u884c\u5206\u7c7b\u3002\u903b\u8f91\u7ed3\u6784\u53cd\u6620\u4e86\u6570\u636e\u4e2d\u5143\u7d20\u4e4b\u95f4\u7684\u903b\u8f91\u5173\u7cfb\uff0c\u7269\u7406\u7ed3\u6784\u53cd\u6620\u4e86\u6570\u636e\u5728\u8ba1\u7b97\u673a\u5185\u5b58\u4e2d\u7684\u5b58\u50a8\u5f62\u5f0f\u3002
  • \u5e38\u89c1\u7684\u903b\u8f91\u7ed3\u6784\u6709\u7ebf\u6027\u3001\u6811\u72b6\u3001\u7f51\u72b6\u7b49\u3002\u6211\u4eec\u4e00\u822c\u6839\u636e\u903b\u8f91\u7ed3\u6784\u5c06\u6570\u636e\u7ed3\u6784\u5206\u4e3a\u7ebf\u6027\uff08\u6570\u7ec4\u3001\u94fe\u8868\u3001\u6808\u3001\u961f\u5217\uff09\u548c\u975e\u7ebf\u6027\uff08\u6811\u3001\u56fe\u3001\u5806\uff09\u4e24\u79cd\u3002\u6839\u636e\u5b9e\u73b0\u65b9\u5f0f\u7684\u4e0d\u540c\uff0c\u54c8\u5e0c\u8868\u53ef\u80fd\u662f\u7ebf\u6027\u6216\u975e\u7ebf\u6027\u3002
  • \u7269\u7406\u7ed3\u6784\u4e3b\u8981\u6709\u4e24\u79cd\uff0c\u5206\u522b\u662f\u8fde\u7eed\u7a7a\u95f4\u5b58\u50a8\uff08\u6570\u7ec4\uff09\u548c\u79bb\u6563\u7a7a\u95f4\u5b58\u50a8\uff08\u94fe\u8868\uff09\uff0c\u6240\u6709\u7684\u6570\u636e\u7ed3\u6784\u90fd\u662f\u7531\u6570\u7ec4\u3001\u6216\u94fe\u8868\u3001\u6216\u4e24\u8005\u7ec4\u5408\u5b9e\u73b0\u7684\u3002
"},{"location":"chapter_graph/graph/","title":"9.1. \u56fe","text":"

\u300c\u56fe Graph\u300d\u662f\u4e00\u79cd\u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784\uff0c\u7531\u300c\u9876\u70b9 Vertex\u300d\u548c\u300c\u8fb9 Edge\u300d\u7ec4\u6210\u3002\u6211\u4eec\u53ef\u5c06\u56fe \\(G\\) \u62bd\u8c61\u5730\u8868\u793a\u4e3a\u4e00\u7ec4\u9876\u70b9 \\(V\\) \u548c\u4e00\u7ec4\u8fb9 \\(E\\) \u7684\u96c6\u5408\u3002\u4f8b\u5982\uff0c\u4ee5\u4e0b\u8868\u793a\u4e00\u4e2a\u5305\u542b 5 \u4e2a\u9876\u70b9\u548c 7 \u6761\u8fb9\u7684\u56fe

\\[ \\begin{aligned} V & = \\{ 1, 2, 3, 4, 5 \\} \\newline E & = \\{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \\} \\newline G & = \\{ V, E \\} \\newline \\end{aligned} \\]

\u90a3\u4e48\uff0c\u56fe\u4e0e\u5176\u4ed6\u6570\u636e\u7ed3\u6784\u7684\u5173\u7cfb\u662f\u4ec0\u4e48\uff1f\u5982\u679c\u6211\u4eec\u628a\u300c\u9876\u70b9\u300d\u770b\u4f5c\u7ed3\u70b9\uff0c\u628a\u300c\u8fb9\u300d\u770b\u4f5c\u8fde\u63a5\u5404\u4e2a\u7ed3\u70b9\u7684\u6307\u9488\uff0c\u5219\u53ef\u5c06\u300c\u56fe\u300d\u770b\u6210\u4e00\u79cd\u4ece\u300c\u94fe\u8868\u300d\u62d3\u5c55\u800c\u6765\u7684\u6570\u636e\u7ed3\u6784\u3002\u76f8\u6bd4\u7ebf\u6027\u5173\u7cfb\uff08\u94fe\u8868\uff09\u548c\u5206\u6cbb\u5173\u7cfb\uff08\u6811\uff09\uff0c\u7f51\u7edc\u5173\u7cfb\uff08\u56fe\uff09\u7684\u81ea\u7531\u5ea6\u66f4\u9ad8\uff0c\u4e5f\u4ece\u800c\u66f4\u4e3a\u590d\u6742\u3002

"},{"location":"chapter_graph/graph/#911","title":"9.1.1. \u56fe\u5e38\u89c1\u7c7b\u578b","text":"

\u6839\u636e\u8fb9\u662f\u5426\u6709\u65b9\u5411\uff0c\u5206\u4e3a\u300c\u65e0\u5411\u56fe Undirected Graph\u300d\u548c\u300c\u6709\u5411\u56fe Directed Graph\u300d\u3002

  • \u5728\u65e0\u5411\u56fe\u4e2d\uff0c\u8fb9\u8868\u793a\u4e24\u7ed3\u70b9\u4e4b\u95f4\u201c\u53cc\u5411\u201d\u7684\u8fde\u63a5\u5173\u7cfb\uff0c\u4f8b\u5982\u5fae\u4fe1\u6216 QQ \u4e2d\u7684\u201c\u597d\u53cb\u5173\u7cfb\u201d\uff1b
  • \u5728\u6709\u5411\u56fe\u4e2d\uff0c\u8fb9\u662f\u6709\u65b9\u5411\u7684\uff0c\u5373 \\(A \\rightarrow B\\) \u548c \\(A \\leftarrow B\\) \u4e24\u4e2a\u65b9\u5411\u7684\u8fb9\u662f\u76f8\u4e92\u72ec\u7acb\u7684\uff0c\u4f8b\u5982\u5fae\u535a\u6216\u6296\u97f3\u4e0a\u7684\u201c\u5173\u6ce8\u201d\u4e0e\u201c\u88ab\u5173\u6ce8\u201d\u5173\u7cfb\uff1b

\u6839\u636e\u6240\u6709\u9876\u70b9\u662f\u5426\u8fde\u901a\uff0c\u5206\u4e3a\u300c\u8fde\u901a\u56fe Connected Graph\u300d\u548c\u300c\u975e\u8fde\u901a\u56fe Disconnected Graph\u300d\u3002

  • \u5bf9\u4e8e\u8fde\u901a\u56fe\uff0c\u4ece\u67d0\u4e2a\u7ed3\u70b9\u51fa\u53d1\uff0c\u53ef\u4ee5\u5230\u8fbe\u5176\u4f59\u4efb\u610f\u7ed3\u70b9\uff1b
  • \u5bf9\u4e8e\u975e\u8fde\u901a\u56fe\uff0c\u4ece\u67d0\u4e2a\u7ed3\u70b9\u51fa\u53d1\uff0c\u81f3\u5c11\u6709\u4e00\u4e2a\u7ed3\u70b9\u65e0\u6cd5\u5230\u8fbe\uff1b

\u6211\u4eec\u53ef\u4ee5\u7ed9\u8fb9\u6dfb\u52a0\u201c\u6743\u91cd\u201d\u53d8\u91cf\uff0c\u5f97\u5230\u300c\u6709\u6743\u56fe Weighted Graph\u300d\u3002\u4f8b\u5982\uff0c\u5728\u738b\u8005\u8363\u8000\u7b49\u6e38\u620f\u4e2d\uff0c\u7cfb\u7edf\u4f1a\u6839\u636e\u5171\u540c\u6e38\u620f\u65f6\u95f4\u6765\u8ba1\u7b97\u73a9\u5bb6\u4e4b\u95f4\u7684\u201c\u4eb2\u5bc6\u5ea6\u201d\uff0c\u8fd9\u79cd\u4eb2\u5bc6\u5ea6\u7f51\u7edc\u5c31\u53ef\u4ee5\u4f7f\u7528\u6709\u6743\u56fe\u6765\u8868\u793a\u3002

"},{"location":"chapter_graph/graph/#912","title":"9.1.2. \u56fe\u5e38\u7528\u672f\u8bed","text":"
  • \u300c\u90bb\u63a5 Adjacency\u300d\uff1a\u5f53\u4e24\u9876\u70b9\u4e4b\u95f4\u6709\u8fb9\u76f8\u8fde\u65f6\uff0c\u79f0\u6b64\u4e24\u9876\u70b9\u201c\u90bb\u63a5\u201d\u3002
  • \u300c\u8def\u5f84 Path\u300d\uff1a\u4ece\u9876\u70b9 A \u5230\u9876\u70b9 B \u8d70\u8fc7\u7684\u8fb9\u6784\u6210\u7684\u5e8f\u5217\uff0c\u88ab\u79f0\u4e3a\u4ece A \u5230 B \u7684\u201c\u8def\u5f84\u201d\u3002
  • \u300c\u5ea6 Degree\u300d\u8868\u793a\u4e00\u4e2a\u9876\u70b9\u5177\u6709\u591a\u5c11\u6761\u8fb9\u3002\u5bf9\u4e8e\u6709\u5411\u56fe\uff0c\u300c\u5165\u5ea6 In-Degree\u300d\u8868\u793a\u6709\u591a\u5c11\u6761\u8fb9\u6307\u5411\u8be5\u9876\u70b9\uff0c\u300c\u51fa\u5ea6 Out-Degree\u300d\u8868\u793a\u6709\u591a\u5c11\u6761\u8fb9\u4ece\u8be5\u9876\u70b9\u6307\u51fa\u3002
"},{"location":"chapter_graph/graph/#913","title":"9.1.3. \u56fe\u7684\u8868\u793a","text":"

\u56fe\u7684\u5e38\u7528\u8868\u793a\u65b9\u6cd5\u6709\u300c\u90bb\u63a5\u77e9\u9635\u300d\u548c\u300c\u90bb\u63a5\u8868\u300d\u3002\u4ee5\u4e0b\u4f7f\u7528\u300c\u65e0\u5411\u56fe\u300d\u6765\u4e3e\u4f8b\u3002

"},{"location":"chapter_graph/graph/#_1","title":"\u90bb\u63a5\u77e9\u9635","text":"

\u8bbe\u56fe\u7684\u9876\u70b9\u6570\u91cf\u4e3a \\(n\\) \uff0c\u300c\u90bb\u63a5\u77e9\u9635 Adjacency Matrix\u300d\u4f7f\u7528\u4e00\u4e2a \\(n \\times n\\) \u5927\u5c0f\u7684\u77e9\u9635\u6765\u8868\u793a\u56fe\uff0c\u6bcf\u4e00\u884c\uff08\u5217\uff09\u4ee3\u8868\u4e00\u4e2a\u9876\u70b9\uff0c\u77e9\u9635\u5143\u7d20\u4ee3\u8868\u8fb9\uff0c\u4f7f\u7528 \\(1\\) \u6216 \\(0\\) \u6765\u8868\u793a\u4e24\u4e2a\u9876\u70b9\u4e4b\u95f4\u6709\u8fb9\u6216\u65e0\u8fb9\u3002

\u90bb\u63a5\u77e9\u9635\u5177\u6709\u4ee5\u4e0b\u6027\u8d28\uff1a

  • \u9876\u70b9\u4e0d\u80fd\u4e0e\u81ea\u8eab\u76f8\u8fde\uff0c\u56e0\u800c\u90bb\u63a5\u77e9\u9635\u4e3b\u5bf9\u89d2\u7ebf\u5143\u7d20\u6ca1\u6709\u610f\u4e49\u3002
  • \u300c\u65e0\u5411\u56fe\u300d\u4e24\u4e2a\u65b9\u5411\u7684\u8fb9\u7b49\u4ef7\uff0c\u6b64\u65f6\u90bb\u63a5\u77e9\u9635\u5173\u4e8e\u4e3b\u5bf9\u89d2\u7ebf\u5bf9\u79f0\u3002
  • \u5c06\u90bb\u63a5\u77e9\u9635\u7684\u5143\u7d20\u4ece \\(1\\) , \\(0\\) \u66ff\u6362\u4e3a\u6743\u91cd\uff0c\u5219\u80fd\u591f\u8868\u793a\u300c\u6709\u6743\u56fe\u300d\u3002

\u4f7f\u7528\u90bb\u63a5\u77e9\u9635\u8868\u793a\u56fe\u65f6\uff0c\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u901a\u8fc7\u8bbf\u95ee\u77e9\u9635\u5143\u7d20\u6765\u83b7\u53d6\u8fb9\uff0c\u56e0\u6b64\u589e\u5220\u67e5\u64cd\u4f5c\u7684\u6548\u7387\u5f88\u9ad8\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u5747\u4e3a \\(O(1)\\) \u3002\u7136\u800c\uff0c\u77e9\u9635\u7684\u7a7a\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n^2)\\) \uff0c\u5185\u5b58\u5360\u7528\u8f83\u5927\u3002

"},{"location":"chapter_graph/graph/#_2","title":"\u90bb\u63a5\u8868","text":"

\u300c\u90bb\u63a5\u8868 Adjacency List\u300d\u4f7f\u7528 \\(n\\) \u4e2a\u94fe\u8868\u6765\u8868\u793a\u56fe\uff0c\u94fe\u8868\u7ed3\u70b9\u8868\u793a\u9876\u70b9\u3002\u7b2c \\(i\\) \u6761\u94fe\u8868\u5bf9\u5e94\u9876\u70b9 \\(i\\) \uff0c\u5176\u4e2d\u5b58\u50a8\u4e86\u6240\u6709\u4e0e\u8be5\u9876\u70b9\u76f8\u8fde\u7684\u9876\u70b9\u3002

\u90bb\u63a5\u8868\u4ec5\u5b58\u50a8\u5b58\u5728\u7684\u8fb9\uff0c\u800c\u8fb9\u7684\u603b\u6570\u5f80\u5f80\u8fdc\u5c0f\u4e8e \\(n^2\\) \uff0c\u56e0\u6b64\u66f4\u52a0\u8282\u7701\u7a7a\u95f4\u3002\u4f46\u662f\uff0c\u56e0\u4e3a\u5728\u90bb\u63a5\u8868\u4e2d\u9700\u8981\u901a\u8fc7\u904d\u5386\u94fe\u8868\u6765\u67e5\u627e\u8fb9\uff0c\u6240\u4ee5\u5176\u65f6\u95f4\u6548\u7387\u4e0d\u5982\u90bb\u63a5\u77e9\u9635\u3002

\u89c2\u5bdf\u4e0a\u56fe\u53d1\u73b0\uff0c\u90bb\u63a5\u8868\u7ed3\u6784\u4e0e\u54c8\u5e0c\u8868\u300c\u94fe\u5730\u5740\u6cd5\u300d\u975e\u5e38\u76f8\u4f3c\uff0c\u56e0\u6b64\u6211\u4eec\u4e5f\u53ef\u4ee5\u7528\u7c7b\u4f3c\u65b9\u6cd5\u6765\u4f18\u5316\u6548\u7387\u3002\u6bd4\u5982\uff0c\u5f53\u94fe\u8868\u8f83\u957f\u65f6\uff0c\u53ef\u4ee5\u628a\u94fe\u8868\u8f6c\u5316\u4e3a\u300cAVL \u6811\u300d\uff0c\u4ece\u800c\u5c06\u65f6\u95f4\u6548\u7387\u4ece \\(O(n)\\) \u4f18\u5316\u81f3 \\(O(\\log n)\\) \uff0c\u8fd8\u53ef\u4ee5\u901a\u8fc7\u4e2d\u5e8f\u904d\u5386\u83b7\u53d6\u6709\u5e8f\u5e8f\u5217\uff1b\u8fd8\u53ef\u4ee5\u5c06\u94fe\u8868\u8f6c\u5316\u4e3a HashSet\uff08\u5373\u54c8\u5e0c\u8868\uff09\uff0c\u5c06\u65f6\u95f4\u590d\u6742\u5ea6\u964d\u4f4e\u81f3 \\(O(1)\\) \uff0c\u3002

"},{"location":"chapter_graph/graph/#914","title":"9.1.4. \u56fe\u5e38\u89c1\u5e94\u7528","text":"

\u73b0\u5b9e\u4e2d\u7684\u8bb8\u591a\u7cfb\u7edf\u90fd\u53ef\u4ee5\u4f7f\u7528\u56fe\u6765\u5efa\u6a21\uff0c\u5bf9\u5e94\u7684\u5f85\u6c42\u89e3\u95ee\u9898\u4e5f\u53ef\u4ee5\u88ab\u7ea6\u5316\u4e3a\u56fe\u8ba1\u7b97\u95ee\u9898\u3002

\u9876\u70b9 \u8fb9 \u56fe\u8ba1\u7b97\u95ee\u9898 \u793e\u4ea4\u7f51\u7edc \u7528\u6237 \u597d\u53cb\u5173\u7cfb \u6f5c\u5728\u597d\u53cb\u63a8\u8350 \u5730\u94c1\u7ebf\u8def \u7ad9\u70b9 \u7ad9\u70b9\u95f4\u7684\u8fde\u901a\u6027 \u6700\u77ed\u8def\u7ebf\u63a8\u8350 \u592a\u9633\u7cfb \u661f\u4f53 \u661f\u4f53\u95f4\u7684\u4e07\u6709\u5f15\u529b\u4f5c\u7528 \u884c\u661f\u8f68\u9053\u8ba1\u7b97"},{"location":"chapter_graph/graph_operations/","title":"9.2. \u56fe\u57fa\u7840\u64cd\u4f5c","text":"

\u56fe\u7684\u57fa\u7840\u64cd\u4f5c\u5206\u4e3a\u5bf9\u300c\u8fb9\u300d\u7684\u64cd\u4f5c\u548c\u5bf9\u300c\u9876\u70b9\u300d\u7684\u64cd\u4f5c\uff0c\u5728\u300c\u90bb\u63a5\u77e9\u9635\u300d\u548c\u300c\u90bb\u63a5\u8868\u300d\u8fd9\u4e24\u79cd\u8868\u793a\u4e0b\u7684\u5b9e\u73b0\u65b9\u5f0f\u4e0d\u540c\u3002

"},{"location":"chapter_graph/graph_operations/#921","title":"9.2.1. \u57fa\u4e8e\u90bb\u63a5\u77e9\u9635\u7684\u5b9e\u73b0","text":"

\u8bbe\u56fe\u7684\u9876\u70b9\u603b\u6570\u4e3a \\(n\\) \uff0c\u5219\u6709\uff1a

  • \u6dfb\u52a0\u6216\u5220\u9664\u8fb9\uff1a\u76f4\u63a5\u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u4fee\u6539\u6307\u5b9a\u8fb9\u7684\u5bf9\u5e94\u5143\u7d20\u5373\u53ef\uff0c\u4f7f\u7528 \\(O(1)\\) \u65f6\u95f4\u3002\u800c\u7531\u4e8e\u662f\u65e0\u5411\u56fe\uff0c\u56e0\u6b64\u9700\u8981\u540c\u65f6\u66f4\u65b0\u4e24\u4e2a\u65b9\u5411\u7684\u8fb9\u3002
  • \u6dfb\u52a0\u9876\u70b9\uff1a\u5728\u90bb\u63a5\u77e9\u9635\u7684\u5c3e\u90e8\u6dfb\u52a0\u4e00\u884c\u4e00\u5217\uff0c\u5e76\u5168\u90e8\u586b \\(0\\) \u5373\u53ef\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\u3002
  • \u5220\u9664\u9876\u70b9\uff1a\u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u4e00\u884c\u4e00\u5217\u3002\u5f53\u5220\u9664\u9996\u884c\u9996\u5217\u65f6\u8fbe\u5230\u6700\u5dee\u60c5\u51b5\uff0c\u9700\u8981\u5c06 \\((n-1)^2\\) \u4e2a\u5143\u7d20\u201c\u5411\u5de6\u4e0a\u79fb\u52a8\u201d\uff0c\u4ece\u800c\u4f7f\u7528 \\(O(n^2)\\) \u65f6\u95f4\u3002
  • \u521d\u59cb\u5316\uff1a\u4f20\u5165 \\(n\\) \u4e2a\u9876\u70b9\uff0c\u521d\u59cb\u5316\u957f\u5ea6\u4e3a \\(n\\) \u7684\u9876\u70b9\u5217\u8868 vertices \uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff1b\u521d\u59cb\u5316 \\(n \\times n\\) \u5927\u5c0f\u7684\u90bb\u63a5\u77e9\u9635 adjMat \uff0c\u4f7f\u7528 \\(O(n^2)\\) \u65f6\u95f4\u3002
\u521d\u59cb\u5316\u90bb\u63a5\u77e9\u9635\u6dfb\u52a0\u8fb9\u5220\u9664\u8fb9\u6dfb\u52a0\u9876\u70b9\u5220\u9664\u9876\u70b9

\u4ee5\u4e0b\u662f\u57fa\u4e8e\u90bb\u63a5\u77e9\u9635\u8868\u793a\u56fe\u7684\u5b9e\u73b0\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig graph_adjacency_matrix.java
/* \u57fa\u4e8e\u90bb\u63a5\u77e9\u9635\u5b9e\u73b0\u7684\u65e0\u5411\u56fe\u7c7b */\nclass GraphAdjMat {\nList<Integer> vertices;     // \u9876\u70b9\u5217\u8868\uff0c\u5143\u7d20\u4ee3\u8868\u201c\u9876\u70b9\u503c\u201d\uff0c\u7d22\u5f15\u4ee3\u8868\u201c\u9876\u70b9\u7d22\u5f15\u201d\nList<List<Integer>> adjMat; // \u90bb\u63a5\u77e9\u9635\uff0c\u884c\u5217\u7d22\u5f15\u5bf9\u5e94\u201c\u9876\u70b9\u7d22\u5f15\u201d\n/* \u6784\u9020\u51fd\u6570 */\npublic GraphAdjMat(int[] vertices, int[][] edges) {\nthis.vertices = new ArrayList<>();\nthis.adjMat = new ArrayList<>();\n// \u6dfb\u52a0\u9876\u70b9\nfor (int val : vertices) {\naddVertex(val);\n}\n// \u6dfb\u52a0\u8fb9\n// \u8bf7\u6ce8\u610f\uff0cedges \u5143\u7d20\u4ee3\u8868\u9876\u70b9\u7d22\u5f15\uff0c\u5373\u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfor (int[] e : edges) {\naddEdge(e[0], e[1]);\n}\n}\n/* \u83b7\u53d6\u9876\u70b9\u6570\u91cf */\npublic int size() {\nreturn vertices.size();\n}\n/* \u6dfb\u52a0\u9876\u70b9 */\npublic void addVertex(int val) {\nint n = size();\n// \u5411\u9876\u70b9\u5217\u8868\u4e2d\u6dfb\u52a0\u65b0\u9876\u70b9\u7684\u503c\nvertices.add(val);\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u6dfb\u52a0\u4e00\u884c\nList<Integer> newRow = new ArrayList<>(n);\nfor (int j = 0; j < n; j++) {\nnewRow.add(0);\n}\nadjMat.add(newRow);\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u6dfb\u52a0\u4e00\u5217\nfor (List<Integer> row : adjMat) {\nrow.add(0);\n}\n}\n/* \u5220\u9664\u9876\u70b9 */\npublic void removeVertex(int index) {\nif (index >= size())\nthrow new IndexOutOfBoundsException();\n// \u5728\u9876\u70b9\u5217\u8868\u4e2d\u79fb\u9664\u7d22\u5f15 index \u7684\u9876\u70b9\nvertices.remove(index);\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u7d22\u5f15 index \u7684\u884c\nadjMat.remove(index);\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u7d22\u5f15 index \u7684\u5217\nfor (List<Integer> row : adjMat) {\nrow.remove(index);\n}\n}\n/* \u6dfb\u52a0\u8fb9 */\n// \u53c2\u6570 i, j \u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\npublic void addEdge(int i, int j) {\n// \u7d22\u5f15\u8d8a\u754c\u4e0e\u76f8\u7b49\u5904\u7406\nif (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\nthrow new IndexOutOfBoundsException();\n// \u5728\u65e0\u5411\u56fe\u4e2d\uff0c\u90bb\u63a5\u77e9\u9635\u6cbf\u4e3b\u5bf9\u89d2\u7ebf\u5bf9\u79f0\uff0c\u5373\u6ee1\u8db3 (i, j) == (j, i)\nadjMat.get(i).set(j, 1);\nadjMat.get(j).set(i, 1);\n}\n/* \u5220\u9664\u8fb9 */\n// \u53c2\u6570 i, j \u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\npublic void removeEdge(int i, int j) {\n// \u7d22\u5f15\u8d8a\u754c\u4e0e\u76f8\u7b49\u5904\u7406\nif (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\nthrow new IndexOutOfBoundsException();\nadjMat.get(i).set(j, 0);\nadjMat.get(j).set(i, 0);\n}\n/* \u6253\u5370\u90bb\u63a5\u77e9\u9635 */\npublic void print() {\nSystem.out.print(\"\u9876\u70b9\u5217\u8868 = \");\nSystem.out.println(vertices);\nSystem.out.println(\"\u90bb\u63a5\u77e9\u9635 =\");\nPrintUtil.printMatrix(adjMat);\n}\n}\n
graph_adjacency_matrix.cpp
\n
graph_adjacency_matrix.py
\n
graph_adjacency_matrix.go
/* \u57fa\u4e8e\u90bb\u63a5\u77e9\u9635\u5b9e\u73b0\u7684\u65e0\u5411\u56fe\u7c7b */\ntype graphAdjMat struct {\n// \u9876\u70b9\u5217\u8868\uff0c\u5143\u7d20\u4ee3\u8868\u201c\u9876\u70b9\u503c\u201d\uff0c\u7d22\u5f15\u4ee3\u8868\u201c\u9876\u70b9\u7d22\u5f15\u201d\nvertices []int\n// \u90bb\u63a5\u77e9\u9635\uff0c\u884c\u5217\u7d22\u5f15\u5bf9\u5e94\u201c\u9876\u70b9\u7d22\u5f15\u201d\nadjMat [][]int\n}\nfunc newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {\n// \u6dfb\u52a0\u9876\u70b9\nn := len(vertices)\nadjMat := make([][]int, n)\nfor i := range adjMat {\nadjMat[i] = make([]int, n)\n}\n// \u521d\u59cb\u5316\u56fe\ng := &graphAdjMat{\nvertices: vertices,\nadjMat:   adjMat,\n}\n// \u6dfb\u52a0\u8fb9\n// \u8bf7\u6ce8\u610f\uff0cedges \u5143\u7d20\u4ee3\u8868\u9876\u70b9\u7d22\u5f15\uff0c\u5373\u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfor i := range edges {\ng.addEdge(edges[i][0], edges[i][1])\n}\nreturn g\n}\n/* \u83b7\u53d6\u9876\u70b9\u6570\u91cf */\nfunc (g *graphAdjMat) size() int {\nreturn len(g.vertices)\n}\n/* \u6dfb\u52a0\u9876\u70b9 */\nfunc (g *graphAdjMat) addVertex(val int) {\nn := g.size()\n// \u5411\u9876\u70b9\u5217\u8868\u4e2d\u6dfb\u52a0\u65b0\u9876\u70b9\u7684\u503c\ng.vertices = append(g.vertices, val)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u6dfb\u52a0\u4e00\u884c\nnewRow := make([]int, n)\ng.adjMat = append(g.adjMat, newRow)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u6dfb\u52a0\u4e00\u5217\nfor i := range g.adjMat {\ng.adjMat[i] = append(g.adjMat[i], 0)\n}\n}\n/* \u5220\u9664\u9876\u70b9 */\nfunc (g *graphAdjMat) removeVertex(index int) {\nif index >= g.size() {\nreturn\n}\n// \u5728\u9876\u70b9\u5217\u8868\u4e2d\u79fb\u9664\u7d22\u5f15 index \u7684\u9876\u70b9\ng.vertices = append(g.vertices[:index], g.vertices[index+1:]...)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u7d22\u5f15 index \u7684\u884c\ng.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u7d22\u5f15 index \u7684\u5217\nfor i := range g.adjMat {\ng.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)\n}\n}\n/* \u6dfb\u52a0\u8fb9 */\n// \u53c2\u6570 i, j \u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfunc (g *graphAdjMat) addEdge(i, j int) {\n// \u7d22\u5f15\u8d8a\u754c\u4e0e\u76f8\u7b49\u5904\u7406\nif i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\nfmt.Errorf(\"%s\", \"Index Out Of Bounds Exception\")\n}\n// \u5728\u65e0\u5411\u56fe\u4e2d\uff0c\u90bb\u63a5\u77e9\u9635\u6cbf\u4e3b\u5bf9\u89d2\u7ebf\u5bf9\u79f0\uff0c\u5373\u6ee1\u8db3 (i, j) == (j, i)\ng.adjMat[i][j] = 1\ng.adjMat[j][i] = 1\n}\n/* \u5220\u9664\u8fb9 */\n// \u53c2\u6570 i, j \u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfunc (g *graphAdjMat) removeEdge(i, j int) {\n// \u7d22\u5f15\u8d8a\u754c\u4e0e\u76f8\u7b49\u5904\u7406\nif i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\nfmt.Errorf(\"%s\", \"Index Out Of Bounds Exception\")\n}\ng.adjMat[i][j] = 0\ng.adjMat[j][i] = 0\n}\n
graph_adjacency_matrix.js
\n
graph_adjacency_matrix.ts
\n
graph_adjacency_matrix.c
\n
graph_adjacency_matrix.cs
\n
graph_adjacency_matrix.swift
/* \u57fa\u4e8e\u90bb\u63a5\u77e9\u9635\u5b9e\u73b0\u7684\u65e0\u5411\u56fe\u7c7b */\nclass GraphAdjMat {\nprivate var vertices: [Int] // \u9876\u70b9\u5217\u8868\uff0c\u5143\u7d20\u4ee3\u8868\u201c\u9876\u70b9\u503c\u201d\uff0c\u7d22\u5f15\u4ee3\u8868\u201c\u9876\u70b9\u7d22\u5f15\u201d\nprivate var adjMat: [[Int]] // \u90bb\u63a5\u77e9\u9635\uff0c\u884c\u5217\u7d22\u5f15\u5bf9\u5e94\u201c\u9876\u70b9\u7d22\u5f15\u201d\n/* \u6784\u9020\u51fd\u6570 */\ninit(vertices: [Int], edges: [[Int]]) {\nself.vertices = []\nadjMat = []\n// \u6dfb\u52a0\u9876\u70b9\nfor val in vertices {\naddVertex(val: val)\n}\n// \u6dfb\u52a0\u8fb9\n// \u8bf7\u6ce8\u610f\uff0cedges \u5143\u7d20\u4ee3\u8868\u9876\u70b9\u7d22\u5f15\uff0c\u5373\u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfor e in edges {\naddEdge(i: e[0], j: e[1])\n}\n}\n/* \u83b7\u53d6\u9876\u70b9\u6570\u91cf */\nfunc size() -> Int {\nvertices.count\n}\n/* \u6dfb\u52a0\u9876\u70b9 */\nfunc addVertex(val: Int) {\nlet n = size()\n// \u5411\u9876\u70b9\u5217\u8868\u4e2d\u6dfb\u52a0\u65b0\u9876\u70b9\u7684\u503c\nvertices.append(val)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u6dfb\u52a0\u4e00\u884c\nlet newRow = Array(repeating: 0, count: n)\nadjMat.append(newRow)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u6dfb\u52a0\u4e00\u5217\nfor i in adjMat.indices {\nadjMat[i].append(0)\n}\n}\n/* \u5220\u9664\u9876\u70b9 */\nfunc removeVertex(index: Int) {\nif index >= size() {\nfatalError(\"\u8d8a\u754c\")\n}\n// \u5728\u9876\u70b9\u5217\u8868\u4e2d\u79fb\u9664\u7d22\u5f15 index \u7684\u9876\u70b9\nvertices.remove(at: index)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u7d22\u5f15 index \u7684\u884c\nadjMat.remove(at: index)\n// \u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u5220\u9664\u7d22\u5f15 index \u7684\u5217\nfor i in adjMat.indices {\nadjMat[i].remove(at: index)\n}\n}\n/* \u6dfb\u52a0\u8fb9 */\n// \u53c2\u6570 i, j \u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfunc addEdge(i: Int, j: Int) {\n// \u7d22\u5f15\u8d8a\u754c\u4e0e\u76f8\u7b49\u5904\u7406\nif i < 0 || j < 0 || i >= size() || j >= size() || i == j {\nfatalError(\"\u8d8a\u754c\")\n}\n// \u5728\u65e0\u5411\u56fe\u4e2d\uff0c\u90bb\u63a5\u77e9\u9635\u6cbf\u4e3b\u5bf9\u89d2\u7ebf\u5bf9\u79f0\uff0c\u5373\u6ee1\u8db3 (i, j) == (j, i)\nadjMat[i][j] = 1\nadjMat[j][i] = 1\n}\n/* \u5220\u9664\u8fb9 */\n// \u53c2\u6570 i, j \u5bf9\u5e94 vertices \u5143\u7d20\u7d22\u5f15\nfunc removeEdge(i: Int, j: Int) {\n// \u7d22\u5f15\u8d8a\u754c\u4e0e\u76f8\u7b49\u5904\u7406\nif i < 0 || j < 0 || i >= size() || j >= size() || i == j {\nfatalError(\"\u8d8a\u754c\")\n}\nadjMat[i][j] = 0\nadjMat[j][i] = 0\n}\n}\n
graph_adjacency_matrix.zig
\n
"},{"location":"chapter_graph/graph_operations/#922","title":"9.2.2. \u57fa\u4e8e\u90bb\u63a5\u8868\u7684\u5b9e\u73b0","text":"

\u8bbe\u56fe\u7684\u9876\u70b9\u603b\u6570\u4e3a \\(n\\) \u3001\u8fb9\u603b\u6570\u4e3a \\(m\\) \uff0c\u5219\u6709\uff1a

  • \u6dfb\u52a0\u8fb9\uff1a\u5728\u9876\u70b9\u5bf9\u5e94\u94fe\u8868\u7684\u5c3e\u90e8\u6dfb\u52a0\u8fb9\u5373\u53ef\uff0c\u4f7f\u7528 \\(O(1)\\) \u65f6\u95f4\u3002\u56e0\u4e3a\u662f\u65e0\u5411\u56fe\uff0c\u6240\u4ee5\u9700\u8981\u540c\u65f6\u6dfb\u52a0\u4e24\u4e2a\u65b9\u5411\u7684\u8fb9\u3002
  • \u5220\u9664\u8fb9\uff1a\u5728\u9876\u70b9\u5bf9\u5e94\u94fe\u8868\u4e2d\u67e5\u8be2\u4e0e\u5220\u9664\u6307\u5b9a\u8fb9\uff0c\u4f7f\u7528 \\(O(m)\\) \u65f6\u95f4\u3002\u4e0e\u6dfb\u52a0\u8fb9\u4e00\u6837\uff0c\u9700\u8981\u540c\u65f6\u5220\u9664\u4e24\u4e2a\u65b9\u5411\u7684\u8fb9\u3002
  • \u6dfb\u52a0\u9876\u70b9\uff1a\u5728\u90bb\u63a5\u8868\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u94fe\u8868\u5373\u53ef\uff0c\u5e76\u4ee5\u65b0\u589e\u9876\u70b9\u4e3a\u94fe\u8868\u5934\u7ed3\u70b9\uff0c\u4f7f\u7528 \\(O(1)\\) \u65f6\u95f4\u3002
  • \u5220\u9664\u9876\u70b9\uff1a\u9700\u8981\u904d\u5386\u6574\u4e2a\u90bb\u63a5\u8868\uff0c\u5220\u9664\u5305\u542b\u6307\u5b9a\u9876\u70b9\u7684\u6240\u6709\u8fb9\uff0c\u4f7f\u7528 \\(O(n + m)\\) \u65f6\u95f4\u3002
  • \u521d\u59cb\u5316\uff1a\u9700\u8981\u5728\u90bb\u63a5\u8868\u4e2d\u5efa\u7acb \\(n\\) \u4e2a\u7ed3\u70b9\u548c \\(2m\\) \u6761\u8fb9\uff0c\u4f7f\u7528 \\(O(n + m)\\) \u65f6\u95f4\u3002
\u521d\u59cb\u5316\u90bb\u63a5\u8868\u6dfb\u52a0\u8fb9\u5220\u9664\u8fb9\u6dfb\u52a0\u9876\u70b9\u5220\u9664\u9876\u70b9

\u57fa\u4e8e\u90bb\u63a5\u8868\u5b9e\u73b0\u56fe\u7684\u4ee3\u7801\u5982\u4e0b\u6240\u793a\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig graph_adjacency_list.java
/* \u9876\u70b9\u7c7b */\nclass Vertex {\nint val;\npublic Vertex(int val) {\nthis.val = val;\n}\n}\n/* \u57fa\u4e8e\u90bb\u63a5\u8868\u5b9e\u73b0\u7684\u65e0\u5411\u56fe\u7c7b */\nclass GraphAdjList {\n// \u8bf7\u6ce8\u610f\uff0cvertices \u548c adjList \u4e2d\u5b58\u50a8\u7684\u90fd\u662f Vertex \u5bf9\u8c61\nMap<Vertex, Set<Vertex>> adjList; // \u90bb\u63a5\u8868\uff08\u4f7f\u7528\u54c8\u5e0c\u8868\u5b9e\u73b0\uff09\n/* \u6784\u9020\u51fd\u6570 */\npublic GraphAdjList(Vertex[][] edges) {\nthis.adjList = new HashMap<>();\n// \u6dfb\u52a0\u6240\u6709\u9876\u70b9\u548c\u8fb9\nfor (Vertex[] edge : edges) {\naddVertex(edge[0]);\naddVertex(edge[1]);\naddEdge(edge[0], edge[1]);\n}\n}\n/* \u83b7\u53d6\u9876\u70b9\u6570\u91cf */\npublic int size() {\nreturn adjList.size();\n}\n/* \u6dfb\u52a0\u8fb9 */\npublic void addEdge(Vertex vet1, Vertex vet2) {\nif (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\nthrow new IllegalArgumentException();\n// \u6dfb\u52a0\u8fb9 vet1 - vet2\nadjList.get(vet1).add(vet2);\nadjList.get(vet2).add(vet1);\n}\n/* \u5220\u9664\u8fb9 */\npublic void removeEdge(Vertex vet1, Vertex vet2) {\nif (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\nthrow new IllegalArgumentException();\n// \u5220\u9664\u8fb9 vet1 - vet2\nadjList.get(vet1).remove(vet2);\nadjList.get(vet2).remove(vet1);\n}\n/* \u6dfb\u52a0\u9876\u70b9 */\npublic void addVertex(Vertex vet) {\nif (adjList.containsKey(vet))\nreturn;\n// \u5728\u90bb\u63a5\u8868\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u65b0\u94fe\u8868\uff08\u5373 HashSet\uff09\nadjList.put(vet, new HashSet<>());\n}\n/* \u5220\u9664\u9876\u70b9 */\npublic void removeVertex(Vertex vet) {\nif (!adjList.containsKey(vet))\nthrow new IllegalArgumentException();\n// \u5728\u90bb\u63a5\u8868\u4e2d\u5220\u9664\u9876\u70b9 vet \u5bf9\u5e94\u7684\u94fe\u8868\uff08\u5373 HashSet\uff09\nadjList.remove(vet);\n// \u904d\u5386\u5176\u5b83\u9876\u70b9\u7684\u94fe\u8868\uff08\u5373 HashSet\uff09\uff0c\u5220\u9664\u6240\u6709\u5305\u542b vet \u7684\u8fb9\nfor (Set<Vertex> set : adjList.values()) {\nset.remove(vet);\n}\n}\n/* \u6253\u5370\u90bb\u63a5\u8868 */\npublic void print() {\nSystem.out.println(\"\u90bb\u63a5\u8868 =\");\nfor (Map.Entry<Vertex, Set<Vertex>> entry : adjList.entrySet()) {\nList<Integer> tmp = new ArrayList<>();\nfor (Vertex vertex : entry.getValue())\ntmp.add(vertex.val);\nSystem.out.println(entry.getKey().val + \": \" + tmp + \",\");\n}\n}\n}\n
graph_adjacency_list.cpp
\n
graph_adjacency_list.py
\n
graph_adjacency_list.go
/* \u9876\u70b9\u7c7b */\ntype vertex struct {\nval int\n}\nfunc newVertex(val int) vertex {\nreturn vertex{\nval: val,\n}\n}\n/* \u57fa\u4e8e\u90bb\u63a5\u8868\u5b9e\u73b0\u7684\u65e0\u5411\u56fe\u7c7b */\ntype graphAdjList struct {\n// \u8bf7\u6ce8\u610f\uff0cvertices \u548c adjList \u4e2d\u5b58\u50a8\u7684\u90fd\u662f Vertex \u5bf9\u8c61\n// \u90bb\u63a5\u8868\uff08\u4f7f\u7528\u54c8\u5e0c\u8868\u5b9e\u73b0\uff09, \u4f7f\u7528\u54c8\u5e0c\u8868\u6a21\u62df\u96c6\u5408\nadjList map[vertex]map[vertex]struct{}\n}\n/* \u6784\u9020\u51fd\u6570 */\nfunc newGraphAdjList(edges [][]vertex) *graphAdjList {\ng := &graphAdjList{\nadjList: make(map[vertex]map[vertex]struct{}),\n}\n// \u6dfb\u52a0\u6240\u6709\u9876\u70b9\u548c\u8fb9\nfor _, edge := range edges {\ng.addVertex(edge[0])\ng.addVertex(edge[1])\ng.addEdge(edge[0], edge[1])\n}\nreturn g\n}\n/* \u83b7\u53d6\u9876\u70b9\u6570\u91cf */\nfunc (g *graphAdjList) size() int {\nreturn len(g.adjList)\n}\n/* \u6dfb\u52a0\u8fb9 */\nfunc (g *graphAdjList) addEdge(vet1 vertex, vet2 vertex) {\n_, ok1 := g.adjList[vet1]\n_, ok2 := g.adjList[vet2]\nif !ok1 || !ok2 || vet1 == vet2 {\npanic(\"error\")\n}\n// \u6dfb\u52a0\u8fb9 vet1 - vet2, \u6dfb\u52a0\u533f\u540d struct{},\ng.adjList[vet1][vet2] = struct{}{}\ng.adjList[vet2][vet1] = struct{}{}\n}\n/* \u5220\u9664\u8fb9 */\nfunc (g *graphAdjList) removeEdge(vet1 vertex, vet2 vertex) {\n_, ok1 := g.adjList[vet1]\n_, ok2 := g.adjList[vet2]\nif !ok1 || !ok2 || vet1 == vet2 {\npanic(\"error\")\n}\n// \u5220\u9664\u8fb9 vet1 - vet2, \u501f\u52a9 delete \u6765\u5220\u9664 map \u4e2d\u7684\u952e\ndelete(g.adjList[vet1], vet2)\ndelete(g.adjList[vet2], vet1)\n}\n/* \u6dfb\u52a0\u9876\u70b9 */\nfunc (g *graphAdjList) addVertex(vet vertex) {\n_, ok := g.adjList[vet]\nif ok {\nreturn\n}\n// \u5728\u90bb\u63a5\u8868\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u65b0\u94fe\u8868\uff08\u5373 set\uff09\ng.adjList[vet] = make(map[vertex]struct{})\n}\n/* \u5220\u9664\u9876\u70b9 */\nfunc (g *graphAdjList) removeVertex(vet vertex) {\n_, ok := g.adjList[vet]\nif !ok {\npanic(\"error\")\n}\n// \u5728\u90bb\u63a5\u8868\u4e2d\u5220\u9664\u9876\u70b9 vet \u5bf9\u5e94\u7684\u94fe\u8868\ndelete(g.adjList, vet)\n// \u904d\u5386\u5176\u5b83\u9876\u70b9\u7684\u94fe\u8868\uff08\u5373 Set\uff09\uff0c\u5220\u9664\u6240\u6709\u5305\u542b vet \u7684\u8fb9\nfor _, set := range g.adjList {\n// \u64cd\u4f5c\ndelete(set, vet)\n}\n}\n
graph_adjacency_list.js
\n
graph_adjacency_list.ts
\n
graph_adjacency_list.c
\n
graph_adjacency_list.cs
\n
graph_adjacency_list.swift
/* \u9876\u70b9\u7c7b */\nclass Vertex: Hashable {\nvar val: Int\ninit(val: Int) {\nself.val = val\n}\nstatic func == (lhs: Vertex, rhs: Vertex) -> Bool {\nlhs.val == rhs.val\n}\nfunc hash(into hasher: inout Hasher) {\nhasher.combine(val)\n}\n}\n/* \u57fa\u4e8e\u90bb\u63a5\u8868\u5b9e\u73b0\u7684\u65e0\u5411\u56fe\u7c7b */\nclass GraphAdjList {\n// \u8bf7\u6ce8\u610f\uff0cvertices \u548c adjList \u4e2d\u5b58\u50a8\u7684\u90fd\u662f Vertex \u5bf9\u8c61\nprivate var adjList: [Vertex: Set<Vertex>] // \u90bb\u63a5\u8868\uff08\u4f7f\u7528\u54c8\u5e0c\u8868\u5b9e\u73b0\uff09\ninit(edges: [[Vertex]]) {\nadjList = [:]\n// \u6dfb\u52a0\u6240\u6709\u9876\u70b9\u548c\u8fb9\nfor edge in edges {\naddVertex(vet: edge[0])\naddVertex(vet: edge[1])\naddEdge(vet1: edge[0], vet2: edge[1])\n}\n}\n/* \u83b7\u53d6\u9876\u70b9\u6570\u91cf */\nfunc size() -> Int {\nadjList.count\n}\n/* \u6dfb\u52a0\u8fb9 */\nfunc addEdge(vet1: Vertex, vet2: Vertex) {\nif adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\nfatalError(\"\u53c2\u6570\u9519\u8bef\")\n}\n// \u6dfb\u52a0\u8fb9 vet1 - vet2\nadjList[vet1]?.insert(vet2)\nadjList[vet2]?.insert(vet1)\n}\n/* \u5220\u9664\u8fb9 */\nfunc removeEdge(vet1: Vertex, vet2: Vertex) {\nif adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\nfatalError(\"\u53c2\u6570\u9519\u8bef\")\n}\n// \u5220\u9664\u8fb9 vet1 - vet2\nadjList[vet1]?.remove(vet2)\nadjList[vet2]?.remove(vet1)\n}\n/* \u6dfb\u52a0\u9876\u70b9 */\nfunc addVertex(vet: Vertex) {\nif adjList[vet] != nil {\nreturn\n}\n// \u5728\u90bb\u63a5\u8868\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u65b0\u94fe\u8868\uff08\u5373 HashSet\uff09\nadjList[vet] = []\n}\n/* \u5220\u9664\u9876\u70b9 */\nfunc removeVertex(vet: Vertex) {\nif adjList[vet] == nil {\nfatalError(\"\u53c2\u6570\u9519\u8bef\")\n}\n// \u5728\u90bb\u63a5\u8868\u4e2d\u5220\u9664\u9876\u70b9 vet \u5bf9\u5e94\u7684\u94fe\u8868\uff08\u5373 HashSet\uff09\nadjList.removeValue(forKey: vet)\n// \u904d\u5386\u5176\u5b83\u9876\u70b9\u7684\u94fe\u8868\uff08\u5373 HashSet\uff09\uff0c\u5220\u9664\u6240\u6709\u5305\u542b vet \u7684\u8fb9\nfor key in adjList.keys {\nadjList[key]?.remove(vet)\n}\n}\n}\n
graph_adjacency_list.zig
\n
"},{"location":"chapter_graph/graph_operations/#923","title":"9.2.3. \u6548\u7387\u5bf9\u6bd4","text":"

\u8bbe\u56fe\u4e2d\u5171\u6709 \\(n\\) \u4e2a\u9876\u70b9\u548c \\(m\\) \u6761\u8fb9\uff0c\u4e0b\u8868\u4e3a\u90bb\u63a5\u77e9\u9635\u548c\u90bb\u63a5\u8868\u7684\u65f6\u95f4\u548c\u7a7a\u95f4\u6548\u7387\u5bf9\u6bd4\u3002

\u90bb\u63a5\u77e9\u9635 \u90bb\u63a5\u8868\uff08\u94fe\u8868\uff09 \u90bb\u63a5\u8868\uff08\u54c8\u5e0c\u8868\uff09 \u5224\u65ad\u662f\u5426\u90bb\u63a5 \\(O(1)\\) \\(O(m)\\) \\(O(1)\\) \u6dfb\u52a0\u8fb9 \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) \u5220\u9664\u8fb9 \\(O(1)\\) \\(O(m)\\) \\(O(1)\\) \u6dfb\u52a0\u9876\u70b9 \\(O(n)\\) \\(O(1)\\) \\(O(1)\\) \u5220\u9664\u9876\u70b9 \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n)\\) \u5185\u5b58\u7a7a\u95f4\u5360\u7528 \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n + m)\\)

\u89c2\u5bdf\u4e0a\u8868\uff0c\u8c8c\u4f3c\u90bb\u63a5\u8868\uff08\u54c8\u5e0c\u8868\uff09\u7684\u65f6\u95f4\u4e0e\u7a7a\u95f4\u6548\u7387\u6700\u4f18\u3002\u4f46\u5b9e\u9645\u4e0a\uff0c\u5728\u90bb\u63a5\u77e9\u9635\u4e2d\u64cd\u4f5c\u8fb9\u7684\u6548\u7387\u66f4\u9ad8\uff0c\u53ea\u9700\u8981\u4e00\u6b21\u6570\u7ec4\u8bbf\u95ee\u6216\u8d4b\u503c\u64cd\u4f5c\u5373\u53ef\u3002\u603b\u7ed3\u4ee5\u4e0a\uff0c\u90bb\u63a5\u77e9\u9635\u4f53\u73b0\u201c\u4ee5\u7a7a\u95f4\u6362\u65f6\u95f4\u201d\uff0c\u90bb\u63a5\u8868\u4f53\u73b0\u201c\u4ee5\u65f6\u95f4\u6362\u7a7a\u95f4\u201d\u3002

"},{"location":"chapter_hashing/hash_collision/","title":"6.2. \u54c8\u5e0c\u51b2\u7a81","text":"

\u7406\u60f3\u60c5\u51b5\u4e0b\uff0c\u54c8\u5e0c\u51fd\u6570\u5e94\u8be5\u4e3a\u6bcf\u4e2a\u8f93\u5165\u4ea7\u751f\u552f\u4e00\u7684\u8f93\u51fa\uff0c\u4f7f\u5f97 key \u548c value \u4e00\u4e00\u5bf9\u5e94\u3002\u800c\u5b9e\u9645\u4e0a\uff0c\u5f80\u5f80\u5b58\u5728\u5411\u54c8\u5e0c\u51fd\u6570\u8f93\u5165\u4e0d\u540c\u7684 key \u800c\u4ea7\u751f\u76f8\u540c\u8f93\u51fa\u7684\u60c5\u51b5\uff0c\u8fd9\u79cd\u60c5\u51b5\u88ab\u79f0\u4e3a\u300c\u54c8\u5e0c\u51b2\u7a81 Hash Collision\u300d\u3002\u54c8\u5e0c\u51b2\u7a81\u4f1a\u5bfc\u81f4\u67e5\u8be2\u7ed3\u679c\u9519\u8bef\uff0c\u4ece\u800c\u4e25\u91cd\u5f71\u54cd\u54c8\u5e0c\u8868\u7684\u53ef\u7528\u6027\u3002

\u90a3\u4e48\uff0c\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u54c8\u5e0c\u51b2\u7a81\u5462\uff1f\u672c\u8d28\u4e0a\u770b\uff0c\u7531\u4e8e\u54c8\u5e0c\u51fd\u6570\u7684\u8f93\u5165\u7a7a\u95f4\u5f80\u5f80\u8fdc\u5927\u4e8e\u8f93\u51fa\u7a7a\u95f4\uff0c\u56e0\u6b64\u4e0d\u53ef\u907f\u514d\u5730\u4f1a\u51fa\u73b0\u591a\u4e2a\u8f93\u5165\u4ea7\u751f\u76f8\u540c\u8f93\u51fa\u7684\u60c5\u51b5\uff0c\u5373\u4e3a\u54c8\u5e0c\u51b2\u7a81\u3002\u6bd4\u5982\uff0c\u8f93\u5165\u7a7a\u95f4\u662f\u5168\u4f53\u6574\u6570\uff0c\u8f93\u51fa\u7a7a\u95f4\u662f\u4e00\u4e2a\u56fa\u5b9a\u5927\u5c0f\u7684\u6876\uff08\u6570\u7ec4\uff09\u7684\u7d22\u5f15\u8303\u56f4\uff0c\u90a3\u4e48\u5fc5\u5b9a\u4f1a\u6709\u591a\u4e2a\u6574\u6570\u540c\u65f6\u6620\u5c04\u5230\u4e00\u4e2a\u6876\u7d22\u5f15\u3002

\u4e3a\u4e86\u7f13\u89e3\u54c8\u5e0c\u51b2\u7a81\uff0c\u4e00\u65b9\u9762\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u300c\u54c8\u5e0c\u8868\u6269\u5bb9\u300d\u6765\u51cf\u5c0f\u51b2\u7a81\u6982\u7387\u3002\u6781\u7aef\u60c5\u51b5\u4e0b\uff0c\u5f53\u8f93\u5165\u7a7a\u95f4\u548c\u8f93\u51fa\u7a7a\u95f4\u5927\u5c0f\u76f8\u7b49\u65f6\uff0c\u54c8\u5e0c\u8868\u5c31\u7b49\u4ef7\u4e8e\u6570\u7ec4\u4e86\uff0c\u53ef\u8c13\u201c\u5927\u529b\u51fa\u5947\u8ff9\u201d\u3002

\u53e6\u4e00\u65b9\u9762\uff0c\u8003\u8651\u901a\u8fc7\u4f18\u5316\u6570\u636e\u7ed3\u6784\u4ee5\u7f13\u89e3\u54c8\u5e0c\u51b2\u7a81\uff0c\u5e38\u89c1\u7684\u65b9\u6cd5\u6709\u300c\u94fe\u5f0f\u5730\u5740\u300d\u548c\u300c\u5f00\u653e\u5bfb\u5740\u300d\u3002

"},{"location":"chapter_hashing/hash_collision/#621","title":"6.2.1. \u54c8\u5e0c\u8868\u6269\u5bb9","text":"

\u300c\u8d1f\u8f7d\u56e0\u5b50 Load Factor\u300d\u5b9a\u4e49\u4e3a \u54c8\u5e0c\u8868\u4e2d\u5143\u7d20\u6570\u91cf\u9664\u4ee5\u6876\u69fd\u6570\u91cf\uff08\u5373\u6570\u7ec4\u5927\u5c0f\uff09\uff0c\u4ee3\u8868\u54c8\u5e0c\u51b2\u7a81\u7684\u4e25\u91cd\u7a0b\u5ea6\u3002

\u8d1f\u8f7d\u56e0\u5b50\u5e38\u7528\u4f5c\u54c8\u5e0c\u8868\u6269\u5bb9\u7684\u89e6\u53d1\u6761\u4ef6\u3002\u6bd4\u5982\u5728 Java \u4e2d\uff0c\u5f53\u8d1f\u8f7d\u56e0\u5b50 \\(> 0.75\\) \u65f6\u5219\u89e6\u53d1\u6269\u5bb9\uff0c\u5c06 HashMap \u5927\u5c0f\u6269\u5145\u81f3\u539f\u5148\u7684 \\(2\\) \u500d\u3002

\u4e0e\u6570\u7ec4\u6269\u5bb9\u7c7b\u4f3c\uff0c\u54c8\u5e0c\u8868\u6269\u5bb9\u64cd\u4f5c\u7684\u5f00\u9500\u5f88\u5927\uff0c\u56e0\u4e3a\u9700\u8981\u5c06\u6240\u6709\u952e\u503c\u5bf9\u4ece\u539f\u54c8\u5e0c\u8868\u4f9d\u6b21\u79fb\u52a8\u81f3\u65b0\u54c8\u5e0c\u8868\u3002

"},{"location":"chapter_hashing/hash_collision/#622","title":"6.2.2. \u94fe\u5f0f\u5730\u5740","text":"

\u5728\u539f\u59cb\u54c8\u5e0c\u8868\u4e2d\uff0c\u6876\u5185\u7684\u6bcf\u4e2a\u5730\u5740\u53ea\u80fd\u5b58\u50a8\u4e00\u4e2a\u5143\u7d20\uff08\u5373\u952e\u503c\u5bf9\uff09\u3002\u8003\u8651\u5c06\u5355\u4e2a\u5143\u7d20\u8f6c\u5316\u6210\u4e00\u4e2a\u94fe\u8868\uff0c\u5c06\u6240\u6709\u51b2\u7a81\u5143\u7d20\u90fd\u5b58\u50a8\u5728\u4e00\u4e2a\u94fe\u8868\u4e2d\u3002

\u94fe\u5f0f\u5730\u5740\u4e0b\uff0c\u54c8\u5e0c\u8868\u64cd\u4f5c\u65b9\u6cd5\u4e3a\uff1a

  • \u67e5\u8be2\u5143\u7d20\uff1a\u5148\u5c06 key \u8f93\u5165\u5230\u54c8\u5e0c\u51fd\u6570\u5f97\u5230\u6876\u5185\u7d22\u5f15\uff0c\u5373\u53ef\u8bbf\u95ee\u94fe\u8868\u5934\u7ed3\u70b9\uff0c\u518d\u901a\u8fc7\u904d\u5386\u94fe\u8868\u67e5\u627e\u5bf9\u5e94 value \u3002
  • \u6dfb\u52a0\u5143\u7d20\uff1a\u5148\u901a\u8fc7\u54c8\u5e0c\u51fd\u6570\u8bbf\u95ee\u94fe\u8868\u5934\u90e8\uff0c\u518d\u5c06\u7ed3\u70b9\uff08\u5373\u952e\u503c\u5bf9\uff09\u6dfb\u52a0\u5230\u94fe\u8868\u5934\u90e8\u5373\u53ef\u3002
  • \u5220\u9664\u5143\u7d20\uff1a\u540c\u6837\u5148\u6839\u636e\u54c8\u5e0c\u51fd\u6570\u7ed3\u679c\u8bbf\u95ee\u94fe\u8868\u5934\u90e8\uff0c\u518d\u904d\u5386\u94fe\u8868\u67e5\u627e\u5bf9\u5e94\u7ed3\u70b9\uff0c\u5220\u9664\u4e4b\u5373\u53ef\u3002

\u94fe\u5f0f\u5730\u5740\u867d\u7136\u89e3\u51b3\u4e86\u54c8\u5e0c\u51b2\u7a81\u95ee\u9898\uff0c\u4f46\u4ecd\u5b58\u5728\u5c40\u9650\u6027\uff0c\u5305\u62ec\uff1a

  • \u5360\u7528\u7a7a\u95f4\u53d8\u5927\uff0c\u56e0\u4e3a\u94fe\u8868\u6216\u4e8c\u53c9\u6811\u5305\u542b\u7ed3\u70b9\u6307\u9488\uff0c\u76f8\u6bd4\u4e8e\u6570\u7ec4\u66f4\u52a0\u8017\u8d39\u5185\u5b58\u7a7a\u95f4\uff1b
  • \u67e5\u8be2\u6548\u7387\u964d\u4f4e\uff0c\u56e0\u4e3a\u9700\u8981\u7ebf\u6027\u904d\u5386\u94fe\u8868\u6765\u67e5\u627e\u5bf9\u5e94\u5143\u7d20\uff1b

\u4e3a\u4e86\u7f13\u89e3\u65f6\u95f4\u6548\u7387\u95ee\u9898\uff0c\u53ef\u4ee5\u628a\u300c\u94fe\u8868\u300d\u8f6c\u5316\u4e3a\u300cAVL \u6811\u300d\u6216\u300c\u7ea2\u9ed1\u6811\u300d\uff0c\u5c06\u67e5\u8be2\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4f18\u5316\u81f3 \\(O(\\log n)\\) \u3002

"},{"location":"chapter_hashing/hash_collision/#623","title":"6.2.3. \u5f00\u653e\u5bfb\u5740","text":"

\u300c\u5f00\u653e\u5bfb\u5740\u300d\u4e0d\u5f15\u5165\u989d\u5916\u6570\u636e\u7ed3\u6784\uff0c\u800c\u662f\u901a\u8fc7\u201c\u591a\u6b21\u63a2\u6d4b\u201d\u6765\u89e3\u51b3\u54c8\u5e0c\u51b2\u7a81\u3002\u6839\u636e\u63a2\u6d4b\u65b9\u6cd5\u7684\u4e0d\u540c\uff0c\u4e3b\u8981\u5206\u4e3a \u7ebf\u6027\u63a2\u6d4b\u3001\u5e73\u65b9\u63a2\u6d4b\u3001\u591a\u6b21\u54c8\u5e0c\u3002

"},{"location":"chapter_hashing/hash_collision/#_1","title":"\u7ebf\u6027\u63a2\u6d4b","text":"

\u300c\u7ebf\u6027\u63a2\u6d4b\u300d\u4f7f\u7528\u56fa\u5b9a\u6b65\u957f\u7684\u7ebf\u6027\u67e5\u627e\u6765\u89e3\u51b3\u54c8\u5e0c\u51b2\u7a81\u3002

\u63d2\u5165\u5143\u7d20\uff1a\u5982\u679c\u51fa\u73b0\u54c8\u5e0c\u51b2\u7a81\uff0c\u5219\u4ece\u51b2\u7a81\u4f4d\u7f6e\u5411\u540e\u7ebf\u6027\u904d\u5386\uff08\u6b65\u957f\u4e00\u822c\u53d6 1 \uff09\uff0c\u76f4\u5230\u627e\u5230\u4e00\u4e2a\u7a7a\u4f4d\uff0c\u5219\u5c06\u5143\u7d20\u63d2\u5165\u5230\u8be5\u7a7a\u4f4d\u4e2d\u3002

\u67e5\u627e\u5143\u7d20\uff1a\u82e5\u51fa\u73b0\u54c8\u5e0c\u51b2\u7a81\uff0c\u5219\u4f7f\u7528\u76f8\u540c\u6b65\u957f\u6267\u884c\u7ebf\u6027\u67e5\u627e\uff0c\u4f1a\u9047\u5230\u4e24\u79cd\u60c5\u51b5\uff1a

  1. \u627e\u5230\u5bf9\u5e94\u5143\u7d20\uff0c\u8fd4\u56de value \u5373\u53ef\uff1b
  2. \u82e5\u9047\u5230\u7a7a\u4f4d\uff0c\u5219\u8bf4\u660e\u67e5\u627e\u952e\u503c\u5bf9\u4e0d\u5728\u54c8\u5e0c\u8868\u4e2d\uff1b

\u7ebf\u6027\u63a2\u6d4b\u5b58\u5728\u4ee5\u4e0b\u7f3a\u9677\uff1a

  • \u4e0d\u80fd\u76f4\u63a5\u5220\u9664\u5143\u7d20\u3002\u5220\u9664\u5143\u7d20\u4f1a\u5bfc\u81f4\u6876\u5185\u51fa\u73b0\u4e00\u4e2a\u7a7a\u4f4d\uff0c\u5728\u67e5\u627e\u5176\u4ed6\u5143\u7d20\u65f6\uff0c\u8be5\u7a7a\u4f4d\u6709\u53ef\u80fd\u5bfc\u81f4\u7a0b\u5e8f\u8ba4\u4e3a\u5143\u7d20\u4e0d\u5b58\u5728\uff08\u5373\u4e0a\u8ff0\u7b2c 2. \u79cd\u60c5\u51b5\uff09\u3002\u56e0\u6b64\u9700\u8981\u501f\u52a9\u4e00\u4e2a\u6807\u5fd7\u4f4d\u6765\u6807\u8bb0\u5220\u9664\u5143\u7d20\u3002
  • \u5bb9\u6613\u4ea7\u751f\u805a\u96c6\u3002\u6876\u5185\u88ab\u5360\u7528\u7684\u8fde\u7eed\u4f4d\u7f6e\u8d8a\u957f\uff0c\u8fd9\u4e9b\u8fde\u7eed\u4f4d\u7f6e\u53d1\u751f\u54c8\u5e0c\u51b2\u7a81\u7684\u53ef\u80fd\u6027\u8d8a\u5927\uff0c\u4ece\u800c\u8fdb\u4e00\u6b65\u4fc3\u8fdb\u8fd9\u4e00\u4f4d\u7f6e\u7684\u201c\u805a\u5806\u751f\u957f\u201d\uff0c\u6700\u7ec8\u5bfc\u81f4\u589e\u5220\u67e5\u6539\u64cd\u4f5c\u6548\u7387\u7684\u52a3\u5316\u3002
"},{"location":"chapter_hashing/hash_collision/#_2","title":"\u591a\u6b21\u54c8\u5e0c","text":"

\u987e\u540d\u601d\u4e49\uff0c\u300c\u591a\u6b21\u54c8\u5e0c\u300d\u7684\u601d\u8def\u662f\u4f7f\u7528\u591a\u4e2a\u54c8\u5e0c\u51fd\u6570 \\(f_1(x)\\) , \\(f_2(x)\\) , \\(f_3(x)\\) , \\(\\cdots\\) \u8fdb\u884c\u63a2\u6d4b\u3002

\u63d2\u5165\u5143\u7d20\uff1a\u82e5\u54c8\u5e0c\u51fd\u6570 \\(f_1(x)\\) \u51fa\u73b0\u51b2\u7a81\uff0c\u5219\u5c1d\u8bd5 \\(f_2(x)\\) \uff0c\u4ee5\u6b64\u7c7b\u63a8\u2026\u2026\u76f4\u5230\u627e\u5230\u7a7a\u4f4d\u540e\u63d2\u5165\u5143\u7d20\u3002

\u67e5\u627e\u5143\u7d20\uff1a\u4ee5\u76f8\u540c\u7684\u54c8\u5e0c\u51fd\u6570\u987a\u5e8f\u67e5\u627e\uff0c\u5b58\u5728\u4e24\u79cd\u60c5\u51b5\uff1a

  1. \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u5219\u8fd4\u56de\u4e4b\uff1b
  2. \u5230\u7a7a\u4f4d\u6216\u5df2\u5c1d\u8bd5\u6240\u6709\u54c8\u5e0c\u51fd\u6570\uff0c\u8bf4\u660e\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64\u5143\u7d20\uff1b

\u76f8\u6bd4\u4e8e\u300c\u7ebf\u6027\u63a2\u6d4b\u300d\uff0c\u300c\u591a\u6b21\u54c8\u5e0c\u300d\u65b9\u6cd5\u66f4\u4e0d\u5bb9\u6613\u4ea7\u751f\u805a\u96c6\uff0c\u4ee3\u4ef7\u662f\u591a\u4e2a\u54c8\u5e0c\u51fd\u6570\u589e\u52a0\u4e86\u989d\u5916\u8ba1\u7b97\u91cf\u3002

\u5de5\u4e1a\u754c\u65b9\u6848

Java \u91c7\u7528\u300c\u94fe\u5f0f\u5730\u5740\u300d\u3002\u5728 JDK 1.8 \u4e4b\u540e\uff0cHashMap \u5185\u6570\u7ec4\u957f\u5ea6\u5927\u4e8e 64 \u65f6\uff0c\u957f\u5ea6\u5927\u4e8e 8 \u7684\u94fe\u8868\u4f1a\u88ab\u8f6c\u5316\u4e3a\u300c\u7ea2\u9ed1\u6811\u300d\uff0c\u4ee5\u63d0\u5347\u67e5\u627e\u6027\u80fd\u3002

Python \u91c7\u7528\u300c\u5f00\u653e\u5bfb\u5740\u300d\u3002\u5b57\u5178 dict \u4f7f\u7528\u4f2a\u968f\u673a\u6570\u8fdb\u884c\u63a2\u6d4b\u3002

"},{"location":"chapter_hashing/hash_map/","title":"6.1. \u54c8\u5e0c\u8868","text":"

\u54c8\u5e0c\u8868\u901a\u8fc7\u5efa\u7acb\u300c\u952e key\u300d\u548c\u300c\u503c value\u300d\u4e4b\u95f4\u7684\u6620\u5c04\uff0c\u5b9e\u73b0\u9ad8\u6548\u7684\u5143\u7d20\u67e5\u627e\u3002\u5177\u4f53\u5730\uff0c\u8f93\u5165\u4e00\u4e2a key \uff0c\u5728\u54c8\u5e0c\u8868\u4e2d\u67e5\u8be2\u5e76\u83b7\u53d6 value \uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(1)\\) \u3002

\u4f8b\u5982\uff0c\u7ed9\u5b9a\u4e00\u4e2a\u5305\u542b \\(n\\) \u4e2a\u5b66\u751f\u7684\u6570\u636e\u5e93\uff0c\u6bcf\u4e2a\u5b66\u751f\u6709\u201c\u59d3\u540d name \u201d\u548c\u201c\u5b66\u53f7 id \u201d\u4e24\u9879\u6570\u636e\uff0c\u5e0c\u671b\u5b9e\u73b0\u4e00\u4e2a\u67e5\u8be2\u529f\u80fd\uff1a\u8f93\u5165\u4e00\u4e2a\u5b66\u53f7\uff0c\u8fd4\u56de\u5bf9\u5e94\u7684\u59d3\u540d\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528\u54c8\u5e0c\u8868\u5b9e\u73b0\u3002

Fig. \u54c8\u5e0c\u8868\u62bd\u8c61\u8868\u793a

"},{"location":"chapter_hashing/hash_map/#611","title":"6.1.1. \u54c8\u5e0c\u8868\u6548\u7387","text":"

\u9664\u4e86\u54c8\u5e0c\u8868\u4e4b\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u6570\u636e\u7ed3\u6784\u6765\u5b9e\u73b0\u4e0a\u8ff0\u67e5\u8be2\u529f\u80fd\uff1a

  1. \u65e0\u5e8f\u6570\u7ec4\uff1a\u6bcf\u4e2a\u5143\u7d20\u4e3a [\u5b66\u53f7, \u59d3\u540d] \uff1b
  2. \u6709\u5e8f\u6570\u7ec4\uff1a\u5c06 1. \u4e2d\u7684\u6570\u7ec4\u6309\u7167\u5b66\u53f7\u4ece\u5c0f\u5230\u5927\u6392\u5e8f\uff1b
  3. \u94fe\u8868\uff1a\u6bcf\u4e2a\u7ed3\u70b9\u7684\u503c\u4e3a [\u5b66\u53f7, \u59d3\u540d] \uff1b
  4. \u4e8c\u53c9\u641c\u7d22\u6811\uff1a\u6bcf\u4e2a\u7ed3\u70b9\u7684\u503c\u4e3a [\u5b66\u53f7, \u59d3\u540d] \uff0c\u6839\u636e\u5b66\u53f7\u5927\u5c0f\u6765\u6784\u5efa\u6811\uff1b

\u4f7f\u7528\u4e0a\u8ff0\u65b9\u6cd5\uff0c\u5404\u9879\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u5982\u4e0b\u8868\u6240\u793a\uff08\u5728\u6b64\u4e0d\u505a\u8d58\u8ff0\uff0c\u8be6\u89e3\u53ef\u89c1 \u4e8c\u53c9\u641c\u7d22\u6811\u7ae0\u8282\uff09\u3002\u65e0\u8bba\u662f\u67e5\u627e\u5143\u7d20\u3001\u8fd8\u662f\u589e\u5220\u5143\u7d20\uff0c\u54c8\u5e0c\u8868\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u662f \\(O(1)\\) \uff0c\u5168\u9762\u80dc\u51fa\uff01

\u65e0\u5e8f\u6570\u7ec4 \u6709\u5e8f\u6570\u7ec4 \u94fe\u8868 \u4e8c\u53c9\u641c\u7d22\u6811 \u54c8\u5e0c\u8868 \u67e5\u627e\u5143\u7d20 \\(O(n)\\) \\(O(\\log n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) \u63d2\u5165\u5143\u7d20 \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) \\(O(\\log n)\\) \\(O(1)\\) \u5220\u9664\u5143\u7d20 \\(O(n)\\) \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\)"},{"location":"chapter_hashing/hash_map/#612","title":"6.1.2. \u54c8\u5e0c\u8868\u5e38\u7528\u64cd\u4f5c","text":"

\u54c8\u5e0c\u8868\u7684\u57fa\u672c\u64cd\u4f5c\u5305\u62ec \u521d\u59cb\u5316\u3001\u67e5\u8be2\u64cd\u4f5c\u3001\u6dfb\u52a0\u4e0e\u5220\u9664\u952e\u503c\u5bf9\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig hash_map.java
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nMap<Integer, String> map = new HashMap<>();\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmap.put(12836, \"\u5c0f\u54c8\");   map.put(15937, \"\u5c0f\u5570\");   map.put(16750, \"\u5c0f\u7b97\");   map.put(13276, \"\u5c0f\u6cd5\");\nmap.put(10583, \"\u5c0f\u9e2d\");\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nString name = map.get(15937);\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmap.remove(10583);\n
hash_map.cpp
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nunordered_map<int, string> map;\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmap[12836] = \"\u5c0f\u54c8\";\nmap[15937] = \"\u5c0f\u5570\";\nmap[16750] = \"\u5c0f\u7b97\";\nmap[13276] = \"\u5c0f\u6cd5\";\nmap[10583] = \"\u5c0f\u9e2d\";\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nstring name = map[15937];\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmap.erase(10583);\n
hash_map.py
\"\"\" \u521d\u59cb\u5316\u54c8\u5e0c\u8868 \"\"\"\nmapp = {}\n\"\"\" \u6dfb\u52a0\u64cd\u4f5c \"\"\"\n# \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmapp[12836] = \"\u5c0f\u54c8\"\nmapp[15937] = \"\u5c0f\u5570\"\nmapp[16750] = \"\u5c0f\u7b97\"\nmapp[13276] = \"\u5c0f\u6cd5\"\nmapp[10583] = \"\u5c0f\u9e2d\"\n\"\"\" \u67e5\u8be2\u64cd\u4f5c \"\"\"\n# \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nname = mapp[15937]\n\"\"\" \u5220\u9664\u64cd\u4f5c \"\"\"\n# \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmapp.pop(10583)\n
hash_map.go
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nmapp := make(map[int]string)\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmapp[12836] = \"\u5c0f\u54c8\"\nmapp[15937] = \"\u5c0f\u5570\"\nmapp[16750] = \"\u5c0f\u7b97\"\nmapp[13276] = \"\u5c0f\u6cd5\"\nmapp[10583] = \"\u5c0f\u9e2d\"\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nname := mapp[15937]\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\ndelete(mapp, 10583)\n
hash_map.js
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nconst map = new ArrayHashMap();\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmap.set(12836, '\u5c0f\u54c8');\nmap.set(15937, '\u5c0f\u5570');\nmap.set(16750, '\u5c0f\u7b97');\nmap.set(13276, '\u5c0f\u6cd5');\nmap.set(10583, '\u5c0f\u9e2d');\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nlet name = map.get(15937);\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmap.delete(10583);\n
hash_map.ts
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nconst map = new Map<number, string>();\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmap.set(12836, '\u5c0f\u54c8');\nmap.set(15937, '\u5c0f\u5570');\nmap.set(16750, '\u5c0f\u7b97');\nmap.set(13276, '\u5c0f\u6cd5');\nmap.set(10583, '\u5c0f\u9e2d');\nconsole.info('\\n\u6dfb\u52a0\u5b8c\u6210\u540e\uff0c\u54c8\u5e0c\u8868\u4e3a\\nKey -> Value');\nconsole.info(map);\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nlet name = map.get(15937);\nconsole.info('\\n\u8f93\u5165\u5b66\u53f7 15937 \uff0c\u67e5\u8be2\u5230\u59d3\u540d ' + name);\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmap.delete(10583);\nconsole.info('\\n\u5220\u9664 10583 \u540e\uff0c\u54c8\u5e0c\u8868\u4e3a\\nKey -> Value');\nconsole.info(map);\n
hash_map.c
\n
hash_map.cs
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nDictionary<int, String> map = new ();\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmap.Add(12836, \"\u5c0f\u54c8\");\nmap.Add(15937, \"\u5c0f\u5570\");\nmap.Add(16750, \"\u5c0f\u7b97\");\nmap.Add(13276, \"\u5c0f\u6cd5\");\nmap.Add(10583, \"\u5c0f\u9e2d\");\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nString name = map[15937];\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmap.Remove(10583);\n
hash_map.swift
/* \u521d\u59cb\u5316\u54c8\u5e0c\u8868 */\nvar map: [Int: String] = [:]\n/* \u6dfb\u52a0\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u6dfb\u52a0\u952e\u503c\u5bf9 (key, value)\nmap[12836] = \"\u5c0f\u54c8\"\nmap[15937] = \"\u5c0f\u5570\"\nmap[16750] = \"\u5c0f\u7b97\"\nmap[13276] = \"\u5c0f\u6cd5\"\nmap[10583] = \"\u5c0f\u9e2d\"\n/* \u67e5\u8be2\u64cd\u4f5c */\n// \u5411\u54c8\u5e0c\u8868\u8f93\u5165\u952e key \uff0c\u5f97\u5230\u503c value\nlet name = map[15937]!\n/* \u5220\u9664\u64cd\u4f5c */\n// \u5728\u54c8\u5e0c\u8868\u4e2d\u5220\u9664\u952e\u503c\u5bf9 (key, value)\nmap.removeValue(forKey: 10583)\n
hash_map.zig
\n

\u904d\u5386\u54c8\u5e0c\u8868\u6709\u4e09\u79cd\u65b9\u5f0f\uff0c\u5373 \u904d\u5386\u952e\u503c\u5bf9\u3001\u904d\u5386\u952e\u3001\u904d\u5386\u503c\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig hash_map.java
/* \u904d\u5386\u54c8\u5e0c\u8868 */\n// \u904d\u5386\u952e\u503c\u5bf9 key->value\nfor (Map.Entry <Integer, String> kv: map.entrySet()) {\nSystem.out.println(kv.getKey() + \" -> \" + kv.getValue());\n}\n// \u5355\u72ec\u904d\u5386\u952e key\nfor (int key: map.keySet()) {\nSystem.out.println(key);\n}\n// \u5355\u72ec\u904d\u5386\u503c value\nfor (String val: map.values()) {\nSystem.out.println(val);\n}\n
hash_map.cpp
/* \u904d\u5386\u54c8\u5e0c\u8868 */\n// \u904d\u5386\u952e\u503c\u5bf9 key->value\nfor (auto kv: map) {\ncout << kv.first << \" -> \" << kv.second << endl;\n}\n// \u5355\u72ec\u904d\u5386\u952e key\nfor (auto key: map) {\ncout << key.first << endl;\n}\n// \u5355\u72ec\u904d\u5386\u503c value\nfor (auto val: map) {\ncout << val.second << endl;\n}\n
hash_map.py
\"\"\" \u904d\u5386\u54c8\u5e0c\u8868 \"\"\"\n# \u904d\u5386\u952e\u503c\u5bf9 key->value\nfor key, value in mapp.items():\nprint(key, \"->\", value)\n# \u5355\u72ec\u904d\u5386\u952e key\nfor key in mapp.keys():\nprint(key)\n# \u5355\u72ec\u904d\u5386\u503c value\nfor value in mapp.values():\nprint(value)\n
hash_map_test.go
/* \u904d\u5386\u54c8\u5e0c\u8868 */\n// \u904d\u5386\u952e\u503c\u5bf9 key->value\nfor key, value := range mapp {\nfmt.Println(key, \"->\", value)\n}\n// \u5355\u72ec\u904d\u5386\u952e key\nfor key := range mapp {\nfmt.Println(key)\n}\n// \u5355\u72ec\u904d\u5386\u503c value\nfor _, value := range mapp {\nfmt.Println(value)\n}\n
hash_map.js
/* \u904d\u5386\u54c8\u5e0c\u8868 */\n// \u904d\u5386\u952e\u503c\u5bf9 key->value\nfor (const entry of map.entries()) {\nif (!entry) continue;\nconsole.info(entry.key + ' -> ' + entry.val);\n}\n// \u5355\u72ec\u904d\u5386\u952e key\nfor (const key of map.keys()) {\nconsole.info(key);\n}\n// \u5355\u72ec\u904d\u5386\u503c value\nfor (const val of map.values()) {\nconsole.info(val);\n}\n
hash_map.ts
/* \u904d\u5386\u54c8\u5e0c\u8868 */\nconsole.info('\\n\u904d\u5386\u952e\u503c\u5bf9 Key->Value');\nfor (const [k, v] of map.entries()) {\nconsole.info(k + ' -> ' + v);\n}\nconsole.info('\\n\u5355\u72ec\u904d\u5386\u952e Key');\nfor (const k of map.keys()) {\nconsole.info(k);\n}\nconsole.info('\\n\u5355\u72ec\u904d\u5386\u503c Value');\nfor (const v of map.values()) {\nconsole.info(v);\n}\n
hash_map.c
\n
hash_map.cs
/* \u904d\u5386\u54c8\u5e0c\u8868 */\n// \u904d\u5386\u952e\u503c\u5bf9 Key->Value\nforeach (var kv in map) {\nConsole.WriteLine(kv.Key + \" -> \" + kv.Value);\n}\n// \u5355\u72ec\u904d\u5386\u952e key\nforeach (int key in map.Keys) {\nConsole.WriteLine(key);\n}\n// \u5355\u72ec\u904d\u5386\u503c value\nforeach (String val in map.Values) {\nConsole.WriteLine(val);\n}\n
hash_map.swift
/* \u904d\u5386\u54c8\u5e0c\u8868 */\n// \u904d\u5386\u952e\u503c\u5bf9 Key->Value\nfor (key, value) in map {\nprint(\"\\(key) -> \\(value)\")\n}\n// \u5355\u72ec\u904d\u5386\u952e Key\nfor key in map.keys {\nprint(key)\n}\n// \u5355\u72ec\u904d\u5386\u503c Value\nfor value in map.values {\nprint(value)\n}\n
hash_map.zig
\n
"},{"location":"chapter_hashing/hash_map/#613","title":"6.1.3. \u54c8\u5e0c\u51fd\u6570","text":"

\u54c8\u5e0c\u8868\u4e2d\u5b58\u50a8\u5143\u7d20\u7684\u6570\u636e\u7ed3\u6784\u88ab\u79f0\u4e3a\u300c\u6876 Bucket\u300d\uff0c\u5e95\u5c42\u5b9e\u73b0\u53ef\u80fd\u662f\u6570\u7ec4\u3001\u94fe\u8868\u3001\u4e8c\u53c9\u6811\uff08\u7ea2\u9ed1\u6811\uff09\uff0c\u6216\u662f\u5b83\u4eec\u7684\u7ec4\u5408\u3002

\u6700\u7b80\u5355\u5730\uff0c\u6211\u4eec\u53ef\u4ee5\u4ec5\u7528\u4e00\u4e2a\u300c\u6570\u7ec4\u300d\u6765\u5b9e\u73b0\u54c8\u5e0c\u8868\u3002\u9996\u5148\uff0c\u5c06\u6240\u6709 value \u653e\u5165\u6570\u7ec4\u4e2d\uff0c\u90a3\u4e48\u6bcf\u4e2a value \u5728\u6570\u7ec4\u4e2d\u90fd\u6709\u552f\u4e00\u7684\u300c\u7d22\u5f15\u300d\u3002\u663e\u7136\uff0c\u8bbf\u95ee value \u9700\u8981\u7ed9\u5b9a\u7d22\u5f15\uff0c\u800c\u4e3a\u4e86 \u5efa\u7acb key \u548c\u7d22\u5f15\u4e4b\u95f4\u7684\u6620\u5c04\u5173\u7cfb\uff0c\u6211\u4eec\u9700\u8981\u4f7f\u7528\u300c\u54c8\u5e0c\u51fd\u6570 Hash Function\u300d\u3002

\u8bbe\u6570\u7ec4\u4e3a bucket \uff0c\u54c8\u5e0c\u51fd\u6570\u4e3a f(x) \uff0c\u8f93\u5165\u952e\u4e3a key \u3002\u90a3\u4e48\u83b7\u53d6 value \u7684\u6b65\u9aa4\u4e3a\uff1a

  1. \u901a\u8fc7\u54c8\u5e0c\u51fd\u6570\u8ba1\u7b97\u51fa\u7d22\u5f15\uff0c\u5373 index = f(key) \uff1b
  2. \u901a\u8fc7\u7d22\u5f15\u5728\u6570\u7ec4\u4e2d\u83b7\u53d6\u503c\uff0c\u5373 value = bucket[index] \uff1b

\u4ee5\u4e0a\u8ff0\u5b66\u751f\u6570\u636e key \u5b66\u53f7 -> value \u59d3\u540d \u4e3a\u4f8b\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u300c\u54c8\u5e0c\u51fd\u6570\u300d\u8bbe\u8ba1\u4e3a

\\[ f(x) = x \\% 100 \\]

Fig. \u54c8\u5e0c\u51fd\u6570

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array_hash_map.java
/* \u952e\u503c\u5bf9 int->String */\nclass Entry {\npublic int key;\npublic String val;\npublic Entry(int key, String val) {\nthis.key = key;\nthis.val = val;\n}\n}\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\nclass ArrayHashMap {\nprivate List<Entry> bucket;\npublic ArrayHashMap() {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nbucket = new ArrayList<>();\nfor (int i = 0; i < 100; i++) {\nbucket.add(null);\n}\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\nprivate int hashFunc(int key) {\nint index = key % 100;\nreturn index;\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\npublic String get(int key) {\nint index = hashFunc(key);\nEntry pair = bucket.get(index);\nif (pair == null) return null;\nreturn pair.val;\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\npublic void put(int key, String val) {\nEntry pair = new Entry(key, val);\nint index = hashFunc(key);\nbucket.set(index, pair);\n}\n/* \u5220\u9664\u64cd\u4f5c */\npublic void remove(int key) {\nint index = hashFunc(key);\n// \u7f6e\u4e3a null \uff0c\u4ee3\u8868\u5220\u9664\nbucket.set(index, null);\n}\n/* \u83b7\u53d6\u6240\u6709\u952e\u503c\u5bf9 */\npublic List<Entry> entrySet() {\nList<Entry> entrySet = new ArrayList<>();\nfor (Entry pair : bucket) {\nif (pair != null)\nentrySet.add(pair);\n}\nreturn entrySet;\n}\n/* \u83b7\u53d6\u6240\u6709\u952e */\npublic List<Integer> keySet() {\nList<Integer> keySet = new ArrayList<>();\nfor (Entry pair : bucket) {\nif (pair != null)\nkeySet.add(pair.key);\n}\nreturn keySet;\n}\n/* \u83b7\u53d6\u6240\u6709\u503c */\npublic List<String> valueSet() {\nList<String> valueSet = new ArrayList<>();\nfor (Entry pair : bucket) {\nif (pair != null)\nvalueSet.add(pair.val);\n}\nreturn valueSet;\n}\n/* \u6253\u5370\u54c8\u5e0c\u8868 */\npublic void print() {\nfor (Entry kv: entrySet()) {\nSystem.out.println(kv.key + \" -> \" + kv.val);\n}\n}\n}\n
array_hash_map.cpp
/* \u952e\u503c\u5bf9 int->String */\nstruct Entry {\npublic:\nint key;\nstring val;\nEntry(int key, string val) {\nthis->key = key;\nthis->val = val;\n}\n};\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\nclass ArrayHashMap {\nprivate:\nvector<Entry*> bucket;\npublic:\nArrayHashMap() {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nbucket= vector<Entry*>(100);\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\nint hashFunc(int key) {\nint index = key % 100;\nreturn index;\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\nstring get(int key) {\nint index = hashFunc(key);\nEntry* pair = bucket[index];\nreturn pair->val;\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\nvoid put(int key, string val) {\nEntry* pair = new Entry(key, val);\nint index = hashFunc(key);\nbucket[index] = pair;\n}\n/* \u5220\u9664\u64cd\u4f5c */\nvoid remove(int key) {\nint index = hashFunc(key);\n// \u7f6e\u4e3a nullptr \uff0c\u4ee3\u8868\u5220\u9664\nbucket[index] = nullptr;\n}\n};\n
array_hash_map.py
\"\"\" \u952e\u503c\u5bf9 int->String \"\"\"\nclass Entry:\ndef __init__(self, key, val):\nself.key = key\nself.val = val\n\"\"\" \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 \"\"\"\nclass ArrayHashMap:\ndef __init__(self):\n# \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nself.bucket = [None] * 100\n\"\"\" \u54c8\u5e0c\u51fd\u6570 \"\"\"\ndef hash_func(self, key):\nindex = key % 100\nreturn index\n\"\"\" \u67e5\u8be2\u64cd\u4f5c \"\"\"\ndef get(self, key):\nindex = self.hash_func(key)\npair = self.bucket[index]\nif pair is None:\nreturn None\nreturn pair.val\n\"\"\" \u6dfb\u52a0\u64cd\u4f5c \"\"\"\ndef put(self, key, val):\npair = Entry(key, val)\nindex = self.hash_func(key)\nself.bucket[index] = pair\n\"\"\" \u5220\u9664\u64cd\u4f5c \"\"\"\ndef remove(self, key):\nindex = self.hash_func(key)\n# \u7f6e\u4e3a None \uff0c\u4ee3\u8868\u5220\u9664\nself.bucket[index] = None\n\"\"\" \u83b7\u53d6\u6240\u6709\u952e\u503c\u5bf9 \"\"\"\ndef entry_set(self):\nresult = []\nfor pair in self.bucket:\nif pair is not None:\nresult.append(pair)\nreturn result\n\"\"\" \u83b7\u53d6\u6240\u6709\u952e \"\"\"\ndef key_set(self):\nresult = []\nfor pair in self.bucket:\nif pair is not None:\nresult.append(pair.key)\nreturn result\n\"\"\" \u83b7\u53d6\u6240\u6709\u503c \"\"\"\ndef value_set(self):\nresult = []\nfor pair in self.bucket:\nif pair is not None:\nresult.append(pair.val)\nreturn result\n\"\"\" \u6253\u5370\u54c8\u5e0c\u8868 \"\"\"\ndef print(self):\nfor pair in self.bucket:\nif pair is not None:\nprint(pair.key, \"->\", pair.val)\n
array_hash_map.go
/* \u952e\u503c\u5bf9 int->String */\ntype entry struct {\nkey int\nval string\n}\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\ntype arrayHashMap struct {\nbucket []*entry\n}\nfunc newArrayHashMap() *arrayHashMap {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nbucket := make([]*entry, 100)\nreturn &arrayHashMap{bucket: bucket}\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\nfunc (a *arrayHashMap) hashFunc(key int) int {\nindex := key % 100\nreturn index\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\nfunc (a *arrayHashMap) get(key int) string {\nindex := a.hashFunc(key)\npair := a.bucket[index]\nif pair == nil {\nreturn \"Not Found\"\n}\nreturn pair.val\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\nfunc (a *arrayHashMap) put(key int, val string) {\npair := &entry{key: key, val: val}\nindex := a.hashFunc(key)\na.bucket[index] = pair\n}\n/* \u5220\u9664\u64cd\u4f5c */\nfunc (a *arrayHashMap) remove(key int) {\nindex := a.hashFunc(key)\n// \u7f6e\u4e3a nil \uff0c\u4ee3\u8868\u5220\u9664\na.bucket[index] = nil\n}\n
array_hash_map.js
/* \u952e\u503c\u5bf9 Number -> String */\nclass Entry {\nconstructor(key, val) {\nthis.key = key;\nthis.val = val;\n}\n}\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\nclass ArrayHashMap {\n#bucket;\nconstructor() {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nthis.#bucket = new Array(100).fill(null);\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\n#hashFunc(key) {\nreturn key % 100;\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\nget(key) {\nlet index = this.#hashFunc(key);\nlet entry = this.#bucket[index];\nif (entry === null) return null;\nreturn entry.val;\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\nset(key, val) {\nlet index = this.#hashFunc(key);\nthis.#bucket[index] = new Entry(key, val);\n}\n/* \u5220\u9664\u64cd\u4f5c */\ndelete(key) {\nlet index = this.#hashFunc(key);\n// \u7f6e\u4e3a null \uff0c\u4ee3\u8868\u5220\u9664\nthis.#bucket[index] = null;\n}\n}\n
array_hash_map.ts
/* \u952e\u503c\u5bf9 Number -> String */\nclass Entry {\npublic key: number;\npublic val: string;\nconstructor(key: number, val: string) {\nthis.key = key;\nthis.val = val;\n}\n}\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\nclass ArrayHashMap {\nprivate readonly bucket: (Entry | null)[];\nconstructor() {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nthis.bucket = (new Array(100)).fill(null);\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\nprivate hashFunc(key: number): number {\nreturn key % 100;\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\npublic get(key: number): string | null {\nlet index = this.hashFunc(key);\nlet entry = this.bucket[index];\nif (entry === null) return null;\nreturn entry.val;\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\npublic set(key: number, val: string) {\nlet index = this.hashFunc(key);\nthis.bucket[index] = new Entry(key, val);\n}\n/* \u5220\u9664\u64cd\u4f5c */\npublic delete(key: number) {\nlet index = this.hashFunc(key);\n// \u7f6e\u4e3a null \uff0c\u4ee3\u8868\u5220\u9664\nthis.bucket[index] = null;\n}\n}\n
array_hash_map.c
\n
array_hash_map.cs
/* \u952e\u503c\u5bf9 int->String */\nclass Entry\n{\npublic int key;\npublic String val;\npublic Entry(int key, String val)\n{\nthis.key = key;\nthis.val = val;\n}\n}\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\nclass ArrayHashMap\n{\nprivate List<Entry?> bucket;\npublic ArrayHashMap()\n{\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nbucket = new ();\nfor (int i = 0; i < 100; i++)\n{\nbucket.Add(null);\n}\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\nprivate int hashFunc(int key)\n{\nint index = key % 100;\nreturn index;\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\npublic String? get(int key)\n{\nint index = hashFunc(key);\nEntry? pair = bucket[index];\nif (pair == null) return null;\nreturn pair.val;\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\npublic void put(int key, String val)\n{\nEntry pair = new Entry(key, val);\nint index = hashFunc(key);\nbucket[index]=pair;\n}\n/* \u5220\u9664\u64cd\u4f5c */\npublic void remove(int key)\n{\nint index = hashFunc(key);\n// \u7f6e\u4e3a null \uff0c\u4ee3\u8868\u5220\u9664\nbucket[index]=null;\n}\n}\n
array_hash_map.swift
/* \u952e\u503c\u5bf9 int->String */\nclass Entry {\nvar key: Int\nvar val: String\ninit(key: Int, val: String) {\nself.key = key\nself.val = val\n}\n}\n/* \u57fa\u4e8e\u6570\u7ec4\u7b80\u6613\u5b9e\u73b0\u7684\u54c8\u5e0c\u8868 */\nclass ArrayHashMap {\nprivate var bucket: [Entry?] = []\ninit() {\n// \u521d\u59cb\u5316\u4e00\u4e2a\u957f\u5ea6\u4e3a 100 \u7684\u6876\uff08\u6570\u7ec4\uff09\nfor _ in 0 ..< 100 {\nbucket.append(nil)\n}\n}\n/* \u54c8\u5e0c\u51fd\u6570 */\nprivate func hashFunc(key: Int) -> Int {\nlet index = key % 100\nreturn index\n}\n/* \u67e5\u8be2\u64cd\u4f5c */\nfunc get(key: Int) -> String? {\nlet index = hashFunc(key: key)\nlet pair = bucket[index]\nreturn pair?.val\n}\n/* \u6dfb\u52a0\u64cd\u4f5c */\nfunc put(key: Int, val: String) {\nlet pair = Entry(key: key, val: val)\nlet index = hashFunc(key: key)\nbucket[index] = pair\n}\n/* \u5220\u9664\u64cd\u4f5c */\nfunc remove(key: Int) {\nlet index = hashFunc(key: key)\n// \u7f6e\u4e3a nil \uff0c\u4ee3\u8868\u5220\u9664\nbucket[index] = nil\n}\n}\n
array_hash_map.zig
\n
"},{"location":"chapter_hashing/hash_map/#614","title":"6.1.4. \u54c8\u5e0c\u51b2\u7a81","text":"

\u7ec6\u5fc3\u7684\u540c\u5b66\u53ef\u80fd\u4f1a\u53d1\u73b0\uff0c\u54c8\u5e0c\u51fd\u6570 \\(f(x) = x \\% 100\\) \u4f1a\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u5931\u6548\u3002\u5177\u4f53\u5730\uff0c\u5f53\u8f93\u5165\u7684 key \u540e\u4e24\u4f4d\u76f8\u540c\u65f6\uff0c\u54c8\u5e0c\u51fd\u6570\u7684\u8ba1\u7b97\u7ed3\u679c\u4e5f\u76f8\u540c\uff0c\u6307\u5411\u540c\u4e00\u4e2a value \u3002\u4f8b\u5982\uff0c\u5206\u522b\u67e5\u8be2\u4e24\u4e2a\u5b66\u53f7 \\(12836\\) \u548c \\(20336\\) \uff0c\u5219\u6709

\\[ f(12836) = f(20336) = 36 \\]

\u4e24\u4e2a\u5b66\u53f7\u6307\u5411\u4e86\u540c\u4e00\u4e2a\u59d3\u540d\uff0c\u8fd9\u660e\u663e\u662f\u4e0d\u5bf9\u7684\uff0c\u6211\u4eec\u5c06\u8fd9\u79cd\u73b0\u8c61\u79f0\u4e3a\u300c\u54c8\u5e0c\u51b2\u7a81 Hash Collision\u300d\u3002\u5982\u4f55\u907f\u514d\u54c8\u5e0c\u51b2\u7a81\u7684\u95ee\u9898\u5c06\u88ab\u7559\u5728\u4e0b\u7ae0\u8ba8\u8bba\u3002

Fig. \u54c8\u5e0c\u51b2\u7a81

\u7efc\u4e0a\u6240\u8ff0\uff0c\u4e00\u4e2a\u4f18\u79c0\u7684\u300c\u54c8\u5e0c\u51fd\u6570\u300d\u5e94\u8be5\u5177\u5907\u4ee5\u4e0b\u7279\u6027\uff1a

  • \u5c3d\u91cf\u5c11\u5730\u53d1\u751f\u54c8\u5e0c\u51b2\u7a81\uff1b
  • \u65f6\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff0c\u8ba1\u7b97\u5c3d\u53ef\u80fd\u9ad8\u6548\uff1b
  • \u7a7a\u95f4\u4f7f\u7528\u7387\u9ad8\uff0c\u5373\u201c\u952e\u503c\u5bf9\u5360\u7528\u7a7a\u95f4 / \u54c8\u5e0c\u8868\u603b\u5360\u7528\u7a7a\u95f4\u201d\u5c3d\u53ef\u80fd\u5927\uff1b
"},{"location":"chapter_hashing/summary/","title":"6.3. \u5c0f\u7ed3","text":""},{"location":"chapter_heap/heap/","title":"8.1. \u5806","text":"

\u300c\u5806 Heap\u300d\u662f\u4e00\u68f5\u9650\u5b9a\u6761\u4ef6\u4e0b\u7684\u300c\u5b8c\u5168\u4e8c\u53c9\u6811\u300d\u3002\u6839\u636e\u6210\u7acb\u6761\u4ef6\uff0c\u5806\u4e3b\u8981\u5206\u4e3a\u4e24\u79cd\u7c7b\u578b\uff1a

  • \u300c\u5927\u9876\u5806 Max Heap\u300d\uff0c\u4efb\u610f\u7ed3\u70b9\u7684\u503c \\(\\geq\\) \u5176\u5b50\u7ed3\u70b9\u7684\u503c\uff1b
  • \u300c\u5c0f\u9876\u5806 Min Heap\u300d\uff0c\u4efb\u610f\u7ed3\u70b9\u7684\u503c \\(\\leq\\) \u5176\u5b50\u7ed3\u70b9\u7684\u503c\uff1b

"},{"location":"chapter_heap/heap/#811","title":"8.1.1. \u5806\u672f\u8bed\u4e0e\u6027\u8d28","text":"
  • \u7531\u4e8e\u5806\u662f\u5b8c\u5168\u4e8c\u53c9\u6811\uff0c\u56e0\u6b64\u6700\u5e95\u5c42\u7ed3\u70b9\u9760\u5de6\u586b\u5145\uff0c\u5176\u5b83\u5c42\u7ed3\u70b9\u7686\u88ab\u586b\u6ee1\u3002
  • \u4e8c\u53c9\u6811\u4e2d\u7684\u6839\u7ed3\u70b9\u5bf9\u5e94\u300c\u5806\u9876\u300d\uff0c\u5e95\u5c42\u6700\u9760\u53f3\u7ed3\u70b9\u5bf9\u5e94\u300c\u5806\u5e95\u300d\u3002
  • \u5bf9\u4e8e\u5927\u9876\u5806 / \u5c0f\u9876\u5806\uff0c\u5176\u5806\u9876\u5143\u7d20\uff08\u5373\u6839\u7ed3\u70b9\uff09\u7684\u503c\u6700\u5927 / \u6700\u5c0f\u3002
"},{"location":"chapter_heap/heap/#812","title":"8.1.2. \u5806\u5e38\u7528\u64cd\u4f5c","text":"

\u503c\u5f97\u8bf4\u660e\u7684\u662f\uff0c\u591a\u6570\u7f16\u7a0b\u8bed\u8a00\u63d0\u4f9b\u7684\u662f\u300c\u4f18\u5148\u961f\u5217 Priority Queue\u300d\uff0c\u5176\u662f\u4e00\u79cd\u62bd\u8c61\u6570\u636e\u7ed3\u6784\uff0c\u5b9a\u4e49\u4e3a\u5177\u6709\u51fa\u961f\u4f18\u5148\u7ea7\u7684\u961f\u5217\u3002

\u800c\u6070\u597d\uff0c\u5806\u7684\u5b9a\u4e49\u4e0e\u4f18\u5148\u961f\u5217\u7684\u64cd\u4f5c\u903b\u8f91\u5b8c\u5168\u543b\u5408\uff0c\u5927\u9876\u5806\u5c31\u662f\u4e00\u4e2a\u5143\u7d20\u4ece\u5927\u5230\u5c0f\u51fa\u961f\u7684\u4f18\u5148\u961f\u5217\u3002\u4ece\u4f7f\u7528\u89d2\u5ea6\u770b\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u300c\u4f18\u5148\u961f\u5217\u300d\u548c\u300c\u5806\u300d\u7406\u89e3\u4e3a\u7b49\u4ef7\u7684\u6570\u636e\u7ed3\u6784\u3002\u56e0\u6b64\uff0c\u672c\u6587\u4e0e\u4ee3\u7801\u5bf9\u4e24\u8005\u4e0d\u505a\u7279\u522b\u533a\u5206\uff0c\u7edf\u4e00\u4f7f\u7528\u300c\u5806\u300d\u6765\u547d\u540d\u3002

\u5806\u7684\u5e38\u7528\u64cd\u4f5c\u89c1\u4e0b\u8868\uff08\u65b9\u6cd5\u547d\u540d\u4ee5 Java \u4e3a\u4f8b\uff09\u3002

Table. \u5806\u7684\u5e38\u7528\u64cd\u4f5c

\u65b9\u6cd5 \u63cf\u8ff0 \u65f6\u95f4\u590d\u6742\u5ea6 add() \u5143\u7d20\u5165\u5806 \\(O(\\log n)\\) poll() \u5806\u9876\u5143\u7d20\u51fa\u5806 \\(O(\\log n)\\) peek() \u8bbf\u95ee\u5806\u9876\u5143\u7d20\uff08\u5927 / \u5c0f\u9876\u5806\u5206\u522b\u4e3a\u6700\u5927 / \u5c0f\u503c\uff09 \\(O(1)\\) size() \u83b7\u53d6\u5806\u7684\u5143\u7d20\u6570\u91cf \\(O(1)\\) isEmpty() \u5224\u65ad\u5806\u662f\u5426\u4e3a\u7a7a \\(O(1)\\)

\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u7f16\u7a0b\u8bed\u8a00\u63d0\u4f9b\u7684\u5806\u7c7b\uff08\u6216\u4f18\u5148\u961f\u5217\u7c7b\uff09\u3002

Tip

\u7c7b\u4f3c\u4e8e\u6392\u5e8f\u4e2d\u201c\u4ece\u5c0f\u5230\u5927\u6392\u5217\u201d\u548c\u201c\u4ece\u5927\u5230\u5c0f\u6392\u5217\u201d\uff0c\u201c\u5927\u9876\u5806\u201d\u548c\u201c\u5c0f\u9876\u5806\u201d\u53ef\u4ec5\u901a\u8fc7\u4fee\u6539 Comparator \u6765\u4e92\u76f8\u8f6c\u6362\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig heap.java
/* \u521d\u59cb\u5316\u5806 */\n// \u521d\u59cb\u5316\u5c0f\u9876\u5806\nQueue<Integer> minHeap = new PriorityQueue<>();\n// \u521d\u59cb\u5316\u5927\u9876\u5806\uff08\u4f7f\u7528 lambda \u8868\u8fbe\u5f0f\u4fee\u6539 Comparator \u5373\u53ef\uff09\nQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> { return b - a; });\n/* \u5143\u7d20\u5165\u5806 */\nmaxHeap.add(1);\nmaxHeap.add(3);\nmaxHeap.add(2);\nmaxHeap.add(5);\nmaxHeap.add(4);\n/* \u83b7\u53d6\u5806\u9876\u5143\u7d20 */\nint peek = maxHeap.peek(); // 5\n/* \u5806\u9876\u5143\u7d20\u51fa\u5806 */\n// \u51fa\u5806\u5143\u7d20\u4f1a\u5f62\u6210\u4e00\u4e2a\u4ece\u5927\u5230\u5c0f\u7684\u5e8f\u5217\npeek = heap.poll();  // 5\npeek = heap.poll();  // 4\npeek = heap.poll();  // 3\npeek = heap.poll();  // 2\npeek = heap.poll();  // 1\n/* \u83b7\u53d6\u5806\u5927\u5c0f */\nint size = maxHeap.size();\n/* \u5224\u65ad\u5806\u662f\u5426\u4e3a\u7a7a */\nboolean isEmpty = maxHeap.isEmpty();\n/* \u8f93\u5165\u5217\u8868\u5e76\u5efa\u5806 */\nminHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));\n
heap.cpp
/* \u521d\u59cb\u5316\u5806 */\n// \u521d\u59cb\u5316\u5c0f\u9876\u5806\npriority_queue<int, vector<int>, greater<int>> minHeap;\n// \u521d\u59cb\u5316\u5927\u9876\u5806\npriority_queue<int, vector<int>, less<int>> maxHeap;\n/* \u5143\u7d20\u5165\u5806 */\nmaxHeap.push(1);\nmaxHeap.push(3);\nmaxHeap.push(2);\nmaxHeap.push(5);\nmaxHeap.push(4);\n/* \u83b7\u53d6\u5806\u9876\u5143\u7d20 */\nint peek = maxHeap.top(); // 5\n/* \u5806\u9876\u5143\u7d20\u51fa\u5806 */\n// \u51fa\u5806\u5143\u7d20\u4f1a\u5f62\u6210\u4e00\u4e2a\u4ece\u5927\u5230\u5c0f\u7684\u5e8f\u5217\nmaxHeap.pop(); // 5\nmaxHeap.pop(); // 4\nmaxHeap.pop(); // 3\nmaxHeap.pop(); // 2\nmaxHeap.pop(); // 1\n/* \u83b7\u53d6\u5806\u5927\u5c0f */\nint size = maxHeap.size();\n/* \u5224\u65ad\u5806\u662f\u5426\u4e3a\u7a7a */\nbool isEmpty = maxHeap.empty();\n/* \u8f93\u5165\u5217\u8868\u5e76\u5efa\u5806 */\nvector<int> input{1, 3, 2, 5, 4};\npriority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());\n
heap.py
\n
heap.go
// Go \u8bed\u8a00\u4e2d\u53ef\u4ee5\u901a\u8fc7\u5b9e\u73b0 heap.Interface \u6765\u6784\u5efa\u6574\u6570\u5927\u9876\u5806\n// \u5b9e\u73b0 heap.Interface \u9700\u8981\u540c\u65f6\u5b9e\u73b0 sort.Interface\ntype intHeap []any\n// Push heap.Interface \u7684\u65b9\u6cd5\uff0c\u5b9e\u73b0\u63a8\u5165\u5143\u7d20\u5230\u5806\nfunc (h *intHeap) Push(x any) {\n// Push \u548c Pop \u4f7f\u7528 pointer receiver \u4f5c\u4e3a\u53c2\u6570\n// \u56e0\u4e3a\u5b83\u4eec\u4e0d\u4ec5\u4f1a\u5bf9\u5207\u7247\u7684\u5185\u5bb9\u8fdb\u884c\u8c03\u6574\uff0c\u8fd8\u4f1a\u4fee\u6539\u5207\u7247\u7684\u957f\u5ea6\u3002\n*h = append(*h, x.(int))\n}\n// Pop heap.Interface \u7684\u65b9\u6cd5\uff0c\u5b9e\u73b0\u5f39\u51fa\u5806\u9876\u5143\u7d20\nfunc (h *intHeap) Pop() any {\n// \u5f85\u51fa\u5806\u5143\u7d20\u5b58\u653e\u5728\u6700\u540e\nlast := (*h)[len(*h)-1]\n*h = (*h)[:len(*h)-1]\nreturn last\n}\n// Len sort.Interface \u7684\u65b9\u6cd5\nfunc (h *intHeap) Len() int {\nreturn len(*h)\n}\n// Less sort.Interface \u7684\u65b9\u6cd5\nfunc (h *intHeap) Less(i, j int) bool {\n// \u5982\u679c\u5b9e\u73b0\u5c0f\u9876\u5806\uff0c\u5219\u9700\u8981\u8c03\u6574\u4e3a\u5c0f\u4e8e\u53f7\nreturn (*h)[i].(int) > (*h)[j].(int)\n}\n// Swap sort.Interface \u7684\u65b9\u6cd5\nfunc (h *intHeap) Swap(i, j int) {\n(*h)[i], (*h)[j] = (*h)[j], (*h)[i]\n}\n// Top \u83b7\u53d6\u5806\u9876\u5143\u7d20\nfunc (h *intHeap) Top() any {\nreturn (*h)[0]\n}\n/* Driver Code */\nfunc TestHeap(t *testing.T) {\n/* \u521d\u59cb\u5316\u5806 */\n// \u521d\u59cb\u5316\u5927\u9876\u5806\nmaxHeap := &intHeap{}\nheap.Init(maxHeap)\n/* \u5143\u7d20\u5165\u5806 */\n// \u8c03\u7528 heap.Interface \u7684\u65b9\u6cd5\uff0c\u6765\u6dfb\u52a0\u5143\u7d20\nheap.Push(maxHeap, 1)\nheap.Push(maxHeap, 3)\nheap.Push(maxHeap, 2)\nheap.Push(maxHeap, 4)\nheap.Push(maxHeap, 5)\n/* \u83b7\u53d6\u5806\u9876\u5143\u7d20 */\ntop := maxHeap.Top()\nfmt.Printf(\"\u5806\u9876\u5143\u7d20\u4e3a %d\\n\", top)\n/* \u5806\u9876\u5143\u7d20\u51fa\u5806 */\n// \u8c03\u7528 heap.Interface \u7684\u65b9\u6cd5\uff0c\u6765\u79fb\u9664\u5143\u7d20\nheap.Pop(maxHeap)\nheap.Pop(maxHeap)\nheap.Pop(maxHeap)\nheap.Pop(maxHeap)\nheap.Pop(maxHeap)\n/* \u83b7\u53d6\u5806\u5927\u5c0f */\nsize := len(*maxHeap)\nfmt.Printf(\"\u5806\u5143\u7d20\u6570\u91cf\u4e3a %d\\n\", size)\n/* \u5224\u65ad\u5806\u662f\u5426\u4e3a\u7a7a */\nisEmpty := len(*maxHeap) == 0\nfmt.Printf(\"\u5806\u662f\u5426\u4e3a\u7a7a %t\\n\", isEmpty)\n}\n
heap.js
\n
heap.ts
\n
heap.c
\n
heap.cs
\n
heap.swift
// Swift \u672a\u63d0\u4f9b\u5185\u7f6e heap \u7c7b\n
heap.zig
\n
"},{"location":"chapter_heap/heap/#813","title":"8.1.3. \u5806\u7684\u5b9e\u73b0","text":"

\u4e0b\u6587\u5b9e\u73b0\u7684\u662f\u300c\u5927\u9876\u5806\u300d\uff0c\u82e5\u60f3\u8f6c\u6362\u4e3a\u300c\u5c0f\u9876\u5806\u300d\uff0c\u5c06\u6240\u6709\u5927\u5c0f\u903b\u8f91\u5224\u65ad\u53d6\u9006\uff08\u4f8b\u5982\u5c06 \\(\\geq\\) \u66ff\u6362\u4e3a \\(\\leq\\) \uff09\u5373\u53ef\uff0c\u6709\u5174\u8da3\u7684\u540c\u5b66\u53ef\u81ea\u884c\u5b9e\u73b0\u3002

"},{"location":"chapter_heap/heap/#_1","title":"\u5806\u7684\u5b58\u50a8\u4e0e\u8868\u793a","text":"

\u5728\u4e8c\u53c9\u6811\u7ae0\u8282\u6211\u4eec\u5b66\u8fc7\uff0c\u300c\u5b8c\u5168\u4e8c\u53c9\u6811\u300d\u975e\u5e38\u9002\u5408\u4f7f\u7528\u300c\u6570\u7ec4\u300d\u6765\u8868\u793a\uff0c\u800c\u5806\u6070\u597d\u662f\u4e00\u68f5\u5b8c\u5168\u4e8c\u53c9\u6811\uff0c\u56e0\u800c\u6211\u4eec\u91c7\u7528\u300c\u6570\u7ec4\u300d\u6765\u5b58\u50a8\u300c\u5806\u300d\u3002

\u4e8c\u53c9\u6811\u6307\u9488\u3002\u4f7f\u7528\u6570\u7ec4\u8868\u793a\u4e8c\u53c9\u6811\u65f6\uff0c\u5143\u7d20\u4ee3\u8868\u7ed3\u70b9\u503c\uff0c\u7d22\u5f15\u4ee3\u8868\u7ed3\u70b9\u5728\u4e8c\u53c9\u6811\u4e2d\u7684\u4f4d\u7f6e\uff0c\u800c\u7ed3\u70b9\u6307\u9488\u901a\u8fc7\u7d22\u5f15\u6620\u5c04\u516c\u5f0f\u6765\u5b9e\u73b0\u3002

\u5177\u4f53\u5730\uff0c\u7ed9\u5b9a\u7d22\u5f15 \\(i\\) \uff0c\u90a3\u4e48\u5176\u5de6\u5b50\u7ed3\u70b9\u7d22\u5f15\u4e3a \\(2i + 1\\) \u3001\u53f3\u5b50\u7ed3\u70b9\u7d22\u5f15\u4e3a \\(2i + 2\\) \u3001\u7236\u7ed3\u70b9\u7d22\u5f15\u4e3a \\((i - 1) / 2\\) \uff08\u5411\u4e0b\u6574\u9664\uff09\u3002\u5f53\u7d22\u5f15\u8d8a\u754c\u65f6\uff0c\u4ee3\u8868\u7a7a\u7ed3\u70b9\u6216\u7ed3\u70b9\u4e0d\u5b58\u5728\u3002

\u6211\u4eec\u5c06\u7d22\u5f15\u6620\u5c04\u516c\u5f0f\u5c01\u88c5\u6210\u51fd\u6570\uff0c\u4ee5\u4fbf\u540e\u7eed\u4f7f\u7528\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig my_heap.java
/* \u83b7\u53d6\u5de6\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nint left(int i) {\nreturn 2 * i + 1;\n}\n/* \u83b7\u53d6\u53f3\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nint right(int i) {\nreturn 2 * i + 2;\n}\n/* \u83b7\u53d6\u7236\u7ed3\u70b9\u7d22\u5f15 */\nint parent(int i) {\nreturn (i - 1) / 2; // \u5411\u4e0b\u6574\u9664\n}\n
my_heap.cpp
// \u4f7f\u7528\u52a8\u6001\u6570\u7ec4\uff0c\u8fd9\u6837\u65e0\u9700\u8003\u8651\u6269\u5bb9\u95ee\u9898\nvector<int> maxHeap;\n/* \u83b7\u53d6\u5de6\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nint left(int i) {\nreturn 2 * i + 1;\n}\n/* \u83b7\u53d6\u53f3\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nint right(int i) {\nreturn 2 * i + 2;\n} /* \u83b7\u53d6\u7236\u7ed3\u70b9\u7d22\u5f15 */\nint parent(int i) {\nreturn (i - 1) / 2; // \u5411\u4e0b\u53d6\u6574\n}\n
my_heap.py
\n
my_heap.go
type maxHeap struct {\n// \u4f7f\u7528\u5207\u7247\u800c\u975e\u6570\u7ec4\uff0c\u8fd9\u6837\u65e0\u9700\u8003\u8651\u6269\u5bb9\u95ee\u9898\ndata []any\n}\n/* \u6784\u9020\u51fd\u6570\uff0c\u5efa\u7acb\u7a7a\u5806 */\nfunc newHeap() *maxHeap {\nreturn &maxHeap{\ndata: make([]any, 0),\n}\n}\n/* \u83b7\u53d6\u5de6\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nfunc (h *maxHeap) left(i int) int {\nreturn 2*i + 1\n}\n/* \u83b7\u53d6\u53f3\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nfunc (h *maxHeap) right(i int) int {\nreturn 2*i + 2\n}\n/* \u83b7\u53d6\u7236\u7ed3\u70b9\u7d22\u5f15 */\nfunc (h *maxHeap) parent(i int) int {\n// \u5411\u4e0b\u6574\u9664\nreturn (i - 1) / 2\n}\n
my_heap.js
\n
my_heap.ts
\n
my_heap.c
\n
my_heap.cs
\n
my_heap.swift
var maxHeap: [Int]\n/* \u6784\u9020\u51fd\u6570\uff0c\u5efa\u7acb\u7a7a\u5806 */\ninit() {\nmaxHeap = []\n}\n/* \u83b7\u53d6\u5de6\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nfunc left(i: Int) -> Int {\n2 * i + 1\n}\n/* \u83b7\u53d6\u53f3\u5b50\u7ed3\u70b9\u7d22\u5f15 */\nfunc right(i: Int) -> Int {\n2 * i + 2\n}\n/* \u83b7\u53d6\u7236\u7ed3\u70b9\u7d22\u5f15 */\nfunc parent(i: Int) -> Int {\n(i - 1) / 2 // \u5411\u4e0b\u6574\u9664\n}\n
my_heap.zig
\n
"},{"location":"chapter_heap/heap/#_2","title":"\u8bbf\u95ee\u5806\u9876\u5143\u7d20","text":"

\u5806\u9876\u5143\u7d20\u662f\u4e8c\u53c9\u6811\u7684\u6839\u7ed3\u70b9\uff0c\u5373\u5217\u8868\u9996\u5143\u7d20\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig my_heap.java
/* \u8bbf\u95ee\u5806\u9876\u5143\u7d20 */\nint peek() {\nreturn maxHeap.get(0);\n}\n
my_heap.cpp
/* \u8bbf\u95ee\u5806\u9876\u5143\u7d20 */\nint peek() {\nreturn maxHeap[0];\n}\n
my_heap.py
\n
my_heap.go
/* \u8bbf\u95ee\u5806\u9876\u5143\u7d20 */\nfunc (h *maxHeap) peek() any {\nreturn h.data[0]\n}\n
my_heap.js
\n
my_heap.ts
\n
my_heap.c
\n
my_heap.cs
\n
my_heap.swift
/* \u8bbf\u95ee\u5806\u9876\u5143\u7d20 */\nfunc peek() -> Int {\nmaxHeap[0]\n}\n
my_heap.zig
\n
"},{"location":"chapter_heap/heap/#_3","title":"\u5143\u7d20\u5165\u5806","text":"

\u7ed9\u5b9a\u5143\u7d20 val \uff0c\u6211\u4eec\u5148\u5c06\u5176\u6dfb\u52a0\u5230\u5806\u5e95\u3002\u6dfb\u52a0\u540e\uff0c\u7531\u4e8e val \u53ef\u80fd\u5927\u4e8e\u5806\u4e2d\u5176\u5b83\u5143\u7d20\uff0c\u6b64\u65f6\u5806\u7684\u6210\u7acb\u6761\u4ef6\u53ef\u80fd\u5df2\u7ecf\u88ab\u7834\u574f\uff0c\u56e0\u6b64\u9700\u8981\u4fee\u590d\u4ece\u63d2\u5165\u7ed3\u70b9\u5230\u6839\u7ed3\u70b9\u8fd9\u6761\u8def\u5f84\u4e0a\u7684\u5404\u4e2a\u7ed3\u70b9\uff0c\u8be5\u64cd\u4f5c\u88ab\u79f0\u4e3a\u300c\u5806\u5316 Heapify\u300d\u3002

\u8003\u8651\u4ece\u5165\u5806\u7ed3\u70b9\u5f00\u59cb\uff0c\u4ece\u5e95\u81f3\u9876\u6267\u884c\u5806\u5316\u3002\u5177\u4f53\u5730\uff0c\u6bd4\u8f83\u63d2\u5165\u7ed3\u70b9\u4e0e\u5176\u7236\u7ed3\u70b9\u7684\u503c\uff0c\u82e5\u63d2\u5165\u7ed3\u70b9\u66f4\u5927\u5219\u5c06\u5b83\u4eec\u4ea4\u6362\uff1b\u5e76\u5faa\u73af\u4ee5\u4e0a\u64cd\u4f5c\uff0c\u4ece\u5e95\u81f3\u9876\u5730\u4fee\u590d\u5806\u4e2d\u7684\u5404\u4e2a\u7ed3\u70b9\uff1b\u76f4\u81f3\u8d8a\u8fc7\u6839\u7ed3\u70b9\u65f6\u7ed3\u675f\uff0c\u6216\u5f53\u9047\u5230\u65e0\u9700\u4ea4\u6362\u7684\u7ed3\u70b9\u65f6\u63d0\u524d\u7ed3\u675f\u3002

Step 1Step 2Step 3Step 4Step 5Step 6

\u8bbe\u7ed3\u70b9\u603b\u6570\u4e3a \\(n\\) \uff0c\u5219\u6811\u7684\u9ad8\u5ea6\u4e3a \\(O(\\log n)\\) \uff0c\u6613\u5f97\u5806\u5316\u64cd\u4f5c\u7684\u5faa\u73af\u8f6e\u6570\u6700\u591a\u4e3a \\(O(\\log n)\\) \uff0c\u56e0\u800c\u5143\u7d20\u5165\u5806\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(\\log n)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig my_heap.java
/* \u5143\u7d20\u5165\u5806 */\nvoid push(int val) {\n// \u6dfb\u52a0\u7ed3\u70b9\nmaxHeap.add(val);\n// \u4ece\u5e95\u81f3\u9876\u5806\u5316\nsiftUp(size() - 1);\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u5e95\u81f3\u9876\u5806\u5316 */\nvoid siftUp(int i) {\nwhile (true) {\n// \u83b7\u53d6\u7ed3\u70b9 i \u7684\u7236\u7ed3\u70b9\nint p = parent(i);\n// \u5f53\u201c\u8d8a\u8fc7\u6839\u7ed3\u70b9\u201d\u6216\u201c\u7ed3\u70b9\u65e0\u9700\u4fee\u590d\u201d\u65f6\uff0c\u7ed3\u675f\u5806\u5316\nif (p < 0 || maxHeap.get(i) <= maxHeap.get(p))\nbreak;\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nswap(i, p);\n// \u5faa\u73af\u5411\u4e0a\u5806\u5316\ni = p;\n}\n}\n
my_heap.cpp
/* \u5143\u7d20\u5165\u5806 */\nvoid push(int val) {\n// \u6dfb\u52a0\u7ed3\u70b9\nmaxHeap.push_back(val);\n// \u4ece\u5e95\u81f3\u9876\u5806\u5316\nshifUp(size() - 1);\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u5e95\u81f3\u9876\u5806\u5316 */\nvoid shifUp(int i) {\nwhile (true) {\n// \u83b7\u53d6\u7ed3\u70b9 i \u7684\u7236\u7ed3\u70b9\nint p =  parent(i);\n// \u5f53\u201c\u8d8a\u8fc7\u6839\u7ed3\u70b9\u201d\u6216\u201c\u7ed3\u70b9\u65e0\u9700\u4fee\u590d\u201d\u65f6\uff0c\u7ed3\u675f\u5806\u5316\nif (p < 0 || maxHeap[i] <= maxHeap[p])\nbreak;\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nswap(maxHeap[i], maxHeap[p]);\n// \u5faa\u73af\u5411\u4e0a\u5806\u5316\ni = p;\n}\n}\n
my_heap.py
\n
my_heap.go
/* \u5143\u7d20\u5165\u5806 */\nfunc (h *maxHeap) push(val any) {\n// \u6dfb\u52a0\u7ed3\u70b9\nh.data = append(h.data, val)\n// \u4ece\u5e95\u81f3\u9876\u5806\u5316\nh.siftUp(len(h.data) - 1)\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u5e95\u81f3\u9876\u5806\u5316 */\nfunc (h *maxHeap) siftUp(i int) {\nfor true {\n// \u83b7\u53d6\u7ed3\u70b9 i \u7684\u7236\u7ed3\u70b9\np := h.parent(i)\n// \u5f53\u201c\u8d8a\u8fc7\u6839\u7ed3\u70b9\u201d\u6216\u201c\u7ed3\u70b9\u65e0\u9700\u4fee\u590d\u201d\u65f6\uff0c\u7ed3\u675f\u5806\u5316\nif p < 0 || h.data[i].(int) <= h.data[p].(int) {\nbreak\n}\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nh.swap(i, p)\n// \u5faa\u73af\u5411\u4e0a\u5806\u5316\ni = p\n}\n}\n
my_heap.js
\n
my_heap.ts
\n
my_heap.c
\n
my_heap.cs
\n
my_heap.swift
/* \u5143\u7d20\u5165\u5806 */\nfunc push(val: Int) {\n// \u6dfb\u52a0\u7ed3\u70b9\nmaxHeap.append(val)\n// \u4ece\u5e95\u81f3\u9876\u5806\u5316\nsiftUp(i: size() - 1)\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u5e95\u81f3\u9876\u5806\u5316 */\nfunc siftUp(i: Int) {\nvar i = i\nwhile true {\n// \u83b7\u53d6\u7ed3\u70b9 i \u7684\u7236\u7ed3\u70b9\nlet p = parent(i: i)\n// \u5f53\u201c\u8d8a\u8fc7\u6839\u7ed3\u70b9\u201d\u6216\u201c\u7ed3\u70b9\u65e0\u9700\u4fee\u590d\u201d\u65f6\uff0c\u7ed3\u675f\u5806\u5316\nif p < 0 || maxHeap[i] <= maxHeap[p] {\nbreak\n}\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nswap(i: i, j: p)\n// \u5faa\u73af\u5411\u4e0a\u5806\u5316\ni = p\n}\n}\n
my_heap.zig
\n
"},{"location":"chapter_heap/heap/#_4","title":"\u5806\u9876\u5143\u7d20\u51fa\u5806","text":"

\u5806\u9876\u5143\u7d20\u662f\u4e8c\u53c9\u6811\u6839\u7ed3\u70b9\uff0c\u5373\u5217\u8868\u9996\u5143\u7d20\uff0c\u5982\u679c\u6211\u4eec\u76f4\u63a5\u5c06\u9996\u5143\u7d20\u4ece\u5217\u8868\u4e2d\u5220\u9664\uff0c\u5219\u4e8c\u53c9\u6811\u4e2d\u6240\u6709\u7ed3\u70b9\u90fd\u4f1a\u968f\u4e4b\u53d1\u751f\u79fb\u4f4d\uff08\u7d22\u5f15\u53d1\u751f\u53d8\u5316\uff09\uff0c\u8fd9\u6837\u540e\u7eed\u4f7f\u7528\u5806\u5316\u4fee\u590d\u5c31\u5f88\u9ebb\u70e6\u4e86\u3002\u4e3a\u4e86\u5c3d\u91cf\u51cf\u5c11\u5143\u7d20\u7d22\u5f15\u53d8\u52a8\uff0c\u91c7\u53d6\u4ee5\u4e0b\u64cd\u4f5c\u6b65\u9aa4\uff1a

  1. \u4ea4\u6362\u5806\u9876\u5143\u7d20\u4e0e\u5806\u5e95\u5143\u7d20\uff08\u5373\u4ea4\u6362\u6839\u7ed3\u70b9\u4e0e\u6700\u53f3\u53f6\u7ed3\u70b9\uff09\uff1b
  2. \u4ea4\u6362\u5b8c\u6210\u540e\uff0c\u5c06\u5806\u5e95\u4ece\u5217\u8868\u4e2d\u5220\u9664\uff08\u6ce8\u610f\uff0c\u56e0\u4e3a\u5df2\u7ecf\u4ea4\u6362\uff0c\u5b9e\u9645\u4e0a\u5220\u9664\u7684\u662f\u539f\u6765\u7684\u5806\u9876\u5143\u7d20\uff09\uff1b
  3. \u4ece\u6839\u7ed3\u70b9\u5f00\u59cb\uff0c\u4ece\u9876\u81f3\u5e95\u6267\u884c\u5806\u5316\uff1b

\u987e\u540d\u601d\u4e49\uff0c\u4ece\u9876\u81f3\u5e95\u5806\u5316\u7684\u64cd\u4f5c\u65b9\u5411\u4e0e\u4ece\u5e95\u81f3\u9876\u5806\u5316\u76f8\u53cd\uff0c\u6211\u4eec\u6bd4\u8f83\u6839\u7ed3\u70b9\u7684\u503c\u4e0e\u5176\u4e24\u4e2a\u5b50\u7ed3\u70b9\u7684\u503c\uff0c\u5c06\u6700\u5927\u7684\u5b50\u7ed3\u70b9\u4e0e\u6839\u7ed3\u70b9\u6267\u884c\u4ea4\u6362\uff0c\u5e76\u5faa\u73af\u4ee5\u4e0a\u64cd\u4f5c\uff0c\u76f4\u5230\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u65f6\u7ed3\u675f\uff0c\u6216\u5f53\u9047\u5230\u65e0\u9700\u4ea4\u6362\u7684\u7ed3\u70b9\u65f6\u63d0\u524d\u7ed3\u675f\u3002

Step 1Step 2Step 3Step 4Step 5Step 6Step 7Step 8Step 9Step 10

\u4e0e\u5143\u7d20\u5165\u5806\u64cd\u4f5c\u7c7b\u4f3c\uff0c\u5806\u9876\u5143\u7d20\u51fa\u5806\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(\\log n)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig my_heap.java
/* \u5143\u7d20\u51fa\u5806 */\nint poll() {\n// \u5224\u7a7a\u5904\u7406\nif (isEmpty())\nthrow new EmptyStackException();\n// \u4ea4\u6362\u6839\u7ed3\u70b9\u4e0e\u6700\u53f3\u53f6\u7ed3\u70b9\uff08\u5373\u4ea4\u6362\u9996\u5143\u7d20\u4e0e\u5c3e\u5143\u7d20\uff09\nswap(0, size() - 1);\n// \u5220\u9664\u7ed3\u70b9\nint val = maxHeap.remove(size() - 1);\n// \u4ece\u9876\u81f3\u5e95\u5806\u5316\nsiftDown(0);\n// \u8fd4\u56de\u5806\u9876\u5143\u7d20\nreturn val;\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u9876\u81f3\u5e95\u5806\u5316 */\nvoid siftDown(int i) {\nwhile (true) {\n// \u5224\u65ad\u7ed3\u70b9 i, l, r \u4e2d\u503c\u6700\u5927\u7684\u7ed3\u70b9\uff0c\u8bb0\u4e3a ma\nint l = left(i), r = right(i), ma = i;\nif (l < size() && maxHeap.get(l) > maxHeap.get(ma))\nma = l;\nif (r < size() && maxHeap.get(r) > maxHeap.get(ma))\nma = r;\n// \u82e5\u7ed3\u70b9 i \u6700\u5927\u6216\u7d22\u5f15 l, r \u8d8a\u754c\uff0c\u5219\u65e0\u9700\u7ee7\u7eed\u5806\u5316\uff0c\u8df3\u51fa\nif (ma == i) break;\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nswap(i, ma);\n// \u5faa\u73af\u5411\u4e0b\u5806\u5316\ni = ma;\n}\n}\n
my_heap.cpp
/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u9876\u81f3\u5e95\u5806\u5316 */\nvoid shifDown(int i) {\nwhile (true) {\n// \u5224\u65ad\u7ed3\u70b9 i, l, r \u4e2d\u503c\u6700\u5927\u7684\u7ed3\u70b9\uff0c\u8bb0\u4e3a ma\nint l = left(i), r = right(i), ma = i;\n// \u82e5\u7ed3\u70b9 i \u6700\u5927\u6216\u7d22\u5f15 l, r \u8d8a\u754c\uff0c\u5219\u65e0\u9700\u7ee7\u7eed\u5806\u5316\uff0c\u8df3\u51fa\nif (l < size() && maxHeap[l] > maxHeap[ma]) ma = l;\nif (r < size() && maxHeap[r] > maxHeap[ma])  ma = r;\n// \u82e5\u7ed3\u70b9 i \u6700\u5927\u6216\u7d22\u5f15 l, r \u8d8a\u754c\uff0c\u5219\u65e0\u9700\u7ee7\u7eed\u5806\u5316\uff0c\u8df3\u51fa\nif (ma == i) break;\nswap(maxHeap[i], maxHeap[ma]);\n// \u5faa\u73af\u5411\u4e0b\u5806\u5316\ni = ma;\n}\n}\n/* \u5143\u7d20\u51fa\u5806 */\nvoid poll() {\n// \u5224\u7a7a\u5904\u7406\nif (empty()) {\ncout << \"Error:\u5806\u4e3a\u7a7a\" << endl;\nreturn;\n}\n// \u4ea4\u6362\u6839\u7ed3\u70b9\u4e0e\u6700\u53f3\u53f6\u7ed3\u70b9\uff08\u5373\u4ea4\u6362\u9996\u5143\u7d20\u4e0e\u5c3e\u5143\u7d20\uff09\nswap(maxHeap[0], maxHeap[size() - 1]);\n// \u5220\u9664\u7ed3\u70b9\nmaxHeap.pop_back();\n// \u4ece\u9876\u81f3\u5e95\u5806\u5316\nshifDown(0);\n}\n
my_heap.py
\n
my_heap.go
/* \u5143\u7d20\u51fa\u5806 */\nfunc (h *maxHeap) poll() any {\n// \u5224\u7a7a\u5904\u7406\nif h.isEmpty() {\nfmt.Println(\"error\")\nreturn nil\n}\n// \u4ea4\u6362\u6839\u7ed3\u70b9\u4e0e\u6700\u53f3\u53f6\u7ed3\u70b9\uff08\u5373\u4ea4\u6362\u9996\u5143\u7d20\u4e0e\u5c3e\u5143\u7d20\uff09\nh.swap(0, h.size()-1)\n// \u5220\u9664\u7ed3\u70b9\nval := h.data[len(h.data)-1]\nh.data = h.data[:len(h.data)-1]\n// \u4ece\u9876\u81f3\u5e95\u5806\u5316\nh.siftDown(0)\n// \u8fd4\u56de\u5806\u9876\u5143\u7d20\nreturn val\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u9876\u81f3\u5e95\u5806\u5316 */\nfunc (h *maxHeap) siftDown(i int) {\nfor true {\n// \u5224\u65ad\u7ed3\u70b9 i, l, r \u4e2d\u503c\u6700\u5927\u7684\u7ed3\u70b9\uff0c\u8bb0\u4e3a max\nl, r, max := h.left(i), h.right(i), i\nif l < h.size() && h.data[l].(int) > h.data[max].(int) {\nmax = l\n}\nif r < h.size() && h.data[r].(int) > h.data[max].(int) {\nmax = r\n}\n// \u82e5\u7ed3\u70b9 i \u6700\u5927\u6216\u7d22\u5f15 l, r \u8d8a\u754c\uff0c\u5219\u65e0\u9700\u7ee7\u7eed\u5806\u5316\uff0c\u8df3\u51fa\nif max == i {\nbreak\n}\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nh.swap(i, max)\n// \u5faa\u73af\u5411\u4e0b\u5806\u5316\ni = max\n}\n}\n
my_heap.js
\n
my_heap.ts
\n
my_heap.c
\n
my_heap.cs
\n
my_heap.swift
/* \u5143\u7d20\u51fa\u5806 */\nfunc poll() -> Int {\n// \u5224\u7a7a\u5904\u7406\nif isEmpty() {\nfatalError(\"\u5806\u4e3a\u7a7a\")\n}\n// \u4ea4\u6362\u6839\u7ed3\u70b9\u4e0e\u6700\u53f3\u53f6\u7ed3\u70b9\uff08\u5373\u4ea4\u6362\u9996\u5143\u7d20\u4e0e\u5c3e\u5143\u7d20\uff09\nswap(i: 0, j: size() - 1)\n// \u5220\u9664\u7ed3\u70b9\nlet val = maxHeap.remove(at: size() - 1)\n// \u4ece\u9876\u81f3\u5e95\u5806\u5316\nsiftDown(i: 0)\n// \u8fd4\u56de\u5806\u9876\u5143\u7d20\nreturn val\n}\n/* \u4ece\u7ed3\u70b9 i \u5f00\u59cb\uff0c\u4ece\u9876\u81f3\u5e95\u5806\u5316 */\nfunc siftDown(i: Int) {\nvar i = i\nwhile true {\n// \u5224\u65ad\u7ed3\u70b9 i, l, r \u4e2d\u503c\u6700\u5927\u7684\u7ed3\u70b9\uff0c\u8bb0\u4e3a ma\nlet l = left(i: i)\nlet r = right(i: i)\nvar ma = i\nif l < size(), maxHeap[l] > maxHeap[ma] {\nma = l\n}\nif r < size(), maxHeap[r] > maxHeap[ma] {\nma = r\n}\n// \u82e5\u7ed3\u70b9 i \u6700\u5927\u6216\u7d22\u5f15 l, r \u8d8a\u754c\uff0c\u5219\u65e0\u9700\u7ee7\u7eed\u5806\u5316\uff0c\u8df3\u51fa\nif ma == i {\nbreak\n}\n// \u4ea4\u6362\u4e24\u7ed3\u70b9\nswap(i: i, j: ma)\n// \u5faa\u73af\u5411\u4e0b\u5806\u5316\ni = ma\n}\n}\n
my_heap.zig
\n
"},{"location":"chapter_heap/heap/#_5","title":"\u8f93\u5165\u6570\u636e\u5e76\u5efa\u5806 *","text":"

\u5982\u679c\u6211\u4eec\u60f3\u8981\u76f4\u63a5\u8f93\u5165\u4e00\u4e2a\u5217\u8868\u5e76\u5c06\u5176\u5efa\u5806\uff0c\u90a3\u4e48\u8be5\u600e\u4e48\u505a\u5462\uff1f\u6700\u76f4\u63a5\u5730\uff0c\u8003\u8651\u4f7f\u7528\u300c\u5143\u7d20\u5165\u5806\u300d\u65b9\u6cd5\uff0c\u5c06\u5217\u8868\u5143\u7d20\u4f9d\u6b21\u5165\u5806\u3002\u5143\u7d20\u5165\u5806\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \uff0c\u800c\u5e73\u5747\u957f\u5ea6\u4e3a \\(\\frac{n}{2}\\) \uff0c\u56e0\u6b64\u8be5\u65b9\u6cd5\u7684\u603b\u4f53\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n \\log n)\\) \u3002

\u7136\u800c\uff0c\u5b58\u5728\u4e00\u79cd\u66f4\u52a0\u4f18\u96c5\u7684\u5efa\u5806\u65b9\u6cd5\u3002\u8bbe\u7ed3\u70b9\u6570\u91cf\u4e3a \\(n\\) \uff0c\u6211\u4eec\u5148\u5c06\u5217\u8868\u6240\u6709\u5143\u7d20\u539f\u5c01\u4e0d\u52a8\u6dfb\u52a0\u8fdb\u5806\uff0c\u7136\u540e\u8fed\u4ee3\u5730\u5bf9\u5404\u4e2a\u7ed3\u70b9\u6267\u884c\u300c\u4ece\u9876\u81f3\u5e95\u5806\u5316\u300d\u3002\u5f53\u7136\uff0c\u65e0\u9700\u5bf9\u53f6\u7ed3\u70b9\u6267\u884c\u5806\u5316\uff0c\u56e0\u4e3a\u5176\u6ca1\u6709\u5b50\u7ed3\u70b9\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig my_heap.java
/* \u6784\u9020\u51fd\u6570\uff0c\u6839\u636e\u8f93\u5165\u5217\u8868\u5efa\u5806 */\nMaxHeap(List<Integer> nums) {\n// \u5c06\u5217\u8868\u5143\u7d20\u539f\u5c01\u4e0d\u52a8\u6dfb\u52a0\u8fdb\u5806\nmaxHeap = new ArrayList<>(nums);\n// \u5806\u5316\u9664\u53f6\u7ed3\u70b9\u4ee5\u5916\u7684\u5176\u4ed6\u6240\u6709\u7ed3\u70b9\nfor (int i = parent(size() - 1); i >= 0; i--) {\nsiftDown(i);\n}\n}\n
my_heap.cpp
/* \u6784\u9020\u51fd\u6570\uff0c\u6839\u636e\u8f93\u5165\u5217\u8868\u5efa\u5806 */\nMaxHeap(vector<int> nums) {\n// \u5c06\u5217\u8868\u5143\u7d20\u539f\u5c01\u4e0d\u52a8\u6dfb\u52a0\u8fdb\u5806\nmaxHeap = nums;\n// \u5806\u5316\u9664\u53f6\u7ed3\u70b9\u4ee5\u5916\u7684\u5176\u4ed6\u6240\u6709\u7ed3\u70b9\nfor (int i = parent(size() - 1); i >= 0; i--) {\nshifDown(i);\n}\n}\n
my_heap.py
\n
my_heap.go
/* \u6784\u9020\u51fd\u6570\uff0c\u6839\u636e\u5207\u7247\u5efa\u5806 */\nfunc newMaxHeap(nums []any) *maxHeap {\n// \u5c06\u5217\u8868\u5143\u7d20\u539f\u5c01\u4e0d\u52a8\u6dfb\u52a0\u8fdb\u5806\nh := &maxHeap{data: nums}\n// \u5806\u5316\u9664\u53f6\u7ed3\u70b9\u4ee5\u5916\u7684\u5176\u4ed6\u6240\u6709\u7ed3\u70b9\nfor i := len(h.data) - 1; i >= 0; i-- {\nh.siftDown(i)\n}\nreturn h\n}\n
my_heap.js
\n
my_heap.ts
\n
my_heap.c
\n
my_heap.cs
\n
my_heap.swift
/* \u6784\u9020\u51fd\u6570\uff0c\u6839\u636e\u8f93\u5165\u5217\u8868\u5efa\u5806 */\ninit(nums: [Int]) {\n// \u5c06\u5217\u8868\u5143\u7d20\u539f\u5c01\u4e0d\u52a8\u6dfb\u52a0\u8fdb\u5806\nmaxHeap = nums\n// \u5806\u5316\u9664\u53f6\u7ed3\u70b9\u4ee5\u5916\u7684\u5176\u4ed6\u6240\u6709\u7ed3\u70b9\nfor i in stride(from: parent(i: size() - 1), through: 0, by: -1) {\nsiftDown(i: i)\n}\n}\n
my_heap.zig
\n

\u90a3\u4e48\uff0c\u7b2c\u4e8c\u79cd\u5efa\u5806\u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u65f6\u591a\u5c11\u5462\uff1f\u6211\u4eec\u6765\u505a\u4e00\u4e0b\u7b80\u5355\u63a8\u7b97\u3002

  • \u5b8c\u5168\u4e8c\u53c9\u6811\u4e2d\uff0c\u8bbe\u7ed3\u70b9\u603b\u6570\u4e3a \\(n\\) \uff0c\u5219\u53f6\u7ed3\u70b9\u6570\u91cf\u4e3a \\((n + 1) / 2\\) \uff0c\u5176\u4e2d \\(/\\) \u4e3a\u5411\u4e0b\u6574\u9664\u3002\u56e0\u6b64\u5728\u6392\u9664\u53f6\u7ed3\u70b9\u540e\uff0c\u9700\u8981\u5806\u5316\u7ed3\u70b9\u6570\u91cf\u4e3a \\((n - 1)/2\\) \uff0c\u5373\u4e3a \\(O(n)\\) \uff1b
  • \u4ece\u9876\u81f3\u5e95\u5806\u5316\u4e2d\uff0c\u6bcf\u4e2a\u7ed3\u70b9\u6700\u591a\u5806\u5316\u81f3\u53f6\u7ed3\u70b9\uff0c\u56e0\u6b64\u6700\u5927\u8fed\u4ee3\u6b21\u6570\u4e3a\u4e8c\u53c9\u6811\u9ad8\u5ea6 \\(O(\\log n)\\) \uff1b

\u5c06\u4e0a\u8ff0\u4e24\u8005\u76f8\u4e58\uff0c\u53ef\u5f97\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n \\log n)\\) \u3002\u7136\u800c\uff0c\u8be5\u4f30\u7b97\u7ed3\u679c\u4ecd\u4e0d\u591f\u51c6\u786e\uff0c\u56e0\u4e3a\u6211\u4eec\u6ca1\u6709\u8003\u8651\u5230 \u4e8c\u53c9\u6811\u5e95\u5c42\u7ed3\u70b9\u8fdc\u591a\u4e8e\u9876\u5c42\u7ed3\u70b9 \u7684\u6027\u8d28\u3002

\u4e0b\u9762\u6211\u4eec\u6765\u5c1d\u8bd5\u5c55\u5f00\u8ba1\u7b97\u3002\u4e3a\u4e86\u51cf\u5c0f\u8ba1\u7b97\u96be\u5ea6\uff0c\u6211\u4eec\u5047\u8bbe\u6811\u662f\u4e00\u4e2a\u300c\u5b8c\u7f8e\u4e8c\u53c9\u6811\u300d\uff0c\u8be5\u5047\u8bbe\u4e0d\u4f1a\u5f71\u54cd\u8ba1\u7b97\u7ed3\u679c\u7684\u6b63\u786e\u6027\u3002\u8bbe\u4e8c\u53c9\u6811\uff08\u5373\u5806\uff09\u7ed3\u70b9\u6570\u91cf\u4e3a \\(n\\) \uff0c\u6811\u9ad8\u5ea6\u4e3a \\(h\\) \u3002\u4e0a\u6587\u63d0\u5230\uff0c\u7ed3\u70b9\u5806\u5316\u6700\u5927\u8fed\u4ee3\u6b21\u6570\u7b49\u4e8e\u8be5\u7ed3\u70b9\u5230\u53f6\u7ed3\u70b9\u7684\u8ddd\u79bb\uff0c\u800c\u8fd9\u6b63\u662f\u201c\u7ed3\u70b9\u9ad8\u5ea6\u201d\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5c06\u5404\u5c42\u7684\u201c\u7ed3\u70b9\u6570\u91cf \\(\\times\\) \u7ed3\u70b9\u9ad8\u5ea6\u201d\u6c42\u548c\uff0c\u5373\u53ef\u5f97\u5230\u6240\u6709\u7ed3\u70b9\u7684\u5806\u5316\u7684\u8fed\u4ee3\u6b21\u6570\u603b\u548c\u3002

\\[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \\cdots + 2^{(h-1)}\\times1 \\]

\u5316\u7b80\u4e0a\u5f0f\u9700\u8981\u501f\u52a9\u4e2d\u5b66\u7684\u6570\u5217\u77e5\u8bc6\uff0c\u5148\u5bf9 \\(T(h)\\) \u4e58\u4ee5 \\(2\\) \uff0c\u6613\u5f97

\\[ \\begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \\cdots + 2^{h-1}\\times1 \\newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \\cdots + 2^{h}\\times1 \\newline \\end{aligned} \\]

\u4f7f\u7528\u9519\u4f4d\u76f8\u51cf\u6cd5\uff0c\u4ee4\u4e0b\u5f0f \\(2 T(h)\\) \u51cf\u53bb\u4e0a\u5f0f \\(T(h)\\) \uff0c\u53ef\u5f97

\\[ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \\cdots + 2^{h-1} + 2^h \\]

\u89c2\u5bdf\u4e0a\u5f0f\uff0c\\(T(h)\\) \u662f\u4e00\u4e2a\u7b49\u6bd4\u6570\u5217\uff0c\u53ef\u76f4\u63a5\u4f7f\u7528\u6c42\u548c\u516c\u5f0f\uff0c\u5f97\u5230\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a

\\[ \\begin{aligned} T(h) & = 2 \\frac{1 - 2^h}{1 - 2} - h \\newline & = 2^{h+1} - h \\newline & = O(2^h) \\end{aligned} \\]

\u8fdb\u4e00\u6b65\u5730\uff0c\u9ad8\u5ea6\u4e3a \\(h\\) \u7684\u5b8c\u7f8e\u4e8c\u53c9\u6811\u7684\u7ed3\u70b9\u6570\u91cf\u4e3a \\(n = 2^{h+1} - 1\\) \uff0c\u6613\u5f97\u590d\u6742\u5ea6\u4e3a \\(O(2^h) = O(n)\\)\u3002\u4ee5\u4e0a\u63a8\u7b97\u8868\u660e\uff0c\u8f93\u5165\u5217\u8868\u5e76\u5efa\u5806\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \uff0c\u975e\u5e38\u9ad8\u6548\u3002

"},{"location":"chapter_heap/heap/#814","title":"8.1.4. \u5806\u5e38\u89c1\u5e94\u7528","text":"
  • \u4f18\u5148\u961f\u5217\u3002\u5806\u5e38\u4f5c\u4e3a\u5b9e\u73b0\u4f18\u5148\u961f\u5217\u7684\u9996\u9009\u6570\u636e\u7ed3\u6784\uff0c\u5165\u961f\u548c\u51fa\u961f\u64cd\u4f5c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(\\log n)\\) \uff0c\u5efa\u961f\u64cd\u4f5c\u4e3a \\(O(n)\\) \uff0c\u7686\u975e\u5e38\u9ad8\u6548\u3002
  • \u5806\u6392\u5e8f\u3002\u7ed9\u5b9a\u4e00\u7ec4\u6570\u636e\uff0c\u6211\u4eec\u4f7f\u7528\u5176\u5efa\u5806\uff0c\u5e76\u4f9d\u6b21\u5168\u90e8\u5f39\u51fa\uff0c\u5219\u53ef\u4ee5\u5f97\u5230\u6709\u5e8f\u7684\u5e8f\u5217\u3002\u5f53\u7136\uff0c\u5806\u6392\u5e8f\u4e00\u822c\u65e0\u9700\u5f39\u51fa\u5143\u7d20\uff0c\u4ec5\u9700\u6bcf\u8f6e\u5c06\u5806\u9876\u5143\u7d20\u4ea4\u6362\u81f3\u6570\u7ec4\u5c3e\u90e8\u5e76\u51cf\u5c0f\u5806\u7684\u957f\u5ea6\u5373\u53ef\u3002
  • \u83b7\u53d6\u6700\u5927\u7684 \\(k\\) \u4e2a\u5143\u7d20\u3002\u8fd9\u65e2\u662f\u4e00\u9053\u7ecf\u5178\u7b97\u6cd5\u9898\u76ee\uff0c\u4e5f\u662f\u4e00\u79cd\u5e38\u89c1\u5e94\u7528\uff0c\u4f8b\u5982\u9009\u53d6\u70ed\u5ea6\u524d 10 \u7684\u65b0\u95fb\u4f5c\u4e3a\u5fae\u535a\u70ed\u641c\uff0c\u9009\u53d6\u524d 10 \u9500\u91cf\u7684\u5546\u54c1\u7b49\u3002
"},{"location":"chapter_introduction/algorithms_are_everywhere/","title":"1.1. \u7b97\u6cd5\u65e0\u5904\u4e0d\u5728","text":"

\u542c\u5230\u201c\u7b97\u6cd5\u201d\u8fd9\u4e2a\u8bcd\uff0c\u6211\u4eec\u4e00\u822c\u4f1a\u8054\u60f3\u5230\u6570\u5b66\u3002\u4f46\u5b9e\u9645\u4e0a\uff0c\u5927\u591a\u6570\u7b97\u6cd5\u5e76\u4e0d\u5305\u542b\u590d\u6742\u7684\u6570\u5b66\uff0c\u800c\u66f4\u50cf\u662f\u5728\u8003\u5bdf\u57fa\u672c\u903b\u8f91\uff0c\u800c\u8fd9\u4e9b\u903b\u8f91\u5728\u6211\u4eec\u65e5\u5e38\u751f\u6d3b\u4e2d\u5904\u5904\u53ef\u89c1\u3002

\u5728\u6b63\u5f0f\u4ecb\u7ecd\u7b97\u6cd5\u4e4b\u524d\uff0c\u6211\u60f3\u544a\u8bc9\u4f60\u4e00\u4ef6\u6709\u8da3\u7684\u4e8b\uff1a\u5176\u5b9e\uff0c\u4f60\u5728\u8fc7\u53bb\u5df2\u7ecf\u5b66\u4f1a\u4e86\u5f88\u591a\u7b97\u6cd5\uff0c\u5e76\u4e14\u5df2\u7ecf\u4e60\u60ef\u5c06\u5b83\u4eec\u5e94\u7528\u5230\u65e5\u5e38\u751f\u6d3b\u4e2d\u3002\u63a5\u4e0b\u6765\uff0c\u6211\u5c06\u4ecb\u7ecd\u4e24\u4e2a\u5177\u4f53\u4f8b\u5b50\u6765\u4f50\u8bc1\u3002

\u4f8b\u4e00\uff1a\u62fc\u79ef\u6728\u3002\u4e00\u5957\u79ef\u6728\uff0c\u9664\u4e86\u6709\u8bb8\u591a\u90e8\u4ef6\u4e4b\u5916\uff0c\u8fd8\u4f1a\u9644\u9001\u8be6\u7ec6\u7684\u62fc\u88c5\u8bf4\u660e\u4e66\u3002\u6211\u4eec\u6309\u7167\u8bf4\u660e\u4e66\u4e0a\u4e00\u6b65\u6b65\u64cd\u4f5c\uff0c\u5373\u53ef\u62fc\u51fa\u590d\u6742\u7684\u79ef\u6728\u6a21\u578b\u3002

\u5982\u679c\u4ece\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u89d2\u5ea6\u770b\uff0c\u5927\u5927\u5c0f\u5c0f\u7684\u300c\u79ef\u6728\u300d\u5c31\u662f\u6570\u636e\u7ed3\u6784\uff0c\u800c\u300c\u62fc\u88c5\u8bf4\u660e\u4e66\u300d\u4e0a\u7684\u4e00\u7cfb\u5217\u6b65\u9aa4\u5c31\u662f\u7b97\u6cd5\u3002

\u4f8b\u4e8c\uff1a\u67e5\u5b57\u5178\u3002\u5728\u5b57\u5178\u4e2d\uff0c\u6bcf\u4e2a\u6c49\u5b57\u90fd\u6709\u4e00\u4e2a\u5bf9\u5e94\u7684\u62fc\u97f3\uff0c\u800c\u5b57\u5178\u662f\u6309\u7167\u62fc\u97f3\u7684\u82f1\u6587\u5b57\u6bcd\u8868\u987a\u5e8f\u6392\u5217\u7684\u3002\u5047\u8bbe\u9700\u8981\u5728\u5b57\u5178\u4e2d\u67e5\u8be2\u4efb\u610f\u4e00\u4e2a\u62fc\u97f3\u9996\u5b57\u6bcd\u4e3a \\(r\\) \u7684\u5b57\uff0c\u4e00\u822c\u6211\u4eec\u4f1a\u8fd9\u6837\u505a\uff1a

  1. \u6253\u5f00\u5b57\u5178\u5927\u81f4\u4e00\u534a\u9875\u6570\u7684\u4f4d\u7f6e\uff0c\u67e5\u770b\u6b64\u9875\u7684\u9996\u5b57\u6bcd\u662f\u4ec0\u4e48\uff08\u5047\u8bbe\u4e3a \\(m\\) \uff09\uff1b
  2. \u7531\u4e8e\u5728\u82f1\u6587\u5b57\u6bcd\u8868\u4e2d \\(r\\) \u5728 \\(m\\) \u7684\u540e\u9762\uff0c\u56e0\u6b64\u5e94\u6392\u9664\u5b57\u5178\u524d\u534a\u90e8\u5206\uff0c\u67e5\u627e\u8303\u56f4\u4ec5\u5269\u540e\u534a\u90e8\u5206\uff1b
  3. \u5faa\u73af\u6267\u884c\u6b65\u9aa4 1-2 \uff0c\u76f4\u5230\u627e\u5230\u62fc\u97f3\u9996\u5b57\u6bcd\u4e3a \\(r\\) \u7684\u9875\u7801\u65f6\u7ec8\u6b62\u3002
Step 1Step 2Step 3Step 4Step 5

\u67e5\u5b57\u5178\u8fd9\u4e2a\u5c0f\u5b66\u751f\u7684\u6807\u914d\u6280\u80fd\uff0c\u5b9e\u9645\u4e0a\u5c31\u662f\u5927\u540d\u9f0e\u9f0e\u7684\u300c\u4e8c\u5206\u67e5\u627e\u300d\u3002\u4ece\u6570\u636e\u7ed3\u6784\u89d2\u5ea6\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5b57\u5178\u770b\u4f5c\u662f\u4e00\u4e2a\u5df2\u6392\u5e8f\u7684\u300c\u6570\u7ec4\u300d\uff1b\u800c\u4ece\u7b97\u6cd5\u89d2\u5ea6\uff0c\u6211\u4eec\u53ef\u5c06\u4e0a\u8ff0\u67e5\u5b57\u5178\u7684\u4e00\u7cfb\u5217\u6307\u4ee4\u770b\u4f5c\u662f\u300c\u4e8c\u5206\u67e5\u627e\u300d\u7b97\u6cd5\u3002

\u5c0f\u5230\u70f9\u996a\u4e00\u9053\u83dc\u3001\u5927\u5230\u661f\u9645\u822a\u884c\uff0c\u51e0\u4e4e\u6240\u6709\u95ee\u9898\u7684\u89e3\u51b3\u90fd\u79bb\u4e0d\u5f00\u7b97\u6cd5\u3002\u8ba1\u7b97\u673a\u7684\u51fa\u73b0\uff0c\u4f7f\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u7f16\u7a0b\u5c06\u6570\u636e\u7ed3\u6784\u5b58\u50a8\u5728\u5185\u5b58\u4e2d\uff0c\u4e5f\u53ef\u4ee5\u7f16\u5199\u4ee3\u7801\u6765\u8c03\u7528 CPU, GPU \u6267\u884c\u7b97\u6cd5\uff0c\u4ece\u800c\u5c06\u751f\u6d3b\u4e2d\u7684\u95ee\u9898\u642c\u8fd0\u5230\u8ba1\u7b97\u673a\u4e2d\uff0c\u66f4\u52a0\u9ad8\u6548\u5730\u89e3\u51b3\u5404\u5f0f\u5404\u6837\u7684\u590d\u6742\u95ee\u9898\u3002

Tip

\u8bfb\u5230\u8fd9\u91cc\uff0c\u5982\u679c\u4f60\u611f\u5230\u5bf9\u6570\u636e\u7ed3\u6784\u3001\u7b97\u6cd5\u3001\u6570\u7ec4\u3001\u4e8c\u5206\u67e5\u627e\u7b49\u6b64\u7c7b\u6982\u5ff5\u4e00\u77e5\u534a\u89e3\uff0c\u90a3\u4e48\u5c31\u592a\u597d\u4e86\uff01\u56e0\u4e3a\u8fd9\u6b63\u662f\u672c\u4e66\u5b58\u5728\u7684\u4ef7\u503c\uff0c\u63a5\u4e0b\u6765\uff0c\u672c\u4e66\u5c06\u4f1a\u4e00\u6b65\u6b65\u5730\u5f15\u5bfc\u4f60\u8fdb\u5165\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u77e5\u8bc6\u6bbf\u5802\u3002

"},{"location":"chapter_introduction/what_is_dsa/","title":"1.2. \u7b97\u6cd5\u662f\u4ec0\u4e48","text":""},{"location":"chapter_introduction/what_is_dsa/#121","title":"1.2.1. \u7b97\u6cd5\u5b9a\u4e49","text":"

\u300c\u7b97\u6cd5 Algorithm\u300d\u662f\u5728\u6709\u9650\u65f6\u95f4\u5185\u89e3\u51b3\u7279\u5b9a\u95ee\u9898\u7684\u4e00\u7ec4\u6307\u4ee4\u6216\u64cd\u4f5c\u6b65\u9aa4\u3002\u7b97\u6cd5\u5177\u6709\u4ee5\u4e0b\u7279\u6027\uff1a

  • \u95ee\u9898\u662f\u660e\u786e\u7684\uff0c\u9700\u8981\u62e5\u6709\u660e\u786e\u7684\u8f93\u5165\u548c\u8f93\u51fa\u5b9a\u4e49\u3002
  • \u89e3\u5177\u6709\u786e\u5b9a\u6027\uff0c\u5373\u7ed9\u5b9a\u76f8\u540c\u8f93\u5165\u65f6\uff0c\u8f93\u51fa\u4e00\u5b9a\u76f8\u540c\u3002
  • \u5177\u6709\u53ef\u884c\u6027\uff0c\u53ef\u5728\u6709\u9650\u6b65\u9aa4\u3001\u6709\u9650\u65f6\u95f4\u3001\u6709\u9650\u5185\u5b58\u7a7a\u95f4\u4e0b\u5b8c\u6210\u3002
  • \u72ec\u7acb\u4e8e\u7f16\u7a0b\u8bed\u8a00\uff0c\u5373\u53ef\u7528\u591a\u79cd\u8bed\u8a00\u5b9e\u73b0\u3002
"},{"location":"chapter_introduction/what_is_dsa/#122","title":"1.2.2. \u6570\u636e\u7ed3\u6784\u5b9a\u4e49","text":"

\u300c\u6570\u636e\u7ed3\u6784 Data Structure\u300d\u662f\u5728\u8ba1\u7b97\u673a\u4e2d\u7ec4\u7ec7\u4e0e\u5b58\u50a8\u6570\u636e\u7684\u65b9\u5f0f\u3002\u4e3a\u4e86\u63d0\u9ad8\u6570\u636e\u5b58\u50a8\u548c\u64cd\u4f5c\u6027\u80fd\uff0c\u6570\u636e\u7ed3\u6784\u7684\u8bbe\u8ba1\u539f\u5219\u6709\uff1a

  • \u7a7a\u95f4\u5360\u7528\u5c3d\u53ef\u80fd\u5c0f\uff0c\u8282\u7701\u8ba1\u7b97\u673a\u5185\u5b58\u3002
  • \u6570\u636e\u64cd\u4f5c\u5c3d\u91cf\u5feb\uff0c\u5305\u62ec\u6570\u636e\u8bbf\u95ee\u3001\u6dfb\u52a0\u3001\u5220\u9664\u3001\u66f4\u65b0\u7b49\u3002
  • \u63d0\u4f9b\u7b80\u6d01\u7684\u6570\u636e\u8868\u793a\u548c\u903b\u8f91\u4fe1\u606f\uff0c\u4ee5\u4fbf\u7b97\u6cd5\u9ad8\u6548\u8fd0\u884c\u3002

\u6570\u636e\u7ed3\u6784\u7684\u8bbe\u8ba1\u662f\u4e00\u4e2a\u5145\u6ee1\u6743\u8861\u7684\u8fc7\u7a0b\uff0c\u8fd9\u610f\u5473\u7740\u5982\u679c\u83b7\u5f97\u67d0\u65b9\u9762\u7684\u4f18\u52bf\uff0c\u5219\u5f80\u5f80\u9700\u8981\u5728\u53e6\u4e00\u65b9\u9762\u505a\u51fa\u59a5\u534f\u3002\u4f8b\u5982\uff0c\u94fe\u8868\u76f8\u5bf9\u4e8e\u6570\u7ec4\uff0c\u6570\u636e\u6dfb\u52a0\u5220\u9664\u64cd\u4f5c\u66f4\u52a0\u65b9\u4fbf\uff0c\u4f46\u727a\u7272\u4e86\u6570\u636e\u7684\u8bbf\u95ee\u901f\u5ea6\uff1b\u56fe\u76f8\u5bf9\u4e8e\u94fe\u8868\uff0c\u63d0\u4f9b\u4e86\u66f4\u591a\u7684\u903b\u8f91\u4fe1\u606f\uff0c\u4f46\u9700\u8981\u5360\u7528\u66f4\u591a\u7684\u5185\u5b58\u7a7a\u95f4\u3002

"},{"location":"chapter_introduction/what_is_dsa/#123","title":"1.2.3. \u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u5173\u7cfb","text":"

\u300c\u6570\u636e\u7ed3\u6784\u300d\u4e0e\u300c\u7b97\u6cd5\u300d\u662f\u9ad8\u5ea6\u76f8\u5173\u3001\u7d27\u5bc6\u5d4c\u5408\u7684\uff0c\u4f53\u73b0\u5728\uff1a

  • \u6570\u636e\u7ed3\u6784\u662f\u7b97\u6cd5\u7684\u5e95\u5ea7\u3002\u6570\u636e\u7ed3\u6784\u4e3a\u7b97\u6cd5\u63d0\u4f9b\u7ed3\u6784\u5316\u5b58\u50a8\u7684\u6570\u636e\uff0c\u4ee5\u53ca\u64cd\u4f5c\u6570\u636e\u7684\u5bf9\u5e94\u65b9\u6cd5\u3002
  • \u7b97\u6cd5\u662f\u53d1\u6325\u6570\u636e\u7ed3\u6784\u4f18\u52bf\u7684\u821e\u53f0\u3002\u6570\u636e\u7ed3\u6784\u4ec5\u5b58\u50a8\u6570\u636e\u4fe1\u606f\uff0c\u7ed3\u5408\u7b97\u6cd5\u624d\u53ef\u89e3\u51b3\u7279\u5b9a\u95ee\u9898\u3002
  • \u7b97\u6cd5\u6709\u5bf9\u5e94\u6700\u4f18\u7684\u6570\u636e\u7ed3\u6784\u3002\u7ed9\u5b9a\u7b97\u6cd5\uff0c\u4e00\u822c\u53ef\u57fa\u4e8e\u4e0d\u540c\u7684\u6570\u636e\u7ed3\u6784\u5b9e\u73b0\uff0c\u800c\u6700\u7ec8\u6267\u884c\u6548\u7387\u5f80\u5f80\u76f8\u5dee\u5f88\u5927\u3002

Fig. \u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u5173\u7cfb

\u5982\u679c\u5c06\u300cLEGO \u4e50\u9ad8\u300d\u7c7b\u6bd4\u5230\u300c\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u300d\uff0c\u90a3\u4e48\u53ef\u4ee5\u5f97\u5230\u4e0b\u8868\u6240\u793a\u7684\u5bf9\u5e94\u5173\u7cfb\u3002

\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5 LEGO \u4e50\u9ad8 \u8f93\u5165\u6570\u636e \u672a\u62fc\u88c5\u7684\u79ef\u6728 \u6570\u636e\u7ed3\u6784 \u79ef\u6728\u7ec4\u7ec7\u5f62\u5f0f\uff0c\u5305\u62ec\u5f62\u72b6\u3001\u5927\u5c0f\u3001\u8fde\u63a5\u65b9\u5f0f\u7b49 \u7b97\u6cd5 \u628a\u79ef\u6728\u62fc\u6210\u76ee\u6807\u5f62\u6001\u7684\u4e00\u7cfb\u5217\u64cd\u4f5c\u6b65\u9aa4 \u8f93\u51fa\u6570\u636e \u79ef\u6728\u6a21\u578b

\u7ea6\u5b9a\u4fd7\u6210\u7684\u7b80\u79f0

\u5728\u5b9e\u9645\u8ba8\u8bba\u4e2d\uff0c\u6211\u4eec\u901a\u5e38\u4f1a\u5c06\u300c\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u300d\u76f4\u63a5\u7b80\u79f0\u4e3a\u300c\u7b97\u6cd5\u300d\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u719f\u79f0\u7684 LeetCode \u7b97\u6cd5\u9898\u76ee\uff0c\u5b9e\u9645\u4e0a\u540c\u65f6\u8003\u5bdf\u4e86\u6570\u636e\u7ed3\u6784\u548c\u7b97\u6cd5\u4e24\u90e8\u5206\u77e5\u8bc6\u3002

"},{"location":"chapter_preface/about_the_book/","title":"0.1. \u5173\u4e8e\u672c\u4e66","text":"

\u4e94\u5e74\u524d\u53d1\u751f\u7684\u4e00\u4ef6\u4e8b\uff0c\u6210\u4e3a\u4e86\u6211\u804c\u4e1a\u751f\u6daf\u7684\u91cd\u8981\u8f6c\u6298\u70b9\u3002\u5f53\u65f6\u7684\u6211\u5728\u4ea4\u5927\u8bfb\u7814\uff0c\u5bf9\u4e92\u8054\u7f51\u6c42\u804c\u4e00\u65e0\u6240\u77e5\uff0c\u4f46\u4ecd\u7136\u786c\u7740\u5934\u76ae\u7533\u8bf7\u4e86 Microsoft \u8f6f\u4ef6\u5de5\u7a0b\u5e08\u5b9e\u4e60\u3002\u9762\u8bd5\u5b98\u8ba9\u6211\u5728\u767d\u677f\u4e0a\u5199\u51fa\u201c\u5feb\u901f\u6392\u5e8f\u201d\u4ee3\u7801\uff0c\u6211\u754f\u754f\u7f29\u7f29\u5730\u5199\u4e86\u4e00\u4e2a\u201c\u5192\u6ce1\u6392\u5e8f\u201d\uff0c\u5e76\u4e14\u8fd8\u5199\u9519\u4e86(ToT) \u3002\u4ece\u9762\u8bd5\u5b98\u7684\u8868\u60c5\u4e0a\uff0c\u6211\u770b\u5230\u4e86\u4e00\u4e2a\u5927\u5927\u7684 \"GG\" \u3002

\u6b64\u6b21\u5931\u5229\u5012\u903c\u6211\u5f00\u59cb\u5237\u7b97\u6cd5\u9898\u3002\u6211\u91c7\u7528\u201c\u626b\u96f7\u6e38\u620f\u201d\u5f0f\u7684\u5b66\u4e60\u65b9\u6cd5\uff0c\u4e24\u773c\u4e00\u62b9\u9ed1\u5237\u9898\uff0c\u626b\u5230\u4e0d\u4f1a\u7684\u201c\u96f7\u201d\u5c31\u901a\u8fc7\u67e5\u8d44\u6599\u628a\u5b83\u201c\u6392\u6389\u201d\uff0c\u914d\u5408\u5468\u671f\u6027\u603b\u7ed3\uff0c\u9010\u6e10\u5f62\u6210\u4e86\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u77e5\u8bc6\u56fe\u666f\u3002\u5e78\u8fd0\u5730\uff0c\u6211\u5728\u79cb\u62db\u65a9\u83b7\u4e86\u591a\u5bb6\u5927\u5382\u7684 Offer \u3002

\u56de\u60f3\u81ea\u5df1\u5f53\u521d\u5728\u201c\u626b\u96f7\u5f0f\u201d\u5237\u9898\u4e2d\u88ab\u70b8\u7684\u6ee1\u5934\u5305\u7684\u75db\u82e6\uff0c\u601d\u8003\u826f\u4e45\uff0c\u6211\u610f\u8bc6\u5230\u4e00\u672c\u201c\u524d\u671f\u5237\u9898\u5fc5\u770b\u201d\u7684\u8bfb\u7269\u53ef\u4ee5\u4f7f\u7b97\u6cd5\u5c0f\u767d\u5c11\u8d70\u8bb8\u591a\u5f2f\u8def\u3002\u5199\u4f5c\u610f\u613f\u6eda\u6eda\u88ad\u6765\uff0c\u90a3\u5c31\u52a8\u7b14\u5427\uff1a

Hello\uff0c\u7b97\u6cd5\uff01"},{"location":"chapter_preface/about_the_book/#011","title":"0.1.1. \u8bfb\u8005\u5bf9\u8c61","text":"

\u524d\u7f6e\u6761\u4ef6

\u60a8\u9700\u8981\u81f3\u5c11\u5177\u5907\u4efb\u4e00\u8bed\u8a00\u7684\u7f16\u7a0b\u57fa\u7840\uff0c\u80fd\u591f\u9605\u8bfb\u548c\u7f16\u5199\u7b80\u5355\u4ee3\u7801\u3002

\u5982\u679c\u60a8\u662f \u7b97\u6cd5\u521d\u5b66\u8005\uff0c\u5b8c\u5168\u6ca1\u6709\u63a5\u89e6\u8fc7\u7b97\u6cd5\uff0c\u6216\u8005\u5df2\u7ecf\u6709\u5c11\u91cf\u5237\u9898\uff0c\u5bf9\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u6709\u6726\u80e7\u7684\u7406\u89e3\uff0c\u5728\u4f1a\u4e0e\u4e0d\u4f1a\u4e4b\u95f4\u53cd\u590d\u6a2a\u8df3\uff0c\u90a3\u4e48\u8fd9\u672c\u4e66\u5c31\u662f\u4e3a\u60a8\u800c\u5199\uff01\u672c\u4e66\u80fd\u591f\u5e26\u6765\uff1a

  • \u4e86\u89e3\u5237\u9898\u6240\u9700\u7684 \u6570\u636e\u7ed3\u6784\uff0c\u5305\u62ec\u5e38\u7528\u64cd\u4f5c\u3001\u4f18\u52bf\u548c\u52a3\u52bf\u3001\u5178\u578b\u5e94\u7528\u3001\u5b9e\u73b0\u65b9\u6cd5\u7b49\u3002
  • \u5b66\u4e60\u5404\u7c7b \u7b97\u6cd5\uff0c\u4ecb\u7ecd\u7b97\u6cd5\u7684\u8bbe\u8ba1\u601d\u60f3\u3001\u8fd0\u884c\u6548\u7387\u3001\u4f18\u52bf\u52a3\u52bf\u3001\u5b9e\u73b0\u65b9\u6cd5\u7b49\u3002
  • \u53ef\u4e00\u952e\u8fd0\u884c\u7684 \u914d\u5957\u4ee3\u7801\uff0c\u5305\u542b\u8be6\u7ec6\u6ce8\u91ca\uff0c\u5e2e\u52a9\u4f60\u901a\u8fc7\u5b9e\u8df5\u52a0\u6df1\u7406\u89e3\u3002

\u5982\u679c\u60a8\u662f \u7b97\u6cd5\u719f\u7ec3\u5de5\uff0c\u5df2\u7ecf\u79ef\u7d2f\u4e00\u5b9a\u5237\u9898\u91cf\uff0c\u63a5\u89e6\u8fc7\u5927\u591a\u6570\u9898\u578b\uff0c\u90a3\u4e48\u672c\u4e66\u5185\u5bb9\u5bf9\u4f60\u6765\u8bf4\u53ef\u80fd\u7a0d\u663e\u57fa\u7840\uff0c\u4f46\u4ecd\u80fd\u591f\u5e26\u6765\u4ee5\u4e0b\u4ef7\u503c\uff1a

  • \u672c\u4e66\u7bc7\u5e45\u4e0d\u957f\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u63d0\u7eb2\u6308\u9886\u5730\u56de\u987e\u7b97\u6cd5\u77e5\u8bc6\u3002
  • \u4e66\u4e2d\u5305\u542b\u8bb8\u591a\u5bf9\u6bd4\u6027\u3001\u603b\u7ed3\u6027\u7684\u7b97\u6cd5\u5185\u5bb9\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4f60\u68b3\u7406\u7b97\u6cd5\u77e5\u8bc6\u4f53\u7cfb\u3002
  • \u6e90\u4ee3\u7801\u5b9e\u73b0\u4e86\u5404\u79cd\u7ecf\u5178\u6570\u636e\u7ed3\u6784\u548c\u7b97\u6cd5\uff0c\u53ef\u4ee5\u4f5c\u4e3a\u201c\u5237\u9898\u5de5\u5177\u5e93\u201d\u6765\u4f7f\u7528\u3002

\u5982\u679c\u60a8\u662f \u7b97\u6cd5\u5927\u4f6c\uff0c\u8bf7\u53d7\u6211\u819c\u62dc\uff01\u5e0c\u671b\u60a8\u53ef\u4ee5\u62bd\u65f6\u95f4\u63d0\u51fa\u610f\u89c1\u5efa\u8bae\uff0c\u6216\u8005\u4e00\u8d77\u53c2\u4e0e\u521b\u4f5c\uff0c\u5e2e\u52a9\u5404\u4f4d\u540c\u5b66\u83b7\u53d6\u66f4\u597d\u7684\u5b66\u4e60\u5185\u5bb9\uff0c\u611f\u8c22\uff01

"},{"location":"chapter_preface/about_the_book/#012","title":"0.1.2. \u5185\u5bb9\u7ed3\u6784","text":"

\u672c\u4e66\u4e3b\u8981\u5185\u5bb9\u5206\u4e3a\u590d\u6742\u5ea6\u5206\u6790\u3001\u6570\u636e\u7ed3\u6784\u3001\u7b97\u6cd5\u4e09\u4e2a\u90e8\u5206\u3002

Fig. \u77e5\u8bc6\u70b9\u601d\u7ef4\u5bfc\u56fe

"},{"location":"chapter_preface/about_the_book/#_1","title":"\u590d\u6742\u5ea6\u5206\u6790","text":"

\u9996\u5148\u4ecb\u7ecd\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u8bc4\u4ef7\u7ef4\u5ea6\u3001\u7b97\u6cd5\u6548\u7387\u7684\u8bc4\u4f30\u65b9\u6cd5\uff0c\u5f15\u51fa\u4e86\u8ba1\u7b97\u590d\u6742\u5ea6\u6982\u5ff5\u3002

\u63a5\u4e0b\u6765\uff0c\u4ece \u51fd\u6570\u6e10\u8fd1\u4e0a\u754c \u5165\u624b\uff0c\u5206\u522b\u4ecb\u7ecd\u4e86 \u65f6\u95f4\u590d\u6742\u5ea6 \u548c \u7a7a\u95f4\u590d\u6742\u5ea6\uff0c\u5305\u62ec\u63a8\u7b97\u65b9\u6cd5\u3001\u5e38\u89c1\u7c7b\u578b\u3001\u793a\u4f8b\u7b49\u3002\u540c\u65f6\uff0c\u5256\u6790\u4e86 \u6700\u5dee\u3001\u6700\u4f73\u3001\u5e73\u5747 \u65f6\u95f4\u590d\u6742\u5ea6\u7684\u8054\u7cfb\u4e0e\u533a\u522b\u3002

"},{"location":"chapter_preface/about_the_book/#_2","title":"\u6570\u636e\u7ed3\u6784","text":"

\u9996\u5148\u4ecb\u7ecd\u4e86\u5e38\u7528\u7684 \u57fa\u672c\u6570\u636e\u7c7b\u578b \u3001\u4ee5\u53ca\u5b83\u4eec\u662f\u5982\u4f55\u5728\u5185\u5b58\u4e2d\u5b58\u50a8\u7684\u3002

\u63a5\u4e0b\u6765\uff0c\u4ecb\u7ecd\u4e86\u4e24\u79cd \u6570\u636e\u7ed3\u6784\u5206\u7c7b\u65b9\u6cd5\uff0c\u5305\u62ec\u903b\u8f91\u7ed3\u6784\u4e0e\u7269\u7406\u7ed3\u6784\u3002

\u540e\u7eed\u5c55\u5f00\u4ecb\u7ecd\u4e86 \u6570\u7ec4\u3001\u94fe\u8868\u3001\u6808\u3001\u961f\u5217\u3001\u6563\u5217\u8868\u3001\u6811\u3001\u5806\u3001\u56fe \u7b49\u6570\u636e\u7ed3\u6784\uff0c\u5173\u5fc3\u4ee5\u4e0b\u5185\u5bb9\uff1a

  • \u57fa\u672c\u5b9a\u4e49\uff1a\u6570\u636e\u7ed3\u6784\u7684\u8bbe\u8ba1\u6765\u6e90\u3001\u5b58\u5728\u610f\u4e49\uff1b
  • \u4e3b\u8981\u7279\u70b9\uff1a\u5728\u5404\u9879\u6570\u636e\u64cd\u4f5c\u4e2d\u7684\u4f18\u52bf\u3001\u52a3\u52bf\uff1b
  • \u5e38\u7528\u64cd\u4f5c\uff1a\u4f8b\u5982\u8bbf\u95ee\u3001\u66f4\u65b0\u3001\u63d2\u5165\u3001\u5220\u9664\u3001\u904d\u5386\u3001\u641c\u7d22\u7b49\uff1b
  • \u5e38\u89c1\u7c7b\u578b\uff1a\u5728\u7b97\u6cd5\u9898\u6216\u5de5\u7a0b\u5b9e\u9645\u4e2d\uff0c\u7ecf\u5e38\u78b0\u5230\u7684\u6570\u636e\u7ed3\u6784\u7c7b\u578b\uff1b
  • \u5178\u578b\u5e94\u7528\uff1a\u6b64\u6570\u636e\u7ed3\u6784\u7ecf\u5e38\u642d\u914d\u54ea\u4e9b\u7b97\u6cd5\u4f7f\u7528\uff1b
  • \u5b9e\u73b0\u65b9\u6cd5\uff1a\u5bf9\u4e8e\u91cd\u8981\u7684\u6570\u636e\u7ed3\u6784\uff0c\u5c06\u7ed9\u51fa\u5b8c\u6574\u7684\u5b9e\u73b0\u793a\u4f8b\uff1b
"},{"location":"chapter_preface/about_the_book/#_3","title":"\u7b97\u6cd5","text":"

\u5305\u62ec \u67e5\u627e\u7b97\u6cd5\u3001\u6392\u5e8f\u7b97\u6cd5\u3001\u641c\u7d22\u4e0e\u56de\u6eaf\u3001\u52a8\u6001\u89c4\u5212\u3001\u5206\u6cbb\u7b97\u6cd5\uff0c\u5185\u5bb9\u5305\u62ec\uff1a

  • \u57fa\u672c\u5b9a\u4e49\uff1a\u7b97\u6cd5\u7684\u8bbe\u8ba1\u601d\u60f3\uff1b
  • \u4e3b\u8981\u7279\u70b9\uff1a\u4f7f\u7528\u524d\u7f6e\u6761\u4ef6\u3001\u4f18\u52bf\u548c\u52a3\u52bf\uff1b
  • \u7b97\u6cd5\u6548\u7387\uff1a\u6700\u5dee\u548c\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u3001\u7a7a\u95f4\u590d\u6742\u5ea6\uff1b
  • \u5b9e\u73b0\u65b9\u6cd5\uff1a\u5b8c\u6574\u7684\u7b97\u6cd5\u5b9e\u73b0\uff0c\u4ee5\u53ca\u4f18\u5316\u63aa\u65bd\uff1b
  • \u793a\u4f8b\u9898\u76ee\uff1a\u7ed3\u5408\u4f8b\u9898\u52a0\u6df1\u7406\u89e3\uff1b
"},{"location":"chapter_preface/about_the_book/#013","title":"0.1.3. \u914d\u5957\u4ee3\u7801","text":"

\u5b8c\u6574\u4ee3\u7801\u6258\u7ba1\u5728 GitHub \u4ed3\u5e93 \uff0c\u7686\u53ef\u4e00\u952e\u8fd0\u884c\u3002

\u524d\u7f6e\u5de5\u4f5c

  1. \u7f16\u7a0b\u73af\u5883\u5b89\u88c5 \uff0c\u82e5\u6709\u8bf7\u8df3\u8fc7
  2. \u4ee3\u7801\u4e0b\u8f7d\u4e0e\u4f7f\u7528\u65b9\u6cd5\u8bf7\u89c1 \u5982\u4f55\u4f7f\u7528\u672c\u4e66
"},{"location":"chapter_preface/about_the_book/#014","title":"0.1.4. \u98ce\u683c\u7ea6\u5b9a","text":"
  • \u6807\u9898\u540e\u6807\u6ce8 * \u7b26\u53f7\u7684\u662f\u9009\u8bfb\u7ae0\u8282\uff0c\u5982\u679c\u4f60\u7684\u65f6\u95f4\u6709\u9650\uff0c\u53ef\u4ee5\u5148\u8df3\u8fc7\u8fd9\u4e9b\u7ae0\u8282\u3002
  • \u6587\u7ae0\u4e2d\u7684\u91cd\u8981\u540d\u8bcd\u4f1a\u7528\u300c\u300d\u7b26\u53f7\u6807\u6ce8\uff0c\u4f8b\u5982\u300c\u6570\u7ec4 Array\u300d\u3002\u540d\u8bcd\u6df7\u6dc6\u4f1a\u5bfc\u81f4\u4e0d\u5fc5\u8981\u7684\u6b67\u4e49\uff0c\u56e0\u6b64\u6700\u597d\u53ef\u4ee5\u8bb0\u4f4f\u8fd9\u7c7b\u540d\u8bcd\uff08\u5305\u62ec\u4e2d\u6587\u548c\u82f1\u6587\uff09\uff0c\u4ee5\u4fbf\u540e\u7eed\u9605\u8bfb\u6587\u732e\u65f6\u4f7f\u7528\u3002
  • \u91cd\u70b9\u5185\u5bb9\u3001\u603b\u8d77\u53e5\u3001\u603b\u7ed3\u53e5\u4f1a\u88ab \u52a0\u7c97\uff0c\u6b64\u7c7b\u6587\u5b57\u503c\u5f97\u7279\u522b\u5173\u6ce8\u3002
  • \u4e13\u6709\u540d\u8bcd\u548c\u6709\u7279\u6307\u542b\u4e49\u7684\u8bcd\u53e5\u4f1a\u4f7f\u7528 \u201c \u201d \u6807\u6ce8\uff0c\u4ee5\u907f\u514d\u6b67\u4e49\u3002
  • \u5728\u5de5\u7a0b\u5e94\u7528\u4e2d\uff0c\u6bcf\u79cd\u8bed\u8a00\u90fd\u6709\u6ce8\u91ca\u89c4\u8303\uff1b\u800c\u672c\u4e66\u653e\u5f03\u4e86\u4e00\u90e8\u5206\u7684\u6ce8\u91ca\u89c4\u8303\u6027\uff0c\u4ee5\u6362\u53d6\u66f4\u52a0\u7d27\u51d1\u7684\u5185\u5bb9\u6392\u7248\u3002\u6ce8\u91ca\u4e3b\u8981\u5206\u4e3a\u4e09\u79cd\u7c7b\u578b\uff1a\u6807\u9898\u6ce8\u91ca\u3001\u5185\u5bb9\u6ce8\u91ca\u3001\u591a\u884c\u6ce8\u91ca\u3002
JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
\"\"\" \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 \"\"\"\n# \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n\"\"\"\n\u591a\u884c\n\u6ce8\u91ca\n\"\"\"\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
/* \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49 */\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n/**\n * \u591a\u884c\n * \u6ce8\u91ca\n */\n
// \u6807\u9898\u6ce8\u91ca\uff0c\u7528\u4e8e\u6807\u6ce8\u51fd\u6570\u3001\u7c7b\u3001\u6d4b\u8bd5\u6837\u4f8b\u7b49\n// \u5185\u5bb9\u6ce8\u91ca\uff0c\u7528\u4e8e\u8be6\u89e3\u4ee3\u7801\n// \u591a\u884c\n// \u6ce8\u91ca\n
"},{"location":"chapter_preface/about_the_book/#015","title":"0.1.5. \u672c\u4e66\u7279\u70b9 *","text":"\u9ed8\u8ba4\u6298\u53e0\uff0c\u53ef\u4ee5\u8df3\u8fc7

\u4ee5\u5b9e\u8df5\u4e3a\u4e3b\u3002\u6211\u4eec\u77e5\u9053\uff0c\u5b66\u4e60\u82f1\u8bed\u671f\u95f4\u5149\u5543\u4e66\u672c\u662f\u8fdc\u8fdc\u4e0d\u591f\u7684\uff0c\u9700\u8981\u591a\u542c\u3001\u591a\u8bf4\u3001\u591a\u5199\uff0c\u5728\u5b9e\u8df5\u4e2d\u57f9\u517b\u8bed\u611f\u3001\u79ef\u7d2f\u7ecf\u9a8c\u3002\u7f16\u7a0b\u8bed\u8a00\u4e5f\u662f\u4e00\u95e8\u8bed\u8a00\uff0c\u56e0\u6b64\u5b66\u4e60\u65b9\u6cd5\u4e5f\u5e94\u662f\u7c7b\u4f3c\u7684\uff0c\u9700\u8981\u591a\u770b\u4f18\u79c0\u4ee3\u7801\u3001\u591a\u6572\u952e\u76d8\u3001\u591a\u601d\u8003\u4ee3\u7801\u903b\u8f91\u3002

\u672c\u4e66\u7684\u7406\u8bba\u90e8\u5206\u5360\u5c11\u91cf\u7bc7\u5e45\uff0c\u4e3b\u8981\u5206\u4e3a\u4e24\u7c7b\uff1a\u4e00\u662f\u57fa\u7840\u4e14\u5fc5\u8981\u7684\u6982\u5ff5\u77e5\u8bc6\uff0c\u4ee5\u57f9\u517b\u8bfb\u8005\u5bf9\u4e8e\u7b97\u6cd5\u7684\u611f\u6027\u8ba4\u8bc6\uff1b\u4e8c\u662f\u91cd\u8981\u7684\u5206\u7c7b\u3001\u5bf9\u6bd4\u6216\u603b\u7ed3\uff0c\u8fd9\u662f\u4e3a\u4e86\u5e2e\u52a9\u4f60\u7ad9\u5728\u66f4\u9ad8\u89c6\u89d2\u4fef\u77b0\u5404\u4e2a\u77e5\u8bc6\u70b9\uff0c\u5f62\u6210\u8fde\u70b9\u6210\u9762\u7684\u6548\u679c\u3002

\u5b9e\u8df5\u90e8\u5206\u4e3b\u8981\u7531\u793a\u4f8b\u548c\u4ee3\u7801\u7ec4\u6210\u3002\u4ee3\u7801\u914d\u6709\u7b80\u8981\u6ce8\u91ca\uff0c\u590d\u6742\u793a\u4f8b\u4f1a\u5c3d\u53ef\u80fd\u5730\u4f7f\u7528\u89c6\u89c9\u5316\u7684\u5f62\u5f0f\u5448\u73b0\u3002\u6211\u5f3a\u70c8\u5efa\u8bae\u8bfb\u8005\u5bf9\u7167\u7740\u4ee3\u7801\u81ea\u5df1\u6572\u4e00\u904d\uff0c\u5982\u679c\u65f6\u95f4\u6709\u9650\uff0c\u4e5f\u81f3\u5c11\u9010\u884c\u8bfb\u3001\u590d\u5236\u5e76\u8fd0\u884c\u4e00\u904d\uff0c\u914d\u5408\u7740\u8bb2\u89e3\u5c06\u4ee3\u7801\u5403\u900f\u3002

\u89c6\u89c9\u5316\u5b66\u4e60\u3002\u4fe1\u606f\u65f6\u4ee3\u4ee5\u6765\uff0c\u89c6\u89c9\u5316\u7684\u811a\u6b65\u4ece\u672a\u505c\u6b62\u3002\u5a92\u4f53\u5f62\u5f0f\u7ecf\u5386\u4e86\u6587\u5b57\u77ed\u4fe1\u3001\u56fe\u6587 Email \u3001\u52a8\u56fe\u3001\u77ed\uff08\u957f\uff09\u89c6\u9891\u3001\u4ea4\u4e92\u5f0f Web \u30013D \u6e38\u620f\u7b49\u6f14\u53d8\u8fc7\u7a0b\uff0c\u4fe1\u606f\u7684\u89c6\u89c9\u5316\u7a0b\u5ea6\u8d8a\u6765\u8d8a\u9ad8\u3001\u6108\u52a0\u7b26\u5408\u4eba\u7c7b\u611f\u5b98\u3001\u4fe1\u606f\u4f20\u64ad\u6548\u7387\u5927\u5927\u63d0\u5347\u3002\u79d1\u6280\u754c\u4e5f\u5728\u5411\u89c6\u89c9\u5316\u8fc8\u8fdb\uff0ciPhone \u5c31\u662f\u4e00\u4e2a\u5178\u578b\u4f8b\u5b50\uff0c\u5176\u76f8\u5bf9\u4e8e\u4f20\u7edf\u624b\u673a\u662f\u9ad8\u5ea6\u89c6\u89c9\u5316\u7684\uff0c\u5305\u542b\u7cbe\u5fc3\u8bbe\u8ba1\u7684\u5b57\u4f53\u3001\u4e3b\u9898\u914d\u8272\u3001\u4ea4\u4e92\u52a8\u753b\u7b49\u3002

\u8fd1\u4e24\u5e74\uff0c\u77ed\u89c6\u9891\u6210\u4e3a\u6700\u53d7\u6b22\u8fce\u7684\u4fe1\u606f\u5a92\u4ecb\uff0c\u53ef\u4ee5\u5728\u77ed\u65f6\u95f4\u5185\u5c06\u9ad8\u5bc6\u5ea6\u7684\u4fe1\u606f\u201c\u704c\u201d\u7ed9\u6211\u4eec\uff0c\u6709\u7740\u6781\u5176\u8212\u9002\u7684\u89c2\u770b\u4f53\u9a8c\u3002\u9605\u8bfb\u5219\u4e0d\u7136\uff0c\u8bfb\u8005\u4e0e\u4e66\u672c\u4e4b\u95f4\u5929\u7136\u5b58\u5728\u4e00\u79cd\u201c\u758f\u79bb\u611f\u201d\uff0c\u6211\u4eec\u770b\u4e66\u4f1a\u7d2f\u3001\u4f1a\u8d70\u795e\u3001\u4f1a\u505c\u4e0b\u6765\u60f3\u5176\u4ed6\u4e8b\u3001\u4f1a\u5212\u4e0b\u559c\u6b22\u7684\u53e5\u5b50\u3001\u4f1a\u601d\u8003\u67d0\u4e00\u7247\u6bb5\u7684\u542b\u4e49\uff0c\u8fd9\u79cd\u758f\u79bb\u611f\u7ed9\u4e86\u8bfb\u8005\u4e0e\u4e66\u672c\u4e4b\u95f4\u5bf9\u8bdd\u7684\u53ef\u80fd\uff0c\u62d3\u5bbd\u4e86\u60f3\u8c61\u7a7a\u95f4\u3002

\u672c\u4e66\u4f5c\u4e3a\u4e00\u672c\u5165\u95e8\u6559\u6750\uff0c\u5e0c\u671b\u53ef\u4ee5\u4fdd\u6709\u4e66\u672c\u7684\u201c\u6162\u8282\u594f\u201d\uff0c\u4f46\u4e5f\u4f1a\u907f\u514d\u4e0e\u8bfb\u8005\u4ea7\u751f\u8fc7\u591a\u201c\u758f\u79bb\u611f\u201d\uff0c\u800c\u662f\u52aa\u529b\u5c06\u77e5\u8bc6\u5b8c\u6574\u6e05\u6670\u5730\u63a8\u9001\u5230\u4f60\u806a\u660e\u7684\u5c0f\u8111\u888b\u74dc\u4e2d\u3002\u6211\u5c06\u91c7\u7528\u89c6\u89c9\u5316\u7684\u65b9\u5f0f\uff08\u4f8b\u5982\u914d\u56fe\u3001\u52a8\u753b\uff09\uff0c\u5c3d\u6211\u53ef\u80fd\u6e05\u6670\u6613\u61c2\u5730\u8bb2\u89e3\u590d\u6742\u6982\u5ff5\u548c\u62bd\u8c61\u793a\u4f8b\u3002

\u5185\u5bb9\u7cbe\u7b80\u5316\u3002\u5927\u591a\u6570\u7684\u7ecf\u5178\u6559\u79d1\u4e66\uff0c\u4f1a\u628a\u6bcf\u4e2a\u4e3b\u9898\u90fd\u8bb2\u7684\u5f88\u900f\u5f7b\u3002\u867d\u7136\u900f\u5f7b\u6027\u6b63\u662f\u5176\u83b7\u5f97\u8bfb\u8005\u9752\u7750\u7684\u539f\u56e0\uff0c\u4f46\u5bf9\u4e8e\u60f3\u8981\u5feb\u901f\u5165\u95e8\u7684\u521d\u5b66\u8005\u6765\u8bf4\uff0c\u8fd9\u4e9b\u6559\u6750\u7684\u5b9e\u7528\u6027\u4e0d\u8db3\u3002\u672c\u4e66\u4f1a\u907f\u514d\u5f15\u5165\u975e\u5fc5\u8981\u7684\u6982\u5ff5\u3001\u540d\u8bcd\u3001\u5b9a\u4e49\u7b49\uff0c\u4e5f\u907f\u514d\u5c55\u5f00\u4e0d\u5fc5\u8981\u7684\u7406\u8bba\u5206\u6790\uff0c\u6bd5\u7adf\u8fd9\u4e0d\u662f\u4e00\u672c\u771f\u6b63\u610f\u4e49\u4e0a\u7684\u6559\u6750\uff0c\u4e3b\u8981\u4efb\u52a1\u662f\u5c3d\u5feb\u5730\u5e26\u9886\u8bfb\u8005\u5165\u95e8\u3002

\u5f15\u5165\u4e00\u4e9b\u751f\u6d3b\u6848\u4f8b\u6216\u8da3\u5473\u5185\u5bb9\uff0c\u975e\u5e38\u9002\u5408\u4f5c\u4e3a\u77e5\u8bc6\u70b9\u7684\u5f15\u5b50\u6216\u8005\u89e3\u91ca\u7684\u8865\u5145\uff0c\u4f46\u5f53\u878d\u5165\u8fc7\u591a\u989d\u5916\u5143\u7d20\u65f6\uff0c\u5185\u5bb9\u4f1a\u7a0d\u663e\u5197\u957f\uff0c\u4e5f\u8bb8\u53cd\u800c\u4f7f\u8bfb\u8005\u5bb9\u6613\u8ff7\u5931\u3001\u6293\u4e0d\u4f4f\u91cd\u70b9\uff0c\u8fd9\u4e5f\u662f\u672c\u4e66\u9700\u8981\u907f\u514d\u7684\u3002

\u6572\u4ee3\u7801\u5982\u540c\u5199\u5b57\uff0c\u201c\u7f8e\u201d\u662f\u7edf\u4e00\u7684\u8ffd\u6c42\u3002\u672c\u4e66\u529b\u6c42\u7f8e\u89c2\u7684\u4ee3\u7801\uff0c\u4fdd\u8bc1\u89c4\u8303\u7684\u53d8\u91cf\u547d\u540d\u3001\u7edf\u4e00\u7684\u7a7a\u683c\u4e0e\u6362\u884c\u3001\u5bf9\u9f50\u7684\u7f29\u8fdb\u3001\u6574\u9f50\u7684\u6ce8\u91ca\u7b49\u3002

"},{"location":"chapter_preface/about_the_book/#016","title":"0.1.6. \u81f4\u8c22","text":"

\u672c\u4e66\u7684\u6210\u4e66\u8fc7\u7a0b\u4e2d\uff0c\u6211\u83b7\u5f97\u4e86\u8bb8\u591a\u4eba\u7684\u5e2e\u52a9\uff0c\u5305\u62ec\u4f46\u4e0d\u9650\u4e8e\uff1a

  • \u611f\u8c22\u6211\u7684\u5973\u670b\u53cb\u6ce1\u6ce1\u62c5\u4efb\u672c\u4e66\u7684\u9996\u4f4d\u8bfb\u8005\uff0c\u4ece\u7b97\u6cd5\u5c0f\u767d\u7684\u89c6\u89d2\u4e3a\u672c\u4e66\u7684\u5199\u4f5c\u63d0\u51fa\u4e86\u8bb8\u591a\u5efa\u8bae\uff0c\u4f7f\u8fd9\u672c\u4e66\u66f4\u52a0\u9002\u5408\u7b97\u6cd5\u521d\u5b66\u8005\u6765\u9605\u8bfb\u3002
  • \u611f\u8c22\u817e\u5b9d\u3001\u7426\u5b9d\u3001\u98de\u5b9d\u4e3a\u672c\u4e66\u8d77\u4e86\u4e2a\u54cd\u5f53\u5f53\u7684\u540d\u5b57\uff0c\u597d\u542c\u53c8\u6709\u6897\uff0c\u76f4\u63a5\u5524\u8d77\u6211\u6700\u521d\u6572\u4e0b\u7b2c\u4e00\u884c\u4ee3\u7801 \"Hello, World!\" \u7684\u56de\u5fc6\u3002
  • \u611f\u8c22\u6211\u7684\u5bfc\u5e08\u674e\u535a\uff0c\u5728\u5c0f\u914c\u7545\u8c08\u65f6\u60a8\u544a\u8bc9\u6211\u201c\u89c9\u5f97\u9002\u5408\u3001\u60f3\u505a\u5c31\u53bb\u505a\u201d\uff0c\u575a\u5b9a\u4e86\u6211\u5199\u8fd9\u672c\u4e66\u7684\u51b3\u5fc3\u3002
  • \u611f\u8c22\u82cf\u6f7c\u4e3a\u672c\u4e66\u8bbe\u8ba1\u4e86\u5c01\u9762\u548c LOGO \uff0c\u6211\u6709\u4e9b\u5f3a\u8feb\u75c7\uff0c\u524d\u540e\u591a\u6b21\u4fee\u6539\uff0c\u8c22\u8c22\u4f60\u7684\u8010\u5fc3\u3002
  • \u611f\u8c22 @squidfunk \uff0c\u5305\u62ec Material-for-MkDocs \u9876\u7ea7\u5f00\u6e90\u9879\u76ee\u4ee5\u53ca\u7ed9\u51fa\u7684\u5199\u4f5c\u6392\u7248\u5efa\u8bae\u3002

\u5728\u5199\u4f5c\u8fc7\u7a0b\u4e2d\uff0c\u6211\u9605\u8bfb\u4e86\u8bb8\u591a\u4e0e\u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u7684\u4e66\u7c4d\u6750\u6599\uff0c\u5b66\u4e60\u5230\u4e86\u8bb8\u591a\u77e5\u8bc6\uff0c\u611f\u8c22\u524d\u8f88\u4eec\u7684\u7cbe\u5f69\u521b\u4f5c\u3002

\u611f\u8c22\u7236\u6bcd\uff0c\u4f60\u4eec\u4e00\u8d2f\u7684\u652f\u6301\u4e0e\u9f13\u52b1\u7ed9\u4e86\u6211\u81ea\u7531\u5ea6\u6765\u505a\u8fd9\u4e9b\u6709\u8da3\u7684\u4e8b\u3002

"},{"location":"chapter_preface/about_the_book/#017","title":"0.1.7. \u4f5c\u8005\u7b80\u4ecb Krahets","text":"\u5927\u5382\u9ad8\u7ea7\u7b97\u6cd5\u5de5\u7a0b\u5e08\u3001\u7b97\u6cd5\u7231\u597d\u8005

\u529b\u6263\uff08LeetCode\uff09\u5168\u7f51\u9605\u8bfb\u91cf\u6700\u9ad8\u535a\u4e3b

\u5206\u4eab\u8fd1\u767e\u9053\u7b97\u6cd5\u9898\u89e3\uff0c\u7d2f\u79ef\u56de\u590d\u6570\u5343\u8bfb\u8005\u7684\u8bc4\u8bba\u95ee\u9898

\u521b\u4f5c LeetBook\u300a\u56fe\u89e3\u7b97\u6cd5\u6570\u636e\u7ed3\u6784\u300b\uff0c\u5df2\u514d\u8d39\u552e\u51fa 22 \u4e07\u672c

"},{"location":"chapter_preface/contribution/","title":"0.4. \u4e00\u8d77\u53c2\u4e0e\u521b\u4f5c","text":"

\u5f00\u6e90\u7684\u9b45\u529b

\u7eb8\u8d28\u4e66\u7c4d\u7684\u4e24\u6b21\u5370\u5237\u7684\u95f4\u9694\u65f6\u95f4\u5f80\u5f80\u9700\u8981\u6570\u5e74\uff0c\u5185\u5bb9\u66f4\u65b0\u975e\u5e38\u4e0d\u65b9\u4fbf\u3002\u4f46\u5728\u672c\u5f00\u6e90 HTML \u4e66\u4e2d\uff0c\u5185\u5bb9\u66f4\u8fed\u7684\u65f6\u95f4\u88ab\u7f29\u77ed\u81f3\u6570\u65e5\u751a\u81f3\u51e0\u4e2a\u5c0f\u65f6\u3002

\u7531\u4e8e\u4f5c\u8005\u6c34\u5e73\u6709\u9650\uff0c\u4e66\u4e2d\u5185\u5bb9\u96be\u514d\u758f\u6f0f\u8c2c\u8bef\uff0c\u8bf7\u60a8\u8c05\u89e3\u3002\u6b64\u5916\uff0c\u671f\u5f85\u60a8\u53ef\u4ee5\u4e00\u540c\u53c2\u4e0e\u672c\u4e66\u7684\u521b\u4f5c\u3002\u5982\u679c\u53d1\u73b0\u7b14\u8bef\u3001\u65e0\u6548\u94fe\u63a5\u3001\u5185\u5bb9\u7f3a\u5931\u3001\u6587\u5b57\u6b67\u4e49\u3001\u89e3\u91ca\u4e0d\u6e05\u6670\u3001\u884c\u6587\u7ed3\u6784\u4e0d\u5408\u7406\u7b49\u95ee\u9898\uff0c\u70e6\u8bf7\u60a8\u4fee\u6b63\u5185\u5bb9\uff0c\u4ee5\u5e2e\u52a9\u5176\u4ed6\u8bfb\u8005\u83b7\u53d6\u66f4\u4f18\u8d28\u7684\u5b66\u4e60\u5185\u5bb9\u3002\u6240\u6709 \u64b0\u7a3f\u4eba \u5c06\u88ab\u5c55\u793a\u5728\u4ed3\u5e93\u4e3b\u9875\uff0c\u4ee5\u611f\u8c22\u60a8\u5bf9\u5f00\u6e90\u793e\u533a\u7684\u65e0\u79c1\u5949\u732e\u3002

"},{"location":"chapter_preface/contribution/#041","title":"0.4.1. \u4fee\u6539\u6587\u5b57\u4e0e\u4ee3\u7801","text":"

\u6bcf\u4e2a\u9875\u9762\u7684\u53f3\u4e0a\u89d2\u90fd\u6709\u4e00\u4e2a\u300c\u7f16\u8f91\u300d\u6309\u94ae\uff0c\u4f60\u53ef\u4ee5\u6309\u7167\u4ee5\u4e0b\u6b65\u9aa4\u4fee\u6539\u6587\u7ae0\uff1a

  1. \u70b9\u51fb\u7f16\u8f91\u6309\u94ae\uff0c\u5982\u679c\u9047\u5230\u63d0\u793a\u201c\u9700\u8981 Fork \u6b64\u4ed3\u5e93\u201d\uff0c\u8bf7\u901a\u8fc7\uff1b
  2. \u4fee\u6539 Markdown \u6e90\u6587\u4ef6\u5185\u5bb9\uff1b
  3. \u5728\u9875\u9762\u5e95\u90e8\u586b\u5199\u66f4\u6539\u8bf4\u660e\uff0c\u7136\u540e\u5355\u51fb\u201cPropose file change\u201d\u6309\u94ae\uff1b
  4. \u9875\u9762\u8df3\u8f6c\u540e\uff0c\u70b9\u51fb\u201cCreate pull request\u201d\u6309\u94ae\u53d1\u8d77\u62c9\u53d6\u8bf7\u6c42\u5373\u53ef\uff0c\u6211\u4f1a\u7b2c\u4e00\u65f6\u95f4\u67e5\u770b\u5904\u7406\u5e76\u53ca\u65f6\u66f4\u65b0\u5185\u5bb9\u3002

"},{"location":"chapter_preface/contribution/#042","title":"0.4.2. \u4fee\u6539\u56fe\u7247\u4e0e\u52a8\u753b","text":"

\u4e66\u4e2d\u7684\u914d\u56fe\u65e0\u6cd5\u76f4\u63a5\u4fee\u6539\uff0c\u9700\u8981\u901a\u8fc7\u4ee5\u4e0b\u9014\u5f84\u63d0\u51fa\u4fee\u6539\u610f\u89c1\uff1a

  1. \u65b0\u5efa\u4e00\u4e2a Issue \uff0c\u5c06\u9700\u8981\u4fee\u6539\u7684\u56fe\u7247\u590d\u5236\u6216\u622a\u56fe\uff0c\u7c98\u8d34\u5728\u9762\u677f\u4e2d\uff1b
  2. \u63cf\u8ff0\u56fe\u7247\u95ee\u9898\uff0c\u5e94\u5982\u4f55\u4fee\u6539\uff1b
  3. \u63d0\u4ea4 Issue \u5373\u53ef\uff0c\u6211\u4f1a\u7b2c\u4e00\u65f6\u95f4\u91cd\u65b0\u753b\u56fe\u5e76\u66ff\u6362\u56fe\u7247\u3002
"},{"location":"chapter_preface/contribution/#043","title":"0.4.3. \u521b\u4f5c\u65b0\u5185\u5bb9","text":"

\u5982\u679c\u60a8\u60f3\u8981\u521b\u4f5c\u65b0\u5185\u5bb9\uff0c\u4f8b\u5982 \u91cd\u5199\u7ae0\u8282\u3001\u65b0\u589e\u7ae0\u8282\u3001\u4fee\u6539\u4ee3\u7801\u3001\u7ffb\u8bd1\u4ee3\u7801\u81f3\u5176\u4ed6\u7f16\u7a0b\u8bed\u8a00 \u7b49\uff0c\u90a3\u4e48\u9700\u8981\u5b9e\u65bd Pull Request \u5de5\u4f5c\u6d41\u7a0b\uff1a

  1. \u767b\u5f55 GitHub \uff0c\u5e76 Fork \u672c\u4ed3\u5e93 \u81f3\u4e2a\u4eba\u8d26\u53f7\uff1b
  2. \u8fdb\u5165 Fork \u4ed3\u5e93\u7f51\u9875\uff0c\u4f7f\u7528 git clone \u514b\u9686\u8be5\u4ed3\u5e93\u81f3\u672c\u5730\uff1b
  3. \u5728\u672c\u5730\u8fdb\u884c\u5185\u5bb9\u521b\u4f5c\uff08\u5efa\u8bae\u901a\u8fc7\u8fd0\u884c\u6d4b\u8bd5\u6765\u9a8c\u8bc1\u4ee3\u7801\u6b63\u786e\u6027\uff09\uff1b
  4. \u5c06\u672c\u5730\u66f4\u6539 Commit \uff0c\u5e76 Push \u81f3\u8fdc\u7a0b\u4ed3\u5e93\uff1b
  5. \u5237\u65b0\u4ed3\u5e93\u7f51\u9875\uff0c\u70b9\u51fb\u201cCreate pull request\u201d\u6309\u94ae\u53d1\u8d77\u62c9\u53d6\u8bf7\u6c42\uff08Pull Request\uff09\u5373\u53ef\uff1b

\u975e\u5e38\u6b22\u8fce\u60a8\u548c\u6211\u4e00\u540c\u6765\u521b\u4f5c\u672c\u4e66\uff01

"},{"location":"chapter_preface/contribution/#044-hello-algo","title":"0.4.4. \u672c\u5730\u90e8\u7f72 hello-algo","text":""},{"location":"chapter_preface/contribution/#docker","title":"Docker","text":"

\u8bf7\u786e\u4fdd Docker \u5df2\u7ecf\u5b89\u88c5\u5e76\u542f\u52a8\uff0c\u5e76\u6839\u636e\u5982\u4e0b\u547d\u4ee4\u79bb\u7ebf\u90e8\u7f72\u3002

\u7a0d\u7b49\u7247\u523b\uff0c\u5373\u53ef\u4f7f\u7528\u6d4f\u89c8\u5668\u6253\u5f00 http://localhost:8000 \u8bbf\u95ee\u672c\u9879\u76ee\u3002

git clone https://github.com/krahets/hello-algo.git\ncd hello-algo\n\ndocker-compose up -d\n

\u4f7f\u7528\u5982\u4e0b\u547d\u4ee4\u5373\u53ef\u5220\u9664\u90e8\u7f72\u3002

docker-compose down\n

\uff08TODO\uff1a\u6559\u5b66\u89c6\u9891\uff09

"},{"location":"chapter_preface/installation/","title":"0.3. \u7f16\u7a0b\u73af\u5883\u5b89\u88c5","text":"

\uff08TODO \u89c6\u9891\u6559\u7a0b\uff09

"},{"location":"chapter_preface/installation/#031-vscode","title":"0.3.1. \u5b89\u88c5 VSCode","text":"

\u672c\u4e66\u63a8\u8350\u4f7f\u7528\u5f00\u6e90\u8f7b\u91cf\u7684 VSCode \u4f5c\u4e3a\u672c\u5730 IDE \uff0c\u4e0b\u8f7d\u5e76\u5b89\u88c5 VSCode \u3002

"},{"location":"chapter_preface/installation/#032-java","title":"0.3.2. Java \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 OpenJDK\uff08\u7248\u672c\u9700\u6ee1\u8db3 > JDK 9\uff09\u3002
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 java \uff0c\u5b89\u88c5 Java Extension Pack \u3002
"},{"location":"chapter_preface/installation/#033-cc","title":"0.3.3. C/C++ \u73af\u5883","text":"
  1. Windows \u7cfb\u7edf\u9700\u8981\u5b89\u88c5 MinGW \uff08\u914d\u7f6e\u6559\u7a0b\uff09\uff0cMacOS \u81ea\u5e26 Clang \u65e0\u9700\u5b89\u88c5\u3002
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 c++ \uff0c\u5b89\u88c5 C/C++ Extension Pack \u3002
"},{"location":"chapter_preface/installation/#034-python","title":"0.3.4. Python \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 Miniconda3 \u3002
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 python \uff0c\u5b89\u88c5 Python Extension Pack \u3002
"},{"location":"chapter_preface/installation/#035-go","title":"0.3.5. Go \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 go \u3002
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 go \uff0c\u5b89\u88c5 Go \u3002
  3. \u5feb\u6377\u952e Ctrl + Shift + P \u547c\u51fa\u547d\u4ee4\u680f\uff0c\u8f93\u5165 go \uff0c\u9009\u62e9 Go: Install/Update Tools \uff0c\u5168\u90e8\u52fe\u9009\u5e76\u5b89\u88c5\u5373\u53ef\u3002
"},{"location":"chapter_preface/installation/#036-javascript","title":"0.3.6. JavaScript \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 node.js \u3002
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 javascript \uff0c\u5b89\u88c5 JavaScript (ES6) code snippets \u3002
"},{"location":"chapter_preface/installation/#037-c","title":"0.3.7. C# \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 .Net 6.0 \uff1b
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 c# \uff0c\u5b89\u88c5 c# \u3002
"},{"location":"chapter_preface/installation/#038-swift","title":"0.3.8. Swift \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 Swift\uff1b
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 swift\uff0c\u5b89\u88c5 Swift for Visual Studio Code\u3002
"},{"location":"chapter_preface/installation/#039-rust","title":"0.3.9. Rust \u73af\u5883","text":"
  1. \u4e0b\u8f7d\u5e76\u5b89\u88c5 Rust\uff1b
  2. \u5728 VSCode \u7684\u63d2\u4ef6\u5e02\u573a\u4e2d\u641c\u7d22 rust\uff0c\u5b89\u88c5 rust-analyzer\u3002
"},{"location":"chapter_preface/suggestions/","title":"0.2. \u5982\u4f55\u4f7f\u7528\u672c\u4e66","text":""},{"location":"chapter_preface/suggestions/#021","title":"0.2.1. \u56fe\u6587\u642d\u914d\u5b66","text":"

\u89c6\u9891\u548c\u56fe\u7247\u76f8\u6bd4\u4e8e\u6587\u5b57\u7684\u4fe1\u606f\u5bc6\u5ea6\u548c\u7ed3\u6784\u5316\u7a0b\u5ea6\u66f4\u9ad8\uff0c\u66f4\u5bb9\u6613\u8ba9\u4eba\u7406\u89e3\u3002\u5728\u672c\u4e66\u4e2d\uff0c\u91cd\u70b9\u548c\u96be\u70b9\u77e5\u8bc6\u4f1a\u4e3b\u8981\u4ee5\u52a8\u753b\u3001\u56fe\u89e3\u7684\u5f62\u5f0f\u5448\u73b0\uff0c\u800c\u6587\u5b57\u7684\u4f5c\u7528\u5219\u662f\u4f5c\u4e3a\u52a8\u753b\u548c\u56fe\u7684\u89e3\u91ca\u4e0e\u8865\u5145\u3002

\u5728\u9605\u8bfb\u672c\u4e66\u7684\u8fc7\u7a0b\u4e2d\uff0c\u82e5\u53d1\u73b0\u67d0\u6bb5\u5185\u5bb9\u63d0\u4f9b\u4e86\u52a8\u753b\u6216\u56fe\u89e3\uff0c\u5efa\u8bae\u4f60\u4ee5\u56fe\u4e3a\u4e3b\u7ebf\uff0c\u5c06\u6587\u5b57\u5185\u5bb9\uff08\u4e00\u822c\u5728\u56fe\u7684\u4e0a\u65b9\uff09\u5bf9\u9f50\u5230\u56fe\u4e2d\u5185\u5bb9\uff0c\u7efc\u5408\u6765\u7406\u89e3\u3002

"},{"location":"chapter_preface/suggestions/#022","title":"0.2.2. \u4ee3\u7801\u5b9e\u8df5\u5b66","text":"

\u524d\u7f6e\u5de5\u4f5c

\u5982\u679c\u6ca1\u6709\u672c\u5730\u7f16\u7a0b\u73af\u5883\uff0c\u53ef\u4ee5\u53c2\u7167\u4e0b\u8282 \u7f16\u7a0b\u73af\u5883\u5b89\u88c5 \u3002

"},{"location":"chapter_preface/suggestions/#_1","title":"\u4e0b\u8f7d\u4ee3\u7801\u4ed3","text":"

\u5982\u679c\u5df2\u7ecf\u5b89\u88c5 Git \uff0c\u53ef\u4ee5\u901a\u8fc7\u547d\u4ee4\u884c\u6765\u514b\u9686\u4ee3\u7801\u4ed3\u3002

git clone https://github.com/krahets/hello-algo.git\n

\u5f53\u7136\uff0c\u4f60\u4e5f\u53ef\u4ee5\u70b9\u51fb\u201cDownload ZIP\u201d\u76f4\u63a5\u4e0b\u8f7d\u4ee3\u7801\u538b\u7f29\u5305\uff0c\u89e3\u538b\u5373\u53ef\u3002

"},{"location":"chapter_preface/suggestions/#_2","title":"\u8fd0\u884c\u6e90\u4ee3\u7801","text":"

\u672c\u4e66\u63d0\u4f9b\u914d\u5957 Java, C++, Python \u4ee3\u7801\u4ed3\uff08\u540e\u7eed\u53ef\u80fd\u62d3\u5c55\u652f\u6301\u8bed\u8a00\uff09\u3002\u4e66\u4e2d\u7684\u4ee3\u7801\u680f\u4e0a\u82e5\u6807\u6709 *.java , *.cpp , *.py \uff0c\u5219\u53ef\u5728\u4ed3\u5e93 codes \u6587\u4ef6\u5939\u4e2d\u627e\u5230\u5bf9\u5e94\u7684 \u4ee3\u7801\u6e90\u6587\u4ef6\u3002

\u8fd9\u4e9b\u6e90\u6587\u4ef6\u4e2d\u5305\u542b\u8be6\u7ec6\u6ce8\u91ca\uff0c\u914d\u6709\u6d4b\u8bd5\u6837\u4f8b\uff0c\u53ef\u4ee5\u76f4\u63a5\u8fd0\u884c\uff0c\u5e2e\u52a9\u4f60\u7701\u53bb\u4e0d\u5fc5\u8981\u7684\u8c03\u8bd5\u65f6\u95f4\uff0c\u53ef\u4ee5\u5c06\u7cbe\u529b\u96c6\u4e2d\u5728\u5b66\u4e60\u5185\u5bb9\u4e0a\u3002

\u4ee3\u7801\u5b66\u4e60\u5efa\u8bae

\u82e5\u5b66\u4e60\u65f6\u95f4\u7d27\u5f20\uff0c\u8bf7\u81f3\u5c11\u5c06\u6240\u6709\u4ee3\u7801\u901a\u8bfb\u5e76\u8fd0\u884c\u4e00\u904d\u3002\u82e5\u65f6\u95f4\u5141\u8bb8\uff0c\u5f3a\u70c8\u5efa\u8bae\u5bf9\u7167\u7740\u4ee3\u7801\u81ea\u5df1\u6572\u4e00\u904d\uff0c\u9010\u6e10\u953b\u70bc\u808c\u8089\u8bb0\u5fc6\u3002\u76f8\u6bd4\u4e8e\u8bfb\u4ee3\u7801\uff0c\u5199\u4ee3\u7801\u7684\u8fc7\u7a0b\u5f80\u5f80\u80fd\u5e26\u6765\u65b0\u7684\u6536\u83b7\u3002

"},{"location":"chapter_preface/suggestions/#023","title":"0.2.3. \u63d0\u95ee\u8ba8\u8bba\u5b66","text":"

\u9605\u8bfb\u672c\u4e66\u65f6\uff0c\u8bf7\u4e0d\u8981\u201c\u60ef\u7740\u201d\u90a3\u4e9b\u5f04\u4e0d\u660e\u767d\u7684\u77e5\u8bc6\u70b9\u3002\u5982\u679c\u6709\u4efb\u4f55\u7591\u60d1\uff0c\u53ef\u4ee5\u5728\u8bc4\u8bba\u533a\u7559\u4e0b\u4f60\u7684\u95ee\u9898\uff0c\u5c0f\u4f19\u4f34\u4eec\u548c\u6211\u90fd\u4f1a\u7ed9\u4e88\u89e3\u7b54\uff08\u60a8\u4e00\u822c 3 \u5929\u5185\u4f1a\u5f97\u5230\u56de\u590d\uff09\u3002

\u540c\u65f6\uff0c\u4e5f\u5e0c\u671b\u4f60\u53ef\u4ee5\u591a\u82b1\u65f6\u95f4\u901b\u901b\u8bc4\u8bba\u533a\u3002\u4e00\u65b9\u9762\uff0c\u53ef\u4ee5\u770b\u770b\u5927\u5bb6\u9047\u5230\u4e86\u4ec0\u4e48\u95ee\u9898\uff0c\u53cd\u8fc7\u6765\u67e5\u6f0f\u8865\u7f3a\uff0c\u8fd9\u5f80\u5f80\u53ef\u4ee5\u5f15\u8d77\u66f4\u52a0\u6df1\u5ea6\u7684\u601d\u8003\u3002\u53e6\u4e00\u65b9\u9762\uff0c\u4e5f\u5e0c\u671b\u4f60\u53ef\u4ee5\u6177\u6168\u5730\u89e3\u7b54\u5c0f\u4f19\u4f34\u4eec\u7684\u95ee\u9898\u3001\u5206\u4eab\u81ea\u5df1\u7684\u89c1\u89e3\uff0c\u5927\u5bb6\u4e00\u8d77\u52a0\u6cb9\u4e0e\u8fdb\u6b65\uff01

"},{"location":"chapter_preface/suggestions/#024","title":"0.2.4. \u7b97\u6cd5\u5b66\u4e60\u201c\u4e09\u6b65\u8d70\u201d","text":"

\u7b2c\u4e00\u9636\u6bb5\uff0c\u7b97\u6cd5\u5165\u95e8\uff0c\u4e5f\u6b63\u662f\u672c\u4e66\u7684\u5b9a\u4f4d\u3002\u719f\u6089\u5404\u79cd\u6570\u636e\u7ed3\u6784\u7684\u7279\u70b9\u3001\u7528\u6cd5\uff0c\u5b66\u4e60\u5404\u79cd\u7b97\u6cd5\u7684\u5de5\u4f5c\u539f\u7406\u3001\u7528\u9014\u3001\u6548\u7387\u7b49\u3002

\u7b2c\u4e8c\u9636\u6bb5\uff0c\u5237\u7b97\u6cd5\u9898\u3002\u53ef\u4ee5\u5148\u4ece\u70ed\u95e8\u9898\u5355\u5f00\u5237\uff0c\u63a8\u8350 \u5251\u6307 Offer\u3001LeetCode \u70ed\u9898 HOT 100 \uff0c\u5148\u79ef\u7d2f\u81f3\u5c11 100 \u9053\u9898\u91cf\uff0c\u719f\u6089\u5927\u591a\u6570\u7684\u7b97\u6cd5\u95ee\u9898\u3002\u521a\u5f00\u59cb\u5237\u9898\u65f6\uff0c\u201c\u9057\u5fd8\u201d\u662f\u6700\u5927\u7684\u56f0\u6270\u70b9\uff0c\u4f46\u8fd9\u662f\u5f88\u6b63\u5e38\u7684\uff0c\u8bf7\u4e0d\u8981\u62c5\u5fc3\u3002\u5b66\u4e60\u4e2d\u6709\u4e00\u79cd\u6982\u5ff5\u53eb\u201c\u5468\u671f\u6027\u56de\u987e\u201d\uff0c\u540c\u4e00\u9053\u9898\u9694\u6bb5\u65f6\u95f4\u505a\u4e00\u6b21\uff0c\u5f53\u505a\u4e86\u4e09\u904d\u4ee5\u4e0a\uff0c\u5f80\u5f80\u5c31\u80fd\u7262\u8bb0\u4e8e\u5fc3\u4e86\u3002

\u7b2c\u4e09\u9636\u6bb5\uff0c\u642d\u5efa\u77e5\u8bc6\u4f53\u7cfb\u3002\u5728\u5b66\u4e60\u65b9\u9762\uff0c\u53ef\u4ee5\u9605\u8bfb\u7b97\u6cd5\u4e13\u680f\u6587\u7ae0\u3001\u89e3\u9898\u6846\u67b6\u3001\u7b97\u6cd5\u6559\u6750\uff0c\u4e0d\u65ad\u5730\u4e30\u5bcc\u77e5\u8bc6\u4f53\u7cfb\u3002\u5728\u5237\u9898\u65b9\u9762\uff0c\u53ef\u4ee5\u5f00\u59cb\u91c7\u7528\u8fdb\u9636\u5237\u9898\u65b9\u6848\uff0c\u4f8b\u5982\u6309\u4e13\u9898\u5206\u7c7b\u3001\u4e00\u9898\u591a\u89e3\u3001\u4e00\u89e3\u591a\u9898\u7b49\uff0c\u5237\u9898\u65b9\u6848\u5728\u793e\u533a\u4e2d\u53ef\u4ee5\u627e\u5230\u4e00\u4e9b\u8bb2\u89e3\uff0c\u5728\u6b64\u4e0d\u505a\u8d58\u8ff0\u3002

"},{"location":"chapter_reference/","title":"\u53c2\u8003\u6587\u732e","text":"

[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

[3] \u7a0b\u6770. \u5927\u8bdd\u6570\u636e\u7ed3\u6784.

[4] \u738b\u4e89. \u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u4e4b\u7f8e.

[5] \u4e25\u851a\u654f. \u6570\u636e\u7ed3\u6784\uff08 C \u8bed\u8a00\u7248\uff09.

[6] \u9093\u4fca\u8f89. \u6570\u636e\u7ed3\u6784\uff08 C++ \u8bed\u8a00\u7248\uff0c\u7b2c\u4e09\u7248\uff09.

[7] \u9a6c\u514b\u00b7\u827e\u4f26\u00b7\u7ef4\u65af\u8457\uff0c\u9648\u8d8a\u8bd1. \u6570\u636e\u7ed3\u6784\u4e0e\u7b97\u6cd5\u5206\u6790\uff1aJava\u8bed\u8a00\u63cf\u8ff0\uff08\u7b2c\u4e09\u7248\uff09.

[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

"},{"location":"chapter_searching/binary_search/","title":"10.2. \u4e8c\u5206\u67e5\u627e","text":"

\u300c\u4e8c\u5206\u67e5\u627e Binary Search\u300d\u5229\u7528\u6570\u636e\u7684\u6709\u5e8f\u6027\uff0c\u901a\u8fc7\u6bcf\u8f6e\u7f29\u5c0f\u4e00\u534a\u641c\u7d22\u533a\u95f4\u6765\u67e5\u627e\u76ee\u6807\u5143\u7d20\u3002

\u4f7f\u7528\u4e8c\u5206\u67e5\u627e\u6709\u4e24\u4e2a\u524d\u7f6e\u6761\u4ef6\uff1a

  • \u8981\u6c42\u8f93\u5165\u6570\u636e\u662f\u6709\u5e8f\u7684\uff0c\u8fd9\u6837\u624d\u80fd\u901a\u8fc7\u5224\u65ad\u5927\u5c0f\u5173\u7cfb\u6765\u6392\u9664\u4e00\u534a\u7684\u641c\u7d22\u533a\u95f4\uff1b
  • \u4e8c\u5206\u67e5\u627e\u4ec5\u9002\u7528\u4e8e\u6570\u7ec4\uff0c\u800c\u5728\u94fe\u8868\u4e2d\u4f7f\u7528\u6548\u7387\u5f88\u4f4e\uff0c\u56e0\u4e3a\u5176\u5728\u5faa\u73af\u4e2d\u9700\u8981\u8df3\u8dc3\u5f0f\uff08\u975e\u8fde\u7eed\u5730\uff09\u8bbf\u95ee\u5143\u7d20\u3002
"},{"location":"chapter_searching/binary_search/#1021","title":"10.2.1. \u7b97\u6cd5\u5b9e\u73b0","text":"

\u7ed9\u5b9a\u4e00\u4e2a\u957f\u5ea6\u4e3a \\(n\\) \u7684\u6392\u5e8f\u6570\u7ec4 nums \uff0c\u5143\u7d20\u4ece\u5c0f\u5230\u5927\u6392\u5217\u3002\u6570\u7ec4\u7684\u7d22\u5f15\u53d6\u503c\u8303\u56f4\u4e3a

\\[ 0, 1, 2, \\cdots, n-1 \\]

\u4f7f\u7528\u300c\u533a\u95f4\u300d\u6765\u8868\u793a\u8fd9\u4e2a\u53d6\u503c\u8303\u56f4\u7684\u65b9\u6cd5\u4e3b\u8981\u6709\u4e24\u79cd\uff1a

  1. \u53cc\u95ed\u533a\u95f4 \\([0, n-1]\\) \uff0c\u5373\u4e24\u4e2a\u8fb9\u754c\u90fd\u5305\u542b\u81ea\u8eab\uff1b\u6b64\u65b9\u6cd5\u4e0b\uff0c\u533a\u95f4 \\([0, 0]\\) \u4ecd\u5305\u542b\u4e00\u4e2a\u5143\u7d20\uff1b
  2. \u5de6\u95ed\u53f3\u5f00 \\([0, n)\\) \uff0c\u5373\u5de6\u8fb9\u754c\u5305\u542b\u81ea\u8eab\u3001\u53f3\u8fb9\u754c\u4e0d\u5305\u542b\u81ea\u8eab\uff1b\u6b64\u65b9\u6cd5\u4e0b\uff0c\u533a\u95f4 \\([0, 0)\\) \u4e3a\u7a7a\uff1b
"},{"location":"chapter_searching/binary_search/#_1","title":"\u201c\u53cc\u95ed\u533a\u95f4\u201d\u5b9e\u73b0","text":"

\u9996\u5148\uff0c\u6211\u4eec\u5148\u91c7\u7528\u201c\u53cc\u95ed\u533a\u95f4\u201d\u7684\u8868\u793a\uff0c\u5728\u6570\u7ec4 nums \u4e2d\u67e5\u627e\u76ee\u6807\u5143\u7d20 target \u7684\u5bf9\u5e94\u7d22\u5f15\u3002

Step 1Step 2Step 3Step 4Step 5Step 6Step 7

\u4e8c\u5206\u67e5\u627e\u201c\u53cc\u95ed\u533a\u95f4\u201d\u8868\u793a\u4e0b\u7684\u4ee3\u7801\u5982\u4e0b\u6240\u793a\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_search.java
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nint binarySearch(int[] nums, int target) {\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\nint i = 0, j = nums.length - 1;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nwhile (i <= j) {\nint m = (i + j) / 2;       // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target)      // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1;\nelse if (nums[m] > target) // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1;\nelse                       // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.cpp
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nint binarySearch(vector<int>& nums, int target) {\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\nint i = 0, j = nums.size() - 1;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nwhile (i <= j) {\nint m = (i + j) / 2;       // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target)      // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1;\nelse if (nums[m] > target) // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1;\nelse                       // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.py
\"\"\" \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 \"\"\"\ndef binary_search(nums, target):\n# \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\ni, j = 0, len(nums) - 1\nwhile i <= j:\nm = (i + j) // 2        # \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif nums[m] < target:    # \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1\nelif nums[m] > target:  # \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1\nelse:\nreturn m            # \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn -1                   # \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\n
binary_search.go
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nfunc binarySearch(nums []int, target int) int {\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\ni, j := 0, len(nums)-1\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nfor i <= j {\nm := (i + j) / 2                // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif nums[m] < target {           // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1\n} else if nums[m] > target {    // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1\n} else {                        // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1\n}\n
binary_search.js
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nfunction binarySearch(nums, target) {\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\nlet i = 0, j = nums.length - 1;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nwhile (i <= j) {\nlet m = parseInt((i + j) / 2); // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m \uff0c\u5728 JS \u4e2d\u9700\u4f7f\u7528 parseInt \u51fd\u6570\u53d6\u6574\nif (nums[m] < target)          // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1;\nelse if (nums[m] > target)     // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1;\nelse\nreturn m;                  // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.ts
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nconst binarySearch = function (nums: number[], target: number): number {\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\nlet i = 0, j = nums.length - 1;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nwhile (i <= j) {\nconst m = Math.floor(i + (j - i) / 2);  // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target) {                 // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1;\n} else if (nums[m] > target) {          // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1;\n} else {                                // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n}\nreturn -1; // \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\n}\n
binary_search.c
\n
binary_search.cs
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nint binarySearch(int[] nums, int target)\n{\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\nint i = 0, j = nums.Length - 1;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nwhile (i <= j)\n{\nint m = (i + j) / 2;       // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target)      // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1;\nelse if (nums[m] > target) // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1;\nelse                       // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.swift
/* \u4e8c\u5206\u67e5\u627e\uff08\u53cc\u95ed\u533a\u95f4\uff09 */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n// \u521d\u59cb\u5316\u53cc\u95ed\u533a\u95f4 [0, n-1] \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20\nvar i = 0\nvar j = nums.count - 1\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i > j \u65f6\u4e3a\u7a7a\uff09\nwhile i <= j {\nlet m = (i + j) / 2 // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif nums[m] < target { // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j] \u4e2d\ni = m + 1\n} else if nums[m] > target { // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m-1] \u4e2d\nj = m - 1\n} else { // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1\n}\n
binary_search.zig
\n
"},{"location":"chapter_searching/binary_search/#_2","title":"\u201c\u5de6\u95ed\u53f3\u5f00\u201d\u5b9e\u73b0","text":"

\u5f53\u7136\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u4f7f\u7528\u201c\u5de6\u95ed\u53f3\u5f00\u201d\u7684\u8868\u793a\u65b9\u6cd5\uff0c\u5199\u51fa\u76f8\u540c\u529f\u80fd\u7684\u4e8c\u5206\u67e5\u627e\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_search.java
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nint binarySearch1(int[] nums, int target) {\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\nint i = 0, j = nums.length;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile (i < j) {\nint m = (i + j) / 2;       // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target)      // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1;\nelse if (nums[m] > target) // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m;\nelse                       // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.cpp
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nint binarySearch1(vector<int>& nums, int target) {\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\nint i = 0, j = nums.size();\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile (i < j) {\nint m = (i + j) / 2;       // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target)      // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1;\nelse if (nums[m] > target) // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m;\nelse                       // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.py
\"\"\" \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 \"\"\"\ndef binary_search1(nums, target):\n# \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\ni, j = 0, len(nums)\n# \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile i < j:\nm = (i + j) // 2        # \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif nums[m] < target:    # \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1\nelif nums[m] > target:  # \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m\nelse:                   # \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m\nreturn -1                   # \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\n
binary_search.go
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nfunc binarySearch1(nums []int, target int) int {\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\ni, j := 0, len(nums)\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nfor i < j {\nm := (i + j) / 2             // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif nums[m] < target {        // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1\n} else if nums[m] > target { // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m\n} else {                     // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1\n}\n
binary_search.js
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nfunction binarySearch1(nums, target) {\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\nlet i = 0, j = nums.length;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile (i < j) {\nlet m = parseInt((i + j) / 2); // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m \uff0c\u5728 JS \u4e2d\u9700\u4f7f\u7528 parseInt \u51fd\u6570\u53d6\u6574\nif (nums[m] < target)          // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1;\nelse if (nums[m] > target)     // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m;\nelse                           // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.ts
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nconst binarySearch1 = function (nums: number[], target: number): number {\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\nlet i = 0, j = nums.length;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile (i < j) {\nconst m = Math.floor(i + (j - i) / 2);  // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target) {                 // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1;\n} else if (nums[m] > target) {          // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m;\n} else {                                // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n}\nreturn -1; // \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\n}\n
binary_search.c
\n
binary_search.cs
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nint binarySearch1(int[] nums, int target)\n{\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\nint i = 0, j = nums.Length;\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile (i < j)\n{\nint m = (i + j) / 2;       // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif (nums[m] < target)      // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1;\nelse if (nums[m] > target) // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m;\nelse                       // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
binary_search.swift
/* \u4e8c\u5206\u67e5\u627e\uff08\u5de6\u95ed\u53f3\u5f00\uff09 */\nfunc binarySearch1(nums: [Int], target: Int) -> Int {\n// \u521d\u59cb\u5316\u5de6\u95ed\u53f3\u5f00 [0, n) \uff0c\u5373 i, j \u5206\u522b\u6307\u5411\u6570\u7ec4\u9996\u5143\u7d20\u3001\u5c3e\u5143\u7d20+1\nvar i = 0\nvar j = nums.count\n// \u5faa\u73af\uff0c\u5f53\u641c\u7d22\u533a\u95f4\u4e3a\u7a7a\u65f6\u8df3\u51fa\uff08\u5f53 i = j \u65f6\u4e3a\u7a7a\uff09\nwhile i < j {\nlet m = (i + j) / 2 // \u8ba1\u7b97\u4e2d\u70b9\u7d22\u5f15 m\nif nums[m] < target { // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [m+1, j) \u4e2d\ni = m + 1\n} else if nums[m] > target { // \u6b64\u60c5\u51b5\u8bf4\u660e target \u5728\u533a\u95f4 [i, m) \u4e2d\nj = m\n} else { // \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn m\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1\n}\n
binary_search.zig
\n
"},{"location":"chapter_searching/binary_search/#_3","title":"\u4e24\u79cd\u8868\u793a\u5bf9\u6bd4","text":"

\u5bf9\u6bd4\u4e0b\u6765\uff0c\u4e24\u79cd\u8868\u793a\u7684\u4ee3\u7801\u5199\u6cd5\u6709\u4ee5\u4e0b\u4e0d\u540c\u70b9\uff1a

\u8868\u793a\u65b9\u6cd5 \u521d\u59cb\u5316\u6307\u9488 \u7f29\u5c0f\u533a\u95f4 \u5faa\u73af\u7ec8\u6b62\u6761\u4ef6 \u53cc\u95ed\u533a\u95f4 \\([0, n-1]\\) \\(i = 0\\) , \\(j = n-1\\) \\(i = m + 1\\) , \\(j = m - 1\\) \\(i > j\\) \u5de6\u95ed\u53f3\u5f00 \\([0, n)\\) \\(i = 0\\) , \\(j = n\\) \\(i = m + 1\\) , \\(j = m\\) \\(i = j\\)

\u89c2\u5bdf\u53d1\u73b0\uff0c\u5728\u201c\u53cc\u95ed\u533a\u95f4\u201d\u8868\u793a\u4e2d\uff0c\u7531\u4e8e\u5bf9\u5de6\u53f3\u4e24\u8fb9\u754c\u7684\u5b9a\u4e49\u662f\u76f8\u540c\u7684\uff0c\u56e0\u6b64\u7f29\u5c0f\u533a\u95f4\u7684 \\(i\\) , \\(j\\) \u5904\u7406\u65b9\u6cd5\u4e5f\u662f\u5bf9\u79f0\u7684\uff0c\u8fd9\u6837\u66f4\u4e0d\u5bb9\u6613\u51fa\u9519\u3002\u7efc\u4e0a\u6240\u8ff0\uff0c\u5efa\u8bae\u4f60\u91c7\u7528\u201c\u53cc\u95ed\u533a\u95f4\u201d\u7684\u5199\u6cd5\u3002

"},{"location":"chapter_searching/binary_search/#_4","title":"\u5927\u6570\u8d8a\u754c\u5904\u7406","text":"

\u5f53\u6570\u7ec4\u957f\u5ea6\u5f88\u5927\u65f6\uff0c\u52a0\u6cd5 \\(i + j\\) \u7684\u7ed3\u679c\u6709\u53ef\u80fd\u4f1a\u8d85\u51fa int \u7c7b\u578b\u7684\u53d6\u503c\u8303\u56f4\u3002\u5728\u6b64\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u9700\u8981\u6362\u4e00\u79cd\u8ba1\u7b97\u4e2d\u70b9\u7684\u5199\u6cd5\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa int \u7684\u53d6\u503c\u8303\u56f4\nint m = (i + j) / 2;\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nint m = i + (j - i) / 2;\n
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa int \u7684\u53d6\u503c\u8303\u56f4\nint m = (i + j) / 2;\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nint m = i + (j - i) / 2;\n
# Python \u4e2d\u7684\u6570\u5b57\u7406\u8bba\u4e0a\u53ef\u4ee5\u65e0\u9650\u5927\uff08\u53d6\u51b3\u4e8e\u5185\u5b58\u5927\u5c0f\uff09\n# \u56e0\u6b64\u65e0\u9700\u8003\u8651\u5927\u6570\u8d8a\u754c\u95ee\u9898\n
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa int \u7684\u53d6\u503c\u8303\u56f4\nm := (i + j) / 2\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nm := i + (j - i) / 2\n
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa int \u7684\u53d6\u503c\u8303\u56f4\nlet m = parseInt((i + j) / 2);\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nlet m = parseInt(i + (j - i) / 2);\n
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa Number \u7684\u53d6\u503c\u8303\u56f4\nlet m = Math.floor((i + j) / 2);\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nlet m = Math.floor(i + (j - i) / 2);\n
\n
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa int \u7684\u53d6\u503c\u8303\u56f4\nint m = (i + j) / 2;\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nint m = i + (j - i) / 2;\n
// (i + j) \u6709\u53ef\u80fd\u8d85\u51fa int \u7684\u53d6\u503c\u8303\u56f4\nlet m = (i + j) / 2\n// \u66f4\u6362\u4e3a\u6b64\u5199\u6cd5\u5219\u4e0d\u4f1a\u8d8a\u754c\nlet m = i + (j - 1) / 2\n
\n
"},{"location":"chapter_searching/binary_search/#1022","title":"10.2.2. \u590d\u6742\u5ea6\u5206\u6790","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(\\log n)\\) \uff1a\u5176\u4e2d \\(n\\) \u4e3a\u6570\u7ec4\u6216\u94fe\u8868\u957f\u5ea6\uff1b\u6bcf\u8f6e\u6392\u9664\u4e00\u534a\u7684\u533a\u95f4\uff0c\u56e0\u6b64\u5faa\u73af\u8f6e\u6570\u4e3a \\(\\log_2 n\\) \uff0c\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\u3002

\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff1a\u6307\u9488 i , j \u4f7f\u7528\u5e38\u6570\u5927\u5c0f\u7a7a\u95f4\u3002

"},{"location":"chapter_searching/binary_search/#1023","title":"10.2.3. \u4f18\u70b9\u4e0e\u7f3a\u70b9","text":"

\u4e8c\u5206\u67e5\u627e\u6548\u7387\u5f88\u9ad8\uff0c\u4f53\u73b0\u5728\uff1a

  • \u4e8c\u5206\u67e5\u627e\u65f6\u95f4\u590d\u6742\u5ea6\u4f4e\u3002\u5bf9\u6570\u9636\u5728\u6570\u636e\u91cf\u5f88\u5927\u65f6\u5177\u6709\u5de8\u5927\u4f18\u52bf\uff0c\u4f8b\u5982\uff0c\u5f53\u6570\u636e\u5927\u5c0f \\(n = 2^{20}\\) \u65f6\uff0c\u7ebf\u6027\u67e5\u627e\u9700\u8981 \\(2^{20} = 1048576\\) \u8f6e\u5faa\u73af\uff0c\u800c\u4e8c\u5206\u67e5\u627e\u4ec5\u9700\u8981 \\(\\log_2 2^{20} = 20\\) \u8f6e\u5faa\u73af\u3002
  • \u4e8c\u5206\u67e5\u627e\u4e0d\u9700\u8981\u989d\u5916\u7a7a\u95f4\u3002\u76f8\u5bf9\u4e8e\u501f\u52a9\u989d\u5916\u6570\u636e\u7ed3\u6784\u6765\u5b9e\u73b0\u67e5\u627e\u7684\u7b97\u6cd5\u6765\u8bf4\uff0c\u5176\u66f4\u52a0\u8282\u7ea6\u7a7a\u95f4\u4f7f\u7528\u3002

\u4f46\u5e76\u4e0d\u610f\u5473\u7740\u6240\u6709\u60c5\u51b5\u4e0b\u90fd\u5e94\u4f7f\u7528\u4e8c\u5206\u67e5\u627e\uff0c\u8fd9\u662f\u56e0\u4e3a\uff1a

  • \u4e8c\u5206\u67e5\u627e\u4ec5\u9002\u7528\u4e8e\u6709\u5e8f\u6570\u636e\u3002\u5982\u679c\u8f93\u5165\u6570\u636e\u662f\u65e0\u5e8f\u7684\uff0c\u4e3a\u4e86\u4f7f\u7528\u4e8c\u5206\u67e5\u627e\u800c\u4e13\u95e8\u6267\u884c\u6570\u636e\u6392\u5e8f\uff0c\u90a3\u4e48\u662f\u5f97\u4e0d\u507f\u5931\u7684\uff0c\u56e0\u4e3a\u6392\u5e8f\u7b97\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e00\u822c\u4e3a \\(O(n \\log n)\\) \uff0c\u6bd4\u7ebf\u6027\u67e5\u627e\u548c\u4e8c\u5206\u67e5\u627e\u90fd\u66f4\u5dee\u3002\u518d\u4f8b\u5982\uff0c\u5bf9\u4e8e\u9891\u7e41\u63d2\u5165\u5143\u7d20\u7684\u573a\u666f\uff0c\u4e3a\u4e86\u4fdd\u6301\u6570\u7ec4\u7684\u6709\u5e8f\u6027\uff0c\u9700\u8981\u5c06\u5143\u7d20\u63d2\u5165\u5230\u7279\u5b9a\u4f4d\u7f6e\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \uff0c\u4e5f\u662f\u975e\u5e38\u6602\u8d35\u7684\u3002
  • \u4e8c\u5206\u67e5\u627e\u4ec5\u9002\u7528\u4e8e\u6570\u7ec4\u3002\u7531\u4e8e\u5728\u4e8c\u5206\u67e5\u627e\u4e2d\uff0c\u8bbf\u95ee\u7d22\u5f15\u662f \u201c\u975e\u8fde\u7eed\u201d \u7684\uff0c\u56e0\u6b64\u94fe\u8868\u6216\u8005\u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6570\u636e\u7ed3\u6784\u90fd\u65e0\u6cd5\u4f7f\u7528\u3002
  • \u5728\u5c0f\u6570\u636e\u91cf\u4e0b\uff0c\u7ebf\u6027\u67e5\u627e\u7684\u6027\u80fd\u66f4\u597d\u3002\u5728\u7ebf\u6027\u67e5\u627e\u4e2d\uff0c\u6bcf\u8f6e\u53ea\u9700\u8981 1 \u6b21\u5224\u65ad\u64cd\u4f5c\uff1b\u800c\u5728\u4e8c\u5206\u67e5\u627e\u4e2d\uff0c\u9700\u8981 1 \u6b21\u52a0\u6cd5\u30011 \u6b21\u9664\u6cd5\u30011 ~ 3 \u6b21\u5224\u65ad\u64cd\u4f5c\u30011 \u6b21\u52a0\u6cd5\uff08\u51cf\u6cd5\uff09\uff0c\u5171 4 ~ 6 \u4e2a\u5355\u5143\u64cd\u4f5c\uff1b\u56e0\u6b64\uff0c\u5728\u6570\u636e\u91cf \\(n\\) \u8f83\u5c0f\u65f6\uff0c\u7ebf\u6027\u67e5\u627e\u53cd\u800c\u6bd4\u4e8c\u5206\u67e5\u627e\u66f4\u5feb\u3002
"},{"location":"chapter_searching/hashing_search/","title":"10.3. \u54c8\u5e0c\u67e5\u627e","text":"

Question

\u5728\u6570\u636e\u91cf\u5f88\u5927\u65f6\uff0c\u300c\u7ebf\u6027\u67e5\u627e\u300d\u592a\u6162\uff1b\u800c\u300c\u4e8c\u5206\u67e5\u627e\u300d\u8981\u6c42\u6570\u636e\u5fc5\u987b\u662f\u6709\u5e8f\u7684\uff0c\u5e76\u4e14\u53ea\u80fd\u5728\u6570\u7ec4\u4e2d\u5e94\u7528\u3002\u90a3\u4e48\u662f\u5426\u6709\u65b9\u6cd5\u53ef\u4ee5\u540c\u65f6\u907f\u514d\u4e0a\u8ff0\u7f3a\u70b9\u5462\uff1f\u7b54\u6848\u662f\u80af\u5b9a\u7684\uff0c\u6b64\u65b9\u6cd5\u88ab\u79f0\u4e3a\u300c\u54c8\u5e0c\u67e5\u627e\u300d\u3002

\u300c\u54c8\u5e0c\u67e5\u627e Hash Searching\u300d\u501f\u52a9\u4e00\u4e2a\u54c8\u5e0c\u8868\u6765\u5b58\u50a8\u9700\u8981\u7684\u300c\u952e\u503c\u5bf9 Key Value Pair\u300d\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 \\(O(1)\\) \u65f6\u95f4\u4e0b\u5b9e\u73b0\u201c\u952e \\(\\rightarrow\\) \u503c\u201d\u6620\u5c04\u67e5\u627e\uff0c\u4f53\u73b0\u7740\u201c\u4ee5\u7a7a\u95f4\u6362\u65f6\u95f4\u201d\u7684\u7b97\u6cd5\u601d\u60f3\u3002

"},{"location":"chapter_searching/hashing_search/#1031","title":"10.3.1. \u7b97\u6cd5\u5b9e\u73b0","text":"

\u5982\u679c\u6211\u4eec\u60f3\u8981\u7ed9\u5b9a\u6570\u7ec4\u4e2d\u7684\u4e00\u4e2a\u76ee\u6807\u5143\u7d20 target \uff0c\u83b7\u53d6\u8be5\u5143\u7d20\u7684\u7d22\u5f15\uff0c\u90a3\u4e48\u53ef\u4ee5\u501f\u52a9\u4e00\u4e2a\u54c8\u5e0c\u8868\u5b9e\u73b0\u67e5\u627e\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig hashing_search.java
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nint hashingSearchArray(Map<Integer, Integer> map, int target) {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn map.getOrDefault(target, -1);\n}\n
hashing_search.cpp
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nint hashingSearchArray(unordered_map<int, int> map, int target) {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nif (map.find(target) == map.end())\nreturn -1;\nreturn map[target];\n}\n
hashing_search.py
\"\"\" \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 \"\"\"\ndef hashing_search_array(mapp, target):\n# \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n# \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn mapp.get(target, -1)\n
hashing_search.go
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunc hashingSearchArray(m map[int]int, target int) int {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nif index, ok := m[target]; ok {\nreturn index\n} else {\nreturn -1\n}\n}\n
hashing_search.js
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunction hashingSearchArray(map, target) {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn map.has(target) ? map.get(target) : -1;\n}\n
hashing_search.ts
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunction hashingSearchArray(map: Map<number, number>, target: number): number {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn map.has(target) ? map.get(target) as number : -1;\n}\n
hashing_search.c
\n
hashing_search.cs
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nint hashingSearchArray(Dictionary<int, int> map, int target)\n{\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn map.GetValueOrDefault(target, -1);\n}\n
hashing_search.swift
/* \u54c8\u5e0c\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunc hashingSearchArray(map: [Int: Int], target: Int) -> Int {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7d22\u5f15\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn map[target, default: -1]\n}\n
hashing_search.zig
\n

\u518d\u6bd4\u5982\uff0c\u5982\u679c\u6211\u4eec\u60f3\u8981\u7ed9\u5b9a\u4e00\u4e2a\u76ee\u6807\u7ed3\u70b9\u503c target \uff0c\u83b7\u53d6\u5bf9\u5e94\u7684\u94fe\u8868\u7ed3\u70b9\u5bf9\u8c61\uff0c\u90a3\u4e48\u4e5f\u53ef\u4ee5\u4f7f\u7528\u54c8\u5e0c\u67e5\u627e\u5b9e\u73b0\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig hashing_search.java
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nListNode hashingSearchLinkedList(Map<Integer, ListNode> map, int target) {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de null\nreturn map.getOrDefault(target, null);\n}\n
hashing_search.cpp
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nListNode* hashingSearchLinkedList(unordered_map<int, ListNode*> map, int target) {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de nullptr\nif (map.find(target) == map.end())\nreturn nullptr;\nreturn map[target];\n}\n
hashing_search.py
\"\"\"  \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 \"\"\"\ndef hashing_search_linkedlist(mapp, target):\n# \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u5143\u7d20\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n# \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de -1\nreturn mapp.get(target, -1)\n
hashing_search.go
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nfunc hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de nil\nif node, ok := m[target]; ok {\nreturn node\n} else {\nreturn nil\n}\n}\n
hashing_search.js
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nfunction hashingSearchLinkedList(map, target) {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de null\nreturn map.has(target) ? map.get(target) : null;\n}\n
hashing_search.ts
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nfunction hashingSearchLinkedList(map: Map<number, ListNode>, target: number): ListNode | null {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de null\nreturn map.has(target) ? map.get(target) as ListNode : null;\n}\n
hashing_search.c
\n
hashing_search.cs
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nListNode? hashingSearchLinkedList(Dictionary<int, ListNode> map, int target)\n{\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de null\nreturn map.GetValueOrDefault(target);\n}\n
hashing_search.swift
/* \u54c8\u5e0c\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nfunc hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? {\n// \u54c8\u5e0c\u8868\u7684 key: \u76ee\u6807\u7ed3\u70b9\u503c\uff0cvalue: \u7ed3\u70b9\u5bf9\u8c61\n// \u82e5\u54c8\u5e0c\u8868\u4e2d\u65e0\u6b64 key \uff0c\u8fd4\u56de null\nreturn map[target]\n}\n
hashing_search.zig
\n
"},{"location":"chapter_searching/hashing_search/#1032","title":"10.3.2. \u590d\u6742\u5ea6\u5206\u6790","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff1a\u54c8\u5e0c\u8868\u7684\u67e5\u627e\u64cd\u4f5c\u4f7f\u7528 \\(O(1)\\) \u65f6\u95f4\u3002

\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \uff1a\u5176\u4e2d \\(n\\) \u4e3a\u6570\u7ec4\u6216\u94fe\u8868\u957f\u5ea6\u3002

"},{"location":"chapter_searching/hashing_search/#1033","title":"10.3.3. \u4f18\u70b9\u4e0e\u7f3a\u70b9","text":"

\u5728\u54c8\u5e0c\u8868\u4e2d\uff0c\u67e5\u627e\u3001\u63d2\u5165\u3001\u5220\u9664\u64cd\u4f5c\u7684\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u4e3a \\(O(1)\\) \uff0c\u8fd9\u610f\u5473\u7740\u65e0\u8bba\u662f\u9ad8\u9891\u589e\u5220\u8fd8\u662f\u9ad8\u9891\u67e5\u627e\u573a\u666f\uff0c\u54c8\u5e0c\u67e5\u627e\u7684\u6027\u80fd\u8868\u73b0\u90fd\u975e\u5e38\u597d\u3002\u5f53\u7136\uff0c\u4e00\u5207\u7684\u524d\u63d0\u662f\u4fdd\u8bc1\u54c8\u5e0c\u8868\u672a\u9000\u5316\u3002

\u5373\u4f7f\u5982\u6b64\uff0c\u54c8\u5e0c\u67e5\u627e\u4ecd\u5b58\u5728\u4e00\u4e9b\u95ee\u9898\uff0c\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\uff0c\u9700\u8981\u6839\u636e\u60c5\u51b5\u7075\u6d3b\u9009\u62e9\u65b9\u6cd5\u3002

  • \u8f85\u52a9\u54c8\u5e0c\u8868 \u9700\u8981\u4f7f\u7528 \\(O(n)\\) \u7684\u989d\u5916\u7a7a\u95f4\uff0c\u610f\u5473\u7740\u9700\u8981\u9884\u7559\u66f4\u591a\u7684\u8ba1\u7b97\u673a\u5185\u5b58\uff1b
  • \u5efa\u7acb\u548c\u7ef4\u62a4\u54c8\u5e0c\u8868\u9700\u8981\u65f6\u95f4\uff0c\u56e0\u6b64\u54c8\u5e0c\u67e5\u627e \u4e0d\u9002\u5408\u9ad8\u9891\u589e\u5220\u3001\u4f4e\u9891\u67e5\u627e\u7684\u4f7f\u7528\u573a\u666f\uff1b
  • \u5f53\u54c8\u5e0c\u51b2\u7a81\u4e25\u91cd\u65f6\uff0c\u54c8\u5e0c\u8868\u4f1a\u9000\u5316\u4e3a\u94fe\u8868\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u52a3\u5316\u81f3 \\(O(n)\\) \uff1b
  • \u5f53\u6570\u636e\u91cf\u5f88\u5c0f\u65f6\uff0c\u7ebf\u6027\u67e5\u627e\u6bd4\u54c8\u5e0c\u67e5\u627e\u66f4\u5feb\u3002\u8fd9\u662f\u56e0\u4e3a\u8ba1\u7b97\u54c8\u5e0c\u6620\u5c04\u51fd\u6570\u53ef\u80fd\u6bd4\u904d\u5386\u4e00\u4e2a\u5c0f\u578b\u6570\u7ec4\u66f4\u6162\uff1b
"},{"location":"chapter_searching/linear_search/","title":"10.1. \u7ebf\u6027\u67e5\u627e","text":"

\u300c\u7ebf\u6027\u67e5\u627e Linear Search\u300d\u662f\u4e00\u79cd\u6700\u57fa\u7840\u7684\u67e5\u627e\u65b9\u6cd5\uff0c\u5176\u4ece\u6570\u636e\u7ed3\u6784\u7684\u4e00\u7aef\u5f00\u59cb\uff0c\u4f9d\u6b21\u8bbf\u95ee\u6bcf\u4e2a\u5143\u7d20\uff0c\u76f4\u5230\u53e6\u4e00\u7aef\u540e\u505c\u6b62\u3002

"},{"location":"chapter_searching/linear_search/#1011","title":"10.1.1. \u7b97\u6cd5\u5b9e\u73b0","text":"

\u7ebf\u6027\u67e5\u627e\u5b9e\u8d28\u4e0a\u5c31\u662f\u904d\u5386\u6570\u636e\u7ed3\u6784 + \u5224\u65ad\u6761\u4ef6\u3002\u6bd4\u5982\uff0c\u6211\u4eec\u60f3\u8981\u5728\u6570\u7ec4 nums \u4e2d\u67e5\u627e\u76ee\u6807\u5143\u7d20 target \u7684\u5bf9\u5e94\u7d22\u5f15\uff0c\u90a3\u4e48\u53ef\u4ee5\u5728\u6570\u7ec4\u4e2d\u8fdb\u884c\u7ebf\u6027\u67e5\u627e\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linear_search.java
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nint linearSearchArray(int[] nums, int target) {\n// \u904d\u5386\u6570\u7ec4\nfor (int i = 0; i < nums.length; i++) {\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif (nums[i] == target)\nreturn i;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
linear_search.cpp
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nint linearSearchArray(vector<int>& nums, int target) {\n// \u904d\u5386\u6570\u7ec4\nfor (int i = 0; i < nums.size(); i++) {\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif (nums[i] == target)\nreturn i;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
linear_search.py
\"\"\" \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 \"\"\"\ndef linear_search_array(nums, target):\n# \u904d\u5386\u6570\u7ec4\nfor i in range(len(nums)):\nif nums[i] == target:  # \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nreturn i\nreturn -1                  # \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\n
linear_search.go
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunc linearSearchArray(nums []int, target int) int {\n// \u904d\u5386\u6570\u7ec4\nfor i := 0; i < len(nums); i++ {\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif nums[i] == target {\nreturn i\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1\n}\n
linear_search.js
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunction linearSearchArray(nums, target) {\n// \u904d\u5386\u6570\u7ec4\nfor (let i = 0; i < nums.length; i++) {\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif (nums[i] === target) {\nreturn i;\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1; }\n
linear_search.ts
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09*/\nfunction linearSearchArray(nums: number[], target: number): number {\n// \u904d\u5386\u6570\u7ec4\nfor (let i = 0; i < nums.length; i++) {\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif (nums[i] === target) {\nreturn i;\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
linear_search.c
\n
linear_search.cs
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nint linearSearchArray(int[] nums, int target)\n{\n// \u904d\u5386\u6570\u7ec4\nfor (int i = 0; i < nums.Length; i++)\n{\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif (nums[i] == target)\nreturn i;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1;\n}\n
linear_search.swift
/* \u7ebf\u6027\u67e5\u627e\uff08\u6570\u7ec4\uff09 */\nfunc linearSearchArray(nums: [Int], target: Int) -> Int {\n// \u904d\u5386\u6570\u7ec4\nfor i in nums.indices {\n// \u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de\u5176\u7d22\u5f15\nif nums[i] == target {\nreturn i\n}\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de -1\nreturn -1\n}\n
linear_search.zig
\n

\u518d\u6bd4\u5982\uff0c\u6211\u4eec\u60f3\u8981\u5728\u7ed9\u5b9a\u4e00\u4e2a\u76ee\u6807\u7ed3\u70b9\u503c target \uff0c\u8fd4\u56de\u6b64\u7ed3\u70b9\u5bf9\u8c61\uff0c\u4e5f\u53ef\u4ee5\u5728\u94fe\u8868\u4e2d\u8fdb\u884c\u7ebf\u6027\u67e5\u627e\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linear_search.java
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nListNode linearSearchLinkedList(ListNode head, int target) {\n// \u904d\u5386\u94fe\u8868\nwhile (head != null) {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif (head.val == target)\nreturn head;\nhead = head.next;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de null\nreturn null;\n}\n
linear_search.cpp
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nListNode* linearSearchLinkedList(ListNode* head, int target) {\n// \u904d\u5386\u94fe\u8868\nwhile (head != nullptr) {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif (head->val == target)\nreturn head;\nhead = head->next;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de nullptr\nreturn nullptr;\n}\n
linear_search.py
\"\"\" \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09 \"\"\"\ndef linear_search_linkedlist(head, target):\n# \u904d\u5386\u94fe\u8868\nwhile head:\nif head.val == target: # \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nreturn head\nhead = head.next\nreturn None                # \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de None\n
linear_search.go
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09*/\nfunc linerSearchLinkedList(node *ListNode, target int) *ListNode {\n// \u904d\u5386\u94fe\u8868\nfor node != nil {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif node.Val == target {\nreturn node\n}\nnode = node.Next\n}\n// \u672a\u627e\u5230\u76ee\u6807\u5143\u7d20\uff0c\u8fd4\u56de nil\nreturn nil\n}\n
linear_search.js
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09*/\nfunction linearSearchLinkedList(head, target) {\n// \u904d\u5386\u94fe\u8868\nwhile(head) {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif(head.val === target) {\nreturn head;\n}\nhead = head.next;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de null\nreturn null;\n}\n
linear_search.ts
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09*/\nfunction linearSearchLinkedList(head: ListNode | null, target: number): ListNode | null {\n// \u904d\u5386\u94fe\u8868\nwhile (head) {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif (head.val === target) {\nreturn head;\n}\nhead = head.next;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de null\nreturn null;\n}\n
linear_search.c
\n
linear_search.cs
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nListNode? linearSearchLinkedList(ListNode head, int target)\n{\n// \u904d\u5386\u94fe\u8868\nwhile (head != null)\n{\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif (head.val == target)\nreturn head;\nhead = head.next;\n}\n// \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de null\nreturn null;\n}\n
linear_search.swift
/* \u7ebf\u6027\u67e5\u627e\uff08\u94fe\u8868\uff09 */\nfunc linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? {\nvar head = head\n// \u904d\u5386\u94fe\u8868\nwhile head != nil {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de\u4e4b\nif head?.val == target {\nreturn head\n}\nhead = head?.next\n}\n// \u672a\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8fd4\u56de null\nreturn nil\n}\n
linear_search.zig
\n
"},{"location":"chapter_searching/linear_search/#1012","title":"10.1.2. \u590d\u6742\u5ea6\u5206\u6790","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \uff1a\u5176\u4e2d \\(n\\) \u4e3a\u6570\u7ec4\u6216\u94fe\u8868\u957f\u5ea6\u3002

\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff1a\u65e0\u9700\u4f7f\u7528\u989d\u5916\u7a7a\u95f4\u3002

"},{"location":"chapter_searching/linear_search/#1013","title":"10.1.3. \u4f18\u70b9\u4e0e\u7f3a\u70b9","text":"

\u7ebf\u6027\u67e5\u627e\u7684\u901a\u7528\u6027\u6781\u4f73\u3002\u7531\u4e8e\u7ebf\u6027\u67e5\u627e\u662f\u4f9d\u6b21\u8bbf\u95ee\u5143\u7d20\u7684\uff0c\u5373\u6ca1\u6709\u8df3\u8dc3\u8bbf\u95ee\u5143\u7d20\uff0c\u56e0\u6b64\u6570\u7ec4\u6216\u94fe\u8868\u7686\u9002\u7528\u3002

\u7ebf\u6027\u67e5\u627e\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u592a\u9ad8\u3002\u5728\u6570\u636e\u91cf \\(n\\) \u5f88\u5927\u65f6\uff0c\u67e5\u627e\u6548\u7387\u5f88\u4f4e\u3002

"},{"location":"chapter_searching/summary/","title":"10.4. \u5c0f\u7ed3","text":"
  • \u7ebf\u6027\u67e5\u627e\u662f\u4e00\u79cd\u6700\u57fa\u7840\u7684\u67e5\u627e\u65b9\u6cd5\uff0c\u901a\u8fc7\u904d\u5386\u6570\u636e\u7ed3\u6784 + \u5224\u65ad\u6761\u4ef6\u5b9e\u73b0\u67e5\u627e\u3002
  • \u4e8c\u5206\u67e5\u627e\u5229\u7528\u6570\u636e\u7684\u6709\u5e8f\u6027\uff0c\u901a\u8fc7\u5faa\u73af\u4e0d\u65ad\u7f29\u5c0f\u4e00\u534a\u641c\u7d22\u533a\u95f4\u6765\u5b9e\u73b0\u67e5\u627e\uff0c\u5176\u8981\u6c42\u8f93\u5165\u6570\u636e\u662f\u6709\u5e8f\u7684\uff0c\u5e76\u4e14\u4ec5\u9002\u7528\u4e8e\u6570\u7ec4\u6216\u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6570\u636e\u7ed3\u6784\u3002
  • \u54c8\u5e0c\u67e5\u627e\u501f\u52a9\u54c8\u5e0c\u8868\u6765\u5b9e\u73b0\u5e38\u6570\u9636\u65f6\u95f4\u590d\u6742\u5ea6\u7684\u67e5\u627e\u64cd\u4f5c\uff0c\u4f53\u73b0\u4ee5\u7a7a\u95f4\u6362\u65f6\u95f4\u7684\u7b97\u6cd5\u601d\u60f3\u3002

Table. \u4e09\u79cd\u67e5\u627e\u65b9\u6cd5\u5bf9\u6bd4

\u7ebf\u6027\u67e5\u627e \u4e8c\u5206\u67e5\u627e \u54c8\u5e0c\u67e5\u627e \u9002\u7528\u6570\u636e\u7ed3\u6784 \u6570\u7ec4\u3001\u94fe\u8868 \u6570\u7ec4 \u6570\u7ec4\u3001\u94fe\u8868 \u8f93\u5165\u6570\u636e\u8981\u6c42 \u65e0 \u6709\u5e8f \u65e0 \u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u67e5\u627e / \u63d2\u5165 / \u5220\u9664 \\(O(n)\\) / \\(O(1)\\) / \\(O(n)\\) \\(O(\\log n)\\) / \\(O(n)\\) / \\(O(n)\\) \\(O(1)\\) / \\(O(1)\\) / \\(O(1)\\) \u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u67e5\u627e / \u63d2\u5165 / \u5220\u9664 \\(O(n)\\) / \\(O(1)\\) / \\(O(n)\\) \\(O(\\log n)\\) / \\(O(n)\\) / \\(O(n)\\) \\(O(n)\\) / \\(O(n)\\) / \\(O(n)\\) \u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \\(O(1)\\) \\(O(n)\\)"},{"location":"chapter_sorting/bubble_sort/","title":"11.2. \u5192\u6ce1\u6392\u5e8f","text":"

\u300c\u5192\u6ce1\u6392\u5e8f Bubble Sort\u300d\u662f\u4e00\u79cd\u6700\u57fa\u7840\u7684\u6392\u5e8f\u7b97\u6cd5\uff0c\u975e\u5e38\u9002\u5408\u4f5c\u4e3a\u7b2c\u4e00\u4e2a\u5b66\u4e60\u7684\u6392\u5e8f\u7b97\u6cd5\u3002\u987e\u540d\u601d\u4e49\uff0c\u300c\u5192\u6ce1\u300d\u662f\u8be5\u7b97\u6cd5\u7684\u6838\u5fc3\u64cd\u4f5c\u3002

\u4e3a\u4ec0\u4e48\u53eb\u201c\u5192\u6ce1\u201d

\u5728\u6c34\u4e2d\uff0c\u8d8a\u5927\u7684\u6ce1\u6ce1\u6d6e\u529b\u8d8a\u5927\uff0c\u6240\u4ee5\u6700\u5927\u7684\u6ce1\u6ce1\u4f1a\u6700\u5148\u6d6e\u5230\u6c34\u9762\u3002

\u300c\u5192\u6ce1\u300d\u64cd\u4f5c\u5219\u662f\u5728\u6a21\u62df\u4e0a\u8ff0\u8fc7\u7a0b\uff0c\u5177\u4f53\u505a\u6cd5\u4e3a\uff1a\u4ece\u6570\u7ec4\u6700\u5de6\u7aef\u5f00\u59cb\u5411\u53f3\u904d\u5386\uff0c\u4f9d\u6b21\u5bf9\u6bd4\u76f8\u90bb\u5143\u7d20\u5927\u5c0f\uff0c\u82e5 \u5de6\u5143\u7d20 > \u53f3\u5143\u7d20 \u5219\u5c06\u5b83\u4fe9\u4ea4\u6362\uff0c\u6700\u7ec8\u53ef\u5c06\u6700\u5927\u5143\u7d20\u79fb\u52a8\u81f3\u6570\u7ec4\u6700\u53f3\u7aef\u3002

\u5b8c\u6210\u6b64\u6b21\u5192\u6ce1\u64cd\u4f5c\u540e\uff0c\u6570\u7ec4\u6700\u5927\u5143\u7d20\u5df2\u5728\u6b63\u786e\u4f4d\u7f6e\uff0c\u63a5\u4e0b\u6765\u53ea\u9700\u6392\u5e8f\u5269\u4f59 \\(n - 1\\) \u4e2a\u5143\u7d20\u3002

Step 1Step 2Step 3Step 4Step 5Step 6Step 7

Fig. \u5192\u6ce1\u64cd\u4f5c

"},{"location":"chapter_sorting/bubble_sort/#1121","title":"11.2.1. \u7b97\u6cd5\u6d41\u7a0b","text":"
  1. \u8bbe\u6570\u7ec4\u957f\u5ea6\u4e3a \\(n\\) \uff0c\u5b8c\u6210\u7b2c\u4e00\u8f6e\u300c\u5192\u6ce1\u300d\u540e\uff0c\u6570\u7ec4\u6700\u5927\u5143\u7d20\u5df2\u5728\u6b63\u786e\u4f4d\u7f6e\uff0c\u63a5\u4e0b\u6765\u53ea\u9700\u6392\u5e8f\u5269\u4f59 \\(n - 1\\) \u4e2a\u5143\u7d20\u3002
  2. \u540c\u7406\uff0c\u5bf9\u5269\u4f59 \\(n - 1\\) \u4e2a\u5143\u7d20\u6267\u884c\u300c\u5192\u6ce1\u300d\uff0c\u53ef\u5c06\u7b2c\u4e8c\u5927\u5143\u7d20\u4ea4\u6362\u81f3\u6b63\u786e\u4f4d\u7f6e\uff0c\u56e0\u800c\u5f85\u6392\u5e8f\u5143\u7d20\u53ea\u5269 \\(n - 2\\) \u4e2a\u3002
  3. \u4ee5\u6b64\u7c7b\u63a8\u2026\u2026 \u5faa\u73af \\(n - 1\\) \u8f6e\u300c\u5192\u6ce1\u300d\uff0c\u5373\u53ef\u5b8c\u6210\u6574\u4e2a\u6570\u7ec4\u7684\u6392\u5e8f\u3002

Fig. \u5192\u6ce1\u6392\u5e8f\u6d41\u7a0b

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig bubble_sort.java
/* \u5192\u6ce1\u6392\u5e8f */\nvoid bubbleSort(int[] nums) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.length - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\n}\n}\n}\n}\n
bubble_sort.cpp
/* \u5192\u6ce1\u6392\u5e8f */\nvoid bubbleSort(vector<int>& nums) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.size() - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n// \u8fd9\u91cc\u4f7f\u7528\u4e86 std::swap() \u51fd\u6570\nswap(nums[j], nums[j + 1]);\n}\n}\n}\n}\n
bubble_sort.py
\"\"\" \u5192\u6ce1\u6392\u5e8f \"\"\"\ndef bubble_sort(nums):\nn = len(nums)\n# \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i in range(n - 1, 0, -1):\n# \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j in range(i):\nif nums[j] > nums[j + 1]:\n# \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nnums[j], nums[j + 1] = nums[j + 1], nums[j]\n
bubble_sort.go
/* \u5192\u6ce1\u6392\u5e8f */\nfunc bubbleSort(nums []int) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i := len(nums) - 1; i > 0; i-- {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j := 0; j < i; j++ {\nif nums[j] > nums[j+1] {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nnums[j], nums[j+1] = nums[j+1], nums[j]\n}\n}\n}\n}\n
bubble_sort.js
/* \u5192\u6ce1\u6392\u5e8f */\nfunction bubbleSort(nums) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (let i = nums.length - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (let j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\n}\n}\n}\n}\n
bubble_sort.ts
/* \u5192\u6ce1\u6392\u5e8f */\nfunction bubbleSort(nums: number[]): void {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (let i = nums.length - 1; i > 0; i--) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (let j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\n}\n}\n}\n}\n
bubble_sort.c
/* \u5192\u6ce1\u6392\u5e8f */\nvoid bubbleSort(int nums[], int size) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = 0; i < size - 1; i++)\n{\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < size - 1 - i; j++)\n{\nif (nums[j] > nums[j + 1])\n{\nint temp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = temp;\n}\n}\n}\n}\n
bubble_sort.cs
/* \u5192\u6ce1\u6392\u5e8f */\nvoid bubbleSort(int[] nums)\n{\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.Length - 1; i > 0; i--)\n{\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++)\n{\nif (nums[j] > nums[j + 1])\n{\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\n}\n}\n}\n}\n
bubble_sort.swift
/* \u5192\u6ce1\u6392\u5e8f */\nfunc bubbleSort(nums: inout [Int]) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i in stride(from: nums.count - 1, to: 0, by: -1) {\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j in stride(from: 0, to: i, by: 1) {\nif nums[j] > nums[j + 1] {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j]\nnums[j] = nums[j + 1]\nnums[j + 1] = tmp\n}\n}\n}\n}\n
bubble_sort.zig
\n
"},{"location":"chapter_sorting/bubble_sort/#1122","title":"11.2.2. \u7b97\u6cd5\u7279\u6027","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n^2)\\) \uff1a\u5404\u8f6e\u300c\u5192\u6ce1\u300d\u904d\u5386\u7684\u6570\u7ec4\u957f\u5ea6\u4e3a \\(n - 1\\) , \\(n - 2\\) , \\(\\cdots\\) , \\(2\\) , \\(1\\) \u6b21\uff0c\u6c42\u548c\u4e3a \\(\\frac{(n - 1) n}{2}\\) \uff0c\u56e0\u6b64\u4f7f\u7528 \\(O(n^2)\\) \u65f6\u95f4\u3002

\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff1a\u6307\u9488 \\(i\\) , \\(j\\) \u4f7f\u7528\u5e38\u6570\u5927\u5c0f\u7684\u989d\u5916\u7a7a\u95f4\u3002

\u539f\u5730\u6392\u5e8f\uff1a\u6307\u9488\u53d8\u91cf\u4ec5\u4f7f\u7528\u5e38\u6570\u5927\u5c0f\u989d\u5916\u7a7a\u95f4\u3002

\u7a33\u5b9a\u6392\u5e8f\uff1a\u4e0d\u4ea4\u6362\u76f8\u7b49\u5143\u7d20\u3002

\u81ea\u9002\u5e94\u6392\u5e8f\uff1a\u5f15\u5165 flag \u4f18\u5316\u540e\uff08\u89c1\u4e0b\u6587\uff09\uff0c\u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(N)\\) \u3002

"},{"location":"chapter_sorting/bubble_sort/#1123","title":"11.2.3. \u6548\u7387\u4f18\u5316","text":"

\u6211\u4eec\u53d1\u73b0\uff0c\u82e5\u5728\u67d0\u8f6e\u300c\u5192\u6ce1\u300d\u4e2d\u672a\u6267\u884c\u4efb\u4f55\u4ea4\u6362\u64cd\u4f5c\uff0c\u5219\u8bf4\u660e\u6570\u7ec4\u5df2\u7ecf\u5b8c\u6210\u6392\u5e8f\uff0c\u53ef\u76f4\u63a5\u8fd4\u56de\u7ed3\u679c\u3002\u8003\u8651\u53ef\u4ee5\u589e\u52a0\u4e00\u4e2a\u6807\u5fd7\u4f4d flag \u6765\u76d1\u542c\u8be5\u60c5\u51b5\uff0c\u82e5\u51fa\u73b0\u5219\u76f4\u63a5\u8fd4\u56de\u3002

\u4f18\u5316\u540e\uff0c\u5192\u6ce1\u6392\u5e8f\u7684\u6700\u5dee\u548c\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u4ecd\u4e3a \\(O(n^2)\\) \uff1b\u800c\u5728\u8f93\u5165\u6570\u7ec4 \u5df2\u6392\u5e8f \u65f6\uff0c\u8fbe\u5230 \u6700\u4f73\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig bubble_sort.java
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nvoid bubbleSortWithFlag(int[] nums) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.length - 1; i > 0; i--) {\nboolean flag = false; // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\nflag = true;  // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif (!flag) break;     // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\n}\n}\n
bubble_sort.cpp
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nvoid bubbleSortWithFlag(vector<int>& nums) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.size() - 1; i > 0; i--) {\nbool flag = false; // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\n// \u8fd9\u91cc\u4f7f\u7528\u4e86 std::swap() \u51fd\u6570\nswap(nums[j], nums[j + 1]);\nflag = true;  // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif (!flag) break;     // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\n}\n}\n
bubble_sort.py
\"\"\" \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09 \"\"\"\ndef bubble_sort_with_flag(nums):\nn = len(nums)\n# \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i in range(n - 1, 0, -1):\nflag = False  # \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n# \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j in range(i):\nif nums[j] > nums[j + 1]:\n# \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nnums[j], nums[j + 1] = nums[j + 1], nums[j]\nflag = True  # \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\nif not flag:\nbreak            # \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\n
bubble_sort.go
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nfunc bubbleSortWithFlag(nums []int) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i := len(nums) - 1; i > 0; i-- {\nflag := false // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor j := 0; j < i; j++ {\nif nums[j] > nums[j+1] {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nnums[j], nums[j+1] = nums[j+1], nums[j]\nflag = true // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif flag == false { // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\nbreak\n}\n}\n}\n
bubble_sort.js
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nfunction bubbleSortWithFlag(nums) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (let i = nums.length - 1; i > 0; i--) {\nlet flag = false; // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (let j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\nflag = true;  // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif (!flag) break;     // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\n}\n}\n
bubble_sort.ts
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nfunction bubbleSortWithFlag(nums: number[]): void {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (let i = nums.length - 1; i > 0; i--) {\nlet flag = false; // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (let j = 0; j < i; j++) {\nif (nums[j] > nums[j + 1]) {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\nflag = true;  // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif (!flag) break;     // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\n}\n}\n
bubble_sort.c
/* \u5192\u6ce1\u6392\u5e8f */\nvoid bubbleSortWithFlag(int nums[], int size) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = 0; i < size - 1; i++)\n{\nbool flag = false;\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < size - 1 - i; j++)\n{\nif (nums[j] > nums[j + 1])\n{\nint temp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = temp;\nflag = true;\n}\n}\nif(!flag) break;\n}\n}\n
bubble_sort.cs
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nvoid bubbleSortWithFlag(int[] nums)\n{\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor (int i = nums.Length - 1; i > 0; i--)\n{\nbool flag = false; // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\n// \u5185\u5faa\u73af\uff1a\u5192\u6ce1\u64cd\u4f5c\nfor (int j = 0; j < i; j++)\n{\nif (nums[j] > nums[j + 1])\n{\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nint tmp = nums[j];\nnums[j] = nums[j + 1];\nnums[j + 1] = tmp;\nflag = true;  // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif (!flag) break;     // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\n}\n}\n
bubble_sort.swift
/* \u5192\u6ce1\u6392\u5e8f\uff08\u6807\u5fd7\u4f18\u5316\uff09*/\nfunc bubbleSortWithFlag(nums: inout [Int]) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i in stride(from: nums.count - 1, to: 0, by: -1) {\nvar flag = false // \u521d\u59cb\u5316\u6807\u5fd7\u4f4d\nfor j in stride(from: 0, to: i, by: 1) {\nif nums[j] > nums[j + 1] {\n// \u4ea4\u6362 nums[j] \u4e0e nums[j + 1]\nlet tmp = nums[j]\nnums[j] = nums[j + 1]\nnums[j + 1] = tmp\nflag = true // \u8bb0\u5f55\u4ea4\u6362\u5143\u7d20\n}\n}\nif !flag { // \u6b64\u8f6e\u5192\u6ce1\u672a\u4ea4\u6362\u4efb\u4f55\u5143\u7d20\uff0c\u76f4\u63a5\u8df3\u51fa\nbreak\n}\n}\n}\n
bubble_sort.zig
\n
"},{"location":"chapter_sorting/insertion_sort/","title":"11.3. \u63d2\u5165\u6392\u5e8f","text":"

\u300c\u63d2\u5165\u6392\u5e8f Insertion Sort\u300d\u662f\u4e00\u79cd\u57fa\u4e8e \u6570\u7ec4\u63d2\u5165\u64cd\u4f5c \u7684\u6392\u5e8f\u7b97\u6cd5\u3002

\u300c\u63d2\u5165\u64cd\u4f5c\u300d\u539f\u7406\uff1a\u9009\u5b9a\u67d0\u4e2a\u5f85\u6392\u5e8f\u5143\u7d20\u4e3a\u57fa\u51c6\u6570 base\uff0c\u5c06 base \u4e0e\u5176\u5de6\u4fa7\u5df2\u6392\u5e8f\u533a\u95f4\u5143\u7d20\u4f9d\u6b21\u5bf9\u6bd4\u5927\u5c0f\uff0c\u5e76\u63d2\u5165\u5230\u6b63\u786e\u4f4d\u7f6e\u3002

\u56de\u5fc6\u6570\u7ec4\u63d2\u5165\u64cd\u4f5c\uff0c\u6211\u4eec\u9700\u8981\u5c06\u4ece\u76ee\u6807\u7d22\u5f15\u5230 base \u4e4b\u95f4\u7684\u6240\u6709\u5143\u7d20\u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\uff0c\u7136\u540e\u518d\u5c06 base \u8d4b\u503c\u7ed9\u76ee\u6807\u7d22\u5f15\u3002

Fig. \u63d2\u5165\u64cd\u4f5c

"},{"location":"chapter_sorting/insertion_sort/#1131","title":"11.3.1. \u7b97\u6cd5\u6d41\u7a0b","text":"
  1. \u7b2c 1 \u8f6e\u5148\u9009\u53d6\u6570\u7ec4\u7684 \u7b2c 2 \u4e2a\u5143\u7d20 \u4e3a base \uff0c\u6267\u884c\u300c\u63d2\u5165\u64cd\u4f5c\u300d\u540e\uff0c\u6570\u7ec4\u524d 2 \u4e2a\u5143\u7d20\u5df2\u5b8c\u6210\u6392\u5e8f\u3002
  2. \u7b2c 2 \u8f6e\u9009\u53d6 \u7b2c 3 \u4e2a\u5143\u7d20 \u4e3a base \uff0c\u6267\u884c\u300c\u63d2\u5165\u64cd\u4f5c\u300d\u540e\uff0c\u6570\u7ec4\u524d 3 \u4e2a\u5143\u7d20\u5df2\u5b8c\u6210\u6392\u5e8f\u3002
  3. \u4ee5\u6b64\u7c7b\u63a8\u2026\u2026\u6700\u540e\u4e00\u8f6e\u9009\u53d6 \u6570\u7ec4\u5c3e\u5143\u7d20 \u4e3a base \uff0c\u6267\u884c\u300c\u63d2\u5165\u64cd\u4f5c\u300d\u540e\uff0c\u6240\u6709\u5143\u7d20\u5df2\u5b8c\u6210\u6392\u5e8f\u3002

Fig. \u63d2\u5165\u6392\u5e8f\u6d41\u7a0b

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig insertion_sort.java
/* \u63d2\u5165\u6392\u5e8f */\nvoid insertionSort(int[] nums) {\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor (int i = 1; i < nums.length; i++) {\nint base = nums[i], j = i - 1;\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile (j >= 0 && nums[j] > base) {\nnums[j + 1] = nums[j];  // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj--;\n}\nnums[j + 1] = base;         // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.cpp
/* \u63d2\u5165\u6392\u5e8f */\nvoid insertionSort(vector<int>& nums) {\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor (int i = 1; i < nums.size(); i++) {\nint base = nums[i], j = i - 1;\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile (j >= 0 && nums[j] > base) {\nnums[j + 1] = nums[j];  // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj--;\n}\nnums[j + 1] = base;         // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.py
\"\"\" \u63d2\u5165\u6392\u5e8f \"\"\"\ndef insertion_sort(nums):\n# \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor i in range(1, len(nums)):\nbase = nums[i]\nj = i - 1\n# \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile j >= 0 and nums[j] > base:\nnums[j + 1] = nums[j]  # 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj -= 1\nnums[j + 1] = base         # 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n
insertion_sort.go
/* \u63d2\u5165\u6392\u5e8f */\nfunc insertionSort(nums []int) {\n// \u5916\u5faa\u73af\uff1a\u5f85\u6392\u5e8f\u5143\u7d20\u6570\u91cf\u4e3a n-1, n-2, ..., 1\nfor i := 1; i < len(nums); i++ {\nbase := nums[i]\nj := i - 1\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nfor j >= 0 && nums[j] > base {\nnums[j+1] = nums[j]     // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj--\n}\nnums[j+1] = base            // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.js
/* \u63d2\u5165\u6392\u5e8f */\nfunction insertionSort(nums) {\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor (let i = 1; i < nums.length; i++) {\nlet base = nums[i], j = i - 1;\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile (j >= 0 && nums[j] > base) {\nnums[j + 1] = nums[j];  // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj--;\n}\nnums[j + 1] = base;         // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.ts
/* \u63d2\u5165\u6392\u5e8f */\nfunction insertionSort(nums: number[]): void {\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor (let i = 1; i < nums.length; i++) {\nconst base = nums[i];\nlet j = i - 1;\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile (j >= 0 && nums[j] > base) {\nnums[j + 1] = nums[j];  // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj--;\n}\nnums[j + 1] = base;         // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.c
/* \u63d2\u5165\u6392\u5e8f */\nvoid insertionSort(int nums[], int size) {\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor (int i = 1; i < size; i++)\n{\nint base = nums[i], j = i - 1;\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile (j >= 0 && nums[j] > base)\n{\n// 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nnums[j + 1] = nums[j]; j--;\n}\n// 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\nnums[j + 1] = base; }\n}\n
insertion_sort.cs
/* \u63d2\u5165\u6392\u5e8f */\nvoid insertionSort(int[] nums)\n{\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor (int i = 1; i < nums.Length; i++)\n{\nint bas = nums[i], j = i - 1;\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile (j >= 0 && nums[j] > bas)\n{\nnums[j + 1] = nums[j]; // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj--;\n}\nnums[j + 1] = bas;         // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.swift
/* \u63d2\u5165\u6392\u5e8f */\nfunc insertionSort(nums: inout [Int]) {\n// \u5916\u5faa\u73af\uff1abase = nums[1], nums[2], ..., nums[n-1]\nfor i in stride(from: 1, to: nums.count, by: 1) {\nlet base = nums[i]\nvar j = i - 1\n// \u5185\u5faa\u73af\uff1a\u5c06 base \u63d2\u5165\u5230\u5de6\u8fb9\u7684\u6b63\u786e\u4f4d\u7f6e\nwhile j >= 0, nums[j] > base {\nnums[j + 1] = nums[j] // 1. \u5c06 nums[j] \u5411\u53f3\u79fb\u52a8\u4e00\u4f4d\nj -= 1\n}\nnums[j + 1] = base // 2. \u5c06 base \u8d4b\u503c\u5230\u6b63\u786e\u4f4d\u7f6e\n}\n}\n
insertion_sort.zig
\n
"},{"location":"chapter_sorting/insertion_sort/#1132","title":"11.3.2. \u7b97\u6cd5\u7279\u6027","text":"

\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n^2)\\) \uff1a\u6700\u5dee\u60c5\u51b5\u4e0b\uff0c\u5404\u8f6e\u63d2\u5165\u64cd\u4f5c\u5faa\u73af \\(n - 1\\) , \\(n-2\\) , \\(\\cdots\\) , \\(2\\) , \\(1\\) \u6b21\uff0c\u6c42\u548c\u4e3a \\(\\frac{(n - 1) n}{2}\\) \uff0c\u4f7f\u7528 \\(O(n^2)\\) \u65f6\u95f4\u3002

\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(1)\\) \uff1a\u6307\u9488 \\(i\\) , \\(j\\) \u4f7f\u7528\u5e38\u6570\u5927\u5c0f\u7684\u989d\u5916\u7a7a\u95f4\u3002

\u539f\u5730\u6392\u5e8f\uff1a\u6307\u9488\u53d8\u91cf\u4ec5\u4f7f\u7528\u5e38\u6570\u5927\u5c0f\u989d\u5916\u7a7a\u95f4\u3002

\u7a33\u5b9a\u6392\u5e8f\uff1a\u4e0d\u4ea4\u6362\u76f8\u7b49\u5143\u7d20\u3002

\u81ea\u9002\u5e94\u6392\u5e8f\uff1a\u6700\u4f73\u60c5\u51b5\u4e0b\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \u3002

"},{"location":"chapter_sorting/insertion_sort/#1133-vs","title":"11.3.3. \u63d2\u5165\u6392\u5e8f vs \u5192\u6ce1\u6392\u5e8f","text":"

Question

\u867d\u7136\u300c\u63d2\u5165\u6392\u5e8f\u300d\u548c\u300c\u5192\u6ce1\u6392\u5e8f\u300d\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u7686\u4e3a \\(O(n^2)\\) \uff0c\u4f46\u5b9e\u9645\u8fd0\u884c\u901f\u5ea6\u5374\u6709\u5f88\u5927\u5dee\u522b\uff0c\u8fd9\u662f\u4e3a\u4ec0\u4e48\u5462\uff1f

\u56de\u987e\u590d\u6742\u5ea6\u5206\u6790\uff0c\u4e24\u4e2a\u65b9\u6cd5\u7684\u5faa\u73af\u6b21\u6570\u90fd\u662f \\(\\frac{(n - 1) n}{2}\\) \u3002\u4f46\u4e0d\u540c\u7684\u662f\uff0c\u300c\u5192\u6ce1\u64cd\u4f5c\u300d\u662f\u5728\u505a \u5143\u7d20\u4ea4\u6362\uff0c\u9700\u8981\u501f\u52a9\u4e00\u4e2a\u4e34\u65f6\u53d8\u91cf\u5b9e\u73b0\uff0c\u5171 3 \u4e2a\u5355\u5143\u64cd\u4f5c\uff1b\u800c\u300c\u63d2\u5165\u64cd\u4f5c\u300d\u662f\u5728\u505a \u8d4b\u503c\uff0c\u53ea\u9700 1 \u4e2a\u5355\u5143\u64cd\u4f5c\uff1b\u56e0\u6b64\uff0c\u53ef\u4ee5\u7c97\u7565\u4f30\u8ba1\u51fa\u5192\u6ce1\u6392\u5e8f\u7684\u8ba1\u7b97\u5f00\u9500\u7ea6\u4e3a\u63d2\u5165\u6392\u5e8f\u7684 3 \u500d\u3002

\u63d2\u5165\u6392\u5e8f\u8fd0\u884c\u901f\u5ea6\u5feb\uff0c\u5e76\u4e14\u5177\u6709\u539f\u5730\u3001\u7a33\u5b9a\u3001\u81ea\u9002\u5e94\u7684\u4f18\u70b9\uff0c\u56e0\u6b64\u5f88\u53d7\u6b22\u8fce\u3002\u5b9e\u9645\u4e0a\uff0c\u5305\u62ec Java \u5728\u5185\u7684\u8bb8\u591a\u7f16\u7a0b\u8bed\u8a00\u7684\u6392\u5e8f\u5e93\u51fd\u6570\u7684\u5b9e\u73b0\u90fd\u7528\u5230\u4e86\u63d2\u5165\u6392\u5e8f\u3002\u5e93\u51fd\u6570\u7684\u5927\u81f4\u601d\u8def\uff1a

  • \u5bf9\u4e8e \u957f\u6570\u7ec4\uff0c\u91c7\u7528\u57fa\u4e8e\u5206\u6cbb\u7684\u6392\u5e8f\u7b97\u6cd5\uff0c\u4f8b\u5982\u300c\u5feb\u901f\u6392\u5e8f\u300d\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n \\log n)\\) \uff1b
  • \u5bf9\u4e8e \u77ed\u6570\u7ec4\uff0c\u76f4\u63a5\u4f7f\u7528\u300c\u63d2\u5165\u6392\u5e8f\u300d\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n^2)\\) \uff1b

\u5728\u6570\u7ec4\u8f83\u77ed\u65f6\uff0c\u590d\u6742\u5ea6\u4e2d\u7684\u5e38\u6570\u9879\uff08\u5373\u6bcf\u8f6e\u4e2d\u7684\u5355\u5143\u64cd\u4f5c\u6570\u91cf\uff09\u5360\u4e3b\u5bfc\u4f5c\u7528\uff0c\u6b64\u65f6\u63d2\u5165\u6392\u5e8f\u8fd0\u884c\u5730\u66f4\u5feb\u3002\u8fd9\u4e2a\u73b0\u8c61\u4e0e\u300c\u7ebf\u6027\u67e5\u627e\u300d\u548c\u300c\u4e8c\u5206\u67e5\u627e\u300d\u7684\u60c5\u51b5\u7c7b\u4f3c\u3002

"},{"location":"chapter_sorting/intro_to_sort/","title":"11.1. \u6392\u5e8f\u7b80\u4ecb","text":"

\u300c\u6392\u5e8f\u7b97\u6cd5 Sorting Algorithm\u300d\u4f7f\u5f97\u5217\u8868\u4e2d\u7684\u6240\u6709\u5143\u7d20\u6309\u7167\u4ece\u5c0f\u5230\u5927\u7684\u987a\u5e8f\u6392\u5217\u3002

  • \u5f85\u6392\u5e8f\u7684\u5217\u8868\u7684 \u5143\u7d20\u7c7b\u578b \u53ef\u4ee5\u662f\u6574\u6570\u3001\u6d6e\u70b9\u6570\u3001\u5b57\u7b26\u3001\u6216\u5b57\u7b26\u4e32\uff1b
  • \u6392\u5e8f\u7b97\u6cd5\u53ef\u4ee5\u6839\u636e\u9700\u8981\u8bbe\u5b9a \u5224\u65ad\u89c4\u5219\uff0c\u4f8b\u5982\u6570\u5b57\u5927\u5c0f\u3001\u5b57\u7b26 ASCII \u7801\u987a\u5e8f\u3001\u81ea\u5b9a\u4e49\u89c4\u5219\uff1b

Fig. \u6392\u5e8f\u4e2d\u7684\u4e0d\u540c\u5143\u7d20\u7c7b\u578b\u548c\u5224\u65ad\u89c4\u5219

"},{"location":"chapter_sorting/intro_to_sort/#1111","title":"11.1.1. \u8bc4\u4ef7\u7ef4\u5ea6","text":"

\u6392\u5e8f\u7b97\u6cd5\u4e3b\u8981\u53ef\u6839\u636e \u7a33\u5b9a\u6027 \u3001\u5c31\u5730\u6027 \u3001\u81ea\u9002\u5e94\u6027 \u3001\u6bd4\u8f83\u7c7b \u6765\u5206\u7c7b\u3002

"},{"location":"chapter_sorting/intro_to_sort/#_1","title":"\u7a33\u5b9a\u6027","text":"
  • \u300c\u7a33\u5b9a\u6392\u5e8f\u300d\u5728\u5b8c\u6210\u6392\u5e8f\u540e\uff0c\u4e0d\u6539\u53d8 \u76f8\u7b49\u5143\u7d20\u5728\u6570\u7ec4\u4e2d\u7684\u76f8\u5bf9\u987a\u5e8f\u3002
  • \u300c\u975e\u7a33\u5b9a\u6392\u5e8f\u300d\u5728\u5b8c\u6210\u6392\u5e8f\u540e\uff0c\u76f8\u7b49\u5143\u7d20\u5728\u6570\u7ec4\u4e2d\u7684\u76f8\u5bf9\u4f4d\u7f6e \u53ef\u80fd\u88ab\u6539\u53d8\u3002

\u5047\u8bbe\u6211\u4eec\u6709\u4e00\u4e2a\u5b58\u50a8\u5b66\u751f\u4fe1\u606f\u7684\u8868\u683c\uff0c\u7b2c 1, 2 \u5217\u5206\u522b\u662f\u59d3\u540d\u548c\u5e74\u9f84\u3002\u90a3\u4e48\u5728\u4ee5\u4e0b\u793a\u4f8b\u4e2d\uff0c\u300c\u975e\u7a33\u5b9a\u6392\u5e8f\u300d\u4f1a\u5bfc\u81f4\u8f93\u5165\u6570\u636e\u7684\u6709\u5e8f\u6027\u4e22\u5931\u3002\u56e0\u6b64\u300c\u7a33\u5b9a\u6392\u5e8f\u300d\u662f\u5f88\u597d\u7684\u7279\u6027\uff0c\u5728\u591a\u7ea7\u6392\u5e8f\u4e2d\u662f\u5fc5\u987b\u7684\u3002

  # \u8f93\u5165\u6570\u636e\u662f\u6309\u7167\u59d3\u540d\u6392\u5e8f\u597d\u7684\n# (name, age)\n('A', 19)\n('B', 18)\n('C', 21)\n('D', 19)\n('E', 23)\n# \u5047\u8bbe\u4f7f\u7528\u975e\u7a33\u5b9a\u6392\u5e8f\u7b97\u6cd5\u6309\u5e74\u9f84\u6392\u5e8f\u5217\u8868\uff0c\n# \u7ed3\u679c\u4e2d ('D', 19) \u548c ('A', 19) \u7684\u76f8\u5bf9\u4f4d\u7f6e\u6539\u53d8\uff0c\n# \u8f93\u5165\u6570\u636e\u6309\u59d3\u540d\u6392\u5e8f\u7684\u6027\u8d28\u4e22\u5931\n('B', 18)\n('D', 19)\n('A', 19)  ('C', 21)\n('E', 23)\n
"},{"location":"chapter_sorting/intro_to_sort/#_2","title":"\u5c31\u5730\u6027","text":"
  • \u300c\u539f\u5730\u6392\u5e8f\u300d\u65e0\u9700\u8f85\u52a9\u6570\u636e\uff0c\u4e0d\u4f7f\u7528\u989d\u5916\u7a7a\u95f4\uff1b
  • \u300c\u975e\u539f\u5730\u6392\u5e8f\u300d\u9700\u8981\u501f\u52a9\u8f85\u52a9\u6570\u636e\uff0c\u4f7f\u7528\u989d\u5916\u7a7a\u95f4\uff1b

\u300c\u539f\u5730\u6392\u5e8f\u300d\u4e0d\u4f7f\u7528\u989d\u5916\u7a7a\u95f4\uff0c\u53ef\u4ee5\u8282\u7ea6\u5185\u5b58\uff1b\u5e76\u4e14\u4e00\u822c\u60c5\u51b5\u4e0b\uff0c\u7531\u4e8e\u6570\u636e\u64cd\u4f5c\u51cf\u5c11\uff0c\u539f\u5730\u6392\u5e8f\u7684\u8fd0\u884c\u6548\u7387\u4e5f\u66f4\u9ad8\u3002

"},{"location":"chapter_sorting/intro_to_sort/#_3","title":"\u81ea\u9002\u5e94\u6027","text":"
  • \u300c\u81ea\u9002\u5e94\u6392\u5e8f\u300d\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u53d7\u8f93\u5165\u6570\u636e\u5f71\u54cd\uff0c\u5373\u6700\u4f73 / \u6700\u5dee / \u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u4e0d\u76f8\u7b49\u3002
  • \u300c\u975e\u81ea\u9002\u5e94\u6392\u5e8f\u300d\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u6052\u5b9a\uff0c\u4e0e\u8f93\u5165\u6570\u636e\u65e0\u5173\u3002

\u6211\u4eec\u5e0c\u671b \u6700\u5dee = \u5e73\u5747\uff0c\u5373\u4e0d\u5e0c\u671b\u6392\u5e8f\u7b97\u6cd5\u7684\u8fd0\u884c\u6548\u7387\u5728\u67d0\u4e9b\u8f93\u5165\u6570\u636e\u4e0b\u53d1\u751f\u52a3\u5316\u3002

"},{"location":"chapter_sorting/intro_to_sort/#_4","title":"\u6bd4\u8f83\u7c7b","text":"
  • \u300c\u6bd4\u8f83\u7c7b\u6392\u5e8f\u300d\u57fa\u4e8e\u5143\u7d20\u4e4b\u95f4\u7684\u6bd4\u8f83\u7b97\u5b50\uff08\u5c0f\u4e8e\u3001\u76f8\u7b49\u3001\u5927\u4e8e\uff09\u6765\u51b3\u5b9a\u5143\u7d20\u7684\u76f8\u5bf9\u987a\u5e8f\u3002
  • \u300c\u975e\u6bd4\u8f83\u7c7b\u6392\u5e8f\u300d\u4e0d\u57fa\u4e8e\u5143\u7d20\u4e4b\u95f4\u7684\u6bd4\u8f83\u7b97\u5b50\u6765\u51b3\u5b9a\u5143\u7d20\u7684\u76f8\u5bf9\u987a\u5e8f\u3002

\u300c\u6bd4\u8f83\u7c7b\u6392\u5e8f\u300d\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u6700\u4f18\u4e3a \\(O(n \\log n)\\) \uff1b\u800c\u300c\u975e\u6bd4\u8f83\u7c7b\u6392\u5e8f\u300d\u53ef\u4ee5\u8fbe\u5230 \\(O(n)\\) \u7684\u65f6\u95f4\u590d\u6742\u5ea6\uff0c\u4f46\u901a\u7528\u6027\u8f83\u5dee\u3002

"},{"location":"chapter_sorting/intro_to_sort/#1112","title":"11.1.2. \u7406\u60f3\u6392\u5e8f\u7b97\u6cd5","text":"
  • \u8fd0\u884c\u5feb\uff0c\u5373\u65f6\u95f4\u590d\u6742\u5ea6\u4f4e\uff1b
  • \u7a33\u5b9a\u6392\u5e8f\uff0c\u5373\u6392\u5e8f\u540e\u76f8\u7b49\u5143\u7d20\u7684\u76f8\u5bf9\u4f4d\u7f6e\u4e0d\u53d8\u5316\uff1b
  • \u539f\u5730\u6392\u5e8f\uff0c\u5373\u8fd0\u884c\u4e2d\u4e0d\u4f7f\u7528\u989d\u5916\u7684\u8f85\u52a9\u7a7a\u95f4\uff1b
  • \u6b63\u5411\u81ea\u9002\u5e94\u6027\uff0c\u5373\u7b97\u6cd5\u7684\u8fd0\u884c\u6548\u7387\u4e0d\u4f1a\u5728\u67d0\u4e9b\u8f93\u5165\u6570\u636e\u4e0b\u53d1\u751f\u52a3\u5316\uff1b

\u7136\u800c\uff0c\u6ca1\u6709\u6392\u5e8f\u7b97\u6cd5\u540c\u65f6\u5177\u5907\u4ee5\u4e0a\u6240\u6709\u7279\u6027\u3002\u6392\u5e8f\u7b97\u6cd5\u7684\u9009\u578b\u4f7f\u7528\u53d6\u51b3\u4e8e\u5177\u4f53\u7684\u5217\u8868\u7c7b\u578b\u3001\u5217\u8868\u957f\u5ea6\u3001\u5143\u7d20\u5206\u5e03\u7b49\u56e0\u7d20\u3002

"},{"location":"chapter_sorting/merge_sort/","title":"11.5. \u5f52\u5e76\u6392\u5e8f","text":"

\u300c\u5f52\u5e76\u6392\u5e8f Merge Sort\u300d\u662f\u7b97\u6cd5\u4e2d\u201c\u5206\u6cbb\u601d\u60f3\u201d\u7684\u5178\u578b\u4f53\u73b0\uff0c\u5176\u6709\u300c\u5212\u5206\u300d\u548c\u300c\u5408\u5e76\u300d\u4e24\u4e2a\u9636\u6bb5\uff1a

  1. \u5212\u5206\u9636\u6bb5\uff1a\u901a\u8fc7\u9012\u5f52\u4e0d\u65ad \u5c06\u6570\u7ec4\u4ece\u4e2d\u70b9\u4f4d\u7f6e\u5212\u5206\u5f00\uff0c\u5c06\u957f\u6570\u7ec4\u7684\u6392\u5e8f\u95ee\u9898\u8f6c\u5316\u4e3a\u77ed\u6570\u7ec4\u7684\u6392\u5e8f\u95ee\u9898\uff1b
  2. \u5408\u5e76\u9636\u6bb5\uff1a\u5212\u5206\u5230\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\uff0c\u5f00\u59cb\u5411\u4e0a\u5408\u5e76\uff0c\u4e0d\u65ad\u5c06 \u5de6\u3001\u53f3\u4e24\u4e2a\u77ed\u6392\u5e8f\u6570\u7ec4 \u5408\u5e76\u4e3a \u4e00\u4e2a\u957f\u6392\u5e8f\u6570\u7ec4\uff0c\u76f4\u81f3\u5408\u5e76\u81f3\u539f\u6570\u7ec4\u65f6\u5b8c\u6210\u6392\u5e8f\uff1b

Fig. \u5f52\u5e76\u6392\u5e8f\u4e24\u9636\u6bb5\uff1a\u5212\u5206\u4e0e\u5408\u5e76

"},{"location":"chapter_sorting/merge_sort/#1151","title":"11.5.1. \u7b97\u6cd5\u6d41\u7a0b","text":"

\u300c\u9012\u5f52\u5212\u5206\u300d \u4ece\u9876\u81f3\u5e95\u9012\u5f52\u5730 \u5c06\u6570\u7ec4\u4ece\u4e2d\u70b9\u5207\u4e3a\u4e24\u4e2a\u5b50\u6570\u7ec4\uff0c\u76f4\u81f3\u957f\u5ea6\u4e3a 1 \uff1b

  1. \u8ba1\u7b97\u6570\u7ec4\u4e2d\u70b9 mid \uff0c\u9012\u5f52\u5212\u5206\u5de6\u5b50\u6570\u7ec4\uff08\u533a\u95f4 [left, mid] \uff09\u548c\u53f3\u5b50\u6570\u7ec4\uff08\u533a\u95f4 [mid + 1, right] \uff09\uff1b
  2. \u9012\u5f52\u6267\u884c 1. \u6b65\u9aa4\uff0c\u76f4\u81f3\u5b50\u6570\u7ec4\u533a\u95f4\u957f\u5ea6\u4e3a 1 \u65f6\uff0c\u7ec8\u6b62\u9012\u5f52\u5212\u5206\uff1b

\u300c\u56de\u6eaf\u5408\u5e76\u300d \u4ece\u5e95\u81f3\u9876\u5730\u5c06\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\u5408\u5e76\u4e3a\u4e00\u4e2a \u6709\u5e8f\u6570\u7ec4 \uff1b

\u9700\u8981\u6ce8\u610f\uff0c\u7531\u4e8e\u4ece\u957f\u5ea6\u4e3a 1 \u7684\u5b50\u6570\u7ec4\u5f00\u59cb\u5408\u5e76\uff0c\u6240\u4ee5 \u6bcf\u4e2a\u5b50\u6570\u7ec4\u90fd\u662f\u6709\u5e8f\u7684\u3002\u56e0\u6b64\uff0c\u5408\u5e76\u4efb\u52a1\u672c\u8d28\u662f\u8981 \u5c06\u4e24\u4e2a\u6709\u5e8f\u5b50\u6570\u7ec4\u5408\u5e76\u4e3a\u4e00\u4e2a\u6709\u5e8f\u6570\u7ec4\u3002

Step1Step2Step3Step4Step5Step6Step7Step8Step9Step10

\u89c2\u5bdf\u53d1\u73b0\uff0c\u5f52\u5e76\u6392\u5e8f\u7684\u9012\u5f52\u987a\u5e8f\u5c31\u662f\u4e8c\u53c9\u6811\u7684\u300c\u540e\u5e8f\u904d\u5386\u300d\u3002

  • \u540e\u5e8f\u904d\u5386\uff1a\u5148\u9012\u5f52\u5de6\u5b50\u6811\u3001\u518d\u9012\u5f52\u53f3\u5b50\u6811\u3001\u6700\u540e\u5904\u7406\u6839\u7ed3\u70b9\u3002
  • \u5f52\u5e76\u6392\u5e8f\uff1a\u5148\u9012\u5f52\u5de6\u5b50\u6811\u3001\u518d\u9012\u5f52\u53f3\u5b50\u6811\u3001\u6700\u540e\u5904\u7406\u5408\u5e76\u3002
JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig merge_sort.java
/* \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4 */\n// \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n// \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\nvoid merge(int[] nums, int left, int mid, int right) {\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4\nint[] tmp = Arrays.copyOfRange(nums, left, right + 1);   // \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15  \nint leftStart = left - left, leftEnd = mid - left;\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15       \nint rightStart = mid + 1 - left, rightEnd = right - left;\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\nint i = leftStart, j = rightStart;                // \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor (int k = left; k <= right; k++) {\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif (i > leftEnd)\nnums[k] = tmp[j++];\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\nelse if (j > rightEnd || tmp[i] <= tmp[j])\nnums[k] = tmp[i++];\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nelse\nnums[k] = tmp[j++];\n}\n}\n/* \u5f52\u5e76\u6392\u5e8f */\nvoid mergeSort(int[] nums, int left, int right) {\n// \u7ec8\u6b62\u6761\u4ef6\nif (left >= right) return;       // \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\n// \u5212\u5206\u9636\u6bb5\nint mid = (left + right) / 2;    // \u8ba1\u7b97\u4e2d\u70b9\nmergeSort(nums, left, mid);      // \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmergeSort(nums, mid + 1, right); // \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right);\n}\n
merge_sort.cpp
/* \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4 */\n// \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n// \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\nvoid merge(vector<int>& nums, int left, int mid, int right) {\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4\nvector<int> tmp(nums.begin() + left, nums.begin() + right + 1);   // \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15  \nint leftStart = left - left, leftEnd = mid - left;\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15       \nint rightStart = mid + 1 - left, rightEnd = right - left;\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\nint i = leftStart, j = rightStart;                // \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor (int k = left; k <= right; k++) {\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif (i > leftEnd)\nnums[k] = tmp[j++];\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\nelse if (j > rightEnd || tmp[i] <= tmp[j])\nnums[k] = tmp[i++];\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nelse\nnums[k] = tmp[j++];\n}\n}\n/* \u5f52\u5e76\u6392\u5e8f */\nvoid mergeSort(vector<int>& nums, int left, int right) {\n// \u7ec8\u6b62\u6761\u4ef6\nif (left >= right) return;       // \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\n// \u5212\u5206\u9636\u6bb5\nint mid = (left + right) / 2;    // \u8ba1\u7b97\u4e2d\u70b9\nmergeSort(nums, left, mid);      // \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmergeSort(nums, mid + 1, right); // \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right);\n}\n
merge_sort.py
\"\"\" \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4 \"\"\"\n# \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n# \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\ndef merge(nums, left, mid, right):\n# \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4 \u501f\u52a9 copy\u6a21\u5757\ntmp = nums[left:right + 1]\n# \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nleft_start, left_end = left - left, mid - left\n# \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nright_start, right_end = mid + 1 - left, right - left\n# i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\ni, j = left_start, right_start\n# \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor k in range(left, right + 1):\n# \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif i > left_end:\nnums[k] = tmp[j]\nj += 1\n# \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\nelif j > right_end or tmp[i] <= tmp[j]:\nnums[k] = tmp[i]\ni += 1\n# \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nelse:\nnums[k] = tmp[j]\nj += 1\n\"\"\" \u5f52\u5e76\u6392\u5e8f \"\"\"\ndef merge_sort(nums, left, right):\n# \u7ec8\u6b62\u6761\u4ef6\nif left >= right:\nreturn                        # \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\n# \u5212\u5206\u9636\u6bb5\nmid = (left + right) // 2         # \u8ba1\u7b97\u4e2d\u70b9\nmerge_sort(nums, left, mid)       # \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmerge_sort(nums, mid + 1, right)  # \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n# \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right)\n
merge_sort.go
/*\n    \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\n    \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n    \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\n*/\nfunc merge(nums []int, left, mid, right int) {\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4 \u501f\u52a9 copy \u6a21\u5757\ntmp := make([]int, right-left+1)\nfor i := left; i <= right; i++ {\ntmp[i-left] = nums[i]\n}\n// \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nleftStart, leftEnd := left-left, mid-left\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nrightStart, rightEnd := mid+1-left, right-left\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\ni, j := leftStart, rightStart\n// \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor k := left; k <= right; k++ {\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif i > leftEnd {\nnums[k] = tmp[j]\nj++\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\n} else if j > rightEnd || tmp[i] <= tmp[j] {\nnums[k] = tmp[i]\ni++\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\n} else {\nnums[k] = tmp[j]\nj++\n}\n}\n}\nfunc mergeSort(nums []int, left, right int) {\n// \u7ec8\u6b62\u6761\u4ef6\nif left >= right {\nreturn\n}\n// \u5212\u5206\u9636\u6bb5\nmid := (left + right) / 2\nmergeSort(nums, left, mid)\nmergeSort(nums, mid+1, right)\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right)\n}\n
merge_sort.js
/* \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4 */\n// \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n// \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\nfunction merge(nums, left, mid, right) {\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4\nlet tmp = nums.slice(left, right + 1);   // \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15  \nlet leftStart = left - left, leftEnd = mid - left;\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15       \nlet rightStart = mid + 1 - left, rightEnd = right - left;\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\nlet i = leftStart, j = rightStart;                // \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor (let k = left; k <= right; k++) {\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif (i > leftEnd) {\nnums[k] = tmp[j++];\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\n} else if (j > rightEnd || tmp[i] <= tmp[j]) {\nnums[k] = tmp[i++];\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\n} else {\nnums[k] = tmp[j++];\n}\n}\n}\n/* \u5f52\u5e76\u6392\u5e8f */\nfunction mergeSort(nums, left, right) {\n// \u7ec8\u6b62\u6761\u4ef6\nif (left >= right) return;       // \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\n// \u5212\u5206\u9636\u6bb5\nlet mid = Math.floor((left + right) / 2);    // \u8ba1\u7b97\u4e2d\u70b9\nmergeSort(nums, left, mid);      // \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmergeSort(nums, mid + 1, right); // \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right);\n}\n
merge_sort.ts
/* \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4 */\n// \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n// \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\nfunction merge(nums: number[], left: number, mid: number, right: number): void {\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4\nlet tmp = nums.slice(left, right + 1);\n// \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nlet leftStart = left - left, leftEnd = mid - left;\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nlet rightStart = mid + 1 - left, rightEnd = right - left;\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\nlet i = leftStart, j = rightStart;\n// \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor (let k = left; k <= right; k++) {\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif (i > leftEnd) {\nnums[k] = tmp[j++];\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\n} else if (j > rightEnd || tmp[i] <= tmp[j]) {\nnums[k] = tmp[i++];\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\n} else {\nnums[k] = tmp[j++];\n}\n}\n}\n/* \u5f52\u5e76\u6392\u5e8f */\nfunction mergeSort(nums: number[], left: number, right: number): void {\n// \u7ec8\u6b62\u6761\u4ef6\nif (left >= right) return; // \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\n// \u5212\u5206\u9636\u6bb5\nlet mid = Math.floor((left + right) / 2); // \u8ba1\u7b97\u4e2d\u70b9\nmergeSort(nums, left, mid); // \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmergeSort(nums, mid + 1, right); // \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right);\n}\n
merge_sort.c
\n
merge_sort.cs
/* \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4 */\n// \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n// \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\nvoid merge(int[] nums, int left, int mid, int right)\n{\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4\nint[] tmp = nums[left..(right + 1)];\n// \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15  \nint leftStart = left - left, leftEnd = mid - left;\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15       \nint rightStart = mid + 1 - left, rightEnd = right - left;\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\nint i = leftStart, j = rightStart;\n// \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor (int k = left; k <= right; k++)\n{\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif (i > leftEnd)\nnums[k] = tmp[j++];\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\nelse if (j > rightEnd || tmp[i] <= tmp[j])\nnums[k] = tmp[i++];\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nelse\nnums[k] = tmp[j++];\n}\n}\n/* \u5f52\u5e76\u6392\u5e8f */\nvoid mergeSort(int[] nums, int left, int right)\n{\n// \u7ec8\u6b62\u6761\u4ef6\nif (left >= right) return;       // \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\n// \u5212\u5206\u9636\u6bb5\nint mid = (left + right) / 2;    // \u8ba1\u7b97\u4e2d\u70b9\nmergeSort(nums, left, mid);      // \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmergeSort(nums, mid + 1, right); // \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums, left, mid, right);\n}\n
merge_sort.swift
/**\n* \u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\n* \u5de6\u5b50\u6570\u7ec4\u533a\u95f4 [left, mid]\n* \u53f3\u5b50\u6570\u7ec4\u533a\u95f4 [mid + 1, right]\n*/\nfunc merge(nums: inout [Int], left: Int, mid: Int, right: Int) {\n// \u521d\u59cb\u5316\u8f85\u52a9\u6570\u7ec4\nlet tmp = Array(nums[left ..< (right + 1)])\n// \u5de6\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nlet leftStart = left - left\nlet leftEnd = mid - left\n// \u53f3\u5b50\u6570\u7ec4\u7684\u8d77\u59cb\u7d22\u5f15\u548c\u7ed3\u675f\u7d22\u5f15\nlet rightStart = mid + 1 - left\nlet rightEnd = right - left\n// i, j \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u7684\u9996\u5143\u7d20\nvar i = leftStart\nvar j = rightStart\n// \u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u6765\u5408\u5e76\u5de6\u5b50\u6570\u7ec4\u548c\u53f3\u5b50\u6570\u7ec4\nfor k in left ... right {\n// \u82e5\u201c\u5de6\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nif i > leftEnd {\nnums[k] = tmp[j]\nj += 1\n}\n// \u5426\u5219\uff0c\u82e5\u201c\u53f3\u5b50\u6570\u7ec4\u5df2\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u6216\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 <= \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u5de6\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 i++\nelse if j > rightEnd || tmp[i] <= tmp[j] {\nnums[k] = tmp[i]\ni += 1\n}\n// \u5426\u5219\uff0c\u82e5\u201c\u5de6\u53f3\u5b50\u6570\u7ec4\u90fd\u672a\u5168\u90e8\u5408\u5e76\u5b8c\u201d\u4e14\u201c\u5de6\u5b50\u6570\u7ec4\u5143\u7d20 > \u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u201d\uff0c\u5219\u9009\u53d6\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\uff0c\u5e76\u4e14 j++\nelse {\nnums[k] = tmp[j]\nj += 1\n}\n}\n}\n/* \u5f52\u5e76\u6392\u5e8f */\nfunc mergeSort(nums: inout [Int], left: Int, right: Int) {\n// \u7ec8\u6b62\u6761\u4ef6\nif left >= right { // \u5f53\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nreturn\n}\n// \u5212\u5206\u9636\u6bb5\nlet mid = (left + right) / 2 // \u8ba1\u7b97\u4e2d\u70b9\nmergeSort(nums: &nums, left: left, right: mid) // \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\nmergeSort(nums: &nums, left: mid + 1, right: right) // \u9012\u5f52\u53f3\u5b50\u6570\u7ec4\n// \u5408\u5e76\u9636\u6bb5\nmerge(nums: &nums, left: left, mid: mid, right: right)\n}\n
merge_sort.zig
\n

\u4e0b\u9762\u91cd\u70b9\u89e3\u91ca\u4e00\u4e0b\u5408\u5e76\u65b9\u6cd5 merge() \u7684\u6d41\u7a0b\uff1a

  1. \u521d\u59cb\u5316\u4e00\u4e2a\u8f85\u52a9\u6570\u7ec4 tmp \u6682\u5b58\u5f85\u5408\u5e76\u533a\u95f4 [left, right] \u5185\u7684\u5143\u7d20\uff0c\u540e\u7eed\u901a\u8fc7\u8986\u76d6\u539f\u6570\u7ec4 nums \u7684\u5143\u7d20\u6765\u5b9e\u73b0\u5408\u5e76\uff1b
  2. \u521d\u59cb\u5316\u6307\u9488 i , j , k \u5206\u522b\u6307\u5411\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\u3001\u539f\u6570\u7ec4\u7684\u9996\u5143\u7d20\uff1b
  3. \u5faa\u73af\u5224\u65ad tmp[i] \u548c tmp[j] \u7684\u5927\u5c0f\uff0c\u5c06\u8f83\u5c0f\u7684\u5148\u8986\u76d6\u81f3 nums[k] \uff0c\u6307\u9488 i , j \u6839\u636e\u5224\u65ad\u7ed3\u679c\u4ea4\u66ff\u524d\u8fdb\uff08\u6307\u9488 k \u4e5f\u524d\u8fdb\uff09\uff0c\u76f4\u81f3\u4e24\u4e2a\u5b50\u6570\u7ec4\u90fd\u904d\u5386\u5b8c\uff0c\u5373\u53ef\u5b8c\u6210\u5408\u5e76\u3002

\u5408\u5e76\u65b9\u6cd5 merge() \u4ee3\u7801\u4e2d\u7684\u4e3b\u8981\u96be\u70b9\uff1a

  • nums \u7684\u5f85\u5408\u5e76\u533a\u95f4\u4e3a [left, right] \uff0c\u800c\u56e0\u4e3a tmp \u53ea\u590d\u5236\u4e86 nums \u8be5\u533a\u95f4\u5143\u7d20\uff0c\u6240\u4ee5 tmp \u5bf9\u5e94\u533a\u95f4\u4e3a [0, right - left] \uff0c\u9700\u8981\u7279\u522b\u6ce8\u610f\u4ee3\u7801\u4e2d\u5404\u4e2a\u53d8\u91cf\u7684\u542b\u4e49\u3002
  • \u5224\u65ad tmp[i] \u548c tmp[j] \u7684\u5927\u5c0f\u7684\u64cd\u4f5c\u4e2d\uff0c\u8fd8 \u9700\u8003\u8651\u5f53\u5b50\u6570\u7ec4\u904d\u5386\u5b8c\u6210\u540e\u7684\u7d22\u5f15\u8d8a\u754c\u95ee\u9898\uff0c\u5373 i > leftEnd \u548c j > rightEnd \u7684\u60c5\u51b5\uff0c\u7d22\u5f15\u8d8a\u754c\u7684\u4f18\u5148\u7ea7\u662f\u6700\u9ad8\u7684\uff0c\u4f8b\u5982\u5982\u679c\u5de6\u5b50\u6570\u7ec4\u5df2\u7ecf\u88ab\u5408\u5e76\u5b8c\u4e86\uff0c\u90a3\u4e48\u4e0d\u7528\u7ee7\u7eed\u5224\u65ad\uff0c\u76f4\u63a5\u5408\u5e76\u53f3\u5b50\u6570\u7ec4\u5143\u7d20\u5373\u53ef\u3002
"},{"location":"chapter_sorting/merge_sort/#1152","title":"11.5.2. \u7b97\u6cd5\u7279\u6027","text":"
  • \u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n \\log n)\\) \uff1a\u5212\u5206\u5f62\u6210\u9ad8\u5ea6\u4e3a \\(\\log n\\) \u7684\u9012\u5f52\u6811\uff0c\u6bcf\u5c42\u5408\u5e76\u7684\u603b\u64cd\u4f5c\u6570\u91cf\u4e3a \\(n\\) \uff0c\u603b\u4f53\u4f7f\u7528 \\(O(n \\log n)\\) \u65f6\u95f4\u3002
  • \u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \uff1a\u9700\u501f\u52a9\u8f85\u52a9\u6570\u7ec4\u5b9e\u73b0\u5408\u5e76\uff0c\u4f7f\u7528 \\(O(n)\\) \u5927\u5c0f\u7684\u989d\u5916\u7a7a\u95f4\uff1b\u9012\u5f52\u6df1\u5ea6\u4e3a \\(\\log n\\) \uff0c\u4f7f\u7528 \\(O(\\log n)\\) \u5927\u5c0f\u7684\u6808\u5e27\u7a7a\u95f4\u3002
  • \u975e\u539f\u5730\u6392\u5e8f\uff1a\u8f85\u52a9\u6570\u7ec4\u9700\u8981\u4f7f\u7528 \\(O(n)\\) \u989d\u5916\u7a7a\u95f4\u3002
  • \u7a33\u5b9a\u6392\u5e8f\uff1a\u5728\u5408\u5e76\u65f6\u53ef\u4fdd\u8bc1\u76f8\u7b49\u5143\u7d20\u7684\u76f8\u5bf9\u4f4d\u7f6e\u4e0d\u53d8\u3002
  • \u975e\u81ea\u9002\u5e94\u6392\u5e8f\uff1a\u5bf9\u4e8e\u4efb\u610f\u8f93\u5165\u6570\u636e\uff0c\u5f52\u5e76\u6392\u5e8f\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u7686\u76f8\u540c\u3002
"},{"location":"chapter_sorting/merge_sort/#1153","title":"11.5.3. \u94fe\u8868\u6392\u5e8f *","text":"

\u5f52\u5e76\u6392\u5e8f\u6709\u4e00\u4e2a\u5f88\u7279\u522b\u7684\u4f18\u52bf\uff0c\u7528\u4e8e\u6392\u5e8f\u94fe\u8868\u65f6\u6709\u5f88\u597d\u7684\u6027\u80fd\u8868\u73b0\uff0c\u7a7a\u95f4\u590d\u6742\u5ea6\u53ef\u88ab\u4f18\u5316\u81f3 \\(O(1)\\) \uff0c\u8fd9\u662f\u56e0\u4e3a\uff1a

  • \u7531\u4e8e\u94fe\u8868\u53ef\u4ec5\u901a\u8fc7\u6539\u53d8\u6307\u9488\u6765\u5b9e\u73b0\u7ed3\u70b9\u589e\u5220\uff0c\u56e0\u6b64\u201c\u5c06\u4e24\u4e2a\u77ed\u6709\u5e8f\u94fe\u8868\u5408\u5e76\u4e3a\u4e00\u4e2a\u957f\u6709\u5e8f\u94fe\u8868\u201d\u65e0\u9700\u4f7f\u7528\u989d\u5916\u7a7a\u95f4\uff0c\u5373\u56de\u6eaf\u5408\u5e76\u9636\u6bb5\u4e0d\u7528\u50cf\u6392\u5e8f\u6570\u7ec4\u4e00\u6837\u5efa\u7acb\u8f85\u52a9\u6570\u7ec4 tmp \uff1b
  • \u901a\u8fc7\u4f7f\u7528\u300c\u8fed\u4ee3\u300d\u4ee3\u66ff\u300c\u9012\u5f52\u5212\u5206\u300d\uff0c\u53ef\u7701\u53bb\u9012\u5f52\u4f7f\u7528\u7684\u6808\u5e27\u7a7a\u95f4\uff1b

\u8be6\u60c5\u53c2\u8003\uff1a148. \u6392\u5e8f\u94fe\u8868

"},{"location":"chapter_sorting/quick_sort/","title":"11.4. \u5feb\u901f\u6392\u5e8f","text":"

\u300c\u5feb\u901f\u6392\u5e8f Quick Sort\u300d\u662f\u4e00\u79cd\u57fa\u4e8e\u201c\u5206\u6cbb\u601d\u60f3\u201d\u7684\u6392\u5e8f\u7b97\u6cd5\uff0c\u901f\u5ea6\u5f88\u5feb\u3001\u5e94\u7528\u5f88\u5e7f\u3002

\u5feb\u901f\u6392\u5e8f\u7684\u6838\u5fc3\u64cd\u4f5c\u4e3a\u300c\u54e8\u5175\u5212\u5206\u300d\uff0c\u5176\u76ee\u6807\u4e3a\uff1a\u9009\u53d6\u6570\u7ec4\u67d0\u4e2a\u5143\u7d20\u4e3a \u57fa\u51c6\u6570\uff0c\u5c06\u6240\u6709\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\u79fb\u52a8\u81f3\u5176\u5de6\u8fb9\uff0c\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\u79fb\u52a8\u81f3\u5176\u53f3\u8fb9\u3002\u300c\u54e8\u5175\u5212\u5206\u300d\u7684\u5b9e\u73b0\u6d41\u7a0b\u4e3a\uff1a

  1. \u4ee5\u6570\u7ec4\u6700\u5de6\u7aef\u5143\u7d20\u4f5c\u4e3a\u57fa\u51c6\u6570\uff0c\u521d\u59cb\u5316\u4e24\u4e2a\u6307\u9488 i , j \u6307\u5411\u6570\u7ec4\u4e24\u7aef\uff1b
  2. \u8bbe\u7f6e\u4e00\u4e2a\u5faa\u73af\uff0c\u6bcf\u8f6e\u4e2d\u4f7f\u7528 i / j \u5206\u522b\u5bfb\u627e\u9996\u4e2a\u6bd4\u57fa\u51c6\u6570\u5927 / \u5c0f\u7684\u5143\u7d20\uff0c\u5e76\u4ea4\u6362\u6b64\u4e24\u5143\u7d20\uff1b
  3. \u4e0d\u65ad\u5faa\u73af\u6b65\u9aa4 2. \uff0c\u76f4\u81f3 i , j \u76f8\u9047\u65f6\u8df3\u51fa\uff0c\u6700\u7ec8\u628a\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u4e2a\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\uff1b

\u300c\u54e8\u5175\u5212\u5206\u300d\u6267\u884c\u5b8c\u6bd5\u540e\uff0c\u539f\u6570\u7ec4\u88ab\u5212\u5206\u6210\u4e24\u4e2a\u90e8\u5206\uff0c\u5373 \u5de6\u5b50\u6570\u7ec4 \u548c \u53f3\u5b50\u6570\u7ec4\uff0c\u4e14\u6ee1\u8db3 \u5de6\u5b50\u6570\u7ec4\u4efb\u610f\u5143\u7d20 < \u57fa\u51c6\u6570 < \u53f3\u5b50\u6570\u7ec4\u4efb\u610f\u5143\u7d20\u3002\u56e0\u6b64\uff0c\u63a5\u4e0b\u6765\u6211\u4eec\u53ea\u9700\u8981\u6392\u5e8f\u4e24\u4e2a\u5b50\u6570\u7ec4\u5373\u53ef\u3002

Step 1Step 2Step 3Step 4Step 5Step 6Step 7Step 8Step 9

Fig. \u54e8\u5175\u5212\u5206

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig quick_sort.java
/* \u5143\u7d20\u4ea4\u6362 */\nvoid swap(int[] nums, int i, int j) {\nint tmp = nums[i];\nnums[i] = nums[j];\nnums[j] = tmp;\n}\n/* \u54e8\u5175\u5212\u5206 */\nint partition(int[] nums, int left, int right) {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nint i = left, j = right;\nwhile (i < j) {\nwhile (i < j && nums[j] >= nums[left])\nj--;          // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nwhile (i < j && nums[i] <= nums[left])\ni++;          // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nswap(nums, i, j); // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums, i, left);  // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i;             // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.cpp
/* \u5143\u7d20\u4ea4\u6362 */\nvoid swap(vector<int>& nums, int i, int j) {\nint tmp = nums[i];\nnums[i] = nums[j];\nnums[j] = tmp;\n}\n/* \u54e8\u5175\u5212\u5206 */\nint partition(vector<int>& nums, int left, int right) {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nint i = left, j = right;\nwhile (i < j) {\nwhile (i < j && nums[j] >= nums[left])\nj--;          // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nwhile (i < j && nums[i] <= nums[left])\ni++;          // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nswap(nums, i, j); // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums, i, left);  // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i;             // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.py
\"\"\" \u54e8\u5175\u5212\u5206 \"\"\"\ndef partition(self, nums, left, right):\n# \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\ni, j = left, right\nwhile i < j:\nwhile i < j and nums[j] >= nums[left]:\nj -= 1  # \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nwhile i < j and nums[i] <= nums[left]:\ni += 1  # \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n# \u5143\u7d20\u4ea4\u6362\nnums[i], nums[j] = nums[j], nums[i]\n# \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nnums[i], nums[left] = nums[left], nums[i]\nreturn i  # \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n
quick_sort.go
/* \u54e8\u5175\u5212\u5206 */\nfunc partition(nums []int, left, right int) int {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\ni, j := left, right\nfor i < j {\nfor i < j && nums[j] >= nums[left] {\nj-- // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\nfor i < j && nums[i] <= nums[left] {\ni++ // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\n//\u5143\u7d20\u4ea4\u6362\nnums[i], nums[j] = nums[j], nums[i]\n}\n// \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nnums[i], nums[left] = nums[left], nums[i]\nreturn i // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.js
/* \u5143\u7d20\u4ea4\u6362 */\nfunction swap(nums, i, j) {\nlet tmp = nums[i];\nnums[i] = nums[j];\nnums[j] = tmp;\n}\n/* \u54e8\u5175\u5212\u5206 */\nfunction partition(nums, left, right) {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nlet i = left, j = right;\nwhile (i < j) {\nwhile (i < j && nums[j] >= nums[left]) {\nj -= 1; // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\nwhile (i < j && nums[i] <= nums[left]) {\ni += 1; // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\n// \u5143\u7d20\u4ea4\u6362\nswap(nums, i, j); // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums, i, left); // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i; // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.ts
/* \u5143\u7d20\u4ea4\u6362 */\nfunction swap(nums: number[], i: number, j: number): void {\nlet tmp = nums[i];\nnums[i] = nums[j];\nnums[j] = tmp;\n}\n/* \u54e8\u5175\u5212\u5206 */\nfunction partition(nums: number[], left: number, right: number): number {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nlet i = left, j = right;\nwhile (i < j) {\nwhile (i < j && nums[j] >= nums[left]) {\nj -= 1; // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\nwhile (i < j && nums[i] <= nums[left]) {\ni += 1; // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\n// \u5143\u7d20\u4ea4\u6362\nswap(nums, i, j); // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums, i, left); // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i; // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.c
\n
quick_sort.cs
/* \u5143\u7d20\u4ea4\u6362 */\nvoid swap(int[] nums, int i, int j)\n{\nint tmp = nums[i];\nnums[i] = nums[j];\nnums[j] = tmp;\n}\n/* \u54e8\u5175\u5212\u5206 */\nint partition(int[] nums, int left, int right)\n{\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nint i = left, j = right;\nwhile (i < j)\n{\nwhile (i < j && nums[j] >= nums[left])\nj--;          // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nwhile (i < j && nums[i] <= nums[left])\ni++;          // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nswap(nums, i, j); // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums, i, left);  // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i;             // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.swift
/* \u5143\u7d20\u4ea4\u6362 */\nfunc swap(nums: inout [Int], i: Int, j: Int) {\nlet tmp = nums[i]\nnums[i] = nums[j]\nnums[j] = tmp\n}\n/* \u54e8\u5175\u5212\u5206 */\nfunc partition(nums: inout [Int], left: Int, right: Int) -> Int {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nvar i = left\nvar j = right\nwhile i < j {\nwhile i < j, nums[j] >= nums[left] {\nj -= 1 // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\nwhile i < j, nums[i] <= nums[left] {\ni += 1 // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n}\nswap(nums: &nums, i: i, j: j) // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums: &nums, i: i, j: left) // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.zig
\n

\u5feb\u901f\u6392\u5e8f\u7684\u5206\u6cbb\u601d\u60f3

\u54e8\u5175\u5212\u5206\u7684\u5b9e\u8d28\u662f\u5c06 \u4e00\u4e2a\u957f\u6570\u7ec4\u7684\u6392\u5e8f\u95ee\u9898 \u7b80\u5316\u4e3a \u4e24\u4e2a\u77ed\u6570\u7ec4\u7684\u6392\u5e8f\u95ee\u9898\u3002

"},{"location":"chapter_sorting/quick_sort/#1141","title":"11.4.1. \u7b97\u6cd5\u6d41\u7a0b","text":"
  1. \u9996\u5148\uff0c\u5bf9\u6570\u7ec4\u6267\u884c\u4e00\u6b21\u300c\u54e8\u5175\u5212\u5206\u300d\uff0c\u5f97\u5230\u5f85\u6392\u5e8f\u7684 \u5de6\u5b50\u6570\u7ec4 \u548c \u53f3\u5b50\u6570\u7ec4\uff1b
  2. \u63a5\u4e0b\u6765\uff0c\u5bf9 \u5de6\u5b50\u6570\u7ec4 \u548c \u53f3\u5b50\u6570\u7ec4 \u5206\u522b \u9012\u5f52\u6267\u884c\u300c\u54e8\u5175\u5212\u5206\u300d\u2026\u2026
  3. \u76f4\u81f3\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6 \u7ec8\u6b62\u9012\u5f52\uff0c\u5373\u53ef\u5b8c\u6210\u5bf9\u6574\u4e2a\u6570\u7ec4\u7684\u6392\u5e8f\uff1b

\u89c2\u5bdf\u53d1\u73b0\uff0c\u5feb\u901f\u6392\u5e8f\u548c\u300c\u4e8c\u5206\u67e5\u627e\u300d\u7684\u539f\u7406\u7c7b\u4f3c\uff0c\u90fd\u662f\u4ee5\u5bf9\u6570\u9636\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u6765\u7f29\u5c0f\u5904\u7406\u533a\u95f4\u3002

Fig. \u5feb\u901f\u6392\u5e8f\u6d41\u7a0b

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig quick_sort.java
/* \u5feb\u901f\u6392\u5e8f */\nvoid quickSort(int[] nums, int left, int right) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif (left >= right)\nreturn;\n// \u54e8\u5175\u5212\u5206\nint pivot = partition(nums, left, right);\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums, left, pivot - 1);\nquickSort(nums, pivot + 1, right);\n}\n
quick_sort.cpp
/* \u5feb\u901f\u6392\u5e8f */\nvoid quickSort(vector<int>& nums, int left, int right) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif (left >= right)\nreturn;\n// \u54e8\u5175\u5212\u5206\nint pivot = partition(nums, left, right);\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums, left, pivot - 1);\nquickSort(nums, pivot + 1, right);\n}\n
quick_sort.py
\"\"\" \u5feb\u901f\u6392\u5e8f \"\"\"\ndef quick_sort(self, nums, left, right):\n# \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif left >= right:\nreturn\n# \u54e8\u5175\u5212\u5206\npivot = self.partition(nums, left, right)\n# \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nself.quick_sort(nums, left, pivot - 1)\nself.quick_sort(nums, pivot + 1, right)\n
quick_sort.go
/* \u5feb\u901f\u6392\u5e8f */\nfunc quickSort(nums []int, left, right int) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif left >= right {\nreturn\n}\n// \u54e8\u5175\u5212\u5206\npivot := partition(nums, left, right)\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums, left, pivot-1)\nquickSort(nums, pivot+1, right)\n}\n
quick_sort.js
/* \u5feb\u901f\u6392\u5e8f */\nfunction quickSort(nums, left, right) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif (left >= right) return;\n// \u54e8\u5175\u5212\u5206\nconst pivot = partition(nums, left, right);\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums, left, pivot - 1);\nquickSort(nums, pivot + 1, right);\n}\n
quick_sort.ts
/* \u5feb\u901f\u6392\u5e8f */\nfunction quickSort(nums: number[], left: number, right: number): void {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif (left >= right) {\nreturn;\n}\n// \u54e8\u5175\u5212\u5206\nconst pivot = partition(nums, left, right);\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums, left, pivot - 1);\nquickSort(nums, pivot + 1, right);\n}\n
quick_sort.c
\n
quick_sort.cs
/* \u5feb\u901f\u6392\u5e8f */\nvoid quickSort(int[] nums, int left, int right)\n{\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif (left >= right)\nreturn;\n// \u54e8\u5175\u5212\u5206\nint pivot = partition(nums, left, right);\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums, left, pivot - 1);\nquickSort(nums, pivot + 1, right);\n}\n
quick_sort.swift
/* \u5feb\u901f\u6392\u5e8f */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\u9012\u5f52\nif left >= right {\nreturn\n}\n// \u54e8\u5175\u5212\u5206\nlet pivot = partition(nums: &nums, left: left, right: right)\n// \u9012\u5f52\u5de6\u5b50\u6570\u7ec4\u3001\u53f3\u5b50\u6570\u7ec4\nquickSort(nums: &nums, left: left, right: pivot - 1)\nquickSort(nums: &nums, left: pivot + 1, right: right)\n}\n
quick_sort.zig
\n
"},{"location":"chapter_sorting/quick_sort/#1142","title":"11.4.2. \u7b97\u6cd5\u7279\u6027","text":"

\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n \\log n)\\) \uff1a\u5e73\u5747\u60c5\u51b5\u4e0b\uff0c\u54e8\u5175\u5212\u5206\u7684\u9012\u5f52\u5c42\u6570\u4e3a \\(\\log n\\) \uff0c\u6bcf\u5c42\u4e2d\u7684\u603b\u5faa\u73af\u6570\u4e3a \\(n\\) \uff0c\u603b\u4f53\u4f7f\u7528 \\(O(n \\log n)\\) \u65f6\u95f4\u3002

\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6 \\(O(n^2)\\) \uff1a\u6700\u5dee\u60c5\u51b5\u4e0b\uff0c\u54e8\u5175\u5212\u5206\u64cd\u4f5c\u5c06\u957f\u5ea6\u4e3a \\(n\\) \u7684\u6570\u7ec4\u5212\u5206\u4e3a\u957f\u5ea6\u4e3a \\(0\\) \u548c \\(n - 1\\) \u7684\u4e24\u4e2a\u5b50\u6570\u7ec4\uff0c\u6b64\u65f6\u9012\u5f52\u5c42\u6570\u8fbe\u5230 \\(n\\) \u5c42\uff0c\u6bcf\u5c42\u4e2d\u7684\u5faa\u73af\u6570\u4e3a \\(n\\) \uff0c\u603b\u4f53\u4f7f\u7528 \\(O(n^2)\\) \u65f6\u95f4\u3002

\u7a7a\u95f4\u590d\u6742\u5ea6 \\(O(n)\\) \uff1a\u8f93\u5165\u6570\u7ec4\u5b8c\u5168\u5012\u5e8f\u4e0b\uff0c\u8fbe\u5230\u6700\u5dee\u9012\u5f52\u6df1\u5ea6 \\(n\\) \u3002

\u539f\u5730\u6392\u5e8f\uff1a\u53ea\u5728\u9012\u5f52\u4e2d\u4f7f\u7528 \\(O(\\log n)\\) \u5927\u5c0f\u7684\u6808\u5e27\u7a7a\u95f4\u3002

\u975e\u7a33\u5b9a\u6392\u5e8f\uff1a\u54e8\u5175\u5212\u5206\u64cd\u4f5c\u53ef\u80fd\u6539\u53d8\u76f8\u7b49\u5143\u7d20\u7684\u76f8\u5bf9\u4f4d\u7f6e\u3002

\u81ea\u9002\u5e94\u6392\u5e8f\uff1a\u6700\u5dee\u60c5\u51b5\u4e0b\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u52a3\u5316\u81f3 \\(O(n^2)\\) \u3002

"},{"location":"chapter_sorting/quick_sort/#1143","title":"11.4.3. \u5feb\u6392\u4e3a\u4ec0\u4e48\u5feb\uff1f","text":"

\u4ece\u547d\u540d\u80fd\u591f\u770b\u51fa\uff0c\u5feb\u901f\u6392\u5e8f\u5728\u6548\u7387\u65b9\u9762\u4e00\u5b9a\u201c\u6709\u4e24\u628a\u5237\u5b50\u201d\u3002\u5feb\u901f\u6392\u5e8f\u7684\u5e73\u5747\u65f6\u95f4\u590d\u6742\u5ea6\u867d\u7136\u4e0e\u300c\u5f52\u5e76\u6392\u5e8f\u300d\u548c\u300c\u5806\u6392\u5e8f\u300d\u4e00\u81f4\uff0c\u4f46\u5b9e\u9645 \u6548\u7387\u66f4\u9ad8\uff0c\u8fd9\u662f\u56e0\u4e3a\uff1a

  • \u51fa\u73b0\u6700\u5dee\u60c5\u51b5\u7684\u6982\u7387\u5f88\u4f4e\uff1a\u867d\u7136\u5feb\u901f\u6392\u5e8f\u7684\u6700\u5dee\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n^2)\\) \uff0c\u4e0d\u5982\u5f52\u5e76\u6392\u5e8f\uff0c\u4f46\u7edd\u5927\u90e8\u5206\u60c5\u51b5\u4e0b\uff0c\u5feb\u901f\u6392\u5e8f\u53ef\u4ee5\u8fbe\u5230 \\(O(n \\log n)\\) \u7684\u590d\u6742\u5ea6\u3002
  • \u7f13\u5b58\u4f7f\u7528\u6548\u7387\u9ad8\uff1a\u54e8\u5175\u5212\u5206\u64cd\u4f5c\u65f6\uff0c\u5c06\u6574\u4e2a\u5b50\u6570\u7ec4\u52a0\u8f7d\u5165\u7f13\u5b58\u4e2d\uff0c\u8bbf\u95ee\u5143\u7d20\u6548\u7387\u5f88\u9ad8\u3002\u800c\u8bf8\u5982\u300c\u5806\u6392\u5e8f\u300d\u9700\u8981\u8df3\u8dc3\u5f0f\u8bbf\u95ee\u5143\u7d20\uff0c\u56e0\u6b64\u4e0d\u5177\u6709\u6b64\u7279\u6027\u3002
  • \u590d\u6742\u5ea6\u7684\u5e38\u6570\u7cfb\u6570\u4f4e\uff1a\u5728\u63d0\u53ca\u7684\u4e09\u79cd\u7b97\u6cd5\u4e2d\uff0c\u5feb\u901f\u6392\u5e8f\u7684 \u6bd4\u8f83\u3001\u8d4b\u503c\u3001\u4ea4\u6362 \u4e09\u79cd\u64cd\u4f5c\u7684\u603b\u4f53\u6570\u91cf\u6700\u5c11\uff08\u7c7b\u4f3c\u4e8e\u300c\u63d2\u5165\u6392\u5e8f\u300d\u5feb\u4e8e\u300c\u5192\u6ce1\u6392\u5e8f\u300d\u7684\u539f\u56e0\uff09\u3002
"},{"location":"chapter_sorting/quick_sort/#1144","title":"11.4.4. \u57fa\u51c6\u6570\u4f18\u5316","text":"

\u666e\u901a\u5feb\u901f\u6392\u5e8f\u5728\u67d0\u4e9b\u8f93\u5165\u4e0b\u7684\u65f6\u95f4\u6548\u7387\u53d8\u5dee\u3002\u4e3e\u4e2a\u6781\u7aef\u4f8b\u5b50\uff0c\u5047\u8bbe\u8f93\u5165\u6570\u7ec4\u662f\u5b8c\u5168\u5012\u5e8f\u7684\uff0c\u7531\u4e8e\u6211\u4eec\u9009\u53d6\u6700\u5de6\u7aef\u5143\u7d20\u4e3a\u57fa\u51c6\u6570\uff0c\u90a3\u4e48\u5728\u54e8\u5175\u5212\u5206\u5b8c\u6210\u540e\uff0c\u57fa\u51c6\u6570\u88ab\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u53f3\u7aef\uff0c\u4ece\u800c \u5de6\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a \\(n - 1\\)\u3001\u53f3\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a \\(0\\) \u3002\u8fd9\u6837\u8fdb\u4e00\u6b65\u9012\u5f52\u4e0b\u53bb\uff0c\u6bcf\u8f6e\u54e8\u5175\u5212\u5206\u540e\u7684\u53f3\u5b50\u6570\u7ec4\u957f\u5ea6\u90fd\u4e3a \\(0\\) \uff0c\u5206\u6cbb\u7b56\u7565\u5931\u6548\uff0c\u5feb\u901f\u6392\u5e8f\u9000\u5316\u4e3a\u300c\u5192\u6ce1\u6392\u5e8f\u300d\u4e86\u3002

\u4e3a\u4e86\u5c3d\u91cf\u907f\u514d\u8fd9\u79cd\u60c5\u51b5\u53d1\u751f\uff0c\u6211\u4eec\u53ef\u4ee5\u4f18\u5316\u4e00\u4e0b\u57fa\u51c6\u6570\u7684\u9009\u53d6\u7b56\u7565\u3002\u9996\u5148\uff0c\u5728\u54e8\u5175\u5212\u5206\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5 \u968f\u673a\u9009\u53d6\u4e00\u4e2a\u5143\u7d20\u4f5c\u4e3a\u57fa\u51c6\u6570\u3002\u4f46\u5982\u679c\u8fd0\u6c14\u5f88\u5dee\uff0c\u6bcf\u6b21\u90fd\u9009\u62e9\u5230\u6bd4\u8f83\u5dee\u7684\u57fa\u51c6\u6570\uff0c\u90a3\u4e48\u6548\u7387\u4f9d\u7136\u4e0d\u597d\u3002

\u8fdb\u4e00\u6b65\u5730\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u6570\u7ec4\u4e2d\u9009\u53d6 3 \u4e2a\u5019\u9009\u5143\u7d20\uff08\u4e00\u822c\u4e3a\u6570\u7ec4\u7684\u9996\u3001\u5c3e\u3001\u4e2d\u70b9\u5143\u7d20\uff09\uff0c\u5e76\u5c06\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\u4f5c\u4e3a\u57fa\u51c6\u6570\uff0c\u8fd9\u6837\u57fa\u51c6\u6570\u201c\u65e2\u4e0d\u5927\u4e5f\u4e0d\u5c0f\u201d\u7684\u6982\u7387\u5c31\u5927\u5927\u63d0\u5347\u4e86\u3002\u5f53\u7136\uff0c\u5982\u679c\u6570\u7ec4\u5f88\u957f\u7684\u8bdd\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u9009\u53d6\u66f4\u591a\u5019\u9009\u5143\u7d20\uff0c\u6765\u8fdb\u4e00\u6b65\u63d0\u5347\u7b97\u6cd5\u7684\u7a33\u5065\u6027\u3002\u91c7\u53d6\u8be5\u65b9\u6cd5\u540e\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u52a3\u5316\u81f3 \\(O(n^2)\\) \u7684\u6982\u7387\u6781\u4f4e\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig quick_sort.java
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nint medianThree(int[] nums, int left, int mid, int right) {\n// \u4f7f\u7528\u4e86\u5f02\u6216\u64cd\u4f5c\u6765\u7b80\u5316\u4ee3\u7801\n// \u5f02\u6216\u89c4\u5219\u4e3a 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1\nif ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))\nreturn left;\nelse if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))\nreturn mid;\nelse\nreturn right;\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 */\nint partition(int[] nums, int left, int right) {\n// \u9009\u53d6\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\nint med = medianThree(nums, left, (left + right) / 2, right);\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nswap(nums, left, med);\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nint i = left, j = right;\nwhile (i < j) {\nwhile (i < j && nums[j] >= nums[left])\nj--;          // \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nwhile (i < j && nums[i] <= nums[left])\ni++;          // \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nswap(nums, i, j); // \u4ea4\u6362\u8fd9\u4e24\u4e2a\u5143\u7d20\n}\nswap(nums, i, left);  // \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nreturn i;             // \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n}\n
quick_sort.cpp
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nint medianThree(vector<int>& nums, int left, int mid, int right) {\n// \u4f7f\u7528\u4e86\u5f02\u6216\u64cd\u4f5c\u6765\u7b80\u5316\u4ee3\u7801\n// \u5f02\u6216\u89c4\u5219\u4e3a 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1\nif ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))\nreturn left;\nelse if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))\nreturn mid;\nelse\nreturn right;\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 */\nint partition(vector<int>& nums, int left, int right) {\n// \u9009\u53d6\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\nint med = medianThree(nums, left, (left + right) / 2, right);\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nswap(nums, left, med);\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\n// \u4e0b\u540c\u7701\u7565...\n}\n
quick_sort.py
\"\"\" \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 \"\"\"\ndef median_three(self, nums, left, mid, right):\n# \u4f7f\u7528\u4e86\u5f02\u6216\u64cd\u4f5c\u6765\u7b80\u5316\u4ee3\u7801\n# \u5f02\u6216\u89c4\u5219\u4e3a 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1\nif (nums[left] < nums[mid]) ^ (nums[left] < nums[right]):\nreturn left\nelif (nums[mid] < nums[left]) ^ (nums[mid] > nums[right]):\nreturn mid\nreturn right\n\"\"\" \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 \"\"\"\ndef partition(self, nums, left, right):\n# \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nmed = self.median_three(nums, left, (left + right) // 2, right)\n# \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nnums[left], nums[med] = nums[med], nums[left]\n# \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\ni, j = left, right\nwhile i < j:\nwhile i < j and nums[j] >= nums[left]:\nj -= 1  # \u4ece\u53f3\u5411\u5de6\u627e\u9996\u4e2a\u5c0f\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\nwhile i < j and nums[i] <= nums[left]:\ni += 1  # \u4ece\u5de6\u5411\u53f3\u627e\u9996\u4e2a\u5927\u4e8e\u57fa\u51c6\u6570\u7684\u5143\u7d20\n# \u5143\u7d20\u4ea4\u6362\nnums[i], nums[j] = nums[j], nums[i]\n# \u5c06\u57fa\u51c6\u6570\u4ea4\u6362\u81f3\u4e24\u5b50\u6570\u7ec4\u7684\u5206\u754c\u7ebf\nnums[i], nums[left] = nums[left], nums[i]\nreturn i  # \u8fd4\u56de\u57fa\u51c6\u6570\u7684\u7d22\u5f15\n
quick_sort.go
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nfunc medianThree(nums []int, left, mid, right int) int {\nif (nums[left] < nums[mid]) != (nums[left] < nums[right]) {\nreturn left\n} else if (nums[mid] > nums[left]) != (nums[mid] > nums[right]) {\nreturn mid\n}\nreturn right\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09*/\nfunc partition(nums []int, left, right int) int {\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\nmed := medianThree(nums, left, (left+right)/2, right)\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nnums[left], nums[med] = nums[med], nums[left]\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\n// \u4e0b\u540c\u7701\u7565...\n}\n
quick_sort.js
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nfunction medianThree(nums, left, mid, right) {\n// \u4f7f\u7528\u4e86\u5f02\u6216\u64cd\u4f5c\u6765\u7b80\u5316\u4ee3\u7801\n// \u5f02\u6216\u89c4\u5219\u4e3a 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1\nif ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))\nreturn left;\nelse if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))\nreturn mid;\nelse\nreturn right;\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 */\nfunction partition(nums, left, right) {\n// \u9009\u53d6\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\nlet med = medianThree(nums, left, Math.floor((left + right) / 2), right);\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nswap(nums, left, med);\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\n// \u4e0b\u540c\u7701\u7565...\n}\n
quick_sort.ts
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nfunction medianThree(nums: number[], left: number, mid: number, right: number): number {\n// \u4f7f\u7528\u4e86\u5f02\u6216\u64cd\u4f5c\u6765\u7b80\u5316\u4ee3\u7801\n// \u5f02\u6216\u89c4\u5219\u4e3a 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1\nif (Number(nums[left] < nums[mid]) ^ Number(nums[left] < nums[right])) {\nreturn left;\n} else if (Number(nums[mid] < nums[left]) ^ Number(nums[mid] < nums[right])) {\nreturn mid;\n} else {\nreturn right;\n}\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 */\nfunction partition(nums: number[], left: number, right: number): number {\n// \u9009\u53d6\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\nlet med = medianThree(nums, left, Math.floor((left + right) / 2), right);\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nswap(nums, left, med);\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\n// \u4e0b\u540c\u7701\u7565...\n
quick_sort.c
\n
quick_sort.cs
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nint medianThree(int[] nums, int left, int mid, int right)\n{\n// \u4f7f\u7528\u4e86\u5f02\u6216\u64cd\u4f5c\u6765\u7b80\u5316\u4ee3\u7801\n// \u5f02\u6216\u89c4\u5219\u4e3a 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1\nif ((nums[left] < nums[mid]) ^ (nums[left] < nums[right]))\nreturn left;\nelse if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right]))\nreturn mid;\nelse\nreturn right;\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 */\nint partition(int[] nums, int left, int right)\n{\n// \u9009\u53d6\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\nint med = medianThree(nums, left, (left + right) / 2, right);\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nswap(nums, left, med);\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\n// \u4e0b\u540c\u7701\u7565...\n}\n
quick_sort.swift
/* \u9009\u53d6\u4e09\u4e2a\u5143\u7d20\u7684\u4e2d\u4f4d\u6570 */\nfunc medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {\nif (nums[left] < nums[mid]) != (nums[left] < nums[right]) {\nreturn left\n} else if (nums[mid] < nums[left]) != (nums[mid] < nums[right]) {\nreturn mid\n} else {\nreturn right\n}\n}\n/* \u54e8\u5175\u5212\u5206\uff08\u4e09\u6570\u53d6\u4e2d\u503c\uff09 */\nfunc partition(nums: inout [Int], left: Int, right: Int) -> Int {\n// \u9009\u53d6\u4e09\u4e2a\u5019\u9009\u5143\u7d20\u7684\u4e2d\u4f4d\u6570\nlet med = medianThree(nums: nums, left: left, mid: (left + right) / 2, right: right)\n// \u5c06\u4e2d\u4f4d\u6570\u4ea4\u6362\u81f3\u6570\u7ec4\u6700\u5de6\u7aef\nswap(nums: &nums, i: left, j: med)\n// \u4ee5 nums[left] \u4f5c\u4e3a\u57fa\u51c6\u6570\n// \u4e0b\u540c\u7701\u7565...\n}\n
quick_sort.zig
\n
"},{"location":"chapter_sorting/quick_sort/#1145","title":"11.4.5. \u5c3e\u9012\u5f52\u4f18\u5316","text":"

\u666e\u901a\u5feb\u901f\u6392\u5e8f\u5728\u67d0\u4e9b\u8f93\u5165\u4e0b\u7684\u7a7a\u95f4\u6548\u7387\u53d8\u5dee\u3002\u4ecd\u7136\u4ee5\u5b8c\u5168\u5012\u5e8f\u7684\u8f93\u5165\u6570\u7ec4\u4e3a\u4f8b\uff0c\u7531\u4e8e\u6bcf\u8f6e\u54e8\u5175\u5212\u5206\u540e\u53f3\u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 0 \uff0c\u90a3\u4e48\u5c06\u5f62\u6210\u4e00\u4e2a\u9ad8\u5ea6\u4e3a \\(n - 1\\) \u7684\u9012\u5f52\u6811\uff0c\u6b64\u65f6\u4f7f\u7528\u7684\u6808\u5e27\u7a7a\u95f4\u5927\u5c0f\u52a3\u5316\u81f3 \\(O(n)\\) \u3002

\u4e3a\u4e86\u907f\u514d\u6808\u5e27\u7a7a\u95f4\u7684\u7d2f\u79ef\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u6bcf\u8f6e\u54e8\u5175\u6392\u5e8f\u5b8c\u6210\u540e\uff0c\u5224\u65ad\u4e24\u4e2a\u5b50\u6570\u7ec4\u7684\u957f\u5ea6\u5927\u5c0f\uff0c\u4ec5\u9012\u5f52\u6392\u5e8f\u8f83\u77ed\u7684\u5b50\u6570\u7ec4\u3002\u7531\u4e8e\u8f83\u77ed\u7684\u5b50\u6570\u7ec4\u957f\u5ea6\u4e0d\u4f1a\u8d85\u8fc7 \\(\\frac{n}{2}\\) \uff0c\u56e0\u6b64\u8fd9\u6837\u505a\u80fd\u4fdd\u8bc1\u9012\u5f52\u6df1\u5ea6\u4e0d\u8d85\u8fc7 \\(\\log n\\) \uff0c\u5373\u6700\u5dee\u7a7a\u95f4\u590d\u6742\u5ea6\u88ab\u4f18\u5316\u81f3 \\(O(\\log n)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig quick_sort.java
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 */\nvoid quickSort(int[] nums, int left, int right) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile (left < right) {\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\nint pivot = partition(nums, left, right);\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif (pivot - left < right - pivot) {\nquickSort(nums, left, pivot - 1);  // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1;  // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n} else {\nquickSort(nums, pivot + 1, right); // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1; // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.cpp
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 */\nvoid quickSort(vector<int>& nums, int left, int right) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile (left < right) {\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\nint pivot = partition(nums, left, right);\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif (pivot - left < right - pivot) {\nquickSort(nums, left, pivot - 1);  // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1;  // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n} else {\nquickSort(nums, pivot + 1, right); // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1; // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.py
\"\"\" \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 \"\"\"\ndef quick_sort(self, nums, left, right):\n# \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile left < right:\n# \u54e8\u5175\u5212\u5206\u64cd\u4f5c\npivot = self.partition(nums, left, right)\n# \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif pivot - left < right - pivot:\nself.quick_sort(nums, left, pivot - 1)  # \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1     # \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\nelse:\nself.quick_sort(nums, pivot + 1, right)  # \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1    # \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n
quick_sort.go
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09*/\nfunc quickSort(nums []int, left, right int) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nfor left < right {\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\npivot := partition(nums, left, right)\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif pivot-left < right-pivot {\nquickSort(nums, left, pivot-1)   // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1                 // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n} else {\nquickSort(nums, pivot+1, right)  // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1                // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.js
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 */\nfunction quickSort(nums, left, right) {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile (left < right) {\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\nlet pivot = partition(nums, left, right);\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif (pivot - left < right - pivot) {\nquickSort(nums, left, pivot - 1);  // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1;  // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n} else {\nquickSort(nums, pivot + 1, right); // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1; // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.ts
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 */\nfunction quickSort(nums: number[], left: number, right: number): void {\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile (left < right) {\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\nlet pivot = partition(nums, left, right);\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif (pivot - left < right - pivot) {\nquickSort(nums, left, pivot - 1); // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1; // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n} else {\nquickSort(nums, pivot + 1, right); // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1; // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.c
\n
quick_sort.cs
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 */\nvoid quickSort(int[] nums, int left, int right)\n{\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile (left < right)\n{\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\nint pivot = partition(nums, left, right);\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif (pivot - left < right - pivot)\n{\nquickSort(nums, left, pivot - 1);  // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1;  // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n}\nelse\n{\nquickSort(nums, pivot + 1, right); // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1; // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.swift
/* \u5feb\u901f\u6392\u5e8f\uff08\u5c3e\u9012\u5f52\u4f18\u5316\uff09 */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\nvar left = left\nvar right = right\n// \u5b50\u6570\u7ec4\u957f\u5ea6\u4e3a 1 \u65f6\u7ec8\u6b62\nwhile left < right {\n// \u54e8\u5175\u5212\u5206\u64cd\u4f5c\nlet pivot = partition(nums: &nums, left: left, right: right)\n// \u5bf9\u4e24\u4e2a\u5b50\u6570\u7ec4\u4e2d\u8f83\u77ed\u7684\u90a3\u4e2a\u6267\u884c\u5feb\u6392\nif (pivot - left) < (right - pivot) {\nquickSort(nums: &nums, left: left, right: pivot - 1) // \u9012\u5f52\u6392\u5e8f\u5de6\u5b50\u6570\u7ec4\nleft = pivot + 1 // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [pivot + 1, right]\n} else {\nquickSort(nums: &nums, left: pivot + 1, right: right) // \u9012\u5f52\u6392\u5e8f\u53f3\u5b50\u6570\u7ec4\nright = pivot - 1 // \u5269\u4f59\u5f85\u6392\u5e8f\u533a\u95f4\u4e3a [left, pivot - 1]\n}\n}\n}\n
quick_sort.zig
\n
"},{"location":"chapter_sorting/summary/","title":"11.6. \u5c0f\u7ed3","text":""},{"location":"chapter_stack_and_queue/deque/","title":"5.3. \u53cc\u5411\u961f\u5217","text":"

\u5bf9\u4e8e\u961f\u5217\uff0c\u6211\u4eec\u53ea\u80fd\u5728\u5934\u90e8\u5220\u9664\u6216\u5728\u5c3e\u90e8\u6dfb\u52a0\u5143\u7d20\uff0c\u800c\u300c\u53cc\u5411\u961f\u5217 Deque\u300d\u66f4\u52a0\u7075\u6d3b\uff0c\u5728\u5176\u5934\u90e8\u548c\u5c3e\u90e8\u90fd\u80fd\u6267\u884c\u5143\u7d20\u6dfb\u52a0\u6216\u5220\u9664\u64cd\u4f5c\u3002

Fig. \u53cc\u5411\u961f\u5217\u7684\u64cd\u4f5c

"},{"location":"chapter_stack_and_queue/deque/#531","title":"5.3.1. \u53cc\u5411\u961f\u5217\u5e38\u7528\u64cd\u4f5c","text":"

\u53cc\u5411\u961f\u5217\u7684\u5e38\u7528\u64cd\u4f5c\u89c1\u4e0b\u8868\uff0c\u65b9\u6cd5\u540d\u9700\u6839\u636e\u7279\u5b9a\u8bed\u8a00\u6765\u786e\u5b9a\u3002

Table. \u53cc\u5411\u961f\u5217\u7684\u5e38\u7528\u64cd\u4f5c

\u65b9\u6cd5\u540d \u63cf\u8ff0 \u65f6\u95f4\u590d\u6742\u5ea6 pushFirst() \u5c06\u5143\u7d20\u6dfb\u52a0\u81f3\u961f\u9996 \\(O(1)\\) pushLast() \u5c06\u5143\u7d20\u6dfb\u52a0\u81f3\u961f\u5c3e \\(O(1)\\) pollFirst() \u5220\u9664\u961f\u9996\u5143\u7d20 \\(O(1)\\) pollLast() \u5220\u9664\u961f\u5c3e\u5143\u7d20 \\(O(1)\\) peekFirst() \u8bbf\u95ee\u961f\u9996\u5143\u7d20 \\(O(1)\\) peekLast() \u8bbf\u95ee\u961f\u5c3e\u5143\u7d20 \\(O(1)\\) size() \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 \\(O(1)\\) isEmpty() \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a \\(O(1)\\)

\u76f8\u540c\u5730\uff0c\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u7f16\u7a0b\u8bed\u8a00\u5b9e\u73b0\u597d\u7684\u53cc\u5411\u961f\u5217\u7c7b\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig deque.java
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\nDeque<Integer> deque = new LinkedList<>();\n/* \u5143\u7d20\u5165\u961f */\ndeque.offerLast(2);   // \u6dfb\u52a0\u81f3\u961f\u5c3e\ndeque.offerLast(5);\ndeque.offerLast(4);\ndeque.offerFirst(3);  // \u6dfb\u52a0\u81f3\u961f\u9996\ndeque.offerFirst(1);\n/* \u8bbf\u95ee\u5143\u7d20 */\nint peekFirst = deque.peekFirst();  // \u961f\u9996\u5143\u7d20\nint peekLast = deque.peekLast();    // \u961f\u5c3e\u5143\u7d20\n/* \u5143\u7d20\u51fa\u961f */\nint pollFirst = deque.pollFirst();  // \u961f\u9996\u5143\u7d20\u51fa\u961f\nint pollLast = deque.pollLast();    // \u961f\u5c3e\u5143\u7d20\u51fa\u961f\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nint size = deque.size();\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nboolean isEmpty = deque.isEmpty();\n
deque.cpp
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\ndeque<int> deque;\n/* \u5143\u7d20\u5165\u961f */\ndeque.push_back(2);   // \u6dfb\u52a0\u81f3\u961f\u5c3e\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3);  // \u6dfb\u52a0\u81f3\u961f\u9996\ndeque.push_front(1);\n/* \u8bbf\u95ee\u5143\u7d20 */\nint front = deque.front(); // \u961f\u9996\u5143\u7d20\nint back = deque.back();   // \u961f\u5c3e\u5143\u7d20\n/* \u5143\u7d20\u51fa\u961f */\ndeque.pop_front();  // \u961f\u9996\u5143\u7d20\u51fa\u961f\ndeque.pop_back();   // \u961f\u5c3e\u5143\u7d20\u51fa\u961f\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nint size = deque.size();\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nbool empty = deque.empty();\n
deque.py
\"\"\" \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 \"\"\"\nduque = deque()\n\"\"\" \u5143\u7d20\u5165\u961f \"\"\"\nduque.append(2)      # \u6dfb\u52a0\u81f3\u961f\u5c3e\nduque.append(5)\nduque.append(4)\nduque.appendleft(3)  # \u6dfb\u52a0\u81f3\u961f\u9996\nduque.appendleft(1)\n\"\"\" \u8bbf\u95ee\u5143\u7d20 \"\"\"\nfront = duque[0]  # \u961f\u9996\u5143\u7d20\nrear = duque[-1]  # \u961f\u5c3e\u5143\u7d20\n\"\"\" \u5143\u7d20\u51fa\u961f \"\"\"\npop_front = duque.popleft()  # \u961f\u9996\u5143\u7d20\u51fa\u961f\npop_rear = duque.pop()       # \u961f\u5c3e\u5143\u7d20\u51fa\u961f\n\"\"\" \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 \"\"\"\nsize = len(duque)\n\"\"\" \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a \"\"\"\nis_empty = len(duque) == 0\n
deque_test.go
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\n// \u5728 Go \u4e2d\uff0c\u5c06 list \u4f5c\u4e3a\u53cc\u5411\u961f\u5217\u4f7f\u7528\ndeque := list.New()\n/* \u5143\u7d20\u5165\u961f */\ndeque.PushBack(2)      // \u6dfb\u52a0\u81f3\u961f\u5c3e\ndeque.PushBack(5)\ndeque.PushBack(4)\ndeque.PushFront(3)     // \u6dfb\u52a0\u81f3\u961f\u9996\ndeque.PushFront(1)\n/* \u8bbf\u95ee\u5143\u7d20 */\nfront := deque.Front() // \u961f\u9996\u5143\u7d20\nrear := deque.Back()   // \u961f\u5c3e\u5143\u7d20\n/* \u5143\u7d20\u51fa\u961f */\ndeque.Remove(front)    // \u961f\u9996\u5143\u7d20\u51fa\u961f\ndeque.Remove(rear)     // \u961f\u5c3e\u5143\u7d20\u51fa\u961f\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nsize := deque.Len()\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nisEmpty := deque.Len() == 0\n
deque.js
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\n// JavaScript \u6ca1\u6709\u5185\u7f6e\u7684\u53cc\u7aef\u961f\u5217\uff0c\u53ea\u80fd\u628a Array \u5f53\u4f5c\u53cc\u7aef\u961f\u5217\u6765\u4f7f\u7528\nconst deque = [];\n/* \u5143\u7d20\u5165\u961f */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// \u8bf7\u6ce8\u610f\uff0c\u7531\u4e8e\u662f\u6570\u7ec4\uff0cunshift() \u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a O(n)\ndeque.unshift(3);\ndeque.unshift(1);\nconsole.log(\"\u53cc\u5411\u961f\u5217 deque = \", deque);\n/* \u8bbf\u95ee\u5143\u7d20 */\nconst peekFirst = deque[0];\nconsole.log(\"\u961f\u9996\u5143\u7d20 peekFirst = \" + peekFirst);\nconst peekLast = deque[deque.length - 1];\nconsole.log(\"\u961f\u5c3e\u5143\u7d20 peekLast = \" + peekLast);\n/* \u5143\u7d20\u51fa\u961f */\n// \u8bf7\u6ce8\u610f\uff0c\u7531\u4e8e\u662f\u6570\u7ec4\uff0cshift() \u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a O(n)\nconst popFront = deque.shift();\nconsole.log(\"\u961f\u9996\u51fa\u961f\u5143\u7d20 popFront = \" + popFront + \"\uff0c\u961f\u9996\u51fa\u961f\u540e deque = \" + deque);\nconst popBack = deque.pop();\nconsole.log(\"\u961f\u5c3e\u51fa\u961f\u5143\u7d20 popBack = \" + popBack + \"\uff0c\u961f\u5c3e\u51fa\u961f\u540e deque = \" + deque);\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nconst size = deque.length;\nconsole.log(\"\u53cc\u5411\u961f\u5217\u957f\u5ea6 size = \" + size);\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nconst isEmpty = size === 0;\nconsole.log(\"\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a = \" + isEmpty);\n
deque.ts
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\n// TypeScript \u6ca1\u6709\u5185\u7f6e\u7684\u53cc\u7aef\u961f\u5217\uff0c\u53ea\u80fd\u628a Array \u5f53\u4f5c\u53cc\u7aef\u961f\u5217\u6765\u4f7f\u7528\nconst deque: number[] = [];\n/* \u5143\u7d20\u5165\u961f */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// \u8bf7\u6ce8\u610f\uff0c\u7531\u4e8e\u662f\u6570\u7ec4\uff0cunshift() \u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a O(n)\ndeque.unshift(3);\ndeque.unshift(1);\nconsole.log(\"\u53cc\u5411\u961f\u5217 deque = \", deque);\n/* \u8bbf\u95ee\u5143\u7d20 */\nconst peekFirst: number = deque[0];\nconsole.log(\"\u961f\u9996\u5143\u7d20 peekFirst = \" + peekFirst);\nconst peekLast: number = deque[deque.length - 1];\nconsole.log(\"\u961f\u5c3e\u5143\u7d20 peekLast = \" + peekLast);\n/* \u5143\u7d20\u51fa\u961f */\n// \u8bf7\u6ce8\u610f\uff0c\u7531\u4e8e\u662f\u6570\u7ec4\uff0cshift() \u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a O(n)\nconst popFront: number = deque.shift() as number;\nconsole.log(\"\u961f\u9996\u51fa\u961f\u5143\u7d20 popFront = \" + popFront + \"\uff0c\u961f\u9996\u51fa\u961f\u540e deque = \" + deque);\nconst popBack: number = deque.pop() as number;\nconsole.log(\"\u961f\u5c3e\u51fa\u961f\u5143\u7d20 popBack = \" + popBack + \"\uff0c\u961f\u5c3e\u51fa\u961f\u540e deque = \" + deque);\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nconst size: number = deque.length;\nconsole.log(\"\u53cc\u5411\u961f\u5217\u957f\u5ea6 size = \" + size);\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nconst isEmpty: boolean = size === 0;\nconsole.log(\"\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a = \" + isEmpty);\n
deque.c
\n
deque.cs
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\n// \u5728 C# \u4e2d\uff0c\u5c06\u94fe\u8868 LinkedList \u770b\u4f5c\u53cc\u5411\u961f\u5217\u6765\u4f7f\u7528\nLinkedList<int> deque = new LinkedList<int>();\n/* \u5143\u7d20\u5165\u961f */\ndeque.AddLast(2);   // \u6dfb\u52a0\u81f3\u961f\u5c3e\ndeque.AddLast(5);\ndeque.AddLast(4);\ndeque.AddFirst(3);  // \u6dfb\u52a0\u81f3\u961f\u9996\ndeque.AddFirst(1);\n/* \u8bbf\u95ee\u5143\u7d20 */\nint peekFirst = deque.First.Value;  // \u961f\u9996\u5143\u7d20\nint peekLast = deque.Last.Value;    // \u961f\u5c3e\u5143\u7d20\n/* \u5143\u7d20\u51fa\u961f */\ndeque.RemoveFirst();  // \u961f\u9996\u5143\u7d20\u51fa\u961f\ndeque.RemoveLast();   // \u961f\u5c3e\u5143\u7d20\u51fa\u961f\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nint size = deque.Count;\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nbool isEmpty = deque.Count == 0;\n
deque.swift
/* \u521d\u59cb\u5316\u53cc\u5411\u961f\u5217 */\n// Swift \u6ca1\u6709\u5185\u7f6e\u7684\u53cc\u5411\u961f\u5217\u7c7b\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u53cc\u5411\u961f\u5217\u6765\u4f7f\u7528\nvar deque: [Int] = []\n/* \u5143\u7d20\u5165\u961f */\ndeque.append(2) // \u6dfb\u52a0\u81f3\u961f\u5c3e\ndeque.append(5)\ndeque.append(4)\ndeque.insert(3, at: 0) // \u6dfb\u52a0\u81f3\u961f\u9996\ndeque.insert(1, at: 0)\n/* \u8bbf\u95ee\u5143\u7d20 */\nlet peekFirst = deque.first! // \u961f\u9996\u5143\u7d20\nlet peekLast = deque.last! // \u961f\u5c3e\u5143\u7d20\n/* \u5143\u7d20\u51fa\u961f */\n// \u4f7f\u7528 Array \u6a21\u62df\u65f6 pollFirst \u7684\u590d\u6742\u5ea6\u4e3a O(n)\nlet pollFirst = deque.removeFirst() // \u961f\u9996\u5143\u7d20\u51fa\u961f\nlet pollLast = deque.removeLast() // \u961f\u5c3e\u5143\u7d20\u51fa\u961f\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nlet size = deque.count\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nlet isEmpty = deque.isEmpty\n
deque.zig
\n
"},{"location":"chapter_stack_and_queue/deque/#532","title":"5.3.2. \u53cc\u5411\u961f\u5217\u5b9e\u73b0","text":"

\u53cc\u5411\u961f\u5217\u9700\u8981\u4e00\u79cd\u53ef\u4ee5\u5728\u4e24\u7aef\u6dfb\u52a0\u3001\u4e24\u7aef\u5220\u9664\u7684\u6570\u636e\u7ed3\u6784\u3002\u4e0e\u961f\u5217\u7684\u5b9e\u73b0\u65b9\u6cd5\u7c7b\u4f3c\uff0c\u53cc\u5411\u961f\u5217\u4e5f\u53ef\u4ee5\u4f7f\u7528\u53cc\u5411\u94fe\u8868\u548c\u5faa\u73af\u6570\u7ec4\u6765\u5b9e\u73b0\u3002

"},{"location":"chapter_stack_and_queue/deque/#_1","title":"\u57fa\u4e8e\u53cc\u5411\u94fe\u8868\u7684\u5b9e\u73b0","text":"

\u6211\u4eec\u5c06\u53cc\u5411\u94fe\u8868\u7684\u5934\u7ed3\u70b9\u548c\u5c3e\u7ed3\u70b9\u5206\u522b\u770b\u4f5c\u53cc\u5411\u961f\u5217\u7684\u961f\u9996\u548c\u961f\u5c3e\uff0c\u5e76\u4e14\u5b9e\u73b0\u5728\u4e24\u7aef\u90fd\u80fd\u6dfb\u52a0\u4e0e\u5220\u9664\u7ed3\u70b9\u3002

LinkedListDequepushLast()pushFirst()pollLast()pollFirst()

\u4ee5\u4e0b\u662f\u4f7f\u7528\u53cc\u5411\u94fe\u8868\u5b9e\u73b0\u53cc\u5411\u961f\u5217\u7684\u793a\u4f8b\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linkedlist_deque.java
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9 */\nclass ListNode {\nint val;       // \u7ed3\u70b9\u503c\nListNode next; // \u540e\u7ee7\u7ed3\u70b9\u5f15\u7528\uff08\u6307\u9488\uff09\nListNode prev; // \u524d\u9a71\u7ed3\u70b9\u5f15\u7528\uff08\u6307\u9488\uff09\nListNode(int val) {\nthis.val = val;\nprev = next = null;\n}\n}\n/* \u57fa\u4e8e\u53cc\u5411\u94fe\u8868\u5b9e\u73b0\u7684\u53cc\u5411\u961f\u5217 */\nclass LinkedListDeque {\nprivate ListNode front, rear; // \u5934\u7ed3\u70b9 front \uff0c\u5c3e\u7ed3\u70b9 rear\nprivate int size = 0;         // \u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6\npublic LinkedListDeque() {\nfront = rear = null;\n}\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\npublic int size() {\nreturn size;\n}\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\npublic boolean isEmpty() {\nreturn size() == 0;\n}\n/* \u5165\u961f\u64cd\u4f5c */\nprivate void push(int num, boolean isFront) {\nListNode node = new ListNode(num);\n// \u82e5\u94fe\u8868\u4e3a\u7a7a\uff0c\u5219\u4ee4 front, rear \u90fd\u6307\u5411 node\nif (isEmpty())\nfront = rear = node;\n// \u961f\u9996\u5165\u961f\u64cd\u4f5c\nelse if (isFront) {\n// \u5c06 node \u6dfb\u52a0\u81f3\u94fe\u8868\u5934\u90e8\nfront.prev = node;\nnode.next = front;\nfront = node; // \u66f4\u65b0\u5934\u7ed3\u70b9\n// \u961f\u5c3e\u5165\u961f\u64cd\u4f5c\n} else {\n// \u5c06 node \u6dfb\u52a0\u81f3\u94fe\u8868\u5c3e\u90e8\nrear.next = node;\nnode.prev = rear;\nrear = node;  // \u66f4\u65b0\u5c3e\u7ed3\u70b9\n}\nsize++; // \u66f4\u65b0\u961f\u5217\u957f\u5ea6\n}\n/* \u961f\u9996\u5165\u961f */\npublic void pushFirst(int num) {\npush(num, true);\n}\n/* \u961f\u5c3e\u5165\u961f */\npublic void pushLast(int num) {\npush(num, false);\n}\n/* \u51fa\u961f\u64cd\u4f5c */\nprivate Integer poll(boolean isFront) {\n// \u82e5\u961f\u5217\u4e3a\u7a7a\uff0c\u76f4\u63a5\u8fd4\u56de null\nif (isEmpty())\nreturn null;\nint val;\n// \u961f\u9996\u51fa\u961f\u64cd\u4f5c\nif (isFront) {\nval = front.val; // \u6682\u5b58\u5934\u7ed3\u70b9\u503c\n// \u5220\u9664\u5934\u7ed3\u70b9\nListNode fNext = front.next;\nif (fNext != null) {\nfNext.prev = null;\nfront.next = null;\n}\nfront = fNext;   // \u66f4\u65b0\u5934\u7ed3\u70b9\n// \u961f\u5c3e\u51fa\u961f\u64cd\u4f5c\n} else {\nval = rear.val;  // \u6682\u5b58\u5c3e\u7ed3\u70b9\u503c\n// \u5220\u9664\u5c3e\u7ed3\u70b9\nListNode rPrev = rear.prev;\nif (rPrev != null) {\nrPrev.next = null;\nrear.prev = null;\n}\nrear = rPrev;    // \u66f4\u65b0\u5c3e\u7ed3\u70b9\n}\nsize--; // \u66f4\u65b0\u961f\u5217\u957f\u5ea6\nreturn val;\n}\n/* \u961f\u9996\u51fa\u961f */\npublic Integer pollFirst() {\nreturn poll(true);\n}\n/* \u961f\u5c3e\u51fa\u961f */\npublic Integer pollLast() {\nreturn poll(false);\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npublic Integer peekFirst() {\nreturn isEmpty() ? null : front.val;\n}\n/* \u8bbf\u95ee\u961f\u5c3e\u5143\u7d20 */\npublic Integer peekLast() {\nreturn isEmpty() ? null : rear.val;\n}\n/* \u6253\u5370\u53cc\u5411\u961f\u5217 */\npublic void print() {\nif (isEmpty()) {\nSystem.out.println(\"[ ]\");\nreturn;\n}\nList<String> list = new ArrayList<>();\nListNode head = front;\nwhile (head != null) {\nlist.add(String.valueOf(head.val));\nhead = head.next;\n}\nSystem.out.println(\"[\" + String.join(\", \", list) + \"]\");\n}\n}\n
linkedlist_deque.cpp
\n
linkedlist_deque.py
\n
linkedlist_deque.go
\n
linkedlist_deque.js
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9 */\nclass ListNode {\nprev;   // \u524d\u9a71\u7ed3\u70b9\u5f15\u7528 (\u6307\u9488)\nnext;   // \u540e\u7ee7\u7ed3\u70b9\u5f15\u7528 (\u6307\u9488)\nval;    // \u7ed3\u70b9\u503c\nconstructor(val) {\nthis.val = val;\nthis.next = null;\nthis.prev = null;\n}\n}\n/* \u57fa\u4e8e\u53cc\u5411\u94fe\u8868\u5b9e\u73b0\u7684\u53cc\u5411\u961f\u5217 */\nclass LinkedListDeque {\nfront;  // \u5934\u7ed3\u70b9 front\nrear;   // \u5c3e\u7ed3\u70b9 rear\nlen;    // \u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6\nconstructor() {\nthis.front = null;\nthis.rear = null;\nthis.len = 0;\n}\n/* \u961f\u5c3e\u5165\u961f\u64cd\u4f5c */\npushLast(val) {\nconst node = new ListNode(val);\n// \u82e5\u94fe\u8868\u4e3a\u7a7a\uff0c\u5219\u4ee4 front, rear \u90fd\u6307\u5411 node\nif (this.len === 0) {\nthis.front = node;\nthis.rear = node;\n} else {\n// \u5c06 node \u6dfb\u52a0\u81f3\u94fe\u8868\u5c3e\u90e8\nthis.rear.next = node;\nnode.prev = this.rear;\nthis.rear = node; // \u66f4\u65b0\u5c3e\u7ed3\u70b9\n}\nthis.len++;\n}\n/* \u961f\u9996\u5165\u961f\u64cd\u4f5c */\npushFirst(val) {\nconst node = new ListNode(val);\n// \u82e5\u94fe\u8868\u4e3a\u7a7a\uff0c\u5219\u4ee4 front, rear \u90fd\u6307\u5411 node\nif (this.len === 0) {\nthis.front = node;\nthis.rear = node;\n} else {\n// \u5c06 node \u6dfb\u52a0\u81f3\u94fe\u8868\u5934\u90e8\nthis.front.prev = node;\nnode.next = this.front;\nthis.front = node; // \u66f4\u65b0\u5934\u7ed3\u70b9\n}\nthis.len++;\n}\n/* \u961f\u5c3e\u51fa\u961f\u64cd\u4f5c */\npollLast() {\nif (this.len === 0) {\nreturn null;\n}\nconst value = this.rear.val; // \u5b58\u50a8\u5c3e\u7ed3\u70b9\u503c\n// \u5220\u9664\u5c3e\u7ed3\u70b9\nlet temp = this.rear.prev;\nif (temp !== null) {\ntemp.next = null;\nthis.rear.prev = null;\n}\nthis.rear = temp;   // \u66f4\u65b0\u5c3e\u7ed3\u70b9\nthis.len--;\nreturn value;\n}\n/* \u961f\u9996\u51fa\u961f\u64cd\u4f5c */\npollFirst() {\nif (this.len === 0) {\nreturn null;\n}\nconst value = this.front.val; // \u5b58\u50a8\u5c3e\u7ed3\u70b9\u503c\n// \u5220\u9664\u5934\u7ed3\u70b9\nlet temp = this.front.next;\nif (temp !== null) {\ntemp.prev = null;\nthis.front.next = null;\n}\nthis.front = temp;   // \u66f4\u65b0\u5934\u7ed3\u70b9\nthis.len--;\nreturn value;\n}\n/* \u8bbf\u95ee\u961f\u5c3e\u5143\u7d20 */\npeekLast() {\nreturn this.len === 0 ? null : this.rear.val;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeekFirst() {\nreturn this.len === 0 ? null : this.front.val;\n}\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nsize() {\nreturn this.len;\n}\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nisEmpty() {\nreturn this.len === 0;\n}\n/* \u6253\u5370\u53cc\u5411\u961f\u5217 */\nprint() {\nconst arr = [];\nlet temp = this.front;\nwhile (temp !== null) {\narr.push(temp.val);\ntemp = temp.next;\n}\nconsole.log(\"[\" + arr.join(\", \") + \"]\");\n}\n}\n
linkedlist_deque.ts
/* \u53cc\u5411\u94fe\u8868\u7ed3\u70b9 */\nclass ListNode {\nprev: ListNode;     // \u524d\u9a71\u7ed3\u70b9\u5f15\u7528 (\u6307\u9488)\nnext: ListNode;     // \u540e\u7ee7\u7ed3\u70b9\u5f15\u7528 (\u6307\u9488)\nval: number;        // \u7ed3\u70b9\u503c\nconstructor(val: number) {\nthis.val = val;\nthis.next = null;\nthis.prev = null;\n}\n}\n/* \u57fa\u4e8e\u53cc\u5411\u94fe\u8868\u5b9e\u73b0\u7684\u53cc\u5411\u961f\u5217 */\nclass LinkedListDeque {\nfront: ListNode;    // \u5934\u7ed3\u70b9 front\nrear: ListNode;     // \u5c3e\u7ed3\u70b9 rear\nlen: number;        // \u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6\nconstructor() {\nthis.front = null;\nthis.rear = null;\nthis.len = 0;\n}\n/* \u961f\u5c3e\u5165\u961f\u64cd\u4f5c */\npushLast(val: number): void {\nconst node: ListNode = new ListNode(val);\n// \u82e5\u94fe\u8868\u4e3a\u7a7a\uff0c\u5219\u4ee4 front, rear \u90fd\u6307\u5411 node\nif (this.len === 0) {\nthis.front = node;\nthis.rear = node;\n} else {\n// \u5c06 node \u6dfb\u52a0\u81f3\u94fe\u8868\u5c3e\u90e8\nthis.rear.next = node;\nnode.prev = this.rear;\nthis.rear = node; // \u66f4\u65b0\u5c3e\u7ed3\u70b9\n}\nthis.len++;\n}\n/* \u961f\u9996\u5165\u961f\u64cd\u4f5c */\npushFirst(val: number): void {\nconst node: ListNode = new ListNode(val);\n// \u82e5\u94fe\u8868\u4e3a\u7a7a\uff0c\u5219\u4ee4 front, rear \u90fd\u6307\u5411 node\nif (this.len === 0) {\nthis.front = node;\nthis.rear = node;\n} else {\n// \u5c06 node \u6dfb\u52a0\u81f3\u94fe\u8868\u5934\u90e8\nthis.front.prev = node;\nnode.next = this.front;\nthis.front = node; // \u66f4\u65b0\u5934\u7ed3\u70b9\n}\nthis.len++;\n}\n/* \u961f\u5c3e\u51fa\u961f\u64cd\u4f5c */\npollLast(): number {\nif (this.len === 0) {\nreturn null;\n}\nconst value: number = this.rear.val; // \u5b58\u50a8\u5c3e\u7ed3\u70b9\u503c\n// \u5220\u9664\u5c3e\u7ed3\u70b9\nlet temp: ListNode = this.rear.prev;\nif (temp !== null) {\ntemp.next = null;\nthis.rear.prev = null;\n}\nthis.rear = temp;   // \u66f4\u65b0\u5c3e\u7ed3\u70b9\nthis.len--;\nreturn value;\n}\n/* \u961f\u9996\u51fa\u961f\u64cd\u4f5c */\npollFirst(): number {\nif (this.len === 0) {\nreturn null;\n}\nconst value: number = this.front.val; // \u5b58\u50a8\u5c3e\u7ed3\u70b9\u503c\n// \u5220\u9664\u5934\u7ed3\u70b9\nlet temp: ListNode = this.front.next;\nif (temp !== null) {\ntemp.prev = null;\nthis.front.next = null;\n}\nthis.front = temp;   // \u66f4\u65b0\u5934\u7ed3\u70b9\nthis.len--;\nreturn value;\n}\n/* \u8bbf\u95ee\u961f\u5c3e\u5143\u7d20 */\npeekLast(): number {\nreturn this.len === 0 ? null : this.rear.val;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeekFirst(): number {\nreturn this.len === 0 ? null : this.front.val;\n}\n/* \u83b7\u53d6\u53cc\u5411\u961f\u5217\u7684\u957f\u5ea6 */\nsize(): number {\nreturn this.len;\n}\n/* \u5224\u65ad\u53cc\u5411\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nisEmpty(): boolean {\nreturn this.len === 0;\n}\n/* \u6253\u5370\u53cc\u5411\u961f\u5217 */\nprint(): void {\nconst arr: number[] = [];\nlet temp: ListNode = this.front;\nwhile (temp !== null) {\narr.push(temp.val);\ntemp = temp.next;\n}\nconsole.log(\"[\" + arr.join(\", \") + \"]\");\n}\n}\n
linkedlist_deque.c
\n
linkedlist_deque.cs
\n
linkedlist_deque.swift
\n
linkedlist_deque.zig
\n
"},{"location":"chapter_stack_and_queue/queue/","title":"5.2. \u961f\u5217","text":"

\u300c\u961f\u5217 Queue\u300d\u662f\u4e00\u79cd\u9075\u5faa\u300c\u5148\u5165\u5148\u51fa first in, first out\u300d\u6570\u636e\u64cd\u4f5c\u89c4\u5219\u7684\u7ebf\u6027\u6570\u636e\u7ed3\u6784\u3002\u987e\u540d\u601d\u4e49\uff0c\u961f\u5217\u6a21\u62df\u7684\u662f\u6392\u961f\u73b0\u8c61\uff0c\u5373\u5916\u9762\u7684\u4eba\u4e0d\u65ad\u52a0\u5165\u961f\u5217\u5c3e\u90e8\uff0c\u800c\u5904\u4e8e\u961f\u5217\u5934\u90e8\u7684\u4eba\u4e0d\u65ad\u5730\u79bb\u5f00\u3002

\u6211\u4eec\u5c06\u961f\u5217\u5934\u90e8\u79f0\u4e3a\u300c\u961f\u9996\u300d\uff0c\u961f\u5217\u5c3e\u90e8\u79f0\u4e3a\u300c\u961f\u5c3e\u300d\uff0c\u5c06\u628a\u5143\u7d20\u52a0\u5165\u961f\u5c3e\u7684\u64cd\u4f5c\u79f0\u4e3a\u300c\u5165\u961f\u300d\uff0c\u5220\u9664\u961f\u9996\u5143\u7d20\u7684\u64cd\u4f5c\u79f0\u4e3a\u300c\u51fa\u961f\u300d\u3002

Fig. \u961f\u5217\u7684\u5148\u5165\u5148\u51fa\u7279\u6027

"},{"location":"chapter_stack_and_queue/queue/#521","title":"5.2.1. \u961f\u5217\u5e38\u7528\u64cd\u4f5c","text":"

\u961f\u5217\u7684\u5e38\u7528\u64cd\u4f5c\u89c1\u4e0b\u8868\uff0c\u65b9\u6cd5\u540d\u9700\u6839\u636e\u7279\u5b9a\u8bed\u8a00\u6765\u786e\u5b9a\u3002

Table. \u961f\u5217\u7684\u5e38\u7528\u64cd\u4f5c

\u65b9\u6cd5\u540d \u63cf\u8ff0 \u65f6\u95f4\u590d\u6742\u5ea6 push() \u5143\u7d20\u5165\u961f\uff0c\u5373\u5c06\u5143\u7d20\u6dfb\u52a0\u81f3\u961f\u5c3e \\(O(1)\\) poll() \u961f\u9996\u5143\u7d20\u51fa\u961f \\(O(1)\\) front() \u8bbf\u95ee\u961f\u9996\u5143\u7d20 \\(O(1)\\) size() \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 \\(O(1)\\) isEmpty() \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a \\(O(1)\\)

\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u7f16\u7a0b\u8bed\u8a00\u5b9e\u73b0\u597d\u7684\u961f\u5217\u7c7b\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig queue.java
/* \u521d\u59cb\u5316\u961f\u5217 */\nQueue<Integer> queue = new LinkedList<>();\n/* \u5143\u7d20\u5165\u961f */\nqueue.offer(1);\nqueue.offer(3);\nqueue.offer(2);\nqueue.offer(5);\nqueue.offer(4);\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nint peek = queue.peek();\n/* \u5143\u7d20\u51fa\u961f */\nint poll = queue.poll();\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nint size = queue.size();\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nboolean isEmpty = queue.isEmpty();\n
queue.cpp
/* \u521d\u59cb\u5316\u961f\u5217 */\nqueue<int> queue;\n/* \u5143\u7d20\u5165\u961f */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nint front = queue.front();\n/* \u5143\u7d20\u51fa\u961f */\nqueue.pop();\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nint size = queue.size();\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nbool empty = queue.empty();\n
queue.py
\"\"\" \u521d\u59cb\u5316\u961f\u5217 \"\"\"\n# \u5728 Python \u4e2d\uff0c\u6211\u4eec\u4e00\u822c\u5c06\u53cc\u5411\u961f\u5217\u7c7b deque \u770b\u4f5c\u961f\u5217\u4f7f\u7528\n# \u867d\u7136 queue.Queue() \u662f\u7eaf\u6b63\u7684\u961f\u5217\u7c7b\uff0c\u4f46\u4e0d\u592a\u597d\u7528\uff0c\u56e0\u6b64\u4e0d\u5efa\u8bae\nque = collections.deque()\n\"\"\" \u5143\u7d20\u5165\u961f \"\"\"\nque.append(1)\nque.append(3)\nque.append(2)\nque.append(5)\nque.append(4)\n\"\"\" \u8bbf\u95ee\u961f\u9996\u5143\u7d20 \"\"\"\nfront = que[0];\n\"\"\" \u5143\u7d20\u51fa\u961f \"\"\"\npop = que.popleft()\n\"\"\" \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 \"\"\"\nsize = len(que)\n\"\"\" \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a \"\"\"\nis_empty = len(que) == 0\n
queue_test.go
/* \u521d\u59cb\u5316\u961f\u5217 */\n// \u5728 Go \u4e2d\uff0c\u5c06 list \u4f5c\u4e3a\u961f\u5217\u6765\u4f7f\u7528\nqueue := list.New()\n/* \u5143\u7d20\u5165\u961f */\nqueue.PushBack(1)\nqueue.PushBack(3)\nqueue.PushBack(2)\nqueue.PushBack(5)\nqueue.PushBack(4)\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeek := queue.Front()\n/* \u5143\u7d20\u51fa\u961f */\npoll := queue.Front()\nqueue.Remove(poll)\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nsize := queue.Len()\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nisEmpty := queue.Len() == 0\n
queue.js
/* \u521d\u59cb\u5316\u961f\u5217 */\n// JavaScript \u6ca1\u6709\u5185\u7f6e\u7684\u961f\u5217\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u961f\u5217\u6765\u4f7f\u7528\nconst queue = [];\n/* \u5143\u7d20\u5165\u961f */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nconst peek = queue[0];\n/* \u5143\u7d20\u51fa\u961f */\n// \u5e95\u5c42\u662f\u6570\u7ec4\uff0c\u56e0\u6b64 shift() \u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a O(n)\nconst poll = queue.shift();\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nconst size = queue.length;\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nconst empty = queue.length === 0;\n
queue.ts
/* \u521d\u59cb\u5316\u961f\u5217 */\n// TypeScript \u6ca1\u6709\u5185\u7f6e\u7684\u961f\u5217\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u961f\u5217\u6765\u4f7f\u7528 \nconst queue: number[] = [];\n/* \u5143\u7d20\u5165\u961f */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nconst peek = queue[0];\n/* \u5143\u7d20\u51fa\u961f */\n// \u5e95\u5c42\u662f\u6570\u7ec4\uff0c\u56e0\u6b64 shift() \u65b9\u6cd5\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a O(n)\nconst poll = queue.shift();\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nconst size = queue.length;\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nconst empty = queue.length === 0;\n
queue.c
\n
queue.cs
/* \u521d\u59cb\u5316\u961f\u5217 */\nQueue<int> queue = new();\n/* \u5143\u7d20\u5165\u961f */\nqueue.Enqueue(1);\nqueue.Enqueue(3);\nqueue.Enqueue(2);\nqueue.Enqueue(5);\nqueue.Enqueue(4);\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nint peek = queue.Peek();\n/* \u5143\u7d20\u51fa\u961f */\nint poll = queue.Dequeue();\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nint size = queue.Count();\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nbool isEmpty = queue.Count() == 0;\n
queue.swift
/* \u521d\u59cb\u5316\u961f\u5217 */\n// Swift \u6ca1\u6709\u5185\u7f6e\u7684\u961f\u5217\u7c7b\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u961f\u5217\u6765\u4f7f\u7528\nvar queue: [Int] = []\n/* \u5143\u7d20\u5165\u961f */\nqueue.append(1)\nqueue.append(3)\nqueue.append(2)\nqueue.append(5)\nqueue.append(4)\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nlet peek = queue.first!\n/* \u5143\u7d20\u51fa\u961f */\n// \u4f7f\u7528 Array \u6a21\u62df\u65f6 poll \u7684\u590d\u6742\u5ea6\u4e3a O(n)\nlet pool = queue.removeFirst()\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nlet size = queue.count\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nlet isEmpty = queue.isEmpty\n
queue.zig
\n
"},{"location":"chapter_stack_and_queue/queue/#522","title":"5.2.2. \u961f\u5217\u5b9e\u73b0","text":"

\u961f\u5217\u9700\u8981\u4e00\u79cd\u53ef\u4ee5\u5728\u4e00\u7aef\u6dfb\u52a0\uff0c\u5e76\u5728\u53e6\u4e00\u7aef\u5220\u9664\u7684\u6570\u636e\u7ed3\u6784\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u94fe\u8868\u6216\u6570\u7ec4\u6765\u5b9e\u73b0\u3002

"},{"location":"chapter_stack_and_queue/queue/#_1","title":"\u57fa\u4e8e\u94fe\u8868\u7684\u5b9e\u73b0","text":"

\u6211\u4eec\u5c06\u94fe\u8868\u7684\u300c\u5934\u7ed3\u70b9\u300d\u548c\u300c\u5c3e\u7ed3\u70b9\u300d\u5206\u522b\u770b\u4f5c\u662f\u961f\u9996\u548c\u961f\u5c3e\uff0c\u5e76\u89c4\u5b9a\u961f\u5c3e\u53ea\u53ef\u6dfb\u52a0\u7ed3\u70b9\uff0c\u961f\u9996\u53ea\u53ef\u5220\u9664\u7ed3\u70b9\u3002

LinkedListQueuepush()poll()

\u4ee5\u4e0b\u662f\u4f7f\u7528\u94fe\u8868\u5b9e\u73b0\u961f\u5217\u7684\u793a\u4f8b\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linkedlist_queue.java
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\nclass LinkedListQueue {\nprivate ListNode front, rear;  // \u5934\u7ed3\u70b9 front \uff0c\u5c3e\u7ed3\u70b9 rear \nprivate int queSize = 0;\npublic LinkedListQueue() {\nfront = null;\nrear = null;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\npublic int size() {\nreturn queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\npublic boolean isEmpty() {\nreturn size() == 0;\n}\n/* \u5165\u961f */\npublic void push(int num) {\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nListNode node = new ListNode(num);\n// \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif (front == null) {\nfront = node;\nrear = node;\n// \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\n} else {\nrear.next = node;\nrear = node;\n}\nqueSize++;\n}\n/* \u51fa\u961f */\npublic int poll() {\nint num = peek();\n// \u5220\u9664\u5934\u7ed3\u70b9\nfront = front.next;\nqueSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npublic int peek() {\nif (size() == 0)\nthrow new EmptyStackException();\nreturn front.val;\n}\n/* \u5c06\u94fe\u8868\u8f6c\u5316\u4e3a Array \u5e76\u8fd4\u56de */\npublic int[] toArray() {\nListNode node = front;\nint[] res = new int[size()];\nfor (int i = 0; i < res.length; i++) {\nres[i] = node.val;\nnode = node.next;\n}\nreturn res;\n}\n}\n
linkedlist_queue.cpp
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\nclass LinkedListQueue {\nprivate:\nListNode *front, *rear;  // \u5934\u7ed3\u70b9 front \uff0c\u5c3e\u7ed3\u70b9 rear \nint queSize;\npublic:\nLinkedListQueue() {\nfront = nullptr;\nrear = nullptr;\nqueSize = 0;\n}\n~LinkedListQueue() {\ndelete front;\ndelete rear;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nint size() {\nreturn queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nbool empty() {\nreturn queSize == 0;\n}\n/* \u5165\u961f */\nvoid push(int num) {\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nListNode* node = new ListNode(num);\n// \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif (front == nullptr) {\nfront = node;\nrear = node;\n}\n// \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\nelse {\nrear->next = node;\nrear = node;\n}\nqueSize++;\n}\n/* \u51fa\u961f */\nvoid poll() {\nint num = peek();\n// \u5220\u9664\u5934\u7ed3\u70b9\nListNode *tmp = front;\nfront = front->next;\n// \u91ca\u653e\u5185\u5b58\ndelete tmp; queSize--;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nint peek() {\nif (size() == 0)\nthrow out_of_range(\"\u961f\u5217\u4e3a\u7a7a\");\nreturn front->val;\n}\n};\n
linkedlist_queue.py
\"\"\" \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 \"\"\"\nclass LinkedListQueue:\ndef __init__(self):\nself.__front = None  # \u5934\u7ed3\u70b9 front\nself.__rear = None   # \u5c3e\u7ed3\u70b9 rear\nself.__size = 0\n\"\"\" \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 \"\"\"\ndef size(self):\nreturn self.__size\n\"\"\" \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a \"\"\"\ndef is_empty(self):\nreturn not self.__front\n\"\"\" \u5165\u961f \"\"\"\ndef push(self, num):\n# \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nnode = ListNode(num)\n# \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif self.__front is None:\nself.__front = node\nself.__rear = node\n# \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\nelse:\nself.__rear.next = node\nself.__rear = node\nself.__size += 1\n\"\"\" \u51fa\u961f \"\"\"\ndef poll(self):\nnum = self.peek()\n# \u5220\u9664\u5934\u7ed3\u70b9\nself.__front = self.__front.next\nself.__size -= 1\nreturn num\n\"\"\" \u8bbf\u95ee\u961f\u9996\u5143\u7d20 \"\"\"\ndef peek(self):\nif self.size() == 0:\nprint(\"\u961f\u5217\u4e3a\u7a7a\")\nreturn False\nreturn self.__front.val\n\"\"\" \u8f6c\u5316\u4e3a\u5217\u8868\u7528\u4e8e\u6253\u5370 \"\"\"\ndef to_list(self):\nqueue = []\ntemp = self.__front\nwhile temp:\nqueue.append(temp.val)\ntemp = temp.next\nreturn queue\n
linkedlist_queue.go
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\ntype linkedListQueue struct {\n// \u4f7f\u7528\u5185\u7f6e\u5305 list \u6765\u5b9e\u73b0\u961f\u5217\ndata *list.List\n}\n// newLinkedListQueue \u521d\u59cb\u5316\u94fe\u8868\nfunc newLinkedListQueue() *linkedListQueue {\nreturn &linkedListQueue{\ndata: list.New(),\n}\n}\n// push \u5165\u961f\nfunc (s *linkedListQueue) push(value any) {\ns.data.PushBack(value)\n}\n// poll \u51fa\u961f\nfunc (s *linkedListQueue) poll() any {\nif s.isEmpty() {\nreturn nil\n}\ne := s.data.Front()\ns.data.Remove(e)\nreturn e.Value\n}\n// peek \u8bbf\u95ee\u961f\u9996\u5143\u7d20\nfunc (s *linkedListQueue) peek() any {\nif s.isEmpty() {\nreturn nil\n}\ne := s.data.Front()\nreturn e.Value\n}\n// size \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6\nfunc (s *linkedListQueue) size() int {\nreturn s.data.Len()\n}\n// isEmpty \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a\nfunc (s *linkedListQueue) isEmpty() bool {\nreturn s.data.Len() == 0\n}\n
linkedlist_queue.js
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\nclass LinkedListQueue {\n#front;  // \u5934\u7ed3\u70b9 #front\n#rear;   // \u5c3e\u7ed3\u70b9 #rear\n#queSize = 0;\nconstructor() {\nthis.#front = null;\nthis.#rear = null;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nget size() {\nreturn this.#queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nisEmpty() {\nreturn this.size === 0;\n}\n/* \u5165\u961f */\npush(num) {\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nconst node = new ListNode(num);\n// \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif (!this.#front) {\nthis.#front = node;\nthis.#rear = node;\n// \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\n} else {\nthis.#rear.next = node;\nthis.#rear = node;\n}\nthis.#queSize++;\n}\n/* \u51fa\u961f */\npoll() {\nconst num = this.peek();\n// \u5220\u9664\u5934\u7ed3\u70b9\nthis.#front = this.#front.next;\nthis.#queSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeek() {\nif (this.size === 0)\nthrow new Error(\"\u961f\u5217\u4e3a\u7a7a\");\nreturn this.#front.val;\n}\n}\n
linkedlist_queue.ts
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\nclass LinkedListQueue {\nprivate front: ListNode | null; // \u5934\u7ed3\u70b9 front\nprivate rear: ListNode | null;  // \u5c3e\u7ed3\u70b9 rear\nprivate queSize: number = 0;\nconstructor() {\nthis.front = null;\nthis.rear = null;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nget size(): number {\nreturn this.queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nisEmpty(): boolean {\nreturn this.size === 0;\n}\n/* \u5165\u961f */\npush(num: number): void {\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nconst node = new ListNode(num);\n// \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif (!this.front) {\nthis.front = node;\nthis.rear = node;\n// \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\n} else {\nthis.rear!.next = node;\nthis.rear = node;\n}\nthis.queSize++;\n}\n/* \u51fa\u961f */\npoll(): number {\nconst num = this.peek();\nif (!this.front)\nthrow new Error(\"\u961f\u5217\u4e3a\u7a7a\")\n// \u5220\u9664\u5934\u7ed3\u70b9\nthis.front = this.front.next;\nthis.queSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeek(): number {\nif (this.size === 0)\nthrow new Error(\"\u961f\u5217\u4e3a\u7a7a\");\nreturn this.front!.val;\n}\n}\n
linkedlist_queue.c
\n
linkedlist_queue.cs
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\nclass LinkedListQueue\n{\nprivate ListNode? front, rear;  // \u5934\u7ed3\u70b9 front \uff0c\u5c3e\u7ed3\u70b9 rear \nprivate int queSize = 0;\npublic LinkedListQueue()\n{\nfront = null;\nrear = null;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\npublic int size()\n{\nreturn queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\npublic bool isEmpty()\n{\nreturn size() == 0;\n}\n/* \u5165\u961f */\npublic void push(int num)\n{\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nListNode node = new ListNode(num);\n// \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif (front == null)\n{\nfront = node;\nrear = node;\n// \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\n}\nelse if (rear != null)\n{\nrear.next = node;\nrear = node;\n}\nqueSize++;\n}\n/* \u51fa\u961f */\npublic int poll()\n{\nint num = peek();\n// \u5220\u9664\u5934\u7ed3\u70b9\nfront = front?.next;\nqueSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npublic int peek()\n{\nif (size() == 0 || front == null)\nthrow new Exception();\nreturn front.val;\n}\n}\n
linkedlist_queue.swift
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u961f\u5217 */\nclass LinkedListQueue {\nprivate var front: ListNode? // \u5934\u7ed3\u70b9\nprivate var rear: ListNode? // \u5c3e\u7ed3\u70b9\nprivate var _size = 0\ninit() {}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nfunc size() -> Int {\n_size\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nfunc isEmpty() -> Bool {\nsize() == 0\n}\n/* \u5165\u961f */\nfunc push(num: Int) {\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nlet node = ListNode(x: num)\n// \u5982\u679c\u961f\u5217\u4e3a\u7a7a\uff0c\u5219\u4ee4\u5934\u3001\u5c3e\u7ed3\u70b9\u90fd\u6307\u5411\u8be5\u7ed3\u70b9\nif front == nil {\nfront = node\nrear = node\n}\n// \u5982\u679c\u961f\u5217\u4e0d\u4e3a\u7a7a\uff0c\u5219\u5c06\u8be5\u7ed3\u70b9\u6dfb\u52a0\u5230\u5c3e\u7ed3\u70b9\u540e\nelse {\nrear?.next = node\nrear = node\n}\n_size += 1\n}\n/* \u51fa\u961f */\n@discardableResult\nfunc poll() -> Int {\nlet num = peek()\n// \u5220\u9664\u5934\u7ed3\u70b9\nfront = front?.next\n_size -= 1\nreturn num\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nfunc peek() -> Int {\nif isEmpty() {\nfatalError(\"\u961f\u5217\u4e3a\u7a7a\")\n}\nreturn front!.val\n}\n}\n
linkedlist_queue.zig
\n
"},{"location":"chapter_stack_and_queue/queue/#_2","title":"\u57fa\u4e8e\u6570\u7ec4\u7684\u5b9e\u73b0","text":"

\u6570\u7ec4\u7684\u5220\u9664\u9996\u5143\u7d20\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \uff0c\u56e0\u6b64\u4e0d\u9002\u5408\u76f4\u63a5\u7528\u6765\u5b9e\u73b0\u961f\u5217\u3002\u7136\u800c\uff0c\u6211\u4eec\u53ef\u4ee5\u501f\u52a9\u4e24\u4e2a\u6307\u9488 front , rear \u6765\u5206\u522b\u8bb0\u5f55\u961f\u9996\u548c\u961f\u5c3e\u7684\u7d22\u5f15\u4f4d\u7f6e\uff0c\u5728\u5165\u961f / \u51fa\u961f\u65f6\u5206\u522b\u5c06 front / rear \u5411\u540e\u79fb\u52a8\u4e00\u4f4d\u5373\u53ef\uff0c\u8fd9\u6837\u6bcf\u6b21\u4ec5\u9700\u64cd\u4f5c\u4e00\u4e2a\u5143\u7d20\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u964d\u81f3 \\(O(1)\\) \u3002

ArrayQueuepush()poll()

\u7ec6\u5fc3\u7684\u540c\u5b66\u53ef\u80fd\u4f1a\u53d1\u73b0\u4e00\u4e2a\u95ee\u9898\uff0c\u5373\u5728\u5165\u961f\u4e0e\u51fa\u961f\u7684\u8fc7\u7a0b\u4e2d\uff0c\u4e24\u4e2a\u6307\u9488\u90fd\u5728\u5411\u540e\u79fb\u52a8\uff0c\u5728\u5230\u8fbe\u5c3e\u90e8\u540e\u5219\u65e0\u6cd5\u7ee7\u7eed\u79fb\u52a8\u4e86\u3002

\u4e3a\u4e86\u89e3\u51b3\u6b64\u95ee\u9898\uff0c\u6211\u4eec\u53ef\u4ee5\u91c7\u53d6\u4e00\u4e2a\u53d6\u5de7\u65b9\u6848\uff0c\u5373\u5c06\u6570\u7ec4\u770b\u4f5c\u662f\u201c\u73af\u5f62\u201d\u7684\u3002\u5177\u4f53\u505a\u6cd5\u662f\u89c4\u5b9a\u6307\u9488\u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\uff0c\u518d\u6b21\u56de\u5230\u5934\u90e8\u63a5\u7eed\u904d\u5386\uff0c\u8fd9\u6837\u76f8\u5f53\u4e8e\u4f7f\u6570\u7ec4\u201c\u9996\u5c3e\u76f8\u8fde\u201d\u4e86\u3002\u5728\u73af\u5f62\u6570\u7ec4\u7684\u8bbe\u5b9a\u4e0b\uff0c\u83b7\u53d6\u957f\u5ea6 size() \u3001\u5165\u961f push() \u3001\u51fa\u961f poll() \u65b9\u6cd5\u90fd\u9700\u8981\u505a\u76f8\u5e94\u7684\u53d6\u4f59\u64cd\u4f5c\u5904\u7406\uff0c\u4f7f\u5f97\u5f53\u5c3e\u6307\u9488\u7ed5\u56de\u6570\u7ec4\u5934\u90e8\u65f6\uff0c\u4ecd\u7136\u53ef\u4ee5\u6b63\u786e\u5904\u7406\u64cd\u4f5c\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array_queue.java
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\nclass ArrayQueue {\nprivate int[] nums;  // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nprivate int front;   // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nprivate int queSize; // \u961f\u5217\u957f\u5ea6\npublic ArrayQueue(int capacity) {\nnums = new int[capacity];\nfront = queSize = 0;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf */\npublic int capacity() {\nreturn nums.length;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\npublic int size() {\nreturn queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\npublic boolean isEmpty() {\nreturn queSize == 0;\n}\n/* \u5165\u961f */\npublic void push(int num) {\nif (queSize == capacity()) {\nSystem.out.println(\"\u961f\u5217\u5df2\u6ee1\");\nreturn;\n}\n// \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nint rear = (front + queSize) % capacity();\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nnums[rear] = num;\nqueSize++;\n}\n/* \u51fa\u961f */\npublic int poll() {\nint num = peek();\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nfront = (front + 1) % capacity();\nqueSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npublic int peek() {\nif (isEmpty())\nthrow new EmptyStackException();\nreturn nums[front];\n}\n/* \u8fd4\u56de\u6570\u7ec4 */\npublic int[] toArray() {\n// \u4ec5\u8f6c\u6362\u6709\u6548\u957f\u5ea6\u8303\u56f4\u5185\u7684\u5217\u8868\u5143\u7d20\nint[] res = new int[queSize];\nfor (int i = 0, j = front; i < queSize; i++, j++) {\nres[i] = nums[j % capacity()];\n}\nreturn res;\n}\n}\n
array_queue.cpp
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\nclass ArrayQueue {\nprivate:\nint *nums;       // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nint front;       // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nint queSize;     // \u961f\u5217\u957f\u5ea6\nint queCapacity; // \u961f\u5217\u5bb9\u91cf\npublic:\nArrayQueue(int capacity) {\n// \u521d\u59cb\u5316\u6570\u7ec4\nnums = new int[capacity];\nqueCapacity = capacity;\nfront = queSize = 0;\n}\n~ArrayQueue() {\ndelete[] nums;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf */\nint capacity() {\nreturn queCapacity;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nint size() {\nreturn queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nbool empty() {\nreturn size() == 0;\n}\n/* \u5165\u961f */\nvoid push(int num) {\nif (queSize == queCapacity) {\ncout << \"\u961f\u5217\u5df2\u6ee1\" << endl;\nreturn;\n}\n// \u8ba1\u7b97\u961f\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nint rear = (front + queSize) % queCapacity;\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nnums[rear] = num;\nqueSize++;\n}\n/* \u51fa\u961f */\nvoid poll() {\nint num = peek();\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nfront = (front + 1) % queCapacity;\nqueSize--;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nint peek() {\nif (empty())\nthrow out_of_range(\"\u961f\u5217\u4e3a\u7a7a\");\nreturn nums[front];\n}\n};\n
array_queue.py
\"\"\" \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 \"\"\"\nclass ArrayQueue:\ndef __init__(self, size):\nself.__nums = [0] * size  # \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nself.__front = 0          # \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nself.__size = 0           # \u961f\u5217\u957f\u5ea6\n\"\"\" \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf \"\"\"\ndef capacity(self):\nreturn len(self.__nums)\n\"\"\" \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 \"\"\"\ndef size(self):\nreturn self.__size\n\"\"\" \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a \"\"\"\ndef is_empty(self):\nreturn self.__size == 0\n\"\"\" \u5165\u961f \"\"\"\ndef push(self, num):\nassert self.__size < self.capacity(), \"\u961f\u5217\u5df2\u6ee1\"\n# \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n# \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nrear = (self.__front + self.__size) % self.capacity()\n# \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nself.__nums[rear] = num\nself.__size += 1\n\"\"\" \u51fa\u961f \"\"\"\ndef poll(self):\nnum = self.peek()\n# \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nself.__front = (self.__front + 1) % self.capacity()\nself.__size -= 1\nreturn num\n\"\"\" \u8bbf\u95ee\u961f\u9996\u5143\u7d20 \"\"\"\ndef peek(self):\nassert not self.is_empty(), \"\u961f\u5217\u4e3a\u7a7a\"\nreturn self.__nums[self.__front]\n\"\"\" \u8fd4\u56de\u5217\u8868\u7528\u4e8e\u6253\u5370 \"\"\"\ndef to_list(self):\nres = [0] * self.size()\nj = self.__front\nfor i in range(self.size()):\nres[i] = self.__nums[(j % self.capacity())]\nj += 1\nreturn res\n
array_queue.go
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\ntype arrayQueue struct {\nnums        []int // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nfront       int   // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nqueSize     int   // \u961f\u5217\u957f\u5ea6\nqueCapacity int   // \u961f\u5217\u5bb9\u91cf\uff08\u5373\u6700\u5927\u5bb9\u7eb3\u5143\u7d20\u6570\u91cf\uff09\n}\n// newArrayQueue \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217\nfunc newArrayQueue(queCapacity int) *arrayQueue {\nreturn &arrayQueue{\nnums:        make([]int, queCapacity),\nqueCapacity: queCapacity,\nfront:       0,\nqueSize:     0,\n}\n}\n// size \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6\nfunc (q *arrayQueue) size() int {\nreturn q.queSize\n}\n// isEmpty \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a\nfunc (q *arrayQueue) isEmpty() bool {\nreturn q.queSize == 0\n}\n// push \u5165\u961f\nfunc (q *arrayQueue) push(num int) {\n// \u5f53 rear == queCapacity \u8868\u793a\u961f\u5217\u5df2\u6ee1\nif q.queSize == q.queCapacity {\nreturn\n}\n// \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nrear := (q.front + q.queSize) % q.queCapacity\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nq.nums[rear] = num\nq.queSize++\n}\n// poll \u51fa\u961f\nfunc (q *arrayQueue) poll() any {\nnum := q.peek()\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nq.front = (q.front + 1) % q.queCapacity\nq.queSize--\nreturn num\n}\n// peek \u8bbf\u95ee\u961f\u9996\u5143\u7d20\nfunc (q *arrayQueue) peek() any {\nif q.isEmpty() {\nreturn nil\n}\nreturn q.nums[q.front]\n}\n// \u83b7\u53d6 Slice \u7528\u4e8e\u6253\u5370\nfunc (q *arrayQueue) toSlice() []int {\nrear := (q.front + q.queSize)\nif rear >= q.queCapacity {\nrear %= q.queCapacity\nreturn append(q.nums[q.front:], q.nums[:rear]...)\n}\nreturn q.nums[q.front:rear]\n}\n
array_queue.js
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\nclass ArrayQueue {\n#nums;         // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\n#front = 0;    // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\n#queSize = 0;  // \u961f\u5217\u957f\u5ea6\nconstructor(capacity) {\nthis.#nums = new Array(capacity);\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf */\nget capacity() {\nreturn this.#nums.length;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nget size() {\nreturn this.#queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nempty() {\nreturn this.#queSize == 0;\n}\n/* \u5165\u961f */\npush(num) {\nif (this.size == this.capacity) {\nconsole.log(\"\u961f\u5217\u5df2\u6ee1\");\nreturn;\n}\n// \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nconst rear = (this.#front + this.size) % this.capacity;\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nthis.#nums[rear] = num;\nthis.#queSize++;\n}\n/* \u51fa\u961f */\npoll() {\nconst num = this.peek();\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nthis.#front = (this.#front + 1) % this.capacity;\nthis.#queSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeek() {\nif (this.empty())\nthrow new Error(\"\u961f\u5217\u4e3a\u7a7a\");\nreturn this.#nums[this.#front];\n}\n}\n
array_queue.ts
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\nclass ArrayQueue {\nprivate nums: number[];  // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nprivate front: number;   // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nprivate queSize: number; // \u961f\u5217\u957f\u5ea6\nconstructor(capacity: number) {\nthis.nums = new Array(capacity);\nthis.front = this.queSize = 0;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf */\nget capacity(): number {\nreturn this.nums.length;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nget size(): number {\nreturn this.queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nempty(): boolean {\nreturn this.queSize == 0;\n}\n/* \u5165\u961f */\npush(num: number): void {\nif (this.size == this.capacity) {\nconsole.log(\"\u961f\u5217\u5df2\u6ee1\");\nreturn;\n}\n// \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nconst rear = (this.front + this.queSize) % this.capacity;\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nthis.nums[rear] = num;\nthis.queSize++;\n}\n/* \u51fa\u961f */\npoll(): number {\nconst num = this.peek();\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nthis.front = (this.front + 1) % this.capacity;\nthis.queSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npeek(): number {\nif (this.empty())\nthrow new Error(\"\u961f\u5217\u4e3a\u7a7a\");\nreturn this.nums[this.front];\n}\n}\n
array_queue.c
\n
array_queue.cs
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\nclass ArrayQueue\n{\nprivate int[] nums;  // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nprivate int front;   // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nprivate int queSize; // \u961f\u5217\u957f\u5ea6\npublic ArrayQueue(int capacity)\n{\nnums = new int[capacity];\nfront = queSize = 0;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf */\npublic int capacity()\n{\nreturn nums.Length;\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\npublic int size()\n{\nreturn queSize;\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\npublic bool isEmpty()\n{\nreturn queSize == 0;\n}\n/* \u5165\u961f */\npublic void push(int num)\n{\nif (queSize == capacity())\n{\nConsole.WriteLine(\"\u961f\u5217\u5df2\u6ee1\");\nreturn;\n}\n// \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nint rear = (front + queSize) % capacity();\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nnums[rear] = num;\nqueSize++;\n}\n/* \u51fa\u961f */\npublic int poll()\n{\nint num = peek();\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nfront = (front + 1) % capacity();\nqueSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\npublic int peek()\n{\nif (isEmpty())\nthrow new Exception();\nreturn nums[front];\n}\n}\n
array_queue.swift
/* \u57fa\u4e8e\u73af\u5f62\u6570\u7ec4\u5b9e\u73b0\u7684\u961f\u5217 */\nclass ArrayQueue {\nprivate var nums: [Int] // \u7528\u4e8e\u5b58\u50a8\u961f\u5217\u5143\u7d20\u7684\u6570\u7ec4\nprivate var front = 0 // \u961f\u9996\u6307\u9488\uff0c\u6307\u5411\u961f\u9996\u5143\u7d20\nprivate var queSize = 0 // \u961f\u5217\u957f\u5ea6\ninit(capacity: Int) {\n// \u521d\u59cb\u5316\u6570\u7ec4\nnums = Array(repeating: 0, count: capacity)\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u5bb9\u91cf */\nfunc capacity() -> Int {\nnums.count\n}\n/* \u83b7\u53d6\u961f\u5217\u7684\u957f\u5ea6 */\nfunc size() -> Int {\nqueSize\n}\n/* \u5224\u65ad\u961f\u5217\u662f\u5426\u4e3a\u7a7a */\nfunc isEmpty() -> Bool {\nqueSize == 0\n}\n/* \u5165\u961f */\nfunc push(num: Int) {\nif size() == capacity() {\nprint(\"\u961f\u5217\u5df2\u6ee1\")\nreturn\n}\n// \u8ba1\u7b97\u5c3e\u6307\u9488\uff0c\u6307\u5411\u961f\u5c3e\u7d22\u5f15 + 1\n// \u901a\u8fc7\u53d6\u4f59\u64cd\u4f5c\uff0c\u5b9e\u73b0 rear \u8d8a\u8fc7\u6570\u7ec4\u5c3e\u90e8\u540e\u56de\u5230\u5934\u90e8\nlet rear = (front + queSize) % capacity()\n// \u5c3e\u7ed3\u70b9\u540e\u6dfb\u52a0 num\nnums[rear] = num\nqueSize += 1\n}\n/* \u51fa\u961f */\n@discardableResult\nfunc poll() -> Int {\nlet num = peek()\n// \u961f\u9996\u6307\u9488\u5411\u540e\u79fb\u52a8\u4e00\u4f4d\uff0c\u82e5\u8d8a\u8fc7\u5c3e\u90e8\u5219\u8fd4\u56de\u5230\u6570\u7ec4\u5934\u90e8\nfront = (front + 1) % capacity()\nqueSize -= 1\nreturn num\n}\n/* \u8bbf\u95ee\u961f\u9996\u5143\u7d20 */\nfunc peek() -> Int {\nif isEmpty() {\nfatalError(\"\u961f\u5217\u4e3a\u7a7a\")\n}\nreturn nums[front]\n}\n}\n
array_queue.zig
\n

\u4ee5\u4e0a\u4ee3\u7801\u4ecd\u5b58\u5728\u5c40\u9650\u6027\uff0c\u5373\u957f\u5ea6\u4e0d\u53ef\u53d8\u3002\u7136\u800c\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5c06\u6570\u7ec4\u66ff\u6362\u4e3a\u5217\u8868\uff08\u5373\u52a8\u6001\u6570\u7ec4\uff09\u6765\u5f15\u5165\u6269\u5bb9\u673a\u5236\uff0c\u6709\u5174\u8da3\u7684\u540c\u5b66\u53ef\u4ee5\u5c1d\u8bd5\u5b9e\u73b0\u3002

"},{"location":"chapter_stack_and_queue/queue/#523","title":"5.2.3. \u4e24\u79cd\u5b9e\u73b0\u5bf9\u6bd4","text":"

\u4e0e\u6808\u7684\u7ed3\u8bba\u4e00\u81f4\uff0c\u5728\u6b64\u4e0d\u518d\u8d58\u8ff0\u3002

"},{"location":"chapter_stack_and_queue/queue/#524","title":"5.2.4. \u961f\u5217\u5178\u578b\u5e94\u7528","text":"
  • \u6dd8\u5b9d\u8ba2\u5355\u3002\u8d2d\u7269\u8005\u4e0b\u5355\u540e\uff0c\u8ba2\u5355\u5c31\u88ab\u52a0\u5165\u5230\u961f\u5217\u4e4b\u4e2d\uff0c\u968f\u540e\u7cfb\u7edf\u518d\u6839\u636e\u987a\u5e8f\u4f9d\u6b21\u5904\u7406\u961f\u5217\u4e2d\u7684\u8ba2\u5355\u3002\u5728\u53cc\u5341\u4e00\u65f6\uff0c\u5728\u77ed\u65f6\u95f4\u5185\u4f1a\u4ea7\u751f\u6d77\u91cf\u7684\u8ba2\u5355\uff0c\u5982\u4f55\u5904\u7406\u300c\u9ad8\u5e76\u53d1\u300d\u5219\u662f\u5de5\u7a0b\u5e08\u4eec\u9700\u8981\u91cd\u70b9\u601d\u8003\u7684\u95ee\u9898\u3002
  • \u5404\u79cd\u5f85\u529e\u4e8b\u9879\u3002\u4f8b\u5982\u6253\u5370\u673a\u7684\u4efb\u52a1\u961f\u5217\u3001\u9910\u5385\u7684\u51fa\u9910\u961f\u5217\u7b49\u7b49\u3002
"},{"location":"chapter_stack_and_queue/stack/","title":"5.1. \u6808","text":"

\u300c\u6808 Stack\u300d\u662f\u4e00\u79cd\u9075\u5faa\u300c\u5148\u5165\u540e\u51fa first in, last out\u300d\u6570\u636e\u64cd\u4f5c\u89c4\u5219\u7684\u7ebf\u6027\u6570\u636e\u7ed3\u6784\u3002\u6211\u4eec\u53ef\u4ee5\u5c06\u6808\u7c7b\u6bd4\u4e3a\u653e\u5728\u684c\u9762\u4e0a\u7684\u4e00\u645e\u76d8\u5b50\uff0c\u5982\u679c\u9700\u8981\u62ff\u51fa\u5e95\u90e8\u7684\u76d8\u5b50\uff0c\u5219\u9700\u8981\u5148\u5c06\u4e0a\u9762\u7684\u76d8\u5b50\u4f9d\u6b21\u53d6\u51fa\u3002

\u201c\u76d8\u5b50\u201d\u662f\u4e00\u79cd\u5f62\u8c61\u6bd4\u55bb\uff0c\u6211\u4eec\u5c06\u76d8\u5b50\u66ff\u6362\u4e3a\u4efb\u610f\u4e00\u79cd\u5143\u7d20\uff08\u4f8b\u5982\u6574\u6570\u3001\u5b57\u7b26\u3001\u5bf9\u8c61\u7b49\uff09\uff0c\u5c31\u5f97\u5230\u4e86\u6808\u6570\u636e\u7ed3\u6784\u3002

\u6211\u4eec\u5c06\u8fd9\u4e00\u645e\u5143\u7d20\u7684\u9876\u90e8\u79f0\u4e3a\u300c\u6808\u9876\u300d\uff0c\u5c06\u5e95\u90e8\u79f0\u4e3a\u300c\u6808\u5e95\u300d\uff0c\u5c06\u628a\u5143\u7d20\u6dfb\u52a0\u5230\u6808\u9876\u7684\u64cd\u4f5c\u79f0\u4e3a\u300c\u5165\u6808\u300d\uff0c\u5c06\u5220\u9664\u6808\u9876\u5143\u7d20\u7684\u64cd\u4f5c\u79f0\u4e3a\u300c\u51fa\u6808\u300d\u3002

Fig. \u6808\u7684\u5148\u5165\u540e\u51fa\u7279\u6027

"},{"location":"chapter_stack_and_queue/stack/#511","title":"5.1.1. \u6808\u5e38\u7528\u64cd\u4f5c","text":"

\u6808\u7684\u5e38\u7528\u64cd\u4f5c\u89c1\u4e0b\u8868\uff08\u65b9\u6cd5\u547d\u540d\u4ee5 Java \u4e3a\u4f8b\uff09\u3002

Table. \u6808\u7684\u5e38\u7528\u64cd\u4f5c

\u65b9\u6cd5 \u63cf\u8ff0 \u65f6\u95f4\u590d\u6742\u5ea6 push() \u5143\u7d20\u5165\u6808\uff08\u6dfb\u52a0\u81f3\u6808\u9876\uff09 \\(O(1)\\) pop() \u6808\u9876\u5143\u7d20\u51fa\u6808 \\(O(1)\\) peek() \u8bbf\u95ee\u6808\u9876\u5143\u7d20 \\(O(1)\\) size() \u83b7\u53d6\u6808\u7684\u957f\u5ea6 \\(O(1)\\) isEmpty() \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a \\(O(1)\\)

\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u7f16\u7a0b\u8bed\u8a00\u5b9e\u73b0\u597d\u7684\u6808\u7c7b\u3002 \u67d0\u4e9b\u8bed\u8a00\u5e76\u672a\u4e13\u95e8\u63d0\u4f9b\u6808\u7c7b\uff0c\u4f46\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u628a\u8be5\u8bed\u8a00\u7684\u300c\u6570\u7ec4\u300d\u6216\u300c\u94fe\u8868\u300d\u770b\u4f5c\u6808\u6765\u4f7f\u7528\uff0c\u5e76\u901a\u8fc7\u201c\u8111\u8865\u201d\u6765\u5c4f\u853d\u65e0\u5173\u64cd\u4f5c\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig stack.java
/* \u521d\u59cb\u5316\u6808 */\n// \u5728 Java \u4e2d\uff0c\u63a8\u8350\u5c06 ArrayList \u5f53\u4f5c\u6808\u6765\u4f7f\u7528\nList<Integer> stack = new ArrayList<>();\n/* \u5143\u7d20\u5165\u6808 */\nstack.add(1);\nstack.add(3);\nstack.add(2);\nstack.add(5);\nstack.add(4);\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nint peek = stack.get(stack.size() - 1);\n/* \u5143\u7d20\u51fa\u6808 */\nint pop = stack.remove(stack.size() - 1);\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nint size = stack.size();\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nboolean isEmpty = stack.isEmpty();\n
stack.cpp
/* \u521d\u59cb\u5316\u6808 */\nstack<int> stack;\n/* \u5143\u7d20\u5165\u6808 */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nint top = stack.top();\n/* \u5143\u7d20\u51fa\u6808 */\nstack.pop();\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nint size = stack.size();\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nbool empty = stack.empty();\n
stack.py
\"\"\" \u521d\u59cb\u5316\u6808 \"\"\"\n# Python \u6ca1\u6709\u5185\u7f6e\u7684\u6808\u7c7b\uff0c\u53ef\u4ee5\u628a List \u5f53\u4f5c\u6808\u6765\u4f7f\u7528 \nstack = []\n\"\"\" \u5143\u7d20\u5165\u6808 \"\"\"\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\"\"\" \u8bbf\u95ee\u6808\u9876\u5143\u7d20 \"\"\"\npeek = stack[-1]\n\"\"\" \u5143\u7d20\u51fa\u6808 \"\"\"\npop = stack.pop()\n\"\"\" \u83b7\u53d6\u6808\u7684\u957f\u5ea6 \"\"\"\nsize = len(stack)\n\"\"\" \u5224\u65ad\u662f\u5426\u4e3a\u7a7a \"\"\"\nis_empty = len(stack) == 0\n
stack_test.go
/* \u521d\u59cb\u5316\u6808 */\n// \u5728 Go \u4e2d\uff0c\u63a8\u8350\u5c06 Slice \u5f53\u4f5c\u6808\u6765\u4f7f\u7528\nvar stack []int\n/* \u5143\u7d20\u5165\u6808 */\nstack = append(stack, 1)\nstack = append(stack, 3)\nstack = append(stack, 2)\nstack = append(stack, 5)\nstack = append(stack, 4)\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npeek := stack[len(stack)-1]\n/* \u5143\u7d20\u51fa\u6808 */\npop := stack[len(stack)-1]\nstack = stack[:len(stack)-1]\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nsize := len(stack)\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nisEmpty := len(stack) == 0\n
stack.js
/* \u521d\u59cb\u5316\u6808 */\n// Javascript \u6ca1\u6709\u5185\u7f6e\u7684\u6808\u7c7b\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u6808\u6765\u4f7f\u7528 \nconst stack = [];\n/* \u5143\u7d20\u5165\u6808 */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nconst peek = stack[stack.length-1];\n/* \u5143\u7d20\u51fa\u6808 */\nconst pop = stack.pop();\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nconst size = stack.length;\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nconst is_empty = stack.length === 0;\n
stack.ts
/* \u521d\u59cb\u5316\u6808 */\n// Typescript \u6ca1\u6709\u5185\u7f6e\u7684\u6808\u7c7b\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u6808\u6765\u4f7f\u7528 \nconst stack: number[] = [];\n/* \u5143\u7d20\u5165\u6808 */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nconst peek = stack[stack.length - 1];\n/* \u5143\u7d20\u51fa\u6808 */\nconst pop = stack.pop();\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nconst size = stack.length;\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nconst is_empty = stack.length === 0;\n
stack.c
\n
stack.cs
/* \u521d\u59cb\u5316\u6808 */\nStack<int> stack = new ();\n/* \u5143\u7d20\u5165\u6808 */\nstack.Push(1);\nstack.Push(3);\nstack.Push(2);\nstack.Push(5);\nstack.Push(4);\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nint peek = stack.Peek();\n/* \u5143\u7d20\u51fa\u6808 */\nint pop = stack.Pop();\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nint size = stack.Count();\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nbool isEmpty = stack.Count()==0;\n
stack.swift
/* \u521d\u59cb\u5316\u6808 */\n// Swift \u6ca1\u6709\u5185\u7f6e\u7684\u6808\u7c7b\uff0c\u53ef\u4ee5\u628a Array \u5f53\u4f5c\u6808\u6765\u4f7f\u7528\nvar stack: [Int] = []\n/* \u5143\u7d20\u5165\u6808 */\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nlet peek = stack.last!\n/* \u5143\u7d20\u51fa\u6808 */\nlet pop = stack.removeLast()\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nlet size = stack.count\n/* \u5224\u65ad\u662f\u5426\u4e3a\u7a7a */\nlet isEmpty = stack.isEmpty\n
stack.zig
\n
"},{"location":"chapter_stack_and_queue/stack/#512","title":"5.1.2. \u6808\u7684\u5b9e\u73b0","text":"

\u4e3a\u4e86\u66f4\u52a0\u6e05\u6670\u5730\u4e86\u89e3\u6808\u7684\u8fd0\u884c\u673a\u5236\uff0c\u63a5\u4e0b\u6765\u6211\u4eec\u6765\u81ea\u5df1\u52a8\u624b\u5b9e\u73b0\u4e00\u4e2a\u6808\u7c7b\u3002

\u6808\u89c4\u5b9a\u5143\u7d20\u662f\u5148\u5165\u540e\u51fa\u7684\uff0c\u56e0\u6b64\u6211\u4eec\u53ea\u80fd\u5728\u6808\u9876\u6dfb\u52a0\u6216\u5220\u9664\u5143\u7d20\u3002\u7136\u800c\uff0c\u6570\u7ec4\u6216\u94fe\u8868\u90fd\u53ef\u4ee5\u5728\u4efb\u610f\u4f4d\u7f6e\u6dfb\u52a0\u5220\u9664\u5143\u7d20\uff0c\u56e0\u6b64 \u6808\u53ef\u88ab\u770b\u4f5c\u662f\u4e00\u79cd\u53d7\u7ea6\u675f\u7684\u6570\u7ec4\u6216\u94fe\u8868\u3002\u6362\u8a00\u4e4b\uff0c\u6211\u4eec\u53ef\u4ee5\u201c\u5c4f\u853d\u201d\u6570\u7ec4\u6216\u94fe\u8868\u7684\u90e8\u5206\u65e0\u5173\u64cd\u4f5c\uff0c\u4f7f\u4e4b\u5bf9\u5916\u7684\u8868\u73b0\u903b\u8f91\u7b26\u5408\u6808\u7684\u89c4\u5b9a\u5373\u53ef\u3002

"},{"location":"chapter_stack_and_queue/stack/#_1","title":"\u57fa\u4e8e\u94fe\u8868\u7684\u5b9e\u73b0","text":"

\u4f7f\u7528\u300c\u94fe\u8868\u300d\u5b9e\u73b0\u6808\u65f6\uff0c\u5c06\u94fe\u8868\u7684\u5934\u7ed3\u70b9\u770b\u4f5c\u6808\u9876\uff0c\u5c06\u5c3e\u7ed3\u70b9\u770b\u4f5c\u6808\u5e95\u3002

\u5bf9\u4e8e\u5165\u6808\u64cd\u4f5c\uff0c\u5c06\u5143\u7d20\u63d2\u5165\u5230\u94fe\u8868\u5934\u90e8\u5373\u53ef\uff0c\u8fd9\u79cd\u7ed3\u70b9\u6dfb\u52a0\u65b9\u5f0f\u88ab\u79f0\u4e3a\u201c\u5934\u63d2\u6cd5\u201d\u3002\u800c\u5bf9\u4e8e\u51fa\u6808\u64cd\u4f5c\uff0c\u5219\u5c06\u5934\u7ed3\u70b9\u4ece\u94fe\u8868\u4e2d\u5220\u9664\u5373\u53ef\u3002

LinkedListStackpush()pop()

\u4ee5\u4e0b\u662f\u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u6808\u7684\u793a\u4f8b\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig linkedlist_stack.java
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\nclass LinkedListStack {\nprivate ListNode stackPeek;  // \u5c06\u5934\u7ed3\u70b9\u4f5c\u4e3a\u6808\u9876\nprivate int stkSize = 0;   // \u6808\u7684\u957f\u5ea6\npublic LinkedListStack() {\nstackPeek = null;\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\npublic int size() {\nreturn stkSize;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\npublic boolean isEmpty() {\nreturn size() == 0;\n}\n/* \u5165\u6808 */\npublic void push(int num) {\nListNode node = new ListNode(num);\nnode.next = stackPeek;\nstackPeek = node;\nstkSize++;\n}\n/* \u51fa\u6808 */\npublic int pop() {\nint num = peek();\nstackPeek = stackPeek.next;\nstkSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npublic int peek() {\nif (size() == 0)\nthrow new EmptyStackException();\nreturn stackPeek.val;\n}\n/* \u5c06 List \u8f6c\u5316\u4e3a Array \u5e76\u8fd4\u56de */\npublic int[] toArray() {\nListNode node = stackPeek;\nint[] res = new int[size()];\nfor (int i = res.length - 1; i >= 0; i--) {\nres[i] = node.val;\nnode = node.next;\n}\nreturn res;\n}\n}\n
linkedlist_stack.cpp
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\nclass LinkedListStack {\nprivate:\nListNode* stackTop; // \u5c06\u5934\u7ed3\u70b9\u4f5c\u4e3a\u6808\u9876\nint stkSize;        // \u6808\u7684\u957f\u5ea6\npublic:\nLinkedListStack() {\nstackTop = nullptr;\nstkSize = 0;\n}\n~LinkedListStack() {\nfreeMemoryLinkedList(stackTop);\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nint size() {\nreturn stkSize;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nbool empty() {\nreturn size() == 0;\n}\n/* \u5165\u6808 */\nvoid push(int num) {\nListNode* node = new ListNode(num);\nnode->next = stackTop;\nstackTop = node;\nstkSize++;\n}\n/* \u51fa\u6808 */\nvoid pop() {\nint num = top();\nListNode *tmp = stackTop;\nstackTop = stackTop->next;\n// \u91ca\u653e\u5185\u5b58\ndelete tmp;\nstkSize--;\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nint top() {\nif (size() == 0)\nthrow out_of_range(\"\u6808\u4e3a\u7a7a\");\nreturn stackTop->val;\n}\n};\n
linkedlist_stack.py
\"\"\" \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 \"\"\"\nclass LinkedListStack:\ndef __init__(self):\nself.__peek = None\nself.__size = 0\n\"\"\" \u83b7\u53d6\u6808\u7684\u957f\u5ea6 \"\"\"\ndef size(self):\nreturn self.__size\n\"\"\" \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a \"\"\"\ndef is_empty(self):\nreturn not self.__peek\n\"\"\" \u5165\u6808 \"\"\"\ndef push(self, val):\nnode = ListNode(val)\nnode.next = self.__peek\nself.__peek = node\nself.__size += 1\n\"\"\" \u51fa\u6808 \"\"\"\ndef pop(self):\nnum = self.peek()\nself.__peek = self.__peek.next\nself.__size -= 1\nreturn num\n\"\"\" \u8bbf\u95ee\u6808\u9876\u5143\u7d20 \"\"\"\ndef peek(self):\n# \u5224\u7a7a\u5904\u7406\nif not self.__peek: return None\nreturn self.__peek.val\n\"\"\" \u8f6c\u5316\u4e3a\u5217\u8868\u7528\u4e8e\u6253\u5370 \"\"\"\ndef to_list(self):\narr = []\nnode = self.__peek\nwhile node:\narr.append(node.val)\nnode = node.next\narr.reverse()\nreturn arr\n
linkedlist_stack.go
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\ntype linkedListStack struct {\n// \u4f7f\u7528\u5185\u7f6e\u5305 list \u6765\u5b9e\u73b0\u6808\ndata *list.List\n}\n// newLinkedListStack \u521d\u59cb\u5316\u94fe\u8868\nfunc newLinkedListStack() *linkedListStack {\nreturn &linkedListStack{\ndata: list.New(),\n}\n}\n// push \u5165\u6808\nfunc (s *linkedListStack) push(value int) {\ns.data.PushBack(value)\n}\n// pop \u51fa\u6808\nfunc (s *linkedListStack) pop() any {\nif s.isEmpty() {\nreturn nil\n}\ne := s.data.Back()\ns.data.Remove(e)\nreturn e.Value\n}\n// peek \u8bbf\u95ee\u6808\u9876\u5143\u7d20\nfunc (s *linkedListStack) peek() any {\nif s.isEmpty() {\nreturn nil\n}\ne := s.data.Back()\nreturn e.Value\n}\n// size \u83b7\u53d6\u6808\u7684\u957f\u5ea6\nfunc (s *linkedListStack) size() int {\nreturn s.data.Len()\n}\n// isEmpty \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a\nfunc (s *linkedListStack) isEmpty() bool {\nreturn s.data.Len() == 0\n}\n
linkedlist_stack.js
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\nclass LinkedListStack {\n#stackPeek;  // \u5c06\u5934\u7ed3\u70b9\u4f5c\u4e3a\u6808\u9876\n#stkSize = 0;   // \u6808\u7684\u957f\u5ea6\nconstructor() {\nthis.#stackPeek = null;\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nget size() {\nreturn this.#stkSize;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nisEmpty() {\nreturn this.size == 0;\n}\n/* \u5165\u6808 */\npush(num) {\nconst node = new ListNode(num);\nnode.next = this.#stackPeek;\nthis.#stackPeek = node;\nthis.#stkSize++;\n}\n/* \u51fa\u6808 */\npop() {\nconst num = this.peek();\nif (!this.#stackPeek) {\nthrow new Error(\"\u6808\u4e3a\u7a7a\uff01\");\n}\nthis.#stackPeek = this.#stackPeek.next;\nthis.#stkSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npeek() {\nif (!this.#stackPeek) {\nthrow new Error(\"\u6808\u4e3a\u7a7a\uff01\");\n}\nreturn this.#stackPeek.val;\n}\n/* \u5c06\u94fe\u8868\u8f6c\u5316\u4e3a Array \u5e76\u8fd4\u56de */\ntoArray() {\nlet node = this.#stackPeek;\nconst res = new Array(this.size);\nfor (let i = res.length - 1; i >= 0; i--) {\nres[i] = node.val;\nnode = node.next;\n}\nreturn res;\n}\n}\n
linkedlist_stack.ts
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\nclass LinkedListStack {\nprivate stackPeek: ListNode | null;  // \u5c06\u5934\u7ed3\u70b9\u4f5c\u4e3a\u6808\u9876\nprivate stkSize: number = 0;   // \u6808\u7684\u957f\u5ea6\nconstructor() {\nthis.stackPeek = null;\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nget size(): number {\nreturn this.stkSize;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nisEmpty(): boolean {\nreturn this.size == 0;\n}\n/* \u5165\u6808 */\npush(num: number): void {\nconst node = new ListNode(num);\nnode.next = this.stackPeek;\nthis.stackPeek = node;\nthis.stkSize++;\n}\n/* \u51fa\u6808 */\npop(): number {\nconst num = this.peek();\nif (!this.stackPeek) {\nthrow new Error(\"\u6808\u4e3a\u7a7a\uff01\");\n}\nthis.stackPeek = this.stackPeek.next;\nthis.stkSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npeek(): number {\nif (!this.stackPeek) {\nthrow new Error(\"\u6808\u4e3a\u7a7a\uff01\");\n}\nreturn this.stackPeek.val;\n}\n/* \u5c06\u94fe\u8868\u8f6c\u5316\u4e3a Array \u5e76\u8fd4\u56de */\ntoArray(): number[] {\nlet node = this.stackPeek;\nconst res = new Array<number>(this.size);\nfor (let i = res.length - 1; i >= 0; i--) {\nres[i] = node!.val;\nnode = node!.next;\n}\nreturn res;\n}\n}\n
linkedlist_stack.c
\n
linkedlist_stack.cs
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\nclass LinkedListStack\n{\nprivate ListNode stackPeek;  // \u5c06\u5934\u7ed3\u70b9\u4f5c\u4e3a\u6808\u9876\nprivate int stkSize = 0;   // \u6808\u7684\u957f\u5ea6\npublic LinkedListStack()\n{\nstackPeek = null;\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\npublic int size()\n{\nreturn stkSize;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\npublic bool isEmpty()\n{\nreturn size() == 0;\n}\n/* \u5165\u6808 */\npublic void push(int num)\n{\nListNode node = new ListNode(num);\nnode.next = stackPeek;\nstackPeek = node;\nstkSize++;\n}\n/* \u51fa\u6808 */\npublic int pop()\n{\nint num = peek();\nstackPeek = stackPeek?.next;\nstkSize--;\nreturn num;\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npublic int peek()\n{\nif (size() == 0)\nthrow new Exception();\nreturn stackPeek.val;\n}\n}\n
linkedlist_stack.swift
/* \u57fa\u4e8e\u94fe\u8868\u5b9e\u73b0\u7684\u6808 */\nclass LinkedListStack {\nprivate var _peek: ListNode? // \u5c06\u5934\u7ed3\u70b9\u4f5c\u4e3a\u6808\u9876\nprivate var _size = 0 // \u6808\u7684\u957f\u5ea6\ninit() {}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nfunc size() -> Int {\n_size\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nfunc isEmpty() -> Bool {\nsize() == 0\n}\n/* \u5165\u6808 */\nfunc push(num: Int) {\nlet node = ListNode(x: num)\nnode.next = _peek\n_peek = node\n_size += 1\n}\n/* \u51fa\u6808 */\n@discardableResult\nfunc pop() -> Int {\nlet num = peek()\n_peek = _peek?.next\n_size -= 1\nreturn num\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nfunc peek() -> Int {\nif isEmpty() {\nfatalError(\"\u6808\u4e3a\u7a7a\")\n}\nreturn _peek!.val\n}\n}\n
linkedlist_stack.zig
\n
"},{"location":"chapter_stack_and_queue/stack/#_2","title":"\u57fa\u4e8e\u6570\u7ec4\u7684\u5b9e\u73b0","text":"

\u4f7f\u7528\u300c\u6570\u7ec4\u300d\u5b9e\u73b0\u6808\u65f6\uff0c\u8003\u8651\u5c06\u6570\u7ec4\u7684\u5c3e\u90e8\u5f53\u4f5c\u6808\u9876\u3002\u8fd9\u6837\u8bbe\u8ba1\u4e0b\uff0c\u300c\u5165\u6808\u300d\u4e0e\u300c\u51fa\u6808\u300d\u64cd\u4f5c\u5c31\u5bf9\u5e94\u5728\u6570\u7ec4\u5c3e\u90e8\u300c\u6dfb\u52a0\u5143\u7d20\u300d\u4e0e\u300c\u5220\u9664\u5143\u7d20\u300d\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u4e3a \\(O(1)\\) \u3002

ArrayStackpush()pop()

\u7531\u4e8e\u5165\u6808\u7684\u5143\u7d20\u53ef\u80fd\u662f\u6e90\u6e90\u4e0d\u65ad\u7684\uff0c\u56e0\u6b64\u53ef\u4ee5\u4f7f\u7528\u652f\u6301\u52a8\u6001\u6269\u5bb9\u7684\u300c\u5217\u8868\u300d\uff0c\u8fd9\u6837\u5c31\u65e0\u9700\u81ea\u884c\u5b9e\u73b0\u6570\u7ec4\u6269\u5bb9\u4e86\u3002\u4ee5\u4e0b\u662f\u793a\u4f8b\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig array_stack.java
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\nclass ArrayStack {\nprivate ArrayList<Integer> stack;\npublic ArrayStack() {\n// \u521d\u59cb\u5316\u5217\u8868\uff08\u52a8\u6001\u6570\u7ec4\uff09\nstack = new ArrayList<>();\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\npublic int size() {\nreturn stack.size();\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\npublic boolean isEmpty() {\nreturn size() == 0;\n}\n/* \u5165\u6808 */\npublic void push(int num) {\nstack.add(num);\n}\n/* \u51fa\u6808 */\npublic int pop() {\nif (isEmpty())\nthrow new EmptyStackException();\nreturn stack.remove(size() - 1);\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npublic int peek() {\nif (isEmpty())\nthrow new EmptyStackException();\nreturn stack.get(size() - 1);\n}\n/* \u5c06 List \u8f6c\u5316\u4e3a Array \u5e76\u8fd4\u56de */\npublic Object[] toArray() {\nreturn stack.toArray();\n}\n}\n
array_stack.cpp
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\nclass ArrayStack {\nprivate:\nvector<int> stack;\npublic:\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nint size() {\nreturn stack.size();\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nbool empty() {\nreturn stack.empty();\n}\n/* \u5165\u6808 */\nvoid push(int num) {\nstack.push_back(num);\n}\n/* \u51fa\u6808 */\nvoid pop() {\nint oldTop = top();\nstack.pop_back();\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nint top() {\nif(empty())\nthrow out_of_range(\"\u6808\u4e3a\u7a7a\");\nreturn stack.back();\n}\n};\n
array_stack.py
\"\"\" \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 \"\"\"\nclass ArrayStack:\ndef __init__(self):\nself.__stack = []\n\"\"\" \u83b7\u53d6\u6808\u7684\u957f\u5ea6 \"\"\"\ndef size(self):\nreturn len(self.__stack)\n\"\"\" \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a \"\"\"\ndef is_empty(self):\nreturn self.__stack == []\n\"\"\" \u5165\u6808 \"\"\"\ndef push(self, item):\nself.__stack.append(item)\n\"\"\" \u51fa\u6808 \"\"\"\ndef pop(self):\nassert not self.is_empty(), \"\u6808\u4e3a\u7a7a\"\nreturn self.__stack.pop()\n\"\"\" \u8bbf\u95ee\u6808\u9876\u5143\u7d20 \"\"\"\ndef peek(self):\nassert not self.is_empty(), \"\u6808\u4e3a\u7a7a\"\nreturn self.__stack[-1]\n\"\"\" \u8fd4\u56de\u5217\u8868\u7528\u4e8e\u6253\u5370 \"\"\"\ndef to_list(self):\nreturn self.__stack\n
array_stack.go
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\ntype arrayStack struct {\ndata []int // \u6570\u636e\n}\nfunc newArrayStack() *arrayStack {\nreturn &arrayStack{\n// \u8bbe\u7f6e\u6808\u7684\u957f\u5ea6\u4e3a 0\uff0c\u5bb9\u91cf\u4e3a 16\ndata: make([]int, 0, 16),\n}\n}\n// size \u6808\u7684\u957f\u5ea6\nfunc (s *arrayStack) size() int {\nreturn len(s.data)\n}\n// isEmpty \u6808\u662f\u5426\u4e3a\u7a7a\nfunc (s *arrayStack) isEmpty() bool {\nreturn s.size() == 0\n}\n// push \u5165\u6808\nfunc (s *arrayStack) push(v int) {\n// \u5207\u7247\u4f1a\u81ea\u52a8\u6269\u5bb9\ns.data = append(s.data, v)\n}\n// pop \u51fa\u6808\nfunc (s *arrayStack) pop() any {\n// \u5f39\u51fa\u6808\u524d\uff0c\u5148\u5224\u65ad\u662f\u5426\u4e3a\u7a7a\nif s.isEmpty() {\nreturn nil\n}\nval := s.peek()\ns.data = s.data[:len(s.data)-1]\nreturn val\n}\n// peek \u83b7\u53d6\u6808\u9876\u5143\u7d20\nfunc (s *arrayStack) peek() any {\nif s.isEmpty() {\nreturn nil\n}\nval := s.data[len(s.data)-1]\nreturn val\n}\n
array_stack.js
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\nclass ArrayStack {\nstack;\nconstructor() {\nthis.stack = [];\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nget size() {\nreturn this.stack.length;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nempty() {\nreturn this.stack.length === 0;\n}\n/* \u5165\u6808 */\npush(num) {\nthis.stack.push(num);\n}\n/* \u51fa\u6808 */\npop() {\nif (this.empty())\nthrow new Error(\"\u6808\u4e3a\u7a7a\");\nreturn this.stack.pop();\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\ntop() {\nif (this.empty())\nthrow new Error(\"\u6808\u4e3a\u7a7a\");\nreturn this.stack[this.stack.length - 1];\n}\n};\n
array_stack.ts
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\nclass ArrayStack {\nprivate stack: number[];\nconstructor() {\nthis.stack = [];\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nget size(): number {\nreturn this.stack.length;\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nempty(): boolean {\nreturn this.stack.length === 0;\n}\n/* \u5165\u6808 */\npush(num: number): void {\nthis.stack.push(num);\n}\n/* \u51fa\u6808 */\npop(): number | undefined {\nif (this.empty())\nthrow new Error('\u6808\u4e3a\u7a7a');\nreturn this.stack.pop();\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\ntop(): number | undefined {\nif (this.empty())\nthrow new Error('\u6808\u4e3a\u7a7a');\nreturn this.stack[this.stack.length - 1];\n}\n};\n
array_stack.c
\n
array_stack.cs
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\nclass ArrayStack\n{\nprivate List<int> stack;\npublic ArrayStack()\n{\n// \u521d\u59cb\u5316\u5217\u8868\uff08\u52a8\u6001\u6570\u7ec4\uff09\nstack = new();\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\npublic int size()\n{\nreturn stack.Count();\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\npublic bool isEmpty()\n{\nreturn size() == 0;\n}\n/* \u5165\u6808 */\npublic void push(int num)\n{\nstack.Add(num);\n}\n/* \u51fa\u6808 */\npublic int pop()\n{\nif (isEmpty())\nthrow new Exception();\nvar val = peek();\nstack.RemoveAt(size() - 1);\nreturn val;\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\npublic int peek()\n{\nif (isEmpty())\nthrow new Exception();\nreturn stack[size() - 1];\n}\n}\n
array_stack.swift
/* \u57fa\u4e8e\u6570\u7ec4\u5b9e\u73b0\u7684\u6808 */\nclass ArrayStack {\nprivate var stack: [Int]\ninit() {\n// \u521d\u59cb\u5316\u5217\u8868\uff08\u52a8\u6001\u6570\u7ec4\uff09\nstack = []\n}\n/* \u83b7\u53d6\u6808\u7684\u957f\u5ea6 */\nfunc size() -> Int {\nstack.count\n}\n/* \u5224\u65ad\u6808\u662f\u5426\u4e3a\u7a7a */\nfunc isEmpty() -> Bool {\nstack.isEmpty\n}\n/* \u5165\u6808 */\nfunc push(num: Int) {\nstack.append(num)\n}\n/* \u51fa\u6808 */\n@discardableResult\nfunc pop() -> Int {\nif isEmpty() {\nfatalError(\"\u6808\u4e3a\u7a7a\")\n}\nreturn stack.removeLast()\n}\n/* \u8bbf\u95ee\u6808\u9876\u5143\u7d20 */\nfunc peek() -> Int {\nif isEmpty() {\nfatalError(\"\u6808\u4e3a\u7a7a\")\n}\nreturn stack.last!\n}\n}\n
array_stack.zig
\n
"},{"location":"chapter_stack_and_queue/stack/#513","title":"5.1.3. \u4e24\u79cd\u5b9e\u73b0\u5bf9\u6bd4","text":""},{"location":"chapter_stack_and_queue/stack/#_3","title":"\u652f\u6301\u64cd\u4f5c","text":"

\u4e24\u79cd\u5b9e\u73b0\u90fd\u652f\u6301\u6808\u5b9a\u4e49\u4e2d\u7684\u5404\u9879\u64cd\u4f5c\uff0c\u6570\u7ec4\u5b9e\u73b0\u989d\u5916\u652f\u6301\u968f\u673a\u8bbf\u95ee\uff0c\u4f46\u8fd9\u5df2\u7ecf\u8d85\u51fa\u6808\u7684\u5b9a\u4e49\u8303\u7574\uff0c\u4e00\u822c\u4e0d\u4f1a\u7528\u5230\u3002

"},{"location":"chapter_stack_and_queue/stack/#_4","title":"\u65f6\u95f4\u6548\u7387","text":"

\u5728\u6570\u7ec4\uff08\u5217\u8868\uff09\u5b9e\u73b0\u4e2d\uff0c\u5165\u6808\u4e0e\u51fa\u6808\u64cd\u4f5c\u90fd\u662f\u5728\u9884\u5148\u5206\u914d\u597d\u7684\u8fde\u7eed\u5185\u5b58\u4e2d\u64cd\u4f5c\uff0c\u5177\u6709\u5f88\u597d\u7684\u7f13\u5b58\u672c\u5730\u6027\uff0c\u6548\u7387\u5f88\u597d\u3002\u7136\u800c\uff0c\u5982\u679c\u5165\u6808\u65f6\u8d85\u51fa\u6570\u7ec4\u5bb9\u91cf\uff0c\u5219\u4f1a\u89e6\u53d1\u6269\u5bb9\u673a\u5236\uff0c\u90a3\u4e48\u8be5\u6b21\u5165\u6808\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e3a \\(O(n)\\) \u3002

\u5728\u94fe\u8868\u5b9e\u73b0\u4e2d\uff0c\u94fe\u8868\u7684\u6269\u5bb9\u975e\u5e38\u7075\u6d3b\uff0c\u4e0d\u5b58\u5728\u4e0a\u8ff0\u6570\u7ec4\u6269\u5bb9\u65f6\u53d8\u6162\u7684\u95ee\u9898\u3002\u7136\u800c\uff0c\u5165\u6808\u64cd\u4f5c\u9700\u8981\u521d\u59cb\u5316\u7ed3\u70b9\u5bf9\u8c61\u5e76\u4fee\u6539\u6307\u9488\uff0c\u56e0\u800c\u6548\u7387\u4e0d\u5982\u6570\u7ec4\u3002\u8fdb\u4e00\u6b65\u5730\u601d\u8003\uff0c\u5982\u679c\u5165\u6808\u5143\u7d20\u4e0d\u662f int \u800c\u662f\u7ed3\u70b9\u5bf9\u8c61\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u7701\u53bb\u521d\u59cb\u5316\u6b65\u9aa4\uff0c\u4ece\u800c\u63d0\u5347\u6548\u7387\u3002

\u7efc\u4e0a\u6240\u8ff0\uff0c\u5f53\u5165\u6808\u4e0e\u51fa\u6808\u64cd\u4f5c\u7684\u5143\u7d20\u662f\u57fa\u672c\u6570\u636e\u7c7b\u578b\uff08\u4f8b\u5982 int , double \uff09\u65f6\uff0c\u5219\u7ed3\u8bba\u5982\u4e0b\uff1a

  • \u6570\u7ec4\u5b9e\u73b0\u7684\u6808\u5728\u89e6\u53d1\u6269\u5bb9\u65f6\u4f1a\u53d8\u6162\uff0c\u4f46\u7531\u4e8e\u6269\u5bb9\u662f\u4f4e\u9891\u64cd\u4f5c\uff0c\u56e0\u6b64 \u603b\u4f53\u6548\u7387\u66f4\u9ad8\uff1b
  • \u94fe\u8868\u5b9e\u73b0\u7684\u6808\u53ef\u4ee5\u63d0\u4f9b \u66f4\u52a0\u7a33\u5b9a\u7684\u6548\u7387\u8868\u73b0\uff1b
"},{"location":"chapter_stack_and_queue/stack/#_5","title":"\u7a7a\u95f4\u6548\u7387","text":"

\u5728\u521d\u59cb\u5316\u5217\u8868\u65f6\uff0c\u7cfb\u7edf\u4f1a\u7ed9\u5217\u8868\u5206\u914d\u201c\u521d\u59cb\u5bb9\u91cf\u201d\uff0c\u8be5\u5bb9\u91cf\u53ef\u80fd\u8d85\u8fc7\u6211\u4eec\u7684\u9700\u6c42\u3002\u5e76\u4e14\u6269\u5bb9\u673a\u5236\u4e00\u822c\u662f\u6309\u7167\u7279\u5b9a\u500d\u7387\uff08\u6bd4\u5982 2 \u500d\uff09\u8fdb\u884c\u6269\u5bb9\uff0c\u6269\u5bb9\u540e\u7684\u5bb9\u91cf\u4e5f\u53ef\u80fd\u8d85\u51fa\u6211\u4eec\u7684\u9700\u6c42\u3002\u56e0\u6b64\uff0c\u6570\u7ec4\u5b9e\u73b0\u6808\u4f1a\u9020\u6210\u4e00\u5b9a\u7684\u7a7a\u95f4\u6d6a\u8d39\u3002

\u5f53\u7136\uff0c\u7531\u4e8e\u7ed3\u70b9\u9700\u8981\u989d\u5916\u5b58\u50a8\u6307\u9488\uff0c\u56e0\u6b64 \u94fe\u8868\u7ed3\u70b9\u6bd4\u6570\u7ec4\u5143\u7d20\u5360\u7528\u66f4\u5927\u3002

\u7efc\u4e0a\uff0c\u6211\u4eec\u4e0d\u80fd\u7b80\u5355\u5730\u786e\u5b9a\u54ea\u79cd\u5b9e\u73b0\u66f4\u52a0\u7701\u5185\u5b58\uff0c\u9700\u8981 case-by-case \u5730\u5206\u6790\u3002

"},{"location":"chapter_stack_and_queue/stack/#514","title":"5.1.4. \u6808\u5178\u578b\u5e94\u7528","text":"
  • \u6d4f\u89c8\u5668\u4e2d\u7684\u540e\u9000\u4e0e\u524d\u8fdb\u3001\u8f6f\u4ef6\u4e2d\u7684\u64a4\u9500\u4e0e\u53cd\u64a4\u9500\u3002\u6bcf\u5f53\u6211\u4eec\u6253\u5f00\u65b0\u7684\u7f51\u9875\uff0c\u6d4f\u89c8\u5668\u5c31\u5c06\u4e0a\u4e00\u4e2a\u7f51\u9875\u6267\u884c\u5165\u6808\uff0c\u8fd9\u6837\u6211\u4eec\u5c31\u53ef\u4ee5\u901a\u8fc7\u300c\u540e\u9000\u300d\u64cd\u4f5c\u6765\u56de\u5230\u4e0a\u4e00\u9875\u9762\uff0c\u540e\u9000\u64cd\u4f5c\u5b9e\u9645\u4e0a\u662f\u5728\u6267\u884c\u51fa\u6808\u3002\u5982\u679c\u8981\u540c\u65f6\u652f\u6301\u540e\u9000\u548c\u524d\u8fdb\uff0c\u90a3\u4e48\u5219\u9700\u8981\u4e24\u4e2a\u6808\u6765\u914d\u5408\u5b9e\u73b0\u3002
  • \u7a0b\u5e8f\u5185\u5b58\u7ba1\u7406\u3002\u6bcf\u5f53\u8c03\u7528\u51fd\u6570\u65f6\uff0c\u7cfb\u7edf\u5c31\u4f1a\u5728\u6808\u9876\u6dfb\u52a0\u4e00\u4e2a\u6808\u5e27\uff0c\u7528\u6765\u8bb0\u5f55\u51fd\u6570\u7684\u4e0a\u4e0b\u6587\u4fe1\u606f\u3002\u5728\u9012\u5f52\u51fd\u6570\u4e2d\uff0c\u5411\u4e0b\u9012\u63a8\u4f1a\u4e0d\u65ad\u6267\u884c\u5165\u6808\uff0c\u5411\u4e0a\u56de\u6eaf\u9636\u6bb5\u65f6\u51fa\u6808\u3002
"},{"location":"chapter_stack_and_queue/summary/","title":"5.4. \u5c0f\u7ed3","text":"
  • \u6808\u662f\u4e00\u79cd\u9075\u5faa\u5148\u5165\u540e\u51fa\u7684\u6570\u636e\u7ed3\u6784\uff0c\u53ef\u4ee5\u4f7f\u7528\u6570\u7ec4\u6216\u94fe\u8868\u5b9e\u73b0\u3002
  • \u5728\u65f6\u95f4\u6548\u7387\u65b9\u9762\uff0c\u6808\u7684\u6570\u7ec4\u5b9e\u73b0\u5177\u6709\u66f4\u597d\u7684\u5e73\u5747\u6548\u7387\uff0c\u4f46\u6269\u5bb9\u65f6\u4f1a\u5bfc\u81f4\u5355\u6b21\u5165\u6808\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u52a3\u5316\u81f3 \\(O(n)\\) \u3002\u76f8\u5bf9\u5730\uff0c\u6808\u7684\u94fe\u8868\u5b9e\u73b0\u5177\u6709\u66f4\u52a0\u7a33\u5b9a\u7684\u6548\u7387\u8868\u73b0\u3002
  • \u5728\u7a7a\u95f4\u6548\u7387\u65b9\u9762\uff0c\u6808\u7684\u6570\u7ec4\u5b9e\u73b0\u4f1a\u9020\u6210\u4e00\u5b9a\u7a7a\u95f4\u6d6a\u8d39\uff0c\u7136\u800c\u94fe\u8868\u7ed3\u70b9\u6bd4\u6570\u7ec4\u5143\u7d20\u5360\u7528\u5185\u5b58\u66f4\u5927\u3002
  • \u961f\u5217\u662f\u4e00\u79cd\u9075\u5faa\u5148\u5165\u5148\u51fa\u7684\u6570\u636e\u7ed3\u6784\uff0c\u53ef\u4ee5\u4f7f\u7528\u6570\u7ec4\u6216\u94fe\u8868\u5b9e\u73b0\u3002\u5bf9\u4e8e\u4e24\u79cd\u5b9e\u73b0\u7684\u65f6\u95f4\u6548\u7387\u4e0e\u7a7a\u95f4\u6548\u7387\u5bf9\u6bd4\uff0c\u4e0e\u4e0a\u8ff0\u6808\u7684\u7ed3\u8bba\u76f8\u540c\u3002
  • \u53cc\u5411\u961f\u5217\u7684\u4e24\u7aef\u90fd\u53ef\u4ee5\u6dfb\u52a0\u4e0e\u5220\u9664\u5143\u7d20\u3002
"},{"location":"chapter_tree/avl_tree/","title":"7.4. AVL \u6811 *","text":"

\u5728\u300c\u4e8c\u53c9\u641c\u7d22\u6811\u300d\u7ae0\u8282\u4e2d\u63d0\u5230\uff0c\u5728\u8fdb\u884c\u591a\u6b21\u63d2\u5165\u4e0e\u5220\u9664\u64cd\u4f5c\u540e\uff0c\u4e8c\u53c9\u641c\u7d22\u6811\u53ef\u80fd\u4f1a\u9000\u5316\u4e3a\u94fe\u8868\u3002\u6b64\u65f6\u6240\u6709\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u4f1a\u7531 \\(O(\\log n)\\) \u52a3\u5316\u81f3 \\(O(n)\\) \u3002

\u5982\u4e0b\u56fe\u6240\u793a\uff0c\u6267\u884c\u4e24\u6b65\u5220\u9664\u7ed3\u70b9\u540e\uff0c\u8be5\u4e8c\u53c9\u641c\u7d22\u6811\u5c31\u4f1a\u9000\u5316\u4e3a\u94fe\u8868\u3002

\u518d\u6bd4\u5982\uff0c\u5728\u4ee5\u4e0b\u5b8c\u7f8e\u4e8c\u53c9\u6811\u4e2d\u63d2\u5165\u4e24\u4e2a\u7ed3\u70b9\u540e\uff0c\u6811\u4e25\u91cd\u5411\u5de6\u504f\u659c\uff0c\u67e5\u627e\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e5f\u968f\u4e4b\u53d1\u751f\u52a3\u5316\u3002

G. M. Adelson-Velsky \u548c E. M. Landis \u5728\u5176 1962 \u5e74\u53d1\u8868\u7684\u8bba\u6587 \"An algorithm for the organization of information\" \u4e2d\u63d0\u51fa\u4e86\u300cAVL \u6811\u300d\u3002\u8bba\u6587\u4e2d\u63cf\u8ff0\u4e86\u4e00\u7cfb\u5217\u64cd\u4f5c\uff0c\u4f7f\u5f97\u5728\u4e0d\u65ad\u6dfb\u52a0\u4e0e\u5220\u9664\u7ed3\u70b9\u540e\uff0cAVL \u6811\u4ecd\u7136\u4e0d\u4f1a\u53d1\u751f\u9000\u5316\uff0c\u8fdb\u800c\u4f7f\u5f97\u5404\u79cd\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u5747\u80fd\u4fdd\u6301\u5728 \\(O(\\log n)\\) \u7ea7\u522b\u3002

\u6362\u8a00\u4e4b\uff0c\u5728\u9891\u7e41\u589e\u5220\u67e5\u6539\u7684\u4f7f\u7528\u573a\u666f\u4e2d\uff0cAVL \u6811\u53ef\u59cb\u7ec8\u4fdd\u6301\u5f88\u9ad8\u7684\u6570\u636e\u589e\u5220\u67e5\u6539\u6548\u7387\uff0c\u5177\u6709\u5f88\u597d\u7684\u5e94\u7528\u4ef7\u503c\u3002

"},{"location":"chapter_tree/avl_tree/#741-avl","title":"7.4.1. AVL \u6811\u5e38\u89c1\u672f\u8bed","text":"

\u300cAVL \u6811\u300d\u65e2\u662f\u300c\u4e8c\u53c9\u641c\u7d22\u6811\u300d\u53c8\u662f\u300c\u5e73\u8861\u4e8c\u53c9\u6811\u300d\uff0c\u540c\u65f6\u6ee1\u8db3\u8fd9\u4e24\u79cd\u4e8c\u53c9\u6811\u7684\u6240\u6709\u6027\u8d28\uff0c\u56e0\u6b64\u53c8\u88ab\u79f0\u4e3a\u300c\u5e73\u8861\u4e8c\u53c9\u641c\u7d22\u6811\u300d\u3002

"},{"location":"chapter_tree/avl_tree/#_1","title":"\u7ed3\u70b9\u9ad8\u5ea6","text":"

\u5728 AVL \u6811\u7684\u64cd\u4f5c\u4e2d\uff0c\u9700\u8981\u83b7\u53d6\u7ed3\u70b9\u300c\u9ad8\u5ea6 Height\u300d\uff0c\u6240\u4ee5\u7ed9 AVL \u6811\u7684\u7ed3\u70b9\u7c7b\u6dfb\u52a0 height \u53d8\u91cf\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* AVL \u6811\u7ed3\u70b9\u7c7b */\nclass TreeNode {\npublic int val; // \u7ed3\u70b9\u503c\npublic int height; // \u7ed3\u70b9\u9ad8\u5ea6\npublic TreeNode left; // \u5de6\u5b50\u7ed3\u70b9\npublic TreeNode right; // \u53f3\u5b50\u7ed3\u70b9\npublic TreeNode(int x) { val = x; }\n}\n
/* AVL \u6811\u7ed3\u70b9\u7c7b */\nstruct TreeNode {\nint val{};              // \u7ed3\u70b9\u503c\nint height = 0;         // \u7ed3\u70b9\u9ad8\u5ea6\nTreeNode *left{};       // \u5de6\u5b50\u7ed3\u70b9\nTreeNode *right{};      // \u53f3\u5b50\u7ed3\u70b9\nTreeNode() = default;\nexplicit TreeNode(int x) : val(x){}\n};\n
\"\"\" AVL \u6811\u7ed3\u70b9\u7c7b \"\"\"\nclass TreeNode:\ndef __init__(self, val=None, left=None, right=None):\nself.val = val      # \u7ed3\u70b9\u503c\nself.height = 0     # \u7ed3\u70b9\u9ad8\u5ea6\nself.left = left    # \u5de6\u5b50\u7ed3\u70b9\u5f15\u7528\nself.right = right  # \u53f3\u5b50\u7ed3\u70b9\u5f15\u7528\n
/* AVL \u6811\u7ed3\u70b9\u7c7b */\ntype TreeNode struct {\nVal    int       // \u7ed3\u70b9\u503c\nHeight int       // \u7ed3\u70b9\u9ad8\u5ea6\nLeft   *TreeNode // \u5de6\u5b50\u7ed3\u70b9\u5f15\u7528\nRight  *TreeNode // \u53f3\u5b50\u7ed3\u70b9\u5f15\u7528\n}\n
class TreeNode {\nval; // \u7ed3\u70b9\u503c\nheight; //\u7ed3\u70b9\u9ad8\u5ea6\nleft; // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nright; // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\nconstructor(val, left, right, height) {\nthis.val = val === undefined ? 0 : val;\nthis.height = height === undefined ? 0 : height;\nthis.left = left === undefined ? null : left;\nthis.right = right === undefined ? null : right;\n}\n}\n
class TreeNode {\nval: number;            // \u7ed3\u70b9\u503c\nheight: number;         // \u7ed3\u70b9\u9ad8\u5ea6\nleft: TreeNode | null;  // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nright: TreeNode | null; // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\nconstructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {\nthis.val = val === undefined ? 0 : val;\nthis.height = height === undefined ? 0 : height; this.left = left === undefined ? null : left; this.right = right === undefined ? null : right; }\n}\n
\n
/* AVL \u6811\u7ed3\u70b9\u7c7b */\nclass TreeNode {\npublic int val;          // \u7ed3\u70b9\u503c\npublic int height;       // \u7ed3\u70b9\u9ad8\u5ea6\npublic TreeNode? left;   // \u5de6\u5b50\u7ed3\u70b9\npublic TreeNode? right;  // \u53f3\u5b50\u7ed3\u70b9\npublic TreeNode(int x) { val = x; }\n}\n
/* AVL \u6811\u7ed3\u70b9\u7c7b */\nclass TreeNode {\nvar val: Int // \u7ed3\u70b9\u503c\nvar height: Int // \u7ed3\u70b9\u9ad8\u5ea6\nvar left: TreeNode? // \u5de6\u5b50\u7ed3\u70b9\nvar right: TreeNode? // \u53f3\u5b50\u7ed3\u70b9\ninit(x: Int) {\nval = x\nheight = 0\n}\n}\n
\n

\u300c\u7ed3\u70b9\u9ad8\u5ea6\u300d\u662f\u6700\u8fdc\u53f6\u7ed3\u70b9\u5230\u8be5\u7ed3\u70b9\u7684\u8ddd\u79bb\uff0c\u5373\u8d70\u8fc7\u7684\u300c\u8fb9\u300d\u7684\u6570\u91cf\u3002\u9700\u8981\u7279\u522b\u6ce8\u610f\uff0c\u53f6\u7ed3\u70b9\u7684\u9ad8\u5ea6\u4e3a 0 \uff0c\u7a7a\u7ed3\u70b9\u7684\u9ad8\u5ea6\u4e3a -1\u3002\u6211\u4eec\u5c01\u88c5\u4e24\u4e2a\u5de5\u5177\u51fd\u6570\uff0c\u5206\u522b\u7528\u4e8e\u83b7\u53d6\u4e0e\u66f4\u65b0\u7ed3\u70b9\u7684\u9ad8\u5ea6\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\nint height(TreeNode node) {\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nreturn node == null ? -1 : node.height;\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nvoid updateHeight(TreeNode node) {\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode.height = Math.max(height(node.left), height(node.right)) + 1;\n}\n
avl_tree.cpp
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\nint height(TreeNode* node) {\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nreturn node == nullptr ? -1 : node->height;\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nvoid updateHeight(TreeNode* node) {\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode->height = max(height(node->left), height(node->right)) + 1;\n}\n
avl_tree.py
\"\"\" \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 \"\"\"\ndef height(self, node: Optional[TreeNode]) -> int:\n# \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nif node is not None:\nreturn node.height\nreturn -1\n\"\"\" \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 \"\"\"\ndef __update_height(self, node: Optional[TreeNode]):\n# \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode.height = max([self.height(node.left), self.height(node.right)]) + 1\n
avl_tree.go
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\nfunc height(node *TreeNode) int {\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nif node != nil {\nreturn node.Height\n}\nreturn -1\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nfunc updateHeight(node *TreeNode) {\nlh := height(node.Left)\nrh := height(node.Right)\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nif lh > rh {\nnode.Height = lh + 1\n} else {\nnode.Height = rh + 1\n}\n}\n
avl_tree.js
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\nheight(node) {\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nreturn node === null ? -1 : node.height;\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nupdateHeight(node) {\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode.height = Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
avl_tree.ts
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\nheight(node: TreeNode): number {\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nreturn node === null ? -1 : node.height;\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nupdateHeight(node: TreeNode): void {\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode.height = Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\npublic int height(TreeNode? node)\n{\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nreturn node == null ? -1 : node.height;\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nprivate void updateHeight(TreeNode node)\n{\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode.height = Math.Max(height(node.left), height(node.right)) + 1;\n}\n
avl_tree.swift
/* \u83b7\u53d6\u7ed3\u70b9\u9ad8\u5ea6 */\nfunc height(node: TreeNode?) -> Int {\n// \u7a7a\u7ed3\u70b9\u9ad8\u5ea6\u4e3a -1 \uff0c\u53f6\u7ed3\u70b9\u9ad8\u5ea6\u4e3a 0\nnode == nil ? -1 : node!.height\n}\n/* \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6 */\nfunc updateHeight(node: TreeNode?) {\n// \u7ed3\u70b9\u9ad8\u5ea6\u7b49\u4e8e\u6700\u9ad8\u5b50\u6811\u9ad8\u5ea6 + 1\nnode?.height = max(height(node: node?.left), height(node: node?.right)) + 1\n}\n
avl_tree.zig
\n
"},{"location":"chapter_tree/avl_tree/#_2","title":"\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50","text":"

\u7ed3\u70b9\u7684\u300c\u5e73\u8861\u56e0\u5b50 Balance Factor\u300d\u662f \u7ed3\u70b9\u7684\u5de6\u5b50\u6811\u9ad8\u5ea6\u51cf\u53bb\u53f3\u5b50\u6811\u9ad8\u5ea6\uff0c\u5e76\u5b9a\u4e49\u7a7a\u7ed3\u70b9\u7684\u5e73\u8861\u56e0\u5b50\u4e3a 0 \u3002\u540c\u6837\u5730\uff0c\u6211\u4eec\u5c06\u83b7\u53d6\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u5c01\u88c5\u6210\u51fd\u6570\uff0c\u4ee5\u4fbf\u540e\u7eed\u4f7f\u7528\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\nint balanceFactor(TreeNode node) {\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif (node == null) return 0;\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn height(node.left) - height(node.right);\n}\n
avl_tree.cpp
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\nint balanceFactor(TreeNode* node) {\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif (node == nullptr) return 0;\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn height(node->left) - height(node->right);\n}\n
avl_tree.py
\"\"\" \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 \"\"\"\ndef balance_factor(self, node: Optional[TreeNode]) -> int:\n# \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif node is None:\nreturn 0\n# \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn self.height(node.left) - self.height(node.right)\n
avl_tree.go
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\nfunc balanceFactor(node *TreeNode) int {\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif node == nil {\nreturn 0\n}\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn height(node.Left) - height(node.Right)\n}\n
avl_tree.js
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\nbalanceFactor(node) {\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif (node === null) return 0;\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn this.height(node.left) - this.height(node.right);\n}\n
avl_tree.ts
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\nbalanceFactor(node: TreeNode): number {\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif (node === null) return 0;\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn this.height(node.left) - this.height(node.right);\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\npublic int balanceFactor(TreeNode? node)\n{\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nif (node == null) return 0;\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn height(node.left) - height(node.right);\n}\n
avl_tree.swift
/* \u83b7\u53d6\u5e73\u8861\u56e0\u5b50 */\nfunc balanceFactor(node: TreeNode?) -> Int {\n// \u7a7a\u7ed3\u70b9\u5e73\u8861\u56e0\u5b50\u4e3a 0\nguard let node = node else { return 0 }\n// \u7ed3\u70b9\u5e73\u8861\u56e0\u5b50 = \u5de6\u5b50\u6811\u9ad8\u5ea6 - \u53f3\u5b50\u6811\u9ad8\u5ea6\nreturn height(node: node.left) - height(node: node.right)\n}\n
avl_tree.zig
\n

Note

\u8bbe\u5e73\u8861\u56e0\u5b50\u4e3a \\(f\\) \uff0c\u5219\u4e00\u68f5 AVL \u6811\u7684\u4efb\u610f\u7ed3\u70b9\u7684\u5e73\u8861\u56e0\u5b50\u7686\u6ee1\u8db3 \\(-1 \\le f \\le 1\\) \u3002

"},{"location":"chapter_tree/avl_tree/#742-avl","title":"7.4.2. AVL \u6811\u65cb\u8f6c","text":"

AVL \u6811\u7684\u72ec\u7279\u4e4b\u5904\u5728\u4e8e\u300c\u65cb\u8f6c Rotation\u300d\u7684\u64cd\u4f5c\uff0c\u5176\u53ef \u5728\u4e0d\u5f71\u54cd\u4e8c\u53c9\u6811\u4e2d\u5e8f\u904d\u5386\u5e8f\u5217\u7684\u524d\u63d0\u4e0b\uff0c\u4f7f\u5931\u8861\u7ed3\u70b9\u91cd\u65b0\u6062\u590d\u5e73\u8861\u3002\u6362\u8a00\u4e4b\uff0c\u65cb\u8f6c\u64cd\u4f5c\u65e2\u53ef\u4ee5\u4f7f\u6811\u4fdd\u6301\u4e3a\u300c\u4e8c\u53c9\u641c\u7d22\u6811\u300d\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u6811\u91cd\u65b0\u6062\u590d\u4e3a\u300c\u5e73\u8861\u4e8c\u53c9\u6811\u300d\u3002

\u6211\u4eec\u5c06\u5e73\u8861\u56e0\u5b50\u7684\u7edd\u5bf9\u503c \\(> 1\\) \u7684\u7ed3\u70b9\u79f0\u4e3a\u300c\u5931\u8861\u7ed3\u70b9\u300d\u3002\u6839\u636e\u7ed3\u70b9\u7684\u5931\u8861\u60c5\u51b5\uff0c\u65cb\u8f6c\u64cd\u4f5c\u5206\u4e3a \u53f3\u65cb\u3001\u5de6\u65cb\u3001\u5148\u53f3\u65cb\u540e\u5de6\u65cb\u3001\u5148\u5de6\u65cb\u540e\u53f3\u65cb\uff0c\u63a5\u4e0b\u6765\u6211\u4eec\u6765\u4e00\u8d77\u6765\u770b\u770b\u5b83\u4eec\u662f\u5982\u4f55\u64cd\u4f5c\u7684\u3002

"},{"location":"chapter_tree/avl_tree/#case-1-","title":"Case 1 - \u53f3\u65cb","text":"

\u5982\u4e0b\u56fe\u6240\u793a\uff08\u7ed3\u70b9\u4e0b\u65b9\u4e3a\u300c\u5e73\u8861\u56e0\u5b50\u300d\uff09\uff0c\u4ece\u5e95\u81f3\u9876\u770b\uff0c\u4e8c\u53c9\u6811\u4e2d\u9996\u4e2a\u5931\u8861\u7ed3\u70b9\u662f \u7ed3\u70b9 3\u3002\u6211\u4eec\u805a\u7126\u5728\u4ee5\u8be5\u5931\u8861\u7ed3\u70b9\u4e3a\u6839\u7ed3\u70b9\u7684\u5b50\u6811\u4e0a\uff0c\u5c06\u8be5\u7ed3\u70b9\u8bb0\u4e3a node \uff0c\u5c06\u5176\u5de6\u5b50\u7ed3\u70b9\u8bb0\u4e3a child \uff0c\u6267\u884c\u300c\u53f3\u65cb\u300d\u64cd\u4f5c\u3002\u5b8c\u6210\u53f3\u65cb\u540e\uff0c\u8be5\u5b50\u6811\u5df2\u7ecf\u6062\u590d\u5e73\u8861\uff0c\u5e76\u4e14\u4ecd\u7136\u4e3a\u4e8c\u53c9\u641c\u7d22\u6811\u3002

Step 1Step 2Step 3Step 4

\u8fdb\u800c\uff0c\u5982\u679c\u7ed3\u70b9 child \u672c\u8eab\u6709\u53f3\u5b50\u7ed3\u70b9\uff08\u8bb0\u4e3a grandChild \uff09\uff0c\u5219\u9700\u8981\u5728\u300c\u53f3\u65cb\u300d\u4e2d\u6dfb\u52a0\u4e00\u6b65\uff1a\u5c06 grandChild \u4f5c\u4e3a node \u7684\u5de6\u5b50\u7ed3\u70b9\u3002

\u201c\u5411\u53f3\u65cb\u8f6c\u201d\u662f\u4e00\u79cd\u5f62\u8c61\u5316\u7684\u8bf4\u6cd5\uff0c\u5b9e\u9645\u9700\u8981\u901a\u8fc7\u4fee\u6539\u7ed3\u70b9\u6307\u9488\u5b9e\u73b0\uff0c\u4ee3\u7801\u5982\u4e0b\u6240\u793a\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u53f3\u65cb\u64cd\u4f5c */\nTreeNode rightRotate(TreeNode node) {\nTreeNode child = node.left;\nTreeNode grandChild = child.right;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild.right = node;\nnode.left = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node);\nupdateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.cpp
/* \u53f3\u65cb\u64cd\u4f5c */\nTreeNode* rightRotate(TreeNode* node) {\nTreeNode* child = node->left;\nTreeNode* grandChild = child->right;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild->right = node;\nnode->left = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node);\nupdateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.py
\"\"\" \u53f3\u65cb\u64cd\u4f5c \"\"\"\ndef __right_rotate(self, node: Optional[TreeNode]) -> TreeNode:\nchild = node.left\ngrand_child = child.right\n# \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild.right = node\nnode.left = grand_child\n# \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nself.__update_height(node)\nself.__update_height(child)\n# \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child\n
avl_tree.go
/* \u53f3\u65cb\u64cd\u4f5c */\nfunc rightRotate(node *TreeNode) *TreeNode {\nchild := node.Left\ngrandChild := child.Right\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild.Right = node\nnode.Left = grandChild\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node)\nupdateHeight(child)\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child\n}\n
avl_tree.js
/* \u53f3\u65cb\u64cd\u4f5c */\nrightRotate(node) {\nconst child = node.left;\nconst grandChild = child.right;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild.right = node;\nnode.left = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nthis.updateHeight(node);\nthis.updateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.ts
/* \u53f3\u65cb\u64cd\u4f5c */\nrightRotate(node: TreeNode): TreeNode {\nconst child = node.left;\nconst grandChild = child.right;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild.right = node;\nnode.left = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nthis.updateHeight(node);\nthis.updateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u53f3\u65cb\u64cd\u4f5c */\nTreeNode? rightRotate(TreeNode? node)\n{\nTreeNode? child = node.left;\nTreeNode? grandChild = child?.right;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild.right = node;\nnode.left = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node);\nupdateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.swift
/* \u53f3\u65cb\u64cd\u4f5c */\nfunc rightRotate(node: TreeNode?) -> TreeNode? {\nlet child = node?.left\nlet grandChild = child?.right\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u53f3\u65cb\u8f6c\nchild?.right = node\nnode?.left = grandChild\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node: node)\nupdateHeight(node: child)\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child\n}\n
avl_tree.zig
\n
"},{"location":"chapter_tree/avl_tree/#case-2-","title":"Case 2 - \u5de6\u65cb","text":"

\u7c7b\u4f3c\u5730\uff0c\u5982\u679c\u5c06\u53d6\u4e0a\u8ff0\u5931\u8861\u4e8c\u53c9\u6811\u7684\u201c\u955c\u50cf\u201d\uff0c\u90a3\u4e48\u5219\u9700\u8981\u300c\u5de6\u65cb\u300d\u64cd\u4f5c\u3002

\u540c\u7406\uff0c\u82e5\u7ed3\u70b9 child \u672c\u8eab\u6709\u5de6\u5b50\u7ed3\u70b9\uff08\u8bb0\u4e3a grandChild \uff09\uff0c\u5219\u9700\u8981\u5728\u300c\u5de6\u65cb\u300d\u4e2d\u6dfb\u52a0\u4e00\u6b65\uff1a\u5c06 grandChild \u4f5c\u4e3a node \u7684\u53f3\u5b50\u7ed3\u70b9\u3002

\u89c2\u5bdf\u53d1\u73b0\uff0c\u300c\u5de6\u65cb\u300d\u548c\u300c\u53f3\u65cb\u300d\u64cd\u4f5c\u662f\u955c\u50cf\u5bf9\u79f0\u7684\uff0c\u4e24\u8005\u5bf9\u5e94\u89e3\u51b3\u7684\u4e24\u79cd\u5931\u8861\u60c5\u51b5\u4e5f\u662f\u5bf9\u79f0\u7684\u3002\u6839\u636e\u5bf9\u79f0\u6027\uff0c\u6211\u4eec\u53ef\u4ee5\u5f88\u65b9\u4fbf\u5730\u4ece\u300c\u53f3\u65cb\u300d\u63a8\u5bfc\u51fa\u300c\u5de6\u65cb\u300d\u3002\u5177\u4f53\u5730\uff0c\u53ea\u9700\u5c06\u300c\u53f3\u65cb\u300d\u4ee3\u7801\u4e2d\u7684\u628a\u6240\u6709\u7684 left \u66ff\u6362\u4e3a right \u3001\u6240\u6709\u7684 right \u66ff\u6362\u4e3a left \uff0c\u5373\u53ef\u5f97\u5230\u300c\u5de6\u65cb\u300d\u4ee3\u7801\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u5de6\u65cb\u64cd\u4f5c */\nTreeNode leftRotate(TreeNode node) {\nTreeNode child = node.right;\nTreeNode grandChild = child.left;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild.left = node;\nnode.right = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node);\nupdateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.cpp
/* \u5de6\u65cb\u64cd\u4f5c */\nTreeNode* leftRotate(TreeNode* node) {\nTreeNode* child = node->right;\nTreeNode* grandChild = child->left;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild->left = node;\nnode->right = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node);\nupdateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.py
\"\"\" \u5de6\u65cb\u64cd\u4f5c \"\"\"\ndef __left_rotate(self, node: Optional[TreeNode]) -> TreeNode:\nchild = node.right\ngrand_child = child.left\n# \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild.left = node\nnode.right = grand_child\n# \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nself.__update_height(node)\nself.__update_height(child)\n# \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child\n
avl_tree.go
/* \u5de6\u65cb\u64cd\u4f5c */\nfunc leftRotate(node *TreeNode) *TreeNode {\nchild := node.Right\ngrandChild := child.Left\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild.Left = node\nnode.Right = grandChild\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node)\nupdateHeight(child)\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child\n}\n
avl_tree.js
/* \u5de6\u65cb\u64cd\u4f5c */\nleftRotate(node) {\nconst child = node.right;\nconst grandChild = child.left;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild.left = node;\nnode.right = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nthis.updateHeight(node);\nthis.updateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.ts
/* \u5de6\u65cb\u64cd\u4f5c */\nleftRotate(node: TreeNode): TreeNode {\nconst child = node.right;\nconst grandChild = child.left;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild.left = node;\nnode.right = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nthis.updateHeight(node);\nthis.updateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u5de6\u65cb\u64cd\u4f5c */\nTreeNode? leftRotate(TreeNode? node)\n{\nTreeNode? child = node.right;\nTreeNode? grandChild = child?.left;\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild.left = node;\nnode.right = grandChild;\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node);\nupdateHeight(child);\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child;\n}\n
avl_tree.swift
/* \u5de6\u65cb\u64cd\u4f5c */\nfunc leftRotate(node: TreeNode?) -> TreeNode? {\nlet child = node?.right\nlet grandChild = child?.left\n// \u4ee5 child \u4e3a\u539f\u70b9\uff0c\u5c06 node \u5411\u5de6\u65cb\u8f6c\nchild?.left = node\nnode?.right = grandChild\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node: node)\nupdateHeight(node: child)\n// \u8fd4\u56de\u65cb\u8f6c\u540e\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn child\n}\n
avl_tree.zig
\n
"},{"location":"chapter_tree/avl_tree/#case-3-","title":"Case 3 - \u5148\u5de6\u540e\u53f3","text":"

\u5bf9\u4e8e\u4e0b\u56fe\u7684\u5931\u8861\u7ed3\u70b9 3 \uff0c\u5355\u4e00\u4f7f\u7528\u5de6\u65cb\u6216\u53f3\u65cb\u90fd\u65e0\u6cd5\u4f7f\u5b50\u6811\u6062\u590d\u5e73\u8861\uff0c\u6b64\u65f6\u9700\u8981\u300c\u5148\u5de6\u65cb\u540e\u53f3\u65cb\u300d\uff0c\u5373\u5148\u5bf9 child \u6267\u884c\u300c\u5de6\u65cb\u300d\uff0c\u518d\u5bf9 node \u6267\u884c\u300c\u53f3\u65cb\u300d\u3002

"},{"location":"chapter_tree/avl_tree/#case-4-","title":"Case 4 - \u5148\u53f3\u540e\u5de6","text":"

\u540c\u7406\uff0c\u53d6\u4ee5\u4e0a\u5931\u8861\u4e8c\u53c9\u6811\u7684\u955c\u50cf\uff0c\u5219\u9700\u8981\u300c\u5148\u53f3\u65cb\u540e\u5de6\u65cb\u300d\uff0c\u5373\u5148\u5bf9 child \u6267\u884c\u300c\u53f3\u65cb\u300d\uff0c\u7136\u540e\u5bf9 node \u6267\u884c\u300c\u5de6\u65cb\u300d\u3002

"},{"location":"chapter_tree/avl_tree/#_3","title":"\u65cb\u8f6c\u7684\u9009\u62e9","text":"

\u4e0b\u56fe\u63cf\u8ff0\u7684\u56db\u79cd\u5931\u8861\u60c5\u51b5\u4e0e\u4e0a\u8ff0 Cases \u9010\u4e2a\u5bf9\u5e94\uff0c\u5206\u522b\u9700\u91c7\u7528 \u53f3\u65cb\u3001\u5de6\u65cb\u3001\u5148\u53f3\u540e\u5de6\u3001\u5148\u5de6\u540e\u53f3 \u7684\u65cb\u8f6c\u64cd\u4f5c\u3002

\u5177\u4f53\u5730\uff0c\u5728\u4ee3\u7801\u4e2d\u4f7f\u7528 \u5931\u8861\u7ed3\u70b9\u7684\u5e73\u8861\u56e0\u5b50\u3001\u8f83\u9ad8\u4e00\u4fa7\u5b50\u7ed3\u70b9\u7684\u5e73\u8861\u56e0\u5b50 \u6765\u786e\u5b9a\u5931\u8861\u7ed3\u70b9\u5c5e\u4e8e\u4e0a\u56fe\u4e2d\u7684\u54ea\u79cd\u60c5\u51b5\u3002

\u5931\u8861\u7ed3\u70b9\u7684\u5e73\u8861\u56e0\u5b50 \u5b50\u7ed3\u70b9\u7684\u5e73\u8861\u56e0\u5b50 \u5e94\u91c7\u7528\u7684\u65cb\u8f6c\u65b9\u6cd5 \\(>0\\) \uff08\u5373\u5de6\u504f\u6811\uff09 \\(\\geq 0\\) \u53f3\u65cb \\(>0\\) \uff08\u5373\u5de6\u504f\u6811\uff09 \\(<0\\) \u5148\u5de6\u65cb\u540e\u53f3\u65cb \\(<0\\) \uff08\u5373\u53f3\u504f\u6811\uff09 \\(\\leq 0\\) \u5de6\u65cb \\(<0\\) \uff08\u5373\u53f3\u504f\u6811\uff09 \\(>0\\) \u5148\u53f3\u65cb\u540e\u5de6\u65cb

\u4e3a\u65b9\u4fbf\u4f7f\u7528\uff0c\u6211\u4eec\u5c06\u65cb\u8f6c\u64cd\u4f5c\u5c01\u88c5\u6210\u4e00\u4e2a\u51fd\u6570\u3002\u81f3\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u6b64\u51fd\u6570\u6765\u65cb\u8f6c\u5404\u79cd\u5931\u8861\u60c5\u51b5\uff0c\u4f7f\u5931\u8861\u7ed3\u70b9\u91cd\u65b0\u6062\u590d\u5e73\u8861\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nTreeNode rotate(TreeNode node) {\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nint balanceFactor = balanceFactor(node);\n// \u5de6\u504f\u6811\nif (balanceFactor > 1) {\nif (balanceFactor(node.left) >= 0) {\n// \u53f3\u65cb\nreturn rightRotate(node);\n} else {\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode.left = leftRotate(node.left);\nreturn rightRotate(node);\n}\n}\n// \u53f3\u504f\u6811\nif (balanceFactor < -1) {\nif (balanceFactor(node.right) <= 0) {\n// \u5de6\u65cb\nreturn leftRotate(node);\n} else {\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode.right = rightRotate(node.right);\nreturn leftRotate(node);\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node;\n}\n
avl_tree.cpp
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nTreeNode* rotate(TreeNode* node) {\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nint _balanceFactor = balanceFactor(node);\n// \u5de6\u504f\u6811\nif (_balanceFactor > 1) {\nif (balanceFactor(node->left) >= 0) {\n// \u53f3\u65cb\nreturn rightRotate(node);\n} else {\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode->left = leftRotate(node->left);\nreturn rightRotate(node);\n}\n}\n// \u53f3\u504f\u6811\nif (_balanceFactor < -1) {\nif (balanceFactor(node->right) <= 0) {\n// \u5de6\u65cb\nreturn leftRotate(node);\n} else {\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode->right = rightRotate(node->right);\nreturn leftRotate(node);\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node;\n}\n
avl_tree.py
\"\"\" \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 \"\"\"\ndef __rotate(self, node: Optional[TreeNode]) -> TreeNode:\n# \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nbalance_factor = self.balance_factor(node)\n# \u5de6\u504f\u6811\nif balance_factor > 1:\nif self.balance_factor(node.left) >= 0:\n# \u53f3\u65cb\nreturn self.__right_rotate(node)\nelse:\n# \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode.left = self.__left_rotate(node.left)\nreturn self.__right_rotate(node)\n# \u53f3\u504f\u6811\nelif balance_factor < -1:\nif self.balance_factor(node.right) <= 0:\n# \u5de6\u65cb\nreturn self.__left_rotate(node)\nelse:\n# \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode.right = self.__right_rotate(node.right)\nreturn self.__left_rotate(node)\n# \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node\n
avl_tree.go
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nfunc rotate(node *TreeNode) *TreeNode {\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\n// Go \u63a8\u8350\u77ed\u53d8\u91cf\uff0c\u8fd9\u91cc bf \u6307\u4ee3 balanceFactor\nbf := balanceFactor(node)\n// \u5de6\u504f\u6811\nif bf > 1 {\nif balanceFactor(node.Left) >= 0 {\n// \u53f3\u65cb\nreturn rightRotate(node)\n} else {\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode.Left = leftRotate(node.Left)\nreturn rightRotate(node)\n}\n}\n// \u53f3\u504f\u6811\nif bf < -1 {\nif balanceFactor(node.Right) <= 0 {\n// \u5de6\u65cb\nreturn leftRotate(node)\n} else {\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode.Right = rightRotate(node.Right)\nreturn leftRotate(node)\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node\n}\n
avl_tree.js
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nrotate(node) {\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nconst balanceFactor = this.balanceFactor(node);\n// \u5de6\u504f\u6811\nif (balanceFactor > 1) {\nif (this.balanceFactor(node.left) >= 0) {\n// \u53f3\u65cb\nreturn this.rightRotate(node);\n} else {\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode.left = this.leftRotate(node.left);\nreturn this.rightRotate(node);\n}\n}\n// \u53f3\u504f\u6811\nif (balanceFactor < -1) {\nif (this.balanceFactor(node.right) <= 0) {\n// \u5de6\u65cb\nreturn this.leftRotate(node);\n} else {\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode.right = this.rightRotate(node.right);\nreturn this.leftRotate(node);\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node;\n}\n
avl_tree.ts
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nrotate(node: TreeNode): TreeNode {\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nconst balanceFactor = this.balanceFactor(node);\n// \u5de6\u504f\u6811\nif (balanceFactor > 1) {\nif (this.balanceFactor(node.left) >= 0) {\n// \u53f3\u65cb\nreturn this.rightRotate(node);\n} else {\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode.left = this.leftRotate(node.left);\nreturn this.rightRotate(node);\n}\n}\n// \u53f3\u504f\u6811\nif (balanceFactor < -1) {\nif (this.balanceFactor(node.right) <= 0) {\n// \u5de6\u65cb\nreturn this.leftRotate(node);\n} else {\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode.right = this.rightRotate(node.right);\nreturn this.leftRotate(node);\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node;\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nTreeNode? rotate(TreeNode? node)\n{\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nint balanceFactorInt = balanceFactor(node);\n// \u5de6\u504f\u6811\nif (balanceFactorInt > 1)\n{\nif (balanceFactor(node.left) >= 0)\n{\n// \u53f3\u65cb\nreturn rightRotate(node);\n}\nelse\n{\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode.left = leftRotate(node?.left);\nreturn rightRotate(node);\n}\n}\n// \u53f3\u504f\u6811\nif (balanceFactorInt < -1)\n{\nif (balanceFactor(node.right) <= 0)\n{\n// \u5de6\u65cb\nreturn leftRotate(node);\n}\nelse\n{\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode.right = rightRotate(node?.right);\nreturn leftRotate(node);\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node;\n}\n
avl_tree.swift
/* \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nfunc rotate(node: TreeNode?) -> TreeNode? {\n// \u83b7\u53d6\u7ed3\u70b9 node \u7684\u5e73\u8861\u56e0\u5b50\nlet balanceFactor = balanceFactor(node: node)\n// \u5de6\u504f\u6811\nif balanceFactor > 1 {\nif self.balanceFactor(node: node?.left) >= 0 {\n// \u53f3\u65cb\nreturn rightRotate(node: node)\n} else {\n// \u5148\u5de6\u65cb\u540e\u53f3\u65cb\nnode?.left = leftRotate(node: node?.left)\nreturn rightRotate(node: node)\n}\n}\n// \u53f3\u504f\u6811\nif balanceFactor < -1 {\nif self.balanceFactor(node: node?.right) <= 0 {\n// \u5de6\u65cb\nreturn leftRotate(node: node)\n} else {\n// \u5148\u53f3\u65cb\u540e\u5de6\u65cb\nnode?.right = rightRotate(node: node?.right)\nreturn leftRotate(node: node)\n}\n}\n// \u5e73\u8861\u6811\uff0c\u65e0\u9700\u65cb\u8f6c\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node\n}\n
avl_tree.zig
\n
"},{"location":"chapter_tree/avl_tree/#743-avl","title":"7.4.3. AVL \u6811\u5e38\u7528\u64cd\u4f5c","text":""},{"location":"chapter_tree/avl_tree/#_4","title":"\u63d2\u5165\u7ed3\u70b9","text":"

\u300cAVL \u6811\u300d\u7684\u7ed3\u70b9\u63d2\u5165\u64cd\u4f5c\u4e0e\u300c\u4e8c\u53c9\u641c\u7d22\u6811\u300d\u4e3b\u4f53\u7c7b\u4f3c\u3002\u4e0d\u540c\u7684\u662f\uff0c\u5728\u63d2\u5165\u7ed3\u70b9\u540e\uff0c\u4ece\u8be5\u7ed3\u70b9\u5230\u6839\u7ed3\u70b9\u7684\u8def\u5f84\u4e0a\u4f1a\u51fa\u73b0\u4e00\u7cfb\u5217\u300c\u5931\u8861\u7ed3\u70b9\u300d\u3002\u6240\u4ee5\uff0c\u6211\u4eec\u9700\u8981\u4ece\u8be5\u7ed3\u70b9\u5f00\u59cb\uff0c\u4ece\u5e95\u81f3\u9876\u5730\u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u6240\u6709\u5931\u8861\u7ed3\u70b9\u6062\u590d\u5e73\u8861\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u63d2\u5165\u7ed3\u70b9 */\nTreeNode insert(int val) {\nroot = insertHelper(root, val);\nreturn root;\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nTreeNode insertHelper(TreeNode node, int val) {\nif (node == null) return new TreeNode(val);\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif (val < node.val)\nnode.left = insertHelper(node.left, val);\nelse if (val > node.val)\nnode.right = insertHelper(node.right, val);\nelse\nreturn node;     // \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\nupdateHeight(node);  // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.cpp
/* \u63d2\u5165\u7ed3\u70b9 */\nTreeNode* insert(int val) {\nroot = insertHelper(root, val);\nreturn root;\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nTreeNode* insertHelper(TreeNode* node, int val) {\nif (node == nullptr) return new TreeNode(val);\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif (val < node->val)\nnode->left = insertHelper(node->left, val);\nelse if (val > node->val)\nnode->right = insertHelper(node->right, val);\nelse\nreturn node;     // \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\nupdateHeight(node);  // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.py
\"\"\" \u63d2\u5165\u7ed3\u70b9 \"\"\"\ndef insert(self, val) -> TreeNode:\nself.root = self.__insert_helper(self.root, val)\nreturn self.root\n\"\"\" \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09\"\"\"\ndef __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode:\nif node is None:\nreturn TreeNode(val)\n# 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9\nif val < node.val:\nnode.left = self.__insert_helper(node.left, val)\nelif val > node.val:\nnode.right = self.__insert_helper(node.right, val)\nelse:\n# \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node\n# \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nself.__update_height(node)\n# 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861\nreturn self.__rotate(node)\n
avl_tree.go
/* \u63d2\u5165\u7ed3\u70b9 */\nfunc (t *avlTree) insert(val int) *TreeNode {\nt.root = insertHelper(t.root, val)\nreturn t.root\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nfunc insertHelper(node *TreeNode, val int) *TreeNode {\nif node == nil {\nreturn NewTreeNode(val)\n}\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif val < node.Val {\nnode.Left = insertHelper(node.Left, val)\n} else if val > node.Val {\nnode.Right = insertHelper(node.Right, val)\n} else {\n// \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\nreturn node\n}\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node)\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node)\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node\n}\n
avl_tree.js
/* \u63d2\u5165\u7ed3\u70b9 */\ninsert(val) {\nthis.root = this.insertHelper(this.root, val);\nreturn this.root;\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\ninsertHelper(node, val) {\nif (node === null) return new TreeNode(val);\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif (val < node.val) node.left = this.insertHelper(node.left, val);\nelse if (val > node.val) node.right = this.insertHelper(node.right, val);\nelse return node; // \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\nthis.updateHeight(node); // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = this.rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.ts
/* \u63d2\u5165\u7ed3\u70b9 */\ninsert(val: number): TreeNode {\nthis.root = this.insertHelper(this.root, val);\nreturn this.root;\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\ninsertHelper(node: TreeNode, val: number): TreeNode {\nif (node === null) return new TreeNode(val);\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif (val < node.val) {\nnode.left = this.insertHelper(node.left, val);\n} else if (val > node.val) {\nnode.right = this.insertHelper(node.right, val);\n} else {\nreturn node; // \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\n}\nthis.updateHeight(node); // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = this.rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u63d2\u5165\u7ed3\u70b9 */\npublic TreeNode? insert(int val)\n{\nroot = insertHelper(root, val);\nreturn root;\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nprivate TreeNode? insertHelper(TreeNode? node, int val)\n{\nif (node == null) return new TreeNode(val);\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif (val < node.val)\nnode.left = insertHelper(node.left, val);\nelse if (val > node.val)\nnode.right = insertHelper(node.right, val);\nelse\nreturn node;     // \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\nupdateHeight(node);  // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.swift
/* \u63d2\u5165\u7ed3\u70b9 */\n@discardableResult\nfunc insert(val: Int) -> TreeNode? {\nroot = insertHelper(node: root, val: val)\nreturn root\n}\n/* \u9012\u5f52\u63d2\u5165\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nfunc insertHelper(node: TreeNode?, val: Int) -> TreeNode? {\nvar node = node\nif node == nil {\nreturn TreeNode(x: val)\n}\n/* 1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u5e76\u63d2\u5165\u7ed3\u70b9 */\nif val < node!.val {\nnode?.left = insertHelper(node: node?.left, val: val)\n} else if val > node!.val {\nnode?.right = insertHelper(node: node?.right, val: val)\n} else {\nreturn node // \u91cd\u590d\u7ed3\u70b9\u4e0d\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\n}\nupdateHeight(node: node) // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node: node)\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node\n}\n
avl_tree.zig
\n
"},{"location":"chapter_tree/avl_tree/#_5","title":"\u5220\u9664\u7ed3\u70b9","text":"

\u300cAVL \u6811\u300d\u5220\u9664\u7ed3\u70b9\u64cd\u4f5c\u4e0e\u300c\u4e8c\u53c9\u641c\u7d22\u6811\u300d\u5220\u9664\u7ed3\u70b9\u64cd\u4f5c\u603b\u4f53\u76f8\u540c\u3002\u7c7b\u4f3c\u5730\uff0c\u5728\u5220\u9664\u7ed3\u70b9\u540e\uff0c\u4e5f\u9700\u8981\u4ece\u5e95\u81f3\u9876\u5730\u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u6240\u6709\u5931\u8861\u7ed3\u70b9\u6062\u590d\u5e73\u8861\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig avl_tree.java
/* \u5220\u9664\u7ed3\u70b9 */\nTreeNode remove(int val) {\nroot = removeHelper(root, val);\nreturn root;\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nTreeNode removeHelper(TreeNode node, int val) {\nif (node == null) return null;\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif (val < node.val)\nnode.left = removeHelper(node.left, val);\nelse if (val > node.val)\nnode.right = removeHelper(node.right, val);\nelse {\nif (node.left == null || node.right == null) {\nTreeNode child = node.left != null ? node.left : node.right;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif (child == null)\nreturn null;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nelse\nnode = child;\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\nTreeNode temp = getInOrderNext(node.right);\nnode.right = removeHelper(node.right, temp.val);\nnode.val = temp.val;\n}\n}\nupdateHeight(node);  // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nTreeNode getInOrderNext(TreeNode node) {\nif (node == null) return node;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (node.left != null) {\nnode = node.left;\n}\nreturn node;\n}\n
avl_tree.cpp
/* \u5220\u9664\u7ed3\u70b9 */\nTreeNode* remove(int val) {\nroot = removeHelper(root, val);\nreturn root;\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nTreeNode* removeHelper(TreeNode* node, int val) {\nif (node == nullptr) return nullptr;\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif (val < node->val)\nnode->left = removeHelper(node->left, val);\nelse if (val > node->val)\nnode->right = removeHelper(node->right, val);\nelse {\nif (node->left == nullptr || node->right == nullptr) {\nTreeNode* child = node->left != nullptr ? node->left : node->right;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif (child == nullptr) {\ndelete node;\nreturn nullptr;\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nelse {\ndelete node;\nnode = child;\n}\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\nTreeNode* temp = getInOrderNext(node->right);\nnode->right = removeHelper(node->right, temp->val);\nnode->val = temp->val;\n}\n}\nupdateHeight(node);  // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.py
\"\"\" \u5220\u9664\u7ed3\u70b9 \"\"\"\ndef remove(self, val: int):\nroot = self.__remove_helper(self.root, val)\nreturn root\n\"\"\" \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 \"\"\"\ndef __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]:\nif node is None:\nreturn None\n# 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b\nif val < node.val:\nnode.left = self.__remove_helper(node.left, val)\nelif val > node.val:\nnode.right = self.__remove_helper(node.right, val)\nelse:\nif node.left is None or node.right is None:\nchild = node.left or node.right\n# \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif child is None:\nreturn None\n# \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nelse:\nnode = child\nelse:  # \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\ntemp = self.__get_inorder_next(node.right)\nnode.right = self.__remove_helper(node.right, temp.val)\nnode.val = temp.val\n# \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nself.__update_height(node)\n# 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861\nreturn self.__rotate(node)\n
avl_tree.go
/* \u5220\u9664\u7ed3\u70b9 */\nfunc (t *avlTree) remove(val int) *TreeNode {\nroot := removeHelper(t.root, val)\nreturn root\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nfunc removeHelper(node *TreeNode, val int) *TreeNode {\nif node == nil {\nreturn nil\n}\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif val < node.Val {\nnode.Left = removeHelper(node.Left, val)\n} else if val > node.Val {\nnode.Right = removeHelper(node.Right, val)\n} else {\nif node.Left == nil || node.Right == nil {\nchild := node.Left\nif node.Right != nil {\nchild = node.Right\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif child == nil {\nreturn nil\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nnode = child\n}\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\ntemp := getInOrderNext(node.Right)\nnode.Right = removeHelper(node.Right, temp.Val)\nnode.Val = temp.Val\n}\n}\n// \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\nupdateHeight(node)\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node)\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node\n}\n
avl_tree.js
/* \u5220\u9664\u7ed3\u70b9 */\nremove(val) {\nthis.root = this.removeHelper(this.root, val);\nreturn this.root;\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nremoveHelper(node, val) {\nif (node === null) return null;\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif (val < node.val) node.left = this.removeHelper(node.left, val);\nelse if (val > node.val) node.right = this.removeHelper(node.right, val);\nelse {\nif (node.left === null || node.right === null) {\nconst child = node.left !== null ? node.left : node.right;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif (child === null) return null;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nelse node = child;\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\nconst temp = this.getInOrderNext(node.right);\nnode.right = this.removeHelper(node.right, temp.val);\nnode.val = temp.val;\n}\n}\nthis.updateHeight(node); // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = this.rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\ngetInOrderNext(node) {\nif (node === null) return node;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (node.left !== null) {\nnode = node.left;\n}\nreturn node;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\ngetInOrderNext(node) {\nif (node === null) return node;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (node.left !== null) {\nnode = node.left;\n}\nreturn node;\n}\n
avl_tree.ts
/* \u5220\u9664\u7ed3\u70b9 */\nremove(val: number): TreeNode {\nthis.root = this.removeHelper(this.root, val);\nreturn this.root;\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nremoveHelper(node: TreeNode, val: number): TreeNode {\nif (node === null) return null;\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif (val < node.val) {\nnode.left = this.removeHelper(node.left, val);\n} else if (val > node.val) {\nnode.right = this.removeHelper(node.right, val);\n} else {\nif (node.left === null || node.right === null) {\nconst child = node.left !== null ? node.left : node.right;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif (child === null) {\nreturn null;\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nnode = child;\n}\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\nconst temp = this.getInOrderNext(node.right);\nnode.right = this.removeHelper(node.right, temp.val);\nnode.val = temp.val;\n}\n}\nthis.updateHeight(node); // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = this.rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\ngetInOrderNext(node: TreeNode): TreeNode {\nif (node === null) return node;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (node.left !== null) {\nnode = node.left;\n}\nreturn node;\n}\n
avl_tree.c
\n
avl_tree.cs
/* \u5220\u9664\u7ed3\u70b9 */\npublic TreeNode? remove(int val)\n{\nroot = removeHelper(root, val);\nreturn root;\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nprivate TreeNode? removeHelper(TreeNode? node, int val)\n{\nif (node == null) return null;\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif (val < node.val)\nnode.left = removeHelper(node.left, val);\nelse if (val > node.val)\nnode.right = removeHelper(node.right, val);\nelse\n{\nif (node.left == null || node.right == null)\n{\nTreeNode? child = node.left != null ? node.left : node.right;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif (child == null)\nreturn null;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nelse\nnode = child;\n}\nelse\n{\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\nTreeNode? temp = getInOrderNext(node.right);\nnode.right = removeHelper(node.right, temp.val);\nnode.val = temp.val;\n}\n}\nupdateHeight(node);  // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node);\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node;\n}\n
avl_tree.swift
/* \u5220\u9664\u7ed3\u70b9 */\n@discardableResult\nfunc remove(val: Int) -> TreeNode? {\nroot = removeHelper(node: root, val: val)\nreturn root\n}\n/* \u9012\u5f52\u5220\u9664\u7ed3\u70b9\uff08\u8f85\u52a9\u51fd\u6570\uff09 */\nfunc removeHelper(node: TreeNode?, val: Int) -> TreeNode? {\nvar node = node\nif node == nil {\nreturn nil\n}\n/* 1. \u67e5\u627e\u7ed3\u70b9\uff0c\u5e76\u5220\u9664\u4e4b */\nif val < node!.val {\nnode?.left = removeHelper(node: node?.left, val: val)\n} else if val > node!.val {\nnode?.right = removeHelper(node: node?.right, val: val)\n} else {\nif node?.left == nil || node?.right == nil {\nlet child = node?.left != nil ? node?.left : node?.right\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 \uff0c\u76f4\u63a5\u5220\u9664 node \u5e76\u8fd4\u56de\nif child == nil {\nreturn nil\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 1 \uff0c\u76f4\u63a5\u5220\u9664 node\nelse {\nnode = child\n}\n} else {\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2 \uff0c\u5219\u5c06\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e2a\u7ed3\u70b9\u5220\u9664\uff0c\u5e76\u7528\u8be5\u7ed3\u70b9\u66ff\u6362\u5f53\u524d\u7ed3\u70b9\nlet temp = getInOrderNext(node: node?.right)\nnode?.right = removeHelper(node: node?.right, val: temp!.val)\nnode?.val = temp!.val\n}\n}\nupdateHeight(node: node) // \u66f4\u65b0\u7ed3\u70b9\u9ad8\u5ea6\n/* 2. \u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u8be5\u5b50\u6811\u91cd\u65b0\u6062\u590d\u5e73\u8861 */\nnode = rotate(node: node)\n// \u8fd4\u56de\u5b50\u6811\u7684\u6839\u7ed3\u70b9\nreturn node\n}\n
avl_tree.zig
\n
"},{"location":"chapter_tree/avl_tree/#_6","title":"\u67e5\u627e\u7ed3\u70b9","text":"

\u300cAVL \u6811\u300d\u7684\u7ed3\u70b9\u67e5\u627e\u64cd\u4f5c\u4e0e\u300c\u4e8c\u53c9\u641c\u7d22\u6811\u300d\u4e00\u81f4\uff0c\u5728\u6b64\u4e0d\u518d\u8d58\u8ff0\u3002

"},{"location":"chapter_tree/avl_tree/#744-avl","title":"7.4.4. AVL \u6811\u5178\u578b\u5e94\u7528","text":"
  • \u7ec4\u7ec7\u5b58\u50a8\u5927\u578b\u6570\u636e\uff0c\u9002\u7528\u4e8e\u9ad8\u9891\u67e5\u627e\u3001\u4f4e\u9891\u589e\u5220\u573a\u666f\uff1b
  • \u7528\u4e8e\u5efa\u7acb\u6570\u636e\u5e93\u4e2d\u7684\u7d22\u5f15\u7cfb\u7edf\uff1b

\u4e3a\u4ec0\u4e48\u7ea2\u9ed1\u6811\u6bd4 AVL \u6811\u66f4\u53d7\u6b22\u8fce\uff1f

\u7ea2\u9ed1\u6811\u7684\u5e73\u8861\u6761\u4ef6\u76f8\u5bf9\u5bbd\u677e\uff0c\u56e0\u6b64\u5728\u7ea2\u9ed1\u6811\u4e2d\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9\u6240\u9700\u7684\u65cb\u8f6c\u64cd\u4f5c\u76f8\u5bf9\u66f4\u5c11\uff0c\u7ed3\u70b9\u589e\u5220\u64cd\u4f5c\u76f8\u6bd4 AVL \u6811\u7684\u6548\u7387\u66f4\u9ad8\u3002

"},{"location":"chapter_tree/binary_search_tree/","title":"7.3. \u4e8c\u53c9\u641c\u7d22\u6811","text":"

\u300c\u4e8c\u53c9\u641c\u7d22\u6811 Binary Search Tree\u300d\u6ee1\u8db3\u4ee5\u4e0b\u6761\u4ef6\uff1a

  1. \u5bf9\u4e8e\u6839\u7ed3\u70b9\uff0c\u5de6\u5b50\u6811\u4e2d\u6240\u6709\u7ed3\u70b9\u7684\u503c \\(<\\) \u6839\u7ed3\u70b9\u7684\u503c \\(<\\) \u53f3\u5b50\u6811\u4e2d\u6240\u6709\u7ed3\u70b9\u7684\u503c\uff1b
  2. \u4efb\u610f\u7ed3\u70b9\u7684\u5de6\u5b50\u6811\u548c\u53f3\u5b50\u6811\u4e5f\u662f\u4e8c\u53c9\u641c\u7d22\u6811\uff0c\u5373\u4e5f\u6ee1\u8db3\u6761\u4ef6 1. \uff1b

"},{"location":"chapter_tree/binary_search_tree/#731","title":"7.3.1. \u4e8c\u53c9\u641c\u7d22\u6811\u7684\u64cd\u4f5c","text":""},{"location":"chapter_tree/binary_search_tree/#_1","title":"\u67e5\u627e\u7ed3\u70b9","text":"

\u7ed9\u5b9a\u76ee\u6807\u7ed3\u70b9\u503c num \uff0c\u53ef\u4ee5\u6839\u636e\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u6027\u8d28\u6765\u67e5\u627e\u3002\u6211\u4eec\u58f0\u660e\u4e00\u4e2a\u7ed3\u70b9 cur \uff0c\u4ece\u4e8c\u53c9\u6811\u7684\u6839\u7ed3\u70b9 root \u51fa\u53d1\uff0c\u5faa\u73af\u6bd4\u8f83\u7ed3\u70b9\u503c cur.val \u548c num \u4e4b\u95f4\u7684\u5927\u5c0f\u5173\u7cfb

  • \u82e5 cur.val < num \uff0c\u8bf4\u660e\u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\uff0c\u56e0\u6b64\u6267\u884c cur = cur.right \uff1b
  • \u82e5 cur.val > num \uff0c\u8bf4\u660e\u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\uff0c\u56e0\u6b64\u6267\u884c cur = cur.left \uff1b
  • \u82e5 cur.val = num \uff0c\u8bf4\u660e\u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\u5e76\u8fd4\u56de\u8be5\u7ed3\u70b9\u5373\u53ef\uff1b
Step 1Step 2Step 3Step 4

\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u67e5\u627e\u64cd\u4f5c\u548c\u4e8c\u5206\u67e5\u627e\u7b97\u6cd5\u5982\u51fa\u4e00\u8f99\uff0c\u4e5f\u662f\u5728\u6bcf\u8f6e\u6392\u9664\u4e00\u534a\u60c5\u51b5\u3002\u5faa\u73af\u6b21\u6570\u6700\u591a\u4e3a\u4e8c\u53c9\u6811\u7684\u9ad8\u5ea6\uff0c\u5f53\u4e8c\u53c9\u6811\u5e73\u8861\u65f6\uff0c\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_search_tree.java
/* \u67e5\u627e\u7ed3\u70b9 */\nTreeNode search(int num) {\nTreeNode cur = root;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != null) {\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse if (cur.val > num) cur = cur.left;\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nelse break;\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn cur;\n}\n
binary_search_tree.cpp
/* \u67e5\u627e\u7ed3\u70b9 */\nTreeNode* search(int num) {\nTreeNode* cur = root;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != nullptr) {\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur->val < num) cur = cur->right;\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse if (cur->val > num) cur = cur->left;\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nelse break;\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn cur;\n}\n
binary_search_tree.py
\"\"\" \u67e5\u627e\u7ed3\u70b9 \"\"\"\ndef search(self, num: int) -> Optional[TreeNode]:\ncur = self.root\n# \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile cur is not None:\n# \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif cur.val < num:\ncur = cur.right\n# \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelif cur.val > num:\ncur = cur.left\n# \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nelse:\nbreak\nreturn cur\n
binary_search_tree.go
/* \u67e5\u627e\u7ed3\u70b9 */\nfunc (bst *binarySearchTree) search(num int) *TreeNode {\nnode := bst.root\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nfor node != nil {\nif node.Val < num {\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nnode = node.Right\n} else if node.Val > num {\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nnode = node.Left\n} else {\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nbreak\n}\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn node\n}\n
binary_search_tree.js
/* \u67e5\u627e\u7ed3\u70b9 */\nfunction search(num) {\nlet cur = root;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur !== null) {\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse if (cur.val > num) cur = cur.left;\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nelse break;\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn cur;\n}\n
binary_search_tree.ts
/* \u67e5\u627e\u7ed3\u70b9 */\nfunction search(num: number): TreeNode | null {\nlet cur = root;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur !== null) {\nif (cur.val < num) {\ncur = cur.right; // \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\n} else if (cur.val > num) {\ncur = cur.left; // \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\n} else {\nbreak; // \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\n}\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn cur;\n}\n
binary_search_tree.c
\n
binary_search_tree.cs
/* \u67e5\u627e\u7ed3\u70b9 */\nTreeNode? search(int num)\n{\nTreeNode? cur = root;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != null)\n{\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse if (cur.val > num) cur = cur.left;\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nelse break;\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn cur;\n}\n
binary_search_tree.swift
/* \u67e5\u627e\u7ed3\u70b9 */\nfunc search(num: Int) -> TreeNode? {\nvar cur = root\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile cur != nil {\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif cur!.val < num {\ncur = cur?.right\n}\n// \u76ee\u6807\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse if cur!.val > num {\ncur = cur?.left\n}\n// \u627e\u5230\u76ee\u6807\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nelse {\nbreak\n}\n}\n// \u8fd4\u56de\u76ee\u6807\u7ed3\u70b9\nreturn cur\n}\n
binary_search_tree.zig
\n
"},{"location":"chapter_tree/binary_search_tree/#_2","title":"\u63d2\u5165\u7ed3\u70b9","text":"

\u7ed9\u5b9a\u4e00\u4e2a\u5f85\u63d2\u5165\u5143\u7d20 num \uff0c\u4e3a\u4e86\u4fdd\u6301\u4e8c\u53c9\u641c\u7d22\u6811\u201c\u5de6\u5b50\u6811 < \u6839\u7ed3\u70b9 < \u53f3\u5b50\u6811\u201d\u7684\u6027\u8d28\uff0c\u63d2\u5165\u64cd\u4f5c\u5206\u4e3a\u4e24\u6b65\uff1a

  1. \u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff1a\u4e0e\u67e5\u627e\u64cd\u4f5c\u7c7b\u4f3c\uff0c\u6211\u4eec\u4ece\u6839\u7ed3\u70b9\u51fa\u53d1\uff0c\u6839\u636e\u5f53\u524d\u7ed3\u70b9\u503c\u548c num \u7684\u5927\u5c0f\u5173\u7cfb\u5faa\u73af\u5411\u4e0b\u641c\u7d22\uff0c\u76f4\u5230\u8d8a\u8fc7\u53f6\u7ed3\u70b9\uff08\u904d\u5386\u5230 \\(\\text{null}\\) \uff09\u65f6\u8df3\u51fa\u5faa\u73af\uff1b
  2. \u5728\u8be5\u4f4d\u7f6e\u63d2\u5165\u7ed3\u70b9\uff1a\u521d\u59cb\u5316\u7ed3\u70b9 num \uff0c\u5c06\u8be5\u7ed3\u70b9\u653e\u5230 \\(\\text{null}\\) \u7684\u4f4d\u7f6e \uff1b

\u4e8c\u53c9\u641c\u7d22\u6811\u4e0d\u5141\u8bb8\u5b58\u5728\u91cd\u590d\u7ed3\u70b9\uff0c\u5426\u5219\u5c06\u4f1a\u8fdd\u80cc\u5176\u5b9a\u4e49\u3002\u56e0\u6b64\u82e5\u5f85\u63d2\u5165\u7ed3\u70b9\u5728\u6811\u4e2d\u5df2\u7ecf\u5b58\u5728\uff0c\u5219\u4e0d\u6267\u884c\u63d2\u5165\uff0c\u76f4\u63a5\u8fd4\u56de\u5373\u53ef\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_search_tree.java
/* \u63d2\u5165\u7ed3\u70b9 */\nTreeNode insert(int num) {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root == null) return null;\nTreeNode cur = root, pre = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != null) {\n// \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\nif (cur.val == num) return null;\npre = cur;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur.left;\n}\n// \u63d2\u5165\u7ed3\u70b9 val\nTreeNode node = new TreeNode(num);\nif (pre.val < num) pre.right = node;\nelse pre.left = node;\nreturn node;\n}\n
binary_search_tree.cpp
/* \u63d2\u5165\u7ed3\u70b9 */\nTreeNode* insert(int num) {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root == nullptr) return nullptr;\nTreeNode *cur = root, *pre = nullptr;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != nullptr) {\n// \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\nif (cur->val == num) return nullptr;\npre = cur;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur->val < num) cur = cur->right;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur->left;\n}\n// \u63d2\u5165\u7ed3\u70b9 val\nTreeNode* node = new TreeNode(num);\nif (pre->val < num) pre->right = node;\nelse pre->left = node;\nreturn node;\n}\n
binary_search_tree.py
\"\"\" \u63d2\u5165\u7ed3\u70b9 \"\"\"\ndef insert(self, num: int) -> Optional[TreeNode]:\nroot = self.root\n# \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif root is None:\nreturn None\n# \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\ncur, pre = root, None\nwhile cur is not None:\n# \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\nif cur.val == num:\nreturn None\npre = cur\n# \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif cur.val < num:\ncur = cur.right\n# \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse:\ncur = cur.left\n# \u63d2\u5165\u7ed3\u70b9 val\nnode = TreeNode(num)\nif pre.val < num:\npre.right = node\nelse:\npre.left = node\nreturn node\n
binary_search_tree.go
/* \u63d2\u5165\u7ed3\u70b9 */\nfunc (bst *binarySearchTree) insert(num int) *TreeNode {\ncur := bst.root\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif cur == nil {\nreturn nil\n}\n// \u5f85\u63d2\u5165\u7ed3\u70b9\u4e4b\u524d\u7684\u7ed3\u70b9\u4f4d\u7f6e\nvar pre *TreeNode = nil\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nfor cur != nil {\nif cur.Val == num {\nreturn nil\n}\npre = cur\nif cur.Val < num {\ncur = cur.Right\n} else {\ncur = cur.Left\n}\n}\n// \u63d2\u5165\u7ed3\u70b9\nnode := NewTreeNode(num)\nif pre.Val < num {\npre.Right = node\n} else {\npre.Left = node\n}\nreturn cur\n}\n
binary_search_tree.js
/* \u63d2\u5165\u7ed3\u70b9 */\nfunction insert(num) {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root === null) return null;\nlet cur = root, pre = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur !== null) {\n// \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\nif (cur.val === num) return null;\npre = cur;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur.left;\n}\n// \u63d2\u5165\u7ed3\u70b9 val\nlet node = new Tree.TreeNode(num);\nif (pre.val < num) pre.right = node;\nelse pre.left = node;\nreturn node;\n}\n
binary_search_tree.ts
/* \u63d2\u5165\u7ed3\u70b9 */\nfunction insert(num: number): TreeNode | null {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root === null) {\nreturn null;\n}\nlet cur = root,\npre: TreeNode | null = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur !== null) {\nif (cur.val === num) {\nreturn null; // \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\n}\npre = cur;\nif (cur.val < num) {\ncur = cur.right as TreeNode; // \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\n} else {\ncur = cur.left as TreeNode; // \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\n}\n}\n// \u63d2\u5165\u7ed3\u70b9 val\nlet node = new TreeNode(num);\nif (pre!.val < num) {\npre!.right = node;\n} else {\npre!.left = node;\n}\nreturn node;\n}\n
binary_search_tree.c
\n
binary_search_tree.cs
/* \u63d2\u5165\u7ed3\u70b9 */\nTreeNode? insert(int num)\n{\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root == null) return null;\nTreeNode? cur = root, pre = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != null)\n{\n// \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\nif (cur.val == num) return null;\npre = cur;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur.left;\n}\n// \u63d2\u5165\u7ed3\u70b9 val\nTreeNode node = new TreeNode(num);\nif (pre != null)\n{\nif (pre.val < num) pre.right = node;\nelse pre.left = node;\n}\nreturn node;\n}\n
binary_search_tree.swift
/* \u63d2\u5165\u7ed3\u70b9 */\nfunc insert(num: Int) -> TreeNode? {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif root == nil {\nreturn nil\n}\nvar cur = root\nvar pre: TreeNode?\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile cur != nil {\n// \u627e\u5230\u91cd\u590d\u7ed3\u70b9\uff0c\u76f4\u63a5\u8fd4\u56de\nif cur!.val == num {\nreturn nil\n}\npre = cur\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif cur!.val < num {\ncur = cur?.right\n}\n// \u63d2\u5165\u4f4d\u7f6e\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse {\ncur = cur?.left\n}\n}\n// \u63d2\u5165\u7ed3\u70b9 val\nlet node = TreeNode(x: num)\nif pre!.val < num {\npre?.right = node\n} else {\npre?.left = node\n}\nreturn node\n}\n
binary_search_tree.zig
\n

\u4e3a\u4e86\u63d2\u5165\u7ed3\u70b9\uff0c\u9700\u8981\u501f\u52a9 \u8f85\u52a9\u7ed3\u70b9 pre \u4fdd\u5b58\u4e0a\u4e00\u8f6e\u5faa\u73af\u7684\u7ed3\u70b9\uff0c\u8fd9\u6837\u5728\u904d\u5386\u5230 \\(\\text{null}\\) \u65f6\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u83b7\u53d6\u5230\u5176\u7236\u7ed3\u70b9\uff0c\u4ece\u800c\u5b8c\u6210\u7ed3\u70b9\u63d2\u5165\u64cd\u4f5c\u3002

\u4e0e\u67e5\u627e\u7ed3\u70b9\u76f8\u540c\uff0c\u63d2\u5165\u7ed3\u70b9\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\u3002

"},{"location":"chapter_tree/binary_search_tree/#_3","title":"\u5220\u9664\u7ed3\u70b9","text":"

\u4e0e\u63d2\u5165\u7ed3\u70b9\u4e00\u6837\uff0c\u6211\u4eec\u9700\u8981\u5728\u5220\u9664\u64cd\u4f5c\u540e\u7ef4\u6301\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u201c\u5de6\u5b50\u6811 < \u6839\u7ed3\u70b9 < \u53f3\u5b50\u6811\u201d\u7684\u6027\u8d28\u3002\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u5728\u4e8c\u53c9\u6811\u4e2d\u6267\u884c\u67e5\u627e\u64cd\u4f5c\uff0c\u83b7\u53d6\u5f85\u5220\u9664\u7ed3\u70b9\u3002\u63a5\u4e0b\u6765\uff0c\u6839\u636e\u5f85\u5220\u9664\u7ed3\u70b9\u7684\u5b50\u7ed3\u70b9\u6570\u91cf\uff0c\u5220\u9664\u64cd\u4f5c\u9700\u8981\u5206\u4e3a\u4e09\u79cd\u60c5\u51b5\uff1a

\u5f53\u5f85\u5220\u9664\u7ed3\u70b9\u7684\u5b50\u7ed3\u70b9\u6570\u91cf \\(= 0\\) \u65f6\uff0c\u8868\u660e\u5f85\u5220\u9664\u7ed3\u70b9\u662f\u53f6\u7ed3\u70b9\uff0c\u76f4\u63a5\u5220\u9664\u5373\u53ef\u3002

\u5f53\u5f85\u5220\u9664\u7ed3\u70b9\u7684\u5b50\u7ed3\u70b9\u6570\u91cf \\(= 1\\) \u65f6\uff0c\u5c06\u5f85\u5220\u9664\u7ed3\u70b9\u66ff\u6362\u4e3a\u5176\u5b50\u7ed3\u70b9\u5373\u53ef\u3002

\u5f53\u5f85\u5220\u9664\u7ed3\u70b9\u7684\u5b50\u7ed3\u70b9\u6570\u91cf \\(= 2\\) \u65f6\uff0c\u5220\u9664\u64cd\u4f5c\u5206\u4e3a\u4e09\u6b65\uff1a

  1. \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\u5728 \u4e2d\u5e8f\u904d\u5386\u5e8f\u5217 \u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff0c\u8bb0\u4e3a nex \uff1b
  2. \u5728\u6811\u4e2d\u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex \uff1b
  3. \u4f7f\u7528 nex \u66ff\u6362\u5f85\u5220\u9664\u7ed3\u70b9\uff1b
Step 1Step 2Step 3Step 4

\u5220\u9664\u7ed3\u70b9\u64cd\u4f5c\u4e5f\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\uff0c\u5176\u4e2d\u67e5\u627e\u5f85\u5220\u9664\u7ed3\u70b9 \\(O(\\log n)\\) \uff0c\u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u540e\u7ee7\u7ed3\u70b9 \\(O(\\log n)\\) \u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_search_tree.java
/* \u5220\u9664\u7ed3\u70b9 */\nTreeNode remove(int num) {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root == null) return null;\nTreeNode cur = root, pre = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != null) {\n// \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif (cur.val == num) break;\npre = cur;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur.left;\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif (cur == null) return null;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif (cur.left == null || cur.right == null) {\n// \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = null / \u8be5\u5b50\u7ed3\u70b9\nTreeNode child = cur.left != null ? cur.left : cur.right;\n// \u5220\u9664\u7ed3\u70b9 cur\nif (pre.left == cur) pre.left = child;\nelse pre.right = child;\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse {\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nTreeNode nex = getInOrderNext(cur.right);\nint tmp = nex.val;\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nremove(nex.val);\n// \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur.val = tmp;\n}\nreturn cur;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nTreeNode getInOrderNext(TreeNode root) {\nif (root == null) return root;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (root.left != null) {\nroot = root.left;\n}\nreturn root;\n}\n
binary_search_tree.cpp
/* \u5220\u9664\u7ed3\u70b9 */\nTreeNode* remove(int num) {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root == nullptr) return nullptr;\nTreeNode *cur = root, *pre = nullptr;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != nullptr) {\n// \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif (cur->val == num) break;\npre = cur;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur->val < num) cur = cur->right;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur->left;\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif (cur == nullptr) return nullptr;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif (cur->left == nullptr || cur->right == nullptr) {\n// \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = nullptr / \u8be5\u5b50\u7ed3\u70b9\nTreeNode* child = cur->left != nullptr ? cur->left : cur->right;\n// \u5220\u9664\u7ed3\u70b9 cur\nif (pre->left == cur) pre->left = child;\nelse pre->right = child;\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse {\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nTreeNode* nex = getInOrderNext(cur->right);\nint tmp = nex->val;\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nremove(nex->val);\n// \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur->val = tmp;\n}\nreturn cur;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nTreeNode* getInOrderNext(TreeNode* root) {\nif (root == nullptr) return root;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (root->left != nullptr) {\nroot = root->left;\n}\nreturn root;\n}\n
binary_search_tree.py
\"\"\" \u5220\u9664\u7ed3\u70b9 \"\"\"\ndef remove(self, num: int) -> Optional[TreeNode]:\nroot = self.root\n# \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif root is None:\nreturn None\n# \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\ncur, pre = root, None\nwhile cur is not None:\n# \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif cur.val == num:\nbreak\npre = cur\nif cur.val < num:  # \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\ncur = cur.right\nelse:  # \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\ncur = cur.left\n# \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif cur is None:\nreturn None\n# \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif cur.left is None or cur.right is None:\n# \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = null / \u8be5\u5b50\u7ed3\u70b9\nchild = cur.left or cur.right\n# \u5220\u9664\u7ed3\u70b9 cur\nif pre.left == cur:\npre.left = child\nelse:\npre.right = child\n# \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse:\n# \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nnex = self.get_inorder_next(cur.right)\ntmp = nex.val\n# \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nself.remove(nex.val)\n# \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur.val = tmp\nreturn cur\n\"\"\" \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 \"\"\"\ndef get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]:\nif root is None:\nreturn root\n# \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile root.left is not None:\nroot = root.left\nreturn root\n
binary_search_tree.go
/* \u5220\u9664\u7ed3\u70b9 */\nfunc (bst *binarySearchTree) remove(num int) *TreeNode {\ncur := bst.root\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif cur == nil {\nreturn nil\n}\n// \u5f85\u5220\u9664\u7ed3\u70b9\u4e4b\u524d\u7684\u7ed3\u70b9\u4f4d\u7f6e\nvar pre *TreeNode = nil\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nfor cur != nil {\nif cur.Val == num {\nbreak\n}\npre = cur\nif cur.Val < num {\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728\u53f3\u5b50\u6811\u4e2d\ncur = cur.Right\n} else {\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728\u5de6\u5b50\u6811\u4e2d\ncur = cur.Left\n}\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif cur == nil {\nreturn nil\n}\n// \u5b50\u7ed3\u70b9\u6570\u4e3a 0 \u6216 1\nif cur.Left == nil || cur.Right == nil {\nvar child *TreeNode = nil\n// \u53d6\u51fa\u5f85\u5220\u9664\u7ed3\u70b9\u7684\u5b50\u7ed3\u70b9\nif cur.Left != nil {\nchild = cur.Left\n} else {\nchild = cur.Right\n}\n// \u5c06\u5b50\u7ed3\u70b9\u66ff\u6362\u4e3a\u5f85\u5220\u9664\u7ed3\u70b9\nif pre.Left == cur {\npre.Left = child\n} else {\npre.Right = child\n}\n// \u5b50\u7ed3\u70b9\u6570\u4e3a 2\n} else {\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u5f85\u5220\u9664\u7ed3\u70b9 cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nnext := bst.getInOrderNext(cur)\ntemp := next.Val\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 next\nbst.remove(next.Val)\n// \u5c06 next \u7684\u503c\u590d\u5236\u7ed9 cur\ncur.Val = temp\n}\nreturn cur\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nfunc (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode {\nif node == nil {\nreturn node\n}\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nfor node.Left != nil {\nnode = node.Left\n}\nreturn node\n}\n
binary_search_tree.js
/* \u5220\u9664\u7ed3\u70b9 */\nfunction remove(num) {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root === null) return null;\nlet cur = root, pre = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur !== null) {\n// \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif (cur.val === num) break;\npre = cur;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur.left;\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif (cur === null) return null;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif (cur.left === null || cur.right === null) {\n// \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = null / \u8be5\u5b50\u7ed3\u70b9\nlet child = cur.left !== null ? cur.left : cur.right;\n// \u5220\u9664\u7ed3\u70b9 cur\nif (pre.left === cur) pre.left = child;\nelse pre.right = child;\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse {\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nlet nex = getInOrderNext(cur.right);\nlet tmp = nex.val;\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nremove(nex.val);\n// \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur.val = tmp;\n}\nreturn cur;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nfunction getInOrderNext(root) {\nif (root === null) return root;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (root.left !== null) {\nroot = root.left;\n}\nreturn root;\n}\n
binary_search_tree.ts
/* \u5220\u9664\u7ed3\u70b9 */\nfunction remove(num: number): TreeNode | null {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root === null) {\nreturn null;\n}\nlet cur = root,\npre: TreeNode | null = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur !== null) {\n// \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif (cur.val === num) {\nbreak;\n}\npre = cur;\nif (cur.val < num) {\ncur = cur.right as TreeNode; // \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\n} else {\ncur = cur.left as TreeNode; // \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\n}\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif (cur === null) {\nreturn null;\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif (cur.left === null || cur.right === null) {\n// \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = null / \u8be5\u5b50\u7ed3\u70b9\nlet child = cur.left !== null ? cur.left : cur.right;\n// \u5220\u9664\u7ed3\u70b9 cur\nif (pre!.left === cur) {\npre!.left = child;\n} else {\npre!.right = child;\n}\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse {\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nlet next = getInOrderNext(cur.right);\nlet tmp = next!.val;\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nremove(next!.val);\n// \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur.val = tmp;\n}\nreturn cur;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nfunction getInOrderNext(root: TreeNode | null): TreeNode | null {\nif (root === null) {\nreturn null;\n}\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (root.left !== null) {\nroot = root.left;\n}\nreturn root;\n}\n
binary_search_tree.c
\n
binary_search_tree.cs
/* \u5220\u9664\u7ed3\u70b9 */\nTreeNode? remove(int num)\n{\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif (root == null) return null;\nTreeNode? cur = root, pre = null;\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile (cur != null)\n{\n// \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif (cur.val == num) break;\npre = cur;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif (cur.val < num) cur = cur.right;\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse cur = cur.left;\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif (cur == null || pre == null) return null;\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif (cur.left == null || cur.right == null)\n{\n// \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = null / \u8be5\u5b50\u7ed3\u70b9\nTreeNode? child = cur.left != null ? cur.left : cur.right;\n// \u5220\u9664\u7ed3\u70b9 cur\nif (pre.left == cur)\n{\npre.left = child;\n}\nelse\n{\npre.right = child;\n}\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse\n{\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nTreeNode? nex = getInOrderNext(cur.right);\nif (nex != null)\n{\nint tmp = nex.val;\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nremove(nex.val);\n// \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur.val = tmp;\n}\n}\nreturn cur;\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nprivate TreeNode? getInOrderNext(TreeNode? root)\n{\nif (root == null) return root;\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile (root.left != null)\n{\nroot = root.left;\n}\nreturn root;\n}\n
binary_search_tree.swift
/* \u5220\u9664\u7ed3\u70b9 */\n@discardableResult\nfunc remove(num: Int) -> TreeNode? {\n// \u82e5\u6811\u4e3a\u7a7a\uff0c\u76f4\u63a5\u63d0\u524d\u8fd4\u56de\nif root == nil {\nreturn nil\n}\nvar cur = root\nvar pre: TreeNode?\n// \u5faa\u73af\u67e5\u627e\uff0c\u8d8a\u8fc7\u53f6\u7ed3\u70b9\u540e\u8df3\u51fa\nwhile cur != nil {\n// \u627e\u5230\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u8df3\u51fa\u5faa\u73af\nif cur!.val == num {\nbreak\n}\npre = cur\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u53f3\u5b50\u6811\u4e2d\nif cur!.val < num {\ncur = cur?.right\n}\n// \u5f85\u5220\u9664\u7ed3\u70b9\u5728 cur \u7684\u5de6\u5b50\u6811\u4e2d\nelse {\ncur = cur?.left\n}\n}\n// \u82e5\u65e0\u5f85\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u76f4\u63a5\u8fd4\u56de\nif cur == nil {\nreturn nil\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 0 or 1\nif cur?.left == nil || cur?.right == nil {\n// \u5f53\u5b50\u7ed3\u70b9\u6570\u91cf = 0 / 1 \u65f6\uff0c child = null / \u8be5\u5b50\u7ed3\u70b9\nlet child = cur?.left != nil ? cur?.left : cur?.right\n// \u5220\u9664\u7ed3\u70b9 cur\nif pre?.left === cur {\npre?.left = child\n} else {\npre?.right = child\n}\n}\n// \u5b50\u7ed3\u70b9\u6570\u91cf = 2\nelse {\n// \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d cur \u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\nlet nex = getInOrderNext(root: cur?.right)\nlet tmp = nex!.val\n// \u9012\u5f52\u5220\u9664\u7ed3\u70b9 nex\nremove(num: nex!.val)\n// \u5c06 nex \u7684\u503c\u590d\u5236\u7ed9 cur\ncur?.val = tmp\n}\nreturn cur\n}\n/* \u83b7\u53d6\u4e2d\u5e8f\u904d\u5386\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7ed3\u70b9\uff08\u4ec5\u9002\u7528\u4e8e root \u6709\u5de6\u5b50\u7ed3\u70b9\u7684\u60c5\u51b5\uff09 */\nfunc getInOrderNext(root: TreeNode?) -> TreeNode? {\nvar root = root\nif root == nil {\nreturn root\n}\n// \u5faa\u73af\u8bbf\u95ee\u5de6\u5b50\u7ed3\u70b9\uff0c\u76f4\u5230\u53f6\u7ed3\u70b9\u65f6\u4e3a\u6700\u5c0f\u7ed3\u70b9\uff0c\u8df3\u51fa\nwhile root?.left != nil {\nroot = root?.left\n}\nreturn root\n}\n
binary_search_tree.zig
\n
"},{"location":"chapter_tree/binary_search_tree/#_4","title":"\u6392\u5e8f","text":"

\u6211\u4eec\u77e5\u9053\uff0c\u300c\u4e2d\u5e8f\u904d\u5386\u300d\u9075\u5faa\u201c\u5de6 \\(\\rightarrow\\) \u6839 \\(\\rightarrow\\) \u53f3\u201d\u7684\u904d\u5386\u4f18\u5148\u7ea7\uff0c\u800c\u4e8c\u53c9\u641c\u7d22\u6811\u9075\u5faa\u201c\u5de6\u5b50\u7ed3\u70b9 \\(<\\) \u6839\u7ed3\u70b9 \\(<\\) \u53f3\u5b50\u7ed3\u70b9\u201d\u7684\u5927\u5c0f\u5173\u7cfb\u3002\u56e0\u6b64\uff0c\u5728\u4e8c\u53c9\u641c\u7d22\u6811\u4e2d\u8fdb\u884c\u4e2d\u5e8f\u904d\u5386\u65f6\uff0c\u603b\u662f\u4f1a\u4f18\u5148\u904d\u5386\u4e0b\u4e00\u4e2a\u6700\u5c0f\u7ed3\u70b9\uff0c\u4ece\u800c\u5f97\u51fa\u4e00\u6761\u91cd\u8981\u6027\u8d28\uff1a\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u4e2d\u5e8f\u904d\u5386\u5e8f\u5217\u662f\u5347\u5e8f\u7684\u3002

\u501f\u52a9\u4e2d\u5e8f\u904d\u5386\u5347\u5e8f\u7684\u6027\u8d28\uff0c\u6211\u4eec\u5728\u4e8c\u53c9\u641c\u7d22\u6811\u4e2d\u83b7\u53d6\u6709\u5e8f\u6570\u636e\u4ec5\u9700 \\(O(n)\\) \u65f6\u95f4\uff0c\u800c\u65e0\u9700\u989d\u5916\u6392\u5e8f\uff0c\u975e\u5e38\u9ad8\u6548\u3002

"},{"location":"chapter_tree/binary_search_tree/#732","title":"7.3.2. \u4e8c\u53c9\u641c\u7d22\u6811\u7684\u6548\u7387","text":"

\u5047\u8bbe\u7ed9\u5b9a \\(n\\) \u4e2a\u6570\u5b57\uff0c\u6700\u5e38\u7528\u7684\u5b58\u50a8\u65b9\u5f0f\u662f\u300c\u6570\u7ec4\u300d\uff0c\u90a3\u4e48\u5bf9\u4e8e\u8fd9\u4e32\u4e71\u5e8f\u7684\u6570\u5b57\uff0c\u5e38\u89c1\u64cd\u4f5c\u7684\u6548\u7387\u4e3a\uff1a

  • \u67e5\u627e\u5143\u7d20\uff1a\u7531\u4e8e\u6570\u7ec4\u662f\u65e0\u5e8f\u7684\uff0c\u56e0\u6b64\u9700\u8981\u904d\u5386\u6570\u7ec4\u6765\u786e\u5b9a\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff1b
  • \u63d2\u5165\u5143\u7d20\uff1a\u53ea\u9700\u5c06\u5143\u7d20\u6dfb\u52a0\u81f3\u6570\u7ec4\u5c3e\u90e8\u5373\u53ef\uff0c\u4f7f\u7528 \\(O(1)\\) \u65f6\u95f4\uff1b
  • \u5220\u9664\u5143\u7d20\uff1a\u5148\u67e5\u627e\u5143\u7d20\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff0c\u518d\u5728\u6570\u7ec4\u4e2d\u5220\u9664\u8be5\u5143\u7d20\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff1b
  • \u83b7\u53d6\u6700\u5c0f / \u6700\u5927\u5143\u7d20\uff1a\u9700\u8981\u904d\u5386\u6570\u7ec4\u6765\u786e\u5b9a\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff1b

\u4e3a\u4e86\u5f97\u5230\u5148\u9a8c\u4fe1\u606f\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u9884\u5148\u5c06\u6570\u7ec4\u5143\u7d20\u8fdb\u884c\u6392\u5e8f\uff0c\u5f97\u5230\u4e00\u4e2a\u300c\u6392\u5e8f\u6570\u7ec4\u300d\uff0c\u6b64\u65f6\u64cd\u4f5c\u6548\u7387\u4e3a\uff1a

  • \u67e5\u627e\u5143\u7d20\uff1a\u7531\u4e8e\u6570\u7ec4\u5df2\u6392\u5e8f\uff0c\u53ef\u4ee5\u4f7f\u7528\u4e8c\u5206\u67e5\u627e\uff0c\u5e73\u5747\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\uff1b
  • \u63d2\u5165\u5143\u7d20\uff1a\u5148\u67e5\u627e\u63d2\u5165\u4f4d\u7f6e\uff0c\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\uff0c\u518d\u63d2\u5165\u5230\u6307\u5b9a\u4f4d\u7f6e\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff1b
  • \u5220\u9664\u5143\u7d20\uff1a\u5148\u67e5\u627e\u5143\u7d20\uff0c\u4f7f\u7528 \\(O(\\log n)\\) \u65f6\u95f4\uff0c\u518d\u5728\u6570\u7ec4\u4e2d\u5220\u9664\u8be5\u5143\u7d20\uff0c\u4f7f\u7528 \\(O(n)\\) \u65f6\u95f4\uff1b
  • \u83b7\u53d6\u6700\u5c0f / \u6700\u5927\u5143\u7d20\uff1a\u6570\u7ec4\u5934\u90e8\u548c\u5c3e\u90e8\u5143\u7d20\u5373\u662f\u6700\u5c0f\u548c\u6700\u5927\u5143\u7d20\uff0c\u4f7f\u7528 \\(O(1)\\) \u65f6\u95f4\uff1b

\u89c2\u5bdf\u53d1\u73b0\uff0c\u65e0\u5e8f\u6570\u7ec4\u548c\u6709\u5e8f\u6570\u7ec4\u4e2d\u7684\u5404\u9879\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u662f\u201c\u504f\u79d1\u201d\u7684\uff0c\u5373\u6709\u7684\u5feb\u6709\u7684\u6162\uff1b\u800c\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u5404\u9879\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u90fd\u662f\u5bf9\u6570\u9636\uff0c\u5728\u6570\u636e\u91cf \\(n\\) \u5f88\u5927\u65f6\u6709\u5de8\u5927\u4f18\u52bf\u3002

\u65e0\u5e8f\u6570\u7ec4 \u6709\u5e8f\u6570\u7ec4 \u4e8c\u53c9\u641c\u7d22\u6811 \u67e5\u627e\u6307\u5b9a\u5143\u7d20 \\(O(n)\\) \\(O(\\log n)\\) \\(O(\\log n)\\) \u63d2\u5165\u5143\u7d20 \\(O(1)\\) \\(O(n)\\) \\(O(\\log n)\\) \u5220\u9664\u5143\u7d20 \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \u83b7\u53d6\u6700\u5c0f / \u6700\u5927\u5143\u7d20 \\(O(n)\\) \\(O(1)\\) \\(O(\\log n)\\)"},{"location":"chapter_tree/binary_search_tree/#733","title":"7.3.3. \u4e8c\u53c9\u641c\u7d22\u6811\u7684\u9000\u5316","text":"

\u7406\u60f3\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u5e0c\u671b\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u662f\u201c\u5de6\u53f3\u5e73\u8861\u201d\u7684\uff08\u8be6\u89c1\u300c\u5e73\u8861\u4e8c\u53c9\u6811\u300d\u7ae0\u8282\uff09\uff0c\u6b64\u65f6\u53ef\u4ee5\u5728 \\(\\log n\\) \u8f6e\u5faa\u73af\u5185\u67e5\u627e\u4efb\u610f\u7ed3\u70b9\u3002

\u5982\u679c\u6211\u4eec\u52a8\u6001\u5730\u5728\u4e8c\u53c9\u641c\u7d22\u6811\u4e2d\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9\uff0c\u5219\u53ef\u80fd\u5bfc\u81f4\u4e8c\u53c9\u6811\u9000\u5316\u4e3a\u94fe\u8868\uff0c\u6b64\u65f6\u5404\u79cd\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u4e5f\u9000\u5316\u4e4b \\(O(n)\\) \u3002

Note

\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\uff0c\u5982\u4f55\u4fdd\u6301\u4e8c\u53c9\u641c\u7d22\u6811\u7684\u5e73\u8861\uff0c\u4e5f\u662f\u4e00\u4e2a\u9700\u8981\u91cd\u8981\u8003\u8651\u7684\u95ee\u9898\u3002

"},{"location":"chapter_tree/binary_search_tree/#734","title":"7.3.4. \u4e8c\u53c9\u641c\u7d22\u6811\u5e38\u89c1\u5e94\u7528","text":"
  • \u7cfb\u7edf\u4e2d\u7684\u591a\u7ea7\u7d22\u5f15\uff0c\u9ad8\u6548\u67e5\u627e\u3001\u63d2\u5165\u3001\u5220\u9664\u64cd\u4f5c\u3002
  • \u5404\u79cd\u641c\u7d22\u7b97\u6cd5\u7684\u5e95\u5c42\u6570\u636e\u7ed3\u6784\u3002
  • \u5b58\u50a8\u6570\u636e\u6d41\uff0c\u4fdd\u6301\u5176\u5df2\u6392\u5e8f\u3002
"},{"location":"chapter_tree/binary_tree/","title":"7.1. \u4e8c\u53c9\u6811","text":"

\u300c\u4e8c\u53c9\u6811 Binary Tree\u300d\u662f\u4e00\u79cd\u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784\uff0c\u4ee3\u8868\u7740\u7956\u5148\u4e0e\u540e\u4ee3\u4e4b\u95f4\u7684\u6d3e\u751f\u5173\u7cfb\uff0c\u4f53\u73b0\u7740\u201c\u4e00\u5206\u4e3a\u4e8c\u201d\u7684\u5206\u6cbb\u903b\u8f91\u3002\u7c7b\u4f3c\u4e8e\u94fe\u8868\uff0c\u4e8c\u53c9\u6811\u4e5f\u662f\u4ee5\u7ed3\u70b9\u4e3a\u5355\u4f4d\u5b58\u50a8\u7684\uff0c\u7ed3\u70b9\u5305\u542b\u300c\u503c\u300d\u548c\u4e24\u4e2a\u300c\u6307\u9488\u300d\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass TreeNode {\nint val;         // \u7ed3\u70b9\u503c\nTreeNode left;   // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nTreeNode right;  // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\nTreeNode(int x) { val = x; }\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7ed3\u6784\u4f53 */\nstruct TreeNode {\nint val;          // \u7ed3\u70b9\u503c\nTreeNode *left;   // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nTreeNode *right;  // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\nTreeNode(int x) : val(x), left(nullptr), right(nullptr) {}\n};\n
\"\"\" \u94fe\u8868\u7ed3\u70b9\u7c7b \"\"\"\nclass TreeNode:\ndef __init__(self, val=None, left=None, right=None):\nself.val = val      # \u7ed3\u70b9\u503c\nself.left = left    # \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nself.right = right  # \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\ntype TreeNode struct {\nVal   int\nLeft  *TreeNode\nRight *TreeNode\n}\n/* \u7ed3\u70b9\u521d\u59cb\u5316\u65b9\u6cd5 */\nfunc NewTreeNode(v int) *TreeNode {\nreturn &TreeNode{\nLeft:  nil,\nRight: nil,\nVal:   v,\n}\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nfunction TreeNode(val, left, right) {\nthis.val = (val === undefined ? 0 : val); // \u7ed3\u70b9\u503c\nthis.left = (left === undefined ? null : left); // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nthis.right = (right === undefined ? null : right); // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass TreeNode {\nval: number;\nleft: TreeNode | null;\nright: TreeNode | null;\nconstructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {\nthis.val = val === undefined ? 0 : val; // \u7ed3\u70b9\u503c\nthis.left = left === undefined ? null : left; // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nthis.right = right === undefined ? null : right; // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\n}\n}\n
\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass TreeNode {\nint val;          // \u7ed3\u70b9\u503c\nTreeNode? left;   // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nTreeNode? right;  // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\nTreeNode(int x) { val = x; }\n}\n
/* \u94fe\u8868\u7ed3\u70b9\u7c7b */\nclass TreeNode {\nvar val: Int // \u7ed3\u70b9\u503c\nvar left: TreeNode? // \u5de6\u5b50\u7ed3\u70b9\u6307\u9488\nvar right: TreeNode? // \u53f3\u5b50\u7ed3\u70b9\u6307\u9488\ninit(x: Int) {\nval = x\n}\n}\n
\n

\u7ed3\u70b9\u7684\u4e24\u4e2a\u6307\u9488\u5206\u522b\u6307\u5411\u300c\u5de6\u5b50\u7ed3\u70b9 Left Child Node\u300d\u548c\u300c\u53f3\u5b50\u7ed3\u70b9 Right Child Node\u300d\uff0c\u5e76\u4e14\u79f0\u8be5\u7ed3\u70b9\u4e3a\u4e24\u4e2a\u5b50\u7ed3\u70b9\u7684\u300c\u7236\u7ed3\u70b9 Parent Node\u300d\u3002\u7ed9\u5b9a\u4e8c\u53c9\u6811\u67d0\u7ed3\u70b9\uff0c\u5c06\u5de6\u5b50\u7ed3\u70b9\u4ee5\u4e0b\u7684\u6811\u79f0\u4e3a\u8be5\u7ed3\u70b9\u7684\u300c\u5de6\u5b50\u6811 Left Subtree\u300d\uff0c\u53f3\u5b50\u6811\u540c\u7406\u3002

\u9664\u4e86\u53f6\u7ed3\u70b9\u5916\uff0c\u6bcf\u4e2a\u7ed3\u70b9\u90fd\u6709\u5b50\u7ed3\u70b9\u548c\u5b50\u6811\u3002\u4f8b\u5982\uff0c\u82e5\u5c06\u4e0b\u56fe\u7684\u300c\u7ed3\u70b9 2\u300d\u770b\u4f5c\u7236\u7ed3\u70b9\uff0c\u90a3\u4e48\u5176\u5de6\u5b50\u7ed3\u70b9\u548c\u53f3\u5b50\u7ed3\u70b9\u5206\u522b\u4e3a\u300c\u7ed3\u70b9 4\u300d\u548c\u300c\u7ed3\u70b9 5\u300d\uff0c\u5de6\u5b50\u6811\u548c\u53f3\u5b50\u6811\u5206\u522b\u4e3a\u300c\u7ed3\u70b9 4 \u53ca\u5176\u4ee5\u4e0b\u7ed3\u70b9\u5f62\u6210\u7684\u6811\u300d\u548c\u300c\u7ed3\u70b9 5 \u53ca\u5176\u4ee5\u4e0b\u7ed3\u70b9\u5f62\u6210\u7684\u6811\u300d\u3002

Fig. \u5b50\u7ed3\u70b9\u4e0e\u5b50\u6811

"},{"location":"chapter_tree/binary_tree/#711","title":"7.1.1. \u4e8c\u53c9\u6811\u5e38\u89c1\u672f\u8bed","text":"

\u4e8c\u53c9\u6811\u7684\u672f\u8bed\u8f83\u591a\uff0c\u5efa\u8bae\u5c3d\u91cf\u7406\u89e3\u5e76\u8bb0\u4f4f\u3002\u540e\u7eed\u53ef\u80fd\u9057\u5fd8\uff0c\u53ef\u4ee5\u5728\u9700\u8981\u4f7f\u7528\u65f6\u56de\u6765\u67e5\u770b\u786e\u8ba4\u3002

  • \u300c\u6839\u7ed3\u70b9 Root Node\u300d\uff1a\u4e8c\u53c9\u6811\u6700\u9876\u5c42\u7684\u7ed3\u70b9\uff0c\u5176\u6ca1\u6709\u7236\u7ed3\u70b9\uff1b
  • \u300c\u53f6\u7ed3\u70b9 Leaf Node\u300d\uff1a\u6ca1\u6709\u5b50\u7ed3\u70b9\u7684\u7ed3\u70b9\uff0c\u5176\u4e24\u4e2a\u6307\u9488\u90fd\u6307\u5411 \\(\\text{null}\\) \uff1b
  • \u7ed3\u70b9\u6240\u5904\u300c\u5c42 Level\u300d\uff1a\u4ece\u9876\u81f3\u5e95\u4f9d\u6b21\u589e\u52a0\uff0c\u6839\u7ed3\u70b9\u6240\u5904\u5c42\u4e3a 1 \uff1b
  • \u7ed3\u70b9\u300c\u5ea6 Degree\u300d\uff1a\u7ed3\u70b9\u7684\u5b50\u7ed3\u70b9\u6570\u91cf\u3002\u4e8c\u53c9\u6811\u4e2d\uff0c\u5ea6\u7684\u8303\u56f4\u662f 0, 1, 2 \uff1b
  • \u300c\u8fb9 Edge\u300d\uff1a\u8fde\u63a5\u4e24\u4e2a\u7ed3\u70b9\u7684\u8fb9\uff0c\u5373\u7ed3\u70b9\u6307\u9488\uff1b
  • \u4e8c\u53c9\u6811\u300c\u9ad8\u5ea6\u300d\uff1a\u4e8c\u53c9\u6811\u4e2d\u6839\u7ed3\u70b9\u5230\u6700\u8fdc\u53f6\u7ed3\u70b9\u8d70\u8fc7\u8fb9\u7684\u6570\u91cf\uff1b
  • \u7ed3\u70b9\u300c\u6df1\u5ea6 Depth\u300d \uff1a\u6839\u7ed3\u70b9\u5230\u8be5\u7ed3\u70b9\u8d70\u8fc7\u8fb9\u7684\u6570\u91cf\uff1b
  • \u7ed3\u70b9\u300c\u9ad8\u5ea6 Height\u300d\uff1a\u6700\u8fdc\u53f6\u7ed3\u70b9\u5230\u8be5\u7ed3\u70b9\u8d70\u8fc7\u8fb9\u7684\u6570\u91cf\uff1b

Fig. \u4e8c\u53c9\u6811\u7684\u5e38\u89c1\u672f\u8bed

\u9ad8\u5ea6\u4e0e\u6df1\u5ea6\u7684\u5b9a\u4e49

\u503c\u5f97\u6ce8\u610f\uff0c\u6211\u4eec\u901a\u5e38\u5c06\u300c\u9ad8\u5ea6\u300d\u548c\u300c\u6df1\u5ea6\u300d\u5b9a\u4e49\u4e3a\u201c\u8d70\u8fc7\u8fb9\u7684\u6570\u91cf\u201d\uff0c\u800c\u6709\u4e9b\u9898\u76ee\u6216\u6559\u6750\u4f1a\u5c06\u5176\u5b9a\u4e49\u4e3a\u201c\u8d70\u8fc7\u7ed3\u70b9\u7684\u6570\u91cf\u201d\uff0c\u6b64\u65f6\u9ad8\u5ea6\u6216\u6df1\u5ea6\u90fd\u9700\u8981 + 1 \u3002

"},{"location":"chapter_tree/binary_tree/#712","title":"7.1.2. \u4e8c\u53c9\u6811\u57fa\u672c\u64cd\u4f5c","text":"

\u521d\u59cb\u5316\u4e8c\u53c9\u6811\u3002\u4e0e\u94fe\u8868\u7c7b\u4f3c\uff0c\u5148\u521d\u59cb\u5316\u7ed3\u70b9\uff0c\u518d\u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_tree.java
// \u521d\u59cb\u5316\u7ed3\u70b9\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.cpp
/* \u521d\u59cb\u5316\u4e8c\u53c9\u6811 */\n// \u521d\u59cb\u5316\u7ed3\u70b9\nTreeNode* n1 = new TreeNode(1);\nTreeNode* n2 = new TreeNode(2);\nTreeNode* n3 = new TreeNode(3);\nTreeNode* n4 = new TreeNode(4);\nTreeNode* n5 = new TreeNode(5);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
binary_tree.py
\"\"\" \u521d\u59cb\u5316\u4e8c\u53c9\u6811 \"\"\"\n# \u521d\u59cb\u5316\u7ed3\u70b9\nn1 = TreeNode(val=1)\nn2 = TreeNode(val=2)\nn3 = TreeNode(val=3)\nn4 = TreeNode(val=4)\nn5 = TreeNode(val=5)\n# \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.go
/* \u521d\u59cb\u5316\u4e8c\u53c9\u6811 */\n// \u521d\u59cb\u5316\u7ed3\u70b9\nn1 := NewTreeNode(1)\nn2 := NewTreeNode(2)\nn3 := NewTreeNode(3)\nn4 := NewTreeNode(4)\nn5 := NewTreeNode(5)\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.Left = n2\nn1.Right = n3\nn2.Left = n4\nn2.Right = n5\n
binary_tree.js
/* \u521d\u59cb\u5316\u4e8c\u53c9\u6811 */\n// \u521d\u59cb\u5316\u7ed3\u70b9\nlet n1 = new TreeNode(1),\nn2 = new TreeNode(2),\nn3 = new TreeNode(3),\nn4 = new TreeNode(4),\nn5 = new TreeNode(5);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.ts
/* \u521d\u59cb\u5316\u4e8c\u53c9\u6811 */\n// \u521d\u59cb\u5316\u7ed3\u70b9\nlet n1 = new TreeNode(1),\nn2 = new TreeNode(2),\nn3 = new TreeNode(3),\nn4 = new TreeNode(4),\nn5 = new TreeNode(5);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.c
\n
binary_tree.cs
/* \u521d\u59cb\u5316\u4e8c\u53c9\u6811 */\n// \u521d\u59cb\u5316\u7ed3\u70b9\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.swift
// \u521d\u59cb\u5316\u7ed3\u70b9\nlet n1 = TreeNode(x: 1)\nlet n2 = TreeNode(x: 2)\nlet n3 = TreeNode(x: 3)\nlet n4 = TreeNode(x: 4)\nlet n5 = TreeNode(x: 5)\n// \u6784\u5efa\u5f15\u7528\u6307\u5411\uff08\u5373\u6307\u9488\uff09\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.zig
\n

\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9\u3002\u4e0e\u94fe\u8868\u7c7b\u4f3c\uff0c\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9\u90fd\u53ef\u4ee5\u901a\u8fc7\u4fee\u6539\u6307\u9488\u5b9e\u73b0\u3002

Fig. \u5728\u4e8c\u53c9\u6811\u4e2d\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_tree.java
TreeNode P = new TreeNode(0);\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1.left = P;\nP.left = n2;\n// \u5220\u9664\u7ed3\u70b9 P\nn1.left = n2;\n
binary_tree.cpp
/* \u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9 */\nTreeNode* P = new TreeNode(0);\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1->left = P;\nP->left = n2;\n// \u5220\u9664\u7ed3\u70b9 P\nn1->left = n2;\n
binary_tree.py
\"\"\" \u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9 \"\"\"\np = TreeNode(0)\n# \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1.left = p\np.left = n2\n# \u5220\u9664\u7ed3\u70b9 P\nn1.left = n2\n
binary_tree.go
/* \u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9 */\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\np := NewTreeNode(0)\nn1.Left = p\np.Left = n2\n// \u5220\u9664\u7ed3\u70b9 P\nn1.Left = n2\n
binary_tree.js
/* \u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9 */\nlet P = new TreeNode(0);\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1.left = P;\nP.left = n2;\n// \u5220\u9664\u7ed3\u70b9 P\nn1.left = n2;\n
binary_tree.ts
/* \u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9 */\nconst P = new TreeNode(0);\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1.left = P;\nP.left = n2;\n// \u5220\u9664\u7ed3\u70b9 P\nn1.left = n2;\n
binary_tree.c
\n
binary_tree.cs
/* \u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9 */\nTreeNode P = new TreeNode(0);\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1.left = P;\nP.left = n2;\n// \u5220\u9664\u7ed3\u70b9 P\nn1.left = n2;\n
binary_tree.swift
let P = TreeNode(x: 0)\n// \u5728 n1 -> n2 \u4e2d\u95f4\u63d2\u5165\u7ed3\u70b9 P\nn1.left = P\nP.left = n2\n// \u5220\u9664\u7ed3\u70b9 P\nn1.left = n2\n
binary_tree.zig
\n

Note

\u63d2\u5165\u7ed3\u70b9\u4f1a\u6539\u53d8\u4e8c\u53c9\u6811\u7684\u539f\u6709\u903b\u8f91\u7ed3\u6784\uff0c\u5220\u9664\u7ed3\u70b9\u5f80\u5f80\u610f\u5473\u7740\u5220\u9664\u4e86\u8be5\u7ed3\u70b9\u7684\u6240\u6709\u5b50\u6811\u3002\u56e0\u6b64\uff0c\u4e8c\u53c9\u6811\u4e2d\u7684\u63d2\u5165\u4e0e\u5220\u9664\u4e00\u822c\u90fd\u662f\u7531\u4e00\u5957\u64cd\u4f5c\u914d\u5408\u5b8c\u6210\u7684\uff0c\u8fd9\u6837\u624d\u80fd\u5b9e\u73b0\u6709\u610f\u4e49\u7684\u64cd\u4f5c\u3002

"},{"location":"chapter_tree/binary_tree/#713","title":"7.1.3. \u5e38\u89c1\u4e8c\u53c9\u6811\u7c7b\u578b","text":""},{"location":"chapter_tree/binary_tree/#_1","title":"\u5b8c\u7f8e\u4e8c\u53c9\u6811","text":"

\u300c\u5b8c\u7f8e\u4e8c\u53c9\u6811 Perfect Binary Tree\u300d\u7684\u6240\u6709\u5c42\u7684\u7ed3\u70b9\u90fd\u88ab\u5b8c\u5168\u586b\u6ee1\u3002\u5728\u5b8c\u7f8e\u4e8c\u53c9\u6811\u4e2d\uff0c\u6240\u6709\u7ed3\u70b9\u7684\u5ea6 = 2 \uff1b\u82e5\u6811\u9ad8\u5ea6 \\(= h\\) \uff0c\u5219\u7ed3\u70b9\u603b\u6570 \\(= 2^{h+1} - 1\\) \uff0c\u5448\u6807\u51c6\u7684\u6307\u6570\u7ea7\u5173\u7cfb\uff0c\u53cd\u6620\u7740\u81ea\u7136\u754c\u4e2d\u5e38\u89c1\u7684\u7ec6\u80de\u5206\u88c2\u3002

Tip

\u5728\u4e2d\u6587\u793e\u533a\u4e2d\uff0c\u5b8c\u7f8e\u4e8c\u53c9\u6811\u5e38\u88ab\u79f0\u4e3a\u300c\u6ee1\u4e8c\u53c9\u6811\u300d\uff0c\u8bf7\u6ce8\u610f\u4e0e\u5b8c\u6ee1\u4e8c\u53c9\u6811\u533a\u5206\u3002

"},{"location":"chapter_tree/binary_tree/#_2","title":"\u5b8c\u5168\u4e8c\u53c9\u6811","text":"

\u300c\u5b8c\u5168\u4e8c\u53c9\u6811 Complete Binary Tree\u300d\u53ea\u6709\u6700\u5e95\u5c42\u7684\u7ed3\u70b9\u672a\u88ab\u586b\u6ee1\uff0c\u4e14\u6700\u5e95\u5c42\u7ed3\u70b9\u5c3d\u91cf\u9760\u5de6\u586b\u5145\u3002

\u5b8c\u5168\u4e8c\u53c9\u6811\u975e\u5e38\u9002\u5408\u7528\u6570\u7ec4\u6765\u8868\u793a\u3002\u5982\u679c\u6309\u7167\u5c42\u5e8f\u904d\u5386\u5e8f\u5217\u7684\u987a\u5e8f\u6765\u5b58\u50a8\uff0c\u90a3\u4e48\u7a7a\u7ed3\u70b9 null \u4e00\u5b9a\u5168\u90e8\u51fa\u73b0\u5728\u5e8f\u5217\u7684\u5c3e\u90e8\uff0c\u56e0\u6b64\u6211\u4eec\u5c31\u53ef\u4ee5\u4e0d\u7528\u5b58\u50a8\u8fd9\u4e9b null \u4e86\u3002

"},{"location":"chapter_tree/binary_tree/#_3","title":"\u5b8c\u6ee1\u4e8c\u53c9\u6811","text":"

\u300c\u5b8c\u6ee1\u4e8c\u53c9\u6811 Full Binary Tree\u300d\u9664\u4e86\u53f6\u7ed3\u70b9\u4e4b\u5916\uff0c\u5176\u4f59\u6240\u6709\u7ed3\u70b9\u90fd\u6709\u4e24\u4e2a\u5b50\u7ed3\u70b9\u3002

"},{"location":"chapter_tree/binary_tree/#_4","title":"\u5e73\u8861\u4e8c\u53c9\u6811","text":"

\u300c\u5e73\u8861\u4e8c\u53c9\u6811 Balanced Binary Tree\u300d\u4e2d\u4efb\u610f\u7ed3\u70b9\u7684\u5de6\u5b50\u6811\u548c\u53f3\u5b50\u6811\u7684\u9ad8\u5ea6\u4e4b\u5dee\u7684\u7edd\u5bf9\u503c \\(\\leq 1\\) \u3002

"},{"location":"chapter_tree/binary_tree/#714","title":"7.1.4. \u4e8c\u53c9\u6811\u7684\u9000\u5316","text":"

\u5f53\u4e8c\u53c9\u6811\u7684\u6bcf\u5c42\u7684\u7ed3\u70b9\u90fd\u88ab\u586b\u6ee1\u65f6\uff0c\u8fbe\u5230\u300c\u5b8c\u7f8e\u4e8c\u53c9\u6811\u300d\uff1b\u800c\u5f53\u6240\u6709\u7ed3\u70b9\u90fd\u504f\u5411\u4e00\u8fb9\u65f6\uff0c\u4e8c\u53c9\u6811\u9000\u5316\u4e3a\u300c\u94fe\u8868\u300d\u3002

  • \u5b8c\u7f8e\u4e8c\u53c9\u6811\u662f\u4e00\u4e2a\u4e8c\u53c9\u6811\u7684\u201c\u6700\u4f73\u72b6\u6001\u201d\uff0c\u53ef\u4ee5\u5b8c\u5168\u53d1\u6325\u51fa\u4e8c\u53c9\u6811\u201c\u5206\u6cbb\u201d\u7684\u4f18\u52bf\uff1b
  • \u94fe\u8868\u5219\u662f\u53e6\u4e00\u4e2a\u6781\u7aef\uff0c\u5404\u9879\u64cd\u4f5c\u90fd\u53d8\u4e3a\u7ebf\u6027\u64cd\u4f5c\uff0c\u65f6\u95f4\u590d\u6742\u5ea6\u9000\u5316\u81f3 \\(O(n)\\) \uff1b

Fig. \u4e8c\u53c9\u6811\u7684\u6700\u4f73\u548c\u6700\u5dee\u7ed3\u6784

\u5982\u4e0b\u8868\u6240\u793a\uff0c\u5728\u6700\u4f73\u548c\u6700\u5dee\u7ed3\u6784\u4e0b\uff0c\u4e8c\u53c9\u6811\u7684\u53f6\u7ed3\u70b9\u6570\u91cf\u3001\u7ed3\u70b9\u603b\u6570\u3001\u9ad8\u5ea6\u7b49\u8fbe\u5230\u6781\u5927\u6216\u6781\u5c0f\u503c\u3002

\u5b8c\u7f8e\u4e8c\u53c9\u6811 \u94fe\u8868 \u7b2c \\(i\\) \u5c42\u7684\u7ed3\u70b9\u6570\u91cf \\(2^{i-1}\\) \\(1\\) \u6811\u7684\u9ad8\u5ea6\u4e3a \\(h\\) \u65f6\u7684\u53f6\u7ed3\u70b9\u6570\u91cf \\(2^h\\) \\(1\\) \u6811\u7684\u9ad8\u5ea6\u4e3a \\(h\\) \u65f6\u7684\u7ed3\u70b9\u603b\u6570 \\(2^{h+1} - 1\\) \\(h + 1\\) \u6811\u7684\u7ed3\u70b9\u603b\u6570\u4e3a \\(n\\) \u65f6\u7684\u9ad8\u5ea6 \\(\\log_2 (n+1) - 1\\) \\(n - 1\\)"},{"location":"chapter_tree/binary_tree/#715","title":"7.1.5. \u4e8c\u53c9\u6811\u8868\u793a\u65b9\u5f0f *","text":"

\u6211\u4eec\u4e00\u822c\u4f7f\u7528\u4e8c\u53c9\u6811\u7684\u300c\u94fe\u8868\u8868\u793a\u300d\uff0c\u5373\u5b58\u50a8\u5355\u4f4d\u4e3a\u7ed3\u70b9 TreeNode \uff0c\u7ed3\u70b9\u4e4b\u95f4\u901a\u8fc7\u6307\u9488\uff08\u5f15\u7528\uff09\u76f8\u8fde\u63a5\u3002\u672c\u6587\u524d\u8ff0\u793a\u4f8b\u4ee3\u7801\u5c55\u793a\u4e86\u4e8c\u53c9\u6811\u5728\u94fe\u8868\u8868\u793a\u4e0b\u7684\u5404\u9879\u57fa\u672c\u64cd\u4f5c\u3002

\u90a3\u80fd\u5426\u53ef\u4ee5\u7528\u300c\u6570\u7ec4\u8868\u793a\u300d\u4e8c\u53c9\u6811\u5462\uff1f\u7b54\u6848\u662f\u80af\u5b9a\u7684\u3002\u5148\u6765\u5206\u6790\u4e00\u4e2a\u7b80\u5355\u6848\u4f8b\uff0c\u7ed9\u5b9a\u4e00\u4e2a\u300c\u5b8c\u7f8e\u4e8c\u53c9\u6811\u300d\uff0c\u5c06\u7ed3\u70b9\u6309\u7167\u5c42\u5e8f\u904d\u5386\u7684\u987a\u5e8f\u7f16\u53f7\uff08\u4ece 0 \u5f00\u59cb\uff09\uff0c\u90a3\u4e48\u53ef\u4ee5\u63a8\u5bfc\u5f97\u51fa\u7236\u7ed3\u70b9\u7d22\u5f15\u4e0e\u5b50\u7ed3\u70b9\u7d22\u5f15\u4e4b\u95f4\u7684\u300c\u6620\u5c04\u516c\u5f0f\u300d\uff1a\u8bbe\u7ed3\u70b9\u7684\u7d22\u5f15\u4e3a \\(i\\) \uff0c\u5219\u8be5\u7ed3\u70b9\u7684\u5de6\u5b50\u7ed3\u70b9\u7d22\u5f15\u4e3a \\(2i + 1\\) \u3001\u53f3\u5b50\u7ed3\u70b9\u7d22\u5f15\u4e3a \\(2i + 2\\) \u3002

\u672c\u8d28\u4e0a\uff0c\u6620\u5c04\u516c\u5f0f\u7684\u4f5c\u7528\u5c31\u662f\u94fe\u8868\u4e2d\u7684\u6307\u9488\u3002\u5bf9\u4e8e\u5c42\u5e8f\u904d\u5386\u5e8f\u5217\u4e2d\u7684\u4efb\u610f\u7ed3\u70b9\uff0c\u6211\u4eec\u90fd\u53ef\u4ee5\u4f7f\u7528\u6620\u5c04\u516c\u5f0f\u6765\u8bbf\u95ee\u5b50\u7ed3\u70b9\u3002\u56e0\u6b64\uff0c\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u5c42\u5e8f\u904d\u5386\u5e8f\u5217\uff08\u5373\u6570\u7ec4\uff09\u6765\u8868\u793a\u5b8c\u7f8e\u4e8c\u53c9\u6811\u3002

\u7136\u800c\uff0c\u5b8c\u7f8e\u4e8c\u53c9\u6811\u53ea\u662f\u4e2a\u4f8b\uff0c\u4e8c\u53c9\u6811\u4e2d\u95f4\u5c42\u5f80\u5f80\u5b58\u5728\u8bb8\u591a\u7a7a\u7ed3\u70b9\uff08\u5373 null \uff09\uff0c\u800c\u5c42\u5e8f\u904d\u5386\u5e8f\u5217\u5e76\u4e0d\u5305\u542b\u8fd9\u4e9b\u7a7a\u7ed3\u70b9\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u5355\u51ed\u5e8f\u5217\u6765\u731c\u6d4b\u7a7a\u7ed3\u70b9\u7684\u6570\u91cf\u548c\u5206\u5e03\u4f4d\u7f6e\uff0c\u5373\u7406\u8bba\u4e0a\u5b58\u5728\u8bb8\u591a\u79cd\u4e8c\u53c9\u6811\u90fd\u7b26\u5408\u8be5\u5c42\u5e8f\u904d\u5386\u5e8f\u5217\u3002\u663e\u7136\uff0c\u8fd9\u79cd\u60c5\u51b5\u65e0\u6cd5\u4f7f\u7528\u6570\u7ec4\u6765\u5b58\u50a8\u4e8c\u53c9\u6811\u3002

\u4e3a\u4e86\u89e3\u51b3\u6b64\u95ee\u9898\uff0c\u8003\u8651\u6309\u7167\u5b8c\u7f8e\u4e8c\u53c9\u6811\u7684\u5f62\u5f0f\u6765\u8868\u793a\u6240\u6709\u4e8c\u53c9\u6811\uff0c\u5373\u5728\u5e8f\u5217\u4e2d\u4f7f\u7528\u7279\u6b8a\u7b26\u53f7\u6765\u663e\u5f0f\u5730\u8868\u793a\u201c\u7a7a\u4f4d\u201d\u3002\u5982\u4e0b\u56fe\u6240\u793a\uff0c\u8fd9\u6837\u5904\u7406\u540e\uff0c\u5e8f\u5217\uff08\u6570\u7ec4\uff09\u5c31\u53ef\u4ee5\u552f\u4e00\u8868\u793a\u4e8c\u53c9\u6811\u4e86\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig
/* \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a */\n// \u4f7f\u7528 int \u7684\u5305\u88c5\u7c7b Integer \uff0c\u5c31\u53ef\u4ee5\u4f7f\u7528 null \u6765\u6807\u8bb0\u7a7a\u4f4d\nInteger[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n
/* \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a */\n// \u4e3a\u4e86\u7b26\u5408\u6570\u636e\u7c7b\u578b\u4e3a int \uff0c\u4f7f\u7528 int \u6700\u5927\u503c\u6807\u8bb0\u7a7a\u4f4d\n// \u8be5\u65b9\u6cd5\u7684\u4f7f\u7528\u524d\u63d0\u662f\u6ca1\u6709\u7ed3\u70b9\u7684\u503c = INT_MAX\nvector<int> tree = { 1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15 };\n
\"\"\" \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a \"\"\"\n# \u76f4\u63a5\u4f7f\u7528 None \u6765\u8868\u793a\u7a7a\u4f4d\ntree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]\n
\n
/* \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a */\n// \u76f4\u63a5\u4f7f\u7528 null \u6765\u8868\u793a\u7a7a\u4f4d\nlet tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a */\n// \u76f4\u63a5\u4f7f\u7528 null \u6765\u8868\u793a\u7a7a\u4f4d\nlet tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
\n
/* \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a */\n// \u4f7f\u7528 int? \u53ef\u7a7a\u7c7b\u578b \uff0c\u5c31\u53ef\u4ee5\u4f7f\u7528 null \u6765\u6807\u8bb0\u7a7a\u4f4d\nint?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n
/* \u4e8c\u53c9\u6811\u7684\u6570\u7ec4\u8868\u793a */\n// \u4f7f\u7528 Int? \u53ef\u7a7a\u7c7b\u578b \uff0c\u5c31\u53ef\u4ee5\u4f7f\u7528 nil \u6765\u6807\u8bb0\u7a7a\u4f4d\nlet tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n
\n

\u56de\u987e\u300c\u5b8c\u5168\u4e8c\u53c9\u6811\u300d\u7684\u5b9a\u4e49\uff0c\u5176\u53ea\u6709\u6700\u5e95\u5c42\u6709\u7a7a\u7ed3\u70b9\uff0c\u5e76\u4e14\u6700\u5e95\u5c42\u7684\u7ed3\u70b9\u5c3d\u91cf\u9760\u5de6\uff0c\u56e0\u800c\u6240\u6709\u7a7a\u7ed3\u70b9\u90fd\u4e00\u5b9a\u51fa\u73b0\u5728\u5c42\u5e8f\u904d\u5386\u5e8f\u5217\u7684\u672b\u5c3e\u3002\u56e0\u4e3a\u6211\u4eec\u5148\u9a8c\u5730\u786e\u5b9a\u4e86\u7a7a\u4f4d\u7684\u4f4d\u7f6e\uff0c\u6240\u4ee5\u5728\u4f7f\u7528\u6570\u7ec4\u8868\u793a\u5b8c\u5168\u4e8c\u53c9\u6811\u65f6\uff0c\u53ef\u4ee5\u7701\u7565\u5b58\u50a8\u201c\u7a7a\u4f4d\u201d\u3002\u56e0\u6b64\uff0c\u5b8c\u5168\u4e8c\u53c9\u6811\u975e\u5e38\u9002\u5408\u4f7f\u7528\u6570\u7ec4\u6765\u8868\u793a\u3002

\u6570\u7ec4\u8868\u793a\u6709\u4e24\u4e2a\u4f18\u70b9\uff1a \u4e00\u662f\u4e0d\u9700\u8981\u5b58\u50a8\u6307\u9488\uff0c\u8282\u7701\u7a7a\u95f4\uff1b\u4e8c\u662f\u53ef\u4ee5\u968f\u673a\u8bbf\u95ee\u7ed3\u70b9\u3002\u7136\u800c\uff0c\u5f53\u4e8c\u53c9\u6811\u4e2d\u7684\u201c\u7a7a\u4f4d\u201d\u5f88\u591a\u65f6\uff0c\u6570\u7ec4\u4e2d\u53ea\u5305\u542b\u5f88\u5c11\u7ed3\u70b9\u7684\u6570\u636e\uff0c\u7a7a\u95f4\u5229\u7528\u7387\u5f88\u4f4e\u3002

"},{"location":"chapter_tree/binary_tree_traversal/","title":"7.2. \u4e8c\u53c9\u6811\u904d\u5386","text":"

\u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784\u7684\u904d\u5386\u64cd\u4f5c\u6bd4\u7ebf\u6027\u6570\u636e\u7ed3\u6784\u66f4\u52a0\u590d\u6742\uff0c\u5f80\u5f80\u9700\u8981\u4f7f\u7528\u641c\u7d22\u7b97\u6cd5\u6765\u5b9e\u73b0\u3002\u5e38\u89c1\u7684\u4e8c\u53c9\u6811\u904d\u5386\u65b9\u5f0f\u6709\u5c42\u5e8f\u904d\u5386\u3001\u524d\u5e8f\u904d\u5386\u3001\u4e2d\u5e8f\u904d\u5386\u3001\u540e\u5e8f\u904d\u5386\u3002

"},{"location":"chapter_tree/binary_tree_traversal/#721","title":"7.2.1. \u5c42\u5e8f\u904d\u5386","text":"

\u300c\u5c42\u5e8f\u904d\u5386 Hierarchical-Order Traversal\u300d\u4ece\u9876\u81f3\u5e95\u3001\u4e00\u5c42\u4e00\u5c42\u5730\u904d\u5386\u4e8c\u53c9\u6811\uff0c\u5e76\u5728\u6bcf\u5c42\u4e2d\u6309\u7167\u4ece\u5de6\u5230\u53f3\u7684\u987a\u5e8f\u8bbf\u95ee\u7ed3\u70b9\u3002

\u5c42\u5e8f\u904d\u5386\u672c\u8d28\u4e0a\u662f\u300c\u5e7f\u5ea6\u4f18\u5148\u641c\u7d22 Breadth-First Traversal\u300d\uff0c\u5176\u4f53\u73b0\u7740\u4e00\u79cd\u201c\u4e00\u5708\u4e00\u5708\u5411\u5916\u201d\u7684\u5c42\u8fdb\u904d\u5386\u65b9\u5f0f\u3002

Fig. \u4e8c\u53c9\u6811\u7684\u5c42\u5e8f\u904d\u5386

\u5e7f\u5ea6\u4f18\u5148\u904d\u5386\u4e00\u822c\u501f\u52a9\u300c\u961f\u5217\u300d\u6765\u5b9e\u73b0\u3002\u961f\u5217\u7684\u89c4\u5219\u662f\u201c\u5148\u8fdb\u5148\u51fa\u201d\uff0c\u5e7f\u5ea6\u4f18\u5148\u904d\u5386\u7684\u89c4\u5219\u662f \u201d\u4e00\u5c42\u5c42\u5e73\u63a8\u201c \uff0c\u4e24\u8005\u80cc\u540e\u7684\u601d\u60f3\u662f\u4e00\u81f4\u7684\u3002

JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_tree_bfs.java
/* \u5c42\u5e8f\u904d\u5386 */\nList<Integer> hierOrder(TreeNode root) {\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nQueue<TreeNode> queue = new LinkedList<>() {{ add(root); }};\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nList<Integer> list = new ArrayList<>();\nwhile (!queue.isEmpty()) {\nTreeNode node = queue.poll();  // \u961f\u5217\u51fa\u961f\nlist.add(node.val);            // \u4fdd\u5b58\u7ed3\u70b9\u503c\nif (node.left != null)\nqueue.offer(node.left);    // \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\nif (node.right != null)\nqueue.offer(node.right);   // \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\n}\nreturn list;\n}\n
binary_tree_bfs.cpp
/* \u5c42\u5e8f\u904d\u5386 */\nvector<int> hierOrder(TreeNode* root) {\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nqueue<TreeNode*> queue;\nqueue.push(root);\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nvector<int> vec;\nwhile (!queue.empty()) {\nTreeNode* node = queue.front();\nqueue.pop();                 // \u961f\u5217\u51fa\u961f\nvec.push_back(node->val);    // \u4fdd\u5b58\u7ed3\u70b9\u503c\nif (node->left != nullptr)\nqueue.push(node->left);  // \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\nif (node->right != nullptr)\nqueue.push(node->right); // \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\n}\nreturn vec;\n}\n
binary_tree_bfs.py
\"\"\" \u5c42\u5e8f\u904d\u5386 \"\"\"\ndef hier_order(root: Optional[TreeNode]):\n# \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nqueue = collections.deque()\nqueue.append(root)\n# \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nres = []\nwhile queue:\nnode = queue.popleft()       # \u961f\u5217\u51fa\u961f\nres.append(node.val)         # \u4fdd\u5b58\u7ed3\u70b9\u503c\nif node.left is not None:\nqueue.append(node.left)  # \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\nif node.right is not None:\nqueue.append(node.right) # \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\nreturn res\n
binary_tree_bfs.go
/* \u5c42\u5e8f\u904d\u5386 */\nfunc levelOrder(root *TreeNode) []int {\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nqueue := list.New()\nqueue.PushBack(root)\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5207\u7247\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nnums := make([]int, 0)\nfor queue.Len() > 0 {\n// poll\nnode := queue.Remove(queue.Front()).(*TreeNode)\n// \u4fdd\u5b58\u7ed3\u70b9\u503c\nnums = append(nums, node.Val)\nif node.Left != nil {\n// \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\nqueue.PushBack(node.Left)\n}\nif node.Right != nil {\n// \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\nqueue.PushBack(node.Right)\n}\n}\nreturn nums\n}\n
binary_tree_bfs.js
/* \u5c42\u5e8f\u904d\u5386 */\nfunction hierOrder(root) {\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nlet queue = [root];\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nlet list = [];\nwhile (queue.length) {\nlet node = queue.shift();   // \u961f\u5217\u51fa\u961f\nlist.push(node.val);        // \u4fdd\u5b58\u7ed3\u70b9\u503c\nif (node.left)\nqueue.push(node.left);  // \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\nif (node.right)\nqueue.push(node.right); // \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\n}\nreturn list;\n}\n
binary_tree_bfs.ts
/* \u5c42\u5e8f\u904d\u5386 */\nfunction hierOrder(root: TreeNode | null): number[] {\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nconst queue = [root];\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nconst list: number[] = [];\nwhile (queue.length) {\nlet node = queue.shift() as TreeNode; // \u961f\u5217\u51fa\u961f\nlist.push(node.val); // \u4fdd\u5b58\u7ed3\u70b9\u503c\nif (node.left) {\nqueue.push(node.left); // \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\n}\nif (node.right) {\nqueue.push(node.right); // \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\n}\n}\nreturn list;\n}\n
binary_tree_bfs.c
\n
binary_tree_bfs.cs
/* \u5c42\u5e8f\u904d\u5386 */\npublic List<int?> hierOrder(TreeNode root)\n{\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nQueue<TreeNode> queue = new();\nqueue.Enqueue(root);\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nList<int> list = new();\nwhile (queue.Count != 0)\n{\nTreeNode node = queue.Dequeue(); // \u961f\u5217\u51fa\u961f\nlist.Add(node.val);              // \u4fdd\u5b58\u7ed3\u70b9\u503c\nif (node.left != null)\nqueue.Enqueue(node.left);    // \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\nif (node.right != null)\nqueue.Enqueue(node.right);   // \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\n}\nreturn list;\n}\n
binary_tree_bfs.swift
/* \u5c42\u5e8f\u904d\u5386 */\nfunc hierOrder(root: TreeNode) -> [Int] {\n// \u521d\u59cb\u5316\u961f\u5217\uff0c\u52a0\u5165\u6839\u7ed3\u70b9\nvar queue: [TreeNode] = [root]\n// \u521d\u59cb\u5316\u4e00\u4e2a\u5217\u8868\uff0c\u7528\u4e8e\u4fdd\u5b58\u904d\u5386\u5e8f\u5217\nvar list: [Int] = []\nwhile !queue.isEmpty {\nlet node = queue.removeFirst() // \u961f\u5217\u51fa\u961f\nlist.append(node.val) // \u4fdd\u5b58\u7ed3\u70b9\u503c\nif let left = node.left {\nqueue.append(left) // \u5de6\u5b50\u7ed3\u70b9\u5165\u961f\n}\nif let right = node.right {\nqueue.append(right) // \u53f3\u5b50\u7ed3\u70b9\u5165\u961f\n}\n}\nreturn list\n}\n
binary_tree_bfs.zig
\n
"},{"location":"chapter_tree/binary_tree_traversal/#722","title":"7.2.2. \u524d\u5e8f\u3001\u4e2d\u5e8f\u3001\u540e\u5e8f\u904d\u5386","text":"

\u76f8\u5bf9\u5730\uff0c\u524d\u3001\u4e2d\u3001\u540e\u5e8f\u904d\u5386\u7686\u5c5e\u4e8e\u300c\u6df1\u5ea6\u4f18\u5148\u904d\u5386 Depth-First Traversal\u300d\uff0c\u5176\u4f53\u73b0\u7740\u4e00\u79cd\u201c\u5148\u8d70\u5230\u5c3d\u5934\uff0c\u518d\u56de\u5934\u7ee7\u7eed\u201d\u7684\u56de\u6eaf\u904d\u5386\u65b9\u5f0f\u3002

\u5982\u4e0b\u56fe\u6240\u793a\uff0c\u5de6\u4fa7\u662f\u6df1\u5ea6\u4f18\u5148\u904d\u5386\u7684\u7684\u793a\u610f\u56fe\uff0c\u53f3\u4e0a\u65b9\u662f\u5bf9\u5e94\u7684\u9012\u5f52\u5b9e\u73b0\u4ee3\u7801\u3002\u6df1\u5ea6\u4f18\u5148\u904d\u5386\u5c31\u50cf\u662f\u7ed5\u7740\u6574\u4e2a\u4e8c\u53c9\u6811\u7684\u5916\u56f4\u201c\u8d70\u201d\u4e00\u5708\uff0c\u8d70\u7684\u8fc7\u7a0b\u4e2d\uff0c\u5728\u6bcf\u4e2a\u7ed3\u70b9\u90fd\u4f1a\u9047\u5230\u4e09\u4e2a\u4f4d\u7f6e\uff0c\u5206\u522b\u5bf9\u5e94\u524d\u5e8f\u904d\u5386\u3001\u4e2d\u5e8f\u904d\u5386\u3001\u540e\u5e8f\u904d\u5386\u3002

Fig. \u4e8c\u53c9\u6811\u7684\u524d / \u4e2d / \u540e\u5e8f\u904d\u5386

\u4f4d\u7f6e \u542b\u4e49 \u6b64\u5904\u8bbf\u95ee\u7ed3\u70b9\u65f6\u5bf9\u5e94 \u6a59\u8272\u5706\u5708\u5904 \u521a\u8fdb\u5165\u6b64\u7ed3\u70b9\uff0c\u5373\u5c06\u8bbf\u95ee\u8be5\u7ed3\u70b9\u7684\u5de6\u5b50\u6811 \u524d\u5e8f\u904d\u5386 Pre-Order Traversal \u84dd\u8272\u5706\u5708\u5904 \u5df2\u8bbf\u95ee\u5b8c\u5de6\u5b50\u6811\uff0c\u5373\u5c06\u8bbf\u95ee\u53f3\u5b50\u6811 \u4e2d\u5e8f\u904d\u5386 In-Order Traversal \u7d2b\u8272\u5706\u5708\u5904 \u5df2\u8bbf\u95ee\u5b8c\u5de6\u5b50\u6811\u548c\u53f3\u5b50\u6811\uff0c\u5373\u5c06\u8fd4\u56de \u540e\u5e8f\u904d\u5386 Post-Order Traversal JavaC++PythonGoJavaScriptTypeScriptCC#SwiftZig binary_tree_dfs.java
/* \u524d\u5e8f\u904d\u5386 */\nvoid preOrder(TreeNode root) {\nif (root == null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nlist.add(root.val);\npreOrder(root.left);\npreOrder(root.right);\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nvoid inOrder(TreeNode root) {\nif (root == null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(root.left);\nlist.add(root.val);\ninOrder(root.right);\n}\n/* \u540e\u5e8f\u904d\u5386 */\nvoid postOrder(TreeNode root) {\nif (root == null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(root.left);\npostOrder(root.right);\nlist.add(root.val);\n}\n
binary_tree_dfs.cpp
/* \u524d\u5e8f\u904d\u5386 */\nvoid preOrder(TreeNode* root) {\nif (root == nullptr) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nvec.push_back(root->val);\npreOrder(root->left);\npreOrder(root->right);\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nvoid inOrder(TreeNode* root) {\nif (root == nullptr) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(root->left);\nvec.push_back(root->val);\ninOrder(root->right);\n}\n/* \u540e\u5e8f\u904d\u5386 */\nvoid postOrder(TreeNode* root) {\nif (root == nullptr) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(root->left);\npostOrder(root->right);\nvec.push_back(root->val);\n}\n
binary_tree_dfs.py
\"\"\" \u524d\u5e8f\u904d\u5386 \"\"\"\ndef pre_order(root: Optional[TreeNode]):\nif root is None:\nreturn\n# \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nres.append(root.val)\npre_order(root=root.left)\npre_order(root=root.right)\n\"\"\" \u4e2d\u5e8f\u904d\u5386 \"\"\"\ndef in_order(root: Optional[TreeNode]):\nif root is None:\nreturn\n# \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\nin_order(root=root.left)\nres.append(root.val)\nin_order(root=root.right)\n\"\"\" \u540e\u5e8f\u904d\u5386 \"\"\"\ndef post_order(root: Optional[TreeNode]):\nif root is None:\nreturn\n# \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npost_order(root=root.left)\npost_order(root=root.right)\nres.append(root.val)\n
binary_tree_dfs.go
/* \u524d\u5e8f\u904d\u5386 */\nfunc preOrder(node *TreeNode) {\nif node == nil {\nreturn\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nnums = append(nums, node.Val)\npreOrder(node.Left)\npreOrder(node.Right)\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nfunc inOrder(node *TreeNode) {\nif node == nil {\nreturn\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(node.Left)\nnums = append(nums, node.Val)\ninOrder(node.Right)\n}\n/* \u540e\u5e8f\u904d\u5386 */\nfunc postOrder(node *TreeNode) {\nif node == nil {\nreturn\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(node.Left)\npostOrder(node.Right)\nnums = append(nums, node.Val)\n}\n
binary_tree_dfs.js
/* \u524d\u5e8f\u904d\u5386 */\nfunction preOrder(root){\nif (root === null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nlist.push(root.val);\npreOrder(root.left);\npreOrder(root.right);\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nfunction inOrder(root) {\nif (root === null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(root.left);\nlist.push(root.val);\ninOrder(root.right);\n}\n/* \u540e\u5e8f\u904d\u5386 */\nfunction postOrder(root) {\nif (root === null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(root.left);\npostOrder(root.right);\nlist.push(root.val);\n}\n
binary_tree_dfs.ts
/* \u524d\u5e8f\u904d\u5386 */\nfunction preOrder(root: TreeNode | null): void {\nif (root === null) {\nreturn;\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nlist.push(root.val);\npreOrder(root.left);\npreOrder(root.right);\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nfunction inOrder(root: TreeNode | null): void {\nif (root === null) {\nreturn;\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(root.left);\nlist.push(root.val);\ninOrder(root.right);\n}\n/* \u540e\u5e8f\u904d\u5386 */\nfunction postOrder(root: TreeNode | null): void {\nif (root === null) {\nreturn;\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(root.left);\npostOrder(root.right);\nlist.push(root.val);\n}\n
binary_tree_dfs.c
\n
binary_tree_dfs.cs
/* \u524d\u5e8f\u904d\u5386 */\nvoid preOrder(TreeNode? root)\n{\nif (root == null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nlist.Add(root.val);\npreOrder(root.left);\npreOrder(root.right);\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nvoid inOrder(TreeNode? root)\n{\nif (root == null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(root.left);\nlist.Add(root.val);\ninOrder(root.right);\n}\n/* \u540e\u5e8f\u904d\u5386 */\nvoid postOrder(TreeNode? root)\n{\nif (root == null) return;\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(root.left);\npostOrder(root.right);\nlist.Add(root.val);\n}\n
binary_tree_dfs.swift
/* \u524d\u5e8f\u904d\u5386 */\nfunc preOrder(root: TreeNode?) {\nguard let root = root else {\nreturn\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u6839\u7ed3\u70b9 -> \u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811\nlist.append(root.val)\npreOrder(root: root.left)\npreOrder(root: root.right)\n}\n/* \u4e2d\u5e8f\u904d\u5386 */\nfunc inOrder(root: TreeNode?) {\nguard let root = root else {\nreturn\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u6839\u7ed3\u70b9 -> \u53f3\u5b50\u6811\ninOrder(root: root.left)\nlist.append(root.val)\ninOrder(root: root.right)\n}\n/* \u540e\u5e8f\u904d\u5386 */\nfunc postOrder(root: TreeNode?) {\nguard let root = root else {\nreturn\n}\n// \u8bbf\u95ee\u4f18\u5148\u7ea7\uff1a\u5de6\u5b50\u6811 -> \u53f3\u5b50\u6811 -> \u6839\u7ed3\u70b9\npostOrder(root: root.left)\npostOrder(root: root.right)\nlist.append(root.val)\n}\n
binary_tree_dfs.zig
\n

Note

\u4f7f\u7528\u5faa\u73af\u4e00\u6837\u53ef\u4ee5\u5b9e\u73b0\u524d\u3001\u4e2d\u3001\u540e\u5e8f\u904d\u5386\uff0c\u4f46\u4ee3\u7801\u76f8\u5bf9\u7e41\u7410\uff0c\u6709\u5174\u8da3\u7684\u540c\u5b66\u53ef\u4ee5\u81ea\u884c\u5b9e\u73b0\u3002

"},{"location":"chapter_tree/summary/","title":"7.5. \u5c0f\u7ed3","text":"
  • \u4e8c\u53c9\u6811\u662f\u4e00\u79cd\u975e\u7ebf\u6027\u6570\u636e\u7ed3\u6784\uff0c\u4ee3\u8868\u7740\u201c\u4e00\u5206\u4e3a\u4e8c\u201d\u7684\u5206\u6cbb\u903b\u8f91\u3002\u4e8c\u53c9\u6811\u7684\u7ed3\u70b9\u5305\u542b\u300c\u503c\u300d\u548c\u4e24\u4e2a\u300c\u6307\u9488\u300d\uff0c\u5206\u522b\u6307\u5411\u5de6\u5b50\u7ed3\u70b9\u548c\u53f3\u5b50\u7ed3\u70b9\u3002
  • \u9009\u5b9a\u4e8c\u53c9\u6811\u4e2d\u67d0\u7ed3\u70b9\uff0c\u5c06\u5176\u5de6\uff08\u53f3\uff09\u5b50\u7ed3\u70b9\u4ee5\u4e0b\u5f62\u6210\u7684\u6811\u79f0\u4e3a\u5de6\uff08\u53f3\uff09\u5b50\u6811\u3002
  • \u4e8c\u53c9\u6811\u7684\u672f\u8bed\u8f83\u591a\uff0c\u5305\u62ec\u6839\u7ed3\u70b9\u3001\u53f6\u7ed3\u70b9\u3001\u5c42\u3001\u5ea6\u3001\u8fb9\u3001\u9ad8\u5ea6\u3001\u6df1\u5ea6\u7b49\u3002
  • \u4e8c\u53c9\u6811\u7684\u521d\u59cb\u5316\u3001\u7ed3\u70b9\u63d2\u5165\u3001\u7ed3\u70b9\u5220\u9664\u64cd\u4f5c\u4e0e\u94fe\u8868\u7684\u64cd\u4f5c\u65b9\u6cd5\u7c7b\u4f3c\u3002
  • \u5e38\u89c1\u7684\u4e8c\u53c9\u6811\u7c7b\u578b\u5305\u62ec\u5b8c\u7f8e\u4e8c\u53c9\u6811\u3001\u5b8c\u5168\u4e8c\u53c9\u6811\u3001\u5b8c\u6ee1\u4e8c\u53c9\u6811\u3001\u5e73\u8861\u4e8c\u53c9\u6811\u3002\u5b8c\u7f8e\u4e8c\u53c9\u6811\u662f\u7406\u60f3\u72b6\u6001\uff0c\u94fe\u8868\u5219\u662f\u9000\u5316\u540e\u7684\u6700\u5dee\u72b6\u6001\u3002
  • \u4e8c\u53c9\u6811\u53ef\u4ee5\u4f7f\u7528\u6570\u7ec4\u8868\u793a\uff0c\u5177\u4f53\u505a\u6cd5\u662f\u5c06\u7ed3\u70b9\u503c\u548c\u7a7a\u4f4d\u6309\u7167\u5c42\u5e8f\u904d\u5386\u7684\u987a\u5e8f\u6392\u5217\uff0c\u5e76\u57fa\u4e8e\u7236\u7ed3\u70b9\u548c\u5b50\u7ed3\u70b9\u4e4b\u95f4\u7684\u7d22\u5f15\u6620\u5c04\u516c\u5f0f\u5b9e\u73b0\u6307\u9488\u3002

  • \u4e8c\u53c9\u6811\u5c42\u5e8f\u904d\u5386\u662f\u4e00\u79cd\u5e7f\u5ea6\u4f18\u5148\u641c\u7d22\uff0c\u4f53\u73b0\u7740\u201c\u4e00\u5708\u4e00\u5708\u5411\u5916\u201d\u7684\u5c42\u8fdb\u5f0f\u904d\u5386\u65b9\u5f0f\uff0c\u901a\u5e38\u501f\u52a9\u961f\u5217\u6765\u5b9e\u73b0\u3002

  • \u524d\u5e8f\u3001\u4e2d\u5e8f\u3001\u540e\u5e8f\u904d\u5386\u662f\u6df1\u5ea6\u4f18\u5148\u641c\u7d22\uff0c\u4f53\u73b0\u7740\u201c\u8d70\u5230\u5934\u3001\u518d\u56de\u5934\u7ee7\u7eed\u201d\u7684\u56de\u6eaf\u904d\u5386\u65b9\u5f0f\uff0c\u901a\u5e38\u4f7f\u7528\u9012\u5f52\u5b9e\u73b0\u3002
  • \u4e8c\u53c9\u641c\u7d22\u6811\u662f\u4e00\u79cd\u9ad8\u6548\u7684\u5143\u7d20\u67e5\u627e\u6570\u636e\u7ed3\u6784\uff0c\u67e5\u627e\u3001\u63d2\u5165\u3001\u5220\u9664\u64cd\u4f5c\u7684\u65f6\u95f4\u590d\u6742\u5ea6\u7686\u4e3a \\(O(\\log n)\\) \u3002\u4e8c\u53c9\u641c\u7d22\u6811\u9000\u5316\u4e3a\u94fe\u8868\u540e\uff0c\u5404\u9879\u65f6\u95f4\u590d\u6742\u5ea6\u52a3\u5316\u81f3 \\(O(n)\\) \uff0c\u56e0\u6b64\u5982\u4f55\u907f\u514d\u9000\u5316\u662f\u975e\u5e38\u91cd\u8981\u7684\u8bfe\u9898\u3002
  • AVL \u6811\u53c8\u79f0\u5e73\u8861\u4e8c\u53c9\u641c\u7d22\u6811\uff0c\u5176\u901a\u8fc7\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u5f97\u5728\u4e0d\u65ad\u63d2\u5165\u4e0e\u5220\u9664\u7ed3\u70b9\u540e\uff0c\u4ecd\u7136\u53ef\u4ee5\u4fdd\u6301\u4e8c\u53c9\u6811\u7684\u5e73\u8861\uff08\u4e0d\u9000\u5316\uff09\u3002
  • AVL \u6811\u7684\u65cb\u8f6c\u64cd\u4f5c\u5206\u4e3a\u53f3\u65cb\u3001\u5de6\u65cb\u3001\u5148\u53f3\u65cb\u540e\u5de6\u65cb\u3001\u5148\u5de6\u65cb\u540e\u53f3\u65cb\u3002\u5728\u63d2\u5165\u6216\u5220\u9664\u7ed3\u70b9\u540e\uff0cAVL \u6811\u4f1a\u4ece\u5e95\u81f3\u9876\u5730\u6267\u884c\u65cb\u8f6c\u64cd\u4f5c\uff0c\u4f7f\u6811\u6062\u590d\u5e73\u8861\u3002
"}]} \ No newline at end of file diff --git a/site/sitemap.xml b/site/sitemap.xml deleted file mode 100644 index 0b29056a8..000000000 --- a/site/sitemap.xml +++ /dev/null @@ -1,228 +0,0 @@ - - - - https://www.hello-algo.com/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_array_and_linkedlist/array/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_array_and_linkedlist/linked_list/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_array_and_linkedlist/list/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_array_and_linkedlist/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_computational_complexity/performance_evaluation/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_computational_complexity/space_complexity/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_computational_complexity/space_time_tradeoff/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_computational_complexity/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_computational_complexity/time_complexity/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_data_structure/classification_of_data_structure/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_data_structure/data_and_memory/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_data_structure/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_graph/graph/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_graph/graph_operations/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_hashing/hash_collision/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_hashing/hash_map/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_hashing/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_heap/heap/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_introduction/algorithms_are_everywhere/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_introduction/what_is_dsa/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_preface/about_the_book/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_preface/contribution/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_preface/installation/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_preface/suggestions/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_reference/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_searching/binary_search/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_searching/hashing_search/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_searching/linear_search/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_searching/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_sorting/bubble_sort/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_sorting/insertion_sort/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_sorting/intro_to_sort/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_sorting/merge_sort/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_sorting/quick_sort/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_sorting/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_stack_and_queue/deque/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_stack_and_queue/queue/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_stack_and_queue/stack/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_stack_and_queue/summary/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_tree/avl_tree/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_tree/binary_search_tree/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_tree/binary_tree/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_tree/binary_tree_traversal/ - 2023-02-07 - daily - - - https://www.hello-algo.com/chapter_tree/summary/ - 2023-02-07 - daily - - \ No newline at end of file diff --git a/site/sitemap.xml.gz b/site/sitemap.xml.gz deleted file mode 100644 index bd747e6d6..000000000 Binary files a/site/sitemap.xml.gz and /dev/null differ diff --git a/site/stylesheets/extra.css b/site/stylesheets/extra.css deleted file mode 100644 index 7171f2ed1..000000000 --- a/site/stylesheets/extra.css +++ /dev/null @@ -1,76 +0,0 @@ - -/* Color Settings */ -/* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ -/* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ -:root > * { - --md-primary-fg-color: #FFFFFF; - --md-primary-bg-color: #1D1D20; - - --md-accent-fg-color: #999; - - --md-typeset-color: #1D1D20; - --md-typeset-a-color: #2AA996; -} - -[data-md-color-scheme="slate"] { - --md-primary-fg-color: #2E303E; - --md-primary-bg-color: #FEFEFE; - - --md-accent-fg-color: #999; - - --md-typeset-color: #FEFEFE; - --md-typeset-a-color: #21C8B8; -} - -/* https://github.com/squidfunk/mkdocs-material/issues/4832#issuecomment-1374891676 */ -.md-nav__link[for] { - color: var(--md-default-fg-color) !important -} - -/* Center Markdown Tables (requires md_in_html extension) */ -.center-table { - text-align: center; -} - -.md-typeset .center-table :is(td,th):not([align]) { - /* Reset alignment for table cells */ - text-align: initial; -} - - -/* Markdown Header */ -/* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ -.md-typeset h1 { - font-weight: 400; - color: var(--md-default-fg-color); -} - -.md-typeset h2 { - font-weight: 400; -} - -.md-typeset h3 { - font-weight: 500; -} - -.md-typeset a { - text-decoration: underline; -} - -/* Image align center */ -.center { - display: block; - margin: 0 auto; -} - -/* font-family setting for Win10 */ -body { - --md-text-font-family: -apple-system,BlinkMacSystemFont,var(--md-text-font,_),Helvetica,Arial,sans-serif; - --md-code-font-family: var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,-apple-system,BlinkMacSystemFont,var(--md-text-font,_),monospace; -} - -/* max height of code block */ -/* https://github.com/squidfunk/mkdocs-material/issues/3444 */ -.md-typeset pre > code { - max-height: 30rem; -}