目录
本文借鉴于:
埃氏筛与欧拉筛(线性筛)_埃氏筛法时间复杂度_吕同学的头发不能秃的博客-优快云博客
埃氏筛法与线性筛法_计科土狗的博客-优快云博客
https://www.cnblogs.com/cbmango/p/16141366.html
1.素数是仅能被它本身和1整除的任何整数。
2.埃拉托斯特尼筛法,简称埃氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
3.判断一个数是不是素数,我们只用判[2,n]
内有没有它的因子。在筛合数的时候也可以这样做,因为一个合数的最小质因子一定小于等于
n。证明如下:
设n为合数,那么n必有两个约数a,b 使得a*b=n。则a、b两个数中必有一个大于等于n,一个小于等于
n,因此合数n的最小质因子一定小于等于
n(证毕)
所以用[2,1e7]中的质数,就可以筛去[2,1e7]中的所有合数。
1.埃氏筛
算法思想:从质数2开始,把每个质数的所有小于n的倍数都标记为合数。
在范围2~n内,①划去2的所有倍数(2保留);②再划去3的所有倍数(3保留);③再划去5的所有所有倍数(5保留,此时4早在第①步时被划去);再划去i的所有倍数......以此类推,直到 i*i>=n为止。
例如:10以内的素数
2 3 4 5 6 7 8 9 10
2*2=4<10,划去2的倍数后为:
2 3 5 7 9
3*3=9<10,划去3的倍数后:
2 3 5 7
因为5*5=25>10,所以循环结束,10以内的素数为2 3 5 7
题目:统计n以内有多少个素数
#include<iostream>
using namespace std;
const int N = 1e7 + 10;
bool A[N];//全局变量,默认值为0
int cnt;
void prime(int n)
{
A[0] = A[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!A[i])//若A[i]值为0,则数i为质数(素数)
{
cnt++;
for (int j = 2*i; j <= n; j += i)
{
A[j] = 1;
}
}
}
}
int main()
{
int n;
cin >> n;
prime(n);
cout << cnt;
}
因为我们在筛2的倍数的时候已经把 2*3 筛去,但是在筛3的倍数的时候又会将 3*2 再筛一遍,就会有时间的浪费,解决这一问题的方法就是每次都从 i*i 开始筛,例如筛5的倍数,我们从 5*5 开始筛,就可以避免 2*5, 3*5 被重复筛了。
因此上述代码有两个地方可以优化:
1.判断一个数是不是素数,我们只用判[2,n]
内有没有它的因子。
2.每次都从 i*i 开始筛。
优化后的埃氏筛:
(注意:不适合大数据,因为i*i可能会超出int范围)
#include<iostream>
#include<cmath>
using namespace std;
const int N = 1e7 + 10;
bool a[N];
int b[N];
int cnt;
void prime(int n)
{
a[0] = a[1] = 1;
for (int i = 2; i <= sqrt(n); i++) //第一处修改
if (!a[i])
{
for (int j = i * i; j <= n; j+=i) //第二处修改
a[j] = 1;
}
}
int main()
{
int n;
cin >> n;
prime(n);
for (int i = 2; i <= n; i++)
if (!a[i])
{
cout << i << ' ';
cnt++;
}
cout << endl << cnt;
}
2.欧拉筛
欧拉筛其实是埃氏筛的改良版,因为埃氏筛会重复筛,增加了时间复杂度,例如 30 这个合数会被2*15 筛去一遍,又会被 3*10 筛一遍,还会被 5*6 筛一遍,浪费了时间(优化后的埃氏筛也不能解决这个问题),因此我们引进欧拉筛:使合数只能被它的最小质因数来筛,比如20 ,只能用它的最小质因数2来筛除,不能用4,或者5去筛除。
#include<iostream>
using namespace std;
const int N = 1e7 + 10;
bool v[N];//用于记录第i个数是否为素数,v[i]=0,表示i为素数v[i]=1表示i为合数
int a[N];//用于存放素数,a[1]=2,a[2]=3...
int cnt;//用于记录共有多少个素数
void prime(int n)
{
v[0] = v[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!v[i])//i如果为素数则存到数组a中去
a[++cnt] = i;
//下方的for循环是在将数i的倍数标记为合数,注意数i是从2~n的自然数,不一定是素数
for (int j = 1; j <= cnt; j++)//j<=cnt表示只用当前已经找到的素数
{
if (i * a[j] > n)//防止v[i*a[j]]越界
break;
v[i * a[j]] = 1;//素数的倍数一定为合数,所以标记为1
if (!(i % a[j]))//省时最关键的一步,即一个合数只用它最小的质因子来标记
break; //例如i=4,a[1]=2,v[4*2]=1,标记了合数8,此时4%2==0退出循环
//如果不退出的话,会再执行v[4*3]=1,标记了合数12,但我们希望12是被它的
//最小质因子2标记而不是3。
//合数12会被v[6*2]标记,读者可自行验证。
}
}
}
int main()
{
int n;
cin >> n;
prime(n);
for (int i = 1; i <= cnt; i++)
cout << a[i] << ' ';
}