1. 程式人生 > >從質數到“計算階乘末尾零的數目”

從質數到“計算階乘末尾零的數目”

問題1:

如何判斷一個數是否為質數?

從解決原理來看,大致有兩種。一種從質數定義出發:除了1和自身無其他約數,那麼解決思路就簡單直接--用這個數除以除1外所有比它小的數,若能整除,則不是質數。

但是實際操作中如果寫用n迴圈去除2到n-1的所有數,然後看是否都不能被整除,未免太慢了。大體改進的方法不外乎設法減少判斷的次數。可以參考這篇文章:https://blog.csdn.net/huang_miao_xin/article/details/51331710

還有一種更快的檢測法--MillerRabin檢測,原理用到費馬小定理。檢測過程大致解釋一下就是:如果一個基佬被一個男人勾引的概率是1/n,那麼如果一個不知道是否是gay人被勾引到了,他必然

gay,如果沒被勾引到只能說明他不一定是gay;但用很多個男人勾引他,都沒勾引到,則他大概率是直的,也就是說大概率是素數,這個概率大到一定程度,就可以用來做素性檢測了。可以參考這篇文章:http://www.matrix67.com/blog/archives/234

 

問題2:

從0到n中有多少個質數?有哪些質數?

如果把問題1的解決函式寫成:

boolean flag isZhiShu(int n)

那就從0開始到n,把每個數都帶入isZhiShu()這個方法把判定為ture的輸出就好了,但速度極慢。

如果我們用2,3,4,5。。。直到sqrt(n)去篩掉他們小於n的倍數,可以把時間複雜度降到線性

:O(n*logn),這種方式就是埃拉託斯特尼篩法

相信很多人也發現了,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從大到小變化;程式碼更簡潔也更快一些,但跟上面比沒那麼直觀,可以自己去寫一個。