diff --git a/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/codes/javascript/chapter_hashing/hash_map_open_addressing.js index b03b6744d..e9a0a3a57 100644 --- a/codes/javascript/chapter_hashing/hash_map_open_addressing.js +++ b/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -1,7 +1,7 @@ /** - * File: hash_map_open_addressing.js - * Created Time: 2023-08-06 - * Author: yuan0221 (yl1452491917@gmail.com) + * File: hashMapOpenAddressing.js + * Created Time: 2023-06-13 + * Author: yuan0221 (yl1452491917@gmail.com), Krahets (krahets@163.com) */ /* 键值对 Number -> String */ @@ -19,16 +19,16 @@ class HashMapOpenAddressing { #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'); // 删除标记 } /* 哈希函数 */ @@ -41,22 +41,48 @@ class HashMapOpenAddressing { 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; } @@ -66,45 +92,32 @@ class HashMapOpenAddressing { 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--; } } @@ -114,11 +127,11 @@ class HashMapOpenAddressing { 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); } } @@ -127,36 +140,38 @@ class HashMapOpenAddressing { /* 打印哈希表 */ 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); } } } } /* Driver Code */ -/* 初始化哈希表 */ -let map = new HashMapOpenAddressing(); +// 初始化哈希表 +const hashmap = new HashMapOpenAddressing(); -/* 添加操作 */ -// 在哈希表中添加键值对 (key, value) -map.put(12836, '小哈'); -map.put(15937, '小啰'); -map.put(16750, '小算'); -map.put(13276, '小法'); -map.put(10583, '小鸭'); +// 添加操作 +// 在哈希表中添加键值对 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小啰'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鸭'); console.log('\n添加完成后,哈希表为\nKey -> Value'); -map.print(); +hashmap.print(); -/* 查询操作 */ -// 向哈希表输入键 key ,得到值 value -const name = map.get(13276); +// 查询操作 +// 向哈希表输入键 key ,得到值 val +const name = hashmap.get(13276); console.log('\n输入学号 13276 ,查询到姓名 ' + name); -/* 删除操作 */ -// 在哈希表中删除键值对 (key, value) -map.remove(16750); +// 删除操作 +// 在哈希表中删除键值对 (key, val) +hashmap.remove(16750); console.log('\n删除 16750 后,哈希表为\nKey -> Value'); -map.print(); +hashmap.print(); diff --git a/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/codes/typescript/chapter_hashing/hash_map_open_addressing.ts index 40fe5552b..876f99190 100644 --- a/codes/typescript/chapter_hashing/hash_map_open_addressing.ts +++ b/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -1,13 +1,14 @@ /** * File: hash_map_open_addressing.ts * Created Time: 2023-08-06 - * Author: yuan0221 (yl1452491917@gmail.com) + * Author: yuan0221 (yl1452491917@gmail.com), Krahets (krahets@163.com) */ /* 键值对 Number -> String */ class Pair { key: number; val: string; + constructor(key: number, val: string) { this.key = key; this.val = val; @@ -16,111 +17,124 @@ class Pair { /* 开放寻址哈希表 */ 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); } } @@ -128,39 +142,41 @@ class HashMapOpenAddressing { /* 打印哈希表 */ 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); } } } } /* Driver Code */ -/* 初始化哈希表 */ -let map = new HashMapOpenAddressing(); - -/* 添加操作 */ -// 在哈希表中添加键值对 (key, value) -map.put(12836, '小哈'); -map.put(15937, '小啰'); -map.put(16750, '小算'); -map.put(13276, '小法'); -map.put(10583, '小鸭'); +// 初始化哈希表 +const hashmap = new HashMapOpenAddressing(); + +// 添加操作 +// 在哈希表中添加键值对 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小啰'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鸭'); console.log('\n添加完成后,哈希表为\nKey -> Value'); -map.print(); +hashmap.print(); -/* 查询操作 */ -// 向哈希表输入键 key ,得到值 value -const name = map.get(13276); +// 查询操作 +// 向哈希表输入键 key ,得到值 val +const name = hashmap.get(13276); console.log('\n输入学号 13276 ,查询到姓名 ' + name); -/* 删除操作 */ -// 在哈希表中删除键值对 (key, value) -map.remove(16750); +// 删除操作 +// 在哈希表中删除键值对 (key, val) +hashmap.remove(16750); console.log('\n删除 16750 后,哈希表为\nKey -> Value'); -map.print(); +hashmap.print(); export {};