diff --git a/codes/csharp/chapter_computational_complexity/time_complexity.cs b/codes/csharp/chapter_computational_complexity/time_complexity.cs index 722b768ba..22427d36c 100644 --- a/codes/csharp/chapter_computational_complexity/time_complexity.cs +++ b/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -85,7 +85,7 @@ public class time_complexity { int count = 0; // 计数器 // 外循环:未排序区间为 [0, i] for (int i = nums.Length - 1; i > 0; i--) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] diff --git a/codes/csharp/chapter_sorting/bubble_sort.cs b/codes/csharp/chapter_sorting/bubble_sort.cs index 265f74170..97c5fa456 100644 --- a/codes/csharp/chapter_sorting/bubble_sort.cs +++ b/codes/csharp/chapter_sorting/bubble_sort.cs @@ -11,7 +11,7 @@ public class bubble_sort { void BubbleSort(int[] nums) { // 外循环:未排序区间为 [0, i] for (int i = nums.Length - 1; i > 0; i--) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -26,7 +26,7 @@ public class bubble_sort { // 外循环:未排序区间为 [0, i] for (int i = nums.Length - 1; i > 0; i--) { bool flag = false; // 初始化标志位 - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] diff --git a/codes/go/chapter_hashing/hash_collision_test.go b/codes/go/chapter_hashing/hash_collision_test.go index d0cc81af2..4e59908d2 100644 --- a/codes/go/chapter_hashing/hash_collision_test.go +++ b/codes/go/chapter_hashing/hash_collision_test.go @@ -26,7 +26,7 @@ func TestHashMapChaining(t *testing.T) { /* 查询操作 */ // 向哈希表中输入键 key ,得到值 value name := hmap.get(15937) - fmt.Println("\n输入学号 15937 ,查询到姓名 ", name) + fmt.Println("\n输入学号 15937 ,查询到姓名", name) /* 删除操作 */ // 在哈希表中删除键值对 (key, value) diff --git a/codes/go/chapter_hashing/hash_map_open_addressing.go b/codes/go/chapter_hashing/hash_map_open_addressing.go index 6fb61c3be..d4b80b9e3 100644 --- a/codes/go/chapter_hashing/hash_map_open_addressing.go +++ b/codes/go/chapter_hashing/hash_map_open_addressing.go @@ -6,7 +6,6 @@ package chapter_hashing import ( "fmt" - "strconv" ) /* 开放寻址哈希表 */ @@ -15,129 +14,113 @@ type hashMapOpenAddressing struct { capacity int // 哈希表容量 loadThres float64 // 触发扩容的负载因子阈值 extendRatio int // 扩容倍数 - buckets []pair // 桶数组 - removed pair // 删除标记 + buckets []*pair // 桶数组 + TOMBSTONE *pair // 删除标记 } /* 构造方法 */ func newHashMapOpenAddressing() *hashMapOpenAddressing { - buckets := make([]pair, 4) return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, - buckets: buckets, - removed: pair{ - key: -1, - val: "-1", - }, + buckets: make([]*pair, 4), + TOMBSTONE: &pair{-1, "-1"}, } } /* 哈希函数 */ -func (m *hashMapOpenAddressing) hashFunc(key int) int { - return key % m.capacity +func (h *hashMapOpenAddressing) hashFunc(key int) int { + return key % h.capacity // 根据键计算哈希值 } /* 负载因子 */ -func (m *hashMapOpenAddressing) loadFactor() float64 { - return float64(m.size) / float64(m.capacity) +func (h *hashMapOpenAddressing) loadFactor() float64 { + return float64(h.size) / float64(h.capacity) // 计算当前负载因子 } -/* 查询操作 */ -func (m *hashMapOpenAddressing) get(key int) string { - idx := m.hashFunc(key) - // 线性探测,从 index 开始向后遍历 - for i := 0; i < m.capacity; i++ { - // 计算桶索引,越过尾部则返回头部 - j := (idx + i) % m.capacity - // 若遇到空桶,说明无此 key ,则返回 null - if m.buckets[j] == (pair{}) { - return "" +/* 搜索 key 对应的桶索引 */ +func (h *hashMapOpenAddressing) findBucket(key int) int { + index := h.hashFunc(key) // 获取初始索引 + firstTombstone := -1 // 记录遇到的第一个TOMBSTONE的位置 + for h.buckets[index] != nil { + if h.buckets[index].key == key { + if firstTombstone != -1 { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + h.buckets[firstTombstone] = h.buckets[index] + h.buckets[index] = h.TOMBSTONE + return firstTombstone // 返回移动后的桶索引 + } + return index // 返回找到的索引 } - // 若遇到指定 key ,则返回对应 val - if m.buckets[j].key == key && m.buckets[j] != m.removed { - return m.buckets[j].val + if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { + firstTombstone = index // 记录遇到的首个删除标记的位置 } + index = (index + 1) % h.capacity // 线性探测,越过尾部则返回头部 + } + // 若 key 不存在,则返回添加点的索引 + if firstTombstone != -1 { + return firstTombstone + } + return index +} + +/* 查询操作 */ +func (h *hashMapOpenAddressing) get(key int) string { + index := h.findBucket(key) // 搜索 key 对应的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + return h.buckets[index].val // 若找到键值对,则返回对应 val } - // 若未找到 key ,则返回空字符串 - return "" + return "" // 若键值对不存在,则返回 "" } /* 添加操作 */ -func (m *hashMapOpenAddressing) put(key int, val string) { - // 当负载因子超过阈值时,执行扩容 - if m.loadFactor() > m.loadThres { - m.extend() +func (h *hashMapOpenAddressing) put(key int, val string) { + if h.loadFactor() > h.loadThres { + h.extend() // 当负载因子超过阈值时,执行扩容 } - idx := m.hashFunc(key) - // 线性探测,从 index 开始向后遍历 - for i := 0; i < m.capacity; i++ { - // 计算桶索引,越过尾部则返回头部 - j := (idx + i) % m.capacity - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if m.buckets[j] == (pair{}) || m.buckets[j] == m.removed { - m.buckets[j] = pair{ - key: key, - val: val, - } - m.size += 1 - return - } - // 若遇到指定 key ,则更新对应 val - if m.buckets[j].key == key { - m.buckets[j].val = val - return - } + index := h.findBucket(key) // 搜索 key 对应的桶索引 + if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { + h.buckets[index] = &pair{key, val} // 若键值对不存在,则添加该键值对 + h.size++ + } else { + h.buckets[index].val = val // 若找到键值对,则覆盖 val } } /* 删除操作 */ -func (m *hashMapOpenAddressing) remove(key int) { - idx := m.hashFunc(key) - // 遍历桶,从中删除键值对 - // 线性探测,从 index 开始向后遍历 - for i := 0; i < m.capacity; i++ { - // 计算桶索引,越过尾部则返回头部 - j := (idx + i) % m.capacity - // 若遇到空桶,说明无此 key ,则直接返回 - if m.buckets[j] == (pair{}) { - return - } - // 若遇到指定 key ,则标记删除并返回 - if m.buckets[j].key == key { - m.buckets[j] = m.removed - m.size -= 1 - } +func (h *hashMapOpenAddressing) remove(key int) { + index := h.findBucket(key) // 搜索 key 对应的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + h.buckets[index] = h.TOMBSTONE // 若找到键值对,则用删除标记覆盖它 + h.size-- } } /* 扩容哈希表 */ -func (m *hashMapOpenAddressing) extend() { - // 暂存原哈希表 - tmpBuckets := make([]pair, len(m.buckets)) - copy(tmpBuckets, m.buckets) - - // 初始化扩容后的新哈希表 - m.capacity *= m.extendRatio - m.buckets = make([]pair, m.capacity) - m.size = 0 +func (h *hashMapOpenAddressing) extend() { + oldBuckets := h.buckets // 暂存原哈希表 + h.capacity *= h.extendRatio // 更新容量 + h.buckets = make([]*pair, h.capacity) // 初始化扩容后的新哈希表 + h.size = 0 // 重置大小 // 将键值对从原哈希表搬运至新哈希表 - for _, p := range tmpBuckets { - if p != (pair{}) && p != m.removed { - m.put(p.key, p.val) + for _, pair := range oldBuckets { + if pair != nil && pair != h.TOMBSTONE { + h.put(pair.key, pair.val) } } } /* 打印哈希表 */ -func (m *hashMapOpenAddressing) print() { - for _, p := range m.buckets { - if p != (pair{}) { - fmt.Println(strconv.Itoa(p.key) + " -> " + p.val) - } else { +func (h *hashMapOpenAddressing) print() { + for _, pair := range h.buckets { + if pair == nil { fmt.Println("nil") + } else if pair == h.TOMBSTONE { + fmt.Println("TOMBSTONE") + } else { + fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } diff --git a/codes/rust/chapter_sorting/bubble_sort.rs b/codes/rust/chapter_sorting/bubble_sort.rs index b69227dd3..d7fde10ae 100644 --- a/codes/rust/chapter_sorting/bubble_sort.rs +++ b/codes/rust/chapter_sorting/bubble_sort.rs @@ -27,7 +27,6 @@ fn bubble_sort_with_flag(nums: &mut [i32]) { // 外循环:未排序区间为 [0, i] for i in (1..nums.len()).rev() { let mut flag = false; // 初始化标志位 - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0..i { if nums[j] > nums[j + 1] { diff --git a/codes/swift/chapter_computational_complexity/time_complexity.swift b/codes/swift/chapter_computational_complexity/time_complexity.swift index 4b70c2e8b..23caaae39 100644 --- a/codes/swift/chapter_computational_complexity/time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -50,7 +50,7 @@ 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] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in 0 ..< i { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] diff --git a/codes/swift/chapter_sorting/bubble_sort.swift b/codes/swift/chapter_sorting/bubble_sort.swift index 589c55fb8..15ff9d7e8 100644 --- a/codes/swift/chapter_sorting/bubble_sort.swift +++ b/codes/swift/chapter_sorting/bubble_sort.swift @@ -8,7 +8,7 @@ func bubbleSort(nums: inout [Int]) { // 外循环:未排序区间为 [0, i] for i in stride(from: nums.count - 1, to: 0, by: -1) { - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 for j in stride(from: 0, to: i, by: 1) { if nums[j] > nums[j + 1] { // 交换 nums[j] 与 nums[j + 1] diff --git a/codes/zig/chapter_computational_complexity/time_complexity.zig b/codes/zig/chapter_computational_complexity/time_complexity.zig index f59cacc91..737367118 100644 --- a/codes/zig/chapter_computational_complexity/time_complexity.zig +++ b/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -57,7 +57,7 @@ fn bubbleSort(nums: []i32) i32 { var i: i32 = @as(i32, @intCast(nums.len)) - 1; while (i > 0) : (i -= 1) { var j: usize = 0; - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] diff --git a/codes/zig/chapter_sorting/bubble_sort.zig b/codes/zig/chapter_sorting/bubble_sort.zig index 96ebb3fce..676a7a830 100644 --- a/codes/zig/chapter_sorting/bubble_sort.zig +++ b/codes/zig/chapter_sorting/bubble_sort.zig @@ -11,7 +11,7 @@ fn bubbleSort(nums: []i32) void { var i: usize = nums.len - 1; while (i > 0) : (i -= 1) { var j: usize = 0; - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] @@ -30,7 +30,7 @@ fn bubbleSortWithFlag(nums: []i32) void { while (i > 0) : (i -= 1) { var flag = false; // 初始化标志位 var j: usize = 0; - // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 while (j < i) : (j += 1) { if (nums[j] > nums[j + 1]) { // 交换 nums[j] 与 nums[j + 1] diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index 2c9c7ebd6..522d055d0 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -31,11 +31,11 @@ # 元素内存地址 = 数组内存地址(首元素内存地址) + 元素长度 * 元素索引 ``` -**Q**:删除节点后,是否需要把 `P.next` 设为 `None` 呢? +**Q**:删除节点 `P` 后,是否需要把 `P.next` 设为 `None` 呢? 不修改 `P.next` 也可以。从该链表的角度看,从头节点遍历到尾节点已经不会遇到 `P` 了。这意味着节点 `P` 已经从链表中删除了,此时节点 `P` 指向哪里都不会对该链表产生影响。 -从垃圾回收的角度看,对于 Java、Python、Go 等拥有自动垃圾回收机制的语言来说,节点 `P` 是否被回收取决于是否仍存在指向它的引用,而不是 `P.next` 的值。在 C 和 C++ 等语言中,我们需要手动释放节点内存。 +从数据结构与算法(做题)的角度看,不断开没有关系,只要保证程序的逻辑是正确的就行。从标准库的角度看,断开更加安全、逻辑更加清晰。如果不断开,假设被删除节点未被正常回收,那么它会影响后继节点的内存回收。 **Q**:在链表中插入和删除操作的时间复杂度是 $O(1)$ 。但是增删之前都需要 $O(n)$ 的时间查找元素,那为什么时间复杂度不是 $O(n)$ 呢? @@ -74,7 +74,3 @@ **Q**:初始化列表 `res = [0] * self.size()` 操作,会导致 `res` 的每个元素引用相同的地址吗? 不会。但二维数组会有这个问题,例如初始化二维列表 `res = [[0] * self.size()]` ,则多次引用了同一个列表 `[0]` 。 - -**Q**:在删除节点中,需要断开该节点与其后继节点之间的引用指向吗? - -从数据结构与算法(做题)的角度看,不断开没有关系,只要保证程序的逻辑是正确的就行。从标准库的角度看,断开更加安全、逻辑更加清晰。如果不断开,假设被删除节点未被正常回收,那么它会影响后继节点的内存回收。 diff --git a/docs/chapter_paperbook/index.md b/docs/chapter_paperbook/index.md index 5066e1fb6..3f1c5514d 100644 --- a/docs/chapter_paperbook/index.md +++ b/docs/chapter_paperbook/index.md @@ -4,7 +4,7 @@ icon: fontawesome/solid/book status: new --- -# 纸质书介绍 +# 纸质书 经过长时间的打磨,《Hello 算法》纸质书终于发布了!此时的心情可以用一句诗来形容: diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md index 2f470b784..1de9d4e26 100644 --- a/docs/chapter_tree/array_representation_of_tree.md +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -12,7 +12,7 @@ ![完美二叉树的数组表示](array_representation_of_tree.assets/array_representation_binary_tree.png) -**映射公式的角色相当于链表中的指针**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。 +**映射公式的角色相当于链表中的引用**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。 ## 表示任意二叉树 diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 5255f40f2..69f94b05b 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -333,4 +333,4 @@ AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。 - 组织和存储大型数据,适用于高频查找、低频增删的场景。 - 用于构建数据库中的索引系统。 -- 红黑树也是一种常见的平衡二叉搜索树。相较于 AVL 树,红黑树的平衡条件相对宽松,插入与删除节点所需的旋转操作相对较少,节点增删操作的平均效率更高。 +- 红黑树也是一种常见的平衡二叉搜索树。相较于 AVL 树,红黑树的平衡条件更宽松,插入与删除节点所需的旋转操作更少,节点增删操作的平均效率更高。 diff --git a/mkdocs.yml b/mkdocs.yml index dfc9c3efd..b09b40fe6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -290,6 +290,5 @@ nav: - 16.3   术语表: chapter_appendix/terminology.md - 参考文献: - chapter_reference/index.md - - 纸质书介绍: - # [status: new] + - 纸质书: - chapter_paperbook/index.md diff --git a/overrides/main.html b/overrides/main.html index 3a70e0096..1ea83977a 100644 --- a/overrides/main.html +++ b/overrides/main.html @@ -2,7 +2,7 @@ {% block announce %} {% if config.theme.language == 'zh' %} - {% set announcements = '纸质书已发布,详情请见纸质书介绍' %} + {% set announcements = '纸质书已发布,详情请见这里' %} {% elif config.theme.language == 'en' %} {% set announcements = 'The paper book (Chinese edition) published. Please visit this link for more details.' %} {% endif %}