数学算法 in python

数学算法

本节内容将介绍数学算法。主要包含数学上几个基本问题,最大公约数问题,算术分析方法,斐波那契数列问题。

知识点

  • 数学几个基本问题
  • 最大公约数问题
  • 算术分析方法
  • 斐波那契数列问题

数学上几个基本问题

在编程界,有这么几个经典的数学问题,这里给大家简单介绍下。

3n+1 问题

对于任意大于 1 的自然数 n,若该数为偶数则将其变为原来的一半,若为奇数则将其变为 3n+1。反复进行上述过程,直到结果为 1 时停止。这就是著名的“3n+1”问题。要求输入 n,输出按“3n+1”规则变换到 1 所需要的数字变换次数。(n<=10^9)

题目描述:

3n+1 问题是一个简单有趣而又没有解决的数学问题。这个问题是由 L. Collatz 在 1937 年提出的。克拉兹问题(Collatz problem)也被叫做 hailstone 问题、3n+1 问题、Hasse 算法问题、Kakutani 算法问题、Thwaites 猜想或者 Ulam 问题。克拉兹问题的特殊之处在于:尽管很容易将这个问题讲清楚,但直到今天仍不能保证这个问题的算法对所有可能的输入都有效——即至今没有人证明对所有的正整数该过程都终止。

问题如下:

  1. 输入一个正整数 n;
  2. 如果 n=1 则结束;
  3. 如果 n 是奇数,则 n 变为 3n+1,否则 n 变为 n/2;
  4. 转入第 2 步。

请大家编程,构造一个 3n+1 函数,该函数完成对 43 的 3n+1 的演化,建立一个列表储存演化过程,最终返回该列表及演化步骤数

例子:

input:7

output:[7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1] 16

参考代码:

def function(num):
    mylist=[num]
    while num!=1:
        if num%2==1: #如果为奇数
            num=3*num+1
            mylist.append(num)
        else: #为偶数
            num=num//2
            mylist.append(num)
    return mylist,len(mylist)-1
print(function(43))

寻找最值问题

在诸多数学问题,最值问题一直是争论的主题。在求最值的探索的过程,如何更快更好地得到最值,一代又一代理论科学家做出很大的贡献,逐渐衍化出来很多排序算法。关于排序算法的知识点,我们将在接下来的章节里介绍,这里将只对最大值,最小值进行简单讨论。

最大值求解

给定一个列表 [2, 4, 9, 7, 19, 94, 5],请编写 find_max 函数,该函数返回列表的最大值。

这个函数很容易,相信大家一定能够自行编写出来。

参考代码如下:

def find_max(nums):
    max = nums[0]
    for x in nums:
      if x > max:
        max = x
    print(max)

def main():
  find_max([2, 4, 9, 7, 19, 94, 5])

if __name__ == '__main__':
  main()

最小值求解

定一个列表 [2, 4, 9, 7, 19, 94, 5],请编写 find_min 函数,该函数返回列表的最小值。

这个函数也很容易,相信大家一定能够自行编写出来。

参考代码如下:

def find_min(nums):
    min=nums[0]
    for x in nums:
        if x<min:
            min=x
    pritn(min)

def main():
  find_max([2, 4, 9, 7, 19, 94, 5])

if __name__ == '__main__':
  main()

最大公约数/最小公倍数

在介绍最大公约数和最小公倍数之前,我们先来介绍下欧几里得算法

欧几里得算法

欧几里得算法又称辗转相除法,用来求两个正整数的最大公约数。以 1997 和 615 为例,用欧几里得算法求解如下:

1997=615*3+152

615=152*4+7

152=7*21+5

7=5*1+2

5=2*2+1

2=1*2+0

当被加的数为 0 时,可以得出,1997 和 615 的最大公约数为 1。

以上做法的依据是以下定理:

两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。

用数学表示为:gcd(a,b)=gcd(b,amodb)

并可以证明欧几里得算法一定能在有限步内结束。

利用图形可以更直观地理解欧几里得算法

数学算法 in python

程序设计:

数学算法 in python

算法思想:a,b 始终扮演被除数和除数的角色。

最大公约数(greatest common divisor)缩写为 gcd

参考代码如下:

def gcd(a,b):
    while b:
        r=a%b
        a=b #经过一次求余数,进入第二轮相除,即b的值扮演了被除数的角色
        b=r #r的值扮演了余数的角色
    return a #这里return a的原因是,b作为在最后一轮的除数已经幅值给了a

而对于最小公倍数则有以下思路求解:

a,b 两个数的最小公倍数乘以他们的最大公约数约等于 a 和 b 本身的乘积

LCM(a,b)GCD(a,b)=ab

所以最小公倍数的求解代码如下:

def lcm(a,b):
    return a*b//gcd(a,b)

请大家编程,构造一个 three_gcd 和 three_lcm 函数,该函数完成三个数的最大公因数和最小公倍数的求解

例子:

input:2,7,6

output:gcd:1 lcm:84

思路:

计算三个数的最大公约数时,利用之前写好的计算 2 个数的最大公约数的方法,先算出 a,b 的公约数,再用 a,b 的公约数与 c 再代入方法,此时返回的值就是三个数的最大公约数了。对于三个数的最小公倍数,继续嵌套,先求出两个数的,再求与第三个数的最小公倍数。

参考代码如下:

def gcd(a,b):
    while b:
        r=a%b
        a=b #经过一次求余数,进入第二轮相除,即b的值扮演了被除数的角色
        b=r #r的值扮演了余数的角色
    return a #这里return a的原因是,b作为在最后一轮的除数已经幅值给了a
def lcm(a,b):
    return a*b//gcd(a,b)
def three_gcd(a,b,c):
    return gcd(gcd(a,b),c)
def three_lcm(a,b,c):
    return lcm(a,b)*c//gcd(gcd(a,b),c)
def main():
    a=2
    b=7
    c=6
    print("a:",a)
    print("b:",b)
    print("c:",c)
    print("gcd(",a,",",b,",",c,"):",three_gcd(a,b,c))
    print("lcm(",a,",",b,",",c,"):",three_lcm(a,b,c))
if __name__=='__main__':
    main()

算术分析方法

在数学问题中,有很多算术分析方法。包括二分法,牛顿递归等等。我们将在接下来的章节里,逐一介绍这两种种方法。

二分法

二分法:对于区间[a,b]上连续不断且 f(a)·f(b)<0 的函数 y=f(x),通过不断地把函数 f(x)的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法叫二分法。

注:定义来自百度百科。

给定精确度 flex,用二分法求函数 f(x) 零点近似值的步骤如下:

  1. 确定区间 [a,b],验证 f(a)*f(b)<0,给定精确度 flex

  2. 求区间 (a,b) 的中点 c

  3. 计算 f©:

    • 如果 f©=0,则 c 就是函数的零点

    • 如果 f(a)*f©<0,则令 b=c

    • 如果 f©*f(b)<0, 则令 a=c

    • 判断是否达到精确度 flex,即若 |a-b|< flex,则得到零点近似值 a(或 b),否则重复 2-3 步骤

例子:请在[1,1000]区间内,找到 x^3-2*x-5 的零点

参考代码:

import math
def bisection(function, a, b):  #指定函数function,指定区间[a,b]
    start = a
    end = b
    if function(a) == 0:  #a即为零点
        return a
    elif function(b) == 0: #b即为零点
        return b
    elif function(a) * function(b) > 0: #二分法无法在该区间找到零点
        print("couldn't find root in [a,b]")
        return
    else: #f(a)*f(b)<0 的情况
        mid = (start + end) / 2
        while abs(start - mid) > 10**-7:  # 精确度设置为10**-7
            if function(mid) == 0:
                return mid
            elif function(mid) * function(start) < 0:
                end = mid
            else:
                start = mid
            mid = (start + end) / 2
        return mid

def f(x):
    return math.pow(x, 3) - 2*x - 5
if __name__ == "__main__":
    print(bisection(f, 1, 1000))

牛顿法

牛顿递归原理

在数值分析里面, 牛顿法(亦称牛顿拉弗森法)是一种方法,用于查找更好的根(或零点)的例子寻根算法。牛顿法也被用于求函数的最值。由于函数取最值的点处的导数值为零,故可用牛顿法求导函数的零点,其迭代式为数学算法 in python

关于牛顿法和拟牛顿法的区别,请参考牛顿法和拟牛顿法这篇回答

例子:请用牛顿法,求函数 x**3-2*x-5 在 3 附近的零点

参考代码如下:

def newton(function,function1,start):
    #function 是 f(x) function1 是 f(x)的导函数,即f'(x)
    x_n=start
    while True:
        x_n1=x_n-function(x_n)/function1(x_n) #递归式
        if abs(x_n-x_n1) < 10**-5:
            return x_n1
        x_n=x_n1
def f(x):
    return (x**3)-2*x-5
def f1(x):
    return 3*(x**2)-2
if __name__=="__main__":
    print(newton(f,f1,3))

问题升级:如果题目不提供导函数,只提供本函数表达式,但是提供一个合理区间,如何求解该区间内的零点?

还是采用牛顿法的思路,导函数可以采用斜率不断递归的方式。

数学算法 in python

参考代码如下:

def intersection(function,start1,start2):
    #function 是 f(x),start1,start2 是区间端点
    x_n=start1
    x_n1=start2
    while True:
        x_n2=x_n1-(function(x_n1)/((function(x_n1)-function(x_n))/(x_n1-x_n))) # 递归式
        if abs(x_n2-x_n1) < 10**-5:
            return x_n2
        x_n=x_n1
        x_n1=x_n2
def f(x):
    return (x**3)-2*x-5
if __name__=="__main__":
    print(intersection(f,3,3.5))

斐波那契数列问题

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34…

在数学上,费波那契数列是以递归的方法来定义的:

  • F(0) = 0

  • F(1) = 1

  • F(n) = F(n - 1) + F(n - 2) (n >= 2)

注:定义来自百度百科。

从数列上看,我们能看到这是一个典型的递归函数,python 递归方法实现。

我们在这里将介绍斐波那契数列的 3 种用 python 方法实现。

  1. 使用 python 递归方法实现斐波那契数列

思路:f(n)=f(n-1)+f(n),我们直接使用 python 递归方法实现斐波那契数列

参考代码如下:

def fibo(num):
    if num==1:
        return 1
    elif num==2:
        return 1
    elif num>2:
        return fibo(num-1)+fibo(num-2)
    else:
        print("False")

代码分析:递归函数特点,在函数内部调用自身本身;所以函数必须要求调用有底线,不能无穷向下调用,斐波那契数列的底线在 n=1 与 n=2

  1. 使用 python 独特的列表实现斐波那契数列

很多递归函数对于 python 来说非常简单,因为 python 有个独特的数据类型 list 列表,list 是动态的我们可以在尾部追加数据,这样一来斐波那契数列就是简单的加法游戏了。

参考代码如下

def fibo(num):
    if num==1:
        return [1]
    elif num==2:
        return [1,1]
    l=[1,1]
    for i in range(2,num):
        l.append(l[-2]+l[-1]) #将列表最后两项的值求和,将值添加到列表最后
    return l

print(fibo(10))
  1. 使用 while 循环实现
def fibo(num):
    a=1
    b=1
    i=0
    l=[]
    while i < num:
        l.append(a)
        a,b=a+b,a
        i=i+1
    return l
print(fibo(10))

关于斐波那契数列还可以用矩阵分解的思路,这里就不详述了,如果大家对矩阵分解的思路感兴趣,请自行探索。