news 2026/3/24 18:42:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
哈希表概述 -常见哈希函数和解决冲突的方法概述

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

哈希函数

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

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

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

除留余数法

此方法为最常用的构造哈希函数方法。
在 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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/16 10:49:44

Git入门学习

Git&#xff1a;分布式管理系统(存档)1. Your Identity 配置你的信息git config --global user.name "827dls"git config --global user.email 1670704430qq.com红色部分表示对第二个存档进行修改 非常直观上传GitHub查看提交日志 : git log

作者头像 李华
网站建设 2026/3/24 15:32:21

项目实战05—XXX火力发电厂工业蒸汽量预测

火力发电是一种很常用的发电技术,但是火力发电的转换效率并不高。其中蒸汽压力的高低直接关系到火力发电的效率,火力发电的效率与蒸汽的压力之间的关系并不是正相关关系。 火力发电过程要尽量使水处在蒸发的临界状态,这时火力发电的效率最高。因此,火力发电厂需要及…

作者头像 李华
网站建设 2026/3/24 9:13:19

在职备战法考,先择校还是先备考?

许多在职考生都听过一个建议&#xff1a;“别想太多&#xff0c;先学起来。”于是&#xff0c;你匆忙找来资料&#xff0c;埋头苦学两月&#xff0c;却越发感到方向模糊、效率低下、坚持困难……这时你可能才意识到&#xff1a;在错误的道路上“先出发”&#xff0c;往往意味着…

作者头像 李华
网站建设 2026/3/24 14:43:52

AgentScope x RocketMQ:打造企业级高可靠 A2A 智能体通信基座

作者&#xff1a;琛琪、稚柳 引言 Agentic AI 时代已至&#xff0c;在智能客服、代码生成、流程自动化等场景中&#xff0c;多智能体&#xff08;Multi-Agent&#xff09;协作正从构想走向落地。然而&#xff0c;当多个 Agent 需要像一个团队那样高效协作时&#xff0c;脆弱的…

作者头像 李华