筛法快速求素数——leetcode计数质数

在算法竞赛中经常会遇到求质数的问题,这种题目一般都是要求出一定范围内[0,n]所有的质数或者质数的个数。最直接的思路就是根据质数的定义来判定一个数是不是质数(即一个数不能被除1和它本身外的任何数整除),如果我们需要对所有的数都这样进行判断,那么当n非常非常大的时候,这种算法的时间开销就会非常大,大概为O(n^2)。这个时候就可以使用筛法来快速地求出素数,筛法求素数的基本思路就是:除了0、1之外,所有的合数都是可以表示为比它小的一个素数的倍数,如果我们已知一个素数,那么我们可以把问题规定的范围内的所有该素数的倍数全都筛除,如果该范围内的所有的合数都被筛出,剩下的自然就都是素数了,时间复杂度大概为O(n)。

leetcode上面的一道题目:计数质数,一道典型求质数的题目,现在给出筛法求素数的解法:

class Solution {
public:
    int countPrimes(int n) {
        if(n==0||n==1)return 0;
        int count=0;
        bool isprime[n];//用来标记是否是素数的数组
        memset(isprime,1,sizeof(isprime));//首先假设所有的数都是素数
        isprime[0]=isprime[1]=0;//0、1不是素数
        for(int i=2;i<n;i++){
            if(isprime[i]==1){//如果是素数
                count++;//计数加一
                for(int j=2*i;j<n;j+=i)//将该质数的倍数标记为合数
                    isprime[j]=0;
            }
        }
        return count;
    }
};

因为我们的算法相当于是从2到n遍历一遍,我们将整个isprime数组都初始化为1,也就是假设有的数开始都是质数,每当访问isprime[i]的时候,如果它是合数,那么在访问它之前它一定已经被比它小的因子素数给筛掉了,所以在这个for(int i=2;i<n;i++)的循环中,每次访问isprime数组的时候都能确定i是不是素数了。

仔细思考一下上面的这种算法:线性筛法虽然大大缩短了求素数的时间,但是实际上还是做了许多重复运算,比如2*3=6,在素数2的时候筛选了一遍,在素数为3时又筛选了一遍。那么我们如何做出优化呢?如果只筛选小于等于素数i的素数与i的乘积,既不会造成重复筛选,又不会遗漏。时间复杂度几乎是线性的。

下面给出优划过后的筛法代码:

class Solution {
public:
    int countPrimes(int n) {
        if(n==0||n==1)return 0;
        int count=0;
        bool isprime[n];//用来标记是否是素数的数组
        vector<int> su;//用来保存已经确定的素数
        memset(isprime,1,sizeof(isprime));//首先假设所有的数都是质数
        isprime[0]=isprime[1]=0;//0、1不是素数
        for(int i=2;i<n;i++){
            if(isprime[i]==1){//如果是素数
                count++;//计数加一
                su.push_back(i);
            }
            for(int j=0;j<su.size()&&i*su[j]<n;j++)
                isprime[su[j]*i]=0;//小于i的所有的素数与i相乘得到的都是合数
        }
        return count;
    }
};

可以看出优化之后的算法需要额外的空间存放vector<int> su来保存已经确定的素数,所谓空间换时间,这样的优化在刚才的基础上又减少了不少的时间复杂度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章