当前位置: 首页 > news >正文

哈希表概述 -常见哈希函数和解决冲突的方法概述

可以把哈希表理解为一种高级的数组,这种数组的下标可以是很大的整数,浮点数,字符串甚至结构体。

哈希函数

核心是均匀,工程上常利用哈希函数把大数据量的样本,均匀哈希到多台机器、多个文件,从而省下内存使用。
性质

  • 输入的规模可能是无限的,输出规模相对有限
  • 单值函数,同样的输入对应同一个输出,完全无随机机制
  • 不同的输入可能对应同一个输出,可能发生哈希碰撞(散列冲突)
  • 大规模的输出,会均匀分布在输出域上

最后一条是哈希函数的核心性质,用好的哈希函数和尽量大的输出域规避碰撞。
好的哈希函数应当易于计算,并且尽量使计算出来的索引均匀分布

除留余数法

此方法为最常用的构造哈希函数方法。
在 OI 中,最常见的情况应该是键值为整数的情况,当键值的范围比较小的时候,可以直接把键值作为数组的下标。
但当键值的范围比较大,比如以1 0 9 10^9109范围内的整数作为键值的时候,就需要用到哈希表。一般把键值模一个较大的质数作为索引,也就是:
f ( x ) = x ( m o d p ) f(x)=x \pmod pf(x)=x(modp)
显然,本方法的关键在于选择合适的 p。根据经验,若哈希表表长为 m,通常 p 为小于或等于表长(最接近 m)的质数或不包含小于 20 质因数的合数。

字符串哈希

处理 key 为字符串的情况。
篇幅较大,详情。

直接定址法

取关键字的某个线性函数值为地址:
f ( x ) = a ⋅ k e y + b f(x)=a\cdot key+bf(x)=akey+b
优点是简单、均匀、不会产生冲突。但需要实现知道关键字的分布情况,适合查找表较小且连续的情况。

数字分析法

抽取关键字的一部分来计算地址。比如手机号的后四位。
适合关键字位数比较多的情况,如果事先知道关键字的分布且关键字分布的若干位分布较均匀,就可以考虑这个方法。

平方取中法

将关键字平方后取中间几位作为地址。
适合不知道关键字分布,而位数又大的情况。

折叠法

将关键字分割成位数相等的几部分,然后将这几部分叠加求和,再取后几位作为地址。
比如:
9876543210 → 987 ∣ 654 ∣ 321 ∣ 0 → 987 + 654 + 321 + 0 = 1962 → 962 9876543210 \to 987|654|321|0 \to 987+654+321+0=1962 \to 9629876543210987∣654∣321∣0987+654+321+0=1962962
适合不知道关键字分布,关键字位数较多的情况。

随机数法

用 random 随机函数值作为地址。
f ( x ) = r a n d o m ( x ) f(x) = random(x)f(x)=random(x)
适合关键字长度不等的情况。


总之,应视不同情况采用不同的哈希函数,考虑:

  • 计算地址所需时间
  • 关键字长度
  • 关键字分布情况
  • 哈希表大小
  • 记录、查找的频率

哈希冲突

在实际情况中,常常会出现两个不同的键值,他们用哈希函数计算出来的索引是相同的。这时候就需要一些方法来处理冲突。
在 OI 中,最常用的方法是拉链法,也称开散列法、链地址法

拉链法/开散列法/链地址法

拉链法是在每个存放数据的地方存放一个链表,如果有多个关键字索引到同一个地方,只要把他们都放进那个索引位置的链表里。
这种方法保证不会出现找不到地址的保障,也带来了时间损耗。查询时需要遍历整个链表。
假设哈希函数优秀,哈希表大小为N NN,地址的范围为1 … M 1 \ldots M1M,那么每次插入/查询的平均时间为O ( N M ) O(\frac{N}{M})O(MN)
Java、C++ 中封装的哈希表常用此方法。

实现
publicclassHashTable{privatestaticfinalintSIZE=1_000_000;privatestaticfinalintM=999_997;privatestaticclassNode{intnext;intvalue;intkey;Node(intnext,intvalue,intkey){this.next=next;this.value=value;this.key=key;}Node(){}}privatefinalNode[]data=newNode[SIZE+1];privatefinalint[]head=newint[M];privateintsize=0;privateintf(intkey){intr=key%M;returnr<0?r+M:r;}publicintget(intkey){for(intp=head[f(key)];p!=0;p=data[p].next){if(data[p].key==key)returndata[p].value;}return-1;}publicintmodify(intkey,intvalue){for(intp=head[f(key)];p!=0;p=data[p].next){if(data[p].key==key)return(data[p].value=value);}return0;}publicintadd(intkey,intvalue){if(get(key)!=-1)return-1;intidx=f(key);size=size+1;data[size]=newNode(head[idx],value,key);head[idx]=size;returnvalue;}}

封装模板:

publicclassHashMap{staticclassData{longu;intv;intnex;Data(longu,intv,intnex){this.u=u;this.v=v;this.nex=nex;}}privatefinalintSZ;privatefinalData[]e;privatefinalint[]h;privateintcnt;publicHashMap(intSZ){this.SZ=SZ;this.e=newData[SZ<<1];this.h=newint[SZ];this.cnt=0;}privateinthash(longu){intr=(int)(u%SZ);returnr<0?r+SZ:r;}publicDataref(longu){inthu=hash(u);for(inti=h[hu];i!=0;i=e[i].nex){if(e[i].u==u)returne[i];}cnt=cnt+1;e[cnt]=newData(u,-1,h[hu]);h[hu]=cnt;returne[cnt];}}

开放定址法/闭散列法

闭散列方法把所有记录直接存储在散列表中,如果发生冲突则根据某种方式继续进行探查。比如线性探测法二次探测法随机探测法
线形探测法:
f i ( x ) = ( f ( x ) + d i ) ( m o d m ) ( d i = 1 , 2 , 3 , ⋯ , m − 1 ) f_i(x)=(f(x)+d_i) \pmod m (d_i = 1,2,3, \cdots, m-1)fi(x)=(f(x)+di)(modm)(di=1,2,3,,m1)
为了不让关键字都聚集在某一区块,可以使用二次探测法:
f i ( x ) = ( f ( x ) + d i ) ( m o d m ) ( d i = 1 2 , − 1 2 , 2 2 , − 2 2 , ⋯ , q 2 , − q 2 , q ≤ m / 2 ) f_i(x)=(f(x)+d_i) \pmod m (d_i = 1^2, -1^2, 2^2, -2^2, \cdots, q^2, -q^2, q≤m/2)fi(x)=(f(x)+di)(modm)(di=12,12,22,22,,q2,q2,qm/2)
随机探测法:
f i ( x ) = ( f ( x ) + d i ) ( m o d m ) ( d 是一个随机数列 ) f_i(x)=(f(x)+d_i) \pmod m (d 是一个随机数列)fi(x)=(f(x)+di)(modm)(d是一个随机数列)
Python、Go 中封装的哈希表常用此方法。其中 Go 还使用溢出桶,但并不是下文要提到的「公共溢出区法」。

再哈希函数法

f i ( x ) = R H i ( x ) ( i = 1 , 2 , ⋯ , k ) f_i(x) = RH_i(x)(i=1,2,\cdots,k)fi(x)=RHi(x)(i=1,2,,k)
R H i RH_iRHi为不同的哈希函数,可以把上文说的什么除留余数法、直接定址法、随机数法全都用上。每当发生一次冲突就换一个哈希函数。
这种方法能使得关键字不聚集,但也增加了计算时间。

公共溢出区法

此方法把所有冲突的关键字统统存放在一个公共溢出区。
查找时先在哈希表中查找,若没找到,再取溢出区顺序查找。
在冲突数据较少时,此方法查找性能还是很高的。
#atom

http://www.cnnetsun.cn/news/114201.html

相关文章:

  • 19、Linux 帧缓冲接口设计与图形库应用
  • 人才发展ℓℓ 人才盘点怎么做?这篇完全应用手册给出答案
  • 真相来了|字节跳动的人才真相:真正拉开差距的,是“人才密度”(附人才密度清单)
  • 力扣(LeetCode) 66: 加一 - 解法思路
  • HC32L130精准延时实现指南
  • 收藏必看!大学生网络安全学习5大方向,校招不踩坑,小白也能逆袭!
  • 收藏!从“黑客梦“到网络安全专家:过来人告诉你自学路线图
  • Bagisto 产品更新后,前台默认语言的内容不更信,其他语言正常。
  • 【收藏】运维转网安的黄金路径:4个高适配岗位+3步落地指南,薪资提升50%
  • 大语言模型全解析:一篇文章带你深入理解AI的强大能力!
  • 【网络】网络通信模型
  • Slimjet浏览器:基于Chromium的高效网页浏览解决方案,内置广告拦截与多功能工具
  • AMP页面还要做吗?2025替代方案及优化指南
  • 为什么你的RAG总是“一本正经地胡说八道”?EAG-RAG揭示真相,准确率暴涨300%的秘密!
  • iOS 项目中证书管理常见的协作问题
  • 理解线程不安全:从观察到原因分析
  • 《Java Web开发入门很简单》——学习笔记,新手入门,收藏这篇就够了
  • 2025年,国内外最火的10款降AI率工具亲测!(持续更新)
  • 基于大数据的餐饮食材管理系统的设计与实现开题报告
  • 基于大数据的交通信号智能控制系统的设计与实现开题报告
  • 基于大数据的交通信号智能控制系统的设计与实现任务书
  • 蜘蛛池站点优化思路分享
  • 2025 OA 选型关键看这 4 点:集成、灵活、安全、易用,附高性价比系统清单
  • 图神经网络与pytorch
  • Xiaomi 商城页面布局(部分)
  • FPGA以太网升级程序:便捷qspi Flash升级,具备校验功能,适用于Xilinx 7系列...
  • 运料小车装卸料控制:西门子1200PLC与TP700触摸屏联机仿真博途16
  • S32K311启动过程中,向量表重定向
  • 从蓝图到产线:高效产品信息传递的桥梁建设
  • 时间复杂度