13_经典数学问题算法(Java、Kotlin描述)

    本篇文章介绍一些经典的数学问题求解算法。

(1)素数

    在大于1的自然数中,除了1和自身外,不能被其他自然数整除的数,称为素数,也叫质数。Java版本算法:

    boolean isPrime(int n){
        for (int i =2;i<n;i++){
            if (n % i == 0){
                return false;
            }
        }
        return true;
    }

    Kotlin版本:

    fun isPrime(n: Int): Boolean {
        for (i in 2 until n) {
            if (n % i == 0) {
                return false
            }
        }
        return true
    }

(2)欧几里得最大公因数算法

    Java实现:

/**
     * 最大公因数算法:欧几里得算法 O(log N)
     * 两个数的最大公因数是同时整除二者的最大整数,gcd(50,15) = 5;
     * 假设m >= n ,如果m < n ,则交换一下顺序
     */
    public static long gcd(long m, long n) {
        while (n != 0) {
            long rem = m % n;
            m = n;
            n = rem;
        }
        return m;
    }

    kotlin实现:

fun gcd(m: Long, n: Long): Long {
        var m = m
        var n = n
        while (n != 0L) {
            val rem = m % n
            m = n
            n = rem
        }
        return m
    }

    单看这个算法实现,是不容易理解的。因为它涉及到下面2个定理,还需要进行证明。

  • 1)如果c可以整除a,同时c也可以整除b,那么c就可以整除ax+by (x、y为任意的整数);
  • 2)如果a = bx + r ,那么gcd(a,b) = gcd(b,r);

    对定理2做一些说明。因为a = bx + r ,那么根据定理1,任何可以整除b、r的公因数,一定可以整除a,也就是说,b、r的公因数都是a、b的公因数。将等式转换一下,r = a - bx , 同样根据定理1,任何可以整除a、b的公因数,一定可以整除r,也就是说,a、b的公因数都是b、r的公因数。所以等式成立。
    注意:如果X整除Y,那么Y/X = a ,a是某个常值。X整除Y,X在分母的位置。
    注意:根据除法定理,对于任意的整数a和b,其中b!=0 ,那么一定存在一组整数q、r使得:a = qb + r ,其中0<= r < |b| 。
    接下来对欧几里得算法进行证明:

假设a、b都大于0 
如果a = q1b + r1,0 <= r1 < b;
同时b = q2r1 + r2,0 <= r2 < r1;
同时r1 = q3r2 + r3, 0 <= r3 < r2;
…
这样不停的分解下去,由于b > r1 > r2 > ... >= 0 ,这样分解下去,必然有一个rn = 0 。也就是说最后两步分解一定是:
rn-3 = qn-1rn-2 + rn-1 0 < rn-1 < rn-2;
rn-2 = qnrn-1 + rn,其中rn = 0

根据上面的定理(2),gcd(a, b) = gcd(b, r1) = gcd(r1, r2) = ... = gcd(rn-2, rn-1)。由于rn-1可以整除rn-2,那么rn-1就是最大公因数。

    再介绍一下它的递归版本,Java实现:

    public static long gcd1(long m, long n) {
        if (n == 0) {
            return m;
        } else {
            return gcd1(n, m % n);
        }
    }

    kotlin实现:

fun gcd1(m: Long, n: Long): Long {
        return if (n == 0L) {
            m
        } else {
            gcd1(n, m % n)
        }
    }

(3)暴力穷举最大公因数算法

    使用暴力穷举法也可以找到最大公因数。它的时间复杂度比不上欧几里得算法,但很容易理解。
    Java实现:

/**
     * 最大公因数算法2 : 连续整数检测,暴力穷举法O(N)
     * 假设m >= n
     */
    public static long gcd2(long m, long n) {
        long result = 1;
        for (long i = n; i >= 1; i--) {
            if (m % i == 0 && n % i == 0) {
                result = i;
                break;
            }
        }
        return result;
    }

    Kotlin实现:

    fun gcd2(m: Long, n: Long): Long {
        var result: Long = 1
        for (i in n downTo 1) {
            if (m % i == 0L && n % i == 0L) {
                result = i
                break
            }
        }
        return result
    }

(4)分解质因数算法

    递归方式的Java实现:

    /**
     * 将一个整数分解质因数 递归方式
     */
    public static void printPrimeNum(int m) {
        //找到最小质因数
        int smallPrime = m;
        for (int i = 2; i < m / 2; i++) {
            if (m % i == 0) {
                smallPrime = i;
                break;
            }
        }
        if (smallPrime < m) {
            System.out.print(smallPrime + "*");
            printPrimeNum(m / smallPrime);
        } else {
            System.out.println(smallPrime);
        }
    }

    递归方式的Kotlin实现:

    fun printPrimeNum(m: Int) {
        //找到最小质因数
        var smallPrime = m
        for (i in 2 until m / 2) {
            if (m % i == 0) {
                smallPrime = i
                break
            }
        }
        if (smallPrime < m) {
            print("$smallPrime*")
            printPrimeNum(m / smallPrime)
        } else {
            println(smallPrime)
        }
    }

    迭代方式的Java实现:

    /**
     * 将一个整数分解质因数 迭代方式
     */
    public static void printPrimeNum2(int m) {
        for (int i = 2; i < m / 2; i++) {
            if (m % i == 0) {
                System.out.print(i + "*");
                m = m / i;
                i--;
            }
        }
        System.out.println(m);
    }

    迭代方式的Kotlin实现:

    fun printPrimeNum2(m: Int) {
        var m = m
        var i = 2
        while (i < m / 2) {
            if (m % i == 0) {
                print("$i*")
                m = m / i
                i--
            }
            i++
        }
        println(m)
    }

(5)最大公倍数

    最大公倍数可以通过最小公约数来得到。Java版本:

    int lcm(int a, int b){
        int c ,d;
        c = gcd(a,b);
        d = (a*b)/c;
        return d;
    }

    int gcd(int m, int n) {
        if (n == 0) {
            return m;
        } else {
            return gcd(n, m % n);
        }
    }

    Kotlin版本:

    fun lcm(a: Int, b: Int): Int {
        val c: Int
        val d: Int
        c = gcd(a, b)
        d = a * b / c
        return d
    }

    fun gcd(m: Int, n: Int): Int {
        return if (n == 0) {
            m
        } else {
            gcd(n, m % n)
        }
    }

(6)非线性方程求解

    线性方程是一元一次方程,它是一条直线。非线性方程是一元多次方程。本小节采用二分法来求解非线性方程。
    主要思想:对于函数f(x),如果x = c 时,f(c) = 0,那么把x = c称为函数f(x)的零点。求解方程就是计算该方程所有的零点。对于二分法,假定非线性方程在区间(x,y)上连续,如果存在两个实数a和b属于区间(x,y),满足条件:f(a) * f(b) < 0,也就是说f(a)、f(b)异号,这说明在(a,b)上一定有零点,至少包含一个解。非线性方程很多时候没有精确解,如果在某个可以接受的精度内,f(x)小于该精度,就可以认为找到了方程的解。
    Java版本:

    /**
     * 二分法求解非线性方程
     * @param a
     * @param b
     * @param limit 精度限制
     * @return
     */
    double binaryGet(double a, double b,double limit){
        double c = (a+b)/2;
        while (Math.abs(func(c)) > limit && Math.abs(func(a - b)) > limit){
            if (func(c) * func(b) < 0){
                a = c;
            }
            if (func(a) * func(c) < 0){
                b = c;
            }
            c = (a+b)/2;
        }
        return c;
    }

    /**
     * 可以是任意的一元n次函数,这里仅以x*x为例
     * @param x
     * @return
     */
    double func(double x){
        return x*x;
    }

    Kotlin版本:

    /**
     * 二分法求解非线性方程
     * @param a
     * @param b
     * @param limit 精度限制
     * @return
     */
    fun binaryGet(a: Double, b: Double, limit: Double): Double {
        var a = a
        var b = b
        var c = (a + b) / 2
        while (Math.abs(func(c)) > limit && Math.abs(func(a - b)) > limit) {
            if (func(c) * func(b) < 0) {
                a = c
            }
            if (func(a) * func(c) < 0) {
                b = c
            }
            c = (a + b) / 2
        }
        return c
    }

    /**
     * 可以是任意的一元n次函数,这里仅以x*x为例
     * @param x
     * @return
     */
    fun func(x: Double): Double {
        return x * x
    }

(7)一维多项式求值

    一个普遍的一维多项式示例如下:
        P(x) = a_{n-1}x^{n-1} + a_{n-2}x^{n-2} + ··· + a_1x + a_0
    将它变换一下形式:
        P(x) = (···(( a_{n-1}x + a_{n-2})x + a_{n-3})x + ··· + a_1 )x + a_0
    可以看出,最内层是:a_{n-1}x + a_{n-2},外层可以通过它来一层层递推得到,Java版本算法如下:

    /**
     * 一维多项式求值
     *
     * @param a 系数 数组
     * @param x 给定的某个x值
     * @param n 项数
     * @return
     */
    double polynomial(double[] a, double x, int n) {
        double result = a[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            result = result * x + a[i];
        }
        return result;
    } 

    Kotlin版本:

    /**
     * 一维多项式求值
     *
     * @param a 系数 数组
     * @param x 给定的某个x值
     * @param n 项数
     * @return
     */
    fun polynomial(a: DoubleArray, x: Double, n: Int): Double {
        var result = a[n - 1]
        for (i in n - 2 downTo 0) {
            result = result * x + a[i]
        }
        return result
    }

(8)斐波那契数列

    来源于13世纪意大利数学家斐波那契,他记载了一个兔子产仔问题,大意:如果一个两个月大的兔子以后每一个月都可以生一对小兔子,而新生的兔子两个月后才可以生小兔子,那么如果没有意外死亡事件,一年后共有多少只兔子?
    通过观察,可以得到:F_n = F_{n-1} + F_{n-2} ,且F(1) = F(2) = 1;
    Java版本算法:

    int fibonacci(int n){
        if (n == 1 || n == 2){
            return 1;
        }
        return fibonacci(n-1) + fibonacci(n-2);
    }

    Kotlin版本:

    fun fibonacci(n: Int): Int {
        return if (n == 1 || n == 2) {
            1
        } else fibonacci(n - 1) + fibonacci(n - 2)
    }

    上面算法的增长速度是指数级的。还有一种线性版本,记录最近算出的两个斐波那契数,它的时间复杂度是O(N)。Java版本如下:

    int fibonacci(int n) {
        if (n <= 1) return 1;
        int last = 1;
        int nextLast = 1;
        int result = 1;

        for (int i = 2; i <= n; i++) {
            result = last + nextLast;
            nextLast = last;
            last = result;
        }
        return result;
    }

    Kotlin版本:

    fun fibonacci(n: Int): Int {
        if (n <= 1) return 1
        var last = 1
        var nextLast = 1
        var result = 1
        for (i in 2..n) {
            result = last + nextLast
            nextLast = last
            last = result
        }
        return result
    }

(9)圆周率计算

    Monte Carlo概率算法求解圆周率\pi,思路:一个边为1的正方形,以它的一个顶点画一个半径为1的圆,与正方形相交的部分面积(阴影面积)是\pi/4;如果均匀的向这个正方形中撒点,那么落入阴影部分的点和落入正方形中的点数之比应该是:S_阴/S_{正方形} = \pi/4;
    Java版本如下:

    static double computePI (int n){
        double PI;
        double x,y;
        int i,sum = 0;
        for (i=1;i<n;i++){
            x = Math.random();
            y = Math.random();

            if ((x*x + y*y) <= 1){ //在阴影面积内
                sum++;
            }
        }
        PI = 4.0 * sum/n;
        return PI;
    }

    测试结果:

n = 100 pi = 3.04
n = 1000 pi = 3.204
n = 10000 pi = 3.1464
n = 100000 pi = 3.1378

    结果总体来说在提升精度,但也有一定的随机性。n的值越大,\pi一定程度上越接近真实值。
    Kotlin版本:

    fun computePI(n: Int): Double {
        val PI: Double
        var x: Double
        var y: Double
        var i: Int
        var sum = 0
        i = 1
        while (i < n) {
            x = Math.random()
            y = Math.random()
            if (x * x + y * y <= 1) { //在阴影面积内
                sum++
            }
            i++
        }
        PI = 4.0 * sum / n
        return PI
    }

(10)X^n求解算法

    这里介绍两种X^n求解算法,也叫幂等算法。先来看第一种,Java版本如下:

/**
     * 幂等运算: O(N)
     */
    public static long pow1(long x, int n) {
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return x;
        }
        return x * pow1(x, n - 1);
    }

    Kotlin版:

fun pow1(x: Long, n: Int): Long {
        if (n == 0) {
            return 1
        }
        return if (n == 1) {
            x
        } else x * pow1(x, n - 1)
    }

    另外一种更高效,Java版如下:

/**
     * 高效的幂等运算 O(log N)
     */
    public static long pow(long x, int n) {
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return x;
        }
        if ((n & 1) == 1) { //奇数
            return pow(x * x, n / 2) * x;
        } else {
            return pow(x * x, n / 2);
        }
    }

    Kotlin版:

fun pow(x: Long, n: Int): Long {
        if (n == 0) {
            return 1
        }
        if (n == 1) {
            return x
        }
        return if (n and 1 == 1) { //奇数
            pow(x * x, n / 2) * x
        } else {
            pow(x * x, n / 2)
        }
    }

(10)编程实现数学递推公式

    用编程实现如下数学递推公式:

C_n = ( 2 / n )\sum_{i=0}^{n-1}C_i + n ,其中C_0 = 1,n = 0, 1, 2, ······ 。

    这里使用递归和迭代两种方式来实现。迭代方式其实属于动态规划算法的一种实践。
    递归方式的Java版本:

    double eval(int n) {
        if (n == 0) return 1.0;
        else {
            double sum = 0.0;
            for (int i = 0; i < n; i++) {
                sum += eval(i);
            }
            return 2.0 * sum / n + n;
        }
    }

    递归方式的Kotlin版本:

    fun eval(n: Int): Double {
        return if (n == 0) 1.0 else {
            var sum = 0.0
            for (i in 0..n-1) {
                sum += eval(i)
            }
            2.0 * sum / n + n
        }
    }

    迭代方式的Java版本:

    double eval2(int n) {
        double[] c = new double[n + 1];
        c[0] = 1.0;
        for (int i = 1; i <= n; i++) {
            double sum = 0.0;
            for (int j = 0; j < i; j++) {
                sum += c[j];
            }
            c[i] = 2.0 * sum / i + i;
        }
        return c[n];
    }

    迭代方式的Kotlin版本:

    fun eval2(n: Int): Double {
        val c = DoubleArray(n + 1)
        c[0] = 1.0
        for (i in 1..n) {
            var sum = 0.0
            for (j in 0 until i) {
                sum += c[j]
            }
            c[i] = 2.0 * sum / i + i
        }
        return c[n]
    }

    Over !

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容

  •     一直以来,对算法既感到赞叹,又感到痛苦。赞叹的地方在于算法的美感,将问题抽象出来,以极为精炼的方式加以解决...
    刘加城阅读 152评论 0 1
  • 即使做web开发,也会遇到各种各种需要解决的算法问题,本文节选部分经典练手算法,并提供相关参考答案,希望对你有所帮...
    Java架构学习者阅读 228评论 0 0
  • 即使做web开发,也会遇到各种各种需要解决的算法问题,本文节选部分经典练手算法,并提供相关参考答案,希望对你有所帮...
    程序员BUG阅读 998评论 0 0
  • 本章涉及知识点1、素数的定义2、寻找素数算法—短除法3、寻找素数算法—筛选法4、互质关系5、欧拉函数的证明6、欧拉...
    PrivateEye_zzy阅读 4,475评论 0 6
  • Java经典问题算法大全 /*【程序1】 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子...
    赵宇_阿特奇阅读 1,850评论 0 2