哈希

经典哈希函数特性

  • 输入域是无穷大的,输出域是有限的
  • 固定的输入,返回的输出hashCode也是固定的
  • 必然有不同的输入相同的输出
  • 哈希函数具有离散性

常见的HASH算法

  • 直接定址法
    • 直接以关键字K或者K加上某个常数(K+C)作为HASH地址
  • 数字分析法
    • 提供关键字中取值比较均匀的数字作为Hash地址
  • 除留余数法
    • 用关键字K除以某个不大于Hash表长度m的数P,将所得余数作为Hash地址
  • 分段叠加法
    • 按照Hash表地址位数将关键字分成位数相等的几部分,其中最后一部分可以比较短,然后将这几部分相加,舍弃最高进位后的结果就是该关键字的Hash地址
  • 平方取中法
    • 如果关键字各个部分分布都不均匀,则可以先求出它的平方值,然后按照需求取中间的几位作为Hash地址
  • 伪随机数法
    • 采用一个伪随机数当作Hash函数
  • 斐波那契法
    • 2 ^ 32 * 0.6180339887为了让数据更加的散列,减少哈希碰撞

并查集

  • 作用
    • 快速的查找两个元素是否属于一个集合
    • 两个元素各自所在的集合,将两个集合合并

解决hash冲突的方法

再散列方法(开放地址法)

线性探测
  • 冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
  • 优点:易于实施;总是找到一个位置(如果有);当表不是很满时,平均情况下的性能非常好。
  • 缺点:表的相邻插槽中会形成“集群”或“集群”键;当这些簇填满整个阵列的大部分时,性能会严重下降,因为探针序列执行的工作实际上是对大部分阵列的穷举搜索。
随机散列
  • di=伪随机数序列。
    具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。
    例如,已知哈希表长度m=11,哈希函数为:H(key)= key % 11,则H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为69,则H(69)=3,与47冲突。
    如果用线性探测再散列处理冲突,下一个哈希地址为H1=(3 + 1)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 + 2)% 11 = 5,还是冲突,继续找下一个哈希地址为H3=(3 + 3)% 11 = 6,此时不再冲突,将69填入5号单元。
    如果用二次探测再散列处理冲突,下一个哈希地址为H1=(3 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时不再冲突,将69填入2号单元。
    如果用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,……..,则下一个哈希地址为H1=(3 + 2)% 11 = 5,仍然冲突,再找下一个哈希地址为H2=(3 + 5)% 11 = 8,此时不再冲突,将69填入8号单元。

双重哈希(再Hash)

  • 这种方法是同时构造多个不同的哈希函数:Hi=RH1(key) i=1,2,…,k。当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

链地址法

  • 这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

建立公共溢出区

这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

拉链法与开放地址法相比的缺点

  • 拉链法的优点
    与开放定址法相比,拉链法有如下几个优点:

    • 拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
    • 由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
    • 开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
    • 在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
  • 拉链法的缺点

    • 指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

经典HASH问题

  • 有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数,但是内存限制只有2G
    • 使用哈希函数分流
    • 分别使用hashMap进行统计,找到最多的数
  • 32位无符号整数的范围是0-42亿,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围内必然有没有出现过的数,可以使用最多10M的内存,只用找到一个没出现过的数即可,该如何找?
    • 范围分成64个区间
    • 申请一个$2^{32}$ / 64 的bitMap,大约占500M/64 = 8M空间
    • 必然有填不满的区间,然后根据这个区间上缺的数遍历这个文件

总结

  • 根据内存限制决定区间的大小,根据区间大小,得到有多少个变量,来记录每个区间的数出现的次数
  • 统计区间上的数的出现次数,找到不足的区间
  • 利用bitMap对不满的区间,进行这个区间上的数的词频统计

哈希
https://x-leonidas.github.io/2022/02/01/02数据结构及算法/哈希/
作者
听风
发布于
2022年2月1日
更新于
2025年4月1日
许可协议