素数筛选算法
理论层面:
怎么筛选出素数?
我们的基本思路是排除掉所有合数,那么留下的就是素数
知道合数可以分解成素数相乘,那么我们只要将不同素数组合相乘后的结果排除掉就可以了
q = p1^a1 * p2^a2 *... pn^an
这里q为合数,pi为质数,ai为幂。
算法框架层面:
首先
构造两个数组,check和prime, check用于标记该数是否是素数,prime是包括所有素数的集合。举例:
check[10] = [0,0,1,1,0,1,0,1,0,0]//初值为0,0表示素数,1表示合数,
//check随着循环而改变
prime[] = [2,3,5,7] // prime刚开始为空,随着循环迭代而生长
然后
我们可以构造一个循环,从2开始,每次+1,直到要求的范围(比如10000以内)
for(i=2;i<=n;i++)
{}
在循环体内:
我们一定可以保证:
假如循环到了i = a,经过i = 2到i = a-1这么多轮筛选之后,对于第a+1这个数已经可以确定它是不是素数(如果a+1是合数就一定已经被标记过了)
算法思路
素数筛算法,这里有三个层次:
1、判断一个数是否为素数
很简单,不说了,也可以用欧拉定理
2、普通素数筛(埃拉托斯特尼筛法)
主要想法是对于已知的一个素数,那么我们依次排除掉这个素数×2, 这个素数×3, 这个素数×4 … 直到达到要求的范围(比如10000以内)再停止。
3、快速素数筛(欧拉筛法、线性筛)
普通素数筛存在一个问题,就是会进行重复排除, 比如:
用2排除12, 2*6会排除一次
当用3排除12时, 3*4又会排除一次
//如果数据规模很大,效率会很低。
快速筛解决了重复的问题,一个合数只会排除一次:
主要想法:假如当前循环到了i=a, 那么依次排除: a * prime[1]、a * prime[2]、a * prime[3]、a * prime[4]…(a乘以所有的已知素数)。 但是如果prime[k] | a, 那么就出现了重复,停止排除(但是a*prime[k]这一项先排除掉),break进入下一个i循环。
停止排除的理由:如果a能被prime[k]整除,那么它在后面的循环中一定会被排除。
证明:
若prime[k] | a,则
a = (a / prime[k]) * prime[k]
//令b = a / prime[k],得到
a = b * prime[k]
设prime[k+]表示表中比prime[k]更大的素数,prime[k-]表示比prime[k]更小的素数,有
a * prime[k+] = b * prime[k] * prime[k+] = (b * prime[k+]) * prime[k]
那么当i = a × prime[k+]时, 知道prime[k-]不整除a和prime[k+], 所以一定可以循环到第(a × prime[k+]) × prime[k],并排除这个数。
证毕!
从数论角度理解:一个数可以分解为多个因数相乘,我们只要排除
最小质因数 × 其它质因数相乘得到的数 = 该数
这一种情况就可以避免重复,而若prime[k] | a, 说明prime[k] 不是 prime[k] * a 的最小质因数。 更不用说prime[k+]的数,但是从小到大就可以保证prime[k-]和prime[k]是最小的质因数,那么将其排除。
很妙!