多项式的余数定理及其应用

多项式的余数定理及其应用(C++)

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) (xc) 除得到的商等于 f ( c ) f(c) f(c)。 也就是如果

f ( x ) = Q ( x ) ( x − c ) + r f(x) = Q(x) (x-c) + r \\ f(x)=Q(x)(xc)+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=0NaixiQ(x)=i=0N1bixi

那么有:
∑ 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=0Naixi=(xc)i=0N1bixi+r=bN1xN+i=1N1(bi1cbi)xib0c+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=bN1ai=bi1cbi,(i=1,2,N1)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 bN1=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(xc)++bN(xc)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)(xc)+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)(xc)+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,,bN1r0,r1,c0,c1,c2,,cN2r0,r1,r2,d0,d1,,dN3r0,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) (x5.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(x5.5)+10959(x5.5)2+1929(x5.5)3+170(x5.5)4+6(x5.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

说明这个代码是正确的。
至此,这个问题就算是比较圆满的解决了。

你可能感兴趣的:(数值计算)