1. 程式人生 > >求小於n的素數個數

求小於n的素數個數

alt AD lean 編程 blog mem logs numbers rime

本文是對 LeetCode Count Primes 解法的探討。

題目:
Count the number of prime numbers less than a non-negative number, n.

盡管題目並沒有要我們寫一個最優的算法,但是身為一個程序員,優化應該是一種習慣,在編程的過程中,隨著思考進行優化。只要求我們滿足給定的時間和空間即可。

如果你只能想出一個最簡單的方法,難道你會有什麽競爭力嗎?

窮舉

最開始我用的就是這個方法,可以說這是最簡單的一種方法了,而且最開始,我就是想的這種方法,說明:我沒有對這個問題進行思考,沒有去優化它,而作為一個程序員,如何提高效率是拿到一個問題首先要思考的事情。

public int countPrimes(int n) {
    int num = 0;
    for (int i = 2; i < n; i++) {
        boolean flag = true;
        for (int j = 2; j < i - 1; j++)
            if (i % j == 0) {
                flag = false;
                break;
            }
        if (flag) {
            num++;
        }
    }
    return
num; }

測試代碼:

public static void main(String[] args) {
    //獲取開始時
    long startTime = System.currentTimeMillis();
    System.out.println("The num is " + new L_204_Count_Primes().countPrimes(2000000));
    long endTime = System.currentTimeMillis();
    //獲取結束時間
    System.out.println("程序運行時間: " + (endTime - startTime) + "ms"
); }

時間太長,已經不能計算。

只能是奇數且小於\(\sqrt{n}\)

思考後發現

  1. 素數一定是奇數
  2. 若 n=ab 是個合數(其中 a 與 b ≠ 1), 則其中一個約數 a 或 b 必定至大為 \(\sqrt{n}\).
public int countPrimes2(int n) {
    int num = 1;
    for (int i = 3; i < n; i += 2) {
        boolean flag = true;
        for (int j = 2; j <= (int) Math.sqrt(i); j++)
            if (i % j == 0) {
                flag = false;
                break;
            }
        if (flag) {
            num++;
        }
    }
    return num;
}

The num is 148933
程序運行時間: 1124ms

試除法:數學知識的運用

查閱 算術基本定理可知:

算術基本定理 :
每個大於1的整數均可寫成一個以上的素數之乘積,且除了質約數的排序不同外是唯一的

也就是說我們可以每個數來除以得到的素數,這樣可大大減少運行次數。

public int countPrimes3(int n) {
    if (n < 3) {
        return 0;
    }
    //0 1 不算做素數,2一定是素數
    List<Integer> list = new ArrayList<>();
    list.add(2);
    boolean flag;
    for (int i = 3; i < n; i += 2) {
        flag = true;
        for (int j = 0; j < list.size() && list.get(j) <= (int) Math.sqrt(n); j++) {
            if (i % list.get(j) == 0) {
                flag = false;
                break;
            }
        }
        if (flag) {
            list.add(i);
        }
    }
    return list.size();
}

The num is 148933
程序運行時間: 383ms

篩選法

埃拉托斯特尼篩法,簡稱埃氏篩,也有人稱素數篩。這是一種簡單且歷史悠久的篩法,用來找出一定範圍內所有的素數。

所使用的原理是從2開始,將每個素數的各個倍數,標記成合數。一個素數的各個倍數,是一個差為此素數本身的等差數列。此為這個篩法和試除法不同的關鍵之處,後者是以素數來測試每個待測數能否被整除。

篩選法的策略是將素數的倍數全部篩掉,剩下的就是素數了,下圖很生動的體現了篩選的過程:

技術分享圖片

篩選的過程是先篩掉非素數,針對本文的題目,每篩掉一個,素數數量-1即可,上面說過素數的一個特點,除了2,其它的素數都是奇數,所以我們只需在奇數範圍內篩選就可以了。

public int countPrimes4(int n) {
    if (n < 3) {
        return 0;
    }
    //false代表素數,true代表非素數
    boolean[] flags = new boolean[n];
    //0不是素數
    flags[0] = true;
    //1不是素數
    flags[1] = true;
    int num = n - 2;
    for (int i = 2; i <= (int) Math.sqrt(n); i++) {
        //當i為素數時,i的所有倍數都不是素數
        if (!flags[i]) {
            for (int j = 2 * i; j < n; j += i) {
                if (!flags[j]) {
                    flags[j] = true;
                    num--;
                }
            }

        }
    }
    return num;
}

The num is 148933
程序運行時間: 43ms

全部代碼放在:
https://github.com/morethink/algorithm/blob/master/src/algorithm/leetcode/L_204_Count_Primes.java

參考文檔

  1. 求質數算法的N種境界[1] - 試除法和初級篩法
  2. 求素數個數
  3. 埃拉托斯特尼篩法

求小於n的素數個數