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,用除留余数法定义的散列函数将关键字映射 ...
随机推荐
- PAT Advanced 1097 Deduplication on a Linked List (25) [链表]
题目 Given a singly linked list L with integer keys, you are supposed to remove the nodes with duplica ...
- java 利用管道实现线程间通信
package com.lb; import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutpu ...
- Java web之javascript(2020.1.6)
1.js输出: windows.alert()---警告框 document.write()---写到html文档中 innerHTML---写到HTML元素 console.log()---写到浏览 ...
- C++对象的动态建立与释放
new运算符动态分配堆内存 (从堆分配一块“类型”大小的存储空间,并且返回首地址) 1.写程序的时候,经常需要动态的分配和撤销内存空间,C语言中常常是利用库函数 malloc 和 free 来分配和 ...
- Openstack 使用Centos官方镜像创建实例记录
Openstack 使用Centos官方镜像创建实例记录 准备centos镜像 官方地址:http://cloud.centos.org/centos/7/images 可以看到有各种版本的镜像,我在 ...
- restful的简单使用
根据http的不同方法,访问不同路由的相同控制器下的不同方法可以实现restful的使用 分别对应 路由方式 get put delete post 对应操作 获取 更新 删除 添加 其中如果要在非l ...
- 四、Shell脚本高级编程实战第四部
一.比较两个数的大小 #!/bin/shread -p "Pls input two num:" a b[ -z "$a" ] || [ -z "$b ...
- C++ lock_guard 互斥锁
概述根据对象的析构函数自动调用的原理,c++11推出了std::lock_guard自动释放锁,其原理是:声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁.最终的 ...
- soap,restful 两种web service实现方式比较
web service服务 目前常用的实现web service的方式有有两种 1.SOAP 原始的web service标准,一堆标准,不过这些标准是在开发框架中实现的,有上层接口,可以调用 2.R ...
- Qt 信息提示框 QMessageBox
information QMessageBox::information(NULL, "Title","Content",QMessageBox::Yes | ...