终于知道哈希表是什么了!

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战算法

哈希表

几乎全部的编程语言都有直接或间接地应用哈希表这种数据结构。哈希表一般是基于数组实现的,它神奇的地方在于对下标值的一种变换,这种变换可称为哈希函数,经过哈希函数能够获取到HashCode。相对于数组,它的优点在于速度快,效率更高。编程

1、认识哈希表

  • 哈希化:将大数字转化成数组范围的下标的过程
  • 哈希函数:一般咱们会将单词转换为大数字(每个字母对应的unicode),将大数字转换为哈希化的代码的过程封装成的函数称为哈希函数
  • 哈希表:最终将数据插入到的这个数组,对整个结构的封装,就称为哈希表

哈希表中的每个key都是惟一值,并且每个key都有其对应的索引值,经过这个索引值直接在哈希表中找到这个key的value值,大大地提升了查找的效率。若是数据庞大的是否,每个索引值对应的就不止一个key,此时就会产生冲突。这个冲突常见有2种解决方式:链地址法和开放地址法。数组

在下面的封装中,采用的基于数组的链地址法。markdown

2、封装哈希表

在哈希表封装的时候,设置哈希表的总长度。通常将总长度设置为存放个数的两倍,好比有50个数据,那咱们设置这个哈希表的总长度为100。数据结构

function HashTable() {
    this.storage = [];  // 存放元素
    this.count = 0;     // 存放个数
    this.limit = 7;     // 当前哈希表的总长度
}
复制代码

哈希函数

哈希函数在哈希表中是相当重要的,几乎每个操做都须要它。它的做用在于获取某个key在哈希表中的索引值,而后经过一些计算方法来计算出它的hashCode,再对这个hashCode进行取余(hashCode的值是很大的,这里是为了让数据量变小),而这个size,就是哈希表的this.limit,即哈希表的长度。编程语言

这里的37实际上是一个底数,好比abc = 1*37^2 + 2*37^1 + 3。这里取一个合适的数就行了,不必定就要37,不过最好是质数。函数

HashTable.prototype.hashFunc = function (str, size) {
    var hashCode = 0;
    // 霍纳算法,计算hashCode的值
    for (var i = 0; i < str.length; i++) {
        hashCode = 37 * hashCode + str.charCodeAt(i);
    }
    // 取余
    var index = hashCode % size;
    return index;
}
复制代码

3、常见操做

这里使用的是链地址法(基于数组,即每个下标值对应的是一个数组)post

a、插入和修改数据

由于在哈希表中,是没有重复的值的,因此当用户传入一个(key, value)时,若是原来不存在这个key,就是插入操做;若是存在,就是修改操做。操做步骤:测试

  1. 根据key获取索引值(经过哈希算法),将数据插入到对应的位置this

    var index = this.hashFunc(key, this.limit);
    复制代码
  2. 根据索引值取出这个位置:先判断该位置是否存在一个数组,若是不存在就建立

    var bucket = this.storage[index];
    if (bucket == null) {
        bucket = [];
        this.storage[index] = bucket;
    }
    复制代码
  3. 判断是新增仍是修改原来的值 (key同样就是修改,不同就是新增)

    若是已经有这个key了,那么就修改该key的值;

    这里的bucket是哈希表中其中一个索引值的引用,存储多个key: value的数组 (这些key经过哈希函数计算出来的索引值是想经过的)

    for (var i = 0; i < bucket.length; i++) {
        var tuple = bucket[i]
        if (tuple[0] == key) {
            tuple[1] = value;
            return;
        }
    }
    复制代码
  4. 若是没有,执行后续的添加操做;注意这里push的是数组对象

    bucket.push([key, value]);  
    this.count += 1;
    复制代码

b、获取和删除操做

这里的前两个步骤跟上面的添加或修改数据是相同的。先经过哈希函数算出这个key对应的索引值,根据这个索引值找到这个key所在的数组,若是存在,再从这个数组里面找这个key对应的value。

  1. 根据key获取对应的index

  2. 根据index获取对应的位置bucket(数组)

  3. 判断bucket是否为null,若是为null直接返回null

    if (bucket == null)   return null;
    复制代码
  4. 线性查找bucket中每个key是否等于传入的key

    若是等于就返回对应的value;若是遍历结束了还没找到就返回null

    for (var i = 0; i < bucket.length; i++) {
        var tuple = bucket[i]
        if (tuple[0] == key) {
            return tuple[1];
        }
    }
    return null;
    复制代码

删除操做在这里思路是差很少的,只不过获取操做获取到这个key对应的value就返回,而删除操做时把它删除;再判断语句中加入下面两行代码对获取到的value值连带key删除掉便可。

bucket.splice(i, 1);
this.count -= 1;
复制代码

c、测试代码

其中,0, 3, 4, 5分别为转换以后的哈希表索引值,当他们的索引值相同时,就会被以key = value的形式存入到一个数组中。

var hash = new HashTable();
 hash.put('mannqo', 123);
 hash.put('mama', 222);
 hash.put('Ytao', 321);
 hash.put('fafa', 213);
 hash.put('neinei', 123);
 hash.remove('neinei');
 console.log(hash.get('neinei'));
 console.log(hash.get('mannqo'));
 console.log(hash);
复制代码

输出结果:

哈希表.png 能够看到,删除后的元素再次get的话返回的是空,并且在哈希表中也看不到了。而索引值相同的mamafafa被存放到索引值为0的数组当中。

4、哈希表扩容

随着数据量的增多,每个索引值对应的bucket(数组)长度也会愈来愈长,会致使效率的下降。这个是否就须要扩容哈希表。在合适的状况下对数组扩容能够提升效率,通常是在loadFactor(存放个数/哈希表长度)>0.75的时候进行扩容。在扩容以后,要记得对全部数据项的位置进行修改(从新调用哈希函数,获取不一样的位置),由于扩容以后每个元素的位置也会发生改变。

  1. 保存旧的数据内容 var oldStorage = this.storage;

  2. 重置全部的属性

    this.storage = [];
    this.count = 0;
    this.limit = newLimt;
    复制代码
  3. 遍历oldStorage中全部的bucket

    这里要先判断每个旧的bucket有没有东西,若是没有就继续执行程序,若是有就把它存放到新的bucket中。

    for (var i = 0; i < oldStorage.length; i++) {
        var bucket = oldStorage[i];
        if (bucket == null) {
            continue;
        }
        for (var j = 0; j < bucket.length; j++) {
            var tuple = bucket[j];
            this.put(tuple[0], tuple[1]);
        }
    }
    复制代码

判断是否须要扩容

if (this.count > this.limit * 0.75) {
    this.resize(this.limit * 2);
}
复制代码

在删除的元素过多的是否,也能够对哈希表进行缩小容量,能够把下面这段判断语句加到删除操做中。

if (this.limit > 7 && this.count < this.limit * 0.25) {
    this.resize(Math.floor(this.limit / 2))
}
复制代码

5、总结

  • 哈希表的做用是十分强大的,最突出的特色在于它查找速度之快。每个key都会有它所对应的索引值,根据这个索引值能够很是快的找到它所在的位置。在这个位置可能不止一个元素(冲突不可避免),可是冲突的元素通常都很少,效率仍是很高滴。
  • 不过哈希表仍是有它的不足,在哈希表中,数据是没有顺序的,并且哈希表中的key是不容许重复的,每个key都对应着一个value值,用于保存不一样的元素。不一样放置相同的key。
相关文章
相关标签/搜索