目录
哈希表其实就是基于数组衍生而来的,哈希表的查找时间复杂度近乎O(1)。用空间换时间的思想。
高效查找的奥秘就在于数组的随机访问特性(在数组中只要知道元素的下标-索引,可以立即在数组中取得该元素)。
一、哈希函数的引入
当有一组数据,要查询某个特定的元素是否在数组内,就将原数组的元素映射为新的布尔数组的下标,要查询某个元素是否存在,只需在对应的布尔数组中查看该索引即可。
当数组的元素跨度较大10w,100w等就得根据元素最大值来开辟数组,会浪费大量空间,当数组的元素存在负数,是无法直接进行映射的。这时引入了哈希函数。
哈希函数:将任意的key(所有数据类型)映射为数组下标。
哈希冲突:原本两个不同的key值,经过哈希函数运算后得到两个相同的int值(从数学角度一定存在)。
在处理整形映射为整形时,最常用的手段就是进行取模运算,一组很大的数组映射为一组很小的数组。例如100w和10w经过取模10之后都得到零(通过哈希冲突)。
二、解决哈希冲突的思路
2.1基于闭散列的思路
当发生哈希冲突时,找冲突位置的附近是都存在空闲位置,如果有就放入冲突元素。(好放难查更难删)。
线性探测:从不发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置即可。
要在哈希表中查询某个元素是否存在:要查找x是否存在,首先对x%n = y。如果发现y未知的索引对应的值不是x,那么就继续向后遍历,若一直向后遍历走到下一个空闲位置还没找到待查找元素,说明该元素不存在。删除也需要先找到再删除。
2.2基于开散列的思路
如果出现哈希冲突,就在对应的位置上将该数组元素变为一个链表(拉链法)。哈希表的数组其实存储的是每个链表的头结点,整个哈希表就是数组+链表的结构。
当要查找某个元素x是否存在,将x取模拿到对应链表的头结点,只要遍历这个链表即可。
基于这种方案解决的哈希冲突就想整张表的冲突问题转为几个链表的冲突问题。
这种思路简单实用,基本是各种哈希表解决冲突的方案首选。
若某个链表冲突非常严重,该立案表长度很长,查找元素又会为链表的遍历时间复杂度又为O(n)。
=》1.针对整个哈希表进行扩容(对原数组扩容),冲突链表上的元素放到新数组上时会进行重新取模,降低冲突概率。(JDK中当整个哈希表元素个数<64采用这种方案)
=》2.将某些冲突的链表再次拆拆解成子哈希表或树化(二叉搜索平衡树),原哈希表中其他位置不影响,不改动,只处理这个冲突严重的链表。(JDK中当整个哈希表元素个数>=64,且某个链表长度>=8,会将此链表转为BR