数学2——拉格朗日插值法

知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。


文章目录

      • 前言
    • 1. 插值能拿来干什么?
    • 2. 插值应该怎么实现呢?
        • 高斯消元解法
    • 3. 拉格朗日插值
        • 3.1 使用交点式?
        • 3.2 拉格朗日函数
        • 3.3 合并!
        • 3.4 代码
      • 后记

前言

\quad 这居然才是我第二次写数学专题的博客……特别是在中考数学没考好之后,我越来越觉得数学要加把劲了 (似乎与中考并没有什么关系?但是不管怎样数学都是要补的)
\quad 以下进入正文内容。

1. 插值能拿来干什么?

我们来回忆初中数学课上常见的一类题目:

  • 给定一个二次函数上一些点的坐标,请描出这个二次函数的图像,然后求出某些横坐标 x i x_i xi 在函数上所对应的值 y i y_i yi

然后给出了这么一张表格:

x i x_i xi 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5
y i y_i yi 0 0 0 1 1 1 4 4 4 9 9 9

这个时候,你可能就会从里面随便选出了三个点: ( 1 , 0 ) , ( 2 , 1 ) , ( 3 , 4 ) (1,0), (2,1), (3,4) (1,0),(2,1),(3,4).
接着设出三个三元一次方程并联立得到:
{ x + y + z = 0 4 x + 2 y + z = 1 9 x + 3 y + z = 4 \begin{cases}x+y+z&=&0\\4x+2y+z&=&1\\9x+3y+z&=&4\end{cases} x+y+z4x+2y+z9x+3y+z===014
这种简单的式子当然难不倒你,你很快算出了解:
{ x = 1 y = − 2 z = 1 \begin{cases}x=1\\y=-2\\z=1\end{cases} x=1y=2z=1
从而得到了二次函数:
f ( x ) = x 2 − 2 x + 1 f(x)=x^2-2x+1 f(x)=x22x+1
假设你不会因式分解简化运算,那么你最后会把 x = 5 x=5 x=5 带入函数式:
f ( 5 ) = x 2 − 2 x + 1 = 5 2 − 2 × 5 + 1 = 16 \begin{matrix}f(5)&=& x^2-2x+1\\&=&5^2-2\times 5+1\\&=&16\end{matrix} f(5)===x22x+1522×5+116
就这样,我们完成了一次 把一个自变量带入函数中,求出其对应函数值的过程 ,这个过程,我们可以把它认为是一次插值
简而言之,你可以把插值简单理解成把新的 x x x 丢进函数中,取出函数值的过程。

2. 插值应该怎么实现呢?

  • 显然,对于一个二次函数,你可以毫不费劲地算出它的函数解析式——甚至比上面的过程看上去更简单。
  • 但是如果是三次函数呢?
    函数解析式是 f ( x ) = a 3 x 3 + a 2 x 2 + a 1 x + a 0 f(x)=a_3x^3+a_2x^2+a_1x+a_0 f(x)=a3x3+a2x2+a1x+a0 ,看起来有 4 4 4 个未知数,于是你列出了 4 4 4 个方程。
  • 似乎难算了一点。
  • 如果是 100 100 100 次函数呢?
  • 如果是 5000 5000 5000 次函数呢?
  • 我们不可能让计算机“一眼看出”答案吧……

但是,我们还有办法。

高斯消元解法

既然我们上面是解方程,那么我们就可以直接按照数学的方法列出方程,并采用矩阵的形式存储下来,就像这样:
[ x 1 n x 1 n − 1 ⋯ x 1 2 x 1 y 1 x 2 n x 2 n − 1 ⋯ x 2 2 x 2 y 2 ⋮ ⋮ ⋱ ⋮ ⋮ ⋮ x n + 1 n x n + 1 n − 1 ⋯ x n + 1 2 x n + 1 y n + 1 ] \begin{bmatrix}x_1^n & x_1^{n-1} & \cdots& x_1^2 & x_1 & y_1\\x_2^n & x_2^{n-1} & \cdots& x_2^2 & x_2 & y_2\\\vdots & \vdots & \ddots& \vdots & \vdots & \vdots\\x_{n+1}^n & x_{n+1}^{n-1} & \cdots& x_{n+1}^2 & x_{n+1} & y_{n+1}\end{bmatrix} x1nx2nxn+1nx1n1x2n1xn+1n1x12x22xn+12x1x2xn+1y1y2yn+1

这种矩阵我们可以通过高斯消元来计算出所有未知数(即函数各项的系数),然后将横坐标回代入函数解析式求出答案。由于不是我们讨论的重点,因此不作详细介绍。

学过高斯消元的同学都知道,高斯消元是一种暴力的解方程的办法, 其时间复杂度是 O ( n 3 ) O(n^3) O(n3) 的。虽说确实可以解决了 100 100 100 次函数插值的问题,但是面对更高次的方程,我们有没有更快的方法呢?

这就是为什么需要使用拉格朗日插值法

3. 拉格朗日插值

3.1 使用交点式?

我们再回到刚开始的二次函数。我们先是使用了一般式,然后勉勉强x强用高斯消元做到了 O ( n 3 ) \mathcal{O}(n^3) O(n3) 的复杂度,不太优秀——但是二次函数还有顶点式和交点式啊!

对于一般的多次函数,顶点式并不适用,因此我们从交点式的角度考虑。

一般的, n n n 次函数的交点式可以写成如下形式:
f ( x ) = a ( x − x 1 ) ( x − x 2 ) ⋯ ( x − x n ) = a   ∏ i n ( x − x i ) \begin{matrix}f(x)&=&a(x-x_1)(x-x_2)\cdots(x-x_n)\\&=&a\ \displaystyle\prod_i^n (x-x_i)\end{matrix} f(x)==a(xx1)(xx2)(xxn)a in(xxi)

接下来,我们考虑怎么把给定的这些点带入上式中。


3.2 拉格朗日函数

考虑对于每一个点定义如下函数 l i ( α ) = { β y i ,  if   x i = α , 0 ,  if   x i ≠ α . \begin{matrix}l_i(\alpha)=\begin{cases}\beta y_i,\ \text{if}\ \ x_i=\alpha,\\0 ,\ \text{if}\ \ x_i\neq \alpha.\end{cases}\end{matrix} li(α)={βyi, if  xi=α,0, if  xi̸=α.,其中 β \beta β 是一个任意的非 0 0 0 常数(即保证函数值是与 y i y_i yi成线性关系的且当 y i y_i yi 0 0 0 l i ( α ) l_i(\alpha) li(α) 也非 0 0 0)。

我们称这个函数为拉格朗日函数。它的定义用语文表述就是:只有在 x i x_i xi 处取到与 y i y_i yi 相关的值,其余位置均为 0 0 0.

另一方面,如果拉格朗日函数的定义域为所有 x i x_i xi 组成的集合,那么第 i i i 个点的拉格朗日函数 l ( i ) l(i) l(i) 也可以表示为:

l i ( x ) = a   ∏ i ≠ j ( x − x j ) l_i(x)=a\ \displaystyle\prod_{i\neq j} (x-x_j) li(x)=a i̸=j(xxj)

显然这个式子满足拉格朗日函数的性质。

我们考虑这样定义的拉格朗日函数有什么优点。当 x ≠ x i x\neq x_i x̸=xi 时,因为定义域是所有 x i x_i xi 组成的集合,所以总有一项为 x j − x j = 0 x_j-x_j=0 xjxj=0,即 l i = 0 l_i=0 li=0

x = x i x=x_i x=xi 时,移项得到 a = l i ( x i )   ∏ i ≠ j ( x i − x j ) = y i   ∏ i ≠ j ( x i − x j ) a=\frac{l_i(x_i)}{\ \displaystyle\prod_{i\neq j} (x_i-x_j)}=\frac{y_i}{\ \displaystyle\prod_{i\neq j} (x_i-x_j)} a= i̸=j(xixj)li(xi)= i̸=j(xixj)yi

我们发现,我们对于所有的点都能用坐标 ( x i , y i ) (x_i,y_i) (xi,yi) 表示出了交点式中的 a a a .

这样问题就好办了。我们把这个 a a a 回代,就有了
f i ( x i ) = y i ∏ i ≠ j ( x i − x i )   ∏ i ≠ j ( x i − x j ) = y i f_i(x_i)=y_i\frac{\displaystyle\prod_{i\neq j} (x_i-x_i)}{\ \displaystyle\prod_{i\neq j} (x_i-x_j)}=y_i fi(xi)=yi i̸=j(xixj)i̸=j(xixi)=yi

满足要求!我们就愉快的把拉格朗日函数的 l l l 记号给扔掉了。

3.3 合并!

同时,我们还注意到,由于拉格朗日函数具有“独立性”,即对于每个 l i ( x ) l_i(x) li(x) 他们之间不会互相干涉(只要 i ≠ j i\neq j i̸=j 那就都是 0 0 0 了;对于每个 x i x_i xi 取到非 0 0 0 函数值的情况有且只有一种),所以我们把所有的 f i ( x ) f_i(x) fi(x) 加起来对于一个单独的 x k x_k xk 来说结果也是不会变的(因为在其它 l i l_i li 处取值都是 0 0 0 ),即对于任意的 x k x_k xk 均有
f ( x k ) = ∑ i = 1 n y i ∏ i ≠ j x k − x j x i − x j f(x_k)=\displaystyle\sum_{i=1}^n y_i\displaystyle\prod_{i\neq j}\frac{x_k-x_j}{x_i-x_j} f(xk)=i=1nyii̸=jxixjxkxj

这样,我们就推出了一个唯一的多项式能经过所有的点。
拉格朗日公式推完了!!!

等下,你的定义域似乎是 所有 x i x_i xi 构成的集合 啊?
那我怎么对于任意数值插入多项式呢?

注意到 n + 1 n+1 n+1 个点只能唯一确定一个 n n n 次多项式,因此我们上面推出的这个函数是唯一确定的。对于一个唯一确定的函数,当然把任意的 x x x 带入都能得到唯一且正确的答案啦!

复杂度优化到了 O ( n 2 ) \mathcal{O}(n^2) O(n2),可以通过 n ≤ 5000 n\leq 5000 n5000 的数据。

3.4 代码

代码还是自己实现的好~其实这个代码真的没什么细节就是对着公式写。
配套练习:洛谷 P4781 【模板】拉格朗日插值

#include
#include
const int MAXN=2010;
const int MOD=998244353;

struct node{
	int x,y;
	node(int x=0,int y=0):x(x),y(y){}
	bool operator<(const node &rhs)const{
		return x<rhs.x||(x==rhs.x&&y<rhs.y);
	}
}p[MAXN];

inline long long inv(long long x){
	long long res=1;
	int b=MOD-2;
	while(b){
		if(b&1)
			res=res*x%MOD;
		x=x*x%MOD;b>>=1;
	}
	return res;
}

int main(){
	int n,k;scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		int x,y;scanf("%d%d",&x,&y);
		p[i]=node(x,y);
	}

	long long res=0;
	for(int i=1;i<=n;i++){
		long long prod1=1,prod2=1;
		for(int j=1;j<=n;j++){
			if(i==j)
				continue;
			prod1=prod1*(k-p[j].x+MOD)%MOD;
			prod2=prod2*(p[i].x-p[j].x+MOD)%MOD;
		}
		res=(res+prod1*inv(prod2)%MOD*p[i].y%MOD)%MOD;
	}
	printf("%lld\n",res);
	return 0;
}

后记

[Warning]
请不要使用拉格朗日插值法完成数学课本上函数相关章节的练习。
[Extra Exercises]
请独立思考如何在给定的点的横坐标为 { x ∈ N ∗ ∣ x ≤ n } \{x\in \mathbb{N}^{*}|x\leq n\} {xNxn} 时将拉格朗日插值的复杂度优化到 O ( n ) \mathcal{O}(n) O(n).

你可能感兴趣的:(数学)