2,解决方法
针对时间,搭配合适的数据结构,如Bloom filter/Hash/bit-map/堆/数据库或倒排索引/trie树;
针对空间,大而化小:分而治之/hash映射。
(1)分而治之/Hash映射 + Hash统计 + 堆/快速/归并排序
【适用范围】
快速查找、删除的基本数据结构,通常需要总数据量可以放入内存。
【基本原理及要点】
先映射,而后统计,最后排序:
1)分而治之/hash映射:针对数据太大,内存受限,只能是:把大文件化成(取模映射)小文件,即16字方针:大而化小,各个击破,缩小规模,逐个解决。
2)hash统计:当大文件转化了小文件,那么我们便可以采用常规的hash_map(key,value)来进行频率统计。
3)堆/快速排序:统计完了之后,便进行排序(可采取堆排序),得到次数最多的key。
综合分析一下,排序的时间复杂度是O(NlgN),而遍历的时间复杂度是O(N),因此该算法的总体时间复杂度就是O(N+NlgN)=O(NlgN)。
时间复杂度是O(NlgN)。
算法的最坏时间复杂度是O(N*K), 其中K是指top数。(每次淘汰一条数据后需要比较k次,移动k次)
B. 海量日志数据,提取出某日访问百度次数最多的那个IP。
IP的数目还是有限的,最多2^32个,可以考虑使用映射的方法,比如%1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
(2)双层桶划分
(3)Bloom Filter
【适用范围】
可以用来实现数据字典,进行数据的判重,或者集合求交集。
【基本原理及要点】
如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表、树、Hash表等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n),O(log n),O(n/k)。
布隆过滤器的原理是,当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个位阵列(Bit array)中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检索元素一定不在;如果都是1,则被检索元素很可能在。这就是布隆过滤器的基本思想。
布隆过滤器存储空间和插入/查询时间都是常数O(k)。
【Bloom Filter的不足】
缺点是有一定的误识别率和删除困难。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
误判是指某个元素并不存在于集合中,却判定为存在于集合中。
【问题实例】
给你A、B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A、B文件共同的URL。如果是三个乃至n个文件呢?
方案1:可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。
遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,...,a999)中。这样每个小文件的大约为300M。
遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记为b0,b1,...,b999)。这样处理后,所有可能相同的url都在对应的小文件(a0 vs b0, a1 vs b1,..., a999 vs b999)中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。
求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。
方案2:果允许有一定的错误率,可以使用Bloom filter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否与Bloom filter,如果是,那么该url应该是共同的url(注意会有一定的错误率)。
(3)Bit-map
(4)数据库索引
【适用范围】
大数据量的增删改查。
【基本原理及要点】
利用数据的设计实现方法,对海量数据的增删改查进行处理。
(4)倒排索引
【适用范围】
搜索引擎,关键字查询。
【基本原理及要点】
为何叫倒排索引?一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。
以英文为例,下面是要被索引的文本:
T0 = "it is what it is"
T1 = "what is it"
T2 = "it is a banana"
我们就能得到下面的反向文件索引:
"a": {2}
"banana": {2}
"is": {0, 1, 2}
"it": {0, 1, 2}
"what": {0, 1}
检索的条件"what","is"和"it"将对应集合的交集。
正向索引开发出来用来存储每个文档的单词的列表。正向索引的查询往往满足每个文档有序频繁的全文查询和每个单词在校验文档中的验证这样的查询。在正向索引中,文档占据了中心的位置,每个文档指向了一个它所包含的索引项的序列。也就是说文档指向了它包含的那些单词,而反向索引则是单词指向了包含它的文档,很容易看到这个反向的关系。
(5)外排序
【适用范围】
大数据的排序,去重。
【基本原理及要点】
外排序的归并方法,置换选择败者树原理,最优归并树。
【问题实例】
有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16个字节,内存限制大小是1M。返回频数最高的100个词。
这个数据具有很明显的特点,词的大小为16个字节,但是内存只有1M做hash有些不够,所以可以用来排序。内存可以当输入缓冲区使用。
(5)分布式处理之Mapreduce
【什么是MapReduce】
MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。这样做的好处是可以在任务被分解后,可以通过大量机器进行并行计算,减少整个操作的时间。但如果你要我再通俗点介绍,那么,说白了,Mapreduce的原理就是一个归并排序。
【适用范围】
数据量大,但是数据种类小可以放入内存。
【基本原理及要点】
将数据交给不同的机器去处理,数据划分,结果归约。
BTW:
public class HeapSort { public static void main(String[] args) { int numLength = 1000000000; int bigLength = 1000; int[] a = new int[bigLength]; Random r=new Random(10); Random r2=new Random(10); for(int i=0;i<bigLength;i++) { a[i] = r.nextInt(); } Sort(a); Display(a, "before sort : "); long start = System.currentTimeMillis(); for(int i=bigLength;i<numLength;i++) { if(a[0] < r2.nextInt()) { a[0] = r2.nextInt(); Sort(a); } } Display(a, "After sort : "); long end = System.currentTimeMillis(); System.out.println(end-start); } public static void Sort(int[] a) { int n = a.length; int temp = 0; for (int i = n / 2; i> 0; i--) Adjust(a, i - 1, n); for (int i = n - 2; i >= 0; i--) { temp = a[i + 1]; a[i + 1] = a[0]; a[0] = temp; Adjust(a, 0, i + 1); } } public static void Adjust(int[] a, int i, int n) { int j = 0; int temp = 0; temp = a[i]; j = 2 * i + 1; while (j <= n - 1) { if (j < n - 1 && a[j] < a[j + 1]) j++; if (temp >= a[j]) break; a[(j - 1) / 2] = a[j]; j = 2 * j + 1; } a[(j - 1) / 2] = temp; } public static void Display(int[] a, String str) { System.out.println(str); for (int i = 0; i < a.length; i++) System.out.print(a[i] + " "); System.out.println(); } }