关于哈希的最通俗的解说
原文地址:https://www.jianshu.com/p/55ac90f353fb
搞IT的,常常面临两个重要问题:存储和查找,包括现在常说的的AI(人工智能),简单说就是算法和大数据的结合,数据的存储和查找就显得尤为重要,对数据的存储和查找的效率往往决定了整个工程的效率。
我们来引出哈希:
比方说,你忘了家里的指甲钳放在哪里了,通常你会在家中所有的抽屉中寻找,直至找到,最坏的情况下,你家里一共有N个抽屉,你就要全部翻一遍这N个抽屉。这种存储方式就叫“数组”,查找方法称为“遍历”
再接着比方,你是一个善于整理的人,家里所有的物品你都有分门别类地整理,然后再放入收纳箱,最后将箱子编号,比如1号箱放的是针线小件,2号箱放的是证件,3号箱放的是细软,4号箱放的是书本。你只需记住编号对应的类别就行,假如哪天你出国需要找到护照,这时你就不要翻开所有抽屉,你直接找到2号箱从中获取即可。这种存储和查找的方式就称为“哈希”,如何对箱子编号,就称为“哈希算法”。
同样是查找,当数据记录量非常大时,差距就非常明显了,假设我们有100亿条数据记录,遍历查找需要最多100亿次,而哈希只需1次。
下面我们来说哈希和哈希算法:
哈希也称散列,哈希表是一种与线性表,树等不同的数据结构,与他们需要不断地遍历比较来查找的方式不同,哈希表设计了一个映射关系:f(key)= address,根据key来存储地址address,这样可以实现1次查找,这里的f函数既是存储数据过程中用来指引数据存储到什么位置的函数,也是将来用来查找这个位置的算法,即哈希算法(哈希函数)。
我们举个例子,比如下面这四位人物,我们先按数组存储:
当我们要找到大胸姐的电话,就需要遍历查找对比整个数组,第一个是余罪,不是,第二个傅老大也不是,第三个大嫂沈嘉文也不是,直到第四个找到大胸姐。
如果这四人用哈希来存储呢?首先,我们就要设计出适合的哈希算法,教材上说的几种方法,比如:直接定址法,平方取中法,除数取余法等,这几个要是认真细讲起来比较晦涩难懂,这里我们就通俗地说,哈希算法的本质其实就是计算出一个数字。比如我们在此处用取姓名的首字母作为哈希算法就很简单适用。此时有:f(余罪)=y,f(傅老大)=f,f(沈嘉文)=s,f(大胸姐)=d。
构建哈希表如下:
我们看到他们分别以姓名首字母的位置插入到这一张表中,这样我们就构建了一个Key-Value表格,此表就是哈希表,即Hash Table。这时候当我们要查找余罪的时候,通过计算,余罪在y位置,可以通过1次查找,直接找到这条记录,即手机号。
这个时候有小伙伴就要问了,如果以取首字母为哈希算法的话,在有很多以y为首字母的姓名的情况下怎么办呢,这个时候就不是1次查找了吧,这时候有很多条记录都会映射到一个位置上,也称为哈希冲突。
哈希冲突是跟哈希算法的设计正相关的,设计的算法的随机性越大,那么产生哈希冲突的可能性就越小,在小概率下,如果还有冲突怎么办呢,这个时候就要做一些有损的设计了,比如如果有两个首字母为y的姓名,那么可以接到余罪的后面,当查找的时候,需要先查找到y,然后再顺序查找,如图所示:
好了,我们总结一下:
首先哈希表是一种数据结构,是一种效率极高的查找方式,哈希表的核心在于哈希算法的设计,哈希冲突了不要紧,我们要增加随机性以及对冲突进行适当的有损化的处理。
其次,在不同高级语言中对哈希的封装和冲突的处理机制都是各不相同各有千秋的,比方说JAVA里面的链表和红黑树互换机制,Redis里面的拉链算法等。本人将在后续的文章中专门讨论对比一下各高级语言中对哈希的封装使用以及冲突的处理。