diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.md b/docs/chapter_computational_complexity/iteration_and_recursion.md index 7adc67549..3a8756eae 100644 --- a/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -172,7 +172,15 @@ status: new === "Zig" ```zig title="iteration.zig" - [class]{}-[func]{forLoop} + // for 循环 + fn forLoop(n: usize) i32 { + var res: i32 = 0; + // 循环求和 1, 2, ..., n-1, n + for (1..n+1) |i| { + res = res + @as(i32, @intCast(i)); + } + return res; + } ``` 图 2-1 展示了该求和函数的流程框图。 @@ -368,7 +376,17 @@ status: new === "Zig" ```zig title="iteration.zig" - [class]{}-[func]{whileLoop} + // while 循环 + fn whileLoop(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += @intCast(i); + i += 1; + } + return res; + } ``` 在 `while` 循环中,由于初始化和更新条件变量的步骤是独立在循环结构之外的,**因此它比 `for` 循环的自由度更高**。 @@ -575,7 +593,19 @@ status: new === "Zig" ```zig title="iteration.zig" - [class]{}-[func]{whileLoopII} + // while 循环(两次更新) + fn whileLoopII(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化条件变量 + // 循环求和 1, 4, ... + while (i <= n) { + res += @intCast(i); + // 更新条件变量 + i += 1; + i *= 2; + } + return res; + } ``` 总的来说,**`for` 循环的代码更加紧凑,`while` 循环更加灵活**,两者都可以实现迭代结构。选择使用哪一个应该根据特定问题的需求来决定。 @@ -775,7 +805,21 @@ status: new === "Zig" ```zig title="iteration.zig" - [class]{}-[func]{nestedForLoop} + // 双层 for 循环 + fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + defer res.deinit(); + var buffer: [20]u8 = undefined; + // 循环 i = 1, 2, ..., n-1, n + for (1..n+1) |i| { + // 循环 j = 1, 2, ..., n-1, n + for (1..n+1) |j| { + var _str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{i, j}); + try res.appendSlice(_str); + } + } + return res.toOwnedSlice(); + } ``` 图 2-2 给出了该嵌套循环的流程框图。 @@ -970,7 +1014,17 @@ status: new === "Zig" ```zig title="recursion.zig" - [class]{}-[func]{recur} + // 递归函数 + fn recur(n: i32) i32 { + // 终止条件 + if (n == 1) { + return 1; + } + // 递:递归调用 + var res: i32 = recur(n - 1); + // 归:返回结果 + return n + res; + } ``` 图 2-3 展示了该函数的递归过程。 @@ -1158,7 +1212,15 @@ status: new === "Zig" ```zig title="recursion.zig" - [class]{}-[func]{tailRecur} + // 尾递归函数 + fn tailRecur(n: i32, res: i32) i32 { + // 终止条件 + if (n == 0) { + return res; + } + // 尾递归调用 + return tailRecur(n - 1, res + n); + } ``` 尾递归的执行过程如图 2-5 所示。对比普通递归和尾递归,求和操作的执行点是不同的。 @@ -1356,7 +1418,17 @@ status: new === "Zig" ```zig title="recursion.zig" - [class]{}-[func]{fib} + // 斐波那契数列 + fn fib(n: i32) i32 { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 or n == 2) { + return n - 1; + } + // 递归调用 f(n) = f(n-1) + f(n-2) + var res: i32 = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; + } ``` 观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一个层数为 $n$ 的「递归树 recursion tree」。 @@ -1655,7 +1727,26 @@ status: new === "Zig" ```zig title="recursion.zig" - [class]{}-[func]{forLoopRecur} + // 使用迭代模拟递归 + fn forLoopRecur(comptime n: i32) i32 { + // 使用一个显式的栈来模拟系统调用栈 + var stack: [n]i32 = undefined; + var res: i32 = 0; + // 递:递归调用 + var i: usize = n; + while (i > 0) { + stack[i - 1] = @intCast(i); + i -= 1; + } + // 归:返回结果 + var index: usize = n; + while (index > 0) { + index -= 1; + res += stack[index]; + } + // res = 1+2+3+...+n + return res; + } ``` 观察以上代码,当递归被转换为迭代后,代码变得更加复杂了。尽管迭代和递归在很多情况下可以互相转换,但也不一定值得这样做,有以下两点原因。 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 9be706db9..6684de4b4 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -178,7 +178,16 @@ comments: true === "Zig" ```zig title="" - + // 在某运行平台下 + fn algorithm(n: usize) void { + var a: i32 = 2; // 1 ns + a += 1; // 1 ns + a *= 2; // 10 ns + // 循环 n 次 + for (0..n) |_| { // 1 ns + std.debug.print("{}\n", .{0}); // 5 ns + } + } ``` 根据以上方法,可以得到算法运行时间为 $6n + 12$ ns : @@ -427,7 +436,24 @@ $$ === "Zig" ```zig title="" - + // 算法 A 的时间复杂度:常数阶 + fn algorithm_A(n: usize) void { + _ = n; + std.debug.print("{}\n", .{0}); + } + // 算法 B 的时间复杂度:线性阶 + fn algorithm_B(n: i32) void { + for (0..n) |_| { + std.debug.print("{}\n", .{0}); + } + } + // 算法 C 的时间复杂度:常数阶 + fn algorithm_C(n: i32) void { + _ = n; + for (0..1000000) |_| { + std.debug.print("{}\n", .{0}); + } + } ``` 图 2-7 展示了以上三个算法函数的时间复杂度。 @@ -606,7 +632,15 @@ $$ === "Zig" ```zig title="" - + fn algorithm(n: usize) void { + var a: i32 = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // 循环 n 次 + for (0..n) |_| { // +1(每轮都执行 i ++) + std.debug.print("{}\n", .{0}); // +1 + } + } ``` 设算法的操作数量是一个关于输入数据大小 $n$ 的函数,记为 $T(n)$ ,则以上函数的的操作数量为: @@ -857,7 +891,22 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 === "Zig" ```zig title="" + fn algorithm(n: usize) void { + var a: i32 = 1; // +0(技巧 1) + a = a + @as(i32, @intCast(n)); // +0(技巧 1) + // +n(技巧 2) + for(0..(5 * n + 1)) |_| { + std.debug.print("{}\n", .{0}); + } + + // +n*n(技巧 3) + for(0..(2 * n)) |_| { + for(0..(n + 1)) |_| { + std.debug.print("{}\n", .{0}); + } + } + } ``` 以下公式展示了使用上述技巧前后的统计结果,两者推出的时间复杂度都为 $O(n^2)$ 。 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index 1be5a3768..dc8898f52 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -2126,16 +2126,16 @@ comments: true #loadThres; // 触发扩容的负载因子阈值 #extendRatio; // 扩容倍数 #buckets; // 桶数组 - #removed; // 删除标记 + #TOMBSTONE; // 删除标记 /* 构造方法 */ constructor() { - this.#size = 0; - this.#capacity = 4; - this.#loadThres = 2.0 / 3.0; - this.#extendRatio = 2; - this.#buckets = new Array(this.#capacity).fill(null); - this.#removed = new Pair(-1, '-1'); + this.#size = 0; // 键值对数量 + this.#capacity = 4; // 哈希表容量 + this.#loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + this.#extendRatio = 2; // 扩容倍数 + this.#buckets = Array(this.#capacity).fill(null); // 桶数组 + this.#TOMBSTONE = new Pair(-1, '-1'); // 删除标记 } /* 哈希函数 */ @@ -2148,22 +2148,48 @@ comments: true return this.#size / this.#capacity; } + /* 搜索 key 对应的桶索引 */ + #findBucket(key) { + let index = this.#hashFunc(key); + let firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (this.#buckets[index] !== null) { + // 若遇到 key ,返回对应桶索引 + if (this.#buckets[index].key === key) { + // 若之前遇到了删除标记,则将键值对移动至该索引 + if (firstTombstone !== -1) { + this.#buckets[firstTombstone] = this.#buckets[index]; + this.#buckets[index] = this.#TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if ( + firstTombstone === -1 && + this.#buckets[index] === this.#TOMBSTONE + ) { + firstTombstone = index; + } + // 计算桶索引,越过尾部返回头部 + index = (index + 1) % this.#capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + /* 查询操作 */ get(key) { - const index = this.#hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (let i = 0; i < this.#capacity; i++) { - // 计算桶索引,越过尾部返回头部 - const j = (index + i) % this.#capacity; - // 若遇到空桶,说明无此 key ,则返回 null - if (this.#buckets[j] === null) return null; - // 若遇到指定 key ,则返回对应 val - if ( - this.#buckets[j].key === key && - this.#buckets[j].key !== this.#removed.key - ) - return this.#buckets[j].val; + // 搜索 key 对应的桶索引 + const index = this.#findBucket(key); + // 若找到键值对,则返回对应 val + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + return this.#buckets[index].val; } + // 若键值对不存在,则返回 null return null; } @@ -2173,45 +2199,32 @@ comments: true if (this.#loadFactor() > this.#loadThres) { this.#extend(); } - const index = this.#hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (let i = 0; i < this.#capacity; i++) { - // 计算桶索引,越过尾部返回头部 - let j = (index + i) % this.#capacity; - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if ( - this.#buckets[j] === null || - this.#buckets[j].key === this.#removed.key - ) { - this.#buckets[j] = new Pair(key, val); - this.#size += 1; - return; - } - // 若遇到指定 key ,则更新对应 val - if (this.#buckets[j].key === key) { - this.#buckets[j].val = val; - return; - } + // 搜索 key 对应的桶索引 + const index = this.#findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index].val = val; + return; } + // 若键值对不存在,则添加该键值对 + this.#buckets[index] = new Pair(key, val); + this.#size++; } /* 删除操作 */ remove(key) { - const index = this.#hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (let i = 0; i < this.#capacity; i++) { - // 计算桶索引,越过尾部返回头部 - const j = (index + i) % this.#capacity; - // 若遇到空桶,说明无此 key ,则直接返回 - if (this.#buckets[j] === null) { - return; - } - // 若遇到指定 key ,则标记删除并返回 - if (this.#buckets[j].key === key) { - this.#buckets[j] = this.#removed; - this.#size -= 1; - return; - } + // 搜索 key 对应的桶索引 + const index = this.#findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index] = this.#TOMBSTONE; + this.#size--; } } @@ -2221,11 +2234,11 @@ comments: true const bucketsTmp = this.#buckets; // 初始化扩容后的新哈希表 this.#capacity *= this.#extendRatio; - this.#buckets = new Array(this.#capacity).fill(null); + this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // 将键值对从原哈希表搬运至新哈希表 for (const pair of bucketsTmp) { - if (pair !== null && pair.key !== this.#removed.key) { + if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } @@ -2234,10 +2247,12 @@ comments: true /* 打印哈希表 */ print() { for (const pair of this.#buckets) { - if (pair !== null) { - console.log(pair.key + ' -> ' + pair.val); - } else { + if (pair === null) { console.log('null'); + } else if (pair === this.#TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); } } } @@ -2249,111 +2264,124 @@ comments: true ```typescript title="hash_map_open_addressing.ts" /* 开放寻址哈希表 */ class HashMapOpenAddressing { - #size: number; // 键值对数量 - #capacity: number; // 哈希表容量 - #loadThres: number; // 触发扩容的负载因子阈值 - #extendRatio: number; // 扩容倍数 - #buckets: Pair[]; // 桶数组 - #removed: Pair; // 删除标记 + private size: number; // 键值对数量 + private capacity: number; // 哈希表容量 + private loadThres: number; // 触发扩容的负载因子阈值 + private extendRatio: number; // 扩容倍数 + private buckets: Array; // 桶数组 + private TOMBSTONE: Pair; // 删除标记 /* 构造方法 */ constructor() { - this.#size = 0; - this.#capacity = 4; - this.#loadThres = 2.0 / 3.0; - this.#extendRatio = 2; - this.#buckets = new Array(this.#capacity).fill(null); - this.#removed = new Pair(-1, '-1'); + this.size = 0; // 键值对数量 + this.capacity = 4; // 哈希表容量 + this.loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + this.extendRatio = 2; // 扩容倍数 + this.buckets = Array(this.capacity).fill(null); // 桶数组 + this.TOMBSTONE = new Pair(-1, '-1'); // 删除标记 } /* 哈希函数 */ - #hashFunc(key: number): number { - return key % this.#capacity; + private hashFunc(key: number): number { + return key % this.capacity; } /* 负载因子 */ - #loadFactor(): number { - return this.#size / this.#capacity; + private loadFactor(): number { + return this.size / this.capacity; + } + + /* 搜索 key 对应的桶索引 */ + private findBucket(key: number): number { + let index = this.hashFunc(key); + let firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (this.buckets[index] !== null) { + // 若遇到 key ,返回对应桶索引 + if (this.buckets[index]!.key === key) { + // 若之前遇到了删除标记,则将键值对移动至该索引 + if (firstTombstone !== -1) { + this.buckets[firstTombstone] = this.buckets[index]; + this.buckets[index] = this.TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if ( + firstTombstone === -1 && + this.buckets[index] === this.TOMBSTONE + ) { + firstTombstone = index; + } + // 计算桶索引,越过尾部返回头部 + index = (index + 1) % this.capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone === -1 ? index : firstTombstone; } /* 查询操作 */ get(key: number): string | null { - const index = this.#hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (let i = 0; i < this.#capacity; i++) { - // 计算桶索引,越过尾部返回头部 - const j = (index + i) % this.#capacity; - // 若遇到空桶,说明无此 key ,则返回 null - if (this.#buckets[j] === null) return null; - // 若遇到指定 key ,则返回对应 val - if ( - this.#buckets[j].key === key && - this.#buckets[j].key !== this.#removed.key - ) - return this.#buckets[j].val; + // 搜索 key 对应的桶索引 + const index = this.findBucket(key); + // 若找到键值对,则返回对应 val + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + return this.buckets[index]!.val; } + // 若键值对不存在,则返回 null return null; } /* 添加操作 */ put(key: number, val: string): void { // 当负载因子超过阈值时,执行扩容 - if (this.#loadFactor() > this.#loadThres) { - this.#extend(); + if (this.loadFactor() > this.loadThres) { + this.extend(); } - const index = this.#hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (let i = 0; i < this.#capacity; i++) { - // 计算桶索引,越过尾部返回头部 - let j = (index + i) % this.#capacity; - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if ( - this.#buckets[j] === null || - this.#buckets[j].key === this.#removed.key - ) { - this.#buckets[j] = new Pair(key, val); - this.#size += 1; - return; - } - // 若遇到指定 key ,则更新对应 val - if (this.#buckets[j].key === key) { - this.#buckets[j].val = val; - return; - } + // 搜索 key 对应的桶索引 + const index = this.findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index]!.val = val; + return; } + // 若键值对不存在,则添加该键值对 + this.buckets[index] = new Pair(key, val); + this.size++; } /* 删除操作 */ remove(key: number): void { - const index = this.#hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (let i = 0; i < this.#capacity; i++) { - // 计算桶索引,越过尾部返回头部 - const j = (index + i) % this.#capacity; - // 若遇到空桶,说明无此 key ,则直接返回 - if (this.#buckets[j] === null) { - return; - } - // 若遇到指定 key ,则标记删除并返回 - if (this.#buckets[j].key === key) { - this.#buckets[j] = this.#removed; - this.#size -= 1; - return; - } + // 搜索 key 对应的桶索引 + const index = this.findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index] = this.TOMBSTONE; + this.size--; } } /* 扩容哈希表 */ - #extend(): void { + private extend(): void { // 暂存原哈希表 - const bucketsTmp = this.#buckets; + const bucketsTmp = this.buckets; // 初始化扩容后的新哈希表 - this.#capacity *= this.#extendRatio; - this.#buckets = new Array(this.#capacity).fill(null); - this.#size = 0; + this.capacity *= this.extendRatio; + this.buckets = Array(this.capacity).fill(null); + this.size = 0; // 将键值对从原哈希表搬运至新哈希表 for (const pair of bucketsTmp) { - if (pair !== null && pair.key !== this.#removed.key) { + if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } @@ -2361,11 +2389,13 @@ comments: true /* 打印哈希表 */ print(): void { - for (const pair of this.#buckets) { - if (pair !== null) { - console.log(pair.key + ' -> ' + pair.val); - } else { + for (const pair of this.buckets) { + if (pair === null) { console.log('null'); + } else if (pair === this.TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); } } } diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index c908a29c8..1f712edc3 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -308,13 +308,9 @@ comments: true for (auto kv: map) { cout << kv.first << " -> " << kv.second << endl; } - // 单独遍历键 key - for (auto kv: map) { - cout << kv.first << endl; - } - // 单独遍历值 value - for (auto kv: map) { - cout << kv.second << endl; + // 使用迭代器遍历 key->value + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; } ```