《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数

这应该是看完最呆(没有想到的那种呆~)的一个小章节了,给作者鼓掌,讲的好好。果然抽象能力才是王道

文章目录

        • 1、散列表
          • 1.1、小概念
          • 1.2、散列函数
          • 1.3、散列冲突
          • 1.4、装载因子
        • 2、实际应用中的散列表注意事项
          • 2.1、散列函数的设计原则
          • 2.2、装载因子过大
          • 2.3、如何避免低效地扩容
          • 2.4、解决冲突的方案选择
          • 2.5、设计一个工业级的散列函数
        • 3、哈希算法
          • 应用

1、散列表

核心:散列表用的是数组支持按照下标随机访问数据的特性。
这个例子举的好好~不抄了,粘原文,重点是下面的亮条条(广告

《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第1张图片

跟着学了几个排序算法后,此时此刻看到散列表想到的是计数排序呢,因为都是想着法儿地给元素和数组下标搞关系。

1.1、小概念
  • 键:也叫关键字,就是最终放到数据结构中的元素啦
  • 散列函数:也叫哈希函数。算命先生,告诉应该去数组的哪个坑里蹲着
  • 散列值:也叫哈希值。蹲的那个坑
《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第2张图片
1.2、散列函数

如果把元素都对应到了数组中,查找的时间复杂度就是O(1),看着很酷呢~
散列函数需要满足三点基本要求:

  • 散列函数计算得到的散列值是一个非负整数
  • 如果key1 = key2,那么hash(key1) = hash(key2)
  • 如果key1 != key2,那么hash(key1) != hash(key2)
    (补充一条:简单不烧脑更好)
1.3、散列冲突

实际中,比较难满足第三点要求,当存在key1 != key2,hash(key1) = hash(key2)时称作散列冲突。解决散列冲突常见的两种办法:

  • 开放寻址法
    思路:出现冲突就重新探测空闲可用的位置来存储数据。一种简单的线性探测方法如下。
《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第3张图片

删除操作:因为使用开放寻址法的时候,key相同的数据存储在同一个位置(但是我们不晓得是有几个相同的数据存储在这一份数据中),所以删除的时候不能直接删,而是标记位deleted,避免被寻址覆盖了。(会有很多的空间浪费吧~不环保,差评!)

《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第4张图片

【性能分析】因为算完还需要找合适的位置,最坏的情况可能需要挨个儿找一遍,O(n)啦

【更好的办法】
二次探测:探测步长变成n^2。
双重散列:使用一组散列函数,挨个算,知道有一个函数算出来没被占用的位置为止(这组散列函数应该很不容易吧)

  • 链表法
    思路:将散列值相同的元素用链表存起来,数组里存储的是这个链表的头的信息。
《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第5张图片
1.4、装载因子

装载因子可以用来表示数组中空位的多少,装在因子越大,说明空闲位置越少,冲突越多,散列表性能会下降。

load factor = 填入表中的元素的个数 / 散列表长度

2、实际应用中的散列表注意事项

2.1、散列函数的设计原则
  • 不能太复杂,避免消耗太多的计算时间
  • 生成的散列值要尽可能随机并且均匀分布
2.2、装载因子过大

动态扩容。

散列表的扩容需要重新计算哈希位置,搬移数据。装载因子特别小时,如果对空间消耗敏感,还可以动态缩容。

2.3、如何避免低效地扩容

避免一次性扩容,将新数据插入新的散列表的过程中搬移旧数据到新的散列表

2.4、解决冲突的方案选择

【开放寻址法】

  • 优点
    数据存储在数组中,可以利用CPU缓存加快查询速度。
    序列化相对简单
  • 缺点
    删除数据比较麻烦,更浪费内存空间。
  • 适用场景
    数据量比较小,装载因子小

【链表法】

  • 优点
    内存利用率高
    对大装载因子的容忍度更高
  • 缺点
    消耗内存
    非连续存储,对CPU缓存不友好。可以通过使用其他数据结构来替代链表优化效率
《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第6张图片
  • 适用场景
    存储大对象、大数据量的散列表
2.5、设计一个工业级的散列函数

像这种要考虑很多方面的问题,大致都只能给个方针啥的,作者给的,我抄过来啦~遇到的时候还能回来翻翻看:

一个工业级的散列表需要满足以下要求:

  • 支持快速的查询、插入、删除操作
  • 内存占用合理,不能浪费过多的内存空间
  • 性能稳定,极端情况下,散列表的性能也不会退化到无法接受的情况

如何实现:

  • 设计一个合适的散列函数
  • 定义装载因子阈值,并且设计动态扩容策略
  • 选择合适的散列冲突解决办法

举栗子的时候给了个HashMap的散列值计算的方法,看完头皮发麻。评论区有小牛角给了分析呢,好好看完,就抄过来啦

	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
《数据结构与算法之美》专栏阅读笔记5——散列表和哈希函数_第7张图片

找了一下位运算技巧:面试常用位运算技巧

位运算的这些没有找到可以总结的办法,所以记不住啦~回头找机会再瞄一瞄吧。

**TODO:**LRU实现

3、哈希算法

原理:将任意长度的二进制值串映射为固定长度的二进制值串的规则。
一个优秀的哈希算法要满足的几点要求:

  • 从哈希值不能反向推导处原始数据
  • 对输入数据非常敏感,哪怕原始数据只修改了一个Bit,哈希值也大不相同
  • 散列冲突概率很小
  • 执行效率高
应用
  • 安全加密
  • 唯一标识
  • 数据校验
  • 散列函数
  • 负载均衡
  • 数据分片
  • 分布式存储
    一致性哈希

你可能感兴趣的:(05_极客时间阅读笔记)