ht.put(‘hgf’, ‘124’)
ht.put(‘wds’, ‘125’)
ht.put(‘wer’, ‘126’)
ht.put(‘kgl’, ‘127’)
ht.put(‘kmg’, ‘128’)
// 查看哈希表内数据个数
console.log(ht.count) // 6
// 通过storage属性查看一下哈希表的内部结构
console.log(ht.storage)
/* storage打印结果
[
[ [ ‘wds’, ‘125’ ], [ ‘kgl’, ‘127’ ], [ ‘kmg’, ‘128’ ] ],
[ [ ‘wer’, ‘126’ ] ],
<1 empty item>,
[ [ ‘hgf’, ‘124’ ] ],
[ [ ‘abc’, ‘123’ ] ]
]
*/
此时的哈希表内部是这样的
get()
方法是用于查询哈希表中某个数据。该方法直接收一个参数,即用于查询的 key
实现思路:
-
通过哈希函数,将
key
哈希化,获取一个索引index
-
判断哈希表
storage
数组的索引index
上有无数据,若无,则返回false
-
若有数据,则遍历该索引上的数组每个元素,比对每个元素的
key
是否与我们传入的key
相等,若有查询到相等的值,则返回该值的value
-
若无数据,则返回
false
思路和代码都比较简单,我们直接来看代码
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
// 获取数据
HashTable.prototype.get = function (key) {
// 1.获取相应的下标值
let index = this.hashFunc(key, this.length)
let current = this.storage[index]
// 2.判断该下标值的位置是否有数据
// 2.1 若该下标值位置不存在任何数据,则查找失败
if(!current) {
return false
}
// 2.2 该下标值位置有数据
// 3. 进行遍历查找
for(let i in current) {
// 3.1 找到对应数据并返回value
if(current[i][0] === key) {
return current[i][1]
}
}
// 3.2 没有找到对应数据,返回false
return false
}
}
我们来使用以下该方法
let ht = new HashTable()
// 执行put()方法 6次
ht.put(‘abc’, ‘123’)
ht.put(‘hgf’, ‘124’)
ht.put(‘wds’, ‘125’)
ht.put(‘wer’, ‘126’)
ht.put(‘kgl’, ‘127’)
ht.put(‘kmg’, ‘128’)
// 执行get()方法,获取 key为 ‘hgf’ 的值
console.log(ht.get(‘hgf’)) // 124
del()
方法是删除哈希表中某个数据。该方法接收一个参数 key
实现思路:
-
通过哈希函数,将
key
哈希化,获取一个索引index
-
判断哈希表
storage
数组的索引index
上有无数据,若无,则返回false
,表示删除失败 -
若有数据,则遍历该索引上的数组每个元素,比对每个元素的
key
是否与我们传入的key
相等,若有查询到相等的值,则直接删除该值,此时this.count --
,并返回被删除元素的value
值 -
若没有查询到相等的值,则返回
false
,表示删除失败
我们来看一下代码
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
// 删除数据
HashTable.prototype.del = function (key) {
// 1.获取相应的下标值
let index = this.hashFunc(key, this.length)
let current = this.storage[index]
// 2. 判断该索引位置有无数据
// 2.1 该下标值位置没有数据,返回false,删除失败
if(!current) {
return false
}
// 2.2 该下标值位置有数据
// 3. 遍历数组查找对应数据
for (let i in current) {
let inner = current[i]
// 3.1 找到对应数据了,删除该数据
if(inner[0] === key) {
current.splice(i, 1)
this.count –
return inner[1]
}
}
// 3.2 没有找到对应数据,则删除失败,返回false
return false
}
}
我们来使用一下该方法
let ht = new HashTable()
// 执行put()方法 6次
ht.put(‘abc’, ‘123’)
ht.put(‘hgf’, ‘124’)
ht.put(‘wds’, ‘125’)
ht.put(‘wer’, ‘126’)
ht.put(‘kgl’, ‘127’)
ht.put(‘kmg’, ‘128’)
// 删除 key为 'hgf’的元素
ht.del(‘hgf’) // 删除成功,返回 124
// 删除 key为 'ppp’的元素
ht.del(‘ppp’) // 删除失败,返回 false
// 查看哈希表内部结构
console.log(ht.storage)
/* storage打印结果
[
[ [ ‘wds’, ‘125’ ], [ ‘kgl’, ‘127’ ], [ ‘kmg’, ‘128’ ] ],
[ [ ‘wer’, ‘126’ ] ],
<1 empty item>,
[],
[ [ ‘abc’, ‘123’ ] ]
]
*/
此时的哈希表内是这样的
isEmpty()
方法是用于判断哈希表是否为空。该方法无需传参
该方法思路比较简单,直接判断属性 count
是否为 0
即可
我们来看一下代码
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
//判断哈希表是否为空
HashTable.prototype.isEmpty = function () {
return this.count === 0
}
}
我们来用一下该方法
let ht = new HashTable()
console.log(ht.isEmpty()) // false,哈希表为空
ht.put(‘abc’, ‘123’)
console.log(ht.isEmpty()) // true,哈希表不为空
size()
方法就是用于返回哈希表中数据个数。该方法也无需传参
该方法实现思路也特别简单,直接返回属性 count
即可
我们来看一下代码
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
// 返回哈希表内元素个数
HashTable.prototype.size = function () {
return this.count
}
}
我们来用一下该方法
let ht = new HashTable()
console.log(ht.size()) // 0,哈希表内没有数据
ht.put(‘abc’, ‘123’)
console.log(ht.size()) // 1,哈希表内有一条数据
resize()
方法就是用来对哈希表的容量进行改变的,当填充因子过大,我们就对其进行扩容;当填充因子较小,我们就增加其容量。该方法接收一个参数 newLength
,表示新的哈希表的容量。
实现思路:
-
将原本的属性
storage
赋值给一个新的变量oldStorage
,然后我们创建一个新的空数组赋值给storage
,并将参数newLength
赋值给属性length
-
遍历
oldStorage
,根据哈希表新的容量大小newLength
将原本哈希表中所有的数据重新哈希化 、插入数据即可
该方法难以用动图演示,所以大家好好理解一下,我们直接用代码来实现
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
//改变哈希表的容量
HashTable.prototype.resize = function(newLength) {
// 1.将旧的哈希表赋值给新变量
let oldStorage = this.storage
// 2.创建新的空数组作为新的哈希表容器
this.storage = []
// 3.修改哈希表容量
this.length = newLength
// 4.遍历旧的哈希表
for(let i = 0; i < oldStorage.length; i++) {
let box = oldStorage[i]
// 4.1 某索引位置上没有数据
if(box === null) {
continue;
}
// 4.2 某索引上有数据
for(let j = 0; j < box.length; j++) {
let inner_box = box[j]
// 4.2.1 将数据重新经过哈希化插入到新的哈希表中
this.put(inner_box[0], inner_box[1])
}
}
}
}
这里就不对该方法做过多的演示了,后面会在 put()
方法 和 del()
方法的改写中用到
isPrime()
方法使用于判断某个数是否为质数的,因此也就只需要接收一个数字为参数即可。
因为我们要实现哈希表的自动扩容与减容,所以在每次容量改变的时候,需要判断新的容量是否为质数,以此来保证之后哈希表中的数据均匀地分布,所以我们还是有必要来封装一下这个方法的。
在说方法实现思路之前,我们来回顾一下,质数是只能被 1
和 自身
整除,因此我们来看一下数字 16
,显然它不是一个质数,那来看看他能被哪些数整除吧
| 左 | 右 | 等于 |
| — | — | — |
| 1 | 16 | 16 |
| 2 | 8 | 16 |
| 4 | 4 | 16 |
| 8 | 2 | 16 |
| 16 | 1 | 16 |
非常明显地看到,只要一个数能被整除,那么一个数肯定是大于等于该数的算数平方根;另一个数肯定小于等于该数的算数平方根
因此,我们在判断一个数是否为质数时,只需从 2
开始逐个判断该数能否被整除,一直判断到该数的算数平方根即可
那么我们来看一下代码怎么实现的吧
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
//判断是是否为质数
HashTable.prototype.isPrime = function(number) {
// 1.获取算数平方根,并取整
let sqrt = Math.floor(Math.sqrt(number))
// 2.从2开始遍历到算数平方根
for(let i = 2; i <= sqrt; i++) {
// 2.1 能被整除,返回 false
if(number % i === 0) return false;
}
// 2.2 不能被整除,返回 true
return true
}
}
我们来简单验证一下该方法
let ht = new HashTable()
console.log(ht.isPrime(3)); // true
console.log(ht.isPrime(4)); // false
console.log(ht.isPrime(16)); // false
console.log(ht.isPrime(11)); // true
toPrime()
方法就是用于获取离某个数最近的质数并返回,因此只需要接收一个数字作为参数即可。
我们在实现扩容或减容时,初始会简单地 乘以2
或者 除以2
,很难保证获得的数是质数,所以我们需要封装这样一个方法来将变换后的值变为质数再进行使用。
实现思路也很简单,就一直 +1
来寻找质数就好了。
我们来看一下代码
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
//获取离某个数最近地质数并返回
HashTable.prototype.toPrime = function(number) {
while(!this.isPrime(number)) {
number ++
}
return number
}
}
我们来简单验证一下该方法
let ht = new HashTable()
console.log(ht.toPrime(3)) // 4
console.log(ht.toPrime(32)); // 37
什么时候需要进行给哈希表扩容呢?那肯定是在哈希表中数据量增加的时候需要考虑扩容的问题,所以我们将在 put()
方法中加上扩容的判断,上文也说到了,填充因子等于 0.75
是我们扩容的临界点,即当填充因子大于 0.75
时,就对哈希表进行扩容
话不多说,我们直接在上文封装好的 put()
方法中进行改写
实现思路:
-
在
this.count ++
之后,判断填充因子的大小,即this.count / this.length
是否大于0.75
,若小于0.75
,则不做任何处理 -
若大于
0.75
,则先获取一个原来哈希表容量两倍的数number
,再调用this.toPrime
方法获得一个离number
最近的一个质数prime
-
最后调用
this.resize
方法,并将prime
作为参数传入,完成扩容功能
直接来看代码吧
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
// 插入或修改数据
HashTable.prototype.put = function (key, value) {
// 1.哈希化获得下标值
let index = this.hashFunc(key, this.length)
let current = this.storage[index]
// 2.判断该下标值的位置是否有数据
// 2.1无数据
if(!current) {
this.storage[index] = [[key, value]]
this.count ++
return;
}
// 2.2有数据
// 3.遍历对应索引上的数组
for (let i = 0; i < current.length; i ++) {
// 3.1已存在相同数据
if(current[i][0] === key) {
current[i][1] = value
return;
}
}
// 3.2未存在相同数据,直接添加数据
current.push([key, value])
this.count ++
// 4.判断是否需要进行扩容
if(this.count / this.length >= 0.75) {
// 4.1 将哈希表容量扩大一倍
let newLength = this.length * 2
// 4.2 获取质数容量
newLength = this.toPrime(newLength)
// 4.3 扩容
this.resize(newLength)
}
}
}
同样的,在我们的 del()
方法中会涉及到数据减少的情况,即 this.count --
,那么此时我们就需要考虑哈希表是否需要减少容量,也就是填充因子是否小于 0.25
,若小于并且哈希表容量大于7,则进行减容;否则不做处理
这里说一下为什么哈希表容量要大于7,因为在减容时,我们要将容量除以2,但哈希表的容量不方便太小太小,所以我就自己设定了一个容量的下限值为7,意思就是当哈希表容量小于或等于7时,即使填充因子小于0.25,也无需进行减容
实现思路:
-
在
this.count --
之后,判断填充因子的大小,即this.count / this.length
是小于0.25
,若大于0.25
,则不做任何处理 -
若小于
0.25
并且哈希表容量大于7
,则先获取一个原来哈希表容量一半的数number
,再调用this.toPrime
方法获得一个离number
最近的一个质数prime
-
最后调用
this.resize
方法,并将prime
作为参数传入,完成减容功能
我们直接来看代码
function HashTable() {
// 属性
// 用于存储数据
this.storage = []
// 统计哈希表内数据个数
this.count = 0
// 设定哈希表初始长度
this.length = 7
//封装哈希函数
HashTable.prototype.hashFunc = function (str, size) {
let hashCode = 0
//取一个很大的数
for (let i = 0; i < str.length; i ++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
//取余
return hashCode % size
}
// 删除数据
HashTable.prototype.del = function (key) {
// 1.获取相应的下标值
let index = this.hashFunc(key, this.length)
let current = this.storage[index]
// 2. 判断该索引位置有无数据
// 2.1 该下标值位置没有数据,返回false,删除失败
if(!current) {
return false
}
// 2.2 该下标值位置有数据
// 3. 遍历数组查找对应数据
for (let i in current) {
let inner = current[i]
// 3.1 找到对应数据了,删除该数据
if(inner[0] === key) {
current.splice(i, 1)
this.count –
// 判断是否需要减容
if(this.count / this.length < 0.25 && this.length > 7) {
// 将哈希表容量减小一倍
let number = Math.floor(this.length / 2)
// 获取质数容量
number = this.toPrime(number)
// 减容
this.resize(number)
}
return inner[1]
}
}
// 3.2 没有找到对应数据,则删除失败,返回false
return false
}
}
==============================================================
哈希表的讲解就到这里了,希望大家对哈希表有了更深一层的理解。下一篇文章我将讲解一下树结构。
大家可以关注我,之后我还会一直更新别的数据结构与算法的文章来供大家学习,并且我会把这些文章放到【数据结构与算法】这个专栏里,供大家学习使用。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。
current) {
let inner = current[i]
// 3.1 找到对应数据了,删除该数据
if(inner[0] === key) {
current.splice(i, 1)
this.count –
// 判断是否需要减容
if(this.count / this.length < 0.25 && this.length > 7) {
// 将哈希表容量减小一倍
let number = Math.floor(this.length / 2)
// 获取质数容量
number = this.toPrime(number)
// 减容
this.resize(number)
}
return inner[1]
}
}
// 3.2 没有找到对应数据,则删除失败,返回false
return false
}
}
==============================================================
哈希表的讲解就到这里了,希望大家对哈希表有了更深一层的理解。下一篇文章我将讲解一下树结构。
大家可以关注我,之后我还会一直更新别的数据结构与算法的文章来供大家学习,并且我会把这些文章放到【数据结构与算法】这个专栏里,供大家学习使用。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-p4N2RjLW-1713633270717)]
[外链图片转存中…(img-evlgNTbQ-1713633270718)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-cNwIlYcj-1713633270718)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-s5phD6CF-1713633270718)]
最后
文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可免费获取,包括答案解析。
[外链图片转存中…(img-SWwaO07P-1713633270719)]