在实际开发中会遇到一些工程问题,需要求解复杂函数方程的问题。使用传统的数学方法比较难以处理。本文将使用二分法不断获取一个函数的近似解。
二分法:其基本思想是利用函数在某个区间内的连续性,通过不断缩小区间范围来逼近方程的解。
算法前置要求
在使用二分法求解函数值近似解之前,需要满足以下几个前置条件:
-
函数连续性
为了确保在区间内存在一个近似解,函数 f(x)必须是连续的。否则,可能会因为函数不连续导致二分法无法收敛到正确的解。 -
单调性判断
在应用时需要确保函数在所选区间内为单调函数。二分法在求解过程中的边界更新依赖于函数的单调性判断。通过比较区间端点的函数值来决定是否为单调递增或者递减。 如果函数整体不是单调的,但是在某区间内单调递增或者单调递减,那么也可以求区间内的结果。 -
初始区间覆盖目标值
要求区间 [lowerBound,upperBound]的值域内必须包含目标值 target 的对应函数值。
以函数示例 f(x) = x^3 - 4x^2 + 6x - 24,同时满足3个条件: 连续性 && 单调性 && 起始位置覆盖区间值
算法思路
整个算法的核心步骤如下:
-
区间初始化
首先,在已知的区间 [lowerBound,upperBound]内,假设函数 f(x)在此区间内是单调连续的,并且存在满足条件的解。 -
迭代过程
- 计算当前区间的中点 mid。
- 判断当前区间长度是否小于 x 方向的容忍误差
toleranceX
,如果满足条件,则停止迭代。 - 同时,计算 f(mid) 与目标值
target
的差距,如果差距小于函数值的容忍误差toleranceResult
,则认为找到近似解。 - 判断函数的单调性
- 单调递增函数:如果 f(mid)小于
target
,则将lowerBound更新为 mid;如果f(mid)大于target,
则将upperBound更新为 mid。 - 单调递减函:如果 f(mid)大于
target
,则将lowerBound更新为 mid;如果f(mid)小于target,
则将upperBound更新为 mid。
- 单调递增函数:如果 f(mid)小于
-
结果判断
当迭代结束后,比较区间两端点以及中点的函数值误差,选择误差最小的点作为最终结果返回。
代码实现与详细注释
下面是 Swift 语言实现的完整代码,函数 f(x) 可以替换成其他单调连续函数:
import Foundationclass YCCloseToResult: NSObject {// 定义函数 f(x) = x^3 - 4x^2 + 6x - 24static func f(x: Double) -> Double {return pow(x, 3) - 4 * pow(x, 2) + 6 * x - 24}// 二分法:在区间 [lowerBound, upperBound] 内寻找使得 f(x) 接近 target 的 x 值/// - Parameters:/// - target: 目标函数值,即希望 f(x) 接近的数值。/// - lowerBound: 搜索区间的下界,表示在该值以上开始寻找满足条件的 x 值。/// - upperBound: 搜索区间的上界,表示在该值以下开始寻找满足条件的 x 值。/// - toleranceX: x 方向的容忍误差。当上下界之差小于此值时,认为 x 的精度已经满足要求,从而停止迭代。/// - toleranceResult: 函数值的容忍误差。当 f(x) 与 target 的差值小于此值时,认为已经找到了足够精确的近似解,从而停止迭代。/// - Returns: 返回使 f(x) 接近 target 的 x 值;如果在给定区间内没有找到合适的解,则返回 nil。static func bisectionMethod(target: Double, lowerBound: Double, upperBound: Double, toleranceX: Double = 0.01, toleranceResult: Double = 0.01) -> Double? {var lower = lowerBound // 区间下界var upper = upperBound // 区间上界var mid: Double = 0var loopCount = 0 // 记录循环次数var fMid: Double = 0 // 中点对应的 f(x) 值var fUpper: Double = self.f(x: upper) // 上界对应的 f(x) 值var fLower: Double = self.f(x: lower) // 下界对应的 f(x) 值if target > max(fLower, fUpper) || target < min(fLower, fUpper) {print("目标值不在给定区间内")return nil}// 开始迭代while true {loopCount += 1mid = (lower + upper) / 2 // 计算中点// 如果区间长度小于 x 的容忍误差,则认为 x 精度达到要求if (upper - lower) <= toleranceX {print("x误差达到精度要求")break}// 计算当前下界、中点和上界的函数值fMid = self.f(x: mid)fUpper = self.f(x: upper)fLower = self.f(x: lower)print("循环次数: \(loopCount); f(\(lower))=\(fLower), f(\(mid))=\(fMid), f(\(upper))=\(fUpper)")// 如果中点对应的函数值与目标值之间的误差满足要求,则退出循环if abs(fMid - target) < toleranceResult {print("结果达到精度要求")break} else {// 根据函数单调性判断:这里假设函数在区间内单调if fLower < fUpper { // 单调递增函数// 如果中点的函数值小于目标值,则更新下界if fMid < target {lower = mid} else { // 否则更新上界upper = mid}} else { // 单调递减函数if fMid < target {upper = mid} else {lower = mid}}}}print("总循环次数: \(loopCount)")// 在 lower, mid, upper 中选择哪个点的函数值最接近 targetlet absLower = abs(fLower - target)let absMid = abs(fMid - target)let absUpper = abs(fUpper - target)var result: Double = 0if absLower < absMid, absLower < absUpper {result = lower} else if absMid < absLower, absMid < absUpper {result = mid} else {result = upper}return result}
}// 测试函数
extension YCCloseToResult {// 示例入口函数,用于测试二分法查找static func test() {// 目标函数值let target = 10.13// x 的容忍误差:当上下界的差值小于这个值时停止迭代let toleranceX: Double = 0.01// 函数值的容忍误差:当 f(x) 与 target 的差值小于这个值时认为已找到近似解let toleranceResult: Double = 0.001// 调用二分法函数,寻找使 f(x) 接近 target 的 x 值if let result = self.bisectionMethod(target: target, lowerBound: -100, upperBound: 100, toleranceX: toleranceX, toleranceResult: toleranceResult) {print("\(target) 的近似解是 \(result)")// 分别打印 result 左侧、处于和右侧附近的 x 对应的函数信息self.showInfo(result: result-toleranceX, target: target)self.showInfo(result: result, target: target)self.showInfo(result: result+toleranceX, target: target)} else {print("在给定的范围内没有找到解。")}}// 打印函数 f(x) 的值以及与目标值 target 的误差static func showInfo(result: Double, target: Double) {let fRes = self.f(x: result)let absRes = abs(fRes - target)print("f(\(result)) 的值是 \(fRes), 误差为 \(absRes)")}
}
运行结果: 通过不断的二分接近,最终求得结果
10.13 的近似解是 4.401206970214844
f(4.401206970214844) 的值是 10.178870703912295, 和目标差值为 0.048870703912294644
二分查找的时间复杂度为O(logN),效率还不错。
使用Python画的函数图。
import matplotlib.pyplot as plt
import numpy as np# 定义函数 f(x)
def f(x):return x**3 - 4*x**2 + 6*x - 24# 生成 x 值
x = np.linspace(-10000, 10000, 10)
# 计算对应的 y 值
y = f(x)# 创建图形
plt.figure(figsize=(10, 6))
plt.plot(x, y, label=r'$f(x) = x^3 - 4x^2 + 6x - 24$')# 添加标题和标签
plt.title('Plot of $f(x) = x^3 - 4x^2 + 6x - 24$')
plt.xlabel('x')
plt.ylabel('f(x)')# 添加网格
plt.grid(True)# 显示图例
plt.legend()# 显示图形
plt.show()