Matrix Chain Multiplication - parentesizzazione ottima
In questa pagina 7
Moltiplicare una catena di matrici rettangolari può costare molto o poco a seconda dell'ordine in cui si eseguono i prodotti, anche se il risultato è sempre lo stesso: il prodotto è associativo (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 →). Scegliere l'ordine migliore è un problema di ottimizzazione risolto con la programmazione dinamica (Programmazione dinamica - Fibonacci, memoizzazione e schema del paradigmala programmazione dinamica (DP) evolve il divide and conquer: invece di risolvere di nuovo le stesse sottoistanze, le salva in una tabella. Fibonacci ricorsivo costa F_n - 1 addizioni (esponenziale), quello con tabella n - 1. Tre condizioni: sottostruttura, sottoistanze ripetute, spazio dei sottoproblemi ristretto. Bottom-up o memoizzazione (ricorsione + tabella globale). Per i problemi di ottimizzazione lo schema è: sottostruttura ottima, ricorrenza sui costi, informazione addizionale, calcolo della tabella, ricostruzione.Programmazione dinamica - Fibonacci, memoizzazione e schema del paradigma →).
Il problema
Due matrici e sono compatibili (il numero di colonne di è il numero di righe di ) e il prodotto righe per colonne è , con .
RECT_MAT(A, B)
p <- rows(A); q <- columns(A); r <- columns(B)
for i <- 1 to p do
for j <- 1 to r do
c_ij <- a_i1 * b_1j
for k <- 2 to q do c_ij <- c_ij + a_ik * b_kj
return CEsegue esattamente prodotti tra scalari e somme; come nelle dispense si contano solo i prodotti (vedi anche Prodotto di matrici - algoritmo iterativo e ricorsivo a blocchimodello di costo sulle matrici n x n (operazioni tra scalari, taglia n oppure N = n^2); somma in n^2 operazioni (ottima); prodotto righe per colonne iterativo con costo esatto 2n^3 - n^2; proprietà frattale del prodotto a blocchi dimostrata sugli indici; algoritmo ricorsivo RMUL con T(n) = 8T(n/2) + n^2, Theta(n^3) e soluzione esatta 2n^3 - n^2 per induzione e per srotolamento: fa le stesse operazioni dell'iterativo.Prodotto di matrici - algoritmo iterativo e ricorsivo a blocchi →). Il prodotto è associativo ma non commutativo.
Si vuole il prodotto di una catena di matrici rettangolari compatibili (colonne di = righe di ). A causa della compatibilità bastano numeri: il vettore , con .
Parentesizzazioni
Ogni ordine di esecuzione è una parentesizzazione completa, e si rappresenta con un albero binario pieno: le foglie sono le matrici, ogni nodo interno è un prodotto (la coppia di parentesi più esterna della sua sottoespressione). Un albero binario pieno con foglie ha nodi interni, quindi ogni parentesizzazione esegue esattamente prodotti tra matrici. Per ce ne sono due, e ; per ce ne sono cinque:
(((A1 A2) A3) A4) ((A1 A2) (A3 A4)) (A1 (A2 (A3 A4))) ((A1 (A2 A3)) A4) (A1 ((A2 A3) A4))Il numero di prodotti tra scalari invece dipende dalla parentesizzazione, perché dipende dalle dimensioni dei fattori.
Esempio. , , , cioè .
- : costa e dà una ; poi costa . Totale .
- : costa e dà una ; poi costa . Totale .
Già con tre matrici c'è un fattore .
Definizione (MCM). Dato , determinare la parentesizzazione della catena associata che richiede il minimo numero di prodotti tra scalari.
L'istanza è solo il vettore : gli elementi delle matrici non servono.
Quante parentesizzazioni?
Sia il numero di parentesizzazioni di matrici. L'ultimo prodotto eseguito divide la catena in e , con ; le due sottocatene si parentesizzano indipendentemente, in e modi:
I primi valori sono (numeri di Catalan). La ricorrenza è difficile da risolvere con gli strumenti noti, ma si minora facilmente per induzione.
Teorema. per ogni .
Dimostrazione. Base: e . Passo: sia e valga la tesi per i valori minori di . Allora
Nel secondo passaggio la sommatoria è stata minorata con i soli termini e , che sono distinti perché (valgono entrambi ).
Quindi le parentesizzazioni sono esponenziali e la ricerca esaustiva non è praticabile. In realtà (formula dei numeri di Catalan, che si prova per induzione, controllata con Python fino a ) e, per l'approssimazione di Stirling, , quadraticamente più grande del limite inferiore dimostrato.
Grafico interattivo: Parentesizzazioni di una catena di n matrici: P(n) esatto (numeri di Catalan, 14 per n = 5, 4862 per n = 10) e approssimazione 4^(n-1) / (√π (n-1)^(3/2)), contro il limite inferiore 2^(n-1) / n
Proprietà di sottostruttura ottima
Una parentesizzazione di contiene parentesizzazioni di sottocatene, quindi lo spazio dei sottoproblemi naturale è l'insieme delle sottocatene non vuote con : sono . Per si indica con il prodotto della sottocatena, con l'albero di una sua parentesizzazione qualsiasi, con il costo (numero di prodotti tra scalari) e con un albero di costo minimo. Si cerca .
Casi base: per una sottocatena di una sola matrice l'unico albero è un nodo, di costo .
Teorema (sottostruttura ottima). Sia e sia un albero ottimo per . Allora esiste con tale che la radice ha come sottoalberi due alberi ottimi e , e dove è un indice che minimizza su .
Dimostrazione. L'albero ha almeno due foglie, quindi la radice ha due sottoalberi e , per un certo : l'ultimo prodotto è , di dimensioni per , che costa . Il costo totale è quindi .
I sottoalberi sono ottimi. Per assurdo, se uno dei due (per esempio ) non avesse costo minimo, lo si sostituirebbe con un albero ottimo della stessa sottocatena: la parentesizzazione risultante è ancora ammissibile (la sottoespressione ha le stesse dimensioni) e costa strettamente meno di . Contraddizione. È il cut-and-paste: si taglia una componente, se ne incolla una migliore.
Scelta di . Per ogni con , comporre e con una nuova radice dà una parentesizzazione ammissibile di costo . Se non minimizzasse questa quantità, la composizione con il minimo batterebbe .
Il valore è l'unica informazione necessaria a ricostruire l'albero ottimo di dagli alberi ottimi delle sottocatene.
Ricorrenza sui costi
Con :
La soluzione è .
L'algoritmo divide and conquer
La ricorrenza si traduce direttamente in codice ricorsivo (chiamata R_MCM(p, 1, n)):
R_MCM(p, i, j)
if i = j then return 0
m <- +infinity
for k <- i to j-1 do
q <- R_MCM(p, i, k) + R_MCM(p, k+1, j) + p_{i-1} * p_k * p_j
if q < m then m <- q
return mContando solo i prodotti tra dimensioni (due per ogni : e il risultato per ), il costo di una catena di matrici è
Minorazione esponenziale. per induzione. Base: e . Per , tenendo solo il termine della sommatoria:
dove si è usato nel primo addendo. Quindi .
Valore esatto: . Per si sottraggono le ricorrenze di e :
(La relazione vale anche per : .) Aggiungendo a entrambi i membri, , cioè . Verificato contando le moltiplicazioni di R_MCM per . Per sono e per sono .
Perché? Per le sottoistanze ripetute. Per le chiamate a R_MCM(p, i, j) sono in tutto (cioè ), e ogni sottocatena compare più volte:
| sottocatena | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| chiamate | 4 | 5 | 5 | 4 | 2 | 2 | 2 | 1 | 1 | 1 |
L'approccio di programmazione dinamica
Si usano due tabelle e , ma solo il triangolo superiore (): contiene e contiene (informazione addizionale). I casi base sono sulla diagonale principale.
Ordine di riempimento. Per calcolare servono (nella stessa riga, a sinistra) e (nella stessa colonna, sotto), per . La scansione per righe crescenti o per colonne crescenti non funziona: in entrambe verrebbe calcolato dopo . Ma i valori necessari stanno su diagonali più vicine alla principale. La diagonale contiene le celle con , cioè le sottocatene di lunghezza , e quelle da cui dipende sono sottocatene più corte, quindi su diagonali . Una scansione per diagonali di lunghezza crescente è coerente con le dipendenze. I casi base sono sulla prima diagonale. Sulla diagonale l'indice di riga va da a e .
MCM(p)
n <- length(p) - 1
for i <- 1 to n do m[i, i] <- 0
for l <- 2 to n do
for i <- 1 to n - l + 1 do
j <- i + l - 1
m[i, j] <- +infinity
for k <- i to j - 1 do
q <- m[i, k] + m[k+1, j] + p_{i-1} * p_k * p_j
if q < m[i, j] then
m[i, j] <- q
s[i, j] <- k
return (m[1, n], s)Costo (somma esatta). Con costo per ogni valore di (i due prodotti tra dimensioni):
usando la somma di Gauss e quella dei quadrati (SommatorieIl simbolo di sommatoria, le sue proprietà (linearità, additività, cambio di indice) e le somme notevoli di Gauss e geometrica.Sommatorie →). Quindi , da confrontare con del D&C (per : contro ). Controllato contando i prodotti dell'implementazione.
Altre scansioni valide. Per righe dalla riga alla riga , ciascuna da a ; oppure per colonne, ciascuna dal basso verso l'alto. Entrambe rispettano il grafico delle dipendenze.
for i <- n downto 1 do for j <- 1 to n do
m[i, i] <- 0 m[j, j] <- 0
for j <- i + 1 to n do for i <- j - 1 downto 1 do
(calcolo di m[i, j] come sopra) (calcolo di m[i, j] come sopra)Esempio eseguito
, cioè matrici: , , , , . Le tabelle (celle dopo la diagonale principale).
Tabella :
| 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|
| 1 | 0 | 120 | 264 | 1080 | 1344 |
| 2 | 0 | 360 | 1320 | 1350 | |
| 3 | 0 | 720 | 1140 | ||
| 4 | 0 | 1680 | |||
| 5 | 0 |
Tabella :
| 2 | 3 | 4 | 5 | |
|---|---|---|---|---|
| 1 | 1 | 2 | 2 | 2 |
| 2 | 2 | 2 | 2 | |
| 3 | 3 | 4 | ||
| 4 | 4 |
Diagonale per diagonale:
: : ; ; ; .
, cella :
- : ;
- : .
Il minimo è con . Analogamente e con .
, cella : : ; : ; : . Minimo con . Per : : ; : ; : ; minimo .
, cella : : ; : ; : ; : . Il minimo è con .
Il costo ottimo è . Ricostruzione: spezza in e ; spezza la seconda in e ; e spezzano le coppie. Parentesizzazione: . Controllo del costo: costa , costa , costa e il prodotto finale ; totale . Dei ordini possibili questo è l'unico di costo minimo e il peggiore costa (calcolato per enumerazione); l'algoritmo è stato confrontato con la forza bruta su vettori casuali.
Stampa della parentesizzazione
Un algoritmo ricorsivo che ricalca l'albero:
PRINT_MCM(s, i, j)
if i = j then
print("A_i")
return
print("(")
PRINT_MCM(s, i, s[i, j])
PRINT_MCM(s, s[i, j] + 1, j)
print(")")(Nelle dispense manca il return dopo la stampa del caso base: senza di esso il caso proseguirebbe e leggerebbe , non definito.) Stampa una parentesi per ogni nodo interno ( in tutto) e un carattere per ogni foglia (): caratteri, ottimo nella dimensione dell'output. In modo analogo, con la tabella e le matrici, una procedura ricorsiva che chiama RECT_MAT esegue prodotti tra matrici e in tutto esattamente prodotti tra scalari.
L'approccio memoizzato
Stessa sottostruttura, con la ricorsione e un valore di default per le celle non calcolate. La sentinella è : segnala le celle da calcolare e inizializza correttamente l'accumulo del minimo (i casi base sono già a ).
INIT_MCM(p)
n <- length(p) - 1
if n = 1 then return 0
for i <- 1 to n do
m[i, i] <- 0
for j <- i + 1 to n do m[i, j] <- +infinity
return REC_MCM(p, 1, n)
REC_MCM(p, i, j)
if m[i, j] = +infinity then
for k <- i to j - 1 do
q <- REC_MCM(p, i, k) + REC_MCM(p, k+1, j) + p_{i-1} * p_k * p_j
if q < m[i, j] then m[i, j] <- q
return m[i, j]Costo. Ogni sottoistanza non di base è un nodo interno una sola volta, con lavoro ; e da si raggiunge ogni coppia (basta scegliere gli giusti). Quindi
(raggruppando per , che compare volte; stessa somma del bottom-up). Diversamente dalla LCS, la memoizzazione non risparmia mai nulla: tutte le sottoistanze vanno comunque risolte, e la fase top-down non permette di saltarne. Il vantaggio è non dover progettare l'ordine di riempimento per diagonali. Verificato anche questo conteggio con Python per .
Domande d'esame
- (Risposta numerica.) Sia il vettore delle dimensioni di una catena di matrici. Quanti prodotti tra scalari richiede la parentesizzazione ottima? Svolto in Esercizio - domande di prima parte su programmazione dinamica (simulazioni d'esame): 130, con .
- (Risposta numerica.) Quante parentesizzazioni distinte ha una catena di matrici? E quanti prodotti tra scalari (sempre due per ) esegue
MCMsu matrici? Traccia: (numero di Catalan); . - (Quesito aperto.) Perché il D&C su MCM è esponenziale mentre la DP è cubica? Perché la scansione per righe non va bene? Traccia: sottoistanze ripetute (le sottocatene sono , ma le chiamate ); dipende da nelle righe successive, che per righe crescenti non sono pronte; per diagonali sì.
Versione ripasso
- Problema. Catena , , istanza = . Prodotto per : prodotti tra scalari (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 →, Prodotto di matrici - algoritmo iterativo e ricorsivo a blocchimodello di costo sulle matrici n x n (operazioni tra scalari, taglia n oppure N = n^2); somma in n^2 operazioni (ottima); prodotto righe per colonne iterativo con costo esatto 2n^3 - n^2; proprietà frattale del prodotto a blocchi dimostrata sugli indici; algoritmo ricorsivo RMUL con T(n) = 8T(n/2) + n^2, Theta(n^3) e soluzione esatta 2n^3 - n^2 per induzione e per srotolamento: fa le stesse operazioni dell'iterativo.Prodotto di matrici - algoritmo iterativo e ricorsivo a blocchi →). Parentesizzazione = albero binario pieno, sempre prodotti tra matrici, costo scalare variabile. Esempio : costa , costa .
- Parentesizzazioni: , (numeri di Catalan, ), per induzione (si tengono i termini e di ), .
- Sottostruttura (cut-and-paste): in la radice divide in e , entrambi ottimi, e ; minimizza questa somma. Casi base: una matrice, costo .
- Ricorrenza: ; . Informazione addizionale .
- D&C
R_MCM: , (si tiene ); esatto , cioè ( per ). Per le chiamate sono e ecc. si ripetono - volte. - Bottom-up per diagonali , , : righe o colonne crescenti non vanno ( non è pronto); valgono anche righe da a o colonne dal basso. Costo .
MCM(p): n <- length(p)-1; for i <- 1 to n do m[i,i] <- 0
for l <- 2 to n do for i <- 1 to n-l+1 do
j <- i+l-1; m[i,j] <- +inf
for k <- i to j-1 do
q <- m[i,k] + m[k+1,j] + p_{i-1} p_k p_j
if q < m[i,j] then m[i,j] <- q; s[i,j] <- k
return (m[1,n], s)
PRINT_MCM(s,i,j): if i = j then print A_i, return; print "("; PRINT_MCM(s,i,s[i,j]); PRINT_MCM(s,s[i,j]+1,j); print ")"- Esempio : ; ; ; . ; ; , ; . Cella : danno . Ottimo : (costi ); il peggiore dei costa . Stampa: caratteri.
- Perché il D&C spreca: per le chiamate a
R_MCMsono ; le sottocatene compaiono volte, due volte ciascuna, una volta. Le sottocatene distinte sono solo . - Altre scansioni valide: righe da a (ciascuna da a ) oppure colonne dal basso ( fino a ). Il grafico delle dipendenze: usa la riga a sinistra e la colonna sotto.
- Tabelle dell'esempio (triangolo superiore; a sinistra , a destra ):
| 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|
| 1 | 0 | 120 | 264 | 1080 | 1344 |
| 2 | 0 | 360 | 1320 | 1350 | |
| 3 | 0 | 720 | 1140 | ||
| 4 | 0 | 1680 | |||
| 5 | 0 |
| 2 | 3 | 4 | 5 | |
|---|---|---|---|---|
| 1 | 1 | 2 | 2 | 2 |
| 2 | 2 | 2 | 2 | |
| 3 | 3 | 4 | ||
| 4 | 4 |
- Calcolo della somma del bottom-up: (Gauss e somma dei quadrati, SommatorieIl simbolo di sommatoria, le sue proprietà (linearità, additività, cambio di indice) e le somme notevoli di Gauss e geometrica.Sommatorie →). D&C contro DP per : contro .
- Memoizzato: sentinella , ogni nodo interno una volta con lavoro , tutte le sottoistanze raggiunte: , mai meglio del bottom-up (diversamente dalla LCS).
REC_MCM(p, i, j) { m[i,i] = 0, le altre celle = +inf }
if m[i,j] = +inf then
for k <- i to j-1 do
q <- REC_MCM(p,i,k) + REC_MCM(p,k+1,j) + p_{i-1} p_k p_j
if q < m[i,j] then m[i,j] <- q
return m[i,j]- Domande tipiche: : costa , l'altro (Esercizio - domande di prima parte su programmazione dinamica (simulazioni d'esame)); ; prodotti di
MCMper : . - Errori tipici: scansione per righe o per colonne crescenti; scambiare con ; contare anche le somme; dimenticare il
returninPRINT_MCM; credere che la memoizzazione riduca il lavoro (qui no).