求小於n的素數個數
本文是對 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}\)
思考後發現
- 素數一定是奇數
- 若 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
參考文檔:
- 求質數算法的N種境界[1] - 試除法和初級篩法
- 求素數個數
- 埃拉托斯特尼篩法
求小於n的素數個數