对于素数的判定:
引理:
若一个正整数N是合数,则存在一个能整除N的正整数T,其中 2 <= T <= sqrt(N)
证明:
反证法;如果正整数N是合数,那么能整除N的正整数M的范围是[sqrt(N) + 1, N - 1],因为M能整除N,所以N/M也能整除N,而N/M的范围是[2, sqrt(N)];N/M的最小值即N/(N - 1),因为N/M是正整数,所以N/(N - 1)最小是2;
那么根据引理,对于一个正整数N,如果所有的T(2 <= T <= sqrt(N))都不能整除N,那么N就不是合数,即N是素数;
素数的筛选:
1、Eratosthenes[ˌerəˈtɒsθəniːz]筛法,(埃拉托色尼筛法)
复杂度O(nloglogn)
思想:
任意x的倍数2x, 3x都不是素数,这符合素数的定义(只能被自身和1整除,2是最小的素数);
所以我们就可以标记,扫描到x时,如果x未被标记,就把2x,3x,4x都标记,那么未被标记的就是素数;其实发现它可以被优化,比如对于6,它被2标记过,也被3标记过,所以标记重复了,其实对于一个正整数x,它的小于x^2的倍数已经被比x更小的数标记了,所以我们只需要从xx开始打标记就行
void primes(int n){
memset(v, 0, sizeof(v));
for(int i = 2; i <= n; i++){
if(!v[i]){
printf("%d\n", v[i]);
for(int j = i; j <= n / i; j++)
v[i * j] = 1;
}
}
}
即使被优化了,Eratosthenes筛法依然存在重复标记的情况,对于12,它会被2标记,也会被3标记,根本原因是我们没有确定出唯一的产生12的方式;
2、线性筛(欧拉筛)
线性筛法通过从大到小累积质因子的方式标记每个合数,即12 = 3 * 2 * 2;
方法:
pre:设数组v记录每个数的最小质因子
1、依次考虑2 ~ N 的每个数 i;
2、若v[i] = i;证明 i 素数(即它的最小质因子为它本身),就把它保存起来;
3、扫描不大于v[i]的每个质数p,令v[i * p] = p;也就是在 i 的基础上累积一个质因子p;因为p <= v[i], 所以 p 就是合数 i * p的最小质因子;
最大的改进在于当prime[j] > v[i]时,break;
void primes(int n){
memset(v,0, sizeof(v));
m = 0;
for(int i = 2; i <= n; i++){
if(v[i] == 0){
v[i] = i;
prime[++m] = i;
}
for(int j = 1; j <= m; i++){
//任何合数都是若干个素数的积,我们讲要从大到小累积质因子
//如果一个质因子比v[i]大,说明不符合
if(prime[j] > v[i] || prime[j] > n / i) break;
v[i * prime[j]] = prime[j];
}
}
for(int i = 0; i <= m; i++)
printf("%d\n", prime[i]);
}
That’s all.