词频统计与取前k内容
比如百度搜索如何取前k条符合的内容呢,最基础的方法是先算出每个内容的匹配值然后取前k位。词频类似,统计词对应频率然后取前k位
Topk 求取算法
TopK Elements 问题用于找出一组数中最大的 K 个的数。
此外还有一种叫 Kth Element 问题,用于找出一组数中第 K 大的数。
其实要求解 TopK Elements,可以先求解 Kth Element,因为找到 Kth Element 之后,再遍历一遍,大于等于 Kth Element 的数都是 TopK Elements。
方法1:
相信快速排序算法这种经典的算法大家并不陌生。但是基于快速算法的各种变形,你了解吗?
其中很重要的一种变形就是快速选择算法, 通常用来在未排序的数组中寻找第k小/第k大的元素。快速选择及其变种是实际应用中最常使用的高效选择算法。
快速选择的总体思路与快速排序一致,选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。不同的是,快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。这降低了平均时间复杂度,从O(n log n)至O(n),不过最坏情况仍然是O(n2)。
方法2:
2.2 堆
维护一个大小为 K 的最小堆,堆顶元素就是 Kth Element。
使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。
维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。
- 时间复杂度 O(NlogK) 、空间复杂度 O(K)
- 特别适合处理海量数据
单机面对内外存储问题而使用的基础词频统计
这也是最常见的
4.1 HashMap
使用 HashMap 进行频率统计,然后使用快速选择或者堆的方式找出频率 TopK。在海量数据场景下,也是使用先拆分再整合的方式来解决空间问题。
4.2 Count-Min Sketch
维护 d*w 大小的二维统计数组,其中 d 是哈希函数的个数,w 根据情况而定。
- 在一个数到来时,计算 d 个哈希值,然后分别将哈希值对 w 取模,把对应统计数组上的值加 1;
- 要得到一个数的频率,也是要计算 d 个哈希值并取模,得到 d 个频率,取其中最小值。
该算法的思想和布隆过滤器类似,具有一定的误差,特别是当 w 很小时。但是它能够在单机环境下解决海量数据的频率统计问题。
4.3 Trie
Trie 树可以用于解决词频统计问题,只要在词汇对应节点保存出现的频率。它很好地适应海量数据场景,因为 Trie 树通常不高,需要的空间不会很大。
mapreduce
分布式情况下如何利用分布的计算资源来进行词频统计
1. 直观理解
想要统计一堆牌中有多少张黑桃,最简单的方法是一张一张去数。而 MapReduce 则是让多个玩家来并行地统计,从而大大缩短统计时间:
- 把这堆牌分配给多个玩家;
- 让每个玩家统计自己手中有多少张黑桃;
- 把所有玩家统计的黑桃数量加起来就是最终的结果。
2. 分而治之
某些计算任务能够并行地进行,这些任务可拆分并且不能有数据依赖。例如斐波那契数列 f(n) = f(n-1) + f(n - 2),第 n 项的计算依赖于第 n - 1 项和第 n - 2 项的计算结果,因此就不能并行。但是对于将一个二维数组中每个元素都进行加 1 的计算,每个元素的计算都没有数据依赖,因此能够并行。
3. 基本原理
MapReduce 用来对海量数据进行离线分析,通过将计算任务分配给集群的多台机器,使得这些计算能够并行地进行。
它分为 Map 和 Reduce 两个阶段:
- Map(映射):对每个目标应用同一操作;
- Reduce(归纳):整合部分结果。
在下图中,
- file 被划分为多个 split,每个分片由一个 Mapper Task 去处理。Map 过程中输入的是 [K1, V1] 数据,这是一种键值对形式的数据,键为泛型的 K1 类型,值为泛型的 V1 类型。输出也是一个泛型的键值对 [K2, V2]。
- Shuffle 过程主要是对 Map 阶段输出的键值对进行整合,将键相同的键值对整合到一组中,得到 [K2, {V2,...}] 的整合数据,作为 Reudce 阶段的输入。
- Reduce 处理完之后得到 [K3, V3] 键值对,Hadoop 会将输出的数据存到分布式文件系统 HDFS 中。
4. WordCount 实例
4.1 文本
the weather is good
today is good
good weather is good
today has good weather
将文本的每一行拆成分片,每个分片的数据提供给 Mapper 进行处理,因此需要 4 个 Mapper。其中 K1 为行号,V1 为该行的字符串。
Split-0: [0, "the weather is good]
Split-1: [1, "today is good"]
Split-2: [2, "good weather is good"]
Split-3: [3, "today has good weather]
4.2 Mapper
将输入的字符串 V1 进行切分,输出 K2 为单词, V2 为词频。
Mapper-0: ["the", 1], ["weather", 1], ["is", 1], ["good", 1]
Mapper-1: ["today", 1], ["is", 1], ["good", 1]
Mapper-2: ["good", 2], ["weather", 1], ["is", 1]
Mapper-3: [today", 1], ["has", 1], ["good", 1], ["weater", 1]
4.3 Shuffle
排序并将拥有相同键的数据整合,得到 [K2, {V2,...}]。
["good", {1, 1, 2, 1}]
["has", {1}]
["is", {1, 1, 1}]
["the", {1}]
["today", {1, 2}]
["weater", {1,1}]
4.4 Reducer
遍历 Shuffle 阶段输出的 {V2,...} 集合,进行最终的统计。
Reducer-0: ["good", 5]
Reducer-1: ["has", 1]
Reducer-2: ["is", 3]
Reducer-3: ["the", 1]
Reducer-4: ["today", 2]
Reducer-5: ["weather", 3]
参考资料
cyc2018