判断一个数是不是质数
如果一个散列表的容量为N,则在再散列的过程中,一般设置新散列表的容量为2N后面的第一个素数。从而,需要判断一个数字是不是质数,因而,本文就重点介绍这个知识点。
例如,令N=7,则再散列之后,新散列表的容量为17。
本文给出四个判断一个自然数是否为质数的算法和算法的Java实现,并且分析其时间复杂度。
基本概念
质数(prime number)又称素数,是指在大于1的自然数中,除了1和它本身以外不再有其它因数的自然数。如果有其它因数,则称为合数。
显然,1既不属于质数也不属于合数,最小的质数是2,最小的合数是4。记自然数为N,N的平方根为sqrt(N)。下面给出四个判断一个自然数是否为质数的思路。
算法1 穷举法
根据质数定义,判断一个整数N是否为素数,只需用从2 到 N-1 之间的每一个整数去除N,如果都不能被整除,那么 N就是一个素数。
/**
* 判断n是否为素数
*
* 不能被2~n-1间的整数整除则为素数,返回 true
*
* @param num
* @return true 是素数
*/
private static boolean isPrimeTraversal(int num) {
System.out.println("方法名:isPrimeTraversal");
if (num < 2) {
return false;
}
int j;
for (j = 2; j < num; j++) {
if (num % j == 0) {
System.out.println(num + " is not a prime number");
return false;
}
}
System.out.println(num + " is a prime number");
return true;
}
算法2
如果 N不能被 2 ~ sqrt(N) 之间任一整数整除,那么N必定是素数。
分析:记x和y都是自然数,且x<=y;令N=x*y,则N<=(x^2+y^2)/2,当且仅当x=y时,等号成立。显然,x=y时,N=x^2。
如果N是合数,则1<x<= sqrt(N) <=y。所以,只需要遍历不大于sqrt(N)的自然数即可。
/**
* @param num
* @return true is a prime number
*/
private static boolean isPrime(int num) {
if (num < 2) {
return false;
}
// you can also use i <= p / 2
int sqrt = 1 + (int) Math.sqrt(num);
for (int i = 2; i < sqrt; i++) {
// 若能被整除,则说明是合数,返回false
if (num % i == 0) {
return false;
}
}
return true;
}
算法2的时间复杂度为O(sqrt(N) ),小于算法1的O(N),效率更高。
算法3 Eratosthenes筛选法
公元前250年,古希腊著名数学家埃拉托塞尼(Eratosthenes)提出一种筛选法:要得到不大于某个自然数N的所有素数,只要在2至N中将不大于sqrt(N)的素数的倍数全部划去即可。
上述方法等价于“如果N是合数,则它有一个因数d满足1<d≤sqrt(N)”。(《基础数论》13页,U杜德利著,上海科技出版社)。
严格而言,这里并非判断一个数是否为素数,而是寻找小于N的所有素数,放在本文有点牵强附会。鉴于是一个不错的算法,姑且如此吧。
/**
* 筛选法查找区间[0, n) 所有素数
* @param n
*/
private static void screenPrimeNum(int n) {
// 数组bs初始化时默认值为false
boolean[] bs = new boolean[n];
for (int i = 2; i < n; i++) {// 从2开始
for (int j = i + 1; j < n; j++) { //从i+1循环就行
if (j % i == 0) {
// j是质数i的倍数,把数组元素bs[j]赋值为true
bs[j] = true;
}
}
}
StringBuilder sb = new StringBuilder();
// 0和1不是质数,因此从2开始循环
for (int i = 2; i < n; i++) {
// 元素为false的下标就是我们苦苦寻觅的素数
if (!bs[i]) {
sb = sb.append(i).append(",");
}
}
System.out.println(sb.substring(0, sb.length() - 1));
}
这个算法的时间复杂度是O(NloglogN),在此,数学证明过程就忽略了。如果用算法1和算法2查找小于N的所有素数,容易求得,应用算法2时的时间复杂度为O(N*sqrt(N) ),应用算法1时的是O(N*N)。
算法4 基于六的求模算法
质数有一个特点,除了2和3之外,它总可以表示为6 N±1的形式,其中 N是大于等于1的自然数。
分析 任何一个自然数,总可以表示成为如下的形式之一:
6N,6N+1,6N+2,6N+3,6N+4,6N+5 (N=0,1,2,…)
显然,当N≥1时,6N,6N+2,6N+3,6N+4都不是素数,只有形如6N+1和6N+5的自然数有可能是素数。所以,除了2和3之外,所有的素数都可以表示成6N±1的形式(N为大于1的自然数)。故循环判断的时候,步长可以设置为6,然后判断6 N±1有无因数即可。
对于输入的自然数较小时,也许效果不怎么明显,但是如果自然数越来越大,那么该方法的执行效率就会越来越明显,而且,要明显优于筛选法。
/**
* 以6为步长,校验一个数字是否为质数
* @param num
* @return true is a prime number
*/
public static boolean isPrime6(int num) {
if (num <= 3) {
return num > 1;
}
// 不在6的倍数两侧的一定不是质数
if (num % 6 != 1 && num % 6 != 5) {
return false;
}
int sqrt = 1 + (int) Math.sqrt(num);
//在6的倍数两侧的也可能不是质数
for (int i = 5; i < sqrt; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
算法的时间复杂度是O(sqrt(N) ),但是,有明显的步长优势,海量数据校验时,效果不可小觑,比如10w+。
求素数练习题
下面给出两道练习题及其Java实现。
一、 求两个整数之间的素数。
/**
* 找到两个自然数之间的素数
*/
private static void findAllPrimeNumbers(int begin, int end) {
StringBuilder sb = new StringBuilder();
for (; begin <= end; begin++) {
if (isPrime(begin)) {
sb = sb.append(begin).append(",");
}
}
System.out.println(sb.substring(0, sb.length() - 1));
}
二、求一个数字乘以2的值之后的第一个素数。
/**
* 查询自然数的、2的倍数(2*num)之后的第一个素数
* @param num
*
*/
private static void nextPrime(int num) {
num = 2 * num;
StringBuilder sb = new StringBuilder();
while (true) {
if (isPrime6(num)) {
break;
}
num ++;
}
System.out.println("自然数两倍之后的第一个素数是:" + num);
}
算法实现整合
这里给出完整的Java代码实现。
package hello;
public class IsPrime {
public static void main(String[] args) {
int num = 17;
nextPrime(num);
boolean bool = false;
bool = isPrime(num);
if (bool) {
System.out.println(num + " is a prime number");
} else {
System.out.println(num + " is not a prime number");
}
bool = isPrime6(num);
if (bool) {
System.out.println(num + " is a prime number proofed by isPrime6");
} else {
System.out.println(num + " is not a prime number proofed by isPrime6");
}
System.out.println("应用实例——找到两个自然数之间的素数");
int begin = 0, end = 101300;
long beginT = System.currentTimeMillis();
findAllPrimeNumbers(begin, end);
long middle = System.currentTimeMillis();
System.out.println(middle - beginT + "是耗时时长。下面使用筛选法找到小于这个整数的所有素数");
screenPrimeNum(end);
System.out.println("筛选法耗时是 " + (System.currentTimeMillis() - middle));
}
/**
* @param num
* @return true is a prime number
*/
private static boolean isPrime(int num) {
if (num < 2) {
return false;
}
// you can also use i <= p / 2
int sqrt = 1 + (int) Math.sqrt(num);
for (int i = 2; i < sqrt; i++) {
// 若能被整除,则说明是合数,返回false
if (num % i == 0) {
return false;
}
}
return true;
}
/**
* 以6为步长,校验一个数字是否为质数
* @param num
* @return true is a prime number
*/
public static boolean isPrime6(int num) {
if (num <= 3) {
return num > 1;
}
// 不在6的倍数两侧的一定不是质数
if (num % 6 != 1 && num % 6 != 5) {
return false;
}
int sqrt = 1 + (int) Math.sqrt(num);
//在6的倍数两侧的也可能不是质数
for (int i = 5; i < sqrt; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
/**
* 判断n是否为素数
* <p>
* 不能被2~n-1间的整数整除则为素数,返回 true
*
* @param num
* @return true 是素数
*/
private static boolean isPrimeTraversal(int num) {
System.out.println("方法名:isPrimeTraversal");
if (num < 2) {
return false;
}
int j;
for (j = 2; j < num; j++) {
if (num % j == 0) {
System.out.println(num + " is not a prime number");
return false;
}
}
System.out.println(num + " is a prime number");
return true;
}
/**
* 找到两个自然数之间的素数
*/
private static void findAllPrimeNumbers(int begin, int end) {
StringBuilder sb = new StringBuilder();
for (; begin <= end; begin++) {
if (isPrime6(begin)) {
sb = sb.append(begin).append(",");
}
}
System.out.println(sb.substring(0, sb.length() - 1));
}
/**
* 查询自然数的、2的倍数(2*num)之后的第一个素数
* @param num
*
*/
private static void nextPrime(int num) {
num = 2 * num;
StringBuilder sb = new StringBuilder();
while (true) {
if (isPrime6(num)) {
break;
}
num ++;
}
System.out.println("自然数两倍之后的第一个素数是:" + num);
}
/**
* 筛选法查找区间[0, n) 所有素数
* @param n
*/
private static void screenPrimeNum(int n) {
// 数组bs初始化时默认值为false
boolean[] bs = new boolean[n];
for (int i = 2; i < n; i++) {// 从2开始
for (int j = i + 1; j < n; j++) { //从i+1循环就行
if (j % i == 0) {
// j是质数i的倍数,把bs[j]赋值为true
bs[j] = true;
}
}
}
StringBuilder sb = new StringBuilder();
// 0和1不是质数,因此从2开始循环
for (int i = 2; i < n; i++) {
// 元素为false的下标就是我们苦苦寻觅的素数
if (!bs[i]) {
sb = sb.append(i).append(",");
}
}
System.out.println(sb.substring(0, sb.length() - 1));
}
}
小 结
首先给出四种求解素数的思路,并分析其执行性能;然后给出相应的Java实现;最后,展示一个求素数的实战训练找出两个自然数之间的素数。
Reference
https://www.cnblogs.com/sea-stream/p/12098804.html
https://baike.baidu.com/item/%E8%B4%A8%E6%95%B0/263515