Misra-Gries频数估计算法
Misra-Gries算法最早由J. Misra和D. Gries在1982年提出 [1]。Misra-Gries算法可以看成是对Majority算法的一个扩展,可以对数据流中的频数提供相对误差为的估计,使用的空间复杂度为\mathrm{O}(1/\epsilon)。
到了2000年左右,随着对数据流研究的再次兴起,[2] 和 [3] 又重新提出了类似的算法来用于频数估计。和原始论文不同,[2] 使用了哈希表而非平衡搜索树来保证频繁项。[3] 则通过多个链表来提高清理记录时的效率,使其在最坏情况下仍然可以保证O(1)的开销。
原始论文侧重于频繁项的估计,并没有对频数估计的误差进行分析。[4] 证明了当k = 1/\epsilon时,频数估计的相对误差为\epsilon。[5] 则表明Misra-Gries算法的估计误差仅取决于那些长尾的数据项。考虑到真实环境中的数据分布通常是倾斜的(如zipf分布),这个结论可以进一步提高收紧了估计误差的边界。
[5] 进一步扩展了Misra-Gries算法,使其可以支持记录有不同权重的场景。[6] 则优化了权重更新场景下更新和合并的效率。[5] 还提供了Misra-Gries算法的合并操作,而 [7] 则对合并的空间复杂度提供了更强的边界。[7] 同时也证明了Misra-Gries算法本质上是和SpaceSaving算法等价的。
算法实现
我们在Misra-Gries算法中维护了一组记录(e, f),其中e是数据流中的数据项,f是对e的频数估计。我们将这组记录记为\mathcal{D}。
Misra-Gries算法的更新算法如下所示。当一个新元素i到达时,我们首先检查其是否已经在\mathcal{D}中存在。如果已经存在的话,那么我们直接将其对应的频数f_i加1;否则,我们在\mathcal{D}中添加一个新记录(e, 1)。如果\mathcal{D}中记录的数目超过了设定的阈值k,那么我们就需要对\mathcal{D}进行清理。我们去除当前\mathcal{D}中所有频数的最小值f_m,并将所有频数都减去f_m。如果一个数据项的频数变为0,那么我们就将其从\mathcal{D}中删除。
0 | if i \in \mathcal{D}, then |
1 | f_i \gets f_i + 1 |
2 | else |
3 | \mathcal{D} \gets \mathcal{D} \cup \{i\} |
4 | f_m = \min \{ f_e | e \in \mathcal{D} \} |
5 | for all j \in \mathcal{D} |
6 | f_j \gets f_j - f_m |
7 | if f_j = 0, then |
8 | \mathcal{D} \gets \mathcal{D} - \{j \} |
9 | end if |
10 | end for |
11 | end if |
Misra-Gries算法的查询操作如下所示。当我们需要查询某个数据项的频数时,如果这个数据项存在于\mathcal{D}中,我们就返回其在\mathcal{D}中对应的频数;否则,我们直接返回0。
0 | if e \in \mathcal{D} |
1 | return f_e |
2 | else |
3 | return 0 |
4 | end if |
Misra-Gries算法也支持合并操作。在合并时,我们任意选择一个频繁集来作为基础,然后再将另一个频繁集中的记录逐个添加进去。Misra-Gries算法的合并操作如下所示:
0 | \mathcal{D} \gets \mathcal{D}_a |
1 | for all i \in \mathcal{D}_b |
2 | if i \in \mathcal{D}, then |
3 | f_i \gets f_i + f_{i, b} |
4 | else |
5 | \mathcal{D} \gets \mathcal{D} \cup \{i \} |
6 | f_i \gets f_{i, b} |
7 | end if |
8 | if |\mathcal{D}| > k, then |
9 | f_m \gets \min \{ f_e | e \in \mathcal{D} \} |
10 | for all j \in \mathcal{D} |
11 | f_j \gets f_j - f_m |
12 | if f_j = 0, then |
13 | \mathcal{D} \gets \mathcal{D} - \{j\} |
14 | end if |
15 | end for |
16 | end if |
17 | end for |
算法分析
定理. Misra-Gries算法对真实频数f提供的估计\hat{f}满足
0 \le f - \hat{f} \le \epsilon n
其中n为所有数据项的频数之和。此时所需的空间复杂度为\mathrm{O}(1/\epsilon)。
证明. 令n为数据流中已经出现的记录数目,n’为保存在\mathcal{D}中所有频数的和。
我们对于频数估计的误差来自于我们对\mathcal{D}的清理操作。每次清理时,我们会将\mathcal{D}中的记录都减去一个相同的值。因此在整个计算过程中,受到影响的数据项数目一定大于等于k。因此,我们有
0 \le f - \hat{f} \le \frac{n - n’}{k} \le \frac{n}{k}
令k = 1/\epsilon,则
0 \le f - \hat{f} \le \epsilon n
参考文献
[1] J. Misra, D. Gries. Finding repeated elements. In Science of Computer Programming 1982, vol. 2(2), pp. 143-152.
[2] E. Demaine et al. Frequency estimation of internet packet streams with limited space. In ESA 2002, pp. 348-360.
[3] R. Karp et al. A simple algorithm for finding frequent elements in streams and bags. In TODS 2003, vol. 28(1), pp. 51-55.
[4] P. Bose et al. Bounds for frequency estimation of packet streams. In SIROCCO 2003.
[5] R. Berinde et al. Space-optimal heavy hitters with strong error bounds. In PODS 2009, pp. 157-166.
[6] D. Anderson et al. A high-performance algorithm for identifying frequent items in data streams. In IMC 2017, pp. 268-282.
[7] P. Agarwal et al. Mergeable summaries. In PODS 2012, pp. 23-34.
Misra-Gries频数估计算法