埃氏筛法
埃拉托斯特尼筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
埃氏筛法即为古老的数学思想,常常用于其快速求取素数表
素数表的快速求取在一道算法题中是尤为关键的,特别是在对于数据量过大的时候,埃氏筛法能够节省大量的运算时间从而快速的完成需求目标,比较于我们在学习编程语言中利用数学库开方求取的算法,其能够遍历更少的元素,节省更多的时间,可以作为一个进阶来学习
无论是在c还是c++的学习当中,我们都曾学习过求取素数的算法
例如c语言版本求取0~n之间的所有素数
#include <stdio.h>
#include <math.h>
bool judge(int n)
{
if(n == 0 || n == 1)return false;
for(int i = 2;i <= sqrt(n);i ++ )
{
if(n % i == 0)return false;
}
return true;
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 0;i <= n;i ++ )
{
if(judge(i))printf("%d ",i);
}
return 0;
}
例如c ++语言版本求取0~n之间的所有素数
#include <iostream>
#include <cmath>
using namespace std;
bool judge(int n)
{
if(n == 0 || n == 1)return false;
for(int i = 2;i <= sqrt(n);i ++ )
{
if(n % i == 0)return false;
}
return true;
}
int main()
{
int n;
cin >> n;
for(int i = 0;i <= n;i ++ )
{
if(judge(i))cout << i << ' ';
}
return 0;
}
由此可见,其共同点都在于一个判断素数的函数,其为代码核心
bool judge(int n)
{
if(n == 0 || n == 1)return false;
for(int i = 2;i <= sqrt(n);i ++ )
{
if(n % i == 0)return false;
}
return true;
}
埃氏筛法的原理在于,通过对于最小的数字开始从小到大进行筛选,素数的定义是,这个数只可以被1和自身整除,因此凡是素数的倍数均不是素数,那么我们从0~20开始举例,通过埃氏筛法来求的话,首先为了减少运算,我们可以直接跳过0和1,直接从2开始筛选。
在进行2的筛选的时候,我们将0~20中所有2的倍数去掉,也就是4、6、8、10、12、14、16、18、20
而后进行3的筛选,将3的倍数筛掉,也就是6(已筛过)、9、12(已筛过)、15、18(已筛过)
再次枚举的时候就可以跳过4了,直接筛5,将10(已筛过)、15、20(已筛过)筛去
继续枚举的时候直接枚举7,筛掉7的倍数14(已筛过)
接下来就是一个不断枚举的过程:
跳过8、9、10枚举11,筛掉11的倍数
跳过12,枚举13,筛掉13的倍数
跳过14、15、16,枚举17,筛掉17的倍数
跳过18,枚举19,筛掉19倍数
跳过20,完成埃氏筛法,操作结束
此上内容为0~20利用埃氏筛法求取素数的详解
由此我们可以发现,埃氏筛法的特点在于在不断进行从0~n的枚举过程之中,一遍枚举一遍进行筛选后面的数字进行快速遍历,而后快速完成时间的判定,即在不断便利的过程之中,可以保证每一个遍历到的数字必为素数且可以快速跳过遍历
总解一句话就是:
先解释一下筛选法的步骤:
<1>先将1去掉
<2>将2的倍数去掉。
<3>将3的倍数去掉。
……
<i>将i的倍数去掉。
要得到不大于某个自然数N的所有素数,只要在2~n中将不大于sqrt(n)的素数的倍数全部划去即可
当然,这种情况的利用仅仅是用于确定判断素数,若是想要拉取素数表的话还是要遍历到n
标准代码为:
#include <iostream>
using namespace std;
const int N = 1e6+10;
int prime[N];
bool isprime[N];
int idx = 0;
int main()
{
int n;
cin >> n;
for(int i = 0;i <= n;i ++ )isprime[i] = true;
prime[0] = prime[1] = false;
for(int i = 2;i <= n;i ++ )
{
if(isprime[i])
{
for(int j = 2 * i;j <= n;j += i)
{
isprime[j] = false;
}
}
if(isprime[i])prime[ ++ idx ] = i;
}
for(int i = 1;i <= idx;i ++ )cout << prime[i] << ' ';
return 0;
}
线性筛法
在使用埃氏筛法的过程之中以及举例模拟时我们可以发现,埃氏筛法虽然比暴力筛法效率高,但是却有着后面元素被反复标记的情况出现,因此也就浪费了一些运算时间。在这里我们将介绍埃氏筛法的优化终极版,也就是线性筛法
线性筛法能够很好的解决元素被重复标记的现象,使后面的被标记元素仅仅被最小的质数因此筛一次(),因此能够减少重复标记的操作,从而使效率最大化
#include <iostream>
using namespace std;
const int N = 1e6+10;
int prime[N];
bool isprime[N];
int idx = 0;
int main()
{
int n;
cin >> n;
for(int i = 2;i <= n;i ++ )
{
if(!isprime[i])
{
prime[ ++ idx ] = i;
}
for(int j = 1;j <= idx && i <= n / prime[j];j ++ )//i * prime[j] <= n有可能会爆int,在这里我们使用等价变式
{
isprime[i * prime[j]] = 1;
if(i % prime[j] == 0)break;
}
}
for(int i = 1;i <= idx;i ++ )cout << prime[i] << ' ';
return 0;
}