我的问题不常见。让我们想象几十亿字符串。字符串通常少于15个字符。在此列表中,我需要找出唯一元素的数量。
首先,我应该使用什么对象?你不应该忘记,如果我添加一个新的元素,我必须检查它是否已经存在于列表中。这在一开始并不是问题,但在几百万字之后,它确实会减慢这个过程。
这就是为什么我认为Hashtable是这项任务的理想选择,因为检查列表理想情况下只有log(1)。不幸的是.net中的单个对象只能是2GB。
下一步将实现一个包含2GB哈希表列表的自定义哈希表。
我想知道也许你们中的一些人知道更好的解决方案。 (计算机具有极高的规格。)
答案 0 :(得分:28)
我会跳过数据结构练习并只使用SQL数据库。为什么要编写另一个必须分析和调试的自定义数据结构,只需使用数据库。他们非常擅长回答这样的问题。
答案 1 :(得分:23)
我认为Trie或Directed acyclic word graph应该比哈希表更节省空间。测试字符串的成员资格将是O(len),其中len是输入字符串的长度,这可能与字符串散列函数相同。
答案 2 :(得分:7)
这可以使用radix sort在最坏情况下的O( n )时间内解决,其中计数排序作为每个字符位置的稳定排序。这在理论上比使用哈希表(O( n )预期但不保证)或mergesort(O( n log n ))更好。使用trie也会导致最坏情况下的O( n ) - 时间解决方案(对 n 键进行恒定时间查找,因为所有字符串的有界长度都是小常数),所以这是可比的。我不确定他们在实践中如何比较。 Radix排序也很容易实现,并且有很多现有的实现。
如果所有字符串都是 d 字符或更短,并且不同字符的数量是 k ,则基数排序需要O( d ( n + k ))排序 n 键的时间。排序后,您可以在O( n )时间内遍历排序列表,并在每次到达新字符串时递增计数器。这将是不同字符串的数量。由于 d 为〜15且 k 与 n (十亿)相比相对较小,因此运行时间也不算太差。
这使用了O( dn )空间(用于保存每个字符串),因此它的空间效率低于尝试。
答案 3 :(得分:4)
如果这些项目是可比较的字符串......那么我建议放弃使用Hashtable的想法并使用更像二进制搜索树的东西。 C#中有几个实现(没有内置于Framework中)。一定要得到一个平衡的,如红黑树或AVL树。
优点是树中的每个对象都相对较小(只包含它的对象,以及到它的父对象和两个叶子的链接),所以你可以有一大堆它们。
另外,因为它已经排序,所以检索和插入时间都是O log(n)。
答案 4 :(得分:3)
由于您指定单个对象不能包含所有字符串,因此我假设您在磁盘或其他外部存储器上有字符串。在那种情况下,我可能会去排序。从排序列表中提取唯一元素很简单。合并排序很受外部排序的欢迎,并且只需要与您拥有的额外空间相等的额外空间。首先将输入分成适合内存的部分,然后对它们进行排序,然后开始合并。
答案 5 :(得分:2)
如果有几十亿个字符串,即使只有几个字符串是唯一的,哈希冲突的可能性也很高(.NET哈希码是32位整数,产生大约40亿个唯一哈希值。如果你有的话很少作为1亿个唯一字符串,哈希冲突的风险可能高得令人无法接受。统计数据不是我的最强点,但是一些谷歌研究发现,完美分布的32位散列的冲突概率是(N-1)/ 2 ^ 32,其中N是散列的唯一事物的数量
使用使用明显更多位such as SHA-1的算法运行哈希冲突的概率较低。
假设有足够的哈希算法,一个接近你已经尝试过的简单方法就是创建一个哈希表数组。将可能的哈希值划分为足够的数值范围,以便任何给定的块不会超过每个对象的2GB限制。根据哈希值选择正确的哈希表,然后在该哈希表中进行搜索。例如,您可以创建256个哈希表并使用(HashValue)%256来获取0..255的哈希表编号。在将字符串分配给存储桶时,以及在检查/检索字符串时使用相同的算法。
答案 6 :(得分:1)
分而治之 - 用前两个字母分隔数据(比如说)
字典xx =>字典字符串=>计数
答案 7 :(得分:1)
我会使用数据库,任何数据库都可以。
可能是最快的因为现代数据库针对速度和内存使用进行了优化。
您只需要一个带索引的列,然后您可以计算记录数。
答案 8 :(得分:1)
字典<>在内部组织为列表列表。您将无法接近64位计算机上的(2GB / 8)^ 2限制。
答案 9 :(得分:0)
您是否尝试过Hash-map(.Net中的字典)?
Dictionary<String, byte>
在x86上每个条目只占用5个字节(4个用于指向字符串池的指针,1个用于字节),这大约是400M个元素。如果有很多重复,他们应该能够适应。实现方面,它可能很慢(或不工作),因为您还需要将所有这些字符串存储在内存中。
如果字符串非常相似,您也可以编写自己的Trie实现。
否则,你最好的赌注就是在磁盘上就地排序数据(之后计算独特的元素是微不足道的),或者使用更低级别,内存更紧密的语言,如C ++。
答案 10 :(得分:0)
我同意有关数据库解决方案的其他海报,但除此之外,触发器的合理智能使用,以及潜在可爱的索引方案(即字符串的数字表示)将是最快的方法,恕我直言
答案 11 :(得分:0)
+1的SQL / Db解决方案,让事情变得简单 - 将让您专注于手头的实际任务。
但仅仅出于学术目的,我想加上我的2美分。
对于哈希表,-1。 (我还不能投票)。因为它们是使用桶实现的,所以在许多实际实现中存储成本可能是巨大的。另外我同意Eric J的说法,碰撞的可能性会削弱时间效率优势。
李,trie或DAWG的构建将占用空间以及一些额外的时间(初始化延迟)。如果这不是问题(将来你可能需要对字符串集执行搜索操作,并且你有足够的内存可用),尝试可能是一个不错的选择。空间将成为Radix排序或类似实现的问题(如KirarinSnow所述),因为数据集很大。
以下是我的一次性重复计数的解决方案,限制了可以使用的空间。
如果我们的存储器可用于在我的内存中保存10亿个元素,我们可以在Θ(n log n)时间内通过heap-sort对它们进行排序,然后在O中简单地遍历集合一次(n)时间并且这样做:
if (a[i] == a[i+1])
dupCount++;
如果我们没有那么多可用内存,我们可以将磁盘上的输入文件分成较小的文件(直到大小变小到足以将集合保存在内存中);然后使用上述技术对每个这样的小文件进行排序;然后将它们合并在一起这需要在主输入文件上进行多次传递。
我希望远离quick-sort因为数据集很大。如果我可以为第二种情况挤出一些内存,我最好用它来减少传递次数而不是浪费它在merge-sort / quick-sort中(实际上,它在很大程度上取决于我们手头的输入类型)。
编辑:仅当您需要长时间存储此数据时,SQl / DB解决方案才有效。
答案 12 :(得分:0)
如果您需要的是唯一计数的近似值,请寻找HyperLogLog算法。它用于获得对大型数据集(如您所指的基数)的基数的近似估计。 Google BigQuery,Reddit将其用于类似目的。许多现代数据库已经实现了这一点。它非常快,并且可以用最少的内存工作。