目录
方法一:埃拉托斯特尼筛(埃氏筛) 复杂度:O(n*logn)
写在前面 :
1.对于判断比较少的个数或比较小的数值时普通的判断素数的方法即可了,类似:
int judge(int n) { int i,flag=1; for(i=2;i<=sqrt(n);i++) { if(n%i==0) { flag=0; break; } } return flag; //flag=1为素数,0为非素数 }
而对于比较大的数据时,普通的判断素数的方法肯定会TLE的,所以我们有什么方法来快速判断素数呢?
方法一:埃拉托斯特尼筛(埃氏筛) 复杂度:O(n*logn)
首先我们得理解一个概念
合数:一个正整数,如果除1和它本身以外,还能被其他正整数整除,就叫合数。如6是合数,除了1和6以外,还能被2和3整除。
埃氏筛的核心思想就是:一个合数(C)总能写出一个素数(P)与任意某个数(O)的乘积。
即: C=P*O;换种说法:素数的倍数一定不是素数。
找1-n范围内的质数。
1.首先,2是最小的质数,i = 2
1、如果i是质数(标记为0),用存储素数的数组存储 i。
2、i * j一定不是质数,将其标记为1,j++。重复此步骤,直到i * j > n(超出范围)
int sushu[10000];//存素数
int book[10000];//标记是否为素数 ,1不是素数。
int count;//个数
void ejudge(int n)
{
int i,j;
for(i=2;i*i<=n;i++) //为啥是i*i下文会讲
{
if(book[i]==0)
{
sushu[count++]=i;
}
for(j=2;j*i<=n;j++)
{
book[i*j]=1;//素数的倍数不是素数。
}
}
for(;i<=n;i++) //sqrt(n)以后的数。
if(book[i]==0)
{
sushu[count++]=i;
}
}
问题:这里关于为啥最外层循环条件为i*i<=n而不直接i<=n;这还省去了后面的再次循环呢,我做个解释。
(⊙o⊙)…首先,一个数假如不是素数,那么,它的因子,肯定一个在自身开方的前,一个在自身开方后,那么当我们遍历完开方前的数,它的某个倍数其实就以及囊括了开方后到n这个范围的数了。这样就省去一部分的重复标记。而最后的循环就不要再通过倍数标记了,直接根据之前的标记判断是不是素数。
埃氏筛有个缺点不知道小伙伴们有没有发现:对于一个数它可能重复的标记 比如 6,
在i=2时,2*3被标记一次,在i=3时,3*2被标记一次。多次重复的标记浪费时间。其实n的标记的次数就等于它的不同的质因子个数。
基于此,让我们隆重介绍下线性筛
方法二:线性筛 复杂度 O(n)
先直接上代码:
int sushu[10000];//存素数
int book[10000];//标记是否为素数 ,1不是素数。
int count;//个数
void xjudge(int n)
{
int i,j;
for(i=2;i<=n;i++)
{
if(book[i]==0)
sushu[count++]=i;
for(j=0;j<count&&i*sushu[j]<=n;j++)
{
book[i*sushu[j]]=1;
if(i%sushu[j]==0) //核心代码
break;
}
}
线性筛与埃氏筛相比,没有重复的标记大大地减少了时间。
其核心思想:
是每个数都只用最小质因子标记一次。这避免了重复标记。
也就是代码中的核心代码: if(i%sushu[j]==0)
为啥呢?
举个例子(没有核心代码时): 假如 i=k*sushu[j];假如 一个数 i*sushu[j+1];那我们筛的时候是用sushu[j+1]筛,可是,i可以拆分,那个数也可以拆成 k*sushu[j]*sushu[j+1],最小的质因子应该是sushu[j],应该由sushu[j]来删,没有了核心代码,跟埃氏筛其实没差。
用数字更直观地来体现:
i=4=2*2;
一个数=i*3=12;
没有核心代码时会重复标记当3,2时。
有核心代码的时候只有2会标记,然后12%2==0,break;不会重复标记。
总结:
二者都是筛选素数的好方法,但明显线性筛更胜一筹。通过运行时间更能体现出差别。
n 埃筛运行时间(毫秒) 线性筛运行时间(毫秒) 1e6 12 9 5e6 85 54 1e7 179 123 2e7 371 246 5e7 996 564 1e8 2143 1173 2e8 4421 2278 3e8 6564 3432 4e8 8990 4651