当前位置: 首页 > news >正文

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录

🔍 若用递归计算每一项,会发生什么?

Horner's Rule(霍纳法则)

 第一步:我们从最原始的泰勒公式出发

第二步:从形式上重新观察展开式 

🌟 第三步:引出霍纳法则(Horner’s Rule)

 第四步:如何用这个结构重写泰勒展开式? 

完整代码

 从迭代转换成递归逻辑

“迭代”和“循环” 


当前递归方法的结构回顾:

double num = 1, den = 1;double taylor(int n, double x) {if (n == 0) return 1;double res = taylor(n - 1, x);  // 一次函数调用num *= x;                       // 分子:一次乘法den *= n;                       // 分母:一次乘法return res + num / den;
}

这一版已经做了初步优化:我们通过 累计 num 和 den 来避免重复调用 pow(x,n)factorial(n)

但这只是相对优化,我们现在要分析:

🔍 若用递归计算每一项,会发生什么?

我们从第 0 项到第 n 项共计算 n+1项,每一项的乘法成本如下:

第 k 项乘法次数
k = 00
k = 11
k = 22
......
k = nn

所以总乘法次数为:

✅ 因此,乘法总次数为 O(n^2)!

🚨 问题在哪里?

  • 你对每一项都重新计算幂和阶乘

  • 导致重复计算,浪费大量 CPU 时间

  • 如果你希望 n = 50n = 100,程序变慢得很明显

🤔 有没有更好的方式?

是的。你就引出了今天的主角:

Horner's Rule(霍纳法则)

我们可以尝试换一种展开方式,让我们不必每次都分别去计算幂和阶乘。

我们将展开式进行重写:

也就是一种嵌套式计算结构。

这个就是 Horner 法则的思路 —— 逐层乘进去、逐层加出来,避免重复乘法和幂展开。


 第一步:我们从最原始的泰勒公式出发

以 e^x 为例,泰勒展开是:

 

这表达得很清晰,每一项结构都类似:

  • 分子是 x^k

  • 分母是 k!

所以直觉上,我们就写了:

double num = 1;  // 分子
double den = 1;  // 分母double taylor(int n, double x) {if (n == 0) return 1;               // 1️⃣ 锚点:停止递归double res = taylor(n - 1, x);      // 2️⃣ 先构造前面所有项的和num *= x;                           // 3️⃣ 然后再更新状态den *= n;return res + num / den;             // 4️⃣ 当前项加进去
}

 递归方法的思路解析,可以看我之前发表的文章:

数据结构:递归:泰勒展开式(Taylor Series Expansion)-CSDN博客

 但是整个算法需要 O(n^2) 次的乘法。

于是我们问自己:

❓有没有一种办法,我们不显式地计算幂和阶乘,而是用一种更聪明的方式重写它?

第二步:从形式上重新观察展开式 

我们把:

我们尝试反向收敛:

从最后一项往前看。 

设:

假设我们已知最后一项是:

我们问:有没有可能构造出一个结构:

 

我们知道这种结构是逐层“乘进去再加”,是一种“嵌套结构”。

这时候,你会自然联想到:


🌟 第三步:引出霍纳法则(Horner’s Rule)

Horner's Rule 是一种重写多项式的方式,使其变成嵌套乘加结构,从而节省乘法次数。

给你一个多项式:

它可以等价重写成:

 

第一步:从最后一项开始反向思考 

先写出最后一项:

但我们不这么直接展开,而是尝试合并每一项,构建嵌套结构。我们回顾一下: 

第二步:尝试因式提取,构造嵌套结构 

我们从最后一项往回包,先只保留最后一项:

向前一项加:

 再加:

 最后加 1 项:

第三步:得出结论(形式)

最终你得到的就是:

 

这就是 Horner 法则在泰勒展开中的精确结构! 

 


 第四步:如何用这个结构重写泰勒展开式? 

霍纳法则告诉我们:

如果你有一个嵌套表达式,它从最深处开始乘加,就可以从最后一项反向累积计算

我们的目标是以某种结构计算它,让乘法次数最少。 

观察这个嵌套结构你会发现:

  • 每一层都包含一个 “1 + x / k * (之前的结果)”

  • 最里面的是 1

  • 然后不断被 x/k 包裹

它是一个“逐层包裹”的结构,每一层是:

 这说明我们可以从“最深的那一层”开始往外展开。

于是你意识到这就是一种“右折叠(right fold)”,即:

result = 1;
result = 1 + x * result / 4;
result = 1 + x * result / 3;
result = 1 + x * result / 2;
result = 1 + x * result / 1;

 从后往前包,每次乘进去一个 x / i,再加 1。

所谓「右折叠」就是我们从表达式的最右边开始构建,逐层包起来。 

1 + x/4              ← 第 1 层(最里面)
1 + x/3 * (1 + x/4)  ← 第 2 层
1 + x/2 * (...)      ← 第 3 层
1 + x/1 * (...)      ← 第 4 层

你看到一种非常明显的重复:

每次的操作是:

result = 1 + x * result / i;

从哪个 i 开始?

  • 最深一层是对应最大项 n

  • 然后是 n - 1

  • 最后是 i = 1

所以你会写一个反向的循环:

for (int i = n; i > 0; --i)

初始值设置为:

double result = 1.0;  // 最里层的恒定值

为什么是 1.0?

因为你可以认为最内层就是 1 + 0,也就是我们从最后一项 x^n / n! 折叠得到的值是最深的那个 1,逐层向外构建。

完整代码

double horner_taylor(int n, double x) {double result = 1.0;                  // 最深嵌套项for (; n > 0; n--) {                 // 从内往外迭代result = 1 + x * result / n;     // 每次构造一层}return result;
}

 从迭代转换成递归逻辑

递归的本质是:

用一个函数在每一层调用自己,把循环变成函数调用链。

从上面的迭代式:

你可以直接转换成递归表达式:

double horner_recursive(int n, double x) {static double result = 1.0;if (n == 0) return result;       // 最深嵌套项(base case)result = 1 + x / n * result;  return horner_recursive(n - 1, x);   
}

循环版结构递归版结构
从 n 逐步降到 1从 n 递归到 0(递归终止条件)
每次更新 result = ...每次返回 1 + x * 下层 / n
初始 result = 1.0horner_recursive(0, x) = 1.0

两者实际上是完全等价的计算结构。


“迭代”“循环” 

什么是“循环”(loop)?

  • 循环是语法结构

  • 是编程语言提供的控制流语句(forwhiledo-while

  • 它的作用是:重复执行某段代码

比如:

for (int i = 0; i < 10; ++i) {// 执行 10 次
}

什么是“迭代”(iteration)?

  • 迭代是算法行为

  • 意思是:基于前一个结果,不断构造下一个结果

  • 它不依赖一定要用循环语法,也可以用递归实现!

举例说明:

✅ 迭代行为 + 循环实现

double result = 1;
for (int i = n; i > 0; --i)result = 1 + x * result / i;
  • 每一轮基于上一轮的 result

  • 所以这是一个迭代算法

  • 同时用了 for,所以也是一个循环结构

 ✅ 迭代行为 + 递归实现

double horner_recursive(int n, double x) {static double result = 1.0;if (n == 0) return result;       // 最深嵌套项(base case)result = 1 + x / n * result;  return horner_recursive(n - 1, x);   
}
  • 每一层基于“下一层”的结果

  • 所以也是一种迭代算法

  • 但这次用的是递归结构

 🚫 循环 ≠ 迭代(反例)

for (int i = 0; i < 10; ++i) {cout << "Hello\n";
}
  • 这使用了循环,但没有迭代行为(没有前后状态依赖)

  • 所以它是循环,但不是迭代

相关文章:

  • ModuleNotFoundError No module named ‘torch_geometric‘未找到
  • 使用vue3+ts+input封装上传组件,上传文件显示文件图标
  • Spring 框架之IOC容器加载重要组件
  • k8s开发webhook使用certmanager生成证书
  • 将图形可视化工具的 Python 脚本打包为 Windows 应用程序
  • MyBatis之测试添加功能
  • Oracle 的 SEC_CASE_SENSITIVE_LOGON 参数
  • Ntfs!ReadIndexBuffer函数分析之nt!CcGetVirtualAddress函数之nt!CcGetVacbMiss
  • DBSyncer:开源数据库同步利器,MySQL/Oracle/ES/SqlServer/PG/
  • Selenium 和playwright 使用场景优缺点对比
  • 【Linux】可重入函数 volatile SIGHLD信号
  • 打卡Day45
  • 渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
  • 2025年渗透测试面试题总结-ali 春招内推电话1面(题目+回答)
  • RKNN3588上部署 RTDETRV2
  • 全球IP归属地查询接口如何用C#进行调用?
  • 使用SSH tunnel访问内网的MySQL
  • 【JS进阶】ES5 实现继承的几种方式
  • python项目如何创建docker环境
  • OpenCV 图像像素的逻辑操作
  • 三明住房建设局网站/竞价推广托管公司价格
  • 赢展网站建设/长沙正规竞价优化推荐
  • wordpress 功能介绍/宁波seo关键词排名优化
  • 凡科的模板做网站/品牌广告语
  • 永康建设网站/培训
  • mibt wordpress/滕州seo