先分清位置、哈希值和键这三件事
哈希表把键经过计算映射到有限的存储位置,目的是减少查找时需要比较的候选项。键的种类可能远多于槽位数量,因此不同键落到同一位置并不罕见。发生冲突只表示需要继续区分候选项,不能直接推出两条记录相同,更不能据此覆盖已有记录。
本文使用七个槽位,编号 0 至 6,教学函数设为 h(k)=k 除以 7 的余数。它直接给出桶下标,便于手算。真实实现常先生成完整哈希值,再根据容量换算位置,所以完整哈希值不同的键也可能落到同一桶。这里的简化规则不代表 Python 字典的内部算法。
原创插入例:三个编号落到同一个桶
依次插入编号 10、17、24,关联值分别为“待处理”“已核对”“需补充”。三者除以 7 的余数都为 3。若使用链地址法,桶 3 保存三组键值对;查找 17 时,进入桶 3 后逐个比较键,跳过 10,匹配 17,再返回“已核对”。
此时插入 (17,“完成”),才应更新原有键 17 的值,而不新增第四个不同键。若插入的是 (31,“完成”),31 虽然也进入桶 3,却是一个新键。可以把规则概括为:哈希负责缩小搜索范围,键相等负责确认记录身份。二者分工不同。
换用线性探测,追踪实际槽位
线性探测把记录直接放入数组:目标槽位已被其他键占据时,就按约定继续检查后面的槽位,并在末端回到开头。以上三个键依次放到 3、4、5。查找 24 从槽位 3 开始,比较 10、17、24,第三次才成功,不能在首个不匹配处立即停止。
查找缺失的 31 时,同样从 3 开始,经过 3、4、5,直到遇到从未使用的空槽 6,才判定不存在。这个停止条件依赖插入和查找使用同一探测序列。如果数组已满,还必须限制最多检查整个容量,避免绕圈。扩容和终止判断都属于实现契约。
删除为什么常需要留下标记
若把槽位 4 的键 17 删除,并直接当作“从未使用”,查找 24 就会在 4 提前停止,尽管 24 仍在 5。在线性探测的常见设计中,删除位置会留下墓碑标记:这里目前没有有效记录,但查找必须继续。标记描述的是探测历史,不是一个普通键。
再插入键时,可以记录遇到的第一个墓碑作为候选位置,但仍要按规则确认后面没有同名键,否则可能制造重复键。具体删除算法也可以搬移部分后续记录;不能只背诵“必须墓碑”,而要说明所选实现怎样维持查找路径的连续性。
Python 字典还要求键的规则相互一致
Python 官方数据模型要求相等的可哈希对象具有相同的哈希值,反向推理则不成立。设计自定义键时,如果相等性由编号决定,哈希也应与该编号保持一致。否则两个逻辑上相同的对象可能被送往不同查找路径,破坏字典所依赖的契约。
键在作为字典键期间,参与相等性和哈希计算的状态不应变化。把一个按内容比较且内容可变的列表直接当键会被拒绝。不可变元组也不是无条件可哈希:如果它包含不可哈希的列表,同样不能用。应检查组成元素,而不是只看最外层容器的名称。
还可以检验一个容易误解的 Python 例子:整数 1、浮点数 1.0 和布尔值 True 在通常的相等比较中相等,用它们依次给同一字典赋值,会指向同一个键条目,而不是三个独立条目。这与“不同但碰撞”的 10 和 17 不同。若业务必须区分这些类型,可以设计包含类型标签的复合键,并确保标签规则与业务身份一致;不能期待哈希表替业务自动决定两条记录是否应当分开。
自测:更新、碰撞与删除后的查找
在空的七槽表中按线性探测插入 8、15、22、6,它们分别落在哪些槽?答案是 1、2、3、6。再插入 (15,新值),应更新槽 2,记录数量不增加。删除 15 并留下墓碑后,查找 22 要经历槽 1、2、3,不能在墓碑处返回不存在。
接着插入 29,假定实现会复用第一个墓碑,同时先完成重复键检查。29 的起点为 1;经过 1、2、3 并在 4 找到从未使用的槽后,确认没有 29,最终可写入槽 2。若答案为 4,可能采用不复用墓碑的另一策略;必须把策略写清楚,不能混用两种过程。
平均很快,不代表每次都只查一次
定义装载因子为有效记录数除以槽位数。四条记录、七个槽位时是 4/7,约 0.571,但它不能单独告诉你本次查找要比较几次:键分布、冲突策略、删除标记和容量都会影响路径。在本教学函数下,连续选择相差 7 的键,就会刻意形成聚集。
常见哈希表在合理分布和容量管理下能提供很快的平均查找,但严重碰撞可能让一次查找接近线性扫描。扩容时也不是把旧数组原样放大:若位置依赖容量,记录需要按照新规则重新安排。不能将一次平均成本描述理解成对所有输入的时间保证。
别把教学哈希当成安全摘要
这里的余数函数用于解释寻址,没有防伪或保密能力。Python 的普通 hash 也不应当作稳定的跨进程文件指纹;官方文档说明字符串等类型的哈希通常带有随机化,进程之间可能不同。存储长期标识、做完整性校验或处理密码,需要各自合适的方案。
复习时给每条记录同时画出原键、计算结果和最终位置,然后对成功查找、失败查找、同键更新、删除后查找各追踪一次。能解释“为什么继续”和“什么时候停止”,就能看出冲突处理是否正确。本文使用的小表只帮助理解机制,不能代替真实实现的性能测试。
参考资料
下一步
如想练习哈希表与数据结构推演,请说明课程使用的冲突处理方法,以及已经完成的插入、查找或删除步骤。相关辅导能否安排,取决于学科匹配和导师时间。
