欧拉筛是线性时间复杂度筛选素数的算法。
先看一般筛法寻找素数:
findPrime(n) isPrime = array primes = empty-list isPrime[1] = false; for(i = 2; i < n; i++) isPrime[i] = true for(i = 2; i < n; i++) if(isPrime[i]) primes.add(i) for p in primes
if(p * i >= n) break isPrime[i * p] = false
return primes
先说明上面的代码可以正确找到所有[1,n)之间的素数。如果一个数x是素数,那么isPrime[x]恒为真。如果x为合数,则可以分解为p与x/p,其中p是x的最小素因子。而p,x/p<x,我们不妨设p<=x/p,则当i=x/p时,此时p已经作为素数加入到了primes中,而isPrime[p * i] = false会将x设为合数,因此x会作为合数分离出去。故最后所有的素数都记录在了primes中,且记录在primes中的也只有素数。
上面的过程是正确的,时间复杂度为∑ln(i)=O(ln(n)*n)。ln(n)是素数定理中对素数分布的估计。
我们发现上面这个过程中涉及到多次对同一个合数设置其isPrime属性,比如12,会由于4*3以及6*2被两次设置,而由于多次设置的现象存在,我们很难将时间复杂度优化到线性。而欧拉筛则在上面这个素数筛的基础上,要求只有当i取值为x的最大因子时,才允许设置x为合数。而实现出乎意料的简单,只用加上一行代码。
findPrime(limit)
isPrime = array
primes = empty-list
isPrime[1] = false;
for(i = 2; i < limit; i++)
isPrime[i] = true
for(i = 2; i < limit; i++)
if(isPrime[i])
primes.add(i)
for p in primes
if(i * p >= n) break
isPrime[i * p] = false
if(i % p == 0) break
return primes
先说明这个算法是正确的。当i>p且能整除p时,对于任意素数k>p,ik的最大因子显然不是i(至少为pk),因此可以直接跳过后面的流程。当然还有一种特殊情况就是i=p,此时跳出与不跳出都不影响结果,因为循环已经濒临结束了。而对于primes中最终结果的正确性与上面算法的证明一致。
对于时间复杂度,外部循环最多执行n次,每次都是常数时间复杂度,而内部循环每次执行都会设置一个合数的isPrime属性,而每个合数只会被设置一次,因此内部循环执行的次数与合数总数等阶,即为O(n)。因此总的时间复杂度为O(n)。