您的位置:首页 > 其它

〖数学算法〗素性测试

2013-01-08 10:25 218 查看
所谓素性测试是检测一个数是否为素数的测试。而对素数的研究是有很长一段历史,把素数的东西写成一本书的话也许得上千页,而现代密码学又加深了科研工作者对素数的研究,今天就以输出100以内的素数的为例,讲讲素性测试的几种方法。

1.试除法

这可能是每个学过计算机的朋友都敲过的代码,原理就是从判断2到sqrt(n)或者n/2能不能整除n,若能整除就不是素数。

代码清单:

public class SuXingCeShi {

public static void main(String[] args) {

int n = 100;
SuXingCeShi robot = new SuXingCeShi();
robot.shichu(n);

}

private void shichu(int n) {

for(int i = 2; i <= n; i++){
int j;
for(j = 2; j < Math.sqrt(i); j++){
if(i % j == 0)
break;
}
if(j > Math.sqrt(i))
System.out.print(i + " ");

}

System.out.println();
}
}

2.eratosthenes筛选法(埃拉托斯特尼筛法

步骤:

1.列出2,3,4..........100

2.选取第一个没被处理过的数(第一次为2)

3.去掉以2为因数的所有数

4.如果下一个素数小于sqrt(100)跳转到第二步

5.结束

以下是来至wikipedia介绍该算法的图片,挺直观的,可以看看



代码清单:

public class SuXingCeShi {

public static void main(String[] args) {

int n = 100;
SuXingCeShi robot = new SuXingCeShi();
robot.eratosthenesShaixuan(n);

}
public void eratosthenesShaixuan(int n) {
// TODO Auto-generated method stub
int[] a = new int[n+1];//申请n+1个空间
a[0] = a[1] = -1;//剔除0,1
for(int i = 2; i < n+1; i++)
a[i] = i;

int temp = 2;
double edge = Math.sqrt(n);//边界值
while(temp <= edge){
if(a[temp] == -1){//如果a[temp]是合数
temp++;
continue;
}
//如果a[temp]是质素,去掉以它为因数的合数
for(int i = 2; i <= (a.length-1)/temp ; i++){
a[temp*i] = -1;
}
temp++;
}
for(int i = 0; i < a.length; i++){
if(a[i] != -1)
System.out.print(a[i] + " ");
}
System.out.println();
}
}

3.一种更快的筛选算法

eratosthenes算法在拆选的时候进行了重复拆选,比如数30,2、3、5都对其进行了拆选,有没有一种更好的方法,让每个合数只被拆选一次呢?

这个算法稍稍有些难理解,先看代码

public class SuXingCeShi {

public static void main(String[] args) {

int n = 100;
SuXingCeShi robot = new SuXingCeShi();
robot.caixuan(n);

}
void caixuan(int n){
boolean flag[] = new boolean[n+1];
int prime[] = new int[n+1];
int prime_num = 0;
for(int i=0; i<=n; i++){
flag[i] = true;//把所有数都看成素数
}
for(int i = 2; i <= n; i++){
if(flag[i] == true){
prime[prime_num++] = i;//记录素数
}
for(int j=0; j <prime_num && prime[j]*i <= n; j++){
flag[prime[j]*i] = false;//把合数赋值成false
if(i % prime[j] == 0)
break;
}
}
for(int i = 0; i < prime.length; i++){
if(prime[i] != 0)
System.out.print(prime[i] + " ");
}
}
}
我们先申请一个boolean[] flag来进行拆选,再申请一个int[] prime记录素数,在第二个for循环中进行判断,if(flag[i] == true)把它记录下来(因为我们把i之前的数都拆选过了,看二重for循环)比如现在
i = 9; i 前面的素数有2,3,5,7都记录在prime[]里了 然后遍历prime数组,j = 0时,flag[prime[j]*i] = false意思是把flag[2*9]
赋值成false,j = 1时候我们把flag[3*9] 赋值成false,关键是下面这句话,if(i % prime[j] == 0) break;它是整个算法的核心,为什么把flag[27]筛出去之后就不再筛flag[36]和flag[45]了呢?因为当i
= 18的时候 flag[2*18] 把flag[36]给筛出去了,但是没有筛选flag[3*18],flag[4*18] ,因为在flag[2*27]和flag[2*36]分别拆选过了。再举个例子比如i
= 35,当flag[2*35]拆选过后 ,还得筛选flag[3*35](假设n>105),因为flag[105]没法再由其他形式拆选了,所以用这样方式才保证了每个合数被筛选且仅被筛选一次。

4.wilson检测(威尔逊检测)

读过数论的朋友一定看过wilson算法,在数论中,威尔逊定理给出了判定一个自然是否为素数的充分必要条件,即:当且仅当p为素数时:(
p -1 )! ≡ -1 ( mod p )

如果您没接触过数论,帮您解释下这个式子是啥意思,(mod p)叫同余 意思就是除以p后余数相等,可以把上述式子理解为当且仅当p为素数时,[(p-1)!+1]%p
== 0

显然这个算法时间复杂度和需要数据类型的表示范围之大都是无法回避的,实际操作不可行,理论价值非常高。

代码清单:

public class SuXingCeShi {

public static void main(String[] args) {

int n = 100;
SuXingCeShi robot = new SuXingCeShi();
robot.wilson(n);

}

public void wilson(int n){
for(int i = 2; i <= n; i++)
if(isPrimeFromWilson(i))
System.out.print(i + " ");
System.out.println();
}

private boolean isPrimeFromWilson(int m) {

long temp = 1;
int i = m-1;
while(i >= 1){
temp *= i;
i--;
}
return (temp+1) % m == 0;
}
}


5.费马查表法

首先介绍一个数论中非常非常重要的一个定理-----费马小定理

假如a是一个整数,p是一个素数,那么

 
 (0 < a < p)

换句话说也就是:如果p是一个素数,且0<a<p,则a^(p-1)%p=1 但是费马小定理是判断素数的必要条件,当a=2时候,有一类伪素数数,它们满足费马小定理,但其本身却不是素数。它们有无穷个,最小的伪素数是341,统计表明前10亿个数中有50847534个素数,满足2^(p-1)%p
= 1的伪素数一共有5597,出错概率非常之低,所以我们可以根据我们的需求建一表伪素数表,算出素数看是否存在这张表里(可以二分,hash,trie)
好了,具备了以上知识我们想想费马小定理的函数该怎么写?
如果直接算2^(p-1)数的表示范围很头疼,于是我们可以利用这个迭代公式来计算


代码清单:

int runFermatPower(int a, int b, int n) {//返回a^b mod n

int result = 1;

for (int i = 0; i < b; i++) {
result *= a;
result %= n;

}
return result;
}


当b很大的时候,上述算法效率很一般,下面就引出密码学中一个重要的算法---大数模幂快速算法
代码清单:
int runFermatPower(int a, int b, int n)// d≡a^b mod n
{
int result = 1;
while (b > 0) {
if ((b & 1) == 1)
result = (result * a) % n;

b >>= 1;
a = (a * a) % n;
}
return result;
}

这个算法的详细讲解在我的另一篇文章http://blog.csdn.net/nash_/article/details/8491301
之后我就可以进行素数判断了
代码清单:
public void femat(int n){
for(int i = 2; i <= n; i++){
if(runFermatPower(2, n-1, n) != 1){
if(i不在伪素数表中)
输出i;
}
}
}


6.Miller-Rabin素性测试(米勒-拉宾检测)

Miller-Rabin素性测试是个概率算法,是实践中较常用的算法,它主要基于二次检测定理

下面我们来看一下Miller-Rabin算法它是一个随机算法,随机生成几个a利用费马小定理和二次探测定理来检测素数
二次探测定理:如果p是一个素数,且0<x<p,则方程x*x≡1(mod
p)的解为x=1,p-1.
我可以把这个二次探测定理加到大数模幂快速算法中
代码清单:
int runFermatPower(int a, int b, int n)// a^b mod n
{
int result = 1;
while (b > 0) {
if ((b & 1) == 1)
result = (result * a) % n;
if ((a * a) % n == 1 && a != 1 && a != n - 1)
return -1;// 二次探测
b >>= 1;
a = (a * a) % n;
}
return result;
}

Miller-Rabin算法随机生成底数a,进行多次调用runFermatPower函数进行测试,Miller-Rabin检测也存在伪素数的问题,但是与费马检测不同,MR检测的正确概率不依赖被检测数p,而仅依赖于检测次数。已经证明,如果一个数p为合数,那么Miller-Rabin检测的证据数量不少于比其小的正整数的3/4,换言之,k次检测后得到错误结果的概率为(1/4)^k
代码清单:
import java.util.Random;

public class SuXingCeShi {

public static void main(String[] args) {

int n = 100;
SuXingCeShi robot = new SuXingCeShi();
robot.getAllPrime(n);

}

int runFermatPower(int a, int b, int n)// d≡a^b mod n
{
int result = 1;
while (b > 0) {
if ((b & 1) == 1)
result = (result * a) % n;
if ((a * a) % n == 1 && a != 1 && a != n - 1)
return -1;// 二次探测
b >>= 1;
a = (a * a) % n;
}
return result;
}

public boolean runMillerRabin(int n, int time) {

Random random = new Random();

for (int i = 0; i < time; i++) {
if (runFermatPower(random.nextInt(n - 1) + 2, n - 1, n) != 1)
return false;
}
return true;
}

public void getAllPrime(int n) {

int time = 100;
for (int i = 2; i <= n; i++) {
if (runMillerRabin(i, time) == true) {
System.out.print(i + " ");
}
}
}
}


==================================================================================================

  作者:nash_  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/nash_/article/details/8290774

===================================================================================================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  素数 质数 质数检测