素数判断题解报告

发布时间 2023-05-16 20:59:43作者: FinderHT

 

第一种方法:枚举

从素数的定义中,我们可以知道,一个整数m要被判断为素数,需要判断n是否能被2、3…n-1中的一个整除,只有2,3,…,n-1都不能整除n,n才能判定为素数,而只要有一个能整除n的数出现,n就可以判定为非素数。

#include<iostream>
#include<cmath>
using namespace std;
bool isprime(int x){//定义判断函数isprime,返回值类型为bool 
    if(x<=1)return false;//如果这个数≤1,那么它肯定不是素数,返回false 
    if(x==2)return true;//如果这个数=2,也需要特判,2是素数,返回true  
    for(int i=2;i<=x-1;i++){//枚举2到x-1 
        if(x%i==0)return false;
        /*如果这其中x能被任意一个i整除,说明x有除了1和它本身之外的其他因数,
        所以它不是素数,返回false*/
    }
    return true;//如果这个数不能被除了1和它本身之外的其他数整除,那么他就是素数,返回true 
} 
int main(){
    int n;//定义一个n,表示要输出的是1-n之间的素数 
    cin>>n;//输入表示范围的n 
    for(int i=1;i<=n;i++) {//枚举1-n之间的每一个数 
        if(isprime(i))cout<<i<<' ';//调用isprime函数,判断i是否为素数,如果返回true则输出 
    }
    return 0;//结束程序 
}

 

 这样的判定方法没什么问题,时间复杂度为 O(n²),但可以优化,因为如果k是n的一个约数,那么n/k也是n的一个约数,k 和 n/k 必然满足一个小于等于 sqrt(n)  另一个大于等于 sqrt(n),其中sqrt(n)为根号 n 。从2到sqrt(n)减少枚举次数所以只要判断n是否能被 2,3…sqrt(n) 中的一个数整除,即可判定n是否为素数,时间复杂度为O(nlogn),第一个程序修改一点点即可得到新的代码。

#include<iostream>
#include<cmath>
using namespace std;
bool isprime(int x){ 
    if(x<=1)return false; 
    if(x==2)return true; 
    for(int i=2;i*i<=x;i++){//等于int y=sqrt(x);for(int i=1;i<=y;i++)这种形式只是减少了函数的调用,实际的复杂度还是相同的
        if(x%i==0)return false;    
    }
    return true;
} 
int main(){
    int n; 
    cin>>n; 
    for(int i=1;i<=n;i++) { 
        if(isprime(i))cout<<i<<' '; 
    }
    return 0; 
}

第二种方法:埃拉托斯特尼筛法(埃氏筛)

埃氏筛也叫素数筛法,其关键在一个“筛”字。算法从小到大枚举所有数,对于每一个素数,筛去它的所有倍数,剩下的就都是素数了。埃氏筛的时间复杂度只有O(n*log(log(n)))

下面是一个例子:求1~15中的所有素数。

根据所学知识得知1不是素数直接筛去

2是素数,因此筛去所有2的倍数,即4、6、8、10、12、14。

3没有被前面的步骤筛去,因此3是素数,筛去所有3的倍数,即6,9,12,15

4已经被筛去,因此4不是素数。

5没有被前面的步骤筛去,因此5是素数,筛去所有5的倍数,即10,15

6已经被筛去,因此6不是素数。

7没有被前面的步骤筛去,因此7是素数,筛去所有7的倍数 ,即14

9已经被筛去,因此9不是素数.

10已经被筛去,因此10不是素数.

11没有被前面的步骤筛去,因此11素数,但15以内没有11的倍数。

12已经被筛去,因此12不是素数.

13没有被前面的步骤筛去,因此13素数,但15以内没有13的倍数。

14已经被筛去,因此14不是素数.

15已经被筛去,因此15不是素数.

至此,1~15以内的所有素数已全部得到,即2、3、5、7、11、13。

由这个例子很形象的展示了埃氏筛的过程,那么接下来就直接上代码:

#include<iostream>
#include<cmath>
using namespace std;
bool isprime[1005];//标记是否为素数,如果i为素数,则isprime[i]为0;否则为1
int n;//定义一个范围,表示从1-n 
void Aiprime(int x){
    isprime[1]=1;//1不是素数
    for(int i=2;i<=x;i++){
        if(isprime[i]==0){//如果i是素数,开始筛除i的倍数 
            for(int j=i*2;j<=x;j+=i)
            //从i的2倍(防止i被筛除)开始筛除合数,直到i比x大时停止,每次增加一倍 
                 isprime[j]=1; //筛去所有i的倍数
        }
    }
} 
int main(){
    cin>>n;
    Aiprime(n);//调用埃氏筛选出从1-n的所有素数 
    for(int i=1;i<=n;i++)//从1枚举到n 
        if(isprime[i]==0)cout<<i<<' ';//如果isprime[i]为0,说明i为素数,输出 
    return 0;
} 

第三种方法:线性筛(欧拉筛)

在上面的埃氏筛中,2从4开始标记合数,3从9开始标记合数。我们会发现虽然免去了重复标记6,但最后这两个数还是免不了重复标记一个数:12 。究其根本原因是2是12的质因子但同样3也是12的质因子,那么有没有一种方法让每个数都被他的最小质因子标记。这就是接下来的线性筛。

思路就是将所有素数放入一个数组里。i从2遍历,将i与每个小于i的素数相乘筛去他们的乘积,直到i等于那个素数或那个素数是i的质因子的时候停下,这样子必然满足合数是由最小质因子筛去的。

假设要筛出n以内的素数。我们先把所有数标记为素数。枚举i从2到n,所以因为i是从小到大的,如果i没有被前面的数(比它小的数)标记为合数,那i就是素数,加入素数列表。现在用i来筛后面的数,枚举已经筛出来的素数prime[j](j=1~cnt),标记i * prime[j]为合数,当i是prime[j]的倍数时退出循环,i++。线性筛的时间复杂度为O(n),比较快速。

#include<iostream>
#include<cmath>
using namespace std;
bool isprime[50005];//标记是否为素数,如果i为素数,则isprime[i]为0;否则为1
int prime[50005];//用来存素数
int cnt;//素数的个数 
int n;//定义一个范围,表示从1-n 
void Qprime(int x){
    isprime[1]=1;//1不是素数 
    for(int i=2;i<=x;i++){
        if(isprime[i]==0)prime[++cnt]=i;
        //如果i是素数,素数个数+1,把i存在prime数组的相应位置中
        for(int j=1;j<=cnt&&i*prime[j]<=x;j++){
            isprime[i*prime[j]]=1;//将i的素数倍标记为合数 
            if(i%prime[j]==0)break;
            //如果i能被素数prime[j]整除,那i*prime[j+1]这个合数一定也能被prime[j]整除,终止循环 
            //保证了一个合数只能被筛除一次,可以保证线性的时间复杂度
        } 
    }
} 
int main(){
    cin>>n;
    Qprime(n);//调用线性筛选出从1-n的所有素数 
    for(int i=1;i<=cnt;i++)cout<<prime[i]<<' ';
    //从1枚举到素数的个数cnt,输出prime数组里的所有素数 
    return 0;
} 

欧拉筛法正确性的证明:假设我们要筛掉合数a,且a的最小质因数为p,令 a=p*b,b先被外层循环碰到。现在b要筛掉它的倍数。因为p是 a的最小质因数,所以b的最小质因数必不小于p,这样就保证 b筛掉a前不会在if(i%prime[j]==0)break;处跳出循环。即使 b的最小质因数等于p,也会先筛掉a后再退出循环。这样就保证了每个合数都会被筛掉。

 

//我的素数判断解题报告到此结束,感谢大家的观看和学习!