基础算法--质数筛法

3 阅读3分钟

质数筛法


什么是质数筛

质数筛是一种筛选质数的方法,比如我想要从1~10000中找出所有的质数,一一枚举肯定是不现实的,这个时候就需要一种快捷可行的方法来帮助我们。


几种质数筛

朴素筛法--时间复杂度O(n^(3/2))

质数就是不能被1和它本身整除的数,如果要判断x是否是质数,我们只需要遍历2~x-1,看其中有没有可以整除x的数。如果有,那么x就不是质数。 代码演示如下:

bool isPrime(int x){
	if(n < 2){
		return 0;
	}
	int tmp = sqrt(n);//sqrt()用于计算一个非负实数的平方根
	for(int i = 2; i <= tmp; ++i){
		if(x % i == 0){
			return 0;
		}
	}
	return 1;
}

埃氏筛法--时间复杂度O(nlog(logn))

埃氏筛法由埃拉托色尼提出,其基本原理是:一个合数总是可以分解成若干个质数的乘积,如果把质数的倍数都去掉,那么剩下的就是质数了。 代码演示如下:

#include <iostream>
#include <cmath>
using namespace std;
int p[10005], sum = 0;
bool isPrime[1000005];//isPrime[i] = 0 i是质数; isPrime[i] = 1 i是合数
int main(){
    int n;
    scanf("%d", &n);
    isPrime[1] = 1;
    for(int i = 2; i <= n; ++i){
        if(isPrime[i] == 0){
            sum++;
            p[sum] = i;
            for(int j = 2 * i; j <= n; j += i){//j就是i的倍数
                isPrime[j] = 1;
            }
        }
    }

    for(int i = 1; i <= sum; ++i){
        cout << p[i] << " ";
    }
    cout << endl;

    return 0;
}

欧拉筛法--时间复杂度O(n)

在埃氏筛法中,对于同一个数可能会被筛选多次,比如6 = 2 * 3,那么6就会被2和3各筛选一次,造成了时间上的冗余。 欧拉筛法针对这个问题进行了改良:规定每个合数只会被它最小的质因数筛去,后面的质因数直接跳过,那么时间复杂度就减小到了O(n),因为对每个数字都只处理了一次,因此也被称作线性筛。 代码演示如下:

#include <iostream>
#include <cmath>
using namespace std;
int p[10005], sum = 0;
bool isPrime[1000005];//isPrime[i] = 0 i是质数; isPrime[i] = 1 i是合数
int main(){
    int n;
    scanf("%d", &n);
    isPrime[1] = 1;
    for(int i = 2; i <= n; ++i){//1.枚举2~n  2.枚举每个质数的倍数
        if(isPrime[i] == 0){
            sum++;
            p[sum] = i;
        }
        //把i当作质数的倍数来看,枚举所有质数的i倍
        for(int j = 1; j <= sum; ++j){
            int x = i * p[j];//枚举质数表中所有元素的i倍
            if(x > n){
                break;
            }
            isPrime[x] = 1;
            if(i % p[j] == 0){
                break;//保证合数只被其最小的质因数标记筛去
            }
        }
    }

    for(int i = 1; i <= sum; ++i){
        cout << p[i] << " ";
    }
    cout << endl;

    return 0;
}

这里最关键的一步是

if(i % p[j] == 0)break;

因为如果没有这一步,那么我们在尝试筛去i * p[j + 1]时,会发现i可以分解为p[j] * i',那么最小的质因数就是p[j]而不是p[j + 1]了

对于欧拉筛,我们介绍一道模板题,巩固一下:

质数筛法.png

这道题的思路其实就是欧拉筛的标准过程,可以作为模板使用,下面是代码实现:

#include <iostream>
#include <vector>
using namespace std;
bool isPrime[100000005];//标记是否是质数
int p[6000005], sum = 0;//存储质数
int n, q, k;

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> q;
    isPrime[1] = 1;
    for(int i = 2; i <= n; ++i){
        if(isPrime[i] == 0){
            sum++;
            p[sum] = i;
        }
        for(int j = 1; j <= sum; ++j){
            int tmp = p[j] * i;
            if(tmp > n){
                break;
            }
            isPrime[tmp] = 1;
            if(i % p[j] == 0){
                break;
            }
        }
    }

    int index;
    vector<int> res;
    for(int i = 1; i <= q; ++i){
        cin >> index;
        res.push_back(p[index]);
    }
    for(int i = 0; i < q; ++i){
        cout << res[i] << endl;
    }


}