Skip to content

Commit

Permalink
Bug fixes and improvements (#1152)
Browse files Browse the repository at this point in the history
* Update avl_tree.md

* Remove the empty space

* Simplify the heading of the paperbook chapter

* Update hash_map_open_addressing.go to the latest version

* Improvements
  • Loading branch information
krahets authored Mar 18, 2024
1 parent 6f1ec66 commit 7f43f92
Show file tree
Hide file tree
Showing 15 changed files with 83 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ int BubbleSort(int[] nums) {
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]
Expand Down
4 changes: 2 additions & 2 deletions codes/csharp/chapter_sorting/bubble_sort.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -26,7 +26,7 @@ void BubbleSortWithFlag(int[] nums) {
// 外循环:未排序区间为 [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]
Expand Down
2 changes: 1 addition & 1 deletion codes/go/chapter_hashing/hash_collision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
151 changes: 67 additions & 84 deletions codes/go/chapter_hashing/hash_map_open_addressing.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package chapter_hashing

import (
"fmt"
"strconv"
)

/* 开放寻址哈希表 */
Expand All @@ -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)
}
}
}
1 change: 0 additions & 1 deletion codes/rust/chapter_sorting/bubble_sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion codes/swift/chapter_sorting/bubble_sort.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions codes/zig/chapter_sorting/bubble_sort.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand Down
8 changes: 2 additions & 6 deletions docs/chapter_array_and_linkedlist/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)$ 呢?

Expand Down Expand Up @@ -74,7 +74,3 @@
**Q**:初始化列表 `res = [0] * self.size()` 操作,会导致 `res` 的每个元素引用相同的地址吗?

不会。但二维数组会有这个问题,例如初始化二维列表 `res = [[0] * self.size()]` ,则多次引用了同一个列表 `[0]`

**Q**:在删除节点中,需要断开该节点与其后继节点之间的引用指向吗?

从数据结构与算法(做题)的角度看,不断开没有关系,只要保证程序的逻辑是正确的就行。从标准库的角度看,断开更加安全、逻辑更加清晰。如果不断开,假设被删除节点未被正常回收,那么它会影响后继节点的内存回收。
2 changes: 1 addition & 1 deletion docs/chapter_paperbook/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ icon: fontawesome/solid/book
status: new
---

# 纸质书介绍
# 纸质书

经过长时间的打磨,《Hello 算法》纸质书终于发布了!此时的心情可以用一句诗来形容:

Expand Down
2 changes: 1 addition & 1 deletion docs/chapter_tree/array_representation_of_tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

![完美二叉树的数组表示](array_representation_of_tree.assets/array_representation_binary_tree.png)

**映射公式的角色相当于链表中的指针**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。
**映射公式的角色相当于链表中的引用**。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。

## 表示任意二叉树

Expand Down
2 changes: 1 addition & 1 deletion docs/chapter_tree/avl_tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,4 @@ AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。

- 组织和存储大型数据,适用于高频查找、低频增删的场景。
- 用于构建数据库中的索引系统。
- 红黑树也是一种常见的平衡二叉搜索树。相较于 AVL 树,红黑树的平衡条件相对宽松,插入与删除节点所需的旋转操作相对较少,节点增删操作的平均效率更高。
- 红黑树也是一种常见的平衡二叉搜索树。相较于 AVL 树,红黑树的平衡条件更宽松,插入与删除节点所需的旋转操作更少,节点增删操作的平均效率更高。
3 changes: 1 addition & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,5 @@ nav:
- 16.3 &nbsp; 术语表: chapter_appendix/terminology.md
- 参考文献:
- chapter_reference/index.md
- 纸质书介绍:
# [status: new]
- 纸质书:
- chapter_paperbook/index.md
2 changes: 1 addition & 1 deletion overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% block announce %}
{% if config.theme.language == 'zh' %}
{% set announcements = '纸质书已发布,详情请见<a href="/chapter_paperbook/">纸质书介绍</a>' %}
{% set announcements = '纸质书已发布,详情请见<a href="/chapter_paperbook/">这里</a>' %}
{% elif config.theme.language == 'en' %}
{% set announcements = 'The paper book (Chinese edition) published. Please visit <a href="/chapter_paperbook/">this link</a> for more details.' %}
{% endif %}
Expand Down

0 comments on commit 7f43f92

Please sign in to comment.