聊聊如何检测素数
最近看到一則頗為有趣的新聞,說北大一名大一新生,以素數為標準選手機號,受到廣大網友膜拜。其實素數的檢測算法是很有趣的,并且會涉及到數論、概率算法等諸多內容,一直覺得素數探測算法是了解概率算法很好的入口。本文和大家簡單聊聊如何確定一個數是素數。
素數
素數的定義
素數是這樣被定義的:
一個大于1的整數,如果不能被除1和它本身外的其它正整數整除,則是素數(又稱質數)。
與素數相關的定義還有合數:
一個大于1的整數,如果不是素數則是合數。其中能整除這個數的正整數叫做約數,不等于1也不等于合數本身的約數叫做非平凡約數。
注意1既不是素數又不是合數。
舉幾個例子:
2是素數,因為除1和2外沒有其它正整數可以整除2。
3也是素數。
4不是素數,因為2可以整除4。
11是素數,除1和11外沒有正整數可以整除它。
15不是素數,3和5可以整除15。
素數的性質
素數有一些有趣的性質,下面不加證明的列幾條。
素數有無窮多個。
設f(n)為定義在大于1的整數集合上的函數,令f(n)的值為不大于n的素數的個數,則:
limn→∞f(n)n/lnn=1limn→∞f(n)n/ln?n=1
這個函數叫做素數分布函數,反映了素數的分布律。換言之,可以認為大于1的前n個正整數中,素數的個數大約是n/lnnn/ln?n。
檢測素數
所謂素數檢測,就是給定任意一個大于1的整數,判斷這個數是否素數。
因子檢測法
最直觀的素數檢測算法就是因子檢測法。說白了,就是從2到n-1一個個拿來試,看能否整除n,如果有能整除的(找到一個因子),則輸出不是質數,否則則認為n為質數。當然,實際上不需要試探到n-1,只要到n√n就好了,原因如下:
設n=a×bn=a×b,且a、b均為n的非平凡約數,顯然a>n√a>n和b>n√b>n不可能同時成立,因為同時成立時a*b就會大于n,所以,如果n存在非平凡約數,則至少有一個小于等于n√n,因此只要遍歷到n√n就可以了。
因子檢測法的實現代碼如下(python):
做幾個測試:
很明顯,因子檢測法的時間復雜度為O(n√)O(n),一般來看,這個時間復雜度已經很不錯了,不過對于超級大的數(例如RSA加密中找幾百位的素數是很正常的),這個復雜度還是太大了。
例如對于下面的整數:
6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554 977296311391480858037121987999716643812574028291115057151
哪位壯士可以試試用因子檢測法檢測這個數是質數還是合數,估計這輩子結果是出不來了,下輩子也懸。所以需要更高效的素數檢測算法。
費馬檢測
坦白說,對于大素數的探測,目前并沒有非常有效的確定性算法。不過借助費馬定理,可以構造一種有效的概率算法來進行素數探測。
費馬定理
首先看一下什么是費馬定理。這條定理是史上最杰出的業余數學家費馬發現的一條數論中的重要定理, 這條定理可以表述為:
如果p為素數,則對任何小于p的正整數a有
ap?1≡1(mod?p)ap?1≡1(mod?p)
根據基本數理邏輯,一個命題正確,當且僅當其逆否命題正確。所以費馬定理蘊含了這樣一個事實:如果某個小于p的正整數不符合上述公式,則p一定不是素數;令人驚訝的是,費馬定理的逆命題也“幾乎正確”,也就是說如果所有小于p的正整數都符合上述公式,則p“幾乎就是一個素數”。當然,“幾乎正確”就意味著有出錯的可能,這個話題我們后續再來討論。至少從目前來看,費馬定理給我們提供了一條檢測素數的方法。
下面再通過例子說明一下費馬定理表達的意義,例如我們知道7是一個素數,則:
162636465666=1=64=729=4096=15625=46656≡1(mod?7)≡1(mod?7)≡1(mod?7)≡1(mod?7)≡1(mod?7)≡1(mod?7)16=1≡1(mod?7)26=64≡1(mod?7)36=729≡1(mod?7)46=4096≡1(mod?7)56=15625≡1(mod?7)66=46656≡1(mod?7)
其它素數可以可以用類似方法驗證,關于這個定理的嚴格證明本文不再給出。
所以可以使用如下方法進行大素數探測:選擇一個底數(例如2),對于大整數p,如果2^(p-1)與1不是模p同余數,則p一定不是素數;否則,則p很可能是一個素數。
至于出現假陽性(即合數被判定為素數)的概率,已有研究表明,隨著整數趨向于無窮,這個概率趨向于零,在以2為底的情況下,512位整數碰到假陽性的概率為1/10^20,而在1024位整數中,碰到假陽性的概率為1/10^41。因此如果使用此法檢測充分大的數,碰到錯誤的可能性微乎其微。
模冪的快速算法
僅有費馬定理還不能寫檢測算法,因為對于大整數p來說,a^(p - 1) (mod p)不是一個容易計算的數字,例如上上面那個超大整數來說,直接計算2的那么多次冪真是要死人了,其效果一點不比因子分解法好。所以尋找一種更有效的取模冪算法。通常來說,重復平方法是一個不錯的選擇。下面通過例子介紹一下這個方法。
假設現在要求2的10次方,一種方法當然是將10個2連乘,不過還有這樣一種計算方法:
10的二進制表示是1010,因此:
210=2[1010]2210=2[1010]2
現初始化結果d=2^0=1,我們希望通過乘上某些數變換到2^10,變換序列如下:
2[0]22[0]22[1]22[10]22[101]2×2[0]2×2[1]2×2[10]2×2[101]2×2×2=2[1]2=2[10]2=2[101]2=2[1010]22[0]22[0]2×2[0]2×2=2[1]22[1]2×2[1]2=2[10]22[10]2×2[10]2×2=2[101]22[101]2×2[101]2=2[1010]2
可以看到這樣一個規律:對中間結果d自身進行平方,等于在二進制指數的尾部“生出”一個0;對中間結果d自身進行平方再乘以底數,等于在二進制指數尾部“生出”一個1??窟@樣不斷讓指數“生長”,就可以構造出冪。如果在每次運算時取模,就可以得到模冪了,下面是這個算法的python實現:
這個算法的復雜度正比于a、p和m中位數最多的數的二進制位數,要遠遠低于樸素的模冪求解法。
例如,下面的代碼在我的機器上瞬間可以完成:
而用直觀方法計算如此大指數的冪基本是不可能的。
費馬檢測的實現
有了上的鋪墊,下面可以實現費馬檢測了:
以下是一些測試:
需要注意的是,倒數第二個結果實際是錯的,因為561可以分解為3和187。
相對來說,因子分解法適合比較小的數的探測,可以給出準確的結論,但是對于大整數效率不可接受,例如上面最后一個超大整數,因子分解法基本不可行;費馬測試當給出否定結論時,是準確的,但是肯定結論有可能是錯誤的,對于大整數的效率很高,并且誤判率隨著整數的增大而降低。
Miller-Rabin檢測
上文說,費馬檢測失誤的概率隨著整數不斷增大而趨向于0,看似是對大素數檢測很好的算法。那么我們考慮另外一個問題:如果一個數p是合數,a是小于p的正整數且a不滿足費馬定理公式,那么a叫做p是合數的一個證據,問題是,對于任意一個合數p,是否總存在證據?
答案是否定的。例如561這個數,可以分解為3乘以187,但是如果你試過會發現所有小于561的正整數均符合費馬定理公式。這就意味著,費馬檢測對于561是完全失效的。類似561這樣是合數但是可以完全欺騙費馬檢測的數叫做Carmichael數。Carmichael數雖然密度不大(前10億個正整數中約600個),但是已經被證明有無窮多個。Carmichael數的存在迫使需要一種更強的檢測條件配合單純費馬檢測使用,其中Miller-Rabin檢測是目前應用比較廣泛的一種。
Miller-Rabin檢測依賴以下定理:
如果p是素數,x是小于p的正整數,且x^2 = 1 mod p,則x要么為1,要么為p-1。
簡單證明:如果x^2 = 1 mod p,則p整除x^2 - 1,即整除(x+1)(x-1),由于p是素數,所以p要么整除x+1,要么整除x-1,前者則x為p-1,后者則x為1。
以上定理說明,如果對于任意一個小于p的正整數x,發現1(模p)的非平凡平方根存在,則說明p是合數。
對于p-1,我們總可以將其表示為u2^t,其中u是奇數,t是正整數。此時:
ap?1=au2t=(au)2tap?1=au2t=(au)2t
也就是可以通過先算出a^u,然后經過連續t次平方計算出a^(p-1),并且,在任意一次平方時發現了非平凡平方根,則斷定p是合數。
例如,560 = 35 * 2^4,所以可設u=35,t=4:
23526321662672mod561=263mod561=166mod561=67mod561=1235mod561=2632632mod561=1661662mod561=67672mod561=1
由于找到了一個非平凡平方根67,所以可以斷言561是合數。因此2就成為了561是合數的一個證據。
一般的,Miller-Rabin算法的python實現如下:
其中miller_rabin_witness用于確認a是否為p為合數的證據,prime_test_miller_rabin共探測k次,每次隨機產生一個1至p-1間的整數。只要有一次發現p為合數的證據就認為p為合數,否則認為p為素數。一些測試:
Miller-Rabin檢測也同樣存在假陽性的問題,但是與費馬檢測不同,MR檢測的正確概率不依賴被檢測數p(排除了Carmichael數失效問題),而僅依賴于檢測次數。已經證明,如果一個數p為合數,那么Miller-Rabin檢測的證據數量不少于比其小的正整數的3/4,換言之,k次檢測后得到錯誤結果的概率為(1/4)^k,例如上面最后一個大整數,Miller-Rabin檢測認為其實素數,我設k為50,也就是說它被誤認為素數的概率為(1/4)^50。這個概率有多小呢,小到你不可想象。直觀來說,大約等于一個人連續中得5次雙色球頭獎的概率。
參考文獻
[1]?算法導論
[2]?http://www.wikipedia.org/
[3]?http://www.matrix67.com/blog/archives/234
[4]?http://www.bigprimes.net/
from:?http://blog.codinglabs.org/articles/prime-test.html
總結
- 上一篇: 为什么算法渐进复杂度中对数的底数总为2
- 下一篇: 闭包漫谈(从抽象代数及函数式编程角度)