数据结构学习笔记 哈希表(一) 哈希表基础与哈希函数

发布于:2022-10-21 ⋅ 阅读:(339) ⋅ 点赞:(0)

------HR:The first question is what you do if you have a conflict with your manager ?

------You:Hashmap ! I'll use Hashmap !

------HR:Hash ? Hash ?  What ? Have you eaten so many hash browns that your brain becomes into the potato ?

哈希表的结构就是数组,但是它神奇的地方在于对下标值的一种变换,这种变换我们称之为哈希函数,通过哈希函数可以获取到HashCode

          什么是哈希化?

              将大数字转化成数组范围内下标的过程,我们就称之为哈希化

          什么是哈希函数?

              通常我们会将单词转成大数字,大数字在进行哈希化的代码实现放在一个函数中,这个函数我们称为哈希函数

          什么是哈希表?

              最终将数据插入到的这个数组,对整个结构的封装,我们就称之为是一个哈希表

          几乎所有的编程语言都有直接或者间接的应用这种数据结构

          哈希表通常是基于数组进行实现的,但是相对于数组,它也很多的优势:

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

              无论多少数据,插入和删除值需要接近常量的时间:即O(1)的时间级.实际上,只需要几个机器指令即可完成

              哈希表的速度比树还要快,基本可以瞬间查找到想要的元素

              哈希表相对于树来说编码要容易很多

          哈希表相对于数组的一些不足:

              哈希表中的数据是没有顺序的,所以不能以一种固定的方式(比如从小到大)来遍历其中的元素

              通常情况下,哈希表中的key是不允许重复的,不能放置相同的key,用于保存不同的元素

解决冲突的两种方案

          链地址法:

            链地址法解决冲突的办法是每个数组单元中存储的不再是单个数据,而是一个链条

            链条一般使用数组和链表这两种数据结构

            比如是链表,也就是每个数组单元中存储着一个链表,一旦发现重复,将重复的元素插入到链表的首端或者末端即可

            当查询时,先根据哈希化后的下标值找到对应的位置,再取出链表,依次查询找寻找的数据

            使用数组或链表在这里效率都差不多

          开放地址法:

            线性的查找空白的单元

            线性探测:

              线性探测就是从index位置+1开始一点点找到合适的位置(空的位置)来放置.查询时从index位置开始进行查找,直到查询到空位置停止.删除时不能讲这个位置的下标设置为null,一般设置为-1,这样看到-1知道查询时继续查询,但插入时这里可以插入

              线性探测的问题:

                会产生聚集问题.聚集就是一连串的填充单元.聚集会影响哈希表的性能,插入查询删除都会影响,二次探测可以解决部分这个问题

            二次探测:

              优化了线性探测的步长,比如从下标值x开始,x + 1^2,x + 2^2等,这样可以一次性探测比较长的距离,避免一些线性探测存在的问题

              二次探测的问题:

                会造成步长不一样的聚集,还是会影响到哈希表的效率.再哈希法可以解决这个问题

            再哈希法:

              把关键字用另外一个哈希函数,再做一次哈希化,用这次哈希化的结果作为步长,对于指定的关键字,步长再整个探测中是不变的,不过不同的关键字使用不同的步长

              第二次哈希化需要与第一个哈希函数不同,且不能输出为0

              第二次哈希函数:

                stepSize = constant - (key % constant)

                其中constant是质数,且小于数组容量

                如:stepSize = 5 - (key % 5)

        方法选择:

            一般开发使用链地址法,它不会因为添加了某元素后性能急剧下降,比如java的Hashmap就用的链地址法

        优秀的哈希函数

            1.计算速度快,尽量少有乘法和除法,因为他们性能比较低

            2.均匀分布,尽可能的将元素映射到不同位置,让元素在哈希表中均匀分布

        快速计算:霍纳法则(秦九韶算法)

            p(x) = (...(anx + an-1)x + ...)x + a0

            例2x^4 - 3x^5 + 5x^2 + x -7转换为

            x(x(x(2x - 3) + 5) + 1) -7

            这样写只需要N次乘法和N次加法,时间复杂度为O(N)

          均匀分布:

            使用常量时尽可能使用质数

            如:哈希表的长度,N次幂的底数

            哈希表长度使用质数不会产生循环,也会让数据在哈希表中更均匀分布.当然在链地址法中质数没那么重要,在java中设置为2^N次幂

        注意:

            java中使用位运算方式提高HashMap效率,但js中位运算有bug,详情参见js浮点数精度问题(众所周知的0.1 + 0.2 !== 0.3),所以js中用取模.

            哈希表容量尽量用质数

 // 设计哈希函数
      // 1.将字符串转成比较大的数字:hashCode
      // 2.将大的数字hashCode压缩到数组范围之内
      function hashFn(str, size) {
        // 1.定义hashCode变量
        let hashCode = 0;
        // 2.霍纳算法,来计算hashCode的值
        for (let i = 0; i < str.length; i++) {
          /* 选择质数 31 41也可以,但用的最多的还是37
          这一步相当于霍纳算法的anx + an-1
          str.charCodeAt()来获取字符串中某个字符的Unicode编码 */
          hashCode = 37 * hashCode + str.charCodeAt(i);
        }
        // 3.取余操作
        const index = hashCode % size;
        return index;
      }
      // 测试哈希函数
      alert(hashFn('abc', 7)); // 4
      alert(hashFn('cba', 7)); // 3
      alert(hashFn('nba', 7)); // 5
      alert(hashFn('mba', 7)); // 1

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到