如何优化eratosthenes的筛子以便存储非常大范围的素数?

时间:2016-02-04 09:48:59

标签: algorithm optimization primes sieve-of-eratosthenes space-complexity

我研究了 Eratosthenes Sieve 的工作,使用迭代生成到达给定数字的素数并且去掉所有复合数。并且算法只需要迭代到sqrt(n),其中n是我们需要找到所有素数的上限。 我们知道,与复合数相比,n = 10 ^ 9的素数数量非常少。因此,我们使用所有空间来通过标记它们的复合来告诉这些数字不是首要的。 我的问题是我们可以修改算法只是存储素数,因为我们处理的范围非常大(因为素数的数量非常少)? 我们可以直接存储素数吗?

5 个答案:

答案 0 :(得分:8)

将结构从一组(筛子)改变 - 每个候选者一位 - 到存储素数(例如在列表,向量或树结构中)实际上会增加存储要求。

示例:在2 ^ 32以下有203.280.221个素数。该大小的uint32_t数组需要大约775 MiB,而相应的位图(a.k.a.set表示)仅占用512 MiB(2 ^ 32位/ 8位/字节= 2 ^ 29字节)。

具有固定单元大小的最紧凑的基于数字的表示将存储连续奇数素数之间的减半距离,因为最多约2 ^ 40,所减半的距离适合于一个字节。对于素数高达2 ^ 32的193 MiB,这比仅有几率的位图略小,但它仅对顺序处理有效。对于筛分它是不合适的,因为正如Anatolijs指出的那样,像Eratosthenes筛子这样的算法实际上需要一套表示。

通过省略多个小素数,可以大幅缩小位图。最着名的是仅有赔率的表示,它排除了数字2及其倍数;这将空间需求减少到256 MiB,几乎不增加代码复杂性。你需要记住在需要的时候用空气拉出数字2,因为它没有在筛子中显示出来。

通过省去多个较小的素数,可以节省更多的空间;这种概率的概括只有'技巧通常被称为轮式存储' (参见维基百科中的Wheel Factorization)。然而,向车轮添加更多小质数的增益变得越来越小,而车轮模数(周长')爆炸式增加。添加3删除剩余数字的1/3,添加5除去另外的1/5,添加7只能再获得1/7,依此类推。

这里概述了为车轮添加另一个素数可以得到什么。 '比'是相对于代表每个数字的全套轮式/减少组的大小; '增量'给出与前一步骤相比的收缩。 '辐条'是指需要表示/存储的含铅辐条的数量;轮子的辐条总数当然等于其模数(周长)。

wheel efficiency table

mod 30车轮(大约136 MiB,质量高达2 ^ 32)提供了极佳的成本/效益比,因为它有八个主承载辐条,这意味着车轮之间存在一对一的对应关系和8位字节。这可以实现许多有效的实现技巧。然而,尽管存在这种偶然的情况,但代码复杂性增加的成本相当可观,并且出于许多目的,仅有几率的筛子(' mod 2 wheel')给予了最大的收益。

值得记住的另外两个考虑因素。首先,像这样的数据大小通常会大大超过内存缓存的容量,因此程序通常会花费大量时间等待内存系统传递数据。筛选的典型访问模式使这种情况更加复杂 - 一次又一次地跨越整个范围。通过以适合处理器的1级数据高速缓存的小批量处理数据(通常为32 KiB),可以实现几个数量级的加速;通过保持L2和L3高速缓存的容量(分别为几百KiB和几个MiB),仍然可以实现较低的加速。此处的关键字是'分段筛选'

第二个考虑因素是许多筛选任务 - 例如着名的SPOJ PRIME1及其更新版本PRINT(具有扩展范围和更严格的时间限制) - 仅需要小的因子素数到正方形永久可用于直接访问的上限的根。这是一个相对较小的数字:在PRINT的情况下筛分高达2 ^ 31时为3512。

由于这些素数已经被筛选,因此不再需要集合表示,并且因为它们很少,所以存储空间没有问题。这意味着它们最有利地保存为矢量或列表中的实际数字,以便于迭代,可能还有额外的辅助数据,如当前工作偏移和相位。然后通过一种称为'窗口筛分' 的技术轻松完成实际的筛分任务。在PRIME1和PRINT的情况下,这比从整个范围筛选到上限要快几个数量级,因为这两个任务只要求筛分少量子范围。

答案 1 :(得分:1)

你可以这样做(从数组/链表中删除被检测为非素数的数字),但算法的时间复杂度会降低到O(N ^ 2 / log(N))或类似的东西而不是原始的O(N * log(N))。这是因为你不能说“数字2X,3X,4X,......”不再是素数。您必须遍历整个压缩列表。

答案 2 :(得分:0)

一旦将其显示为复合,就可以从数组/向量中删除每个复合数。或者当您填充一组数字以通过筛子时,删除所有偶数(2除外)和所有以5结尾的数字。

答案 3 :(得分:0)

如果你正确地研究了筛子,你必须知道我们没有开始使用的素数。我们有array,sizeof等于范围。现在,如果您希望范围是10e9,您希望它是数组的大小。您没有提到任何语言,但对于每个数字,您必须使用bit来表示它是否为素数。

即使这意味着你需要10^9 bits = 1.125 * 10^8个字节,这个字节大于100 MB的RAM。

假设您拥有所有这些,大多数优化的筛选花费O(n * log(log n))时间,即n = 10e9,在每秒评估10e8条指令的计算机上,仍需要几分钟。

现在,假设你拥有所有这些,仍然是10e9 q = 50,847,534之前的素数,保存这些仍然需要q * 4 bytes,这仍然大于100MB。 (更多RAM)

即使删除了2,3或5的倍数的索引,也会删除21 numbers in every 30。这还不够好,因为总的来说,你仍需​​要大约140 MB个空间。 (40MB = 10 ^ 9位的三分之一+ ~100MB用于存储素数)。

所以,因为,为了存储质数,你将在任何情况下需要相似数量的内存(与计算的顺序相同),你的问题,IMO没有解决方案。

答案 4 :(得分:0)

只需“存储”即可将筛子的大小减半。奇数。这需要代码明确处理测试偶数的情况。对于奇数,筛子的位b代表n = 2b + 3.因此,位0代表3,位1代表5,依此类推。在数字n和位索引b之间进行转换的开销很小。

这项技术是否对您有用取决于您需要的内存/速度平衡。