(转)预估大数据量下UV的方法

在实际应用中,我们经常碰到这种情况,即要统计某个对象或者事件独立出现的次数。对于较小的数据量,这很容易解决,我们可以首先在内存中对序列进行排序,然后扫描有序序列统计独立元素数目。其中排序时间复杂度为O(n*log(n)),扫描时间复杂度为O(n),所以总的时间复杂度为O(n*log(n))。当内存非常充裕时,我们还可以考虑使用哈希,将时间复杂度降到O(n)。尤其是当元素只能取有限范围的整数值时,我们还可以使用BitMap节约内存。但是在处理数据流序列时,比如,google的独立访问IP统计,由于序列非常长,元素取值范围可能比较广,单个元素占用内存可能比较多,导致内存中无法容纳整个序列,甚至无法容纳整个独立元素集合。此时,不论是基于排序还是基于哈希的方法都不具备可行性。

Flajolet-Martin(FM)算法能够较好地解决估算数据流序列中独立元素数目的问题。

假设我们有1万个int型数字(可重复的),我们想找出这个数字集合中不重复的数字的个数。怎么办呢?很简单,将这1万个数字读进内存,存放到hashset中,那么hashset的size就是不重复数字的个数。接下来,问题变得更加的复杂,有100亿个数字,怎么办? 全部读取到内存中可能会有问题,如果这其中有1亿个不重复的数字,那么至少需要内存 100M * sizeof(int),内存也许不够。 FM算法就是为了解决这个问题。假设n个object,其中有m个唯一的,那么FM算法只需要log(m)的内存占用(实际操作中会是k*log(m)),以及O(n)的运算时间。当然,FM的问题是,它的结果只是一个估计值,不是精确结果。

具体思路如下:

假定哈希函数H(e)能够把元素e映射到[0, 2^m-1]区间上;再假定函数TailZero(x)能够计算正整数x的二进制表示中末尾连续的0的个数,譬如TailZero(2) = TailZero(0010) = 1,TailZero(8) = TailZero(1000) = 3,TailZero(10) = TailZero(1010) = 1;我们对每个元素e计算TailZero(H(e)),并求出最大的TailZero(H(e))记为Max,那么对于独立元素数目的估计为2^Max。

这种估算的理论依据证明参见  原文

举例来说,给定序列{e1, e2, e3, e2},独立元素数目N = 3。假设给定哈希函数H(e),有:

H(e1) = 2 = 0010,TailZero(H(e1)) = 1

H(e2) = 8 = 1000,TailZero(H(e2)) = 3

H(e3) = 10 = 1010,TailZero(H(e3)) = 1

第1步,将Max初始化为0;

第2步,对于序列中第1项e1,计算TailZero(H(e1)) = 1 > Max,更新Max = 1;

第3步,对于序列中第2项e2,计算TailZero(H(e2)) = 3 > Max,更新Max = 3;

第4步,对于序列中第3项e3,计算TailZero(H(e3)) = 1 ≤ Max,不更新Max;

第5步,对于序列中第4项e2,计算TailZero(H(e2)) = 3 ≤ Max,不更新Max;

第6步,估计独立元素数目为N’ = 2^Max = 2^3 = 8。

在这个简单例子中,实际值N = 3,估计值N’ = 8,误差比较大。此外,估计值只能取2的乘方,精度不够高。

在实际应用中,为了减小误差,提高精度,我们通常采用一系列的哈希函数H1(e), H2(e), H3(e)……,计算一系列的Max值Max1, Max2, Max3……,从而估算一系列的估计值2^Max1, 2^Max2, 2^Max3……,最后进行综合得到最终的估计值。具体做法是:首先设计A*B个互不相同的哈希函数,分成A组,每组B个哈希函数;然后利用每组中的B个哈希函数计算出B个估计值;接着求出B个估计值的算术平均数为该组的估计值;最后选取各组的估计值的中位数作为最终的估计值。

举例来说,对于序列S,使用3*4 = 12个互不相同的哈希函数H(e),分成3组,每组4个哈希函数,使用12个H(e)估算出12个估计值:

第1组的4个估计值为<2, 2, 4, 4>,算术平均值为(2 + 2 + 4 + 4) / 4 = 3;

第2组的4个估计值为<8, 2, 2, 2>,算术平均值为(8 + 2 + 2 + 2) / 4 = 3.5;

第3组的4个估计值为<2, 8, 8, 2>,算术平均值为(2 + 8 + 8 + 2) / 4 = 5;

3个组的估计值分别为<3, 3.5, 5>,中位数为3.5;

因此3.5 ≈ 4即为最终的估计值。

分析FM算法的时间复杂度。假定序列长度为N,哈希函数H(e)的数目为K。初始化K个Max值的时间复杂度为O(K);对N个元素e使用K个哈希函数H(e)计算TailZero(H(e))并更新Max值的时间复杂度为O(N*K);综合K个Max值给出最终估计值的时间复杂度为O(K)。因此总的时间复杂度为O(N*K)。

分析FM算法的空间复杂度。该算法需要存储K个Max值,而每个元素e在进行相关计算后就可以丢掉。因此总的空间复杂度为O(K)。

综上所述,FM算法的时间复杂度为O(N*K),空间复杂度为O(K)。一般来说K比较小,可以认为FM算法的时间复杂度为O(N),空间复杂度为O(1)。

FM算法可以用于估算独立Cookie数目,独立URL数目,独立邮箱地址数目等等。

原文地址:https://www.cnblogs.com/sunshisonghit/p/6705156.html