本文主要介绍一下hash与多值hash,然后在讨论一下支撑hash的伪随机数生成器。不感兴趣者可以走了……
一:hash 与 多值hash:
野史
第一次见到hash,是在算法导论里看到的,其中的桶排序就是使用了此类思想,当然也有专门介绍hash的章节。感觉却是高明。后来在sqlseveral中也经常接触,毕竟作为join的一种优化方法经常都很靠谱。而多值hash则是在《数学之美》中接触到的,其中用作垃圾邮件过滤器。两个字:神奇。后来在一些分类统计中经常使用,感触更深。hash的本质就是用一个函数根据内容本身计算出内容的存储位置然后存储之,取的时候自然也就是看到内容立刻就能计算出(而不是找出)目标的位置。
碰撞
这个计算位置的函数先放一下,假设已经有了,而且内容无规律,我们选择了随机函数;先讨论碰撞问题。假设在一个100万的序列中已经有1万个位置被占满,那么再放一个元素时他会落到别人的位置上的概率是1%。这就是碰撞的概率了。当然如果内容存在一定规律,我们不使用随机函数作为hash函数可能会完全不碰撞并且位置使用率很高,例如:如果要计算一篇文章的所有单字的字频统计,就可以使用汉字的编码所代表的数字做一个线性平移。但如果统计词组的频率呢?四字成语所代表数字可不小(2的64次方)。回到之前的问题,在一个100万的序列中存储一个元素时碰撞概率为1%的问题。如果我们把每个元素存放在2个随机的位置会发生什么呢?明显需要两个位置同时碰撞才会使信息真正遗失,而此时概率为0.01%。方法很简单,但有效。
最优多值
这个多值是不是月多越好呢?明显不是,当一个元素需要1千万个位置时恐怕那个百万序列存不了几个数字。在假设随机函数完全随机的情况下,我们要在一个M的序列中存放N个元素,每个元素占据K个位置。最优K一个近似的偏小的估计值是M/N/e(也就是那个接近2.7的无理数)。后来我找到了一个更精确的计算方法,但结果不那么容易记,在此也不说了。如果在实验时发现上面的最优值与实际测试出入较大,在怀疑它之前,先怀疑自己的随见函数是否真的是足够随机。这便是下面要讲的——构建一个好的随机数发生器。
二:随机数发生器
再看导论之前,用过随机数发生器,看过导论之后写过。至于那些七大类八大纲就不扯了,说一下我用的第一个hash函数和最让我满意的一个。
乘法
乘法是指用一个无理数(比如sqrt(6))与需要存储的内容所对应的唯一值(如ASCII码代表的值)进行乘法后取小数部分,然后再用这个小数乘以散列长度。但我在使用的时候发现其碰撞概率比我的估计值要大了几个数量级,最后看了几种不同随机数产生方法,把目标放在md5上。随机数算法要尽可能简单,下面是个逻辑过程,没有照抄md5,但很类似。散列长度2**24.【就是一些移位与异或,符号采取的是Python中的】
1. a = ASCII(string); a1 = 0
2. if a <2**64,a = a*138647902547865326901【随意一个大数,别取那些2^n的】
(a>0):
4. a1 = a1^(a&(2**24-1)); a = a>>24
(a1)
这个方法还是不错的,在我的实验中,得到的碰撞概率比完全随机假设时的理论结果还要低。这个原因是因为多值hash时,多个随机数发生器对应同一个种子时更倾向于产生不同的位置,相互之间碰撞概率低于随机情况。至少目前我是这么解释。
检验
怎么说明随机函数是否随机呢?看分布图等很有说服力,但是不够严谨。在一过程中我之所以能够区分出孰优孰劣也是因为下面介绍的一个方法。
假设一个长度为M的散列的散列函数完全随机,存储了N个不同元素后,其中有K个位置被占据。忽略高阶小量(三个或更多元素存在同一个位置),其中某一个元素发生碰撞的概率为(N-K)/N。而另一方面在在这个元素没有放进去之前,(K-1)位置已经存储元素(小概率被忽略),总长度M,碰撞概率(k-1)/M。于是根据我们存储的唯一元素个数N,占据位置数量K,和散列长度M,我们可以估计出两个概率,而它们的含义相同【(N-K)/N=(k-1)/M】。即 这两者也接近 函数越接近随机。当N已经较大时(比如大于M的0.01倍,太大时高阶小量就不能忽略了),在这个残酷的检验中MD5的方法比乘法以及另外一个求余法要好了不少。
结语
上周就说要写的,今天终于写完了,下次谈谈Hadoop的吧,哎,被老板拉去做些装机的苦力活,不知道会遇到些什么困难,又会学些什么东西。