埃拉托色尼筛法的复杂度估计与改进 及线性时间筛法简介
本文研究用以求出1~n内的所有素数的算法——埃拉托色尼筛法(the Sieve of Eratosthenes),估计其算法复杂度,并介绍其改进——线性时间筛法。以下内容谢绝转载。
1.埃拉托色尼筛法(the Sieve of Eratosthenes)
埃氏筛法是一种古老的筛选素数的方法,以古希腊数学家埃拉托色尼的名字命名。
算法思想是从1~n中依次删除2的倍数,3的倍数,.......,从而得到所有的素数。
算法伪码:
用$A[i]$表示$i$是否为素数,是为true,否为false。初始化$A[2 \cdots n]=true$.
for $i=2$ to $\sqrt n$
if $A[i]$ is true
for $j=2$ to $n/i$
$A[i*j]$=false;
end
end
end
输出:
all $p$ s.t. $A[p]$ is true
由于合数$N$一定有不超过$\sqrt N$的质因子,因此外层循环只需循环到$\sqrt n$
2.埃氏筛法的复杂度估计
复杂度估计可以如下简单地考虑:外层循环是 $i$ 时,内层循环共执行 $\frac{n}{i}-1$ 次。因此总的时间消耗为:
$$\sum_{primes \, p\le \sqrt n} \frac{n}{p}-1 = n \sum_{primes \, p \le \sqrt n} \frac{1}{p} - \pi(n)$$
其中$\pi(x)$表示不超过$x$的素数个数。根据素数定理,$\pi(x)=\Theta(\frac{x}{\ln x})$
根据Mertens' 2nd theorem:
$$\lim_{n\to\infty} \sum_{primes \ p \le n}\frac{1}{p} -\ln\ln n =M$$
其中$M$是Meissel-Mertens常数,约为$0.26$
由此可见算法的时间消耗为$\Theta (n \log \log n)$
注:一个小优化
可以将内层循环的从$j=i$而不是$j=2$开始。这是因为一个合数一定在$i$是其最小素因子时所标记过,因此只要考虑$j$比$i$大的情况就可以了.
但是这个优化对复杂度的阶并没有改进。这是因为对于不超过$\frac{1}{2}\sqrt n$的素因子来说,以上优化只减少了至多一半的操作量(证明留作习题)
然而上述求和即使把求和上限改成$\frac{1}{2}\sqrt n$,其阶仍然有$\Omega (n \log \log n)$,因此这只是一个常数级别的优化。
3.埃氏筛法的改进——线性时间筛法
从上述分析可以看出,埃氏算法并不是一个线性时间算法。事实上,这是因为对于一个合数而言,它有多少个素因子,就被标记成合数多少次。这对于约数较多的数是很慢的。
埃氏筛法有一个简单的改进,使得其复杂度降低到线性时间。我们称它为线性时间筛法。
算法伪码:
用$A[i]$表示$i$是否为素数,是为true,否为false。初始化$A[2 \cdots n]=true$。维护一个数组q,存放所有的素数。num表示q中质数的数量,初始为0.
for $i=2$ to $n$
if $A[i]$ is true
num=num+1;
q[num]=i;
end
for $j=1$ to num
$p$=$q[j]$;
$A[i*p]$=false;
if $p|i$ break;
end//其实这个循环的意思就是,对所有不超过 $i$ 最小素因子的素数p,将 $i*p$标记为合数
end
输出:
q[1...num]
下面证明算法的正确性和线性时间性。
容易看出,一个数$k$被算法标记为合数当且仅当存在$p$和$i$使得$k=ip$,且$p$不超过$i$的最小素因子。这也就是说$p$是$k$的最小素因子。
对于合数$k$来说,这表明它仅被标记为合数一次.
而对于素数$k$来说,由于标记为合数要求$k=ip,i,p\ge 2$,因此它不会被标记为合数。
因此,算法是正确的,也是线性时间的。于是我们完成了证明。
References:
Mertens' 2nd theorem:
http://en.wikipedia.org/wiki/Mertens%27_theorems