我设置了A和B
设置A有1亿个数字(每个数字是64位)
设置B有1亿个数字(每个数字是64位)
两套相同的尺寸。
数据全部是随机的,没有排序。
您建议使用哪种算法来查找两组之间的重复数字?
(我可以利用大约4G内存和100~200 Gb的磁盘空间)
提前谢谢。
答案 0 :(得分:2)
假设首先arr是arr1,第二个是arr2;
sort arr1//max O(n*log_n)
for(int i = 0; i < arr2.length; i++){ //n
binarySearch(arr1, arr2[i]) //log_n
}
总计O(n logn)
答案 1 :(得分:2)
执行时间方面可能最便宜(但不幸的是,编程时间)是将A中的元素放入open-addressed hash table,然后在哈希表中查看B的每个元素。如果你能提出一个合理的散列函数(下面更多),那么你可以使用简单的线性散列,加载因子约为60%,这意味着你的表将占用10 8 *(1 /。 6)* 8字节,或约1.3 GB。 (我不知道在标准库中提供开放地址哈希表的语言; C ++的unordered_sets是用存储桶链实现的,如果单个元素不是单独的存储分配,那只会略微增加开销。一个好的分配器可能会使这个可行。)
幸运的是,开放式地址线性探测哈希表非常容易编写,特别是如果您不需要处理删除元素。您只有两个问题:
您需要保留一些意味着“无人居住”的值。
您需要一个好的哈希函数。或者至少是合理的。
如果您的数据真正随机分布在64位空间中,则散列很简单;您只需将数据减少到所需的大小。一个简单的方法是使用模运算符,即使数据不是完全随机分布也应该很好,只要你安排使表大小成为一个素数(166666783大约适合60%负载大小)有1亿元素的因素)。
找到一个意味着“无人居住”的值可能会更棘手。您可能已经知道一个值是不可能的(可能是值0
)。如果没有,你可以选择一个随机的64位数字;在你的数据集中没有它的可能性非常好,但是如果你有一个简单的备份:不要把它放到哈希表中,并检查B
的每一个值。
Pseudo-C ++ - 代码,基于上面的描述,包括提到的“无价值”黑客:
class HundredMillionSet {
std::vector<uint64_t> h_;
const size_t size_
const uint64_t no_value_;
bool has_no_value_;
HundredMillionSet(size_t size, uint64_t no_value)
: h_(size, no_value), size_(size), no_value_(no_value), has_no_value_(false) {}
void insert(uint64_t v) {
if (v == no_value_) { has_no_value_ = true; }
else {
size_t i = v % size_;
while (h_[i] != no_value_) {
if (++i == size_) i = 0;
}
h_[i] = v;
}
}
bool check(uint64_t v) {
if (v == no_value_) return has_no_value_;
size_t i = v % size_;
while (h_[i] != v && h_[i] != no_value_) {
if (++i == size_) i = 0;
}
return h_[i] == v;
}
};
答案 2 :(得分:0)
64bit = 8byte。 2 * 8 * 100,000,000byte = 1.6GB =&gt;你可以在RAM中保存你的数字(你可能需要两倍的节点结构...)。我会选择平衡二叉树(只搜索AVL,AB ...树木的维基)。只需将一组中的数字添加到一棵树,从另一组添加到另一棵树,然后进行交叉。
您可能只想对两个数组进行排序并将它们相交。这应该是最简单的解决方案。
如果您无法处理内存中的所有数字,请使用数据库(MySQL,PostgreSQL ...)。两个排序的表和交叉点。它应该非常快,最重要的是易于实现。
答案 3 :(得分:0)
由于您的整个数据集(很容易)适合您的RAM,因此您无需在订购时做任何聪明的事情,也不需要使用磁盘空间(除了首先加载数据)。
我假设每个元素在每个列表中最多只能存在一次。
哑(蛮力)方法,O(n ^ 2):
foreach a in A (this could be as you read it from disk)
foreach b in B
if a is b
increase count
break out of inner loop
预先排序的方法:(2 * n * log(n)+ n),所以O(n * log(n))
sort A
sort B
B_ind = 0
foreach a in A
foreach b in B from B_ind
if a is b
increase count
B_ind = current b index + 1
break out of inner loop
else if b > a
B_ind = current b index
break out of inner loop
我推荐前者,但需要并行处理。很容易将外部循环拆分成块以在处理器/工作站之间进行分割。后者也可以在某种程度上并行化(同时进行两种排序)但远不及那么多。
同样在前者的情况下,通过将b循环拆分为缓存大小的块,可以获得一些性能提升。即检查A [0]对B [0 ... 1023](如果你的缓存可容纳1024项),然后检查A [1],... A [final],然后检查A [0]对B [1024] ...... [2047]等等。