Transponowanie macierzy Mnożenie macierzy Potęgowanie macierzy Wyznacznik macierzy
Problem Transponować macierz A m n na A T n m. Operacja transponowania macierzy polega na zamianie wierszy w kolumny i kolumn w wiersze. Operację transponowania oznaczamy literką T. Ponieważ kolumny i wiersze wymieniają się wzajemnie, macierz transponowana posiada n wierszy i m kolumn. Przykład: 3 6 3 1 7 2 4 2 0 3 3 1 = 3 1 4 3 6 7 2 3 3 2 0 1
Wejście m liczba wierszy macierzy, m N n liczba kolumn macierzy, n N A macierz do transponowania o m wierszach i n kolumnach, A R B macierz wynikowa o n wierszach i m kolumnach, B R Wyjście: Macierz B = A T Elementy pomocnicze: i w indeks wierszowy macierzy, i w N i k indeks kolumnowy macierzy, i k N Lista kroków: K01: Dla i w = 1,2,...,m wykonuj K02...K03 K02: Dla i k = 1,2,...,n wykonuj K03 K03: B[i k,i w ] A[i w,i k ] K04: Zakończ
Program generuje losowe liczby m i n (od 2 do 8). Następnie tworzy macierz A m n oraz macierz B n m. Macierz A jest wypełniana losowymi liczbami z zakresu od -99 do 99, wyświetlana i transponowana w macierzy B. Na koniec program wyświetla zawartość macierzy B.
#include <iostream> #include <iomanip> #include <cstdlib> #include <time.h> using namespace std; int main() int **A,**B,n,m,iw,ik; srand((unsigned)time(null)); // losujemy wymiary macierzy m = rand() % 7 + 2; n = rand() % 7 + 2; // tworzymy tablicę wskaźników A = new int * [m]; B = new int * [n]; // tworzymy tablice wierszy for(iw = 0; iw < m; iw++) A[iw] = new int[n]; for(iw = 0; iw < n; iw++) B[iw] = new int[m]; // wypełniamy macierz A losowymi liczbami for(iw = 0; iw < m; iw++) for(ik = 0; ik < n; ik++) A[iw][ik] = rand() % 199-99; // wyświetlamy macierz A cout << "m = " << m << endl << "n = " << n << endl << endl << "Matrix A:" << endl; for(iw = 0; iw < m; iw++) for(ik = 0; ik < n; ik++) cout << setw(5) << A[iw][ik]; cout << endl; // transponujemy macierz A w B for(iw = 0; iw < m; iw++) for(ik = 0; ik < n; ik++) B[ik][iw] = A[iw][ik]; // wyświetlamy macierz wynikową cout << endl << "Matrix B = AT:" << endl; for(iw = 0; iw < n; iw++) for(ik = 0; ik < m; ik++) cout << setw(5) << B[iw][ik]; cout << endl; // koniec, zwalniamy pamięć zajętą przez macierze for(iw = 0; iw < m; iw++) delete [] A[iw]; for(iw = 0; iw < n; iw++) delete [] B[iw]; delete [] A; delete [] B; return 0;
Problem: Transponować macierz kwadratową A n n w miejscu. Podany powyżej algorytm transponowania macierzy wymaga dodatkowej pamięci. Jeśli macierz jest kwadratowa, to możemy transponować ją w miejscu. zamieniając miejscami elementy kolumn i wierszy. Zasada jest następująca: Załóżmy, iż mamy transponować poniższą macierz stopnia 4: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Zwróć uwagę, iż dla macierzy kwadratowej główna przekątna pozostaje bez zmian po transponowaniu. Elementy leżące na głównej przekątnej nie musimy przemieszczać. W pierwszym kroku wymienimy zatem elementy pierwszej kolumny i pierwszego wiersza z pominięciem elementu 1, który leży właśnie na głównej przekątnej: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Po wykonaniu tej operacji pierwsza kolumna i pierwszy wiersz macierzy są gotowe. Nie będziemy ich już zmieniać. W drugim kroku wykonujemy identyczną operację, lecz na pozostałej części macierzy, tzn. z pominięciem pierwszego wiersza i pierwszej kolumny: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
W efekcie dwa pierwsze wiersze i dwie pierwsze kolumny macierzy są gotowe. Operację kontynuujemy z pozostałą częścią macierzy: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Operacja jest zakończona, ponieważ pozostała część macierzy jest już macierzą jednoelementową (zawiera tylko element 16), a transpozycja macierzy jednoelementowej jest tożsamościowa.
Wejście n stopień macierzy, n N A kwadratowa macierz do transponowania stopnia n, A R Wyjście: Macierz A = AT Elementy pomocnicze: i,j indeksy, i,j N t tymczasowe miejsce przechowywania elementu macierzy, t R Lista kroków: K01: Dla i = 1,2,...,n - 1 wykonuj K02...K05 K02: Dla j = i + 1,i + 2,...,n wykonuj K03...K05 K03: t A[i,j] K04: A[i,j] A[j,i] K05: A[j,i] t K06: Zakończ
Program generuje losowy stopień macierzy n (od 2 do 8). Następnie tworzy macierz kwadratową A n n, którą wypełnia losowymi liczbami z zakresu od -99 do 99. Macierz A zostaje wyświetlona i transponowana. Na koniec program wyświetla ponownie zawartość macierzy A.
#include <iostream> #include <iomanip> #include <cstdlib> #include <time.h> using namespace std; main() int **A,n,i,j,t; srand((unsigned)time(null)); // losujemy stopień macierzy n = rand() % 7 + 2; // tworzymy tablicę wskaźników A = new int * [n]; // tworzymy tablice wierszy A[i] = new int[n]; // wypełniamy macierz A losowymi liczbami for(j = 0; j < n; j++) A[i][j] = rand() % 199-99; cout << "n = " << n << endl << endl << "Matrix A:" << endl; for(j = 0; j < n; j++) cout << setw(5) << A[i][j]; cout << endl; // transponujemy macierz A for(i = 0; i < n - 1; i++) for(j = i + 1; j < n; j++) t = A[i][j]; A[i][j] = A[j][i]; A[j][i] = t; // wyświetlamy macierz A cout << endl << "Matrix AT:" << endl; for(j = 0; j < n; j++) cout << setw(5) << A[i][j]; cout << endl; // koniec, zwalniamy pamięć zajętą przez macierz delete [] A[i]; delete [] A; // wyświetlamy macierz A
Problem: Znaleźć wynik mnożenia macierzy A m n przez macierz B n p. Aby zrozumieć zasadę mnożenia dwóch macierzy, zastosujemy prosty schemat postępowania. Załóżmy, iż mamy macierz A 3 4 i B 4 5 o następującej zawartości: A 3 x 4 = 1 2 3 4 5 6 7 8 9 8 7 6 A 4 x5 = 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1
Wynikiem mnożenia dwóch macierzy jest nowa macierz, która posiada tyle wierszy, ile wierszy miała macierz A oraz tyle kolumn, ile kolumn miała macierz B. W naszym przypadku macierz ta będzie posiadała rozmiar 3 5, ponieważ macierz A posiada 3 wiersze, a macierz B posiada 5 kolumn. Zatem: A m n B n p = C m p Oznaczmy tę macierz jako C 3 5. Po lewej stronie macierzy C umieszczamy macierz A, natomiast macierz B umieszczamy ponad macierzą C. Obliczamy element c 1,1 jako sumę iloczynów kolejnych elementów wiersza 1 macierzy A przez elementy kolumny 1 macierzy B: 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 8 7 6 54?????????????? c 1,1 = (1 4) + (2 3) + (3 8) + (4 5) = 4 + 6 + 24 + 20 = 54
Wynika z tego fakt, iż macierz A musi posiadać w wierszu tyle samo elementów, co macierz B w kolumnie. Zatem rozmiary tych macierzy nie mogą być dowolne, lecz muszą spełniać prosty warunek: A m n, B n p Podobnie obliczamy element c 1,2 jako sumę iloczynów kolejnych elementów wiersza 1 macierzy A przez elementy kolumny 2 macierzy B: 1 2 3 4 5 6 7 8 9 8 7 6 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 54 54????????????? c 1,2 = (1 3) + (2 4) + (3 9) + (4 4) = 3 + 8 + 27 + 16 = 54
Operację tę kontynuujemy aż do wyliczenia wszystkich elementów w wierszu 1 macierzy C: 1 2 3 4 5 6 7 8 9 8 7 6 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 54 54 48 42 38?????????? c 1,3 = (1 2) + (2 5) + (3 8) + (4 3) = 2 + 10 + 24 + 12 = 48 c 1,4 = (1 1) + (2 6) + (3 7) + (4 2) = 1 + 12 + 21 + 8 = 42 c 1,5 = (1 2) + (2 7) + (3 6) + (4 1) = 2 + 14 + 18 + 4 = 38
Po wyliczeniu wiersza 1 macierzy C rozpoczynamy obliczanie elementów wiersza 2. Działania wykonujemy wg tego samego schematu sumujemy iloczyny kolejnych elementów wiersza macierzy A przez kolejne elementy kolumny macierzy B. 1 2 3 4 5 6 7 8 9 8 7 6 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 54 54 48 42 38 134 134 120 106 102????? c 2,1 = (5 4) + (6 3) + (7 8) + (8 5) = 20 + 18 + 56 + 40 = 134 c 2,2 = (5 3) + (6 4) + (7 9) + (8 4) = 15 + 24 + 63 + 32 = 134 c 2,3 = (5 2) + (6 5) + (7 8) + (8 3) = 10 + 30 + 56 + 24 = 120 c 2,4 = (5 1) + (6 6) + (7 7) + (8 2) = 5 + 36 + 49 + 16 = 106 c 2,5 = (5 2) + (6 7) + (7 6) + (8 1) = 10 + 42 + 42 + 8 = 102
Na koniec wyliczamy ostatni wiersz macierzy C według tego samego schematu postępowania: 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 8 7 6 54 54 48 42 38 134 134 120 106 102 146 146 132 118 122 c 3,1 = (9 4) + (8 3) + (7 8) + (6 5) = 36 + 24 + 56 + 30 = 146 c 3,2 = (9 3) + (8 4) + (7 9) + (6 4) = 27 + 32 + 63 + 24 = 146 c 3,3 = (9 2) + (8 5) + (7 8) + (6 3) = 18 + 40 + 56 + 18 = 132 c 3,4 = (9 1) + (8 6) + (7 7) + (6 2) = 9 + 48 + 49 + 12 = 118 c 3,5 = (9 2) + (8 7) + (7 6) + (6 1) = 18 + 56 + 42 + 6 = 122
Rachunek skończony. Możemy zapisać: 1 2 3 4 5 6 7 8 9 8 7 6 Χ 4 3 2 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 = 54 54 48 42 38 134 134 120 106 102 146 146 132 118 122
Wejście m,n,p rozmiary macierzy, m,n,p N A macierz o rozmiarze m n, A R B macierz o rozmiarze n p, B R C macierz o rozmiarze m p, C R Wyjście: Macierz C = A B Elementy pomocnicze: i,j,k indeksy elementów macierzy, i,j,k N s suma częściowa, s R Lista kroków: K01: Dla i = 1,2,...,m wykonuj K02...K06 K02: Dla j = 1,2,...,p wykonuj K03...K06 K03: s 0 ; zerujemy sumę częściową K04: Dla k = 1,2,...,n wykonuj K05 ; obliczamy sumę iloczynów K05: s s + A[i,k] B[k,j] K06: C[i,j] s ; sumę umieszczamy w elemencie macierzy wynikowej K07: Zakończ
// Mnożenie macierzy // Data: 26.01.2010 // (C)2012 mgr Jerzy Wałaszek //----------------------------- #include <iostream> #include <iomanip> using namespace std; int main() int **A,**B,**C,m,n,p,i,j,k,s; // odczytujemy wymiary macierzy cin >> m >> n >> p; // tworzymy macierze o odpowiednich rozmiarach A = new int * [m]; B = new int * [n]; C = new int * [m]; for(i = 0; i < m; i++) A[i] = new int[n]; C[i] = new int[p]; B[i] = new int[p]; // odczytujemy dane dla macierzy A for(i = 0; i < m; i++) for(j = 0; j < n; j ++) cin >> A[i][j]; // odczytujemy dane dla macierzy B for(j = 0; j < p; j++) cin >> B[i][j]; cout << endl; // mnożymy macierz A przez B i wynik umieszczamy w C for(i = 0; i < m; i++) for(j = 0; j < p; j++) s = 0; for(k = 0; k < n; k++) s += A[i][k] * B[k][j]; C[i][j] = s; // wyprowadzamy wynik mnożenia w C cout << "C = A x B:\n"; for(i = 0; i < m; i++) for(j = 0; j < p; j++) cout << setw(6) << C[i][j]; cout << endl; // zwalniamy pamięć zajętą przez macierze for(i = 0; i < m; i++) delete [] A[i]; delete [] C[i]; delete [] B[i]; delete [] A; delete [] B; delete [] C; return 0;
Problem: Obliczyć k-tą potęgę A n n. Rozwiązanie nr 1: Zdefiniujmy następujące operacje: A 0 = I (macierz jednostkowa) A 1 = A A 2 = A A A 3 = A A A A k = A A... A (k - 1 mnożeń) Pierwszy algorytm będzie algorytmem naiwnym, który wykonuje k-1 mnożeń macierzy A.
Wejście n stopień macierzy, n N, n > 0 A potęgowana macierz, A R k wykładnik potęgowy, k N, k 0 Wyjście: A zawiera k-tą potęgę wejściowej macierzy A. Elementy pomocnicze: W macierz pomocnicza, przechowuje wyniki częściowe mnożenia, W R P macierz pomocnicza, przechowuje A, P R i do sterowania pętlą, i N Lista kroków: K01: Jeśli k > 0, to idź do K04 K02: Ustaw macierz jednostkową w A ;A0 = I K03: Zakończ K04: Jeśli k = 1, to zakończ ; A1 = A K05: P A ; zapamiętaj oryginalną macierz A K06: Dla i = 2,3,..., k wykonuj K07...K08 K07: W P A ; wykonaj mnożenie K08: A W ; w A wynik mnożenia (i potęgowania) K09: Zakończ
// Potęgowanie macierzy // Data: 9.02.2011 // (C)2012 mgr Jerzy Wałaszek //----------------------------- #include <iostream> #include <iomanip> using namespace std; // procedura mnożenia macierzy // C = A x B //---------------------------- void mnoz(int n, double ** A, double ** B, double ** C) int i,j,k; double s; for(j = 0; j < n; j++) s = 0; for(k = 0; k < n; k++) s += A[i][k] * B[k][j]; C[i][j] = s; // procedura przepisuje macierz A do macierzy B //--------------------------------------------- void przepisz(int n, double ** A, double ** B) int i,j; for(j = 0; j < n; j++) B[i][j] = A[i][j]; // procedura ustawia w macierzy A macierz jednostkową //--------------------------------------------------- void jednostkowa(int n, double ** A) int i,j; for(j = 0; j < n; j++) A[i][j] = 0; A[i][i] = 1; // procedura wylicza potęgę k-tą macierzy A //----------------------------------------- void potega(int k, int n, double ** A) double ** P, ** W; int i; if(!k) jednostkowa(n,a); else if(k > 1) // tworzymy macierze pomocnicze P i W P = new double * [n]; W = new double * [n]; P[i] = new double[n]; W[i] = new double[n]; // macierz A zapamiętujemy w P przepisz(n,a,p); // w pętli wykonujemy kolejne mnożenia - wynik zawsze w A for(i = 2; i <= k; i++) mnoz(n,p,a,w); // W <- P x A przepisz(n,w,a); // A <- P x A // usuwamy macierze P i W delete [] P[i]; delete [] W[i]; delete [] P; delete [] W; //*** PROGRAM GŁÓWNY *** //---------------------- int main() double ** A; int n,i,j,k; cout << fixed << setprecision(2); // wczytujemy wykładnik k oraz stopień macierzy n cin >> k >> n; // tworzymy macierz dynamiczną i wczytujemy dane wierszami A = new double * [n]; A[i] = new double[n]; for(j = 0; j < n; j++) cin >> A[i][j]; // obliczamy k-tą potęgę A potega(k,n,a); // wyświetlamy wyniki cout << endl; for(j = 0; j < n; j++) cout << setw(10) << A[i][j] << " "; cout << endl; // usuwamy macierz A delete [] A[i]; delete [] A; return 0;
Rozwiązanie nr 2: Zauważmy następującą własność mnożenia macierzy: An+m = An Am Dalej: A2 = A A A4 = A2 A2 A8 = A4 A4... Jak widzimy, potęgi o wykładnikach będących potęgami liczby 2 możemy łatwo otrzymywać mnożąc macierz przez siebie. Idea nowego algorytmu potęgowania przedstawię na prostym przykładzie. Załóżmy, iż chcemy obliczyć A13. Zatem k = 13. Możemy zapisać: A13 = A8+4+1 = A8 A4 A Zwróć uwagę, iż mnożymy przez siebie macierze, które są potęgami pierwotnej macierzy A o wykładnikach równych 8, 4 i 1. Na takie liczby rozkłada się 13. Takie potęgi bardzo łatwo otrzymać. Potrzebujemy 3 mnożeń do wyliczenia wszystkich niezbędnych potęg macierzy A. Aby ostatecznie otrzymać A13, potrzebne są jeszcze dwa dodatkowe mnożenia, czyli w sumie 5 mnożeń. Poprzedni algorytm doszedłby do tego samego wyniku po wykonaniu aż 12 mnożeń. Przy większych potęgach zysk jest jeszcze większy.
Wykładnika k wcale nie musimy rozbijać na potęgi liczby 2. W pamięci komputera k jest przechowywane w postaci binarnej. Wystarczy testować stan odpowiednich bitów k: kolejne potęgi liczby 2 = 2 4 2 3 2 2 2 1 2 0 bity k = 0 1 1 0 1 numery pozycji bitów kolejnych = 4 3 2 1 0 Zasada pracy algorytmu jest następująca: W macierzy wyniku W ustawiamy macierz jednostkową. Dopóki k jest większe od zera, testujemy najmłodszy bit k i jeśli ma on wartość 1, to wykonujemy mnożenie W = W A. Bity k przesuwamy o 1 w prawo, aby pozbyć się testowanego bitu. Jeśli po tej operacji k ma wartość 0, to potęgowanie jest zakończone i przerywamy pętlę. W przeciwnym razie macierz A podnosimy do kwadratu: A = A A i pętlę kontynuujemy. Gdy pętla się zakończy, przepisujemy macierz W do A.
Wejście n stopień macierzy, n N, n > 0 A potęgowana macierz, A R k wykładnik potęgowy, k N, k 0 Wyjście: A zawiera k-tą potęgę wejściowej macierzy A. Elementy pomocnicze: W macierz pomocnicza, tworzony jest w niej wynik potęgowania, W R P macierz pomocnicza, P R Lista kroków: K01: Ustaw macierz jednostkową w W K02: Dopóki k > 0, wykonuj K03...K09 ; w pętli obliczamy k-tą potęgę A K03: Jeśli k and 1 = 0, to idź do K06 ; testujemy kolejne bity k K04 P W A ; jeśli bit jest ustawiony, to do W dołączamy A K05: W P K06: k k shr 1 ; przesuwamy w prawo bity k K07: Jeśli k = 0, to idź do K10 K08: P A A ; wyznaczamy kolejny kwadrat A K09 A P K10: A W ; wynik do A K11: Zakończ
// procedura wylicza potęgę k-tą macierzy A //----------------------------------------- void potega(int k, int n, double ** A) double ** P, ** W; int i; // Szybkie potęgowanie macierzy // Data: 19.02.2012 // (C)2012 mgr Jerzy Wałaszek //----------------------------- #include <iostream> #include <iomanip> using namespace std; // procedura mnożenia macierzy // C = A x B //---------------------------- void mnoz(int n, double ** A, double ** B, double ** C) int i,j,k; double s; for(j = 0; j < n; j++) s = 0; for(k = 0; k < n; k++) s += A[i][k] * B[k][j]; C[i][j] = s; // procedura przepisuje macierz A do macierzy B //--------------------------------------------- void przepisz(int n, double ** A, double ** B) int i,j; for(j = 0; j < n; j++) B[i][j] = A[i][j]; // procedura ustawia w macierzy A macierz jednostkową //--------------------------------------------------- void jednostkowa(int n, double ** A) int i,j; for(j = 0; j < n; j++) A[i][j] = 0; A[i][i] = 1; // tworzymy macierze W i P W = new double * [n]; P = new double * [n]; W[i] = new double[n]; P[i] = new double[n]; // w macierzy W ustawiamy macierz jednostkową jednostkowa(n,w); // w pętli obliczamy potęgę macierzy A w W while(k) if(k & 1) // testujemy najmłodszy bit k mnoz(n,w,a,p); // jeśli ustawiony, to przepisz(n,p,w); // W = W x A k >>= 1; // przesuwamy bity k w prawo if(!k) break; // jeśli brak bitów 1, przerywamy mnoz(n,a,a,p); // A = A x A przepisz(n,p,a); // wynik potęgowania wraca do macierzy A przepisz(n,w,a); // usuwamy macierze W i P delete [] W[i]; delete [] P[i]; delete [] W; delete [] P; //*** PROGRAM GŁÓWNY *** //---------------------- int main() double ** A; int n,i,j,k; cout << fixed << setprecision(2); // wczytujemy wykładnik k oraz stopień macierzy n cin >> k >> n; // tworzymy macierz dynamiczną i wczytujemy dane wierszami A = new double * [n]; A[i] = new double[n]; for(j = 0; j < n; j++) cin >> A[i][j]; // obliczamy k-tą potęgę A potega(k,n,a); // wyświetlamy wyniki cout << endl; for(j = 0; j < n; j++) cout << setw(10) << A[i][j] << " "; cout << endl; // usuwamy macierz A delete [] A[i]; delete [] A; return 0;
Problem: Obliczyć wyznacznik macierzy kwadratowej A n n za pomocą rozwinięcia Laplace'a. Wyznacznik (ang. determinant oznaczany det) jest liczbą, która została skojarzona z daną macierzą kwadratową A. Rozwinięcie Laplace'a pozwala w sposób rekurencyjny policzyć wyznacznik dowolnej macierzy kwadratowej. Wzór jest następujący: A macierz kwadratowa o rozmiarze n, której wyznacznik liczymy i ustalony wiersz macierzy A, wg którego dokonujemy rozwinięcia Laplace'a j kolejne numery kolumn w macierzy A ai,j element macierzy A leżący w i-tym wierszu i j-tej kolumnie Mi,j minor elementu ai,j - jest to wyznacznik macierzy powstałej z A po usunięciu z niej i-tego wiersza i j-tej kolumny
Powyższy wzór jest rekurencyjny, ponieważ występuje w nim Minor, który sam jest wyznacznikiem, a zatem obliczamy go za pomocą tego samego wzoru. Dla przykładu obliczmy wyznacznik macierzy: Jako wiersz rozwinięcia wybierzemy pierwszy wiersz zawierający elementy 1, 2 i 3. Dla każdego z tych elementów musimy wyznaczyć minory. Najpierw wyznaczmy macierze minorowe:
Obliczamy wyznaczniki M. Rozwijamy je wg pierwszego wiersza. Minory macierzy o rozmiarze 2 są macierzami jednoelementowymi. Wyznacznik macierzy jednoelementowej jest równy temu elementowi: M1,1 = (-1)1+1 5 2 + (-1)1+2 4 7 M1,1 = (-1)2 10 + (-1)3 28 M1,1 = 10-28 M1,1 = -18 M1,2 = (-1)1+1 6 2 + (-1)1+2 4 3 M1,2 = (-1)2 12 + (-1)3 12 M1,2 = 12-12 M1,2 = 0 M1,3 = (-1)1+1 6 7 + (-1)1+2 5 3 M1,3 = (-1)2 42 + (-1)3 15 M1,3 = 42-15 M1,3 = 27
Mając policzone wartości minorów, możemy przystąpić do wyznaczenia wyznacznika macierzy A: det A = (-1) 1+1 1 M 1,1 + (-1) 1+2 2 M 1,2 + (-1) 1+3 3 M 1,3 det A = (-1) 2 M 1,1 + (-1) 3 2M 1,2 + (-1) 4 3M 1,3 det A = M 1,1-2M 1,2 + 3M 1,3 det A = -18-0 + 81 det A = 63 Możemy już przystąpić do wstępnego określenia algorytmu. Wyznacznik macierzy A n n obliczamy następująco: Jeśli n = 1, to wyznacznik macierzy jest równy wartości elementu tej macierzy, czyli: det A 1 1 = det [ a 1 ] = a 1
Dla n > 1 wybieramy dowolny wiersz lub kolumnę (najlepiej taki, w którym jest najwięcej zer). Następnie każdy wyraz tego wiersza lub kolumny przemnażamy przez wyznacznik macierzy, która powstaje przez usunięcie wiersza i kolumny z mnożonym wyrazem (wyznacznik ten obliczamy rekurencyjnie tą samą metodą). Jeśli suma numeru wiersza i kolumny mnożonego wyrazu jest nieparzysta, to otrzymany iloczyn mnożymy dodatkowo przez -1. Wyliczone iloczyny sumujemy otrzymując wartość wyznacznika. Klasa złożoności obliczeniowej podanej metody jest równa O(n!). Dokonajmy prostej analizy. Wyznacznik wyliczamy mnożąc kolejne wyrazy wiersza (lub kolumny) przez wartości wyznaczników niższego poziomu i sumując otrzymane iloczyny (dochodzi jeszcze ewentualna zmiana znaku). Zatem dla wyznacznika n-tego poziomu musimy wyliczyć n wyznaczników poziomu (n-1). Czyli: ilość mnożeń = n ilość mnożeń dla wyznacznika poziomu n - 1 ilość dodawań = n ilość dodawań dla wyznacznika poziomu n - 1 + (n - 1) dodawań iloczynów
W poniższej tabeli wyliczyliśmy ilości mnożeń i dodawań dla kilku kolejnych wartości n. Złożoność obliczeniowa operacji wyliczania wyznacznika macierzy n Ilość dodawań ilość mnożeń 1 d 1 = 0 m 1 = 0 2 d 2 = 1 = 2d 1 + 1 m 2 = 2 3 d 3 = 5 = 3d 2 + 2 m 3 = 6 = 3m 2 4 d 4 = 23 = 4d 3 + 3 m 4 = 24 = 4m 3 5 d 5 = 119 = 5d 4 + 4 m 5 = 120 = 5m 4 Widać wyraźnie, iż od wartości n = 2 ilość mnożeń jest równa n!, a ilość dodawań jest równa n! - 1. Prowadzi to do bardzo szybkiego wzrostu czasu wykonania algorytmu. Pozostaje drobna kwestia techniczna. W algorytmie obliczamy wyznaczniki macierzy, które powstają przez usunięcie z macierzy głównej wiersza i kolumny z elementem mnożącym. Aby uniknąć kopiowania elementów, do algorytmu wyliczającego wyznacznik będziemy przekazywali wektor numerów kolumn, w którym umieścimy kolejne numery kolumn zawarte w tej podmacierzy oraz numer pierwszego wiersza. Na przykład w poniższej macierzy chcemy wyliczyć wyznacznik wg elementu a1,3:
1 2 3 4 podmacierz wektor kolumn 1 a 1,1 a 1,2 a 1,3 1 2 4 a 1,4 2 a 2,1 a 2,2 a 1 2 4 2 a 2,1 a 2,2 a 2,3 2,4 a 2,4 3 a 3,1 a 3,2 a 3,4 3 a 3,1 a 3,2 a 3,3 numer wiersza = 2 a 3,4 4 a 4,1 a 4,2 a 4,4 4 a 4,1 a 4,2 a 4,3 a 4,4 Dane te jednoznacznie określą podmacierz, której wyznacznik należy wyliczyć. Zwróć uwagę, iż wektor kolumn zawiera tyle elementów, ile wynosi stopień wyznacznika. Numer wiersza zawsze będzie numerem o 1 większym od wiersza zawierającego mnożony przez wyznacznik element. Do elementów podmacierzy będziemy się odwoływać zawsze poprzez wektor kolumn.
Wejście n określa stopień wyznacznika do obliczenia. n N w określa numer wiersza, w którym rozpoczyna się podmacierz. w N WK wektor kolumn. Zawiera numery kolumn z macierzy głównej, które zawiera podmacierz. WK zawiera tyle numerów kolumn, ile wynosi stopień wyznacznika. Elementy N i są numerowane od 0. A macierz, której wyznacznik liczymy. Wiersze i kolumny są numerowane od zera. A R Wyjście: Wynik działania funkcji det(n,w,wk,a) jest wartością wyznacznika Elementy pomocnicze: i,j,k zmienne pomocnicze dla pętli i,j,k N m mnożnik iloczynu wyrazu macierzy przez wyznacznik podmacierzy. Przyjmuje na przemian wartości 1 oraz (-1), m C. KK wektor kolumn umożliwiający rekurencyjne przekazywanie numerów kolumn podmacierzy, dla których liczone są wyznaczniki. Elementami wektora kolumn są liczby całkowite. Elementy C i są numerowane od zera. Wektor KK ma o jeden element mniej niż wektor WK otrzymany na wejściu. s zlicza sumę iloczynów wyrazów wiersza przez wyznaczniki niższych stopni. s R
Lista kroków: Funkcja rekurencyjna det(n,w,wk,a): Parametrami funkcji są: n stopień podmacierzy przekazywane przez wartość w bieżący wiersz macierzy głównej, w którym rozpoczyna się podmacierz przekazywane przez wartość WK wektor kolumn o n elementach przekazanie przez referencję A macierz podstawowa przekazanie przez referencję K01: Jeśli n = 1, to zakończ z wynikiem A[w][WK[0]] ; sprawdzamy zakończenie rekurencji zwracamy wynik funkcji K02: Utwórz wektor KK o n-1 elementach ; tworzymy tablicę dynamiczną K03: s 0 ; przygotowujemy się do sumowania iloczynów wyrazów przez minory K04: m 1 ; mnożnik (-1)i+j K05: Dla i = 0,1,...,n - 1 wykonuj K06...K12 ; rozpoczynamy pętlę obliczającą rekurencyjnie rozwinięcie Laplace'a K06: k 0 ; przygotowujemy wektor kolumn dla wywołania rekurencyjnego K07: Dla j = 0,1,...,n - 2 wykonuj K08...K10 K08: Jeśli k = i, to k k + 1 ; pomijamy kolumnę z bieżącym wyrazem K09: KK[j] WK[k] ; przepisujemy kolumny z WK do KK pomijając bieżącą K10: k k + 1 K11: s s + m A[w][WK[i]] det(n - 1, w + 1, KK, A) ; wywołanie rekurencyjne K12: m (- m) ; następny mnożnik (-1)i+j K13: Usuń wektor KK ; tablica dynamiczna przestaje być potrzebna, zwalniamy pamięć K12: Zakończ z wynikiem s ; wynik funkcji
// Wyznacznik wg rekurencyjnego rozwinięcia Laplace'a // Data : 8.02.2011 // (C)2012 mgr Jerzy Wałaszek //---------------------------- #include <iostream> #include <iomanip> using namespace std; // Rekurencyjna funkcja obliczająca rozwinięcie Laplace'a //------------------------------------------------------- double det(int n, int w, int * WK, double ** A) int i,j,k,m, * KK; double s; if(n == 1) return A[w][WK[0]]; else // sprawdzamy warunek zakończenia rekurencji // macierz 1 x 1, wyznacznik równy elementowi //*** PROGRAM GŁÓWNY *** //---------------------- int main() int n,i,j; // stopień macierzy int * WK; // wektor kolumn double ** A; // macierz cout << fixed << setprecision(4); cin >> n; A = new double * [n]; A[i] = new double[n]; for(j = 0; j < n; j++) cin >> A[i][j]; // odczytujemy stopień macierzy // tworzymy macierz wskaźników // tworzymy wiersz // czytamy wiersz macierzy KK = new int[n - 1]; // tworzymy dynamiczny wektor kolumn WK = new int[n]; // tworzymy wiersz kolumn s = 0; m = 1; // zerujemy wartość rozwinięcia // początkowy mnożnik WK[i] = i; // wypełniamy go numerami kolumn // pętla obliczająca rozwinięcie k = 0; // tworzymy wektor kolumn dla rekurencji for(j = 0; j < n - 1; j++) // ma on o 1 kolumnę mniej niż WK if(k == i) k++; // pomijamy bieżącą kolumnę KK[j] = WK[k++]; // pozostałe kolumny przenosimy do KK s += m * A[w][WK[i]] * det(n - 1,w + 1, KK, A); cout << endl; cout << det(n, 0, WK, A) << endl; // obliczamy i wyświetlamy wyznacznik delete [] WK; // usuwamy tablice dynamiczne delete [] A[i]; delete [] A; m = -m; // kolejny mnożnik delete [] KK; return s; // usuwamy zbędną już tablicę dynamiczną // ustalamy wartość funkcji