算法学习(17)二分法求非线性方程的解

1.前言

算法学习这部分,自从离开了数论后,程序效率可提升的地方变少了,因此看起来,好像没意思了……换句话说,之前那种通过尝试不同语句、不同求解思路来极大提升程序效率的快感没了……这好像也间接导致了我更新博客的速度变慢了(有推脱自己三分钟热度的嫌疑)……

接下来这部分的内容,程序主要依赖于数学分析,而程序的主体,按着数学分析的步骤表述清楚即可,因此,分析说明会更加简单。(写博客的初衷是为了督促自己持续编程,而不是为了写成很详细的说明文档,具体专题,还是要参考书籍,请谅解~)

2.问题

用二分法求方程
2 x 3 − 5 x − 1 = 0 2x^3-5x-1=0 2x35x1=0
在区间 [ 1 , 2 ] [1,2] [1,2]的根,使绝对误差不超过 1 0 − 5 10^{-5} 105

2.1 名词解释

  1. 二分法:又称对分法,最简单的解一元非线性方程根的算法之一。基本思路是,将含根区间 I I I 逐次分半减小,得到一个区间长度以 I / 2 I/2 I/2 的比例减小的含根区间序列 I 1 , I 2 . . . . . I_1,I_2..... I1,I2.....
  2. 绝对误差:区间长度 I i I_i Ii 小于给定的限制,如本题的 1 0 − 5 10^{-5} 105

3.编程解决

3.1 编程解决

3.1.1 编程思路

  1. 从给定区间 [ a , b ] [a, b] [a,b] 开始,验证区间端点,一定要满足 f ( a ) ∗ f ( b ) < 0 f(a) *f(b)<0 f(a)f(b)<0, 不满足,一般不存在根或是区间太过宽泛
  2. 取区间 [ a , b ] [a, b] [a,b]中点 c = a + b 2 c=\frac{a+b}{2} c=2a+b, 验证 f ( c ) f(c) f(c)是否等于 0 0 0, 等于则 c c c就是方程的解,退出;否则到第3步
  3. 区间缩小一半。判断是 f ( a ) ∗ f ( c ) < 0 f(a)*f(c)<0 f(a)f(c)<0 还是 f ( b ) ∗ f ( c ) < 0 f(b)*f(c)<0 f(b)f(c)<0,目的是提出乘积小于0的组合,然后,把 c c c替换成 a a a 或是 b b b; 跳到步骤2继续执行,知道区间长度 b − a < 1 0 − 5 b-a<10^{-5} ba<105, 跳出循环

3.1.2 实现代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2019-05-03 20:54:28
# @Author  : Promise ([email protected])
# @Link    : ${link}
# @Version : $Id$

# 用二分法求方程的关键是,先定义返回待计算函数值的函数,然后判断端点的取值,从而缩小区间
import math


# 定义函数
def func(x):
    return 2*x*x*x - 5*x - 1


# 给定根的区间,通过不断判断区间中点的函数值是否小于误差,还有区间端点的精度够不够,从而决定是不是拿中点当解
def findAnswer(a, b):  # 输入解的端点
    # 正常这里要检查端点,先忽略了
    if func(a) * func(b) > 0:
        print('所选区间有误,请更正!')  # 很大程度会是错的
        return None
    middle = (a + b) / 2
    while (b-a) > 1e-5 or math.fabs(func(middle)) > 1e-5:  # 一方面是区间要小于10的负五,取值的差要衡量跟0的距离
        # 更新区间
        if func(middle) * func(a) < 0:
            b = middle
        elif func(middle) * func(b) < 0:
            a = middle
        else:
            print('所选区间不止一个根')
        middle = (a + b) / 2  # 更新区间
    return middle


def main():
    a, b = eval(input('请输入区间端点:'))
    result = findAnswer(a, b)
    print('解x=', result)
    print(func(result))


if __name__ == '__main__':
    main()

3.1.3 运行效果

运行效果图
上图是只有单独一个判断条件 ( b − a ) > 1 0 − 5 (b-a)>10^{-5} (ba)>105
在这里插入图片描述
本图是判断条件有两个 ( b − a ) > 1 0 − 5 (b-a)>10^{-5} (ba)>105 and ∣ f u n c ( m i d d l e ) ∣ > 1 e − 5 |func(middle)| > 1e-5 func(middle)>1e5, 第二个条件用于刻画函数值跟零的接近程度,由于舍入误差,我们不可能得到函数值完全等于 0 0 0的情况,于是,只能借助不等式来表达。不过,对比上图,二者差别其实不大。

3.1.4 代码分析

  1. 单独定义一个方程求值的函数 f u n c ( x ) func(x) func(x),便于值比较 ,更高级的求解,是函数可以输入系数,这里从简,省略了
  2. 在求解的主函数,判断区间是否可用,很重要,如果区间一开始不满足端点符号相异,接下来的求解都没有意义。
  3. 根据实验结果的分析,只要根的精确度在给定的区间,函数值一般不会差太多,所以增加函数值判断的条件,可有可无
  4. 注意区间端点的更新条件

总结

本篇博客使用求解非线性方程最简单额二分法进行求解,难点在于区间更新和值精确度的确定,弄懂了这两个,就能正确求解。

下一篇是牛顿迭代法求解。

你可能感兴趣的:(学习札记)