Prodotto di matrici - algoritmo iterativo e ricorsivo a blocchi
In questa pagina 5
Si lavora con matrici quadrate a elementi complessi. Il caso rettangolare si tratta allo stesso modo; il caso quadrato non è una vera limitazione. Per le dimostrazioni sulla struttura a blocchi si assume con , così ogni matrice si divide in quattro quadranti fino alle matrici .
Modello di costo. Costo unitario per ogni operazione aritmetica tra due scalari (somma, sottrazione, prodotto, divisione). Gli assegnamenti, le copie e il calcolo degli indici costano . La complessità si esprime in funzione di (dimensione della matrice) oppure di (numero di elementi): sono la stessa quantità descritta in due modi, quindi va sempre dichiarata la metrica di taglia. Un algoritmo con è lineare nella taglia .
Notazione: le matrici sono lettere maiuscole, i loro elementi la lettera minuscola con due indici, . Le precondizioni sull'input (qui: le matrici hanno la stessa dimensione) si chiamano vincoli statici. Gli algoritmi "basati sulla definizione" calcolano ogni elemento del risultato con la formula che lo definisce. Per la definizione di prodotto righe per colonne vedi Operazioni tra matriciLe matrici m×n formano uno spazio vettoriale (somma e prodotto per scalare elemento per elemento); il prodotto righe per colonne corrisponde alla composizione di funzioni lineari, è associativo ma non commutativo; la trasposta scambia righe e colonne e (AB)^T = B^T A^T.Operazioni tra matrici →.
Somma e sottrazione
è definita da .
SUM(A, B)
n <- rows(A)
for i <- 1 to n do
for j <- 1 to n do
c[i][j] <- a[i][j] + b[i][j]
return COgni for si traduce in una sommatoria con la stessa variabile, il cui termine generico è il lavoro di una iterazione. for annidati diventano sommatorie annidate:
Qualunque algoritmo di somma deve eseguire, al caso peggiore, somme distinte (ogni dipende da una coppia diversa di input, e non c'è modo di ottenerlo da altre somme): quindi non è migliorabile e l'algoritmo basato sulla definizione è ottimo. Lo stesso vale per la sottrazione SUB.
Prodotto righe per colonne, versione iterativa
è definita da
MUL(A, B)
n <- rows(A)
for i <- 1 to n do
for j <- 1 to n do
c[i][j] <- a[i][1] * b[1][j]
for k <- 2 to n do
c[i][j] <- c[i][j] + a[i][k] * b[k][j]
return CSi inizializza con il primo prodotto (e non con ) perché sommare a sarebbe un'operazione in più. Conto le operazioni sostituendo i for con sommatorie. Per ogni coppia il lavoro è: prodotto iniziale, poi per ciascun un prodotto e una somma, cioè operazioni:
La sommatoria interna ha termini (da a ), da cui il fattore . In totale i prodotti sono e le somme (un prodotto per ogni terna , e per ogni coppia una somma in meno dei prodotti). Quindi : non è lineare nel numero di elementi.
Esempio ().
Le operazioni sono prodotti e somme, cioè , come prevede la formula.
Prodotto ricorsivo a blocchi
Si cerca un algoritmo divide & conquer (vedi Divide and conquer - proprietà di sottostruttura e albero delle chiamateUn algoritmo divide and conquer nasce da una proprietà di sottostruttura (funzioni Divide e Conquer, base n0) con sottoistanze di taglia strettamente minore; l'albero delle chiamate ne descrive l'esecuzione, e il D&C non ha memoria (ripete le sottoistanze uguali). Esempio completo: MAX di un array, con correttezza per induzione, ricorrenza e conteggio esatto n-1 tramite il teorema sugli alberi binari pieni.Divide and conquer - proprietà di sottostruttura e albero delle chiamate →). Per si scrivono e a quadranti:
Proprietà di sottostruttura (proprietà frattale)
Teorema (proprietà frattale del prodotto). I quadranti di si ottengono con la stessa formula del prodotto righe per colonne di matrici , dove al posto degli scalari ci sono i quadranti: per
Dimostrazione per (gli altri tre quadranti sono identici). Con si indica l'elemento di posto della matrice ; per vale perché occupa le prime righe e colonne.
- Si parte dalla definizione e si spezza la somma su in due metà, la prima per e la seconda per :
- Nella seconda somma si pone con (traslazione dell'indice, non cambia i termini):
- Si riconoscono i quadranti. Nella prima somma e : e . Nella seconda e : e (riga del blocco in basso, colonna del blocco a sinistra):
Quindi .
La proprietà dice che un prodotto si riduce a 8 prodotti e a 4 somme di matrici .
Algoritmo RMUL
RMUL(A, B)
n <- rows(A)
if n = 1 then return [a11 * b11] { matrice 1x1 }
* A = [A11 A12; A21 A22], B = [B11 B12; B21 B22] *
for i <- 1 to 2 do
for j <- 1 to 2 do
Cij <- SUM(RMUL(Ai1, B1j), RMUL(Ai2, B2j))
return CLa riga fra asterischi è un metacomando: non è un'istruzione. In un linguaggio reale la divisione in quadranti si realizza passando alla funzione ricorsiva i parametri che indicano la sottomatrice (riga e colonna di partenza e dimensione), senza copiare niente. Costi: nel caso base c'è un prodotto; nel caso ricorsivo, 8 chiamate su taglia più 4 somme di matrici , ciascuna di costo per , cioè :
Analisi con il Master Theorem
Qui , , e la funzione di soglia è . Poiché con , siamo nel caso 1 del Master Theorem - enunciato, casi e applicazioniMaster Theorem per T(n) = aT(n/b) + w(n): si confronta w(n) con la funzione soglia n^(log_b a). Caso 1 (w minore di una potenza polinomiale): Theta(n^(log_b a)); caso 2 (w della stessa taglia): Theta(n^(log_b a) log n); caso 3 (w maggiore e regolare): Theta(w(n)). Prova completa dai livelli dell'albero, versione estesa (parti intere, n0 qualsiasi), esempi, e l'uso scorretto quando nessun caso si applica (w = n log n).Master Theorem - enunciato, casi e applicazioni →: . A dominare è il lavoro alle foglie ( foglie, una per terna ): l'arietà dell'albero fa crescere troppo in fretta il numero di foglie, mentre il lavoro nei nodi interni è piccolo. Non c'è nessun miglioramento rispetto all'iterativo.
Soluzione esatta: stesso conteggio dell'iterativo
Proposizione. per ogni : RMUL esegue esattamente le stesse operazioni di MUL, solo in ordine diverso.
Dimostrazione per induzione su (vedi Dimostrazioni, induzione e invariantiTecniche di dimostrazione (esempio, controesempio, assurdo), induzione con casi base multipli, invarianti di ciclo (inizializzazione, conservazione, uso alla fine), schema generale per provare la correttezza; esempi svolti su arrayMax, sequenza di bit e numeri di Fibonacci.Dimostrazioni, induzione e invarianti →).
- Base (): e .
- Ipotesi: .
- Passo:
La stessa formula si ottiene srotolando l'albero (formula generale: vedi Albero della ricorrenza e formula generale per le ricorrenzeL'albero della ricorrenza ha la forma dell'albero delle chiamate dell'istanza peggiore e T(n) è la somma dei lavori dei nodi. Per la famiglia T(n) = s(n)T(f(n)) + w(n) con base T0 per n <= n0, contando i nodi per livello con le iterate di f e il livello f*(n,n0) si ottiene la formula chiusa T(n) = somma_l [prodotto_j s(f^(j)(n))] w(f^(l)(n)) + [prodotto s] T0, verificata per induzione e applicata a esempi (merge sort, Hanoi, ricorrenze con radice).Albero della ricorrenza e formula generale per le ricorrenze →). Al livello ci sono nodi di taglia , ciascuno con lavoro ; i nodi interni sono i livelli e le foglie valgono ciascuna:
La somma geometrica .
Esempio (). Il livello ha una chiamata con somme (cioè operazioni); il livello ha chiamate di taglia , ognuna con somme di matrici ( operazioni), cioè ; il livello ha foglie, prodotti. Totale .
Verifica con un programma
def mul(A, B): # iterativo, conta le operazioni
n, ops = len(A), 0
C = [[0]*n for _ in range(n)]
for i in range(n):
for j in range(n):
c = A[i][0]*B[0][j]; ops += 1
for k in range(1, n):
c += A[i][k]*B[k][j]; ops += 2
C[i][j] = c
return C, ops
# n = 1, 2, 4, ..., 64: ops == 2*n**3 - n**2Per il conteggio dà operazioni, e la versione ricorsiva dà lo stesso valore.
Il Master Theorem non basta a battere il cubo: serve ridurre il numero di chiamate ricorsive da a meno, accettando di aumentare il lavoro dei nodi interni. È l'idea di Algoritmo di Strassen e ibridazioneStrassen calcola il prodotto n x n con 7 chiamate ricorsive invece di 8, usando 10 somme/sottrazioni nel divide e 8 nel conquer: T(n) = 7T(n/2) + (9/2)n^2, Theta(n^(log2 7)) con soluzione esatta 7n^(log2 7) - 6n^2; la costante 7 è più che tripla di quella dell'iterativo (2) e Strassen batte l'iterativo solo da n = 655 (per le potenze di 2, da 1024). L'ibridazione cambia il caso base (iterativo per n <= n0): costo n^(log2 7)(2n0+5)/n0^(log2 7 - 2) - 6n^2, minimo per n0 = 8 con costante ~3.92.Algoritmo di Strassen e ibridazione →.
Esercizi collegati
- Esercizio - prodotto di matrici complesse, ricorrenza e induzione parametrica (compito del 8 luglio 2024, es. 1): conteggio esatto in operazioni reali di questo stesso algoritmo.
- Esercizio - domande di prima parte su moltiplicazione di matrici (simulazioni d'esame): la domanda sul numero di operazioni per .
Domande d'esame
- Quante operazioni tra scalari esegue il prodotto righe per colonne ricorsivo basato sulla definizione per ? Risposta: ( prodotti e somme, perché ogni prodotto si somma nel tranne i primi). Traccia: l'algoritmo ricorsivo fa le stesse operazioni dell'iterativo.
- Perché il prodotto ricorsivo a blocchi con chiamate non migliora l'iterativo? Traccia: , soglia , lavoro polinomialmente minore, caso 1 del Master Theorem: dominano le foglie, ; per di più il conteggio esatto è identico, .
- Perché la somma di matrici è ottima? Traccia: ogni richiede la propria somma, quindi servono operazioni e è anche un limite inferiore.
Versione ripasso
- Modello di costo: costo per ogni operazione tra scalari; assegnamenti, copie e indici gratis. Taglia (dimensione) oppure (elementi): dichiarare sempre quale. Un'istanza è ben formata se rispetta i vincoli statici (stessa dimensione). Vedi Operazioni tra matriciLe matrici m×n formano uno spazio vettoriale (somma e prodotto per scalare elemento per elemento); il prodotto righe per colonne corrisponde alla composizione di funzioni lineari, è associativo ma non commutativo; la trasposta scambia righe e colonne e (AB)^T = B^T A^T.Operazioni tra matrici →.
- Somma: , . È ottima: servono somme distinte, quindi è anche un limite inferiore. Ogni
fordiventa una sommatoria con lo stesso indice. - Prodotto iterativo: ; si parte da (niente somma con ) e si somma per ( operazioni ciascuno): prodotti e somme: . Esempio : operazioni, .
- Proprietà frattale (): sui quadranti. Dimostrazione per : si spezza in e ; nella seconda si pone ; si riconoscono e , cioè .
- RMUL: restituisce ; altrimenti
SUM(RMUL,RMUL): chiamate e somme da , cioè lavoro . La divisione in quadranti è un metacomando (in pratica si passano indici). - Master Theorem: , , soglia , (): caso 1, (Master Theorem - enunciato, casi e applicazioniMaster Theorem per T(n) = aT(n/b) + w(n): si confronta w(n) con la funzione soglia n^(log_b a). Caso 1 (w minore di una potenza polinomiale): Theta(n^(log_b a)); caso 2 (w della stessa taglia): Theta(n^(log_b a) log n); caso 3 (w maggiore e regolare): Theta(w(n)). Prova completa dai livelli dell'albero, versione estesa (parti intere, n0 qualsiasi), esempi, e l'uso scorretto quando nessun caso si applica (w = n log n).Master Theorem - enunciato, casi e applicazioni →). Dominano le foglie (): l'arietà è troppo alta.
- Soluzione esatta :
- induzione: base ; passo ;
- srotolamento (Albero della ricorrenza e formula generale per le ricorrenzeL'albero della ricorrenza ha la forma dell'albero delle chiamate dell'istanza peggiore e T(n) è la somma dei lavori dei nodi. Per la famiglia T(n) = s(n)T(f(n)) + w(n) con base T0 per n <= n0, contando i nodi per livello con le iterate di f e il livello f*(n,n0) si ottiene la formula chiusa T(n) = somma_l [prodotto_j s(f^(j)(n))] w(f^(l)(n)) + [prodotto s] T0, verificata per induzione e applicata a esempi (merge sort, Hanoi, ricorrenze con radice).Albero della ricorrenza e formula generale per le ricorrenze →): livello ha nodi con lavoro , quindi .
- Conclusione: RMUL fa esattamente le stesse operazioni di MUL, in ordine diverso. Esempio : ; : .
- Morale: per battere bisogna ridurre le chiamate sotto (Algoritmo di Strassen e ibridazioneStrassen calcola il prodotto n x n con 7 chiamate ricorsive invece di 8, usando 10 somme/sottrazioni nel divide e 8 nel conquer: T(n) = 7T(n/2) + (9/2)n^2, Theta(n^(log2 7)) con soluzione esatta 7n^(log2 7) - 6n^2; la costante 7 è più che tripla di quella dell'iterativo (2) e Strassen batte l'iterativo solo da n = 655 (per le potenze di 2, da 1024). L'ibridazione cambia il caso base (iterativo per n <= n0): costo n^(log2 7)(2n0+5)/n0^(log2 7 - 2) - 6n^2, minimo per n0 = 8 con costante ~3.92.Algoritmo di Strassen e ibridazione →).
- Errori tipici: contare somme (sono : il primo prodotto di ogni non si somma); contare le copie dei quadranti; confondere taglia con ; credere che il ricorsivo a blocchi sia più veloce dell'iterativo.
Esercizi su questo argomento
- Esercizio - domande di prima parte su moltiplicazione di matrici (simulazioni d'esame)
- Esercizio - matrici ARB, prodotto con tre chiamate ricorsive (compito del 19 giugno 2024, es. 1)
- Esercizio - matrici RCA, somma e prodotto matrice-vettore (compito del 18 giugno 2026, es. 1)
- Esercizio - matrici SIR, somma, prodotto e inversa (compito del 29 gennaio 2026, es. 1)
- Esercizio - prodotto di matrici complesse, ricorrenza e induzione parametrica (compito del 8 luglio 2024, es. 1)