素数筛选法(埃氏筛 欧拉筛)

质数筛选法

文章目录


前言

当需要大范围内的素数时,例如 1 e 6 1e6 1e6以内时,我们就需要优秀的筛选法,如果是枚举(1 ~ n)数做朴素的判断x是否能被 2 ~ x \sqrt{x} x 中的数整除,x 从 2 ~ n, 复杂度就太高了,估算约
O ( 2 + 3 + . . . + n − 1 + n ) O(\sqrt{2} + \sqrt{3} + ... + \sqrt{n-1} + \sqrt{n}) O(2 ​+3 ​+...+n−1 ​+n ​)
势必超时,但早在几百多年前就有两位伟大的数学家发明出了埃氏筛 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)和欧拉筛 O ( n ) O(n) O(n)
两者在 1 e 6 1e6 1e6范围内效果相当,但到了 1 e 7 1e7 1e7上埃氏筛就显得乏力了。
预备知识 : 算数基本定理
简单的说一个大于1的数一定可以由若干个质数相乘得到例如: 8 = 2 ∗ 2 ∗ 2 8 = 2 * 2 * 2 8=2∗2∗2

一、埃氏筛 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

//利用2 3 5 7 11 13...这些质数筛掉(2x, 3x, 5x, 7x, 11x, 13x, ...)剩下的便是合数
const int N = 1e6 + 10;
bool prime[N];
void prepare(int n)
{
	prime[0] = prime[1] = true;	//0和1都不是质数
    for(int i = 2; i <= n; ++i)
    {
        //prime[i] = flase 代表i是质数
        if(!prime[i])   
        {
        	//将i的倍数都标记为合数,从两倍开始
            for(int j = i << 1; j <= n; j += i)
            {
                //prime[i] = true 代表j是合数
                prime[j] = true;        
            }
        }
    }
}

总结:埃氏筛存在的缺点是一个合数可能会被多次筛选例如6会被2和3同时筛选(数据范围不超过 1 e 6 1e6 1e6影响不大),所以下面引出欧拉筛(线性筛选)

二、欧拉筛 O ( n ) O(n) O(n)

const int N = 1e6 + 10;
int cnt;
int primes[N];   //从小到大存下每个质数 
bool st[N];      //false为质数,true为合数
void get_primes(int n)
{
    for(int i = 2; i <= n; ++i)
    {
        //将质数加入质数表
        if(!st[i]) primes[cnt++] = i;
        
        //利用最小质因子来给合数打标记,必须保证每个合数都是利用最小质因子筛选一次
        for(int j = 0; primes[j] <= n / i; ++j)
        {
            st[i * primes[j]] = true;   //primes[j]为构成i * primes[j] 的最小质因子
            if(i % primes[j] == 0)
                break;
            //这段很关键,为什么要break掉,因为 i % primes[j] == 0
            //说明 primes[j]是 i 的最小质因子, 令 i = k * primes[j]
            //同时也是(i * primes[j+1]) 的最小质因子
            //如果继续执行下层循环primes[i * primes[j+1]] = true; 就说明筛选必定重复
            //因为 i * primes[j+1] = (k * primes[j]) * primes[j+1];
            //i * primes[j] 在i = (k * primes[j+1]) 时会被筛选
        }
    }
}

总结

我对素数筛选法的理解大概是这样,只是欧拉筛确实还是有点难以理解,欧拉筛这里的内层循环判断结束也很有趣,可以分三种情况。

  • primes[j] * i > n 说明范围内的素数已经被筛过,无需筛
  • i % primes[j] == 0 break 分两种
    -1. i 是合数, primes[j] 一定是小于i的最小质因数,
    -2. i 是质数, primes[j] 为当前质数表里的最后一个质数且等于i,这里保证了 j 不会大于 cnt, 不会发生质数表越界
上一篇:Mybatis1


下一篇:《C++11Primer》阅读随记 -- 十三、拷贝控制