一:什么是哈希表?
哈希表是一种常见的数据结构,它通过将键映射到值的方式来存储和组织数据。具体来说,哈希表使用哈希函数将每个键映射到一个索引(在数组中的位置),然后将该键值对存储在该索引处。当需要检索某个键的值时,哈希表会再次使用哈希函数将该键转换为相应的索引,并在该索引处查找其关联的值。由于哈希函数通常能够较快地计算出索引,因此哈希表在插入和查找操作上具有很高的效率。
在 JavaScript 中,哈希表通常是通过对象来实现的。对象的属性名就是键,而属性值则是与之关联的值。然而,在某些情况下,如果需要更多的控制权或者需要处理大量的数据,可以使用第三方库或自己实现哈希表数据结构。
二:哈希化
哈希化是将任意长度的消息(例如一个字符串、文件或数据记录)转换为固定长度的值(通常是较小的整数),该值称为哈希值。哈希函数执行哈希化操作,它将输入键映射到唯一的索引值,这个索引值可以用来在哈希表中查找对应的值。
哈希化具有以下特点:
- 哈希化是单向的:从哈希值无法推断出原始输入值,而且很难找到两个不同的输入值生成相同的哈希值。
- 哈希化是确定性的:对于给定的输入,哈希函数总是返回相同的哈希值。这个特性使得哈希表能够高效地进行查找和插入等操作。
- 哈希化是均匀的:好的哈希函数应该将输入数据均匀地分布在哈希空间中,以避免碰撞(即两个不同的输入值生成相同的哈希值)。
总而言之
哈希化
:将大数字转化成数组范围内下标的过程,我们就称之为哈希化
哈希函数
:通常我们会将单词转成大数字,大数字在进行哈希化的代码实现放在一个函数中,这个函数我们称为哈希函数
哈希表
:最终将数据插入到的这个数组,对整个结构的封装,我们就称之为是一个哈希表
三:哈希函数
好的哈希函数应该具备:
快速的计算
:哈希表的优势就在于效率,所以快速获取到对应的hashCode非常重要。我们需要通过快速的计算来获取到元素对应的hashCode。
均匀的分布
:哈希表中,无论是链地址法还是开放地址法,当多个元素映射到同一个位置的时候,都会影响效率,所以,优秀的哈希函数应该尽可能将元素映射到不同的位置,让元素在哈希表中均匀的分布。
哈希函数设计
- 将字符串转换成比较大的数字:hashCode
- 将大的数字hashCode压缩到数组范围之内
- 传入两个参数:str 字符串;size 数组大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function hashFunc(str, size) { let hashCode = 0;
for (let i = 0; i <str.length ; i++) { hashCode = 37*hashCode + str.charCodeAt(i) } let index = hashCode % size return index }
|
四:实现哈希表(链地址法)
1.逻辑
实现的哈希表(基于storage的数组)每个index对应的是一个是数组(bucket)。
bucket中存放什么呢?我们最好将key和value都放进去,我们继续使用一个数组
最终我们的哈希表的数据格式是这样:[[[k,v],[k,v],[k,v]],[[k,v],[k,v]],[[k,v]]]
2.扩容和扩容时机
将所有的数据项放在长度为7的数组中的,因为我们使用的是链地址法,loadFactor可以大于1,所以这个哈希表可以无限制的插入新数据。但是随着数据量的增多,每一个index对应的bucket会越来越长,也就造成效率的降低。所以,需要在合适的情况对数组进行扩容。比如扩容两倍。
扩容时机
比较常见的情况是填装因子 > 0.75 的时候扩容
3.属性和方法
定义属性:
storage: 作为我们的数组,数组中存放相关的元素;
count:表示当前数组中已经存放了多少数据,用于计算填装因子的值,以便扩容等操作;
limit:用于标记数组的长度;
定义方法:
- put(key,value):新增和修改方法。哈希表的插入和修改是同一个函数,因为,当使用者传入一个<key,value>时,如果原来不存在该key,那么就是插入操作;如果已经存在该key,那么就是修改操作
- 根据传入的key获取对应的hashCode,也就是数组的index;
从哈希表的index位置中取出bucket(一个数组);
查看上一步取出的数组是否为null ,如果为null,则表示之前在该位置没有放置过任何的内容,那么就新建一个数组[];
查看是否之前已经放置过key对应的value:如果放置过,那么就是依次替换操作,而不是插入新数据;
如果不是修改操作,那么插入新的数据 :在数组中push新的[key,value]即可。注意: 这里需要将count+1,因为数据增加了一项。
最后判断是否需要扩容: loadFactor > 0.75 的时候扩容
是则加一再判断,直到是质数,则跳出返回这个质数
- get(key):获取数据方法
- 根据传入的key获取对应的hashCode,也就是数组的index;
- 根据index拿到对应的bucket;
- 判断bucket是否为null,如果是null则返回null;
- 如果不是null,则遍历bucket,判断里面是否包含key对应的数据,有则返回;
- 如果bucket中没有找到,那么返回null;
- remove(key):删除方法
- 根据传入的key获取对应的hashCode,也就是数组的index;
- 根据index拿到对应的bucket;
- 判断bucket是否存在,如果不存在则返回null;
- 如果存在则遍历bucket,找到里面包含key对应的数据,有则删除,并且数量减一,同时需要判断是否需要缩容,返回被删除的数据;
- 如果不存在则返回null
- resize(newLimit):扩容和缩容方法
- 先存储原数组
- 原属性重置
- 遍历原数组,把里面每个索引中的每个bucket中的每一个值都取出来重新添加进扩容后的数组中
- isPrime(num):判断是否为质数
- 将传入的num开平方根,然后遍历2和开平方根之后的值之间的数值,如果传入的num值可以和这中间一个值除尽,那么就跳出返回false不是质数。遍历结束返回true,是质数。
4.代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
| function HashTable () { this.storage = [] this.count = 0 this.limit = 7
HashTable.prototype.hashFunc = function (str, size) { let hashCode = 0
for (let i = 0; i < str.length; i++) { hashCode = 37 * hashCode + str.charCodeAt(i) }
let index = hashCode % size return index }
HashTable.prototype.put = function (key, value) { let index = this.hashFunc(key, this.limit) let bucket = this.storage[index] if (bucket === null || bucket === undefined) { bucket = [] this.storage[index] = bucket } for (let i = 0; i < bucket.length; i++) { let tuple = bucket[i] if (tuple[0] === key) { tuple[1] = value return } } bucket.push([key, value]) this.count++ if (this.count > this.limit * 0.75) { const newSize = this.limit * 2 const primeSize = this.getPrime(newSize) this.resize(primeSize) } } HashTable.prototype.get = function (key) { let index = this.hashFunc(key, this.limit) let bucket = this.storage[index] if (bucket === null || bucket === undefined) { return null } for (let i = 0; i < bucket.length; i++) { let tuple = bucket[i] if (tuple[0] === key) { return tuple[1] } } return null } HashTable.prototype.remove = function (key) { let index = this.hashFunc(key, this.limit) let bucket = this.storage[index] if (bucket === null || bucket === undefined) { return null } for (let i = 0; i < bucket.length; i++) { let tuple = bucket[i] if (tuple[0] === key) { bucket.splice(i, 1) this.count-- if (this.count < this.limit * 0.25) { const newSize = Math.floor(this.limit / 2) const primeSize = this.getPrime(newSize) this.resize(primeSize) } return tuple[1] } } return null
}
HashTable.prototype.isEmpyt = function () { return this.count === 0 }
HashTable.prototype.size = function () { return this.count }
HashTable.prototype.resize = function (newLimit) { const oldStorage = this.storage this.storage = [] this.limit = newLimit this.count = 0 for (let i = 0; i < oldStorage.length; i++) { const bucket = oldStorage[i] if (bucket === null || bucket === undefined) { continue } for (let j = 0; j < bucket.length; j++) { let tuple = bucket[j] this.put(tuple[0], tuple[1]) } } }
HashTable.prototype.isPrime = function (num) { const temp = parseInt(Math.sqrt(num)) for (let i = 2; i < temp; i++) { if (temp % 2 === 0) { return false } } return true }
HashTable.prototype.getPrime = function (num) { while (!this.isPrime(num)) { num++ } return num }
}
const hashTable = new HashTable() hashTable.put('abc', 18) hashTable.put('aaa', 20) hashTable.put('vvv', 1111) console.log(hashTable); console.log(hashTable.get('vvv')); hashTable.remove('aaa') console.log(hashTable);
|