先证明一个结论:只要一个数n不能被所有的从2到(n的开方)之间的素数整除,那它就一定是质数。
假如n是合数,那么n肯定能表示成x*y,1<x,y<n
不失一般性,如果x>=(n的开方),则有y<=(n的开方)
因此如果n是合数,就能在2~(n的开方)范围内找到它的一个因子
即一个数是否是素数是查找范围不大于这个数的开方
因此,如果n不能被所有的从2到(n的开方)之间的素数整除,(因为n的因子或者为素数,或者为合数,是合数的话肯定能表示成素数的乘积)那么它一定是素数,换句话说合数肯定有素数因子
基于这个思想,最简单的素数筛选算法如下,时间复杂度O(n*sqrt(n)):
int num = 0;
int prime[N];
for(i = 4; i <= N; i++)
{
for(j = 2; j <= sqrt(i); j++)
{
if(i%j == 0) break;
prime[num++] = i;
}
}
上面的算法没有一点的复杂度,估计连算法都称不上吧。
我们可以利用素数筛法的思想:当一个数是素数时,它的倍数肯定是合数。基于前面的推论有了以下的筛选算法:
bool prime[N+1];
for(i = 0; i < N+1; i++)
下标为奇数的标为true,下标为偶数的标为false
for(i = 3; i <= sqrt(N); i++)
{
if(prime[i])
{
for(j = i+i; j < N+1; j+=i)
prime[j] = false;
}
}
最后输出下标为true的元素即可
可以考虑进一步优化算法。
首先能想到的是偶数肯定不是素数,因此prime只表示奇数,不表示偶数,即0表示3,1表示5,2表示7,3表示9....下标i表示的整数为(2i+3),算法的复杂度减少了不只一半的循环时间。
bool prime[N+1];
for(i = 0; i < N+1; i++)
下标为奇数的标为true,下标为偶数的标为false
for(i = 3; i <= sqrt(N); i=i+2)
{
if(prime[i/2-1])
{
for(j = 2*i+i; j < N+1; j+=2*i)
prime[j/2-1] = false;
}
}
最后输出下标为true的元素即可
进一步分析,可以发现,有些元素重复筛过的,例如合数15在素数3和素数5时都遍历了
由前面的推论的可以知道:n如果是素数的话,那么就肯定能在n开方的范围内找出它的因子,从而判断它是否为素数。逆向思考,为了判断n是否为素数,由于我们已经遍历了n开方内的所有素数i(的所有合数),小于i平方的数要么是素数,要么是合数是已经确定的。因此,当遍历i的合数是时,我们只需从i平方开始,小于i平方的数无需重复遍历。好像有点绕,说白了还是推论的思想。
举个例子,3(i=0)直接从下标[3](对应值为9)开始筛,5(i=1)从下标为[11](对应值25)开始筛。7(i=2)本来应该从下标为[9](对应值21)开始筛,但是由于[9]被筛过了(被i=0筛过),而[16]也已经被5(i=1)筛过。于是7(i=2)从[23](就是2*23+3=49)开始筛。于是当外围循环为下标为i时,内循环的下标是从i+[(2*i+3)2-(2i+3)]/2即i*(2*i+6)+3开始筛的。
bool prime[N+1];
for(i = 0; i < N+1; i++)
下标为奇数的标为true,下标为偶数的标为false
for(i = 3; i <= sqrt(N); i=i+2)
{
if(prime[i/2-1])
{
for(j = i*i; j < N+1; j+=2*i)
prime[j/2-1] = false;
}
}
最后输出下标为true的元素即可
代码还没验证过,尚需完善...