diff --git a/chapter_array_and_linkedlist/array.md b/chapter_array_and_linkedlist/array.md index 0d505d7a2..67fee24c3 100755 --- a/chapter_array_and_linkedlist/array.md +++ b/chapter_array_and_linkedlist/array.md @@ -16,12 +16,12 @@ comments: true 我们可以根据需求选用数组的两种初始化方式:无初始值、给定初始值。在未指定初始值的情况下,大多数编程语言会将数组元素初始化为 $0$ 。 -=== "Java" +=== "Python" - ```java title="array.java" - /* 初始化数组 */ - int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } - int[] nums = { 1, 3, 2, 5, 4 }; + ```python title="array.py" + # 初始化数组 + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] ``` === "C++" @@ -36,12 +36,20 @@ comments: true int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` -=== "Python" +=== "Java" - ```python title="array.py" - # 初始化数组 - arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] - nums: list[int] = [1, 3, 2, 5, 4] + ```java title="array.java" + /* 初始化数组 */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 初始化数组 */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; ``` === "Go" @@ -55,6 +63,14 @@ comments: true nums := []int{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] + ``` + === "JS" ```javascript title="array.js" @@ -71,37 +87,6 @@ comments: true let nums: number[] = [1, 3, 2, 5, 4]; ``` -=== "C" - - ```c title="array.c" - int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } - int nums[5] = { 1, 3, 2, 5, 4 }; - ``` - -=== "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 }; - ``` - === "Dart" ```dart title="array.dart" @@ -118,6 +103,21 @@ comments: true let nums: Vec = vec![1, 3, 2, 5, 4]; ``` +=== "C" + + ```c title="array.c" + int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } + int nums[5] = { 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 }; + ``` + ### 2.   访问元素 数组元素被存储在连续的内存空间中,这意味着计算数组元素的内存地址非常容易。给定数组内存地址(即首元素内存地址)和某个元素的索引,我们可以使用图 4-2 所示的公式计算得到该元素的内存地址,从而直接访问此元素。 @@ -130,17 +130,16 @@ comments: true 在数组中访问元素是非常高效的,我们可以在 $O(1)$ 时间内随机访问数组中的任意一个元素。 -=== "Java" +=== "Python" - ```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; - } + ```python title="array.py" + def random_access(nums: list[int]) -> int: + """随机访问元素""" + # 在区间 [0, len(nums)-1] 中随机抽取一个数字 + random_index = random.randint(0, len(nums) - 1) + # 获取并返回随机元素 + random_num = nums[random_index] + return random_num ``` === "C++" @@ -156,16 +155,31 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="array.py" - def random_access(nums: list[int]) -> int: - """随机访问元素""" - # 在区间 [0, len(nums)-1] 中随机抽取一个数字 - random_index = random.randint(0, len(nums) - 1) - # 获取并返回随机元素 - random_num = nums[random_index] - return random_num + ```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#" + + ```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; + } ``` === "Go" @@ -181,6 +195,19 @@ comments: true } ``` +=== "Swift" + + ```swift title="array.swift" + /* 随机访问元素 */ + func randomAccess(nums: [Int]) -> Int { + // 在区间 [0, nums.count) 中随机抽取一个数字 + let randomIndex = nums.indices.randomElement()! + // 获取并返回随机元素 + let randomNum = nums[randomIndex] + return randomNum + } + ``` + === "JS" ```javascript title="array.js" @@ -207,59 +234,6 @@ comments: true } ``` -=== "C" - - ```c title="array.c" - /* 随机访问元素 */ - int randomAccess(int *nums, int size) { - // 在区间 [0, size) 中随机抽取一个数字 - int randomIndex = rand() % size; - // 获取并返回随机元素 - int randomNum = nums[randomIndex]; - return randomNum; - } - ``` - -=== "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" - // 随机访问元素 - fn randomAccess(nums: []i32) i32 { - // 在区间 [0, nums.len) 中随机抽取一个整数 - var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len); - // 获取并返回随机元素 - var randomNum = nums[randomIndex]; - return randomNum; - } - ``` - === "Dart" ```dart title="array.dart" @@ -286,6 +260,32 @@ comments: true } ``` +=== "C" + + ```c title="array.c" + /* 随机访问元素 */ + int randomAccess(int *nums, int size) { + // 在区间 [0, size) 中随机抽取一个数字 + int randomIndex = rand() % size; + // 获取并返回随机元素 + int randomNum = nums[randomIndex]; + return randomNum; + } + ``` + +=== "Zig" + + ```zig title="array.zig" + // 随机访问元素 + fn randomAccess(nums: []i32) i32 { + // 在区间 [0, nums.len) 中随机抽取一个整数 + var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len); + // 获取并返回随机元素 + var randomNum = nums[randomIndex]; + return randomNum; + } + ``` + ### 3.   插入元素 数组元素在内存中是“紧挨着的”,它们之间没有空间再存放任何数据。如图 4-3 所示,如果想要在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。 @@ -296,18 +296,16 @@ comments: true 值得注意的是,由于数组的长度是固定的,因此插入一个元素必定会导致数组尾部元素的“丢失”。我们将这个问题的解决方案留在列表章节中讨论。 -=== "Java" +=== "Python" - ```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; - } + ```python title="array.py" + def insert(nums: list[int], num: int, index: int): + """在数组的索引 index 处插入元素 num""" + # 把索引 index 以及之后的所有元素向后移动一位 + for i in range(len(nums) - 1, index, -1): + nums[i] = nums[i - 1] + # 将 num 赋给 index 处元素 + nums[index] = num ``` === "C++" @@ -324,16 +322,32 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="array.py" - def insert(nums: list[int], num: int, index: int): - """在数组的索引 index 处插入元素 num""" - # 把索引 index 以及之后的所有元素向后移动一位 - for i in range(len(nums) - 1, index, -1): - nums[i] = nums[i - 1] - # 将 num 赋给 index 处元素 - nums[index] = num + ```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; + } + ``` + +=== "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; + } ``` === "Go" @@ -350,6 +364,20 @@ comments: true } ``` +=== "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 + } + ``` + === "JS" ```javascript title="array.js" @@ -378,63 +406,6 @@ comments: true } ``` -=== "C" - - ```c title="array.c" - /* 在数组的索引 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; - } - ``` - -=== "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; - } - ``` - -=== "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 - } - ``` - -=== "Zig" - - ```zig title="array.zig" - // 在数组的索引 index 处插入元素 num - 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; - } - ``` - === "Dart" ```dart title="array.dart" @@ -463,6 +434,35 @@ comments: true } ``` +=== "C" + + ```c title="array.c" + /* 在数组的索引 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; + } + ``` + +=== "Zig" + + ```zig title="array.zig" + // 在数组的索引 index 处插入元素 num + 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; + } + ``` + ### 4.   删除元素 同理,如图 4-4 所示,若想要删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。 @@ -473,16 +473,14 @@ comments: true 请注意,删除元素完成后,原先末尾的元素变得“无意义”了,所以我们无须特意去修改它。 -=== "Java" +=== "Python" - ```java title="array.java" - /* 删除索引 index 处元素 */ - void remove(int[] nums, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } + ```python title="array.py" + def remove(nums: list[int], index: int): + """删除索引 index 处元素""" + # 把索引 index 之后的所有元素向前移动一位 + for i in range(index, len(nums) - 1): + nums[i] = nums[i + 1] ``` === "C++" @@ -497,14 +495,28 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="array.py" - def remove(nums: list[int], index: int): - """删除索引 index 处元素""" - # 把索引 index 之后的所有元素向前移动一位 - for i in range(index, len(nums) - 1): - nums[i] = nums[i + 1] + ```java title="array.java" + /* 删除索引 index 处元素 */ + void remove(int[] nums, int index) { + // 把索引 index 之后的所有元素向前移动一位 + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 删除索引 index 处元素 */ + void remove(int[] nums, int index) { + // 把索引 index 之后的所有元素向前移动一位 + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; + } + } ``` === "Go" @@ -519,6 +531,19 @@ comments: true } ``` +=== "Swift" + + ```swift title="array.swift" + /* 删除索引 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] + } + } + ``` + === "JS" ```javascript title="array.js" @@ -543,57 +568,6 @@ comments: true } ``` -=== "C" - - ```c title="array.c" - /* 删除索引 index 处元素 */ - // 注意:stdio.h 占用了 remove 关键词 - void removeItem(int *nums, int size, int index) { - // 把索引 index 之后的所有元素向前移动一位 - for (int i = index; i < size - 1; i++) { - nums[i] = nums[i + 1]; - } - } - ``` - -=== "C#" - - ```csharp title="array.cs" - /* 删除索引 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 处元素 */ - 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 处元素 - fn remove(nums: []i32, index: usize) void { - // 把索引 index 之后的所有元素向前移动一位 - var i = index; - while (i < nums.len - 1) : (i += 1) { - nums[i] = nums[i + 1]; - } - } - ``` - === "Dart" ```dart title="array.dart" @@ -618,6 +592,32 @@ comments: true } ``` +=== "C" + + ```c title="array.c" + /* 删除索引 index 处元素 */ + // 注意:stdio.h 占用了 remove 关键词 + void removeItem(int *nums, int size, int index) { + // 把索引 index 之后的所有元素向前移动一位 + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "Zig" + + ```zig title="array.zig" + // 删除索引 index 处元素 + fn remove(nums: []i32, index: usize) void { + // 把索引 index 之后的所有元素向前移动一位 + var i = index; + while (i < nums.len - 1) : (i += 1) { + nums[i] = nums[i + 1]; + } + } + ``` + 总的来看,数组的插入与删除操作有以下缺点。 - **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(n)$ ,其中 $n$ 为数组长度。 @@ -628,36 +628,6 @@ comments: true 在大多数编程语言中,我们既可以通过索引遍历数组,也可以直接遍历获取数组中的每个元素。 -=== "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" @@ -675,6 +645,53 @@ comments: true count += 1 ``` +=== "C++" + + ```cpp title="array.cpp" + /* 遍历数组 */ + void traverse(int *nums, int size) { + int count = 0; + // 通过索引遍历数组 + for (int i = 0; i < size; i++) { + count++; + } + } + ``` + +=== "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#" + + ```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++; + } + } + ``` + === "Go" ```go title="array.go" @@ -693,6 +710,23 @@ comments: true } ``` +=== "Swift" + + ```swift title="array.swift" + /* 遍历数组 */ + func traverse(nums: [Int]) { + var count = 0 + // 通过索引遍历数组 + for _ in nums.indices { + count += 1 + } + // 直接遍历数组 + for _ in nums { + count += 1 + } + } + ``` + === "JS" ```javascript title="array.js" @@ -727,72 +761,6 @@ comments: true } ``` -=== "C" - - ```c title="array.c" - /* 遍历数组 */ - void traverse(int *nums, int size) { - int count = 0; - // 通过索引遍历数组 - for (int i = 0; i < size; i++) { - count++; - } - } - ``` - -=== "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" - // 遍历数组 - 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; - } - } - ``` - === "Dart" ```dart title="array.dart" @@ -831,23 +799,53 @@ comments: true } ``` +=== "C" + + ```c title="array.c" + /* 遍历数组 */ + void traverse(int *nums, int size) { + int count = 0; + // 通过索引遍历数组 + for (int i = 0; i < size; i++) { + count++; + } + } + ``` + +=== "Zig" + + ```zig title="array.zig" + // 遍历数组 + 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; + } + } + ``` + ### 6.   查找元素 在数组中查找指定元素需要遍历数组,每轮判断元素值是否匹配,若匹配则输出对应索引。 因为数组是线性数据结构,所以上述查找操作被称为“线性查找”。 -=== "Java" +=== "Python" - ```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; - } + ```python title="array.py" + def find(nums: list[int], target: int) -> int: + """在数组中查找指定元素""" + for i in range(len(nums)): + if nums[i] == target: + return i + return -1 ``` === "C++" @@ -863,15 +861,30 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="array.py" - def find(nums: list[int], target: int) -> int: - """在数组中查找指定元素""" - for i in range(len(nums)): - if nums[i] == target: - return i - return -1 + ```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#" + + ```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; + } ``` === "Go" @@ -890,6 +903,20 @@ comments: true } ``` +=== "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 + } + ``` + === "JS" ```javascript title="array.js" @@ -916,58 +943,6 @@ comments: true } ``` -=== "C" - - ```c title="array.c" - /* 在数组中查找指定元素 */ - int find(int *nums, int size, int target) { - for (int i = 0; i < size; i++) { - if (nums[i] == target) - return i; - } - return -1; - } - ``` - -=== "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" - // 在数组中查找指定元素 - fn find(nums: []i32, target: i32) i32 { - for (nums, 0..) |num, i| { - if (num == target) return @intCast(i); - } - return -1; - } - ``` - === "Dart" ```dart title="array.dart" @@ -994,26 +969,49 @@ comments: true } ``` +=== "C" + + ```c title="array.c" + /* 在数组中查找指定元素 */ + int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + ``` + +=== "Zig" + + ```zig title="array.zig" + // 在数组中查找指定元素 + fn find(nums: []i32, target: i32) i32 { + for (nums, 0..) |num, i| { + if (num == target) return @intCast(i); + } + return -1; + } + ``` + ### 7.   扩容数组 在复杂的系统环境中,程序难以保证数组之后的内存空间是可用的,从而无法安全地扩展数组容量。因此在大多数编程语言中,**数组的长度是不可变的**。 如果我们希望扩容数组,则需重新建立一个更大的数组,然后把原数组元素依次拷贝到新数组。这是一个 $O(n)$ 的操作,在数组很大的情况下是非常耗时的。 -=== "Java" +=== "Python" - ```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; - } + ```python title="array.py" + def extend(nums: list[int], enlarge: int) -> list[int]: + """扩展数组长度""" + # 初始化一个扩展长度后的数组 + res = [0] * (len(nums) + enlarge) + # 将原数组中的所有元素复制到新数组 + for i in range(len(nums)): + res[i] = nums[i] + # 返回扩展后的新数组 + return res ``` === "C++" @@ -1034,18 +1032,36 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="array.py" - def extend(nums: list[int], enlarge: int) -> list[int]: - """扩展数组长度""" - # 初始化一个扩展长度后的数组 - res = [0] * (len(nums) + enlarge) - # 将原数组中的所有元素复制到新数组 - for i in range(len(nums)): - res[i] = nums[i] - # 返回扩展后的新数组 - return res + ```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#" + + ```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; + } ``` === "Go" @@ -1064,6 +1080,22 @@ comments: true } ``` +=== "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 + } + ``` + === "JS" ```javascript title="array.js" @@ -1100,73 +1132,6 @@ comments: true } ``` -=== "C" - - ```c title="array.c" - /* 扩展数组长度 */ - int *extend(int *nums, int size, int enlarge) { - // 初始化一个扩展长度后的数组 - int *res = (int *)malloc(sizeof(int) * (size + enlarge)); - // 将原数组中的所有元素复制到新数组 - for (int i = 0; i < size; i++) { - res[i] = nums[i]; - } - // 初始化扩展后的空间 - for (int i = size; i < size + enlarge; i++) { - res[i] = 0; - } - // 返回扩展后的新数组 - return res; - } - ``` - -=== "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" - // 扩展数组长度 - fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 { - // 初始化一个扩展长度后的数组 - var res = try mem_allocator.alloc(i32, nums.len + enlarge); - @memset(res, 0); - // 将原数组中的所有元素复制到新数组 - std.mem.copy(i32, res, nums); - // 返回扩展后的新数组 - return res; - } - ``` - === "Dart" ```dart title="array.dart" @@ -1199,6 +1164,41 @@ comments: true } ``` +=== "C" + + ```c title="array.c" + /* 扩展数组长度 */ + int *extend(int *nums, int size, int enlarge) { + // 初始化一个扩展长度后的数组 + int *res = (int *)malloc(sizeof(int) * (size + enlarge)); + // 将原数组中的所有元素复制到新数组 + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // 初始化扩展后的空间 + for (int i = size; i < size + enlarge; i++) { + res[i] = 0; + } + // 返回扩展后的新数组 + return res; + } + ``` + +=== "Zig" + + ```zig title="array.zig" + // 扩展数组长度 + fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 { + // 初始化一个扩展长度后的数组 + var res = try mem_allocator.alloc(i32, nums.len + enlarge); + @memset(res, 0); + // 将原数组中的所有元素复制到新数组 + std.mem.copy(i32, res, nums); + // 返回扩展后的新数组 + return res; + } + ``` + ## 4.1.2   数组优点与局限性 数组存储在连续的内存空间内,且元素类型相同。这种做法包含丰富的先验信息,系统可以利用这些信息来优化数据结构的操作效率。 diff --git a/chapter_array_and_linkedlist/linked_list.md b/chapter_array_and_linkedlist/linked_list.md index ae01048fc..58390b0ea 100755 --- a/chapter_array_and_linkedlist/linked_list.md +++ b/chapter_array_and_linkedlist/linked_list.md @@ -22,15 +22,14 @@ comments: true 如以下代码所示,链表节点 `ListNode` 除了包含值,还需额外保存一个引用(指针)。因此在相同数据量下,**链表比数组占用更多的内存空间**。 -=== "Java" +=== "Python" - ```java title="" - /* 链表节点类 */ - class ListNode { - int val; // 节点值 - ListNode next; // 指向下一节点的引用 - ListNode(int x) { val = x; } // 构造函数 - } + ```python title="" + class ListNode: + """链表节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.next: Optional[ListNode] = None # 指向下一节点的引用 ``` === "C++" @@ -44,14 +43,26 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="" - class ListNode: - """链表节点类""" - def __init__(self, val: int): - self.val: int = val # 节点值 - self.next: Optional[ListNode] = None # 指向下一节点的引用 + ```java title="" + /* 链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向下一节点的引用 + ListNode(int x) { val = x; } // 构造函数 + } + ``` + +=== "C#" + + ```csharp title="" + /* 链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向下一节点的引用 + ListNode(int x) => val = x; //构造函数 + } ``` === "Go" @@ -72,6 +83,20 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 链表节点类 */ + class ListNode { + var val: Int // 节点值 + var next: ListNode? // 指向下一节点的引用 + + init(x: Int) { // 构造函数 + val = x + } + } + ``` + === "JS" ```javascript title="" @@ -100,72 +125,6 @@ comments: true } ``` -=== "C" - - ```c title="" - /* 链表节点结构体 */ - struct ListNode { - int val; // 节点值 - struct ListNode *next; // 指向下一节点的指针 - }; - - typedef struct ListNode ListNode; - - /* 构造函数 */ - ListNode *newListNode(int val) { - ListNode *node, *next; - node = (ListNode *) malloc(sizeof(ListNode)); - node->val = val; - node->next = NULL; - return node; - } - ``` - -=== "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; - } - }; - } - ``` - === "Dart" ```dart title="" @@ -190,27 +149,68 @@ comments: true } ``` +=== "C" + + ```c title="" + /* 链表节点结构体 */ + struct ListNode { + int val; // 节点值 + struct ListNode *next; // 指向下一节点的指针 + }; + + typedef struct ListNode ListNode; + + /* 构造函数 */ + ListNode *newListNode(int val) { + ListNode *node, *next; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; + } + ``` + +=== "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; + } + }; + } + ``` + ## 4.2.1   链表常用操作 ### 1.   初始化链表 建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。初始化完成后,我们就可以从链表的头节点出发,通过引用指向 `next` 依次访问所有节点。 -=== "Java" +=== "Python" - ```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; + ```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 ``` === "C++" @@ -230,21 +230,38 @@ comments: true n3->next = n4; ``` -=== "Python" +=== "Java" - ```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 + ```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#" + + ```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; ``` === "Go" @@ -264,6 +281,23 @@ comments: true 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 + ``` + === "JS" ```javascript title="linked_list.js" @@ -298,74 +332,6 @@ comments: true n3.next = n4; ``` -=== "C" - - ```c title="linked_list.c" - /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个节点 - ListNode* n0 = newListNode(1); - ListNode* n1 = newListNode(3); - ListNode* n2 = newListNode(2); - ListNode* n3 = newListNode(5); - ListNode* n4 = newListNode(4); - // 构建引用指向 - n0->next = n1; - n1->next = n2; - n2->next = n3; - n3->next = n4; - ``` - -=== "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; - ``` - === "Dart" ```dart title="linked_list.dart" @@ -401,6 +367,40 @@ comments: true n3.borrow_mut().next = Some(n4.clone()); ``` +=== "C" + + ```c title="linked_list.c" + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个节点 + ListNode* n0 = newListNode(1); + ListNode* n1 = newListNode(3); + ListNode* n2 = newListNode(2); + ListNode* n3 = newListNode(5); + ListNode* n4 = newListNode(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; + ``` + 数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而链表是由多个独立的节点对象组成的。**我们通常将头节点当作链表的代称**,比如以上代码中的链表可被记做链表 `n0` 。 ### 2.   插入节点 @@ -413,15 +413,14 @@ comments: true

图 4-6   链表插入节点示例

-=== "Java" +=== "Python" - ```java title="linked_list.java" - /* 在链表的节点 n0 之后插入节点 P */ - void insert(ListNode n0, ListNode P) { - ListNode n1 = n0.next; - P.next = n1; - n0.next = P; - } + ```python title="linked_list.py" + def insert(n0: ListNode, P: ListNode): + """在链表的节点 n0 之后插入节点 P""" + n1 = n0.next + P.next = n1 + n0.next = P ``` === "C++" @@ -435,14 +434,26 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="linked_list.py" - def insert(n0: ListNode, P: ListNode): - """在链表的节点 n0 之后插入节点 P""" - n1 = n0.next - P.next = n1 - n0.next = P + ```java title="linked_list.java" + /* 在链表的节点 n0 之后插入节点 P */ + void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* 在链表的节点 n0 之后插入节点 P */ + void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } ``` === "Go" @@ -456,6 +467,17 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + /* 在链表的节点 n0 之后插入节点 P */ + func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + P.next = n1 + n0.next = P + } + ``` + === "JS" ```javascript title="linked_list.js" @@ -478,50 +500,6 @@ comments: true } ``` -=== "C" - - ```c title="linked_list.c" - /* 在链表的节点 n0 之后插入节点 P */ - void insert(ListNode *n0, ListNode *P) { - ListNode *n1 = n0->next; - P->next = n1; - n0->next = P; - } - ``` - -=== "C#" - - ```csharp title="linked_list.cs" - /* 在链表的节点 n0 之后插入节点 P */ - void insert(ListNode n0, ListNode P) { - ListNode? n1 = n0.next; - P.next = n1; - n0.next = P; - } - ``` - -=== "Swift" - - ```swift title="linked_list.swift" - /* 在链表的节点 n0 之后插入节点 P */ - func insert(n0: ListNode, P: ListNode) { - let n1 = n0.next - P.next = n1 - n0.next = P - } - ``` - -=== "Zig" - - ```zig title="linked_list.zig" - // 在链表的节点 n0 之后插入节点 P - fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { - var n1 = n0.?.next; - P.?.next = n1; - n0.?.next = P; - } - ``` - === "Dart" ```dart title="linked_list.dart" @@ -545,6 +523,28 @@ comments: true } ``` +=== "C" + + ```c title="linked_list.c" + /* 在链表的节点 n0 之后插入节点 P */ + void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; + } + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // 在链表的节点 n0 之后插入节点 P + fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { + var n1 = n0.?.next; + P.?.next = n1; + n0.?.next = P; + } + ``` + ### 3.   删除节点 如图 4-7 所示,在链表中删除节点也非常方便,**只需改变一个节点的引用(指针)即可**。 @@ -555,18 +555,17 @@ comments: true

图 4-7   链表删除节点

-=== "Java" +=== "Python" - ```java title="linked_list.java" - /* 删除链表的节点 n0 之后的首个节点 */ - void remove(ListNode n0) { - if (n0.next == null) - return; - // n0 -> P -> n1 - ListNode P = n0.next; - ListNode n1 = P.next; - n0.next = n1; - } + ```python title="linked_list.py" + def remove(n0: ListNode): + """删除链表的节点 n0 之后的首个节点""" + if not n0.next: + return + # n0 -> P -> n1 + P = n0.next + n1 = P.next + n0.next = n1 ``` === "C++" @@ -585,17 +584,32 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="linked_list.py" - def remove(n0: ListNode): - """删除链表的节点 n0 之后的首个节点""" - if not n0.next: - return - # n0 -> P -> n1 - P = n0.next - n1 = P.next - n0.next = n1 + ```java title="linked_list.java" + /* 删除链表的节点 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#" + + ```csharp title="linked_list.cs" + /* 删除链表的节点 n0 之后的首个节点 */ + void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } ``` === "Go" @@ -613,6 +627,22 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + /* 删除链表的节点 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 + } + ``` + === "JS" ```javascript title="linked_list.js" @@ -641,66 +671,6 @@ comments: true } ``` -=== "C" - - ```c title="linked_list.c" - /* 删除链表的节点 n0 之后的首个节点 */ - // 注意:stdio.h 占用了 remove 关键词 - void removeNode(ListNode *n0) { - if (!n0->next) - return; - // n0 -> P -> n1 - ListNode *P = n0->next; - ListNode *n1 = P->next; - n0->next = n1; - // 释放内存 - free(P); - } - ``` - -=== "C#" - - ```csharp title="linked_list.cs" - /* 删除链表的节点 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 之后的首个节点 */ - 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 之后的首个节点 - 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; - } - ``` - === "Dart" ```dart title="linked_list.dart" @@ -730,22 +700,50 @@ comments: true } ``` +=== "C" + + ```c title="linked_list.c" + /* 删除链表的节点 n0 之后的首个节点 */ + // 注意:stdio.h 占用了 remove 关键词 + void removeNode(ListNode *n0) { + if (!n0->next) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 释放内存 + free(P); + } + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // 删除链表的节点 n0 之后的首个节点 + 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.   访问节点 **在链表访问节点的效率较低**。如上节所述,我们可以在 $O(1)$ 时间下访问数组中的任意元素。链表则不然,程序需要从头节点出发,逐个向后遍历,直至找到目标节点。也就是说,访问链表的第 $i$ 个节点需要循环 $i - 1$ 轮,时间复杂度为 $O(n)$ 。 -=== "Java" +=== "Python" - ```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; - } + ```python title="linked_list.py" + def access(head: ListNode, index: int) -> ListNode | None: + """访问链表中索引为 index 的节点""" + for _ in range(index): + if not head: + return None + head = head.next + return head ``` === "C++" @@ -762,16 +760,32 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="linked_list.py" - def access(head: ListNode, index: int) -> ListNode | None: - """访问链表中索引为 index 的节点""" - for _ in range(index): - if not head: - return None - head = head.next - return head + ```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#" + + ```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; + } ``` === "Go" @@ -789,6 +803,22 @@ comments: true } ``` +=== "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 + } + ``` + === "JS" ```javascript title="linked_list.js" @@ -819,64 +849,6 @@ comments: true } ``` -=== "C" - - ```c title="linked_list.c" - /* 访问链表中索引为 index 的节点 */ - ListNode *access(ListNode *head, int index) { - while (head && head->next && index) { - head = head->next; - index--; - } - return head; - } - ``` - -=== "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 的节点 - 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; - } - ``` - === "Dart" ```dart title="linked_list.dart" @@ -903,24 +875,50 @@ comments: true } ``` +=== "C" + + ```c title="linked_list.c" + /* 访问链表中索引为 index 的节点 */ + ListNode *access(ListNode *head, int index) { + while (head && head->next && index) { + head = head->next; + index--; + } + return head; + } + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // 访问链表中索引为 index 的节点 + 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; + } + ``` + ### 5.   查找节点 遍历链表,查找链表内值为 `target` 的节点,输出节点在链表中的索引。此过程也属于线性查找。 -=== "Java" +=== "Python" - ```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; - } + ```python title="linked_list.py" + def find(head: ListNode, target: int) -> int: + """在链表中查找值为 target 的首个节点""" + index = 0 + while head: + if head.val == target: + return index + head = head.next + index += 1 + return -1 ``` === "C++" @@ -939,18 +937,36 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="linked_list.py" - def find(head: ListNode, target: int) -> int: - """在链表中查找值为 target 的首个节点""" - index = 0 - while head: - if head.val == target: - return index - head = head.next - index += 1 - return -1 + ```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#" + + ```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; + } ``` === "Go" @@ -970,6 +986,24 @@ comments: true } ``` +=== "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 + } + ``` + === "JS" ```javascript title="linked_list.js" @@ -1004,72 +1038,6 @@ comments: true } ``` -=== "C" - - ```c title="linked_list.c" - /* 在链表中查找值为 target 的首个节点 */ - int find(ListNode *head, int target) { - int index = 0; - while (head) { - if (head->val == target) - return index; - head = head->next; - index++; - } - return -1; - } - ``` - -=== "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 的首个节点 - 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; - } - ``` - === "Dart" ```dart title="linked_list.dart" @@ -1100,6 +1068,38 @@ comments: true } ``` +=== "C" + + ```c title="linked_list.c" + /* 在链表中查找值为 target 的首个节点 */ + int find(ListNode *head, int target) { + int index = 0; + while (head) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; + } + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // 在链表中查找值为 target 的首个节点 + 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.2   数组 VS 链表 表 4-1 总结对比了数组和链表的各项特点与操作效率。由于它们采用两种相反的存储策略,因此各种性质和操作效率也呈现对立的特点。 @@ -1128,16 +1128,15 @@ comments: true - **环形链表**:如果我们令单向链表的尾节点指向头节点(即首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。 - **双向链表**:与单向链表相比,双向链表记录了两个方向的引用。双向链表的节点定义同时包含指向后继节点(下一个节点)和前驱节点(上一个节点)的引用(指针)。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。 -=== "Java" +=== "Python" - ```java title="" - /* 双向链表节点类 */ - class ListNode { - int val; // 节点值 - ListNode next; // 指向后继节点的引用 - ListNode prev; // 指向前驱节点的引用 - ListNode(int x) { val = x; } // 构造函数 - } + ```python title="" + class ListNode: + """双向链表节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.next: Optional[ListNode] = None # 指向后继节点的引用 + self.prev: Optional[ListNode] = None # 指向前驱节点的引用 ``` === "C++" @@ -1152,15 +1151,28 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="" - class ListNode: - """双向链表节点类""" - def __init__(self, val: int): - self.val: int = val # 节点值 - self.next: Optional[ListNode] = None # 指向后继节点的引用 - self.prev: Optional[ListNode] = None # 指向前驱节点的引用 + ```java title="" + /* 双向链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向后继节点的引用 + ListNode prev; // 指向前驱节点的引用 + ListNode(int x) { val = x; } // 构造函数 + } + ``` + +=== "C#" + + ```csharp title="" + /* 双向链表节点类 */ + class ListNode { + int val; // 节点值 + ListNode next; // 指向后继节点的引用 + ListNode prev; // 指向前驱节点的引用 + ListNode(int x) => val = x; // 构造函数 + } ``` === "Go" @@ -1183,6 +1195,21 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 双向链表节点类 */ + class ListNode { + var val: Int // 节点值 + var next: ListNode? // 指向后继节点的引用 + var prev: ListNode? // 指向前驱节点的引用 + + init(x: Int) { // 构造函数 + val = x + } + } + ``` + === "JS" ```javascript title="" @@ -1215,78 +1242,6 @@ comments: true } ``` -=== "C" - - ```c title="" - /* 双向链表节点结构体 */ - struct ListNode { - int val; // 节点值 - struct ListNode *next; // 指向后继节点的指针 - struct ListNode *prev; // 指向前驱节点的指针 - }; - - typedef struct ListNode ListNode; - - /* 构造函数 */ - ListNode *newListNode(int val) { - ListNode *node, *next; - node = (ListNode *) malloc(sizeof(ListNode)); - node->val = val; - node->next = NULL; - node->prev = NULL; - return node; - } - ``` - -=== "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; - } - }; - } - ``` - === "Dart" ```dart title="" @@ -1325,6 +1280,51 @@ comments: true } ``` +=== "C" + + ```c title="" + /* 双向链表节点结构体 */ + struct ListNode { + int val; // 节点值 + struct ListNode *next; // 指向后继节点的指针 + struct ListNode *prev; // 指向前驱节点的指针 + }; + + typedef struct ListNode ListNode; + + /* 构造函数 */ + ListNode *newListNode(int val) { + ListNode *node, *next; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + node->prev = NULL; + return node; + } + ``` + +=== "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; + } + }; + } + ``` + ![常见链表种类](linked_list.assets/linkedlist_common_types.png)

图 4-8   常见链表种类

diff --git a/chapter_array_and_linkedlist/list.md b/chapter_array_and_linkedlist/list.md index 3d69ec746..533b9eeee 100755 --- a/chapter_array_and_linkedlist/list.md +++ b/chapter_array_and_linkedlist/list.md @@ -14,15 +14,14 @@ comments: true 我们通常使用“无初始值”和“有初始值”这两种初始化方法。 -=== "Java" +=== "Python" - ```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)); + ```python title="list.py" + # 初始化列表 + # 无初始值 + list1: list[int] = [] + # 有初始值 + list: list[int] = [1, 3, 2, 5, 4] ``` === "C++" @@ -36,14 +35,26 @@ comments: true vector list = { 1, 3, 2, 5, 4 }; ``` -=== "Python" +=== "Java" - ```python title="list.py" - # 初始化列表 - # 无初始值 - list1: list[int] = [] - # 有初始值 - list: list[int] = [1, 3, 2, 5, 4] + ```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#" + + ```csharp title="list.cs" + /* 初始化列表 */ + // 无初始值 + List list1 = new (); + // 有初始值 + int[] numbers = new int[] { 1, 3, 2, 5, 4 }; + List list = numbers.ToList(); ``` === "Go" @@ -56,6 +67,16 @@ comments: true list := []int{1, 3, 2, 5, 4} ``` +=== "Swift" + + ```swift title="list.swift" + /* 初始化列表 */ + // 无初始值 + let list1: [Int] = [] + // 有初始值 + var list = [1, 3, 2, 5, 4] + ``` + === "JS" ```javascript title="list.js" @@ -76,42 +97,6 @@ comments: true const list: number[] = [1, 3, 2, 5, 4]; ``` -=== "C" - - ```c title="list.c" - // C 未提供内置动态数组 - ``` - -=== "C#" - - ```csharp title="list.cs" - /* 初始化列表 */ - // 无初始值 - List list1 = new (); - // 有初始值 - 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 }); - ``` - === "Dart" ```dart title="list.dart" @@ -132,18 +117,33 @@ comments: true let list2: Vec = vec![1, 3, 2, 5, 4]; ``` +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "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 }); + ``` + ### 2.   访问元素 列表本质上是数组,因此可以在 $O(1)$ 时间内访问和更新元素,效率很高。 -=== "Java" +=== "Python" - ```java title="list.java" - /* 访问元素 */ - int num = list.get(1); // 访问索引 1 处的元素 + ```python title="list.py" + # 访问元素 + num: int = list[1] # 访问索引 1 处的元素 - /* 更新元素 */ - list.set(1, 0); // 将索引 1 处的元素更新为 0 + # 更新元素 + list[1] = 0 # 将索引 1 处的元素更新为 0 ``` === "C++" @@ -156,14 +156,24 @@ comments: true list[1] = 0; // 将索引 1 处的元素更新为 0 ``` -=== "Python" +=== "Java" - ```python title="list.py" - # 访问元素 - num: int = list[1] # 访问索引 1 处的元素 + ```java title="list.java" + /* 访问元素 */ + int num = list.get(1); // 访问索引 1 处的元素 - # 更新元素 - list[1] = 0 # 将索引 1 处的元素更新为 0 + /* 更新元素 */ + list.set(1, 0); // 将索引 1 处的元素更新为 0 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 访问元素 */ + int num = list[1]; // 访问索引 1 处的元素 + + /* 更新元素 */ + list[1] = 0; // 将索引 1 处的元素更新为 0 ``` === "Go" @@ -176,6 +186,16 @@ comments: true list[1] = 0 // 将索引 1 处的元素更新为 0 ``` +=== "Swift" + + ```swift title="list.swift" + /* 访问元素 */ + let num = list[1] // 访问索引 1 处的元素 + + /* 更新元素 */ + list[1] = 0 // 将索引 1 处的元素更新为 0 + ``` + === "JS" ```javascript title="list.js" @@ -196,42 +216,6 @@ comments: true list[1] = 0; // 将索引 1 处的元素更新为 0 ``` -=== "C" - - ```c title="list.c" - // 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 - ``` - === "Dart" ```dart title="list.dart" @@ -251,28 +235,44 @@ comments: true list[1] = 0; // 将索引 1 处的元素更新为 0 ``` +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 访问元素 + var num = list.items[1]; // 访问索引 1 处的元素 + + // 更新元素 + list.items[1] = 0; // 将索引 1 处的元素更新为 0 + ``` + ### 3.   插入与删除元素 相较于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但插入和删除元素的效率仍与数组相同,时间复杂度为 $O(n)$ 。 -=== "Java" +=== "Python" - ```java title="list.java" - /* 清空列表 */ - list.clear(); + ```python title="list.py" + # 清空列表 + list.clear() - /* 尾部添加元素 */ - list.add(1); - list.add(3); - list.add(2); - list.add(5); - list.add(4); + # 尾部添加元素 + list.append(1) + list.append(3) + list.append(2) + list.append(5) + list.append(4) - /* 中间插入元素 */ - list.add(3, 6); // 在索引 3 处插入数字 6 + # 中间插入元素 + list.insert(3, 6) # 在索引 3 处插入数字 6 - /* 删除元素 */ - list.remove(3); // 删除索引 3 处的元素 + # 删除元素 + list.pop(3) # 删除索引 3 处的元素 ``` === "C++" @@ -295,24 +295,44 @@ comments: true list.erase(list.begin() + 3); // 删除索引 3 处的元素 ``` -=== "Python" +=== "Java" - ```python title="list.py" - # 清空列表 - list.clear() + ```java title="list.java" + /* 清空列表 */ + list.clear(); - # 尾部添加元素 - list.append(1) - list.append(3) - list.append(2) - list.append(5) - list.append(4) + /* 尾部添加元素 */ + list.add(1); + list.add(3); + list.add(2); + list.add(5); + list.add(4); - # 中间插入元素 - list.insert(3, 6) # 在索引 3 处插入数字 6 + /* 中间插入元素 */ + list.add(3, 6); // 在索引 3 处插入数字 6 - # 删除元素 - list.pop(3) # 删除索引 3 处的元素 + /* 删除元素 */ + list.remove(3); // 删除索引 3 处的元素 + ``` + +=== "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); ``` === "Go" @@ -335,6 +355,26 @@ comments: true list = append(list[:3], list[4:]...) // 删除索引 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 处的元素 + ``` + === "JS" ```javascript title="list.js" @@ -375,72 +415,6 @@ comments: true list.splice(3, 1); ``` -=== "C" - - ```c title="list.c" - // 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 处的元素 - ``` - === "Dart" ```dart title="list.dart" @@ -481,24 +455,48 @@ comments: true list.remove(3); // 删除索引 3 处的元素 ``` +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "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 处的元素 + ``` + ### 4.   遍历列表 与数组一样,列表可以根据索引遍历,也可以直接遍历各元素。 -=== "Java" +=== "Python" - ```java title="list.java" - /* 通过索引遍历列表 */ - int count = 0; - for (int i = 0; i < list.size(); i++) { - count++; - } + ```python title="list.py" + # 通过索引遍历列表 + count = 0 + for i in range(len(list)): + count += 1 - /* 直接遍历列表元素 */ - count = 0; - for (int n : list) { - count++; - } + # 直接遍历列表元素 + count = 0 + for n in list: + count += 1 ``` === "C++" @@ -517,18 +515,36 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="list.py" - # 通过索引遍历列表 - count = 0 - for i in range(len(list)): - count += 1 + ```java title="list.java" + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < list.size(); i++) { + count++; + } - # 直接遍历列表元素 - count = 0 - for n in list: - count += 1 + /* 直接遍历列表元素 */ + count = 0; + for (int n : list) { + count++; + } + ``` + +=== "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++; + } ``` === "Go" @@ -547,6 +563,22 @@ comments: true } ``` +=== "Swift" + + ```swift title="list.swift" + /* 通过索引遍历列表 */ + var count = 0 + for _ in list.indices { + count += 1 + } + + /* 直接遍历列表元素 */ + count = 0 + for _ in list { + count += 1 + } + ``` + === "JS" ```javascript title="list.js" @@ -579,61 +611,6 @@ comments: true } ``` -=== "C" - - ```c title="list.c" - // 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; - } - ``` - === "Dart" ```dart title="list.dart" @@ -666,16 +643,39 @@ comments: true } ``` +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "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; + } + ``` + ### 5.   拼接列表 给定一个新列表 `list1` ,我们可以将该列表拼接到原列表的尾部。 -=== "Java" +=== "Python" - ```java title="list.java" - /* 拼接两个列表 */ - List list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); - list.addAll(list1); // 将列表 list1 拼接到 list 之后 + ```python title="list.py" + # 拼接两个列表 + list1: list[int] = [6, 8, 7, 10, 9] + list += list1 # 将列表 list1 拼接到 list 之后 ``` === "C++" @@ -687,12 +687,20 @@ comments: true list.insert(list.end(), list1.begin(), list1.end()); ``` -=== "Python" +=== "Java" - ```python title="list.py" - # 拼接两个列表 - list1: list[int] = [6, 8, 7, 10, 9] - list += list1 # 将列表 list1 拼接到 list 之后 + ```java title="list.java" + /* 拼接两个列表 */ + List list1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + list.addAll(list1); // 将列表 list1 拼接到 list 之后 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 拼接两个列表 */ + List list1 = new() { 6, 8, 7, 10, 9 }; + list.AddRange(list1); // 将列表 list1 拼接到 list 之后 ``` === "Go" @@ -703,6 +711,14 @@ comments: true list = append(list, list1...) // 将列表 list1 拼接到 list 之后 ``` +=== "Swift" + + ```swift title="list.swift" + /* 拼接两个列表 */ + let list1 = [6, 8, 7, 10, 9] + list.append(contentsOf: list1) // 将列表 list1 拼接到 list 之后 + ``` + === "JS" ```javascript title="list.js" @@ -719,38 +735,6 @@ comments: true list.push(...list1); // 将列表 list1 拼接到 list 之后 ``` -=== "C" - - ```c title="list.c" - // 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 之后 - ``` - === "Dart" ```dart title="list.dart" @@ -767,15 +751,31 @@ comments: true list.extend(list1); ``` +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "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 之后 + ``` + ### 6.   排序列表 完成列表排序后,我们便可以使用在数组类算法题中经常考察的“二分查找”和“双指针”算法。 -=== "Java" +=== "Python" - ```java title="list.java" - /* 排序列表 */ - Collections.sort(list); // 排序后,列表元素从小到大排列 + ```python title="list.py" + # 排序列表 + list.sort() # 排序后,列表元素从小到大排列 ``` === "C++" @@ -785,11 +785,18 @@ comments: true sort(list.begin(), list.end()); // 排序后,列表元素从小到大排列 ``` -=== "Python" +=== "Java" - ```python title="list.py" - # 排序列表 - list.sort() # 排序后,列表元素从小到大排列 + ```java title="list.java" + /* 排序列表 */ + Collections.sort(list); // 排序后,列表元素从小到大排列 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 排序列表 */ + list.Sort(); // 排序后,列表元素从小到大排列 ``` === "Go" @@ -799,6 +806,13 @@ comments: true sort.Ints(list) // 排序后,列表元素从小到大排列 ``` +=== "Swift" + + ```swift title="list.swift" + /* 排序列表 */ + list.sort() // 排序后,列表元素从小到大排列 + ``` + === "JS" ```javascript title="list.js" @@ -813,33 +827,6 @@ comments: true list.sort((a, b) => a - b); // 排序后,列表元素从小到大排列 ``` -=== "C" - - ```c title="list.c" - // 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)); - ``` - === "Dart" ```dart title="list.dart" @@ -854,6 +841,19 @@ comments: true list.sort(); // 排序后,列表元素从小到大排列 ``` +=== "C" + + ```c title="list.c" + // C 未提供内置动态数组 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 排序列表 + std.sort.sort(i32, list.items, {}, comptime std.sort.asc(i32)); + ``` + ## 4.3.2   列表实现 许多编程语言都提供内置的列表,例如 Java、C++、Python 等。它们的实现比较复杂,各个参数的设定也非常有考究,例如初始容量、扩容倍数等。感兴趣的读者可以查阅源码进行学习。 @@ -864,106 +864,85 @@ comments: true - **数量记录**:声明一个变量 size,用于记录列表当前元素数量,并随着元素插入和删除实时更新。根据此变量,我们可以定位列表尾部,以及判断是否需要扩容。 - **扩容机制**:若插入元素时列表容量已满,则需要进行扩容。首先根据扩容倍数创建一个更大的数组,再将当前数组的所有元素依次移动至新数组。在本示例中,我们规定每次将数组扩容至之前的 2 倍。 -=== "Java" +=== "Python" - ```java title="my_list.java" - /* 列表类简易实现 */ - class MyList { - private int[] nums; // 数组(存储列表元素) - private int capacity = 10; // 列表容量 - private int size = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 + ```python title="my_list.py" + class MyList: + """列表类简易实现""" - /* 构造方法 */ - public MyList() { - nums = new int[capacity]; - } + def __init__(self): + """构造方法""" + self.__capacity: int = 10 # 列表容量 + self.__nums: list[int] = [0] * self.__capacity # 数组(存储列表元素) + self.__size: int = 0 # 列表长度(即当前元素数量) + self.__extend_ratio: int = 2 # 每次列表扩容的倍数 - /* 获取列表长度(即当前元素数量) */ - public int size() { - return size; - } + def size(self) -> int: + """获取列表长度(即当前元素数量)""" + return self.__size - /* 获取列表容量 */ - public int capacity() { - return capacity; - } + def capacity(self) -> int: + """获取列表容量""" + return self.__capacity - /* 访问元素 */ - public int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("索引越界"); - return nums[index]; - } + def get(self, index: int) -> int: + """访问元素""" + # 索引如果越界则抛出异常,下同 + if index < 0 or index >= self.__size: + raise IndexError("索引越界") + return self.__nums[index] - /* 更新元素 */ - public void set(int index, int num) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("索引越界"); - nums[index] = num; - } + def set(self, num: int, index: int): + """更新元素""" + if index < 0 or index >= self.__size: + raise IndexError("索引越界") + self.__nums[index] = num - /* 尾部添加元素 */ - public void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (size == capacity()) - extendCapacity(); - nums[size] = num; - // 更新元素数量 - size++; - } + def add(self, num: int): + """尾部添加元素""" + # 元素数量超出容量时,触发扩容机制 + if self.size() == self.capacity(): + self.extend_capacity() + self.__nums[self.__size] = num + self.__size += 1 - /* 中间插入元素 */ - 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++; - } + def insert(self, num: int, index: int): + """中间插入元素""" + if index < 0 or index >= self.__size: + raise IndexError("索引越界") + # 元素数量超出容量时,触发扩容机制 + if self.__size == self.capacity(): + self.extend_capacity() + # 将索引 index 以及之后的元素都向后移动一位 + for j in range(self.__size - 1, index - 1, -1): + self.__nums[j + 1] = self.__nums[j] + self.__nums[index] = num + # 更新元素数量 + self.__size += 1 - /* 删除元素 */ - 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; - } + def remove(self, index: int) -> int: + """删除元素""" + if index < 0 or index >= self.__size: + raise IndexError("索引越界") + 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 - /* 列表扩容 */ - public void extendCapacity() { - // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组拷贝到新数组 - nums = Arrays.copyOf(nums, capacity() * extendRatio); - // 更新列表容量 - capacity = nums.length; - } + def extend_capacity(self): + """列表扩容""" + # 新建一个长度为原数组 __extend_ratio 倍的新数组,并将原数组拷贝到新数组 + self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1) + # 更新列表容量 + self.__capacity = len(self.__nums) - /* 将列表转换为数组 */ - public int[] toArray() { - int size = size(); - // 仅转换有效长度范围内的列表元素 - int[] nums = new int[size]; - for (int i = 0; i < size; i++) { - nums[i] = get(i); - } - return nums; - } - } + def to_array(self) -> list[int]: + """返回有效长度的列表""" + return self.__nums[: self.__size] ``` === "C++" @@ -1081,85 +1060,207 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="my_list.py" - class MyList: - """列表类简易实现""" + ```java title="my_list.java" + /* 列表类简易实现 */ + class MyList { + private int[] nums; // 数组(存储列表元素) + private int capacity = 10; // 列表容量 + private int size = 0; // 列表长度(即当前元素数量) + private int extendRatio = 2; // 每次列表扩容的倍数 - def __init__(self): - """构造方法""" - self.__capacity: int = 10 # 列表容量 - self.__nums: list[int] = [0] * self.__capacity # 数组(存储列表元素) - self.__size: int = 0 # 列表长度(即当前元素数量) - self.__extend_ratio: int = 2 # 每次列表扩容的倍数 + /* 构造方法 */ + public MyList() { + nums = new int[capacity]; + } - def size(self) -> int: - """获取列表长度(即当前元素数量)""" - return self.__size + /* 获取列表长度(即当前元素数量) */ + public int size() { + return size; + } - def capacity(self) -> int: - """获取列表容量""" - return self.__capacity + /* 获取列表容量 */ + public int capacity() { + return capacity; + } - def get(self, index: int) -> int: - """访问元素""" - # 索引如果越界则抛出异常,下同 - if index < 0 or index >= self.__size: - raise IndexError("索引越界") - return self.__nums[index] + /* 访问元素 */ + public int get(int index) { + // 索引如果越界则抛出异常,下同 + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + return nums[index]; + } - def set(self, num: int, index: int): - """更新元素""" - if index < 0 or index >= self.__size: - raise IndexError("索引越界") - self.__nums[index] = num + /* 更新元素 */ + public void set(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + nums[index] = num; + } - def add(self, num: int): - """尾部添加元素""" - # 元素数量超出容量时,触发扩容机制 - if self.size() == self.capacity(): - self.extend_capacity() - self.__nums[self.__size] = num - self.__size += 1 + /* 尾部添加元素 */ + public void add(int num) { + // 元素数量超出容量时,触发扩容机制 + if (size == capacity()) + extendCapacity(); + nums[size] = num; + // 更新元素数量 + size++; + } - def insert(self, num: int, index: int): - """中间插入元素""" - if index < 0 or index >= self.__size: - raise IndexError("索引越界") - # 元素数量超出容量时,触发扩容机制 - if self.__size == self.capacity(): - self.extend_capacity() - # 将索引 index 以及之后的元素都向后移动一位 - for j in range(self.__size - 1, index - 1, -1): - self.__nums[j + 1] = self.__nums[j] - self.__nums[index] = num - # 更新元素数量 - self.__size += 1 + /* 中间插入元素 */ + 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++; + } - def remove(self, index: int) -> int: - """删除元素""" - if index < 0 or index >= self.__size: - raise IndexError("索引越界") - 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 + /* 删除元素 */ + 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; + } - def extend_capacity(self): - """列表扩容""" - # 新建一个长度为原数组 __extend_ratio 倍的新数组,并将原数组拷贝到新数组 - self.__nums = self.__nums + [0] * self.capacity() * (self.__extend_ratio - 1) - # 更新列表容量 - self.__capacity = len(self.__nums) + /* 列表扩容 */ + public void extendCapacity() { + // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组拷贝到新数组 + nums = Arrays.copyOf(nums, capacity() * extendRatio); + // 更新列表容量 + capacity = nums.length; + } - def to_array(self) -> list[int]: - """返回有效长度的列表""" - return self.__nums[: self.__size] + /* 将列表转换为数组 */ + 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#" + + ```csharp title="my_list.cs" + /* 列表类简易实现 */ + class MyList { + private int[] nums; // 数组(存储列表元素) + private int numsCapacity = 10; // 列表容量 + private int numsSize = 0; // 列表长度(即当前元素数量) + private int extendRatio = 2; // 每次列表扩容的倍数 + + /* 构造方法 */ + public MyList() { + nums = new int[numsCapacity]; + } + + /* 获取列表长度(即当前元素数量)*/ + public int size() { + return numsSize; + } + + /* 获取列表容量 */ + public int capacity() { + return numsCapacity; + } + + /* 访问元素 */ + public int get(int index) { + // 索引如果越界则抛出异常,下同 + if (index < 0 || index >= numsSize) + throw new IndexOutOfRangeException("索引越界"); + return nums[index]; + } + + /* 更新元素 */ + public void set(int index, int num) { + if (index < 0 || index >= numsSize) + throw new IndexOutOfRangeException("索引越界"); + nums[index] = num; + } + + /* 尾部添加元素 */ + public void add(int num) { + // 元素数量超出容量时,触发扩容机制 + if (numsSize == numsCapacity) + extendCapacity(); + nums[numsSize] = num; + // 更新元素数量 + numsSize++; + } + + /* 中间插入元素 */ + public void insert(int index, int num) { + if (index < 0 || index >= numsSize) + throw new IndexOutOfRangeException("索引越界"); + // 元素数量超出容量时,触发扩容机制 + if (numsSize == numsCapacity) + extendCapacity(); + // 将索引 index 以及之后的元素都向后移动一位 + for (int j = numsSize - 1; j >= index; j--) { + nums[j + 1] = nums[j]; + } + nums[index] = num; + // 更新元素数量 + numsSize++; + } + + /* 删除元素 */ + public int remove(int index) { + if (index < 0 || index >= numsSize) + throw new IndexOutOfRangeException("索引越界"); + int num = nums[index]; + // 将索引 index 之后的元素都向前移动一位 + for (int j = index; j < numsSize - 1; j++) { + nums[j] = nums[j + 1]; + } + // 更新元素数量 + numsSize--; + // 返回被删除元素 + return num; + } + + /* 列表扩容 */ + public void extendCapacity() { + // 新建一个长度为 numsCapacity * extendRatio 的数组,并将原数组拷贝到新数组 + Array.Resize(ref nums, numsCapacity * extendRatio); + // 更新列表容量 + numsCapacity = nums.Length; + } + + /* 将列表转换为数组 */ + public int[] toArray() { + // 仅转换有效长度范围内的列表元素 + int[] nums = new int[numsSize]; + for (int i = 0; i < numsSize; i++) { + nums[i] = get(i); + } + return nums; + } + } ``` === "Go" @@ -1270,6 +1371,113 @@ comments: true } ``` +=== "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() { + // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组拷贝到新数组 + nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) + // 更新列表容量 + _capacity = nums.count + } + + /* 将列表转换为数组 */ + func toArray() -> [Int] { + var nums = Array(repeating: 0, count: _size) + for i in 0 ..< _size { + nums[i] = get(index: i) + } + return nums + } + } + ``` + === "JS" ```javascript title="my_list.js" @@ -1472,442 +1680,6 @@ comments: true } ``` -=== "C" - - ```c title="my_list.c" - /* 列表类简易实现 */ - struct myList { - int *nums; // 数组(存储列表元素) - int capacity; // 列表容量 - int size; // 列表大小 - int extendRatio; // 列表每次扩容的倍数 - }; - - typedef struct myList myList; - - /* 构造函数 */ - myList *newMyList() { - myList *list = malloc(sizeof(myList)); - list->capacity = 10; - list->nums = malloc(sizeof(int) * list->capacity); - list->size = 0; - list->extendRatio = 2; - return list; - } - - /* 析构函数 */ - void delMyList(myList *list) { - free(list->nums); - free(list); - } - - /* 获取列表长度 */ - int size(myList *list) { - return list->size; - } - - /* 获取列表容量 */ - int capacity(myList *list) { - return list->capacity; - } - - /* 访问元素 */ - int get(myList *list, int index) { - assert(index >= 0 && index < list->size); - return list->nums[index]; - } - - /* 更新元素 */ - void set(myList *list, int index, int num) { - assert(index >= 0 && index < list->size); - list->nums[index] = num; - } - - /* 尾部添加元素 */ - void add(myList *list, int num) { - if (size(list) == capacity(list)) { - extendCapacity(list); // 扩容 - } - list->nums[size(list)] = num; - list->size++; - } - - /* 中间插入元素 */ - void insert(myList *list, int index, int num) { - assert(index >= 0 && index < size(list)); - for (int i = size(list); i > index; --i) { - list->nums[i] = list->nums[i - 1]; - } - list->nums[index] = num; - list->size++; - } - - /* 删除元素 */ - // 注意:stdio.h 占用了 remove 关键词 - int removeNum(myList *list, int index) { - assert(index >= 0 && index < size(list)); - int num = list->nums[index]; - for (int i = index; i < size(list) - 1; i++) { - list->nums[i] = list->nums[i + 1]; - } - list->size--; - return num; - } - - /* 列表扩容 */ - void extendCapacity(myList *list) { - // 先分配空间 - int newCapacity = capacity(list) * list->extendRatio; - int *extend = (int *)malloc(sizeof(int) * newCapacity); - int *temp = list->nums; - - // 拷贝旧数据到新数据 - for (int i = 0; i < size(list); i++) - extend[i] = list->nums[i]; - - // 释放旧数据 - free(temp); - - // 更新新数据 - list->nums = extend; - list->capacity = newCapacity; - } - - /* 将列表转换为 Array 用于打印 */ - int *toArray(myList *list) { - return list->nums; - } - ``` - -=== "C#" - - ```csharp title="my_list.cs" - /* 列表类简易实现 */ - class MyList { - private int[] nums; // 数组(存储列表元素) - private int numsCapacity = 10; // 列表容量 - private int numsSize = 0; // 列表长度(即当前元素数量) - private int extendRatio = 2; // 每次列表扩容的倍数 - - /* 构造方法 */ - public MyList() { - nums = new int[numsCapacity]; - } - - /* 获取列表长度(即当前元素数量)*/ - public int size() { - return numsSize; - } - - /* 获取列表容量 */ - public int capacity() { - return numsCapacity; - } - - /* 访问元素 */ - public int get(int index) { - // 索引如果越界则抛出异常,下同 - if (index < 0 || index >= numsSize) - throw new IndexOutOfRangeException("索引越界"); - return nums[index]; - } - - /* 更新元素 */ - public void set(int index, int num) { - if (index < 0 || index >= numsSize) - throw new IndexOutOfRangeException("索引越界"); - nums[index] = num; - } - - /* 尾部添加元素 */ - public void add(int num) { - // 元素数量超出容量时,触发扩容机制 - if (numsSize == numsCapacity) - extendCapacity(); - nums[numsSize] = num; - // 更新元素数量 - numsSize++; - } - - /* 中间插入元素 */ - public void insert(int index, int num) { - if (index < 0 || index >= numsSize) - throw new IndexOutOfRangeException("索引越界"); - // 元素数量超出容量时,触发扩容机制 - if (numsSize == numsCapacity) - extendCapacity(); - // 将索引 index 以及之后的元素都向后移动一位 - for (int j = numsSize - 1; j >= index; j--) { - nums[j + 1] = nums[j]; - } - nums[index] = num; - // 更新元素数量 - numsSize++; - } - - /* 删除元素 */ - public int remove(int index) { - if (index < 0 || index >= numsSize) - throw new IndexOutOfRangeException("索引越界"); - int num = nums[index]; - // 将索引 index 之后的元素都向前移动一位 - for (int j = index; j < numsSize - 1; j++) { - nums[j] = nums[j + 1]; - } - // 更新元素数量 - numsSize--; - // 返回被删除元素 - return num; - } - - /* 列表扩容 */ - public void extendCapacity() { - // 新建一个长度为 numsCapacity * extendRatio 的数组,并将原数组拷贝到新数组 - Array.Resize(ref nums, numsCapacity * extendRatio); - // 更新列表容量 - numsCapacity = nums.Length; - } - - /* 将列表转换为数组 */ - public int[] toArray() { - // 仅转换有效长度范围内的列表元素 - int[] nums = new int[numsSize]; - for (int i = 0; i < numsSize; i++) { - nums[i] = get(i); - } - return nums; - } - } - ``` - -=== "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() { - // 新建一个长度为原数组 extendRatio 倍的新数组,并将原数组拷贝到新数组 - nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) - // 更新列表容量 - _capacity = nums.count - } - - /* 将列表转换为数组 */ - func toArray() -> [Int] { - var nums = Array(repeating: 0, count: _size) - for i in 0 ..< _size { - nums[i] = get(index: i) - } - return nums - } - } - ``` - -=== "Zig" - - ```zig title="my_list.zig" - // 列表类简易实现 - 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); - @memset(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(); - // 将索引 index 以及之后的元素都向后移动一位 - 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); - @memset(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()); - @memset(nums, @as(T, 0)); - for (nums, 0..) |*num, i| { - num.* = self.get(i); - } - return nums; - } - }; - } - ``` - === "Dart" ```dart title="my_list.dart" @@ -2111,3 +1883,231 @@ comments: true } } ``` + +=== "C" + + ```c title="my_list.c" + /* 列表类简易实现 */ + struct myList { + int *nums; // 数组(存储列表元素) + int capacity; // 列表容量 + int size; // 列表大小 + int extendRatio; // 列表每次扩容的倍数 + }; + + typedef struct myList myList; + + /* 构造函数 */ + myList *newMyList() { + myList *list = malloc(sizeof(myList)); + list->capacity = 10; + list->nums = malloc(sizeof(int) * list->capacity); + list->size = 0; + list->extendRatio = 2; + return list; + } + + /* 析构函数 */ + void delMyList(myList *list) { + free(list->nums); + free(list); + } + + /* 获取列表长度 */ + int size(myList *list) { + return list->size; + } + + /* 获取列表容量 */ + int capacity(myList *list) { + return list->capacity; + } + + /* 访问元素 */ + int get(myList *list, int index) { + assert(index >= 0 && index < list->size); + return list->nums[index]; + } + + /* 更新元素 */ + void set(myList *list, int index, int num) { + assert(index >= 0 && index < list->size); + list->nums[index] = num; + } + + /* 尾部添加元素 */ + void add(myList *list, int num) { + if (size(list) == capacity(list)) { + extendCapacity(list); // 扩容 + } + list->nums[size(list)] = num; + list->size++; + } + + /* 中间插入元素 */ + void insert(myList *list, int index, int num) { + assert(index >= 0 && index < size(list)); + for (int i = size(list); i > index; --i) { + list->nums[i] = list->nums[i - 1]; + } + list->nums[index] = num; + list->size++; + } + + /* 删除元素 */ + // 注意:stdio.h 占用了 remove 关键词 + int removeNum(myList *list, int index) { + assert(index >= 0 && index < size(list)); + int num = list->nums[index]; + for (int i = index; i < size(list) - 1; i++) { + list->nums[i] = list->nums[i + 1]; + } + list->size--; + return num; + } + + /* 列表扩容 */ + void extendCapacity(myList *list) { + // 先分配空间 + int newCapacity = capacity(list) * list->extendRatio; + int *extend = (int *)malloc(sizeof(int) * newCapacity); + int *temp = list->nums; + + // 拷贝旧数据到新数据 + for (int i = 0; i < size(list); i++) + extend[i] = list->nums[i]; + + // 释放旧数据 + free(temp); + + // 更新新数据 + list->nums = extend; + list->capacity = newCapacity; + } + + /* 将列表转换为 Array 用于打印 */ + int *toArray(myList *list) { + return list->nums; + } + ``` + +=== "Zig" + + ```zig title="my_list.zig" + // 列表类简易实现 + 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); + @memset(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(); + // 将索引 index 以及之后的元素都向后移动一位 + 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); + @memset(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()); + @memset(nums, @as(T, 0)); + for (nums, 0..) |*num, i| { + num.* = self.get(i); + } + return nums; + } + }; + } + ``` diff --git a/chapter_backtracking/backtracking_algorithm.md b/chapter_backtracking/backtracking_algorithm.md index 3e6117197..adb297a49 100644 --- a/chapter_backtracking/backtracking_algorithm.md +++ b/chapter_backtracking/backtracking_algorithm.md @@ -14,21 +14,18 @@ comments: true 对于此题,我们前序遍历这颗树,并判断当前节点的值是否为 $7$ ,若是则将该节点的值加入到结果列表 `res` 之中。相关过程实现如图 13-1 和以下代码所示。 -=== "Java" +=== "Python" - ```java title="preorder_traversal_i_compact.java" - /* 前序遍历:例题一 */ - void preOrder(TreeNode root) { - if (root == null) { - return; - } - if (root.val == 7) { - // 记录解 - res.add(root); - } - preOrder(root.left); - preOrder(root.right); - } + ```python title="preorder_traversal_i_compact.py" + def pre_order(root: TreeNode): + """前序遍历:例题一""" + if root is None: + return + if root.val == 7: + # 记录解 + res.append(root) + pre_order(root.left) + pre_order(root.right) ``` === "C++" @@ -48,18 +45,38 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="preorder_traversal_i_compact.py" - def pre_order(root: TreeNode): - """前序遍历:例题一""" - if root is None: - return - if root.val == 7: - # 记录解 - res.append(root) - pre_order(root.left) - pre_order(root.right) + ```java title="preorder_traversal_i_compact.java" + /* 前序遍历:例题一 */ + void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 记录解 + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_i_compact.cs" + /* 前序遍历:例题一 */ + void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 记录解 + res.Add(root); + } + preOrder(root.left); + preOrder(root.right); + } ``` === "Go" @@ -79,6 +96,23 @@ comments: true } ``` +=== "Swift" + + ```swift title="preorder_traversal_i_compact.swift" + /* 前序遍历:例题一 */ + func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // 记录解 + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) + } + ``` + === "JS" ```javascript title="preorder_traversal_i_compact.js" @@ -113,52 +147,6 @@ comments: true } ``` -=== "C" - - ```c title="preorder_traversal_i_compact.c" - [class]{}-[func]{preOrder} - ``` - -=== "C#" - - ```csharp title="preorder_traversal_i_compact.cs" - /* 前序遍历:例题一 */ - void preOrder(TreeNode root) { - if (root == null) { - return; - } - if (root.val == 7) { - // 记录解 - res.Add(root); - } - preOrder(root.left); - preOrder(root.right); - } - ``` - -=== "Swift" - - ```swift title="preorder_traversal_i_compact.swift" - /* 前序遍历:例题一 */ - func preOrder(root: TreeNode?) { - guard let root = root else { - return - } - if root.val == 7 { - // 记录解 - res.append(root) - } - preOrder(root: root.left) - preOrder(root: root.right) - } - ``` - -=== "Zig" - - ```zig title="preorder_traversal_i_compact.zig" - [class]{}-[func]{preOrder} - ``` - === "Dart" ```dart title="preorder_traversal_i_compact.dart" @@ -195,6 +183,18 @@ comments: true } ``` +=== "C" + + ```c title="preorder_traversal_i_compact.c" + [class]{}-[func]{preOrder} + ``` + +=== "Zig" + + ```zig title="preorder_traversal_i_compact.zig" + [class]{}-[func]{preOrder} + ``` + ![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png)

图 13-1   在前序遍历中搜索节点

@@ -213,25 +213,22 @@ comments: true 在例题一代码的基础上,我们需要借助一个列表 `path` 记录访问过的节点路径。当访问到值为 $7$ 的节点时,则复制 `path` 并添加进结果列表 `res` 。遍历完成后,`res` 中保存的就是所有的解。 -=== "Java" +=== "Python" - ```java title="preorder_traversal_ii_compact.java" - /* 前序遍历:例题二 */ - void preOrder(TreeNode root) { - if (root == null) { - return; - } - // 尝试 - path.add(root); - if (root.val == 7) { - // 记录解 - res.add(new ArrayList<>(path)); - } - preOrder(root.left); - preOrder(root.right); - // 回退 - path.remove(path.size() - 1); - } + ```python title="preorder_traversal_ii_compact.py" + def pre_order(root: TreeNode): + """前序遍历:例题二""" + if root is None: + return + # 尝试 + path.append(root) + if root.val == 7: + # 记录解 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() ``` === "C++" @@ -255,22 +252,46 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="preorder_traversal_ii_compact.py" - def pre_order(root: TreeNode): - """前序遍历:例题二""" - if root is None: - return - # 尝试 - path.append(root) - if root.val == 7: - # 记录解 - res.append(list(path)) - pre_order(root.left) - pre_order(root.right) - # 回退 - path.pop() + ```java title="preorder_traversal_ii_compact.java" + /* 前序遍历:例题二 */ + void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 尝试 + path.add(root); + if (root.val == 7) { + // 记录解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_ii_compact.cs" + /* 前序遍历:例题二 */ + void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 尝试 + path.Add(root); + if (root.val == 7) { + // 记录解 + res.Add(new List(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } ``` === "Go" @@ -294,6 +315,27 @@ comments: true } ``` +=== "Swift" + + ```swift title="preorder_traversal_ii_compact.swift" + /* 前序遍历:例题二 */ + func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 尝试 + path.append(root) + if root.val == 7 { + // 记录解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() + } + ``` + === "JS" ```javascript title="preorder_traversal_ii_compact.js" @@ -340,81 +382,6 @@ comments: true } ``` -=== "C" - - ```c title="preorder_traversal_ii_compact.c" - /* 前序遍历:例题二 */ - void preOrder(TreeNode *root, vector *path, vector *res) { - if (root == NULL) { - return; - } - // 尝试 - vectorPushback(path, root, sizeof(TreeNode)); - if (root->val == 7) { - // 记录解 - vector *newPath = newVector(); - for (int i = 0; i < path->size; i++) { - vectorPushback(newPath, path->data[i], sizeof(int)); - } - vectorPushback(res, newPath, sizeof(vector)); - } - - preOrder(root->left, path, res); - preOrder(root->right, path, res); - - // 回退 - vectorPopback(path); - } - ``` - -=== "C#" - - ```csharp title="preorder_traversal_ii_compact.cs" - /* 前序遍历:例题二 */ - void preOrder(TreeNode root) { - if (root == null) { - return; - } - // 尝试 - path.Add(root); - if (root.val == 7) { - // 记录解 - res.Add(new List(path)); - } - preOrder(root.left); - preOrder(root.right); - // 回退 - path.RemoveAt(path.Count - 1); - } - ``` - -=== "Swift" - - ```swift title="preorder_traversal_ii_compact.swift" - /* 前序遍历:例题二 */ - func preOrder(root: TreeNode?) { - guard let root = root else { - return - } - // 尝试 - path.append(root) - if root.val == 7 { - // 记录解 - res.append(path) - } - preOrder(root: root.left) - preOrder(root: root.right) - // 回退 - path.removeLast() - } - ``` - -=== "Zig" - - ```zig title="preorder_traversal_ii_compact.zig" - [class]{}-[func]{preOrder} - ``` - === "Dart" ```dart title="preorder_traversal_ii_compact.dart" @@ -464,6 +431,39 @@ comments: true } ``` +=== "C" + + ```c title="preorder_traversal_ii_compact.c" + /* 前序遍历:例题二 */ + void preOrder(TreeNode *root, vector *path, vector *res) { + if (root == NULL) { + return; + } + // 尝试 + vectorPushback(path, root, sizeof(TreeNode)); + if (root->val == 7) { + // 记录解 + vector *newPath = newVector(); + for (int i = 0; i < path->size; i++) { + vectorPushback(newPath, path->data[i], sizeof(int)); + } + vectorPushback(res, newPath, sizeof(vector)); + } + + preOrder(root->left, path, res); + preOrder(root->right, path, res); + + // 回退 + vectorPopback(path); + } + ``` + +=== "Zig" + + ```zig title="preorder_traversal_ii_compact.zig" + [class]{}-[func]{preOrder} + ``` + 在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。 观察图 13-2 所示的过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为逆向的。 @@ -513,28 +513,25 @@ comments: true 为了满足以上约束条件,**我们需要添加剪枝操作**:在搜索过程中,若遇到值为 $3$ 的节点,则提前返回,停止继续搜索。 -=== "Java" +=== "Python" - ```java title="preorder_traversal_iii_compact.java" - /* 前序遍历:例题三 */ - void preOrder(TreeNode root) { - // 剪枝 - if (root == null || root.val == 3) { - return; - } - // 尝试 - path.add(root); - if (root.val == 7) { - // 记录解 - res.add(new ArrayList<>(path)); - path.remove(path.size() - 1); - return; - } - preOrder(root.left); - preOrder(root.right); - // 回退 - path.remove(path.size() - 1); - } + ```python title="preorder_traversal_iii_compact.py" + def pre_order(root: TreeNode): + """前序遍历:例题三""" + # 剪枝 + if root is None or root.val == 3: + return + # 尝试 + path.append(root) + if root.val == 7: + # 记录解 + res.append(list(path)) + path.pop() + return + pre_order(root.left) + pre_order(root.right) + # 回退 + path.pop() ``` === "C++" @@ -561,25 +558,52 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="preorder_traversal_iii_compact.py" - def pre_order(root: TreeNode): - """前序遍历:例题三""" - # 剪枝 - if root is None or root.val == 3: - return - # 尝试 - path.append(root) - if root.val == 7: - # 记录解 - res.append(list(path)) - path.pop() - return - pre_order(root.left) - pre_order(root.right) - # 回退 - path.pop() + ```java title="preorder_traversal_iii_compact.java" + /* 前序遍历:例题三 */ + void preOrder(TreeNode root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 尝试 + path.add(root); + if (root.val == 7) { + // 记录解 + res.add(new ArrayList<>(path)); + path.remove(path.size() - 1); + return; + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_iii_compact.cs" + /* 前序遍历:例题三 */ + void preOrder(TreeNode root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 尝试 + path.Add(root); + if (root.val == 7) { + // 记录解 + res.Add(new List(path)); + path.RemoveAt(path.Count - 1); + return; + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } ``` === "Go" @@ -606,6 +630,30 @@ comments: true } ``` +=== "Swift" + + ```swift title="preorder_traversal_iii_compact.swift" + /* 前序遍历:例题三 */ + func preOrder(root: TreeNode?) { + // 剪枝 + guard let root = root, root.val != 3 else { + return + } + // 尝试 + path.append(root) + if root.val == 7 { + // 记录解 + res.append(path) + path.removeLast() + return + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() + } + ``` + === "JS" ```javascript title="preorder_traversal_iii_compact.js" @@ -658,89 +706,6 @@ comments: true } ``` -=== "C" - - ```c title="preorder_traversal_iii_compact.c" - /* 前序遍历:例题三 */ - void preOrder(TreeNode *root, vector *path, vector *res) { - // 剪枝 - if (root == NULL || root->val == 3) { - return; - } - // 尝试 - vectorPushback(path, root, sizeof(TreeNode)); - if (root->val == 7) { - // 记录解 - vector *newPath = newVector(); - for (int i = 0; i < path->size; i++) { - vectorPushback(newPath, path->data[i], sizeof(int)); - } - vectorPushback(res, newPath, sizeof(vector)); - res->depth++; - } - - preOrder(root->left, path, res); - preOrder(root->right, path, res); - - // 回退 - vectorPopback(path); - } - ``` - -=== "C#" - - ```csharp title="preorder_traversal_iii_compact.cs" - /* 前序遍历:例题三 */ - void preOrder(TreeNode root) { - // 剪枝 - if (root == null || root.val == 3) { - return; - } - // 尝试 - path.Add(root); - if (root.val == 7) { - // 记录解 - res.Add(new List(path)); - path.RemoveAt(path.Count - 1); - return; - } - preOrder(root.left); - preOrder(root.right); - // 回退 - path.RemoveAt(path.Count - 1); - } - ``` - -=== "Swift" - - ```swift title="preorder_traversal_iii_compact.swift" - /* 前序遍历:例题三 */ - func preOrder(root: TreeNode?) { - // 剪枝 - guard let root = root, root.val != 3 else { - return - } - // 尝试 - path.append(root) - if root.val == 7 { - // 记录解 - res.append(path) - path.removeLast() - return - } - preOrder(root: root.left) - preOrder(root: root.right) - // 回退 - path.removeLast() - } - ``` - -=== "Zig" - - ```zig title="preorder_traversal_iii_compact.zig" - [class]{}-[func]{preOrder} - ``` - === "Dart" ```dart title="preorder_traversal_iii_compact.dart" @@ -795,6 +760,41 @@ comments: true } ``` +=== "C" + + ```c title="preorder_traversal_iii_compact.c" + /* 前序遍历:例题三 */ + void preOrder(TreeNode *root, vector *path, vector *res) { + // 剪枝 + if (root == NULL || root->val == 3) { + return; + } + // 尝试 + vectorPushback(path, root, sizeof(TreeNode)); + if (root->val == 7) { + // 记录解 + vector *newPath = newVector(); + for (int i = 0; i < path->size; i++) { + vectorPushback(newPath, path->data[i], sizeof(int)); + } + vectorPushback(res, newPath, sizeof(vector)); + res->depth++; + } + + preOrder(root->left, path, res); + preOrder(root->right, path, res); + + // 回退 + vectorPopback(path); + } + ``` + +=== "Zig" + + ```zig title="preorder_traversal_iii_compact.zig" + [class]{}-[func]{preOrder} + ``` + 剪枝是一个非常形象的名词。如图 13-3 所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。 ![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) @@ -807,30 +807,26 @@ comments: true 在以下框架代码中,`state` 表示问题的当前状态,`choices` 表示当前状态下可以做出的选择。 -=== "Java" +=== "Python" - ```java title="" - /* 回溯算法框架 */ - void backtrack(State state, List choices, List res) { - // 判断是否为解 - if (isSolution(state)) { - // 记录解 - recordSolution(state, res); - // 停止继续搜索 - return; - } - // 遍历所有选择 - for (Choice choice : choices) { - // 剪枝:判断选择是否合法 - if (isValid(state, choice)) { - // 尝试:做出选择,更新状态 - makeChoice(state, choice); - backtrack(state, choices, res); - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state, choice); - } - } - } + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """回溯算法框架""" + # 判断是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 停止继续搜索 + return + # 遍历所有选择 + for choice in choices: + # 剪枝:判断选择是否合法 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) ``` === "C++" @@ -859,26 +855,56 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="" - def backtrack(state: State, choices: list[choice], res: list[state]): - """回溯算法框架""" - # 判断是否为解 - if is_solution(state): - # 记录解 - record_solution(state, res) - # 停止继续搜索 - return - # 遍历所有选择 - for choice in choices: - # 剪枝:判断选择是否合法 - if is_valid(state, choice): - # 尝试:做出选择,更新状态 - make_choice(state, choice) - backtrack(state, choices, res) - # 回退:撤销选择,恢复到之前的状态 - undo_choice(state, choice) + ```java title="" + /* 回溯算法框架 */ + void backtrack(State state, List choices, List res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 停止继续搜索 + return; + } + // 遍历所有选择 + for (Choice choice : choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + /* 回溯算法框架 */ + void backtrack(State state, List choices, List res) { + // 判断是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + // 停止继续搜索 + return; + } + // 遍历所有选择 + foreach (Choice choice in choices) { + // 剪枝:判断选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } ``` === "Go" @@ -907,6 +933,32 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 回溯算法框架 */ + func backtrack(state: inout State, choices: [Choice], res: inout [State]) { + // 判断是否为解 + if isSolution(state: state) { + // 记录解 + recordSolution(state: state, res: &res) + // 停止继续搜索 + return + } + // 遍历所有选择 + for choice in choices { + // 剪枝:判断选择是否合法 + if isValid(state: state, choice: choice) { + // 尝试:做出选择,更新状态 + makeChoice(state: &state, choice: choice) + backtrack(state: &state, choices: choices, res: &res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state: &state, choice: choice) + } + } + } + ``` + === "JS" ```javascript title="" @@ -959,90 +1011,6 @@ comments: true } ``` -=== "C" - - ```c title="" - /* 回溯算法框架 */ - void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { - // 判断是否为解 - if (isSolution(state)) { - // 记录解 - recordSolution(state, res, numRes); - // 停止继续搜索 - return; - } - // 遍历所有选择 - for (int i = 0; i < numChoices; i++) { - // 剪枝:判断选择是否合法 - if (isValid(state, &choices[i])) { - // 尝试:做出选择,更新状态 - makeChoice(state, &choices[i]); - backtrack(state, choices, numChoices, res, numRes); - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state, &choices[i]); - } - } - } - ``` - -=== "C#" - - ```csharp title="" - /* 回溯算法框架 */ - void backtrack(State state, List choices, List res) { - // 判断是否为解 - if (isSolution(state)) { - // 记录解 - recordSolution(state, res); - // 停止继续搜索 - return; - } - // 遍历所有选择 - foreach (Choice choice in choices) { - // 剪枝:判断选择是否合法 - if (isValid(state, choice)) { - // 尝试:做出选择,更新状态 - makeChoice(state, choice); - backtrack(state, choices, res); - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state, choice); - } - } - } - ``` - -=== "Swift" - - ```swift title="" - /* 回溯算法框架 */ - func backtrack(state: inout State, choices: [Choice], res: inout [State]) { - // 判断是否为解 - if isSolution(state: state) { - // 记录解 - recordSolution(state: state, res: &res) - // 停止继续搜索 - return - } - // 遍历所有选择 - for choice in choices { - // 剪枝:判断选择是否合法 - if isValid(state: state, choice: choice) { - // 尝试:做出选择,更新状态 - makeChoice(state: &state, choice: choice) - backtrack(state: &state, choices: choices, res: &res) - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state: &state, choice: choice) - } - } - } - ``` - -=== "Zig" - - ```zig title="" - - ``` - === "Dart" ```dart title="" @@ -1075,58 +1043,83 @@ comments: true ``` -接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表。 +=== "C" -=== "Java" - - ```java title="preorder_traversal_iii_template.java" - /* 判断当前状态是否为解 */ - boolean isSolution(List state) { - return !state.isEmpty() && state.get(state.size() - 1).val == 7; - } - - /* 记录解 */ - void recordSolution(List state, List> res) { - res.add(new ArrayList<>(state)); - } - - /* 判断在当前状态下,该选择是否合法 */ - boolean isValid(List state, TreeNode choice) { - return choice != null && choice.val != 3; - } - - /* 更新状态 */ - void makeChoice(List state, TreeNode choice) { - state.add(choice); - } - - /* 恢复状态 */ - void undoChoice(List state, TreeNode choice) { - state.remove(state.size() - 1); - } - - /* 回溯算法:例题三 */ - void backtrack(List state, List choices, List> res) { - // 检查是否为解 + ```c title="" + /* 回溯算法框架 */ + void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { + // 判断是否为解 if (isSolution(state)) { // 记录解 - recordSolution(state, res); + recordSolution(state, res, numRes); + // 停止继续搜索 + return; } // 遍历所有选择 - for (TreeNode choice : choices) { - // 剪枝:检查选择是否合法 - if (isValid(state, choice)) { + for (int i = 0; i < numChoices; i++) { + // 剪枝:判断选择是否合法 + if (isValid(state, &choices[i])) { // 尝试:做出选择,更新状态 - makeChoice(state, choice); - // 进行下一轮选择 - backtrack(state, Arrays.asList(choice.left, choice.right), res); + makeChoice(state, &choices[i]); + backtrack(state, choices, numChoices, res, numRes); // 回退:撤销选择,恢复到之前的状态 - undoChoice(state, choice); + undoChoice(state, &choices[i]); } } } ``` +=== "Zig" + + ```zig title="" + + ``` + +接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表。 + +=== "Python" + + ```python title="preorder_traversal_iii_template.py" + def is_solution(state: list[TreeNode]) -> bool: + """判断当前状态是否为解""" + return state and state[-1].val == 7 + + def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """记录解""" + res.append(list(state)) + + def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """判断在当前状态下,该选择是否合法""" + return choice is not None and choice.val != 3 + + def make_choice(state: list[TreeNode], choice: TreeNode): + """更新状态""" + state.append(choice) + + def undo_choice(state: list[TreeNode], choice: TreeNode): + """恢复状态""" + state.pop() + + def backtrack( + state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] + ): + """回溯算法:例题三""" + # 检查是否为解 + if is_solution(state): + # 记录解 + record_solution(state, res) + # 遍历所有选择 + for choice in choices: + # 剪枝:检查选择是否合法 + if is_valid(state, choice): + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + ``` + === "C++" ```cpp title="preorder_traversal_iii_template.cpp" @@ -1178,47 +1171,104 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="preorder_traversal_iii_template.py" - def is_solution(state: list[TreeNode]) -> bool: - """判断当前状态是否为解""" - return state and state[-1].val == 7 + ```java title="preorder_traversal_iii_template.java" + /* 判断当前状态是否为解 */ + boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } - def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): - """记录解""" - res.append(list(state)) + /* 记录解 */ + void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } - def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: - """判断在当前状态下,该选择是否合法""" - return choice is not None and choice.val != 3 + /* 判断在当前状态下,该选择是否合法 */ + boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } - def make_choice(state: list[TreeNode], choice: TreeNode): - """更新状态""" - state.append(choice) + /* 更新状态 */ + void makeChoice(List state, TreeNode choice) { + state.add(choice); + } - def undo_choice(state: list[TreeNode], choice: TreeNode): - """恢复状态""" - state.pop() + /* 恢复状态 */ + void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } - def backtrack( - state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] - ): - """回溯算法:例题三""" - # 检查是否为解 - if is_solution(state): - # 记录解 - record_solution(state, res) - # 遍历所有选择 - for choice in choices: - # 剪枝:检查选择是否合法 - if is_valid(state, choice): - # 尝试:做出选择,更新状态 - make_choice(state, choice) - # 进行下一轮选择 - backtrack(state, [choice.left, choice.right], res) - # 回退:撤销选择,恢复到之前的状态 - undo_choice(state, choice) + /* 回溯算法:例题三 */ + void backtrack(List state, List choices, List> res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (TreeNode choice : choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + backtrack(state, Arrays.asList(choice.left, choice.right), res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_iii_template.cs" + /* 判断当前状态是否为解 */ + bool isSolution(List state) { + return state.Count != 0 && state[^1].val == 7; + } + + /* 记录解 */ + void recordSolution(List state, List> res) { + res.Add(new List(state)); + } + + /* 判断在当前状态下,该选择是否合法 */ + bool isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新状态 */ + void makeChoice(List state, TreeNode choice) { + state.Add(choice); + } + + /* 恢复状态 */ + void undoChoice(List state, TreeNode choice) { + state.RemoveAt(state.Count - 1); + } + + /* 回溯算法:例题三 */ + void backtrack(List state, List choices, List> res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + foreach (TreeNode choice in choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + backtrack(state, new List { choice.left, choice.right }, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } ``` === "Go" @@ -1273,6 +1323,55 @@ comments: true } ``` +=== "Swift" + + ```swift title="preorder_traversal_iii_template.swift" + /* 判断当前状态是否为解 */ + func isSolution(state: [TreeNode]) -> Bool { + !state.isEmpty && state.last!.val == 7 + } + + /* 记录解 */ + func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { + res.append(state) + } + + /* 判断在当前状态下,该选择是否合法 */ + func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { + choice != nil && choice!.val != 3 + } + + /* 更新状态 */ + func makeChoice(state: inout [TreeNode], choice: TreeNode) { + state.append(choice) + } + + /* 恢复状态 */ + func undoChoice(state: inout [TreeNode], choice: TreeNode) { + state.removeLast() + } + + /* 回溯算法:例题三 */ + func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { + // 检查是否为解 + if isSolution(state: state) { + recordSolution(state: state, res: &res) + } + // 遍历所有选择 + for choice in choices { + // 剪枝:检查选择是否合法 + if isValid(state: state, choice: choice) { + // 尝试:做出选择,更新状态 + makeChoice(state: &state, choice: choice) + // 进行下一轮选择 + backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state: &state, choice: choice) + } + } + } + ``` + === "JS" ```javascript title="preorder_traversal_iii_template.js" @@ -1377,180 +1476,6 @@ comments: true } ``` -=== "C" - - ```c title="preorder_traversal_iii_template.c" - /* 判断当前状态是否为解 */ - bool isSolution(vector *state) { - return state->size != 0 && ((TreeNode *)(state->data[state->size - 1]))->val == 7; - } - - /* 记录解 */ - void recordSolution(vector *state, vector *res) { - vector *newPath = newVector(); - for (int i = 0; i < state->size; i++) { - vectorPushback(newPath, state->data[i], sizeof(int)); - } - vectorPushback(res, newPath, sizeof(vector)); - } - - /* 判断在当前状态下,该选择是否合法 */ - bool isValid(vector *state, TreeNode *choice) { - return choice != NULL && choice->val != 3; - } - - /* 更新状态 */ - void makeChoice(vector *state, TreeNode *choice) { - vectorPushback(state, choice, sizeof(TreeNode)); - } - - /* 恢复状态 */ - void undoChoice(vector *state, TreeNode *choice) { - vectorPopback(state); - } - - /* 前序遍历:例题三 */ - void backtrack(vector *state, vector *choices, vector *res) { - // 检查是否为解 - if (isSolution(state)) { - // 记录解 - recordSolution(state, res); - return; - } - // 遍历所有选择 - for (int i = 0; i < choices->size; i++) { - TreeNode *choice = choices->data[i]; - // 剪枝:检查选择是否合法 - if (isValid(state, choice)) { - // 尝试:做出选择,更新状态 - makeChoice(state, choice); - // 进行下一轮选择 - vector *nextChoices = newVector(); - vectorPushback(nextChoices, choice->left, sizeof(TreeNode)); - vectorPushback(nextChoices, choice->right, sizeof(TreeNode)); - backtrack(state, nextChoices, res); - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state, choice); - } - } - } - ``` - -=== "C#" - - ```csharp title="preorder_traversal_iii_template.cs" - /* 判断当前状态是否为解 */ - bool isSolution(List state) { - return state.Count != 0 && state[^1].val == 7; - } - - /* 记录解 */ - void recordSolution(List state, List> res) { - res.Add(new List(state)); - } - - /* 判断在当前状态下,该选择是否合法 */ - bool isValid(List state, TreeNode choice) { - return choice != null && choice.val != 3; - } - - /* 更新状态 */ - void makeChoice(List state, TreeNode choice) { - state.Add(choice); - } - - /* 恢复状态 */ - void undoChoice(List state, TreeNode choice) { - state.RemoveAt(state.Count - 1); - } - - /* 回溯算法:例题三 */ - void backtrack(List state, List choices, List> res) { - // 检查是否为解 - if (isSolution(state)) { - // 记录解 - recordSolution(state, res); - } - // 遍历所有选择 - foreach (TreeNode choice in choices) { - // 剪枝:检查选择是否合法 - if (isValid(state, choice)) { - // 尝试:做出选择,更新状态 - makeChoice(state, choice); - // 进行下一轮选择 - backtrack(state, new List { choice.left, choice.right }, res); - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state, choice); - } - } - } - ``` - -=== "Swift" - - ```swift title="preorder_traversal_iii_template.swift" - /* 判断当前状态是否为解 */ - func isSolution(state: [TreeNode]) -> Bool { - !state.isEmpty && state.last!.val == 7 - } - - /* 记录解 */ - func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { - res.append(state) - } - - /* 判断在当前状态下,该选择是否合法 */ - func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { - choice != nil && choice!.val != 3 - } - - /* 更新状态 */ - func makeChoice(state: inout [TreeNode], choice: TreeNode) { - state.append(choice) - } - - /* 恢复状态 */ - func undoChoice(state: inout [TreeNode], choice: TreeNode) { - state.removeLast() - } - - /* 回溯算法:例题三 */ - func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { - // 检查是否为解 - if isSolution(state: state) { - recordSolution(state: state, res: &res) - } - // 遍历所有选择 - for choice in choices { - // 剪枝:检查选择是否合法 - if isValid(state: state, choice: choice) { - // 尝试:做出选择,更新状态 - makeChoice(state: &state, choice: choice) - // 进行下一轮选择 - backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) - // 回退:撤销选择,恢复到之前的状态 - undoChoice(state: &state, choice: choice) - } - } - } - ``` - -=== "Zig" - - ```zig title="preorder_traversal_iii_template.zig" - [class]{}-[func]{isSolution} - - [class]{}-[func]{recordSolution} - - [class]{}-[func]{isValid} - - [class]{}-[func]{makeChoice} - - [class]{}-[func]{undoChoice} - - [class]{}-[func]{backtrack} - ``` - === "Dart" ```dart title="preorder_traversal_iii_template.dart" @@ -1655,6 +1580,81 @@ comments: true } ``` +=== "C" + + ```c title="preorder_traversal_iii_template.c" + /* 判断当前状态是否为解 */ + bool isSolution(vector *state) { + return state->size != 0 && ((TreeNode *)(state->data[state->size - 1]))->val == 7; + } + + /* 记录解 */ + void recordSolution(vector *state, vector *res) { + vector *newPath = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(newPath, state->data[i], sizeof(int)); + } + vectorPushback(res, newPath, sizeof(vector)); + } + + /* 判断在当前状态下,该选择是否合法 */ + bool isValid(vector *state, TreeNode *choice) { + return choice != NULL && choice->val != 3; + } + + /* 更新状态 */ + void makeChoice(vector *state, TreeNode *choice) { + vectorPushback(state, choice, sizeof(TreeNode)); + } + + /* 恢复状态 */ + void undoChoice(vector *state, TreeNode *choice) { + vectorPopback(state); + } + + /* 前序遍历:例题三 */ + void backtrack(vector *state, vector *choices, vector *res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices->size; i++) { + TreeNode *choice = choices->data[i]; + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + vector *nextChoices = newVector(); + vectorPushback(nextChoices, choice->left, sizeof(TreeNode)); + vectorPushback(nextChoices, choice->right, sizeof(TreeNode)); + backtrack(state, nextChoices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } + ``` + +=== "Zig" + + ```zig title="preorder_traversal_iii_template.zig" + [class]{}-[func]{isSolution} + + [class]{}-[func]{recordSolution} + + [class]{}-[func]{isValid} + + [class]{}-[func]{makeChoice} + + [class]{}-[func]{undoChoice} + + [class]{}-[func]{backtrack} + ``` + 根据题意,我们在找到值为 7 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。图 13-4 对比了保留或删除 `return` 语句的搜索过程。 ![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) diff --git a/chapter_backtracking/n_queens_problem.md b/chapter_backtracking/n_queens_problem.md index 2acba566a..84897e192 100644 --- a/chapter_backtracking/n_queens_problem.md +++ b/chapter_backtracking/n_queens_problem.md @@ -52,6 +52,97 @@ comments: true 请注意,$n$ 维方阵中 $row - col$ 的范围是 $[-n + 1, n - 1]$ ,$row + col$ 的范围是 $[0, 2n - 2]$ ,所以主对角线和次对角线的数量都为 $2n - 1$ ,即数组 `diag1` 和 `diag2` 的长度都为 $2n - 1$ 。 +=== "Python" + + ```python title="n_queens.py" + def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], + ): + """回溯算法:N 皇后""" + # 当放置完所有行时,记录解 + if row == n: + res.append([list(row) for row in state]) + return + # 遍历所有列 + for col in range(n): + # 计算该格子对应的主对角线和副对角线 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 + if not cols[col] and not diags1[diag1] and not diags2[diag2]: + # 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = True + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = False + + def n_queens(n: int) -> list[list[list[str]]]: + """求解 N 皇后""" + # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # 记录列是否有皇后 + diags1 = [False] * (2 * n - 1) # 记录主对角线是否有皇后 + diags2 = [False] * (2 * n - 1) # 记录副对角线是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + ``` + +=== "C++" + + ```cpp title="n_queens.cpp" + /* 回溯算法:N 皇后 */ + void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // 当放置完所有行时,记录解 + if (row == n) { + res.push_back(state); + return; + } + // 遍历所有列 + for (int col = 0; col < n; col++) { + // 计算该格子对应的主对角线和副对角线 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 N 皇后 */ + vector>> nQueens(int n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + vector> state(n, vector(n, "#")); + vector cols(n, false); // 记录列是否有皇后 + vector diags1(2 * n - 1, false); // 记录主对角线是否有皇后 + vector diags2(2 * n - 1, false); // 记录副对角线是否有皇后 + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + ``` + === "Java" ```java title="n_queens.java" @@ -108,15 +199,19 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="n_queens.cpp" + ```csharp title="n_queens.cs" /* 回溯算法:N 皇后 */ - void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, - vector &diags1, vector &diags2) { + void backtrack(int row, int n, List> state, List>> res, + bool[] cols, bool[] diags1, bool[] diags2) { // 当放置完所有行时,记录解 if (row == n) { - res.push_back(state); + List> copyState = new List>(); + foreach (List sRow in state) { + copyState.Add(new List(sRow)); + } + res.Add(copyState); return; } // 遍历所有列 @@ -139,13 +234,20 @@ comments: true } /* 求解 N 皇后 */ - vector>> nQueens(int n) { + List>> nQueens(int n) { // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 - vector> state(n, vector(n, "#")); - vector cols(n, false); // 记录列是否有皇后 - vector diags1(2 * n - 1, false); // 记录主对角线是否有皇后 - vector diags2(2 * n - 1, false); // 记录副对角线是否有皇后 - vector>> res; + List> state = new List>(); + for (int i = 0; i < n; i++) { + List row = new List(); + for (int j = 0; j < n; j++) { + row.Add("#"); + } + state.Add(row); + } + bool[] cols = new bool[n]; // 记录列是否有皇后 + bool[] diags1 = new bool[2 * n - 1]; // 记录主对角线是否有皇后 + bool[] diags2 = new bool[2 * n - 1]; // 记录副对角线是否有皇后 + List>> res = new List>>(); backtrack(0, n, state, res, cols, diags1, diags2); @@ -153,52 +255,6 @@ comments: true } ``` -=== "Python" - - ```python title="n_queens.py" - def backtrack( - row: int, - n: int, - state: list[list[str]], - res: list[list[list[str]]], - cols: list[bool], - diags1: list[bool], - diags2: list[bool], - ): - """回溯算法:N 皇后""" - # 当放置完所有行时,记录解 - if row == n: - res.append([list(row) for row in state]) - return - # 遍历所有列 - for col in range(n): - # 计算该格子对应的主对角线和副对角线 - diag1 = row - col + n - 1 - diag2 = row + col - # 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 - if not cols[col] and not diags1[diag1] and not diags2[diag2]: - # 尝试:将皇后放置在该格子 - state[row][col] = "Q" - cols[col] = diags1[diag1] = diags2[diag2] = True - # 放置下一行 - backtrack(row + 1, n, state, res, cols, diags1, diags2) - # 回退:将该格子恢复为空位 - state[row][col] = "#" - cols[col] = diags1[diag1] = diags2[diag2] = False - - def n_queens(n: int) -> list[list[list[str]]]: - """求解 N 皇后""" - # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 - state = [["#" for _ in range(n)] for _ in range(n)] - cols = [False] * n # 记录列是否有皇后 - diags1 = [False] * (2 * n - 1) # 记录主对角线是否有皇后 - diags2 = [False] * (2 * n - 1) # 记录副对角线是否有皇后 - res = [] - backtrack(0, n, state, res, cols, diags1, diags2) - - return res - ``` - === "Go" ```go title="n_queens.go" @@ -284,6 +340,54 @@ comments: true } ``` +=== "Swift" + + ```swift title="n_queens.swift" + /* 回溯算法:N 皇后 */ + func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { + // 当放置完所有行时,记录解 + if row == n { + res.append(state) + return + } + // 遍历所有列 + for col in 0 ..< n { + // 计算该格子对应的主对角线和副对角线 + let diag1 = row - col + n - 1 + let diag2 = row + col + // 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + // 放置下一行 + backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + // 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + } + } + } + + /* 求解 N 皇后 */ + func nQueens(n: Int) -> [[[String]]] { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + var state = Array(repeating: Array(repeating: "#", count: n), count: n) + var cols = Array(repeating: false, count: n) // 记录列是否有皇后 + var diags1 = Array(repeating: false, count: 2 * n - 1) // 记录主对角线是否有皇后 + var diags2 = Array(repeating: false, count: 2 * n - 1) // 记录副对角线是否有皇后 + var res: [[[String]]] = [] + + backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + + return res + } + ``` + === "JS" ```javascript title="n_queens.js" @@ -378,126 +482,6 @@ comments: true } ``` -=== "C" - - ```c title="n_queens.c" - [class]{}-[func]{backtrack} - - [class]{}-[func]{nQueens} - ``` - -=== "C#" - - ```csharp title="n_queens.cs" - /* 回溯算法:N 皇后 */ - void backtrack(int row, int n, List> state, List>> res, - bool[] cols, bool[] diags1, bool[] diags2) { - // 当放置完所有行时,记录解 - if (row == n) { - List> copyState = new List>(); - foreach (List sRow in state) { - copyState.Add(new List(sRow)); - } - res.Add(copyState); - return; - } - // 遍历所有列 - for (int col = 0; col < n; col++) { - // 计算该格子对应的主对角线和副对角线 - int diag1 = row - col + n - 1; - int diag2 = row + col; - // 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 - if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { - // 尝试:将皇后放置在该格子 - state[row][col] = "Q"; - cols[col] = diags1[diag1] = diags2[diag2] = true; - // 放置下一行 - backtrack(row + 1, n, state, res, cols, diags1, diags2); - // 回退:将该格子恢复为空位 - state[row][col] = "#"; - cols[col] = diags1[diag1] = diags2[diag2] = false; - } - } - } - - /* 求解 N 皇后 */ - List>> nQueens(int n) { - // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 - List> state = new List>(); - for (int i = 0; i < n; i++) { - List row = new List(); - for (int j = 0; j < n; j++) { - row.Add("#"); - } - state.Add(row); - } - bool[] cols = new bool[n]; // 记录列是否有皇后 - bool[] diags1 = new bool[2 * n - 1]; // 记录主对角线是否有皇后 - bool[] diags2 = new bool[2 * n - 1]; // 记录副对角线是否有皇后 - List>> res = new List>>(); - - backtrack(0, n, state, res, cols, diags1, diags2); - - return res; - } - ``` - -=== "Swift" - - ```swift title="n_queens.swift" - /* 回溯算法:N 皇后 */ - func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { - // 当放置完所有行时,记录解 - if row == n { - res.append(state) - return - } - // 遍历所有列 - for col in 0 ..< n { - // 计算该格子对应的主对角线和副对角线 - let diag1 = row - col + n - 1 - let diag2 = row + col - // 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 - if !cols[col] && !diags1[diag1] && !diags2[diag2] { - // 尝试:将皇后放置在该格子 - state[row][col] = "Q" - cols[col] = true - diags1[diag1] = true - diags2[diag2] = true - // 放置下一行 - backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) - // 回退:将该格子恢复为空位 - state[row][col] = "#" - cols[col] = false - diags1[diag1] = false - diags2[diag2] = false - } - } - } - - /* 求解 N 皇后 */ - func nQueens(n: Int) -> [[[String]]] { - // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 - var state = Array(repeating: Array(repeating: "#", count: n), count: n) - var cols = Array(repeating: false, count: n) // 记录列是否有皇后 - var diags1 = Array(repeating: false, count: 2 * n - 1) // 记录主对角线是否有皇后 - var diags2 = Array(repeating: false, count: 2 * n - 1) // 记录副对角线是否有皇后 - var res: [[[String]]] = [] - - backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) - - return res - } - ``` - -=== "Zig" - - ```zig title="n_queens.zig" - [class]{}-[func]{backtrack} - - [class]{}-[func]{nQueens} - ``` - === "Dart" ```dart title="n_queens.dart" @@ -614,6 +598,22 @@ comments: true } ``` +=== "C" + + ```c title="n_queens.c" + [class]{}-[func]{backtrack} + + [class]{}-[func]{nQueens} + ``` + +=== "Zig" + + ```zig title="n_queens.zig" + [class]{}-[func]{backtrack} + + [class]{}-[func]{nQueens} + ``` + 逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n$、$n-1$、$\dots$、$2$、$1$ 个选择,**因此时间复杂度为 $O(n!)$** 。实际上,根据对角线约束的剪枝也能够大幅地缩小搜索空间,因而搜索效率往往优于以上时间复杂度。 数组 `state` 使用 $O(n^2)$ 空间,数组 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空间。最大递归深度为 $n$ ,使用 $O(n)$ 栈帧空间。因此,**空间复杂度为 $O(n^2)$** 。 diff --git a/chapter_backtracking/permutations_problem.md b/chapter_backtracking/permutations_problem.md index d6d73abff..ce5898ee3 100644 --- a/chapter_backtracking/permutations_problem.md +++ b/chapter_backtracking/permutations_problem.md @@ -55,39 +55,35 @@ comments: true 想清楚以上信息之后,我们就可以在框架代码中做“完形填空”了。为了缩短代码行数,我们不单独实现框架代码中的各个函数,而是将他们展开在 `backtrack()` 函数中。 -=== "Java" +=== "Python" - ```java title="permutations_i.java" - /* 回溯算法:全排列 I */ - void backtrack(List state, int[] choices, boolean[] selected, List> res) { - // 当状态长度等于元素数量时,记录解 - if (state.size() == choices.length) { - res.add(new ArrayList(state)); - return; - } - // 遍历所有选择 - for (int i = 0; i < choices.length; i++) { - int choice = choices[i]; - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if (!selected[i]) { - // 尝试:做出选择,更新状态 - selected[i] = true; - state.add(choice); - // 进行下一轮选择 - backtrack(state, choices, selected, res); - // 回退:撤销选择,恢复到之前的状态 - selected[i] = false; - state.remove(state.size() - 1); - } - } - } + ```python title="permutations_i.py" + def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] + ): + """回溯算法:全排列 I""" + # 当状态长度等于元素数量时,记录解 + if len(state) == len(choices): + res.append(list(state)) + return + # 遍历所有选择 + for i, choice in enumerate(choices): + # 剪枝:不允许重复选择元素 + if not selected[i]: + # 尝试:做出选择,更新状态 + selected[i] = True + state.append(choice) + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = False + state.pop() - /* 全排列 I */ - List> permutationsI(int[] nums) { - List> res = new ArrayList>(); - backtrack(new ArrayList(), nums, new boolean[nums.length], res); - return res; - } + def permutations_i(nums: list[int]) -> list[list[int]]: + """全排列 I""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res ``` === "C++" @@ -127,35 +123,74 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="permutations_i.py" - def backtrack( - state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] - ): - """回溯算法:全排列 I""" - # 当状态长度等于元素数量时,记录解 - if len(state) == len(choices): - res.append(list(state)) - return - # 遍历所有选择 - for i, choice in enumerate(choices): - # 剪枝:不允许重复选择元素 - if not selected[i]: - # 尝试:做出选择,更新状态 - selected[i] = True - state.append(choice) - # 进行下一轮选择 - backtrack(state, choices, selected, res) - # 回退:撤销选择,恢复到之前的状态 - selected[i] = False - state.pop() + ```java title="permutations_i.java" + /* 回溯算法:全排列 I */ + void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } - def permutations_i(nums: list[int]) -> list[list[int]]: - """全排列 I""" - res = [] - backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) - return res + /* 全排列 I */ + List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + ``` + +=== "C#" + + ```csharp title="permutations_i.cs" + /* 回溯算法:全排列 I */ + void backtrack(List state, int[] choices, bool[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.Add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 I */ + List> permutationsI(int[] nums) { + List> res = new List>(); + backtrack(new List(), nums, new bool[nums.Length], res); + return res; + } ``` === "Go" @@ -195,6 +230,42 @@ comments: true } ``` +=== "Swift" + + ```swift title="permutations_i.swift" + /* 回溯算法:全排列 I */ + func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 当状态长度等于元素数量时,记录解 + if state.count == choices.count { + res.append(state) + return + } + // 遍历所有选择 + for (i, choice) in choices.enumerated() { + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if !selected[i] { + // 尝试:做出选择,更新状态 + selected[i] = true + state.append(choice) + // 进行下一轮选择 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.removeLast() + } + } + } + + /* 全排列 I */ + func permutationsI(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res + } + ``` + === "JS" ```javascript title="permutations_i.js" @@ -268,136 +339,6 @@ comments: true } ``` -=== "C" - - ```c title="permutations_i.c" - /* 回溯算法:全排列 I */ - void backtrack(vector *state, vector *choices, vector *selected, vector *res) { - // 当状态长度等于元素数量时,记录解 - if (state->size == choices->size) { - vector *newState = newVector(); - for (int i = 0; i < state->size; i++) { - vectorPushback(newState, state->data[i], sizeof(int)); - } - vectorPushback(res, newState, sizeof(vector)); - return; - } - // 遍历所有选择 - for (int i = 0; i < choices->size; i++) { - int *choice = malloc(sizeof(int)); - *choice = *((int *)(choices->data[i])); - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - bool select = *((bool *)(selected->data[i])); - if (!select) { - // 尝试:做出选择,更新状态 - *((bool *)selected->data[i]) = true; - vectorPushback(state, choice, sizeof(int)); - // 进行下一轮选择 - backtrack(state, choices, selected, res); - // 回退:撤销选择,恢复到之前的状态 - *((bool *)selected->data[i]) = false; - vectorPopback(state); - } - } - } - - /* 全排列 I */ - vector *permutationsI(vector *nums) { - vector *iState = newVector(); - - int select[3] = {false, false, false}; - vector *bSelected = newVector(); - for (int i = 0; i < nums->size; i++) { - vectorPushback(bSelected, &select[i], sizeof(int)); - } - - vector *res = newVector(); - - // 前序遍历 - backtrack(iState, nums, bSelected, res); - return res; - } - ``` - -=== "C#" - - ```csharp title="permutations_i.cs" - /* 回溯算法:全排列 I */ - void backtrack(List state, int[] choices, bool[] selected, List> res) { - // 当状态长度等于元素数量时,记录解 - if (state.Count == choices.Length) { - res.Add(new List(state)); - return; - } - // 遍历所有选择 - for (int i = 0; i < choices.Length; i++) { - int choice = choices[i]; - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if (!selected[i]) { - // 尝试:做出选择,更新状态 - selected[i] = true; - state.Add(choice); - // 进行下一轮选择 - backtrack(state, choices, selected, res); - // 回退:撤销选择,恢复到之前的状态 - selected[i] = false; - state.RemoveAt(state.Count - 1); - } - } - } - - /* 全排列 I */ - List> permutationsI(int[] nums) { - List> res = new List>(); - backtrack(new List(), nums, new bool[nums.Length], res); - return res; - } - ``` - -=== "Swift" - - ```swift title="permutations_i.swift" - /* 回溯算法:全排列 I */ - func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { - // 当状态长度等于元素数量时,记录解 - if state.count == choices.count { - res.append(state) - return - } - // 遍历所有选择 - for (i, choice) in choices.enumerated() { - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if !selected[i] { - // 尝试:做出选择,更新状态 - selected[i] = true - state.append(choice) - // 进行下一轮选择 - backtrack(state: &state, choices: choices, selected: &selected, res: &res) - // 回退:撤销选择,恢复到之前的状态 - selected[i] = false - state.removeLast() - } - } - } - - /* 全排列 I */ - func permutationsI(nums: [Int]) -> [[Int]] { - var state: [Int] = [] - var selected = Array(repeating: false, count: nums.count) - var res: [[Int]] = [] - backtrack(state: &state, choices: nums, selected: &selected, res: &res) - return res - } - ``` - -=== "Zig" - - ```zig title="permutations_i.zig" - [class]{}-[func]{backtrack} - - [class]{}-[func]{permutationsI} - ``` - === "Dart" ```dart title="permutations_i.dart" @@ -473,6 +414,65 @@ comments: true } ``` +=== "C" + + ```c title="permutations_i.c" + /* 回溯算法:全排列 I */ + void backtrack(vector *state, vector *choices, vector *selected, vector *res) { + // 当状态长度等于元素数量时,记录解 + if (state->size == choices->size) { + vector *newState = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(newState, state->data[i], sizeof(int)); + } + vectorPushback(res, newState, sizeof(vector)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices->size; i++) { + int *choice = malloc(sizeof(int)); + *choice = *((int *)(choices->data[i])); + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + bool select = *((bool *)(selected->data[i])); + if (!select) { + // 尝试:做出选择,更新状态 + *((bool *)selected->data[i]) = true; + vectorPushback(state, choice, sizeof(int)); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + *((bool *)selected->data[i]) = false; + vectorPopback(state); + } + } + } + + /* 全排列 I */ + vector *permutationsI(vector *nums) { + vector *iState = newVector(); + + int select[3] = {false, false, false}; + vector *bSelected = newVector(); + for (int i = 0; i < nums->size; i++) { + vectorPushback(bSelected, &select[i], sizeof(int)); + } + + vector *res = newVector(); + + // 前序遍历 + backtrack(iState, nums, bSelected, res); + return res; + } + ``` + +=== "Zig" + + ```zig title="permutations_i.zig" + [class]{}-[func]{backtrack} + + [class]{}-[func]{permutationsI} + ``` + ## 13.2.2   考虑相等元素的情况 !!! question @@ -505,41 +505,37 @@ comments: true 在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希表 `duplicated` ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝。 -=== "Java" +=== "Python" - ```java title="permutations_ii.java" - /* 回溯算法:全排列 II */ - void backtrack(List state, int[] choices, boolean[] selected, List> res) { - // 当状态长度等于元素数量时,记录解 - if (state.size() == choices.length) { - res.add(new ArrayList(state)); - return; - } - // 遍历所有选择 - Set duplicated = new HashSet(); - for (int i = 0; i < choices.length; i++) { - int choice = choices[i]; - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if (!selected[i] && !duplicated.contains(choice)) { - // 尝试:做出选择,更新状态 - duplicated.add(choice); // 记录选择过的元素值 - selected[i] = true; - state.add(choice); - // 进行下一轮选择 - backtrack(state, choices, selected, res); - // 回退:撤销选择,恢复到之前的状态 - selected[i] = false; - state.remove(state.size() - 1); - } - } - } + ```python title="permutations_ii.py" + def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] + ): + """回溯算法:全排列 II""" + # 当状态长度等于元素数量时,记录解 + if len(state) == len(choices): + res.append(list(state)) + return + # 遍历所有选择 + duplicated = set[int]() + for i, choice in enumerate(choices): + # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if not selected[i] and choice not in duplicated: + # 尝试:做出选择,更新状态 + duplicated.add(choice) # 记录选择过的元素值 + selected[i] = True + state.append(choice) + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = False + state.pop() - /* 全排列 II */ - List> permutationsII(int[] nums) { - List> res = new ArrayList>(); - backtrack(new ArrayList(), nums, new boolean[nums.length], res); - return res; - } + def permutations_ii(nums: list[int]) -> list[list[int]]: + """全排列 II""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res ``` === "C++" @@ -581,37 +577,78 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="permutations_ii.py" - def backtrack( - state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] - ): - """回溯算法:全排列 II""" - # 当状态长度等于元素数量时,记录解 - if len(state) == len(choices): - res.append(list(state)) - return - # 遍历所有选择 - duplicated = set[int]() - for i, choice in enumerate(choices): - # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if not selected[i] and choice not in duplicated: - # 尝试:做出选择,更新状态 - duplicated.add(choice) # 记录选择过的元素值 - selected[i] = True - state.append(choice) - # 进行下一轮选择 - backtrack(state, choices, selected, res) - # 回退:撤销选择,恢复到之前的状态 - selected[i] = False - state.pop() + ```java title="permutations_ii.java" + /* 回溯算法:全排列 II */ + void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 遍历所有选择 + Set duplicated = new HashSet(); + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 尝试:做出选择,更新状态 + duplicated.add(choice); // 记录选择过的元素值 + selected[i] = true; + state.add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } - def permutations_ii(nums: list[int]) -> list[list[int]]: - """全排列 II""" - res = [] - backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) - return res + /* 全排列 II */ + List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + ``` + +=== "C#" + + ```csharp title="permutations_ii.cs" + /* 回溯算法:全排列 II */ + void backtrack(List state, int[] choices, bool[] selected, List> res) { + // 当状态长度等于元素数量时,记录解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + ISet duplicated = new HashSet(); + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && !duplicated.Contains(choice)) { + // 尝试:做出选择,更新状态 + duplicated.Add(choice); // 记录选择过的元素值 + selected[i] = true; + state.Add(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 II */ + List> permutationsII(int[] nums) { + List> res = new List>(); + backtrack(new List(), nums, new bool[nums.Length], res); + return res; + } ``` === "Go" @@ -654,6 +691,44 @@ comments: true } ``` +=== "Swift" + + ```swift title="permutations_ii.swift" + /* 回溯算法:全排列 II */ + func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 当状态长度等于元素数量时,记录解 + if state.count == choices.count { + res.append(state) + return + } + // 遍历所有选择 + var duplicated: Set = [] + for (i, choice) in choices.enumerated() { + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if !selected[i], !duplicated.contains(choice) { + // 尝试:做出选择,更新状态 + duplicated.insert(choice) // 记录选择过的元素值 + selected[i] = true + state.append(choice) + // 进行下一轮选择 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.removeLast() + } + } + } + + /* 全排列 II */ + func permutationsII(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res + } + ``` + === "JS" ```javascript title="permutations_ii.js" @@ -731,97 +806,6 @@ comments: true } ``` -=== "C" - - ```c title="permutations_ii.c" - [class]{}-[func]{backtrack} - - [class]{}-[func]{permutationsII} - ``` - -=== "C#" - - ```csharp title="permutations_ii.cs" - /* 回溯算法:全排列 II */ - void backtrack(List state, int[] choices, bool[] selected, List> res) { - // 当状态长度等于元素数量时,记录解 - if (state.Count == choices.Length) { - res.Add(new List(state)); - return; - } - // 遍历所有选择 - ISet duplicated = new HashSet(); - for (int i = 0; i < choices.Length; i++) { - int choice = choices[i]; - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if (!selected[i] && !duplicated.Contains(choice)) { - // 尝试:做出选择,更新状态 - duplicated.Add(choice); // 记录选择过的元素值 - selected[i] = true; - state.Add(choice); - // 进行下一轮选择 - backtrack(state, choices, selected, res); - // 回退:撤销选择,恢复到之前的状态 - selected[i] = false; - state.RemoveAt(state.Count - 1); - } - } - } - - /* 全排列 II */ - List> permutationsII(int[] nums) { - List> res = new List>(); - backtrack(new List(), nums, new bool[nums.Length], res); - return res; - } - ``` - -=== "Swift" - - ```swift title="permutations_ii.swift" - /* 回溯算法:全排列 II */ - func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { - // 当状态长度等于元素数量时,记录解 - if state.count == choices.count { - res.append(state) - return - } - // 遍历所有选择 - var duplicated: Set = [] - for (i, choice) in choices.enumerated() { - // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 - if !selected[i], !duplicated.contains(choice) { - // 尝试:做出选择,更新状态 - duplicated.insert(choice) // 记录选择过的元素值 - selected[i] = true - state.append(choice) - // 进行下一轮选择 - backtrack(state: &state, choices: choices, selected: &selected, res: &res) - // 回退:撤销选择,恢复到之前的状态 - selected[i] = false - state.removeLast() - } - } - } - - /* 全排列 II */ - func permutationsII(nums: [Int]) -> [[Int]] { - var state: [Int] = [] - var selected = Array(repeating: false, count: nums.count) - var res: [[Int]] = [] - backtrack(state: &state, choices: nums, selected: &selected, res: &res) - return res - } - ``` - -=== "Zig" - - ```zig title="permutations_ii.zig" - [class]{}-[func]{backtrack} - - [class]{}-[func]{permutationsII} - ``` - === "Dart" ```dart title="permutations_ii.dart" @@ -901,6 +885,22 @@ comments: true } ``` +=== "C" + + ```c title="permutations_ii.c" + [class]{}-[func]{backtrack} + + [class]{}-[func]{permutationsII} + ``` + +=== "Zig" + + ```zig title="permutations_ii.zig" + [class]{}-[func]{backtrack} + + [class]{}-[func]{permutationsII} + ``` + 假设元素两两之间互不相同,则 $n$ 个元素共有 $n!$ 种排列(阶乘);在记录结果时,需要复制长度为 $n$ 的列表,使用 $O(n)$ 时间。**因此时间复杂度为 $O(n!n)$** 。 最大递归深度为 $n$ ,使用 $O(n)$ 栈帧空间。`selected` 使用 $O(n)$ 空间。同一时刻最多共有 $n$ 个 `duplicated` ,使用 $O(n^2)$ 空间。**因此空间复杂度为 $O(n^2)$** 。 diff --git a/chapter_backtracking/subset_sum_problem.md b/chapter_backtracking/subset_sum_problem.md index 42fba6d8f..d30bfeea6 100644 --- a/chapter_backtracking/subset_sum_problem.md +++ b/chapter_backtracking/subset_sum_problem.md @@ -21,76 +21,6 @@ comments: true 而与全排列问题不同的是,**本题集合中的元素可以被无限次选取**,因此无须借助 `selected` 布尔列表来记录元素是否已被选择。我们可以对全排列代码进行小幅修改,初步得到解题代码。 -=== "Java" - - ```java title="subset_sum_i_naive.java" - /* 回溯算法:子集和 I */ - void backtrack(List state, int target, int total, int[] choices, List> res) { - // 子集和等于 target 时,记录解 - if (total == target) { - res.add(new ArrayList<>(state)); - return; - } - // 遍历所有选择 - for (int i = 0; i < choices.length; i++) { - // 剪枝:若子集和超过 target ,则跳过该选择 - if (total + choices[i] > target) { - continue; - } - // 尝试:做出选择,更新元素和 total - state.add(choices[i]); - // 进行下一轮选择 - backtrack(state, target, total + choices[i], choices, res); - // 回退:撤销选择,恢复到之前的状态 - state.remove(state.size() - 1); - } - } - - /* 求解子集和 I(包含重复子集) */ - List> subsetSumINaive(int[] nums, int target) { - List state = new ArrayList<>(); // 状态(子集) - int total = 0; // 子集和 - List> res = new ArrayList<>(); // 结果列表(子集列表) - backtrack(state, target, total, nums, res); - return res; - } - ``` - -=== "C++" - - ```cpp title="subset_sum_i_naive.cpp" - /* 回溯算法:子集和 I */ - void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { - // 子集和等于 target 时,记录解 - if (total == target) { - res.push_back(state); - return; - } - // 遍历所有选择 - for (size_t i = 0; i < choices.size(); i++) { - // 剪枝:若子集和超过 target ,则跳过该选择 - if (total + choices[i] > target) { - continue; - } - // 尝试:做出选择,更新元素和 total - state.push_back(choices[i]); - // 进行下一轮选择 - backtrack(state, target, total + choices[i], choices, res); - // 回退:撤销选择,恢复到之前的状态 - state.pop_back(); - } - } - - /* 求解子集和 I(包含重复子集) */ - vector> subsetSumINaive(vector &nums, int target) { - vector state; // 状态(子集) - int total = 0; // 子集和 - vector> res; // 结果列表(子集列表) - backtrack(state, target, total, nums, res); - return res; - } - ``` - === "Python" ```python title="subset_sum_i_naive.py" @@ -127,6 +57,111 @@ comments: true return res ``` +=== "C++" + + ```cpp title="subset_sum_i_naive.cpp" + /* 回溯算法:子集和 I */ + void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.push_back(state); + return; + } + // 遍历所有选择 + for (size_t i = 0; i < choices.size(); i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } + } + + /* 求解子集和 I(包含重复子集) */ + vector> subsetSumINaive(vector &nums, int target) { + vector state; // 状态(子集) + int total = 0; // 子集和 + vector> res; // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; + } + ``` + +=== "Java" + + ```java title="subset_sum_i_naive.java" + /* 回溯算法:子集和 I */ + void backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.add(new ArrayList<>(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I(包含重复子集) */ + List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // 状态(子集) + int total = 0; // 子集和 + List> res = new ArrayList<>(); // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; + } + ``` + +=== "C#" + + ```csharp title="subset_sum_i_naive.cs" + /* 回溯算法:子集和 I */ + void backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.Length; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.Add(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I(包含重复子集) */ + List> subsetSumINaive(int[] nums, int target) { + List state = new List(); // 状态(子集) + int total = 0; // 子集和 + List> res = new List>(); // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; + } + ``` + === "Go" ```go title="subset_sum_i_naive.go" @@ -163,6 +198,41 @@ comments: true } ``` +=== "Swift" + + ```swift title="subset_sum_i_naive.swift" + /* 回溯算法:子集和 I */ + func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { + // 子集和等于 target 时,记录解 + if total == target { + res.append(state) + return + } + // 遍历所有选择 + for i in stride(from: 0, to: choices.count, by: 1) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if total + choices[i] > target { + continue + } + // 尝试:做出选择,更新元素和 total + state.append(choices[i]) + // 进行下一轮选择 + backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) + // 回退:撤销选择,恢复到之前的状态 + state.removeLast() + } + } + + /* 求解子集和 I(包含重复子集) */ + func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状态(子集) + let total = 0 // 子集和 + var res: [[Int]] = [] // 结果列表(子集列表) + backtrack(state: &state, target: target, total: total, choices: nums, res: &res) + return res + } + ``` + === "JS" ```javascript title="subset_sum_i_naive.js" @@ -239,123 +309,6 @@ comments: true } ``` -=== "C" - - ```c title="subset_sum_i_naive.c" - /* 回溯算法:子集和 I */ - void backtrack(vector *state, int target, int total, vector *choices, vector *res) { - // 子集和等于 target 时,记录解 - if (total == target) { - vector *tmpVector = newVector(); - for (int i = 0; i < state->size; i++) { - vectorPushback(tmpVector, state->data[i], sizeof(int)); - } - vectorPushback(res, tmpVector, sizeof(vector)); - return; - } - // 遍历所有选择 - for (size_t i = 0; i < choices->size; i++) { - // 剪枝:若子集和超过 target ,则跳过该选择 - if (total + *(int *)(choices->data[i]) > target) { - continue; - } - // 尝试:做出选择,更新元素和 total - vectorPushback(state, choices->data[i], sizeof(int)); - // 进行下一轮选择 - backtrack(state, target, total + *(int *)(choices->data[i]), choices, res); - // 回退:撤销选择,恢复到之前的状态 - vectorPopback(state); - } - } - - /* 求解子集和 I(包含重复子集) */ - vector *subsetSumINaive(vector *nums, int target) { - vector *state = newVector(); // 状态(子集) - int total = 0; // 子集和 - vector *res = newVector(); // 结果列表(子集列表) - backtrack(state, target, total, nums, res); - return res; - } - ``` - -=== "C#" - - ```csharp title="subset_sum_i_naive.cs" - /* 回溯算法:子集和 I */ - void backtrack(List state, int target, int total, int[] choices, List> res) { - // 子集和等于 target 时,记录解 - if (total == target) { - res.Add(new List(state)); - return; - } - // 遍历所有选择 - for (int i = 0; i < choices.Length; i++) { - // 剪枝:若子集和超过 target ,则跳过该选择 - if (total + choices[i] > target) { - continue; - } - // 尝试:做出选择,更新元素和 total - state.Add(choices[i]); - // 进行下一轮选择 - backtrack(state, target, total + choices[i], choices, res); - // 回退:撤销选择,恢复到之前的状态 - state.RemoveAt(state.Count - 1); - } - } - - /* 求解子集和 I(包含重复子集) */ - List> subsetSumINaive(int[] nums, int target) { - List state = new List(); // 状态(子集) - int total = 0; // 子集和 - List> res = new List>(); // 结果列表(子集列表) - backtrack(state, target, total, nums, res); - return res; - } - ``` - -=== "Swift" - - ```swift title="subset_sum_i_naive.swift" - /* 回溯算法:子集和 I */ - func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { - // 子集和等于 target 时,记录解 - if total == target { - res.append(state) - return - } - // 遍历所有选择 - for i in stride(from: 0, to: choices.count, by: 1) { - // 剪枝:若子集和超过 target ,则跳过该选择 - if total + choices[i] > target { - continue - } - // 尝试:做出选择,更新元素和 total - state.append(choices[i]) - // 进行下一轮选择 - backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) - // 回退:撤销选择,恢复到之前的状态 - state.removeLast() - } - } - - /* 求解子集和 I(包含重复子集) */ - func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { - var state: [Int] = [] // 状态(子集) - let total = 0 // 子集和 - var res: [[Int]] = [] // 结果列表(子集列表) - backtrack(state: &state, target: target, total: total, choices: nums, res: &res) - return res - } - ``` - -=== "Zig" - - ```zig title="subset_sum_i_naive.zig" - [class]{}-[func]{backtrack} - - [class]{}-[func]{subsetSumINaive} - ``` - === "Dart" ```dart title="subset_sum_i_naive.dart" @@ -432,6 +385,53 @@ comments: true } ``` +=== "C" + + ```c title="subset_sum_i_naive.c" + /* 回溯算法:子集和 I */ + void backtrack(vector *state, int target, int total, vector *choices, vector *res) { + // 子集和等于 target 时,记录解 + if (total == target) { + vector *tmpVector = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(tmpVector, state->data[i], sizeof(int)); + } + vectorPushback(res, tmpVector, sizeof(vector)); + return; + } + // 遍历所有选择 + for (size_t i = 0; i < choices->size; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + *(int *)(choices->data[i]) > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + vectorPushback(state, choices->data[i], sizeof(int)); + // 进行下一轮选择 + backtrack(state, target, total + *(int *)(choices->data[i]), choices, res); + // 回退:撤销选择,恢复到之前的状态 + vectorPopback(state); + } + } + + /* 求解子集和 I(包含重复子集) */ + vector *subsetSumINaive(vector *nums, int target) { + vector *state = newVector(); // 状态(子集) + int total = 0; // 子集和 + vector *res = newVector(); // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; + } + ``` + +=== "Zig" + + ```zig title="subset_sum_i_naive.zig" + [class]{}-[func]{backtrack} + + [class]{}-[func]{subsetSumINaive} + ``` + 向以上代码输入数组 $[3, 4, 5]$ 和目标元素 $9$ ,输出结果为 $[3, 3, 3], [4, 5], [5, 4]$ 。**虽然成功找出了所有和为 $9$ 的子集,但其中存在重复的子集 $[4, 5]$ 和 $[5, 4]$** 。 这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如图 13-10 所示,先选 $4$ 后选 $5$ 与先选 $5$ 后选 $4$ 是两个不同的分支,但两者对应同一个子集。 @@ -473,42 +473,39 @@ comments: true - 在开启搜索前,先将数组 `nums` 排序。在遍历所有选择时,**当子集和超过 `target` 时直接结束循环**,因为后边的元素更大,其子集和都一定会超过 `target` 。 - 省去元素和变量 `total`,**通过在 `target` 上执行减法来统计元素和**,当 `target` 等于 $0$ 时记录解。 -=== "Java" +=== "Python" - ```java title="subset_sum_i.java" - /* 回溯算法:子集和 I */ - void backtrack(List state, int target, int[] choices, int start, List> res) { - // 子集和等于 target 时,记录解 - if (target == 0) { - res.add(new ArrayList<>(state)); - return; - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - for (int i = start; i < choices.length; i++) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if (target - choices[i] < 0) { - break; - } - // 尝试:做出选择,更新 target, start - state.add(choices[i]); - // 进行下一轮选择 - backtrack(state, target - choices[i], choices, i, res); - // 回退:撤销选择,恢复到之前的状态 - state.remove(state.size() - 1); - } - } + ```python title="subset_sum_i.py" + def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] + ): + """回溯算法:子集和 I""" + # 子集和等于 target 时,记录解 + if target == 0: + res.append(list(state)) + return + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in range(start, len(choices)): + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0: + break + # 尝试:做出选择,更新 target, start + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() - /* 求解子集和 I */ - List> subsetSumI(int[] nums, int target) { - List state = new ArrayList<>(); // 状态(子集) - Arrays.sort(nums); // 对 nums 进行排序 - int start = 0; // 遍历起始点 - List> res = new ArrayList<>(); // 结果列表(子集列表) - backtrack(state, target, nums, start, res); - return res; - } + def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 I""" + state = [] # 状态(子集) + nums.sort() # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res ``` === "C++" @@ -549,39 +546,80 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="subset_sum_i.py" - def backtrack( - state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] - ): - """回溯算法:子集和 I""" - # 子集和等于 target 时,记录解 - if target == 0: - res.append(list(state)) - return - # 遍历所有选择 - # 剪枝二:从 start 开始遍历,避免生成重复子集 - for i in range(start, len(choices)): - # 剪枝一:若子集和超过 target ,则直接结束循环 - # 这是因为数组已排序,后边元素更大,子集和一定超过 target - if target - choices[i] < 0: - break - # 尝试:做出选择,更新 target, start - state.append(choices[i]) - # 进行下一轮选择 - backtrack(state, target - choices[i], choices, i, res) - # 回退:撤销选择,恢复到之前的状态 - state.pop() + ```java title="subset_sum_i.java" + /* 回溯算法:子集和 I */ + void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.remove(state.size() - 1); + } + } - def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: - """求解子集和 I""" - state = [] # 状态(子集) - nums.sort() # 对 nums 进行排序 - start = 0 # 遍历起始点 - res = [] # 结果列表(子集列表) - backtrack(state, target, nums, start, res) - return res + /* 求解子集和 I */ + List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // 状态(子集) + Arrays.sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = new ArrayList<>(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } + ``` + +=== "C#" + + ```csharp title="subset_sum_i.cs" + /* 回溯算法:子集和 I */ + void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.Add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I */ + List> subsetSumI(int[] nums, int target) { + List state = new List(); // 状态(子集) + Array.Sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = new List>(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } ``` === "Go" @@ -623,6 +661,44 @@ comments: true } ``` +=== "Swift" + + ```swift title="subset_sum_i.swift" + /* 回溯算法:子集和 I */ + func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等于 target 时,记录解 + if target == 0 { + res.append(state) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in stride(from: start, to: choices.count, by: 1) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 { + break + } + // 尝试:做出选择,更新 target, start + state.append(choices[i]) + // 进行下一轮选择 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) + // 回退:撤销选择,恢复到之前的状态 + state.removeLast() + } + } + + /* 求解子集和 I */ + func subsetSumI(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状态(子集) + let nums = nums.sorted() // 对 nums 进行排序 + let start = 0 // 遍历起始点 + var res: [[Int]] = [] // 结果列表(子集列表) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res + } + ``` + === "JS" ```javascript title="subset_sum_i.js" @@ -705,131 +781,6 @@ comments: true } ``` -=== "C" - - ```c title="subset_sum_i.c" - /* 回溯算法:子集和 I */ - void backtrack(vector *state, int target, vector *choices, int start, vector *res) { - // 子集和等于 target 时,记录解 - if (target == 0) { - vector *tmpVector = newVector(); - for (int i = 0; i < state->size; i++) { - vectorPushback(tmpVector, state->data[i], sizeof(int)); - } - vectorPushback(res, tmpVector, sizeof(vector)); - return; - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - for (int i = start; i < choices->size; i++) { - // 剪枝:若子集和超过 target ,则跳过该选择 - if (target - *(int *)(choices->data[i]) < 0) { - break; - } - // 尝试:做出选择,更新 target, start - vectorPushback(state, choices->data[i], sizeof(int)); - // 进行下一轮选择 - backtrack(state, target - *(int *)(choices->data[i]), choices, i, res); - // 回退:撤销选择,恢复到之前的状态 - vectorPopback(state); - } - } - - /* 求解子集和 I */ - vector *subsetSumI(vector *nums, int target) { - vector *state = newVector(); // 状态(子集) - qsort(nums->data, nums->size, sizeof(int *), comp); // 对 nums 进行排序 - int start = 0; // 子集和 - vector *res = newVector(); // 结果列表(子集列表) - backtrack(state, target, nums, start, res); - return res; - } - ``` - -=== "C#" - - ```csharp title="subset_sum_i.cs" - /* 回溯算法:子集和 I */ - void backtrack(List state, int target, int[] choices, int start, List> res) { - // 子集和等于 target 时,记录解 - if (target == 0) { - res.Add(new List(state)); - return; - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - for (int i = start; i < choices.Length; i++) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if (target - choices[i] < 0) { - break; - } - // 尝试:做出选择,更新 target, start - state.Add(choices[i]); - // 进行下一轮选择 - backtrack(state, target - choices[i], choices, i, res); - // 回退:撤销选择,恢复到之前的状态 - state.RemoveAt(state.Count - 1); - } - } - - /* 求解子集和 I */ - List> subsetSumI(int[] nums, int target) { - List state = new List(); // 状态(子集) - Array.Sort(nums); // 对 nums 进行排序 - int start = 0; // 遍历起始点 - List> res = new List>(); // 结果列表(子集列表) - backtrack(state, target, nums, start, res); - return res; - } - ``` - -=== "Swift" - - ```swift title="subset_sum_i.swift" - /* 回溯算法:子集和 I */ - func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { - // 子集和等于 target 时,记录解 - if target == 0 { - res.append(state) - return - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - for i in stride(from: start, to: choices.count, by: 1) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if target - choices[i] < 0 { - break - } - // 尝试:做出选择,更新 target, start - state.append(choices[i]) - // 进行下一轮选择 - backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) - // 回退:撤销选择,恢复到之前的状态 - state.removeLast() - } - } - - /* 求解子集和 I */ - func subsetSumI(nums: [Int], target: Int) -> [[Int]] { - var state: [Int] = [] // 状态(子集) - let nums = nums.sorted() // 对 nums 进行排序 - let start = 0 // 遍历起始点 - var res: [[Int]] = [] // 结果列表(子集列表) - backtrack(state: &state, target: target, choices: nums, start: start, res: &res) - return res - } - ``` - -=== "Zig" - - ```zig title="subset_sum_i.zig" - [class]{}-[func]{backtrack} - - [class]{}-[func]{subsetSumI} - ``` - === "Dart" ```dart title="subset_sum_i.dart" @@ -912,6 +863,55 @@ comments: true } ``` +=== "C" + + ```c title="subset_sum_i.c" + /* 回溯算法:子集和 I */ + void backtrack(vector *state, int target, vector *choices, int start, vector *res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + vector *tmpVector = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(tmpVector, state->data[i], sizeof(int)); + } + vectorPushback(res, tmpVector, sizeof(vector)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices->size; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (target - *(int *)(choices->data[i]) < 0) { + break; + } + // 尝试:做出选择,更新 target, start + vectorPushback(state, choices->data[i], sizeof(int)); + // 进行下一轮选择 + backtrack(state, target - *(int *)(choices->data[i]), choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + vectorPopback(state); + } + } + + /* 求解子集和 I */ + vector *subsetSumI(vector *nums, int target) { + vector *state = newVector(); // 状态(子集) + qsort(nums->data, nums->size, sizeof(int *), comp); // 对 nums 进行排序 + int start = 0; // 子集和 + vector *res = newVector(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } + ``` + +=== "Zig" + + ```zig title="subset_sum_i.zig" + [class]{}-[func]{backtrack} + + [class]{}-[func]{subsetSumI} + ``` + 如图 13-12 所示,为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入到以上代码后的整体回溯过程。 ![子集和 I 回溯过程](subset_sum_problem.assets/subset_sum_i.png) @@ -940,47 +940,43 @@ comments: true ### 2.   代码实现 -=== "Java" +=== "Python" - ```java title="subset_sum_ii.java" - /* 回溯算法:子集和 II */ - void backtrack(List state, int target, int[] choices, int start, List> res) { - // 子集和等于 target 时,记录解 - if (target == 0) { - res.add(new ArrayList<>(state)); - return; - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - // 剪枝三:从 start 开始遍历,避免重复选择同一元素 - for (int i = start; i < choices.length; i++) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if (target - choices[i] < 0) { - break; - } - // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 - if (i > start && choices[i] == choices[i - 1]) { - continue; - } - // 尝试:做出选择,更新 target, start - state.add(choices[i]); - // 进行下一轮选择 - backtrack(state, target - choices[i], choices, i + 1, res); - // 回退:撤销选择,恢复到之前的状态 - state.remove(state.size() - 1); - } - } + ```python title="subset_sum_ii.py" + def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] + ): + """回溯算法:子集和 II""" + # 子集和等于 target 时,记录解 + if target == 0: + res.append(list(state)) + return + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + # 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in range(start, len(choices)): + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0: + break + # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start and choices[i] == choices[i - 1]: + continue + # 尝试:做出选择,更新 target, start + state.append(choices[i]) + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop() - /* 求解子集和 II */ - List> subsetSumII(int[] nums, int target) { - List state = new ArrayList<>(); // 状态(子集) - Arrays.sort(nums); // 对 nums 进行排序 - int start = 0; // 遍历起始点 - List> res = new ArrayList<>(); // 结果列表(子集列表) - backtrack(state, target, nums, start, res); - return res; - } + def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: + """求解子集和 II""" + state = [] # 状态(子集) + nums.sort() # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + return res ``` === "C++" @@ -1026,43 +1022,90 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="subset_sum_ii.py" - def backtrack( - state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] - ): - """回溯算法:子集和 II""" - # 子集和等于 target 时,记录解 - if target == 0: - res.append(list(state)) - return - # 遍历所有选择 - # 剪枝二:从 start 开始遍历,避免生成重复子集 - # 剪枝三:从 start 开始遍历,避免重复选择同一元素 - for i in range(start, len(choices)): - # 剪枝一:若子集和超过 target ,则直接结束循环 - # 这是因为数组已排序,后边元素更大,子集和一定超过 target - if target - choices[i] < 0: - break - # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 - if i > start and choices[i] == choices[i - 1]: - continue - # 尝试:做出选择,更新 target, start - state.append(choices[i]) - # 进行下一轮选择 - backtrack(state, target - choices[i], choices, i + 1, res) - # 回退:撤销选择,恢复到之前的状态 - state.pop() + ```java title="subset_sum_ii.java" + /* 回溯算法:子集和 II */ + void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.remove(state.size() - 1); + } + } - def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: - """求解子集和 II""" - state = [] # 状态(子集) - nums.sort() # 对 nums 进行排序 - start = 0 # 遍历起始点 - res = [] # 结果列表(子集列表) - backtrack(state, target, nums, start, res) - return res + /* 求解子集和 II */ + List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // 状态(子集) + Arrays.sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = new ArrayList<>(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } + ``` + +=== "C#" + + ```csharp title="subset_sum_ii.cs" + /* 回溯算法:子集和 II */ + void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.Add(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 II */ + List> subsetSumII(int[] nums, int target) { + List state = new List(); // 状态(子集) + Array.Sort(nums); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + List> res = new List>(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } ``` === "Go" @@ -1109,6 +1152,49 @@ comments: true } ``` +=== "Swift" + + ```swift title="subset_sum_ii.swift" + /* 回溯算法:子集和 II */ + func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等于 target 时,记录解 + if target == 0 { + res.append(state) + return + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in stride(from: start, to: choices.count, by: 1) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if target - choices[i] < 0 { + break + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if i > start, choices[i] == choices[i - 1] { + continue + } + // 尝试:做出选择,更新 target, start + state.append(choices[i]) + // 进行下一轮选择 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) + // 回退:撤销选择,恢复到之前的状态 + state.removeLast() + } + } + + /* 求解子集和 II */ + func subsetSumII(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状态(子集) + let nums = nums.sorted() // 对 nums 进行排序 + let start = 0 // 遍历起始点 + var res: [[Int]] = [] // 结果列表(子集列表) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res + } + ``` + === "JS" ```javascript title="subset_sum_ii.js" @@ -1201,147 +1287,6 @@ comments: true } ``` -=== "C" - - ```c title="subset_sum_ii.c" - /* 回溯算法:子集和 II */ - void backtrack(vector *state, int target, vector *choices, int start, vector *res) { - // 子集和等于 target 时,记录解 - if (target == 0) { - vector *tmpVector = newVector(); - for (int i = 0; i < state->size; i++) { - vectorPushback(tmpVector, state->data[i], sizeof(int)); - } - vectorPushback(res, tmpVector, sizeof(vector)); - return; - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - // 剪枝三:从 start 开始遍历,避免重复选择同一元素 - for (int i = start; i < choices->size; i++) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if (target - *(int *)(choices->data[i]) < 0) { - continue; - } - // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 - if (i > start && *(int *)(choices->data[i]) == *(int *)(choices->data[i - 1])) { - continue; - } - // 尝试:做出选择,更新 target, start - vectorPushback(state, choices->data[i], sizeof(int)); - // 进行下一轮选择 - backtrack(state, target - *(int *)(choices->data[i]), choices, i + 1, res); - // 回退:撤销选择,恢复到之前的状态 - vectorPopback(state); - } - } - - /* 求解子集和 II */ - vector *subsetSumII(vector *nums, int target) { - vector *state = newVector(); // 状态(子集) - qsort(nums->data, nums->size, sizeof(int *), comp); // 对 nums 进行排序 - int start = 0; // 子集和 - vector *res = newVector(); // 结果列表(子集列表) - backtrack(state, target, nums, start, res); - return res; - } - ``` - -=== "C#" - - ```csharp title="subset_sum_ii.cs" - /* 回溯算法:子集和 II */ - void backtrack(List state, int target, int[] choices, int start, List> res) { - // 子集和等于 target 时,记录解 - if (target == 0) { - res.Add(new List(state)); - return; - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - // 剪枝三:从 start 开始遍历,避免重复选择同一元素 - for (int i = start; i < choices.Length; i++) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if (target - choices[i] < 0) { - break; - } - // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 - if (i > start && choices[i] == choices[i - 1]) { - continue; - } - // 尝试:做出选择,更新 target, start - state.Add(choices[i]); - // 进行下一轮选择 - backtrack(state, target - choices[i], choices, i + 1, res); - // 回退:撤销选择,恢复到之前的状态 - state.RemoveAt(state.Count - 1); - } - } - - /* 求解子集和 II */ - List> subsetSumII(int[] nums, int target) { - List state = new List(); // 状态(子集) - Array.Sort(nums); // 对 nums 进行排序 - int start = 0; // 遍历起始点 - List> res = new List>(); // 结果列表(子集列表) - backtrack(state, target, nums, start, res); - return res; - } - ``` - -=== "Swift" - - ```swift title="subset_sum_ii.swift" - /* 回溯算法:子集和 II */ - func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { - // 子集和等于 target 时,记录解 - if target == 0 { - res.append(state) - return - } - // 遍历所有选择 - // 剪枝二:从 start 开始遍历,避免生成重复子集 - // 剪枝三:从 start 开始遍历,避免重复选择同一元素 - for i in stride(from: start, to: choices.count, by: 1) { - // 剪枝一:若子集和超过 target ,则直接结束循环 - // 这是因为数组已排序,后边元素更大,子集和一定超过 target - if target - choices[i] < 0 { - break - } - // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 - if i > start, choices[i] == choices[i - 1] { - continue - } - // 尝试:做出选择,更新 target, start - state.append(choices[i]) - // 进行下一轮选择 - backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) - // 回退:撤销选择,恢复到之前的状态 - state.removeLast() - } - } - - /* 求解子集和 II */ - func subsetSumII(nums: [Int], target: Int) -> [[Int]] { - var state: [Int] = [] // 状态(子集) - let nums = nums.sorted() // 对 nums 进行排序 - let start = 0 // 遍历起始点 - var res: [[Int]] = [] // 结果列表(子集列表) - backtrack(state: &state, target: target, choices: nums, start: start, res: &res) - return res - } - ``` - -=== "Zig" - - ```zig title="subset_sum_ii.zig" - [class]{}-[func]{backtrack} - - [class]{}-[func]{subsetSumII} - ``` - === "Dart" ```dart title="subset_sum_ii.dart" @@ -1434,6 +1379,61 @@ comments: true } ``` +=== "C" + + ```c title="subset_sum_ii.c" + /* 回溯算法:子集和 II */ + void backtrack(vector *state, int target, vector *choices, int start, vector *res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + vector *tmpVector = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(tmpVector, state->data[i], sizeof(int)); + } + vectorPushback(res, tmpVector, sizeof(vector)); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices->size; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - *(int *)(choices->data[i]) < 0) { + continue; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && *(int *)(choices->data[i]) == *(int *)(choices->data[i - 1])) { + continue; + } + // 尝试:做出选择,更新 target, start + vectorPushback(state, choices->data[i], sizeof(int)); + // 进行下一轮选择 + backtrack(state, target - *(int *)(choices->data[i]), choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + vectorPopback(state); + } + } + + /* 求解子集和 II */ + vector *subsetSumII(vector *nums, int target) { + vector *state = newVector(); // 状态(子集) + qsort(nums->data, nums->size, sizeof(int *), comp); // 对 nums 进行排序 + int start = 0; // 子集和 + vector *res = newVector(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } + ``` + +=== "Zig" + + ```zig title="subset_sum_ii.zig" + [class]{}-[func]{backtrack} + + [class]{}-[func]{subsetSumII} + ``` + 图 13-14 展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 ![子集和 II 回溯过程](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/chapter_computational_complexity/iteration_and_recursion.md b/chapter_computational_complexity/iteration_and_recursion.md index 7c0f9bb2d..40b922eb8 100644 --- a/chapter_computational_complexity/iteration_and_recursion.md +++ b/chapter_computational_complexity/iteration_and_recursion.md @@ -17,18 +17,16 @@ status: new 以下函数基于 `for` 循环实现了求和 $1 + 2 + \dots + n$ ,求和结果使用变量 `res` 记录。需要注意的是,Python 中 `range(a, b)` 对应的区间是“左闭右开”的,对应的遍历范围为 $a, a + 1, \dots, b-1$ 。 -=== "Java" +=== "Python" - ```java title="iteration.java" - /* for 循环 */ - int forLoop(int n) { - int res = 0; - // 循环求和 1, 2, ..., n-1, n - for (int i = 1; i <= n; i++) { - res += i; - } - return res; - } + ```python title="iteration.py" + def for_loop(n: int) -> int: + """for 循环""" + res = 0 + # 循环求和 1, 2, ..., n-1, n + for i in range(1, n + 1): + res += i + return res ``` === "C++" @@ -45,16 +43,32 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="iteration.py" - def for_loop(n: int) -> int: - """for 循环""" - res = 0 - # 循环求和 1, 2, ..., n-1, n - for i in range(1, n + 1): - res += i - return res + ```java title="iteration.java" + /* for 循环 */ + int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + ``` + +=== "C#" + + ```csharp title="iteration.cs" + /* for 循环 */ + int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } ``` === "Go" @@ -71,6 +85,20 @@ status: new } ``` +=== "Swift" + + ```swift title="iteration.swift" + /* for 循环 */ + func forLoop(n: Int) -> Int { + var res = 0 + // 循环求和 1, 2, ..., n-1, n + for i in 1 ... n { + res += i + } + return res + } + ``` + === "JS" ```javascript title="iteration.js" @@ -99,46 +127,6 @@ status: new } ``` -=== "C" - - ```c title="iteration.c" - [class]{}-[func]{forLoop} - ``` - -=== "C#" - - ```csharp title="iteration.cs" - /* for 循环 */ - int forLoop(int n) { - int res = 0; - // 循环求和 1, 2, ..., n-1, n - for (int i = 1; i <= n; i++) { - res += i; - } - return res; - } - ``` - -=== "Swift" - - ```swift title="iteration.swift" - /* for 循环 */ - func forLoop(n: Int) -> Int { - var res = 0 - // 循环求和 1, 2, ..., n-1, n - for i in 1 ... n { - res += i - } - return res - } - ``` - -=== "Zig" - - ```zig title="iteration.zig" - [class]{}-[func]{forLoop} - ``` - === "Dart" ```dart title="iteration.dart" @@ -167,6 +155,18 @@ status: new } ``` +=== "C" + + ```c title="iteration.c" + [class]{}-[func]{forLoop} + ``` + +=== "Zig" + + ```zig title="iteration.zig" + [class]{}-[func]{forLoop} + ``` + 图 2-1 展示了该求和函数的流程框图。 ![求和函数的流程框图](iteration_and_recursion.assets/iteration.png) @@ -181,20 +181,18 @@ status: new 下面,我们用 `while` 循环来实现求和 $1 + 2 + \dots + n$ 。 -=== "Java" +=== "Python" - ```java title="iteration.java" - /* while 循环 */ - int whileLoop(int n) { - int res = 0; - int i = 1; // 初始化条件变量 - // 循环求和 1, 2, ..., n-1, n - while (i <= n) { - res += i; - i++; // 更新条件变量 - } - return res; - } + ```python title="iteration.py" + def while_loop(n: int) -> int: + """while 循环""" + res = 0 + i = 1 # 初始化条件变量 + # 循环求和 1, 2, ..., n-1, n + while i <= n: + res += i + i += 1 # 更新条件变量 + return res ``` === "C++" @@ -213,18 +211,36 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="iteration.py" - def while_loop(n: int) -> int: - """while 循环""" - res = 0 - i = 1 # 初始化条件变量 - # 循环求和 1, 2, ..., n-1, n - while i <= n: - res += i - i += 1 # 更新条件变量 - return res + ```java title="iteration.java" + /* while 循环 */ + int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; + } + ``` + +=== "C#" + + ```csharp title="iteration.cs" + /* while 循环 */ + int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i += 1; // 更新条件变量 + } + return res; + } ``` === "Go" @@ -245,6 +261,22 @@ status: new } ``` +=== "Swift" + + ```swift title="iteration.swift" + /* while 循环 */ + func whileLoop(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while i <= n { + res += i + i += 1 // 更新条件变量 + } + return res + } + ``` + === "JS" ```javascript title="iteration.js" @@ -277,50 +309,6 @@ status: new } ``` -=== "C" - - ```c title="iteration.c" - [class]{}-[func]{whileLoop} - ``` - -=== "C#" - - ```csharp title="iteration.cs" - /* while 循环 */ - int whileLoop(int n) { - int res = 0; - int i = 1; // 初始化条件变量 - // 循环求和 1, 2, ..., n-1, n - while (i <= n) { - res += i; - i += 1; // 更新条件变量 - } - return res; - } - ``` - -=== "Swift" - - ```swift title="iteration.swift" - /* while 循环 */ - func whileLoop(n: Int) -> Int { - var res = 0 - var i = 1 // 初始化条件变量 - // 循环求和 1, 2, ..., n-1, n - while i <= n { - res += i - i += 1 // 更新条件变量 - } - return res - } - ``` - -=== "Zig" - - ```zig title="iteration.zig" - [class]{}-[func]{whileLoop} - ``` - === "Dart" ```dart title="iteration.dart" @@ -353,26 +341,36 @@ status: new } ``` +=== "C" + + ```c title="iteration.c" + [class]{}-[func]{whileLoop} + ``` + +=== "Zig" + + ```zig title="iteration.zig" + [class]{}-[func]{whileLoop} + ``` + 在 `while` 循环中,由于初始化和更新条件变量的步骤是独立在循环结构之外的,**因此它比 `for` 循环的自由度更高**。 例如在以下代码中,条件变量 $i$ 每轮进行了两次更新,这种情况就不太方便用 `for` 循环实现。 -=== "Java" +=== "Python" - ```java title="iteration.java" - /* while 循环(两次更新) */ - int whileLoopII(int n) { - int res = 0; - int i = 1; // 初始化条件变量 - // 循环求和 1, 4, ... - while (i <= n) { - res += i; - // 更新条件变量 - i++; - i *= 2; - } - return res; - } + ```python title="iteration.py" + def while_loop_ii(n: int) -> int: + """while 循环(两次更新)""" + res = 0 + i = 1 # 初始化条件变量 + # 循环求和 1, 4, ... + while i <= n: + res += i + # 更新条件变量 + i += 1 + i *= 2 + return res ``` === "C++" @@ -393,20 +391,40 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="iteration.py" - def while_loop_ii(n: int) -> int: - """while 循环(两次更新)""" - res = 0 - i = 1 # 初始化条件变量 - # 循环求和 1, 4, ... - while i <= n: - res += i - # 更新条件变量 - i += 1 - i *= 2 - return res + ```java title="iteration.java" + /* while 循环(两次更新) */ + int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; + } + ``` + +=== "C#" + + ```csharp title="iteration.cs" + /* while 循环(两次更新) */ + int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, 4, 5... + while (i <= n) { + res += i; + i += 1; // 更新条件变量 + res += i; + i *= 2; // 更新条件变量 + } + return res; + } ``` === "Go" @@ -428,6 +446,24 @@ status: new } ``` +=== "Swift" + + ```swift title="iteration.swift" + /* while 循环(两次更新) */ + func whileLoopII(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化条件变量 + // 循环求和 1, 4, ... + while i <= n { + res += i + // 更新条件变量 + i += 1 + i *= 2 + } + return res + } + ``` + === "JS" ```javascript title="iteration.js" @@ -464,54 +500,6 @@ status: new } ``` -=== "C" - - ```c title="iteration.c" - [class]{}-[func]{whileLoopII} - ``` - -=== "C#" - - ```csharp title="iteration.cs" - /* while 循环(两次更新) */ - int whileLoopII(int n) { - int res = 0; - int i = 1; // 初始化条件变量 - // 循环求和 1, 2, 4, 5... - while (i <= n) { - res += i; - i += 1; // 更新条件变量 - res += i; - i *= 2; // 更新条件变量 - } - return res; - } - ``` - -=== "Swift" - - ```swift title="iteration.swift" - /* while 循环(两次更新) */ - func whileLoopII(n: Int) -> Int { - var res = 0 - var i = 1 // 初始化条件变量 - // 循环求和 1, 4, ... - while i <= n { - res += i - // 更新条件变量 - i += 1 - i *= 2 - } - return res - } - ``` - -=== "Zig" - - ```zig title="iteration.zig" - [class]{}-[func]{whileLoopII} - ``` - === "Dart" ```dart title="iteration.dart" @@ -548,27 +536,36 @@ status: new } ``` +=== "C" + + ```c title="iteration.c" + [class]{}-[func]{whileLoopII} + ``` + +=== "Zig" + + ```zig title="iteration.zig" + [class]{}-[func]{whileLoopII} + ``` + 总的来说,**`for` 循环的代码更加紧凑,`while` 循环更加灵活**,两者都可以实现迭代结构。选择使用哪一个应该根据特定问题的需求来决定。 ### 3.   嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,以 `for` 循环为例: -=== "Java" +=== "Python" - ```java title="iteration.java" - /* 双层 for 循环 */ - String nestedForLoop(int n) { - StringBuilder res = new StringBuilder(); - // 循环 i = 1, 2, ..., n-1, n - for (int i = 1; i <= n; i++) { - // 循环 j = 1, 2, ..., n-1, n - for (int j = 1; j <= n; j++) { - res.append("(" + i + ", " + j + "), "); - } - } - return res.toString(); - } + ```python title="iteration.py" + def nested_for_loop(n: int) -> str: + """双层 for 循环""" + res = "" + # 循环 i = 1, 2, ..., n-1, n + for i in range(1, n + 1): + # 循环 j = 1, 2, ..., n-1, n + for j in range(1, n + 1): + res += f"({i}, {j}), " + return res ``` === "C++" @@ -588,18 +585,38 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="iteration.py" - def nested_for_loop(n: int) -> str: - """双层 for 循环""" - res = "" - # 循环 i = 1, 2, ..., n-1, n - for i in range(1, n + 1): - # 循环 j = 1, 2, ..., n-1, n - for j in range(1, n + 1): - res += f"({i}, {j}), " - return res + ```java title="iteration.java" + /* 双层 for 循环 */ + String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.append("(" + i + ", " + j + "), "); + } + } + return res.toString(); + } + ``` + +=== "C#" + + ```csharp title="iteration.cs" + /* 双层 for 循环 */ + string nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.Append($"({i}, {j}), "); + } + } + return res.ToString(); + } ``` === "Go" @@ -619,6 +636,23 @@ status: new } ``` +=== "Swift" + + ```swift title="iteration.swift" + /* 双层 for 循环 */ + func nestedForLoop(n: Int) -> String { + var res = "" + // 循环 i = 1, 2, ..., n-1, n + for i in 1 ... n { + // 循环 j = 1, 2, ..., n-1, n + for j in 1 ... n { + res.append("(\(i), \(j)), ") + } + } + return res + } + ``` + === "JS" ```javascript title="iteration.js" @@ -653,52 +687,6 @@ status: new } ``` -=== "C" - - ```c title="iteration.c" - [class]{}-[func]{nestedForLoop} - ``` - -=== "C#" - - ```csharp title="iteration.cs" - /* 双层 for 循环 */ - string nestedForLoop(int n) { - StringBuilder res = new StringBuilder(); - // 循环 i = 1, 2, ..., n-1, n - for (int i = 1; i <= n; i++) { - // 循环 j = 1, 2, ..., n-1, n - for (int j = 1; j <= n; j++) { - res.Append($"({i}, {j}), "); - } - } - return res.ToString(); - } - ``` - -=== "Swift" - - ```swift title="iteration.swift" - /* 双层 for 循环 */ - func nestedForLoop(n: Int) -> String { - var res = "" - // 循环 i = 1, 2, ..., n-1, n - for i in 1 ... n { - // 循环 j = 1, 2, ..., n-1, n - for j in 1 ... n { - res.append("(\(i), \(j)), ") - } - } - return res - } - ``` - -=== "Zig" - - ```zig title="iteration.zig" - [class]{}-[func]{nestedForLoop} - ``` - === "Dart" ```dart title="iteration.dart" @@ -733,6 +721,18 @@ status: new } ``` +=== "C" + + ```c title="iteration.c" + [class]{}-[func]{nestedForLoop} + ``` + +=== "Zig" + + ```zig title="iteration.zig" + [class]{}-[func]{nestedForLoop} + ``` + 图 2-2 给出了该嵌套循环的流程框图。 ![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png) @@ -758,19 +758,18 @@ status: new 观察以下代码,我们只需调用函数 `recur(n)` ,就可以完成 $1 + 2 + \dots + n$ 的计算: -=== "Java" +=== "Python" - ```java title="recursion.java" - /* 递归 */ - int recur(int n) { - // 终止条件 - if (n == 1) - return 1; - // 递:递归调用 - int res = recur(n - 1); - // 归:返回结果 - return n + res; - } + ```python title="recursion.py" + def recur(n: int) -> int: + """递归""" + # 终止条件 + if n == 1: + return 1 + # 递:递归调用 + res = recur(n - 1) + # 归:返回结果 + return n + res ``` === "C++" @@ -788,18 +787,34 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="recursion.py" - def recur(n: int) -> int: - """递归""" - # 终止条件 - if n == 1: - return 1 - # 递:递归调用 - res = recur(n - 1) - # 归:返回结果 - return n + res + ```java title="recursion.java" + /* 递归 */ + int recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; + } + ``` + +=== "C#" + + ```csharp title="recursion.cs" + /* 递归 */ + int recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; + } ``` === "Go" @@ -818,6 +833,22 @@ status: new } ``` +=== "Swift" + + ```swift title="recursion.swift" + /* 递归 */ + func recur(n: Int) -> Int { + // 终止条件 + if n == 1 { + return 1 + } + // 递:递归调用 + let res = recur(n: n - 1) + // 归:返回结果 + return n + res + } + ``` + === "JS" ```javascript title="recursion.js" @@ -846,49 +877,6 @@ status: new } ``` -=== "C" - - ```c title="recursion.c" - [class]{}-[func]{recur} - ``` - -=== "C#" - - ```csharp title="recursion.cs" - /* 递归 */ - int recur(int n) { - // 终止条件 - if (n == 1) - return 1; - // 递:递归调用 - int res = recur(n - 1); - // 归:返回结果 - return n + res; - } - ``` - -=== "Swift" - - ```swift title="recursion.swift" - /* 递归 */ - func recur(n: Int) -> Int { - // 终止条件 - if n == 1 { - return 1 - } - // 递:递归调用 - let res = recur(n: n - 1) - // 归:返回结果 - return n + res - } - ``` - -=== "Zig" - - ```zig title="recursion.zig" - [class]{}-[func]{recur} - ``` - === "Dart" ```dart title="recursion.dart" @@ -919,6 +907,18 @@ status: new } ``` +=== "C" + + ```c title="recursion.c" + [class]{}-[func]{recur} + ``` + +=== "Zig" + + ```zig title="recursion.zig" + [class]{}-[func]{recur} + ``` + 图 2-3 展示了该函数的递归过程。 ![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png) @@ -959,17 +959,16 @@ status: new 以计算 $1 + 2 + \dots + n$ 为例,我们可以将结果变量 `res` 设为函数参数,从而实现尾递归。 -=== "Java" +=== "Python" - ```java title="recursion.java" - /* 尾递归 */ - int tailRecur(int n, int res) { - // 终止条件 - if (n == 0) - return res; - // 尾递归调用 - return tailRecur(n - 1, res + n); - } + ```python title="recursion.py" + def tail_recur(n, res): + """尾递归""" + # 终止条件 + if n == 0: + return res + # 尾递归调用 + return tail_recur(n - 1, res + n) ``` === "C++" @@ -985,16 +984,30 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="recursion.py" - def tail_recur(n, res): - """尾递归""" - # 终止条件 - if n == 0: - return res - # 尾递归调用 - return tail_recur(n - 1, res + n) + ```java title="recursion.java" + /* 尾递归 */ + int tailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); + } + ``` + +=== "C#" + + ```csharp title="recursion.cs" + /* 尾递归 */ + int tailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); + } ``` === "Go" @@ -1011,6 +1024,20 @@ status: new } ``` +=== "Swift" + + ```swift title="recursion.swift" + /* 尾递归 */ + func tailRecur(n: Int, res: Int) -> Int { + // 终止条件 + if n == 0 { + return res + } + // 尾递归调用 + return tailRecur(n: n - 1, res: res + n) + } + ``` + === "JS" ```javascript title="recursion.js" @@ -1035,45 +1062,6 @@ status: new } ``` -=== "C" - - ```c title="recursion.c" - [class]{}-[func]{tailRecur} - ``` - -=== "C#" - - ```csharp title="recursion.cs" - /* 尾递归 */ - int tailRecur(int n, int res) { - // 终止条件 - if (n == 0) - return res; - // 尾递归调用 - return tailRecur(n - 1, res + n); - } - ``` - -=== "Swift" - - ```swift title="recursion.swift" - /* 尾递归 */ - func tailRecur(n: Int, res: Int) -> Int { - // 终止条件 - if n == 0 { - return res - } - // 尾递归调用 - return tailRecur(n: n - 1, res: res + n) - } - ``` - -=== "Zig" - - ```zig title="recursion.zig" - [class]{}-[func]{tailRecur} - ``` - === "Dart" ```dart title="recursion.dart" @@ -1100,6 +1088,18 @@ status: new } ``` +=== "C" + + ```c title="recursion.c" + [class]{}-[func]{tailRecur} + ``` + +=== "Zig" + + ```zig title="recursion.zig" + [class]{}-[func]{tailRecur} + ``` + 两种递归的过程对比如图 2-5 所示。 - **普通递归**:求和操作是在“归”的过程中执行的,每层返回后都要再执行一次求和操作。 @@ -1126,19 +1126,18 @@ status: new 按照递推关系进行递归调用,将前两个数字作为终止条件,便可写出递归代码。调用 `fib(n)` 即可得到斐波那契数列的第 $n$ 个数字。 -=== "Java" +=== "Python" - ```java title="recursion.java" - /* 斐波那契数列:递归 */ - int fib(int n) { - // 终止条件 f(1) = 0, f(2) = 1 - if (n == 1 || n == 2) - return n - 1; - // 递归调用 f(n) = f(n-1) + f(n-2) - int res = fib(n - 1) + fib(n - 2); - // 返回结果 f(n) - return res; - } + ```python title="recursion.py" + def fib(n: int) -> int: + """斐波那契数列:递归""" + # 终止条件 f(1) = 0, f(2) = 1 + if n == 1 or n == 2: + return n - 1 + # 递归调用 f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # 返回结果 f(n) + return res ``` === "C++" @@ -1156,18 +1155,34 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="recursion.py" - def fib(n: int) -> int: - """斐波那契数列:递归""" - # 终止条件 f(1) = 0, f(2) = 1 - if n == 1 or n == 2: - return n - 1 - # 递归调用 f(n) = f(n-1) + f(n-2) - res = fib(n - 1) + fib(n - 2) - # 返回结果 f(n) - return res + ```java title="recursion.java" + /* 斐波那契数列:递归 */ + int fib(int n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; + } + ``` + +=== "C#" + + ```csharp title="recursion.cs" + /* 斐波那契数列:递归 */ + int fib(int n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; + } ``` === "Go" @@ -1186,6 +1201,22 @@ status: new } ``` +=== "Swift" + + ```swift title="recursion.swift" + /* 斐波那契数列:递归 */ + func fib(n: Int) -> Int { + // 终止条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 递归调用 f(n) = f(n-1) + f(n-2) + let res = fib(n: n - 1) + fib(n: n - 2) + // 返回结果 f(n) + return res + } + ``` + === "JS" ```javascript title="recursion.js" @@ -1214,49 +1245,6 @@ status: new } ``` -=== "C" - - ```c title="recursion.c" - [class]{}-[func]{fib} - ``` - -=== "C#" - - ```csharp title="recursion.cs" - /* 斐波那契数列:递归 */ - int fib(int n) { - // 终止条件 f(1) = 0, f(2) = 1 - if (n == 1 || n == 2) - return n - 1; - // 递归调用 f(n) = f(n-1) + f(n-2) - int res = fib(n - 1) + fib(n - 2); - // 返回结果 f(n) - return res; - } - ``` - -=== "Swift" - - ```swift title="recursion.swift" - /* 斐波那契数列:递归 */ - func fib(n: Int) -> Int { - // 终止条件 f(1) = 0, f(2) = 1 - if n == 1 || n == 2 { - return n - 1 - } - // 递归调用 f(n) = f(n-1) + f(n-2) - let res = fib(n: n - 1) + fib(n: n - 2) - // 返回结果 f(n) - return res - } - ``` - -=== "Zig" - - ```zig title="recursion.zig" - [class]{}-[func]{fib} - ``` - === "Dart" ```dart title="recursion.dart" @@ -1287,6 +1275,18 @@ status: new } ``` +=== "C" + + ```c title="recursion.c" + [class]{}-[func]{fib} + ``` + +=== "Zig" + + ```zig title="recursion.zig" + [class]{}-[func]{fib} + ``` + 观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一个层数为 $n$ 的「递归树 recursion tree」。 ![斐波那契数列的递归树](iteration_and_recursion.assets/recursion_tree.png) diff --git a/chapter_computational_complexity/space_complexity.md b/chapter_computational_complexity/space_complexity.md index 0b7c8b80e..b131f2915 100755 --- a/chapter_computational_complexity/space_complexity.md +++ b/chapter_computational_complexity/space_complexity.md @@ -28,29 +28,26 @@ comments: true

图 2-15   算法使用的相关空间

-=== "Java" +=== "Python" - ```java title="" - /* 类 */ - class Node { - int val; - Node next; - Node(int x) { val = x; } - } - - /* 函数 */ - int function() { - // 执行某些操作... - 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; // 输出数据 - } + ```python title="" + class Node: + """类""" + def __init__(self, x: int): + self.val: int = x # 节点值 + self.next: Optional[Node] = None # 指向下一节点的引用 + + def function() -> int: + """函数""" + # 执行某些操作... + return 0 + + def algorithm(n) -> int: # 输入数据 + A = 0 # 暂存数据(常量,一般用大写字母表示) + b = 0 # 暂存数据(变量) + node = Node(0) # 暂存数据(对象) + c = function() # 栈帧空间(调用函数) + return A + b + c # 输出数据 ``` === "C++" @@ -78,26 +75,54 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="" - class Node: - """类""" - def __init__(self, x: int): - self.val: int = x # 节点值 - self.next: Optional[Node] = None # 指向下一节点的引用 + ```java title="" + /* 类 */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* 函数 */ + int function() { + // 执行某些操作... + 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; // 输出数据 + } + ``` - def function() -> int: - """函数""" - # 执行某些操作... - return 0 +=== "C#" - def algorithm(n) -> int: # 输入数据 - A = 0 # 暂存数据(常量,一般用大写字母表示) - b = 0 # 暂存数据(变量) - node = Node(0) # 暂存数据(对象) - c = function() # 栈帧空间(调用函数) - return A + b + c # 输出数据 + ```csharp title="" + /* 类 */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* 函数 */ + int function() { + // 执行某些操作... + return 0; + } + + int algorithm(int n) { // 输入数据 + const int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + Node node = new Node(0); // 暂存数据(对象) + int c = function(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } ``` === "Go" @@ -129,6 +154,34 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 类 */ + class Node { + var val: Int + var next: Node? + + init(x: Int) { + val = x + } + } + + /* 函数 */ + func function() -> Int { + // 执行某些操作... + 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 // 输出数据 + } + ``` + === "JS" ```javascript title="" @@ -185,82 +238,6 @@ comments: true } ``` -=== "C" - - ```c title="" - /* 函数 */ - int func() { - // 执行某些操作... - return 0; - } - - int algorithm(int n) { // 输入数据 - const int a = 0; // 暂存数据(常量) - int b = 0; // 暂存数据(变量) - int c = func(); // 栈帧空间(调用函数) - return a + b + c; // 输出数据 - } - ``` - -=== "C#" - - ```csharp title="" - /* 类 */ - class Node { - int val; - Node next; - Node(int x) { val = x; } - } - - /* 函数 */ - int function() { - // 执行某些操作... - return 0; - } - - int algorithm(int n) { // 输入数据 - const 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 { - // 执行某些操作... - 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="" - - ``` - === "Dart" ```dart title="" @@ -292,6 +269,29 @@ comments: true ``` +=== "C" + + ```c title="" + /* 函数 */ + int func() { + // 执行某些操作... + return 0; + } + + int algorithm(int n) { // 输入数据 + const int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + int c = func(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + ## 2.4.2   推算方法 空间复杂度的推算方法与时间复杂度大致相同,只需将统计对象从“操作数量”转为“使用空间大小”。 @@ -303,15 +303,14 @@ comments: true 1. **以最差输入数据为准**:当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但当 $n > 10$ 时,初始化的数组 `nums` 占用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ 。 2. **以算法运行中的峰值内存为准**:例如,程序在执行最后一行之前,占用 $O(1)$ 空间;当初始化数组 `nums` 时,程序占用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ 。 -=== "Java" +=== "Python" - ```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) - } + ```python title="" + def algorithm(n: int): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) ``` === "C++" @@ -325,14 +324,27 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="" - def algorithm(n: int): - a = 0 # O(1) - b = [0] * 10000 # O(1) - if n > 10: - nums = [0] * n # O(n) + ```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#" + + ```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) + } + } ``` === "Go" @@ -349,6 +361,18 @@ comments: true } ``` +=== "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) + } + } + ``` + === "JS" ```javascript title="" @@ -373,47 +397,6 @@ comments: true } ``` -=== "C" - - ```c title="" - void algorithm(int n) { - int a = 0; // O(1) - int b[10000]; // O(1) - if (n > 10) - int nums[n] = {0}; // O(n) - } - ``` - -=== "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="" - - ``` - === "Dart" ```dart title="" @@ -432,29 +415,44 @@ comments: true ``` +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 0; // O(1) + int b[10000]; // O(1) + if (n > 10) + int nums[n] = {0}; // O(n) + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + **在递归函数中,需要注意统计栈帧空间**。例如在以下代码中: - 函数 `loop()` 在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。 - 递归函数 `recur()` 在运行过程中会同时存在 $n$ 个未返回的 `recur()` ,从而占用 $O(n)$ 的栈帧空间。 -=== "Java" +=== "Python" - ```java title="" - int function() { - // 执行某些操作 - 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); - } + ```python title="" + def function() -> int: + # 执行某些操作 + return 0 + + def loop(n: int): + """循环 O(1)""" + for _ in range(n): + function() + + def recur(n: int) -> int: + """递归 O(n)""" + if n == 1: return + return recur(n - 1) ``` === "C++" @@ -477,22 +475,44 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="" - def function() -> int: - # 执行某些操作 - return 0 + ```java title="" + int function() { + // 执行某些操作 + 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); + } + ``` - def loop(n: int): - """循环 O(1)""" - for _ in range(n): - function() +=== "C#" - def recur(n: int) -> int: - """递归 O(n)""" - if n == 1: return - return recur(n - 1) + ```csharp title="" + int function() { + // 执行某些操作 + 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); + } ``` === "Go" @@ -519,6 +539,31 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + @discardableResult + func function() -> Int { + // 执行某些操作 + 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) + } + ``` + === "JS" ```javascript title="" @@ -559,77 +604,6 @@ comments: true } ``` -=== "C" - - ```c title="" - int func() { - // 执行某些操作 - 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); - } - ``` - -=== "C#" - - ```csharp title="" - int function() { - // 执行某些操作 - 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 { - // 执行某些操作 - 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="" - - ``` - === "Dart" ```dart title="" @@ -656,6 +630,32 @@ comments: true ``` +=== "C" + + ```c title="" + int func() { + // 执行某些操作 + 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); + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + ## 2.4.3   常见类型 设输入数据大小为 $n$ ,图 2-16 展示了常见的空间复杂度类型(从低到高排列)。 @@ -677,31 +677,26 @@ $$ 需要注意的是,在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,因此不会累积占用空间,空间复杂度仍为 $O(1)$ : -=== "Java" +=== "Python" - ```java title="space_complexity.java" - /* 函数 */ - int function() { - // 执行某些操作 - return 0; - } + ```python title="space_complexity.py" + def function() -> int: + """函数""" + # 执行某些操作 + return 0 - /* 常数阶 */ - 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(); - } - } + def constant(n: int): + """常数阶""" + # 常量、变量、对象占用 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() ``` === "C++" @@ -731,26 +726,58 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="space_complexity.py" - def function() -> int: - """函数""" - # 执行某些操作 - return 0 + ```java title="space_complexity.java" + /* 函数 */ + int function() { + // 执行某些操作 + return 0; + } - def constant(n: int): - """常数阶""" - # 常量、变量、对象占用 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() + /* 常数阶 */ + 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#" + + ```csharp title="space_complexity.cs" + /* 函数 */ + int function() { + // 执行某些操作 + return 0; + } + + /* 常数阶 */ + 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(); + } + } ``` === "Go" @@ -782,6 +809,34 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 函数 */ + @discardableResult + func function() -> Int { + // 执行某些操作 + return 0 + } + + /* 常数阶 */ + 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() + } + } + ``` + === "JS" ```javascript title="space_complexity.js" @@ -836,119 +891,6 @@ $$ } ``` -=== "C" - - ```c title="space_complexity.c" - /* 函数 */ - int func() { - // 执行某些操作 - return 0; - } - - /* 常数阶 */ - void constant(int n) { - // 常量、变量、对象占用 O(1) 空间 - const int a = 0; - int b = 0; - int nums[1000]; - ListNode *node = newListNode(0); - free(node); - // 循环中的变量占用 O(1) 空间 - for (int i = 0; i < n; i++) { - int c = 0; - } - // 循环中的函数占用 O(1) 空间 - for (int i = 0; i < n; i++) { - func(); - } - } - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 函数 */ - int function() { - // 执行某些操作 - return 0; - } - - /* 常数阶 */ - 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" - /* 函数 */ - @discardableResult - func function() -> Int { - // 执行某些操作 - return 0 - } - - /* 常数阶 */ - 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" - [class]{}-[func]{function} - - // 常数阶 - 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; - } - ``` - === "Dart" ```dart title="space_complexity.dart" @@ -1004,28 +946,79 @@ $$ } ``` +=== "C" + + ```c title="space_complexity.c" + /* 函数 */ + int func() { + // 执行某些操作 + return 0; + } + + /* 常数阶 */ + void constant(int n) { + // 常量、变量、对象占用 O(1) 空间 + const int a = 0; + int b = 0; + int nums[1000]; + ListNode *node = newListNode(0); + free(node); + // 循环中的变量占用 O(1) 空间 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (int i = 0; i < n; i++) { + func(); + } + } + ``` + +=== "Zig" + + ```zig title="space_complexity.zig" + [class]{}-[func]{function} + + // 常数阶 + 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; + } + ``` + ### 2.   线性阶 $O(n)$ 线性阶常见于元素数量与 $n$ 成正比的数组、链表、栈、队列等: -=== "Java" +=== "Python" - ```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)); - } - } + ```python title="space_complexity.py" + def linear(n: int): + """线性阶""" + # 长度为 n 的列表占用 O(n) 空间 + nums = [0] * n + # 长度为 n 的哈希表占用 O(n) 空间 + hmap = dict[int, str]() + for i in range(n): + hmap[i] = str(i) ``` === "C++" @@ -1048,17 +1041,44 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="space_complexity.py" - def linear(n: int): - """线性阶""" - # 长度为 n 的列表占用 O(n) 空间 - nums = [0] * n - # 长度为 n 的哈希表占用 O(n) 空间 - hmap = dict[int, str]() - for i in range(n): - hmap[i] = str(i) + ```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#" + + ```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()); + } + } ``` === "Go" @@ -1081,6 +1101,20 @@ $$ } ``` +=== "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)") }) + } + ``` + === "JS" ```javascript title="space_complexity.js" @@ -1121,6 +1155,47 @@ $$ } ``` +=== "Dart" + + ```dart title="space_complexity.dart" + /* 线性阶 */ + void linear(int n) { + // 长度为 n 的数组占用 O(n) 空间 + List nums = List.filled(n, 0); + // 长度为 n 的列表占用 O(n) 空间 + List nodes = []; + for (var i = 0; i < n; i++) { + nodes.add(ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + Map map = HashMap(); + for (var i = 0; i < n; i++) { + map.putIfAbsent(i, () => i.toString()); + } + } + ``` + +=== "Rust" + + ```rust title="space_complexity.rs" + /* 线性阶 */ + #[allow(unused)] + fn linear(n: i32) { + // 长度为 n 的数组占用 O(n) 空间 + let mut nums = vec![0; n as usize]; + // 长度为 n 的列表占用 O(n) 空间 + let mut nodes = Vec::new(); + for i in 0..n { + nodes.push(ListNode::new(i)) + } + // 长度为 n 的哈希表占用 O(n) 空间 + let mut map = HashMap::new(); + for i in 0..n { + map.insert(i, i.to_string()); + } + } + ``` + === "C" ```c title="space_complexity.c" @@ -1168,40 +1243,6 @@ $$ } ``` -=== "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" @@ -1229,59 +1270,17 @@ $$ } ``` -=== "Dart" - - ```dart title="space_complexity.dart" - /* 线性阶 */ - void linear(int n) { - // 长度为 n 的数组占用 O(n) 空间 - List nums = List.filled(n, 0); - // 长度为 n 的列表占用 O(n) 空间 - List nodes = []; - for (var i = 0; i < n; i++) { - nodes.add(ListNode(i)); - } - // 长度为 n 的哈希表占用 O(n) 空间 - Map map = HashMap(); - for (var i = 0; i < n; i++) { - map.putIfAbsent(i, () => i.toString()); - } - } - ``` - -=== "Rust" - - ```rust title="space_complexity.rs" - /* 线性阶 */ - #[allow(unused)] - fn linear(n: i32) { - // 长度为 n 的数组占用 O(n) 空间 - let mut nums = vec![0; n as usize]; - // 长度为 n 的列表占用 O(n) 空间 - let mut nodes = Vec::new(); - for i in 0..n { - nodes.push(ListNode::new(i)) - } - // 长度为 n 的哈希表占用 O(n) 空间 - let mut map = HashMap::new(); - for i in 0..n { - map.insert(i, i.to_string()); - } - } - ``` - 如图 2-17 所示,此函数的递归深度为 $n$ ,即同时存在 $n$ 个未返回的 `linear_recur()` 函数,使用 $O(n)$ 大小的栈帧空间: -=== "Java" +=== "Python" - ```java title="space_complexity.java" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - System.out.println("递归 n = " + n); - if (n == 1) - return; - linearRecur(n - 1); - } + ```python title="space_complexity.py" + def linear_recur(n: int): + """线性阶(递归实现)""" + print("递归 n =", n) + if n == 1: + return + linear_recur(n - 1) ``` === "C++" @@ -1296,15 +1295,27 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="space_complexity.py" - def linear_recur(n: int): - """线性阶(递归实现)""" - print("递归 n =", n) - if n == 1: - return - linear_recur(n - 1) + ```java title="space_complexity.java" + /* 线性阶(递归实现) */ + void linearRecur(int n) { + System.out.println("递归 n = " + n); + if (n == 1) + return; + linearRecur(n - 1); + } + ``` + +=== "C#" + + ```csharp title="space_complexity.cs" + /* 线性阶(递归实现) */ + void linearRecur(int n) { + Console.WriteLine("递归 n = " + n); + if (n == 1) return; + linearRecur(n - 1); + } ``` === "Go" @@ -1320,6 +1331,19 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 线性阶(递归实现) */ + func linearRecur(n: Int) { + print("递归 n = \(n)") + if n == 1 { + return + } + linearRecur(n: n - 1) + } + ``` + === "JS" ```javascript title="space_complexity.js" @@ -1342,53 +1366,6 @@ $$ } ``` -=== "C" - - ```c title="space_complexity.c" - /* 线性阶(递归实现) */ - void linearRecur(int n) { - printf("递归 n = %d\r\n", n); - if (n == 1) - return; - linearRecur(n - 1); - } - ``` - -=== "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); - } - ``` - === "Dart" ```dart title="space_complexity.dart" @@ -1411,6 +1388,29 @@ $$ } ``` +=== "C" + + ```c title="space_complexity.c" + /* 线性阶(递归实现) */ + void linearRecur(int n) { + printf("递归 n = %d\r\n", n); + if (n == 1) + return; + linearRecur(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.assets/space_complexity_recursive_linear.png)

图 2-17   递归函数产生的线性阶空间复杂度

@@ -1419,6 +1419,32 @@ $$ 平方阶常见于矩阵和图,元素数量与 $n$ 成平方关系: +=== "Python" + + ```python title="space_complexity.py" + def quadratic(n: int): + """平方阶""" + # 二维列表占用 O(n^2) 空间 + num_matrix = [[0] * n for _ in range(n)] + ``` + +=== "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); + } + } + ``` + === "Java" ```java title="space_complexity.java" @@ -1438,32 +1464,25 @@ $$ } ``` -=== "C++" +=== "C#" - ```cpp title="space_complexity.cpp" + ```csharp title="space_complexity.cs" /* 平方阶 */ void quadratic(int n) { + // 矩阵占用 O(n^2) 空间 + int[,] numMatrix = new int[n, n]; // 二维列表占用 O(n^2) 空间 - vector> numMatrix; + List> numList = new(); for (int i = 0; i < n; i++) { - vector tmp; + List tmp = new(); for (int j = 0; j < n; j++) { - tmp.push_back(0); + tmp.Add(0); } - numMatrix.push_back(tmp); + numList.Add(tmp); } } ``` -=== "Python" - - ```python title="space_complexity.py" - def quadratic(n: int): - """平方阶""" - # 二维列表占用 O(n^2) 空间 - num_matrix = [[0] * n for _ in range(n)] - ``` - === "Go" ```go title="space_complexity.go" @@ -1477,6 +1496,16 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 平方阶 */ + func quadratic(n: Int) { + // 二维列表占用 O(n^2) 空间 + let numList = Array(repeating: Array(repeating: 0, count: n), count: n) + } + ``` + === "JS" ```javascript title="space_complexity.js" @@ -1519,79 +1548,6 @@ $$ } ``` -=== "C" - - ```c title="space_complexity.c" - /* 平方阶 */ - void quadratic(int n) { - // 二维列表占用 O(n^2) 空间 - int **numMatrix = malloc(sizeof(int *) * n); - for (int i = 0; i < n; i++) { - int *tmp = malloc(sizeof(int) * n); - for (int j = 0; j < n; j++) { - tmp[j] = 0; - } - numMatrix[i] = tmp; - } - - // 内存释放 - for (int i = 0; i < n; i++) { - free(numMatrix[i]); - } - free(numMatrix); - } - ``` - -=== "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); - } - } - ``` - === "Dart" ```dart title="space_complexity.dart" @@ -1631,20 +1587,62 @@ $$ } ``` +=== "C" + + ```c title="space_complexity.c" + /* 平方阶 */ + void quadratic(int n) { + // 二维列表占用 O(n^2) 空间 + int **numMatrix = malloc(sizeof(int *) * n); + for (int i = 0; i < n; i++) { + int *tmp = malloc(sizeof(int) * n); + for (int j = 0; j < n; j++) { + tmp[j] = 0; + } + numMatrix[i] = tmp; + } + + // 内存释放 + for (int i = 0; i < n; i++) { + free(numMatrix[i]); + } + free(numMatrix); + } + ``` + +=== "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); + } + } + ``` + 如图 2-18 所示,该函数的递归深度为 $n$ ,在每个递归函数中都初始化了一个数组,长度分别为 $n$、$n-1$、$\dots$、$2$、$1$ ,平均长度为 $n / 2$ ,因此总体占用 $O(n^2)$ 空间: -=== "Java" +=== "Python" - ```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); - } + ```python title="space_complexity.py" + def quadratic_recur(n: int) -> int: + """平方阶(递归实现)""" + if n <= 0: + return 0 + # 数组 nums 长度为 n, n-1, ..., 2, 1 + nums = [0] * n + return quadratic_recur(n - 1) ``` === "C++" @@ -1660,16 +1658,30 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="space_complexity.py" - def quadratic_recur(n: int) -> int: - """平方阶(递归实现)""" - if n <= 0: - return 0 - # 数组 nums 长度为 n, n-1, ..., 2, 1 - nums = [0] * n - return quadratic_recur(n - 1) + ```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#" + + ```csharp title="space_complexity.cs" + /* 平方阶(递归实现) */ + int quadraticRecur(int n) { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); + return quadraticRecur(n - 1); + } ``` === "Go" @@ -1686,6 +1698,22 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 平方阶(递归实现) */ + @discardableResult + func quadraticRecur(n: Int) -> Int { + if n <= 0 { + return 0 + } + // 数组 nums 长度为 n, n-1, ..., 2, 1 + let nums = Array(repeating: 0, count: n) + print("递归 n = \(n) 中的 nums 长度 = \(nums.count)") + return quadraticRecur(n: n - 1) + } + ``` + === "JS" ```javascript title="space_complexity.js" @@ -1710,61 +1738,6 @@ $$ } ``` -=== "C" - - ```c title="space_complexity.c" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) { - if (n <= 0) - return 0; - int *nums = malloc(sizeof(int) * n); - printf("递归 n = %d 中的 nums 长度 = %d\r\n", n, n); - int res = quadraticRecur(n - 1); - free(nums); - return res; - } - ``` - -=== "C#" - - ```csharp title="space_complexity.cs" - /* 平方阶(递归实现) */ - int quadraticRecur(int n) { - if (n <= 0) return 0; - int[] nums = new int[n]; - Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); - return quadraticRecur(n - 1); - } - ``` - -=== "Swift" - - ```swift title="space_complexity.swift" - /* 平方阶(递归实现) */ - @discardableResult - func quadraticRecur(n: Int) -> Int { - if n <= 0 { - return 0 - } - // 数组 nums 长度为 n, n-1, ..., 2, 1 - let nums = Array(repeating: 0, count: n) - print("递归 n = \(n) 中的 nums 长度 = \(nums.count)") - 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); - } - ``` - === "Dart" ```dart title="space_complexity.dart" @@ -1790,6 +1763,33 @@ $$ } ``` +=== "C" + + ```c title="space_complexity.c" + /* 平方阶(递归实现) */ + int quadraticRecur(int n) { + if (n <= 0) + return 0; + int *nums = malloc(sizeof(int) * n); + printf("递归 n = %d 中的 nums 长度 = %d\r\n", n, n); + int res = quadraticRecur(n - 1); + free(nums); + return res; + } + ``` + +=== "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.assets/space_complexity_recursive_quadratic.png)

图 2-18   递归函数产生的平方阶空间复杂度

@@ -1798,18 +1798,17 @@ $$ 指数阶常见于二叉树。观察图 2-19 ,高度为 $n$ 的“满二叉树”的节点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间: -=== "Java" +=== "Python" - ```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; - } + ```python title="space_complexity.py" + def build_tree(n: int) -> TreeNode | None: + """指数阶(建立满二叉树)""" + if n == 0: + return None + root = TreeNode(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root ``` === "C++" @@ -1826,17 +1825,31 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="space_complexity.py" - def build_tree(n: int) -> TreeNode | None: - """指数阶(建立满二叉树)""" - if n == 0: - return None - root = TreeNode(0) - root.left = build_tree(n - 1) - root.right = build_tree(n - 1) - return root + ```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#" + + ```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; + } ``` === "Go" @@ -1854,6 +1867,21 @@ $$ } ``` +=== "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 + } + ``` + === "JS" ```javascript title="space_complexity.js" @@ -1880,62 +1908,6 @@ $$ } ``` -=== "C" - - ```c title="space_complexity.c" - /* 指数阶(建立满二叉树) */ - TreeNode *buildTree(int n) { - if (n == 0) - return NULL; - TreeNode *root = newTreeNode(0); - root->left = buildTree(n - 1); - root->right = buildTree(n - 1); - return root; - } - ``` - -=== "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; - } - ``` - === "Dart" ```dart title="space_complexity.dart" @@ -1962,6 +1934,34 @@ $$ } ``` +=== "C" + + ```c title="space_complexity.c" + /* 指数阶(建立满二叉树) */ + TreeNode *buildTree(int n) { + if (n == 0) + return NULL; + TreeNode *root = newTreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(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.assets/space_complexity_exponential.png)

图 2-19   满二叉树产生的指数阶空间复杂度

diff --git a/chapter_computational_complexity/time_complexity.md b/chapter_computational_complexity/time_complexity.md index 4345aaf18..e57b6f282 100755 --- a/chapter_computational_complexity/time_complexity.md +++ b/chapter_computational_complexity/time_complexity.md @@ -12,19 +12,17 @@ comments: true 例如在以下代码中,输入数据大小为 $n$ : -=== "Java" +=== "Python" - ```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 - } - } + ```python title="" + # 在某运行平台下 + def algorithm(n: int): + 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 ``` === "C++" @@ -42,17 +40,34 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="" - # 在某运行平台下 - def algorithm(n: int): - 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 + ```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#" + + ```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 + } + } ``` === "Go" @@ -70,6 +85,21 @@ comments: true } ``` +=== "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 + } + } + ``` + === "JS" ```javascript title="" @@ -100,57 +130,6 @@ comments: true } ``` -=== "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="" - - ``` - === "Dart" ```dart title="" @@ -181,6 +160,27 @@ comments: true } ``` +=== "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 + } + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + 根据以上方法,可以得到算法运行时间为 $6n + 12$ ns : $$ @@ -195,25 +195,20 @@ $$ “时间增长趋势”这个概念比较抽象,我们通过一个例子来加以理解。假设输入数据大小为 $n$ ,给定三个算法函数 `A`、`B` 和 `C` : -=== "Java" +=== "Python" - ```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); - } - } + ```python title="" + # 算法 A 的时间复杂度:常数阶 + def algorithm_A(n: int): + print(0) + # 算法 B 的时间复杂度:线性阶 + def algorithm_B(n: int): + for _ in range(n): + print(0) + # 算法 C 的时间复杂度:常数阶 + def algorithm_C(n: int): + for _ in range(1000000): + print(0) ``` === "C++" @@ -237,20 +232,46 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="" - # 算法 A 的时间复杂度:常数阶 - def algorithm_A(n: int): - print(0) - # 算法 B 的时间复杂度:线性阶 - def algorithm_B(n: int): - for _ in range(n): - print(0) - # 算法 C 的时间复杂度:常数阶 - def algorithm_C(n: int): - for _ in range(1000000): - print(0) + ```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#" + + ```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); + } + } ``` === "Go" @@ -274,6 +295,29 @@ $$ } ``` +=== "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) + } + } + ``` + === "JS" ```javascript title="" @@ -317,77 +361,6 @@ $$ } ``` -=== "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="" - - ``` - === "Dart" ```dart title="" @@ -430,6 +403,33 @@ $$ } ``` +=== "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); + } + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + 图 2-7 展示了以上三个算法函数的时间复杂度。 - 算法 `A` 只有 $1$ 个打印操作,算法运行时间不随着 $n$ 增大而增长。我们称此算法的时间复杂度为“常数阶”。 @@ -450,18 +450,16 @@ $$ 给定一个输入大小为 $n$ 的函数: -=== "Java" +=== "Python" - ```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 - } - } + ```python title="" + def algorithm(n: int): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # 循环 n 次 + for i in range(n): # +1 + print(0) # +1 ``` === "C++" @@ -478,16 +476,32 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="" - def algorithm(n: int): - a = 1 # +1 - a = a + 1 # +1 - a = a * 2 # +1 - # 循环 n 次 - for i in range(n): # +1 - print(0) # +1 + ```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#" + + ```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 + } + } ``` === "Go" @@ -504,6 +518,20 @@ $$ } ``` +=== "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 + } + } + ``` + === "JS" ```javascript title="" @@ -532,54 +560,6 @@ $$ } ``` -=== "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="" - - ``` - === "Dart" ```dart title="" @@ -609,6 +589,26 @@ $$ } ``` +=== "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 + } + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + 设算法的操作数量是一个关于输入数据大小 $n$ 的函数,记为 $T(n)$ ,则以上函数的的操作数量为: $$ @@ -647,23 +647,19 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 给定一个函数,我们可以用上述技巧来统计操作数量。 -=== "Java" +=== "Python" - ```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); - } - } - } + ```python title="" + def algorithm(n: int): + 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) ``` === "C++" @@ -685,19 +681,42 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 } ``` -=== "Python" +=== "Java" - ```python title="" - def algorithm(n: int): - 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) + ```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#" + + ```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); + } + } + } ``` === "Go" @@ -719,6 +738,25 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 } ``` +=== "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) + } + } + } + ``` + === "JS" ```javascript title="" @@ -757,69 +795,6 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 } ``` -=== "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="" - - ``` - === "Dart" ```dart title="" @@ -860,6 +835,31 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 } ``` +=== "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); + } + } + } + ``` + +=== "Zig" + + ```zig title="" + + ``` + 以下公式展示了使用上述技巧前后的统计结果,两者推出的时间复杂度都为 $O(n^2)$ 。 $$ @@ -911,17 +911,16 @@ $$ 在以下函数中,尽管操作数量 `size` 可能很大,但由于其与输入数据大小 $n$ 无关,因此时间复杂度仍为 $O(1)$ : -=== "Java" +=== "Python" - ```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; - } + ```python title="time_complexity.py" + def constant(n: int) -> int: + """常数阶""" + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count ``` === "C++" @@ -937,16 +936,30 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def constant(n: int) -> int: - """常数阶""" - count = 0 - size = 100000 - for _ in range(size): - count += 1 - return count + ```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#" + + ```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; + } ``` === "Go" @@ -963,6 +976,20 @@ $$ } ``` +=== "Swift" + + ```swift title="time_complexity.swift" + /* 常数阶 */ + func constant(n: Int) -> Int { + var count = 0 + let size = 100_000 + for _ in 0 ..< size { + count += 1 + } + return count + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -987,64 +1014,6 @@ $$ } ``` -=== "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 = 100_000 - 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: + """线性阶""" + count = 0 + for _ in range(n): + count += 1 + return count ``` === "C++" @@ -1102,15 +1101,28 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def linear(n: int) -> int: - """线性阶""" - count = 0 - for _ in range(n): - count += 1 - return count + ```java title="time_complexity.java" + /* 线性阶 */ + int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + ``` + +=== "C#" + + ```csharp title="time_complexity.cs" + /* 线性阶 */ + int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } ``` === "Go" @@ -1126,6 +1138,19 @@ $$ } ``` +=== "Swift" + + ```swift title="time_complexity.swift" + /* 线性阶 */ + func linear(n: Int) -> Int { + var count = 0 + for _ in 0 ..< n { + count += 1 + } + return count + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -1148,58 +1173,6 @@ $$ } ``` -=== "C" - - ```c title="time_complexity.c" - /* 线性阶 */ - int linear(int n) { - int count = 0; - for (int i = 0; i < n; i++) { - count++; - } - return count; - } - ``` - -=== "C#" - - ```csharp title="time_complexity.cs" - /* 线性阶 */ - int linear(int n) { - int count = 0; - for (int i = 0; i < n; i++) - count++; - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 线性阶 */ - func linear(n: Int) -> 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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -1226,22 +1199,47 @@ $$ } ``` -遍历数组和遍历链表等操作的时间复杂度均为 $O(n)$ ,其中 $n$ 为数组或链表的长度: +=== "C" -=== "Java" - - ```java title="time_complexity.java" - /* 线性阶(遍历数组) */ - int arrayTraversal(int[] nums) { + ```c title="time_complexity.c" + /* 线性阶 */ + int linear(int n) { int count = 0; - // 循环次数与数组长度成正比 - for (int num : nums) { + for (int i = 0; i < n; i++) { count++; } 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$ 为数组或链表的长度: + +=== "Python" + + ```python title="time_complexity.py" + def array_traversal(nums: list[int]) -> int: + """线性阶(遍历数组)""" + count = 0 + # 循环次数与数组长度成正比 + for num in nums: + count += 1 + return count + ``` + === "C++" ```cpp title="time_complexity.cpp" @@ -1256,16 +1254,32 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def array_traversal(nums: list[int]) -> int: - """线性阶(遍历数组)""" - count = 0 - # 循环次数与数组长度成正比 - for num in nums: - count += 1 - return count + ```java title="time_complexity.java" + /* 线性阶(遍历数组) */ + int arrayTraversal(int[] nums) { + int count = 0; + // 循环次数与数组长度成正比 + for (int num : nums) { + count++; + } + return count; + } + ``` + +=== "C#" + + ```csharp title="time_complexity.cs" + /* 线性阶(遍历数组) */ + int arrayTraversal(int[] nums) { + int count = 0; + // 循环次数与数组长度成正比 + foreach (int num in nums) { + count++; + } + return count; + } ``` === "Go" @@ -1282,6 +1296,20 @@ $$ } ``` +=== "Swift" + + ```swift title="time_complexity.swift" + /* 线性阶(遍历数组) */ + func arrayTraversal(nums: [Int]) -> Int { + var count = 0 + // 循环次数与数组长度成正比 + for _ in nums { + count += 1 + } + return count + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -1310,62 +1338,6 @@ $$ } ``` -=== "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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -1394,26 +1366,51 @@ $$ } ``` +=== "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; + } + ``` + +=== "Zig" + + ```zig title="time_complexity.zig" + // 线性阶(遍历数组) + fn arrayTraversal(nums: []i32) i32 { + var count: i32 = 0; + // 循环次数与数组长度成正比 + for (nums) |_| { + count += 1; + } + return count; + } + ``` + 值得注意的是,**输入数据大小 $n$ 需根据输入数据的类型来具体确定**。比如在第一个示例中,变量 $n$ 为输入数据大小;在第二个示例中,数组长度 $n$ 为数据大小。 ### 3.   平方阶 $O(n^2)$ 平方阶的操作数量相对于输入数据大小 $n$ 以平方级别增长。平方阶通常出现在嵌套循环中,外层循环和内层循环都为 $O(n)$ ,因此总体为 $O(n^2)$ : -=== "Java" +=== "Python" - ```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; - } + ```python title="time_complexity.py" + def quadratic(n: int) -> int: + """平方阶""" + count = 0 + # 循环次数与数组长度成平方关系 + for i in range(n): + for j in range(n): + count += 1 + return count ``` === "C++" @@ -1432,17 +1429,36 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def quadratic(n: int) -> int: - """平方阶""" - count = 0 - # 循环次数与数组长度成平方关系 - for i in range(n): - for j in range(n): - count += 1 - return count + ```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#" + + ```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; + } ``` === "Go" @@ -1461,6 +1477,22 @@ $$ } ``` +=== "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 + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -1493,72 +1525,6 @@ $$ } ``` -=== "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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -1591,6 +1557,40 @@ $$ } ``` +=== "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; + } + ``` + +=== "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; + } + ``` + 图 2-10 对比了常数阶、线性阶和平方阶三种时间复杂度。 ![常数阶、线性阶和平方阶的时间复杂度](time_complexity.assets/time_complexity_constant_linear_quadratic.png) @@ -1599,27 +1599,23 @@ $$ 以冒泡排序为例,外层循环执行 $n - 1$ 次,内层循环执行 $n-1$、$n-2$、$\dots$、$2$、$1$ 次,平均为 $n / 2$ 次,因此时间复杂度为 $O((n - 1) n / 2) = O(n^2)$ 。 -=== "Java" +=== "Python" - ```java title="time_complexity.java" - /* 平方阶(冒泡排序) */ - int bubbleSort(int[] nums) { - int count = 0; // 计数器 - // 外循环:未排序区间为 [0, i] - for (int i = nums.length - 1; i > 0; 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 title="time_complexity.py" + def bubble_sort(nums: list[int]) -> int: + """平方阶(冒泡排序)""" + count = 0 # 计数器 + # 外循环:未排序区间为 [0, i] + for i in range(len(nums) - 1, 0, -1): + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交换 nums[j] 与 nums[j + 1] + tmp: int = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交换包含 3 个单元操作 + return count ``` === "C++" @@ -1645,23 +1641,48 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def bubble_sort(nums: list[int]) -> int: - """平方阶(冒泡排序)""" - count = 0 # 计数器 - # 外循环:未排序区间为 [0, i] - for i in range(len(nums) - 1, 0, -1): - # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - for j in range(i): - if nums[j] > nums[j + 1]: - # 交换 nums[j] 与 nums[j + 1] - tmp: int = nums[j] - nums[j] = nums[j + 1] - nums[j + 1] = tmp - count += 3 # 元素交换包含 3 个单元操作 - return count + ```java title="time_complexity.java" + /* 平方阶(冒泡排序) */ + int bubbleSort(int[] nums) { + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] + for (int i = nums.length - 1; i > 0; 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; // 计数器 + // 外循环:未排序区间为 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + count += 3; // 元素交换包含 3 个单元操作 + } + } + } + return count; + } ``` === "Go" @@ -1687,6 +1708,29 @@ $$ } ``` +=== "Swift" + + ```swift title="time_complexity.swift" + /* 平方阶(冒泡排序) */ + func bubbleSort(nums: inout [Int]) -> Int { + var count = 0 // 计数器 + // 外循环:未排序区间为 [0, i] + for i in stride(from: nums.count - 1, to: 0, by: -1) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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 + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -1733,98 +1777,6 @@ $$ } ``` -=== "C" - - ```c title="time_complexity.c" - /* 平方阶(冒泡排序) */ - int bubbleSort(int *nums, int n) { - int count = 0; // 计数器 - // 外循环:未排序区间为 [0, i] - for (int i = n - 1; i > 0; 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; // 计数器 - // 外循环:未排序区间为 [0, i] - for (int i = nums.Length - 1; i > 0; i--) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - for (int j = 0; j < i; j++) { - if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 与 nums[j + 1] - (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); - count += 3; // 元素交换包含 3 个单元操作 - } - } - } - return count; - } - ``` - -=== "Swift" - - ```swift title="time_complexity.swift" - /* 平方阶(冒泡排序) */ - func bubbleSort(nums: inout [Int]) -> Int { - var count = 0 // 计数器 - // 外循环:未排序区间为 [0, i] - for i in stride(from: nums.count - 1, to: 0, by: -1) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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; // 计数器 - // 外循环:未排序区间为 [0, i] - var i: i32 = @as(i32, @intCast(nums.len)) - 1; - while (i > 0) : (i -= 1) { - var j: usize = 0; - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -1871,28 +1823,74 @@ $$ } ``` +=== "C" + + ```c title="time_complexity.c" + /* 平方阶(冒泡排序) */ + int bubbleSort(int *nums, int n) { + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] + for (int i = n - 1; i > 0; 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; + } + ``` + +=== "Zig" + + ```zig title="time_complexity.zig" + // 平方阶(冒泡排序) + fn bubbleSort(nums: []i32) i32 { + var count: i32 = 0; // 计数器 + // 外循环:未排序区间为 [0, i] + var i: i32 = @as(i32, @intCast(nums.len)) - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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; + } + ``` + ### 4.   指数阶 $O(2^n)$ 生物学的“细胞分裂”是指数阶增长的典型例子:初始状态为 $1$ 个细胞,分裂一轮后变为 $2$ 个,分裂两轮后变为 $4$ 个,以此类推,分裂 $n$ 轮后有 $2^n$ 个细胞。 图 2-11 和以下代码模拟了细胞分裂的过程,时间复杂度为 $O(2^n)$ 。 -=== "Java" +=== "Python" - ```java title="time_complexity.java" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0, base = 1; - // 细胞每轮一分为二,形成数列 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 title="time_complexity.py" + def exponential(n: int) -> int: + """指数阶(循环实现)""" + count = 0 + base = 1 + # 细胞每轮一分为二,形成数列 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 ``` === "C++" @@ -1913,20 +1911,40 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def exponential(n: int) -> int: - """指数阶(循环实现)""" - count = 0 - base = 1 - # 细胞每轮一分为二,形成数列 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 + ```java title="time_complexity.java" + /* 指数阶(循环实现) */ + int exponential(int n) { + int count = 0, base = 1; + // 细胞每轮一分为二,形成数列 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#" + + ```csharp title="time_complexity.cs" + /* 指数阶(循环实现) */ + int exponential(int n) { + int count = 0, bas = 1; + // 细胞每轮一分为二,形成数列 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; + } ``` === "Go" @@ -1947,6 +1965,25 @@ $$ } ``` +=== "Swift" + + ```swift title="time_complexity.swift" + /* 指数阶(循环实现) */ + func exponential(n: Int) -> Int { + var count = 0 + var base = 1 + // 细胞每轮一分为二,形成数列 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 + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -1985,83 +2022,6 @@ $$ } ``` -=== "C" - - ```c title="time_complexity.c" - /* 指数阶(循环实现) */ - int exponential(int n) { - int count = 0; - int bas = 1; - // 细胞每轮一分为二,形成数列 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; - // 细胞每轮一分为二,形成数列 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 - // 细胞每轮一分为二,形成数列 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; - // 细胞每轮一分为二,形成数列 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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -2099,21 +2059,60 @@ $$ } ``` +=== "C" + + ```c title="time_complexity.c" + /* 指数阶(循环实现) */ + int exponential(int n) { + int count = 0; + int bas = 1; + // 细胞每轮一分为二,形成数列 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; + } + ``` + +=== "Zig" + + ```zig title="time_complexity.zig" + // 指数阶(循环实现) + fn exponential(n: i32) i32 { + var count: i32 = 0; + var bas: i32 = 1; + var i: i32 = 0; + // 细胞每轮一分为二,形成数列 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.assets/time_complexity_exponential.png)

图 2-11   指数阶的时间复杂度

在实际算法中,指数阶常出现于递归函数中。例如在以下代码中,其递归地一分为二,经过 $n$ 次分裂后停止: -=== "Java" +=== "Python" - ```java title="time_complexity.java" - /* 指数阶(递归实现) */ - int expRecur(int n) { - if (n == 1) - return 1; - return expRecur(n - 1) + expRecur(n - 1) + 1; - } + ```python title="time_complexity.py" + def exp_recur(n: int) -> int: + """指数阶(递归实现)""" + if n == 1: + return 1 + return exp_recur(n - 1) + exp_recur(n - 1) + 1 ``` === "C++" @@ -2127,14 +2126,25 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def exp_recur(n: int) -> int: - """指数阶(递归实现)""" - if n == 1: - return 1 - return exp_recur(n - 1) + exp_recur(n - 1) + 1 + ```java title="time_complexity.java" + /* 指数阶(递归实现) */ + 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; + } ``` === "Go" @@ -2149,6 +2159,18 @@ $$ } ``` +=== "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 + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -2169,49 +2191,6 @@ $$ } ``` -=== "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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -2234,6 +2213,27 @@ $$ } ``` +=== "C" + + ```c title="time_complexity.c" + /* 指数阶(递归实现) */ + int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(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; + } + ``` + 指数阶增长非常迅速,在穷举法(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心等算法来解决。 ### 5.   对数阶 $O(\log n)$ @@ -2242,18 +2242,16 @@ $$ 图 2-12 和以下代码模拟了“每轮缩减到一半”的过程,时间复杂度为 $O(\log_2 n)$ ,简记为 $O(\log n)$ 。 -=== "Java" +=== "Python" - ```java title="time_complexity.java" - /* 对数阶(循环实现) */ - int logarithmic(float n) { - int count = 0; - while (n > 1) { - n = n / 2; - count++; - } - return count; - } + ```python title="time_complexity.py" + def logarithmic(n: float) -> int: + """对数阶(循环实现)""" + count = 0 + while n > 1: + n = n / 2 + count += 1 + return count ``` === "C++" @@ -2270,16 +2268,32 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def logarithmic(n: float) -> int: - """对数阶(循环实现)""" - count = 0 - while n > 1: - n = n / 2 - count += 1 - return count + ```java title="time_complexity.java" + /* 对数阶(循环实现) */ + 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; + } ``` === "Go" @@ -2296,6 +2310,21 @@ $$ } ``` +=== "Swift" + + ```swift title="time_complexity.swift" + /* 对数阶(循环实现) */ + func logarithmic(n: Double) -> Int { + var count = 0 + var n = n + while n > 1 { + n = n / 2 + count += 1 + } + return count + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -2324,65 +2353,6 @@ $$ } ``` -=== "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: Double) -> 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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -2411,21 +2381,50 @@ $$ } ``` +=== "C" + + ```c title="time_complexity.c" + /* 对数阶(循环实现) */ + int logarithmic(float n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + 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.assets/time_complexity_logarithmic.png)

图 2-12   对数阶的时间复杂度

与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一个高度为 $\log_2 n$ 的递归树: -=== "Java" +=== "Python" - ```java title="time_complexity.java" - /* 对数阶(递归实现) */ - int logRecur(float n) { - if (n <= 1) - return 0; - return logRecur(n / 2) + 1; - } + ```python title="time_complexity.py" + def log_recur(n: float) -> int: + """对数阶(递归实现)""" + if n <= 1: + return 0 + return log_recur(n / 2) + 1 ``` === "C++" @@ -2439,51 +2438,9 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def log_recur(n: float) -> int: - """对数阶(递归实现)""" - 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 - } - ``` - -=== "JS" - - ```javascript title="time_complexity.js" - /* 对数阶(递归实现) */ - function logRecur(n) { - if (n <= 1) return 0; - return logRecur(n / 2) + 1; - } - ``` - -=== "TS" - - ```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" + ```java title="time_complexity.java" /* 对数阶(递归实现) */ int logRecur(float n) { if (n <= 1) @@ -2502,6 +2459,18 @@ $$ } ``` +=== "Go" + + ```go title="time_complexity.go" + /* 对数阶(递归实现)*/ + func logRecur(n float64) int { + if n <= 1 { + return 0 + } + return logRecur(n/2) + 1 + } + ``` + === "Swift" ```swift title="time_complexity.swift" @@ -2514,11 +2483,21 @@ $$ } ``` -=== "Zig" +=== "JS" - ```zig title="time_complexity.zig" - // 对数阶(递归实现) - fn logRecur(n: f32) i32 { + ```javascript title="time_complexity.js" + /* 对数阶(递归实现) */ + function logRecur(n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } + ``` + +=== "TS" + + ```typescript title="time_complexity.ts" + /* 对数阶(递归实现) */ + function logRecur(n: number): number { if (n <= 1) return 0; return logRecur(n / 2) + 1; } @@ -2546,6 +2525,27 @@ $$ } ``` +=== "C" + + ```c title="time_complexity.c" + /* 对数阶(递归实现) */ + int logRecur(float n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; + } + ``` + +=== "Zig" + + ```zig title="time_complexity.zig" + // 对数阶(递归实现) + fn logRecur(n: f32) i32 { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } + ``` + 对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。 !!! tip "$O(\log n)$ 的底数是多少?" @@ -2562,20 +2562,17 @@ $$ 线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 $O(\log n)$ 和 $O(n)$ 。相关代码如下: -=== "Java" +=== "Python" - ```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; - } + ```python title="time_complexity.py" + def linear_log_recur(n: float) -> int: + """线性对数阶""" + if n <= 1: + return 1 + count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2) + for _ in range(n): + count += 1 + return count ``` === "C++" @@ -2593,17 +2590,35 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def linear_log_recur(n: float) -> int: - """线性对数阶""" - if n <= 1: - return 1 - count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2) - for _ in range(n): - count += 1 - return count + ```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#" + + ```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; + } ``` === "Go" @@ -2623,6 +2638,22 @@ $$ } ``` +=== "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 stride(from: 0, to: n, by: 1) { + count += 1 + } + return count + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -2651,68 +2682,6 @@ $$ } ``` -=== "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 stride(from: 0, to: n, by: 1) { - 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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -2744,6 +2713,37 @@ $$ } ``` +=== "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; + } + ``` + +=== "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; + } + ``` + 图 2-13 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。 ![线性对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic_linear.png) @@ -2762,20 +2762,18 @@ $$ 阶乘通常使用递归实现。如图 2-14 和以下代码所示,第一层分裂出 $n$ 个,第二层分裂出 $n - 1$ 个,以此类推,直至第 $n$ 层时停止分裂: -=== "Java" +=== "Python" - ```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; - } + ```python title="time_complexity.py" + def factorial_recur(n: int) -> int: + """阶乘阶(递归实现)""" + if n == 0: + return 1 + count = 0 + # 从 1 个分裂出 n 个 + for _ in range(n): + count += factorial_recur(n - 1) + return count ``` === "C++" @@ -2794,18 +2792,35 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="time_complexity.py" - def factorial_recur(n: int) -> int: - """阶乘阶(递归实现)""" - if n == 0: - return 1 - count = 0 - # 从 1 个分裂出 n 个 - for _ in range(n): - count += factorial_recur(n - 1) - return count + ```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#" + + ```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; + } ``` === "Go" @@ -2825,6 +2840,23 @@ $$ } ``` +=== "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 + } + ``` + === "JS" ```javascript title="time_complexity.js" @@ -2855,69 +2887,6 @@ $$ } ``` -=== "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; - } - ``` - === "Dart" ```dart title="time_complexity.dart" @@ -2950,6 +2919,37 @@ $$ } ``` +=== "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; + } + ``` + +=== "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.assets/time_complexity_factorial.png)

图 2-14   阶乘阶的时间复杂度

@@ -2965,6 +2965,56 @@ $$ “最差时间复杂度”对应函数渐近上界,使用大 $O$ 记号表示。相应地,“最佳时间复杂度”对应函数渐近下界,用 $\Omega$ 记号表示: +=== "Python" + + ```python title="worst_best_time_complexity.py" + def random_numbers(n: int) -> list[int]: + """生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱""" + # 生成数组 nums =: 1, 2, 3, ..., n + nums = [i for i in range(1, n + 1)] + # 随机打乱数组元素 + random.shuffle(nums) + return nums + + def find_one(nums: list[int]) -> int: + """查找数组 nums 中数字 1 所在索引""" + for i in range(len(nums)): + # 当元素 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; + } + ``` + === "Java" ```java title="worst_best_time_complexity.java" @@ -2997,26 +3047,31 @@ $$ } ``` -=== "C++" +=== "C#" - ```cpp title="worst_best_time_complexity.cpp" + ```csharp title="worst_best_time_complexity.cs" /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - vector randomNumbers(int n) { - vector nums(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; } - // 使用系统时间生成随机种子 - unsigned seed = chrono::system_clock::now().time_since_epoch().count(); + // 随机打乱数组元素 - shuffle(nums.begin(), nums.end(), default_random_engine(seed)); + 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(vector &nums) { - for (int i = 0; i < nums.size(); i++) { + int findOne(int[] nums) { + for (int i = 0; i < nums.Length; i++) { // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) if (nums[i] == 1) @@ -3026,27 +3081,6 @@ $$ } ``` -=== "Python" - - ```python title="worst_best_time_complexity.py" - def random_numbers(n: int) -> list[int]: - """生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱""" - # 生成数组 nums =: 1, 2, 3, ..., n - nums = [i for i in range(1, n + 1)] - # 随机打乱数组元素 - random.shuffle(nums) - return nums - - def find_one(nums: list[int]) -> int: - """查找数组 nums 中数字 1 所在索引""" - 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" @@ -3077,6 +3111,31 @@ $$ } ``` +=== "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 + } + ``` + === "JS" ```javascript title="worst_best_time_complexity.js" @@ -3143,125 +3202,6 @@ $$ } ``` -=== "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; - } - ``` - === "Dart" ```dart title="worst_best_time_complexity.dart" @@ -3315,6 +3255,66 @@ $$ } ``` +=== "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; + } + ``` + +=== "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; + } + ``` + 值得说明的是,我们在实际中很少使用最佳时间复杂度,因为通常只有在很小概率下才能达到,可能会带来一定的误导性。**而最差时间复杂度更为实用,因为它给出了一个效率安全值**,让我们可以放心地使用算法。 从上述示例可以看出,最差或最佳时间复杂度只出现于“特殊的数据分布”,这些情况的出现概率可能很小,并不能真实地反映算法运行效率。相比之下,**平均时间复杂度可以体现算法在随机输入数据下的运行效率**,用 $\Theta$ 记号来表示。 diff --git a/chapter_data_structure/basic_data_types.md b/chapter_data_structure/basic_data_types.md index 94e0bcc56..914c4e13f 100644 --- a/chapter_data_structure/basic_data_types.md +++ b/chapter_data_structure/basic_data_types.md @@ -51,26 +51,6 @@ comments: true 换句话说,**基本数据类型提供了数据的“内容类型”,而数据结构提供了数据的“组织方式”**。例如以下代码,我们用相同的数据结构(数组)来存储与表示不同的基本数据类型,包括 `int`、`float`、`char`、`bool` 等。 -=== "Java" - - ```java title="" - // 使用多种基本数据类型来初始化数组 - int[] numbers = new int[5]; - float[] decimals = new float[5]; - char[] characters = new char[5]; - boolean[] bools = new boolean[5]; - ``` - -=== "C++" - - ```cpp title="" - // 使用多种基本数据类型来初始化数组 - int numbers[5]; - float decimals[5]; - char characters[5]; - bool bools[5]; - ``` - === "Python" ```python title="" @@ -84,6 +64,36 @@ comments: true data = [0, 0.0, 'a', False, ListNode(0)] ``` +=== "C++" + + ```cpp title="" + // 使用多种基本数据类型来初始化数组 + int numbers[5]; + float decimals[5]; + char characters[5]; + bool bools[5]; + ``` + +=== "Java" + + ```java title="" + // 使用多种基本数据类型来初始化数组 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + boolean[] bools = new boolean[5]; + ``` + +=== "C#" + + ```csharp title="" + // 使用多种基本数据类型来初始化数组 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + bool[] bools = new bool[5]; + ``` + === "Go" ```go title="" @@ -94,6 +104,16 @@ comments: true var bools = [5]bool{} ``` +=== "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 bools = Array(repeating: Bool(), count: 5) + ``` + === "JS" ```javascript title="" @@ -110,42 +130,6 @@ comments: true const bools: boolean[] = []; ``` -=== "C" - - ```c title="" - // 使用多种基本数据类型来初始化数组 - int numbers[10]; - float decimals[10]; - char characters[10]; - bool bools[10]; - ``` - -=== "C#" - - ```csharp title="" - // 使用多种基本数据类型来初始化数组 - int[] numbers = new int[5]; - float[] decimals = new float[5]; - char[] characters = new char[5]; - bool[] bools = 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 bools = Array(repeating: Bool(), count: 5) - ``` - -=== "Zig" - - ```zig title="" - - ``` - === "Dart" ```dart title="" @@ -161,3 +145,19 @@ comments: true ```rust title="" ``` + +=== "C" + + ```c title="" + // 使用多种基本数据类型来初始化数组 + int numbers[10]; + float decimals[10]; + char characters[10]; + bool bools[10]; + ``` + +=== "Zig" + + ```zig title="" + + ``` diff --git a/chapter_divide_and_conquer/binary_search_recur.md b/chapter_divide_and_conquer/binary_search_recur.md index 1fe2eca57..4578c61b3 100644 --- a/chapter_divide_and_conquer/binary_search_recur.md +++ b/chapter_divide_and_conquer/binary_search_recur.md @@ -47,35 +47,31 @@ status: new 在实现代码中,我们声明一个递归函数 `dfs()` 来求解问题 $f(i, j)$ 。 -=== "Java" +=== "Python" - ```java title="binary_search_recur.java" - /* 二分查找:问题 f(i, j) */ - int dfs(int[] nums, int target, int i, int j) { - // 若区间为空,代表无目标元素,则返回 -1 - if (i > j) { - return -1; - } - // 计算中点索引 m - int m = (i + j) / 2; - if (nums[m] < target) { - // 递归子问题 f(m+1, j) - return dfs(nums, target, m + 1, j); - } else if (nums[m] > target) { - // 递归子问题 f(i, m-1) - return dfs(nums, target, i, m - 1); - } else { - // 找到目标元素,返回其索引 - return m; - } - } + ```python title="binary_search_recur.py" + def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """二分查找:问题 f(i, j)""" + # 若区间为空,代表无目标元素,则返回 -1 + if i > j: + return -1 + # 计算中点索引 m + m = (i + j) // 2 + if nums[m] < target: + # 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # 找到目标元素,返回其索引 + return m - /* 二分查找 */ - int binarySearch(int[] nums, int target) { - int n = nums.length; - // 求解问题 f(0, n-1) - return dfs(nums, target, 0, n - 1); - } + def binary_search(nums: list[int], target: int) -> int: + """二分查找""" + n = len(nums) + # 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1) ``` === "C++" @@ -109,31 +105,66 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="binary_search_recur.py" - def dfs(nums: list[int], target: int, i: int, j: int) -> int: - """二分查找:问题 f(i, j)""" - # 若区间为空,代表无目标元素,则返回 -1 - if i > j: - return -1 - # 计算中点索引 m - m = (i + j) // 2 - if nums[m] < target: - # 递归子问题 f(m+1, j) - return dfs(nums, target, m + 1, j) - elif nums[m] > target: - # 递归子问题 f(i, m-1) - return dfs(nums, target, i, m - 1) - else: - # 找到目标元素,返回其索引 - return m + ```java title="binary_search_recur.java" + /* 二分查找:问题 f(i, j) */ + int dfs(int[] nums, int target, int i, int j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } + } - def binary_search(nums: list[int], target: int) -> int: - """二分查找""" - n = len(nums) - # 求解问题 f(0, n-1) - return dfs(nums, target, 0, n - 1) + /* 二分查找 */ + int binarySearch(int[] nums, int target) { + int n = nums.length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); + } + ``` + +=== "C#" + + ```csharp title="binary_search_recur.cs" + /* 二分查找:问题 f(i, j) */ + int dfs(int[] nums, int target, int i, int j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } + } + + /* 二分查找 */ + int binarySearch(int[] nums, int target) { + int n = nums.Length; + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); + } ``` === "Go" @@ -169,6 +200,37 @@ status: new } ``` +=== "Swift" + + ```swift title="binary_search_recur.swift" + /* 二分查找:问题 f(i, j) */ + func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { + // 若区间为空,代表无目标元素,则返回 -1 + if i > j { + return -1 + } + // 计算中点索引 m + let m = (i + j) / 2 + if nums[m] < target { + // 递归子问题 f(m+1, j) + return dfs(nums: nums, target: target, i: m + 1, j: j) + } else if nums[m] > target { + // 递归子问题 f(i, m-1) + return dfs(nums: nums, target: target, i: i, j: m - 1) + } else { + // 找到目标元素,返回其索引 + return m + } + } + + /* 二分查找 */ + func binarySearch(nums: [Int], target: Int) -> Int { + let n = nums.count + // 求解问题 f(0, n-1) + return dfs(nums: nums, target: target, i: 0, j: n - 1) + } + ``` + === "JS" ```javascript title="binary_search_recur.js" @@ -231,84 +293,6 @@ status: new } ``` -=== "C" - - ```c title="binary_search_recur.c" - [class]{}-[func]{dfs} - - [class]{}-[func]{binarySearch} - ``` - -=== "C#" - - ```csharp title="binary_search_recur.cs" - /* 二分查找:问题 f(i, j) */ - int dfs(int[] nums, int target, int i, int j) { - // 若区间为空,代表无目标元素,则返回 -1 - if (i > j) { - return -1; - } - // 计算中点索引 m - int m = (i + j) / 2; - if (nums[m] < target) { - // 递归子问题 f(m+1, j) - return dfs(nums, target, m + 1, j); - } else if (nums[m] > target) { - // 递归子问题 f(i, m-1) - return dfs(nums, target, i, m - 1); - } else { - // 找到目标元素,返回其索引 - return m; - } - } - - /* 二分查找 */ - int binarySearch(int[] nums, int target) { - int n = nums.Length; - // 求解问题 f(0, n-1) - return dfs(nums, target, 0, n - 1); - } - ``` - -=== "Swift" - - ```swift title="binary_search_recur.swift" - /* 二分查找:问题 f(i, j) */ - func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { - // 若区间为空,代表无目标元素,则返回 -1 - if i > j { - return -1 - } - // 计算中点索引 m - let m = (i + j) / 2 - if nums[m] < target { - // 递归子问题 f(m+1, j) - return dfs(nums: nums, target: target, i: m + 1, j: j) - } else if nums[m] > target { - // 递归子问题 f(i, m-1) - return dfs(nums: nums, target: target, i: i, j: m - 1) - } else { - // 找到目标元素,返回其索引 - return m - } - } - - /* 二分查找 */ - func binarySearch(nums: [Int], target: Int) -> Int { - let n = nums.count - // 求解问题 f(0, n-1) - return dfs(nums: nums, target: target, i: 0, j: n - 1) - } - ``` - -=== "Zig" - - ```zig title="binary_search_recur.zig" - [class]{}-[func]{dfs} - - [class]{}-[func]{binarySearch} - ``` - === "Dart" ```dart title="binary_search_recur.dart" @@ -367,3 +351,19 @@ status: new dfs(nums, target, 0, n - 1) } ``` + +=== "C" + + ```c title="binary_search_recur.c" + [class]{}-[func]{dfs} + + [class]{}-[func]{binarySearch} + ``` + +=== "Zig" + + ```zig title="binary_search_recur.zig" + [class]{}-[func]{dfs} + + [class]{}-[func]{binarySearch} + ``` diff --git a/chapter_divide_and_conquer/build_binary_tree_problem.md b/chapter_divide_and_conquer/build_binary_tree_problem.md index be0f0599a..d685b146c 100644 --- a/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -72,70 +72,6 @@ status: new 为了提升查询 $m$ 的效率,我们借助一个哈希表 `hmap` 来存储数组 `inorder` 中元素到索引的映射。 -=== "Java" - - ```java title="build_tree.java" - /* 构建二叉树:分治 */ - TreeNode dfs(int[] preorder, int[] inorder, Map hmap, int i, int l, int r) { - // 子树区间为空时终止 - if (r - l < 0) - return null; - // 初始化根节点 - TreeNode root = new TreeNode(preorder[i]); - // 查询 m ,从而划分左右子树 - int m = hmap.get(preorder[i]); - // 子问题:构建左子树 - root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); - // 子问题:构建右子树 - root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); - // 返回根节点 - return root; - } - - /* 构建二叉树 */ - TreeNode buildTree(int[] preorder, int[] inorder) { - // 初始化哈希表,存储 inorder 元素到索引的映射 - Map hmap = new HashMap<>(); - for (int i = 0; i < inorder.length; i++) { - hmap.put(inorder[i], i); - } - TreeNode root = dfs(preorder, inorder, hmap, 0, 0, inorder.length - 1); - return root; - } - ``` - -=== "C++" - - ```cpp title="build_tree.cpp" - /* 构建二叉树:分治 */ - TreeNode *dfs(vector &preorder, vector &inorder, unordered_map &hmap, int i, int l, int r) { - // 子树区间为空时终止 - if (r - l < 0) - return NULL; - // 初始化根节点 - TreeNode *root = new TreeNode(preorder[i]); - // 查询 m ,从而划分左右子树 - int m = hmap[preorder[i]]; - // 子问题:构建左子树 - root->left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); - // 子问题:构建右子树 - root->right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); - // 返回根节点 - return root; - } - - /* 构建二叉树 */ - TreeNode *buildTree(vector &preorder, vector &inorder) { - // 初始化哈希表,存储 inorder 元素到索引的映射 - unordered_map hmap; - for (int i = 0; i < inorder.size(); i++) { - hmap[inorder[i]] = i; - } - TreeNode *root = dfs(preorder, inorder, hmap, 0, 0, inorder.size() - 1); - return root; - } - ``` - === "Python" ```python title="build_tree.py" @@ -170,6 +106,102 @@ status: new return root ``` +=== "C++" + + ```cpp title="build_tree.cpp" + /* 构建二叉树:分治 */ + TreeNode *dfs(vector &preorder, vector &inorder, unordered_map &hmap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return NULL; + // 初始化根节点 + TreeNode *root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = hmap[preorder[i]]; + // 子问题:构建左子树 + root->left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); + // 子问题:构建右子树 + root->right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; + } + + /* 构建二叉树 */ + TreeNode *buildTree(vector &preorder, vector &inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + unordered_map hmap; + for (int i = 0; i < inorder.size(); i++) { + hmap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorder, hmap, 0, 0, inorder.size() - 1); + return root; + } + ``` + +=== "Java" + + ```java title="build_tree.java" + /* 构建二叉树:分治 */ + TreeNode dfs(int[] preorder, int[] inorder, Map hmap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return null; + // 初始化根节点 + TreeNode root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = hmap.get(preorder[i]); + // 子问题:构建左子树 + root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; + } + + /* 构建二叉树 */ + TreeNode buildTree(int[] preorder, int[] inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + Map hmap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + hmap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorder, hmap, 0, 0, inorder.length - 1); + return root; + } + ``` + +=== "C#" + + ```csharp title="build_tree.cs" + /* 构建二叉树:分治 */ + TreeNode dfs(int[] preorder, int[] inorder, Dictionary hmap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return null; + // 初始化根节点 + TreeNode root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = hmap[preorder[i]]; + // 子问题:构建左子树 + root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); + // 子问题:构建右子树 + root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; + } + + /* 构建二叉树 */ + TreeNode buildTree(int[] preorder, int[] inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + Dictionary hmap = new Dictionary(); + for (int i = 0; i < inorder.Length; i++) { + hmap.TryAdd(inorder[i], i); + } + TreeNode root = dfs(preorder, inorder, hmap, 0, 0, inorder.Length - 1); + return root; + } + ``` + === "Go" ```go title="build_tree.go" @@ -204,6 +236,35 @@ status: new } ``` +=== "Swift" + + ```swift title="build_tree.swift" + /* 构建二叉树:分治 */ + func dfs(preorder: [Int], inorder: [Int], hmap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { + // 子树区间为空时终止 + if r - l < 0 { + return nil + } + // 初始化根节点 + let root = TreeNode(x: preorder[i]) + // 查询 m ,从而划分左右子树 + let m = hmap[preorder[i]]! + // 子问题:构建左子树 + root.left = dfs(preorder: preorder, inorder: inorder, hmap: hmap, i: i + 1, l: l, r: m - 1) + // 子问题:构建右子树 + root.right = dfs(preorder: preorder, inorder: inorder, hmap: hmap, i: i + 1 + m - l, l: m + 1, r: r) + // 返回根节点 + return root + } + + /* 构建二叉树 */ + func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { + // 初始化哈希表,存储 inorder 元素到索引的映射 + let hmap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } + return dfs(preorder: preorder, inorder: inorder, hmap: hmap, i: 0, l: 0, r: inorder.count - 1) + } + ``` + === "JS" ```javascript title="build_tree.js" @@ -273,83 +334,6 @@ status: new } ``` -=== "C" - - ```c title="build_tree.c" - [class]{}-[func]{dfs} - - [class]{}-[func]{buildTree} - ``` - -=== "C#" - - ```csharp title="build_tree.cs" - /* 构建二叉树:分治 */ - TreeNode dfs(int[] preorder, int[] inorder, Dictionary hmap, int i, int l, int r) { - // 子树区间为空时终止 - if (r - l < 0) - return null; - // 初始化根节点 - TreeNode root = new TreeNode(preorder[i]); - // 查询 m ,从而划分左右子树 - int m = hmap[preorder[i]]; - // 子问题:构建左子树 - root.left = dfs(preorder, inorder, hmap, i + 1, l, m - 1); - // 子问题:构建右子树 - root.right = dfs(preorder, inorder, hmap, i + 1 + m - l, m + 1, r); - // 返回根节点 - return root; - } - - /* 构建二叉树 */ - TreeNode buildTree(int[] preorder, int[] inorder) { - // 初始化哈希表,存储 inorder 元素到索引的映射 - Dictionary hmap = new Dictionary(); - for (int i = 0; i < inorder.Length; i++) { - hmap.TryAdd(inorder[i], i); - } - TreeNode root = dfs(preorder, inorder, hmap, 0, 0, inorder.Length - 1); - return root; - } - ``` - -=== "Swift" - - ```swift title="build_tree.swift" - /* 构建二叉树:分治 */ - func dfs(preorder: [Int], inorder: [Int], hmap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { - // 子树区间为空时终止 - if r - l < 0 { - return nil - } - // 初始化根节点 - let root = TreeNode(x: preorder[i]) - // 查询 m ,从而划分左右子树 - let m = hmap[preorder[i]]! - // 子问题:构建左子树 - root.left = dfs(preorder: preorder, inorder: inorder, hmap: hmap, i: i + 1, l: l, r: m - 1) - // 子问题:构建右子树 - root.right = dfs(preorder: preorder, inorder: inorder, hmap: hmap, i: i + 1 + m - l, l: m + 1, r: r) - // 返回根节点 - return root - } - - /* 构建二叉树 */ - func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { - // 初始化哈希表,存储 inorder 元素到索引的映射 - let hmap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } - return dfs(preorder: preorder, inorder: inorder, hmap: hmap, i: 0, l: 0, r: inorder.count - 1) - } - ``` - -=== "Zig" - - ```zig title="build_tree.zig" - [class]{}-[func]{dfs} - - [class]{}-[func]{buildTree} - ``` - === "Dart" ```dart title="build_tree.dart" @@ -421,6 +405,22 @@ status: new } ``` +=== "C" + + ```c title="build_tree.c" + [class]{}-[func]{dfs} + + [class]{}-[func]{buildTree} + ``` + +=== "Zig" + + ```zig title="build_tree.zig" + [class]{}-[func]{dfs} + + [class]{}-[func]{buildTree} + ``` + 图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(即引用)是在向上“归”的过程中建立的。 === "<1>" diff --git a/chapter_divide_and_conquer/hanota_problem.md b/chapter_divide_and_conquer/hanota_problem.md index 5786841e8..ce3c7845b 100644 --- a/chapter_divide_and_conquer/hanota_problem.md +++ b/chapter_divide_and_conquer/hanota_problem.md @@ -97,38 +97,34 @@ status: new 在代码中,我们声明一个递归函数 `dfs(i, src, buf, tar)` ,它的作用是将柱 `src` 顶部的 $i$ 个圆盘借助缓冲柱 `buf` 移动至目标柱 `tar` 。 -=== "Java" +=== "Python" - ```java title="hanota.java" - /* 移动一个圆盘 */ - void move(List src, List tar) { - // 从 src 顶部拿出一个圆盘 - Integer pan = src.remove(src.size() - 1); - // 将圆盘放入 tar 顶部 - tar.add(pan); - } + ```python title="hanota.py" + def move(src: list[int], tar: list[int]): + """移动一个圆盘""" + # 从 src 顶部拿出一个圆盘 + pan = src.pop() + # 将圆盘放入 tar 顶部 + tar.append(pan) - /* 求解汉诺塔:问题 f(i) */ - void dfs(int i, List src, List buf, List tar) { - // 若 src 只剩下一个圆盘,则直接将其移到 tar - if (i == 1) { - move(src, tar); - return; - } - // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf - dfs(i - 1, src, tar, buf); - // 子问题 f(1) :将 src 剩余一个圆盘移到 tar - move(src, tar); - // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar - dfs(i - 1, buf, src, tar); - } + def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): + """求解汉诺塔:问题 f(i)""" + # 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1: + move(src, tar) + return + # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf) + # 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar) + # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar) - /* 求解汉诺塔 */ - void solveHanota(List A, List B, List C) { - int n = A.size(); - // 将 A 顶部 n 个圆盘借助 B 移到 C - dfs(n, A, B, C); - } + def solve_hanota(A: list[int], B: list[int], C: list[int]): + """求解汉诺塔""" + n = len(A) + # 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C) ``` === "C++" @@ -166,34 +162,73 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="hanota.py" - def move(src: list[int], tar: list[int]): - """移动一个圆盘""" - # 从 src 顶部拿出一个圆盘 - pan = src.pop() - # 将圆盘放入 tar 顶部 - tar.append(pan) + ```java title="hanota.java" + /* 移动一个圆盘 */ + void move(List src, List tar) { + // 从 src 顶部拿出一个圆盘 + Integer pan = src.remove(src.size() - 1); + // 将圆盘放入 tar 顶部 + tar.add(pan); + } - def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): - """求解汉诺塔:问题 f(i)""" - # 若 src 只剩下一个圆盘,则直接将其移到 tar - if i == 1: - move(src, tar) - return - # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf - dfs(i - 1, src, tar, buf) - # 子问题 f(1) :将 src 剩余一个圆盘移到 tar - move(src, tar) - # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar - dfs(i - 1, buf, src, tar) + /* 求解汉诺塔:问题 f(i) */ + void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); + } - def solve_hanota(A: list[int], B: list[int], C: list[int]): - """求解汉诺塔""" - n = len(A) - # 将 A 顶部 n 个圆盘借助 B 移到 C - dfs(n, A, B, C) + /* 求解汉诺塔 */ + void solveHanota(List A, List B, List C) { + int n = A.size(); + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); + } + ``` + +=== "C#" + + ```csharp title="hanota.cs" + /* 移动一个圆盘 */ + void move(List src, List tar) { + // 从 src 顶部拿出一个圆盘 + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // 将圆盘放入 tar 顶部 + tar.Add(pan); + } + + /* 求解汉诺塔:问题 f(i) */ + void dfs(int i, List src, List buf, List tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); + } + + /* 求解汉诺塔 */ + void solveHanota(List A, List B, List C) { + int n = A.Count; + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); + } ``` === "Go" @@ -232,6 +267,41 @@ status: new } ``` +=== "Swift" + + ```swift title="hanota.swift" + /* 移动一个圆盘 */ + func move(src: inout [Int], tar: inout [Int]) { + // 从 src 顶部拿出一个圆盘 + let pan = src.popLast()! + // 将圆盘放入 tar 顶部 + tar.append(pan) + } + + /* 求解汉诺塔:问题 f(i) */ + func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if i == 1 { + move(src: &src, tar: &tar) + return + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src: &src, tar: &tar) + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) + } + + /* 求解汉诺塔 */ + func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { + let n = A.count + // 列表尾部是柱子顶部 + // 将 src 顶部 n 个圆盘借助 B 移到 C + dfs(i: n, src: &A, buf: &B, tar: &C) + } + ``` + === "JS" ```javascript title="hanota.js" @@ -300,96 +370,6 @@ status: new } ``` -=== "C" - - ```c title="hanota.c" - [class]{}-[func]{move} - - [class]{}-[func]{dfs} - - [class]{}-[func]{solveHanota} - ``` - -=== "C#" - - ```csharp title="hanota.cs" - /* 移动一个圆盘 */ - void move(List src, List tar) { - // 从 src 顶部拿出一个圆盘 - int pan = src[^1]; - src.RemoveAt(src.Count - 1); - // 将圆盘放入 tar 顶部 - tar.Add(pan); - } - - /* 求解汉诺塔:问题 f(i) */ - void dfs(int i, List src, List buf, List tar) { - // 若 src 只剩下一个圆盘,则直接将其移到 tar - if (i == 1) { - move(src, tar); - return; - } - // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf - dfs(i - 1, src, tar, buf); - // 子问题 f(1) :将 src 剩余一个圆盘移到 tar - move(src, tar); - // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar - dfs(i - 1, buf, src, tar); - } - - /* 求解汉诺塔 */ - void solveHanota(List A, List B, List C) { - int n = A.Count; - // 将 A 顶部 n 个圆盘借助 B 移到 C - dfs(n, A, B, C); - } - ``` - -=== "Swift" - - ```swift title="hanota.swift" - /* 移动一个圆盘 */ - func move(src: inout [Int], tar: inout [Int]) { - // 从 src 顶部拿出一个圆盘 - let pan = src.popLast()! - // 将圆盘放入 tar 顶部 - tar.append(pan) - } - - /* 求解汉诺塔:问题 f(i) */ - func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { - // 若 src 只剩下一个圆盘,则直接将其移到 tar - if i == 1 { - move(src: &src, tar: &tar) - return - } - // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf - dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) - // 子问题 f(1) :将 src 剩余一个圆盘移到 tar - move(src: &src, tar: &tar) - // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar - dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) - } - - /* 求解汉诺塔 */ - func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { - let n = A.count - // 列表尾部是柱子顶部 - // 将 src 顶部 n 个圆盘借助 B 移到 C - dfs(i: n, src: &A, buf: &B, tar: &C) - } - ``` - -=== "Zig" - - ```zig title="hanota.zig" - [class]{}-[func]{move} - - [class]{}-[func]{dfs} - - [class]{}-[func]{solveHanota} - ``` - === "Dart" ```dart title="hanota.dart" @@ -458,6 +438,26 @@ status: new } ``` +=== "C" + + ```c title="hanota.c" + [class]{}-[func]{move} + + [class]{}-[func]{dfs} + + [class]{}-[func]{solveHanota} + ``` + +=== "Zig" + + ```zig title="hanota.zig" + [class]{}-[func]{move} + + [class]{}-[func]{dfs} + + [class]{}-[func]{solveHanota} + ``` + 如图 12-15 所示,汉诺塔问题形成一个高度为 $n$ 的递归树,每个节点代表一个子问题、对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 ![汉诺塔问题的递归树](hanota_problem.assets/hanota_recursive_tree.png) diff --git a/chapter_dynamic_programming/dp_problem_features.md b/chapter_dynamic_programming/dp_problem_features.md index e40f50557..7402bb1ad 100644 --- a/chapter_dynamic_programming/dp_problem_features.md +++ b/chapter_dynamic_programming/dp_problem_features.md @@ -41,25 +41,22 @@ $$ 根据状态转移方程,以及初始状态 $dp[1] = cost[1]$ 和 $dp[2] = cost[2]$ ,我们就可以得到动态规划代码。 -=== "Java" +=== "Python" - ```java title="min_cost_climbing_stairs_dp.java" - /* 爬楼梯最小代价:动态规划 */ - int minCostClimbingStairsDP(int[] cost) { - int n = cost.length - 1; - if (n == 1 || n == 2) - return cost[n]; - // 初始化 dp 表,用于存储子问题的解 - int[] dp = new int[n + 1]; - // 初始状态:预设最小子问题的解 - dp[1] = cost[1]; - dp[2] = cost[2]; - // 状态转移:从较小子问题逐步求解较大子问题 - for (int i = 3; i <= n; i++) { - dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; - } - return dp[n]; - } + ```python title="min_cost_climbing_stairs_dp.py" + def min_cost_climbing_stairs_dp(cost: list[int]) -> int: + """爬楼梯最小代价:动态规划""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + # 初始化 dp 表,用于存储子问题的解 + dp = [0] * (n + 1) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = cost[1], cost[2] + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return dp[n] ``` === "C++" @@ -83,22 +80,46 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="min_cost_climbing_stairs_dp.py" - def min_cost_climbing_stairs_dp(cost: list[int]) -> int: - """爬楼梯最小代价:动态规划""" - n = len(cost) - 1 - if n == 1 or n == 2: - return cost[n] - # 初始化 dp 表,用于存储子问题的解 - dp = [0] * (n + 1) - # 初始状态:预设最小子问题的解 - dp[1], dp[2] = cost[1], cost[2] - # 状态转移:从较小子问题逐步求解较大子问题 - for i in range(3, n + 1): - dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] - return dp[n] + ```java title="min_cost_climbing_stairs_dp.java" + /* 爬楼梯最小代价:动态规划 */ + int minCostClimbingStairsDP(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + ``` + +=== "C#" + + ```csharp title="min_cost_climbing_stairs_dp.cs" + /* 爬楼梯最小代价:动态规划 */ + int minCostClimbingStairsDP(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } ``` === "Go" @@ -123,6 +144,28 @@ $$ } ``` +=== "Swift" + + ```swift title="min_cost_climbing_stairs_dp.swift" + /* 爬楼梯最小代价:动态规划 */ + func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in stride(from: 3, through: n, by: 1) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] + } + ``` + === "JS" ```javascript title="min_cost_climbing_stairs_dp.js" @@ -167,77 +210,6 @@ $$ } ``` -=== "C" - - ```c title="min_cost_climbing_stairs_dp.c" - [class]{}-[func]{minCostClimbingStairsDP} - ``` - -=== "C#" - - ```csharp title="min_cost_climbing_stairs_dp.cs" - /* 爬楼梯最小代价:动态规划 */ - int minCostClimbingStairsDP(int[] cost) { - int n = cost.Length - 1; - if (n == 1 || n == 2) - return cost[n]; - // 初始化 dp 表,用于存储子问题的解 - int[] dp = new int[n + 1]; - // 初始状态:预设最小子问题的解 - dp[1] = cost[1]; - dp[2] = cost[2]; - // 状态转移:从较小子问题逐步求解较大子问题 - for (int i = 3; i <= n; i++) { - dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; - } - return dp[n]; - } - ``` - -=== "Swift" - - ```swift title="min_cost_climbing_stairs_dp.swift" - /* 爬楼梯最小代价:动态规划 */ - func minCostClimbingStairsDP(cost: [Int]) -> Int { - let n = cost.count - 1 - if n == 1 || n == 2 { - return cost[n] - } - // 初始化 dp 表,用于存储子问题的解 - var dp = Array(repeating: 0, count: n + 1) - // 初始状态:预设最小子问题的解 - dp[1] = 1 - dp[2] = 2 - // 状态转移:从较小子问题逐步求解较大子问题 - for i in stride(from: 3, through: n, by: 1) { - dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] - } - return dp[n] - } - ``` - -=== "Zig" - - ```zig title="min_cost_climbing_stairs_dp.zig" - // 爬楼梯最小代价:动态规划 - fn minCostClimbingStairsDP(comptime cost: []i32) i32 { - comptime var n = cost.len - 1; - if (n == 1 or n == 2) { - return cost[n]; - } - // 初始化 dp 表,用于存储子问题的解 - var dp = [_]i32{-1} ** (n + 1); - // 初始状态:预设最小子问题的解 - dp[1] = cost[1]; - dp[2] = cost[2]; - // 状态转移:从较小子问题逐步求解较大子问题 - for (3..n + 1) |i| { - dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; - } - return dp[n]; - } - ``` - === "Dart" ```dart title="min_cost_climbing_stairs_dp.dart" @@ -278,6 +250,34 @@ $$ } ``` +=== "C" + + ```c title="min_cost_climbing_stairs_dp.c" + [class]{}-[func]{minCostClimbingStairsDP} + ``` + +=== "Zig" + + ```zig title="min_cost_climbing_stairs_dp.zig" + // 爬楼梯最小代价:动态规划 + fn minCostClimbingStairsDP(comptime cost: []i32) i32 { + comptime var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + // 初始化 dp 表,用于存储子问题的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + ``` + 图 14-7 展示了以上代码的动态规划过程。 ![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png) @@ -286,22 +286,18 @@ $$ 本题也可以进行空间优化,将一维压缩至零维,使得空间复杂度从 $O(n)$ 降低至 $O(1)$ 。 -=== "Java" +=== "Python" - ```java title="min_cost_climbing_stairs_dp.java" - /* 爬楼梯最小代价:空间优化后的动态规划 */ - int minCostClimbingStairsDPComp(int[] cost) { - int n = cost.length - 1; - if (n == 1 || n == 2) - return cost[n]; - int a = cost[1], b = cost[2]; - for (int i = 3; i <= n; i++) { - int tmp = b; - b = Math.min(a, tmp) + cost[i]; - a = tmp; - } - return b; - } + ```python title="min_cost_climbing_stairs_dp.py" + def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: + """爬楼梯最小代价:空间优化后的动态规划""" + n = len(cost) - 1 + if n == 1 or n == 2: + return cost[n] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b ``` === "C++" @@ -322,18 +318,40 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="min_cost_climbing_stairs_dp.py" - def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: - """爬楼梯最小代价:空间优化后的动态规划""" - n = len(cost) - 1 - if n == 1 or n == 2: - return cost[n] - a, b = cost[1], cost[2] - for i in range(3, n + 1): - a, b = b, min(a, b) + cost[i] - return b + ```java title="min_cost_climbing_stairs_dp.java" + /* 爬楼梯最小代价:空间优化后的动态规划 */ + int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + ``` + +=== "C#" + + ```csharp title="min_cost_climbing_stairs_dp.cs" + /* 爬楼梯最小代价:空间优化后的动态规划 */ + int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.Min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } ``` === "Go" @@ -357,6 +375,23 @@ $$ } ``` +=== "Swift" + + ```swift title="min_cost_climbing_stairs_dp.swift" + /* 爬楼梯最小代价:空间优化后的动态规划 */ + func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in stride(from: 3, through: n, by: 1) { + (a, b) = (b, min(a, b) + cost[i]) + } + return b + } + ``` + === "JS" ```javascript title="min_cost_climbing_stairs_dp.js" @@ -397,68 +432,6 @@ $$ } ``` -=== "C" - - ```c title="min_cost_climbing_stairs_dp.c" - [class]{}-[func]{minCostClimbingStairsDPComp} - ``` - -=== "C#" - - ```csharp title="min_cost_climbing_stairs_dp.cs" - /* 爬楼梯最小代价:空间优化后的动态规划 */ - int minCostClimbingStairsDPComp(int[] cost) { - int n = cost.Length - 1; - if (n == 1 || n == 2) - return cost[n]; - int a = cost[1], b = cost[2]; - for (int i = 3; i <= n; i++) { - int tmp = b; - b = Math.Min(a, tmp) + cost[i]; - a = tmp; - } - return b; - } - ``` - -=== "Swift" - - ```swift title="min_cost_climbing_stairs_dp.swift" - /* 爬楼梯最小代价:空间优化后的动态规划 */ - func minCostClimbingStairsDPComp(cost: [Int]) -> Int { - let n = cost.count - 1 - if n == 1 || n == 2 { - return cost[n] - } - var (a, b) = (cost[1], cost[2]) - for i in stride(from: 3, through: n, by: 1) { - (a, b) = (b, min(a, b) + cost[i]) - } - return b - } - ``` - -=== "Zig" - - ```zig title="min_cost_climbing_stairs_dp.zig" - // 爬楼梯最小代价:空间优化后的动态规划 - fn minCostClimbingStairsDPComp(cost: []i32) i32 { - var n = cost.len - 1; - if (n == 1 or n == 2) { - return cost[n]; - } - var a = cost[1]; - var b = cost[2]; - // 状态转移:从较小子问题逐步求解较大子问题 - for (3..n + 1) |i| { - var tmp = b; - b = @min(a, tmp) + cost[i]; - a = tmp; - } - return b; - } - ``` - === "Dart" ```dart title="min_cost_climbing_stairs_dp.dart" @@ -493,6 +466,33 @@ $$ } ``` +=== "C" + + ```c title="min_cost_climbing_stairs_dp.c" + [class]{}-[func]{minCostClimbingStairsDPComp} + ``` + +=== "Zig" + + ```zig title="min_cost_climbing_stairs_dp.zig" + // 爬楼梯最小代价:空间优化后的动态规划 + fn minCostClimbingStairsDPComp(cost: []i32) i32 { + var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + var a = cost[1]; + var b = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + var tmp = b; + b = @min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + ``` + ## 14.2.2   无后效性 无后效性是动态规划能够有效解决问题的重要特性之一,定义为:**给定一个确定的状态,它的未来发展只与当前状态有关,而与当前状态过去所经历过的所有状态无关**。 @@ -535,28 +535,23 @@ $$ 最终,返回 $dp[n, 1] + dp[n, 2]$ 即可,两者之和代表爬到第 $n$ 阶的方案总数。 -=== "Java" +=== "Python" - ```java title="climbing_stairs_constraint_dp.java" - /* 带约束爬楼梯:动态规划 */ - int climbingStairsConstraintDP(int n) { - if (n == 1 || n == 2) { - return 1; - } - // 初始化 dp 表,用于存储子问题的解 - int[][] dp = new int[n + 1][3]; - // 初始状态:预设最小子问题的解 - dp[1][1] = 1; - dp[1][2] = 0; - dp[2][1] = 0; - dp[2][2] = 1; - // 状态转移:从较小子问题逐步求解较大子问题 - for (int i = 3; i <= n; i++) { - dp[i][1] = dp[i - 1][2]; - dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; - } - return dp[n][1] + dp[n][2]; - } + ```python title="climbing_stairs_constraint_dp.py" + def climbing_stairs_constraint_dp(n: int) -> int: + """带约束爬楼梯:动态规划""" + if n == 1 or n == 2: + return 1 + # 初始化 dp 表,用于存储子问题的解 + dp = [[0] * 3 for _ in range(n + 1)] + # 初始状态:预设最小子问题的解 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + return dp[n][1] + dp[n][2] ``` === "C++" @@ -583,23 +578,52 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="climbing_stairs_constraint_dp.py" - def climbing_stairs_constraint_dp(n: int) -> int: - """带约束爬楼梯:动态规划""" - if n == 1 or n == 2: - return 1 - # 初始化 dp 表,用于存储子问题的解 - dp = [[0] * 3 for _ in range(n + 1)] - # 初始状态:预设最小子问题的解 - dp[1][1], dp[1][2] = 1, 0 - dp[2][1], dp[2][2] = 0, 1 - # 状态转移:从较小子问题逐步求解较大子问题 - for i in range(3, n + 1): - dp[i][1] = dp[i - 1][2] - dp[i][2] = dp[i - 2][1] + dp[i - 2][2] - return dp[n][1] + dp[n][2] + ```java title="climbing_stairs_constraint_dp.java" + /* 带约束爬楼梯:动态规划 */ + int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + int[][] dp = new int[n + 1][3]; + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; + } + ``` + +=== "C#" + + ```csharp title="climbing_stairs_constraint_dp.cs" + /* 带约束爬楼梯:动态规划 */ + int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + int[,] dp = new int[n + 1, 3]; + // 初始状态:预设最小子问题的解 + dp[1, 1] = 1; + dp[1, 2] = 0; + dp[2, 1] = 0; + dp[2, 2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i, 1] = dp[i - 1, 2]; + dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; + } + return dp[n, 1] + dp[n, 2]; + } ``` === "Go" @@ -626,6 +650,30 @@ $$ } ``` +=== "Swift" + + ```swift title="climbing_stairs_constraint_dp.swift" + /* 带约束爬楼梯:动态规划 */ + func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in stride(from: 3, through: n, by: 1) { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] + } + ``` + === "JS" ```javascript title="climbing_stairs_constraint_dp.js" @@ -674,84 +722,6 @@ $$ } ``` -=== "C" - - ```c title="climbing_stairs_constraint_dp.c" - [class]{}-[func]{climbingStairsConstraintDP} - ``` - -=== "C#" - - ```csharp title="climbing_stairs_constraint_dp.cs" - /* 带约束爬楼梯:动态规划 */ - int climbingStairsConstraintDP(int n) { - if (n == 1 || n == 2) { - return 1; - } - // 初始化 dp 表,用于存储子问题的解 - int[,] dp = new int[n + 1, 3]; - // 初始状态:预设最小子问题的解 - dp[1, 1] = 1; - dp[1, 2] = 0; - dp[2, 1] = 0; - dp[2, 2] = 1; - // 状态转移:从较小子问题逐步求解较大子问题 - for (int i = 3; i <= n; i++) { - dp[i, 1] = dp[i - 1, 2]; - dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; - } - return dp[n, 1] + dp[n, 2]; - } - ``` - -=== "Swift" - - ```swift title="climbing_stairs_constraint_dp.swift" - /* 带约束爬楼梯:动态规划 */ - func climbingStairsConstraintDP(n: Int) -> Int { - if n == 1 || n == 2 { - return 1 - } - // 初始化 dp 表,用于存储子问题的解 - var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) - // 初始状态:预设最小子问题的解 - dp[1][1] = 1 - dp[1][2] = 0 - dp[2][1] = 0 - dp[2][2] = 1 - // 状态转移:从较小子问题逐步求解较大子问题 - for i in stride(from: 3, through: n, by: 1) { - dp[i][1] = dp[i - 1][2] - dp[i][2] = dp[i - 2][1] + dp[i - 2][2] - } - return dp[n][1] + dp[n][2] - } - ``` - -=== "Zig" - - ```zig title="climbing_stairs_constraint_dp.zig" - // 带约束爬楼梯:动态规划 - fn climbingStairsConstraintDP(comptime n: usize) i32 { - if (n == 1 or n == 2) { - return 1; - } - // 初始化 dp 表,用于存储子问题的解 - var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); - // 初始状态:预设最小子问题的解 - dp[1][1] = 1; - dp[1][2] = 0; - dp[2][1] = 0; - dp[2][2] = 1; - // 状态转移:从较小子问题逐步求解较大子问题 - for (3..n + 1) |i| { - dp[i][1] = dp[i - 1][2]; - dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; - } - return dp[n][1] + dp[n][2]; - } - ``` - === "Dart" ```dart title="climbing_stairs_constraint_dp.dart" @@ -798,6 +768,36 @@ $$ } ``` +=== "C" + + ```c title="climbing_stairs_constraint_dp.c" + [class]{}-[func]{climbingStairsConstraintDP} + ``` + +=== "Zig" + + ```zig title="climbing_stairs_constraint_dp.zig" + // 带约束爬楼梯:动态规划 + fn climbingStairsConstraintDP(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; + } + ``` + 在上面的案例中,由于仅需多考虑前面一个状态,我们仍然可以通过扩展状态定义,使得问题重新满足无后效性。然而,某些问题具有非常严重的“有后效性”。 !!! question "爬楼梯与障碍生成" diff --git a/chapter_dynamic_programming/dp_solution_pipeline.md b/chapter_dynamic_programming/dp_solution_pipeline.md index f23b45038..62e615fe6 100644 --- a/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/chapter_dynamic_programming/dp_solution_pipeline.md @@ -111,25 +111,22 @@ $$ - **终止条件**:当 $i = 0$ 且 $j = 0$ 时,返回代价 $grid[0, 0]$ 。 - **剪枝**:当 $i < 0$ 时或 $j < 0$ 时索引越界,此时返回代价 $+\infty$ ,代表不可行。 -=== "Java" +=== "Python" - ```java title="min_path_sum.java" - /* 最小路径和:暴力搜索 */ - int minPathSumDFS(int[][] grid, int i, int j) { - // 若为左上角单元格,则终止搜索 - if (i == 0 && j == 0) { - return grid[0][0]; - } - // 若行列索引越界,则返回 +∞ 代价 - if (i < 0 || j < 0) { - return Integer.MAX_VALUE; - } - // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 - int left = minPathSumDFS(grid, i - 1, j); - int up = minPathSumDFS(grid, i, j - 1); - // 返回从左上角到 (i, j) 的最小路径代价 - return Math.min(left, up) + grid[i][j]; - } + ```python title="min_path_sum.py" + def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: + """最小路径和:暴力搜索""" + # 若为左上角单元格,则终止搜索 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0: + return inf + # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + left = min_path_sum_dfs(grid, i - 1, j) + up = min_path_sum_dfs(grid, i, j - 1) + # 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] ``` === "C++" @@ -153,22 +150,46 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="min_path_sum.py" - def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: - """最小路径和:暴力搜索""" - # 若为左上角单元格,则终止搜索 - if i == 0 and j == 0: - return grid[0][0] - # 若行列索引越界,则返回 +∞ 代价 - if i < 0 or j < 0: - return inf - # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 - left = min_path_sum_dfs(grid, i - 1, j) - up = min_path_sum_dfs(grid, i, j - 1) - # 返回从左上角到 (i, j) 的最小路径代价 - return min(left, up) + grid[i][j] + ```java title="min_path_sum.java" + /* 最小路径和:暴力搜索 */ + int minPathSumDFS(int[][] grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int left = minPathSumDFS(grid, i - 1, j); + int up = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.min(left, up) + grid[i][j]; + } + ``` + +=== "C#" + + ```csharp title="min_path_sum.cs" + /* 最小路径和:暴力搜索 */ + int minPathSumDFS(int[][] grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int left = minPathSumDFS(grid, i - 1, j); + int up = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return Math.Min(left, up) + grid[i][j]; + } ``` === "Go" @@ -192,6 +213,27 @@ $$ } ``` +=== "Swift" + + ```swift title="min_path_sum.swift" + /* 最小路径和:暴力搜索 */ + func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // 若为左上角单元格,则终止搜索 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return .max + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + let left = minPathSumDFS(grid: grid, i: i - 1, j: j) + let up = minPathSumDFS(grid: grid, i: i, j: j - 1) + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) + grid[i][j] + } + ``` + === "JS" ```javascript title="min_path_sum.js" @@ -238,75 +280,6 @@ $$ } ``` -=== "C" - - ```c title="min_path_sum.c" - [class]{}-[func]{minPathSumDFS} - ``` - -=== "C#" - - ```csharp title="min_path_sum.cs" - /* 最小路径和:暴力搜索 */ - int minPathSumDFS(int[][] grid, int i, int j) { - // 若为左上角单元格,则终止搜索 - if (i == 0 && j == 0) { - return grid[0][0]; - } - // 若行列索引越界,则返回 +∞ 代价 - if (i < 0 || j < 0) { - return int.MaxValue; - } - // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 - int left = minPathSumDFS(grid, i - 1, j); - int up = minPathSumDFS(grid, i, j - 1); - // 返回从左上角到 (i, j) 的最小路径代价 - return Math.Min(left, up) + grid[i][j]; - } - ``` - -=== "Swift" - - ```swift title="min_path_sum.swift" - /* 最小路径和:暴力搜索 */ - func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { - // 若为左上角单元格,则终止搜索 - if i == 0, j == 0 { - return grid[0][0] - } - // 若行列索引越界,则返回 +∞ 代价 - if i < 0 || j < 0 { - return .max - } - // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 - let left = minPathSumDFS(grid: grid, i: i - 1, j: j) - let up = minPathSumDFS(grid: grid, i: i, j: j - 1) - // 返回从左上角到 (i, j) 的最小路径代价 - return min(left, up) + grid[i][j] - } - ``` - -=== "Zig" - - ```zig title="min_path_sum.zig" - // 最小路径和:暴力搜索 - fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { - // 若为左上角单元格,则终止搜索 - if (i == 0 and j == 0) { - return grid[0][0]; - } - // 若行列索引越界,则返回 +∞ 代价 - if (i < 0 or j < 0) { - return std.math.maxInt(i32); - } - // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 - var left = minPathSumDFS(grid, i - 1, j); - var up = minPathSumDFS(grid, i, j - 1); - // 返回从左上角到 (i, j) 的最小路径代价 - return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; - } - ``` - === "Dart" ```dart title="min_path_sum.dart" @@ -350,6 +323,33 @@ $$ } ``` +=== "C" + + ```c title="min_path_sum.c" + [class]{}-[func]{minPathSumDFS} + ``` + +=== "Zig" + + ```zig title="min_path_sum.zig" + // 最小路径和:暴力搜索 + fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { + // 若为左上角单元格,则终止搜索 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + var left = minPathSumDFS(grid, i - 1, j); + var up = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + ``` + 图 14-14 给出了以 $dp[2, 1]$ 为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格 `grid` 的尺寸变大而急剧增多。 本质上看,造成重叠子问题的原因为:**存在多条路径可以从左上角到达某一单元格**。 @@ -364,30 +364,28 @@ $$ 我们引入一个和网格 `grid` 相同尺寸的记忆列表 `mem` ,用于记录各个子问题的解,并将重叠子问题进行剪枝。 -=== "Java" +=== "Python" - ```java title="min_path_sum.java" - /* 最小路径和:记忆化搜索 */ - int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { - // 若为左上角单元格,则终止搜索 - if (i == 0 && j == 0) { - return grid[0][0]; - } - // 若行列索引越界,则返回 +∞ 代价 - if (i < 0 || j < 0) { - return Integer.MAX_VALUE; - } - // 若已有记录,则直接返回 - if (mem[i][j] != -1) { - return mem[i][j]; - } - // 左边和上边单元格的最小路径代价 - int left = minPathSumDFSMem(grid, mem, i - 1, j); - int up = minPathSumDFSMem(grid, mem, i, j - 1); - // 记录并返回左上角到 (i, j) 的最小路径代价 - mem[i][j] = Math.min(left, up) + grid[i][j]; - return mem[i][j]; - } + ```python title="min_path_sum.py" + def min_path_sum_dfs_mem( + grid: list[list[int]], mem: list[list[int]], i: int, j: int + ) -> int: + """最小路径和:记忆化搜索""" + # 若为左上角单元格,则终止搜索 + if i == 0 and j == 0: + return grid[0][0] + # 若行列索引越界,则返回 +∞ 代价 + if i < 0 or j < 0: + return inf + # 若已有记录,则直接返回 + if mem[i][j] != -1: + return mem[i][j] + # 左边和上边单元格的最小路径代价 + left = min_path_sum_dfs_mem(grid, mem, i - 1, j) + up = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] ``` === "C++" @@ -416,28 +414,56 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="min_path_sum.py" - def min_path_sum_dfs_mem( - grid: list[list[int]], mem: list[list[int]], i: int, j: int - ) -> int: - """最小路径和:记忆化搜索""" - # 若为左上角单元格,则终止搜索 - if i == 0 and j == 0: - return grid[0][0] - # 若行列索引越界,则返回 +∞ 代价 - if i < 0 or j < 0: - return inf - # 若已有记录,则直接返回 - if mem[i][j] != -1: - return mem[i][j] - # 左边和上边单元格的最小路径代价 - left = min_path_sum_dfs_mem(grid, mem, i - 1, j) - up = min_path_sum_dfs_mem(grid, mem, i, j - 1) - # 记录并返回左上角到 (i, j) 的最小路径代价 - mem[i][j] = min(left, up) + grid[i][j] - return mem[i][j] + ```java title="min_path_sum.java" + /* 最小路径和:记忆化搜索 */ + int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + int left = minPathSumDFSMem(grid, mem, i - 1, j); + int up = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; + } + ``` + +=== "C#" + + ```csharp title="min_path_sum.cs" + /* 最小路径和:记忆化搜索 */ + int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + int left = minPathSumDFSMem(grid, mem, i - 1, j); + int up = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = Math.Min(left, up) + grid[i][j]; + return mem[i][j]; + } ``` === "Go" @@ -466,6 +492,32 @@ $$ } ``` +=== "Swift" + + ```swift title="min_path_sum.swift" + /* 最小路径和:记忆化搜索 */ + func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若为左上角单元格,则终止搜索 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,则返回 +∞ 代价 + if i < 0 || j < 0 { + return .max + } + // 若已有记录,则直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左边和上边单元格的最小路径代价 + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] + } + ``` + === "JS" ```javascript title="min_path_sum.js" @@ -523,91 +575,6 @@ $$ } ``` -=== "C" - - ```c title="min_path_sum.c" - [class]{}-[func]{minPathSumDFSMem} - ``` - -=== "C#" - - ```csharp title="min_path_sum.cs" - /* 最小路径和:记忆化搜索 */ - int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { - // 若为左上角单元格,则终止搜索 - if (i == 0 && j == 0) { - return grid[0][0]; - } - // 若行列索引越界,则返回 +∞ 代价 - if (i < 0 || j < 0) { - return int.MaxValue; - } - // 若已有记录,则直接返回 - if (mem[i][j] != -1) { - return mem[i][j]; - } - // 左边和上边单元格的最小路径代价 - int left = minPathSumDFSMem(grid, mem, i - 1, j); - int up = minPathSumDFSMem(grid, mem, i, j - 1); - // 记录并返回左上角到 (i, j) 的最小路径代价 - mem[i][j] = Math.Min(left, up) + grid[i][j]; - return mem[i][j]; - } - ``` - -=== "Swift" - - ```swift title="min_path_sum.swift" - /* 最小路径和:记忆化搜索 */ - func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { - // 若为左上角单元格,则终止搜索 - if i == 0, j == 0 { - return grid[0][0] - } - // 若行列索引越界,则返回 +∞ 代价 - if i < 0 || j < 0 { - return .max - } - // 若已有记录,则直接返回 - if mem[i][j] != -1 { - return mem[i][j] - } - // 左边和上边单元格的最小路径代价 - let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) - let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) - // 记录并返回左上角到 (i, j) 的最小路径代价 - mem[i][j] = min(left, up) + grid[i][j] - return mem[i][j] - } - ``` - -=== "Zig" - - ```zig title="min_path_sum.zig" - // 最小路径和:记忆化搜索 - fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { - // 若为左上角单元格,则终止搜索 - if (i == 0 and j == 0) { - return grid[0][0]; - } - // 若行列索引越界,则返回 +∞ 代价 - if (i < 0 or j < 0) { - return std.math.maxInt(i32); - } - // 若已有记录,则直接返回 - if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { - return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; - } - // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 - var left = minPathSumDFSMem(grid, mem, i - 1, j); - var up = minPathSumDFSMem(grid, mem, i, j - 1); - // 返回从左上角到 (i, j) 的最小路径代价 - // 记录并返回左上角到 (i, j) 的最小路径代价 - mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; - return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; - } - ``` - === "Dart" ```dart title="min_path_sum.dart" @@ -661,6 +628,39 @@ $$ } ``` +=== "C" + + ```c title="min_path_sum.c" + [class]{}-[func]{minPathSumDFSMem} + ``` + +=== "Zig" + + ```zig title="min_path_sum.zig" + // 最小路径和:记忆化搜索 + fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { + // 若为左上角单元格,则终止搜索 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 若已有记录,则直接返回 + if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + var left = minPathSumDFSMem(grid, mem, i - 1, j); + var up = minPathSumDFSMem(grid, mem, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + ``` + 如图 14-15 所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 ![记忆化搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) @@ -671,31 +671,26 @@ $$ 基于迭代实现动态规划解法。 -=== "Java" +=== "Python" - ```java title="min_path_sum.java" - /* 最小路径和:动态规划 */ - int minPathSumDP(int[][] grid) { - int n = grid.length, m = grid[0].length; - // 初始化 dp 表 - int[][] dp = new int[n][m]; - dp[0][0] = grid[0][0]; - // 状态转移:首行 - for (int j = 1; j < m; j++) { - dp[0][j] = dp[0][j - 1] + grid[0][j]; - } - // 状态转移:首列 - for (int i = 1; i < n; i++) { - dp[i][0] = dp[i - 1][0] + grid[i][0]; - } - // 状态转移:其余行列 - for (int i = 1; i < n; i++) { - for (int j = 1; j < m; j++) { - dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; - } - } - return dp[n - 1][m - 1]; - } + ```python title="min_path_sum.py" + def min_path_sum_dp(grid: list[list[int]]) -> int: + """最小路径和:动态规划""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [[0] * m for _ in range(n)] + dp[0][0] = grid[0][0] + # 状态转移:首行 + for j in range(1, m): + dp[0][j] = dp[0][j - 1] + grid[0][j] + # 状态转移:首列 + for i in range(1, n): + dp[i][0] = dp[i - 1][0] + grid[i][0] + # 状态转移:其余行列 + for i in range(1, n): + for j in range(1, m): + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + return dp[n - 1][m - 1] ``` === "C++" @@ -725,26 +720,58 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="min_path_sum.py" - def min_path_sum_dp(grid: list[list[int]]) -> int: - """最小路径和:动态规划""" - n, m = len(grid), len(grid[0]) - # 初始化 dp 表 - dp = [[0] * m for _ in range(n)] - dp[0][0] = grid[0][0] - # 状态转移:首行 - for j in range(1, m): - dp[0][j] = dp[0][j - 1] + grid[0][j] - # 状态转移:首列 - for i in range(1, n): - dp[i][0] = dp[i - 1][0] + grid[i][0] - # 状态转移:其余行列 - for i in range(1, n): - for j in range(1, m): - dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] - return dp[n - 1][m - 1] + ```java title="min_path_sum.java" + /* 最小路径和:动态规划 */ + int minPathSumDP(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[][] dp = new int[n][m]; + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + ``` + +=== "C#" + + ```csharp title="min_path_sum.cs" + /* 最小路径和:动态规划 */ + int minPathSumDP(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[,] dp = new int[n, m]; + dp[0, 0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0, j] = dp[0, j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i, 0] = dp[i - 1, 0] + grid[i][0]; + } + // 状态转移:其余行列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; + } + } + return dp[n - 1, m - 1]; + } ``` === "Go" @@ -777,6 +804,34 @@ $$ } ``` +=== "Swift" + + ```swift title="min_path_sum.swift" + /* 最小路径和:动态规划 */ + func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // 状态转移:首行 + for j in stride(from: 1, to: m, by: 1) { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // 状态转移:首列 + for i in stride(from: 1, to: n, by: 1) { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // 状态转移:其余行列 + for i in stride(from: 1, to: n, by: 1) { + for j in stride(from: 1, to: m, by: 1) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] + } + ``` + === "JS" ```javascript title="min_path_sum.js" @@ -837,95 +892,6 @@ $$ } ``` -=== "C" - - ```c title="min_path_sum.c" - [class]{}-[func]{minPathSumDP} - ``` - -=== "C#" - - ```csharp title="min_path_sum.cs" - /* 最小路径和:动态规划 */ - int minPathSumDP(int[][] grid) { - int n = grid.Length, m = grid[0].Length; - // 初始化 dp 表 - int[,] dp = new int[n, m]; - dp[0, 0] = grid[0][0]; - // 状态转移:首行 - for (int j = 1; j < m; j++) { - dp[0, j] = dp[0, j - 1] + grid[0][j]; - } - // 状态转移:首列 - for (int i = 1; i < n; i++) { - dp[i, 0] = dp[i - 1, 0] + grid[i][0]; - } - // 状态转移:其余行列 - for (int i = 1; i < n; i++) { - for (int j = 1; j < m; j++) { - dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; - } - } - return dp[n - 1, m - 1]; - } - ``` - -=== "Swift" - - ```swift title="min_path_sum.swift" - /* 最小路径和:动态规划 */ - func minPathSumDP(grid: [[Int]]) -> Int { - let n = grid.count - let m = grid[0].count - // 初始化 dp 表 - var dp = Array(repeating: Array(repeating: 0, count: m), count: n) - dp[0][0] = grid[0][0] - // 状态转移:首行 - for j in stride(from: 1, to: m, by: 1) { - dp[0][j] = dp[0][j - 1] + grid[0][j] - } - // 状态转移:首列 - for i in stride(from: 1, to: n, by: 1) { - dp[i][0] = dp[i - 1][0] + grid[i][0] - } - // 状态转移:其余行列 - for i in stride(from: 1, to: n, by: 1) { - for j in stride(from: 1, to: m, by: 1) { - dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] - } - } - return dp[n - 1][m - 1] - } - ``` - -=== "Zig" - - ```zig title="min_path_sum.zig" - // 最小路径和:动态规划 - fn minPathSumDP(comptime grid: anytype) i32 { - comptime var n = grid.len; - comptime var m = grid[0].len; - // 初始化 dp 表 - var dp = [_][m]i32{[_]i32{0} ** m} ** n; - dp[0][0] = grid[0][0]; - // 状态转移:首行 - for (1..m) |j| { - dp[0][j] = dp[0][j - 1] + grid[0][j]; - } - // 状态转移:首列 - for (1..n) |i| { - dp[i][0] = dp[i - 1][0] + grid[i][0]; - } - // 状态转移:其余行列 - for (1..n) |i| { - for (1..m) |j| { - dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; - } - } - return dp[n - 1][m - 1]; - } - ``` - === "Dart" ```dart title="min_path_sum.dart" @@ -980,6 +946,40 @@ $$ } ``` +=== "C" + + ```c title="min_path_sum.c" + [class]{}-[func]{minPathSumDP} + ``` + +=== "Zig" + + ```zig title="min_path_sum.zig" + // 最小路径和:动态规划 + fn minPathSumDP(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_][m]i32{[_]i32{0} ** m} ** n; + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (1..m) |j| { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (1..n) |i| { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行列 + for (1..n) |i| { + for (1..m) |j| { + dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + ``` + 图 14-16 展示了最小路径和的状态转移过程,其遍历了整个网格,**因此时间复杂度为 $O(nm)$** 。 数组 `dp` 大小为 $n \times m$ ,**因此空间复杂度为 $O(nm)$** 。 @@ -1028,30 +1028,26 @@ $$ 请注意,因为数组 `dp` 只能表示一行的状态,所以我们无法提前初始化首列状态,而是在遍历每行中更新它。 -=== "Java" +=== "Python" - ```java title="min_path_sum.java" - /* 最小路径和:空间优化后的动态规划 */ - int minPathSumDPComp(int[][] grid) { - int n = grid.length, m = grid[0].length; - // 初始化 dp 表 - int[] dp = new int[m]; - // 状态转移:首行 - dp[0] = grid[0][0]; - for (int j = 1; j < m; j++) { - dp[j] = dp[j - 1] + grid[0][j]; - } - // 状态转移:其余行 - for (int i = 1; i < n; i++) { - // 状态转移:首列 - dp[0] = dp[0] + grid[i][0]; - // 状态转移:其余列 - for (int j = 1; j < m; j++) { - dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; - } - } - return dp[m - 1]; - } + ```python title="min_path_sum.py" + def min_path_sum_dp_comp(grid: list[list[int]]) -> int: + """最小路径和:空间优化后的动态规划""" + n, m = len(grid), len(grid[0]) + # 初始化 dp 表 + dp = [0] * m + # 状态转移:首行 + dp[0] = grid[0][0] + for j in range(1, m): + dp[j] = dp[j - 1] + grid[0][j] + # 状态转移:其余行 + for i in range(1, n): + # 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + # 状态转移:其余列 + for j in range(1, m): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] ``` === "C++" @@ -1080,26 +1076,56 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="min_path_sum.py" - def min_path_sum_dp_comp(grid: list[list[int]]) -> int: - """最小路径和:空间优化后的动态规划""" - n, m = len(grid), len(grid[0]) - # 初始化 dp 表 - dp = [0] * m - # 状态转移:首行 - dp[0] = grid[0][0] - for j in range(1, m): - dp[j] = dp[j - 1] + grid[0][j] - # 状态转移:其余行 - for i in range(1, n): - # 状态转移:首列 - dp[0] = dp[0] + grid[i][0] - # 状态转移:其余列 - for j in range(1, m): - dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] - return dp[m - 1] + ```java title="min_path_sum.java" + /* 最小路径和:空间优化后的动态规划 */ + int minPathSumDPComp(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[] dp = new int[m]; + // 状态转移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + ``` + +=== "C#" + + ```csharp title="min_path_sum.cs" + /* 最小路径和:空间优化后的动态规划 */ + int minPathSumDPComp(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[] dp = new int[m]; + dp[0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } ``` === "Go" @@ -1128,6 +1154,33 @@ $$ } ``` +=== "Swift" + + ```swift title="min_path_sum.swift" + /* 最小路径和:空间优化后的动态规划 */ + func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: m) + // 状态转移:首行 + dp[0] = grid[0][0] + for j in stride(from: 1, to: m, by: 1) { + dp[j] = dp[j - 1] + grid[0][j] + } + // 状态转移:其余行 + for i in stride(from: 1, to: n, by: 1) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0] + // 状态转移:其余列 + for j in stride(from: 1, to: m, by: 1) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] + } + ``` + === "JS" ```javascript title="min_path_sum.js" @@ -1182,91 +1235,6 @@ $$ } ``` -=== "C" - - ```c title="min_path_sum.c" - [class]{}-[func]{minPathSumDPComp} - ``` - -=== "C#" - - ```csharp title="min_path_sum.cs" - /* 最小路径和:空间优化后的动态规划 */ - int minPathSumDPComp(int[][] grid) { - int n = grid.Length, m = grid[0].Length; - // 初始化 dp 表 - int[] dp = new int[m]; - dp[0] = grid[0][0]; - // 状态转移:首行 - for (int j = 1; j < m; j++) { - dp[j] = dp[j - 1] + grid[0][j]; - } - // 状态转移:其余行 - for (int i = 1; i < n; i++) { - // 状态转移:首列 - dp[0] = dp[0] + grid[i][0]; - // 状态转移:其余列 - for (int j = 1; j < m; j++) { - dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; - } - } - return dp[m - 1]; - } - ``` - -=== "Swift" - - ```swift title="min_path_sum.swift" - /* 最小路径和:空间优化后的动态规划 */ - func minPathSumDPComp(grid: [[Int]]) -> Int { - let n = grid.count - let m = grid[0].count - // 初始化 dp 表 - var dp = Array(repeating: 0, count: m) - // 状态转移:首行 - dp[0] = grid[0][0] - for j in stride(from: 1, to: m, by: 1) { - dp[j] = dp[j - 1] + grid[0][j] - } - // 状态转移:其余行 - for i in stride(from: 1, to: n, by: 1) { - // 状态转移:首列 - dp[0] = dp[0] + grid[i][0] - // 状态转移:其余列 - for j in stride(from: 1, to: m, by: 1) { - dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] - } - } - return dp[m - 1] - } - ``` - -=== "Zig" - - ```zig title="min_path_sum.zig" - // 最小路径和:空间优化后的动态规划 - fn minPathSumDPComp(comptime grid: anytype) i32 { - comptime var n = grid.len; - comptime var m = grid[0].len; - // 初始化 dp 表 - var dp = [_]i32{0} ** m; - // 状态转移:首行 - dp[0] = grid[0][0]; - for (1..m) |j| { - dp[j] = dp[j - 1] + grid[0][j]; - } - // 状态转移:其余行 - for (1..n) |i| { - // 状态转移:首列 - dp[0] = dp[0] + grid[i][0]; - for (1..m) |j| { - dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; - } - } - return dp[m - 1]; - } - ``` - === "Dart" ```dart title="min_path_sum.dart" @@ -1317,3 +1285,35 @@ $$ dp[m - 1] } ``` + +=== "C" + + ```c title="min_path_sum.c" + [class]{}-[func]{minPathSumDPComp} + ``` + +=== "Zig" + + ```zig title="min_path_sum.zig" + // 最小路径和:空间优化后的动态规划 + fn minPathSumDPComp(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_]i32{0} ** m; + // 状态转移:首行 + dp[0] = grid[0][0]; + for (1..m) |j| { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (1..n) |i| { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + for (1..m) |j| { + dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + ``` diff --git a/chapter_dynamic_programming/edit_distance_problem.md b/chapter_dynamic_programming/edit_distance_problem.md index f1ca020b4..ecef8fa73 100644 --- a/chapter_dynamic_programming/edit_distance_problem.md +++ b/chapter_dynamic_programming/edit_distance_problem.md @@ -78,34 +78,28 @@ $$ ### 2.   代码实现 -=== "Java" +=== "Python" - ```java title="edit_distance.java" - /* 编辑距离:动态规划 */ - int editDistanceDP(String s, String t) { - int n = s.length(), m = t.length(); - int[][] dp = new int[n + 1][m + 1]; - // 状态转移:首行首列 - for (int i = 1; i <= n; i++) { - dp[i][0] = i; - } - for (int j = 1; j <= m; j++) { - dp[0][j] = j; - } - // 状态转移:其余行列 - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - if (s.charAt(i - 1) == t.charAt(j - 1)) { - // 若两字符相等,则直接跳过此两字符 - dp[i][j] = dp[i - 1][j - 1]; - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; - } - } - } - return dp[n][m]; - } + ```python title="edit_distance.py" + def edit_distance_dp(s: str, t: str) -> int: + """编辑距离:动态规划""" + n, m = len(s), len(t) + dp = [[0] * (m + 1) for _ in range(n + 1)] + # 状态转移:首行首列 + for i in range(1, n + 1): + dp[i][0] = i + for j in range(1, m + 1): + dp[0][j] = j + # 状态转移:其余行列 + for i in range(1, n + 1): + for j in range(1, m + 1): + if s[i - 1] == t[j - 1]: + # 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + else: + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 + return dp[n][m] ``` === "C++" @@ -138,28 +132,64 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="edit_distance.py" - def edit_distance_dp(s: str, t: str) -> int: - """编辑距离:动态规划""" - n, m = len(s), len(t) - dp = [[0] * (m + 1) for _ in range(n + 1)] - # 状态转移:首行首列 - for i in range(1, n + 1): - dp[i][0] = i - for j in range(1, m + 1): - dp[0][j] = j - # 状态转移:其余行列 - for i in range(1, n + 1): - for j in range(1, m + 1): - if s[i - 1] == t[j - 1]: - # 若两字符相等,则直接跳过此两字符 - dp[i][j] = dp[i - 1][j - 1] - else: - # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 - return dp[n][m] + ```java title="edit_distance.java" + /* 编辑距离:动态规划 */ + int editDistanceDP(String s, String t) { + int n = s.length(), m = t.length(); + int[][] dp = new int[n + 1][m + 1]; + // 状态转移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + ``` + +=== "C#" + + ```csharp title="edit_distance.cs" + /* 编辑距离:动态规划 */ + int editDistanceDP(string s, string t) { + int n = s.Length, m = t.Length; + int[,] dp = new int[n + 1, m + 1]; + // 状态转移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i, 0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0, j] = j; + } + // 状态转移:其余行列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i, j] = dp[i - 1, j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; + } + } + } + return dp[n, m]; + } ``` === "Go" @@ -196,6 +226,37 @@ $$ } ``` +=== "Swift" + + ```swift title="edit_distance.swift" + /* 编辑距离:动态规划 */ + func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // 状态转移:首行首列 + for i in stride(from: 1, through: n, by: 1) { + dp[i][0] = i + } + for j in stride(from: 1, through: m, by: 1) { + dp[0][j] = j + } + // 状态转移:其余行列 + for i in stride(from: 1, through: n, by: 1) { + for j in stride(from: 1, through: m, by: 1) { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] + } + ``` + === "JS" ```javascript title="edit_distance.js" @@ -268,104 +329,6 @@ $$ } ``` -=== "C" - - ```c title="edit_distance.c" - [class]{}-[func]{editDistanceDP} - ``` - -=== "C#" - - ```csharp title="edit_distance.cs" - /* 编辑距离:动态规划 */ - int editDistanceDP(string s, string t) { - int n = s.Length, m = t.Length; - int[,] dp = new int[n + 1, m + 1]; - // 状态转移:首行首列 - for (int i = 1; i <= n; i++) { - dp[i, 0] = i; - } - for (int j = 1; j <= m; j++) { - dp[0, j] = j; - } - // 状态转移:其余行列 - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - if (s[i - 1] == t[j - 1]) { - // 若两字符相等,则直接跳过此两字符 - dp[i, j] = dp[i - 1, j - 1]; - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; - } - } - } - return dp[n, m]; - } - ``` - -=== "Swift" - - ```swift title="edit_distance.swift" - /* 编辑距离:动态规划 */ - func editDistanceDP(s: String, t: String) -> Int { - let n = s.utf8CString.count - let m = t.utf8CString.count - var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) - // 状态转移:首行首列 - for i in stride(from: 1, through: n, by: 1) { - dp[i][0] = i - } - for j in stride(from: 1, through: m, by: 1) { - dp[0][j] = j - } - // 状态转移:其余行列 - for i in stride(from: 1, through: n, by: 1) { - for j in stride(from: 1, through: m, by: 1) { - if s.utf8CString[i - 1] == t.utf8CString[j - 1] { - // 若两字符相等,则直接跳过此两字符 - dp[i][j] = dp[i - 1][j - 1] - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 - } - } - } - return dp[n][m] - } - ``` - -=== "Zig" - - ```zig title="edit_distance.zig" - // 编辑距离:动态规划 - fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { - comptime var n = s.len; - comptime var m = t.len; - var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); - // 状态转移:首行首列 - for (1..n + 1) |i| { - dp[i][0] = @intCast(i); - } - for (1..m + 1) |j| { - dp[0][j] = @intCast(j); - } - // 状态转移:其余行列 - for (1..n + 1) |i| { - for (1..m + 1) |j| { - if (s[i - 1] == t[j - 1]) { - // 若两字符相等,则直接跳过此两字符 - dp[i][j] = dp[i - 1][j - 1]; - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; - } - } - } - return dp[n][m]; - } - ``` - === "Dart" ```dart title="edit_distance.dart" @@ -426,6 +389,43 @@ $$ } ``` +=== "C" + + ```c title="edit_distance.c" + [class]{}-[func]{editDistanceDP} + ``` + +=== "Zig" + + ```zig title="edit_distance.zig" + // 编辑距离:动态规划 + fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); + // 状态转移:首行首列 + for (1..n + 1) |i| { + dp[i][0] = @intCast(i); + } + for (1..m + 1) |j| { + dp[0][j] = @intCast(j); + } + // 状态转移:其余行列 + for (1..n + 1) |i| { + for (1..m + 1) |j| { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + ``` + 如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作是填写一个二维网格的过程。 === "<1>" @@ -481,37 +481,32 @@ $$ 为此,我们可以使用一个变量 `leftup` 来暂存左上方的解 $dp[i-1, j-1]$ ,从而只需考虑左方和上方的解。此时的情况与完全背包问题相同,可使用正序遍历。 -=== "Java" +=== "Python" - ```java title="edit_distance.java" - /* 编辑距离:空间优化后的动态规划 */ - int editDistanceDPComp(String s, String t) { - int n = s.length(), m = t.length(); - int[] dp = new int[m + 1]; - // 状态转移:首行 - for (int j = 1; j <= m; j++) { - dp[j] = j; - } - // 状态转移:其余行 - for (int i = 1; i <= n; i++) { - // 状态转移:首列 - int leftup = dp[0]; // 暂存 dp[i-1, j-1] - dp[0] = i; - // 状态转移:其余列 - for (int j = 1; j <= m; j++) { - int temp = dp[j]; - if (s.charAt(i - 1) == t.charAt(j - 1)) { - // 若两字符相等,则直接跳过此两字符 - dp[j] = leftup; - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; - } - leftup = temp; // 更新为下一轮的 dp[i-1, j-1] - } - } - return dp[m]; - } + ```python title="edit_distance.py" + def edit_distance_dp_comp(s: str, t: str) -> int: + """编辑距离:空间优化后的动态规划""" + n, m = len(s), len(t) + dp = [0] * (m + 1) + # 状态转移:首行 + for j in range(1, m + 1): + dp[j] = j + # 状态转移:其余行 + for i in range(1, n + 1): + # 状态转移:首列 + leftup = dp[0] # 暂存 dp[i-1, j-1] + dp[0] += 1 + # 状态转移:其余列 + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + else: + # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # 更新为下一轮的 dp[i-1, j-1] + return dp[m] ``` === "C++" @@ -547,32 +542,70 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="edit_distance.py" - def edit_distance_dp_comp(s: str, t: str) -> int: - """编辑距离:空间优化后的动态规划""" - n, m = len(s), len(t) - dp = [0] * (m + 1) - # 状态转移:首行 - for j in range(1, m + 1): - dp[j] = j - # 状态转移:其余行 - for i in range(1, n + 1): - # 状态转移:首列 - leftup = dp[0] # 暂存 dp[i-1, j-1] - dp[0] += 1 - # 状态转移:其余列 - for j in range(1, m + 1): - temp = dp[j] - if s[i - 1] == t[j - 1]: - # 若两字符相等,则直接跳过此两字符 - dp[j] = leftup - else: - # 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[j] = min(dp[j - 1], dp[j], leftup) + 1 - leftup = temp # 更新为下一轮的 dp[i-1, j-1] - return dp[m] + ```java title="edit_distance.java" + /* 编辑距离:空间优化后的动态规划 */ + int editDistanceDPComp(String s, String t) { + int n = s.length(), m = t.length(); + int[] dp = new int[m + 1]; + // 状态转移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (int i = 1; i <= n; i++) { + // 状态转移:首列 + int leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; + } + ``` + +=== "C#" + + ```csharp title="edit_distance.cs" + /* 编辑距离:空间优化后的动态规划 */ + int editDistanceDPComp(string s, string t) { + int n = s.Length, m = t.Length; + int[] dp = new int[m + 1]; + // 状态转移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (int i = 1; i <= n; i++) { + // 状态转移:首列 + int leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; + } ``` === "Go" @@ -609,6 +642,40 @@ $$ } ``` +=== "Swift" + + ```swift title="edit_distance.swift" + /* 编辑距离:空间优化后的动态规划 */ + func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // 状态转移:首行 + for j in stride(from: 1, through: m, by: 1) { + dp[j] = j + } + // 状态转移:其余行 + for i in stride(from: 1, through: n, by: 1) { + // 状态转移:首列 + var leftup = dp[0] // 暂存 dp[i-1, j-1] + dp[0] = i + // 状态转移:其余列 + for j in stride(from: 1, through: m, by: 1) { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m] + } + ``` + === "JS" ```javascript title="edit_distance.js" @@ -677,113 +744,6 @@ $$ } ``` -=== "C" - - ```c title="edit_distance.c" - [class]{}-[func]{editDistanceDPComp} - ``` - -=== "C#" - - ```csharp title="edit_distance.cs" - /* 编辑距离:空间优化后的动态规划 */ - int editDistanceDPComp(string s, string t) { - int n = s.Length, m = t.Length; - int[] dp = new int[m + 1]; - // 状态转移:首行 - for (int j = 1; j <= m; j++) { - dp[j] = j; - } - // 状态转移:其余行 - for (int i = 1; i <= n; i++) { - // 状态转移:首列 - int leftup = dp[0]; // 暂存 dp[i-1, j-1] - dp[0] = i; - // 状态转移:其余列 - for (int j = 1; j <= m; j++) { - int temp = dp[j]; - if (s[i - 1] == t[j - 1]) { - // 若两字符相等,则直接跳过此两字符 - dp[j] = leftup; - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; - } - leftup = temp; // 更新为下一轮的 dp[i-1, j-1] - } - } - return dp[m]; - } - ``` - -=== "Swift" - - ```swift title="edit_distance.swift" - /* 编辑距离:空间优化后的动态规划 */ - func editDistanceDPComp(s: String, t: String) -> Int { - let n = s.utf8CString.count - let m = t.utf8CString.count - var dp = Array(repeating: 0, count: m + 1) - // 状态转移:首行 - for j in stride(from: 1, through: m, by: 1) { - dp[j] = j - } - // 状态转移:其余行 - for i in stride(from: 1, through: n, by: 1) { - // 状态转移:首列 - var leftup = dp[0] // 暂存 dp[i-1, j-1] - dp[0] = i - // 状态转移:其余列 - for j in stride(from: 1, through: m, by: 1) { - let temp = dp[j] - if s.utf8CString[i - 1] == t.utf8CString[j - 1] { - // 若两字符相等,则直接跳过此两字符 - dp[j] = leftup - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 - } - leftup = temp // 更新为下一轮的 dp[i-1, j-1] - } - } - return dp[m] - } - ``` - -=== "Zig" - - ```zig title="edit_distance.zig" - // 编辑距离:空间优化后的动态规划 - fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { - comptime var n = s.len; - comptime var m = t.len; - var dp = [_]i32{0} ** (m + 1); - // 状态转移:首行 - for (1..m + 1) |j| { - dp[j] = @intCast(j); - } - // 状态转移:其余行 - for (1..n + 1) |i| { - // 状态转移:首列 - var leftup = dp[0]; // 暂存 dp[i-1, j-1] - dp[0] = @intCast(i); - // 状态转移:其余列 - for (1..m + 1) |j| { - var temp = dp[j]; - if (s[i - 1] == t[j - 1]) { - // 若两字符相等,则直接跳过此两字符 - dp[j] = leftup; - } else { - // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 - dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; - } - leftup = temp; // 更新为下一轮的 dp[i-1, j-1] - } - } - return dp[m]; - } - ``` - === "Dart" ```dart title="edit_distance.dart" @@ -849,3 +809,43 @@ $$ dp[m] } ``` + +=== "C" + + ```c title="edit_distance.c" + [class]{}-[func]{editDistanceDPComp} + ``` + +=== "Zig" + + ```zig title="edit_distance.zig" + // 编辑距离:空间优化后的动态规划 + fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_]i32{0} ** (m + 1); + // 状态转移:首行 + for (1..m + 1) |j| { + dp[j] = @intCast(j); + } + // 状态转移:其余行 + for (1..n + 1) |i| { + // 状态转移:首列 + var leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = @intCast(i); + // 状态转移:其余列 + for (1..m + 1) |j| { + var temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; + } + ``` diff --git a/chapter_dynamic_programming/intro_to_dynamic_programming.md b/chapter_dynamic_programming/intro_to_dynamic_programming.md index 6b6c13de0..d49d26866 100644 --- a/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -21,34 +21,30 @@ status: new 本题的目标是求解方案数量,**我们可以考虑通过回溯来穷举所有可能性**。具体来说,将爬楼梯想象为一个多轮选择的过程:从地面出发,每轮选择上 $1$ 阶或 $2$ 阶,每当到达楼梯顶部时就将方案数量加 $1$ ,当越过楼梯顶部时就将其剪枝。 -=== "Java" +=== "Python" - ```java title="climbing_stairs_backtrack.java" - /* 回溯 */ - void backtrack(List choices, int state, int n, List res) { - // 当爬到第 n 阶时,方案数量加 1 - if (state == n) - res.set(0, res.get(0) + 1); - // 遍历所有选择 - for (Integer choice : choices) { - // 剪枝:不允许越过第 n 阶 - if (state + choice > n) - break; - // 尝试:做出选择,更新状态 - backtrack(choices, state + choice, n, res); - // 回退 - } - } + ```python title="climbing_stairs_backtrack.py" + def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """回溯""" + # 当爬到第 n 阶时,方案数量加 1 + if state == n: + res[0] += 1 + # 遍历所有选择 + for choice in choices: + # 剪枝:不允许越过第 n 阶 + if state + choice > n: + break + # 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res) + # 回退 - /* 爬楼梯:回溯 */ - int climbingStairsBacktrack(int n) { - List choices = Arrays.asList(1, 2); // 可选择向上爬 1 或 2 阶 - int state = 0; // 从第 0 阶开始爬 - List res = new ArrayList<>(); - res.add(0); // 使用 res[0] 记录方案数量 - backtrack(choices, state, n, res); - return res.get(0); - } + def climbing_stairs_backtrack(n: int) -> int: + """爬楼梯:回溯""" + choices = [1, 2] # 可选择向上爬 1 或 2 阶 + state = 0 # 从第 0 阶开始爬 + res = [0] # 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res) + return res[0] ``` === "C++" @@ -80,30 +76,63 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="climbing_stairs_backtrack.py" - def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: - """回溯""" - # 当爬到第 n 阶时,方案数量加 1 - if state == n: - res[0] += 1 - # 遍历所有选择 - for choice in choices: - # 剪枝:不允许越过第 n 阶 - if state + choice > n: - break - # 尝试:做出选择,更新状态 - backtrack(choices, state + choice, n, res) - # 回退 + ```java title="climbing_stairs_backtrack.java" + /* 回溯 */ + void backtrack(List choices, int state, int n, List res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res.set(0, res.get(0) + 1); + // 遍历所有选择 + for (Integer choice : choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + break; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } + } - def climbing_stairs_backtrack(n: int) -> int: - """爬楼梯:回溯""" - choices = [1, 2] # 可选择向上爬 1 或 2 阶 - state = 0 # 从第 0 阶开始爬 - res = [0] # 使用 res[0] 记录方案数量 - backtrack(choices, state, n, res) - return res[0] + /* 爬楼梯:回溯 */ + int climbingStairsBacktrack(int n) { + List choices = Arrays.asList(1, 2); // 可选择向上爬 1 或 2 阶 + int state = 0; // 从第 0 阶开始爬 + List res = new ArrayList<>(); + res.add(0); // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res.get(0); + } + ``` + +=== "C#" + + ```csharp title="climbing_stairs_backtrack.cs" + /* 回溯 */ + void backtrack(List choices, int state, int n, List res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res[0]++; + // 遍历所有选择 + foreach (int choice in choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + break; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬楼梯:回溯 */ + int climbingStairsBacktrack(int n) { + List choices = new List { 1, 2 }; // 可选择向上爬 1 或 2 阶 + int state = 0; // 从第 0 阶开始爬 + List res = new List { 0 }; // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res[0]; + } ``` === "Go" @@ -141,6 +170,36 @@ status: new } ``` +=== "Swift" + + ```swift title="climbing_stairs_backtrack.swift" + /* 回溯 */ + func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // 当爬到第 n 阶时,方案数量加 1 + if state == n { + res[0] += 1 + } + // 遍历所有选择 + for choice in choices { + // 剪枝:不允许越过第 n 阶 + if state + choice > n { + break + } + backtrack(choices: choices, state: state + choice, n: n, res: &res) + } + } + + /* 爬楼梯:回溯 */ + func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // 可选择向上爬 1 或 2 阶 + let state = 0 // 从第 0 阶开始爬 + var res: [Int] = [] + res.append(0) // 使用 res[0] 记录方案数量 + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] + } + ``` + === "JS" ```javascript title="climbing_stairs_backtrack.js" @@ -202,106 +261,6 @@ status: new } ``` -=== "C" - - ```c title="climbing_stairs_backtrack.c" - [class]{}-[func]{backtrack} - - [class]{}-[func]{climbingStairsBacktrack} - ``` - -=== "C#" - - ```csharp title="climbing_stairs_backtrack.cs" - /* 回溯 */ - void backtrack(List choices, int state, int n, List res) { - // 当爬到第 n 阶时,方案数量加 1 - if (state == n) - res[0]++; - // 遍历所有选择 - foreach (int choice in choices) { - // 剪枝:不允许越过第 n 阶 - if (state + choice > n) - break; - // 尝试:做出选择,更新状态 - backtrack(choices, state + choice, n, res); - // 回退 - } - } - - /* 爬楼梯:回溯 */ - int climbingStairsBacktrack(int n) { - List choices = new List { 1, 2 }; // 可选择向上爬 1 或 2 阶 - int state = 0; // 从第 0 阶开始爬 - List res = new List { 0 }; // 使用 res[0] 记录方案数量 - backtrack(choices, state, n, res); - return res[0]; - } - ``` - -=== "Swift" - - ```swift title="climbing_stairs_backtrack.swift" - /* 回溯 */ - func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { - // 当爬到第 n 阶时,方案数量加 1 - if state == n { - res[0] += 1 - } - // 遍历所有选择 - for choice in choices { - // 剪枝:不允许越过第 n 阶 - if state + choice > n { - break - } - backtrack(choices: choices, state: state + choice, n: n, res: &res) - } - } - - /* 爬楼梯:回溯 */ - func climbingStairsBacktrack(n: Int) -> Int { - let choices = [1, 2] // 可选择向上爬 1 或 2 阶 - let state = 0 // 从第 0 阶开始爬 - var res: [Int] = [] - res.append(0) // 使用 res[0] 记录方案数量 - backtrack(choices: choices, state: state, n: n, res: &res) - return res[0] - } - ``` - -=== "Zig" - - ```zig title="climbing_stairs_backtrack.zig" - // 回溯 - fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { - // 当爬到第 n 阶时,方案数量加 1 - if (state == n) { - res.items[0] = res.items[0] + 1; - } - // 遍历所有选择 - for (choices) |choice| { - // 剪枝:不允许越过第 n 阶 - if (state + choice > n) { - break; - } - // 尝试:做出选择,更新状态 - backtrack(choices, state + choice, n, res); - // 回退 - } - } - - // 爬楼梯:回溯 - fn climbingStairsBacktrack(n: usize) !i32 { - var choices = [_]i32{ 1, 2 }; // 可选择向上爬 1 或 2 阶 - var state: i32 = 0; // 从第 0 阶开始爬 - var res = std.ArrayList(i32).init(std.heap.page_allocator); - defer res.deinit(); - try res.append(0); // 使用 res[0] 记录方案数量 - backtrack(&choices, state, @intCast(n), res); - return res.items[0]; - } - ``` - === "Dart" ```dart title="climbing_stairs_backtrack.dart" @@ -360,6 +319,47 @@ status: new } ``` +=== "C" + + ```c title="climbing_stairs_backtrack.c" + [class]{}-[func]{backtrack} + + [class]{}-[func]{climbingStairsBacktrack} + ``` + +=== "Zig" + + ```zig title="climbing_stairs_backtrack.zig" + // 回溯 + fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) { + res.items[0] = res.items[0] + 1; + } + // 遍历所有选择 + for (choices) |choice| { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) { + break; + } + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } + } + + // 爬楼梯:回溯 + fn climbingStairsBacktrack(n: usize) !i32 { + var choices = [_]i32{ 1, 2 }; // 可选择向上爬 1 或 2 阶 + var state: i32 = 0; // 从第 0 阶开始爬 + var res = std.ArrayList(i32).init(std.heap.page_allocator); + defer res.deinit(); + try res.append(0); // 使用 res[0] 记录方案数量 + backtrack(&choices, state, @intCast(n), res); + return res.items[0]; + } + ``` + ## 14.1.1   方法一:暴力搜索 回溯算法通常并不显式地对问题进行拆解,而是将问题看作一系列决策步骤,通过试探和剪枝,搜索所有可能的解。 @@ -388,23 +388,21 @@ $$ 观察以下代码,它和标准回溯代码都属于深度优先搜索,但更加简洁。 -=== "Java" +=== "Python" - ```java title="climbing_stairs_dfs.java" - /* 搜索 */ - int dfs(int i) { - // 已知 dp[1] 和 dp[2] ,返回之 - if (i == 1 || i == 2) - return i; - // dp[i] = dp[i-1] + dp[i-2] - int count = dfs(i - 1) + dfs(i - 2); - return count; - } + ```python title="climbing_stairs_dfs.py" + def dfs(i: int) -> int: + """搜索""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count - /* 爬楼梯:搜索 */ - int climbingStairsDFS(int n) { - return dfs(n); - } + def climbing_stairs_dfs(n: int) -> int: + """爬楼梯:搜索""" + return dfs(n) ``` === "C++" @@ -426,21 +424,42 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="climbing_stairs_dfs.py" - def dfs(i: int) -> int: - """搜索""" - # 已知 dp[1] 和 dp[2] ,返回之 - if i == 1 or i == 2: - return i - # dp[i] = dp[i-1] + dp[i-2] - count = dfs(i - 1) + dfs(i - 2) - return count + ```java title="climbing_stairs_dfs.java" + /* 搜索 */ + int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; + } - def climbing_stairs_dfs(n: int) -> int: - """爬楼梯:搜索""" - return dfs(n) + /* 爬楼梯:搜索 */ + int climbingStairsDFS(int n) { + return dfs(n); + } + ``` + +=== "C#" + + ```csharp title="climbing_stairs_dfs.cs" + /* 搜索 */ + int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; + } + + /* 爬楼梯:搜索 */ + int climbingStairsDFS(int n) { + return dfs(n); + } ``` === "Go" @@ -463,6 +482,26 @@ $$ } ``` +=== "Swift" + + ```swift title="climbing_stairs_dfs.swift" + /* 搜索 */ + func dfs(i: Int) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count + } + + /* 爬楼梯:搜索 */ + func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) + } + ``` + === "JS" ```javascript title="climbing_stairs_dfs.js" @@ -499,73 +538,6 @@ $$ } ``` -=== "C" - - ```c title="climbing_stairs_dfs.c" - [class]{}-[func]{dfs} - - [class]{}-[func]{climbingStairsDFS} - ``` - -=== "C#" - - ```csharp title="climbing_stairs_dfs.cs" - /* 搜索 */ - int dfs(int i) { - // 已知 dp[1] 和 dp[2] ,返回之 - if (i == 1 || i == 2) - return i; - // dp[i] = dp[i-1] + dp[i-2] - int count = dfs(i - 1) + dfs(i - 2); - return count; - } - - /* 爬楼梯:搜索 */ - int climbingStairsDFS(int n) { - return dfs(n); - } - ``` - -=== "Swift" - - ```swift title="climbing_stairs_dfs.swift" - /* 搜索 */ - func dfs(i: Int) -> Int { - // 已知 dp[1] 和 dp[2] ,返回之 - if i == 1 || i == 2 { - return i - } - // dp[i] = dp[i-1] + dp[i-2] - let count = dfs(i: i - 1) + dfs(i: i - 2) - return count - } - - /* 爬楼梯:搜索 */ - func climbingStairsDFS(n: Int) -> Int { - dfs(i: n) - } - ``` - -=== "Zig" - - ```zig title="climbing_stairs_dfs.zig" - // 搜索 - fn dfs(i: usize) i32 { - // 已知 dp[1] 和 dp[2] ,返回之 - if (i == 1 or i == 2) { - return @intCast(i); - } - // dp[i] = dp[i-1] + dp[i-2] - var count = dfs(i - 1) + dfs(i - 2); - return count; - } - - // 爬楼梯:搜索 - fn climbingStairsDFS(comptime n: usize) i32 { - return dfs(n); - } - ``` - === "Dart" ```dart title="climbing_stairs_dfs.dart" @@ -602,6 +574,34 @@ $$ } ``` +=== "C" + + ```c title="climbing_stairs_dfs.c" + [class]{}-[func]{dfs} + + [class]{}-[func]{climbingStairsDFS} + ``` + +=== "Zig" + + ```zig title="climbing_stairs_dfs.zig" + // 搜索 + fn dfs(i: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1) + dfs(i - 2); + return count; + } + + // 爬楼梯:搜索 + fn climbingStairsDFS(comptime n: usize) i32 { + return dfs(n); + } + ``` + 图 14-3 展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 ![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) @@ -619,31 +619,28 @@ $$ 1. 当首次计算 $dp[i]$ 时,我们将其记录至 `mem[i]` ,以便之后使用。 2. 当再次需要计算 $dp[i]$ 时,我们便可直接从 `mem[i]` 中获取结果,从而避免重复计算该子问题。 -=== "Java" +=== "Python" - ```java title="climbing_stairs_dfs_mem.java" - /* 记忆化搜索 */ - int dfs(int i, int[] mem) { - // 已知 dp[1] 和 dp[2] ,返回之 - if (i == 1 || i == 2) - return i; - // 若存在记录 dp[i] ,则直接返回之 - if (mem[i] != -1) - return mem[i]; - // dp[i] = dp[i-1] + dp[i-2] - int count = dfs(i - 1, mem) + dfs(i - 2, mem); - // 记录 dp[i] - mem[i] = count; - return count; - } + ```python title="climbing_stairs_dfs_mem.py" + def dfs(i: int, mem: list[int]) -> int: + """记忆化搜索""" + # 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 or i == 2: + return i + # 若存在记录 dp[i] ,则直接返回之 + if mem[i] != -1: + return mem[i] + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # 记录 dp[i] + mem[i] = count + return count - /* 爬楼梯:记忆化搜索 */ - int climbingStairsDFSMem(int n) { - // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 - int[] mem = new int[n + 1]; - Arrays.fill(mem, -1); - return dfs(n, mem); - } + def climbing_stairs_dfs_mem(n: int) -> int: + """爬楼梯:记忆化搜索""" + # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + mem = [-1] * (n + 1) + return dfs(n, mem) ``` === "C++" @@ -672,28 +669,58 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="climbing_stairs_dfs_mem.py" - def dfs(i: int, mem: list[int]) -> int: - """记忆化搜索""" - # 已知 dp[1] 和 dp[2] ,返回之 - if i == 1 or i == 2: - return i - # 若存在记录 dp[i] ,则直接返回之 - if mem[i] != -1: - return mem[i] - # dp[i] = dp[i-1] + dp[i-2] - count = dfs(i - 1, mem) + dfs(i - 2, mem) - # 记录 dp[i] - mem[i] = count - return count + ```java title="climbing_stairs_dfs_mem.java" + /* 记忆化搜索 */ + int dfs(int i, int[] mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; + } - def climbing_stairs_dfs_mem(n: int) -> int: - """爬楼梯:记忆化搜索""" - # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 - mem = [-1] * (n + 1) - return dfs(n, mem) + /* 爬楼梯:记忆化搜索 */ + int climbingStairsDFSMem(int n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + ``` + +=== "C#" + + ```csharp title="climbing_stairs_dfs_mem.cs" + /* 记忆化搜索 */ + int dfs(int i, int[] mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; + } + + /* 爬楼梯:记忆化搜索 */ + int climbingStairsDFSMem(int n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + int[] mem = new int[n + 1]; + Array.Fill(mem, -1); + return dfs(n, mem); + } ``` === "Go" @@ -727,6 +754,34 @@ $$ } ``` +=== "Swift" + + ```swift title="climbing_stairs_dfs_mem.swift" + /* 记忆化搜索 */ + func dfs(i: Int, mem: inout [Int]) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // 若存在记录 dp[i] ,则直接返回之 + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // 记录 dp[i] + mem[i] = count + return count + } + + /* 爬楼梯:记忆化搜索 */ + func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) + } + ``` + === "JS" ```javascript title="climbing_stairs_dfs_mem.js" @@ -775,97 +830,6 @@ $$ } ``` -=== "C" - - ```c title="climbing_stairs_dfs_mem.c" - [class]{}-[func]{dfs} - - [class]{}-[func]{climbingStairsDFSMem} - ``` - -=== "C#" - - ```csharp title="climbing_stairs_dfs_mem.cs" - /* 记忆化搜索 */ - int dfs(int i, int[] mem) { - // 已知 dp[1] 和 dp[2] ,返回之 - if (i == 1 || i == 2) - return i; - // 若存在记录 dp[i] ,则直接返回之 - if (mem[i] != -1) - return mem[i]; - // dp[i] = dp[i-1] + dp[i-2] - int count = dfs(i - 1, mem) + dfs(i - 2, mem); - // 记录 dp[i] - mem[i] = count; - return count; - } - - /* 爬楼梯:记忆化搜索 */ - int climbingStairsDFSMem(int n) { - // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 - int[] mem = new int[n + 1]; - Array.Fill(mem, -1); - return dfs(n, mem); - } - ``` - -=== "Swift" - - ```swift title="climbing_stairs_dfs_mem.swift" - /* 记忆化搜索 */ - func dfs(i: Int, mem: inout [Int]) -> Int { - // 已知 dp[1] 和 dp[2] ,返回之 - if i == 1 || i == 2 { - return i - } - // 若存在记录 dp[i] ,则直接返回之 - if mem[i] != -1 { - return mem[i] - } - // dp[i] = dp[i-1] + dp[i-2] - let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) - // 记录 dp[i] - mem[i] = count - return count - } - - /* 爬楼梯:记忆化搜索 */ - func climbingStairsDFSMem(n: Int) -> Int { - // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 - var mem = Array(repeating: -1, count: n + 1) - return dfs(i: n, mem: &mem) - } - ``` - -=== "Zig" - - ```zig title="climbing_stairs_dfs_mem.zig" - // 记忆化搜索 - fn dfs(i: usize, mem: []i32) i32 { - // 已知 dp[1] 和 dp[2] ,返回之 - if (i == 1 or i == 2) { - return @intCast(i); - } - // 若存在记录 dp[i] ,则直接返回之 - if (mem[i] != -1) { - return mem[i]; - } - // dp[i] = dp[i-1] + dp[i-2] - var count = dfs(i - 1, mem) + dfs(i - 2, mem); - // 记录 dp[i] - mem[i] = count; - return count; - } - - // 爬楼梯:记忆化搜索 - fn climbingStairsDFSMem(comptime n: usize) i32 { - // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 - var mem = [_]i32{ -1 } ** (n + 1); - return dfs(n, &mem); - } - ``` - === "Dart" ```dart title="climbing_stairs_dfs_mem.dart" @@ -914,6 +878,42 @@ $$ } ``` +=== "C" + + ```c title="climbing_stairs_dfs_mem.c" + [class]{}-[func]{dfs} + + [class]{}-[func]{climbingStairsDFSMem} + ``` + +=== "Zig" + + ```zig title="climbing_stairs_dfs_mem.zig" + // 记忆化搜索 + fn dfs(i: usize, mem: []i32) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; + } + + // 爬楼梯:记忆化搜索 + fn climbingStairsDFSMem(comptime n: usize) i32 { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + var mem = [_]i32{ -1 } ** (n + 1); + return dfs(n, &mem); + } + ``` + 观察图 14-4 ,**经过记忆化处理后,所有重叠子问题都只需被计算一次,时间复杂度被优化至 $O(n)$** ,这是一个巨大的飞跃。 ![记忆化搜索对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) @@ -928,24 +928,21 @@ $$ 由于动态规划不包含回溯过程,因此只需使用循环迭代实现,无须使用递归。在以下代码中,我们初始化一个数组 `dp` 来存储子问题的解,它起到了记忆化搜索中数组 `mem` 相同的记录作用。 -=== "Java" +=== "Python" - ```java title="climbing_stairs_dp.java" - /* 爬楼梯:动态规划 */ - int climbingStairsDP(int n) { - if (n == 1 || n == 2) - return n; - // 初始化 dp 表,用于存储子问题的解 - int[] dp = new int[n + 1]; - // 初始状态:预设最小子问题的解 - dp[1] = 1; - dp[2] = 2; - // 状态转移:从较小子问题逐步求解较大子问题 - for (int i = 3; i <= n; i++) { - dp[i] = dp[i - 1] + dp[i - 2]; - } - return dp[n]; - } + ```python title="climbing_stairs_dp.py" + def climbing_stairs_dp(n: int) -> int: + """爬楼梯:动态规划""" + if n == 1 or n == 2: + return n + # 初始化 dp 表,用于存储子问题的解 + dp = [0] * (n + 1) + # 初始状态:预设最小子问题的解 + dp[1], dp[2] = 1, 2 + # 状态转移:从较小子问题逐步求解较大子问题 + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] ``` === "C++" @@ -968,21 +965,44 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="climbing_stairs_dp.py" - def climbing_stairs_dp(n: int) -> int: - """爬楼梯:动态规划""" - if n == 1 or n == 2: - return n - # 初始化 dp 表,用于存储子问题的解 - dp = [0] * (n + 1) - # 初始状态:预设最小子问题的解 - dp[1], dp[2] = 1, 2 - # 状态转移:从较小子问题逐步求解较大子问题 - for i in range(3, n + 1): - dp[i] = dp[i - 1] + dp[i - 2] - return dp[n] + ```java title="climbing_stairs_dp.java" + /* 爬楼梯:动态规划 */ + int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + ``` + +=== "C#" + + ```csharp title="climbing_stairs_dp.cs" + /* 爬楼梯:动态规划 */ + int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + int[] dp = new int[n + 1]; + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } ``` === "Go" @@ -1006,6 +1026,27 @@ $$ } ``` +=== "Swift" + + ```swift title="climbing_stairs_dp.swift" + /* 爬楼梯:动态规划 */ + func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用于存储子问题的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始状态:预设最小子问题的解 + dp[1] = 1 + dp[2] = 2 + // 状态转移:从较小子问题逐步求解较大子问题 + for i in stride(from: 3, through: n, by: 1) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] + } + ``` + === "JS" ```javascript title="climbing_stairs_dp.js" @@ -1044,75 +1085,6 @@ $$ } ``` -=== "C" - - ```c title="climbing_stairs_dp.c" - [class]{}-[func]{climbingStairsDP} - ``` - -=== "C#" - - ```csharp title="climbing_stairs_dp.cs" - /* 爬楼梯:动态规划 */ - int climbingStairsDP(int n) { - if (n == 1 || n == 2) - return n; - // 初始化 dp 表,用于存储子问题的解 - int[] dp = new int[n + 1]; - // 初始状态:预设最小子问题的解 - dp[1] = 1; - dp[2] = 2; - // 状态转移:从较小子问题逐步求解较大子问题 - for (int i = 3; i <= n; i++) { - dp[i] = dp[i - 1] + dp[i - 2]; - } - return dp[n]; - } - ``` - -=== "Swift" - - ```swift title="climbing_stairs_dp.swift" - /* 爬楼梯:动态规划 */ - func climbingStairsDP(n: Int) -> Int { - if n == 1 || n == 2 { - return n - } - // 初始化 dp 表,用于存储子问题的解 - var dp = Array(repeating: 0, count: n + 1) - // 初始状态:预设最小子问题的解 - dp[1] = 1 - dp[2] = 2 - // 状态转移:从较小子问题逐步求解较大子问题 - for i in stride(from: 3, through: n, by: 1) { - dp[i] = dp[i - 1] + dp[i - 2] - } - return dp[n] - } - ``` - -=== "Zig" - - ```zig title="climbing_stairs_dp.zig" - // 爬楼梯:动态规划 - fn climbingStairsDP(comptime n: usize) i32 { - // 已知 dp[1] 和 dp[2] ,返回之 - if (n == 1 or n == 2) { - return @intCast(n); - } - // 初始化 dp 表,用于存储子问题的解 - var dp = [_]i32{-1} ** (n + 1); - // 初始状态:预设最小子问题的解 - dp[1] = 1; - dp[2] = 2; - // 状态转移:从较小子问题逐步求解较大子问题 - for (3..n + 1) |i| { - dp[i] = dp[i - 1] + dp[i - 2]; - } - return dp[n]; - } - ``` - === "Dart" ```dart title="climbing_stairs_dp.dart" @@ -1152,6 +1124,34 @@ $$ } ``` +=== "C" + + ```c title="climbing_stairs_dp.c" + [class]{}-[func]{climbingStairsDP} + ``` + +=== "Zig" + + ```zig title="climbing_stairs_dp.zig" + // 爬楼梯:动态规划 + fn climbingStairsDP(comptime n: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (n == 1 or n == 2) { + return @intCast(n); + } + // 初始化 dp 表,用于存储子问题的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (3..n + 1) |i| { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + ``` + 图 14-5 模拟了以上代码的执行过程。 ![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) @@ -1170,21 +1170,17 @@ $$ 细心的你可能发现,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无须使用一个数组 `dp` 来存储所有子问题的解**,而只需两个变量滚动前进即可。 -=== "Java" +=== "Python" - ```java title="climbing_stairs_dp.java" - /* 爬楼梯:空间优化后的动态规划 */ - int climbingStairsDPComp(int n) { - if (n == 1 || n == 2) - return n; - int a = 1, b = 2; - for (int i = 3; i <= n; i++) { - int tmp = b; - b = a + b; - a = tmp; - } - return b; - } + ```python title="climbing_stairs_dp.py" + def climbing_stairs_dp_comp(n: int) -> int: + """爬楼梯:空间优化后的动态规划""" + if n == 1 or n == 2: + return n + a, b = 1, 2 + for _ in range(3, n + 1): + a, b = b, a + b + return b ``` === "C++" @@ -1204,17 +1200,38 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="climbing_stairs_dp.py" - def climbing_stairs_dp_comp(n: int) -> int: - """爬楼梯:空间优化后的动态规划""" - if n == 1 or n == 2: - return n - a, b = 1, 2 - for _ in range(3, n + 1): - a, b = b, a + b - return b + ```java title="climbing_stairs_dp.java" + /* 爬楼梯:空间优化后的动态规划 */ + int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + ``` + +=== "C#" + + ```csharp title="climbing_stairs_dp.cs" + /* 爬楼梯:空间优化后的动态规划 */ + int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } ``` === "Go" @@ -1234,6 +1251,23 @@ $$ } ``` +=== "Swift" + + ```swift title="climbing_stairs_dp.swift" + /* 爬楼梯:空间优化后的动态规划 */ + func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in stride(from: 3, through: n, by: 1) { + (a, b) = (b, a + b) + } + return b + } + ``` + === "JS" ```javascript title="climbing_stairs_dp.js" @@ -1268,65 +1302,6 @@ $$ } ``` -=== "C" - - ```c title="climbing_stairs_dp.c" - [class]{}-[func]{climbingStairsDPComp} - ``` - -=== "C#" - - ```csharp title="climbing_stairs_dp.cs" - /* 爬楼梯:空间优化后的动态规划 */ - int climbingStairsDPComp(int n) { - if (n == 1 || n == 2) - return n; - int a = 1, b = 2; - for (int i = 3; i <= n; i++) { - int tmp = b; - b = a + b; - a = tmp; - } - return b; - } - ``` - -=== "Swift" - - ```swift title="climbing_stairs_dp.swift" - /* 爬楼梯:空间优化后的动态规划 */ - func climbingStairsDPComp(n: Int) -> Int { - if n == 1 || n == 2 { - return n - } - var a = 1 - var b = 2 - for _ in stride(from: 3, through: n, by: 1) { - (a, b) = (b, a + b) - } - return b - } - ``` - -=== "Zig" - - ```zig title="climbing_stairs_dp.zig" - // 爬楼梯:空间优化后的动态规划 - fn climbingStairsDPComp(comptime n: usize) i32 { - if (n == 1 or n == 2) { - return @intCast(n); - } - var a: i32 = 1; - var b: i32 = 2; - for (3..n + 1) |_| { - var tmp = b; - b = a + b; - a = tmp; - } - return b; - } - ``` - === "Dart" ```dart title="climbing_stairs_dp.dart" @@ -1359,6 +1334,31 @@ $$ } ``` +=== "C" + + ```c title="climbing_stairs_dp.c" + [class]{}-[func]{climbingStairsDPComp} + ``` + +=== "Zig" + + ```zig title="climbing_stairs_dp.zig" + // 爬楼梯:空间优化后的动态规划 + fn climbingStairsDPComp(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return @intCast(n); + } + var a: i32 = 1; + var b: i32 = 2; + for (3..n + 1) |_| { + var tmp = b; + b = a + b; + a = tmp; + } + return b; + } + ``` + 观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降低至 $O(1)$ 。 在动态规划问题中,当前状态往往仅与前面有限个状态有关,这时我们可以只保留必要的状态,通过“降维”来节省内存空间。**这种空间优化技巧被称为“滚动变量”或“滚动数组”**。 diff --git a/chapter_dynamic_programming/knapsack_problem.md b/chapter_dynamic_programming/knapsack_problem.md index 1fe338269..a4843542e 100644 --- a/chapter_dynamic_programming/knapsack_problem.md +++ b/chapter_dynamic_programming/knapsack_problem.md @@ -63,25 +63,22 @@ $$ - **终止条件**:当物品编号越界 $i = 0$ 或背包剩余容量为 $0$ 时,终止递归并返回价值 $0$ 。 - **剪枝**:若当前物品重量超出背包剩余容量,则只能不放入背包。 -=== "Java" +=== "Python" - ```java title="knapsack.java" - /* 0-1 背包:暴力搜索 */ - int knapsackDFS(int[] wgt, int[] val, int i, int c) { - // 若已选完所有物品或背包无容量,则返回价值 0 - if (i == 0 || c == 0) { - return 0; - } - // 若超过背包容量,则只能不放入背包 - if (wgt[i - 1] > c) { - return knapsackDFS(wgt, val, i - 1, c); - } - // 计算不放入和放入物品 i 的最大价值 - int no = knapsackDFS(wgt, val, i - 1, c); - int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; - // 返回两种方案中价值更大的那一个 - return Math.max(no, yes); - } + ```python title="knapsack.py" + def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """0-1 背包:暴力搜索""" + # 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 or c == 0: + return 0 + # 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs(wgt, val, i - 1, c) + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 返回两种方案中价值更大的那一个 + return max(no, yes) ``` === "C++" @@ -105,22 +102,46 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="knapsack.py" - def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: - """0-1 背包:暴力搜索""" - # 若已选完所有物品或背包无容量,则返回价值 0 - if i == 0 or c == 0: - return 0 - # 若超过背包容量,则只能不放入背包 - if wgt[i - 1] > c: - return knapsack_dfs(wgt, val, i - 1, c) - # 计算不放入和放入物品 i 的最大价值 - no = knapsack_dfs(wgt, val, i - 1, c) - yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] - # 返回两种方案中价值更大的那一个 - return max(no, yes) + ```java title="knapsack.java" + /* 0-1 背包:暴力搜索 */ + int knapsackDFS(int[] wgt, int[] val, int i, int c) { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.max(no, yes); + } + ``` + +=== "C#" + + ```csharp title="knapsack.cs" + /* 0-1 背包:暴力搜索 */ + int knapsackDFS(int[] weight, int[] val, int i, int c) { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能不放入背包 + if (weight[i - 1] > c) { + return knapsackDFS(weight, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFS(weight, val, i - 1, c); + int yes = knapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return Math.Max(no, yes); + } ``` === "Go" @@ -144,6 +165,27 @@ $$ } ``` +=== "Swift" + + ```swift title="knapsack.swift" + /* 0-1 背包:暴力搜索 */ + func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 返回两种方案中价值更大的那一个 + return max(no, yes) + } + ``` + === "JS" ```javascript title="knapsack.js" @@ -191,75 +233,6 @@ $$ } ``` -=== "C" - - ```c title="knapsack.c" - [class]{}-[func]{knapsackDFS} - ``` - -=== "C#" - - ```csharp title="knapsack.cs" - /* 0-1 背包:暴力搜索 */ - int knapsackDFS(int[] weight, int[] val, int i, int c) { - // 若已选完所有物品或背包无容量,则返回价值 0 - if (i == 0 || c == 0) { - return 0; - } - // 若超过背包容量,则只能不放入背包 - if (weight[i - 1] > c) { - return knapsackDFS(weight, val, i - 1, c); - } - // 计算不放入和放入物品 i 的最大价值 - int no = knapsackDFS(weight, val, i - 1, c); - int yes = knapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; - // 返回两种方案中价值更大的那一个 - return Math.Max(no, yes); - } - ``` - -=== "Swift" - - ```swift title="knapsack.swift" - /* 0-1 背包:暴力搜索 */ - func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { - // 若已选完所有物品或背包无容量,则返回价值 0 - if i == 0 || c == 0 { - return 0 - } - // 若超过背包容量,则只能不放入背包 - if wgt[i - 1] > c { - return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) - } - // 计算不放入和放入物品 i 的最大价值 - let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) - let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] - // 返回两种方案中价值更大的那一个 - return max(no, yes) - } - ``` - -=== "Zig" - - ```zig title="knapsack.zig" - // 0-1 背包:暴力搜索 - fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { - // 若已选完所有物品或背包无容量,则返回价值 0 - if (i == 0 or c == 0) { - return 0; - } - // 若超过背包容量,则只能不放入背包 - if (wgt[i - 1] > c) { - return knapsackDFS(wgt, val, i - 1, c); - } - // 计算不放入和放入物品 i 的最大价值 - var no = knapsackDFS(wgt, val, i - 1, c); - var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; - // 返回两种方案中价值更大的那一个 - return @max(no, yes); - } - ``` - === "Dart" ```dart title="knapsack.dart" @@ -302,6 +275,33 @@ $$ } ``` +=== "C" + + ```c title="knapsack.c" + [class]{}-[func]{knapsackDFS} + ``` + +=== "Zig" + + ```zig title="knapsack.zig" + // 0-1 背包:暴力搜索 + fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若超过背包容量,则只能不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + var no = knapsackDFS(wgt, val, i - 1, c); + var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return @max(no, yes); + } + ``` + 如图 14-18 所示,由于每个物品都会产生不选和选两条搜索分支,因此时间复杂度为 $O(2^n)$ 。 观察递归树,容易发现其中存在重叠子问题,例如 $dp[1, 10]$ 等。而当物品较多、背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅增多。 @@ -316,30 +316,28 @@ $$ 引入记忆化之后,**时间复杂度取决于子问题数量**,也就是 $O(n \times cap)$ 。 -=== "Java" +=== "Python" - ```java title="knapsack.java" - /* 0-1 背包:记忆化搜索 */ - int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { - // 若已选完所有物品或背包无容量,则返回价值 0 - if (i == 0 || c == 0) { - return 0; - } - // 若已有记录,则直接返回 - if (mem[i][c] != -1) { - return mem[i][c]; - } - // 若超过背包容量,则只能不放入背包 - if (wgt[i - 1] > c) { - return knapsackDFSMem(wgt, val, mem, i - 1, c); - } - // 计算不放入和放入物品 i 的最大价值 - int no = knapsackDFSMem(wgt, val, mem, i - 1, c); - int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; - // 记录并返回两种方案中价值更大的那一个 - mem[i][c] = Math.max(no, yes); - return mem[i][c]; - } + ```python title="knapsack.py" + def knapsack_dfs_mem( + wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int + ) -> int: + """0-1 背包:记忆化搜索""" + # 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 or c == 0: + return 0 + # 若已有记录,则直接返回 + if mem[i][c] != -1: + return mem[i][c] + # 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c: + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) + # 计算不放入和放入物品 i 的最大价值 + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] ``` === "C++" @@ -368,28 +366,56 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="knapsack.py" - def knapsack_dfs_mem( - wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int - ) -> int: - """0-1 背包:记忆化搜索""" - # 若已选完所有物品或背包无容量,则返回价值 0 - if i == 0 or c == 0: - return 0 - # 若已有记录,则直接返回 - if mem[i][c] != -1: - return mem[i][c] - # 若超过背包容量,则只能不放入背包 - if wgt[i - 1] > c: - return knapsack_dfs_mem(wgt, val, mem, i - 1, c) - # 计算不放入和放入物品 i 的最大价值 - no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) - yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] - # 记录并返回两种方案中价值更大的那一个 - mem[i][c] = max(no, yes) - return mem[i][c] + ```java title="knapsack.java" + /* 0-1 背包:记忆化搜索 */ + int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; + } + ``` + +=== "C#" + + ```csharp title="knapsack.cs" + /* 0-1 背包:记忆化搜索 */ + int knapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能不放入背包 + if (weight[i - 1] > c) { + return knapsackDFSMem(weight, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(weight, val, mem, i - 1, c); + int yes = knapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = Math.Max(no, yes); + return mem[i][c]; + } ``` === "Go" @@ -418,6 +444,32 @@ $$ } ``` +=== "Swift" + + ```swift title="knapsack.swift" + /* 0-1 背包:记忆化搜索 */ + func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // 若已选完所有物品或背包无容量,则返回价值 0 + if i == 0 || c == 0 { + return 0 + } + // 若已有记录,则直接返回 + if mem[i][c] != -1 { + return mem[i][c] + } + // 若超过背包容量,则只能不放入背包 + if wgt[i - 1] > c { + return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // 计算不放入和放入物品 i 的最大价值 + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes) + return mem[i][c] + } + ``` + === "JS" ```javascript title="knapsack.js" @@ -478,90 +530,6 @@ $$ } ``` -=== "C" - - ```c title="knapsack.c" - [class]{}-[func]{knapsackDFSMem} - ``` - -=== "C#" - - ```csharp title="knapsack.cs" - /* 0-1 背包:记忆化搜索 */ - int knapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { - // 若已选完所有物品或背包无容量,则返回价值 0 - if (i == 0 || c == 0) { - return 0; - } - // 若已有记录,则直接返回 - if (mem[i][c] != -1) { - return mem[i][c]; - } - // 若超过背包容量,则只能不放入背包 - if (weight[i - 1] > c) { - return knapsackDFSMem(weight, val, mem, i - 1, c); - } - // 计算不放入和放入物品 i 的最大价值 - int no = knapsackDFSMem(weight, val, mem, i - 1, c); - int yes = knapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; - // 记录并返回两种方案中价值更大的那一个 - mem[i][c] = Math.Max(no, yes); - return mem[i][c]; - } - ``` - -=== "Swift" - - ```swift title="knapsack.swift" - /* 0-1 背包:记忆化搜索 */ - func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { - // 若已选完所有物品或背包无容量,则返回价值 0 - if i == 0 || c == 0 { - return 0 - } - // 若已有记录,则直接返回 - if mem[i][c] != -1 { - return mem[i][c] - } - // 若超过背包容量,则只能不放入背包 - if wgt[i - 1] > c { - return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) - } - // 计算不放入和放入物品 i 的最大价值 - let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) - let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] - // 记录并返回两种方案中价值更大的那一个 - mem[i][c] = max(no, yes) - return mem[i][c] - } - ``` - -=== "Zig" - - ```zig title="knapsack.zig" - // 0-1 背包:记忆化搜索 - fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { - // 若已选完所有物品或背包无容量,则返回价值 0 - if (i == 0 or c == 0) { - return 0; - } - // 若已有记录,则直接返回 - if (mem[i][c] != -1) { - return mem[i][c]; - } - // 若超过背包容量,则只能不放入背包 - if (wgt[i - 1] > c) { - return knapsackDFSMem(wgt, val, mem, i - 1, c); - } - // 计算不放入和放入物品 i 的最大价值 - var no = knapsackDFSMem(wgt, val, mem, i - 1, c); - var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; - // 记录并返回两种方案中价值更大的那一个 - mem[i][c] = @max(no, yes); - return mem[i][c]; - } - ``` - === "Dart" ```dart title="knapsack.dart" @@ -620,6 +588,38 @@ $$ } ``` +=== "C" + + ```c title="knapsack.c" + [class]{}-[func]{knapsackDFSMem} + ``` + +=== "Zig" + + ```zig title="knapsack.zig" + // 0-1 背包:记忆化搜索 + fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { + // 若已选完所有物品或背包无容量,则返回价值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + var no = knapsackDFSMem(wgt, val, mem, i - 1, c); + var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = @max(no, yes); + return mem[i][c]; + } + ``` + 图 14-19 展示了在记忆化递归中被剪掉的搜索分支。 ![0-1 背包的记忆化搜索递归树](knapsack_problem.assets/knapsack_dfs_mem.png) @@ -630,28 +630,24 @@ $$ 动态规划实质上就是在状态转移中填充 $dp$ 表的过程,代码如下所示。 -=== "Java" +=== "Python" - ```java title="knapsack.java" - /* 0-1 背包:动态规划 */ - int knapsackDP(int[] wgt, int[] val, int cap) { - int n = wgt.length; - // 初始化 dp 表 - int[][] dp = new int[n + 1][cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int c = 1; c <= cap; c++) { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); - } - } - } - return dp[n][cap]; - } + ```python title="knapsack.py" + def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状态转移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] ``` === "C++" @@ -678,24 +674,52 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="knapsack.py" - def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: - """0-1 背包:动态规划""" - n = len(wgt) - # 初始化 dp 表 - dp = [[0] * (cap + 1) for _ in range(n + 1)] - # 状态转移 - for i in range(1, n + 1): - for c in range(1, cap + 1): - if wgt[i - 1] > c: - # 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c] - else: - # 不选和选物品 i 这两种方案的较大值 - dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) - return dp[n][cap] + ```java title="knapsack.java" + /* 0-1 背包:动态规划 */ + int knapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + ``` + +=== "C#" + + ```csharp title="knapsack.cs" + /* 0-1 背包:动态规划 */ + int knapsackDP(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (weight[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i, c] = dp[i - 1, c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); + } + } + } + return dp[n, cap]; + } ``` === "Go" @@ -725,6 +749,30 @@ $$ } ``` +=== "Swift" + + ```swift title="knapsack.swift" + /* 0-1 背包:动态规划 */ + func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for c in stride(from: 1, through: cap, by: 1) { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] + } + ``` + === "JS" ```javascript title="knapsack.js" @@ -787,84 +835,6 @@ $$ } ``` -=== "C" - - ```c title="knapsack.c" - [class]{}-[func]{knapsackDP} - ``` - -=== "C#" - - ```csharp title="knapsack.cs" - /* 0-1 背包:动态规划 */ - int knapsackDP(int[] weight, int[] val, int cap) { - int n = weight.Length; - // 初始化 dp 表 - int[,] dp = new int[n + 1, cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int c = 1; c <= cap; c++) { - if (weight[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[i, c] = dp[i - 1, c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); - } - } - } - return dp[n, cap]; - } - ``` - -=== "Swift" - - ```swift title="knapsack.swift" - /* 0-1 背包:动态规划 */ - func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { - let n = wgt.count - // 初始化 dp 表 - var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - for c in stride(from: 1, through: cap, by: 1) { - if wgt[i - 1] > c { - // 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c] - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) - } - } - } - return dp[n][cap] - } - ``` - -=== "Zig" - - ```zig title="knapsack.zig" - // 0-1 背包:动态规划 - fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { - comptime var n = wgt.len; - // 初始化 dp 表 - var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); - // 状态转移 - for (1..n + 1) |i| { - for (1..cap + 1) |c| { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); - } - } - } - return dp[n][cap]; - } - ``` - === "Dart" ```dart title="knapsack.dart" @@ -913,6 +883,36 @@ $$ } ``` +=== "C" + + ```c title="knapsack.c" + [class]{}-[func]{knapsackDP} + ``` + +=== "Zig" + + ```zig title="knapsack.zig" + // 0-1 背包:动态规划 + fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 状态转移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + ``` + 如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 === "<1>" @@ -992,26 +992,25 @@ $$ 在代码实现中,我们仅需将数组 `dp` 的第一维 $i$ 直接删除,并且把内循环更改为倒序遍历即可。 -=== "Java" +=== "Python" - ```java title="knapsack.java" - /* 0-1 背包:空间优化后的动态规划 */ - int knapsackDPComp(int[] wgt, int[] val, int cap) { - int n = wgt.length; - // 初始化 dp 表 - int[] dp = new int[cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - // 倒序遍历 - for (int c = cap; c >= 1; c--) { - if (wgt[i - 1] <= c) { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); - } - } - } - return dp[cap]; - } + ```python title="knapsack.py" + def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 背包:空间优化后的动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 状态转移 + for i in range(1, n + 1): + # 倒序遍历 + for c in range(cap, 0, -1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] ``` === "C++" @@ -1036,25 +1035,51 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="knapsack.py" - def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: - """0-1 背包:空间优化后的动态规划""" - n = len(wgt) - # 初始化 dp 表 - dp = [0] * (cap + 1) - # 状态转移 - for i in range(1, n + 1): - # 倒序遍历 - for c in range(cap, 0, -1): - if wgt[i - 1] > c: - # 若超过背包容量,则不选物品 i - dp[c] = dp[c] - else: - # 不选和选物品 i 这两种方案的较大值 - dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) - return dp[cap] + ```java title="knapsack.java" + /* 0-1 背包:空间优化后的动态规划 */ + int knapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + ``` + +=== "C#" + + ```csharp title="knapsack.cs" + /* 0-1 背包:空间优化后的动态规划 */ + int knapsackDPComp(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c > 0; c--) { + if (weight[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } ``` === "Go" @@ -1079,6 +1104,28 @@ $$ } ``` +=== "Swift" + + ```swift title="knapsack.swift" + /* 0-1 背包:空间优化后的动态规划 */ + func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + // 倒序遍历 + for c in stride(from: cap, through: 1, by: -1) { + if wgt[i - 1] <= c { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] + } + ``` + === "JS" ```javascript title="knapsack.js" @@ -1127,82 +1174,6 @@ $$ } ``` -=== "C" - - ```c title="knapsack.c" - [class]{}-[func]{knapsackDPComp} - ``` - -=== "C#" - - ```csharp title="knapsack.cs" - /* 0-1 背包:空间优化后的动态规划 */ - int knapsackDPComp(int[] weight, int[] val, int cap) { - int n = weight.Length; - // 初始化 dp 表 - int[] dp = new int[cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - // 倒序遍历 - for (int c = cap; c > 0; c--) { - if (weight[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[c] = dp[c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); - } - } - } - return dp[cap]; - } - ``` - -=== "Swift" - - ```swift title="knapsack.swift" - /* 0-1 背包:空间优化后的动态规划 */ - func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { - let n = wgt.count - // 初始化 dp 表 - var dp = Array(repeating: 0, count: cap + 1) - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - // 倒序遍历 - for c in stride(from: cap, through: 1, by: -1) { - if wgt[i - 1] <= c { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) - } - } - } - return dp[cap] - } - ``` - -=== "Zig" - - ```zig title="knapsack.zig" - // 0-1 背包:空间优化后的动态规划 - fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { - var n = wgt.len; - // 初始化 dp 表 - var dp = [_]i32{0} ** (cap + 1); - // 状态转移 - for (1..n + 1) |i| { - // 倒序遍历 - var c = cap; - while (c > 0) : (c -= 1) { - if (wgt[i - 1] < c) { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); - } - } - } - return dp[cap]; - } - ``` - === "Dart" ```dart title="knapsack.dart" @@ -1246,3 +1217,32 @@ $$ dp[cap] } ``` + +=== "C" + + ```c title="knapsack.c" + [class]{}-[func]{knapsackDPComp} + ``` + +=== "Zig" + + ```zig title="knapsack.zig" + // 0-1 背包:空间优化后的动态规划 + fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { + var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 状态转移 + for (1..n + 1) |i| { + // 倒序遍历 + var c = cap; + while (c > 0) : (c -= 1) { + if (wgt[i - 1] < c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; + } + ``` diff --git a/chapter_dynamic_programming/unbounded_knapsack_problem.md b/chapter_dynamic_programming/unbounded_knapsack_problem.md index 0adbdd806..902ed2d83 100644 --- a/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -39,28 +39,24 @@ $$ 对比两道题目的代码,状态转移中有一处从 $i-1$ 变为 $i$ ,其余完全一致。 -=== "Java" +=== "Python" - ```java title="unbounded_knapsack.java" - /* 完全背包:动态规划 */ - int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { - int n = wgt.length; - // 初始化 dp 表 - int[][] dp = new int[n + 1][cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int c = 1; c <= cap; c++) { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); - } - } - } - return dp[n][cap]; - } + ```python title="unbounded_knapsack.py" + def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状态转移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + return dp[n][cap] ``` === "C++" @@ -87,24 +83,52 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="unbounded_knapsack.py" - def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: - """完全背包:动态规划""" - n = len(wgt) - # 初始化 dp 表 - dp = [[0] * (cap + 1) for _ in range(n + 1)] - # 状态转移 - for i in range(1, n + 1): - for c in range(1, cap + 1): - if wgt[i - 1] > c: - # 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c] - else: - # 不选和选物品 i 这两种方案的较大值 - dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) - return dp[n][cap] + ```java title="unbounded_knapsack.java" + /* 完全背包:动态规划 */ + int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + ``` + +=== "C#" + + ```csharp title="unbounded_knapsack.cs" + /* 完全背包:动态规划 */ + int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i, c] = dp[i - 1, c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n, cap]; + } ``` === "Go" @@ -134,6 +158,30 @@ $$ } ``` +=== "Swift" + + ```swift title="unbounded_knapsack.swift" + /* 完全背包:动态规划 */ + func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for c in stride(from: 1, through: cap, by: 1) { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] + } + ``` + === "JS" ```javascript title="unbounded_knapsack.js" @@ -196,84 +244,6 @@ $$ } ``` -=== "C" - - ```c title="unbounded_knapsack.c" - [class]{}-[func]{unboundedKnapsackDP} - ``` - -=== "C#" - - ```csharp title="unbounded_knapsack.cs" - /* 完全背包:动态规划 */ - int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { - int n = wgt.Length; - // 初始化 dp 表 - int[,] dp = new int[n + 1, cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int c = 1; c <= cap; c++) { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[i, c] = dp[i - 1, c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); - } - } - } - return dp[n, cap]; - } - ``` - -=== "Swift" - - ```swift title="unbounded_knapsack.swift" - /* 完全背包:动态规划 */ - func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { - let n = wgt.count - // 初始化 dp 表 - var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - for c in stride(from: 1, through: cap, by: 1) { - if wgt[i - 1] > c { - // 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c] - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) - } - } - } - return dp[n][cap] - } - ``` - -=== "Zig" - - ```zig title="unbounded_knapsack.zig" - // 完全背包:动态规划 - fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { - comptime var n = wgt.len; - // 初始化 dp 表 - var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); - // 状态转移 - for (1..n + 1) |i| { - for (1..cap + 1) |c| { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[i][c] = dp[i - 1][c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); - } - } - } - return dp[n][cap]; - } - ``` - === "Dart" ```dart title="unbounded_knapsack.dart" @@ -322,6 +292,36 @@ $$ } ``` +=== "C" + + ```c title="unbounded_knapsack.c" + [class]{}-[func]{unboundedKnapsackDP} + ``` + +=== "Zig" + + ```zig title="unbounded_knapsack.zig" + // 完全背包:动态规划 + fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 状态转移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + ``` + ### 3.   空间优化 由于当前状态是从左边和上边的状态转移而来,**因此空间优化后应该对 $dp$ 表中的每一行采取正序遍历**。 @@ -350,28 +350,25 @@ $$ 代码实现比较简单,仅需将数组 `dp` 的第一维删除。 -=== "Java" +=== "Python" - ```java title="unbounded_knapsack.java" - /* 完全背包:空间优化后的动态规划 */ - int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { - int n = wgt.length; - // 初始化 dp 表 - int[] dp = new int[cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int c = 1; c <= cap; c++) { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[c] = dp[c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); - } - } - } - return dp[cap]; - } + ```python title="unbounded_knapsack.py" + def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """完全背包:空间优化后的动态规划""" + n = len(wgt) + # 初始化 dp 表 + dp = [0] * (cap + 1) + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for c in range(1, cap + 1): + if wgt[i - 1] > c: + # 若超过背包容量,则不选物品 i + dp[c] = dp[c] + else: + # 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] ``` === "C++" @@ -398,25 +395,52 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="unbounded_knapsack.py" - def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: - """完全背包:空间优化后的动态规划""" - n = len(wgt) - # 初始化 dp 表 - dp = [0] * (cap + 1) - # 状态转移 - for i in range(1, n + 1): - # 正序遍历 - for c in range(1, cap + 1): - if wgt[i - 1] > c: - # 若超过背包容量,则不选物品 i - dp[c] = dp[c] - else: - # 不选和选物品 i 这两种方案的较大值 - dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) - return dp[cap] + ```java title="unbounded_knapsack.java" + /* 完全背包:空间优化后的动态规划 */ + int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + ``` + +=== "C#" + + ```csharp title="unbounded_knapsack.cs" + /* 完全背包:空间优化后的动态规划 */ + int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } ``` === "Go" @@ -443,6 +467,30 @@ $$ } ``` +=== "Swift" + + ```swift title="unbounded_knapsack.swift" + /* 完全背包:空间优化后的动态规划 */ + func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for c in stride(from: 1, through: cap, by: 1) { + if wgt[i - 1] > c { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c] + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] + } + ``` + === "JS" ```javascript title="unbounded_knapsack.js" @@ -495,84 +543,6 @@ $$ } ``` -=== "C" - - ```c title="unbounded_knapsack.c" - [class]{}-[func]{unboundedKnapsackDPComp} - ``` - -=== "C#" - - ```csharp title="unbounded_knapsack.cs" - /* 完全背包:空间优化后的动态规划 */ - int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { - int n = wgt.Length; - // 初始化 dp 表 - int[] dp = new int[cap + 1]; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int c = 1; c <= cap; c++) { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[c] = dp[c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); - } - } - } - return dp[cap]; - } - ``` - -=== "Swift" - - ```swift title="unbounded_knapsack.swift" - /* 完全背包:空间优化后的动态规划 */ - func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { - let n = wgt.count - // 初始化 dp 表 - var dp = Array(repeating: 0, count: cap + 1) - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - for c in stride(from: 1, through: cap, by: 1) { - if wgt[i - 1] > c { - // 若超过背包容量,则不选物品 i - dp[c] = dp[c] - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) - } - } - } - return dp[cap] - } - ``` - -=== "Zig" - - ```zig title="unbounded_knapsack.zig" - // 完全背包:空间优化后的动态规划 - fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { - comptime var n = wgt.len; - // 初始化 dp 表 - var dp = [_]i32{0} ** (cap + 1); - // 状态转移 - for (1..n + 1) |i| { - for (1..cap + 1) |c| { - if (wgt[i - 1] > c) { - // 若超过背包容量,则不选物品 i - dp[c] = dp[c]; - } else { - // 不选和选物品 i 这两种方案的较大值 - dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); - } - } - } - return dp[cap]; - } - ``` - === "Dart" ```dart title="unbounded_knapsack.dart" @@ -621,6 +591,36 @@ $$ } ``` +=== "C" + + ```c title="unbounded_knapsack.c" + [class]{}-[func]{unboundedKnapsackDPComp} + ``` + +=== "Zig" + + ```zig title="unbounded_knapsack.zig" + // 完全背包:空间优化后的动态规划 + fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 状态转移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; + } + ``` + ## 14.5.2   零钱兑换问题 背包问题是一大类动态规划问题的代表,其拥有很多的变种,例如零钱兑换问题。 @@ -672,33 +672,28 @@ $$ 最后返回前,判断 $dp[n, amt]$ 是否等于 $amt + 1$ ,若是则返回 $-1$ ,代表无法凑出目标金额。 -=== "Java" +=== "Python" - ```java title="coin_change.java" - /* 零钱兑换:动态规划 */ - int coinChangeDP(int[] coins, int amt) { - int n = coins.length; - int MAX = amt + 1; - // 初始化 dp 表 - int[][] dp = new int[n + 1][amt + 1]; - // 状态转移:首行首列 - for (int a = 1; a <= amt; a++) { - dp[0][a] = MAX; - } - // 状态转移:其余行列 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); - } - } - } - return dp[n][amt] != MAX ? dp[n][amt] : -1; - } + ```python title="coin_change.py" + def coin_change_dp(coins: list[int], amt: int) -> int: + """零钱兑换:动态规划""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 状态转移:首行首列 + for a in range(1, amt + 1): + dp[0][a] = MAX + # 状态转移:其余行列 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else: + # 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + return dp[n][amt] if dp[n][amt] != MAX else -1 ``` === "C++" @@ -730,28 +725,62 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="coin_change.py" - def coin_change_dp(coins: list[int], amt: int) -> int: - """零钱兑换:动态规划""" - n = len(coins) - MAX = amt + 1 - # 初始化 dp 表 - dp = [[0] * (amt + 1) for _ in range(n + 1)] - # 状态转移:首行首列 - for a in range(1, amt + 1): - dp[0][a] = MAX - # 状态转移:其余行列 - for i in range(1, n + 1): - for a in range(1, amt + 1): - if coins[i - 1] > a: - # 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a] - else: - # 不选和选硬币 i 这两种方案的较小值 - dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) - return dp[n][amt] if dp[n][amt] != MAX else -1 + ```java title="coin_change.java" + /* 零钱兑换:动态规划 */ + int coinChangeDP(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 状态转移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; + } + ``` + +=== "C#" + + ```csharp title="coin_change.cs" + /* 零钱兑换:动态规划 */ + int coinChangeDP(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 状态转移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0, a] = MAX; + } + // 状态转移:其余行列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[i, a] = dp[i - 1, a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); + } + } + } + return dp[n, amt] != MAX ? dp[n, amt] : -1; + } ``` === "Go" @@ -789,6 +818,35 @@ $$ } ``` +=== "Swift" + + ```swift title="coin_change.swift" + /* 零钱兑换:动态规划 */ + func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 状态转移:首行首列 + for a in stride(from: 1, through: amt, by: 1) { + dp[0][a] = MAX + } + // 状态转移:其余行列 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1 + } + ``` + === "JS" ```javascript title="coin_change.js" @@ -851,103 +909,6 @@ $$ } ``` -=== "C" - - ```c title="coin_change.c" - [class]{}-[func]{coinChangeDP} - ``` - -=== "C#" - - ```csharp title="coin_change.cs" - /* 零钱兑换:动态规划 */ - int coinChangeDP(int[] coins, int amt) { - int n = coins.Length; - int MAX = amt + 1; - // 初始化 dp 表 - int[,] dp = new int[n + 1, amt + 1]; - // 状态转移:首行首列 - for (int a = 1; a <= amt; a++) { - dp[0, a] = MAX; - } - // 状态转移:其余行列 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[i, a] = dp[i - 1, a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); - } - } - } - return dp[n, amt] != MAX ? dp[n, amt] : -1; - } - ``` - -=== "Swift" - - ```swift title="coin_change.swift" - /* 零钱兑换:动态规划 */ - func coinChangeDP(coins: [Int], amt: Int) -> Int { - let n = coins.count - let MAX = amt + 1 - // 初始化 dp 表 - var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) - // 状态转移:首行首列 - for a in stride(from: 1, through: amt, by: 1) { - dp[0][a] = MAX - } - // 状态转移:其余行列 - for i in stride(from: 1, through: n, by: 1) { - for a in stride(from: 1, through: amt, by: 1) { - if coins[i - 1] > a { - // 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a] - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) - } - } - } - return dp[n][amt] != MAX ? dp[n][amt] : -1 - } - ``` - -=== "Zig" - - ```zig title="coin_change.zig" - // 零钱兑换:动态规划 - fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { - comptime var n = coins.len; - comptime var max = amt + 1; - // 初始化 dp 表 - var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); - // 状态转移:首行首列 - for (1..amt + 1) |a| { - dp[0][a] = max; - } - // 状态转移:其余行列 - for (1..n + 1) |i| { - for (1..amt + 1) |a| { - if (coins[i - 1] > @as(i32, @intCast(a))) { - // 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); - } - } - } - if (dp[n][amt] != max) { - return @intCast(dp[n][amt]); - } else { - return -1; - } - } - ``` - === "Dart" ```dart title="coin_change.dart" @@ -1006,6 +967,45 @@ $$ } ``` +=== "C" + + ```c title="coin_change.c" + [class]{}-[func]{coinChangeDP} + ``` + +=== "Zig" + + ```zig title="coin_change.zig" + // 零钱兑换:动态规划 + fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 状态转移:首行首列 + for (1..amt + 1) |a| { + dp[0][a] = max; + } + // 状态转移:其余行列 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[n][amt] != max) { + return @intCast(dp[n][amt]); + } else { + return -1; + } + } + ``` + 图 14-25 展示了零钱兑换的动态规划过程,和完全背包非常相似。 === "<1>" @@ -1059,31 +1059,27 @@ $$ 零钱兑换的空间优化的处理方式和完全背包一致。 -=== "Java" +=== "Python" - ```java title="coin_change.java" - /* 零钱兑换:空间优化后的动态规划 */ - int coinChangeDPComp(int[] coins, int amt) { - int n = coins.length; - int MAX = amt + 1; - // 初始化 dp 表 - int[] dp = new int[amt + 1]; - Arrays.fill(dp, MAX); - dp[0] = 0; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); - } - } - } - return dp[amt] != MAX ? dp[amt] : -1; - } + ```python title="coin_change.py" + def coin_change_dp_comp(coins: list[int], amt: int) -> int: + """零钱兑换:空间优化后的动态规划""" + n = len(coins) + MAX = amt + 1 + # 初始化 dp 表 + dp = [MAX] * (amt + 1) + dp[0] = 0 + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + else: + # 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + return dp[amt] if dp[amt] != MAX else -1 ``` === "C++" @@ -1112,27 +1108,58 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="coin_change.py" - def coin_change_dp_comp(coins: list[int], amt: int) -> int: - """零钱兑换:空间优化后的动态规划""" - n = len(coins) - MAX = amt + 1 - # 初始化 dp 表 - dp = [MAX] * (amt + 1) - dp[0] = 0 - # 状态转移 - for i in range(1, n + 1): - # 正序遍历 - for a in range(1, amt + 1): - if coins[i - 1] > a: - # 若超过背包容量,则不选硬币 i - dp[a] = dp[a] - else: - # 不选和选硬币 i 这两种方案的较小值 - dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) - return dp[amt] if dp[amt] != MAX else -1 + ```java title="coin_change.java" + /* 零钱兑换:空间优化后的动态规划 */ + int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Arrays.fill(dp, MAX); + dp[0] = 0; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + ``` + +=== "C#" + + ```csharp title="coin_change.cs" + /* 零钱兑换:空间优化后的动态规划 */ + int coinChangeDPComp(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Array.Fill(dp, MAX); + dp[0] = 0; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } ``` === "Go" @@ -1167,6 +1194,32 @@ $$ } ``` +=== "Swift" + + ```swift title="coin_change.swift" + /* 零钱兑换:空间优化后的动态规划 */ + func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return dp[amt] != MAX ? dp[amt] : -1 + } + ``` + === "JS" ```javascript title="coin_change.js" @@ -1219,96 +1272,6 @@ $$ } ``` -=== "C" - - ```c title="coin_change.c" - [class]{}-[func]{coinChangeDPComp} - ``` - -=== "C#" - - ```csharp title="coin_change.cs" - /* 零钱兑换:空间优化后的动态规划 */ - int coinChangeDPComp(int[] coins, int amt) { - int n = coins.Length; - int MAX = amt + 1; - // 初始化 dp 表 - int[] dp = new int[amt + 1]; - Array.Fill(dp, MAX); - dp[0] = 0; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); - } - } - } - return dp[amt] != MAX ? dp[amt] : -1; - } - ``` - -=== "Swift" - - ```swift title="coin_change.swift" - /* 零钱兑换:空间优化后的动态规划 */ - func coinChangeDPComp(coins: [Int], amt: Int) -> Int { - let n = coins.count - let MAX = amt + 1 - // 初始化 dp 表 - var dp = Array(repeating: MAX, count: amt + 1) - dp[0] = 0 - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - for a in stride(from: 1, through: amt, by: 1) { - if coins[i - 1] > a { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a] - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) - } - } - } - return dp[amt] != MAX ? dp[amt] : -1 - } - ``` - -=== "Zig" - - ```zig title="coin_change.zig" - // 零钱兑换:空间优化后的动态规划 - fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { - comptime var n = coins.len; - comptime var max = amt + 1; - // 初始化 dp 表 - var dp = [_]i32{0} ** (amt + 1); - @memset(&dp, max); - dp[0] = 0; - // 状态转移 - for (1..n + 1) |i| { - for (1..amt + 1) |a| { - if (coins[i - 1] > @as(i32, @intCast(a))) { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); - } - } - } - if (dp[amt] != max) { - return @intCast(dp[amt]); - } else { - return -1; - } - } - ``` - === "Dart" ```dart title="coin_change.dart" @@ -1362,6 +1325,43 @@ $$ } ``` +=== "C" + + ```c title="coin_change.c" + [class]{}-[func]{coinChangeDPComp} + ``` + +=== "Zig" + + ```zig title="coin_change.zig" + // 零钱兑换:空间优化后的动态规划 + fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + @memset(&dp, max); + dp[0] = 0; + // 状态转移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[amt] != max) { + return @intCast(dp[amt]); + } else { + return -1; + } + } + ``` + ## 14.5.3   零钱兑换问题 II !!! question @@ -1386,32 +1386,27 @@ $$ ### 2.   代码实现 -=== "Java" +=== "Python" - ```java title="coin_change_ii.java" - /* 零钱兑换 II:动态规划 */ - int coinChangeIIDP(int[] coins, int amt) { - int n = coins.length; - // 初始化 dp 表 - int[][] dp = new int[n + 1][amt + 1]; - // 初始化首列 - for (int i = 0; i <= n; i++) { - dp[i][0] = 1; - } - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a]; - } else { - // 不选和选硬币 i 这两种方案之和 - dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; - } - } - } - return dp[n][amt]; - } + ```python title="coin_change_ii.py" + def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """零钱兑换 II:动态规划""" + n = len(coins) + # 初始化 dp 表 + dp = [[0] * (amt + 1) for _ in range(n + 1)] + # 初始化首列 + for i in range(n + 1): + dp[i][0] = 1 + # 状态转移 + for i in range(1, n + 1): + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + else: + # 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + return dp[n][amt] ``` === "C++" @@ -1442,27 +1437,60 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="coin_change_ii.py" - def coin_change_ii_dp(coins: list[int], amt: int) -> int: - """零钱兑换 II:动态规划""" - n = len(coins) - # 初始化 dp 表 - dp = [[0] * (amt + 1) for _ in range(n + 1)] - # 初始化首列 - for i in range(n + 1): - dp[i][0] = 1 - # 状态转移 - for i in range(1, n + 1): - for a in range(1, amt + 1): - if coins[i - 1] > a: - # 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a] - else: - # 不选和选硬币 i 这两种方案之和 - dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] - return dp[n][amt] + ```java title="coin_change_ii.java" + /* 零钱兑换 II:动态规划 */ + int coinChangeIIDP(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; + } + ``` + +=== "C#" + + ```csharp title="coin_change_ii.cs" + /* 零钱兑换 II:动态规划 */ + int coinChangeIIDP(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i, 0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[i, a] = dp[i - 1, a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; + } + } + } + return dp[n, amt]; + } ``` === "Go" @@ -1496,6 +1524,34 @@ $$ } ``` +=== "Swift" + + ```swift title="coin_change_ii.swift" + /* 零钱兑换 II:动态规划 */ + func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 初始化首列 + for i in stride(from: 0, through: n, by: 1) { + dp[i][0] = 1 + } + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] + } + ``` + === "JS" ```javascript title="coin_change_ii.js" @@ -1556,96 +1612,6 @@ $$ } ``` -=== "C" - - ```c title="coin_change_ii.c" - [class]{}-[func]{coinChangeIIDP} - ``` - -=== "C#" - - ```csharp title="coin_change_ii.cs" - /* 零钱兑换 II:动态规划 */ - int coinChangeIIDP(int[] coins, int amt) { - int n = coins.Length; - // 初始化 dp 表 - int[,] dp = new int[n + 1, amt + 1]; - // 初始化首列 - for (int i = 0; i <= n; i++) { - dp[i, 0] = 1; - } - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[i, a] = dp[i - 1, a]; - } else { - // 不选和选硬币 i 这两种方案之和 - dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; - } - } - } - return dp[n, amt]; - } - ``` - -=== "Swift" - - ```swift title="coin_change_ii.swift" - /* 零钱兑换 II:动态规划 */ - func coinChangeIIDP(coins: [Int], amt: Int) -> Int { - let n = coins.count - // 初始化 dp 表 - var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) - // 初始化首列 - for i in stride(from: 0, through: n, by: 1) { - dp[i][0] = 1 - } - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - for a in stride(from: 1, through: amt, by: 1) { - if coins[i - 1] > a { - // 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a] - } else { - // 不选和选硬币 i 这两种方案之和 - dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] - } - } - } - return dp[n][amt] - } - ``` - -=== "Zig" - - ```zig title="coin_change_ii.zig" - // 零钱兑换 II:动态规划 - fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { - comptime var n = coins.len; - // 初始化 dp 表 - var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); - // 初始化首列 - for (0..n + 1) |i| { - dp[i][0] = 1; - } - // 状态转移 - for (1..n + 1) |i| { - for (1..amt + 1) |a| { - if (coins[i - 1] > @as(i32, @intCast(a))) { - // 若超过背包容量,则不选硬币 i - dp[i][a] = dp[i - 1][a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; - } - } - } - return dp[n][amt]; - } - ``` - === "Dart" ```dart title="coin_change_ii.dart" @@ -1702,33 +1668,64 @@ $$ } ``` +=== "C" + + ```c title="coin_change_ii.c" + [class]{}-[func]{coinChangeIIDP} + ``` + +=== "Zig" + + ```zig title="coin_change_ii.zig" + // 零钱兑换 II:动态规划 + fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 初始化首列 + for (0..n + 1) |i| { + dp[i][0] = 1; + } + // 状态转移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过背包容量,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[n][amt]; + } + ``` + ### 3.   空间优化 空间优化处理方式相同,删除硬币维度即可。 -=== "Java" +=== "Python" - ```java title="coin_change_ii.java" - /* 零钱兑换 II:空间优化后的动态规划 */ - int coinChangeIIDPComp(int[] coins, int amt) { - int n = coins.length; - // 初始化 dp 表 - int[] dp = new int[amt + 1]; - dp[0] = 1; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a]; - } else { - // 不选和选硬币 i 这两种方案之和 - dp[a] = dp[a] + dp[a - coins[i - 1]]; - } - } - } - return dp[amt]; - } + ```python title="coin_change_ii.py" + def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """零钱兑换 II:空间优化后的动态规划""" + n = len(coins) + # 初始化 dp 表 + dp = [0] * (amt + 1) + dp[0] = 1 + # 状态转移 + for i in range(1, n + 1): + # 正序遍历 + for a in range(1, amt + 1): + if coins[i - 1] > a: + # 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + else: + # 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] ``` === "C++" @@ -1756,26 +1753,54 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="coin_change_ii.py" - def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: - """零钱兑换 II:空间优化后的动态规划""" - n = len(coins) - # 初始化 dp 表 - dp = [0] * (amt + 1) - dp[0] = 1 - # 状态转移 - for i in range(1, n + 1): - # 正序遍历 - for a in range(1, amt + 1): - if coins[i - 1] > a: - # 若超过背包容量,则不选硬币 i - dp[a] = dp[a] - else: - # 不选和选硬币 i 这两种方案之和 - dp[a] = dp[a] + dp[a - coins[i - 1]] - return dp[amt] + ```java title="coin_change_ii.java" + /* 零钱兑换 II:空间优化后的动态规划 */ + int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + ``` + +=== "C#" + + ```csharp title="coin_change_ii.cs" + /* 零钱兑换 II:空间优化后的动态规划 */ + int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } ``` === "Go" @@ -1804,6 +1829,31 @@ $$ } ``` +=== "Swift" + + ```swift title="coin_change_ii.swift" + /* 零钱兑换 II:空间优化后的动态规划 */ + func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // 状态转移 + for i in stride(from: 1, through: n, by: 1) { + for a in stride(from: 1, through: amt, by: 1) { + if coins[i - 1] > a { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a] + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] + } + ``` + === "JS" ```javascript title="coin_change_ii.js" @@ -1854,87 +1904,6 @@ $$ } ``` -=== "C" - - ```c title="coin_change_ii.c" - [class]{}-[func]{coinChangeIIDPComp} - ``` - -=== "C#" - - ```csharp title="coin_change_ii.cs" - /* 零钱兑换 II:空间优化后的动态规划 */ - int coinChangeIIDPComp(int[] coins, int amt) { - int n = coins.Length; - // 初始化 dp 表 - int[] dp = new int[amt + 1]; - dp[0] = 1; - // 状态转移 - for (int i = 1; i <= n; i++) { - for (int a = 1; a <= amt; a++) { - if (coins[i - 1] > a) { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a]; - } else { - // 不选和选硬币 i 这两种方案之和 - dp[a] = dp[a] + dp[a - coins[i - 1]]; - } - } - } - return dp[amt]; - } - ``` - -=== "Swift" - - ```swift title="coin_change_ii.swift" - /* 零钱兑换 II:空间优化后的动态规划 */ - func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { - let n = coins.count - // 初始化 dp 表 - var dp = Array(repeating: 0, count: amt + 1) - dp[0] = 1 - // 状态转移 - for i in stride(from: 1, through: n, by: 1) { - for a in stride(from: 1, through: amt, by: 1) { - if coins[i - 1] > a { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a] - } else { - // 不选和选硬币 i 这两种方案之和 - dp[a] = dp[a] + dp[a - coins[i - 1]] - } - } - } - return dp[amt] - } - ``` - -=== "Zig" - - ```zig title="coin_change_ii.zig" - // 零钱兑换 II:空间优化后的动态规划 - fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { - comptime var n = coins.len; - // 初始化 dp 表 - var dp = [_]i32{0} ** (amt + 1); - dp[0] = 1; - // 状态转移 - for (1..n + 1) |i| { - for (1..amt + 1) |a| { - if (coins[i - 1] > @as(i32, @intCast(a))) { - // 若超过背包容量,则不选硬币 i - dp[a] = dp[a]; - } else { - // 不选和选硬币 i 这两种方案的较小值 - dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; - } - } - } - return dp[amt]; - } - ``` - === "Dart" ```dart title="coin_change_ii.dart" @@ -1984,3 +1953,34 @@ $$ dp[amt] } ``` + +=== "C" + + ```c title="coin_change_ii.c" + [class]{}-[func]{coinChangeIIDPComp} + ``` + +=== "Zig" + + ```zig title="coin_change_ii.zig" + // 零钱兑换 II:空间优化后的动态规划 + fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + dp[0] = 1; + // 状态转移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超过背包容量,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[amt]; + } + ``` diff --git a/chapter_graph/graph_operations.md b/chapter_graph/graph_operations.md index efe0f47e6..0d41cb10f 100644 --- a/chapter_graph/graph_operations.md +++ b/chapter_graph/graph_operations.md @@ -34,6 +34,171 @@ comments: true 以下是基于邻接矩阵表示图的实现代码。 +=== "Python" + + ```python title="graph_adjacency_matrix.py" + class GraphAdjMat: + """基于邻接矩阵实现的无向图类""" + + # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vertices: list[int] = [] + # 邻接矩阵,行列索引对应“顶点索引” + adj_mat: list[list[int]] = [] + + def __init__(self, vertices: list[int], edges: list[list[int]]): + """构造方法""" + self.vertices: list[int] = [] + self.adj_mat: list[list[int]] = [] + # 添加顶点 + for val in vertices: + self.add_vertex(val) + # 添加边 + # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """获取顶点数量""" + return len(self.vertices) + + def add_vertex(self, val: int): + """添加顶点""" + n = self.size() + # 向顶点列表中添加新顶点的值 + self.vertices.append(val) + # 在邻接矩阵中添加一行 + new_row = [0] * n + self.adj_mat.append(new_row) + # 在邻接矩阵中添加一列 + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """删除顶点""" + if index >= self.size(): + raise IndexError() + # 在顶点列表中移除索引 index 的顶点 + self.vertices.pop(index) + # 在邻接矩阵中删除索引 index 的行 + self.adj_mat.pop(index) + # 在邻接矩阵中删除索引 index 的列 + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """添加边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) + self.adj_mat[i][j] = 1 + self.adj_mat[j][i] = 1 + + def remove_edge(self, i: int, j: int): + """删除边""" + # 参数 i, j 对应 vertices 元素索引 + # 索引越界与相等处理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + self.adj_mat[i][j] = 0 + self.adj_mat[j][i] = 0 + + def print(self): + """打印邻接矩阵""" + print("顶点列表 =", self.vertices) + print("邻接矩阵 =") + print_matrix(self.adj_mat) + ``` + +=== "C++" + + ```cpp title="graph_adjacency_matrix.cpp" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + public: + /* 构造方法 */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() const { + return vertices.size(); + } + + /* 添加顶点 */ + void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.push_back(val); + // 在邻接矩阵中添加一行 + adjMat.emplace_back(vector(n, 0)); + // 在邻接矩阵中添加一列 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 删除顶点 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("顶点不存在"); + } + // 在顶点列表中移除索引 index 的顶点 + vertices.erase(vertices.begin() + index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.erase(adjMat.begin() + index); + // 在邻接矩阵中删除索引 index 的列 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void print() { + cout << "顶点列表 = "; + printVector(vertices); + cout << "邻接矩阵 =" << endl; + printVectorMatrix(adjMat); + } + }; + ``` + === "Java" ```java title="graph_adjacency_matrix.java" @@ -124,68 +289,71 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="graph_adjacency_matrix.cpp" + ```csharp title="graph_adjacency_matrix.cs" /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { - vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” - public: - /* 构造方法 */ - GraphAdjMat(const vector &vertices, const vector> &edges) { + /* 构造函数 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new List(); + this.adjMat = new List>(); // 添加顶点 - for (int val : vertices) { + foreach (int val in vertices) { addVertex(val); } // 添加边 // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - for (const vector &edge : edges) { - addEdge(edge[0], edge[1]); + foreach (int[] e in edges) { + addEdge(e[0], e[1]); } } /* 获取顶点数量 */ - int size() const { - return vertices.size(); + public int size() { + return vertices.Count; } /* 添加顶点 */ - void addVertex(int val) { + public void addVertex(int val) { int n = size(); // 向顶点列表中添加新顶点的值 - vertices.push_back(val); + vertices.Add(val); // 在邻接矩阵中添加一行 - adjMat.emplace_back(vector(n, 0)); + List newRow = new List(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); // 在邻接矩阵中添加一列 - for (vector &row : adjMat) { - row.push_back(0); + foreach (List row in adjMat) { + row.Add(0); } } /* 删除顶点 */ - void removeVertex(int index) { - if (index >= size()) { - throw out_of_range("顶点不存在"); - } + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfRangeException(); // 在顶点列表中移除索引 index 的顶点 - vertices.erase(vertices.begin() + index); + vertices.RemoveAt(index); // 在邻接矩阵中删除索引 index 的行 - adjMat.erase(adjMat.begin() + index); + adjMat.RemoveAt(index); // 在邻接矩阵中删除索引 index 的列 - for (vector &row : adjMat) { - row.erase(row.begin() + index); + foreach (List row in adjMat) { + row.RemoveAt(index); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 - void addEdge(int i, int j) { + public void addEdge(int i, int j) { // 索引越界与相等处理 - if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { - throw out_of_range("顶点不存在"); - } + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfRangeException(); // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) adjMat[i][j] = 1; adjMat[j][i] = 1; @@ -193,100 +361,22 @@ comments: true /* 删除边 */ // 参数 i, j 对应 vertices 元素索引 - void removeEdge(int i, int j) { + public void removeEdge(int i, int j) { // 索引越界与相等处理 - if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { - throw out_of_range("顶点不存在"); - } + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfRangeException(); adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 打印邻接矩阵 */ - void print() { - cout << "顶点列表 = "; - printVector(vertices); - cout << "邻接矩阵 =" << endl; - printVectorMatrix(adjMat); + public void print() { + Console.Write("顶点列表 = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("邻接矩阵 ="); + PrintUtil.PrintMatrix(adjMat); } - }; - ``` - -=== "Python" - - ```python title="graph_adjacency_matrix.py" - class GraphAdjMat: - """基于邻接矩阵实现的无向图类""" - - # 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - vertices: list[int] = [] - # 邻接矩阵,行列索引对应“顶点索引” - adj_mat: list[list[int]] = [] - - def __init__(self, vertices: list[int], edges: list[list[int]]): - """构造方法""" - self.vertices: list[int] = [] - self.adj_mat: list[list[int]] = [] - # 添加顶点 - for val in vertices: - self.add_vertex(val) - # 添加边 - # 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - for e in edges: - self.add_edge(e[0], e[1]) - - def size(self) -> int: - """获取顶点数量""" - return len(self.vertices) - - def add_vertex(self, val: int): - """添加顶点""" - n = self.size() - # 向顶点列表中添加新顶点的值 - self.vertices.append(val) - # 在邻接矩阵中添加一行 - new_row = [0] * n - self.adj_mat.append(new_row) - # 在邻接矩阵中添加一列 - for row in self.adj_mat: - row.append(0) - - def remove_vertex(self, index: int): - """删除顶点""" - if index >= self.size(): - raise IndexError() - # 在顶点列表中移除索引 index 的顶点 - self.vertices.pop(index) - # 在邻接矩阵中删除索引 index 的行 - self.adj_mat.pop(index) - # 在邻接矩阵中删除索引 index 的列 - for row in self.adj_mat: - row.pop(index) - - def add_edge(self, i: int, j: int): - """添加边""" - # 参数 i, j 对应 vertices 元素索引 - # 索引越界与相等处理 - if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: - raise IndexError() - # 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) - self.adj_mat[i][j] = 1 - self.adj_mat[j][i] = 1 - - def remove_edge(self, i: int, j: int): - """删除边""" - # 参数 i, j 对应 vertices 元素索引 - # 索引越界与相等处理 - if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: - raise IndexError() - self.adj_mat[i][j] = 0 - self.adj_mat[j][i] = 0 - - def print(self): - """打印邻接矩阵""" - print("顶点列表 =", self.vertices) - print("邻接矩阵 =") - print_matrix(self.adj_mat) + } ``` === "Go" @@ -388,6 +478,96 @@ comments: true } ``` +=== "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 + } + + /* 打印邻接矩阵 */ + func print() { + Swift.print("顶点列表 = ", terminator: "") + Swift.print(vertices) + Swift.print("邻接矩阵 =") + PrintUtil.printMatrix(matrix: adjMat) + } + } + ``` + === "JS" ```javascript title="graph_adjacency_matrix.js" @@ -572,366 +752,6 @@ comments: true } ``` -=== "C" - - ```c title="graph_adjacency_matrix.c" - /* 基于邻接矩阵实现的无向图类结构 */ - struct graphAdjMat { - int *vertices; // 顶点列表 - unsigned int **adjMat; // 邻接矩阵,元素代表“边”,索引代表“顶点索引” - unsigned int size; // 顶点数量 - unsigned int capacity; // 图容量 - }; - - typedef struct graphAdjMat graphAdjMat; - - /* 添加边 */ - // 参数 i, j 对应 vertices 元素索引 - void addEdge(graphAdjMat *t, int i, int j) { - // 越界检查 - if (i < 0 || j < 0 || i >= t->size || j >= t->size || i == j) { - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - exit(1); - } - // 添加边 - // 参数 i, j 对应 vertices 元素索引 - t->adjMat[i][j] = 1; - t->adjMat[j][i] = 1; - } - - /* 删除边 */ - // 参数 i, j 对应 vertices 元素索引 - void removeEdge(graphAdjMat *t, int i, int j) { - // 越界检查 - if (i < 0 || j < 0 || i >= t->size || j >= t->size || i == j) { - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - exit(1); - } - // 删除边 - // 参数 i, j 对应 vertices 元素索引 - t->adjMat[i][j] = 0; - t->adjMat[j][i] = 0; - } - - /* 添加顶点 */ - void addVertex(graphAdjMat *t, int val) { - // 如果实际使用不大于预设空间,则直接初始化新空间 - if (t->size < t->capacity) { - t->vertices[t->size] = val; // 初始化新顶点值 - for (int i = 0; i < t->size; i++) { - t->adjMat[i][t->size] = 0; // 邻接矩新列阵置0 - } - memset(t->adjMat[t->size], 0, sizeof(unsigned int) * (t->size + 1)); // 将新增行置 0 - t->size++; - return; - } - - // 扩容,申请新的顶点数组 - int *temp = (int *)malloc(sizeof(int) * (t->size * 2)); - memcpy(temp, t->vertices, sizeof(int) * t->size); - temp[t->size] = val; - - // 释放原数组 - free(t->vertices); - t->vertices = temp; - - // 扩容,申请新的二维数组 - unsigned int **tempMat = (unsigned int **)malloc(sizeof(unsigned int *) * t->size * 2); - unsigned int *tempMatLine = (unsigned int *)malloc(sizeof(unsigned int) * (t->size * 2) * (t->size * 2)); - memset(tempMatLine, 0, sizeof(unsigned int) * (t->size * 2) * (t->size * 2)); - for (int k = 0; k < t->size * 2; k++) { - tempMat[k] = tempMatLine + k * (t->size * 2); - } - - for (int i = 0; i < t->size; i++) { - memcpy(tempMat[i], t->adjMat[i], sizeof(unsigned int) * t->size); // 原数据复制到新数组 - } - - for (int i = 0; i < t->size; i++) { - tempMat[i][t->size] = 0; // 将新增列置 0 - } - memset(tempMat[t->size], 0, sizeof(unsigned int) * (t->size + 1)); // 将新增行置 0 - - // 释放原数组 - free(t->adjMat[0]); - free(t->adjMat); - - // 扩容后,指向新地址 - t->adjMat = tempMat; // 指向新的邻接矩阵地址 - t->capacity = t->size * 2; - t->size++; - } - - /* 删除顶点 */ - void removeVertex(graphAdjMat *t, unsigned int index) { - // 越界检查 - if (index < 0 || index >= t->size) { - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - exit(1); - } - for (int i = index; i < t->size - 1; i++) { - t->vertices[i] = t->vertices[i + 1]; // 清除删除的顶点,并将其后所有顶点前移 - } - t->vertices[t->size - 1] = 0; // 将被前移的最后一个顶点置 0 - - // 清除邻接矩阵中删除的列 - for (int i = 0; i < t->size - 1; i++) { - if (i < index) { - for (int j = index; j < t->size - 1; j++) { - t->adjMat[i][j] = t->adjMat[i][j + 1]; // 被删除列后的所有列前移 - } - } else { - memcpy(t->adjMat[i], t->adjMat[i + 1], sizeof(unsigned int) * t->size); // 被删除行的下方所有行上移 - for (int j = index; j < t->size; j++) { - t->adjMat[i][j] = t->adjMat[i][j + 1]; // 被删除列后的所有列前移 - } - } - } - t->size--; - } - - /* 打印顶点与邻接矩阵 */ - void printGraph(graphAdjMat *t) { - if (t->size == 0) { - printf("graph is empty\n"); - return; - } - printf("顶点列表 = ["); - for (int i = 0; i < t->size; i++) { - if (i != t->size - 1) { - printf("%d, ", t->vertices[i]); - } else { - printf("%d", t->vertices[i]); - } - } - printf("]\n"); - printf("邻接矩阵 =\n[\n"); - for (int i = 0; i < t->size; i++) { - printf(" ["); - for (int j = 0; j < t->size; j++) { - if (j != t->size - 1) { - printf("%u, ", t->adjMat[i][j]); - } else { - printf("%u", t->adjMat[i][j]); - } - } - printf("],\n"); - } - printf("]\n"); - } - - /* 构造函数 */ - graphAdjMat *newGraphAjdMat(unsigned int numberVertices, int *vertices, unsigned int **adjMat) { - // 申请内存 - graphAdjMat *newGraph = (graphAdjMat *)malloc(sizeof(graphAdjMat)); // 为图分配内存 - newGraph->vertices = (int *)malloc(sizeof(int) * numberVertices * 2); // 为顶点列表分配内存 - newGraph->adjMat = (unsigned int **)malloc(sizeof(unsigned int *) * numberVertices * 2); // 为邻接矩阵分配二维内存 - unsigned int *temp = (unsigned int *)malloc(sizeof(unsigned int) * numberVertices * 2 * numberVertices * 2); // 为邻接矩阵分配一维内存 - newGraph->size = numberVertices; // 初始化顶点数量 - newGraph->capacity = numberVertices * 2; // 初始化图容量 - - // 配置二维数组 - for (int i = 0; i < numberVertices * 2; i++) { - newGraph->adjMat[i] = temp + i * numberVertices * 2; // 将二维指针指向一维数组 - } - - // 赋值 - memcpy(newGraph->vertices, vertices, sizeof(int) * numberVertices); - for (int i = 0; i < numberVertices; i++) { - memcpy(newGraph->adjMat[i], adjMat[i], sizeof(unsigned int) * numberVertices); // 将传入的邻接矩阵赋值给结构体内邻接矩阵 - } - - // 返回结构体指针 - return newGraph; - } - ``` - -=== "C#" - - ```csharp title="graph_adjacency_matrix.cs" - /* 基于邻接矩阵实现的无向图类 */ - class GraphAdjMat { - List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” - - /* 构造函数 */ - public GraphAdjMat(int[] vertices, int[][] edges) { - this.vertices = new List(); - this.adjMat = new List>(); - // 添加顶点 - foreach (int val in vertices) { - addVertex(val); - } - // 添加边 - // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - foreach (int[] e in edges) { - addEdge(e[0], e[1]); - } - } - - /* 获取顶点数量 */ - public int size() { - return vertices.Count; - } - - /* 添加顶点 */ - public void addVertex(int val) { - int n = size(); - // 向顶点列表中添加新顶点的值 - vertices.Add(val); - // 在邻接矩阵中添加一行 - List newRow = new List(n); - for (int j = 0; j < n; j++) { - newRow.Add(0); - } - adjMat.Add(newRow); - // 在邻接矩阵中添加一列 - foreach (List row in adjMat) { - row.Add(0); - } - } - - /* 删除顶点 */ - public void removeVertex(int index) { - if (index >= size()) - throw new IndexOutOfRangeException(); - // 在顶点列表中移除索引 index 的顶点 - vertices.RemoveAt(index); - // 在邻接矩阵中删除索引 index 的行 - adjMat.RemoveAt(index); - // 在邻接矩阵中删除索引 index 的列 - foreach (List row in adjMat) { - row.RemoveAt(index); - } - } - - /* 添加边 */ - // 参数 i, j 对应 vertices 元素索引 - public void addEdge(int i, int j) { - // 索引越界与相等处理 - if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) - throw new IndexOutOfRangeException(); - // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) - adjMat[i][j] = 1; - adjMat[j][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 IndexOutOfRangeException(); - adjMat[i][j] = 0; - adjMat[j][i] = 0; - } - - /* 打印邻接矩阵 */ - public void print() { - Console.Write("顶点列表 = "); - PrintUtil.PrintList(vertices); - Console.WriteLine("邻接矩阵 ="); - PrintUtil.PrintMatrix(adjMat); - } - } - ``` - -=== "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 - } - - /* 打印邻接矩阵 */ - func print() { - Swift.print("顶点列表 = ", terminator: "") - Swift.print(vertices) - Swift.print("邻接矩阵 =") - PrintUtil.printMatrix(matrix: adjMat) - } - } - ``` - -=== "Zig" - - ```zig title="graph_adjacency_matrix.zig" - - ``` - === "Dart" ```dart title="graph_adjacency_matrix.dart" @@ -1122,6 +942,186 @@ comments: true } ``` +=== "C" + + ```c title="graph_adjacency_matrix.c" + /* 基于邻接矩阵实现的无向图类结构 */ + struct graphAdjMat { + int *vertices; // 顶点列表 + unsigned int **adjMat; // 邻接矩阵,元素代表“边”,索引代表“顶点索引” + unsigned int size; // 顶点数量 + unsigned int capacity; // 图容量 + }; + + typedef struct graphAdjMat graphAdjMat; + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(graphAdjMat *t, int i, int j) { + // 越界检查 + if (i < 0 || j < 0 || i >= t->size || j >= t->size || i == j) { + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + exit(1); + } + // 添加边 + // 参数 i, j 对应 vertices 元素索引 + t->adjMat[i][j] = 1; + t->adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(graphAdjMat *t, int i, int j) { + // 越界检查 + if (i < 0 || j < 0 || i >= t->size || j >= t->size || i == j) { + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + exit(1); + } + // 删除边 + // 参数 i, j 对应 vertices 元素索引 + t->adjMat[i][j] = 0; + t->adjMat[j][i] = 0; + } + + /* 添加顶点 */ + void addVertex(graphAdjMat *t, int val) { + // 如果实际使用不大于预设空间,则直接初始化新空间 + if (t->size < t->capacity) { + t->vertices[t->size] = val; // 初始化新顶点值 + for (int i = 0; i < t->size; i++) { + t->adjMat[i][t->size] = 0; // 邻接矩新列阵置0 + } + memset(t->adjMat[t->size], 0, sizeof(unsigned int) * (t->size + 1)); // 将新增行置 0 + t->size++; + return; + } + + // 扩容,申请新的顶点数组 + int *temp = (int *)malloc(sizeof(int) * (t->size * 2)); + memcpy(temp, t->vertices, sizeof(int) * t->size); + temp[t->size] = val; + + // 释放原数组 + free(t->vertices); + t->vertices = temp; + + // 扩容,申请新的二维数组 + unsigned int **tempMat = (unsigned int **)malloc(sizeof(unsigned int *) * t->size * 2); + unsigned int *tempMatLine = (unsigned int *)malloc(sizeof(unsigned int) * (t->size * 2) * (t->size * 2)); + memset(tempMatLine, 0, sizeof(unsigned int) * (t->size * 2) * (t->size * 2)); + for (int k = 0; k < t->size * 2; k++) { + tempMat[k] = tempMatLine + k * (t->size * 2); + } + + for (int i = 0; i < t->size; i++) { + memcpy(tempMat[i], t->adjMat[i], sizeof(unsigned int) * t->size); // 原数据复制到新数组 + } + + for (int i = 0; i < t->size; i++) { + tempMat[i][t->size] = 0; // 将新增列置 0 + } + memset(tempMat[t->size], 0, sizeof(unsigned int) * (t->size + 1)); // 将新增行置 0 + + // 释放原数组 + free(t->adjMat[0]); + free(t->adjMat); + + // 扩容后,指向新地址 + t->adjMat = tempMat; // 指向新的邻接矩阵地址 + t->capacity = t->size * 2; + t->size++; + } + + /* 删除顶点 */ + void removeVertex(graphAdjMat *t, unsigned int index) { + // 越界检查 + if (index < 0 || index >= t->size) { + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + exit(1); + } + for (int i = index; i < t->size - 1; i++) { + t->vertices[i] = t->vertices[i + 1]; // 清除删除的顶点,并将其后所有顶点前移 + } + t->vertices[t->size - 1] = 0; // 将被前移的最后一个顶点置 0 + + // 清除邻接矩阵中删除的列 + for (int i = 0; i < t->size - 1; i++) { + if (i < index) { + for (int j = index; j < t->size - 1; j++) { + t->adjMat[i][j] = t->adjMat[i][j + 1]; // 被删除列后的所有列前移 + } + } else { + memcpy(t->adjMat[i], t->adjMat[i + 1], sizeof(unsigned int) * t->size); // 被删除行的下方所有行上移 + for (int j = index; j < t->size; j++) { + t->adjMat[i][j] = t->adjMat[i][j + 1]; // 被删除列后的所有列前移 + } + } + } + t->size--; + } + + /* 打印顶点与邻接矩阵 */ + void printGraph(graphAdjMat *t) { + if (t->size == 0) { + printf("graph is empty\n"); + return; + } + printf("顶点列表 = ["); + for (int i = 0; i < t->size; i++) { + if (i != t->size - 1) { + printf("%d, ", t->vertices[i]); + } else { + printf("%d", t->vertices[i]); + } + } + printf("]\n"); + printf("邻接矩阵 =\n[\n"); + for (int i = 0; i < t->size; i++) { + printf(" ["); + for (int j = 0; j < t->size; j++) { + if (j != t->size - 1) { + printf("%u, ", t->adjMat[i][j]); + } else { + printf("%u", t->adjMat[i][j]); + } + } + printf("],\n"); + } + printf("]\n"); + } + + /* 构造函数 */ + graphAdjMat *newGraphAjdMat(unsigned int numberVertices, int *vertices, unsigned int **adjMat) { + // 申请内存 + graphAdjMat *newGraph = (graphAdjMat *)malloc(sizeof(graphAdjMat)); // 为图分配内存 + newGraph->vertices = (int *)malloc(sizeof(int) * numberVertices * 2); // 为顶点列表分配内存 + newGraph->adjMat = (unsigned int **)malloc(sizeof(unsigned int *) * numberVertices * 2); // 为邻接矩阵分配二维内存 + unsigned int *temp = (unsigned int *)malloc(sizeof(unsigned int) * numberVertices * 2 * numberVertices * 2); // 为邻接矩阵分配一维内存 + newGraph->size = numberVertices; // 初始化顶点数量 + newGraph->capacity = numberVertices * 2; // 初始化图容量 + + // 配置二维数组 + for (int i = 0; i < numberVertices * 2; i++) { + newGraph->adjMat[i] = temp + i * numberVertices * 2; // 将二维指针指向一维数组 + } + + // 赋值 + memcpy(newGraph->vertices, vertices, sizeof(int) * numberVertices); + for (int i = 0; i < numberVertices; i++) { + memcpy(newGraph->adjMat[i], adjMat[i], sizeof(unsigned int) * numberVertices); // 将传入的邻接矩阵赋值给结构体内邻接矩阵 + } + + // 返回结构体指针 + return newGraph; + } + ``` + +=== "Zig" + + ```zig title="graph_adjacency_matrix.zig" + + ``` + ## 9.2.2   基于邻接表的实现 设无向图的顶点总数为 $n$、边总数为 $m$ ,则可根据图 9-8 所示的方法实现各种操作。 @@ -1155,79 +1155,66 @@ comments: true 2. 如果类似邻接矩阵那样,使用顶点列表索引来区分不同顶点。那么,假设我们想要删除索引为 $i$ 的顶点,则需要遍历整个邻接表,将其中 $> i$ 的索引全部减 $1$ ,这样操作效率较低。 3. 因此我们考虑引入顶点类 `Vertex` ,使得每个顶点都是唯一的对象,此时删除顶点时就无须改动其余顶点了。 -=== "Java" +=== "Python" - ```java title="graph_adjacency_list.java" - /* 基于邻接表实现的无向图类 */ - class GraphAdjList { - // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 - Map> adjList; + ```python title="graph_adjacency_list.py" + class GraphAdjList: + """基于邻接表实现的无向图类""" - /* 构造方法 */ - public GraphAdjList(Vertex[][] edges) { - this.adjList = new HashMap<>(); - // 添加所有顶点和边 - for (Vertex[] edge : edges) { - addVertex(edge[0]); - addVertex(edge[1]); - addEdge(edge[0], edge[1]); - } - } + def __init__(self, edges: list[list[Vertex]]): + """构造方法""" + # 邻接表,key: 顶点,value:该顶点的所有邻接顶点 + self.adj_list = dict[Vertex, Vertex]() + # 添加所有顶点和边 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) - /* 获取顶点数量 */ - public int size() { - return adjList.size(); - } + def size(self) -> int: + """获取顶点数量""" + return len(self.adj_list) - /* 添加边 */ - 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); - } + def add_edge(self, vet1: Vertex, vet2: Vertex): + """添加边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 添加边 vet1 - vet2 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(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); - } + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """删除边""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 删除边 vet1 - vet2 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) - /* 添加顶点 */ - public void addVertex(Vertex vet) { - if (adjList.containsKey(vet)) - return; - // 在邻接表中添加一个新链表 - adjList.put(vet, new ArrayList<>()); - } + def add_vertex(self, vet: Vertex): + """添加顶点""" + if vet in self.adj_list: + return + # 在邻接表中添加一个新链表 + self.adj_list[vet] = [] - /* 删除顶点 */ - public void removeVertex(Vertex vet) { - if (!adjList.containsKey(vet)) - throw new IllegalArgumentException(); - // 在邻接表中删除顶点 vet 对应的链表 - adjList.remove(vet); - // 遍历其他顶点的链表,删除所有包含 vet 的边 - for (List list : adjList.values()) { - list.remove(vet); - } - } + def remove_vertex(self, vet: Vertex): + """删除顶点""" + if vet not in self.adj_list: + raise ValueError() + # 在邻接表中删除顶点 vet 对应的链表 + self.adj_list.pop(vet) + # 遍历其他顶点的链表,删除所有包含 vet 的边 + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) - /* 打印邻接表 */ - public void print() { - System.out.println("邻接表 ="); - for (Map.Entry> pair : adjList.entrySet()) { - List tmp = new ArrayList<>(); - for (Vertex vertex : pair.getValue()) - tmp.add(vertex.val); - System.out.println(pair.getKey().val + ": " + tmp + ","); - } - } - } + def print(self): + """打印邻接表""" + print("邻接表 =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") ``` === "C++" @@ -1315,66 +1302,154 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="graph_adjacency_list.py" - class GraphAdjList: - """基于邻接表实现的无向图类""" + ```java title="graph_adjacency_list.java" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 + Map> adjList; - def __init__(self, edges: list[list[Vertex]]): - """构造方法""" - # 邻接表,key: 顶点,value:该顶点的所有邻接顶点 - self.adj_list = dict[Vertex, Vertex]() - # 添加所有顶点和边 - for edge in edges: - self.add_vertex(edge[0]) - self.add_vertex(edge[1]) - self.add_edge(edge[0], edge[1]) + /* 构造方法 */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // 添加所有顶点和边 + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } - def size(self) -> int: - """获取顶点数量""" - return len(self.adj_list) + /* 获取顶点数量 */ + public int size() { + return adjList.size(); + } - def add_edge(self, vet1: Vertex, vet2: Vertex): - """添加边""" - if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: - raise ValueError() - # 添加边 vet1 - vet2 - self.adj_list[vet1].append(vet2) - self.adj_list[vet2].append(vet1) + /* 添加边 */ + 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); + } - def remove_edge(self, vet1: Vertex, vet2: Vertex): - """删除边""" - if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: - raise ValueError() - # 删除边 vet1 - vet2 - self.adj_list[vet1].remove(vet2) - self.adj_list[vet2].remove(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); + } - def add_vertex(self, vet: Vertex): - """添加顶点""" - if vet in self.adj_list: - return - # 在邻接表中添加一个新链表 - self.adj_list[vet] = [] + /* 添加顶点 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 在邻接表中添加一个新链表 + adjList.put(vet, new ArrayList<>()); + } - def remove_vertex(self, vet: Vertex): - """删除顶点""" - if vet not in self.adj_list: - raise ValueError() - # 在邻接表中删除顶点 vet 对应的链表 - self.adj_list.pop(vet) - # 遍历其他顶点的链表,删除所有包含 vet 的边 - for vertex in self.adj_list: - if vet in self.adj_list[vertex]: - self.adj_list[vertex].remove(vet) + /* 删除顶点 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (List list : adjList.values()) { + list.remove(vet); + } + } - def print(self): - """打印邻接表""" - print("邻接表 =") - for vertex in self.adj_list: - tmp = [v.val for v in self.adj_list[vertex]] - print(f"{vertex.val}: {tmp},") + /* 打印邻接表 */ + public void print() { + System.out.println("邻接表 ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } + } + ``` + +=== "C#" + + ```csharp title="graph_adjacency_list.cs" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 + public Dictionary> adjList; + + /* 构造函数 */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new Dictionary>(); + // 添加所有顶点和边 + foreach (Vertex[] edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + public int size() { + return adjList.Count; + } + + /* 添加边 */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 添加边 vet1 - vet2 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* 删除边 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 删除边 vet1 - vet2 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* 添加顶点 */ + public void addVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // 在邻接表中添加一个新链表 + adjList.Add(vet, new List()); + } + + /* 删除顶点 */ + public void removeVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.Remove(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* 打印邻接表 */ + public void print() { + Console.WriteLine("邻接表 ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = new List(); + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } + } ``` === "Go" @@ -1468,6 +1543,86 @@ comments: true } ``` +=== "Swift" + + ```swift title="graph_adjacency_list.swift" + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 构造方法 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 添加所有顶点和边 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 获取顶点数量 */ + public func size() -> Int { + adjList.count + } + + /* 添加边 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("参数错误") + } + // 添加边 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 删除边 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("参数错误") + } + // 删除边 vet1 - vet2 + adjList[vet1]?.removeAll(where: { $0 == vet2 }) + adjList[vet2]?.removeAll(where: { $0 == vet1 }) + } + + /* 添加顶点 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在邻接表中添加一个新链表 + adjList[vet] = [] + } + + /* 删除顶点 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("参数错误") + } + // 在邻接表中删除顶点 vet 对应的链表 + adjList.removeValue(forKey: vet) + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for key in adjList.keys { + adjList[key]?.removeAll(where: { $0 == vet }) + } + } + + /* 打印邻接表 */ + public func print() { + Swift.print("邻接表 =") + for pair in adjList { + var tmp: [Int] = [] + for vertex in pair.value { + tmp.append(vertex.val) + } + Swift.print("\(pair.key.val): \(tmp),") + } + } + } + ``` + === "JS" ```javascript title="graph_adjacency_list.js" @@ -1646,296 +1801,6 @@ comments: true } ``` -=== "C" - - ```c title="graph_adjacency_list.c" - /* 基于邻接链表实现的无向图类结构 */ - struct graphAdjList { - Vertex **verticesList; // 邻接表 - unsigned int size; // 顶点数量 - unsigned int capacity; // 顶点容量 - }; - - typedef struct graphAdjList graphAdjList; - - /* 添加边 */ - void addEdge(graphAdjList *t, int i, int j) { - // 越界检查 - if (i < 0 || j < 0 || i == j || i >= t->size || j >= t->size) { - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - return; - } - // 查找欲添加边的顶点 vet1 - vet2 - Vertex *vet1 = t->verticesList[i]; - Vertex *vet2 = t->verticesList[j]; - - // 连接顶点 vet1 - vet2 - pushBack(vet1->linked, vet2); - pushBack(vet2->linked, vet1); - } - - /* 删除边 */ - void removeEdge(graphAdjList *t, int i, int j) { - // 越界检查 - if (i < 0 || j < 0 || i == j || i >= t->size || j >= t->size) { - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - return; - } - - // 查找欲删除边的顶点 vet1 - vet2 - Vertex *vet1 = t->verticesList[i]; - Vertex *vet2 = t->verticesList[j]; - - // 移除待删除边 vet1 - vet2 - removeLink(vet1->linked, vet2); - removeLink(vet2->linked, vet1); - } - - /* 添加顶点 */ - void addVertex(graphAdjList *t, int val) { - // 若大小超过容量,则扩容 - if (t->size >= t->capacity) { - Vertex **tempList = (Vertex **)malloc(sizeof(Vertex *) * 2 * t->capacity); - memcpy(tempList, t->verticesList, sizeof(Vertex *) * t->size); - free(t->verticesList); // 释放原邻接表内存 - t->verticesList = tempList; // 指向新邻接表 - t->capacity = t->capacity * 2; // 容量扩大至2倍 - } - // 申请新顶点内存并将新顶点地址存入顶点列表 - Vertex *newV = newVertex(val); // 建立新顶点 - newV->pos = t->size; // 为新顶点标记下标 - newV->linked = newLinklist(newV); // 为新顶点建立链表 - t->verticesList[t->size] = newV; // 将新顶点加入邻接表 - t->size++; - } - - /* 删除顶点 */ - void removeVertex(graphAdjList *t, unsigned int index) { - // 越界检查 - if (index < 0 || index >= t->size) { - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - exit(1); - } - - Vertex *vet = t->verticesList[index]; // 查找待删节点 - if (vet == 0) { // 若不存在该节点,则返回 - printf("index is:%d\n", index); - printf("Out of range in %s:%d\n", __FILE__, __LINE__); - return; - } - - // 遍历待删除顶点的链表,将所有与待删除结点有关的边删除 - Node *temp = vet->linked->head->next; - while (temp != 0) { - removeLink(temp->val->linked, vet); // 删除与该顶点有关的边 - temp = temp->next; - } - - // 将顶点前移 - for (int i = index; i < t->size - 1; i++) { - t->verticesList[i] = t->verticesList[i + 1]; // 顶点前移 - t->verticesList[i]->pos--; // 所有前移的顶点索引值减1 - } - t->verticesList[t->size - 1] = 0; // 将被删除顶点的位置置 0 - t->size--; - - // 释放内存 - freeVertex(vet); - } - - /* 打印顶点与邻接矩阵 */ - void printGraph(graphAdjList *t) { - printf("邻接表 =\n"); - for (int i = 0; i < t->size; i++) { - Node *n = t->verticesList[i]->linked->head->next; - printf("%d: [", t->verticesList[i]->val); - while (n != 0) { - if (n->next != 0) { - printf("%d, ", n->val->val); - } else { - printf("%d", n->val->val); - } - n = n->next; - } - printf("]\n"); - } - } - - /* 构造函数 */ - graphAdjList *newGraphAdjList(unsigned int verticesCapacity) { - // 申请内存 - graphAdjList *newGraph = (graphAdjList *)malloc(sizeof(graphAdjList)); - // 建立顶点表并分配内存 - newGraph->verticesList = (Vertex **)malloc(sizeof(Vertex *) * verticesCapacity); // 为顶点列表分配内存 - memset(newGraph->verticesList, 0, sizeof(Vertex *) * verticesCapacity); // 顶点列表置 0 - newGraph->size = 0; // 初始化顶点数量 - newGraph->capacity = verticesCapacity; // 初始化顶点容量 - // 返回图指针 - return newGraph; - } - ``` - -=== "C#" - - ```csharp title="graph_adjacency_list.cs" - /* 基于邻接表实现的无向图类 */ - class GraphAdjList { - // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 - public Dictionary> adjList; - - /* 构造函数 */ - public GraphAdjList(Vertex[][] edges) { - this.adjList = new Dictionary>(); - // 添加所有顶点和边 - foreach (Vertex[] edge in edges) { - addVertex(edge[0]); - addVertex(edge[1]); - addEdge(edge[0], edge[1]); - } - } - - /* 获取顶点数量 */ - public int size() { - return adjList.Count; - } - - /* 添加边 */ - public void addEdge(Vertex vet1, Vertex vet2) { - if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) - throw new InvalidOperationException(); - // 添加边 vet1 - vet2 - adjList[vet1].Add(vet2); - adjList[vet2].Add(vet1); - } - - /* 删除边 */ - public void removeEdge(Vertex vet1, Vertex vet2) { - if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) - throw new InvalidOperationException(); - // 删除边 vet1 - vet2 - adjList[vet1].Remove(vet2); - adjList[vet2].Remove(vet1); - } - - /* 添加顶点 */ - public void addVertex(Vertex vet) { - if (adjList.ContainsKey(vet)) - return; - // 在邻接表中添加一个新链表 - adjList.Add(vet, new List()); - } - - /* 删除顶点 */ - public void removeVertex(Vertex vet) { - if (!adjList.ContainsKey(vet)) - throw new InvalidOperationException(); - // 在邻接表中删除顶点 vet 对应的链表 - adjList.Remove(vet); - // 遍历其他顶点的链表,删除所有包含 vet 的边 - foreach (List list in adjList.Values) { - list.Remove(vet); - } - } - - /* 打印邻接表 */ - public void print() { - Console.WriteLine("邻接表 ="); - foreach (KeyValuePair> pair in adjList) { - List tmp = new List(); - foreach (Vertex vertex in pair.Value) - tmp.Add(vertex.val); - Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); - } - } - } - ``` - -=== "Swift" - - ```swift title="graph_adjacency_list.swift" - /* 基于邻接表实现的无向图类 */ - class GraphAdjList { - // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 - public private(set) var adjList: [Vertex: [Vertex]] - - /* 构造方法 */ - public init(edges: [[Vertex]]) { - adjList = [:] - // 添加所有顶点和边 - for edge in edges { - addVertex(vet: edge[0]) - addVertex(vet: edge[1]) - addEdge(vet1: edge[0], vet2: edge[1]) - } - } - - /* 获取顶点数量 */ - public func size() -> Int { - adjList.count - } - - /* 添加边 */ - public func addEdge(vet1: Vertex, vet2: Vertex) { - if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { - fatalError("参数错误") - } - // 添加边 vet1 - vet2 - adjList[vet1]?.append(vet2) - adjList[vet2]?.append(vet1) - } - - /* 删除边 */ - public func removeEdge(vet1: Vertex, vet2: Vertex) { - if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { - fatalError("参数错误") - } - // 删除边 vet1 - vet2 - adjList[vet1]?.removeAll(where: { $0 == vet2 }) - adjList[vet2]?.removeAll(where: { $0 == vet1 }) - } - - /* 添加顶点 */ - public func addVertex(vet: Vertex) { - if adjList[vet] != nil { - return - } - // 在邻接表中添加一个新链表 - adjList[vet] = [] - } - - /* 删除顶点 */ - public func removeVertex(vet: Vertex) { - if adjList[vet] == nil { - fatalError("参数错误") - } - // 在邻接表中删除顶点 vet 对应的链表 - adjList.removeValue(forKey: vet) - // 遍历其他顶点的链表,删除所有包含 vet 的边 - for key in adjList.keys { - adjList[key]?.removeAll(where: { $0 == vet }) - } - } - - /* 打印邻接表 */ - public func print() { - Swift.print("邻接表 =") - for pair in adjList { - var tmp: [Int] = [] - for vertex in pair.value { - tmp.append(vertex.val) - } - Swift.print("\(pair.key.val): \(tmp),") - } - } - } - ``` - -=== "Zig" - - ```zig title="graph_adjacency_list.zig" - [class]{GraphAdjList}-[func]{} - ``` - === "Dart" ```dart title="graph_adjacency_list.dart" @@ -2110,6 +1975,141 @@ comments: true } ``` +=== "C" + + ```c title="graph_adjacency_list.c" + /* 基于邻接链表实现的无向图类结构 */ + struct graphAdjList { + Vertex **verticesList; // 邻接表 + unsigned int size; // 顶点数量 + unsigned int capacity; // 顶点容量 + }; + + typedef struct graphAdjList graphAdjList; + + /* 添加边 */ + void addEdge(graphAdjList *t, int i, int j) { + // 越界检查 + if (i < 0 || j < 0 || i == j || i >= t->size || j >= t->size) { + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + return; + } + // 查找欲添加边的顶点 vet1 - vet2 + Vertex *vet1 = t->verticesList[i]; + Vertex *vet2 = t->verticesList[j]; + + // 连接顶点 vet1 - vet2 + pushBack(vet1->linked, vet2); + pushBack(vet2->linked, vet1); + } + + /* 删除边 */ + void removeEdge(graphAdjList *t, int i, int j) { + // 越界检查 + if (i < 0 || j < 0 || i == j || i >= t->size || j >= t->size) { + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + return; + } + + // 查找欲删除边的顶点 vet1 - vet2 + Vertex *vet1 = t->verticesList[i]; + Vertex *vet2 = t->verticesList[j]; + + // 移除待删除边 vet1 - vet2 + removeLink(vet1->linked, vet2); + removeLink(vet2->linked, vet1); + } + + /* 添加顶点 */ + void addVertex(graphAdjList *t, int val) { + // 若大小超过容量,则扩容 + if (t->size >= t->capacity) { + Vertex **tempList = (Vertex **)malloc(sizeof(Vertex *) * 2 * t->capacity); + memcpy(tempList, t->verticesList, sizeof(Vertex *) * t->size); + free(t->verticesList); // 释放原邻接表内存 + t->verticesList = tempList; // 指向新邻接表 + t->capacity = t->capacity * 2; // 容量扩大至2倍 + } + // 申请新顶点内存并将新顶点地址存入顶点列表 + Vertex *newV = newVertex(val); // 建立新顶点 + newV->pos = t->size; // 为新顶点标记下标 + newV->linked = newLinklist(newV); // 为新顶点建立链表 + t->verticesList[t->size] = newV; // 将新顶点加入邻接表 + t->size++; + } + + /* 删除顶点 */ + void removeVertex(graphAdjList *t, unsigned int index) { + // 越界检查 + if (index < 0 || index >= t->size) { + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + exit(1); + } + + Vertex *vet = t->verticesList[index]; // 查找待删节点 + if (vet == 0) { // 若不存在该节点,则返回 + printf("index is:%d\n", index); + printf("Out of range in %s:%d\n", __FILE__, __LINE__); + return; + } + + // 遍历待删除顶点的链表,将所有与待删除结点有关的边删除 + Node *temp = vet->linked->head->next; + while (temp != 0) { + removeLink(temp->val->linked, vet); // 删除与该顶点有关的边 + temp = temp->next; + } + + // 将顶点前移 + for (int i = index; i < t->size - 1; i++) { + t->verticesList[i] = t->verticesList[i + 1]; // 顶点前移 + t->verticesList[i]->pos--; // 所有前移的顶点索引值减1 + } + t->verticesList[t->size - 1] = 0; // 将被删除顶点的位置置 0 + t->size--; + + // 释放内存 + freeVertex(vet); + } + + /* 打印顶点与邻接矩阵 */ + void printGraph(graphAdjList *t) { + printf("邻接表 =\n"); + for (int i = 0; i < t->size; i++) { + Node *n = t->verticesList[i]->linked->head->next; + printf("%d: [", t->verticesList[i]->val); + while (n != 0) { + if (n->next != 0) { + printf("%d, ", n->val->val); + } else { + printf("%d", n->val->val); + } + n = n->next; + } + printf("]\n"); + } + } + + /* 构造函数 */ + graphAdjList *newGraphAdjList(unsigned int verticesCapacity) { + // 申请内存 + graphAdjList *newGraph = (graphAdjList *)malloc(sizeof(graphAdjList)); + // 建立顶点表并分配内存 + newGraph->verticesList = (Vertex **)malloc(sizeof(Vertex *) * verticesCapacity); // 为顶点列表分配内存 + memset(newGraph->verticesList, 0, sizeof(Vertex *) * verticesCapacity); // 顶点列表置 0 + newGraph->size = 0; // 初始化顶点数量 + newGraph->capacity = verticesCapacity; // 初始化顶点容量 + // 返回图指针 + return newGraph; + } + ``` + +=== "Zig" + + ```zig title="graph_adjacency_list.zig" + [class]{GraphAdjList}-[func]{} + ``` + ## 9.2.3   效率对比 设图中共有 $n$ 个顶点和 $m$ 条边,表 9-2 对比了邻接矩阵和邻接表的时间和空间效率。 diff --git a/chapter_graph/graph_traversal.md b/chapter_graph/graph_traversal.md index 3f73682e7..0b1fbf543 100644 --- a/chapter_graph/graph_traversal.md +++ b/chapter_graph/graph_traversal.md @@ -26,35 +26,30 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 为了防止重复遍历顶点,我们需要借助一个哈希表 `visited` 来记录哪些节点已被访问。 -=== "Java" +=== "Python" - ```java title="graph_bfs.java" - /* 广度优先遍历 BFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - List graphBFS(GraphAdjList graph, Vertex startVet) { - // 顶点遍历序列 - List res = new ArrayList<>(); - // 哈希表,用于记录已被访问过的顶点 - Set visited = new HashSet<>(); - visited.add(startVet); - // 队列用于实现 BFS - Queue que = new LinkedList<>(); - que.offer(startVet); - // 以顶点 vet 为起点,循环直至访问完所有顶点 - while (!que.isEmpty()) { - Vertex vet = que.poll(); // 队首顶点出队 - res.add(vet); // 记录访问顶点 - // 遍历该顶点的所有邻接顶点 - for (Vertex adjVet : graph.adjList.get(vet)) { - if (visited.contains(adjVet)) - continue; // 跳过已被访问过的顶点 - que.offer(adjVet); // 只入队未访问的顶点 - visited.add(adjVet); // 标记该顶点已被访问 - } - } - // 返回顶点遍历序列 - return res; - } + ```python title="graph_bfs.py" + def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """广度优先遍历 BFS""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set[Vertex]([start_vet]) + # 队列用于实现 BFS + que = deque[Vertex]([start_vet]) + # 以顶点 vet 为起点,循环直至访问完所有顶点 + while len(que) > 0: + vet = que.popleft() # 队首顶点出队 + res.append(vet) # 记录访问顶点 + # 遍历该顶点的所有邻接顶点 + for adj_vet in graph.adj_list[vet]: + if adj_vet in visited: + continue # 跳过已被访问过的顶点 + que.append(adj_vet) # 只入队未访问的顶点 + visited.add(adj_vet) # 标记该顶点已被访问 + # 返回顶点遍历序列 + return res ``` === "C++" @@ -88,30 +83,66 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` -=== "Python" +=== "Java" - ```python title="graph_bfs.py" - def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: - """广度优先遍历 BFS""" - # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - # 顶点遍历序列 - res = [] - # 哈希表,用于记录已被访问过的顶点 - visited = set[Vertex]([start_vet]) - # 队列用于实现 BFS - que = deque[Vertex]([start_vet]) - # 以顶点 vet 为起点,循环直至访问完所有顶点 - while len(que) > 0: - vet = que.popleft() # 队首顶点出队 - res.append(vet) # 记录访问顶点 - # 遍历该顶点的所有邻接顶点 - for adj_vet in graph.adj_list[vet]: - if adj_vet in visited: - continue # 跳过已被访问过的顶点 - que.append(adj_vet) # 只入队未访问的顶点 - visited.add(adj_vet) # 标记该顶点已被访问 - # 返回顶点遍历序列 - return res + ```java title="graph_bfs.java" + /* 广度优先遍历 BFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List graphBFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new ArrayList<>(); + // 哈希表,用于记录已被访问过的顶点 + Set visited = new HashSet<>(); + visited.add(startVet); + // 队列用于实现 BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.isEmpty()) { + Vertex vet = que.poll(); // 队首顶点出队 + res.add(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳过已被访问过的顶点 + que.offer(adjVet); // 只入队未访问的顶点 + visited.add(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; + } + ``` + +=== "C#" + + ```csharp title="graph_bfs.cs" + /* 广度优先遍历 BFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List graphBFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new List(); + // 哈希表,用于记录已被访问过的顶点 + HashSet visited = new HashSet() { startVet }; + // 队列用于实现 BFS + Queue que = new Queue(); + que.Enqueue(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // 队首顶点出队 + res.Add(vet); // 记录访问顶点 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳过已被访问过的顶点 + } + que.Enqueue(adjVet); // 只入队未访问的顶点 + visited.Add(adjVet); // 标记该顶点已被访问 + } + } + + // 返回顶点遍历序列 + return res; + } ``` === "Go" @@ -150,6 +181,36 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` +=== "Swift" + + ```swift title="graph_bfs.swift" + /* 广度优先遍历 BFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希表,用于记录已被访问过的顶点 + var visited: Set = [startVet] + // 队列用于实现 BFS + var que: [Vertex] = [startVet] + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while !que.isEmpty { + let vet = que.removeFirst() // 队首顶点出队 + res.append(vet) // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问过的顶点 + } + que.append(adjVet) // 只入队未访问的顶点 + visited.insert(adjVet) // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res + } + ``` + === "JS" ```javascript title="graph_bfs.js" @@ -212,116 +273,6 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` -=== "C" - - ```c title="graph_bfs.c" - /* 广度优先遍历 */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - Vertex **graphBFS(graphAdjList *t, Vertex *startVet) { - // 顶点遍历序列 - Vertex **res = (Vertex **)malloc(sizeof(Vertex *) * t->size); - memset(res, 0, sizeof(Vertex *) * t->size); - // 队列用于实现 BFS - queue *que = newQueue(t->size); - // 哈希表,用于记录已被访问过的顶点 - hashTable *visited = newHash(t->size); - int resIndex = 0; - queuePush(que, startVet); // 将第一个元素入队 - hashMark(visited, startVet->pos); // 标记第一个入队的顶点 - // 以顶点 vet 为起点,循环直至访问完所有顶点 - while (que->head < que->tail) { - // 遍历该顶点的边链表,将所有与该顶点有连接的,并且未被标记的顶点入队 - Node *n = queueTop(que)->linked->head->next; - while (n != 0) { - // 查询哈希表,若该索引的顶点已入队,则跳过,否则入队并标记 - if (hashQuery(visited, n->val->pos) == 1) { - n = n->next; - continue; // 跳过已被访问过的顶点 - } - queuePush(que, n->val); // 只入队未访问的顶点 - hashMark(visited, n->val->pos); // 标记该顶点已被访问 - } - // 队首元素存入数组 - res[resIndex] = queueTop(que); // 队首顶点加入顶点遍历序列 - resIndex++; - queuePop(que); // 队首元素出队 - } - // 释放内存 - freeQueue(que); - freeHash(visited); - resIndex = 0; - // 返回顶点遍历序列 - return res; - } - ``` - -=== "C#" - - ```csharp title="graph_bfs.cs" - /* 广度优先遍历 BFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - List graphBFS(GraphAdjList graph, Vertex startVet) { - // 顶点遍历序列 - List res = new List(); - // 哈希表,用于记录已被访问过的顶点 - HashSet visited = new HashSet() { startVet }; - // 队列用于实现 BFS - Queue que = new Queue(); - que.Enqueue(startVet); - // 以顶点 vet 为起点,循环直至访问完所有顶点 - while (que.Count > 0) { - Vertex vet = que.Dequeue(); // 队首顶点出队 - res.Add(vet); // 记录访问顶点 - foreach (Vertex adjVet in graph.adjList[vet]) { - if (visited.Contains(adjVet)) { - continue; // 跳过已被访问过的顶点 - } - que.Enqueue(adjVet); // 只入队未访问的顶点 - visited.Add(adjVet); // 标记该顶点已被访问 - } - } - - // 返回顶点遍历序列 - return res; - } - ``` - -=== "Swift" - - ```swift title="graph_bfs.swift" - /* 广度优先遍历 BFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { - // 顶点遍历序列 - var res: [Vertex] = [] - // 哈希表,用于记录已被访问过的顶点 - var visited: Set = [startVet] - // 队列用于实现 BFS - var que: [Vertex] = [startVet] - // 以顶点 vet 为起点,循环直至访问完所有顶点 - while !que.isEmpty { - let vet = que.removeFirst() // 队首顶点出队 - res.append(vet) // 记录访问顶点 - // 遍历该顶点的所有邻接顶点 - for adjVet in graph.adjList[vet] ?? [] { - if visited.contains(adjVet) { - continue // 跳过已被访问过的顶点 - } - que.append(adjVet) // 只入队未访问的顶点 - visited.insert(adjVet) // 标记该顶点已被访问 - } - } - // 返回顶点遍历序列 - return res - } - ``` - -=== "Zig" - - ```zig title="graph_bfs.zig" - [class]{}-[func]{graphBFS} - ``` - === "Dart" ```dart title="graph_bfs.dart" @@ -388,6 +339,55 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` +=== "C" + + ```c title="graph_bfs.c" + /* 广度优先遍历 */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + Vertex **graphBFS(graphAdjList *t, Vertex *startVet) { + // 顶点遍历序列 + Vertex **res = (Vertex **)malloc(sizeof(Vertex *) * t->size); + memset(res, 0, sizeof(Vertex *) * t->size); + // 队列用于实现 BFS + queue *que = newQueue(t->size); + // 哈希表,用于记录已被访问过的顶点 + hashTable *visited = newHash(t->size); + int resIndex = 0; + queuePush(que, startVet); // 将第一个元素入队 + hashMark(visited, startVet->pos); // 标记第一个入队的顶点 + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (que->head < que->tail) { + // 遍历该顶点的边链表,将所有与该顶点有连接的,并且未被标记的顶点入队 + Node *n = queueTop(que)->linked->head->next; + while (n != 0) { + // 查询哈希表,若该索引的顶点已入队,则跳过,否则入队并标记 + if (hashQuery(visited, n->val->pos) == 1) { + n = n->next; + continue; // 跳过已被访问过的顶点 + } + queuePush(que, n->val); // 只入队未访问的顶点 + hashMark(visited, n->val->pos); // 标记该顶点已被访问 + } + // 队首元素存入数组 + res[resIndex] = queueTop(que); // 队首顶点加入顶点遍历序列 + resIndex++; + queuePop(que); // 队首元素出队 + } + // 释放内存 + freeQueue(que); + freeHash(visited); + resIndex = 0; + // 返回顶点遍历序列 + return res; + } + ``` + +=== "Zig" + + ```zig title="graph_bfs.zig" + [class]{}-[func]{graphBFS} + ``` + 代码相对抽象,建议对照图 9-10 来加深理解。 === "<1>" @@ -447,32 +447,29 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 这种“走到尽头再返回”的算法范式通常基于递归来实现。与广度优先遍历类似,在深度优先遍历中我们也需要借助一个哈希表 `visited` 来记录已被访问的顶点,以避免重复访问顶点。 -=== "Java" +=== "Python" - ```java title="graph_dfs.java" - /* 深度优先遍历 DFS 辅助函数 */ - void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { - res.add(vet); // 记录访问顶点 - visited.add(vet); // 标记该顶点已被访问 - // 遍历该顶点的所有邻接顶点 - for (Vertex adjVet : graph.adjList.get(vet)) { - if (visited.contains(adjVet)) - continue; // 跳过已被访问过的顶点 - // 递归访问邻接顶点 - dfs(graph, visited, res, adjVet); - } - } + ```python title="graph_dfs.py" + def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """深度优先遍历 DFS 辅助函数""" + res.append(vet) # 记录访问顶点 + visited.add(vet) # 标记该顶点已被访问 + # 遍历该顶点的所有邻接顶点 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + continue # 跳过已被访问过的顶点 + # 递归访问邻接顶点 + dfs(graph, visited, res, adjVet) - /* 深度优先遍历 DFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - List graphDFS(GraphAdjList graph, Vertex startVet) { - // 顶点遍历序列 - List res = new ArrayList<>(); - // 哈希表,用于记录已被访问过的顶点 - Set visited = new HashSet<>(); - dfs(graph, visited, res, startVet); - return res; - } + def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """深度优先遍历 DFS""" + # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + # 顶点遍历序列 + res = [] + # 哈希表,用于记录已被访问过的顶点 + visited = set[Vertex]() + dfs(graph, visited, res, start_vet) + return res ``` === "C++" @@ -503,29 +500,61 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` -=== "Python" +=== "Java" - ```python title="graph_dfs.py" - def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): - """深度优先遍历 DFS 辅助函数""" - res.append(vet) # 记录访问顶点 - visited.add(vet) # 标记该顶点已被访问 - # 遍历该顶点的所有邻接顶点 - for adjVet in graph.adj_list[vet]: - if adjVet in visited: - continue # 跳过已被访问过的顶点 - # 递归访问邻接顶点 - dfs(graph, visited, res, adjVet) + ```java title="graph_dfs.java" + /* 深度优先遍历 DFS 辅助函数 */ + void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // 记录访问顶点 + visited.add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳过已被访问过的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } - def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: - """深度优先遍历 DFS""" - # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - # 顶点遍历序列 - res = [] - # 哈希表,用于记录已被访问过的顶点 - visited = set[Vertex]() - dfs(graph, visited, res, start_vet) - return res + /* 深度优先遍历 DFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List graphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new ArrayList<>(); + // 哈希表,用于记录已被访问过的顶点 + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + ``` + +=== "C#" + + ```csharp title="graph_dfs.cs" + /* 深度优先遍历 DFS 辅助函数 */ + void dfs(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // 记录访问顶点 + visited.Add(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳过已被访问过的顶点 + } + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度优先遍历 DFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + List graphDFS(GraphAdjList graph, Vertex startVet) { + // 顶点遍历序列 + List res = new List(); + // 哈希表,用于记录已被访问过的顶点 + HashSet visited = new HashSet(); + dfs(graph, visited, res, startVet); + return res; + } ``` === "Go" @@ -559,6 +588,35 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` +=== "Swift" + + ```swift title="graph_dfs.swift" + /* 深度优先遍历 DFS 辅助函数 */ + func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 记录访问顶点 + visited.insert(vet) // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳过已被访问过的顶点 + } + // 递归访问邻接顶点 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } + } + + /* 深度优先遍历 DFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 顶点遍历序列 + var res: [Vertex] = [] + // 哈希表,用于记录已被访问过的顶点 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res + } + ``` + === "JS" ```javascript title="graph_dfs.js" @@ -623,111 +681,6 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` -=== "C" - - ```c title="graph_dfs.c" - /* 深度优先遍历 DFS 辅助函数 */ - int resIndex = 0; - void dfs(graphAdjList *graph, hashTable *visited, Vertex *vet, Vertex **res) { - if (hashQuery(visited, vet->pos) == 1) { - return; // 跳过已被访问过的顶点 - } - hashMark(visited, vet->pos); // 标记顶点并将顶点存入数组 - res[resIndex] = vet; // 将顶点存入数组 - resIndex++; - // 遍历该顶点链表 - Node *n = vet->linked->head->next; - while (n != 0) { - // 递归访问邻接顶点 - dfs(graph, visited, n->val, res); - n = n->next; - } - return; - } - - /* 深度优先遍历 DFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - Vertex **graphDFS(graphAdjList *graph, Vertex *startVet) { - // 顶点遍历序列 - Vertex **res = (Vertex **)malloc(sizeof(Vertex *) * graph->size); - memset(res, 0, sizeof(Vertex *) * graph->size); - // 哈希表,用于记录已被访问过的顶点 - hashTable *visited = newHash(graph->size); - dfs(graph, visited, startVet, res); - // 释放哈希表内存并将数组索引归零 - freeHash(visited); - resIndex = 0; - // 返回遍历数组 - return res; - } - ``` - -=== "C#" - - ```csharp title="graph_dfs.cs" - /* 深度优先遍历 DFS 辅助函数 */ - void dfs(GraphAdjList graph, HashSet visited, List res, Vertex vet) { - res.Add(vet); // 记录访问顶点 - visited.Add(vet); // 标记该顶点已被访问 - // 遍历该顶点的所有邻接顶点 - foreach (Vertex adjVet in graph.adjList[vet]) { - if (visited.Contains(adjVet)) { - continue; // 跳过已被访问过的顶点 - } - // 递归访问邻接顶点 - dfs(graph, visited, res, adjVet); - } - } - - /* 深度优先遍历 DFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - List graphDFS(GraphAdjList graph, Vertex startVet) { - // 顶点遍历序列 - List res = new List(); - // 哈希表,用于记录已被访问过的顶点 - HashSet visited = new HashSet(); - dfs(graph, visited, res, startVet); - return res; - } - ``` - -=== "Swift" - - ```swift title="graph_dfs.swift" - /* 深度优先遍历 DFS 辅助函数 */ - func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { - res.append(vet) // 记录访问顶点 - visited.insert(vet) // 标记该顶点已被访问 - // 遍历该顶点的所有邻接顶点 - for adjVet in graph.adjList[vet] ?? [] { - if visited.contains(adjVet) { - continue // 跳过已被访问过的顶点 - } - // 递归访问邻接顶点 - dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) - } - } - - /* 深度优先遍历 DFS */ - // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 - func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { - // 顶点遍历序列 - var res: [Vertex] = [] - // 哈希表,用于记录已被访问过的顶点 - var visited: Set = [] - dfs(graph: graph, visited: &visited, res: &res, vet: startVet) - return res - } - ``` - -=== "Zig" - - ```zig title="graph_dfs.zig" - [class]{}-[func]{dfs} - - [class]{}-[func]{graphDFS} - ``` - === "Dart" ```dart title="graph_dfs.dart" @@ -793,6 +746,53 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这 } ``` +=== "C" + + ```c title="graph_dfs.c" + /* 深度优先遍历 DFS 辅助函数 */ + int resIndex = 0; + void dfs(graphAdjList *graph, hashTable *visited, Vertex *vet, Vertex **res) { + if (hashQuery(visited, vet->pos) == 1) { + return; // 跳过已被访问过的顶点 + } + hashMark(visited, vet->pos); // 标记顶点并将顶点存入数组 + res[resIndex] = vet; // 将顶点存入数组 + resIndex++; + // 遍历该顶点链表 + Node *n = vet->linked->head->next; + while (n != 0) { + // 递归访问邻接顶点 + dfs(graph, visited, n->val, res); + n = n->next; + } + return; + } + + /* 深度优先遍历 DFS */ + // 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 + Vertex **graphDFS(graphAdjList *graph, Vertex *startVet) { + // 顶点遍历序列 + Vertex **res = (Vertex **)malloc(sizeof(Vertex *) * graph->size); + memset(res, 0, sizeof(Vertex *) * graph->size); + // 哈希表,用于记录已被访问过的顶点 + hashTable *visited = newHash(graph->size); + dfs(graph, visited, startVet, res); + // 释放哈希表内存并将数组索引归零 + freeHash(visited); + resIndex = 0; + // 返回遍历数组 + return res; + } + ``` + +=== "Zig" + + ```zig title="graph_dfs.zig" + [class]{}-[func]{dfs} + + [class]{}-[func]{graphDFS} + ``` + 深度优先遍历的算法流程如图 9-12 所示。 - **直虚线代表向下递推**,表示开启了一个新的递归方法来访问新顶点。 diff --git a/chapter_greedy/fractional_knapsack_problem.md b/chapter_greedy/fractional_knapsack_problem.md index e862c793d..5e062e289 100644 --- a/chapter_greedy/fractional_knapsack_problem.md +++ b/chapter_greedy/fractional_knapsack_problem.md @@ -40,45 +40,35 @@ status: new 我们建立了一个物品类 `Item` ,以便将物品按照单位价值进行排序。循环进行贪心选择,当背包已满时跳出并返回解。 -=== "Java" +=== "Python" - ```java title="fractional_knapsack.java" - /* 物品 */ - class Item { - int w; // 物品重量 - int v; // 物品价值 + ```python title="fractional_knapsack.py" + class Item: + """物品""" - public Item(int w, int v) { - this.w = w; - this.v = v; - } - } + def __init__(self, w: int, v: int): + self.w = w # 物品重量 + self.v = v # 物品价值 - /* 分数背包:贪心 */ - double fractionalKnapsack(int[] wgt, int[] val, int cap) { - // 创建物品列表,包含两个属性:重量、价值 - Item[] items = new Item[wgt.length]; - for (int i = 0; i < wgt.length; i++) { - items[i] = new Item(wgt[i], val[i]); - } - // 按照单位价值 item.v / item.w 从高到低进行排序 - Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); - // 循环贪心选择 - double res = 0; - for (Item item : items) { - if (item.w <= cap) { - // 若剩余容量充足,则将当前物品整个装进背包 - res += item.v; - cap -= item.w; - } else { - // 若剩余容量不足,则将当前物品的一部分装进背包 - res += (double) item.v / item.w * cap; - // 已无剩余容量,因此跳出循环 - break; - } - } - return res; - } + def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """分数背包:贪心""" + # 创建物品列表,包含两个属性:重量、价值 + items = [Item(w, v) for w, v in zip(wgt, val)] + # 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort(key=lambda item: item.v / item.w, reverse=True) + # 循环贪心选择 + res = 0 + for item in items: + if item.w <= cap: + # 若剩余容量充足,则将当前物品整个装进背包 + res += item.v + cap -= item.w + else: + # 若剩余容量不足,则将当前物品的一部分装进背包 + res += (item.v / item.w) * cap + # 已无剩余容量,因此跳出循环 + break + return res ``` === "C++" @@ -121,35 +111,86 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="fractional_knapsack.py" - class Item: - """物品""" + ```java title="fractional_knapsack.java" + /* 物品 */ + class Item { + int w; // 物品重量 + int v; // 物品价值 - def __init__(self, w: int, v: int): - self.w = w # 物品重量 - self.v = v # 物品价值 + public Item(int w, int v) { + this.w = w; + this.v = v; + } + } - def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: - """分数背包:贪心""" - # 创建物品列表,包含两个属性:重量、价值 - items = [Item(w, v) for w, v in zip(wgt, val)] - # 按照单位价值 item.v / item.w 从高到低进行排序 - items.sort(key=lambda item: item.v / item.w, reverse=True) - # 循环贪心选择 - res = 0 - for item in items: - if item.w <= cap: - # 若剩余容量充足,则将当前物品整个装进背包 - res += item.v - cap -= item.w - else: - # 若剩余容量不足,则将当前物品的一部分装进背包 - res += (item.v / item.w) * cap - # 已无剩余容量,因此跳出循环 - break - return res + /* 分数背包:贪心 */ + double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + Item[] items = new Item[wgt.length]; + for (int i = 0; i < wgt.length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); + // 循环贪心选择 + double res = 0; + for (Item item : items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (double) item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; + } + ``` + +=== "C#" + + ```csharp title="fractional_knapsack.cs" + /* 物品 */ + class Item { + public int w; // 物品重量 + public int v; // 物品价值 + + public Item(int w, int v) { + this.w = w; + this.v = v; + } + } + + /* 分数背包:贪心 */ + double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + Item[] items = new Item[wgt.Length]; + for (int i = 0; i < wgt.Length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); + // 循环贪心选择 + double res = 0; + foreach (Item item in items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (double)item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; + } ``` === "Go" @@ -190,6 +231,45 @@ status: new } ``` +=== "Swift" + + ```swift title="fractional_knapsack.swift" + /* 物品 */ + class Item { + var w: Int // 物品重量 + var v: Int // 物品价值 + + init(w: Int, v: Int) { + self.w = w + self.v = v + } + } + + /* 分数背包:贪心 */ + func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { + // 创建物品列表,包含两个属性:重量、价值 + var items = zip(wgt, val).map { Item(w: $0, v: $1) } + // 按照单位价值 item.v / item.w 从高到低进行排序 + items.sort(by: { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }) + // 循环贪心选择 + var res = 0.0 + var cap = cap + for item in items { + if item.w <= cap { + // 若剩余容量充足,则将当前物品整个装进背包 + res += Double(item.v) + cap -= item.w + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += Double(item.v) / Double(item.w) * Double(cap) + // 已无剩余容量,因此跳出循环 + break + } + } + return res + } + ``` + === "JS" ```javascript title="fractional_knapsack.js" @@ -263,102 +343,6 @@ status: new } ``` -=== "C" - - ```c title="fractional_knapsack.c" - [class]{Item}-[func]{} - - [class]{}-[func]{fractionalKnapsack} - ``` - -=== "C#" - - ```csharp title="fractional_knapsack.cs" - /* 物品 */ - class Item { - public int w; // 物品重量 - public int v; // 物品价值 - - public Item(int w, int v) { - this.w = w; - this.v = v; - } - } - - /* 分数背包:贪心 */ - double fractionalKnapsack(int[] wgt, int[] val, int cap) { - // 创建物品列表,包含两个属性:重量、价值 - Item[] items = new Item[wgt.Length]; - for (int i = 0; i < wgt.Length; i++) { - items[i] = new Item(wgt[i], val[i]); - } - // 按照单位价值 item.v / item.w 从高到低进行排序 - Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); - // 循环贪心选择 - double res = 0; - foreach (Item item in items) { - if (item.w <= cap) { - // 若剩余容量充足,则将当前物品整个装进背包 - res += item.v; - cap -= item.w; - } else { - // 若剩余容量不足,则将当前物品的一部分装进背包 - res += (double)item.v / item.w * cap; - // 已无剩余容量,因此跳出循环 - break; - } - } - return res; - } - ``` - -=== "Swift" - - ```swift title="fractional_knapsack.swift" - /* 物品 */ - class Item { - var w: Int // 物品重量 - var v: Int // 物品价值 - - init(w: Int, v: Int) { - self.w = w - self.v = v - } - } - - /* 分数背包:贪心 */ - func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { - // 创建物品列表,包含两个属性:重量、价值 - var items = zip(wgt, val).map { Item(w: $0, v: $1) } - // 按照单位价值 item.v / item.w 从高到低进行排序 - items.sort(by: { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }) - // 循环贪心选择 - var res = 0.0 - var cap = cap - for item in items { - if item.w <= cap { - // 若剩余容量充足,则将当前物品整个装进背包 - res += Double(item.v) - cap -= item.w - } else { - // 若剩余容量不足,则将当前物品的一部分装进背包 - res += Double(item.v) / Double(item.w) * Double(cap) - // 已无剩余容量,因此跳出循环 - break - } - } - return res - } - ``` - -=== "Zig" - - ```zig title="fractional_knapsack.zig" - [class]{Item}-[func]{} - - [class]{}-[func]{fractionalKnapsack} - ``` - === "Dart" ```dart title="fractional_knapsack.dart" @@ -441,6 +425,22 @@ status: new } ``` +=== "C" + + ```c title="fractional_knapsack.c" + [class]{Item}-[func]{} + + [class]{}-[func]{fractionalKnapsack} + ``` + +=== "Zig" + + ```zig title="fractional_knapsack.zig" + [class]{Item}-[func]{} + + [class]{}-[func]{fractionalKnapsack} + ``` + 最差情况下,需要遍历整个物品列表,**因此时间复杂度为 $O(n)$** ,其中 $n$ 为物品数量。 由于初始化了一个 `Item` 对象列表,**因此空间复杂度为 $O(n)$** 。 diff --git a/chapter_greedy/greedy_algorithm.md b/chapter_greedy/greedy_algorithm.md index fc1a9f90b..38878f9d6 100644 --- a/chapter_greedy/greedy_algorithm.md +++ b/chapter_greedy/greedy_algorithm.md @@ -26,27 +26,24 @@ status: new 实现代码如下所示。你可能会不由地发出感叹:So Clean !贪心算法仅用十行代码就解决了零钱兑换问题。 -=== "Java" +=== "Python" - ```java title="coin_change_greedy.java" - /* 零钱兑换:贪心 */ - int coinChangeGreedy(int[] coins, int amt) { - // 假设 coins 列表有序 - int i = coins.length - 1; - int count = 0; - // 循环进行贪心选择,直到无剩余金额 - while (amt > 0) { - // 找到小于且最接近剩余金额的硬币 - while (i > 0 && coins[i] > amt) { - i--; - } - // 选择 coins[i] - amt -= coins[i]; - count++; - } - // 若未找到可行方案,则返回 -1 - return amt == 0 ? count : -1; - } + ```python title="coin_change_greedy.py" + def coin_change_greedy(coins: list[int], amt: int) -> int: + """零钱兑换:贪心""" + # 假设 coins 列表有序 + i = len(coins) - 1 + count = 0 + # 循环进行贪心选择,直到无剩余金额 + while amt > 0: + # 找到小于且最接近剩余金额的硬币 + while i > 0 and coins[i] > amt: + i -= 1 + # 选择 coins[i] + amt -= coins[i] + count += 1 + # 若未找到可行方案,则返回 -1 + return count if amt == 0 else -1 ``` === "C++" @@ -72,24 +69,50 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="coin_change_greedy.py" - def coin_change_greedy(coins: list[int], amt: int) -> int: - """零钱兑换:贪心""" - # 假设 coins 列表有序 - i = len(coins) - 1 - count = 0 - # 循环进行贪心选择,直到无剩余金额 - while amt > 0: - # 找到小于且最接近剩余金额的硬币 - while i > 0 and coins[i] > amt: - i -= 1 - # 选择 coins[i] - amt -= coins[i] - count += 1 - # 若未找到可行方案,则返回 -1 - return count if amt == 0 else -1 + ```java title="coin_change_greedy.java" + /* 零钱兑换:贪心 */ + int coinChangeGreedy(int[] coins, int amt) { + // 假设 coins 列表有序 + int i = coins.length - 1; + int count = 0; + // 循环进行贪心选择,直到无剩余金额 + while (amt > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > amt) { + i--; + } + // 选择 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,则返回 -1 + return amt == 0 ? count : -1; + } + ``` + +=== "C#" + + ```csharp title="coin_change_greedy.cs" + /* 零钱兑换:贪心 */ + int coinChangeGreedy(int[] coins, int amt) { + // 假设 coins 列表有序 + int i = coins.Length - 1; + int count = 0; + // 循环进行贪心选择,直到无剩余金额 + while (amt > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > amt) { + i--; + } + // 选择 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,则返回 -1 + return amt == 0 ? count : -1; + } ``` === "Go" @@ -118,6 +141,30 @@ status: new } ``` +=== "Swift" + + ```swift title="coin_change_greedy.swift" + /* 零钱兑换:贪心 */ + func coinChangeGreedy(coins: [Int], amt: Int) -> Int { + // 假设 coins 列表有序 + var i = coins.count - 1 + var count = 0 + var amt = amt + // 循环进行贪心选择,直到无剩余金额 + while amt > 0 { + // 找到小于且最接近剩余金额的硬币 + while i > 0 && coins[i] > amt { + i -= 1 + } + // 选择 coins[i] + amt -= coins[i] + count += 1 + } + // 若未找到可行方案,则返回 -1 + return amt == 0 ? count : -1 + } + ``` + === "JS" ```javascript title="coin_change_greedy.js" @@ -164,65 +211,6 @@ status: new } ``` -=== "C" - - ```c title="coin_change_greedy.c" - [class]{}-[func]{coinChangeGreedy} - ``` - -=== "C#" - - ```csharp title="coin_change_greedy.cs" - /* 零钱兑换:贪心 */ - int coinChangeGreedy(int[] coins, int amt) { - // 假设 coins 列表有序 - int i = coins.Length - 1; - int count = 0; - // 循环进行贪心选择,直到无剩余金额 - while (amt > 0) { - // 找到小于且最接近剩余金额的硬币 - while (i > 0 && coins[i] > amt) { - i--; - } - // 选择 coins[i] - amt -= coins[i]; - count++; - } - // 若未找到可行方案,则返回 -1 - return amt == 0 ? count : -1; - } - ``` - -=== "Swift" - - ```swift title="coin_change_greedy.swift" - /* 零钱兑换:贪心 */ - func coinChangeGreedy(coins: [Int], amt: Int) -> Int { - // 假设 coins 列表有序 - var i = coins.count - 1 - var count = 0 - var amt = amt - // 循环进行贪心选择,直到无剩余金额 - while amt > 0 { - // 找到小于且最接近剩余金额的硬币 - while i > 0 && coins[i] > amt { - i -= 1 - } - // 选择 coins[i] - amt -= coins[i] - count += 1 - } - // 若未找到可行方案,则返回 -1 - return amt == 0 ? count : -1 - } - ``` - -=== "Zig" - - ```zig title="coin_change_greedy.zig" - [class]{}-[func]{coinChangeGreedy} - ``` - === "Dart" ```dart title="coin_change_greedy.dart" @@ -273,6 +261,18 @@ status: new } ``` +=== "C" + + ```c title="coin_change_greedy.c" + [class]{}-[func]{coinChangeGreedy} + ``` + +=== "Zig" + + ```zig title="coin_change_greedy.zig" + [class]{}-[func]{coinChangeGreedy} + ``` + ## 15.1.1   贪心优点与局限性 **贪心算法不仅操作直接、实现简单,而且通常效率也很高**。在以上代码中,记硬币最小面值为 $\min(coins)$ ,则贪心选择最多循环 $amt / \min(coins)$ 次,时间复杂度为 $O(amt / \min(coins))$ 。这比动态规划解法的时间复杂度 $O(n \times amt)$ 提升了一个数量级。 diff --git a/chapter_greedy/max_capacity_problem.md b/chapter_greedy/max_capacity_problem.md index 4ae6bae4c..b25b37b30 100644 --- a/chapter_greedy/max_capacity_problem.md +++ b/chapter_greedy/max_capacity_problem.md @@ -93,29 +93,26 @@ $$ 变量 $i$、$j$、$res$ 使用常数大小额外空间,**因此空间复杂度为 $O(1)$** 。 -=== "Java" +=== "Python" - ```java title="max_capacity.java" - /* 最大容量:贪心 */ - int maxCapacity(int[] ht) { - // 初始化 i, j 分列数组两端 - int i = 0, j = ht.length - 1; - // 初始最大容量为 0 - int res = 0; - // 循环贪心选择,直至两板相遇 - while (i < j) { - // 更新最大容量 - int cap = Math.min(ht[i], ht[j]) * (j - i); - res = Math.max(res, cap); - // 向内移动短板 - if (ht[i] < ht[j]) { - i++; - } else { - j--; - } - } - return res; - } + ```python title="max_capacity.py" + def max_capacity(ht: list[int]) -> int: + """最大容量:贪心""" + # 初始化 i, j 分列数组两端 + i, j = 0, len(ht) - 1 + # 初始最大容量为 0 + res = 0 + # 循环贪心选择,直至两板相遇 + while i < j: + # 更新最大容量 + cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + # 向内移动短板 + if ht[i] < ht[j]: + i += 1 + else: + j -= 1 + return res ``` === "C++" @@ -143,26 +140,54 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="max_capacity.py" - def max_capacity(ht: list[int]) -> int: - """最大容量:贪心""" - # 初始化 i, j 分列数组两端 - i, j = 0, len(ht) - 1 - # 初始最大容量为 0 - res = 0 - # 循环贪心选择,直至两板相遇 - while i < j: - # 更新最大容量 - cap = min(ht[i], ht[j]) * (j - i) - res = max(res, cap) - # 向内移动短板 - if ht[i] < ht[j]: - i += 1 - else: - j -= 1 - return res + ```java title="max_capacity.java" + /* 最大容量:贪心 */ + int maxCapacity(int[] ht) { + // 初始化 i, j 分列数组两端 + int i = 0, j = ht.length - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + ``` + +=== "C#" + + ```csharp title="max_capacity.cs" + /* 最大容量:贪心 */ + int maxCapacity(int[] ht) { + // 初始化 i, j 分列数组两端 + int i = 0, j = ht.Length - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.Min(ht[i], ht[j]) * (j - i); + res = Math.Max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } ``` === "Go" @@ -190,6 +215,31 @@ $$ } ``` +=== "Swift" + + ```swift title="max_capacity.swift" + /* 最大容量:贪心 */ + func maxCapacity(ht: [Int]) -> Int { + // 初始化 i, j 分列数组两端 + var i = 0, j = ht.count - 1 + // 初始最大容量为 0 + var res = 0 + // 循环贪心选择,直至两板相遇 + while i < j { + // 更新最大容量 + let cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 向内移动短板 + if ht[i] < ht[j] { + i += 1 + } else { + j -= 1 + } + } + return res + } + ``` + === "JS" ```javascript title="max_capacity.js" @@ -242,68 +292,6 @@ $$ } ``` -=== "C" - - ```c title="max_capacity.c" - [class]{}-[func]{maxCapacity} - ``` - -=== "C#" - - ```csharp title="max_capacity.cs" - /* 最大容量:贪心 */ - int maxCapacity(int[] ht) { - // 初始化 i, j 分列数组两端 - int i = 0, j = ht.Length - 1; - // 初始最大容量为 0 - int res = 0; - // 循环贪心选择,直至两板相遇 - while (i < j) { - // 更新最大容量 - int cap = Math.Min(ht[i], ht[j]) * (j - i); - res = Math.Max(res, cap); - // 向内移动短板 - if (ht[i] < ht[j]) { - i++; - } else { - j--; - } - } - return res; - } - ``` - -=== "Swift" - - ```swift title="max_capacity.swift" - /* 最大容量:贪心 */ - func maxCapacity(ht: [Int]) -> Int { - // 初始化 i, j 分列数组两端 - var i = 0, j = ht.count - 1 - // 初始最大容量为 0 - var res = 0 - // 循环贪心选择,直至两板相遇 - while i < j { - // 更新最大容量 - let cap = min(ht[i], ht[j]) * (j - i) - res = max(res, cap) - // 向内移动短板 - if ht[i] < ht[j] { - i += 1 - } else { - j -= 1 - } - } - return res - } - ``` - -=== "Zig" - - ```zig title="max_capacity.zig" - [class]{}-[func]{maxCapacity} - ``` - === "Dart" ```dart title="max_capacity.dart" @@ -355,6 +343,18 @@ $$ } ``` +=== "C" + + ```c title="max_capacity.c" + [class]{}-[func]{maxCapacity} + ``` + +=== "Zig" + + ```zig title="max_capacity.zig" + [class]{}-[func]{maxCapacity} + ``` + ### 3.   正确性证明 之所以贪心比穷举更快,是因为每轮的贪心选择都会“跳过”一些状态。 diff --git a/chapter_greedy/max_product_cutting_problem.md b/chapter_greedy/max_product_cutting_problem.md index 11b61ab54..490ae65a0 100644 --- a/chapter_greedy/max_product_cutting_problem.md +++ b/chapter_greedy/max_product_cutting_problem.md @@ -74,29 +74,24 @@ $$ 请注意,对于 $n \leq 3$ 的边界情况,必须拆分出一个 $1$ ,乘积为 $1 \times (n - 1)$ 。 -=== "Java" +=== "Python" - ```java title="max_product_cutting.java" - /* 最大切分乘积:贪心 */ - int maxProductCutting(int n) { - // 当 n <= 3 时,必须切分出一个 1 - if (n <= 3) { - return 1 * (n - 1); - } - // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 - int a = n / 3; - int b = n % 3; - if (b == 1) { - // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 - return (int) Math.pow(3, a - 1) * 2 * 2; - } - if (b == 2) { - // 当余数为 2 时,不做处理 - return (int) Math.pow(3, a) * 2; - } - // 当余数为 0 时,不做处理 - return (int) Math.pow(3, a); - } + ```python title="max_product_cutting.py" + def max_product_cutting(n: int) -> int: + """最大切分乘积:贪心""" + # 当 n <= 3 时,必须切分出一个 1 + if n <= 3: + return 1 * (n - 1) + # 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + a, b = n // 3, n % 3 + if b == 1: + # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return int(math.pow(3, a - 1)) * 2 * 2 + if b == 2: + # 当余数为 2 时,不做处理 + return int(math.pow(3, a)) * 2 + # 当余数为 0 时,不做处理 + return int(math.pow(3, a)) ``` === "C++" @@ -124,24 +119,54 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="max_product_cutting.py" - def max_product_cutting(n: int) -> int: - """最大切分乘积:贪心""" - # 当 n <= 3 时,必须切分出一个 1 - if n <= 3: - return 1 * (n - 1) - # 贪心地切分出 3 ,a 为 3 的个数,b 为余数 - a, b = n // 3, n % 3 - if b == 1: - # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 - return int(math.pow(3, a - 1)) * 2 * 2 - if b == 2: - # 当余数为 2 时,不做处理 - return int(math.pow(3, a)) * 2 - # 当余数为 0 时,不做处理 - return int(math.pow(3, a)) + ```java title="max_product_cutting.java" + /* 最大切分乘积:贪心 */ + int maxProductCutting(int n) { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return (int) Math.pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (int) Math.pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return (int) Math.pow(3, a); + } + ``` + +=== "C#" + + ```csharp title="max_product_cutting.cs" + /* 最大切分乘积:贪心 */ + int maxProductCutting(int n) { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return (int)Math.Pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (int)Math.Pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return (int)Math.Pow(3, a); + } ``` === "Go" @@ -169,6 +194,31 @@ $$ } ``` +=== "Swift" + + ```swift title="max_product_cutting.swift" + /* 最大切分乘积:贪心 */ + func maxProductCutting(n: Int) -> Int { + // 当 n <= 3 时,必须切分出一个 1 + if n <= 3 { + return 1 * (n - 1) + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + let a = n / 3 + let b = n % 3 + if b == 1 { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return pow(3, a - 1) * 2 * 2 + } + if b == 2 { + // 当余数为 2 时,不做处理 + return pow(3, a) * 2 + } + // 当余数为 0 时,不做处理 + return pow(3, a) + } + ``` + === "JS" ```javascript title="max_product_cutting.js" @@ -219,68 +269,6 @@ $$ } ``` -=== "C" - - ```c title="max_product_cutting.c" - [class]{}-[func]{maxProductCutting} - ``` - -=== "C#" - - ```csharp title="max_product_cutting.cs" - /* 最大切分乘积:贪心 */ - int maxProductCutting(int n) { - // 当 n <= 3 时,必须切分出一个 1 - if (n <= 3) { - return 1 * (n - 1); - } - // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 - int a = n / 3; - int b = n % 3; - if (b == 1) { - // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 - return (int)Math.Pow(3, a - 1) * 2 * 2; - } - if (b == 2) { - // 当余数为 2 时,不做处理 - return (int)Math.Pow(3, a) * 2; - } - // 当余数为 0 时,不做处理 - return (int)Math.Pow(3, a); - } - ``` - -=== "Swift" - - ```swift title="max_product_cutting.swift" - /* 最大切分乘积:贪心 */ - func maxProductCutting(n: Int) -> Int { - // 当 n <= 3 时,必须切分出一个 1 - if n <= 3 { - return 1 * (n - 1) - } - // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 - let a = n / 3 - let b = n % 3 - if b == 1 { - // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 - return pow(3, a - 1) * 2 * 2 - } - if b == 2 { - // 当余数为 2 时,不做处理 - return pow(3, a) * 2 - } - // 当余数为 0 时,不做处理 - return pow(3, a) - } - ``` - -=== "Zig" - - ```zig title="max_product_cutting.zig" - [class]{}-[func]{maxProductCutting} - ``` - === "Dart" ```dart title="max_product_cutting.dart" @@ -331,6 +319,18 @@ $$ } ``` +=== "C" + + ```c title="max_product_cutting.c" + [class]{}-[func]{maxProductCutting} + ``` + +=== "Zig" + + ```zig title="max_product_cutting.zig" + [class]{}-[func]{maxProductCutting} + ``` + ![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png)

图 15-16   最大切分乘积的计算方法

diff --git a/chapter_hashing/hash_algorithm.md b/chapter_hashing/hash_algorithm.md index 5aa9f5e82..e60e33317 100644 --- a/chapter_hashing/hash_algorithm.md +++ b/chapter_hashing/hash_algorithm.md @@ -51,48 +51,40 @@ index = hash(key) % capacity - **异或哈希**:将输入数据的每个元素通过异或操作累积到一个哈希值中。 - **旋转哈希**:将每个字符的 ASCII 码累积到一个哈希值中,每次累积之前都会对哈希值进行旋转操作。 -=== "Java" +=== "Python" - ```java title="simple_hash.java" - /* 加法哈希 */ - int addHash(String key) { - long hash = 0; - final int MODULUS = 1000000007; - for (char c : key.toCharArray()) { - hash = (hash + (int) c) % MODULUS; - } - return (int) hash; - } + ```python title="simple_hash.py" + def add_hash(key: str) -> int: + """加法哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus - /* 乘法哈希 */ - int mulHash(String key) { - long hash = 0; - final int MODULUS = 1000000007; - for (char c : key.toCharArray()) { - hash = (31 * hash + (int) c) % MODULUS; - } - return (int) hash; - } + def mul_hash(key: str) -> int: + """乘法哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus - /* 异或哈希 */ - int xorHash(String key) { - int hash = 0; - final int MODULUS = 1000000007; - for (char c : key.toCharArray()) { - hash ^= (int) c; - } - return hash & MODULUS; - } + def xor_hash(key: str) -> int: + """异或哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus - /* 旋转哈希 */ - int rotHash(String key) { - long hash = 0; - final int MODULUS = 1000000007; - for (char c : key.toCharArray()) { - hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; - } - return (int) hash; - } + def rot_hash(key: str) -> int: + """旋转哈希""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus ``` === "C++" @@ -140,40 +132,92 @@ index = hash(key) % capacity } ``` -=== "Python" +=== "Java" - ```python title="simple_hash.py" - def add_hash(key: str) -> int: - """加法哈希""" - hash = 0 - modulus = 1000000007 - for c in key: - hash += ord(c) - return hash % modulus + ```java title="simple_hash.java" + /* 加法哈希 */ + int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } - def mul_hash(key: str) -> int: - """乘法哈希""" - hash = 0 - modulus = 1000000007 - for c in key: - hash = 31 * hash + ord(c) - return hash % modulus + /* 乘法哈希 */ + int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } - def xor_hash(key: str) -> int: - """异或哈希""" - hash = 0 - modulus = 1000000007 - for c in key: - hash ^= ord(c) - return hash % modulus + /* 异或哈希 */ + int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } - def rot_hash(key: str) -> int: - """旋转哈希""" - hash = 0 - modulus = 1000000007 - for c in key: - hash = (hash << 4) ^ (hash >> 28) ^ ord(c) - return hash % modulus + /* 旋转哈希 */ + int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + ``` + +=== "C#" + + ```csharp title="simple_hash.cs" + /* 加法哈希 */ + int addHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } + + /* 乘法哈希 */ + int mulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } + + /* 异或哈希 */ + int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } + + /* 旋转哈希 */ + int rotHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; + } + return (int)hash; + } ``` === "Go" @@ -228,6 +272,58 @@ index = hash(key) % capacity } ``` +=== "Swift" + + ```swift title="simple_hash.swift" + /* 加法哈希 */ + func addHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (hash + Int(scalar.value)) % MODULUS + } + } + return hash + } + + /* 乘法哈希 */ + func mulHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (31 * hash + Int(scalar.value)) % MODULUS + } + } + return hash + } + + /* 异或哈希 */ + func xorHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash ^= Int(scalar.value) + } + } + return hash & MODULUS + } + + /* 旋转哈希 */ + func rotHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS + } + } + return hash + } + ``` + === "JS" ```javascript title="simple_hash.js" @@ -316,126 +412,6 @@ index = hash(key) % capacity } ``` -=== "C" - - ```c title="simple_hash.c" - [class]{}-[func]{addHash} - - [class]{}-[func]{mulHash} - - [class]{}-[func]{xorHash} - - [class]{}-[func]{rotHash} - ``` - -=== "C#" - - ```csharp title="simple_hash.cs" - /* 加法哈希 */ - int addHash(string key) { - long hash = 0; - const int MODULUS = 1000000007; - foreach (char c in key) { - hash = (hash + c) % MODULUS; - } - return (int)hash; - } - - /* 乘法哈希 */ - int mulHash(string key) { - long hash = 0; - const int MODULUS = 1000000007; - foreach (char c in key) { - hash = (31 * hash + c) % MODULUS; - } - return (int)hash; - } - - /* 异或哈希 */ - int xorHash(string key) { - int hash = 0; - const int MODULUS = 1000000007; - foreach (char c in key) { - hash ^= c; - } - return hash & MODULUS; - } - - /* 旋转哈希 */ - int rotHash(string key) { - long hash = 0; - const int MODULUS = 1000000007; - foreach (char c in key) { - hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; - } - return (int)hash; - } - ``` - -=== "Swift" - - ```swift title="simple_hash.swift" - /* 加法哈希 */ - func addHash(key: String) -> Int { - var hash = 0 - let MODULUS = 1_000_000_007 - for c in key { - for scalar in c.unicodeScalars { - hash = (hash + Int(scalar.value)) % MODULUS - } - } - return hash - } - - /* 乘法哈希 */ - func mulHash(key: String) -> Int { - var hash = 0 - let MODULUS = 1_000_000_007 - for c in key { - for scalar in c.unicodeScalars { - hash = (31 * hash + Int(scalar.value)) % MODULUS - } - } - return hash - } - - /* 异或哈希 */ - func xorHash(key: String) -> Int { - var hash = 0 - let MODULUS = 1_000_000_007 - for c in key { - for scalar in c.unicodeScalars { - hash ^= Int(scalar.value) - } - } - return hash & MODULUS - } - - /* 旋转哈希 */ - func rotHash(key: String) -> Int { - var hash = 0 - let MODULUS = 1_000_000_007 - for c in key { - for scalar in c.unicodeScalars { - hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS - } - } - return hash - } - ``` - -=== "Zig" - - ```zig title="simple_hash.zig" - [class]{}-[func]{addHash} - - [class]{}-[func]{mulHash} - - [class]{}-[func]{xorHash} - - [class]{}-[func]{rotHash} - ``` - === "Dart" ```dart title="simple_hash.dart" @@ -492,6 +468,30 @@ index = hash(key) % capacity [class]{}-[func]{rot_hash} ``` +=== "C" + + ```c title="simple_hash.c" + [class]{}-[func]{addHash} + + [class]{}-[func]{mulHash} + + [class]{}-[func]{xorHash} + + [class]{}-[func]{rotHash} + ``` + +=== "Zig" + + ```zig title="simple_hash.zig" + [class]{}-[func]{addHash} + + [class]{}-[func]{mulHash} + + [class]{}-[func]{xorHash} + + [class]{}-[func]{rotHash} + ``` + 观察发现,每种哈希算法的最后一步都是对大质数 $1000000007$ 取模,以确保哈希值在合适的范围内。值得思考的是,为什么要强调对质数取模,或者说对合数取模的弊端是什么?这是一个有趣的问题。 先抛出结论:**当我们使用大质数作为模数时,可以最大化地保证哈希值的均匀分布**。因为质数不会与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。 @@ -559,57 +559,6 @@ $$ 请注意,不同编程语言的内置哈希值计算函数的定义和方法不同。 -=== "Java" - - ```java title="built_in_hash.java" - int num = 3; - int hashNum = Integer.hashCode(num); - // 整数 3 的哈希值为 3 - - boolean bol = true; - int hashBol = Boolean.hashCode(bol); - // 布尔量 true 的哈希值为 1231 - - double dec = 3.14159; - int hashDec = Double.hashCode(dec); - // 小数 3.14159 的哈希值为 -1340954729 - - String str = "Hello 算法"; - int hashStr = str.hashCode(); - // 字符串 Hello 算法 的哈希值为 -727081396 - - Object[] arr = { 12836, "小哈" }; - int hashTup = Arrays.hashCode(arr); - // 数组 [12836, 小哈] 的哈希值为 1151158 - - ListNode obj = new ListNode(0); - int hashObj = obj.hashCode(); - // 节点对象 utils.ListNode@7dc5e7b4 的哈希值为 2110121908 - ``` - -=== "C++" - - ```cpp title="built_in_hash.cpp" - int num = 3; - size_t hashNum = hash()(num); - // 整数 3 的哈希值为 3 - - bool bol = true; - size_t hashBol = hash()(bol); - // 布尔量 1 的哈希值为 1 - - double dec = 3.14159; - size_t hashDec = hash()(dec); - // 小数 3.14159 的哈希值为 4614256650576692846 - - string str = "Hello 算法"; - size_t hashStr = hash()(str); - // 字符串 Hello 算法 的哈希值为 15466937326284535026 - - // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 - // 数组、对象的哈希值计算需要自行实现 - ``` - === "Python" ```python title="built_in_hash.py" @@ -638,28 +587,55 @@ $$ # 节点对象 的哈希值为 274267521 ``` -=== "Go" +=== "C++" - ```go title="built_in_hash.go" + ```cpp title="built_in_hash.cpp" + int num = 3; + size_t hashNum = hash()(num); + // 整数 3 的哈希值为 3 + bool bol = true; + size_t hashBol = hash()(bol); + // 布尔量 1 的哈希值为 1 + + double dec = 3.14159; + size_t hashDec = hash()(dec); + // 小数 3.14159 的哈希值为 4614256650576692846 + + string str = "Hello 算法"; + size_t hashStr = hash()(str); + // 字符串 Hello 算法 的哈希值为 15466937326284535026 + + // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 + // 数组、对象的哈希值计算需要自行实现 ``` -=== "JS" +=== "Java" - ```javascript title="built_in_hash.js" - // JavaScript 未提供内置 hash code 函数 - ``` + ```java title="built_in_hash.java" + int num = 3; + int hashNum = Integer.hashCode(num); + // 整数 3 的哈希值为 3 -=== "TS" + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + // 布尔量 true 的哈希值为 1231 - ```typescript title="built_in_hash.ts" - // TypeScript 未提供内置 hash code 函数 - ``` + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + // 小数 3.14159 的哈希值为 -1340954729 -=== "C" + String str = "Hello 算法"; + int hashStr = str.hashCode(); + // 字符串 Hello 算法 的哈希值为 -727081396 - ```c title="built_in_hash.c" + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + // 数组 [12836, 小哈] 的哈希值为 1151158 + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + // 节点对象 utils.ListNode@7dc5e7b4 的哈希值为 2110121908 ``` === "C#" @@ -690,6 +666,12 @@ $$ // 节点对象 0 的哈希值为 39053774; ``` +=== "Go" + + ```go title="built_in_hash.go" + + ``` + === "Swift" ```swift title="built_in_hash.swift" @@ -718,10 +700,16 @@ $$ // 节点对象 utils.ListNode 的哈希值为 -2434780518035996159 ``` -=== "Zig" +=== "JS" - ```zig title="built_in_hash.zig" + ```javascript title="built_in_hash.js" + // JavaScript 未提供内置 hash code 函数 + ``` +=== "TS" + + ```typescript title="built_in_hash.ts" + // TypeScript 未提供内置 hash code 函数 ``` === "Dart" @@ -758,6 +746,18 @@ $$ ``` +=== "C" + + ```c title="built_in_hash.c" + + ``` + +=== "Zig" + + ```zig title="built_in_hash.zig" + + ``` + 在许多编程语言中,**只有不可变对象才可作为哈希表的 `key`** 。假如我们将列表(动态数组)作为 `key` ,当列表的内容发生变化时,它的哈希值也随之改变,我们就无法在哈希表中查询到原先的 `value` 了。 虽然自定义对象(比如链表节点)的成员变量是可变的,但它是可哈希的。**这是因为对象的哈希值通常是基于内存地址生成的**,即使对象的内容发生了变化,但它的内存地址不变,哈希值仍然是不变的。 diff --git a/chapter_hashing/hash_collision.md b/chapter_hashing/hash_collision.md index 9c9630b37..8d3ae42f0 100644 --- a/chapter_hashing/hash_collision.md +++ b/chapter_hashing/hash_collision.md @@ -37,118 +37,87 @@ comments: true - 使用列表(动态数组)代替链表,从而简化代码。在这种设定下,哈希表(数组)包含多个桶,每个桶都是一个列表。 - 以下实现包含哈希表扩容方法。当负载因子超过 $0.75$ 时,我们将哈希表扩容至 $2$ 倍。 -=== "Java" +=== "Python" - ```java title="hash_map_chaining.java" - /* 链式地址哈希表 */ - class HashMapChaining { - int size; // 键值对数量 - int capacity; // 哈希表容量 - double loadThres; // 触发扩容的负载因子阈值 - int extendRatio; // 扩容倍数 - List> buckets; // 桶数组 + ```python title="hash_map_chaining.py" + class HashMapChaining: + """链式地址哈希表""" - /* 构造方法 */ - public HashMapChaining() { - size = 0; - capacity = 4; - loadThres = 2 / 3.0; - extendRatio = 2; - buckets = new ArrayList<>(capacity); - for (int i = 0; i < capacity; i++) { - buckets.add(new ArrayList<>()); - } - } + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets = [[] for _ in range(self.capacity)] # 桶数组 - /* 哈希函数 */ - int hashFunc(int key) { - return key % capacity; - } + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity - /* 负载因子 */ - double loadFactor() { - return (double) size / capacity; - } + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity - /* 查询操作 */ - String get(int key) { - int index = hashFunc(key); - List bucket = buckets.get(index); - // 遍历桶,若找到 key 则返回对应 val - for (Pair pair : bucket) { - if (pair.key == key) { - return pair.val; - } - } - // 若未找到 key 则返回 null - return null; - } + def get(self, key: int) -> str: + """查询操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若找到 key 则返回对应 val + for pair in bucket: + if pair.key == key: + return pair.val + # 若未找到 key 则返回 None + return None - /* 添加操作 */ - void put(int key, String val) { - // 当负载因子超过阈值时,执行扩容 - if (loadFactor() > loadThres) { - extend(); - } - int index = hashFunc(key); - List bucket = buckets.get(index); - // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 - for (Pair pair : bucket) { - if (pair.key == key) { - pair.val = val; - return; - } - } - // 若无该 key ,则将键值对添加至尾部 - Pair pair = new Pair(key, val); - bucket.add(pair); - size++; - } + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket: + if pair.key == key: + pair.val = val + return + # 若无该 key ,则将键值对添加至尾部 + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 - /* 删除操作 */ - void remove(int key) { - int index = hashFunc(key); - List bucket = buckets.get(index); - // 遍历桶,从中删除键值对 - for (Pair pair : bucket) { - if (pair.key == key) { - bucket.remove(pair); - size--; - break; - } - } - } + def remove(self, key: int): + """删除操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # 遍历桶,从中删除键值对 + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break - /* 扩容哈希表 */ - void extend() { - // 暂存原哈希表 - List> bucketsTmp = buckets; - // 初始化扩容后的新哈希表 - capacity *= extendRatio; - buckets = new ArrayList<>(capacity); - for (int i = 0; i < capacity; i++) { - buckets.add(new ArrayList<>()); - } - size = 0; - // 将键值对从原哈希表搬运至新哈希表 - for (List bucket : bucketsTmp) { - for (Pair pair : bucket) { - put(pair.key, pair.val); - } - } - } + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [[] for _ in range(self.capacity)] + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for bucket in buckets: + for pair in bucket: + self.put(pair.key, pair.val) - /* 打印哈希表 */ - void print() { - for (List bucket : buckets) { - List res = new ArrayList<>(); - for (Pair pair : bucket) { - res.add(pair.key + " -> " + pair.val); - } - System.out.println(res); - } - } - } + def print(self): + """打印哈希表""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) ``` === "C++" @@ -269,87 +238,230 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="hash_map_chaining.py" - class HashMapChaining: - """链式地址哈希表""" + ```java title="hash_map_chaining.java" + /* 链式地址哈希表 */ + class HashMapChaining { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + List> buckets; // 桶数组 - def __init__(self): - """构造方法""" - self.size = 0 # 键值对数量 - self.capacity = 4 # 哈希表容量 - self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 - self.extend_ratio = 2 # 扩容倍数 - self.buckets = [[] for _ in range(self.capacity)] # 桶数组 + /* 构造方法 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } - def hash_func(self, key: int) -> int: - """哈希函数""" - return key % self.capacity + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } - def load_factor(self) -> float: - """负载因子""" - return self.size / self.capacity + /* 负载因子 */ + double loadFactor() { + return (double) size / capacity; + } - def get(self, key: int) -> str: - """查询操作""" - index = self.hash_func(key) - bucket = self.buckets[index] - # 遍历桶,若找到 key 则返回对应 val - for pair in bucket: - if pair.key == key: - return pair.val - # 若未找到 key 则返回 None - return None + /* 查询操作 */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,若找到 key 则返回对应 val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key 则返回 null + return null; + } - def put(self, key: int, val: str): - """添加操作""" - # 当负载因子超过阈值时,执行扩容 - if self.load_factor() > self.load_thres: - self.extend() - index = self.hash_func(key) - bucket = self.buckets[index] - # 遍历桶,若遇到指定 key ,则更新对应 val 并返回 - for pair in bucket: - if pair.key == key: - pair.val = val - return - # 若无该 key ,则将键值对添加至尾部 - pair = Pair(key, val) - bucket.append(pair) - self.size += 1 + /* 添加操作 */ + void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } - def remove(self, key: int): - """删除操作""" - index = self.hash_func(key) - bucket = self.buckets[index] - # 遍历桶,从中删除键值对 - for pair in bucket: - if pair.key == key: - bucket.remove(pair) - self.size -= 1 - break + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 遍历桶,从中删除键值对 + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } - def extend(self): - """扩容哈希表""" - # 暂存原哈希表 - buckets = self.buckets - # 初始化扩容后的新哈希表 - self.capacity *= self.extend_ratio - self.buckets = [[] for _ in range(self.capacity)] - self.size = 0 - # 将键值对从原哈希表搬运至新哈希表 - for bucket in buckets: - for pair in bucket: - self.put(pair.key, pair.val) + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + List> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } - def print(self): - """打印哈希表""" - for bucket in self.buckets: - res = [] - for pair in bucket: - res.append(str(pair.key) + " -> " + pair.val) - print(res) + /* 打印哈希表 */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } + } + ``` + +=== "C#" + + ```csharp title="hash_map_chaining.cs" + /* 链式地址哈希表 */ + class HashMapChaining { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + List> buckets; // 桶数组 + + /* 构造方法 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2 / 3.0; + extendRatio = 2; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add(new List()); + } + } + + /* 哈希函数 */ + private int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + private double loadFactor() { + return (double)size / capacity; + } + + /* 查询操作 */ + public string get(int key) { + int index = hashFunc(key); + // 遍历桶,若找到 key 则返回对应 val + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key 则返回 null + return null; + } + + /* 添加操作 */ + public void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + buckets[index].Add(new Pair(key, val)); + size++; + } + + /* 删除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 遍历桶,从中删除键值对 + foreach (Pair pair in buckets[index].ToList()) { + if (pair.key == key) { + buckets[index].Remove(pair); + size--; + break; + } + } + } + + /* 扩容哈希表 */ + private void extend() { + // 暂存原哈希表 + List> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add(new List()); + } + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + foreach (List bucket in bucketsTmp) { + foreach (Pair pair in bucket) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + public void print() { + foreach (List bucket in buckets) { + List res = new List(); + foreach (Pair pair in bucket) { + res.Add(pair.key + " -> " + pair.val); + } + foreach (string kv in res) { + Console.WriteLine(kv); + } + } + } + } ``` === "Go" @@ -479,6 +591,110 @@ comments: true } ``` +=== "Swift" + + ```swift title="hash_map_chaining.swift" + /* 链式地址哈希表 */ + class HashMapChaining { + var size: Int // 键值对数量 + var capacity: Int // 哈希表容量 + var loadThres: Double // 触发扩容的负载因子阈值 + var extendRatio: Int // 扩容倍数 + var buckets: [[Pair]] // 桶数组 + + /* 构造方法 */ + init() { + size = 0 + capacity = 4 + loadThres = 2 / 3 + extendRatio = 2 + buckets = Array(repeating: [], count: capacity) + } + + /* 哈希函数 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 负载因子 */ + func loadFactor() -> Double { + Double(size / capacity) + } + + /* 查询操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 遍历桶,若找到 key 则返回对应 val + for pair in bucket { + if pair.key == key { + return pair.val + } + } + // 若未找到 key 则返回 nil + return nil + } + + /* 添加操作 */ + func put(key: Int, val: String) { + // 当负载因子超过阈值时,执行扩容 + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + let bucket = buckets[index] + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for pair in bucket { + if pair.key == key { + pair.val = val + return + } + } + // 若无该 key ,则将键值对添加至尾部 + let pair = Pair(key: key, val: val) + buckets[index].append(pair) + size += 1 + } + + /* 删除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 遍历桶,从中删除键值对 + for (pairIndex, pair) in bucket.enumerated() { + if pair.key == key { + buckets[index].remove(at: pairIndex) + } + } + size -= 1 + } + + /* 扩容哈希表 */ + func extend() { + // 暂存原哈希表 + let bucketsTmp = buckets + // 初始化扩容后的新哈希表 + capacity *= extendRatio + buckets = Array(repeating: [], count: capacity) + size = 0 + // 将键值对从原哈希表搬运至新哈希表 + for bucket in bucketsTmp { + for pair in bucket { + put(key: pair.key, val: pair.val) + } + } + } + + /* 打印哈希表 */ + func print() { + for bucket in buckets { + let res = bucket.map { "\($0.key) -> \($0.val)" } + Swift.print(res) + } + } + } + ``` + === "JS" ```javascript title="hash_map_chaining.js" @@ -695,234 +911,6 @@ comments: true } ``` -=== "C" - - ```c title="hash_map_chaining.c" - [class]{hashMapChaining}-[func]{} - ``` - -=== "C#" - - ```csharp title="hash_map_chaining.cs" - /* 链式地址哈希表 */ - class HashMapChaining { - int size; // 键值对数量 - int capacity; // 哈希表容量 - double loadThres; // 触发扩容的负载因子阈值 - int extendRatio; // 扩容倍数 - List> buckets; // 桶数组 - - /* 构造方法 */ - public HashMapChaining() { - size = 0; - capacity = 4; - loadThres = 2 / 3.0; - extendRatio = 2; - buckets = new List>(capacity); - for (int i = 0; i < capacity; i++) { - buckets.Add(new List()); - } - } - - /* 哈希函数 */ - private int hashFunc(int key) { - return key % capacity; - } - - /* 负载因子 */ - private double loadFactor() { - return (double)size / capacity; - } - - /* 查询操作 */ - public string get(int key) { - int index = hashFunc(key); - // 遍历桶,若找到 key 则返回对应 val - foreach (Pair pair in buckets[index]) { - if (pair.key == key) { - return pair.val; - } - } - // 若未找到 key 则返回 null - return null; - } - - /* 添加操作 */ - public void put(int key, string val) { - // 当负载因子超过阈值时,执行扩容 - if (loadFactor() > loadThres) { - extend(); - } - int index = hashFunc(key); - // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 - foreach (Pair pair in buckets[index]) { - if (pair.key == key) { - pair.val = val; - return; - } - } - // 若无该 key ,则将键值对添加至尾部 - buckets[index].Add(new Pair(key, val)); - size++; - } - - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 遍历桶,从中删除键值对 - foreach (Pair pair in buckets[index].ToList()) { - if (pair.key == key) { - buckets[index].Remove(pair); - size--; - break; - } - } - } - - /* 扩容哈希表 */ - private void extend() { - // 暂存原哈希表 - List> bucketsTmp = buckets; - // 初始化扩容后的新哈希表 - capacity *= extendRatio; - buckets = new List>(capacity); - for (int i = 0; i < capacity; i++) { - buckets.Add(new List()); - } - size = 0; - // 将键值对从原哈希表搬运至新哈希表 - foreach (List bucket in bucketsTmp) { - foreach (Pair pair in bucket) { - put(pair.key, pair.val); - } - } - } - - /* 打印哈希表 */ - public void print() { - foreach (List bucket in buckets) { - List res = new List(); - foreach (Pair pair in bucket) { - res.Add(pair.key + " -> " + pair.val); - } - foreach (string kv in res) { - Console.WriteLine(kv); - } - } - } - } - ``` - -=== "Swift" - - ```swift title="hash_map_chaining.swift" - /* 链式地址哈希表 */ - class HashMapChaining { - var size: Int // 键值对数量 - var capacity: Int // 哈希表容量 - var loadThres: Double // 触发扩容的负载因子阈值 - var extendRatio: Int // 扩容倍数 - var buckets: [[Pair]] // 桶数组 - - /* 构造方法 */ - init() { - size = 0 - capacity = 4 - loadThres = 2 / 3 - extendRatio = 2 - buckets = Array(repeating: [], count: capacity) - } - - /* 哈希函数 */ - func hashFunc(key: Int) -> Int { - key % capacity - } - - /* 负载因子 */ - func loadFactor() -> Double { - Double(size / capacity) - } - - /* 查询操作 */ - func get(key: Int) -> String? { - let index = hashFunc(key: key) - let bucket = buckets[index] - // 遍历桶,若找到 key 则返回对应 val - for pair in bucket { - if pair.key == key { - return pair.val - } - } - // 若未找到 key 则返回 nil - return nil - } - - /* 添加操作 */ - func put(key: Int, val: String) { - // 当负载因子超过阈值时,执行扩容 - if loadFactor() > loadThres { - extend() - } - let index = hashFunc(key: key) - let bucket = buckets[index] - // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 - for pair in bucket { - if pair.key == key { - pair.val = val - return - } - } - // 若无该 key ,则将键值对添加至尾部 - let pair = Pair(key: key, val: val) - buckets[index].append(pair) - size += 1 - } - - /* 删除操作 */ - func remove(key: Int) { - let index = hashFunc(key: key) - let bucket = buckets[index] - // 遍历桶,从中删除键值对 - for (pairIndex, pair) in bucket.enumerated() { - if pair.key == key { - buckets[index].remove(at: pairIndex) - } - } - size -= 1 - } - - /* 扩容哈希表 */ - func extend() { - // 暂存原哈希表 - let bucketsTmp = buckets - // 初始化扩容后的新哈希表 - capacity *= extendRatio - buckets = Array(repeating: [], count: capacity) - size = 0 - // 将键值对从原哈希表搬运至新哈希表 - for bucket in bucketsTmp { - for pair in bucket { - put(key: pair.key, val: pair.val) - } - } - } - - /* 打印哈希表 */ - func print() { - for bucket in buckets { - let res = bucket.map { "\($0.key) -> \($0.val)" } - Swift.print(res) - } - } - } - ``` - -=== "Zig" - - ```zig title="hash_map_chaining.zig" - [class]{HashMapChaining}-[func]{} - ``` - === "Dart" ```dart title="hash_map_chaining.dart" @@ -1158,6 +1146,18 @@ comments: true } ``` +=== "C" + + ```c title="hash_map_chaining.c" + [class]{hashMapChaining}-[func]{} + ``` + +=== "Zig" + + ```zig title="hash_map_chaining.zig" + [class]{HashMapChaining}-[func]{} + ``` + 值得注意的是,当链表很长时,查询效率 $O(n)$ 很差。**此时可以将链表转换为“AVL 树”或“红黑树”**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。 ## 6.2.2   开放寻址 @@ -1187,127 +1187,99 @@ comments: true - 我们使用一个固定的键值对实例 `removed` 来标记已删除元素。也就是说,当一个桶内的元素为 $\text{None}$ 或 `removed` 时,说明这个桶是空的,可用于放置键值对。 - 在线性探测时,我们从当前索引 `index` 向后遍历;而当越过数组尾部时,需要回到头部继续遍历。 -=== "Java" +=== "Python" - ```java title="hash_map_open_addressing.java" - /* 开放寻址哈希表 */ - class HashMapOpenAddressing { - private int size; // 键值对数量 - private int capacity; // 哈希表容量 - private double loadThres; // 触发扩容的负载因子阈值 - private int extendRatio; // 扩容倍数 - private Pair[] buckets; // 桶数组 - private Pair removed; // 删除标记 + ```python title="hash_map_open_addressing.py" + class HashMapOpenAddressing: + """开放寻址哈希表""" - /* 构造方法 */ - public HashMapOpenAddressing() { - size = 0; - capacity = 4; - loadThres = 2.0 / 3.0; - extendRatio = 2; - buckets = new Pair[capacity]; - removed = new Pair(-1, "-1"); - } + def __init__(self): + """构造方法""" + self.size = 0 # 键值对数量 + self.capacity = 4 # 哈希表容量 + self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 + self.extend_ratio = 2 # 扩容倍数 + self.buckets: list[Pair | None] = [None] * self.capacity # 桶数组 + self.removed = Pair(-1, "-1") # 删除标记 - /* 哈希函数 */ - public int hashFunc(int key) { - return key % capacity; - } + def hash_func(self, key: int) -> int: + """哈希函数""" + return key % self.capacity - /* 负载因子 */ - public double loadFactor() { - return (double) size / capacity; - } + def load_factor(self) -> float: + """负载因子""" + return self.size / self.capacity - /* 查询操作 */ - public String get(int key) { - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶,说明无此 key ,则返回 null - if (buckets[j] == null) - return null; - // 若遇到指定 key ,则返回对应 val - if (buckets[j].key == key && buckets[j] != removed) - return buckets[j].val; - } - return null; - } + def get(self, key: int) -> str: + """查询操作""" + index = self.hash_func(key) + # 线性探测,从 index 开始向后遍历 + for i in range(self.capacity): + # 计算桶索引,越过尾部返回头部 + j = (index + i) % self.capacity + # 若遇到空桶,说明无此 key ,则返回 None + if self.buckets[j] is None: + return None + # 若遇到指定 key ,则返回对应 val + if self.buckets[j].key == key and self.buckets[j] != self.removed: + return self.buckets[j].val - /* 添加操作 */ - public void put(int key, String val) { - // 当负载因子超过阈值时,执行扩容 - if (loadFactor() > loadThres) { - extend(); - } - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if (buckets[j] == null || buckets[j] == removed) { - buckets[j] = new Pair(key, val); - size += 1; - return; - } - // 若遇到指定 key ,则更新对应 val - if (buckets[j].key == key) { - buckets[j].val = val; - return; - } - } - } + def put(self, key: int, val: str): + """添加操作""" + # 当负载因子超过阈值时,执行扩容 + if self.load_factor() > self.load_thres: + self.extend() + index = self.hash_func(key) + # 线性探测,从 index 开始向后遍历 + for i in range(self.capacity): + # 计算桶索引,越过尾部返回头部 + j = (index + i) % self.capacity + # 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if self.buckets[j] in [None, self.removed]: + self.buckets[j] = Pair(key, val) + self.size += 1 + return + # 若遇到指定 key ,则更新对应 val + if self.buckets[j].key == key: + self.buckets[j].val = val + return - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶,说明无此 key ,则直接返回 - if (buckets[j] == null) { - return; - } - // 若遇到指定 key ,则标记删除并返回 - if (buckets[j].key == key) { - buckets[j] = removed; - size -= 1; - return; - } - } - } + def remove(self, key: int): + """删除操作""" + index = self.hash_func(key) + # 线性探测,从 index 开始向后遍历 + for i in range(self.capacity): + # 计算桶索引,越过尾部返回头部 + j = (index + i) % self.capacity + # 若遇到空桶,说明无此 key ,则直接返回 + if self.buckets[j] is None: + return + # 若遇到指定 key ,则标记删除并返回 + if self.buckets[j].key == key: + self.buckets[j] = self.removed + self.size -= 1 + return - /* 扩容哈希表 */ - public void extend() { - // 暂存原哈希表 - Pair[] bucketsTmp = buckets; - // 初始化扩容后的新哈希表 - capacity *= extendRatio; - buckets = new Pair[capacity]; - size = 0; - // 将键值对从原哈希表搬运至新哈希表 - for (Pair pair : bucketsTmp) { - if (pair != null && pair != removed) { - put(pair.key, pair.val); - } - } - } + def extend(self): + """扩容哈希表""" + # 暂存原哈希表 + buckets_tmp = self.buckets + # 初始化扩容后的新哈希表 + self.capacity *= self.extend_ratio + self.buckets = [None] * self.capacity + self.size = 0 + # 将键值对从原哈希表搬运至新哈希表 + for pair in buckets_tmp: + if pair not in [None, self.removed]: + self.put(pair.key, pair.val) - /* 打印哈希表 */ - public void print() { - for (Pair pair : buckets) { - if (pair != null) { - System.out.println(pair.key + " -> " + pair.val); - } else { - System.out.println("null"); - } - } - } - } + def print(self): + """打印哈希表""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + else: + print("None") ``` === "C++" @@ -1435,99 +1407,250 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="hash_map_open_addressing.py" - class HashMapOpenAddressing: - """开放寻址哈希表""" + ```java title="hash_map_open_addressing.java" + /* 开放寻址哈希表 */ + class HashMapOpenAddressing { + private int size; // 键值对数量 + private int capacity; // 哈希表容量 + private double loadThres; // 触发扩容的负载因子阈值 + private int extendRatio; // 扩容倍数 + private Pair[] buckets; // 桶数组 + private Pair removed; // 删除标记 - def __init__(self): - """构造方法""" - self.size = 0 # 键值对数量 - self.capacity = 4 # 哈希表容量 - self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 - self.extend_ratio = 2 # 扩容倍数 - self.buckets: list[Pair | None] = [None] * self.capacity # 桶数组 - self.removed = Pair(-1, "-1") # 删除标记 + /* 构造方法 */ + public HashMapOpenAddressing() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new Pair[capacity]; + removed = new Pair(-1, "-1"); + } - def hash_func(self, key: int) -> int: - """哈希函数""" - return key % self.capacity + /* 哈希函数 */ + public int hashFunc(int key) { + return key % capacity; + } - def load_factor(self) -> float: - """负载因子""" - return self.size / self.capacity + /* 负载因子 */ + public double loadFactor() { + return (double) size / capacity; + } - def get(self, key: int) -> str: - """查询操作""" - index = self.hash_func(key) - # 线性探测,从 index 开始向后遍历 - for i in range(self.capacity): - # 计算桶索引,越过尾部返回头部 - j = (index + i) % self.capacity - # 若遇到空桶,说明无此 key ,则返回 None - if self.buckets[j] is None: - return None - # 若遇到指定 key ,则返回对应 val - if self.buckets[j].key == key and self.buckets[j] != self.removed: - return self.buckets[j].val + /* 查询操作 */ + public String get(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则返回 null + if (buckets[j] == null) + return null; + // 若遇到指定 key ,则返回对应 val + if (buckets[j].key == key && buckets[j] != removed) + return buckets[j].val; + } + return null; + } - def put(self, key: int, val: str): - """添加操作""" - # 当负载因子超过阈值时,执行扩容 - if self.load_factor() > self.load_thres: - self.extend() - index = self.hash_func(key) - # 线性探测,从 index 开始向后遍历 - for i in range(self.capacity): - # 计算桶索引,越过尾部返回头部 - j = (index + i) % self.capacity - # 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if self.buckets[j] in [None, self.removed]: - self.buckets[j] = Pair(key, val) - self.size += 1 - return - # 若遇到指定 key ,则更新对应 val - if self.buckets[j].key == key: - self.buckets[j].val = val - return + /* 添加操作 */ + public void put(int key, String val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if (buckets[j] == null || buckets[j] == removed) { + buckets[j] = new Pair(key, val); + size += 1; + return; + } + // 若遇到指定 key ,则更新对应 val + if (buckets[j].key == key) { + buckets[j].val = val; + return; + } + } + } - def remove(self, key: int): - """删除操作""" - index = self.hash_func(key) - # 线性探测,从 index 开始向后遍历 - for i in range(self.capacity): - # 计算桶索引,越过尾部返回头部 - j = (index + i) % self.capacity - # 若遇到空桶,说明无此 key ,则直接返回 - if self.buckets[j] is None: - return - # 若遇到指定 key ,则标记删除并返回 - if self.buckets[j].key == key: - self.buckets[j] = self.removed - self.size -= 1 - return + /* 删除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则直接返回 + if (buckets[j] == null) { + return; + } + // 若遇到指定 key ,则标记删除并返回 + if (buckets[j].key == key) { + buckets[j] = removed; + size -= 1; + return; + } + } + } - def extend(self): - """扩容哈希表""" - # 暂存原哈希表 - buckets_tmp = self.buckets - # 初始化扩容后的新哈希表 - self.capacity *= self.extend_ratio - self.buckets = [None] * self.capacity - self.size = 0 - # 将键值对从原哈希表搬运至新哈希表 - for pair in buckets_tmp: - if pair not in [None, self.removed]: - self.put(pair.key, pair.val) + /* 扩容哈希表 */ + public void extend() { + // 暂存原哈希表 + Pair[] bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair pair : bucketsTmp) { + if (pair != null && pair != removed) { + put(pair.key, pair.val); + } + } + } - def print(self): - """打印哈希表""" - for pair in self.buckets: - if pair is not None: - print(pair.key, "->", pair.val) - else: - print("None") + /* 打印哈希表 */ + public void print() { + for (Pair pair : buckets) { + if (pair != null) { + System.out.println(pair.key + " -> " + pair.val); + } else { + System.out.println("null"); + } + } + } + } + ``` + +=== "C#" + + ```csharp title="hash_map_open_addressing.cs" + /* 开放寻址哈希表 */ + class HashMapOpenAddressing { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + Pair[] buckets; // 桶数组 + Pair removed; // 删除标记 + + /* 构造方法 */ + public HashMapOpenAddressing() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new Pair[capacity]; + removed = new Pair(-1, "-1"); + } + + /* 哈希函数 */ + private int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + private double loadFactor() { + return (double)size / capacity; + } + + /* 查询操作 */ + public string get(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则返回 null + if (buckets[j] == null) + return null; + // 若遇到指定 key ,则返回对应 val + if (buckets[j].key == key && buckets[j] != removed) + return buckets[j].val; + } + return null; + } + + /* 添加操作 */ + public void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if (buckets[j] == null || buckets[j] == removed) { + buckets[j] = new Pair(key, val); + size += 1; + return; + } + // 若遇到指定 key ,则更新对应 val + if (buckets[j].key == key) { + buckets[j].val = val; + return; + } + } + } + + /* 删除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 线性探测,从 index 开始向后遍历 + for (int i = 0; i < capacity; i++) { + // 计算桶索引,越过尾部返回头部 + int j = (index + i) % capacity; + // 若遇到空桶,说明无此 key ,则直接返回 + if (buckets[j] == null) { + return; + } + // 若遇到指定 key ,则标记删除并返回 + if (buckets[j].key == key) { + buckets[j] = removed; + size -= 1; + return; + } + } + } + + /* 扩容哈希表 */ + private void extend() { + // 暂存原哈希表 + Pair[] bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + foreach (Pair pair in bucketsTmp) { + if (pair != null && pair != removed) { + put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + public void print() { + foreach (Pair pair in buckets) { + if (pair != null) { + Console.WriteLine(pair.key + " -> " + pair.val); + } else { + Console.WriteLine("null"); + } + } + } + } ``` === "Go" @@ -1666,6 +1789,131 @@ comments: true } ``` +=== "Swift" + + ```swift title="hash_map_open_addressing.swift" + /* 开放寻址哈希表 */ + class HashMapOpenAddressing { + var size: Int // 键值对数量 + var capacity: Int // 哈希表容量 + var loadThres: Double // 触发扩容的负载因子阈值 + var extendRatio: Int // 扩容倍数 + var buckets: [Pair?] // 桶数组 + var removed: Pair // 删除标记 + + /* 构造方法 */ + init() { + size = 0 + capacity = 4 + loadThres = 2 / 3 + extendRatio = 2 + buckets = Array(repeating: nil, count: capacity) + removed = Pair(key: -1, val: "-1") + } + + /* 哈希函数 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 负载因子 */ + func loadFactor() -> Double { + Double(size / capacity) + } + + /* 查询操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + // 线性探测,从 index 开始向后遍历 + for i in stride(from: 0, to: capacity, by: 1) { + // 计算桶索引,越过尾部返回头部 + let j = (index + i) % capacity + // 若遇到空桶,说明无此 key ,则返回 nil + if buckets[j] == nil { + return nil + } + // 若遇到指定 key ,则返回对应 val + if buckets[j]?.key == key, buckets[j] != removed { + return buckets[j]?.val + } + } + return nil + } + + /* 添加操作 */ + func put(key: Int, val: String) { + // 当负载因子超过阈值时,执行扩容 + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + // 线性探测,从 index 开始向后遍历 + for i in stride(from: 0, through: capacity, by: 1) { + // 计算桶索引,越过尾部返回头部 + let j = (index + i) % capacity + // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 + if buckets[j] == nil || buckets[j] == removed { + buckets[j] = Pair(key: key, val: val) + size += 1 + return + } + // 若遇到指定 key ,则更新对应 val + if buckets[j]?.key == key { + buckets[j]?.val = val + return + } + } + } + + /* 删除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + // 线性探测,从 index 开始向后遍历 + for i in stride(from: 0, to: capacity, by: 1) { + // 计算桶索引,越过尾部返回头部 + let j = (index + i) % capacity + // 若遇到空桶,说明无此 key ,则直接返回 + if buckets[j] == nil { + return + } + // 若遇到指定 key ,则标记删除并返回 + if buckets[j]?.key == key { + buckets[j] = removed + size -= 1 + return + } + } + } + + /* 扩容哈希表 */ + func extend() { + // 暂存原哈希表 + let bucketsTmp = buckets + // 初始化扩容后的新哈希表 + capacity *= extendRatio + buckets = Array(repeating: nil, count: capacity) + size = 0 + // 将键值对从原哈希表搬运至新哈希表 + for pair in bucketsTmp { + if let pair, pair != removed { + put(key: pair.key, val: pair.val) + } + } + } + + /* 打印哈希表 */ + func print() { + for pair in buckets { + if let pair { + Swift.print("\(pair.key) -> \(pair.val)") + } else { + Swift.print("null") + } + } + } + } + ``` + === "JS" ```javascript title="hash_map_open_addressing.js" @@ -1922,266 +2170,6 @@ comments: true } ``` -=== "C" - - ```c title="hash_map_open_addressing.c" - [class]{hashMapOpenAddressing}-[func]{} - ``` - -=== "C#" - - ```csharp title="hash_map_open_addressing.cs" - /* 开放寻址哈希表 */ - class HashMapOpenAddressing { - int size; // 键值对数量 - int capacity; // 哈希表容量 - double loadThres; // 触发扩容的负载因子阈值 - int extendRatio; // 扩容倍数 - Pair[] buckets; // 桶数组 - Pair removed; // 删除标记 - - /* 构造方法 */ - public HashMapOpenAddressing() { - size = 0; - capacity = 4; - loadThres = 2.0 / 3.0; - extendRatio = 2; - buckets = new Pair[capacity]; - removed = new Pair(-1, "-1"); - } - - /* 哈希函数 */ - private int hashFunc(int key) { - return key % capacity; - } - - /* 负载因子 */ - private double loadFactor() { - return (double)size / capacity; - } - - /* 查询操作 */ - public string get(int key) { - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶,说明无此 key ,则返回 null - if (buckets[j] == null) - return null; - // 若遇到指定 key ,则返回对应 val - if (buckets[j].key == key && buckets[j] != removed) - return buckets[j].val; - } - return null; - } - - /* 添加操作 */ - public void put(int key, string val) { - // 当负载因子超过阈值时,执行扩容 - if (loadFactor() > loadThres) { - extend(); - } - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if (buckets[j] == null || buckets[j] == removed) { - buckets[j] = new Pair(key, val); - size += 1; - return; - } - // 若遇到指定 key ,则更新对应 val - if (buckets[j].key == key) { - buckets[j].val = val; - return; - } - } - } - - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶,说明无此 key ,则直接返回 - if (buckets[j] == null) { - return; - } - // 若遇到指定 key ,则标记删除并返回 - if (buckets[j].key == key) { - buckets[j] = removed; - size -= 1; - return; - } - } - } - - /* 扩容哈希表 */ - private void extend() { - // 暂存原哈希表 - Pair[] bucketsTmp = buckets; - // 初始化扩容后的新哈希表 - capacity *= extendRatio; - buckets = new Pair[capacity]; - size = 0; - // 将键值对从原哈希表搬运至新哈希表 - foreach (Pair pair in bucketsTmp) { - if (pair != null && pair != removed) { - put(pair.key, pair.val); - } - } - } - - /* 打印哈希表 */ - public void print() { - foreach (Pair pair in buckets) { - if (pair != null) { - Console.WriteLine(pair.key + " -> " + pair.val); - } else { - Console.WriteLine("null"); - } - } - } - } - ``` - -=== "Swift" - - ```swift title="hash_map_open_addressing.swift" - /* 开放寻址哈希表 */ - class HashMapOpenAddressing { - var size: Int // 键值对数量 - var capacity: Int // 哈希表容量 - var loadThres: Double // 触发扩容的负载因子阈值 - var extendRatio: Int // 扩容倍数 - var buckets: [Pair?] // 桶数组 - var removed: Pair // 删除标记 - - /* 构造方法 */ - init() { - size = 0 - capacity = 4 - loadThres = 2 / 3 - extendRatio = 2 - buckets = Array(repeating: nil, count: capacity) - removed = Pair(key: -1, val: "-1") - } - - /* 哈希函数 */ - func hashFunc(key: Int) -> Int { - key % capacity - } - - /* 负载因子 */ - func loadFactor() -> Double { - Double(size / capacity) - } - - /* 查询操作 */ - func get(key: Int) -> String? { - let index = hashFunc(key: key) - // 线性探测,从 index 开始向后遍历 - for i in stride(from: 0, to: capacity, by: 1) { - // 计算桶索引,越过尾部返回头部 - let j = (index + i) % capacity - // 若遇到空桶,说明无此 key ,则返回 nil - if buckets[j] == nil { - return nil - } - // 若遇到指定 key ,则返回对应 val - if buckets[j]?.key == key, buckets[j] != removed { - return buckets[j]?.val - } - } - return nil - } - - /* 添加操作 */ - func put(key: Int, val: String) { - // 当负载因子超过阈值时,执行扩容 - if loadFactor() > loadThres { - extend() - } - let index = hashFunc(key: key) - // 线性探测,从 index 开始向后遍历 - for i in stride(from: 0, through: capacity, by: 1) { - // 计算桶索引,越过尾部返回头部 - let j = (index + i) % capacity - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if buckets[j] == nil || buckets[j] == removed { - buckets[j] = Pair(key: key, val: val) - size += 1 - return - } - // 若遇到指定 key ,则更新对应 val - if buckets[j]?.key == key { - buckets[j]?.val = val - return - } - } - } - - /* 删除操作 */ - func remove(key: Int) { - let index = hashFunc(key: key) - // 线性探测,从 index 开始向后遍历 - for i in stride(from: 0, to: capacity, by: 1) { - // 计算桶索引,越过尾部返回头部 - let j = (index + i) % capacity - // 若遇到空桶,说明无此 key ,则直接返回 - if buckets[j] == nil { - return - } - // 若遇到指定 key ,则标记删除并返回 - if buckets[j]?.key == key { - buckets[j] = removed - size -= 1 - return - } - } - } - - /* 扩容哈希表 */ - func extend() { - // 暂存原哈希表 - let bucketsTmp = buckets - // 初始化扩容后的新哈希表 - capacity *= extendRatio - buckets = Array(repeating: nil, count: capacity) - size = 0 - // 将键值对从原哈希表搬运至新哈希表 - for pair in bucketsTmp { - if let pair, pair != removed { - put(key: pair.key, val: pair.val) - } - } - } - - /* 打印哈希表 */ - func print() { - for pair in buckets { - if let pair { - Swift.print("\(pair.key) -> \(pair.val)") - } else { - Swift.print("null") - } - } - } - } - ``` - -=== "Zig" - - ```zig title="hash_map_open_addressing.zig" - [class]{HashMapOpenAddressing}-[func]{} - ``` - === "Dart" ```dart title="hash_map_open_addressing.dart" @@ -2449,6 +2437,18 @@ comments: true } ``` +=== "C" + + ```c title="hash_map_open_addressing.c" + [class]{hashMapOpenAddressing}-[func]{} + ``` + +=== "Zig" + + ```zig title="hash_map_open_addressing.zig" + [class]{HashMapOpenAddressing}-[func]{} + ``` + ### 2.   多次哈希 顾名思义,多次哈希方法是使用多个哈希函数 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 进行探测。 diff --git a/chapter_hashing/hash_map.md b/chapter_hashing/hash_map.md index 1091eda56..50b682c68 100755 --- a/chapter_hashing/hash_map.md +++ b/chapter_hashing/hash_map.md @@ -36,27 +36,27 @@ comments: true 哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。 -=== "Java" +=== "Python" - ```java title="hash_map.java" - /* 初始化哈希表 */ - Map map = new HashMap<>(); + ```python title="hash_map.py" + # 初始化哈希表 + hmap: dict = {} - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); - map.put(13276, "小法"); - map.put(10583, "小鸭"); + # 添加操作 + # 在哈希表中添加键值对 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小啰" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鸭" - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - String name = map.get(15937); + # 查询操作 + # 向哈希表输入键 key ,得到值 value + name: str = hmap[15937] - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.remove(10583); + # 删除操作 + # 在哈希表中删除键值对 (key, value) + hmap.pop(10583) ``` === "C++" @@ -82,27 +82,50 @@ comments: true map.erase(10583); ``` -=== "Python" +=== "Java" - ```python title="hash_map.py" - # 初始化哈希表 - hmap: dict = {} + ```java title="hash_map.java" + /* 初始化哈希表 */ + Map map = new HashMap<>(); - # 添加操作 - # 在哈希表中添加键值对 (key, value) - hmap[12836] = "小哈" - hmap[15937] = "小啰" - hmap[16750] = "小算" - hmap[13276] = "小法" - hmap[10583] = "小鸭" + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); - # 查询操作 - # 向哈希表输入键 key ,得到值 value - name: str = hmap[15937] + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + String name = map.get(15937); - # 删除操作 - # 在哈希表中删除键值对 (key, value) - hmap.pop(10583) + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + ``` + +=== "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); ``` === "Go" @@ -128,6 +151,29 @@ comments: true delete(hmap, 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) + ``` + === "JS" ```javascript title="hash_map.js" @@ -177,64 +223,6 @@ comments: true console.info(map); ``` -=== "C" - - ```c title="hash_map.c" - // 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" - - ``` - === "Dart" ```dart title="hash_map.dart" @@ -264,24 +252,33 @@ comments: true ``` +=== "C" + + ```c title="hash_map.c" + // C 未提供内置哈希表 + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + 哈希表有三种常用遍历方式:遍历键值对、遍历键和遍历值。 -=== "Java" +=== "Python" - ```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); - } + ```python title="hash_map.py" + # 遍历哈希表 + # 遍历键值对 key->value + for key, value in hmap.items(): + print(key, "->", value) + # 单独遍历键 key + for key in hmap.keys(): + print(key) + # 单独遍历值 value + for value in hmap.values(): + print(value) ``` === "C++" @@ -302,19 +299,40 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="hash_map.py" - # 遍历哈希表 - # 遍历键值对 key->value - for key, value in hmap.items(): - print(key, "->", value) - # 单独遍历键 key - for key in hmap.keys(): - print(key) - # 单独遍历值 value - for value in hmap.values(): - print(value) + ```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#" + + ```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); + } ``` === "Go" @@ -335,6 +353,24 @@ comments: true } ``` +=== "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) + } + ``` + === "JS" ```javascript title="hash_map.js" @@ -371,54 +407,6 @@ comments: true } ``` -=== "C" - - ```c title="hash_map.c" - // 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" - - ``` - === "Dart" ```dart title="hash_map.dart" @@ -445,6 +433,18 @@ comments: true ``` +=== "C" + + ```c title="hash_map.c" + // C 未提供内置哈希表 + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + ## 6.1.2   哈希表简单实现 我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为「桶 bucket」,每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。 @@ -470,98 +470,78 @@ index = hash(key) % capacity 以下代码实现了一个简单哈希表。其中,我们将 `key` 和 `value` 封装成一个类 `Pair` ,以表示键值对。 -=== "Java" +=== "Python" - ```java title="array_hash_map.java" - /* 键值对 */ - class Pair { - public int key; - public String val; + ```python title="array_hash_map.py" + class Pair: + """键值对""" - public Pair(int key, String val) { - this.key = key; - this.val = val; - } - } + def __init__(self, key: int, val: str): + self.key = key + self.val = val - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private List buckets; + class ArrayHashMap: + """基于数组简易实现的哈希表""" - public ArrayHashMap() { - // 初始化数组,包含 100 个桶 - buckets = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - buckets.add(null); - } - } + def __init__(self): + """构造方法""" + # 初始化数组,包含 100 个桶 + self.buckets: list[Pair | None] = [None] * 100 - /* 哈希函数 */ - private int hashFunc(int key) { - int index = key % 100; - return index; - } + def hash_func(self, key: int) -> int: + """哈希函数""" + index = key % 100 + return index - /* 查询操作 */ - public String get(int key) { - int index = hashFunc(key); - Pair pair = buckets.get(index); - if (pair == null) - return null; - return pair.val; - } + def get(self, key: int) -> str: + """查询操作""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] + if pair is None: + return None + return pair.val - /* 添加操作 */ - public void put(int key, String val) { - Pair pair = new Pair(key, val); - int index = hashFunc(key); - buckets.set(index, pair); - } + def put(self, key: int, val: str): + """添加操作""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 置为 null ,代表删除 - buckets.set(index, null); - } + def remove(self, key: int): + """删除操作""" + index: int = self.hash_func(key) + # 置为 None ,代表删除 + self.buckets[index] = None - /* 获取所有键值对 */ - public List pairSet() { - List pairSet = new ArrayList<>(); - for (Pair pair : buckets) { - if (pair != null) - pairSet.add(pair); - } - return pairSet; - } + def entry_set(self) -> list[Pair]: + """获取所有键值对""" + result: list[Pair] = [] + for pair in self.buckets: + if pair is not None: + result.append(pair) + return result - /* 获取所有键 */ - public List keySet() { - List keySet = new ArrayList<>(); - for (Pair pair : buckets) { - if (pair != null) - keySet.add(pair.key); - } - return keySet; - } + def key_set(self) -> list[int]: + """获取所有键""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.key) + return result - /* 获取所有值 */ - public List valueSet() { - List valueSet = new ArrayList<>(); - for (Pair pair : buckets) { - if (pair != null) - valueSet.add(pair.val); - } - return valueSet; - } + def value_set(self) -> list[str]: + """获取所有值""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.val) + return result - /* 打印哈希表 */ - public void print() { - for (Pair kv : pairSet()) { - System.out.println(kv.key + " -> " + kv.val); - } - } - } + def print(self): + """打印哈希表""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) ``` === "C++" @@ -669,78 +649,189 @@ index = hash(key) % capacity }; ``` -=== "Python" +=== "Java" - ```python title="array_hash_map.py" - class Pair: - """键值对""" + ```java title="array_hash_map.java" + /* 键值对 */ + class Pair { + public int key; + public String val; - def __init__(self, key: int, val: str): - self.key = key - self.val = val + public Pair(int key, String val) { + this.key = key; + this.val = val; + } + } - class ArrayHashMap: - """基于数组简易实现的哈希表""" + /* 基于数组简易实现的哈希表 */ + class ArrayHashMap { + private List buckets; - def __init__(self): - """构造方法""" - # 初始化数组,包含 100 个桶 - self.buckets: list[Pair | None] = [None] * 100 + public ArrayHashMap() { + // 初始化数组,包含 100 个桶 + buckets = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + buckets.add(null); + } + } - def hash_func(self, key: int) -> int: - """哈希函数""" - index = key % 100 - return index + /* 哈希函数 */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } - def get(self, key: int) -> str: - """查询操作""" - index: int = self.hash_func(key) - pair: Pair = self.buckets[index] - if pair is None: - return None - return pair.val + /* 查询操作 */ + public String get(int key) { + int index = hashFunc(key); + Pair pair = buckets.get(index); + if (pair == null) + return null; + return pair.val; + } - def put(self, key: int, val: str): - """添加操作""" - pair = Pair(key, val) - index: int = self.hash_func(key) - self.buckets[index] = pair + /* 添加操作 */ + public void put(int key, String val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets.set(index, pair); + } - def remove(self, key: int): - """删除操作""" - index: int = self.hash_func(key) - # 置为 None ,代表删除 - self.buckets[index] = None + /* 删除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 置为 null ,代表删除 + buckets.set(index, null); + } - def entry_set(self) -> list[Pair]: - """获取所有键值对""" - result: list[Pair] = [] - for pair in self.buckets: - if pair is not None: - result.append(pair) - return result + /* 获取所有键值对 */ + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + pairSet.add(pair); + } + return pairSet; + } - def key_set(self) -> list[int]: - """获取所有键""" - result = [] - for pair in self.buckets: - if pair is not None: - result.append(pair.key) - return result + /* 获取所有键 */ + public List keySet() { + List keySet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + keySet.add(pair.key); + } + return keySet; + } - def value_set(self) -> list[str]: - """获取所有值""" - result = [] - for pair in self.buckets: - if pair is not None: - result.append(pair.val) - return result + /* 获取所有值 */ + public List valueSet() { + List valueSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + valueSet.add(pair.val); + } + return valueSet; + } - def print(self): - """打印哈希表""" - for pair in self.buckets: - if pair is not None: - print(pair.key, "->", pair.val) + /* 打印哈希表 */ + public void print() { + for (Pair kv : pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + } + } + ``` + +=== "C#" + + ```csharp title="array_hash_map.cs" + /* 键值对 int->string */ + class Pair { + public int key; + public string val; + public Pair(int key, string val) { + this.key = key; + this.val = val; + } + } + + /* 基于数组简易实现的哈希表 */ + class ArrayHashMap { + private List buckets; + public ArrayHashMap() { + // 初始化数组,包含 100 个桶 + buckets = new(); + for (int i = 0; i < 100; i++) { + buckets.Add(null); + } + } + + /* 哈希函数 */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查询操作 */ + public string? get(int key) { + int index = hashFunc(key); + Pair? pair = buckets[index]; + if (pair == null) return null; + return pair.val; + } + + /* 添加操作 */ + public void put(int key, string val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* 删除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 置为 null ,代表删除 + buckets[index] = null; + } + + /* 获取所有键值对 */ + public List pairSet() { + List pairSet = new(); + foreach (Pair? pair in buckets) { + if (pair != null) + pairSet.Add(pair); + } + return pairSet; + } + + /* 获取所有键 */ + public List keySet() { + List keySet = new(); + foreach (Pair? pair in buckets) { + if (pair != null) + keySet.Add(pair.key); + } + return keySet; + } + + /* 获取所有值 */ + public List valueSet() { + List valueSet = new(); + foreach (Pair? pair in buckets) { + if (pair != null) + valueSet.Add(pair.val); + } + return valueSet; + } + + /* 打印哈希表 */ + public void print() { + foreach (Pair kv in pairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + } + } ``` === "Go" @@ -837,6 +928,100 @@ index = hash(key) % capacity } ``` +=== "Swift" + + ```swift title="array_hash_map.swift" + /* 键值对 */ + class Pair { + var key: Int + var val: String + + init(key: Int, val: String) { + self.key = key + self.val = val + } + } + + /* 基于数组简易实现的哈希表 */ + class ArrayHashMap { + private var buckets: [Pair?] = [] + + init() { + // 初始化数组,包含 100 个桶 + for _ in 0 ..< 100 { + buckets.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 = buckets[index] + return pair?.val + } + + /* 添加操作 */ + func put(key: Int, val: String) { + let pair = Pair(key: key, val: val) + let index = hashFunc(key: key) + buckets[index] = pair + } + + /* 删除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + // 置为 nil ,代表删除 + buckets[index] = nil + } + + /* 获取所有键值对 */ + func pairSet() -> [Pair] { + var pairSet: [Pair] = [] + for pair in buckets { + if let pair = pair { + pairSet.append(pair) + } + } + return pairSet + } + + /* 获取所有键 */ + func keySet() -> [Int] { + var keySet: [Int] = [] + for pair in buckets { + if let pair = pair { + keySet.append(pair.key) + } + } + return keySet + } + + /* 获取所有值 */ + func valueSet() -> [String] { + var valueSet: [String] = [] + for pair in buckets { + if let pair = pair { + valueSet.append(pair.val) + } + } + return valueSet + } + + /* 打印哈希表 */ + func print() { + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") + } + } + } + ``` + === "JS" ```javascript title="array_hash_map.js" @@ -1019,314 +1204,6 @@ index = hash(key) % capacity } ``` -=== "C" - - ```c title="array_hash_map.c" - /* 键值对 int->string */ - struct pair { - int key; - char *val; - }; - - typedef struct pair pair; - - [class]{arrayHashMap}-[func]{} - ``` - -=== "C#" - - ```csharp title="array_hash_map.cs" - /* 键值对 int->string */ - class Pair { - public int key; - public string val; - public Pair(int key, string val) { - this.key = key; - this.val = val; - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private List buckets; - public ArrayHashMap() { - // 初始化数组,包含 100 个桶 - buckets = new(); - for (int i = 0; i < 100; i++) { - buckets.Add(null); - } - } - - /* 哈希函数 */ - private int hashFunc(int key) { - int index = key % 100; - return index; - } - - /* 查询操作 */ - public string? get(int key) { - int index = hashFunc(key); - Pair? pair = buckets[index]; - if (pair == null) return null; - return pair.val; - } - - /* 添加操作 */ - public void put(int key, string val) { - Pair pair = new Pair(key, val); - int index = hashFunc(key); - buckets[index] = pair; - } - - /* 删除操作 */ - public void remove(int key) { - int index = hashFunc(key); - // 置为 null ,代表删除 - buckets[index] = null; - } - - /* 获取所有键值对 */ - public List pairSet() { - List pairSet = new(); - foreach (Pair? pair in buckets) { - if (pair != null) - pairSet.Add(pair); - } - return pairSet; - } - - /* 获取所有键 */ - public List keySet() { - List keySet = new(); - foreach (Pair? pair in buckets) { - if (pair != null) - keySet.Add(pair.key); - } - return keySet; - } - - /* 获取所有值 */ - public List valueSet() { - List valueSet = new(); - foreach (Pair? pair in buckets) { - if (pair != null) - valueSet.Add(pair.val); - } - return valueSet; - } - - /* 打印哈希表 */ - public void print() { - foreach (Pair kv in pairSet()) { - Console.WriteLine(kv.key + " -> " + kv.val); - } - } - } - ``` - -=== "Swift" - - ```swift title="array_hash_map.swift" - /* 键值对 */ - class Pair { - var key: Int - var val: String - - init(key: Int, val: String) { - self.key = key - self.val = val - } - } - - /* 基于数组简易实现的哈希表 */ - class ArrayHashMap { - private var buckets: [Pair?] = [] - - init() { - // 初始化数组,包含 100 个桶 - for _ in 0 ..< 100 { - buckets.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 = buckets[index] - return pair?.val - } - - /* 添加操作 */ - func put(key: Int, val: String) { - let pair = Pair(key: key, val: val) - let index = hashFunc(key: key) - buckets[index] = pair - } - - /* 删除操作 */ - func remove(key: Int) { - let index = hashFunc(key: key) - // 置为 nil ,代表删除 - buckets[index] = nil - } - - /* 获取所有键值对 */ - func pairSet() -> [Pair] { - var pairSet: [Pair] = [] - for pair in buckets { - if let pair = pair { - pairSet.append(pair) - } - } - return pairSet - } - - /* 获取所有键 */ - func keySet() -> [Int] { - var keySet: [Int] = [] - for pair in buckets { - if let pair = pair { - keySet.append(pair.key) - } - } - return keySet - } - - /* 获取所有值 */ - func valueSet() -> [String] { - var valueSet: [String] = [] - for pair in buckets { - if let pair = pair { - valueSet.append(pair.val) - } - } - return valueSet - } - - /* 打印哈希表 */ - func print() { - for pair in pairSet() { - Swift.print("\(pair.key) -> \(pair.val)") - } - } - } - ``` - -=== "Zig" - - ```zig title="array_hash_map.zig" - // 键值对 - const Pair = struct { - key: usize = undefined, - val: []const u8 = undefined, - - pub fn init(key: usize, val: []const u8) Pair { - return Pair { - .key = key, - .val = val, - }; - } - }; - - // 基于数组简易实现的哈希表 - fn ArrayHashMap(comptime T: type) type { - return struct { - bucket: ?std.ArrayList(?T) = null, - mem_allocator: std.mem.Allocator = undefined, - - const Self = @This(); - - // 构造函数 - pub fn init(self: *Self, allocator: std.mem.Allocator) !void { - self.mem_allocator = allocator; - // 初始化一个长度为 100 的桶(数组) - self.bucket = std.ArrayList(?T).init(self.mem_allocator); - var i: i32 = 0; - while (i < 100) : (i += 1) { - try self.bucket.?.append(null); - } - } - - // 析构函数 - pub fn deinit(self: *Self) void { - if (self.bucket != null) self.bucket.?.deinit(); - } - - // 哈希函数 - fn hashFunc(key: usize) usize { - var index = key % 100; - return index; - } - - // 查询操作 - pub fn get(self: *Self, key: usize) []const u8 { - var index = hashFunc(key); - var pair = self.bucket.?.items[index]; - return pair.?.val; - } - - // 添加操作 - pub fn put(self: *Self, key: usize, val: []const u8) !void { - var pair = Pair.init(key, val); - var index = hashFunc(key); - self.bucket.?.items[index] = pair; - } - - // 删除操作 - pub fn remove(self: *Self, key: usize) !void { - var index = hashFunc(key); - // 置为 null ,代表删除 - self.bucket.?.items[index] = null; - } - - // 获取所有键值对 - pub fn pairSet(self: *Self) !std.ArrayList(T) { - var entry_set = std.ArrayList(T).init(self.mem_allocator); - for (self.bucket.?.items) |item| { - if (item == null) continue; - try entry_set.append(item.?); - } - return entry_set; - } - - // 获取所有键 - pub fn keySet(self: *Self) !std.ArrayList(usize) { - var key_set = std.ArrayList(usize).init(self.mem_allocator); - for (self.bucket.?.items) |item| { - if (item == null) continue; - try key_set.append(item.?.key); - } - return key_set; - } - - // 获取所有值 - pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { - var value_set = std.ArrayList([]const u8).init(self.mem_allocator); - for (self.bucket.?.items) |item| { - if (item == null) continue; - try value_set.append(item.?.val); - } - return value_set; - } - - // 打印哈希表 - pub fn print(self: *Self) !void { - var entry_set = try self.pairSet(); - defer entry_set.deinit(); - for (entry_set.items) |item| { - std.debug.print("{} -> {s}\n", .{item.key, item.val}); - } - } - }; - } - ``` - === "Dart" ```dart title="array_hash_map.dart" @@ -1488,6 +1365,129 @@ index = hash(key) % capacity } ``` +=== "C" + + ```c title="array_hash_map.c" + /* 键值对 int->string */ + struct pair { + int key; + char *val; + }; + + typedef struct pair pair; + + [class]{arrayHashMap}-[func]{} + ``` + +=== "Zig" + + ```zig title="array_hash_map.zig" + // 键值对 + const Pair = struct { + key: usize = undefined, + val: []const u8 = undefined, + + pub fn init(key: usize, val: []const u8) Pair { + return Pair { + .key = key, + .val = val, + }; + } + }; + + // 基于数组简易实现的哈希表 + fn ArrayHashMap(comptime T: type) type { + return struct { + bucket: ?std.ArrayList(?T) = null, + mem_allocator: std.mem.Allocator = undefined, + + const Self = @This(); + + // 构造函数 + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + self.mem_allocator = allocator; + // 初始化一个长度为 100 的桶(数组) + self.bucket = std.ArrayList(?T).init(self.mem_allocator); + var i: i32 = 0; + while (i < 100) : (i += 1) { + try self.bucket.?.append(null); + } + } + + // 析构函数 + pub fn deinit(self: *Self) void { + if (self.bucket != null) self.bucket.?.deinit(); + } + + // 哈希函数 + fn hashFunc(key: usize) usize { + var index = key % 100; + return index; + } + + // 查询操作 + pub fn get(self: *Self, key: usize) []const u8 { + var index = hashFunc(key); + var pair = self.bucket.?.items[index]; + return pair.?.val; + } + + // 添加操作 + pub fn put(self: *Self, key: usize, val: []const u8) !void { + var pair = Pair.init(key, val); + var index = hashFunc(key); + self.bucket.?.items[index] = pair; + } + + // 删除操作 + pub fn remove(self: *Self, key: usize) !void { + var index = hashFunc(key); + // 置为 null ,代表删除 + self.bucket.?.items[index] = null; + } + + // 获取所有键值对 + pub fn pairSet(self: *Self) !std.ArrayList(T) { + var entry_set = std.ArrayList(T).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try entry_set.append(item.?); + } + return entry_set; + } + + // 获取所有键 + pub fn keySet(self: *Self) !std.ArrayList(usize) { + var key_set = std.ArrayList(usize).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try key_set.append(item.?.key); + } + return key_set; + } + + // 获取所有值 + pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { + var value_set = std.ArrayList([]const u8).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try value_set.append(item.?.val); + } + return value_set; + } + + // 打印哈希表 + pub fn print(self: *Self) !void { + var entry_set = try self.pairSet(); + defer entry_set.deinit(); + for (entry_set.items) |item| { + std.debug.print("{} -> {s}\n", .{item.key, item.val}); + } + } + }; + } + ``` + ## 6.1.3   哈希冲突与扩容 本质上看,哈希函数的作用是将所有 `key` 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。 diff --git a/chapter_heap/build_heap.md b/chapter_heap/build_heap.md index b59cee050..9f5004fa6 100644 --- a/chapter_heap/build_heap.md +++ b/chapter_heap/build_heap.md @@ -26,18 +26,16 @@ comments: true - 由于叶节点没有子节点,因此无需对它们执行堆化。最后一个节点的父节点是最后一个非叶节点。 - 在倒序遍历中,我们能够保证当前节点之下的子树已经完成堆化(已经是合法的堆),而这是堆化当前节点的前置条件。 -=== "Java" +=== "Python" - ```java title="my_heap.java" - /* 构造方法,根据输入列表建堆 */ - MaxHeap(List nums) { - // 将列表元素原封不动添加进堆 - maxHeap = new ArrayList<>(nums); - // 堆化除叶节点以外的其他所有节点 - for (int i = parent(size() - 1); i >= 0; i--) { - siftDown(i); - } - } + ```python title="my_heap.py" + def __init__(self, nums: list[int]): + """构造方法,根据输入列表建堆""" + # 将列表元素原封不动添加进堆 + self.max_heap = nums + # 堆化除叶节点以外的其他所有节点 + for i in range(self.parent(self.size() - 1), -1, -1): + self.sift_down(i) ``` === "C++" @@ -54,16 +52,33 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="my_heap.py" - def __init__(self, nums: list[int]): - """构造方法,根据输入列表建堆""" - # 将列表元素原封不动添加进堆 - self.max_heap = nums - # 堆化除叶节点以外的其他所有节点 - for i in range(self.parent(self.size() - 1), -1, -1): - self.sift_down(i) + ```java title="my_heap.java" + /* 构造方法,根据输入列表建堆 */ + MaxHeap(List nums) { + // 将列表元素原封不动添加进堆 + maxHeap = new ArrayList<>(nums); + // 堆化除叶节点以外的其他所有节点 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 构造函数,根据输入列表建堆 */ + MaxHeap(IEnumerable nums) { + // 将列表元素原封不动添加进堆 + maxHeap = new List(nums); + // 堆化除叶节点以外的其他所有节点 + var size = parent(this.size() - 1); + for (int i = size; i >= 0; i--) { + siftDown(i); + } + } ``` === "Go" @@ -81,6 +96,20 @@ comments: true } ``` +=== "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) + } + } + ``` + === "JS" ```javascript title="my_heap.js" @@ -109,69 +138,6 @@ comments: true } ``` -=== "C" - - ```c title="my_heap.c" - /* 构造函数,根据切片建堆 */ - maxHeap *newMaxHeap(int nums[], int size) { - // 所有元素入堆 - maxHeap *h = (maxHeap *)malloc(sizeof(maxHeap)); - h->size = size; - memcpy(h->data, nums, size * sizeof(int)); - for (int i = parent(size - 1); i >= 0; i--) { - // 堆化除叶节点以外的其他所有节点 - siftDown(h, i); - } - return h; - } - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - /* 构造函数,根据输入列表建堆 */ - MaxHeap(IEnumerable nums) { - // 将列表元素原封不动添加进堆 - maxHeap = new List(nums); - // 堆化除叶节点以外的其他所有节点 - var size = parent(this.size() - 1); - for (int i = size; i >= 0; i--) { - siftDown(i); - } - } - ``` - -=== "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" - // 构造方法,根据输入列表建堆 - fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { - if (self.max_heap != null) return; - self.max_heap = std.ArrayList(T).init(allocator); - // 将列表元素原封不动添加进堆 - try self.max_heap.?.appendSlice(nums); - // 堆化除叶节点以外的其他所有节点 - var i: usize = parent(self.size() - 1) + 1; - while (i > 0) : (i -= 1) { - try self.siftDown(i - 1); - } - } - ``` - === "Dart" ```dart title="my_heap.dart" @@ -201,6 +167,40 @@ comments: true } ``` +=== "C" + + ```c title="my_heap.c" + /* 构造函数,根据切片建堆 */ + maxHeap *newMaxHeap(int nums[], int size) { + // 所有元素入堆 + maxHeap *h = (maxHeap *)malloc(sizeof(maxHeap)); + h->size = size; + memcpy(h->data, nums, size * sizeof(int)); + for (int i = parent(size - 1); i >= 0; i--) { + // 堆化除叶节点以外的其他所有节点 + siftDown(h, i); + } + return h; + } + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 构造方法,根据输入列表建堆 + fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // 将列表元素原封不动添加进堆 + try self.max_heap.?.appendSlice(nums); + // 堆化除叶节点以外的其他所有节点 + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + ``` + ## 8.2.3   复杂度分析 下面,我们来尝试推算第二种建堆方法的时间复杂度。 diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index addddf3d5..e8ae66689 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -47,41 +47,45 @@ comments: true 类似于排序算法中的“从小到大排列”和“从大到小排列”,我们可以通过修改 Comparator 来实现“小顶堆”与“大顶堆”之间的转换。 -=== "Java" +=== "Python" - ```java title="heap.java" - /* 初始化堆 */ - // 初始化小顶堆 - Queue minHeap = new PriorityQueue<>(); - // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) - Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); - - /* 元素入堆 */ - maxHeap.offer(1); - maxHeap.offer(3); - maxHeap.offer(2); - maxHeap.offer(5); - maxHeap.offer(4); - - /* 获取堆顶元素 */ - int peek = maxHeap.peek(); // 5 - - /* 堆顶元素出堆 */ - // 出堆元素会形成一个从大到小的序列 - peek = maxHeap.poll(); // 5 - peek = maxHeap.poll(); // 4 - peek = maxHeap.poll(); // 3 - peek = maxHeap.poll(); // 2 - peek = maxHeap.poll(); // 1 - - /* 获取堆大小 */ - int size = maxHeap.size(); - - /* 判断堆是否为空 */ - boolean isEmpty = maxHeap.isEmpty(); - - /* 输入列表并建堆 */ - minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ```python title="heap.py" + # 初始化小顶堆 + min_heap, flag = [], 1 + # 初始化大顶堆 + max_heap, flag = [], -1 + + # Python 的 heapq 模块默认实现小顶堆 + # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 + # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 + + # 元素入堆 + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + # 获取堆顶元素 + peek: int = flag * max_heap[0] # 5 + + # 堆顶元素出堆 + # 出堆元素会形成一个从大到小的序列 + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + # 获取堆大小 + size: int = len(max_heap) + + # 判断堆是否为空 + is_empty: bool = not max_heap + + # 输入列表并建堆 + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) ``` === "C++" @@ -122,45 +126,78 @@ comments: true priority_queue, greater> minHeap(input.begin(), input.end()); ``` -=== "Python" +=== "Java" - ```python title="heap.py" - # 初始化小顶堆 - min_heap, flag = [], 1 - # 初始化大顶堆 - max_heap, flag = [], -1 + ```java title="heap.java" + /* 初始化堆 */ + // 初始化小顶堆 + Queue minHeap = new PriorityQueue<>(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /* 元素入堆 */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); // 5 + + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 + + /* 获取堆大小 */ + int size = maxHeap.size(); + + /* 判断堆是否为空 */ + boolean isEmpty = maxHeap.isEmpty(); + + /* 输入列表并建堆 */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ``` - # Python 的 heapq 模块默认实现小顶堆 - # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 - # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 +=== "C#" - # 元素入堆 - heapq.heappush(max_heap, flag * 1) - heapq.heappush(max_heap, flag * 3) - heapq.heappush(max_heap, flag * 2) - heapq.heappush(max_heap, flag * 5) - heapq.heappush(max_heap, flag * 4) + ```csharp title="heap.cs" + /* 初始化堆 */ + // 初始化小顶堆 + PriorityQueue minHeap = new PriorityQueue(); + // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) + PriorityQueue maxHeap = new PriorityQueue(Comparer.Create((x, y) => y - x)); - # 获取堆顶元素 - peek: int = flag * max_heap[0] # 5 + /* 元素入堆 */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); - # 堆顶元素出堆 - # 出堆元素会形成一个从大到小的序列 - val = flag * heapq.heappop(max_heap) # 5 - val = flag * heapq.heappop(max_heap) # 4 - val = flag * heapq.heappop(max_heap) # 3 - val = flag * heapq.heappop(max_heap) # 2 - val = flag * heapq.heappop(max_heap) # 1 + /* 获取堆顶元素 */ + int peek = maxHeap.Peek();//5 - # 获取堆大小 - size: int = len(max_heap) + /* 堆顶元素出堆 */ + // 出堆元素会形成一个从大到小的序列 + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 - # 判断堆是否为空 - is_empty: bool = not max_heap + /* 获取堆大小 */ + int size = maxHeap.Count; - # 输入列表并建堆 - min_heap: list[int] = [1, 3, 2, 5, 4] - heapq.heapify(min_heap) + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.Count == 0; + + /* 输入列表并建堆 */ + minHeap = new PriorityQueue(new List<(int, int)> { (1, 1), (3, 3), (2, 2), (5, 5), (4, 4), }); ``` === "Go" @@ -242,6 +279,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="heap.swift" + // Swift 未提供内置 Heap 类 + ``` + === "JS" ```javascript title="heap.js" @@ -254,61 +297,6 @@ comments: true // TypeScript 未提供内置 Heap 类 ``` -=== "C" - - ```c title="heap.c" - // C 未提供内置 Heap 类 - ``` - -=== "C#" - - ```csharp title="heap.cs" - /* 初始化堆 */ - // 初始化小顶堆 - PriorityQueue minHeap = new PriorityQueue(); - // 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可) - PriorityQueue maxHeap = new PriorityQueue(Comparer.Create((x, y) => y - x)); - - /* 元素入堆 */ - maxHeap.Enqueue(1, 1); - maxHeap.Enqueue(3, 3); - maxHeap.Enqueue(2, 2); - maxHeap.Enqueue(5, 5); - maxHeap.Enqueue(4, 4); - - /* 获取堆顶元素 */ - int peek = maxHeap.Peek();//5 - - /* 堆顶元素出堆 */ - // 出堆元素会形成一个从大到小的序列 - peek = maxHeap.Dequeue(); // 5 - peek = maxHeap.Dequeue(); // 4 - peek = maxHeap.Dequeue(); // 3 - peek = maxHeap.Dequeue(); // 2 - peek = maxHeap.Dequeue(); // 1 - - /* 获取堆大小 */ - int size = maxHeap.Count; - - /* 判断堆是否为空 */ - bool isEmpty = maxHeap.Count == 0; - - /* 输入列表并建堆 */ - minHeap = new PriorityQueue(new List<(int, int)> { (1, 1), (3, 3), (2, 2), (5, 5), (4, 4), }); - ``` - -=== "Swift" - - ```swift title="heap.swift" - // Swift 未提供内置 Heap 类 - ``` - -=== "Zig" - - ```zig title="heap.zig" - - ``` - === "Dart" ```dart title="heap.dart" @@ -321,6 +309,18 @@ comments: true ``` +=== "C" + + ```c title="heap.c" + // C 未提供内置 Heap 类 + ``` + +=== "Zig" + + ```zig title="heap.zig" + + ``` + ## 8.1.2   堆的实现 下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断取逆(例如,将 $\geq$ 替换为 $\leq$ )。感兴趣的读者可以自行实现。 @@ -339,23 +339,20 @@ comments: true 我们可以将索引映射公式封装成函数,方便后续使用。 -=== "Java" +=== "Python" - ```java title="my_heap.java" - /* 获取左子节点索引 */ - int left(int i) { - return 2 * i + 1; - } + ```python title="my_heap.py" + def left(self, i: int) -> int: + """获取左子节点索引""" + return 2 * i + 1 - /* 获取右子节点索引 */ - int right(int i) { - return 2 * i + 2; - } + def right(self, i: int) -> int: + """获取右子节点索引""" + return 2 * i + 2 - /* 获取父节点索引 */ - int parent(int i) { - return (i - 1) / 2; // 向下整除 - } + def parent(self, i: int) -> int: + """获取父节点索引""" + return (i - 1) // 2 # 向下整除 ``` === "C++" @@ -377,20 +374,42 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="my_heap.py" - def left(self, i: int) -> int: - """获取左子节点索引""" - return 2 * i + 1 + ```java title="my_heap.java" + /* 获取左子节点索引 */ + int left(int i) { + return 2 * i + 1; + } - def right(self, i: int) -> int: - """获取右子节点索引""" - return 2 * i + 2 + /* 获取右子节点索引 */ + int right(int i) { + return 2 * i + 2; + } - def parent(self, i: int) -> int: - """获取父节点索引""" - return (i - 1) // 2 # 向下整除 + /* 获取父节点索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 获取左子节点索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取父节点索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } ``` === "Go" @@ -413,6 +432,25 @@ comments: true } ``` +=== "Swift" + + ```swift title="my_heap.swift" + /* 获取左子节点索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 获取右子节点索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 获取父节点索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 // 向下整除 + } + ``` + === "JS" ```javascript title="my_heap.js" @@ -451,83 +489,6 @@ comments: true } ``` -=== "C" - - ```c title="my_heap.c" - /* 获取左子节点索引 */ - int left(maxHeap *h, int i) { - return 2 * i + 1; - } - - /* 获取右子节点索引 */ - int right(maxHeap *h, int i) { - return 2 * i + 2; - } - - /* 获取父节点索引 */ - int parent(maxHeap *h, int i) { - return (i - 1) / 2; - } - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - /* 获取左子节点索引 */ - int left(int i) { - return 2 * i + 1; - } - - /* 获取右子节点索引 */ - int right(int i) { - return 2 * i + 2; - } - - /* 获取父节点索引 */ - int parent(int i) { - return (i - 1) / 2; // 向下整除 - } - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 获取左子节点索引 */ - 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" - // 获取左子节点索引 - fn left(i: usize) usize { - return 2 * i + 1; - } - - // 获取右子节点索引 - fn right(i: usize) usize { - return 2 * i + 2; - } - - // 获取父节点索引 - fn parent(i: usize) usize { - // return (i - 1) / 2; // 向下整除 - return @divFloor(i - 1, 2); - } - ``` - === "Dart" ```dart title="my_heap.dart" @@ -566,17 +527,55 @@ comments: true } ``` +=== "C" + + ```c title="my_heap.c" + /* 获取左子节点索引 */ + int left(maxHeap *h, int i) { + return 2 * i + 1; + } + + /* 获取右子节点索引 */ + int right(maxHeap *h, int i) { + return 2 * i + 2; + } + + /* 获取父节点索引 */ + int parent(maxHeap *h, int i) { + return (i - 1) / 2; + } + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 获取左子节点索引 + fn left(i: usize) usize { + return 2 * i + 1; + } + + // 获取右子节点索引 + fn right(i: usize) usize { + return 2 * i + 2; + } + + // 获取父节点索引 + fn parent(i: usize) usize { + // return (i - 1) / 2; // 向下整除 + return @divFloor(i - 1, 2); + } + ``` + ### 2.   访问堆顶元素 堆顶元素即为二叉树的根节点,也就是列表的首个元素。 -=== "Java" +=== "Python" - ```java title="my_heap.java" - /* 访问堆顶元素 */ - int peek() { - return maxHeap.get(0); - } + ```python title="my_heap.py" + def peek(self) -> int: + """访问堆顶元素""" + return self.max_heap[0] ``` === "C++" @@ -588,12 +587,22 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="my_heap.py" - def peek(self) -> int: - """访问堆顶元素""" - return self.max_heap[0] + ```java title="my_heap.java" + /* 访问堆顶元素 */ + int peek() { + return maxHeap.get(0); + } + ``` + +=== "C#" + + ```csharp title="my_heap.cs" + /* 访问堆顶元素 */ + int peek() { + return maxHeap[0]; + } ``` === "Go" @@ -605,6 +614,15 @@ comments: true } ``` +=== "Swift" + + ```swift title="my_heap.swift" + /* 访问堆顶元素 */ + func peek() -> Int { + maxHeap[0] + } + ``` + === "JS" ```javascript title="my_heap.js" @@ -623,42 +641,6 @@ comments: true } ``` -=== "C" - - ```c title="my_heap.c" - /* 访问堆顶元素 */ - int peek(maxHeap *h) { - return h->data[0]; - } - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - /* 访问堆顶元素 */ - int peek() { - return maxHeap[0]; - } - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 访问堆顶元素 */ - func peek() -> Int { - maxHeap[0] - } - ``` - -=== "Zig" - - ```zig title="my_heap.zig" - // 访问堆顶元素 - fn peek(self: *Self) T { - return self.max_heap.?.items[0]; - } - ``` - === "Dart" ```dart title="my_heap.dart" @@ -677,6 +659,24 @@ comments: true } ``` +=== "C" + + ```c title="my_heap.c" + /* 访问堆顶元素 */ + int peek(maxHeap *h) { + return h->data[0]; + } + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 访问堆顶元素 + fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + ``` + ### 3.   元素入堆 给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 val 可能大于堆中其他元素,堆的成立条件可能已被破坏。因此,**需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为「堆化 heapify」。 @@ -714,31 +714,28 @@ comments: true 设节点总数为 $n$ ,则树的高度为 $O(\log n)$ 。由此可知,堆化操作的循环轮数最多为 $O(\log n)$ ,**元素入堆操作的时间复杂度为 $O(\log n)$** 。 -=== "Java" +=== "Python" - ```java title="my_heap.java" - /* 元素入堆 */ - void push(int val) { - // 添加节点 - maxHeap.add(val); - // 从底至顶堆化 - siftUp(size() - 1); - } + ```python title="my_heap.py" + def push(self, val: int): + """元素入堆""" + # 添加节点 + self.max_heap.append(val) + # 从底至顶堆化 + self.sift_up(self.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; - } - } + def sift_up(self, i: int): + """从节点 i 开始,从底至顶堆化""" + while True: + # 获取节点 i 的父节点 + p = self.parent(i) + # 当“越过根节点”或“节点无须修复”时,结束堆化 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 交换两节点 + self.swap(i, p) + # 循环向上堆化 + i = p ``` === "C++" @@ -768,28 +765,58 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="my_heap.py" - def push(self, val: int): - """元素入堆""" - # 添加节点 - self.max_heap.append(val) - # 从底至顶堆化 - self.sift_up(self.size() - 1) + ```java title="my_heap.java" + /* 元素入堆 */ + void push(int val) { + // 添加节点 + maxHeap.add(val); + // 从底至顶堆化 + siftUp(size() - 1); + } - def sift_up(self, i: int): - """从节点 i 开始,从底至顶堆化""" - while True: - # 获取节点 i 的父节点 - p = self.parent(i) - # 当“越过根节点”或“节点无须修复”时,结束堆化 - if p < 0 or self.max_heap[i] <= self.max_heap[p]: - break - # 交换两节点 - self.swap(i, p) - # 循环向上堆化 - i = p + /* 从节点 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#" + + ```csharp title="my_heap.cs" + /* 元素入堆 */ + 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[i] <= maxHeap[p]) + break; + // 交换两节点 + swap(i, p); + // 循环向上堆化 + i = p; + } + } ``` === "Go" @@ -820,6 +847,35 @@ comments: true } ``` +=== "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 + } + } + ``` + === "JS" ```javascript title="my_heap.js" @@ -872,124 +928,6 @@ comments: true } ``` -=== "C" - - ```c title="my_heap.c" - /* 元素入堆 */ - void push(maxHeap *h, int val) { - // 默认情况下,不应该添加这么多节点 - if (h->size == MAX_SIZE) { - printf("heap is full!"); - return; - } - // 添加节点 - h->data[h->size] = val; - h->size++; - - // 从底至顶堆化 - siftUp(h, h->size - 1); - } - - /* 从节点 i 开始,从底至顶堆化 */ - void siftUp(maxHeap *h, int i) { - while (true) { - // 获取节点 i 的父节点 - int p = parent(h, i); - // 当“越过根节点”或“节点无须修复”时,结束堆化 - if (p < 0 || h->data[i] <= h->data[p]) { - break; - } - // 交换两节点 - swap(h, i, p); - // 循环向上堆化 - i = p; - } - } - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - /* 元素入堆 */ - 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[i] <= maxHeap[p]) - break; - // 交换两节点 - swap(i, p); - // 循环向上堆化 - i = p; - } - } - ``` - -=== "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" - // 元素入堆 - fn push(self: *Self, val: T) !void { - // 添加节点 - try self.max_heap.?.append(val); - // 从底至顶堆化 - try self.siftUp(self.size() - 1); - } - - // 从节点 i 开始,从底至顶堆化 - fn siftUp(self: *Self, i_: usize) !void { - var i = i_; - while (true) { - // 获取节点 i 的父节点 - var p = parent(i); - // 当“越过根节点”或“节点无须修复”时,结束堆化 - if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; - // 交换两节点 - try self.swap(i, p); - // 循环向上堆化 - i = p; - } - } - ``` - === "Dart" ```dart title="my_heap.dart" @@ -1050,6 +988,68 @@ comments: true } ``` +=== "C" + + ```c title="my_heap.c" + /* 元素入堆 */ + void push(maxHeap *h, int val) { + // 默认情况下,不应该添加这么多节点 + if (h->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // 添加节点 + h->data[h->size] = val; + h->size++; + + // 从底至顶堆化 + siftUp(h, h->size - 1); + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(maxHeap *h, int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(h, i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || h->data[i] <= h->data[p]) { + break; + } + // 交换两节点 + swap(h, i, p); + // 循环向上堆化 + i = p; + } + } + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 元素入堆 + fn push(self: *Self, val: T) !void { + // 添加节点 + try self.max_heap.?.append(val); + // 从底至顶堆化 + try self.siftUp(self.size() - 1); + } + + // 从节点 i 开始,从底至顶堆化 + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 获取节点 i 的父节点 + var p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // 交换两节点 + try self.swap(i, p); + // 循环向上堆化 + i = p; + } + } + ``` + ### 4.   堆顶元素出堆 堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化修复变得困难。为了尽量减少元素索引的变动,我们采用以下操作步骤。 @@ -1094,42 +1094,39 @@ comments: true 与元素入堆操作相似,堆顶元素出堆操作的时间复杂度也为 $O(\log n)$ 。 -=== "Java" +=== "Python" - ```java title="my_heap.java" - /* 元素出堆 */ - int pop() { - // 判空处理 - if (isEmpty()) - throw new IndexOutOfBoundsException(); - // 交换根节点与最右叶节点(即交换首元素与尾元素) - swap(0, size() - 1); - // 删除节点 - int val = maxHeap.remove(size() - 1); - // 从顶至底堆化 - siftDown(0); - // 返回堆顶元素 - return val; - } + ```python title="my_heap.py" + def pop(self) -> int: + """元素出堆""" + # 判空处理 + if self.is_empty(): + raise IndexError("堆为空") + # 交换根节点与最右叶节点(即交换首元素与尾元素) + self.swap(0, self.size() - 1) + # 删除节点 + val = self.max_heap.pop() + # 从顶至底堆化 + self.sift_down(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; - } - } + def sift_down(self, i: int): + """从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l, r, ma = self.left(i), self.right(i), i + if l < self.size() and self.max_heap[l] > self.max_heap[ma]: + ma = l + if r < self.size() and self.max_heap[r] > self.max_heap[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + self.swap(i, ma) + # 循环向下堆化 + i = ma ``` === "C++" @@ -1169,39 +1166,80 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="my_heap.py" - def pop(self) -> int: - """元素出堆""" - # 判空处理 - if self.is_empty(): - raise IndexError("堆为空") - # 交换根节点与最右叶节点(即交换首元素与尾元素) - self.swap(0, self.size() - 1) - # 删除节点 - val = self.max_heap.pop() - # 从顶至底堆化 - self.sift_down(0) - # 返回堆顶元素 - return val + ```java title="my_heap.java" + /* 元素出堆 */ + int pop() { + // 判空处理 + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 交换根节点与最右叶节点(即交换首元素与尾元素) + swap(0, size() - 1); + // 删除节点 + int val = maxHeap.remove(size() - 1); + // 从顶至底堆化 + siftDown(0); + // 返回堆顶元素 + return val; + } - def sift_down(self, i: int): - """从节点 i 开始,从顶至底堆化""" - while True: - # 判断节点 i, l, r 中值最大的节点,记为 ma - l, r, ma = self.left(i), self.right(i), i - if l < self.size() and self.max_heap[l] > self.max_heap[ma]: - ma = l - if r < self.size() and self.max_heap[r] > self.max_heap[ma]: - ma = r - # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if ma == i: - break - # 交换两节点 - self.swap(i, ma) - # 循环向下堆化 - i = ma + /* 从节点 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#" + + ```csharp title="my_heap.cs" + /* 元素出堆 */ + int pop() { + // 判空处理 + if (isEmpty()) + throw new IndexOutOfRangeException(); + // 交换根节点与最右叶节点(即交换首元素与尾元素) + swap(0, size() - 1); + // 删除节点 + int val = maxHeap.Last(); + maxHeap.RemoveAt(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[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若“节点 i 最大”或“越过叶节点”,则结束堆化 + if (ma == i) break; + // 交换两节点 + swap(i, ma); + // 循环向下堆化 + i = ma; + } + } ``` === "Go" @@ -1249,6 +1287,51 @@ comments: true } ``` +=== "Swift" + + ```swift title="my_heap.swift" + /* 元素出堆 */ + func pop() -> 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 + } + } + ``` + === "JS" ```javascript title="my_heap.js" @@ -1321,173 +1404,6 @@ comments: true } ``` -=== "C" - - ```c title="my_heap.c" - /* 元素出堆 */ - int pop(maxHeap *h) { - // 判空处理 - if (isEmpty(h)) { - printf("heap is empty!"); - return INT_MAX; - } - // 交换根节点与最右叶节点(即交换首元素与尾元素) - swap(h, 0, size(h) - 1); - // 删除节点 - int val = h->data[h->size - 1]; - h->size--; - // 从顶至底堆化 - siftDown(h, 0); - - // 返回堆顶元素 - return val; - } - - /* 从节点 i 开始,从顶至底堆化 */ - void siftDown(maxHeap *h, int i) { - while (true) { - // 判断节点 i, l, r 中值最大的节点,记为 max - int l = left(h, i); - int r = right(h, i); - int max = i; - if (l < size(h) && h->data[l] > h->data[max]) { - max = l; - } - if (r < size(h) && h->data[r] > h->data[max]) { - max = r; - } - // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if (max == i) { - break; - } - // 交换两节点 - swap(h, i, max); - // 循环向下堆化 - i = max; - } - } - ``` - -=== "C#" - - ```csharp title="my_heap.cs" - /* 元素出堆 */ - int pop() { - // 判空处理 - if (isEmpty()) - throw new IndexOutOfRangeException(); - // 交换根节点与最右叶节点(即交换首元素与尾元素) - swap(0, size() - 1); - // 删除节点 - int val = maxHeap.Last(); - maxHeap.RemoveAt(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[l] > maxHeap[ma]) - ma = l; - if (r < size() && maxHeap[r] > maxHeap[ma]) - ma = r; - // 若“节点 i 最大”或“越过叶节点”,则结束堆化 - if (ma == i) break; - // 交换两节点 - swap(i, ma); - // 循环向下堆化 - i = ma; - } - } - ``` - -=== "Swift" - - ```swift title="my_heap.swift" - /* 元素出堆 */ - func pop() -> 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" - // 元素出堆 - fn pop(self: *Self) !T { - // 判断处理 - if (self.isEmpty()) unreachable; - // 交换根节点与最右叶节点(即交换首元素与尾元素) - try self.swap(0, self.size() - 1); - // 删除节点 - var val = self.max_heap.?.pop(); - // 从顶至底堆化 - try self.siftDown(0); - // 返回堆顶元素 - return val; - } - - // 从节点 i 开始,从顶至底堆化 - fn siftDown(self: *Self, i_: usize) !void { - var i = i_; - while (true) { - // 判断节点 i, l, r 中值最大的节点,记为 ma - var l = left(i); - var r = right(i); - var ma = i; - if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; - if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; - // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if (ma == i) break; - // 交换两节点 - try self.swap(i, ma); - // 循环向下堆化 - i = ma; - } - } - ``` - === "Dart" ```dart title="my_heap.dart" @@ -1566,6 +1482,90 @@ comments: true } ``` +=== "C" + + ```c title="my_heap.c" + /* 元素出堆 */ + int pop(maxHeap *h) { + // 判空处理 + if (isEmpty(h)) { + printf("heap is empty!"); + return INT_MAX; + } + // 交换根节点与最右叶节点(即交换首元素与尾元素) + swap(h, 0, size(h) - 1); + // 删除节点 + int val = h->data[h->size - 1]; + h->size--; + // 从顶至底堆化 + siftDown(h, 0); + + // 返回堆顶元素 + return val; + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(maxHeap *h, int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 max + int l = left(h, i); + int r = right(h, i); + int max = i; + if (l < size(h) && h->data[l] > h->data[max]) { + max = l; + } + if (r < size(h) && h->data[r] > h->data[max]) { + max = r; + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (max == i) { + break; + } + // 交换两节点 + swap(h, i, max); + // 循环向下堆化 + i = max; + } + } + ``` + +=== "Zig" + + ```zig title="my_heap.zig" + // 元素出堆 + fn pop(self: *Self) !T { + // 判断处理 + if (self.isEmpty()) unreachable; + // 交换根节点与最右叶节点(即交换首元素与尾元素) + try self.swap(0, self.size() - 1); + // 删除节点 + var val = self.max_heap.?.pop(); + // 从顶至底堆化 + try self.siftDown(0); + // 返回堆顶元素 + return val; + } + + // 从节点 i 开始,从顶至底堆化 + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) break; + // 交换两节点 + try self.swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + ``` + ## 8.1.3   堆常见应用 - **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建队操作为 $O(n)$ ,这些操作都非常高效。 diff --git a/chapter_heap/top_k.md b/chapter_heap/top_k.md index 24bba9f2c..8de4a252e 100644 --- a/chapter_heap/top_k.md +++ b/chapter_heap/top_k.md @@ -76,26 +76,22 @@ comments: true 另外,该方法适用于动态数据流的使用场景。在不断加入数据时,我们可以持续维护堆内的元素,从而实现最大 $k$ 个元素的动态更新。 -=== "Java" +=== "Python" - ```java title="top_k.java" - /* 基于堆查找数组中最大的 k 个元素 */ - Queue topKHeap(int[] nums, int k) { - Queue heap = new PriorityQueue(); - // 将数组的前 k 个元素入堆 - for (int i = 0; i < k; i++) { - heap.offer(nums[i]); - } - // 从第 k+1 个元素开始,保持堆的长度为 k - for (int i = k; i < nums.length; i++) { - // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 - if (nums[i] > heap.peek()) { - heap.poll(); - heap.offer(nums[i]); - } - } - return heap; - } + ```python title="top_k.py" + def top_k_heap(nums: list[int], k: int) -> list[int]: + """基于堆查找数组中最大的 k 个元素""" + heap = [] + # 将数组的前 k 个元素入堆 + for i in range(k): + heapq.heappush(heap, nums[i]) + # 从第 k+1 个元素开始,保持堆的长度为 k + for i in range(k, len(nums)): + # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if nums[i] > heap[0]: + heapq.heappop(heap) + heapq.heappush(heap, nums[i]) + return heap ``` === "C++" @@ -120,22 +116,48 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="top_k.py" - def top_k_heap(nums: list[int], k: int) -> list[int]: - """基于堆查找数组中最大的 k 个元素""" - heap = [] - # 将数组的前 k 个元素入堆 - for i in range(k): - heapq.heappush(heap, nums[i]) - # 从第 k+1 个元素开始,保持堆的长度为 k - for i in range(k, len(nums)): - # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 - if nums[i] > heap[0]: - heapq.heappop(heap) - heapq.heappush(heap, nums[i]) - return heap + ```java title="top_k.java" + /* 基于堆查找数组中最大的 k 个元素 */ + Queue topKHeap(int[] nums, int k) { + Queue heap = new PriorityQueue(); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + ``` + +=== "C#" + + ```csharp title="top_k.cs" + /* 基于堆查找数组中最大的 k 个元素 */ + PriorityQueue topKHeap(int[] nums, int k) { + PriorityQueue heap = new PriorityQueue(); + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.Length; i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } ``` === "Go" @@ -161,46 +183,6 @@ comments: true } ``` -=== "JS" - - ```javascript title="top_k.js" - [class]{}-[func]{topKHeap} - ``` - -=== "TS" - - ```typescript title="top_k.ts" - [class]{}-[func]{topKHeap} - ``` - -=== "C" - - ```c title="top_k.c" - [class]{}-[func]{topKHeap} - ``` - -=== "C#" - - ```csharp title="top_k.cs" - /* 基于堆查找数组中最大的 k 个元素 */ - PriorityQueue topKHeap(int[] nums, int k) { - PriorityQueue heap = new PriorityQueue(); - // 将数组的前 k 个元素入堆 - for (int i = 0; i < k; i++) { - heap.Enqueue(nums[i], nums[i]); - } - // 从第 k+1 个元素开始,保持堆的长度为 k - for (int i = k; i < nums.Length; i++) { - // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 - if (nums[i] > heap.Peek()) { - heap.Dequeue(); - heap.Enqueue(nums[i], nums[i]); - } - } - return heap; - } - ``` - === "Swift" ```swift title="top_k.swift" @@ -220,9 +202,15 @@ comments: true } ``` -=== "Zig" +=== "JS" - ```zig title="top_k.zig" + ```javascript title="top_k.js" + [class]{}-[func]{topKHeap} + ``` + +=== "TS" + + ```typescript title="top_k.ts" [class]{}-[func]{topKHeap} ``` @@ -267,3 +255,15 @@ comments: true heap } ``` + +=== "C" + + ```c title="top_k.c" + [class]{}-[func]{topKHeap} + ``` + +=== "Zig" + + ```zig title="top_k.zig" + [class]{}-[func]{topKHeap} + ``` diff --git a/chapter_preface/suggestions.md b/chapter_preface/suggestions.md index ab9cc638b..b23e77738 100644 --- a/chapter_preface/suggestions.md +++ b/chapter_preface/suggestions.md @@ -17,17 +17,17 @@ comments: true - 当涉及到编程语言之间不一致的名词时,本书均以 Python 为准,例如使用 $\text{None}$ 来表示“空”。 - 本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 -=== "Java" +=== "Python" - ```java title="" - /* 标题注释,用于标注函数、类、测试样例等 */ + ```python title="" + """标题注释,用于标注函数、类、测试样例等""" - // 内容注释,用于详解代码 + # 内容注释,用于详解代码 - /** - * 多行 - * 注释 - */ + """ + 多行 + 注释 + """ ``` === "C++" @@ -43,17 +43,30 @@ comments: true */ ``` -=== "Python" +=== "Java" - ```python title="" - """标题注释,用于标注函数、类、测试样例等""" + ```java title="" + /* 标题注释,用于标注函数、类、测试样例等 */ - # 内容注释,用于详解代码 + // 内容注释,用于详解代码 - """ - 多行 - 注释 - """ + /** + * 多行 + * 注释 + */ + ``` + +=== "C#" + + ```csharp title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ ``` === "Go" @@ -69,6 +82,19 @@ comments: true */ ``` +=== "Swift" + + ```swift title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + === "JS" ```javascript title="" @@ -95,6 +121,25 @@ comments: true */ ``` +=== "Dart" + + ```dart title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Rust" + + ```rust title="" + + ``` + === "C" ```c title="" @@ -108,32 +153,6 @@ comments: true */ ``` -=== "C#" - - ```csharp title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Swift" - - ```swift title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - === "Zig" ```zig title="" @@ -145,25 +164,6 @@ comments: true // 注释 ``` -=== "Dart" - - ```dart title="" - /* 标题注释,用于标注函数、类、测试样例等 */ - - // 内容注释,用于详解代码 - - /** - * 多行 - * 注释 - */ - ``` - -=== "Rust" - - ```rust title="" - - ``` - ## 0.2.2   在动画图解中高效学习 相较于文字,视频和图片具有更高的信息密度和结构化程度,更易于理解。在本书中,**重点和难点知识将主要通过动画和图解形式展示**,而文字则作为动画和图片的解释与补充。 diff --git a/chapter_searching/binary_search.md b/chapter_searching/binary_search.md index 6f64d8ec9..a41018976 100755 --- a/chapter_searching/binary_search.md +++ b/chapter_searching/binary_search.md @@ -51,26 +51,24 @@ comments: true 值得注意的是,由于 $i$ 和 $j$ 都是 `int` 类型,**因此 $i + j$ 可能会超出 `int` 类型的取值范围**。为了避免大数越界,我们通常采用公式 $m = \lfloor {i + (j - i) / 2} \rfloor$ 来计算中点。 -=== "Java" +=== "Python" - ```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 - 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; - } - // 未找到目标元素,返回 -1 - return -1; - } + ```python title="binary_search.py" + def binary_search(nums: list[int], target: int) -> int: + """二分查找(双闭区间)""" + # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + i, j = 0, len(nums) - 1 + # 循环,当搜索区间为空时跳出(当 i > j 时为空) + while i <= j: + # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 + else: + return m # 找到目标元素,返回其索引 + return -1 # 未找到目标元素,返回 -1 ``` === "C++" @@ -95,24 +93,48 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_search.py" - def binary_search(nums: list[int], target: int) -> int: - """二分查找(双闭区间)""" - # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - i, j = 0, len(nums) - 1 - # 循环,当搜索区间为空时跳出(当 i > j 时为空) - while i <= j: - # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: - i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 - elif nums[m] > target: - j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 - else: - return m # 找到目标元素,返回其索引 - return -1 # 未找到目标元素,返回 -1 + ```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 - 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; + } + // 未找到目标元素,返回 -1 + return -1; + } + ``` + +=== "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 - 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; + } + // 未找到目标元素,返回 -1 + return -1; + } ``` === "Go" @@ -138,6 +160,30 @@ comments: true } ``` +=== "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 - 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 + } + } + // 未找到目标元素,返回 -1 + return -1 + } + ``` + === "JS" ```javascript title="binary_search.js" @@ -190,98 +236,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_search.c" - /* 二分查找(双闭区间) */ - int binarySearch(int *nums, int len, int target) { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - int i = 0, j = len - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - int m = 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; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "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 - 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; - } - // 未找到目标元素,返回 -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 - 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 - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "Zig" - - ```zig title="binary_search.zig" - // 二分查找(双闭区间) - fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { - // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 - var i: usize = 0; - var j: usize = nums.items.len - 1; - // 循环,当搜索区间为空时跳出(当 i > j 时为空) - while (i <= j) { - var m = i + (j - i) / 2; // 计算中点索引 m - if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 - i = m + 1; - } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 - j = m - 1; - } else { // 找到目标元素,返回其索引 - return @intCast(m); - } - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - === "Dart" ```dart title="binary_search.dart" @@ -332,6 +286,52 @@ comments: true } ``` +=== "C" + + ```c title="binary_search.c" + /* 二分查找(双闭区间) */ + int binarySearch(int *nums, int len, int target) { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = len - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) { + int m = 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; + } + // 未找到目标元素,返回 -1 + return -1; + } + ``` + +=== "Zig" + + ```zig title="binary_search.zig" + // 二分查找(双闭区间) + fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + var i: usize = 0; + var j: usize = nums.items.len - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) { + var m = i + (j - i) / 2; // 计算中点索引 m + if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + } else { // 找到目标元素,返回其索引 + return @intCast(m); + } + } + // 未找到目标元素,返回 -1 + return -1; + } + ``` + **时间复杂度 $O(\log n)$** :在二分循环中,区间每轮缩小一半,循环次数为 $\log_2 n$ 。 **空间复杂度 $O(1)$** :指针 $i$ 和 $j$ 使用常数大小空间。 @@ -342,26 +342,23 @@ comments: true 我们可以基于该表示实现具有相同功能的二分查找算法。 -=== "Java" +=== "Python" - ```java title="binary_search.java" - /* 二分查找(左闭右开) */ - int binarySearchLCRO(int[] nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = 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; - } - // 未找到目标元素,返回 -1 - return -1; - } + ```python title="binary_search.py" + def binary_search_lcro(nums: list[int], target: int) -> int: + """二分查找(左闭右开)""" + # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + i, j = 0, len(nums) + # 循环,当搜索区间为空时跳出(当 i = j 时为空) + while i < j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 + elif nums[m] > target: + j = m # 此情况说明 target 在区间 [i, m) 中 + else: + return m # 找到目标元素,返回其索引 + return -1 # 未找到目标元素,返回 -1 ``` === "C++" @@ -386,23 +383,48 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_search.py" - def binary_search_lcro(nums: list[int], target: int) -> int: - """二分查找(左闭右开)""" - # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - i, j = 0, len(nums) - # 循环,当搜索区间为空时跳出(当 i = j 时为空) - while i < j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: - i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 - elif nums[m] > target: - j = m # 此情况说明 target 在区间 [i, m) 中 - else: - return m # 找到目标元素,返回其索引 - return -1 # 未找到目标元素,返回 -1 + ```java title="binary_search.java" + /* 二分查找(左闭右开) */ + int binarySearchLCRO(int[] nums, int target) { + // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.length; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) { + int m = 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; + } + // 未找到目标元素,返回 -1 + return -1; + } + ``` + +=== "C#" + + ```csharp title="binary_search.cs" + /* 二分查找(左闭右开) */ + int binarySearchLCRO(int[] nums, int target) { + // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.Length; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) { + int m = 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; + } + // 未找到目标元素,返回 -1 + return -1; + } ``` === "Go" @@ -428,6 +450,30 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search.swift" + /* 二分查找(左闭右开) */ + func binarySearchLCRO(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 - 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 + } + } + // 未找到目标元素,返回 -1 + return -1 + } + ``` + === "JS" ```javascript title="binary_search.js" @@ -481,98 +527,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_search.c" - /* 二分查找(左闭右开) */ - int binarySearchLCRO(int *nums, int len, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = len; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = 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; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "C#" - - ```csharp title="binary_search.cs" - /* 二分查找(左闭右开) */ - int binarySearchLCRO(int[] nums, int target) { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - int i = 0, j = nums.Length; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i < j) { - int m = 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; - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - -=== "Swift" - - ```swift title="binary_search.swift" - /* 二分查找(左闭右开) */ - func binarySearchLCRO(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 - 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 - } - } - // 未找到目标元素,返回 -1 - return -1 - } - ``` - -=== "Zig" - - ```zig title="binary_search.zig" - // 二分查找(左闭右开) - fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { - // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 - var i: usize = 0; - var j: usize = nums.items.len; - // 循环,当搜索区间为空时跳出(当 i = j 时为空) - while (i <= j) { - var m = i + (j - i) / 2; // 计算中点索引 m - if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 - i = m + 1; - } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m) 中 - j = m; - } else { // 找到目标元素,返回其索引 - return @intCast(m); - } - } - // 未找到目标元素,返回 -1 - return -1; - } - ``` - === "Dart" ```dart title="binary_search.dart" @@ -623,6 +577,52 @@ comments: true } ``` +=== "C" + + ```c title="binary_search.c" + /* 二分查找(左闭右开) */ + int binarySearchLCRO(int *nums, int len, int target) { + // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = len; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) { + int m = 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; + } + // 未找到目标元素,返回 -1 + return -1; + } + ``` + +=== "Zig" + + ```zig title="binary_search.zig" + // 二分查找(左闭右开) + fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + var i: usize = 0; + var j: usize = nums.items.len; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i <= j) { + var m = i + (j - i) / 2; // 计算中点索引 m + if (nums.items[m] < target) { // 此情况说明 target 在区间 [m+1, j) 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情况说明 target 在区间 [i, m) 中 + j = m; + } else { // 找到目标元素,返回其索引 + return @intCast(m); + } + } + // 未找到目标元素,返回 -1 + return -1; + } + ``` + 如图 10-3 所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。 由于“双闭区间”表示中的左右边界都被定义为闭区间,因此指针 $i$ 和 $j$ 缩小区间操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。 diff --git a/chapter_searching/binary_search_edge.md b/chapter_searching/binary_search_edge.md index 5ada581ae..ad5d3665f 100644 --- a/chapter_searching/binary_search_edge.md +++ b/chapter_searching/binary_search_edge.md @@ -20,20 +20,18 @@ status: new 当遇到以上两种情况时,直接返回 $-1$ 即可。 -=== "Java" +=== "Python" - ```java title="binary_search_edge.java" - /* 二分查找最左一个 target */ - int binarySearchLeftEdge(int[] nums, int target) { - // 等价于查找 target 的插入点 - int i = binary_search_insertion.binarySearchInsertion(nums, target); - // 未找到 target ,返回 -1 - if (i == nums.length || nums[i] != target) { - return -1; - } - // 找到 target ,返回索引 i - return i; - } + ```python title="binary_search_edge.py" + def binary_search_left_edge(nums: list[int], target: int) -> int: + """二分查找最左一个 target""" + # 等价于查找 target 的插入点 + i = binary_search_insertion(nums, target) + # 未找到 target ,返回 -1 + if i == len(nums) or nums[i] != target: + return -1 + # 找到 target ,返回索引 i + return i ``` === "C++" @@ -52,18 +50,36 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="binary_search_edge.py" - def binary_search_left_edge(nums: list[int], target: int) -> int: - """二分查找最左一个 target""" - # 等价于查找 target 的插入点 - i = binary_search_insertion(nums, target) - # 未找到 target ,返回 -1 - if i == len(nums) or nums[i] != target: - return -1 - # 找到 target ,返回索引 i - return i + ```java title="binary_search_edge.java" + /* 二分查找最左一个 target */ + int binarySearchLeftEdge(int[] nums, int target) { + // 等价于查找 target 的插入点 + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + ``` + +=== "C#" + + ```csharp title="binary_search_edge.cs" + /* 二分查找最左一个 target */ + int binarySearchLeftEdge(int[] nums, int target) { + // 等价于查找 target 的插入点 + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.Length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } ``` === "Go" @@ -82,6 +98,22 @@ status: new } ``` +=== "Swift" + + ```swift title="binary_search_edge.swift" + /* 二分查找最左一个 target */ + func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // 等价于查找 target 的插入点 + let i = binarySearchInsertion(nums: nums, target: target) + // 未找到 target ,返回 -1 + if i == nums.count || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i + } + ``` + === "JS" ```javascript title="binary_search_edge.js" @@ -114,50 +146,6 @@ status: new } ``` -=== "C" - - ```c title="binary_search_edge.c" - [class]{}-[func]{binarySearchLeftEdge} - ``` - -=== "C#" - - ```csharp title="binary_search_edge.cs" - /* 二分查找最左一个 target */ - int binarySearchLeftEdge(int[] nums, int target) { - // 等价于查找 target 的插入点 - int i = binary_search_insertion.binarySearchInsertion(nums, target); - // 未找到 target ,返回 -1 - if (i == nums.Length || nums[i] != target) { - return -1; - } - // 找到 target ,返回索引 i - return i; - } - ``` - -=== "Swift" - - ```swift title="binary_search_edge.swift" - /* 二分查找最左一个 target */ - func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { - // 等价于查找 target 的插入点 - let i = binarySearchInsertion(nums: nums, target: target) - // 未找到 target ,返回 -1 - if i == nums.count || nums[i] != target { - return -1 - } - // 找到 target ,返回索引 i - return i - } - ``` - -=== "Zig" - - ```zig title="binary_search_edge.zig" - [class]{}-[func]{binarySearchLeftEdge} - ``` - === "Dart" ```dart title="binary_search_edge.dart" @@ -190,6 +178,18 @@ status: new } ``` +=== "C" + + ```c title="binary_search_edge.c" + [class]{}-[func]{binarySearchLeftEdge} + ``` + +=== "Zig" + + ```zig title="binary_search_edge.zig" + [class]{}-[func]{binarySearchLeftEdge} + ``` + ## 10.3.2   查找右边界 那么如何查找最右一个 `target` 呢?最直接的方式是修改代码,替换在 `nums[m] == target` 情况下的指针收缩操作。代码在此省略,有兴趣的同学可以自行实现。 @@ -208,22 +208,20 @@ status: new 请注意,返回的插入点是 $i$ ,因此需要将其减 $1$ ,从而获得 $j$ 。 -=== "Java" +=== "Python" - ```java title="binary_search_edge.java" - /* 二分查找最右一个 target */ - int binarySearchRightEdge(int[] nums, int target) { - // 转化为查找最左一个 target + 1 - int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); - // j 指向最右一个 target ,i 指向首个大于 target 的元素 - int j = i - 1; - // 未找到 target ,返回 -1 - if (j == -1 || nums[j] != target) { - return -1; - } - // 找到 target ,返回索引 j - return j; - } + ```python title="binary_search_edge.py" + def binary_search_right_edge(nums: list[int], target: int) -> int: + """二分查找最右一个 target""" + # 转化为查找最左一个 target + 1 + i = binary_search_insertion(nums, target + 1) + # j 指向最右一个 target ,i 指向首个大于 target 的元素 + j = i - 1 + # 未找到 target ,返回 -1 + if j == -1 or nums[j] != target: + return -1 + # 找到 target ,返回索引 j + return j ``` === "C++" @@ -244,20 +242,40 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="binary_search_edge.py" - def binary_search_right_edge(nums: list[int], target: int) -> int: - """二分查找最右一个 target""" - # 转化为查找最左一个 target + 1 - i = binary_search_insertion(nums, target + 1) - # j 指向最右一个 target ,i 指向首个大于 target 的元素 - j = i - 1 - # 未找到 target ,返回 -1 - if j == -1 or nums[j] != target: - return -1 - # 找到 target ,返回索引 j - return j + ```java title="binary_search_edge.java" + /* 二分查找最右一个 target */ + int binarySearchRightEdge(int[] nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + ``` + +=== "C#" + + ```csharp title="binary_search_edge.cs" + /* 二分查找最右一个 target */ + int binarySearchRightEdge(int[] nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } ``` === "Go" @@ -278,6 +296,24 @@ status: new } ``` +=== "Swift" + + ```swift title="binary_search_edge.swift" + /* 二分查找最右一个 target */ + func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // 转化为查找最左一个 target + 1 + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + let j = i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j + } + ``` + === "JS" ```javascript title="binary_search_edge.js" @@ -314,54 +350,6 @@ status: new } ``` -=== "C" - - ```c title="binary_search_edge.c" - [class]{}-[func]{binarySearchRightEdge} - ``` - -=== "C#" - - ```csharp title="binary_search_edge.cs" - /* 二分查找最右一个 target */ - int binarySearchRightEdge(int[] nums, int target) { - // 转化为查找最左一个 target + 1 - int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); - // j 指向最右一个 target ,i 指向首个大于 target 的元素 - int j = i - 1; - // 未找到 target ,返回 -1 - if (j == -1 || nums[j] != target) { - return -1; - } - // 找到 target ,返回索引 j - return j; - } - ``` - -=== "Swift" - - ```swift title="binary_search_edge.swift" - /* 二分查找最右一个 target */ - func binarySearchRightEdge(nums: [Int], target: Int) -> Int { - // 转化为查找最左一个 target + 1 - let i = binarySearchInsertion(nums: nums, target: target + 1) - // j 指向最右一个 target ,i 指向首个大于 target 的元素 - let j = i - 1 - // 未找到 target ,返回 -1 - if j == -1 || nums[j] != target { - return -1 - } - // 找到 target ,返回索引 j - return j - } - ``` - -=== "Zig" - - ```zig title="binary_search_edge.zig" - [class]{}-[func]{binarySearchRightEdge} - ``` - === "Dart" ```dart title="binary_search_edge.dart" @@ -398,6 +386,18 @@ status: new } ``` +=== "C" + + ```c title="binary_search_edge.c" + [class]{}-[func]{binarySearchRightEdge} + ``` + +=== "Zig" + + ```zig title="binary_search_edge.zig" + [class]{}-[func]{binarySearchRightEdge} + ``` + ### 2.   转化为查找元素 我们知道,当数组不包含 `target` 时,最终 $i$ 和 $j$ 会分别指向首个大于、小于 `target` 的元素。 diff --git a/chapter_searching/binary_search_insertion.md b/chapter_searching/binary_search_insertion.md index e782637d6..a13900787 100644 --- a/chapter_searching/binary_search_insertion.md +++ b/chapter_searching/binary_search_insertion.md @@ -29,25 +29,22 @@ status: new 因此二分结束时一定有:$i$ 指向首个大于 `target` 的元素,$j$ 指向首个小于 `target` 的元素。**易得当数组不包含 `target` 时,插入索引为 $i$** 。 -=== "Java" +=== "Python" - ```java title="binary_search_insertion.java" - /* 二分查找插入点(无重复元素) */ - int binarySearchInsertionSimple(int[] nums, int target) { - int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] - while (i <= j) { - int m = i + (j - i) / 2; // 计算中点索引 m - if (nums[m] < target) { - i = m + 1; // target 在区间 [m+1, j] 中 - } else if (nums[m] > target) { - j = m - 1; // target 在区间 [i, m-1] 中 - } else { - return m; // 找到 target ,返回插入点 m - } - } - // 未找到 target ,返回插入点 i - return i; - } + ```python title="binary_search_insertion.py" + def binary_search_insertion_simple(nums: list[int], target: int) -> int: + """二分查找插入点(无重复元素)""" + i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] + while i <= j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在区间 [i, m-1] 中 + else: + return m # 找到 target ,返回插入点 m + # 未找到 target ,返回插入点 i + return i ``` === "C++" @@ -71,22 +68,46 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="binary_search_insertion.py" - def binary_search_insertion_simple(nums: list[int], target: int) -> int: - """二分查找插入点(无重复元素)""" - i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] - while i <= j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: - i = m + 1 # target 在区间 [m+1, j] 中 - elif nums[m] > target: - j = m - 1 # target 在区间 [i, m-1] 中 - else: - return m # 找到 target ,返回插入点 m - # 未找到 target ,返回插入点 i - return i + ```java title="binary_search_insertion.java" + /* 二分查找插入点(无重复元素) */ + int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i; + } + ``` + +=== "C#" + + ```csharp title="binary_search_insertion.cs" + /* 二分查找插入点(无重复元素) */ + int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i; + } ``` === "Go" @@ -115,6 +136,27 @@ status: new } ``` +=== "Swift" + + ```swift title="binary_search_insertion.swift" + /* 二分查找插入点(无重复元素) */ + func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + var i = 0, j = nums.count - 1 // 初始化双闭区间 [0, n-1] + while i <= j { + let m = i + (j - i) / 2 // 计算中点索引 m + if nums[m] < target { + i = m + 1 // target 在区间 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在区间 [i, m-1] 中 + } else { + return m // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i + } + ``` + === "JS" ```javascript title="binary_search_insertion.js" @@ -162,60 +204,6 @@ status: new } ``` -=== "C" - - ```c title="binary_search_insertion.c" - [class]{}-[func]{binarySearchInsertionSimple} - ``` - -=== "C#" - - ```csharp title="binary_search_insertion.cs" - /* 二分查找插入点(无重复元素) */ - int binarySearchInsertionSimple(int[] nums, int target) { - int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] - while (i <= j) { - int m = i + (j - i) / 2; // 计算中点索引 m - if (nums[m] < target) { - i = m + 1; // target 在区间 [m+1, j] 中 - } else if (nums[m] > target) { - j = m - 1; // target 在区间 [i, m-1] 中 - } else { - return m; // 找到 target ,返回插入点 m - } - } - // 未找到 target ,返回插入点 i - return i; - } - ``` - -=== "Swift" - - ```swift title="binary_search_insertion.swift" - /* 二分查找插入点(无重复元素) */ - func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { - var i = 0, j = nums.count - 1 // 初始化双闭区间 [0, n-1] - while i <= j { - let m = i + (j - i) / 2 // 计算中点索引 m - if nums[m] < target { - i = m + 1 // target 在区间 [m+1, j] 中 - } else if nums[m] > target { - j = m - 1 // target 在区间 [i, m-1] 中 - } else { - return m // 找到 target ,返回插入点 m - } - } - // 未找到 target ,返回插入点 i - return i - } - ``` - -=== "Zig" - - ```zig title="binary_search_insertion.zig" - [class]{}-[func]{binarySearchInsertionSimple} - ``` - === "Dart" ```dart title="binary_search_insertion.dart" @@ -258,6 +246,18 @@ status: new } ``` +=== "C" + + ```c title="binary_search_insertion.c" + [class]{}-[func]{binarySearchInsertionSimple} + ``` + +=== "Zig" + + ```zig title="binary_search_insertion.zig" + [class]{}-[func]{binarySearchInsertionSimple} + ``` + ## 10.2.2   存在重复元素的情况 !!! question @@ -314,25 +314,22 @@ status: new 即便如此,我们仍然可以将判断条件保持展开,因为其逻辑更加清晰、可读性更好。 -=== "Java" +=== "Python" - ```java title="binary_search_insertion.java" - /* 二分查找插入点(存在重复元素) */ - int binarySearchInsertion(int[] nums, int target) { - int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] - while (i <= j) { - int m = i + (j - i) / 2; // 计算中点索引 m - if (nums[m] < target) { - i = m + 1; // target 在区间 [m+1, j] 中 - } else if (nums[m] > target) { - j = m - 1; // target 在区间 [i, m-1] 中 - } else { - j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 - } - } - // 返回插入点 i - return i; - } + ```python title="binary_search_insertion.py" + def binary_search_insertion(nums: list[int], target: int) -> int: + """二分查找插入点(存在重复元素)""" + i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] + while i <= j: + m = (i + j) // 2 # 计算中点索引 m + if nums[m] < target: + i = m + 1 # target 在区间 [m+1, j] 中 + elif nums[m] > target: + j = m - 1 # target 在区间 [i, m-1] 中 + else: + j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 + # 返回插入点 i + return i ``` === "C++" @@ -356,22 +353,46 @@ status: new } ``` -=== "Python" +=== "Java" - ```python title="binary_search_insertion.py" - def binary_search_insertion(nums: list[int], target: int) -> int: - """二分查找插入点(存在重复元素)""" - i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] - while i <= j: - m = (i + j) // 2 # 计算中点索引 m - if nums[m] < target: - i = m + 1 # target 在区间 [m+1, j] 中 - elif nums[m] > target: - j = m - 1 # target 在区间 [i, m-1] 中 - else: - j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 - # 返回插入点 i - return i + ```java title="binary_search_insertion.java" + /* 二分查找插入点(存在重复元素) */ + int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; + } + ``` + +=== "C#" + + ```csharp title="binary_search_insertion.cs" + /* 二分查找插入点(存在重复元素) */ + int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; + } ``` === "Go" @@ -400,6 +421,27 @@ status: new } ``` +=== "Swift" + + ```swift title="binary_search_insertion.swift" + /* 二分查找插入点(存在重复元素) */ + func binarySearchInsertion(nums: [Int], target: Int) -> Int { + var i = 0, j = nums.count - 1 // 初始化双闭区间 [0, n-1] + while i <= j { + let m = i + (j - i) / 2 // 计算中点索引 m + if nums[m] < target { + i = m + 1 // target 在区间 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在区间 [i, m-1] 中 + } else { + j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i + } + ``` + === "JS" ```javascript title="binary_search_insertion.js" @@ -444,60 +486,6 @@ status: new } ``` -=== "C" - - ```c title="binary_search_insertion.c" - [class]{}-[func]{binarySearchInsertion} - ``` - -=== "C#" - - ```csharp title="binary_search_insertion.cs" - /* 二分查找插入点(存在重复元素) */ - int binarySearchInsertion(int[] nums, int target) { - int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1] - while (i <= j) { - int m = i + (j - i) / 2; // 计算中点索引 m - if (nums[m] < target) { - i = m + 1; // target 在区间 [m+1, j] 中 - } else if (nums[m] > target) { - j = m - 1; // target 在区间 [i, m-1] 中 - } else { - j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 - } - } - // 返回插入点 i - return i; - } - ``` - -=== "Swift" - - ```swift title="binary_search_insertion.swift" - /* 二分查找插入点(存在重复元素) */ - func binarySearchInsertion(nums: [Int], target: Int) -> Int { - var i = 0, j = nums.count - 1 // 初始化双闭区间 [0, n-1] - while i <= j { - let m = i + (j - i) / 2 // 计算中点索引 m - if nums[m] < target { - i = m + 1 // target 在区间 [m+1, j] 中 - } else if nums[m] > target { - j = m - 1 // target 在区间 [i, m-1] 中 - } else { - j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中 - } - } - // 返回插入点 i - return i - } - ``` - -=== "Zig" - - ```zig title="binary_search_insertion.zig" - [class]{}-[func]{binarySearchInsertion} - ``` - === "Dart" ```dart title="binary_search_insertion.dart" @@ -540,6 +528,18 @@ status: new } ``` +=== "C" + + ```c title="binary_search_insertion.c" + [class]{}-[func]{binarySearchInsertion} + ``` + +=== "Zig" + + ```zig title="binary_search_insertion.zig" + [class]{}-[func]{binarySearchInsertion} + ``` + !!! tip 本节的代码都是“双闭区间”写法。有兴趣的读者可以自行实现“左闭右开”写法。 diff --git a/chapter_searching/replace_linear_by_hashing.md b/chapter_searching/replace_linear_by_hashing.md index 15a46c901..a9640f436 100755 --- a/chapter_searching/replace_linear_by_hashing.md +++ b/chapter_searching/replace_linear_by_hashing.md @@ -18,21 +18,17 @@ comments: true

图 10-9   线性查找求解两数之和

-=== "Java" +=== "Python" - ```java title="two_sum.java" - /* 方法一:暴力枚举 */ - int[] twoSumBruteForce(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]; - } + ```python title="two_sum.py" + def two_sum_brute_force(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 [] ``` === "C++" @@ -52,17 +48,38 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="two_sum.py" - def two_sum_brute_force(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 [] + ```java title="two_sum.java" + /* 方法一:暴力枚举 */ + int[] twoSumBruteForce(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#" + + ```csharp title="two_sum.cs" + /* 方法一:暴力枚举 */ + int[] twoSumBruteForce(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 Array.Empty(); + } ``` === "Go" @@ -83,6 +100,23 @@ comments: true } ``` +=== "Swift" + + ```swift title="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] + } + ``` + === "JS" ```javascript title="two_sum.js" @@ -119,80 +153,6 @@ comments: true } ``` -=== "C" - - ```c title="two_sum.c" - /* 方法一:暴力枚举 */ - int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { - for (int i = 0; i < numsSize; ++i) { - for (int j = i + 1; j < numsSize; ++j) { - if (nums[i] + nums[j] == target) { - int *res = malloc(sizeof(int) * 2); - res[0] = i, res[1] = j; - *returnSize = 2; - return res; - } - } - } - *returnSize = 0; - return NULL; - } - ``` - -=== "C#" - - ```csharp title="two_sum.cs" - /* 方法一:暴力枚举 */ - int[] twoSumBruteForce(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 Array.Empty(); - } - ``` - -=== "Swift" - - ```swift title="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="two_sum.zig" - // 方法一:暴力枚举 - fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { - 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(i), @intCast(j)}; - } - } - } - return null; - } - ``` - === "Dart" ```dart title="two_sum.dart" @@ -227,6 +187,46 @@ comments: true } ``` +=== "C" + + ```c title="two_sum.c" + /* 方法一:暴力枚举 */ + int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { + for (int i = 0; i < numsSize; ++i) { + for (int j = i + 1; j < numsSize; ++j) { + if (nums[i] + nums[j] == target) { + int *res = malloc(sizeof(int) * 2); + res[0] = i, res[1] = j; + *returnSize = 2; + return res; + } + } + } + *returnSize = 0; + return NULL; + } + ``` + +=== "Zig" + + ```zig title="two_sum.zig" + // 方法一:暴力枚举 + fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { + 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(i), @intCast(j)}; + } + } + } + return null; + } + ``` + 此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。 ## 10.4.2   哈希查找:以空间换时间 @@ -249,23 +249,19 @@ comments: true 实现代码如下所示,仅需单层循环即可。 -=== "Java" +=== "Python" - ```java title="two_sum.java" - /* 方法二:辅助哈希表 */ - int[] twoSumHashTable(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]; - } + ```python title="two_sum.py" + def two_sum_hash_table(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 [] ``` === "C++" @@ -287,19 +283,42 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="two_sum.py" - def two_sum_hash_table(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 [] + ```java title="two_sum.java" + /* 方法二:辅助哈希表 */ + int[] twoSumHashTable(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#" + + ```csharp title="two_sum.cs" + /* 方法二:辅助哈希表 */ + int[] twoSumHashTable(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 Array.Empty(); + } ``` === "Go" @@ -320,6 +339,24 @@ comments: true } ``` +=== "Swift" + + ```swift title="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] + } + ``` + === "JS" ```javascript title="two_sum.js" @@ -359,6 +396,43 @@ comments: true } ``` +=== "Dart" + + ```dart title="two_sum.dart" + /* 方法二: 辅助哈希表 */ + List twoSumHashTable(List nums, int target) { + int size = nums.length; + // 辅助哈希表,空间复杂度 O(n) + Map dic = HashMap(); + // 单层循环,时间复杂度 O(n) + for (var i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return [dic[target - nums[i]]!, i]; + } + dic.putIfAbsent(nums[i], () => i); + } + return [0]; + } + ``` + +=== "Rust" + + ```rust title="two_sum.rs" + /* 方法二:辅助哈希表 */ + pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { + // 辅助哈希表,空间复杂度 O(n) + let mut dic = HashMap::new(); + // 单层循环,时间复杂度 O(n) + for (i, num) in nums.iter().enumerate() { + match dic.get(&(target - num)) { + Some(v) => return Some(vec![*v as i32, i as i32]), + None => dic.insert(num, i as i32) + }; + } + None + } + ``` + === "C" ```c title="two_sum.c" @@ -408,43 +482,6 @@ comments: true } ``` -=== "C#" - - ```csharp title="two_sum.cs" - /* 方法二:辅助哈希表 */ - int[] twoSumHashTable(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 Array.Empty(); - } - ``` - -=== "Swift" - - ```swift title="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="two_sum.zig" @@ -466,43 +503,6 @@ comments: true } ``` -=== "Dart" - - ```dart title="two_sum.dart" - /* 方法二: 辅助哈希表 */ - List twoSumHashTable(List nums, int target) { - int size = nums.length; - // 辅助哈希表,空间复杂度 O(n) - Map dic = HashMap(); - // 单层循环,时间复杂度 O(n) - for (var i = 0; i < size; i++) { - if (dic.containsKey(target - nums[i])) { - return [dic[target - nums[i]]!, i]; - } - dic.putIfAbsent(nums[i], () => i); - } - return [0]; - } - ``` - -=== "Rust" - - ```rust title="two_sum.rs" - /* 方法二:辅助哈希表 */ - pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { - // 辅助哈希表,空间复杂度 O(n) - let mut dic = HashMap::new(); - // 单层循环,时间复杂度 O(n) - for (i, num) in nums.iter().enumerate() { - match dic.get(&(target - num)) { - Some(v) => return Some(vec![*v as i32, i as i32]), - None => dic.insert(num, i as i32) - }; - } - None - } - ``` - 此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降低至 $O(n)$ ,大幅提升运行效率。 由于需要维护一个额外的哈希表,因此空间复杂度为 $O(n)$ 。**尽管如此,该方法的整体时空效率更为均衡,因此它是本题的最优解法**。 diff --git a/chapter_sorting/bubble_sort.md b/chapter_sorting/bubble_sort.md index d6d8ac6e0..414b005cd 100755 --- a/chapter_sorting/bubble_sort.md +++ b/chapter_sorting/bubble_sort.md @@ -44,24 +44,19 @@ comments: true

图 11-5   冒泡排序流程

-=== "Java" +=== "Python" - ```java title="bubble_sort.java" - /* 冒泡排序 */ - void bubbleSort(int[] nums) { - // 外循环:未排序区间为 [0, i] - for (int i = nums.length - 1; i > 0; 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; - } - } - } - } + ```python title="bubble_sort.py" + def bubble_sort(nums: list[int]): + """冒泡排序""" + n = len(nums) + # 外循环:未排序区间为 [0, i] + for i in range(n - 1, 0, -1): + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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] ``` === "C++" @@ -83,19 +78,44 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="bubble_sort.py" - def bubble_sort(nums: list[int]): - """冒泡排序""" - n = len(nums) - # 外循环:未排序区间为 [0, i] - for i in range(n - 1, 0, -1): - # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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] + ```java title="bubble_sort.java" + /* 冒泡排序 */ + void bubbleSort(int[] nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.length - 1; i > 0; 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#" + + ```csharp title="bubble_sort.cs" + /* 冒泡排序 */ + void bubbleSort(int[] nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.Length - 1; i > 0; 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; + } + } + } + } ``` === "Go" @@ -116,6 +136,26 @@ comments: true } ``` +=== "Swift" + + ```swift title="bubble_sort.swift" + /* 冒泡排序 */ + func bubbleSort(nums: inout [Int]) { + // 外循环:未排序区间为 [0, i] + for i in stride(from: nums.count - 1, to: 0, by: -1) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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 + } + } + } + } + ``` + === "JS" ```javascript title="bubble_sort.js" @@ -156,87 +196,6 @@ comments: true } ``` -=== "C" - - ```c title="bubble_sort.c" - /* 冒泡排序 */ - void bubbleSort(int nums[], int size) { - // 外循环:未排序区间为 [0, i] - for (int i = 0; i < size - 1; i++) { - // 内循环:将未排序区间 [0, 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) { - // 外循环:未排序区间为 [0, i] - for (int i = nums.Length - 1; i > 0; 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]) { - // 外循环:未排序区间为 [0, i] - for i in stride(from: nums.count - 1, to: 0, by: -1) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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" - // 冒泡排序 - fn bubbleSort(nums: []i32) void { - // 外循环:未排序区间为 [0, i] - var i: usize = nums.len - 1; - while (i > 0) : (i -= 1) { - var j: usize = 0; - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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; - } - } - } - } - ``` - === "Dart" ```dart title="bubble_sort.dart" @@ -277,34 +236,70 @@ comments: true } ``` +=== "C" + + ```c title="bubble_sort.c" + /* 冒泡排序 */ + void bubbleSort(int nums[], int size) { + // 外循环:未排序区间为 [0, i] + for (int i = 0; i < size - 1; i++) { + // 内循环:将未排序区间 [0, 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; + } + } + } + } + ``` + +=== "Zig" + + ```zig title="bubble_sort.zig" + // 冒泡排序 + fn bubbleSort(nums: []i32) void { + // 外循环:未排序区间为 [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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; + } + } + } + } + ``` + ## 11.3.2   效率优化 我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 `flag` 来监测这种情况,一旦出现就立即返回。 经过优化,冒泡排序的最差和平均时间复杂度仍为 $O(n^2)$ ;但当输入数组完全有序时,可达到最佳时间复杂度 $O(n)$ 。 -=== "Java" +=== "Python" - ```java title="bubble_sort.java" - /* 冒泡排序(标志优化) */ - void bubbleSortWithFlag(int[] nums) { - // 外循环:未排序区间为 [0, i] - for (int i = nums.length - 1; i > 0; i--) { - boolean flag = false; // 初始化标志位 - // 内循环:将未排序区间 [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; - flag = true; // 记录交换元素 - } - } - if (!flag) - break; // 此轮冒泡未交换任何元素,直接跳出 - } - } + ```python title="bubble_sort.py" + def bubble_sort_with_flag(nums: list[int]): + """冒泡排序(标志优化)""" + n = len(nums) + # 外循环:未排序区间为 [0, i] + for i in range(n - 1, 0, -1): + flag = False # 初始化标志位 + # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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 # 此轮冒泡未交换任何元素,直接跳出 ``` === "C++" @@ -330,23 +325,51 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="bubble_sort.py" - def bubble_sort_with_flag(nums: list[int]): - """冒泡排序(标志优化)""" - n = len(nums) - # 外循环:未排序区间为 [0, i] - for i in range(n - 1, 0, -1): - flag = False # 初始化标志位 - # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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 # 此轮冒泡未交换任何元素,直接跳出 + ```java title="bubble_sort.java" + /* 冒泡排序(标志优化) */ + void bubbleSortWithFlag(int[] nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + boolean flag = false; // 初始化标志位 + // 内循环:将未排序区间 [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; + flag = true; // 记录交换元素 + } + } + if (!flag) + break; // 此轮冒泡未交换任何元素,直接跳出 + } + } + ``` + +=== "C#" + + ```csharp title="bubble_sort.cs" + /* 冒泡排序(标志优化)*/ + void bubbleSortWithFlag(int[] nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + bool flag = false; // 初始化标志位 + // 内循环:将未排序区间 [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; + flag = true; // 记录交换元素 + } + } + if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + } + } ``` === "Go" @@ -372,6 +395,30 @@ comments: true } ``` +=== "Swift" + + ```swift title="bubble_sort.swift" + /* 冒泡排序(标志优化)*/ + func bubbleSortWithFlag(nums: inout [Int]) { + // 外循环:未排序区间为 [0, i] + 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 + } + } + } + ``` + === "JS" ```javascript title="bubble_sort.js" @@ -418,101 +465,6 @@ comments: true } ``` -=== "C" - - ```c title="bubble_sort.c" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(int nums[], int size) { - // 外循环:未排序区间为 [0, i] - for (int i = 0; i < size - 1; i++) { - bool flag = false; - // 内循环:将未排序区间 [0, 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; - flag = true; - } - } - if (!flag) - break; - } - } - ``` - -=== "C#" - - ```csharp title="bubble_sort.cs" - /* 冒泡排序(标志优化)*/ - void bubbleSortWithFlag(int[] nums) { - // 外循环:未排序区间为 [0, i] - for (int i = nums.Length - 1; i > 0; i--) { - bool flag = false; // 初始化标志位 - // 内循环:将未排序区间 [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; - flag = true; // 记录交换元素 - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - -=== "Swift" - - ```swift title="bubble_sort.swift" - /* 冒泡排序(标志优化)*/ - func bubbleSortWithFlag(nums: inout [Int]) { - // 外循环:未排序区间为 [0, i] - 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" - // 冒泡排序(标志优化) - fn bubbleSortWithFlag(nums: []i32) void { - // 外循环:未排序区间为 [0, i] - var i: usize = nums.len - 1; - while (i > 0) : (i -= 1) { - var flag = false; // 初始化标志位 - var j: usize = 0; - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 - 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; - flag = true; - } - } - if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 - } - } - ``` - === "Dart" ```dart title="bubble_sort.dart" @@ -559,6 +511,54 @@ comments: true } ``` +=== "C" + + ```c title="bubble_sort.c" + /* 冒泡排序(标志优化)*/ + void bubbleSortWithFlag(int nums[], int size) { + // 外循环:未排序区间为 [0, i] + for (int i = 0; i < size - 1; i++) { + bool flag = false; + // 内循环:将未排序区间 [0, 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; + flag = true; + } + } + if (!flag) + break; + } + } + ``` + +=== "Zig" + + ```zig title="bubble_sort.zig" + // 冒泡排序(标志优化) + fn bubbleSortWithFlag(nums: []i32) void { + // 外循环:未排序区间为 [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var flag = false; // 初始化标志位 + var j: usize = 0; + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + 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; + flag = true; + } + } + if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + } + } + ``` + ## 11.3.3   算法特性 - **时间复杂度为 $O(n^2)$、自适应排序**:各轮“冒泡”遍历的数组长度依次为 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,总和为 $(n - 1) n / 2$ 。在引入 `flag` 优化后,最佳时间复杂度可达到 $O(n)$ 。 diff --git a/chapter_sorting/bucket_sort.md b/chapter_sorting/bucket_sort.md index 71bb4edac..341ecbbcb 100644 --- a/chapter_sorting/bucket_sort.md +++ b/chapter_sorting/bucket_sort.md @@ -20,6 +20,62 @@ comments: true

图 11-13   桶排序算法流程

+=== "Python" + + ```python title="bucket_sort.py" + def bucket_sort(nums: list[float]): + """桶排序""" + # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. 将数组元素分配到各个桶中 + for num in nums: + # 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + i = int(num * k) + # 将 num 添加进桶 i + buckets[i].append(num) + # 2. 对各个桶执行排序 + for bucket in buckets: + # 使用内置排序函数,也可以替换成其他排序算法 + bucket.sort() + # 3. 遍历桶合并结果 + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + ``` + +=== "C++" + + ```cpp title="bucket_sort.cpp" + /* 桶排序 */ + void bucketSort(vector &nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = nums.size() / 2; + vector> buckets(k); + // 1. 将数组元素分配到各个桶中 + for (float num : nums) { + // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + int i = num * k; + // 将 num 添加进桶 bucket_idx + buckets[i].push_back(num); + } + // 2. 对各个桶执行排序 + for (vector &bucket : buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + sort(bucket.begin(), bucket.end()); + } + // 3. 遍历桶合并结果 + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + ``` + === "Java" ```java title="bucket_sort.java" @@ -53,62 +109,39 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="bucket_sort.cpp" + ```csharp title="bucket_sort.cs" /* 桶排序 */ - void bucketSort(vector &nums) { + void bucketSort(float[] nums) { // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 - int k = nums.size() / 2; - vector> buckets(k); + int k = nums.Length / 2; + List> buckets = new List>(); + for (int i = 0; i < k; i++) { + buckets.Add(new List()); + } // 1. 将数组元素分配到各个桶中 - for (float num : nums) { + foreach (float num in nums) { // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] - int i = num * k; - // 将 num 添加进桶 bucket_idx - buckets[i].push_back(num); + int i = (int)(num * k); + // 将 num 添加进桶 i + buckets[i].Add(num); } // 2. 对各个桶执行排序 - for (vector &bucket : buckets) { + foreach (List bucket in buckets) { // 使用内置排序函数,也可以替换成其他排序算法 - sort(bucket.begin(), bucket.end()); + bucket.Sort(); } // 3. 遍历桶合并结果 - int i = 0; - for (vector &bucket : buckets) { - for (float num : bucket) { - nums[i++] = num; + int j = 0; + foreach (List bucket in buckets) { + foreach (float num in bucket) { + nums[j++] = num; } } } ``` -=== "Python" - - ```python title="bucket_sort.py" - def bucket_sort(nums: list[float]): - """桶排序""" - # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 - k = len(nums) // 2 - buckets = [[] for _ in range(k)] - # 1. 将数组元素分配到各个桶中 - for num in nums: - # 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] - i = int(num * k) - # 将 num 添加进桶 i - buckets[i].append(num) - # 2. 对各个桶执行排序 - for bucket in buckets: - # 使用内置排序函数,也可以替换成其他排序算法 - bucket.sort() - # 3. 遍历桶合并结果 - i = 0 - for bucket in buckets: - for num in bucket: - nums[i] = num - i += 1 - ``` - === "Go" ```go title="bucket_sort.go" @@ -143,6 +176,37 @@ comments: true } ``` +=== "Swift" + + ```swift title="bucket_sort.swift" + /* 桶排序 */ + func bucketSort(nums: inout [Double]) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + let k = nums.count / 2 + var buckets = (0 ..< k).map { _ in [Double]() } + // 1. 将数组元素分配到各个桶中 + for num in nums { + // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + let i = Int(num * Double(k)) + // 将 num 添加进桶 i + buckets[i].append(num) + } + // 2. 对各个桶执行排序 + for i in buckets.indices { + // 使用内置排序函数,也可以替换成其他排序算法 + buckets[i].sort() + } + // 3. 遍历桶合并结果 + var i = nums.startIndex + for bucket in buckets { + for num in bucket { + nums[i] = num + nums.formIndex(after: &i) + } + } + } + ``` + === "JS" ```javascript title="bucket_sort.js" @@ -209,128 +273,6 @@ comments: true } ``` -=== "C" - - ```c title="bucket_sort.c" - /* 桶排序 */ - void bucketSort(float nums[], int size) { - // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 - int k = size / 2; - float **buckets = calloc(k, sizeof(float *)); - for (int i = 0; i < k; i++) { - // 每个桶最多可以分配 k 个元素 - buckets[i] = calloc(ARRAY_SIZE, sizeof(float)); - } - - // 1. 将数组元素分配到各个桶中 - for (int i = 0; i < size; i++) { - // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] - int bucket_idx = nums[i] * k; - int j = 0; - // 如果桶中有数据且数据小于当前值 nums[i], 要将其放到当前桶的后面,相当于 cpp 中的 push_back - while (buckets[bucket_idx][j] > 0 && buckets[bucket_idx][j] < nums[i]) { - j++; - } - float temp = nums[i]; - while (j < ARRAY_SIZE && buckets[bucket_idx][j] > 0) { - swap(&temp, &buckets[bucket_idx][j]); - j++; - } - buckets[bucket_idx][j] = temp; - } - - // 2. 对各个桶执行排序 - for (int i = 0; i < k; i++) { - qsort(buckets[i], ARRAY_SIZE, sizeof(float), compare_float); - } - - // 3. 遍历桶合并结果 - for (int i = 0, j = 0; j < k; j++) { - for (int l = 0; l < ARRAY_SIZE; l++) { - if (buckets[j][l] > 0) { - nums[i++] = buckets[j][l]; - } - } - } - - // 释放上述分配的内存 - for (int i = 0; i < k; i++) { - free(buckets[i]); - } - free(buckets); - } - ``` - -=== "C#" - - ```csharp title="bucket_sort.cs" - /* 桶排序 */ - void bucketSort(float[] nums) { - // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 - int k = nums.Length / 2; - List> buckets = new List>(); - for (int i = 0; i < k; i++) { - buckets.Add(new List()); - } - // 1. 将数组元素分配到各个桶中 - foreach (float num in nums) { - // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] - int i = (int)(num * k); - // 将 num 添加进桶 i - buckets[i].Add(num); - } - // 2. 对各个桶执行排序 - foreach (List bucket in buckets) { - // 使用内置排序函数,也可以替换成其他排序算法 - bucket.Sort(); - } - // 3. 遍历桶合并结果 - int j = 0; - foreach (List bucket in buckets) { - foreach (float num in bucket) { - nums[j++] = num; - } - } - } - ``` - -=== "Swift" - - ```swift title="bucket_sort.swift" - /* 桶排序 */ - func bucketSort(nums: inout [Double]) { - // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 - let k = nums.count / 2 - var buckets = (0 ..< k).map { _ in [Double]() } - // 1. 将数组元素分配到各个桶中 - for num in nums { - // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] - let i = Int(num * Double(k)) - // 将 num 添加进桶 i - buckets[i].append(num) - } - // 2. 对各个桶执行排序 - for i in buckets.indices { - // 使用内置排序函数,也可以替换成其他排序算法 - buckets[i].sort() - } - // 3. 遍历桶合并结果 - var i = nums.startIndex - for bucket in buckets { - for num in bucket { - nums[i] = num - nums.formIndex(after: &i) - } - } - } - ``` - -=== "Zig" - - ```zig title="bucket_sort.zig" - [class]{}-[func]{bucketSort} - ``` - === "Dart" ```dart title="bucket_sort.dart" @@ -392,6 +334,64 @@ comments: true } ``` +=== "C" + + ```c title="bucket_sort.c" + /* 桶排序 */ + void bucketSort(float nums[], int size) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = size / 2; + float **buckets = calloc(k, sizeof(float *)); + for (int i = 0; i < k; i++) { + // 每个桶最多可以分配 k 个元素 + buckets[i] = calloc(ARRAY_SIZE, sizeof(float)); + } + + // 1. 将数组元素分配到各个桶中 + for (int i = 0; i < size; i++) { + // 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + int bucket_idx = nums[i] * k; + int j = 0; + // 如果桶中有数据且数据小于当前值 nums[i], 要将其放到当前桶的后面,相当于 cpp 中的 push_back + while (buckets[bucket_idx][j] > 0 && buckets[bucket_idx][j] < nums[i]) { + j++; + } + float temp = nums[i]; + while (j < ARRAY_SIZE && buckets[bucket_idx][j] > 0) { + swap(&temp, &buckets[bucket_idx][j]); + j++; + } + buckets[bucket_idx][j] = temp; + } + + // 2. 对各个桶执行排序 + for (int i = 0; i < k; i++) { + qsort(buckets[i], ARRAY_SIZE, sizeof(float), compare_float); + } + + // 3. 遍历桶合并结果 + for (int i = 0, j = 0; j < k; j++) { + for (int l = 0; l < ARRAY_SIZE; l++) { + if (buckets[j][l] > 0) { + nums[i++] = buckets[j][l]; + } + } + } + + // 释放上述分配的内存 + for (int i = 0; i < k; i++) { + free(buckets[i]); + } + free(buckets); + } + ``` + +=== "Zig" + + ```zig title="bucket_sort.zig" + [class]{}-[func]{bucketSort} + ``` + ## 11.8.2   算法特性 桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。 diff --git a/chapter_sorting/counting_sort.md b/chapter_sorting/counting_sort.md index c7294b8f9..bcf0ae42b 100644 --- a/chapter_sorting/counting_sort.md +++ b/chapter_sorting/counting_sort.md @@ -18,31 +18,27 @@ comments: true

图 11-16   计数排序流程

-=== "Java" +=== "Python" - ```java title="counting_sort.java" - /* 计数排序 */ - // 简单实现,无法用于排序对象 - void countingSortNaive(int[] nums) { - // 1. 统计数组最大元素 m - int m = 0; - for (int num : nums) { - m = Math.max(m, num); - } - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - int[] counter = new int[m + 1]; - for (int num : nums) { - counter[num]++; - } - // 3. 遍历 counter ,将各元素填入原数组 nums - int i = 0; - for (int num = 0; num < m + 1; num++) { - for (int j = 0; j < counter[num]; j++, i++) { - nums[i] = num; - } - } - } + ```python title="counting_sort.py" + def counting_sort_naive(nums: list[int]): + """计数排序""" + # 简单实现,无法用于排序对象 + # 1. 统计数组最大元素 m + m = 0 + for num in nums: + m = max(m, num) + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 遍历 counter ,将各元素填入原数组 nums + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 ``` === "C++" @@ -72,27 +68,58 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="counting_sort.py" - def counting_sort_naive(nums: list[int]): - """计数排序""" - # 简单实现,无法用于排序对象 - # 1. 统计数组最大元素 m - m = 0 - for num in nums: - m = max(m, num) - # 2. 统计各数字的出现次数 - # counter[num] 代表 num 的出现次数 - counter = [0] * (m + 1) - for num in nums: - counter[num] += 1 - # 3. 遍历 counter ,将各元素填入原数组 nums - i = 0 - for num in range(m + 1): - for _ in range(counter[num]): - nums[i] = num - i += 1 + ```java title="counting_sort.java" + /* 计数排序 */ + // 简单实现,无法用于排序对象 + void countingSortNaive(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + ``` + +=== "C#" + + ```csharp title="counting_sort.cs" + /* 计数排序 */ + // 简单实现,无法用于排序对象 + void countingSortNaive(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } ``` === "Go" @@ -124,6 +151,31 @@ comments: true } ``` +=== "Swift" + + ```swift title="counting_sort.swift" + /* 计数排序 */ + // 简单实现,无法用于排序对象 + func countingSortNaive(nums: inout [Int]) { + // 1. 统计数组最大元素 m + let m = nums.max()! + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 遍历 counter ,将各元素填入原数组 nums + var i = 0 + for num in stride(from: 0, to: m + 1, by: 1) { + for _ in stride(from: 0, to: counter[num], by: 1) { + nums[i] = num + i += 1 + } + } + } + ``` + === "JS" ```javascript title="counting_sort.js" @@ -178,93 +230,6 @@ comments: true } ``` -=== "C" - - ```c title="counting_sort.c" - /* 计数排序 */ - // 简单实现,无法用于排序对象 - void countingSortNaive(int nums[], int size) { - // 1. 统计数组最大元素 m - int m = 0; - for (int i = 0; i < size; i++) { - if (nums[i] > m) { - m = nums[i]; - } - } - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - int *counter = malloc(sizeof(int) * m); - for (int i = 0; i < size; i++) { - counter[nums[i]]++; - } - // 3. 遍历 counter ,将各元素填入原数组 nums - int i = 0; - for (int num = 0; num < m + 1; num++) { - for (int j = 0; j < counter[num]; j++, i++) { - nums[i] = num; - } - } - } - ``` - -=== "C#" - - ```csharp title="counting_sort.cs" - /* 计数排序 */ - // 简单实现,无法用于排序对象 - void countingSortNaive(int[] nums) { - // 1. 统计数组最大元素 m - int m = 0; - foreach (int num in nums) { - m = Math.Max(m, num); - } - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - int[] counter = new int[m + 1]; - foreach (int num in nums) { - counter[num]++; - } - // 3. 遍历 counter ,将各元素填入原数组 nums - int i = 0; - for (int num = 0; num < m + 1; num++) { - for (int j = 0; j < counter[num]; j++, i++) { - nums[i] = num; - } - } - } - ``` - -=== "Swift" - - ```swift title="counting_sort.swift" - /* 计数排序 */ - // 简单实现,无法用于排序对象 - func countingSortNaive(nums: inout [Int]) { - // 1. 统计数组最大元素 m - let m = nums.max()! - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - var counter = Array(repeating: 0, count: m + 1) - for num in nums { - counter[num] += 1 - } - // 3. 遍历 counter ,将各元素填入原数组 nums - var i = 0 - for num in stride(from: 0, to: m + 1, by: 1) { - for _ in stride(from: 0, to: counter[num], by: 1) { - nums[i] = num - i += 1 - } - } - } - ``` - -=== "Zig" - - ```zig title="counting_sort.zig" - [class]{}-[func]{countingSortNaive} - ``` - === "Dart" ```dart title="counting_sort.dart" @@ -317,6 +282,41 @@ comments: true } ``` +=== "C" + + ```c title="counting_sort.c" + /* 计数排序 */ + // 简单实现,无法用于排序对象 + void countingSortNaive(int nums[], int size) { + // 1. 统计数组最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int *counter = malloc(sizeof(int) * m); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + ``` + +=== "Zig" + + ```zig title="counting_sort.zig" + [class]{}-[func]{countingSortNaive} + ``` + !!! note "计数排序与桶排序的联系" 从桶排序的角度看,我们可以将计数排序中的计数数组 `counter` 的每个索引视为一个桶,将统计数量的过程看作是将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。 @@ -366,42 +366,34 @@ $$ 计数排序的实现代码如下所示。 -=== "Java" +=== "Python" - ```java title="counting_sort.java" - /* 计数排序 */ - // 完整实现,可排序对象,并且是稳定排序 - void countingSort(int[] nums) { - // 1. 统计数组最大元素 m - int m = 0; - for (int num : nums) { - m = Math.max(m, num); - } - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - int[] counter = new int[m + 1]; - for (int num : nums) { - counter[num]++; - } - // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” - // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 - for (int i = 0; i < m; i++) { - counter[i + 1] += counter[i]; - } - // 4. 倒序遍历 nums ,将各元素填入结果数组 res - // 初始化数组 res 用于记录结果 - int n = nums.length; - int[] res = new int[n]; - for (int i = n - 1; i >= 0; i--) { - int num = nums[i]; - res[counter[num] - 1] = num; // 将 num 放置到对应索引处 - counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 - } - // 使用结果数组 res 覆盖原数组 nums - for (int i = 0; i < n; i++) { - nums[i] = res[i]; - } - } + ```python title="counting_sort.py" + def counting_sort(nums: list[int]): + """计数排序""" + # 完整实现,可排序对象,并且是稳定排序 + # 1. 统计数组最大元素 m + m = max(nums) + # 2. 统计各数字的出现次数 + # counter[num] 代表 num 的出现次数 + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i in range(m): + counter[i + 1] += counter[i] + # 4. 倒序遍历 nums ,将各元素填入结果数组 res + # 初始化数组 res 用于记录结果 + n = len(nums) + res = [0] * n + for i in range(n - 1, -1, -1): + num = nums[i] + res[counter[num] - 1] = num # 将 num 放置到对应索引处 + counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 + # 使用结果数组 res 覆盖原数组 nums + for i in range(n): + nums[i] = res[i] ``` === "C++" @@ -440,34 +432,80 @@ $$ } ``` -=== "Python" +=== "Java" - ```python title="counting_sort.py" - def counting_sort(nums: list[int]): - """计数排序""" - # 完整实现,可排序对象,并且是稳定排序 - # 1. 统计数组最大元素 m - m = max(nums) - # 2. 统计各数字的出现次数 - # counter[num] 代表 num 的出现次数 - counter = [0] * (m + 1) - for num in nums: - counter[num] += 1 - # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” - # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 - for i in range(m): - counter[i + 1] += counter[i] - # 4. 倒序遍历 nums ,将各元素填入结果数组 res - # 初始化数组 res 用于记录结果 - n = len(nums) - res = [0] * n - for i in range(n - 1, -1, -1): - num = nums[i] - res[counter[num] - 1] = num # 将 num 放置到对应索引处 - counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 - # 使用结果数组 res 覆盖原数组 nums - for i in range(n): - nums[i] = res[i] + ```java title="counting_sort.java" + /* 计数排序 */ + // 完整实现,可排序对象,并且是稳定排序 + void countingSort(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + ``` + +=== "C#" + + ```csharp title="counting_sort.cs" + /* 计数排序 */ + // 完整实现,可排序对象,并且是稳定排序 + void countingSort(int[] nums) { + // 1. 统计数组最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.Length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } ``` === "Go" @@ -510,6 +548,40 @@ $$ } ``` +=== "Swift" + + ```swift title="counting_sort.swift" + /* 计数排序 */ + // 完整实现,可排序对象,并且是稳定排序 + func countingSort(nums: inout [Int]) { + // 1. 统计数组最大元素 m + let m = nums.max()! + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for i in stride(from: 0, to: m, by: 1) { + counter[i + 1] += counter[i] + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + var res = Array(repeating: 0, count: nums.count) + for i in stride(from: nums.count - 1, through: 0, by: -1) { + let num = nums[i] + res[counter[num] - 1] = num // 将 num 放置到对应索引处 + counter[num] -= 1 // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + for i in stride(from: 0, to: nums.count, by: 1) { + nums[i] = res[i] + } + } + ``` + === "JS" ```javascript title="counting_sort.js" @@ -586,121 +658,6 @@ $$ } ``` -=== "C" - - ```c title="counting_sort.c" - /* 计数排序 */ - // 完整实现,可排序对象,并且是稳定排序 - void countingSort(int nums[], int size) { - // 1. 统计数组最大元素 m - int m = 0; - for (int i = 0; i < size; i++) { - if (nums[i] > m) { - m = nums[i]; - } - } - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - int *counter = malloc(sizeof(int) * m); - for (int i = 0; i < size; i++) { - counter[nums[i]]++; - } - // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” - // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 - for (int i = 0; i < m; i++) { - counter[i + 1] += counter[i]; - } - // 4. 倒序遍历 nums ,将各元素填入结果数组 res - // 初始化数组 res 用于记录结果 - int *res = malloc(sizeof(int) * size); - for (int i = size - 1; i >= 0; i--) { - int num = nums[i]; - res[counter[num] - 1] = num; // 将 num 放置到对应索引处 - counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 - } - // 使用结果数组 res 覆盖原数组 nums - memcpy(nums, res, size * sizeof(int)); - } - ``` - -=== "C#" - - ```csharp title="counting_sort.cs" - /* 计数排序 */ - // 完整实现,可排序对象,并且是稳定排序 - void countingSort(int[] nums) { - // 1. 统计数组最大元素 m - int m = 0; - foreach (int num in nums) { - m = Math.Max(m, num); - } - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - int[] counter = new int[m + 1]; - foreach (int num in nums) { - counter[num]++; - } - // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” - // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 - for (int i = 0; i < m; i++) { - counter[i + 1] += counter[i]; - } - // 4. 倒序遍历 nums ,将各元素填入结果数组 res - // 初始化数组 res 用于记录结果 - int n = nums.Length; - int[] res = new int[n]; - for (int i = n - 1; i >= 0; i--) { - int num = nums[i]; - res[counter[num] - 1] = num; // 将 num 放置到对应索引处 - counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 - } - // 使用结果数组 res 覆盖原数组 nums - for (int i = 0; i < n; i++) { - nums[i] = res[i]; - } - } - ``` - -=== "Swift" - - ```swift title="counting_sort.swift" - /* 计数排序 */ - // 完整实现,可排序对象,并且是稳定排序 - func countingSort(nums: inout [Int]) { - // 1. 统计数组最大元素 m - let m = nums.max()! - // 2. 统计各数字的出现次数 - // counter[num] 代表 num 的出现次数 - var counter = Array(repeating: 0, count: m + 1) - for num in nums { - counter[num] += 1 - } - // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” - // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 - for i in stride(from: 0, to: m, by: 1) { - counter[i + 1] += counter[i] - } - // 4. 倒序遍历 nums ,将各元素填入结果数组 res - // 初始化数组 res 用于记录结果 - var res = Array(repeating: 0, count: nums.count) - for i in stride(from: nums.count - 1, through: 0, by: -1) { - let num = nums[i] - res[counter[num] - 1] = num // 将 num 放置到对应索引处 - counter[num] -= 1 // 令前缀和自减 1 ,得到下次放置 num 的索引 - } - // 使用结果数组 res 覆盖原数组 nums - for i in stride(from: 0, to: nums.count, by: 1) { - nums[i] = res[i] - } - } - ``` - -=== "Zig" - - ```zig title="counting_sort.zig" - [class]{}-[func]{countingSort} - ``` - === "Dart" ```dart title="counting_sort.dart" @@ -772,6 +729,49 @@ $$ } ``` +=== "C" + + ```c title="counting_sort.c" + /* 计数排序 */ + // 完整实现,可排序对象,并且是稳定排序 + void countingSort(int nums[], int size) { + // 1. 统计数组最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + int *counter = malloc(sizeof(int) * m); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int *res = malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + memcpy(nums, res, size * sizeof(int)); + } + ``` + +=== "Zig" + + ```zig title="counting_sort.zig" + [class]{}-[func]{countingSort} + ``` + ## 11.9.3   算法特性 - **时间复杂度 $O(n + m)$** :涉及遍历 `nums` 和遍历 `counter` ,都使用线性时间。一般情况下 $n \gg m$ ,时间复杂度趋于 $O(n)$ 。 diff --git a/chapter_sorting/heap_sort.md b/chapter_sorting/heap_sort.md index ebc5a3f4c..799ac9ea1 100644 --- a/chapter_sorting/heap_sort.md +++ b/chapter_sorting/heap_sort.md @@ -68,6 +68,82 @@ comments: true 在代码实现中,我们使用了与堆章节相同的从顶至底堆化 `sift_down()` 函数。值得注意的是,由于堆的长度会随着提取最大元素而减小,因此我们需要给 `sift_down()` 函数添加一个长度参数 $n$ ,用于指定堆的当前有效长度。 +=== "Python" + + ```python title="heap_sort.py" + def sift_down(nums: list[int], n: int, i: int): + """堆的长度为 n ,从节点 i 开始,从顶至底堆化""" + while True: + # 判断节点 i, l, r 中值最大的节点,记为 ma + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i: + break + # 交换两节点 + nums[i], nums[ma] = nums[ma], nums[i] + # 循环向下堆化 + i = ma + + def heap_sort(nums: list[int]): + """堆排序""" + # 建堆操作:堆化除叶节点以外的其他所有节点 + for i in range(len(nums) // 2 - 1, -1, -1): + sift_down(nums, len(nums), i) + # 从堆中提取最大元素,循环 n-1 轮 + for i in range(len(nums) - 1, 0, -1): + # 交换根节点与最右叶节点(即交换首元素与尾元素) + nums[0], nums[i] = nums[i], nums[0] + # 以根节点为起点,从顶至底进行堆化 + sift_down(nums, i, 0) + ``` + +=== "C++" + + ```cpp title="heap_sort.cpp" + /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ + void siftDown(vector &nums, int n, int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) { + break; + } + // 交换两节点 + swap(nums[i], nums[ma]); + // 循环向下堆化 + i = ma; + } + } + + /* 堆排序 */ + void heapSort(vector &nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = nums.size() - 1; i > 0; --i) { + // 交换根节点与最右叶节点(即交换首元素与尾元素) + swap(nums[0], nums[i]); + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } + } + ``` + === "Java" ```java title="heap_sort.java" @@ -112,11 +188,11 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="heap_sort.cpp" + ```csharp title="heap_sort.cs" /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ - void siftDown(vector &nums, int n, int i) { + void siftDown(int[] nums, int n, int i) { while (true) { // 判断节点 i, l, r 中值最大的节点,记为 ma int l = 2 * i + 1; @@ -127,67 +203,31 @@ comments: true if (r < n && nums[r] > nums[ma]) ma = r; // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if (ma == i) { + if (ma == i) break; - } // 交换两节点 - swap(nums[i], nums[ma]); + (nums[ma], nums[i]) = (nums[i], nums[ma]); // 循环向下堆化 i = ma; } } /* 堆排序 */ - void heapSort(vector &nums) { + void heapSort(int[] nums) { // 建堆操作:堆化除叶节点以外的其他所有节点 - for (int i = nums.size() / 2 - 1; i >= 0; --i) { - siftDown(nums, nums.size(), i); + for (int i = nums.Length / 2 - 1; i >= 0; i--) { + siftDown(nums, nums.Length, i); } // 从堆中提取最大元素,循环 n-1 轮 - for (int i = nums.size() - 1; i > 0; --i) { + for (int i = nums.Length - 1; i > 0; i--) { // 交换根节点与最右叶节点(即交换首元素与尾元素) - swap(nums[0], nums[i]); + (nums[i], nums[0]) = (nums[0], nums[i]); // 以根节点为起点,从顶至底进行堆化 siftDown(nums, i, 0); } } ``` -=== "Python" - - ```python title="heap_sort.py" - def sift_down(nums: list[int], n: int, i: int): - """堆的长度为 n ,从节点 i 开始,从顶至底堆化""" - while True: - # 判断节点 i, l, r 中值最大的节点,记为 ma - l = 2 * i + 1 - r = 2 * i + 2 - ma = i - if l < n and nums[l] > nums[ma]: - ma = l - if r < n and nums[r] > nums[ma]: - ma = r - # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if ma == i: - break - # 交换两节点 - nums[i], nums[ma] = nums[ma], nums[i] - # 循环向下堆化 - i = ma - - def heap_sort(nums: list[int]): - """堆排序""" - # 建堆操作:堆化除叶节点以外的其他所有节点 - for i in range(len(nums) // 2 - 1, -1, -1): - sift_down(nums, len(nums), i) - # 从堆中提取最大元素,循环 n-1 轮 - for i in range(len(nums) - 1, 0, -1): - # 交换根节点与最右叶节点(即交换首元素与尾元素) - nums[0], nums[i] = nums[i], nums[0] - # 以根节点为起点,从顶至底进行堆化 - sift_down(nums, i, 0) - ``` - === "Go" ```go title="heap_sort.go" @@ -231,6 +271,50 @@ comments: true } ``` +=== "Swift" + + ```swift title="heap_sort.swift" + /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ + func siftDown(nums: inout [Int], n: Int, i: Int) { + var i = i + while true { + // 判断节点 i, l, r 中值最大的节点,记为 ma + let l = 2 * i + 1 + let r = 2 * i + 2 + var ma = i + if l < n, nums[l] > nums[ma] { + ma = l + } + if r < n, nums[r] > nums[ma] { + ma = r + } + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if ma == i { + break + } + // 交换两节点 + nums.swapAt(i, ma) + // 循环向下堆化 + i = ma + } + } + + /* 堆排序 */ + func heapSort(nums: inout [Int]) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { + siftDown(nums: &nums, n: nums.count, i: i) + } + // 从堆中提取最大元素,循环 n-1 轮 + for i in stride(from: nums.count - 1, to: 0, by: -1) { + // 交换根节点与最右叶节点(即交换首元素与尾元素) + nums.swapAt(0, i) + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums: &nums, n: i, i: 0) + } + } + ``` + === "JS" ```javascript title="heap_sort.js" @@ -317,143 +401,6 @@ comments: true } ``` -=== "C" - - ```c title="heap_sort.c" - /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ - void siftDown(int nums[], int n, int i) { - while (1) { - // 判断节点 i, l, r 中值最大的节点,记为 ma - int l = 2 * i + 1; - int r = 2 * i + 2; - int ma = i; - if (l < n && nums[l] > nums[ma]) - ma = l; - if (r < n && nums[r] > nums[ma]) - ma = r; - // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if (ma == i) { - break; - } - // 交换两节点 - int temp = nums[i]; - nums[i] = nums[ma]; - nums[ma] = temp; - // 循环向下堆化 - i = ma; - } - } - - /* 堆排序 */ - void heapSort(int nums[], int n) { - // 建堆操作:堆化除叶节点以外的其他所有节点 - for (int i = n / 2 - 1; i >= 0; --i) { - siftDown(nums, n, i); - } - // 从堆中提取最大元素,循环 n-1 轮 - for (int i = n - 1; i > 0; --i) { - // 交换根节点与最右叶节点(即交换首元素与尾元素) - int tmp = nums[0]; - nums[0] = nums[i]; - nums[i] = tmp; - // 以根节点为起点,从顶至底进行堆化 - siftDown(nums, i, 0); - } - } - ``` - -=== "C#" - - ```csharp title="heap_sort.cs" - /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ - void siftDown(int[] nums, int n, int i) { - while (true) { - // 判断节点 i, l, r 中值最大的节点,记为 ma - int l = 2 * i + 1; - int r = 2 * i + 2; - int ma = i; - if (l < n && nums[l] > nums[ma]) - ma = l; - if (r < n && nums[r] > nums[ma]) - ma = r; - // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if (ma == i) - break; - // 交换两节点 - (nums[ma], nums[i]) = (nums[i], nums[ma]); - // 循环向下堆化 - i = ma; - } - } - - /* 堆排序 */ - void heapSort(int[] nums) { - // 建堆操作:堆化除叶节点以外的其他所有节点 - for (int i = nums.Length / 2 - 1; i >= 0; i--) { - siftDown(nums, nums.Length, i); - } - // 从堆中提取最大元素,循环 n-1 轮 - for (int i = nums.Length - 1; i > 0; i--) { - // 交换根节点与最右叶节点(即交换首元素与尾元素) - (nums[i], nums[0]) = (nums[0], nums[i]); - // 以根节点为起点,从顶至底进行堆化 - siftDown(nums, i, 0); - } - } - ``` - -=== "Swift" - - ```swift title="heap_sort.swift" - /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ - func siftDown(nums: inout [Int], n: Int, i: Int) { - var i = i - while true { - // 判断节点 i, l, r 中值最大的节点,记为 ma - let l = 2 * i + 1 - let r = 2 * i + 2 - var ma = i - if l < n, nums[l] > nums[ma] { - ma = l - } - if r < n, nums[r] > nums[ma] { - ma = r - } - // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 - if ma == i { - break - } - // 交换两节点 - nums.swapAt(i, ma) - // 循环向下堆化 - i = ma - } - } - - /* 堆排序 */ - func heapSort(nums: inout [Int]) { - // 建堆操作:堆化除叶节点以外的其他所有节点 - for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { - siftDown(nums: &nums, n: nums.count, i: i) - } - // 从堆中提取最大元素,循环 n-1 轮 - for i in stride(from: nums.count - 1, to: 0, by: -1) { - // 交换根节点与最右叶节点(即交换首元素与尾元素) - nums.swapAt(0, i) - // 以根节点为起点,从顶至底进行堆化 - siftDown(nums: &nums, n: i, i: 0) - } - } - ``` - -=== "Zig" - - ```zig title="heap_sort.zig" - [class]{}-[func]{siftDown} - - [class]{}-[func]{heapSort} - ``` - === "Dart" ```dart title="heap_sort.dart" @@ -542,6 +489,59 @@ comments: true } ``` +=== "C" + + ```c title="heap_sort.c" + /* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ + void siftDown(int nums[], int n, int i) { + while (1) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) { + break; + } + // 交换两节点 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 循环向下堆化 + i = ma; + } + } + + /* 堆排序 */ + void heapSort(int nums[], int n) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = n / 2 - 1; i >= 0; --i) { + siftDown(nums, n, i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = n - 1; i > 0; --i) { + // 交换根节点与最右叶节点(即交换首元素与尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } + } + ``` + +=== "Zig" + + ```zig title="heap_sort.zig" + [class]{}-[func]{siftDown} + + [class]{}-[func]{heapSort} + ``` + ## 11.7.2   算法特性 - **时间复杂度 $O(n \log n)$、非自适应排序**:建堆操作使用 $O(n)$ 时间。从堆中提取最大元素的时间复杂度为 $O(\log n)$ ,共循环 $n - 1$ 轮。 diff --git a/chapter_sorting/insertion_sort.md b/chapter_sorting/insertion_sort.md index 7372cbaf7..27e483ccb 100755 --- a/chapter_sorting/insertion_sort.md +++ b/chapter_sorting/insertion_sort.md @@ -27,22 +27,20 @@ comments: true

图 11-7   插入排序流程

-=== "Java" +=== "Python" - ```java title="insertion_sort.java" - /* 插入排序 */ - void insertionSort(int[] nums) { - // 外循环:已排序元素数量为 1, 2, ..., n - 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]; // 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = base; // 将 base 赋值到正确位置 - } - } + ```python title="insertion_sort.py" + def insertion_sort(nums: list[int]): + """插入排序""" + # 外循环:已排序区间为 [0, i-1] + for i in range(1, len(nums)): + base = nums[i] + j = i - 1 + # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 + while j >= 0 and nums[j] > base: + nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 + j -= 1 + nums[j + 1] = base # 将 base 赋值到正确位置 ``` === "C++" @@ -63,20 +61,40 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="insertion_sort.py" - def insertion_sort(nums: list[int]): - """插入排序""" - # 外循环:已排序区间为 [0, i-1] - for i in range(1, len(nums)): - base = nums[i] - j = i - 1 - # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 - while j >= 0 and nums[j] > base: - nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 - j -= 1 - nums[j + 1] = base # 将 base 赋值到正确位置 + ```java title="insertion_sort.java" + /* 插入排序 */ + void insertionSort(int[] nums) { + // 外循环:已排序元素数量为 1, 2, ..., n + 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]; // 将 nums[j] 向右移动一位 + j--; + } + nums[j + 1] = base; // 将 base 赋值到正确位置 + } + } + ``` + +=== "C#" + + ```csharp title="insertion_sort.cs" + /* 插入排序 */ + void insertionSort(int[] nums) { + // 外循环:已排序元素数量为 1, 2, ..., n + 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]; // 将 nums[j] 向右移动一位 + j--; + } + nums[j + 1] = bas; // 将 base 赋值到正确位置 + } + } ``` === "Go" @@ -98,6 +116,25 @@ comments: true } ``` +=== "Swift" + + ```swift title="insertion_sort.swift" + /* 插入排序 */ + func insertionSort(nums: inout [Int]) { + // 外循环:已排序元素数量为 1, 2, ..., n + 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] // 将 nums[j] 向右移动一位 + j -= 1 + } + nums[j + 1] = base // 将 base 赋值到正确位置 + } + } + ``` + === "JS" ```javascript title="insertion_sort.js" @@ -136,82 +173,6 @@ comments: true } ``` -=== "C" - - ```c title="insertion_sort.c" - /* 插入排序 */ - void insertionSort(int nums[], int size) { - // 外循环:已排序元素数量为 1, 2, ..., n - for (int i = 1; i < size; i++) { - int base = nums[i], j = i - 1; - // 内循环:将 base 插入到已排序部分的正确位置 - while (j >= 0 && nums[j] > base) { - // 将 nums[j] 向右移动一位 - nums[j + 1] = nums[j]; - j--; - } - // 将 base 赋值到正确位置 - nums[j + 1] = base; - } - } - ``` - -=== "C#" - - ```csharp title="insertion_sort.cs" - /* 插入排序 */ - void insertionSort(int[] nums) { - // 外循环:已排序元素数量为 1, 2, ..., n - 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]; // 将 nums[j] 向右移动一位 - j--; - } - nums[j + 1] = bas; // 将 base 赋值到正确位置 - } - } - ``` - -=== "Swift" - - ```swift title="insertion_sort.swift" - /* 插入排序 */ - func insertionSort(nums: inout [Int]) { - // 外循环:已排序元素数量为 1, 2, ..., n - 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] // 将 nums[j] 向右移动一位 - j -= 1 - } - nums[j + 1] = base // 将 base 赋值到正确位置 - } - } - ``` - -=== "Zig" - - ```zig title="insertion_sort.zig" - // 插入排序 - fn insertionSort(nums: []i32) void { - // 外循环:已排序元素数量为 1, 2, ..., n - var i: usize = 1; - while (i < nums.len) : (i += 1) { - var base = nums[i]; - var j: usize = i; - // 内循环:将 base 插入到已排序部分的正确位置 - while (j >= 1 and nums[j - 1] > base) : (j -= 1) { - nums[j] = nums[j - 1]; // 将 nums[j] 向右移动一位 - } - nums[j] = base; // 将 base 赋值到正确位置 - } - } - ``` - === "Dart" ```dart title="insertion_sort.dart" @@ -248,6 +209,45 @@ comments: true } ``` +=== "C" + + ```c title="insertion_sort.c" + /* 插入排序 */ + void insertionSort(int nums[], int size) { + // 外循环:已排序元素数量为 1, 2, ..., n + for (int i = 1; i < size; i++) { + int base = nums[i], j = i - 1; + // 内循环:将 base 插入到已排序部分的正确位置 + while (j >= 0 && nums[j] > base) { + // 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; + j--; + } + // 将 base 赋值到正确位置 + nums[j + 1] = base; + } + } + ``` + +=== "Zig" + + ```zig title="insertion_sort.zig" + // 插入排序 + fn insertionSort(nums: []i32) void { + // 外循环:已排序元素数量为 1, 2, ..., n + var i: usize = 1; + while (i < nums.len) : (i += 1) { + var base = nums[i]; + var j: usize = i; + // 内循环:将 base 插入到已排序部分的正确位置 + while (j >= 1 and nums[j - 1] > base) : (j -= 1) { + nums[j] = nums[j - 1]; // 将 nums[j] 向右移动一位 + } + nums[j] = base; // 将 base 赋值到正确位置 + } + } + ``` + ## 11.4.2   算法特性 - **时间复杂度 $O(n^2)$、自适应排序**:最差情况下,每次插入操作分别需要循环 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此时间复杂度为 $O(n^2)$ 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 $O(n)$ 。 diff --git a/chapter_sorting/merge_sort.md b/chapter_sorting/merge_sort.md index 591d4bbc8..9e2289c41 100755 --- a/chapter_sorting/merge_sort.md +++ b/chapter_sorting/merge_sort.md @@ -59,47 +59,50 @@ comments: true - **后序遍历**:先递归左子树,再递归右子树,最后处理根节点。 - **归并排序**:先递归左子数组,再递归右子数组,最后处理合并。 -=== "Java" +=== "Python" - ```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++]; - } - } + ```python title="merge_sort.py" + def merge(nums: list[int], left: int, mid: int, right: int): + """合并左子数组和右子数组""" + # 左子数组区间 [left, mid] + # 右子数组区间 [mid + 1, right] + # 初始化辅助数组 + tmp = list(nums[left : right + 1]) + # 左子数组的起始索引和结束索引 + left_start = 0 + left_end = mid - left + # 右子数组的起始索引和结束索引 + right_start = mid + 1 - left + right_end = right - left + # i, j 分别指向左子数组、右子数组的首元素 + i = left_start + j = 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 - /* 归并排序 */ - 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); - } + def merge_sort(nums: list[int], left: int, right: int): + """归并排序""" + # 终止条件 + 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) ``` === "C++" @@ -145,50 +148,89 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="merge_sort.py" - def merge(nums: list[int], left: int, mid: int, right: int): - """合并左子数组和右子数组""" - # 左子数组区间 [left, mid] - # 右子数组区间 [mid + 1, right] - # 初始化辅助数组 - tmp = list(nums[left : right + 1]) - # 左子数组的起始索引和结束索引 - left_start = 0 - left_end = mid - left - # 右子数组的起始索引和结束索引 - right_start = mid + 1 - left - right_end = right - left - # i, j 分别指向左子数组、右子数组的首元素 - i = left_start - j = 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 + ```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++]; + } + } - def merge_sort(nums: list[int], left: int, right: int): - """归并排序""" - # 终止条件 - 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) + /* 归并排序 */ + 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#" + + ```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); + } ``` === "Go" @@ -242,6 +284,59 @@ comments: true } ``` +=== "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) + } + ``` + === "JS" ```javascript title="merge_sort.js" @@ -334,202 +429,6 @@ comments: true } ``` -=== "C" - - ```c title="merge_sort.c" - /* 合并左子数组和右子数组 */ - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - void merge(int *nums, int left, int mid, int right) { - int index; - // 初始化辅助数组 - int tmp[right + 1 - left]; - for (index = left; index < right + 1; index++) { - tmp[index - left] = nums[index]; - } - // 左子数组的起始索引和结束索引 - 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#" - - ```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" - // 合并左子数组和右子数组 - // 左子数组区间 [left, mid] - // 右子数组区间 [mid + 1, right] - fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { - // 初始化辅助数组 - var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer mem_arena.deinit(); - const mem_allocator = mem_arena.allocator(); - var tmp = try mem_allocator.alloc(i32, right + 1 - left); - std.mem.copy(i32, tmp, nums[left..right+1]); - // 左子数组的起始索引和结束索引 - var leftStart = left - left; - var leftEnd = mid - left; - // 右子数组的起始索引和结束索引 - var rightStart = mid + 1 - left; - var rightEnd = right - left; - // i, j 分别指向左子数组、右子数组的首元素 - var i = leftStart; - var j = rightStart; - // 通过覆盖原数组 nums 来合并左子数组和右子数组 - var k = left; - while (k <= right) : (k += 1) { - // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if (i > leftEnd) { - nums[k] = tmp[j]; - j += 1; - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if (j > rightEnd or tmp[i] <= tmp[j]) { - nums[k] = tmp[i]; - i += 1; - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ - } else { - nums[k] = tmp[j]; - j += 1; - } - } - } - - // 归并排序 - fn mergeSort(nums: []i32, left: usize, right: usize) !void { - // 终止条件 - if (left >= right) return; // 当子数组长度为 1 时终止递归 - // 划分阶段 - var mid = (left + right) / 2; // 计算中点 - try mergeSort(nums, left, mid); // 递归左子数组 - try mergeSort(nums, mid + 1, right); // 递归右子数组 - // 合并阶段 - try merge(nums, left, mid, right); - } - ``` - === "Dart" ```dart title="merge_sort.dart" @@ -620,6 +519,107 @@ comments: true } ``` +=== "C" + + ```c title="merge_sort.c" + /* 合并左子数组和右子数组 */ + // 左子数组区间 [left, mid] + // 右子数组区间 [mid + 1, right] + void merge(int *nums, int left, int mid, int right) { + int index; + // 初始化辅助数组 + int tmp[right + 1 - left]; + for (index = left; index < right + 1; index++) { + tmp[index - left] = nums[index]; + } + // 左子数组的起始索引和结束索引 + 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); + } + ``` + +=== "Zig" + + ```zig title="merge_sort.zig" + // 合并左子数组和右子数组 + // 左子数组区间 [left, mid] + // 右子数组区间 [mid + 1, right] + fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { + // 初始化辅助数组 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var tmp = try mem_allocator.alloc(i32, right + 1 - left); + std.mem.copy(i32, tmp, nums[left..right+1]); + // 左子数组的起始索引和结束索引 + var leftStart = left - left; + var leftEnd = mid - left; + // 右子数组的起始索引和结束索引 + var rightStart = mid + 1 - left; + var rightEnd = right - left; + // i, j 分别指向左子数组、右子数组的首元素 + var i = leftStart; + var j = rightStart; + // 通过覆盖原数组 nums 来合并左子数组和右子数组 + var k = left; + while (k <= right) : (k += 1) { + // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ + if (i > leftEnd) { + nums[k] = tmp[j]; + j += 1; + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ + } else if (j > rightEnd or tmp[i] <= tmp[j]) { + nums[k] = tmp[i]; + i += 1; + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + } else { + nums[k] = tmp[j]; + j += 1; + } + } + } + + // 归并排序 + fn mergeSort(nums: []i32, left: usize, right: usize) !void { + // 终止条件 + if (left >= right) return; // 当子数组长度为 1 时终止递归 + // 划分阶段 + var mid = (left + right) / 2; // 计算中点 + try mergeSort(nums, left, mid); // 递归左子数组 + try mergeSort(nums, mid + 1, right); // 递归右子数组 + // 合并阶段 + try merge(nums, left, mid, right); + } + ``` + 实现合并函数 `merge()` 存在以下难点。 - **需要特别注意各个变量的含义**。`nums` 的待合并区间为 `[left, right]` ,但由于 `tmp` 仅复制了 `nums` 该区间的元素,因此 `tmp` 对应区间为 `[0, right - left]` 。 diff --git a/chapter_sorting/quick_sort.md b/chapter_sorting/quick_sort.md index 6a87be9d9..bd2c8256b 100755 --- a/chapter_sorting/quick_sort.md +++ b/chapter_sorting/quick_sort.md @@ -47,30 +47,23 @@ comments: true 哨兵划分的实质是将一个较长数组的排序问题简化为两个较短数组的排序问题。 -=== "Java" +=== "Python" - ```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; // 返回基准数的索引 - } + ```python title="quick_sort.py" + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分""" + # 以 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 # 返回基准数的索引 ``` === "C++" @@ -99,23 +92,56 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="quick_sort.py" - def partition(self, nums: list[int], left: int, right: int) -> int: - """哨兵划分""" - # 以 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 # 返回基准数的索引 + ```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#" + + ```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; // 返回基准数的索引 + } ``` === "Go" @@ -141,6 +167,35 @@ comments: true } ``` +=== "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 // 返回基准数的索引 + } + ``` + === "JS" ```javascript title="quick_sort.js" @@ -201,120 +256,6 @@ comments: true } ``` -=== "C" - - ```c title="quick_sort.c" - /* 元素交换 */ - 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#" - - ```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" - // 元素交换 - fn swap(nums: []i32, i: usize, j: usize) void { - var tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - // 哨兵划分 - fn partition(nums: []i32, left: usize, right: usize) usize { - // 以 nums[left] 作为基准数 - var i = left; - var j = right; - while (i < j) { - while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 - while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - === "Dart" ```dart title="quick_sort.dart" @@ -360,6 +301,65 @@ comments: true } ``` +=== "C" + + ```c title="quick_sort.c" + /* 元素交换 */ + 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; + } + ``` + +=== "Zig" + + ```zig title="quick_sort.zig" + // 元素交换 + fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵划分 + fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 作为基准数 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + ``` + ## 11.5.1   算法流程 快速排序的整体流程如图 11-9 所示。 @@ -372,20 +372,19 @@ comments: true

图 11-9   快速排序流程

-=== "Java" +=== "Python" - ```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); - } + ```python title="quick_sort.py" + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序""" + # 子数组长度为 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) ``` === "C++" @@ -404,19 +403,36 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="quick_sort.py" - def quick_sort(self, nums: list[int], left: int, right: int): - """快速排序""" - # 子数组长度为 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) + ```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#" + + ```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); + } ``` === "Go" @@ -436,6 +452,23 @@ comments: true } ``` +=== "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) + } + ``` + === "JS" ```javascript title="quick_sort.js" @@ -468,6 +501,38 @@ comments: true } ``` +=== "Dart" + + ```dart title="quick_sort.dart" + /* 快速排序 */ + void quickSort(List 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); + } + ``` + +=== "Rust" + + ```rust title="quick_sort.rs" + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子数组长度为 1 时终止递归 + if left >= right { + return; + } + // 哨兵划分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 递归左子数组、右子数组 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } + ``` + === "C" ```c title="quick_sort.c" @@ -508,39 +573,6 @@ comments: true } ``` -=== "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" @@ -556,38 +588,6 @@ comments: true } ``` -=== "Dart" - - ```dart title="quick_sort.dart" - /* 快速排序 */ - void quickSort(List 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); - } - ``` - -=== "Rust" - - ```rust title="quick_sort.rs" - /* 快速排序 */ - pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { - // 子数组长度为 1 时终止递归 - if left >= right { - return; - } - // 哨兵划分 - let pivot = Self::partition(nums, left as usize, right as usize) as i32; - // 递归左子数组、右子数组 - Self::quick_sort(left, pivot - 1, nums); - Self::quick_sort(pivot + 1, right, nums); - } - ``` - ## 11.5.2   算法特性 - **时间复杂度 $O(n \log n)$、自适应排序**:在平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。在最差情况下,每轮哨兵划分操作都将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 @@ -612,39 +612,37 @@ comments: true 为了进一步改进,我们可以在数组中选取三个候选元素(通常为数组的首、尾、中点元素),**并将这三个候选元素的中位数作为基准数**。这样一来,基准数“既不太小也不太大”的概率将大幅提升。当然,我们还可以选取更多候选元素,以进一步提高算法的稳健性。采用这种方法后,时间复杂度劣化至 $O(n^2)$ 的概率大大降低。 -=== "Java" +=== "Python" - ```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; - } + ```python title="quick_sort.py" + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """选取三个元素的中位数""" + # 此处使用异或运算来简化代码 + # 异或规则为 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 - /* 哨兵划分(三数取中值) */ - 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; // 返回基准数的索引 - } + def partition(self, nums: list[int], left: int, right: int) -> int: + """哨兵划分(三数取中值)""" + # 以 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 # 返回基准数的索引 ``` === "C++" @@ -682,37 +680,74 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="quick_sort.py" - def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: - """选取三个元素的中位数""" - # 此处使用异或运算来简化代码 - # 异或规则为 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 + ```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; + } - def partition(self, nums: list[int], left: int, right: int) -> int: - """哨兵划分(三数取中值)""" - # 以 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 # 返回基准数的索引 + /* 哨兵划分(三数取中值) */ + 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#" + + ```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] 作为基准数 + 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; // 返回基准数的索引 + } ``` === "Go" @@ -754,6 +789,30 @@ comments: true } ``` +=== "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 partitionMedian(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) + return partition(nums: &nums, left: left, right: right) + } + ``` + === "JS" ```javascript title="quick_sort.js" @@ -842,149 +901,6 @@ comments: true } ``` -=== "C" - - ```c title="quick_sort.c" - /* 快速排序类(中位基准数优化) */ - // 选取三个元素的中位数 - 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 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 partitionMedian(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#" - - ```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] 作为基准数 - 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 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 partitionMedian(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) - return partition(nums: &nums, left: left, right: right) - } - ``` - -=== "Zig" - - ```zig title="quick_sort.zig" - // 选取三个元素的中位数 - fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { - // 此处使用异或运算来简化代码 - // 异或规则为 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; - } - } - - // 哨兵划分(三数取中值) - fn partition(nums: []i32, left: usize, right: usize) usize { - // 选取三个候选元素的中位数 - var med = medianThree(nums, left, (left + right) / 2, right); - // 将中位数交换至数组最左端 - swap(nums, left, med); - // 以 nums[left] 作为基准数 - var i = left; - var j = right; - while (i < j) { - while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 - while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 - swap(nums, i, j); // 交换这两个元素 - } - swap(nums, i, left); // 将基准数交换至两子数组的分界线 - return i; // 返回基准数的索引 - } - ``` - === "Dart" ```dart title="quick_sort.dart" @@ -1055,31 +971,112 @@ comments: true } ``` +=== "C" + + ```c title="quick_sort.c" + /* 快速排序类(中位基准数优化) */ + // 选取三个元素的中位数 + 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 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 partitionMedian(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; // 返回基准数的索引 + } + ``` + +=== "Zig" + + ```zig title="quick_sort.zig" + // 选取三个元素的中位数 + fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { + // 此处使用异或运算来简化代码 + // 异或规则为 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; + } + } + + // 哨兵划分(三数取中值) + fn partition(nums: []i32, left: usize, right: usize) usize { + // 选取三个候选元素的中位数 + var med = medianThree(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + swap(nums, left, med); + // 以 nums[left] 作为基准数 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 从右向左找首个小于基准数的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + ``` + ## 11.5.5   尾递归优化 **在某些输入下,快速排序可能占用空间较多**。以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 $0$ ,递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。 为了防止栈帧空间的累积,我们可以在每轮哨兵排序完成后,比较两个子数组的长度,**仅对较短的子数组进行递归**。由于较短子数组的长度不会超过 $n / 2$ ,因此这种方法能确保递归深度不超过 $\log n$ ,从而将最差空间复杂度优化至 $O(\log n)$ 。 -=== "Java" +=== "Python" - ```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] - } - } - } + ```python title="quick_sort.py" + def quick_sort(self, nums: list[int], left: int, right: int): + """快速排序(尾递归优化)""" + # 子数组长度为 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] ``` === "C++" @@ -1103,22 +1100,46 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="quick_sort.py" - def quick_sort(self, nums: list[int], left: int, right: int): - """快速排序(尾递归优化)""" - # 子数组长度为 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] + ```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#" + + ```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] + } + } + } ``` === "Go" @@ -1142,6 +1163,29 @@ comments: true } ``` +=== "Swift" + + ```swift title="quick_sort.swift" + /* 快速排序(尾递归优化) */ + func quickSortTailCall(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) { + quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 递归排序左子数组 + left = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 递归排序右子数组 + right = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] + } + } + } + ``` + === "JS" ```javascript title="quick_sort.js" @@ -1184,95 +1228,6 @@ comments: true } ``` -=== "C" - - ```c title="quick_sort.c" - /* 快速排序类(尾递归优化) */ - // 快速排序(尾递归优化) - void quickSortTailCall(int nums[], int left, int right) { - // 子数组长度为 1 时终止 - while (left < right) { - // 哨兵划分操作 - int pivot = partition(nums, left, right); - // 对两个子数组中较短的那个执行快排 - if (pivot - left < right - pivot) { - quickSortTailCall(nums, left, pivot - 1); // 递归排序左子数组 - left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] - } else { - quickSortTailCall(nums, pivot + 1, right); // 递归排序右子数组 - right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "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 quickSortTailCall(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) { - quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 递归排序左子数组 - left = pivot + 1 // 剩余未排序区间为 [pivot + 1, right] - } else { - quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 递归排序右子数组 - right = pivot - 1 // 剩余未排序区间为 [left, pivot - 1] - } - } - } - ``` - -=== "Zig" - - ```zig title="quick_sort.zig" - // 快速排序(尾递归优化) - fn quickSort(nums: []i32, left_: usize, right_: usize) void { - var left = left_; - var right = right_; - // 子数组长度为 1 时终止递归 - while (left < right) { - // 哨兵划分操作 - var 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] - } - } - } - ``` - === "Dart" ```dart title="quick_sort.dart" @@ -1314,3 +1269,48 @@ comments: true } } ``` + +=== "C" + + ```c title="quick_sort.c" + /* 快速排序类(尾递归优化) */ + // 快速排序(尾递归优化) + void quickSortTailCall(int nums[], int left, int right) { + // 子数组长度为 1 时终止 + while (left < right) { + // 哨兵划分操作 + int pivot = partition(nums, left, right); + // 对两个子数组中较短的那个执行快排 + if (pivot - left < right - pivot) { + quickSortTailCall(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSortTailCall(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] + } + } + } + ``` + +=== "Zig" + + ```zig title="quick_sort.zig" + // 快速排序(尾递归优化) + fn quickSort(nums: []i32, left_: usize, right_: usize) void { + var left = left_; + var right = right_; + // 子数组长度为 1 时终止递归 + while (left < right) { + // 哨兵划分操作 + var 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] + } + } + } + ``` diff --git a/chapter_sorting/radix_sort.md b/chapter_sorting/radix_sort.md index cee5fbbd7..cbb642444 100644 --- a/chapter_sorting/radix_sort.md +++ b/chapter_sorting/radix_sort.md @@ -30,6 +30,102 @@ $$ 此外,我们需要小幅改动计数排序代码,使之可以根据数字的第 $k$ 位进行排序。 +=== "Python" + + ```python title="radix_sort.py" + def digit(num: int, exp: int) -> int: + """获取元素 num 的第 k 位,其中 exp = 10^(k-1)""" + # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num // exp) % 10 + + def counting_sort_digit(nums: list[int], exp: int): + """计数排序(根据 nums 第 k 位排序)""" + # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 + counter = [0] * 10 + n = len(nums) + # 统计 0~9 各数字的出现次数 + for i in range(n): + d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d + counter[d] += 1 # 统计数字 d 的出现次数 + # 求前缀和,将“出现个数”转换为“数组索引” + for i in range(1, 10): + counter[i] += counter[i - 1] + # 倒序遍历,根据桶内统计结果,将各元素填入 res + res = [0] * n + for i in range(n - 1, -1, -1): + d = digit(nums[i], exp) + j = counter[d] - 1 # 获取 d 在数组中的索引 j + res[j] = nums[i] # 将当前元素填入索引 j + counter[d] -= 1 # 将 d 的数量减 1 + # 使用结果覆盖原数组 nums + for i in range(n): + nums[i] = res[i] + + def radix_sort(nums: list[int]): + """基数排序""" + # 获取数组的最大元素,用于判断最大位数 + m = max(nums) + # 按照从低位到高位的顺序遍历 + exp = 1 + while exp <= m: + # 对数组元素的第 k 位执行计数排序 + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # 即 exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + ``` + +=== "C++" + + ```cpp title="radix_sort.cpp" + /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + int digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10; + } + + /* 计数排序(根据 nums 第 k 位排序) */ + void countingSortDigit(vector &nums, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 + vector counter(10, 0); + int n = nums.size(); + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + vector res(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d]--; // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for (int i = 0; i < n; i++) + nums[i] = res[i]; + } + + /* 基数排序 */ + void radixSort(vector &nums) { + // 获取数组的最大元素,用于判断最大位数 + int m = *max_element(nums.begin(), nums.end()); + // 按照从低位到高位的顺序遍历 + for (int exp = 1; exp <= m; exp *= 10) + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } + ``` + === "Java" ```java title="radix_sort.java" @@ -83,9 +179,9 @@ $$ } ``` -=== "C++" +=== "C#" - ```cpp title="radix_sort.cpp" + ```csharp title="radix_sort.cs" /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ int digit(int num, int exp) { // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 @@ -93,10 +189,10 @@ $$ } /* 计数排序(根据 nums 第 k 位排序) */ - void countingSortDigit(vector &nums, int exp) { + void countingSortDigit(int[] nums, int exp) { // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 - vector counter(10, 0); - int n = nums.size(); + int[] counter = new int[10]; + int n = nums.Length; // 统计 0~9 各数字的出现次数 for (int i = 0; i < n; i++) { int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d @@ -107,7 +203,7 @@ $$ counter[i] += counter[i - 1]; } // 倒序遍历,根据桶内统计结果,将各元素填入 res - vector res(n, 0); + int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); int j = counter[d] - 1; // 获取 d 在数组中的索引 j @@ -115,70 +211,29 @@ $$ counter[d]--; // 将 d 的数量减 1 } // 使用结果覆盖原数组 nums - for (int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { nums[i] = res[i]; + } } /* 基数排序 */ - void radixSort(vector &nums) { + void radixSort(int[] nums) { // 获取数组的最大元素,用于判断最大位数 - int m = *max_element(nums.begin(), nums.end()); + int m = int.MinValue; + foreach (int num in nums) { + if (num > m) m = num; + } // 按照从低位到高位的顺序遍历 - for (int exp = 1; exp <= m; exp *= 10) + for (int exp = 1; exp <= m; exp *= 10) { // 对数组元素的第 k 位执行计数排序 // k = 1 -> exp = 1 // k = 2 -> exp = 10 // 即 exp = 10^(k-1) countingSortDigit(nums, exp); + } } ``` -=== "Python" - - ```python title="radix_sort.py" - def digit(num: int, exp: int) -> int: - """获取元素 num 的第 k 位,其中 exp = 10^(k-1)""" - # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 - return (num // exp) % 10 - - def counting_sort_digit(nums: list[int], exp: int): - """计数排序(根据 nums 第 k 位排序)""" - # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 - counter = [0] * 10 - n = len(nums) - # 统计 0~9 各数字的出现次数 - for i in range(n): - d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d - counter[d] += 1 # 统计数字 d 的出现次数 - # 求前缀和,将“出现个数”转换为“数组索引” - for i in range(1, 10): - counter[i] += counter[i - 1] - # 倒序遍历,根据桶内统计结果,将各元素填入 res - res = [0] * n - for i in range(n - 1, -1, -1): - d = digit(nums[i], exp) - j = counter[d] - 1 # 获取 d 在数组中的索引 j - res[j] = nums[i] # 将当前元素填入索引 j - counter[d] -= 1 # 将 d 的数量减 1 - # 使用结果覆盖原数组 nums - for i in range(n): - nums[i] = res[i] - - def radix_sort(nums: list[int]): - """基数排序""" - # 获取数组的最大元素,用于判断最大位数 - m = max(nums) - # 按照从低位到高位的顺序遍历 - exp = 1 - while exp <= m: - # 对数组元素的第 k 位执行计数排序 - # k = 1 -> exp = 1 - # k = 2 -> exp = 10 - # 即 exp = 10^(k-1) - counting_sort_digit(nums, exp) - exp *= 10 - ``` - === "Go" ```go title="radix_sort.go" @@ -236,6 +291,63 @@ $$ } ``` +=== "Swift" + + ```swift title="radix_sort.swift" + /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + func digit(num: Int, exp: Int) -> Int { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + (num / exp) % 10 + } + + /* 计数排序(根据 nums 第 k 位排序) */ + func countingSortDigit(nums: inout [Int], exp: Int) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 + var counter = Array(repeating: 0, count: 10) + let n = nums.count + // 统计 0~9 各数字的出现次数 + for i in nums.indices { + let d = digit(num: nums[i], exp: exp) // 获取 nums[i] 第 k 位,记为 d + counter[d] += 1 // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for i in 1 ..< 10 { + counter[i] += counter[i - 1] + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + var res = Array(repeating: 0, count: n) + for i in stride(from: n - 1, through: 0, by: -1) { + let d = digit(num: nums[i], exp: exp) + let j = counter[d] - 1 // 获取 d 在数组中的索引 j + res[j] = nums[i] // 将当前元素填入索引 j + counter[d] -= 1 // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for i in nums.indices { + nums[i] = res[i] + } + } + + /* 基数排序 */ + func radixSort(nums: inout [Int]) { + // 获取数组的最大元素,用于判断最大位数 + var m = Int.min + for num in nums { + if num > m { + m = num + } + } + // 按照从低位到高位的顺序遍历 + for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums: &nums, exp: exp) + } + } + ``` + === "JS" ```javascript title="radix_sort.js" @@ -350,239 +462,6 @@ $$ } ``` -=== "C" - - ```c title="radix_sort.c" - /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ - int digit(int num, int exp) { - // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 - return (num / exp) % 10; - } - - /* 计数排序(根据 nums 第 k 位排序) */ - void countingSortDigit(int nums[], int size, int exp) { - // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 - int *counter = (int *)malloc((sizeof(int) * 10)); - // 统计 0~9 各数字的出现次数 - for (int i = 0; i < size; i++) { - // 获取 nums[i] 第 k 位,记为 d - int d = digit(nums[i], exp); - // 统计数字 d 的出现次数 - counter[d]++; - } - // 求前缀和,将“出现个数”转换为“数组索引” - for (int i = 1; i < 10; i++) { - counter[i] += counter[i - 1]; - } - // 倒序遍历,根据桶内统计结果,将各元素填入 res - int *res = (int *)malloc(sizeof(int) * size); - for (int i = size - 1; i >= 0; i--) { - int d = digit(nums[i], exp); - int j = counter[d] - 1; // 获取 d 在数组中的索引 j - res[j] = nums[i]; // 将当前元素填入索引 j - counter[d]--; // 将 d 的数量减 1 - } - // 使用结果覆盖原数组 nums - for (int i = 0; i < size; i++) { - nums[i] = res[i]; - } - } - - /* 基数排序 */ - void radixSort(int nums[], int size) { - // 获取数组的最大元素,用于判断最大位数 - int max = INT32_MIN; - for (size_t i = 0; i < size - 1; i++) { - if (nums[i] > max) { - max = nums[i]; - } - } - // 按照从低位到高位的顺序遍历 - for (int exp = 1; max >= exp; exp *= 10) - // 对数组元素的第 k 位执行计数排序 - // k = 1 -> exp = 1 - // k = 2 -> exp = 10 - // 即 exp = 10^(k-1) - countingSortDigit(nums, size, exp); - } - ``` - -=== "C#" - - ```csharp title="radix_sort.cs" - /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ - int digit(int num, int exp) { - // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 - return (num / exp) % 10; - } - - /* 计数排序(根据 nums 第 k 位排序) */ - void countingSortDigit(int[] nums, int exp) { - // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 - int[] counter = new int[10]; - int n = nums.Length; - // 统计 0~9 各数字的出现次数 - for (int i = 0; i < n; i++) { - int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d - counter[d]++; // 统计数字 d 的出现次数 - } - // 求前缀和,将“出现个数”转换为“数组索引” - for (int i = 1; i < 10; i++) { - counter[i] += counter[i - 1]; - } - // 倒序遍历,根据桶内统计结果,将各元素填入 res - int[] res = new int[n]; - for (int i = n - 1; i >= 0; i--) { - int d = digit(nums[i], exp); - int j = counter[d] - 1; // 获取 d 在数组中的索引 j - res[j] = nums[i]; // 将当前元素填入索引 j - counter[d]--; // 将 d 的数量减 1 - } - // 使用结果覆盖原数组 nums - for (int i = 0; i < n; i++) { - nums[i] = res[i]; - } - } - - /* 基数排序 */ - void radixSort(int[] nums) { - // 获取数组的最大元素,用于判断最大位数 - int m = int.MinValue; - foreach (int num in nums) { - if (num > m) m = num; - } - // 按照从低位到高位的顺序遍历 - for (int exp = 1; exp <= m; exp *= 10) { - // 对数组元素的第 k 位执行计数排序 - // k = 1 -> exp = 1 - // k = 2 -> exp = 10 - // 即 exp = 10^(k-1) - countingSortDigit(nums, exp); - } - } - ``` - -=== "Swift" - - ```swift title="radix_sort.swift" - /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ - func digit(num: Int, exp: Int) -> Int { - // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 - (num / exp) % 10 - } - - /* 计数排序(根据 nums 第 k 位排序) */ - func countingSortDigit(nums: inout [Int], exp: Int) { - // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 - var counter = Array(repeating: 0, count: 10) - let n = nums.count - // 统计 0~9 各数字的出现次数 - for i in nums.indices { - let d = digit(num: nums[i], exp: exp) // 获取 nums[i] 第 k 位,记为 d - counter[d] += 1 // 统计数字 d 的出现次数 - } - // 求前缀和,将“出现个数”转换为“数组索引” - for i in 1 ..< 10 { - counter[i] += counter[i - 1] - } - // 倒序遍历,根据桶内统计结果,将各元素填入 res - var res = Array(repeating: 0, count: n) - for i in stride(from: n - 1, through: 0, by: -1) { - let d = digit(num: nums[i], exp: exp) - let j = counter[d] - 1 // 获取 d 在数组中的索引 j - res[j] = nums[i] // 将当前元素填入索引 j - counter[d] -= 1 // 将 d 的数量减 1 - } - // 使用结果覆盖原数组 nums - for i in nums.indices { - nums[i] = res[i] - } - } - - /* 基数排序 */ - func radixSort(nums: inout [Int]) { - // 获取数组的最大元素,用于判断最大位数 - var m = Int.min - for num in nums { - if num > m { - m = num - } - } - // 按照从低位到高位的顺序遍历 - for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { - // 对数组元素的第 k 位执行计数排序 - // k = 1 -> exp = 1 - // k = 2 -> exp = 10 - // 即 exp = 10^(k-1) - countingSortDigit(nums: &nums, exp: exp) - } - } - ``` - -=== "Zig" - - ```zig title="radix_sort.zig" - // 获取元素 num 的第 k 位,其中 exp = 10^(k-1) - fn digit(num: i32, exp: i32) i32 { - // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 - return @mod(@divFloor(num, exp), 10); - } - - // 计数排序(根据 nums 第 k 位排序) - fn countingSortDigit(nums: []i32, exp: i32) !void { - // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 - var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - // defer mem_arena.deinit(); - const mem_allocator = mem_arena.allocator(); - var counter = try mem_allocator.alloc(usize, 10); - @memset(counter, 0); - var n = nums.len; - // 统计 0~9 各数字的出现次数 - for (nums) |num| { - var d: u32 = @bitCast(digit(num, exp)); // 获取 nums[i] 第 k 位,记为 d - counter[d] += 1; // 统计数字 d 的出现次数 - } - // 求前缀和,将“出现个数”转换为“数组索引” - var i: usize = 1; - while (i < 10) : (i += 1) { - counter[i] += counter[i - 1]; - } - // 倒序遍历,根据桶内统计结果,将各元素填入 res - var res = try mem_allocator.alloc(i32, n); - i = n - 1; - while (i >= 0) : (i -= 1) { - var d: u32 = @bitCast(digit(nums[i], exp)); - var j = counter[d] - 1; // 获取 d 在数组中的索引 j - res[j] = nums[i]; // 将当前元素填入索引 j - counter[d] -= 1; // 将 d 的数量减 1 - if (i == 0) break; - } - // 使用结果覆盖原数组 nums - i = 0; - while (i < n) : (i += 1) { - nums[i] = res[i]; - } - } - - // 基数排序 - fn radixSort(nums: []i32) !void { - // 获取数组的最大元素,用于判断最大位数 - var m: i32 = std.math.minInt(i32); - for (nums) |num| { - if (num > m) m = num; - } - // 按照从低位到高位的顺序遍历 - var exp: i32 = 1; - while (exp <= m) : (exp *= 10) { - // 对数组元素的第 k 位执行计数排序 - // k = 1 -> exp = 1 - // k = 2 -> exp = 10 - // 即 exp = 10^(k-1) - try countingSortDigit(nums, exp); - } - } - ``` - === "Dart" ```dart title="radix_sort.dart" @@ -684,6 +563,127 @@ $$ } ``` +=== "C" + + ```c title="radix_sort.c" + /* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + int digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10; + } + + /* 计数排序(根据 nums 第 k 位排序) */ + void countingSortDigit(int nums[], int size, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 + int *counter = (int *)malloc((sizeof(int) * 10)); + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < size; i++) { + // 获取 nums[i] 第 k 位,记为 d + int d = digit(nums[i], exp); + // 统计数字 d 的出现次数 + counter[d]++; + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + int *res = (int *)malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d]--; // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for (int i = 0; i < size; i++) { + nums[i] = res[i]; + } + } + + /* 基数排序 */ + void radixSort(int nums[], int size) { + // 获取数组的最大元素,用于判断最大位数 + int max = INT32_MIN; + for (size_t i = 0; i < size - 1; i++) { + if (nums[i] > max) { + max = nums[i]; + } + } + // 按照从低位到高位的顺序遍历 + for (int exp = 1; max >= exp; exp *= 10) + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, size, exp); + } + ``` + +=== "Zig" + + ```zig title="radix_sort.zig" + // 获取元素 num 的第 k 位,其中 exp = 10^(k-1) + fn digit(num: i32, exp: i32) i32 { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return @mod(@divFloor(num, exp), 10); + } + + // 计数排序(根据 nums 第 k 位排序) + fn countingSortDigit(nums: []i32, exp: i32) !void { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var counter = try mem_allocator.alloc(usize, 10); + @memset(counter, 0); + var n = nums.len; + // 统计 0~9 各数字的出现次数 + for (nums) |num| { + var d: u32 = @bitCast(digit(num, exp)); // 获取 nums[i] 第 k 位,记为 d + counter[d] += 1; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + var i: usize = 1; + while (i < 10) : (i += 1) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + var res = try mem_allocator.alloc(i32, n); + i = n - 1; + while (i >= 0) : (i -= 1) { + var d: u32 = @bitCast(digit(nums[i], exp)); + var j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d] -= 1; // 将 d 的数量减 1 + if (i == 0) break; + } + // 使用结果覆盖原数组 nums + i = 0; + while (i < n) : (i += 1) { + nums[i] = res[i]; + } + } + + // 基数排序 + fn radixSort(nums: []i32) !void { + // 获取数组的最大元素,用于判断最大位数 + var m: i32 = std.math.minInt(i32); + for (nums) |num| { + if (num > m) m = num; + } + // 按照从低位到高位的顺序遍历 + var exp: i32 = 1; + while (exp <= m) : (exp *= 10) { + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + try countingSortDigit(nums, exp); + } + } + ``` + !!! question "为什么从最低位开始排序?" 在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果 $a < b$ ,而第二轮排序结果 $a > b$ ,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,我们应该先排序低位再排序高位。 diff --git a/chapter_sorting/selection_sort.md b/chapter_sorting/selection_sort.md index a9508774a..a0817e8df 100644 --- a/chapter_sorting/selection_sort.md +++ b/chapter_sorting/selection_sort.md @@ -51,26 +51,21 @@ comments: true 在代码中,我们用 $k$ 来记录未排序区间内的最小元素。 -=== "Java" +=== "Python" - ```java title="selection_sort.java" - /* 选择排序 */ - void selectionSort(int[] nums) { - int n = nums.length; - // 外循环:未排序区间为 [i, n-1] - for (int i = 0; i < n - 1; i++) { - // 内循环:找到未排序区间内的最小元素 - int k = i; - for (int j = i + 1; j < n; j++) { - if (nums[j] < nums[k]) - k = j; // 记录最小元素的索引 - } - // 将该最小元素与未排序区间的首个元素交换 - int temp = nums[i]; - nums[i] = nums[k]; - nums[k] = temp; - } - } + ```python title="selection_sort.py" + def selection_sort(nums: list[int]): + """选择排序""" + n = len(nums) + # 外循环:未排序区间为 [i, n-1] + for i in range(n - 1): + # 内循环:找到未排序区间内的最小元素 + k = i + for j in range(i + 1, n): + if nums[j] < nums[k]: + k = j # 记录最小元素的索引 + # 将该最小元素与未排序区间的首个元素交换 + nums[i], nums[k] = nums[k], nums[i] ``` === "C++" @@ -93,21 +88,46 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="selection_sort.py" - def selection_sort(nums: list[int]): - """选择排序""" - n = len(nums) - # 外循环:未排序区间为 [i, n-1] - for i in range(n - 1): - # 内循环:找到未排序区间内的最小元素 - k = i - for j in range(i + 1, n): - if nums[j] < nums[k]: - k = j # 记录最小元素的索引 - # 将该最小元素与未排序区间的首个元素交换 - nums[i], nums[k] = nums[k], nums[i] + ```java title="selection_sort.java" + /* 选择排序 */ + void selectionSort(int[] nums) { + int n = nums.length; + // 外循环:未排序区间为 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } + } + ``` + +=== "C#" + + ```csharp title="selection_sort.cs" + /* 选择排序 */ + void selectionSort(int[] nums) { + int n = nums.Length; + // 外循环:未排序区间为 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + (nums[k], nums[i]) = (nums[i], nums[k]); + } + } ``` === "Go" @@ -133,6 +153,26 @@ comments: true } ``` +=== "Swift" + + ```swift title="selection_sort.swift" + /* 选择排序 */ + func selectionSort(nums: inout [Int]) { + // 外循环:未排序区间为 [i, n-1] + for i in nums.indices.dropLast() { + // 内循环:找到未排序区间内的最小元素 + var k = i + for j in nums.indices.dropFirst(i + 1) { + if nums[j] < nums[k] { + k = j // 记录最小元素的索引 + } + } + // 将该最小元素与未排序区间的首个元素交换 + nums.swapAt(i, k) + } + } + ``` + === "JS" ```javascript title="selection_sort.js" @@ -175,73 +215,6 @@ comments: true } ``` -=== "C" - - ```c title="selection_sort.c" - /* 选择排序 */ - void selectionSort(int nums[], int n) { - // 外循环:未排序区间为 [i, n-1] - for (int i = 0; i < n - 1; i++) { - // 内循环:找到未排序区间内的最小元素 - int k = i; - for (int j = i + 1; j < n; j++) { - if (nums[j] < nums[k]) - k = j; // 记录最小元素的索引 - } - // 将该最小元素与未排序区间的首个元素交换 - int temp = nums[i]; - nums[i] = nums[k]; - nums[k] = temp; - } - } - ``` - -=== "C#" - - ```csharp title="selection_sort.cs" - /* 选择排序 */ - void selectionSort(int[] nums) { - int n = nums.Length; - // 外循环:未排序区间为 [i, n-1] - for (int i = 0; i < n - 1; i++) { - // 内循环:找到未排序区间内的最小元素 - int k = i; - for (int j = i + 1; j < n; j++) { - if (nums[j] < nums[k]) - k = j; // 记录最小元素的索引 - } - // 将该最小元素与未排序区间的首个元素交换 - (nums[k], nums[i]) = (nums[i], nums[k]); - } - } - ``` - -=== "Swift" - - ```swift title="selection_sort.swift" - /* 选择排序 */ - func selectionSort(nums: inout [Int]) { - // 外循环:未排序区间为 [i, n-1] - for i in nums.indices.dropLast() { - // 内循环:找到未排序区间内的最小元素 - var k = i - for j in nums.indices.dropFirst(i + 1) { - if nums[j] < nums[k] { - k = j // 记录最小元素的索引 - } - } - // 将该最小元素与未排序区间的首个元素交换 - nums.swapAt(i, k) - } - } - ``` - -=== "Zig" - - ```zig title="selection_sort.zig" - [class]{}-[func]{selectionSort} - ``` - === "Dart" ```dart title="selection_sort.dart" @@ -284,6 +257,33 @@ comments: true } ``` +=== "C" + + ```c title="selection_sort.c" + /* 选择排序 */ + void selectionSort(int nums[], int n) { + // 外循环:未排序区间为 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } + } + ``` + +=== "Zig" + + ```zig title="selection_sort.zig" + [class]{}-[func]{selectionSort} + ``` + ## 11.2.1   算法特性 - **时间复杂度为 $O(n^2)$、非自适应排序**:外循环共 $n - 1$ 轮,第一轮的未排序区间长度为 $n$ ,最后一轮的未排序区间长度为 $2$ ,即各轮外循环分别包含 $n$、$n - 1$、$\dots$、$3$、$2$ 轮内循环,求和为 $\frac{(n - 1)(n + 2)}{2}$ 。 diff --git a/chapter_stack_and_queue/deque.md b/chapter_stack_and_queue/deque.md index 949166f42..5f1b4d35b 100644 --- a/chapter_stack_and_queue/deque.md +++ b/chapter_stack_and_queue/deque.md @@ -31,32 +31,32 @@ comments: true 同样地,我们可以直接使用编程语言中已实现的双向队列类。 -=== "Java" +=== "Python" - ```java title="deque.java" - /* 初始化双向队列 */ - Deque deque = new LinkedList<>(); + ```python title="deque.py" + # 初始化双向队列 + deque: deque[int] = collections.deque() - /* 元素入队 */ - deque.offerLast(2); // 添加至队尾 - deque.offerLast(5); - deque.offerLast(4); - deque.offerFirst(3); // 添加至队首 - deque.offerFirst(1); + # 元素入队 + deque.append(2) # 添加至队尾 + deque.append(5) + deque.append(4) + deque.appendleft(3) # 添加至队首 + deque.appendleft(1) - /* 访问元素 */ - int peekFirst = deque.peekFirst(); // 队首元素 - int peekLast = deque.peekLast(); // 队尾元素 + # 访问元素 + front: int = deque[0] # 队首元素 + rear: int = deque[-1] # 队尾元素 - /* 元素出队 */ - int popFirst = deque.pollFirst(); // 队首元素出队 - int popLast = deque.pollLast(); // 队尾元素出队 + # 元素出队 + pop_front: int = deque.popleft() # 队首元素出队 + pop_rear: int = deque.pop() # 队尾元素出队 - /* 获取双向队列的长度 */ - int size = deque.size(); + # 获取双向队列的长度 + size: int = len(deque) - /* 判断双向队列是否为空 */ - boolean isEmpty = deque.isEmpty(); + # 判断双向队列是否为空 + is_empty: bool = len(deque) == 0 ``` === "C++" @@ -87,32 +87,61 @@ comments: true bool empty = deque.empty(); ``` -=== "Python" +=== "Java" - ```python title="deque.py" - # 初始化双向队列 - deque: deque[int] = collections.deque() + ```java title="deque.java" + /* 初始化双向队列 */ + Deque deque = new LinkedList<>(); - # 元素入队 - deque.append(2) # 添加至队尾 - deque.append(5) - deque.append(4) - deque.appendleft(3) # 添加至队首 - deque.appendleft(1) + /* 元素入队 */ + deque.offerLast(2); // 添加至队尾 + deque.offerLast(5); + deque.offerLast(4); + deque.offerFirst(3); // 添加至队首 + deque.offerFirst(1); - # 访问元素 - front: int = deque[0] # 队首元素 - rear: int = deque[-1] # 队尾元素 + /* 访问元素 */ + int peekFirst = deque.peekFirst(); // 队首元素 + int peekLast = deque.peekLast(); // 队尾元素 - # 元素出队 - pop_front: int = deque.popleft() # 队首元素出队 - pop_rear: int = deque.pop() # 队尾元素出队 + /* 元素出队 */ + int popFirst = deque.pollFirst(); // 队首元素出队 + int popLast = deque.pollLast(); // 队尾元素出队 - # 获取双向队列的长度 - size: int = len(deque) + /* 获取双向队列的长度 */ + int size = deque.size(); - # 判断双向队列是否为空 - is_empty: bool = len(deque) == 0 + /* 判断双向队列是否为空 */ + boolean isEmpty = deque.isEmpty(); + ``` + +=== "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; ``` === "Go" @@ -144,6 +173,36 @@ comments: true isEmpty := deque.Len() == 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 模拟时 popFirst 的复杂度为 O(n) + let popFirst = deque.removeFirst() // 队首元素出队 + let popLast = deque.removeLast() // 队尾元素出队 + + /* 获取双向队列的长度 */ + let size = deque.count + + /* 判断双向队列是否为空 */ + let isEmpty = deque.isEmpty + ``` + === "JS" ```javascript title="deque.js" @@ -220,77 +279,6 @@ comments: true console.log("双向队列是否为空 = " + isEmpty); ``` -=== "C" - - ```c title="deque.c" - // 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 模拟时 popFirst 的复杂度为 O(n) - let popFirst = deque.removeFirst() // 队首元素出队 - let popLast = deque.removeLast() // 队尾元素出队 - - /* 获取双向队列的长度 */ - let size = deque.count - - /* 判断双向队列是否为空 */ - let isEmpty = deque.isEmpty - ``` - -=== "Zig" - - ```zig title="deque.zig" - - ``` - === "Dart" ```dart title="deque.dart" @@ -326,6 +314,18 @@ comments: true ``` +=== "C" + + ```c title="deque.c" + // C 未提供内置双向队列 + ``` + +=== "Zig" + + ```zig title="deque.zig" + + ``` + ## 5.3.2   双向队列实现 * 双向队列的实现与队列类似,可以选择链表或数组作为底层数据结构。 @@ -357,134 +357,113 @@ comments: true 实现代码如下所示。 -=== "Java" +=== "Python" - ```java title="linkedlist_deque.java" - /* 双向链表节点 */ - class ListNode { - int val; // 节点值 - ListNode next; // 后继节点引用 - ListNode prev; // 前驱节点引用 + ```python title="linkedlist_deque.py" + class ListNode: + """双向链表节点""" - ListNode(int val) { - this.val = val; - prev = next = null; - } - } + def __init__(self, val: int): + """构造方法""" + self.val: int = val + self.next: ListNode | None = None # 后继节点引用 + self.prev: ListNode | None = None # 前驱节点引用 - /* 基于双向链表实现的双向队列 */ - class LinkedListDeque { - private ListNode front, rear; // 头节点 front ,尾节点 rear - private int queSize = 0; // 双向队列的长度 + class LinkedListDeque: + """基于双向链表实现的双向队列""" - public LinkedListDeque() { - front = rear = null; - } + def __init__(self): + """构造方法""" + self.front: ListNode | None = None # 头节点 front + self.rear: ListNode | None = None # 尾节点 rear + self.__size: int = 0 # 双向队列的长度 - /* 获取双向队列的长度 */ - public int size() { - return queSize; - } + def size(self) -> int: + """获取双向队列的长度""" + return self.__size - /* 判断双向队列是否为空 */ - public boolean isEmpty() { - return size() == 0; - } + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self.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; // 更新尾节点 - } - queSize++; // 更新队列长度 - } + def push(self, num: int, is_front: bool): + """入队操作""" + node = ListNode(num) + # 若链表为空,则令 front, rear 都指向 node + if self.is_empty(): + self.front = self.rear = node + # 队首入队操作 + elif is_front: + # 将 node 添加至链表头部 + self.front.prev = node + node.next = self.front + self.front = node # 更新头节点 + # 队尾入队操作 + else: + # 将 node 添加至链表尾部 + self.rear.next = node + node.prev = self.rear + self.rear = node # 更新尾节点 + self.__size += 1 # 更新队列长度 - /* 队首入队 */ - public void pushFirst(int num) { - push(num, true); - } + def push_first(self, num: int): + """队首入队""" + self.push(num, True) - /* 队尾入队 */ - public void pushLast(int num) { - push(num, false); - } + def push_last(self, num: int): + """队尾入队""" + self.push(num, False) - /* 出队操作 */ - private Integer pop(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; // 更新尾节点 - } - queSize--; // 更新队列长度 - return val; - } + def pop(self, is_front: bool) -> int: + """出队操作""" + # 若队列为空,直接返回 None + if self.is_empty(): + return None + # 队首出队操作 + if is_front: + val: int = self.front.val # 暂存头节点值 + # 删除头节点 + fnext: ListNode | None = self.front.next + if fnext != None: + fnext.prev = None + self.front.next = None + self.front = fnext # 更新头节点 + # 队尾出队操作 + else: + val: int = self.rear.val # 暂存尾节点值 + # 删除尾节点 + rprev: ListNode | None = self.rear.prev + if rprev != None: + rprev.next = None + self.rear.prev = None + self.rear = rprev # 更新尾节点 + self.__size -= 1 # 更新队列长度 + return val - /* 队首出队 */ - public Integer popFirst() { - return pop(true); - } + def pop_first(self) -> int: + """队首出队""" + return self.pop(True) - /* 队尾出队 */ - public Integer popLast() { - return pop(false); - } + def pop_last(self) -> int: + """队尾出队""" + return self.pop(False) - /* 访问队首元素 */ - public Integer peekFirst() { - return isEmpty() ? null : front.val; - } + def peek_first(self) -> int: + """访问队首元素""" + return None if self.is_empty() else self.front.val - /* 访问队尾元素 */ - public Integer peekLast() { - return isEmpty() ? null : rear.val; - } + def peek_last(self) -> int: + """访问队尾元素""" + return None if self.is_empty() else self.rear.val - /* 返回数组用于打印 */ - 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; - } - } + def to_array(self) -> list[int]: + """返回数组用于打印""" + node = self.front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res ``` === "C++" @@ -629,113 +608,277 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="linkedlist_deque.py" - class ListNode: - """双向链表节点""" + ```java title="linkedlist_deque.java" + /* 双向链表节点 */ + class ListNode { + int val; // 节点值 + ListNode next; // 后继节点引用 + ListNode prev; // 前驱节点引用 - def __init__(self, val: int): - """构造方法""" - self.val: int = val - self.next: ListNode | None = None # 后继节点引用 - self.prev: ListNode | None = None # 前驱节点引用 + ListNode(int val) { + this.val = val; + prev = next = null; + } + } - class LinkedListDeque: - """基于双向链表实现的双向队列""" + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private ListNode front, rear; // 头节点 front ,尾节点 rear + private int queSize = 0; // 双向队列的长度 - def __init__(self): - """构造方法""" - self.front: ListNode | None = None # 头节点 front - self.rear: ListNode | None = None # 尾节点 rear - self.__size: int = 0 # 双向队列的长度 + public LinkedListDeque() { + front = rear = null; + } - def size(self) -> int: - """获取双向队列的长度""" - return self.__size + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } - def is_empty(self) -> bool: - """判断双向队列是否为空""" - return self.size() == 0 + /* 判断双向队列是否为空 */ + public boolean isEmpty() { + return size() == 0; + } - def push(self, num: int, is_front: bool): - """入队操作""" - node = ListNode(num) - # 若链表为空,则令 front, rear 都指向 node - if self.is_empty(): - self.front = self.rear = node - # 队首入队操作 - elif is_front: - # 将 node 添加至链表头部 - self.front.prev = node - node.next = self.front - self.front = node # 更新头节点 - # 队尾入队操作 - else: - # 将 node 添加至链表尾部 - self.rear.next = node - node.prev = self.rear - self.rear = node # 更新尾节点 - self.__size += 1 # 更新队列长度 + /* 入队操作 */ + 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; // 更新尾节点 + } + queSize++; // 更新队列长度 + } - def push_first(self, num: int): - """队首入队""" - self.push(num, True) + /* 队首入队 */ + public void pushFirst(int num) { + push(num, true); + } - def push_last(self, num: int): - """队尾入队""" - self.push(num, False) + /* 队尾入队 */ + public void pushLast(int num) { + push(num, false); + } - def pop(self, is_front: bool) -> int: - """出队操作""" - # 若队列为空,直接返回 None - if self.is_empty(): - return None - # 队首出队操作 - if is_front: - val: int = self.front.val # 暂存头节点值 - # 删除头节点 - fnext: ListNode | None = self.front.next - if fnext != None: - fnext.prev = None - self.front.next = None - self.front = fnext # 更新头节点 - # 队尾出队操作 - else: - val: int = self.rear.val # 暂存尾节点值 - # 删除尾节点 - rprev: ListNode | None = self.rear.prev - if rprev != None: - rprev.next = None - self.rear.prev = None - self.rear = rprev # 更新尾节点 - self.__size -= 1 # 更新队列长度 - return val + /* 出队操作 */ + private Integer pop(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; // 更新尾节点 + } + queSize--; // 更新队列长度 + return val; + } - def pop_first(self) -> int: - """队首出队""" - return self.pop(True) + /* 队首出队 */ + public Integer popFirst() { + return pop(true); + } - def pop_last(self) -> int: - """队尾出队""" - return self.pop(False) + /* 队尾出队 */ + public Integer popLast() { + return pop(false); + } - def peek_first(self) -> int: - """访问队首元素""" - return None if self.is_empty() else self.front.val + /* 访问队首元素 */ + public Integer peekFirst() { + return isEmpty() ? null : front.val; + } - def peek_last(self) -> int: - """访问队尾元素""" - return None if self.is_empty() else self.rear.val + /* 访问队尾元素 */ + public Integer peekLast() { + return isEmpty() ? null : rear.val; + } - def to_array(self) -> list[int]: - """返回数组用于打印""" - node = self.front - res = [0] * self.size() - for i in range(self.size()): - res[i] = node.val - node = node.next - return res + /* 返回数组用于打印 */ + 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#" + + ```csharp title="linkedlist_deque.cs" + /* 双向链表节点 */ + class ListNode { + public int val; // 节点值 + public ListNode? next; // 后继节点引用 + public ListNode? prev; // 前驱节点引用 + + public ListNode(int val) { + this.val = val; + prev = null; + next = null; + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private ListNode? front, rear; // 头节点 front, 尾节点 rear + private int queSize = 0; // 双向队列的长度 + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + private void push(int num, bool isFront) { + ListNode node = new ListNode(num); + // 若链表为空,则令 front, rear 都指向 node + if (isEmpty()) { + front = node; + rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front.prev = node; + node.next = front; + front = node; // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear.next = node; + node.prev = rear; + rear = node; // 更新尾节点 + } + + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + public void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + public void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + private int? pop(bool 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; // 更新尾节点 + } + + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + public int? popFirst() { + return pop(true); + } + + /* 队尾出队 */ + public int? popLast() { + return pop(false); + } + + /* 访问队首元素 */ + public int? peekFirst() { + return isEmpty() ? null : front.val; + } + + /* 访问队尾元素 */ + public int? peekLast() { + return isEmpty() ? null : rear.val; + } + + /* 返回数组用于打印 */ + 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; + } + } ``` === "Go" @@ -818,6 +961,140 @@ comments: true } ``` +=== "Swift" + + ```swift title="linkedlist_deque.swift" + /* 双向链表节点 */ + class ListNode { + var val: Int // 节点值 + var next: ListNode? // 后继节点引用 + weak var prev: ListNode? // 前驱节点引用 + + init(val: Int) { + self.val = val + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private var front: ListNode? // 头节点 front + private var rear: ListNode? // 尾节点 rear + private var queSize: Int // 双向队列的长度 + + init() { + queSize = 0 + } + + /* 获取双向队列的长度 */ + func size() -> Int { + queSize + } + + /* 判断双向队列是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入队操作 */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // 若链表为空,则令 front, rear 都指向 node + if isEmpty() { + front = node + rear = node + } + // 队首入队操作 + else if isFront { + // 将 node 添加至链表头部 + front?.prev = node + node.next = front + front = node // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾节点 + } + queSize += 1 // 更新队列长度 + } + + /* 队首入队 */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* 队尾入队 */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* 出队操作 */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + let val: Int + // 队首出队操作 + if isFront { + val = front!.val // 暂存头节点值 + // 删除头节点 + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // 更新头节点 + } + // 队尾出队操作 + else { + val = rear!.val // 暂存尾节点值 + // 删除尾节点 + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // 更新尾节点 + } + queSize -= 1 // 更新队列长度 + return val + } + + /* 队首出队 */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* 队尾出队 */ + func popLast() -> Int { + pop(isFront: false) + } + + /* 访问队首元素 */ + func peekFirst() -> Int? { + isEmpty() ? nil : front?.val + } + + /* 访问队尾元素 */ + func peekLast() -> Int? { + isEmpty() ? nil : rear?.val + } + + /* 返回数组用于打印 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } + } + ``` + === "JS" ```javascript title="linkedlist_deque.js" @@ -1072,609 +1349,6 @@ comments: true } ``` -=== "C" - - ```c title="linkedlist_deque.c" - /* 双向链表节点 */ - struct doublyListNode { - int val; // 节点值 - struct doublyListNode *next; // 后继节点 - struct doublyListNode *prev; // 前驱节点 - }; - - typedef struct doublyListNode doublyListNode; - - /* 构造函数 */ - doublyListNode *newDoublyListNode(int num) { - doublyListNode *new = (doublyListNode *)malloc(sizeof(doublyListNode)); - new->val = num; - new->next = NULL; - new->prev = NULL; - return new; - } - - /* 析构函数 */ - void delDoublyListNode(doublyListNode *node) { - free(node); - } - - /* 基于双向链表实现的双向队列 */ - struct linkedListDeque { - doublyListNode *front, *rear; // 头节点 front ,尾节点 rear - int queSize; // 双向队列的长度 - }; - - typedef struct linkedListDeque linkedListDeque; - - /* 构造函数 */ - linkedListDeque *newLinkedListDeque() { - linkedListDeque *deque = (linkedListDeque *)malloc(sizeof(linkedListDeque)); - deque->front = NULL; - deque->rear = NULL; - deque->queSize = 0; - return deque; - } - - /* 析构函数 */ - void delLinkedListdeque(linkedListDeque *deque) { - // 释放所有节点 - for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { - doublyListNode *tmp = deque->front; - deque->front = deque->front->next; - free(tmp); - } - // 释放 deque 结构体 - free(deque); - } - - /* 获取队列的长度 */ - int size(linkedListDeque *deque) { - return deque->queSize; - } - - /* 判断队列是否为空 */ - bool empty(linkedListDeque *deque) { - return (size(deque) == 0); - } - - /* 入队 */ - void push(linkedListDeque *deque, int num, bool isFront) { - doublyListNode *node = newDoublyListNode(num); - // 若链表为空,则令 front, rear 都指向node - if (empty(deque)) { - deque->front = deque->rear = node; - } - // 队首入队操作 - else if (isFront) { - // 将 node 添加至链表头部 - deque->front->prev = node; - node->next = deque->front; - deque->front = node; // 更新头节点 - } - // 对尾入队操作 - else { - // 将 node 添加至链表尾部 - deque->rear->next = node; - node->prev = deque->rear; - deque->rear = node; - } - deque->queSize++; // 更新队列长度 - } - - /* 队首入队 */ - void pushFirst(linkedListDeque *deque, int num) { - push(deque, num, true); - } - - /* 队尾入队 */ - void pushLast(linkedListDeque *deque, int num) { - push(deque, num, false); - } - - /* 访问队首元素 */ - int peekFirst(linkedListDeque *deque) { - assert(size(deque) && deque->front); - return deque->front->val; - } - - /* 访问队尾元素 */ - int peekLast(linkedListDeque *deque) { - assert(size(deque) && deque->rear); - return deque->rear->val; - } - - /* 出队 */ - int pop(linkedListDeque *deque, bool isFront) { - if (empty(deque)) - return -1; - int val; - // 队首出队操作 - if (isFront) { - val = peekFirst(deque); // 暂存头节点值 - doublyListNode *fNext = deque->front->next; - if (fNext) { - fNext->prev = NULL; - deque->front->next = NULL; - delDoublyListNode(deque->front); - } - deque->front = fNext; // 更新头节点 - } - // 队尾出队操作 - else { - val = peekLast(deque); // 暂存尾节点值 - doublyListNode *rPrev = deque->rear->prev; - if (rPrev) { - rPrev->next = NULL; - deque->rear->prev = NULL; - delDoublyListNode(deque->rear); - } - deque->rear = rPrev; // 更新尾节点 - } - deque->queSize--; // 更新队列长度 - return val; - } - - /* 队首出队 */ - int popFirst(linkedListDeque *deque) { - return pop(deque, true); - } - - /* 队尾出队 */ - int popLast(linkedListDeque *deque) { - return pop(deque, false); - } - - /* 打印队列 */ - void printLinkedListDeque(linkedListDeque *deque) { - int arr[deque->queSize]; - // 拷贝链表中的数据到数组 - int i; - doublyListNode *node; - for (i = 0, node = deque->front; i < deque->queSize; i++) { - arr[i] = node->val; - node = node->next; - } - printArray(arr, deque->queSize); - } - ``` - -=== "C#" - - ```csharp title="linkedlist_deque.cs" - /* 双向链表节点 */ - class ListNode { - public int val; // 节点值 - public ListNode? next; // 后继节点引用 - public ListNode? prev; // 前驱节点引用 - - public ListNode(int val) { - this.val = val; - prev = null; - next = null; - } - } - - /* 基于双向链表实现的双向队列 */ - class LinkedListDeque { - private ListNode? front, rear; // 头节点 front, 尾节点 rear - private int queSize = 0; // 双向队列的长度 - - public LinkedListDeque() { - front = null; - rear = null; - } - - /* 获取双向队列的长度 */ - public int size() { - return queSize; - } - - /* 判断双向队列是否为空 */ - public bool isEmpty() { - return size() == 0; - } - - /* 入队操作 */ - private void push(int num, bool isFront) { - ListNode node = new ListNode(num); - // 若链表为空,则令 front, rear 都指向 node - if (isEmpty()) { - front = node; - rear = node; - } - // 队首入队操作 - else if (isFront) { - // 将 node 添加至链表头部 - front.prev = node; - node.next = front; - front = node; // 更新头节点 - } - // 队尾入队操作 - else { - // 将 node 添加至链表尾部 - rear.next = node; - node.prev = rear; - rear = node; // 更新尾节点 - } - - queSize++; // 更新队列长度 - } - - /* 队首入队 */ - public void pushFirst(int num) { - push(num, true); - } - - /* 队尾入队 */ - public void pushLast(int num) { - push(num, false); - } - - /* 出队操作 */ - private int? pop(bool 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; // 更新尾节点 - } - - queSize--; // 更新队列长度 - return val; - } - - /* 队首出队 */ - public int? popFirst() { - return pop(true); - } - - /* 队尾出队 */ - public int? popLast() { - return pop(false); - } - - /* 访问队首元素 */ - public int? peekFirst() { - return isEmpty() ? null : front.val; - } - - /* 访问队尾元素 */ - public int? peekLast() { - return isEmpty() ? null : rear.val; - } - - /* 返回数组用于打印 */ - 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; - } - } - ``` - -=== "Swift" - - ```swift title="linkedlist_deque.swift" - /* 双向链表节点 */ - class ListNode { - var val: Int // 节点值 - var next: ListNode? // 后继节点引用 - weak var prev: ListNode? // 前驱节点引用 - - init(val: Int) { - self.val = val - } - } - - /* 基于双向链表实现的双向队列 */ - class LinkedListDeque { - private var front: ListNode? // 头节点 front - private var rear: ListNode? // 尾节点 rear - private var queSize: Int // 双向队列的长度 - - init() { - queSize = 0 - } - - /* 获取双向队列的长度 */ - func size() -> Int { - queSize - } - - /* 判断双向队列是否为空 */ - func isEmpty() -> Bool { - size() == 0 - } - - /* 入队操作 */ - private func push(num: Int, isFront: Bool) { - let node = ListNode(val: num) - // 若链表为空,则令 front, rear 都指向 node - if isEmpty() { - front = node - rear = node - } - // 队首入队操作 - else if isFront { - // 将 node 添加至链表头部 - front?.prev = node - node.next = front - front = node // 更新头节点 - } - // 队尾入队操作 - else { - // 将 node 添加至链表尾部 - rear?.next = node - node.prev = rear - rear = node // 更新尾节点 - } - queSize += 1 // 更新队列长度 - } - - /* 队首入队 */ - func pushFirst(num: Int) { - push(num: num, isFront: true) - } - - /* 队尾入队 */ - func pushLast(num: Int) { - push(num: num, isFront: false) - } - - /* 出队操作 */ - private func pop(isFront: Bool) -> Int { - if isEmpty() { - fatalError("双向队列为空") - } - let val: Int - // 队首出队操作 - if isFront { - val = front!.val // 暂存头节点值 - // 删除头节点 - let fNext = front?.next - if fNext != nil { - fNext?.prev = nil - front?.next = nil - } - front = fNext // 更新头节点 - } - // 队尾出队操作 - else { - val = rear!.val // 暂存尾节点值 - // 删除尾节点 - let rPrev = rear?.prev - if rPrev != nil { - rPrev?.next = nil - rear?.prev = nil - } - rear = rPrev // 更新尾节点 - } - queSize -= 1 // 更新队列长度 - return val - } - - /* 队首出队 */ - func popFirst() -> Int { - pop(isFront: true) - } - - /* 队尾出队 */ - func popLast() -> Int { - pop(isFront: false) - } - - /* 访问队首元素 */ - func peekFirst() -> Int? { - isEmpty() ? nil : front?.val - } - - /* 访问队尾元素 */ - func peekLast() -> Int? { - isEmpty() ? nil : rear?.val - } - - /* 返回数组用于打印 */ - func toArray() -> [Int] { - var node = front - var res = Array(repeating: 0, count: size()) - for i in res.indices { - res[i] = node!.val - node = node?.next - } - return res - } - } - ``` - -=== "Zig" - - ```zig title="linkedlist_deque.zig" - // 双向链表节点 - fn ListNode(comptime T: type) type { - return struct { - const Self = @This(); - - val: T = undefined, // 节点值 - next: ?*Self = null, // 后继节点指针 - prev: ?*Self = null, // 前驱节点指针 - - // Initialize a list node with specific value - pub fn init(self: *Self, x: i32) void { - self.val = x; - self.next = null; - self.prev = null; - } - }; - } - - // 基于双向链表实现的双向队列 - fn LinkedListDeque(comptime T: type) type { - return struct { - const Self = @This(); - - front: ?*ListNode(T) = null, // 头节点 front - rear: ?*ListNode(T) = null, // 尾节点 rear - que_size: usize = 0, // 双向队列的长度 - 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.front = null; - self.rear = null; - self.que_size = 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.que_size; - } - - // 判断双向队列是否为空 - pub fn isEmpty(self: *Self) bool { - return self.size() == 0; - } - - // 入队操作 - pub fn push(self: *Self, num: T, is_front: bool) !void { - var node = try self.mem_allocator.create(ListNode(T)); - node.init(num); - // 若链表为空,则令 front, rear 都指向 node - if (self.isEmpty()) { - self.front = node; - self.rear = node; - // 队首入队操作 - } else if (is_front) { - // 将 node 添加至链表头部 - self.front.?.prev = node; - node.next = self.front; - self.front = node; // 更新头节点 - // 队尾入队操作 - } else { - // 将 node 添加至链表尾部 - self.rear.?.next = node; - node.prev = self.rear; - self.rear = node; // 更新尾节点 - } - self.que_size += 1; // 更新队列长度 - } - - // 队首入队 - pub fn pushFirst(self: *Self, num: T) !void { - try self.push(num, true); - } - - // 队尾入队 - pub fn pushLast(self: *Self, num: T) !void { - try self.push(num, false); - } - - // 出队操作 - pub fn pop(self: *Self, is_front: bool) T { - if (self.isEmpty()) @panic("双向队列为空"); - var val: T = undefined; - // 队首出队操作 - if (is_front) { - val = self.front.?.val; // 暂存头节点值 - // 删除头节点 - var fNext = self.front.?.next; - if (fNext != null) { - fNext.?.prev = null; - self.front.?.next = null; - } - self.front = fNext; // 更新头节点 - // 队尾出队操作 - } else { - val = self.rear.?.val; // 暂存尾节点值 - // 删除尾节点 - var rPrev = self.rear.?.prev; - if (rPrev != null) { - rPrev.?.next = null; - self.rear.?.prev = null; - } - self.rear = rPrev; // 更新尾节点 - } - self.que_size -= 1; // 更新队列长度 - return val; - } - - // 队首出队 - pub fn popFirst(self: *Self) T { - return self.pop(true); - } - - // 队尾出队 - pub fn popLast(self: *Self) T { - return self.pop(false); - } - - // 访问队首元素 - pub fn peekFirst(self: *Self) T { - if (self.isEmpty()) @panic("双向队列为空"); - return self.front.?.val; - } - - // 访问队尾元素 - pub fn peekLast(self: *Self) T { - if (self.isEmpty()) @panic("双向队列为空"); - return self.rear.?.val; - } - - // 返回数组用于打印 - pub fn toArray(self: *Self) ![]T { - var node = self.front; - var res = try self.mem_allocator.alloc(T, self.size()); - @memset(res, @as(T, 0)); - var i: usize = 0; - while (i < res.len) : (i += 1) { - res[i] = node.?.val; - node = node.?.next; - } - return res; - } - }; - } - ``` - === "Dart" ```dart title="linkedlist_deque.dart" @@ -1973,6 +1647,332 @@ comments: true } ``` +=== "C" + + ```c title="linkedlist_deque.c" + /* 双向链表节点 */ + struct doublyListNode { + int val; // 节点值 + struct doublyListNode *next; // 后继节点 + struct doublyListNode *prev; // 前驱节点 + }; + + typedef struct doublyListNode doublyListNode; + + /* 构造函数 */ + doublyListNode *newDoublyListNode(int num) { + doublyListNode *new = (doublyListNode *)malloc(sizeof(doublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; + } + + /* 析构函数 */ + void delDoublyListNode(doublyListNode *node) { + free(node); + } + + /* 基于双向链表实现的双向队列 */ + struct linkedListDeque { + doublyListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize; // 双向队列的长度 + }; + + typedef struct linkedListDeque linkedListDeque; + + /* 构造函数 */ + linkedListDeque *newLinkedListDeque() { + linkedListDeque *deque = (linkedListDeque *)malloc(sizeof(linkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; + } + + /* 析构函数 */ + void delLinkedListdeque(linkedListDeque *deque) { + // 释放所有节点 + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + doublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // 释放 deque 结构体 + free(deque); + } + + /* 获取队列的长度 */ + int size(linkedListDeque *deque) { + return deque->queSize; + } + + /* 判断队列是否为空 */ + bool empty(linkedListDeque *deque) { + return (size(deque) == 0); + } + + /* 入队 */ + void push(linkedListDeque *deque, int num, bool isFront) { + doublyListNode *node = newDoublyListNode(num); + // 若链表为空,则令 front, rear 都指向node + if (empty(deque)) { + deque->front = deque->rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // 更新头节点 + } + // 对尾入队操作 + else { + // 将 node 添加至链表尾部 + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(linkedListDeque *deque, int num) { + push(deque, num, true); + } + + /* 队尾入队 */ + void pushLast(linkedListDeque *deque, int num) { + push(deque, num, false); + } + + /* 访问队首元素 */ + int peekFirst(linkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; + } + + /* 访问队尾元素 */ + int peekLast(linkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; + } + + /* 出队 */ + int pop(linkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // 队首出队操作 + if (isFront) { + val = peekFirst(deque); // 暂存头节点值 + doublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + delDoublyListNode(deque->front); + } + deque->front = fNext; // 更新头节点 + } + // 队尾出队操作 + else { + val = peekLast(deque); // 暂存尾节点值 + doublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + delDoublyListNode(deque->rear); + } + deque->rear = rPrev; // 更新尾节点 + } + deque->queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int popFirst(linkedListDeque *deque) { + return pop(deque, true); + } + + /* 队尾出队 */ + int popLast(linkedListDeque *deque) { + return pop(deque, false); + } + + /* 打印队列 */ + void printLinkedListDeque(linkedListDeque *deque) { + int arr[deque->queSize]; + // 拷贝链表中的数据到数组 + int i; + doublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + } + ``` + +=== "Zig" + + ```zig title="linkedlist_deque.zig" + // 双向链表节点 + fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 节点值 + next: ?*Self = null, // 后继节点指针 + prev: ?*Self = null, // 前驱节点指针 + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; + } + + // 基于双向链表实现的双向队列 + fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // 头节点 front + rear: ?*ListNode(T) = null, // 尾节点 rear + que_size: usize = 0, // 双向队列的长度 + 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.front = null; + self.rear = null; + self.que_size = 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.que_size; + } + + // 判断双向队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 入队操作 + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // 若链表为空,则令 front, rear 都指向 node + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // 队首入队操作 + } else if (is_front) { + // 将 node 添加至链表头部 + self.front.?.prev = node; + node.next = self.front; + self.front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + self.rear.?.next = node; + node.prev = self.rear; + self.rear = node; // 更新尾节点 + } + self.que_size += 1; // 更新队列长度 + } + + // 队首入队 + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // 队尾入队 + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // 出队操作 + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("双向队列为空"); + var val: T = undefined; + // 队首出队操作 + if (is_front) { + val = self.front.?.val; // 暂存头节点值 + // 删除头节点 + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = self.rear.?.val; // 暂存尾节点值 + // 删除尾节点 + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // 更新尾节点 + } + self.que_size -= 1; // 更新队列长度 + return val; + } + + // 队首出队 + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // 队尾出队 + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // 访问队首元素 + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("双向队列为空"); + return self.front.?.val; + } + + // 访问队尾元素 + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("双向队列为空"); + return self.rear.?.val; + } + + // 返回数组用于打印 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; + } + ``` + ### 2.   基于数组的实现 如图 5-9 所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。 @@ -1996,113 +1996,95 @@ comments: true 在队列的实现基础上,仅需增加“队首入队”和“队尾出队”的方法。 -=== "Java" +=== "Python" - ```java title="array_deque.java" - /* 基于环形数组实现的双向队列 */ - class ArrayDeque { - private int[] nums; // 用于存储双向队列元素的数组 - private int front; // 队首指针,指向队首元素 - private int queSize; // 双向队列长度 + ```python title="array_deque.py" + class ArrayDeque: + """基于环形数组实现的双向队列""" - /* 构造方法 */ - public ArrayDeque(int capacity) { - this.nums = new int[capacity]; - front = queSize = 0; - } + def __init__(self, capacity: int): + """构造方法""" + self.__nums: list[int] = [0] * capacity + self.__front: int = 0 + self.__size: int = 0 - /* 获取双向队列的容量 */ - public int capacity() { - return nums.length; - } + def capacity(self) -> int: + """获取双向队列的容量""" + return len(self.__nums) - /* 获取双向队列的长度 */ - public int size() { - return queSize; - } + def size(self) -> int: + """获取双向队列的长度""" + return self.__size - /* 判断双向队列是否为空 */ - public boolean isEmpty() { - return queSize == 0; - } + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self.__size == 0 - /* 计算环形数组索引 */ - private int index(int i) { - // 通过取余操作实现数组首尾相连 - // 当 i 越过数组尾部后,回到头部 - // 当 i 越过数组头部后,回到尾部 - return (i + capacity()) % capacity(); - } + def index(self, i: int) -> int: + """计算环形数组索引""" + # 通过取余操作实现数组首尾相连 + # 当 i 越过数组尾部后,回到头部 + # 当 i 越过数组头部后,回到尾部 + return (i + self.capacity()) % self.capacity() - /* 队首入队 */ - public void pushFirst(int num) { - if (queSize == capacity()) { - System.out.println("双向队列已满"); - return; - } - // 队首指针向左移动一位 - // 通过取余操作,实现 front 越过数组头部后回到尾部 - front = index(front - 1); - // 将 num 添加至队首 - nums[front] = num; - queSize++; - } + def push_first(self, num: int): + """队首入队""" + if self.__size == self.capacity(): + print("双向队列已满") + return + # 队首指针向左移动一位 + # 通过取余操作,实现 front 越过数组头部后回到尾部 + self.__front = self.index(self.__front - 1) + # 将 num 添加至队首 + self.__nums[self.__front] = num + self.__size += 1 - /* 队尾入队 */ - public void pushLast(int num) { - if (queSize == capacity()) { - System.out.println("双向队列已满"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - int rear = index(front + queSize); - // 将 num 添加至队尾 - nums[rear] = num; - queSize++; - } + def push_last(self, num: int): + """队尾入队""" + if self.__size == self.capacity(): + print("双向队列已满") + return + # 计算尾指针,指向队尾索引 + 1 + rear = self.index(self.__front + self.__size) + # 将 num 添加至队尾 + self.__nums[rear] = num + self.__size += 1 - /* 队首出队 */ - public int popFirst() { - int num = peekFirst(); - // 队首指针向后移动一位 - front = index(front + 1); - queSize--; - return num; - } + def pop_first(self) -> int: + """队首出队""" + num = self.peek_first() + # 队首指针向后移动一位 + self.__front = self.index(self.__front + 1) + self.__size -= 1 + return num - /* 队尾出队 */ - public int popLast() { - int num = peekLast(); - queSize--; - return num; - } + def pop_last(self) -> int: + """队尾出队""" + num = self.peek_last() + self.__size -= 1 + return num - /* 访问队首元素 */ - public int peekFirst() { - if (isEmpty()) - throw new IndexOutOfBoundsException(); - return nums[front]; - } + def peek_first(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self.__nums[self.__front] - /* 访问队尾元素 */ - public int peekLast() { - if (isEmpty()) - throw new IndexOutOfBoundsException(); - // 计算尾元素索引 - int last = index(front + queSize - 1); - return nums[last]; - } + def peek_last(self) -> int: + """访问队尾元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + # 计算尾元素索引 + last = self.index(self.__front + self.__size - 1) + return self.__nums[last] - /* 返回数组用于打印 */ - public int[] toArray() { - // 仅转换有效长度范围内的列表元素 - int[] res = new int[queSize]; - for (int i = 0, j = front; i < queSize; i++, j++) { - res[i] = nums[index(j)]; - } - return res; - } - } + def to_array(self) -> list[int]: + """返回数组用于打印""" + # 仅转换有效长度范围内的列表元素 + res = [] + for i in range(self.__size): + res.append(self.__nums[self.index(self.__front + i)]) + return res ``` === "C++" @@ -2216,95 +2198,224 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="array_deque.py" - class ArrayDeque: - """基于环形数组实现的双向队列""" + ```java title="array_deque.java" + /* 基于环形数组实现的双向队列 */ + class ArrayDeque { + private int[] nums; // 用于存储双向队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 双向队列长度 - def __init__(self, capacity: int): - """构造方法""" - self.__nums: list[int] = [0] * capacity - self.__front: int = 0 - self.__size: int = 0 + /* 构造方法 */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } - def capacity(self) -> int: - """获取双向队列的容量""" - return len(self.__nums) + /* 获取双向队列的容量 */ + public int capacity() { + return nums.length; + } - def size(self) -> int: - """获取双向队列的长度""" - return self.__size + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } - def is_empty(self) -> bool: - """判断双向队列是否为空""" - return self.__size == 0 + /* 判断双向队列是否为空 */ + public boolean isEmpty() { + return queSize == 0; + } - def index(self, i: int) -> int: - """计算环形数组索引""" - # 通过取余操作实现数组首尾相连 - # 当 i 越过数组尾部后,回到头部 - # 当 i 越过数组头部后,回到尾部 - return (i + self.capacity()) % self.capacity() + /* 计算环形数组索引 */ + private int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } - def push_first(self, num: int): - """队首入队""" - if self.__size == self.capacity(): - print("双向队列已满") - return - # 队首指针向左移动一位 - # 通过取余操作,实现 front 越过数组头部后回到尾部 - self.__front = self.index(self.__front - 1) - # 将 num 添加至队首 - self.__nums[self.__front] = num - self.__size += 1 + /* 队首入队 */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("双向队列已满"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作,实现 front 越过数组头部后回到尾部 + front = index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } - def push_last(self, num: int): - """队尾入队""" - if self.__size == self.capacity(): - print("双向队列已满") - return - # 计算尾指针,指向队尾索引 + 1 - rear = self.index(self.__front + self.__size) - # 将 num 添加至队尾 - self.__nums[rear] = num - self.__size += 1 + /* 队尾入队 */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("双向队列已满"); + return; + } + // 计算尾指针,指向队尾索引 + 1 + int rear = index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } - def pop_first(self) -> int: - """队首出队""" - num = self.peek_first() - # 队首指针向后移动一位 - self.__front = self.index(self.__front + 1) - self.__size -= 1 - return num + /* 队首出队 */ + public int popFirst() { + int num = peekFirst(); + // 队首指针向后移动一位 + front = index(front + 1); + queSize--; + return num; + } - def pop_last(self) -> int: - """队尾出队""" - num = self.peek_last() - self.__size -= 1 - return num + /* 队尾出队 */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } - def peek_first(self) -> int: - """访问队首元素""" - if self.is_empty(): - raise IndexError("双向队列为空") - return self.__nums[self.__front] + /* 访问队首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } - def peek_last(self) -> int: - """访问队尾元素""" - if self.is_empty(): - raise IndexError("双向队列为空") - # 计算尾元素索引 - last = self.index(self.__front + self.__size - 1) - return self.__nums[last] + /* 访问队尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 计算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } - def to_array(self) -> list[int]: - """返回数组用于打印""" - # 仅转换有效长度范围内的列表元素 - res = [] - for i in range(self.__size): - res.append(self.__nums[self.index(self.__front + i)]) - return res + /* 返回数组用于打印 */ + public int[] toArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } + } + ``` + +=== "C#" + + ```csharp title="array_deque.cs" + /* 基于环形数组实现的双向队列 */ + class ArrayDeque { + private readonly int[] nums; // 用于存储双向队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 双向队列长度 + + /* 构造方法 */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* 获取双向队列的容量 */ + public int capacity() { + return nums.Length; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool isEmpty() { + return queSize == 0; + } + + /* 计算环形数组索引 */ + private int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 队首入队 */ + public void pushFirst(int num) { + if (queSize == capacity()) { + Console.WriteLine("双向队列已满"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作,实现 front 越过数组头部后回到尾部 + front = index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } + + /* 队尾入队 */ + public void pushLast(int num) { + if (queSize == capacity()) { + Console.WriteLine("双向队列已满"); + return; + } + // 计算尾指针,指向队尾索引 + 1 + int rear = index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 队首出队 */ + public int popFirst() { + int num = peekFirst(); + // 队首指针向后移动一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 队尾出队 */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peekFirst() { + if (isEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* 访问队尾元素 */ + public int peekLast() { + if (isEmpty()) { + throw new InvalidOperationException(); + } + // 计算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回数组用于打印 */ + public int[] toArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } + } ``` === "Go" @@ -2419,6 +2530,118 @@ comments: true } ``` +=== "Swift" + + ```swift title="array_deque.swift" + /* 基于环形数组实现的双向队列 */ + class ArrayDeque { + private var nums: [Int] // 用于存储双向队列元素的数组 + private var front: Int // 队首指针,指向队首元素 + private var queSize: Int // 双向队列长度 + + /* 构造方法 */ + init(capacity: Int) { + nums = Array(repeating: 0, count: capacity) + front = 0 + queSize = 0 + } + + /* 获取双向队列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 获取双向队列的长度 */ + func size() -> Int { + queSize + } + + /* 判断双向队列是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 计算环形数组索引 */ + private func index(i: Int) -> Int { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + (i + capacity()) % capacity() + } + + /* 队首入队 */ + func pushFirst(num: Int) { + if size() == capacity() { + print("双向队列已满") + return + } + // 队首指针向左移动一位 + // 通过取余操作,实现 front 越过数组头部后回到尾部 + front = index(i: front - 1) + // 将 num 添加至队首 + nums[front] = num + queSize += 1 + } + + /* 队尾入队 */ + func pushLast(num: Int) { + if size() == capacity() { + print("双向队列已满") + return + } + // 计算尾指针,指向队尾索引 + 1 + let rear = index(i: front + size()) + // 将 num 添加至队尾 + nums[rear] = num + queSize += 1 + } + + /* 队首出队 */ + func popFirst() -> Int { + let num = peekFirst() + // 队首指针向后移动一位 + front = index(i: front + 1) + queSize -= 1 + return num + } + + /* 队尾出队 */ + func popLast() -> Int { + let num = peekLast() + queSize -= 1 + return num + } + + /* 访问队首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + return nums[front] + } + + /* 访问队尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + // 计算尾元素索引 + let last = index(i: front + size() - 1) + return nums[last] + } + + /* 返回数组用于打印 */ + func toArray() -> [Int] { + // 仅转换有效长度范围内的列表元素 + var res = Array(repeating: 0, count: size()) + for (i, j) in sequence(first: (0, front), next: { $0 < self.size() - 1 ? ($0 + 1, $1 + 1) : nil }) { + res[i] = nums[index(i: j)] + } + return res + } + } + ``` + === "JS" ```javascript title="array_deque.js" @@ -2635,356 +2858,6 @@ comments: true } ``` -=== "C" - - ```c title="array_deque.c" - /* 基于环形数组实现的双向队列 */ - struct arrayDeque { - int *nums; // 用于存储队列元素的数组 - int front; // 队首指针,指向队首元素 - int queSize; // 尾指针,指向队尾 + 1 - int queCapacity; // 队列容量 - }; - - typedef struct arrayDeque arrayDeque; - - /* 构造函数 */ - arrayDeque *newArrayDeque(int capacity) { - arrayDeque *deque = (arrayDeque *)malloc(sizeof(arrayDeque)); - // 初始化数组 - deque->queCapacity = capacity; - deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); - deque->front = deque->queSize = 0; - return deque; - } - - /* 析构函数 */ - void delArrayDeque(arrayDeque *deque) { - free(deque->nums); - deque->queCapacity = 0; - } - - /* 获取双向队列的容量 */ - int capacity(arrayDeque *deque) { - return deque->queCapacity; - } - - /* 获取双向队列的长度 */ - int size(arrayDeque *deque) { - return deque->queSize; - } - - /* 判断双向队列是否为空 */ - bool empty(arrayDeque *deque) { - return deque->queSize == 0; - } - - /* 计算环形数组索引 */ - int dequeIndex(arrayDeque *deque, int i) { - // 通过取余操作实现数组首尾相连 - // 当 i 越过数组尾部时,回到头部 - // 当 i 越过数组头部后,回到尾部 - return ((i + capacity(deque)) % capacity(deque)); - } - - /* 队首入队 */ - void pushFirst(arrayDeque *deque, int num) { - if (deque->queSize == capacity(deque)) { - printf("双向队列已满\r\n"); - return; - } - // 队首指针向左移动一位 - // 通过取余操作,实现 front 越过数组头部回到尾部 - deque->front = dequeIndex(deque, deque->front - 1); - // 将 num 添加到队首 - deque->nums[deque->front] = num; - deque->queSize++; - } - - /* 队尾入队 */ - void pushLast(arrayDeque *deque, int num) { - if (deque->queSize == capacity(deque)) { - printf("双向队列已满\r\n"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - int rear = dequeIndex(deque, deque->front + deque->queSize); - // 将 num 添加至队尾 - deque->nums[rear] = num; - deque->queSize++; - } - - /* 访问队首元素 */ - int peekFirst(arrayDeque *deque) { - // 访问异常:双向队列为空 - assert(empty(deque) == 0); - return deque->nums[deque->front]; - } - - /* 访问队尾元素 */ - int peekLast(arrayDeque *deque) { - // 访问异常:双向队列为空 - assert(empty(deque) == 0); - int last = dequeIndex(deque, deque->front + deque->queSize - 1); - return deque->nums[last]; - } - - /* 队首出队 */ - int popFirst(arrayDeque *deque) { - int num = peekFirst(deque); - // 队首指针向后移动一位 - deque->front = dequeIndex(deque, deque->front + 1); - deque->queSize--; - return num; - } - - /* 队尾出队 */ - int popLast(arrayDeque *deque) { - int num = peekLast(deque); - deque->queSize--; - return num; - } - - /* 打印队列 */ - void printArrayDeque(arrayDeque *deque) { - int arr[deque->queSize]; - // 拷贝 - for (int i = 0, j = deque->front; i < deque->queSize; i++, j++) { - arr[i] = deque->nums[j % deque->queCapacity]; - } - printArray(arr, deque->queSize); - } - ``` - -=== "C#" - - ```csharp title="array_deque.cs" - /* 基于环形数组实现的双向队列 */ - class ArrayDeque { - private readonly int[] nums; // 用于存储双向队列元素的数组 - private int front; // 队首指针,指向队首元素 - private int queSize; // 双向队列长度 - - /* 构造方法 */ - public ArrayDeque(int capacity) { - this.nums = new int[capacity]; - front = queSize = 0; - } - - /* 获取双向队列的容量 */ - public int capacity() { - return nums.Length; - } - - /* 获取双向队列的长度 */ - public int size() { - return queSize; - } - - /* 判断双向队列是否为空 */ - public bool isEmpty() { - return queSize == 0; - } - - /* 计算环形数组索引 */ - private int index(int i) { - // 通过取余操作实现数组首尾相连 - // 当 i 越过数组尾部后,回到头部 - // 当 i 越过数组头部后,回到尾部 - return (i + capacity()) % capacity(); - } - - /* 队首入队 */ - public void pushFirst(int num) { - if (queSize == capacity()) { - Console.WriteLine("双向队列已满"); - return; - } - // 队首指针向左移动一位 - // 通过取余操作,实现 front 越过数组头部后回到尾部 - front = index(front - 1); - // 将 num 添加至队首 - nums[front] = num; - queSize++; - } - - /* 队尾入队 */ - public void pushLast(int num) { - if (queSize == capacity()) { - Console.WriteLine("双向队列已满"); - return; - } - // 计算尾指针,指向队尾索引 + 1 - int rear = index(front + queSize); - // 将 num 添加至队尾 - nums[rear] = num; - queSize++; - } - - /* 队首出队 */ - public int popFirst() { - int num = peekFirst(); - // 队首指针向后移动一位 - front = index(front + 1); - queSize--; - return num; - } - - /* 队尾出队 */ - public int popLast() { - int num = peekLast(); - queSize--; - return num; - } - - /* 访问队首元素 */ - public int peekFirst() { - if (isEmpty()) { - throw new InvalidOperationException(); - } - return nums[front]; - } - - /* 访问队尾元素 */ - public int peekLast() { - if (isEmpty()) { - throw new InvalidOperationException(); - } - // 计算尾元素索引 - int last = index(front + queSize - 1); - return nums[last]; - } - - /* 返回数组用于打印 */ - public int[] toArray() { - // 仅转换有效长度范围内的列表元素 - int[] res = new int[queSize]; - for (int i = 0, j = front; i < queSize; i++, j++) { - res[i] = nums[index(j)]; - } - return res; - } - } - ``` - -=== "Swift" - - ```swift title="array_deque.swift" - /* 基于环形数组实现的双向队列 */ - class ArrayDeque { - private var nums: [Int] // 用于存储双向队列元素的数组 - private var front: Int // 队首指针,指向队首元素 - private var queSize: Int // 双向队列长度 - - /* 构造方法 */ - init(capacity: Int) { - nums = Array(repeating: 0, count: capacity) - front = 0 - queSize = 0 - } - - /* 获取双向队列的容量 */ - func capacity() -> Int { - nums.count - } - - /* 获取双向队列的长度 */ - func size() -> Int { - queSize - } - - /* 判断双向队列是否为空 */ - func isEmpty() -> Bool { - size() == 0 - } - - /* 计算环形数组索引 */ - private func index(i: Int) -> Int { - // 通过取余操作实现数组首尾相连 - // 当 i 越过数组尾部后,回到头部 - // 当 i 越过数组头部后,回到尾部 - (i + capacity()) % capacity() - } - - /* 队首入队 */ - func pushFirst(num: Int) { - if size() == capacity() { - print("双向队列已满") - return - } - // 队首指针向左移动一位 - // 通过取余操作,实现 front 越过数组头部后回到尾部 - front = index(i: front - 1) - // 将 num 添加至队首 - nums[front] = num - queSize += 1 - } - - /* 队尾入队 */ - func pushLast(num: Int) { - if size() == capacity() { - print("双向队列已满") - return - } - // 计算尾指针,指向队尾索引 + 1 - let rear = index(i: front + size()) - // 将 num 添加至队尾 - nums[rear] = num - queSize += 1 - } - - /* 队首出队 */ - func popFirst() -> Int { - let num = peekFirst() - // 队首指针向后移动一位 - front = index(i: front + 1) - queSize -= 1 - return num - } - - /* 队尾出队 */ - func popLast() -> Int { - let num = peekLast() - queSize -= 1 - return num - } - - /* 访问队首元素 */ - func peekFirst() -> Int { - if isEmpty() { - fatalError("双向队列为空") - } - return nums[front] - } - - /* 访问队尾元素 */ - func peekLast() -> Int { - if isEmpty() { - fatalError("双向队列为空") - } - // 计算尾元素索引 - let last = index(i: front + size() - 1) - return nums[last] - } - - /* 返回数组用于打印 */ - func toArray() -> [Int] { - // 仅转换有效长度范围内的列表元素 - var res = Array(repeating: 0, count: size()) - for (i, j) in sequence(first: (0, front), next: { $0 < self.size() - 1 ? ($0 + 1, $1 + 1) : nil }) { - res[i] = nums[index(i: j)] - } - return res - } - } - ``` - -=== "Zig" - - ```zig title="array_deque.zig" - [class]{ArrayDeque}-[func]{} - ``` - === "Dart" ```dart title="array_deque.dart" @@ -3208,6 +3081,133 @@ comments: true } ``` +=== "C" + + ```c title="array_deque.c" + /* 基于环形数组实现的双向队列 */ + struct arrayDeque { + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 尾指针,指向队尾 + 1 + int queCapacity; // 队列容量 + }; + + typedef struct arrayDeque arrayDeque; + + /* 构造函数 */ + arrayDeque *newArrayDeque(int capacity) { + arrayDeque *deque = (arrayDeque *)malloc(sizeof(arrayDeque)); + // 初始化数组 + deque->queCapacity = capacity; + deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); + deque->front = deque->queSize = 0; + return deque; + } + + /* 析构函数 */ + void delArrayDeque(arrayDeque *deque) { + free(deque->nums); + deque->queCapacity = 0; + } + + /* 获取双向队列的容量 */ + int capacity(arrayDeque *deque) { + return deque->queCapacity; + } + + /* 获取双向队列的长度 */ + int size(arrayDeque *deque) { + return deque->queSize; + } + + /* 判断双向队列是否为空 */ + bool empty(arrayDeque *deque) { + return deque->queSize == 0; + } + + /* 计算环形数组索引 */ + int dequeIndex(arrayDeque *deque, int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部时,回到头部 + // 当 i 越过数组头部后,回到尾部 + return ((i + capacity(deque)) % capacity(deque)); + } + + /* 队首入队 */ + void pushFirst(arrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("双向队列已满\r\n"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作,实现 front 越过数组头部回到尾部 + deque->front = dequeIndex(deque, deque->front - 1); + // 将 num 添加到队首 + deque->nums[deque->front] = num; + deque->queSize++; + } + + /* 队尾入队 */ + void pushLast(arrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("双向队列已满\r\n"); + return; + } + // 计算尾指针,指向队尾索引 + 1 + int rear = dequeIndex(deque, deque->front + deque->queSize); + // 将 num 添加至队尾 + deque->nums[rear] = num; + deque->queSize++; + } + + /* 访问队首元素 */ + int peekFirst(arrayDeque *deque) { + // 访问异常:双向队列为空 + assert(empty(deque) == 0); + return deque->nums[deque->front]; + } + + /* 访问队尾元素 */ + int peekLast(arrayDeque *deque) { + // 访问异常:双向队列为空 + assert(empty(deque) == 0); + int last = dequeIndex(deque, deque->front + deque->queSize - 1); + return deque->nums[last]; + } + + /* 队首出队 */ + int popFirst(arrayDeque *deque) { + int num = peekFirst(deque); + // 队首指针向后移动一位 + deque->front = dequeIndex(deque, deque->front + 1); + deque->queSize--; + return num; + } + + /* 队尾出队 */ + int popLast(arrayDeque *deque) { + int num = peekLast(deque); + deque->queSize--; + return num; + } + + /* 打印队列 */ + void printArrayDeque(arrayDeque *deque) { + int arr[deque->queSize]; + // 拷贝 + for (int i = 0, j = deque->front; i < deque->queSize; i++, j++) { + arr[i] = deque->nums[j % deque->queCapacity]; + } + printArray(arr, deque->queSize); + } + ``` + +=== "Zig" + + ```zig title="array_deque.zig" + [class]{ArrayDeque}-[func]{} + ``` + ## 5.3.3   双向队列应用 双向队列兼具栈与队列的逻辑,**因此它可以实现这两者的所有应用场景,同时提供更高的自由度**。 diff --git a/chapter_stack_and_queue/queue.md b/chapter_stack_and_queue/queue.md index b18e02c3e..3a8476801 100755 --- a/chapter_stack_and_queue/queue.md +++ b/chapter_stack_and_queue/queue.md @@ -30,58 +30,6 @@ comments: true 我们可以直接使用编程语言中现成的队列类。 -=== "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 pop = 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" @@ -110,6 +58,84 @@ comments: true is_empty: bool = len(que) == 0 ``` +=== "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(); + ``` + +=== "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 pop = queue.poll(); + + /* 获取队列的长度 */ + int size = queue.size(); + + /* 判断队列是否为空 */ + boolean isEmpty = queue.isEmpty(); + ``` + +=== "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 pop = queue.Dequeue(); + + /* 获取队列的长度 */ + int size = queue.Count; + + /* 判断队列是否为空 */ + bool isEmpty = queue.Count == 0; + ``` + === "Go" ```go title="queue_test.go" @@ -138,6 +164,34 @@ comments: true isEmpty := queue.Len() == 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! + + /* 元素出队 */ + // 由于是数组,因此 removeFirst 的复杂度为 O(n) + let pool = queue.removeFirst() + + /* 获取队列的长度 */ + let size = queue.count + + /* 判断队列是否为空 */ + let isEmpty = queue.isEmpty + ``` + === "JS" ```javascript title="queue.js" @@ -194,72 +248,6 @@ comments: true const empty = queue.length === 0; ``` -=== "C" - - ```c title="queue.c" - // 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 pop = 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! - - /* 元素出队 */ - // 由于是数组,因此 removeFirst 的复杂度为 O(n) - let pool = queue.removeFirst() - - /* 获取队列的长度 */ - let size = queue.count - - /* 判断队列是否为空 */ - let isEmpty = queue.isEmpty - ``` - -=== "Zig" - - ```zig title="queue.zig" - - ``` - === "Dart" ```dart title="queue.dart" @@ -293,6 +281,18 @@ comments: true ``` +=== "C" + + ```c title="queue.c" + // C 未提供内置队列 + ``` + +=== "Zig" + + ```zig title="queue.zig" + + ``` + ## 5.2.2   队列实现 为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素。因此,链表和数组都可以用来实现队列。 @@ -314,72 +314,63 @@ comments: true 以下是用链表实现队列的代码。 -=== "Java" +=== "Python" - ```java title="linkedlist_queue.java" - /* 基于链表实现的队列 */ - class LinkedListQueue { - private ListNode front, rear; // 头节点 front ,尾节点 rear - private int queSize = 0; + ```python title="linkedlist_queue.py" + class LinkedListQueue: + """基于链表实现的队列""" - public LinkedListQueue() { - front = null; - rear = null; - } + def __init__(self): + """构造方法""" + self.__front: ListNode | None = None # 头节点 front + self.__rear: ListNode | None = None # 尾节点 rear + self.__size: int = 0 - /* 获取队列的长度 */ - public int size() { - return queSize; - } + def size(self) -> int: + """获取队列的长度""" + return self.__size - /* 判断队列是否为空 */ - public boolean isEmpty() { - return size() == 0; - } + def is_empty(self) -> bool: + """判断队列是否为空""" + return not self.__front - /* 入队 */ - 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++; - } + def push(self, num: int): + """入队""" + # 尾节点后添加 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 - /* 出队 */ - public int pop() { - int num = peek(); - // 删除头节点 - front = front.next; - queSize--; - return num; - } + def pop(self) -> int: + """出队""" + num = self.peek() + # 删除头节点 + self.__front = self.__front.next + self.__size -= 1 + return num - /* 访问队首元素 */ - public int peek() { - if (size() == 0) - throw new IndexOutOfBoundsException(); - return front.val; - } + def peek(self) -> int: + """访问队首元素""" + if self.size() == 0: + print("队列为空") + return False + return self.__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; - } - } + def to_list(self) -> list[int]: + """转化为列表用于打印""" + queue = [] + temp = self.__front + while temp: + queue.append(temp.val) + temp = temp.next + return queue ``` === "C++" @@ -461,63 +452,143 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="linkedlist_queue.py" - class LinkedListQueue: - """基于链表实现的队列""" + ```java title="linkedlist_queue.java" + /* 基于链表实现的队列 */ + class LinkedListQueue { + private ListNode front, rear; // 头节点 front ,尾节点 rear + private int queSize = 0; - def __init__(self): - """构造方法""" - self.__front: ListNode | None = None # 头节点 front - self.__rear: ListNode | None = None # 尾节点 rear - self.__size: int = 0 + public LinkedListQueue() { + front = null; + rear = null; + } - def size(self) -> int: - """获取队列的长度""" - return self.__size + /* 获取队列的长度 */ + public int size() { + return queSize; + } - def is_empty(self) -> bool: - """判断队列是否为空""" - return not self.__front + /* 判断队列是否为空 */ + public boolean isEmpty() { + return size() == 0; + } - def push(self, num: int): - """入队""" - # 尾节点后添加 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 + /* 入队 */ + 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++; + } - def pop(self) -> int: - """出队""" - num = self.peek() - # 删除头节点 - self.__front = self.__front.next - self.__size -= 1 - return num + /* 出队 */ + public int pop() { + int num = peek(); + // 删除头节点 + front = front.next; + queSize--; + return num; + } - def peek(self) -> int: - """访问队首元素""" - if self.size() == 0: - print("队列为空") - return False - return self.__front.val + /* 访问队首元素 */ + public int peek() { + if (size() == 0) + throw new IndexOutOfBoundsException(); + return front.val; + } - def to_list(self) -> list[int]: - """转化为列表用于打印""" - queue = [] - temp = self.__front - while temp: - queue.append(temp.val) - temp = temp.next - return queue + /* 将链表转化为 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#" + + ```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 pop() { + int num = peek(); + // 删除头节点 + front = front?.next; + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peek() { + if (size() == 0 || front == null) + throw new Exception(); + return front.val; + } + + /* 将链表转化为 Array 并返回 */ + public int[] toArray() { + if (front == null) + return Array.Empty(); + + 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; + } + } ``` === "Go" @@ -576,6 +647,75 @@ comments: true } ``` +=== "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 pop() -> Int { + let num = peek() + // 删除头节点 + front = front?.next + _size -= 1 + return num + } + + /* 访问队首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("队列为空") + } + return front!.val + } + + /* 将链表转化为 Array 并返回 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } + } + ``` + === "JS" ```javascript title="linkedlist_queue.js" @@ -713,323 +853,6 @@ comments: true } ``` -=== "C" - - ```c title="linkedlist_queue.c" - /* 基于链表实现的队列 */ - struct linkedListQueue { - ListNode *front, *rear; - int queSize; - }; - - typedef struct linkedListQueue linkedListQueue; - - /* 构造函数 */ - linkedListQueue *newLinkedListQueue() { - linkedListQueue *queue = (linkedListQueue *)malloc(sizeof(linkedListQueue)); - queue->front = NULL; - queue->rear = NULL; - queue->queSize = 0; - return queue; - } - - /* 析构函数 */ - void delLinkedListQueue(linkedListQueue *queue) { - // 释放所有节点 - for (int i = 0; i < queue->queSize && queue->front != NULL; i++) { - ListNode *tmp = queue->front; - queue->front = queue->front->next; - free(tmp); - } - // 释放 queue 结构体 - free(queue); - } - - /* 获取队列的长度 */ - int size(linkedListQueue *queue) { - return queue->queSize; - } - - /* 判断队列是否为空 */ - bool empty(linkedListQueue *queue) { - return (size(queue) == 0); - } - - /* 入队 */ - void push(linkedListQueue *queue, int num) { - // 尾节点处添加 node - ListNode *node = newListNode(num); - // 如果队列为空,则令头、尾节点都指向该节点 - if (queue->front == NULL) { - queue->front = node; - queue->rear = node; - } - // 如果队列不为空,则将该节点添加到尾节点后 - else { - queue->rear->next = node; - queue->rear = node; - } - queue->queSize++; - } - - /* 访问队首元素 */ - int peek(linkedListQueue *queue) { - assert(size(queue) && queue->front); - return queue->front->val; - } - - /* 出队 */ - void pop(linkedListQueue *queue) { - int num = peek(queue); - ListNode *tmp = queue->front; - queue->front = queue->front->next; - free(tmp); - queue->queSize--; - } - - /* 打印队列 */ - void printLinkedListQueue(linkedListQueue *queue) { - int arr[queue->queSize]; - // 拷贝链表中的数据到数组 - int i; - ListNode *node; - for (i = 0, node = queue->front; i < queue->queSize && queue->front != queue->rear; i++) { - arr[i] = node->val; - node = node->next; - } - printArray(arr, queue->queSize); - } - ``` - -=== "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 pop() { - int num = peek(); - // 删除头节点 - front = front?.next; - queSize--; - return num; - } - - /* 访问队首元素 */ - public int peek() { - if (size() == 0 || front == null) - throw new Exception(); - return front.val; - } - - /* 将链表转化为 Array 并返回 */ - public int[] toArray() { - if (front == null) - return Array.Empty(); - - 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; - } - } - ``` - -=== "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 pop() -> Int { - let num = peek() - // 删除头节点 - front = front?.next - _size -= 1 - return num - } - - /* 访问队首元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("队列为空") - } - return front!.val - } - - /* 将链表转化为 Array 并返回 */ - func toArray() -> [Int] { - var node = front - var res = Array(repeating: 0, count: size()) - for i in res.indices { - res[i] = node!.val - node = node?.next - } - return res - } - } - ``` - -=== "Zig" - - ```zig title="linkedlist_queue.zig" - // 基于链表实现的队列 - fn LinkedListQueue(comptime T: type) type { - return struct { - const Self = @This(); - - front: ?*inc.ListNode(T) = null, // 头节点 front - rear: ?*inc.ListNode(T) = null, // 尾节点 rear - que_size: usize = 0, // 队列的长度 - 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.front = null; - self.rear = null; - self.que_size = 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.que_size; - } - - // 判断队列是否为空 - pub fn isEmpty(self: *Self) bool { - return self.size() == 0; - } - - // 访问队首元素 - pub fn peek(self: *Self) T { - if (self.size() == 0) @panic("队列为空"); - return self.front.?.val; - } - - // 入队 - pub fn push(self: *Self, num: T) !void { - // 尾节点后添加 num - var node = try self.mem_allocator.create(inc.ListNode(T)); - node.init(num); - // 如果队列为空,则令头、尾节点都指向该节点 - if (self.front == null) { - self.front = node; - self.rear = node; - // 如果队列不为空,则将该节点添加到尾节点后 - } else { - self.rear.?.next = node; - self.rear = node; - } - self.que_size += 1; - } - - // 出队 - pub fn pop(self: *Self) T { - var num = self.peek(); - // 删除头节点 - self.front = self.front.?.next; - self.que_size -= 1; - return num; - } - - // 将链表转换为数组 - pub fn toArray(self: *Self) ![]T { - var node = self.front; - var res = try self.mem_allocator.alloc(T, self.size()); - @memset(res, @as(T, 0)); - var i: usize = 0; - while (i < res.len) : (i += 1) { - res[i] = node.?.val; - node = node.?.next; - } - return res; - } - }; - } - ``` - === "Dart" ```dart title="linkedlist_queue.dart" @@ -1182,6 +1005,183 @@ comments: true } ``` +=== "C" + + ```c title="linkedlist_queue.c" + /* 基于链表实现的队列 */ + struct linkedListQueue { + ListNode *front, *rear; + int queSize; + }; + + typedef struct linkedListQueue linkedListQueue; + + /* 构造函数 */ + linkedListQueue *newLinkedListQueue() { + linkedListQueue *queue = (linkedListQueue *)malloc(sizeof(linkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; + } + + /* 析构函数 */ + void delLinkedListQueue(linkedListQueue *queue) { + // 释放所有节点 + for (int i = 0; i < queue->queSize && queue->front != NULL; i++) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // 释放 queue 结构体 + free(queue); + } + + /* 获取队列的长度 */ + int size(linkedListQueue *queue) { + return queue->queSize; + } + + /* 判断队列是否为空 */ + bool empty(linkedListQueue *queue) { + return (size(queue) == 0); + } + + /* 入队 */ + void push(linkedListQueue *queue, int num) { + // 尾节点处添加 node + ListNode *node = newListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // 如果队列不为空,则将该节点添加到尾节点后 + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; + } + + /* 访问队首元素 */ + int peek(linkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; + } + + /* 出队 */ + void pop(linkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + } + + /* 打印队列 */ + void printLinkedListQueue(linkedListQueue *queue) { + int arr[queue->queSize]; + // 拷贝链表中的数据到数组 + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize && queue->front != queue->rear; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + } + ``` + +=== "Zig" + + ```zig title="linkedlist_queue.zig" + // 基于链表实现的队列 + fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // 头节点 front + rear: ?*inc.ListNode(T) = null, // 尾节点 rear + que_size: usize = 0, // 队列的长度 + 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.front = null; + self.rear = null; + self.que_size = 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.que_size; + } + + // 判断队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问队首元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("队列为空"); + return self.front.?.val; + } + + // 入队 + pub fn push(self: *Self, num: T) !void { + // 尾节点后添加 num + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (self.front == null) { + self.front = node; + self.rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // 出队 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 删除头节点 + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // 将链表转换为数组 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; + } + ``` + ### 2.   基于数组的实现 由于数组删除首元素的时间复杂度为 $O(n)$ ,这会导致出队操作效率较低。然而,我们可以采用以下巧妙方法来避免这个问题。 @@ -1210,75 +1210,63 @@ comments: true 对于环形数组,我们需要让 `front` 或 `rear` 在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示。 -=== "Java" +=== "Python" - ```java title="array_queue.java" - /* 基于环形数组实现的队列 */ - class ArrayQueue { - private int[] nums; // 用于存储队列元素的数组 - private int front; // 队首指针,指向队首元素 - private int queSize; // 队列长度 + ```python title="array_queue.py" + class ArrayQueue: + """基于环形数组实现的队列""" - public ArrayQueue(int capacity) { - nums = new int[capacity]; - front = queSize = 0; - } + def __init__(self, size: int): + """构造方法""" + self.__nums: list[int] = [0] * size # 用于存储队列元素的数组 + self.__front: int = 0 # 队首指针,指向队首元素 + self.__size: int = 0 # 队列长度 - /* 获取队列的容量 */ - public int capacity() { - return nums.length; - } + def capacity(self) -> int: + """获取队列的容量""" + return len(self.__nums) - /* 获取队列的长度 */ - public int size() { - return queSize; - } + def size(self) -> int: + """获取队列的长度""" + return self.__size - /* 判断队列是否为空 */ - public boolean isEmpty() { - return queSize == 0; - } + def is_empty(self) -> bool: + """判断队列是否为空""" + return self.__size == 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++; - } + def push(self, num: int): + """入队""" + if self.__size == self.capacity(): + raise IndexError("队列已满") + # 计算尾指针,指向队尾索引 + 1 + # 通过取余操作,实现 rear 越过数组尾部后回到头部 + rear: int = (self.__front + self.__size) % self.capacity() + # 将 num 添加至队尾 + self.__nums[rear] = num + self.__size += 1 - /* 出队 */ - public int pop() { - int num = peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - queSize--; - return num; - } + def pop(self) -> int: + """出队""" + num: int = self.peek() + # 队首指针向后移动一位,若越过尾部则返回到数组头部 + self.__front = (self.__front + 1) % self.capacity() + self.__size -= 1 + return num - /* 访问队首元素 */ - public int peek() { - if (isEmpty()) - throw new IndexOutOfBoundsException(); - return nums[front]; - } + def peek(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("队列为空") + return self.__nums[self.__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; - } - } + def to_list(self) -> list[int]: + """返回列表用于打印""" + res = [0] * self.size() + j: int = self.__front + for i in range(self.size()): + res[i] = self.__nums[(j % self.capacity())] + j += 1 + return res ``` === "C++" @@ -1360,63 +1348,146 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="array_queue.py" - class ArrayQueue: - """基于环形数组实现的队列""" + ```java title="array_queue.java" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private int[] nums; // 用于存储队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 队列长度 - def __init__(self, size: int): - """构造方法""" - self.__nums: list[int] = [0] * size # 用于存储队列元素的数组 - self.__front: int = 0 # 队首指针,指向队首元素 - self.__size: int = 0 # 队列长度 + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } - def capacity(self) -> int: - """获取队列的容量""" - return len(self.__nums) + /* 获取队列的容量 */ + public int capacity() { + return nums.length; + } - def size(self) -> int: - """获取队列的长度""" - return self.__size + /* 获取队列的长度 */ + public int size() { + return queSize; + } - def is_empty(self) -> bool: - """判断队列是否为空""" - return self.__size == 0 + /* 判断队列是否为空 */ + public boolean isEmpty() { + return queSize == 0; + } - def push(self, num: int): - """入队""" - if self.__size == self.capacity(): - raise IndexError("队列已满") - # 计算尾指针,指向队尾索引 + 1 - # 通过取余操作,实现 rear 越过数组尾部后回到头部 - rear: int = (self.__front + self.__size) % self.capacity() - # 将 num 添加至队尾 - self.__nums[rear] = num - self.__size += 1 + /* 入队 */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("队列已满"); + return; + } + // 计算尾指针,指向队尾索引 + 1 + // 通过取余操作,实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % capacity(); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } - def pop(self) -> int: - """出队""" - num: int = self.peek() - # 队首指针向后移动一位,若越过尾部则返回到数组头部 - self.__front = (self.__front + 1) % self.capacity() - self.__size -= 1 - return num + /* 出队 */ + public int pop() { + int num = peek(); + // 队首指针向后移动一位,若越过尾部则返回到数组头部 + front = (front + 1) % capacity(); + queSize--; + return num; + } - def peek(self) -> int: - """访问队首元素""" - if self.is_empty(): - raise IndexError("队列为空") - return self.__nums[self.__front] + /* 访问队首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } - def to_list(self) -> list[int]: - """返回列表用于打印""" - res = [0] * self.size() - j: int = self.__front - for i in range(self.size()): - res[i] = self.__nums[(j % self.capacity())] - j += 1 - return res + /* 返回数组 */ + 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#" + + ```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 pop() { + int num = peek(); + // 队首指针向后移动一位,若越过尾部则返回到数组头部 + front = (front + 1) % capacity(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peek() { + if (isEmpty()) + throw new Exception(); + 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 % this.capacity()]; + } + return res; + } + } ``` === "Go" @@ -1492,6 +1563,79 @@ comments: true } ``` +=== "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 pop() -> Int { + let num = peek() + // 队首指针向后移动一位,若越过尾部则返回到数组头部 + front = (front + 1) % capacity() + queSize -= 1 + return num + } + + /* 访问队首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("队列为空") + } + return nums[front] + } + + /* 返回数组 */ + func toArray() -> [Int] { + // 仅转换有效长度范围内的列表元素 + var res = Array(repeating: 0, count: queSize) + for (i, j) in sequence(first: (0, front), next: { $0 < self.queSize - 1 ? ($0 + 1, $1 + 1) : nil }) { + res[i] = nums[j % capacity()] + } + return res + } + } + ``` + === "JS" ```javascript title="array_queue.js" @@ -1631,325 +1775,6 @@ comments: true } ``` -=== "C" - - ```c title="array_queue.c" - /* 基于环形数组实现的队列 */ - struct arrayQueue { - int *nums; // 用于存储队列元素的数组 - int front; // 队首指针,指向队首元素 - int queSize; // 尾指针,指向队尾 + 1 - int queCapacity; // 队列容量 - }; - - typedef struct arrayQueue arrayQueue; - - /* 构造函数 */ - arrayQueue *newArrayQueue(int capacity) { - arrayQueue *queue = (arrayQueue *)malloc(sizeof(arrayQueue)); - // 初始化数组 - queue->queCapacity = capacity; - queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); - queue->front = queue->queSize = 0; - return queue; - } - - /* 析构函数 */ - void delArrayQueue(arrayQueue *queue) { - free(queue->nums); - queue->queCapacity = 0; - } - - /* 获取队列的容量 */ - int capacity(arrayQueue *queue) { - return queue->queCapacity; - } - - /* 获取队列的长度 */ - int size(arrayQueue *queue) { - return queue->queSize; - } - - /* 判断队列是否为空 */ - bool empty(arrayQueue *queue) { - return queue->queSize == 0; - } - - /* 访问队首元素 */ - int peek(arrayQueue *queue) { - assert(size(queue) != 0); - return queue->nums[queue->front]; - } - - /* 入队 */ - void push(arrayQueue *queue, int num) { - if (size(queue) == capacity(queue)) { - printf("队列已满\r\n"); - return; - } - // 计算队尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - int rear = (queue->front + queue->queSize) % queue->queCapacity; - // 将 num 添加至队尾 - queue->nums[rear] = num; - queue->queSize++; - } - - /* 出队 */ - void pop(arrayQueue *queue) { - int num = peek(queue); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - queue->front = (queue->front + 1) % queue->queCapacity; - queue->queSize--; - } - - /* 打印队列 */ - void printArrayQueue(arrayQueue *queue) { - int arr[queue->queSize]; - // 拷贝 - for (int i = 0, j = queue->front; i < queue->queSize; i++, j++) { - arr[i] = queue->nums[j % queue->queCapacity]; - } - printArray(arr, queue->queSize); - } - ``` - -=== "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 pop() { - int num = peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity(); - queSize--; - return num; - } - - /* 访问队首元素 */ - public int peek() { - if (isEmpty()) - throw new Exception(); - 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 % this.capacity()]; - } - return res; - } - } - ``` - -=== "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 pop() -> Int { - let num = peek() - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - front = (front + 1) % capacity() - queSize -= 1 - return num - } - - /* 访问队首元素 */ - func peek() -> Int { - if isEmpty() { - fatalError("队列为空") - } - return nums[front] - } - - /* 返回数组 */ - func toArray() -> [Int] { - // 仅转换有效长度范围内的列表元素 - var res = Array(repeating: 0, count: queSize) - for (i, j) in sequence(first: (0, front), next: { $0 < self.queSize - 1 ? ($0 + 1, $1 + 1) : nil }) { - res[i] = nums[j % capacity()] - } - return res - } - } - ``` - -=== "Zig" - - ```zig title="array_queue.zig" - // 基于环形数组实现的队列 - fn ArrayQueue(comptime T: type) type { - return struct { - const Self = @This(); - - nums: []T = undefined, // 用于存储队列元素的数组 - cap: usize = 0, // 队列容量 - front: usize = 0, // 队首指针,指向队首元素 - queSize: usize = 0, // 尾指针,指向队尾 + 1 - mem_arena: ?std.heap.ArenaAllocator = null, - mem_allocator: std.mem.Allocator = undefined, // 内存分配器 - - // 构造函数(分配内存+初始化数组) - pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { - if (self.mem_arena == null) { - self.mem_arena = std.heap.ArenaAllocator.init(allocator); - self.mem_allocator = self.mem_arena.?.allocator(); - } - self.cap = cap; - self.nums = try self.mem_allocator.alloc(T, self.cap); - @memset(self.nums, @as(T, 0)); - } - - // 析构函数(释放内存) - pub fn deinit(self: *Self) void { - if (self.mem_arena == null) return; - self.mem_arena.?.deinit(); - } - - // 获取队列的容量 - pub fn capacity(self: *Self) usize { - return self.cap; - } - - // 获取队列的长度 - pub fn size(self: *Self) usize { - return self.queSize; - } - - // 判断队列是否为空 - pub fn isEmpty(self: *Self) bool { - return self.queSize == 0; - } - - // 入队 - pub fn push(self: *Self, num: T) !void { - if (self.size() == self.capacity()) { - std.debug.print("队列已满\n", .{}); - return; - } - // 计算尾指针,指向队尾索引 + 1 - // 通过取余操作,实现 rear 越过数组尾部后回到头部 - var rear = (self.front + self.queSize) % self.capacity(); - // 尾节点后添加 num - self.nums[rear] = num; - self.queSize += 1; - } - - // 出队 - pub fn pop(self: *Self) T { - var num = self.peek(); - // 队首指针向后移动一位,若越过尾部则返回到数组头部 - self.front = (self.front + 1) % self.capacity(); - self.queSize -= 1; - return num; - } - - // 访问队首元素 - pub fn peek(self: *Self) T { - if (self.isEmpty()) @panic("队列为空"); - return self.nums[self.front]; - } - - // 返回数组 - pub fn toArray(self: *Self) ![]T { - // 仅转换有效长度范围内的列表元素 - var res = try self.mem_allocator.alloc(T, self.size()); - @memset(res, @as(T, 0)); - var i: usize = 0; - var j: usize = self.front; - while (i < self.size()) : ({ i += 1; j += 1; }) { - res[i] = self.nums[j % self.capacity()]; - } - return res; - } - }; - } - ``` - === "Dart" ```dart title="array_queue.dart" @@ -2103,6 +1928,181 @@ comments: true } ``` +=== "C" + + ```c title="array_queue.c" + /* 基于环形数组实现的队列 */ + struct arrayQueue { + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 尾指针,指向队尾 + 1 + int queCapacity; // 队列容量 + }; + + typedef struct arrayQueue arrayQueue; + + /* 构造函数 */ + arrayQueue *newArrayQueue(int capacity) { + arrayQueue *queue = (arrayQueue *)malloc(sizeof(arrayQueue)); + // 初始化数组 + queue->queCapacity = capacity; + queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); + queue->front = queue->queSize = 0; + return queue; + } + + /* 析构函数 */ + void delArrayQueue(arrayQueue *queue) { + free(queue->nums); + queue->queCapacity = 0; + } + + /* 获取队列的容量 */ + int capacity(arrayQueue *queue) { + return queue->queCapacity; + } + + /* 获取队列的长度 */ + int size(arrayQueue *queue) { + return queue->queSize; + } + + /* 判断队列是否为空 */ + bool empty(arrayQueue *queue) { + return queue->queSize == 0; + } + + /* 访问队首元素 */ + int peek(arrayQueue *queue) { + assert(size(queue) != 0); + return queue->nums[queue->front]; + } + + /* 入队 */ + void push(arrayQueue *queue, int num) { + if (size(queue) == capacity(queue)) { + printf("队列已满\r\n"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作,实现 rear 越过数组尾部后回到头部 + int rear = (queue->front + queue->queSize) % queue->queCapacity; + // 将 num 添加至队尾 + queue->nums[rear] = num; + queue->queSize++; + } + + /* 出队 */ + void pop(arrayQueue *queue) { + int num = peek(queue); + // 队首指针向后移动一位,若越过尾部则返回到数组头部 + queue->front = (queue->front + 1) % queue->queCapacity; + queue->queSize--; + } + + /* 打印队列 */ + void printArrayQueue(arrayQueue *queue) { + int arr[queue->queSize]; + // 拷贝 + for (int i = 0, j = queue->front; i < queue->queSize; i++, j++) { + arr[i] = queue->nums[j % queue->queCapacity]; + } + printArray(arr, queue->queSize); + } + ``` + +=== "Zig" + + ```zig title="array_queue.zig" + // 基于环形数组实现的队列 + fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // 用于存储队列元素的数组 + cap: usize = 0, // 队列容量 + front: usize = 0, // 队首指针,指向队首元素 + queSize: usize = 0, // 尾指针,指向队尾 + 1 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化数组) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取队列的容量 + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // 获取队列的长度 + pub fn size(self: *Self) usize { + return self.queSize; + } + + // 判断队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // 入队 + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("队列已满\n", .{}); + return; + } + // 计算尾指针,指向队尾索引 + 1 + // 通过取余操作,实现 rear 越过数组尾部后回到头部 + var rear = (self.front + self.queSize) % self.capacity(); + // 尾节点后添加 num + self.nums[rear] = num; + self.queSize += 1; + } + + // 出队 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 队首指针向后移动一位,若越过尾部则返回到数组头部 + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // 访问队首元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("队列为空"); + return self.nums[self.front]; + } + + // 返回数组 + pub fn toArray(self: *Self) ![]T { + // 仅转换有效长度范围内的列表元素 + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; + } + ``` + 以上实现的队列仍然具有局限性,即其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的同学可以尝试自行实现。 两种实现的对比结论与栈一致,在此不再赘述。 diff --git a/chapter_stack_and_queue/stack.md b/chapter_stack_and_queue/stack.md index 1ded144a5..459dd5a40 100755 --- a/chapter_stack_and_queue/stack.md +++ b/chapter_stack_and_queue/stack.md @@ -32,30 +32,31 @@ comments: true 通常情况下,我们可以直接使用编程语言内置的栈类。然而,某些语言可能没有专门提供栈类,这时我们可以将该语言的“数组”或“链表”视作栈来使用,并在程序逻辑上忽略与栈无关的操作。 -=== "Java" +=== "Python" - ```java title="stack.java" - /* 初始化栈 */ - Stack stack = new Stack<>(); - - /* 元素入栈 */ - 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.size(); - - /* 判断是否为空 */ - boolean isEmpty = stack.isEmpty(); + ```python title="stack.py" + # 初始化栈 + # Python 没有内置的栈类,可以把 List 当作栈来使用 + stack: list[int] = [] + + # 元素入栈 + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + # 访问栈顶元素 + peek: int = stack[-1] + + # 元素出栈 + pop: int = stack.pop() + + # 获取栈的长度 + size: int = len(stack) + + # 判断是否为空 + is_empty: bool = len(stack) == 0 ``` === "C++" @@ -84,31 +85,56 @@ comments: true bool empty = stack.empty(); ``` -=== "Python" +=== "Java" - ```python title="stack.py" - # 初始化栈 - # Python 没有内置的栈类,可以把 List 当作栈来使用 - stack: list[int] = [] + ```java title="stack.java" + /* 初始化栈 */ + Stack stack = new Stack<>(); + + /* 元素入栈 */ + 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.size(); + + /* 判断是否为空 */ + boolean isEmpty = stack.isEmpty(); + ``` + +=== "C#" + + ```csharp title="stack.cs" + /* 初始化栈 */ + Stack stack = new (); - # 元素入栈 - stack.append(1) - stack.append(3) - stack.append(2) - stack.append(5) - stack.append(4) + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); - # 访问栈顶元素 - peek: int = stack[-1] + /* 访问栈顶元素 */ + int peek = stack.Peek(); - # 元素出栈 - pop: int = stack.pop() + /* 元素出栈 */ + int pop = stack.Pop(); - # 获取栈的长度 - size: int = len(stack) + /* 获取栈的长度 */ + int size = stack.Count; - # 判断是否为空 - is_empty: bool = len(stack) == 0 + /* 判断是否为空 */ + bool isEmpty = stack.Count == 0; ``` === "Go" @@ -139,6 +165,33 @@ comments: true isEmpty := len(stack) == 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 + ``` + === "JS" ```javascript title="stack.js" @@ -193,71 +246,6 @@ comments: true const is_empty = stack.length === 0; ``` -=== "C" - - ```c title="stack.c" - // 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" - - ``` - === "Dart" ```dart title="stack.dart" @@ -291,6 +279,18 @@ comments: true ``` +=== "C" + + ```c title="stack.c" + // C 未提供内置栈 + ``` + +=== "Zig" + + ```zig title="stack.zig" + + ``` + ## 5.1.2   栈的实现 为了深入了解栈的运行机制,我们来尝试自己实现一个栈类。 @@ -316,62 +316,55 @@ comments: true 以下是基于链表实现栈的示例代码。 -=== "Java" +=== "Python" - ```java title="linkedlist_stack.java" - /* 基于链表实现的栈 */ - class LinkedListStack { - private ListNode stackPeek; // 将头节点作为栈顶 - private int stkSize = 0; // 栈的长度 + ```python title="linkedlist_stack.py" + class LinkedListStack: + """基于链表实现的栈""" - public LinkedListStack() { - stackPeek = null; - } + def __init__(self): + """构造方法""" + self.__peek: ListNode | None = None + self.__size: int = 0 - /* 获取栈的长度 */ - public int size() { - return stkSize; - } + def size(self) -> int: + """获取栈的长度""" + return self.__size - /* 判断栈是否为空 */ - public boolean isEmpty() { - return size() == 0; - } + def is_empty(self) -> bool: + """判断栈是否为空""" + return not self.__peek - /* 入栈 */ - public void push(int num) { - ListNode node = new ListNode(num); - node.next = stackPeek; - stackPeek = node; - stkSize++; - } + def push(self, val: int): + """入栈""" + node = ListNode(val) + node.next = self.__peek + self.__peek = node + self.__size += 1 - /* 出栈 */ - public int pop() { - int num = peek(); - stackPeek = stackPeek.next; - stkSize--; - return num; - } + def pop(self) -> int: + """出栈""" + num: int = self.peek() + self.__peek = self.__peek.next + self.__size -= 1 + return num - /* 访问栈顶元素 */ - public int peek() { - if (size() == 0) - throw new IndexOutOfBoundsException(); - return stackPeek.val; - } + def peek(self) -> int: + """访问栈顶元素""" + # 判空处理 + if not self.__peek: + return None + return self.__peek.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; - } - } + def to_list(self) -> list[int]: + """转化为列表用于打印""" + arr = [] + node = self.__peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr ``` === "C++" @@ -442,55 +435,126 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="linkedlist_stack.py" - class LinkedListStack: - """基于链表实现的栈""" + ```java title="linkedlist_stack.java" + /* 基于链表实现的栈 */ + class LinkedListStack { + private ListNode stackPeek; // 将头节点作为栈顶 + private int stkSize = 0; // 栈的长度 - def __init__(self): - """构造方法""" - self.__peek: ListNode | None = None - self.__size: int = 0 + public LinkedListStack() { + stackPeek = null; + } - def size(self) -> int: - """获取栈的长度""" - return self.__size + /* 获取栈的长度 */ + public int size() { + return stkSize; + } - def is_empty(self) -> bool: - """判断栈是否为空""" - return not self.__peek + /* 判断栈是否为空 */ + public boolean isEmpty() { + return size() == 0; + } - def push(self, val: int): - """入栈""" - node = ListNode(val) - node.next = self.__peek - self.__peek = node - self.__size += 1 + /* 入栈 */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } - def pop(self) -> int: - """出栈""" - num: int = self.peek() - self.__peek = self.__peek.next - self.__size -= 1 - return num + /* 出栈 */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } - def peek(self) -> int: - """访问栈顶元素""" - # 判空处理 - if not self.__peek: - return None - return self.__peek.val + /* 访问栈顶元素 */ + public int peek() { + if (size() == 0) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } - def to_list(self) -> list[int]: - """转化为列表用于打印""" - arr = [] - node = self.__peek - while node: - arr.append(node.val) - node = node.next - arr.reverse() - return arr + /* 将 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#" + + ```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() { + if (stackPeek == null) + throw new Exception(); + + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + public int peek() { + if (size() == 0 || stackPeek == null) + throw new Exception(); + return stackPeek.val; + } + + /* 将 List 转化为 Array 并返回 */ + public int[] toArray() { + if (stackPeek == null) + return Array.Empty(); + + 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; + } + } ``` === "Go" @@ -549,6 +613,64 @@ comments: true } ``` +=== "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 + } + + /* 将 List 转化为 Array 并返回 */ + func toArray() -> [Int] { + var node = _peek + var res = Array(repeating: 0, count: _size) + for i in sequence(first: res.count - 1, next: { $0 >= 0 + 1 ? $0 - 1 : nil }) { + res[i] = node!.val + node = node?.next + } + return res + } + } + ``` + === "JS" ```javascript title="linkedlist_stack.js" @@ -664,281 +786,6 @@ comments: true } ``` -=== "C" - - ```c title="linkedlist_stack.c" - /* 基于链表实现的栈 */ - struct linkedListStack { - ListNode *top; // 将头节点作为栈顶 - int size; // 栈的长度 - }; - - typedef struct linkedListStack linkedListStack; - - /* 构造函数 */ - linkedListStack *newLinkedListStack() { - linkedListStack *s = malloc(sizeof(linkedListStack)); - s->top = NULL; - s->size = 0; - return s; - } - - /* 析构函数 */ - void delLinkedListStack(linkedListStack *s) { - while (s->top) { - ListNode *n = s->top->next; - free(s->top); - s->top = n; - } - free(s); - } - - /* 获取栈的长度 */ - int size(linkedListStack *s) { - assert(s); - return s->size; - } - - /* 判断栈是否为空 */ - bool isEmpty(linkedListStack *s) { - assert(s); - return size(s) == 0; - } - - /* 访问栈顶元素 */ - int peek(linkedListStack *s) { - assert(s); - assert(size(s) != 0); - return s->top->val; - } - - /* 入栈 */ - void push(linkedListStack *s, int num) { - assert(s); - ListNode *node = (ListNode *)malloc(sizeof(ListNode)); - node->next = s->top; // 更新新加节点指针域 - node->val = num; // 更新新加节点数据域 - s->top = node; // 更新栈顶 - s->size++; // 更新栈大小 - } - - /* 出栈 */ - int pop(linkedListStack *s) { - if (s->size == 0) { - printf("stack is empty.\n"); - return INT_MAX; - } - assert(s); - int val = peek(s); - ListNode *tmp = s->top; - s->top = s->top->next; - // 释放内存 - free(tmp); - s->size--; - return val; - } - ``` - -=== "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() { - if (stackPeek == null) - throw new Exception(); - - int num = peek(); - stackPeek = stackPeek.next; - stkSize--; - return num; - } - - /* 访问栈顶元素 */ - public int peek() { - if (size() == 0 || stackPeek == null) - throw new Exception(); - return stackPeek.val; - } - - /* 将 List 转化为 Array 并返回 */ - public int[] toArray() { - if (stackPeek == null) - return Array.Empty(); - - 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; - } - } - ``` - -=== "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 - } - - /* 将 List 转化为 Array 并返回 */ - func toArray() -> [Int] { - var node = _peek - var res = Array(repeating: 0, count: _size) - for i in sequence(first: res.count - 1, next: { $0 >= 0 + 1 ? $0 - 1 : nil }) { - res[i] = node!.val - node = node?.next - } - return res - } - } - ``` - -=== "Zig" - - ```zig title="linkedlist_stack.zig" - // 基于链表实现的栈 - fn LinkedListStack(comptime T: type) type { - return struct { - const Self = @This(); - - stack_top: ?*inc.ListNode(T) = null, // 将头节点作为栈顶 - stk_size: usize = 0, // 栈的长度 - 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.stack_top = null; - self.stk_size = 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.stk_size; - } - - // 判断栈是否为空 - pub fn isEmpty(self: *Self) bool { - return self.size() == 0; - } - - // 访问栈顶元素 - pub fn peek(self: *Self) T { - if (self.size() == 0) @panic("栈为空"); - return self.stack_top.?.val; - } - - // 入栈 - pub fn push(self: *Self, num: T) !void { - var node = try self.mem_allocator.create(inc.ListNode(T)); - node.init(num); - node.next = self.stack_top; - self.stack_top = node; - self.stk_size += 1; - } - - // 出栈 - pub fn pop(self: *Self) T { - var num = self.peek(); - self.stack_top = self.stack_top.?.next; - self.stk_size -= 1; - return num; - } - - // 将栈转换为数组 - pub fn toArray(self: *Self) ![]T { - var node = self.stack_top; - var res = try self.mem_allocator.alloc(T, self.size()); - @memset(res, @as(T, 0)); - var i: usize = 0; - while (i < res.len) : (i += 1) { - res[res.len - i - 1] = node.?.val; - node = node.?.next; - } - return res; - } - }; - } - ``` - === "Dart" ```dart title="linkedlist_stack.dart" @@ -1068,6 +915,159 @@ comments: true } ``` +=== "C" + + ```c title="linkedlist_stack.c" + /* 基于链表实现的栈 */ + struct linkedListStack { + ListNode *top; // 将头节点作为栈顶 + int size; // 栈的长度 + }; + + typedef struct linkedListStack linkedListStack; + + /* 构造函数 */ + linkedListStack *newLinkedListStack() { + linkedListStack *s = malloc(sizeof(linkedListStack)); + s->top = NULL; + s->size = 0; + return s; + } + + /* 析构函数 */ + void delLinkedListStack(linkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); + } + + /* 获取栈的长度 */ + int size(linkedListStack *s) { + assert(s); + return s->size; + } + + /* 判断栈是否为空 */ + bool isEmpty(linkedListStack *s) { + assert(s); + return size(s) == 0; + } + + /* 访问栈顶元素 */ + int peek(linkedListStack *s) { + assert(s); + assert(size(s) != 0); + return s->top->val; + } + + /* 入栈 */ + void push(linkedListStack *s, int num) { + assert(s); + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // 更新新加节点指针域 + node->val = num; // 更新新加节点数据域 + s->top = node; // 更新栈顶 + s->size++; // 更新栈大小 + } + + /* 出栈 */ + int pop(linkedListStack *s) { + if (s->size == 0) { + printf("stack is empty.\n"); + return INT_MAX; + } + assert(s); + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // 释放内存 + free(tmp); + s->size--; + return val; + } + ``` + +=== "Zig" + + ```zig title="linkedlist_stack.zig" + // 基于链表实现的栈 + fn LinkedListStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack_top: ?*inc.ListNode(T) = null, // 将头节点作为栈顶 + stk_size: usize = 0, // 栈的长度 + 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.stack_top = null; + self.stk_size = 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.stk_size; + } + + // 判断栈是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问栈顶元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("栈为空"); + return self.stack_top.?.val; + } + + // 入栈 + pub fn push(self: *Self, num: T) !void { + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; + } + + // 出栈 + pub fn pop(self: *Self) T { + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; + return num; + } + + // 将栈转换为数组 + pub fn toArray(self: *Self) ![]T { + var node = self.stack_top; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[res.len - i - 1] = node.?.val; + node = node.?.next; + } + return res; + } + }; + } + ``` + ### 2.   基于数组的实现 使用数组实现栈时,我们可以将数组的尾部作为栈顶。如图 5-3 所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。 @@ -1085,6 +1085,89 @@ comments: true 由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组,这样就无须自行处理数组扩容问题。以下为示例代码。 +=== "Python" + + ```python title="array_stack.py" + class ArrayStack: + """基于数组实现的栈""" + + def __init__(self): + """构造方法""" + self.__stack: list[int] = [] + + def size(self) -> int: + """获取栈的长度""" + return len(self.__stack) + + def is_empty(self) -> bool: + """判断栈是否为空""" + return self.__stack == [] + + def push(self, item: int): + """入栈""" + self.__stack.append(item) + + def pop(self) -> int: + """出栈""" + if self.is_empty(): + raise IndexError("栈为空") + return self.__stack.pop() + + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self.__stack[-1] + + def to_list(self) -> list[int]: + """返回列表用于打印""" + return self.__stack + ``` + +=== "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(); + } + + /* 返回 Vector */ + vector toVector() { + return stack; + } + }; + ``` + === "Java" ```java title="array_stack.java" @@ -1133,87 +1216,53 @@ comments: true } ``` -=== "C++" +=== "C#" - ```cpp title="array_stack.cpp" + ```csharp title="array_stack.cs" /* 基于数组实现的栈 */ class ArrayStack { - private: - vector stack; + private List stack; + public ArrayStack() { + // 初始化列表(动态数组) + stack = new(); + } - public: /* 获取栈的长度 */ - int size() { - return stack.size(); + public int size() { + return stack.Count(); } /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); + public bool isEmpty() { + return size() == 0; } /* 入栈 */ - void push(int num) { - stack.push_back(num); + public void push(int num) { + stack.Add(num); } /* 出栈 */ - void pop() { - int oldTop = top(); - stack.pop_back(); + public int pop() { + if (isEmpty()) + throw new Exception(); + var val = peek(); + stack.RemoveAt(size() - 1); + return val; } /* 访问栈顶元素 */ - int top() { - if (empty()) - throw out_of_range("栈为空"); - return stack.back(); + public int peek() { + if (isEmpty()) + throw new Exception(); + return stack[size() - 1]; } - /* 返回 Vector */ - vector toVector() { - return stack; + /* 将 List 转化为 Array 并返回 */ + public int[] toArray() { + return stack.ToArray(); } - }; - ``` - -=== "Python" - - ```python title="array_stack.py" - class ArrayStack: - """基于数组实现的栈""" - - def __init__(self): - """构造方法""" - self.__stack: list[int] = [] - - def size(self) -> int: - """获取栈的长度""" - return len(self.__stack) - - def is_empty(self) -> bool: - """判断栈是否为空""" - return self.__stack == [] - - def push(self, item: int): - """入栈""" - self.__stack.append(item) - - def pop(self) -> int: - """出栈""" - if self.is_empty(): - raise IndexError("栈为空") - return self.__stack.pop() - - def peek(self) -> int: - """访问栈顶元素""" - if self.is_empty(): - raise IndexError("栈为空") - return self.__stack[-1] - - def to_list(self) -> list[int]: - """返回列表用于打印""" - return self.__stack + } ``` === "Go" @@ -1270,6 +1319,57 @@ comments: true } ``` +=== "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! + } + + /* 将 List 转化为 Array 并返回 */ + func toArray() -> [Int] { + stack + } + } + ``` + === "JS" ```javascript title="array_stack.js" @@ -1358,225 +1458,6 @@ comments: true } ``` -=== "C" - - ```c title="array_stack.c" - /* 基于数组实现的栈 */ - struct arrayStack { - int *data; - int size; - }; - - typedef struct arrayStack arrayStack; - - /* 构造函数 */ - arrayStack *newArrayStack() { - arrayStack *s = malloc(sizeof(arrayStack)); - // 初始化一个大容量,避免扩容 - s->data = malloc(sizeof(int) * MAX_SIZE); - s->size = 0; - return s; - } - - /* 获取栈的长度 */ - int size(arrayStack *s) { - return s->size; - } - - /* 判断栈是否为空 */ - bool isEmpty(arrayStack *s) { - return s->size == 0; - } - - /* 入栈 */ - void push(arrayStack *s, int num) { - if (s->size == MAX_SIZE) { - printf("stack is full.\n"); - return; - } - s->data[s->size] = num; - s->size++; - } - - /* 访问栈顶元素 */ - int peek(arrayStack *s) { - if (s->size == 0) { - printf("stack is empty.\n"); - return INT_MAX; - } - return s->data[s->size - 1]; - } - - /* 出栈 */ - int pop(arrayStack *s) { - if (s->size == 0) { - printf("stack is empty.\n"); - return INT_MAX; - } - int val = peek(s); - s->size--; - return val; - } - ``` - -=== "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]; - } - - /* 将 List 转化为 Array 并返回 */ - public int[] toArray() { - return stack.ToArray(); - } - } - ``` - -=== "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! - } - - /* 将 List 转化为 Array 并返回 */ - func toArray() -> [Int] { - stack - } - } - ``` - -=== "Zig" - - ```zig title="array_stack.zig" - // 基于数组实现的栈 - fn ArrayStack(comptime T: type) type { - return struct { - const Self = @This(); - - stack: ?std.ArrayList(T) = null, - - // 构造方法(分配内存+初始化栈) - pub fn init(self: *Self, allocator: std.mem.Allocator) void { - if (self.stack == null) { - self.stack = std.ArrayList(T).init(allocator); - } - } - - // 析构方法(释放内存) - pub fn deinit(self: *Self) void { - if (self.stack == null) return; - self.stack.?.deinit(); - } - - // 获取栈的长度 - pub fn size(self: *Self) usize { - return self.stack.?.items.len; - } - - // 判断栈是否为空 - pub fn isEmpty(self: *Self) bool { - return self.size() == 0; - } - - // 访问栈顶元素 - pub fn peek(self: *Self) T { - if (self.isEmpty()) @panic("栈为空"); - return self.stack.?.items[self.size() - 1]; - } - - // 入栈 - pub fn push(self: *Self, num: T) !void { - try self.stack.?.append(num); - } - - // 出栈 - pub fn pop(self: *Self) T { - var num = self.stack.?.pop(); - return num; - } - - // 返回 ArrayList - pub fn toList(self: *Self) std.ArrayList(T) { - return self.stack.?; - } - }; - } - ``` - === "Dart" ```dart title="array_stack.dart" @@ -1673,6 +1554,125 @@ comments: true } ``` +=== "C" + + ```c title="array_stack.c" + /* 基于数组实现的栈 */ + struct arrayStack { + int *data; + int size; + }; + + typedef struct arrayStack arrayStack; + + /* 构造函数 */ + arrayStack *newArrayStack() { + arrayStack *s = malloc(sizeof(arrayStack)); + // 初始化一个大容量,避免扩容 + s->data = malloc(sizeof(int) * MAX_SIZE); + s->size = 0; + return s; + } + + /* 获取栈的长度 */ + int size(arrayStack *s) { + return s->size; + } + + /* 判断栈是否为空 */ + bool isEmpty(arrayStack *s) { + return s->size == 0; + } + + /* 入栈 */ + void push(arrayStack *s, int num) { + if (s->size == MAX_SIZE) { + printf("stack is full.\n"); + return; + } + s->data[s->size] = num; + s->size++; + } + + /* 访问栈顶元素 */ + int peek(arrayStack *s) { + if (s->size == 0) { + printf("stack is empty.\n"); + return INT_MAX; + } + return s->data[s->size - 1]; + } + + /* 出栈 */ + int pop(arrayStack *s) { + if (s->size == 0) { + printf("stack is empty.\n"); + return INT_MAX; + } + int val = peek(s); + s->size--; + return val; + } + ``` + +=== "Zig" + + ```zig title="array_stack.zig" + // 基于数组实现的栈 + fn ArrayStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack: ?std.ArrayList(T) = null, + + // 构造方法(分配内存+初始化栈) + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.stack == null) { + self.stack = std.ArrayList(T).init(allocator); + } + } + + // 析构方法(释放内存) + pub fn deinit(self: *Self) void { + if (self.stack == null) return; + self.stack.?.deinit(); + } + + // 获取栈的长度 + pub fn size(self: *Self) usize { + return self.stack.?.items.len; + } + + // 判断栈是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问栈顶元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("栈为空"); + return self.stack.?.items[self.size() - 1]; + } + + // 入栈 + pub fn push(self: *Self, num: T) !void { + try self.stack.?.append(num); + } + + // 出栈 + pub fn pop(self: *Self) T { + var num = self.stack.?.pop(); + return num; + } + + // 返回 ArrayList + pub fn toList(self: *Self) std.ArrayList(T) { + return self.stack.?; + } + }; + } + ``` + ## 5.1.3   两种实现对比 **支持操作** diff --git a/chapter_tree/array_representation_of_tree.md b/chapter_tree/array_representation_of_tree.md index b137b2e68..3bed9ff53 100644 --- a/chapter_tree/array_representation_of_tree.md +++ b/chapter_tree/array_representation_of_tree.md @@ -32,12 +32,12 @@ comments: true 为了解决此问题,**我们可以考虑在层序遍历序列中显式地写出所有 $\text{None}$** 。如图 7-14 所示,这样处理后,层序遍历序列就可以唯一表示二叉树了。 -=== "Java" +=== "Python" - ```java title="" - /* 二叉树的数组表示 */ - // 使用 int 的包装类 Integer ,就可以使用 null 来标记空位 - Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ```python title="" + # 二叉树的数组表示 + # 使用 None 来表示空位 + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" @@ -48,12 +48,20 @@ comments: true vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` -=== "Python" +=== "Java" - ```python title="" - # 二叉树的数组表示 - # 使用 None 来表示空位 - tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ```java title="" + /* 二叉树的数组表示 */ + // 使用 int 的包装类 Integer ,就可以使用 null 来标记空位 + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* 二叉树的数组表示 */ + // 使用 int? 可空类型 ,就可以使用 null 来标记空位 + int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "Go" @@ -64,6 +72,14 @@ comments: true tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` +=== "Swift" + + ```swift title="" + /* 二叉树的数组表示 */ + // 使用 Int? 可空类型 ,就可以使用 nil 来标记空位 + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + === "JS" ```javascript title="" @@ -80,36 +96,6 @@ comments: true let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` -=== "C" - - ```c title="" - /* 二叉树的数组表示 */ - // 使用 int 最大值标记空位,因此要求节点值不能为 INT_MAX - int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; - ``` - -=== "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="" - - ``` - === "Dart" ```dart title="" @@ -124,6 +110,20 @@ comments: true ``` +=== "C" + + ```c title="" + /* 二叉树的数组表示 */ + // 使用 int 最大值标记空位,因此要求节点值不能为 INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Zig" + + ```zig title="" + + ``` + ![任意类型二叉树的数组表示](array_representation_of_tree.assets/array_representation_with_empty.png)

图 7-14   任意类型二叉树的数组表示

@@ -141,96 +141,81 @@ comments: true - 给定某节点,获取它的值、左(右)子节点、父节点。 - 获取前序遍历、中序遍历、后序遍历、层序遍历序列。 -=== "Java" +=== "Python" - ```java title="array_binary_tree.java" - /* 数组表示下的二叉树类 */ - class ArrayBinaryTree { - private List tree; + ```python title="array_binary_tree.py" + class ArrayBinaryTree: + """数组表示下的二叉树类""" - /* 构造方法 */ - public ArrayBinaryTree(List arr) { - tree = new ArrayList<>(arr); - } + def __init__(self, arr: list[int | None]): + """构造方法""" + self.__tree = list(arr) - /* 节点数量 */ - public int size() { - return tree.size(); - } + def size(self): + """节点数量""" + return len(self.__tree) - /* 获取索引为 i 节点的值 */ - public Integer val(int i) { - // 若索引越界,则返回 null ,代表空位 - if (i < 0 || i >= size()) - return null; - return tree.get(i); - } + def val(self, i: int) -> int: + """获取索引为 i 节点的值""" + # 若索引越界,则返回 None ,代表空位 + if i < 0 or i >= self.size(): + return None + return self.__tree[i] - /* 获取索引为 i 节点的左子节点的索引 */ - public Integer left(int i) { - return 2 * i + 1; - } + def left(self, i: int) -> int | None: + """获取索引为 i 节点的左子节点的索引""" + return 2 * i + 1 - /* 获取索引为 i 节点的右子节点的索引 */ - public Integer right(int i) { - return 2 * i + 2; - } + def right(self, i: int) -> int | None: + """获取索引为 i 节点的右子节点的索引""" + return 2 * i + 2 - /* 获取索引为 i 节点的父节点的索引 */ - public Integer parent(int i) { - return (i - 1) / 2; - } + def parent(self, i: int) -> int | None: + """获取索引为 i 节点的父节点的索引""" + return (i - 1) // 2 - /* 层序遍历 */ - public List levelOrder() { - List res = new ArrayList<>(); - // 直接遍历数组 - for (int i = 0; i < size(); i++) { - if (val(i) != null) - res.add(val(i)); - } - return res; - } + def level_order(self) -> list[int]: + """层序遍历""" + self.res = [] + # 直接遍历数组 + for i in range(self.size()): + if self.val(i) is not None: + self.res.append(self.val(i)) + return self.res - /* 深度优先遍历 */ - private void dfs(Integer i, String order, List res) { - // 若为空位,则返回 - if (val(i) == null) - return; - // 前序遍历 - if (order == "pre") - res.add(val(i)); - dfs(left(i), order, res); - // 中序遍历 - if (order == "in") - res.add(val(i)); - dfs(right(i), order, res); - // 后序遍历 - if (order == "post") - res.add(val(i)); - } + def __dfs(self, i: int, order: str): + """深度优先遍历""" + if self.val(i) is None: + return + # 前序遍历 + if order == "pre": + self.res.append(self.val(i)) + self.__dfs(self.left(i), order) + # 中序遍历 + if order == "in": + self.res.append(self.val(i)) + self.__dfs(self.right(i), order) + # 后序遍历 + if order == "post": + self.res.append(self.val(i)) - /* 前序遍历 */ - public List preOrder() { - List res = new ArrayList<>(); - dfs(0, "pre", res); - return res; - } + def pre_order(self) -> list[int]: + """前序遍历""" + self.res = [] + self.__dfs(0, order="pre") + return self.res - /* 中序遍历 */ - public List inOrder() { - List res = new ArrayList<>(); - dfs(0, "in", res); - return res; - } + def in_order(self) -> list[int]: + """中序遍历""" + self.res = [] + self.__dfs(0, order="in") + return self.res - /* 后序遍历 */ - public List postOrder() { - List res = new ArrayList<>(); - dfs(0, "post", res); - return res; - } - } + def post_order(self) -> list[int]: + """后序遍历""" + self.res = [] + self.__dfs(0, order="post") + return self.res ``` === "C++" @@ -327,81 +312,188 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="array_binary_tree.py" - class ArrayBinaryTree: - """数组表示下的二叉树类""" + ```java title="array_binary_tree.java" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + private List tree; - def __init__(self, arr: list[int | None]): - """构造方法""" - self.__tree = list(arr) + /* 构造方法 */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } - def size(self): - """节点数量""" - return len(self.__tree) + /* 节点数量 */ + public int size() { + return tree.size(); + } - def val(self, i: int) -> int: - """获取索引为 i 节点的值""" - # 若索引越界,则返回 None ,代表空位 - if i < 0 or i >= self.size(): - return None - return self.__tree[i] + /* 获取索引为 i 节点的值 */ + public Integer val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } - def left(self, i: int) -> int | None: - """获取索引为 i 节点的左子节点的索引""" - return 2 * i + 1 + /* 获取索引为 i 节点的左子节点的索引 */ + public Integer left(int i) { + return 2 * i + 1; + } - def right(self, i: int) -> int | None: - """获取索引为 i 节点的右子节点的索引""" - return 2 * i + 2 + /* 获取索引为 i 节点的右子节点的索引 */ + public Integer right(int i) { + return 2 * i + 2; + } - def parent(self, i: int) -> int | None: - """获取索引为 i 节点的父节点的索引""" - return (i - 1) // 2 + /* 获取索引为 i 节点的父节点的索引 */ + public Integer parent(int i) { + return (i - 1) / 2; + } - def level_order(self) -> list[int]: - """层序遍历""" - self.res = [] - # 直接遍历数组 - for i in range(self.size()): - if self.val(i) is not None: - self.res.append(self.val(i)) - return self.res + /* 层序遍历 */ + public List levelOrder() { + List res = new ArrayList<>(); + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } - def __dfs(self, i: int, order: str): - """深度优先遍历""" - if self.val(i) is None: - return - # 前序遍历 - if order == "pre": - self.res.append(self.val(i)) - self.__dfs(self.left(i), order) - # 中序遍历 - if order == "in": - self.res.append(self.val(i)) - self.__dfs(self.right(i), order) - # 后序遍历 - if order == "post": - self.res.append(self.val(i)) + /* 深度优先遍历 */ + private void dfs(Integer i, String order, List res) { + // 若为空位,则返回 + if (val(i) == null) + return; + // 前序遍历 + if (order == "pre") + res.add(val(i)); + dfs(left(i), order, res); + // 中序遍历 + if (order == "in") + res.add(val(i)); + dfs(right(i), order, res); + // 后序遍历 + if (order == "post") + res.add(val(i)); + } - def pre_order(self) -> list[int]: - """前序遍历""" - self.res = [] - self.__dfs(0, order="pre") - return self.res + /* 前序遍历 */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } - def in_order(self) -> list[int]: - """中序遍历""" - self.res = [] - self.__dfs(0, order="in") - return self.res + /* 中序遍历 */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } - def post_order(self) -> list[int]: - """后序遍历""" - self.res = [] - self.__dfs(0, order="post") - return self.res + /* 后序遍历 */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } + } + ``` + +=== "C#" + + ```csharp title="array_binary_tree.cs" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + private List tree; + + /* 构造方法 */ + public ArrayBinaryTree(List arr) { + tree = new List(arr); + } + + /* 节点数量 */ + public int size() { + return tree.Count; + } + + /* 获取索引为 i 节点的值 */ + public int? val(int i) { + // 若索引越界,则返回 null ,代表空位 + if (i < 0 || i >= size()) + return null; + return tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + public int left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + public int right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + public int parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + public List levelOrder() { + List res = new List(); + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i).HasValue) + res.Add(val(i).Value); + } + return res; + } + + /* 深度优先遍历 */ + private void dfs(int i, string order, List res) { + // 若为空位,则返回 + if (!val(i).HasValue) + return; + // 前序遍历 + if (order == "pre") + res.Add(val(i).Value); + dfs(left(i), order, res); + // 中序遍历 + if (order == "in") + res.Add(val(i).Value); + dfs(right(i), order, res); + // 后序遍历 + if (order == "post") + res.Add(val(i).Value); + } + + /* 前序遍历 */ + public List preOrder() { + List res = new List(); + dfs(0, "pre", res); + return res; + } + + /* 中序遍历 */ + public List inOrder() { + List res = new List(); + dfs(0, "in", res); + return res; + } + + /* 后序遍历 */ + public List postOrder() { + List res = new List(); + dfs(0, "post", res); + return res; + } + } ``` === "Go" @@ -504,6 +596,104 @@ comments: true } ``` +=== "Swift" + + ```swift title="array_binary_tree.swift" + /* 数组表示下的二叉树类 */ + class ArrayBinaryTree { + private var tree: [Int?] + + /* 构造方法 */ + init(arr: [Int?]) { + tree = arr + } + + /* 节点数量 */ + func size() -> Int { + tree.count + } + + /* 获取索引为 i 节点的值 */ + func val(i: Int) -> Int? { + // 若索引越界,则返回 null ,代表空位 + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* 获取索引为 i 节点的左子节点的索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 获取索引为 i 节点的右子节点的索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 获取索引为 i 节点的父节点的索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* 层序遍历 */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // 直接遍历数组 + for i in stride(from: 0, to: size(), by: 1) { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* 深度优先遍历 */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // 若为空位,则返回 + guard let val = val(i: i) else { + return + } + // 前序遍历 + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // 中序遍历 + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // 后序遍历 + if order == "post" { + res.append(val) + } + } + + /* 前序遍历 */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* 中序遍历 */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* 后序遍历 */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } + } + ``` + === "JS" ```javascript title="array_binary_tree.js" @@ -676,291 +866,6 @@ comments: true } ``` -=== "C" - - ```c title="array_binary_tree.c" - /* 数组表示下的二叉树类 */ - struct arrayBinaryTree { - vector *tree; - }; - - typedef struct arrayBinaryTree arrayBinaryTree; - - /* 构造函数 */ - arrayBinaryTree *newArrayBinaryTree(vector *arr) { - arrayBinaryTree *newABT = malloc(sizeof(arrayBinaryTree)); - newABT->tree = arr; - return newABT; - } - - /* 节点数量 */ - int size(arrayBinaryTree *abt) { - return abt->tree->size; - } - - /* 获取索引为 i 节点的值 */ - int val(arrayBinaryTree *abt, int i) { - // 若索引越界,则返回 INT_MAX ,代表空位 - if (i < 0 || i >= size(abt)) - return INT_MAX; - return *(int *)abt->tree->data[i]; - } - - /* 深度优先遍历 */ - void dfs(arrayBinaryTree *abt, int i, const char *order, vector *res) { - // 若为空位,则返回 - if (val(abt, i) == INT_MAX) - return; - // 前序遍历 - if (strcmp(order, "pre") == 0) { - int tmp = val(abt, i); - vectorPushback(res, &tmp, sizeof(tmp)); - } - dfs(abt, left(i), order, res); - // 中序遍历 - if (strcmp(order, "in") == 0) { - int tmp = val(abt, i); - vectorPushback(res, &tmp, sizeof(tmp)); - } - dfs(abt, right(i), order, res); - // 后序遍历 - if (strcmp(order, "post") == 0) { - int tmp = val(abt, i); - vectorPushback(res, &tmp, sizeof(tmp)); - } - } - - /* 层序遍历 */ - vector *levelOrder(arrayBinaryTree *abt) { - vector *res = newVector(); - // 直接遍历数组 - for (int i = 0; i < size(abt); i++) { - if (val(abt, i) != INT_MAX) { - int tmp = val(abt, i); - vectorPushback(res, &tmp, sizeof(int)); - } - } - return res; - } - - /* 前序遍历 */ - vector *preOrder(arrayBinaryTree *abt) { - vector *res = newVector(); - dfs(abt, 0, "pre", res); - return res; - } - - /* 中序遍历 */ - vector *inOrder(arrayBinaryTree *abt) { - vector *res = newVector(); - dfs(abt, 0, "in", res); - return res; - } - - /* 后序遍历 */ - vector *postOrder(arrayBinaryTree *abt) { - vector *res = newVector(); - dfs(abt, 0, "post", res); - return res; - } - ``` - -=== "C#" - - ```csharp title="array_binary_tree.cs" - /* 数组表示下的二叉树类 */ - class ArrayBinaryTree { - private List tree; - - /* 构造方法 */ - public ArrayBinaryTree(List arr) { - tree = new List(arr); - } - - /* 节点数量 */ - public int size() { - return tree.Count; - } - - /* 获取索引为 i 节点的值 */ - public int? val(int i) { - // 若索引越界,则返回 null ,代表空位 - if (i < 0 || i >= size()) - return null; - return tree[i]; - } - - /* 获取索引为 i 节点的左子节点的索引 */ - public int left(int i) { - return 2 * i + 1; - } - - /* 获取索引为 i 节点的右子节点的索引 */ - public int right(int i) { - return 2 * i + 2; - } - - /* 获取索引为 i 节点的父节点的索引 */ - public int parent(int i) { - return (i - 1) / 2; - } - - /* 层序遍历 */ - public List levelOrder() { - List res = new List(); - // 直接遍历数组 - for (int i = 0; i < size(); i++) { - if (val(i).HasValue) - res.Add(val(i).Value); - } - return res; - } - - /* 深度优先遍历 */ - private void dfs(int i, string order, List res) { - // 若为空位,则返回 - if (!val(i).HasValue) - return; - // 前序遍历 - if (order == "pre") - res.Add(val(i).Value); - dfs(left(i), order, res); - // 中序遍历 - if (order == "in") - res.Add(val(i).Value); - dfs(right(i), order, res); - // 后序遍历 - if (order == "post") - res.Add(val(i).Value); - } - - /* 前序遍历 */ - public List preOrder() { - List res = new List(); - dfs(0, "pre", res); - return res; - } - - /* 中序遍历 */ - public List inOrder() { - List res = new List(); - dfs(0, "in", res); - return res; - } - - /* 后序遍历 */ - public List postOrder() { - List res = new List(); - dfs(0, "post", res); - return res; - } - } - ``` - -=== "Swift" - - ```swift title="array_binary_tree.swift" - /* 数组表示下的二叉树类 */ - class ArrayBinaryTree { - private var tree: [Int?] - - /* 构造方法 */ - init(arr: [Int?]) { - tree = arr - } - - /* 节点数量 */ - func size() -> Int { - tree.count - } - - /* 获取索引为 i 节点的值 */ - func val(i: Int) -> Int? { - // 若索引越界,则返回 null ,代表空位 - if i < 0 || i >= size() { - return nil - } - return tree[i] - } - - /* 获取索引为 i 节点的左子节点的索引 */ - func left(i: Int) -> Int { - 2 * i + 1 - } - - /* 获取索引为 i 节点的右子节点的索引 */ - func right(i: Int) -> Int { - 2 * i + 2 - } - - /* 获取索引为 i 节点的父节点的索引 */ - func parent(i: Int) -> Int { - (i - 1) / 2 - } - - /* 层序遍历 */ - func levelOrder() -> [Int] { - var res: [Int] = [] - // 直接遍历数组 - for i in stride(from: 0, to: size(), by: 1) { - if let val = val(i: i) { - res.append(val) - } - } - return res - } - - /* 深度优先遍历 */ - private func dfs(i: Int, order: String, res: inout [Int]) { - // 若为空位,则返回 - guard let val = val(i: i) else { - return - } - // 前序遍历 - if order == "pre" { - res.append(val) - } - dfs(i: left(i: i), order: order, res: &res) - // 中序遍历 - if order == "in" { - res.append(val) - } - dfs(i: right(i: i), order: order, res: &res) - // 后序遍历 - if order == "post" { - res.append(val) - } - } - - /* 前序遍历 */ - func preOrder() -> [Int] { - var res: [Int] = [] - dfs(i: 0, order: "pre", res: &res) - return res - } - - /* 中序遍历 */ - func inOrder() -> [Int] { - var res: [Int] = [] - dfs(i: 0, order: "in", res: &res) - return res - } - - /* 后序遍历 */ - func postOrder() -> [Int] { - var res: [Int] = [] - dfs(i: 0, order: "post", res: &res) - return res - } - } - ``` - -=== "Zig" - - ```zig title="array_binary_tree.zig" - [class]{ArrayBinaryTree}-[func]{} - ``` - === "Dart" ```dart title="array_binary_tree.dart" @@ -1157,6 +1062,101 @@ comments: true } ``` +=== "C" + + ```c title="array_binary_tree.c" + /* 数组表示下的二叉树类 */ + struct arrayBinaryTree { + vector *tree; + }; + + typedef struct arrayBinaryTree arrayBinaryTree; + + /* 构造函数 */ + arrayBinaryTree *newArrayBinaryTree(vector *arr) { + arrayBinaryTree *newABT = malloc(sizeof(arrayBinaryTree)); + newABT->tree = arr; + return newABT; + } + + /* 节点数量 */ + int size(arrayBinaryTree *abt) { + return abt->tree->size; + } + + /* 获取索引为 i 节点的值 */ + int val(arrayBinaryTree *abt, int i) { + // 若索引越界,则返回 INT_MAX ,代表空位 + if (i < 0 || i >= size(abt)) + return INT_MAX; + return *(int *)abt->tree->data[i]; + } + + /* 深度优先遍历 */ + void dfs(arrayBinaryTree *abt, int i, const char *order, vector *res) { + // 若为空位,则返回 + if (val(abt, i) == INT_MAX) + return; + // 前序遍历 + if (strcmp(order, "pre") == 0) { + int tmp = val(abt, i); + vectorPushback(res, &tmp, sizeof(tmp)); + } + dfs(abt, left(i), order, res); + // 中序遍历 + if (strcmp(order, "in") == 0) { + int tmp = val(abt, i); + vectorPushback(res, &tmp, sizeof(tmp)); + } + dfs(abt, right(i), order, res); + // 后序遍历 + if (strcmp(order, "post") == 0) { + int tmp = val(abt, i); + vectorPushback(res, &tmp, sizeof(tmp)); + } + } + + /* 层序遍历 */ + vector *levelOrder(arrayBinaryTree *abt) { + vector *res = newVector(); + // 直接遍历数组 + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) { + int tmp = val(abt, i); + vectorPushback(res, &tmp, sizeof(int)); + } + } + return res; + } + + /* 前序遍历 */ + vector *preOrder(arrayBinaryTree *abt) { + vector *res = newVector(); + dfs(abt, 0, "pre", res); + return res; + } + + /* 中序遍历 */ + vector *inOrder(arrayBinaryTree *abt) { + vector *res = newVector(); + dfs(abt, 0, "in", res); + return res; + } + + /* 后序遍历 */ + vector *postOrder(arrayBinaryTree *abt) { + vector *res = newVector(); + dfs(abt, 0, "post", res); + return res; + } + ``` + +=== "Zig" + + ```zig title="array_binary_tree.zig" + [class]{ArrayBinaryTree}-[func]{} + ``` + ## 7.3.3   优势与局限性 二叉树的数组表示主要有以下优点。 diff --git a/chapter_tree/avl_tree.md b/chapter_tree/avl_tree.md index 67be3fcb6..c789c83d4 100644 --- a/chapter_tree/avl_tree.md +++ b/chapter_tree/avl_tree.md @@ -28,17 +28,16 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 由于 AVL 树的相关操作需要获取节点高度,因此我们需要为节点类添加 `height` 变量。 -=== "Java" +=== "Python" - ```java title="" - /* AVL 树节点类 */ - class TreeNode { - public int val; // 节点值 - public int height; // 节点高度 - public TreeNode left; // 左子节点 - public TreeNode right; // 右子节点 - public TreeNode(int x) { val = x; } - } + ```python title="" + class TreeNode: + """AVL 树节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.height: int = 0 # 节点高度 + self.left: Optional[TreeNode] = None # 左子节点引用 + self.right: Optional[TreeNode] = None # 右子节点引用 ``` === "C++" @@ -55,16 +54,30 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 }; ``` -=== "Python" +=== "Java" - ```python title="" - class TreeNode: - """AVL 树节点类""" - def __init__(self, val: int): - self.val: int = val # 节点值 - self.height: int = 0 # 节点高度 - self.left: Optional[TreeNode] = None # 左子节点引用 - self.right: Optional[TreeNode] = None # 右子节点引用 + ```java title="" + /* AVL 树节点类 */ + class TreeNode { + public int val; // 节点值 + public int height; // 节点高度 + public TreeNode left; // 左子节点 + public TreeNode right; // 右子节点 + public TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* AVL 树节点类 */ + class TreeNode { + public int val; // 节点值 + public int height; // 节点高度 + public TreeNode? left; // 左子节点 + public TreeNode? right; // 右子节点 + public TreeNode(int x) { val = x; } + } ``` === "Go" @@ -79,6 +92,23 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` +=== "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 + } + } + ``` + === "JS" ```javascript title="" @@ -115,6 +145,25 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` +=== "Dart" + + ```dart title="" + /* AVL 树节点类 */ + class TreeNode { + int val; // 节点值 + int height; // 节点高度 + TreeNode? left; // 左子节点 + TreeNode? right; // 右子节点 + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + + ``` + === "C" ```c title="" @@ -141,77 +190,28 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` -=== "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="" ``` -=== "Dart" - - ```dart title="" - /* AVL 树节点类 */ - class TreeNode { - int val; // 节点值 - int height; // 节点高度 - TreeNode? left; // 左子节点 - TreeNode? right; // 右子节点 - TreeNode(this.val, [this.height = 0, this.left, this.right]); - } - ``` - -=== "Rust" - - ```rust title="" - - ``` - “节点高度”是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1 。我们将创建两个工具函数,分别用于获取和更新节点的高度。 -=== "Java" +=== "Python" - ```java title="avl_tree.java" - /* 获取节点高度 */ - int height(TreeNode node) { - // 空节点高度为 -1 ,叶节点高度为 0 - return node == null ? -1 : node.height; - } + ```python title="avl_tree.py" + def height(self, node: TreeNode | None) -> int: + """获取节点高度""" + # 空节点高度为 -1 ,叶节点高度为 0 + if node is not None: + return node.height + return -1 - /* 更新节点高度 */ - void updateHeight(TreeNode node) { - // 节点高度等于最高子树高度 + 1 - node.height = Math.max(height(node.left), height(node.right)) + 1; - } + def __update_height(self, node: TreeNode | None): + """更新节点高度""" + # 节点高度等于最高子树高度 + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 ``` === "C++" @@ -230,20 +230,36 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def height(self, node: TreeNode | None) -> int: - """获取节点高度""" - # 空节点高度为 -1 ,叶节点高度为 0 - if node is not None: - return node.height - return -1 + ```java title="avl_tree.java" + /* 获取节点高度 */ + int height(TreeNode node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == null ? -1 : node.height; + } - def __update_height(self, node: TreeNode | None): - """更新节点高度""" - # 节点高度等于最高子树高度 + 1 - node.height = max([self.height(node.left), self.height(node.right)]) + 1 + /* 更新节点高度 */ + void updateHeight(TreeNode node) { + // 节点高度等于最高子树高度 + 1 + node.height = Math.max(height(node.left), height(node.right)) + 1; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 获取节点高度 */ + 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; + } ``` === "Go" @@ -271,6 +287,22 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` +=== "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 + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -305,80 +337,6 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 获取节点高度 */ - int height(TreeNode *node) { - // 空节点高度为 -1 ,叶节点高度为 0 - if (node != NULL) { - return node->height; - } - return -1; - } - - /* 更新节点高度 */ - void updateHeight(TreeNode *node) { - int lh = height(node->left); - int rh = height(node->right); - // 节点高度等于最高子树高度 + 1 - if (lh > rh) { - node->height = lh + 1; - } else { - node->height = rh + 1; - } - } - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 获取节点高度 */ - 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; - } - ``` - -=== "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" - // 获取节点高度 - fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { - _ = self; - // 空节点高度为 -1 ,叶节点高度为 0 - return if (node == null) -1 else node.?.height; - } - - // 更新节点高度 - fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { - // 节点高度等于最高子树高度 + 1 - node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -418,21 +376,62 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` +=== "C" + + ```c title="avl_tree.c" + /* 获取节点高度 */ + int height(TreeNode *node) { + // 空节点高度为 -1 ,叶节点高度为 0 + if (node != NULL) { + return node->height; + } + return -1; + } + + /* 更新节点高度 */ + void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // 节点高度等于最高子树高度 + 1 + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } + } + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 获取节点高度 + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // 空节点高度为 -1 ,叶节点高度为 0 + return if (node == null) -1 else node.?.height; + } + + // 更新节点高度 + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // 节点高度等于最高子树高度 + 1 + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + ``` + ### 2.   节点平衡因子 节点的「平衡因子 balance factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用。 -=== "Java" +=== "Python" - ```java title="avl_tree.java" - /* 获取平衡因子 */ - int balanceFactor(TreeNode node) { - // 空节点平衡因子为 0 - if (node == null) - return 0; - // 节点平衡因子 = 左子树高度 - 右子树高度 - return height(node.left) - height(node.right); - } + ```python title="avl_tree.py" + def balance_factor(self, node: TreeNode | None) -> int: + """获取平衡因子""" + # 空节点平衡因子为 0 + if node is None: + return 0 + # 节点平衡因子 = 左子树高度 - 右子树高度 + return self.height(node.left) - self.height(node.right) ``` === "C++" @@ -448,16 +447,29 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def balance_factor(self, node: TreeNode | None) -> int: - """获取平衡因子""" - # 空节点平衡因子为 0 - if node is None: - return 0 - # 节点平衡因子 = 左子树高度 - 右子树高度 - return self.height(node.left) - self.height(node.right) + ```java title="avl_tree.java" + /* 获取平衡因子 */ + int balanceFactor(TreeNode node) { + // 空节点平衡因子为 0 + if (node == null) + return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 获取平衡因子 */ + int balanceFactor(TreeNode? node) { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } ``` === "Go" @@ -474,6 +486,18 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` +=== "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) + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -498,56 +522,6 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 获取平衡因子 */ - int balanceFactor(TreeNode *node) { - // 空节点平衡因子为 0 - if (node == NULL) { - return 0; - } - // 节点平衡因子 = 左子树高度 - 右子树高度 - return height(node->left) - height(node->right); - } - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 获取平衡因子 */ - 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" - // 获取平衡因子 - fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { - // 空节点平衡因子为 0 - if (node == null) return 0; - // 节点平衡因子 = 左子树高度 - 右子树高度 - return self.height(node.?.left) - self.height(node.?.right); - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -576,6 +550,32 @@ AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉 } ``` +=== "C" + + ```c title="avl_tree.c" + /* 获取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空节点平衡因子为 0 + if (node == NULL) { + return 0; + } + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node->left) - height(node->right); + } + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 获取平衡因子 + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // 空节点平衡因子为 0 + if (node == null) return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return self.height(node.?.left) - self.height(node.?.right); + } + ``` + !!! note 设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。 @@ -612,22 +612,21 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 “向右旋转”是一种形象化的说法,实际上需要通过修改节点指针来实现,代码如下所示。 -=== "Java" +=== "Python" - ```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; - } + ```python title="avl_tree.py" + def __right_rotate(self, node: TreeNode | None) -> TreeNode | None: + """右旋操作""" + 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 ``` === "C++" @@ -648,21 +647,40 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def __right_rotate(self, node: TreeNode | None) -> TreeNode | None: - """右旋操作""" - 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 + ```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#" + + ```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; + } ``` === "Go" @@ -683,6 +701,24 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` +=== "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 + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -719,79 +755,6 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 右旋操作 */ - TreeNode *rightRotate(TreeNode *node) { - TreeNode *child, *grandChild; - child = node->left; - grandChild = child->right; - // 以 child 为原点,将 node 向右旋转 - child->right = node; - node->left = grandChild; - // 更新节点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } - ``` - -=== "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" - // 右旋操作 - fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { - var child = node.?.left; - var grandChild = child.?.right; - // 以 child 为原点,将 node 向右旋转 - child.?.right = node; - node.?.left = grandChild; - // 更新节点高度 - self.updateHeight(node); - self.updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -833,6 +796,43 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` +=== "C" + + ```c title="avl_tree.c" + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // 以 child 为原点,将 node 向右旋转 + child->right = node; + node->left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 右旋操作 + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // 以 child 为原点,将 node 向右旋转 + child.?.right = node; + node.?.left = grandChild; + // 更新节点高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + ### 2.   左旋 相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行图 7-28 所示的“左旋”操作。 @@ -849,22 +849,21 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 可以观察到,**右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的**。基于对称性,我们只需将右旋的实现代码中的所有的 `left` 替换为 `right` ,将所有的 `right` 替换为 `left` ,即可得到左旋的实现代码。 -=== "Java" +=== "Python" - ```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; - } + ```python title="avl_tree.py" + def __left_rotate(self, node: TreeNode | None) -> TreeNode | None: + """左旋操作""" + 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 ``` === "C++" @@ -885,21 +884,40 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def __left_rotate(self, node: TreeNode | None) -> TreeNode | None: - """左旋操作""" - 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 + ```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#" + + ```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; + } ``` === "Go" @@ -920,6 +938,24 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` +=== "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 + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -956,79 +992,6 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 左旋操作 */ - TreeNode *leftRotate(TreeNode *node) { - TreeNode *child, *grandChild; - child = node->right; - grandChild = child->left; - // 以 child 为原点,将 node 向左旋转 - child->left = node; - node->right = grandChild; - // 更新节点高度 - updateHeight(node); - updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } - ``` - -=== "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" - // 左旋操作 - fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { - var child = node.?.right; - var grandChild = child.?.left; - // 以 child 为原点,将 node 向左旋转 - child.?.left = node; - node.?.right = grandChild; - // 更新节点高度 - self.updateHeight(node); - self.updateHeight(child); - // 返回旋转后子树的根节点 - return child; - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -1070,6 +1033,43 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` +=== "C" + + ```c title="avl_tree.c" + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // 以 child 为原点,将 node 向左旋转 + child->left = node; + node->right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 左旋操作 + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // 以 child 为原点,将 node 向左旋转 + child.?.left = node; + node.?.right = grandChild; + // 更新节点高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + ``` + ### 3.   先左旋后右旋 对于图 7-30 中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 `child` 执行“左旋”,再对 `node` 执行“右旋”。 @@ -1111,38 +1111,33 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 为了便于使用,我们将旋转操作封装成一个函数。**有了这个函数,我们就能对各种失衡情况进行旋转,使失衡节点重新恢复平衡**。 -=== "Java" +=== "Python" - ```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; - } + ```python title="avl_tree.py" + def __rotate(self, node: TreeNode | None) -> TreeNode | None: + """执行旋转操作,使该子树重新恢复平衡""" + # 获取节点 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 ``` === "C++" @@ -1179,33 +1174,72 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def __rotate(self, node: TreeNode | None) -> TreeNode | None: - """执行旋转操作,使该子树重新恢复平衡""" - # 获取节点 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 + ```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#" + + ```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; + } ``` === "Go" @@ -1243,6 +1277,40 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` +=== "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 + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -1311,142 +1379,6 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 执行旋转操作,使该子树重新恢复平衡 */ - TreeNode *rotate(TreeNode *node) { - // 获取节点 node 的平衡因子 - int 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; - } - ``` - -=== "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" - // 执行旋转操作,使该子树重新恢复平衡 - fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { - // 获取节点 node 的平衡因子 - var balance_factor = self.balanceFactor(node); - // 左偏树 - if (balance_factor > 1) { - if (self.balanceFactor(node.?.left) >= 0) { - // 右旋 - return self.rightRotate(node); - } else { - // 先左旋后右旋 - node.?.left = self.leftRotate(node.?.left); - return self.rightRotate(node); - } - } - // 右偏树 - if (balance_factor < -1) { - if (self.balanceFactor(node.?.right) <= 0) { - // 左旋 - return self.leftRotate(node); - } else { - // 先右旋后左旋 - node.?.right = self.rightRotate(node.?.right); - return self.leftRotate(node); - } - } - // 平衡树,无须旋转,直接返回 - return node; - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -1520,37 +1452,103 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中 } ``` +=== "C" + + ```c title="avl_tree.c" + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode *rotate(TreeNode *node) { + // 获取节点 node 的平衡因子 + int 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; + } + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 执行旋转操作,使该子树重新恢复平衡 + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // 获取节点 node 的平衡因子 + var balance_factor = self.balanceFactor(node); + // 左偏树 + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // 右旋 + return self.rightRotate(node); + } else { + // 先左旋后右旋 + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // 右偏树 + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // 左旋 + return self.leftRotate(node); + } else { + // 先右旋后左旋 + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + ``` + ## 7.5.3   AVL 树常用操作 ### 1.   插入节点 AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,**我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡**。 -=== "Java" +=== "Python" - ```java title="avl_tree.java" - /* 插入节点 */ - void insert(int val) { - root = insertHelper(root, val); - } + ```python title="avl_tree.py" + def insert(self, val): + """插入节点""" + self.root = self.__insert_helper(self.root, val) - /* 递归插入节点(辅助方法) */ - 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; - } + def __insert_helper(self, node: TreeNode | None, 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) ``` === "C++" @@ -1580,29 +1578,57 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def insert(self, val): - """插入节点""" - self.root = self.__insert_helper(self.root, val) + ```java title="avl_tree.java" + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } - def __insert_helper(self, node: TreeNode | None, 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) + /* 递归插入节点(辅助方法) */ + 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#" + + ```csharp title="avl_tree.cs" + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 递归插入节点(辅助方法) */ + 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; + } ``` === "Go" @@ -1636,6 +1662,36 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + /* 插入节点 */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* 递归插入节点(辅助方法) */ + 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 + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -1687,125 +1743,6 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 插入节点 */ - void insert(aVLTree *tree, int val) { - tree->root = insertHelper(tree->root, val); - } - - /* 递归插入节点(辅助函数) */ - TreeNode *insertHelper(TreeNode *node, int val) { - if (node == NULL) { - 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; - } - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 插入节点 */ - void insert(int val) { - root = insertHelper(root, val); - } - - /* 递归插入节点(辅助方法) */ - 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" - /* 插入节点 */ - func insert(val: Int) { - root = insertHelper(node: root, val: val) - } - - /* 递归插入节点(辅助方法) */ - 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" - // 插入节点 - fn insert(self: *Self, val: T) !void { - self.root = (try self.insertHelper(self.root, val)).?; - } - - // 递归插入节点(辅助方法) - fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { - var node = node_; - if (node == null) { - var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); - tmp_node.init(val); - return tmp_node; - } - // 1. 查找插入位置,并插入节点 - if (val < node.?.val) { - node.?.left = try self.insertHelper(node.?.left, val); - } else if (val > node.?.val) { - node.?.right = try self.insertHelper(node.?.right, val); - } else { - return node; // 重复节点不插入,直接返回 - } - self.updateHeight(node); // 更新节点高度 - // 2. 执行旋转操作,使该子树重新恢复平衡 - node = self.rotate(node); - // 返回子树的根节点 - return node; - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -1874,47 +1811,30 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` -### 2.   删除节点 +=== "C" -类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶地执行旋转操作,使所有失衡节点恢复平衡。 - -=== "Java" - - ```java title="avl_tree.java" - /* 删除节点 */ - void remove(int val) { - root = removeHelper(root, val); + ```c title="avl_tree.c" + /* 插入节点 */ + void insert(aVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); } - /* 递归删除节点(辅助方法) */ - 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 = node.right; - while (temp.left != null) { - temp = temp.left; - } - node.right = removeHelper(node.right, temp.val); - node.val = temp.val; - } + /* 递归插入节点(辅助函数) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); } - updateHeight(node); // 更新节点高度 + /* 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); // 返回子树的根节点 @@ -1922,6 +1842,80 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` +=== "Zig" + + ```zig title="avl_tree.zig" + // 插入节点 + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // 递归插入节点(辅助方法) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. 查找插入位置,并插入节点 + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // 重复节点不插入,直接返回 + } + self.updateHeight(node); // 更新节点高度 + // 2. 执行旋转操作,使该子树重新恢复平衡 + node = self.rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +### 2.   删除节点 + +类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶地执行旋转操作,使所有失衡节点恢复平衡。 + +=== "Python" + + ```python title="avl_tree.py" + def remove(self, val: int): + """删除节点""" + self.root = self.__remove_helper(self.root, val) + + def __remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: + """递归删除节点(辅助方法)""" + 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 = node.right + while temp.left is not None: + temp = temp.left + node.right = self.__remove_helper(node.right, temp.val) + node.val = temp.val + # 更新节点高度 + self.__update_height(node) + # 2. 执行旋转操作,使该子树重新恢复平衡 + return self.__rotate(node) + ``` + === "C++" ```cpp title="avl_tree.cpp" @@ -1971,42 +1965,91 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` -=== "Python" +=== "Java" - ```python title="avl_tree.py" - def remove(self, val: int): - """删除节点""" - self.root = self.__remove_helper(self.root, val) + ```java title="avl_tree.java" + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } - def __remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: - """递归删除节点(辅助方法)""" - 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 = node.right - while temp.left is not None: - temp = temp.left - node.right = self.__remove_helper(node.right, temp.val) - node.val = temp.val - # 更新节点高度 - self.__update_height(node) - # 2. 执行旋转操作,使该子树重新恢复平衡 - return self.__rotate(node) + /* 递归删除节点(辅助方法) */ + 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 = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "C#" + + ```csharp title="avl_tree.cs" + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 递归删除节点(辅助方法) */ + 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 = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } ``` === "Go" @@ -2059,6 +2102,54 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + /* 删除节点 */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* 递归删除节点(辅助方法) */ + 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 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node: node) + // 返回子树的根节点 + return node + } + ``` + === "JS" ```javascript title="avl_tree.js" @@ -2143,195 +2234,6 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` -=== "C" - - ```c title="avl_tree.c" - /* 删除节点 */ - // 由于引入了 stdio.h ,此处无法使用 remove 关键词 - void removeNode(aVLTree *tree, int val) { - TreeNode *root = removeHelper(tree->root, val); - } - - /* 递归删除节点(辅助函数) */ - TreeNode *removeHelper(TreeNode *node, int val) { - TreeNode *child, *grandChild; - 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) { - child = node->left; - if (node->right != NULL) { - child = node->right; - } - // 子节点数量 = 0 ,直接删除 node 并返回 - if (child == NULL) { - return NULL; - } else { - // 子节点数量 = 1 ,直接删除 node - node = child; - } - } else { - // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 - TreeNode *temp = node->right; - while (temp->left != NULL) { - temp = temp->left; - } - int tempVal = temp->val; - node->right = removeHelper(node->right, temp->val); - node->val = tempVal; - } - } - // 更新节点高度 - updateHeight(node); - /* 2. 执行旋转操作,使该子树重新恢复平衡 */ - node = rotate(node); - // 返回子树的根节点 - return node; - } - ``` - -=== "C#" - - ```csharp title="avl_tree.cs" - /* 删除节点 */ - void remove(int val) { - root = removeHelper(root, val); - } - - /* 递归删除节点(辅助方法) */ - 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 = node.right; - while (temp.left != null) { - temp = temp.left; - } - 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" - /* 删除节点 */ - func remove(val: Int) { - root = removeHelper(node: root, val: val) - } - - /* 递归删除节点(辅助方法) */ - 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 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 - var temp = node?.right - while temp?.left != nil { - temp = temp?.left - } - 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" - // 删除节点 - fn remove(self: *Self, val: T) void { - self.root = self.removeHelper(self.root, val).?; - } - - // 递归删除节点(辅助方法) - fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { - var node = node_; - if (node == null) return null; - // 1. 查找节点,并删除之 - if (val < node.?.val) { - node.?.left = self.removeHelper(node.?.left, val); - } else if (val > node.?.val) { - node.?.right = self.removeHelper(node.?.right, val); - } else { - if (node.?.left == null or node.?.right == null) { - var child = if (node.?.left != null) node.?.left else node.?.right; - // 子节点数量 = 0 ,直接删除 node 并返回 - if (child == null) { - return null; - // 子节点数量 = 1 ,直接删除 node - } else { - node = child; - } - } else { - // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 - var temp = node.?.right; - while (temp.?.left != null) { - temp = temp.?.left; - } - node.?.right = self.removeHelper(node.?.right, temp.?.val); - node.?.val = temp.?.val; - } - } - self.updateHeight(node); // 更新节点高度 - // 2. 执行旋转操作,使该子树重新恢复平衡 - node = self.rotate(node); - // 返回子树的根节点 - return node; - } - ``` - === "Dart" ```dart title="avl_tree.dart" @@ -2433,6 +2335,104 @@ AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区 } ``` +=== "C" + + ```c title="avl_tree.c" + /* 删除节点 */ + // 由于引入了 stdio.h ,此处无法使用 remove 关键词 + void removeNode(aVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); + } + + /* 递归删除节点(辅助函数) */ + TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + 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) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == NULL) { + return NULL; + } else { + // 子节点数量 = 1 ,直接删除 node + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // 更新节点高度 + updateHeight(node); + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + ``` + +=== "Zig" + + ```zig title="avl_tree.zig" + // 删除节点 + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // 递归删除节点(辅助方法) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. 查找节点,并删除之 + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == null) { + return null; + // 子节点数量 = 1 ,直接删除 node + } else { + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // 更新节点高度 + // 2. 执行旋转操作,使该子树重新恢复平衡 + node = self.rotate(node); + // 返回子树的根节点 + return node; + } + ``` + ### 3.   查找节点 AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。 diff --git a/chapter_tree/binary_search_tree.md b/chapter_tree/binary_search_tree.md index 22307123e..59b9ba525 100755 --- a/chapter_tree/binary_search_tree.md +++ b/chapter_tree/binary_search_tree.md @@ -41,27 +41,24 @@ comments: true 二叉搜索树的查找操作与二分查找算法的工作原理一致,都是每轮排除一半情况。循环次数最多为二叉树的高度,当二叉树平衡时,使用 $O(\log n)$ 时间。 -=== "Java" +=== "Python" - ```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; - } + ```python title="binary_search_tree.py" + def search(self, num: int) -> TreeNode | None: + """查找节点""" + 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 ``` === "C++" @@ -87,24 +84,50 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_search_tree.py" - def search(self, num: int) -> TreeNode | None: - """查找节点""" - 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 + ```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#" + + ```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; + } ``` === "Go" @@ -131,6 +154,32 @@ comments: true } ``` +=== "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 + } + ``` + === "JS" ```javascript title="binary_search_tree.js" @@ -171,103 +220,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_search_tree.c" - /* 查找节点 */ - TreeNode *search(binarySearchTree *bst, int num) { - TreeNode *cur = bst->root; - // 循环查找,越过叶节点后跳出 - while (cur != NULL) { - if (cur->val < num) { - // 目标节点在 cur 的右子树中 - cur = cur->right; - } else if (cur->val > num) { - // 目标节点在 cur 的左子树中 - cur = cur->left; - } else { - // 找到目标节点,跳出循环 - break; - } - } - // 返回目标节点 - return cur; - } - ``` - -=== "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" - // 查找节点 - fn search(self: *Self, num: T) ?*inc.TreeNode(T) { - var cur = self.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; - } - ``` - === "Dart" ```dart title="binary_search_tree.dart" @@ -318,6 +270,54 @@ comments: true } ``` +=== "C" + + ```c title="binary_search_tree.c" + /* 查找节点 */ + TreeNode *search(binarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + if (cur->val < num) { + // 目标节点在 cur 的右子树中 + cur = cur->right; + } else if (cur->val > num) { + // 目标节点在 cur 的左子树中 + cur = cur->left; + } else { + // 找到目标节点,跳出循环 + break; + } + } + // 返回目标节点 + return cur; + } + ``` + +=== "Zig" + + ```zig title="binary_search_tree.zig" + // 查找节点 + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.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; + } + ``` + ### 2.   插入节点 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根节点 < 右子树”的性质,插入操作流程如图 7-18 所示。 @@ -334,37 +334,34 @@ comments: true - 二叉搜索树不允许存在重复节点,否则将违反其定义。因此,若待插入节点在树中已存在,则不执行插入,直接返回。 - 为了实现插入节点,我们需要借助节点 `pre` 保存上一轮循环的节点。这样在遍历至 $\text{None}$ 时,我们可以获取到其父节点,从而完成节点插入操作。 -=== "Java" +=== "Python" - ```java title="binary_search_tree.java" - /* 插入节点 */ - void insert(int num) { - // 若树为空,则初始化根节点 - if (root == null) { - root = new TreeNode(num); - return; - } - TreeNode cur = root, pre = null; - // 循环查找,越过叶节点后跳出 - while (cur != null) { - // 找到重复节点,直接返回 - if (cur.val == num) - return; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur.val < num) - cur = cur.right; - // 插入位置在 cur 的左子树中 - else - cur = cur.left; - } - // 插入节点 - TreeNode node = new TreeNode(num); - if (pre.val < num) - pre.right = node; - else - pre.left = node; - } + ```python title="binary_search_tree.py" + def insert(self, num: int): + """插入节点""" + # 若树为空,则初始化根节点 + if self.__root is None: + self.__root = TreeNode(num) + return + # 循环查找,越过叶节点后跳出 + cur, pre = self.__root, None + while cur is not None: + # 找到重复节点,直接返回 + if cur.val == num: + return + pre = cur + # 插入位置在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 插入位置在 cur 的左子树中 + else: + cur = cur.left + # 插入节点 + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node ``` === "C++" @@ -400,34 +397,73 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_search_tree.py" - def insert(self, num: int): - """插入节点""" - # 若树为空,则初始化根节点 - if self.__root is None: - self.__root = TreeNode(num) - return - # 循环查找,越过叶节点后跳出 - cur, pre = self.__root, None - while cur is not None: - # 找到重复节点,直接返回 - if cur.val == num: - return - pre = cur - # 插入位置在 cur 的右子树中 - if cur.val < num: - cur = cur.right - # 插入位置在 cur 的左子树中 - else: - cur = cur.left - # 插入节点 - node = TreeNode(num) - if pre.val < num: - pre.right = node - else: - pre.left = node + ```java title="binary_search_tree.java" + /* 插入节点 */ + void insert(int num) { + // 若树为空,则初始化根节点 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; + } + // 插入节点 + TreeNode node = new TreeNode(num); + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + ``` + +=== "C#" + + ```csharp title="binary_search_tree.cs" + /* 插入节点 */ + void insert(int num) { + // 若树为空,则初始化根节点 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子树中 + else + cur = cur.left; + } + + // 插入节点 + TreeNode node = new TreeNode(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + } ``` === "Go" @@ -465,6 +501,44 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + /* 插入节点 */ + func insert(num: Int) { + // 若树为空,则初始化根节点 + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // 循环查找,越过叶节点后跳出 + while cur != nil { + // 找到重复节点,直接返回 + if cur!.val == num { + return + } + pre = cur + // 插入位置在 cur 的右子树中 + if cur!.val < num { + cur = cur?.right + } + // 插入位置在 cur 的左子树中 + else { + cur = cur?.left + } + } + // 插入节点 + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + ``` + === "JS" ```javascript title="binary_search_tree.js" @@ -523,152 +597,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_search_tree.c" - /* 插入节点 */ - void insert(binarySearchTree *bst, int num) { - // 若树为空,则初始化根节点 - if (bst->root == NULL) { - bst->root = newTreeNode(num); - return; - } - TreeNode *cur = bst->root, *pre = NULL; - // 循环查找,越过叶节点后跳出 - while (cur != NULL) { - // 找到重复节点,直接返回 - if (cur->val == num) { - return; - } - pre = cur; - if (cur->val < num) { - // 插入位置在 cur 的右子树中 - cur = cur->right; - } else { - // 插入位置在 cur 的左子树中 - cur = cur->left; - } - } - // 插入节点 - TreeNode *node = newTreeNode(num); - if (pre->val < num) { - pre->right = node; - } else { - pre->left = node; - } - } - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 插入节点 */ - void insert(int num) { - // 若树为空,则初始化根节点 - if (root == null) { - root = new TreeNode(num); - return; - } - TreeNode? cur = root, pre = null; - // 循环查找,越过叶节点后跳出 - while (cur != null) { - // 找到重复节点,直接返回 - if (cur.val == num) - return; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur.val < num) - cur = cur.right; - // 插入位置在 cur 的左子树中 - else - cur = cur.left; - } - - // 插入节点 - TreeNode node = new TreeNode(num); - if (pre != null) { - if (pre.val < num) - pre.right = node; - else - pre.left = node; - } - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - /* 插入节点 */ - func insert(num: Int) { - // 若树为空,则初始化根节点 - if root == nil { - root = TreeNode(x: num) - return - } - var cur = root - var pre: TreeNode? - // 循环查找,越过叶节点后跳出 - while cur != nil { - // 找到重复节点,直接返回 - if cur!.val == num { - return - } - pre = cur - // 插入位置在 cur 的右子树中 - if cur!.val < num { - cur = cur?.right - } - // 插入位置在 cur 的左子树中 - else { - cur = cur?.left - } - } - // 插入节点 - let node = TreeNode(x: num) - if pre!.val < num { - pre?.right = node - } else { - pre?.left = node - } - } - ``` - -=== "Zig" - - ```zig title="binary_search_tree.zig" - // 插入节点 - fn insert(self: *Self, num: T) !void { - // 若树为空,则初始化根节点 - if (self.root == null) { - self.root = try self.mem_allocator.create(inc.TreeNode(T)); - return; - } - var cur = self.root; - var pre: ?*inc.TreeNode(T) = null; - // 循环查找,越过叶节点后跳出 - while (cur != null) { - // 找到重复节点,直接返回 - if (cur.?.val == num) return; - pre = cur; - // 插入位置在 cur 的右子树中 - if (cur.?.val < num) { - cur = cur.?.right; - // 插入位置在 cur 的左子树中 - } else { - cur = cur.?.left; - } - } - // 插入节点 - var node = try self.mem_allocator.create(inc.TreeNode(T)); - node.init(num); - if (pre.?.val < num) { - pre.?.right = node; - } else { - pre.?.left = node; - } - } - ``` - === "Dart" ```dart title="binary_search_tree.dart" @@ -741,6 +669,78 @@ comments: true } ``` +=== "C" + + ```c title="binary_search_tree.c" + /* 插入节点 */ + void insert(binarySearchTree *bst, int num) { + // 若树为空,则初始化根节点 + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + // 找到重复节点,直接返回 + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // 插入位置在 cur 的右子树中 + cur = cur->right; + } else { + // 插入位置在 cur 的左子树中 + cur = cur->left; + } + } + // 插入节点 + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } + } + ``` + +=== "Zig" + + ```zig title="binary_search_tree.zig" + // 插入节点 + fn insert(self: *Self, num: T) !void { + // 若树为空,则初始化根节点 + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 循环查找,越过叶节点后跳出 + while (cur != null) { + // 找到重复节点,直接返回 + if (cur.?.val == num) return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur.?.val < num) { + cur = cur.?.right; + // 插入位置在 cur 的左子树中 + } else { + cur = cur.?.left; + } + } + // 插入节点 + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + ``` + 与查找节点相同,插入节点使用 $O(\log n)$ 时间。 ### 3.   删除节点 @@ -786,59 +786,54 @@ comments: true 删除节点操作同样使用 $O(\log n)$ 时间,其中查找待删除节点需要 $O(\log n)$ 时间,获取中序遍历后继节点需要 $O(\log n)$ 时间。 -=== "Java" +=== "Python" - ```java title="binary_search_tree.java" - /* 删除节点 */ - void remove(int num) { - // 若树为空,直接提前返回 - if (root == null) - return; - 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; - // 子节点数量 = 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 (cur != root) { - if (pre.left == cur) - pre.left = child; - else - pre.right = child; - } else { - // 若删除节点为根节点,则重新指定根节点 - root = child; - } - } - // 子节点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个节点 - TreeNode tmp = cur.right; - while (tmp.left != null) { - tmp = tmp.left; - } - // 递归删除节点 tmp - remove(tmp.val); - // 用 tmp 覆盖 cur - cur.val = tmp.val; - } - } + ```python title="binary_search_tree.py" + def remove(self, num: int): + """删除节点""" + # 若树为空,直接提前返回 + if self.__root is None: + return + # 循环查找,越过叶节点后跳出 + cur, pre = self.__root, None + while cur is not None: + # 找到待删除节点,跳出循环 + if cur.val == num: + break + pre = cur + # 待删除节点在 cur 的右子树中 + if cur.val < num: + cur = cur.right + # 待删除节点在 cur 的左子树中 + else: + cur = cur.left + # 若无待删除节点,则直接返回 + if cur is None: + return + + # 子节点数量 = 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 cur != self.__root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # 若删除节点为根节点,则重新指定根节点 + self.__root = child + # 子节点数量 = 2 + else: + # 获取中序遍历中 cur 的下一个节点 + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # 递归删除节点 tmp + self.remove(tmp.val) + # 用 tmp 覆盖 cur + cur.val = tmp.val ``` === "C++" @@ -899,54 +894,114 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_search_tree.py" - def remove(self, num: int): - """删除节点""" - # 若树为空,直接提前返回 - if self.__root is None: - return - # 循环查找,越过叶节点后跳出 - cur, pre = self.__root, None - while cur is not None: - # 找到待删除节点,跳出循环 - if cur.val == num: - break - pre = cur - # 待删除节点在 cur 的右子树中 - if cur.val < num: - cur = cur.right - # 待删除节点在 cur 的左子树中 - else: - cur = cur.left - # 若无待删除节点,则直接返回 - if cur is None: - return + ```java title="binary_search_tree.java" + /* 删除节点 */ + void remove(int num) { + // 若树为空,直接提前返回 + if (root == null) + return; + 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; + // 子节点数量 = 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 (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } + ``` - # 子节点数量 = 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 cur != self.__root: - if pre.left == cur: - pre.left = child - else: - pre.right = child - else: - # 若删除节点为根节点,则重新指定根节点 - self.__root = child - # 子节点数量 = 2 - else: - # 获取中序遍历中 cur 的下一个节点 - tmp: TreeNode = cur.right - while tmp.left is not None: - tmp = tmp.left - # 递归删除节点 tmp - self.remove(tmp.val) - # 用 tmp 覆盖 cur - cur.val = tmp.val +=== "C#" + + ```csharp title="binary_search_tree.cs" + /* 删除节点 */ + void remove(int num) { + // 若树为空,直接提前返回 + if (root == null) + return; + 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; + // 子节点数量 = 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 (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 递归删除节点 tmp + remove(tmp.val); + // 用 tmp 覆盖 cur + cur.val = tmp.val; + } + } ``` === "Go" @@ -1014,6 +1069,68 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + /* 删除节点 */ + func remove(num: Int) { + // 若树为空,直接提前返回 + if root == nil { + return + } + 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 + } + // 子节点数量 = 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 cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child + } + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // 递归删除节点 tmp + remove(num: tmp!.val) + // 用 tmp 覆盖 cur + cur?.val = tmp!.val + } + } + ``` + === "JS" ```javascript title="binary_search_tree.js" @@ -1113,227 +1230,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_search_tree.c" - /* 删除节点 */ - // 由于引入了 stdio.h ,此处无法使用 remove 关键词 - void removeNode(binarySearchTree *bst, int num) { - // 若树为空,直接提前返回 - if (bst->root == NULL) - return; - TreeNode *cur = bst->root, *pre = NULL; - // 循环查找,越过叶节点后跳出 - while (cur != NULL) { - // 找到待删除节点,跳出循环 - if (cur->val == num) - break; - pre = cur; - if (cur->val < num) { - // 待删除节点在 root 的右子树中 - cur = cur->right; - } else { - // 待删除节点在 root 的左子树中 - cur = cur->left; - } - } - // 若无待删除节点,则直接返回 - if (cur == NULL) - return; - // 判断待删除节点是否存在子节点 - if (cur->left == NULL || cur->right == NULL) { - /* 子节点数量 = 0 or 1 */ - // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 - TreeNode *child = cur->left != NULL ? cur->left : cur->right; - // 删除节点 cur - if (pre->left == cur) { - pre->left = child; - } else { - pre->right = child; - } - } else { - /* 子节点数量 = 2 */ - // 获取中序遍历中 cur 的下一个节点 - TreeNode *tmp = cur->right; - while (tmp->left != NULL) { - tmp = tmp->left; - } - int tmpVal = tmp->val; - // 递归删除节点 tmp - removeNode(bst, tmp->val); - // 用 tmp 覆盖 cur - cur->val = tmpVal; - } - } - ``` - -=== "C#" - - ```csharp title="binary_search_tree.cs" - /* 删除节点 */ - void remove(int num) { - // 若树为空,直接提前返回 - if (root == null) - return; - 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; - // 子节点数量 = 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 (cur != root) { - if (pre.left == cur) - pre.left = child; - else - pre.right = child; - } else { - // 若删除节点为根节点,则重新指定根节点 - root = child; - } - } - // 子节点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个节点 - TreeNode? tmp = cur.right; - while (tmp.left != null) { - tmp = tmp.left; - } - // 递归删除节点 tmp - remove(tmp.val); - // 用 tmp 覆盖 cur - cur.val = tmp.val; - } - } - ``` - -=== "Swift" - - ```swift title="binary_search_tree.swift" - /* 删除节点 */ - func remove(num: Int) { - // 若树为空,直接提前返回 - if root == nil { - return - } - 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 - } - // 子节点数量 = 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 cur !== root { - if pre?.left === cur { - pre?.left = child - } else { - pre?.right = child - } - } else { - // 若删除节点为根节点,则重新指定根节点 - root = child - } - } - // 子节点数量 = 2 - else { - // 获取中序遍历中 cur 的下一个节点 - var tmp = cur?.right - while tmp?.left != nil { - tmp = tmp?.left - } - // 递归删除节点 tmp - remove(num: tmp!.val) - // 用 tmp 覆盖 cur - cur?.val = tmp!.val - } - } - ``` - -=== "Zig" - - ```zig title="binary_search_tree.zig" - // 删除节点 - fn remove(self: *Self, num: T) void { - // 若树为空,直接提前返回 - if (self.root == null) return; - var cur = self.root; - var pre: ?*inc.TreeNode(T) = 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; - // 子节点数量 = 0 or 1 - if (cur.?.left == null or cur.?.right == null) { - // 当子节点数量 = 0 / 1 时, child = null / 该子节点 - var child = if (cur.?.left != null) cur.?.left else cur.?.right; - // 删除节点 cur - if (pre.?.left == cur) { - pre.?.left = child; - } else { - pre.?.right = child; - } - // 子节点数量 = 2 - } else { - // 获取中序遍历中 cur 的下一个节点 - var tmp = cur.?.right; - while (tmp.?.left != null) { - tmp = tmp.?.left; - } - var tmp_val = tmp.?.val; - // 递归删除节点 tmp - self.remove(tmp.?.val); - // 用 tmp 覆盖 cur - cur.?.val = tmp_val; - } - } - ``` - === "Dart" ```dart title="binary_search_tree.dart" @@ -1456,6 +1352,110 @@ comments: true } ``` +=== "C" + + ```c title="binary_search_tree.c" + /* 删除节点 */ + // 由于引入了 stdio.h ,此处无法使用 remove 关键词 + void removeNode(binarySearchTree *bst, int num) { + // 若树为空,直接提前返回 + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // 循环查找,越过叶节点后跳出 + while (cur != NULL) { + // 找到待删除节点,跳出循环 + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // 待删除节点在 root 的右子树中 + cur = cur->right; + } else { + // 待删除节点在 root 的左子树中 + cur = cur->left; + } + } + // 若无待删除节点,则直接返回 + if (cur == NULL) + return; + // 判断待删除节点是否存在子节点 + if (cur->left == NULL || cur->right == NULL) { + /* 子节点数量 = 0 or 1 */ + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // 删除节点 cur + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + } else { + /* 子节点数量 = 2 */ + // 获取中序遍历中 cur 的下一个节点 + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 递归删除节点 tmp + removeNode(bst, tmp->val); + // 用 tmp 覆盖 cur + cur->val = tmpVal; + } + } + ``` + +=== "Zig" + + ```zig title="binary_search_tree.zig" + // 删除节点 + fn remove(self: *Self, num: T) void { + // 若树为空,直接提前返回 + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = 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; + // 子节点数量 = 0 or 1 + if (cur.?.left == null or cur.?.right == null) { + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // 删除节点 cur + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // 子节点数量 = 2 + } else { + // 获取中序遍历中 cur 的下一个节点 + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // 递归删除节点 tmp + self.remove(tmp.?.val); + // 用 tmp 覆盖 cur + cur.?.val = tmp_val; + } + } + ``` + ### 4.   中序遍历有序 如图 7-22 所示,二叉树的中序遍历遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历顺序,而二叉搜索树满足“左子节点 $<$ 根节点 $<$ 右子节点”的大小关系。 diff --git a/chapter_tree/binary_tree.md b/chapter_tree/binary_tree.md index e0bb2975e..2eff4e1e8 100644 --- a/chapter_tree/binary_tree.md +++ b/chapter_tree/binary_tree.md @@ -6,16 +6,15 @@ comments: true 「二叉树 binary tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含:值、左子节点引用、右子节点引用。 -=== "Java" +=== "Python" - ```java title="" - /* 二叉树节点类 */ - class TreeNode { - int val; // 节点值 - TreeNode left; // 左子节点引用 - TreeNode right; // 右子节点引用 - TreeNode(int x) { val = x; } - } + ```python title="" + class TreeNode: + """二叉树节点类""" + def __init__(self, val: int): + self.val: int = val # 节点值 + self.left: Optional[TreeNode] = None # 左子节点引用 + self.right: Optional[TreeNode] = None # 右子节点引用 ``` === "C++" @@ -30,15 +29,28 @@ comments: true }; ``` -=== "Python" +=== "Java" - ```python title="" - class TreeNode: - """二叉树节点类""" - def __init__(self, val: int): - self.val: int = val # 节点值 - self.left: Optional[TreeNode] = None # 左子节点引用 - self.right: Optional[TreeNode] = None # 右子节点引用 + ```java title="" + /* 二叉树节点类 */ + class TreeNode { + int val; // 节点值 + TreeNode left; // 左子节点引用 + TreeNode right; // 右子节点引用 + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* 二叉树节点类 */ + class TreeNode { + int val; // 节点值 + TreeNode? left; // 左子节点引用 + TreeNode? right; // 右子节点引用 + TreeNode(int x) { val = x; } + } ``` === "Go" @@ -60,6 +72,21 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 二叉树节点类 */ + class TreeNode { + var val: Int // 节点值 + var left: TreeNode? // 左子节点引用 + var right: TreeNode? // 右子节点引用 + + init(x: Int) { + val = x + } + } + ``` + === "JS" ```javascript title="" @@ -88,6 +115,24 @@ comments: true } ``` +=== "Dart" + + ```dart title="" + /* 二叉树节点类 */ + class TreeNode { + int val; // 节点值 + TreeNode? left; // 左子节点引用 + TreeNode? right; // 右子节点引用 + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + + ``` + === "C" ```c title="" @@ -114,57 +159,12 @@ comments: true } ``` -=== "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="" ``` -=== "Dart" - - ```dart title="" - /* 二叉树节点类 */ - class TreeNode { - int val; // 节点值 - TreeNode? left; // 左子节点引用 - TreeNode? right; // 右子节点引用 - TreeNode(this.val, [this.left, this.right]); - } - ``` - -=== "Rust" - - ```rust title="" - - ``` - 每个节点都有两个引用(指针),分别指向「左子节点 left-child node」和「右子节点 right-child node」,该节点被称为这两个子节点的「父节点 parent node」。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的「左子树 left subtree」,同理可得「右子树 right subtree」。 **在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如图 7-1 所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。 @@ -200,20 +200,21 @@ comments: true 与链表类似,首先初始化节点,然后构建引用(指针)。 -=== "Java" +=== "Python" - ```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; + ```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 ``` === "C++" @@ -233,21 +234,37 @@ comments: true n2->right = n5; ``` -=== "Python" +=== "Java" - ```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 + ```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#" + + ```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; ``` === "Go" @@ -267,6 +284,22 @@ comments: true 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 + ``` + === "JS" ```javascript title="binary_tree.js" @@ -301,62 +334,6 @@ comments: true n2.right = n5; ``` -=== "C" - - ```c title="binary_tree.c" - /* 初始化二叉树 */ - // 初始化节点 - TreeNode *n1 = newTreeNode(1); - TreeNode *n2 = newTreeNode(2); - TreeNode *n3 = newTreeNode(3); - TreeNode *n4 = newTreeNode(4); - TreeNode *n5 = newTreeNode(5); - // 构建引用指向(即指针) - n1->left = n2; - n1->right = n3; - n2->left = n4; - n2->right = n5; - ``` - -=== "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" - - ``` - === "Dart" ```dart title="binary_tree.dart" @@ -380,6 +357,29 @@ comments: true ``` +=== "C" + + ```c title="binary_tree.c" + /* 初始化二叉树 */ + // 初始化节点 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 构建引用指向(即指针) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + ### 2.   插入与删除节点 与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。图 7-3 给出了一个示例。 @@ -388,15 +388,16 @@ comments: true

图 7-3   在二叉树中插入与删除节点

-=== "Java" +=== "Python" - ```java title="binary_tree.java" - TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入节点 P - n1.left = P; - P.left = n2; - // 删除节点 P - n1.left = n2; + ```python title="binary_tree.py" + # 插入与删除节点 + p = TreeNode(0) + # 在 n1 -> n2 中间插入节点 P + n1.left = p + p.left = n2 + # 删除节点 P + n1.left = n2 ``` === "C++" @@ -411,16 +412,27 @@ comments: true n1->left = n2; ``` -=== "Python" +=== "Java" - ```python title="binary_tree.py" - # 插入与删除节点 - p = TreeNode(0) - # 在 n1 -> n2 中间插入节点 P - n1.left = p - p.left = n2 - # 删除节点 P - n1.left = n2 + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1.left = P; + P.left = n2; + // 删除节点 P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 插入与删除节点 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1.left = P; + P.left = n2; + // 删除节点 P + n1.left = n2; ``` === "Go" @@ -435,6 +447,17 @@ comments: true 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 + ``` + === "JS" ```javascript title="binary_tree.js" @@ -459,47 +482,6 @@ comments: true n1.left = n2; ``` -=== "C" - - ```c title="binary_tree.c" - /* 插入与删除节点 */ - TreeNode *P = newTreeNode(0); - // 在 n1 -> n2 中间插入节点 P - n1->left = P; - P->left = n2; - // 删除节点 P - n1->left = n2; - ``` - -=== "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" - - ``` - === "Dart" ```dart title="binary_tree.dart" @@ -518,6 +500,24 @@ comments: true ``` +=== "C" + + ```c title="binary_tree.c" + /* 插入与删除节点 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1->left = P; + P->left = n2; + // 删除节点 P + n1->left = n2; + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + !!! note 需要注意的是,插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除操作通常是由一套操作配合完成的,以实现有实际意义的操作。 diff --git a/chapter_tree/binary_tree_traversal.md b/chapter_tree/binary_tree_traversal.md index ad72bcb77..aa93bffc1 100755 --- a/chapter_tree/binary_tree_traversal.md +++ b/chapter_tree/binary_tree_traversal.md @@ -22,26 +22,24 @@ comments: true 广度优先遍历通常借助“队列”来实现。队列遵循“先进先出”的规则,而广度优先遍历则遵循“逐层推进”的规则,两者背后的思想是一致的。 -=== "Java" +=== "Python" - ```java title="binary_tree_bfs.java" - /* 层序遍历 */ - List levelOrder(TreeNode root) { - // 初始化队列,加入根节点 - Queue queue = new LinkedList<>(); - queue.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; - } + ```python title="binary_tree_bfs.py" + def level_order(root: TreeNode | None) -> list[int]: + """层序遍历""" + # 初始化队列,加入根节点 + queue: deque[TreeNode] = deque() + queue.append(root) + # 初始化一个列表,用于保存遍历序列 + res = [] + while queue: + node: TreeNode = 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 ``` === "C++" @@ -67,24 +65,48 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_tree_bfs.py" - def level_order(root: TreeNode | None) -> list[int]: - """层序遍历""" - # 初始化队列,加入根节点 - queue: deque[TreeNode] = deque() - queue.append(root) - # 初始化一个列表,用于保存遍历序列 - res = [] - while queue: - node: TreeNode = 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 + ```java title="binary_tree_bfs.java" + /* 层序遍历 */ + List levelOrder(TreeNode root) { + // 初始化队列,加入根节点 + Queue queue = new LinkedList<>(); + queue.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#" + + ```csharp title="binary_tree_bfs.cs" + /* 层序遍历 */ + List levelOrder(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; + } ``` === "Go" @@ -115,6 +137,29 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_tree_bfs.swift" + /* 层序遍历 */ + func levelOrder(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 + } + ``` + === "JS" ```javascript title="binary_tree_bfs.js" @@ -157,129 +202,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_tree_bfs.c" - /* 层序遍历 */ - int *levelOrder(TreeNode *root, int *size) { - /* 辅助队列 */ - int front, rear; - int index, *arr; - TreeNode *node; - TreeNode **queue; - - /* 辅助队列 */ - queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_NODE_SIZE); - // 队列指针 - front = 0, rear = 0; - // 加入根节点 - queue[rear++] = root; - // 初始化一个列表,用于保存遍历序列 - /* 辅助数组 */ - arr = (int *)malloc(sizeof(int) * MAX_NODE_SIZE); - // 数组指针 - index = 0; - while (front < rear) { - // 队列出队 - node = queue[front++]; - // 保存节点值 - arr[index++] = node->val; - if (node->left != NULL) { - // 左子节点入队 - queue[rear++] = node->left; - } - if (node->right != NULL) { - // 右子节点入队 - queue[rear++] = node->right; - } - } - // 更新数组长度的值 - *size = index; - arr = realloc(arr, sizeof(int) * (*size)); - - // 释放辅助数组空间 - free(queue); - return arr; - } - ``` - -=== "C#" - - ```csharp title="binary_tree_bfs.cs" - /* 层序遍历 */ - List levelOrder(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 levelOrder(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" - // 层序遍历 - fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { - // 初始化队列,加入根节点 - const L = std.TailQueue(*inc.TreeNode(T)); - var queue = L{}; - var root_node = try mem_allocator.create(L.Node); - root_node.data = root; - queue.append(root_node); - // 初始化一个列表,用于保存遍历序列 - var list = std.ArrayList(T).init(std.heap.page_allocator); - while (queue.len > 0) { - var queue_node = queue.popFirst().?; // 队列出队 - var node = queue_node.data; - try list.append(node.val); // 保存节点值 - if (node.left != null) { - var tmp_node = try mem_allocator.create(L.Node); - tmp_node.data = node.left.?; - queue.append(tmp_node); // 左子节点入队 - } - if (node.right != null) { - var tmp_node = try mem_allocator.create(L.Node); - tmp_node.data = node.right.?; - queue.append(tmp_node); // 右子节点入队 - } - } - return list; - } - ``` - === "Dart" ```dart title="binary_tree_bfs.dart" @@ -324,6 +246,84 @@ comments: true } ``` +=== "C" + + ```c title="binary_tree_bfs.c" + /* 层序遍历 */ + int *levelOrder(TreeNode *root, int *size) { + /* 辅助队列 */ + int front, rear; + int index, *arr; + TreeNode *node; + TreeNode **queue; + + /* 辅助队列 */ + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_NODE_SIZE); + // 队列指针 + front = 0, rear = 0; + // 加入根节点 + queue[rear++] = root; + // 初始化一个列表,用于保存遍历序列 + /* 辅助数组 */ + arr = (int *)malloc(sizeof(int) * MAX_NODE_SIZE); + // 数组指针 + index = 0; + while (front < rear) { + // 队列出队 + node = queue[front++]; + // 保存节点值 + arr[index++] = node->val; + if (node->left != NULL) { + // 左子节点入队 + queue[rear++] = node->left; + } + if (node->right != NULL) { + // 右子节点入队 + queue[rear++] = node->right; + } + } + // 更新数组长度的值 + *size = index; + arr = realloc(arr, sizeof(int) * (*size)); + + // 释放辅助数组空间 + free(queue); + return arr; + } + ``` + +=== "Zig" + + ```zig title="binary_tree_bfs.zig" + // 层序遍历 + fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // 初始化队列,加入根节点 + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // 初始化一个列表,用于保存遍历序列 + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // 队列出队 + var node = queue_node.data; + try list.append(node.val); // 保存节点值 + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // 左子节点入队 + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // 右子节点入队 + } + } + return list; + } + ``` + ### 2.   复杂度分析 - **时间复杂度 $O(n)$** :所有节点被访问一次,使用 $O(n)$ 时间,其中 $n$ 为节点数量。 @@ -343,38 +343,35 @@ comments: true 深度优先搜索通常基于递归实现: -=== "Java" +=== "Python" - ```java title="binary_tree_dfs.java" - /* 前序遍历 */ - void preOrder(TreeNode root) { - if (root == null) - return; - // 访问优先级:根节点 -> 左子树 -> 右子树 - list.add(root.val); - preOrder(root.left); - preOrder(root.right); - } + ```python title="binary_tree_dfs.py" + def pre_order(root: TreeNode | None): + """前序遍历""" + if root is None: + return + # 访问优先级:根节点 -> 左子树 -> 右子树 + res.append(root.val) + pre_order(root=root.left) + pre_order(root=root.right) - /* 中序遍历 */ - void inOrder(TreeNode root) { - if (root == null) - return; - // 访问优先级:左子树 -> 根节点 -> 右子树 - inOrder(root.left); - list.add(root.val); - inOrder(root.right); - } + def in_order(root: TreeNode | None): + """中序遍历""" + if root is None: + return + # 访问优先级:左子树 -> 根节点 -> 右子树 + in_order(root=root.left) + res.append(root.val) + in_order(root=root.right) - /* 后序遍历 */ - void postOrder(TreeNode root) { - if (root == null) - return; - // 访问优先级:左子树 -> 右子树 -> 根节点 - postOrder(root.left); - postOrder(root.right); - list.add(root.val); - } + def post_order(root: TreeNode | None): + """后序遍历""" + if root is None: + return + # 访问优先级:左子树 -> 右子树 -> 根节点 + post_order(root=root.left) + post_order(root=root.right) + res.append(root.val) ``` === "C++" @@ -411,35 +408,69 @@ comments: true } ``` -=== "Python" +=== "Java" - ```python title="binary_tree_dfs.py" - def pre_order(root: TreeNode | None): - """前序遍历""" - if root is None: - return - # 访问优先级:根节点 -> 左子树 -> 右子树 - res.append(root.val) - pre_order(root=root.left) - pre_order(root=root.right) + ```java title="binary_tree_dfs.java" + /* 前序遍历 */ + void preOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(root.val); + preOrder(root.left); + preOrder(root.right); + } - def in_order(root: TreeNode | None): - """中序遍历""" - if root is None: - return - # 访问优先级:左子树 -> 根节点 -> 右子树 - in_order(root=root.left) - res.append(root.val) - in_order(root=root.right) + /* 中序遍历 */ + void inOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } - def post_order(root: TreeNode | None): - """后序遍历""" - if root is None: - return - # 访问优先级:左子树 -> 右子树 -> 根节点 - post_order(root=root.left) - post_order(root=root.right) - res.append(root.val) + /* 后序遍历 */ + void postOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + ``` + +=== "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); + } ``` === "Go" @@ -479,6 +510,43 @@ comments: true } ``` +=== "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) + } + ``` + === "JS" ```javascript title="binary_tree_dfs.js" @@ -547,139 +615,6 @@ comments: true } ``` -=== "C" - - ```c title="binary_tree_dfs.c" - /* 前序遍历 */ - void preOrder(TreeNode *root, int *size) { - if (root == NULL) - return; - // 访问优先级:根节点 -> 左子树 -> 右子树 - arr[(*size)++] = root->val; - preOrder(root->left, size); - preOrder(root->right, size); - } - - /* 中序遍历 */ - void inOrder(TreeNode *root, int *size) { - if (root == NULL) - return; - // 访问优先级:左子树 -> 根节点 -> 右子树 - inOrder(root->left, size); - arr[(*size)++] = root->val; - inOrder(root->right, size); - } - - /* 后序遍历 */ - void postOrder(TreeNode *root, int *size) { - if (root == NULL) - return; - // 访问优先级:左子树 -> 右子树 -> 根节点 - postOrder(root->left, size); - postOrder(root->right, size); - arr[(*size)++] = root->val; - } - ``` - -=== "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" - // 前序遍历 - fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { - if (root == null) return; - // 访问优先级:根节点 -> 左子树 -> 右子树 - try list.append(root.?.val); - try preOrder(T, root.?.left); - try preOrder(T, root.?.right); - } - - // 中序遍历 - fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { - if (root == null) return; - // 访问优先级:左子树 -> 根节点 -> 右子树 - try inOrder(T, root.?.left); - try list.append(root.?.val); - try inOrder(T, root.?.right); - } - - // 后序遍历 - fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { - if (root == null) return; - // 访问优先级:左子树 -> 右子树 -> 根节点 - try postOrder(T, root.?.left); - try postOrder(T, root.?.right); - try list.append(root.?.val); - } - ``` - === "Dart" ```dart title="binary_tree_dfs.dart" @@ -754,6 +689,71 @@ comments: true } ``` +=== "C" + + ```c title="binary_tree_dfs.c" + /* 前序遍历 */ + void preOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + arr[(*size)++] = root->val; + preOrder(root->left, size); + preOrder(root->right, size); + } + + /* 中序遍历 */ + void inOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root->left, size); + arr[(*size)++] = root->val; + inOrder(root->right, size); + } + + /* 后序遍历 */ + void postOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root->left, size); + postOrder(root->right, size); + arr[(*size)++] = root->val; + } + ``` + +=== "Zig" + + ```zig title="binary_tree_dfs.zig" + // 前序遍历 + fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); + } + + // 中序遍历 + fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); + } + + // 后序遍历 + fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); + } + ``` + !!! note 深度优先搜索也可以基于迭代实现,有兴趣的同学可以自行研究。