为什么Eratosthenes的Sieve比简单的“哑”算法更有效?

时间:2011-04-13 12:18:45

标签: algorithm performance big-o

如果你需要从1到N生成素数,那么“愚蠢”的方法就是迭代从2到N的所有数字并检查这些数字是否可以被到目前为止找到的任何素数除数,这是小于相关数字的平方根。

正如我所看到的,Eratosthenes的筛子做了同样的事情,除了其他方式 - 当它找到一个素数N时,它标记所有N的倍数的数字。

但是,当你找到N时是否标记X,或者你检查X是否可以被N除可,基本的复杂性,大O保持不变。你仍然按照数字对进行一次恒定时间操作。事实上,愚蠢的算法一旦找到一个素数就会中断,但是Eratosthenes的筛子会多次标记每个数字 - 一次对于每个素数它都可以被分割。对于除素数之外的每个数字,这是最少两倍的操作。

我在这里误解了什么吗?

7 个答案:

答案 0 :(得分:18)

在试验分割算法中,确定数字n是否为素数所需的大部分工作是通过素数来测试可分性,大约为sqrt(n)

n是素数或两个几乎相同大小的素数(包括素数的平方)的乘积时,会遇到最坏的情况。如果n有两个以上的素因子,或两个大小不同的素因子,其中至少有一个比sqrt(n)小得多,所以即使是所有这些数字所需的累积工作(形成的也是如此)绝大多数N的数字,对于足够大的N来说是相对微不足道的,我将忽略这一点,并与小说一起确定复合数字是在没有做任何工作的情况下确定的(两个产品)大约相等的素数在数量上很少,因此虽然它们的成本与相似大小的素数一样多,但总的来说这是一个可以忽略不计的工作量。)

那么,在N之前测试质数的工作量是多少?

根据素数定理,素数<= n的数量(n足够大),大约n/log n(它是n/log n + lower order terms)。相反,这意味着 k -th prime是(对于 k 不是太小)关于k*log k(+低阶项)。

因此,测试 k -th prime需要pi(sqrt(p_k))的试验除法,大约2*sqrt(k/log k),素数。总结k <= pi(N) ~ N/log N总共产生约4/3*N^(3/2)/(log N)^2个除法。因此,通过忽略复合,我们发现通过试验除法(仅使用素数)找到N的素数是Omega(N^1.5 / (log N)^2)。对复合材料的仔细分析表明它是Theta(N^1.5 / (log N)^2)。使用滚轮减少了常数因素,但不会改变复杂性。

另一方面,在筛子中,每种复合物作为至少一种撇号的倍数被划掉。根据您是在2*p还是p*p开始交叉,复合被划掉多次,因为它具有不同的素因子或不同的素因子<= sqrt(n)。由于任何数字最多只有一个素数因子超过sqrt(n),因此差异不是很大,它对复杂性没有影响,但是有很多数字只有两个素数因子(或者三个比一个更大) sqrt(n)),因此它在运行时间方面有明显的不同。无论如何,一个数n > 0只有很少的不同素数因子,一个简单的估计表明,不同素数因子的数量受lg n(基数为2的对数)的限制,因此数量的上限为过筛的过程是N*lg N

通过计算每个复合材料被划掉的频率,但是每个素数的多少被划掉,正如IVlad已经做过的那样,人们很容易发现交叉的数量实际上是Theta(N*log log N)。同样,使用车轮不会改变复杂性,但会降低常数因素。但是,这里它的影响力比试验分区要大,所以至少应该跳过平均值(除了减少工作量,它还会减少存储空间,因此可以改善缓存局部性)。

因此,即使不考虑该划分比加法和乘法更昂贵,我们也看到筛子所需的操作次数远小于试验所需的操作次数(如果限制不是太小)。 / p>

汇总:
试验师通过划分素数来完成徒劳的工作,筛子通过反复穿越复合材料来完成徒劳的工作。有相对较少的素数,但许多复合材料,所以人们可能会想到试验师浪费较少的工作 但是:复合材料只有很少的不同素因子,而有许多素数低于sqrt(p)

答案 1 :(得分:11)

在天真的方法中,您为每个号码O(sqrt(num))执行num次操作,检查是否为素数。这总是O(n*sqrt(n))

在筛选方法中,对于从1到n的每个未标记的数字,当标记n / 2的{​​{1}},2的倍数时,您会执行n / 3次操作标记3时的{},n / 5,这是5,即n*(1/2 + 1/3 + 1/5 + 1/7 + ...)。有关该结果,请参阅here

所以渐近的复杂性与你所说的不一样。即使是一个天真的筛子也会很快击败天真的素数法。筛网的优化版本可以更快,但大哦保持不变。

这两个并不像你说的那样。对于每个数字,您将在朴素素数生成算法中使用相同的素数O(n log log n)检查可分性。随着您的进步,您可以通过相同的一系列数字检查可分性(当您接近2, 3, 5, 7, ...时,您会越来越多地检查)。对于筛子,当你接近n时,你会越来越少地检查。首先,您检查n,然后是2,然后是3等增量。这将点击5并停止更快。

答案 2 :(得分:6)

因为使用筛选方法,当运行的素数达到N的平方根时,停止标记正在运行的素数的多个。

说,你想找到不到一百万的所有素数。

首先设置一个数组

for i = 2 to 1000000
  primetest[i] = true

然后你迭代

for j=2 to 1000         <--- 1000 is the square root of 10000000
  if primetest[j]                                    <--- if j is prime
    ---mark all multiples of j (except j itself) as "not a prime"
    for k = j^2 to 1000000 step j
      primetest[k] = false

你不必在1000之后检查j,因为j * j将超过一百万。 你从j * j开始(你不必将j的倍数标记为小于j ^ 2,因为它们已被标记为先前找到的较小的素数的倍数)

所以,最后你完成了1000次循环,if部分仅用于那些作为素数的j。

第二个原因是,用筛子,你只做乘法,而不是除法。如果你聪明地做,你只做加法,甚至不是乘法。

分裂的复杂性大于加法。通常的除法方法有O(n^2)复杂度,而加法有O(n)

答案 3 :(得分:5)

在本文中解释:http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf

我认为即使没有Haskell知识它也很可读。

答案 4 :(得分:4)

第一个区别是除法 比加法更昂贵。即使每个数字都被“标记”了好几次,但与“哑”算法所需的大量分割相比,这是微不足道的。

答案 5 :(得分:0)

Eratosthenes的“天真”筛子将多次标记非素数。 但是,如果你在链表上有你的数字并删除数字taht是倍数(你仍然需要走列表的其余部分),找到素数后剩下的工作总是小于找到素数之前的工作。

答案 6 :(得分:0)

http://en.wikipedia.org/wiki/Prime_number#Number_of_prime_numbers_below_a_given_number

  • “哑”算法i/log(i) ~= N/log(N)适用于每个素数
  • 真实算法N/i ~= 1适用于每个素数

乘以大约N/log(N)素数。