dynamic-programming
Moltiplicazione della catena di matrici
Ricerca…
Soluzione ricorsiva
La moltiplicazione della catena della matrice è un problema di ottimizzazione che può essere risolto utilizzando la programmazione dinamica. Data una sequenza di matrici, l'obiettivo è trovare il modo più efficiente per moltiplicare queste matrici. Il problema non è in realtà quello di eseguire le moltiplicazioni, ma semplicemente di decidere la sequenza delle moltiplicazioni di matrice coinvolte.
Diciamo che abbiamo due matrici A 1 e A 2 di dimensione m * n e p * q . Dalle regole della moltiplicazione della matrice, sappiamo che,
- Possiamo moltiplicare A 1 e A 2 se e solo se n = p . Ciò significa che il numero di colonne di A 1 deve essere uguale al numero di righe di A 2 .
- Se la prima condizione è soddisfatta e moltiplichiamo A 1 e A 2 , otterremo una nuova matrice, chiamiamola A 3 di dimensione m * q .
- Per moltiplicare A 1 e A 2 , dobbiamo fare alcune moltiplicazioni degli scalers. Il numero totale di moltiplicatori dello scaler, che dobbiamo eseguire è ( m * n * q ) o ( m * p * q ).
- A 1 * A 2 non è uguale a A 2 * A 1 .
Se abbiamo tre matrici A 1 , A 2 e A 3 aventi rispettivamente dimensione m * n , n * p e p * q , allora A 4 = A 1 * A 2 * A 3 avrà dimensione di m * q . Ora possiamo eseguire questa moltiplicazione di matrice in due modi:
- Per prima cosa moltiplichiamo A 1 e A 2 , quindi con il risultato moltiplichiamo A 3 . Cioè: ( A 1 * A 2 ) * A 3 .
- Per prima cosa moltiplichiamo A 2 e A 3 , quindi con il risultato moltiplichiamo A 1 . Cioè: A 1 * ( A 2 * A 3 ).
È possibile notare che l'ordine della moltiplicazione rimane lo stesso, ovvero non moltiplichiamo ( A 1 * A 3 ) * A 2 perché potrebbe non essere valido. Modifichiamo solo la parentesi per moltiplicare un set prima di moltiplicarlo con il rimanente. Come poniamo queste parentesi sono importanti. Perché? Diciamo che la dimensione di 3 matrici A 1 , A 2 e A 3 sono 10 * 100 , 100 * 5 , 5 * 50 . Poi,
- Per ( A 1 * A 2 ) * A 3 , il numero totale di moltiplicatori dello scaler è: ( 10 * 100 * 5 ) + ( 10 * 5 * 50 ) = 7500 volte.
- Per A 1 * ( A 2 * A 3 ), il numero totale di moltiplicatori dello scaler è: ( 100 * 5 * 50 ) + ( 10 * 100 * 50 ) = 75000 volte.
Per il 2 ° tipo il numero della moltiplicazione dello scaler è 10 volte il numero del 1 ° tipo! Quindi, se è possibile escogitare un modo per scoprire l'orientamento corretto delle parentesi necessarie per minimizzare la moltiplicazione dello scaler totale, ridurrebbe sia il tempo che la memoria necessari per la moltiplicazione della matrice. È qui che la moltiplicazione della catena di matrici è utile. Qui, non ci occuperemo della moltiplicazione effettiva delle matrici, scopriremo solo l'ordine di parentesi corretto in modo che il numero di moltiplicazione dello scaler sia ridotto al minimo. Avremo matrici A 1 , A 2 , A 3 ........ A n e scopriremo il numero minimo di moltiplicatori di scaler necessari per moltiplicarli. Assumiamo che le dimensioni date siano valide, ovvero soddisfa il nostro primo requisito per la moltiplicazione della matrice.
Useremo il metodo divide et impera per risolvere questo problema. La programmazione dinamica è necessaria a causa di sottoproblemi comuni. Ad esempio: per n = 5 , abbiamo 5 matrici A 1 , A 2 , A 3 , A 4 e A 5 . Vogliamo scoprire il numero minimo di moltiplicazione necessario per eseguire questa moltiplicazione di matrice A 1 * A 2 * A 3 * A 4 * A 5 . Tra i molti modi, concentriamoci su uno: ( A 1 * A 2 ) * ( A 3 * A 4 * A 5 ).
Per questo, scopriremo A left = A 1 * A 2 . A destra = A 3 * A 4 * A 5 . Quindi scopriremo una risposta = A sinistra * A destra . Il numero totale di moltiplicatori dello scaler necessario per trovare una risposta : = Il numero totale di moltiplicatori dello scaler necessarie per determinare A sinistra + Il numero totale di moltiplicatori dello scaler necessarie per determinare A destra + Il numero totale di moltiplicatori dello scaler necessarie per determinare A sinistra * Un diritto
L'ultimo termine, Il numero totale di moltiplicatori dello scaler necessarie per determinare A sinistra * A destra può essere scritto come: Il numero di righe in A sinistra * il numero di colonne in A sinistra * il numero di colonne in A destra . (Secondo la seconda regola della moltiplicazione delle matrici)
Ma potremmo impostare la parentesi anche in altri modi. Per esempio:
- A 1 * ( A 2 * A 3 * A 4 * A 5 )
- ( A 1 * A 2 * A 3 ) * ( A 4 * A 5 )
- ( A 1 * A 2 * A 3 * A 4 ) * A 5 ecc.
Per ogni caso possibile, determineremo il numero di moltiplicazione dello scalatore necessario per scoprire A sinistra e A destra , quindi A sinistra * A destra . Se hai un'idea generale sulla ricorsione, hai già capito come eseguire questa attività. Il nostro algoritmo sarà:
- Set parenthesis in all possible ways.
- Recursively solve for the smaller parts.
- Find out the total number of scaler multiplication by merging left and right.
- Of all possible ways, choose the best one.
Perché questo è un problema di programmazione dinamica? Per determinare ( A 1 * A 2 * A 3 ), se hai già calcolato ( A 1 * A 2 ), sarà utile in questo caso.
Per determinare lo stato di questa ricorsione, possiamo vedere che per risolvere per ogni caso, avremo bisogno di conoscere l'intervallo di matrici con cui stiamo lavorando. Quindi avremo bisogno di un inizio e di una fine . Dato che stiamo usando divide e conquista, il nostro caso base avrà meno di 2 matrici ( inizio > = fine ), dove non è necessario moltiplicare del tutto. Avremo 2 array: riga e colonna . la riga [i] e la colonna [i] memorizzeranno il numero di righe e colonne per la matrice A i . Avremo una matrice dp [n] [n] per memorizzare i valori già calcolati e inizializzarla con -1 , dove -1 rappresenta il valore non ancora calcolato. dp [i] [j] rappresenta il numero di moltiplicatori dello scaler necessari per moltiplicare A i , A i + 1 , ....., A j inclusive. Lo pseudo-codice sarà simile a:
Procedure matrixChain(begin, end):
if begin >= end
Return 0
else if dp[begin][end] is not equal to -1
Return dp[begin][end]
end if
answer := infinity
for mid from begin to end
operation_for_left := matrixChain(begin, mid)
operation_for_right := matrixChain(mid+1, right)
operation_for_left_and_right := row[begin] * column[mid] * column[end]
total := operation_for_left + operation_for_right + operation_for_left_and_right
answer := min(total, answer)
end for
dp[begin][end] := answer
Return dp[begin][end]
Complessità:
Il valore di inizio e fine può variare da 1 a n . Esistono n 2 stati diversi. Per ogni stato, il ciclo interno verrà eseguito n volte. Complessità temporale totale: O(n³)
e complessità della memoria: O(n²)
.