Rozwiązywanie układów równań liniowych metodą Gaussa.

Transkrypt

Rozwiązywanie układów równań liniowych metodą Gaussa.
Algorytmy, struktury danych i techniki programowania
Piotr Wróblewski
Rozwiązywanie układów równań liniowych metodą Gaussa
Potrzeba rozwiązywania układów równań liniowych zachodzi w wielu dziedzinach,
szczególnie technicznych. Biorąc pod uwagę, że w samym rozwiązywaniu układów równań
nie ma nic odkrywczego (uczono nas już tego w szkole podstawowej!), cenne wydaje się
dysponowanie procedurą komputerową, która wykona za nas tę żmudną pracę.
Aby komputer mógł rozwiązać dany układ równań, musimy go uprzednio zapisać w postaci
rozszerzonej, tzn. nie eliminując współczynników równych zero i pisząc zmienne
w określonej kolejności. To wszystko ma na celu prawidłowe skonstruowanie macierzy
rozszerzonej układu.
Układ równań:
5x + z = 9
x- z+y=6
2x - y + z = 0
musi zatem zostać przedstawiony jako:
5x + 0y + 1z = 9
1x + 1y - 1z = 6
2x - 1y + 1z = 0
co pozwoli na zapisanie całości w postaci macierzowej:
 5 0 1   x   9

   
 1 1 − 1  y  =  6 
 2 − 1 1   z   0

   
Wymnożenie tych macierzy powinno spowodować powrót do klasycznej, czytelnej postaci.
Zaletą reprezentacji macierzowej jest możliwość zapisania wszystkich współczynników
liczbowych w jednej tablicy N × (N+1) i operowania nimi podczas rozwiązywania układu.
Operacje na tej macierzy będą odbiciem przekształceń dokonywanych na równaniach
(np. w celu eliminacji zmiennych, dodawania równania stronami itd.).
Z uwagi na łatwość implementacji programowej bardzo szeroko rozpowszechnioną metodą
rozwiązywania układów równań liniowych jest tzw. eliminacja Gaussa. Przebiega ona
zasadniczo w dwóch etapach: sprowadzania macierzy układu do tzw. macierzy trójkątnej,
wypełnionej zerami poniżej przekątnej, oraz redukcji wstecznej, mającej na celu wyliczenie
wartości poszukiwanych zmiennych. W pierwszym etapie eliminujemy zmienną x
z wszystkich oprócz pierwszego wiersza (poprzez klasyczne dodawanie wiersza bieżącego,
pomnożonego przez współczynnik, który spowoduje eliminację). W etapie drugim
postępujemy identycznie ze zmienną y i wierszem 2. w celu ostatecznego otrzymania
macierzy trójkątnej. Popatrzmy na przykładzie:
-eliminacja x z wierszy 2. i 3. (efekt dodawania wierszy jest pokazany w etapie następnym):
Algorytmy, struktury danych i techniki programowania
*(-0,2)
5x + 0y + 1z = 9
1x + 1y - 1z = 6
2x - 1y + 1z = 0
Piotr Wróblewski
*(-0,4)
-eliminacja y z wierszy 1. i 3. (w pierwszym nie ma już nic do zrobienia):
5x + 0y + 1z = 9
0x + 1y – 1,2z = 4,2
0x - 1y + 0,6z = -3,6
*1
-otrzymujemy ostatecznie macierz trójkątną:
5x + 0y + 1z = 9
0x + 1y – 1,2z = 4,2
-0x + 0y – 0,6z = 0,6
Mając macierz w takiej postaci, można już pokusić się o wyliczenie zmiennych (redukcja
wsteczna, idziemy do ostatniego do pierwszego wiersza układu):
z = -0,6/0,6 = -1
y = 1,2z + 4,2 = 3
x = (9-z)/5 = 2
Metoda nie jest zatem skomplikowana, choć jej zapis w C++ może się wydać początkowo
nieczytelny. Jedyną niebezpieczną operacją metody eliminacji Gaussa jest … eliminacja
zmiennych, która czasami może prowadzić do dzielenia przez zero (jeśli na etapie i
eliminowana zmienna w danym równaniu nie występuje). Biorąc jednak pod uwagę,
że zamiana wierszy miejscami nie wpływa na rozwiązanie układu, niebezpieczeństwo
dzielenia przez zero może być łatwo oddalone poprzez taki właśnie wybieg. Oczywiście,
zamiana wierszy może okazać się niemożliwa ze względu na niespełnienie warunku, jakim
jest znalezienie poniżej wiersza i takiego wiersza, który ma konfliktową zmienną różną
od zera. W takim przypadku układ równań nie ma rozwiązania, co też jest pewną informacją
dla użytkownika.
Oto pełna treść programu wykonującego eliminację Gaussa, wraz z danymi przykładowymi:
const int N=3;
double x[N]; // wyniki
double a[N][N+1]=
{
{5, 0, 1,
{1, 1, -1,
{2, -1, 1,
};
double as[N][N+1]=
{
{1 , 1, 1,
{0 , 1, 1,
9},
6},
0}
9},
6},
Algorytmy, struktury danych i techniki programowania
{0,
1,
1,
Piotr Wróblewski
4}
};
int gauss(double a[N][N+1], double x[N])
{
int max;
double tmp;
for(int i=0; i<N; i++) // eliminacja
{
max=i;
for(int j=i+1; j<N; j++)
if(fabs(a[j][i])>fabs(a[max][i]))
max=j;
//fabs(x)=|x|, wartość bezwzględna dla danych double
for(int k=i; k<N+1; k++) // zamiana wierszy wartościami
{
tmp=a[i][k];
a[i][k]=a[max][k];
a[max][k]=tmp;
}
if(a[i][i]==0)
return 0; // Układ sprzeczny!
for(j=i+1; j<N; j++)
for(k=N; k>=i; k--)
// mnożenie wiersza j przez współczynnik "zerujący":
a[j][k]=a[j][k]-a[i][k]*a[j][i]/a[i][i];
}
}
// redukcja wsteczna
for(int j=N-1; j>=0; j--)
{
tmp=0;
for(int k=j+1; k<=N; k++)
tmp=tmp+a[j][k]*x[k];
x[j]=(a[j][N]-tmp)/a[j][j];
}
return 1; // wszystko w porządku!
int main()
{
if(!gauss(a, x))
cout << "Układ (1) jest sprzeczny!\n";
else
{
cout << "Rozwiązanie:\n";
for(int i=0;i<N;i++)
cout << "x["<<i<<"]="<<x[i] << endl;
}
if(!gauss(as, x))
cout << "Układ (2) jest sprzeczny!\n";
}