前面我们已经讲到了数组和链表,数组能通过下标 O(1) 访问,但是删除一个中间元素却要移动其他元素,时间 O(n)。 循环双端链表倒是可以在知道一个节点的情况下迅速删除它,但是吧查找又成了 O(n)。

难道就没有一种方法可以快速定位和删除元素吗?似乎想要快速找到一个元素除了知道下标之外别无他法,于是乎聪明的计算机科学家又想到了一种方法。 能不能给每个元素一种『逻辑下标』,然后直接找到它呢,哈希表就是这种实现。它通过一个哈希函数来计算一个元素应该放在数组哪个位置,当然对于一个 特定的元素,哈希函数每次计算的下标必须要一样才可以,而且范围不能超过给定的数组长度。

我们还是以书中的例子说明,假如我们有一个数组 T,包含 M=13 个元素,我们可以定义一个简单的哈希函数 h

h(key) = key % M

这里取模运算使得 h(key) 的结果不会超过数组的长度下标。我们来分别插入以下元素:

765, 431, 96, 142, 579, 226, 903, 388

先来计算下它们应用哈希函数后的结果:

下边我画个图演示整个插入过程(纯手工绘制,原谅我字写得不太优雅):

哈希冲突 (collision)

这里到插入 226 这个元素的时候,不幸地发现 h(226) = h(96) = 5,不同的 key 通过我们的哈希函数计算后得到的下标一样, 这种情况成为哈希冲突。怎么办呢?聪明的计算机科学家又想到了办法,其实一种直观的想法是如果冲突了我能不能让数组中 对应的槽变成一个链式结构呢?这就是其中一种解决方法,叫做 链接法(chaining)。如果我们用链接法来处理冲突,后边的插入是这样的:

这样就用链表解决了冲突问题,但是如果哈希函数选不好的话,可能就导致冲突太多一个链变得太长,这样查找就不再是 O(1) 的了。 还有一种叫做开放寻址法(open addressing),它的基本思想是当一个槽被占用的时候,采用一种方式来寻找下一个可用的槽。 (这里槽指的是数组中的一个位置),根据找下一个槽的方式不同,分为:

  • 线性探查(linear probing): 当一个槽被占用,找下一个可用的槽。h(k,i)=(h′(k)+i)%m,i=0,1,...,m−1h(k,i)=(h′(k)+i)%m,i=0,1,...,m−1
  • 二次探查(quadratic probing): 当一个槽被占用,以二次方作为偏移量。 h(k,i)=(h′(k)+c1+c2i2)%m,i=0,1,...,m−1h(k,i)=(h′(k)+c1+c2i2)%m,i=0,1,...,m−1
  • 双重散列(double hashing): 重新计算 hash 结果。 h(k,i)=(h1(k)+ih2(k))%mh(k,i)=(h1(k)+ih2(k))%m

我们选一个简单的二次探查函数 h(k,i)=(home+i2)%mh(k,i)=(home+i2)%m,它的意思是如果 遇到了冲突,我们就在原始计算的位置不断加上 i 的平方。我写了段代码来模拟整个计算下标的过程:


这段代码输出的结果如下:


遇到冲突之后会重新计算,每个待插入元素最终的下标就是:

Cpython 如何解决哈希冲突

如不同 cpython 版本实现的探查方式是不同的,后边我们自己实现 HashTable ADT 的时候会模仿这个探查方式来解决冲突。

哈希函数

到这里你应该明白哈希表插入的工作原理了,不过有个重要的问题之前没提到,就是 hash 函数怎么选? 当然是散列得到的冲突越来越小就好啦,也就是说每个 key 都能尽量被等可能地散列到 m 个槽中的任何一个,并且与其他 key 被散列到哪个槽位无关。 如果你感兴趣,可以阅读后边提到的一些参考资料。视频里我们使用二次探查函数,它相比线性探查得到的结果冲突会更少。

装载因子(load factor)

如果继续往我们的哈希表里塞东西会发生什么?空间不够用。这里我们定义一个负载因子的概念(load factor),其实很简单,就是已经使用的槽数比哈希表大小。 比如我们上边的例子插入了 8 个元素,哈希表总大小是 13, 它的 load factor 就是 8/13≈0.628/13≈0.62。当我们继续往哈希表插入数据的时候,很快就不够用了。 通常当负载因子开始超过 0.8 的时候,就要新开辟空间并且重新进行散列了。

重哈希(Rehashing)

当负载因子超过 0.8 的时候,需要进行 rehashing 操作了。步骤就是重新开辟一块新的空间,开多大呢?感兴趣的话可以看下 cpython 的 dictobject.c 文件然后搜索 GROWTH_RATE 这个关键字,你会发现不同版本的 cpython 使用了不同的策略。python3.3 的策略是扩大为已经使用的槽数目的两倍。开辟了新空间以后,会把原来哈希表里 不为空槽的数据重新插入到新的哈希表里,插入方式和之前一样。这就是 rehashing 操作。

HashTable ADT

实践是检验真理的唯一标准,这里我们来实现一个简化版的哈希表 ADT,主要是为了让你更好地了解它的工作原理,有了它,后边实现起 dict 和 set 来就小菜一碟了。 这里我们使用到了定长数组,还记得我们在数组和列表章节里实现的 Array 吧,这里要用上了。

解决冲突我们使用二次探查法,模拟 cpython 二次探查函数的实现。我们来实现三个哈希表最常用的基本操作,这实际上也是使用字典的时候最常用的操作。

  • add(key, value)
  • get(key, default)
  • remove(key)


具体的实现和代码编写在视频里讲解。这个代码可不太好实现,稍不留神就会有错,我们还是通过编写单元测试验证代码的正确性。公众号:学习py最风sao的方式欢迎大家继续关注!

哈希表数据结构_算法与数据结构-哈希表相关推荐

  1. 数据结构与算法五:哈希表-哈希函数设计原则-哈希冲突解决方案

    一.哈希表的定义: 二.哈希表举例: 哈希函数就是映射关系 三.哈希表应用举例: Leetcode上第387题: 思路:通过s.charAt(i)-'a'将字符串中的字符映射成hash表,出现一次,在 ...

  2. 复制成绩表计算机专业的表结构,数据结构 数据结构与算法期末实验考试成绩表.doc...

    数据结构 数据结构与算法期末实验考试成绩表.doc (2页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.90 积分 数据结构与算法期末实验考试成绩表 ...

  3. (二)《数据结构与算法》 青岛大学-王卓 线性表 C++

    <数据结构与算法> 青岛大学-王卓 线性表(链式存储)C++ B站链接:[https://www.bilibili.com/video/BV1nJ411V7bd?p=20] 本人能力有限, ...

  4. 数据结构与算法入门---数据结构类型

    数据结构与算法入门---数据结构类型 数据的逻辑结构 数据的逻辑结构指数据元素之间的逻辑哦关系(和实现无关) 分类一:线性结构和非线性结构 线性结构:有且只有一个开始结点和一个终端节点,并且所有节点都 ...

  5. 【数据结构与算法】数据结构+算法=程序

    [数据结构与算法]数据结构+算法=程序 数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成.记为:Data_Structure=(D,R)其中D是数据元 ...

  6. 数据结构与算法笔记:哈希表——力扣389

    原题: 给定两个字符串 s 和 t ,它们只包含小写字母.字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母.请找出在 t 中被添加的字母. 思路: 首先咱们抛开编程知识,就当它是咱们日常 ...

  7. 【数据结构与算法篇】 哈希表原理、底层实现剖析

    一个在校大二学生,在CSDN记录自我成长!!!最近在自学数据结构和算法时,学到了哈希表,有很多地方都不明白.如何使用哈希表?原理是什么?如何工作的?我们如何设计哈希表?等等,所以就在网络上查了相关博客 ...

  8. python【数据结构与算法】深入浅出哈希表

    哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列 ...

  9. 【JS数据结构与算法】认识哈希表

    目录 一.什么是哈希表? 二.哈希表的优势. 三.哈希表与数组相比较. 四.数据的存储. 方法一:ASCII编码之和 方法二:幂的连乘 五.方法二改进--哈希化 六.解决冲突 一:链地址法(拉链法). ...

最新文章

  1. cmake编译出错:No CMAKE_CXX_COMPILER could be found.
  2. 任务栏托盘不消失的问题-有启示
  3. 计算机操作系统——页面置换算法
  4. 观察者模式及c++实现
  5. Excel VBA - Workbook对象
  6. 新闻发布项目——接口类(newsTbDao)
  7. [react] 请描述你对纯函数的理解?
  8. 20210602:力扣第243周周赛(下)
  9. Java虚拟机(JVM)初探
  10. 鸟哥的 linux 的私房菜 基础学习篇,鸟哥的 Linux 私房菜 -- 基础学习篇
  11. MicroSIP注册点星PBX后拨打电话undefined external error故障解决方法
  12. 云erp系统、进销存软件、仓储管理系统之间有哪些区别
  13. php递归算法获取树形菜单数据TreeMenu代码实现
  14. 解决征信中心密码控件无法安装
  15. Cfree5可以JAVA_C语言中free函数的使用详解
  16. [论文阅读笔记26]MRC4NER:使用阅读理解方法来解决NER任务
  17. win7共享计算机打不开,windows7共享文件夹打不开怎么办
  18. 全球及中国海上撇油系统行业市场深度分析与十四五前景预测报告2022-2028年
  19. Python批量统计数据分布的偏度并画图
  20. 响应式编程在Android中的应用

热门文章

  1. python爬虫requests源码链家_Python爬虫之---爬链家
  2. php 随机两位小数数_使用8086微处理器找出两个8位N数数组
  3. 【MATLAB】求点到多边形的最短距离
  4. Python:通过SNMP协议获取华为交换机的ARP地址表
  5. mysqli与pdo防sql注入源码
  6. Spring+SpringMVC+Mybatis 整合入门
  7. LeetCode——866.回文素数
  8. nohup xxx 后台进程关闭,可以这样避免
  9. python合并两个数据框_使用python合并两个数据框
  10. 计算机基础知识复习资料,计算机基础知识复习资料