ACM数论专题3——素数(质数)

素数是什么

质数1 (prime number)又称素数,有无限个。
质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数。
所以最小的素数是2哦~依次是2,3,5,7,11,13,17,19…

蛮力算法求素数

蛮力算法的实现以及分析

很容易能够想到就是枚举一下所有大于一但是小于它本身的所有数字,看是否有能够被整除的数字。

bool prime1(int n){//判断x是不是质数。如果是,返回true,否则返回false 
    if(n < 2) return false; 
    for(int i = 2; i < n; i++){
        if(n % i == 0) return false;
    }
    return true;
}

时间复杂度

很容易发现是O(n)

蛮力算法的改进

如果一个数不是质数,那么他一定有除了1和他本身之外的两个因子(除了平方数),而且这两个因子一定有一个在[2,n][2,\sqrt{n}]之间(因为如果两个因子都大于n\sqrt{n}的话乘起来一定会大于n)。
所以我们只要在[2,n][2,\sqrt{n}]之间寻找一个因子就行了,如果找不到的话就能说明没有啦~

bool prime2(int n){//判断x是不是质数。如果是,返回true,否则返回false 
    if(n < 2) return false; 
    for(int i = 2; i <= sqrt(n); i++){
        if(n % i == 0) return false;
    }
    return true;
}

如果觉得sqrt有精度问题的话可以用这种方式

bool prime3(int n){//判断x是不是质数。如果是,返回true,否则返回false 
    if(n < 2) return false; 
    for(int i = 2; i*i <= n; i++){
        if(n % i == 0) return false;
    }
    return true;
}

时间复杂度

很容易发现是O(n)O(\sqrt{n})

那么如果我们要找到m以内的全部素数,就需要nlognnlogn的时间复杂度。

埃筛

埃拉托斯特尼筛法2,或者叫埃氏筛法,也叫素数筛法。
这个方法利用到的一个定理就是:如果找到一个质数,那么这个质数的倍数都不是质数。
举个例子,我们从一开始的2开始,剔除掉2的倍数,即4,6,8,10,…
接下来是3,3没被剔除,也就是说3不能整除所有小于3的素数,3就是素数。接下来剔除6,9,12,…
接下来是4,4已经是合数了。
5没被剔除,所以剔除5的倍数10,15,…
代码实现如下:

bool prime3(int n){//判断x是不是质数。如果是,返回true,否则返回false 
    if(n < 2) return false; 
    for(int i = 2; i*i <= n; i++){
        if(n % i == 0) return false;
    }
    return true;
}

时间复杂度

这样做很直观,我们做的无用功少了。但是时间复杂度具体的计算就是个问题。显然不是线性的,比如6会被2,3,5都标记一次,具体可以看下图(图源*)
ACM数论专题3——素数(质数)
那么也就是说对于每个素数p,都要划掉n/pn/p个数(包括重复划掉的)那么我们一共做了多少次运算呢?
很容易想到是np=2isprime(p)andp&lt;n1/pn* \sum_{p = 2}^{isprime(p)andp &lt; n}1/p(小于n的素数的倒数之和)
小于n的素数的倒数之和是用 (11p)1(1-\frac{1}{p})^{-1}的连乘积看出来的。这个连乘积的 log “=” 小于 n 的素数的倒数和,而它本身大致是 1+1/2+…+1/n,log(n)log(n). 所以是 loglognloglog n.

线筛

线筛原理分析

线筛顾名思义,时间复杂度是线性的,也就是O(n)O(n)。既然是筛法,就要在埃筛的基础上进行。而埃筛接近线性却没达到线性的原因,就是一个合数会被所有小于它的素数筛一次,而不是只被某一个素数筛一次。
如果我们改进这个算法,让一个数字只被它最小的素因子筛掉就结束,那么每个数字都会被筛一次。虽然可能有一些细节是占用时间的,但是总的来说时间是接近线性的。
既然我们要知道一个数最小的素因子是谁,那么我们就要打表。但是我们可以在筛的过程中打表,因为一个合数的素因子一定是小于它的,而小于它的素数一定是先被处理出来之后的。所以我们不需要另外打表。

线筛实现

const int N = 100000 + 10;
bool prime[N];//prime[i]表示i是不是素数 
int p[N],num = 0;//p[N]是素数表,num是素数的个数。这里P[n]其实开大了 我忘记小于n的素数有多少了呜呜呜 
void prime4(){
    for(int i = 2; i < N; i ++) prime[i] = true;//初始化为质数 
    for(int i = 2; i < N; i++){
        if(prime[i]) p[tot ++] = i;//把质数存起来 
        for(int j = 0; j < tot && i * p[j] < N; j++){
            prime[i * p[j]] = false;
            if(i % p[j] == 0) break;//保证每个合数被它最小的质因数筛去 
        }
    }    
}

有问题随时提出~~~~


  1. 百度百科-质数 ↩︎

  2. 百度百科-埃筛 ↩︎