当前位置:   article > 正文

TS哈希表_ts hashtable

ts hashtable

哈希表的简介

哈希表示根据数组来进行实现的,但相对于数组,它拥有许多优势,同时也有一定的劣势;

优势:

1、它可以提供非常快速的 查找——删除——插入操作

2、实现较为容易(相对于树)

劣势:

1、它是无序的

2、key是不允许重复的

密乘


123=1*10**3+2*10**2+3

作用:保证key的唯一性,但同时也占据了内存

那么如何解解决占据内存大的问题呢?

答:给最终答案再规定范围内去进行取余。

而规定范围就是你所要存储内存的范围:比如你最多要存储100个,那么你的规定范围就是0~100。

而得到的结果则是123%100=23;

这时,它的最终结果就会小很多,但届时,又会有其他的问题,比如3取余也是3,1023取余也是23,等等...

也就是说,还是会存在重复的问题;

那么,此时给了其他的解决办法:

1、链地址法

2、开放地址法

链地址法


为了解决哈希表的冲突,链地址法则是将每一个下标值中再创建一个数组或链表,而这个数组或链表中存储的都是下标值相同的值,就如同我们小学时用字典搜索字的那种检索;

开放地址法


此时我们数组上存储的已有1——8——9;

此时我们在想存储个2然后再存储个21;

首先,会将2插入到正确的位置,其次我们的算法得知(21%10=1),它的下标值为1;但此时1已被占用,但开放地址法它会往下顺延,直到找到一个空位置,来存放21的地方,这个方法就叫做线性探测;

例如:21——下标值为1,而1已被占用,那么它就会继续寻找下一个位置,也就是2;但此时的2已被刚才插入的2占用,此时只能再找下一个位置,而3是空的,没有被占用,那此时就将21插入下标识3中;

那如果我在想插入3呢?

答:当然与插入21时差不多啦;

算法就是:index+1;

那这样顺序打乱了,我如何查找我想要的值呢?

当他搜索21时,他会优先找到下标值为1的位置,然后比对值,如果是,就返回值,如果不是,则会按着index+1的算法依次往下找,但只要发现有一个位置为null,则就返回false(表示没有此内容),反之找到了对应的匹配值则就会返回其匹配值;

那我假设:我存的有1,21,31,41,然后我删除的31,然后我又准备查找41的时候,那我检测的过程中,31的位置为null,那我岂不是返回的就是false;

那这就涉及到删除的问题了,当我们删除的时候则用一种软删除,也就是说,我们在删除31的时候,用一个特殊的字符站用这个位置,比如“||”这个字符,当我们检测到这个字符的时候,就不是检测到为nul,就可以继续往下查找。

同时,当我们再次插入51的时候,我们检测顺序是1——21——“||”,此时我们就知道“||”此符号表示占位的意思,我们就顺理成章的将51插入其中,此时的数字排序则是【1——21——51——41】

而这种寻找空位的方法就被称为“线性探测”,而这种找空位放置内容的方式,显然弊端很大,如果这里1,2,3,4,5,都有值了,那么21只能插入到6上,如果这个空位间隔更大呢?那还不如用一个链表呢。

那有没有可以优化的呢?
当然有,那就是我们接下来所说的探索中的二次哈希;

二次哈希


1、二次哈希是对哈希化过后的哈希值再次哈希;

2、二次哈希的算法不可与第一次哈希算法一样;

3、已有一套合理的二次哈希的算法:stepSize=constant-(key%constant)

3.1constant是一个质数,而key就是一次哈希的值

我们这里依旧用21来举例;

21的一次哈希;

(21%10)=1;

21的二次哈希且假设contant===5;

5-(1%5)=4;

填充因子

填充因子=总数据项/哈希表长度

两种哈希法则都可以,但我们选择更加人性化的链地址法,谁叫咱和字典感情深呢,而在实现链地址法的前提下,我们来介绍一下,霍尔法则

霍尔法则


我们假设 a=1,b=2,c=3,d=4;

输入:abcda

设x=10;

那么根据我们上面提到的密乘,最终得到的结果应该是

result=1*x^4+2*x^3+3*x^2+4*x+1=12341

我们可以看到这样是非常繁琐的,而且毫不避讳的说,我是用算计器算出来的,而计算机尤其是面对密乘的时候,计算机远远不如直接的加载乘除来的快捷;那么有什么方式进行优化呢?

废话短说,至于霍尔法则是怎样的,自己应该在网上都可以查询出来,我这里就将它简单的优化一下,实现一下霍尔法则;

result=x(1*x^3+2*x^2+3*x+4)+1

         =x(x(1*10^2+2*x+3)+4)+1

         =x(x(x(1*x+2)+3)+4)+1

       =10(10(10(1*10+2)+3)+4)+1

        =10(10(10(12)+3)+4)+1

       =10(10(123)+4)+1

     =10(1234)+1

      =12341

看上去简单许多;那么如何将它华为一个完整的算法呢

由于我们还没有设计哈希表无法找到对应得值,所以我们简单的就用num=12341来分装一个霍尔法则;

通过上面的式子可以总结过程

let result=【第一位数乘以10+第二位数】

result=[result*10+第三位数]

result=[result*10+第四位数]

result=[result*10+第五位数]

.....

直到最后一位数

  1. function Horner(num: string): Number {
  2. let x = 10;
  3. let result = Number(num[0])
  4. let index = 0
  5. while (num[index + 1] !== undefined) {
  6. index = index + 1
  7. console.log(num[index]);
  8. result = result * x + Number(num[index])
  9. }
  10. return result
  11. }
  12. let res = Horner(num)
  13. console.log(res);

设计好了霍尔法则以后,我们就可以去进行功能上的开发了

哈希表的代码实现


  1. //霍尔法则
  2. function Horner(str: string): number {
  3. let x = 37;
  4. let result = str.charCodeAt(0)
  5. let index = 0
  6. while (str[index + 1] !== undefined) {
  7. index = index + 1
  8. result = result * x + str.charCodeAt(index)
  9. }
  10. return result
  11. }
  12. class HashTable {
  13. storage: any[]
  14. count: number//用于记录数组中已经存放的元素
  15. limit: number//用于记录数组中的长度
  16. constructor() {
  17. this.storage = []
  18. this.count = 0
  19. this.limit = 7
  20. }
  21. // 哈希函数
  22. hashWatch(str: string, ListSize: number) {
  23. let hashCode = Horner(str);
  24. // 取存放的下标值
  25. let index = hashCode % ListSize;
  26. return index
  27. }
  28. // 插入与修改操作
  29. put() {}
  30. // 获取
  31. get() {};
  32. remove() { }
  33. // 扩容
  34. dilatation() {}
  35. // 判断是否为质数
  36. getPrime() {}
  37. }
  38. let hs = new HashTable()

这里面一眼望去,可以看见一个函数和一个类,开头的函数就是最开始设计的霍尔法则,由于我们最终传递的是一个个字母,所以这里我们进行了一个小小的改造,但原理还是未变;

然后类是哈希表的真正载体,里面我们定义了一些变量以及一些方法,而其中有一个【哈希函数】我们是已经定义好了,它的作用就是将霍尔函数最后计算出来的数值通过计算,取出对应元素的存放地址,这里可以清晰的看见,我们使用的是取模。

接下来,我们会探讨具体的方法实现

getPrime方法


  1. getPrime(num: number) {
  2. for (let i = 2; i < Math.floor(Math.sqrt(num)); i++) {
  3. if (num % i === 0) {
  4. return this.getPrime(num + 1)
  5. }
  6. }
  7. return num
  8. }

得到质数:传入一个数先判断是否是一个质数,如果是,则返回,如果不是则不反回,并且以此+1,直到返回的是一个质数

首先,我们要了解什么是质数,就是除了1与它本身之外,没有任何数的乘积可以得到它,例如:3,5,7,11,17....

为什么要有这一步?

要想知道,必须要了解质数对于计算机的好处

使数列取模分布相对均匀,可以减少冲突

具体可看这片文章:​​​​​​(6条消息) 算法分析:哈希表的大小为何是素数_夜行625的博客-CSDN博客_hash 素数

这里主要检测的是数组中的长度是否是质数,用于扩容;

dilatation方法


  1. // 扩容
  2. dilatation(newLimit) {
  3. this.limit = newLimit;
  4. //olderStorage用于存放以前的哈希表中的数组
  5. let olderStorage = this.storage;
  6. //将哈希表的数组置空,以方便扩容
  7. this.storage = [];
  8. //同时存放的数量也需要清空
  9. this.count = 0;
  10. //这里运用的是双重for循环,将老的哈希表中每一个数组中的元素,依次使用put方法放置于新的哈希表中
  11. for (let i = 0; i < olderStorage.length; i++) {
  12. let bucket: { key?: any, value?: { [vaule: string]: any } }[] = olderStorage[i];
  13. if (!bucket) {
  14. continue
  15. }
  16. for (let j = 0; j < bucket.length; j++) {
  17. //这里调用的是已定义的put方法
  18. this.put(bucket[j].key, bucket[i].value)
  19. }
  20. }
  21. return true
  22. }

这一段作用是将哈希表进行扩容。

为什么扩容?是因为哈希表的长度不够用吗?
这里不是的,按道理来说,我们先将我们创建的哈希表的模式给介绍下,然后在解释为什么需要进行扩容:

我们的哈希表设计成链地址,所以先将数据模型给写出来:

  1. [
  2. [{key:string,value:any},{key:string,value:any}],
  3. [{key:string,value:any},{key:string,value:any}],
  4. [{key:string,value:any},{key:string,value:any}],
  5. [{key:string,value:any},{key:string,value:any}]
  6. ]

从本质上来说,我们的哈希表是一个二维数组,我们的最外面一层的数组长度是由limit来进行限制其长度的,主要是更据对应得【哈希函数】将值插入对应的内部一层的数组,像我们打比方的这个数组,此时的limit=4。而内部一层的数组我们有设置长度吗?显然是没有设置任何的长度。所以从某种角度来说,哈希表可以是无限的。

还是打个比方吧,还是以字典为例:字典26个字母是有限的,但26个字母之下每个字母对用的字数可以是无限的。

那既然是无限的,那为什么还有扩容呢?

我会告诉你是出于性能考虑:

还是用数据来说话吧

假设此时我们limit去5,而拥有的数有[5,10,15,20,6] 这几个数

根据【哈希函数】取对应的下标值

 我们可以清晰的看到大部分得数值都被分到index=0下面去了,而其他的基基本上没有,那么相对于其他,我们检索20,与检测6的速度是不是不一样呢?毕竟index=0下面的for循环更久一些。这几个数都是如此,那么更多的数值呢?

那接下来还是这几个数,我们来进行一下扩limit=7;

 如此看来是不是检索20起来是不是速度要快上很多,

那什么时候扩容呢?

PUT方法里面介绍

PUT方法


  1. put(key: string, vaule: { [vaule: string]: any }) {
  2. let index = this.hashWatch(key, this.limit);
  3. // 取出对应索引的bucket的一个下标值;
  4. let bucket: { key?: any, value?: { [vaule: string]: any } }[] = this.storage[index];
  5. // 先判断对用的bucken是否存在,如果没有,则添加
  6. if (!bucket) {
  7. bucket = [];
  8. this.storage[index] = bucket
  9. }
  10. // 判断是否为修改
  11. for (let i = 0; i < bucket.length; i++) {
  12. if (bucket[i].key === key) {
  13. // 判断是修改某一个值还是插入一个新值
  14. let keys = Object.keys(bucket[i].value);
  15. keys.forEach(item => {
  16. for (let key in vaule) {
  17. if (key === item) {
  18. console.log(vaule, "vaule");
  19. bucket[i].value[item] = vaule[key]
  20. }
  21. bucket[i].value[key] = vaule[key]
  22. }
  23. })
  24. }
  25. return true
  26. }
  27. bucket.push({ key, value: vaule })
  28. this.count += 1;
  29. if (bucket.length > this.limit * 0.75) {
  30. let newLimit = this.getPrime(this.limit * 2)
  31. this.dilatation(newLimit)
  32. }
  33. return true
  34. }

put方法中主要实现的便是哈希表元素的储存与修改;

前面的我就不在介绍了,主要是介绍这一段

  1. if (bucket.length > this.limit * 0.75) {
  2. let newLimit = this.getPrime(this.limit * 2)
  3. this.dilatation(newLimit)
  4. }

判断什麽时候进行扩容

当所填充的bucket(也就是所填充对应下标值的长度)大于限制长度的0.75倍开始扩容;

remove


 

  1. remove(key: string) {
  2. let index = this.hashWatch(key, this.limit);
  3. let bucket: { key?: any, value?: { [vaule: string]: any } }[] = this.storage[index];
  4. if (!bucket) {
  5. return false
  6. };
  7. bucket.forEach((item, index) => {
  8. if (key === item) {
  9. delete bucket[index];
  10. this.count -= 1
  11. }
  12. return true
  13. })
  14. if (bucket.length < this.limit * 0.45 && this.limit > 7) {
  15. let newLimt = this.getPrime(this.limit / 2)
  16. this.dilatation(newLimt)
  17. }
  18. return false
  19. }

这是删除操作,同样的,这里有个减容操作

  1. if (bucket.length < this.limit * 0.45 && this.limit > 7) {
  2. let newLimt = this.getPrime(this.limit / 2)
  3. this.dilatation(newLimt)
  4. }

但我们这里有个条件,就是应该拥有最低的容器长度;

get


  1. // 获取
  2. get(key: string) {
  3. let index = this.hashWatch(key, this.limit);
  4. let bucket: { key?: any, value?: { [vaule: string]: any } }[] = this.storage[index];
  5. if (!bucket) {
  6. return false
  7. }
  8. for (let i = 0; i < bucket.length; i++) {
  9. if (bucket[i].key === key) {
  10. return bucket[i]
  11. }
  12. }
  13. return false
  14. };

获取对应的值;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/617242
推荐阅读
相关标签
  

闽ICP备14008679号