高频数据项问题 (Heavy Hitters),在互联网时代有许多应用场景,如计算热门 商品、计算热搜、识别高流量的TCP流、识别高频交易的股票。计数再排序的方 法太消耗内存了,本文介绍一个 时间,常量空间的概率算法,解决 这个问题。
首先,我们看看这个问题的简化版本。
查找多数派
这是个技术面试中常见的问题。给定一个长度为 的数组 , 保证其中必定有多数派,多数派就是出现次数超过 的元素, 你的任务就是找到多数派元素。元素类型可以是数字、URL、商品编号等。
这个问题可以用线性时间解决。我们假象一个团队决斗比赛,队内不能决斗,只 能一一决斗。两人决斗结果只有一换一,决斗两人都退场。如果有一个队伍成员 超过半数,那么最后赢得肯定是这个队。
我们得出的算法就是这样:
element_type find_mojority_elem(element_type A[], int n)
{
element_type current;
int counter = 0;
for (int i = 0; i < n; i++) {
if (counter == 0) {
current = A[i];
counter++;
} else if (A[i] == current) {
counter++;
} else {
counter--;
}
}
return current;
}
算法很简单,时间复杂度 常量空间。实际根本没人会去解决这种 问题。但这个问题的通用版本却大有用途。
高频元素问题
高频元素问题 (heavy hitters) ,是指从长度 的数组 中找 到出现次数至少是 的元素。例如, 指的是找到 出现频率是总数的 以上的元素。
显然最多有 个这样的元素,最少一个这样的元素都没得。多数派问题 中 并且保证多数派元素必定存在。
高频元素问题常常出现再互联网时代,列举一些例子:
- 电商网站计算热门商品。
- 搜索网站计算热搜 (热门搜索词)。
- 网络交换机识别高流量TCP流。
- 计算热门页面。
一个明显的算法是,一轮扫描给 每个元素计数,然后输出计数超过 的元素,这样太浪费内存了。也可以先给 排序, 再一次扫描输出现次数超过 的元素,这样还得排序。数据 量大的话 都不能整个放到内存里,这两个办法都不太行。
那么,有没有只要一次遍历,还省内存的算法呢?
绝无可能!
解决高频元素问题,避不开另一个问题:某个元素是否在某个集合内?哈希表、 树之类的数据结构免不了的,不然就得 了。
算法设计大失败,赶紧夹着尾巴滚回家,躺到床上嘤嘤嘤吧!
近似高频元素问题
仔细回顾高频问题的提出,精确的结果确实很难,需要大量的计算或者大量的内 存,时间换空间或者空间换时间不停纠结,让人烦躁。不过,如果我们给出近似 解,有没有意义呢?我们把问题修改为 问题 ( approximate heavy hitt4ers problem):
输入长度为 的数组 和参数 和 , 要求输出一个列表,使得:
- 数组 中出现至少 次的元素都在输出列表里。
- 输出列表里所有元素在数组 中出现至少 次。
如果 就是高频元素问题了。如果 ,那么出现 次的都被统 计输出,输出的结果里可能有一些出现至少 次的元素。这 个问题仍然很有意义。
计数素描 (Count-Min Sketch)
解决近似高频元素问题的方法挺多的:
接下来讲的小数据结构解决这个问题,名为 count-min sketch,因为它足够简 单,而且被应用到了现实世界。 AT&T 在网络交换机使用这个数据结构分析网络流量。 Google 在 MapReduce 上也实现了类似的算法,来做日志分析。
如果你了解 Bloom-Filter,或者有损压缩,这个数据结构很容易理解,没听过 也没有关系。
count-min-sketch (CMS) 支持两个操作: 和 。 表示 出现的频次,也就是 被调用的次 数。
整个结构是个 行 列的数组 , 表示哈希桶 个数, 表示哈希函数个数。通常 约为上千, 不超过10。
两个操作分别为:
Inc(x) :
Count(x) :
两个操作时间复杂度都是 。这个数据结构只可能高估频率,不会低 估。高估的多少,以及概率取决于 和 。如果 增大,就减 少哈希冲突;如果 增大,就计算更多估值,得到更可靠的结果。
如果只想要搞懂数据结构,后面误差分析的内容可以跳过了。
误差分析
误差和参数 、 有什么关系?
简单起见,我们先考虑 中一行的计数。考虑元素 ,如果运气好, 没有哈希冲突, 就是 x 的实际频次。如果运 气不好,其它一些元素 的哈希值和 冲突了,那么 出现的 频次 也被加到 数组上:
其中 ,表示哈希值和 冲 突集合。 和 是常量, 取决于哈希函数 的选择。
元素 与其他元素,大概有 的概率冲突,所以我们估计:
期望高估 。 在 大概几十亿, 约一万的场景,这个误差应该可以接受。注意我 们现在只是在数组的一行上分析。
我们假设对与任意两个元素 和 ,有 。令
其中当哈希冲突时, ,否则 。其期望不大于 ,因此
令 表示误差, (这是为了方便计算), 那么期望误差 为 。根据 马尔可夫不等式:
得
因此
假设哈希函数都是各自独立得,那么
误差概率为 ,只需要令 即可。如果要让误差概率大概为 , 令 是足够的。