从质数到“计算阶乘末尾零的数目”

问题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从大到小变化;代码更简洁也更快一些,但跟上面比没那么直观,可以自己去写一个。

猜你喜欢

转载自blog.csdn.net/cn_leeyiru_static/article/details/85329616