朴素算法
可以先来看看一道简单的题目:
试打印 1 - n 范围内的素数表。
分析:
好,我们从1~n进行枚举,判断每个素数是否是素数,如果是素数,则加入素数表。那么这个算法的时间复杂度是多少呢?可以看到,枚举部分的复杂度是O(n),而判断素数的算法是O(n^1/2),因此总复杂度是O(n * n ^1/2)。但是显然这个复杂度对n超过10 ^ 5的情况来说是有问题的。因为对一般的oj系统来说,一秒能承受的运算次数大概是10 ^ 7 ~ 10 ^ 8,而程序一般要求1s内进行。自己可以估算一下。
给出代码(以求解100以内的所有素数为例):
#include<stdio.h>
#include<math.h>
bool isPrime(int n){//判断n是否为素数
if(n<=1)return false;
int sqr = (int)sqrt(1.0*n);
for(int i = 2;i<=sqr;i++){
if(n%i==0)return false;
}
return true;
}
int prime[101],pNum = 0;//prime[]数组存放素数,pNum为数组下标
bool p[101] = {0};
void Find_Prime(){
for(int i = 1;i<101;i++){
if(isPrime(i) == true){
prime[pNum++] = i;
p[i] = true;
}
}
}
int main(){
Find_Prime();
for(int i = 0;i<pNum;i++){
printf("%d ",prime[i]);
}
}
埃氏筛法
好,我们接着上面讲的继续讲下去,既然朴素算法的时间复杂度不是很令人满意,那么是否有更好的算法呢?
“埃氏筛法”就是众多筛法中简单的一种,可以达到O(nloglogn)的时间复杂度。所谓“筛法”关键就在于一个筛字。从小到大枚举所有数,对每一个素数,筛去它的所有倍数,剩下的都是素数了。这听起来好像是在逗人???你还不知道哪个数是素数呢,简直就是悖论。
先来看一个例子吧:求1~15中的所有素数。
- 2是素数(唯一需要事先确定),因此筛去所有2的倍数,即2,4,6,8
- 3没有被前面的步骤筛去,因此3是素数,筛去所有3的倍数,即6,9,12,15。
- 4已经被步骤1筛去,因此4不是素数
- 5没有被前面的数筛去,因此5是素数,筛去所有5的倍数,即10、15
- 6被筛去,不是素数
- 7没有被筛去,是素数,筛去7的倍数:14
- 8被筛去,不是素数
- 9被筛去,不是素数
- 10被筛去,不是素数
- 11没被筛去,是素数
- 12被筛去,不是素数
- 13没被筛去,是素数
- 14被筛去,不是素数
- 15被筛去,是素数
至此,1~15内的所有素数都已得到,即2、3、5、7、11、13。
由上面的例子可以发现,当从小到大到达某数a时,如果a没有被前面步骤的数筛去,那么a一定是素数。这是因为,如果a不是素数,那么a一定有小于a的素因子,这样在之前的步骤中a一定会被筛掉,所以,如果当枚举到a时还没有被筛掉,那么a一定是素数。
分析:
至于“筛”这个动作的实现,可以使用一个bool型数组p来标记,如果a被筛掉,那么p[a]为true,否则,p[a]为false。在程序开始时可以初始化p数组为false。
给出上述题目的代码(埃氏筛法):
#include<stdio.h>
const int maxn = 101;
int prime[maxn], pNum = 0;
bool p[maxn] = {0};
//初始化p数组全部为false,如果a被筛掉,那么p[a]为true;否则,p[a]为false
void Find_Prime(){
for(int i = 2;i<maxn;i++){
if(p[i] == false){//p[i] == false说明i是素数,那么就把此数i存到prime数组中
prime[pNum++] = i;
for(int j = i+i;j<maxn;j+=i){
//筛去i的所有倍数
p[j] = true;
//i的所有倍数对应的p[i]设置为true,这样就不会存到prime数组中
}
}
}
}
int main(){
Find_Prime();
for(int i = 0;i<pNum;i++){
printf("%d ",prime[i]);
}
}
最后留道题目供练习:
令Pi表示第 i 个素数,现任意给两个正整数M<=N<=10^4,请输出PM到PN的所有素数。
输入格式:
输入在一行中给出M和N,其间以空格分隔。
输出格式:
输出从PM~PN的所有素数,每10个数字占一行,其间以空格分隔,但行末不能有空格。
给出代码(埃氏筛法和朴素都可以,这里给出的是埃):
//埃氏筛法
#include<stdio.h>
const int maxn = 1000001;
int prime[maxn], num = 0;
bool p[maxn] = {0};
//初始化p数组全部为false,如果a被筛掉,那么p[a]为true;否则,p[a]为false
void Find_Prime(int n){
for(int i = 2;i<maxn;i++){
if(p[i] == false){//p[i] == false说明i是素数,那么就把此数i存到prime数组中
prime[num++] = i;
if(num>=n)break;//只需要n个素数,超过时就退出
for(int j = i+i;j<maxn;j+=i){
//筛去i的所有倍数
p[j] = true;
//i的所有倍数对应的p[i]设置为true,这样就不会存到prime数组中
}
}
}
}
int main(){
int m,n,count = 0;
scanf("%d %d",&m,&n);
Find_Prime(n);
for(int i = m;i<=n;i++){
printf("%d",prime[i-1]);
count++;
if(count%10!=0&&i<n)
printf(" ");
else printf("\n");
}
return 0;
}
版权声明:本文为bogedaye原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。