- Kot prej poimenujmo levo matriko ki jo množimo:
A
, desno:B
, trenutno (self):C
in po potrebi delovnoD
- Naj za te matrike velja:
A
jeN × M
matrika,B
jeM × K
matrika, iz tega torej sledi da staC
inD
N × K
matriki - Nadalje naj velja
n := N // 2
,m := M // 2
,k := K // 2
inL = max{N, M, K}
terl = L // 2 = max{n, m, k}
- Seštevanje in množenje skalarjev ima konstantno časovno in prostorsko zahtevnost
- Dostop do posamezne matrike/podmatrike ima konstantno časovno zahtevnosz (če ne pride do kopiranja), v nasprotnem primeru je njegova časovna zahtevnost
O(NK)
, če dostopamo do matrikeN × M
- Prepisovanje posamezne matrike/podmatrike velikosti
N × M
ima časovno zahtevnostO(NM)
in porabiO(1)
dodatnega prostora. - Seštevanje/odštevanje dveh
N × M
matrik ima časovno zahtevnostO(NM)
, ter ob ustvarjanju nove matrike porabiO(NM)
dodatnega spomina - Prištevanje/odštevanje (z operatorjema
+=
in-=
) dvehN × M
matrik, ki se v nadmatriki ne prekrivata ima časovno zahtevnostO(NM)
, ter porabiO(1)
dodatnega spomina (po popravku, matrik, ki se prekrivaju ne bomo prištevali in se zato z njihovo prostorsko zahtevnostjo ne bomo ukvarjali) - Pomnoževanje matrike s skalarjem (
A *= k
, za skalarnik
), kjer jeA
N × M
matrika ima časovno zahtevnostO(NM)
in porabiO(1)
dodatnega prostora.
-
Naivno množenje
Metoda
dot_product
seštejea
produktov (opravi2*a
osnovnih operacij), torej je njena časovna zahtevnostO(a)
, ker jo uporabljamo za skalarnih produt dveh vektorjev je to kar enako dolžini enega izmed njih, v primeru naše matrike pa kar številu stolpcev vA
in številu vrstic vB
torejO(M)
.Metoda
multiply
vsebuje dve gnezdenifor
zanki, ki se izvedeta do konca, zunanja se izvedeN
-krat, notranjaK
krat, v notranji padot_product
na vektorju dolžineM
. Torej je časovna zahtevnost metodemultiply
v razreduSlowMatrix
O(NMK)
. -
Strassenovo množenje
Najprej se osredotočimo na primer, ko so vse dimenzije tako
A
inB
matrike deljive z2
, v baznem primeru v najslabšem primeru opravimoN × M × K
operacij, kjer je vsaj ena izmed konstant enaka1
, kar nas v tem primeru še vedno pusti v časovni zahtevnostiO(L^2)
.Za analizo časovne zahtevnosti bomo uporabili krovni izrek (Master Theorem), kjer se bomo pri končni analizi, zaradi omejitev le-tega na zgolj eno spremenljivko v rekurzivni zvezi, omejili na največjo izmed dimenzij matrik (
L
).Priprava primernih podmatrik zahteva konstantno časovno zahtevnost, nato sledi 8 seštevanj matrik (4 ×
n × m
in 4 ×m × k
), kar lahko skupno omejimo zO(l²) = O(L²)
.Sledi 7 rekurzivnih množenj na matrikah pol manjših velikosti (
n × m
×m × k
matrike), kar lahko na podlagi krovnega izreka zapišemo kot7T(L/2)
. Potem pa je potrebno dobljene produkte še sestaviti nazaj v skupno matriko, kar vsebuje 7 seštevanjn × k
matrik in kriiranje treh novihn × k
matrik, kar lahko spet omejimo zO(l^2) = O(L^2)
Ob prespostavki, da imamo matrike sodih dimenzij tako velja oblika krovnega izreka:
T(L) = 7×T(L/2) + O(L^2)
-
Strassenovo množenje z manjšo porabo spomina
V razredu
CheapMatrix
je implementirana varainta Strassenovega algoritma, ki porabi manj spomina (dodatnega spomina porabi zgolj dovolj za vzdrževanje sklada ob rekurziji).Postopamo podobno kot pri standardni implementaciji Strassenovega algoritma, la da moramo biti pri vseh operacija pazljivi in namesto ustvarjanja novih matrik uporabljati spomin, ki nam je na voljo (leva(
A
) in desna(B
) matrika, ki ju moramo po končanem opravljanju vrniti nazaj na prvotno stanje, matrika rezultata(C
), ki mora na koncu vsebovati rezultat in delovna matrika(D
), ki je enakih dimenzij kotC
, o njej pa nimamo nobenih pogojev) in zato vse operacije izvajamo na mestu.Tako za računanje produktov vsto bločnih matrik, kjer smo lahko pri
FastMatrix
uporabljali normalno seštevanje najprej popravimo osnovno matriko, opravimo množenje rekurzivno, kjer mu za delovno matriko podamo primerno bločno matriko vD
nato pa prvotno matriko popravimo nazaj (odštejemo/prištejemo kar smo prej prišteli/odšteli). V svoji implementaciji sem za delovno matriko v rekurziji vseskozi uporabljalD22
saj sem tako imel proste roke za shranjevanje ostalih vmesnih matrik na ostala mesta v delovni matriki.Ko smo izračunali vseh 7 rekurzivnih množenj, ki si jih sranjujemo tako v
C
kot vD
je potrebno vse te bločne matrike še zgolj sestaviti vC
na pravilen način.Zadnji del je spet podoben delu v
FastMatrix
, la da je potrebno zopet paziti da na ustvarjamo novih matrik. Tako zopet sledimo isti ideji kot prej, na željeni matriki množimo levo in desno in to matriko. Za razliko od prej si je potrebno nekajkrat pred množenjem pospraviti ciljno matriko, čemur bi se sicer lahko izognili, če bi ob vsakem doprištevanju prepisali pravi del ciljne matrike, a se mi zdi da je zaradi jasnosti, za katero bločno matriko rezultata gre vredno žrtvovati nekaj dodatnih operacij.Ob obravnavanju robnih primerv zaradi lažjega zapisa/razumevanja do dvakrat usvarimo novo
1 × 1
matriko, da lahko normalno kličemo množenje.Zgolj zaradi preprostosti večino algoritma implementiramo v privatni metodi
_multiply
, kjer lahko eksplicitno zahtevamo podajanje delovne matrike.