说,我有10亿个数字存储在一个文件中。我怎么能找到以前曾出现的号码?
好吧,我不能只是在数组中一次填充数十亿个数字,然后保持一个简单的嵌套循环来检查数字是否先前出现过。
你会如何解决这个问题?
提前致谢:)
答案 0 :(得分:7)
我曾经把这作为面试问题。
这是一个O(N)
的算法使用哈希表。按顺序存储指向数字的指针,其中散列键是根据数值计算的。一旦发生碰撞,您就找到了副本。
下面,@ Phimuemue明确指出,在保证冲突之前,4字节整数具有固定的界限;即2 ^ 32,或约。 4GB。在伴随此答案的对话中考虑时,此算法的最坏情况内存消耗将大幅降低。
此外,使用如下所述的位阵列可以将内存消耗减少到1/8,512mb。在许多机器上,现在可以在不考虑 持久散列,或性能较低的排序优先策略的情况下进行此计算。
现在,较长的数字或双精度数字是比特阵列策略的低效方案。
当然需要采取一些“特殊”哈希表:
获取由2 ^ 32位组成的哈希表。由于该问题询问了4字节整数,因此它们中至多有2 ^ 32个不同,即每个数字一位。 2 ^ 32位= 512mb。
所以现在只需确定hashmap中相应位的位置并进行设置即可。如果遇到已经设置的位,则序列中已经出现了数字。
答案 1 :(得分:4)
重要的问题是,您是想要解决有效这个问题,还是想要准确。
如果你真的拥有100亿个数字并且只有一个单个副本,那么你就处于“大海捞针”状态。直观地说,没有非常肮脏和不稳定的解决方案,没有希望在不存储大量数字的情况下解决这个问题。
相反,转向概率解决方案,这已经在这个问题的大多数实际应用中使用过(在网络分析中,你要做的是寻找鼠标,即在大型数据集中很少出现的元素。)
可能的解决方案,可以找到确切的结果:使用足够高分辨率的Bloom filter。使用过滤器来确定是否已经看到一个元素,或者,如果你想要完美的准确性,使用(如kbrimington建议你使用标准哈希表)过滤器,呃,过滤掉你不可能拥有的元素看到,并在第二次通过,确定你实际看到两次的元素。
如果你的问题略有不同 - 例如,你知道你有至少0.001%的元素重复两次,你想知道有多少,或者你想获得这些元素的随机样本 - 然后在Flajolet & Martin,Alon等人的静脉中存在一整套概率流算法,并且非常有趣(更不用说高效)。
答案 2 :(得分:3)
读取文件一次,创建一个哈希表,存储您遇到每个项目的次数。可是等等!不使用项本身作为键,而是使用项iself的哈希值,例如最低有效数字,例如20个数字(1M项)。
第一次通过后,所有具有计数器>的项目1可能指向重复的项目,或者是误报。重新扫描文件,只考虑可能导致重复的项目(查找表1中的每个项目),使用实际值作为键构建新的哈希表并再次存储计数。
第二次传递后,计数>的项目第二个表中的1是您的副本。
这仍然是O(n),只是单次通过的两倍。
答案 3 :(得分:1)
怎么样:
答案 4 :(得分:1)
查找重复项
注意到它的32位整数意味着你将会有大量的重复,因为32位int只能代表43亿个不同的数字而且你有“10亿”。
如果您使用紧凑的集合,您可以表示是否所有可能性都是512 MB,这可以很容易地适应当前的RAM值。作为一个开头很容易让你认识到一个数字是否重复的事实。
重复计算
如果你需要知道一个数字被重复多少次,你就会得到一个只包含重复的hashmap(使用ram的前500MB来有效地告诉它是否应该在地图中)。在最糟糕的情况下,有一个很大的差价,你将无法适应ram。
如果数字具有均匀数量的重复数据,则另一种方法是使用紧密排列的数组,每个值为2-8位,占用大约1-4GB的RAM,允许您计算每个数字最多255次出现。< / p>
它将成为一个黑客,但它可行。
答案 5 :(得分:0)
你需要实现某种循环结构来一次读取一个数字,因为你不能一次将它们放在内存中。
如何?哦,你用的是哪种语言?
答案 6 :(得分:0)
你必须读取每个数字并将其存储到一个hashmap中,这样如果一个数字再次出现,它将自动被丢弃。
答案 7 :(得分:0)
如果文件中可能的数字范围不是太大,那么您可以使用一些位数组来指示范围内的某些数字是否出现。
答案 8 :(得分:0)
如果数字的范围足够小,您可以使用位字段存储(如果它在那里) - 通过文件中的单次扫描初始化该字段。每个可能的数字占一位。
对于大范围(如int),您需要每次都读取文件。文件布局可以允许更有效的查找(即,在排序数组的情况下进行二进制搜索)。
答案 9 :(得分:0)
如果时间不是问题且RAM是,您可以读取每个数字,然后通过读取文件将其与每个后续数字进行比较,而不将其存储在RAM中。这需要花费大量的时间,但你不会耗尽内存。
答案 10 :(得分:0)
我必须同意kbrimington和他对哈希表的看法,但首先,我想知道你正在寻找的数字的范围。基本上,如果您正在寻找32位数字,则需要一个4.294.967.296位的单个数组。首先将所有位设置为0,文件中的每个数字都将设置一个特定的位。如果该位已经设置,那么您已经找到了之前发生过的数字。你还需要知道它们发生的频率吗?
至少,它至少需要536.870.912字节。 (512 MB。)它很多,需要一些狡猾的编程技巧。根据您的编程语言和个人经验,将有数百种解决方案以这种方式解决。
答案 11 :(得分:0)
很久以前不得不这样做。 我做了什么......我尽可能多地对数字进行排序(有时间约束限制)并在排序时按照这样排列:
1到10,12,16,20到50,52会变成..
[1,10], 12, 16, [20,50], 52, ...
因为在我的情况下,我有数百个非常“接近”的数字($ a- $ b = 1),从几百万集我的内存使用率非常低
P.S。另一种存储方式
1, -9, 12, 16, 20, -30, 52,
当我没有低于零的数字时
之后我在减少的数据集上应用了各种算法(由其他海报描述)
答案 12 :(得分:0)
#include <stdio.h>
#include <stdlib.h>
/* Macro is overly general but I left it 'cos it's convenient */
#define BITOP(a,b,op) \
((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a))))
int main(void)
{
unsigned x=0;
size_t *seen = malloc(1<<8*sizeof(unsigned)-3);
while (scanf("%u", &x)>0 && !BITOP(seen,x,&)) BITOP(seen,x,|=);
if (BITOP(seen,x,&)) printf("duplicate is %u\n", x);
else printf("no duplicate\n");
return 0;
}
答案 13 :(得分:0)
这是一个简单的问题,可以很容易地解决(几行代码)和非常快(几分钟的执行)与正确的工具
我个人的做法是使用 MapReduce
MapReduce: Simplified Data Processing on Large Clusters
我很抱歉没有详细介绍,但是一旦熟悉了MapReduce的概念,就可以非常清楚如何定位解决方案了
基本上我们将实现两个简单的功能
答案 14 :(得分:0)
实现BitArray,使得该数组的第i个索引对应于数字8 * i +1到8 *(i + 1)-1。即如果我们已经看到8 * i + 1,则第i个第一位是1。如果我们已经看到8 * i + 2,那么第i个第二位是1。
初始化此位数组,其大小为Integer.Max / 8,每当您看到数字k时,如果此位已经为1,则将k / 8索引的k%8位设置为1表示您已经看到此数字。< / p>