判斷一個數是否是素數的 n 多種方法
阿新 • • 發佈:2021-06-11
素數:只能除以1和自身的數(需要大於1)就是素數,又叫質數。
方法
- 從2開始一直除到該數之前的那個自然數,如果有能被整除的就不是素數
bool isPrime(int n) {
if (n == 1) {
return false;
}
if (n == 2) {
return true;
}
for (int i = 2; i <n ; ++i) {
if (n % i== 0) {
return false;
}
}
return true;
}
- 假設 d 為 n 的約數,那麼 n/d 也是 n 的約數,因為有: n = d * (n/d),即:min(d, n/d) <= sqrt(n),因此我們只要檢測 2 ~ sqrt(n) 範圍內所有的整數就可以了,
bool isPrime(int n) {
if (n == 1) {
return false;
}
if (n == 2) {
return true;
}
for (int i = 2; i <= sqrt(n); i++) {
if (n % i== 0) {
return false;
}
}
return true;
}
- 如果不能被 2 整除,那麼 n 即為奇數,判斷其是否能被奇數整除就好了,因此迴圈的範圍就可以減小為 [3, sqrt(n)]內的奇數
bool isPrime(int n) { if (n == 2) { // 2 是素數 return true; } if (n != 2 && n % 2 == 0) { // 如果能被 2 整除,證明 n 不是素數(2 本身除外) return false; } // 如果不能被 2 整除,那麼 n 即為奇數,判斷其是否能被奇數整除,前面我們已經處理了 2 和 2 的倍數,因此這個迴圈的範圍就是 [3, sqrt(n)]內的奇數 for (int i = 3; i <= sqrt(n); i += 2) { //i <= sqrt(n); 可以換成i*i<n。為什麼加2,因為能被偶數整除的,一定能被2整除。上面已經討論過了 if (n % i == 0) { return false; } } }
上面的其實是之前我寫過的,用的c++,迴文字串判斷、素數的判斷、求最大公約數最小公倍數(c++)我懶得改了,下面的會用一些 python 的
- 既然可以通過除以 2 這個素數來先排除一半 ,那麼我已可以除以其他的3、5、7、11來再排除一些嘛
def isPrime(n): if n <= 1: return False elif n % 2 == 0 and n != 2: # 去除能被2整除的 不包括2 return False elif n % 3 == 0 and n != 3: # 去除能被3整除的 不包括3 return False elif n % 5 == 0 and n != 5: # 去除能被5整除的 不包括5 return False elif n % 7 == 0 and n != 7: # 去除能被7整除的 不包括7 return False else: for i in range(3, int(math.sqrt(n)) + 1, 2): # 這裡 +1是因為range不含end值,是個左閉右開的(start,end) if n % i == 0: return False return True
- 6倍原理。所有的素數都在6的倍數的左側或者右側,也即
num % 6 == 1 || num % 6 == 5
,不滿足者不是素數,滿足者繼續驗證,步驟如下
- 對於1,2,3三個數字特殊處理
- 所有的素數都在6的倍數的左側或者右側,也即
num % 6 == 1 || num % 6 == 5
,不滿足者不是素數,滿足者繼續驗證- 計算sqrt(num),從5,11,17,23...開始驗證,每次驗證i和i+2,一旦整除,不是素數多次篩的時候可以打表。
注意: 只有6的倍數附近的數才有可能是質數。為什麼說可能是質數的,我舉個反例,25,35,49。。。
bool isPrime(int n) {
if (n == 2 || n == 3) { // 先排除2、3這兩個特例
return true;
}
if (n % 6 != 1 && n % 6 != 5) { //如果不在6的倍數附近,肯定不是素數
return false;
}
for (int i = 5; i <= sqrt(n); i += 6) { //對6倍數附近的數進行判斷
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}
- 費馬素性檢驗,基於費馬小定理。缺點:在 232以內,這種方法的準確性尚可接受,但到了 264級別,出錯的概率就太高了。
假如a是一個整數,p是一個質數,那麼 ap - a 是p 的倍數
那麼反過來呢?如果存在某個 ap - a 是p 的倍數,是否就能判定 p 是素數呢?並不行,
例如 2341 - 2 是341 的倍數,但 341 是合數;再比如 31105 - 3 是 1105的倍數,但 1105 是合數;這類合數被稱為費馬偽素數。
不過幸好,一個合數是費馬偽素數的概率並不是很高。所以我們可以多測試幾個 a,只要有一個a不滿足,就說明即可說明 p 不是素數。
都成立,那它就很可能是素數了。注意,是很可能,不是一定是
import random
def isprime(n,k=128): // 預設測試128 個a
if n<2:
return False
for _ in range(k):
a = random.randrange(1,n)
if pow(a,n-1,n)!=1:
return False
return True
這個東西我也不是很熟啊,所以建議配合Wiki看,Wiki比我講的好的多
- 米勒-羅賓(Miller-Robin)素性檢驗。解決了費馬素性檢驗錯誤率有點高的情況,
#include <iostream>
using namespace std;
typedef long long ll;
ll qpow(ll a, ll n, ll p) // 快速冪
{
ll ans = 1;
while (n){
if (n & 1)
ans = (__int128)ans * a % p; // 注意!中間結果可能溢位,需要使用__int128過渡
a = (__int128)a * a % p;
n >>= 1;
}
return ans;
}
bool is_prime(ll x)
{
if (x < 3) // 特判1,2
return x == 2;
if (x % 2 == 0) // 特判偶數
return false;
ll A[] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022}, d = x - 1, r = 0;
while (d % 2 == 0) // 算出d, r
d /= 2, ++r;
for (auto a : A){
ll v = qpow(a, d, x); // a^d
if (v <= 1 || v == x - 1) // 如果a^d≡0,說明是a是x的倍數;如果a^d≡1或-1,說明這串數接下來一定都是1,不用繼續計算
continue;
for (int i = 0; i < r; ++i){
v = (__int128)v * v % x; // 同樣使用__int128過渡
if (v == x - 1){ // 得到-1,說明接下來都是1,可以退出了
v = 1;
break;
}
if (v == 1) // 在中途而非開頭得到1,卻沒有經過-1,說明存在其他數字y≠-1滿足y^2≡1,則x一定不是奇素數
return false;
}
if (v != 1) // 檢視是不是以1結尾
return false;
}
return true;
}
int main()
{
cout<<bool(is_prime(6))<<endl;
return 0;
}
6一般不咋用了,7一般用在要測試的那個數很大很大的情況,較小的情況其實前面幾種方法基本可以了,還有其他的一些方法比如尤拉篩選、埃拉託斯特尼篩選等等,詳見 素性測試,當然,Wiki百科也有可能出錯,但是概率較低,畢竟可以自己修改 wiki 百科