跳到主要内容

4.2 数值计算

知识点​

1.什么是"数值计算"​

数值计算:以数值为处理对象,通过算术运算得到数值结果的问题。

典型特征:

  • 输入是数,输出也是数;
  • 可以用数学公式描述;
  • 常常需要近似(很多问题的精确解求不出来)。

本小节要掌握的四种策略:

策略一句话典型问题
解析法套公式直接算圆面积、一元二次方程
迭代法反复逼近,越来越接近答案求平方根、二分法求根、求 π
递推法用前一项推后一项斐波那契、累加、累乘
穷举法全试一遍,符合条件的留下百钱百鸡、水仙花数、素数判断

选择思路:有公式用解析 → 没公式用迭代 → 有前后关系用递推 → 实在不行就穷举。

2.解析法:套用公式​

思路:问题已经有明确的数学公式,直接代入计算即可。

关键:把公式准确翻译成表达式,注意运算优先级与单位换算。

示例 1:圆面积与周长

import math
r = float(input("半径:"))
area = math.pi * r ** 2 # πr²
perimeter = 2 * math.pi * r # 2πr
print(f"面积 {area:.2f},周长 {perimeter:.2f}")

示例 2:一元二次方程求根

import math
a, b, c = 1, -3, 2 # 对应 x² - 3x + 2 = 0
delta = b ** 2 - 4 * a * c # 判别式
if delta > 0:
x1 = (-b + math.sqrt(delta)) / (2 * a)
x2 = (-b - math.sqrt(delta)) / (2 * a)
print(x1, x2) # 2.0 1.0
elif delta == 0:
print(-b / (2 * a))
else:
print("无实数根")

要点:

  1. 先判断判别式,再求根(否则 math.sqrt 遇负数报错);
  2. 分母不能为 0(a != 0 是一元二次方程的前提);
  3. 除法用 /(真除法),注意括号:(-b + ...) / (2*a) 不能漏括号。

示例 3:单位换算与复合公式

# 摄氏温度转华氏温度:F = C × 9/5 + 32
c = float(input("摄氏温度:"))
f = c * 9 / 5 + 32
print(f"{c}℃ = {f:.1f}℉")

解析法的局限:很多问题没有公式(如求 sin(37°) 的值、求任意方程的根),这时就要迭代。

3.迭代法:用旧值逼近新值​

(1)什么是迭代​

核心思想:先给一个初始猜测值,然后用同一个公式不断算出更好的近似值,直到满足精度要求为止。

迭代的三要素:

  1. 初始值(猜一个);
  2. 迭代公式(怎么变好);
  3. 终止条件(什么时候够好了)。

标准模板:

x = 初始猜测值
while abs(新值 - 旧值) > 精度: # 终止条件
旧值 = 新值
新值 = 迭代公式(旧值)
结果 = 新值

精度通常取 1e-6(即 0.000001,1e-6 是科学计数法写法)。

(2)二分法求方程的根(经典,必会)​

问题:求 f(x) = x³ - 2x - 5 = 0 在区间 [2, 3] 内的根。

原理:若 f(a) 与 f(b) 异号(一正一负),则区间内必有根。取中点 m,看根在哪一半,不断把区间折半。

# 4-1 二分法求 x³ - 2x - 5 = 0 在 [2,3] 内的根
def f(x):
return x ** 3 - 2 * x - 5

a, b = 2.0, 3.0
while b - a > 1e-6: # 区间足够小就停
m = (a + b) / 2
if f(a) * f(m) <= 0: # 根在左半区间
b = m
else: # 根在右半区间
a = m
print(f"根约为 {(a + b) / 2:.6f}") # 约 2.094551

要点:

  • 每次区间减半,所以复杂度是 O(log n)——100 次二分可把区间缩小到 10⁻³⁰ 量级;
  • 必须保证初始区间两端异号,否则不保证有根;
  • 教材常以"猜数字(1~100)"作为二分法的直观例子:每次猜中间值,7 次必中。

(3)牛顿迭代法求平方根​

原理:求 √a 等价于解 x² - a = 0。牛顿迭代公式:

x(n+1) = (x(n) + a / x(n)) / 2
# 4-2 用牛顿迭代法求平方根
def my_sqrt(a):
x = a / 2 # 初始猜测
while abs(x * x - a) > 1e-9: # 精度控制
x = (x + a / x) / 2
return x

print(f"{my_sqrt(2):.10f}") # 1.4142135624

优点:收敛极快(每步有效数字大约翻倍),一般 5~6 次就够; 缺点:对初始值敏感,初值太离谱可能不收敛。

(4)用级数求 π​

莱布尼茨级数:

π/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - …
# 4-3 用级数求 π(交错求和)
pi_4 = 0
sign = 1
for i in range(1, 1000000, 2): # 分母 1,3,5,…
pi_4 += sign / i
sign = -sign # 正负号交替
print(f"π ≈ {pi_4 * 4:.6f}")

要点:正负号交替用 sign = -sign 实现;精度随项数增加而提高,但收敛很慢(100 万项才 6 位左右)——这正说明"算法效率"的重要。

另一种经典方法:蒙特卡洛法(随机投点)

import random
n = 100000
hit = 0
for _ in range(n):
x, y = random.random(), random.random()
if x * x + y * y <= 1: # 落在 1/4 圆内
hit += 1
print(f"π ≈ {4 * hit / n:.4f}") # 面积比 = π/4

思想:用频率估计概率,点越多越接近 π。这是"用随机模拟解决确定问题"的典型。

4.递推法:由前一项推出后一项​

定义:已知初始项,通过递推关系逐项算出后面的项。

与迭代的关系:递推是迭代的一种具体形式(按序号一步步推)。

(1)累加与累乘(最基础的递推)​

# 求 1+2+…+100
s = 0
for i in range(1, 101):
s = s + i # 递推关系:S(n) = S(n-1) + n

# 求 10!
f = 1
for i in range(1, 11):
f = f * i # 递推关系:F(n) = F(n-1) × n

(2)斐波那契数列​

定义:F(1) = 1, F(2) = 1, F(n) = F(n-1) + F(n-2)(n ≥ 3)。

# 4-4 输出前 20 项斐波那契数
a, b = 1, 1
for i in range(20):
print(a, end=" ")
a, b = b, a + b # 两变量滚动,省去数组

要点:用两个变量"滚动",不需要保存整个数列(空间 O(1));这体现了"用少量变量实现递推"的技巧。

注意:斐波那契增长极快,第 90 项左右就超出普通整数范围(Python 支持大整数,但其他语言可能溢出)。

(3)辗转相除法求最大公约数​

# 4-5 辗转相除
def gcd(m, n):
while n != 0:
m, n = n, m % n # 递推:gcd(m,n) = gcd(n, m%n)
return m

print(gcd(48, 18)) # 6

原理:m 与 n 的最大公约数等于 n 与 m % n 的最大公约数,余数为 0 时除数即为答案。

5.穷举法(枚举法)​

思路:把所有可能的情况列举出来,逐个检验,满足条件的输出。

适用条件:

  1. 解的范围有限且可枚举;
  2. 有明确的检验条件。

(1)水仙花数​

# 4-6 三位数中各位数字立方和等于本身的数
for n in range(100, 1000):
a = n // 100 # 百位
b = n // 10 % 10 # 十位
c = n % 10 # 个位
if a**3 + b**3 + c**3 == n:
print(n, end=" ") # 153 370 371 407

(2)百钱百鸡​

# 4-7 公鸡 5 元、母鸡 3 元、小鸡 1 元 3 只,100 元买 100 只
for x in range(0, 21): # 公鸡最多 20 只
for y in range(0, 34): # 母鸡最多 33 只
z = 100 - x - y # 小鸡数量由总数推出,省一层循环
if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100:
print(f"公鸡{x}只,母鸡{y}只,小鸡{z}只")

穷举的两条优化法则(重要):

  1. 缩小枚举范围——用总数量、总金额等约束算出上界(公鸡不会超过 20 只);
  2. 减少循环层数——用"总数 - 已知部分"直接算出未知量,把三重循环降为两重,效率提升几十倍。

(3)判断素数​

# 4-8 判断素数(优化版)
n = int(input("n = "))
is_prime = True
if n < 2:
is_prime = False
else:
for i in range(2, int(n ** 0.5) + 1): # 只需试到 √n
if n % i == 0:
is_prime = False
break # 找到因数立即结束
print("是素数" if is_prime else "不是素数")

两个优化点:

  • 只需试到 √n——因为若 n = a × b,则 a、b 中必有一个不超过 √n;
  • 找到因数就 break——不必试完。

效率对比:判断 1000003 是否素数,试到 √n 约 1000 次,而试到 n-1 要 100 万次——相差 1000 倍。

6.递归:自己调用自己(拓展)​

定义:函数直接或间接地调用自身,把问题转化为规模更小的同类问题。

两要素:

  1. 终止条件(边界)——最小的、已知答案的情况;
  2. 递归关系——大问题如何化成小问题。
# 4-9 递归求阶乘
def fact(n):
if n == 1: # 终止条件
return 1
return n * fact(n - 1) # 递归关系

print(fact(5)) # 120

递归与递推的对比:

对比递推(循环)递归
方向从已知到未知,正向推从大到小,层层深入再逐层返回
代码稍长短,贴近数学定义
效率高有函数调用开销,可能重复计算
空间通常 O(1)与递归深度成正比
适用顺序明确的累加、数列具有自相似结构的问题(汉诺塔、树的遍历)

结论:能用循环就用循环;结构天然递归(如汉诺塔、目录遍历)时用递归更简洁。

递归的陷阱:没有终止条件或终止条件永远达不到,会报 RecursionError: maximum recursion depth exceeded。

7.实现细节与易错点​

序号易错点说明正确做法
1精度写成 1e6那是 100 万,不是 0.0000011e-6
2用 == 判断两个浮点数相等浮点误差导致永远不成立abs(a - b) < 1e-9
3二分法初始区间两端同号区间内可能无根,结果错先验证 f(a) * f(b) <= 0
4迭代不更新变量死循环循环体内必须有 x = 新值
5穷举范围开太大运行极慢甚至超时用约束缩小范围
6math.sqrt 参数为负ValueError: math domain error先判断非负
7除法分母为 0ZeroDivisionError先判断
8整数除法用了 / 得到小数结果类型不符需要整数时用 //
9递归无终止条件RecursionError先写边界
10级数求 π 忘记交错符号结果偏离很大sign = -sign 交替

精度控制的三种常见写法(等价,任选):

while abs(x_new - x_old) > 1e-6: # 相邻两次差小于精度
while abs(f(x)) > 1e-6: # 函数值足够接近 0
while b - a > 1e-6: # 区间足够小

8.速查表​

序号考点结论
1四种数值计算策略解析、迭代、递推、穷举
2解析法套公式直接算
3解析法前提有明确的数学公式
4一元二次方程先算判别式 Δ = b²-4ac
5迭代三要素初始值、迭代公式、终止条件
6常用精度1e-6(0.000001)
7二分法前提区间两端函数值异号
8二分法复杂度O(log n)(每次减半)
9牛顿迭代求平方根公式x = (x + a/x) / 2
10级数求 π 的符号正负交替(sign = -sign)
11蒙特卡洛求 π 的原理面积比 = π/4,用频率估计概率
12递推由前项推后项
13累加器初值0
14累乘器初值1
15斐波那契递推式F(n)=F(n-1)+F(n-2),前两项为 1
16斐波那契实现技巧两个变量滚动,空间 O(1)
17辗转相除gcd(m,n)=gcd(n,m%n),余 0 时除数即答案
18穷举两法则缩小范围 + 减少循环层数
19判断素数优化试到 √n,找到因数 break
20水仙花数范围100~999(三位数)
21递归两要素终止条件 + 递归关系
22递归无终止的报错RecursionError
23递归空间复杂度与递归深度成正比
24浮点比较用 abs(a-b) < 1e-9

9.课堂提示(讲课参考)​

  • "猜数字"引出二分法:先让学生玩 1~100 猜数字,问"最少几次必中",答案是 7 次(2⁷=128)——log 的概念顺势讲出。
  • 牛顿迭代的"神奇"现场演示:打印每次迭代的 x 值,学生看到 1.5 → 1.4167 → 1.414215…三步就精确到 6 位,会很有触动。
  • 穷举优化的对比实验:百钱百鸡先写三重循环(约 100 万次),再改成两重(约 700 次),用计时器显示时间差。
  • 精度写成 1e6 的坑要让学生踩一次:程序跑很久不结束,再解释 1e-6 与 1e6 的差别。
  • 递推与递归用斐波那契对照讲:同一数列两种写法,比较代码长度与执行效率,再引到 4.3 汉诺塔"非递归不可"的场景。