今天来看一个简单的算法,计算一元多项式的值。
暴力求值
对于多项式 f(x)=a0+a1x+⋯+anx,最基本的计算思路就是逐次计算每一项,将结果相加。
应该将一个多项式抽象成什么样的数据结构呢?
注意到对于一个 n 次多项式,其系数刚好可以构成一个线性结构,因此可以用一个数组表示。
因此,我们可以写出如下的代码
pub fn evaluate_v1(coefficients: Vec<i32>, x: i32) -> i32 {
let mut res = 0;
for (i, a) in coefficients.iter().enumerate() {
let mut temp = 1;
for _ in 0..i {
temp = temp * x as i32;
}
res = res + a * temp;
}
res
}
优化
以上代码虽然可以求值,但是在计算 xi 时进行了大量的重复运算。
事实上,每一次外层循环都已经计算了当前的 xi,在下一次循环时,只需要计算 xi+1=xi×x 就可以了。
pub fn evaluate_v2(coefficients: Vec<i32>, x: i32) -> i32 {
let mut res = 0;
let mut temp = 1;
for (i, a) in coefficients.iter().enumerate() {
if i > 0 {
temp = temp * x as i32;
}
res = res + a * temp;
}
res
}
一共是进行了 n+1 次加法和 2n+1 次乘法。
秦九韶算法(Horner's Rule)
秦九韶算法是一个高效的求解一元高次多项式值的算法,具体的原理如下:
对于 n 次多项式 f(x)=anxn+an−1xn−1+an−2xn−2+⋯+a2x2+a1x+a0
将前 n 项提取公因子 x,得
f(x)=(anxn−1+an−1xn−2+an−2xn−3+⋯+a2x+a1)x+a0
再将括号内的前 n−1 项提取公因子 x,得
f(x)=((anxn−2+an−1xn−3+an−2xn−4+⋯+a2)x+a1)x+a0
如此反复提取公因子 x,最后将方程化为
f(x)=(((anx+an−1)x+an−2)x+⋯+a1)x+a0
令
f1=anx+an−1
f2=f1x+an−2]
f3=f2x+an−3
…
fn=fn−1x+a0
注意到第 2 到第 n 项可以得到一个递推式
fi=fi−1x+an−i,if i≥2
令f0=an+1x+an, an+1=0,可以得到
∀i∈{1,…,n},fi=fi−1x+an−i
因此,我们可以写出如下代码
pub fn evaluate_v3(coefficients: Vec<i32>, x: i32) -> i32 {
let mut res = 0;
for i in (0..coefficients.len()).rev() {
res = coefficients[i] + res * x;
}
res
}
第一次循环计算的是 f0=an+1x+an,以此类推,最终只需要 n+1 次乘法和 n+1 次加法完成求值。
总结
今天我们一步一步的完成了一个高效的求一元多项式值的算法。
但是有一点小问题: 对于一个稀疏多项式,使用一元数组存储会造成极大的空间浪费。