diff --git a/codes/javascript/chapter_hashing/array_hash_map.js b/codes/javascript/chapter_hashing/array_hash_map.js index 270825e8a5..cb0a9bd2f0 100644 --- a/codes/javascript/chapter_hashing/array_hash_map.js +++ b/codes/javascript/chapter_hashing/array_hash_map.js @@ -62,7 +62,7 @@ class ArrayHashMap { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { - arr.push(this.#buckets[i]?.key); + arr.push(this.#buckets[i].key); } } return arr; @@ -73,7 +73,7 @@ class ArrayHashMap { let arr = []; for (let i = 0; i < this.#buckets.length; i++) { if (this.#buckets[i]) { - arr.push(this.#buckets[i]?.val); + arr.push(this.#buckets[i].val); } } return arr; diff --git a/codes/javascript/chapter_hashing/hash_map_chaining.js b/codes/javascript/chapter_hashing/hash_map_chaining.js new file mode 100644 index 0000000000..7d4bd79c91 --- /dev/null +++ b/codes/javascript/chapter_hashing/hash_map_chaining.js @@ -0,0 +1,142 @@ +/** + * File: hash_map_chaining.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 键值对 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 链式地址哈希表 */ +class HashMapChaining { + #size; // 键值对数量 + #capacity; // 哈希表容量 + #loadThres; // 触发扩容的负载因子阈值 + #extendRatio; // 扩容倍数 + #buckets; // 桶数组 + + /* 构造方法 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 哈希函数 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 查询操作 */ + get(key) { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若找到 key 则返回对应 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key 则返回 null + return null; + } + + /* 添加操作 */ + put(key, val) { + // 当负载因子超过阈值时,执行扩容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 删除操作 */ + remove(key) { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 遍历桶,从中删除键值对 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.size--; + break; + } + } + } + + /* 扩容哈希表 */ + #extend() { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print() { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化哈希表 */ +const map = new HashMapChaining(); + +/* 添加操作 */ +// 在哈希表中添加键值对 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小啰'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +map.print(); + +/* 查询操作 */ +// 向哈希表输入键 key ,得到值 value +const name = map.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +/* 删除操作 */ +// 在哈希表中删除键值对 (key, value) +map.remove(12836); +console.log('\n删除 12836 后,哈希表为\nKey -> Value'); +map.print(); diff --git a/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/codes/javascript/chapter_hashing/hash_map_open_addressing.js new file mode 100644 index 0000000000..9d03a84d5a --- /dev/null +++ b/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -0,0 +1,162 @@ +/** + * File: hash_map_open_addressing.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 键值对 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + #size; // 键值对数量 + #capacity; // 哈希表容量 + #loadThres; // 触发扩容的负载因子阈值 + #extendRatio; // 扩容倍数 + #buckets; // 桶数组 + #removed; // 删除标记 + + /* 构造方法 */ + 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'); + } + + /* 哈希函数 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 查询操作 */ + 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; + } + return null; + } + + /* 添加操作 */ + put(key, val) { + // 当负载因子超过阈值时,执行扩容 + 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; + } + } + } + + /* 删除操作 */ + 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; + } + } + } + + /* 扩容哈希表 */ + #extend() { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const pair of bucketsTmp) { + if (pair !== null && pair.key !== this.#removed.key) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print() { + for (const pair of this.#buckets) { + if (pair !== null) { + console.log(pair.key + ' -> ' + pair.val); + } else { + console.log('null'); + } + } + } +} + +/* Driver Code */ +/* 初始化哈希表 */ +let map = new HashMapOpenAddressing(); + +/* 添加操作 */ +// 在哈希表中添加键值对 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小啰'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +map.print(); + +/* 查询操作 */ +// 向哈希表输入键 key ,得到值 value +const name = map.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +/* 删除操作 */ +// 在哈希表中删除键值对 (key, value) +map.remove(16750); +console.log('\n删除 16750 后,哈希表为\nKey -> Value'); +map.print(); diff --git a/codes/javascript/chapter_hashing/simple_hash.js b/codes/javascript/chapter_hashing/simple_hash.js new file mode 100644 index 0000000000..5c2a654093 --- /dev/null +++ b/codes/javascript/chapter_hashing/simple_hash.js @@ -0,0 +1,60 @@ +/** + * File: simple_hash.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法哈希 */ +function addHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法哈希 */ +function mulHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 异或哈希 */ +function xorHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash & MODULUS; +} + +/* 旋转哈希 */ +function rotHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 算法'; + +let hash = addHash(key); +console.log('加法哈希值为 ' + hash); + +hash = mulHash(key); +console.log('乘法哈希值为 ' + hash); + +hash = xorHash(key); +console.log('异或哈希值为 ' + hash); + +hash = rotHash(key); +console.log('旋转哈希值为 ' + hash); diff --git a/codes/typescript/chapter_hashing/array_hash_map.ts b/codes/typescript/chapter_hashing/array_hash_map.ts index a039ea9358..eba8e69be1 100644 --- a/codes/typescript/chapter_hashing/array_hash_map.ts +++ b/codes/typescript/chapter_hashing/array_hash_map.ts @@ -66,7 +66,7 @@ class ArrayHashMap { let arr: (number | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { - arr.push(this.buckets[i]?.key); + arr.push(this.buckets[i].key); } } return arr; @@ -77,7 +77,7 @@ class ArrayHashMap { let arr: (string | undefined)[] = []; for (let i = 0; i < this.buckets.length; i++) { if (this.buckets[i]) { - arr.push(this.buckets[i]?.val); + arr.push(this.buckets[i].val); } } return arr; diff --git a/codes/typescript/chapter_hashing/hash_map_chaining.ts b/codes/typescript/chapter_hashing/hash_map_chaining.ts new file mode 100644 index 0000000000..3da27403e3 --- /dev/null +++ b/codes/typescript/chapter_hashing/hash_map_chaining.ts @@ -0,0 +1,146 @@ +/** + * File: hash_map_chaining.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 键值对 Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 链式地址哈希表 */ +class HashMapChaining { + #size: number; // 键值对数量 + #capacity: number; // 哈希表容量 + #loadThres: number; // 触发扩容的负载因子阈值 + #extendRatio: number; // 扩容倍数 + #buckets: Pair[][]; // 桶数组 + + /* 构造方法 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 哈希函数 */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* 查询操作 */ + get(key: number): string | null { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若找到 key 则返回对应 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key 则返回 null + return null; + } + + /* 添加操作 */ + put(key: number, val: string): void { + // 当负载因子超过阈值时,执行扩容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 删除操作 */ + remove(key: number): void { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 遍历桶,从中删除键值对 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 扩容哈希表 */ + #extend(): void { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print(): void { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化哈希表 */ +const map = new HashMapChaining(); + +/* 添加操作 */ +// 在哈希表中添加键值对 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小啰'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +map.print(); + +/* 查询操作 */ +// 向哈希表输入键 key ,得到值 value +const name = map.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +/* 删除操作 */ +// 在哈希表中删除键值对 (key, value) +map.remove(12836); +console.log('\n删除 12836 后,哈希表为\nKey -> Value'); +map.print(); + +export {}; diff --git a/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/codes/typescript/chapter_hashing/hash_map_open_addressing.ts new file mode 100644 index 0000000000..adca37ce1b --- /dev/null +++ b/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -0,0 +1,166 @@ +/** + * File: hash_map_open_addressing.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 键值对 Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + #size: number; // 键值对数量 + #capacity: number; // 哈希表容量 + #loadThres: number; // 触发扩容的负载因子阈值 + #extendRatio: number; // 扩容倍数 + #buckets: Pair[]; // 桶数组 + #removed: 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'); + } + + /* 哈希函数 */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* 负载因子 */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* 查询操作 */ + 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; + } + return null; + } + + /* 添加操作 */ + put(key: number, val: string): void { + // 当负载因子超过阈值时,执行扩容 + 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; + } + } + } + + /* 删除操作 */ + 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; + } + } + } + + /* 扩容哈希表 */ + #extend(): void { + // 暂存原哈希表 + const bucketsTmp = this.#buckets; + // 初始化扩容后的新哈希表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null); + this.#size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (const pair of bucketsTmp) { + if (pair !== null && pair.key !== this.#removed.key) { + this.put(pair.key, pair.val); + } + } + } + + /* 打印哈希表 */ + print(): void { + for (const pair of this.#buckets) { + if (pair !== null) { + console.log(pair.key + ' -> ' + pair.val); + } else { + console.log('null'); + } + } + } +} + +/* Driver Code */ +/* 初始化哈希表 */ +let map = new HashMapOpenAddressing(); + +/* 添加操作 */ +// 在哈希表中添加键值对 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小啰'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鸭'); +console.log('\n添加完成后,哈希表为\nKey -> Value'); +map.print(); + +/* 查询操作 */ +// 向哈希表输入键 key ,得到值 value +const name = map.get(13276); +console.log('\n输入学号 13276 ,查询到姓名 ' + name); + +/* 删除操作 */ +// 在哈希表中删除键值对 (key, value) +map.remove(16750); +console.log('\n删除 16750 后,哈希表为\nKey -> Value'); +map.print(); + +export {}; diff --git a/codes/typescript/chapter_hashing/simple_hash.ts b/codes/typescript/chapter_hashing/simple_hash.ts new file mode 100644 index 0000000000..9d2be67c18 --- /dev/null +++ b/codes/typescript/chapter_hashing/simple_hash.ts @@ -0,0 +1,60 @@ +/** + * File: simple_hash.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法哈希 */ +function addHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法哈希 */ +function mulHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 异或哈希 */ +function xorHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash & MODULUS; +} + +/* 旋转哈希 */ +function rotHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 算法'; + +let hash = addHash(key); +console.log('加法哈希值为 ' + hash); + +hash = mulHash(key); +console.log('乘法哈希值为 ' + hash); + +hash = xorHash(key); +console.log('异或哈希值为 ' + hash); + +hash = rotHash(key); +console.log('旋转哈希值为 ' + hash);