目录
- 一、数学归纳法是什么
- 二、使用编程来模拟数学归纳法的证明
人类做重复性的劳动没有效率,而计算机却能更快更准确的完成重复性劳动。所以以重复为特点的迭代法在编程中有着⼴泛的应⽤。实际项目中是否可以用不断更新变量值或者缩小搜索的区间范围的方法,来获得最终的解(或近似解、局部最优解)?如果是,那么你就可以尝试迭代法。
程序员数学 | 迭代法
还有某些特定的迭代问题,其实可以用数学归纳法,避免⼀步步的计算,直接从理论上证明某个结论,节约⼤量的计算资源和时间。
一、数学归纳法是什么
数学归纳法最早出现于Francesco Maurolico的Arithmeticorum libri duo(1575年)。Maurolico利用递推关系巧妙地证明出前n个奇数的总和是n^2,由此总结出了数学归纳法。
在数论中,数学归纳法(Mathematical Induction)是以一种不同的方式来证明任意一个给定的情形都是正确的(第一个,第二个,第三个,一直下去概不例外)的数学定理。
对于类似⽆穷数列的问题,我们通常可以采⽤数学归纳法来证明。
- 常见的数学归纳法是证明当n等于任意一个自然数时某命题成立。证明分下面两步:
1.证明当n= 1时命题成立。
2.假设n=m时命题成立,那么可以推导出在n=m+1时命题也成立。(m代表任意自然数)
依然用那个麦子的故事举例:
棋盘的第⼀个小格内放⼊⼀粒麦子,在第⼆个小格内放⼊两粒,第三小格内放⼊给四粒,以此类推,每⼀小格内都⽐前⼀小格加⼀倍的麦子,直到放满64个格子。
为了便于理解,我们将这一命题分为两个命题来证明:
1、第
n
n
n个棋格放的麦粒数为
2
n
−
1
2^{n-1}
2n−1。
2、前
n
n
n个棋格放的麦粒数总和为
2
n
−
1
2^{n}-1
2n−1。
- 先来证明第⼀个子命题:
1)当 n = 1 n=1 n=1的时候,第⼀格内的⻨粒数为 1 1 1,和 2 1 − 1 2^{1-1} 21−1相等。
2)假设第 n − 1 n-1 n−1格的⻨粒数为 2 n − 2 2^{n-2} 2n−2。那么第 n n n格的⻨粒数为第 n − 1 n-1 n−1格的 2 2 2倍,也就是 2 n − 2 ∗ 2 = 2 n − 1 2^{n - 2}*2=2^{n-1} 2n−2∗2=2n−1。
所以,第⼀个子命题成⽴。
- 再证明第⼆个子命题。
1) n = 1 n=1 n=1的时候,所有格⼦的⻨粒总数为 1 1 1。
2)假设前 n − 1 n-1 n−1格的⻨粒总数为 2 n − 1 − 1 2^{n-1}-1 2n−1−1,基于前⼀个命题的结论,第n格的⻨粒数为 2 n − 1 2^{n-1} 2n−1。那么前 n n n格的⻨粒总数为 ( 2 n − 1 − 1 ) + ( 2 n − 1 ) = 2 ∗ 2 n − 1 − 1 = 2 n − 1 (2^{n-1}-1)+(2^{n-1})=2*2^{n-1}-1=2^{n}-1 (2n−1−1)+(2n−1)=2∗2n−1−1=2n−1。
第二个子命题也成立。
和使用迭代法的计算相比,数学归纳法最⼤的特点就在于“归纳”⼆字。它已经总结出了规律,只要我们能够证明这个规律是正确的,就没有必要进⾏逐步的推算,可以节省很多时间和资源。
二、使用编程来模拟数学归纳法的证明
仔细观察⼀下数学归纳法,其实与函数的递归调用很像。
在代码的实现中,我们可以将其转为函数的递归(嵌套)调用,被调用的函数逐步返回 n − 1 n-1 n−1时命题是否成⽴,直到被调用的函数回退到 n = 1 n=1 n=1的情况。
递归调用的代码和数学归纳法的逻辑是⼀致的。理解了数学归纳法就很容易理解递归调用。只要数学归纳证明的逻辑是对的,递归调用的逻辑就是对的,稍有不同的是,递归编程的代码需要返回若⼲的变量。
# 初始化第一个格子的麦子数量
grains = [1]
def count_grains(row):
# 基础情况:如果已经到了第六十四行,返回0表示满了
if row == 64:
return 0
# 根据规则,当前行的麦子数量是上一行的两倍
current_row = grains[row - 1] * 2
grains.append(current_row)
# 递归计算下一行
return count_grains(row + 1)
# 使用递归计算所有64行的总麦子数
total_grains = count_grains(1) + grains[-1]
print(f"总共需要的麦子数量为: {total_grains}")