散列表学习笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012292754/article/details/84475996

1 散列介绍

散列表,又称 Hash Table,哈希表。用的是数组支持按照下标随机访问数据的特性,所以散列表是数组的扩展,由数组演化而来。

1.1 设计散列函数的要求

  1. 由散列函数得到的散列值是非负整数;
  2. 如果 key1 = key2, 那么 hash(key1) = hash(key2);
  3. 如果 key1 不等于 key2, 那么 hash(key1) 不等于 hash(key2);

1.2 解决散列冲突的方法

在真实的情况下,找到一个不同key对应的散列值不一样的散列函数,几乎不可能。
在这里插入图片描述

1.2.1 线性探测(Linear Probing)

往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用,就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。

1.2.2 二次探测(Quadratic Probing)

它和线性探测很像,线性探测每次探测步长1,即hash(key)+0,hash(key)+1…;
二次探测就是探测的步长变成平方,即hash(key)+0,hash(key)+1^2…;

1.2.3 双重散列(Double hashing )

不仅要使用一个散列函数,使用的是一组散列函数,hash1(key),hash2(key)…,先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,直到找到空闲的位置。

1.3 装载因子(load factor)

装载因子 = 填入表中的元素个数 / 散列表的长度
装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。

1.4 链表法

在散列表中,每个桶(bucket) 或者 槽 (slot) 会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。

1.5 思考题

  • 0 万条 URL 访问日志,如何按照访问次数给 URL 排序
遍历10万条数据,用URL 为 key,访问次数作为 value,存入散列表,同时记录下访问的最大次数MAX, 时间复杂度 O(n);
如果MAX不是很大,用桶排序,时间复杂度 O(n)。如果MAX 非常大,就用快排,时间复杂度 O(n logn)

  • 有两个字符串数组,每个数组大约有 10 万条字符串,如何快速找出两个数组中相同的字符串?
用第一个字符串数组构建散列表,key 为字符串,value 为出现次数。然后再次遍历第二个字符串数组,以字符串为key 在散列表中查找,如果value > 0,说明存在相同的字符串,时间复杂度O(n)

2 如何设计散列函数

散列函数设计的好坏,决定了散列表冲突的概率大小,也直接决定了散列表的性能。

  • 散列函数不能设计太复杂,如果过于复杂,势必会消耗很多计算时间,;
  • 散列函数生成的值尽可能随机且均匀分布;

2.1 散列函数的设计方法

直接寻址法,平方取中法,折叠法,随机数法,

2.2 散列冲突

Java 中 LinkedHashMap 就采用了链表法解决,ThreadLocalMap通过线性探测的开放寻址法来解决。

2.2.1 开放寻址法

  • 优点 : 散列表的数据存储在数组中,有效利用CPU缓存加快查询速度;序列化比较简单,而链表包含指针,序列化比较困难;
  • 缺点:删除数据比较麻烦,需要特殊标记已经删除的数据;所有数据存储在数组中,比起链表,冲突的代价更高;装载因子不能太大,比链表法更加浪费内存
  • 当数据量小,装载因子小的时候,适合采用开放寻址法;

2.2.2 链表法

  • 优点:对内存的利用率比开放寻址法高,因为链表节点可以在需要的时候再创建,不需要开放寻址法那样先申请好;
  • 对大装载因子的容忍度更高,开放寻址法只适用装载因子小于1,接近1时就会有大量的散列冲突。但是对于链表,只要散列函数的值随机均匀,即使装载因子变成10,也就是链表长度变长,查找效率有所下降
  • 适合存储大对象、大数据量的散列表,比起开放寻址法,它更加灵活,支持更多的优化策略,如可以用红黑树代替链表;

3 HashMap 举例

  1. 初始大小
    HashMap默认初始大小是16,这个初始值可以设置
  2. 装载因子和动态扩容
    最大的装载因子默认0.75,当HashMap元素超过 0.75*capacity,就会启动扩容,扩容为原来的2倍。
  3. 散列冲突的解决
    HashMap 底层采用链表法解决散列冲突。在JDK1.8中,进行了优化,引入了红黑树。当链表长度太长(默认8),链表就转换为红黑树。利用红黑树的快速增删改查的特点提高性能。当红黑树节点个数少于8,红黑树又转换为链表。因为在数据量少的时候,红黑树要维护平衡,比起链表,性能优势不明显。

4 设计一个工业级的散列表

要求:

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

4.1 如何实现一个工业级的散列表

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

猜你喜欢

转载自blog.csdn.net/u012292754/article/details/84475996