哈希函数和哈希表

哈希表实现源码
C++中以哈希表为底层数据结构的容器

哈希函数和哈希表?

哈希函数算法的原理是超纲的内容(底层的实现是位运算),常见的哈希算法有扩展MD5(返回2^ 64)、SHA1(返回2^128)(哈希函数的实现有数千种实现方法)

什么是哈希函数(散列函数)

1) 输入域是无限的。
2) 输出域虽然很大,但是有限的。
3) 哈希函数不是随机函数,没有任何的随机成分。相同的输入值,一定得到相同的输出值。
4) 因为输入域是无限的,输出域是有限的,必然有不同的输入对应相同的输出。(哈希碰撞)
5) 离散性:虽然输入域是无穷大,输出域是s,但是不同的输入如果想要得到整个s域上所有的返回值,在整个s域上的返回值会几乎均匀分布。 (均衡性越好->离散性越好->函数越优良)

哈希函数特点

1)与输入的规律没有关系,否则就没有办法做到均匀性了
2)作用是打乱输入规律,但不是随机的,没有任何随机因素
3)得到的哈希code取模后同样是均匀的。取余m,那么在0~m-1上也是均匀分布

要求让你找1000个独立的哈希函数,那你会怎么办?

去找hash函数库吗?错了。应该把得到的16个16进制的数分成两部分,高八个(h1)与低八个(h2)是独立的,可以用h1+n*h2来构造不同的哈希函数或者是找两个哈希函数进行组合计算。

可以认为哈希code中每个位都是独立的,所以通过每个位置元素的组合本身就可以得到许多哈希函数。

哈希表(散列表)的经典结构

哈希表开头就是哈希域,这个一般是经过我们压缩了的哈希域,但可以保证离散性。
经典结构:哈希前面的哈希域作为桶,后面挂一个链表。当容量不够的时候,进行扩容。会先分配一个空间(桶)(例如17个空间),如果是put一个记录,那么就会将key1拿出来计算hash code,然后拿着这个值取模17,然后将这个键值对挂在对应的位置下,如果冲突了,首先检查在对应位置上是否有后面加入的key,如果有就直接修改key对应的值为新值,如果不存在那么就像一个链表一样往后挂。即相同的key是不额外占用空间的,不同的key占用额外空间。因为哈希函数的性质,所以我们可以近似认为每个链表都是均匀增长的,如果达到指定的长度之后(随着每条链表的长度增加,操作效率就会降低),我们就进行哈希扩容,桶的个数增加一倍,先将每一个数据拿出来之后重新进行计算然后取模,重新寻找他在哪个桶里,完成。一般情况下默认哈希表的增删改查都是O(1)的,但是常数项比较大,和已有的数据量是无关的,因为哈希函数在计算的时候代价比较高。

开放地址法:如果一个桶被占用了,就顺序向下移动。用完之后就进行扩容。

扩容不需要代价吗?你为什么说自己可以做到O(1)?

当我们将不同的数据挂到哈希域中之后,我们发现我们查找的难度就小了,因为我们只需要根据需要查的值计算出它所挂的域,然后我们就在指定的域开始遍历链表,就成了,但是如果随着数据量的增大, 导致我们每个链表的长度都很大,这样我们可以扩展哈希表,降低每个链表的查询难度,扩容的时间复杂度可以做到O(1)。扩容之后需要重新计算每个数据需要挂载的位置(O(N))。扩容的时候是可以离线的,也就是说,扩容的时候你想查,那么我就让你查旧结构(虽然慢,但是可以查),新结构创建好之后,在让你查新结构。

一方面,样本量N,如果每次扩容都翻倍,那么我们需要进行log(n)次,如果增加5倍,所以扩容的代价平均下来很低。你会说那也不至于是O(1)吧?!实际在使用过程中可以进行离线扩容,也就是说在扩容的时候,不妨碍你查询老结构,等到新结构建立起来之后就可以使用新结构了。这样就不需要占用在线时间了。所以说hash的增删改查是O(1)的。离线是怎么做到的?这个技巧一般使用在服务器上的,在JAVA中所有的结构都是JVM托管的,在C++中可能就要忍受log5(N)的代价了。

哈希表的增删改查都是O(1),因为我们可以做到离线扩容,他在数学上肯定不是O(1)。经典结构本身就是存在扩容代价,但实际上可以通过各种优化策略来大幅度降低这个代价,所以可以认为O(1),但是常数项比较大,原因是哈希函数在计算的时候代价比较高。

C++实现中的具体问题

虽然开链法不要求表格大小必须为质数,但是在linuxC++中,仍然以质数来设计表格的大小,并且先将28个质数计算好,以备随时访问。所以STL中以哈希表为底层数据结构的所有容器,当他们满载后,扩容不是几倍几倍增长的。
clear操作并不会影响到哈希表的桶子大小,只是将链表中的数据释放了。

面试题:

假设有一个大文件,每一行都是一个字符串,无序的,文件大小为100T,我想把这个文件中重复的字符串打印出来。问你怎么做?

如果你单CPU,单核,一台机器,去完成这件事情,那就废了。

方法:通过哈希来分流。 首先,应该问面试官,“你给我多少台机器?”,他说:我给你1000台机器来做这件事情。然后把机器标好号,从0到999。随后问面试官文件存在于哪儿?告诉你存在于一个分布式文件系统上,然后问他“那我要想按行读取文件是不是有效率非常高的工具啊?”,他告诉你,有!。最后将每一行读出来,让后计算其hashcode,然后模上1000,模出来的值对应处理他的机器编号。这样我就将所有不同的字符串均分到1000台机器上。而且相同的文本一定会分配到一台机器上。然后在一台机器上完成重复检查。这样很快。如果还是太大,那么就再次通过哈希函数来实现多任务处理。使用哈希函数来做分流,相同输入导致相同输出,不同输入导致均匀分布的特征。

相关推荐
©️2020 CSDN 皮肤主题: 我行我“速” 设计师:Amelia_0503 返回首页