质数:除了1和它本身以外不再有其他因数的自然数。
合数:与质数相反。
枚举法是查找质数最容易想到的方法,又被称为试除法。
它的思路就是遍历从2到n这个数的所有的数字,判断这个数字能否被这个序列种的任意一个序列整除,如果整除,则说明它不具有唯一的因子(1和它本身)
代码如下:
int func(int n)
{
int ans = 0;
bool isPrimer;
for (int i = 2; i < n; i++)
{
isPrimer = true; //默认其是一个质数
for (int j = 2; j < i; j++)
{
if (i % j == 0)
{ //如果有任意一个数能够被整除,则说明其一定不是质数,则直接退出
isPrimer = false;
break;
}
}
if (isPrimer) //如果是质数,则递增
{
ans++;
}
}
return ans;
}
枚举优化
观察以下数字的形式:
4: 1 * 4 ,2 * 2 , 4 * 1
5: 1* 5 ,sqrt(5) * sqrt(5) , 5 * 1
6: 1* 6 ,2 * 3 ,sqrt(6) * sqrt(6) ,3 * 2 , 6 * 1
8: 1* 6 ,2 * 4 ,sqrt(8) * sqrt(8) ,4 * 2 , 6 * 1
可以得到,无论数字是质数还是合数,可以认为以中间的两个因子相乘,他们的左右两边是一致的。也就是说,遍历了前面,我们如果继续遍历,则会导致重复的现象出现,则我们完全可以避免他。
优化一个地方:
...
for (int j=2;j<=sqrt(i);j++) //只遍历前面的sqrt部分即可
...
引论: 一个质数的倍数一定是合数 ,如 3 是一个质数,则 3 * 3 , 3 * 3 + 3 , 3 * 3 + 3 + 3 一定是合数。
所以我们可以在准备判断一个数字是否是质数的时候,计算他的倍数,把他后面的倍数都计算出来,然后他们一定是合数,我们可以直接排除。
我们怎么实现这一点呢,可以定义一个质数数组,一共有n个数字,则用n个数字 1 填充数组。
图解:

//埃氏筛
int countPrimes_2(int n) {
//首先全部初始化为1
vector<int> isPrimer(n, 1);
int ans = 0;
for (int i = 2; i < n; i++)
{
if (isPrimer[i] == 1)
{
ans++;
if ((long long) i*i < n) //注意首先判断第一个 i*i是否位于这个区间种
{
//将其 x(x+1) x(x+2) 倍数全部设置为0,即一定是非奇数
for (int j = i * i; j < n; j += i)
{
isPrimer[j] = 0;
}
}
}
}
return ans;
}
在埃氏筛中,我们存储重复标记元素的情况,我们无法避免它,但是我们利用线氏筛来完全避免这种情况。
根据《算术基本定理》:
算术基本定理: 任何一个大于1的 自然数 N,如果N不为 质数 ,那么N可以 唯一 分解成有限个质数的乘积.
例如:
16是一个合数,16可以 分解为 2 * 2 * 2 *2 是一个唯一的,有限个的质数乘积。
20 是一个合数,20 可以分解为 2 * 2 * 5 也是一个唯一的,有限个的质数的乘积。
因此得到结论:
有限多个质数的乘积 一定是一个合数 ---->质数 * 质数 * 质数 … = 合数
作为因子的每个质数不同,则最终的合数也不同。
临时存储每一个质数,只要后面的数字能够被这些质数整除,则直接结束循环,原因:
当前数 能被 质数数组中的某质数 整除,当前数一定是包含 该质数 的合数
合数拆分时,因子中,会出现 两个相同质数,不能保证合数不同
质数数组中后面质数都比 该质数 大。该质数 * >它的质数 = 合数后面一定会遇到
图例:
我的理解: 12可以被拆分为 2 * 2 * 3,2和3都在质数数组中出现了,因此就没有必要再利用3 * 4得到 12了,因为4可以由 2 * 2得到,因此直接结束此次循环。
//线氏筛
int countPrimes_1(int n) {
vector<int> primes;
vector<int> isPrime(n, 1);
for (int i = 2; i < n; ++i) {
if (isPrime[i]) {
primes.push_back(i);
}
for (int j = 0; j < primes.size() && i * primes[j] < n; ++j) {
isPrime[i * primes[j]] = 0;
if (i % primes[j] == 0) {
break;
}
}
}
return primes.size();
}
优化后的埃氏筛:
只在奇数范围内寻找。class Solution {
public:
int countPrimes(int n) {
//默认全部为true
vector<int> primer(n,1);
int res=(n>2)?1:0;
int t=0;
for (int i=3;i<n;i+=2)
{
if (primer[i]==1)
{
res++;
if ((long long)i*i < n)
{
for (int j=i;(t=i*j)<n;j+=2)
{
primer[t]=0;
}
}
}
}
return res;
}
};