Cuprins 1 Introducere ˆın algoritmi 3 1.1 Limbajul pseudocod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.3 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2 Elemente de analiza algoritmilor 2.1 Metoda substitut¸iei . . . . . . . 2.2 Schimbarea de variabil˘a . . . . 2.3 Metoda iterativ˘a . . . . . . . . 2.4 Teorema master . . . . . . . . . 2.5 Exercit¸ii . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
3 Grafuri. Grafuri neorientate 3.1 Not¸iuni de baz˘a . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Operat¸ii pe grafuri . . . . . . . . . . . . . . . . . . . . . . 3.3 Moduri de reprezentare . . . . . . . . . . . . . . . . . . . . 3.4 Parcurgerea grafurilor . . . . . . . . . . . . . . . . . . . . 3.4.1 Parcurgerea ˆın l˘a¸time (BF-Breadth-First) . . . . . . 3.4.2 Parcurgerea D (D - Depth) . . . . . . . . . . . . . 3.4.3 Parcurgerea ˆın adˆancime (DFS-Depth First Search) 3.5 Componente conexe . . . . . . . . . . . . . . . . . . . . . . 3.6 Muchie critic˘a . . . . . . . . . . . . . . . . . . . . . . . . . 3.7 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Grafuri euleriene ¸si hamiltoniene 4.1 Grafuri Euleriene . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Algoritm pentru determinarea unui ciclu eulerian 4.1.2 Algoritmul lui Rosenstiehl . . . . . . . . . . . . . 4.1.3 Algoritmul lui Fleury . . . . . . . . . . . . . . . . 4.2 Grafuri Hamiltoniene . . . . . . . . . . . . . . . . . . . . 4.2.1 Problema comis–voiajorului . . . . . . . . . . . . 5 Arbori. Arbori binari 5.1 Arbori binari . . . . . . . . . 5.1.1 Moduri de reprezentare 5.1.2 Metode de parcurgere . 5.2 Arbori binari de c˘autare . . . 5.3 Exercit¸ii . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
1
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
23 27 27 28 29 31
. . . . . . . . . .
34 34 39 42 46 46 50 51 55 56 61
. . . . . .
64 64 65 68 72 74 76
. . . . .
87 88 89 91 97 105
6 Arbori oarecare 6.1 Moduri de reprezentare . . . . . . . . . . . . . . . 6.2 Metode de parcurgere . . . . . . . . . . . . . . . . 6.3 Arbori de acoperire de cost minim . . . . . . . . . 6.3.1 Algoritmul lui Boruvka . . . . . . . . . . . 6.3.2 Algoritmul lui Prim . . . . . . . . . . . . . 6.3.3 Structuri de date pentru mult¸imi disjuncte 6.3.4 Algoritmul lui Kruskal . . . . . . . . . . . 6.4 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . 7 Grafuri orientate 7.1 Not¸iuni de baz˘a . . . . . . . . . 7.2 Parcurgerea grafurilor . . . . . 7.3 Sortarea topologic˘a . . . . . . . 7.4 Componente tare conexe . . . . 7.4.1 Algoritmul lui Kosaraju 7.4.2 Algoritmul lui Tarjan . . 7.4.3 Algoritmul lui Gabow . 7.5 Exercit¸ii . . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
8 Distant¸e ˆın grafuri 8.1 Drumul minim de la un vˆarf la celelalte vˆarfuri . 8.1.1 Algoritmul lui Moore . . . . . . . . . . . 8.1.2 Algoritmul lui Dijkstra . . . . . . . . . . 8.2 Drumuri minime ˆıntre toate perechile de vˆarfuri 8.2.1 Algoritmul lui Roy-Floyd-Warshall . . . 8.3 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
9 Fluxuri ˆın ret¸ele de transport 9.1 Ret¸ea de transport. Flux. T˘aietur˘a . . . . . . . . . . . . 9.2 Graf rezidual. Drum de ameliorare. Flux maxim–t˘aietur˘a 9.3 Metoda Ford-Fulkerson . . . . . . . . . . . . . . . . . . . 9.3.1 Algoritmul Ford-Fulkerson (variant˘a) . . . . . . . 9.4 Exercit¸ii . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . minim˘a . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . .
109 . 109 . 112 . 117 . 119 . 120 . 123 . 128 . 131
. . . . . . . .
134 . 134 . 136 . 139 . 143 . 144 . 147 . 150 . 151
. . . . . .
156 . 157 . 157 . 158 . 166 . 166 . 170
. . . . .
174 . 174 . 178 . 180 . 182 . 186
A Probleme. Algoritmi. Complexitate 188 A.0.1 Verificare ˆın timp polinomial . . . . . . . . . . . . . . . . . . . . . . . . 190 A.0.2 Reduceri polinomiale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 B Metoda Backtracking
193
2
Capitolul 1 Introducere ˆın algoritmi Definit¸ia 1.1 Algoritmul constituie o reprezentare finit˘ a a unei metode de calcul ce permite rezolvarea unei anumite probleme. Se poate spune c˘ a un algoritm reprezint˘ a o secvent¸˘a finit˘a de operat¸ii, ordonat˘a ¸si complet definit˘ a, care, pornind de la datele de intrare, produce rezultate. Termenul de algoritm ˆıi este atribuit matematicianului persan Abu Ja‘far Mohammed ibn Musa al-Khowarizmi (sec. VIII-IX), care a scris o carte de matematic˘a cunoscut˘a ˆın traducere latin˘a sub titlul de Algorithmi de numero indorum, iar apoi ca Liber algorithmi, unde algorithm provine de la al-Khowarizmi, ceea ce literal ˆınseamn˘a din orasul Khowarizm. Matematicienii din Evul Mediu ˆınt¸elegeau prin algoritm o regul˘a (sau o mult¸ime de reguli) pe baza c˘areia se efectuau calcule aritmetice: de exemplu ˆın secolul al XVI-lea, algoritmii se foloseau la ˆınmult¸iri sau ˆınjum˘at˘a¸tiri de numere. Fiecare propozit¸ie ce face parte din descrierea unui algoritm este de fapt o comand˘a ce trebuie executat˘a de cineva, acesta putˆand fi o persoan˘a sau o ma¸sin˘a de calcul. De altfel, un algoritm poate fi descris cu ajutorul oric˘arui limbaj, de la limbajul natural ¸si pˆan˘a la limbajul de asamblare al unui calculator. Denumim limbaj algoritmic un limbaj al c˘arui scop este acela de a descrie algoritmi. Algoritmul specific˘a succesiuni posibile de transform˘ari ale datelor. Un tip de date poate fi caracterizat printr-o mult¸ime de valori ce reprezint˘a domeniul tipului de date ¸si o mult¸ime de operat¸ii definite peste acest domeniu. Tipurile de date pot fi organizate ˆın urm˘atoarele categorii: 1. tipuri de date elementare (de exemplu tipul ˆıntreg, tipul real ) - valorile sunt unit˘a¸ti atomice de informat¸ie; 2. tipuri de date structurate (de exemplu tipul tablou, tipul ˆınregistrare) - valorile sunt structuri relativ simple rezultate ˆın urma combinat¸iei unor valori elementare; 3. tipuri de date structurate de nivel ˆınalt (de exemplu stiva) - se pot descrie independent de limbaj iar valorile au o structur˘a mai complex˘a. Un algoritm trebuie s˘a posede urm˘atoarele tr˘ as˘ aturi caracteristice: 1. claritate - la fiecare pas trebuie s˘a specifice operat¸ia pe care urmeaz˘a s˘a o efectueze algoritmul asupra datelor de intrare; 2. corectitudine - rezultatele trebuie s˘a fie corecte;
3
3. generalitate - algoritmul trebuie s˘a ofere solut¸ia nu numai pentru o singur˘a problem˘a ci pentru o ˆıntreag˘a clas˘a de probleme; 4. finitudine - algoritmul trebuie s˘a se termine ˆıntr-un timp finit; 5. eficient¸˘a - un algoritm poate fi utilizat numai ˆın situat¸ia ˆın care resursele de calcul necesare acestuia sunt ˆın cantit˘a¸ti rezonabile, ¸si nu dep˘a¸sesc cu mult posibilit˘a¸tile calculatoarelor la un moment dat. Un program reprezint˘a implementarea unui algoritm ˆıntr-un limbaj de programare. Studiul algoritmilor cuprinde mai multe aspecte: • elaborare - activitatea de concepere a unui algoritm are ¸si un caracter creativ, din aceast˘a cauz˘a nefiind posibil˘a deprinderea numai pe cale mecanic˘a. Pentru a facilita obt¸inerea unei solut¸ii la o problem˘a concret˘a se recomand˘a folosirea tehnicilor generale de elaborare a algoritmilor la care se adaug˘a ˆın mod hot˘arˆator intuit¸ia programatorului; • exprimare - implementarea unui algoritm intr-un limbaj de programare se poate face utilizˆand mai multe stiluri: programare structurat˘ a, programare orientat˘ a pe obiecte etc.; • validare - verificarea corectitudinii algoritmului prin metode formale; • analiz˘a - stabilirea unor criterii pentru evaluarea eficient¸ei unui algoritm pentru a-i putea compara ¸si clasifica. Un model de reprezentare al memoriei unei ma¸sini de calcul este acela al unei structuri liniare compus˘a din celule, fiecare celul˘a fiind identificat˘a printr-o adres˘a ¸si putˆand p˘astra o valoare corespunz˘atoare unui anumit tip de dat˘a. Accesul la celule este facilitat de variabile. O variabil˘a se caracterizeaz˘a prin: • un identificator - un nume ce refer˘a variabila; • o adres˘a - desemneaz˘a o locat¸ie de memorie; • un tip de date - descrie tipul valorilor memorate ˆın celula de memorie asociat˘a.
1.1
Limbajul pseudocod
Limbajul natural nu permite o descriere suficient de riguroas˘a a algoritmilor, de aceea, pentru reprezentarea acestora se folosesc alte modalit˘a¸ti de descriere precum: • scheme logice; • limbajul pseudocod.
ˆIn continuare vom prezenta pricipalele construct¸ii din cadrul limbajului pseudocod. Intr˘ ari/ie¸siri Citirea datelor de intrare se poate realiza prin intermediul enunt¸ului Input: 1: Input {lista variabile} Afi¸sarea rezultatelor este reprezentat˘a cu ajutorul instruct¸iunii Output: 1: Output {lista de valori} 4
Instruct¸iunea de atribuire Este instruct¸iunea cel mai des utilizat˘a ˆıntr-un algoritm ¸si realizeaz˘a ˆınc˘arcarea unei variabile (locat¸ii de memorie) cu o anumit˘a valoare. Are urm˘atoarea sintax˘a: 1: < variabila >←< expresie >
unde < expresie > este o expresie aritmetic˘a sau logic˘a. Se evalueaz˘a expresia < expresie > iar rezultatul se atribuie variabilei < variabila >, memorˆandu-se ˆın locat¸ia de memorie asociat˘a. Aceast˘a variabil˘a trebuie s˘a fie de acela¸si tip de dat˘a cu expresia sau un tip de dat˘a care s˘a includ˘a ¸si tipul expresiei. O expresie este constituit˘a din operanzi ¸si operatori. Operanzii pot fi variabile ¸si valori constante, iar operatorii pot fi: • operatori aritmetici - + (adunare), − (sc˘adere), ∗ (ˆınmult¸ire), / (ˆımp˘art¸ire), ˆ (ridicare la putere), div (cˆatul ˆımp˘art¸irii ˆıntregi), mod (restul ˆımp˘art¸irii ˆıntregi); • operatori relat¸ionali - = (egal), 6= (diferit), < (strict mai mic), ≤ (mai mic sau egal), > (strict mai mare), ≥ (mai mare sau egal); • operatori logici - OR sau ∨ (disjunct¸ie), AND sau ∧ (conjunct¸ie), NOT sau ¬ (negat¸ie). Cea mai simpl˘a expresie este format˘a dintr-o variabil˘ a sau o constant˘ a (operand). Expresiile mai complicate se obt¸in din operat¸ii efectuate ˆıntre variabile ¸si constante. La scrierea expresiilor trebuie s˘a se ¸tin˘a cont de faptul c˘a, ˆın momentul evalu˘arii lor, ˆın primul rˆand se vor evalua expresiile din paranteze, iar operat¸iile se execut˘a ˆın ordinea determinat˘a de priorit˘a¸tile lor. Enunt¸uri de ramificare 1: if
then 2:
1: if (a mod 2 = 0) then 2: Output {’Numarul este par’ } 3: else 4: Output {’Numarul este impar’ } 5: end if
[ 3: else 4: ] 5: end if
Enunt¸ul if...then...else evalueaz˘a mai ˆıntˆai expresia boolean˘a pentru a determina unul din cele dou˘a drumuri pe care le poate lua execut¸ia algoritmului. Ea poate include opt¸ional o clauz˘a else. Dac˘a se evalueaz˘a la valoarea de adev˘ar true, atunci se execut˘a ¸si se continu˘a cu urm˘atoarea instruct¸iune dup˘a if. Dac˘a are valoarea de adev˘ar false, atunci se execut˘a . ¸si sunt instruct¸iuni compuse ce pot s˘a cont¸in˘a, la rˆandul lor, o alt˘a instruct¸iune if. Exemplul 1.1 Un algoritm simplu este cel ce rezolv˘ a ecuat¸ia de gradul I, ax+b = 0, a, b ∈ R (algoritmul 1). Acesta se bazeaz˘a pe rezolvarea matematic˘ a general˘ a a ecuat¸iei de gradul I: 1. dac˘a a = 0, atunci ecuat¸ia devine 0 · x + b = 0. Pentru cazul ˆın care b 6= 0 avem 0 · x + b = 0, egalitate ce nu poate fi satisf˘ acut˘ a pentru nicio valoare a lui x ∈ R sau C. Spunem ˆın acest caz c˘a ecuat¸ia este incompatibil˘ a. Dac˘a b = 0, avem 0·x+0 = 0, relat¸ia fiind adev˘arat˘a pentru orice valoare a lui x ∈ R. Spunem c˘ a ecuat¸ia este compatibil nedeterminat˘a. 5
Algoritm 1 Algoritm pentru rezolvarea ecuat¸iei de gradul I 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
Input {a, b} if (a = 0) then if (b = 0) then Output { ’Ecuatie compatibil nedeterminata’ } else Output { ’Ecuatie incompatibila’ } end if else x ← − ab Output { ’Solutia este:’, x } end if
2. dac˘a a 6= 0, ecuat¸ia are o singur˘ a solut¸ie, x1 = − ab ∈ R. Avˆand acest algoritm drept model s˘ a se realizeze un algoritm pentru rezolvarea ecuat¸iei de 2 gradul al II-lea, ax + bx + c = 0, unde a, b, c ∈ R. Enunt¸uri repetitive Enunt¸urile repetitive permit descrierea unor prelucr˘ari ce trebuie efectuate ˆın mod repetat, ˆın funct¸ie de pozit¸ia condit¸iei de continuare existˆand dou˘a variante de structuri repetitive: structura repetitiv˘a condit¸ionat˘a anterior ¸si structura repetitiv˘ a condit¸ionat˘ a posterior. Structura repetitiv˘a condit¸ionat˘a anterior while (sau instruct¸iune de ciclare cu test init¸ial) are sintaxa: 1: 2: 3: 4: 5: 6:
1: while do 2: 3: end while
sum ← 0 i←1 while (i ≤ n) do sum ← sum + i i ← i+1 end while
Cˆat timp este adevarat˘ a, se execut˘a ; dac˘a este fals˘a chiar la prima evaluare atunci nu ajunge s˘a fie realizat˘a niciodat˘a. Acest comportament este exact opus celui corespunz˘ator structurii repetitive condit¸ionat˘a posterior repeat, unde este executat˘a cel put¸in o dat˘a. (Dac˘a o expresie are valoarea de adev˘ar true spunem atunci c˘a expresia este adev˘ arat˘a ; dac˘a o expresie are valoarea de adev˘ar false spunem atunci c˘a expresia nu este adev˘arat˘a – este fals˘a). Exemplul 1.2 Vom realiza un algoritm pentru calculul cˆ atului ¸si restului ˆımp˘ art¸irii a dou˘a numere ˆıntregi, prin sc˘aderi succesive (vezi algoritmul 2). Mai ˆıntˆai se init¸ializeaz˘a cˆatul cu valoarea zero (linia 3) ¸si restul cu valoarea deˆımp˘art¸itului (linia 4). Apoi, atˆata timp cˆat restul este mai mare decˆ at valoarea ˆımp˘ art¸itorului (liniile 5 8), vom incrementa cˆatul cu o unitate, ¸si decrementa restul cu valoarea ˆımp˘ art¸itorului. La final, sunt afi¸sate valorile cˆatului ¸si restului. Un caz particular de structur˘a repetitiv˘a condit¸ionat˘a anterior este for, utilizat˘a ˆın cazul ˆın care o instruct¸iune sau un grup de instruct¸iuni trebuie s˘a se repete de 0 sau mai multe ori - num˘arul de repetit¸ii fiind cunoscut ˆınainte de ˆınceperea sa. Enunt¸urile repetitive bazate
6
Algoritm 2 Algoritm pentru calculul ˆımp˘art¸irii ˆıntregi 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
Input {a, b} if ((a > 0) ∧ (b > 0)) then cat ← 0 rest ← a while (rest ≥ b) do rest ← rest − b cat ← cat + 1 end while Output {cat, rest} else Output {’Numerele trebuie sa fie strict pozitive!’} end if
pe instruct¸iunile while ¸si repeat sunt mult mai potrivite ˆın cazul ˆın care condit¸ia de terminare trebuie reevaluat˘a ˆın timpul cicl˘ arii (atunci cˆ and num˘ arul de repetit¸ii nu este cunoscut apriori). Instruct¸iunea for are urm˘atoarea sintax˘a: 1: for ← , , 1: sum ← 0 2: for i ← 1, n do Step < p > do 3: sum ← sum + i 2: 4: end for 3: end for
Comportamentul enunt¸ului repetitiv for se descrie cel mai bine prin comparat¸ie cu enunt¸ul repetitiv while. Astfel secvent¸a urm˘atoare de limbaj pseudocod 1: for v ← e1 , e2 ST EP p do 2: < instructiune > 3: end for
este echivalent˘a cu secvent¸a ce cont¸ine enunt¸ul repetitiv while: 1: v ← e1 2: while (v ≤ e2 ) do 3: 4: v ←v+p 5: end while
e1 reprezint˘a valoarea init¸ial˘a, e2 limita final˘a, iar p pasul de lucru: la fiecare pas, valoarea variabilei v este incrementat˘a cu valoarea lui p, ce poate fi atˆat pozitiv˘a cˆat ¸si negativ˘a. Dac˘a p ≥ 0 atunci v va lua valori ˆın ordine cresc˘atoare, iar dac˘a p < 0 atunci v va primi valori ˆın ordine descresc˘atoare. ˆIn cazul ˆın care pasul de incrementare este 1, atunci el se poate omite din enunt¸ul repetitiv for, variabila contor fiind incrementat˘a automat la fiecare repetit¸ie cu valoarea 1. Dac˘a pasul de incrementare p are valoarea 1 atunci avem urm˘atoarele situat¸ii posibile pentru construct¸ia for: • Expresie1 > Expresie2: nu se execut˘a niciodat˘a; • Expresie1 = Expresie2: se execut˘a exact o dat˘a; • Expresie1 < Expresie2: se execut˘a de Expresie2 − Expresie1 + 1 ori. Exemplul 1.3 Un caz destul de frecvent ˆıntˆ alnit ˆın practic˘ a este cel ˆın care se cere aflarea elementului maxim, respectiv minim dintr-un ¸sir de elemente. Acest ¸sir de elemente poate fi 7
p˘astrat ˆıntr-o structur˘a de date elementar˘ a, cunoscut˘ a sub numele de vector. Un vector este un caz particular de matrice avˆand o singur˘ a linie ¸si n coloane. Prezent˘am ˆın continuare algoritmul pentru determinarea elementului de valoare minim˘a ce face parte dintr-un ¸sir de numere naturale (vezi algoritmul 3). Algoritm 3 Algoritm pentru calculul elementului de valoare minim˘a dintr–un ¸sir 1: 2: 3: 4: 5: 6: 7: 8:
Input {N, x1 , x2 , ..., xN } min ← x1 for i ← 2, N do if (min > xi ) then min ← xi end if end for Output { min }
ˆIn acest algoritm se observ˘a utilizarea enunt¸ului repetitiv for, ˆıntˆ alnit, ˆın general, ˆın situat¸iile ˆın care se cunoa¸ste apriori num˘ arul de pa¸si pe care trebuie s˘ a-l realizeze instruct¸iunea repetitiv˘a. Mai ˆıntˆai se init¸ializeaz˘a variabila min cu x1 , presupunˆ and c˘ a elementul de valoare minim˘a este primul element din cadrul vectorului X (linia 2). ˆ In continuare vom compara valoarea variabilei min cu valoarea fiec˘ arui element din ¸sir (liniile 3–7): dac˘ a vom g˘asi un element a c˘arui valoare este mai mic˘ a decˆ at cea a minimului calculat pˆ an˘ a la momentul curent (linia 4), vom ret¸ine noua valoare ˆın variabila min (linia 5). Structura repetitiv˘a condit¸ionat˘a posterior repeat. . .until prezint˘a urm˘atoarea sintax˘a: 1: 2: 3: 4: 5: 6:
1: repeat 2: 3: until
sum ← 0 i←1 repeat sum ← sum + i i ← i+1 until (i > n)
Atˆata timp cˆat este fals˘a, se execut˘a . Se observ˘a faptul c˘a instruct¸iunile dintre repeat ¸si until se vor executa cel put¸in o dat˘a. Exemplul 1.4 Algoritmul 4 prezentat ˆın continuare calculeaz˘ a suma primelor n numere naturale, S = 1 + 2 + 3 + . . . + n. Not˘am cu Sn suma primelor n numere naturale (Sn = 1 + 2 + 3 + . . . + n). Aceasta se poate calcula ˆıntr-o manier˘a incremental˘ a astfel: S1 S2 S3 S4 Sn Observat¸ia 1.5
= = = = ... =
1 1 + 2 = S1 + 2 (1 + 2) + 3 = S2 + 3 (1 + 2 + 3) + 4 = S3 + 4 (1 + 2 + . . . + n − 1) + n = Sn−1 + n
1. Algoritmul 4 utilizeaz˘ a enunt¸ul repetitiv cu test final, repeat ...until. 8
Algoritm 4 Algoritm pentru calculul sumei primelor n numere naturale (prima variant˘a) 1: 2: 3: 4: 5: 6: 7: 8:
Input {N } S←0 i←1 repeat S ←S+i i←i+1 until (i > N ) Output {S}
2. Enunt¸urile S ← 0 ¸si i ← 1 au rolul de a atribui valori init¸iale variabilelor S ¸si i. ˆIntotdeauna variabilele trebuie init¸ializate corect din punctul de vedere al algoritmului de calcul. Variabila ce ret¸ine rezultatul unui proces de ˆınsum˘ ari succesive se va init¸ializa ˆıntotdeauna cu 0, valoare ce nu va influent¸a rezultatul final. O variabil˘ a ce p˘astreaz˘a rezultatul unei sume sau al unui produs se va init¸ializa cu elementul neutru fat¸˘a de operat¸ia respectiv˘a. 3. Atribuirile i = i + 1 ¸si S = S + i (liniile 5 ¸si 6) sunt lipsite de sens din punct de vedere algebric. ˆIns˘a atunci cˆand ne referim la un sistem de calcul electronic, aceste operat¸ii se refer˘a la valoarea curent˘a ¸si la cea anterioar˘ a a unei variabile. De asemenea, trebuie s˘a se fac˘a o distinct¸ie clar˘a ˆıntre operat¸ia de atribuire ¸si cea de egalitate: operat¸ia de atribuire (’←’) face ca valoarea curent˘ a a variabilei din stˆ anga operatorului de atribuire s˘a fie init¸ializat˘a cu valoarea expresiei din dreapta acestuia (ˆın expresia ’k ← i + 2’ variabila k prime¸ste valoarea rezultat˘ a ˆın urma evalu˘ arii expresiei ’i + 2’), pe cˆand operat¸ia de egalitate verific˘a dac˘ a valoarea elementului din stˆ anga operatorului ’=’ (sau a expresiei aflate ˆın partea stˆang˘ a a operatorului) este egal˘ a cu valoarea expresiei din dreapta acestuia (de exemplu rezultatul evalu˘ arii expresiei k = i + 2) are valoarea de adev˘ar true dac˘a valoarea variabilei ’k’ este egal˘ a cu valoarea rezultat˘ a ˆın urma evalu˘arii expresiei ’i + 2’ ¸si are valoarea de adev˘ ar false ˆın caz contrar). Prin urmare, ˆın cazul expresiei i ← i + 1, valoarea curent˘ a a variabilei i devine valoarea anterioar˘a a aceleia¸si variabile incrementat˘ a cu o unitate. Suma primelor n numere naturale se constituie ˆın suma termenilor unei progresii arit. Avˆand metice ce se poate calcula direct cu formula sumei, astfel 1 + 2 + . . . + n = n(n+1) 2 ˆın vedere aceast˘a identitate, algoritmul de calcul a sumei primelor n numere naturale se simplific˘a foarte mult (vezi algoritmul 5). Algoritm 5 Algoritm pentru calculul sumei primelor n numere naturale (a doua variant˘a) 1: Input {N } 2: S ← n · (n + 1)/2 3: Output {S}
Proceduri ¸si funct¸ii Procedurile sunt subrutine ale c˘aror instruct¸iuni se execut˘a ori de cˆate ori acestea sunt apelate prin numele lor. 9
Apelarea procedurilor se face ˆın unitatea de program apelant˘a prin numele procedurii, primit la definire, urmat de lista parametrilor actuali. Aceasta trebuie s˘a corespund˘a ca num˘ar ¸si tip cu lista parametrilor formali, ˆın situat¸ia ˆın care exist˘a o list˘a de parametri formali ˆın antetul subrutinei. Definit¸ia unei proceduri are urm˘atoarea sintax˘a: 1: procedure () 2: 3: end procedure
lista parametri formali (opt¸ional˘a) simbolizeaz˘a o list˘a de identificatori (parametri) ce permite transferul datelor ˆıntre subrutina apelant˘ a ¸si subrutina apelat˘ a. Ace¸sti parametri se specific˘a prin nume (identificator) urmat, eventual, de tipul de dat˘a al parametrului. Mai mult¸i parametri de acela¸si tip pot fi grupat¸i, folosindu-se drept separator virgula. De fapt, lista parametrilor formali poate fi descris˘a mai detaliat astfel: 1: procedure () 2: end procedure
unde PFI = lista parametrilor formali de intrare. PFO = lista parametrilor formali de ie¸sire. Enunt¸ul de apel al unei proceduri are urm˘atoarea sintax˘a: 1: CALL (PAI; PAO)
PAI - lista parametrilor actuali de intrare, ce corespund parametrilor formali de intrare. Corespondent¸a se face de la stˆanga la dreapta ¸si trebuie s˘a fie de acela¸si tip cu parametrii formali. PAO - lista parametrilor actuali de ie¸sire. Exemplul 1.6 S˘a se realizeze un algoritm care determin˘ a cel mai mare divizor comun a dou˘a numere naturale. Reamintim cˆateva rezultate teoretice ce vor fi folositoare pentru ˆınt¸elegerea procesului de calcul al algoritmului ce va fi prezentat. Teorema 1.7 (Teorema ˆımp˘ art¸irii cu rest) Pentru dou˘ a numere ˆıntregi a ¸si b, cu b 6= 0, ∃ dou˘a numere ˆıntregi q ¸si r, unice, astfel ˆıncˆ at: a = b · q + r, 0 ≤ r < |b| unde a se nume¸ste deˆımp˘art¸itul, b ˆımp˘ art¸itorul, q cˆ atul iar r restul. Definit¸ia 1.2 Cel mai mare divizor comun (notat cmmdc) a dou˘ a numere naturale a ¸si b este un num˘ar natural d cu propriet˘a¸tile: 1. d|a, d|b (d este un divizor comun al lui a ¸si b) 2. ∀d′ ∈ N astfel ˆıncˆat d′ |a, d′ |b avem d′ |d (oricare alt divizor comun al lui a ¸si b, ˆıl divide ¸si pe d). Observat¸ia 1.8
1. cmmdc(a, 0) = a.
2. Dac˘a cmmdc(a, b) = 1 se spune c˘ a numerele a ¸si b sunt prime ˆıntre ele. 3. ˆIntre cel mai mic multiplu comun ¸si cel mai mare divizor comun exist˘ a urm˘atoarea relat¸ie: a·b cmmmc(a, b) = (1.1) cmmdc(a, b) 10
Metoda de calcul pentru a obt¸ine cel mai mare divizor comun a dou˘ a numere a ¸si b prin ˆımp˘art¸iri succesive, cunoscut˘a sub numele de algoritmul lui Euclid, se poate descrie astfel: Se ˆımparte a la b ¸si se ret¸ine restul r. Dac˘ a r este nul atunci cel mai mare divizor comun ˆ este b. In caz contrar, se ˆımparte b la r ¸si se p˘ astreaz˘ a noul rest. Calculele se continu˘a, folosindu-se ca deˆımp˘art¸it vechiul ˆımp˘ art¸itor iar ca ˆımp˘ art¸itor ultimul rest obt¸inut, pˆan˘a cˆand restul obt¸inut devine nul. Operat¸iile anterioare pot fi transpuse prin intermediul urm˘ atoarelor formule matematice: a = bq1 + r1 b = r1 q2 + r2 r1 = r2 q3 + r3 ... rk = rk+1 qk+2 + rk+2 ... rn−2 = rn−1 qn + rn rn−1 = rn qn+1 + rn+1
0 ≤ r1 < b 0 ≤ r 2 < r1 0 ≤ r 3 < r2 0 ≤ rk+2 < rk+1 0 ≤ rn < rn−1 0 ≤ rn+1 < rn
Aceste ˆımp˘art¸iri nu se constituie ˆıntr-un proces infinit, deoarece secvent¸a de numere naturale r1 , r2 , . . . , rn+1 , . . . este strict descresc˘ atoare (r1 > r2 > . . . > rn > . . .) ¸si m˘arginit˘a inferior de 0. Rezult˘a c˘a ∃p ∈ N astfel ˆıncˆ at rp 6= 0, ¸si rp+1 = 0. Algoritm 6 Algoritmul lui Euclid pentru calculul cmmdc a dou˘a numere 1: procedure cmmdc(x, y; b) 2: a←x 3: b←y 4: if (b = 0) then 5: b←a 6: else 7: r ← a mod b 8: while (r 6= 0) do 9: a←b 10: b←r 11: r ← a mod b 12: end while 13: end if 14: end procedure
⊲ Procesul se ˆıncheie atunci cˆ and r ia valoarea 0
Dac˘a analiz˘am ˆıntregul proces de calcul, se observ˘a faptul c˘a deˆımp˘art¸itul este ˆımp˘art¸itorul de la etapa anterioar˘a, iar ˆımp˘art¸itorul este restul de la etapa anterioar˘a. De asemenea, trebuie ment¸ionat c˘a ˆın acest proces de calcul cˆatul nu particip˘a activ. Algoritmul 6 este un foarte bun exemplu de utilizare a instruct¸iunii de ciclare cu test init¸ial, while. Din analiza algoritmului reiese faptul c˘a, mai ˆıntˆai se efectueaz˘a o evaluare a restului r (calculul s˘au, liniile 7 ¸si 11), dup˘a care se testeaz˘a condit¸ia egalit˘a¸tii acestuia cu 0 (linia 8). Funct¸iile au aceea¸si sintax˘a ca ¸si procedurile: 1: function () 2: 3: end function
Funct¸iile pot fi apelate prin numele lor, ca termen al unei expresii. 11
1.2
Exemple
Exemplul 1.9 S˘a se verifice printr-un algoritm dac˘ a un num˘ ar natural este prim sau nu. Definit¸ia 1.3 Un num˘ar natural k ≥ 2 se nume¸ste prim dac˘ a singurii s˘ ai divizori naturali sunt 1 ¸si k. Pe baza acestei definit¸ii se poate concluziona c˘ a un num˘ ar natural k este prim dac˘a nu are niciun divizor propriu ˆın intervalul [2, k − 1]. Algoritm 7 Algoritm pentru verificarea dac˘a un num˘ar este prim 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
Input {N } if (n < 2) then Output {’Numarul nu este prim.’} else i←2 prim ← true while ((i ≤ n − 1) ∧ (prim = true)) do if (n mod i = 0) then prim ← f alse else i ← i+1 end if end while if (prim = true) then Output {’Numarul este prim.’} else Output {’Numarul nu este prim.’} end if end if
Dac˘a n este num˘ar prim, corpul enunt¸ului de ciclare while se va executa de n − 2 ori (vezi algoritmul 7). Condit¸ia i ≤ n − 1 poate fi ˆımbun˘ at˘ a¸tit˘ a cu i ≤ n/2, deoarece ˆıntre jum˘ atatea num˘arului ¸si n nu mai exist˘a niciun alt divizor, pentru orice valoare a lui n. Dac˘a 2 este un divizor al num˘arului n atunci ¸si n/2 este un divizor al lui n. Dac˘a 2 nu este divizor al lui n atunci nici n/2 nu este divizor al lui n. Un enunt¸ asem˘ an˘ ator este valabil ¸si pentru numerele 3, 4, 5, . . .. Astfel se formeaz˘ a dou˘ a ¸siruri: corespunde n 2 → 2 corespunde n 3 → 3 corespunde n 4 → 4 .. .. . . n corespunde k → |{z} k |{z} Sir1
Sir2
Se observ˘a faptul c˘a primul ¸sir (Sir1 ) este compus din elemente cu valori consecutive, iar al doilea ¸sir (Sir2 ) nu respect˘a aceast˘ a proprietate. Numerele dintr–o pereche (p, np ) sunt 12
legate ˆıntre ele astfel: ˆın momentul ˆın care am verificat faptul c˘ a num˘ arul p nu este divizor al lui n, implicit am dedus c˘a nici n/p nu este un divizor al lui n. Prin urmare nu mai este nevoie s˘a verific˘am ¸si valoarea n/p. Primul ¸sir este strict cresc˘ ator iar cel de-al doilea este strict descresc˘ator. Elementele din primul ¸sir devin mai mari decˆ at cele din al doilea atunci √ 2 cˆand k ≅ n/k√ ⇒ k ≅ n ⇒ k ≅ n. Astfel rezult˘ a condit¸ia pentru limita superioar˘a a cicl˘arii: i ≤ ⌊ n⌋ (vezi algoritmul 8). Algoritm 8 Algoritm pentru verificarea dac˘a un num˘ar este prim (variant˘a optimizat˘a) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
Input {N } if (n < 2) then Output {’Numarul nu este prim.’} else i←2 prim ← true √ while ((i ≤ ⌊ n⌋) ∧ (prim = true)) do if (n mod i = 0) then prim ← f alse else i ← i+1 end if end while if (prim = true) then Output {’Numarul este prim.’} else Output {’Numarul nu este prim.’} end if end if
Exemplul 1.10 S˘a se aproximeze, cu o precizie ǫ dat˘ a, limita ¸sirului de numere reale an , n ≥ 0: n X 1 an = . (1.2) k! k=0 Analizˆand structura termenului general al ¸sirului an , an =
1 1 1 1 1 + + + ...+ + 0! 1! 2! (n − 1)! n!
se observ˘a c˘a avem relat¸ia: an+1 − an =
(1.3)
1 . (n + 1)!
Se noteaz˘a tn = n!1 . Vom calcula termenul an pˆ an˘ a cˆ and diferent¸a dintre doi termeni consecutivi ai ¸sirului este mai mic˘a decˆat ǫ: 1 < ǫ ⇒ tn < ǫ. n!
|an − an−1 | < ǫ ⇒
(1.4)
Pe de alt˘a parte avem urm˘atoarea relat¸ie ˆıntre doi termeni consecutivi ai ¸sirului tn tn+1 =
tn , n+1
pe care o vom folosi pentru a reduce num˘ arul de calcule efectuate de algoritmul 9. 13
(1.5)
Algoritm 9 Algoritm pentru calculul limitei unui ¸sir de numere cu aproximare 1: 2: 3: 4: 5: 6: 7: 8: 9:
Input {ǫ} a ← 1, t ← 1, k ← 1 while (t ≥ ǫ) do a←a+t t t ← k+1 k ←k+1 end while a ←a+t Output {a, k}
Exemplul 1.11 Se spune c˘a un vector este simetric dac˘ a primul element este egal cu ultimul, al doilea cu penultimul etc. Algoritmul urm˘ ator verific˘ a dac˘ a un vector de numere ˆıntregi este simetric. Vom utiliza dou˘a variabile de indici, ce pornesc, una din stˆ anga, ¸si cealalt˘ a din dreapta. Atˆata timp cˆat variabila din stˆanga este mai mic˘ a decˆ at variabila din dreapta iar elementele din vector corespunz˘atoare variabilelor de indiciere sunt egale, se incrementeaz˘ a variabila din stˆanga ¸si se decrementeaz˘a variabila din dreapta. Dup˘ a p˘ ar˘ asirea instruct¸iunii de ciclare, se face o verificare pentru determinarea condit¸iei care nu a mai fost ˆındeplinit˘ a ¸si a condus la ie¸sirea din bucl˘a. Dac˘a i ≥ j atunci condit¸ia (i < j) nu a mai fost ˆındeplinit˘ a. Acest lucru conduce la concluzia c˘a cealalt˘a condit¸ie, (xi = xj ) a fost ˆındeplinit˘ a tot timpul (∀i < j, i, j = 1, n). Deci ¸sirul este simetric ˆın aceast˘ a situat¸ie. Algoritm 10 Algoritm pentru verificarea simetriei unui vector 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
Input {N, x1 , x2 , ..., xN } i ← 1, j ← N while ((i < j) ∧ (xi = xj )) do i←i+1 j ← j −1 end while if (i ≥ j) then Output {’Vectorul este simetric.’} else Output {’Vectorul nu este simetric.’} end if
Exemplul 1.12 Se d˘a urm˘atorul ¸sir de numere naturale: 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, . . .. Pentru un num˘ar natural n dat, s˘a se determine cel de-al n-lea element al ¸sirului. De exemplu, cel de-al 5-lea element al ¸sirului prezentat este 2. Dup˘a cum se observ˘a din construct¸ia ¸sirului, acesta este format din concatenarea mai multor secvent¸e de numere naturale consecutive, fiecare secvent¸˘ a ˆıncepˆ and cu valoarea 1. Lungimea fiec˘arei subsecvent¸e este cu o unitate mai mare decˆ at lungimea subsecvent¸ei anterioare: astfel prima subsecvent¸˘a are lungimea 1, a doua subsecvent¸˘ a are lungimea 2, a treia subsecvent¸˘a are lungimea 3, etc. ˆIn cadrul algoritmului 11 se va ˆıncerca decrementarea variabilei index cu lungimea cˆate unui ¸sir ˆın ˆıntregime (index ← index − k), atˆ ata timp cˆat este posibil acest lucru. Valoarea cu care r˘amˆ ane variabila index reprezint˘ a cel de-al n-lea element al ¸sirului. Se poate optimiza algoritmul 11 astfel ˆıncˆ at s˘ a renunt¸a˘m la enunt¸ul de ciclare cu test init¸ial while, procesul de calcul efectuˆ and astfel mai put¸ine calcule (exercit¸iu). 14
Algoritm 11 Algoritm pentru determinarea celui de–al n–lea element 1: 2: 3: 4: 5: 6: 7: 8:
Input {N } k←1 index ← N while (k < index) do index ← index − k k ←k+1 end while Output {’Cel de-al n-lea element este’, index}
Exemplul 1.13 Se dau dou˘a ¸siruri de numere naturale, a1 , a2 , . . . , an ¸si b1 , b2 , . . . , bn (ai , bi ∈ N). S˘a se determine dou˘a numere naturale d ¸si e (d, e ∈ N) astfel ˆıncˆ at: (a21 + b21 ) · (a22 + b22 ) · . . . (a2n + b2n ) = d2 + e2 Vom demonstra prin induct¸ie matematic˘ a faptul c˘ a at (a21 + b21 ) · (a22 + b22 ) · . . . (a2n + b2n ) = d2 + e2 (1.6) ∀ai , bi ∈ N, i = 1, n, ∃d, e ∈ N astfel ˆıncˆ Fie n = 2. Ar trebui s˘a determin˘ am valorile necunoscutelor d ¸si e astfel ˆıncˆ at s˘a avem (a21 + b21 ) · (a22 + b22 ) = d2 + e2 . Dac˘a vom considera ( d = a1 a2 + b1 b2 e = a1 b2 − b1 a2 atunci, dup˘a efectuarea calculelor obt¸inem d2 +e2 = (a1 a2 +b1 b2 )2 +(a1 b2 −b1 a2 )2 = a21 a22 +b21 b22 +2a1 a2 b1 b2 +a21 b22 +b21 a22 −2a1 a2 b1 b2 = 2 2 a1 a2 + b21 b22 + a21 b22 + b21 a22 = (a21 + b21 ) · (a22 + b22 ), ¸si relat¸ia anterioar˘a devine adev˘arat˘ a. Presupunem c˘a relat¸ia (1.6) este adev˘ arat˘ a ∀k ≤ n. Vom arat˘a c˘a ea este adev˘arat˘a ¸si pentru n + 1: ∀ai , bi ∈ N, i = 1, n + 1, ∃d, e ∈ N astfel ˆıncˆ at (a21 + b21 ) · (a22 + b22 ) · . . . (a2n+1 + b2n+1 ) = d2 + e2 (1.7) Aplicˆand de dou˘a ori ipoteza de induct¸ie avem: (a21 + b21 ) · (a22 + b22 ) · . . . (a2n + b2n ) ·(a2n+1 + b2n+1 ) = (u2 + v 2 ) · (a2n+1 + b2n+1 ) = d2 + e2 (1.8) | {z } u2 +v2
unde e = uan+1 + vbn+1 ¸si e = ubn+1 − van+1 . Algoritmul 12 se bazeaz˘a pe modelul de construct¸ie pentru d ¸si e, prezentat ˆın demonstrat¸ia anterioar˘a.
Exemplul 1.14 Se d˘a un num˘ar de 9 monede de acela¸si tip. Printre ele s-a strecurat o moned˘a fals˘a: ea este mai grea sau mai u¸soar˘ a decˆ at celelalte. Avem la dispozit¸ie o balant¸˘a f˘ar˘a greut˘a¸ti. Se cere un algoritm care s˘ a determine moneda fals˘ a, ¸stiind c˘ a putem folosi cˆantarul doar pentru 3 operat¸ii de cˆant˘ arire. Fie a1 , a2 , . . . , a9 cele 9 monede. Vom ˆımp˘ art¸i monedele ˆın trei grupe egale: A = {a1 , a2 , a3 }, B = {a4 , a5 , a6 } ¸si C = {a7 , a8 , a9 }. Pentru a determina moneda fals˘ a avem nevoie de dou˘a informat¸ii: 15
Algoritm 12 Algoritm pentru calculul elementelor unei expresii 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
Input {N, a1 , . . . , aN , b1 , . . . , bN } d ← a1 e ← b1 for i ← 2, N do u = d · ai + e · bi v = d · bi − e · ai d←u e←v end for Output {d, e}
1. ˆın ce grup˘a se afl˘a moneda fals˘a; 2. cum este moneda fals˘a (mai grea sau mai u¸soar˘ a fat¸˘ a de restul). Not˘am cu g(·) greutatea unei monede sau a unei mult¸imi de monede. Vom compara grupurile A ¸si B cu ajutorul balant¸ei. Avem urm˘ atoarele cazuri: 1. g(A) = g(B). ˆIn aceast˘a situat¸ie moneda fals˘ a se afl˘ a ˆın cea de–a treia grup˘ a, C = {a7 , a8 , a9 }. Compar˘am monedele a7 ¸si a8 , rezultˆ and una din situat¸iile urm˘ atoare: (a) g(a7 ) = g(a8 ) ⇒ a9 este moneda fals˘ a.
(b) g(a7 ) < g(a8 ). Mai departe compar˘am monedele a7 cu a9 ¸si avem: i. g(a7 ) = g(a9 ) ⇒ a8 este moneda fals˘ a ¸si este mai grea decˆ at restul monedelor. ii. g(a7 ) > g(a9 ) ⇒ a8 > a7 > a9 (imposibil deoarece dou˘ a monede trebuie s˘a fie egale). iii. g(a7 ) < g(a9 ) ⇒ a7 este moneda fals˘ a ¸si este mai u¸soar˘ a decˆ at restul monedelor. (c) g(a7 ) > g(a8 ). Se trateaz˘a ˆın mod analog cu situat¸ia de la punctul b).
2. g(A) < g(B). ˆIn continuare compar˘am grupa A cu grupa C ¸si vom obt¸ine una din situat¸iile urm˘atoare: (a) g(A) = g(C) ⇒ moneda fals˘ a se afl˘ a ˆın grupa B ¸si este mai grea. Mai departe compar˘am moneda a4 cu moneda a5 : i. g(a4 ) = g(a5 ) ⇒ a6 este moneda fals˘ a. ii. g(a4 ) < g(a5 ) ⇒ a5 este moneda fals˘ a (deoarece este mai grea). iii. g(a4 ) > g(a5 ) ⇒ a4 este moneda fals˘ a. g(A)
(b) g(A) < g(C) ⇒ g(B) = g(C) ⇒ moneda fals˘ a se afl˘ a ˆın grupa A ¸si este mai u¸soar˘a. ˆIn acest caz, compar˘am moneda a1 cu moneda a2 : i. g(a1 ) = g(a2 ) ⇒ a3 este moneda fals˘ a. ii. g(a1 ) < g(a2 ) ⇒ a1 este moneda fals˘ a (deoarece este mai u¸soar˘ a). 16
iii. g(a1 ) > g(a2 ) ⇒ a2 este moneda fals˘ a.
(c) g(A) > g(C) - Acest caz nu poate s˘ a apar˘ a deoarece am avea g(B) > g(A) > g(C), imposibil, deoarece dou˘a grupe trebuie s˘ a fie egale ˆın greutate. 3. g(A) > g(B). Rat¸ionamentul este analog cu cel de la punctul 2). Exemplul 1.15 Se dau n numere naturale a1 , a2 , . . . , an (ai ∈ N). Se cere s˘ a se determine num˘arul de cifre de 0 ˆın care se termin˘ a produsul a1 · a2 · . . . · an . La o prim˘a evaluare a cerint¸ei, se poate schit¸a urm˘ atoarea solut¸ie: se calculeaz˘ a produsul celor n numere ¸si apoi se ˆımparte rezultatul succesiv la 10 atˆ ata timp cˆ at restul este nul. Se poate ˆıns˘a ca produsul celor n numere s˘ a fie un num˘ ar mare ce dep˘ a¸se¸ste valoarea maxim˘a ce poate fi p˘astrat˘a ˆıntr–o variabil˘ a dintr–unul din tipurile elementare. ˆ Intr-o astfel de situat¸ie este necesar s˘a se implementeze operat¸ii cu numere mari. Se pune ˆıntrebarea dac˘a se poate r˘ aspunde la cerint¸a problemei f˘ ar˘ a a se face calculul efectiv al produsului celor n numere. Fie exp2 valoarea exponentului num˘ arului 2 din descompunerea ˆın factori primi a termenului produs a1 ·a2 ·. . .·an , ¸si exp5 valoarea exponentului num˘ arului 5 din descompunerea ˆın factori primi a aceluia¸si termen. Num˘ arul de zerouri ˆın care se termin˘ a produsul a1 ·a2 ·. . .·an este min (exp2, exp5) (vezi algoritmul 13). Algoritm 13 Algoritm pentru calculul num˘arului de zerouri ale unui produs de numere 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
Input {N, a1 , . . . , aN } exp2 ← 0 exp5 ← 0 for i ← 1, N do while ((ai 6= 0) ∧ (ai mod 2 = 0)) do exp2 ← exp2 + 1 ai ← ai /2 end while while ((ai 6= 0) ∧ (ai mod 5 = 0)) do exp5 ← exp5 + 1 ai ← ai /5 end while end for if (exp2 < exp5) then min ← exp2 else min ← exp5 end if Output {min}
Exemplul 1.16 Se d˘a un num˘ar format din n cifre. Vom prezenta un algoritm ˆın care se determin˘a cel mai mare num˘ar obt¸inut prin eliminarea a k cifre (k < n) dintre cele n (vezi algoritmul 14). Compararea a dou˘a numere naturale ce prezint˘ a acela¸si num˘ ar de cifre se face ˆıncepˆand de la stˆanga spre dreapta (de la cifrele mai semnificative c˘ atre cele mai put¸in semnificative).
17
Fie A = a1 a2 . . . an num˘arul nostru.Vom nota prin m num˘ arul de cifre ce vor r˘ amˆane din num˘arul init¸ial dup˘a eliminarea celor k cifre (m = n−k). Problema se reduce la determinarea celui mai mare num˘ar format cu m cifre dintre cele n cifre init¸iale. La ˆınceput num˘arul cont¸ine n cifre, p˘ astrate ˆın ordine, ˆın vectorul A: a1 , a2 , . . . , an . Solut¸ia se construie¸ste ˆın mod iterativ, la pasul i alegˆ andu–se o valoare corespunz˘atoare: se determin˘a prima cifr˘a de valoare maxim˘ a ¸si pozit¸ia acesteia ˆın cadrul subsecvent¸ei de limite l ¸si n − m + i, al , . . . , an−m+i . Algoritm 14 Algoritm pentru determinarea num˘arului maxim obt¸inut dup˘a eliminarea a k cifre 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
Input {n, a1 , . . . , an , k} m← n−k l←0 for i ← 1, m do l ←l+1 j←l max ← 0 while (j ≤ n − m + i) do if (max < aj ) then max ← aj l←j end if j ←j+1 end while Output {max} end for
• ˆIn instruct¸iunea for (linia 4) pentru fiecare pozit¸ie liber˘a din num˘arul rezultat, se va alege cifra maxim˘a dintre cele disponibile; • linia 5 - se trece la pozit¸ia urm˘atoare: l reprezint˘a indexul din ¸sirul A al cifrei aleas˘a la pasul anterior (i − 1) ¸si cont¸ine pozit¸ia pe care a fost g˘asit˘a cifra maxim˘a. Cifra maxim˘a pentru pasul curent va fi c˘autat˘a ˆın intervalul l . . . n − m + i, unde n − m + i reprezint˘a limita superioar˘a a indexului pˆan˘a la care se poate alege un element la pasul curent; a1 . . . a ...a . . . an | l {zn−m+i} subsecvent¸a pentru care se determin˘ a cifra maxim˘ a
• liniile 6, 7 - se init¸ializeaz˘a variabila de ciclare j cu l ¸si valoarea maxim˘a cu 0; Deoarece ai ≥ 0, ∀i = 1, n, se poate init¸ializa variabila max (unde se va p˘astra valoarea maxim˘a) cu 0; • linia 8 - se caut˘a cifra maxim˘a pˆan˘a la limita din dreapta n − m + i (dac˘a se merge mai la dreapta dincolo de aceast˘a limit˘a nu se mai pot alege restul de m − i cifre);
• linia 10 - se actualizeaz˘a valoarea maxim˘a; • linia 11 - se actualizeaz˘a pozit¸ia valorii maxime.
18
Exemplul 1.17 Vom prezenta doi algoritmi ce determin˘ a toate tripletele de numere naturale 2 2 2 (a, b, c) ce verific˘a relat¸ia a + b = c unde c ≤ 100. Prima variant˘a de lucru prezint˘a generarea tuturor tripletelor (i, j, k) unde 2 ≤ i < j < k ≤ n. Dintre acestea se vor alege doar acelea ce verific˘ a relat¸ia k · k = i · i + j · j. Dac˘a vom considera doar instruct¸iunea de ciclare 4, atunci condit¸ia 5 se va verifica de n − (j + 1) + 1 = n − j ori. Deoarece j = i + 1, n − 1, vom obt¸ine faptul c˘ a instruct¸iunea 5 se va executa de: (n − i − 1) + (n − i − 2) + . . . + (n − n + 1) =
n−i−1 X
j=
j=1
(n − i) · (n − i − 1) 2
ori. Din i = 2, n − 2 rezult˘a c˘a n−2 n−i−1 X X i=2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
j =
j=1
n−2 X (n − i) · (n − i − 1) i=2
2
n−2
=
1X 2 (i + (1 − 2n)i + n2 − n) 2 i=2
n−2 n−2 X 1X 2 = [ i + (1 − 2n) i + (n2 − n) · (n − 3)] 2 i=2 i=2
Input {n} for i ← 2, n − 2 do for j ← i + 1, n − 1 do for k ← j + 1, n do if (k · k = i · i + j · j) then Output {i, j, k} end if end for end for end for return
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
Input {n} for i ← 2, n − 1 do for j ← i + 1, n do s ← i·i+j ·j √ k ← ⌊ s⌋ if ((k · k = s) ∧ (k ≤ n)) then Output {i, j, k} end if end for end for return
Pentru cea de-a doua variant˘a, cea din dreapta, se genereaz˘ a perechile de numere (i, j) cu proprietatea c˘a 2 ≤ i < j ≤ n, se calculeaz˘ a s ← i · i + j · j ¸si se verific˘ a dac˘ a num˘arul √ obt¸inut este p˘atrat perfect (linia 6: (⌊ s⌋)2 = s). Condit¸ia de la linia 6 se va executa de n − (i + 1) + 1 = n − i ori. Deoarece linia 2 se va executa de n − 1 − 2 + 1 = n − 2 ori, valoarea total˘ a reprezentˆand num˘arul de verific˘ari (linia 6) va fi: n−1 X i=2
1.3
(n − i) =
n−1 X i=1
(n − i) − (n − 1) =
n−1 X i=1
i − (n − 1) =
n(n − 1) −n+1 2
Exercit¸ii
1. (a) S˘a se determine dac˘a un num˘ar natural este simetric sau nu. (b) S˘a se determine toate cifrele distincte dintr-un num˘ar natural. (c) S˘a se determine reprezentarea ˆın baza 2 a unui num˘ar natural. 19
(d) S˘a se determine forma corespunz˘atoare ˆın baza 10 a unui num˘ar reprezentat ˆın baza 2. (e) S˘a se elimine dintr–un num˘ar natural toate cifrele de forma 3k + 1 ¸si s˘a se afi¸seze num˘arul rezultat. (f) Pentru un num˘ar natural dat s˘a se construiasc˘a din cifrele acestuia cel mai mare num˘ar prin amplasarea mai ˆıntˆai a cifrelor impare ¸si apoi a cifrelor pare. 2. (a) (b) (c) (d) (e) (f)
S˘a se determine tot¸i divizorii unui num˘ar natural. S˘a se verifice dac˘a dou˘a numere naturale sunt prime ˆıntre ele. S˘a se determine tot¸i divizorii comuni a dou˘a numere naturale. S˘a se calculeze toate numerele prime mai mici decˆat o valoare specificat˘a. S˘a se descompun˘a ˆın factori primi un num˘ar natural. S˘a se determine cel mai mare divizor comun a dou˘a numere naturale folosindu–se descompunerea ˆın factori primi.
3. Pentru un num˘ar natural b (1 < b < 10) s˘a se determine toate numerele naturale mai mici decˆat o valoare specificat˘a n, (1 ≤ n ≤ 105 ) care scrise ˆın baza b, cont¸in numai cifrele 0 ¸si 1. Intrare 7 5000 Iesire 0 1 7 8 49 50 56 57 343 344 350 351 392 393 399 400 2401
4. (a) Pentru un num˘ar natural n dat (n < 32000), s˘a se determine ultima cifr˘a nenul˘a a lui n!. (b) Pentru un num˘ar natural n dat (n < 1000), s˘a se determine num˘arul de cifre ale lui n!. 5. Se d˘a un num˘ar natural n (0 ≤ n ≤ 106 ). Se aplic˘a asupra num˘arului n una dintre urm˘atoarele transform˘ari: (a) Se calculeaz˘a suma factorialelor cifrelor num˘arului n ¸si se obt¸ine un nou num˘ar. (b) Se calculeaz˘a suma cuburilor cifrelor num˘arului n. Dup˘a aceasta, procedeul se repet˘a aplicˆandu-se de fiecare dat˘a numai una din cele dou˘a operat¸ii (a) sau (b) (cea cu care s-a ˆınceput). S˘a se afi¸seze ¸sirul de numere astfel obt¸inut pˆan˘a cˆand elementele lui ˆıncep s˘a se repete. Se cere lungimea listei pˆan˘a la prima repetit¸ie ¸si pozit¸ia elementului care se repet˘a primul. 6. O secvent¸˘a Farey[44] de ordinul n este un ¸sir de numere rat¸ionale ab unde 0 ≤ a ≤ b ≤ n, (a, b) = 1. De exemplu, secvent¸ele Farey de ordinul 1, 2 ¸si 3 sunt: 0 1 F1 = { , } 1 1 0 1 1 F2 = { , , } 1 2 1 0 1 1 2 1 F3 = { , , , , } 1 3 2 3 1 20
Pentru un num˘ar natural n (n < 216 ) s˘a se determine elementele secvent¸ei Farey de ordinul n. Intrare 7 Iesire (0,1),(1,7),(1,6),(1,5),(1,4),(2,7),(1,3),(2,5),(3,7),(1,2),(4,7),(3,5), (2,3),(5,7),(3,4),(5,6),(6,7),(1,1)
Indicat¸ie Fie ab11 , ab22 , ab33 trei elemente consecutive ale unei secvent¸e Farey de ordinul n. Atunci avem relat¸iile ([68]): b1 a2 − a1 b2 = 1 a1 + a3 a2 = b2 b1 + b3
(1.9) (1.10)
Dac˘a ab11 ¸si ab33 sunt dou˘a elemente consecutive ale unei secvent¸e Farey de ordinul n, 3 atunci ab11 +a este un element al secvent¸ei Farey de ordinul n + 1. +b3 Fie ab11 ¸si ab22 dou˘a elemente consecutive ale unei secvent¸e Farey de ordinul n. Elementul +a3 urm˘ator ce apart¸ine secvent¸ei, ab33 , se poate determina astfel: ab22 = ab11 +b . ∃k ∈ N∗ astfel 3 ˆıncˆat ka2 = a1 + a3 ¸si kb2 = b1 + b3 ⇒ a3 = ka2 − a1 ¸si b3 = kb2 − b1 . Valoarea lui k trebuie aleas˘a astfel ˆıncˆat fract¸ia ab33 s˘a fie cˆat mai apropiat˘a de ab22 . Astfel k trebuie s˘a 1 fie cˆat mai mare, ˆıns˘a respectˆand restrict¸ia k ≤ n+b . b2 7. Fiind date dou˘a numere ˆıntregi X ¸si Y , s˘a se determine cea mai mic˘a baz˘a pentru Y astfel ˆıncˆat X ¸si Y s˘a reprezinte aceea¸si valoare. De exemplu numerele 12 ¸si 5 nu sunt egale ˆın baza 10 (1210 6= 510 ), ˆıns˘a ele sunt egale dac˘a le consider˘am ca fiind reprezentate ˆın bazele 3 ¸si respectiv 6: 123 = 510 , 56 = 510 . Bazele ˆın care se vor considera reprezentate numerele au valoarea maxim˘a 36. Intrare Ie¸sire 12 5 123 = 56 10 A 1010 = A11 12 34 1217 = 345 123 456 no solution 10 2 102 = 23 (ACM, 1995)
8. S˘a se calculeze 2n pentru 30 < n < 100. 9. Fie D o dreapt˘a avˆand ecuat¸ia ax + by + c = 0 ¸si un cerc C(O, r) unde O(x0 , y0 ) este centrul cercului iar r este raza acestuia. S˘a se realizeze un algoritm care s˘a stabileasc˘a pozit¸ia dreptei fat¸˘a de cercul C ˆın funct¸ie de valorile parametrilor reali a, b, c, x0 , y0 , r. 10. S˘a se determine toate numerele naturale n (1 ≤ n ≤ 106 ) cu proprietatea c˘a atˆat n cˆat ¸si oglinditul s˘au sunt numere prime.
21
11. S˘a se scrie un algoritm ce calculeaz˘a coeficient¸ii polinomului urm˘ator, fiind date numerele a, b ¸si n: P (X) = (aX + b)n , a, b ∈ Z, n ∈ N. (1.11) 12. S˘a se determine toate r˘ad˘acinile rat¸ionale ale unei ecuat¸ii cu coeficient¸i ˆıntregi. Fie ecuat¸ia: an xn + an−1 xn−1 + . . . + a1 x + a0 = 0. O r˘ad˘acin˘a rat¸ional˘a a ecuat¸iei 1.12 va avea forma
p q
(1.12)
unde p|a0 ¸si q|an .
13. Pentru o secvent¸˘a de numere naturale de lungime n, s˘a se realizeze un algoritm care s˘a determine cea mai lung˘a subsecvent¸˘a de elemente consecutive avˆand aceea¸si valoare. 14. S˘a se realizeze un algoritm care s˘a determine primele n numere prime, pentru un num˘ar natural n dat. 15. S˘a se realizeze un algoritm pentru calcularea tuturor solut¸iilor ecuat¸iei: 3x + y + 4xz = 100, x, y, z ∈ N.
(1.13)
Indicat¸ie Pentru o valoare z ≥ 25 ¸si x 6= 0 (x > 0) avem: 3x + y + 4xz ≥ 3x + 4xz ≥ 3 + 4z > 100. Astfel pentru z obt¸inem c˘a 0 ≤ z ≤ 24. Ecuat¸ia 1.13 se poate scrie astfel:
3x + y + 4xz = 100 ⇔ x(3 + 4z) = 100 − y ⇔ x = Dac˘a x 6= 0 ¸si y ≥ 0 ⇒ x =
100−y 3+4z
≥
100−y 3+4z
⇒ x ∈ [1,
100−y 3+4z
100 − y 3 + 4z
(1.14)
].
Astfel, pentru fiecare pereche de numere (z, x) vom calcula pe y astfel: y = 100 − 3x − 4xz. De asemenea, ˆın 1.14 dac˘a lu˘am x = 0 obt¸inem mult¸imea de solut¸ii (0, 100, z), ∀z ∈ N. 16. Un moment de timp este specificat ˆın formatul ora:min:sec:sutimi. • S˘a se elaboreze o subrutin˘a ce calculeaz˘a timpul (ˆın sutimi de secund˘a) trecut de la momentul 00 : 00 : 00 : 00 al fiec˘arei zile ¸si pˆan˘a la momentul curent; • S˘a se calculeze timpul scurs ˆıntre dou˘a momente de timp hh1 : mm1 : ss1 : tt1 ¸si hh2 : mm2 : ss2 : tt2 ; • Fiind dat un num˘ar natural n, ce reprezint˘a num˘arul de secunde trecute de la momentul 00 : 00 : 00 : 00, s˘a se realizeze o subrutin˘a ce transform˘a acest num˘ar ˆın formatul ora:min:sec:sutimi. Ora ia valori ˆın intervalul 0 . . . 23, minutul ¸si secundele iau valori ˆın intervalul 0 . . . 59, iar sutimile de secund˘a au valori ˆın intervalul 0 . . . 99.
22
Capitolul 2 Elemente de analiza algoritmilor Gradul de dificultate al unei probleme P poate fi pus in evident¸˘a prin timpul de execut¸ie al algoritmului corespunz˘ator ¸si/sau prin spat¸iul de memorie necesar. Timpul de execut¸ie ˆın cazul cel mai defavorabil ne d˘a durata maxim˘a de execut¸ie a algoritmului. Timpul mediu de execut¸ie se obt¸ine prin ˆınsumarea timpilor de execut¸ie pentru toate mult¸imile de date de intrare urmat˘a de raportarea la num˘arul acestora. Definit¸ia 2.1 Timpul de execut¸ie ˆın cazul cel mai defavorabil al unui algoritm A este o funct¸ie TA : N −→ N unde TA (n) reprezint˘ a num˘ arul maxim de instruct¸iuni executate de c˘atre A ˆın cazul unor date de intrare de dimensiune n. Definit¸ia 2.2 Timpul mediu de execut¸ie al unui algoritm A este o funct¸ie TAmed : N −→ N unde TAmed (n) reprezint˘a num˘arul mediu de instruct¸iuni executate de c˘ atre A ˆın cazul unor date de intrare de dimensiune n. Fiind dat˘a o problem˘a P , o funct¸ie T (n) se spune c˘a este o margine superioar˘ a dac˘a exist˘a un algoritm A ce rezolv˘a problema P iar timpul de execut¸ie ˆın cazul cel mai defavorabil al algoritmului A este cel mult T (n). Fiind dat˘a o problem˘a P , o funct¸ie T (n) se spune c˘a este o margine pentru cazul mediu dac˘a exist˘a un algoritm A ce rezolv˘a problema P iar timpul mediu de execut¸ie al algoritmului A este cel mult T (n). Fiind dat˘a o problem˘a P , o funct¸ie T (n) se spune c˘a este o margine inferioar˘ a dac˘a orice algoritm A ce rezolv˘a problema P va avea cel put¸in un timp T (n) pentru unele date de intrare de dimensiune n, atunci cˆand n tinde la infinit (n → ∞). Definit¸ia 2.3 Fiind dat˘a o funct¸ie g : N −→ R vom nota cu O(g(n)) mult¸imea: O(g(n)) = {f (n)| ∃c > 0, n0 ≥ 0 a.ˆı. 0 ≤ f (n) ≤ c · g(n), ∀n ≥ n0 }. Vom spune despre f c˘a nu cre¸ste ˆın mod sigur mai repede decˆat funct¸ia g. Pentru a indica faptul c˘a o funct¸ie f (n) este un membru al mult¸imii O(g(n)), vom scrie f (n) = O(g(n)), ˆın loc de f (n) ∈ O(g(n)). Observat¸ia 2.1 Vom prezenta cˆateva propriet˘ a¸ti ale lui O(·): • O(f (n) + g(n)) = O(max(f (n), g(n))).
De exemplu pentru funct¸ia f (n) = 7 · n5 − n2 + 3 · log n aplicˆ and regula valorii maxime vom avea: O(f (n)) = O(max(7 · n5 , n2 , 3 · log n)) adic˘ a O(f (n)) = O(n5 ).
• O(loga n) = O(logb n); 23
• f (n) = O(f (n)) (reflexivitate); • f (n) = O(g(n)) ¸si g(n) = O(h(n)) atunci f (n) = O(h(n)) (tranzitivitate). Definit¸ia 2.4 Fiind dat˘a o funct¸ie g : N −→ R vom nota cu Θ(g(n)) mult¸imea: Θ(g(n)) = {f (n)| ∃c1 , c2 > 0, ∃n0 ≥ 0 a.ˆı. 0 ≤ c1 · g(n) ≤ f (n) ≤ c2 · g(n), ∀n ≥ n0 } Spunem c˘a g(n) este o margine asimptotic tare pentru f (n). Definit¸ia 2.5 Fiind dat˘a o funct¸ie g : N −→ R vom nota cu Ω(g(n)) mult¸imea: Ω(g(n)) = {f (n)| ∃c > 0, n0 ≥ 0 a.ˆı. 0 ≤ c · g(n) ≤ f (n), ∀n ≥ n0 } La fel cum O furnizeaz˘a o delimitare asimptotic˘a superioar˘a pentru o funct¸ie, Ω furnizeaz˘a o delimitare asimptotic˘a inferioar˘a pentru aceasta. Teorema 2.2 Pentru orice dou˘a funct¸ii f (n) ¸si g(n), avem f (n) = Θ(g(n)) ⇐⇒ f (n) = O(g(n)) ¸si f (n) = Ω(g(n)). Definit¸ia 2.6 Fiind dat˘a o funct¸ie g : N −→ R vom nota cu o(g(n)) mult¸imea: o(g(n)) = {f (n)| ∀c > 0, ∃n0 > 0 a.ˆı. 0 ≤ f (n) < c · g(n), ∀n ≥ n0 } Observat¸ia 2.3 f (n) = o(g(n)) ⇐⇒ limn→∞
f (n) g(n)
= 0.
Definit¸ia 2.7 f (n) ∈ ω(g(n)) ⇐⇒ g(n) ∈ o(f (n)) ω(g(n)) este o mult¸ime ce se define¸ste astfel: ω(g(n)) = {f (n)| ∀c > 0, ∃n0 > 0 a.ˆı. 0 ≤ c · g(n) < f (n), ∀n ≥ n0 } Observat¸ia 2.4
1. f (n) = Θ(g(n)), g(n) = Θ(h(n)) =⇒ f (n) = Θ(h(n)) (tranzitivitate);
2. f (n) = Θ(f (n)) (reflexivitate); 3. f (n) = Θ(g(n)) ⇐⇒ g(n) = Θ(f (n)) (simetrie). Definit¸ia 2.8 O funct¸ie f are o cre¸stere exponent¸ial˘ a dac˘ a ∃c > 1 a.i. f (x) = Ω(cx ) ¸si x ∃d a.ˆı. f (x) = O(d ). O funct¸ie f este polinomial˘ a de gradul d dac˘ a f (n) = Θ(nd ) ¸si ′ f (n) = O(nd ), ∀d′ ≥ d. Teorema 2.5
Exemplul 2.6 Deoarece
c g(n) lim = 0 n→∞ f (n) ∞
⇒ g(n) ∈ Θ(f (n)), c > 0 ⇒ g(n) ∈ o(f (n)) ⇒ f (n) ∈ o(g(n))
(2.1)
• pentru x ∈ R∗+ avem xn ∈ o(n!). xn xn xn xn 1 < 4 ≤ = = ( )n 4 4 n/2 2n n! (x ) x x x . . x} | .{z
(2.2)
⌈n/2⌉ori
rezult˘a c˘a
xn =0 n→∞ n! lim
24
(2.3)
• log n ∈ o(n).
1 log x (log x)′ x ln 2 lim = lim = lim =0 x→∞ x x→∞ (x)′ x→∞ 1
• pentru a, b > 1, a, b ∈ R∗ avem loga n ∈ Θ(logb n). Calcularea cu exactitate a timpului de execut¸ie al unui program oarecare se poate dovedi o activitate dificil˘a. De aceea, ˆın practic˘a se utilizeaz˘a estim˘ari ˆıncercˆandu-se eliminarea constantelor ¸si simplificarea formulelor ce intervin ˆın cadrul evalu˘arii. Dac˘a dou˘a p˘art¸i ale unui program, P1 ¸si P2 , au timpii de execut¸ie corespunz˘atori T1 (n) ¸si T2 (n), unde T1 (n) = O(f (n)) ¸si T2 (n) = O(g(n)), atunci programul P1 ⊕ P2 va avea timpul de execut¸ie T1 (n) + T2 (n) = O(max(f (n), g(n))) (regula sumei ), unde ⊕ reprezint˘a operat¸ia de concatenare. Prin aplicarea acestei reguli rezult˘a faptul c˘a timpul necesar execut¸iei unui num˘ar finit de operat¸ii este, neluˆand ˆın considerare constantele, caracterizat ˆın principal de c˘atre timpul de execut¸ie al operat¸iei cele mai costisitoare. Cea de–a doua regul˘a, regula produsului, spune c˘a fiind date dou˘a funt¸ii f (n) ¸si g(n) astfel ˆıncˆat T1 (n) = O(f (n)) ¸si T2 (n) = O(g(n)), atunci avem T1 (n) · T2 (n) = O(f (n) · g(n)). Pe baza regulilor produsului ¸si sumei putem face urm˘atoarele reduceri: O(1) ⊆ O(log n) ⊆ O(n) ⊆ O(n log n) ⊆ O(n2 ) ⊆ O(n3) ⊆ O(2n ) Prezent˘am ˆın continuare cˆateva reguli generale pentru evaluarea complexit˘a¸tii unui algoritm: • timpul de execut¸ie al unei instruct¸iuni de atribuire, citire sau afi¸sare a unei variabile este O(1). • timpul de execut¸ie al unei secvent¸e de instruct¸iuni este proport¸ional cu timpul instruct¸iunii care dureaz˘a cel mai mult. • timpul de execut¸ie al unei instruct¸iuni de decizie (if ) este timpul execut˘arii instruct¸iunilor de pe ramura aleas˘a plus timpul necesar evalu˘arii condit¸iei. • timpul de execut¸ie pentru o instruct¸iune de ciclare este suma, dup˘a num˘arul de pa¸si pe care ˆıi realizeaz˘a instruct¸iunea de ciclare, dintre timpul necesar execut˘arii corpului instruct¸iunii plus timpul necesar evalu˘arii condit¸iei. 1: for i ← 1, n do 2: A(i) 3: end for
Dac˘a instruct¸iunii compuse A(i) ˆıi corespunde un timp de execut¸ie constant t, ce nu depinde de i, atunci timpul corespunz˘ator ˆıntregii instruct¸iuni de ciclare for anterioare este: n n X X t=t 1 = t · n = O(n) (2.4) i=1
i=1
ˆIn cazul general, timpul de execut¸ie al instruct¸iunii compuse A(i) depinde de pasul i, P notat cu ti . Astfel timpul total corespunz˘ator instruct¸iunii for este ni=1 ti .
• Instruct¸iunile de ciclare al c˘aror num˘ar de pa¸si depinde de ˆındeplinirea unei condit¸ii (while) sau de neˆındeplinirea acesteia (repeat ... until), sunt mult mai dificil de analizat deoarece nu exist˘a o metod˘a general˘a de a afla cu exactitate num˘arul de repet˘ari al corpului instruct¸iunii. De exemplu, pentru fragmentul urm˘ator 25
1: i ← l 2: while (ai < x) do 3: ... 4: i←i+1 5: end while
⊲ calcule ce pot modifica, direct sau indirect, valoarea lui ai
nu se poate preciza de cˆate ori se va ajunge la realizarea instruct¸iunii de incrementare din interiorul instruct¸iunii while. ˆIn marea majoritate a cazurilor se utilizeaz˘a teoria probabilit˘a¸tilor ˆın vederea obt¸inerii unei estim˘ari a acestui num˘ar. Exemplul 2.7 De exemplu 1: for i ← 1, n do 2: instructiune1 3: end for
are timpul de execut¸ie O(n). 1: for i ← 1, n do 2: for j ← 1, m do 3: instructiune2 4: end for 5: end for
Timpul de execut¸ie pentru secvent¸a de cod ce cont¸ine dou˘ a instruct¸iuni de ciclare imbricate este O(n · m). Exemplul 2.8 S˘a consider˘am un alt exemplu: se d˘ a un P¸sir A de n numere reale, ¸si se dore¸ste calcularea elementelor unui ¸sir B astfel ˆıncˆ at bi = 1i · ij=1 aj , pentru i = 1, n. 1: for i ← 1, n do 2: s←0 3: for j ← 1, i do 4: s ← s + aj 5: end for 6: bi ← si 7: end for 8: return
Dac˘a not˘am cu o constant˘a cx timpul necesar pentru efectuarea unei operat¸ii atomice, vom obt¸ine: costul efectu˘arii liniei 1 este c1 · n, al liniei 2 este c2 · n, al liniei 3 este c3 · i, al liniei 4 este c4 · i, al liniei 6 este c5 · n iar al liniei 8 este c6 . T (n) = c1 n + c2 n +
n X i=1
c3 i +
n X
c4 i + c5 n + c6 = c1 n + c2 n + c3
i=1
n X i=1
i + c4
n X
i + c5 n + c6
i=1
n(n + 1) = (c3 + c4 ) + n(c1 + c2 + c5 ) + c6 2 1 1 = (c3 + c4 )n2 + (c3 + c4)n + n(c1 + c2 + c5 ) + c6 2 2 1 1 (c3 + c4 )n2 + [ (c3 + c4 ) + c1 + c2 + c5 ]n + c6 = n2 · p + n · q + c6 = 2 2 De obicei nu se efectueaz˘a o analiz˘a a¸sa de detaliat˘ a a algoritmului, dar se ˆıncearc˘a o evaluare a blocurilor principale cum ar fi instruct¸iunile de ciclare, atribuindu–le direct valori corespunz˘atoare complexit˘a¸tii timp, atunci cˆ and este posibil: T (n) = O(n2 ) + O(n) + O(1) = O(n2 ) 26
(2.5)
Vom modifica algoritmul anterior astfel ˆıncˆ at s˘ a reducem num˘ arul de calcule efectuate: 1: 2: 3: 4: 5: 6:
s←0 for i ← 1, n do s ← s + ai bi ← si end for return
Pentru aceast˘a variant˘a de lucru, complexitatea timp este urm˘ atoarea: T (n) = O(1)(linia 1) + O(n)(liniile 2–4) + O(1)(linia 6) = O(n)
(2.6)
ˆIn urma analizei de complexitate, putem concluziona c˘ a cea de-a doua variant˘ a a algoritmului ruleaz˘a ˆıntr–un timp liniar.
2.1
Metoda substitut¸iei
Metoda substitut¸iei presupune mai ˆıntˆai ghicirea (estimarea) formei solut¸iei pentru relat¸ia de recurent¸˘a ¸si apoi demonstrarea prin induct¸ie matematic˘a a corectitudinii solut¸iei alese. Metoda poate fi aplicat˘a ˆın cazul unor ecuat¸ii pentru care forma solut¸iei poate fi estimat˘a. S˘a consider˘am urm˘atoarea relat¸ie de recurent¸˘a: ( 1 , dac˘a n = 1 (2.7) T (n) = n 2T ( 2 ) + n , ˆın rest Vom presupune c˘a solut¸ia acestei relat¸ii de recurent¸˘a este T (n) = O(n log n) (not˘am log2 n = log n). Folosind metoda induct¸iei matematice, vom ˆıncerca s˘a demonstr˘am inegalitatea: T (n) ≤ cn log n. (2.8)
Presupunem mai ˆıntˆai c˘a inecuat¸ia (2.8) are loc pentru ∀k < n, inclusiv pentru n2 , adic˘a T ( n2 ) ≤ c · n2 · log ( n2 ). Vom demonstra c˘a inecuat¸ia (2.8) este ˆındeplinit˘a ¸si pentru n: T (n) = 2T ( n2 ) + n. n n n T (n) ≤ 2(c log ) + n = cn log + n 2 2 2 = cn log n − cn log 2 + n = cn log n − cn + n ≤ cn log n
(2.9)
Metoda induct¸iei matematice presupune s˘a ar˘at˘am c˘a solut¸ia respect˘a ¸si cazurile particulare (T (1) = 1). Pentru n = 1 vom avea T (1) = c · 1 · log 1 = 0 ceea ce contrazice relat¸ia T (1) = 1. Aceast˘a situat¸ie poate fi rezolvat˘a dac˘a consider˘am c˘a T (n) = cn log n, ∀n ≥ n0 (n0 este o constant˘a). Din T (2) = 4 ¸si T (3) = 5 vom alege valoarea parametrului c astfel ˆıncˆat s˘a fie ˆındeplinite inegalit˘a¸tile T (2) ≤ 2c log 2 ¸si T (3) ≤ 3c log 3. Se observ˘a c˘a orice valoare a lui c ≥ 2 satisface inegalit˘a¸tile anterioare.
2.2
Schimbarea de variabil˘ a
Aceast˘a metod˘a presupune realizarea unei schimb˘ari de variabil˘a ˆın vederea simplific˘arii formulei sau pentru reg˘asirea unei formule deja cunoscut˘a. 27
√ S˘a consider˘am ecuat¸ia T (n) = 2T ( n) + log n. Dac˘a ˆınlocuim pe n cu 2m obt¸inem: m
m
T (2m ) = 2T (2 2 ) + log 2m = 2T (2 2 ) + m
(2.10)
Not˘am cu S(m) = T (2m ) ¸si ˆınlocuind ˆın (2.10) obt¸inem o nou˘a relat¸ie de recurent¸˘a: m S(m) = 2S( ) + m (2.11) 2 Se observ˘a c˘a aceast˘a relat¸ie are o form˘a similar˘a cu cea din formula (2.7). Solut¸ia relat¸iei (2.11) este: S(m) = O(m log m) ⇒ T (n) = T (2m ) = S(m) = O(m log m) = O(log n log log n)
(2.12)
ˆIn concluzie avem T (n) = O(log n log log n).
2.3
Metoda iterativ˘ a
Metoda iterativ˘a este mult mai eficient˘a deoarece nu presupune ’ghicirea’ solut¸iei, variant˘a ce conduce deseori la rezultate gre¸site ¸si timp pierdut. De exemplu, s˘a consider˘am formula de recurent¸˘a: ( 1 , dac˘a n = 1 (2.13) T (n) = n T ( 2 ) + 1 , ˆın rest Pentru rezolvarea acestei recurent¸e vom substitui succesiv pe n cu n2 : n T (n) = T ( ) + 1 2 n = T( ) + 2 4 n = T( ) + 3 8 ... n = T( k) + k 2 n Prin urmare atunci cˆand 1 = 2k ⇒ k = log n, vom avea T (n) = 1 + k = 1 + log n ⇒ T (n) = O(log n). Fie o alt˘a formul˘a de recurent¸˘a: ( 1 , dac˘a n = 1 T (n) = n 2T ( 2 ) + n , ˆın rest ˆInlocuind succesiv pe n cu
(2.14)
(2.15)
n 2
vom obt¸ine urm˘atoarea serie de identit˘a¸ti: n T (n) = 2T ( ) + n 2 n n n = 2(2T ( ) + ) + n = 4T ( ) + 2n 2 2 4 n n n (2.16) = 4(2T ( ) + ) + 2n = 8T ( ) + 3n 8 4 8 ... n = 2k T ( k ) + kn 2 Deoarece substitut¸ia se opre¸ste atunci cˆand 2nk = 1 adic˘a pentru k = log n, vom avea: T (n) = 2k + kn = n + n log n = O(n log n) 28
(2.17)
2.4
Teorema master
Metoda master pune la dispozit¸ie o variant˘a de rezolvare a unor recurent¸e de forma n T (n) = aT ( ) + f (n) b
(2.18)
unde a ≥ 1, b > 1 sunt constante, iar f (n) este o funct¸ie asimptotic pozitiv˘a. Formula de recurent¸˘a (2.18) descrie timpul de execut¸ie al unui algoritm ce presupune descompunerea unei probleme de dimensiune n ˆın a subprobleme, fiecare avˆand dimensiunea datelor de intrare nb . f (n) reprezint˘a costul asociat ˆımp˘art¸irii datelor de intrare cˆat ¸si costul combin˘arii rezultatelor celor a subprobleme. Teorema 2.9 (Teorema master) [30] Fie a ≥ 1 ¸si b > 1 dou˘ a constante, f (n) o funct¸ie asimptotic pozitiv˘a, ¸si T (n) definit˘a de relat¸ia de recurent¸a T (n) = aT ( nb ) + f (n), unde nb va fi ⌊ nb ⌋ sau ⌈ nb ⌉. Atunci T (n) este m˘arginit˘a asimptotic dup˘ a cum urmeaz˘ a: 1. dac˘a f (n) = O(nlogb a−ǫ ) pentru o constant˘ a ǫ > 0, atunci T (n) = Θ(nlogb a ); 2. dac˘a f (n) = Θ(nlogb a logk n), atunci T (n) = Θ(nlogb a logk+1 n) (de obicei k = 0); 3. dac˘a f (n) = Ω(nlogb a+ǫ ) pentru o constant˘ a ǫ > 0, ¸si dac˘ a af ( nb ) ≤ cf (n) pentru o constant˘a c < 1 ¸si oricare num˘ar n suficient de mare, atunci T (n) = Θ(f (n)). Corolarul 2.10 Pentru o funct¸ie f de tip polinomial unde f (n) = cnk avem: 1. dac˘a a > bk atunci T (n) = O(nlogb a ); 2. dac˘a a = bk atunci T (n) = O(nk log n); 3. dac˘a a < bk atunci T (n) = O(nk ). Exemplul 2.11 • Fie T (n) = 2T ( n2 )+n. Avem a = 2, b = 2, k = 1, f (n) = nk . Aplicˆand corolarul pentru cazul a = bk , solut¸ia este T (n) = O(nk log n) = O(n log n). • Fie T (n) = 9T ( n3 ) + n. Avem a = 9, b = 3, k = 1. Aplicˆ and corolarul pentru cazul k log3 9 2 a > b , se obt¸ine solut¸ia T (n) = O(n ) = O(n ). • Fie T (n) = 4T ( n2 ) + n3 . Avem a = 4, b = 2, k = 3, f (n) = n3 . Aplicˆ and corolarul k 3 pentru cazul a < b , se obt¸ine solut¸ia T (n) = O(n ). • Fie T (n) = 2n T ( n2 ) + nn . Nu se poate aplica Teorema master deoarece a = 2n nu este constant. • Fie T (n) = 2T ( n2 ) + n log n. Atunci avem a = 2, b = 2, k = 1, f (n) = Θ(nlog2 2 logk n), ¸si aplicˆand Teorema master, cazul al doilea, obt¸inem: T (n) = Θ(n log2 n). • Fie T (n) = 12 T ( n2 ) + n1 . Nu se poate aplica Teorema master deoarece a = 12 < 1. √ √ • Fie T (n) = 2T ( n2 ) + log n. Aplicˆ and Teorema master pentru a = 2, b = 2, f (n) = √ 1 O(n 2 −ǫ ) obt¸inem: T (n) = Θ( n). • Fie T (n) = 3T ( n4 ) + n log n. Aplicˆ and Teorema master pentru a = 3, b = 4, f (n) = Ω(nlog4 3+ǫ ) obt¸inem: T (n) = Θ(n log n). 29
• Fie T (n) = 16T ( n4 ) − n log n. Nu se poate aplica Teorema master deoarece f (n) = −n log n nu este o funct¸ie asimptotic pozitiv˘ a. Exemplul 2.12 Analiza act¸iunilor Deschiderea unei act¸iuni la o anumit˘ a dat˘ a calendaristic˘ a se calculeaz˘ a drept num˘arul maxim de zile consecutive (pˆan˘a la acea dat˘ a) ˆın care pret¸ul act¸iunii a fost mai mic sau egal cu pret¸ul din ziua respectiv˘a. Fie pk pret¸ul unei act¸iuni ˆın ziua k iar dk deschiderea calculat˘a ˆın aceea¸si zi. ˆIn continuare se prezint˘a un exemplu de calcul al deschiderii unei act¸iuni pentru un num˘ar de 7 zile: p0 9 d0 1
p1 6 d1 1
p2 3 d2 1
p3 4 d3 2
p4 2 d4 1
p5 5 d5 4
p6 7 d6 6
Algoritm 15 Algoritm de calcul al deschiderii unei act¸iuni (prima variant˘a) 1: procedure ComputeSpan1(p, n; d) 2: for k ← 0, n − 1 do 3: j←1 4: while (j < k) ∧ (pk−j ≤ pk ) do 5: j ←j+1 6: end while 7: dk ← j 8: end for 9: return 10: end procedure
Algoritmul 15 calculeaz˘a deschiderea unei act¸iuni pentru un interval mai lung de timp pe baza evolut¸iei pret¸urilor acesteia. Timpul de execut¸ie al acestui algoritm este O(n2 ). Vom prezenta un alt mod de calcul al deschiderii unei act¸iuni folosind o stiv˘ a (vezi algoritmul 16). Stiva este o structur˘a de date de tip container deoarece ea depoziteaz˘ a elemente de un anumit tip. Pentru a putea s˘a oper˘am asupra unei colect¸ii de elemente p˘ astrate ˆıntr–o stiv˘a se definesc urm˘atoarele operat¸ii, ˆın afar˘ a de operat¸iile fundamentale push ¸si pop: • push(x:object) – adaug˘a obiectul x ˆın vˆ arful stivei. • pop():object – elimin˘a ¸si ˆıntoarce obiectul din vˆ arful stivei; dac˘ a stiva este goal˘a avem o situat¸ie ce genereaz˘a o except¸ie. • peek():object – ˆıntoarce valoarea obiectului din vˆ arful stivei f˘ ar˘ a a–l extrage. • size():integer – ˆıntoarce num˘ arul de obiecte din stiv˘ a. • isEmpty():boolean – ˆıntoarce true dac˘ a stiva nu cont¸ine nici un obiect. • isFull():
boolean – ˆıntoarce true dac˘ a stiva este plin˘ a.
• init(capacitate:integer) – init¸ializeaz˘ a stiva. Timpul de execut¸ie al noului algoritm este O(n). 30
Algoritm 16 Algoritm de calcul al deschiderii unei act¸iuni (a doua variant˘a) 1: procedure ComputeSpan2(p, n; d) 2: call init(S) 3: for k ← 0, n − 1 do 4: ind ← 1 5: while (isEmpty(S) = f alse) ∧ (ind = 1) do 6: if (pk ≥ ppeek(S)) then 7: call pop(S) 8: else 9: ind ← 0 10: end if 11: end while 12: if (ind = 1) then 13: h ← −1 14: else 15: h ← peek(S) 16: end if 17: dk ← k − h 18: call push(S, k) 19: end for 20: return 21: end procedure
2.5
Exercit¸ii
1. S˘a se evalueze complexitatea algoritmului 17, ce realizeaz˘a descompunerea ˆın factori primi a unui num˘ar natural. Algoritm 17 Algoritm pentru descompunerea ˆın factori primi a unui num˘ar natural 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
Input {n} j←2 while (j ≤ n) do if (n mod j = 0) then k←0 while (n mod j = 0) do k ←k+1 n ← n div j end while Output {j k } end if j ← j +1 end while
2. Pentru fiecare dintre urm˘atoarele relat¸ii de recurent¸˘a calculat¸i complexitatea timp: (a) T (n) = 2T ( n2 ) +
n ; log n
(b) T (n) = 16T ( n4 ) + n!; √ (c) T (n) = 3T ( n3 ) + n; 31
(d) T (n) = 3T ( n3 ) + n2 . 3. S˘a se calculeze timpul mediu de lucru T (n) al algoritmului 18. Algoritm 18 Algoritm Calcul5 1: for i ← 1, n do 2: j←n 3: while (j ≥ 1) do 4: ... 5: j ← ⌊ 2j ⌋ 6: end while 7: end for
4. S˘a se calculeze timpul mediu de lucru T (n) al algoritmului 19. Algoritm 19 Algoritm Calcul6 1: i ← n 2: while (i ≥ 1) do 3: j←i 4: while (j ≤ n) do 5: ... 6: j ←j·2 7: end while 8: i ← ⌊ 2i ⌋ 9: end while
5. ˆIntr-o secvent¸˘a de n numere naturale (a1 , a2 , . . . , an , ai ∈ N, 1 ≤ ai ≤ 65535, 1 ≤ n ≤ 107 ) se spune c˘a un num˘ar este majoritar dac˘a num˘arul de aparit¸ii este cel put¸in n2 + 1. S˘a se realizeze un algoritm ce determin˘a dac˘a avem vreun num˘ar majoritar ˆın secvent¸a de n elemente, ¸si ˆın caz afirmativ s˘a se afi¸seze aceast˘a valoare. 6. S˘a se rezolve urm˘atoarele ecuat¸ii recurente: • T (n) = 2 · T ( n2 ) + n lg n, n = 2k ;
• T (n) = 3 · T ( n2 ) + c · n, n = 2k > 1. 7. S˘a se rezolve ecuat¸ia recurent˘a T (n) = n · T 2 ( n2 ), n = 2k , T (1) = 6. 8. S˘a se arate c˘a ex = 1 + x + Ω(x2 ) unde x → ∞. 9. S˘a se determine primele n elemente ale ¸sirurilor ak ¸si bk date prin urm˘atoarele relat¸ii de recurent¸˘a: 5ak + 3 ak + 3 ak+1 = , bk = , k ≥ 0, a0 = 1. (2.19) ak + 3 ak + 1 10. Fie [P1 P2 . . . Pn ] un poligon convex dat prin coordonatele carteziene ale vˆarfurilor sale, ˆın ordine trigonometric˘a. S˘a se calculeze aria poligonului. 11. S˘a se verifice dac˘a urm˘atoarele afirmat¸ii sunt adev˘arate: (a) n2 ∈ O(n3 ); 32
(b) n3 ∈ O(n2 );
(c) 2n+1 ∈ O(2n );
(d) (n + 1)! ∈ O(n!);
(e) ∀f : N −→ R∗ , f ∈ O(n) ⇒ f 2 ∈ O(n2 ); (f) ∀f : N −→ R∗ , f ∈ O(n) ⇒ 2f ∈ O(2n ).
12. S˘a se realizeze un algoritm ce verific˘a dac˘a un text dat T constituie o permutare circular˘a a unui alt text T0 . 13. Se d˘a o secvent¸˘a de numere ˆıntregi ˆın care pot s˘a existe ¸si numere duplicate. S˘a se realizeze un algoritm care s˘a determine o pereche de numere apart¸inˆand secvent¸ei, a c˘aror sum˘a s˘a fie egal˘a cu o valoare specificat˘a k. 14. S˘a se realizeze o subrutin˘a ce verific˘a dac˘a un ¸sir de caractere S reprezint˘a o anagram˘a a altui ¸sir de caractere S0 . 15. O secvent¸˘a S cont¸ine n − 1 numere naturale distincte ale c˘aror valori apart¸in mult¸imii de valori {1, 2, . . . , n}. S˘a se realizeze un algoritm ce determin˘a ce num˘ar din intervalul [1, n] lipse¸ste din secvent¸˘a. 16. S˘a se elaboreze un algoritm pentru ¸stergerea dintr–un vector a unui element aflat pe pozit¸ia k.
33
Capitolul 3 Grafuri. Grafuri neorientate 3.1
Not¸iuni de baz˘ a
Fie V o mult¸ime finit˘a ¸si nevid˘a avˆand n elemente (V = {x1 , x2 , . . . , xn }). Fie E o mult¸ime finit˘a astfel ˆıncˆat E ⊆ V × V (unde V × V este produsul cartezian al mult¸imii V cu ea ˆıns˘a¸si) ¸si E = {(x, y)|x, y ∈ V } (E este mult¸imea perechilor (x, y) cu proprietatea c˘a x ¸si y apart¸in mult¸imii V ). Definit¸ia 3.1 Se nume¸ste graf o pereche ordonat˘ a G = (V, E). Elementele xi ∈ V se numesc noduri sau vˆ arfuri. Elementele mult¸imii E sunt arce sau muchii. O muchie (xk , xl ) ∈ E se mai noteaz˘ a ¸si cu [xk , xl ]. |V | se nume¸ste ordinul grafului G ¸si reprezint˘a num˘arul vˆarfurilor acestuia, iar |E| se nume¸ste dimensiunea grafului G ¸si reprezint˘a num˘arul muchiilor/ arcelor grafului G. Graful Φ = (∅, ∅) se nume¸ste graful vid. Dac˘a ˆıntr-o pereche [xk , xl ] nu ¸tinem cont de ordine atunci graful este neorientat iar perechea reprezint˘a o muchie ([xk , xl ] = [xl , xk ]). Dac˘a se introduce un sens fiec˘arei muchii atunci aceasta devine arc iar graful se nume¸ste orientat ([xk , xl ] 6= [xl , xk ]). Muchiile ce au acelea¸si vˆarfuri se spune c˘a sunt paralele. O muchie de forma [u, u] se nume¸ste bucl˘ a. Un graf G se nume¸ste simplu dac˘a oricare dou˘a vˆarfuri ale sale sunt extremit˘a¸ti pentru cel mult o muchie. Un graf este finit dac˘a mult¸imile V ¸si E sunt finite. ˆIn continuare vom considera un graf neorientat, simplu ¸si finit. Dac˘a [x, y] ∈ E vom spune c˘a x ¸si y sunt adiacente, iar muchia [x, y] este incident˘a cu vˆarfurile x ¸si y. x ¸si y se
Fig. 3.1: a) Un exemplu de graf neorientat cu 6 vˆarfuri b) Graful complet K5
34
mai numesc capetele muchiei. Dou˘a muchii sunt adiacente dac˘a au un vˆarf comun. Un graf este trivial dac˘a are un singur vˆarf. Dac˘a E = ∅ atunci graful G = (V, E) se nume¸ste graf nul. Observat¸ia 3.1 Un graf neorientat, simplu, finit poate fi utilizat drept model de reprezentare al unei relat¸ii simetrice peste o mult¸ime. Definit¸ia 3.2 Se nume¸ste graf complet de ordinul n, ¸si se noteaz˘ a cu Kn , un graf cu proprietatea c˘a oricare dou˘a vˆarfuri distincte ale sale sunt adiacente (∀x ∈ V, y ∈ V, x 6= y ⇒ [x, y] ∈ E). Exemplul 3.2 S˘a consider˘am grafurile din figura 3.1. a) G = (V, E), V = {1, 2, 3, 4, 5, 6}, E = {[1, 2], [1, 5], [2, 3], [2, 6], [3, 4], [4, 5], [5, 6]} (vezi figura 3.1 a)); b) K5 este graful complet cu 5 vˆarfuri. Vˆ arfurile 3 ¸si 5 sunt adiacente (vezi figura 3.1 b)).
Fig. 3.2: a) Un exemplu de graf neorientat cu 5 vˆarfuri. b) Subgraf al grafului din figura 3.1 b). c) Graf part¸ial al grafului din figura 3.1 b).
Definit¸ia 3.3 Un graf part¸ial al unui graf dat G = (V, E) este un graf G1 = (V, E1 ) unde E1 ⊆ E. Definit¸ia 3.4 Un subgraf al unui graf G = (V, E) este un graf H = (V1 , E1 ) unde V1 ⊂ V iar muchiile din E1 sunt toate muchiile din E care au ambele extremit˘ a¸ti ˆın mult¸imea V1 (E1 = E|V1 ×V1 = {[x, y]|[x, y] ∈ E, x, y ∈ V1 }). Se spune c˘a graful H este indus sau generat de submult¸imea de vˆarfuri V1 ¸si se noteaz˘a H = G|V1 sau H = [V1 ]G . Iat˘a alte cˆateva notat¸ii des ˆıntˆalnite: - G − V1 = subgraful ce se obt¸ine din G prin eliminarea submult¸imii de vˆarfuri V1 (V1 ⊆ V ); - G − x = subgraful G − {x}; - < E1 >G = graful part¸ial al lui G generat de E1 ; 35
- G − E1 =< E \ E1 >G ; - G − e = G − {e}, graful part¸ial obt¸inut prin eliminarea unei muchii e. Exemplul 3.3 Graful part¸ial din figura 3.2 c) se obt¸ine din graful 3.1 b) prin ¸stergerea muchiilor [2, 4], [2, 5], [3, 5], [4, 5]. Subgraful din figura 3.2 b) este indus de mult¸imea V1 = {1, 3, 4, 5} din graful complet K5 (H = K5 |V1 ). Definit¸ia 3.5 Gradul unui vˆarf este egal cu num˘ arul muchiilor incidente cu vˆ arful x ¸si se noteaz˘a cu d(x) (d(x) = |{[x, u]|[x, u] ∈ E, u ∈ V }|). Un vˆ arf cu gradul 0 (d(x) = 0) se nume¸ste vˆ arf izolat. Not˘am δ(G) = min{dG (u)|u ∈ G} ¸si ∆(G) = max{dG (u)|u ∈ G}.
Fig. 3.3: Graful lui Petersen
Exemplul 3.4 Graful lui Petersen din figura 3.3 este un exemplu de graf trivalent sau cubic (toate vˆarfurile grafului au acela¸si grad, 3). Propozit¸ia 3.5 Pentru un graf G = (V, E), V = {x1 , x2 , . . . , xn }, |E| = m, avem urm˘atoarea relat¸ie: n X d(xk ) = 2m. (3.1) k=1
Astfel ˆın orice graf G exist˘a un num˘ar par de vˆarfuri al c˘aror grad este un num˘ar impar. Definit¸ia 3.6 Se nume¸ste secvent¸a ˘ grafic˘ a un ¸sir de numere naturale d1 , d2 , . . . , dn cu proprietatea c˘a ele reprezint˘a gradele vˆ arfurilor unui graf neorientat. Corolarul 3.6 Pentru ca o secvent¸˘a de numere naturale d1 , d2 , . . . , dn s˘ a fie secvent¸˘a grafic˘a, este necesar ca: 1. ∀k = 1, n, dk ≤ n − 1; Pn 2. ar par. k=1 dk este un num˘
Definit¸ia 3.7 Un lant¸ L = [v0 , v1 , . . . , vm ] este o succesiune de vˆ arfuri cu proprietatea c˘a oricare dou˘ a vˆarfuri vecine sunt adiacente ([vi , vi+1 ] ∈ E, ∀i = 0, m − 1). Vˆ arfurile v0 ¸si vm se numesc extremit˘ a¸tile lant¸ului, iar m reprezint˘ a lungimea lant¸ului. 36
Dac˘a vˆarfurile v0 , v1 , . . . , vm sunt distincte dou˘a cˆate dou˘a, lant¸ul se nume¸ste elementar (vi 6= vj , ∀i, j = 0, m). Definit¸ia 3.8 Un lant¸ L pentru care v0 = vm se nume¸ste ciclu. Definit¸ia 3.9 Se nume¸ste ciclu hamiltonian un ciclu elementar ce trece prin toate vˆarfurile grafului. Un graf ce admite un ciclu hamiltonian se nume¸ste graf Hamilton sau graf hamiltonian. Definit¸ia 3.10 Un lant¸ L ce cont¸ine fiecare muchie exact o singur˘ a dat˘ a se nume¸ste lant¸ eulerian. Dac˘a v0 = vm ¸si lant¸ul este eulerian atunci ciclul se nume¸ste ciclu eulerian. Un graf ce cont¸ine un ciclu eulerian se nume¸ste graf eulerian. Exemplul 3.7 [1, 2, 3, 1, 4] este un exemplu de lant¸ ˆın graful din figura 3.2 c). Vˆarfurile 1 ¸si 4 sunt extremit˘a¸tile lant¸ului. Lant¸ul nu este elementar deoarece vˆ arful 1 se ˆıntˆalne¸ste de dou˘a ori, ˆın schimb [1, 2, 3, 4, 1] este un ciclu elementar. [3, 4, 5, 1, 2, 3] este un ciclu hamiltonian ˆın graful din figura 3.2 a). [4, 5, 1, 2, 4, 3] este un ˆ acest graf nu exist˘ lant¸ eulerian, precum ¸si [2, 4, 3, 2, 1, 5, 4]. In a nici un ciclu eulerian.
Fig. 3.4: Componente conexe
Definit¸ia 3.11 Un graf se nume¸ste conex dac˘ a pentru orice pereche de vˆ arfuri x ¸si y exist˘a un lant¸ de la x la y (∀x, y ∈ V, ∃x y). Se nume¸ste component˘ a conex˘ a un subgraf conex maximal, adic˘ a un subgraf conex ˆın care nici un vˆarf din subgraf nu este adiacent cu unul din afara lui prin intermediul unei muchii apart¸inˆand grafului init¸ial. Definit¸ia 3.12 O muchie e ∈ E se nume¸ste muchie critic˘ a dac˘ a prin eliminarea acesteia o component˘a conex˘a se ˆımparte ˆın dou˘ a sau mai multe componente conexe. Exemplul 3.8 Graful din figura 3.4 are dou˘ a componente conexe: {1, 2, 3, 4, 5, 6} ¸si {7, 8, 9}. Muchia [2, 5] este muchie critic˘a. Definit¸ia 3.13 Un graf planar este un graf ce poate fi reprezentat ˆın plan astfel ˆıncˆat muchiile sale s˘a nu se intersecteze dou˘ a cˆ ate dou˘ a. Definit¸ia 3.14 Un graf G = (V, E) se nume¸ste graf bipartit dac˘ a exist˘ a o partit¸ie a mult¸imii nodurilor {V1 , V2 } (V = V1 ∪V2 , V1 ∩V2 = ∅, V1 , V2 6= ∅) astfel ˆıncˆ at orice muchie din E va avea o extremitate ˆın mult¸imea V1 ¸si cealalt˘ a extremitate ˆın mult¸imea V2 (∀[x, y] ∈ E avem x ∈ V1 ¸si y ∈ V2 ). 37
Propozit¸ia 3.9 Un graf este bipartit dac˘ a ¸si numai dac˘ a nu cont¸ine cicluri de lungime impar˘a. Definit¸ia 3.15 Un graf bipartit este complet dac˘ a ∀x ∈ V1 , ∀y ∈ V2 , ∃[x, y] ∈ E (G = (V, E), V = V1 ∪ V2 , V1 ∩ V2 = ∅). Dac˘ a |V1 | = p, |V2 | = q atunci graful bipartit complet se noteaz˘a Kp,q .
Fig. 3.5: Un exemplu de graf bipartit ¸si graf bipartit complet.
Exemplul 3.10 ˆIn figura 3.5 a) este ilustrat un graf bipartit (V1 = {1, 2, 3}, V2 = {4, 5}), iar ˆın cazul b) avem un graf bipartit complet, K2,3 . Definit¸ia 3.16 Se nume¸ste izomorfism de la graful G la graful G′ o funct¸ie bijectiv˘a ϕ : V (G) → V (G′ ) astfel ˆıncˆat [u, v] ∈ E(G) ⇔ ϕ([u, v]) ∈ E(G′ ). Dac˘a exist˘a un izomorfism de la graful G la graful G′ atunci se spune c˘ a G este izomorf ′ ′ cu G ¸si not˘ am acest lucru astfel: G ≅ G . Definit¸ia 3.17 Un graf etichetat este un graf ˆın care fiecare muchie ¸si vˆ arf poate avea asociat˘a o etichet˘a. Vom prezenta ˆın continuare cˆateva operat¸ii dintre cele mai ˆıntˆalnite ce se pot efectua asupra unui graf oarecare: • fiind dat un nod se cere lista vecinilor s˘ai. Aceast˘a operat¸ie este cea mai utilizat˘a pentru o serie ˆıntreag˘a de algoritmi pe grafuri; • fiind dat˘a o pereche de noduri {u, v} se cere s˘a se determine dac˘a aceasta constituie o muchie sau un arc al grafului; • ad˘augarea sau ¸stergerea unui nod sau a unei muchii/arc; • translatarea dintr–un mod de reprezentare ˆın altul. De foarte multe ori modul de reprezentare sub forma c˘aruia au fost introduse datele, difer˘a de modul de reprezentare optim recomandat pentru un anumit algoritm. ˆIn aceast˘a situat¸ie este indicat˘a transformarea primului mod de reprezentare ˆın modul de reprezentare optim; • fiind dat˘a o muchie sau un nod se cere o informat¸ie asociat˘a acestui element: de exemplu lungimea muchiei sau distant¸a de la surs˘a la nodul specificat. 38
3.2
Operat¸ii pe grafuri
1. Complementarul grafului G = (V, E) se define¸ste astfel: este graful Gc = (V c , E c ), unde V c = V ¸si E c = {[x, y]|x, y ∈ V, [x, y] ∈ / E}. Altfel spus E c = (V × V ) \ E.
Fig. 3.6: Un exemplu de graf complementar altui graf.
ˆIn figura 3.6 b) este reprezentat graful complementar Gc al grafului G = (V, E), din figura 3.6 a) (V = {1, 2, 3, 4, 5}, E = {[1, 2], [1, 5], [2, 3], [3, 4], [4, 5]}). Conform definit¸iei Gc = (V c , E c ) unde V c = V ¸si E c = {[1, 3], [1, 4], [2, 4], [2, 5], [3, 5]}. 2. Graful obt¸inut din G = (V, E) prin insert¸ia unui vˆarf v ∈ / V pe o muchie [x, y] ∈ E este graful Gi = (V i , E i ) unde V i = V ∪ {v}, E i = (E \ {[x, y]}) ∪ {[x, v], [v, y]}.
Considerˆand graful din figura 3.6 a), prin insert¸ia vˆarfului 6 pe muchia [1, 5] se obt¸ine graful din figura 3.7 a).
3. Graful obt¸inut din G = (V, E) prin contract¸ia unei muchii u = [x, y] la un vˆarf t este graful Gct = (V ct , E ct ), unde V ct = (V \ {x, y}) ∪ {t}.
Fig. 3.7: a) Graful obt¸inut prin inserarea vˆarfului 6 ˆın graful din figura 3.6 a). b) Graful obt¸inut prin contract¸ia muchiei [3, 4] ˆın graful din figura 3.6 a).
ˆIn figura 3.7 b) este reprezentat graful obt¸inut prin contract¸ia muchiei [3, 4] din graful G = (V, E) (vezi figura 3.6 a)). 4. Se nume¸ste graf reprezentativ al muchiilor unui graf G = (V, E), graful GR = (VR , ER ) ˆın care |VR | = |E| ¸si ER = {[e, f ]|e, f ∈ E adiacente} (vezi figura 3.8). 5. Definim graful total al unui graf G = (V, E) ca fiind graful GT = (VT , ET ) unde VT = V ∪ E ¸si ET = {[u, v]|u, v ∈ V ∪ E iar u ¸si v sunt adiacente sau incidente ˆın G}. 39
Fig. 3.8: Figura b) prezint˘a graful reprezentativ al muchiilor grafului din figura a).
Fig. 3.9: a) Un exemplu de graf neorientat. b) Graful total al grafului din figura a).
Graful total GT = (VT , ET ), unde VT = {1, 2, 3, 4, v1, v2 , v3 , v4 }, ¸si E = {[1, 2], [1, 4], [2, 3], [3, 4], [v1, v2 ], [v1 , v4 ], [v2 , v3 ], [v3 , v4 ], [v1 , 1], [v1 , 2], [v2 , 2], [v2 , 3], [v3 , 3], [v3 , 4], [v4 , 4], [v4 , 1]}, corespunde grafului G = (V, E), V = {1, 2, 3, 4}, E = {[1, 2], [1, 4], [2, 3], [3, 4]} (vezi figura 3.9). 6. Reuniunea ¸si intersect¸ia a dou˘a grafuri se definesc astfel: • dac˘a V1 = V2 atunci: G1 ∪ G2 = (V1 , E1 ∪ E2 ), G1 ∩ G2 = (V1 , E1 ∩ E2 );
• dac˘a V1 ∩ V2 6= ∅ atunci: G1 ∪ G2 = (V1 ∪ V2 , E1 ∪ E2 ), G1 ∩ G2 = (V1 ∩ V2 , E1 ∩ E2 ); • dac˘a V1 ∩ V2 = ∅ atunci G1 ∪ G2 se nume¸ste reuniune disjunct˘ a a grafurilor G1 ¸si G2 , iar G1 ∩ G2 este graful vid.
7. Definim suma grafurilor G1 ¸si G2 ca fiind graful complementar al reuniunii complementarelor celor dou˘a grafuri: G1 ⊕ G2 = (Gc1 ∪ Gc2 )c .
Pentru grafurile complete Kp ¸si Kq avem: Kp ⊕ Kq = (Kpc ∪ Kqc )c = Kp+q . Un alt exemplu de sum˘a a dou˘a grafuri poate fi urm˘arit ˆın figura 3.10.
8. Se nume¸ste produsul cartezian al grafurilor G1 = (V1 , E1 ) ¸si G2 = (V2 , E2 ), graful G1 × G2 = (V × , E × ), unde V × = V1 × V2 ¸si E × = {[(u1 , u2), (v1 , v2 )]|u1 , v1 ∈ V1 , u2 , v2 ∈ V2 ; u1 = v1 ¸si [u2 , v2 ] ∈ E2 sau u2 = v2 ¸si [u1 , v1 ] ∈ E1 }. 40
Fig. 3.10: a) Dou˘a grafuri neorientate, G1 ¸si G2 . b) Grafurile complementare Gc1 ¸si Gc2 . c) Reuniunea grafurilor Gc1 ¸si Gc2 . d) Graful complementar al grafului reuniune (Gc1 ∪ Gc2 )c .
Fig. 3.11: Graful produs cartezian a dou˘a grafuri
Fie G1 = (V1 , E1 ) unde V1 = {u1 , v1 } ¸si E1 = {[u1 , v1 ]}, ¸si G2 = (V2 , E2 ), unde V2 = {u2, v2 , w2 } ¸si E2 = {[u2 , v2 ], [v2 , w2]}, dou˘a grafuri neorientate. Atunci graful produs cartezian al grafurilor G1 ¸si G2 este G1 × G2 = (V × , E × ) unde:
V × = {(u1 , u2 ), (u1, v2 ), (u1, w2 ), (v1 , u2 ), (v1 , v2 ), (v1 , w2 )}, ¸si
E × = {[(u1 , u2), (v1 , u2)], [(u1 , u2 ), (u1, v2 )], [(u1 , v2 ), (v1 , v2 )], [(u1 , v2 ), (u1, w2 )], [(u1 , w2), (v1 , w2 )], [(v1 , u2 ), (v1 , v2 )], [(v1 , v2 ), (v1 , w2 )]} (vezi figura 3.11). 9. Se nume¸ste r˘ad˘acina p˘atrat˘a a grafului G, un graf neorientat H cu proprietatea c˘a H 2 = G (H 2 = H × H). 10. Operat¸ia de compunere a dou˘a grafuri neorientate G1 = (V1 , E1 ) ¸si G2 = (V2 , E2 ), notat˘a cu G1 [G2 ], se realizeaz˘a astfel: vˆarful (x1 , y1 ) este adiacent cu vˆarful (x2 , y2) ˆın graful rezultat dac˘a vˆarful x1 este adiacent cu vˆarful x2 ˆın graful G1 sau (x1 = x2 ¸si vˆarful y1 este adiacent cu vˆarful y2 ˆın graful G2 ) (vezi figura 3.12).
41
Fig. 3.12: Graful rezultat ˆın urma compunerii grafurilor G1 ¸si G2 din figura 3.11
3.3
Moduri de reprezentare
Fig. 3.13: Un exemplu de graf neorientat
Pentru o mult¸ime de noduri V vom presupune un mod de ordonare a nodurilor grafului (primul nod, al doilea nod, etc.), deoarece reprezentarea unui graf depinde de aceast˘a ordonare. Astfel, numerot˘am ˆın mod arbitrar vˆarfurile grafului cu 1, 2, . . . , |V |. 1. Matricea de adiacent¸˘a - este o matrice A ∈ Mn×n Z unde n = |V |, iar ai,j = 1 dac˘a exist˘a o muchie ˆıntre nodurile numerotate i ¸si j (ˆıntre xi ¸si xj ), sau ai,j = 0 dac˘a nu exist˘a ( o muchie ˆıntre acestea. ai,j =
1 , dac˘a [xi , xj ] ∈ E 0 , dac˘a [xi , xj ] ∈ /E
Pentru un graf neorientat aceast˘a matrice este simetric˘a (ai,j = aj,i). Exemplul 3.11 Matricea de adiacent¸a ˘ corespunz˘ atoare grafului din figura 3.13 este:
0 1 1 1 A= 0 0 0 0
1 0 0 0 1 0 0 0
1 0 0 0 1 0 0 0
42
1 0 0 0 0 1 0 0
0 1 1 0 0 0 1 0
0 0 0 1 0 0 1 1
0 0 0 0 1 1 0 0
0 0 0 0 0 1 0 0
Fig. 3.14: Un exemplu de graf neorientat ponderat
Matricea de adiacent¸˘a are un num˘ar de n2 elemente, iar elementele neredundante sunt . Prin urmare complexitatea–spat¸iu a matricii de adiacent¸˘a este ˆın num˘ar de n(n−1) 2 O(n2). 2. Matricea costurilor - reprezint˘a o variant˘a a matricei de adiacent¸˘ a ce se obt¸ine ˆın mod natural luˆandu–se ˆın considerare graful ponderat: fiec˘arei muchii i se ata¸seaz˘a un cost d, iar valorile matricii A se definesc astfel:
ai,j sau ai,j
, dac˘a xi = xj 0 = +∞ , dac˘a [xi , xj ] ∈ /E d > 0 , dac˘a [xi , xj ] ∈ E
, dac˘a xi = xj 0 = −∞ , dac˘a [xi , xj ] ∈ /E d > 0 , dac˘a [xi , xj ] ∈ E.
(3.2)
(3.3)
Exemplul 3.12 Matricea costurilor corespunz˘ atoare grafului din figura 3.14 are urm˘ atoarele valori:
0 35 7 20 A= ∞ ∞ ∞ ∞
35 0 ∞ ∞ 50 ∞ ∞ ∞
7 ∞ 0 ∞ 22 ∞ ∞ ∞
20 ∞ ∞ 0 ∞ 15 ∞ ∞
∞ 50 22 ∞ 0 ∞ 44 ∞
∞ ∞ ∞ 15 ∞ 0 10 27
∞ ∞ ∞ ∞ 44 10 0 ∞
∞ ∞ ∞ ∞ ∞ 27 ∞ 0
Notat¸ia (3.2) este folosit˘a ˆın aplicat¸ii ˆın care se cere un drum de lungime minim˘a iar (3.3) este folosit˘a ˆın aplicat¸ii ˆın care se cere un drum de lungime maxim˘a ˆıntre dou˘a noduri. Matricea costurilor va ocupa un spat¸iu de memorie mai mare decˆat cel ocupat de matricea de adiacent¸˘a: pentru reprezentarea unui element al matricii de adiacent¸˘a este suficient un bit, pe cˆand pentru reprezentarea unui element al matricii costurilor se folose¸ste un byte, un ˆıntreg (lung) sau un num˘ar real. 43
Prin urmare pentru reprezentarea unei matrici de adiacent¸˘a de dimensiuni n × n sunt necesari n · ⌈ n8 ⌉ octet¸i, pe cˆand pentru reprezentarea unei matrici de costuri cu elemente numere ˆıntregi sunt necesari 2 · n2 octet¸i (dac˘a presupunem c˘a un num˘ar ˆıntreg f˘ar˘a semn este p˘astrat pe 2 octet¸i). 3. Liste de adiacent¸˘a - pentru fiecare nod se ret¸ine lista nodurilor adiacente. Astfel unui nod xk i se va ata¸sa lista tuturor vecinilor s˘ai. Aceast˘a reprezentare se preteaz˘a mai bine pentru grafuri cu un num˘ar mare de noduri ¸si un num˘ar mic de muchii (graf rar ). ˆIn cazul unui graf neorientat num˘arul elementelor din listele de adiacent¸˘a este 2 · |E|, deoarece o muchie [u, v] va fi prezent˘a de dou˘a ori, atˆat ˆın lista de adiacent¸˘a a nodului u cˆat ¸si ˆın lista de adiacent¸˘a a nodului v. Exemplul 3.13 Listele de adiacent¸˘ a pentru graful din figura 3.13 sunt: 1: 2: 3: 4:
(2, (1, (1, (1,
3, 4) 5) 5) 6)
5: 6: 7: 8:
(2, 3, 7) (4, 7, 8) (5, 6) (6)
Aceste liste de adiacent¸˘a (sau liste de vecini) pot fi reprezentate prin intermediul tablourilor sau prin intermediul listelor simplu sau dublu ˆınl˘ant¸uite. Pentru reprezentarea ce utilizeaz˘a tablouri, se vor defini dou˘a tablouri Cap ¸si List (Cap ∈ R1×n , List ∈ R2×m , m = |E|) unde: 0 , nodul respectiv nu are vecini Capk = j , indic˘a num˘arul coloanei din matricea List unde se afl˘a primul vecin al vˆarfului xk . List1,j - indicele vˆarfului ce se afl˘a ˆın lista de vecini a vˆarfului xk .
List2,j
0 , dac˘a vˆarful respectiv este ultimul (aceast˘a valoare are aceea¸si semnificat¸ie ca ¸si valoarea NIL sau NULL utilizat˘a ˆın lucrul cu pointeri) = i , indic˘a num˘arul coloanei unde se afl˘a urm˘atorul vecin din lista de vecini a nodului xk .
Exemplul 3.14 Pentru exemplul din figura 3.13 avem urm˘ atoarea configurat¸ie pentru cei doi vectori considerat¸i: Nod Cap List =
1 1
2 4
3 6
4 8
5 10
6 13
7 16
8 18
2 3 4 1 5 1 5 1 6 2 3 7 4 7 8 5 6 6 2 3 0 5 0 7 0 9 0 11 12 0 14 15 0 17 0 0
ˆIn limbajul C definim urm˘atoarele structuri de date avˆand rolul de a ne ajuta la ment¸inerea ˆın memorie a listelor de adiacent¸˘a ale unui graf oarecare folosind liste liniare simplu ˆınl˘ant¸uite (vezi ¸si figura 3.15):
44
1
2
3
2
1
5
NULL
3
5 6 3
NULL
5
1 1 2
6
4
7
7 8
5
6
4
6
4
NULL
NULL
7
NULL
8
NULL
NULL
NULL
Fig. 3.15: Liste de adiacent¸˘a
typedef struct nod{ int nodeIndex; struct nod *next; }NOD; #define MAX 100 NOD* vecini[MAX];
NOD* vecini[MAX] este un array de pointeri ce cont¸ine ˆın vecini[i] capul listei de vecini ai nodului de indice i. struct nod *next reprezint˘a un pointer c˘atre urm˘atoarea ˆınregistrare de tip NOD ce p˘astreaz˘a informat¸ii cu privire la urm˘atorul vecin. Cantitatea de memorie necesar˘a pentru p˘astrarea listelor de adiacent¸˘a este: • reprezentarea folosind matricile Cap ¸si List - putem presupune c˘a n, m ≤ 65535 adic˘a sunt suficient¸i 2 octet¸i pentru reprezentarea acestor numere ˆın memorie (216 − 1 = 65535). ˆIn aceast˘a situat¸ie cantitatea de memorie necesar˘a pentru matricea Cap este 2n, iar pentru matricea List este 8m. Astfel complexitatea– spat¸iu este O(n + m). • reprezentarea folosind pointeri - presupunem c˘a avem 2 octet¸i pentru informat¸ia util˘a ¸si 2 octet¸i pentru informat¸ia de leg˘atur˘a. Num˘arul de octet¸i folosit¸i pentru reprezentarea unui pointer ˆın limbajul C poate depinde de modelul de memorie ales (small, huge, etc.) sau de dimensiunea instrut¸iunilor (pe 16, 32 sau 64 de bit¸i). Astfel se utilizeaz˘a 2n octet¸i pentru vectorul ce cont¸ine adresele primului element (capul) al fiec˘arei liste de vecini, ¸si 8m octet¸i pentru ˆınregistr˘arile listelor de vecini. Din aceast˘a cantitate de memorie, 4m reprezint˘a informat¸ie util˘a (indicii vecinilor) iar 2n + 4m informat¸ie auxiliar˘a (pointeri). 4. Lista de muchii - ˆıntr-o structur˘a de date se p˘astreaz˘a lista tuturor muchiilor grafului. Aceasta constituie cea mai simpl˘a modalitate de reprezentare a unui graf. Operat¸ia de ad˘augare a unui nod sau a unei muchii se face ˆıntr–un timp constant, pe cˆand, alte operat¸ii sunt mai costisitoare: de exemplu, determinarea listei de vecini a unui nod necesit˘a un timp Ω(m). Exemplul 3.15 Pentru p˘astrarea ˆın memorie se poate folosi o matrice M cu dou˘a linii ¸si |E| coloane (M ∈ M2×|E|), unde: 45
M1,i - indicele vˆarfului ce se afl˘ a ˆın prima extremitate a muchiei i; M2,i - indicele vˆarfului ce se afl˘ a ˆın cea de–a doua extremitate a muchiei i. 1 1 1 2 3 4 5 6 6 M= 2 3 4 5 5 6 7 7 8 Vom sintetiza complexitatea timp pentru diverse operat¸ii folosind fiecare dintre modurile de reprezentare prezentate: Gradul unui nod xi (xi , xj ) ∈ E? Urm˘ atorul vecin al lui xi
3.4
Matricea de adiacent¸˘ a O(n) O(1) O(n)
Liste de vecini O(d(xi )) O(d(xi )) O(d(xi ))
Lista de muchii O(m) O(m) O(m)
Parcurgerea grafurilor
Pentru un graf dat este important s˘a se determine o modalitate sistematic˘a de vizitare a tuturor vˆarfurilor grafului, vizitare ce se realizeaz˘a ˆın scopul prelucr˘arii informat¸iei cont¸inute de acestea.
3.4.1
Parcurgerea ˆın l˘ a¸time (BF-Breadth-First)
Metoda de parcurgere ˆın l˘a¸time viziteaz˘a nodurile grafului ˆın felul urm˘ator (vezi algoritmul 20): • se viziteaz˘a mai ˆıntˆai vˆarful de pornire k; • urmeaz˘a ˆın ordine tot¸i vecinii ˆınc˘a nevizitat¸i ai acestuia; • se continu˘a cu vecinii ˆınc˘a nevizitat¸i ai acestora, ¸s.a.m.d.
Fig. 3.16: Arbore de acoperire ˆın l˘a¸time
Pentru graful considerat ˆın figura 3.13 ordinea de parcurgere este urm˘atoarea: 1, 2, 3, 4, 5, 6, 7, 8. Dac˘a consider˘am muchiile folosite ˆın timpul parcurgerilor (muchiile prin intermediul c˘arora s–a ˆınaintat ˆın graf) se obt¸ine un arbore/p˘adure de parcurgere/vizitare/acoperire. ˆIn figura 3.16 este reprezentat arborele de acoperire ˆın l˘a¸time rezultat ˆın urma parcurgerii grafului din exemplu. 46
Algoritmul de parcurgere utilizeaz˘a o structur˘a de date de tip coad˘ a ˆın care vor fi introduse nodurile vizitate, dar care nu au fost ˆınc˘a prelucrate (nu au fost cercetat¸i vecinii lor). Reamintim c˘a num˘arul de noduri din mult¸imea V este n. Vectorul vizitat p˘astreaz˘a situat¸ia vizit˘arii nodurilor grafului G: ( 1 , dac˘a nodul k a fost vizitat vizitatk = 0 , dac˘a nodul k nu a fost vizitat. Algoritm 20 Algoritm de vizitare ˆın l˘a¸time 1: procedure BFS(k, n, V ecin)
- nodul de la care se porne¸ste vizitarea k Input: n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a a grafului 2: for i ← 1, n do 3: vizitati ← 0 4: end for 5: vizitatk ← 1 ⊲ marcarea nodului curent ca fiind vizitat 6: Q⇐k ⊲ inserarea nodului curent k ˆın coad˘ a 7: call V izitare(k) ⊲ vizitarea nodului curent 8: while (Q 6= ∅) do 9: Q⇒k ⊲ extragere nod curent din coad˘ a 10: for i ← 1, n do 11: if (vizitati = 0) ∧ (vecink,i = 1) then 12: vizitati ← 1 ⊲ marcarea nodului i ca fiind vizitat 13: Q⇐i ⊲ inserarea nodului i ˆın coad˘ a 14: call V izitare(i) ⊲ vizitarea nodului i 15: end if 16: end for 17: end while 18: end procedure
Implementarea ˆın limbajul C a algoritmului 20 este urm˘atoarea: #include #include #define MAX 100 #define TRUE 1 #define FALSE 0 // Numarul de noduri din graf int n; // Matricea de adiacenta char vecin[MAX][MAX]; // coada int coada[MAX]; // Indicele primului element din coada int first; // Indicele ultimului element din coada int last; // Pastreaza starea cozii
47
int vida; /** * Initializarea cozii circulare. */ void initQueue(void) { vida = TRUE; first = 0; last = MAX; } /** * Intoarce pentru pozitia i, urmatoarea pozitie din coada. */ int next(int i) { return (i + 1) % MAX; } /** * Insereaza elementul a carui valoare este pastrata in variabila k in coada. */ void enQueue(int k) { last = next(last); coada[last] = k; if (vida) vida = FALSE; } /** * Extrage un element din coada. */ int deQueue(void) { int k = coada[first]; first = next(first); if (first == next(last)) vida = TRUE; return k; } /** * Parcurge in latime graful pornind de la nodul de inceput k. */ void bfs(int k) { int i; char vizitat[MAX]; memset(vizitat, 0, sizeof(vizitat)); vizitat[k] = 1; enQueue(k); printf("%d ", k); while (!vida) { k = deQueue();
48
for (i = 0; i < n; i++) if ((vizitat[i] == 0) && (vecin[k][i] == 1)) { vizitat[i] = 1; enQueue(i); printf("%d ", i); } } } /** * Se citesc numarul de noduri precum si matricea de adiacenta. */ void readInput(void) { int i, j; printf("n = "); scanf("%d",&n); for (i = 0; i < n - 1; i++) for (j = i + 1; j < n; j++) { printf("a[%d,%d] = ", i, j); scanf("%d", &vecin[i][j]); vecin[j][i] = vecin[i][j]; } } void main(void) { readInput(); initQueue(); bfs(0); }
• ˆIn exemplul anterior funct¸ia de citire readInput() este foarte simpl˘a: se cite¸ste mai ˆıntˆai num˘arul de noduri al grafului, ¸si apoi se cite¸ste matricea de adiacent¸˘a a grafului. Variabilele n ¸si vecin sunt declarate drept variabile globale. Deoarece matricea vecin este simetric˘a ¸si pentru a reduce num˘arul de citiri, se vor solicita numai valorile aflate deasupra diagonalei principale: void readInput(void) { int i, j; printf("n = "); scanf("%d",&n); for (i = 0; i < n - 1; i++) for (j = i + 1; j < n; j++) { printf("a[%d,%d] = ", i, j); scanf("%d", &vecin[i][j]); vecin[j][i] = vecin[i][j]; } }
• Funct¸ia memset() este o funct¸ie de bibliotec˘a a c˘arei declarat¸ie poate fi g˘asit˘a ˆın headerele mem.h ¸si string.h la Borland C sau memory.h ¸si string.h la Visual C++ 2010 1. Funct¸ia are urm˘atoarea semn˘atur˘a2 1 2
http://msdn.microsoft.com/en-us/library/1fdeehz6.aspx http://en.wikibooks.org/wiki/C_Programming/Strings#The_memset_function
49
void *memset(void *dest, int c, size t count); ¸si are rolul de a init¸ializa cu valoarea c primii count octet¸i din zona de memorie ce ˆıncepe de la adresa identificat˘a de pointerul dest. • vida este o variabil˘a global˘a ce ia valoarea true atunci cˆand coada nu cont¸ine nici un element ¸si valoarea false ˆın caz contrar. • Coada este implementat˘a static sub forma unei cozi circulare. • Vizitarea ˆıncepe de la nodul 0 (bfs(0)).
Algoritmul BF este utilizat de c˘atre metoda Branch-and-Bound ca metod˘a de explorare a spat¸iului solut¸iilor, cˆat ¸si ˆın cadrul problemelor de determinare a distant¸ei minime de la un vˆarf la toate celelalte vˆarfuri ˆın cazul ˆın care lungimea unui drum dintre dou˘a noduri se consider˘a ca fiind num˘arul de noduri intermediare ale acestuia.
3.4.2
Parcurgerea D (D - Depth)
Metoda de parcurgere D este asem˘an˘atoare cu metoda de parcurgere ˆın l˘ a¸time (BF ): • la ˆınceput se viziteaz˘a vˆarful de pornire k; • urmeaz˘a ˆın ordine tot¸i vecinii ˆınc˘a nevizitat¸i ai acestuia; • la pasul urm˘ator se consider˘a ultimul nod vizitat v ¸si se ˆıncearc˘a vizitarea vecinilor ˆınc˘a nevizitat¸i ai acestuia. Dac˘a nodul v nu mai are vecini nevizitat¸i, se continu˘a procesul de parcurgere cu nodul ce a fost vizitat exact ˆınaintea nodului v, etc. Metoda de parcurgere D este o combinat¸ie ˆıntre metoda de parcurgere ˆın l˘a¸time (vezi sect¸iunea 3.4.1) ¸si metoda de parcurgere ˆın adˆancime (vezi sect¸iunea 3.4.3). Pentru un nod se viziteaz˘a tot¸i vecinii ˆınc˘a nevizitat¸i ai acestuia, ˆıns˘a spre deosebire de metoda de parcurgere ˆın l˘a¸time unde ordinea ˆın care au fost vizitate nodurile se p˘astreaz˘a ¸si la parcurgere, la metoda de parcurgere D se prelucreaz˘a mereu ultimul nod vizitat. Pentru aceasta ˆın algoritmul D se ˆınlocuie¸ste structura de date de tip coad˘a utilizat˘a ˆın algoritmul BF, cu o structur˘a de date de tip stiv˘a (vezi algoritmul 21).
Fig. 3.17: Arbore de acoperire pentru parcurgerea D
ˆIn figura 3.17 poate fi urm˘arit arborele de acoperire pentru parcurgerea D a grafului G din figura 3.13. 50
Algoritm 21 Algoritm de vizitare D 1: procedure D(k, n, V ecin)
- nodul de la care se porne¸ste vizitarea k Input: n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a a grafului 2: for i ← 1, n do 3: vizitati ← 0 4: end for 5: vizitatk ← 1 ⊲ marcarea nodului curent ca fiind vizitat 6: S⇐k ⊲ inserarea nodului curent k ˆın stiv˘ a 7: call V izitare(k) ⊲ vizitarea nodului curent 8: while (S 6= ∅) do 9: S⇒k ⊲ extragerea nodului curent din stiv˘ a 10: for i ← 1, n do 11: if (vizitati = 0) ∧ (vecink,i = 1) then 12: vizitati ← 1 ⊲ marcarea nodului i ca fiind vizitat 13: S⇐i ⊲ inserarea nodului i in stiv˘ a 14: call V izitare(i) ⊲ vizitarea nodului i 15: end if 16: end for 17: end while 18: end procedure
3.4.3
Parcurgerea ˆın adˆ ancime (DFS-Depth First Search)
ˆIn cadrul acestei metode se va merge ˆın adˆancime ori de cˆate ori este posibil: prelucrarea unui vˆarf const˘a ˆın prelucrarea primului dintre vecinii s˘ai ˆınc˘a nevizitat¸i. • se viziteaz˘a vˆarful de pornire k; • urmeaz˘a, ˆın ordine, primul vecin ˆınc˘a nevizitat al acestuia; • se caut˘a primul vecin ˆınc˘a nevizitat al primului vecin nevizitat al nodului de start, ¸s.a.m.d.; • se merge ˆın adˆancime pˆan˘a cˆand se ajunge la un vˆarf ce nu are vecini, sau pentru care tot¸i vecinii s˘ai au fost vizitat¸i. ˆIn acest caz, se revine ˆın nodul s˘au p˘arinte (nodul din care a fost vizitat nodul curent), ¸si se continu˘a algoritmul. Pentru graful considerat (vezi figura 3.13), ˆın urma parcurgerii ˆın adˆancime, vom obt¸ine nodurile ˆın ordinea urm˘atoare: 1, 2, 5, 3, 7, 6, 4, 8 (vezi figura 3.18). Dac˘a ne propunem s˘a vizit˘am exact o singur˘a dat˘a toate nodurile unui graf, aplicˆand algoritmul DFS de cˆate ori este nevoie, ¸si select˘am numai muchiile utilizate ˆın timpul explor˘arii, rezult˘a o p˘adure de arbori. Fiecare dintre ace¸sti arbori constituie un arbore de acoperire ˆın adˆ ancime. ˆIn urma parcurgerii ˆın adˆancime muchiile unui graf pot fi clasificate ˆın urm˘atoarele categorii: 1. muchie a arborelui de acoperire - muchia [u, v] este o muchie a arborelui de acoperire dac˘a df s(u) apeleaz˘a direct df s(v) sau invers; 2. muchie de ˆıntoarcere - muchia [u, v] este o muchie de ˆıntoarcere dac˘a df s(u) apeleaz˘a indirect df s(v) (∃x ∈ V a.i. df s(u) → df s(x) → df s(v)) sau invers. 51
Fig. 3.18: Arbore de acoperire ˆın adˆancime
Exemplul 3.16 Pentru graful din figura 3.13 avem: • muchiile [1, 2], [2, 5], [3, 5], [4, 6], [5, 7], [6, 7], [6, 8] sunt muchii ale arborelui de acoperire; • muchiile [1, 3], [1, 4] sunt muchii de ˆıntoarcere; Algoritm 22 Algoritm de vizitare ˆın adˆancime (varianta recursiv˘a) 1: procedure DFS(k, n, V ecin)
- nodul vizitat k Input: n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a a grafului 2: vizitatk ← 1 ⊲ marcarea nodului curent ca fiind vizitat 3: call V izitare(k) ⊲ vizitarea nodului curent 4: for i ← 1, n do 5: if (vizitati = 0) ∧ (vecink,i = 1) then 6: call DF S(i, n, vecin) ⊲ apelul recursiv al subrutinei DF S pentru nodul i 7: end if 8: end for 9: end procedure
Implementarea algoritmului se poate realiza fie ˆın variant˘a recursiv˘a (vezi algoritmul 22), fie ˆın variant˘a nerecursiv˘a (vezi algoritmul 23). Ca ¸si la algoritmul de parcurgere ˆın l˘a¸time, vectorul vizitat ( p˘astreaz˘a situat¸ia vizit˘arii nodurilor grafului G: 1 , dac˘a nodul k a fost vizitat vizitatk = 0 , ˆın caz contrar. Subrutina DFS (algoritmul 22) ˆıncepe cu marcarea nodului curent ca fiind vizitat (vizitatk ← 1) ¸si apelul procedurii V izitare (call V izitare(k)). Apoi se caut˘a primul vecin nevizitat i al vˆarfului curent k ((vizitati = 0) ∧ (vecink,i = 1)) ¸si se reia secvent¸a de operat¸ii pentru acesta, printr–un apel recursiv (call DF S(i)). Enunt¸ul repetitiv for i ← 1, n poate fi optimizat astfel ˆıncˆat s˘a nu se mai verifice toate vˆarfurile grafului, ci numai nodurile adiacente cu nodul curent: ˆın aceast˘a situat¸ie reprezentarea cu liste de adiacent¸˘a este optim˘a din punct 52
de vedere al num˘arului de operat¸ii efectuate, fiind preferat˘a reprezent˘arii prin matricea de adiacent¸˘a. Algoritm 23 Algoritm de vizitare ˆın adˆancime (varianta nerecursiv˘a) 1: procedure DFS Nerecursiv(k, n, V ecin)
- nodul de la care se porne¸ste vizitarea k Input: n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a a grafului 2: for i ← 1, n do 3: vizitati ← 0 4: end for 5: vizitatk ← 1 ⊲ marcarea nodului curent ca fiind vizitat 6: call V izitare(k) ⊲ vizitarea nodului curent 7: S⇐k ⊲ inserarea nodului curent k ˆın stiv˘ a 8: f ound ← f alse 9: while (S 6= ∅) do ⊲ cˆ at timp stiva nu este vid˘ a 10: if (¬(f ound)) then 11: S⇒k ⊲ extragerea nodului curent din stiv˘ a 12: end if 13: i←1 14: f ound ← f alse 15: while (i ≤ n) ∧ (f ound = f alse) do 16: if (vizitati = 0) ∧ (vecink,i = 1) then 17: vizitati ← 1 ⊲ marcarea nodului i ca fiind vizitat 18: S⇐k ⊲ inserarea nodului curent k ˆın stiv˘ a 19: call V izitare(i) ⊲ vizitarea nodului i 20: k←i 21: f ound ← true 22: else 23: i←i+1 24: end if 25: end while 26: end while 27: end procedure
Algoritmul 23 utilizeaz˘a o stiv˘a S pentru a p˘astra tot timpul nodul grafului din care s-a ajuns la nodul curent (tat˘al nodului curent din arborele de acoperire ˆın adˆancime). Secvent¸a de instruct¸iuni 23.13–23.25 nu este optim˘a deoarece, de fiecare dat˘a cˆand se revine la un nod p˘arinte, se verific˘a ˆıntreaga mult¸ime V pentru a identifica un vecin nevizitat al acestuia. ˆIn vederea reducerii num˘arului de verific˘ari, se va utiliza reprezentarea prin liste de adiacent¸˘a, ¸si se salveaz˘a pe stiv˘a, pe lˆang˘a valoarea nodului curent k, valoarea vecinului nevizitat g˘asit, astfel ˆıncˆat, la revenire, s˘a se continue c˘autarea urm˘atorului vecin nevizitat al nodului curent k pornind de la acest nod. Dac˘a nu mai exist˘a nici un vecin nevizitat al vˆarfului k (linia 10), atunci se revine la p˘arintele nodului curent aflat pe stiv˘a. Algoritmul se opre¸ste ˆın cazul ˆın care stiva este vid˘a (atunci cˆand nodul curent este r˘ad˘acina arborelui de vizitare ˆın adˆancime). Am ales aici varianta recursiv˘a pentru u¸surint¸a de programare ¸si elegant¸a ei. Prezent˘am ˆın continuare implementarea ˆın limbajul C a algoritmului 22: #include
53
#include #define MAX 100 // Matricea de adiacenta char vecin[MAX][MAX]; // Vector ce pastreaza starea unui nod: vizitat sau nevizitat char vizitat[MAX]; // Numarul de varfuri din graf int n; // Nodul din care se porneste vizitarea int nodi; /** * Se citesc numarul de noduri precum si matricea de adiacenta. */ void readInput(void) { int i, j; printf("n = "); scanf("%d",&n); for (i = 0; i < n - 1; i++) for (j = i + 1; j < n; j++) { printf("a[%d,%d] = ", i, j); scanf("%d", &vecin[i][j]); vecin[j][i] = vecin[i][j]; } printf(Nodul initial = ); scanf(%d,&nodi); memset(vizitat, 0, sizeof(vizitat)); } /** * Parcurgerea in adancime pornind din nodul k. */ void dfs(int k) { int i; vizitat[k] = 1; printf("%d ",k); for (i = 0; i < n; i++) if (vizitat[i] == 0 && vecin[k][i] == 1) dfs(i); } void main(void) { readInput(); dfs(nodi); }
Complexitatea algoritmilor de parcurgere Complexitatea algoritmilor prezentat¸i este O(n2 ) deoarece se utilizeaz˘a matricea de adiacen¸t˘a drept modalitate de reprezentare a grafului. Dac˘a se utilizeaz˘a reprezentarea prin liste de 54
vecini, atunci complexitatea algoritmilor devine O(n + m), unde m = |E|. BFS D DFS
3.5
Matricea de adiacent¸˘ a 2 O(n ) O(n2 ) O(n2 )
Liste de vecini O(n + m) O(n + m) O(n + m)
Lista de muchii O(n + m2 ) O(n + m2 ) O(n + m2 )
Componente conexe
Se poate defini pe mult¸imea vˆarfurilor unui graf neorientat G o relat¸ie ρ astfel: xρy dac˘a x = y sau exist˘a ˆın G un lant¸ de la x la y. Se demonstreaz˘a foarte u¸sor c˘a relat¸ia ρ este o relat¸ie de echivalent¸˘a. Se cunoa¸ste faptul c˘a o relat¸ie de echivalent¸˘a determin˘a pe mult¸imea pe care este definit˘a o partit¸ie. Componentele conexe vor fi elementele acestei partit¸ii formate din vˆarfurile ce sunt echivalente ˆıntre ele (conform relat¸iei de echivalent¸˘a). Cu alte cuvinte, exist˘a o partit¸ie a mult¸imii vˆarfurilor V V =
m [
i=1
Vi , Vi ∩ Vj = ∅, ∀i, j = 1, m, i 6= j
¸si o partit¸ie a mult¸imii muchiilor E E=
m [
i=1
Ei , Ei ∩ Ej = ∅, ∀i, j = 1, m, i 6= j
astfel ˆıncˆat fiecare graf Gi = (Vi , Ei ), ∀i = 1, m, este conex. Algoritm 24 Algoritm pentru determinarea componentelor conexe ( n - num˘ arul de noduri din graf Input: V ecin - matricea de adiacent¸˘ a a grafului 1: for i ← 1, n do 2: vizitati ← 0 3: end for 4: cmp conex nr ← 0 5: for i ← 1, n do 6: if (vizitati = 0) then 7: cmp conex nr ← cmp conex nr + 1 8: call DF S(i, n, V ecin) ⊲ determinarea componentei conexe ce cont¸ine nodul i 9: end if 10: end for
Pentru determinarea componentelor conexe vom utiliza metoda de vizitare ˆın adˆancime a unui graf. Pa¸sii algoritmului descri¸si ˆın limbaj natural sunt urm˘atorii (algoritmul 24 prezint˘a descrierea formalizat˘a ˆın pseudo–cod): Pas 1. Se caut˘a un nod ˆınc˘a nevizitat. Pas 2. ˆIncepˆand cu acesta se parcurg toate nodurile accesibile ¸si nevizitate, avˆand grij˘a s˘a marc˘am vizitarea acestora. Toate aceste noduri formeaz˘a o component˘a conex˘a. Pas 3. Dac˘a mai exist˘a noduri nevizitate mergi la pasul Pas 1, altfel algoritmul se termin˘a. 55
3.6
Muchie critic˘ a
Reamintim faptul c˘a ˆıntr–un graf neorientat G = (V, E), o muchie critic˘ a este acea muchie care prin eliminarea ei conduce la cre¸sterea num˘arului de componente conexe ale grafului. Se cere s˘a se determine toate muchiile critice ale unui graf dat. ˆIn continuare sunt prezentate dou˘a variante de rezolvare. Solut¸ia I Pentru simplitate, vom trata situat¸ia ˆın care graful considerat este conex (dac˘a graful nu este conex se determin˘a num˘arul de componente conexe ¸si se aplic˘a algoritmul pentru fiecare component˘a conex˘a ˆın parte). Se elimin˘a, pe rˆand, fiecare muchie a grafului ¸si apoi se verific˘a dac˘a graful rezultat mai este conex (vezi algoritmul 25). Algoritm 25 Algoritm de determinare a muchiei critice (prima variant˘a) 1: procedure MuchieCriticaI(n, m, V ecin)
- num˘ arul de noduri din graf n Input: m - num˘ arul de muchii din graf V ecin - matricea de adiacent¸˘ a 2: for k ← 1, m do 3: elimin˘ a muchia k 4: if (conex(n, vecin) = f alse) then 5: Output {’Muchia k este critica’} 6: end if 7: adaug˘ a muchia k 8: end for 9: end procedure 10: function Conex(n, V ecin) 11: for i ← 1, n do 12: vizitati ← 0 13: end for 14: call DF S(1, n, V ecin) 15: for i ← 1, n do 16: if (vizitati = 0) then 17: return f alse 18: end if 19: end for 20: return true 21: end function
⊲ reface graful init¸ial
⊲ vizitarea ˆın adˆ ancime a grafului
Subrutina Conex verific˘a dac˘a graful identificat prin matricea de adiacent¸˘a V ecin este conex. Subrutina ˆıntoarce valoarea 1 ˆın cazul ˆın care graful considerat este conex ¸si 0 ˆın caz contrar. Pentru aceasta, la ˆınceput se marcheaz˘a toate nodurile ca fiind nevizitate, ¸si se ˆıncearc˘a parcurgerea nodurilor grafului, prin intermediul unui apel al subrutinei de vizitare. Implementarea ˆın limbajul C: #include #include #define MAX 100
56
#define TRUE 1 #define FALSE 0 char vecin[MAX][MAX]; char vizitat[MAX]; int n; /** * Se citesc numarul de noduri precum si matricea de adiacenta. */ void readInput(void) { int i, j; printf("n = "); scanf("%d", &n); for (i = 0; i < n - 1; i++) for (j = i + 1; j < n; j++) { printf("a[%d,%d] = ", i, j); scanf("%d", &vecin[i][j]); vecin[j][i] = vecin[i][j]; } } /** * Parcurgerea in adancime a grafului pornind din nodul de start k. */ void dfs(int k) { int i; vizitat[k] = TRUE; printf("%d ", k); for (i = 0; i < n; i++) if ((vizitat[i] == FALSE) && (vecin[k][i] == 1)) dfs(i); } /** * Functia verifica daca graful este conex. */ int conex(void) { int i; memset(vizitat, 0, sizeof(vizitat)); dfs(0); for (i = 0; i < n; i++) if (vizitat[i] == FALSE) { return FALSE; } return TRUE; } void main(void) { int i, j;
57
readInput(); for (i = 1; i < n; i++) for (j = 0; j < i; j++) if (vecin[i][j] == 1) { vecin[i][j] = 0; vecin[j][i] = 0; if (conex() == FALSE) { printf("Muchia (%d,%d) este critica! \n",i,j); } vecin[i][j] = 1; vecin[j][i] = 1; } }
Observat¸ia 3.17 Am presupus c˘a numerotarea nodurilor se face de la 0 ¸si c˘ a exist˘a un nod cu eticheta 0. Complexitatea–timp a acestui algoritm (presupunem c˘a avem o singur˘a component˘a conex˘a), este O(m(n + m)): se ia fiecare muchie (O(m)), se elimin˘a din graf, ¸si se verific˘a dac˘a graful r˘amas este conex (O(n + m)). Solut¸ia a II–a Cea de–a doua variant˘a de abordare pentru identificarea unei solut¸ii folose¸ste urm˘atoarea proprietate: Observat¸ia 3.18 O muchie nu este critic˘ a dac˘ a ea face parte din cel put¸in un ciclu elementar al grafului respectiv. ˆIn urma vizit˘arii DFS, toate muchiile unui graf se ˆımpart ˆın muchii ale arborelui de acoperire ¸si muchii de ˆıntoarcere. Vom numerota toate nodurile grafului ˆın preordine - ˆın momentul ˆın care un nod este marcat ca fiind vizitat - toate valorile fiind p˘astrate ˆıntr-un vector prenum. Fie lowu valoarea unui nod u, calculat˘a dup˘a formula: prenumu , ¸si lowu = min prenumx , dac˘a [u, x] este muchie de ˆıntoarcere, ¸si lowy , ∀y descendent direct al lui u.
Dac˘a prenumu ≥ lowv , ∀v descendent direct al lui u, atunci ˆınseamn˘a c˘a v sau un descendent al lui v are o muchie de ˆıntoarcere la u sau la un str˘amo¸s al acestuia. Astfel muchia [u, v] apart¸ine unui ciclu elementar, ¸si prin urmare nu este muchie critic˘a (vezi algoritmul 26). Prin negat¸ie, dac˘a exist˘a cel put¸in un vˆarf v, descendent direct al lui u, cu proprietatea c˘a prenumu < lowv atunci [u, v] este muchie critic˘a. Dup˘a fiecare apel recursiv al subrutinei DFS (linia 16) se verific˘a gradul de adev˘ar al expresiei (prenumk < lowi ) (linia 18). Variabila counter este global˘a pentru cele dou˘a subrutine, MuchieCriticaII() ¸si DF S critic(). La numerotarea ˆın preordine se folosesc atribuirile: counter ← counter + 1, ¸si prenumk ← counter. Cˆand un nod i este vecin cu nodul curent k, ¸si nu a fost ˆınc˘a vizitat, valoarea lui lowk se calculeaz˘a astfel: lowk ← min {lowk , lowi}. Pentru un nod i vecin cu nodul curent k, deja vizitat, avem o muchie de ˆıntoarcere (nodului k i s–a atribuit deja un num˘ar ˆın preordine): astfel valoarea lui lowk se calculeaz˘a dup˘a formula lowk ← min {lowk , prenumi }. Implementarea ˆın limbajul C a algoritmului 26 este: 58
Algoritm 26 Algoritm de determinare a muchiei critice (a doua variant˘a) 1: procedure ( MuchieCriticaII(n, V ecin)
n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a for i ← 1, n do vizitati ← 0 end for counter ← 0 call DF S critic(1, n, V ecin) end procedure procedure DFS critic(k, n, V ecin) vizitatk ← 1 counter ← counter + 1 prenumk ← counter, lowk ← counter for i ← 1, n do if (vecink,i = 1) then if (vizitati = 0) then tatai ← k call DF S critic(i, n, V ecin) lowk ← M in(lowk , lowi ) if (prenumk < lowi ) then Output {’Muchia (k,i) este critica’} end if else if (tatak 6= i) then lowk ← M in(lowk , prenumi ) end if end if end if end for end procedure
Input: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
⊲ vizitarea ˆın adˆ ancime a grafului
#include #include #define MAX 100 /** * Numarul de noduri din graf */ int n; /** * Matricea de adiacenta */ char vecin[MAX][MAX]; /** * Vector ce pastreaza starea unui nod: vizitat sau nevizitat. */ char vizitat[MAX]; /**
59
* Pastreaza tatal fiecarui nod in arborele de acoperire * in adincime generat de metoda DFS. */ int tata[MAX]; /** * prenum[i] - numerotarea in preordine */ int prenum[MAX]; int low[MAX]; /** * contor global ce numara momentul cand este vizitat un nod. */ int counter; void readInput(void) { int i, j; printf("n = "); scanf("%d",&n); for (i = 0; i < n - 1; i++) for (j = i + 1; j < n; j++) { printf("a[%d,%d] = ", i, j); scanf("%d", &vecin[i][j]); vecin[j][i] = vecin[i][j]; } } int min(int x, int y) { return (x < y) ? x : y; } void dfs(int k) { int i; vizitat[k] = 1; counter++; prenum[k] = counter; low[k] = counter; for (i = 0; i < n; i++) if (vecin[k][i] == 1) if (vizitat[i] == 0) { tata[i] = k; dfs(i); low[k] = min(low[k], low[i]); if (prenum[k] < low[i]) printf("%d -> %d \n", k, i); } else if (tata[k] != i) low[k] = min(low[k], prenum[i]); /*printf("prenum[%d] = %d low[%d] = %d\n",k,prenum[k],k,low[k]);*/ } void critic(void) {
60
memset(vizitat, 0, sizeof(vizitat)); counter = 0; dfs(0); } void main(void) { printf("\n"); readInput(); critic(); }
Fig. 3.19: Algoritmul 26 aplicat pentru graful din figura 3.13
Exemplul 3.19 Pentru graful din figura 3.13, valorile prenum ¸si low sunt cele din figura 3.19 (valoarea din partea dreapt˘a a unui vˆ arf reprezint˘ a valoarea sa la numerotarea ˆın preordine, prenum, iar num˘arul din stˆanga reprezint˘ a valoarea calculat˘ a low). Condit¸ia prenumu < lowv , unde [u, v] este o muchie a grafului, este ˆındeplinit˘ a doar pentru u = 6 ¸si v = 8. Prin urmare [6, 8] este muchie critic˘a ˆın graful considerat. Deoarece acest algoritm este constituit din algoritmul modificat de vizitare ˆın adˆancime a unui graf, complexitatea–timp este O(n + m) atunci cˆand graful este reprezentat prin liste de adiacent¸˘a ¸si O(n2 ) ˆın situat¸ia ˆın care graful este reprezentat prin matricea de adiacent¸˘a.
3.7
Exercit¸ii
1. Un graf neorientat cu n noduri, G = (V, E) se nume¸ste graf scorpion dac˘a posed˘a trei noduri speciale: (a) acul - dG (u) = 1 ¸si este legat de coad˘a; (b) coada - dG (u) = 2 ¸si este legat˘a de ac ¸si corp; (c) corpul - dG (u) = n − 2 fiind legat de toate nodurile din graf cu except¸ia acului. 61
Fig. 3.20: Un exemplu de graf neorientat cu 8 vˆarfuri
Nu exist˘a alte restrict¸ii cu privire la nodurile grafului. S˘a se realizeze un algoritm care determin˘a dac˘a un graf este graf scorpion folosind O(n) ˆıntreb˘ari de forma ”Exist˘a o muchie ˆıntre nodurile u ¸si v?”. 2. Se consider˘a n bazine dispuse circular ¸si lipite unul de altul ˆın ordinea numerelor de ordine. Se d˘a lista celor m perechi de bazine ce trebuie unite prin canale. Un num˘ar de bazine poate s˘a apar˘a ˆın mai multe perechi. Canalele pot fi realizate ˆın interiorul sau ˆın exteriorul cercului, cu condit¸ia ca ele s˘a nu se intersecteze. Dac˘a problema are solut¸ie, vor fi afi¸sate dou˘a liste: cea a perechilor de bazine unite prin canale interioare ¸si cea a perechilor de bazine unite prin canale exterioare. Dac˘a problema nu are solut¸ie, atunci se va raporta acest lucru. ˆIn cazul ˆın care problema are solut¸ie s˘a se determine toate posibilit˘a¸tile de unire prin canale exterioare sau interioare. (ONI, 1993)
3. S˘a se parcurg˘a ˆın l˘a¸time ¸si ˆın adˆancime graful din figura 3.20, s˘a se construiasc˘a arborii de parcurgere ¸si s˘a se pun˘a ˆın evident¸˘a muchiile de ˆıntoarcere. 4. Compania TLC (Telephone Line Company) a stabilit o nou˘a ret¸ea de cablu telefonic. Ea leag˘a mai multe locuri numerotate cu numere ˆıntregi de la 1 la N. Nu exist˘a dou˘a locuri numerotate cu acela¸si num˘ar. Liniile sunt bidirect¸ionale ¸si leag˘a dou˘a locuri; fiecare loc are un post telefonic. Din orice loc poate fi apelat oricare alt loc, prin leg˘atura direct˘a sau conexiuni intermediare. Uneori ret¸eaua cade ˆın unele locuri ¸si conexiunea aferent˘a nu mai este posibil˘a. Tehnicienii de la TLC au realizat c˘a ˆın acest caz, nu numai c˘a locul respectiv nu mai poate fi apelat, dar el rupe leg˘atura ¸si ˆıntre alte locuri pentru care asigur˘a conexiunea. ˆIn aceast˘a situat¸ie spunem c˘a locul (unde a ap˘arut c˘aderea) este critic. Se cere s˘a de dezvolte un algoritm care s˘a calculeze num˘arul tuturor acestor puncte critice. (ACM Europa Central˘a, 1996)
5. Fiind dat un graf G = (V, E), s˘a se verifice dac˘a este aciclic. 6. Fiind dat un graf G = (V, E), s˘a se verifice dac˘a este bipartit. 62
7. ˆIntr–un ora¸s intersect¸iile sunt numerotate de la 1 la n (se consider˘a intersect¸ie nu numai locul unde se intersecteaz˘a mai multe str˘azi ci ¸si punctele terminale – capetele de str˘azi). Edilii ora¸sului doresc s˘a numeroteze ¸si str˘azile ora¸sului, dar ˆıntr–un mod care s˘a ¸tin˘a seama de numerotarea intersect¸iilor, ¸si anume: dou˘a str˘azi diferite vor avea numere diferite ¸si ˆın fiecare intersect¸ie trebuie s˘a soseasc˘a o strad˘a care s˘a aib˘a num˘arul intersect¸iei. Se cere s˘a se realizeze un algoritm care s˘a determine o astfel de numerotare, dac˘a exist˘a. Datele de intrare constau dintr–un num˘ar n reprezentˆand num˘arul de puncte de intersect¸ie; fiecare punct este identificat prin num˘arul cu care este numerotat, ˆıntre 1 ¸si n, urmat de lista punctelor cu care acesta comunic˘a direct printr–o strad˘a. Prin strad˘a se ˆınt¸elege o port¸iune de drum aflat˘a ˆıntre dou˘a puncte de intersect¸ie, neglijˆand faptul c˘a ˆın practic˘a str˘azile cuprind mai multe intersect¸ii. 8. Se consider˘a o tabl˘a de ¸sah de dimensiune n × n (n ≤ 100) pe care sunt dispuse obstacole. Se cere s˘a se determine num˘arul minim de mut˘ari necesare unui nebun pentru a se deplasa, respectˆand regulile jocului de ¸sah ¸si ocolind obstacolele, dintr–o pozit¸ie init¸ial˘a dat˘a ˆıntr–o pozit¸ie final˘a dat˘a. Se consider˘a c˘a obstacolele nu coincid cu pozit¸ia init¸ial˘a ¸si nici cu cea final˘a a nebunului. (Concurs student¸esc, 1993)
9. Pe o tarla agricol˘a sunt mai multe parcele ce trebuie cosite. Parcelele de diferite culturi (lucern˘a, trifoi, iarb˘a, furaje) vor fi cosite de muncitori diferit¸i ce lucreaz˘a numai la un anumit fel de cultur˘a (de exemplu este specializat numai pe cositul lucernei). Terenul agricol se modeleaz˘a printr–o matrice n × m. Fiecare element al matricii corespunde unei mult¸imi de un anumit tip. Dou˘a elemente ale matricii sunt vecine dac˘a au o latur˘a comun˘a. O parcel˘a este o mult¸ime maximal˘a de elemente astfel ˆıncˆat un muncitor se poate deplasa ˆıntre oricare dou˘a elemente de–a lungul a dou˘a elemente vecine. Se cere s˘a se determine num˘arul de muncitori care pot s˘a coseasc˘a o cultur˘a dat˘a.
63
Capitolul 4 Grafuri euleriene ¸si hamiltoniene 4.1
Grafuri Euleriene
ˆIn anul 1736, matematicianul Leonhard Euler publica o lucrare asupra problemei podurilor din K¨onigsberg1 (figura 4.1) ˆın care se prezint˘a un studiu teoretic asupra lant¸urilor ¸si ciclurilor Euleriene.
Fig. 4.1: Podurile din K¨onigsberg
S˘a consider˘am un graf neorientat G = (V, E). Definit¸ia 4.1 Un lant¸ L ce cont¸ine fiecare muchie a unui graf G o singur˘ a dat˘ a se nume¸ste lant¸ Euler sau lant¸ eulerian. Dac˘ a extremit˘ a¸tile lant¸ului sunt identice ¸si lant¸ul este eulerian atunci ciclul se nume¸ste ciclu Euler sau ciclu eulerian. Un graf ce cont¸ine un ciclu eulerian se nume¸ste graf eulerian. O problem˘a mai cunoscut˘a legat˘a de not¸iunile de ciclu ¸si lant¸ eulerian este aceea de a desena cu o singur˘a linie neˆıntrerupt˘a o anumit˘a figur˘a (vezi figura 4.2). Teorema 4.1 (Euler) Un graf G conex (|V | ≥ 3) este eulerian dac˘ a ¸si numai dac˘a gradul fiec˘arui vˆarf este par. 1
http://en.wikipedia.org/wiki/Seven Bridges of K¨onigsberg
64
Fig. 4.2: Dou˘a figuri geometrice ce pot fi desenate folosind o singur˘a linie
Demonstrat¸ie: ” ⇒ ” Fie G un graf conex eulerian ⇒ ∃ un ciclu eulerian C. Acesta trece o singur˘a dat˘a prin toate muchiile grafului. Gradul fiec˘arui vˆarf u este par deoarece pentru fiecare muchie incident˘a cu u prin intermediul c˘areia se ajunge ˆın vˆarful u, exist˘a o alt˘a muchie prin intermediul c˘areia se p˘ar˘ase¸ste vˆarful u. ” ⇐ ” Presupunem c˘a ∀u ∈ V , dG (u) este par. Fie L un lant¸ de lungime maxim˘a ˆın G ¸si fie u ¸si v extremit˘a¸tile lant¸ului. Presupunem c˘a u 6= v. Deoarece dG (v) este par, ∃[v, w] ∈ E astfel ˆıncˆat [v, w] ∈ / L. Lant¸ul L ∪ {[v, w]} are lungimea mai mare decˆat L, contradict¸ie. Prin urmare u = v. Rezult˘a c˘a L = C este un ciclu de lungime maxim˘a. Presupunem c˘a ciclul C nu este ciclu eulerian. Atunci ∃x, y ∈ V, [x, y] ∈ E, [x, y] ∈ / C ¸si x ∈ C (exist˘a o muchie a grafului G ce nu apart¸ine ciclului C cu proprietatea c˘a cel put¸in o extremitate apart¸ine ciclului). Fie L1 = [x, . . . , u, v, . . . , x] un lant¸ ˆın G ce cont¸ine exact muchiile lui C. Atunci L2 = [x, . . . , u, v, . . . , x, y] este un lant¸ ˆın G ce are lungimea mai mare decˆat L, contradict¸ie. Rezult˘a atunci c˘a ciclul C este eulerian ⇒ G este un graf eulerian. Teorema 4.2 (Euler) Un graf G conex are un lant¸ eulerian dac˘ a ¸si numai dac˘ a exist˘a exact dou˘a vˆarfuri ˆın G al c˘aror grad s˘a fie impar. Demonstrat¸ie: ” ⇒ ” Se demonstreaz˘a la fel ca la teorema 4.1. ” ⇐ ” Fie G = (V, E) un graf conex ce are exact dou˘a vˆarfuri de grad impar, u ¸si v. S˘a consider˘a un nod w ∈ / V ˆımpreun˘a cu muchiile [u, w] ¸si [v, w]. Fie G′ = (V ′ , E ′ ), V ′ = V ∪ {w}, E ′ = E ∪ {[u, w], [v, w]}, graful obt¸inut din G prin ad˘augarea nodului w ¸si a muchiilor [u, w], [v, w]. Atunci dG′ (u) este par, ∀u ∈ V ′ ⇒ exist˘a un ciclu eulerian C ˆın G′ . Dac˘a elimin˘am muchiile [u, w] ¸si [v, w] obt¸inem un lant¸ eulerian ˆın G. Corolarul 4.3 Un graf G conex (|V | ≥ 3) este eulerian dac˘ a ¸si numai dac˘ a mult¸imea muchiilor sale poate fi descompus˘a ˆın cicluri disjuncte. Teorema 4.4 Pentru un graf conex cu 2k vˆ arfuri de grad impar, k ≥ 1, exist˘ a k lant¸uri ˆın G ale c˘aror mult¸imi de muchii formeaz˘ a o partit¸ie a lui E, ¸si din care cel mult unul are lungime impar˘a.
4.1.1
Algoritm pentru determinarea unui ciclu eulerian
Pornind de la Teorema 4.1 construim un ciclu eulerian. Se consider˘a drept punct de plecare un vˆarf oarecare al grafului. La fiecare pas se alege pentru vˆarful curent u, un vˆarf adiacent 65
Fig. 4.3: Exemplu de graf eulerian
v, diferit de vˆarful din care s–a ajuns ˆın u. Parcurgerea se continu˘a pˆan˘a ˆın momentul ˆın care ajungem ˆıntr–un vˆarf ce a fost deja vizitat ¸si se ˆınchide ciclul C. Dac˘a ciclul C nu este eulerian, atunci se elimin˘a din graful G toate muchiile ciclului C, rezultˆand mai multe componente conexe G11 , G12 , . . . , G1k . Pentru fiecare astfel de component˘a G1i , ce are cel put¸in dou˘a vˆarfuri, aplic˘am recursiv algoritmul. Conform teoremei 4.1, fiecare component˘a G1i este eulerian˘a. Fie Ci1 ciclul eulerian corespunz˘ator componentei G1i . Ciclul C are cel put¸in cˆate un vˆarf comun cu fiecare ciclu Ci1 . Fie acest vˆarf ui . Definim un ciclu C0 astfel: pentru fiecare i, i = 1, k, ad˘aug˘am la ciclul C, ˆın vˆarful ui, ciclul Ci1 . Ciclul C0 cont¸ine toate muchiile grafului G, prin urmare este eulerian. Funt¸ia IsEulerian() verific˘a dac˘a un graf este eulerian conform teoremei 4.1 (vezi algoritmul 27). Algoritm 27 Algoritm de verificare dac˘a un graf conex este eulerian 1: function IsEulerian(G = (V, E)) 2: if (|V | < 3) then 3: return f alse 4: end if 5: for i ← 1, n do 6: s←0 7: for j ← 1, n do 8: s ← s + vecini,j 9: end for 10: if (s mod 2 = 1) then 11: return false 12: end if 13: end for 14: return true 15: end function
Dup˘a cum se poate vedea din funct¸ia IsEulerian numai componentele conexe ce au ordinul mai mare sau egal cu 3 vor fi luate ˆın considerare. ˆIn algoritmul 28 este prezentat˘a funct¸ia EulerRec pentru determinarea unui ciclu eulerian. 66
Procedura F indCycle(G) (vezi algoritmul 28) construie¸ste un ciclu: se alege un vˆarf u0 ¸si se caut˘a prima muchie [u0 , u1]. ˆIn vˆarful u1 se caut˘a o muchie [u1 , u2 ] astfel ˆıncˆat vˆarful u2 s˘a fie distinct de vˆarful din care s–a ajuns ˆın u1 (u2 6= u0 ) ¸si s˘a nu mai fi fost vizitat. Dac˘a u2 a fost vizitat atunci funct¸ia se termin˘a. Se continu˘a procedeul de c˘autare pˆan˘a cˆand se ajunge la un vˆarf ce a fost vizitat anterior ¸si se returneaz˘a ciclul cuprins ˆıntre cele dou˘a aparit¸ii ale aceluia¸si vˆarf. Procedura F indComponents(G′ ; k, G11 , G12 , . . . , G1k ) determin˘a componentele conexe ale grafului G′ = G − E(C), obt¸inut prin eliminarea din graful G a muchiilor ciclului C, unde G11 , G12 , . . . , G1k sunt cele k componente conexe returnate de aceasta. Procedura MergeCycles(C, C11 , . . . , Ck1 ; C0 ) construie¸ste un ciclul C0 obt¸inut prin ad˘augarea la ciclul C, succesiv, a ciclurilor C11 , . . . , Ck1 . Algoritm 28 Algoritm de determinare a unui ciclu eulerian ˆıntr–un graf conex 1: function EulerRec(G = (V, E)) 2: if (IsEulerian(G) = f alse) then 3: return ∅ 4: end if 5: call F indCycle(G; C) 6: if (E(G) \ E(C) = ∅) then 7: return C 8: end if 9: G′ ← G − E(C) 10: call F indComponents(G′ ; k, G11 , G12 , . . . , G1k ) 11: for i ← 1, k do 12: Ci1 ← EulerRec(G1i ) 13: end for 14: call M ergeCycles(C, C11 , . . . , Ck1 ; C0 ) 15: return C0 16: end function
Fig. 4.4: Grafurile part¸iale obt¸inute ˆın urma primelor dou˘a etape ale algoritmului 28
Exemplul 4.5 S˘a aplic˘am algoritmul 28 pentru graful din figura 4.3. Presupunem c˘a primul element (elementul de start) este vˆarful u0 = 1. Procedura F indCycle construie¸ste lant¸ul 67
Fig. 4.5: Grafurile part¸iale obt¸inute ˆın urma etapelor 3 ¸si 4 ale algoritmului 28
L = [1, 2, 3, 4, 5, 3]. Se observ˘a prezent¸a ciclului C1 = [3, 4, 5, 3]. ˆ In urma elimin˘ arii acestuia r˘amˆane graful din figura 4.4 a) care este un graf conex. Continu˘am procesul de construire a unui ciclu eulerian, prin apelul procedurii F indCycle pentru noul graf. Aceasta returneaz˘a ciclul C2 = [1, 2, 3, 9, 1]. Prin eliminarea muchiilor ˆ continuare se construie¸ste lant¸ul acestuia, E(C2 ), se obt¸ine graful din figura 4.4 b). In L = [2, 8, 7, 5, 6, 4, 10, 2], ce cont¸ine ciclul C3 = [2, 8, 7, 5, 6, 4, 10, 2]. Graful obt¸inut prin eliminarea muchiilor ciclului este cel din figura 4.5 a). Apelul procedurii F indCycle conduce la determinarea lant¸ului L = [6, 7, 9, 8, 10, 6] ¸si a ciclului C4 = [6, 7, 9, 8, 10, 6]. Ciclul eulerian se formeaz˘a prin reuniunea ciclurilor intermediare determinate: C = (((C4 ∪ C3 ) ∪ C2 ) ∪ C1 ) adic˘a C = [1, 2, 8, 7, 5, 6, 7, 9, 8, 10, 6, 4, 10, 2, 3, 4, 5, 3, 9, 1] (C4 ∪ C3 = [2, 8, 7, 5, 6, 7, 9, 8, 10, 6, 4, 10, 2]).
4.1.2
Algoritmul lui Rosenstiehl
Algoritmul lui Rosenstiehl [51] construie¸ste un lant¸ L care la final va deveni un ciclu eulerian, folosind pentru aceasta o stiv˘a drept structur˘a de date auxiliar˘a. Se porne¸ste de la un nod oarecare. Se ˆınainteaz˘a atˆata timp cˆat este posibil: pentru nodul curent u se caut˘a o muchie incident˘a cu el, ¸si care s˘a nu mai fi fost parcurs˘a la unul din pa¸sii anteriori. Dac˘a exist˘a o astfel de muchie (e = [u, v]), atunci se salveaz˘a pe stiv˘a nodul u, iar nodul v devine nodul curent. ˆIn momentul ˆın care nodul curent u nu mai are muchii nevizitate, se adaug˘a lant¸ului L, ¸si se extrage de pe stiv˘a nodul anterior (din care s–a ajuns ˆın u) (vezi algoritmul 29). Vectorul vizit are drept scop s˘a p˘astreze situat¸ia muchiilor: ( 1 , dac˘a e ∈ E a fost parcurs˘a vizite = 0 , dac˘a e ∈ E nu a fost ˆınc˘a parcurs˘a. Exemplul 4.6 S˘a aplic˘am algoritmul 29 pentru graful din figura 4.3. S˘ a presupunem c˘a vˆarful u de la care porne¸ste algoritmul este vˆ arful 1. La sfˆ ar¸situl primului pas al enunt¸ului repetitiv while (liniile 6 – 14) avem valorile: S = [1, 2, 3, 4, 5, 3, 9], L = [1], u = 1. La pasul urm˘ator, se extrage valoarea 9 de pe stiv˘ a ¸si astfel u devine 9. La sfˆ ar¸situl acestuia avem: S = [1, 2, 3, 4, 5, 3, 9, 7, 5, 6, 4, 10, 2, 8, 7, 6, 10, 8], L = [1, 9], u = 9. 68
Algoritm 29 Algoritm lui Rosenstiehl de determinare a unui ciclu eulerian ˆıntr–un graf conex 1: function Rosenstiehl(u, G) 2: for e ∈ E do 3: vizite ← 0 4: end for 5: S⇐u 6: while (S 6= ∅) do 7: S⇒u 8: while (∃e = [u, v] ∈ E) ∧ (vizite = 0)) do 9: vizite ← 1 10: S⇐u 11: u←v 12: end while 13: L⇐u 14: end while 15: return L 16: end function
⊲ Se insereaz˘ a pe stiv˘ a nodul u ⊲ Se extrage din stiv˘ a nodul curent ⊲ Se marcheaz˘ a muchia e ca fiind utilizat˘ a ⊲ Se salveaz˘ a pe stiv˘ a nodul curent u ⊲ Nodul curent devine nodul v ⊲ Se adaug˘ a la lista L nodul curent
ˆIn continuare, deoarece nu mai exist˘a muchii nevizitate, se vor extrage elementele aflate pe stiva S cˆate unul la fiecare pas, formˆand secvent¸a (8, 10, 6, 7, 8, 2, 10, 4, 6, 5, 7, 9, 3, 5, 4, 3, 2, 1), ¸si se vor introduce ˆın lista L: P as 20 : S = [], L = [1, 9, 8, 10, 6, 7, 8, 2, 10, 4, 6, 5, 7, 9, 3, 5, 4, 3, 2, 1], u = 1. Ciclul eulerian obt¸inut se afl˘a ˆın lista L. ˆIn continuare se prezint˘a implementarea ˆın limbajul C + + a algoritmului lui Rosenstiehl : #include #include #include #include #include #include #include
using namespace std; #define INPUT_FILE "graf1.txt" typedef vector Linie; typedef vector Matrice; int readInput(Matrice& ma) { int n, i, j, value; ifstream fin(INPUT_FILE); fin >> n; ma = Matrice(n, Linie(n, 0)); for (i = 0; i < n; i++)
69
for (j = 0; j < n; j++) { fin >> ma[i][j]; } fin.close(); return n; } void print(list& l) { list::iterator it; cout << "Ciclu eulerian: ["; for (it = l.begin(); it != l.end(); it++) cout << *it << ", "; cout << "]\n"; } void rosenstiehl(int n, Matrice& ma, int u, list& l) { stack s; int v; s.push(u); while (!s.empty()) { u = s.top(); s.pop(); v = 0; while (v < n) { if (ma[u][v] == 1) { ma[u][v] = 0; ma[v][u] = 0; s.push(u); u = v; v = 0; } else v++; } l.push_back(u); } }
void main(void) { Matrice ma; list l = list(); int n = readInput(ma); rosenstiehl(n, ma, 0, l); print(l); }
Datele de intrare vor fi preluate dintr-un fi¸sier. Cont¸inutul fi¸sierului de intrare graf1.txt corespunz˘ator grafului din figura 4.6 este urm˘atorul: 70
6 0 1 1 1 1 0
1 0 1 1 1 0
1 1 0 1 0 1
1 1 1 0 0 1
1 1 0 0 0 0
0 0 1 1 0 0
Pe prima linie avem num˘arul de vˆarfuri al grafului (cardinalul mult¸imii V ), ¸si ˆıncepˆand cu cea de-a doua linie avem valorile matricei de adiacent¸˘a, separate prin spat¸ii, cˆate un rˆand al acesteia pe fiecare pe linie.
Fig. 4.6: Un alt exemplu de graf eulerian
Pentru a reduce timpul de implementare, am utilizat cˆateva structuri de date deja existente ˆın limbajul C++, mai exact structuri de date implementate cu ajutorul template-urilor din cadrul libr˘ariei Standard Template Library - STL2 3 : • stiva - stack 4. Am folosit o stiv˘a de numere ˆıntregi: stack s;. • lista - list 5 . Pentru a p˘astra elementele ciclului eulerian am folosit o list˘a liniar˘a, informat¸ia util˘a din cadrul nodurilor fiind constituit˘a din valori ˆıntregi: list l = list();. • vector - vector 6 . Matricea de adiacent¸˘a am implementat-o sub forma unui vector de vectori cu elemente numere ˆıntregi: typedef vector Linie; typedef vector Matrice; ... Matrice ma; ma = Matrice(n, Linie(n, 0));
Dup˘a cum se poate observa din programul anterior, pentru reprezentarea intern˘a a grafului s-a folosit matricea de adiacent¸˘ a. 2
http://en.wikipedia.org/wiki/Standard_Template_Library, http://www.sgi.com/tech/stl/ 4 http://www.sgi.com/tech/stl/stack.html 5 http://www.sgi.com/tech/stl/List.html 6 http://www.sgi.com/tech/stl/Vector.html 3
71
• Parcurgerea elementelor listei se realizeaz˘a cu ajutorul unui iterator - iterator 7 . Declararea unui obiect de tip list::iterator se poate face astfel: list::iterator it;. void print(list& l) { list::iterator it; cout << "Ciclu eulerian: ["; for (it = l.begin(); it != l.end(); it++) cout << *it << ", "; cout << "]\n"; }
Deoarece muchiile sunt parcurse o singur˘a dat˘a ˆın cadrul acestui algoritm, ¸si pentru a nu mai p˘astra o structur˘a auxiliar˘a care s˘a marcheze faptul c˘a o muchie a fost vizitat˘a, vom marca parcurgerea unei muchii prin ¸stergerea acesteia din matricea de adiacent¸˘a astfel: ma[u][v] = 0; ma[v][u] = 0;
Ciclul eulerian obt¸inut pentru graful din figura 4.6 este: L = [1, 5, 2, 4, 6, 3, 4, 1, 3, 2, 1].
4.1.3
Algoritmul lui Fleury
Un alt algoritm pentru determinarea unui ciclu eulerian este algoritmul lui Fleury.
Fig. 4.7: Exemplu de graf eulerian pentru algoritmul lui Fleury
Se porne¸ste cu un vˆarf oarecare al grafului G (G = (V, E), |V | = n, |E| = m). Ideea const˘a ˆın a construi un lant¸ prin alegerea la fiecare pas a unei muchii nealeas˘ a la pa¸sii anteriori, ¸si care, de preferat, s˘a nu fie muchie critic˘a ˆın graful part¸ial obt¸inut prin eliminarea muchiilor deja alese. S˘a consider˘am c˘a, la un moment dat, avem construit un lant¸ Lk = [v0 , [v0 , v1 ], v1 , . . . , [vk−1 , vk ], vk ]. C˘aut˘am o muchie ek+1 = [vk , vk+1 ] astfel ˆıncˆat ek+1 ∈ / Lk ¸si care nu este muchie 7
http://www.sgi.com/tech/stl/Iterators.html
72
critic˘a ˆın graful part¸ial Gk = G − {e1 , e2 , . . . , ek } (graful obt¸inut prin eliminarea tuturor muchiilor lant¸ului Lk ). Dac˘a nu exist˘a decˆat muchii critice ˆın graful part¸ial Gk incidente cu vk atunci alegem una dintre ele. Dac˘a exist˘a o muchie ek+1 cu aceste propriet˘a¸ti atunci construim lant¸ul Lk+1 = [Lk , ek+1, vk+1 ] (Lk+1 = [v0 , [v0 , v1 ], v1 , . . . , [vk−1 , vk ], vk , [vk , vk+1 ], vk+1 ]). ˆIn momentul ˆın care E(Lk ) = E(G) algoritmul lui Fleury se opre¸ste (vezi algoritmul 30). Algoritm 30 Algoritm lui Fleury de determinare a unui ciclu eulerian ˆıntr–un graf conex 1: function Fleury(u0 , G = (V, E)) 2: k←0 3: L0 ← [u0 ] 4: while (k ≤ m) do 5: caut ek+1 = [vk , vk+1 ] a.i. ek+1 ∈ / Lk ¸si ek+1 , de preferat, nu este muchie critic˘ a ˆın graful 6: 7: 8: 9: 10:
Gk = G − {e1 , e2 , . . . , ek } Lk+1 ← [Lk , ek+1 , vk+1 ] k ←k+1 end while return Lk end function
Exemplul 4.7 S˘a aplic˘am algoritmul 30 pentru graful din figura 4.7. La ˆınceput L0 = [u1 ]. Apoi se intr˘a ˆın ciclul while (liniile 4 – 8): tabelul urm˘ ator indic˘ a muchia ce a fost aleas˘a la fiecare pas k, precum ¸si configurat¸ia lant¸ului Lk . Pasul k 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Muchia aleas˘ a [u1 u4 ] [u4 u10 ] [u10 u3 ] [u3 u2 ] [u2 u6 ] [u6 u1 ] [u1 u5 ] [u5 u6 ] [u6 u7 ] [u7 u2 ] [u2 u8 ] [u8 u3 ] [u3 u9 ] [u9 u8 ] [u8 u7 ] [u7 u13 ] [u13 u5 ] [u5 u12 ] [u12 u4 ]
Lk L1 = [u1 , [u1 u4 ], u4 ] L2 = [u1 , [u1 u4 ], u4 , [u4 u10 ], u10 ] L3 = [u1 , [u1 u4 ], u4 , [u4 u10 ], u10 , [u10 u3 ], u3 ] L4 = [. . . , [u10 u3 ], u3 , [u3 , u2 ], u2 ] L5 = [. . . , u2 , [u2 u6 ], u6 ] L6 = [. . . , u6 , [u6 u1 ], u1 ] L7 = [. . . , u1 , [u1 u5 ], u5 ] L8 = [. . . , u5 , [u5 u6 ], u6 ] L9 = [. . . , u6 , [u6 u7 ], u7 ] L10 = [. . . , u7 , [u7 u2 ], u2 ] L11 = [. . . , u2 , [u2 u8 ], u8 ] L12 = [. . . , u8 , [u8 u3 ], u3 ] L13 = [. . . , u3 , [u3 u9 ], u9 ] L14 = [. . . , u9 , [u9 u8 ], u8 ] L15 = [. . . , u8 , [u8 u7 ], u7 ] L16 = [. . . , u7 , [u7 u13 ], u13 ] L17 = [. . . , u13 , [u13 u5 ], u5 ] L18 = [. . . , u5 , [u5 u12 ], u12 ] L19 = [. . . , u12 , [u12 u4 ], u4 ]
73
Pasul k 20 21 22 23 24 25 26
Muchia aleas˘ a [u4 u11 ] [u11 u10 ] [u10 u9 ] [u9 u13 ] [u13 u11 ] [u11 u12 ] [u12 u1 ]
Lk L20 = [. . . , u4 , [u4 u11 ], u11 ] L21 = [. . . , u11 , [u11 u10 ], u10 ] L22 = [. . . , u10 , [u10 u9 ], u9 ] L23 = [. . . , u9 , [u9 u13 ], u13 ] L24 = [. . . , u13 , [u13 u11 ], u11 ] L25 = [. . . , u11 , [u11 u12 ], u12 ] L26 = [. . . , u12 , [u12 u1 ], u1 ]
La final, lant¸ul are urm˘atoarea component¸˘ a: L2 = [u1 , u4 , u10 , u3, u2 , u6 , u1, u5 , u6 , u7, u2 , u8 , u3, u9 , u8 , u7, u13 , u5 , u12 , u4 , u11, u10 , u9 , u13 , u11 , u12 , u1 ] (s-au omis din aceast˘ a enumerare muchiile grafului).
4.2
Grafuri Hamiltoniene
Definit¸ia 4.2 Se nume¸ste lant¸ Hamilton sau lant¸ hamiltonian un lant¸ L ce trece o singur˘a dat˘a prin toate vˆarfurile unui graf. Definit¸ia 4.3 Se nume¸ste ciclu hamiltonian un ciclu elementar ce trece prin toate vˆarfurile grafului. Un graf ce admite un ciclu hamiltonian se nume¸ste graf hamiltonian. Problema determin˘arii dac˘a un graf oarecare G este hamiltonian este o problem˘a dificil˘a, atent¸ia cercet˘atorilor ˆındreptˆandu–se c˘atre enunt¸area unor condit¸ii suficiente de existent¸˘a a unui ciclu hamiltonian. Lema 4.8 Fie G = (V, E) un graf neorientat ¸si fie u ¸si v dou˘ a vˆ arfuri neadiacente ale grafului (u, v ∈ V , u 6= v, [u, v] ∈ / E), astfel ˆıncˆ at dG (u) + dG (v) ≥ n. Atunci G este hamiltonian ⇔ G + [u, v] este hamiltonian. Demonstrat¸ie: ” ⇒ ” Dac˘a G este hamiltonian, atunci cu atˆat mai mult, graful G+[u, v] este hamiltonian. ” ⇐ ” Presupunem c˘a G + [u, v] este un graf hamiltonian. Atunci exist˘a un ciclu hamiltonian ˆın graful G + [u, v] pe care ˆıl not˘am cu C. 1. dac˘a [u, v] ∈ / C atunci C este un ciclu hamiltonian ¸si ˆın graful G ⇒ G este un graf hamiltonian. 2. dac˘a [u, v] ∈ C atunci C = [u, v, x3 , x4 , . . . , xn−1 , xn , u]. Pentru fiecare muchie [u, xk ] ∈ E putem avea urm˘atoarele situat¸ii: (a) [v, xk+1] ∈ E. Atunci ciclul C1 = [u, xk , xk−1 , . . . , x3 , v, xk+1, . . . , xn , u] este hamiltonian ˆın graful G ⇒ graful G este hamiltonian. (b) [v, xk+1] ∈ / E. Not˘am dG+[u,v] (u) = k. Atunci dG+[u,v] (v) ≤ n − k − 1. ¸si De aici, rezult˘a c˘a dG (u) + dG (v) < dG+[u,v] (u) + dG+[u,v] (v) ≤ n − k + k − 1 = n − 1 < n. Contradict¸ie cu dG (u) + dG (v) ≥ n, ∀u, v ∈ V, u 6= v, [u, v] ∈ / E. Prin urmare lema este demonstrat˘a.
Teorema 4.9 (Dirac, 1952) Un graf G = (V, E) (|V | ≥ 3) este hamiltonian dac˘a ∀u ∈ V avem dG (u) ≥ n2 (orice vˆarf al grafului are gradul mai mare decˆ at jum˘ atate din num˘arul de vˆarfuri din graf ). 74
Teorema 4.10 (Ore, 1961) Un graf G = (V, E) (|V | ≥ 3) este hamiltonian dac˘ a ∀u, v ∈ V avem dG (u) + dG (v) ≥ n unde u 6= v, [u, v] ∈ / E (pentru oricare dou˘ a vˆ arfuri distincte, neadiacente, ale grafului suma gradelor lor este mai mare decˆ at num˘ arul de vˆ arfuri din graf ). Teorema 4.11 (Chvatal, 1972) Fie un graf G = (V, E) (|V | ≥ 3) ¸si d1 , d2 , . . . , dn o secvent¸˘a grafic˘a. Dac˘a este satisf˘acut˘ a relat¸ia ∀k a.i. dk ≤ k ≤
n ⇒ dn−k ≥ n − k 2
atunci graful este hamiltonian. Definit¸ia 4.4 Pentru un graf G construim un ¸sir de grafuri G = G1 , G2 , . . . astfel: graful Gk+1 se obt¸ine din graful Gk prin ad˘ augarea muchiei [uk , vk ], unde vˆ arfurile uk , vk ∈ V nu sunt adiacente ˆın Gk ¸si dG (uk ) + dG (vk ) ≥ n. Procesul se ˆıncheie ˆın momentul ˆın care nu mai exist˘a dou˘a vˆarfuri neadiacente distincte astfel ˆıncˆ at dG (up ) + dG (vp ) ≥ n. Graful Gp se nume¸ste ˆınchiderea lui G ¸si se noteaz˘ a cu cl(G). Lema 4.12 Orice graf prezint˘a o singur˘ a ˆınchidere. Corolarul 4.13 Un graf G = (V, E) (|V | ≥ 3) este hamiltonian dac˘ a cl(G) ≅ Kn (dac˘a ˆınchiderea lui G este izomorf˘a cu graful complet de ordinul n). Definim δ(G) = min{dG (u)|u ∈ V } ¸si ∆(G) = max{dG (u)|u ∈ V }. Corolarul 4.14 Un graf G = (V, E) (|V | ≥ 3) este hamiltonian dac˘ a δ(G) ≥ n2 . Definit¸ia 4.5 O mult¸ime de vˆarfuri A a unui graf G se spune c˘ a este independent˘a dac˘a oricare dou˘a elemente distincte din A sunt independente. Num˘ arul de independent¸˘ a al lui G, notat cu β(G), reprezint˘a num˘arul maxim de vˆ arfuri dintr–o mult¸ime independent˘a. β(G) = 1 dac˘a ¸si numai dac˘a graful G este complet. Definit¸ia 4.6 Pentru un graf G se nume¸ste conectivitatea lui G, ¸si not˘ am cu κ(G), num˘arul minim de vˆarfuri ale unei t˘aieturi. O t˘ aietur˘ a este o submult¸ime U a lui V astfel ˆıncˆat graful G − U s˘a fie neconex. Teorema 4.15 Un graf G = (V, E) avˆ and ordinul n ≥ 3, este hamiltonian dac˘a κ(G) ≥ β(G). Observat¸ia 4.16 Pentru un graf bipartit G = (V1 , V2 , E) condit¸ia necesar˘ a pentru a fi hamiltonian este |V1 | = |V2 |. Problema comis–voiajorului include problema determin˘arii existent¸ei unui ciclu hamiltonian ˆıntr–un graf. C˘autarea optimului printre toate variantele de cicluri nu este o solut¸ie fezabil˘a deoarece pentru un graf G num˘arul de cicluri poate fi foarte mare. De exemplu pentru graful complet Kn avem (n−1)! cicluri hamiltoniene distincte. 2 Exemplul 4.17 Graful din figura 3.20 nu este hamiltonian (nu admite un ciclu hamiltonian). 75
• Graful din figura 4.6 are mai multe cicluri hamiltoniene: de exemplu ciclurile C1 = [1, 5, 2, 3, 6, 4, 1] ¸si C2 = [1, 3, 6, 4, 2, 5, 1]. Nod 1 dG 4
2 3 4 4
4 5 6 4 2 2
Teorema lui Dirac (1952) nu se poate aplica deoarece nu este ˆındeplinit˘ a condit¸ia ”orice vˆarf al grafului are gradul mai mare decˆat jum˘ atate din num˘ arul de vˆ arfuri din graf”: dG (5) = 2 <
6 n = = 3. 2 2
De asemenea, pentru teorema lui Ore (1961) nu este ˆındeplinit˘ a condit¸ia ”pentru oricare dou˘a vˆarfuri dinstincte, neadiacente, ale grafului suma gradelor lor este mai mare decˆ at num˘arul de vˆarfuri din graf”: fie vˆarfurile 5 ¸si 6, neadiacente, pentru care avem dG (5) + dG (6) = 2 + 2 = 4 < 6. • Pentru graful din figura 4.8 avem: Nod 1 dG 3
2 3 4 3
4 5 6 4 4 5
7 8 3 4
– condit¸ia teoremei lui Dirac (1952), nu este ˆındeplinit˘ a: dG (1) = 3 < 4 = n2 ; – condit¸ia teoremei lui Ore (1961), nu este ˆındeplinit˘ a: dG (2) + dG (7) = 4 + 3 = 7 < 8; – observ˘am c˘a nici condit¸iile teoremei lui Chvatal (1972) nu sunt ˆındeplinite: fie d1 , d2 , . . . , dn o secvent¸˘a grafic˘a. Nod dG
1 3
3 7 2 3 3 4
4 5 4 4
8 6 4 5
Relat¸ia urm˘atoare nu este satisf˘ acut˘ a: ∀k a.i. dk ≤ k ≤
4.2.1
n ⇒ dn−k ≥ n − k. 2
Problema comis–voiajorului
Un comis-voiajor trebuie s˘a viziteze n ora¸se etichetate cu numerele de la 1 la n. Pentru a simplifica problema, acesta va pleca ˆıntotdeauna din ora¸sul num˘ arul 1 ¸si se va ˆıntoarce tot ˆın 1, trecˆand prin fiecare ora¸s o singur˘a dat˘ a (cu except¸ia ora¸sului 1 care va fi vizitat de dou˘a ori). Cunoscˆand distant¸ele ˆıntre ora¸se, s˘ a se determine o parcurgere (ciclu hamiltonian) de cost minim. Pentru a reprezenta drumurile directe dintre ora¸se (existent¸a unei leg˘aturi directe ˆıntre acestea) utiliz˘am matricea costurilor A = (aij ), i, j = 1, n: , dac˘a xi = xj 0 ai,j = +∞ (4.1) , dac˘a [xi , xj ] ∈ /E. d > 0 , dac˘a [xi , xj ] ∈ E
Vectorul solut¸iilor rezultat va fi X = (x1 , x2 , . . . , xn+1 ) ∈ V × . . . × V unde x1 = xn+1 = 1: la pasul i, i = 2, n + 1, se viziteaz˘a ora¸sul xi ∈ V . Primul ora¸s, ora¸sul din care se pleac˘a, este fixat la 1 (vezi algoritmul 31). 76
La pasul k (2 ≤ k ≤ n + 1) se ˆıncearc˘a vizitarea ora¸sului xk + 1 dac˘a nu a fost vizitat la un pas anterior, ¸si dac˘a costul part¸ial al lant¸ului este mai mic decˆat costul celui mai bun ciclu hamiltonian obt¸inut pˆan˘a ˆın acel moment ˆın graf: 1 ≤ xk < n ¸si vizitatxk +1 = 0 ¸si cost + axk−1 ,xk +1 ≤ costoptim
(4.2)
(pentru k = n + 1 trebuie verificat˘a ¸si condit¸ia xk = 1). La ˆınceput costul celui mai scurt ciclu hamiltonian poate fi determinat cu un algoritm de tip Greedy sau i se poate atribui valoarea +∞ (o valoare suficient de mare, de exemplu n · max{ai,j |∀i, j = 1, n, i 6= j, ai,j 6= ∞}). Dac˘a condit¸iile anterioare sunt ˆındeplinite, atunci xk ← xk + 1. Se marcheaz˘a ora¸sul ales la pasul k ca fiind vizitat (vizitatk ← 1), se actualizeaz˘a costul drumului pˆan˘a ˆın momentul curent (cost ← cost + axk−1 ,xk ), ¸si se trece la pasul k + 1. ˆIn caz contrar, nu exist˘a nici o alegere convenabil˘a pentru ora¸sul de pe pozit¸ia k ¸si va trebui s˘a ne ˆıntoarcem pentru o alt˘a alegere pentru ora¸sul vizitat la pasul k−1. Pentru aceasta se marcheaz˘a ora¸sul ales la pasul anterior ca nefiind vizitat (vizitatxk ← 0), ¸si se scade din costul drumului pˆan˘a ˆın momentul curent, costul muchiei [xk−1 , xk ] (cost ← cost − axk−1 ,xk ).
Fig. 4.8: Exemplu de graf pentru problema comis-voiajorului
Exemplul 4.18 Matricea costurilor corespunz˘ atoare valori: 0 14 6 ∞ 5 14 0 ∞ 12 16 6 ∞ 0 ∞ ∞ ∞ 12 ∞ 0 21 A= 5 16 ∞ 21 0 ∞ 20 12 ∞ 16 ∞ ∞ ∞ 24 ∞ ∞ ∞ 12 10 ∞
Graful respectiv are mai multe cicluri hamiltoniene: • C1 = [1, 2, 4, 5, 6, 7, 8, 3, 1]. • C2 = [1, 2, 5, 4, 8, 7, 6, 3, 1]. • C3 = [1, 5, 6, 2, 4, 7, 8, 3, 1]. 77
grafului din figura 4.8 are urm˘atoarele ∞ 20 12 ∞ 16 0 14 6
∞ ∞ ∞ 24 ∞ 14 0 10
∞ ∞ 12 10 ∞ 6 10 0
Algoritm 31 Algoritm pentru problema comis-voiajorului ( A - matricea de costuri ce cont¸ine distant¸ele dintre ora¸se Input: n - num˘ arul de ora¸se 1: function CanContinue(A, X, k, cost) 2: if (vizitatxk = 1) ∨ (cost + axk−1 ,xk > costoptim ) ∨ ((k 6= n + 1) ∧ (xk = x1 ))) then 3: return f alse 4: else 5: return true 6: end if 7: end function 8: procedure Evaluare Solutie(X, cost) 9: X optim ← X 10: costoptim ← cost 11: end procedure 12: procedure ComisVoiajorBacktracking(A, n) 13: x1 ← 1, cost ← 0, costoptim ← ∞ 14: k ← 2, xk ← 1 15: while (k > 1) do 16: gasit ← f alse 17: while ((xk + 1 ≤ n) ∧ (gasit = f alse)) do 18: xk ← xk + 1 19: gasit ← CanContinue(A, X, k, cost) 20: end while 21: if (gasit = true) then 22: if (k = n + 1) then 23: call Evaluare Solutie(X, cost + axk−1 ,xk ) 24: else 25: vizitatxk ← 1 26: cost ← cost + axk−1 ,xk k ←k+1 27: 28: xk ← 0 29: end if 30: else 31: k ←k−1 32: vizitatxk ← 0 33: if (k > 1) then 34: cost ← cost − axk−1 ,xk 35: end if 36: end if 37: end while optim } 38: Output {’Solutia optima: ’, xoptim , . . . , xoptim 1 n+1 , cost 39: end procedure
• C4 = [1, 3, 6, 7, 8, 4, 2, 5, 1]. Programul corespunz˘ator scris ˆın limbajul C este urm˘atorul: #include #include
78
Fig. 4.9: Rezultatul rul˘arii programului pentru determinarea ciclului hamiltonian optim
#define NMAX 100 //numarul maxim de noduri din graf #define LIMIT 100000 //lungimea maxima a unei muchii echivalenta cu infinit typedef int Matrice[NMAX][NMAX]; typedef int Vector[NMAX]; long cost_optim; //costul circuitului hamiltonian optim Vector x_optim; //elementele circuitului hamiltonian optim /** * Functia citeste valorile datelor de intrare. */ int readInput(Matrice a) { int n; int i, j, max = 0; scanf("%d", &n); for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) { scanf("%d", &a[i][j]); if (max < a[i][j]) max = a[i][j]; if (a[i][j] < 0) a[i][j] = LIMIT; } cost_optim = n * max; return n; } /** * Functia verifica daca a + b > c. */ int mai_mare(long a, long b, long c) { if (a + b > c)
79
return 1; else return 0; } /** * Functia afiseaza solutia calculata a problemei. */ void AfisareSolutie(int n) { int i; printf("Costul ciclului optim: %ld \n", cost_optim); printf("Ciclu hamiltonian: ["); for (i = 1; i < n+1; i++) printf("%d, ", x_optim[i]); printf("%d]\n", x_optim[n+1]); } /** * Functia de continuare: aici se verifica daca elementul de pe pozitia k * nu se mai afla in sirul A. */ int CanContinue(int n, Matrice a, int k, long cost, Vector vizitat, Vector x) { if ((vizitat[x[k]] == 1) || (mai_mare(cost, a[x[k-1]][x[k]], cost_optim)) || ((k != n+1) && (x[k] == 1))) return 0; else return 1; } /** * Functia salveaza solutia optima (circuitul hamiltonian de cost minim) * determinata/gasita pana in momentul curent. */ void EvaluareSolutie(int n, long cost, Vector x) { int i; cost_optim = cost; for (i = 1; i <= n+1; i++) x_optim[i] = x[i]; AfisareSolutie(n); } /** * Metoda backtracking implementata. */ void ComisVoiajorBacktracking(int n, Matrice a) { Vector x; Vector vizitat; int k, gasit; long cost;
80
memset(vizitat, 0, sizeof(vizitat)); x[1] = 1; cost = 0; k = 2; x[k] = 1; while (k > 1) { gasit = 0; while ((x[k] + 1 <= n) && (gasit == 0)) { x[k] = x[k] + 1; gasit = CanContinue(n, a, k, cost, vizitat, x); } if (gasit == 1) { if (k == n+1) EvaluareSolutie(n, cost + a[x[k-1]][x[k]], x); else { vizitat[x[k]] = 1; cost = cost + a[x[k-1]][x[k]]; k = k + 1; x[k] = 0; } } else { k = k - 1; vizitat[x[k]] = 0; if (k > 1) cost = cost - a[x[k-1]][x[k]]; } } } void main(void) { Matrice a; int n = readInput(a); ComisVoiajorBacktracking(n, a); }
Cont¸inutul fi¸sierului graf1.txt este: 8 0 14 6 -1 5 -1 -1 -1
14 0 -1 12 16 20 -1 -1
6 -1 0 -1 -1 12 -1 12
-1 12 -1 0 21 -1 24 10
5 16 -1 21 0 16 -1 -1
-1 20 12 -1 16 0 14 6
-1 -1 -1 24 -1 14 0 10
-1 -1 12 10 -1 6 10 0
Corespunz˘ator valorii ∞ am utilizat ˆın fi¸sierul de test valoarea negativ˘a −1. ˆIn timpul citirii datelor aceasta a fost ˆınlocuit˘a cu o constant˘a suficient de mare: ... scanf("%d", &a[i][j]); if (max < a[i][j])
81
max = a[i][j]; if (a[i][j] < 0) a[i][j] = LIMIT;
ˆIn timpul citirii se determin˘a valoarea maxim˘a, max{ai,j |∀i, j = 1, n, i 6= j, ai,j 6= ∞}, pentru a se init¸ializa mai apoi costul optim cu valoarea n · max. Funct¸ia ˆın care se verific˘a condit¸iile de continuare este urm˘atoarea: int CanContinue(int n, Matrice a, int k, long cost, Vector vizitat, Vector x) { if ((vizitat[x[k]] == 1) || (mai_mare(cost, a[x[k-1]][x[k]], cost_optim)) || ((k != n+1) && (x[k] == 1))) return 0; else return 1; }
Folosim o funct¸ie (mai mare()) cu scopul de a realiza verificarea inegalit˘a¸tii cost + axk−1 ,xk +1 > costoptim : int mai_mare(long a, long b, long c) { if (a + b > c) return 1; else return 0; }
¸si a preveni eventualele dep˘a¸siri (overflow )8 ˆın timpul calculelor. Nu am afi¸sat numai solut¸ia optim˘a, ci am ales s˘a afi¸s˘am toate ciclurile hamiltoniene care ˆımbun˘at˘a¸teau solut¸ia anterioar˘a: void EvaluareSolutie(int n, long cost, Vector x) { int i; cost_optim = cost; for (i = 1; i <= n+1; i++) x_optim[i] = x[i]; AfisareSolutie(n); }
Partea principal˘a a programului, motorul, este constituit˘a din funct¸ia: void ComisVoiajorBacktracking(int n, Matrice a) { Vector x; Vector vizitat; int k, gasit; long cost; memset(vizitat, 0, sizeof(vizitat)); x[1] = 1; cost = 0; k = 2; x[k] = 1; while (k > 1) { gasit = 0; 8
http://en.wikipedia.org/wiki/Arithmetic_overflow
82
while ((x[k] + 1 <= n) && (gasit == 0)) { x[k] = x[k] + 1; gasit = CanContinue(n, a, k, cost, vizitat, x); } if (gasit == 1) { if (k == n+1) EvaluareSolutie(n, cost + a[x[k-1]][x[k]], x); else { vizitat[x[k]] = 1; cost = cost + a[x[k-1]][x[k]]; k = k + 1; x[k] = 0; } } else { k = k - 1; vizitat[x[k]] = 0; if (k > 1) cost = cost - a[x[k-1]][x[k]]; } } }
Pentru n = 8, mult¸imea Ai va fi alc˘atuit˘a din elementele {1, 2, . . . , 8}. La ˆınceput, x1 va primi valoarea 1, iar pentru k = 2, x2 va primi tot valoarea 1: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 1 Pentru k = 2, se verific˘a condit¸iile de continuare pentru x2 = x2 + 1 = 1 + 1 = 2, ¸si deoarece acestea sunt respectate, se trece la pasul urm˘ator (ora¸sul urm˘ator) (la init¸ializare avem k = k + 1 = 3, x3 = 0): x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 0 Se verific˘a mai multe valori (1, 2 ¸si 3) pentru x3 , pentru care condit¸iile de continuare nu sunt ˆındeplinite. Pentru x3 = 4 condit¸iile de continuare sunt ˆındeplinite, ¸si se trece la pasul urm˘ator, k = k + 1 = 4: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 0 Pentru k = 4, prima valoare ce verific˘a condit¸iile de continuare este 5: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 0 ˆIn mod asem˘an˘ator se aleg valorile pentru pa¸sii k = 5, 6, 7, 8: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 6 3 8 7 0 La pasul k = 9, nici una dintre valorile 1, 2, . . . , 8 nu verific˘a condit¸iile de continuare (fiind la ultimul ora¸s, acesta ar trebui s˘a fie 1, adic˘a ora¸sul de unde s-a pornit). Astfel, se trece la pasul anterior: k = k - 1; vizitat[x[k]] = 0;
83
if (k > 1) cost = cost - a[x[k-1]][x[k]];
adic˘a la pasul k = k − 1 = 9 − 1 = 8. Valoarea 8, urm˘atoarea valoare din domeniul de valori neverificat˘a ¸si singura ce mai r˘am˘asese de atribuit pentru x8 , nu verific˘a condit¸iile de continuare, ¸si deoarece nu mai sunt valori de testat la pasul k = 8, se trece la nivelul anterior: k = k − 1 = 8 − 1 = 7. La pasul k = 7, deoarece nu au mai r˘amas valori netestate din mult¸imea A7 = {1, 2, . . . , 8}, se trece la pasul k = 6: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 6 3 Aici continu˘am cu verificarea valorilor 4, 5, 6 ¸si 7. Pentru x6 = 7 sunt verificate condit¸iile de continuare ¸si se poate trece la pasul urm˘ator, k = 7: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 6 7 0 Doar pentru valoarea 8 sunt ˆındeplinite condit¸iile de continuare: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 6 7 8 0 La pasul k = 8, xk = 3 ˆındepline¸ste cont¸iile de continuare ¸si se poate trece la nivelul urm˘ator, k = 9: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 6 7 8 3 0 Aici solut¸ia optim˘a g˘asit˘a este urm˘atoarea: x1 x2 x3 x4 x5 x6 x7 x8 x9 1 2 4 5 6 7 8 3 1 avˆand un cost al ciclului hamiltonian de 105. Se p˘astreaz˘a solut¸ia optim˘a identificat˘a pˆan˘a ˆın acest moment, se afi¸seaz˘a, ¸si se continu˘a efectuarea calculelor metodei pˆan˘a la epuizarea spat¸iului solut¸iilor posibile.
84
Fig. 4.10: Pasii efectuati de algoritm pentru determinarea ciclului hamiltonian asociat grafului din figura 4.8
85
Fig. 4.11: Pasii efectuati de algoritm pentru determinarea ciclului hamiltonian asociat grafului din figura 4.8 (continuare)
86
Capitolul 5 Arbori. Arbori binari Definit¸ia 5.1 Fiind dat˘a o mult¸ime M de elemente denumite noduri, vom numi arbore o submult¸ime finit˘a de noduri astfel ˆıncˆ at: 1. exist˘a un nod cu destinat¸ie special˘a, numit r˘ ad˘ acina arborelui; 2. celelalte noduri sunt repartizate ˆın n mult¸imi distincte, disjuncte dou˘ a cˆ ate dou˘ a, A1 , A2 , . . . , An , fiecare mult¸ime Ai constituind la rˆandul ei un arbore. Aceast˘a definit¸ie este una recursiv˘a, construct¸ia unui arbore depinzˆand de alt¸i arbori. Descendent¸ii direct¸i ai r˘ad˘acinii arborelui sunt r˘ad˘acinile subarborilor A1 , A2 , . . . , An , n ≥ 1. Orice nod al unui arbore cu r˘ad˘acin˘a constituie r˘ad˘acina unui subarbore compus din nodul respectiv ¸si tot¸i descendent¸ii s˘ai. Se observ˘a c˘a un arbore impune o structur˘a de organizare ierarhic˘a asupra elementelor unei mult¸imi. Definit¸ia 5.2 Se nume¸ste arbore liber (”free tree”) un graf G = (V, E) neorientat, conex ¸si aciclic. Teorema 5.1 (Propriet˘a¸tile arborilor liberi) Fie G = (V, E) un graf neorientat. Urm˘atoarele afirmat¸ii sunt adev˘arate: 1. G este un arbore liber. 2. Oricare dou˘a vˆarfuri din G sunt conectate printr-un drum elementar unic. 3. G este conex, dar, dac˘a elimin˘am o muchie oarecare din E, graful obt¸inut nu mai este conex. 4. G este conex, ¸si |E| = |V | − 1.
5. G este aciclic, ¸si |E| = |V | − 1.
6. G este aciclic, dar dac˘a ad˘aug˘am o muchie oarecare ˆın E, graful obt¸inut cont¸ine un ciclu. Dac˘a se alege un vˆarf sau nod drept r˘ ad˘ acina arborelui, atunci un arbore liber devine ˆ arbore cu r˘ad˘acin˘a. In general vom folosi termenul de arbore ˆın loc de termenul corect arbore cu r˘ad˘acin˘a. Un vˆarf terminal (frunz˘a) este un vˆarf f˘ar˘a descendent¸i. Vˆarfurile care nu sunt terminale sunt neterminale (sau noduri interioare). De obicei, se consider˘a c˘a exist˘a o ordonare a descendent¸ilor aceluia¸si p˘arinte.
87
1
2
3
4
8
5
9
10
6
12
11
7
13
14
15
Fig. 5.1: Exemplu de arbore binar plin cu 24 − 1 vˆarfuri
Definit¸ia 5.3 Adˆancimea unui vˆarf este dat˘ a de lungimea drumului de la r˘ ad˘ acin˘a la acel ˆ vˆarf. In˘alt¸imea unui vˆarf se determin˘a ca fiind lungimea celui mai lung drum dintre acel vˆarf ¸si un vˆarf terminal. ˆIn˘alt¸imea r˘ad˘acinii determin˘ a ˆın˘ alt¸imea arborelui. Nivelul unui vˆarf se calculeaz˘a ca diferent¸a dintre ˆın˘alt¸imea arborelui ¸si adˆ ancimea acestui vˆ arf. Toate vˆarfurile unui arbore ce au aceea¸si adˆancime se spune c˘a sunt pe acela¸si nivel. Un arbore oarecare cu r˘ad˘acin˘a este n-ar dac˘a fiecare vˆarf are pˆan˘a la n descendent¸i direct¸i. O mult¸ime de arbori disjunct¸i formeaz˘a o p˘ adure. Din punct de vedere grafic, un arbore se poate reprezenta descriind nodurile cu ajutorul unor cercuri sau p˘atrate ˆın care se afl˘a informat¸ia aferent˘a, iar relat¸ia de descendent¸˘a prin leg˘aturile ce unesc nodurile cu fii lor.
5.1
Arbori binari
Definit¸ia 5.4 Un arbore binar este un arbore oarecare cu r˘ ad˘ acin˘ a, fiecare vˆ arf al s˘au avˆand cel mult doi descendent¸i, indicˆandu-se cine este descendentul stˆ ang al r˘ ad˘ acinii ¸si cine este cel drept. Trebuie ment¸ionat c˘a un arbore binar nu este un caz particular de arbore oarecare, deoarece ˆın cazul unui arbore oarecare este suficient s˘a spunem c˘a c˘a un vˆarf are un singur decendent spre deosebire de cazul arborelui binar cˆand trebuie precizat dac˘a descendentul este drept sau stˆang. Arborele binar ce nu cont¸ine nici un nod se nume¸ste arbore vid sau arbore nul. Definit¸ia 5.5 Se nume¸ste arbore binar plin un arbore binar cu 2k − 1 vˆ arfuri a¸sezate pe i−1 k nivele astfel ˆıncˆat pe fiecare nivel i avem 2 vˆ arfuri. Se observ˘a c˘a fiecare vˆarf al arborelui are doi descendent¸i, descendentul stˆang ¸si descendentul drept, cu except¸ia nodurilor terminale. ˆIn figura 5.1 este prezentat un arbore binar plin ˆın care vˆarfurile sunt a¸sezate pe 4 niveluri (k = 4). De regul˘a vˆarfurile unui arbore binar plin se numeroteaz˘a ˆın ordinea nivelurilor, iar ˆın cadrul unui nivel, de la stˆanga la dreapta. Definit¸ia 5.6 Se nume¸ste arbore binar complet cu n vˆ arfuri un arbore binar plin avˆand k nivele, unde 2k−1 ≤ n < 2k , din care se elimin˘ a vˆ arfurile numerotate n+ 1, n+ 2, . . . , 2k −1.
88
1
2
3
4
5
8
9
6
10
7
12
11
Fig. 5.2: Exemplu de arbore binar complet cu 12 vˆarfuri
ˆIn figura 5.2 este ilustrat un exemplu de arbore binar complet. Un arbore binar poate fi transformat ˆıntr–un arbore binar plin ˆın modul urm˘ator: complet˘am arborele cu noi vˆarfuri astfel ˆıncˆat fiecare vˆarf s˘a aib˘a doi descendent¸i sau nici unul, renumerot˘am vˆarfurile iar vˆarfurilor nou introduse li se asociaz˘a o valoare ce va indica faptul c˘a ele sunt fictive.
5.1.1
Moduri de reprezentare
Cele mai cunoscute modalit˘a¸ti de reprezentare a arborilor binari sunt: 1. expresii cu paranteze; 2. forma standard ; 3. reprezentarea tip tat˘a. 1
2
8
3
5 4
9
6
7
Fig. 5.3: Exemplu de arbore binar
1. expresii cu paranteze– expresia ˆıncepe cu r˘ad˘acina ¸si dup˘a fiecare vˆarf k urmeaz˘a expresiile subarborilor ce au ca r˘ad˘acini descendent¸ii vˆarfului respectiv, separate prin virgul˘a ¸si incluse ˆıntre paranteze. Dac˘a un descendent al unui vˆarf nu exist˘a atunci expresia respectiv˘a este ˆınlocuit˘a cu 0 sau NULL. Un arbore cu un singur vˆarf (r˘ad˘acina), etichetat cu a1 , se reprezint˘a astfel: a1 (0, 0). Un arbore format din r˘ad˘acin˘a a1 ¸si doi descendent¸i, a2 ¸si a3 , se reprezint˘a astfel: a1 (a2 (0, 0), a3(0, 0)). Pentru arborele din figura 5.3 avem: 1(2(3(0, 4(0, 0)), 5(6(0, 0), 7(0, 0))), 8(0, 9(0, 0))) 89
2. forma standard – se indic˘a r˘ad˘acina arborelui, iar pentru fiecare vˆarf k se precizeaz˘a descendentul s˘au stˆang ¸si/sau drept. ( i , dac˘a nodul i este descendentul stˆang al nodului k Stangk = 0 , dac˘a nu exist˘a descendentul stˆang ( i , dac˘a nodul i este descendentul drept al nodului k Dreptk = 0 , dac˘a nu exist˘a descendentul drept. Pentru arborele din figura 5.3 avem urm˘atoarea reprezentare (Rad = 1): Nod Stang Drept
1 2 2 3 8 5
3 4 0 0 4 0
5 6 7 6 0 0 7 0 0
8 9 0 0 9 0
Fig. 5.4: Exemplu de arbore binar cu 8 noduri
Dup˘a modelul listei liniare dublu ˆınl˘a¸tuit˘a, putem folosi o structur˘a de date asem˘an˘atoare pentru reprezentarea unui arbore binar. Un nod al arborelui are urm˘atoarea configurat¸ie: typedef struct nod { TipOarecare data; struct nod* stang; struct nod* drept; }Nod;
Nod ∗ rad; – rad este o variabil˘a de tip pointer la Nod (variabila p˘astreaz˘a adresa unei zone de memorie ce cont¸ine date numerice de tip Nod). rad desemneaz˘a adresa r˘ad˘acinii arborelui. ˆIn figura 5.5 avem reprezentarea arborelui din figura 5.4. 3. reprezentarea tip tat˘a – fiec˘arui vˆarf k i se indic˘a tat˘al nodului respectiv. ( i , dac˘a nodul i este p˘arintele nodului k tatak = 0 , nu exist˘a Nod Tata
1 2 0 1
3 4 5 2 3 2
6 7 5 5
8 9 1 8
Observat¸ia 5.2 Urm˘atoarea numerotare este utilizat˘ a pentru anumite tipuri de arbori binari, ¸si are drept caracteristic˘ a faptul c˘ a structura arborelui se poate deduce printr-o numerotare adecvat˘a a vˆarfurilor: 90
Fig. 5.5: Exemplu de arbore binar cu 8 noduri
( ⌊ 2i ⌋ T atai = nu exist˘a ( 2·i Stangi = nu exist˘a
5.1.2
, i≥2 , i=1 , 2·i≤n , 2·i>n
( 2·i+1 , 2·i+1≤ n Drepti = nu exist˘ a , 2·i+1>n
Metode de parcurgere
Exist˘a mai multe moduri de parcurgere a unui arbore binar. Indiferent de metoda de parcurgere aleas˘a se parcurge mai ˆıntˆai subarborele stˆang ¸si apoi subarborele drept. Dup˘a momentul ˆın care un nod k este vizitat fat¸˘a de subarborii s˘ai stˆang ¸si drept, avem: • parcurgere ˆın preordine (r˘ad˘acin˘ a, subarbore stˆ ang, subarbore drept). ˆIn urma parcurgerii ˆın preordine a arborelui din figura 5.3 rezult˘a urm˘atoarea ordine pentru noduri: 1, 2, 3, 4, 5, 6, 7, 8, 9. ˆIn figura 5.6 este prezentat˘a parcurgerea ˆın preordine a unui arbore binar, descris˘a ˆın mai mult¸i pa¸si, conform descrierii recursive a acestei metode. Vom da exemplu de o procedur˘a nerecursiv˘a de vizitare ˆın preordine (vezi algoritmul 32). Se pleac˘a de la r˘ad˘acin˘a ¸si se merge spre stˆanga atˆata timp cˆat este posibil (liniile 5–8). Dac˘a nu se mai poate merge spre stˆanga se ˆıncearc˘a continuarea algorimului din descendentul drept, dac˘a este posibil. Dac˘a nu se poate face un pas spre dreapta (descendentul drept nu exist˘a – linia 10), se va urca ˆın arbore cˆate un nivel (liniile 12–22), pˆan˘a cˆand se ajunge ˆın situat¸ia de a se veni din descendentul stˆang (linia 19) ¸si se reia algoritmul trecˆand ˆın descendentul drept, dac˘a exist˘a (linia 25). ˆIn momentul ˆın care s–a ajuns ˆın nodul r˘ad˘acin˘a venind din dreapta, algoritmul se ˆıncheie. Datorit˘a faptului c˘a atunci cˆand se urc˘a ˆın arbore trebuie s˘a ¸stim din ce descendent venim, vom utiliza ¸si vectorul tata. Aceast˘a procedur˘a poate fi utilizat˘a ¸si pentru celelalte moduri de parcurgere, modific˘arile necesare referindu–se la momentul cˆand trebuie vizitat nodul curent (prin apelul procedurii V izit - instruct¸iunea call V izit(k)). 91
Fig. 5.6: Parcurgerea ˆın preordine a unui arbore binar a) arborele init¸ial; b) arborele vizitat ˆın preordine, primul nivel; c) arborele vizitat ˆın preordine, al doilea nivel; d) arborele vizitat ˆın preordine, al treilea nivel.
Implementarea ˆın limbajul C a algoritmului de parcurgere ˆın preordine este: #include #define MAX 100 /** stang[k] - descendentul stang al nodului k; 0 daca nu exista */ char stang[MAX]; /** drept[k] - descendentul drept al nodului k */ char drept[MAX]; /** tata[k] - parintele nodului k */ char tata[MAX]; /** numarul de noduri din arbore */ int n; /** nodul radacina */ int rad; void vizit(int nod) { printf("%2d ", nod); } void readInput(void) { int k; printf("n = "); scanf("%d", &n); printf("rad = "); scanf("%d", &rad); for (k = 1; k <= n; k++) { printf("stang[%d] = ", k); scanf("%d", &stang[k]); }
92
Algoritm 32 Algoritm de parcurgere ˆın preordine 1: procedure Preordine(Rad, Stang, Drept, T ata) 2: k ← rad 3: q←0 4: while (q = 0) do 5: while (stangk 6= 0) do 6: call V izit(k) 7: k ← stangk 8: end while 9: call V izit(k) 10: while (q = 0) ∧ (dreptk = 0) do 11: f ound ← 0 12: while (q = 0) ∧ (f ound = 0) do 13: j←k 14: k ← tatak 15: if (k = 0) then 16: q←1 17: else 18: if (j = stangk ) then 19: f ound ← 1 20: end if 21: end if 22: end while 23: end while 24: if (q = 0) then 25: k ← dreptk 26: end if 27: end while 28: return 29: end procedure
⊲ prelucrarea informat¸iei din nodul vizitat
for (k = 1; k <= n; k++) { printf("drept[%d] = ", k); scanf("%d", &drept[k]); } for (k = 1; k <= n; k++) { printf("tata[%d] = ", k); scanf("%d", &tata[k]); } } void preord(int rad) { int i, j; int q, found; i = rad; q = 0; while (q == 0) { while (stang[i] != 0) { vizit(i); i = stang[i];
93
} vizit(i); while ((q == 0) && (drept[i] == 0)) { found = 0; while ((q == 0) && (found == 0)) { j = i; i = tata[i]; if (i == 0) q = 1; else if (j == stang[i]) found = 1; } } if (q == 0) i = drept[i]; } } void main(void){ readInput(); preord(rad); }
• parcurgere ˆın inordine (arbore stˆ ang, r˘ ad˘ acin˘ a, arbore drept): 3, 4, 2, 6, 5, 7, 1, 8, 9.
Metoda de parcurgere ˆın inordine este ilustrat˘a de algoritmul 33 [93], [123]. Vom utiliza o stiv˘a S unde se vor introduce nodurile din care se coboar˘a spre stˆanga (liniile 6–9). Atunci cˆand nu se mai poate merge spre stˆanga se ˆıncearc˘a continuarea algoritmului din descendentul drept al nodului curent. Dac˘a acesta nu exist˘a, se reface drumul ˆınapoi spre r˘ad˘acin˘a, compus numai din descendent¸ii stˆangi (liniile 11–18). Parcurgerea se termin˘a ˆın momentul ˆın care stiva este vid˘a (vezi algoritmul 33). Implementarea ˆın limbajul C a algoritmului 33 este: #include #define MAX 100 /** stang[k] - descendentul stang al nodului k; 0 daca nu exista */ char stang[MAX]; /** drept[k] - descendentul drept al nodului k */ char drept[MAX]; /** Numarul de noduri din arbore */ int n; /** Nodul radacina */ int rad; void vizit(int nod) { printf("%2d ", nod); } void readInput(void) {
94
Algoritm 33 Algoritm de parcurgere ˆın inordine 1: procedure Inordine(Rad, Stang, Drept) 2: k ← rad 3: q←0 4: S←∅ 5: while (q = 0) do 6: while (stangk 6= 0) do 7: S⇐k 8: k ← stangk 9: end while 10: call V izit(k) 11: while (q = 0) ∧ (dreptk = 0) do 12: if (S = ∅) then 13: q←1 14: else 15: S⇒k 16: call V izit(k) 17: end if 18: end while 19: if (q = 0) then 20: k ← dreptk 21: end if 22: end while 23: return 24: end procedure
⊲ init¸ializare stiv˘a vid˘ a
⊲ S.push(k)
⊲ verificare dac˘ a stiva este vid˘ a
⊲ S.pop(k)
int k; printf("n = "); scanf("%d", &n); printf("rad = "); scanf("%d", &rad); for (k = 1; k <= n; k++) { printf("stang[%d] = ", k); scanf("%d", &stang[k]); } for (k = 1; k <= n; k++) { printf("drept[%d] = ", k); scanf("%d", &drept[k]); } } void inord(int rad) { int i, cap; int q; int stack[MAX]; i = rad; q = 0; cap = -1; while (q == 0) { while (stang[i] > 0) {
95
cap++; stack[cap] = i; i = stang[i]; } vizit(i); while ((q == 0) && (drept[i] == 0)) { if (cap < 0) q = 1; else { i = stack[cap]; cap--; vizit(i); } } if (q == 0) i = drept[i]; } } void main(void) { readInput(); inord(rad); }
• parcurgere ˆın postordine (subarbore stˆ ang, subarbore drept, r˘ ad˘ acin˘ a): 4, 3, 6, 7, 5, 2, 9, 8, 1 (vezi algoritmul 34). Algoritm 34 Algoritm de parcurgere ˆın postordine 1: procedure Postordine(K, Stang, Drept) 2: if (k 6= 0) then 3: call P ostordine(stangk , stang, drept) 4: call P ostordine(dreptk , stang, drept) 5: call V izit(k) 6: end if 7: return 8: end procedure
Modalitatea de vizitare recursiv˘a a arborilor binari este exemplificat˘a la arborii binari de sortare. Dac˘a presupunem realizarea unei act¸iuni de vizitare pe exteriorul frontierei arborelui, deplasarea efectuˆandu-se ˆın sens trigonometric ¸si avˆand drept punct de plecare r˘ad˘acina acestuia, vom ajunge s˘a vizit˘am cel put¸in o dat˘a toate nodurile arborelui (vezi figura 5.7). T ¸ inˆand cont de momentul vizit˘arii se poate obt¸ine oricare dintre metodele de parcurgere (preordine, inordine, postordine): pentru preordine vom marca un nod ˆın momentul ˆın care ˆıl vizit˘am pentru prima dat˘a, ˆın cazul metodei de vizitare ˆın inordine vom marca un nod ˆın momentul ˆın care ˆıl vizit˘am pentru prima dat˘a dac˘a este frunz˘a, respectiv a doua oar˘a dac˘a este un nod interior, iar la postordine vom marca un nod ˆın momentul ˆın care ˆıl vizit˘am pentru ultima oar˘a.
96
1
2
8
3
9
5 6
7
4
Fig. 5.7: Vizitarea unui arbore pe frontier˘a
Fig. 5.8: Arbore asociat expresiei aritmetice 2 · ((a + b) − (c + b))
Arborele asociat unei expresii aritmetice Oric˘arei expresii aritmetice i se poate asocia un arbore binar astfel: • fiec˘arui operator ˆıi corespunde un nod neterminal avˆand drept subarbori stˆang ¸si drept cei doi operanzi corespunz˘atori. • fiecare nod terminal este etichetat cu o variabil˘a sau o constant˘a. Arborele din figura 5.8 corespunde expresiei matematice: 2 · ((a + b) − (c + b)). ˆIn urma vizit˘arii ˆın postordine a acestui arbore se obt¸ine forma polonez˘ a postfixat˘ a asociat˘a expresiei anterioare: 2ab + cb + −∗. Pentru a obt¸ine valoarea expresiei respective, arborele poate fi parcurs ˆın postordine.
5.2
Arbori binari de c˘ autare
Definit¸ia 5.7 [93] Se nume¸ste arbore binar de c˘ autare un arbore binar ce verific˘a urm˘atoarea proprietate:
97
pentru orice vˆarf i apart¸inˆand arborelui, avem ( Infi ≥ Infj , pentru toate vˆarfurile j ce apart¸in descendentului stˆ ang al vˆ arfului i Infi ≤ Infj , pentru toate vˆarfurile j ce apart¸in descendentului drept al vˆ arfului i
unde Infi este informat¸ia asociat˘a vˆarfului i. Aceast˘ a informat¸ie este de un tip de date peste care avem o relat¸ie de ordine (de obicei un tip numeric sau tipul ¸sir de caractere). Un arbore binar de c˘autare mai este cunoscut ¸si sub numele de arbore binar de sortare deoarece parcurgerea ˆın inordine a unui arbore binar de c˘autare ne conduce la obt¸inerea elementelor vectorului Inf ˆın ordine cresc˘atoare. ˆIn principal aceast˘a structur˘a de date este utilizat˘a pentru reg˘asirea eficient˘a a unor informat¸ii. Operat¸iile de creare a arborelui, ¸stergere a unui nod, modificare a informat¸iei asociate unui nod sau inserare a unui nod se pot realiza ˆıntr-un mod optim astfel ˆıncˆat s˘a nu se distrug˘a proprietatea de arbore de c˘autare. Dac˘a dorim ca arborele s˘a nu cont¸in˘a chei duplicate, vom modifica definit¸ia de mai sus astfel: pentru orice vˆarf i apart¸inˆand arborelui, avem ( Infi < Infj , pentru toate vˆarfurile j ce apart¸in descendentului stˆang al vˆarfului i Infi > Infj , pentru toate vˆarfurile j ce apart¸in descendentului drept al vˆarfului i. ˆIn continuare vom utiliza aceast˘a definit¸ie pentru descrierea algoritmilor ce urmeaz˘a. Crearea unui arbore de c˘ autare Crearea (construirea) unui arbore de c˘autare se face aplicˆand ˆın mod repetat operat¸ia de inserare. C˘ autarea unei informatii ˆıntr-un arbore de c˘ autare Informat¸ia asociat˘a unui nod mai poart˘a numele de cheie. S˘a presupunem c˘a dorim s˘a c˘aut˘am o cheie x ˆıntr-un arbore de c˘autare. Vom compara mai ˆıntˆai cheia x cu informat¸ia asociat˘a r˘ad˘acinii arborelui (vezi algoritmul 35): 1. dac˘a cheia x este mai mic˘a decˆat cheia r˘ad˘acinii, atunci se va continua c˘autarea ˆın subarborele stˆang; 2. dac˘a cheia x este mai mare decˆat cheia r˘ad˘acinii, atunci se va continua c˘autarea ˆın subarborele drept; 3. dac˘a cheia x este egal˘a cu cheia r˘ad˘acinii, atunci c˘autarea se ˆıncheie cu succes. Inserarea unui nod ˆıntr-un arbore de c˘ autare Aceasta operat¸ie prezint˘a urm˘atoarele particularit˘a¸ti (vezi algoritmul 36): • se verific˘a prin intermediul operat¸iei de c˘autare dac˘a cheia x a nodului care se dore¸ste s˘a fie inserat mai exist˘a ˆın arbore; • ˆın cazul ˆın care c˘autarea se ˆıncheie cu succes, inserarea nu va mai avea loc; • ˆın cazul ˆın care operat¸ia de c˘autare se termin˘a f˘ar˘a succes, atunci se va crea un nod nou ˆın locul subarborelui vid unde s-a terminat c˘autarea. ˆIn figura 5.9 este ilustrat˘a inserarea unui nod ˆıntr–un arbore binar. 98
Algoritm 35 Algoritm de c˘autare a unei valori ˆıntr-un arbore binar de c˘autare 1: function SearchNode(p, Key) 2: if (p 6= N U LL) then 3: if (p.inf o > key) then 4: return SearchN ode(p.lef t, key) 5: else 6: if (p.inf o < key) then 7: return SearchN ode(p.right, key) 8: else 9: return p 10: end if 11: end if 12: else 13: return N U LL 14: end if 15: end function
Algoritm 36 Algoritm de inserare ˆıntr-un arbore binar de c˘autare 1: function InsertNode(p, Key) 2: if (p = N U LL) then 3: call CreateN ode(p, key) 4: else 5: if (p.inf o > key) then 6: p.lef t ← InsertN ode(p.lef t, key) 7: else 8: if (p.inf o < key) then 9: p.right ← InsertN ode(p.right, key) 10: end if 11: end if 12: end if 13: return p 14: end function
S ¸ tergerea unui nod dintr-un arbore de c˘ autare Mai ˆıntˆai se va c˘auta nodul ce se dore¸ste s˘a fie eliminat. S˘a presupunem c˘a am g˘asit nodul ce urmeaz˘a a fi ¸sters. Avem urm˘atoarele situat¸ii (vezi algoritmul 38): 1. nodul ce urmeaz˘a s˘a fie ¸sters este un nod terminal - se face ¸stergerea normal avˆand grij˘a s˘a se ˆınlocuiasc˘a leg˘atura din nodul p˘arinte c˘atre el cu null; 2. nodul ce urmeaz˘a s˘a fie ¸sters are un singur descendent - nodul respectiv se ¸sterge iar p˘arintele va cont¸ine acum noua leg˘atur˘a c˘atre descendentul fostului fiu; 3. nodul ce urmeaz˘a a fi ¸sters (notat A) are doi descendent¸i: • se determin˘a cel mai din stˆanga nod (notat B) din subarborele drept al nodului ce trebuie ¸sters (vezi algoritmul 37); acesta va fi ¸sters ˆın mod fizic, dar la un pas ulterior; 99
Fig. 5.9: Arbore binar de c˘autare. Inserarea unui nod.
• toate informat¸iile legate de datele cont¸inute ˆın nodul B vor fi copiate ˆın nodul A; • subarborele drept al nodului B se va lega fie ca descendent drept al nodului A (dac˘a nodul B este descendent direct al nodului A), fie ca descendent stˆang al tat˘alui nodului B; • se va ¸sterge fizic nodul B. Exemplul 5.3 Fie un arbore binar, eticheta fiec˘ arui nod fiind un ¸sir de caractere (un cuvˆant). Se cere s˘a se realizeze un program care s˘ a creeze un arbore binar de sortare, s˘ a-l parcurg˘a ˆın inordine ¸si s˘a permit˘a inserarea ¸si ¸stergerea unui nod specificat. #include #include #include #include
typedef struct nod {
100
Fig. 5.10: Arbore binar de c˘autare. S¸tergerea unui nod. a) Nod f˘ar˘a descendent¸i b) Nod cu un singur descendent c) Nod cu doi descendent¸i.
101
Algoritm 37 Algoritm pentru determinarea celui mai din stˆanga nod 1: function LeftmostNode(P arent, Curent) 2: while (curent.lef t 6= N U LL) do 3: parent ← curent 4: curent ← curent.lef t 5: end while 6: if (parent.right = curent) then 7: parent.right ← curent.right 8: else 9: parent.lef t ← curent.right 10: end if 11: return curent 12: end function
char *cuvint; struct nod *left,*right; } NOD; NOD *rad; NOD* Search(NOD *p, char *sir) { int ind; if (p != NULL) { ind = strcmp(p->cuvint, sir); if (ind < 0) return Search(p->right, sir); else if (ind > 0) return Search(p->left, sir); else return p; } else return NULL; } NOD* CreareNod(char *sir) { NOD *p; p = (NOD*)malloc(sizeof(NOD)); p->left = p->right = NULL; p->cuvint = (char*)malloc(sizeof(char) * (strlen(sir) + 1)); strcpy(p->cuvint, sir); return p; } NOD* Insert(NOD *p, char *sir) { int ind;
102
Algoritm 38 Algoritm de ¸stergere a unui nod ˆıntr-un arbore binar de c˘autare 1: function DeleteNode(p, Key) 2: if (p 6= N U LL) then 3: if (p.inf o > key) then 4: p.lef t ← DeleteN ode(p.lef t, key) 5: else 6: if (p.inf o < key) then 7: p.right ← DeleteN ode(p.right, key) 8: else 9: if (p.lef t = N U LL) ∨ (p.right = N U LL) then 10: if (p.lef t 6= N U LL) then 11: tmp ← p.lef t 12: else 13: tmp ← p.right 14: end if 15: call DisposeN ode(p) 16: p ← tmp 17: else 18: tmp ← p.right 19: tmp ← Lef tmostN ode(p, tmp) 20: tmp.lef t ← p.lef t 21: tmp.right ← p.right 22: call DisposeN ode(p) 23: p ← tmp 24: end if 25: end if 26: end if 27: end if 28: return p 29: end function
if (p == NULL) p = CreareNod(sir); else { ind = strcmp(p->cuvint, sir); if (ind < 0) p->right = Insert(p->right, sir); else if (ind > 0) p->left = Insert(p->left, sir); } return p; } void VisitInord(NOD *p) { if (p != NULL) { VisitInord(p->left); printf("[%s] ", p->cuvint);
103
VisitInord(p->right); } } NOD* LeftmostNod(NOD* parent, NOD* curent) { while (curent->left != NULL) { parent = curent; curent = curent->left; } if (parent->right == curent) parent->right = curent->right; else parent ->left = curent->right; return curent; } NOD* ElibNod(NOD *p) { free(p->cuvint); free(p); return NULL; } NOD* DeleteNod(NOD* p, char *sir) { NOD *tmp; int ind; if (p != NULL){ ind = strcmp(p->cuvint, sir); if (ind < 0) p->right = DeleteNod(p->right, sir); else if (ind > 0) p->left = DeleteNod(p->left, sir); else if (p->left == NULL || p->right == NULL) { if (p->left != NULL) tmp = p->left; else tmp = p->right; ElibNod(p); p = tmp; } else{ tmp = p->right; tmp = LeftmostNod(p, tmp); tmp->left = p->left; tmp->right = p->right; ElibNod(p); p = tmp; }
104
} return p; } void main(void) { char cuvint[100]; char ch; NOD *p; rad = NULL; while (1) { printf("***********************************\n"); printf("1. Inserare \n"); printf("2. Cautare\n"); printf("3. Stergere\n"); printf("4. Afisare arbore\n"); printf("0. Exit \n"); ch = getch(); if (ch != ’0’ && ch != ’4’) { printf("Cuvint: "); scanf("%s", cuvint); } switch (ch) { case ’1’: rad = Insert(rad, cuvint); break; case ’2’: p = Search(rad, cuvint); if (!p) printf("Cuvintul %s nu a fost gasit! \n", cuvint); else printf("Cuvintul %s exista in arbore! \n", cuvint); break; case ’3’: rad = DeleteNod(rad, cuvint); break; case ’4’: VisitInord(rad); break; case ’0’: exit(1); } } }
5.3 1.
Exercit¸ii (a) S˘a se realizeze o subrutin˘a ce determin˘a cea mai mare valoare p˘astrat˘a ˆıntr–un arbore binar de c˘autare; (b) Aceea¸si cerint¸˘a pentru cea mai mic˘a valoare.
2. S˘a se realizeze o subrutin˘a ce determin˘a toate nodurile unui arbore binar de c˘autare cu proprietatea c˘a informat¸ia k asociat˘a unui nod verific˘a relat¸ia a ≤ k ≤ b, unde a ¸si b sunt date. 3.
(a) S˘a se realizeze un algoritm care determin˘a num˘arul arborilor binari distinct¸i cu n noduri, unde n este un num˘ar natural dat. 105
Fig. 5.11: Exemplu de arbore binar complet cu 7 vˆarfuri
(b) Aceea¸si problem˘a pentru arbori binari de c˘autare. 4. S˘a consider˘am o secvent¸˘a de intrare compus˘a din termeni definit¸i recursiv astfel: (a) X este un termen; (b) dac˘a A ¸si B sunt termeni, atunci (A B) este un termen; (c) orice termen se construie¸ste cu ajutorul regulilor (i) ¸si (ii). Un termen este transformat cu ajutorul urm˘atoarei reguli de rescriere: ¸sablonul (((X A) B) C) unde A, B ¸si C sunt termeni, este ˆınlocuit cu ((A C)(B C)). Reducerea unui termen ˆınseamn˘a aplicarea acestei reguli. Un termen se nume¸ste redus dac˘a nu cont¸ine nici un subtermen care s˘a poat˘a fi redus. Un termen poate fi considerat ca subtermen pentru el ˆınsu¸si. S˘a se realizeze un algoritm care determin˘a pentru un termen de intrare, termenul redus corespunz˘ator. Un termen cont¸ine doar simbolurile ’X’, ’(’ ¸si ’)’, f˘ar˘a spat¸ii ˆıntre ele. Lungimea unui termen de intrare este 100. Observat¸ia 5.4 O expresie va avea maxim 9 nivele de parantezare. Num˘ arul de reduceri ce pot fi efectuate este finit. Liniile de ie¸sire cont¸in maxim 80 caractere. Caracterele posibile din termenii de ie¸sire sunt tot ’X’, ’(’ ¸si ’)’. Intrare (((XX)X)X) (((XX)(XX)X) (XX)
Ie¸sire ((XX)(XX)) ((XX)((XX)X)) (XX)
(ACM, Bucure¸sti, 1996)
5. Prin arbore binar complet ˆınt¸elegem un arbore binar ˆın care un nod are fie doi descendent¸i, fie nu are nici unul. Un arbore binar complet poate fi reprezentat prin codificarea drumurilor de la r˘ad˘acin˘a la fiecare frunz˘a utilizˆand numai valorile 0 ¸si 1: atunci cˆand coborˆam ˆın arbore spre stˆanga se adaug˘a valoarea 0 iar atunci cˆand coborˆam spre dreapta se adaug˘a valoarea 1.
106
Pentru arborele din figura 5.11 drumurile de astfel: ABC : ABDE : ABDF : AG :
la r˘ad˘acin˘a la fiecare frunz˘a se codific˘a 00 010 011 1
Concatenˆand toate drumurile de la r˘ad˘acin˘a c˘atre frunze, arborele anterior poate fi reprezentat prin secvent¸a ce poate fi interpretat˘a ca reprezentarea ˆın baza 2 a unui num˘ar (000100111). Fiind date m ¸si n dou˘a numere naturale, se cere s˘a se determine dac˘a exist˘a un arbore binar complet a c˘arui reprezentare va cont¸ine exact m cifre 0 ¸si n cifre 1 (0 < m, n ≤ 100). (Timi¸soara-preg˘atire, 1996)
6. Scriet¸i o subrutin˘a nerecursiv˘a care num˘ar˘a nodurile frunz˘a ale unui arbore binar. 7. Realizat¸i o subrutin˘a care s˘a determine nivelul cu cele mai multe noduri frunze dintr–un arbore binar. 8. Determinat¸i ˆın˘a¸timea unui arbore binar printr–o funct¸ie nerecursiv˘a. 9. Se consider˘a un dict¸ionar ce este implementat sub forma unei structuri de arbore. Pentru fiecare cuvˆant se cunoa¸ste traducerea acestuia precum ¸si probabilitatea lui de aparit¸ie ˆın texte. Se cere s˘a se realizeze un algoritm care s˘a conduc˘a la o c˘autare optim˘a a cuvintelor ˆın dict¸ionar. Se ment¸ioneaz˘a faptul c˘a probabilitatea de a c˘auta un cuvˆant care nu se g˘ase¸ste ˆın dict¸ionar este zero. Datele de ie¸sire constau din afi¸sarea arborelui optimal de c˘autare compus din cuvintele introduse. Spre exemplu, un set de date de intrare poate fi: 5 1 4 2 1 2
abroad in_strainatate baker brutar calf gamba dice zaruri ear ureche
10. O cas˘a de comenzi este interesat˘a de crearea unui program pe calculator care s˘a ¸tin˘a evident¸a comenzilor ˆın ordinea datelor la care au fost onorate, iar ˆın cadrul fiec˘arei date de livrare, ˆın ordine lexicografic˘a dup˘a numele produsului. S˘a se scrie un program care folose¸ste structuri de date de tip arbore. Intrarea este format˘a din mai multe linii. Pe fiecare linie avem cˆate o comand˘a dat˘a sub forma: [numep rodus] [zi] [luna] [an]. Intrarea se termin˘a cu o linie pe care avem doar ’&’. Ie¸sirea este format˘a prin afi¸sarea comenzilor ˆın ordinea datei la care au fost onorate, iar ˆın cadrul unei date, ˆın ordine lexicografic˘a ˆın funct¸ie de numele comenzii. Indicat¸ie: Se va crea un arbore de c˘autare dup˘a data de livrare ¸si ˆın fiecare nod va fi un arbore de c˘autare dup˘a numele comenzii. 107
11. Se consider˘a o expresie logic˘a formata din n variabile logice reprezentate printr–o singur˘a liter˘a ¸si operatorii & (AND), | (OR), ! (NOT) (nu avem paranteze). Expresia este reprezentat˘a sub forma unui arbore binar. Se cere s˘a se realizeze un algoritm care pentru o expresie logic˘a dat˘a cerceteaz˘a existent¸a unei combinat¸ii de valori logice (true/false), pentru care expresia dat˘a ia valoarea logic˘a true. De exemplu pentru expresia a|!b|c&d solut¸ia este a = f alse, b = f alse, c = f alse, d = f alse, iar pentru expresia !a&a nu avem solut¸ie. 12. Se consider˘a o expresie matematic˘a reprezentat˘a printr-un arbore binar. Operatorii corespund operat¸iilor matematice uzuale +, −, ∗, /, ¸si ? (pentru ridicare la putere). Nu avem paranteze ¸si tot¸i operatorii sunt operatori binari. Operanzii sunt identificat¸i printr–o singur˘a liter˘a. Se cere s˘a se realizeze un algoritm ce creaz˘a structura de date corespunz˘atoare unei expresii date ¸si s˘a evalueaze expresia pentru o mult¸ime de valori ale operanzilor. Pentru expresia a + b ∗ c unde a = 2, b = 3 ¸si c = −1 avem valoarea −1, iar ˆın urma evalu˘arii expresiei a − b/c?a cu a = 2, b = 9 ¸si c = 3 obt¸inem valoarea 1.
108
Capitolul 6 Arbori oarecare Reamintim definit¸ia unui arbore oarecare: Definit¸ia 6.1 Fiind dat˘a o mult¸ime M de elemente denumite noduri, vom numi arbore o submult¸ime finit˘a de noduri astfel ˆıncˆ at: 1. exist˘a un nod cu destinat¸ie special˘a, numit r˘ ad˘ acina arborelui; 2. celelalte noduri sunt repartizate ˆın n mult¸imi disjuncte dou˘ a cˆ ate dou˘ a, A1 , A2 , · · · , An , fiecare mult¸ime Ai constituind la rˆandul ei un arbore.
6.1
Moduri de reprezentare
Pentru reprezentarea arborilor oarecare pot fi utilizate urm˘atoarele metode: • leg˘aturi fiu-frate: f iuk - este primul descendent al vˆarfului k, f ratek - urm˘atorul descendent al tat˘alui nodului k, ce urmeaz˘a dup˘a k. ˆIntre descendent¸ii unui vˆarf se presupune c˘a definim o relat¸ie de ordine. R˘ad˘acina arborelui din figura 6.1 este Rad = 1. 1
2
3
5
4
6
7
8
9
10
Fig. 6.1: Exemplu de arbore oarecare
Table 6.1: Reprezentarea cu leg˘aturi fiu–frate a arborelui din figura 6.1. Nod 1 2 Fiu 2 0 Frate 0 3
3 4 5 7 4 0
5 6 7 0 0 0 6 0 8
109
8 9 10 0 0 0 9 10 0
Dac˘a identific˘am F iu cu Stang ¸si F rate cu Drept, unui arbore oarecare i se poate asocia un arbore binar. Pentru reprezentarea unui arbore oarecare, modelul de reprezentare fiu-frate ˆın varianta de alocare static˘a poate fi u¸sor extins la o variant˘a de alocare dinamic˘a, folosind pointeri pentru leg˘aturile c˘atre primul descendent respectiv pentru urm˘atorul frate. Astfel un nod al arborelui poate avea urm˘atoarea configurat¸ie: typedef struct nod { TipOarecare data; struct nod* fiu; struct nod* frate; }Nod;
Asem˘an˘ator cu modelul construit la arbori binari, rad (Nod ∗ rad;) este o variabil˘a de tip pointer la Nod (variabila p˘astreaz˘a adresa unei zone de memorie). rad desemneaz˘a adresa r˘ad˘acinii arborelui.
Fig. 6.2: Exemplu de arbore oarecare cu 10 noduri reprezentat prin leg˘aturi fiu-frate
ˆIn figura 6.2 este reprezentat arborele din figura 6.1 prin leg˘ aturi fiu-frate. • lista descendent¸ilor. ˆIn cadrul acestui mod de reprezentare fiecare vˆarf este descris prin lista descendent¸ilor s˘ai. Pentru memorare se va utiliza un vector cu n componente: ( 0 , vˆarful respectiv nu are descendent¸i Ck = j , j indic˘a adresa (coloana) unde ˆıncepe lista descendent¸ilor vˆarfului k. Listele de descendent¸i se p˘astreaz˘a prin intermediul unei matrice L cu 2 linii ¸si N − 1 coloane: L1,k - un descendent al vˆarfului a c˘arui list˘a cont¸ine coloana k a matricei date. ( 0 , dac˘a descendentul respectiv este ultimul L2,k = j , j indic˘a coloana unde se afl˘a urm˘atorul descendent
110
Table 6.2: Reprezentarea arborelui din figura 6.1 folosind liste cu descendent¸i. Nod 1 2 3 C 1 0 4 2 3 4 L= 2 3 0
4 6
5 0 5 6 5 0
6 0 7 7
7 0 8 8
8 0 9 9
9 10 0 0 10 0
Exploatˆand mai departe aceast˘a idee, ˆıntr-un nod al arborelui oarecare se poate p˘astra o list˘a cu adresele descendent¸ilor s˘ai, lista fiind reprezentat˘a sub forma unui tablou alocat static sau dinamic. Oricum modelul se recomand˘a atunci cˆand num˘arul descendent¸ilor unui nod este limitat superior, sau cˆand acesta este cunoscut/stabilit ˆın momentul ˆın care se construie¸ste arborele. Modific˘arile efectuate asupra arborelui, cum ar fi inser˘ari de descendent¸i noi, atunci cˆand intr˘arile alocate pentru ace¸sti descendent¸i sunt deja alocate nu poate conduce decˆat la realocarea spat¸iului de memorie cu un cost reflectat ˆın complexitatea algoritmului. Astfel dac˘a num˘arul de descendent¸i este limitat superior putem defini #define NMAX 100 typedef struct nod { TipOarecare data; struct nod* children[NMAX]; }Nod;
sau typedef struct nod { TipOarecare data; int no_of_children; struct nod** children; }Nod;
atunci cˆand num˘arul maxim de descendent¸i nu poate fi cunoscut decˆat ˆın momentul construirii arborelui. Astfel, ˆın acel moment, se aloc˘a spat¸iu pentru un tablou de pointeri c˘atre structura de tip Nod definit˘a (children = malloc(sizeof(Nod*) * no of children)). ˆIn figura 6.3 este reprezentat arborele din figura 6.1 folosind liste cu descendent¸i. • Tata - fiec˘arui nod k se indic˘a nodul p˘arinte. Table 6.3: Reprezentarea arborelui din figura 6.1 folosind vectorul tata. Nod Tata
1 2 3 0 1 1
4 5 1 3
6 7 8 3 4 4
9 10 4 4
Acest mod de reprezentare are dezavantajul c˘a nu p˘astreaz˘a ˆın mod explicit ordinea descendent¸ilor unui nod. Aceast˘a ordine poate fi dedus˘a printr–o numerotare adecvat˘a a nodurilor. De obicei reprezentarea cu vectorul tata nu este folosit˘a singur˘a ci ˆımpreun˘a cu alte moduri de reprezentare, ca o completare. 111
rad 3
1
2
0
2
3
5
4
0
6
0
7
0
4
8
0
9
0
10
0
Fig. 6.3: Exemplu de arbore oarecare cu 10 noduri reprezentat prin liste cu descendent¸i
De exemplu, reprezentarea unui nod propus˘a la primul punct, poate fi modifcat˘a astfel ˆıncˆat s˘a incorporeze ¸si informat¸ii despre nodul p˘arinte: typedef struct nod { TipOarecare struct nod* struct nod* struct nod* }Nod;
rad
data; fiu; tata; frate;
NULL 1
NULL NULL
2
3
4
NULL
NULL 5
6
NULL
NULL
NULL
7
8
9
10
NULL
NULL
NULL
NULL
NULL
Fig. 6.4: Exemplu de arbore oarecare cu 10 noduri reprezentat prin leg˘aturi fiu-frate-tata
ˆIn figura 6.4, reprezentarea din figura 6.2 este ˆımbun˘at˘a¸tit˘a prin leg˘ atura tata.
6.2
Metode de parcurgere
Pentru parcurgerea unui arbore oarecare se pot folosi metodele generale pentru parcurgerea arborelui binar asociat. Arborele binar asociat unui arbore oarecare se obt¸ine ˆın urma realiz˘arii corespondent¸ei F iu ⇒ Stang ¸si F rate ⇒ Drept. 112
ˆIn plus fat¸˘a de acestea, avem ¸si dou˘a metode de parcurgere pe care putem s˘a le numim specifice unui arbore oarecare. Acestea se pot clasifica dup˘a momentul ˆın care se realizeaz˘a vizitarea nodului p˘arinte astfel: • A–preordine: se viziteaz˘a mai ˆıntˆai r˘ad˘acina, ¸si apoi, ˆın ordine, subarborii s˘ai [93], [123]. Metoda este identic˘a cu metoda de parcurgere ˆın preordine a arborelui binar ata¸sat: 1, 2, 3, 5, 6, 4, 7, 8, 9, 10 (vezi algoritmul 39). Parcurgerea ˆın A–preordine este exemplificat˘a pe arborele oarecare din figura 6.1. Algoritm 39 Algoritm de parcurgere ˆın A-preordine 1: procedure APreordine(Rad, F iu, F rate) 2: k ← rad 3: q←0 4: S←∅ 5: while (q = 0) do 6: if (k 6= 0) then 7: call V izit(k) 8: S⇐k 9: k ← f iuk 10: else 11: if (S = ∅) then 12: q←1 13: else 14: S⇒k 15: k ← f ratek 16: end if 17: end if 18: end while 19: return 20: end procedure
⊲ init¸ializare stiv˘a vid˘ a
⊲ S.push(k)
⊲ verificare dac˘ a stiva este vid˘ a
⊲ S.pop(k)
• A–postordine: se viziteaz˘a mai ˆıntˆai tot¸i subarborii ce au drept r˘ad˘acini, descendent¸ii r˘ad˘acinii arborelui ¸si apoi r˘ad˘acina arborelui [93], [123]. ˆIn urma parcurgerii ˆın A– postordine a arborelui din figura 6.1 se obt¸ine 2, 5, 6, 3, 7, 8, 9, 10, 4, 1. Exemplul 6.1 [31] ˆIn continuare se prezint˘ a implementarea ˆın limbajul de programare C a variantelor recursive pentru fiecare dintre cele dou˘ a metode de vizitare ale unui arbore oarecare. #include #include #include /** * Definitii de tipuri necesare cozii */ typedef struct nod { int id; float info; struct nod *down, *next;
113
}NOD; typedef NOD* TINFO; /** * Definitii de tipuri pentru reprezentarea arborelui */ typedef struct cnod { TINFO info; //informatia memorata in nod struct cnod *next; //adresa urmatorului element }CNOD; CNOD *prim = NULL, *ultim = NULL; NOD* citListaDescendenti(NOD *up); /** * Functie ce testeaza daca coada e vida * @return 1 daca coada e vida * 0 altfel */ int isEmpty(void) { if (prim == NULL) return 1; else return 0; } /** * Functia adauga un element la sfarsitul unei cozi * unde n reprezinta elementul ce se adauga. */ void add(TINFO n) { CNOD *curent; curent = (CNOD *) malloc(sizeof(CNOD)); curent->info = n; curent->next = NULL; if (!prim) { prim = ultim = curent; } else { ultim->next = curent; ultim = curent; } } /** * Functia extrage un element din coada. Returneaza elementul scos. */ TINFO get(void) { CNOD *curent; TINFO n;
114
if (prim == ultim) { n = prim->info; free(ultim); prim = ultim = NULL; } else { curent = prim; n = prim->info; prim = prim->next; free(curent); } return n; } /** * Functia realizeaza crearea unui arbore oarecare. Se introduce initial radacina, * apoi descendentii ei, dupa care se introduc descendentii nodurilor de pe * nivelul 1, dupa care se introduc descendentii nodurilor de pe nivelul 2 s.a.m.d. */ NOD* creare(void) { NOD *p, *c; p = (NOD *)malloc(sizeof(NOD)); p->next = NULL; printf("Dati id:"); scanf("%d", &p->id); add(p); while (!isEmpty()) { c = get(); c->down = citListaDescendenti(c); } return p; } /** * Functia citeste informatia asociata unui nod. * @return 1 daca citirea s-a facut corect * 0 altfel */ int citInfo(NOD *pn) { printf("Id:"); pn->next = NULL; pn->down = NULL; return scanf("%d",&pn->id) == 1; } /** * Functia realizeaza citirea listei de descendenti ai nodului up si * returneaza adresa primului descendent din lista. */ NOD* citListaDescendenti(NOD *up) { NOD *prim, *ultim, *p; NOD n;
115
printf("\nLista de descendenti pt %d (CTRL+Z) daca nu are\n", up->id); prim = ultim = NULL; while (citInfo(&n)) { p = (NOD *)malloc(sizeof(NOD)); *p = n; if (prim == NULL) prim = ultim = p; else { ultim->next = p; ultim = p; } add(p); } return prim; } /** * Parcurgerea in A-preordine a arborelui cu radacina p. */ void aPreordine(NOD *p) { NOD *desc; printf("%d ", p->id); desc = p->down; while (desc != NULL) { aPreordine(desc); desc = desc->next; } } /** * Parcurgerea in A-postordine a arborelui cu radacina p. */ void aPostordine(NOD *p) { NOD *desc; desc = p->down; while (desc != NULL) { aPostordine(desc); desc = desc->next; } printf("%d ",p->id); } void main(void) { NOD *rad; rad = creare(); printf("\n Parcurgerea in A-Preordine este:\n"); aPreordine(rad); printf("\n Parcurgerea in A-Postordine este:\n");
116
aPostordine(rad); }
6.3
Arbori de acoperire de cost minim
Fie G = (V, E) un graf neorientat ¸si fie c : E −→ R o funct¸ie de cost ce asocieaz˘a o valoare real˘a fiec˘arei muchii. Not˘am cu TG mult¸imea arborilor part¸iali ai grafului G (un arbore part¸ial este un graf part¸ial conex ¸si f˘ar˘a cicluri al grafului init¸ial). Cerint¸a problemei este aceea de a determina un arbore T ′ ∈ TG avˆand costul cel mai mic dintre tot¸i arborii ce apart¸in mult¸imii TG : c(T ′ ) = min{c(T )|T ∈ TG } P unde costul unui arbore T se definet¸te ca c(T ) = e∈ET c(e). Arborele T ′ ce posed˘a aceast˘a proprietate se nume¸ste arbore de acoperire de cost minim (eng. minimum spanning tree MST ). Se mai ˆıntˆalne¸ste ¸si sub denumirea de arbore part¸ial de cost minim sau arbore part¸ial minim. Altfel spus, se cere s˘a se determine un graf part¸ial conex G1 = (V, E1 ) (E1 ⊆ E) cu proprietatea c˘a suma costurilor tuturor muchiilor este minim˘a, graful part¸ial de cost minim fiind chiar un arbore. Determinarea arborelui de acoperire de cost minim are multe aplicat¸ii practice: de exemplu, dac˘a se dau n ora¸se precum ¸si costul leg˘aturilor ˆıntre acestea, se cere s˘a se determine o conectare a tuturor ora¸selor astfel ˆıncˆat oricare dou˘a ora¸se s˘a fie conectate direct sau indirect iar costul conect˘arii s˘a fie minim. Lema 6.2 (Proprietatea t˘aieturii) Fie S o submult¸ime de noduri a lui V ¸si e muchia ce are costul minim dintre toate muchiile ce au o singur˘ a extremitate ˆın S. Atunci arborele part¸ial de cost minim va cont¸ine muchia e. Demonstrat¸ie: Fie T ′ un arbore part¸ial de cost minim. Presupunem prin reducere la absurd c˘a e ∈ / ET ′ . Dac˘a ad˘aug˘am muchia e la T ′ se obt¸ine un ciclu C. ˆIn acest ciclu exist˘a o alt˘a muchie f ce are exact o extremitate ˆın mult¸imea S. ˆInlocuind muchia f cu muchia e ˆın arborele T ′ , obt¸inem un alt arbore de acoperire T ′′ = ′ T ∪ {e} \ {f }. Deoarece c(e) < c(f ) vom avea c˘a c(T ′′ ) = c(T ′ ) + c(e) − c(f ) ⇒ c(T ′′ ) < c(T ′ ) adic˘a am | {z } <0
obt¸inut un arbore de acoperire T ′′ ce are costul mai mic decˆat T ′ , contradict¸ie cu faptul c˘a T ′ este un arbore de acoperire de cost minim. Definit¸ia 6.2 Se nume¸ste t˘ aietur˘ a a grafului G o partit¸ie de dou˘ a submult¸imi a mult¸imii nodurilor V , notat˘a astfel: < S, T > (unde S ∪ T = V ¸si S ∩ T = ∅). Lema 6.3 (Proprietatea ciclului) Fie C un ciclu ¸si f muchia ce are costul maxim dintre toate muchiile ce apart¸in lui C. Atunci arborele part¸ial de cost minim T ′ nu cont¸ine muchia f. Demonstrat¸ie: Fie T ′ un arbore part¸ial de cost minim. Presupunem prin reducere la absurd c˘a f ∈ ET ′ . Dac˘a ¸stergem muchia f din arborele T ′ , se obt¸ine o t˘aietur˘a < S, V \ S >. Avem c˘a f ∈ C ¸si o extremitate a lui f apart¸ine lui S. Exist˘a atunci o alt˘a muchie e cu proprietatea c˘a e ∈ C ¸si e are o extremitate ce apart¸ine lui S. 117
ˆInlocuind muchia f cu muchia e ˆın arborele T ′ , obt¸inem un alt arbore de acoperire T ′′ = T ′ ∪ {e} \ {f }. Deoarece c(e) < c(f ) ⇒ c(T ′′ ) < c(T ′ ) adic˘a am obt¸inut un arbore de acoperire T ′′ ce are costul mai mic decˆat T ′ , contradict¸ie. Majoritatea algoritmilor ce calculeaz˘a arborele de acoperire de cost minim prezint˘a aceea¸si tehnic˘a general˘a de calcul. La ˆınceput se porne¸ste cu o p˘adure de arbori, T 0 = {T10 , T20, . . . , Tn0 } unde Ti0 = {xi }, i = 1, n este un arbore format dintr–un singur nod. La pasul k vom avea k mult¸imea T k compus˘a din n − k arbori: T k = {T1k , T2k , . . . , Tn−k }. k ′ ′ La fiecare pas, se alege un arbore Ti ¸si o muchie (u , v ) avˆand costul minim dintre toate muchiile (u, v) cu proprietatea c˘a o extremitate apart¸ine mult¸imii de noduri a arborelui Tik (u′ ∈ Tik ) ¸si cealalt˘a extremitate apart¸ine mult¸imii V \ VTik (v ′ ∈ V \ VTik ). Prin urmare, mult¸imea T k+1 se obt¸ine din mult¸imea T k prin reuniunea arborilor Tik ¸si Tjk (i 6= j), unde u′ ∈ Tik ¸si v ′ ∈ Tjk (T k+1 = T k \ {Tik , Tjk } ∪ {Tik ∪ Tjk }). ˆIn final, la pasul n − 1, mult¸imea T n−1 = {T n−1 } va fi compus˘a dintr–un singur element, 1 acesta fiind arborele de acoperire de cost minim. Algoritmii cei mai cunoscut¸i pentru determinarea arborilor de acoperire de cost minim sunt: 1. Algoritmul lui Boruvka (vezi algoritmul 40) Algoritm 40 Algoritmul lui Boruvka (varianta schematica) 1: procedure Boruvka1(G, C, n; L) 2: init¸ializeaz˘ a p˘ adurea de arbori P compus˘ a din n arbori, fiecare arbore fiind compus dintr–un 3: 4: 5: 6: 7: 8: 9: 10: 11:
singur nod L←∅ while (|P| > 1) do for T ∈ P do alege e muchia de cost minim de la T la G \ T L ⇐ e ⊲ adaug˘ a muchia e la lista de muchii alese ce vor forma arborele de acoperire de cost minim end for adaug˘ a toate muchiile selectate ˆın cadrul f or-ului anterior la P end while end procedure
2. Algoritmul lui Prim (vezi algoritmul 41) 3. Algoritmul lui Kruskal (vezi algoritmul 42) Algoritmul lui Prim implementat simplu are o complexitate O(n2 )[30] ¸si atinge o complexitate de O(m log n) dac˘a se folosesc heap–uri Fibonacci [54], sau pairing heaps [115]. ˆIn tabelul 6.4 sunt prezentat¸i mai mult¸i algoritmi dezvoltat¸i de–a lungul timpului pentru determinarea arborelui de acoperire minimal ¸si complexit˘a¸tile lor. Karger, Klein ¸si Tarjan [79] pornind de la algoritmul lui Boruvka au realizat un algoritm randomizat pentru determinarea arborelui de acoperire minimal, avˆand o complexitate liniar˘a, iar Chazelle [26] a dezvoltat un algoritm avˆand complexitatea O(nα(m, n)) (α(m, n) este inversa funct¸iei lui Ackerman). Pe de alt˘a parte, Pettie ¸si Ramachandran [105] au propus un algoritm demonstrat ca fiind optimal, avˆand complexitatea cuprins˘a ˆıntre O(n + m) ¸si O(nα(m, n)). 118
Algoritm 41 Algoritmul lui Prim (varianta schematica) 1: procedure Prim1(n, C, u; L) 2: S ← {u}, L ← ∅ 3: for i ← 1, n do 4: di ← cu,i 5: end for 6: for i ← 1, n − 1 do 7: k ← min {dk |k ∈ V \ S} 8: L ⇐ (tatak , k) 9: S ← S ∪ {k} 10: for each j ∈ V \ S do 11: dj ← min {dj , ck,j } 12: end for 13: end for 14: end procedure
Algoritm 42 Algoritmul lui Kruskal (varianta schematica) 1: procedure Kruskal1(G, C, n; L) 2: ordoneaz˘ a muchiile ˆın ordine cresc˘ atoare dup˘ a cost 3: L←∅ 4: for each u ∈ V do 5: creaz˘ a o mult¸ime compus˘ a din {u} 6: end for 7: count ← 0 8: while count < n − 1 do 9: alege muchia (u, v) 10: if (u ¸si v sunt ˆın mult¸imi diferite) then 11: L ⇐ (u, v) 12: reune¸ste mult¸imile ce cont¸in pe u ¸si v 13: count ← count + 1 14: end if 15: end while 16: end procedure
Fie G(V, E) un graf neorientat unde V = {1, 2, ..., n} este mult¸imea nodurilor ¸si E este mult¸imea muchiilor (E ⊆ V × V ). Pentru reprezentarea grafului se utilizeaz˘a matricea costurilor C: , dac˘a i = j 0 ci,j = ∞ , dac˘a (i, j) ∈ /E d > 0 , dac˘a (i, j) ∈ E
6.3.1
Algoritmul lui Boruvka
Algoritmul lui Boruvka [77] a fost descoperit de c˘atre matematicianul ceh Otakar Boruvka ˆın 1926 [21], ¸si redescoperit apoi de c˘atre alt¸i cercet˘atori. Dintre ace¸stia, Sollin (1961) este cel care a mai dat numele algoritmului, acesta fiind cunoscut ˆın literatura de specialitate ¸si sub numele de algoritmul lui Sollin. Pentru ca acest algoritm s˘a poat˘a fi aplicat, trebuie ca muchiile grafului s˘a aib˘a costuri distincte (vezi algoritmul 43). 119
Table 6.4: Algoritmi pentru determinarea arborelui de acoperire minim Anul 1975 1976 1984 1986 1997 2000 2002
Complexitate E log log V E log log V E log∗ V, E + V log V E log log ∗ V Eα(V ) log α(V ) Eα(V ) optimal
Autori Yao Cheriton-Tarjan Friedman-Tarjan Gabow-Galil-Spencer-Tarjan Chazelle Chazelle [26] Pettie-Ramachandran [105]
Algoritm 43 Algoritmul lui Boruvka 1: procedure Boruvka2(G, C, n; L) 2: for i ← 1, n do 3: Vi ← {i} 4: end for 5: L ← ∅, M ← {V1 , . . . , Vn } 6: while (|T | < n − 1) do 7: for U ∈ M do 8: fie (u, v) muchia pentru care se obt¸ine valoarea minim˘ a min{c(u′ , v ′ )|(u′ , v ′ ) ∈ E, u′ ∈ 9: 10: 11: 12: 13: 14: 15: 16:
/ V \ U} U, v ′ ∈ determin˘ a componenta U ′ ce cont¸ine pe v L ⇐ (u, v) end for for U ∈ M do reune¸ste mult¸imile ce cont¸in pe u ¸si v, U ¸si U ′ end for end while end procedure
Exemplul 6.4 S˘a consider˘am graful din figura 6.5: G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8} Aplicˆand algoritmul lui Boruvka, la pasul ˆıntˆ ai vor fi selectate muchiile (1, 2), (3, 6), (4, 5), ˆ (4, 7) ¸si (7, 8). In urma operat¸iilor de reuniune a componentelor conexe pe baza muchiilor selectate, vor mai r˘amˆane trei componente conexe ˆın mult¸imea M. La pasul al doilea sunt alese muchiile (2, 5) ¸si (1, 3) ce conduc, ˆın urma operat¸iilor de reuniune, la o singur˘a component˘a conex˘ a.
6.3.2
Algoritmul lui Prim
Algoritmul a fost descoperit mai ˆıntˆai de V. Jarnik (1930) [76], ¸si apoi independent de Prim (1957) [106] ¸si Djikstra (1959) [38]. Se porne¸ste cu o mult¸ime S format˘a dintr-un singur nod (S = {v0 }). La fiecare pas se alege muchia de cost minim ce are numai o extremitate ˆın mult¸imea S. Procesul se ˆıncheie dup˘a n − 1 pa¸si, rezultˆand un graf part¸ial aciclic. Din teorema 5.1 rezult˘a faptul c˘a acest graf part¸ial aciclic cu n − 1 muchii este un arbore (de acoperire). 120
Fig. 6.5: Exemplu de graf ponderat - aplicat¸ie algoritmul lui Boruvka
Conform Propriet˘a¸tii t˘aieturii, toate muchiile alese apart¸in arborelui part¸ial de cost minim de unde rezult˘a c˘a acest arbore de acoperire este un arbore part¸ial minim. Vom utiliza trei vectori de dimensiune n, unde n reprezint˘a num˘arul de vˆarfuri al grafului (vezi algoritmul 44): • vizitat - vector caracteristic
( 1 , dac˘a nodul k ∈ S vizitatk = 0 , dac˘a nodul k ∈ V \ S
• d - pentru un nod k ∈ / S, dk va cont¸ine distant¸a minim˘a de la k la un nod j ∈ S. La ˆınceput, dj = cv0 ,j . Pentru un nod k ales la un moment dat, dj (j ∈ S) se modific˘a numai dac˘a ck,j < dj astfel dj = ck,j . • tata - cont¸ine pentru fiecare nod k ∈ / S nodul j ∈ S astfel ˆıncˆat ck,j = min{ck,i|i ∈ S}. La ˆınceput, ( 0 , dac˘a nodul k = v0 tatak = v0 , dac˘a nodul k 6= v0 ˆIn momentul ˆın care se modific˘a dj , se va modifica ¸si valoarea lui tataj = k.
Exemplul 6.5 Fie graful din figura 6.6: G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8} Vom lua nodul init¸ial v0 1 2 3 d 14 6 tata 0 1 1 vizitat 1 0 0 Dup˘a primul pas 1 2 d 14 tata 0 1 vizitat 1 0
= 1. La ˆınceput, 4 5 6 7 ∞ 5 ∞ ∞ 1 1 1 1 0 0 0 0
dup˘ a etapa de init¸ializare avem: 8 ∞ 1 0
al ciclului, select˘ am muchia (1, 5). 3 4 5 6 7 8 6 21 5 16 ∞ ∞ 1 5 1 5 1 1 0 0 1 0 0 0 121
Algoritm 44 Algoritmul Prim (varianta detaliata) 1: function DistantaMinima(n, vizitat, d) 2: min ← ∞ 3: for j ← 1, n do 4: if (vizitatj 6= 1) ∧ (dj < min) then 5: min ← dj 6: j0 ← j 7: end if 8: end for 9: if (min = ∞) then 10: return −1 11: else 12: return j0 13: end if 14: end function 15: procedure Prim2(n, C, v0 ; d, tata) 16: vizitatv0 ← 1 17: tatav0 ← 0 18: for i ← 1, n do 19: if (i 6= v0 ) then 20: vizitati ← 0 21: di ← cv0 ,i 22: tatai ← v0 23: end if 24: end for 25: for i ← 1, n − 1 do 26: k ← DistantaM inima(n, vizitat, d) 27: if (k < 0) then 28: Output ’Graful nu este conex!’ 29: return 30: end if 31: vizitatk ← 1 ⊲ (k, tatak ) este o muchie ce apart¸ine arborelui part¸ial minim 32: for j ← 1, n do 33: if (vizitatj = 6 1) ∧ (dj > ck,j ) then 34: tataj ← k 35: dj ← ck,j 36: end if 37: end for 38: end for 39: end procedure
La pasul al doilea se 1 2 3 d 14 6 tata 0 1 1 vizitat 1 0 1
alege nodul 3 ¸si muchia (1, 3): 4 5 6 7 8 21 5 12 ∞ 12 5 1 3 1 3 0 1 0 0 0
La pasul al treilea avem dou˘a noduri ale c˘ aror distant¸e la noduri din mult¸imea S sunt egale: 6 ¸si 8. Alegem primul nod - 6 ¸si muchia (3, 6): 122
Fig. 6.6: Exemplu de graf ponderat - aplicat¸ie algoritmul lui Prim
1 2 3 d 14 6 tata 0 1 1 vizitat 1 0 1
4 21 5 0
5 6 5 12 1 3 1 1
7 14 6 0
8 6 6 0
Al patrulea nod ales 1 2 3 d 14 6 tata 0 1 1 vizitat 1 0 1
este 4 10 8 0
8: 5 6 5 12 1 3 1 1
7 10 8 0
8 6 6 1
La pasul cinci, nodul aflat la 1 2 3 4 5 d 14 6 10 5 tata 0 1 1 8 1 vizitat 1 0 1 0 1
distant¸˘ a 6 7 12 10 3 8 1 1
minim˘ a este 7: 8 6 6 1
La pasul ¸sase este ales nodul 1 2 3 4 5 d 12 6 10 5 tata 0 4 1 8 1 vizitat 1 0 1 1 1
4: 6 12 3 1
8 6 6 1
7 10 8 1
La final, ultimul nod ales este 2 ˆımpreun˘ a cu muchia (4, 2). Trebuie s˘a remarc˘am faptul c˘a algoritmul lui Prim (vezi algoritmul 44) este aproape identic cu algoritmul lui Dijkstra (vezi algoritmul 59). Dup˘a cum am subliniat, implementarea optim˘a se realizeaz˘a folosind ni¸ste structuri de date avansate - heap–uri Fibonacci sau pairing heaps (vezi algoritmul 45).
6.3.3
Structuri de date pentru mult¸imi disjuncte
O partit¸ie a unei mult¸imi A este o secvent¸˘a finit˘a de mult¸imi (submult¸imi) A1 , . . . , Am disjuncte Sm dou˘a cˆate dou˘a, cu proprietatea c˘a reuniunea acestora este chiar mult¸imea A (A = i=1 Ai ¸si Ai ∩ Aj = ∅, ∀i, j = 1, m, i 6= j). 123
Algoritm 45 Algoritmul lui Prim folosind structuri de date avansate 1: procedure Prim3(n, C, u) 2: for fiecare v ∈ V do 3: dv ← ∞ 4: end for 5: Q←∅ ⊲ init¸ializeaz˘ a coada cu prioritate Q cu mult¸imea vid˘ a 6: for fiecare v ∈ V do 7: Q⇐v 8: end for 9: S←∅ ⊲ init¸ializeaz˘ a mult¸imea S cu mult¸imea vid˘ a 10: while Q 6= ∅ do 11: u ← deleteM in(Q) ⊲ extrage nodul de prioritate minim˘ a din Q 12: S ← S ∪ {u} 13: for fiecare muchie e = (u, v) ∈ E do 14: if (v ∈ / S) ∧ (c(e) < dv ) then 15: actualizeaza prioritatea lui v: dv ← c(e) 16: end if 17: end for 18: end while 19: end procedure
Exist˘a mai multe probleme ai c˘aror algoritmi de rezolvare depind de urm˘atoarele operat¸ii efectuate asupra elementelor partit¸iei: verificarea dac˘a dou˘a elemente fac parte din aceea¸si submult¸ime precum ¸si operat¸ia de reuniune a dou˘a submult¸imi. O structur˘a de date pentru mult¸imi disjuncte memoreaz˘a o colect¸ie de mult¸imi disjuncte dinamice. Fiecare mult¸ime este identificat˘a printr–un reprezentant [30]. Operat¸iile de baz˘a ale acestei structuri de date sunt [2]: • init(x, B) - procedura creaz˘a o mult¸ime B format˘a dintr–un singur element x; • f ind(x) - ˆıntoarce reprezentantul mult¸imii c˘areia ˆıi apart¸ine x; • merge(A, B) - reune¸ste mult¸imile distincte A ¸si B (x ∈ A, y ∈ B) ˆıntr–o nou˘a mult¸ime ce cont¸ine elementele celor dou˘a mult¸imi. O astfel de structur˘a de date pentru mult¸imi disjuncte se mai nume¸ste ¸si structur˘a de date union–find (eng. union–find data structure). Reprezentarea folosind vectori Vom folosi un vector C de dimensiune n, unde n reprezint˘a num˘arul de elemente, iar ck = u indic˘a faptul c˘a elementul k apart¸ine mult¸imii u. Init const˘a din init¸ializarea lui cx cu identificatorul mult¸imii, u. F ind ˆıntoarce valoarea lui cx (valoarea identificatorului mult¸imii). ˆIn funct¸ia Merge se caut˘a toate elementele ce fac parte din mult¸imea de identificator cy ¸si se trec ˆın mult¸imea al c˘arui identificator este cx (vezi algoritmul 46). Exemplul 6.6 Pentru o putea opera cu modul de reprezentare ales, cel cu vectori, trebuie ca elementele mult¸imii s˘a ia valori naturale ˆın intervalul [1,. . . ,n]. Dac˘ a acest lucru nu este posibil atunci folosim un vector auxiliar A ce p˘ astreaz˘ a valorile elementelor, ˆın cadrul reprezent˘arii utilizˆandu–se indicele acestora. S˘ a presupunem c˘ a avem mult¸imea de elemente 124
Algoritm 46 Algoritmi pentru operat¸iile init, find, merge (varianta ˆıntˆai) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
procedure Init(x, u) cx ← u end procedure function Find(x) return cx end function function Merge(x, y) setx ← cx sety ← cy for k ← 1, n do if (ck = sety) then ck ← setx end if end for return setx end function
A = {a, b, e, x, y, z, u, v} ¸si partit¸ia reprezentare descris avem: 1 2 3 4 5 6 A ’a’ ’b’ ’e’ ’x’ ’y’ ’z’ C 1 2 3 1 1 2
{a, x, y}, {b, z, v}, {e, u}. Atunci conform modului de 7 ’u’ 2
8 ’v’ 3
a2 =′ b′ are semnificat¸ia urm˘atoare: elementul de pe pozit¸ia 2 are valoarea ′ b′ . c2 = 2 elementul de pe pozit¸ia 2 face parte din mult¸imea de identificator 2. Sau a4 =′ x′ - elementul de pe pozit¸ia 4 are valoarea ′ x′ , ¸si c4 = 1 - elementul de pe pozit¸ia 4 face parte din mult¸imea de identificator 1. Reprezentarea folosind liste ˆınl˘ ant¸uite ˆIn cadrul acestei metode fiecare mult¸ime este reprezentat˘a printr–o list˘a simplu ˆınl˘ant¸uit˘a, elementul reprezentant fiind elementul aflat ˆın capul listei. Un nod al listei va avea un cˆamp ce p˘astreaz˘a informat¸ia cu privire la un element al unei mult¸imi, ¸si un cˆamp ce cont¸ine leg˘atura c˘atre urm˘atorul nod al listei. Vom folosi un vector de adrese (List), ce cont¸ine adresa primului element din fiecare list˘a (Listi ). Procedura Init aloc˘a un nod ¸si init¸ializeaz˘a cˆampurile acestuia. Adresa nodului alocat este p˘astrat˘a ˆın Listk (vezi algoritmul 47). Funct¸ia F ind caut˘a un element printre elementele p˘astrate de fiecare list˘a ˆın parte. Se parcurge vectorul List ¸si se obt¸ine reprezentantul fiec˘arei submult¸imi p˘astrate. Se parcurge lista (c˘autare liniar˘a) ¸si se caut˘a un element cu valoarea x. Dac˘a elementul este g˘asit atunci se ˆıntoarce capul listei ˆın care a fost g˘asit. Dac˘a pˆan˘a la sfˆar¸sit nu a fost identificat elementul x, atunci funct¸ia returneaz˘a valoarea NULL. Funct¸ia Merge reune¸ste dou˘a liste: practic se adaug˘a o list˘a la sfˆar¸situl celeilalte. Pentru a realiza aceast˘a operat¸ie pe lˆang˘a adresa primului element avem nevoie de adresa ultimului element. Acest lucru se poate obt¸ine indirect prin parcurgerea listei de la primul la ultimul element, fie direct dac˘a ˆın momentul cre˘arii p˘astr˘am pentru fiecare list˘a ˆınc˘a o variabil˘a ce cont¸ine adresa ultimului element (last). 125
Algoritm 47 Algoritmi pentru operat¸iile init, find, merge (varianta a II-a) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
procedure Init(x, k) Listk ← new node Listk .data ← x Listk .next ← N U LL end procedure function Find(x) for i ← 1, m do p ← Listi , reprezentant ← p while (p 6= N U LL) ∧ (p.data 6= x) do p ← p.next end while if (p 6= N U LL) then return reprezentant end if end for return N U LL end function function Merge(x, y) capx ← F ind(x) capy ← F ind(y) curent ← capx while (curent.next 6= N U LL) do curent ← curent.next end while curent.next ← capy return capx end function
⊲ aloc˘ a spat¸iu pentru un nod al listei
Reprezentarea folosind o p˘ adure de arbori ˆIn cadrul acestei modalit˘a¸ti de reprezentare fiecare mult¸ime este p˘astrat˘a sub forma unui arbore. Pentru fiecare nod p˘astr˘am informat¸ia despre p˘arintele s˘au: tatak reprezint˘a indicele nodului p˘arinte al nodului k. Se poate observa foarte u¸sor (vezi algoritmul 48) faptul c˘a arborele obt¸inut ˆın urma unor operat¸ii repetate Merge poate deveni degenerat (o list˘a liniar˘a). Pentru a putea s˘a evit˘am o astfel de situat¸ie, reuniunea se poate realiza ¸tinˆand cont de una din urm˘atoarele caracteristici: 1. num˘arul de elemente din fiecare arbore - r˘ad˘acina arborelui ce are mai put¸ine elemente (not˘am arborele cu TB ) va deveni descendentul direct al r˘ad˘acinii arborelui ce posed˘a mai multe elemente (notat cu TA ). Astfel toate nodurile din arborele TA vor r˘amˆane cu aceea¸si adˆancime, pe cˆand nodurile din arborele TB vor avea adˆancimea incrementat˘a cu 1. De asemenea, arborele rezultat ˆın urma reuniunii va avea cel put¸in de dou˘a ori mai multe noduri decˆat arborele TB .
126
Algoritm 48 Algoritmi pentru operat¸iile init, find, merge (varianta a III-a) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
procedure Init(x) tatax ← x end procedure function Find(x) while (x 6= tatax ) do x ← tatax end while return x end function function Merge(x, y) radx ← F ind(x) rady ← F ind(y) tatarady ← radx return radx end function
1: 2: 3: 4: 5: 6: 7: 8: 9:
1: function Merge(x, y) 2: radx ← F ind(x) 3: rady ← F ind(y) 4: size ← |tataradx | + |tatarady | 5: if (|tataradx | > |tatarady |) then 6: tatarady ← radx 7: tataradx ← −size 8: return radx 9: else 10: tataradx ← rady 11: tatarady ← −size 12: return rady 13: end if 14: end function
procedure Init(x) tatax ← −1 end procedure function Find(x) while (tatax > 0) do x ← tatax end while return x end function
Prin aceast˘a euristic˘a simpl˘a se urm˘are¸ste o echilibrare a arborelui rezultat pentru a se evita cazurile degenerate. Complexitatea timp a procedurii F ind este O(log n). 2. ˆın˘alt¸imea fiec˘arui arbore - r˘ad˘acina arborelui ce are ˆın˘alt¸imea mai mic˘a (not˘am arborele cu TB ) va deveni descendentul direct al r˘ad˘acinii arborelui cu ˆın˘alt¸imea mai mare (notat cu TA ). Dac˘a ˆın˘alt¸imile lui TA ¸si TB sunt egale ¸si TA devine subarbore al lui TB atunci ˆın˘alt¸imea lui TB cre¸ste cu o unitate. ˆIn˘alt¸imea fiec˘arui subarbore de r˘ad˘acin˘a u se p˘astreaz˘a ˆıntr–o variabil˘a nou˘a hu . Pentru a economisi cantitatea de memorie utilizat˘a, se poate p˘astra ˆın˘alt¸imea unui arbore ˆın vectorul tata: tatax = −valoarea ˆın˘alt¸imii arborelui, atunci cˆand x reprezint˘a nodul r˘ad˘acin˘a al unui arbore.
127
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
1: function Merge(x, y) 2: radx ← F ind(x) 3: rady ← F ind(y) 4: if (hradx > hrady ) then 5: tatarady ← radx 6: return radx 7: else 8: tataradx ← rady 9: if (hradx = hrady ) then 10: hrady ← hrady + 1 11: end if 12: return rady 13: end if 14: end function
procedure Init(x) tatax ← x hx ← 0 end procedure function Find(x) while (tatax 6= x) do x ← tatax end while return x end function
Cea de–a doua tehnic˘a utilizat˘a pentru a reduce complexitatea timp a operat¸iilor F ind ¸si Merge este reprezentat˘a de comprimarea drumului. Aceasta const˘a ˆın a apropia fiecare nod de r˘ad˘acina arborelui c˘aruia ˆıi apart¸ine. Astfel ˆın timpul operat¸iei F ind(x) se determin˘a mai ˆıntˆai r˘ad˘acina arborelui c˘aruia ˆıi apart¸ine (rad) ¸si apoi se mai parcurge o dat˘a drumul de la nodul x la r˘ad˘acin˘a, astfel tatay ← rad, ∀y ∈ lant¸ul de la x la rad. 1: function Find(x) 2: y←x 3: while (tatay 6= y) do 4: y ← tatay end while 5: 6: rad ← y 7: y←x 8: while (tatay = 6 y) do 9: y ← tatay tatax ← rad 10: 11: x←y 12: end while 13: return rad 14: end function
6.3.4
Algoritmul lui Kruskal
Algoritmul lui Kruskal [88] este o ilustrare foarte bun˘a a metodei generale Greedy (vezi algoritmul 49). La ˆınceput se pleac˘a cu o p˘adure de arbori, fiecare arbore fiind alc˘atuit dintr–un singur nod. La fiecare pas se alege o muchie: dac˘a cele dou˘a extremit˘a¸ti (noduri) fac parte din acela¸si arbore atunci nu se va ad˘auga muchia curent˘a la arborele part¸ial respectiv deoarece ar conduce la un ciclu, ceea ce ar strica proprietatea de graf aciclic. Altfel, dac˘a cele dou˘a noduri fac parte din arbori part¸iali distinct¸i, se adaug˘a muchia curent˘a la mult¸imea de muchii alese anterior, iar cei doi arbori part¸iali devin unul singur. La fiecare pas, num˘arul de arbori part¸iali scade cu 1. Dup˘a n − 1 alegeri p˘adurea init¸ial˘a compus˘a din n arbori s–a transformat ˆıntr–un singur arbore. Pentru a p˘astra muchiile grafului vom utiliza o matrice A cu m coloane ¸si 3 linii: primele dou˘a linii cont¸in extremit˘a¸tile muchiilor, iar linia a 3-a costul muchiei respective. Pentru a se aplica o strategie Greedy, datele de intrare – muchiile – se vor ordona cresc˘ator ˆın funct¸ie de costul asociat. Deoarece graful part¸ial respectiv este un arbore cu n noduri el va avea n − 1 muchii.
128
O verificare eficient˘a cu privire la apartenent¸a la acela¸si arbore a celor dou˘a extremit˘a¸ti ale unei muchii se poate face dac˘a vom utiliza o structur˘a de date pentru mult¸imi disjuncte. Ca implementare, am ales reprezentarea cu p˘adure de arbori. Vom folosi un vector tata care, pentru fiecare nod, va p˘astra tat˘ al acestuia ˆın arborele part¸ial c˘aruia ˆıi apart¸ine. Cˆand sunt preluate ca date de intrare, muchiile vor fi prezentate ˆın ordinea cresc˘atoare a extremit˘a¸tilor lor, de forma i j cost, cu i < j. Algoritm 49 Algoritmul lui Kruskal 1: procedure Kruskal2(n, m, A; L, cost) 2: for i ← 1, n do 3: call Init(i) 4: end for 5: cost ← 0 6: j ← 1, L ← ∅ 7: for i ← 1, n − 1 do 8: q←0 9: while (q = 0) do 10: r1 ← F ind(A1,j ) 11: r2 ← F ind(A2,j ) 12: if (r1 6= r2 ) then 13: cost ← cost + A3,j 14: call M erge(r1 , r2 ) 15: L ← L ∪ {(A1,j , A2,j )} 16: q←1 17: end if 18: j ←j+1 19: end while 20: end for 21: end procedure
Exemplul 6.7 S˘a consider˘am graful din figura 6.6. Muchiile grafului ¸si costurile lor sunt (am ales s˘a reprezent˘am matricea A sub forma unei matrice cu m coloane ¸si 3 linii din motive de spat¸iu): A 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 linia 1 1 1 1 2 2 2 3 3 4 4 4 5 6 6 7 linia 2 2 3 5 4 5 6 6 8 5 7 8 6 7 8 8 linia 3 14 6 5 12 16 20 12 12 21 24 10 16 14 6 10 Dup˘a a¸sezarea ˆın ordine cresc˘atoare a muchiilor dup˘ a cost avem: A 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 linia 1 1 1 6 4 7 2 3 3 1 6 2 5 2 4 4 linia 2 5 3 8 8 8 4 6 8 2 7 5 6 6 5 7 linia 3 5 6 6 10 10 12 12 12 14 14 16 16 20 21 24 ˆIn figura 6.7, sunt ilustrat¸i pa¸sii algoritmului lui Kruskal aplicat pe graful considerat. La ˆınceput se init¸ializeaz˘a p˘adurea de arbori, fiecare arbore fiind alc˘ atuit dintr–un singur nod, acesta fiind ¸si r˘ad˘acina arborelui. Apoi la fiecare pas se alege o muchie ¸si se verific˘a dac˘a extremit˘a¸tile acesteia fac parte din arbori diferit¸i. Dac˘ a r˘ aspunsul este afirmativ atunci muchia respectiv˘a este selectat˘a, altfel se trece la muchia urm˘ atoare. 129
Fig. 6.7: Algoritmului lui Kruskal exemplificat pe graful din figura 6.6
La pasul ˆıntˆai evalu˘am muchia (1, 5), ¸si deoarece cele dou˘ a extremit˘ a¸ti fac parte din arbori distinct¸i, vom selecta aceast˘a muchie (L = {(1, 5)}). Reunim arborii din care fac parte cele dou˘a extremit˘a¸ti, (vezi figura 6.7 (1)). La pasul al doilea, se ia ˆın considerare muchia (1, 3) ¸si mult¸imea muchilor selectate devine L = {(1, 5), (1, 3)} (vezi figura 6.7 (2)). La pasul al patrulea, ajungem la muchia (4, 8) ce are costul 10. Nodul 4 face parte din arborele de r˘ad˘acin˘a 4 iar nodul 8 face parte din arborele de r˘ ad˘ acin˘ a 6. Deoarece extremit˘a¸tile muchiei sunt amplasate ˆın arbori distinct¸i select˘ am muchia curent˘ a pentru arborele part¸ial ˆ de cost minim, L = {(1, 5), (1, 3), (6, 8), (4, 8)}. In urma reuniunii arborilor corespunz˘atori celor dou˘a noduri se obt¸ine configurat¸ia din figura 6.7 (4). 130
Subliniem faptul c˘a arborii reprezentat¸i ˆın figura 6.7 sunt diferit¸i de arborii ce conduc la obt¸inerea solut¸iei problemei: arborii din figur˘ a constituie suportul necesar pentru reprezentarea structurii de date pentru mult¸imi disjuncte, ce este utilizat˘ a pentru efectuarea eficient˘a a operat¸iilor F ind ¸si Merge. Acest lucru justific˘ a faptul c˘ a de¸si la pasul al patrulea select˘am muchia (4, 8) pentru arborele part¸ial de cost minim, ea nu se reg˘ ase¸ste ˆın configurat¸ia (4) (nodul 4 este unit direct cu nodul 6, vezi figura 6.7). Singura leg˘ atur˘ a dintre un arbore suport pentru reprezentarea unei mult¸imi ¸si un arbore folosit pentru obt¸inerea arborelui part¸ial de cost minim, este mult¸imea de noduri ce este comun˘ a. La pa¸sii urm˘atori, cinci ¸si ¸sase, sunt alese muchiile (7, 8) ¸si respectiv (2, 4): L = {(1, 5), (1, 3), (6, 8), (4, 8), (7, 8), (2, 4)}. La pasul al ¸saptelea se evalueaz˘a muchia (3, 6). Nodurile 3 ¸si 6 fac parte din arbori diferit¸i, astfel ˆıncˆat muchia curent˘a poate fi selectat˘ a pentru solut¸ia problemei, L = {(1, 5), (1, 3), (6, 8), (4, 8), (7, 8), (2, 4), (3, 6)}.
6.4
Exercit¸ii
1. S˘a se realizeze o subrutin˘a ce ˆıntoarce num˘arul de noduri dintr–un arbore oarecare. 2. Se d˘a o secvent¸˘a format˘a din n numere naturale d1 , d2 , . . . , dn . S˘a se realizeze un algoritm prin care s˘a se verifice dac˘a exist˘a un arbore cu n noduri ale c˘aror grade sunt d1 , d2 , . . . , dn . Dac˘a exist˘a un astfel de arbore, acesta va fi reprezentat prin liste ale nodurilor. O list˘a a unui nod cont¸ine num˘arul nodului urmat de fii s˘ai. Intrare 1123113
Ie¸sire DA 1 2 3 4 2 5 6 3 7 4 5 6 7
(Timi¸soara-preg˘atire, 1996)
3. Fie un graf G cu n vˆarfuri ¸si m muchii de cost pozitiv. Alegˆand un nod, numit nod central, s˘a se determine un subarbore al lui G astfel ˆıncˆat drumurile de la nodul central la toate celelalte noduri s˘a aib˘a lungimea minim˘a. 4. Fiind date n puncte ˆın spat¸iul R3 determinate prin coordonatele (x, y, z), s˘a se elaboreze un algoritm ce determin˘a sfera de raz˘a minim˘a cu centrul ˆıntr–unul din punctele date ¸si care cont¸ine ˆın interiorul ei toate cele n puncte. 5. Se consider˘a procesul de proiectare a unei pl˘aci electronice cu N componente (1 ≤ N ≤ 100). Pentru fiecare component˘a electronic˘a C se cunoa¸ste num˘arul de interconexiuni. Se consider˘a graful determinat de mult¸imea pinilor ¸si mult¸imea interconect˘arilor posibile ale tuturor componentelor, precum ¸si lungimile lor. Dintre toate modalit˘a¸tile de interconectare posibile se cere cea corespunz˘atoare arborelui de acoperire minim (interconectarea pentru care suma tuturor circuitelor imprimate are lungimea minim˘a).
131
Datele de intrare sunt alc˘atuite din descrierile mai multor pl˘aci electronice. Descrierea fiec˘arei pl˘aci cont¸ine num˘arul N de componente ¸si num˘arul M de interconexiuni. O conexiune se caracterizeaz˘a prin trei valori: dou˘a vˆarfuri, u ¸si v, precum ¸si lungimea acesteia l. Datele de ie¸sire constau dintr-un r˘aspuns pentru fiecare mult¸ime de date de test, compus din num˘arul testului (se ˆıncepe numerotarea cu 1), precum ¸si costul interconect˘arii de lungime minim˘a. Exemplul 6.8 Pentru datele de intrare 5 7 1 2 40 0 0
2 3 35
1 5 27
1 4 37
4 5 30
2 4 58
3 4 60
vom obt¸ine rezultatul Cazul 1: [1 5] [4 5] [2 3] [1 2] Interconectarea de cost minim are valoarea 132.
ˆIn figura 6.8 este reprezentat graful corespunz˘ ator datelor de intrare.
Fig. 6.8: Descrierea conexiunilor posibile dintre componentele unei pl˘aci electronice
6. Realizat¸i o subrutin˘a nerecursiv˘a care s˘a determine cheia minim˘a dintr–un arbore oarecare. 7. Determinat¸i ˆın˘alt¸imea unui arbore oarecare printr–o funct¸ie nerecursiv˘a. 8. Pentru asigurarea securit˘a¸tii activit˘a¸tilor dintr–un combinat chimic s–a apelat la o companie de pompieri. Desf˘a¸surarea activit˘a¸tilor companiei presupune stabilirea locurilor de instalare a comandamentului precum ¸si a posturilor de supraveghere. Pentru aceasta sunt disponibile ˆın cadrul combinatului n puncte de control. Pentru fiecare pereche de puncte de control se cunoa¸ste dac˘a exist˘a o leg˘atur˘a direct˘a ˆıntre ele ¸si, ˆın caz afirmativ, distant¸a dintre ele. Se cunoa¸ste, de asemenea, faptul c˘a ˆıntre oricare dou˘a puncte de control exist˘a un drum direct sau indirect. Odat˘a stabilite amplasamentele comandamentului ¸si ale punctelor de supraveghere ˆın cˆate unul dintre cele n puncte de control este posibil s˘a se ajung˘a de la comandament 132
la fiecare punct de supraveghere parcurgˆand un anumit drum. Evident este de dorit ca lungimea acestui drum s˘a fie minim˘a ¸si valoarea maxim˘a dintre lungimile drumurilor minime pentru fiecare punct de supraveghere ˆın parte s˘a fie cˆat mai mic˘a posibil˘a. Se cere s˘a se determine punctul de control ˆın care trebuie instalat comandamentul cˆat ¸si drumurile ce vor fi parcurse de la comandament la fiecare punct de supraveghere, astfel ˆıncˆat aceste drumuri s˘a aib˘a valoarea lungimii minim˘a ¸si cel mai lung dintre ele s˘a fie cˆat mai scurt posibil.
133
Capitolul 7 Grafuri orientate 7.1
Not¸iuni de baz˘ a
Fie o mult¸ime finit˘a V = {x1 , x2 , . . . , xn }. Fie E ⊆ V × V (unde V × V este produsul cartezian al mult¸imii V cu ea ˆıns˘a¸si). ˆIn cazul unui graf orientat, not¸iunea de muchie este ˆınlocuit˘a cu not¸iunea de arc (o pereche de noduri (x, y) devine ordonat˘a, adic˘a (x, y) 6= (y, x)). Pentru un arc (x, y) ∈ E, vˆarful x reprezint˘a extremitatea init¸ial˘a a arcului, iar vˆarful y reprezint˘a extremitatea final˘a. Vom spune c˘a vˆarfurile x ¸si y sunt adiacente. Definit¸ia 7.1 Un graf orientat este o pereche ordonat˘ a G = (V, E), unde V este o mult¸ime de vˆarfuri sau noduri, iar E este o mult¸ime de arce. Exemplul 7.1 Fie graful orientat G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8}, E = {(1, 2), (1, 3), (1, 4), (2, 1), (3, 5), (4, 6), (5, 2), (5, 7), (6, 4), (6, 8), (7, 5), (7, 6)}. Acest graf se poate reprezenta ca ˆın figura 7.1. Definit¸ia 7.2 Un graf part¸ial al unui graf orientat G = (V, E) este un graf orientat G1 = (V, E1 ) unde E1 ⊂ E. Exemplul 7.2 Pentru graful G din exemplul anterior (G = (V, E)), consider˘am graful part¸ial G1 = (V, E1 ), cu E1 = {(1, 3), (2, 1), (3, 5), (4, 6), (5, 2), (5, 7), (6, 4), (6, 8)}, ¸si V = {1, 2, 3, 4, 5, 6, 7, 8}. Definit¸ia 7.3 Un subgraf al unui graf orientat G = (V, E) este un graf orientat H = (V1 , E1 ) unde V1 ⊂ V iar arcele din E1 sunt toate arcele din E ce au ambele extremit˘a¸ti ˆın mult¸imea V1 (E1 = E|V1 ×V1 ). Exemplul 7.3 Fie subgrafurile H1 ¸si H2 ale grafului G din exemplul anterior: H1 = (V1 , E1 ), unde V1 = {1, 2, 3, 5}, iar E1 = {(1, 2), (1, 3), (2, 1), (3, 5), (5, 2)}, ¸si H2 = (V2 , E2 ), unde V = {4, 6, 7, 8} ¸si E2 = {(4, 6), (6, 4), (6, 8), (7, 6)}. Definit¸ia 7.4 Gradul exterior al unui vˆ arf d+ (x) este egal cu num˘ arul arcelor ce au ca extremitate init¸ial˘a pe x. Gradul interior al unui vˆ arf d− (x) este egal cu num˘ arul arcelor ce + − au ca extremitate final˘a pe x (d (x) = |{(x, u)|(x, u) ∈ E, u ∈ V }|, d (x) = |{(u, x)|(u, x) ∈ E, u ∈ V }|).
134
Fig. 7.1: Un exemplu de graf orientat
Definit¸ia 7.5 Se nume¸ste lant¸ o secvent¸˘ a de arce L = {l1 , . . . , lp } cu proprietatea c˘a oricare dou˘a arce consecutive ale secvent¸ei au o extremitate comun˘ a. (L = [v0 , v1 , . . . , vp ] unde (vi , vi+1 ) ∈ E sau (vi+1 , vi ) ∈ E, ∀i = 0, p − 1). Definit¸ia 7.6 Un drum D = [v0 , v1 , . . . , vp ] este o succesiune de vˆ arfuri cu proprietatea c˘a oricare dou˘a vˆarfuri vecine sunt adiacente. ((vi , vi+1 ) ∈ E, i = 0, p − 1). Dac˘a vˆarfurile v0 , v1 , . . . , vp sunt distincte dou˘a cˆate dou˘a, drumul se nume¸ste elementar. Exemplul 7.4 L1 = {(6, 8), (7, 6), (7, 5), (3, 5), (1, 3)} ¸si L2 = {(6, 8), (6, 4), (1, 4)} sunt lant¸uri de la vˆarful 8 la vˆarful 1, iar D1 = {(1, 3), (3, 5), (5, 7), (7, 6), (6, 8)} ¸si D2 = {(1, 4), (4, 6), (6, 8)} sunt drumuri elementare de la vˆ arful 1 la vˆ arful 8. Definit¸ia 7.7 Un drum D pentru care v0 = vp se nume¸ste circuit. Definit¸ia 7.8 Se nume¸ste circuit hamiltonian un circuit elementar ce trece prin toate vˆarfurile grafului. Un graf ce admite un circuit hamiltonian se nume¸ste graf hamiltonian orientat. Definit¸ia 7.9 Un drum D ce cont¸ine fiecare arc exact o singur˘ a dat˘ a se nume¸ste drum eulerian. Dac˘a v0 = vp ¸si drumul este eulerian atunci circuitul se nume¸ste circuit eulerian. Un graf ce cont¸ine un circuit eulerian se nume¸ste graf eulerian orientat. Definit¸ia 7.10 Un graf se nume¸ste conex dac˘ a pentru orice pereche de vˆ arfuri x ¸si y exist˘a un lant¸ Lxy de la x la y. Definit¸ia 7.11 Un graf orientat este complet dac˘ a oricare dou˘ a vˆ arfuri sunt adiacente. Metodele utilizate pentru reprezentarea unui graf neorientat se pot adapta ˆın mod natural pentru reprezentarea unui graf orientat, ˆınlocuind not¸iunea de muchie cu cea de arc.
135
Fig. 7.2: Arbore de acoperire ˆın l˘a¸time pentru graful orientat din figura 7.1
7.2
Parcurgerea grafurilor
ˆIn urma vizit˘arii unui graf neorientat de obicei nu rezult˘a numai un arbore de acoperire ci o p˘adure de arbori de acoperire. ˆIn urma unei parcurgeri ˆın l˘a¸time a unui graf orientat, se ˆıntˆalnesc urm˘atoarele tipuri de arce: • arc al arborelui de acoperire - arcul (u, v) este un arc al arborelui de acoperire ˆın l˘a¸time. • arc de ˆıntoarcere - arcul (u, v) se nume¸ste arc de ˆıntoarcere dac˘a are sensul contrar unui drum de la v la u ˆın arborele de acoperire (v u) (se spune c˘a u este un descendent al lui v ¸si v este un str˘amo¸s al lui u). • arc de traversare - arcul (u, v) este un arc de traversare dac˘a v nu este nici descendent direct, nici str˘amo¸s al lui u. ˆIn figura 7.2 este prezentat arborele de acoperire ˆın l˘a¸time corespunz˘ator grafului din figura 7.1 avˆand r˘ad˘acina 1: arcele (2, 1), (7, 5), (6, 4) sunt arce de ˆıntoarcere, (5, 2), (7, 6) sunt arce de traversare, iar celelalte sunt arce ale arborelui de acoperire (de exemplu (1, 3), (6, 8)). Vom prezenta schit¸a unui algoritm mai general pentru parcurgerea unui graf (vezi algoritmul 50). Pentru aceasta se utilizeaz˘a dou˘a mult¸imi de noduri, V izitat ¸si Neexplorat, unde V izitat reprezint˘a mult¸imea nodurilor vizitate iar Neexplorat (Neexplorat ⊂ V izitat) reprezint˘a mult¸imea nodurilor vizitate dar neexplorate (noduri ce prezint˘a vecini ˆınc˘a nevizitat¸i). ˆIn urma unei parcurgeri ˆın adˆancime a unui graf orientat, fiecare arc din mult¸imea arcelor poate fi ˆıncadrat ˆıntr–unul dintre tipurile urm˘atoare: • arc al arborelui de acoperire - arcul (u, v) este un arc al arborelui de acoperire dac˘a call df s(u) apeleaz˘a df s(v) (df s(u) df s(v)). • arc de ˆınaintare - arcul (u, v) este un arc de ˆınaintare dac˘a este paralel cu un drum de la u la v din arborele de acoperire (u v) (nu face parte din arborele de acoperire). 136
Algoritm 50 Algoritm de vizitare a unui graf (model general) 1: procedure ( ParcurgereGraf(u, G)
u - vˆ arful de unde se porne¸ste vizitarea G - graful V izitat ← {u} N eexplorat ← V izitat while (N eexplorat 6= ∅) do extrage un nod x din N eexplorat determin˘ a y, urm˘ atorul vecin al lui x ce nu a fost vizitat if (y = N U LL) then elimin˘ a x din N eexplorat else if (y ∈ / V izitat) then V izitat ← V izitat ∪ {y} N eexplorat ← N eexplorat ∪ {y} end if end if end while end procedure
Input: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
• arc de ˆıntoarcere - arcul (u, v) se nume¸ste arc de ˆıntoarcere dac˘a are sensul contrar unui drum de la v la u din arborele de acoperire (v u). • arc de traversare - arcul (u, v) este un arc de traversare dac˘a df s(v) a fost apelat ¸si s–a terminat ˆınainte de apelul lui df s(u).
Fig. 7.3: Arbore de acoperire ˆın adˆancime pentru graful orientat din figura 7.1
Pentru fiecare nod v al unui graf vom introduce dou˘a numere, prenumv ¸si postnumv , numere ce depind de ordinea ˆın care sunt ˆıntˆalnite nodurile acestuia ˆın timpul vizit˘arii ˆın adˆancime: prenumv marcheaz˘a momentul cˆand este ˆıntˆalnit nodul v pentru prima oar˘a 137
iar postnumv momentul ˆın care prelucrarea nodului v s–a ˆıncheiat. Variabila counter este init¸ializat˘a cu valoarea 1: 1: procedure Prenum(v) 2: prenumv ← counter 3: counter ← counter + 1 4: end procedure
¸si 1: procedure Postnum(v) 2: postnumv ← counter 3: counter ← counter + 1 4: end procedure
Un arc (u, v) va avea urm˘atoarele propriet˘a¸ti, ˆın funct¸ie de una din cele patru categorii de arce introduse anterior ˆın care se ˆıncadreaz˘a: 1. arc al arborelui de acoperire: prenumu < prenumv ¸si postnumu > postnumv ; 2. arc de ˆınaintare: prenumu < prenumv ¸si postnumu > postnumv ; 3. arc de ˆıntoarcere: prenumu > prenumv ¸si postnumu < postnumv ; 4. arc de traversare: prenumu > prenumv ¸si postnumu > postnumv . Arborele de acoperire ˆın adˆancime corespunz˘ator grafului din figura 7.1 ilustreaz˘a aceste tipuri de arce: arc de ˆınaintare – (1, 4), arce de ˆıntoarcere – (2, 1), (7, 5), (4, 6) arc de traversare – (5, 2), arce ale arborelui de acoperire – (1, 2), (1, 3), (3, 5), (5, 7), (7, 6), (6, 8), (6, 4). (vezi figura 7.3). Algoritm 51 Algoritm de vizitare ˆın adˆancime pentru un graf orientat 1: procedure DFSNum(k, n, V ecin)
- nodul curent vizitat k Input: n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a a grafului 2: vizitatk ← 1 ⊲ marcarea nodului curent ca fiind vizitat 3: call P renum(k) 4: call V izitare(k) ⊲ vizitarea nodului curent 5: for i ← 1, n do 6: if (vizitati = 0) ∧ (vecink,i = 1) then 7: call DF SN um(i, n, V ecin) ⊲ apelul recursiv al subrutinei DF SN um pentru nodul i 8: end if 9: end for 10: call P ostnum(k) 11: end procedure
Lema 7.5 Fiind dat un graf orientat G ¸si dou˘ a noduri oarecare u, v ∈ G ce apart¸in aceluia¸si arbore de acoperire ˆın adˆancime rezultat ˆın urma parcurgerii cu metoda DF S a grafului G avem: 1. dac˘a (u, v) este un arc al arborelui de acoperire sau un arc de ˆınaintare sau un arc de traversare ⇒ postnumu > postnumv ; 138
2. dac˘a (u, v) este un arc de ˆıntoarcere ⇒ postnumu < postnumv . Lema 7.6 Fiind dat un graf orientat G pentru oricare dou˘ a noduri u, v ∈ G avem: 1. v este un descendent al lui u ˆın p˘adurea de arbori de acoperire rezultat¸i ˆın urma vizit˘arii ˆın adˆancime a grafului G ⇔ intervalul [prenumv , postnumv ] este inclus ˆın intervalul [prenumu , postnumu ]; 2. nu exist˘a nici o leg˘atur˘a ˆıntre u ¸si v ˆın p˘ adurea de arbori de acoperire rezultat¸i ˆın urma vizit˘arii ˆın adˆancime a grafului G ⇔ intervalele [prenumu , postnumu ] ¸si [prenumv , postnumv ] sunt disjuncte; 3. situat¸ii prenumu < prenumv < postnumu < postnumv sau prenumv < prenumu < postnumv < postnumu nu sunt posibile. Lema 7.7 Fiind dat un graf neorientat G, dac˘ a pentru dou˘ a noduri oarecare u, v ∈ G avem relat¸ia prenumu < prenumv < postnumu atunci: • prenumu < prenumv < postnumv < postnumu .
• exist˘a un drum de la u la v ˆın G.
Exemplul 7.8 ˆIn urma apel˘arii procedurii DF SNum(1, 8, V ecin) (vezi algoritmul 51), secvent¸a de apeluri recursive ale procedurii DF SNum este ilustrat˘ a prin arborele de acoperire ˆın adˆancime din figura 7.3. Numerotarea nodurilor ˆın preordine ¸si postordine rezultat˘ a ˆın urma vizit˘ arii este urm˘atoarea: 1 2 prenum 1 2 postnum 16 3
3 4 4 8 15 9
5 6 7 8 5 6 7 10 14 13 12 11
Pentru arcul (1, 4) avem prenum1 = 1, postnum1 = 16, prenum4 = 8 ¸si postnum4 = 9. Deoarece relat¸ia prenumu < prenumv < postnumu este adev˘ arat˘ a conform lemei 7.7 ar trebui s˘a avem prenumu < prenumv < postnumv < postnumu ¸si s˘ a existe un drum de la u la v, lucruri care sunt adev˘arate (prenum1 < prenum4 < postnum4 < postnum1 ¸si exist˘a un drum de la 1 la 4 ˆın arborele de acoperire ˆın adˆ ancime).
7.3
Sortarea topologic˘ a
Fig. 7.4: Un graf orientat aciclic
139
Definit¸ia 7.12 Un graf orientat ¸si care nu posed˘ a circuite se nume¸ste graf orientat aciclic (directed acyclic graph - DAG). Lema 7.9 ˆIntr–un graf orientat aciclic, dac˘ a prenumu < prenumv ¸si exist˘ a un drum de la u la v ˆın G, atunci prenumu < prenumv < postnumv < postnumu . ˆIn practic˘a, exist˘a mai multe situat¸ii ˆın care o mult¸ime de activit˘a¸ti sau sarcini, trebuie organizate ˆıntr–o anumit˘a ordine, ˆıntre acestea existˆand, de obicei, o mult¸ime de restrict¸ii sau dependent¸e. ˆIn activitatea de construire a unei cl˘adiri, anumite activit˘a¸ti nu pot fi ˆıncepute decˆat dup˘a finalizarea altor activit˘a¸ti: spre exemplu, nu se poate ˆıncepe ridicarea peret¸ilor decˆat dup˘a turnarea fundat¸iei ¸si finalizarea structurii de rezistent¸˘a, nu se poate realiza instalat¸ia electric˘a dac˘a nu au fost ridicate zidurile, ¸s.a.m.d. Sau dac˘a un student dore¸ste s˘a ˆı¸si alc˘atuiasc˘a un plan de studiu individual ce va cont¸ine pe lˆang˘a cursurile obligatorii, ¸si o serie de cursuri opt¸ionale, va trebui s˘a ¸tin˘a cont de anul de studiu ˆın care se pred˘a fiecare materie, precum ¸si de cerint¸ele obligatorii ale acestora: un curs nu poate fi inclus ˆın planul de studiu individual al unui student decˆat dac˘a acesta a parcurs ¸si a obt¸inut creditele la toate materiile anterioare cerute explicit ˆın programa cursului. Algoritm 52 Algoritm de sortare topologic˘a a unui graf orientat (prima variant˘a) 1: procedure ( SortTop1(n, V ecin)
n - num˘ arul de noduri din graf V ecin - vector ce cont¸ine listele cu vecini ai fiec˘ arui nod for k ← 1, n do dminusk ← 0 end for for ∀(u, v) ∈ E do dminusv = dminusv + 1 end for Q←∅ ⊲ se init¸ializeaz˘ a coada for k ← 1, n do if (dminusk = 0) then Q⇐k ⊲ se insereaz˘ a ˆıntr–o coad˘ a nodurile cu gradul interior 0 end if end for while (Q 6= ∅) do Q⇒k ⊲ se extrage din coad˘ a un nod L⇐k ⊲ se insereaz˘ a nodul ˆıntr-o list˘ a w ← V ecink ⊲ v ia valoarea capului listei de vecini a nodului k while (w 6= N U LL) do dminusw.nodeIndex ← dminusw.nodeIndex − 1 if (dminusw.nodeIndex = 0) then Q ⇐ w.nodeIndex ⊲ se insereaz˘ a ˆın coad˘ a nodul w.nodeIndex end if w ← w.next ⊲ se trece la urm˘ atorul vecin end while end while end procedure
Input: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
Dependent¸a dintre dou˘a activit˘a¸ti A ¸si B o putem modela prin introducerea a dou˘a noduri ˆın graf xi ¸si xj , asociate celor dou˘a activit˘a¸ti. Dac˘a activitatea A trebuie realizat˘a ˆınaintea activit˘a¸tii B, atunci se adaug˘a arcul (xi , xj ). 140
Definit¸ia 7.13 Se nume¸ste sortare topologic˘ a pentru un graf orientat G = (V, E) o ordonare {x1 , x2 , . . . , xn } a nodurilor grafului astfel ˆıncˆ at pentru orice arc (xi , xj ) s˘a avem i < j. Prin urmare o sortare topologic˘a presupune aranjarea liniar˘a a vˆarfurilor unui graf astfel ˆıncˆat toate arcele sale s˘a fie orientate de la stˆanga la dreapta. Lema 7.10 Dac˘a un graf orientat G admite o sortare topologic˘ a atunci G este aciclic. Lema 7.11 Dac˘a un graf orientat G este aciclic atunci el admite o sortare topologic˘a. Observat¸ia 7.12 ˆIntr–un graf orientat aciclic exist˘ a cel put¸in un nod al c˘ arui grad interior este 0 (graful nu posed˘a arce care s˘a aib˘ a nodul v drept extremitate final˘ a). Pornind de la aceast˘a observat¸ie se schit¸eaz˘a urm˘atorul algoritm [78]: ˆıntr–un graf G se determin˘a un nod v astfel ˆıncˆat gradul s˘au interior s˘a fie zero (d− (v) = 0); se adaug˘a acest nod la o list˘a ce va cont¸ine ordonarea topologic˘a, se ¸sterge nodul din graf ˆımpreun˘a cu toate arcele ce ˆıl au ca extremitate init¸ial˘a, ¸si se reia algoritmul pentru graful G′ = (V ′ , E ′ ), unde V ′ = V \ {v}, E ′ = E|V ′ ×V ′ . Algoritmul 52 se termin˘a ˆın cel mult n pa¸si (|V | = n). Dac˘a se termin˘a mai devreme, atunci graful G nu este aciclic (la un moment dat nu mai exist˘a nici un nod v astfel ˆıncˆat d− (v) = 0).
Fig. 7.5: Sortare topologic˘a cu algoritmul 52 pentru graful orientat din figura 7.4
Exemplul 7.13 Fie graful orientat din figura 7.5. Vˆ arful 3 are d− (v) = 0. Se adaug˘a acest nod la lista final˘a ce va cont¸ine nodurile ordonate, se ¸sterge nodul ˆımpreun˘ a cu arcele care ˆıl ˆ au drept extremitate init¸ial˘a. In graful rezultat, vˆ arful 1 are proprietatea c˘ a d− (v) = 0. Se adaug˘a la lista de rezultate, ¸si se elimin˘ a din graf ˆımpreun˘ a cu arcele ce pleac˘ a din el. Se continu˘a procedeul pˆan˘a cˆand graful devine vid (ˆıntregul proces poate fi urm˘ arit ˆın figura 7.5). 141
Algoritm 53 Algoritm de sortare topologic˘a a unui graf orientat (a doua variant˘a) 1: procedure ( SortTop2(n, V ecin)
n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a a grafului for k ← 1, n do prenumk ← 0, postnumk ← 0 vizitatk ← 0 end for for k ← 1, n do if (vizitatk = 0) then call DF SN um(k, n, V ecin) end if end for se ordoneaz˘ a descresc˘ ator nodurile dup˘ a postnumk end procedure
Input: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
Lema 7.14 Un graf orientat G este aciclic dac˘ a ¸si numai dac˘ a ˆın urma unei vizit˘ari ˆın adˆancime a acestuia nu este ˆıntˆalnit nici un arc de ˆıntoarcere. Ideea algoritmului 53 o reprezint˘a lema 7.14[118]. Exemplul 7.15 S˘a consider˘am ca date de intrare pentru algoritmul 53 graful orientat din figura 7.4. Dup˘a etapa de init¸ializare (liniile 2 - 5) avem: 1 prenum 0 postnum 0 vizitat 0
2 0 0 0
3 0 0 0
4 0 0 0
5 0 0 0
6 0 0 0
7 0 0 0
Se apeleaz˘a mai ˆıntˆai DF SNum(1, 7, V ecin). Secvent¸a rezultat˘ a de apeluri recursive este urm˘atoarea: DF SNum(1, 7, V ecin) → DF SNum(2, 7, V ecin) → DF SNum(4, 7, V ecin) → DF SNum(5, 7, V ecin) → DF SNum(7, 7, V ecin) → DF SNum(6, 7, V ecin). ˆIn urma acestei secvent¸e valorile vectorilor prenum ¸si postnum sunt urm˘ atoarele: 1 2 prenum 1 2 postnum 12 11 vizitat 1 1
3 4 5 0 3 4 0 10 9 0 1 1
6 6 7 1
7 5 8 1
Mai r˘amˆane nevizitat un singur nod, 3, drept pentru care vom mai avea un apel DF SNum(3, 7, V ecin) din procedura principal˘ a SortT op2: 1 2 prenum 1 2 postnum 12 11 vizitat 1 1
3 4 5 13 3 4 14 10 9 1 1 1
6 6 7 1
7 5 8 1
Ordonarea descresc˘atoare a nodurilor mult¸imii V dup˘ a valorile vectorului postnum conduce la urm˘ atoarea a¸sezare:
142
postnum 14 12 3 1
11 10 9 2 4 5
8 7 7 6
Astfel sortarea topologic˘a a nodurilor grafului obt¸inut˘ a ˆın urma aplic˘ arii algoritmului 53 este: 3, 1, 2, 4, 5, 7, 6. Observat¸ia 7.16 O optimizare a algoritmului 53 se refer˘ a la ad˘ augarea nodului v ˆıntr-o stiv˘a atunci cˆand se termin˘a vizitarea acestui nod v ¸si se calculeaz˘ a postnumv . Aceast˘a stiv˘a la sfˆar¸sit va cont¸ine nodurile grafului ˆın ordinea descresc˘ atoare a valorilor postnumv . Prin urmare nu mai este nevoie s˘ a ordon˘ am descresc˘ ator valorile vectorului postnum pentru a obt¸ine o sortare topologic˘a a nodurilor grafului G, iar complexitatea algoritmului 53 devine O(|V | + |E|).
7.4
Componente tare conexe
Fig. 7.6: Graf orientat. Componente tare conexe.
Fie G = (V, E) un graf orientat unde V = {1, 2, ..., n} este mult¸imea nodurilor ¸si E este mult¸imea arcelor (E ⊆ V × V ). Definit¸ia 7.14 O component˘ a tare conex˘ a a unui graf orientat G este o mult¸ime maximal˘a de vˆarfuri U ⊆ V , astfel ˆıncˆat, pentru fiecare pereche de vˆ arfuri {u, v} (u, v ∈ U) exist˘a atˆat un drum de la u la v cˆat ¸si un drum de la v la u. Prin urmare se spune c˘ a vˆarfurile u ¸si v sunt accesibile unul din cel˘alalt. Un graf orientat ce are o singur˘a component˘a tare conex˘a astfel ˆıncˆat U = V este un graf tare conex. ˆIn cazul ˆın care un graf orientat nu este tare conex atunci el se poate descompune ˆın mai multe componente tare conexe. O astfel de component˘a este determinat˘a de un subgraf al grafului init¸ial. Definit¸ia 7.15 Graful orientat G se nume¸ste tare conex dac˘ a pentru orice pereche de vˆarfuri u 6= v, exist˘a un drum de la nodul u la nodul v ¸si un drum de la nodul v la nodul u. Se poate ar˘ata c˘a relat¸ia de tare conexitate este o relat¸ie de echivalent¸˘a. O relat¸ie (notat˘a cu ∼) este o relat¸ie de echivalent¸˘a dac˘a prezint˘a propriet˘a¸tile de reflexivitate, simetrie ¸si tranzitivitate: 143
• reflexivitate: a ∼ a; • simetrie: a ∼ b ⇒ b ∼ a; • tranzitivitate: a ∼ b, b ∼ c ⇒ a ∼ c. O clas˘a de echivalent¸˘a este mult¸imea tuturor elementelor care se afl˘a ˆın relat¸ia ∼ ([x]∼ = {y|x ∼ y}). Relat¸ia de echivalent¸˘a determin˘a o partit¸ie ˆın clase de echivalent¸˘a a mult¸imii peste care a fost definit˘a. Prin urmare relat¸ia de tare conexitate determin˘a o partit¸ie a mult¸imii V . Clasele de echivalent¸˘a determinate de relat¸ia de tare conexitate sunt componentele tare conexe. Pentru determinarea componentelor tare conexe exist˘a mai mult¸i algoritmi, dintre care amintim algoritmul lui Tarjan, algoritmul lui Kosaraju ¸si algoritmul lui Gabow.
7.4.1
Algoritmul lui Kosaraju
Algoritmul lui Kosaraju–Sharir a fost prezentat de Aho, Hopcroft ¸si Ullman ˆın lucrarea lor [2], fiind preluat dintr–un manuscris al lui S. Rao Kosaraju (M. Sharir l–a prezentat ˆın lucrarea [110]). Algoritmul folose¸ste graful transpus GT asociat grafului init¸ial G ([11], [23], [30]), fiind compus din urm˘atorii pa¸si: Pas 1. Se realizeaz˘a parcugerea grafului cu algoritmul de vizitare ˆın adˆancime, pornind de la un nod arbitrar. ˆIn timpul vizit˘arii se realizeaz˘a numerotarea ˆın postordine a nodurilor ˆın cadrul vectorului postnum. Pas 2. Se obt¸ine un nou graf GT = (V, E T ) prin inversarea sensului arcelor grafului G (E T = {(u, v)|(v, u) ∈ E, u, v ∈ V }). Pas 3. Se caut˘a nodul nevizitat din graful GT ce are cel mai mare num˘ar atribuit ˆın urma parcurgerii de la Pasul 1. Din acest nod se init¸iaz˘a parcugerea grafului cu algoritmul de vizitare ˆın adˆancime. Pas 4. Dac˘a mai r˘amˆan noduri nevizitate atunci se reia Pasul 3, altfel algoritmul se termin˘a. Fiecare arbore rezultat ˆın urma parcurgerii de la Pasul 3 constituie o component˘ a tare conex˘a a grafului G. Prezent˘am implementarea ˆın limbajul C a algoritmului anterior: #include #include #define MAX 100 #define TRUE 1 #define FALSE 0 /** * Matricea de adiacenta */ char vecin[MAX][MAX]; /** * Vector ce pastreaza starea unui nod: vizitat sau nevizitat. */
144
char vizitat[MAX]; /** * Numarul de varfuri din graf */ int n; int nump; /** * Numarul asociat fiecarui varf la vizitarea in postordine. */ int postnum[MAX]; void dfs(int k) { int i; vizitat[k] = TRUE; for (i = 0; i < n; i++) if ((vizitat[i] == FALSE) && (vecin[k][i] > 0)) dfs(i); nump++; postnum[k] = nump; } void dfs1(int k) { int i; vizitat[k] = TRUE; for (i = 0; i < n; i++) if ((vizitat[i] == FALSE) && (vecin[k][i] > 0)) dfs1(i); printf("%d ", k); } void readInput(void) { int i, j; printf("n = "); scanf("%d", &n); do{ printf("nod1 nod2 : "); scanf("%d %d", &i, &j); if (i >= 0) vecin[i][j] = 1; } while (i >= 0); } void main(void) { int i, j, k, tmp; int maxim; int nod; readInput(); // prima etapa memset(vizitat, 0, sizeof(vizitat));
145
nump = 0; for (i = 0; i < n; i++) if (vizitat[i] == FALSE) dfs(i); // etapa a doua memset(vizitat, 0, sizeof(vizitat)); for (i = 0; i < n; i++) for (j = i; j < n; j++) { tmp = vecin[i][j]; vecin[i][j] = vecin[j][i]; vecin[j][i] = tmp; } k = 0; while (TRUE) { maxim = 0; for (i = 0; i < n; i++) if ((vizitat[i] == FALSE) && (maxim < postnum[i])) { maxim = postnum[i]; nod = i; } if (maxim == 0) break; k++; printf("Componenta %d : ", k); dfs1(nod); printf("\n"); } }
Exemplul 7.17 Dup˘a parcurgerea ˆın adˆ ancime a grafului 7.6, valorile vectorilor vizitat ¸si postnum sunt urm˘atoarele: 1 postnum 8 vizitat 1
2 3 7 5 1 1
4 5 6 6 1 2 1 1 1
7 8 3 4 1 1
Fig. 7.7: Graful transpus GT corespunz˘ator grafului din figura 7.6
Se construie¸ste graful transpus, GT (vezi figura 7.7). Se caut˘ a primul nod u ˆınc˘a nevizitat (vizitatu = 0), c˘aruia ˆıi corespunde cea mai mare valoare postnumu . ˆ In acest mod se 146
identific˘a componenta tare conex˘a compus˘ a numai din nodul 1: {1}. Urm˘atorul nod, ca valoarea a vectorului postnum ordonat descresc˘ ator, este 2. Din nodul 2 se parcurg nodurile 3 ¸si 4, rezultˆand o alt˘ a component˘ a tare conex˘ a: {2, 3, 4}. Urm˘atorul nod nevizitat u ce are valoarea prenumu maxim˘ a este nodul 8. Se identific˘a mai ˆıntˆai componenta tare conex˘a {7, 8} ¸si apoi {5, 6}. Analiza algoritmului Algoritmul lui Kosaraju realizeaz˘a dou˘a parcurgeri ale tuturor elementelor grafului G: prima dat˘a parcurge graful G ¸si a doua oar˘a parcurge graful GT . Prin urmare complexitatea timp a algoritmului este Ω(|V | + |E|) ˆın cazul ˆın care graful este reprezentat prin liste de adiacent¸˘a ¸si este p˘atratic˘a ˆın |V |2 (O(|V |2 )) atunci cˆand graful este reprezentat prin matricea de adiacent¸˘a.
7.4.2
Algoritmul lui Tarjan
Algoritmul lui Tarjan [117] este considerat drept o ˆımbun˘at˘a¸tire a algoritmului lui Kosaraju prin aceea c˘a nu mai este nevoie de parcurgerea grafului G de dou˘a ori. Algoritmul init¸iaz˘a o parcurgere ˆın adˆancime a nodurilor grafului G pornind de la un nod oarecare. O component˘a tare conex˘a a grafului G, dac˘a exist˘a, constituie un subarbore al arborelui de acoperire ˆın adˆancime, iar r˘ad˘acina acestui subarbore este reprezentantul clasei de echivalent¸˘a. Prin urmare componentele tare conexe se obt¸in prin descompunerea arborelui/arborilor de acoperire ˆın adˆancime dac˘a elimin˘am anumite arce ale acestora. Un nod este capul unei componente tare conexe (sau r˘ad˘acina), dac˘a acesta constituie r˘ad˘acina subarborelui corespunz˘ator componentei. Arcul ce are nodul–cap drept extremitate final˘a este cel ce trebuie eliminat. Dup˘a ce determin˘am toate nodurile–cap, subarborii arborelui/arborilor de acoperire ˆın adˆancime ce ˆıi au drept r˘ad˘acini sunt componentele tare conexe. Algoritmul lui Tarjan are drept scop determinarea nodurilor–cap. Pentru a p˘astra o ordine a vizit˘arii acestora pe parcursul algoritmului, nodurile vor fi ad˘augate pe o stiv˘a. Ele vor fi extrase din stiv˘a ˆın momentul ˆın care procedura de vizitare DF S a unui nod este ˆıncheiat˘a: se determin˘a dac˘a nodul curent este r˘ad˘acina unei componente tare conexe, ¸si, ˆın caz afirmativ, toate nodurile care au fost vizitate din nodul curent ˆın cadrul parcurgerii ˆın adˆancime sunt marcate ca fiind elemente ale componentei tare conexe. Vom numerota toate nodurile grafului ˆın preordine (altfel spus acest num˘ar indic˘a pentru nodul curent num˘arul de noduri vizitate ˆınaintea sa), valorile fiind p˘astrate ˆın vectorul prenum (cititorul este invitat s˘a revad˘a ¸si sect¸iunea despre Muchie critic˘ a, cea de-a doua solut¸ie). Pentru un nod u ∈ G definim lowu astfel: prenumu lowu = min prenumx , dac˘a [u, x] este arc de ˆıntoarcere sau de traversare ¸si x ∈ S lowy , ∀y descendent direct al lui u
Dac˘a dintr–un vˆarf u ∈ G exist˘a un arc de ˆıntoarcere sau de traversare c˘atre un nod v ∈ G ˆın afara subarborelui de vizitare ˆın adˆancime determinat de u, atunci acest nod v trebuie s˘a fi fost vizitat ˆınaintea lui u (prenumu > prenumv ). Dac˘a un astfel de nod nu exist˘a atunci lowu = prenumu . Stiva S p˘astreaz˘a nodurile grafului, pe m˘asur˘a ce sunt vizitate ele fiind ad˘augate la S. Dac˘a u este un nod–cap (lowu = prenumu ) se extrag din stiv˘a toate nodurile dintre vˆarful stivei ¸si u inclusiv: acestea formeaz˘a o component˘a tare conex˘a. Se observ˘a c˘a algoritmul lui Tarjan (algoritmul 54) seam˘an˘a foarte mult cu algoritmul 26 de determinare a muchiei critice (varianta a II-a). 147
Algoritm 54 Algoritmul lui Tarjan pentru determinarea componentelor tare conexe 1: procedure ( Tarjan(n, V ecin)
n - num˘ arul de noduri din graf V ecin - matricea de adiacent¸˘ a for i ← 1, n do vizitati ← 0 end for counter ← 0 S←∅ for i ← 1, n do if (vizitati = 0) then call DF ST arjan(i, n, V ecin) end if end for end procedure procedure DFSTarjan(k, n, V ecin) S⇐k vizitatk ← 1 counter ← counter + 1 prenumk ← counter, lowk ← counter for i ← 1, n do if (vecink,i = 1) then if (vizitati = 0) then call DF ST arjan(i, n, V ecin) lowk ← M in(lowk , lowi ) else if (i ∈ S) then lowk ← M in(lowk , prenumi ) end if end if end if end for if (lowk = prenumk ) then repeat S⇒u Output ”u” until (u = k) end if end procedure
Input: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
⊲ vizitarea ˆın adˆ ancime a grafului
Exemplul 7.18 La ˆınceput valorile vectorilor vizitat ¸si prenum sunt urm˘ atoarele: 1 prenum 0 vizitat 0
2 3 4 0 0 0 0 0 0
5 6 0 0 0 0
7 8 0 0 0 0
Primul element nevizitat este nodul 1 (linia 9), prin urmare se apeleaz˘ a DF ST arjan(1, 8, V ecin). Rezult˘a o secvent¸˘a de apeluri recursive: DF SNum(1, 8, V ecin) → DF SNum(2, 8, V ecin) → DF SNum(3, 8, V ecin) → DF SNum(6, 8, V ecin) → DF SNum(5, 8, V ecin). 148
1 prenum 1 vizitat 1 low 1
2 2 1 2
3 3 1 0
4 0 0 0
5 5 1 4
6 4 1 4
7 0 0 0
8 0 0 0
Stiva cont¸ine urm˘atoarele elemente (vˆ arful stivei fiind ˆın dreapta): {1, 2, 3, 6, 5}. low5 = min{prenum6 , low5 } = min{4, 5} = 4 (deoarece de¸si vizitat6 = 1, avem 6 ∈ S, adic˘a nodul 6 se afl˘a pe stiv˘a). Astfel ˆın momentul termin˘ arii apelului DF ST arjan(5, 8, V ecin), low5 = 4. Acum la nivelul lui DF ST arjan(6, 8, V ecin), ˆın urma revenirii din DF ST arjan(5, 8, V ecin), se calculeaz˘a low6 = min{low6 , low5 } = min{4, 4} = 4 (linia 22). Nodul 6 este un nod–cap (low6 = prenum6 ) (vezi linia 30), ¸si prin urmare se extrag de pe stiv˘a toate elementele dintre vˆarful stivei ¸si elementul 6 inclusiv, rezultˆ and prima component˘a tare conex˘a: {5, 6}. Stiva r˘amˆane cu elementele (vˆ arful stivei fiind ˆın dreapta) {1, 2, 3}. Din nodul 3 se continu˘a cu vizitarea nodului 8, ˆınc˘ a nevizitat: DF SNum(3, 8, V ecin) → DF SNum(8, 8, V ecin) → DF SNum(7, 8, V ecin). Cont¸inutul stivei este {1, 2, 3, 8, 7}. La nivelul apelului DF SNum(7, 8, V ecin) nu se ia ˆın considerare la calculului valorii lui low7 nodul 6: de¸si exist˘a arcul (7, 6), nodul 6 a fost vizitat (vizitati 6= 0) ¸si nu se mai afl˘a pe stiv˘a (i ∈ / S). Astfel low7 = min{low7 , prenum8 } = min{7, 6} = 6. La nivelul lui DF SNum(8, 8, V ecin), low8 = min{low8 , low7 } = min{6, 6} = 6 (linia 22). Avem c˘a (low8 = prenum8 ) (linia 30) ¸si prin urmare 8 este un nod–cap. Se extrag de pe stiv˘a toate elementele dintre vˆarful stivei ¸si elementul 8 inclusiv, rezultˆ and a doua component˘a tare conex˘a: {7, 8}. Stiva r˘amˆane cu elementele (vˆarful stivei fiind ˆın dreapta) {1, 2, 3}. 1 prenum 1 vizitat 1 low 1
2 2 1 2
3 3 1 0
4 0 0 0
5 5 1 4
6 4 1 4
7 7 1 6
8 6 1 6
ˆIn momentul termin˘arii apelului DF ST arjan(3, 8, V ecin), low3 = 2. Revenind la nivelul apelului DF ST arjan(2, 8, V ecin), se calculeaz˘ a low2 = min{low2 , low3 } = min{2, 2} = 2, ¸si se continu˘a cu urm˘atorul nod ˆınc˘ a nevizitat, 4: DF SNum(2, 8, V ecin) → DF SNum(4, 8, V ecin). Se adaug˘a nodul 4 pe stiv˘a (stiva devine {1, 2, 3, 4}), low4 = prenum4 = 8. low4 se calculeaz˘a din low4 = min{low4 , prenum3 } = min{8, 3} = 3 (exist˘ a arcul (4, 3), nodul 3 a fost vizitat - vizitat3 = 1 ¸si 3 ∈ S - nodul 3 se afl˘ a pe stiv˘ a). 1 2 3 4 5 6 7 8 prenum 1 2 3 8 5 4 7 6 vizitat 1 1 1 1 1 1 1 1 low 1 2 2 3 4 4 6 6 Ne ˆıntoarcem la nivelul apelului DF ST arjan(2, 8, V ecin), low2 = min{low2 , low4 } = min{2, 3} = 2. Arcul (2, 5) nu este luat ˆın considerare la calculul lui low2 deoarece nodul 5 a fost vizitat (vizitat5 = 1) ¸si 5 nu se reg˘ ase¸ste pe stiv˘ a. Astfel low2 r˘ amˆ ane cu valoarea 2. Deoarece low2 = prenum2 , extragem de pe stiv˘ a elementele ce determin˘ a cea de-a treia component˘a tare conex˘a: {4, 3, 2}. Pe stiv˘ a mai r˘ amˆ ane un singur element, {1}, ce va determina ultima component˘a tare conex˘ a.
149
ˆIn final, valorile low ¸si prenum sunt urm˘ atoarele: 1 prenum 1 vizitat 1 low 1
7.4.3
2 2 1 2
3 3 1 2
4 8 1 3
5 5 1 4
6 4 1 4
7 7 1 6
8 6 1 6
Algoritmul lui Gabow
Algoritmul a fost introdus de c˘atre Joseph Cheriyan and Kurt Mehlhorn ˆın 1996 [28] ¸si apoi independent de c˘atre Harold Gabow ˆın 1999 [56]. Algoritmul construie¸ste un graf H [56] ce reprezint˘a o contract¸ie a grafului original G: unul sau mai multe noduri din G pot s˘a corespund˘a unui nod din H. La ˆınceput se init¸ializeaz˘a H = G. Se ˆıncepe construirea unui drum P alegˆandu–se un nod oarecare v ∈ H.
Fig. 7.8: Drumul P ˆın algoritmul lui Gabow pentru graful din figura 7.6
La fiecare pas al algoritmului se ˆıncearc˘a s˘a se augmenteze drumul P = P ∪ {w} = [v1 , . . . , vk , w] prin parcurgerea tuturor arcelor (vk , w): - dac˘a w ∈ / P , atunci se adaug˘a nodul w la drumul P (P = [v1 , . . . , vk , w]); - dac˘a w ∈ P , fie w = vj . Contract˘am circuitul {vj , vj+1 , . . . , vk , w} ˆın graful H: ˆın locul mult¸imii de vˆarfuri {vj , vj+1, . . . , vk } va r˘amˆane doar reprezentantul acesteia, e.g. nodul ce are valoarea minim˘a ˆın urma numerot˘arii ˆın preordine; - dac˘a nu avem nici un arc neverificat care s˘a aib˘a nodul vk ca extremitate init¸ial˘a, se marcheaz˘a nodul vk ca apart¸inˆand unei componente conexe. Se ¸sterge nodul vk din graful H ¸si din drumul P , ˆımpreun˘a cu toate arcele ce ˆıl au ca extremitate. Dac˘a P 6= ∅ atunci se continu˘a algoritmul ˆıncercˆandu–se augmentarea drumului P . ˆIn caz contrar se ˆıncearc˘a init¸ializarea unui drum nou P ˆın H. S¸i acest algoritm are la baz˘a metoda de parcurgere ˆın adˆancime a unui graf (DF S). ˆIn timpul vizit˘arii, algoritmul utilizeaz˘a dou˘a stive S ¸si P : S cont¸ine toate nodurile ce nu au fost atribuite ˆınc˘a unei componente tare conexe, ˆın ordinea ˆın care au fost ˆıntˆalnite, iar P cont¸ine toate nodurile despre care ˆınc˘a nu se poate spune nimic cu privire la apartenent¸a lor la componente conexe diferite (cont¸ine nodurile drumului P din graful H). Altfel spus, se 150
observ˘a faptul c˘a stiva P p˘astreaz˘a nodurile r˘ ad˘ acin˘ a ce formeaz˘a o subsecvent¸˘a a secvent¸ei nodurilor ce se afl˘a la un moment dat ˆın cealalt˘a stiv˘a S. ˆIn cadrul secvent¸ei urm˘atoare, pentru un nod i adiacent cu nodul curent k, se verific˘a dac˘a nu a mai fost vizitat. Dac˘a vizitati = 0 atunci se continu˘a vizitarea ˆın adˆancime (call DF SGabow(i, n, V ecin)). Altfel, se verific˘a dac˘a nodul i nu a fost deja asignat unei componente tare conexe, ˆın caz afirmativ eliminˆandu–se din drumul P circuitul {vi , . . . , vk , vi }: 1: if (vizitati = 0) then 2: call DF SGabow(i, n, V ecin) 3: else 4: if (i ∈ S) then 5: while (prenumi < prenumpeek(P )) do 6: P ⇒u 7: end while 8: end if 9: end if
⊲i
La finalul procedurii DF SGabow, se verific˘a dac˘a nodul curent este identic cu cel aflat ˆın vˆarful stivei P . ˆIn caz afirmativ se extrag de pe stiv˘a toate nodurile dintre vˆarful curent al stivei S ¸si nodul k, ¸si se marcheaz˘a ca fiind elemente ale componentei tare conexe a c˘arei r˘ad˘acin˘a este nodul k: if (k = peek(P )) then P ⇒u repeat S⇒u Output ”u” until (u = k) end if
7.5
Exercit¸ii
1. (Compilator ) ˆIn trecut compilatoarele erau foarte simple. ˆIn acele vremuri oamenii preferau s˘a includ˘a tot programul ˆıntr-un singur fi¸sier. Dac˘a cineva f˘acea o modificare ˆın program, trebuia recompilat tot codul surs˘a. Cre¸sterea lungimii programelor conducea la timpi de compilare tot mai mari, ceea ce constituia o piedic˘a ˆın ceea ce prive¸ste cre¸sterea complexit˘a¸tii algoritmilor implementat¸i. De aceea programatorii au dezvoltat o tehnic˘a pentru eliminarea compil˘arilor redundante. Ei au ˆınceput prin a sparge programele ˆın mai multe module mici pe care le compilau separat. Astfel, pentru orice modificare care se opera ˆıntr-un anumit modul, se compila numai acesta, ¸si nu ˆıntreaga aplicat¸ie. Fiecare modul cont¸ine la ˆınceput lista celorlaltor module pe care le folose¸ste. Modulul A trebuie recompilat numai dac˘a a fost modificat sau are ˆın lista sa un modul B care a fost recompilat la rˆandul s˘au. ˆIn celelalte cazuri nu este necesar˘a recompilarea modulului A. Problema cere s˘a se realizeze un algoritm ¸si pe baza acestuia un program, care s˘a decid˘a ce module trebuie recompilate ¸si care nu. Pentru a avea un timp de recompilare minim, va trebui s˘a c˘autam compilarea unui num˘ar cˆat mai mic de linii. Prima linie a datelor de intrare cont¸ine num˘arul de module N (1 ≤ N ≤ 100). Urmeaz˘a descrierea modulelor. Prima linie a descrierii cont¸ine numele modulului. A doua linie 151
Algoritm 55 Algoritmul lui Gabow pentru determinarea componentelor tare conexe 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:
procedure Gabow(n, V ecin) for i ← 1, n do vizitati ← 0 end for counter ← 0 S ← ∅, P ← ∅ for i ← 1, n do if (vizitati = 0) then call DF SGabow(i, n, V ecin) end if end for end procedure procedure DFSGabow(k, n, V ecin) vizitatk ← 1 S ⇐ k, P ⇐ k counter ← counter + 1 prenumk ← counter for i ← 1, n do if (vecink,i = 1) then if (vizitati = 0) then call DF SGabow(i, n, V ecin) else if (i ∈ S) then ⊲ i nu a fost asignat ˆınc˘ a unei componente tare conexe while (prenumi < prenumpeek(P )) do P ⇒u end while end if end if end if end for if (k = peek(P )) then P ⇒u repeat S⇒u Output ”u” until (u = k) end if end procedure
cont¸ine num˘arul liniilor din codul surs˘a. A treia linie cont¸ine num˘arul M (0 ≤ M < N) de module de care depinde modulul actual. Linia urm˘atoare cont¸ine numele acestor module, separate printr–un spat¸iu. Numele unui modul nu dep˘a¸se¸ste 20 de caractere. Descrierea modulelor este urmat˘a de mai multe blocuri, cˆate unul pentru fiecare versiune a programului. Prima linie a fiec˘arui bloc cont¸ine num˘arul k (1 ≤ k ≤ N) de module ce au suferit modific˘ari de la recompilarea versiunii precedente. Linia urm˘atoare cont¸ine numele modulelor, separate prin spat¸iu, ˆın care au survenit modific˘ari. Dup˘a ultimul bloc exist˘a o singur˘a linie ce cont¸ine doar num˘arul 0. Pentru fiecare versiune a programului se scrie o linie ce cont¸ine num˘arul liniilor codului 152
surs˘a care au trebuit s˘a fie recompilate. Intrare 3 MATH 20 0 MAIN 100 2 MATH IO IO 7 0 3 MATH IO MAIN 0
Ie¸sire 127
(Propusa la CEOI 1997)
2. Se d˘a un num˘ar de k (k < 1000) reguli, numerotate de la 1 la k. O regul˘a are forma x → y unde x ¸si y sunt propozit¸ii cu urm˘atoarea semnificat¸ie: dac˘a propozit¸ia x este adevarat˘a, atunci proprozit¸ia y este adev˘arat˘a. S˘a consider˘am pentru k = 5 urm˘atoarele reguli: (1) 1 → 2
(2) 1 → 3 (3) 3 → 4 (4) 4 → 5
(5) 1 → 5
Profesorul cere s˘a se demonstreze regula 1 → 5. Demonstrat¸ia optim˘a const˘a ˆın aplicarea direct˘a a regulii (5). (2)
(1)
(3)
(4)
Un elev demonstreaz˘a regula (5) astfel: 2 1 3 4 (1 → 3, 1 → 2, 3 → 4, 4 → 5). Aceasta (1)
este o demonstrat¸ie corect˘a, ce cont¸ine ˆın plus regula 1 → 2. Profesorul ar fi fost (2)
(3)
(4)
mult¸umit cu demonstrat¸ia 2 3 4 (1 → 3, 3 → 4, 4 → 5).
S˘a se realizeze un algoritm ce utilizeaz˘a drept date de intrare k, x (propozit¸ia care se consider˘a adev˘arat˘a), y (propozit¸ia ce trebuie demonstrat˘a), ¸sirul celor k reguli cˆat ¸si ¸sirul numerelor de ordine ale regulilor ce constituie demonstrat¸ia elevului. Algoritmul verific˘a dac˘a ¸sirul numerelor de ordine ale regulilor constituie o demonstrat¸ie. ˆIn caz negativ va afi¸sa ′ NU ′ , iar ˆın caz afirmativ va afi¸sa ′ DA′ , urmat pe linia urm˘atoare de demonstrat¸ia elevului din care au fost eliminate afirmat¸iile inutile. Se consider˘a c˘a un ¸sir de reguli f˘ar˘a reguli ˆın plus constituie o demonstrat¸ie corect˘a a propozit¸iei y pornind de la propozitia x, dac˘a renunt¸area la orice regul˘a ar conduce la o secvent¸˘a prin care y nu se mai poate deduce din x. (Marele premiu PACO, 1997)
3. Dup˘a cum se poate observa prin reclamele de la televizor, multe companii cheltuiesc foarte mult¸i bani pentru a convinge oamenii c˘a ofer˘a cele mai bune servicii la cel mai sc˘azut pret¸. O companie de telefoane ofer˘a cercuri de apel (calling circles). Un abonat poate s˘a fac˘a o list˘a cu persoanele pe care le sun˘a cel mai frecvent (¸si care constituie cercul s˘au de prieteni). Dac˘a acesta sun˘a pe cineva inclus ˆın aceast˘a list˘a, ¸si persoana respectiv˘a este, de asemenea, abonat˘a la aceea¸si companie, va beneficia de un discount mai mare decˆat pentru o convorbire telefonic˘a cu cineva din afara listei. O alt˘a companie a aflat de aceast˘a init¸iativ˘a ¸si se ofer˘a s˘a determine ea lista de cuno¸stint¸e cu care un abonat vorbe¸ste cel mai frecvent la telefon. 153
LibertyBellPhone este o companie nou˘a de telefoane ce se gˆande¸ste s˘a ofere un plan de apeluri mult mai avantajos decˆat alte companii. Ea ofer˘a nu numai reduceri pentru ”cercul de apel”, cˆat ¸si determin˘a pentru un abonat acest cerc. Iat˘a cum procedeaz˘a: compania p˘astreaz˘a numerele tuturor persoanelor participante la fiecare apel telefonic. ˆIn afara unui abonat, cercul s˘au de apel const˘a din toate persoanele pe care le sun˘a ¸si care ˆıl sun˘a, direct sau indirect. De exemplu, dac˘a Ben ˆıl sun˘a pe Alexander, Alexander o sun˘a pe Dolly ¸si Dolly ˆıl suna pe Ben, atunci ei tot¸i fac parte din acela¸si cerc. Dac˘a Dolly ˆıl mai sun˘a ¸si pe Benedict iar Benedict o sun˘a pe Dolly, atunci Benedict este ˆın acela¸si cerc cu Dolly, Ben ¸si Alexander. ˆIn fine, dac˘a Alexander ˆıl sun˘a pe Aron dar Aaron nu ˆıl sun˘a pe Alexander, Ben, Dolly sau Benedict, atunci Aaron nu este ˆın cerc. S˘a se realizeze un algoritm ce determin˘a cercurile de apel, cunoscˆandu–se lista apelurilor telefonice dintre abonat¸i. (Finala ACM 1995, Calling Circles)
4. La facultatea X exist˘a dou˘a alternative pentru ca student¸ii s˘a aib˘a timp s˘a asimileze cuno¸stint¸ele: (a) M˘arirea zilei la 30 de ore, ¸si (b) Reducerea programei ¸scolare. Optˆand pentru a doua variant˘a, Liga Student¸ilor introduce o platform˘a program care: (a) Stabile¸ste care sunt materiile necesare pentru a putea studia o nou˘a materie. De exemplu, pentru a studia cursul ”Management” este nevoie de cursul ”Teorie Economic˘a” ¸si de cursul ”Marketing”. (b) Stabile¸ste care sunt materiile cu adev˘arat utile dintre toate cele studiate ˆın facultate. De exemplu, cursul de ”M˘asur˘ari electrice” nu este util la absolut nimic. (c) Cere s˘a se elimine din program˘a materiile care nu sunt nici folositoare, nici nu servesc (direct sau indirect) la ˆınv˘a¸tarea unor materii folositoare. (d) Cere s˘a se indice care dintre materii nu pot fi predate ˆın nici o ordine. De exemplu, cursul ”Mecanic˘a” se bazeaz˘a pe cursul ”Ecuat¸ii Diferent¸iale”, dar cursul de ”Ecuat¸ii Diferent¸iale” ˆı¸si preia exemplele din ”Mecanic˘a”. Prin urmare nu exist˘a nici o ordine ˆın care aceste materii s˘a fie predate f˘ar˘a a introduce cuno¸stint¸e nedemonstrate. 5. Distribuirea c˘art¸ilor cerute de c˘atre cititorii de la sala de lectur˘a a unei biblioteci este f˘acut˘a de c˘atre un robot ce are posibilitatea s˘a ajung˘a la orice carte ce poate fi solicitat˘a. Din p˘acate, rafturile unde sunt depozitate c˘art¸ile sunt dispuse astfel ˆıncˆat robotul nu poate lua c˘art¸ile ˆıntr–un singur drum. Dup˘a recept¸ionarea comenzilor de la mai mult¸i cititori, robotul cunoa¸ste pozit¸ia c˘art¸ilor ˆın rafturi ¸si drumurile c˘atre acestea. Din p˘acate, de la pozit¸ia unei c˘art¸i, robotul nu se poate deplasa decˆat spre anumite c˘art¸i. Acesta porne¸ste ¸si culege c˘art¸ile ce sunt accesibile ˆıntr–un drum, apoi porne¸ste de la o alt˘a pozitie de carte ¸si culege acele c˘art¸i ce sunt accesibile din acel punct ¸si a¸sa mai departe. 154
Datele de intrare constau din num˘arul de c˘art¸i n precum ¸si num˘arul m de leg˘aturi dintre pozit¸iile acestora (1 ≤ n ≤ 100, 11 ≤ m ≤ 10000), urmate de m perechi de numere naturale, ce semnific˘a leg˘aturile directe ˆıntre pozit¸iile c˘art¸ilor. Datele de ie¸sire constau din drumurile pe care le face robotul pentru a culege c˘art¸ile cerute.
Fig. 7.9: Pozit¸iile c˘art¸ilor ˆıntr–o bibliotec˘a precum ¸si posibilit˘a¸tile de deplasare ale robotului
6. Se consider˘a o mult¸ime de n elevi dintr–o clas˘a. Fiecare elev are cuno¸stint¸e mai avansate ˆıntr–un anumit domeniu. Pentru ridicarea nivelului clasei, dirigintele vrea s˘a–i aranjeze ˆın grupuri astfel ˆıncˆat tot¸i elevii dintr–un grup s˘a ajung˘a s˘a cunoasc˘a, ˆıntr–o anumit˘a perioad˘a de timp, toate cuno¸stint¸ele tuturor celorlalt¸i colegi din acela¸si grup. Grupurile de elevi nu ˆı¸si vor schimba cuno¸stint¸ele ˆıntre ele. Se ¸stie c˘a un elev nu se poate face ˆınt¸eles de c˘atre oricine. El are o list˘a de preferint¸e fat¸˘a de care el le va ˆımp˘art˘a¸si cuno¸stintele sale. Relat¸ia de preferint¸˘a nu este simetric˘a. S˘a se determine num˘arul minim de grupuri ˆın care va fi ˆımp˘art¸it˘a clasa precum ¸si component¸a acestora.
155
Capitolul 8 Distant¸e ˆın grafuri Ret¸eaua de drumuri europene, nat¸ionale ¸si judet¸ene dintr–o ¸tar˘a, ret¸eaua feroviar˘a reprezentˆand infrastructura c˘ailor ferate absolut necesar˘a pentru realizarea transportului de m˘arfuri ¸si c˘al˘atori cu trenul, ret¸eaua de fibr˘a optic˘a folosit˘a pentru traficul de date ˆın Internet, ret¸eaua de transport a energiei electrice folosit˘a pentru alimentarea cu energie electric˘a a consumatorilor casnici ¸si a celor industriali, ret¸eaua de canalizare dintr–un ora¸s folosit˘a pentru evacuarea de¸seurilor, ret¸eaua CAT V a unui operator de televiziune prin cablu, ret¸eaua de linii de autobuz ce face parte din sistemul public de transport al unui ora¸s sunt exemple tipice de grafuri cu care interact¸ion˘am ˆın viat¸a de zi cu zi. Putem spune c˘a ret¸elele de transport ¸si cele de comunicat¸ii de date ne influent¸eaz˘a ˆın mod direct modul de viat¸˘a, devenind elemente indispensabile ale omului modern, ¸si astfel, problemele referitoare la studiul c˘ailor de comunicat¸ie, drumurilor, conexiunilor, cap˘at˘a un interes special. De obicei suntem interesat¸i de aspecte precum drumul cel mai scurt sau cel mai lung, drumul cel mai ieftin sau drumul care se poate parcurge cel mai repede, drumul cel mai sigur. Teoria grafurilor ne pune la dispozit¸ie mijloacele pentru aflarea r˘aspunsului la multe astfel de ˆıntreb˘ari. Definit¸iile pentru drum, lant¸, ciclu, circuit au fost prezentate ˆın capitolele anterioare, ˆımpreun˘a cu multe alte concepte teoretice. Lungimea unui lant¸ este determinat˘a de num˘arul muchiilor sale. ˆIn mod analog, lungimea unui drum este egal˘a cu num˘arul arcelor sale. ˆIntr–un graf G se introduce o funct¸ie de cost ce asocieaz˘a o valoare real˘a fiec˘arei muchii sau arc, dup˘a caz: c : E −→ R sau c : V × V −→ R Not˘am costul unui drum ca fiind suma costurilor arcelor componente. Astfel pentru un drum D = [v0 , v1 , . . . , vm ] de lungime m, costul acestuia va fi dat de urm˘atoarea formul˘a: c(D) =
m−1 X
c((vi , vi+1 ))
i=0
Not˘am cu Ms,t mult¸imea drumurilor dintre nodul xs ¸si nodul xt , xs , xt ∈ V . Pentru dou˘a noduri oarecare xs ¸si xt , se dore¸ste s˘a se determine un drum δs,t de la xs la xt a c˘arui valoare s˘a fie optim˘a (minim˘a sau maxim˘a): opt c(δs,t )=
min c(δs,t )
δs,t ∈Ms,t
156
8.1
Drumul minim de la un vˆ arf la celelalte vˆ arfuri
8.1.1
Algoritmul lui Moore
Fie G = (V, E) un graf orientat unde V = {x1 , x2 , . . . , xn } este mult¸imea nodurilor ¸si E este mult¸imea arcelor (E ⊆ V × V ). Pentru reprezentarea grafului vom utiliza listele de adiacent¸˘a (listele cu vecini). Fie u un vˆarf al grafului numit surs˘ a. Dorim s˘a determin˘am pentru fiecare vˆarf w ∈ V, w 6= u, dac˘a exist˘a, un drum de lungime minim˘a de la u la w. Lungimea unui drum se define¸ste ca fiind num˘arul arcelor ce ˆıl compun. Algoritmul lui Moore (algoritmul 56) se bazeaz˘a pe structura algoritmului de parcurgere ˆın l˘a¸time al unui graf (breadth first search – vezi algoritmul 20). ˆIn lucrarea [98], Moore ˆı¸si prezint˘a algoritmul astfel: Write 0 on the source. Then look at all the neighbors of the source and write 1 on them. Then look at all the neighbors of nodes with 1 on them and write 2 on them. And so on. Vom nota cu dk – costul drumului minim de la vˆarful u la vˆarful xk , iar tatak – vˆarful anterior lui xk , pe drumul de cost minim de la u la xk . Algoritm 56 Algoritmul lui Moore 1: procedure Moore1(k, n, V ecin; d, tata)
Input:
k n V ecin d tata
- nodul surs˘ a - num˘ arul de noduri din graf - vector ce cont¸ine capetele listelor de vecini
- vectorul distant¸elor de la nodul surs˘ a la celelalte noduri Output: - vector ce cont¸ine pentru fiecare nod k, predecesorul acestuia pe drumul de cost minim de la nodul surs˘ a la nodul k 2: for i ← 1, n do 3: di ← +∞ 4: end for 5: dk ← 0 ⊲ distant¸a de la un nod la el ˆınsu¸si este 0 6: Q⇐k ⊲ inserarea nodului curent k ˆın coad˘ a 7: while (Q 6= ∅) do ⊲ cˆ at timp coada nu este vid˘ a 8: Q⇒k ⊲ extrage nodul curent din coad˘ a 9: v ← vecink ⊲ se pleac˘ a cu primul vecin din lista de vecini 10: while (v 6= N U LL) do 11: if (dv.nodeIndex = +∞) then ⊲ v.nodeIndex este indicele nodului vecin 12: dv.nodeIndex ← dk + 1, tatav.nodeIndex ← k 13: Q ⇐ v.nodeIndex ⊲ inserarea nodului v.nodeIndex ˆın coada 14: end if 15: v ← v.next ⊲ se trece la urm˘ atorul vecin 16: end while 17: end while 18: end procedure
Exemplul 8.1 Fie un graf orientat definit de c˘ atre urm˘ atoarele liste de vecini: 157
1: 2: 3: 4: 5: 6:
(2, 4) (3, 6) (2) (1, 5) (4, 6, 9) (7, 8)
7: (8) 8: (6, 7) 9: () 10: (11) 11: (10)
ˆIn urma aplic˘arii algoritmului lui Moore pentru acest graf ¸si avˆ and nodul 1 drept surs˘a, se obt¸in urm˘atoarele valori pentru vectorii d ¸si tata: d tata
1 0
2 1 1
3 2 2
4 1 1
5 2 4
6 2 2
7 3 6
8 3 6
9 3 5
10 ∞
11 ∞
Urm˘arind valorile afi¸sate, putem spune c˘ a drumul de lungime minim˘ a de la nodul 1 la nodul 9 are costul 3 ¸si este compus din nodurile [1, 4, 5, 9]. Algoritmul 57 calculeaz˘a lungimea drumurilor minime de la un nod surs˘a la toate nodurile accesibile din acesta, ˆın cazul ˆın care lungimea unui drum se define¸ste ca fiind suma costurilor asociate arcelor ce-l compun. Algoritm 57 Algoritm lui Moore (a doua variant˘a) 1: procedure Moore2(k, n, C; d, tata)
a k - nodul surs˘ Input: n - num˘ arul de noduri din graf C - matricea costurilor 2: for i ← 1, n do 3: di ← +∞ 4: end for 5: dk ← 0 6: Q⇐k 7: while (Q 6= ∅) do 8: Q⇒k 9: for fiecare vecin (v = xi ) al lui xk do 10: if (dk + ck,i < di ) then 11: di ← dk + ck,i , tatai ← k 12: Q⇐i 13: end if 14: end for 15: end while 16: end procedure
8.1.2
⊲ distant¸a de la un nod la el ˆınsu¸si este 0 ⊲ inserarea nodului curent k ˆın coad˘ a ⊲ cˆ at timp coada nu este vid˘ a ⊲ extrage nodul curent din coad˘ a
⊲ inserarea nodului i ˆın coada
Algoritmul lui Dijkstra
Fie G = (V, E) un graf orientat unde V = {1, 2, . . . , n} este mult¸imea nodurilor ¸si E este mult¸imea arcelor (E ⊆ V ×V ). Pentru reprezentarea grafului se utilizeaz˘a matricea costurilor C: , dac˘a i = j 0 ci,j = ∞ , dac˘a (i, j) ∈ / E, i 6= j d > 0 , dac˘a (i, j) ∈ E 158
Fie u un vˆarf al grafului numit surs˘ a. Dorim s˘a determin˘am pentru fiecare vˆarf j ∈ V, j 6= u, dac˘a exist˘a, un drum de lungime minim˘a de la u la j. Lungimea unui drum se define¸ste ca fiind suma costurilor asociate arcelor ce-l compun. Algoritmul lui Dijkstra [38] utilizeaz˘a metoda general˘a de elaborare a algoritmilor Greedy, drumurile de lungime minim˘a fiind generate ˆın ordinea cresc˘atoare a lungimii lor. Vom nota cu S mult¸imea nodurilor grafului G pentru care se cunoa¸ste (s-a calculat) drumul de lungime minim˘a de la surs˘a. La ˆınceput mult¸imea S este format˘a doar din nodul surs˘a. La fiecare pas, se adaug˘a la mult¸imea S un nod k ∈ V \ S cu proprietatea c˘a drumul de la surs˘a la acel nod este cel mai mic dintre toate drumurile posibile de la surs˘a la noduri din mult¸imea V \ S, cu proprietatea c˘a un astfel de drum are drept noduri intermediare numai elemente din mult¸imea S, cu except¸ia extremit˘a¸tii finale. Vom utiliza un tablou D ce va p˘astra pentru fiecare nod k, lungimea drumului cel mai scurt de la surs˘a ce trece numai prin noduri din mult¸imea S (vezi algoritmul 58). Dup˘a ce am identificat un astfel de nod k, mult¸imea S se modific˘a astfel: S = S ∪ {k}. Este posibil ca lungimea unui drum de la surs˘a la un nod j ∈ V \ S, ce are drept noduri intermediare noduri din mult¸imea S, s˘a se modifice datorit˘a faptului c˘a, mai ˆınainte, nodul k nu fusese luat ˆın considerare, deoarece nu apart¸inea mult¸imii S. Astfel, se poate ca un drum de la surs˘a la nodul j, ce trece prin nodul k, s˘a fie mai mic decˆat drumul anterior de la surs˘a la nodul j: dk + ck,j < dj . Algoritm 58 Algoritmul lui Dijkstra (schema general˘a) 1: procedure Dijkstra1(n, C, u) 2: S ← {u} 3: for i ← 1, n do 4: di ← cu,i 5: end for 6: for i ← 1, n − 1 do 7: k ← min{dk |k ∈ V \ S} 8: S ← S ∪ {k} 9: for each j ∈ V \ S do 10: dj ← min(dj , dk + ck,j ) 11: end for 12: end for 13: end procedure
Vom utiliza trei vectori (vezi algoritmul 59): • vizitat - vector caracteristic
( 1 , dac˘a nodul k ∈ S vizitatk = 0 , dac˘a nodul k ∈ V \ S
• d - vectorul distant¸elor de la nodul u la celelalte noduri ale grafului. ˆIn momentul init¸ial dj = cu,j . Fie k nodul ales la un moment dat. Atunci dj se modific˘a numai dac˘a dk + ck,j < dj , fiind actualizat astfel: dj = dk + ck,j . • tatak - cont¸ine pentru fiecare nod k nodul anterior j (j ∈ S) pe drumul de cost minim de la u la k. La ˆınceput, ( 0 , dac˘a nodul k = u sau cu,k = ∞ tatak = u , ˆın rest (k 6= u ¸si cu,k 6= ∞) 159
Un element al acestui vector se poate modifica atunci cˆand se modific˘a dj , caz ˆın care tataj = k. Algoritm 59 Algoritmul lui Dijkstra 1: function DistantaMinima(n, vizitat, d) 2: min ← ∞ 3: for j ← 1, n do 4: if (vizitatj 6= 1) ∧ (dj < min) then 5: min ← dj 6: j0 ← j 7: end if 8: end for 9: if (min = ∞) then 10: return −1 11: else 12: return j0 13: end if 14: end function 15: procedure Dijkstra2(n, C, u) 16: vizitatu ← 1 17: du ← 0, tatau ← 0 18: for i ← 1, n do 19: if (i 6= u) then 20: vizitati ← 0 21: di ← cu,i , tatai ← u 22: end if 23: end for 24: while (true) do 25: k ← DistantaM inima(n, vizitat, d) 26: if (k < 0) then 27: break ⊲ forteaza iesirea dintr-o instructiune de ciclare 28: else 29: vizitatk ← 1 30: for i ← 1, n do 31: if (vizitati 6= 1) ∧ (dk + ck,i < di ) then 32: tatai ← k 33: di ← dk + ck,i 34: end if 35: end for 36: end if 37: end while 38: end procedure
Observat¸ia 8.2 Algoritmul lui Dijkstra prezint˘ a asem˘ an˘ ari cu algoritmul de c˘autare ˆın l˘a¸time, la final, vectorul tata p˘astrˆand un arbore al drumurilor minime ce are drept r˘ad˘ acin˘a nodul surs˘a.
160
j x
u k
Fig. 8.1: O cale speciala mai scurta
Demonstrarea corectitudinii algoritmului ˆIn momentul ˆın care alegem nodul k ∈ V \ S avˆand proprietatea c˘a drumul de la surs˘a la acel nod este cel mai mic dintre toate drumurile posibile de la surs˘a la noduri din mult¸imea V \ S, ce trec numai prin noduri din mult¸imea S, cu except¸ia extremit˘a¸tii finale, acel drum este cel mai mic drum de la surs˘a la k dintre toate drumurile posibile. Denumim drum special un drum de la surs˘a la un nod k ce are drept noduri intermediare numai elemente din mult¸imea S, cu except¸ia extremit˘a¸tii finale. S˘a presupunem prin reducere la absurd, c˘a exist˘a un drum de lungime mai mic˘a de la nodul surs˘a la nodul k, ce nu are toate nodurile intermediare din mult¸imea S. Fie j ∈ V \ S, primul nod pe drumul de la nodul surs˘a la nodul k, ce nu apart¸ine mult¸imii S. Atunci drumul de la nodul surs˘a u la nodul k se compune dintr-un drum de la u la j, ¸si un drum de la j la k. Datorit˘a modului de alegere al lui j, drumul de la u la j are drept noduri intermediare numai elemente din mult¸imea S (j este primul nod pe drumul de la nodul surs˘a la nodul k, care nu apart¸ine mult¸imii S), deci este un drum special ¸si are o lungime mai mic˘a decˆat drumul special de la surs˘a la nodul k. Prin urmare am g˘asit un alt drum special de lungime mai mic˘a, ceea ce contrazice modul de alegere al nodului k. Trebuie s˘a demonstr˘am c˘a ˆın orice moment, dk p˘astreaz˘a lungimea celui mai scurt drum special de la nodul surs˘a u la nodul k ∈ V \ S. ˆIn momentul ˆın care nodul k este ad˘augat mult¸imii S, avem grij˘a s˘a verific˘am dac˘a nu exist˘a un drum special de la nodul u la un nod j ∈ V \ S care s˘a aib˘a o lungime mai mic˘a. S˘a presupunem c˘a pentru un nod j fixat exist˘a un nod x ∈ S astfel ˆıncˆat drumul special u k +k x + (x, j) s˘a aib˘a o lungime mai mic˘a (vezi figura 8.1). Deoarece nodul x a fost ad˘augat mult¸imii S ˆınaintea nodului k avem dx ≤ dk . Prin urmare dx + cx,j ≤ dk + cx,j < dk + costk j + cx,j , adic˘a drumul special de la surs˘a la nodul j ce trece prin nodul x are lungimea mai mic˘a decˆat drumul special compus din drumul de la nodul surs˘a la nodul k, drumul de la nodul k la nodul x ¸si arcul (x, j). Exemplul 8.3 S˘a presupunem c˘a nodul surs˘ a este nodul 1 pentru graful din figura 8.2. Dup˘a etapa de init¸ializare vom avea urm˘atoarele valori: 1 d tata vizitat
0 1
2 1 1 0
3 ∞ 1 0
4 21 1 0
5 ∞ 1 0
161
Fig. 8.2: Un exemplu de graf orientat ponderat
Dup˘a prima iterat¸ie avem: d2 + c2,3 < d3 ⇔ 1 + 8 < +∞ d2 + c2,4 < d4 ⇔ 1 + ∞ < 21 d2 + c2,5 < d5 ⇔ 1 + 4 < +∞ 1 d tata vizitat
0 1
2 1 1 1
3 9 2 0
4 21 1 0
5 5 2 0
ˆIn timpul celei de-a doua iterat¸ii se calculeaz˘ a: d5 + c5,3 < d3 ⇔ 5 + 3 < 9 d5 + c5,4 < d4 ⇔ 5 + ∞ < 21 1 d tata vizitat
0 1
2 1 1 1
3 8 5 0
4 21 1 0
5 5 2 1
Dup˘a pasul al treilea avem: d3 + c3,4 < d4 ⇔ 8 + 12 < 21 1 d tata vizitat
0 1
2 1 1 1
3 8 5 1
4 20 3 0
5 5 2 1
La sfˆa¸situl ultimei iterat¸ii vom obt¸ine: 1 d tata vizitat
0 1
2 1 1 1
3 8 5 1
4 20 3 1
5 5 2 1
Implementarea ˆın limbajul C a algoritmului 59 este urm˘atoarea:
162
#include #include #define MAX 100 #define TRUE 1 #define FALSE 0 /* numarul de noduri din graf */ int n; /* matricea costurilor */ int c[MAX][MAX]; /* vector care pastreaza starea unui nod: vizitat sau nevizitat */ char vizitat[MAX]; /* nodul fata de care se calculeaza drumurile de lungime minima */ int u; /* tata[i] parintele varfului i in arborele rezultat */ int tata[MAX]; /* distanta de la fiecare nod la u */ int d[MAX]; /** * Citirea datelor de intrare - +infinit = -1 */ void readInput(void) { int i, j; printf("n = "); scanf("%d", &n); for (i = 0; i < n; i++) for (j = 0; j < n; j++) { scanf("%d", &c[i][j]); if (c[i][j] < 0) c[i][j] = MAXINT; } printf("Nodul initial : "); scanf("%d", &u); } int minim(int* d) { int j, j0; int min; min = MAXINT; for (j = 0; j < n; j++) if (!vizitat[j] && d[j] < min) { min = d[j]; j0 = j; } if (min == MAXINT) return -1; else return j0; }
163
/** * Functia verifica daca a>b+c; variabilele sunt de tip long * pentru ca daca suma depaseste 32767 se face trunchiere. */ int mai_mare(long a, long b, long c) { return (a > b+c) ? 1 : 0; } void dijkstra(int u) { int i, k; //initializari vizitat[u] = TRUE; tata[u] = -1; for (i = 0; i < n; i++) if (i != u) { vizitat[i] = FALSE; d[i] = c[u][i]; tata[i] = u; } //partea principala while (TRUE) { k = minim(d); if (k < 0) break; vizitat[k] = TRUE; //actualizare for (i = 0; i < n; i++) if (!vizitat[i] && mai_mare(d[i],d[k],c[k][i])) { tata[i] = k; d[i] = d[k] + c[k][i]; } } } void printSolution(void) { int i; for (i = 0; i < n; i++) if (i != i0) if (d[i] == MAXINT) printf("Nu exista drum de la varful %d la varful %d. \n", i0, i); else printf("Distanta de la varful %d la varful %d este %d. \n", i0, i, d[i]); } void main(void) { readInput(); dijkstra(u); printSolution();
164
}
Utilizarea structurii coad˘ a cu prioritate ˆın algoritmul lui Dijkstra Complexitatea–timp al algoritmului lui Dijkstra poate fi ˆımbun˘at˘a¸tit˘a prin utilizarea structurii de date de coad˘a cu prioritate (vezi algoritmul 60). Algoritm 60 Algoritmul lui Dijkstra (varianta ce folose¸ste o coad˘a cu prioritate) 1: procedure Dijkstra3(n, C, u) 2: for i ← 1, n do 3: di ← ∞ 4: tatai ← N U LL 5: call Insert(Q, i, di ) ⊲ insereaz˘ a ˆın coad˘ a elementul i cu prioritatea di 6: end for 7: du ← 0 8: call DecreaseKey(Q, u, du ) ⊲ actualizeaz˘ a prioritatea vˆ arfului u la du 9: S←∅ 10: for i ← 1, n − 1 do 11: k ← DeleteM in(Q) ⊲ ¸sterge elementul de prioritate minim˘ a din coad˘ a 12: S ← S ∪ {k} 13: for f iecare j ∈ V \ S, astfel ˆıncˆ at (k, j) ∈ E do 14: if (dj > dk + ck,j ) then 15: dj ← dk + ck,j 16: tataj ← k 17: call DecreaseKey(Q, j, dj ) ⊲ actualizeaz˘ a prioritatea vˆ arfului j la dj 18: end if 19: end for 20: end for 21: end procedure
Analizˆand algoritmul 60 obt¸inem c˘a timpul de lucru al acestuia depinde de formula: T (n, m) = O(n · TInsert + n · TDeleteM in + m · TDecreaseKey )
(8.1)
unde n = num˘arul de noduri ale grafului G iar m = num˘arul de muchii ale aceluia¸si graf. Cele trei operat¸ii ˆıntˆalnite ˆın cadrul algoritmlui sunt (vezi ¸si capitolul ??): 1. DeleteMin(Q) - ¸sterge nodul ce cont¸ine cheia de valoare minim˘a ¸si reorganizeaz˘a structura prin refacerea propriet˘a¸tii de coad˘ a cu prioritate. 2. Insert(Q, p, x) - insereaz˘a nodul p ˆın coada cu prioritate Q. 3. DecreaseKey(Q, p, v) - modific˘a valoarea priorit˘a¸tii nodului p din coada cu prioritate Q, atribuindu–i valoarea v ¸si reorganizeaz˘a aceast˘a structur˘a. Astfel pentru diferite implement˘ari ale cozii cu prioritate Q, complexitatea algoritmului va fi [30]: 1. List˘a liniar˘a dublu ˆınl˘ant¸uit˘a neordonat˘a: T (n, m) = O(n · 1 + n · n + m · 1) = O(n2 ) 165
2. Arbore 2 − 3:
T (n, m) = O(n · 1 + n · n + m · 1) = O(n2 )
3. Heap-uri Fibonacci: T (n, m) = O(n · 1 + n · n + m · 1) = O(n2 )
8.2
Drumuri minime ˆıntre toate perechile de vˆ arfuri
Fie G = (V, E) un graf orientat unde V = {x1 , x2 , . . . , xn } reprezint˘a mult¸imea nodurilor ¸si E reprezint˘a mult¸imea arcelor (E ⊆ V × V ). Fiec˘arui arc din E i se asociaz˘a o valoare pozitiv˘a ci,j (ci,j ≥ 0) ce reprezint˘a lungimea arcului. Se dore¸ste calcularea drumurilor de lungime minim˘a pentru oricare pereche de noduri xi , xj . Pentru a determina drumurile minime ˆıntre toate perechile de vˆarfuri, putem alege un algoritm ce determin˘a drumurile minime de surs˘a unic˘a (drumurile minime de la acel vˆarf la toate celelalte), ¸si pe care s˘a–l aplic˘am pentru fiecare vˆarf al grafului. Un astfel de algoritm este algoritmul lui Dijkstra, algoritm ce a cunoscut multiple ˆımbun˘at˘a¸tiri fat¸˘a de varianta orginal˘a, ce se bazeaz˘a pe utilizarea unor structuri de date avansate (de exemplu heap-uri Fibonacci), menite s˘a–i optimizeze timpul de execut¸ie. Astfel complexitatea– timp ˆın cazul cel mai defavorabil pentru calculul celor mai scurte drumuri pornind dintr–un vˆarf al unui graf cu n noduri ¸si m arce este O(m + n log n). Dac˘a aplic˘am acest algoritm pentru toate cele n vˆarfuri vom obt¸ine un timp de lucru de O(mn + n2 log n). Algoritmul 61 prezint˘a o variant˘a a algoritmului lui Dijkstra ˆın care drumurile de lungime minim˘a ˆıntre oricare dou˘a vˆarfuri sunt calculate incremental ¸si intercalat [35]. Matricea D va p˘astra pentru fiecare pereche [xi , xj ] lungimea di,j a drumului minim dintre cele explorate pˆan˘a la momentul curent. Coada de priorit˘a¸ti C p˘astreaz˘a toate perechile (i, j), ordonate cresc˘ator ˆın funct¸ie de prioritatea di,j a fiec˘aruia. La fiecare pas, se extrage din coad˘a perechea (i, j) avˆand prioritatea cea mai mic˘a ¸si se ˆıncearc˘a extinderea drumului [xi , xj ] cu exact un arc la fiecare extremitate a drumului. Tehnica utilizat˘a este cea a relax˘arii, ˆıntˆalnit˘a ¸si ˆın cadrul algoritmului lui Bellman, cu scopul de a mic¸sora costul drumului de la xi la xj , prin parcurgerea tuturor arcelor ce p˘ar˘asesc vˆarful xj ¸si a celor ce sosesc ˆın vˆarful xi .
8.2.1
Algoritmul lui Roy-Floyd-Warshall
Fie p = [x1 , x2 , . . . , xk ] un drum elementar. Definim un vˆarf intermediar orice vˆarf u ∈ {x2 , . . . , xk−1 } (u 6= x1 , u 6= xk – vˆarful intermediar este diferit de extremit˘a¸tile drumului p). Fie U = {x1 , x2 , . . . , xk } o mult¸ime de noduri. Not˘am cu MUi,j mult¸imea drumurilor de la xi la xj ce au drept noduri intermediare elemente din mult¸imea U. Fie p drumul de cost minim de la xi la xj (p ∈ MUi,j ). Un astfel de drum este elementar deoarece am presupus c˘a nu exist˘a cicluri al c˘aror cost s˘a fie negativ. Not˘am dki,j costul drumului minim de la xi la xj ce are noduri intermediare numai din mult¸imea {x1 , x2 , . . . , xk }. Dac˘a xk ∈ / p (xk nu apart¸ine drumului p) atunci drumul de cost minim de la xi la xj avˆand toate nodurile intermediare din mult¸imea {x1 , x2 , . . . , xk−1 } va fi ¸si drum de cost minim de la xi la xj cu toate nodurile intermediare din mult¸imea {x1 , x2 , . . . , xk }: dki,j = dk−1 i,j Dac˘a xk este un vˆarf intermediar al drumului p, fie p1 ¸si p2 subdrumuri ale lui p, p = p1 ⊕ p2 (p1 drumul de la xi la xk avˆand nodurile intermediare din mult¸imea {x1 , x2 , . . . , xk } ¸si p2 166
Algoritm 61 Algoritmul lui Dijkstra pentru toate perechile de vˆarfuri 1: procedure DijkstraAll(n, C; D) 2: for i ← 1, n do 3: for j ← 1, n do 4: if ((i, j) ∈ E) then 5: di,j ← ci,j 6: else 7: di,j ← ∞ 8: end if 9: C ⇐ (i, j) cu prioritatea di,j 10: end for 11: end for 12: while (C = 6 ∅) do 13: C ⇒ (i, j) 14: for k ← 1, n do 15: if (di,j + cj,k < di,k ) then 16: di,k ← di,j + cj,k 17: actualizeaz˘ a prioritatea lui (i, k) din C la di,k 18: end if 19: end for 20: for k ← 1, n do 21: if (ck,i + di,j < dk,j ) then 22: dk,j ← ck,i + di,j 23: actualizeaz˘ a prioritatea lui (k, j) din C la dk,j 24: end if 25: end for 26: end while 27: end procedure
⊲ sau (ci,j > 0) ∧ (ci,j < ∞)
drumul de la xk la xj avˆand nodurile intermediare din mult¸imea {x1 , x2 , . . . , xk }). Deoarece k−1 k k p1 ¸si p2 sunt drumuri de cost minim ¸si xk ∈ / {x1 , x2 , . . . , xk−1 } avem: dk−1 i,k = di,k , dk,j = dk,j . k−1 c(p) = c(p1 ) + c(p2 ) ⇒ dki,j = dk−1 i,k + dk,j
Algoritmul va construi un ¸sir de matrici D 0 , D 1 , . . . , D k , . . . , D n . Pentru fiecare k (1 ≤ k ≤ n), dki,j va cont¸ine lungimea drumului minim de la nodul xi la nodul xj ce are drept noduri intermediare numai noduri din mult¸imea {x1 , x2 , . . . , xk }. Matricea D 0 se va init¸ializa astfel: , dac˘a i = j 0 0 di,j = +∞ (8.2) , dac˘a (xi , xj ) ∈ / E, i 6= j d > 0 , dac˘a (xi , xj ) ∈ E
(un drum ce nu are nici un vˆarf intermediar este determinat de costul leg˘aturii directe dintre xi ¸si xj ). Drumul de lungime minim˘a de la xi la xj ce trece numai prin nodurile {x1 , x2 , . . . , xk } fie k−1 nu cont¸ine nodul xk caz ˆın care dki,j = dk−1 ıl cont¸ine, ¸si atunci dki,j = dk−1 i,j , fie ˆ i,k + dk,j . Prin k−1 k−1 urmare dki,j = min{dk−1 i,j , di,k + dk,j }. De¸si aceast˘a ultim˘a relat¸ie sugereaz˘a un algoritm recursiv, o abordare iterativ˘a este mai eficient˘a atˆat ˆın ceea ce prive¸ste complexitatea timp cˆat ¸si din punctul de vedere al necesarului 167
de memorie. Se observ˘a c˘a matricea D k nu mai este necesar˘a de ˆındat˘a ce matricea D k+1 a fost calculat˘a. D n = (dni,j ), dni,j = δi,j , ∀xi , xj ∈ V. Algoritm 62 Algoritmul Floyd–Warshall 1: procedure FloydWarshall1(n, C; D n) 2: for i ← 1, n do 3: for j ← 1, n do 4: d0i,j ← ci,j 5: end for 6: end for 7: for k ← 1, n do 8: for i ← 1, n do 9: for j ← 1, n do k−1 k−1 10: dki,j ← min {dk−1 i,j , di,k + dk,j } 11: end for 12: end for 13: end for 14: end procedure
ˆIn algoritmul 62 se poate renunt¸a la indicii superiori, obt¸inˆandu–se astfel o economie de spat¸iu de la Θ(n3 ) la Θ(n2 ) (vezi algoritmul 63). Algoritm 63 Algoritmul Floyd–Warshall (varianta optimizat˘a) 1: procedure FloydWarshall2(n, C; D) 2: for i ← 1, n do 3: for j ← 1, n do 4: di,j ← ci,j 5: end for 6: end for 7: for k ← 1, n do 8: for i ← 1, n do 9: for j ← 1, n do 10: di,j ← min {di,j , di,k + dk,j } 11: end for 12: end for 13: end for 14: end procedure
Exemplul 8.4 Fie graful din figura 8.2. Matricea costurilor asociat˘ a acestui graf este: 0 1 +∞ 21 +∞ +∞ 0 8 +∞ 4 12 +∞ C= 3 +∞ 0 +∞ +∞ 9 0 +∞ +∞ +∞ 3 +∞ 0 168
S¸irul de matrice D k , rezultat al aplic˘ arii algoritmului Roy-Floyd-Warshall pentru acest graf, este: 0 1 ∞ 21 ∞ ∞ 0 8 ∞ 4 3 ∞ 0 12 ∞ D0 = ∞ ∞ 9 0 ∞ ∞ ∞ 3 ∞ 0
D2
D4
0 1 9 ∞ 0 8 3 4 0 ∞ ∞ 9 ∞ ∞ 3
21 5 ∞ 4 12 8 0 ∞ ∞ 0
0 1 9 11 0 8 3 4 0 12 13 9 6 7 3
21 5 20 4 12 8 0 17 15 0
= =
0 1 ∞ 21 ∞ ∞ 0 8 ∞ 4 3 4 0 12 ∞ D1 = ∞ ∞ 9 0 ∞ ∞ ∞ 3 ∞ 0
D3
D5
0 1 9 11 0 8 3 4 0 12 13 9 6 7 3
21 5 20 4 12 8 0 17 15 0
0 1 8 10 0 7 3 4 0 12 13 9 6 7 3
20 5 19 4 12 8 0 17 15 0
= =
ˆInchiderea tranzitiv˘ a Definit¸ia 8.1 Fie G = (V, E) un graf orientat, simplu ¸si finit, V = {x1 , x2 , . . . , xn }. ˆ Inchiderea tranzitiv˘ a a grafului G este un graf G+ = (V, E + ), unde E + = {(xi , xj )| ∃ un drum de la xi la xj ˆın G}.
ˆInchiderea tranzitiv˘a a unui graf orientat prezint˘a multiple aplicat¸ii drept subproblem˘a ˆın cazul unor probleme computat¸ionale cum ar fi: construirea unui automat de parsare utilizat la realizarea unui compilator, evaluarea interog˘arilor recursive realizate asupra unei baze de date sau analiza elementelor accesibile ˆıntr–o ret¸ea de tranzit¸ie asociat˘a unui sistem paralel sau distribuit. Prima variant˘a de a determina ˆınchiderea tranzitiv˘a presupune atribuirea unui cost unitar (= 1) arcelor din E urmat˘a de aplicarea algoritmului Roy–Floyd–Warshall. Vom obt¸ine o matrice ˆın care, di,j = +∞ dac˘a nu exist˘a un drum de la xi la xj sau di,j = c < n dac˘a exist˘a. ˆIn cadrul celei de-a doua variante de calcul (vezi algoritmul 64), se ˆınlocuiesc operat¸iile min cu ∨ (sau logic) ¸si respectiv + cu ∧ (si logic). dki,j = 1 dac˘a exist˘a un drum de la xi la xj ˆın graful G avˆand vˆarfurile intermediare numai din mult¸imea {x1 , . . . , xk }. ( 1 , dac˘a i = j sau (xi , xj ) ∈ E (8.3) dki,j = 0 , dac˘a i 6= j ¸si (xi , xj ) ∈ /E k−1 k−1 dki,j = dk−1 i,j ∨ (di,k ∧ dk,j )
Exemplul 8.5 Fie graful din figura 8.2, din care am eliminat arcul (1, 2). Matricea costurilor asociat˘a acestui graf modificat este: 0 ∞ ∞ 21 ∞ ∞ 0 8 ∞ 4 3 ∞ 0 12 ∞ C= ∞ ∞ 9 0 ∞ ∞ ∞ 3 ∞ 0 169
Algoritm 64 Algoritm de calcul a ˆınchiderii tranzitive 1: procedure InchidereTranzitiva(n, C; D) 2: for i ← 1, n do 3: for j ← 1, n do 4: if (xi = xj ) ∨ (ci,j 6= +∞) then 5: d0i,j ← 1 6: else 7: d0i,j ← 0 8: end if 9: end for 10: end for 11: for k ← 1, n do 12: for i ← 1, n do 13: for j ← 1, n do k−1 k−1 14: dki,j ← dk−1 i,j ∨ (di,k ∧ dk,j ) 15: end for 16: end for 17: end for 18: end procedure
D0
D2
D4
1 0 1 0 0
0 1 0 0 0
0 1 1 1 1
1 0 1 1 0
0 1 0 0 1
1 0 1 0 0
0 1 0 0 0
0 1 1 1 1
1 0 1 1 0
0 1 0 0 1
1 1 1 1 1
0 1 0 0 0
1 1 1 1 1
1 1 1 1 1
0 1 0 0 1
= = =
8.3
D1
D3
D5
1 0 1 0 0
0 1 0 0 0
0 1 1 1 1
1 0 1 1 0
0 1 0 0 1
1 1 1 1 1
0 1 0 0 0
0 1 1 1 1
1 1 1 1 1
0 1 0 0 1
1 1 1 1 1
0 1 0 0 0
1 1 1 1 1
1 1 1 1 1
0 1 0 0 1
= = =
Exercit¸ii
1. Cutii n–dimensionale S˘a consider˘am o cutie n–dimensional˘a, dat˘a prin lungimea fiec˘arei laturi (o cutie bidimensional˘a este un dreptunghi, o cutie tridimensional˘a este un paralelipiped, etc.). Problema cere analizarea unui grup de k cutii n–dimensionale: trebuie g˘asit˘a o secvent¸˘a de lungime maxim˘a (b1 , b2 , . . .) din grupul de k cutii astfel ˆıncˆat fiecare cutie bi s˘a poat˘a fi introdus˘a ˆın cutia bi+1 . Cutia D = (d1 , d2, . . . , dn ) poate fi introdusa ˆın cutia E = (e1 , e2 , . . . , en ) numai dac˘a laturile cutiei D pot fi combinate ˆın a¸sa fel cu laturile cutiei E, astfel ˆıncˆat lungimea 170
fiec˘arei laturi a cutiei D s˘a nu dep˘a¸seasc˘a lungimea laturii corespunz˘atoare din cutia E. De exemplu cutia (2, 6) poate fi introdus˘a ˆın cutia (7, 3). Cutiile egale pot fi introduse una ˆıntr–alta. M˘asurile laturilor sunt numere reale mai mici sau egale cu 1000. Num˘arul de cutii nu dep˘a¸seste 100, iar num˘arul de dimensiuni nu dep˘a¸se¸ste 20. (Tuymaada, 1997)
2. Suntet¸i angajat la o companie ce asigur˘a servicii de transport m˘arfuri en-gross. Client¸ii au magazine ˆın ora¸se din toat˘a ¸tara ¸si ei sunt aprovizionat¸i din depozitele locale ale companiei. Pentru o aprovizionare optim˘a, compania vrea s˘a investeasca ˆın construirea unui depozit central din care s˘a se aprovizioneze toate zonele; va fi deci necesar ca acest depozit s˘a fie plasat ˆıntr-unul din ora¸se ˆın a¸sa fel ˆıncˆat timpul total de livrare din acest depozit ˆın toate ora¸sele s˘a fie minim. Camioanele companiei transport˘a marfa la depozitele locale ¸si se ˆıntorc la depozitul central. Timpul de livrare este format din timpul necesar parcurgerii drumului de la depozitul central la ora¸s ¸si ˆınapoi (se presupune c˘a ¸soferul va urma de fiecare dat˘a calea cea mai rapid˘a, iar ˆın fiecare ora¸s depozitul local se afl˘a amplasat la intrare) ¸si timpul de desc˘arcare al camionului la destinat¸ie, ce este de exact 30 minute. Drumurile ce leag˘a ora¸sele sunt de aceea¸si calitate, dar pot exista drumuri ce iau mai mult timp ˆıntr-un sens decˆat ˆın sens invers. Pot fi, de asemenea, drumuri cu sens unic. Pentru a simplifica modelul, s-a stabilit pentru fiecare ora¸s o list˘a cu toate drumurile ce pleac˘a din ora¸s spre celelalte ora¸se ¸si cˆat timp ia parcurgerea fiec˘arui drum. (Concurs ACM Zona Pacificul de Sud)
3. Se d˘a ret¸eaua hidrografic˘a a unei ¸t˘ari constituit˘a din mult¸imea rˆaurilor ¸si afluent¸ii lor. Cele N rˆauri (2 ≤ n ≤ 1000) sunt numerotate de la 1 la N. Leg˘atura dintre un rˆau v ¸si un afluent al s˘au u este specificat˘a prin perechea (u, v). Pentru a se putea face estim˘ari cu privire la potent¸ialul risc de inundai¸e pe cursul unui rˆau trebuie s˘a se calculeze debitul fiec˘arui rˆau ˆın parte. Debitul unui izvor se define¸ste ca fiind cantitatea de ap˘a ce trece prin sect¸iunea izvorului ˆın unitatea de timp. Debitul rˆaului u la v˘arsare va fi egal cu debitul izvorului rˆaului u plus suma debitelor afluent¸ilor la v˘arsare ˆın rˆaul u. Se cere s˘a se realizeze un algoritm care s˘a calculeze debitul la v˘arsare al fiec˘arui rˆau. 4. S˘a se determine drumul de lungime minim˘a necesar deplas˘arii unui cal pe o tabl˘a de ¸sah (avˆand dimensiunile 8 × 8) de la un punct de plecare la unui de sosire, ¸stiind c˘a pe tabl˘a exist˘a ¸si g˘auri. Calul poate fi mutat numai ˆın c˘asut¸ele f˘ar˘a g˘auri. (ACM, Eastern Europe, 1994)
5. Un traduc˘ator g˘ase¸ste o carte scris˘a cu litere latine, ˆın care ˆıns˘a ordinea literelor din alfabet este schimbat˘a. La sfˆar¸situl c˘art¸ii se afl˘a un index de cuvinte complet ¸si necontradictoriu. Se cere s˘a se determine ordinea literelor din noul alfabet. (ONI, 1991)
6. Profesorul Heif realizeaz˘a ni¸ste experimente cu o specie de albine din America de Sud pe care le–a descoperit ˆın timpul unei expedit¸ii ˆın jungla Amazonului. Mierea produs˘a 171
de aceste albine este superioar˘a din punct de vedere calitativ mierii produse de albinele din Europa sau din America de Nord. Din p˘acate, aceste albine nu se ˆınmult¸esc ˆın captivitate. Profesorul Heiff crede c˘a pozit¸iile larvelor (albine lucr˘atoare, regina) din fagure depind de condit¸iile de mediu, care sunt diferite ˆın laborator fat¸˘a de p˘adurea tropical˘a. Ca un prim pas pentru a-¸si verifica teoria, profesorul Heiff dore¸ste s˘a calculeze diferent¸a dintre pozit¸iile larvelor. Pentru aceasta el m˘asoar˘a distant¸a dintre celulele fagurelui ˆın care sunt plasate larvele. El a numerotat celulele alegˆand ˆın mod arbitrat una ¸si numerotˆand–o cu 1, apoi celelalte celule r˘amase sunt numerotate circular, ˆın sensul acelor de ceas, ca ˆın figur˘a. __ __ __ __ __/ \__/ \__/ \__/ \__ __/ \__/ \__/53\__/ \__/ \__ / \__/ \__/52\__/54\__/ \__/ \ \__/ \__/51\__/31\__/55\__/ \__/ / \__/50\__/30\__/32\__/56\__/ \ \__/49\__/29\__/15\__/33\__/57\__/ / \__/28\__/14\__/16\__/34\__/ \ \__/48\__/13\__/ 5\__/17\__/58\__/ /..\__/27\__/ 4\__/ 6\__/35\__/ \ \__/47\__/12\__/ 1\__/18\__/59\__/ /..\__/26\__/ 3\__/ 7\__/36\__/ \ \__/46\__/11\__/ 2\__/19\__/60\__/ /..\__/25\__/10\__/ 8\__/37\__/ \ \__/45\__/24\__/ 9\__/20\__/61\__/ /..\__/44\__/23\__/21\__/38\__/ \ \__/70\__/43\__/22\__/39\__/62\__/ / \__/69\__/42\__/40\__/63\__/ \ \__/ \__/68\__/41\__/64\__/ \__/ / \__/ \__/67\__/65\__/ \__/ \ \__/ \__/ \__/66\__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/
De exemplu, dou˘a larve pozit¸ionate ˆın celulele 19 ¸si 30 se afl˘a la distant¸a de 5 celule. Unul din drumurile minime ce unesc cele dou˘a celule trece prin celulele 19-7-6-5-15-30. S˘a se scrie un algoritm ce calculeaz˘a distant¸a minim˘a dintre oricare dou˘a celule. Datele de intrare constau din dou˘a numere naturale u ¸si v (u, v ≤ 10.000) reprezentˆand num˘arul celulelor ˆıntre care se dore¸ste s˘a se determine distant¸a minim˘a. (ACM Final, 1999)
7. ˆIn fiecare noapte un print¸ intr˘a ˆın subsolurile unui castel unde locuie¸ste o print¸es˘a. Drumul c˘atre print¸es˘a este ca un labirint. Sarcina print¸ului este s˘a se gr˘abeasc˘a ¸si s˘a g˘aseasc˘a drumul prin labirint c˘atre print¸es˘a, deoarece trebuie s˘a se ˆıntoarc˘a ˆın zori. Acesta are unelte potrivite cu ajutorul c˘arora poate s˘a sparg˘a numai un singur perete pentru a–¸si scurta drumul c˘atre destinat¸ie. (a) g˘asit¸i lungimea celui mai scurt drum din labirint din locul ˆın care print¸ul intr˘a ˆın labirint pˆan˘a ˆın locul ˆın care tr˘aie¸ste print¸esa; 172
(b) g˘asit¸i lungimea celui mai scurt drum dac˘a se sparge un perete. Castelul este de form˘a dreptunghiular˘a cu m × n camere (1 ≤ n, m ≤ 100). Fiecare camer˘a poate avea peret¸i spre E, S, V ¸si N codificat¸i cu un num˘ar ˆıntre 1 ¸si 14: 1, 2, 4, 8. Se ¸stie c˘a nu exist˘a camere f˘ar˘a nici un perete (codul > 0) ¸si nici camere care s˘a aib˘a peret¸i ˆın toate direct¸iile (codul < 15). Lungimea celui mai scurt drum se define¸ste ca fiind num˘arul de camere dintre surs˘a ¸si destinat¸ie inclusiv. Datele de intrare constau din M linii ce cont¸in fiecare N numere ˆıntregi reprezentˆand codificarea peret¸ilor din fiecare camer˘a, urmate se coordonatele print¸ului ¸si ale print¸esei. De exemplu, pentru datele de intrare 5 8 14 10 10 10 10 10 10 9 12 10 10 10 10 10 10 3 5 14 9 14 8 11 14 9 4 10 2 8 3 12 10 1 6 11 14 2 10 2 10 3 1 1 5 8
avem rezultatul 26 ¸si 12. (ACM Bucure¸sti, 1999)
173
Capitolul 9 Fluxuri ˆın ret¸ele de transport Problema studierii fluxului ˆın ret¸ele de transport ˆı¸si are originea ˆın analiza unor probleme de transport [108]. Un graf orientat poate modela procesul de transport dintre un produc˘ator ¸si un consumator prin intermediul unei ret¸ele de transport. Ceea ce se trimite pe un drum nu poate dep˘a¸si capacitatea sa de transport. La destinat¸ie nu poate s˘a ajung˘a o cantitate mai mare decˆat cea care a fost realizat˘a de c˘atre produc˘ator. ˆIn jurul nostru exist˘a foarte multe ret¸ele cum ar fi ret¸eaua electric˘a, ret¸eaua de ap˘a, ret¸eaua de drumuri, ret¸eaua de leg˘aturi telefonice, ce au devenit elemente indispensabile ale modului de viat¸˘a actual. O ret¸ea de transport poate modela curgerea unui lichid ˆıntr-o ret¸ea de conducte, deplasarea curentului prin ret¸ele electrice, transportul de m˘arfuri de-a lungul unei ret¸ele de drumuri, etc. Dou˘a dintre lucr˘arile considerate a avea un caracter de pionierat ˆın acest domeniu ˆı¸si bazeaz˘a studiul de caz pe ret¸eaua feroviar˘a existent˘a ˆın fosta U.R.S.S. [108]. A¸sa cum oberv˘a ¸si autorii lui [63] o abordare a acestei probleme folosea algoritmi avˆand la baz˘a programarea liniar˘a, mai exact metoda simplex [33], ˆıns˘a algoritmii dezvoltat¸i independent de aceast˘a metod˘a au condus la rezultate mai bune ˆın ceea ce prive¸ste complexitatea timp. Prima formulare a problemei determin˘arii fluxului maxim ˆıntr-o ret¸ea de transport a fost f˘acut˘a de T. E. Harris ˆın [70]: Consider a rail network connecting two cities by way of a number of intermediate cities, where each link of the network has a number assigned to it representing its capacity. Assuming a steady state condition, find a maximal flow from one given city to the other. 1 Exist˘a dou˘a probleme celebre care s-a demonstrat matematic a fi duale una alteia: problema fluxului maxim ˆıntr-o ret¸ea de transport ¸si problema determin˘ arii t˘ aieturii de capacitate minim˘a.
9.1
Ret¸ea de transport. Flux. T˘ aietur˘ a
Definit¸ia 9.1 Se nume¸ste ret¸ea de transport o secvent¸˘ a < G, c, s, t > ce prezint˘ a urm˘atoarele propriet˘a¸ti: 1
Luat¸i ˆın considerare o ret¸ea de cale ferat˘a ce leag˘a dou˘a ora¸se prin intermediul unui num˘ ar de ora¸se intermediare, unde fiecare leg˘atur˘a are atribuit un num˘ar ce reprezint˘a capacitatea acesteia. Presupunˆ and c˘a sistemul se afl˘a ˆıntr-o stare de echilibru, se cere s˘a se determine un flux maxim de la un ora¸s specificat la altul.
174
1. G = (V, E) este un graf orientat, unde fiec˘ arui arc (u, v) ∈ E ˆıi este asociat˘ a o valoare pozitiv˘a denumit˘a capacitate, c(u, v) ≥ 0; 2. exist˘a dou˘a noduri speciale, s, t ∈ V (s - surs˘ a, t - destinat¸ie);
3. ∀u ∈ V \ {s, t}, nodul u se g˘ase¸ste pe cel put¸in un drum de la surs˘ a la destinat¸ie. Observat¸ia 9.1 1. Dac˘a (u, v) ∈ / E vom considera c˘ a valoarea capacit˘ a¸tii arcului este 0 (c(u, v) = 0). 2. Graful este conex ¸si |E| ≥ |V | − 1. Definit¸ia 9.2 Pentru o ret¸ea de transport < G, c, s, t > definim fluxul asociat ret¸elei de transport drept o funct¸ie f : V × V → R ce satisface urm˘ atoarele condit¸ii: 1. ∀u, v ∈ V , avem 0 ≤ f (u, v) ≤ c(u, v) (restrict¸ie de capacitate); 2. ∀u, v ∈ V , avem f (u, v) = −f (v, u) (antisimetrie); P 3. ∀u ∈ V \ {s, t} avem v∈V f (u, v) = 0 (conservarea fluxului).
Definit¸ia 9.3 Se spune c˘a arcul (u, v) este saturat dac˘ a f (u, v) = c(u, v). Dac˘a f (u, v) > 0 se spune c˘a fluxul p˘ ar˘ ase¸ste nodul u, iar dac˘a pentru arcul (u, v) avem f (u, v) < 0 (echivalent cu f (v, u) > 0) spunem c˘a fluxul intr˘ a ˆın nodul u. Observat¸ia 9.2 f (u, u) = 0, ∀u ∈ V . Observat¸ia 9.3 Pentru dou˘a submult¸imi oarecare A, B (A, B ⊆ V ), se define¸ste f (A, B) astfel: XX f (A, B) = f (u, v). u∈A v∈B
Condit¸ia de conservare a fluxului ˆıntr-un nod poate fi descris˘ a astfel: f (v, V ) = 0. Propozit¸ia 9.4 1. ∀A ⊆ V , f (A, A) = 0;
2. ∀A, B, C ⊆ V avem:
f (A ∪ B, C) = f (A, C) + f (B, C) − f (A ∩ B, C) f (A, B ∪ C) = f (A, B) + f (A, C) − f (A, B ∩ C);
(9.1)
3. ∀A, B, C ∈ V , B ⊂ A f (A \ B, C) = f (A, C) − f (B, C) f (C, A \ B) = f (C, A) − f (C, B); 4. ∀A, B ⊆ V , avem f (A, B) = −f (B, A); 5. ∀A ⊆ V , f (A, ∅) = f (∅, A) = 0.
175
(9.2)
Definim valoarea fluxului f astfel: |f | =
X
f (s, v) . Drept urmare, valoarea fluxului
v∈V
ˆın exces ce p˘ar˘ase¸ste nodul surs˘a s este: X |f | = f (s, v) − v∈V,f (s,v)>0
X
f (u, s)
(9.3)
u∈V,f (u,s)>0
P P Se poate demonstra c˘a |f | = u∈V,f (u,t)>0 f (u, t) − v∈V,f (t,v)>0 f (t, v) (valoarea fluxului f este diferent¸a dintre suma fluxurilor ce intr˘a ˆın destinat¸ie minus suma fluxurilor ce p˘ar˘asesc destinat¸ia). Din regula de conservare a fluxului rezult˘a c˘a: X X f (u, v) = f (v, u), ∀u ∈ V \ {s, t} (9.4) v∈V,f (u,v)>0
v∈V,f (v,u)>0
Cu alte cuvinte, suma fluxurilor ce intr˘ a ˆıntr-un nod u este egal˘ a cu suma fluxurilor ce p˘ar˘asesc acela¸si nod u. ˆIntr-o ret¸ea de transport avem urm˘atoarea relat¸ie: X X X ( f (u, v) − f (w, u)) = 0. (9.5) u∈V v∈V,f (u,v)>0
Pe de alt˘a parte X X ( f (u, v) − u∈V v∈V,f (u,v)>0
X
w∈V,f (w,u)>0
f (w, u))
w∈V,f (w,u)>0
Astfel rezult˘a c˘a |f | =
P P = ( v∈V,f (s,v)>0 f (s, v) − u∈V,f (u,s)>0 f (u, s)) + (
X
P
f (t, v) −
P
u∈V,f (u,t)>0 f (u, t)) P P = |f | + ( v∈V,f (t,v)>0 f (t, v) − u∈V,f (u,t)>0 f (u, t))
u∈V,f (u,t)>0
f (u, t) −
v∈V,f (t,v)>0
X
f (t, v).
(9.6)
v∈V,f (t,v)>0
Definit¸ia 9.4 O < s, t > - t˘aietur˘a este o pereche de mult¸imi disjuncte (A, B) din V (A∩B = ∅, A ∪ B = V , A, B ⊆ V ) cu proprietatea c˘ a s ∈ A ¸si t ∈ B. Capacitatea unei < s, t > - t˘aieturi este suma capacit˘a¸tilor arcelor ce au o extremitate ˆın mult¸imea A ¸si cealalt˘a extremitate ˆın mult¸imea B: X c(A, B) = c(u, v) (9.7) u∈A,v∈B
Se define¸ste fluxul de-a lungul t˘aieturii (A, B) ca fiind: XX f (A, B) = f (u, v)
(9.8)
u∈A v∈V
not
|f | = f ({s}, V \ {s}) =
X
f (s, v) (fluxul net ce p˘ar˘ase¸ste nodul surs˘a)
(9.9)
v∈V
Problema fluxului maxim presupune determinarea unui flux de valoare maxim˘a de la s la t pentru ret¸eaua de transport < G, c, s, t > [47], [48]. 176
Lema 9.5 Pentru < G, c, s, t > ret¸ea de transport, (A, B) o < s, t >-t˘ aietur˘ a ¸si f un flux ˆın ret¸eaua de transport, avem |f | = f (A, B) (valoarea fluxului ˆın ret¸eaua de transport este egal˘a cu valoarea fluxului de-a lungul t˘ aieturii). Demonstrat¸ie: S˘a ne reamintim c˘a dac˘a (A, B) este o < s, t >-t˘aietur˘a avem A ∩ B = ∅, A ∪ B = V . Atunci din propozit¸ia 9.4 obt¸inem f (A, V ) = f (A, A ∪ B) = f (A, A) + f (A, B) − f (A, A ∩ B) = f (A, B)
(9.10)
deoarece f (A, A) = 0 ¸si f (A, A ∩ B) = f (A, ∅) = 0. Astfel pentru o < s, t >-t˘aietur˘a avem f (A, B) = f (A, V ). Consider˘am identitatea A = (A \ {s}) ∪ {s}. f (A, V ) = f (A \ {s}, V ) + f ({s}, V ) − f (∅, V ) = f (A \ {s}, V ) + f ({s}, V )
(9.11)
Pe de alt˘a parte avem identitatea: f (A \ {s}, V ) = 0.
(9.12)
Din ultimele trei relat¸ii concluzion˘am faptul c˘a: 9.10
9.11
9.12
f (A, B) = f (A, V ) = f ({s}, V ) + f (A \ {s}, V ) = f ({s}, V ) = |f |.
(9.13)
Corolarul 9.6 Pentru un flux f oarecare ˆın ret¸eaua de transport < G, c, s, t > avem inegalitatea: |f | ≤ c(A, B) Demonstrat¸ie: Din Lema 9.5 avem: XX def X X def def L9.5 |f | = f (A, B) = f (u, v) ≤ c(u, v) = c(A, B). u∈A v∈B
(9.14)
u∈A v∈B
T˘aietura minim˘a reprezint˘a modalitatea cea mai eficient˘a / simpl˘a de a ˆıntrerupe fluxul (curgerea) de la s la t. Problema t˘aieturii minime se refer˘a la a determina o < s, t > - t˘aietur˘a a c˘arei capacitate s˘a fie maxim˘a. Exemplul 9.7 ˆIn figura 9.1 este prezentat un exemplu de ret¸ea de transport ¸si un flux f . Ca o ilustrare a celor discutate vom calcula suma fluxurilor pentru nodul 1: X f (1, v) = f (1, s) + f (1, 2) + f (1, 3) + f (1, 4) + f (1, t) v∈V
= −f (s, 1) + (−1) + 12 + 0 + 0 = −11 − 1 + 12 = 0
(9.15)
f (1, 2) = 0 − 1 = −1
Sau X
f (4, v) = f (4, s) + f (4, 1) + f (4, 2) + f (4, 3) + f (4, t)
v∈V
= 0 + 0 + (−f (2, 4)) + 7 + 4 = −11 + 7 + 4 = 0 177
(9.16)
Fig. 9.1: Un exemplu de ret¸ea de transport
9.2
Graf rezidual. Drum de ameliorare. Flux maxim– t˘ aietur˘ a minim˘ a
Definit¸ia 9.5 Definim capacitatea rezidual˘ a a unui arc (u, v) apart¸inˆ and grafului G astfel: cR (u, v) = c(u, v) − f (u, v) . (9.17) Definit¸ia 9.6 Graful rezidual[47] asociat cu graful G = (V, E) ¸si capacitatea c, este graful GR = (V, ER ) ¸si capacitate cR , unde ER se define¸ste astfel: ER = {(u, v) ∈ V × V |cR (u, v) > 0}. Observat¸ia 9.8 ˆIntre dou˘a noduri u ¸si v din graful G vom avea cel mult dou˘ a arce ˆın graful rezidual GR : 1. dac˘a (u, v) ∈ E ¸si f (u, v) < c(u, v) atunci exist˘ a arcul (u, v) ˆın graful rezidual ((u, v) ∈ ER ) ¸si cR (u, v) = c(u, v) − f (u, v) (cR (u, v) > 0);
2. dac˘a (u, v) ∈ E ¸si f (u, v) > 0 atunci exist˘ a arcul (v, u) ˆın graful rezidual ((v, u) ∈ ER ) ¸si cR (v, u) = f (u, v). Altfel spus, ˆın graful rezidual GR pot s˘a apar˘a arce noi, care nu existau ˆın graful init¸ial G. Un alt element ce merit˘a subliniat se refer˘a la faptul c˘a dac˘a ˆıntre dou˘a noduri nu exist˘a nici un arc ˆın graful init¸ial G, atunci nu va exista nici un arc ˆıntre cele dou˘a noduri nici ˆın graful rezidual GR . Prin urmare, num˘arul total de arce din graful rezidual GR este cel mult de dou˘a ori mai mare decˆat num˘arul arcelor din graful G. Lema 9.9 Fie < G, c, s, t > o ret¸ea de transport ¸si f un flux ˆın aceast˘ a ret¸ea de transport. Dac˘a fR este un flux ˆın ret¸eaua de transport < GR , cR , s, t > (GR este graful rezidual asociat cu G), atunci f + fR este un flux ˆın < G, c, s, t >, iar |f + fR | = |f | + |fR |. Demonstrat¸ie: Pentru a ar˘ata c˘a f + fR este un flux ˆın ret¸eaua de transport < G, c, s, t > trebuie s˘a verific˘am dac˘a condit¸iile din definit¸ia 9.2 sunt ˆındeplinite: 1. restrict¸ie de capacitate Avem fR (u, v) ≤ cR (u, v), ∀u, v ∈ V . (f + fR )(u, v) = f (u, v) + fR (u, v) ≤ f (u, v) + cR (u, v) = f (u, v) + (c(u, v) − f (u, v)) = c(u, v)(9.18) 178
2. antisimetrie (f + fR )(u, v) = f (u, v) + fR (u, v) = −f (v, u) − fR (v, u) = −(f (v, u) + fR (v, u)) = −(f + fR )(v, u) 3. conservarea fluxului X X (f + fR )(u, v) = (f (u, v) + fR (u, v)) v∈V v∈V X X = f (u, v) + fR (u, v) = 0 + 0 = 0 v∈V
(9.20)
v∈V
S˘a demonstr˘am c˘a |f + fR | = |f | + |fR |: X X |f + fR | = (f + fR )(s, v) = (f (s, v) + fR (s, v)) v∈V v∈V X X = f (s, v) + fR (s, v) = |f | + |fR |. v∈V
(9.19)
(9.21)
v∈V
not
Corolarul 9.10 Dac˘a f ′ = f +fR , unde fR este un flux ˆın ret¸eaua de transport < GR , cR , s, t > a grafului rezidual, atunci f ′ este un flux ˆın < G, c, s, t > ¸si |f ′| = |f | + |fR | > |f |. Demonstrat¸ia acestui corolar rezult˘a imediat din lema 9.9. Lema 9.11 Fie f un flux ˆın < G, c, s, t > ¸si GR graful rezidual asociat cu G. Atunci avem: 1. funct¸ia fR este un flux maxim ˆın < GR , cR , s, t > dac˘ a f + fR este un flux maxim ˆın < G, c, s, t >; 2. valoarea funct¸iei este aditiv˘a: |f + fR | = |f | + |fR |, |f − fR | = |f | − |fR |;
3. dac˘a f este un flux oarecare ¸si f ∗ este fluxul maxim ˆın < G, c, s, t >, atunci valoarea fluxului maxim ˆın < GR , cR , s, t > este |f ∗ | − |f |. Definit¸ia 9.7 Fiind dat˘a o ret¸ea de transport < G, c, s, t > ¸si un flux f ˆın aceast˘a ret¸ea, atunci o cale de cre¸ stere d (drum de ameliorare, drum de augmentare) este un drum de la s la t ˆın graful rezidual GR . Numim capacitate rezidual˘ a a lui d, cantitatea maxim˘ a a fluxului ce poate fi transportat de-a lungul drumului de ameliorare d: cR (d) = min{cR (u, v)|(u, v) arc pe drumul d}
(9.22)
Observat¸ia 9.12 Unui drum de ameliorare ˆın graful rezidual GR ˆıi corespunde un lant¸ ˆın graful G. Definit¸ia 9.8 Un arc (u, v) se nume¸ste arc critic dac˘ a face parte dintr-un drum de ameliorare d ¸si dac˘a capacitatea sa rezidual˘ a este egal˘ a cu capacitatea rezidual˘ a a drumului de ameliorare (c(u, v) = cR (d)). Teorema 9.13 (flux maxim - t˘aietur˘ a minim˘ a) [46], [48], [49] Fie o ret¸ea de transport < G, c, s, t > ¸si f un flux ˆın aceast˘a ret¸ea. Urm˘ atoarele afirmat¸ii sunt echivalente: 179
1. f este un flux maxim ˆın ret¸eaua de transport; 2. nu exist˘a nici o cale de cre¸stere (drum de ameliorare) ˆın ret¸eaua de transport < G, c, s, t >; 3. exist˘a o < s, t >-t˘aietur˘a a lui G pentru care |f | = c(A, B) (exist˘ a o < s, t >-t˘ aietur˘a pentru care valoarea fluxului maxim este egal˘ a cu capacitatea t˘ aieturii). Demonstrat¸ie: Vom demonstra aceast˘a teorem˘a abordˆand urm˘atoarea serie de implicat¸ii: 1) ⇒ 2) ⇒ 3) ⇒ 1). 1) ⇒ 2) S˘a presupunem prin reducere la absurd c˘a exist˘a un drum de ameliorare ˆın ret¸eaua de transport < G, c, s, t > pentru un flux maxim |f |. Dac˘a exist˘a un drum de ameliorare atunci exist˘a un flux nenul fp ˆın ret¸eaua < GR , cR , s, t >. Fie f ′ fluxul construit astfel: f ′ = f + fp . Din corolarul 9.10 obt¸inem c˘a f ′ este un flux ˆın ret¸eaua de transport < G, c, s, t > ¸si |f ′| > |f |. Am determinat astfel un flux f ′ ˆın < G, c, s, t > a c˘arui valoare este mai mare decˆat cea a fluxului f . Contradict¸ie cu faptul c˘a f este un flux maximal ˆın ret¸eaua de transport < G, c, s, t >. 2) ⇒ 3) Dac˘a nu exist˘a nici un drum de ameliorare ˆın < G, c, s, t > atunci nu exist˘a nici un drum de la s la t ˆın graful rezidual GR (GR = (V, ER )). S˘a not˘am S = {v ∈ V |∃ un drum de la s la t ˆın GR } ¸si T = V \ S. Deoarece nu exist˘a nici un drum de la s la t ˆın GR ⇒ t ∈ T . De asemenea avem c˘a s ∈ S, S ∩ T = ∅, S ∪ T = V . Prin urmare S, T este o < s, t >-t˘aietur˘a. Din cauza faptului c˘a nu exist˘a nici nici un drum de la s la t ˆın GR ⇒ nu exist˘a arce ˆın GR care s˘a aib˘a o extremitate ˆın S ¸si cealalt˘a extremitate ˆın T ⇒ ∀u ∈ S, v ∈ T , dac˘a ∃(u, v) ∈ E atunci f (u, v) = c(u, v). Aplicˆand Lema 9.5 obt¸inem c˘a |f | = f (S, T ) = c(S, T ). 3) ⇒ 1) Din Corolarul 9.6 avem |f | ≤ c(A, B), ∀(A, B) < s, t >-t˘aietur˘a a lui G. Deoarece |f | = c(A, B) rezult˘a c˘a f este fluxul maxim ce se poate obt¸ine ˆın ret¸eaua de transport < G, c, s, t >. Valoarea maxim˘a a unui flux ˆıntr–o ret¸ea de transport < G, c, s, t > este egal˘a cu valoarea capacit˘a¸tii t˘aieturii minime pentru aceea¸si ret¸ea de transport. Corolarul 9.14 Fie f un flux ˆıntr-o ret¸ea de transport < G, c, s, t >. Not˘ am cu Af mult¸imea nodurilor ce apart¸in unui drum de ameliorare. Fie Bf = V \ Af . Atunci f este un flux maximal dac˘a ¸si numai dac˘a t ∈ Bf . Mai mult, Af , Bf reprezint˘a o < s, t >-t˘ aietur˘ a minim˘ a (de cost minim): |f | = c(Af , Bf ).
9.3
Metoda Ford-Fulkerson
Pe baza rezultatelor anterioare a fost elaborat un algoritm cunoscut sub numele de algoritmul lui Ford ¸si Fulkerson (vezi 65) [46], [48], [49]. Exemplul 9.15 Pentru ret¸eaua de transport din figura 9.1 construim ret¸eaua rezidual˘a din figura 9.2: cR (s, 1) = c(s, 1) − f (s, 1) = 16 − 11 = 5 cR (1, s) = c(1, s) − f (1, s) = 0 − (−11) = 11 180
Algoritm 65 Algoritmul Ford–Fulkerson (varianta simplificat˘a) 1: 2: 3: 4: 5:
se init¸ializeaz˘ a fluxul cu valoarea 0 while (∃ un drum de ameliorare d ˆın GR ) do m˘ are¸ste fluxul f de-a lungul drumului de ameliorare d end while return |f |
Fig. 9.2: Ret¸eaua rezidual˘a corespunz˘atoare ret¸elei de transport din figura 9.1
cR (1, 3) cR (3, 1) cR (4, 3) cR (3, 4) cR (2, 3) cR (3, 2) cR (2, 1) cR (1, 2)
= = = = = = = =
c(1, 3) − f (1, 3) = 12 − 12 = 0 c(3, 1) − f (3, 1) = 0 − (−f (1, 3)) = 12 c(4, 3) − f (4, 3) = 7 − 7 = 0 c(3, 4) − f (3, 4) = 0 − (−f (4, 3)) = f (4, 3) = 7 c(2, 3) − f (2, 3) = 0 − (−f (3, 2)) = f (3, 2) = 4 c(3, 2) − f (3, 2) = 9 − 4 = 5 c(2, 1) − f (2, 1) = 4 − 1 = 3 c(1, 2) − f (1, 2) = 10 − (0 − 1) = 11 | {z } f luxul net
Fie d = (s, 2, 3, t) o cale de cre¸stere (drum de ameriorare) ˆın graful din figura 9.2. Capacitatea rezidual˘a a acestui drum este: cR (d) = min{5, 4, 5} = 4 Vom m˘ari fluxul f pentru arcele din G ce sunt ˆıntˆalnite de–a lungul drumului de ameliorare d. Avem dou˘a tipuri de arce: • arc ˆınainte - arc al c˘arui sens corespunde cu sensul parcurgerii de la s la t; pentru un astfel de arc, (u, v) ∈ E, f (u, v) = f (u, v) + cR (d); • arc ˆınapoi - arcul are sens contrar sensului de parcurgere de la s la t: (u, v) ∈ E, f (u, v) = f (u, v) − cR (d). Astfel noul flux corespunz˘ator grafului din figura 9.1 ¸si drumului de augmentare d = (s, 2, 3, t) poate fi urm˘arit ˆın figura 9.3. Vom prezenta o variant˘a mai elaborat˘a a algoritmului Ford–Fulkerson. 181
Fig. 9.3: Flux ˆıntr-o ret¸ea de transport dup˘a o cre¸stere pe baza unui drum de ameliorare
Algoritm 66 Algoritmul Ford–Fulkerson (varianta a doua) 1: for (fiecare arc (u, v) ∈ E) do 2: f (u, v) ← 0, f (v, u) ← 0 3: end for 4: while (∃ un drum d de la s la t ˆın ret¸eaua rezidual˘ a < GR , cR , s, t >) do 5: cR (d) ← min{cR (u, v)|(u, v) arc pe drumul d} 6: for fiecare arc((u, v) ∈ E corespunz˘ ator lant¸ului d do 7:
f (u, v) ←
8: end for 9: end while 10: return |f |
f (u, v) + cR (d) f (u, v) − cR (d)
, dac˘ a (u, v) este arc de ˆınaintare pentru d , dac˘ a (u, v) este arc de ˆıntoarcere pentru d
Observat¸ia 9.16 Determinarea fluxului maxim ˆıntr-o ret¸ea de transport avˆ and capacit˘a¸tile arcelor numere rat¸ionale, precum ¸si valorile fluxului tot numere rat¸ionale, se reduce la determinarea unui flux maxim avˆand componentele fluxului cˆ at ¸si capacit˘ a¸tile arcelor numere naturale (aceast˘a reducere este posibil˘ a deoarece valorile rat¸ionale pot fi amplificate cu o valoare pozitiv˘ a, cel mai mic multiplu comun al numitorilor acestor numere). Complexitatea algoritmului ˆIn tabelul 9.1 [127] sunt prezentat¸i principalii algoritmi pentru determinarea fluxului maxim ˆıntr-o ret¸ea de transport, anul ˆın care au ap˘arut precum ¸si complexitatea lor. Astfel ace¸stia se pot compara din punct de vedere al complexit˘a¸tii teoretice ¸si se poate alege varianta potrivit˘a rezolv˘arii unor probleme concrete.
9.3.1
Algoritmul Ford-Fulkerson (variant˘ a)
Prezent˘am ˆın continuare o variant˘a a algoritmului Ford-Fulkerson: Pas 1. Se consider˘a un flux init¸ial f0 . De obicei se alege drept flux init¸ial fluxul nul (f0 (u, v) = 0, ∀u, v ∈ V, (u, v) ∈ E). Pas 2. Se construie¸ste un flux maxim: Pas 2.1. Se marcheaz˘a nodul s cu semnul ’+’. 182
Table 9.1: Principalii algoritmi pentru determinarea fluxului maxim ˆıntr-o ret¸ea de transport Autor(i) Ford & Fulkerson Dinic Edmonds & Karp Karzanov Cherkassky Malhotra, Kumar ¸si Maheswari Galil Galil & Naamad Sleator Shiloach & Vishkin Sleator & Tarjan Tarjan Goldberg Goldberg & Tarjan Ahuja & Orlin
Anul aparit¸iei 1956 [48] 1970 [39] 1972 [42] 1974 [80] 1976 [29] 1978 [94] 1980 [58] 1980 [59] 1980 [113] 1982 [111] 1983 [114] 1984 [120] 1985 [61] 1986 [62] 1989 [4]
Complexitate O(V EU) O(V 2 E) O(V E 2 ) O(V√3 ) O(V 2 E) O(V 3 ) 5 2 O(V 3 E 3 ) O(EV (log V )2 ) O(EV log V ) O(V 2 log V ) O(EV log V ) O(V 3 ) O(V 3 ) 2 O(EV log ( V ) E ) O(V E + V 2 log U)
Pas 2.2. Pentru un vˆarf u etichetat avem cazurile: i. dac˘a (u, v) ∈ E, v este neetichetat iar arcul (u, v) este nesaturat atunci se eticheteaz˘a v cu [+u]; ii. dac˘a (v, u) ∈ E, v este neetichetat iar fluxul corespunz˘ator arcului (v, u) este nenul atunci se eticheteaz˘a v cu [−u]; Se repet˘a pasul 2.2 atˆata timp cˆat este posibil (cˆat timp nu a fost etichetat nodul t sau se mai pot eticheta noduri noi). Pas 2.3. Dac˘a t nu a fost etichetat, atunci fluxul construit pˆan˘a la momentul actual este maxim ¸si procesul se opre¸ste. Pas 2.4. Dac˘a t a fost etichetat, atunci se identific˘a un drum d de la s la t. Not˘am cu U + mult¸imea arcelor (u, v) ∈ d unde v a fost etichetat cu ’+’ ¸si U − mult¸imea arcelor (u, v) ∈ d unde v a fost etichetat cu ’−’. ε0 = ε1 =
min {c(u, v) − f (u, v)}
(u,v)∈U +
min {f (u, v)}
(u,v)∈U −
ε∗ = min{ε0 , ε1 }
(9.23)
Valoarea fluxului de-a lungul fiec˘arui arc din d, devine f + ε∗ dac˘a arcul apart¸ine lui U + sau f − ε∗ dac˘a arcul apart¸ine lui U − . Exemplul 9.17 Fie ret¸eaua de transport din figura 9.4 ˆın care a fost considerat la ˆınceput fluxul nul. 183
Fig. 9.4: Flux ˆıntr-o ret¸ea de transport
Se eticheteaz˘a vˆarful s cu [+]. Deoarece arcele (s, 1) ¸si (s, 2) sunt nesaturate, vˆarfurile 1 ¸si 2 pot fi etichetate cu [+s]. ˆIn continuare, deoarece arcele (1, 3) ¸si (2, 4) sunt nesaturate vor fi etichetate nodurile 3 ¸si 4 cu [+1] respectiv [+2]. Arcul (3, 2) are fluxul nul ¸si astfel nodul 3 nu poate fi etichetat cu [−2]. Analog, se eticheteaz˘ a nodul t cu [+3] (vezi figura 9.4). Deoarece nodul destinat¸ie t a fost etichetat, se poate trage concluzia c˘ a fluxul curent nu este maxim. Trebuie subliniat faptul c˘a etichetarea nodurilor nu este unic˘ a, aceasta depinzˆ and de modul de parcurgerea al nodurilor grafului. Spre exemplu, nodul 3 poate fi etichetat ¸si cu [+4] ajungˆandu-se la el prin intermediul nodului 4 ¸si al arcului (4, 3). Pornind de la nodul t ¸si mergˆand ˆınapoi pe secvent¸a de etichetare, se identific˘ a un drum de la s la t, d1 = (s, 1, 3, t). ε∗ =
min {c(u, v) − f (u, v)}
(u,v)∈U +
= min{c(s, 1) − f (s, 1), c(1, 3) − f (1, 3), c(3, t) − f (3, t)} = min{16, 12, 20} = 12 Pe acest drum avem arcele (s, 1), (1, 3), (3, t) ∈ U + . Prin urmare valoarea fluxului de-a lungul acestor arce devine (vezi figura 9.5): f (s, 1) = f (s, 1) + ε∗ = 0 + 12 = 12, f (1, 3) = 0 + 12 = 12, f (3, t) = 0 + 12 = 12 ˆIn figura 9.5 poate fi urm˘arit˘a o nou˘ a etichetare a nodurilor grafului. Se marcheaz˘a nodul s cu [+]. Din nodul s prin intermediul arcelor nesaturate (s, 1) ¸si (s, 2) pot fi etichetate nodurile 1 ¸si 2 cu [+s]. Arcul (1, 3) nu mai poate fi utilizat ˆın procesul de etichetare deoarece este saturat (f (1, 3) = c(1, 3) = 12). Din nodul 1 mai avem arcul (1, 2) ˆıns˘ a nodul 2 a fost etichetat cu [+s]. Prin intermediul arcului (2, 4) vom eticheta nodul 4 cu [+2]. Arcul (3, 2) nu poate fi utilizat pentru etichetarea nodului 3 cu [−2] deoarece fluxul corespunz˘ ator are ˆın continuare valoarea 0. Din nodul 4 se eticheteaz˘a nodurile 3 ¸si t cu [+4] prin intermediul arcelor (4, 3) ¸si (4, t). Pornind de la nodul t ¸si mergˆand ˆınapoi pe secvent¸a de etichetare, se identific˘ a un drum de la s la t, d2 = (s, 2, 4, t). ε∗ =
min {c(u, v) − f (u, v)}
(u,v)∈U +
= min{c(s, 2) − f (s, 2), c(2, 4) − f (2, 4), c(4, t) − f (4, t)} = min{13, 14, 4} = 4 184
Fig. 9.5: Flux ˆıntr-o ret¸ea de transport
Arcele ˆıntˆalnite de-a lungul drumului d2 , (s, 1), (1, 3), (3, t), apart¸in mult¸imii U + . Prin urmare valoarea fluxului de-a lungul acestor arce devine (vezi figura 9.6): f (s, 2) = f (s, 2) + ε∗ = 0 + 4 = 4, f (2, 4) = 0 + 4 = 4, f (4, t) = 0 + 4 = 4
Fig. 9.6: Flux ˆıntr-o ret¸ea de transport
Cea de-a treia etap˘a de etichetare este ilustrat˘ a ˆın figura 9.6: nodul s se eticheteaz˘a cu [+]. Urmeaz˘a apoi nodurile 1 ¸si 2 cu [+s] fiecare, ¸si nodul 4 cu [+2]. De aici mai departe nodul 3 se eticheteaz˘a cu [+4] iar t cu [+3]. Pentru aceast˘a etichetare se identific˘ a drumul d3 de la s la t: d3 = (s, 2, 4, 3, t). ε∗ =
min {c(u, v) − f (u, v)}
(u,v)∈U +
= min{c(s, 2) − f (s, 2), c(2, 4) − f (2, 4), c(4, 3) − f (4, 3), c(3, t) − f (3, t)} = min{13 − 4, 14 − 4, 7 − 0, 20 − 12} = 7 Toate arcele din care este alc˘atuit drumul d3 apart¸in mult¸mii U + . Valoarea fluxul calculat pentru fiecare arc ˆın parte este (vezi figura 9.7): f (s, 2) = f (s, 2)+ε∗ = 4+7 = 11, f (2, 4) = 4+7 = 11, f (4, 3) = 0+7 = 7, f (3, t) = 12+7 = 19 185
Fig. 9.7: Flux maxim ˆıntr-o ret¸ea de transport
Analizˆand ret¸eaua de transport ¸si fluxul din figura 9.7 se observ˘ a c˘ a nu se mai poate eticheta nodul t pornind din s datorit˘ a prezent¸ei arcelor saturate (1, 3), (4, 3), (4, t) precum ¸si a arcului (3, 2) ce are ˆıns˘a valoarea fluxului nul˘ a. ˆIn aceast˘a situat¸ie procesul iterativ de etichetare se opre¸ste conform Pasului 2.3 din algoritmul anterior. Fluxul maxim determinat ˆın ret¸eaua de transport este: |f | = f (s, 1) + f (s, 2) = 11 + 12 = 23 sau |f | = f (3, t) + f (4, t) = 19 + 4 = 23.
9.4
Exercit¸ii
1. Aplicˆadu-se algoritmul Ford-Fulkerson s˘a se determine fluxul maxim ˆın ret¸elele de transport din figura 9.1. Nodul 0 este nodul surs˘ a iar nodul 5 respectiv nodul 6 este nodul destinat¸ie.
186
4
1
4
1 3
10
3
5
10
3 8
0
2
8
10
10
2
9
1
0
5
6
9
9
2
4
9
b) 4 1
1
10 15
4
4
3
a) 1
5
2
4
10
15
1 3
4 2
0
5
15
2 4
8 6
5
10
5 0
30
3
2 9 1
15 10
3
6
2
5
3
3
4
c)
d)
Fig. 9.8: Alte exemple de ret¸ele de transport
187
6
Appendix A Probleme. Algoritmi. Complexitate Vom considera o problem˘a computat¸ional˘ a ca fiind o aplicat¸ie P : I −→ O
(A.1)
unde I reprezint˘a mult¸imea intr˘arilor problemei, mult¸imea instant¸elor problemei, iar O reprezint˘a mult¸imea ie¸sirilor, r˘aspunsurilor, solut¸iilor. Aceast˘a problem˘a P pentru fiecare intrare i ∈ I ofer˘a o ie¸sire P(i) ∈ O. Definit¸ia A.1 Dac˘a O = {da, nu} atunci P se va numi problem˘ a de decizie, P(i) ∈ {da, nu} va fi r˘aspunsul la ˆıntrebarea pus˘ a de P, iar forma uzual˘ a de prezentare a pro-blemei va fi: P Intrare: i ∈ I. ˆ Intrebare: . . .. Exemplul A.1 S˘a consider˘am urm˘atoarea problem˘ a: NrCompus Intrare: n ∈ N, n ≥ 2. ˆ Intrebare: Exist˘ a p, q ∈ N, p, q ≥ 2 ¸si n = pq? Un alt tip de probleme ce apar ˆın mod frecvent sunt cele de c˘ autare: mult¸imea O cont¸ine pentru fiecare i ∈ I m˘acar o solut¸ie acceptabil˘a ˆın raport cu un anumit criteriu precizat, iar problema cere g˘asirea unei astfel de solut¸ii. O clas˘a special˘a de astfel de probleme este cea a problemelor de optimizare, ¸si care pot fi probleme de maximizare sau de minimizare. Exemplul A.2 Prezent˘am cˆateva exemple de astfel de probleme de optimizare: Drum Intrare: G un graf, a, b dou˘ a vˆ arfuri ale lui G. Ie¸ sire: P un drum ˆın G de la a la b (dac˘ a ∃). sau DrumMin Intrare: G un graf, a, b dou˘ a vˆ arfuri ale lui G, o funct¸ie de lungime a muchiilor lui G. Ie¸ sire: P ∗ un drum ˆın G de la a la b cu suma lungimilor muchiilor minim˘ a, printre toate drumurile de la a la b ˆın G. sau MaxCut Intrare: G graf, o funct¸ie de lungime a muchiilor lui G. Ie¸ sire: O bipartit¸ie (S, T ) a mult¸imii vˆ arfurilor grafului G cu suma lungimilor muchiilor dintre cele dou˘ a clase maxim˘ a.
188
Oric˘arei probleme de optimizare i se poate asocia o problem˘ a de decizie (care ne va da informat¸ii asupra dificult˘a¸tii ei computat¸ionale). Pentru cele dou˘a probleme de optimizare de mai sus, avem: DrumMin–D Intrare: G un graf, a, b vˆarfuri ale lui G, k ∈ N o funct¸ie de lungime a muchiilor lui G. ˆIntrebare: Exist˘a P drum ˆın G de la a la b cu suma lungimilor muchiilor ≤ k? sau MaxCut–D Intrare: G un graf, k ∈ N, o funct¸ie de lungime a muchiilor lui G. ˆIntrebare: Exist˘a (S, T ) bipartit¸ie a mult¸imii vˆarfurilor grafului G cu suma lungimilor muchiilor dintre cele dou˘a clase ≥ k? Vom considera ˆın continuare doar probleme de decizie. Pentru a fi rezolvate cu ajutorul calculatorului problemele sunt codificate. Fie Σ o mult¸ime finit˘a, fixat˘a, numit˘a alfabet, iar Σ∗ mult¸imea tuturor cuvintelor peste Σ. Definit¸ia A.2 Obiectele care apar ˆın descrierea unei probleme (numere rat¸ionale, grafuri, funct¸ii, matrici etc.) vor fi reprezentate cu ajutorul unor cuvinte w ∈ Σ∗ . Lungimea cuvˆantului w se va nota cu |w|. Orice mult¸ime de cuvinte peste Σ, adic˘ a orice submult¸ime a lui Σ∗ se nume¸ste limbaj (peste Σ). Problemele de decizie au urm˘atoarea form˘a: Fiind dat un anumit obiect, are el o anumit˘a proprietate?. Deoarece obiectele le reprezent˘am cu ajutorul cuvintelor ˆınseamn˘a c˘a problema se reduce la ˆıntrebarea: Fiind dat un cuvˆ ant, prezint˘ a el o anumit˘ a proprietate? Definit¸ia A.3 Vom considera problem˘ a de decizie o funct¸ie P : Σ∗ −→ {da, nu}
(A.2)
Definit¸ia A.4 Limbajul asociat problemei P este P −1 (da) = {w|w ∈ Σ∗ ¸si P (w) = da}
(A.3)
Definit¸ia A.5 Vom considera c˘a un algoritm este o funct¸ie A : Σ∗ −→ {da, nu, nedecidabil}
(A.4)
Definit¸ia A.6 Limbajul L ⊆ Σ∗ este acceptat de algoritmul A dac˘ a L = {w|w ∈ ∗ Σ ¸si A(w) = da}. Definit¸ia A.7 Limbajul L ⊆ Σ∗ este decis de algoritmul A dac˘ a ∀w ∈ L : A(w) = da ¸si ∀w ∈ / L : A(w) = nu. Definit¸ia A.8 Limbajul L ⊆ Σ∗ este acceptat de algoritmul A ˆın timp polinomial dac˘a L este acceptat de A ¸si ∃k ∈ N astfel ˆıncˆ at pentru orice w ∈ L algoritmul A evalueaz˘a A(w) = da ˆın timpul O(|w|k ). Definit¸ia A.9 Limbajul L ⊆ Σ∗ este decis de algoritmul A ˆın timp polinomial dac˘a ∃k ∈ N astfel ˆıncˆat pentru orice w ∈ Σ algoritmul A evalueaz˘ a A(w) = da, dac˘ a w ∈ L ¸si k A(w) = nu, dac˘a w ∈ / Σ, ˆın timpul O(|w| ). Definit¸ia A.10 Clasa de complexitate P: P = {L ⊂ Σ∗ |∃A alg. a.ˆı. L e decis de A ˆın timp polinomial}. 189
(A.5)
Dac˘a P este o problem˘a de decizie, atunci ea este rezolvabil˘ a ˆın timp polinomial dac˘a −1 limbajul L = P (da) satisface L ∈ P. Se noteaz˘a aceasta P ∈ P. De exemplu, problema DrumMIN − D este rezolvabil˘a ˆın timp polinomial dac˘a funct¸ia de lungime (specificat˘a ˆın intrarea ei) prezint˘a numai valori nenegative. Dac˘a se permit ¸si valori negative, atunci nu se cunoa¸ste nici o demonstrat¸ie a apartenent¸ei DrumMin − D ∈ P (¸si nici nu se crede c˘a ar exista una). De asemenea, NrCompus ∈ P s-a demonstrat destul de recent, ˆın 2002. Observat¸ia A.3 1. Dac˘a not˘am P’ = {L ⊂ Σ∗ |∃A alg. a.ˆı. L este acceptat de A ˆın timp polinomial}, se observ˘a imediat c˘a P ⊆ P′ ¸si nu este dificil s˘ a se arate ¸si incluziunea invers˘ a. Prin urmare avem P = P′ . 2. Se verific˘a u¸sor c˘a dac˘a P ∈ P atunci ¸si Σ∗ \ P ∈ P.
A.0.1
Verificare ˆın timp polinomial
Un algoritm de verificare este o funct¸ie A : Σ∗ × Σ∗ −→ {da, nu, nedecidabil}. Pentru A(x, y), x reprezint˘a intrarea iar y este certificatul. Limbajul L ⊆ Σ∗ este verificat de algoritmul de verificare A dac˘a L = {w|w ∈ Σ∗ ¸si ∃y ∈ Σ∗ a.ˆı. A(w, y) = da}. Definit¸ia A.11 Clasa de complexitate NP: NP = {L ⊆ Σ∗ |∃A algoritm de verificare cu timp de lucru polinomial a.i.
L = {x ∈ Σ∗ |∃y ∈ Σ∗ , ∃k ∈ N a.ˆı. |y| = O(|x|k ) ¸si A(x, y) = da}}
(A.6)
Dac˘a P este o problem˘a de decizie, atunci ea este o problem˘a (din) NP dac˘a limbajul L = P −1 (da) satisface L ∈ NP. NP este mnemonic pentru Nedeterminist Polinomial. Nu este bine s˘a asimil˘am NP cu nepolinomial deoarece textP ⊆ NP. Justificarea este imediat˘a: Dac˘a L ∈ P, atunci exist˘a A : Σ∗ −→ {da, nu, nedecidabil} algoritm ce decide L ˆın timp polinomial. Consider˘am A′ : Σ∗ × Σ∗ −→ {da, nu, nedecidabil}, satisf˘acˆand A′ (x, x) = A(x) pentru orice x ∈ Σ∗ . Se vede u¸sor c˘a L este verificat de A′ ˆın timp polinomial. Din definit¸iile de mai sus ar fi fost mai normal s˘a folosim notat¸ia VP (verificabil polinomial ). Sintagma Nedeterminist Polinomial se justific˘a dac˘a am considera algoritmi nedetermini¸sti ˆın care, dup˘a fiecare pas, este posibil s˘a se execute unul dintre pa¸sii specificat¸i dintr–o mult¸ime finit˘a de pa¸si succesori. Un astfel de algoritm accept˘a un cuvˆant de intrare dac˘a este posibil˘a o execut¸ie care s˘a conduc˘a la rezultatul ’da’. Se poate ar˘ata c˘a aceast˘a definit¸ie a accept˘arii nedeterministe este echivalent˘a cu cea de verificare dat˘a mai sus, ˆın care certificatul este utilizat pentru efectuarea alegerilor corecte ale pa¸ssilor urm˘atori ai unei execut¸ii a algoritmului nedeterminist. NP noteaz˘a clasa problemelor de decizie pentru care r˘aspunsurile afirmative au certificate care pot fi folosite pentru a demonstra succint (ˆın timp polinomial) corectitudinea lor. 190
Intuitiv, NP este clasa tuturor problemelor de decizie pentru care se poate verifica un r˘aspuns pozitiv (’da’) rapid dac˘a ni se d˘a o solut¸ie. De exemplu, pentru problema MaxCut − D un r˘aspuns afirmativ are drept certificat o partit¸ie (S ∗ , T ∗ ) a mult¸imii vˆarfurilor grafului (iar proprietatea c˘a suma lungimilor muchiilor cu o extremitate ˆın S ∗ ¸si cealalt˘a ˆın T ∗ nu dep˘a¸se¸ste pragul k se face ˆın timp polinomial). Prin urmare MaxCut − D ∈ NP. Dac˘a am considera urm˘atoarea problem˘a de decizie: UnDrum Intrare: G un graf, a, b vˆarfuri ale lui G. ˆIntrebare: Exist˘a un drum unic de la a la b ˆın G? Cum s-ar putea justifica un r˘aspuns ’da’ la o problem˘a UnDrum? Dac˘a prezent˘am un drum anume drept certificat, el nu ne asigur˘a c˘a nu mai exist˘a ¸s altele. O demonstrat¸ie succint˘a pare a nu exista. Prin urmare nu se cunoa¸ste dac˘a UnDrum ∈ NP. Definit¸ia A.12 Clasa de complexitate co–NP: co − NP = {L ⊆ Σ∗ |Σ∗ \ L ∈ NP}. O problem˘a de decizie P ∈ co − NP dac˘a L = P −1 (da) ∈ co − NP (echivalent, L = P −1 (nu) ∈ NP). Exemplu de problem˘a din co − NP: NeHam Intrare: G un graf. ˆIntrebare: Este adev˘arat c˘a ˆın G nu exist˘a un circuit care s˘a treac˘a exact o dat˘a prin fiecare vˆarf al s˘au? Observat¸ia A.4 P ⊆ NP ∩ co − NP.
A.0.2
Reduceri polinomiale
Definit¸ia A.13 Fie L1 , L2 ⊆ Σ∗ . Spunem c˘ a L1 se reduce polinomial la L2 , ¸si not˘am ∗ aceasta prin L1 ∝ L2 , dac˘a exist˘a f : Σ −→ Σ∗ o funct¸ie polinomial calculabil˘ a astfel ˆıncˆat ∀w ∈ Σ∗ : w ∈ L1 dac˘a ¸si numai dac˘a f (w) ∈ L2 . f se nume¸ste funct¸ie de reducere ¸si algoritmul polinomial F ce calculeaz˘a f se nume¸ste algoritm de reducere polinomial˘a. Observat¸ia A.5 1. Dac˘a L ∈ P ¸si L′ ∝ L, atunci L′ ∈ P. Fie A un algoritm polinomial care decide L ¸si F un algoritm de reducere polinomial˘a a lui L′ la L. Atunci, A′ = A ◦ F este un algoritm polinomial care decide L′ . (∀x ∈ Σ∗ , A′ (x) = da ⇔ A(F (x)) = da ⇔ F (x) ∈ L ⇔ x ∈ L′ ; A′ este polinomial deoarece mult¸imea polinoamelor este ˆınchis˘a la operat¸ia de compunere). 2. Relat¸ia ∝ este tranzitiv˘a: L1 ∝ L2 , L2 ∝ L3 ⇒ L1 ∝ L3 . Definit¸ia A.14 Limbajul L ⊂ Σ∗ este NP–dificil (engl. NP-hard) dac˘ a ∀L′ ∈ NP are loc ′ L ∝ L. Definit¸ia A.15 Limbajul L ⊆ Σ∗ este NP–complet dac˘ a L ∈ NP ¸si L este NP–dificil. Terminologia se transfer˘a pentru probleme de decizie. Spunem c˘a problema de decizie P1 se reduce polinomial la problema de decizie P2 , ¸si not˘am P1 ∝ P2 , dac˘a L1 = P1−1 (da) ∝ L2 = P2−1 (da). 191
Definit¸ia A.16 Problema de decizie P este NP–dificil˘ a dac˘ a ∀P ′ ∈ NP are loc P ′ ∝ P. Definit¸ia A.17 Problema de decizie P este NP–complet˘ a dac˘ a P ∈ NP ¸si P este NP–dificil˘ a. Folosind observat¸ia anterioar˘a ¸si faptul c˘a P ⊆ NP rezult˘a urm˘atoarea teorem˘a. Teorema A.6 Dac˘a P o problem˘a oarecare NP–complet˘ a satisface P ∈ P atunci P = NP. Rezult˘a c˘a problemele NP–complete formeaz˘a o submult¸ime a lui NP ce cont¸ine cele mai dificile probleme. Dac˘a g˘asim un algoritm polinomial pentru una dintre ele, putem construi un algoritm polinomial pentru oricare alt˘a problem˘a din NP. Din p˘acate, de¸si nu exist˘a o demonstrat¸ie, oamenii cred c˘a, de fapt, aceste probleme nu admit algoritmi polinomiali de rezolvare. Se obi¸snuie¸ste s˘a se spun˘a c˘a o problem˘a de optimizare este NP–dificil˘ a, dac˘a problema de decizie asociat˘a este a¸sa. Pentru a fi consistent¸i fat¸˘a de definit¸iile precedente, vom spune c˘a o problem˘a oarecare P (nu neap˘arat de decizie) este NP–dificil˘ a dac˘a existent¸a unui algoritm polinomial pentru P implic˘a P = NP. S˘a prezent˘am un exemplu pentru clasa de complexitate a problemelor NP–complete. Primul om care a demonstrat existent¸a unei astfel de probleme este Cook, care ˆın 1971 a ar˘atat c˘a SAT ∈ NP, unde SAT Intrare: U = {u1, . . . , un } o mult¸ime finit˘a de variabile booleene. C = C1 ∧ C2 ∧ . . . ∧ Cm o formul˘a ˆın form˘a conjunctiv˘a peste U: Ci = vi1 ∨ vi2 ∨ . . . ∨ viki , ∀i = 1, m unde ∀ij ∃α ∈ {1, . . . , n} astfel ˆıncˆat vij = uα sau vij = uα . ˆIntrebare: Exist˘a o atribuire t : U −→ {A, F } astfel ˆıncˆat t(C) = A? Evaluarea lui t(C) se bazeaz˘a pe formulele uzuale din calculul boolean: A ∧ A = A, F ∧ A = F ∧ F = A ∧ F = F, F ∨ F = F, F ∨ A = A ∨ F = A ∨ A = A, F = A, A = F, (F ) = F, (A) = A,
(A.7) (A.8) (A.9)
¸si se poate face ˆın timp polinomial. 3SAT este restrict¸ia lui SAT ˆın care fiecare clauz˘a Ci are exact trei literali (ki = 3), un literal vij fiind, a¸sa cum este descris mai sus, o variabil˘a sau negat¸ia ei. Se poate ar˘ata u¸sor c˘a SAT ∝ 3SAT ¸si astfel se obt¸ine faptul c˘a 3SAT este NP– complet˘a (apartenent¸a la NP este clar˘a fiind o restrict¸ie a lui SAT care apart¸ine lui NP, iar tranzitivitatea relat¸iei ∝ ˆımpreun˘a cu teorema lui Cook termin˘a demonstrat¸ia). ˆIn concluzie, pentru a ar˘ata c˘a o problem˘a de decizie P este NP–complet˘ a se procedeaz˘a astfel: 1. Se arat˘a c˘a L = P −1(da) satisface L ∈ NP. 2. Se selecteaz˘a un limbaj L′ despre care ¸stim c˘a este NP–complet. 3. Se ˆıncearc˘a construirea unui algoritm de reducere F de la L′ la L. 4. Se demonstreaz˘a c˘a F este algoritm de reducere. 5. Se arat˘a c˘a F este algoritm polinomial. 192
Appendix B Metoda Backtracking Metoda Backtracking este una dintre cele mai cunoscute metode de elaborare a algoritmilor. Metoda se aplic˘a numai ˆın situat¸ia ˆın care nu exist˘a nici o alt˘a modalitate de rezolvare a problemei propuse deoarece timpul de execut¸ie depinde exponent¸ial de dimensiunea datelor de intrare. Se utilizeaz˘a o structur˘a de tip stiv˘a iar metoda poate fi implementat˘a atˆat iterativ cˆat ¸si recursiv. Metoda se aplic˘a acelor probleme ˆın care solut¸ia se poate reprezenta sub forma unui vector x = (x1 , x2 , . . . , xn ) ∈ A1 × A2 × . . . × An , unde mult¸imile Ai , (i = 1, n) sunt finite ¸si nevide (|Ai| = ni > 0). ˆIn plus, pentru fiecare problem˘a ˆın parte este necesar ca solut¸ia x1 , x2 , . . . , xn s˘a satisfac˘a anumite condit¸ii interne ρ(x1 , x2 , . . . , xn ) (vezi algoritmul 67). Algoritm 67 Algoritm Backtracking (varianta general˘a) 1: procedure Backtracking(n, A) 2: k←1 3: xk ← prima valoare din afara domeniului de valori 4: while (k > 0) do 5: gasit ← f alse 6: while (∃ valori neverif icate pentru xk ) ∧ (gasit 6= true) do 7: xk ← urmatoarea valoare neverif icata 8: if (ρk (x1 , x2 , . . . , xk ) = true) then 9: gasit ← true 10: end if 11: end while 12: if (gasit = true) then 13: if (k = n) then 14: call Af is Solutie(x1 , . . . , xn ) 15: else 16: k ←k+1 17: xk ← prima valoare din afara domeniului de valori 18: end if 19: else 20: k ←k−1 21: end if 22: end while 23: end procedure
De exemplu, s˘a consider˘am dou˘a mult¸imi, S1 = {a, b, c} ¸si S2 = {m, n}. Se dore¸ste s˘a se determine perechile (x1 , x2 ), cu x1 ∈ A1 ¸si x2 ∈ A2 , cu proprietatea c˘a dac˘a x1 este a sau b 193
atunci x2 nu poate fi n. Rezolvarea conduce la urm˘atoarele variantele: (a, m), (b, m), (c, m), (c, n). Din cele ¸sase solut¸ii posibile (a, m), (a, n), (b, m), (b, n), (c, m), (c, n), numai acestea patru ˆındeplinesc condit¸iile interne. Mult¸imea A = A1 × A2 × ... × An se nume¸ste spat¸iul solut¸iilor posibile, iar elementele x ∈ A ce satisfac condit¸iile interne se numesc solut¸ii rezultat. Ne propunem determinarea tuturor solut¸iilor rezultat, eventual pentru a alege dintre ele pe aceea ce minimizeaz˘a sau maximizeaz˘a o funct¸ie obiectiv. Metoda Backtracking evit˘a generarea tuturor solut¸iilor posibile (toate elementele produsului cartezian A1 × A2 × ... × An ). Construirea unei solut¸ii se face ˆın mai mult¸i pa¸si, elementele vectorului X primind valori pe rˆand: elementului xk ∈ Ak , k = 1, n, i se atribuie o valoare numai dup˘a ce au fost atribuite valori pentru componentele x1 ∈ A1 , x2 ∈ A2 , . . ., xk−1 ∈ Ak−1 . Metoda trece la atribuirea unei valori pentru xk+1 ∈ Ak+1 doar dac˘a xk ˆımpreun˘a cu x1 , x2 , . . . , xk−1 verific˘a condit¸iile de continuare, notate cu ρk (x1 , x2 , . . . , xk ). Dac˘a aceste condit¸ii ρk (x1 , x2 , . . . , xk ) sunt ˆındeplinite se trece la c˘autarea unei valori pentru elementul xk+1 ∈ Ak+1 al solut¸iei. Neˆındeplinirea condit¸iilor are urm˘atoarea semnificat¸ie: pentru orice alegere xk+1 ∈ Ak+1 , . . . , xn ∈ An , nu vom putea ajunge la o solut¸ie rezultat ˆın care condit¸iile interne s˘a fie ˆındeplinite. ˆIn aceast˘a situat¸ie va trebui s˘a ˆıncerc˘am o alt˘a alegere pentru xk , acest lucru fiind posibil doar dac˘a nu am epuizat toate valorile disponibile din mult¸imea Ak . Dac˘a mult¸imea Ak a fost epuizat˘a va trebui s˘a mic¸sor˘am valoarea variabilei k cu o unitate (k ← k − 1), ¸si s˘a trecem la alegerea unei alte valori pentru elementul xk−1 ∈ Ak−1 . Mic¸sorarea valorii curente a variabilei k cu o unitate d˘a numele metodei ¸si semnific˘a faptul c˘a atunci cˆand nu putem avansa, vom urm˘ari ˆınapoi secvent¸a curent˘a din solut¸ie. Faptul c˘a valorile v1 , v2 , . . . , vk−1 , ale componentelor x1 , x2 , . . . , xk−1 , satisfac condit¸iile de continuare, nu este suficient pentru a garanta obt¸inerea unei solut¸ii ale c˘arei prime k − 1 componente coincid cu aceste valori. O alegere bun˘a a condit¸iilor de continuare conduce la reducerea num˘arului de calcule, fiind de dorit ca aceste condit¸ii de continuare s˘a fie nu numai necesare, dar ¸si suficiente pentru obt¸inerea unei solut¸ii. Condit¸iile interne devin chiar condit¸ii de continuare pentru k = n. Orice vector solut¸ie se obt¸ine progresiv ˆıncepˆand cu prima component˘a ¸si deplasˆandune c˘atre ultima, cu eventuale reveniri asupra valorilor atribuite anterior. Anumite valori sunt consumate ˆın urma unor atribuiri sau ˆıncerc˘ari de atribuire e¸suate din cauza neˆındeplinirii condit¸iilor de continuare. Exist˘a dou˘a variante fat¸˘a de metoda standard: • solut¸iile pot avea num˘ar variabil de componente; • dintre solut¸ii se alege cea care minimizeaz˘a sau maximizeaz˘a o funct¸ie cost sau o funct¸ie obiectiv dat˘a. Exemplul B.1 Enunt¸ul problemei: Se cere s˘ a se genereze permut˘ ari de n elemente. n Rezolvare: Spat¸iul solut¸iilor este A = ×i=1 Ai , Ai = {1, 2, . . . , n}, |Ai | = n. Solut¸ia va fi obt¸inut˘a ˆın vectorul x = (x1 , x2 , . . . , xn ) ∈ A1 × A2 × . . . An . La pasul k se ˆıncearc˘a atribuirea unui alt element din mult¸imea Ak lui xk . Vom trece la pasul k + 1 doar dac˘a sunt ˆındeplinite condit¸iile de continuare: (x1 , x2 , . . . , xk ) nu cont¸ine elemente care se repet˘a. Deoarece aceast˘ a verificare se face la fiecare pas, este suficient s˘a verific˘am dac˘a valoarea elementului xk nu se reg˘ ase¸ste printre valorile x1 , x2 , . . . , xk−1 (vezi funct¸ia CanContinue() din algoritmul 68). Exemplul B.2 Fie n = 4. Atunci Ai va fi compus din elementele Ai = {1, 2, 3, 4}. Pentru k = 1, x1 va primi valuarea 1 (vezi linia 15): 1 194
Pentru k = 2, se verific˘a x2 = 1, ˆıns˘ a nu se respect˘ a condit¸iile de continuare (linia 16). Astfel se trece la urm˘atoarea valoare, x2 = x1 + 1 = 2 (linia 15). Pentru aceast˘ a configurat¸ie condit¸iile de continuare sunt ˆındeplinite, ¸si se trece la pasul urm˘ ator, k = k + 1 = 3 (linia 24): 1 2 Se verific˘a valorile 1, 2 ¸si 3 pentru x3 (linia 16), dintre care, numai ultima satisface condit¸iile de continuare: 1 2 3 ˆ urma verific˘arii Ajun¸si la ultimul pas, k = 4, se verific˘ a valorile 1, 2, 3 ¸si 4 pentru x4 . In condit¸iilor de continuare pentru x4 = 4, se urm˘ are¸ste trecerea la pasul k = k+1 = 5. Deoarece suntem la ultimul element al vectorului x (linia 21), se obt¸ine prima solut¸ie, ce se ¸si afi¸seaz˘a: 1 2 3 4 Mai departe, nu mai exist˘a valori netestate pentru x4 (linia 14), ¸si revenim la nivelul anterior k = k −1 = 3 (linia 28). Aici urm˘ atoarea valoare din domeniul de valori neverificat˘a este x3 = 4 (linia 15). Solut¸ia part¸ial˘a respect˘ a condit¸iile de continuare (linia 16), astfel ˆıncˆat se trece la nivelul urm˘ator (linia 24), k = k + 1 = 4: 1 2 4 Pentru x4 sunt verificate valorile, 1, 2, 3, 4 (liniile 15–16), dintre acestea numai valoarea 3, conducˆand la o solut¸ie final˘a (linia 21): 1 2 4 3 Algoritmul se continu˘ a ˆın acela¸si mod pˆan˘a se obt¸in toate solut¸iile: (1, 2, 3, 4) (1, 2, 4, 3) (1, 3, 2, 4) (1, 3, 4, 2) (1, 4, 2, 3) (1, 4, 3, 2) (2, 1, 3, 4) (2, 1, 4, 3) (2, 3, 1, 4) (2, 3, 4, 1) ... O variant˘a de implementare a algoritmului 68 ˆın limbajul C este:
#include #define TRUE 1 #define FALSE 0 #define NN 100 int n; int a[NN]; /** * Functia citeste valorile datelor de intrare. */ void readInput(void) { //se citeste numarul de elemente n printf("n="); scanf("%d", &n); }
195
Algoritm 68 Algoritm pentru generare permut˘ari (varianta backtracking) Input: n - num˘ arul de elemente din mult¸imea init¸ial˘ a 1: function CanContinue(A, k) 2: for i ← 1, k − 1 do 3: if (ai = ak ) then 4: return f alse 5: end if 6: end for 7: return true 8: end function 9: procedure Permutari(n) 10: k←1 11: xk ← 0 12: while (k > 0) do 13: gasit ← f alse 14: while (xk + 1 ≤ n) ∧ (gasit 6= true) do 15: xk ← xk + 1 16: if ((CanContinue(X, k)) = true) then 17: gasit ← true 18: end if 19: end while 20: if (gasit = true) then 21: if (k = n) then 22: Output {x1 , . . . , xn } 23: else 24: k ←k+1 25: xk ← 0 26: end if 27: else 28: k ←k−1 29: end if 30: end while 31: end procedure
/** * Functia afiseaza solutia calculata a problemei. */ void list(void) { int i; for (i = 1; i <= n; i++) printf("%d ", a[i]); printf("\n"); } /** * Functia de continuare: aici se verifica daca elementul de pe pozitia k * nu se mai afla in sirul A. */
196
int canContinue(int k) { int i; for (i = 1; i <= k - 1; i++) if (a[k] == a[i]) return FALSE; return TRUE; } void run(void) { int i; int gata; //initializare i = 1; a[i] = 0; while (i > 0) { gata = FALSE; while ((a[i] + 1 <= n) && !gata) { /** * cat timp exista elementul urmator si nu sunt verificate * conditiile de continuare */ //treci la elementul urmator a[i]++; //verifica conditiile de continuare if (canContinue(i)) gata = TRUE; //sau: gata = canContinue(i); } if (gata) //daca s-au verificat conditiile de continuare if (i == n) //daca suntem la ultimul element afisam solutia list(); else { //altfel trecem la elementul urmator i++; a[i] = 0; } else //altfel revenim la un element anterior si alegem o alta valoare i--; } } void main(void) { read_data(); run(); }
Exemplul B.3 Enunt¸ul problemei: S˘ a se genereze combin˘ ari de n luate cˆ ate m (0 ≤ 197
m ≤ n). Rezolvare: Spat¸iul solut¸iilor este A = ×m ¸ia va i=1 Ai , Ai = {1, 2, . . . , n}, |Ai | = n. Solut fi obt¸inut˘a ˆın vectorul x = (x1 , x2 , . . . , xm ) ∈ A1 × A2 × . . . Am (vezi algoritmul 69). La pasul k se ˆıncearc˘a atribuirea unui alt element din mult¸imea Ak lui xk . Condit¸ia de continuare se refer˘a la proprietatea c˘ a elementele vectorului solut¸ie trebuie s˘ a respecte relat¸ia x1 < x2 < . . . < xk . Acest lucru se obt¸ine foarte u¸sor prin urm˘ atorul procedeu: la trecerea la pasul k + 1, variabila xk+1 va primi, ca valoare init¸ial˘ a, valoarea curent˘ a a variabilei xk (vezi linia 15 din algorimul 69). Pentru n = 5 ¸si m = 3 avem: A = ×3i=1 Ai = A1 × A2 × A3 , unde A1 = A2 = A3 = {1, 2, 3, 4, 5}. Solut¸iile obt¸inute sunt: (1, 2, 3) (1, 2, 4) (1, 2, 5) (1, 3, 4) (1, 3, 5) (1, 4, 5) (2, 3, 4) (2, 3, 5) (2, 4, 5) (3, 4, 5) Algoritm 69 Algoritm pentru generare combin˘ari (varianta backtracking) 1: procedure Combinari(n, m) 2: k←1 3: xk ← 0 4: while (k > 0) do 5: gasit ← f alse 6: while (xk + 1 ≤ n) ∧ (gasit 6= true) do 7: xk ← xk + 1 8: gasit ← true 9: end while 10: if (gasit = true) then 11: if (k = m) then 12: Output {x1 , . . . , xm } 13: else 14: k ←k+1 15: xk ← xk−1 16: end if 17: else 18: k ←k−1 19: end if 20: end while 21: end procedure
ˆIn continuare prezent˘am o implementare a algoritmului 69 ˆın limbajul C: 198
#include #define TRUE 1 #define FALSE 0 #define NN 100 int n, m; int a[NN]; /** * Functia citeste valorile datelor de intrare. */ void readInput(void) { printf("n="); scanf("%d", &n); printf("m="); scanf("%d", &m); } /** * Functia afiseaza solutia calculata de functia run(). */ void list(void) { int i; for (i = 1; i <= m; i++) printf("%d ", a[i]); printf("\n"); } void run(void) { int i; int gata; //initializare i = 1; a[i] = 0; while (i > 0) { gata = FALSE; while ((a[i] + 1 <= n) && !gata) { /** * cat timp exista elementul urmator si nu sunt verificate * conditiile de continuare, treci la elementul urmator */ a[i]++; gata = TRUE; } if (gata) //daca s-au verificat conditiile de continuare if (i == m) //daca suntem la ultimul element afisam solutia list(); else { //altfel trecem la elementul urmator i++;
199
a[i] = a[i - 1]; } else //altfel revenim la un element anterior si alegem o alta valoare i--; } } void main(void) { readInput(); run(); }
Exemplul B.4 Problema damelor Enunt¸ul problemei: Pe o tabl˘a de ¸sah cu n linii ¸si n coloane (n × n p˘ atrate), s˘a se pozit¸ioneze n dame astfel ˆıncˆat s˘a nu se atace reciproc. Rezolvare: Tabla de ¸sah va fi reprezentat˘ a prin matricea A = (aij ), i, j = 1, n. Dou˘a dame aflate ˆın pozit¸iile aij ¸si akl se vor ataca reciproc dac˘ a: i = k sau j = l sau |i − k| = |j − l| Prima observat¸ie care se poate deduce se refer˘ a la faptul c˘ a dou˘ a dame nu trebuie s˘a se afle pe aceea¸si linie sau pe aceea¸si coloan˘ a. Not˘am cu V = {1, 2, . . . , n} mult¸imea celor n coloane ale tablei de ¸sah. Vectorul solut¸iei rezultat X = (x1 , x2 , . . . , xn ) ∈ V × ... × V are urm˘ atoarea semnificat¸ie: componenta i a vectorului (i = 1, n) reprezint˘a linia i de pe tabla de ¸sah. Valoarea xi a acestei componente reprezint˘a coloana pe care se a¸seaz˘a dama de pe linia i (vezi algoritmul 70). La ˆınceput, toate damele sunt ˆın afara tablei de ¸sah, prin urmare xi = 0, i = 1, n. La pasul k vom ˆıncerca a¸sezarea damei k (de pe linia k) pe coloana urm˘ atoare (xk + 1). Init¸ial dama a k-a este pozit¸ionat˘a ˆın afara tablei (xk = 0 ∈ / V ). O dam˘ a poate fi a¸sezat˘a pe o alt˘a coloan˘a dac˘a vechea pozit¸ie satisface condit¸ia xk < n. Noua pozit¸ie (xk + 1) trebuie s˘a satisfac˘a condit¸iile interne ρk : • ∀i, 1 ≤ i < k, xi 6= xk + 1 (nu sunt pe aceea¸si coloan˘ a);
• |i − k| = 6 |xi − (xk + 1)| (nu sunt pe aceea¸si diagonal˘ a). Dac˘a xk < n ¸si ρk sunt adevar˘ate, dama de pe linia k va fi pozit¸ionat˘ a pe coloana xk + 1 ˆ ¸si se trece la pasul k + 1, corespunz˘ator pozit¸ion˘ arii damei k + 1. In caz contrar, dama k nu poate fi a¸sezat˘a pe tabl˘a ¸si va trebui s˘ a relu˘ am pozit¸ionarea damei k − 1. Exemplul B.5 Problema color˘arii h˘ art¸ilor Enunt¸ul problemei: S˘a se coloreze o hart˘ a reprezentˆ and n ¸t˘ ari folosind m culori etichetate 1, . . . , m. Rezolvare: Datele necesare pentru descrierea h˘ art¸ii vor fi reprezentate de o matrice A = (aij ), i, j = 1, n, ale c˘arei elemente au urm˘ atoarea semnificat¸ie: ( 1 , dac˘a ¸tara i are frontier˘ a comun˘ a cu ¸tara j aij = 0 , ˆın caz contrar Matricea A ∈ Mn×n este simetric˘ a ¸si elementele de pe diagonala principal˘ a sunt nule: aij = aji ¸si aii = 0. 200
Algoritm 70 Algoritm Problema damelor Input: n - num˘ arul de linii/coloane de pe tabla de ¸sah 1: function CanContinue(A, k) 2: for j ← 1, k − 1 do 3: if (aj = ak ) ∨ ((k − j) = |ak − aj |) then 4: return f alse 5: end if 6: end for 7: return true 8: end function 9: procedure DameBacktracking(n) 10: i←1 11: xi ← 0 12: while (i > 1) do 13: gasit ← f alse 14: while (xi + 1 ≤ n) ∧ (gasit 6= true) do 15: xi ← xi + 1 16: if (CanContinue(X, i) = true) then 17: gasit ← true 18: end if 19: end while 20: if (gasit = true) then 21: if (i = n) then 22: call Af is Solutie(x1 , . . . , xn ) 23: else 24: i←i+1 25: xi ← 0 26: end if 27: else 28: i ← i−1 29: end if 30: end while 31: end procedure
Fie C = {c1 , c2 , . . . , cm } mult¸imea culorilor. Vectorul solut¸iei rezultat X = (x1 , x2 , . . . , xn ) ∈ C × ... × C, are urm˘ atoarea semnificat¸ie: ¸tara i are repartizat˘a culoarea xi ∈ C, i = 1, n. La ˆınceput, fiec˘ arei ¸t˘ ari i se va atribui valoarea 0. La pasul k ˆıncerc˘am s˘a atribuim o nou˘ a culoare (xk + 1) pentru ¸tara k, k = 1, n, dac˘a sunt ˆındeplinite condit¸iile: 1. xk < m (dac˘a mai exist˘a alte culori neatribuite pentru ¸tara k); 2. pentru ∀i, 1 ≤ i < k cu ai,k = 1 avem xi 6= xk + 1. Dac˘a cele dou˘a condit¸ii anterioare sunt ˆındeplinite, atunci noua culoare pentru ¸tara k va ˆ caz contrar, fi xk + 1, ¸si se va trece la pasul k + 1 (stabilirea unei culori pentru ¸tara k + 1). In ¸t˘arii k nu i se mai poate atribui o alt˘ a culoare ¸si ne ˆıntoarcem la pasul k − 1, corespunz˘ator alegerii unei noi culori pentru ¸tara k − 1. Algoritmul se termin˘ a atunci cˆ and nu mai exist˘a nici o culoare neverificat˘a pentru ¸tara 1 (vezi algoritmul 71). 201
Algoritm 71 Algoritm Problema color˘arii h˘art¸ilor a/de vecin˘ atate a ¸t˘ arilor A - matricea de adiacent¸˘ Input: n - num˘ arul de ¸t˘ ari m - num˘ arul de culori disponibile 1: function CanContinue(A, X, k) 2: for j ← 1, k − 1 do 3: if (xj = xk ) ∧ (aj,k = 1) then 4: return f alse 5: end if 6: end for 7: return true 8: end function 9: procedure ColorBacktracking(A, n, m) 10: k←1 11: xk ← 0 12: while (k > 0) do 13: gasit ← f alse 14: while (xk + 1 ≤ m) ∧ (gasit 6= true) do 15: xk ← xk + 1 16: gasit ← CanContinue(A, X, k) 17: end while 18: if (gasit = true) then 19: if (k = n) then 20: call Af is Solutie(x1 , . . . , xn ) 21: else 22: k ←k+1 23: xk ← 0 24: end if 25: else 26: k ←k−1 27: end if 28: end while 29: end procedure
Exemplul B.6 Enunt¸ul problemei: Se d˘ a o sum˘ a s ¸si n tipuri de monede avˆ and valorile a1 , a2 , . . . , an lei. Realizat¸i un algoritm care s˘ a determine o modalitate de plat˘ a a sumei s utilizˆand un num˘ar minim de monede. Rezolvare: #include #define FALSE 0 #define TRUE 1 #define NN 100 #define MAX 10000 int suma_plata; //suma ce trebuie platita int n; //numarul de monezi
202
int int int int int
limit[NN]; val[NN]; taken[NN]; minim; keep[NN];
//limit[i] numarul maxim de monede de tipul val[i] //val[i] valoarea unei monede //cate monede de valoare val[i] au fost luate //numarul minim de monede cu care se poate face plata //solutia optima pina la momentul curent
/** * Se citesc datele de intrare: suma de plata, numarul de * tipuri de monezi si valoarea fiecarui tip. */ void readInput(void) { int i; printf("Suma= "); scanf("%d", &suma_plata); printf("Numarul de tipuri de monede: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("val[%d]=", i); scanf("%d", &val[i]); limit[i] = suma_plata / val[i]; } } /** * Se verifica daca * suma partiala sa * @param k - pasul */ int canContinue(int int i, suma_tmp;
sunt satisfacute conditiile de continuare: nu depaseasca valoarea totala de platit. curent k) {
suma_tmp = 0; for (i = 0; i <= k; i++) suma_tmp += val[i] * taken[i]; if ((k == n - 1 && suma_tmp == suma_plata) || (k != n - 1 && suma_tmp <= suma_plata)) return TRUE; else return FALSE; } /** * Se pastreaza solutia actuala (taken) numai daca este * mai buna decat cea anterioara (keep). */ void final(void) { int i; int monezi = 0; //se numara cate monede sunt in solutie for (i = 0; i < n; monezi += taken[i], i++); if (monezi < minim) { minim = monezi;
203
for (i = 0; i < n; i++) keep[i] = taken[i]; } } /** * Se afiseaza solutia optima sau mesajul ’Nu avem solutie.’ */ void print(void) { int i, first = 0; if (minim != MAX) { //verificam daca am obtinut o solutie printf("%d =", suma_plata); for (i = 0; i < n; i++) if (keep[i] != 0) { printf((first == 1) ? " + " : " "); printf("%d X %d", keep[i], val[i]); first = 1; } printf("\n"); } else printf("Nu avem solutie! \n"); } /** * Implementarea metodei backtracking. */ void run(void) { int i; int gata; /* are valoarea TRUE cand sunt verificate conditiile de continuare */ minim = MAX; i = 0; taken[i] = -1; while (i >= 0) { gata = FALSE; while ((taken[i] + 1 <= limit[i]) && !gata) { /* cat timp exista elementul urmator si nu sunt verificate conditiile de continuare */ taken[i]++; //treci la elementul urmator //verifica conditiile de continuare if (canContinue(i)) gata = TRUE; //sau: gata = canContinue(i); } if (gata) //daca s-au verificat conditiile de continuare if (i == n-1) //daca suntem la ultimul element pastram solutia final(); else { //altfel trecem la elementul urmator i++; taken[i] = -1;
204
} else //altfel revenim la un element anterior si alegem o alta valoare i--; } } void main(void) { readInput(); run(); print(); }
205
Bibliografie [1] A. Aho, J. Hopcroft, J. Ullman, On finding lowest common ancestors in trees, Proc. 5th ACM Symp. Theory of Computing (STOC), pp. 253-265, 1973. [2] A. V. Aho, J. E. Hopcroft, J. D. Ulmann, Data Structures and algorithms, AddisonWesley, 1983. [3] A. V. Aho, J. D. Ulmann, Foundation of Computer Science, Computer Science Press, 1992. [4] R. K. Ahuja, J. B. Orlin, A Fast and Simple Algorithm for the Maximum Flow Problem, Operations Research, vol. 37(5), pp. 748–759, 1989. [5] R. Ahuja, T. Magnanti, J. Orlin, Network Flows, Prentice Hall, 1993. [6] R. Andonie, I. Gˆarbacea, Algoritmi fundamentali, o perspectiv˘ a C++, Editura Libris, 1995. [7] C. Aragon, R. Seidel, Randomized Search Trees, in Proc. of 30th IEEE Symposium on Foundations of Computer Science, pp. 540–546, 1989. [8] A. Atanasiu, Concursuri de informatic˘ a. Probleme propuse (1994), Editura Petrion, Bucure¸sti, 1995. [9] M.D. Atkinson, J.-R. Sack, N. Santoro, T. Strothotte, Min-max heaps and generalized priority queues, Programming techniques and Data structures. Comm. ACM, vol. 29(10), pp. 996-1000, 1986. [10] M. Augenstein, A. Tenenbaum, Program efficiency and data structures, Proceedings of the eighth SIGCSE technical symposium on Computer science education, pp. 21–27, 1977. [11] S. Baase, Computer algorithms. Introduction to Design and Analysis, Addison-Wesley, 1992. [12] P. B˘az˘avan, Elemente de Teoria Algoritmilor, Editura Sitech, Craiova, 2007. [13] R. Bellman, On a routing problem, Quarterly Applied Mathematics, XVI(1), pp. 87-90, 1958. [14] M. A. Bender, M. Farach-Colton, The LCA problem revisited, Proceedings of the 4th Latin American Symposium on Theoretical Informatics, LNCS, vol. 1776, SpringerVerlag, pp. 88-94, 2000. [15] J. Bentley, R. Sedgewick, Fast Algorithms for Sorting and Searching Strings, Proceedings of the 8th Annual ACM-SIAM Symposium on Discrete Algorithms, 1997. 206
[16] J. Bentley, R. Sedgewick, Ternary Search Trees, Dr. Dobb’s Journal, 1998. [17] C. Bereanu, Algoritmica Grafurilor, Editura Sitech, Craiova, 2006. [18] C. Berge, Graphes et hypergraphes, Dunod, Paris 1970. [19] O. Berkman, D. Breslauer, Z. Galil, B. Schieber, ¸si U. Vishkin, Highly parallelizable problems, in Proceedings of the 21st Annual ACM Symposium on Theory of Computing, pp. 309-319, 1989. [20] O. Berkman, U. Vishkin, Recursive Star-Tree Parallel Data Structure, SIAM Journal on Computing, vol.22(2), pp.221-242, 1993. [21] O. Boruvka, O jist´em probl´emu minim´ aln´ım (About a certain minimal problem), Acta Societ. Scient. Natur. Moravicae, 3, pp. 37-58, 1926. [22] R. P. Brent, An improved Monte Carlo factorization algorithm, BIT Numerical Mathematics, Springer, vol. 20(2), pp. 176–184, 1980. [23] D. D. Burdescu, Analiza complexit˘ a¸tii algoritmilor, Editura Albastr˘a, Cluj–Napoca, 1998. [24] D. D. Burdescu, M. Brezovan, M. Co¸sulschi, Structuri de date arborescente cu aplicat¸ii ˆın Pascal ¸si C, Reprografia Universit˘a¸tii din Craiova, 2000. [25] D. D. Burdescu, Liste, arbori, grafuri, Editura Sitech, Craiova, 2005. [26] B. Chazelle, A minimum spanning tree algorithm with inverse-Ackerman type complexity, J. ACM, 47, pp. 1028-1047, 2000. [27] D. Cherition, R. E. Tarjan, Finding minimum spanning trees, SIAM Journal on Computing, vol. 5, pp. 724-741, 1976. [28] J. Cheriyan, K. Mehlhorn, Algorithms for dense graphs and networks on the random access computer, Algorithmica, vol. 15, pp. 521-549, 1996. [29] B. V. Cherkassky, √ Algorithm for construction of maximal flows in networks with complexity of O(V 2 E), Mathematical Methods of Solution of Economical Problems, vol. 7, pp. 112–125, 1977. [30] T. H. Cormen, C. E. Leiserson, R. L. Rivest, Introducere ˆın Algoritmi, Computer Libris Agora, Cluj–Napoca, 1999. [31] M. Co¸sulschi, M. Gabroveanu, Algoritmi o abordare pragmatic˘ a, Editia a 2-a, Universitaria, Craiova, 2004. [32] C. Croitoru, Tehnici de baz˘a ˆın optimizarea combinatorie, Editura Univ. Al. I. Cuza Ia¸si, Ia¸si, 1992. [33] G. B. Dantzig, Linear programming and extensions, University Press, Princeton, 1962. [34] S. Dasgupta, C. H. Papadimitriou, U. V. Vazirani, Algorithms, McGraw–Hill, 2006. [35] C. Demetrescu, G. F. Italiano, Engineering Shortest Path Algorithms, WEA, Springer, LNCS 3059, pp. 191–198, 2004. 207
[36] R. B. Dial, Algorithm 360: shortest-path forest with topological ordering [H], Communications of the ACM, vol. 12:11, pp. 632–633, 1969. [37] Dict¸ionarul explicativ al limbii romˆ ane, Academia Romˆan˘a, Institutul de Lingvistic˘a Iorgu Iordan, Editura Univers Enciclopedic, 1998. [38] E. W. Dijkstra, A note on two problems in connections with graphs, Numerische Mathematik, 1, pp. 269-271, 1959. [39] Y. Dinitz, Algorithm for solution of a problem of maximum flow in a network with power estimation, Doklady Akademii nauk SSSR, vol. 11, pp. 1277-1280, 1970. [40] Y. Dinitz, Dinitz’ Algorithm: The Original Version and Even’s Version, in Oded Goldreich, Arnold L. Rosenberg, and Alan L. Selman, Theoretical Computer Science: Essays in Memory of Shimon Even, Springer, pp. 218-240, 2006. [41] J. Edmonds, Paths, Trees and Flowers, Canadian J. Math, vol. 17, pp. 449-467, 1965. [42] J. Edmonds, R. M. Karp, Theoretical improvements in algorithmic efficiency for network flow problems, Journal of the ACM, vol. 19(2), pp. 248-264, 1972. [43] J. Egerv´ary, Matrixok kombinatorius tulajdons´ agair´ ol, (in Hungarian), Matematikai ´es Fizikai Lapok, vol. 38 pp. 16-28, 1931. [44] J. Farey, On a Curious Property of Vulgar Fractions, London, Edinburgh and Dublin Phil. Mag. 47, 385, 1816. [45] J. Feng, G. Li, J. Wang, L. Zhou, Finding and ranking compact connected trees for effective keyword proximity search in XML documents, Information Systems, vol. 35(2), pp. 186–203, 2010. [46] L. R. Ford, Network flow theory, Technical Report P-923, RAND, Santa Monica, CA, 1956. [47] L. R. Ford, Jr., D. R. Fulkerson, Maximal Flow Through a Network, Canadian Journal of Mathematics, 8, pp. 399–404, 1956. [48] L. R. Ford, Jr., D. R. Fulkerson, A Simple Algorithm for Finding Maximal Network Flows and an Application to the Hitchcock Problem, Canadian Journal of Mathematics, 9, pp. 210–218, 1957. [49] L. R. Ford, Jr., D. R. Fulkerson, Flows in Networks, Princeton University Press, Princeton, 1962. [50] C. L. Foster, The Design and Analysis of Algorithms, Springer Verlag, 1992. [51] J.-C. Fournier, Graph Theory and Applications, Wiley-Blackwell, 2009. [52] E. Fredkin, Trie Memory, Communications of the ACM, 3:(9), pp. 490, 1960. [53] M. Fredman, R. Sedgewick, R. Sleator, R. Tarjan, The pairing heap: A new form of self-adjusting heap, Algorithmica, 1, pp. 111–129, 1986. [54] M.L. Fredman, R.E. Tarjan, Fibonacci heaps and their use in improved network optimization algorithms, Journal of the ACM, vol. 34, pp. 596-615, 1987. 208
[55] H. N. Gabow, R. E. Tarjan, A linear-time algorithm for a special case of disjoint set union, Proceedings of the 15th ACM Symposium on Theory of Computing (STOC), pp. 246-251, 1983. [56] H. N. Gabow, Path-based depth-first search for strong and biconnected components, Information Processing Letters, pp. 107-114, 2000. [57] D. Gale, L. S. Shapley, College Admissions and the Stability of Marriage, American Mathematical Monthly, vol. 69, pp. 9–14, 1962. 5
[58] Z. Galil, An O(V 3 E f rac23 ) algorithm for the maximal flow problem, Acta Informatica, vol. 14, pp. 221–242, 1980. [59] Z. Galil, A. Naamad, An O(EV (log V )2 ) algorithm for the maximal flow problem, Journal of Computer and System Sciences, vol. 21(2), pp. 203–217, 1980. [60] C. Giumale, Introducere ˆın analiza algoritmilor, Editura Polirom, Ia¸si, 2004. [61] A. V. Goldberg, A new max-flow algorithm, Technical Report MIT/LCS/TM-291, Laboratory for Computer Science, MIT, 1985. [62] A. V. Goldberg, R. E. Tarjan, A new approach to the maximum flow problem, in Proceedings of the 18th ACM Symposium on Theory of Computing, ACM, pp. 136–146, 1986. ´ Tardos, R. E. Tarjan, Network flow algorithms, Algorithms and [63] A. V. Goldberg, E. Combinatorics, vol. 9. in B. Korte, L. Lovsz, H. J. Prmel, A. Schrijver, Editors, Paths, Flows, and VLSI-Layout, Springer-Verlag, Berlin, pp.101-164, 1990. [64] R. L. Graham, D. E. Knuth, O. Patashnik, Concrete Mathematics: A Foundation for Computer Science, 2nd Edition, Addison-Wesley, 1994. [65] D. Gries, The Science of Programming, Springer Verlag, Heidelberg, New–York, 1981. [66] L. Guo, F. Shao, C. Botev, J. Shanmugasundaram, XRANK: ranked keyword search over XML documents, in Proceedings of the 2003 ACM SIGMOD International Conference on Management of Data, pp. 16–27, 2003. [67] D. Gusfield, Algorithms on Strings, Trees, and Sequences, Cambridge University Press, 1997. [68] G. H. Hardy, E. M. Wright, An Introduction to the Theory of Numbers, 5th Edition, Oxford University Press, 1979. [69] D. Harel, R. E. Tarjan, Fast algorithms for finding nearest common ancestors, SIAM Journal on Computing, vol. 13(2), pp. 338-355, 1984. [70] T. E. Harris, F. S. Ross, Fundamentals of a Method for Evaluating Rail Net Capacities, Research Memorandum RM-1573, The RAND Corporation, Santa Monica, California, 1955. [71] I. N. Herstein, I. Kaplansky, Matters mathematical, 2nd Edition, Chelsea Publishing Company, 1978.
209
5
[72] J. E. Hopcroft, R. Karp, An n 2 algorithm for maximum matchings in bipartite graphs, SIAM Journal on Computing, vol. 2(4), pp.225-231, 1973. [73] V. Hristidis , N. Koudas , Y. Papakonstantinou, D. Srivastava, Keyword Proximity Search in XML Trees, IEEE Transactions on Knowledge and Data Engineering, vol. 18(4), pp. 525–539, 2006. [74] D. A. Huffman, A Method for the Construction of Minimum-Redundancy Codes, Proceedings of the I.R.E., 1952, pp. 1098-1102. [75] C. Ionescu, I. Zsako, Structuri arborescente, Editura Tehnic˘a, Bucure¸sti, 1990. [76] V. Jarn´ık, O jist´em probl´emu minim´ aln´ım (About a certain minimal problem), Pr´ace Moravsk´e Pr´ırodovedeck´e Spolecnosti, 6, pp. 57–63, 1930. [77] D. Jungnickel, Graphs, Networks and Algorithms, Algorithms and Computation in Mathematics, vol. 5, 3rd Edition, Springer, 2008. [78] A. B. Kahn, Topological sorting of large networks, Communications of the ACM, vol. 5(11), pp. 558-562, 1962. [79] D. Karger, P. Klein, R. Tarjan, A randomized linear-time algorithm to find minimum spanning trees, Journal of ACM, 42, pp. 321328, 1995. [80] A. V. Karzanov, Determining the maximum flow in the network by the method of preflows, Doklady Akademii nauk SSSR 15, pp. 434-437, 1974. [81] B. W. Kernigham, D. M. Ritchie, The C Programming Language, 2nd Edition, Prentice Hall, 1988. [82] D. C. Kozen, The Design and Analysis of Algorithms. Texts and Monographs in Computer Science, Springer, 1993. [83] D. K˝onig, Theorie der endlichen und unendlichen Graphen, Leipzig: Akademische Verlagsgesellschaft, 1936. Translated from German by Richard McCoart, Theory of finite and infinite graphs, Birkh¨auser, 1990. [84] J. Kleinberg, E. Tardos, Algorithm Design, Addison-Wesley, 2005. [85] D. E. Knuth, Arta program˘arii calculatoarelor, vol. 1 Algoritmi fundamentali, Teora, Bucure¸sti, 1999. [86] D. E. Knuth, Arta program˘arii calculatoarelor, vol. 2 Algoritmi seminumerici, Teora, Bucure¸sti, 2000. [87] D. E. Knuth, Arta program˘arii calculatoarelor, vol. 3 Sortare ¸si C˘ autare, Teora, Bucure¸sti, 2001. [88] J. B. Kruskal, On the shortest spanning subtree of a graph and the traveling salesman problem, Proc. of the American Mathematical Society, 7, pp. 48-50, 1956. [89] H. W. Kuhn, On combinatorial properties of matrices, Logistics Papers (George Washington University), vol. 11, pp. 1-11, 1955.
210
[90] H. W. Kuhn, The Hungarian Method for the assignment problem, Naval Research Logistics Quarterly, vol. 2, pp. 83-97, 1955. [91] H. W. Kuhn, Variants of the Hungarian method for assignment problems, Naval Research Logistics Quarterly, vol. 3, pp. 253-258, 1956. [92] Y. Li , C. Yu , H. V. Jagadish, Schema-free XQuery, in Proceedings of the Thirtieth International Conference on Very Large Databases (VLDB), pp. 72–83, 2004. [93] L. Livovschi, H. Georgescu, Analiza ¸si sinteza algoritmilor, Ed. S¸tiint¸ific˘a ¸si Enciclopedic˘a, Bucure¸sti, 1986. [94] V. M. Malhotra, M. P. Kumar, S. N. Maheshwari, An O(|V |3 ) algorithm for finding maximum flows in networks, Information Processing Letters, 7(6), pp.277–278, 1978. [95] K. Mehlhorn, Data Structures and Algorithms: Graph Algorithms and NP-Completeness, Springer Verlag, 1984. p [96] S. Micali, V. V. Vazirani, An O( |V | · |E|) algorithm for finding maximum matching in general graphs, Proc. 21st IEEE Symp. Foundations of Computer Science, pp. 17-27, 1980. [97] V. Mitrana, Provocarea algoritmilor, Editura Agni, Bucure¸sti, 1994. [98] E. F. Moore, The shortest path through a maze, in Proceedings of International Symposium on the Theory of Switching, Part II, pp. 285-292, 1959. (prezentat la simpozion la Universitatea Harvard in aprilie 1957) [99] R. Motwani, Average-case analysis of algorithms for matchings and related problems, Journal of the ACM, vol. 41(6), pp. 1329-1356, 1994. [100] J. Munkres, Algorithms for the Assignment and Transportation Problems, Journal of the Society for Industrial and Applied Mathematics, vol. 5(1), pp. 32-38, 1957. [101] G. Nivasch, Cycle detection using a stack, Information Processing Letters, vol. 90(3), pp. 135–140, 2004. [102] I. Od˘agescu, F. Furtun˘a, Metode ¸si tehnici de programare, Computer Libris Agora, Cluj–Napoca, 1998. [103] S. Pettie, A faster all-pairs shortest path algorithm for real-weighted sparse graphs, in Proceedings of 29th International Colloquium on Automata, Languages, and Programming (ICALP02), LNCS Vol. 2380, pp. 85-97, 2002. [104] S. Pettie, V. Ramachandran, Computing shortest paths with comparisons and additions, in Proceedings of the 13th Annual ACM-SIAM Symposium on Discrete Algorithms (SODA02), SIAM, pp. 267-276, 2002. [105] S. Pettie, V. Ramachandran, An optimal minimum spanning tree algorithm, Journal of ACM, 49:1634, 2002. [106] R. C. Prim, Shortest connection networks and some generalizations, Bell System Technical Journal, 36, pp. 1389-1401, 1957.
211
[107] B. Schieber, U. Vishkin, On finding lowest common ancestors: Simplification and parallelization, SIAM J. Comput., vol. 17, pp. 1253-1262, 1988. [108] A. Schrijver, On the history of the transportation and maximum flowproblems, Mathematical Programming, vol. 91, issue 3, pp. 437-445, 2002. [109] R. Seidel, C. Aragon, Randomized Search Trees, Algorithmica, vol. 16, pp. 464–497, 1996. [110] M. Sharir, A strong-connectivity algorithm and its applications in data fow analysis, Computers and Mathematics with Applications, vol. 7(1), pp. 67–72, 1981. [111] Y. Shiloach, U. Vishkin, An O(n2 log n) parallel max-flow algorithm, Journal of Algorithms, vol. 3(2), pp. 128-146, 1982. [112] S. Skiena, The Algorithm Design Manual, 2nd Edition, Springer, 2008. [113] D. D. Sleator, An O(EV log V ) algorithm for maximum network flow, Technical Report, STAN-CS-80-831, 1980. [114] D. D. Sleator, R. E. Tarjan, A data structure for dynamic trees, Journal of Computer Sciences, vol. 26, pp. 362–391, 1983. [115] J. Stasko, J. Vitter, Pairing heaps: Experiments and analysis, Communications of the ACM, vol. 30(3), pp. 234-249, 1987. [116] T. Takaoka, Theory of 2-3 heaps, Discrete Applied Mathematics, Vol. 126(1), 5th Annual International Computing and Combinatories Conference (COCOON’99), pp. 115– 128, 2003. [117] R. Tarjan, Depth–first search and linear graph algorithms, SIAM Journal on Computing, vol. 1(2), pp. 146–160, 1972. [118] R. E. Tarjan, Edge-disjoint spanning trees and depth-first search, Algorithmica, vol. 6(2), pp. 171-185, 1976. [119] R. E. Tarjan, Applications of path compression on balanced trees, Journal of the ACM, vol. 26(4), pp. 690-715, 1979. [120] R. E. Tarjan, A simple version of Karzanov’s blocking flow algorithm, Operation Research Letters, vol. 2, pp.265–268, 1984. [121] I. Tomescu, Grafuri ¸si programare liniar˘ a, Ed. Didactic˘a ¸si Pedagogic˘a, Bucure¸sti, 1975. [122] I. Tomescu, Probleme de combinatoric˘ a ¸si teoria grafurilor, Ed. Didactic˘a ¸si Pedagogic˘a, Bucure¸sti, 1981. [123] I. Tutunea, Algoritmi, logic˘a ¸si programare, Reprografia Universit˘a¸tii din Craiova, 1993. [124] I. Tutunea, S. Pesc˘aru¸s, Algoritmi ¸si programe Pascal. (Culegere de probleme), Reprografia Universit˘a¸tii din Craiova, 1994. [125] J. Vuillemin, A data structure for manipulating priority queues, Communications of the ACM, vol. 21:4, pp. 309–315, 1978. 212
[126] J. Vuillemin, A unifying look at data structures, Communications of the ACM, vol. 23:4, pp. 229-239, 1980. [127] G. A. Waissi, A new Karzanov-type O(n3 ) max-flow algorithm, Mathematical and Computer Modelling, vol. 16(2), pp.65–72, 1992. [128] H. S. Wilf, Algorithms and Complexity, Internet edition, 1996. [129] J. W. J. Williams, Algorithm 232 - Heapsort, Communications of the ACM, vol. 7(6), pp. 347-348, 1964. [130] Y. Xu, Y. Papakonstantinou, Efficient keyword search for smallest LCAs in XML databases, in Proceedings of the 2005 ACM SIGMOD International Conference on Management of Data, pp. 527–538, 2005. [131] D. Zaharie, Introducere ˆın proiectarea ¸si analiza algoritmilor, Eubeea, Timi¸soara, 2008.
213