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

数据结构:泰勒展开式:霍纳法则(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";
}
  • 这使用了循环,但没有迭代行为(没有前后状态依赖)

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

http://www.xdnf.cn/news/12074.html

相关文章:

  • DeepSeek-R1-0528-Qwen3-8B为底座微调领域大模型准备:制作领域专用数据集
  • HarmonyOS:Counter计数器组件
  • QQ邮箱发送验证码(Springboot)
  • AI系统负载均衡与动态路由
  • 力扣HOT100之二分查找: 34. 在排序数组中查找元素的第一个和最后一个位置
  • 数学建模-嘉陵江铊污染事件解题全过程文档及程序
  • 联软NSPM自动化策略管理 助力上交所加速国产化替代提升运维效率
  • matlab实现DBR激光器计算
  • 全维度测试通过!DolphinScheduler 3.2.0单节点部署与验证实录
  • cursor-free-vip使用
  • [实际项目2] 从西门子PLC中读取曲线数值并绘图
  • 半监督学习:低密度分离假设 (Low-Density Separation Assumption)
  • IT组织转型记:从运维支持到数字利润中心的蜕变
  • OpenCV CUDA模块图像处理-----对图像执行 均值漂移过程(Mean Shift Procedure)函数meanShiftProc()
  • opencv学习笔记1:图像基础、图像操作、直方图均衡化详解
  • 破局新能源消纳难题!安科瑞智慧能源平台助力10KV配电网重构未来
  • Python数据可视化科技图表绘制系列教程(三)
  • AtCoder-abc408_b 解析
  • React hook之userReducer
  • vue-19(Vuex异步操作和变更)
  • 【android bluetooth 协议分析 02】【bluetooth hal 层详解 7】【高通蓝牙hal-读流程介绍】
  • 古老界面硬核工具:小兵以太网测试仪(可肆意组包,打vlan)
  • 重启路由器ip不变怎么回事?原因分析与解决方法
  • Java八股文——集合「List篇」
  • 【计算机网络】NAT、代理服务器、内网穿透、内网打洞、局域网中交换机
  • npm install 报错:npm error: ...node_modules\deasync npm error command failed
  • linux 安装mysql8.0;支持国产麒麟,统信uos系统
  • SpringCloud——Nacos
  • SpringBoot自动化部署全攻略:CI/CD高效实践与避坑指南
  • 桂花网蓝牙网关物联网医院动态血糖管理应用案例