Esența programării dinamice. Probleme aplicate de programare dinamică. Programare dinamică. Noțiuni de bază

Programare dinamică.

La modelare structuri de retea Pe lângă problemele legate de existența fluxurilor în rețele de transport, electrice, telefonice, informatice și alte tipuri de rețele, apare o întreagă clasă de probleme care pot fi reduse la problema drumului cel mai scurt. De exemplu, problema celei mai scurte căi este rezolvată de fiecare dată de un program de router atunci când un site este găsit după numele său pe Internet.

Problema cu calea cea mai scurtă într-o rețea direcționată este o problemă tipică programare dinamică, prin urmare, deși programarea dinamică, precum și planificarea rețelei, sunt asociate cu dezvoltarea proceselor în timp, a căror modelare este discutată mai detaliat în secțiunea următoare, vom lua în considerare în acest paragraf metoda de programare dinamică folosind exemplul de a găsi calea cea mai scurtă.

Conceptul de programare dinamică este strâns legat de procese în mai multe etape luarea deciziilor. Un proces de luare a deciziilor în mai multe etape poate fi definit ca procesul de luare a deciziilor succesive care vizează atingerea unui obiectiv dat. Procesele de luare a deciziilor în mai multe etape sunt întâlnite în mod constant în cele mai multe situatii diferite, de la repararea mașinii într-un centru de service auto până la controlul unei nave spațiale.

Programarea dinamică poate fi definită vag ca un set de proceduri matematice utilizate în analiza proceselor decizionale în mai multe etape. Fiecare proces de decizie în mai multe etape este o evoluție a următoarei probleme: găsirea celei mai scurte căi într-o rețea direcționată, aciclică.

Programarea dinamică poate fi considerată ca o teorie unificată datorită unui singur set de idei și tehnici care sunt utilizate în analiză matematică diverse sarcini. Aceste idei și tehnici sunt esența programării dinamice. Bellman a fost unul dintre primii care au înțeles esența principiului optimității și a început să-l aplice la multe probleme de optimizare apărute în matematică, inginerie, cercetare operațională și alte domenii.

Astfel, conceptul de programare dinamică este asociat cu un proces decizional în mai multe etape pentru atingerea unui obiectiv specific. De exemplu, traducerea aeronave de la o orbită la alta este o problemă tipică de programare dinamică, cu condiția ca corectarea orbitei să fie efectuată prin aplicarea unui impuls în momente discrete și scopul să fie economia de combustibil.

Caracterizarea programarii dinamice ca un set de proceduri matematice pentru control optim sistem discret, V vedere generala Problema de control optim poate fi formulată după cum urmează. În momente discrete în timp t= 1, 2,..., N sistemul se află într-una din mulțimile s i de stări caracterizate de vectorul de stare x (t) . Tranziția între stări succesive se realizează folosind vectorul de control u (t) conform legii:

X (t) = g (t) (X (t) , u (t)) ; t= 1, 2,..., N

Controalele u (t) sunt selectate din setul de controale admisibile și formează o secvență de comenzi admisibile u (0) ,u (1) ,…,u (N) . Secvența controalelor admisibile pentru o stare inițială dată x (0) determină traiectoria sistemului x (0), x (1), x (2),…, x (N).

Fiecare traiectorie are propria sa valoare a criteriului de optimitate F, sau funcție obiectivă control, constând în contribuții individuale la fiecare etapă de control:

Problema optimă de control este de a găsi dintr-un set de secvențe de control una care să realizeze valoarea minima F. Această secvență se numește secvența optimă de control și determină traiectoria optimă.

Programarea dinamică se bazează pe principiul optimității Bellman, care poate fi formulat după cum urmează. O strategie optimă are proprietatea că, indiferent de starea inițială și de decizia la momentul inițial, deciziile ulterioare trebuie să formuleze o strategie optimă în raport cu starea apărută după decizia inițială.

Sensul principiului optimității devine mai clar dacă avem în vedere că pentru o traiectorie optimă, fiecare secțiune dintre punctul final și orice punct intermediar este și ea o traiectorie optimă. Principiul optimității sau, altfel, metoda de programare dinamică, vă permite să găsiți strategia optimă în mai multe etape prin rezolvarea unui set de probleme de optimizare mai simple într-un singur pas.

Metoda de programare dinamică este bine ilustrată de exemplul găsirii celei mai scurte căi între nodurile extreme ale unei rețele orientate. Luați în considerare o rețea direcționată cu 12 noduri, care trebuie parcursă de la nodul de început (1) la nodul final (12) în patru pași, deplasându-se de la nod la nod cu fiecare pas.

Orez. 6.4.1. Parcurgerea unei rețele direcționate pe calea cea mai scurtă.

Numerele indicate la arce ( i,j) sunt egale cu lungimile arcurilor l ijîntre noduri iȘi j(în unități convenționale). Stări posibile ale sistemului se află în în acest caz, asociat cu a fi în i Nodul, controlul u(t) este asociat cu alegerea direcției căii la fiecare pas de control. Patru trepte de control u (1),...,u (4) transferă secvenţial sistemul din starea iniţială s 1 în starea finală s 12 şi, astfel, formează o anumită traiectorie care trebuie găsită. Criteriul de optimitate F în acest caz este lungimea traiectoriei L, constând din lungimile arcurilor individuale:

Dacă căutarea drumului cel mai scurt, adică a traiectoriei optime, începe nu de la început, ci de la sfârșitul rețelei și trece la direcție inversă la început, apoi în acest caz avem un algoritm de „măturăre înapoi”. În acest caz, la implementarea algoritmului de baleiaj înapoi, mișcarea este efectuată de la starea finală s 12 la starea inițială s 1.

La începutul căutării celei mai scurte căi, este compilat un tabel de tranziție. Numărul de rânduri de tabel este egal cu numărul de pași de control, numărul de coloane este egal cu numărul de stări minus unu. Acest tabel va stoca pașii de control și valorile corespunzătoare ale criteriului de optimitate L t pentru toate stările posibile ale sistemului după fiecare pas.

Tabelul 6.4.1

aceasta s 1 s 2 s 3 s 4 s 5 S 6 s 7 s 8 s 9 s 10 s 11
12 12 6
10 11 10
5
1


Celulele umplute ale tabelului sunt împărțite în jumătate. ÎN top parte controlul u(t) este introdus în celula umplută, adică, în acest caz, numărul nodului către care se face tranziția. Valoarea contribuției Lt la valoarea totală a criteriului de optimitate L, care a fost obținută în timpul trecerii de la nodul corespunzător acestei celule la nodul final, se introduce în partea inferioară a celulei umplute.

Completarea tabelului începe cu prima linie, unde sunt stocate informații despre ultimul pas al căii. Ultimul, în acest caz, al patrulea pas al căii este determinat în mod unic în timpul tranziției de la orice penultima stare, care poate fi oricare dintre cele trei posibile: s 9, s 10, s 11. De aceea control optim ultimul pas este evident. În funcție de penultima stare, contribuția la criteriul de optimitate este L 4 (9) = 12, L 4 (10) = 6 sau L 4 (11) = 7. Aceste valori de contribuție la L sunt scrise în partea de jos. a celulelor din primul rând al tabelului. 6.4.1.

Înainte de penultimul - în acest caz, al treilea - pas, setul de stări posibile ale sistemului este (s 5, s 6, s 7, s 8). Să aplicăm acum principiul lui Bellman pentru a determina traiectoria la al treilea și al patrulea pas. Constă în faptul că, indiferent de primii doi pași de control, segmentul de traiectorie din ultimii doi pași este el însuși o traiectorie optimă, adică. dă contribuţia minimă a lui L 3 la criteriul optimităţii.

Dacă starea sistemului înainte de penultimul pas este starea s 8, atunci la ultimii pași contribuţia la L este determinată de relaţia

L 3 (s 5)=min( }.

Deoarece tranzițiile de la s 5 la s 9 și s 11 sunt posibile, adică:

g(s 5,9) = s 9; ; L 4 (s 9) = 12,

g(s 5,11) = s 11; ; L 4 (s 11) = 7,

L 3 (s 5) = min(6+12, 4+7) = 11 și u (3) = 11.

Aceasta înseamnă că, dacă sistemul este în starea s 5, atunci controlul optim constă în tranziția mai întâi la starea s 11, apoi la starea s 12. Lungimea arcului de la s 5 la s 12 se dovedește a fi egală cu 11 unități.

Calculând contribuția la L în mod similar pentru tranzițiile din stările s 6, s 7, s 8, obținem următoarele contribuții:

L3(s6)=min(7+12, 6+6)=12, u (3) =10;

L3(s7)=min(5+6, 3+7)=10, u (3) =11;

L3(s8)=min(10+6, 12+7)=16, u (3) =10;

Cele patru perechi de numere rezultate sunt scrise pe a doua linie a tabelului. 6.4.1.

La a doua etapă de control, contribuția la criteriul de optimitate depinde de stare initiala Există

L2 (s2) = min() = min(11+11, 14+10) = 22, u (2) = 5;

L 2 (s 3) = min( ) = min(7+11, 9+12) = 18, u (2) = 5;

L2 (s 4) = min( ) = min(2+16, 3+12, 6+10) = 15, u (2) = 6;

Cele trei perechi de numere rezultate sunt scrise în a treia linie a tabelului 6.4.1.

Starea inițială s 1 este determinată în mod unic, deci în ultima linie Tabelul este completat în singura celulă în care sunt introduse valorile 3 și 24 deoarece:

L 1 (s 1) = min( ) = min(5+22, 6+18, 11+15) = 24, u (1) = 3.

Acum putem determina în sfârșit secvența controlului optim în mai mulți pași. La prima etapă u (1) = 3, adică. de la nodul 1 mergem la nodul 3, la pasul al doilea u (2) = 5, i.e. mergem la nodul 5, apoi dupa controlul u (3) = 11 - la nodul 11 ​​si, in final, la nodul 12. Obtinem in final ca cea mai scurta cale prin reteaua prezentata in Fig. 6.4.1, trece prin succesiunea stărilor s 1 →s 2 →s 5 →s 11 →s 12, iar lungimea sa este de 24 de unități convenționale.

Căutarea căii celei mai scurte poate fi efectuată și de la începutul rețelei, implementând în același timp un algoritm de baleiaj înainte, care efectuează în esență aceleași operațiuni de adăugare și comparare, dar într-o secvență ușor diferită.

Algoritmii de baleiaj înainte și înapoi, deși în esență diferiți, oferă o adăugare și o comparație pe arc. Prin urmare, ambii algoritmi au aceleași performanțe. Cu toate acestea, există o diferență importantă. Algoritmul de baleiaj înainte ia în considerare arcurile de ieșire dintre aceste noduri, cele mai scurte căi eu care sunt deja cunoscute.

Algoritmul de baleiaj înapoi ia în considerare arcurile Inbox spre acele noduri, cele mai scurte căi l j la care sunt încă necunoscute. Datorită acestei din urmă împrejurări, se preferă adesea algoritmul de măturare directă. Acest algoritm poate fi aplicat oricărei structuri a celui mai scurt set de căi.

Rezolvarea unei probleme simple cu calea cea mai scurtă ilustrează următoarele: trasaturi caracteristice, care sunt inerente proceselor decizionale mult mai complexe în mai multe etape:

1. Problema originală este cufundată în multe probleme de optimizare; în acest caz, fiecare nod își rezolvă propria problemă.

2. Setul de soluții la probleme de optimizare este descris de o ecuație funcțională, care este un sistem de ecuații care conectează mai multe probleme de optimizare. Într-un astfel de sistem, fiecare ecuație corespunde unui nod și de obicei conține operatori precum min, max sau minimax la dreapta semnului egal și variabile de tip g i și g j - pe ambele părți ale acestuia.

3. Soluția la multe probleme de optimizare poate fi găsită folosind un algoritm de baleiaj înapoi, care este echivalent cu o procedură ordonată pentru rezolvarea unei secvențe de ecuații funcționale.

Programarea dinamică este potrivită pentru rezolvarea problemelor de simulare sisteme de rețea, care nu au o structură specială. Astfel, algoritmii de baleiaj înainte și înapoi sunt potriviți pentru efectuarea de calcule în rețele aciclice. Algoritmul de baleiaj înapoi poate fi generalizat și utilizat pentru a rezolva probleme în care există un element de aleatorie. Acest lucru nu se poate face pentru algoritmul de baleiaj înainte.

Conceptul de „stat” joacă un rol central în programarea dinamică, iar stările înseamnă următoarele. Tranziția se realizează de la o stare la o stare care conține întreaga istorie a procesului, adică starea este descrisă cu un grad de detaliu care permite calcularea (evaluarea) soluțiilor alternative actuale.

Pentru model de rețea stările sunt noduri, iar arcele care emană de la un nod reprezintă diverse solutii, care poate fi acceptat într-un nod (stare) dat. Cu această interpretare, putem spune că trecerea are loc de la stat la stat, iar stările reprezintă puncte în care se iau deciziile. Declarația de mai sus înseamnă că arcele care părăsesc un nod nu au nicio legătură cu calea prin care a fost atins acest sau acel nod, adică nu depind de arcurile de intrare.

Elementele de stat sunt adesea numite variabile de stare.În modelele de programare dinamică, stările sunt uneori grupate în etape, iar tranziția se face de la o etapă la alta. De exemplu, în problema căii celei mai scurte există stări, dar nu există etape, deoarece este imposibil să grupați stările în mulțimi în așa fel încât să existe o tranziție de la o mulțime la alta.

Scufundarea într-o varietate de probleme de optimizare echivalează cu introducerea conceptului spaţiul de stat care reprezintă un ansamblu de stări. În ecuația funcțională, răspunsul optim este considerat ca o funcție a stării inițiale, iar principiul optimității stabilește relația dintre răspunsurile optime pentru diferite stări de plecare.

O multime de S sunt numite stări posibile (sau observabile). spațiu de stat,și element s din S definește un specific stat. Cu fiecare condiție s multe conectate D(s). Element d din multe D(s) se numește decizie. Se numește regula conform căreia se determină o soluție fezabilă pentru fiecare stare strategie d.

De fapt strategie d se potrivește cu fiecare stare s un element d( s) din multe D(s). Mulțimea tuturor acestor forme d spațiu de strategie D. Aceasta din urmă înseamnă că alegerea unei soluții într-o anumită stare nu constrânge alegerea în toate celelalte stări. În esență, D este un produs cartezian al mulțimilor D(s) De s.

Una dintre ideile programării dinamice este că fiecare strategie d trebuie să aibă așa-numita funcție de profit Vd(s), care poate fi obținut în funcție de stat sși utilizarea strategiei d. Conceptul de funcție de profit (sau venit) generalizează conceptul de contribuție L t la valoarea globală a criteriului de optimitate L luat în considerare la rezolvarea problemei celei mai scurte drumuri.

Expresia „folosirea strategiei d” înseamnă că ești capabil s solutia d( s); apoi se presupune că procesul a intrat în stare s„, adică statul este realizat s", în care soluția d( s"), etc. Funcția de profit are o structură destul de complexă, deoarece depinde de succesiunea stărilor și deciziilor, de recompensele care sunt asociate cu aceste stări și decizii, precum și de modul în care recompensele sunt agregate.

O stare este o descriere a istoriei unui proces cu un nivel de detaliu care permite o evaluare a curentului solutii alternative. Principala proprietate a statelor este că statul este o scurtă înregistrare a istoriei procesului și gradul de detaliu ne permite să determinăm funcția de venit local. funcţie locală venitul poate depinde doar de s, dȘi v.

Următorul capitol va analiza mai detaliat lanțurile Markov care au mare importanță pentru a simula evoluţia în timp a producţiei şi sisteme tehnice. Există, de asemenea, modele de luare a deciziilor Markov în care statul s determinat de o pereche de numere (n,i) , soluția este funcția în funcție de ele k, iar funcția de venit local este determinată de o expresie ca h[(n, eu) , k, v] = R k i(n) + å j P k ij(n)v(n+ 1,j) (n ).

Modelele Markov de luare a deciziilor sunt generalizate în direcții diferite, în special, la caz Probleme de reconstrucție Markov. Cea mai utilă generalizare se obține atunci când se consideră timpi de tranziție inegali sau variabili. În modelele simple, se presupune că trecerea de la stare la stare și observarea stării sunt instantanee, iar intervalul de timp dintre tranzițiile de la stare la stare poate fi de lungime variabilă sau aleatorie.

Ori de câte ori se observă o stare, o soluție este selectată și nu poate fi schimbată până când procesul trece într-o stare nouă, unde este selectată o nouă soluție etc. Acest model este o combinație între teoria lanțului Markov și teoria restaurării; numit de obicei Problema reconstrucției Markov.

Întrebări de test pentru capitolul 6.

1. Din ce componente constă o rețea direcționată?

1. Cum este construită matricea capacității rețelei?

1. Cum se formează matricea fluxului rețelei?

1. De ce se scad matricele de capacitate și flux?

1. Ce este o diagramă de rețea și pentru ce este folosită?

1. Cum se determină orele de început devreme și de terminare timpurie a lucrărilor?

1. Care este flota totală pentru un anumit eveniment din diagrama rețelei?

1. Cum este determinată calea critică?

1. Ce se numește vectorul de stare al unui anumit sistem?

1. Care este traiectoria unui sistem în spațiul de stare?

1. Care este problema controlului optim?

1. Cum este formulat criteriul de optimitate?

1. Ce este programarea dinamică?

1. Formulați principiul optimității Bellman.

1. Care este esența algoritmilor de baleiaj înainte și înapoi atunci când căutați calea cea mai scurtă?

Opțiuni pentru teme pentru capitolul 6.

Pentru rețele în fiecare dintre opțiuni:

1) Aflați debitul maxim de la sursa (1) până la nodul final al rețelei - chiuveta, presupunând că unul dintre numerele din paranteze pentru fiecare arc (i, j) determină capacitatea arcului;

1) Presupunând că arcurile (1)®(2), (1)®(3), etc. definesc unele locuri de muncă, a căror durată minimă și maximă sunt date de numerele indicate sub arcurile corespunzătoare, găsiți calea critică de la evenimentul inițial (1) până la final;

1) Căutați cea mai scurtă cale de la nodul de început până la nodul final al rețelei. Se consideră distanțele dintre nodurile i, j date de unul dintre numerele dintre paranteze.





X 4

Să presupunem că există o problemă pe care am rezolvat-o deja prin programare dinamică, de exemplu, eternele numere Fibonacci.
Să o reformulăm puțin. Să avem un vector din care dorim să obținem un vector. Să extindem puțin asupra formulelor: . Puteți vedea că dintr-un vector puteți obține un vector prin înmulțirea cu vreo matrice, deoarece în vectorul final apar doar variabilele adăugate din primul vector. Această matrice este ușor de obținut, aici este: . Să-i numim matricea de tranziție.

Aceasta înseamnă că dacă luăm vectorul și o înmulțim cu matricea de tranziție n - de 1 ori, obținem un vector care conține fib[n] - răspunsul la problemă.

Acum, de ce sunt necesare toate acestea? Înmulțirea matriceală are proprietatea asociativității, adică (dar în același timp nu are comutativitate, ceea ce după părerea mea este surprinzător). Această proprietate ne dă dreptul de a face acest lucru: .

Acest lucru este bun pentru că acum puteți folosi metoda exponențiării rapide, care funcționează în . În total, am putut calcula al N-lea număr Fibonacci ca logaritm al operațiilor aritmetice.

Și acum un exemplu mai serios:

Exemplul #3: Secvența rampei
Să notăm o secvență cu dinți de ferăstrău de lungime N ca o secvență în care este îndeplinită următoarea condiție pentru fiecare element neextrem: este fie mai mică decât ambii vecini, fie mai mare. Trebuie să numărați numărul de secvențe din dinți de ferăstrău de cifre de lungime N . Arata cam asa:

Soluţie

În primul rând, o soluție fără o matrice de tranziție:

1) Stare dinamică: dp[n] - numărul de secvențe din dinți de ferăstrău de lungime n care se termină cu numărul ultimul. Mai mult, dacă este mai mică == 0, atunci ultima cifră este mai mică decât penultima, iar dacă este mai mică == 1, atunci este mai mare.
2) Valorile inițiale:
pentru ultimul din interval (10): dp = 9 - ultimul dp = ultimul 3) Recalcularea dinamicii:
pentru prev în intervalul (10): dacă prev > last: dp[n] += dp dacă prev< last: dp[n] += dp 4) Порядок пересчёта: мы всегда обращаемся к предыдущей длине, так что просто пара вложенных for "ов.
5) Răspunsul este suma dp[N] .

Acum trebuie să venim cu un vector inițial și o matrice de tranziție către acesta. Vectorul pare să apară rapid: toate stările indică lungimea secvenței N . Ei bine, matricea de tranziție este derivată analizând formulele de conversie.

Vector de tranziție și matrice

Dinamica pe subsegmente

Aceasta este o clasă de dinamică în care starea este granițele unui subsegment al unei matrice. Ideea este să calculăm răspunsurile pentru subprobleme pe baza tuturor subsegmentelor posibile ale matricei noastre. De obicei, acestea sunt sortate în ordinea lungimii crescătoare, iar recalcularea se bazează, în consecință, pe segmente mai scurte.
Exemplul #4: împachetarea unui șir
Iată condiția extinsă. O voi rezuma pe scurt:

Să definim un șir comprimat:
1) Un șir format numai din litere este un șir comprimat. Ea se strânge în sine.
2) Un șir care este concatenarea a două șiruri comprimate A și B. Este decomprimat într-o concatenare de șiruri decomprimate A și B.
3) Șirul D(X) , unde D este un număr întreg mai mare decât 1 și X este un șir comprimat. Este decomprimat într-o concatenare de șiruri D decomprimate din X .
Exemplu: „3(2(A)2(B))C” se extinde la „AABBAABBAABBC” .

Este necesar să aflați din șirul s lungimea celui mai scurt șir comprimat care se decomprimă în el.

Soluţie

Această problemă este rezolvată, așa cum probabil ați ghicit deja, prin dinamica în subsegmente.

1) Stare dinamică: d[l][r] - șir comprimat de lungime minimă, decomprimat în șir s
2) Stări inițiale: toate subșirurile de lungime 1 pot fi comprimate numai în sine.
3) Recalcularea dinamicii:
Cel mai bun răspuns are câteva ultima operatie compresie: fie este doar un șir de la litere mari, sau este concatenarea a două șiruri, sau compresia în sine. Deci haideți să trecem prin toate opțiunile și să o alegem pe cea mai bună.

Dp_len = r - l dp[l][r] = dp_len # Prima opțiune de compresie este doar un șir. pentru i în intervalul (l + 1, r): dp[l][r] = min(dp[l][r], dp[l][i] + dp[i][r]) # Încercați să împărțiți la două subșiruri comprimate pentru cnt în interval (2, dp_len): if (dp_len % cnt == 0): # Dacă nu este divizibil, atunci nu are rost să încerci să împărțim bun = Adevărat pentru j în interval (1, ( dp_len / cnt) + 1 ): # Verificați dacă toate subșirurile cnt sunt aceleași bune &= s == s dacă sunt bune: # Încercați să împărțiți în cnt subșiruri identice și comprimați dp[l][r] = min(dp[l ][r], len (str(cnt)) + 1 + dp[l] + 1) 4) Ordinea recalculării: lungimea substring ascendentă directă sau dinamică leneșă.
5) Răspunsul se află în d.

Exemplul #5:

Dinamica prin subarbori

Parametrul de stare al dinamicii de-a lungul subarborilor este de obicei un vârf, care denotă subarborele în care acest vârf este rădăcina. Pentru a obține valoarea stării actuale, de obicei trebuie să cunoașteți rezultatele tuturor copiilor voștri. Cel mai adesea o implementează leneș - pur și simplu scriu o căutare în profunzime de la rădăcina copacului.
Exemplul #6: Arborele logic
Dat un copac agățat, ale cărui frunze conțin numere pe un singur bit - 0 sau 1. Toate vârfurile interne conțin și numere, dar după următoarea regulă: pentru fiecare vârf este selectată una dintre operațiile logice: „ȘI” sau „SAU”. Dacă este „ȘI”, atunci valoarea vârfului este un „ȘI” logic din valorile tuturor copiilor săi. Dacă „SAU”, atunci valoarea vârfului este un „SAU” logic din valorile tuturor copiilor săi.

Este necesar să găsiți numărul minim de modificări în operațiile logice în nodurile interne, astfel încât valoarea de la rădăcină să se schimbe sau să raportați că acest lucru este imposibil.

Soluţie

1) Stare dinamică: d[v][x] - numărul de operații necesare pentru a obține valoarea x la vârful v. Dacă acest lucru nu este posibil, atunci valoarea stării este +inf .
2) Valori inițiale: pentru frunze este evident că valoarea ta poate fi obținută în zero modificări, dar este imposibil să schimbi valoarea, adică se poate, dar numai cu operații +inf.
3) Formula de conversie:
Dacă acest vârf are deja o valoare x , atunci zero. Dacă nu, atunci există două opțiuni: schimbați sau nu operația la vârful curent. Pentru amândoi trebuie să găsiți cea mai buna varianta si alege-l pe cel mai bun.

Dacă operația este „ȘI” și trebuie să obțineți „0”, atunci răspunsul este minimul valorilor d[i] , unde i este fiul lui v.
Dacă operația este „ȘI” și doriți să obțineți „1”, atunci răspunsul este suma tuturor valorilor d[i] , unde i este fiul lui v.
Dacă operația este „SAU” și doriți să obțineți „0”, atunci răspunsul este suma tuturor valorilor d[i] , unde i este fiul lui v .
Dacă operația este „SAU” și trebuie să obțineți „1”, atunci răspunsul este minimul valorilor d[i] , unde i este fiul lui v .

4) Ordinea de recalculare: cel mai simplu mod de a o implementa este leneș - sub forma unei căutări în profunzime de la rădăcină.
5) Răspunsul este d xor 1].

Dinamica pe subseturi

În dinamica submulților, starea include de obicei o mască a unei mulțimi date. Ele sunt cel mai adesea sortate în ordinea creșterii numărului de unități din această mască și recalculate, în consecință, din stările care sunt mai mici în includere. Dinamica leneșă este de obicei folosită pentru a nu se gândi în mod specific la ordinea traversării, care uneori nu este complet banală.
Exemplul #7: Ciclul hamiltonian al greutății minime sau problema vânzătorului ambulant
Având în vedere un grafic ponderat (greutățile marginilor sunt nenegative) G de dimensiunea N . Găsiți un ciclu hamiltonian (un ciclu care trece prin toate vârfurile fără auto-intersecții) de greutate minimă.

Soluţie

Deoarece căutăm un ciclu care trece prin toate vârfurile, putem alege orice vârf ca vârf „inițial”. Fie acesta să fie numărul de vârf 0.

1) Starea dinamicii: dp[v] - calea de greutate minimă de la vârful 0 la vârful v, trecând prin toate vârfurile aflate în mască și numai prin acestea.
2) Valori inițiale: dp = 0, toate celelalte stări sunt inițial +inf.
3) Formula de conversie: Dacă al-lea bit din mască este 1 și există o margine de la i la v, atunci:
dp[v] = min(dp[v], dp[i] + w[i][v]) Unde w[i][v] este greutatea muchiei de la i la v .
4) Procedura de recalculare: cea mai simplă și mod convenabil- aceasta este pentru a scrie dinamica leneșă, dar puteți deveni creativ și puteți scrie o enumerare a măștilor în ordinea creșterii numărului de biți de unitate din ea.
5) Răspunsul se află în d[(1<< N) - 1] .

Dinamica după profil

Problemele clasice rezolvate prin dinamica profilului sunt probleme de placare a unui câmp cu unele figuri. Mai mult, pot fi cerute diferite lucruri, de exemplu, numărul de metode de placare sau placare cu un număr minim de cifre.

Aceste probleme pot fi rezolvate prin căutare exhaustivă în , unde a este numărul de opțiuni de tiling pentru o celulă. Dinamica după profil optimizează timpul de-a lungul uneia dintre dimensiuni la liniar, lăsând doar un coeficient în exponențial. Veți obține așa ceva: .

Un profil este reprezentat de k (adesea unul) stâlpi, care reprezintă limita dintre partea care a fost deja pavată și partea care nu a fost încă pavată. Acest chenar este doar parțial umplut. Foarte des face parte din starea dinamică.

Aproape întotdeauna statul este un profil și unde este acel profil. Iar tranziția mărește această locație cu unul. Puteți afla dacă este posibil să treceți de la un profil la altul într-un timp liniar față de dimensiunea profilului. Acest lucru poate fi verificat de fiecare dată în timpul recalculării, dar poate fi și precalculat. Vom precalcula o matrice bidimensională cutie - este posibil să trecem de la o mască la alta prin plasarea mai multor figuri, mărind poziția profilului cu una. Dacă precalculați, va dura mai puțin timp pentru finalizare și mai multă memorie.

Exemplul #8: Tigla de domino
Găsiți numărul de moduri de a plasa o masă N x M folosind piese de domino 1 x 2 și 2 x 1.

Soluţie

Aici profilul este o singură coloană. Este convenabil să-l stocați sub forma unei măști binare: 0 - o celulă până la coloană, 1 - o celulă cu gresie. Adică profiluri totale.

0) Precalcul (opțional): parcurgeți toate perechile de profiluri și verificați dacă puteți trece de la unul la altul. În această problemă, acest lucru este verificat astfel:

Dacă în primul profil există un 1 pe locul următor, atunci în al doilea trebuie să existe un 0, deoarece nu vom putea acoperi această celulă cu nicio cifră.

Dacă în primul profil există un 0 pe locul următor, atunci există două opțiuni - fie în al doilea este 0 sau 1.
Dacă 0, aceasta înseamnă că trebuie să plasăm un domino vertical, ceea ce înseamnă că următoarea celulă poate fi considerată 1. Dacă 1, atunci plasăm un domino vertical și trecem la următoarea celulă.

Exemple de tranziții (din profilul de sus puteți merge la cele de jos și numai acolo):

După aceea, salvați totul din matricea can - 1 dacă puteți merge, 0 dacă nu puteți.
1) Stare dinamică: dp - numărul de tilinguri complete ale primei poziții - 1 coloane cu profilul masca.
2) Stare inițială: dp = 1 - marginea stângă a câmpului - perete drept.
3) Formula de conversie:
dp += dp * poate
4) Ordinea parcurgerii este în ordine crescătoare a poz.
5) Răspunsul este în dp.

Asimptoticele rezultate sunt .

Dinamica de-a lungul unui profil rupt

Aceasta este o optimizare foarte puternică a dinamicii profilului. Aici profilul nu este doar o mască, ci și un punct de întrerupere. Arata cam asa:

Acum, după ce ați adăugat o pauză la profil, puteți trece la următoarea stare, adăugând doar o cifră care acoperă celula din stânga a pauzei. Adică, prin creșterea numărului de stări de N ori (trebuie să ne amintim unde este punctul de întrerupere), am redus numărul de tranziții de la o stare la alta de la la . Asimptoticele s-au îmbunătățit de la la .

Tranziții în dinamică de-a lungul unui profil rupt folosind exemplul unei probleme despre placarea cu domino (exemplul nr. 8):

Recuperare răspuns

Uneori se întâmplă ca simpla cunoaștere a unei caracteristici a celui mai bun răspuns să nu fie suficientă. De exemplu, în sarcina „Ambalare șiruri” (exemplul nr. 4), ajungem să obținem doar lungimea celui mai scurt șir comprimat, dar, cel mai probabil, nu avem nevoie de lungimea acestuia, ci de șirul în sine. În acest caz, trebuie să restabiliți răspunsul.

Fiecare problemă are propriul mod de a recupera răspunsul, dar cele mai frecvente sunt:

  • Stocați răspunsul complet la subsarcină lângă valoarea stării dinamicii. Dacă răspunsul este ceva mare, poate necesita prea multă memorie, așa că dacă se poate folosi o altă metodă, de obicei se face.
  • Reconstituiți răspunsul, cunoscând strămoșii stării date. Este adesea posibil să reconstruiți un răspuns știind doar cum a fost primit. În același „String Packing”, pentru a restabili răspunsul, puteți stoca doar tipul ultimei acțiuni și stările din care a fost primită.
  • Există o modalitate care nu utilizează deloc memoria suplimentară - după recalcularea dinamicii, mergeți de la capăt pe calea cea mai bună și compuneți răspunsul pe parcurs.

Mici optimizări

Memorie
Adesea în dinamică se poate întâlni o problemă în care o stare necesită numărarea unui număr mic de alte stări. De exemplu, atunci când calculăm numerele Fibonacci, le folosim doar pe ultimele două și nu ne întoarcem niciodată la cele anterioare. Asta înseamnă că poți să le uiți, adică să nu le păstrezi în memorie. Uneori, acest lucru îmbunătățește estimarea asimptotică din memorie. Această tehnică poate fi utilizată în exemplele nr. 1, nr. 2, nr. 3 (în soluția fără matrice de tranziție), nr. 7 și nr. 8. Adevărat, acest lucru nu poate fi folosit în niciun fel dacă ordinea de traversare este dinamică leneșă.
Timp
Uneori se întâmplă să puteți îmbunătăți timpul asimptotic folosind un fel de structură de date. De exemplu, în algoritmul lui Dijkstra, puteți utiliza o coadă de prioritate pentru a schimba timpul asimptotic.

Înlocuirea statului

În soluțiile prin dinamică apare neapărat o stare - parametri care definesc în mod unic subsarcina, dar această stare nu este neapărat singura. Uneori puteți veni cu alți parametri și puteți obține un beneficiu din acest lucru sub forma unei reduceri a timpului asimptotic sau a memoriei.
Exemplul #9: extinderea numărului
Trebuie să găsiți numărul de descompuneri ale numărului N în termeni diferiți. De exemplu, dacă N = 7, atunci există 5 astfel de expansiuni:
  • 3 + 4
  • 2 + 5
  • 1 + 7
  • 1 + 2 + 4

Printre problemele rezolvate folosind programarea matematică, se poate distinge o clasă separată de probleme care necesită optimizarea proceselor în mai multe etape (mai multe etape). Astfel de probleme se disting prin posibilitatea împărțirii soluției în mai multe etape interconectate. Pentru a rezolva astfel de probleme, se folosește programarea dinamică sau, așa cum se mai numește, programarea în mai multe etape. Metodele sale sunt optimizate pentru a găsi soluția optimă la problemele cu mai multe etape care pot fi împărțite în mai multe etape, pași etc.

Originea termenului

Folosirea cuvântului „dinamic” în nume a implicat inițial că împărțirea în subsarcini ar avea loc în primul rând în timp. Atunci când se utilizează metode dinamice pentru a rezolva probleme de producție, economice și de altă natură în care apare factorul timp, împărțirea acestuia în etape separate nu este dificilă. Dar este posibil să se utilizeze tehnici de programare dinamică în sarcini în care etapele individuale nu sunt legate în timp. Într-o problemă cu mai mulți pași, puteți selecta oricând un parametru sau o proprietate care poate fi folosită pentru a o împărți în pași separati.

Algoritm (metodă) pentru rezolvarea problemelor în mai multe etape

Algoritmul sau metoda de programare dinamică se bazează pe principiul optimizării secvențiale a unei probleme, atunci când soluția unei probleme generale este împărțită într-un număr de soluții la subprobleme individuale și apoi combinată într-o singură soluție. Foarte des, subproblemele individuale se dovedesc a fi aceleași, iar o soluție generală reduce semnificativ timpul de calcul.

O caracteristică a metodei este autonomia de rezolvare a problemei în fiecare etapă individuală, adică indiferent de modul în care procesul a fost optimizat și rezolvat în etapa anterioară, în calculul curent sunt utilizați doar parametrii procesului care o caracterizează în prezent. De exemplu, un șofer care se deplasează de-a lungul drumului ia o decizie cu privire la virajul curent, indiferent de cât și cât timp a condus înainte.

Metoda de sus și metoda de jos

În ciuda faptului că, atunci când se calculează într-o etapă separată de rezolvare a unei probleme, sunt utilizați parametrii procesului în momentul actual, rezultatul optimizării din etapa anterioară afectează calculele etapelor ulterioare pentru a obține cel mai bun rezultat în ansamblu. Programarea dinamică numește acest principiu de soluție metoda optimității, care determină ca strategia optimă de rezolvare a unei probleme, indiferent de deciziile și condițiile inițiale, trebuie urmată de decizii ulterioare în toate etapele pentru a crea o strategie optimă în raport cu starea inițială. După cum putem vedea, procesul de rezolvare a unei probleme este o optimizare continuă a rezultatului în fiecare etapă individuală de la prima până la ultima. Această metodă se numește metoda de programare de sus în jos. Figura prezintă schematic algoritmul soluției de sus în jos. Dar există o clasă de probleme cu mai multe etape în care efectul maxim la ultima etapă este deja cunoscut, de exemplu, am ajuns deja de la punctul A la punctul B și acum vrem să aflăm dacă am condus corect la fiecare precedent stadiu sau dacă ceva ar fi putut fi făcut mai optim. Apare o succesiune recursivă de etape, adică mergem, parcă, „din direcția opusă”. Această metodă de soluție se numește „metoda de programare de jos în sus”.

Uz practic

Programarea dinamică poate fi utilizată în orice domeniu de activitate în care există procese care pot fi împărțite într-un număr de etape mici identice în funcție de un anumit parametru (timp, cantitate, temperatură etc.). Metodele de soluție dinamică sunt cele mai utilizate în teoria controlului și în dezvoltarea sistemelor informatice.

Găsirea căii optime

Folosind optimizarea dinamică, este posibil să se rezolve o clasă largă de probleme de găsire sau optimizare a căii celei mai scurte și alte probleme în care metoda „clasică” de enumerare a posibilelor opțiuni de soluție duce la o creștere a timpului de calcul și uneori este complet inacceptabilă. Problema clasică de programare dinamică este problema rucsacului: dat fiind un anumit număr de obiecte cu o anumită masă și cost, și este necesar să se selecteze un set de obiecte cu costul și masa maximă care să nu depășească volumul rucsacului. O căutare clasică a tuturor opțiunilor în căutarea soluției optime va dura considerabil, dar cu ajutorul metodelor dinamice problema este rezolvată într-un interval de timp acceptabil. Problemele de găsire a celei mai scurte căi pentru logistica transportului sunt de bază, iar metodele de soluții dinamice sunt optim potrivite pentru rezolvarea acestora. Cel mai simplu exemplu al unei astfel de sarcini este construirea celei mai scurte rute folosind un navigator GPS pentru mașină.

Productie

Programarea dinamică este utilizată pe scară largă în rezolvarea unei varietăți de probleme de producție, cum ar fi gestionarea stocurilor pentru a menține numărul necesar de componente la un moment dat, programarea procesului de producție, rutina și revizia echipamentelor, volumul de lucru uniform al personalului, cea mai eficientă distribuție. a fondurilor de investiții etc. Pentru a rezolva problemele de producție folosind metode de programare dinamică, au fost dezvoltate pachete speciale de software care sunt integrate în sistemele populare de management al întreprinderilor, cum ar fi SAP.

Domeniul stiintific

Metodele de programare dinamică sunt utilizate pe scară largă în diverse cercetări științifice. De exemplu, sunt utilizați cu succes în algoritmii de recunoaștere a vorbirii și a imaginilor, atunci când procesează cantități mari de date în sociologie și

Bună, Habrakhabr. În prezent lucrez la un manual despre programarea olimpiadelor, unul dintre paragrafele căruia este dedicat programării dinamice. Mai jos este un extras din acest paragraf. Încercând să explic cât mai simplu acest subiect, am încercat să însoțesc momentele complexe cu ilustrații. Sunt interesat de părerea dumneavoastră despre cât de clar este acest material. De asemenea, aș fi bucuros să primesc sfaturi despre ce alte sarcini ar trebui incluse în această secțiune.

În multe probleme de programare la olimpiade, rezolvarea folosind recursiunea sau căutarea exhaustivă necesită efectuarea unui număr foarte mare de operații. O încercare de a rezolva astfel de probleme, de exemplu, prin căutare exhaustivă, duce la depășirea timpului de execuție.

Cu toate acestea, printre căutarea exhaustivă și unele alte probleme, putem distinge o clasă de probleme care au o singură proprietate bună: a avea soluții la unele subprobleme (de exemplu, pentru un număr mai mic n), puteți găsi o soluție la problema inițială aproape fără a căuta.

Astfel de probleme sunt rezolvate folosind metoda de programare dinamică, iar prin programarea dinamică în sine înțelegem reducerea problemei la subsarcini.

Secvențe

Problema secvenței clasice este următoarea.

Secvența Fibonacci F n este dat de formulele: F 1 = 1, F 2 = 1,
Fn = F n - 1 + F n- 2 la n> 1. Trebuie să găsiți F n după număr n.

O soluție care poate părea logică și eficientă este rezolvarea utilizând recursiunea:

Int F(int n) ( dacă (n< 2) return 1; else return F(n - 1) + F(n - 2); }
Folosind o astfel de funcție, vom rezolva problema „de la sfârșit” - vom reduce pas cu pas n până ajungem la valori cunoscute.

Dar după cum puteți vedea, așa aparent program simplu deja la n= 40 funcționează vizibil mult timp. Acest lucru se datorează faptului că aceleași date intermediare sunt calculate de mai multe ori - numărul de operații crește cu aceeași viteză cu creșterea numerelor Fibonacci - exponențial.

O cale de ieșire din această situație este salvarea deja găsită rezultate intermediareîn scopul reutilizării lor:

Int F(int n) ( dacă (A[n] != -1) returnează A[n]; dacă (n< 2) return 1; else { A[n] = F(n - 1) + F(n - 2); return A[n]; } }
Soluția dată este corectă și eficientă. Dar pentru această problemă se aplică și o soluție mai simplă:

F = 1; F = 1; pentru (i = 2; i< n; i++) F[i] = F + F;
Această soluție poate fi numită o soluție „de la început” - mai întâi completăm valorile cunoscute, apoi găsim prima valoare necunoscută ( F 3), apoi următorul etc., până ajungem la cel dorit.

Aceasta este exact soluția clasică pentru programarea dinamică: am rezolvat mai întâi toate subproblemele (am găsit toate F i Pentru i < n), apoi, cunoscând soluțiile subproblemelor, am găsit răspunsul ( F n = F n - 1 + F n - 2 , F n- 1 și F n- 2 au fost deja găsite).

Programare dinamică unidimensională

Pentru a înțelege mai bine esența programării dinamice, mai întâi definim mai formal conceptele de sarcină și subsarcină.

Fie ca problema inițială să fie găsirea unui anumit număr T cu datele inițiale n 1 , n 2 , ..., n k. Adică putem vorbi despre funcție T(n 1 , n 2 , ..., n k), a cărui valoare este răspunsul de care avem nevoie. Apoi vom considera sarcinile ca fiind subsarcini
T(i 1 , i 2 , ..., i k) la i 1 < n 1 , i 2 < n 2 , ..., i k < n k .

Următoarea problemă de programare dinamică unidimensională apare în diferite variații.

La n < 32 полный перебор потребует нескольких секунд, а при n= 64 căutarea exhaustivă nu este fezabilă în principiu. Pentru a rezolva problema folosind metoda de programare dinamică, reducem problema initiala la subsarcini.

La n = 1, n= 2 raspunsul este evident. Să presupunem că am găsit deja K n - 1 , K n- 2 — numărul de astfel de secvențe de lungime n- 1 și n - 2.

Să vedem cât poate fi lungimea secvenței n. Dacă ultimul său caracter este 0, atunci primul n- 1 — orice succesiune corectă de lungime
n- 1 (nu contează dacă se termină cu zero sau cu unu - 0 urmează). Există doar astfel de secvențe K n- 1 . Dacă ultimul caracter este egal cu 1, atunci penultimul caracter trebuie să fie egal cu 0 (altfel vor fi doi la rând), iar primul
n- 2 caractere - orice succesiune validă de lungime n- 2, numărul de astfel de secvențe este egal K n - 2 .

Prin urmare, K 1 = 2, K 2 = 3, K n = K n - 1 + K n- 2 la n> 2. Adică aceasta sarcina de fapt, se reduce la găsirea numerelor Fibonacci.

Programare dinamică 2D

O problemă clasică în programarea dinamică bidimensională este problema rutelor pe un câmp dreptunghiular.
În diferite formulări, trebuie să numărați numărul de rute sau să găsiți o rută care este cea mai bună într-un anumit sens.

Iată câteva formulări ale unor astfel de sarcini:

Sarcina 2. n*m celule. Puteți face pași dintr-o celulă spre dreapta sau în jos. Numărați câte moduri puteți ajunge din celula din stânga sus la celula din dreapta jos.

Sarcina 3. Având în vedere un câmp dreptunghiular de dimensiune n*m celule. Puteți face pași dintr-o celulă spre dreapta, în jos sau în diagonală spre dreapta și în jos. Fiecare celulă conține câteva numar natural. Trebuie să ajungeți din celula din stânga sus la celula din dreapta jos. Greutatea traseului este calculată ca suma numerelor din toate celulele vizitate. Este necesar să găsiți un traseu cu greutate minimă.

O trăsătură caracteristică a tuturor acestor probleme este că fiecare rută individuală nu poate trece prin aceeași celulă de două sau mai multe ori.

Să luăm în considerare sarcina 2 mai detaliat La o celulă cu coordonate (. i,j) poate veni doar de sus sau din stânga, adică din celule cu coordonate ( i - 1, j) Și ( i, j - 1):

Astfel, pentru o celulă ( i, j) numărul de rute A[i][j] va fi egal cu
A[j] + A[i], adică problema se reduce la două subsarcini. Această implementare folosește doi parametri − iȘi j- prin urmare, în raport cu această problemă vorbim de programare dinamică bidimensională.

Acum putem parcurge secvențial rândurile (sau coloanele) ale matricei A, găsind numărul de rute pentru celula curentă folosind formula de mai sus. Mai întâi trebuie să puneți numărul 1 în A.

În problema 3, într-o celulă cu coordonate ( i, j) putem obține din celule cu coordonate
(i- 1, j), ( i, j- 1) și ( i - 1, j- 1). Să presupunem că pentru fiecare dintre aceste trei celule am găsit deja traseul greutății minime și am plasat greutățile în sine în W[j], W[i],
W. Pentru a găsi greutatea minimă pentru ( i, j), trebuie să selectați minimul greutăților W[j], W[i], W și să adăugați la acesta numărul scris în celula curentă:

W[i][j] = min(W[j], W[i], W) + A[i][j];

Această sarcină este complicată de faptul că este necesar să se găsească nu numai greutatea minimă, ci și traseul în sine. Prin urmare, într-o altă matrice vom înregistra suplimentar pentru fiecare celulă din ce parte trebuie să o introducem.

Figura următoare prezintă un exemplu de date inițiale și unul dintre pașii algoritmului.

Exact o săgeată duce la fiecare dintre celulele deja trecute. Această săgeată arată din ce parte trebuie să veniți la această celulă pentru a obține greutatea minimă înregistrată în celulă.

După ce treceți întreaga matrice, va trebui să urmăriți traseul în sine din ultima celulă, urmând săgețile în direcția opusă.

Probleme ulterioare

Luați în considerare problema ulterioară în creștere.

Sarcina 4. Dată o succesiune de numere întregi. Este necesar să se găsească cea mai lungă succesiune strict crescătoare.

Să începem să rezolvăm problema de la început - vom căuta răspunsul, pornind de la primii termeni ai acestei secvențe. Pentru fiecare număr i vom căuta cea mai mare subsecvență crescătoare care se termină cu un element în poziție i. Lasă secvența originală să fie stocată în tabloul A. În tabloul L vom înregistra lungimile subsecvențelor maxime care se termină cu elementul curent. Să găsim toate L[i] pentru 1<= i <= k- 1. Acum puteți găsi L[k] după cum urmează. Căutăm prin toate elementele lui A[i] pentru 1<= i < k- 1. Dacă
A[i]< A[k], то k Al-lea element poate deveni o continuare a subsecvenței care se termină cu elementul A[i]. Lungimea subsecvenței rezultate va fi cu 1 mai mare decât L[i]. Pentru a găsi L[k], trebuie să parcurgeți toate i de la 1 la k - 1:
L[k] = max(L[i]) + 1, unde maximul este luat peste toate i astfel încât A[i]< A[k] и
1 <= i < k.

Aici, maximul setului gol va fi considerat egal cu 0. În acest caz, elementul curent va deveni singurul din secvența selectată și nu va fi o continuare a unuia dintre cele anterioare. După completarea matricei L, lungimea celei mai mari subsecvențe crescătoare va fi egală cu elementul maxim L.

Pentru a restabili subsecvența în sine, puteți stoca și numărul elementului selectat anterior pentru fiecare element, de exemplu, într-o matrice N.

Să luăm în considerare soluția acestei probleme folosind exemplul șirului 2, 8, 5, 9, 12, 6. Deoarece nu există niciun element înainte de 2, subsecvența maximă conține un singur element - L = 1 și nu există niciun element înainte de 2. it - N = 0. Mai mult,
2 < 8, поэтому 8 может стать продолжением последовательности с предыдущим элементом. Тогда L = 2, N = 1.

Singurul element mai mic decât A = 5 este A = 2, deci 5 poate deveni o continuare a unei singure subsecvențe - cea care conține 2. Atunci
L = L + 1 = 2, N = 1, deoarece 2 este în poziția numărul 1. În mod similar, mai efectuăm trei pași ai algoritmului și obținem rezultatul final.

Acum selectăm elementul maxim din tabloul L și din tabloul N reconstruim subsecvența în sine 2, 5, 9, 12.

O altă problemă clasică de programare dinamică este problema palindromului.

Sarcina 5. Dat un șir de majuscule ale alfabetului latin. Trebuie să găsiți lungimea celui mai mare palindrom care poate fi obținut prin tăierea unor litere dintr-un șir dat.

Să notăm acest șir cu S și simbolurile sale cu S[i], 1<= i <= n. Vom lua în considerare posibilele subșiruri ale acestui șir cu i th j al-lea simbol, le notăm prin S(i, j). Vom scrie lungimile palindromurilor maxime pentru subșiruri într-o matrice pătrată L: L[i][j] este lungimea palindromului maxim care poate fi obținut din subșir. S(i, j).

Să începem să rezolvăm problema cu cele mai simple subșiruri. Pentru un singur șir de caractere (adică un subșir al formularului S(i, i)) răspunsul este evident - nu este nevoie să taiți nimic, un astfel de șir va fi un palindrom. Pentru un șir de două caractere S(i, i+ 1) sunt posibile două opțiuni: dacă caracterele sunt egale, atunci avem un palindrom, nu este nevoie să tăiați nimic. Dacă personajele nu sunt egale, atunci tăiați pe oricare.

Să ni se dea acum un subșir S(i, j). Dacă primul (S[i]) și ultimul (S[j]) caractere ale subșirului nu se potrivesc, atunci unul dintre ele trebuie șters. Apoi rămânem cu subșirul S(i, j- 1) sau S(i + 1, j) - adică reducem problema la o subsarcină: L[i][j] = max(L[i], L[j]). Dacă primul și ultimul caracter sunt egale, atunci le putem lăsa pe amândouă, dar trebuie să știm soluția problemei S(i + 1, j - 1):
L[i][j] = L + 2.

Să ne uităm la soluția folosind șirul ABACCBA ca exemplu. În primul rând, umplem diagonala matricei cu unități, acestea vor corespunde subșirurilor S(i, i) dintr-un personaj. Apoi începem să ne uităm la subșiruri de lungime doi. În toate subșirurile cu excepția S(4, 5), simbolurile sunt diferite, așa că scriem 1 în celulele corespunzătoare și 2 în L.

Se pare că vom umple matricea în diagonală, începând cu diagonala principală care duce din colțul din stânga sus spre dreapta jos. Pentru subșiruri de lungime 3, se obțin următoarele valori: într-un subșir ABA, prima și ultima literă sunt egale, deci
L = L + 2. În subșirurile rămase, prima și ultima literă sunt diferite.

BAC: L = max(L, L) = 1.
ACC: L = max(L, L) = 2.
CCB: L = max(L, L) = 2.
CBA: L = max(L, L) = 1.

Dacă în sarcină este necesar să scoatem nu lungimea, ci palindromul în sine, atunci, pe lângă matricea de lungimi, trebuie să construim o matrice de tranziții - pentru fiecare celulă, amintiți-vă care dintre cazuri a fost implementat (pentru claritate, în figură, în loc de valorile numerice care codifică tranzițiile, sunt desenate săgețile corespunzătoare) .

  • Esența metodei de programare dinamică……………..4

  • Un exemplu de rezolvare a unei probleme utilizând metoda de programare dinamică……………………………………………………………..7

    Lista surselor utilizate……………………………………………...11

    1. Programare dinamică. Noțiuni de bază.

    Programare dinamică (DP)în teoria sistemelor informatice, o modalitate de a rezolva probleme complexe prin împărțirea lor în subsarcini mai simple. Se aplică problemelor cu substructură optimă, care arată ca un set de subprobleme suprapuse a căror complexitate este puțin mai mică decât cea inițială. În acest caz, timpul de calcul, în comparație cu metodele „naive”, poate fi redus semnificativ.

    Ideea cheie în programarea dinamică este destul de simplă. De regulă, pentru a rezolva o anumită problemă, este necesar să se rezolve părți individuale ale problemei (subsarcini) și apoi să se combine soluțiile subsarcinilor într-o singură soluție generală. Adesea, multe dintre aceste subsarcini sunt aceleași. Abordarea de programare dinamică este de a rezolva fiecare subproblemă o singură dată, reducând astfel numărul de calcule. Acest lucru este util în special în cazurile în care numărul de subsarcini repetate este exponențial mare.

    Programarea dinamică este o tehnică matematică care abordează rezolvarea unei anumite clase de probleme, împărțindu-le în părți, probleme mai mici și mai puțin complexe. Totodată, o trăsătură distinctivă este soluţionarea problemelor în etape, la intervale fixe, perioade de timp, care au determinat apariţia termenului de programare dinamică. De remarcat că metodele de programare dinamică sunt folosite cu succes și la rezolvarea problemelor în care factorul timp nu este luat în considerare. În general, aparatul matematic poate fi reprezentat ca programare pas cu pas sau pas cu pas. Rezolvarea problemelor prin metode de programare dinamică se realizează pe baza principiului optimității formulat de R. E. Bellman: comportamentul optim are proprietatea că oricare ar fi starea inițială a sistemului și soluția inițială, soluția ulterioară trebuie să determine comportamentul optim. raportat la starea obţinută ca urmare a soluţiei iniţiale. De aici rezultă că planificarea fiecărei etape trebuie efectuată ținând cont de beneficiul general obținut la finalizarea întregului proces, ceea ce permite optimizarea rezultatului final în funcție de criteriul selectat.

    Astfel, programarea dinamică în sens larg este controlul optim al unui proces prin modificarea parametrilor controlați la fiecare pas, și, prin urmare, influențând progresul procesului, modificând starea sistemului la fiecare pas. În general, programarea dinamică este o teorie armonioasă de înțeles și suficient de simplă pentru a fi utilizată în activități comerciale atunci când se rezolvă probleme atât liniare, cât și neliniare.

    Programarea dinamică este una dintre ramurile programării optime. Se caracterizează prin metode și tehnici specifice aplicate operațiunilor în care procesul decizional este împărțit în etape (etape). Metodele de programare dinamică sunt utilizate pentru rezolvarea problemelor de optimizare a variantelor cu criterii de optimitate date, cu anumite legături între variabile și funcția obiectiv, exprimate printr-un sistem de ecuații sau inegalități. În acest caz, ca și în problemele rezolvate prin metode de programare liniară, restricțiile pot fi date sub formă de egalități sau inegalități. Totuși, dacă în problemele de programare liniară dependențele dintre funcția de criteriu și variabile sunt în mod necesar liniare, atunci în problemele de programare dinamică aceste dependențe pot fi și neliniare. Programarea dinamică poate fi utilizată atât pentru rezolvarea problemelor asociate cu dinamica unui proces sau sistem, cât și pentru probleme statice asociate, de exemplu, cu alocarea resurselor. Acest lucru extinde semnificativ domeniul de aplicare al programării dinamice pentru rezolvarea problemelor de control. Iar posibilitatea simplificării procesului de soluționare, care se realizează prin limitarea ariei și numărului examinate la trecerea la următoarea etapă de opțiuni, crește avantajele acestui set de metode.

    Cu toate acestea, programarea dinamică are și dezavantaje. În primul rând, nu există o metodă unică de soluție universală. Aproape fiecare problemă rezolvată prin această metodă se caracterizează prin propriile caracteristici și necesită căutarea celui mai potrivit set de metode pentru a o rezolva. În plus, volumele mari și complexitatea rezolvării problemelor în mai multe etape cu multe stări duc la necesitatea de a selecta probleme de dimensiuni reduse sau de a utiliza informații comprimate. Acesta din urmă se realizează folosind metode de analiză a opțiunilor și procesarea listei de stări.

    Pentru procesele în timp continuu, programarea dinamică este considerată o versiune limitativă a unei scheme de soluții discrete. Rezultatele obţinute în acest caz coincid practic cu cele obţinute prin metodele maxime ale lui L. S. Pontryagin sau Hamilton-Jacobi-Bellman.

    Programarea dinamică este utilizată pentru a rezolva probleme în care căutarea unui optim este posibilă printr-o abordare pas cu pas, de exemplu, distribuția investițiilor de capital limitate între noi domenii de utilizare a acestora; elaborarea regulilor de gestionare a cererii sau a stocurilor care stabilesc momentul reaprovizionării și mărimea comenzii de reaprovizionare; elaborarea principiilor de programare a producției și de egalizare a ocupării forței de muncă în condiții de fluctuație a cererii de produse; întocmirea planurilor calendaristice pentru reparațiile curente și majore ale echipamentelor și înlocuirea acestora; cauta cele mai scurte distante de pe reteaua de transport; formarea secvenței de desfășurare a unei operațiuni comerciale etc.