目录
一、什么是哈希表
哈希表是一种基于数组的随机访问特性衍生出来的数据结构(用空间换时间的思想)
例如:
如果存在一组数据{9,5,2,7,3,6,8},要查询某个特定的元素是否在数组中,可以将原数组的元素映射成为新的布尔数组的下标。要看某个元素是否存在,只需在对应的布尔值数组中查看该索引是true还是false即可。
但是,这种方法的缺陷也很明显
当数组的元素跨度较大,例如{10w, 1, 99, 1000w...}, 就得根据元素的最大值来开辟新数组,浪费大量的空间。同时,当数组中存在负数的时候,无法将负数映射为新数组的下标。
由此引申出了哈希函数和哈希冲突
哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表)
1.1 哈希函数和哈希冲突
哈希函数:将任意的Key值(所有的数据类型)映射为数字下标 --- (Object -->int)
哈希冲突:原本两个不同的Key值,经过哈希函数运算后得到两个相同的val值
ex: 在处理整型映射时,最常用的手段就是进行取模运算,将一组很大的数组映射为很小的数字
nums[1,10,101,1w,100w....] % 10==> 将nums数组的每个元素映射为arr[0.....9]
虽然将数组内的数字通过哈希函数映射为了很小的一组数字,但也存在问题
例如1和11进行%10运算,得到的值都为1 ======》这就是哈希冲突
1.2 解决哈希冲突的思路
1.2.1基于闭散列方案的思路
当发生哈希冲突时,找冲突附近是否存在空闲位置,如果有就放入冲突元素
ex:假设现在有一组数据[1,2,3,19,120,121],通过哈希函数对101取模后放入新数组arr中,19和120对101取模都为19,所以我们只能在arr[19]附近找空位置来存放120,例如此时arr数组中下标为20的位置就是空的,所以将120存入arr[20]中。
在这个哈希表中查找一个元素的流程:要查找120是否存在,首先对120%101 = 19,找19这个索引对应的值是不是120,如果不是,就向后继续遍历查找,如果在后面找到,则120这个元素存在,否则说明该元素不存在!
缺点:
当整个哈希表出现大量的哈希冲突时,查找某个元素基本上就等同于遍历数组,退化为O(n)
1.2.2基于开散列方案的思路
如果出现哈希冲突,就在对应的位置上将数组元素变为一个链表(拉链法)
ex:[1,2,3,19,120] 通过哈希函数对101取模后放入新数组arr中
19和120发生了哈希冲突,取模后都为19,此时,将120作为链表的节点尾插(头插)到19对应的元素之后。
当要查找某个元素x是否存在,就x%101取模拿到对应链表的头结点,只需要遍历这个链表即可。
因此
基于开散列的方案,哈希表的数组其实储存的就是每个链表的头节点!
整体的哈希表就是一个数组+链表的结构
基于这种方案解决的哈希冲突就将整张表的哈希冲突问题转为某几个链表的冲突问题
1.2.3开散列方案中存在的问题和解决方法
若当前开散列方案下,某个链表的冲突非常严重,该链表的长度过长,查找元素就又会退化为链表的遍历O(n)
方案1:针对整个哈希表进行扩容(对原数组扩容),冲突链表上的元素放到新数组上时就会重新进行取模运算,降低冲突概率 (c++采取方案1进行大量冲突的处理)
方案2:将某些冲突的链表再次变为哈希表或者树化(二分平衡搜索树),哈希表中其他的位置不影响,不改动,只处理这个冲突严重的链表。

本文深入探讨了哈希表的基本概念,包括哈希函数、哈希冲突及其解决方案,并详细介绍了基于拉链法的哈希表实现过程,涵盖节点类定义、哈希函数设计、扩容策略及基本操作。
最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



