从判断质数说起
以下定义能帮您更好理解本文:
0. 本文没有具体提到的数均为正整数(包括因数等)
1. 质数=素数=prime:除1与它本身,没有其他因数。
2. 开方=开平方=sqrt=square root
3. 本文如果没有明确说明,需要判断的质数的变量名均为n
第一章:朴素判断法
1.1 定义判断法
让我们来回顾下质数的定义
除1与它本身,没有其他因数的数,叫做质数
那么,我们就得出了定义判断法:
i循环[2,n-1],判断是否存在i,使得n能被i整除。
显然,这是一个正确的算法,时间复杂度 O(n)
1.2 改进
然而我们发现,我们需要更高效的算法,于是,我们有了折半定义判断法:
明显地,i不需要做那么多次循环,只需要循环到[2,n/2]即可。
显然,这个算法是正确的,时间复杂度O(n/2)相当于O(n)级别的,但是范围能显著增加
1.3 再次改进
那么,是否还有更为高效的做法呢?
答案是有的:我们有了开方判断法:
明显低,1.2中,i还是做了很多无用功,实际上只需要循环到[2,sqrt(n)]即可,这个优化,大大缩小了数量规模
为什么呢?
如果n为合数,那么设ab=n,且a>=sqrt(n),b<=sqrt(n),那么b肯定先被查找到,那么已经判断出来了合数,即可。
显然,时间复杂度O(sqrt(n)),能应付基本的判断质数了
第二章:筛法
2.1 埃斯托斯特尼筛法
具体做法是:给出要筛数值的范围sqrt(n),找出sqrt(n)以内的素数p1,p2,p3,......,pk。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个素数5筛,把5留下,把5的倍数剔除掉;不断重复下去......。
Eratosthenes筛法名字虽然高贵冷艳,但是并不难理解,原理就不多说了,但是它做了许多无用功,一个数会被筛到好几次,最后的时间复杂度是O(nloglogn),不要以为这个复杂度已经很好了,因为有直接O(n)的欧拉筛法存在,下面简单叙述一下其原理,最后由程序的运行结果来说明两者的差距。
欧拉筛法的思想就是不做无用功,原本Eratosthenes筛法的第一重循环是用来找素数,然后把素数的倍数标记,而欧拉筛法换了一个角度,第一位是找素数没有问题,但是标记的时候用的是所有数(合数素数都能用来做标记)来标记,并加上了一句特判来跳出循环,什么意思呢?对于当前的一个数i,欧拉筛法把从2, 3, 5....到小于 i 的最大素数分别和 i 相乘得到的数标记成合数。并且过程中一旦发现 i % (p[j]) == 0,则跳出循环,有什么用呢?这样做保证了每个合数只被他的最小素因子筛到一次,为什么?这么想,我们每次想要标记的数是 i * p[j], 有因子p[j], 如果 i 没有因子 p[j] 则标记(这是第一次用p[j]标记的时候干的事),易于发现当 i 中 p[j] 的指数为x时,i 是被 (i / p[j]) 这个数 * p[j] 时标记的,只标记了一次。
下面是两者对比的代码,网上看到说由于欧拉筛法的特判有取模运算,所以在数据小的时候不如Eratosthenes筛法快,亲测了一下,根本感觉不到差距,反而是数据变大以后,两者差距变得越来越明显,从消耗的时间上可以看出。(当然我在其中加了求欧拉函数的操作)。欧拉筛法的思想很巧妙,特别是应用在求一些积性函数的时候会比普通筛法更快,比如欧拉函数。
——Lerence1201
时间复杂度O(kn),k为常数,很小,几乎忽略,线性时间算法。
第三章:最终章
米勒拉宾质数检验
首先,让我们来看看跟素数有关的费马定理:
如果 p 是素数,则对于所有整数 a,a p ≡ a (mod p)。
根据上述的费马定理,每当 p 是素数以及 a 不是 p 的倍数时,我们有 a p - 1 ≡ 1 (mod p) 。而且有有效的方法计算 a n - 1 mod n ,且只需要 O(log n) 个模 n 乘法运算。因此我们可以确定,当这个关系不成立时,n 不是素数。对于一个给定的数的非素性来说,费马定理是一个强有力的检验。当 n 不是素数时,总是有可能来求 a < n 的一个值,使得 a n - 1 ≠ 1 (mod n) 。事实上,经验证明,这样一个值几乎总能非常快地求出。有某些稀少的 n 值,它们经常使得 a n - 1 ≡ 1 (mod n) ,但在此情况下,n 有小于 n 1/3 的因子。
我没有找到确定一个很大的数是否素数的有效的算法。但是 Miller-Rabin primatlity test 算法能够以很高的概率来检验一个很大的数是否素数。
具体可google一下,时间复杂度O(n),能够非常快地判断出是否为质数,但有一定概率,为3/4判断对,2147483647内,如果选2、5、7,则都可以判断出来。