在OI与密码学等各个方面,我们经常会遇到需要判断素数的情况。这个问题看似简单,实则不然。判素就像是排序,只会快排是不能走遍天下的,想要成为一名神犇,就需要接触更多的算法。

  在OI与密码学等各个方面,我们经常会遇到需要判断素数的情况。这个问题看似简单,实则不然。判素就像是排序,只会快排是不能走遍天下的,想要成为一名神犇,就需要接触更多的算法。

 

一:什么是素数

  素数,也可以叫做质数。如果一个大于1的自然数,除去1和他本身,不能被其他数字整除,那么他就是一个素数。任何一个大于1的自然数,要么是素数,要么是可以写做一堆素数相乘。

 

二:素数的性质

(1)质数p的约数只有两个:1和p。

(2)初等数学基本定理:任一大于1的自然数,要么本身是质数,要么可以分解为几个质数之积,且这种分解是唯一的。

(3)质数的个数是无限的。

(4)质数的个数公式f(n) 是不减函数。

(5)若n为正整数,在n2 到(n+1)2之间至少有一个质数。

(6)若n为大于或等于2的正整数,在n到n! 之间至少有一个质数。

(7)若质数p为不超过n(n≥ 4)的最大质数,则p>n/2。

(8)如果P是素数,a是小于P的正整数,那么a^(p-1) mod p = 1(一会要用到,顺便吐槽博客编辑器真的好难用)。

  ……

 

三:判素方法

 

  I:埃拉托斯特尼筛法

的所有素数的倍数剔除,剩下的就是素数。

  这个办法的原理很好理解,它就是利用了素数的定义,把所有素数的倍数拆掉了。但有人看了后仍然会很疑惑,我们不知道那个是素数,为什么还要用素数来筛呢?而且为什么要用不大于√n的素数?要解释第一个问题,我们需要进入到算法的实现过程中去。

  首先,2是一个素数,我们把2的倍数全部都去掉,然后往下找素数。怎么找呢?接下来第一个没被筛掉的数字3肯定是素数,这是为什么呢?因为一个合数的因数肯定比他本身要小(除去它本身),且一定可以写作比他小的质数的乘积形式,我们用3之前的素数2来进行筛,并没有筛掉3,也就是说3不能写作比他小的质数的乘积形式,那么3肯定是一个素数了。然后继续向下筛,碰到没筛掉的就认为它是素数,用它再来筛。

  然后,我们要来解释第二个问题了。

  用√n来作为界限,我们只是为了减少运算量,那么用比√n大的数字呢?当然可以;用比√n小的数字呢?当然不行。可是,为什么不行。(√n略丑,各位看官将就着看)

  我们用最大的数字n来看,假定这个数可以写作1*n,2*n/2,3*N/3,.......,√n*√n。仔细看这一系列的式子,我们会发现,乘号左边的数字一直在变大,而右边的数字一直在变小,这个原因我想应该不用我去解释了。这个有规律的变化会一直持续的√n*√n,两边数字一样大了,如果再继续变化,又会重复原来的式子了。而比n小的数字x,他开方后也就更小,所以我们只需要求到√n就行了。当然,这个√n的小数部分是没有用的,我们应当将它向下取整。

 

伪代码

function Eratosthenes;
  begin
   for i:=2 to sqrt(n) do
    if i为素数 then
    筛选
  end;

 

 

 II.试除法

  顾名思义,这个办法就是把比n小的数字统统除一遍,如果没有一个数字可以整除n,那么n就是一个素数了。当然这么变态的方法肯定只适用于,数字少加上数字小的情况。如果一台电脑1s可以计算10^9次,那么我们1S中最大可以判断一个10^18的数字,当然这是加了优化的。

  什么优化呢?从筛法中,我已经说过了,判断质数判断到√n就可以了,这样时间复杂度瞬间小了啊。

  如果这样你还嫌太慢,那么你就只能随机找几个比他小的数字试一试了,这样很容易把某些合数当成质数的。

伪代码

function sc;
    for i:=2 to sqrt(n) do
    begin
    f=true;
    if n mod i=0 then
      f=false            
    if f then
      n为质数
      else
      n为合数
    end;

 

 

  III.费马素性检验

  前面已经提到试除法只能适用于数字少加上数字小的情况,那么如果数字多加上数字大怎么办,那我们只能祭出恐怖的费马素性检验了。

  费马素性检验基于费马小定理,即上面我提到的最后一条性质。我们先来证明一下。

的既约剩余系p={1,2,3,...,p-1}

,由引理3可得A = {a,2a,3a,...,(p-1)a}

  也是p的一个既约剩余系。

  由既约剩余系的性质,1x2x3x...x(p-1)≡a·2a·3a·...·(p-1)a (mod p)

  即(p-1)! ≡ (p-1)!·ap-1(mod p)

  同余式两边可约去(p-1)!,得到ap-1≡1 (mod p)

费马小定理,是不是很神奇呢。(以上内容来自百度百科)

  这个定理的逆命题不是正确的哦,比如虽然2的340次方除以341余1,但341=11*31,我们将这些不是素数还满足2^(p-1) mod p = 1数字称之为伪素数,实际上这类数字并不多。

  不满足一定是合数,满足可能是素数,这样我们制作一张伪素数表就可以了。那么有多少伪素数呢,已知在十亿以内一共有50847534个素数,而伪素数有5594个,判断出错可能性为0.00011,也许我们需要改进一下算法。

  我们把a换成3再重新试一试,某些伪素数满足2^(p-1) mod p = 1,但不满足3^(p-1) mod p = 1。到这里发生了一些小问题,满足2^(p-1) mod p = 1的叫做伪素数,满足3^(p-1) mod p = 1的难道就不是了么?当然是,我们把伪素数的定义改一下,把满足a^(p-1) mod p = 1的伪素数称为以a为底的伪素数。十亿以内同时满足2^(p-1) mod p = 1和3^(p-1) mod p = 1的数字只有1272个,是不是突然就少了好多。然后我们发现,测试越多越好,那我们就随便抽出几个来测试一下吧,测试越多,出错几率越小。

  那么我们把所有小于n的a都测试一遍,肯定能排除伪素数了吧。╮(╯▽╰)╭,你还真别这么想,Robert Daniel Carmichael在1910年找到了这么一个数,而且这个数连1000都不到,他只有561这么大,而且十亿中有600个这样的数,天哪。

 

 IV.Miller Rabin算法

  我先介绍一下Miller Rabin这个人,哦不,这两个人。Gary Lee Miller是卡内基梅斯大学的计算机系教授,他首先针对判素提出了基于广义黎曼猜想的确定性算法,可是广义黎曼猜想并没有被证明过,于是没有人可以证明他的办法是正确的。后来在耶路撒冷希伯来大学,出现了一名名曰Micheal O. Rabin的教授,在75年,对这种办法做出修改,使其不再基于广义黎曼猜想,实际上,Dr. Rabin早在53年就已经拿到了图灵奖。

  该算法基于以下定理:如果p是素数,x是小于p的正整数,且x^2 mod p = 1,那么要么x=1,要么x=p-1。(二次试探定理)

  这个真心是好证到爆的,我就不证明了,各位看官自己想想吧。

  然后我们需要把这个东西加到fermat里,把它俩搅拌成同一个东西。

  搅拌的过程十分简单,只需要在原算法里加入这个二次试探定理就好了,在测试a^(p-1) mod p = 1的同时,我们还要测试a^2 mod p是否等于1,这个小优化多耗费的时间极少,而取得的效果却极大。

  实际上只用fermat小定理就可以叫做Miller Rabin算法了,但是我们还是更习惯于把两个东西搅拌在一起。