如果我们想要知道小于等于n
有多少个质数呢?
需要质数筛法。
三种筛法参考: https://www.acwing.com/solution/content/7950/。
1.朴素筛法:
(1).做法:把2~(n-1)中的所有的数的倍数都标记上,最后没有被标记的数就是质数。
(2).原理:假定有一个数p未被2~(p-1)
中的数标记过,那么说明,不存在2~(p-1)
中的任何一个数的倍数是p,
也就是说p不是2~(p-1)
中的任何数的倍数,也就是说2~(p-1)
中不存在p的约数,因此,根据质数的定义可知:
p是质数。
1 | void get_primes(){ |
计算时间复杂度:
调和级数:当n趋近于正无穷的时候,1/2+1/3+1/4+1/5+…+1/n=ln n+c,(c是欧拉常数,约等于0.577左右)
内层循环计算次数,$n/2 + n/3 + … + n/n \approx n \star ln n < n \star log_2 n$,所以时间复杂度为:$O(n \star log n)$。
这里Latex公式渲染有问题,两个
*
会被hexo转义为斜体,所以用\star
代替。
2.埃拉托斯特尼筛法:简称埃氏筛法。
我们发现朴素筛存在可以优化的地方,首先它有大量重复标记;其次对于一个数p,判断它是不是质数,由算术基本定理,只需要判断它是不是[2,p-1]
中的质数的倍数,而不用判断[2,p-1]
中的所有数的倍数,这就是埃氏筛改进的地方。
1 | void get_primes(){ |
质数定理:1~n中有$\frac n {ln n}$个质数。
计算时间复杂度:
内层循环计算次数,$n/2 + n/3 + n/5… + n/[prime]$,时间复杂度为:$O(n*log log n)$。
3.线性筛法:也称为欧拉筛法。
蓝桥杯(三十)中有介绍过欧拉筛法,这里重复一遍。
都叫线性筛了,时间复杂度当然是优秀的O(n)。
参考文章: https://zhuanlan.zhihu.com/p/124068032。
埃氏筛法仍有优化空间,朴素筛法中将一个合数重复多次标记的问题埃氏筛也没有解决。
比如:2和3都是质数,它们都会将合数12标记。
如果能让每个合数都只被标记一次,那么时间复杂度就可以降到$O(n)$了。
在数量级达到n = 10^7
时,线性筛会比埃氏筛快1倍左右。
欧拉筛法的核心思想:每个合数只会被它的最小质因子筛掉,所以只会被筛一次。
算法模拟:
s1:建立标记数列(从2到n的自然数)和素数数列;
s2:第一个数字是2(看标记是素数),把素数2放进素数数列,标记2×2为非素数;
s3:第二个数字是3(看标记是素数),把素数3放入素数数列,标记3×2、3×3为非素数;
s4:第三个数字是4(看标记是合数),标记4×2为非素数,break;
s5:第四个数字是5(看标记是素数),把素数5放入素数数列,标记5×2、5×3、5×5为非素数;
……
注意到筛法求素数的同时也得到了每个数的最小质因子。
1 | void get_primes(){ |
为什么要break?
避免i*primes[j+1]
被提前筛掉,满足if条件,i*primes[j+1]
的最小质因子就是primes[j]
,如果不退出它就会被primes[j+1]
筛掉,会重复。
比如12有两种分解方式:4*3
和6*2
,在4 % 2 == 0
的时候就知道了4的最小质因子是2,所以4*3
的最小质因子也是2,必须退出,直到6*2
用最小质因子2筛掉它。
primes[j]*i
取决于两者,看primes[j]
和i
谁的最小质因子更小,代码注释中已经解释了2种情况。
欧拉筛法可以顺便求出primes[j]*i
的最小质因子primes[j]
。
4.其他筛法优化技巧:
只筛奇数,
因为除 2 以外的偶数都是合数,所以我们可以直接跳过它们,只用关心奇数就好。
首先,这样做能让我们内存需求减半;其次,所需的操作大约也减半。
5.总结:
筛质数一般用线性筛法就行,埃氏筛法不怎么用但是思想还是很有用。
例题:868. 筛质数(模板题)
1 | 给定一个正整数 n,请你求出 1∼n 中质数的个数。 |
算法1:朴素筛法。
1 |
|
算法2:埃氏筛法。
1 |
|
算法3:线性筛法。
1 |
|