筛素数(欧拉筛法)

欧拉筛素数算法是一个很巧妙的算法,他的核心思想是保证每个合数只会被其最小质因数筛掉,所以能达到O(n)的时间复杂度。

先看代码,后看原理

C++实现

const int N = 1000010;
int n,cnt;
int primes[N];//质数数组
bool st[N];
int main(){
    cin >> n;
    for(int i = 2;i <= n;i ++) {//1不是质数,循环从2到n所有的数,同时也有充当遍历primes中的质数所有要乘上的倍数的作用
        if(!st[i]) primes[cnt ++] = i;//!st[i] 表示是质数
        for(int j = 0;j < cnt && primes[j] <= n / i;j ++) {
            st[primes[j] * i] = true;//将已经得到的各个质数的i倍都筛掉
            if(i % primes[j] == 0) break;
        }
    }    
    printf("%d",cnt);
    return 0;
}

最后得到的prime数组里面就存了1---n中所有的质数

原理

欧拉筛法之所以能达到O(n)的时间复杂度,是因为他保证了所有合数都能被他的最小质数给筛掉。关键在于if(i % primes[j] == 0) break;

假设没有这个判断,外层循环从2到n的时候的每一次循环,都枚举primes中的所有元素,然后把所有元素的i倍都给筛掉,因为外层循环是从2到n,那么就能把任何primes[i]的2...n的倍数都给筛掉,这样剩下的元素就是质数了。但是效率会比较低,因为一个数会被他的各个质数筛好几次,比如20 分解质因数为20 =2 * 2 * 5,他会被他的两个质数2和5筛掉,这么做就是重复了。效率变低。

那么为什么加了这个判断就可做到所有合数都被他的最小质数筛掉呢?

可以分两种情况:

1.当i,j满足i % primes[j] == 0时,这个时候,primes[j]就是i的一个质数,而且由于(1)我们是从primes[0]开始枚举的,(2)primes数组里的元素是递增的(因为第一层枚举2到n的时候将i放进primes中)。所以primes[j]肯定是i的最小质因数 ===> 那么自然primes[j]是合数x = i * primes[k] 的最小质因数,k= j,j + 1,j + 2...cnt。      

又因为i = primes[j] * p,所以 x = i * primes[k]  = primes[j] * p * primes[k] ,所以x = i * primes[k] 会在i 循环到 p * primes[k],即i = p * primes[k]的时候被primes[j]筛掉。

所以我们得出结论,当i ,j满足i % primes[j] == 0的时候,任取正整数 j < k <= cnt,合数x = primes[k] * i都会被他的最小质因子primes[j]在未来i 循环到 p * primes[k]的时候被筛掉。所以当前不需要再继续枚举primes[k] 来筛掉primes[k] * i, 直接break。反正primes[k] * i会在未来外层循环循环到某个数的时候被他的最小质因子primes[j]筛掉,不必继续在当前循环中枚举primes[k]进行重复筛。

2.当i ,j 不满足i % primes[j] == 0时,说明primes[j] 不是i的质因数,那么就不能保证后面的枚举的primes[k] * i能在未来被筛掉,所以要继续枚举。而且还有一点,x = i * primes[j] 的最小质因数是primes[j],那么x 也是被他的最小质因数给筛掉的。

所以可以得出所有合数都能被他的最小质因数筛掉。因为有n个数,每个数的最小质因数只有一个,所以每个数被枚举一次,也就是被筛一次===> 时间复杂度为O(n).

 补充问题:

1.当i,j符合i % primes[j] == 0的时候,设有一合数x = primes[k] * i   ( k = j + 1,.....,cnt) ,由上可得primes[j]一定是x的最小质因数,会在将来i = x / primes[j]的时候被筛掉,但是在i = x /primes的时候第二层循环中primes[j]是否一定会被枚举到??会不会第二层循环没有枚举到primes[j]就break了,这样会导致x不会被筛掉??

不会,可以用反证法证明,假设primes[j]不会被枚举到就break了,说明存在一个小于primes[j]的质数是i = x /primes[j]的质因数,由于primes[j]是质数,所以他只能是x的一个质因数,而且这个质因数比primes[j]小,所以与题设primes[j]就是x的最小质因数矛盾,所以假设不成立。

 2.为什么primes[j]循环到n / i??

因为性质:任何数至多存在一个大于根号n的约数(证明:如果存在两个大于根号n的约数,乘积肯定大于n,所以不可能),所以外层循环到i的时候,如果primes[j] >= n/i,那么primes[j] * i肯定大于n,没有任何意义所以要保证primes[j] * i的值不大于n,只要循环到primes[j] <= n/ i。那为什么不primes[j] * i <= n 这么写呢?因为这样可能会溢出,所以写成primes[j] <= n / i 是最好的

posted @ 2020-08-27 11:59  驿站Eventually  阅读(620)  评论(0编辑  收藏  举报