NUMERYCZNE ALGORYTMY PRZECHOWYWANIA MACIERZY
Transkrypt
NUMERYCZNE ALGORYTMY PRZECHOWYWANIA MACIERZY
Scientific Bulletin of Chelm Section of Mathematics and Computer Science No. 1/2008 NUMERYCZNE ALGORYTMY PRZECHOWYWANIA MACIERZY RZADKICH RADOSŁAW MATUSIK Katedra Analizy Matematycznej i Teorii Sterowania, Wydział Matematyki i Informatyki, Uniwersytet Łódzki Streszczenie. W artykule przedstawiono różne typy macierzy rzadkich, a także wybrane numeryczne algorytmy przechowywania tych macierzy. Każdy z algorytmów omówiony został na przykładzie. W artykule wskazano również obszary zastosowań każdego ze schematów, a także korzyści wynikające z przechowywania elementów macierzy rzadkich za pomocą tych algorytmów. 1. Wstęp Przez macierz rzadką będziemy rozumieć macierz, której tylko nieznaczna część elementów jest różna od zera. Macierze tego typu mają szerokie zastosowanie w praktyce, m.in. w teorii sieci elektrycznych, teorii grafów, genetyce, czy też socjologii. Istnieje wiele numerycznych algorytmów przechowywania w pamięci komputera macierzy rzadkich. Powstały one w celu zaoszczędzenia pamięci, a także ograniczenia liczby wykonywanych na nich operacji. Pozwalają m.in. na rozwiązywanie układów równań liniowych przy mniejszej liczbie działań arytmetycznych, niż w przypadku układu o tej samej liczbie równań z macierzą pełną, umożliwiają wyznaczenie rozwiązania z większą precyzją obliczeń, a także pozwalają zaoszczędzić pamięć maszyny wykonującej obliczenia. Kilka algorytmów przechowywania macierzy rzadkich zostało omówionych w dalszej części artykułu. 2. Przykłady macierzy rzadkich Do macierzy rzadkich zaliczamy następujące typy macierzy o charakterystycznej strukturze: a) diagonalne: elementy niezerowe znajdują się na głównej przekątnej ⎡ x 0 x 0 ... 0 0 ⎢0 ⎢ ⎢ 0 A=⎢ ⎢ ⎣... 0 0 x ... 0 225 ... ... ... ... 0 ⎤ 0 0⎥ ⎥ 0⎥ ⎥, ⎥ ...⎦ x 226 RADOSŁAW MATUSIK b) trójkątne: elementy niezerowe znajdują się pod główną przekątną (macierz trójkątna dolna) lub nad główną przekątną (macierz trójkątna górna) ⎡ x ⎢x ⎢ ⎢x A=⎢ ⎢ ⎣... x 0 x x ... x 0 0 x ... x ... ... ... ... x ⎤ ⎡ 0 0⎥ ⎥ 0⎥ ⎥ ⎥ ...⎦ x x ⎢0 ⎢ A=⎢ ⎢0 ⎢ ⎣... 0 lub x x 0 ... 0 x x x ... 0 ... ... ... ... 0 ⎤ x x⎥ ⎥ x⎥ ⎥, ⎥ ...⎦ x c) wstęgowe: αij = 0, jeśli i − j > β lub j − i > γ dla pewnych β i γ, αk,k−β = 0 dla chociaż jednej wartości k, αk,k+γ = 0 dla chociaż jednej wartości k. Szerokość wstęgi (czyli maksymalna liczba elementów niezerowych w wierszu) takiej macierzy wynosi w = β + γ + 1. Macierz wstęgowa jest symetryczna, gdy γ = β, αij = 0 dla i − j > 1 oraz j − i > 2 ⎡ x ⎢x ⎢ ⎢ ⎢0 ⎢ A=⎢ ⎢0 ⎢0 ⎢ ⎢ ⎣0 0 x x x 0 0 0 0 x x x x 0 0 0 0 x x x x 0 0 0 0 x x x x 0 0 0 0 x x x x ⎤ 0 0⎥ ⎥ ⎥ 0⎥ ⎥ 0⎥ ⎥. x⎥ ⎥ ⎥ x⎦ x Widać, że szerokość wstęgi wynosi w tym przypadku w = 1 + 2 + 1 = 4. d) trójdiagonalne (szczególny przypadek macierzy wstęgowej, gdy β = γ = 1): elementy niezerowe występują symetrycznie po obu stronach głównej przekątnej ⎡ x ⎢ ⎢x ⎢ ⎢0 A=⎢ ⎢... ⎢ ⎢ ⎣0 0 x x x ... 0 0 0 x x ... 0 0 0 0 x ... 0 0 0 0 0 ... 0 0 ... ... ... ... ... ... 0 0 0 ... x 0 0 0 0 ... x x ⎤ 0 0⎥ ⎥ ⎥ 0⎥ ⎥. ...⎥ ⎥ x⎥ ⎦ x Do macierzy rzadkich zaliczamy nie tylko te o specjalnej strukturze, jak w powyższych przykładach. Za macierz rzadką uznaje się każdą macierz, w której liczba elementów zerowych zdecydowanie przewyższa liczbę elementów niezerowych, np. ⎡ ⎤ 0 0 x 0 x 0 0 0 0 0 0 0 0 0 0 0 0 0 0⎥ ⎦. x 0 0 0 0 0 x x 0 0 A=⎢ ⎣0 3. Numeryczne algorytmy przechowywania macierzy rzadkich Bardzo często macierze rzadkie mają duże rozmiary i przechowywanie wszystkich elementów w tradycyjny sposób w pamięci komputera, tj. w postaci jednej tablicy, jest nieefektywne. Dlatego stosuje się różne metody, których celem jest zoptymalizowanie zapisu takich macierzy. W ogólnym przypadku algorytmy przechowywania macierzy rzadkich nie zapamiętują położenia elementów zerowych. NUMERYCZNE ALGORYTMY PRZECHOWYWANIA MACIERZY RZADKICH a) Schemat diagonalny Rozważmy następującą macierz: ⎡ 1 0 2 3 4 0 0 0 0 ⎢0 ⎢ ⎢ ⎢0 ⎢ A=⎢ ⎢0 ⎢0 ⎢ ⎢ ⎣0 0 3 5 0 0 0 0 227 ⎤ 0 0 0 0 4 0 0 0⎥ ⎥ ⎥ 0 0 0 0⎥ ⎥ 6 7 0 0⎥ ⎥. 7 8 9 10⎥ ⎥ ⎥ 0 9 11 0 ⎦ 0 10 0 12 W przypadku schematu diagonalnego elementy niezerowe przechowywane są w tablicy AN o wymiarze n × β + 1, gdzie n jest wymiarem macierzy wejściowej, natomiast β = max βi jest tzw. półszerokością wstęgi, przy założeniu, że na lewo od głównej i=1,2,...,n przekątnej znajduje się element niezerowy. Otrzymujemy zatem: β1 = 0, β2 = 0, β3 = 1, β4 = 2, β5 = 1, β6 = 1 oraz β7 = 2, stąd β = 2. Zatem macierz AN ma wymiar n × β + 1 = 7 × 2 + 1 = 7 × 3 i przyjmuje postać: ⎡ ⎤ 1 2⎥ ⎥ ⎥ 5⎥ ⎥ 6⎥ ⎥. 8⎥ ⎥ ⎥ 11⎦ 12 ⎢ ⎢ ⎢ ⎢0 ⎢ A=⎢ ⎢4 ⎢0 ⎢ ⎢ ⎣0 0 3 0 7 9 10 0 b) Schemat powłokowy Rozważmy ponownie macierz A z poprzedniego przykładu. W przypadku schematu powłokowego tworzy się dwie jednowymiarowe tablice: AN - w której przechowywane są wierszami wszystkie elementy niezerowe (ze względu na symetrię macierzy w każdym wierszu zapamiętywane są elementy maksymalnie do głównej przekątnej) AN = [1 2 3 5 4 0 6 7 8 9 11 10 0 12] , IA - w której przechowywane są wskaźniki do elementów znajdujących się na głównej przekątnej. Zauważmy, że elementom znajdującym się na głównej przekątnej odpowiadają następujące pozycje w tablicy AN : 1 − 1, 2 − 2, 5 − 4, 6 − 7, 8 − 9, 11 − 11 oraz 12 − 14. Stąd ! " IA = 1 2 4 7 9 11 14 . c) Schemat współrzędnych Rozważmy następującą macierz: ⎡ 1 ⎢0 ⎢ A=⎢ ⎣0 6 0 3 0 7 0 4 5 0 ⎤ 2 0⎥ ⎥ ⎥. 0⎦ 8 228 RADOSŁAW MATUSIK W przypadku schematu współrzędnych tworzy się trzy jednowymiarowe tablice: AN - w której przechowywane są wierszami wszystkie elementy niezerowe ! " AN = 1 2 3 4 5 6 7 8 , IA - w której przechowywane są indeksy wierszy, w których znajdują się elementy niezerowe ! " IN = 1 1 2 2 3 4 4 4 , JA - w której przechowywane są wierszami indeksy kolumn, w których znajdują się elementy niezerowe ! " JA = 1 4 2 3 3 1 2 4 . d) Schemat rozrzedzony wierszowy Rozważmy następującą macierz: ⎡ ⎤ 0 0 1 2 0 0 0 3 0 0 ⎥ A=⎢ ⎣0 0 0 0 0 0 0 0 0 0 ⎦ . 0 0 0 0 0 4 0 5 0 0 W przypadku schematu rozrzedzonego wierszowego tworzy się trzy jednowymiarowe tablice: AN - w której przechowywane są wierszami wszystkie elementy niezerowe ! " AN = 1 2 3 4 5 , JA - w której przechowywane są wierszami wskaźniki do indeksów kolumnowych, pod którymi znajdują się elementy niezerowe. Zauważmy, że niezerowe elementy znajdują się kolejno w kolumnie trzeciej, czwartej, ósmej (w pierwszym wierszu) oraz szóstej i ósmej (w wierszu trzecim). Stąd ! " JA = 3 4 8 6 8 , IA - w której przechowywane są wierszami wskaźniki do danych zawartych w tablicach AN i JA wskazujące pozycje pierwszych elementów niezerowych w każdym wierszu ! " IA = 1 4 4 6 . e) Kompresja Shermana Rozważmy macierz: ⎡ 1 ⎢0 ⎢ ⎢ ⎢0 ⎢ ⎢0 ⎢ ⎢0 A=⎢ ⎢ ⎢0 ⎢ ⎢0 ⎢ ⎢0 ⎢ ⎢ ⎣0 0 ⎤ 0 11 0 0 0 12 0 0 13 2 0 0 0 14 0 0 15 0 ⎥ ⎥ ⎥ 0 3 0 0 0 16 0 0 17⎥ ⎥ 0 0 4 0 0 0 0 18 0 ⎥ ⎥ 0 0 0 5 0 19 0 0 20⎥ ⎥ ⎥. 0 0 0 0 6 0 21 22 23⎥ ⎥ 0 0 0 0 0 7 24 25 0 ⎥ ⎥ 0 0 0 0 0 0 8 26 0 ⎥ ⎥ ⎥ 0 0 0 0 0 0 0 9 27⎦ 0 0 0 0 0 0 0 0 10 NUMERYCZNE ALGORYTMY PRZECHOWYWANIA MACIERZY RZADKICH 229 W przypadku kompresji Shermana tworzy się pięć jednowymiarowych tablic: AD - w której przechowywane są elementy znajdujące się na głównej przekątnej ! " AD = 1 2 3 4 5 6 7 8 9 10 , AN - w której przechowywane są wierszami wszystkie elementy niezerowe macierzy trójkątnej (oczywiście oprócz głównej przekątnej) AN = [11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27] , IA - w której przechowywane są wskaźniki do danych zawartych w tablicach AD i AN wskazujące pozycje pierwszych elementów niezerowych w każdym wierszu (jak w przypadku schematu rozrzedzonego wierszowego). Zauważmy, że wypełnianie tablicy rozpoczynamy od pierwszego elementu, znajdującego się na głównej przekątnej. W pierwszym wierszu mamy trzy elementy niezerowe, stąd 1 + 3 = 4. W drugim wierszu mamy dwa elementy niezerowe, stąd 4 + 2 = 6. W trzecim wierszu elementów niezerowych jest 2 stąd 6 + 2 = 8. Łatwo widać, że tablica IA zawiera następujące wskaźniki: IA = [1 4 6 8 9 11 14 16 17 18 18] , JA - w której przechowywane są indeksy kolumnowe do elementów niezerowych wykorzystujące powtarzalność struktury. Zauważmy, że w pierwszym wierszu elementy niezerowe znajdują się w kolumnach 3, 7 i 10, w drugim wierszu w kolumnach 6 i 9. Wiersze trzeci, czwarty i piąty pomijamy ze względu na powtarzalność struktury. W wierszu szóstym elementy niezerowe znajdują się w kolumnach 8, 9 i 10, w wierszu siódmym w 8 i 9. Wiersz ósmy pomijamy także ze względu na powtarzalność. W wierszu dziewiątym mamy jeden element niezerowy znajdujący się w kolumnie 10. Stąd tablica JA przyjmuje postać: JA = [3 7 10 6 9 8 9 10 8 9 10] , IJ - w której przechowywane są wskaźniki do elementów znajdujących się w tablicy JA umożliwiające wybór indeksów kolumnowych. ! " IJ = 1 4 2 5 2 6 9 10 11 . f) Schemat Knutha Rozważmy macierz: ⎡ ⎤ 0 1 0 0 3 0 4⎥ ⎥ ⎥. 0 0 0⎦ 0 6 0 7 W przypadku schematu Knutha tworzy się siedem jednowymiarowych tablic: AN - w której wierszami przechowywane są kolejne niezerowe elementy ⎢2 A=⎢ ⎢ ⎣5 ! " AN = 1 2 3 4 5 6 7 , I - w której przechowywane są indeksy wierszowe do elementów niezerowych. Zauważmy, że w pierwszym wierszu znajduje się jeden element niezerowy, w drugim trzy, w trzecim jeden i w wierszu czwartym dwa elementy. Stąd: ! " I= 1 2 2 2 3 4 4 , 230 RADOSŁAW MATUSIK J - w której przechowywane są (wierszami) indeksy kolumnowe do elementów niezerowych. Zauważmy, że w pierwszym wierszu element niezerowy znajduje się w drugiej kolumnie, w drugim wierszu elementy niezerowe znajdują się w kolumnie pierwszej, drugiej i czwartej, w trzecim wierszu w kolumnie pierwszej i w wierszu czwartym w kolumnie drugiej i czwartej. Stąd: ! " J = 2 1 24 1 2 4 . N R - w której przechowywane są informacje o następnym elemencie wiersza. Zero oznacza, że w danym wierszu oprócz danego elementu niezerowego nie ma innych elementów niezerowych ! " NR = 0 3 4 0 0 7 0 , N C - w której przechowywane są informacje o następnym elemencie kolumny. Znaczenie ”0” jest analogiczne, jak w opisie tablicy N R ! " NC = 3 5 6 7 0 0 0 , JR - w której przechowywane są informacje o pierwszym niezerowym elemencie danego wiersza. Zauważmy, że licząc od pierwszego elementu niezerowego, pierwsze elementy niezerowe w danym wierszu zaczynają się od pozycji 2 (jeden w wierszu pierwszym plus jeden w drugim), 5 (dwa wcześniejsze plus trzy w trzecim wierszu) i 6 (pięć wcześniejszych plus jeden w wierszu czwartym). Stąd: ! " JR = 1 2 5 6 , JC - w której przechowywane są informacje o pierwszym niezerowym elemencie danej kolumny. Zauważmy, że pierwsze elementy niezerowe w poszczególnych kolumnach znajdują się na pozycjach 2 (w pierwszej kolumnie), 1 (w drugiej kolumnie), 0 (w kolumnie trzeciej nie ma elementów niezerowych) oraz 2 (w kolumnie czwartej). Stąd: ! " JC = 2 1 0 2 . Niektóre z zaprezentowanych w niniejszym artykule algorytmów przechowywania macierzy rzadkich można zmodyfikować, np. pewną modyfikacją schematu Knutha jest powszechnie znany algorytm KRM (Knutha-Rheinboldta-Mesztenyi). Różnica polega jedynie na sposobie zapamiętywania indeksów w tablicach N R oraz N C. Również po drobnych modyfikacjach rozrzedzony schemat wierszowy można przedstawić jako inny algorytm - rozrzedzony schemat kolumnowy. Jednak różnice między tymi algorytmami są na tyle niewielkie, że nie wymagają osobnego przedstawienia w niniejszym artykule. 4. Podsumowanie W poprzednim rozdziale zaprezentowane zostały wybrane numeryczne algorytmy przechowywania macierzy rzadkich. Wybór algorytmu uzależniony jest od postaci samej macierzy. W przypadku macierzy symetrycznej, w której elementy niezerowe skupione są wokół głównej przekątnej należy wykorzystać schemat diagonalny, bądź powłokowy. Aby przechować elementy macierzy trójkątnej (dolnej, bądź górnej) należy zastosować kompresję Shermana. Bardziej uniwersalne są schematy: współrzędnych, rozrzedzony wierszowy oraz Knutha, które pozwalają na zapamiętanie macierzy rzadkich o dowolnym NUMERYCZNE ALGORYTMY PRZECHOWYWANIA MACIERZY RZADKICH 231 umiejscowieniu elementów niezerowych. Schemat Knutha zalecany jest w przypadku, gdy elementy danej macierzy wygenerowane zostały przez jakiś algorytm, tj. w przypadku, gdy nie jest znany ani wymiar macierzy, ani ilość elementów niezerowych. Rozważmy rozrzedzony schemat wierszowy i macierz z podpunktu d): ⎡ ⎤ 0 0 1 2 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0⎥ ⎦. 0 0 0 0 0 4 0 5 0 0 ⎢ A = ⎣0 Zakładamy, że na zapamiętanie elementu macierzy potrzeba 8 bajtów, natomiast na zapamiętanie wskaźnika 4 bajty. Do zapamiętania wszystkich elementów macierzy A potrzeba 240 bajtów (30 elementów, każdy po 8 bajtów). Natomiast w rozrzedzonym schemacie wierszowym musimy zapamiętać: jedną tablicę zawierającą 5 elementów niezerowych (każdy po 8 bajtów), jedną tablicę zawierającą 5 wskaźników (każdy po 4 bajty) oraz jedną tablicę zawierającą 4 wskaźniki (także każdy po 4 bajty). Stąd, na zapamiętanie odpowiednich elementów i wskaźników w przypadku rozrzedzonego schematu wierszowego potrzebujemy 5 · 8B + 5 · 4B + 4 · 4B = 76B. Widać zatem, że zastosowanie specjalnego algorytmu do przechowywania elementów macierzy rzadkiej pozwoliło zaoszczędzić 164 bajty w pamięci komputera. Warto się zastanowić, czy w każdym przypadku do zapamiętania elementów macierzy rzadkiej trzeba stosować wybrany algorytm, czy może w niektórych przypadkach taką macierz należy przechować w zwykłej tablicy, zapamiętując wszystkie jej elementy (także zerowe). Rozważmy ponownie schemat Knutha i przykładową macierz (z podpunktu f): ⎡ 0 1 3 0 0 6 ⎢2 ⎢ A=⎢ ⎣5 0 0 0 0 ⎤ 0 4⎥ ⎥ ⎥. 0⎦ 7 Do zapamiętania wszystkich elementów macierzy A potrzeba 128 bajtów (16 elementów, każdy po 8 bajtów). W schemacie Knutha potrzebujemy zapamiętać: 7 elementów niezerowych, każdy po 8 bajtów, cztery siedmioelementowe tablice zawierające wskaźniki (czyli na zapamiętanie jednego wskaźnika potrzebujemy 4 bajty) oraz dwie czteroelementowe tablice (również na zapamiętanie jednego wskaźnika potrzebujemy 4 bajty). Stąd, aby przechować potrzebne elementy oraz odpowiednie wskaźniki w schemacie Knutha potrzebujemy 7 · 8B + 4 · 7 · 4B + 2 · 4 · 4B = 200B. Widać, że w tym przypadku zastosowanie schematu Knutha nie jest korzystne, gdyż na zapamiętanie elementów niezerowych i odpowiednich wskaźników potrzebujemy o 72 bajty więcej, niż w przypadku zapamiętania wszystkich elementów macierzy A (także zerowych). Jest to spowodowane małym wymiarem macierzy oraz niewielką liczbą elementów zerowych. Podsumowując, w przypadku macierzy o bardzo dużych wymiarach, zawierających jednocześnie bardzo dużą liczbę elementów zerowych, należy stosować wybrany algorytm przechowywania macierzy rzadkiej (odpowiednio do typu macierzy). Pozwoli on na zaoszczędzenie pamięci, a w przypadku wykonywania na macierzy dodatkowych operacji, ograniczy ich liczbę. Jednak, jak pokazuje powyższy przykład, nie zawsze korzystne jest stosowanie specjalnego algorytmu do zapamiętania elementów macierzy rzadkiej. 232 RADOSŁAW MATUSIK Bibliografia [1] Bjoerck, Dahlquist G., Metody numeryczne, Państwowe Wydawnictwo Naukowe, Warszawa 1987. [2] Fortuna Z., Macukow B., Wąsowski J., Metody numeryczne, Wydawnictwa Naukowo-Techniczne, Warszawa 1982. [3] Śmietański M., Wykłady z algorytmów numerycznych - skrypt, 2006. NUMERICAL ALGORITHMS STORING SPARSE MATRICES RADOSŁAW MATUSIK Abstract. The subject of article concerns the various types of sparse matrices and selected numerical algorithms keeping these matrices. Each of these algorithms was discussed on example. This article pointed out areas of adoptions each of schemas and profits come off keeping elements of sparse matrices by means of these algorithms.