JS数据结构(8)—— 哈希表

JS数据结构(8)—— 哈希表

1.什么是哈希表?

哈希表的复杂之处,就是它不好理解。它的结构就是数组,但是它神奇的地方在于对下标值的一种变换,这种变换我们可以称之为哈希函数,通过哈希函数可以获取到HashCode
哈希表是一种存储键值对的数据结构,是基于数组来实现的,哈希表内部是使用一个哈希函数把值转换成一个数字,而这个数字作为键值对的key,通过这个key来完成查询,插入,修改等操作。

2.哈希表的一些概念

哈希化:将大数字转化成数组范围内下标的过程。
哈希函数:通常我们会将单词转换成大数字以及大数字进行哈希化,这两个步骤会封装一个函数,这个函数就是哈希函数
哈希表:最终将数据插入到的这个数组, 对整个结构的封装,就是一个哈希表。

3.解决冲突
3.1什么是冲突

比如有7个0~100的数字,我们使用10个位置通过哈希表来存储,但是依然可能发生冲突。即两个单词通过哈希函数得到数组的下标值后,发现那个位置上已经存在单词了,这就是冲突。

3.2解决冲突

冲突是不可避免的,我们只能解决冲突。
两种方案:
方案一:链地址法
链地址法解决冲突
链地址法解决冲突的办法是每个数组单元中存储的不再是单个数据,而是一个链表或者数组。
一旦发现冲突,则将冲突玄素插入到链表的首端或末端(数组的末端)。
当查询时,先根据哈希化后的下标值找到对应的位置,再取出链表(数组),一次查询要寻找的数据。
方案二:开放地址法
开放地址法的主要工作方式是寻找空白的单元格来添加重发的数据
那么如何来寻找空白的单元格呢,又有三种方法:
(1)线性探测:线性的查找空白的单元
插入:如果插入元素时发现有冲突,则从冲突的位置后面的位置开始一个一个往后查找,直到找到空白的位置来存放该元素。
查询:查询某个元素时,先用哈希化得到该元素的下标值,然后在哈希表中查找该下标值所对应的数据,如果和查询的元素相同则直接返回,若不相同,则开始线性查找,直到查找到或者直到碰到空位置,则停止。
删除:删除操作和插入查询很相似,但是有一个特别的注意点:删除一个数据时,不可以把这个位置的下标的内容设置为null,因为如果设置为null的话,可能会影响我们之后再查询其他的操作,所以通常删除一个位置的数据项时,我们可以将它进行特殊处理(设置为-1)
线性探测的问题:聚集(一连串填充单元)会影响哈希表的性能。
而二次探测可以解决一部分这个问题。
(2)二次探测:主要优化的是探测时的步长
二次探测队步长做了优化,比如从下标值x开始,x+1×1,x+2×2,x+3×3...
这样就可以一次性探测比较长的距离,避免那些聚集带来的影响
二次探测存在的问题:
比如连续插入的是2,12,32,42,52,62,那么它们依次累加的步长是相同的。
会造成不唱不一的一种聚集,还是会影响效率。
而再哈希法可以根本上解决这个问题。
(3)再哈希法:把关键字用另外一个哈希函数,再做一次哈希化,而这个哈希化的结果作为步长

第二次哈希化的特点:

  • 和第一个哈希函数不同
  • 不能输出为0

stepSize = constant - (key % constant)
这就是第二次哈希函数的计算步长公式,其中constant是质数,且小于数组的容量。
在开发过程中,经常使用链地址法,链地址法效率更高一些。

4.哈希表与数组的比较

哈希表通常是基于数组进行实现的,但是相对于数组,它有很多优势:

  • 它可以提供非常快速的插入-删除-查找操作
  • 无论有多少数据,插入和删除值需要接近常量的时间:即 O(1)的时间级。实际上,只需要几个机器指令即可完成。

哈希表相对于数组的一些缺点:

  • 哈希表中的数据是没有顺序的,所以不能以一种固定的方式来遍历其中的元素
  • 通常情况下,哈希表中的key是不允许重复的,不能放置相同的key,用于保存不同的元素
5.哈希表的封装
5.1代码思路

(1)我们在这里采用链地址法来实现(用数组)
(2)我们需要三个属性:

  • storage:数组,数组中存放相关的元素
  • count:表示当前已经存在了多少数据
  • limit:用于标记数组中一共可以存放多少个元素

(3)哈希表的常见方法:

  • 哈希函数(hashFunc):将字符串转换成比较大的数字:hashCode,并且将大的数字hashCode压缩到数组范围之内
  • 插入/修改(put):传入一个<key,value>时,如果原本不存在key,则插入,否则,则修改
  • 获取(get):传入key,如果能找到相同的key则返回对应的value,否则,返回null
  • 删除(delete):传入key,如果能找到相同的key则删除这个bucket中对应的key和value,否则返回null
  • 其他方法:判断哈希表是否为空,获取哈希表中元素个数
5.2代码实现
function HashTable() {
  // 属性
  this.storage = [];
  this.count = 0;
  this.limit = 7;
  // 方法
  // 哈希函数
  HashTable.prototype.hashFunc = function(str, size) {
    // 定义 hashCode 变量
    var hashCode = 0;
    // 根据霍纳算法,计算 hashCode 的值
    for(var i = 0; i < str.length; i++) {
      hashCode = 37 * hashCode + str.charCodeAt(i);
    }
    // 取余操作
    var index = hashCode % size;
    return index;
  }
  // 插入和修改操作
  HashTable.prototype.put = function(key, value) {
    // 根据key获取索引值
    var index = this.hashFunc(key.toString(), this.limit);
    // 根据索引值取出bucket(数组单元中存储的数组)
    var bucket = this.storage[index];
    // ----如果bucket不存在,创建并且放置在该索引的位置
    if(bucket == null){
      bucket = [];
      this.storage[index] = bucket;
    }
    // 判断是新增还是修改(如果已经有值了,那么修改值,否则,添加)
    for(var i = 0; i < bucket.length; i++){
      var tuple = bucket[i];
      if(tuple[0] == key){
        tuple[1] = value;
        return;
      }
    }
    // 添加操作
    bucket.push([key,value]);
    this.count ++;
  }

  // 获取操作
  HashTable.prototype.get = function(key) {
    // 根据key获取对应的index
    var index = this.hashFunc(key.toString(), this.limit);
    // 根据index获取对应的bucket
    var bucket = this.storage[index];
    // 判断bucket是否为null,如果为null,直接返回null
    if(bucket == null)
      return null;
    // 线性查找bucket中的每个key是否等于传入的key
    for(var i = 0; i < bucket.length; i++) {
      var tuple = bucket[i];
      if(tuple[0] == key)
        return tuple[1];
    }
    return null;
  }
  
  // 删除操作
  HashTable.prototype.delete = function(key) {
    // 根据key获取对应的index
    var index = this.hashFunc(key.toString(), this.limit)
    // 根据index获取对应的bucket
    var bucket = this.storage[index];
    // 判断bucket是否为null,如果为null,返回false
    if(bucket == null)  return nul;
    // 线性查找bucket中的每个key是否等于传入的key
    for(var i = 0; i < bucket.length; i++){
      var tuple = bucket[i];
      if(tuple[0] == key){
        bucket.splice(i,1);
        this.count --;
        return tuple[1];
      }
    }
    return null;
  }
  
  // 判断哈希表是否为空
  HashTable.prototype.isEmpty = function() {
    return this.count == 0;
  }
  // 获取哈希表中元素的个数
  HashTable.prototype.size = function() {
    return this.count;
  }
}

// 测试哈希表:
var hashTable = new HashTable();

// 插入
hashTable.put('name', 'msy');
hashTable.put('age', 18);
hashTable.put('address','shanxi');
hashTable.put('school', 'xian')
console.log(hashTable.storage);
/*结果为:
[
  <2 empty items>,
  [ [ 'age', 18 ] ],
  [ [ 'school', 'xian' ] ],
  <1 empty item>,
  [ [ 'name', 'msy' ] ],
  [ [ 'address', 'shanxi' ] ]
]
*/

// 获取
console.log(hashTable.get('name'));  // 结果为:msy

// 修改
hashTable.put('name', 'ppp');
console.log(hashTable.get('name'));  // 结果为:ppp

// 删除
hashTable.delete('school');
console.log(hashTable.get('school')); // 结果为:null
console.log(hashTable.storage);
/*
结果为:[
  <2 empty items>,
  [ [ 'age', 18 ] ],
  [],
  <1 empty item>,
  [ [ 'name', 'ppp' ] ],
  [ [ 'address', 'shanxi' ] ]
]
*/

(如果有什么错误,欢迎大佬在评论区指正,thank you~)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值