js 实现数据结构 -- 散列(HashTable)
原文:
概念:
HashTable 类, 也叫 HashMap 类,是 Dictionary 类的一种散列表实现方式。
散列算法的作用是尽可能快地在数据结构中定位到某个值,如之前的一些数据结构中(说的就是 LinkedList),需要遍历数据结构才能得到,如果使用散列函数,就能知道值的具体位置,因此就能快速检索到该值,单列函数的作用是给定一个键值(位置),然后返回值在表中的地址。
例如下例,我们通过使用 'lose lose' 散列函数(简单将每个键值中的每个字母 ASCII 值相加),获得一个散列表:

使用 js 实现如下:
class HashTable {
constructor() {
this.table = []; // 数组形式存储
}
// 散列运算函数,可自定义
// 此处时最常见的散列函数 ‘lose lose’
static loseloseHashCode(key) {
let hash = 0;
for (let codePoint
key) {
hash += codePoint.charCodeAt();
}
return hash % 37;
}
// 修改和增加元素
put(key, value) {
const position = HashTable.loseloseHashCode(key);
console.log(`${position} - ${key}`);
this.table[position] = value;
}
get(key) {
return this.table[HashTable.loseloseHashCode(key)];
}
remove(key) {
this.table[HashTable.loseloseHashCode(key)] = undefined;
}
}
const hash = new HashTable();
hash.put('Surmon', 'surmon.me@email.com') // 15 - Surmon
hash.put('Jhon', 'Jhonsnow@email.com') // 29 - Jhon
hash.put('Tyrion', 'Tyrion@email.com') // 16 - Tyrion
console.log(hash.get('Surmon')); // surmon.me@email.com
console.log(hash.get('Loiane')); // undefined
console.log(hash)
除散列表之外,还有散列映射和散列几何等散列结构。散列映射与散列表是一样的,而散列集合在插入、移除和获取元素的时候不再添加键值对,而是使用散列函数来代替 key。 和 集合相似,散列集合只存储唯一的不重复的值。
处理冲突:
散列表中有个头疼的问题是当一些键具有相同的散列值的时候,该如何处理这些冲突?比如:
const hash = new HashTable()
hash.put('Gandalf', 'gandalf@email.com')
hash.put('John', 'johnsnow®email.com')
hash.put('Tyrion', 'tyrion@email.com')
hash.put('Aaron', 'aaronOemail.com')
hash.put('Donnie', 'donnie@email.com')
hash.put('Ana', 'ana©email.com')
hash.put('Jonathan', 'jonathan@email.com')
hash.put('Jamie', 'jamie@email.com')
hash.put('Sue', 'sueOemail.com')
hash.put('Mindy', 'mindy@email.com')
hash.put('Paul', 'paul©email.com')
hash.put('Nathan', 'nathan@email.com')
栗子中,Tyrion 和 Aaron 有相同的散列值(16),Donnie 和 Ana 有相同的散列值(13),Jonathan、Jamie 和 Sue 有相同的散列值(5), Mindy 和 Paul 也有相同的散列值(32),导致最终的数据对象中,只有最后一次被添加/修改的数据会覆盖原本数据,进而生效。
处理冲突的几种方法:分离链接、线性查探和双散列法。下面是前两种方法:
分离链接:
分离链接的实质是在散列表的每一个位置创建一个链表并将元素存储,是解决冲入的最简单的方法,但是会在 HashTable 实例之外创建额外的存储空间。其示意图如下:

为了实现上述的分离链接,我们只需引入 LinkedList 类并修改 put、get 和 remove 这三个方法即可:
put(key, value) {
const position = HashTable.loseloseHashCode(key)
if (this.table[position] === undefined) {
this.table[position] = new LinkedList()
}
this.table[position].append({ key, value })
}
get(key) {
const position = HashTable.loseloseHashCode(key)
if (this.table[position] === undefined) return undefined
const getElementValue = node => {
if (!node && !node.element) return undefined
if (Object.is(node.element.key, key)) {
return node.element.value
} else {
return getElementValue(node.next)
}
}
return getElementValue(this.table[position].head)
}
remove(key) {
const position = HashTable.loseloseHashCode(key)
if (this.table[position] === undefined) return undefined
const getElementValue = node => {
if (!node && !node.element) return false
if (Object.is(node.element.key, key)) {
this.table[position].remove(node.element)
if (this.table[position].isEmpty) {
this.table[position] = undefined
}
return true
} else {
return getElementValue(node.next)
}
}
return getElementValue(this.table[position].head)
}
线性查探
线性查探的基本思路是,在插入元素时,如果索引为 index 的位置已经被占据,就尝试 index + 1 的位置,如果 index + 1 的位置也被占据,则尝试 index + 2 的位置,以此类推。如下图:

同样,线性查探只需要修改 put、get、remove 方法,不需要使用额外空间:
put(key, value) {
const position = HashTable.loseloseHashCode(key)
if (this.table[position] === undefined) {
this.table[position] = { key, value }
} else {
let index = ++position
while (this.table[index] !== undefined) {
index++
}
this.table[index] = { key, value }
}
this.table[position].append({ key, value }) // 这句没理解什么意思
}
get(key) {
const position = HashTable.loseloseHashCode(key)
const getElementValue = index => {
if (this.table[index] === undefined) return undefined
if (Object.is(this.table[index].key, key)) { // 再根据 key 值是否相等判断是否是需要的元素
return this.table[index].value
} else {
return getElementValue(index + 1)
}
}
return getElementValue(position)
}
remove(key) {
const position = HashTable.loseloseHashCode(key)
const removeElementValue = index => {
if (this.table[index] === undefined) return false
if (Object.is(this.table[index].key, key)) {
this.table[index] = undefined
return true
} else {
return removeElementValue(index + 1)
}
}
return removeElementValue(position)
}
从上例中我们可以看出,优化 HashTable 的要点在于散列函数。'lose lose' 散列函数并不是一个表现良好的散列函数,而网上也有其他的散列函数:djb2、sdbm.... 或者页可以实现自己的散列函数。下面是吸能良好的 djb2 函数:
static djb2HashCode(key) {
let hash = 5381
for (let codePoint of key) {
hash = hash * 33 + codePoint.charCodeAt()
}
return hash % 1013
}
js 实现数据结构 -- 散列(HashTable)的更多相关文章
- 数据结构和算法 – 7.散列和 Hashtable 类
7.1.散列函数 散列是一种常见的存储数据的技术,按照这种方式可以非常迅速地插入和取回数据.散列所采用的数据结构被称为是散列表.尽管散列表提供了快速地插入.删除.以及取回数据的操作,但是诸如查找最大值 ...
- js数据结构之hash散列的详细实现方法
hash散列中需要确定key和value的唯一确定关系. hash散列便于快速的插入删除和修改,不便于查找最大值等其他操作 以下为字符和数字的hash散列: function HashTable () ...
- javascript数据结构与算法--散列
一:javascript数据结构与算法--散列 一:什么是哈希表? 哈希表也叫散列表,是根据关键码值(key,value)而直接进行访问的数据结构,它是通过键码值映射到表中一个位置来访问记录的,散列 ...
- 算法与数据结构(十二) 散列(哈希)表的创建与查找(Swift版)
散列表又称为哈希表(Hash Table), 是为了方便查找而生的数据结构.关于散列的表的解释,我想引用维基百科上的解释,如下所示: 散列表(Hash table,也叫哈希表),是根据键(Key)而直 ...
- JavaScript数据结构与算法-散列练习
散列的实现 // 散列类 - 线性探测法 function HashTable () { this.table = new Array(137); this.values = []; this.sim ...
- JS中数据结构之散列表
散列是一种常用的数据存储技术,散列后的数据可以快速地插入或取用.散列使用的数据 结构叫做散列表.在散列表上插入.删除和取用数据都非常快. 下面的散列表是基于数组进行设计的,数组的长度是预先设定的,如有 ...
- 《数据结构与算法分析——C语言描述》ADT实现(NO.05) : 散列(Hash)
散列(Hash)是一种以常数复杂度实现查找功能的数据结构.它将一个关键词Key,通过某种映射(哈希函数)转化成索引值直接定位到相应位置. 实现散列有两个关键,一是哈希函数的选择,二是冲突的处理. 对于 ...
- PTA数据结构与算法题目集(中文) 7-43字符串关键字的散列映射 (25 分)
PTA数据结构与算法题目集(中文) 7-43字符串关键字的散列映射 (25 分) 7-43 字符串关键字的散列映射 (25 分) 给定一系列由大写英文字母组成的字符串关键字和素数P,用移位法定义 ...
- PTA数据结构与算法题目集(中文) 7-42整型关键字的散列映射 (25 分)
PTA数据结构与算法题目集(中文) 7-42整型关键字的散列映射 (25 分) 7-42 整型关键字的散列映射 (25 分) 给定一系列整型关键字和素数P,用除留余数法定义的散列函数将关键字映射 ...
随机推荐
- 小白学习之pytorch框架(5)-多层感知机(MLP)-(tensor、variable、计算图、ReLU()、sigmoid()、tanh())
先记录一下一开始学习torch时未曾记录(也未好好弄懂哈)导致又忘记了的tensor.variable.计算图 计算图 计算图直白的来说,就是数学公式(也叫模型)用图表示,这个图即计算图.借用 htt ...
- python图像处理常用方法
在线标注网站 https://gitlab.com/vgg/via http://www.robots.ox.ac.uk/~vgg/software/via/via.html 数组与图像互转 from ...
- HTTP Error 502.5 - Process Failure 解决方案
.netcore 2.1.4的程序部署到IIS后报以下错误: ======================================================= HTTP Error 50 ...
- Django_JavaScript
JavaScript是什么 JavaScript是一种运行在客户端(浏览器)的编程语言,用来给网页添加动态功能. JavaScript的作用 最初目的 为了处理表单的验证操作 现在广泛的应用场景 网页 ...
- Java的各类型数据在内存中分配情况详解
1. 有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java一族偏重于系统架构.说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出,此说法不虚,但 ...
- 数据库T-SQL语言操作(T-SQL语句、数据库、表、视图、索引)
T-SQL语言 按用途分四部分 数据定义语言(CREATE,DROP,ALTER) 数据操作语言(INSERT,DELETE,UPDATE) 数据查询语言(SELECT) 数据控制语言(GRANT,R ...
- 18)PHP,可变函数,匿名函数 变量的作用域
(1)可变函数: 可变函数,就是函数名“可变”——其实跟可变变量一样的道理. $str1 = “f1”; //只是一个字符串,内容为”f1” $v1 = $str1(3, 4); //形式上看 ...
- 怎么通过scanf读取一个空白前的字符
/************************************************************************* > File Name: scanf2.c ...
- UML-6.4-用例-准则
1.以无用户界面约束的本质风格编写用例--目标的目标(根源目标 or 意图 ,二阶思维). 比如:“登录”,脑子里不仅想到了用户名/密码/验证码这些图形,而且,更关注为啥需要登录:如“系统根据身份id ...
- [HNOI2019]JOJO(KMP自动机+主席树)
一道神仙题,考察选手对KMP的深入理解. 先考虑没有2操作的做法.设每一段为一个二元组(x,c),考虑一段前缀匹配后缀,除了第一段的字符,其他段的二元组(x,c)必须相等,所以可以将其视为特殊字符进行 ...