数据结构 -- 哈希表原理

目录

一、散列表(Hash Table)

1、散列函数

2、散列冲突

1、开放寻址法:

2、链表法:

3、方法比较

二、散列表设计

三、散列表应用

1、通过散列表和双向链表的组合,实现一个高效的LRU 缓存淘汰算法:

2、通过散列表和跳表的组合,实现Redis有序集合

3、通过散列表和双向链表的组合,实现LinkedHashMap

参考链接


一、散列表(Hash Table)

散列表又叫“哈希表”或“Hash表”。散列表用的是数组支持按照下标随机访问数据的特性,时间复杂度为O(1),所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。

通过散列函数(或叫哈希函数)把元素的键值(key)映射为下标(散列值或叫哈希值),然后将数据存储在数组中对应下标的位置。当我们按照键值查询元素时,我们用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据

1、散列函数

hash(key),其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。该如何构造散列函数呢?散列函数设计的三点基本要求:

  • 散列函数计算得到的散列值是一个非负整数;
  • 如果 key1 = key2,那 hash(key1) == hash(key2);
  • 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。

2、散列冲突

在构建散列函数时,其实要满足三点基本要求中的第三点很难,要想找到一个不同的 key 对应的散列值都不一样的散列函数,几乎是不可能的,所以就会产生散列冲突,那要如何避免呢?常用的散列冲突解决方法有两类,开放寻址法(open addressing)和 链表法(chaining)

不管采用哪种探测方法,当散列表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证散列表的操作效率,一般情况下,我们会尽可能保证散列表中有一定比例的空闲槽位。我们用装载因子(load factor)来表示空位的多少

  • 散列表的装载因子 = 填入表中的元素个数 / 散列表的长度

1、开放寻址法:

线性探测法:当我们往散列表中插入数据时,通过散列函数求出要查找元素的键值对应的散列值,然后比较数组中下标为散列值的元素和要查找的元素,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置插入,直到找到为止

二次探测(Quadratic probing)法:线性探测每次探测的步长是 1,那它探测的下标序列就是 hash(key)+0,hash(key)+1,hash(key)+2……而二次探测探测的步长就变成了原来的“二次方”,也就是说,它探测的下标序列就是 hash(key)+0,hash(key)+12,hash(key)+22……

双重散列法:意思就是不仅要使用一个散列函数。我们使用一组散列函数 hash1(key),hash2(key),hash3(key)……我们先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,依次类推,直到找到空闲的存储位置

2、链表法:

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

3、方法比较

  • 当数据量比较小、装载因子小的时候,适合采用开放寻址法散列表
  • 当存储大对象、大数据量的时候,适合采用链表法散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表。

二、散列表设计

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

首先,散列函数的设计不能太复杂;其次,散列函数生成的值要尽可能随机并且均匀分布,散列到每个槽里的数据也会比较平均,不会出现某个槽内数据特别多的情况。

设计思路:

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

三、散列表应用

1、通过散列表和双向链表的组合,实现一个高效的LRU 缓存淘汰算法:

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

1、添加: 先看这个数据是否已经在缓存中。如果已经在其中,需要将其移动到双向链表的尾部;如果不在其中,还要看缓存有没有满。如果满了,则将双向链表头部的结点删除,然后再将数据放到链表的尾部;如果没有满,就直接将数据放到链表的尾部。

2、查询: 每当缓存命中(即缓存数据被访问),则将数据移到链表尾部; 

3、删除:按照双向链表删除节点方法删除节点

2、通过散列表和跳表的组合,实现Redis有序集合

3、通过散列表和双向链表的组合,实现LinkedHashMap

参考链接

https://time.geekbang.org/column/intro/126  《数据结构与算法之美--王争》

发布了35 篇原创文章 · 获赞 22 · 访问量 1136

猜你喜欢

转载自blog.csdn.net/m0_37845735/article/details/103750243