一元多项式的计算

29 阅读1分钟

今天来看一个简单的算法,计算一元多项式的值。

暴力求值

对于多项式 f(x)=a0+a1x++anxf(x) = a_0 + a_1x + \dots + a_nx,最基本的计算思路就是逐次计算每一项,将结果相加。

应该将一个多项式抽象成什么样的数据结构呢?

注意到对于一个 nn 次多项式,其系数刚好可以构成一个线性结构,因此可以用一个数组表示。

因此,我们可以写出如下的代码

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;
    // calculate x to the ith power
    for _ in 0..i {
      temp = temp * x as i32;
    }
    res = res + a * temp;
  }
  res
}

优化

以上代码虽然可以求值,但是在计算 xix^i 时进行了大量的重复运算。

事实上,每一次外层循环都已经计算了当前的 xix^i,在下一次循环时,只需要计算 xi+1=xi×xx^{i+1} = x^i \times 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() {
    // calculate x to the ith power, note x to the 0 power is 1
    if i > 0 {
      temp = temp * x as i32;
    }
    res = res + a * temp;
  }
  res
}

一共是进行了 n+1n + 1 次加法和 2n+12n + 1 次乘法。

秦九韶算法(Horner's Rule)

秦九韶算法是一个高效的求解一元高次多项式值的算法,具体的原理如下:

对于 nn 次多项式 f(x)=anxn+an1xn1+an2xn2++a2x2+a1x+a0f(x) = a_n x ^ n + a_{n - 1} x ^ {n - 1} + a_{n - 2} x ^ {n - 2} + \dots + a_2 x ^ 2 + a_1 x + a_0

将前 nn 项提取公因子 xx,得

f(x)=(anxn1+an1xn2+an2xn3++a2x+a1)x+a0f(x) = (a_n x ^ {n - 1} + a_{n - 1} x ^ {n - 2} + a_{n - 2} x ^ {n - 3} + \dots + a_2 x + a_1) x + a_0

再将括号内的前 n1n - 1 项提取公因子 xx,得

f(x)=((anxn2+an1xn3+an2xn4++a2)x+a1)x+a0f(x) = ((a_n x ^ {n - 2} + a_{n - 1} x ^ {n - 3} + a_{n - 2} x ^ {n - 4} + \dots + a_2) x + a_1) x + a_0

如此反复提取公因子 xx,最后将方程化为

f(x)=(((anx+an1)x+an2)x++a1)x+a0f(x) = (((a_n x + a_{n - 1}) x + a_{n - 2}) x +\dots + a_1) x + a_0

f1=anx+an1f_1 = a_nx + a_{n - 1}

f2=f1x+an2]f_2 = f_1x + a_{n - 2}]

f3=f2x+an3f_3 = f_2x + a_{n - 3}

\dots

fn=fn1x+a0f_n = f_{n-1}x + a_0

注意到第 22 到第 nn 项可以得到一个递推式

fi=fi1x+ani,if i2f_i = f_{i - 1}x + a_{n - i}, \text{if }i \ge 2

f0=an+1x+anf_0 = a_{n + 1}x + a_nan+1=0a_{n + 1} = 0,可以得到

i{1,,n},fi=fi1x+ani\forall i \in \{1, \dots , n \}, f_i = f_{i - 1}x + a_{n - 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+anf_0 = a_{n + 1}x + a_n,以此类推,最终只需要 n+1n + 1 次乘法和 n+1n + 1 次加法完成求值。

总结

今天我们一步一步的完成了一个高效的求一元多项式值的算法。

但是有一点小问题: 对于一个稀疏多项式,使用一元数组存储会造成极大的空间浪费。