关于本篇博客
这是我的第一篇博客,大二,刚开始备赛蓝桥杯的c/c++组,就遇到了一道有关素数的题,那篇试题给出解答的作者提到了先用素数筛把素数选出来,用到了一个我陌生的算法,所以我下来看了关于素数筛的很多博客。
关于素数筛
我想在网上已经有很多很多博客都有关于素数筛选的方法了,总的来看我发现大的方向来说就三种,在这里归纳一下。
- 直接暴力利用素数的定义筛除
- 埃氏筛法及优化
- 欧式筛法
第一种:定义法
这里引用百度百科上素数的定义,质数也就是素数,指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
所以给了我们的一个比较直接能够想到的思路就是:我们如果能在小于它的数中找到它的一个除了1和0以外的因素,它就不是素数,反之则就是一个素数。
对于程序实现来说,这个算法也是非常的简单,下面贴上代码:
#include <stdio.h>
#define N 100000
void main(){
int prime[N],cot=0;
for (int n=2; n<N; n++){
for (int i=2; i<n; i++)
if (!(n%i)) break;
if (i==n) prime[cot++]=n;
}
printf("在%d以内一共有%d个素数\n",N,cot);
for (int i=0; i<cot; i++){
printf("%d ",prime[i]);
if (!(i%10) && i!=0) printf("\n");
}
}
第二种:埃式筛法
这种方法的原理是:把所求范围内的整数顺序排列,在1以后的,当前最小的数则是素数,然后再逐个剔除它的倍数,直到越界时结束,依次类推直到越界则剩下的全是素数。
这个算法的原理也是简单易懂,下面附上代码:
#include <stdio.h>
#include <string.h>
#define N 100000
void main(){
int prime[N],isprime[N]={0},cot=0;
for (int i=1; i<N; i++) isprime[i]=1;
for(i=2; i<N); i++){
if (isprime[i]){
prime[cot++]=i;
for(int j=2; i*j<N; j++){
isprime[i*j]=0;
}
}
}
printf("在%d以内一共有%d个素数\n",N,cot);
for (i=0; i<cot; i++){
printf("%d ",prime[i]);
if (!(i%10) && i!=0) printf("\n");
}
}
但是在你跟着程序多走几步以后你就会发现它的一个比较大的缺陷。它会进行大量的重复剔除,例如当剔除2的倍数时,4,6,8,10,12…会被标记剔除,当剔除3的倍数时,6,9,12…会被标记剔除,我们能看到就短短几步,就有6和12被重复标记了。
我们再仔细观察,当剔除2的倍数时,我们已经把所有的偶数都已经剔除掉了,剩下的全是奇数,所以在后面剔除奇数的偶数倍时,都是在重复标记。所以提供了一个基本的思路就是:先把所有的除2以外的偶数直接标记剔除掉,然后再用大于2的奇数剔除它们的奇数倍即可。
代码部分只需在上面的基础上做一些简单的修改即可:
#include <stdio.h>
#include <string.h>
#define N 10000
void main(){
int prime[N],isprime[N]={0},cot=0;
for (int i=3; i<N; i+=2) isprime[i]=1;
isprime[2]=1;
for(i=2; i<N; i++){
if (isprime[i]){
prime[cot++]=i;
for(int j=3; i*j<N; j+=2){
isprime[i*j]=0;
}
}
}
printf("在%d以内一共有%d个素数\n",N,cot);
for (i=0; i<cot; i++){
printf("%d ",prime[i]);
if (!(i%10) && i!=0) printf("\n");
}
}
在这个基础上我们还能对算法进一步优化,就是关于倍数for循环的起始值问题,上面的代码是都是从3开始,但是我们如果跟着程序走几个数就会发现,在 i * (i-2k) 前,必有 (i-2k) * i 已经把它剔除过了,例如5*3,在3*5时就已经剔除过了。所以我们的起始值就可以设置为 i 本身,这样又可以再减少一些步骤。
#include <stdio.h>
#include <string.h>
#define N 10000
void main(){
int prime[N],isprime[N]={0},cot=1;
for (int i=3; i<N; i+=2) isprime[i]=1;
isprime[2]=1; prime[0]=2;
for(i=3; i<N; i++){
if (isprime[i]){
prime[cot++]=i;
for(int j=i; i*j<N; j+=2){
isprime[i*j]=0;
}
}
}
printf("在%d以内一共有%d个素数\n",N,cot);
for (i=0; i<cot; i++){
printf("%d ",prime[i]);
if (!(i%10) && i!=0) printf("\n");
}
}
在这里我发现上诉的这些方法都还可以进一步优化,比如3*15和5*9就还是重复,但是这里我还没有想到进一步的优化方法。大家可以下去自己尝试一下,也可以和我一起讨论一下。