From 5e23c75870df5928e72fbd35e173a39910d304ba Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Sun, 8 Jan 2023 23:44:13 +0800 Subject: [PATCH 1/7] feat: add Swift codes for list article --- codes/swift/Package.swift | 4 + .../chapter_array_and_linkedlist/list.swift | 64 ++++++++ .../my_list.swift | 147 ++++++++++++++++++ docs/chapter_array_and_linkedlist/list.md | 143 ++++++++++++++++- 4 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 codes/swift/chapter_array_and_linkedlist/list.swift create mode 100644 codes/swift/chapter_array_and_linkedlist/my_list.swift diff --git a/codes/swift/Package.swift b/codes/swift/Package.swift index 4cf3601c7..3a5a18ed3 100644 --- a/codes/swift/Package.swift +++ b/codes/swift/Package.swift @@ -11,6 +11,8 @@ let package = Package( .executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]), .executable(name: "array", targets: ["array"]), .executable(name: "linked_list", targets: ["linked_list"]), + .executable(name: "list", targets: ["list"]), + .executable(name: "my_list", targets: ["my_list"]), ], targets: [ .target(name: "utils", path: "utils"), @@ -20,5 +22,7 @@ let package = Package( .executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]), .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), + .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), + .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), ] ) diff --git a/codes/swift/chapter_array_and_linkedlist/list.swift b/codes/swift/chapter_array_and_linkedlist/list.swift new file mode 100644 index 000000000..27801d13f --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/list.swift @@ -0,0 +1,64 @@ +/** + * File: list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum List { + /* Driver Code */ + static func main() { + /* 初始化列表 */ + var list = [1, 3, 2, 5, 4] + print("列表 list = \(list)") + + /* 访问元素 */ + let num = list[1] + print("访问索引 1 处的元素,得到 num = \(num)") + + /* 更新元素 */ + list[1] = 0 + print("将索引 1 处的元素更新为 0 ,得到 list = \(list)") + + /* 清空列表 */ + list.removeAll() + print("清空列表后 list = \(list)") + + /* 尾部添加元素 */ + list.append(1) + list.append(3) + list.append(2) + list.append(5) + list.append(4) + print("添加元素后 list = \(list)") + + /* 中间插入元素 */ + list.insert(6, at: 3) + print("在索引 3 处插入数字 6 ,得到 list = \(list)") + + /* 删除元素 */ + list.remove(at: 3) + print("删除索引 3 处的元素,得到 list = \(list)") + + /* 通过索引遍历列表 */ + var count = 0 + for _ in list.indices { + count += 1 + } + + /* 直接遍历列表元素 */ + count = 0 + for _ in list { + count += 1 + } + + /* 拼接两个列表 */ + let list1 = [6, 8, 7, 10, 9] + list.append(contentsOf: list1) + print("将列表 list1 拼接到 list 之后,得到 list = \(list)") + + /* 排序列表 */ + list.sort() + print("排序列表后 list = \(list)") + } +} diff --git a/codes/swift/chapter_array_and_linkedlist/my_list.swift b/codes/swift/chapter_array_and_linkedlist/my_list.swift new file mode 100644 index 000000000..423b02027 --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -0,0 +1,147 @@ +/** + * File: my_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 列表类简易实现 */ +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 >= _size { + fatalError("索引越界") + } + return nums[index] + } + + /* 更新元素 */ + func set(index: Int, num: Int) { + if 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 >= _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 >= _size { + fatalError("索引越界") + } + let num = nums[index] + // 将索引 index 之后的元素都向前移动一位 + for j in index ..< (_size - 1) { + nums[j] = nums[j + 1] + } + // 更新元素数量 + _size -= 1 + // 返回被删除元素 + return num + } + + /* 列表扩容 */ + func extendCapacity() { + // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 + nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) + // 更新列表容量 + _capacity = nums.count + } + + /* 将列表转换为数组 */ + func toArray() -> [Int] { + var nums = Array(repeating: 0, count: _size) + for i in 0 ..< _size { + nums[i] = get(index: i) + } + return nums + } +} + +@main +enum _MyList { + /* Driver Code */ + static func main() { + /* 初始化列表 */ + let list = MyList() + /* 尾部添加元素 */ + list.add(num: 1) + list.add(num: 3) + list.add(num: 2) + list.add(num: 5) + list.add(num: 4) + print("列表 list = \(list.toArray()) ,容量 = \(list.capacity()) ,长度 = \(list.size())") + + /* 中间插入元素 */ + list.insert(index: 3, num: 6) + print("在索引 3 处插入数字 6 ,得到 list = \(list.toArray())") + + /* 删除元素 */ + list.remove(index: 3) + print("删除索引 3 处的元素,得到 list = \(list.toArray())") + + /* 访问元素 */ + let num = list.get(index: 1) + print("访问索引 1 处的元素,得到 num = \(num)") + + /* 更新元素 */ + list.set(index: 1, num: 0) + print("将索引 1 处的元素更新为 0 ,得到 list = \(list.toArray())") + + /* 测试扩容机制 */ + for i in 0 ..< 10 { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + list.add(num: i) + } + print("扩容后的列表 list = \(list.toArray()) ,容量 = \(list.capacity()) ,长度 = \(list.size())") + } +} diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index ca4774a04..ce8bb8870 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -94,7 +94,11 @@ comments: true === "Swift" ```swift title="list.swift" - + /* 初始化列表 */ + // 无初始值 + let list1: [Int] = [] + // 有初始值 + var list = [1, 3, 2, 5, 4] ``` **访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 @@ -178,7 +182,11 @@ comments: true === "Swift" ```swift title="list.swift" + /* 访问元素 */ + let num = list[1] // 访问索引 1 处的元素 + /* 更新元素 */ + list[1] = 0 // 将索引 1 处的元素更新为 0 ``` **在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 @@ -332,7 +340,21 @@ comments: true === "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 处的元素 ``` **遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 @@ -458,7 +480,17 @@ comments: true === "Swift" ```swift title="list.swift" + /* 通过索引遍历列表 */ + var count = 0 + for _ in list.indices { + count += 1 + } + /* 直接遍历列表元素 */ + count = 0 + for _ in list { + count += 1 + } ``` **拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 @@ -529,7 +561,9 @@ comments: true === "Swift" ```swift title="list.swift" - + /* 拼接两个列表 */ + let list1 = [6, 8, 7, 10, 9] + list.append(contentsOf: list1) // 将列表 list1 拼接到 list 之后 ``` **排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 @@ -592,7 +626,8 @@ comments: true === "Swift" ```swift title="list.swift" - + /* 排序列表 */ + list.sort() // 排序后,列表元素从小到大排列 ``` ## 列表简易实现 * @@ -1263,6 +1298,106 @@ 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 >= _size { + fatalError("索引越界") + } + return nums[index] + } + + /* 更新元素 */ + func set(index: Int, num: Int) { + if 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 >= _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 >= _size { + fatalError("索引越界") + } + let num = nums[index] + // 将索引 index 之后的元素都向前移动一位 + for j in index ..< (_size - 1) { + nums[j] = nums[j + 1] + } + // 更新元素数量 + _size -= 1 + // 返回被删除元素 + return num + } + + /* 列表扩容 */ + func extendCapacity() { + // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 + nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) + // 更新列表容量 + _capacity = nums.count + } + + /* 将列表转换为数组 */ + func toArray() -> [Int] { + var nums = Array(repeating: 0, count: _size) + for i in 0 ..< _size { + nums[i] = get(index: i) + } + return nums + } + } + ``` From e6021ff59e66fea7d5189135ccdf214697d647ab Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Sun, 8 Jan 2023 23:48:35 +0800 Subject: [PATCH 2/7] fix: predicate --- .../chapter_computational_complexity/time_complexity.swift | 4 ++-- docs/chapter_computational_complexity/time_complexity.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codes/swift/chapter_computational_complexity/time_complexity.swift b/codes/swift/chapter_computational_complexity/time_complexity.swift index 41695cfd6..1b1027ef2 100644 --- a/codes/swift/chapter_computational_complexity/time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -49,7 +49,7 @@ func quadratic(n: Int) -> Int { func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 ? $0 - 1 : nil }) { + for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { // 内循环:冒泡操作 for j in 0 ..< i { if nums[j] > nums[j + 1] { @@ -149,7 +149,7 @@ enum TimeComplexity { count = quadratic(n: n) print("平方阶的计算操作数量 = \(count)") - var nums = Array(sequence(first: n, next: { $0 > 0 ? $0 - 1 : nil })) // [n,n-1,...,2,1] + var nums = Array(sequence(first: n, next: { $0 > 0 + 1 ? $0 - 1 : nil })) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) print("平方阶(冒泡排序)的计算操作数量 = \(count)") diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 25d9fce88..09aee7b79 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -1481,7 +1481,7 @@ $$ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 ? $0 - 1 : nil }) { + for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { // 内循环:冒泡操作 for j in 0 ..< i { if nums[j] > nums[j + 1] { From 0b965a5e32e9c4adeee9b869c35b355549c9d4bb Mon Sep 17 00:00:00 2001 From: iStig Date: Mon, 9 Jan 2023 11:15:26 +0800 Subject: [PATCH 3/7] Update stack.md --- docs/chapter_stack_and_queue/stack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 8bb343c4f..d5a3f9164 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -906,5 +906,5 @@ comments: true ## 栈典型应用 -- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就讲上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 +- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 - **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 From aaa2ff29f93f6ea1cae4021241344276f006e854 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Mon, 9 Jan 2023 22:39:30 +0800 Subject: [PATCH 4/7] Fix all the ** (bolded symbols). --- docs/chapter_array_and_linkedlist/array.md | 6 ++-- docs/chapter_array_and_linkedlist/list.md | 6 ++-- .../performance_evaluation.md | 6 ++-- .../space_complexity.md | 2 +- .../classification_of_data_structure.md | 10 +++--- .../chapter_data_structure/data_and_memory.md | 2 +- docs/chapter_hashing/hash_collision.md | 14 ++++---- docs/chapter_hashing/hash_map.md | 8 ++--- docs/chapter_preface/about_the_book.md | 6 ++-- docs/chapter_preface/suggestions.md | 2 +- docs/chapter_searching/binary_search.md | 6 ++-- docs/chapter_searching/hashing_search.md | 4 +-- docs/chapter_searching/linear_search.md | 4 +-- docs/chapter_sorting/bubble_sort.md | 10 +++--- docs/chapter_sorting/insertion_sort.md | 12 +++---- docs/chapter_sorting/intro_to_sort.md | 4 +-- docs/chapter_sorting/merge_sort.md | 22 ++++++------- docs/chapter_sorting/quick_sort.md | 32 +++++++++---------- docs/chapter_tree/avl_tree.md | 4 +-- docs/chapter_tree/binary_search_tree.md | 26 +++++++-------- 20 files changed, 93 insertions(+), 93 deletions(-) diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 2cf096137..2251362d0 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -356,9 +356,9 @@ elementAddr = firtstElementAddr + elementLength * elementIndex **数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: -- **时间复杂度高:** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 -- **丢失元素:** 由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 -- **内存浪费:** 我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。 +- **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 +- **丢失元素**:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 +- **内存浪费**:我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。 ![array_insert_remove_element](array.assets/array_insert_remove_element.png) diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index ca4774a04..a02ede876 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -599,9 +599,9 @@ comments: true 为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: -- **初始容量:** 选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。 -- **数量记录:** 需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。 -- **扩容机制:** 插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。 +- **初始容量**:选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。 +- **数量记录**:需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。 +- **扩容机制**:插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。 本示例是为了帮助读者对如何实现列表产生直观的认识。实际编程语言中,列表的实现远比以下代码复杂且标准,感兴趣的读者可以查阅源码学习。 diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index f75b4d8d4..9d6fd6628 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -13,8 +13,8 @@ comments: true 换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括: -- **时间效率** ,即算法的运行速度的快慢。 -- **空间效率** ,即算法占用的内存空间大小。 +- **时间效率**,即算法的运行速度的快慢。 +- **空间效率**,即算法占用的内存空间大小。 数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 @@ -32,7 +32,7 @@ comments: true 既然实际测试具有很大的局限性,那么我们是否可以仅通过一些计算,就获知算法的效率水平呢?答案是肯定的,我们将此估算方法称为「复杂度分析 Complexity Analysis」或「渐近复杂度分析 Asymptotic Complexity Analysis」。 -**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势** 。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 +**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势**。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 **复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 6686aac50..e594bec8d 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -4,7 +4,7 @@ comments: true # 空间复杂度 -「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势** 。这个概念与时间复杂度很类似。 +「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。 ## 算法相关空间 diff --git a/docs/chapter_data_structure/classification_of_data_structure.md b/docs/chapter_data_structure/classification_of_data_structure.md index 749542eb9..202af50a2 100644 --- a/docs/chapter_data_structure/classification_of_data_structure.md +++ b/docs/chapter_data_structure/classification_of_data_structure.md @@ -12,8 +12,8 @@ comments: true 我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线形的(例如是网状或树状的),那么就是非线性数据结构。 -- **线性数据结构:** 数组、链表、栈、队列、哈希表; -- **非线性数据结构:** 树、图、堆、哈希表; +- **线性数据结构**:数组、链表、栈、队列、哈希表; +- **非线性数据结构**:树、图、堆、哈希表; ![classification_logic_structure](classification_of_data_structure.assets/classification_logic_structure.png) @@ -25,7 +25,7 @@ comments: true 若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。 -**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 +**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 ![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png) @@ -33,8 +33,8 @@ comments: true **所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 -- **基于数组可实现:** 栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等; -- **基于链表可实现:** 栈、队列、堆、哈希表、树、图等; +- **基于数组可实现**:栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等; +- **基于链表可实现**:栈、队列、堆、哈希表、树、图等; 基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度。 diff --git a/docs/chapter_data_structure/data_and_memory.md b/docs/chapter_data_structure/data_and_memory.md index 59cb74e45..93147fb65 100644 --- a/docs/chapter_data_structure/data_and_memory.md +++ b/docs/chapter_data_structure/data_and_memory.md @@ -42,7 +42,7 @@ comments: true **「基本数据类型」与「数据结构」之间的联系与区别** -我们知道,数据结构是在计算机中 **组织与存储数据的方式** ,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 +我们知道,数据结构是在计算机中 **组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 === "Java" diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index 05dc20881..45a529970 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -24,9 +24,9 @@ comments: true 在原始哈希表中,一个桶地址只能存储一个元素(即键值对)。**考虑将桶地址内的单个元素转变成一个链表,将所有冲突元素都存储在一个链表中**,此时哈希表操作方法为: -- **查询元素:** 先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。 -- **添加元素:** 先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。 -- **删除元素:** 同样先访问链表头部,再遍历链表查找对应元素,删除之即可。 +- **查询元素**:先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。 +- **添加元素**:先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。 +- **删除元素**:同样先访问链表头部,再遍历链表查找对应元素,删除之即可。 (图) @@ -46,9 +46,9 @@ comments: true 「线性探测」使用固定步长的线性查找来解决哈希冲突。 -**插入元素:** 如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。 +**插入元素**:如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。 -**查找元素:** 若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况: +**查找元素**:若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况: 1. 找到对应元素,返回 value 即可; 2. 若遇到空位,则说明查找键值对不在哈希表中; @@ -64,9 +64,9 @@ comments: true 顾名思义,「多次哈希」的思路是基于多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。 -**插入元素:** 若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。 +**插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。 -**查找元素:** 以相同的哈希函数顺序查找,存在两种情况: +**查找元素**:以相同的哈希函数顺序查找,存在两种情况: 1. 找到目标元素,则返回之; 2. 到空位或已尝试所有哈希函数,说明哈希表中无此元素; diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 2d8a66a5d..cbb4ba049 100644 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -16,10 +16,10 @@ comments: true 除了哈希表之外,还可以使用以下数据结构来实现上述查询功能: -1. **无序数组:** 每个元素为 `[学号, 姓名]` ; -2. **有序数组:** 将 `1.` 中的数组按照学号从小到大排序; -3. **链表:** 每个结点的值为 `[学号, 姓名]` ; -4. **二叉搜索树:** 每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树; +1. **无序数组**:每个元素为 `[学号, 姓名]` ; +2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序; +3. **链表**:每个结点的值为 `[学号, 姓名]` ; +4. **二叉搜索树**:每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树; 使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 [二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/#_6))。无论是查找元素、还是增删元素,哈希表的时间复杂度都是 $O(1)$ ,全面胜出! diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index 2fba23502..c3209280c 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -44,13 +44,13 @@ comments: true 首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。 -接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度** 和 **空间复杂度** ,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。 +接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度** 和 **空间复杂度**,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。 ### 数据结构 首先介绍了常用的 **基本数据类型** 、以及它们是如何在内存中存储的。 -接下来,介绍了两种 **数据结构分类方法** ,包括逻辑结构与物理结构。 +接下来,介绍了两种 **数据结构分类方法**,包括逻辑结构与物理结构。 后续展开介绍了 **数组、链表、栈、队列、散列表、树、堆、图** 等数据结构,关心以下内容: @@ -84,7 +84,7 @@ comments: true - 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。 - 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。 -- 重点内容、总起句、总结句会被 **加粗** ,此类文字值得特别关注。 +- 重点内容、总起句、总结句会被 **加粗**,此类文字值得特别关注。 - 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。 - 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 diff --git a/docs/chapter_preface/suggestions.md b/docs/chapter_preface/suggestions.md index d4dbd24bc..b2e18905f 100644 --- a/docs/chapter_preface/suggestions.md +++ b/docs/chapter_preface/suggestions.md @@ -32,7 +32,7 @@ git clone https://github.com/krahets/hello-algo.git ### 运行源代码 -本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件** 。 +本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件**。 ![code_md_to_repo](suggestions.assets/code_md_to_repo.png) diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 466e76658..2bd2d4396 100644 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -9,7 +9,7 @@ comments: true 使用二分查找有两个前置条件: - **要求输入数据是有序的**,这样才能通过判断大小关系来排除一半的搜索区间; -- **二分查找仅适用于数组** ,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。 +- **二分查找仅适用于数组**,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。 ## 算法实现 @@ -480,9 +480,9 @@ $$ ## 复杂度分析 -**时间复杂度 $O(\log n)$ :** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 +**时间复杂度 $O(\log n)$** :其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 -**空间复杂度 $O(1)$ :** 指针 `i` , `j` 使用常数大小空间。 +**空间复杂度 $O(1)$** :指针 `i` , `j` 使用常数大小空间。 ## 优点与缺点 diff --git a/docs/chapter_searching/hashing_search.md b/docs/chapter_searching/hashing_search.md index 12746d2ef..076457303 100644 --- a/docs/chapter_searching/hashing_search.md +++ b/docs/chapter_searching/hashing_search.md @@ -193,9 +193,9 @@ comments: true ## 复杂度分析 -**时间复杂度:** $O(1)$ ,哈希表的查找操作使用 $O(1)$ 时间。 +**时间复杂度 $O(1)$** :哈希表的查找操作使用 $O(1)$ 时间。 -**空间复杂度:** $O(n)$ ,其中 $n$ 为数组或链表长度。 +**空间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 ## 优点与缺点 diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md index be98c013d..d9371ab2c 100644 --- a/docs/chapter_searching/linear_search.md +++ b/docs/chapter_searching/linear_search.md @@ -252,9 +252,9 @@ comments: true ## 复杂度分析 -**时间复杂度 $O(n)$ :** 其中 $n$ 为数组或链表长度。 +**时间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 -**空间复杂度 $O(1)$ :** 无需使用额外空间。 +**空间复杂度 $O(1)$** :无需使用额外空间。 ## 优点与缺点 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index 1dbc56475..e3e394b20 100644 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -220,15 +220,15 @@ comments: true ## 算法特性 -**时间复杂度 $O(n^2)$ :** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 +**时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 -**空间复杂度 $O(1)$ :** 指针 $i$ , $j$ 使用常数大小的额外空间。 +**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 -**原地排序:** 指针变量仅使用常数大小额外空间。 +**原地排序**:指针变量仅使用常数大小额外空间。 -**稳定排序:** 不交换相等元素。 +**稳定排序**:不交换相等元素。 -**自适排序:** 引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 +**自适排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 ## 效率优化 diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 679182f42..05ee7293e 100644 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -183,15 +183,15 @@ comments: true ## 算法特性 -**时间复杂度 $O(n^2)$ :** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 +**时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 -**空间复杂度 $O(1)$ :** 指针 $i$ , $j$ 使用常数大小的额外空间。 +**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 -**原地排序:** 指针变量仅使用常数大小额外空间。 +**原地排序**:指针变量仅使用常数大小额外空间。 -**稳定排序:** 不交换相等元素。 +**稳定排序**:不交换相等元素。 -**自适应排序:** 最佳情况下,时间复杂度为 $O(n)$ 。 +**自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。 ## 插入排序 vs 冒泡排序 @@ -199,7 +199,7 @@ comments: true 虽然「插入排序」和「冒泡排序」的时间复杂度皆为 $O(n^2)$ ,但实际运行速度却有很大差别,这是为什么呢? -回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换** ,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值** ,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。 +回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换**,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值**,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。 插入排序运行速度快,并且具有原地、稳定、自适应的优点,因此很受欢迎。实际上,包括 Java 在内的许多编程语言的排序库函数的实现都用到了插入排序。库函数的大致思路: diff --git a/docs/chapter_sorting/intro_to_sort.md b/docs/chapter_sorting/intro_to_sort.md index a3cb071dc..c4d50c535 100644 --- a/docs/chapter_sorting/intro_to_sort.md +++ b/docs/chapter_sorting/intro_to_sort.md @@ -7,7 +7,7 @@ comments: true 「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。 - 待排序的列表的 **元素类型** 可以是整数、浮点数、字符、或字符串; -- 排序算法可以根据需要设定 **判断规则** ,例如数字大小、字符 ASCII 码顺序、自定义规则; +- 排序算法可以根据需要设定 **判断规则**,例如数字大小、字符 ASCII 码顺序、自定义规则; ![sorting_examples](intro_to_sort.assets/sorting_examples.png) @@ -55,7 +55,7 @@ comments: true - 「自适应排序」的时间复杂度受输入数据影响,即最佳 / 最差 / 平均时间复杂度不相等。 - 「非自适应排序」的时间复杂度恒定,与输入数据无关。 -我们希望 **最差 = 平均** ,即不希望排序算法的运行效率在某些输入数据下发生劣化。 +我们希望 **最差 = 平均**,即不希望排序算法的运行效率在某些输入数据下发生劣化。 ### 比较类 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index ae0cfc7c2..5895fcb8e 100644 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -6,8 +6,8 @@ comments: true 「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段: -1. **划分阶段:** 通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; -2. **合并阶段:** 划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; +1. **划分阶段**:通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; +2. **合并阶段**:划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; ![merge_sort_preview](merge_sort.assets/merge_sort_preview.png) @@ -15,14 +15,14 @@ comments: true ## 算法流程 -**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组** ,直至长度为 1 ; +**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ; 1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` ); 2. 递归执行 `1.` 步骤,直至子数组区间长度为 1 时,终止递归划分; **「回溯合并」** 从底至顶地将左子数组和右子数组合并为一个 **有序数组** ; -需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的** 。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组** 。 +需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的**。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组**。 === "Step1" ![merge_sort_step1](merge_sort.assets/merge_sort_step1.png) @@ -56,8 +56,8 @@ comments: true 观察发现,归并排序的递归顺序就是二叉树的「后序遍历」。 -- **后序遍历:** 先递归左子树、再递归右子树、最后处理根结点。 -- **归并排序:** 先递归左子树、再递归右子树、最后处理合并。 +- **后序遍历**:先递归左子树、再递归右子树、最后处理根结点。 +- **归并排序**:先递归左子树、再递归右子树、最后处理合并。 === "Java" @@ -406,11 +406,11 @@ comments: true ## 算法特性 -- **时间复杂度 $O(n \log n)$ :** 划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 -- **空间复杂度 $O(n)$ :** 需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 -- **非原地排序:** 辅助数组需要使用 $O(n)$ 额外空间。 -- **稳定排序:** 在合并时可保证相等元素的相对位置不变。 -- **非自适应排序:** 对于任意输入数据,归并排序的时间复杂度皆相同。 +- **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 +- **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 +- **非原地排序**:辅助数组需要使用 $O(n)$ 额外空间。 +- **稳定排序**:在合并时可保证相等元素的相对位置不变。 +- **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。 ## 链表排序 * diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index 3eeac9c65..a712a1bff 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -6,13 +6,13 @@ comments: true 「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。 -快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 **基准数** ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为: +快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 **基准数**,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为: 1. 以数组最左端元素作为基准数,初始化两个指针 `i` , `j` 指向数组两端; 2. 设置一个循环,每轮中使用 `i` / `j` 分别寻找首个比基准数大 / 小的元素,并交换此两元素; 3. 不断循环步骤 `2.` ,直至 `i` , `j` 相遇时跳出,最终把基准数交换至两个子数组的分界线; -「哨兵划分」执行完毕后,原数组被划分成两个部分,即 **左子数组** 和 **右子数组** ,且满足 **左子数组任意元素 < 基准数 < 右子数组任意元素**。因此,接下来我们只需要排序两个子数组即可。 +「哨兵划分」执行完毕后,原数组被划分成两个部分,即 **左子数组** 和 **右子数组**,且满足 **左子数组任意元素 < 基准数 < 右子数组任意元素**。因此,接下来我们只需要排序两个子数组即可。 === "Step 1" ![pivot_division_step1](quick_sort.assets/pivot_division_step1.png) @@ -235,9 +235,9 @@ comments: true ## 算法流程 -1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组** 。 +1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**; 2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」…… -3. 直至子数组长度为 1 时 **终止递归** ,即可完成对整个数组的排序。 +3. 直至子数组长度为 1 时 **终止递归**,即可完成对整个数组的排序; 观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。 @@ -373,31 +373,31 @@ comments: true ## 算法特性 -**平均时间复杂度 $O(n \log n)$ :** 平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 +**平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 -**最差时间复杂度 $O(n^2)$ :** 最差情况下,哨兵划分操作将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 +**最差时间复杂度 $O(n^2)$** :最差情况下,哨兵划分操作将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 -**空间复杂度 $O(n)$ :** 输入数组完全倒序下,达到最差递归深度 $n$ 。 +**空间复杂度 $O(n)$** :输入数组完全倒序下,达到最差递归深度 $n$ 。 -**原地排序:** 只在递归中使用 $O(\log n)$ 大小的栈帧空间。 +**原地排序**:只在递归中使用 $O(\log n)$ 大小的栈帧空间。 -**非稳定排序:** 哨兵划分操作可能改变相等元素的相对位置。 +**非稳定排序**:哨兵划分操作可能改变相等元素的相对位置。 -**自适应排序:** 最差情况下,时间复杂度劣化至 $O(n^2)$ 。 +**自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。 ## 快排为什么快? -从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高** ,这是因为: +从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为: -- **出现最差情况的概率很低:** 虽然快速排序的最差时间复杂度为 $O(n^2)$ ,不如归并排序,但绝大部分情况下,快速排序可以达到 $O(n \log n)$ 的复杂度。 -- **缓存使用效率高:** 哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 -- **复杂度的常数系数低:** 在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 +- **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,不如归并排序,但绝大部分情况下,快速排序可以达到 $O(n \log n)$ 的复杂度。 +- **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 +- **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 ## 基准数优化 -**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$ 、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 +**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 -为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数** 。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 +为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数**。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 进一步地,我们可以在数组中选取 3 个候选元素(一般为数组的首、尾、中点元素),**并将三个候选元素的中位数作为基准数**,这样基准数“既不大也不小”的概率就大大提升了。当然,如果数组很长的话,我们也可以选取更多候选元素,来进一步提升算法的稳健性。采取该方法后,时间复杂度劣化至 $O(n^2)$ 的概率极低。 diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 9944f29e8..6f6272645 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -106,7 +106,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ``` -「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。 +「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新结点的高度。 === "Java" @@ -310,7 +310,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ### Case 1 - 右旋 -如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3** 。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 +如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3**。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 === "Step 1" ![right_rotate_step1](avl_tree.assets/right_rotate_step1.png) diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index a4c5a5d2a..a0322154e 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -202,8 +202,8 @@ comments: true 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步: -1. **查找插入位置:** 与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环; -2. **在该位置插入结点:** 初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置 ; +1. **查找插入位置**:与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环; +2. **在该位置插入结点**:初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置 ; 二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。 @@ -442,15 +442,15 @@ comments: true 与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况: -**待删除结点的子结点数量 $= 0$ **。表明待删除结点是叶结点,直接删除即可。 +**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。 ![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png) -**待删除结点的子结点数量 $= 1$ **。将待删除结点替换为其子结点。 +**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。 ![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png) -**待删除结点的子结点数量 $= 2$ **。删除操作分为三步: +**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步: 1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ; 2. 在树中递归删除结点 `nex` ; @@ -830,17 +830,17 @@ comments: true 假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: -- **查找元素:** 由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间; -- **插入元素:** 只需将元素添加至数组尾部即可,使用 $O(1)$ 时间; -- **删除元素:** 先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素:** 需要遍历数组来确定,使用 $O(n)$ 时间; +- **查找元素**:由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间; +- **插入元素**:只需将元素添加至数组尾部即可,使用 $O(1)$ 时间; +- **删除元素**:先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; +- **获取最小 / 最大元素**:需要遍历数组来确定,使用 $O(n)$ 时间; 为了得到先验信息,我们也可以预先将数组元素进行排序,得到一个「排序数组」,此时操作效率为: -- **查找元素:** 由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间; -- **插入元素:** 先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间; -- **删除元素:** 先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素:** 数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间; +- **查找元素**:由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间; +- **插入元素**:先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间; +- **删除元素**:先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; +- **获取最小 / 最大元素**:数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间; 观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。 From d6d6a16c7e24d9737d667507a729ec9e60cd1bda Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Tue, 10 Jan 2023 00:46:04 +0800 Subject: [PATCH 5/7] Update the access() function of linked_list --- .../chapter_array_and_linkedlist/linked_list.cpp | 2 +- .../chapter_array_and_linkedlist/linked_list.cs | 2 +- .../go/chapter_array_and_linkedlist/linked_list.go | 2 +- .../chapter_array_and_linkedlist/linked_list.java | 2 +- .../chapter_array_and_linkedlist/linked_list.py | 2 +- .../chapter_array_and_linkedlist/linked_list.swift | 2 +- docs/chapter_array_and_linkedlist/linked_list.md | 13 +++++++------ 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp index 13fa68691..5e976a89a 100644 --- a/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -28,9 +28,9 @@ void remove(ListNode* n0) { /* 访问链表中索引为 index 的结点 */ ListNode* access(ListNode* head, int index) { for (int i = 0; i < index; i++) { - head = head->next; if (head == nullptr) return nullptr; + head = head->next; } return head; } diff --git a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs index d700a5ae7..d765724ff 100644 --- a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs +++ b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -39,9 +39,9 @@ namespace hello_algo.chapter_array_and_linkedlist { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } diff --git a/codes/go/chapter_array_and_linkedlist/linked_list.go b/codes/go/chapter_array_and_linkedlist/linked_list.go index fa4538ab4..8a60fd18e 100644 --- a/codes/go/chapter_array_and_linkedlist/linked_list.go +++ b/codes/go/chapter_array_and_linkedlist/linked_list.go @@ -29,10 +29,10 @@ func removeNode(n0 *ListNode) { /* 访问链表中索引为 index 的结点 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { - head = head.Next if head == nil { return nil } + head = head.Next } return head } diff --git a/codes/java/chapter_array_and_linkedlist/linked_list.java b/codes/java/chapter_array_and_linkedlist/linked_list.java index 570778ff5..0db8f6ae7 100644 --- a/codes/java/chapter_array_and_linkedlist/linked_list.java +++ b/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -29,9 +29,9 @@ public class linked_list { /* 访问链表中索引为 index 的结点 */ static ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } diff --git a/codes/python/chapter_array_and_linkedlist/linked_list.py b/codes/python/chapter_array_and_linkedlist/linked_list.py index dce110340..4fb6b1ba5 100644 --- a/codes/python/chapter_array_and_linkedlist/linked_list.py +++ b/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -26,9 +26,9 @@ def remove(n0): """ 访问链表中索引为 index 的结点 """ def access(head, index): for _ in range(index): - head = head.next if not head: return None + head = head.next return head """ 在链表中查找值为 target 的首个结点 """ diff --git a/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/codes/swift/chapter_array_and_linkedlist/linked_list.swift index 06837f750..90218b26b 100644 --- a/codes/swift/chapter_array_and_linkedlist/linked_list.swift +++ b/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -29,10 +29,10 @@ func remove(n0: ListNode) { func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { - head = head?.next if head == nil { return nil } + head = head?.next } return head } diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 7aa2754ca..8dd069788 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -400,6 +400,7 @@ comments: true n0.next = P; P.next = n1; } + /* 删除链表的结点 n0 之后的首个结点 */ function remove(n0: ListNode): void { if (!n0.next) { @@ -474,9 +475,9 @@ comments: true /* 访问链表中索引为 index 的结点 */ ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } @@ -488,9 +489,9 @@ comments: true /* 访问链表中索引为 index 的结点 */ ListNode* access(ListNode* head, int index) { for (int i = 0; i < index; i++) { - head = head->next; if (head == nullptr) return nullptr; + head = head->next; } return head; } @@ -502,9 +503,9 @@ comments: true """ 访问链表中索引为 index 的结点 """ def access(head, index): for _ in range(index): - head = head.next if not head: return None + head = head.next return head ``` @@ -514,10 +515,10 @@ comments: true /* 访问链表中索引为 index 的结点 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { - head = head.Next if head == nil { return nil } + head = head.Next } return head } @@ -566,9 +567,9 @@ comments: true { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } @@ -581,10 +582,10 @@ comments: true func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { - head = head?.next if head == nil { return nil } + head = head?.next } return head } From b5019b0494bfbe28d50c0e3c5442cb3ba62edc03 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Tue, 10 Jan 2023 01:14:44 +0800 Subject: [PATCH 6/7] Update "about the book". --- docs/chapter_preface/about_the_book.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index c3209280c..01f513f00 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -205,9 +205,6 @@ comments: true */ ``` -""" -在 Java, C, C++, C#, Go, JS, TS 的代码注释中,`/* ... */` 用于注释函数、类、测试样例等标题, `// ...` 用于解释代码内容;类似地,在 Python 中,`""" ... """` 用于注释标题, `# ...` 用于解释代码。 - ## 本书特点 * ??? abstract "默认折叠,可以跳过" From b7e09c4c1db1fd316d16f39453b9e8782a5d5579 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Tue, 10 Jan 2023 01:49:16 +0800 Subject: [PATCH 7/7] Unify the comment format of C# codes. --- .../chapter_array_and_linkedlist/array.cs | 30 +++++-------------- .../linked_list.cs | 18 ++++------- .../csharp/chapter_tree/binary_search_tree.cs | 6 +--- codes/csharp/chapter_tree/binary_tree_bfs.cs | 6 +--- codes/csharp/chapter_tree/binary_tree_dfs.cs | 15 ++-------- 5 files changed, 18 insertions(+), 57 deletions(-) diff --git a/codes/csharp/chapter_array_and_linkedlist/array.cs b/codes/csharp/chapter_array_and_linkedlist/array.cs index 95479574f..97a37f046 100644 --- a/codes/csharp/chapter_array_and_linkedlist/array.cs +++ b/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -8,9 +8,7 @@ namespace hello_algo.chapter_array_and_linkedlist { public class Array { - /// - /// 随机返回一个数组元素 - /// + /* 随机返回一个数组元素 */ public static int RandomAccess(int[] nums) { Random random = new(); @@ -19,9 +17,7 @@ namespace hello_algo.chapter_array_and_linkedlist return randomNum; } - /// - /// 扩展数组长度 - /// + /* 扩展数组长度 */ public static int[] Extend(int[] nums, int enlarge) { // 初始化一个扩展长度后的数组 @@ -35,9 +31,7 @@ namespace hello_algo.chapter_array_and_linkedlist return res; } - /// - /// 在数组的索引 index 处插入元素 num - /// + /* 在数组的索引 index 处插入元素 num */ public static void Insert(int[] nums, int num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 @@ -49,9 +43,7 @@ namespace hello_algo.chapter_array_and_linkedlist nums[index] = num; } - /// - /// 删除索引 index 处元素 - /// + /* 删除索引 index 处元素 */ public static void Remove(int[] nums, int index) { // 把索引 index 之后的所有元素向前移动一位 @@ -61,9 +53,7 @@ namespace hello_algo.chapter_array_and_linkedlist } } - /// - /// 遍历数组 - /// + /* 遍历数组 */ public static void Traverse(int[] nums) { int count = 0; @@ -79,9 +69,7 @@ namespace hello_algo.chapter_array_and_linkedlist } } - /// - /// 在数组中查找指定元素 - /// + /* 在数组中查找指定元素 */ public static int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) @@ -92,15 +80,13 @@ namespace hello_algo.chapter_array_and_linkedlist return -1; } - /// - /// 辅助函数,数组转字符串 - /// + /* 辅助函数,数组转字符串 */ public static string ToString(int[] nums) { return string.Join(",", nums); } - // Driver Code + [Test] public static void Test() { diff --git a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs index d765724ff..81e1499cb 100644 --- a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs +++ b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -9,9 +9,7 @@ namespace hello_algo.chapter_array_and_linkedlist { public class linked_list { - /// - /// 在链表的结点 n0 之后插入结点 P - /// + /* 在链表的结点 n0 之后插入结点 P */ public static void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; @@ -19,9 +17,7 @@ namespace hello_algo.chapter_array_and_linkedlist P.next = n1; } - /// - /// 删除链表的结点 n0 之后的首个结点 - /// + /* 删除链表的结点 n0 之后的首个结点 */ public static void Remove(ListNode n0) { if (n0.next == null) @@ -32,9 +28,7 @@ namespace hello_algo.chapter_array_and_linkedlist n0.next = n1; } - /// - /// 访问链表中索引为 index 的结点 - /// + /* 访问链表中索引为 index 的结点 */ public static ListNode? Access(ListNode head, int index) { for (int i = 0; i < index; i++) @@ -46,9 +40,7 @@ namespace hello_algo.chapter_array_and_linkedlist return head; } - /// - /// 在链表中查找值为 target 的首个结点 - /// + /* 在链表中查找值为 target 的首个结点 */ public static int Find(ListNode head, int target) { int index = 0; @@ -62,7 +54,7 @@ namespace hello_algo.chapter_array_and_linkedlist return -1; } - // Driver Code + [Test] public void Test() { diff --git a/codes/csharp/chapter_tree/binary_search_tree.cs b/codes/csharp/chapter_tree/binary_search_tree.cs index 5164cf780..d2e5f95e6 100644 --- a/codes/csharp/chapter_tree/binary_search_tree.cs +++ b/codes/csharp/chapter_tree/binary_search_tree.cs @@ -35,11 +35,7 @@ namespace hello_algo.chapter_tree return root; } - /// - /// 查找结点 - /// - /// - /// + /* 查找结点 */ public TreeNode? search(int num) { TreeNode? cur = root; diff --git a/codes/csharp/chapter_tree/binary_tree_bfs.cs b/codes/csharp/chapter_tree/binary_tree_bfs.cs index f0c914acb..9a57dbc17 100644 --- a/codes/csharp/chapter_tree/binary_tree_bfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -12,11 +12,7 @@ namespace hello_algo.chapter_tree public class binary_tree_bfs { - /// - /// 层序遍历 - /// - /// - /// + /* 层序遍历 */ public List hierOrder(TreeNode root) { // 初始化队列,加入根结点 diff --git a/codes/csharp/chapter_tree/binary_tree_dfs.cs b/codes/csharp/chapter_tree/binary_tree_dfs.cs index 0f89cb3b2..f8669d782 100644 --- a/codes/csharp/chapter_tree/binary_tree_dfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -13,10 +13,7 @@ namespace hello_algo.chapter_tree { List list = new(); - /// - /// 前序遍历 - /// - /// + /* 前序遍历 */ void preOrder(TreeNode? root) { if (root == null) return; @@ -26,10 +23,7 @@ namespace hello_algo.chapter_tree preOrder(root.right); } - /// - /// 中序遍历 - /// - /// + /* 中序遍历 */ void inOrder(TreeNode? root) { if (root == null) return; @@ -39,10 +33,7 @@ namespace hello_algo.chapter_tree inOrder(root.right); } - /// - /// 后序遍历 - /// - /// + /* 后序遍历 */ void postOrder(TreeNode? root) { if (root == null) return;