From ce6ce9b444da4beaeeeca7ecd188020cc0ba8c48 Mon Sep 17 00:00:00 2001 From: krahets Date: Mon, 26 Jun 2023 23:13:42 +0800 Subject: [PATCH] build --- chapter_array_and_linkedlist/list.md | 8 +- chapter_hashing/hash_algorithm.md | 62 ++++- chapter_hashing/hash_collision.md | 320 ++++++++++++++++-------- chapter_searching/binary_search_edge.md | 6 +- chapter_sorting/summary.md | 4 + 5 files changed, 291 insertions(+), 109 deletions(-) diff --git a/chapter_array_and_linkedlist/list.md b/chapter_array_and_linkedlist/list.md index 7f0ffebcf..b6eedf3d3 100755 --- a/chapter_array_and_linkedlist/list.md +++ b/chapter_array_and_linkedlist/list.md @@ -950,7 +950,7 @@ comments: true // 元素数量超出容量时,触发扩容机制 if (size() == capacity()) extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 + // 将索引 index 以及之后的元素都向后移动一位 for (int j = size() - 1; j >= index; j--) { nums[j + 1] = nums[j]; } @@ -1050,7 +1050,7 @@ comments: true # 元素数量超出容量时,触发扩容机制 if self.__size == self.capacity(): self.extend_capacity() - # 索引 i 以及之后的元素都向后移动一位 + # 将索引 index 以及之后的元素都向后移动一位 for j in range(self.__size - 1, index - 1, -1): self.__nums[j + 1] = self.__nums[j] self.__nums[index] = num @@ -1150,7 +1150,7 @@ comments: true if l.numsSize == l.numsCapacity { l.extendCapacity() } - // 索引 i 以及之后的元素都向后移动一位 + // 将索引 index 以及之后的元素都向后移动一位 for j := l.numsSize - 1; j >= index; j-- { l.nums[j+1] = l.nums[j] } @@ -1776,7 +1776,7 @@ comments: true if (index < 0 or index >= self.size()) @panic("索引越界"); // 元素数量超出容量时,触发扩容机制 if (self.size() == self.capacity()) try self.extendCapacity(); - // 索引 i 以及之后的元素都向后移动一位 + // 将索引 index 以及之后的元素都向后移动一位 var j = self.size() - 1; while (j >= index) : (j -= 1) { self.nums[j + 1] = self.nums[j]; diff --git a/chapter_hashing/hash_algorithm.md b/chapter_hashing/hash_algorithm.md index 20b6a9649..eb20cbd0c 100644 --- a/chapter_hashing/hash_algorithm.md +++ b/chapter_hashing/hash_algorithm.md @@ -265,13 +265,45 @@ index = hash(key) % capacity === "C#" ```csharp title="simple_hash.cs" - [class]{simple_hash}-[func]{addHash} + /* 加法哈希 */ + int addHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } - [class]{simple_hash}-[func]{mulHash} + /* 乘法哈希 */ + int mulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } - [class]{simple_hash}-[func]{xorHash} + /* 异或哈希 */ + int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } - [class]{simple_hash}-[func]{rotHash} + /* 旋转哈希 */ + 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" @@ -473,7 +505,29 @@ $$ === "C#" ```csharp title="built_in_hash.cs" + int num = 3; + int hashNum = num.GetHashCode(); + // 整数 3 的哈希值为 3; + bool bol = true; + int hashBol = bol.GetHashCode(); + // 布尔量 true 的哈希值为 1; + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + // 小数 3.14159 的哈希值为 -1340954729; + + string str = "Hello 算法"; + int hashStr = str.GetHashCode(); + // 字符串 Hello 算法 的哈希值为 -586107568; + + object[] arr = { 12836, "小哈" }; + int hashTup = arr.GetHashCode(); + // 数组 [12836, 小哈] 的哈希值为 42931033; + + ListNode obj = new ListNode(0); + int hashObj = obj.GetHashCode(); + // 节点对象 0 的哈希值为 39053774; ``` === "Swift" diff --git a/chapter_hashing/hash_collision.md b/chapter_hashing/hash_collision.md index 4bc9e5c49..0b02e9de4 100644 --- a/chapter_hashing/hash_collision.md +++ b/chapter_hashing/hash_collision.md @@ -40,17 +40,6 @@ comments: true === "Java" ```java title="hash_map_chaining.java" - /* 键值对 */ - class Pair { - public int key; - public String val; - - public Pair(int key, String val) { - this.key = key; - this.val = val; - } - } - /* 链式地址哈希表 */ class HashMapChaining { int size; // 键值对数量 @@ -163,17 +152,6 @@ comments: true === "C++" ```cpp title="hash_map_chaining.cpp" - /* 键值对 */ - struct Pair { - public: - int key; - string val; - Pair(int key, string val) { - this->key = key; - this->val = val; - } - }; - /* 链式地址哈希表 */ class HashMapChaining { private: @@ -280,13 +258,6 @@ comments: true === "Python" ```python title="hash_map_chaining.py" - class Pair: - """键值对""" - - def __init__(self, key: int, val: str): - self.key = key - self.val = val - class HashMapChaining: """链式地址哈希表""" @@ -370,8 +341,6 @@ comments: true === "Go" ```go title="hash_map_chaining.go" - [class]{pair}-[func]{} - /* 链式地址哈希表 */ type hashMapChaining struct { size int // 键值对数量 @@ -499,61 +468,147 @@ comments: true === "JavaScript" ```javascript title="hash_map_chaining.js" - [class]{Pair}-[func]{} - [class]{HashMapChaining}-[func]{} ``` === "TypeScript" ```typescript title="hash_map_chaining.ts" - [class]{Pair}-[func]{} - [class]{HashMapChaining}-[func]{} ``` === "C" ```c title="hash_map_chaining.c" - [class]{pair}-[func]{} - [class]{hashMapChaining}-[func]{} ``` === "C#" ```csharp title="hash_map_chaining.cs" - [class]{Pair}-[func]{} + /* 链式地址哈希表 */ + class HashMapChaining { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + List> buckets; // 桶数组 - [class]{HashMapChaining}-[func]{} + /* 构造方法 */ + 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--; + } + + /* 扩容哈希表 */ + 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]{Pair}-[func]{} - [class]{HashMapChaining}-[func]{} ``` === "Zig" ```zig title="hash_map_chaining.zig" - [class]{Pair}-[func]{} - [class]{HashMapChaining}-[func]{} ``` === "Dart" ```dart title="hash_map_chaining.dart" - /* 键值对 */ - class Pair { - int key; - String val; - Pair(this.key, this.val); - } - /* 链式地址哈希表 */ class HashMapChaining { late int size; // 键值对数量 @@ -687,17 +742,6 @@ comments: true === "Java" ```java title="hash_map_open_addressing.java" - /* 键值对 */ - class Pair { - public int key; - public String val; - - public Pair(int key, String val) { - this.key = key; - this.val = val; - } - } - /* 开放寻址哈希表 */ class HashMapOpenAddressing { private int size; // 键值对数量 @@ -821,15 +865,6 @@ comments: true === "C++" ```cpp title="hash_map_open_addressing.cpp" - /* 键值对 */ - struct Pair { - int key; - string val; - - Pair(int k, string v) : key(k), val(v) { - } - }; - /* 开放寻址哈希表 */ class HashMapOpenAddressing { private: @@ -955,13 +990,6 @@ comments: true === "Python" ```python title="hash_map_open_addressing.py" - class Pair: - """键值对""" - - def __init__(self, key: int, val: str): - self.key = key - self.val = val - class HashMapOpenAddressing: """开放寻址哈希表""" @@ -1057,8 +1085,6 @@ comments: true === "Go" ```go title="hash_map_open_addressing.go" - [class]{pair}-[func]{} - /* 链式地址哈希表 */ type hashMapOpenAddressing struct { size int // 键值对数量 @@ -1195,61 +1221,159 @@ comments: true === "JavaScript" ```javascript title="hash_map_open_addressing.js" - [class]{Pair}-[func]{} - [class]{HashMapOpenAddressing}-[func]{} ``` === "TypeScript" ```typescript title="hash_map_open_addressing.ts" - [class]{Pair}-[func]{} - [class]{HashMapOpenAddressing}-[func]{} ``` === "C" ```c title="hash_map_open_addressing.c" - [class]{pair}-[func]{} - [class]{hashMapOpenAddressing}-[func]{} ``` === "C#" ```csharp title="hash_map_open_addressing.cs" - [class]{Pair}-[func]{} + /* 开放寻址哈希表 */ + class HashMapOpenAddressing { + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + Pair[] buckets; // 桶数组 + Pair removed; // 删除标记 - [class]{HashMapOpenAddressing}-[func]{} + /* 构造方法 */ + 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]{Pair}-[func]{} - [class]{HashMapOpenAddressing}-[func]{} ``` === "Zig" ```zig title="hash_map_open_addressing.zig" - [class]{Pair}-[func]{} - [class]{HashMapOpenAddressing}-[func]{} ``` === "Dart" ```dart title="hash_map_open_addressing.dart" - /* 键值对 */ - class Pair { - int key; - String val; - Pair(this.key, this.val); - } - /* 开放寻址哈希表 */ class HashMapOpenAddressing { late int _size; // 键值对数量 diff --git a/chapter_searching/binary_search_edge.md b/chapter_searching/binary_search_edge.md index 7b80a535e..523e9160b 100644 --- a/chapter_searching/binary_search_edge.md +++ b/chapter_searching/binary_search_edge.md @@ -10,7 +10,7 @@ comments: true 给定一个长度为 $n$ 的有序数组 `nums` ,数组可能包含重复元素。请查找并返回元素 `target` 在数组中首次出现的索引。若数组中不包含该元素,则返回 $-1$ 。 -## 10.2.1.   简单方法 +## 10.2.1.   线性方法 为了查找数组中最左边的 `target` ,我们可以分为两步: @@ -21,11 +21,11 @@ comments: true

Fig. 线性查找最左边的元素

-这个方法虽然有效,但由于包含线性查找,**其时间复杂度可能会劣化至 $O(n)$** 。 +这个方法虽然有效,但由于包含线性查找,时间复杂度为 $O(n)$ ,当存在很多重复的 `target` 时效率较低。 ## 10.2.2.   二分方法 -实际上,我们可以仅通过二分查找解决以上问题。整体算法流程不变,先计算中点索引 $m$ ,再判断 `target` 和 `nums[m]` 大小关系: +考虑仅使用二分查找解决该问题。整体算法流程不变,先计算中点索引 $m$ ,再判断 `target` 和 `nums[m]` 大小关系: - 当 `nums[m] < target` 或 `nums[m] > target` 时,说明还没有找到 `target` ,因此采取与上节代码相同的缩小区间操作,**从而使指针 $i$ 和 $j$ 向 `target` 靠近**。 - 当 `nums[m] == target` 时,说明“小于 `target` 的元素”在区间 $[i, m - 1]$ 中,因此采用 $j = m - 1$ 来缩小区间,**从而使指针 $j$ 向小于 `target` 的元素靠近**。 diff --git a/chapter_sorting/summary.md b/chapter_sorting/summary.md index 1e0f0af86..a7e144813 100644 --- a/chapter_sorting/summary.md +++ b/chapter_sorting/summary.md @@ -44,6 +44,10 @@ comments: true 回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 $n, n - 1, n - 2, ..., 2, 1$ ,从而递归深度为 $n$ 。尾递归优化可以避免这种情况的出现。 +!!! question "当数组中所有元素都相等时,快速排序的时间复杂度是 $O(n^2)$ 吗?该如何处理这种退化情况?" + + 是的。这种情况可以考虑通过哨兵划分将数组划分为三个部分:小于、等于、大于基准数。仅向下递归小于和大于的两部分。在该方法下,输入元素全部相等的数组,仅一轮哨兵划分即可完成排序。 + !!! question "桶排序的最差时间复杂度为什么是 $O(n^2)$ ?" 最差情况下,所有元素被分至同一个桶中。如果我们采用一个 $O(n^2)$ 算法来排序这些元素,则时间复杂度为 $O(n^2)$ 。