朴素筛法 O(nlogn)
不管是质数还是合数,都筛掉他们的倍数
int get_prime(int k)
{
//for(int i=2;i<=k/i;i++) 这里需要都筛一遍
for(int i=2;i<=k;i++)
{
if(st[i]==0) prime[cnt++]=i;
for(int j=i+i;j<=k;j+=i)
{
st[j]=1;
}
}
return cnt;
}
时间复杂度分析:
n
2
+
n
3
+
.
.
.
+
n
n
−
1
+
n
n
=
n
∗
(
1
2
+
1
3
+
.
.
.
+
1
n
−
1
+
1
n
)
\frac{n}{2}+ \frac{n}{3}+ ...+ \frac{n}{n-1}+ \frac{n}{n} =n*(\frac{1}{2}+ \frac{1}{3}+ ...+ \frac{1}{n-1}+ \frac{1}{n} )
2n+3n+...+n−1n+nn=n∗(21+31+...+n−11+n1)
括号内为调和级数,时间复杂度可记为O(nlogn)
埃氏筛法 O(nloglogn)
只筛掉质数的倍数
int get_prime(int k)
{
for(int i=2;i<=k;i++)
{
if(st[i]==0)
{
prime[cnt++]=i;
for(int j=i+i;j<=k;j+=i) st[j]=1;
}
}
return cnt;
}
线性筛法O(n)
每次都用最小质因子筛,保证每个数只会被筛一次
利用1 ~ j个质数与当前的i,筛掉prime[j] * i,每次遍历1 ~ j个质数,与当前的i相乘
🎈🎈🎈问题是如何保证prime[j]是prime[j]*i的最小质因子?
当i%prime[j]!=0时:说明还没有执行if(i%prime[1~j-1]==0) break;也就是i并没有小于prime[j]的质因子,当前prime[j]*i中i并没有小于prime[j]的质因子,所以prime[j]是prime[j]*i的最小质因子
当i%prime[j]==0时:i可以写成k*prime[j],prime[j+1]*i可以写成prime[j+1]*k *prime[j],所以往后遍历到的都不会是最小质因子,那后面的会如何筛掉?后面的在i=k时就被筛掉了,也就是在前面就被筛掉了
int get_prime(int k)
{
for(int i=2;i<=k;i++)
{
if(st[i]==0) prime[cnt++]=i;
//for(int j=0;j<=k/prime[j];j++)
for(int j=0;prime[j]<=k/i;j++)
{
st[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
return cnt;
}
🎈🎈🎈有一个问题是for(int j=0;prime[j]<=k/i;j++)是如何保证prime[j]中的j<=cnt的??? (如果j>cnt那么prime数组中的数还没有添加进去,全为0,这样就会没有意义)
分析:
如果当前i为质数:那么i一定会被存到prime[j]中,当prime[j]=i时,满足i%prime[j]==0,会break
如果当前i为合数:那么一定会有小于i的质数满足i%prime[j]==0
所有综上:跳出循环的条件一定会满足prime[j]<=i,而<=i的质数肯定都被筛过了, 所以这样是合理可行的。if(i%prime[j]==0) break 保证了程序会在合适的时机跳出循环
🎈🎈🎈还有一个问题就是前面素数个数很少的时候,即j很小,prime[j]*i会不会漏掉,就是前面我们得到的素数个数很少,我们会不会漏掉?
其实不会,根据上一个问题的分析发现,每次prime[j]并不会用完,当i%prime[j]==0时,就会break
prime[1~j] i=1
prime[1~j] i=2
prime[1~j] i=3
prime[1~j] i=4
…
prime[1~j] i=n
这个过程中prime[j]与每一个i都组合过,当不符合条件时就停止,所以不会漏掉
这个算法理解起来有种玄学的味道…