Day 13:杂凑表(hash table)

在通讯录或朋友列表里,我们可以搜寻一个名字,就找到电话或页面,只需要O(1)。如果想要实现这样的操作,可以想像一种融合上一回阵列和链结串列的资料结构。

杂凑函式(hash function)

杂凑表是可以储存键(key)和值(value)的资料结构,但它并不是像试算表一样,只是单纯地把「John Smith」和他的电话「521-1234」存在一起。它做的事情比较像是,当我们输入John Smith,可以找到他的电话的储存位址。而杂凑表就是透过杂凑函式的运算,来做到这种比较复杂的结构。

杂凑函式可以输入资料,并回传一个值。以下图为例,杂凑函式将John Smith这个字串转为一个杂凑值(02),并以这个值作为阵列的索引,将John Smith的电话储存在这个位址。换句话说,透过杂凑函式,我们可以得到一个资料存放的位址。

图片来源:维基百科

杂凑函式的特性是:

  • 具有一致性,也就是每次输入John Smith都会输出同样的值。
  • 不同的输入可能会得到一样的值(同样的储存位址),也就是所谓的杂凑冲突(hash collision,也译作碰撞) 。

杂凑冲突

理想的情况下,我们希望每个资料都有一个独一无二的阵列位址,这样存取所有的资料都可以实现O(1),可是当资料量很大时,必须考虑阵列的储存空间,而且很难实现杂凑函式回传的值都不重复,所以必须想办法解决杂凑冲突的问题。

举例来说,如果有一个英文名单,我们用杂凑函式将所有人名的第一个字母转为0-25的其中一个数字,存在长度为26的阵列中。

那很快就会出现冲突的情况,因为名字开头字母很容易重复。冲突的一个解决方法是使用链结串列(linked list),我们可以使用链结串列储存索引位置相同的资料(如下图)。这种情况的杂凑表基本上就是每个元素都是一个链结串列的阵列。

图片来源:https://itnext.io/data-structures-in-js-hash-tables-app-with-react-b28b02a9e6b5

但这时候可能又有另一个问题,假设名单里S开头的人名特别多,那就会变成阵列中S的索引位置是一个超长的链结串列,而其他位址是空的,那麽其实跟直接把名单存在一个链结串列中没有两样,存取也会变得非常没有效率。

所以由此可知,一个好的杂凑函式要能减少冲突的发生、让资料平均分布在杂凑表中,提升资料结构的效率。要从零开始设计出一个时间空间效率俱佳的杂凑函式不是非常容易,不过好消息是在开发时基本上不会需要自己撰写杂凑函式,各种语言的函式库里都有效能不错的杂凑函式可以直接使用。


<<:  Parser Generator (三)

>>:  [Tableau Public] day 26:台湾姓氏分布分析-4

Day 04:金鱼记忆力太短暂,交给外挂记吧!autosuggestions 与 substring-search

更新 我把从第一天到现在每天的 Home 目录都放上 GitHub 了,README.md 里面有...

【Day 04】C 的一些基本语法

识别符号 用来标示函式、变数,或者使用者自定专案的名称,识别符号可用大写字母(A 到 Z)、小写字母...

[铁人赛 Day05] React 中的 Code splitting(代码分离)方法

什麽是 Code splitting?为什麽要做 Code splitting? 如果你的网站是用 ...

Day 25:Ansible Playbook

昨天有成功使用 Ansible 执行一个 echo 印出东西了,这在 Ansible 里面称作 ad...

【Day 17】 实作 - 启用 AWS VPC 日志

哈罗大家好~ 美好的礼拜五终於到了.... 明後天就有更多时间可以赶铁人赛了哈哈 (呜呜抱佛脚活该的...