目录
1.哈希概念(主要应用于频繁查找的场景)
- 顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2N),搜索的效率取决于搜索过程中元素的比较次数。理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
- 插入元素:有了我们定义的哈希函数我们就可以将当前要存储的值当作函数的参数,我们将其传入哈希函数之后就可以由哈希函数计算出一个值作为我们要存储的函数位置。
- 查找元素:我们插入的时候由哈希函数计算出当前值来存入那么我们查找的时候也按照当前函数得出存储位置我们直接到相应位置来查找即可。
- 举个例子:这里我们写一个简单的哈希函数,通过要存储的值模除当前要存储的值:然后得到存储位置,那么我们查找相同元素得时候就可以通过哈希函数来求得存储位置来取到元素。
- 负载因子:
- 负载因子=哈希函数中插入元素个数/哈希存储空间大小。负载因子一定是小于1的因为我们一定要让新插入元素有位置存放。
2. 哈希冲突
对于两个数据元素的关键字 和 (i != j),有ki !=kj ,但有:Hash(ki ) == Hash( kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
也就是我们下面的例子
3.解决哈希冲突
- 首先我们要知道差生哈希冲突的原因主要有两个方面一个是哈希函数的设计会导致我们在存储元素的时候计算元素的存储位置的时候导致两个元素存储的位置相同,还有就是我们的空间问题,我们导致哈希冲突的主要原因就是我们在计算元素存储位置时候当前元素要存储的位置已经有了元素,但是相同的存储位置只能存储一个元素。那么我们如果将同一个位置可以存储多个元素那么我们是不是就也解决了哈希冲突,所以我们接下来从这两方面来入手解决哈希冲突。
3.1哈希函数的选择
-
1. 直接定制法--(常用)
- 取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况。我们来看一道题来体会当前哈希函数的应用场景。
-
2. 除留余数法--(常用)
- 设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数(这里最好取素数当作除数来取余因为这样产生的哈希冲突相对较少)p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。这里就相当于我们上面最一开始举例的方式。
-
3. 平方取中法--(了解)
- 假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址 平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
-
4. 折叠法--(了解)
- 折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
-
5. 随机数法--(了解)
- 选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。通常应用于关键字长度不等时采用此法
-
6.数学分析法--(了解)
- 设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。例如:假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改成12+34=46)等方法。数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况
- 总结:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突,所以我们真正解决哈希冲突的方式是解决数据的存储空间问题。
3.2.闭散列和开散列
3.2.1闭散列
- 闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
-
1. 线性探测
- 比如上面的场景,现在需要插入元素77,先通过哈希函数计算哈希地址,hashAddr为7,因此44理论上应该插在该位置,但是该位置已经放了值为7的元素,即发生哈希冲突。
- 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
- 插入
- 通过哈希函数获取待插入元素在哈希表中的位置如果该位置中没有元素则直接插入新元素,如果