Eratosthenes的筛子(减少空间复杂性)

时间:2013-03-27 23:36:56

标签: range primes sieve-of-eratosthenes space-complexity number-theory

我想在两个给定数字'a''b' b> a )之间生成素数。我所做的是将布尔值存储在大小为 b-1 的数组中(即数字 2 b )然后我应用了筛选法。

如果我不需要从 2 b 的所有素数,是否有更好的方法可以降低空间复杂度?

2 个答案:

答案 0 :(得分:1)

您需要存储小于b的平方根的所有素数,然后对于a和b之间的每个数字,检查它们是否可以被这些数字中的任何一个整除,并且它们不等于这些数字。所以在我们的例子中,幻数是sqrt(b)

答案 1 :(得分:1)

您可以使用Eratosthenes的分段筛。基本想法非常简单。

在一个典型的筛子中,我们从大量布尔值开始,都设置为相同的值。这些代表奇数,从3开始。我们看第一个并看到它是真的,所以我们将它添加到素数列表中。然后我们将该数字的每个倍数标记为非素数。

现在,问题在于它不是非常缓存友好的。当我们标记每个数字的倍数时,我们将遍历整个数组。然后当我们到达终点时,我们从头开始(不再在缓存中)并再次遍历整个阵列。每次通过数组,我们再次从主存中读取整个数组。

对于分段筛,我们做的事情有点不同。我们首先只找到我们关心的极限平方根的素数。然后我们使用它们来标记主阵列中的素数。这里的区别是 order ,我们在其中标记素数。而不是标记 all 三的倍数,然后是5的倍数,依此类推,我们首先标记适合缓存的数据的三倍数。然后,我们不再继续使用数组中的更多数据,而是返回并为适合缓存的数据标记五的倍数。然后是7的倍数,依此类推。

然后,当我们标记了那个缓存大小的数据块中的所有倍数时,我们继续前进到下一个缓存大小的数据块。我们重新开始,在这个块中标记3的倍数,然后是5的倍数,依此类推,直到我们标记了这个块中的所有倍数。我们继续这种模式,直到我们标记了所有块中的所有非素数,我们就完成了。

所以,如果N素数低于我们所关心的极限的平方根,那么一个天真的筛子将会读取整个布尔数阵列N次。相比之下,分段筛只会读取一次数据的每个块。一旦从主存储器读取了一大块数据,就会在从主存储器读取更多数据之前完成该块上的所有处理。

这给出的确切加速将取决于缓存速度与主内存速度的比率,您使用的阵列的大小与缓存的大小等等。尽管如此,它通常非常重要 - 例如,在我的特定机器上,寻找高达1亿的质数,分段筛的速度优势大约为10:1。

如果你正在使用C ++,你必须记住一件事。 std::vector<bool>的一个众所周知的问题是在C ++ 98/03下,vector<bool>需要是一个特殊化,它将每个布尔值存储为一个位,并带有一些代理技巧以获得类似bool的行为。从那以后,这个要求被取消了,但许多图书馆仍然包括它。

使用非分段筛,通常是一种有用的权衡。虽然它需要一点额外的CPU时间来计算掩码,并且一次只能修改一个比特,但它可以为主存储器节省足够的带宽,而不是补偿。

使用分段筛,主内存的带宽几乎不是一个很大的因素,因此使用vector<char>通常似乎可以提供更好的结果(至少使用我编写的编译器和处理器)。

从分段筛获得最佳性能确实需要了解处理器缓存的大小,但要正确地获得它并不重要 - 如果您认为尺寸小于实际尺寸,则不一定充分利用您的缓存,但通常也不会失去很多。