LeetCode(204):计算质数

1. 题目描述

2. 题目理解

题目看着很简单,最先想到的就是做一个判断是不是质数的函数isPrim),然后对n以内的正整数做个遍历判断是质数的话,计数器count++,最傻瓜的代码如下:

int isPrime(int n);//该函数用来判断传入参数是不是质数,是:返回1;不是:返回0
int countPrimes(int n){
    int cnt=0;
    for(int i=1;i<n;i++){
        if(isPrime(i))cnt++;
    }
    return cnt;
}
//判断质数的函数,最傻瓜的算法
int isPrime(int n){
    if(n==1)return 0;
    if(n==2)return 1;
    int cmp=sqrt(n);
    if((cmp*cmp==n)||(n%2==0))return 0;//先把偶数排除了
    for(int i=3;i<=cmp;i+=2){//所以这里每次+2
        if(n%i==0)return 0;
    }
    return 1;
}

但是这个算法效率很低,时间复杂度是O(n^2),非常耗时,我们想想有没有其他方法可以加速这个过程。

3. 埃拉托色尼筛选法(the Sieve of Eratosthenes)

埃拉托色尼筛选法的基本原则是:根据容斥原理,我们要找n以内的所有质数,其实可以转换为,找n以内的所有非质数,然后将n以内正整数的集合中,减去所有非质数的集合,就得到了质数的集合

问题是:我们怎么找非质数?(可以参考LeetCode上的解答,我这里又做了一点点优化)

我们从3开始,i=3是质数,那么j=3+3*k(k=1,2,3...),肯定是非质数;其实j从i*i开始就行,为什么?因为i*i前面的,比如还是i=3的时候,3*2可以看做2*3,在i=2的时候已经遍历到了这个位置,因此没必要从j=i开始遍历,从j=i*i开始就可以,然后j=i*i+i*k(k=1,2,3,...)

除此之外,其实j没必要每次都加i,因为i是奇数,所以i*i就是奇数个ii*i+i,就是奇数个i再+i(是不是就是偶数个i),那么不用想,i*i+i就是偶数个i,肯定不是质数。因此j=i*i+i*k(k=2,4,6,8...),每次加两个i

最后一步:如果内部循环j=i*i,发现i*i已经不是质数了,那么我们就没必要再对j=i*i以及后面的j=i*i+i*k(k=2,4,6,8...)判断了。举个例子:当i=5的时候,j=i*i+i*k(k=2,4,6,8...)可以遍历到15,25,35,45,55.....225,235...等等,将这些数标记为非质数,那么,当i=15的时候,由于i*i=225已经被标记为非质数了,所以就不用对i=15做内层的循环遍历了(因为i=15的时候,内层循环会遍历到225,255,285,315...,而这些数,都在i=5这次循环中被遍历到了)。

最后的代码如下:

int countPrimes(int n){
    int* prim=(int*)calloc(n,sizeof(int));//初始化一个数组,数组包含n个元素,并且每个元素被初始化为0
    //以下思路,将不是质数的prim[i]设为1,怎么判断一个数不是质数:
    //这个for循环将所有的偶数的prim设为1,表示不是质数
    for(int j=4;j<n;j+=2)
        prim[j]=1;
    //这个循环从奇数开始,首先i平方不是质数,然后每次加2*i,都不是质数,prim设为1
    for(int i=3;i*i<n;i+=2){//i从3开始,设想,第一轮外层大for循环结束的时候,
        //如果prim[i*i]=1,说明i*i在之前已经遍历到了,那么久无需遍历i后面间隔为2*i的数了
        //最简单的例子:当i=5的时候,往后遍历,当j=i*i就是25的时候,25已经被遍历到了;因此当i=25的时候,就不用往后遍历了
        //(因为25已经被标记为非质数,25再加奇数个25,肯定也是非负数)
        if(prim[i*i])continue;
        //j从i*i开始,每次加2*i,
        //因为加奇数个i的话,由于j=初始是i*i(就是奇数个i),再加奇数个i就是偶数个i了,就是偶数,肯定之前已经遍历过了
        //所以这里加的是偶数个i,也就是每次加2*i
        for(int j=i*i;j<n;j+=(2*i))
            prim[j]=1;  
    }
    int cnt=0;
    //在prim中统计prim值为0,的元素的个数(值为0表示该元素为奇数)
    for(int i=2;i<n;i++)
        if(!prim[i])
            cnt++;
    
    free(prim);
    return cnt;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值