数据结构与算法:18 | 散列表(上):Word文档中的单词拼写检查功能是如何实现的?

文章目录

      • 散列思想
      • 散列函数
      • 散列冲突
        • 开放寻址法
        • 链表法
      • 解答开篇
      • 课后思考

散列思想

散列表的英文叫“Hash Table”,平时也叫它“哈希表”或者“Hash 表”:

散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。

数据结构与算法:18 | 散列表(上):Word文档中的单词拼写检查功能是如何实现的?_第1张图片

散列函数

散列函数,顾名思义,它是一个函数。可以把它定义成 h a s h ( k e y ) hash(key) hash(key),其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。

该如何构造散列函数呢?我总结了三点散列函数设计的基本要求:

  • 散列函数计算得到的散列值是一个非负整数;
  • 如果 key1 = key2,那 hash(key1) == hash(key2);
  • 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。

第一点理解起来应该没有任何问题, 因为数组下标是从 0 开始的,所以散列函数生成的散列值也要是非负整数。

第二点也很好理解。相同的 key,经过散列函数得到的散列值也应该是相同的。

第三点理解起来可能会有问题,这个要求看起来合情合理,但在真实的情况下,要想找到一个不同的 key 对应的散列值都不一样的散列函数,几乎是不可能的。即便业界著名的MD5、SHA、CRC等哈希算法,也无法完全避免这种散列冲突。而且,因为数组的存储空间有限,也会加大散列冲突的概率。

散列冲突

再好的散列函数也无法避免散列冲突。那究竟该如何解决散列冲突问题呢?常用的散列冲突解决方法有两类,开放寻址法(open addressing)和链表法(chaining)。

开放寻址法
  • 线性探测
  • 二次探测
  • 双重散列

不管采用哪种探测方法,当散列表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证散列表的操作效率,一般情况下,我们会尽可能保证散列表中有一定比例的空闲槽位。

用装载因子(load factor)来表示空位的多少:

散 列 表 的 装 载 因 子 = 填 入 表 中 的 元 素 个 数 / 散 列 表 的 长 度 散列表的装载因子=填入表中的元素个数/散列表的长度 =/

装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。

链表法

链表法是一种更加常用的散列冲突解决办法,相比开放寻址法,它要简单很多。我们来看这个图,在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。
数据结构与算法:18 | 散列表(上):Word文档中的单词拼写检查功能是如何实现的?_第2张图片
插入的时候,只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 O(1)。

当查找、删除一个元素时,同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。这两个操作的时间复杂度跟链表的长度 k 成正比,也就是 O(k)。对于散列比较均匀的散列函数来说,理论上讲,k=n/m,其中 n 表示散列中数据的个数,m 表示散列表中“槽”的个数。

解答开篇

开篇的思考题:Word 文档中单词拼写检查功能是如何实现的?

常用的英文单词有 20 万个左右,假设单词平均长度是 10 个字母,平均一个单词占用 10 个字节的内存空间,那 20 万英文单词大约占 2MB 的存储空间,就算放大 10 倍也就是 20MB。对于现在的计算机来说,这个大小完全可以放在内存里面。所以我们可以用散列表来存储整个英文单词词典。

当用户输入某个英文单词时,拿用户输入的单词去散列表中查找。如果查到,则说明拼写正确;如果没有查到,则说明拼写可能有误,给予提示。

散列表两个核心问题是散列函数设计散列冲突解决

课后思考

留言的答案。。

1、假设我们有 10 万条 URL 访问日志,如何按照访问次数给 URL 排序?
URL作为key, 访问次数作为散列值value,存入散列表,存储的时候进行比较操作就能得到访问次数最多的值K,复杂度O(n);

如果K不大的话,使用桶排序,复杂度O(n)
否则,使用快速排序,复杂度O(nlogn)

2、有两个字符串数组,每个数组大约有 10 万条字符串,如何快速找出两个数组中相同的字符串?

以第一个字符串数组构建散列表,key 为字符串,value 为出现次数。再遍历第二个字符串数组,以字符串为 key 在散列表中查找,如果 value 大于零,说明存在相同字符串。时间复杂度 O(N)。

你可能感兴趣的:(数据结构)