概念:散列表(Hash table,也叫哈希表),是根据关键码-值(Key-value)的形式进行存储和访问的数据结构。
散列表类似于字典,是以 键-值(Key-value) 对形式存储的数据结构,不同点在于散列表的键key是经过散列函数计算得出,我们称之为关键码,每个关键码都对应一个值,我们把这种以 关键码-值 形式存储数据的数组称为散列表。
作用:可以快速定位元素,并拿到对应的值。相比其他数据结构需要遍历元素来取值,而散列表可以直接通过关键码来找到对应的值。
操作方法:put()增加元素,remove()移除元素,get()获取元素
散列表的核心是散列函数,键名通过散列函数转换成关键码,并把值以 关键码-值 存在于散列表里。当需要查找某个值时,可以通过查找关键码快速定位目标值所在的位置,然后取值。下面用js里的数组来实现一个普通散列表:
HashTable类表示一个普通散列表(哈希表)
class HashTable{
constructor(){
this.table = [] // 散列表
}
/* 散列函数1:冲突相对比较 */
loseloseHashCode(key){ // 通过ASCII码转换生成key
let hash = 0
for (let i = 0; i< key.length; i++) {
hash += key[i].charCodeAt() // 计算key的ASCII码
}
return hash%37 // loseloseHashCode方法计算关键码的公式,即 ASCII码取余37
}
/* 新增元素 */
put(key,value){
const position = this.loseloseHashCode(key) // 元素在散列表里的地址
this.table[position] = value
}
/* 移除元素 */
remove(key){
this.table[this.loseloseHashCode(key)] = undefined
}
/* 获取元素 */
get(key){
return this.table[this.loseloseHashCode(key)]
}
}
上面散列函数有一个缺陷,也就是计算出来的关键码有可能会相同,以至于在存储过程中会产生冲突。下面介绍两个改善的方法,可以更好的避免此类问题的发生。
原理:当遇到关键码冲突时,使下标加一继续往后查找,直到找到空的地址,然后把散列值存储在该位置。
相比于普通的散列表,线性探查法实现的散列表它在添加元素时做了特殊处理,普通散列表通过查找到关键码所在的位置直接插入值,而线性探查法是查找到关键码所在位置,并判断该位置是否有值,如果有值则关键码(下标)+1继续查找下一个位置,直到查找的位置为空,然后插入值。
定义一个辅助类Node,表示新节点
class __Node{
constructor(key,value){
this.key = key
this.value = value
}
}
散列表类HashTable_Line,及散列函数
class HashTable_Line{
constructor(){
this.table = []
}
loseloseHashCode(key){
let hash = 0
for(let i=0; i
其中HashTable_Line类里包含了以下几个操作方法
插入元素
put(key,value){
const position = this.loseloseHashCode(key)
const node = new __Node(key,value)
if(this.table[position]){ // 如果该位置已经有值
let index = position // 当前查找位置的下标
while(this.table[index]!==undefined){ // 如果当前位置的值不为undefined,说明也有值
index++ // 继续查找下一个,下标加一
}
this.table[index] = node // 直到当前位置没有值,存储在该位置
} else{
this.table[position] = node // 如果没值,直接赋值
}
}
移除元素
remove(key){
const position = this.loseloseHashCode(key)
if (this.table[position]) {
let index = position
while(this.table[index].key !== key){
index++
}
this.table[index] = undefined
return true
} else {
return false
}
}
查找某个元素
get(key){
const position = this.loseloseHashCode(key)
if (this.table[position]) {
let index = position
while(this.table[index].key !== key){
index++
}
return this.table[index]
} else {
return false
}
}
原理:把散列表的值存储在一个链表里。
该方法实现的散列表存储形式是一个关键码对应一个链表,关键码相同的值依次存储在链表上,这样就避免了键-值对形式的冲突。如下为js的实现过程:
新节点辅助类Node
class _Node{
constructor(key,value){
this.key = key
this.value = value
}
}
散列表类HashTable_LinkedList,及散列函数
class HashTable_LinkedList{
constructor(){
this.table = [] // 散列表
}
loseloseHashCode(key){ // 通过ASCII码转换生成key
let hash = 0
for (let i = 0; i< key.length; i++) {
hash += key[i].charCodeAt() // 计算key的ASCII码
}
return hash%37 // loseloseHashCode方法计算关键码的公式,即 ASCII码的和 取余37
}
}
其中HashTable_LinkedList类包含了如下几个方法
新增元素
put(key,value){
const position = this.loseloseHashCode(key) // 元素在散列表中的存储地址
const l = new LinkedList()
const node = new _Node(key,value)
if(!this.table[position]){ // 还没有链表(此位置还没有值)
this.table[position] = l
l.append(node) // 链表的append方法新增元素
}
else{ // 已存在链表
this.table[position].append(node)
}
}
移除元素
remove(key){
const position = this.loseloseHashCode(key)
if (this.table[position]) { // 已存在链表
let current = this.table[position].head // 链表
while(current){
if(current.element.key == key){
this.table[position].remove(current.element) //调用链表的remove方法删除元素
if(this.table[position].isEmpty()){
this.table[position] = undefined // 如果该链表为空,则设置此散列值为undefined,释放内存
}
return true
}
current = current.next
}
} else {
return false // 该元素不存在
}
}
获取元素
get(key){
const position = this.loseloseHashCode(key)
if(this.table[position]){
let current = this.table[position].head
while(current){
if(current.element.key == key){
return current.element.value
}
current = current.next
}
}else{
return undefined
}
}
完整代码
更多数据结构相关,请查看专栏:《JavaScript数据结构与算法》