f ( x ) f(x) f(x) 是一个 N N N 次多项式: f ( x ) = a 0 + a 1 x + ⋯ + a N x N f(x) = a_0 + a_1 x + \cdots + a_N x^N f(x)=a0+a1x+⋯+aNxN
那么 f ( x ) f(x) f(x) 被 ( x − c ) (x - c) (x−c) 除得到的商等于 f ( c ) f(c) f(c)。 也就是如果
f ( x ) = Q ( x ) ( x − c ) + r f(x) = Q(x) (x-c) + r \\ f(x)=Q(x)(x−c)+r
那么 : r = f ( c ) r = f(c) r=f(c)
利用这个定理,可以将计算 f ( c ) f(c) f(c) 转化为计算 r r r。如果计算 r r r 有快速算法,那么就可以快速计算出 f ( c ) f(c) f(c)。 下面就来推导如何计算 r r r。
设:
f ( x ) = ∑ i = 0 N a i x i Q ( x ) = ∑ i = 0 N − 1 b i x i f(x) = \sum_{i=0}^{N} {a_i x^i} \\ Q(x) = \sum_{i=0}^{N-1} {b_i x^i} f(x)=i=0∑NaixiQ(x)=i=0∑N−1bixi
那么有:
∑ i = 0 N a i x i = ( x − c ) ∑ i = 0 N − 1 b i x i + r = b N − 1 x N + ∑ i = 1 N − 1 ( b i − 1 − c b i ) x i − b 0 c + r \sum_{i=0}^{N} {a_i x^i} = (x -c)\sum_{i=0}^{N-1} {b_i x^i} + r \\ = b_{N-1} x^N + \sum_{i=1}^{N-1} {(b_{i-1} - c b_i) x^{i} } - b_0 c + r i=0∑Naixi=(x−c)i=0∑N−1bixi+r=bN−1xN+i=1∑N−1(bi−1−cbi)xi−b0c+r
两边同幂次的项相等:
a N = b N − 1 a i = b i − 1 − c b i , ( i = 1 , 2 , ⋯ N − 1 ) a 0 = − c b 0 + r a_N = b_{N-1} \\ a_i = b_{i -1} - c b_i, (i = 1, 2, \cdots N-1 )\\ a_0 = - c b_0 + r aN=bN−1ai=bi−1−cbi,(i=1,2,⋯N−1)a0=−cb0+r
所以:
b N − 1 = a N b i = a i + 1 + c b i + 1 r = a 0 + c b 0 b_{N-1} = a_N\\ b_i = a_{i+1} + c b_{i+1} \\ r = a_0 + c b_0 bN−1=aNbi=ai+1+cbi+1r=a0+cb0
了解多项式的 Horner 公式的人可能会发现,这里的公式怎么和 Horner 公式几乎一样。确实,这里其实就是 Horner 公式的另一种解释方法。
按照这个思路我们就可以写个 C 函数。
/**
* @brief polydiv 计算 p(x) / (x - a) 的商和余数
* @param p 多项式的系数,p0 .. pn
* @param n 多项式的次数
* @param q 结果返回 r, q0, q1, ... qn-1 ,注意第一个数是余数
* @return 返回的也是余数,也等于 p(a) 的值
*/
double polydiv(double p[], int n, double a, double q[])
{
q[n] = p[n];
for(int i = n - 1; i >= 0; i--)
{
q[i] = p[i] + a * q[i+1];
}
return q[0];
}
当然,如果我们不需要返回 q[],可以简化为:
/**
* @brief polyeval 利用 Horner 公式计算多项式的值
* @param p 多项式的系数 a0, a1, a2,\cdots a_n
* @param n 多项式的次数 n
* @param x 要计算的值
* @return 返回多项式在 x 点的值
*/
double polyeval(double p[], int n, double x)
{
double r = 0;
while (n >= 0)
{
r = p[n] + r * x;
n--;
}
return r;
}
实际上,我们还可以更进一步,考虑另一个问题。
f ( x ) f(x) f(x) 是一个 N N N 次多项式: f ( x ) = a 0 + a 1 x + ⋯ + a N x N f(x) = a_0 + a_1 x + \cdots + a_N x^N f(x)=a0+a1x+⋯+aNxN
有时我们需要将它变换为另一个等价的多项式 q ( x ) = b 0 + b 1 ( x − c ) + ⋯ + b N ( x − c ) N q(x) = b_0 + b_1 (x-c) + \cdots + b_N (x-c)^N q(x)=b0+b1(x−c)+⋯+bN(x−c)N
那么如何通过 a 0 , a 1 , ⋯   , a N a_0, a_1, \cdots , a_N a0,a1,⋯,aN 来得到 b 0 , b 1 , ⋯   , b N b_0, b_1, \cdots , b_N b0,b1,⋯,bN。
我们同样设:
f ( x ) = Q 1 ( x ) ( x − c ) + r 0 f(x) = Q_1(x) (x-c) + r_0 \\ f(x)=Q1(x)(x−c)+r0
把 x = c x = c x=c 带入,可以很容易验证 b 0 = f ( c ) = r 0 b_0 = f(c) = r_0 b0=f(c)=r0
再继续设:
Q 1 ( x ) = Q 2 ( x ) ( x − c ) + r 1 Q_1(x) = Q_2(x) (x-c) + r_1 \\ Q1(x)=Q2(x)(x−c)+r1
同样的方法可以验证: b 1 = Q 1 ( c ) = r 1 b_1 = Q_1(c) = r_1 b1=Q1(c)=r1
这个过程反复做下去就可以依次得到 b 0 , b 1 , ⋯   , b N b_0, b_1, \cdots , b_{N} b0,b1,⋯,bN
这个计算过程张,系数的变化如下:
a 0 , a 1 , a 2 , a 3 , a 4 , ⋯   , a N r 0 , b 0 , b 1 , b 2 , b 3 , ⋯   , b N − 1 r 0 , r 1 , c 0 , c 1 , c 2 , ⋯   , c N − 2 r 0 , r 1 , r 2 , d 0 , d 1 , ⋯   , d N − 3 ⋯ r 0 , r 1 , r 2 , r 3 , r 4 , ⋯   , r N a_0, a_1, a_2, a_3, a_4, \cdots , a_N \\ r_0, b_0, b_1, b_2, b_3, \cdots, b_{N-1} \\ r_0, r_1, c_0, c_1, c_2, \cdots, c_{N-2} \\ r_0, r_1, r_2, d_0, d_1, \cdots, d_{N-3} \\ \cdots \\ r_0, r_1, r_2, r_3, r_4, \cdots, r_N a0,a1,a2,a3,a4,⋯,aNr0,b0,b1,b2,b3,⋯,bN−1r0,r1,c0,c1,c2,⋯,cN−2r0,r1,r2,d0,d1,⋯,dN−3⋯r0,r1,r2,r3,r4,⋯,rN
为了实现这个过程,我们需要对刚刚写的 polydiv 函数做些修改,让它在原位修改各个系数的值。
/**
* @brief polydiv2 计算 p(x) / (x - a) 的除数和余数。
* @param p 多项式的系数,p0 .. pn,同时计算结束后也返回 r, q0, q1, ... qn-1
* @param n 多项式的次数
* @param a
* @return 返回的也是余数,也等于 p(a) 的值
*/
double polydiv2(double p[], int n, double a)
{
for(int i = n - 1; i >= 0; i--)
{
p[i] = p[i] + a * p[i+1];
}
return p[0];
}
/**
* @brief polyshift 将 x 的多项式变换为 (x-a) 的多项式
* @param p 多项式的系数,p0 .. pn, 计算结束后返回新的系数
* @param n 多项式的次数
* @param a 将 (x - a)
*/
void polyshift(double p[], int n, double a)
{
for(int i = n; i >= 1; i--)
{
polydiv2(p, i, a);
p++;
}
}
下面是个测试用例:
有一个多项式: f ( x ) = 1 + 2 x + 3 x 2 + 4 x 3 + 5 x 4 + 6 x 5 f(x) = 1 +2 x + 3 x^2 + 4 x^3 + 5 x^4 + 6 x^5 f(x)=1+2x+3x2+4x3+5x4+6x5
将其变换为 ( x − 5.5 ) (x- 5.5) (x−5.5) 的形式,用 Mathematica 可以算出是:
f ( x ) = 35540.6 + 31177.4 ( x − 5.5 ) + 10959 ( x − 5.5 ) 2 + 1929 ( x − 5.5 ) 3 + 170 ( x − 5.5 ) 4 + 6 ( x − 5.5 ) 5 f(x) = 35540.6 + 31177.4 (x-5.5) + 10959 (x-5.5)^2 + 1929 (x-5.5)^3 + 170 (x-5.5)^4 + 6 (x-5.5)^5 f(x)=35540.6+31177.4(x−5.5)+10959(x−5.5)2+1929(x−5.5)3+170(x−5.5)4+6(x−5.5)5
Mathematica 的代码如下:
p = 1 + 2 x + 3 x^2 + 4 x^3 + 5 x^4 + 6 x^5
Expand[p /. {x -> y + 5.5}] /. {y -> x - 5.5}
C++ 的验证代码如下:
double ax[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
polyshift(ax, 5, 5.5);
for(int i = 0; i <= 5; i++)
{
std::cout << ax[i] << std::endl;
}
计算结果如下:
35540.6
31177.4
10959
1929
170
6
说明这个代码是正确的。
至此,这个问题就算是比较圆满的解决了。