哈希表
数组的特点(补充)
- 数组进行插入时,效率较低
- 数组进行查找操作的效率
- 如果是基于索引进行查找效率非常高
- 基于内容查找,效率较低
- 数组进行删除操作,效率较低
哈希表的特点
- 哈希表通常是基于数组进行实现的
- 优点
- 提供非常快速的插入-删除-查找操作
- 无论数据多少,插入和删除的效率都非常高(即 O(1)的时间级)
- 哈希表的速度比树快,几乎瞬间可找到
- 代码相对树简单
- 缺点
- 数据没有顺序,不能以一种固定的方式来遍历其中的元素
- 哈希表的 key 是不允许重复的,不能放置相同的 key,用于保存不同的数据
探测方法
- 线性探测(缺点:连续的一串数字[聚集],导致性能增大)
- 二次探测(缺点:对于 32-12-42-2,也会导致性能增大)[探测步长是固定的:1,4,9,16…]
- 再哈希法(关键字用另外一种哈希函数,再次哈希化)
stepSize[步长] = constant[质数] - (key % constant)
冲突解决方案
- 链地址法
- 开放地址法(寻找空白单元格,添加重复的数据)
填装因子
- 表示当前哈希表中已经包含的数据项和整个哈希表长度的比值
- 填装因子 = 总数据项 / 哈希表长度
- 开放地址法 最大为:1 (必须寻找到空白的单元才能将元素放入)
- 链地址法 可以大于 1 (可以无限延伸下去,效率逐渐变低)
冲突解决方案效率比较
- 如果无冲突,效率很高
- 如果有冲突,存取时间就依赖后来的探测长度
- 平均探测长度以及平均存取时间,取决于填装因子(增大而越长,效率将会下降)
- 线性探测(探测序列 P 和填装因子 L)–填装因子越大,效率越低
成功情况: P = 1 + 1 ( 1 − L ) 2 2 P= \frac{1+\frac{1}{(1-L)^2}}{2} P=21+(1−L)21
失败情况: P = 1 + 1 ( 1 − L ) 2 P= \frac{1+\frac{1}{(1-L)}}{2} P=21+(1−L)1 - 二次探测/再哈希法–填装因子越大,效率越低
成功情况: P = − l o g 2 1 − l o a d F a c t o r l o a d F a c t o r P=-log{_2} \frac{1-loadFactor}{loadFactor} P=−log2loadFactor1−loadFactor
失败情况: P = 1 1 − l o a d F a c t o r P= \frac{1}{1-loadFactor} P=1−loadFactor1 - 链地址法:
P
=
N
a
r
r
a
y
S
i
z
e
P= \frac{N}{arraySize}
P=arraySizeN
成功情况: P = 1 + l o a d F a c t o r 2 P= 1+\frac{loadFactor}{2} P=1+2loadFactor
失败情况: 1 + l o a d F a c t o r 1+loadFactor 1+loadFactor
设计哈希函数具备的优点
- 快速的计算
- 快速获取到对应的 hashCode
- 通过快速的计算获取到元素对应的 hashCode
- 均匀的分布
- 多个元素映射到同一个位置时,都会影响效率
- 优秀的哈希函数应该尽可能将元素映射到不同的位置,让元素均匀分布
class HashTable {
constructor() {
this.storage = []
this.count = 0
this.limit = 7 /* 当前总长度(尽可能为质数) */
}
// 1. 哈希函数
hashFunc(str, size) {
// 1. 定义hashCode变量
var hashCode = 0
// 2. 霍纳算法,计算hashCode的值
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
// 3. 取余操作
var index = hashCode % size
// 4. 返回index(索引值)
return index
}
// 2. 插入&修改数据
put(key, value) {
// 1. 根据key获取对应的索引值
var index = this.hashFunc(key, this.limit)
// 2. 根据index取出对应的bucket
var bucket = this.storage[index]
// 3. 判断bucket是否为空
if (bucket == null) {
bucket = []
this.storage[index] = bucket
}
// 4. 判断是否为修改数据
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] == key) {
tuple[1] = value
return
}
}
// 5. 进行添加数据
bucket.push([key, value])
this.count += 1
}
// 3. 获取数据
get(key) {
// 1. 获取对应的索引
var index = this.hashFunc(key, this.limit)
// 2. 获取对应的bucket
var bucket = this.storage[index]
// 3. 判断是否为空
if (bucket == null) {
return null /* 值不存在 */
}
// 4. 存在bucket,进行线性查找
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] == key) {
return tuple[1]
}
}
// 5. bucket内不存在该值
return null
}
// 4. 删除数据
remove(key) {
// 1. 找到对应的索引值
var index = this.hashFunc(key, this.limit)
// 2. 找到对应的Bucket
var bucket = this.storage[index]
// 3. 判断bucket是否存在
if (bucket == null) {
return false
}
// 4. 寻找对应的数据,并删除
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] == key) {
bucket.splice(i, 1)
this.count -= 1
return tuple[1]
}
}
// 5. 不存在该数据
return false
}
/* 其余方法 */
// 5. 判断是否为空
isEmpty() {
return this.count == 0
}
// 6. 获取元素的个数
size() {
return this.count
}
}
扩容
- 当 loadFactor>0.75 时,需要进行扩容
- 可以扩容(容量增大一倍),但随数据增多导致效率降低
- 扩容之后,所有数据一定要同时进行修改
判断:
-
当 this.count > this.limit * 0.75 时,需要扩容
if (this.count > this.limit * 0.75) { var newSize = this.limit * 2 var newPrime = this.getPrime(newSize) /* 获取质数 */ this.resize(newPrime) }
-
当 this.limit > 7 && this.count < this.limit * 0.25 时,需要减容
if (this.limit > 7 && this.count < this.limit * 0.25) { var newSize = Math.floor(this.limit / 2) var newPrime = this.getPrime(newSize) /* 获取质数 */ this.resize(newPrime) }
代码部分:
resize (newLimit) {
// 1. 保存旧的数据内容
var oldStorage = this.storage
// 2. 重置内容
this.storage = []
this.count = 0
this.limit = newLimit
// 3. 遍历oldStorage中所有的bucket
for (let i = 0; i < oldStorage.length; i++) {
// 3.1 取出对应的bucket
var bucket = oldStorage[i]
// 3.2 判断bucket是否为空
if (bucket == null) {
continue
}
// 3.3 取出数据(有数据),重新插入
for (let j = 0; j < bucket.length; j++) {
const tuple = bucket[j]
this.put(tuple[0], tuple[1])
}
}
}
/* 判断某个数是否是质数 */
isPrime (num) {
// 1. 获取平方根
var temp = parseInt(Math.sqrt(num))
// 2. 循环判断
for (let i = 0; i <= temp; i++) {
if (num % i == 0) {
return false
}
}
return true
}
/* 获取质数的方法 */
getPrime (num) {
while (!this.isPrime(num)) {
num++
}
return num
}