噠噠噠!掌握一種心理學的學習概念,人的認知是不斷成長的,不必要因爲一時的失意,而否定您。
數學不好,也沒關係,一起成長。早在出生起,我們每天笨拙的咿呀咿呀學漢語與走路,我們最終都學會了。爲什麼,因爲那時候的我們,不會在意別人的眼光,不會怕做的不好就不去練習。
來吧,短暫人生路,讓我們一起擁有手眼通天的膽量!更重要的是,如果您可以認真看完這裏的,數論可以算入門了,不過我會實時更新,常回來看看哈,RSA加密數論部分更新ing。
《目錄》
數論基礎前置知識:
整除:
- 如果 a 被 p 整除,a 是 p 的倍數,記爲:p | a ,如 3 | 9 , 1 | 9, 9 | 9; 其中 1、9 是9的頻繁因子 , 3是9的非頻繁因子。
傳遞性:
- 如果 a 是 p 的倍數,那麼 a 的倍數也是 p 的倍數,如 2 | 4 , 4 | 12 . 故有 2 | 12,記爲 a | b, b | c, 則 a | c。
整數的質因數分解 :
一個正整數拆分爲質數的冪的乘積,如 24 = 2 * 2 * 2 * 3 = * , 17 =
質因數分解採用短除法【小學知識】
唯一分解定理 :【數論基石,也叫算術基本定理】
- 正整數的質因數分解是唯一的 , n = * * ··· , 是質數,如上 24 和 17。
質因數分解看除法 :
如 ,12 = * , 180 = * * , 那麼;
易發現,正整數的除法,即把倆個數的質因數分解,接着每個質數的質數相減。
質因數分解看整除 :
m | n , 顯然 是當且僅當 n ➗ m 是整數。[ 4 | 12 => 12 ➗ 4 ]
解釋一下,如果 ,那麼算出來的就是一個分數,不是整數。如,。
學了這些,我們就可用這個代替整除,把 n 和 m 質因數分解,然後對比指數。
如果 n 每一個質數的指數都大於等於m 的對應指數,那麼 n 就是 m 的倍數 !!
編程,只學理論,不敲代碼;都是耍流氓
題目:20 * 30 * 50 / 60 * 70 / 80 * 90 和 7 是否 相等。
3 min ...
那麼,暴力高精度 O() , n 是數字長度,可是太慢。用 double 計算,如果有上萬次運算,精度無法保證。
需要指出,我們可以採用 剛剛學習的質因數分解方法解,您看最大數 90,90以內的素數只有 24 個。[利用容斥原理也可以得到素數個數 , , , 小於9的素數只有4個, 2、3、5、7,解出來]
我們把每個整數分解成質數的冪,然後存儲這些指數。
乘法 : 指數相加 [如果是加法,那隻能高精度了]
除法 : 指數相減 [如果是減法,那隻能高精度了]
判斷是否相等 : 判斷每一個指數是否相等 [唯一分解定理]
#include <iostream> using namespace std; const int n = 24; int prime[n] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89}; struct Hi { int flag, r[n]; // r[] 存儲每個質數的指數 friend Hi operator * (Hi a, Hi b) // 指數加法 { Hi ans; for( size_t i = 0; i < n; i ++ ) ans.r[i] = a.r[i] + b.r[i]; return ans; } friend bool operator != (Hi a, Hi b) // 判斷指數是否相等 { for( size_t i = 0; i < n; i ++ ) if( a.r[i] != b.r[i] ) return true; return false; } }; int main(void) { // 同九義汝何秀 return 0; }
NO. 1 約數篇[因數]
- 整數 a(4) 能被整數 b(2) 整除,a(4) 叫做 b(2) 的倍數,b(2) 就叫做 a(4) 的約數。
- 約數個數是有限的,記爲 d(n)
(自然數是這樣 - 從0開始的整數)- 所有數的約數都包含 自身 和 (或) 1
借 圖.jpg 獻計:
請看,n = 1 , d(n) = 1 => 1 的約數有且只有一個即 1;
n = 10 , d(n) = 4 => 10 的約數共 4 個即 1、2、5、10;
其餘數同。
那麼想一想,我們怎麼把這個表格打出來?? 莫急莫急,要成長,就需要建立新的神經元連接,數學與思考正是良計!!
#include <stdio.h> #define N 1000 int d[N]; // 默認爲 0 ,存儲在 .data 段 // 計算 [1,n] 的約數個數 O(n log n) void fews(int n){ for( int i = 1; i <= n; i ++ ) { for( int j = i; j <= n; j += i ) // 枚舉 i 的所有倍數,換一個角度想,本來是約數 d[j] ++; } } // 【人之千慮,比有一得 ~ ,您的方法是怎麼樣的,推敲一遍】 int main(void) { int cnt = 0; int n; scanf("%d",&n); fews(n); for( int i = 1; i <= N; i ++ ) if(d[i]) cnt++,printf("%-3d%c",d[i],cnt%10 ? ' ' : '\n'); printf("\n[1,n] 每個數約數表"); return 0; }
下面選修 ~
數論的基石,唯一分解定理解析約數個數。
一個數 ··· ,且 [a , b , c] > 0, 如
這裏指數一一對應, a 可取 {0,1,2} , b 可取 {0,1,2} , c 可取 {0,1} , (a,b,c) 一共3 * 3 * 2 = 18種取法,每種取法對應一個約數。
e.g. a , b , c 都取 1 時, ,而 30 就是 180 的一個約數。
所以,如果一個數 n 可以分解 爲 , 則 ta 的約數有
e.g. 180 , , d(n) = (2+1) * (2+1) * (1+1) = 18 種。
OK,歡迎在評論區或者郵件分享您的代碼哦·簡單的理解了約數和倍數,接下來學習的素數,是數論裏的?,非常重要。
許許多多的理論與問題都圍繞素數建立,如
- 被用於電影,安全碼,謎題,甚至是大學教授的孤獨主題
- 哥德巴赫猜想
- 是否有無窮多個梅森素數
- 第六代加密算法 - RSA非對稱加密法 [手機支付加密、網銀加密]
- 斐波那契數列是否存在無窮多個素數
- 費馬大定理是否成立
- 黎曼猜想
- 孿生素數是否有無窮多組 [間隔爲2,如 (3,5) , ... ,(71,73) , ...]
- 中國剩餘定理 [韓信點兵、物不知數]
- 自然界多數生物的生命週期(年)是素數,最大限度減少碰見天敵的機會[蟬昆蟲每隔13或17年從地面發芽]
- 機械中齒輪的齒數是素數,以增加倆⚙️倆個相同齒相遇齧合次數的最小公倍數 [提高了耐用度, 減少故障]
- ... ...
可見素數不是一般的重要,上面列舉出來的,程序員是要學習與理解的,只爲優質的算法效率足以。
P.S. 我會將上面的問題,都解出來,不過,大概需要一些時間。star ~
什麼是素數
數學定義: 素數, 又叫質數, 是指在一個大於 1 的自然數中, 除了1 和 自身外, 無法被其他自然數整除的數。
p.s. 1 、0, 即非素數又非合數,素數也是 "不可分割數" 。
- Primes是所有數字的基石。就像在化學中一樣,瞭解材料的化學結構有助於理解和預測其性質。
- Primes具有特殊屬性,如難以確定(是的,即使是困難也可能是一個積極的特徵)。這些屬性可用於加密,循環以及查看其他數字如何相乘。
小學生都明白的概念,可是,從古至今,多少數學家都想能明白素數的排列的規則,卻找不到其具體的分佈的規律,只知道是螺旋排列,如下圖
每一個黑點都是一個素數 ~
介紹一個程序裏算法常模質數 : 19260817。在計數問題中,由於產生的數據規模非常大[高精度問題],這時候要規定一個範圍,通常情況是模一個素數。大部分情況 選 19260817 就好,大小合適,也是質數。
目前我知道最大的素數是 2017年12月全球合作項目 "互聯網梅森素數搜索" 中由 現年51歲的田納西州的電氣工程師Jonthan Pace 在自己電腦上,發現了M77232917,即 2的77232917次方 - 1。嘖嘖,不愧是電氣工程師,電腦配置肯定槓槓的 ~
素數驗證方法:
方法一: 枚舉 2 ~ n-1, 如果 n % i == 0, 則 n 爲合數
代碼略 ... ... O(n²);
方法二: 優化方法一, 若一個數是合數, 則必有一個小於等於ta的平方根的因數 O(n√n)
比如, n = 15, n 的素數有 1、3、5, 除數5因爲再 n 被3整除時, 其商是5, 也就表示 n 能被 5 整除。
#include <stdio.h>
#include <math.h>
bool is_prime(size_t n)
{
size_t qn = sqrt(n);
for( int i = 2 ; i <= qn; i ++ )
if( qn % i == 0 )
return false;
return true;
}
int main(void)
{
printf("%d\n",is_prime(1));
return 0;
}
加油加油 ~
方法三: 埃拉托色尼篩 O( n log logn )
一個合數可以被一個不超過 ta 的平方根的素數整除,如果,爲找出不超過 100 的素數的個數,首先注意不超過 100 的合數一定有一個不超過 10 的素因子。由於小於 10 的素數只有 2 、3、5、 7,因此不超過 100 的素數就是這 4 個素數以及那些大於 1 和不超過 100 且不被 2、3、5、7 整除的正整數。
把 n 個自然數按次數排序,可用計數數組。
step-0: 篩去 0、1 , 先算出 根號n 的長度,就不用每次循環 i*i 或者 sqrt(n) [優化步驟, 可省略]
step-1: 篩去 合數, 減少一半時間
step-2: 篩去 2的倍數
step-3: 篩去上一步沒篩的第一個數(3)的倍數
step-4: 篩去上一步沒篩的第一個數(5)的倍數 [如果沒去合數,那麼就是4, 下面同理,所以說減一半時間]
step-5: 篩去上一步沒篩的第一個數(7)的倍數
step-6: 第一個劃掉的數字總是當前這輪所用素數的平方, 避免重複判斷[優化步驟,可省略]
step-7: 一直循環, 最後留下來的就是不超過n的素數
請看動圖
如,i 爲 24 時,i 模了 2, 3 (沒優化合數和模4) 。
#include <stdio.h> #include <math.h> #define Max 1000 int flag[Max]; // 默認爲0, 存儲在 .data void Init(int *a); void Eratosthenes( size_t n ); // O( n log(logn) ) int main(void) { size_t cnt = 0; size_t n = Max; Init(flag); Eratosthenes(n); printf("\t\t[2,%d] 素數表:>\n\n",Max); for ( int i = 2; i < Max; i++ ) if( !(flag[i]) ) cnt++, printf("%-5d%c", i, cnt % 8 ? ' ': 10); printf("\n\n%d %s %u", Max, "以內的素數共", cnt); return 0; } void Eratosthenes( size_t N ) { for (int i = 2; i * i <= N; i++) // 試除 [2,√n] { if ( !(flag[i]) ) // 篩選素數的標誌 { for (int j = i * i; j <= N; j += i) // 篩去 i * n,即 i 倍 // 每一輪篩選時, 第一個劃掉的數字總是當前這輪所用素數的平方 { flag[j]++; } } } } void Init(int *a) // 去合數,優化步驟 step-0 { for( int i = 4; i * i <= Max; i += 2 ) // 合數形式 2n if( i & 0x1 == 0 ) // 同 % 2 a[i] ++; }
歐拉篩法(線性篩) :過程同埃篩,不過只模最小質數。 加油,下面有舉例。不過,我先講清楚原理,您認真看上面的動圖,可以發現,會有重複篩除的合數。比如 30,在 i = 2 時,會篩30,i = 5 時,會篩30,所以就有了線性篩。
#include <stdio.h> #include <math.h> #define Max 1000 int flag[ Max ], cnt; // 默認爲0, 存儲在 .data void Init(int *a); int juge[ Max ]; void Euler( size_t N ); // O( n ) int main(void) { size_t cnt = 0; size_t n = Max; Init(flag); Euler(n); printf("\t\t[2,%d] 素數表:>\n\n",Max); for ( int i = 2; i < Max; i++ ) if( !(flag[i]) ) cnt++, printf("%-5d%c", i, i % 8 ? 32 : 10); printf("\n\n%d %s %u", Max, "以內的素數共", cnt); return 0; } // 改動部分 void Euler( size_t N ) { for( int i = 2; i <= N; i ++ ) // 枚舉每個數 { if( !flag[i] ) juge[cnt++] = i; // juge[] 存儲素數 for( int j = 0; i * juge[j] <= N; j ++ ) // 枚舉已經篩好的素數 { flag[i * juge[j]] ++; // 篩去合數, 任何合數都可以寫爲多個素數積 if( !( i % juge[j] ) ) break; // 保證 p[j] 是 i * p[j] 最小的素因子, 每個數只被最小質因數篩 } } } void Init(int *a) // 去合數 { for( int i = 4; i * i <= Max; i += 2 ) if( i & 0x1 == 0 ) // 於 % 2 a[i] ++; }
對於上面的程序只改動了這一個接口,這一接口相對上面的埃篩,不同的是最後取模 if(..) break 因爲ta 保證取最小素數模。
如,i 爲 24 時,i 只模了 2; 還有 3, 4, 並沒有模。
方法五,比 歐拉篩 更快。 思路,空間換時間。
倆種實現,第一種,把目前所有知道的素數按順序存儲在一個數組裏 [大數存儲要設計算法]
第二種,把一定範圍的素數按照數組下標緩存,是素數,那麼對應下標設爲0 [個人愛好]。
說到空間,這裏有一種節約空間的方法。位圖篩法求素數,代碼拿出來好好研究(在理解埃篩後補習一下位操作)。
#include <stdio.h>
#include <stdlib.h>
#define SHIFT 5 // 2的5次方, 一個int佔32位
#define MASK 0x1F // 0x0001 1111
const int N = 100;
int state[ (N>> SHIFT) + 1 ]; // 申請 100 位 (N>>SHIFT):100-(32*3) = 4 bits
int totalPrime[N]; // 記錄有多少個素數
bool isPrime( int x )
{ return !( state[x>>SHIFT] & (1<<(x & MASK) ) ); } // 獲取結果,0爲素數 接着取反 !0 = 1, 所以打印時素數變成 1
void Prime( void )
{
memset(totalPrime, 0, sizeof(totalPrime) ); // 記錄素數的數組清零
memset( state, 0, sizeof(state) ); // 狀態數組清零
state[0] |= (1<<0); // 設置 0 爲合數類(規定 1 不是素數)
state[0] |= (1<<1); // 設置 1 爲合數類(規定 1 不是素數)
for( size_t i = 2; i < N; i ++ )
{
totalPrime[i] = totalPrime[i-1]; // 因爲記錄是一個數組,所以需要加上之前統計的
if( (state[i>>SHIFT] & (1<<(i&MASK) ) ) ) // 獲得狀態,是 真 即合數
continue;
for( size_t j = i*i; j < N; j += i )
state[j>>SHIFT] |= (1<<( j&MASK) ); // 位設置
if( isPrime(i) ) // 如果是素數計數++
totalPrime[i] ++;
}
}
int main(void)
{
Prime( );
size_t cnt = 0;
for( size_t i=0; i<N; i ++ )
cnt ++,
printf("%4d : %-4d%c", i, isPrime(i), cnt&3?'\t':'\n' ); // cnt&3 == cnt%4
printf("\n 共 %d 個素數",totalPrime[cnt-1]);
return 0;
}
2019,新年快樂,祝一切順利。
當然,如果對素數有興趣。可以參加全球合作項目 "互聯網梅森素數搜索"GIMPS。1996年迄今爲止,ta 共找到了16個梅森素數。
Jonthan Pace 獲得了 3000 美金,雖然不多但很有意義。發現更多的素數,可以幫助數學家更深入的理解,素數是在上面情況下出現。
【賞金】 GIMPS:
- 誰第一個找到 1 億位的素數會有 15 萬美元的獎勵
- 誰第一個找到 10 億位的素數會有 25 萬美元的獎勵
目前,找素數都是分佈式網絡,許多的計算機聯合暴力計算完成。因爲,誰也沒有發現素數具體的分佈規律,也沒有特定的公式,所以找素數的任務交給了 GIMPS。
下面正式進入素數的世界【反證法,證明素數是無窮多個】
預先給定幾個素數,則有比他們更多的素數。
設 a, b, c 爲給定的素數, 構造一個數 A 等於 給定素數相乘變爲合數再加一
形如, A = a * b * c + 1
那麼 A 可以質因數分解嘛 ?[表示成上述的質數他們的乘積]
很顯然,我們用其中任何一個素數均不能整除 A 。
- 要麼 A 本身是一個素數,那麼 A 就不等於 a , b , c 任意一個數。
- 要麼 A 能被不同於 a , b , c 的某個數整除。
無論,上述哪種情況。必然存在一個素數 s 不同於已有的素數a, b, c ;如
- 2 * 3 * 5 + 1 = 31
- 3 * 5 * 7 + 1 = 106 = 2 * 53
也就是說,有了 n 個素數,就可以構造出第 n + 1 個素數,因此素數有無窮多個。
素數是有限的,這不是謬論嗎?
所以,質數數是無窮的!!質數存儲在螺旋數字裏的分佈也只是模糊的螺旋狀。相信,當我們知道的素數達到一定界限時,我們可以找到一條定理來描述素數分佈的規律,ta實在太重要了,對數學家,編程愛好者都是如此。
-------------------------------------------------------------------------------------------------------------------------------
威爾遜定理趣談
有人說,如果一個人不知道威爾遜定理,那麼ta的 算術 算是白學了!
誇張嗎 ~ 我覺得是有戲劇性的。那您可知道,ta爲什麼這麼說呢??事實上,威爾遜定理是萊布尼茨首先發現的,後經拉格朗日證明的;威爾遜(這裏是人名,英國法官J. Wilson)的一位擅長拍馬屁的朋友 沃潤(E. Waring) 於 1770年出版一本書裏說,這條定理是威爾遜發明, 而且對外說這條定理永遠不會被證明(因爲缺少好的符號來處理素數),這話被高斯聽見了,當時高斯也不清楚拉格朗日證明了這條定理。站在 blackboard 前 5 分鐘,就證明了這條定理。所以,高斯說,威爾遜差的不是符號,而是概念。
故事說完了,定理我們展開慢慢說。
-------------------------------------------------------------------------------------------------------------------------------
威爾遜定理 :
- 若 p 爲素數, 則 (其中 !表示 階乘),
則威爾遜逆定理也成立,即:
- 若對某一正整數 p,有 (p-1)! + 1 一定是 p 的倍數,所以再利用 sin 函數的特點,可以構造出一個素數分佈的函數曲線 這個函數值爲 0 的點都是素數所在的點。
稍稍解釋一下,如果看了不明白,那麼看一下同餘。
威爾遜定理應用很廣,例如對較大的質數 p,我們雖然無力算出 (p-1)! 的值,但卻知道 (p-1)! 被 p 除但餘數是 -1 或 p - 1。事實上,由於 (p-1)! + 1 可被 p 整除,則存在自然數 n,使得 (p-1)! + 1 = np,(p-1)! = np - 1 = (n-1)p+(p-1),所以 (p-1)! 被 p除的餘數是 -1 或者 p - 1。
我假定,數論您是零基礎的,沒關係,我們一起解讀。
先學習一個同餘的概念,選修 ~
,ta 意味着 32 - 2 = 5 * n, n =》6
若 a, b 爲倆個整數,且它們的差 a - b 能被某個自然數 m 所整除,則稱 a 就模 m 來說同餘於b,或者說 a 和 b 關於模 m 同餘,記爲 ,等同 a - b = m * k (k是整數) 。
推出: (p-1)! + 1 = p * n,OK 同餘補習好了,請看上面的紅色推導部分吧。
如果我們要判斷 p 是否是素數,那麼就看 p 滿不滿足 ,滿足就是質數。
#include <iostream> using namespace std; const int Q = 20; void factorial(long long (&fac)[Q] ) // 傳引用, 求階乘 { for( size_t i=1; i<=Q; i ++ ) { fac[i] = fac[i-1] * i; } } // 可以換 預處理階乘 或 快速乘 bool check( long long *fac, size_t p ) // p 爲一個正整數 { if( fac[p-1]%p == p-1 ) // fac 階乘數組 [公式: (p-1)! + 1 = p * n] return true; return false; } // 威爾遜逆定理 , O(n) 容易爆 long long int main(void) { long long fac[Q] = {1,1}; factorial(fac); for( size_t i=2; i<=Q; i ++ ){ if( check(fac,i) ) cout<<i<<" "; } return 0; }
其實,這定理。有點費呀,n! 階乘,對於計算機的內存都是 很傷的,按照 一位算法工程師角度說,這應該是一個無效的壞算法!
下面,是證明威爾遜定理的一種方法。[學會證明,是數學學習的一種很好的偷懶方法因爲高效]
設 p 是素數,p = 2 時,定理成立不足道。考慮到所佔空間較多,我在博客最後證明。
-------------------------------------------------------------------------------------------------------------------------------
GCD 最大公約數 與 LCM 最小公倍數
有倆個數 n and m,ta們的最大公約數 gcd(n,m) ,滿足 d | n 與 d | m 的最大值 d。
那麼 d 是多少 ? 如果從質因數分解的角度說,d的指數要儘可能大,但得小於等於 n 和 m 的最小指數[如果選 n 和 m最大指數,就是 最小公約數]。
e.g.
GCD(n,m) =》 n 和 m 質因數分解,於是每個質數取其在 n , m 中最小指數。
LCM(n,m) =》 n 和 m 質因數分解,於是每個質數取其在 n , m 中最大指數。
下面講解,gcd 證明,選修 ~
xxx
編程,只學理論,不敲代碼;都是耍流氓
倆個數的 GCD 我們用 歐幾里得算法,LCM 這裏有一個很漂亮很漂亮的性質。
GCD(n,m) * LCM(n,m) = n * m
下面證明,爲什麼是這樣的,選修 ~ [前置技能 : 質因數分解]
···
···
p.s. 實際不止 2 , 3 , 5 ,還有更多素數,不過,這個公式銜接我還不會,所以,··· 加不上。
質因數分解那裏,有說倆數相乘就是指數相加。
, are you ok , 是不是有點感覺 ?
故,
就等於 n * m , 證畢 。
, 程序寫法 : lcm = n * m / gcd(n,m);
程序裏面,您可不能這樣寫,因爲 n * m 較大時, 可會爆 long long 。想一想,要改成什麼樣呢 ???
答 : , 程序寫法 : lcm = n / gcd(n,m) * m;
求GCD我知道的實現方法:
- 暴力枚舉、
- 更相減損、
- 輾轉相除(也叫歐幾里得)
- 二進制算法(更相減損+輾轉相除優化)、
- 貝祖等式優化歐幾里得(輾轉相除)、
- 硬件+奇偶優化
代碼更新ing...
威爾遜定理證明的一種方法:
對於奇素數,令 , 則 中不會對於除數 p 同餘的倆個數; 事實上,若 ,,,則 可被 p 除盡,而 ,但 B 中數不可能被 p 除盡。 於是 B 中數被 p 除得到的餘數形成的集合 C = {1, 2, ··· , p-1}。
設 B 中被 p 除餘 1 的數 是 :
- 若 ,則 被 p 除於 a ,又 a 2,與 矛盾,故 。
- 若 ,則 ,ta 被 p 除餘 a,所以 。
- 若 ,則 ,由於 ,故應有 , 這隻能是 a = 1 或 a = p-1,此與 矛盾,故 。
由 1、2、3 得,,且 。
a 不同時, 亦相異; 若 , 且 ,因 , 而 B 中數關於 mod p 不同餘,可見 , 則 。
依次取 a 爲 2, 3, ···, ; 使 的數 分別爲 即:>
從而
2 * 3 * ··· (p-2) 1 (mod p)
又 (p-1)! -1 (mod p),則
(p-1)! = 1 * 2 * 3 * ··· * (p-2) * (p-1) -1 (mod p)
從而 (p-1)! + 1 可被 p 除盡。
若 p 是合數, p 有因數 q, 從而 (p-1)! 可被 q 整除;(p-1)! + 1 不能被 q 整除,亦不能被 p 整除。
計算機RSA算法部分數論知識
Miller_Rabin素數判定 :有一定概率會失敗,我們稱這樣判斷的素數爲 "工業素數",我在加密博客RSA算法判斷素數時便採用的 Miller_Rabin素數判定。
數論學家利用費馬小定理研究出許多種素數測試方法,Miller_Rabin素數測試算法是其中一種,其過程如下:
- 計算奇數 M,使得
- 選擇一個隨機數 A < N
- 對於任意 i < r,若 ,則 N 通過隨機數 A 的測試
- 或者,若 ,則 N 通過隨機數 A 的測試
- 讓 A 取不同的值對 N 進行 5 次測試,若全部通過則判定 N 爲素數
若 N 通過一次測試,則 N 不是素數的概率爲 25% ,若 N 通過 t 次測試,則 N 不是素數的概率爲 。事實上,t = 5 時,N 不是素數的概率爲 ,N 爲素數的概率已經大於 99.99%。實際應用中,可首先用 300 ~ 500 個小素數對 N 進行測試,以提高Miller_Rabin素數測試通過的概率,從而提高測試速度。而在生成隨機素數時,選取的隨機數最好讓 r = 0,則可省去 步驟3 的測試,進一步提高測試速度。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
const int cnt = 10;
int modular_exp( int a, int m, int n ) // 模冪運算採用平方 -- 乘降冪法算法(下面的模運算細說),RSA 加密重點, 可以改成迭代
{
if( 0 == m ) return 1;
if( 1 == m ) return ( a % n );
long long w = modular_exp( a, m>>1, n );
w = w * w % n;
if( m & 1 ) w = w * a % n;
return w;
}
/* 平方 -- 乘降冪法算法: 避免出現 大數的中間值,比如倆個 200 位素數相乘變成 40000 位的中間值 */
bool Miller_Rabin( int n )
{
if( 2 == n ) return true;
for( int i = 0; i < cnt; i ++ )
{
int a = rand( ) % ( n - 2 ) + 2;
if( modular_exp( a, n, n ) != a ) return false;
}
return true;
}
int main(void)
{
srand( time(NULL) );
int n;
scanf("%d",&n);
if( Miller_Rabin(n) )
printf("%s","Probably a prime\n"); // 質數
else
printf("%s","A composite\n"); // 合數
return 0;
}
假如隨機數選取 4 個數是 2、3、5、7,則在 以內唯一一個判斷失誤的數爲 3 215 031 751。
費馬定理 : 若 p 爲素數,a 爲正整數,且 a 和 p 互質,則 :
證明 :
首先,p-1 個整數 a, 2a, 3a, ··· (p-1)a 中沒有一個是 p 的倍數。
其次,a, 2a, 3a, ···(p-1)a 中沒有任何倆個同餘於模 p 的。
於是 : a, 2a, 3a, ···(p-1)a 對模 p 的同餘既不是0,也沒有倆個同餘相同,因此,這 p-1 個數對模 p 的同餘一定是 1, 2, 3, ···p-1 的某一種排列,即:
化簡爲 :
又由於 p 是素數,根據威爾遜定理得出 (p-1)! 和 p 互質。所以約去 (p-1)!,得到 :
其實這是一種特殊形式,一般情況下,若 p 爲素數,則 : ,這就是著名的費馬小定理。
歐拉定理 : 若 a 與 m 互質,則
費馬定理是用來闡述在素數模下,指數的同餘性質。當模是合數的時候,就要應用範圍更廣的歐拉定理了。
歐拉函數 : 對正整數 n,歐拉函數是小於等於 n 的數中與 n 互質的數的數目。
歐拉函數又稱爲 函數,如 ,因爲 1、3、5、7 均和 8 互質。
引理( 1 ):
1. 如果 n 爲某一個素數 p,則 ;
2. 如果 n 爲某一個素數 p 的冪次 ,則 : ;
3. 如果 n 爲任意倆個互質的數 a、b 的積,則 : ;
證明:
1. 顯然;
2. 因爲比 小的正整數有 個。其中,所有能被 p 整除的那些數可以表示成 ,即共有 個這樣的數能被 p 整除,從而不與 互質。所以,;
3. 在比 a * b 小的 a * b - 1 個整數中,只有那些既與 a 互質(有 個),又與 b 互質(有 個)的數,纔會與 a * b 互質。顯然滿足這種條件的數有 個。所以
比如: 要求 ,因爲40 = 5 * 8,且 5 和 8 互質,所以: 。而 。
引理( 2 ):
設 爲正整數 n 的素數冪乘積表達式,則:
證明: 由於諸素數冪互相之間是互質的,根據引理(1)得出:
比如:
素數的一般判別方法:
- 枚舉 優化爲
- 威爾遜逆定理 基本沒用,需要大數計算來擴展
- Miller_Rabin素數判定 ,RSA加密術用 C/C++ 實現需要實現大數,讓倆個素數達到 1024 位以上
- Eratosthenes埃篩 非常接近線性
- 歐拉篩(線性篩)
- 位圖篩法 時間複雜度同篩法,空間複雜度極小~
- 安利資料:判定素數
模 %
模 廣泛應用,
- 時鐘、手錶是 模 12 系統;
- 計算機的 n 位運算器是 模 n 系統,
- 算盤 也是一個模系統;
- 哈希算法本質也是模 運算,讓餘數保持在一個固定的範圍。
- 我們約定的星期幾,其實也是一個模 7(6) 系統,
- Web 編程的分頁也是 模運算
- ... ...
加法模,例如時鐘,10 - 4 = 10 - (12 - 4) = 10 + 8 = 6 (mod 12),
模 7 系統裏, 3 + 3 = 6 (mod 7), 6 + 6 = 5(mod 7),
乘法模,在模 13 系統裏, 11 * 9 = 8 (mod 13),
11 * 9 = 99 % 13 = 8,
模的另外一種表現形式 : n % p 是否整除可以寫爲 ,還可以擴展代替除法
隨時取模(加法和乘法):
- (a+b)%p = (a%p + b%p) % p ,運用這個模性質,不會讓 程序裏的 a + b 溢出;
- (a*b)%p = [ (a%p) * (b%p) ] %p,基本同上;
利用上面倆個性質,可將模冪運算轉化成模乘運算如,以計算 % n 爲例,可以分解爲( % n × a % n)%n,將 % n又可以繼續分解爲( % n × % n)%n, % n最終分解爲( % n × % n)%n , % n 即 (a % n * a % n)%n。
費馬大定理
當 n 3 時, 方程不存在自然數解。