素数判断算法
一. 朴素判断素数
1. 简单算法
bool isprime (int n) {
if (n < 2) return 0;
for (int i=2; i*i <= n; ++i)
if (n%i == 0)
return false;
return true;
}
2. 简单算法改进版
bool isprime (int n) {
if (n < 2) return false;
if (n == 2) return true;
for (int i=3; i*i<n; i+=2)
if (n%i == 0) return false;
return true;
}
时间复杂度均为o(根号n),对于一般素数判断,可以直接使用
二. 筛选法
筛素数的基本方法是用来筛选出一定范围内的素数,基本原理是利用素数p只有1和p这两个约数,并且一个数的约数一定不大于本身,其过程:
把从1开始的、某一范围内的正整数从小到大顺序排列,1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。以此类推,直至筛子为空时结束。
1. 简单版
#define MAX 10001
bool isprime[MAX];
void TheSieveofEeatosthees() {
int i, j;
for (i=2; i<MAX; ++i)
isprime[i] = true;
for (i=2; i<MAX; ++i)
if (isprime[i])
for (j=i+i; j<MAX; j+=i)
isprime[j] = false;
}
isprime[i]中true表示为素数,false表示为不是素数
算法执行完后可以在的时间内判断出MAX以内的任意数是不是素数。筛选法的实际复杂度是log(log n)
2. 进阶版
#define MAX 10001
bool isprime[MAX] = {0};
int p[MAX] = {0};
void prime (int n) {
int np=0, i, j;
for (i=2; i<=n; i++) {
if (!isprime[i]) p[np++] = i;
for (j=0; j<np && p[j]*i<=n; j++) {
isprime[p[j]*i] = true;
if (i%p[j] == 0) break;
}
}
}
“if (i%p[j] == 0) break;”该句使得任何一个合数只能被它最小的质因数标记过一次,再一次进行优化。所以整个算法是线性的。但是空间复杂度非常巨大。
三. 费马素数测试
1. 费马小定理:
有N为任意正整数,P为素数,且N不能被P整除(显然N和P互质),
则有:
N^P%P = N (即:N的P次方除以P的余数是N)
公式变形为:
(N^(P-1))%P = 1
公式推理:
N^P%P = N
(N^P-N)%P = 0
(N*(N^(P-1)-1))%P = 0
因此N*(N^(P-1)-1)是N和P的公倍数,又因为N和P互质,所以
N*(N^(P-1)-1 = M*N*P (N*P为N和P的最小公倍数)
N^(P-1)-1 = M*P
又M为整数,所以
(N^(P-1)-1)%P = 0
N^(P-1)%P = 1
2. 积模分解公式
如果有X%Z=0,则有(X+Y)%Z=Y%Z
设有X、Y、Z三个正整数,则必有(X*Y)%Z=((X%Z)*(Y%Z))%Z
假设: X=Z*I+A (1)
Y=Z*J+B (2)
将(1)(2)式代入(X*Y)%Z得:((Z*I+A)*(Z*J+B))%Z
=(Z*(Z*I*J+I*B+J*A)+A*B)%Z
=(A*B)%Z
=((X%Z)*(Y%Z))%Z
3. 快速计算乘方的算法
若计算2^13,传统方法需要进行12次乘法。
若将2*2的结果保存起来:4*4*4*4*4*4*2
再把4*4的结果保存起来:16*16*16*2
一共5次运算,分别是2*2、4*4、16*16*2
unsigned Power (unsigned n, unsigned p) { //计算n的p次方
unsigned odd = 1; //odd用来计算“剩下的”乘积
while (p > 1) { //一直计算到指数小于或等于1
if ((p&1) != 0) //判断p是否为奇数,偶数的最低位必为0
odd *= n; //若p为奇数,则把“剩下的”乘起来
n *= n; //主体乘方
p /= 2; //指数除以2
}
return n*odd; //最后把主体和“剩下的”乘起来作为结果
}
4. 利用费马定理判断素数
算法思路:
对于N,从素数表中取出任意的素数对其进行费马测试,如果取了很多个素数,N仍未测试失败,则认为N是素数。测试次数越多越准确,一般50次足够了。
int Montgomery (int n, int p, int m) { //蒙格马利快速幂模算法
int k = 1; //快速计算(n^e)%m的值,逐次平方法
n %= m;
while (p != 1) {
if ((p&1) != 0) {
k = (k*n)%m;
n = (n*n)%m;
p >>= 1;
}
}
return (n*k)%m;
}
bool *isprime;
int *p;
int np = 0;
void prime(int n) { //生成一个我们想要的小范围的素数表用于素数判断
*isprime = (bool *)malloc(n*sizeof(bool));
*p = (int *)malloc(n*sizeof(int));
for (int i=2; i<=n; i++) {
if (!isprime[i]) n[np++] = i;
for (int j=0; j<np && p[j]*i<=n; j++) {
isprime[p[j]*i] = 1;
if (i%p[j] == 0) break;
}
}
}
bool isprime(unsigned n) {
if (n < 2)
throw 0;
for (int i=0; i<np; ++i)
if (Montgomery(p[i],n-1,n))
return false;
return true;
}
费马定理是素数的必要条件而非充分条件,因此判断出的数不一定就是素数
四. 米勒拉宾素性测试
<span style="font-size:14px;font-weight: normal;">long long qpow(int a, int b, int r) { //快速幂
long long ans=1, buff=a;
while (b) {
if (b&1) ans = (ans*buff)%r;
buff = (buff*buff)%r;
b>>=1;
}
return ans;
}
bool Miller_Rabbin(int n, int a) { //米勒拉宾素数测试
int r=0, s=n-1, j;
if (!(n%a)) return false;
while(!(s&1)) {
s >>= 1;
r++;
}
long long k = qpow(a, s, n);
if (k == 1)
return true;
for (j=0; j<r; j++, k=k*k%n)
if (k == n-1)
return true;
return false;
}
bool isprime(int n) { //判断是否是素数
int tab[] = {2, 3, 5, 7};
for (int i=0; i<4; i++) {
if (n == tab[i])
return true;
if (!Miller_Rabbin(n, tab[i]))
return false;
}
return true;
}</span>