JS数据结构与算法 —— 散列表(哈希表)

概念:散列表(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<key.length; i++){
            hash += key[i].charCodeAt()
        }
        return hash%37
    }
}

其中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数据结构与算法》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值