|
Serwis Edukacyjny w I-LO w Tarnowie
Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2026 mgr Jerzy Wałaszek
|
| SPIS TREŚCI REMANENT |
|
| Podrozdziały |
Interpolacja funkcjami sklejanymi (ang. spline interpolation) polega na przybliżaniu wartości interpolowanej funkcji za pomocą kawałków wielomianów niskiego stopnia (np. sześciennych) zdefiniowanych na poszczególnych podprzedziałach (segmentach) przedziału interpolacji. Jeśli wielomian jest funkcją liniową, otrzymujemy interpolację za pomocą łamanej, co opisuje wcześniejszy rozdział.
Angielska nazwa spline nawiązuje do tzw. krzywek, które dawniej były używane przez inżynierów do rysowania dowolnych krzywych na rysunkach technicznych (w przemyśle okrętowym, lotniczym i motoryzacyjnym).

Na przykład, jeśli mamy dziesięć węzłów funkcji w przedziale interpolacji, to zamiast dopasowywać do nich wszystkich pojedynczy wielomian dziewiątego stopnia, dopasowujemy dziewięć funkcji sześciennych do kolejnych par węzłów. W takiej interpolacji zwykle otrzymuje się lepsze przybliżenie wartości funkcji (przy odpowiednim rozkładzie węzłów) oraz unika się zjawiska Rungego, które pojawia się przy interpolacji wielomianami wysokich stopni.
Przyjrzyjmy się sposobom konstruowania funkcji sklejanych. Załóżmy, że w przedziale <XS;XE> (XS - początek przedziału [X START], XE - koniec przedziału interpolacji [X END]) dla funkcji y = f(x) mamy 4 różne węzły, które dzielą ten przedział na trzy segmenty 0...2 (jak pokazano poniżej):

Dla każdego segmentu definiujemy osobny wielomian sześcienny \(S_i (x)\) (zwany również wielomianem kubicznym):
\(a_i\), \(b_i\), \(c_i\) oraz \(d_i\) : \({i = 0,1,2}\) to współczynniki wielomianu sześciennego, które dla każdego segmentu \(s_i\) mogą być inne i należy je wyznaczyć. \(x_0\), \( x_1\) i \(x_2\) to współrzędne \(x\) początków segmentów \(s_0\), \( s_1\) i \(s_2\). Aby wyznaczyć współczynniki wielomianów sześciennych \(S_0(x)\), \(S_1(x)\) i \(S_2(x)\), musimy wykonać kilka założeń.
1. W węźle początkowym i końcowym każdego segmentu przypisany do niego wielomian sześcienny musi mieć wartość równą współrzędnej \(y\) tego węzła:
2. Aby wielomiany sześcienne gładko przechodziły jeden w następny na styku segmentów, ich pierwsze pochodne muszą być równe w tych węzłach:
3. To samo dla drugich pochodnych:
4. Dodatkowo zakładamy, iż druga pochodna wielomianu sześciennego na początku pierwszego segmentu i na końcu ostatniego segmentu ma wartość 0. Taki dobór warunków definiuje tzw. naturalną kubiczną funkcję sklejaną (ang. natural cubic spline). Zapewnia to otrzymanie najgładszej krzywej interpolacyjnej, co eliminuje nienaturalne wygięcia na końcach przedziału interpolacji.
Założenia te pozwolą nam ułożyć odpowiednią ilość równań liniowych do wyznaczenia współczynników. W równaniach tych niewiadomymi będą współczynniki \(a_i\), \(b_i\), \(c_i\) oraz \(d_i\) : \({i = 0,1,2}\), natomiast wartości wyliczone ze współrzędnych węzłów będą stanowiły wiadome elementy (współczynniki oraz wyrazy wolne) w naszym układzie równań. Współczynników jest 12, zatem musimy mieć 12 równań. Wprowadzone założenia dadzą nam właśnie te 12 równań.
Rozpiszmy te równania.
Z punktu 1:
Upraszczamy:
Z punktu 2.
Z punktu 3.
Z punktu 4.
Zbieramy równania w jeden układ równań. Niewiadomymi są tu współczynniki \(a \dots d\). Współrzędne węzłów są współczynnikami równań (czyli na odwrót, dlatego zapiszemy elementy odwrotnie):
Dla uproszczenia wprowadźmy:
Po podstawieniu otrzymujemy prostszy układ równań:
Teraz zapiszmy układ równań w postaci macierzowej:
Układ równań rozwiązujemy jednym z wcześniej podanych algorytmów, np. eliminacją Gaussa-Crouta. Otrzymujemy współczynniki wielomianów kubicznych dla każdego segmentu.
Interpolacja dla danego x jest dwuetapowa:
Etap I: mając x znajdujemy segment przedziału interpolacji, w którym x się znajduje:
Jeśli segmenty są równej długości dx, to
Jeśli segmenty mają różną długość, to do znalezienia właściwego segmentu można użyć wyszukiwania liniowego lub lepiej binarnego.
Etap II: mając numer segmentu i otrzymujemy dostęp do współczynników wielomianu kubicznego Si(x). Wielomian ten wykorzystujemy do aproksymacji funkcji f(x) w segmencie si:

Przedział interpolacji: <-π/2;π/2>. W przedziale program generuje NN równoodległych współrzędnych x, wylicza dla nich współrzędną y wg przepisu funkcji i dostaje współrzędne NN węzłów interpolacyjnych. Następnie wypełnia macierz współczynników i wyrazów wolnych na podstawie wartości x i y współrzędnych węzłów, po czym rozwiązuje układ równań metodą eliminacji Gaussa-Crouta, otrzymując macierz współczynników wielomianów kubicznych. Na koniec generuje N punktów pseudolosowych w przedziale interpolacji i wylicza dla nich wartość interpolowaną funkcji f(x). Wyniki są porównywane z wartością funkcji.
C++// Aproksymacja sklejana
// (C)2026 mgr Jerzy Wałaszek
// Metody numeryczne 0077
//---------------------------
#include <iostream>
#include <windows.h>
#include <iomanip>
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
// Definicje stałych
//------------------
// Przedział interpolacji
// od XS do XE
const double XS = -M_PI / 2;
const double XE = M_PI / 2;
// Ilość węzłów interpolacji
const int NN = 10;
// Ilość punktów interpolowanych
const int N = 10;
// Szerokość segmentu
const double DX = (XE-XS)/(NN-1);
// Przyrównanie do zera
const double EPS = 1e-12;
// Tablice
//--------
// Współrzędne węzłów
double nx[NN];
double ny[NN];
// Współczynniki wielomianów
// w kolejnych segmentach
double A[NN-1][4];
// Funkcje
//--------
// Funkcja interpolowana
//----------------------
double f(double x)
{
return sin(x);
}
// Zwraca wartość pseudolosową
// w przedziale [0;1)
//----------------------------
double random()
{
return rand() /
((double)RAND_MAX + 1);
}
// Wyznacza węzły interpolacji
//----------------------------
void set_nodes(void)
{
int i;
// wyliczamy NN punktów x,y
for(i = 0; i < NN; i++)
{
nx[i] = XS + i * DX;
ny[i] = f(nx[i]);
}
}
// Funkcja wylicza współczynniki
// dla wielomianów kubicznych.
// Współczynniki są w tablicy
// A[NN-1][4]. Współczynniki
// są czwórkami: a b c d dla
// kolejnych wielomianów.
// Zwraca true, gdy operacja
// się powiedzie.
bool set_a(void)
{
// Najpierw ustawiamy macierz
// rozszerzoną układu równań
int n = 4 * (NN - 1); // wiersze
double AB[n][n+1];
int i,j;
// Zerowanie macierzy AB
for(i = 0; i < n; i++)
for(j = 0; j <= n; j++)
AB[i][j] = 0;
// Wiersze z punktu 1 (wartości)
int row = 0;
for(i = 0; i < NN - 1; i++)
{
// Początek segmentu i
AB[row][i*4] = 1; // a_i
AB[row][n] = ny[i];
row++;
// Koniec segmentu i
AB[row][i*4] = 1; // a_i
AB[row][i*4+1] = DX; // b_i
AB[row][i*4+2] = DX*DX; // c_i
AB[row][i*4+3] = DX*DX*DX; // d_i
AB[row][n] = ny[i+1];
row++;
}
// Wiersze z punktu 2 (pochodne S')
for(i = 0; i < NN - 2; i++)
{
AB[row][i*4+1] = 1; // b_i
AB[row][i*4+2] = 2*DX; // c_i
AB[row][i*4+3] = 3*DX*DX;// d_i
AB[row][(i+1)*4+1] = -1; // -b_{i+1}
row++;
}
// Wiersze z punktu 3 (pochodne S'')
for(i = 0; i < NN - 2; i++)
{
AB[row][i*4+2] = 2; // 2c_i
AB[row][i*4+3] = 6*DX; // 6*DX*d_i
AB[row][(i+1)*4+2] = -2; // -2c_{i+1}
row++;
}
// Wiersze z punktu 4 (brzegi)
AB[row][2] = 2; // 2c_0 = 0
row++;
int last = NN - 2;
AB[row][last*4+2] = 2; // 2c_{last}
AB[row][last*4+3] = 6*DX; // 6*DX*d_{last}
row++;
// Eliminacja Gaussa
int k;
for(i = 0; i < n; i++)
{
// Szukanie elementu osiowego
for(j = i + 1; j < n; j++)
{
if(fabs(AB[j][i]) >
fabs(AB[i][i]))
{
for(k = i; k <= n; k++)
{
double tmp = AB[i][k];
AB[i][k] = AB[j][k];
AB[j][k] = tmp;
}
}
}
// Sprawdzenie osobliwosci
if(fabs(AB[i][i]) < EPS)
return false;
// Zerowanie pod przekątną
for(j = i + 1; j < n; j++)
{
double factor =
AB[j][i] / AB[i][i];
for(k = i; k <= n; k++)
AB[j][k] -=
factor * AB[i][k];
}
}
// Postępowanie wsteczne
double X[n];
for(i = n - 1; i >= 0; i--)
{
double sum = 0;
for(j = i + 1; j < n; j++)
sum += AB[i][j] * X[j];
X[i] = (AB[i][n] - sum) /
AB[i][i];
}
// Przepisanie do tablicy A
for(i = 0; i < NN - 1; i++)
{
A[i][0] = X[i*4]; // a
A[i][1] = X[i*4+1]; // b
A[i][2] = X[i*4+2]; // c
A[i][3] = X[i*4+3]; // d
}
return true;
}
// Funkcja zwraca numer
// segmentu dla x
int find_s(double x)
{
return floor((x - XS) / DX);
}
// Wylicza wartość S_i(x)
//-----------------------
double f_i(double x)
{
int i = find_s(x);
double dx = x - nx[i];
// Schemat Hornera
return A[i][0] + dx * (
A[i][1] + dx * (
A[i][2] + dx *
A[i][3]));
}
// Program główny
int main()
{
int i;
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
cout << setprecision(4)
<< fixed;
// Inicjujemy generator
// pseudolosowy
srand(time(nullptr));
// Generujemy węzły
set_nodes();
// Obliczamy współczynniki
if(set_a())
{
cout << "Interpolacja sklejana\n"
"---------------------\n\n";
// Generujemy N punktów x,
// liczymy dla nich y
// i wyświetlamy wynik
double x,y;
for(i = 0; i < N; i++)
{
x = XS + random() * (XE - XS);
y = f_i(x);
cout << "x = "
<< setw(7) << x
<< ", f(x) = "
<< setw(7) << f(x)
<< ", interpolacja f(x) = "
<< setw(7) << y
<< endl;
}
}
else
cout << "Błąd w obliczeniach\n";
cout << endl;
system("pause");
return 0;
}
|
| Wynik: |
Interpolacja sklejana --------------------- x = -0.0394, f(x) = -0.0394, interpolacja f(x) = -0.0394 x = 0.5146, f(x) = 0.4921, interpolacja f(x) = 0.4922 x = -0.8775, f(x) = -0.7692, interpolacja f(x) = -0.7692 x = -0.8499, f(x) = -0.7512, interpolacja f(x) = -0.7511 x = 1.1111, f(x) = 0.8962, interpolacja f(x) = 0.8977 x = -0.1182, f(x) = -0.1179, interpolacja f(x) = -0.1179 x = -0.3359, f(x) = -0.3297, interpolacja f(x) = -0.3297 x = -0.8531, f(x) = -0.7533, interpolacja f(x) = -0.7532 x = 0.4591, f(x) = 0.4432, interpolacja f(x) = 0.4433 x = 0.6380, f(x) = 0.5956, interpolacja f(x) = 0.5953 |
Python
(dodatek) # Aproksymacja sklejana
# (C)2026 mgr Jerzy Wałaszek
# Metody numeryczne 0077
#---------------------------
from math import sin, floor, pi
from random import uniform
# Definicje stałych
#------------------
# Przedział interpolacji
# od XS do XE
XS = -pi / 2
XE = pi / 2
# Ilość węzłów interpolacji
NN = 10
# Ilość punktów interpolowanych
N = 10
# Szerokość segmentu
DX = (XE - XS) / (NN - 1)
# Przyrównanie do zera
EPS = 1e-12
# Tablice
#--------
# Współrzędne węzłów
nx = [0] * NN
ny = [0] * NN
# Współczynniki wielomianów
# w kolejnych segmentach
a = [[0] * 4 for _ in range(NN - 1)]
# Funkcje
#--------
# Funkcja interpolowana
#----------------------
def f(x):
return sin(x)
# Wyznacza węzły interpolacji
#----------------------------
def set_nodes():
# wyliczamy NN punktów x,y
for i in range(NN):
nx[i] = XS + i * DX
ny[i] = f(nx[i])
# Funkcja wylicza współczynniki
# dla wielomianów kubicznych.
# Współczynniki są w tablicy
# A[NN-1][4]. Współczynniki
# są czwórkami: a b c d dla
# kolejnych wielomianów.
# Zwraca true, gdy operacja
# się powiedzie.
def set_a():
# Najpierw ustawiamy macierz
# rozszerzoną układu równań
n = 4 * (NN - 1) # wiersze
ab = [[0] * (n + 1)
for _ in range(n)]
# Wiersze z punktu 1 (wartości)
row = 0
for i in range(NN - 1):
# Początek segmentu i
ab[row][i*4] = 1 # a_i
ab[row][n] = ny[i]
row += 1
# Koniec segmentu i
ab[row][i*4] = 1 # a_i
ab[row][i*4+1] = DX # b_i
ab[row][i*4+2] = DX*DX # c_i
ab[row][i*4+3] = DX*DX*DX # d_i
ab[row][n] = ny[i+1]
row += 1
# Wiersze z punktu 2 (pochodne S')
for i in range(NN - 2):
ab[row][i*4+1] = 1 # b_i
ab[row][i*4+2] = 2*DX # c_i
ab[row][i*4+3] = 3*DX*DX # d_i
ab[row][(i+1)*4+1] = -1 # -b_{i+1}
row += 1
# Wiersze z punktu 3 (pochodne S'')
for i in range(NN - 2):
ab[row][i*4+2] = 2 # 2c_i
ab[row][i*4+3] = 6*DX # 6*DX*d_i
ab[row][(i+1)*4+2] = -2 # -2c_{i+1}
row += 1
# Wiersze z punktu 4 (brzegi)
ab[row][2] = 2 # 2c_0 = 0
row +=1
last = NN - 2
ab[row][last*4+2] = 2 # 2c_{last}
ab[row][last*4+3] = 6*DX # 6*DX*d_{last}
row += 1
# Eliminacja Gaussa
for i in range(n):
# Szukanie elementu osiowego
for j in range(i + 1, n):
if (abs(ab[j][i]) >
abs(ab[i][i])):
for k in range(i, n + 1):
ab[i][k], ab[j][k] = \
ab[j][k], ab[i][k]
# Sprawdzenie osobliwosci
if abs(ab[i][i]) < EPS:
return False
# Zerowanie pod przekątną
for j in range(i + 1, n):
factor = ab[j][i] / ab[i][i]
for k in range(i, n + 1):
ab[j][k] -= (factor *
ab[i][k])
# Postępowanie wsteczne
x = [0] * n
for i in reversed(range(n)):
sum_val = (sum(ab[i][j] * x[j]
for j in range(i + 1, n)))
x[i] = ((ab[i][n] - sum_val) /
ab[i][i])
# Przepisanie do tablicy a
for i in range(NN - 1):
a[i][0] = x[i*4] # a
a[i][1] = x[i*4+1] # b
a[i][2] = x[i*4+2] # c
a[i][3] = x[i*4+3] # d
return True
# Funkcja zwraca numer
# segmentu dla x
def find_s(x):
return floor((x - XS) / DX)
# Wylicza wartość S_i(x)
#-----------------------
def f_i(x):
i = find_s(x)
dx = x - nx[i]
# Schemat Hornera
return a[i][0] + dx * (
a[i][1] + dx * (
a[i][2] + dx *
a[i][3]))
# Program główny
#---------------
# Generujemy węzły
set_nodes()
# Obliczamy współczynniki
if set_a():
print("Interpolacja sklejana\n"
"---------------------\n")
# Generujemy N punktów x,
# liczymy dla nich y
# i wyświetlamy wynik
for i in range(N):
x = uniform(XS,XE)
y = f_i(x)
print(f"x = {x:7.4f}, "
f"f(x) = {f(x):7.4f}, "
f"interpolacja f(x) = "
f"{y:7.4f}")
print()
input("Naciśnij Enter...")
|
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2026 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.