质数筛选--快速筛选出小于等于N的质数

Eratosthenes筛法(O(nlog^logn) )

此方法非常的简单:
1. 维护一个数组vector prime(n + 1, true)既让数组中的所有数都为真,表明它是质数。
2. 对于每一个质数,删除它的倍数,因为质数的倍数一定是合数。让后将数组中对应的数改为false,这样就可以避免遍历他。

代码如下:

int findPrime(int n) {
    vector<bool> prime(n + 1, true);
    int res=0;
    for (int i = 2; i <= n; i++) {
        if (prime[i]) {        //判断其是否是素数
            for (int j = 2; j*i <= n; j++)  //删除所有该素数的倍数
                prime[j*i] = false;
            res++;
        }
    }
    return res;
}

上述的筛选速度已经是很快了,但是他有一个问题。比如合数10,在枚举2的时候我们判定了一次,在枚举5的时候我们又判定了一次。因此使得其时间复杂度比O(n)要高

Eular质数筛法

与Eratosthenes筛法不同的是,对于外层枚举i,无论i是质数,还是是合数,我们都会用i的倍数去筛。但在枚举的时候,我们只枚举i的质数倍。比如2i,3i,5i,…,而不去枚举4i,6i…,原因我们后面会讲到。

此外,在从小到大依次枚举质数p来计算i的倍数时,我们还需要检查i是否能够整除p。若i能够整除p,则停止枚举。

利用该算法,可以保证每个合数只会被枚举到一次。我们可以证明如下命题:

假设一个合数k=M*p1,p1为其最小的质因子。则k只会在i=M,primeList[j]=p1时被筛掉一次。

首先会在i=M,primeList[j]=p1时被筛掉是显然的。因为p1是k的最小质因子,所以i=M的所有质因子也≥p1。于是j循环在枚举到primeList[j]=p1前不会break,从而一定会在i=M,primeList[j]=p1时被筛掉

其次不会在其他时候被筛掉。否则假设k在i=N, primeList[j]=p1时被筛掉了,此时有k=N*p2。由于p1是k最小的质因子,所以p2 > p1,M > N且p|N。则i=N,j枚举到primeList[j]=p1时(没到primeList[j]=p2)就break了。所以不会有其他时候筛掉k。

同时,不枚举合数倍数的原因也在此:对于一个合数k=M*2*3。只有在枚举到i=M*3时,才会计算到k。若我们枚举合数倍数,则可能会在i=M时,通过M*6计算到k,这样也就造成了多次重复计算了。

综上,Eular筛法可以保证每个合数只会被枚举到一次,时间复杂度为O(n)。当N越大时,其相对于Eratosthenes筛法的优势也就越明显。

代码如下:

int findPrime1(int n) {
    vector<bool> prime(n + 1, true);
    vector<int> primeList(n+1); int primeCount = 0;
    for (int i = 2; i <= n; i++) {
        if (prime[i]) {
            primeCount++;
            primeList[primeCount] = i;
        }
        for (int j = 1; j <= primeCount; j++) {
            if (i*primeList[j] > n) break;
            prime[i*primeList[j]] = false;
            if (i%primeList[j] == 0) break;
        }
    }
    return primeCount;
}

对于两者的时间测试来看,其差别是非常小的:

int main() {
    clock_t start = clock();
    cout << findPrime1(5000000) << endl; 
    clock_t finish = clock();
    cout<<(double)(finish - start) / CLOCKS_PER_SEC<//11.854s
    clock_t start2 = clock();
    cout << findPrime1(5000000) << endl;
    clock_t finish2 = clock();
    cout << (double)(finish2-start2) / CLOCKS_PER_SEC << endl;     //11.81s
}

你可能感兴趣的:(Alogrithm)