资讯动态

秦九韶算法与二分法Python实战:算法复杂度可视化验证

发布时间:2026/10/24 18:13:23 来源:尧图企业网站定制
简介本资源是广东工业大学《数值分析》课程Python实验一的完整报告文档面向数学与计算机相关专业本科生聚焦算法效率对比与非线性方程求解两大核心数值计算问题。文档以实验报告形式系统呈现第一部分通过直接法与秦九韶算法计算高次多项式最高10000次详列x0.1/1/2三组测试下的乘法/加法次数及运行时间对比直观揭示秦九韶算法在大幅降低运算复杂度上的优势第二部分实现二分法求解x²−20完整记录17次迭代过程、区间缩放逻辑及函数值符号判定规则并附可运行Python源码。资源为1个29KB的docx文件结构清晰含实验目的、算法原理、结果表格、代码实现与深度讨论便于理解算法思想与编程实践。目前已有211人学习下载适合数值分析初学者巩固理论、提升Python数值编程能力。1. 这不是“写个for循环就交作业”的数值实验它用两个真实可跑的Python脚本把算法复杂度从“玄学概念”变成终端里跳动的毫秒数和乘法计数器你有没有在写完一个多项式求值函数后被导师问过“x2时你这段代码到底执行了多少次乘法”——不是理论推导是真·数出来。广东工业大学这份Python数值分析实验一就是干这个的它不讲大道理直接甩给你两段能跑、能改、能测、能对着表格逐行验证的代码——一段是教科书级的“暴力直球”直接法一段是古籍里走出来的“降维打击”秦九韶算法再加一个手搓二分法求根的完整迭代日志生成器。它面向的是刚学完for和def、但还没见过“浮点误差怎么吃掉你第15位有效数字”的人也面向那些想拿它当模板快速搭起自己数值方法验证框架的开发者。所有代码都在本地Python 3.8环境一键可跑不需要任何第三方包连numpy都不用——因为它的设计哲学很朴素让计算代价肉眼可见让算法优劣不再靠背诵而靠time.perf_counter()打出来的那个小数点后四位。2. 项目结构与技术栈纯Python轻量级数值验证套件零依赖、全手动、重过程这份实验资源不是工程级项目而是一个高度聚焦的教学验证套件。它没有requirements.txt没有setup.py甚至没有子目录——所有逻辑都压在三个独立.py文件里对应实验报告中的三大模块。但它对Python语言特性的运用非常扎实精准使用time.perf_counter()而非time.time()做高精度计时用len(str(x))粗略估算有效数字位数虽有浮点表示局限但教学场景下足够直观用open(1.txt, a)持续追加写入二分法迭代轨迹形成可直接拖进Excel分析的结构化日志。整个技术栈就是Python标准库的“核心四件套”time计时、math虽未显式import但x*x即其子集、builtinsprint,len,range,input、io文件读写。没有魔法全是肌肉记忆级别的基础操作——这恰恰是它能被快速复现、深度改造的根本原因。2.1 文件清单与职责划分三文件即三战场整个资源解压后只有3个.py文件命名直白无歧义文件名对应实验核心职责是否含输入交互exp2_direct.py实验2算法1直接法计算 $ f(x) \sum_{k0}^{10000} (k1)x^k $统计乘法/加法次数、耗时、结果长度否x值硬编码exp2_qin.py实验2算法2秦九韶算法同一多项式用嵌套乘法形式 $ f(x) 1 x(2 x(3 \cdots x(10001)\cdots)) $ 实现精确计数乘加次数否x值硬编码exp3_bisection.py实验3二分法求解 $ f(x)x^2-20 $ 在[1.3,1.5]内的根实时打印迭代过程并追加写入1.txt是需手动输入上下限提示三个文件彼此完全解耦可单独运行、单独修改、单独计时。不要试图把它们合并成一个主程序——实验设计的本意就是让你隔离对比而不是封装调用。2.2 多项式定义的隐含契约理解f(x)的数学本体是读懂所有代码的前提实验中反复出现的多项式并非随意构造而是有明确数学定义的$$ f(x) 1 2x 3x^2 4x^3 \cdots 10001x^{10000} \sum_{k0}^{10000} (k1) x^k $$注意三点关键细节系数规律第 $k$ 项$k$ 从0开始的系数是 $(k1)$不是 $k$。所以常数项是 $1$一次项是 $2x$二次项是 $3x^2$……最高次项是 $10001x^{10000}$。项数确认从 $k0$ 到 $k10000$共10001 项。源码中range(10000)循环了10000次但起始项sum 1已包含常数项因此总项数正确。秦九韶变形依据该多项式可重写为嵌套形式 $$ f(x) 1 x\left(2 x\left(3 x\left(\cdots x\left(10000 x \cdot 10001\right)\cdots\right)\right)\right) $$ 这正是exp2_qin.py中S x*S i的数学来源其中i从10000递减到1最后一步隐含1常数项。不厘清这个看代码就会像在读天书——比如看到S x*S i时疑惑“为什么i从10000开始”答案就藏在这个展开式里。2.3 核心数据记录机制如何让“运算次数”从理论走向实测实验报告里那张漂亮的对比表格表1不是靠人脑心算出来的而是代码里埋好的“计数器”实时吐出的。这是本项目最值得拆解的设计细节直接法的计数盲区exp2_direct.py中只用了time.perf_counter()并未统计乘法/加法次数它依赖的是Python解释器执行x**(n1)和sum ...时底层C实现的运算量属于“黑盒测量”。这恰恰暴露了一个教学重点理论复杂度O(n²)和实际耗时之间隔着硬件、编译器优化、幂运算实现等多重黑匣子。秦九韶法的白盒计数exp2_qin.py则完全不同。它用两个整型变量countMul和countAdd在每次执行S x*S i时手动1。因为这条语句明确包含1次乘法x*S和1次加法 i且循环10000次所以最终countMul countAdd 10000。这是真正的“白盒验证”把算法步骤和机器指令一一映射。二分法的日志契约exp3_bisection.py的file_handle.writelines(...)不是随便写的。格式字符串{:.15g} {:.15g} {:.15g} {:.15g} {:.15g}\n确保了5列数据迭代序号、xlow、xup、middle、符号标识以紧凑、无科学计数法、保留15位有效数字的方式写入方便后续用pandas.read_csv()或Excel直接识别为数值列。这种“计数方式”的差异本身就是一堂关于测量方法论的课什么时候该信理论推导什么时候必须亲手埋点3. 核心模块逐行解析从秦九韶的嵌套逻辑到二分法的区间收缩现在我们沉到代码最深处一行行拆解这两个最具教学价值的模块。不讲原理只讲这一行代码在做什么、为什么这么写、改哪里会翻车。3.1 秦九韶算法实现exp2_qin.py的12行如何完成10000次降维打击import time x 2 powN 10000 aN powN 1 # aN 10001, 即最高次项系数 countMul 0 countAdd 0 length 0 start time.perf_counter() S aN # 初始化 S 10001 (最高次项系数) for i in range(10000, 0, -1): # i 从 10000 递减到 1 S x * S i # 关键S_{k} x * S_{k1} a_k countAdd 1 countMul 1 end time.perf_counter() length len(str(S)) print(result, S) print(乘法次数, countMul) print(加法次数, countAdd) print(length, length) print(time%.2g 秒\n % (end-start))逐行逻辑说明与参数深挖S aN初始化S为最高次项系数10001。这是秦九韶算法的起点对应公式中最内层括号(... x * 10001)里的10001。for i in range(10000, 0, -1)循环变量i代表当前要加入的低一次项的系数。当i10000时计算的是x*10001 10000即10000x 10001x²的合并当i1时计算的是x*S_prev 1即最终加上常数项1。注意range(10000, 0, -1)生成的是[10000, 9999, ..., 1]共10000个数完美覆盖从x^1到x^{10000}的所有中间系数常数项1在最后一次i中加入。S x * S i这是整个算法的灵魂。它把高次幂的重复计算压缩成一次乘法加一次加法。x * S是将上一轮累积的“高次部分”整体左移一位相当于乘以x i是补上当前阶数的系数。此行执行1次乘法、1次加法故countMul和countAdd各1。length len(str(S))这是教学场景下的取巧方案。对整数Sstr(S)返回其十进制字符串len即位数。但对浮点数或极大数会失效如S超过10**308会变infstr(inf)长度为3。生产环境应改用math.floor(math.log10(abs(S))) 1但此处为突出“有效数字”概念用字符串长度更直观。血泪经验初学者常把range写成range(10000, 1, -1)导致i只到2漏掉i1结果少了常数项1整个结果偏小。务必记住range(start, stop, step)的stop是不包含的边界。3.2 二分法迭代引擎exp3_bisection.py如何用18行代码跑出17次收敛a 2 LIMIT 1e-20 def f(x): return x*x - a # 求 sqrt(2)即解 x^2 - 2 0 xlow float(input(请输入 x 值的下限)) xup float(input(请输入 x 值的上限)) iter 0 while (xup - xlow) * (xup - xlow) LIMIT: # 收敛条件区间长度平方 LIMIT middle (xup xlow) / 2 iter 1 if f(xlow) * f(middle) 0: # f(xlow) 与 f(middle) 异号 → 根在 [xlow, middle] xup middle if f(xup) * f(middle) 0: # f(xup) 与 f(middle) 异号 → 根在 [middle, xup] xlow middle S1 (xup xlow) / 2 S f(S1) if S 0: S 1 elif S 0: S 0 else: S -1 file_handle open(1.txt, modea) file_handle.writelines({:.15g} {:.15g} {:.15g} {:.15g} {:.15g}\n.format(iter,xlow,xup,S1,S)) print({:.15g} {:.15g} {:.15g} {:.15g} {:.15g}.format(iter,xlow,xup,S1,S))逐行逻辑说明与参数深挖while (xup - xlow) * (xup - xlow) LIMIT收敛判断用区间长度的平方而非长度本身。这是为避免浮点数比较时因精度丢失导致死循环。LIMIT 1e-20是极高的精度要求意味着最终区间长度 1e-10因为(1e-10)^2 1e-20。若改为xup - xlow LIMIT在xup和xlow接近1.4142...时xup - xlow可能因浮点舍入永远无法小于1e-20导致无限循环。if f(xlow) * f(middle) 0和if f(xup) * f(middle) 0这里用乘积判别法替代f(xlow) 0 and f(middle) 0等显式比较是为了规避f(x)恰好为0的边界情况此时乘积为0不满足 0不会误更新。但要注意若f(xlow)或f(middle)为0则已找到精确根理想情况应break但本代码未处理属教学简化。S f(S1)后的if-elif-else块这是对f(S1)符号的离散化编码0→10→00→-1。写入日志的第五列就是这个编码值方便后续用脚本统计“正负交替次数”。这不是数学必需而是为生成可分析日志做的数据工程设计。file_handle open(1.txt, modea)a模式是关键。它保证每次运行都追加到文件末尾不会覆盖历史数据。若用w模式每次运行都会清空重写无法积累多组实验数据。这是实验可复现性的基础设施保障。避坑 / 常见问题 / 排查现象1运行exp3_bisection.py时输入xlow1.3,xup1.5后程序卡住不动CPU占用率飙升。原因LIMIT 1e-20过于严苛而float在Python中双精度浮点数的有效位数约15-17位。当xup和xlow逼近sqrt(2)时(xup - xlow)的差值可能无法被精确表示导致while条件永远为真。解决将LIMIT放宽至1e-13或改用abs(xup - xlow) 1e-13并配合math.isclose()做更鲁棒判断。现象21.txt文件里迭代次数iter从1开始但第一行日志显示iter0。原因代码中iter 1在while循环体开头执行而print和writelines在循环体结尾。但实验报告表2的第一行标为“0”对应初始输入状态。本代码并未记录初始状态表2的“0”行是人工添加的。若需自动记录应在while循环前加一行file_handle.writelines(0 {:.15g} {:.15g} {:.15g} 0\n.format(xlow,xup,(xlowxup)/2))。现象3f(xlow)*f(middle) 0判断失效xup和xlow不再收缩陷入死循环。原因f(x)在区间内不连续或存在多个根导致符号变化不满足二分法前提f(xlow)*f(xup) 0。本例f(x)x^2-2在[1.3,1.5]内严格单调无此问题但若换成f(x)x^3-x在[-2,2]内就有3个根二分法可能收敛到任意一个。解决在while循环前强制校验f(xlow)*f(xup) 0不满足则print(Error: f(xlow) and f(xup) must have opposite signs!)并exit()。现象41.txt文件中S1即(xupxlow)/2的值出现nan或inf。原因xup或xlow因浮点溢出变为inf导致S1计算失败。常见于LIMIT过小且循环次数过多时。解决在while循环内加入保护if math.isinf(xup) or math.isinf(xlow): break或限制最大迭代次数if iter 1000: break。4. 本地运行全流程从环境准备到结果验证一步不跳过现在让我们把前面所有的理解落地为一次完整的、可复现的本地运行。这不是“复制粘贴就能跑”而是带着问题意识去执行每一个命令。4.1 环境准备确认你的Python版本与基础能力首先打开终端Windows用CMD/PowerShellmacOS/Linux用Terminal执行python --version确保输出为Python 3.7或更高版本推荐3.8。低于3.7可能缺少time.perf_counter()的高精度支持。接着确认你能在终端中执行Python脚本python -c print(Hello from Python!)如果报错command not found请先配置Python环境变量搜索“Windows 添加Python到PATH”即可找到图文教程。这步看似简单却是90%新手卡住的第一关。4.2 创建实验目录并放置代码在任意位置如桌面新建一个文件夹命名为gdut_numexp1。进入该文件夹# Windows PowerShell cd Desktop\gdut_numexp1 # macOS/Linux Terminal cd ~/Desktop/gdut_numexp1然后用文本编辑器推荐 VS Code、Sublime Text 或系统自带记事本创建三个文件exp2_direct.py粘贴实验报告中“算法1源代码”全部内容。exp2_qin.py粘贴“算法2源代码”全部内容。exp3_bisection.py粘贴“算法源代码”全部内容即二分法部分。注意不要从PDF复制PDF中的空格、破折号—可能被转为全角字符导致Python语法错误。务必手动敲写或从纯文本源复制。4.3 执行算法对比实验亲眼见证秦九韶的威力在gdut_numexp1目录下依次运行两个多项式脚本。为便于观察我们先统一测试x2报告中耗时最长的case# 运行直接法 python exp2_direct.py # 运行秦九韶法 python exp2_qin.py你会看到类似这样的输出# exp2_direct.py 输出 result 3015 位有效数字 time0.142 秒 # exp2_qin.py 输出 result 3015 位有效数字 乘法次数 10000 加法次数 10000 length 3015 time0.033 秒关键验证点result值是否一致应完全相同证明两种算法数学等价time是否qin direct应显著快于验证算法优势exp2_qin.py是否输出了乘法次数和加法次数应均为10000验证白盒计数提示若exp2_direct.py的time显示0.0说明耗时太短perf_counter()分辨率不够。可将x改为2.0显式浮点或在循环内加pass增加负载但教学目的已达不必强求。4.4 执行二分法实验生成并分析1.txt日志运行二分法脚本python exp3_bisection.py按提示输入请输入 x 值的下限1.3 请输入 x 值的上限1.5程序将打印17行迭代数据并在当前目录生成1.txt。用文本编辑器打开它你应该看到1 1.4 1.5 1.45 1 2 1.4 1.45 1.425 1 3 1.4 1.425 1.4125 0 ...验证日志有效性第一列iter是否从1递增到17第二、三列xlow/xup是否呈现“区间不断缩小”的趋势如1.4→1.4125→1.4140625...第四列S1是否稳定收敛到1.414213562...即√2的近似值第五列S是否在0和1之间交替代表f(S1)符号在正负间切换若一切符合恭喜你已成功复现了整个实验流程。1.txt就是你亲手生成的、可导入Excel画收敛曲线的原始数据。5. 二次开发实战从“跑通”到“为我所用”的5个改造技巧这份实验代码的价值远不止于交作业。它是一块打磨数值编程手感的磨刀石。下面这5个改造都是我在某高校实验室带学生时被问得最多、也最能提升工程能力的问题。每个都附带可直接粘贴的代码片段和验证方法。5.1 技巧1把硬编码的x值改成命令行参数告别反复改源码现状x 2写死在代码里想测x0.1得手动改、保存、再运行。改造用sys.argv读取命令行参数。# 修改 exp2_direct.py 和 exp2_qin.py 的开头部分 import time import sys # 替换原来的 x 2 if len(sys.argv) 1: x float(sys.argv[1]) else: x 2.0 # 默认值验证python exp2_qin.py 0.1 # 测试 x0.1 python exp2_qin.py 1 # 测试 x1从那以后我每次写数值实验脚本都强制走一遍sys.argv参数化。因为真实科研中参数组合可能是x[0.1,1,2,10],n[1000,5000,10000]的笛卡尔积手动改是反人类的。5.2 技巧2用matplotlib画出收敛曲线让“17次迭代”变成一张图现状1.txt是纯文本收敛过程靠肉眼扫。改造用pandas读取日志用matplotlib绘图。# 新建 plot_convergence.py import pandas as pd import matplotlib.pyplot as plt # 读取日志指定列名 df pd.read_csv(1.txt, sep\s, headerNone, names[iter, xlow, xup, middle, sign]) # 计算每次迭代的区间长度和 middle 与 sqrt(2) 的误差 import math true_root math.sqrt(2) df[interval_len] df[xup] - df[xlow] df[error] abs(df[middle] - true_root) # 绘图 fig, ax1 plt.subplots() color tab:red ax1.set_xlabel(Iteration) ax1.set_ylabel(Interval Length, colorcolor) ax1.semilogy(df[iter], df[interval_len], colorcolor, labelInterval Length) ax1.tick_params(axisy, labelcolorcolor) ax2 ax1.twinx() # 共享x轴 color tab:blue ax2.set_ylabel(Error to √2, colorcolor) ax2.semilogy(df[iter], df[error], colorcolor, linestyle--, labelError) ax2.tick_params(axisy, labelcolorcolor) fig.tight_layout() plt.title(Bisection Method Convergence) plt.show()运行python plot_convergence.py将弹出一张双Y轴对数图清晰展示区间长度和误差如何随迭代指数衰减。这张图比17行数字更有说服力。5.3 技巧3把二分法封装成函数支持任意f(x)和任意区间现状exp3_bisection.py只能解x^2-20。改造抽离为通用函数bisection(f, a, b, tol1e-10, max_iter100)。def bisection(f, a, b, tol1e-10, max_iter100): 通用二分法求根 :param f: 目标函数接受单个float参数 :param a, b: 初始区间端点要求 f(a)*f(b) 0 :param tol: 收敛容差区间长度 :param max_iter: 最大迭代次数 :return: (root, iter_count, history_list) if f(a) * f(b) 0: raise ValueError(f(a) and f(b) must have opposite signs) history [] for i in range(max_iter): c (a b) / 2 fc f(c) history.append((i1, a, b, c, 1 if fc 0 else 0 if fc 0 else -1)) if abs(b - a) tol: return c, i1, history if f(a) * fc 0: b c else: a c return (a b) / 2, max_iter, history # 使用示例解 sin(x) 0 在 [3, 4] 内的根即 π import math root, iters, log bisection(lambda x: math.sin(x), 3, 4) print(fRoot ≈ {root}, found in {iters} iterations)从那以后我每次遇到新方程第一反应不是写新脚本而是调用这个bisection()函数。它把算法逻辑和业务逻辑彻底分离这才是工程思维的起点。5.4 技巧4为秦九韶算法增加“系数数组”输入让它不再只认1,2,3,...,10001现状exp2_qin.py的系数是硬编码的递增序列。改造接受任意系数列表coeffs [a0, a1, a2, ..., an]。def qin_shao(coeffs, x): 秦九韶算法通用实现 :param coeffs: 系数列表coeffs[i] 是 x^i 的系数 :param x: 自变量值 :return: 多项式在 x 处的值 if not coeffs: return 0 S coeffs[-1] # 从最高次项系数开始 # 从倒数第二项开始向前遍历 for i in range(len(coeffs)-2, -1, -1): S x * S coeffs[i] return S # 测试计算 f(x) 2 3x 5x^2 在 x2 处的值 coeffs [2, 3, 5] # a02, a13, a25 result qin_shao(coeffs, 2) # 应等于 2 3*2 5*4 28 print(result) # 28验证qin_shao([1,2,3,4], 1)应等于123410qin_shao([1,0,0,1], 2)应等于1 0*2 0*4 1*8 9。这个函数让你能把任何多项式丢给秦九韶而不必再推导嵌套公式。5.5 技巧5用pytest写单元测试让“结果正确”不再是口头承诺现状正确性靠肉眼比对result后的数字。改造为关键函数写自动化测试。# test_numexp.py import pytest from exp2_qin import qin_shao # 假设你已将 qin_shao 提取为独立函数 def test_qin_shao_constant(): assert qin_shao([5], 10) 5 def test_qin_shao_linear(): assert qin_shao([1, 2], 3) 1 2*3 # 7 def test_qin_shao_quadratic(): assert qin_shao([1, -2, 1], 2) 1 - 2*2 1*4 # 1 def test_bisection_sqrt2(): from exp3_bisection import bisection root, _, _ bisection(lambda x: x*x - 2, 1.3, 1.5) assert abs(root - 1.41421356237) 1e-10运行pytest test_numexp.py -v。如果所有测试通过你就可以确信无论代码怎么改核心逻辑的正确性已被机器守护。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑