1. 程式人生 > >篩法快速求素數——leetcode計數質數

篩法快速求素數——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來儲存已經確定的素數,所謂空間換時間,這樣的優化在剛才的基礎上又減少了不少的時間複雜度。