最近在学数据结构和算法,正好将学习的东西记录下来,我是跟着一个b站博主学习的,是使用js来进行讲解的,待会也会在文章后面附上视频链接地址,大家想学习的可以去看看
本文主要讲解哈希表,其他数据结构后续学完也会持续更新!
目录
一、什么是哈希表
下面是 chatGPT 给出的解释:
下面是PPT中的讲解
想要学习哈希表,那就不得不了解一些哈希表的概念
二、哈希化时产生的问题:冲突
注意:常见解决冲突的方法有两种,链地址法(拉链法)和开放地址法,但是相对来说链地址法的效率是要好于开放地址法的,所以等会在用 js 封装哈希表的时候,我们采用的也是链地址法。
三、哈希函数的实现
四、封装哈希表
1、创建哈希表
2、插入&修改数据
3、获取方法
4、删除方法
5、其他方法
6、测试哈希表
7、完整代码
五、哈希表的扩容
1、哈希表的扩容思想
2、哈希表的扩容实现
下面修改插入&修改方法,如果添加东西太多,实现扩容
下面修改删除方法,如果删除东西太多,实现减容
六、实现容量恒为质数
下面是所有完善之后的代码
一、什么是哈希表
下面是 chatGPT 给出的解释:
哈希表(Hash Table)又称为散列表,是一种用于存储键值对的数据结构。它通过哈希函数将键映射到一个固定大小的数组中的位置,该位置称为哈希桶(Hash Bucket)。哈希表的优势在于可以快速插入、删除和查找数据。
哈希函数是哈希表的核心,它接受一个任意大小的输入(键),并通过计算返回一个固定大小的哈希值。哈希值被用作数组的索引,用于存储和检索对应的键值对。由于哈希函数的输出是固定的,不同的键可能会映射到相同的哈希值,这就产生了哈希冲突。
解决哈希冲突的一种常见方法是使用链表或其他数据结构来存储哈希冲突的键值对。当多个键被映射到同一个哈希桶时,它们会被链接在一起形成一个链表(或其他数据结构),并存储在同一个桶内。这样,当查找键值对时,可以先通过哈希函数找到对应的桶,然后在桶内顺序查找或通过进一步的数据结构快速查找目标键值对。
哈希表在平均情况下具有非常高效的插入、删除和查找操作。在理想情况下,哈希函数能够将键均匀地映射到不同的桶,从而实现常数时间复杂度的操作。然而,在某些情况下,哈希冲突可能会导致性能下降,甚至出现较差的情况。因此,在设计哈希函数时需要考虑均匀性和避免冲突的因素。
虽然文字有点多,但是讲的还是很好理解的。
下面是PPT中的讲解
想要学习哈希表,那就不得不了解一些哈希表的概念
二、哈希化时产生的问题:冲突
注意:常见解决冲突的方法有两种,链地址法(拉链法)和开放地址法,但是相对来说链地址法的效率是要好于开放地址法的,所以等会在用 js 封装哈希表的时候,我们采用的也是链地址法。
三、哈希函数的实现
// 设计哈希函数
// 1 -> 将字符串转换成比较大的数字:hashCode
// 2 -> 将大的数字 hashCode 压缩到数组的范围之内
function hashFunc(str, size) {
// 1、定义hashCode变量
var hashCode = 0
// 2、霍纳算法,用来计算hashCode的值
// cats -> Unicode 编码
for (var i = 0; i < str.length; i++) {
// 一般选用 37 为那个质数 ,然后得到一个比较大的数字
hashCode = 37 * hashCode + str.charCodeAt(i)
}
// 3、取余操作,得到压缩到数组范围内的数字
var index = hashCode % size
return index
}
console.log(hashFunc("abc", 7));
四、封装哈希表
1、创建哈希表
// 封装哈希表类
function HashTable() {
// 属性
this.storage = []
this.count = 0
this.limit = 7
// 方法
// 哈希函数的封装
function hashFunc(str, size) {
// 1、定义hashCode变量
var hashCode = 0
// 2、霍纳算法,用来计算hashCode的值
// cats -> Unicode 编码
for (var i = 0; i < str.length; i++) {
// 一般选用 37 为那个质数 ,然后得到一个比较大的数字
hashCode = 37 * hashCode + str.charCodeAt(i)
}
// 3、取余操作,得到压缩到数组范围内的数字
var index = hashCode % size
return index
}
}
2、插入&修改数据
// 插入 & 修改操作的封装
HashTable.prototype.put = function(key, value) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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])
}
3、获取方法
// 获取操作的封装
HashTable.prototype.get = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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、依然没有找到,那么返回 null
return null
}
4、删除方法
// 删除操作的封装
HashTable.prototype.remove = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断 bucket 是否为 null
if (bucket == null) {
return null
}
// 4、如果有 bucket ,那么就进行线性查找,并且删除
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]
}
}
// 5、如果依然没有找到,那么返回 null
return null
}
5、其他方法
// 封装其他方法
// 判断哈希表是否为 null 的封装
HashTable.prototype.isEmpty = function() {
return this.count == 0
}
// 获取哈希表中元素个数的封装
HashTable.prototype.size = function() {
return this.count
}
6、测试哈希表
// 测试哈希表
// 1、创建哈希表
var ht = new HashTable()
// 2、插入数据
ht.put("abc", "哈哈哈")
ht.put("cba", "啦啦啦")
// 3、获取数据
console.log(ht.get("abc")); // 哈哈哈
// 4、修改方法
ht.put("abc", "哈哈哈哈哈")
console.log(ht.get("abc")); // 哈哈哈哈哈
// 5、删除方法
ht.remove("abc")
console.log(ht.get("abc")); // null
// 6、判断是否为空
console.log(ht.isEmpty()); // false
// 7、查看哈希表中元素的个数
console.log(ht.size()); // 1
7、完整代码
// 封装哈希表类
function HashTable() {
// 属性
this.storage = []
this.count = 0
this.limit = 7
// 方法
// 哈希函数的封装
HashTable.prototype.hashFunc = function(str, size) {
// 1、定义hashCode变量
var hashCode = 0
// 2、霍纳算法,用来计算hashCode的值
// cats -> Unicode 编码
for (var i = 0; i < str.length; i++) {
// 一般选用 37 为那个质数 ,然后得到一个比较大的数字
hashCode = 37 * hashCode + str.charCodeAt(i)
}
// 3、取余操作,得到压缩到数组范围内的数字
var index = hashCode % size
return index
}
// 插入 & 修改操作的封装
HashTable.prototype.put = function(key, value) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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
}
// 获取操作的封装
HashTable.prototype.get = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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、依然没有找到,那么返回 null
return null
}
// 删除操作的封装
HashTable.prototype.remove = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断 bucket 是否为 null
if (bucket == null) {
return null
}
// 4、如果有 bucket ,那么就进行线性查找,并且删除
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]
}
}
// 5、如果依然没有找到,那么返回 null
return null
}
// 封装其他方法
// 判断哈希表是否为 null 的封装
HashTable.prototype.isEmpty = function() {
return this.count == 0
}
// 获取哈希表中元素个数的封装
HashTable.prototype.size = function() {
return this.count
}
}
// 测试哈希表
// 1、创建哈希表
var ht = new HashTable()
// 2、插入数据
ht.put("abc", "哈哈哈")
ht.put("cba", "啦啦啦")
// 3、获取数据
console.log(ht.get("abc"));
// 4、修改方法
ht.put("abc", "哈哈哈哈哈")
console.log(ht.get("abc"));
// 5、删除方法
ht.remove("abc")
console.log(ht.get("abc"));
// 6、判断是否为空
console.log(ht.isEmpty());
// 7、查看哈希表中元素的个数
console.log(ht.size());
五、哈希表的扩容
1、哈希表的扩容思想
2、哈希表的扩容实现
// 哈希表扩容/缩容封装
HashTable.prototype.resize = function(newLimit) {
// 保存旧的数组内容
var oldStorage = this.storage
// 2、重置所有的属性
this.storage = []
this.conut = 0
this.limit = newLimit
// 3、遍历 oldStorage 中所有的 bucket
for (var i = 0; i < oldStorage.length; i++) {
// 3.1 取出对应的 bucket
var bucket = oldStorage[i]
// 3.2 判断 bucket 是否为 null
if (bucket == null) {
continue
}
// 3.3 bucket 中有数据,那么取出数据,重新插入
for (var j = 0; j < bucket.length; j++) {
var tuple = bucket[j]
this.put(tuple[0], tuple[1])
}
}
}
下面修改插入&修改方法,如果添加东西太多,实现扩容
// 修改插入&修改方法,如果添加东西太多,实现扩容
// 插入 & 修改操作的封装
HashTable.prototype.put = function(key, value) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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
// 6、判断是否需要扩容操作
if (this.count > this.limit * 0.75) {
this.resize(this.limit * 2)
}
}
下面修改删除方法,如果删除东西太多,实现减容
// 修改删除方法,如果删除东西太多,实现减容
// 删除操作的封装
HashTable.prototype.remove = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断 bucket 是否为 null
if (bucket == null) {
return null
}
// 4、如果有 bucket ,那么就进行线性查找,并且删除
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]
// 缩小容量
if (this.limit > 7 && this.count < this.limit * 0.25) {
// Math.floor 向下取整
this.resize(Math.floor(this.limit / 2))
}
}
}
// 5、如果依然没有找到,那么返回 null
return null
}
六、实现容量恒为质数
// 封装判断某个数字是否是质数
HashTable.prototype.isPrime = function(num) {
// 1、获取 num 的平方根
var temp = parseInt(Math.sqrt(num))
// 2、循环判断
for (var i = 2; i <= temp; i++) {
if (num % i == 0) {
return false
}
}
return true
}
// 封装获取质数的方法
HashTable.prototype.getPrime = function(num) {
// 循环判断这个数是否为质数,如果不是质数,就一直加一,什么时候是质数就返回这个数
while (!this.isPrime(num)) {
num++
}
return num
}
下面是所有完善之后的代码
// 封装哈希表类
function HashTable() {
// 属性
this.storage = []
this.count = 0
this.limit = 7
// 方法
// 哈希函数的封装
HashTable.prototype.hashFunc = function(str, size) {
// 1、定义hashCode变量
var hashCode = 0
// 2、霍纳算法,用来计算hashCode的值
// cats -> Unicode 编码
for (var i = 0; i < str.length; i++) {
// 一般选用 37 为那个质数 ,然后得到一个比较大的数字
hashCode = 37 * hashCode + str.charCodeAt(i)
}
// 3、取余操作,得到压缩到数组范围内的数字
var index = hashCode % size
return index
}
// 插入 & 修改操作的封装
HashTable.prototype.put = function(key, value) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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
// 6、判断是否需要扩容操作
if (this.count > this.limit * 0.75) {
var newSize = this.limit * 2
var newPrime = this.getPrime(newSize)
this.resize(newPrime)
}
}
// 获取操作的封装
HashTable.prototype.get = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断该 bucket 是否为 null
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、依然没有找到,那么返回 null
return null
}
// 删除操作的封装
HashTable.prototype.remove = function(key) {
// 1、根据 key 获取对应的 index
var index = this.hashFunc(key, this.limit)
// 2、根据 index 取出对应的 bucket
var bucket = this.storage[index]
// 3、判断 bucket 是否为 null
if (bucket == null) {
return null
}
// 4、如果有 bucket ,那么就进行线性查找,并且删除
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]
// 缩小容量
if (this.limit > 7 && this.count < this.limit * 0.25) {
// Math.floor 向下取整
var newSize = Math.floor(this.limit / 2)
var newPrime = this.getPrime(newSize)
this.resize(newPrime)
}
}
}
// 5、如果依然没有找到,那么返回 null
return null
}
// 封装其他方法
// 判断哈希表是否为 null 的封装
HashTable.prototype.isEmpty = function() {
return this.count == 0
}
// 获取哈希表中元素个数的封装
HashTable.prototype.size = function() {
return this.count
}
// 哈希表扩容/缩容封装
HashTable.prototype.resize = function(newLimit) {
// 保存旧的数组内容
var oldStorage = this.storage
// 2、重置所有的属性
this.storage = []
this.conut = 0
this.limit = newLimit
// 3、遍历 oldStorage 中所有的 bucket
for (var i = 0; i < oldStorage.length; i++) {
// 3.1 取出对应的 bucket
var bucket = oldStorage[i]
// 3.2 判断 bucket 是否为 null
if (bucket == null) {
continue
}
// 3.3 bucket 中有数据,那么取出数据,重新插入
for (var j = 0; j < bucket.length; j++) {
var tuple = bucket[j]
this.put(tuple[0], tuple[1])
}
}
}
// 封装判断某个数字是否是质数
HashTable.prototype.isPrime = function(num) {
// 1、获取 num 的平方根
var temp = parseInt(Math.sqrt(num))
// 2、循环判断
for (var i = 2; i <= temp; i++) {
if (num % i == 0) {
return false
}
}
return true
}
// 封装获取质数的方法
HashTable.prototype.getPrime = function(num) {
// 循环判断这个数是否为质数,如果不是质数,就一直加一,什么时候是质数就返回这个数
while (!this.isPrime(num)) {
num++
}
return num
}
}
// 测试哈希表
// 1、创建哈希表
var ht = new HashTable()
// 2、插入数据
ht.put("abc", "哈哈哈")
ht.put("cba", "啦啦啦")
// 3、获取数据
console.log(ht.get("abc"));
// 4、修改方法
ht.put("abc", "哈哈哈哈哈")
console.log(ht.get("abc"));
// 5、删除方法
ht.remove("abc")
console.log(ht.get("abc"));
// 6、判断是否为空
console.log(ht.isEmpty());
// 7、查看哈希表中元素的个数
console.log(ht.size());
下面附上b站视频链接,需要学习的可以去看看(JavaScript算法与数据结构),如果光看文章可能还是有点晦涩难懂,但是看老师视频的讲解,基本就能掌握了。