埃氏筛法(埃拉托斯特尼筛法)
在求解小于n有多少个素数的问题中,当n的数值较大时,暴力求解显然是不可取的。
埃氏筛法的原理:任意大于1的正整数n,它的x倍(x>1)就是合数,由此来筛选出素数。
可以这样理解,暴力求解是直接通过遍历计算判断这个数是否是素数;而埃氏筛法则是创建一个大小为N的数组,通过倍数关系筛选出素数。
vector<int> Eratosthenes(int N)
{
vector<int> Pri; //存储筛选出来的素数
vector<bool> is_Pri(N, true); //标记素数,初始均为true,通过倍数关系排除掉合数
for(int i = 2; i < N; i++)
{
if(is_Pri[i])
Pri.push_back(i);
for(int j = i * i; j < N; j += i)
is_Pri[j] = false;
}
return Pri;
}
为什么从i*i
开始筛选掉合数:被省略的部分 {i * 2, i * 3, ..., i * (i - 1)}
,不难发现在排除(i-1)
的倍数时就已经进行过(i-1) * i
的运算(例如i*2
, 在排除2的倍数时,就已排除了2*i
),故可省略掉。
但在埃氏筛法中,存在着同一个数被反复标记的情况(例如18,在2*9
中被标记,在3*6
中再次被标记),故线性筛诞生了。
线性筛(Euler筛法)埃氏筛法的升级版
本文对于线性筛的理解:一个数若为合数,则其一定等于两个小于它的数的乘积(除1和其本身以外),在此取其最小质因数与另一个值的乘积,所以我们可以通过将所有质数依次与(2, 3, 4, …, N-1)相乘来标记合数,但依旧存在很多重复标记的地方(例如2*9
与 3*6
)。
解决方法:在进行运算 i * Pri[m]
(Pri[m]存储素数)时,如果i
为Pri[m]
的倍数,即i = k * Pri[m]
,故计算i * Pri[m+1]
时,可变换为i * Pri[m+1] = k * Pri[m] * Pri[m+1]
在后续的遍历计算中会再次出现i = k * Pri[m+1]
情况,故此时i
不用提前与Pri[m+1]
进行乘积,从而达到减少重复筛选数量。
此时依旧存在优化空间,可参考埃氏筛法中从i*i
开始筛选。素数Pri[m]
只与大于它的i
相乘,若i
小于Pri[m]
,在后续计算中均会被计算,若i
为素数,在后续计算中存在I = Pri[m]
与Pri[n] = i
进行Pri[n] * I
乘积,若i
为合数,可将其拆分为i = Pri[n] * k
,与上述线性筛同理可进行省略,避免提前运算。
vector<int> Euler(int N)
{
vector<int> Pri;
vector<bool> not_Pri(N);
for(int i = 2; i < N; i++)
{
if(!not_Pri[i])
Pri.push_back(i);
for(int tmp_Pri : Pri)
{
if(tmp_Pri * i > N)
break;
not_Pri[tmp_Pri * i] = true;
if(i % tmp_Pri == 0)
break;
}
}
return Pri;
}
参考文章:筛法