P5 判断一个数是不是质数

      如果一个散列表的容量为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

原文地址:https://www.cnblogs.com/east7/p/12681483.html