從質數到“計算階乘末尾零的數目”
問題1:
如何判斷一個數是否為質數?
從解決原理來看,大致有兩種。一種從質數定義出發:除了1和自身無其他約數,那麼解決思路就簡單直接--用這個數除以除1外所有比它小的數,若能整除,則不是質數。
但是實際操作中如果寫用n迴圈去除2到n-1的所有數,然後看是否都不能被整除,未免太慢了。大體改進的方法不外乎設法減少判斷的次數。可以參考這篇文章:https://blog.csdn.net/huang_miao_xin/article/details/51331710
還有一種更快的檢測法--MillerRabin檢測,原理用到費馬小定理。檢測過程大致解釋一下就是:如果一個基佬被一個男人勾引的概率是1/n,那麼如果一個不知道是否是gay人被勾引到了,他必然是
問題2:
從0到n中有多少個質數?有哪些質數?
如果把問題1的解決函式寫成:
boolean flag isZhiShu(int n)
那就從0開始到n,把每個數都帶入isZhiShu()這個方法把判定為ture的輸出就好了,但速度極慢。
如果我們用2,3,4,5。。。直到sqrt(n)去篩掉他們小於n的倍數,可以把時間複雜度降到線性
相信很多人也發現了,4去篩人的時候會把2篩掉的又走一遍,6去篩人的時候又把2,3篩掉的又走一遍。。。最精簡的方式應該是隻用小於n的質數去篩就好了,但顯然求小於n的質數演算法本身就是耗時的,然而在空間中建立一個“曾用質數表”可以每次對比是否重複出現過而換取時間(搜一下尤拉篩法);
再比如用5去篩的時候,小於5^2的數都被除掉了,因為若x是個小於5的數,那麼5*x早在x作為篩子時早就被x篩掉了而不用等到5來篩它,所以對每個篩子i,可以從i^2開始篩;
總之,改進思路很多,有很多想象空間。
附上埃拉託斯特尼篩法程式碼:(java)
import static java.lang.Math.sqrt; public class findSushu { private static void Eratosthenes(int n) { boolean[] check = new boolean[n+1]; for (int i=2; i<=n; i++){ check[i] = true; } for (int i=2; i<=sqrt(n); i++){ if (check[i]){ int j = i*i; while (j < n){ check[j] = false; j = j + i; } } } for (int i = 2;i < n;i++){ if (check[i] == true){ System.out.println(i); } } } public static void main(String[] args) { int n = 1999; long start = System.currentTimeMillis(); Eratosthenes(n); long over = System.currentTimeMillis(); System.out.println("耗時:" + (over - start) + " ms"); } }
問題3:
一個數如何因式分解成質數之積?
任何一個合數必然可以寫成多個質數之積(質數就寫成它自身),如何把它因式分解?
用質數去分解n,如果能整除,那麼結果將會繼續被分解直到本身就是個質數;如果不能整除,那麼繼續用下一個質數嘗試分解
這裡,“繼續取下一個質數”是一個非常複雜的事情,所以,在被分解數不是很大的時候,可以直接取下一個自然數。這裡可能有人擔心下一個自然數不是質數怎麼辦?那不是分解出來一個合數因子了麼?其實這種情況並不會出現,假設x是上述那個合數因子的話,又因為x可以寫成兩個比x小的數之積:a*b,所以,其實n早就被a或b分解了。
附上質因數分解程式碼: (java)
public class resolveZhiShu {
static class StopMsgException extends RuntimeException {
} //會報Exception,為了強制停止遞迴
private static void resolve(int n) {
for (int i = 2;i <= n;i++){
if (n == i){ //全部分解完畢
System.out.print(n +",");
throw new StopMsgException();//強制跳出遞迴
}else if(n % i == 0) { //分解成功一個
System.out.print(i + ",");
resolve(n / i);
}
}
}
public static void main(String[] args) {
int n = 735;
resolve(n);
}
}
問題4:
給定一個正整數n,n的階乘結果末尾有多少個零?
首先,最暴力的方法就是直接求出n!然後取結果的末位0,這個計算量恐怖至極,就不說了
step1:一個數末尾有多少0,就代表它的因式分解後有多少個10,同樣,乘數中有多少個10,結果末尾就有多少個0;
step2:10是怎麼來的?按因式分解成質因數來看,只有2*5才能產生一個10,
也就是說,這個階乘式全分解成質因數乘積後,除了2*5,沒有其他相乘式能產生10了。證明:(很挫,這裡空白太少就不寫了:)
step3:質因數分解後,需要證明min(num2,num5) = 5,即5出現次數比2出現次數少,則只要知道5出現了多少次
接下來要找在等差數列中(從1到n),對其全部質因數分解後某個數出現的次數。可以發現,一個數x作為質因數只能以x,x^2,x^3.....等方式出現,那麼怎麼統計呢,以n = 27;x = 3為例,見下圖:
可以看出,1次方每x格出現一次且每次帶來一個x,2次方每x^2格出現一次且每次帶來兩個x,但那個“9”分解成的兩個3其中一個已經是3^1帶來的了。以此類推,我們不用去考慮每個“落點”包含了幾個x,只要統計落點次數,每次落就+1即可
附上求階乘末尾0程式碼:(java)
//時間複雜度O(logn)
public int trailingZeroes(int n) {
if(n < 5){
return 0;
}
int totalNumFive = 0;//5做為質數出現在數列中的次數
int powOfFive = 1;//高次冪引數
while(Math.pow(5,powOfFive) <= n){
totalNumFive = totalNumFive + (int)(n/(Math.pow(5,powOfFive)));
powOfFive++;
}
return totalNumFive;
}
注:可以每次改n,n/5,n從大到小變化;程式碼更簡潔也更快一些,但跟上面比沒那麼直觀,可以自己去寫一個。