概述
求小于n的素数集合。
什么是素数?
素数,也称为质数。素数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
特殊的:偶数2是素数,除2以外的其他素数均是奇数。
三种方法
枚举
对于一个非素数n,一定存在两个整数x,y, 满足1 < x <= y < n ,使得 x*y = n,即 n % x = 0。
推导x的取值范围,即遍历的结束条件:
有
1
<
x
<
=
y
<
n
x
∗
y
=
n
则有
x
∗
x
<
=
x
∗
y
=
n
即
x
<
=
n
,也即
x
2
<
=
n
有 1 < x <=y<n \\ x*y=n \\ 则有 x * x <= x*y = n \\ 即 x <= \sqrt{n},也即x^2 <= n
有1<x<=y<nx∗y=n则有x∗x<=x∗y=n即x<=n,也即x2<=n
核心逻辑代码:
// 判断是否为素数
bool isPrime(int x)
{
for(int i = 2; i * i <= x; i++){ // 将 i*i <= x作为循环结束标志
if(x % i == 0) return false;
}
return true;
}
// 素数除2外,全部为奇数。
// 从奇数3开始,每次判断后加2,继续判断下一个奇数。
if (n >= 2) primes.push_back(2);
for(int i = 3; i <= n; i += 2){
if(isPrime(i)) {
primes.push_back(i);
}
}
埃氏筛
对于一个素数 x,其素数的整数倍 2x, 3x, 4x … 一定不是素数。
对于一个非素数y,一定可以由一个小于y的素数的乘积表示(两个乘数至少有一个可以是素数),所以y的整数倍也一定可以由比y小的素数的乘积表示(两个乘数至少有一个可以是素数)。
y
=
a
∗
b
,其中
a
是小于
y
的素数
2
y
=
a
∗
(
2
b
)
,
其中
a
是小于
y
的素数
m
∗
y
=
a
∗
(
m
∗
b
)
,
其中
a
是小于
y
的素数
y = a * b \ \ ,其中a是小于y的素数 \\ 2y = a * (2b) \ \ , 其中a是小于y的素数 \\ m*y = a * (m*b) \ \ , 其中a是小于y的素数 \\
y=a∗b ,其中a是小于y的素数2y=a∗(2b) ,其中a是小于y的素数m∗y=a∗(m∗b) ,其中a是小于y的素数
对于一个质数 x,如果按上文说的我们从 2x,3x,4x 开始标记其实是冗余的,应该直接从 x* x开始标记,因为 2x,3x,4x, … 这些数一定在 x 之前就被其他数的倍数标记过了,例如 2 的所有倍数,3 的所有倍数等。
核心代码:
std::vector<int> isPrime(n, 1); // 是否是素数
std::vector<int> primes; // 存放素数
for(int i = 2; i <= n; i++) {
if(isPrime[i]){
primes.push_back(i);
for(long long j = (long long)i * i ; j <= n; j += i){
isPrime[j] = 0;
}
}
}
线性筛
不作为重点,简单解释。
上面提到的埃氏筛,对于某些特殊的数,比如45,是素数3和5的倍数,并且3*3<45,5*5 < 45, 所以45会被3和5标记为非素数,这样重复标记是冗余的。所以线性筛就是实现每个非素数只会被标记一次。
与埃氏筛的不同点是,标记过程不再仅当x是素数时才进行,对于每个整数都进行。对于整数 x,我们不再标记其所有的倍数 x,2x, 3x⋅(x+1),而是只标记素数集合primes中的数与 x 相乘的数,即x*primes[0], x*primes[1] … 。结束标志是x%primes[j] = 0。
其实,简单的说,就是实现对于每一个非素数,都是被一个最大整数因子标记。即 x = 最大整数因子 * 最小素数因子。对于45,则会在取出15时标记, isPrime[15*3] = 0,而不会是isPrime[9*5] = 0。
如果理解了,那核心代码写起来就很简单了。
std::vector<int> isPrime(n, 1); // 是否是素数
std::vector<int> primes; // 存放素数
if(n > 2) primes.push_back(2);
for(int i = 3; i < n; i+=2){
if(isPrime[i]) primes.push_back(i);
for(int j : primes){
if((long long)i*j < n) isPrime[i*j] = 0; // long long 防溢出
else break;
if(i % j == 0) break;
}
}
测试代码
枚举法求素数
#include <iostream>
#include <vector>
bool isPrime(int x)
{
for(int i = 2; i * i <= x; i++){
if(x % i == 0) return false;
}
return true;
}
int main()
{
int n = 15;
std::vector<int> primes;
primes.push_back(2);
for(int i = 3; i <= n; i += 2){
if(isPrime(i)) primes.push_back(i);
}
if(n < 2) primes.pop_back();
for(int i = 0; i < primes.size(); i++)
std::cout << primes[i] << std::endl;
return 0;
}
埃氏筛求素数:
#include <iostream>
#include <vector>
int main()
{
int n = 15;
std::vector<int> isPrime(n, 1);
std::vector<int> primes;
for(int i = 2; i <= n; i++) {
if(isPrime[i]){
primes.push_back(i);
for(long long j = (long long)i * i ; j <= n; j += i){
isPrime[j] = 0;
}
}
}
for(int i = 0; i < primes.size(); i++)
std::cout << primes[i] << std::endl;
return 0;
}
线性筛求素数:
#include <iostream>
#include <vector>
int main()
{
int n = 15;
std::vector<int> isPrime(n, 1);
std::vector<int> primes;
if(n > 2) primes.push_back(2);
for(int i = 3; i < n; i+=2){
if(isPrime[i]) primes.push_back(i);
for(int j : primes){
if((long long)i*j < n) isPrime[i*j] = 0;
else break;
if(i % j == 0) break;
}
}
for(int i = 0; i < primes.size(); i++)
std::cout << primes[i] << std::endl;
return 0;
}