helloworld之后,就该写点真正有意义的程序了。
经常有人会问,为什么他写的素数程序看上去挺完美,但是跑出来的结果不对?
通常,2重循环的内循环,满足某种条件后该内循环终止,外循环加一的情况,需要加个标志(flg),以标明内循环的终止,是因为内循环正常结束了?还是满足条件了?
一定范围的素数寻找,就是这样的。跑出来的结果不对,通常就是标志(flg)的初始值设定,判定逻辑不对,甚至,标志(flg)根本就没有。
具体的错误程序就不再谈了,素数的定义,相关定理网上到处有,就不c&p了,但是还是应该好好了解一下,以后研究密码学的时候,还是非常有用的。
开始用java写个素数检验程序吧。
1, 最朴素的算法:
由2开始到n-1,一个一个去判断是否可以整除,如果一旦满足,就是非素数,判断终止。
public static boolean isPrime(long num) {
for (int j = 2; j < num; j++) {
if (num % j == 0) {
return false;
}
}
return true;
}
2,一定范围内的素数:
用上面的isPrime方法,最朴素的输出一定范围的素数。
long longTime = System.currentTimeMillis();
for (long i = 3; i <= 10000; i = i + 2) {
if (isPrime(i))
System.out.print(i + " ");
}
System.out.println();
System.out.println((System.currentTimeMillis()-longTime)+"ms");
执行时间:328ms
3,素数判断算法的优化:
很多人(包括俺自己)一开始就发现朴素的素数判断算法的效率问题,就是不需要判断到n-1。
那么,判断到多少就足够了呢?很多人(包括俺自己)以为到n/2就可以了,很可惜n/2还是太大了,没有必要。实际上,只要循环到n^(1/2)就可以了。具体的证明,用反证法非常简单,就不叙述了。
唯一的,看到有的文章说要循环到,n^(1/2)+1或者(n+1)^(1/2),这是没有必要的,就循环到n^(1/2)向下取整就可以。
public static boolean isPrime(long num) {
long sqrtI = (long) Math.sqrt(num);
for (int j = 2; j <=sqrtI; j++) {
if (num % j == 0) {
return false;
}
}
return true;
}
执行时间:110ms
4,改进的一定范围内的素数:
3,5,7,9...递增2的去判断,还不是够好的办法,可以做如下的改进:
考察6m:
6m+0(2,3的倍数,合数)
6m+1(要判断,可能是素数)
6m+2(2的倍数,合数)
6m+3(3的倍数,合数)
6m+4(2的倍数,合数)
6m+5(要判断,可能是素数)
原来需要判断1/2的数,现在只要判断1/3的,提高了些。程序如下:
long longTime = System.currentTimeMillis();
for (long i = 1; i <= 1667; i++ ) {
if (isPrime(6*i+1))
System.out.print((6*i+1) + " ");
if (isPrime(6*i+5))
System.out.print((6*i+5) + " ");
}
System.out.println();
System.out.println((System.currentTimeMillis() - longTime) + "ms");
执行时间:94ms
当然考察6(2*3)可以提到一些效率,那么考察30(2*3*5),只要看 1,7,11 ,13,17,19,23,29(2,3,5意外的素数(1是例外)),8/30的数需要判断,又快了点......再快一点就是埃拉托斯特尼筛法了。
5 埃拉托斯特尼筛法:
根据改进的输出一定范围内的素数的算法,可以很容易发现,只要一个素数表,把这个范围内的素数的倍数筛选出去,剩下的就是素数了。那么素数表要多大呢? 2,3,5...n^(1/2)向上取整,就可以了。
反过来,有2,3,5...n的素数表,也可以得出2...n^2的素数表。
程序如下:
public static int[] getPrimeNumbers(int num) {
int[] primeNumbers = new int[num];
for (int i = 0; i < num; i++)
primeNumbers[i] = i + 1;
primeNumbers[0] = 0;
int i = 0;
int j = num - 1;
while (true) {
for (; i < num; i++)
if (primeNumbers[i] != 0)
break;
for (; j >= 0; j--)
if (primeNumbers[j] != 0)
break;
if (primeNumbers[i] * primeNumbers[i] > primeNumbers[j]) {
int m = 0;
for (int n = 0; n < num; n++) {
if (primeNumbers[n] != 0)
m++;
}
int[] primeNumbersNew = new int[m];
for (int n = 0, l = 0; n < num; n++) {
if (primeNumbers[n] != 0)
primeNumbersNew[l++] = primeNumbers[n];
}
return primeNumbersNew;
}
int prime = primeNumbers[i];
for (int k = prime + prime; k <= j + 1; k += prime)
primeNumbers[k - 1] = 0;
i++;
}
}
输出程序如下:
longTime = System.currentTimeMillis();
int[] ints = getPrimeNumbers(10000);
for (int i = 0; i < ints.length; i++)
System.out.println(ints[i]);
System.out.println((System.currentTimeMillis() - longTime) + "ms");
执行时间:32ms
在范围大的情况下,埃拉托斯特尼筛法的优势是明显的,因为它是n^(1/2)收敛的。
试一下1000000,方法4用了6047ms;而,埃拉托斯特尼筛法用了3625ms。
6 其他方法
以上的都是”试除法“,还有ECPP(使用椭圆曲线演算法,O((log n)^6))、AKS(多项式演算法,O((log n)^12))、Lucas-Lehemer(只用于测试梅森素数,2^(n-2)*log(2+3^(1/2)) )等方法,是利用数论中的一些定律,精确判定是否是素数的方法。具体的算法和程序,以后有时间,再写了。
除此以外,实践中概率式判断法也很常用,在一些非高机密的领域,比如银行军事意外的场合,用来生成密钥还是可行的。
经常有人会问,为什么他写的素数程序看上去挺完美,但是跑出来的结果不对?
通常,2重循环的内循环,满足某种条件后该内循环终止,外循环加一的情况,需要加个标志(flg),以标明内循环的终止,是因为内循环正常结束了?还是满足条件了?
一定范围的素数寻找,就是这样的。跑出来的结果不对,通常就是标志(flg)的初始值设定,判定逻辑不对,甚至,标志(flg)根本就没有。
具体的错误程序就不再谈了,素数的定义,相关定理网上到处有,就不c&p了,但是还是应该好好了解一下,以后研究密码学的时候,还是非常有用的。
开始用java写个素数检验程序吧。
1, 最朴素的算法:
由2开始到n-1,一个一个去判断是否可以整除,如果一旦满足,就是非素数,判断终止。
public static boolean isPrime(long num) {
for (int j = 2; j < num; j++) {
if (num % j == 0) {
return false;
}
}
return true;
}
2,一定范围内的素数:
用上面的isPrime方法,最朴素的输出一定范围的素数。
long longTime = System.currentTimeMillis();
for (long i = 3; i <= 10000; i = i + 2) {
if (isPrime(i))
System.out.print(i + " ");
}
System.out.println();
System.out.println((System.currentTimeMillis()-longTime)+"ms");
执行时间:328ms
3,素数判断算法的优化:
很多人(包括俺自己)一开始就发现朴素的素数判断算法的效率问题,就是不需要判断到n-1。
那么,判断到多少就足够了呢?很多人(包括俺自己)以为到n/2就可以了,很可惜n/2还是太大了,没有必要。实际上,只要循环到n^(1/2)就可以了。具体的证明,用反证法非常简单,就不叙述了。
唯一的,看到有的文章说要循环到,n^(1/2)+1或者(n+1)^(1/2),这是没有必要的,就循环到n^(1/2)向下取整就可以。
public static boolean isPrime(long num) {
long sqrtI = (long) Math.sqrt(num);
for (int j = 2; j <=sqrtI; j++) {
if (num % j == 0) {
return false;
}
}
return true;
}
执行时间:110ms
4,改进的一定范围内的素数:
3,5,7,9...递增2的去判断,还不是够好的办法,可以做如下的改进:
考察6m:
6m+0(2,3的倍数,合数)
6m+1(要判断,可能是素数)
6m+2(2的倍数,合数)
6m+3(3的倍数,合数)
6m+4(2的倍数,合数)
6m+5(要判断,可能是素数)
原来需要判断1/2的数,现在只要判断1/3的,提高了些。程序如下:
long longTime = System.currentTimeMillis();
for (long i = 1; i <= 1667; i++ ) {
if (isPrime(6*i+1))
System.out.print((6*i+1) + " ");
if (isPrime(6*i+5))
System.out.print((6*i+5) + " ");
}
System.out.println();
System.out.println((System.currentTimeMillis() - longTime) + "ms");
执行时间:94ms
当然考察6(2*3)可以提到一些效率,那么考察30(2*3*5),只要看 1,7,11 ,13,17,19,23,29(2,3,5意外的素数(1是例外)),8/30的数需要判断,又快了点......再快一点就是埃拉托斯特尼筛法了。
5 埃拉托斯特尼筛法:
根据改进的输出一定范围内的素数的算法,可以很容易发现,只要一个素数表,把这个范围内的素数的倍数筛选出去,剩下的就是素数了。那么素数表要多大呢? 2,3,5...n^(1/2)向上取整,就可以了。
反过来,有2,3,5...n的素数表,也可以得出2...n^2的素数表。
程序如下:
public static int[] getPrimeNumbers(int num) {
int[] primeNumbers = new int[num];
for (int i = 0; i < num; i++)
primeNumbers[i] = i + 1;
primeNumbers[0] = 0;
int i = 0;
int j = num - 1;
while (true) {
for (; i < num; i++)
if (primeNumbers[i] != 0)
break;
for (; j >= 0; j--)
if (primeNumbers[j] != 0)
break;
if (primeNumbers[i] * primeNumbers[i] > primeNumbers[j]) {
int m = 0;
for (int n = 0; n < num; n++) {
if (primeNumbers[n] != 0)
m++;
}
int[] primeNumbersNew = new int[m];
for (int n = 0, l = 0; n < num; n++) {
if (primeNumbers[n] != 0)
primeNumbersNew[l++] = primeNumbers[n];
}
return primeNumbersNew;
}
int prime = primeNumbers[i];
for (int k = prime + prime; k <= j + 1; k += prime)
primeNumbers[k - 1] = 0;
i++;
}
}
输出程序如下:
longTime = System.currentTimeMillis();
int[] ints = getPrimeNumbers(10000);
for (int i = 0; i < ints.length; i++)
System.out.println(ints[i]);
System.out.println((System.currentTimeMillis() - longTime) + "ms");
执行时间:32ms
在范围大的情况下,埃拉托斯特尼筛法的优势是明显的,因为它是n^(1/2)收敛的。
试一下1000000,方法4用了6047ms;而,埃拉托斯特尼筛法用了3625ms。
6 其他方法
以上的都是”试除法“,还有ECPP(使用椭圆曲线演算法,O((log n)^6))、AKS(多项式演算法,O((log n)^12))、Lucas-Lehemer(只用于测试梅森素数,2^(n-2)*log(2+3^(1/2)) )等方法,是利用数论中的一些定律,精确判定是否是素数的方法。具体的算法和程序,以后有时间,再写了。
除此以外,实践中概率式判断法也很常用,在一些非高机密的领域,比如银行军事意外的场合,用来生成密钥还是可行的。