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 |
©2024 mgr Jerzy Wałaszek |
ProblemDaną macierz kwadratową An×n należy rozłożyć na dwie macierze: trójkątną dolną Ln×n o elementach głównej przekątnej równych 1 i trójkątną górną Un×n, takich że:
|
Rozważmy macierz kwadratową A o wymiarze n×n. Załóżmy, iż jest ona iloczynem dwóch macierzy trójkątnych L i U:
An×n =
|
|
= Ln×n×Un×n = |
= |
|
× |
|
Wykorzystując wzory na mnożenie macierzy otrzymujemy dla każdego elementu ai, j macierzy A następujące równanie:
An×n = Ln×n×Un×n |
ai,j =
|
n Σ k=1 |
li,k×uk,j |
Ponieważ macierze L i U są macierzami trójkątnymi, to część ich elementów jest równa zero. Otrzymujemy stąd dwa przypadki w zależności od indeksów i oraz j:
1) i ≤ j, czyli element macierzy A ai,j leży na głównej przekątnej lub ponad nią. W takim przypadku ostatnim, niezerowym elementem w wierszu i-tym macierzy L jest element li,i. Skoro tak, to podany wzór możemy zredukować do postaci:
ai,j =
|
i Σ k=1 |
li,k×uk,j |
Dodatkowo z postaci macierzy L wiemy, iż element li,i, leżący na głównej przekątnej, jest zawsze równy 1. Stąd:
ai,j = (
|
i-1 Σ k=1 |
li,k×uk,j)+ui,j |
Wzór ten pozwala nam wyliczyć element ui,j o ile są znane poprzednie elementy macierzy L i U:
ui,j = ai,j- |
i-1 Σ k=1 |
li,k×uk,j |
2) i > j, czyli element macierzy A: ai,j leży pod główną przekątną. W tym przypadku ostatnim, niezerowym elementem w j-tej kolumnie macierzy U jest uj,j. Zatem wzór wyjściowy redukuje się do postaci:
ai,j =
|
j Σ k=1 |
li,k×uk,j |
ai,j = (
|
j-1 Σ k=1 |
li,k×uk,j)+li,j×uj, j |
Otrzymany wzór pozwala wyliczyć element li, j o ile znane są poprzednie elementy macierzy L i U:
li,j =
|
1 |
×(ai,j-
|
i-1 Σ k=1 |
li,k×uk,j) |
uj,j
|
W ten sposób doszliśmy do algorytmu Doolitle'a wyznaczania rozkładu macierzy A na iloczyn dwóch macierzy L i U (ang. LU matrix decomposition). Zasada działania tego algorytmu jest następująca:
Tworzymy dwie macierze L i U o stopniu macierzy A. Wypełniamy je zerami. W macierzy L główną przekątną wypełniamy elementami o wartości 1.
Dla j = 1, 2, …, n: wykonujemy kolejno dwie poniższe operacje:
Dla i = 1, 2, …, j: wykorzystujemy wzór z punktu 1) do wyznaczenia elementów ui, j.
Dla i = j+1, j+2, …, n: wykorzystujemy wzór z punktu 2) do wyznaczenia elementów li, j.
Przykład:
W celu zobrazowania pracy algorytmu Doolitle'a wyznaczmy wzory na rozkład LU macierzy A3×3:
A3×3 =
|
|
= L3×3×U3×3 = |
= |
|
× |
|
i = 1, i = j, zatem wyznaczamy u1,1:
u1,1 = a1,1- |
0 Σ k=1 |
l1,k×uk,1 = a1,1 |
i = 2, i > j, wyznaczamy l2,1:
l2,1 = 1/u1,1×(a2,1- |
0 Σ k=1 |
l2,k×uk,1) = 1/u1,1×a2,1 |
i = 3, i > j, wyznaczamy l3,1:
l3,1 = 1/u1,1×(a3,1- |
0 |
l3,k×uk,1) = 1/u1,1×a3,1 |
Σ |
||
k=1 |
i = 1, i < j, wyznaczamy u1,2:
u1,2 = a1,2- |
0 Σ k=1 |
l1,k×uk,2 = a1,2 |
i = 2, i = j, wyznaczamy u2,2:
u2,2 = a2,2- |
1 Σ k=1 |
l2,k×uk,2 = a2,2-l2,1×u1,2 |
i = 3, i > j, wyznaczamy l3,2:
l3,2 = 1/u2,2×(a3,2- |
1 Σ k=1 |
l3,k×uk,2) = 1/u2,2×(a3,2-l3,1×u1,2) |
i = 1, i < j, wyznaczamy u1,3:
u1,3 = a1,3- |
0 Σ k=1 |
l1,k×uk,3 = a1,3 |
i = 2, i < j, wyznaczamy u2, 3:
u2,3 = a2,3- |
1 Σ k=1 |
l2,k×uk,3 = a2,3-l2,1×u1,3 |
i = 3, i = j, wyznaczamy u3,3:
u3,3 = a3,3- |
2 Σ k=1 |
l3,k×uk,3 = a3,3-l3,1×u1,3-l3,2×u2,3 |
Dla przykładowych danych liczbowych otrzymujemy ze wzorów:
|
= |
|
× |
|
W powyższym przykładzie zwróć uwagę na następujące fakty:
Prowadzi to do wniosku, iż macierze L i U mogą zostać umieszczone w miejscu macierzy A, czyli operacja będzie wykonana in situ (łac. w miejscu) i zaoszczędzimy pamięć. W macierzy L istotna jest tylko część leżąca pod główną przekątną. Elementy przekątnej są zawsze równe 1, a elementy leżące nad przekątną są równe zero i nie musimy ich wcale zapamiętywać. Z drugiej strony w macierzy U istotna staje się tylko główna przekątna oraz elementy leżące nad nią – pozostałe elementy są równe zero i również nie musimy ich zapamiętywać. Zatem algorytm Doolitle'a może przekształcać macierz A na połączone macierze L i U:
|
→ |
→ |
|
K01: r ← false ; zakładamy porażkę K02: Dla j = 1, 2, …, n: wykonuj kroki K03…K11 K03: Jeśli |A[j,j]| < ε, ; rozkład niemożliwy, to zakończ ; gdyż dzielnik równy zero K04: Dla i = 1, 2, …, j: ; najpierw wyznaczamy w kolumnie wyrazy u wykonuj kroki K05…K07 K05: s ← 0 K06: Dla k = 1, 2, …, i-1: ; wyznaczamy sumę iloczynów wyrazów z L i U wykonuj s ← s+A[i,k]×A[k,j] K07: A[i,j] ← A[i,j]-s ; obliczamy U[i,j] K08: Dla i = j+1, j+2, …, n: wykonuj kroki K09…K11 K09: s ← 0 K10: Dla k = 1, 2, …, j-1: ; wyznaczamy sumę iloczynów wyrazów z L i U wykonuj s ← s+A[i,k]×A[k,j] K11: A[i,j] ← (A[i,j]-s):A[j,j] ; obliczamy L[i,j] K12: r ← true ; zaznaczamy sukces K13: Zakończ
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program odczytuje ze standardowego wejścia macierz A (najpierw liczba n określająca stopień macierzy, a następnie n2 liczb dla kolejnych elementów), wyznacza jej rozkład LU za pomocą algorytmu Doolitle'a i wyświetla wynikową macierz A. |
Dane przykładowe (przekopiuj do schowka i
wklej do okna konsoli):
3 1 4 7 2 5 8 3 6 9 |
Pascal// Rozkład LU algorytmem Doolitle'a // Data: 21.03.2010 // (C)2020 mgr Jerzy Wałaszek //----------------------------- program ludoolitle; type NReal = array of double; // typ tablicy wierszy MReal = array of NReal; // typ tablicy wskaźników const eps = 1e-12; // Funkcja realizuje algorytm Doolitle'a rozkładu LU //-------------------------------------------------- function Doolitle(n : integer; A : MReal) : boolean; var i, j, k : integer; s : double; begin for j := 0 to n-1 do begin if abs(A[j][j]) < eps then Exit(false); for i := 0 to j do begin s := 0; for k := 0 to i-1 do s := s+A[i][k]*A[k][j]; A[i][j] := A[i][j]-s; end; for i := j+1 to n-1 do begin s := 0; for k := 0 to j-1 do s := s+A[i][k]*A[k][j]; A[i][j] := (A[i][j]-s)/A[j][j]; end; end; Doolitle := true; end; // Program główny var A : MReal; n, i, j : integer; begin // odczytujemy stopień macierzy A read(n); // tworzymy macierz A o odpowiednich rozmiarach SetLength(A, n); for i := 0 to n-1 do SetLength(A[i], n); // odczytujemy dane dla macierzy A for i := 0 to n-1 do for j := 0 to n-1 do read(A[i][j]); // wyznaczamy rozkład LU macierzy A if Doolitle(n, A) then for i := 0 to n-1 do begin for j := 0 to n-1 do write(A[i][j]:8:3, ' '); writeln; end else writeln('DZIELNIK ZERO'); // usuwamy macierz z pamięci for i := 0 to n-1 do SetLength(A[i], 0); SetLength(A, 0); end. |
// Rozkład LU algorytmem Doolitle'a // Data: 21.03.2010 // (C)2020 mgr Jerzy Wałaszek //----------------------------- #include <iostream> #include <iomanip> #include <cmath> using namespace std; const double eps = 1e-12; // Funkcja realizuje algorytm // Doolitle'a rozkładu LU //--------------------------- bool Doolitle(int n, double ** A) { int i, j, k; double s; for(j = 0; j < n; j++) { if(fabs(A[j][j]) < eps) return false; for(i = 0; i <= j; i++) { s = 0; for(k = 0; k < i; k++) s += A[i][k]*A[k][j]; A[i][j] -= s; } for(i = j+1; i < n; i++) { s = 0; for(k = 0; k < j; k++) s += A[i][k]*A[k][j]; A[i][j] = (A[i][j]-s)/A[j][j]; } } return true; } // Program główny int main() { double **A; int n, i, j; cout << setprecision(3) << fixed; // odczytujemy stopień macierzy A cin >> n; // tworzymy macierz A o odpowiednich rozmiarach A = new double * [n]; for(i = 0; i < n; i++) A[i] = new double[n]; // odczytujemy dane dla macierzy A for(i = 0; i < n; i++) for(j = 0; j < n; j++) cin >> A[i][j]; // wyznaczamy rozkład LU macierzy A if(Doolitle(n, A)) for(i = 0; i < n; i++) { for(j = 0; j < n; j++) cout << setw(8) << A[i][j] << " "; cout << endl; } else cout << "DZIELNIK ZERO\n"; // usuwamy macierz z pamięci for(i = 0; i < n; i++) delete [] A[i]; delete [] A; return 0; } |
Basic' Rozkład LU algorytmem Doolitle'a ' Data: 21.03.2010 ' (C)2020 mgr Jerzy Wałaszek '----------------------------- Const eps As Double = 1e-12 ' Funkcja realizuje algorytm ' Doolitle'a rozkładu LU '--------------------------- Function doolitle(n As Integer, _ A As Double Ptr Ptr) _ As Integer Dim As Integer i, j, k Dim As Double s For j = 0 To n-1 If Abs(A[j][j]) < eps Then Return 0 For i = 0 To j s = 0 For k = 0 To i-1 s += A[i][k]*A[k][j] Next A[i][j] -= s Next For i = j+1 To n-1 s = 0 For k = 0 To j-1 s += A[i][k]*A[k][j] Next A[i][j] = (A[i][j]-s)/A[j][j] Next Next doolitle = 1 End Function Dim As Integer i, j, n Dim A As Double Ptr Ptr Open Cons For Input As #1 ' odczytujemy stopień macierzy A Input #1, n ' tworzymy macierz A o odpowiednich rozmiarach A = New Double Ptr [n] For i = 0 To n-1 A[i] = New Double [n] Next ' odczytujemy dane dla macierzy A For i = 0 To n-1 For j = 0 To n-1 Input #1, A[i][j] Next Next Close #1 ' wyznaczamy rozkład LU macierzy A If doolitle(n, A) = 1 Then For i = 0 To n-1 For j = 0 To n-1 Print Using "####.### ";A[i][j]; Next Print Next Else Print "DZIELNIK ZERO" End If ' usuwamy macierz z pamięci For i = 0 To n-1 Delete [] A[i] Next Delete [] A End |
Python
(dodatek)# Rozkład LU algorytmem Doolitle'a # Data: 23.04.2024 # (C)2024 mgr Jerzy Wałaszek #----------------------------- EPS = 1e-12 # Funkcja realizuje algorytm # Doolitle'a rozkładu LU #--------------------------- def doolitle(a): n = len(a) for j in range(n): if abs(a[j][j]) < EPS: return False for i in range(j+1): s = 0 for k in range(i): s += a[i][k]*a[k][j] a[i][j] -= s for i in range(j+1, n): s = 0 for k in range(j): s += a[i][k]*a[k][j] a[i][j] = (a[i][j]-s)/a[j][j] return True # Program główny # odczytujemy stopień macierzy A n = int(input()) # odczytujemy macierz A a = [] for i in range(n): arr = input().split() arr = [float(arr[j]) for j in range(n)] a.append(arr) # wyznaczamy rozkład LU macierzy A if doolitle(a): for i in range(n): for j in range(n): print("%8.3f " % a[i][j], end="") print() else: print("DZIELNIK ZERO") # usuwamy macierz z pamięci a = None |
Wynik: |
3 1 4 7 2 5 8 3 6 9 1.000 4.000 7.000 2.000 -3.000 -6.000 3.000 2.000 0.000 |
W literaturze informatycznej można znaleźć prostsze rozwiązanie wyznaczania rozkładu LU macierzy. Polega ono na przechodzeniu przez kolejne elementy głównej przekątnej macierzy A od pierwszego do przedostatniego i dla każdego elementu A[k, k] wykonywaniu kolejno dwóch operacji:
Dzięki takiemu podejściu algorytm rozkładu LU znacznie się upraszcza.
K01: r ← false ; zakładamy porażkę K02: Dla k = 1, 2, …, n-1: wykonuj kroki K03…K06 K03: Jeśli |A[k, k]| < ε, ; element zerowy nie pozwoli to zakończ ; wykonać dzielenia K04: Dla i = k+1, k+2, …, n: ; normalizujemy kolumnę wykonuj: A[i, j] ← A[i, k]:A[k, k] K05: Dla i = k+1, k+2, …, n: wykonuj krok K06 K06: Dla j = k+1, k+2, …, n: ; modyfikujemy podmacierz wykonuj: A[i, j] ← A[i, j]-A[i, k]×A[k, j] K07: r ← true ; sukces K08: Zakończ
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program odczytuje ze standardowego wejścia macierz A (najpierw liczba n określająca stopień macierzy, a następnie n2 liczb dla kolejnych elementów), wyznacza jej rozkład LU i wyświetla wynikową macierz A. |
Pascal// Rozkład LU // Data: 30.04.2010 // (C)2020 mgr Jerzy Wałaszek //----------------------------- program ludecomp; type NReal = array of double; // typ tablicy wierszy MReal = array of NReal; // typ tablicy wskaźników const eps = 1e-12; // Funkcja realizuje algorytm rozkładu LU //--------------------------------------- function lu(n : integer; A : MReal) : boolean; var i, j, k : integer; begin for k := 0 to n-2 do begin if abs(A[k][k]) < eps then Exit(false); for i := k+1 to n-1 do A[i][k] := A[i][k]/A[k][k]; for i := k+1 to n-1 do for j := k+1 to n-1 do A[i][j] := A[i][j]-A[i][k]*A[k][j]; end; lu := true; end; // Program główny var A : MReal; n, i, j : integer; begin // odczytujemy stopień macierzy A read(n); // tworzymy macierz A o odpowiednich rozmiarach SetLength(A, n); for i := 0 to n-1 do SetLength(A[i], n); // odczytujemy dane dla macierzy A for i := 0 to n-1 do for j := 0 to n-1 do read(A[i][j]); // wyznaczamy rozkład LU macierzy A if lu(n, A) then for i := 0 to n-1 do begin for j := 0 to n-1 do write(A[i][j]:8:3, ' '); writeln; end else writeln('DZIELNIK ZERO'); // usuwamy macierz z pamięci for i := 0 to n-1 do SetLength(A[i], 0); SetLength(A, 0); end. |
// Rozkład LU // Data: 30.04.2010 // (C)2020 mgr Jerzy Wałaszek //----------------------------- #include <iostream> #include <iomanip> #include <cmath> using namespace std; const double eps = 1e-12; // Funkcja realizuje algorytm rozkładu LU //--------------------------------------- bool lu(int n, double ** A) { int i, j, k; for(k = 0; k < n-1; k++) { if(fabs(A[k][k]) < eps) return false; for(i = k+1; i < n; i++) A[i][k] /= A[k][k]; for(i = k+1; i < n; i++) for(j = k+1; j < n; j++) A[i][j] -= A[i][k]*A[k][j]; } return true; } // Program główny int main() { double **A; int n, i, j; cout << setprecision(3) << fixed; // odczytujemy stopień macierzy A cin >> n; // tworzymy macierz A // o odpowiednich rozmiarach A = new double * [n]; for(i = 0; i < n; i++) A[i] = new double[n]; // odczytujemy dane dla macierzy A for(i = 0; i < n; i++) for(j = 0; j < n; j++) cin >> A[i][j]; // wyznaczamy rozkład LU macierzy A if(lu(n, A)) for(i = 0; i < n; i++) { for(j = 0; j < n; j++) cout << setw(8) << A[i][j] << " "; cout << endl; } else cout << "DZIELNIK ZERO\n"; // usuwamy macierz z pamięci for(i = 0; i < n; i++) delete [] A[i]; delete [] A; return 0; } |
Basic' Rozkład LU ' Data: 30.04.2010 ' (C)2020 mgr Jerzy Wałaszek '----------------------------- Const eps As Double = 1e-12 ' Funkcja realizuje algorytm rozkładu LU '--------------------------------------- Function lu(n As Integer, _ A As Double Ptr Ptr) _ As Integer Dim As Integer i, j, k For k = 0 To n-2 If Abs(A[k][k]) < eps Then _ Return 0 For i = k+1 To n-1 A[i][k] /= A[k][k] Next For i = k+1 To n-1 For j = k+1 To n-1 A[i][j] -= A[i][k]*A[k][j] Next Next Next lu = 1 End Function Dim A As Double Ptr Ptr Dim As Integer i, j, n Open Cons For Input As #1 ' odczytujemy stopień macierzy A Input #1, n ' tworzymy macierz A ' o odpowiednich rozmiarach A = New Double Ptr[n] For i = 0 To n-1 A[i] = New Double[n] Next ' odczytujemy dane dla macierzy A For i = 0 To n-1 For j = 0 To n-1 Input #1, A[i][j] Next Next Close #1 ' wyznaczamy rozkład LU macierzy A If lu(n, A) = 1 Then For i = 0 To n-1 For j = 0 To n-1 Print Using "####.### ";A[i][j]; Next Print Next Else Print "DZIELNIK ZERO" End If ' Usuwamy macierz A For i = 0 To n-1 Delete [] A[i] Next Delete [] A End |
Python
(dodatek)# Rozkład LU # Data: 23.04.2024 # (C)2024 mgr Jerzy Wałaszek #--------------------------- EPS = 1e-12 # Funkcja realizuje algorytm rozkładu LU #--------------------------------------- def lu(a): n = len(a) for k in range(n-1): if abs(a[k][k]) < EPS: return False for i in range(k+1, n): a[i][k] /= a[k][k] for i in range(k+1, n): for j in range(k+1, n): a[i][j] -= a[i][k]*a[k][j] return True # Program główny #--------------- # odczytujemy stopień macierzy A n = int(input()) # odczytujemy macierz A a = [] for i in range(n): arr = input().split() arr = [float(arr[j]) for j in range(n)] a.append(arr) # wyznaczamy rozkład LU macierzy A if lu(a): for i in range(n): for j in range(n): print("%8.3f" % a[i][j], end=" ") print() else: print("DZIELNIK ZERO") # usuwamy macierz z pamięci a = None |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2024 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.