试除法
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。如果一个数 z z z 为非素数,则有 x × y = z x\times y = z x×y=z,不妨设 x ≤ y x\le y x≤y,则有 x ∈ [ 2 , z ] x \in [2, \sqrt{z}] x∈[2,z]。因此要验证一个数是否为素数,只需看 [ 2 , z ] [2, \sqrt{z}] [2,z] 范围内有没有其因数即可。
#include <bits/stdc++.h>
using namespace std;
bool is_prime(int number){
if(number<2)return false;
for(int i=2;i<=(int)sqrt(number);i++){
if(number%i==0)return false;
}
return true;
}
int main(){
int n;
cin >> n;
cout << is_prime(n) << endl;
return 0;
}
筛法
在 [ 2 , n ] [2,n] [2,n] 的正整数序列中筛除其中的非素数,这是筛法要解决的问题。
埃氏筛法(Eratosthenes筛法)
不是任一素数的倍数的数一定是素数,这是埃氏筛法基于的思想。假设有 { x ∣ 2 ≤ x ≤ n , x ∈ N } \{x|2\le x\le n,x\in N\} {x∣2≤x≤n,x∈N} 的正整数序列 A A A,埃氏筛法的算法如下:
- 取出当前序列 A i A_i Ai 中的最小数 a i a_i ai( a i a_i ai 必为素数)且不再放回序列。
- 除去当前序列 A i A_i Ai 中所有 a i a_i ai 的倍数,得到序列 A i + 1 A_{i+1} Ai+1。
- 若 A i + 1 A_{i+1} Ai+1 为空则算法结束,否则将 A i + 1 A_{i+1} Ai+1 当作当前序列返回第一步。
接下来使用第二数学归纳法简要证明 a i a_i ai 必为素数:
- 当 i = 1 i=1 i=1 ,有 a 1 = 2 a_1=2 a1=2 为素数。
- 设 i = k i=k i=k 时 { a 1 , a 2 , ⋯ , a k − 1 } \{a_1,a_2, \cdots, a_{k-1}\} {a1,a2,⋯,ak−1} 皆为素数。
- 则对于序列 A k A_{k} Ak 中的最小数 a k a_{k} ak,有 { a 1 , a 2 , ⋯ , a k − 1 } \{a_1,a_2, \cdots, a_{k-1}\} {a1,a2,⋯,ak−1} 是小于 a k + 1 a_{k+1} ak+1 的全部的素数。因为 A k A_{k} Ak中 { a 1 , a 2 , ⋯ , a k − 1 } \{a_1,a_2, \cdots, a_{k-1}\} {a1,a2,⋯,ak−1} 的倍数皆已被除去, 所以 a k + 1 a_{k+1} ak+1 不是 { a 1 , a 2 , ⋯ , a k } \{a_1,a_2, \cdots, a_k\} {a1,a2,⋯,ak} 的倍数。一个数如果不是小于它的所有素数的倍数,这个数一定是素数。故可知 a k a_{k} ak 是素数。
- 故根据第二数学归纳法可知 a i a_i ai 必为素数。
代码如下:
#include <iostream>
using namespace std;
void filter_prime(bool *const is_prime,size_t range){
is_prime[0] = is_prime[1] = false;
for(size_t i=2;i<=range;i++){///假定所有数都为素数
is_prime[i] = true;
}
for(size_t i=2;i<=range;i++){///查找范围内的素数
if(is_prime[i]){
for(size_t j=2*i;j<=range;j+=i){///将所有素数的倍数全部标记为假
is_prime[j] = false;
}
}
}
}
int main(){
size_t n;
cin >> n;
bool *is_prime = new bool[n+10];
filter_prime(is_prime,n);
size_t c = 0;
for(size_t i=0;i<=n;i++){
c += is_prime[i];
}
cout << c << endl;
delete is_prime;
return 0;
}
改进的埃氏筛法
通过埃氏算法我们可以推出它的一个致命缺点,那就是对某些数会进行重复的筛除操作,例如:6 会被素数 2 筛除一次又会被 3 再筛除一次。对埃氏算法的改进就是尽可能的减少重复的筛除操作,改进的地方主要有以下三处:
- 初始化
除去 2 以外的所有偶数都不是素数,因此在初始化时便将其全部筛除。 - 倍数的起点
原本倍数的起点是2,即原本的筛除操作是从素数 a a a 的 2 倍开始的,即 s = 2 a s=2 a s=2a,改进的方法是将起点设为 s = a 2 s= a^2 s=a2。
下证其合理性:
设 ∀ k ∈ [ 2 , a ) ∈ N \forall k \in[2,a) \in N ∀k∈[2,a)∈N
则必有 k = a ′ ⋅ b k=a^{'} \cdot b k=a′⋅b,其中 a ′ a^{'} a′ 为素数、 b b b 为整数
则 k ⋅ a = a ′ ⋅ b ⋅ a = c ⋅ a ′ k \cdot a = a^{'} \cdot b \cdot a = c \cdot a^{'} k⋅a=a′⋅b⋅a=c⋅a′,将 b ⋅ a b \cdot a b⋅a 记作 c c c
由 a ′ < k < a a^{'}<k<a a′<k<a
得 b ⋅ a = c > a ′ b \cdot a=c> a^{'} b⋅a=c>a′
则 k ⋅ a = c ⋅ a ′ k \cdot a = c \cdot a^{'} k⋅a=c⋅a′ 且 c > a ′ c> a^{'} c>a′, a > a ′ a> a^{'} a>a′
那么 [ 2 a , a 2 ) [2 a,a^2) [2a,a2) 中 a a a 倍数都可以被一个小于 a a a 的质数 a ′ a^{'} a′ 乘以一个大于 a ′ a^{'} a′ 的整数来表示。即这些数已经被较小的质数筛除过了,无需再进行筛除。
算法是筛除 [ 2 , n ] [2,n] [2,n] 中的非素数,使用本条改进策略后,筛除工作不必在达到 n n n 时才截止,达到 n \sqrt{n} n 时即可截止。因为 [ n , n ] [\sqrt{n},n] [n,n] 中的素数筛除操作的起点 s = a 2 s= a^2 s=a2 已经大于 n n n。 - 倍数的增量
已知素数筛除操作的起点 s = a 2 s= a^2 s=a2,若接下来的筛除操作倍数的增量为奇数,即依次筛除 a 2 , ( a + 1 ) a , ( a + 3 ) a , ( a + 5 ) a , ⋯ , ( a + 2 k + 1 ) a a^2,(a+1)a,(a+3)a,(a+5)a,\cdots,(a+2k+1)a a2,(a+1)a,(a+3)a,(a+5)a,⋯,(a+2k+1)a,是一种重复的筛除。因为质数一定是奇数,两个奇数的和一定是偶数,所以一个质数加一个奇数等于一个偶数,即 a + 2 k + 1 a+2k+1 a+2k+1 一定是偶数。因为偶数乘以任何整数都是偶数,所以 ( a + 2 k + 1 ) a (a+2k+1)a (a+2k+1)a 一定是偶数。因为偶数在初始化时已经筛除,所以该筛除操作是重复的。因此,筛除操作倍数的增量应为偶数,即依次筛除 a 2 , ( a + 2 ) a , ( a + 4 ) a , ( a + 6 ) a , ⋯ , ( a + 2 k ) a a^2,(a+2)a,(a+4)a,(a+6)a,\cdots,(a+2k)a a2,(a+2)a,(a+4)a,(a+6)a,⋯,(a+2k)a。
代码如下:
#include <iostream>
#include <cmath>
using namespace std;
void filter_prime(bool *const is_prime,size_t range){
is_prime[0] = is_prime[1] = false;
is_prime[2] = true;
for(size_t i=3;i<=range;i++){///大于等于三的奇数标为true,偶数标为false
is_prime[i] = true;
if(++i>range)break;
is_prime[i] = false;
}
for(size_t i=3;i<=sqrt(range);i+=2){
if(is_prime[i]){
for(size_t j=i*i;j<=range;j+=2*i){
is_prime[j] = false;
}
}
}
}
int main(){
size_t n;
cin >> n;
bool *is_prime = new bool[n+10];
filter_prime(is_prime,n);
size_t c = 0;
for(size_t i=0;i<=n;i++){
c += is_prime[i];
}
cout << c << endl;
delete is_prime;
return 0;
}
线性筛法(Eural筛法)
#include <iostream>
#include <cmath>
using namespace std;
size_t filter_prime(bool *const is_prime,size_t range,size_t *const prime){
is_prime[0] = is_prime[1] = false;
for(size_t i=2;i<=range;i++){///假定所有数都为素数
is_prime[i] = true;
}
size_t counter = 0;
for(size_t i=2;i<=range;i++){
if(is_prime[i]){
prime[counter++] = i;
}
for(size_t j=0;j<counter&&i*prime[j]<=range;j++){
is_prime[i*prime[j]] = false;
if(i%prime[j]==0)break;
}
}
return counter;
}
int main(){
size_t n;
cin >> n;
bool *is_prime = new bool[n+10];
size_t *prime = new size_t[n];
cout << filter_prime(is_prime,n,prime) << endl;
delete[] is_prime;
delete[] prime;
return 0;
}