Principiul optimalității
Programarea dinamică în
problemele NP-Complete
Mihai Oltean
Pentru a aplica metoda programării dinamice este necesar
să putem descompune problema inițială într-o serie de subprobleme, incluse
unele în altele. Pornim apoi, cu calculul, de la subproblemele cele mai
simple (care nu au nici o subproblemă în subordine) către subproblemele
cele mai complexe (care se descompun direct, sau indirect) în tot mai
multe subprobleme. În majoritatea cazurilor întâlnite, numărul subproblemelor
este polinomial. Numărul subproblemelor în care se descompune problema
inițială este de data aceasta exponențial.
Drum hamiltonian
Un comis-voiajor trebuie să viziteze o singură dată fiecare
din cele n+1 orașe, plecând dintr-un oraș oarecare și întorcându-se
tot în acel oraș. Între oricare două orașe există un drum direct care
are atașat un cost numit distanță. Care drum minimizează distanța totală
parcursă de comis-voiajor?
Rezolvare
Numerotăm orașele cu numere distincte din intervalul [0,
n].
Fără a pierde din generalitate, deoarece fiecare tur este
ciclic, putem fixa originea în orașul 0. Presupunem că la un anumit pas
al unui ciclu optim, care începe cu orașul 0, s-a ajuns la orașul i
și au mai rămas k orașe j1, j2,
..., jk nevizitate înainte de a ne întoarce în 0. Este
clar că, deoarece turul este optimal, acel drum care pornește din orașul
i, trece prin orașele j1, j2,
..., jk și se întoarce în 0, trebuie să fie optim (să
aibă lungime minimă), deoarece în caz contrar întregul ciclu nu ar mai
fi optim, deoarece s-ar putea alege o cale mai scurtă de la i astfel
încât să treacă prin orașele j1, j2,
..., jk, iar apoi să revină în 0.
Să definim deci:
c[i, j1, j2,
..., jk] = lungimea unui drum minim între orașul i
și orașul 0 care trece o singură dată prin fiecare din orașele nevizitate
j1, j2, ..., jk.
Soluția problemei se va afla în c[0, j1,
j2, ..., jN].
Să notăm cu d[i,j] distanțele directe
dintre două orașe i și j. Atunci, ca o consecință a observației
anterioare avem:
Această formulă este o aplicație directă a principiului
optimalității.
Pentru calcularea acestei formule începem cu:
c[i, j] = d[i, j]
+ d[j, 0],
iar apoi calculăm pe c[i, j1,
j2], apoi pe c[i, j1,
j2, j3] etc., iar în final pe c[0,
j1, j2, ..., jN].
Secvența de valori jm care minimizează
expresia de mai sus, conduce la drumul minim.
Să vedem cum implementăm aceste formule într-o complexitate
cât mai redusă. Cum se vede și din formula optimalității, numărul de parametri
ai funcției c este mare (în cel mai defavorabil caz N+1).
Dar, deoarece numerele j1, j2, ...,jk
nu trebuie stocate în ordine, pentru reținerea lor nu este nevoie de un
șir de lungime k, ci de o mulțime având cardinalitatea k.
Se știe că mulțimile în Turbo Pascal sunt
reprezentate la nivel de bit, deci în loc să avem o reprezentare a numerelor
j1, j2, ..., jk
pe k octeți, vom avea nevoie de doar [k/8]+1 octeți.
Nici codificarea sub formă de mulțimi nu este foarte economă
din punct de vedere al memoriei ocupate. Orașele sunt etichetate cu numere
întregi din intervalul [0, n]. La pasul unu (inițial) trebuie să
calculăm numerele c[i, j1] care sunt în
număr de N. La pasul 2 trebuie să calculăm numerele c[i,
j1, j2] care sunt în număr de , ...,
la un pas k trebuie să calculăm numerele c[i, j1,
j2, ..., jk] care sunt în număr de
pentru un i fixat. Submulțimile j1, j2,
..., jk ale unei mulțimi de N elemente sunt în
număr de . Acestea se pot genera în ordine lexicografică
(ca și în cartea de telefon). Mai mult decât atât, dându-se o submulțime
având cardinalitatea k a unei mulțimi de cardinal N, se
poate obține direct numărul ei de ordine lexicografică. De exemplu, submulțimea
{1, 2, ..., k} are numărul de ordine lexicografică
1, iar submulțimea {N-k+1, ..., N} are numărul
de ordine lexicografică . Procedeul este valabil și invers, adică, dându-se
un număr de ordine lexicografică se poate genera direct (printr-un algoritm
de complexitate O(k)) submulțimea corespunzătoare de cardinalitate
k.
Deci, revenind la problema noastră, nu este nevoie de reținerea
întregii submulțimi ci doar de un singur număr care să indice numărul
de ordine lexicografică. Cel mai mare număr de ordine lexicografică este
acela pentru care k este jumătate din N.
Pentru a putea rezolva problema pentru un număr cât mai
mare de orașe, stocarea acestei matrice se va face în Heap, adică
accesul se va face în genul c[i]^[j]. Deoarece în
momentul în care stocăm funcția c[i, j1,
j2, ..., jk], trebuie să reținem și
cardinalul mulțimii {j1, j2, ...,
jk}; la impementare vom avea de a face cu încă un parametru:
lungimea combinării. Deci matricea în care vom construi soluția are trei
parametri c[i, k]^[w], unde i are semnificația
de mai sus, k este lungimea combinării, iar w este numărul
de ordine lexicografică al combinării (j1, j2,
..., jk) în cadrul mulțimii combinărilor de n
elemente luate câte k.
Complexitatea va fi în acest caz de ordinul O(N32N),
dacă facem abstracție de determinarea numărului de ordine lexicografică.
Programul care calculează drumul de cost minim este următorul.
Listing HAM.PAS
Reconstituirea soluției se face fie prin stocarea într-o
altă matrice a poziției de proveniență a minimului nume-relor c[i]^[j],
fie prin reconstituirea (de data aceasta de sus în jos) a soluției optime.
Listing procedura Reconstituie
Ieșire
Drumul minim are lungimea: 29
Drumul este: 0 1 3 2 4 0
Intrare
30 115 10 1115 0 6 2010 6 0 21 20 2 0
Ieșire
Drumul minim are lungimea: 37
Drumul este: 0 2 1 3 0
Putem reduce cantitatea de memorie folosită, deoarece știm
că numerele c[i, k], depind doar de numerele c[i,
k-1], adică doar de submulțimile de cardinal k-1 a mulțimii
numerelor j1, j2, ..., jk.
În locul matricei c vom avea acum doi vectori c1
și c2.
c1,c2:array[0..MaxN] of ^TVectorDinamic;
Noua procedură Calcul este:
Listing procedura Calcul
În acest caz, reconstituirea soluției nu se mai face așa
de simplu, ea necesitând refacerea matricei c.
Se presupune că nu între toate orașele există legături
directe. Determinați în acest caz formulele de calcul ale numerelor c[i,
j1, ..., jk] și implementați într-un
program ceea ce ați obținut. Calculați drumul elementar de lungime maximă
dintre două noduri ale acestui graf.
Diferență minimă
Se dă un șir de n cifre (n par). Se cere ca
din cifrele date să se formeze două numere (fiecare de lungime [n/2])
astfel încât diferența dintre ele să fie minimă.
Restricții
Datele de intrare se citesc dintr-un fișier text având următoarea
structură:
- pe prima linie se află numărul n (par);
- pe următoarea linie se dau cele n cifre, despărțite
printr-un spațiu.
Soluție
Presupunem că la un anumit pas al unei soluții optime, punem
a i-a cifră (din șirul de cifre dat) la primul număr și a j-a
cifră la cel de al doilea număr și au mai rămas 2k cifre j1,
j2, ..., j2k nefolosite. Construirea
celor două numere se face în paralel (deoarece au și lungimi egale), începând
cu cifra cea mai semnificativă și continuând spre cifra cea mai nesemnificativă.
Este clar că, deoarece diferența dintre numere este în
final minimă, trebuie ca diferența dintre numerele care au cifrele cele
mai semnificative a i-a, respectiv a j-a și care mai folosesc
cifrele j1, j2, ..., j2k,
să fie de asemenea minimă, deoarece în caz contrar întreaga diferență
nu ar mai fi optimă pentru că s-ar putea face o altă distribuție mai bună
a numerelor j1, j2, ..., j2k.
Să definim deci:
c[i, j, j1, j2,
..., j2k] = diferența dintre două numere care încep
cu a i-a respectiv a j-a cifră (dintre cele date) și care
mai folosesc și cifrele j1, j2, ...,
j2k.
Soluția problemei se va afla printre numerele c[i,
j, j1, j2, ..., jN-2].
Fie a vectorul în care am citit cifrele de la intrare.
Avem:
Această formulă este o aplicație directă a principiului
optimalității.
Pentru calcularea acestei formule începem cu:
c[i, j]=a[i]-a[j],
iar apoi calculăm pe c[i, j, j1,
j2], apoi pe c[i, j, j1,
j2, j3, j4] etc.
Secvența de valori (jp, jq)
care minimizează expresia de mai sus conduce la soluția optimă.
Compexitatea acestui algoritm este O(N52N).
Listing IMPART.PAS
Intrare Ieșire
4 Diferenta minima: 1
1 1 9 2 Primul numar: 19
Al doilea numar: 21
Intrare Ieșire
6 Diferenta minima: 2
9 9 2 0 1 1 Primul numar: 201
Al doilea numar: 199
Ca și în cazul problemei drumului hamiltonian se poate
implementa un program care să nu mai necesite parametrul k în matricea
c. În acest caz reconstituirea numerelor care au generat această
soluție s-ar face mai greu.
Concluzie
Ceea ce am propus în aceste pagini este o modalitate specială
de a rezolva problemele NP-complete (sau chiar unele probleme polinomiale
despre care nu suntem siguri că algoritmul propus este optim) folosind
metoda programării dinamice. Ideea algoritmului este identică în ambele
probleme rezolvate mai sus, de aceea, cu ajutorul ei se pot soluționa
o serie de probleme având ordin de complexitate exponențial.
Bibliografie
1. Colecția Journal of the ACM
2. Mihai Oltean, Proiectarea și implementarea
algoritmilor, Editura Computer Libris Agora, Cluj-Napoca, 1999.
Mihai Oltean este doctorand al Universității "Babeș-Bolyai"
din Cluj și poate fi contactat pe adresa moltean@cs.ubbcluj.ro.
[cuprins]
|