Sunteți pe pagina 1din 154

Algoritmi și tehnici de programare 1

4. Metode de programare
(partea a III-a)

Material realizat după

C. Uscatu, C. Cocianu, B. Ghilic-Micu, M. Stoica, M.


Mircea, Algoritmi și tehnici de programare, Editura ASE,
București, 2014, ISBN 978-606-505-774-6
2 Metode de programare

4.1. Metoda Divide et impera

Metoda Divide et Impera este utilizată în rezolvarea unor probleme


complexe, care au următoarele caracteristici:

 pot fi descompuse în probleme cu complexitate mai mică. Aceste probleme sînt


fie de același tip cu problema inițială, fie au soluție imediată (probleme primitive).
 rezolvarea problemelor rezultate în urma descompunerii este mai ușoară decît
rezolvarea întregii probleme inițiale;
 prin combinarea soluțiilor problemelor rezultate în urma descompunerii (după
reguli simple) se obține o soluție pentru problema inițială.

Pentru fiecare din problemele rezultate în urma descompunerii se aplică


același procedeu de descompunere. Algoritmul este de tip recursiv, implementarea
fiind realizată de obicei prin subprograme recursive.

Exemplu. Să presupunem că se doreşte aflarea valorii maxime dintr-o secvenţă de


𝑛 numere: {𝑎1 , 𝑎2 , … 𝑎𝑛 , }. Una din metodele de rezolvare este următoarea: se
determină valoarea cea mai mare din prima jumătate (fie aceasta 𝑥1 ), apoi se
determină valoarea cea mai mare din a doua jumătate a secvenţei (fie aceasta 𝑥2 ).
Soluţia problemei inițiale este maximul dintre cele două valori găsite: 𝑚𝑎𝑥(𝑥1 , 𝑥2 ).
Problema iniţială a fost descompusă în două subprobleme de acelaşi tip, dar „mai
simple” deoarece lungimea fiecărei secvenţe este jumătate din lungimea secvenţei
iniţiale.

Nu este obligatorie descompunerea problemei inițiale în două „jumătăți”,


ca mai sus. Problema poate fi rezolvată pe baza unei metode care o „reduce”
succesiv la o problemă „mai simplă”, determinîndu-se valoarea maximă din
primele 𝑛 − 1 componente ale secvenţei (fie aceasta 𝑥1 ), soluția problemei fiind
𝑚𝑎𝑥(𝑥1 , 𝑎𝑛 ).

Alegerea modului de descompunere a problemei se face în funcție de


problema rezolvată și experiența celui care o rezolvă.

Pentru ambele opțiuni descompunerea continuă pînă cînd se ajunge la


probleme cu soluție imediată (determinarea maximului dintr-o secvență cu un
singur element). Funcțiile recursive care implementează cele două opțiuni de mai
sus sînt max1 și max2, prezentate împreună cu un exemplu de apel.
Algoritmi și tehnici de programare 3

#include <stdio.h>
#include <conio.h>

double max1(double *a, int s, int d)


{ double x1,x2,m;
if (s==d)
m=a[s];
else
{ x1=max1(a,s,(s+d)/2);
x2=max1(a,(s+d)/2+1,d);
if (x1>x2)
m=x1;
else
m=x2;
}
return m;
}

double max2(double *a,int n)


{ double x1;
if (n==0)
return a[0];
else
{ x1=max2(a,n-1);
if (x1>a[n-1])
return x1;
else
return a[n-1];
}
}

void main()
{ int i,n;
double *v, max;
printf("\nDimensiunea vectorului:");
scanf("%i",&n);
v=new double[n];
printf("Elementele vectorului\n");
for(i=0;i<n;i++)
scanf("%lf",&v[i]);

max=max1(v,0,n-1);
printf("\nElementul maxim:%8.3f",max);

max=max2(v,n);
printf("\nElementul maxim:%8.3f",max);

delete v;
getch();
}
4 Metode de programare

Exemplu. Se presupune că ecuaţia 𝑓(𝑥) = 0 are o singură soluţie 𝑥0 în intervalul


(𝑎, 𝑏). Se doreşte obţinerea unei valori aproximative 𝑥̂ astfel încît |𝑥0 − 𝑥̂| < 𝜀,
pentru 𝜀 > 0 dat.

Deoarece ecuaţia 𝑓(𝑥) = 0 are o singură soluţie 𝑥0 în intervalul (𝑎, 𝑏),


rezultă că 𝑓(𝑎) × 𝑓(𝑏) < 0 şi, de asemenea, dacă pentru 𝑎 < 𝛼 < 𝛽 < 𝑏 e valabilă
relația 𝑓(𝛼) × 𝑓(𝛽) < 0, atunci 𝑥0 ∈ (𝛼, 𝛽).

Pe baza acestei proprietăţi, dacă 𝑐 este mijlocul intervalului (𝑎, 𝑏), atunci
este îndeplinită una şi numai una dintre relaţiile 𝑓(𝑎) × 𝑓(𝑐) < 0, 𝑓(𝑐) = 0,
𝑓(𝑏) × 𝑓(𝑐) < 0:

 dacă 𝑓(𝑐) = 0, atunci 𝑥0 = 𝑐;


 dacă 𝑓(𝑎) × 𝑓(𝑐) < 0 atunci 𝑥0 ∈ (𝑎, 𝑐);
 altfel 𝑥0 ∈ (𝑐, 𝑏).

Să presupunem că 𝑓(𝑎) × 𝑓(𝑐) < 0. Putem aplica acelaşi procedeu


intervalului (𝑎, 𝑐) şi se continuă pînă cînd sau este obţinută soluţia exactă sau
intervalul care conţine soluţia este de lungime inferioară lui 𝜀. În cazul în care
terminarea calculului se realizează prin identificarea unui interval de lungime
inferioară lui 𝜀, 𝑥̂ poate fi considerat oricare dintre numerele din acel interval, de
exemplu mijlocul intervalului.

Numărul maxim de iteraţii 𝑁 pentru obţinerea preciziei 𝜀 rezultă din


inegalitatea

𝑏−𝑎 𝑏−𝑎
𝑁
< 𝜀 ⇒ 𝑁 = [log 2 ( )] + 1
2 𝜀

Să presupunem că funcţia f este calculată prin apelul subprogramului cu


același nume de mai jos. O variantă recursivă de implementare a metodei este
următoarea:
#include <stdio.h>
#include <conio.h>
#include<math.h>

float f(float x)
{ return (2*pow(x,3)-pow(x,2)+x-2);
}

unsigned bisectie(float x0,float x1,unsigned n,float eps, float (* f)(float), float


*x2)
{ if (n>0)
Algoritmi și tehnici de programare 5

{ *x2=(x0+x1)/2;
if((*f)(*x2)==0)
return 1;
if(fabs(*x2-x1)>eps)
if((*f)(*x2)*(*f)(x0)<0)
return bisectie(x0,*x2,n-1,eps,f,x2);
else
return bisectie (*x2,x1,n-1,eps,f,x2);
else
return 2;
}
}

void main()
{ float eps,x0,x1,x2;
int n;
printf("x0= ");
scanf("%f",&x0);
printf("x1= ");
scanf("%f",&x1);
printf("n= ");
scanf("%d",&n);
printf("eps= ");
scanf("%f",&eps);
switch (bisectie(x0,x1,n,eps,f,&x2))
{ case 0:{ printf("Nu s-a gasit nici o solutie !"); break; }
case 1:{ printf("Solutia exacta: %f",x2); break; }
case 2:{ printf("Solutia aproximativa: %f",x2); break; }
}
getch();
}

Exemplu. Problema turnurilor din Hanoi ilustrează foarte bine avantajele metodei
divide et impera. Problema poate fi enunţată astfel: se presupune că există trei tije
a, b, c. Pe tija a sînt plasate n discuri de diametre diferite în ordinea descrescătoare
a acestora (de la bază spre vîrf). Se cere ca cele n discuri de pe tija a să fie
deplasate pe tija c astfel respectînd următoarele restricții:

 la fiecare mutare este deplasat un singur disc, aflat pe poziţia superioară pe una
din tije;
 oricare din discuri poate fi aşezat numai pe un disc de diametru mai mare, cu
excepția primului disc amplasat pe o tijă (la bază), care nu are un alt disc sub el;
 tija b poate fi folosită pentru deplasări intermediare.

Notînd cu P(n,a,c) problema transferului celor n discuri de pe tija a pe tija


c, pentru rezolvarea ei putem raţiona în modul următor: dacă s-a rezolvat problema
6 Metode de programare

P(n-1,a,b) (mutarea primelor 𝑛 − 1 discuri de pe tija a pe tija b), atunci discul de


diametru maxim care se află încă pe tija a este deplasat pe tija c şi în continuare se
rezolvă problema P(n-1,b,c) (mută 𝑛 − 1 discuri de pe tija b pe tija c). Soluţia
recursivă este prezentată în funcția Hanoi de mai jos.

Exemplu. Pentru 𝑛 = 3, presupunînd că discurile sînt numerotate în ordinea


crescătoare a diametrelor cu etichetele 1, 2, 3, o soluţie a problemei poate fi
descrisă astfel:

Tija a Tija b Tija c Mutarea efectuată


1
2 ac
3
2
ab
3 1
3 2 1 cb
1
ac
3 2
1
ba
2 3
1 2 3 bc
2
ac
1 3
1
2
3

#include <stdio.h>
#include <conio.h>

void hanoi(unsigned n, unsigned a, unsigned b, unsigned c)


{
if(n>0)
{ hanoi(n-1,a,c,b);
printf("\nMuta un disc de pe %d pe %d",a,c);
hanoi(n-1,b,a,c);
}
}

void main()
{
unsigned n,a,b,c;
a=1;b=2;c=3;
printf("Numarul de discuri");
Algoritmi și tehnici de programare 7

scanf("%d",&n);
hanoi(n,a,b,c);
getch();
}

Observaţie. Rezolvarea problemei turnurilor din Hanoi a fost realizată pe baza


unei metode de reducere şi anume: problema deplasării a 𝑛 discuri 𝑃(𝑛) a fost
redusă succesiv la rezolvarea problemelor mai simple 𝑃(𝑛 − 1), 𝑃(𝑛 − 2), …,
𝑃(1).

Observaţie. Examinînd exemplele anterioare. se ajunge la concluzia că soluţiile


recursive propuse se bazează fie pe o metodă de tip reducere, fie pe o metodă de
descompunere. În toate cazurile a fost posibilă „sinteza” unei soluţii a problemei
date din soluţiile subproblemelor la care problema s-a redus, respectiv în care s-a
descompus.

De asemenea, pentru fiecare dintre problemele considerate au fost definite


subproblemele primitive (condiţii terminale) a căror soluţie este „cunoscută” sau
dată. Metoda de rezolvare se numeşte Divide et Impera (dezbină şi stăpîneşte) şi
semnifică ideea prin care este realizată construcţia soluţiei.

4.2. Metoda Greedy

Metoda optimului local (Greedy) este o metodă rapidă (cu consum mic de
resurse) folosită în multe probleme de optimizare pentru obținerea unei soluții bune
(acceptabile), posibil chiar soluția optimă.

Obținerea soluției este rapidă deoarece este o metodă simplă, în maxim 𝑛


pași (complexitatea metodei este în principiu de ordinul 𝑛 – O(𝑛) – dar poate fi
mai mare în unele probleme). Soluția obținută poate fi optimă (cea mai bună) sau
nu. În unele probleme este considerată acceptabilă o soluție care nu e optimă dar
poate fi obținută rapid (cu consum mic de resurse), în condițiile în care găsirea
soluției optime necesită consum exagerat de mare de resurse. Pentru unele
probleme metoda Greedy poate chiar să nu găsească o soluție, deși aceasta există.

Numele metodei provine de la faptul că la fiecare pas se face cea mai bună
alegere în contextul local (se alege optimul local), deși aceasta nu garantează o
soluție optimă la nivel global și nici măcar găsirea unei soluții (greedy – lacomul,
care se repede la ce are mai bun în față, ignorînd contextul mai larg). O soluție
8 Metode de programare

acceptabilă găsită prin această metodă poate fi folosită ca bază de pornire pentru
aplicare unui alt algoritm, în vederea găsirii soluției optime.

Motivul pentru care soluția obținută nu este întotdeauna optimă este


următorul: metoda optimului local presupune luarea unei decizii la fiecare pas, în
funcție de contextul local disponibil. Această decizie nu mai poate fi schimbată
într-un pas ulterior, deși ea poate fi greșită în contextul global. Este posibil ca
soluția obținută să fie nu numai non-optimă, ci chiar cea mai proastă posibilă.

Problemele pentru care se poate aplica metoda optimului local se prezintă


astfel:

 datele de intrare ale problemei pot fi descrise ca o mulțime cu 𝑛 elemente (𝐴);


 o soluție posibilă este o submulțime (𝐵 ⊆ 𝐴) care îndeplinește o condiție dată
(𝐵 este acceptabilă);
 pot exista mai multe submulțimi diferite acceptabile (soluții posibile), dintre
care una este considerată soluție optimă, pe baza unui criteriu care trebuie
maximizat (minimizat).

Soluțiile acceptabile au următoarea proprietate: dacă submulțimea 𝐵 este o


soluție acceptabilă, atunci orice submulțime 𝐶 ⊂ 𝐵 este tot o soluție acceptabilă.

Metoda optimului local implică utilizarea următoarelor operații (care nu


întotdeauna sînt evidente în rezolvare):

 alegerea unui element candidat 𝑥 din mulțimea 𝐴 (𝑥 ∈ 𝐴);


 verificarea acceptabilității elementului ales: adăugarea elementului la soluția
parțială construită o menține acceptabilă sau o face inacceptabilă?
 adăugarea elementului ales la soluția parțială, dacă ea rămîne acceptabilă.

Numărul de pași (𝑝) poate fi mai mic decît numărul de elemente ale
mulțimii inițiale (𝑛). 𝑝 poate să fie cunoscut de la început sau nu; în ultimul caz,
verificarea făcută la fiecare pas poate determina și oprirea algoritmului.

Considerînd aceste operații, algoritmul poate fi descris astfel:

 se primește mulțimea 𝐴 cu 𝑛 elemente


 se inițializează soluția 𝐵 ca mulțime vidă
 repetă de 𝑝 ≤ 𝑛 ori
o alege un element candidat 𝑥 din mulțimea 𝐴
o verifică dacă 𝐵 ∪ {𝑥} este soluție acceptabilă
Algoritmi și tehnici de programare 9

o dacă da, adaugă 𝑥 la mulțimea 𝐵: 𝐵 = 𝐵 ∪ {𝑥}


 se trimite mulțimea 𝐵 ca soluție

Algoritmul anterior este prezentat în forma cea mai generală, fără a preciza
exact cum se alege un element sau cum se verifică dacă elementul ales este
acceptabil. Aceste aspecte depind de problema concretă care se rezolvă și se
implementează ca atare. De obicei se poate asocia o valoare numerică fiecărui
element al mulțimii 𝐴 și, în funcție de criteriul de optimizare, se alege cel cu
valoarea cea mai mare sau cea mai mică.

O variantă a algoritmului rezolvă problema alegerii în felul următor:


elementele mulțimii 𝐴 sînt aranjate de la început în ordinea în care vor fi analizate
în vederea construirii soluției. În acest caz elementul candidat este întotdeauna
următorul element al mulțimii 𝐴. Adesea prelucrarea mulțimii 𝐴 este o sortare
crescătoare sau descrescătoare (pe baza valorilor numerice asociate elementelor), în
funcție de direcția criteriului de optimizare. Existența acestei prelucrări schimbă
ordinul de complexitate a algoritmului la nivel general, în sens nefavorabil (același
efect îl poate avea și folosire unui criteriu complex de alegere a elementului
candidat).

4.2.1. Exemple de probleme rezolvate prin algoritmi de tip Greedy:

Exemplele următoare sînt probleme tipice; enunțurile lor pot fi întîlnite ca


atare sau cu diverse variațiuni.

4.2.1.1. Arborele parțial de cost minim (maxim).


Atît algoritmul lui Prim cît și algoritmul lui Kruskal au fost studiați în
capitolul Grafuri, ca urmare nu vom insista asupra algoritmilor în sine aici.

Ce anume din acești algoritmi îndreptățește calificarea lor ca fiind


algoritmi de tip Greedy? În ambii algoritmi putem identifica elementele generale
specifice metodei Greedy astfel:

 există o mulțime inițială 𝐴 din care se aleg elementele care vor compune soluția
(𝐴 este mulțimea muchiilor grafului; fie 𝑛 numărul de muchii și 𝑚 numărul de
vîrfuri ale grafului);
 fiecărui element al mulțimii 𝐴 îi este asociată o valoare numerică (ponderea
muchiei);
10 Metode de programare

 mulțimea inițială se sortează crescător, în ordinea ponderilor asociate muchiilor;


 din mulțimea 𝐴 se aleg, în ordine, primele 𝑚 − 1 elemente care nu formează
cicluri (criteriul de alegere).

În cazul algoritmului lui Kruskal numărul de pași efectuați nu este


cunoscut apriori. La fiecare pas algoritmul acceptă sau respinge elementul următor
(muchia), terminîndu-se după ce a acceptat 𝑚 − 1 muchii. În cel mai rău caz,
numărul maxim de pași este egal cu numărul muchiilor din graf.

În cazul algoritmului lui Prim numărul de pași este cunoscut apriori (𝑚 −


1), dar alegerea elementului următor este mai complexă.

4.2.1.2. Problema rucsacului


Se consideră un mijloc de transport cu o capacitate dată (𝐶). Cu acesta
trebuie transportate obiecte dintr-o mulțime 𝐴 = {𝑎𝑖 | 𝑖 = 1, 𝑛}. Fiecare obiect
ocupă o capacitate specifică 𝑐𝑖 și aduce un cîștig specific 𝑣𝑖 – fiecare obiect luat
separat încape în mijlocul de transport. Se cere să se determine o modalitate de
încărcare care să maximizeze cîștigul obținut la un transport.

Dacă obiectele transportate pot fi fracționate, problema este numită


continuă. În acest caz se va utiliza întotdeauna întreaga capacitate de transport.

Dacă obiectele nu pot fi fracționate, problema este numită întreagă. În


acest caz e posibil ca soluția obținută să nu utilizeze întreaga capacitate de
transport. De asemenea, soluția obținută poate să nu fie optimă. În plus, e posibil să
existe o soluție de utilizare a întregii capacități de transport, dar aceasta să nu fie
găsită prin algoritmul de tip Greedy.

Pentru aplicarea algoritmului, elementele mulțimii 𝐴 sînt sortate în ordinea


descrescătoare a valorilor numerice asociate. Valorile imediat disponibile sînt
capacitatea specifică și cîștigul specific. Utilizarea uneia dintre ele este în spiritul
metodei Greedy, dar soluțiile obținute nu sînt cele mai bune, cu șanse de a fi chiar
foarte îndepărtate de scopul urmărit. Cea mai bună soluție se obține dacă se
folosește o valoare derivată: cîștigul unitar (𝑐𝑖 /𝑣𝑖 ).

La fiecare pas se ia în considerare următorul element al mulțimii sortate


(alegere). Dacă acesta încape în mijlocul de transport (verificare), e adăugat la
soluție (adăugare) și se diminuează capacitatea de transport rămasă disponibilă.
Dacă nu încape, atunci:
Algoritmi și tehnici de programare 11

 dacă problema e continuă, se ia din obiectul curent atît cît încape (adăugare);
 dacă problema e întreagă, se respinge obiectul curent.

În ambele cazuri restul obiectelor se resping și algoritmul se termină.

Pentru reprezentarea soluției se poate folosi un vector 𝐵 în care fiecare


element are valoarea 1 (obiect acceptat) sau 0 (obiect respins). În cazul în care se
acceptă fracționarea obiectelor, unul singur va fi fracționat iar elementul din
vectorul soluție corespunzător lui va avea o valoare în intervalul (0,1). Elementul
𝑏𝑖 arată dacă obiectul 𝑎𝑖 este acceptat sau respins (sau acceptat fracționat).

Funcția următoare rezolvă problema rucsacului în formă continuă.


Parametrii au semnificația indicată mai jos, cu observația că 𝑐 și 𝑣 sînt vectori, cu
elementele în ordinea descrescătoare a cîștigului specific asociat fiecărui obiect.
//problema rucsacului, continua
// I: capacitate totala (q), nr. obiecte (n), capacitate ocupata (c), cistig (v)
// E: solutia x (x[i]= raportul in care e incarcat obiectul i)
void Rucsac_c(float q, int n, float* c, float* x)
{ float qr;
int i,j;

qr=q; //capacitatea ramasa disponibila, initial capacitatea totala


for(i=0; i<n && qr>0; i++)
if(qr>=c[i])
{ x[i]=1;
qr-=c[i]; //qr-=c[i]*x[i]
}
else
{ x[i]=qr/c[i];
qr=0; //qr-=c[i]*x[i]
for(j=i+1;j<n;j++)
x[j]=0;
}
}

Temă. Scrieți o funcție care implementează problema rucsacului în forma


întreagă.

4.2.1.3. Suma maximă


Se dă o mulțime de elemente reale 𝐴 = {𝑎1 , 𝑎2 , … , 𝑎𝑛 , }. Se cere să se
determine o submulțime 𝑆 ⊆ 𝐴 astfel încît suma elementelor submulțimii 𝑆 să fie
cea mai mare posibilă.

Fiind valori reale, elementele mulțimii 𝐴 sînt pozitive, nule sau negative,
fiecare avînd un efect specific asupra sumei. În spiritul cerinței, trebuie evitate
elementele negative și trebuie alese cele pozitive. Elementele nule nu afectează
12 Metode de programare

suma și pot fi alese sau nu (de exemplu, dacă se cere să găsim o submulțime cu
număr cît mai mic de elemente, vom evita și elementele nule).

O sortare inițială descrescătoare a mulțimii date asigură un număr mai mic


de pași la aplicarea algoritmului Greedy (oprire la primul element nul/negativ), dar
crește complexitatea la nivel general, de aceea e preferabil (în acest caz) să nu se
facă prelucrarea inițială și să se analizeze toate elementele mulțimii inițiale.

Funcția următoare rezolvă problema sumei maxime, fără sortare prealabilă


a mulțimii inițiale, fiind astfel necesară parcurgerea întregii mulțimi.

// Submultimea care da suma maxima: se iau doar elementele (strict)


pozitive
// I: multimea (a), numar de elemente (n)
// E: submultimea (b), numar de elemente (nr)
void suma_maxima(float* a, int n, float *b, int &nr)
{ int i;
nr=0;
for(i=0;i<n;i++)
if(a[i]>=0) // = pentru zero, care nu afecteaza suma.
b[nr++]=a[i];
}

Temă. Scrieți o funcție care sortează mulțimea inițială înainte de


construirea soluției. Care variantă este preferabilă pentru această problemă?

4.3. Metoda Backtracking

Adesea soluția unei probleme este formată din mai multe elemente
distincte și poate fi prezentată ca un vector de lungime 𝑛: 𝑆𝑜𝑙 = (𝑥1 , 𝑥2 , … , 𝑥𝑛 ),
ale cărui elemente satisfac anumite condiții. Fiecare dintre elementele componente
ale soluției (𝑥𝑖 ) este ales dintr-o mulțime ordonată și finită 𝑆𝑖 , care are 𝑠𝑖 elemente
(𝑥𝑖 ∈ 𝑆𝑖 ), deci soluția aparține spațiului cu 𝑛 dimensiuni 𝑆 = 𝑆1 × 𝑆2 × … × 𝑆𝑛
(𝑆𝑜𝑙 ∈ 𝑆). Spațiul 𝑆 este numit spațiul soluțiilor pentru problema dată. Numărul de
elemente ale acestui spațiu este
𝑛
|𝑆| = ∏ 𝑠𝑖
𝑖=1
Algoritmi și tehnici de programare 13

Evident, nu orice element din spațiul soluțiilor constituie o soluție acceptabilă și


este posibil ca mai multe elemente din spațiul soluțiilor să constituie soluții
acceptabile, în aceeași măsură. Este posibil să se ierarhizeze soluțiile acceptabile
astfel încît să se aleagă cea mai bună dintre ele.

Spațiul soluțiilor poate fi imaginat ca un masiv multidimensional sau un


arbore (numărul de elemente ale soluției este numărul de dimensiuni ale masivului
respectiv numărul de niveluri ale arborelui). Căutarea unei soluții este echivalentă
cu parcurgerea masivului (în ordine lexicografică, de exemplu) sau parcurgerea
arborelui (în preordine, de exemplu). Atunci cînd, în timpul parcurgerii, se
stabilește că s-a găsit o soluție a problemei, setul de coordonate ale elementului
curent (în cazul masivului multidimensional) respectiv traseul de la rădăcină pînă
la nodul frunză curent (în cazul arborelui) indică elementele respective din
mulțimile 𝑆𝑖 care compun soluția.

Exemple

1. Fie problema următoare: să se determine poziția celui mai mic element


dintr-o matrice dreptunghiulară cu 𝑚 linii și 𝑛 coloane. Soluția problemei este
formată dintr-o pereche de coordonate 𝑆𝑜𝑙 = (𝑙, 𝑐) reprezentînd indicii liniei
respectiv coloanei unde se află elementul minim (pentru scopul exemplului
considerăm că există un singur element cu valoarea minimă). Primul element al
soluției aparține mulțimii indicilor liniilor matricei, 𝑆1 = 𝐿 = {0,1, … , 𝑚 − 1}, care
are 𝑠1 = 𝑚 elemente. Al doilea element al soluției aparține mulțimii indicilor
coloanelor matricei, 𝑆2 = 𝐶 = {0,1, … , 𝑛 − 1}, care are 𝑠2 = 𝑛 elemente. Spațiul
soluțiilor problemei este mulțimea 𝑆 = 𝐿 × 𝐶, care are |𝑆| = 𝑚 ⋅ 𝑛 elemente.

2. Să presupunem că problema este de a ajunge în vârful unui munte pornind


de la baza lui. De la bază se pornește pe o potecă iar pe drum se întîlnesc bifurcații.
Nu toate potecile formate conduc spre vârful muntelui. În acest caz, o soluţie e
constituită din șirul de decizii care se iau la fiecare punct de ramificare a potecilor,
astfel încît să se ajungă în vîrf. Drumurile obținute pot fi ierarhizate după diverse
criterii precum: distanța, efortul necesar, trecerea prin anumite puncte preferate etc.
Impunerea unui astfel de criteriu duce la alegerea unui anumit drum dintre cele
posibile (dacă sînt mai multe).

3. Fie un labirint cu una sau mai multe ieșiri. Problema este să se găsească un
drum dintr-un punct dat (care poate fi oricare din camerele labirintului, inclusiv cea
14 Metode de programare

de intrare) pînă la una dintre ieșiri (sau o anumită ieșire). La fiecare pas trebuie
aleasă o opțiune de continuare a drumului. Dacă opțiunea aleasă conduce la o
cameră fără altă ieșire („capcană”), atunci se revine la pasul anterior și se alege altă
opțiune de continuare a drumului.

Soluția unei probleme poate fi găsită în două feluri: prin calcule sau prin
căutarea în spațiul soluțiilor, cu reținerea acelor elemente care satisfac condițiile
problemei. Este preferabilă soluționarea prin calcule1 (datorită consumului mic de
resurse), dar nu întotdeauna e disponibil un astfel de algoritm. Una din metodele de
căutare în spațiul soluțiilor este metoda backtracking. Ea implică o verificare a
tuturor elementelor din spațiul soluțiilor (parcurgerea întregului spațiu). Problema
labirintului constituie un exemplu tipic de aplicare a metodei backtracking.

Backtracking este o metodă costisitoare deoarece implică un consum mare


de resurse, care crește exponențial odată cu dimensiunea problemei. Metoda este
utilizată atunci cînd nu există altă posibilitate de rezolvare a problemei date. Pentru
a reduce complexitatea metodei, este posibilă combinarea cu alte metode, în scopul
eliminării unor porțiuni ale spațiului soluțiilor din procesul de căutare (în acele
porțiuni cu siguranță nu se află soluții acceptabile).

Prin utilizarea acestei metode se explorează întregul spațiu al soluțiilor,


găsind toate soluțiile acceptabile pentru problema dată. În unele probleme se poate
cere selectarea acelor soluții care îndeplinesc sau optimizează un criteriu dat.

Backtracking este o metodă de natură recursivă care poate fi implementată


iterativ sau recursiv.

Metoda este formalizată astfel: soluția este exprimată sub forma unui
vector 𝑋 = (𝑥1 , 𝑥2 , … , 𝑥𝑛 ). Pentru fiecare element al soluției este definită
mulțimea valorilor posibile (𝑥𝑖 ∈ 𝑆𝑖 ), definind astfel spațiul soluțiilor problemei
𝑋 ∈ 𝑆 = 𝑆1 × 𝑆2 × … × 𝑆𝑛 . Elementele care compun soluția trebuie să
îndeplinească anumite condiții, numite condiții interne. Condițiile interne pot fi
aplicate asupra unui element, independent, sau pot exprima relații între unele

1
Exemple:
a) Rezolvarea ecuației de gradul 1 (𝑎 ∙ 𝑥 + 𝑏 = 0 unde 𝑎, 𝑏 ∈ ℝ) se poate face prin calcule
(𝑥 = −𝑏 ∕ 𝑎) sau prin verificarea tuturor elementelor din spațiul soluțiilor (mulțimea ℝ).
b) Rezolvarea ecuației de gradul 2 (𝑎 ⋅ 𝑥 2 + 𝑏 ⋅ 𝑥 + 𝑐 = 0 unde 𝑎, 𝑏, 𝑐 ∈ ℝ) se poate face prin
calcule sau prin verificare tuturor elementelor din spațiul soluțiilor (mulțimea ℂ2 ).
În ambele exemple este evident preferabilă utilizarea formulelor, a doua variantă fiind practic
imposibilă (spațiul soluțiilor este infinit).
Algoritmi și tehnici de programare 15

elemente ale soluției (posibil toate). Elementele din spațiul soluțiilor care
îndeplinesc aceste condiții se numesc soluții rezultat. Condițiile de continuare
constituie restricția condițiilor interne la primele 𝑖 elemente vectorului 𝑋. Atunci
cînd 𝑖 este egal cu 𝑛 condițiile de continuare reprezintă chiar condițiile interne.

Construirea unei soluții se face prin alegerea de valori pentru fiecare


element (din mulțimea specifică lui), pe rînd: elementul 𝑥𝑖 primește o valoare din
𝑆𝑖 numai după ce toate elementele anterioare (𝑥1 , 𝑥2 , … , 𝑥𝑖−1 ) au primit valori.
După alegerea unei valori pentru 𝑥𝑖 se verifică dacă soluția parțială (𝑥1 , 𝑥2 , … , 𝑥𝑖 )
îndeplinește condițiile de continuare. Îndeplinirea condițiilor de continuare este
strict necesară. Numai dacă acestea sînt îndeplinite se trece la alegerea unei valori
din 𝑆𝑖+1 pentru elementul 𝑥𝑖+1. Valoarea aleasă este considerată consumată și nu
va fi disponibilă dacă trebuie aleasă altă valoare pentru elementul 𝑥𝑖 .
Neîndeplinirea condițiilor de continuare înseamnă că orice valori s-ar alege pentru
elementele următoare (𝑥𝑗 cu 𝑗 = 𝑖 + 1, 𝑛), nu se va ajunge la o soluție rezultat.
Dacă nu sînt îndeplinite condițiile de continuare se alege altă valoare pentru 𝑥𝑖 din
mulțimea 𝑆𝑖 , o valoare care nu a fost aleasă anterior (neconsumată). Dacă în
mulțimea 𝑆𝑖 nu mai sînt elemente disponibile, atunci se revine la elementul anterior
𝑥𝑖−1 și se alege pentru el altă valoare (neconsumată) din mulțimea 𝑆𝑖−1 . Atunci
cînd are loc o revenire la elementul anterior, se consideră că nici o valoare din
mulțimea 𝑆𝑖 nu a fost consumată. Această revenire a dat numele metodei: revenirea
înseamnă întoarcerea (cu unul sau mai mulți pași) pe drumul urmat în construirea
soluției, pînă la o bifurcație de unde se poate continua pe alt drum.

Îndeplinirea condițiilor de continuare de către valorile 𝑣1 , 𝑣2 , … , 𝑣𝑖 alese


pentru elementele 𝑥1 , 𝑥2 , … , 𝑥𝑖 ale soluției nu garantează obținerea unei soluții
rezultat ale cărei prime 𝑖 componente să aibă aceste valori. O alegere bună a acestor
condiții poate duce la reducerea substanțială a volumului de calcul, prin eliminarea
unor porțiuni ale spațiului soluțiilor din procesul de căutare (combinarea cu alte
metode poate extinde ariile eliminate din procesul de căutare). În cazul ideal,
condițiile de continuare nu sînt doar necesare ci și suficiente.

Pentru a ține evidența valorilor consumate se notează cu 𝐶𝑖 , 𝑖 = ̅̅̅̅̅


1, 𝑛,
mulțimea valorilor consumate din mulțimea 𝑆𝑖 la momentul curent.

Atunci cînd se încearcă alegerea unei valori noi pentru elementul 𝑥𝑖 trebuie
precizate următoarele: valorile deja atribuite pentru elementele 𝑥1 , 𝑥2 , … , 𝑥𝑖−1
(fie acestea 𝑣1 , 𝑣2 , … , 𝑣𝑖−1 ), și mulțimile de valori consumate pentru elementele
𝑥1 , 𝑥2 , … , 𝑥𝑖 (fie acestea 𝐶1 , 𝐶2 , … , 𝐶𝑖 ) – pentru elementele 𝑥𝑖+1 , 𝑥𝑖+2 , … , 𝑥𝑛
16 Metode de programare

nu s-au consumat încă valori, deci mulțimile 𝐶𝑖+1 , 𝐶𝑖+2 , … , 𝐶𝑛 sînt vide. Toate
aceste date pot fi reprezentate sub forma unui tabel, numit configurație:
𝑣1 … 𝑣𝑖−1 𝑥𝑖 … 𝑥𝑛
(𝐶 … 𝐶𝑖−1 |𝐶𝑖 ∅ ∅)
1

Elementele din acest tabel au următoarea semnificație:

 componentele 𝑥1 , 𝑥2 , … , 𝑥𝑖−1 au primit valorile 𝑣1 , 𝑣2 , … , 𝑣𝑖−1 ;


 valorile atribuite satisfac condiţiile de continuare (se află în stînga barei
verticale);
 urmează încercarea de atribuire a unei valori pentru componenta 𝑥𝑖 . Ea va
primi o valoare neconsumată (din mulţimea 𝑆𝑖 − 𝐶𝑖 );
 valorile consumate pentru componentele 𝑥1 , 𝑥2 , … , 𝑥𝑖−1 sînt cele din
mulţimile 𝐶1 , 𝐶2 , … , 𝐶𝑖−1 (valorile 𝑣1 , 𝑣2 , … , 𝑣𝑖−1 sînt considerate
consumate, deci sînt incluse în mulțimile respective);
 pentru componentele pentru 𝑥𝑖+1 , 𝑥𝑖+2 , … , 𝑥𝑛 nu s-a consumat nici o valoare,
deci mulţimile 𝐶𝑖+1 , 𝐶𝑖+2 , … , 𝐶𝑛 sînt vide;
 pînă acum au fost construite eventuale soluţii de forma (𝑣1 , 𝑣2 , … ,
𝑣𝑖−1 , 𝑐𝑖 , … ), cu 𝑐𝑖 ∈ 𝐶𝑖 şi eventuale soluţii de forma (𝑐1 , 𝑐2 , … , 𝑐𝑖−1 , … ) cu
(𝑐1 , 𝑐2 , … , 𝑐𝑖−1 ) ∈ 𝐶1 × 𝐶2 × … × 𝐶𝑖−1 şi (𝑐1 , 𝑐2 , … , 𝑐𝑖−1 ) ≠ (𝑣1 , 𝑣2 , … ,
𝑣𝑖−1 ).

Găsirea unei soluții constituie un proces prin care se pornește de la o


configurație inițială și se caută în spațiul soluțiilor una sau toate configurațiile
soluție. După găsirea unei configurații soluție algoritmul continuă cu analizarea
altor elemente și găsirea altor configurații soluție, pînă cînd se ajunge la
configurația finală (există posibilitatea opririi căutării la găsirea primei configurații
soluție).

Tipurile speciale de configurații întîlnite sînt următoarele:


𝑥1 … 𝑥𝑛
 configurație inițială: (| ) – procesul de căutare încă nu a început.
∅ ∅ ∅
𝑣1 … 𝑣𝑛
 configurație soluție: (𝐶 … 𝐶 |) – s-au ales valori acceptabile pentru toate
1 𝑛
elementele soluției, mai există porțiuni neexplorate ale spațiului soluțiilor.
𝑥1 … 𝑥𝑛
 configurație finală: (|𝑆 ∅ ∅ ) – nu mai există valori neconsumate pentru
1
primul element al soluției, deci nu se mai pot construi soluții noi. S-a încheiat
explorarea întregului spațiu al soluțiilor.
Algoritmi și tehnici de programare 17

Asupra unei configurații se pot efectua următoarele operații:

1) Atribuie și avansează. Are loc atunci cînd există valori neconsumate pentru
𝑥𝑖 , iar valoarea aleasă 𝑣𝑖 ∈ 𝑆𝑖 − 𝐶𝑖 are proprietatea că soluția parțială (𝑣1 , 𝑣2 , … ,
𝑣𝑖−1 , 𝑣𝑖 ) îndeplineşte condiţiile de continuare. În acest caz 𝑣𝑖 se atribuie lui 𝑥𝑖 şi
se adaugă la 𝐶𝑖 după care se avansează la componenta 𝑥𝑖+1 . Operaţia este notată
astfel:
… 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 … … 𝑣𝑖−1 𝑣𝑖 𝑥𝑖+1 …
(… 𝐶 |𝐶 ∅ …) → (… 𝐶𝑖−1 𝐶𝑖 ∪ {𝑣𝑖 }| ∅ )
𝑖−1 𝑖 …

2) Încercare eșuată. Se produce atunci cînd mai există valori neconsumate


pentru 𝑥𝑖 , iar valoarea aleasă 𝑣𝑖 face ca (𝑣1 , 𝑣2 , … , 𝑣𝑖−1 , 𝑣𝑖 ) să nu îndeplinească
condiţiile de continuare. În acest caz se adaugă 𝑣𝑖 la 𝐶𝑖 dar nu se avansează la
următoarea componentă. Operaţia este notată astfel:
… 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 … … 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 …
(… 𝐶 |𝐶 ∅ …) === (… 𝐶𝑖−1 |𝐶𝑖 ∪ {𝑣𝑖 } ∅ …)
𝑖−1 𝑖

3) Revenire. Operaţia are loc atunci cînd toate valorile pentru 𝑥𝑖 au fost
consumate (𝐶𝑖 = 𝑆𝑖 ), deci este imposibilă alegerea unei valori noi. Se revine la
componenta 𝑥𝑖−1 încercînd atribuirea unei noi valori, neconsumate, pentru ea.
Pentru această nouă valoare a lui 𝑥𝑖−1 se vor încerca ulterior toate valorile posibile
pentru 𝑥𝑖 , deci are loc o reconsiderare a valorilor consumate pentru 𝑥𝑖 , 𝐶𝑖 devenind
mulţimea vidă. Operaţia este notată astfel:
… 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 … … 𝑣𝑖−2 𝑥𝑖−1 𝑥𝑖 …
(… 𝐶𝑖−1 |𝐶𝑖 ∅ …) ← (… 𝐶𝑖−2 |𝐶𝑖−1 ∅ …)

4) Revenire după construirea unei soluții. În acest caz se revine la situaţia în


care ultima componentă urmează să primească alte valori, dacă mai există, pentru
construirea altor soluţii. Operaţia este notată astfel:
… 𝑣𝑛 … 𝑣𝑛−1 𝑥𝑛
(… 𝐶 |) ⋘ (… 𝐶 |𝐶 )
𝑛 𝑛−1 𝑛

Deoarece mulțimile 𝑆𝑖 sînt finite, prin consumarea valorilor și reveniri se


ajunge după un număr finit de pași la configurația finală. Obținerea ei marchează
finalul procesului de căutare a soluțiilor, toate elementele spațiului soluțiilor fiind
analizate. Se poate spune că procesul de căutare se încheie atunci cînd 𝐶1 = 𝑆1 .

În implementare se folosește valoarea indicelui 𝑖 pentru a sesiza


următoarele situații:
18 Metode de programare

 obținerea unei configurații soluție (𝑖 = 𝑛 + 1): după atribuirea unei valori


pentru ultima componentă a soluției se avansează și se încearcă atribuirea unei
valori pentru poziția 𝑛 + 1, care nu există.
 obținerea configurației finale (𝑖 = 0): după consumarea tuturor valorilor
posibile pentru primul element al soluției se revine la elementul anterior, poziția
0, dar aceasta nu există.

Cu aceste considerații, procesul de căutare a soluțiilor este ușor de


implementat deoarece orice modificare de configurație afectează puține elemente:
poziția barei (reprezentată de indicele elementului curent 𝑖), componenta 𝑥𝑖 și
mulțimea de valori consumate 𝐶𝑖 .

În forma cea mai generală, algoritmul de căutare prin metoda backtracking


poate fi descris astfel:

 inițializare 𝑆1 , 𝑆2 , … , 𝑆𝑛
 𝐶𝑖 = ∅, 𝑖 = 1, 𝑛 //construire configurație inițială
 𝑖=1
 cît timp 𝑖 > 0 //cît timp configurația nu e finală
o dacă 𝑖 == 𝑛 + 1 //configurație de tip soluție?
 reține soluția 𝑆𝑜𝑙 = (𝑥1 , 𝑥2 , … , 𝑥𝑛 )
 𝑖 =𝑖−1 //revenire după găsirea unei soluții
o altfel
 dacă 𝐶𝑖 ≠ 𝑆𝑖 //mai sînt valori neconsumate
 alege o valoare 𝑣𝑖 ∈ 𝑆𝑖 − 𝐶𝑖
 𝐶𝑖 = 𝐶𝑖 ∪ {𝑣𝑖 }
 dacă (𝑣1 , 𝑣2 , … , 𝑣𝑖−1 , 𝑣𝑖 ) satisfac condițiile de continuare
o 𝑥𝑖 = 𝑣𝑖 //atribuie și avansează
o 𝑖 = 𝑖+1
 altfel //revenire
 𝐶𝑖 = ∅
 𝑖 =𝑖−1

Forma generală a algoritmului nu precizează cum se alege o valoare


𝑣𝑖 ∈ 𝑆𝑖 − 𝐶𝑖 . Alegerea se face ținînd cont de relația de ordine din fiecare mulțime
𝑆𝑖 . Conform acestei ordini se poate determina un prim element (primul ales) și, la
fiecare pas, un element următor. Deoarece mulțimile 𝑆𝑖 sînt finite, va exista mereu
un ultim element, pentru care nu există element următor.
Algoritmi și tehnici de programare 19

Aceste aspecte țin strict de problema rezolvată și se implementează ca


atare. Unele din problemele care se rezolvă prin metoda backtracking prezintă
particularități care ușurează implementarea:

 elementele mulțimilor 𝑆𝑖 sînt numerice (sau li se pot asocia valori numerice), în


progresie aritmetică cu valoarea inițială 𝑎𝑖 , rația 𝑟𝑖 și valoarea finală 𝑠𝑖 . De
multe ori toate mulțimile 𝑆𝑖 au aceeași valoare inițială (deseori aceasta este 1) și
aceeași rație (deseori aceasta este 1). În această situație a doua linie a
configurației nu mai trebuie reprezentată explicit deoarece valorile pentru
fiecare componentă se aleg în ordin crescătoare iar valorile consumate sînt cele
mai mici decît valoarea curentă 𝑣𝑖 (inclusiv aceasta) a elementului 𝑥𝑖 , deci e
suficientă cunoașterea lui 𝑣𝑖 pentru a cunoaște și 𝐶𝑖 .
 în multe probleme toate mulțimile 𝑆𝑖 sînt identice cu mulțimea 𝑆 = {1,2, … , 𝑛}.

Cu aceste considerații, algoritmul poate fi prezentat în următoarea formă:

 𝑖=1
 𝑥𝑖 = 𝑎𝑖 − 𝑟𝑖 //primul element minus rația, de multe ori 0=1-1
 cît timp 𝑖 > 0
o 𝑣𝑏 = 0 //variabila de impas
o cît timp 𝑥𝑖 < 𝑠𝑖 și 𝑣𝑏 == 0 //alegerea unei noi valori pentru 𝑥𝑖
 𝑥𝑖 = 𝑥𝑖 + 𝑟𝑖 //următorul element din 𝑆𝑖
 posibil(𝑥𝑖 ,vb) //sînt îndeplinite condițiile de continuare
o dacă 𝑣𝑏 == 0 //impas => revenire
 𝑖 =𝑖−1
o altfel
 dacă 𝑖 == 𝑛
 dacă condiții_finale(x) //configurație soluție?
o reține_soluția 𝑆𝑜𝑙 = (𝑥1 , 𝑥2 , … , 𝑥𝑛 )
 altfel //avans
 𝑖 =𝑖+1
 𝑥𝑖 = 𝑎𝑖 − 𝑟𝑖 //primul element din 𝑆𝑖 minus rația

În acest algoritm sînt utilizate trei proceduri, cu următoarele roluri:

 posibil verifică dacă valoarea aleasă pentru elementul curent îndeplinește


condițiile de continuare. Variabila de impas vb primește valoarea 1 în caz
afirmativ, 0 în caz contrar.
20 Metode de programare

 condiții_finale verifică dacă soluția completă construită este acceptabilă.


 reține_soluția are rolul de a memora configurația soluție curentă în vederea
prelucrării ulterioare. Verificarea condițiilor finale poate fi inclusă în acest
subprogram.

Față de varianta standard, pot să apară variațiuni: exista probleme în care


numărul de componente ale soluției poate fi variabil sau dintre toate soluțiile
posibile se alege una singură, care maximizează (sau minimizează) un criteriu – în
acest caz reține_soluția compară soluția curentă cu cea reținută anterior și o reține
pe ea dacă e mai bună sau o abandonează dacă cea anterioară este mai bună.

Avînd o natură recursivă, metoda backtracking este ușor de implementat


recursiv. O formă generală a subprogramului recursiv care implementează metoda
backtracking este următoare:

backtracking(i)
{ dacă (i==n+1)
retine_solutia();
altfel
x[i]=init(i);
cît timp ( următor(i) )
dacă ( posibil(i) )
backtracking(i+1);
}

În această formă, subprogramele apelate au următoarea semnificație: init


atribuie elementului curent al soluției o valoare care arată că încă nu s-a ales nici
una din valorile posibile din mulțimea 𝑆𝑖 asociată; următor atribuie elementului
curent al soluției următoarea valoare, în ordine, din mulțimea 𝑆𝑖 asociată, dacă
există o astfel de valoare.

Pentru problemele care prezintă particularitățile exprimate mai sus


(mulțimile 𝑆𝑖 exprimate ca progresii aritmetice), forma recursivă poate fi descrisă
astfel:

backtracking(i)
{ dacă (i==n+1)
retine_solutia();
altfel
pentru (j=a[i]; j<=s[i]; j+=r[i])
Algoritmi și tehnici de programare 21

{ x[i]=j;
dacă ( posibil(i) )
backtracking(i+1);
}
}

În ambele variante, subprograme retine_solutia și posibil au aceeași


semnificație ca mai sus. De asemenea, indicele 𝑖 are aceeași semnificație ca mai
sus. La apelul inițial al subprogramului recursiv se transmite valoarea 1 pentru
parametrul 𝑖.

Atunci cînd se implementează forma recursivă a rezolvării, trebuie


rezolvate probleme suplimentare legate de transmiterea parametrilor (pasul curent,
soluția parțială construită etc.)

4.3.1. Exemple de problem care se rezolvă prin metoda


backtracking

Exemplele următoare sînt probleme tipice; enunțurile lor pot fi întîlnite ca


atare sau cu diverse variațiuni. Pentru rezolvarea lor se scriu funcții care
implementează algoritmul general al metodei backtracking. Provocarea este, în
fiecare caz, exprimarea problemei în termenii specifici metodei backtracking.

4.3.1.1. Plata unei sume (cu/fără bancnotă unitate)


Fie 𝑛 tipuri de bancnote, cu valorile nominale 𝑡𝑖 , 𝑖 = ̅̅̅̅̅
1, 𝑛. Din fiecare tip
̅̅̅̅̅
este disponibilă cantitatea 𝑛𝑟𝑖 , 𝑖 = 1, 𝑛. Să se determine toate modalitățile în care
se poate plăti o sumă 𝑆 folosind aceste bancnote.

Condiție suplimentară: să se aleagă modalitatea de plată care utilizează


cele mai puține bancnote.

Pentru rezolvare, se reprezintă tipurile de bancnote disponibile într-un


vector (prin valorile lor nominale) iar în alt vector cantitățile disponibile din
tipurile respective (asigurînd corespondența tip-cantitate prin folosirea aceleiași
poziții în cei doi vectori). Soluția problemei poate fi exprimată sub forma unui
vector 𝑋 în care elementul 𝑥𝑖 indică numărul de bancnote de tipul 𝑡𝑖 utilizate: 𝑥𝑖 ∈
22 Metode de programare

𝑆𝑖 = {0,1,2, … , 𝑛𝑟𝑖 }, 𝑖 = ̅̅̅̅̅


1, 𝑛. Mulțimile 𝑆𝑖 se pot exprima ca progresii aritmetice
cu elementul inițial 𝑎𝑖 = 0, rația 𝑟𝑖 = 1 și ultimul element 𝑠𝑖 = 𝑛𝑟𝑖 .

O valoare atribuită elementului 𝑥𝑖 este acceptabilă dacă prin adăugarea


bancnotelor respective (cu valoarea 𝑥𝑖 ∗ 𝑡𝑖 ) la bancnotele anterior alese nu se
depășește suma de plată. Această condiție nu este suficientă; după ce se aleg și
acceptă valori pentru toate elementele 𝑥𝑖 , valoarea tuturor bancnotelor selectate
(∑𝑛𝑖=1 𝑥𝑖 ∗ 𝑡𝑖 ) trebuie să fie egală cu suma de plată.

Pentru a evita calcularea acestei sume la fiecare pas, se poate păstra într-o
variabilă suma curentă plătită, care se ajustează de fiecare dată cînd se atribuie o
valoare nouă unui element al soluției. Inițial aceasta este zero.

Algoritmul funcționează și dacă între tipurile de bancnote disponibile nu


se află și bancnota unitate.

Cu aceste considerații, se poate aplica forma generală a algoritmului


backtracking.

Pentru a utiliza o funcție recursivă care rezolvă această problemă, toate


datele care evoluează de la un apel la altul trebuie transmise ca parametri. Funcția
următoare constituie un exemplu de implementare. În scopul evitării complicațiilor
legate modul de implementare a masivelor în C/C++, se utilizează vectori cu
dimensiunea 𝑛 + 1, ignorînd primul element, la fel ca la problema anterioară.
//Plata unei sume
//I: suma (s), suma curenta (crt), tipuri (t), cantitati (nr), numar de tipuri (n)
// numarul de solutii gasite (ns), pasul curent (i), solutia (x)
//E: numarul de solutii
int plata(int suma, int crt, int* t, int* nr, int n, int ns, int i, int* x)
{ int j;
if(i==(n+1) )
retine(t, x, n, ++ns);
else
{ for(j=0; j<=nr[i]; j++)
{ x[i]=j;
crt+=t[i]*j;
if( posibil(x, i, n, suma,crt) )
ns=plata(suma, crt, t, nr, n, ns, i+1, x);
crt-=t[i]*j;
}
}
return ns;
}

La apelul inițial suma curentă are valoarea zero iar pasul curent este 1
(configurație inițială).
Algoritmi și tehnici de programare 23

Funcția următoare verifică dacă numărul de bancnote alese pentru tipul


curent este acceptabil. Ea este folosită și pentru a verifica dacă întreaga soluție
construită este acceptabilă.
//I: solutia (x), pasul curent (i), numarul de tipuri (n), suma de plata (s)
// suma curenta (c)
//E: 1 daca valoarea este acceptabila, 0 daca nu e acceptabila
int posibil(int* x, int i, int n, int s, int c)
{ if(i==n)
return s==c;
else
return s>=c;
}

Funcția retine afișează soluția găsită pe ecran. În exemplul următor se


afișează numărul soluției și pentru fiecare tip de bancnotă numărul de unități
folosite și suma cumulată.
//I: tipuri (t), solutia (x), numarul de tipuri (n), numarul de solutii (ns)
void retine(int* t, int* x, int n, int ns)
{ int i,s;
s=0;
printf("\n\nSolutia numarul %d", ns);
for(i=1;i<=n;i++)
printf("\n %3d * %2d = %4d, suma=%4d",t[i],x[i],t[i]*x[i],s+=t[i]*x[i]);
if(r=='n')
{ printf("\n\nUrmatoarea (n) sau Ultima (l)?");
r=_getch();
}
}
Exemplu de soluție afișată de funcția anterioară:
Solutia numarul 1
1 * 4 = 4, suma= 4
5 * 0 = 0, suma= 4
10 * 3 = 30, suma= 34
50 * 0 = 0, suma= 34
100 * 0 = 0, suma= 34
200 * 1 = 200, suma= 234
500 * 2 = 1000, suma=1234

În acest exemplu s-au folosit următoarele tipuri de bancnote: 1, 5, 10, 50,


100, 200, 500, cu cantitățile maxime 20 pentru fiecare tip, pentru a plăti o sumă
totală de 1234 unități. Pentru aceste valori se obțin 9414 variante de plată.

Temă. Scrieți o funcție iterativă pentru rezolvarea problemei de mai sus


prin metoda backtracking.
24 Metode de programare

Pentru a rezolva condiția suplimentară (utilizarea unui număr minim de


bancnote) există două variante: dacă este disponibilă bancnota unitate, e
recomandată utilizarea metodei Greedy, mult mai rapidă; dacă bancnota unitate nu
e disponibilă, atunci se poate modifica funcția retine de mai sus astfel: în funcție se
calculează numărul de bancnote folosite (∑𝑛𝑖=1 𝑥𝑖 ) și se compară cu numărul de
bancnote folosite de soluția reținută anterior. Dacă s-a obținut o soluție mai bună,
este păstrată aceasta, altfel soluția curentă e ignorată. Înainte de începerea căutării,
se inițializează numărul de bancnote utilizate cu numărul maxim de bancnote
disponibile (∑𝑛𝑖=1 𝑛𝑟𝑖 ).

Temă. Realizați modificările necesare pentru a reține doar soluția de


plată care utilizează numărul minim de bancnote.

4.3.1.2. Generarea tuturor permutărilor


Să se genereze toate permutările unei mulțimi cu 𝑛 elemente.

Problema generării permutărilor unei mulțimi se exprimă foarte ușor în


termenii metodei backtracking, dacă se reprezintă mulțimea liniar și se asociază
fiecărui element o valoare numerică, în funcție de poziția ocupată. În continuare
fiecare element va fi reprezentat de o valoare numerică 𝑥𝑖 , 𝑖 = ̅̅̅̅̅
1, 𝑛 cu semnificația
următoare: 𝑥𝑖 este poziția elementului 𝑖 în permutare; pentru mulțimea inițială 𝑥𝑖 =
𝑖. Cu aceste considerații, spațiul soluțiilor este definit prin intermediul mulțimilor
identice 𝑆𝑖 , 𝑥𝑖 ∈ 𝑆𝑖 = {1, 2, … , 𝑛}, 𝑖 = ̅̅̅̅̅
1, 𝑛 . Aceste mulțimi pot fi exprimate ca
progresii aritmetice cu elementul inițial 𝑎𝑖 = 1, rația 𝑟𝑖 = 1 și ultimul element 𝑠𝑖 =
𝑛.

Deoarece într-o permutare nu pot fi două elemente pe aceeași poziție,


valoarea atribuită unui element 𝑥𝑖 este acceptabilă dacă este diferită de valorile
atribuite elementelor anteriore din soluția parțială construită: 𝑥𝑖 ≠ 𝑥𝑗 , 𝑗 = ̅̅̅̅̅̅̅̅̅
1, 𝑖 − 1.
Condiția este suficientă, astfel încît nu e necesară verificarea vreunei condiții
suplimentare după construirea unei soluții. La găsirea unei soluții (permutări)
aceasta este numărată (și afișată pe ecran, în exemplul următor de implementare).

În exemplul următor se reprezintă soluția într-un vector, lucrînd cu indicii


de masive specifici limbajului C/C++ (primul element al soluției se va afla în
vector pe poziția 0, ultimul pe poziția 𝑛 − 1), de aceea este necesară ajustarea
corespunzătoare a algoritmului backtracking: indicele pasului curent (𝑖) pornește de
Algoritmi și tehnici de programare 25

la zero (configurație inițială) și indică o configurație soluție dacă 𝑖 = 𝑛 și


configurație finală dacă 𝑖 = −1.
char r='n';

// Verifica daca valoarea elementului i este acceptabila


// (diferita de cele anterioare)
int posibil( int* v, int i)
{ int j, r;
r=1;
for(j=0;j<i;j++)
if(v[i]==v[j])
r=0;
return r;
}

// Afisare solutie pe ecran


// I: numarul solutiei (num), dimensiunea permutarii (nr), permutarea (v)
void retine_solutia(int num, int nr, int* v)
{ int i;

printf("\n\n Solutia numarul %d\n",num);


for(i=0; i<nr; i++)
printf("%2d ",v[i]);

if(r=='n')
{ printf("\n\nUrmatoarea (n) sau Ultima (l)?");
r=_getch();
}
}

// Genereaza permutari de n elemente (1..n)


// I: n
// E: numar permutari
int permutari(int n)
{ int* p,i,am,nr;
// pentru ca se foloseste un vector de dimensiune n, cu indici de la 0 la n-1
// (in loc de 1..n)
// indicele i arata o configuratie solutie daca i==n, configuratie finala daca
// i=-1

p=new int[n]; //vectorul solutie


nr=0; //numar solutii
i=0;
p[0]=0; //prima valoare minus ratia
while(i>=0) //cit timp nu am ajuns la configuratia finala
{ am=0;
while( p[i]<n && !am) //alege urmatoarea valoare acceptabila pentru x[i]
{ p[i]++; //urmatoarea valoare pentru x[i]
am=posibil(p,i); //este acceptabila?
}
if(!am) //impas, revenire
i--;
else
if( i==n-1 ) //configuratie solutie
retine_solutia(++nr,n,p);
else
26 Metode de programare

p[++i]=0; //prima valoare minus ratia


}
delete p;
return nr;
}

Funcția următoare constituie implementarea recursivă a metodei


backtracking pentru generarea permutărilor:
//Permutari recursiv
//I: dimensiune (n), pas curent (i), sol. partiala curenta (x), nr. crt. sol. (nr)
//E: numar solutii (nr)
int permutari_r(int n, int i, int* x, int nr)
{ int j;

if( i==n)
retine_solutia(++nr,n,x);
else
for(j=1; j<=n; j++ )
{ x[i]=j;
if( posibil(x,i) )
nr=permutari_r(n,i+1,x,nr);
}
return nr;
}

La apelul inițial se folosesc următoarele valori: pasul curent este zero


(configurație inițială), numărul de soluții găsite este zero. Vectorul soluție trebuie
alocat dinamic înainte de apel.
int* x=new int[n];
nr=0;
nr=permutari_r(n,0,x,0);
Algoritmi și tehnici de programare 1

Algoritmi si tehnici de programare


(partea a II-a)

Material suport elaborat de


Prof.dr. Ion Gh. Roşca
Prof.dr. Bogdan Ghilic-Micu
Conf.dr. Cătălina Cocianu
Conf.dr. Marian Stoica
Lect.dr. Cristian Uscatu
Asist. Marinela Mircea

Editura ASE,
Bucureşti 2007
2 Metode de programare

1. Introducere_______________________________________________3
2. Grafuri 3
2.1. Definiţii şi reprezentări ale grafurilor 3
2.1.1 Moduri de reprezentare a grafurilor
2.1.2. Reprezentarea matriceală
2.1.3. Reprezentarea tabelară
2.1.4. Reprezentarea prin intermediul listelor
2.2. Modalităţi de parcurgere a grafurilor 7
1.2.1. Metoda de parcurgere BF (Breadth First)
1.2.2. Metoda de parcurgere DF (Depth First)
1.2.3. Parcurgerea în adîncime în varianta generalizată – DFG
2.3. Drumuri în grafuri. Conexitate 21
2.3.1 Drumuri; definiţii
2.3.2. Matricea existenţei drumurilor; algoritmul Roy-Warshall
2.3.3. Componente conexe ale unui graf
2.3.4. Drumuri de cost minim
2.4. Circuite şi cicluri în grafuri şi în digrafuri 31

3. Structuri arborescente 37
3.1. Grafuri de tip arbore 37
3.1.1. Definiţii şi caracterizări ale grafurilor arbori
3.1.2. Reprezentări şi parcurgeri ale arborilor orientaţi
3.1.3. Arbori parţiali. Algoritmul Kruskal
3.2. Arbori binari 49
3.2.1. Reprezentarea arborilor binari. Modalităţi de parcurgere
3.2.2. Arbori de sortare
3.2.3. Arbori de structură

Bibliografie _62

2
Algoritmi și tehnici de programare 3

1. Introducere

Grafurile sînt structuri de date cu aplicaţii în multe domenii ale informaticii, algoritmii
pentru reprezentarea şi prelucrarea grafurilor fiind consideraţi fundamentali în acest domeniu. În
subcapitolul 2.1 sînt prezentate principalele noţiuni ale domeniului, precum şi modalităţile uzuale
de reprezentare a structurii de graf. În continuare sînt descrise tehnicile de parcurgere a grafurilor
în lăţime şi în adîncime. Traversarea în adîncime a grafurilor determină obţinerea unei clasificări
a muchiilor, în funcţie de care pot fi derivate diferite proprietăţi ale grafurilor. Verificarea
conexităţii şi calculul drumurilor în grafuri sînt tratate în subcapitolul 2.3. În finalul capitolului
este studiată problema determinării circuitelor şi ciclurilor în grafuri şi digrafuri.

2. Grafuri

2.1. Definiţii şi reprezentări ale grafurilor


Definiţia 2.1.1. Se numeşte graf sau graf neorientat o structură G=(V,E), unde V este o
mulţime nevidă iar E este o submulţime posibil vidă a mulţimii perechilor neordonate cu
componente distincte din V.
Elementele mulţimii V se numesc vîrfuri, iar obiectele mulţimii E se numesc muchii.
Dacă e  E, e  (u,v) not. uv , vîrfurile u şi v se numesc extremităţi ale lui e, muchia e fiind
determinată de vîrfurile u şi v. Dacă e=uv  E se spune că vîrfurile u, v sînt incidente cu muchia e.
Definiţia 2.1.2. Fie G=(V,E) graf. Vîrfurile u, v sînt adiacente în G dacă uv  E.
Definiţia 2.1.3. Graful G=(V,E) este graf finit, dacă V este o mulţime finită.
În cadrul acestui capitol vor fi considerate în exclusivitate grafurile finite, chiar dacă
acest lucru nu va fi precizat în mod explicit.
Definiţia 2.1.4. Fie Gi =(V i,Ei), i=1,2 grafuri. G2 este un subgraf al grafului G1 dacă
V2  V1 şi E2  E1 . G2 este este un graf parţial al lui G1 dacă V2=V1 şi G2 este subgraf al lui
G1.
Definiţia 2.1.5. Un digraf este o structură D=(V,E), unde V este o mulţime nevidă de
vîrfuri, iar E este o mulţime posibil vidă de perechi ordonate cu componente elemente distincte
din V. Elementele mulţimii E sînt numite arce sau muchii ordonate. Un graf direcţionat este o
structură D=(V,E), unde V este o mulţime nevidă de vîrfuri, iar E este o mulţime posibil vidă de
perechi ordonate cu componente elemente din V, nu neapărat distincte. Evident, orice digraf este
un graf direcţionat.
Terminologia utilizată relativ la digrafuri este similară celei corespunzătoare grafurilor.
În continuare vom referi prin muchie şi elementele mulţimii E a unui graf direcţionat, în situaţia
în care este tratat cazul unui graf oarecare (neorientat sau direcţionat).
Definiţia 2.1.6. Se numeşte graf ponderat o structură (V,E,W), unde G=(V,E) este graf şi
W este o funcţie definită prin W : E  0 , . Funcţia W este numită pondere şi ea asociază
4 Metode de programare

fiecărei muchii a grafului un cost/cîştig al parcurgerii ei.


Definiţia 2.1.7. Fie G=(V,E) un graf, u,vV. Secvenţa de vîrfuri :u0,u1,..,un este un

u-v drum dacă u0=u, un=v, uiui+1E pentru toţi i, 0  i  n .

Definiţia 2.1.8. Fie G=(V,E) un graf. Elementul vV se numeşte vîrf izolat dacă,

pentru orice e  E, u nu este incident cu e.

2.1.1 Moduri de reprezentare a grafurilor

Cea mai simplă reprezentare a unui graf este cea intuitivă, grafică; fiecare vîrf este
figurat printr-un punct, respectiv muchiile sînt reprezentate prin segmentele de dreaptă, orientate
(în cazul digrafurilor) sau nu şi etichetate (în cazul grafurilor ponderate) sau nu, avînd ca
extremităţi punctele corespunzătoare vîrfurilor care o determină

Exemple
2.1.1. Fie G=(V,E) graf, cu V={1,2,3,4,5,6}, E={(1,2),(1,3),(2,5),(3,5),(5,6)}. O posibilă
reprezentare grafică este,

2
4
6
5
3

2.1.2. Fie D=(V,E) digraf, V={1,…,5}, E={(1,2), (1,3), (1,5), (2,5), (3,5), (4,1), (5,4)}.
Digraful poate fi reprezentat grafic astfel,
1

4
2

3
5

2.1.3. Fie D=(V,E) graf direcţionat, V={1,2,3,4,5}, E={(1,2), (1,3), (1,5) (2,5), (3,5),
(4,4)}. Reprezentarea grafică este,

4
Algoritmi și tehnici de programare 5

4
2

3
5

2.1.4. Fie G=(V,E,W) graf ponderat, V={1,2,3,4}, E={(1,2), (1,3), (1,4), (2,3), (2,4)},
W((1,2))=5, W((1,3))=1, W((1,4))=7, W((2,3))=4, W((2,4))=2. O posibilă reprezentare grafică
este:
1

5 1
2 3
4
2
7

În scopul reprezentării grafurilor în memoria calculatorului sînt utilizate în general


următoarele structuri de date.

2.1.2. Reprezentarea matriceală


Grafurile, digrafurile şi grafurile direcţionate pot fi reprezentate prin matricea de
adiacenţă. Dacă G=(V,E ) este graf, digraf sau graf direcţionat cu V  n , atunci matricea de
adiacenţă A  Mnxn({0,1}) are componentele,
1, dacă vi ,v j   E
aij   ,
0 , altfel
unde vi, vj reprezintă cel de-al i-lea, respectiv cel de-al j-lea nod din V. În cazul unui graf
neorientat, matricea de adiacenţă este simetrică.

Exemplu
2.1.5. Graful din exemplul 2.1.1, digraful din exemplul 2.1.2 şi graful direcţionat din
exemplul 2.1.3 sînt reprezentate prin matricele de adiacenţă,
0

1 1 0 0 0
 0 1 1 0 1
0 1
  
1 1 0

1 0 0 0 1 0
1 0 0 0 0 1 0 0 0 0 1
0
 (2.1.1), A   0 
0 0 0 1
A 0 0 0 1 (2.1.2), A   0 0 0 0 1  (2.1.3)
0 0 0 0 0 0   
  1 0 0 0 0 0 0 0 1 0
0 1 1 0 0 1  
0  0 0  0 0 0 0 0
 0 0 0 1 0  0 0 1

În cazul grafurilor ponderate, reprezentarea poate fi realizată prin matricea ponderilor.


Dacă G=(V,E,W) este graf ponderat, V  n , W  Mnxn((0,  )) are componentele,
6 Metode de programare

W ( vi ,v j ), dacă vi ,v j   E


wi , j  
 , altfel
unde vi, vj reprezintă cel de-al i-lea, respectiv cel de-al j-lea nod din V,   0 , dacă
ponderea are semnificaţia de cîştig, respectiv    în cazul în care se doreşte reprezentarea
costurilor ca ponderi ale grafului.

Exemplu
2.1.6. Presupunînd că ponderile reprezintă costuri, matricea de reprezentare a grafului
 5 1 7
 
5  4 2
din exemplul 2.1.4. este W   .
1 4  
 
7 2   

2.1.3. Reprezentarea tabelară

Reţinînd muchiile prin intermediul extremităţilor şi eventual valoarea ponderii ei, se


obţine reprezentarea tabelară, mai economică din punctul de vedere al spaţiului de memorie
necesar. Dacă graful conţine vîrfuri izolate atunci este necesară păstrarea acestora într-un vector
suplimentar VS. Mulţimea muchiilor este reţinută într-o matrice A cu E linii şi c coloane, unde
c=2 dacă graful nu este ponderat, altfel c=3. În primele două coloane se scriu perechile de vîrfuri
ce determină muchiile, în cazul grafurilor ponderate cea de-a treia coloană conţine valoarea
ponderii muchiei respective.

Exemple
2.1.7. Graful din exemplul 2.1.1 poate fi reprezentat astfel, VS=(4),
1 2
 
1 3
A  2 5
 
3 5
5 6 

1 2
 
1 3
1 5
 
2.1.8. Digraful din exemplul 2.1.2 este reprezentat prin A   2 5 .
 
3 5
4 1
 
5 4 

6
Algoritmi și tehnici de programare 7

1 2
 
1 3
1 5
2.1.9. Graful direcţionat din 2.1.3. este reprezentat prin A   .
2 5
 
3 5
4 4 

2.1.10. Graful ponderat din exemplul 2.1.4. nu are vîrfuri izolate, deci este reprezentat
1 2 5
 
1 3 1
prin intermediul matricei A   2 3 4 .
 
1 4 7
 
2 4 2

2.1.4. Reprezentarea prin intermediul listelor

Această reprezentare permite utilizarea economică a spaţiului de memorare şi, în anumite


cazuri, implementări mai eficiente pentru anumite clase de algoritmi. Vîrfurile grafului sînt
memorate într-o listă, fiecare nod al listei N conţinînd o referinţă spre lista vecinilor vîrfului
memorat ca informaţie în N.
Dacă graful nu este ponderat, el poate fi reprezentat prin structura listă de liste, şi anume:
nodurile grafului se trec într-o listă L_nod, fiecare celulă avînd structura,

informaţie legătură vecini legătură nod următor


unde,
 cîmpul informaţie conţine identificatorul nodului;
 legătură vecini reprezintă referinţa spre începutul listei vecinilor;
 legătură nod următor conţine adresa următoarei celule din lista L_nod.
Un graf ponderat poate fi reprezentat în mod similar, cu diferenţa că, fiecare celulă din
lista vecinilor conţine şi ponderea muchiei respective (muchia care are ca extremităţi vîrful referit
prin identificatorul de nod din lista vecinilor şi respectiv vîrful indicat de informaţia acelei celule
din L_nod ce conţine adresa primului element al listei vecinilor).

2.2. Modalităţi de parcurgere a grafurilor


Modalitatea de vizitare a tuturor vîrfurilor grafului în care fiecare vîrf al grafului este
vizitat o singură dată se numeşte parcurgere sau traversare. În acest paragraf sînt prezentate
metodele de parcurgere BF (în lăţime), DF (în adîncime) şi metoda DF generalizată, notată
DFG.
Primele două metode de parcurgere sînt aplicate grafurilor neorientate respectiv
grafurilor direcţionate şi presupun selectarea unui vîrf iniţial v0 şi identificarea acelor vîrfuri
ale grafului v cu proprietatea că există cel puţin un drum de la vîrful iniţial către v. Grafurile
cu proprietatea că oricare două vîrfuri sînt conectate printr-un drum se numesc grafuri conexe
şi sînt prezentate în § 2.3. Dacă graful este conex, atunci prin aplicarea metodelor de
parcurgere vor fi identificate toate vîrfurile grafului. Cele două modalităţi de parcurgere sînt
prezentate în continuare în cazul grafurilor neorientate, extinderea la digrafuri şi grafuri
direcţionate fiind imediată. Studiul proprietăţii metodei BF de a calcula distanţele minim între
8 Metode de programare

orice vîrf al grafului conectat de vîrful iniţial şi vîrful iniţial este prezentat în cazul grafurilor
oarecare.
Parcurgerea DFG presupune vizitarea tuturor vîrfurilor unui graf sau graf direcţionat
prin aplicarea metodei DF tuturor vîrfurilor care, după ultima traversare DF, nu au fost încă
vizitate.

2.2.1. Metoda de parcurgere BF (Breadth First)

Traversarea BF presupune parcurgerea în lăţime a grafului, în sensul că, vîrfurile


grafului sînt prelucrate în ordinea crescătoare a distanţelor la vîrful iniţial (teorema 2.2.1).
Distanţa de la u la v, notată  u ,v  , este numărul de muchii ale unui cel mai scurt u-v drum.
La momentul iniţial vîrf curent este v0. Deoarece vîrful curent la fiecare moment
trebuie să fie unul dintre vîrfurile aflate la distanţă minimă de v0 se poate proceda în modul
următor: iniţial lui v0 i se asociază valoarea 0, d v0   0 şi fiecărui vîrf v  v0 i se asociază
valoarea  , d v   . Dacă valoarea asociată vîrfului curent este m, atunci fiecăruia dintre
vecinii acestuia de valoare  li se asociază valoarea m+1. Se observă că, dacă după ce toate
vîrfurile de valoare m au fost considerate şi nici unui vîrf nu i-a fost recalculată valoarea,
atunci toate vîrfurile conectate cu v0 au fost vizitate, deci calculul se încheie.

Exemple
2.2.1. Fie graful,
1

2 3

6
4

7
5

şi v0=1.Valorile calculate prin aplicarea metodei prezentate sînt,

vîrf 1 2 3 4 5 6 7
d
0 0      

1 0 1 1 1   1

2 0 1 1 1 2 2 1

0 1 1 1 2 2 1

8
Algoritmi și tehnici de programare 9

2.2.2. Fie graful,

1 8

3
2

6
4 9 10

11
5 7

şi v0=1. Se observă că vîrfurile 8, 9, 10 şi 11 nu sînt conectate cu vîrful iniţial.


Valorile rezultate prin aplicarea metodei sînt:

1 2 3 4 5 6 7 8 9 10 11
vîrf
d

0 0          

1 0 1 1  1  1    

2 0 1 1 2 1 2 1    

0 1 1 2 1 2 1    

Se observă că valorile lui d calculate în final reprezintă numărul de muchii


corespunzător unui cel mai scurt drum care conectează vîrful iniţial cu vîrful respectiv, pentru
vîrfurile neconectate cu v0 valoarea d[v0] rezultată la terminarea calculului este  .
Fie G=(V,E) un graf, V  n . O alternativă de implementare a metodei BF este
construită prin utilizarea următoarelor structuri de date,
 A matricea de adiacenţă a grafului;
 o structură de tip coadă, C, în care sînt introduse vîrfurile ce urmează a fi vizitate şi
procesate (în sensul cercetării vecinilor lor);
 un vector c cu n componente, unde,
dacă i 1a, fost adăugat în coadă
ci  
altfel0,

Componentele vectorului c sînt iniţializate cu valoarea 0.


10 Metode de programare

Parcurgerea BF poate fi descrisă astfel,


 coada C este iniţializată cu vîrful v0;
 cît timp C  Ø, este extras şi vizitat un vîrf i din coadă, apoi sînt introduşi în coadă
vecinii lui i care nu au fost deja introduşi (acele vîrfuri k cu proprietatea că c[k]=0 şi a[i][k]=1).
Vîrfurile i ce au fost introduse în coadă sînt marcate prin c[i]=1.

Exemplu
2.2.3. Pentru graful din exemplul 2.2.1., aplicarea metodei de traversare BF

determină următoarea evoluţie,

c
t 1 2 3 4 5 6 7
t=1 1 0 0 0 0 0 0
t=2 1 1 1 1 0 0 1
t=3 1 1 1 1 1 0 1
t=4 1 1 1 1 1 1 1
t=5 1 1 1 1 1 1 1
t=6 1 1 1 1 1 1 1
t=7 1 1 1 1 1 1 1
t=8 1 1 1 1 1 1 1
C
t
t=1 1
t=2 2 3 4 7
t=3 3 4 7 5
t=4 4 7 5 6
t=5 7 5 6
t=6 5 6
t=7 6
t=8

Observaţie Deoarece graful din exemplul 2.2.1. este conex, traversarea BF realizează
vizitarea tuturor vîrfurilor grafului. Aplicarea metodei BF grafului din exemplul 2.2.2. nu
determină vizitarea vîrfurilor 8,9, 10 şi 11, deoarece acestea sînt vîrfuri neconectate cu vîrful
iniţial . Cu alte cuvinte, metoda BF aplicată unui graf determină vizitarea tuturor vîrfurilor
care sînt conectate cu vîrful iniţial selectat.

Sursa C pentru implementarea metodei BF este,


#include <stdio.h>
#include <conio.h>
#include <alloc.h>

typedef struct nn
{ int inf;
struct nn *leg;
} nod,* pnod;

int insereaza_coada(pnod *head,pnod *tail,int info)


{
pnod nou;
if(nou=(pnod)malloc(sizeof(nod))){

10
Algoritmi și tehnici de programare 11

nou->inf=info;
nou->leg=NULL;
if(*head==NULL) *head=nou;
else (*tail)->leg=nou;
*tail=nou;
return 1;
}
else return 0;
}

int extrage_coada(pnod *head,pnod *tail, int *info)


{
if(*head){
pnod aux=*head;
*info=(*head)->inf;
(*head)=(*head)->leg;
free(aux);
if(*head==NULL)*head=*tail=NULL;
return 1;
}
else return 0;}
void breadth_first(int v0,int a[10][10],int n)
{
pnod head=NULL;
pnod tail=NULL;
int c[10];
for(int i=0;i<n;c[i++]=0);
int r=insereaza_coada(&head,&tail,v0);
c[v0]=1;
while(head){
r=extrage_coada(&head,&tail,&i);
printf("\n%i",i+1);
for(int k=0;k<n;k++)
if((a[i][k]==1)&&(c[k]==0)){
r=insereaza_coada(&head,&tail,k);
c[k]=1;
}
}
}

void main()
{
int n,v0,a[10][10];
clrscr();
printf("Numarul de varfuri:");
scanf("%i",&n);
printf("\nMatricea de adiacenta\n");
for(int i=0;i<n;i++)
for(int j=0;j<i;j++){
scanf("%i",&v0);
a[j][i]=a[i][j]=v0;
}
for(i=0;i<n;i++)a[i][i]=0;
printf("\nVarful initial ");
scanf("%i",&v0);
printf("\nParcurgerea BF a grafului este");
breadth_first(v0,a,n);
}
12 Metode de programare

În continuare sînt prezentate o serie de rezultate prin care este demonstrată proprietatea
parcurgerii BF de a calcula distanţa minimă de la orice vîrf v conectat de vîrful iniţial v0 la v0.

Lema 2.2.1. Fie G=(V,E) un graf oarecare şi v0  V arbitrar. Atunci, pentru orice
muchie u ,v   E ,  v0 ,v    v0 ,u   1 .
Demonstraţie Dacă u este conectat de v0 în G, atunci, evident, şi v este conectat de v0 în
G. În acest caz, cel mai scurt drum de la v0 la v nu poate fi mai lung decît cel mai scurt drum de la
v0 la u prelungit cu muchia (u,v), deci afirmaţia este demonstrată. În situaţia în care u nu este
conectat de v0 în G, atunci, evident, rezultă inegalitatea  v0 ,v    v0 ,u   1 .

Lema 2.2.2 Fie G=(V,E) un graf neorientat sau graf direcţionat şi v0  V vîrf iniţial al
procedurii de traversare BF. Atunci orice v  V vizitat, are loc inegalitatea d v   v0 ,v  .
Demonstraţie Afirmaţia este demonstrată prin inducţie după ordinea vizitării BF a
elementelor v  V conectate cu v0 în G.
Dacă v  v0 , rezultă d v  0 şi, pentru orice u  V \ v, d u      u ,v  , deci
afirmaţia este adevărată.
Fie v vîrful vizitat ca rezultat al procesării vîrfului u. Prin aplicarea ipotezei inductive,
d u    v0 ,u  , a rezultatului lemei 2.2.1 şi a procedurii de parcurgere BF obţinem,
d v  d u  1   v0 ,u   1   v0 ,v  .
Deoarece vîrful v nu a fost anterior găsit în lista vecinilor nici unui nod studiat înaintea
vîrfului u, v este inserat în C.


Lema 2.2.3 Fie G=(V,E) un graf neorientat sau graf direcţionat şi C  v1 ,v2 ,...,v p 
coada calculată la un moment al aplicării procedurii de parcurgere BF. Atunci următoarele
inegalităţile sînt verificate, [Cor,Lei şa]
 
d v p  d v1   1
d vi   d vi 1 , i  1,..., p  1 .

Teorema 2.2.1. Corectitudinea procedurii BF


Fie G=(V,E) graf neorientat sau graf direcţionat şi v0  V vîrf iniţial al procedurii de
traversare BF. Atunci metoda BF calculează toate vîrfurile v conectate cu v0 în G şi, pentru orice
v  v0 , v  V vizitat, un cel mai scurt v0-v drum este format dintr-un v0-u drum şi muchia (u,v),
unde u este acel vîrf prin procesarea căruia este determinată vizitarea lui v.
Demonstraţie Fie Vk  v  V /  v0 ,v   k mulţimea vîrfurilor situate la distanţă k de
v0. Rezultatul teoremei este demonstrat prin inducţie după k, cu ipoteza inductivă,
Ik: v  Vk , există un singur moment al execuţiei procedurii BF în care este determinată
următoarea evoluţie,
d v  k şi v  C ;
dacă v  v0 , vîrful u care determină inserarea lui v în C este element al mulţimii Vk 1 .
Pentru k  0 , V0  v0 . La momentul iniţial, C  v0 şi d v0   0 , deci I este
verificată.
Verificarea ipotezei Ik în condiţiile în care I0 ,…,Ik-1 sînt adevărate este bazată pe
următoarea observaţie. Pe tot parcursul execuţiei procedurii BF, C  Ø şi, dacă u  C , atunci

12
Algoritmi și tehnici de programare 13

d u  şi vîrful care a determinat procesarea lui u rămîn constante.


Din lema 2.2.3. rezultă că, dacă C  v1 ,v2 ,...,v p , atunci
d vi   d vi 1 , i  1,..., p  1 . Fie v  Vk , k  1 . Din proprietatea de monotonie şi ipoteza Ik-1,
rezultă că v a fost inserat în C după ce toate vîrfurile u  Vk 1 au fost deja inserate în coadă.
Deoarece  v0 ,v   k , obţinem că există un v0-v drum de lungime k şi u  Vk 1 astfel încît
u ,v   E . Fără a pierde din generalitate, vom presupune că u este primul vîrf din Vk 1 inserat în
C.
La momentul în care vîrful u devine prim element al cozii C, toate vîrfurile vecine cu u în
G sînt inserate în C, deci şi vîrful v. Rezultă că d v  d u   1  k , unde u este acel vîrf care
precede v pe un cel mai scurt v0-v drum.
Observaţii
1. Demonstrarea teoremei de corectitudine a parcurgerii BF stabileşte şi o modalitate de
calcul al unui cel mai scurt v0-v drum astfel. Pentru orice v  V conectat cu v0 în G, fie pv  V
vîrful a cărui procesare a determinat inserarea lui v în C. Un v0-v drum de lungime minimă este
v0- pv  drumul cel mai scurt “prelungit” cu muchia  pv,v  .
2. Aplicarea metodei BF unui graf oarecare G determină obţinerea unui arbore (vezi


capitolul 9) Gp, numit subgraful predecesorilor definit de BF pe G, unde G p  V p , E p şi 

V p  v  V / pv V  v0 , E p   pv,v   E / v  V \ v0 .

Exemplu

2.2.4 Prin aplicarea procedurii BF grafului din 2.2.1, obţinem,

2
3 4 7

5 6
14 Metode de programare

2.2.2. Metoda de parcurgere DF (Depth First)

Ideea metodei DF revine la parcurgerea în adîncime a grafurilor. Considerînd v0 vîrf


iniţial şi M mulţimea vîrfurilor vizitate de procedură, pentru vizitarea vecinilor este considerat
unul din vîrfurile din M cu proprietatea că lungimea drumului calculat de metodă pînă la
vîrful iniţial v0 este maximă.
Implementarea acestei metode poate fi realizată în mai multe moduri, pentru
menţinerea mulţimii vîrfurilor grafului disponibilizate pînă la momentul curent fiind utilizată
o structură de de date de tip stivă S. La momentul iniţial se introduce în stivă v0. La fiecare
pas, se preia cu ştergere ca vîrf curent vîrful stivei S şi se introduc în stivă vecinii încă
nevizitaţi ai vîrfului curent. Un vîrf se marchează ca vizitat în momentul introducerii lui în S.
Calculul continuă pînă cînd este efectuat un acces de preluare din stivă şi se constată că S este
vidă. Pentru gestiunea vîrfurilor vizitate, se utilizează un vector c cu n componente, unde n
reprezintă numărul vîrfurilor grafului şi, la fiecare moment, componentele sînt:
1, dacă i a fost vizitat
ci  
0, altfel

Componentele vectorului c vor fi iniţializate cu valoarea 0.

Exemple
2.2.5. Pentru graful,
1

2 3

6
4

7
5

şi v0=1, prin aplicarea metodei descrise, rezultă următoarea evoluţie.

c
1 2 3 4 5 6 7
t

t=1 1 0 0 0 0 0 0

t=2 1 1 1 1 0 0 1

t=3 1 1 1 1 0 1 1

t=4 1 1 1 1 0 1 1

14
Algoritmi și tehnici de programare 15

t=5 1 1 1 1 1 1 1

t=6 1 1 1 1 1 1 1

t=7 1 1 1 1 1 1 1

t=8 1 1 1 1 1 1 1

t=1 1

t=2 7 4 3 2

t=3 6 4 3 2

t=4 4 3 2

t=5 5 3 2

t=6 3 2

t=7 2

t=8

Ordinea în care sînt vizitate vîrfurilor corespunzător acestei variante de parcurgere


DF este: 1, 2, 3, 4, 7, 6, 5.

2.2.6. Pentru graful din exemplul 2.2.2 vîrfurile 8,9,10 care nu sînt conectate cu vîrful
iniţial nu vor fi vizitate nici prin aplicarea metodei DF. Ordinea în care sînt vizitate vîrfurilor
corespunzător acestei variante este: 1, 2, 3, 4, 6, 7, 5.
O variantă de implementare a metodei DF rezultă prin gestionarea stivei S în modul
următor. Iniţial vîrful v0 este unicul component al lui S. La fiecare etapă se preia, fără
ştergere, ca vîrf curent vîrful stivei. Se introduce în stivă unul dintre vecinii vîrfului curent
încă nevizitat. Vizitarea unui vîrf revine la introducerea lui în S. Dacă vîrful curent nu are
vecini încă nevizitaţi, atunci el este eliminat din stivă şi este efectuat un nou acces de preluare
a noului vîrf al stivei ca vîrf curent. Calculul se încheie în momentul în care este efectuat un
acces de preluare a vîrfului stivei ca vîrf curent şi se constată că S este vidă. Evident, nici în
cazul acestei variante nu vor fi vizitate vîrfurile care nu sînt conectate cu vîrful iniţial ales.
16 Metode de programare

Exemplu
2.2.7. Pentru graful,
1

2 3

6
4

7
5

şi v0=1, prin aplicarea metodei descrise, rezultă următoarea evoluţie.

c 1 2 3 4 5 6 7

t=1 1 0 0 0 0 0 0
t=2 1 1 0 0 0 0 0
t=3 1 1 0 1 0 0 0
t=4 1 1 1 1 0 0 0
t=5 1 1 1 1 0 1 0
t=6 1 1 1 1 0 1 1
t=7 1 1 1 1 0 1 1
t=8 1 1 1 1 0 1 1
t=9 1 1 1 1 0 1 1
t=10 1 1 1 1 1 1 1
t=11 1 1 1 1 1 1 1
t=12 1 1 1 1 1 1 1
t=13 1 1 1 1 1 1 1
t=14 1 1 1 1 1 1 1

t=1 1
t=2 2 1
t=3 4 2 1
t=4 3 4 2 1
t=5 6 3 4 2 1
t=6 7 6 3 4 2 1
t=7 6 3 4 2 1
t=8 3 4 2 1
t=9 4 2 1
t=10 5 4 2 1
t=11 4 2 1

16
Algoritmi și tehnici de programare 17

t=12 2 1
t=13 1
t=14

Ordinea în care sînt vizitate vîrfurile corespunzător acestei variante este: 1, 2, 4, 3, 6,


7, 5.
Următoarea sursă C implementează varianta precedentă de parcurgere DF.

#include <stdio.h>
#include <conio.h>
#include <alloc.h>
typedef struct nn{
int inf;
struct nn *leg;
}nod,* pnod;

int insereaza_stiva(pnod *head,int info)


{
pnod nou;
if(nou=(pnod)malloc(sizeof(nod))){
nou->inf=info;
nou->leg=*head;
*head=nou;
return 1;
}
else return 0;
}

int extrage_stiva(pnod *head,int *info)


{
if(head){
pnod aux=*head;
*info=(*head)->inf;
(*head)=(*head)->leg;
free(aux);
return 1;
}
else return 0;}
void depth_first(int v0,int a[10][10],int n)
{
pnod head=NULL;
int c[10];
for(int i=0;i<n;c[i++]=0);
int r=insereaza_stiva(&head,v0);
c[v0]=1;
printf("\n%i",v0+1);
while(head){
r=extrage_stiva(&head,&i);
for(int k=0;k<n;k++)
if((a[i][k]==1)&&(c[k]==0)){
r=insereaza_stiva(&head,k);
c[k]=1;printf("\n%i",k+1);
}
}
}

void main()
{
int n,v0,a[10][10];
clrscr();
18 Metode de programare

printf("Numarul de varfuri:");scanf("%i",&n);
printf("\nMatricea de adiacenta\n");
for(int i=0;i<n;i++)
for(int j=0;j<i;j++){
scanf("%i",&v0); a[j][i]=a[i][j]=v0;
}
for(i=0;i<n;i++)a[i][i]=0;
printf("\nVarful initial ");scanf("%i",&v0);
printf("\nParcurgerea DF a grafului este");
depth_first(v0,a,n);
}

2.2.3. Parcurgerea în adîncime în varianta generalizată – DFG

Următoarea variantă de implementare a parcurgerii în adîncime, DFG, determină


vizitarea tutror vîrfurilor grafului analizat (considerat neorientat sau direcţionat), indiferent dacă
acesta este conex sau neconex.
Fie G=(V,E) un graf oarecare. Vom presupune în continuare că nici un vîrf din V nu este
etichetat cu informaţia 0. Implementare traversării DFG utilizează următoarele structuri,
 A, matricea de adiacenţă a grafului;
 p, vectorul predecesorilor (vezi § 2.2.1);
 f, vectorul care marchează încheierea analizării listei vîrfurilor vecinilor nodului
curent;
 mark, definit pentru orice v  V prin,
0, dacă v nu a fost încă analizat
mark[v]= 1, dacă v este procesat la momentul curent
2, dacă consultarea lui v este încheiată
 d, definit pentru orice v  V prin d[v]=t, unde t este momentul de timp la care este
iniţiată analiza vîrfului v.
Considerînd t variabilă publică desemnînd momentul prelucrării, procedura DFG poate fi
descrisă prin intermediul următoarelor funcţii.

void DFG(graf G)
{
for( u  V ){
mark[u]=0;
p[u]=0;
}
t=0;
for( u  V )
if(!mark[u])DF_Visit(u);
}

void DF_Visit(varf u)
{
mark[u]=1;
d[u]=t++;
for( v  V :A[u][v]==1)
if(!mark[v]){
p[v]=u;
DF_Visit(v);
}
mark[u]=2;
f[u]=t++;
}

18
Algoritmi și tehnici de programare 19

Prin aplicarea parcurgerii în adîncime în varianta generalizată sînt obţinute informaţii


suplimentare ale grafului de intrare. Metoda DFG determină obţinerea subgraful predecesorilor,
G p , de tip graf pădure (componentele conexe – vezi § 2.3- sînt arbori - vezi capitolul 9). Fiecare
componentă conexă a lui G p este construită prin executarea modulului DF_Visit. De asemenea,
vîrfurile u ,v  V au proprietatea că u  pv dacă şi numai dacă funcţia DF_Visit(v) a fost
apelată la momentul căutării în lista vecinilor vîrfului u.
O altă proprietate importantă a metodei de parcurgere DFG este aceea că, după încheierea
calculului, este determinată o structură de tip paranteză astfel. Dacă momentul selectării vîrfului
u pentru procesare este marcat prin “(u” şi momentul încheierii prelucrării lui u este notat “u)”,
atunci istoricul traversării DFG pentru calculul fiecărui arbore din G p poate fi reprezentat prin
intermediul unei expresii corecte din punct de vedere al parantezării.

Teorema 2.2.2 Corectitudinea parantezării determinată de aplicarea metodei DFG


Fie G=(V,E) un graf sau un graf direcţionat. Prin aplicarea traversării DFG este obţinut
următorul rezultat. Pentru orice u ,v  V , una şi numai una din următoarele afirmaţii este
adevărată, [Cor,Lei şa]
1) d u , f u  şi d v, f v sînt disjuncte;
2) d u, f u  d v, f v şi u este un descendent al lui v în arborele corespunzător
din G p ;
3) d u, f u  d v, f v şi u este un ancestor al lui v în arborele corespunzător din
Gp .

Observaţie Fie G=(V,E) un graf sau un graf direcţionat. Pe baza procedurii DFG poate fi
realizată următoarea clasificare a elementelor e  u ,v   E ,
1) Muchii de tip arbore în DF- graful pădure G p , etichetate cu T: u ,v  are eticheta T
dacă procesarea vîrfului v a fost decisă ca rezultat al testării existenţei muchiei e;
2) Muchii de tip înapoi, cu etichetă B: u ,v  este muchie B dacă v este ancestorul lui u
într-o componentă conexă a DF- grafului pădure G p ;
3) Muchii de tip înainte, notate cu F: acele muchii u ,v  , neetichetate cu T şi în care v
este descendent al lui u într-o componentă conexă a DF- grafului pădure G p ;
4) Muchii de tip trecere, etichetate cu C: toate muchiile u ,v  rămase neetichetate după
încheierea etichetării cu T, B şi F.

Teorema 2.2.3.
Fie G=(V,E) un graf neorientat. Orice element e  E este fie de tip T, fie de tip B.
[Cor,Lei şa]

Teorema 2.2.4. Criteriu de aciclicitate pentru grafuri direcţionate


Un graf direcţionat este aciclic (vezi §2.4) dacă şi numai dacă nici una din muchii nu este
de tip B. [Cor,Lei şa]
20 Metode de programare

Exemple
2.2.8. Pentru graful
1 8

3
2

6
4 9 10

5 7
obţinem,
1) ordinea de parcurgere DFG a vîrfurilor: 1,2,3,4,6,7,5,8,9,10
2) graful pădure G p ,
8
1
2 9
3
10
4

6 5

3) structurile paranteză: (1 (2 (3 3) (4 (6 (7 7) 6) (5 5) 4) 2) 1) şi (8 (9 (10 10) 9) 8)


4) clasificarea muchiilor,
8
T
T 1 T
2 9
3
T
10 T
4 B
B T
T
6 5 B
T
7
2.2.9 Fie graful direcţionat,
6 7 1 2

5 8 4 3

Prin parcurgerea DFG obţinem următoarea ordonare: 1,7,6,8,5,2,4,3.


Subgraful predecesorilor este format din următoarele componente,

20
Algoritmi și tehnici de programare 21

1 2

4 3
7

6 8

Structurile paranteză: (1 (7 (6 (5 5) 6) (8 8) 7) 1) şi (2 (4 4) (3 3) 2).


Clasificarea muchiilor grafului direcţionat este,
1 2
B
T T T
7 F 4 3
C
T C
T
B 6
8
T
C

2.3. Drumuri în grafuri. Conexitate


2.3.1 Drumuri; definiţii

Una dintre cele mai importante proprietăţi ale grafurilor o constituie posibilitatea de
accesare, prin intermediul unei secvenţe de muchii (arce), dintr-un vîrf dat a oricărui alt vîrf al
grafului, proprietate cunoscută sub numele de conexitate sau conexiune. Aşa după cum a rezultat
în §2.2., dacă G=(V,E) este un graf conex, atunci pentru orice vîrf iniţial v0 considerat metodele
BF şi DF permit vizitarea tuturor vîrfurilor din V.
Definiţia 2.3.1. Fie G=(V,E) un graf, u,vV. Secvenţa de vîrfuri : u0, u1,..,un este un u-v

drum dacă u0=u, un=v, uiui+1E pentru toţi i, 0  i  n . Lungimea drumului, notată l() este

egală cu n. Convenţional, se numeşte drum trivial, un drum  cu l()=0.

Definiţia 2.3.2. Fie : u0, u1,..,un un drum în graful G=(V,E).  este un drum închis dacă
u0=un; în caz contrar,  se numeşte drum deschis. Drumul  este elementar dacă oricare două
vîrfuri din  sînt distincte, cu excepţia, eventual, a extremităţilor. Drumul  este proces dacă,
pentru orice 0  i  j  n  1 uiui+1  ujuj+1.
Evident, orice drum elementar este un proces.
22 Metode de programare

Exemplu
2.3.1. Pentru graful,
v2

v4
v5

v1 v3
1: v1, v2, v3, v2, v5, v3, v4 este un v1- v4 drum care nu este proces;
2: v1, v2, v5, v1, v3, v4 este un v1- v4 proces care nu este drum elementar;
3: v1, v3, v4 este un v1- v4 drum elementar.

Definiţia 2.3.3. Fie : u0, u1,..,un un drum în graful G=(V,E). ’: v0, v1,..,vm este un
subdrum al lui  dacă ’ este un drum şi pentru orice j, 0  j  m , există i, 0  i  n astfel
încît ui=vj.

Observaţie Orice drum cu lungime cel puţin 1 conţine cel puţin un drum elementar cu
aceleaşi extremităţi.
Într-adevăr, dacă : u0, u1,..,un nu este elementar, atunci există 0  i  j  n şi i  0 sau
j  n astfel încît ui=uj. Atunci drumul
u j u j 1 ...u n , dacă i  0

 : u0 u1 ...u i , dacă j  0
'

u u ...u u ...u , dacă i  0 , j  n


 0 1 i j 1 n
este de asemenea un u0-un drum. Aplicînd în continuare eliminarea duplicatelor vîrfurilor în
modul descris, rezultă în final un u0-um drum elementar.

Exemplu
2.3.2. În graful,
v2 v6 v7

v1 v4 v5 v8 v10

v3 v9

dacă : v1, v2, v4, v5, v3, v1, v2, v5, v6, v7, v8, v9, v5, v9, v8, v10, atunci 1: v1, v2, v5, v9, v8, v10,
2: v1, v2, v4, v5, v9, v8, v10 sînt v1-v10 subdrumuri elementare.

22
Algoritmi și tehnici de programare 23

2.3.2. Matricea existenţei drumurilor; algoritmul Roy-Warshall

Lema 2.3.1. Fie G=(V,E) un graf, V  n . Dacă A este matricea de adiacenţă asociată
grafului, atunci, pentru orice p1, a ij( p ) este numărul vi-vj drumurilor distincte de lungime p din
 
graful G, unde A p  aij( p ) .
Demonstraţie
Demonstrarea acestei afirmaţii este realizată prin inducţie după p
Pentru p=1, deoarece pentru orice 1  i , j  n există cel mult un vi-vj drum de lungime 1
şi dacă există, fie acesta Г: vi, vj. Rezultă că numărul vi-vj drumurilor de lungime 1 este egal cu
aij1 .
 
Presupunem că Ap-1 = aij p 1 are proprietatea că pentru toţi 1  i , j  n , aij p 1 este 
egal cu numărul vi-vj drumurilor de lungime p-1 în G.

 
p
Cum Ap=Ap-1A = aij p  , rezultă că, 1  i , j  n , a ij( p )  a
k 1
( p 1 )
ik a kj . Orice vi-vj drum

de lungime p în G conţine un vi-vk drum de lungime p-1 pentru un anume vk adiacent cu vj şi


reciproc, pentru orice vk adiacent cu vj oricărui vi-vk drum de lungime p-1 îi corespunde un vi-vj
drum de lungime p.
 
Din relaţia care caracterizează elementele aij p  , utilizînd ipoteza inductivă, rezultă
afirmaţia enunţată mai sus.
Definiţia 2.3.4. Fie Mn({0,1)} mulţimea matricelor de dimensiuni nxn, componentele
fiind elemente din mulţimea {0,1}. Pe Mn({0,1)}se definesc operaţiile binare, notate  şi  ,
astfel: pentru orice A=(aij), B=(bij) din Mn({0,1)}, A  B=(cij), A  B=(dij), unde
1  i , j  n , cij=max{aij, bij}
dij=max{min{aik, bkj}, 1  k  n }.
Dacă A=(aij)  Mn({0,1)}, se notează A  a ij  ; k  1 secvenţa de matrice definită
k (k )

prin:
1 k ( k 1 )
A  A, A  A  A
, k  2 .
Dacă A este matricea de adiacenţă a unui graf G=(V,E), atunci pentru fiecare k,
(k ) 1, dacă există drum de la i la j de lungime k
1  k  n  1 , a ij  
0 , altfel
(1) (2) ( n 1 )
Matricea M  A  A    A se numeşte matricea existenţei drumurilor în
graful G. Semnificaţia componentelor matricei M este:
0 , dacă nu există vi  v j drum în G
1  i , j  n , mij  
1, altfel
24 Metode de programare

Exemplu
2.3.3. Pentru graful,
2

4
0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1
       
1 0 0 0 2 0 1 1 1 3 1 0 1 1 1 1 1 1
A , A  , A  , M 
1 0 0 1 1 1 1 1 1 1 1 1  1 1 1 1
       
1 0  1 1 1 1 1 1 1
 0 1  1 1  1 1  1

Observaţie Calculul matricei existenţei drumurilor permite verificarea dacă un graf dat
este conex. Graful este conex dacă şi numai dacă toate componentele matricei M sînt egale cu 1.
Algoritmul Roy-Warshall calculează matricea existenţei drumurilor într-un graf G cu n
vîrfuri.
void Roy_Warshall (unsigned char a[10][10],unsigned n,unsigned char m[10][10])
{int i,j,k;
for (i=0;i<n;i++)
for (j=0;j<n;j++)
m[i][j]=a[i][j];
for (j=0;j<n;j++)
for (i=0;i<n;i++)
if(m[i][j])
for (k=0;k<n;k++)
if (m[i][k]<m[k][j]) m[i][k]=m[k][j];}
Datele de intrare sînt: n, numărul de noduri şi A, matricea de adiacenţă corespunzătoare
grafului. Matricea M calculată de algoritm constituie ieşirea şi este matricea existenţei drumurilor
în graful G.

2.3.3. Componente conexe ale unui graf

Definiţia 2.3.5. Fie G=(V,E) graf netrivial. Vîrfurile u,v  V sînt conectate dacă există un
u-v drum în G.
Definiţia 2.3.6. Dacă G este un graf, atunci o componentă conexă a lui G este un subgraf
conex al lui G, maximal în raport cu proprietatea de conexitate.

24
Algoritmi și tehnici de programare 25

Exemplu
2.3.4. Componentele conexe ale grafului
1
5

2
6

4
3

sînt: C1={1,2,3}, C2={4,5}, C3={6}.

Observaţii
1) Un graf este conex dacă şi numai dacă numărul componentelor sale conexe este
1.
2) Mulţimile de vîrfuri corespunzătoare oricăror două componente conexe distincte
sînt disjuncte. Rezultă că mulţimile de vîrfuri corespunzătoare componentelor conexe ale
unui graf formează o partiţie a mulţimii vîrfurilor grafului.

Problema determinării componentelor conexe corespunzătoare unui graf poate fi


rezolvată în modul următor. Iniţial, este selectat drept vîrf curent un vîrf al grafului pentru care
este calculată componenta conexă care îl conţine. Dacă există vîrfuri care nu aparţin componentei
conexe determinate, este ales drept vîrf curent unul dintre aceste vîrfuri. În continuare este
aplicată aceeaşi metodă, pînă cînd au fost găsite toate componentele conexe ale grafului.
Determinarea componentei conexe care conţine un vîrf v0 dat poate fi realizată pe baza
următorului algoritm.
Pentru G=(V,E), V  n , n  1 şi v0  V, paşii algoritmului sînt:
Pas1: V0={v0}; E0=  ; i=0;
Pas 2: repetă Pas 3 pînă cînd Vi=Vi-1 şi Ei=Ei-1
Pas 3: i=i+1;
Vi  Vi 1  v / v  V, u  Vi 1 , uv  E;
E i  E i 1  e / e  E, u  Vi 1 , u incident cu e;
Ieşirea este Gi=(Vi,Ei), componenta conexă din care face parte v0.

Exemplu
2.3.5. Pentru graful,
1 7 3
2

4 5 8 9 6

Aplicarea algoritmului descris pentru v0=1, determină următoarea evoluţie:


I Vi Ei
i=0 {1} Ø
i=1 {1,2,4} {(1,2),(1,4)}
i=2 {1,2,4,7,8,5} {(1,2),(1,4),(2,7),(2,8),(7,8),(4,5),(4,7),(5,8)}
26 Metode de programare

2.3.4. Drumuri de cost minim

Definiţia 2.3.7. Fie G=(V,E,w) un graf ponderat. Costul drumului : u1,u2,..,un, notat
L(), este definit prin:
n 1
L    wu i ,u i 1  .
i 1
Pentru orice u şi v vîrfuri conectate în G, u  v, w-distanţa între u şi v, notată D(u,v), este
definită prin,
Du ,v   minL ,   Duv , unde Duv desemnează mulţimea tuturor u-v drumurilor
elementare din G. Dacă   Duv este astfel încît D(u,v)=L(), drumul  se numeşte drum de cost
minim.

Observaţie Cu toate că este utilizat termenul de w-distanţă, în general D nu este o


distanţă în sensul matematic al cuvîntului.În particular, dacă funcţia pondere asociază valoarea 1
fiecărei muchii a grafului, atunci pentru fiecare pereche de vîrfuri distincte ale grafului, costul
D(u,v) este lungimea unui cel mai scurt drum între cele două vîrfuri. În acest caz D este o distanţă
pe mulţimea vîrfurilor.

Algoritmul Dijkstra
Următorul algoritm a fost propus de către E. W. Dijkstra pentru determinarea w-
distanţelor D(u0,v) şi a cîte unui u0-v drum de cost minim pentru fiecare vîrf vu0 într-un graf
ponderat, unde u0 este prestabilit.
Fie G=(V,E,w) un graf conex ponderat, u0V, SV, u0S. Se notează S  V \ S şi
   
D u0 , S  min Du0 , x ; x  S . Fie v S astfel încît D(u0,v)=D(u0, S ),  : u0, u1,…,upv un u0-
v drum de cost minim. Evident, 0ip uiS şi  ’: u0, u1,…,up un u0- up drum de cost minim.
De asemenea,
  
D u0 , S  min Du0 ,u   w( uv ); u  S ,v  S ,uv  E . 
 
Dacă xS, y S astfel încît D u0 , S  Du0 , x   w( xy ) , rezultă
Du0 , y   Du0 , x   w( xy ) .
Pentru determinarea a cîte unui cel mai ieftin u0-v drum, algoritmul consideră o etichetare
dinamică a vîrfurilor grafului.Eticheta vîrfului v este (L(v),u), unde L(v) este lungimea unui cel
mai ieftin u0-v drum determinat pînă la momentul respectiv şi u este predecesorul lui v pe un
astfel de drum.
Pentru (V,E,w) graf conex ponderat, V  n şi u0V, calculul implicat de algoritmul
Dijkstra poate fi descris astfel:
Pas 1: i=0; S0={u0}; L(u0)=0, L(v)=  pentru toţi v  V, v≠u0. Dacă n=1 atunci stop
Pas 2: Pentru toţi v S i , dacă L(v)>L(ui)+w(uiv), atunci L(v)=L(ui)+w(uiv) şi etiche-
tează v cu (L(v),ui).
Pas 3: Se determină d=min{L(v), v S i } şi se alege ui+1 S i astfel încît L(ui+1)=d.
Pas 4: Si+1=Si  {ui+1}
Pas 5: i=i+1. Dacă i=n-1, atunci stop. Altfel, reia Pas 2.

Observaţie Dacă (V,E,w) graf ponderat neconex, atunci, pentru u0V, algoritmul lui
Dijkstra permite determinarea w-distanţelor D(u0,v) şi a cîte unui u0-v drum de cost minim pentru
toate vîrfurile v din componenta conexă căreia îi aparţine u0.

26
Algoritmi și tehnici de programare 27

Exemplu
2.3.6. Fie graful ponderat,
1

5 1

2 9
3
16
2
5 5
4

Considerînd u0=1, etapele în aplicarea algoritmului Dijkstra sînt:


P1: i=0; S0={1}; L(1)=0, L(i)=  pentru toţi i  2,5 .
P2: S 0 ={2,3,4,5}, u0=1
L(2)=  >L(1)+5=5  L(2)=5, etichetează 2 cu 1
L(3)=  >L(1)+1=1  L(3)=1, etichetează 3 cu 1
L(4)=  >L(1)+9=9  L(4)=9, etichetează 4 cu 1
L(5)=  , w(1,5)=  , deci L(5) nu se modifică
P3: selectează u1=3, L(3)=1, cea mai mică dintre w-distanţele calculate la P2
P4: S1={1,3}
P5: i=i+1=1 ≠ 4, reia P2

P2: S1 ={2,4,5}, u1=3


Nu se modifică nici o etichetă şi nici o w-distanţă (w(3,i)=  , pentru toţi i din
S1 )
P3: selectează u2=2, L(2)=5, cea mai mică dintre w-distanţele calculate la P2
P4: S2={1,3,2}
P5: i=i+1=2 ≠ 4, reia P2

P2: S 2 ={4,5}, u2=2


L(4)= 9>L(2)+2=7  L(4)=7, etichetează 4 cu 2
L(5)=  >L(2)+16=21, etichetează 5 cu 2
P3: selectează u3=4, L(4)=7, cea mai mică dintre w-distanţele calculate la P2
P4: S3={1,3,2,4}
P5: i=i+1=3 ≠ 4, reia P2

P2: S 3 ={5}, u3=4


L(5)= 21>L(4)+5=12, etichetează 5 cu 4
P3: selectează u4=5, L(5)=12, cea mai mică dintre w-distanţele calculate la P2
P4: S3={1,3,2,4,5}
P5: i=i+1=4, stop.
Algoritmul calculează următoarele rezultate:

Vîrful v pînă la care este 1 2 3 4 5


calculată w-distanţa
D(1,v), eticheta lui v 0, 1 5, 1 1, 1 7, 2 12, 4
28 Metode de programare

Drumurile de cost minim de la vîrful 1 la fiecare dintre vîrfurile grafului se stabilesc pe


baza sistemului de etichete astfel: drumul de la 1 la un vîrf v este dat de: v1, eticheta lui v, v2
eticheta lui v1 şamd, pînă se ajunge la eticheta 1. Astfel, v0 -drumurile de cost minim sînt:
pînă la 2: 2,1;
pînă la 3: 3,1;
pînă la 4: 4,2,1;
pînă la 5: 5,4,2,1.

Următoarea sursă C implementează algoritmul Dijkstra.

#include<stdio.h>
#include<conio.h>
#include<alloc.h>

typedef struct{
int predv;
float L;
} eticheta;

void creaza(int *s,int *sb,int nv,int u0)


{
s[0]=u0;
for(int j=0,i=0;i<nv;i++)
if(i-u0)sb[j++]=i;
}

void modifica(int *s,int *sb,int ui, int *ns, int *nb)


{
s[*ns]=ui;
(*ns)++;
for(int i=0;i<*nb;i++)
if(sb[i]==ui){
for(int j=i+1;j<*nb;j++)
sb[j-1]=sb[j];
(*nb)--;
return;
}
}

eticheta *Dijkstra(float w[][50],int nv,int u0)


{
eticheta *r=(eticheta *)malloc(nv*sizeof(eticheta));
for(int i=0;i<nv;i++)r[i].L=1000;
r[u0].L=0;
r[u0].predv=u0;
int s[50],sb[50],ns=1,nb=nv-1;
creaza(s,sb,nv,u0);
for(i=0;i<nv-1;i++){
float dmin=1000;
for(int j=0;j<nb;j++)
for(int k=0;k<ns;k++)
if(r[sb[j]].L>r[s[k]].L+w[sb[j]][s[k]]){
r[sb[j]].L=r[s[k]].L+w[sb[j]][s[k]];
r[sb[j]].predv=s[k];
}
int ui;
for(j=0;j<nb;j++)
if(r[sb[j]].L<dmin){
dmin=r[sb[j]].L;
ui=sb[j];
}

28
Algoritmi și tehnici de programare 29

modifica(s,sb,ui,&ns,&nb);
}
return r;
}

void main()
{
int n,i,j;
clrscr();
printf("Numarul de varfuri");
scanf("%i",&n);
printf("Matricea ponderilor:\n");
float w[50][50];
for(i=0;i<n;i++)
for(j=0;j<n;j++)
scanf("%f",&w[i][j]);
int u0;
printf("\nVarful initial:");
scanf("%i",&u0);
u0--;
eticheta *rez=Dijkstra(w,n,u0);
for(i=0;i<n;i++){
printf("Distanta de la vf. %i la vf. %i este %7.2f\n",u0+1,i+1,rez[i].L);
printf("Un drum de cost minim este:");
printf("%i, ",i+1);
j=rez[i].predv;
while(j-u0){
printf("%i, ", j+1);
j=rez[j].predv;
}
printf("%i\n\n",u0+1);
}
free(rez);
getch();
}
În anumite aplicaţii este necesară exclusiv determinarea w-distanţelor D(v0,v), pentru toţi
vV. În acest caz algoritmul Roy-Floyd permite o rezolvare a acestei probleme mai simplu de
implementat decît algoritmul Dijkstra.

Algoritmul Roy-Floyd
Pentru (V,E,w) graf ponderat, V  n şi W matricea ponderilor, sistemul de w-distanţe
D(v0,v), vV, poate fi calculat pe baza următoarei funcţii (similară algoritmului Roy-Warshall),

void Roy_Floyd (float w[10][10],unsigned n,float


d[10][10],float MAX)
{int i,j,k;
for (i=0;i<n;i++)
for (j=0;j<n;j++)
d[i][j]=w[i][j];
for (j=0;j<n;j++)
for (i=0;i<n;i++)
if(d[i][j]<MAX)
for (k=0;k<n;k++)
if (d[i][k]>d[i][j]+d[j][k])
d[i][k]=d[i][j]+d[j][k];
}

Matricea D calculată de algoritm este matricea w-distanţelor D(u,v) în graful ponderat


conex (V,E,w); pentru orice 1  i , j  n
30 Metode de programare

D( vi ,v j ), vi ,v j sunt conectate


d ij  
 , altfel
Într-adevăr, procedura realizează calculul dinamic al w-distanţei între oricare două vîrfuri
i şi k, astfel: dacă există un drum i-k drum ce trece prin j ( 1  j  n ), cu costul corespunzător
(dij+djk) inferior costului curent (dik), atunci noul drum de la i la k via j este de cost mai mic decît
costul drumului vechi, deci w-distanţa între i şi k trebuie reactualizată la dij+djk.

Algoritmul Yen
Algoritmul propus de Yen pentru calculul tuturor w-distanţelor într-un graf ponderat este
mai eficient din punctul de vedere al volumului de operaţii decît algoritmul Roy-Floyd. Fie
(V,E,w) un graf ponderat şi W matricea ponderilor. Pentru determinarea w-distanţelor de la vîrful
vk fixat la celelalte vîrfuri ale grafului, algoritmul Yen iniţiază următoarele operaţii,

Pas 1: D=W
Pas 2: i=1; λ(k)=0, b(k)=0; λ(j)=0, pentru toţi 1  j  n , j  k
Pas 3: Calculează min{dkj; 1  j  n , λ(j)=1};
Determină j0 astfel încît λ(j0)=1 şi d kj0 = min{dkj; 1  j  n , λ(j)=1}
B(j0)= d kj0 , λ(j0)=0
d[k,j] =min{d[k,j],d[k,j0]+d[j0,j]}, pentru toţi j, 1  j  n
i=i+1
Pas 4: Dacă i<n, reia Pas 3, altfel stop.

La terminarea algoritmului componentele vectorului B sînt respectiv egale cu w-distanţa


de la vîrful vk la orice alt vîrf al grafului. Într-adevăr, componentele egale cu 1 ale vectorului λ
indică, la fiecare reluare a pasului 3, vîrfurile grafului pentru care nu s-a calculat încă w-distanţa
la vîrful vk. După fiecare efectuare a etapei 3, dacă j0 a fost selectat, atunci B(j0)=D(vk, v j0 ).

Exemplu
2.3.7. Fie graful
1

4
3
5 7
2
2
1
5
4 3
4

30
Algoritmi și tehnici de programare 31

Se consideră vk=1.

 3 2 7 4 
 
3  5  
Pas 1: D   2 5  4 1
 
7  4  
4    
 1

Pas 2: i=1, λ=(0,1,1,1,1); B(1)=0


 3 2 6 3 
 
3  5  
Pas 3: j0=3, B(3)=2, λ=(0,1,0,1,1); D   2 5  4 1  ; i=2
 
7  4  
4    
 1
Pas 4: i<5, reia Pas 3

Pas 3: j0=2, B(2)=3, λ=(0,0,0,1,1); nici o modificare în matricea D; i=3


Pas 4: i<5, reia Pas 3

Pas 3: j0=5, B(5)=3, λ=(0,0,0,1,0); nici o modificare în matricea D; i=4


Pas 4: i<5, reia Pas 3

Pas 3: j0=4, B(4)=6, λ=(0,0,0,0,0); nici o modificare în matricea D; i=5


Pas 4: i=5, stop.

2.4. Circuite şi cicluri în grafuri şi în digrafuri


Definiţia 2.4.1. Fie G=(V,E) un graf netrivial, u, vV şi  un u-v drum în G.  se
numeşte proces dacă toate muchiile drumului  sînt distincte. Drumul  este trivial dacă  : u,u.
Definiţia 2.4.2. Drumul  este un circuit dacă  este un proces netrivial închis.
Definiţia 2.4.3. Circuitul  : v1, v2,…., vn, v1 cu n3 este un ciclu al grafului, dacă, pentru
orice i, j, cu 1  i , j  n, i  j , rezultă vivj.
Observaţie Orice ciclu este un drum elementar închis.
Definiţia 2.4.4. Graful G este aciclic dacă nu există cicluri în G.
Observaţie Într-un digraf D noţiunile de proces, circuit, ciclu sînt definite ca şi în cazul
grafurilor.
32 Metode de programare

Exemple
2.4.1. În graful,
v1
v4
v2

v3
v5 v6
 1: v1, v2, v3, v6, v5 este un proces;
 2: v1, v2, v3, v6, v5, v3, v4, v1 este un circuit şi nu este ciclu;
 3: v1, v3, v5, v4, v1 este un ciclu.

2.4.2. Drumul  : v1,v2,v4,v3,v1 este un ciclu, deci graful conţine cicluri.


v1 v2

v3 v4
2.4.3. Digraful,
V1 V2

V3 V4
nu conţine cicluri.
Definiţia 2.4.5. Fie D=(V,E) un digraf. Funcţiile grad exterior, odD, respectiv grad
interior, idD, sînt definite prin, od D : V  N ; id D : V  N ,
u  V , od D u   v / v  V , uv  E , u  V , id D u   v / v  V , vu  E
Funcţia grad, notată degD, este definită astfel,
deg D : V  N, u  V, deg D u   id D u   od D u  .

Algoritmul Marimont
Procedura Marimont verifică dacă un digraf D=(V,E), V  n , este sau nu aciclic. La
terminarea calculului este afişat mesajul “DA”, dacă digraful D este aciclic, respectiv “NU”, în
caz contrar. Descrierea pe paşi a algoritmului Marimont este,
Pas 1: V0=V, E0=E, D0=(V0,E0)
Pas 2: Dacă od D0 v   1 pentru toţi vV0, scrie “NU”, stop (dacă toate vîrfurile sînt
extremităţi iniţiale ale măcar unui arc, atunci există cicluri în D0); altfel, continuă.
Pas 3: Selectează vV0 cu od D0 v   0 ;V0=V0\{v}; E0=E0-{e/ eE0, e incidentă cu v în
D0}; D0=(V0,E0)
Pas 4: Dacă V0Ø, atunci reia pasul 2; altfel scrie “DA”, stop.

Exemple
2.4.4 Pentru digraful,

32
Algoritmi și tehnici de programare 33

1
e1 e2
2
4 e3
e4
3 e5
5
evoluţia algoritmului Marimont este,
Pas 1: V0={1,2,3,4,5}, E0={e1,e2,e3,e4,e5}
Pas 2: od D0 5  0 , continuă
1
e1 e2
2
4 e3
e4
3
Pas 3: Selectează vîrful 5, elimină 5 din V0, elimină arcul e5 E0
Pas 4: reia de la pasul 2
Pas 2: od D0 i   1 pentru toţi i din V0 ={1,2,3,4}, scrie “NU”, stop.
2.4.5. Pentru digraful D:
1
6
e7 e4 e1
5 4 e5
e9 e6 2
e8 e3 e2
7 3
algoritmul Marimont determină următoarea secvenţă de operaţii:
Pas 1: V0={1,2,3,4,5,6,7}, E0={e1,e2,e3,e4,e5,e6,e7,e8,e9}
Pas 2: od D0 7   0 , continuă
Pas 3: Selectează vîrful 7, elimină 7 din V0, elimină arcele e8 şi e9 din E0
D0:
1
6
e7 e4 e1
5 4 e5
e6 2
e3 e2
3
Pas 4: reia de la pasul 2

Pas 2: od D0 6  0 , continuă


Pas 3: Selectează vîrful 6, elimină 6 din V0, elimină arcul e7 din E0
D0:
1
e4 e1
5 4 e5
e6 2
e3 e2
3
34 Metode de programare

Pas 4: reia de la pasul 2


Pas 2: od D0 5  0 , continuă
Pas 3: Selectează vîrful 5, elimină 5 din V0, elimină arcul e6 din E0
D0:
1
e4 e1
4 e5
2
e3 e2
3
Pas 4: reia de la pasul 2
Pas 2: od D0 4  0 , continuă
Pas 3: Selectează vîrful 4, elimină 4 din V0, elimină arcele e4, e5 şi e3 din E0
D0:
1
e1
2
e2
3
Pas 4: reia de la pasul 2
Pas 2: od D0 3  0 , continuă
Pas 3: Selectează vîrful 3, elimină 3 din V0, elimină arcul e2 din E0
D0:
1
e1
2
Pas 4: reia de la pasul 2
Pas 2: od D0 2  0 , continuă
Pas 3: Selectează vîrful 2, elimină 2 din V0, elimină arcul e1 din E0

D0: • 1
Pas 4: reia de la pasul 2
Pas 2: od D0 1  0 , continuă
Pas 3: Selectează vîrful 1, elimină 1 din V0
V0=Ø
Pas 4: scrie “DA”, stop.
Algoritmul Marimont poate fi descris în C astfel,

#include<stdio.h>
#include<conio.h>

typedef struct{
int vi,vf;} arc;
int grad_exterior(arc *arce,int na,int v)
{
int od=0;
for(int i=0;i<na;i++)

34
Algoritmi și tehnici de programare 35

if(arce[i].vi==v) od++;
return od;
}

void elimina_varf(int *varf,int *nv,int v)


{
int gasit=0;
for(int i=0;(i<*nv)&&!gasit;i++)
if(varf[i]==v){
gasit=1;
for(int j=i+1;j<*nv;j++)
varf[j-1]=varf[j];
}
(*nv)--;
}

void elimina_arce(arc *arce,int *na, int v)


{
for(int i=0;i<*na;)
if((arce[i].vi==v)||(arce[i].vf==v)){
for(int j=i+1;j<*na;j++){
arce[j-1].vi=arce[j].vi;
arce[j-1].vf=arce[j].vf;
}
(*na)--;
}
else i++;
}

int ciclic(int *varf,arc *arce,int nv, int na)


{
for(int i=0;i<nv;i++)
if(!grad_exterior(arce,na,varf[i])) return 0;
return 1;
}

int Marimont(int *varf,arc *arce,int nv, int na)


{
while(nv){
printf("\n\nGraful curent\n");
printf("Varfuri:");
for(int i=0;i<nv;i++) printf("%i ",varf[i]);
printf("\nArce:");
for(i=0;i<na;i++) printf("(%i,%i) ",arce[i].vi,arce[i].vf);
getch();
if(ciclic(varf,arce,nv,na)) return 0;
int gasit=0;
for(i=0;(i<nv)&&!gasit;i++)
if(!grad_exterior(arce,na,varf[i])){
gasit=1;
elimina_arce(arce,&na,varf[i]);
elimina_varf(varf,&nv,varf[i]);
}
}
return 1;
}

void main()
{
int n,nv, na;
int vf[20],i,j,a[20][20];
arc arce[100];
clrscr();
36 Metode de programare

printf("Numarul de varfuri");
scanf("%i",&n);
for(i=0;i<n;i++)
vf[i]=i+1;
nv=n;na=0;
printf("Matricea de adiacenta:\n");
for(i=0;i<n;i++)
for(j=0;j<n;j++){
scanf("%i",&a[i][j]);
if(a[i][j]){
arce[na].vi=i+1;
arce[na].vf=j+1;
na++;
}
}
if(Marimont(vf,arce,nv,na))
printf("\n\nDigraful este aciclic");
else printf("\n\nDigraful este ciclic");
getch();
}

36
Algoritmi și tehnici de programare 37

3. Structuri arborescente

Una dintre cele mai studiate clase de grafuri sînt cele de tip arbore. În acest capitol sînt
prezentate principalele caracteristici ale arborilor, algoritmi pentru calculul arborelui parţial de
cost minim, arbori direcţionaţi, arbori cu rădăcină şi arbori binari. Pe lîngă operaţiile primitive
asupra arborilor – căutarea unei informaţii, inserarea unui nod, extragerea unui nod şi metode de
parcurgere, sînt prezentate două clase importante de arbori binari: arbori de sortare şi arbori de
structură.

3.1. Grafuri de tip arbore


3.1.1. Definiţii şi caracterizări ale grafurilor arbori

Structurile cele mai simple şi care apar cel mai frecvent în aplicaţii sînt cele arborescente
(arbori). Grafurile arbori constituie o subclasă a grafurilor conexe.
Definiţia 3.1.1 Graful G este arbore dacă G este aciclic şi conex.
Definiţia 3.1.2. Fie G=(V,E) graf arbore. Subgraful H=(V1,E1) al lui G este subarbore al
lui G dacă H este graf arbore.

Exemple
3.1.1. Graful
1

2
3 4

5 6
7
este arbore, deoarece, orice (i,j)  E , i≠j, există un i-j drum şi graful nu conţine cicluri.
38 Metode de programare

3.1.2. Graful
1 3

4 2 7
5
7
nu este arbore, deoarece drumul  :1,4,6,2,1 este un ciclu.

3.1.3. Graful
1 3 7

4 2
5
9
6
8
nu este arbore, deoarece conţine trei componente conexe: {1,2,3,4,6}, {3} şi {7,8}.

Verificarea proprietăţii unui graf de a fi arbore poate fi realizată prin intermediul unor
algoritmi care să verifice calităţile de conexitate şi respectiv aciclicitate. De asemenea, verificarea
proprietăţii unui graf de a fi arbore poate fi realizată astfel.
Proprietatea 1. Un graf G=(V,E), cu V  n , E  m este graf arbore dacă şi numai dacă
G este aciclic şi n=m+1.

Exemple
3.1.4. Graful din 3.1.1 este arbore, pentru că este aciclic şi n=7, m=6.
3.1.5. Graful din 3.1.2. nu este arbore pentru că este ciclic.
3.1.6. Graful din exemplul 3.1.3. nu este arbore deoarece este aciclic, dar n=9, m=6.
Proprietatea 2 Un graf G=(V,E), cu V  n , E  m este graf arbore dacă şi numai dacă
G este conex şi n=m+1.

Exemple
3.1.7. Graful din 3.1.1. este arbore deoarece este conex şi n=m+1.
3.1.8. Graful conex din exemplul 3.1.2. nu este arbore pentru că n=6 şi m=8.
3.1.9. Graful din 3.1.3. nu este conex, deci nu este graf arbore.
Observaţie
Fie G=(V,E) un graf. Următoarele afirmaţii sînt echivalente,
1. G este graf arbore;
2. G este graf conex minimal: oricare ar fi eE, prin eliminarea muchiei e din E, graful
rezultat nu este conex;
3. G este graf aciclic maximal: prin adăugarea unei noi muchii în graf rezultă cel puţin
un ciclu.

38
Algoritmi și tehnici de programare 39

Definiţia 3.1.3. Se numeşte graf asimetric un digraf D=(V,E) cu proprietatea că pentru


orice u ,v  E dacă uvE, atunci vu  E. Digraful D este simetric dacă u ,v  E , uvE, dacă şi
numai dacă vuE.
Definiţia 3.1.4. Fie D=(V,E) digraf netrivial. Graful G=(V,E’), unde E’={uv/ uvE sau
vuE} se numeşte graf suport al digrafului D.
Definiţia 3.1.5. Un arbore direcţionat este un graf orientat asimetric şi astfel încît graful
suport corespunzător lui este graf arbore.
Definiţia 3.1.6. Arborele direcţionat T=(V,E) este arbore cu rădăcină dacă există rV
astfel încît, pentru orice uV, u  r, există r-u drum în T. Vîrful r se numeşte rădăcina arborelui
direcţionat T.
Definiţia 3.1.7. Fie T=(V,E) arbore direcţionat. Arborele T1=(V1,E1) este subarbore al lui
T dacă V1V, E1E şi T1 este arbore direcţionat.

Observaţie Graful suport al unui arbore direcţionat este aciclic, deci, pentru orice uV,
u  r, r-u drumul din T este unic. De asemenea, un arbore direcţionat are cel mult o rădăcină.
Rezultă că, pentru orice uV, u  r, distanţa de la rădăcină la vîrful u este egală cu numărul de
muchii ale r-u drumului în T.

Exemple
3.1.10. Arborele direcţionat
1
3 4

6
5
2

7 8 9 10
este arbore cu rădăcină 1.

3.1.11 Arborele direcţionat


1 2

3 7

5 6
nu are rădăcină.
3.1.12. Arborele
40 Metode de programare

1
4

6
5
2

8 10
este un subarbore cu rădăcină 1 al arborelui din 3.1.10.

3.1.2. Reprezentări şi parcurgeri ale arborilor orientaţi

Definiţia 3.1.8. Un arbore orientat este un arbore direcţionat cu rădăcină.


Definiţia 3.1.9. Fie T=(V,E), un arbore orientat cu rădăcină r. Un vîrf v  V este

situat pe nivelul i al arborelui T, dacă distanţa de la vîrf la rădăcină este egală cu i. Rădăcina

arborelui este considerată de nivel 0.

Deoarece orice arbore orientat este în particular digraf, reprezentarea arborilor orientaţi
poate fi realizată prin utilizarea oricăreia dintre modalităţile prezentate în §8.1. Datorită
caracteristicilor arborilor orientaţi pot fi însă obţinute reprezentări mai eficiente din punct de
vedere al spaţiului de memorie solicitat.
Una dintre modalităţi este reprezentarea de tip FIU-FRATE, care constă în numerotarea
convenţională a vîrfurilor grafului şi memorarea, pentru fiecare vîrf i al arborelui, a următoarelor
informaţii,
- FIU(i): numărul ataşat primului descendent al vîrfului i;
- FRATE(i): numărul ataşat vîrfului descendent al tatălui vîrfului i şi care urmează
imediat lui i;
- INF(i): informaţia ataşată vîrfului i (de obicei valoarea i).
Pentru reprezentarea arborelui sînt reţinute rădăcina şi numărul nodurilor. Absenţa
„fiului”, respectiv a :fratelui” unui vîrf este marcată printr-o valoare din afara mulţimii de numere
ataşate vîrfurilor (de obicei valoarea 0).

Exemplu
3.1.13. Arborele orientat

40
Algoritmi și tehnici de programare 41

2 3 4

5 6 7 8

9 10 11 12 13 14 15 16

este reprezentat astfel,


N=16, R=1 (rădăcina),
FIU=(2,5,0,8,0,9,0,14,0,0,0,0,0,0,0,0)
FRATE=(0,3,4,0,6,7,0,0,10,11,12,13,0,15,16,0)

O alternativă a reprezentării FIU-FRATE poate fi obţinută prin utilizarea structurile


de date dinamice. Presupunînd că fiecare vîrf al arborelui are cel mult n descendenţi, fiecărui
vîrf îi este ataşată structura,

identificator vector de legături către descendenţii vîrfului

vîrf
adresă fiu 1 … adresă fiu n

Următoarea sursă C implementează problema construcţiei unui arbore orientat,


reprezentat prin intermediul unei structuri dinamice arborescente. Numărul maxim de
descendenţi ai unui nod este 4. În cazul unui număr mai mare de descendenţi este preferată în
general reprezentarea FIU-FRATE, datorită dimensiunii spaţiului de memorie ocupat.
Afişarea informaţiilor arborelui creat este realizată prin traversarea în A-preordine (a se vedea
paragraful următor).

#include<stdio.h>
#include<conio.h>
#include<alloc.h>

typedef struct nod{


int inf;
struct nod *fiu[4];
} arb, *arbore;
void inserare_tata(arbore *ptata,int k,int info)
{ arbore nou=(arbore)malloc(sizeof(arb));
nou->inf=info;
for(int i=0;i<4;i++)nou->fiu[i]=NULL;
(*ptata)->fiu[k]=nou;
}
42 Metode de programare

void inserare(arbore *ppred)


{ int j,info;
arbore *pred;
for(int nr=0;(*ppred)->fiu[nr];nr++){
(*pred)=(*ppred)->fiu[nr];
printf("Numarul de fii ai nodului %i:",(*pred)->inf);
scanf("%i",&j);
for(int k=0;k<j;k++){
scanf("%i",&info);
inserare_tata(pred,k,info);
}
}
for(nr=0;(*ppred)->fiu[nr];nr++)
inserare(&((*ppred)->fiu[nr]));
}

void A_preordine(arbore r)
{
if(r){
printf("%i ",r->inf);
for(int i=0;i<4;i++)
A_preordine(r->fiu[i]);
}
}
void main(){
clrscr();
int n,j,info;
arbore radacina=NULL;
printf("Introduceti informatiile pe niveluri\n");
printf("Introduceti radacina\n");
scanf("%i",&info);
radacina=(arbore)malloc(sizeof(arb));
radacina->inf=info;
for(int i=0;i<4;i++)radacina->fiu[i]=NULL;
printf("Numarul de fii ai nodului %i",radacina->inf);
scanf("%i",&j);
for(int k=0;k<j;k++){
scanf("%i",&info);
inserare_tata(&radacina,k,info);
}
arbore ppred=radacina;
inserare(&ppred);
printf("Parcurgerea A-preordine a arborelui : \n");
A_preordine(radacina);
getch();}

Parcurgerea unui arbore orientat revine la aplicarea sistematică a unei reguli de


vizitare a vîrfurilor arborelui. Cele mai utilizate reguli de parcurgere a arborilor orientaţi sînt
A-preordine, A-postordine şi parcurgerea pe niveluri.

Parcurgerea în A-preordine
Modalitatea de vizitare a vîrfurilor în parcurgerea în A-preordine poate fi descrisă
astfel. Iniţial, rădăcina arborelui este selectată drept vîrf curent. Este vizitat vîrful curent şi
sînt identificaţi descendenţii lui. Se aplică aceeaşi regulă de vizitare pentru arborii avînd ca
rădăcini descendenţii vîrfului curent, arborii fiind vizitaţi în ordinea precizată prin numerele
ataşate vîrfurilor rădăcină corespunzătoare.

42
Algoritmi și tehnici de programare 43

Exemplu
3.1.14. Pentru arborele orientat din exemplul 3.1.13., prin aplicarea parcurgerii în A-
preordine, rezultă: 1,2,5,6,9,10,11,12,13,7,3,4,8,14,15,16.

În reprezentarea FIU-FRATE, implementarea parcurgerii în A-preordine este


realizată prin următoarea funcţie recursivă, cu parametru de intrare rădăcina arborelui curent.
void A_preordine (nod R)
{
if (R){
vizit (R);
A_preordine(FIU[R]);
A_preordine(FRATE[R]);
}
}

În sursa prezentată în paragraful precedent, funcţia A-preordine implementează acest


tip de traversare în cazul arborilor orientaţi reprezentaţi prin intermediul structurilor
arborescente.

Parcurgerea A-postordine
Regula de parcurgerea în A-postordine este asemănătoare traversării A-preordine,
singura diferenţă fiind aceea că, în acest tip de traversare, rădăcina fiecărui arbore este
vizitată după ce au fost vizitate toate celelalte vîrfuri ale arborelui.

Exemplu
3.1.15. Pentru arborele orientat din exemplul 3.1.13. ordinea de vizitare a vîrfurilor
este: 5,9,10,11,12,13,6,7,2,3,14,15,16,8,4,1.
Pentru arbori reprezentaţi prin structuri dinamice de date, implementarea parcurgerii
în A-postordine poate fi obţinută pe baza următoarei funcţii recursive. Parametrul de intrare
al funcţiei A_postordine reprezintă rădăcina arborelui curent în momentul apelului.

void A_postordine (nod R)


{
if (R) {
for(i=0;i<n;i++) A_postordine(R->leg[i]);
vizit (R);
}
}
Observaţie Parcurgerile în A-preordine şi A-postordine sînt variante de parcurgeri în
adîncime (variante ale metodei DF). Ambele metode consideră prioritare vîrfurile aflate la
distanţă maximă faţă de rădăcina arborelui iniţial.
Parcurgerea pe niveluri
Parcurgerea unui arbore orientat pe niveluri constă în vizitarea vîrfurilor sale în
ordinea crescătoare a distanţelor faţă de rădăcină.

Exemplu
3.1.16. Pentru arborele definit în exemplul 3.1.13., prin aplicarea parcurgerii pe
niveluri, rezultă următoarea ordine de vizitare a nodurilor, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16.

Ca şi în cazul metodei BF, implementarea parcurgerii pe niveluri este bazată pe


utilizarea unei structuri de coadă C. La momentul iniţial rădăcina arborelui este inserată în C.
44 Metode de programare

Atîta timp cît timp coada este nevidă, este preluat cu ştergere un vîrf din C, este vizitat şi sînt
introduşi în coadă descendenţii săi. Calculul este încheiat cînd C=Ø.
În cazul reprezentării FIU-FRATE a arborelui de traversat, parcurgerea pe niveluri
poate fi implementată prin următoarea funcţie.

void parcurgere_pe_niveluri(nod R,int FIU[],int FRATE[],int n)


{
ptcoada C=NULL;push(C,R);
while (C) {
pop(C,v); VIZIT(v);
v=FIU[v];
while (v){
push(C,v); v=FRATE[v];
}
}
}

Observaţie Funcţiile push şi pop implementează inserarea unuei celule în coadă,


respectiv extragerea unui element al cozii.

Exemplu
3.1.17. Pentru arborele de la exemplul 3.1.13., evoluţia algoritmului este,

t=1 1 8

t=2 2 3 4

t=3 3 4 5 6 7

t=4 4 5 6 7

t=5 5 6 7 8

t=6 6 7 8

t=7 7 8 9 1 1 1 1

0 1 2 3

t=8 8 9 1 1 1 1

0 1 2 3

44
Algoritmi și tehnici de programare 45

t=9 9 1 1 1 1 1 1 1

0 1 2 3 4 5 6

t=10 1 1 1 1 1 1 1

0 1 2 3 4 5 6

t=11 1 1 1 1 1 1

1 2 3 4 5 6

t=12 1 1 1 1 1

2 3 4 5 6

t=13 1 1 1 1

3 4 5 6

t=14 1 1 1

4 5 6

t=15 1 1

5 6

t=16 1

t=17

deci vîrfurile sînt vizitate în ordinea: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16.

Observaţie Metoda BF pentru parcurgerea grafurilor este o generalizare a tehnicii de


parcurgere pe niveluri a arborilor orientaţi.
O alternativă de implementare a parcurgerii pe niveluri poate fi descrisă prin
intermediul funcţiilor recursive frati şi parc. Coada C este o variabilă globală şi este
iniţializată cu rădăcina arborelui. Parcurgerea este realizată prin apelul parc(C).

void frati(v)
{if (v){push(C,v);
fraţi(FRATE[v]);
}
}

void parc()
{if (C){pop(C,v);VIZIT(v);
frati(FIU[v]); parc();
}
}
46 Metode de programare

3.1.3. Arbori parţiali. Algoritmul Kruskal

Definiţia 3.1.10. Fie G un graf. Subgraful parţial H este un arbore parţial al lui G
dacă H este graf arbore.
Definiţia 3.1.11. Fie G=(V,E,w) un graf ponderat conex.Dacă T=(V,E0) este un arbore
parţial al grafului G’=(V,E), ponderea arborelui T, notată W(T), este definită prin
W(T)= 
eE0
w( e ) .

Exemplu
3.1.18. Pentru graful ponderat
1
4 3
2

2 6 8 5

2 9
1 12

4 3

T este un arbore parţial de pondere 32.

4 2 3

2 6 5

8 9

4 3

Definiţia 3.1.12. Arborele parţial T0T(G) este arbore parţial minim pentru G dacă
W(T0)=min{W(T); TT(G)}, unde T(G) este mulţimea arborilor parţiali corespunzători
grafului G.
Observaţie Dacă G este graf finit, atunci T(G) este o mulţime finită, deci orice graf
finit ponderat şi conex are cel puţin un arbore parţial minim.
În continuare este prezentat algoritmul Kruskal pentru determinarea unui arbore
parţial minim al unui graf ponderat conex G=(V,E,w).
Pas 1: i=1; E0=
Pas 2: Determină R={e/eE \ Ei-1 astfel încît graful (V,Ei-1  {e}) este aciclic}
Dacă R=, atunci stop; altfel, selectează eiR cu w(ei)=min{w(e), eR}; Ei=Ei-
1  {e i}

46
Algoritmi și tehnici de programare 47

Pas 3: i=i+1 şi reia pasul 2.


Arborele parţial de cost minim al grafului G este (V,Ei-1).
Pentru implementarea algoritmului Kruskal, graful conex ponderat este reprezentat
sub formă tabelară, muchiile fiind ordonate crescător după ponderi. Muchiile selectate de
algoritm pot fi menţinute de asemenea într-o structură tabelară, sau doar marcate ca fiind
incluse în mulţimea de muchii din arborele parţial minim a cărui construcţie este dorită. În
varianta prezentată în continuare muchiile selectate sînt afişate.
Verificarea condiţiei ca muchia selectată să nu formeze nici un ciclu cu muchiile
selectate la etapele precedente este realizată prin utilizarea un vector TATA, definit astfel.
Pentru fiecare vîrf i (vîrfurile grafului fiind numerotate de la 1 la n, unde n este numărul de
noduri ale grafului), componenta TATA [i] este predecesorul său în arborele care conţine
vîrful i construit pînă la momentul curent dacă i nu este rădăcina acelui arbore, respectiv
TATA[i] este egal cu –numărul de vîrfuri ale arborelui de rădăcină i, în caz contrar.
Componentele vectorului TATA sînt iniţializate cu valoarea -1.
Calculul care realizează adăugarea unei noi muchii poate fi descris astfel. Este
determinată o muchie de cost minim e=v1v2 care nu a fost selectată anterior. Dacă vîrfurile
v1 şi v2 nu aparţin aceluiaşi arbore, atunci proprietatea de aciclicitate este îndeplinită şi
muchia e este adăugată la structura curentă. Adăugarea muchiei e selectate este realizată prin
reunirea arborilor din care fac parte v1 şi v2 de rădăcini r1, respectiv r2, astfel: dacă
TATA[r1]<TATA[r2], atunci arborele rezultat prin reunirea celor doi arbori are ca rădăcină
vîrful r1, iar vîrful r2 devine fiu al lui r1. Altfel, rădăcina arborelui rezultat prin reunire fiind
r2, iar r1 devenind fiu al rădăcinii. Calculul se încheie după ce a fost adăugată şi cea de-a (n-
1)-a muchie.
Algoritmul Kruskall poate fi implementat prin următoarea sursă C:

#include<stdio.h>
#include<conio.h>
int radacina(int v,int *tata)
{ int u=v;
while(tata[u]>=0) u=tata[u];
return u; }

int kruskal(int a[][3],int nm, int nv)


{
int tata[50],i,j;
int c=0;
for(i=0;i<nv;i++)tata[i]=-1;
for(j=i=0;i<nv-1;j++){
int v1=a[j][0]; int v2=a[j][1];
int k=radacina(v2,tata);int p=radacina(v1,tata);
if(k-p){
if(tata[k]<tata[p]){
tata[k]+=tata[p];tata[p]=k;
}
else{
tata[p]+=tata[k];tata[k]=p;
}
c+=a[j][2];printf("%i -> %i cost %i\n",v1+1,v2+1,a[j][2]);
i++;
}
}
return c;
}

void main()
{
clrscr();
48 Metode de programare

int nv,nm, a[100][3];


printf("Numarul de varfuri:");scanf("%i",&nv);
printf("Numarul de muchii");scanf("%i",&nm);
printf("Matricea de reprezentare\n");
for(int i=0;i<nm;i++)
for(int j=0;j<3;j++)
scanf("%i",&a[i][j]);
for(i=0;i<nm;i++)
for(int j=0;j<2;j++)a[i][j]--;
printf("Arborele de cost minim: \n");
int cost=kruskal(a,nm,nv);
printf("\ncu costul%i",cost);
getch();
}
Exemplu
3.1.19. Evoluţia determinată de program pentru graful
1 2 3 1
 
2 4 2
1 6 2
4 2 3  
1 5 3
2 6 8 5 A  3 4 4


1 2 4
9  
2 8 1 4 6 8
5 6 8
 
4
3 6 9 
4 3

este:
i, j după cea de-a t-a iteraţie muchia selectată TATA Costul

t=0 (-1,-1,-1,-1,-1,-1)

t=1,i=0,j=0 (2,3) (-1,-2,2,-1,-1,-1) 1

t=2,i=1,j=1 (2,4) (-1,-3,2,2,-1,-1) 2

t=3,i=2,j=2 (1,6) (-2,-3,2,2,-1,1) 2

t=4,i=3,j=3 (1,5) (-3,-3,2,2,1,1) 3

t=5,i=4,j=4 (-3,-3,2,2,1,1)

t=6,i=4,j=5 (1,2) (-5,1,1,2,1,1) 4

MUCHIILE ARBORELUI MINIM: {(2,3),(2,4),(1,6),(1,5),(1,2)} COSTUL:

12

48
Algoritmi și tehnici de programare 49

3.2. Arbori binari


3.2.1. Reprezentarea arborilor binari. Modalităţi de parcurgere
Definiţia 3.2.1. Un arbore binar este un arbore orientat cu proprietatea că pentru orice vîrf
v, od(v)2. Dacă od(v)=2, cei doi descendenţi sînt desemnaţi ca descendent stîng (fiu stînga)
respectiv descendent drept (fiu dreapta). Pentru vîrfurile cu od(v)=1, unicul descendent este
specificat fie ca fiu stînga, fie ca fiu dreapta.
Definiţia 3.1.2. Se numeşte nod terminal orice vîrf v al arborelui cu od(v)=0. În caz
contrar nodul v este neterminal.
Reprezentarea unui arbore binar este realizată printr-o structură arborescentă. Pentru
fiecare nod N al arborelui binar sînt memorate informaţia asociată lui N şi legăturile către
descendenţii lui. Absenţa unui descendent este reprezentată prin NULL.
identificator legătură fiu legătură fiu
nod stîng drept
Definiţia 3.2.3. Fie T=(V,E) un arbore binar cu rădăcina R. Subarborele stîng al lui T este
ST=(V\{R},E\{RS}), unde S este fiul stînga al rădăcinii. Subarborele drept al lui T este
DT=(V\{R},E\{RD}), unde D este fiul dreapta al rădăcinii.

Exemplu
3.2.1. Pentru arborele binar,
1

2 3

4 5 6 7

8 9 10
subarborii rădăcinii sînt:
2 3

6 7
4 5

8 9 10
Subarbore stîng Subarbore drept
În plus faţă de metodele A-preordine, A-postordine şi pe niveluri, parcurgerile în
preordine (RSD), inordine (SRD) şi respectiv postordine (SDR) sînt special considerate pentru
arbori binari şi au multiple aplicaţii. Regula de vizitare pentru aceste tipuri de parcurgere revine la
parcurgerea subarborelui stîng şi parcurgerea subarborelui drept corespunzători vîrfului curent.
La momentul iniţial vîrful curent este rădăcina arborelui. Diferenţa dintre cele trei tipuri de
parcurgere este dată de momentul în care devine vizitat fiecare vîrf al arborelui. În parcurgerea
RSD (rădăcină-subarbore stîng-subarbore drept), fiecare vîrf al arborelui este vizitat în momentul
în care este vîrf curent; în parcurgerea SRD, vizitarea vîrfului curent R este efectuată după ce a
fost parcurs subarborele stîng al lui R, respectiv în parcurgerea SDR vizitarea fiecărui vîrf este
efectuată după ce au fost parcurşi subarborii aferenţi lui.
50 Metode de programare

Exemplu
3.2.2. Pentru arborele de la exemplul 3.2.1., secvenţele de vîrfuri rezultate prin
aplicarea parcurgerilor RSD, SRD, SDR sînt:
- preordine: 1,2,4,8,5,3,6,9,10,7
- inordine: 4,8,2,5,1,9,6,10,3,7
- postordine: 8,4,5,2,9,10,6,7,3,1.

3.2.2. Arbori de sortare


Definiţia 3.2.4. Un arbore de sortare este un arbore binar cu următoarele proprietăţi,
- fiecărui nod i al arborelui îi este ataşată o informaţie INF(i) dintr-o mulţime ordonată
de valori;
- pentru fiecare nod i, INF(i) este mai mare decît INF(j), pentru toate nodurile j din
subarborele stîng al arborelui cu rădăcină i;
- pentru fiecare nod i, INF(i) este mai mică decît INF(j), pentru toate nodurile j din
subarborele drept al arborelui cu rădăcină i;
- pentru orice vîrfuri i şi j daca ij atunci INF(i)INF(j).

Exemplu 3.2.3. Arborele binar


50

30 70

10 40 90

20 80

este arbore de sortare.


Operaţiile primitive asupra arborilor de sortare sînt inserarea unui nod, ştergerea unui nod
şi parcurgerea arborelui (în preordine, inordine sau postordine). Inserarea şi ştergerea de noduri
aplicate unui arbore de sortare trebuie realizate astfel încît arborele rezultat să fie de asemenea
arbore de sortare.
Observaţie Parcurgerea în inordine a unui arbore de sortare determină obţinerea
secvenţei informaţiilor asociate vîrfurilor arborelui în ordine crescătoare.

Inserarea unui nod într-un arbore de sortare


Algoritmul de inserare a unei informaţii nr în arborele de sortare de rădăcină rad este
recursiv şi constă în efectuarea următoarelor operaţii: vîrful curent v la momentul iniţial este
rădăcina arborelui; dacă arborele de rădăcină v este vid, este generat arborele cu un singur nod, cu
informaţia ataşată nr; altfel:
- dacă informaţia ataşată nodului v este mai mare decît nr, atunci vîrf curent
devine fiul stînga al lui v;
- dacă informaţia ataşată nodului v este egală cu nr, atunci stop;
- dacă informaţia ataşată nodului v este mai mică decît nr, atunci vîrf curent
devine fiul dreapta al lui v.

50
Algoritmi și tehnici de programare 51

Exemplu
3.2.4. Aplicarea algoritmul descris pentru inserarea informaţiei 55 în arborele de sortare
din exemplul 3.2.3. determină următoarele operaţii,
INF(v)=50; 50<55, inserează în subarborele cu rădăcina avînd informaţia ataşată 70.
INF(v)=70; 70>55, inserează în subarborele stîng cu rădăcina NULL.
Este creat nodul cu informaţie 55, fiu stîng al nodului de informaţie 70.
Arborele rezultat este
50

30 70

10 40 55 90

20 80

Ştergerea unei informaţii dintr-un arbore de sortare


Algoritmul pentru ştergerea unei informaţii nr din arborele de sortare de rădăcină rad este
recursiv şi poate fi descris astfel. Vîrful curent v la momentul iniţial este rădăcina arborelui.
1. dacă arborele este vid atunci stop;
2. altfel
a) dacă informaţia ataşată nodului v este mai mare decît nr, atunci vîrful curent devine fiul
stînga al lui v;
b) dacă informaţia ataşată nodului v este mai mică decît nr, vîrful curent devine fiul dreapta al
lui v;
c) dacă INF(v)=nr atunci:
c1) dacă subarborele stîng este vid, atunci adresa vîrfului v este memorată într-o celulă
suplimentară aux, v devine fiul dreapta al lui v, iar celula aux este eliberată din memorie;
c2) dacă subarborele stîng este nevid atunci se determină cel mai mare element din
subarborele stîng;
c2.1) dacă fiul stînga al lui v nu are subarbore drept, atunci informaţia ataşată fiului
stînga se transferă în vîrful curent, iar fiul stînga este înlocuit cu fiul său stînga şi este
eliberată memoria corespunzătoare celulei v->fius;
c2.2) altfel, se transferă în rădăcină informaţia ataşată ultimului nod p determinat la
c2), nodul p este înlocuit cu fiul său stîng şi celula corespunzătoare lui p este
eliberată din memorie.

Exemplu
3.2.5. Ştergerea informaţiei 70 din arborele de sortare din exemplul 3.2.4. este realizată
astfel:
70>50, decide ştergerea din subarborele drept
70=70, decide ştergerea din arborele curent: rădăcina etichetată cu 70; există subarbore
stîng iar acesta nu are subarbore drept- nodul cu informaţie 70 este etichetat cu 55, iar p este
înlocuit cu subarborele său stîng (vid). Arborele rezultat
52 Metode de programare

50

30 55

10 40 90

20 80
este arbore de sortare.

Observaţie
Punctul c) de la pasul 2 al algoritmului de eliminare a unei informaţii dintr-un arbore de
sortare poate fi înlocuit cu:
c) dacă INF(v)=nr atunci:
c1) dacă subarborele drept este vid, atunci adresa vîrfului v este memorată într-o celulă
suplimentară aux, v devine fiul stînga al lui v, iar celula aux este eliberată din memorie;
c2) dacă subarborele drept este nevid atunci se determină cel mai mic element din
subarborele drept, altfel
c2.1.) dacă fiul dreapta al lui v nu are subarbore stîng, atunci informaţia ataşată fiului
dreapta este transferată în vîrful curent, iar fiul dreapta este înlocuit cu fiul său
dreapta şi este eliberată memoria corespunzătoare celulei v->fiud.
c2.2) altfel, se transferă în rădăcină informaţia ataşată ultimului nod p determinat la
c2), nodul p este înlocuit cu fiul său dreapta şi celula corespunzătoare lui p este
eliberată din memorie.

În următoarea sursă C sînt implementaţi algoritmii de adăugare şi ştergere în arbori de


sortare.

#include<stdio.h>
#include<conio.h>
#include<alloc.h>

typedef struct nod{


int inf;
struct nod *l,*r;
} arb, *arbore;

void inserare(arbore *radacina,int info)


{
if(*radacina==NULL){
arbore nou;
nou=(arbore)malloc(sizeof(arb));
nou->inf=info;
nou->l=nou->r=NULL;
*radacina=nou;
}
else if((*radacina)->inf>info)
inserare(&((*radacina)->l),info);
else if((*radacina)->inf<info)
inserare(&((*radacina)->r),info);
}

int extragere(arbore *radacina,int info)


{

52
Algoritmi și tehnici de programare 53

if(*radacina==NULL) return 0;
else if((*radacina)->inf>info)
return extragere(&((*radacina)->l),info);
else if((*radacina)->inf<info)
return extragere(&((*radacina)->r),info);
else{
if((*radacina)->l==NULL){
arbore aux=*radacina;
*radacina=(*radacina)->r;
free(aux);
}
else{
arbore p,p1;
for(p=(*radacina)->l;p->r;p1=p,p=p->r);
if(((*radacina)->l)->r==NULL){
(*radacina)->inf=p->inf;
(*radacina)->l=p->l;
free(p);
}
else{
(*radacina)->inf=p->inf;
arbore aux=p;
p1->r=p->l;
free(aux);
}
}
return 1;
}
}

void srd(arbore radacina)


{
if(radacina){
srd(radacina->l);
printf("%i ",radacina->inf);
srd(radacina->r);
}
}

void main()
{
clrscr();
int n,info;
arbore radacina=NULL;
printf("Numarul de noduri:");
scanf("%i",&n);
printf("Introduceti informatiile\n");
for(int i=0;i<n;i++){
scanf("%i",&info);
inserare(&radacina,info);
}
printf("Parcurgerea SRD a arborelui de sortare: \n");
srd(radacina);
printf("\nInformatia nodului de extras:");
scanf("%i",&info);
if(extragere(&radacina,info)){
printf("\nArborele rezultat in parcurgere SRD\n");
srd(radacina);
}
else printf("\nInformatia nu este in arbore");
getch();
}
54 Metode de programare

3.2.3. Arbori de structură

Expresiile aritmetice în care intervin numai operatori binari pot fi reprezentate prin
intermediul arborilor binari în care fiecare nod neterminal are doi fii.
Definiţia 3.2.5. Un arbore de structură are vîrfurile etichetate astfel:
- fiecare nod neterminal este etichetat cu un simbol corespunzător unuia dintre operatori;
- fiecare nod terminal este etichetat cu un operand.
Construcţia arborelui de structură corespunzător unei expresii aritmetice date se
realizează pe baza parantezării existente în expresie şi a priorităţilor convenţional asociate
operatorilor (ordinea operaţiilor) astfel încît rădăcina fiecărui subarbore este etichetată cu
operatorul care se execută ultimul în evaluarea subexpresiei corespunzătoare acelui subarbore.

Exemplu
3.2.6. Pentru expresia matematică (a+b)*(c-d)+e/g, arborele de structură corespunzător
este
+

* /

+ - e g

a b c d

Construcţia arborelui de structură pentru o expresie s este realizată în două etape:


1. ataşarea de priorităţi operatorilor şi operanzilor; priorităţile ataşate permit eliminarea
parantezelor fără ca semnificaţia expresiei să se modifice;
2. construcţia propriu-zisă.

Prima etapă este realizată astfel:


- prioritatea iniţială a operatorilor ‘+’,’-‘ este 1 (dacă expresia nu conţine paranteze
atunci în construcţie aceşti operatori vor fi primii luaţi în considerare în ordinea de la
dreapta la stînga);
- prioritatea iniţială a operatorilor ‘/’,’*‘ este 10 (dacă expresia nu conţine paranteze,
aceştia sînt consideraţi după operatorii de prioritate 1 în ordinea de la dreapta la
stînga);
- prioritatea fiecărui operator este incrementată cu valoarea 10 pentru fiecare pereche de
paranteze în interiorul cărora se află;
- prioritatea ataşată fiecărui operand este MAXINT.
După stabilirea sistemului de priorităţi sînt eliminate parantezele din expresie, ordinea de
efectuare a operaţiilor în cadrul expresiei fiind indicată de vectorul de priorităţi ataşat.
Construcţia arborelui de structură pe baza expresiei s din care au fost eliminate
parantezele şi a vectorului de priorităţi, poate fi realizează recursiv în modul următor (la
momentul iniţial expresia curentă este expresia dată):
- pentru expresia curentă se determină operatorul/operandul de prioritate minimă care se
ataşează ca etichetă a rădăcinii r a subarborelui de structură corespunzător ei; fie i
poziţia acestuia în cadrul expresiei;

54
Algoritmi și tehnici de programare 55

- dacă expresia are un singur simbol, atunci r->fius=r->fiud=NULL;


- altfel, se consideră subexpresiile s1 şi s2, constînd din simbolurile de pe poziţiile 0 pînă
la i-1 şi respectiv i+1 pînă la lungimea şirului s.; arborii de structură corespunzători
subexpresiilor s1 şi s2 se ataşează ca subarbore stîng, respectiv subarbore drept vîrfului
r.

Exemplu 3.2.7. Etapele calculului sistemului de priorităţi şi al arborelui de structură


pentru expresia de la exemplul 3.2.6. pot fi descrise astfel,

Dim vectorul prioritate


1 (MAXINT)
2 (MAXINT,11)
3 (MAXINT,11,MAXINT)
3 (MAXINT,11,MAXINT)
4 (MAXINT,11,MAXINT,10)
4 (MAXINT,11,MAXINT,10)
5 (MAXINT,11,MAXINT,10,MAXINT)
6 (MAXINT,11,MAXINT,10,MAXINT,11)
7 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT)
7 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT)
8 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1)
9 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT)
10 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT,10)
11 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT,10,MAXINT)
După eliminarea parantezelor, expresia rezultată este s=a+b*c-d+e/g.

Arborele de structură este construit astfel:

+
* în construcţie

în construcţie în construcţie în construcţie în construcţie

+ +

în construcţie * în construcţie
*

+ în construcţie + în construcţie

în construcţie în construcţie a în construcţie


56 Metode de programare

+ +

* în construcţie * în construcţie

+ în construcţie + -

a b a b în construcţie în construcţie

+ +

* în construcţie * în construcţie

+ - + -

a b c în construcţie a b c d

* /

+ - în construcţie în construcţie

a b c d

* /

+ - e în construcţie

a b c d

56
Algoritmi și tehnici de programare 57

* /

+ - e g

a b c d

Observaţie Construcţia arborelui de structură poate fi realizată în ipoteza în care expresia


este corectă.

Definiţia 3.2.6. Se numeşte forma poloneză directă a unei expresii, expresia rezultată în
urma parcurgerii RSD a arborelui de structură. Se numeşte forma poloneză inversă a unei
expresii, expresia rezultată în urma parcurgerii SDR a arborelui de structură.
Exemplu
3.2.8. Pentru expresia considerată la exemplul 3.2.7., forma poloneză directă este +*+ab-
cd/eg. Forma poloneză inversă a expresiei date este ab+cd-*eg/+.
Observaţie Parcurgerea arborelui în inordine determină secvenţa de simboluri rezultată
prin eliminarea parantezelor din expresia dată. Restaurarea unei forme parantezate poate fi
realizată printr-o parcurgere SRD şi anume în modul următor. La momentul iniţial vîrful curent
este rădăcina arborelui de structură. Dacă vîrful curent v nu este vîrf terminal, atunci se generează
(s1) eticheta(v)(s2), unde eticheta(v) este operatorul etichetă a vîrfului, s1 este secvenţa rezultată
prin traversarea SRD a subarborelui stîng, s2 este secvenţa rezultată prin traversarea SRD a
subarborelui drept. Dacă v este vîrf terminal atunci este generată secvenţa eticheta(v).

Evaluarea expresiilor aritmetice pe baza arborilor de structură


Traversarea SRD a arborelui de structură ataşat unei expresii aritmetice permite evaluarea
expresiei pentru valorile curente corespunzătoare variabilelor. Evaluarea poate fi efectuată în mod
recursiv astfel. La momentul iniţial vîrful curent este rădăcina arborelui. Dacă v este vîrf curent
atunci noua informaţie asociată lui v este:
- val(eticheta(v)), dacă v este vîrf terminal;
- val(s1)eticheta(v)val(s2), dacă v este neterminal, unde val(s1), val(s2) sînt valorile
rezultate prin evaluările subarborilor stîng şi respectiv drept ai lui v, val(eticheta(v))
este valoarea curentă a variabilei, dacă eticheta lui v este variabilă, respectiv valoarea
constantei, dacă eticheta lui v este o constantă.

Exemplu 3.2.9. Prin aplicarea metodei de evaluare descrise pentru a=3, b=2, c=5, d=2,
e=6 şi g=2, obţinem:
58 Metode de programare

18

15 3

5 3 6 2

3 2 5 2

Construcţia arborelui de structură asociat unei expresii şi evaluarea expresiei pentru


valori date ale operanzilor pot fi implementate prin intermediul următoarei surse C.

#include<stdio.h>
#include<conio.h>
#include<alloc.h>
#include<values.h>
#include<string.h>
#include<math.h>

typedef struct nod{


char inf;
float v;
struct nod *l,*r;
} arb, *arbore;

void prioritati(char *s, int *prioritate)


{
int i,j,dim;
//stabilirea prioritatilor
for(i=j=dim=0;i<strlen(s);i++)
switch(s[i]){
case ')':j-=10;break;
case '(':j+=10;break;
case '+':{prioritate[dim]=j+1;dim++;break;}
case '-':{prioritate[dim]=j+1;dim++;break;}
case '*':{prioritate[dim]=j+10;dim++;break;}
case '/':{prioritate[dim]=j+10;dim++;break;}
default:{prioritate[dim]=MAXINT;dim++;break;}
}
//eliminarea parantezelor
for(i=0;i<strlen(s);)
if((s[i]==')')||(s[i]=='(')){
for(j=i+1;j<strlen(s);j++)s[j-1]=s[j];
s[strlen(s)-1]='\0';}
else i++;
}
void cr_arb_str(arbore *rad, unsigned p, unsigned u, char *s,int *pri)
{
int min=pri[p];
int poz=p;
for(int i=p+1;i<=u;i++)
if(min>pri[i]){min=pri[i];poz=i;}
(*rad)=(arbore)malloc(sizeof(arb));
(*rad)->inf=s[poz];
if(p==u)
(*rad)->l=(*rad)->r=NULL;
else{

58
Algoritmi și tehnici de programare 59

cr_arb_str(&((*rad)->l),p,poz-1,s,pri);
cr_arb_str(&((*rad)->r),poz+1,u,s,pri);
}
}
void forma_poloneza(arbore rad)
{
if(rad){
printf("%c",rad->inf);
forma_poloneza(rad->l);
forma_poloneza(rad->r);
}
}

float eval(arbore rad)


{
char s[1];
if(rad){
if((rad->r==rad->l)&&(rad->l==NULL))return rad->v;
else{
switch (rad->inf){
case '+':rad->v=eval(rad->l)+eval(rad->r);break;
case '-':rad->v=eval(rad->l)-eval(rad->r);break;
case '*':rad->v=eval(rad->l)*eval(rad->r);break;
case '/':rad->v=eval(rad->l)/eval(rad->r);break;
}
return rad->v;
}
}
}
void atribuie_arbore(arbore rad)
{
if(rad){
if((rad->r==rad->l)&&(rad->l==NULL)){
printf("%c =",rad->inf);
float t;
scanf("%f",&t);
rad->v=t;
}
else
{atribuie_arbore(rad->l);
atribuie_arbore(rad->r);
}
}
}

void main()
{
clrscr();
char s[100];
int p[100];
arbore radacina=NULL;
printf("Expresia:");
scanf("%s",&s);
prioritati(s,p);
int n=strlen(s);
cr_arb_str(&radacina,0,n-1,s,p);
printf("\nForma poloneza inversa ");
forma_poloneza(radacina);
printf("\n Valori pentru varabile\n");
atribuie_arbore(radacina);
printf("\nEvaluarea: %7.3f",eval(radacina));
getch();
}
60 Metode de programare

Bibliografie

1. [Aho, Hop şa] Aho A., Hopcroft J., Ullman J., Data Structures and Algorithms,
Addison-Wesley, 1983
2. [Bras, Brat] Brassard G., Bratley P., Algoritmics: Theory and Practice, Prentice-Hall,
1988
3. [Cor, Lei şa] Cormen T., Leiserson C., Rivest R., Introduction to Algorithms, MIT
Press, sixteenth printing, 1996
4. [Gon] Gonnet G.H., Handbook of Algorithms and Date Structures, Addison-Wesley,
1984
5. [Hor] Horowitz E., Sahni S., Fundamentals of Computer Algorithms, Computer
Science Press, 1978
6. [Knu] Knuth D., Fundamental Algorithms, vol 1 of The Art of Computer
Programming, Addison-Wesley, 1973
7. [Knu] Knuth D., Sorting and Searching, vol 3 of The Art of Computer Programming,
Addison-Wesley, 1973
8. [Man] Manmber U., Introduction to Algorithms: A Creative Approach, Addison-
Wesley, 1989
9. [Pop, Geo şa] Popovici Ct., Georgescu H., State L., Bazele informaticii, vol 1, Tip.
Universităţii din Bucureşti, 1990
10. [Tom] Tomescu I.., Probleme de combinatorică şi teoria grafurilor, Editura
Didactică şi Pedagogică, Bucureşti, 1981
11. [Tud] Tudor S., Tehnici de programare, Ed. Teora, 1994
12. [Wil] Wilf H., Algorithms and Complexity, Prentice-Hall, 1986
13. [Negrescu, 1994] Liviu Negrescu, Limbajele C şi C++ pentru începători, Editura
Microinfomatica, Cluj-Napoca, 1994
14. [Smeureanu, 1995] Ion Smeureanu, Ion Ivan, Marian Dârdală, Limbajul C/C++ prin
exemple, Editura Cison, Bucureşti 1995
15. [Ghilic, 2003] Bogdan Ghilic-Micu, Ion Gh. Roşca, Constantin Apostol, Marian
Stoica, Cătălina Lucia Cocianu, Algoritmi în programare, Editura ASE,
Bucureşti 2003

60
lOMoARcPSD|5932583

Rezolvare 5 bilete examen ATP

Limbaje de programare Programming languages (Academia de Studii Economice din


București)

StuDocu nu este sponsorizat sau avizat de nicio universitate


Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)
lOMoARcPSD|5932583

1. Divide et impera metoda bisectiei


#include<stdio.h>
#include<conio.h>
#include<math.h>

float fct(float x) { return x*x*x-3*x+14;}

int bisectie(float x0,float x1,unsigned n, float eps,float (*f)(float), float *sol)


{ int cod;
if((*f)(x0)* (*f)(x1)>0) cod=3;
else if(n==0) cod=0;
else
{
*sol=(x0+x1)/2;
if((*f)(*sol)==0) cod=1;
else if (fabs (x0-x1)<=eps) cod=2;
else if((*f)(*sol)*(*f)(x0)<0)
cod=bisectie(x0,*sol,n-1,eps,f,sol);
else cod=(sol,x1,n-1,eps,f,*sol);}
return cod;
}

int main()
{ float x0,x1,eps,sol;
int cod;
unsigned n;
float (*functie)(float);

printf("Introduceti capetele intervalului:");


scanf("%f%f",&x0,&x1); printf("\nEroarea admisa:");
scanf("%f",&eps);
printf("\nNumarul maxim de termeni construiti:"); scanf("%d",&n);
functie=fct;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

cod=bisectie(x0,x1,n,eps,functie,&sol);
if(cod==0) printf("nu s-a gasit sol");
else if(cod==1) printf("s-a obtinut solutia exacta:%5.2f", sol);
else if(cod==2) printf("s-a obtinut o sol aproximativa:%5.2f", sol);
else if(cod==3) printf("intervalul dat nu are nici o sol");

return 0;}
2. Divide et imprea maxim
#include<stdio.h>

float max(float *a, int s, int d)


{ float x1,x2;
if(s==d) return a[s];
else { x1=max(a,s,(s+d)/2);
x2= max(a,(s+d)/2+1,d);
if(x1>x2) return x1;
else return x2;}
}

int main()
{ int i,n;
float v[100],m;
printf("nr elemente\n");
scanf("%d", &n);
printf("elemente: \n");
for(i=0;i<n;i++)
scanf("%f ", &v[i]);
m= max(v,0,n-1);
printf("nr maxim este=%5.2f", m);
}

3. Graf Dijkstra
#include <stdio.h>

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

#pragma warning (disable:4996)

void preluare(int *n, int c[30][30])


{
FILE *f;
int m, i, j, x, y, cost;
f = fopen("lista.txt", "r");
if (f)
{

fscanf(f, "%d", &*n);


fscanf(f, "%d", &m);
for (int i = 0; i < *n; i++)
for (int j = 0; j < *n; j++)
if (i == j)c[i][i] = 0;
else c[i][j] = 1000;
for (i = 0; i < m; i++)
{
fscanf(f, "%d", &x);
fscanf(f, "%d", &y);
fscanf(f, "%d", &cost);
c[x - 1][y - 1] = c[y - 1][x - 1] = cost;
}
fclose(f);
}
else printf("Fisierul nu exista");
}
void dijkstra(int a[30][30], int n, int l[30], int pred[30], int x)
{
int s[30], k, c, r, i, m, g, x0;
x0 = x - 1;
for (i = 0; i<n; i++)
{

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

l[i] = a[x0][i];
s[i] = 0;
if (a[x0][i] != 1000)
pred[i] = x0;
else pred[i] = -1;
}
g = 0;
s[x0] = 1;
pred[x0] = 0;
r = 1;
do
{
m = 1000;
for (i = 0; i<n; i++)
if ((s[i] == 0) && (l[i]<m))
{
m = l[i]; k = i;
}
r = r + 1;
if ((l[k] == 1000) || (r>n - 1))g = 1;
else
{
s[k] = 1;
for (i = 0; i<n; i++)
if ((s[i] == 0) && (l[i]>l[k] + a[i][k]))
{
l[i] = l[k] + a[i][k]; pred[i] = k;
}
}
} while (g != 1);
}
void afisare(int a[30][30], int n)
{

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%5d ", a[i][j]);
printf("\n");
}
}

void main()
{
int c[30][30], n, l[30], pred[30], x,i ;
preluare(&n, c);
afisare(c, n);
printf("\nVarful selectat este:");
scanf("%d", &x);
dijkstra(c, n, l, pred, x);
for (i = 0; i < n; i++)
if((i+1)!=x) printf("\nCosturile minime de la %d la %d este: %d",
x,i+1,l[i]);
for (i = 0; i < n; i++) if ((i + 1) != x) printf("\nParintele nodului
%d este: %d", i + 1, pred[i] + 1);

}
4. Graf parcurgere BF
#include<stdio.h>

void citire(int *nv, int *nm, int a[20][20])


{

FILE *f;
int i, j;
int x, y;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

f = fopen("lista.txt", "r");
fscanf(f, "%d", nv);
fscanf(f, "%d", nm);
for (i = 0; i<*nv; i++)
for (j = 0; j<*nv; j++)
a[i][j] = a[j][i] = 0;
for (i = 0; i<*nm; i++) {
fscanf(f, "%d", &x);
fscanf(f, "%d", &y);
a[x - 1][y - 1] = a[y - 1][x - 1] = 1;
}
fclose(f);

}
void BF(int a[20][20], int n, int c[20], int vo, int *u, int parinte[20], int
m[20])
{
int i, k, p;

c[0] = vo - 1; // c este multimea vf vizitate


parinte[vo - 1] = -1;
p = 0;// pentru a retine ultimul varf adaugat
*u = 0; // u nr de noduri vizitate
m[vo - 1] = 1; // m arata daca un vf a fost viztat sau nu
while (p <= (*u))
{
i = c[p];
for (k = 0; k<n; k++)
if ((a[i][k] == 1) && (m[k] == 0))
{
(*u) = (*u) + 1;
c[(*u)] = k;
parinte[k] = i;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

m[k] = 1;
}
p = p + 1;
}
}

int main() {
int a[20][20], c[20], u, n, nm, vo, i, j, parinte[20], m[20];
citire(&n, &nm, a);
for (i = 0; i<n; i++)
{
for (j = 0; j<n; j++)
printf("%d ", a[i][j]);
printf("\n");
}
printf("vo= ");
scanf("%d", &vo);
for (i = 0; i<n; i++)
m[i] = 0;
for (i = 0; i<n; i++)
c[i] = -1;
BF(a, n, c, vo, &u, parinte, m);
printf("\n nr. varfurilor vizitate=%d", u);
printf("\n varfurile vizitate: ");
for (i = 0; i <= u; i++)
printf("%d ", c[i] + 1);
printf("\nparintii: ");
for (i = 0; i <= u; i++)
if (i != vo - 1) printf("\nparintele lui %d este %d ", i + 1,
parinte[i] + 1);
//for (i = 0; i <= u; i++)
// printf("\nm[%d]=%d", i + 1, m[i]);
int nr = 1;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

for (j = 0; j<n; j++)


if (m[j] == 0)
{
nr++;
BF(a, n, c, j + 1, &u, parinte, m);
}
if (nr == 1)
printf("\ngraf conex");
else printf("\ngraful nu este conex.El are %d componente conexe",
nr);

5. Graf Roy Floyd


#include <stdio.h>

void afisare(int a[100][100], int n)


{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%5d ", a[i][j]);
printf("\n");
}
}

void preluare2(int *n, int c[100][100])


{
FILE *f;
int m, i, j, x, y,cost;
f = fopen("lista.txt", "r");

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

if (f)
{

fscanf(f, "%d", &*n);


fscanf(f, "%d", &m);
for (int i = 0; i < *n; i++)
for (int j = 0; j < *n; j++)
if (i == j)c[i][i] = 0;
else c[i][j] = 1000;
for (i = 0; i < m; i++)
{
fscanf(f, "%d", &x);
fscanf(f, "%d", &y);
fscanf(f, "%d", &cost);
c[x - 1][y - 1] = c[y - 1][x - 1] = cost;
}
fclose(f);
}
else printf("Fisierul nu exista");
}
void RF(int n, int C[100][100])
{
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (C[i][j]>C[i][k]+C[k][j])
C[i][j]=C[i][k] + C[k][j];
}
int main()
{
int c[100][100], n;
preluare2(&n,c);
afisare(c, n);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("\n");
RF(n, c);
afisare(c, n);
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

Rezolvare bilete examen ATP

Limbaje de programare Programming languages (Academia de Studii Economice din


București)

StuDocu nu este sponsorizat sau avizat de nicio universitate


Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)
lOMoARcPSD|5932583

1. Recursivitate Ackermann
#include<stdio.h>
#include<conio.h>
#include<math.h>

int ack(int m, int n)


{
int ac;
if(m==0) ac=n+1;
else if ((m>0)&&(n==0)) ac=ack(m-1,1);
else if ((m>0) && (n>0)) ac=ack(m-1,ack(m,n-1));
return ac;
}
int main()
{
int n,a,m;

printf("Cititi m si n: ");
scanf("%d ", &m);
scanf("%d", &n);
a=ack(m,n);
printf("Ackerman pentru m=%d si n=%d este =%d", m,n,a);
return 0;
}
2. Recursivitate combinari
#include<stdio.h>
#include<stdio.h>
#include<conio.h>
#include<math.h>

comb(int n, int k)
{ int c;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

if(n<k) c=0;
else if(k==0||k==n) c=1;
else c=comb(n-1,k-1) + comb(n-1,k);
return c;}
int main()
{
int n,c,k;

printf("Combinari de n luate cate k \n Cititi n-ul: \n");


scanf("%d", &n);
printf("Cititi k-ul: \n");
scanf("%d", &k);
c=comb(n,k);
printf("Combinari de %d luate cate %d = %d",n,k,c);
}

3. Recursivitate Fibonacci
#include<stdio.h>
#include<conio.h>
#include<math.h>

int fib(int n)
{
int f;
if(n==0||n==1) return 1;
else if(n>1) return fib(n-1)+fib(n-2);

}
int main()
{
int n,f;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("Cititi n: ");

scanf("%d", &n);
f=fib(n-1);
printf("Termenul %d din sirul lui Fibonaci este %d",n,f);
return 0;
}
4. Recursivitate Hermite

#include "stdio.h"
using namespace std;
//Subprogram pentru calcularea functiei HEMITE
long Hemite(int n,int x)
{
if(n==0) return 1;
else if(n==1) return 2*x;
else if(n>1) return 2*n* Hemite(n-1,x) - 2*(n-1)* Hemite(n-2,x);
}

int main()
{
int n,x;
long h;
printf("x="); scanf("%d", &x);
printf("n="); scanf("%d", &n);
h=Hemite(n,x);
printf("%ld",h);
return 0;
}

5. Recursivitate maxim vectori

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

#include<stdio.h>
#include<conio.h>
#include<math.h>

int a[100],n,i;
int max(int a,int b)
{
if(a>b) return a;
else return b;
}
int maxim(int a[],int n)
{
if(n==1) return a[0];
else return max(a[n],maxim(a,n-1));
}

int main()
{
int m;
printf("cititi dimensiunea vectorului: ");
scanf("%d",&n);
printf("Elementele vectorului sunt:");
for(i=0;i<n;i++)
{ printf("a[%d]= ", i);
scanf("%d", &a[i]);
}
m=maxim(a,n);
printf("%d", m);
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

Rezolvare subiecte ATP

Limbaje de programare Programming languages (Academia de Studii Economice din


București)

StuDocu nu este sponsorizat sau avizat de nicio universitate


Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)
lOMoARcPSD|5932583

1. Graf Roy Warshall


#include <stdio.h>

void preluare(int a[100][100], int *n)


{
FILE *f;
int m, i, j, x, y;
f = fopen("lista.txt", "r");
if (f)
{

fscanf(f, "%d", &*n);


fscanf(f, "%d", &m);
for (i = 0; i < *n; i++)
for (j = 0; j < *n; j++)
a[i][j] = 0;
for (i = 0; i < m; i++)
{
fscanf(f, "%d", &x);
fscanf(f, "%d", &y);
a[x - 1][y - 1] = a[y - 1][x - 1] = 1;
}
fclose(f);
}
else printf("Fisierul nu exista");
}

void afisare(int a[100][100], int n)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%5d ", a[i][j]);
printf("\n");
}
}
void RW(int n,int m[100][100])
{
for (int k = 0; k < n;k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (m[i][j] == 0)
m[i][j] = m[i][k] * m[k][j];
}

int main()
{
int a[100][100], n, m[100][100];
preluare(a,&n);
afisare(a, n);
printf("\n");
RW(n, a);
afisare(a, n);
}
2. Interclasare vectori

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

#include<conio.h>
#include<stdio.h>
#include<malloc.h>

//I - vectorul cu valori intregi care se sorteaza, lungimea vectorului


sortat
//E - nu se construieste o structura separata, vectorul de intrare
avand elementele sortate

void sortare(int* pv, int m)


{ for(int i=0;i<m-1;i++)
for(int j=i+1; j<m; j++)
if(*(pv+i)>*(pv+j))
{ int aux=*(pv+i);
*(pv+i)=*(pv+j);
*(pv+j)=aux;
}
}

int main()
{ int m,n,*px,*py,i,j,k;

printf("Introduceti dimensiunea vectorului 1:");


scanf("%d",&m);
px=(int*)malloc(m*sizeof(int));
for(i=0;i<m;i++)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

{ printf("v1[%d]=", i+1);
scanf("%d",px+i);
}

printf("Introduceti dimensiunea vectorului 2:");


scanf("%d",&n);
py=(int*)malloc(n*sizeof(int));
for(i=0;i<n;i++)
{ printf("v2[%d]=", i+1);
scanf("%d",py+i);
}

sortare(px,m);
sortare(py,n);

int *pi=(int*)malloc((m+n)*sizeof(int));
k=0;
i=0;
j=0;

while(i<m && j<n)


{ if(*(px+i) < *(py+j))
{ *(pi+k)=*(px+i);
i=i+1;
}
else
{*(pi+k)=*(py+j);
j=j+1;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

}
k=k+1;
}

if(i<m)
for(int ii=i;ii<m;ii++)
{ *(pi+k)=*(px+ii);
k=k+1;
}
else
for(int jj=j;jj<n;jj++)
{ *(pi+k)=*(py+jj);
k=k+1;
}

printf("\n");
for(i=0;i<k;i++)
printf(" %d ", *(pi+i));

free(px);
free(py);
free(pi);

getch();
return 0;
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

3. Recursivitate: Permutari
#include<stdio.h>
#include<stdio.h>
#include<conio.h>
#include<math.h>

permutari(int n)
{ int p;
if(n==0||n==1) p=1;
else p= n* permutari(n-1);
return p;}
int main()
{
int n,p;

printf("Permutari de n\n Cititi n-ul: ");


scanf("%d", &n);
p=permutari(n);
printf("Permutari de %d = %d",n,p);
}
4. Recursivitate: Aranjamente

#include<stdio.h>
#include<stdio.h>
#include<conio.h>
#include<math.h>

aranj(int n, int k)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

{ int a;
if(n<k) a=0;
else if(k==0) a=1;
else a= n* aranj(n-1,k-1);
return a;}
int main()
{
int n,a,k;

printf("Aranjamente de n luate cate k \n Cititi n-ul: \n");


scanf("%d", &n);
printf("Cititi k-ul: \n");
scanf("%d", &k);
a=aranj(n,k);
printf("Aranjamente de %d luate cate %d = %d",n,k,a);
}
5. Recursivitate: CMMDC
#include <stdio.h>

unsigned cmmdc ( unsigned a, unsigned b)


{ unsigned r, rez;
r=a%b;
if (r==0) rez=b;
else
rez=cmmdc(b,r);
return rez;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

int main ()
{
unsigned n1,n2,div;
printf("dati cele doua numere\n");
scanf("%d %d", &n1, &n2);
div=cmmdc(n1,n2);
printf("cel mai mare divizor comun este: %d", div);
return 0;
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

Rezolvare subiecte backtracking examen ATP

Limbaje de programare Programming languages (Academia de Studii Economice din


București)

StuDocu nu este sponsorizat sau avizat de nicio universitate


Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)
lOMoARcPSD|5932583

BACKTRACKING
1. Scrieti un program care sa calculeze aranjamente de n folosind metoda backtracking

#include<stdio.h> // PERMUTĂRI
int maxim=20;
int n,v[20] ; //n-nr. de elemente, v[20]-vectorul în care construim soluţia
int max;
int valid(int k) //verificăm condiţiile de continuare
{int i;
for (i=1;i<=k-1;i++) //comparăm fiecare element din vectorul v cu ultimul element
selectat
if (v[i]==v[k])
return 0;
return 1;
}
int solutie(int k) //verificăm dacă am obţinut o soluţie
{if (k==max)
return 1;
return 0;
}
void afisare(int k) //afişează conţinutul vectorului v
{int i;
for (i=1;i<=k;i++)
printf("%d ",v[i]);
printf("\n");
}
void BK(int k)
{int i;
for (i=1;i<=n;i++)
{v[k]=i; //selectăm un element din mulţimea Sk
if (valid(k)) //verificăm dacă eelementul ales îndeplineşte condiiile de continuare
{if (solutie(k)) //verificăm dacă am obţinut o soluţie
afisare(k);
else
BK(k+1); //reapelmăm funcţia pentru poziţia k+1 din vectorul v
}
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

int main()
{ printf("aranjamente de n luate cate k \n");
printf("dati n-ul:"); scanf("%d" ,&n);
printf("\ndati k-ul:"); scanf("%d" ,&max);
BK(1);
return 0;
}
2. Scrieti un program care sa calculeze combinari de n luate cate k folosind metoda
backtracking
#include<stdio.h> // PERMUTĂRI
int maxim=20;
int n,v[20] ; //n-nr. de elemente, v[20]-vectorul în care construim soluţia
int max;
int valid(int k) //verificăm condiţiile de continuare
{int i;
for (i=1;i<=k-1;i++) //comparăm fiecare element din vectorul v cu ultimul element
selectat
if (v[i]==v[k]||v[i]>v[k])
return 0;
return 1;
}
int solutie(int k) //verificăm dacă am obţinut o soluţie
{if (k==max)
return 1;
return 0;
}
void afisare(int k) //afişează conţinutul vectorului v
{int i;
for (i=1;i<=k;i++)
printf("%d ",v[i]);
printf("\n");
}
void BK(int k)
{int i;
for (i=1;i<=n;i++)
{v[k]=i; //selectăm un element din mulţimea Sk

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

if (valid(k)) //verificăm dacă eelementul ales îndeplineşte condiiile de continuare


{if (solutie(k)) //verificăm dacă am obţinut o soluţie
afisare(k);
else
BK(k+1); //reapelmăm funcţia pentru poziţia k+1 din vectorul v
}
}
}

int main()
{ printf("combinari de n luate cate k \n");
printf("dati n-ul:"); scanf("%d" ,&n);
printf("\ndati k-ul:"); scanf("%d" ,&max);
BK(1);
return 0;
}
3. Scrieti un program care sa calculeze problema damelor folosind metoda backtracking
#include<stdio.h> // PERMUTÃRI
#include<math.h>
int maxim=20;
int n,sol,v[20] ; //n-nr. de elemente, v[20]-vectorul în care construim soluþia

int valid(int k) //verificãm condiþiile de continuare


{int i;
for (i=1;i<=k-1;i++)
if ((v[i]==v[k])||(fabs(v[k]-v[i])==(k-i)))
return 0;
return 1;}

int solutie(int k) //verificãm dacã am obþinut o soluþie


{if (k==n)
return 1;
return 0;
}
void afisare(int k) //afiºeazã conþinutul vectorului v

{int i,j,x;
sol++; printf("\n Solutia: \n", sol);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

for (i=1;i<=n;i++)
{for (j=1;j<=n;j++)
if (v[i]==j) printf("D ");
else printf("_ ");
printf("\n");
}
}

void BK(int k)
{int i;
for (i=1;i<=n;i++)
{v[k]=i; //selectãm un element din mulþimea Sk
if (valid(k)) //verificãm dacã eelementul ales îndeplineºte condiiile de continuare
{if (solutie(k)) //verificãm dacã am obþinut o solutie
afisare(k);
else
BK(k+1); //reapelmãm funcþia pentru poziþia k+1 din vectorul v
}
}
}

int main()
{ printf("n= ");scanf("%d",&n);
BK(1);
return 0;
}
4. Scrieti un program care sa calculeze permutari de k folosind metoda backtracking
#include<stdio.h> // PERMUTÃRI
int maxim=20;
int n,v[20] ; //n-nr. de elemente, v[20]-vectorul în care construim soluþia

int valid(int k) //verificãm condiþiile de continuare


{int i;
for (i=1;i<=k-1;i++) //comparãm fiecare element din vectorul v cu ultimul element
selectat
if (v[i]==v[k])
return 0;
return 1;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

}
int solutie(int k) //verificãm dacã am obþinut o soluþie
{if (k==n)
return 1;
return 0;
}
void afisare(int k) //afiºeazã conþinutul vectorului v
{int i;
for (i=1;i<=k;i++)
printf("%d ",v[i]);
printf("\n");
}
void BK(int k)
{int i;
for (i=1;i<=n;i++)
{v[k]=i; //selectãm un element din mulþimea Sk
if (valid(k)) //verificãm dacã eelementul ales îndeplineºte condiiile de continuare
{if (solutie(k)) //verificãm dacã am obþinut o soluþie
afisare(k);
else
BK(k+1); //reapelmãm funcþia pentru poziþia k+1 din vectorul v
}
}
}

int main()
{ printf("n= ");scanf("%d",&n);
BK(1);
return 0;
}
5. Scrieti un program care sa calculeze problema suma folosind metoda backtracking
#include <stdio.h> // PLATA SUMEI

#define MAX 20
int n=0,x,v[MAX],w[MAX],z[MAX],S,Suma,sol;
//v-vectorul soluþie,w-valoarea monedelor,z-nr.maxim de monede de un anumit tip
void citire();
int valid(int k);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

int solutie();
void afisare(int k);
void BK(int k);
int main()
{citire();
BK(1);
return 0;
}
void BK(int k)
{int i;
for (i=0;i<=z[k];i++)
{v[k]=i;
if (valid(k)==1)
{if (solutie()==1)
afisare(k);
else
BK(k+1);
}
}
}
void citire()
{int i;
printf("suma este: "); scanf("%d",&S);
printf("nr de monede este: "); scanf("%d",&n); //se citeºte suma S ºi numãrul de
monede
printf("valoarea monedelor este:\n");
for(i=0;i<n;i++)
{scanf("%d",&w[i]); //se citesc valorile monedelor
z[i]=S/w[i];} //z-memorezã numãrul maxim de monede de un anumit tip, penru a plati
suma S
}
int valid(int k)
{int i;
Suma=0;
for (i=0;i<k;i++)
Suma=Suma+v[i]*w[i];
if ((Suma<=S)&&(k<=n))
return 1;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

return 0;}
int solutie()
{if (Suma==S)
return 1;
return 0;}
void afisare(int k)
{int i;
sol++;printf("solutia: %d", sol);
for (i=0;i<k;i++)
if (v[i]) {printf(" \n%d monede de valoarea %d", v[i],w[i]); printf("\n");}
printf("\n");}

6. Scrieti un program pentru problema rucsacului prin backtracking


/*#include <stdio.h>
#include <conio.h>

void Rucsac_c(float q, int n, float* c, float* x)


{ float qr;
int i,j;
// qr - capacitatea ramasa disponibila
// q - initial capacitatea totala
qr=q;
for(i=0; i<n && qr>0; i++)
if (qr>=c[i])
{ x[i]=1;
qr-=c[i]; //qr-=c[i]*x[i]
}
else
{ x[i]=qr/c[i];
qr=0; //qr-=c[i]*x[i]
for(j=i+1;j<n;j++)
x[j]=0;
}
}
int main()
{
int n,i;
float q,c[100],v[100],x[100];

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("nr obiecte:"); scanf("%d", &n);


printf("capacitate rucsac: "); scanf("%f",q);
for (i=0;i<n;i++)
{printf("capacitate obiect %d", i+1);
scanf("%f", &c[i]);}
for (i=0;i<n;i++)
{printf("valoare obiect %d", i+1);
scanf("%f", &v[i]);
}
Rucsac_c(q, n, c, x);
for(i=0;i<n;i++)
printf("%f", x[i]);
}
*/

#include <stdio.h>

#define MAX 20
int n,v[MAX],sol_max[MAX],g[MAX],c[MAX],s,s_max,G,gr,nr_sol;
char nume[MAX][MAX];
void back(int k);
int valid(int k);
void optim();
void citire();
void afisare();
main()
{citire();
back(1);
afisare(); //afisam solutia optima
}
void back(int k)
{ int i;
for(i=0;i<=1;i++)
{v[k]=i; //0-obiectul k sete NEselectat, 1-obiectul k este selectat
if (valid(k))
if (k==n) optim(); //din multimea solutiilor vom retine doar solutia optima
else back(k+1); }
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

int valid(int k)
{ gr=0;
for(int j=0;j<k;j++)
gr=gr+v[j]*g[j]; //-insumam greutatile obiectelor selectate pana la pasul k
if(gr<=G) return 1; //verificam daca greutatea cumulata nu depaseste greutatea maxima
G
else return 0;
}
void optim()
{int s=0,j; nr_sol++;
for(int j=0;j<n;j++)
s=s+v[j]*c[j]; //s-calculam suma câºtigurilor obiectelor selectate
if((nr_sol==0)||(s>s_max)) //daca s>suma maxima, solutia este mai buna
{s_max=s; //retinem solutia in sol_max
for(j=0;j<n;j++)
sol_max[j]=v[j];
}
}
void citire()
{ int i;

printf("nr obiecte:"); scanf("%d", &n);


for(i=0;i<n;i++)
{printf("greutate obiect %d:",i+1);
scanf("%d",&g[i]);}
for (i=0;i<n;i++)
{printf("castig obiect %d:",i+1);
scanf("%d", &c[i]);
}
}
void afisare()
{ nr_sol++;
printf("Castigul maxim: %d\n",s_max);
for (int j=0;j<n;j++)
if(sol_max[j]) {printf("%s", nume[j]); printf("%d",g[j]); printf("%d\n", c[j]);}
}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

Rezolvare subiecte examen ATP: Sortari

Limbaje de programare Programming languages (Academia de Studii Economice din


București)

StuDocu nu este sponsorizat sau avizat de nicio universitate


Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)
lOMoARcPSD|5932583

1. Sortare numarare
#include <stdio.h>
#include <malloc.h>
void sort_numarare(int v[] ,int l)
{int i,j,*num;
int *temp;
temp=(int*)malloc(l*sizeof(int));
num=(int*)malloc(l*sizeof(int));
for(i=0;i<l;i++)
num[i]=0;
for(i=0;i<l-1;i++)
for(j=i+1;j<l;j++)
if(v[j]<v[i])
num[i]++;
else
num[j]++;
for(i=0;i<l;i++)
temp[num[i]]=v[i];
for(i=0;i<l;i++)
v[i]=temp[i];
free(temp);
free(num);

}
int main()
{
int v[20];
int n,i;
printf("nr elem vector: ");
scanf("%d", &n);
printf("elem vector: \n");
for (i=0;i<n;i++)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

scanf("%d", &v[i]);
sort_numarare(v,n);
printf("vector sortat\n");
for (i=0;i<n;i++)
printf("%d ", v[i]);
return 0;}

2. Sortare bule
#include <stdio.h>
void sort_bule(int v[],int l)
{int i,gata;
int a;
gata=0;
while(!gata)
{gata=1;
for(i=0;i<l-1;i++)
if(v[i]>v[i+1])
{gata=0;
a=v[i];
v[i]=v[i+1];
v[i+1]=a;
}}}
int main()
{
int v[20];
int n,i;
printf("nr elem vector: ");
scanf("%d", &n);
printf("elem vector: \n");
for (i=0;i<n;i++)
scanf("%d", &v[i]);
sort_bule(v,n);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("vector sortat\n");
for (i=0;i<n;i++)
printf("%d ", v[i]);
return 0;}
3. Sortare insertie
#include<stdio.h>

void sortare(int a[100], int n)


{
int aux,i,j;
for(i=0;i<n-1;i++)
{
aux=a[i];
j=i-1;
while(j>=0 && a[j]>aux)
{
a[j+1]=a[j];
j=j-1;
}
a[j+1]=aux;}
}
int main()
{
int a[100],n,i;
printf("nr el vector\n");
scanf("%d",&n);
printf(" el vector\n");
for(i=0;i<n;i++)
scanf("%d", &a[i]);
sortare(a,n);
printf("vector sortat \n");

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

for(i=0;i<n;i++)
printf("%d ", a[i]);
}
4. Sortare prin interclasare
#include<conio.h>
#include<stdio.h>
#include<malloc.h>

void interclasare(float v[], int ls, int m, int ld)


{
int i,j,k;
float v1[100];
for(i=ls,j=m+1,k=0; i<=m && j<=ld;k++)
v1[k]=(v[i]<v[j])?v[i++]:v[j++];
while(i<=m) v1[k++]=v[i++];
while(j<=ld) v1[k++]=v[j++];
for(i=0;i<k;i++) v[ls+i]=v1[i];
}

void sortare(float v[], int ls, int ld)


{
int m;
if(ld>ls) {m=(ld+ls)/2;
sortare(v,ls,m);
sortare(v,m+1,ld);
interclasare(v,ls,m,ld);}
}

int main()
{ int i,n;
float v[100];

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("nr elemente\n");
scanf("%d", &n);
printf("elemente: \n");
for(i=0;i<n;i++)
{

printf("v[%d]=", i+1);
scanf("%f", &v[i]);}
sortare(v,0,n-1);
for(i=0;i<n;i++)
printf("%5.2f", v[i]);
}
5. Sortare prin selectie
#include <stdio.h>

void sort_selectie(int v[],int l)


{int i,j;
int a;
for(i=0;i<l-1;i++)
{
for(j=i+1;j<l;j++)
if(v[j]<v[i])

a=v[i];
v[i]=v[j];
v[j]=a;}}
int main()
{
int v[20];
int n,i;
printf("nr elem vector: ");
scanf("%d", &n);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("elem vector: \n");


for (i=0;i<n;i++)
scanf("%d", &v[i]);
sort_selectie(v,n);
printf("vector sortat\n");
for (i=0;i<n;i++)
printf("%d ", v[i]);
return 0;}
6. Sortare quicksort
#include <stdio.h>
#include <conio.h>

int poz(float *x,int p,int u)


{ int i,j,l,di,dj;
float v;
//di, dj: pasii de incrementare pentru i si j; ei indica sensul parcurgerii
i=p;j=u;di=0;dj=-1;
while(i<j)
if (x[i]>x[j])
{ v=x[i];x[i]=x[j];x[j]=v;
l=di;di=-dj;dj=-l;
i+=di;j+=dj;
}
else
{ i+=di;j+=dj;
}
return i;
}

void quick(float *x,int p,int u)


{ int i;
if (p<u)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

{ i=poz(x,p,u);
quick(x,p,i-1);
quick(x,i+1,u);
}
}
int main()
{ int n,i;
float v[1000];
printf("Dimensiunea vectorului:");
scanf("%i",&n);
printf("Elementele vectorului\n");
for(unsigned i=0;i<n;i++)
scanf("%f",&v[i]);
quick(v,0,n-1);
printf("Vectorul sortat\n");
for(i=0;i<n;i++)
printf("%8.4f ",v[i]);
getch();
}
7. Sortare shell
#include <stdio.h>
void sort_shell(int v[], int l)
{int i,j,inc;
int a;
for(inc/2;inc>0;inc=inc/2)
for(i=inc;i<l;i++)
for(j=i-inc;(j>=0)&&(v[j]>v[j+inc]);j=j-inc)
{a=v[j];
v[j]=v[j+inc];
v[j+inc]=a;
}}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

int main()
{
int v[20];
int n,i;
printf("nr elem vector: ");
scanf("%d", &n);
printf("elem vector: \n");
for (i=0;i<n;i++)
scanf("%d", &v[i]);
sort_shell(v,n);
printf("vector sortat\n");
for (i=0;i<n;i++)
printf("%d ", v[i]);
return 0;}

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

Subiecte examen ATP rezolvate

Limbaje de programare Programming languages (Academia de Studii Economice din


București)

StuDocu nu este sponsorizat sau avizat de nicio universitate


Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)
lOMoARcPSD|5932583

1. Recursivitate termeni de rang n


#include<stdio.h>
#include<conio.h>
#include<math.h>
float alfa, beta, gama,delta, a0,b0;
float B(unsigned a);
float A(unsigned n)
{if (n==0)
return a0;
else return alfa*A(n-1)+beta*B(n-1);
}
float B(unsigned n)
{ if(n==0)
return b0;
else return gama*A(n-1)+delta*B(n-1);
}

int main()
{ float x,y;
unsigned n;
printf("Cititi n- rangul termenului: "); scanf("%d", &n);
printf("Cititi a0: "); scanf("%f", &a0);
printf("Cititi b0: "); scanf("%f", &b0);
printf("Cititi alfa: "); scanf("%f", &alfa);
printf("Cititi beta: ");scanf("%f", &beta);
printf("Cititi gama: ");scanf("%f", &gama);
printf("Cititi delta: ");scanf("%f", &delta);
x= A(n);
y=B(n);
printf("an=%5.2f, bn=%5.2f",x,y);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

2. Recursivitate turnurile din Hanoi


#include<stdio.h>

void hanoi(int n, int a, int b, int c)


{
if(n>0)
{
hanoi(n-1,a,c,b);
printf("muta un disc de pe %d pe %d\n", a,c);
hanoi(n-1,b,a,c);
}
}
int main()
{
int a,b,c,n;
a=1;b=2;c=3;
printf("nr discuri ");
scanf("%d",&n);
hanoi(n,a,b,c);
}

3.Recursivitatea valoarea unui polinom intr-un punct dat


#include "stdio.h"

using namespace std;

//Subprogram pentru a calcula x la puterea n

float putere( float x, int n)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

{ float p;

if(n==0) p=1;

else p= x*putere(x,n-1);

return p;

//Subprogram pentru a calcula valoare unui polinom intr-un punct dat(x)

//P(x)= a[n]*putere(x,n)+a[n-1]*putere(x,n-1)+...+a[o]

double polinom(float a[100], int n, float x)

if(n==0) return a[0];

else return a[n]*putere(x,n)+ polinom(a,n-1,x);

int main()

float x, a[100];

double p;

int n,i;

printf("x="); scanf("%f", &x);

printf("n="); scanf("%d", &n);

for(i=0;i<n+1;i++)

printf("a[%d]=",i);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

scanf("%f", &a[i]);

p=polinom(a,n,x);

printf ("\n");

printf("Valoarea polinomului in punctul %5.2f este %5.2lf",x, p);

return 0;

4.Bisectie
#include<stdio.h>

#include<conio.h>

#include<math.h>

/*prototipul functiei bisectie*/

void bisectie(float,float,float(*f)(float),float,long,int *,float *);

/*prototipul functiei pentru care se aplica metoda bisectiei*/

float fct(float);

/* functia principala*/

int main()

{ float a,b,eps,x;

int cod;

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

long n;

float (*functie)(float);

printf("Introduceti capetele intervalului:");

scanf("%f%f",&a,&b); printf("\nEroarea admisa:");

scanf("%f",&eps);

printf("\nNumarul maxim de termeni construiti:"); scanf("%li",&n);

functie=fct;

bisectie(a,b,functie,eps,n,&cod,&x);

if(!cod)printf("\nNu se poate calcula solutia aproximativa");

else printf("\n Solutia aproximativa este: %f",x);

return 0;}

/*descrierea functiei pentru care se aplica metoda bisectiei*/

float fct(float x) { return x*x*x-3*x+14;}

/*functia ce implementeaza metoda bisectiei*/

void bisectie(float a,float b,float (*f)(float),float eps,long n, int *cod,float *x)

{ int gata=0; long c;

for(c=0;(c<n)&&!gata;c++)

{*x=(a+b)/2; gata=fabs(*x-a)<eps; if ((*f)(*x)*(*f)(a)<0)b=*x; else a=*x;}

*cod=gata;}

5.Cautare binara

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

#include<stdio.h>

#include<conio.h>

#include<math.h>

int caut_bin(float *v, int ld, int ls, float k)

int mij;

if(ld>ls) return -1;

mij=(ld+ls)/2;

if(v[mij]==k) return mij;

else if(v[mij]>k) return caut_bin(v,ld,mij-1,k);

else return caut_bin(v,mij+1,ls,k);

int main()

{ int i,n,p;

float v[100],k;

printf("nr elemente\n");

scanf("%d", &n);

printf("elemente: \n");

for(i=0;i<n;i++)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

scanf("%f ", &v[i]);

scanf("%f", &k);

printf("val cautat este: %f\n",k);

p= caut_bin(v,0,n-1,k);

if(p==-1) printf("elementul cautat nu a fost gasit");

else

printf("pozitia pe care se afla elementul cautat este: %d ",p+1);

6.Problema paznic
#include<stdio.h>

#include<math.h>

int cmmdc ( int a, int b)

{ int r;

while (b!=0)

{r=a%b;

a=b;

b=r;

if (a==1)

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

return 1;

else

return 0;

int pomi( int m, int n, int x, int y)

int i,j,nr;

nr=0;

for(i=0;i<m;i++)

for(j=0;j<n;j++)

nr=nr+ (cmmdc((fabs(x-i)),(fabs(y-j)))==1);

int main()

int m,n,x,y,nr;

printf("dimensiuni livada: \n");

printf("m=");scanf("%d",&m);

printf("n=");scanf("%d",&n);

printf("coordonate paznic: \n");

printf("x=");scanf("%d",&x);

printf("y=");scanf("%d",&y);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

nr= pomi(m-1,n-1,x-1,y-1);

printf("paznicul vede %d pomi\n", nr);

7.Plata suma greedy


#include<stdio.h>

int sol[100];

void plata_unitate(int s, int *t, int n, int *sol)

int i, sr;

sr=s;

for(i=0;i<n;i++)

sol[i]=sr/t[i];

sr=sr%t[i];

int main()

int i, S,n, t[100];

printf("suma este: "); scanf("%d",&S);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5932583

printf("nr de monede este: "); scanf("%d",&n); //se citeºte suma S ºi numãrul de


monede

printf("valoarea monedelor este:\n");

for(i=0;i<n;i++)

{ printf("tipul %d:",i+1);

scanf("%d",&t[i]);} //se citesc valorile monedelor

plata_unitate(S, t, n, sol);

for(i=0;i<n;i++)

printf("nr tip %d: %d\n",i+1,sol[i]);

Desc?rcat de Andreea Pop (andreeapop2306@gmail.com)


lOMoARcPSD|5884781

Atp final toate gtile si probleme

Algoritmi si tehnici de programare (Academia de Studii Economice din București)

StuDocu is not sponsored or endorsed by any college or university


Downloaded by Samedin Aime (samedin.aime@gmail.com)
lOMoARcPSD|5884781

1. Care dintre urmatoarele afirmatii legate de metoda Backtracking sunt


adevarate:
R: - Este o metoda lenta
- Este o metoda costisitoare
- Este o metoda de complexitate mare
- Solutia se construieste element cu element
- Verificarea conditiei de continuare NU garanteaza obtinerea unei solutii
rezultat

2. Alg. Dijkstra :
R: calculeaza distantele si drumurile minime de la un nod al unui graf la toate
celelalte noduri din graf

3. Fie functia :
int calc (int n) calc(3)
{ rez=2*calc(2)+calc(1)=7
int rez ; =3 =1
if (n==0 || n==1)
rez=1; calc(2)
else rez=2*calc(1)+calc(0)=3
rez=2* calc(n-1)+calc(n-2); =1 =1
return rez;
}

Ce va returna apelul calc(3)?


R: 7

4. Care dintre urmatoarele afirmatii NU corespunde metodei Greedy :


F: problema se descompune in problem de complexitate mai mica sau problem
cu rezolvare imediata
A: problema poate fi imaginata ca o mult A cu n elemente
A: pot exista mai multe submult. diferite acceptabile (solutii posibile), dintre care
una este considerata solutie optima pe baza unui criteriu care trebuie maximizat
(minimizat)
A: o solutie posibila este o multime (B) care indeplineste o conditie data (B este
acceptabila)
A: se repeat selectarea unui elem. din mult. A de maxim n ori (nr. de elem.
corespunzator mult. A)

5. Un graf G este arbore daca G este:


R: aciclic si conex

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

6. Prin recursivitate indirecta (mutuala) se intelege:


R: un subprogram A apeleaza un alt subprogram B, iar subprog. B apeleaza
subprog. A

7. Fie graful G=(V,E) , cu V={1,2,3,…,9} , E={(1,2);(1,4);(2,7);(2,8);(3,6);(3,9);


(4,5);(4,7);(7,8)} si =4 .
Ordinea in care sunt vizitate varfurile corespunzatoare parcurgerii in adancime DF
este:

R: 4,1,2,7,8,5

8. Care dintre urmatoarele afirmatii legate de sortarea crescatoare prin


interclasarea unei secvente de nr. reale este adevarata :
R: utilizeaza metoda Divide et Impera

9. Determinarea arborelui partial de cost minim se poate face folosind:


R: - Alg. lui Prim
- Alg. lui Kruskal

10. Un algoritm de tip backtracking genereaza, in ordine, toate permutarile unei


multimi cu 4 elem. Primele 3 solutii generate sunt: 1234 , 1243 , 1324 . Care
este a 4-a solutie generata de acest algoritm ?

R: 1342

11. Care dintre afirmatiile urmatoare referitoate la Divide et Impera NU este


adevarata
F - descompune problema in problem de complexitate mai mica pt. care se
apeleaza alte metode de descompunere diferite de metoda de descompunere
initial
A - implementarea este realizata de obicei prin subprograme recursive
A – descompune problema in problem de complexitate mai mica de acelasi tip cu
problema initiala sau in probleme cu rezolvare imediata (primitive)
A – combina (asambleaza) solutiile problemelor obtinute in urma decompunerii
pt. a obtine Solutia problemei initiale

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

A – sunt definite subprograme primitive (conditii terminale) a caror solutie


este ,,cunoscuta” sau data.
12. Subprogramul :
int cauta (float v[] , int n , float val)
{
int rez;
if(n==0)
rez=-1;
else
if (v[n-1]==val)
rez=n-1;
else
rez=cauta(v, n-1, val);
return rez;
}

calculeaza:
R: ultima aparitie a unei valori date (val) intr-un vector

13. Care dintre urm. afirmatii referitoare la arborele orientat sunt adevarate?
R: -graful suport este conex
- graful suport este aciclic
- este graf asimetric
- este arbore directionat cu radacina

14. In configuratia urmatoare (specifica metodei backtracking):

Este prezentata operatia de:


R: incercare esuata

15. Reteaua strazilor auto din Bucuresti se reprezinta correct cu ajutorul


structurilor de date :
R: graf orientat

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

16. Care dintre urmatoarele afirmatii legate de subprogramele recursive sunt


adevarate:
A – pot fi bazate pe o metoda de tip reducere
A – pot fi bazate pe o metoda de tip descompunere
A - pot fi folosie in rezolvarea problemelor care utilizeaza metoda Divide et
Impera
A- pot fi folosite la implementarea algoritmilor de parcurgere a arborilor
F – nu pot fi scrise pt. implementarea algoritmilor iterative

17. Fie subprogramul :


void Test (int I, int n) *Test(2,5)+ i=1
{ **Test(3,5)++ i=2
printf(“*”); ***Test(4,5)+++ i=3
if(i<n) ****Test(5,5)++++ i=4
Test (i+1,n);
printf(“+”);
}
Ce va afisa apelul Test (1, 5) ?

R: * * * * * + + + + +

18. Fie graful G=(V,E) cu V={1,…,6} , E+{(1,2);(1,4);(2,4);(4,5);(5,6)} si =2 .


Ordinea in care sunt vizitate varfurile corespunzator parcurgerii in latime BF
este:
R: 2,1,4,5,6

19. Care dintre afirmatiile urmatoare NU este valabila pt. Alg. lui Kruskal :
F – dintre arcele disponibile se allege arcul cu ponderea cea mai mica si care
formeaza un ciclu prin adaugarea la arbore
A- determina arborele partial de cost min
A- dintre arcele disponibile care nu au fost analizate inca se allege arcul cu
ponderea cea mai mica si care nu formeaza un ciclu prin adaugarea la arbore
A – mult. muchiilor selectate E0 se initializeaza la inceput cu mult. vida

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

A – determinan n-1 muchii , unde n este nr de varfuri

20. Metoda Greedy este:


R: o metoda rapida, de complexitate redusa, pt obtinerea unei solutii
acceptabile, nu neaparat cea mai buna

21. Fie mult. de litere {a,b,c,d} . Se genereaza permutarile acestei mult. Precizati
care sunt solutiile anterioara si urmatoare solutiei cabd
R: bdca si cadb

22. Fie functia :


int s(int n) { s(0)
{ rez=0
int rez; {s(1)
int (n==0) rez=1+s(0)=1
rez=0; {s(2)
else rez=2+s(1)=3
rez=n+s(n-1); {s(3)
return rez; rez=3+s(2)=6
}

In cazul apelului s(3) , functia va returna:


R: 6

23. Care dintre urm. afirm. referitoare la metoda Divide et Impera sunt
adevarate?
A – este utilizata in rezolvarea unor problem complexe
A – implementarea este realizata de obicei prin subprogram recursive
A – se aplica pt problem care pot fi descompuse in problem cu complexitate
mai mica
A – rezolvarea problemelor rezultate in urma descompunerii este mai usoara
decat rezolvarea intregii problem initiale
F – pt fiecare dintre problemele rezultate in urma descompunerii se aplica un
procedeu diferit de descompunere

24. Fie graful G=(V,E) cu V={1,2,…7} , E={(1,4);(1,5);(2,4);(3,6);(4,7)} si v0=2 .


Ordinea in care sunt vizitate vf. corespunzatoare parcurgerii in latime BF
este:
R: 2,4,1,7,5

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

25. Care dintre urm. afirmatii NU este adevarata:


F- la recursivitatea directa, apelul recursiv se realizeaza prin intermediul mai
multor functii care se apeleaza circular

26. Algoritmul prezentat determina:


R: arbore partial de cost minim (alg. Prim)

E0 se initializeaza ca mult. vida


Alege un vf initial v0 apartine lui V , A={v0} , B=V-A
Repeta de n-1 ori
Alege muchia e=(u,v) cu cea mai mica pondere a.i. u apartine lui S si v lui B
E0 <- E0ᵘ{e}
A <- Aᵘ{v}
B <- B-{v}

27. Un arbore directionat este:


R: un graf orientat asimetric cu graful support corespunzator lui de tip arbore

28. In configuratia urmatoare (specifica metodei backtracking):

R: atribuie si avanseaza

29. Care dintre urm. afirmatii referitoare la implementarea recursive e adev.:


R: usurinta in proiectare / programare (lungime mica a codului sursa)

30. Un graf reprezentat prin matrice de adiacenta poate fi verificat daca este
conex prin urm. metode:
R: - folosind parcurgerea in adancime
-folosind parcurgerea in latime

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

-folosind matricea existentei drumurilor

31. Secventa:
for (inc=n/2;inc>0;inc=inc/2)
for(i=inc;i<n;i++)
for(j=i-inc;;(j>=0)&&(v[j]>v[j+inc]);j=j-inc)
{
a=v[j]
v[j]=v[j+inc];
v[j+inc]=a;
}

Realizeaza: sortarea elem. unui vector prin metoda Shell

32. Daca G este un graf neorientat, conex si aciclic, atunci graful:


R: este arbore

33. Fie arborele din figura. Parcurgerea prin metoda A-postordine


(reprezentarea Fiu-Frate) determina urm. evolutie:
R: 5,9,10,11,12,13,6,7,2,3,8,4,1

34. Care dintre urm. afirm. legate de subprograme recursive NU este adevarata:
R: pot fi folosite numai pt. implementarea unui alg. recursive

35. Un alg. de tip backtracking genereaza, in ordine, toate permutarile unei


mult. cu 5 elem. Primele 4 solutii generate sunt: 12345, 12354, 12435,
12453. Care este a 5-a solutie generate?
R: 12534

36. Intr-un graf neorientat G notam cu n nr. de vf. si cu m nr. de muchii. Daca
graful este arbore, atunci intre n si m exista urm. relatie matematica:
R: n=m+1

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

37. Care dintre urm. operatii NU fac parte din operatiile specific metodei
optimului local:
R: -construirea unui element candidat x
- eliminarea elem. x selectat din Solutia problemei

38. In configuratia urm. (specifica met. backtracking):

R: revenire dupa construirea unei solutii

39. Fie multimea de litere {a, b, c, d}. Se genereaza permutarile acestei multimi,
Precizati care sunt solutiile anterioara si urmatoare solutiei cabd.
R:bdca sic dab
40. Fie graful G=(V,E) cu V={1,…,7} , E+{(1,4);(1,5);(2,4);(3,6);(4,7)} si v_0=2 .
Ordinea in care sunt vizitate varfurile corespunzator parcurgerii in latime BF
este:
R: 2, 4, 1, 7, 5

41. Care din urmatoarele


afirmatii NU este adevarata:
a) Un algoritm iterativ sau recursiv poate fi implementat printr-un subprogram
iterativ sau recursive
b) Un subprogram recursive genereaza (cel putin) un apel catre el insusi.
c) La recursivitatea directa, apelul recursiv se realizeaza prin intermediul mai
multor functii care apeleaza circular.
d) Recursivitatea directa poate fi simpla sau multipla.
e) Fiecare apel recursive trebuie aplicat unei probleme mai simple decat in pasul
anterior.
42. Algoritmul prezentat:

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

 E_0 se initializeaza ca multime vida


 Alege un varf initial V_0 ∈V, A={V_0}, B=V-A
 Repeta de n-1 ori
 Alege muchia e=(u,v) cu cea mai mica pondere astfel incat u ∈A si v ∈B
 E0E0Ʋ{e}
 AAƲ{v}
 BBƲ{v}
Determina:
R:arborele partial de cost minim (algoritmul lui Prim)
43. Un graf neorientat G contine un arbore partial daca si numai
daca G este:
R: conex

44. Un arbore directionat este:


R: un graf orientat asimetric cu graful support corespunzator lui de tip arbore
45. Fie graful G=(V,E) , cu V={1,2,3,…,8} , E={(1,2);(1,4);(1,8);(2,3);(2,6);(2,7);
(3,5); (4,5); (6.7); (7,8)} si v_0=3 .
Ordinea in care sunt vizitate varfurile corespunzatoare parcurgerii in adancime DF
este:
R:3,3,1,4,5,8,7,6
46. Intr-un graf, prin circuit elementar se intelege:
R: un drum in care fiecare nod apare o singura data, cu exceptia celui final,care
coincide cu cel initial
47. Fie graful G=(V,E) , cu V={1,2,3,…,6} , E={(1,4);(1,6);(2,3);(2,4);(2,5);} Care
este nodul ce poate fi ales ca radacina a arborelui astfel incat fiecare nod
care nu este de tip frunza sa aiba un numar impar de descendenti directi
(fii)?
R:2
48. Fie functia: int f (int n) { int rez; if (n <= 0) return 0; else return 2*f(n-1)+n}
Ce va returna apelul f(5)?
R:57
49. Utilizandu-se metoda backtracking pentru a genera toate permutarile de 5
obiecte, dupa identificarea solutiei 2-1-5-4-3 care va fi urmatoarea solutie
identificata:
R:2-3-1-4-5
50. Fie graful G=(V,E), V={1,2..7}, E={(1,2,2), (1,7,4), (2,3,4), (2,5,5), (2,6,6),
(2,7,3), (3,4,3) (3,5,5),(4,5,..), (5,6,3), (6,7,5)}. Aplicand algoritmul lui Prim,
care va fi rezultatul obtinut plecand de la varful 2?
R: 20
51. Fie graful G=(V,E), graf cu V={1,2..8}, E={(1,2,), (1,,4), (1,8),(2,3,),(2,6,),
(2,7), (3,5),(4,5), (6,7) (7,8)} v_0=1. Ordinea in care sunt vizitate varfurile
corespunzator parcurgerii in adancime DF/ in latime BF este:
BF: 12443675
DF 12354678

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

52. In configuratia urmatoare (specifica metodei backtracking) este prezentata


operatia de:

R: atribuie si avanseaza
53. In configuratia urmatoare (specifica metodei backtracking) este prezentata
operatia de:

R: revenire dupa
construirea unei
solutii
54. Care din urmatoarele afirmatii referitoare la implementarea recursiva este
adevarata:
 consum mic de resurse de memorie
 timp de executie mic
 usurinta in proiectare/programare (lungimea mica a codului sursa)
 scaderea numarului de operatii
 se poate aplica numai pentru rezolvarea unor probleme complexe, care nu
pot fi rezolvate prin implementare iterativa
55. Fie functia:
int f (int n)
{ int rez;
if (n == 0) rez = 0;
else if (n % 2) rez = n + f (n – 1);
else rez =n / … 1);
return rez; }
Ce va returna apelul f(4)?
R:7
56. Fie functia:
int s(int n)
{ int rez;
if (n == 0) rez = 0;
else rez = n + s(n – 1); return rez; }.
In cazul apelului s(3), functia va returna valorea:
R:6
57. Care din urmatoarele afirmatii referitoare la metoda Divide et Impera sunt
adevarate?
 este utilizata in rezolvarea unor probleme complexe
 implementarea este realizata de obicei prin subprograme recursive

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

 se aplica pentru problemele care pot fi descompuse in probleme cu


complexitate mai mica
 rezolvarea problemelor rezultate in urma descompunerii este mai
usoara decat rezolvarea intregii probleme initiale
 pentru fiecare din problemele rezultate in urma descompunerii se
aplica un procedeu diferit de descompunere
58. Fie graful ponderat G=(V,E), graf cu B varfuri si urmatoarele muchii (varf,
varf, cost). E={(1,2,2}, (1,4,3), (1,5,4), (1,7,2), (1,8,3), (2,3,1), (2,5,3), (2,8,1),
(3,4,2), (4,5,1), (5,6,2,) (6,7,3), (7,8,4)}. In cee ace priveste costurile, care va
fi rezultatul aplicarii algoritmului Dijkstra plecand din varful 7?

R: [2,4,5,5,5,3,0,4]
59. Fie graful G=(V,E), V={1,2..7}, E={(1,2,3), (1,4,3), (1,7,4), (2,3,4), (2,5,5),
(2,6,6), (2,7,3) (3,4,4),(3,5,5), (4,5,7), (5,6,3), (6,7,5), }. Aplicand algoritmul
lui Prim, care va fi rezultatul obtinut plecand de la varful 2?
R: 22
60. Fie graful G=(V,E), V=(A,B,C,D,E,F), SI E={ (A,B), (B.E), (B,F), (C,E), (C,D)}. Care
este nodul ce poate fi ales ca radacina a arborelui astfel incat fiecare nod
care nu este de tip frunza sa aiba un numar impar de descendenti directi
(fii)?
R: B
61. Fie graful G=(V,E), V=(A,B,C,D,E,F), SI E={ (A,E), (B.D), (B,E), (C,D), (E,F)}. Care
este nodul ce poate fi ales ca radacina a arborelui astfel incat fiecare nod
care nu este de tip frunza sa aiba un numar impar de descendenti directi
(fii)?
R: E
62. Daca in cadrul unui arbore se elimina o muchie atunci se obtine:
R: un graf neconex
63. Ce reprezinta un graf partial al unui graf neorientat?
R: un graf din care se elimina anumite muchii
64. Fie urmatoarea functie:
Int f (int n)
{ if (n==0)
Return 0;
Else return f(n-1)+2*n;}
Care va fi rezultatul pt apelul functiei f(5) si f(4)
R: 30-f(5)
20-f(4)
65. Ce reprezinta un subgraf al unui graf orientat?
R: un graf din care se elimina noduri si muchii adiacente
66. Utilizandu-se metoda backtracking pentru a genera toate permutarile de 5
obiecte, dupa identificarea solutiei 2-4-5-3-1 care va fi urmatoarea solutie
identificata:
R: 2-5-1-3-4

Downloaded by Samedin Aime (samedin.aime@gmail.com)


lOMoARcPSD|5884781

67. Care din urmatoarele afirmatii legate de subprogramele recursive NU este


adevarata:
R: pot fi folosite numai pentru implementarea unor algoritmi recursivi
68. Fie graful G=(V,E) cu V={1,…,7} , E={(1,2);(1,4);(2,3);(2,6);(2,7), (3.5), (4,5)
(6.7)} si v_0=6 . Ordinea in care sunt vizitate varfurile corespunzator
parcurgerii in latime BF este:
R: 26,2,7,1,3,4,5
69. Se considera un graf neorientat cu 8 varfuri si 6 muchii care este alcatuit
din 2 componente conexe. Care este gradul maxim pe care il poate avea un
nod?
R: 5
70. Utilizandu-se metoda backtracking pentru a genera toate permutarile de 5
obiecte, dupa identificarea solutiei 2-3-5-4-1 care va fi urmatoarea solutie
identificata:
R: 2-4-1-3-5
71. Fie graful G=(V,E) cu V={1,2…,8} , E={(1,2);(1,4);(1,8);(2,3);(2,6), (2.7), (3.5,),
(4.5) (6.7), (7.8) } si v_0=1 . Ordinea in care sunt vizitate varfurile
corespunzator parcurgerii in latime BF este:
R: 2,1,3,6,7,4,8,5
72. Fie graful G=(V,E) cu V={1,2…,8} , E={(1,2);(1,4);(1,8);(2,3);(2,6), (3.5,), (4.5)
(5,6), (6.7), (7.8) } si v_0=5 . Ordinea in care sunt vizitate varfurile
corespunzator parcurgerii in adancime DF este:
R:5,3,2,1,4,8,7,6
73. Un graf G este arbore daca G este :
R: aciclic si conex

74. Parcurgerea generalizata a unui graf se face:


R: in adancime sau in latime

Downloaded by Samedin Aime (samedin.aime@gmail.com)

S-ar putea să vă placă și