In cosa consiste la programmazione dinamica?


33

Scusa in anticipo se questa domanda sembra stupida ...

Per quanto ne so, la creazione di un algoritmo utilizzando la programmazione dinamica funziona in questo modo:

  1. esprimere il problema come relazione di ricorrenza;
  2. attuare la relazione di ricorrenza tramite memoizzazione o un approccio dal basso verso l'alto.

Per quanto ne so, ho detto tutto sulla programmazione dinamica. Voglio dire: la programmazione dinamica non fornisce strumenti / regole / metodi / teoremi per esprimere relazioni di ricorrenza, né per trasformarle in codice.

Quindi, cosa c'è di speciale nella programmazione dinamica? Cosa ti dà, a parte un metodo vago per affrontare un certo tipo di problemi?


11
Factoide storico (questo commento non ti aiuterà, ma Bellman è in realtà un buon vantaggio se vuoi approfondire la teoria sulla programmazione dinamica): quando Bellman ha inventato quella che ora è conosciuta come programmazione dinamica, ha chiamato l'idea "programmazione dinamica "perché il lavoro puramente teorico non volava con il suo datore di lavoro in quel momento, quindi aveva bisogno di qualcosa di più vivace che non potesse essere usato in modo peggiorativo .
G. Bach,

3
Per quanto ne so, sono esattamente questi due punti che menzioni. Diventa speciale quando evita l'esplosione esponenziale a causa di sottoproblemi sovrapposti. È tutto. Ah, a proposito, il mio professore preferisce il "paradigma algoritmico" al "metodo vago".
Hendrik Jan

"Programmazione dinamica" sembra essere principalmente una parola d'ordine (che da allora ha perso il suo ronzio). Ciò non significa che non sia utile ovviamente.
user253751

3
Non degno di una risposta, ma per me la programmazione dinamica è sicuramente "quella cosa che usi quando cerchi di risolvere un problema in modo ricorsivo, ma finisci per perdere tempo a rivisitare gli stessi sottoproblemi ripetutamente".
Hobbs,

@hobbs: Esatto, ma l'abilità sta nel trovare quel modo iniziale di perdere tempo;)
j_random_hacker

Risposte:


27

La programmazione dinamica ti offre un modo di pensare al design dell'algoritmo. Questo è spesso molto utile.

I metodi di memorizzazione e bottom-up offrono una regola / metodo per trasformare le relazioni di ricorrenza in codice. La memorizzazione è un'idea relativamente semplice, ma spesso le idee migliori lo sono!

La programmazione dinamica ti offre un modo strutturato di pensare al tempo di esecuzione del tuo algoritmo. Il tempo di esecuzione è sostanzialmente determinato da due numeri: il numero di sottoproblemi che devi risolvere e il tempo necessario per risolvere ogni sottoproblema. Ciò fornisce un modo semplice e conveniente per pensare al problema di progettazione dell'algoritmo. Quando hai una relazione di ricorrenza candidata, puoi guardarla e capire molto rapidamente quale potrebbe essere il tempo di esecuzione (ad esempio, spesso puoi dire molto rapidamente quanti sottoproblemi ci saranno, che è un limite inferiore al tempo di esecuzione; se ci sono molti sottoproblemi in modo esponenziale che devi risolvere, probabilmente la ricorrenza non sarà un buon approccio). Ciò consente anche di escludere le decomposizioni dei sottoproblemi candidati. Ad esempio, se abbiamo una stringaS [ 1 .. i ] S [ j . . n ] S [ i . . j ] n S nS[1..n], definire un sottoproblema con un prefisso o suffisso o sottostringa potrebbe essere ragionevole (il numero di sottoproblemi è polinomiale in ), ma definire un sottoproblema da una sottosequenza di non è probabile che sia un buon approccio (il numero di sottoproblemi è esponenziale in ). Ciò consente di eliminare lo "spazio di ricerca" di possibili ricorrenze.S[1..i]S[j..n]S[i..j]nSn

La programmazione dinamica ti offre un approccio strutturato per cercare le relazioni di ricorrenza del candidato. Empiricamente, questo approccio è spesso efficace. In particolare, ci sono alcune euristiche / schemi comuni che puoi riconoscere per modi comuni di definire i sottoproblemi, a seconda del tipo di input. Per esempio:

  • Se l'input è un numero intero positivo , un modo candidato per definire un sottoproblema è sostituendo con un numero intero più piccolo (st ).n n 0 n nnnn0nn

  • Se l'input è una stringa , alcuni modi candidati per definire un sottoproblema includono: sostituire con un prefisso ; sostituire con un suffisso ; sostituire con una sottostringa . (Qui il sottoproblema è determinato dalla scelta di .)S [ 1 .. n ] S [ 1 .. i ] S [ 1 .. n ] S [ j . . n ] S [ 1 .. n ] S [ i . . j ] i , jS[1..n]S[1..n]S[1..i]S[1..n]S[j..n]S[1..n]S[i..j]i,j

  • Se l'input è un elenco , fai lo stesso che faresti per una stringa.

  • Se l'input è un albero , un modo candidato per definire un sottoproblema è sostituire con qualsiasi sottostruttura di (cioè, selezionare un nodo e sostituire con la sottostruttura radicata in ; il sottoproblema è determinato dalla scelta di ).T T x T x xTTTxTxx

  • Se l'input è una coppia , guarda ricorsivamente il tipo di e il tipo di per identificare un modo per scegliere un sottoproblema per ciascuno. In altre parole, un modo per definire un candidato sottoproblema è quello di sostituire da dove è un sottoproblemi per ed è un sottoproblemi per . (Puoi anche considerare i sottoproblemi del modulo o .)x y ( x , y ) ( x , y ) x x y y ( x , y ) ( x , y )(x,y)xy(x,y)(x,y)xxyy(x,y)(x,y)

E così via. Questo ti dà un'euristica molto utile: semplicemente guardando la firma del tipo del metodo, puoi trovare un elenco di modi candidati per definire i sottoproblemi. In altre parole, semplicemente osservando la dichiarazione del problema - osservando solo i tipi di input - puoi trovare una manciata di modi candidati per definire un sottoproblema.

Questo è spesso molto utile. Non ti dice quale sia la relazione di ricorrenza, ma quando hai una scelta particolare su come definire il sottoproblema, spesso non è troppo difficile elaborare una relazione di ricorrenza corrispondente. Quindi, spesso trasforma la progettazione di un algoritmo di programmazione dinamica in un'esperienza strutturata. Annoti su carta straccia un elenco di modi candidati per definire i sottoproblemi (usando l'euristica sopra). Quindi, per ciascun candidato, si tenta di annotare una relazione di ricorrenza e di valutarne il tempo di esecuzione contando il numero di sottoproblemi e il tempo trascorso per sottoproblema. Dopo aver provato ogni candidato, mantieni il migliore che sei riuscito a trovare. Fornire una struttura al processo di progettazione dell'algoritmo è di grande aiuto, poiché altrimenti la progettazione dell'algoritmo può essere intimidatoria (


Quindi confermi che la programmazione dinamica non prevede "procedure" concrete da seguire. È solo "un modo di pensare", come hai detto. Nota che non sto sostenendo che DP sia inutile (al contrario!), Sto solo cercando di capire se c'è qualcosa che mi manca o se dovrei semplicemente esercitarmi di più.
Ehi, ehi,

@heyhey, beh, sì ... e no. Vedi la mia risposta rivista per ulteriori elaborazioni. Non è un proiettile d'argento, ma fornisce alcune procedure semi-concrete che sono spesso utili (non garantite per funzionare, ma spesso si rivelano utili).
DW

Grazie molto! Con la pratica sto acquisendo sempre più familiarità con alcune di quelle "procedure semi-concrete" che stai descrivendo.
Ehi, ehi,

"se ci sono in modo esponenziale molti sottoproblemi che devi risolvere, probabilmente la ricorrenza non sarà un buon approccio". Per molti problemi non esiste un algoritmo temporale polinomiale noto. Perché questo dovrebbe essere un criterio per l'utilizzo della DP?
Chiel ten Brinke,

@Chiel, non è un criterio per l'utilizzo di DP. Se hai un problema in cui saresti felice con un algoritmo a tempo esponenziale, allora puoi ignorare quella particolare osservazione tra parentesi. È solo un esempio per cercare di illustrare il punto generale che stavo sollevando, non qualcosa che dovresti prendere troppo sul serio o interpretare come una regola rigida.
DW

9

La tua comprensione della programmazione dinamica è corretta ( afaik ) e la tua domanda è giustificata.

Penso che lo spazio di progettazione aggiuntivo che otteniamo dal tipo di ricorrenze che chiamiamo "programmazione dinamica" possa essere visto meglio rispetto ad altri schemi di approcci ricorsivi.

A[1..n]

  1. Approccio induttivo

    Qui l'idea è di ridurre il problema, risolvere la versione più piccola e ricavare una soluzione per quella originale. schematicamente,

    f(A)=g(f(A[1..nc]),A)

    g

    Esempio: ricerca di superstar in tempo lineare

  2. Dividi e conquista

    Suddividere l'input in più parti più piccole, risolvere il problema per ciascuna e combinare. Schematicamente (per due parti),

    f(A)=g(f(A[1..c]),f(A[c+1..n]),A)

    Esempi: Unisci / Quicksort, La distanza più breve a coppie nel piano

  3. Programmazione dinamica

    Prendi in considerazione tutti i modi per suddividere il problema in problemi più piccoli e scegli il migliore. Schematicamente (per due parti),

    f(A)=best{g(f(A[1..c]),f(A[c+1..n]))|1cn1}

    Esempi: modifica distanza, problema di modifica

    best

In un certo senso, sai sempre meno staticamente andare dall'alto verso il basso e devi prendere sempre più decisioni dinamicamente.

La lezione da imparare sulla programmazione dinamica è che va bene provare tutti i possibili partizionamenti (beh, è ​​necessario per correttezza) perché può ancora essere efficiente usando la memoizzazione.


La "Programmazione dinamica potata" (quando applicabile) dimostra che NON è necessario provare tutte le possibilità per la correttezza.
Ben Voigt,

@BenVoigt Certo. Sono rimasto deliberatamente vago sul significato di "tutti i modi di partizionare"; vuoi escluderne il maggior numero possibile, ovviamente! (Tuttavia, anche se provi tutti i modi di partizionare non ottieni forza bruta poiché indaga sempre e solo combinazioni di soluzioni ottimali ai sottoproblemi, mentre la forza bruta studierebbe tutte le combinazioni di tutte le soluzioni.)
Raffaello


5

La programmazione dinamica consente di scambiare la memoria con i tempi di calcolo. Considera l'esempio classico, Fibonacci.

Fib(n)=Fib(n1)+Fib(n2)O(2n)Fib()n

Fib(2)Fib(3)Fib(4)O(n)

mm


1
Parli solo della parte della memoizzazione, che manca il punto della domanda.
Raffaello

1
"La programmazione dinamica ti consente di scambiare la memoria con i tempi di calcolo" non è qualcosa che ho sentito durante gli studi universitari, ed è un ottimo modo per esaminare questo argomento. Questa è una risposta intuitiva con un esempio sintetico.
Trueshot,

@trueshot: ad eccezione del fatto che a volte la programmazione dinamica (e in particolare la "Programmazione dinamica potata") è in grado di ridurre i requisiti di tempo e spazio.
Ben Voigt,

@ Ben non ho detto che era uno scambio uno a uno. Puoi anche potare un albero di ricorrenza. Suppongo di aver risposto alla domanda, che era "Cosa ci dà DP?" Ci rende algoritmi più veloci scambiando spazio per il tempo. Concordo sul fatto che la risposta accettata è più approfondita, ma anche questa è valida.
Kittsil,

2

Ecco un altro modo leggermente diverso di esprimere ciò che la programmazione dinamica ti offre. La programmazione dinamica fa collassare un numero esponenziale di soluzioni candidate in un numero polinomiale di classi di equivalenza, in modo tale che le soluzioni candidate in ciascuna classe siano indistinguibili in un certo senso.

kAn2nO(n2)f(i,)i

f(i,)=j<i such thatA[j]<A[i]f(j,1)
f(i,1)=1 for all i=1n

O(n2k)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.