数学思维,就是用数学的方式去解决问题,就象吃饭用筷子、喝水用杯子一样,自然而然又理所当然。数学思维并非知识的积累,而是一种由特定思维习惯蕴育而成的能力——这种特定习惯的养成,往往是从解决看似简单的问题开始。本文正是从最简单的自由落体运动抽象出数学分析的一般性原理,进而用来回答蠕虫悖论问题。即使对数学抱有恐惧心理的同学,若能静下心来花上十分钟阅读一遍,也一定可以从中体会到数学之美以及数学分析这个工具的威力。

将长度1米的橡皮筋的一端固定,小明拉着另一端以1米/秒的速度向前奔跑。与此同时,一条毛毛虫从橡皮筋固定的一端开始以1厘米/秒的速度追赶小明。假定橡皮筋可以无限均匀拉伸,小明和毛毛虫都可以长生不老,那么,毛毛虫最终能够追得上小明吗?

设计一个描述自由落体Python python自由落体运动_蠕虫悖论


这就是被称为“蠕虫悖论”的经典问题,据说是由古希腊数学家芝诺(Zeno of Elea)提出来的——我对此表示怀疑:四千多年前的古希腊人应该不会制造橡皮筋吧?古希腊儿童也许会跳皮筋,但跳的肯定不是橡皮筋。

不过,考古不是我们的话题,搞明白毛毛虫最终能否追得上小明才是当务之急。乍一看,这是一个典型的追击问题,小学生最拿手。可细细琢磨,每秒钟爬1厘米的毛毛虫似乎永远也不可能追上每秒钟跑100厘米的小明。

抱着脑袋想了很久,稍微有了一点眉目:毛毛虫爬过的距离和橡皮筋总长之比设计一个描述自由落体Python python自由落体运动_数学思维_02,一定是时间设计一个描述自由落体Python python自由落体运动_三角函数_03的函数,记作:
设计一个描述自由落体Python python自由落体运动_python_04

因为橡皮筋的拉伸是均匀的,当毛毛虫处于某个位置时,如果停止爬行,设计一个描述自由落体Python python自由落体运动_数学思维_02就保持不变;只要向前爬,设计一个描述自由落体Python python自由落体运动_数学思维_02就一定是单向递增的;当设计一个描述自由落体Python python自由落体运动_数学思维_02等于1时,就意味着毛毛虫追上了小明,此时对应的设计一个描述自由落体Python python自由落体运动_三角函数_03就是毛毛虫追上小明所花费的时间。

可是,设计一个描述自由落体Python python自由落体运动_三角函数_09又该如何表示呢?既然是追击问题,我们不妨梳理一下从小到大积累起来的、和速度路程相关的知识点,也许用最简单的思想就能解决这个看似复杂的问题。

小学阶段,我们学会了解决以匀速运动为前提的速度和路程问题。假设物体在设计一个描述自由落体Python python自由落体运动_三角函数_03时间内移动的距离为设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_11,则物体移动的平均速度设计一个描述自由落体Python python自由落体运动_python_12记作:

设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_13

中学阶段,我们理解了加速度的概念。假设物体运动的初始速度是设计一个描述自由落体Python python自由落体运动_三角函数_14,加速度为设计一个描述自由落体Python python自由落体运动_蠕虫悖论_15,经过设计一个描述自由落体Python python自由落体运动_三角函数_03时间后的物体运动速度设计一个描述自由落体Python python自由落体运动_python_12记作:

设计一个描述自由落体Python python自由落体运动_蠕虫悖论_18

对于自由落体而言,加速度一般用设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_19表示(约等于设计一个描述自由落体Python python自由落体运动_蠕虫悖论_20),其初始速度为0,自由落体的瞬时速度设计一个描述自由落体Python python自由落体运动_python_12记作:

设计一个描述自由落体Python python自由落体运动_python_22

设计一个描述自由落体Python python自由落体运动_三角函数_03时间内,自由落体下落的距离设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_11记作:

设计一个描述自由落体Python python自由落体运动_python_25

这里,自由落体的瞬时速度设计一个描述自由落体Python python自由落体运动_python_12和下落距离设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_11都是时间设计一个描述自由落体Python python自由落体运动_三角函数_03的函数,二者之间可以互相推导:对距离函数设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_11求导就是瞬时速度函数设计一个描述自由落体Python python自由落体运动_python_12,对瞬时速度函数设计一个描述自由落体Python python自由落体运动_python_12积分,就是距离函数设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_11

设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_33
设计一个描述自由落体Python python自由落体运动_数学思维_34

上式中的常数C表示在自由落体开始降落前的下降距离,显然,C=0。如果理解这两个公式有点吃力,那么请牢记以下三点,这是应用数学分析的方法解决实际问题的三大法宝。

  • 导数是变化率
  • 导数是速度
  • 导数是斜率

导数是变化率!忽然间灵光乍现:既然毛毛虫爬过的距离和橡皮筋总长之比设计一个描述自由落体Python python自由落体运动_数学思维_02函数不容易直接写出来,何不尝试一下这个函数的导数设计一个描述自由落体Python python自由落体运动_蠕虫悖论_36——也就是在任意的设计一个描述自由落体Python python自由落体运动_三角函数_03时刻单位时间内毛毛虫的爬行距离与橡皮筋的长度之比?

不难理解,任意设计一个描述自由落体Python python自由落体运动_三角函数_03时刻的橡皮筋长度为设计一个描述自由落体Python python自由落体运动_三角函数_39;单位时间内毛毛虫的爬行距离是1厘米,换算成标准单位是0.01米。MyGod,没想到设计一个描述自由落体Python python自由落体运动_蠕虫悖论_36就这么轻易地写出来了:

设计一个描述自由落体Python python自由落体运动_蠕虫悖论_41

接下来,只要对设计一个描述自由落体Python python自由落体运动_蠕虫悖论_36积分,就可以得到函数设计一个描述自由落体Python python自由落体运动_数学思维_02了。积分,就得需要求导公式。也许有些同学已经忘记了如何求导,没关系,只要理解导数的意义,求导公式可以很容易地在网上找到。下面是常用六大基本初等函数的求导公司,其中三角函数和反三角函数的求导公式,我只记住了两个。

  1. 常数函数
    设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_44
  2. 幂函数
    设计一个描述自由落体Python python自由落体运动_蠕虫悖论_45
  3. 指数函数
    设计一个描述自由落体Python python自由落体运动_蠕虫悖论_46
    设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_47
  4. 对数函数
    设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_48
  5. 三角函数
    设计一个描述自由落体Python python自由落体运动_蠕虫悖论_49
    设计一个描述自由落体Python python自由落体运动_三角函数_50
  6. 反三角函数
    设计一个描述自由落体Python python自由落体运动_蠕虫悖论_51
    设计一个描述自由落体Python python自由落体运动_蠕虫悖论_52

显而易见,设计一个描述自由落体Python python自由落体运动_蠕虫悖论_36的模样最接近对数函数的导数形式,可以很轻松地写出积分式:

设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_54

在毛毛虫开始爬行前设计一个描述自由落体Python python自由落体运动_数学思维_02为0,因此上式中的设计一个描述自由落体Python python自由落体运动_数学思维_56也等于0。有了设计一个描述自由落体Python python自由落体运动_数学思维_02函数,蠕虫悖论问题就变成了求解如下方程的问题:

设计一个描述自由落体Python python自由落体运动_python_58

至此,蠕虫悖论问题总算走对了方向,剩下的工作都是水到渠成、手到擒来的事了。对于这个方程,至少有3种方式可以求解。

1. 调包侠:使用SciPy模块求解非线性方程

SciPy的优化与求根子模块scipy.optimize提供了非线性方程的求解方案。使用SciPy求解非线性方程要求将方程表示为 设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_59设计一个描述自由落体Python python自由落体运动_数学思维_60的形式,这里的函数设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_61就是求根函数的参数。如果函数设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_61只有一个未知数,则称为标量函数,可用optimize.root_scalar()函数求根;如果函数设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_61包含多个未知数,则称为向量函数,可用optimize.root()函数求根。

对标量函数求根的函数optimize.root_scalar(), 除了需要传入标量函数这个必要参数,还接受method(求根方法)、bracket(求根区间)、fprime(标量函数是否可导)等参数。大多数情况下,只需要给出 bracket 参数即可。对于本例,猜测方程的根在设计一个描述自由落体Python python自由落体运动_python_64设计一个描述自由落体Python python自由落体运动_三角函数_65之间。

>>> import numpy as np
>>> from scipy import optimize
>>> def k(t):
	return 0.01*np.log(1+t) - 1

>>> result = optimize.root_scalar(k, bracket=[1e4, 1e50]) # bracket指定求根区间
>>> result.root # 方程的根
2.688117141816133e+43
>>> k(result.root) # 检验方程根,越接近0,近似程度越高
0.0
>>> result.iterations # 迭代求解的次数
32

结果显示,经过32次迭代,root_scalar()返回的方程根为设计一个描述自由落体Python python自由落体运动_python_66,带入原方程检测,误差为0。也就是说,毛毛虫最终一定可以追上小明,只是时间有点长,大约需要设计一个描述自由落体Python python自由落体运动_python_66秒钟,相当于设计一个描述自由落体Python python自由落体运动_数学思维_68年。

2. 经典派:使用牛顿逼近法求解非线性方程

如果不喜欢调包,这个方程也可以用我在《Python代码中的数学之美:用牛顿逼近法计算2的算术平方根》一文中介绍的牛顿逼近法来求解。

>>> import numpy as np
>>> def k(t):
	return 0.01*np.log(1+t) - 1

>>> def dk(t):
	return 0.01/(1+t)

>>> def newton_raphson_method():
    # 牛顿-拉弗森方法

    tiny = 1e-15 # 当k(t_n)小于tiny时,迭代结束
    i, t = 0, 10000 # 迭代计数器和初始值
    while True:
        i += 1 # 迭代计数器加1
        t = t-k(t)/dk(t) # 计算下一个t
        if abs(k(t)) < tiny: # 满足迭代结束条件
            return(i, t)

        
>>> newton_raphson_method()
(32, 2.688117141816148e+43)

如果以设计一个描述自由落体Python python自由落体运动_python_64作为初始值的话,同样经过32次迭代,使用牛顿逼近法求得的方程根为设计一个描述自由落体Python python自由落体运动_python_66,精度和上面的方法相比略有差异,小到可以忽略不计。

3. 太极派:四两拨千斤

不管是调包侠还是经典派,本质上都是反复迭代渐次逼近的方法,所得到的方程根是近似解。实际上,这个方程可以用纯数学的方式轻松得到精确解。以下是求解过程

设计一个描述自由落体Python python自由落体运动_python_58
设计一个描述自由落体Python python自由落体运动_python_72
设计一个描述自由落体Python python自由落体运动_python_73
设计一个描述自由落体Python python自由落体运动_三角函数_74
设计一个描述自由落体Python python自由落体运动_设计一个描述自由落体Python_75

最终求得设计一个描述自由落体Python python自由落体运动_数学思维_76,这个数字写出来如下:

>>> import numpy as np
>>> np.power(np.e, 100) - 1
2.6881171418161212e+43

这个精确解和前两种方式计算出来的近似解,尾数的前13位相同,三者之间的差异几乎可以忽略。