Sunteți pe pagina 1din 112

CUPRINS

PREFA................................................................................. Pag. 2 LISTE SIMPLU NLNUITE............................................. Pag. 5 LISTE CIRCULARE SIMPLU NLNUITE.................... Pag. 15 LISTE DUBLU NLNUITE.............................................. Pag. 22 ARBORI................................................................................... Pag. 30 ARBORI BINARI DE CUTARE.......................................... Pag. 39 REPREZENTAREA I TRAVERSAREA GRAFURILOR........................................................................ Pag. 58 ALGORITMI PENTRU PRELUCRAREA GRAFURILOR........................................................................ Pag. 63 TABELE DE DISPERSIE...................................................... Pag. 69 METODE GENERALE DE ELABORARE A ALGORITMILOR (I)............................................................. Pag. 75 METODE GENERALE DE ELABORARE A ALGORITMILOR (II)............................................................ Pag. 87 METODE GENERALE DE ELABORARE A ALGORITMILOR (III).......................................................... Pag. 95 ALGORITMI FUNDAMENTALI DE SORTARE............... Pag. 104 BIBLIOGRAFIE...................................................................... Pag. 115

PREFA

Lucrarea de laborator nr. 1.

LISTE SIMPLU NLNUITE


1. Coninutul lucrrii n lucrare sunt prezentate operaiile importante asupra listelor simplu nlnuite i particularitile stivelor i cozilor. 2. Consideraii teoretice Lista este o mulime finit i ordonat de elemente de acelai tip. Elementele listei se numesc noduri. Listele pot fi organizate sub form static, de tablou, caz n care ordinea este implicit dat de tipul tablou unidimensional, sau cel mai des, sub form de liste dinamice, n care ordinea nodurilor este stabilit prin pointeri. Nodurile listelor dinamice sunt alocate n memoria heap. Listele dinamice se numesc liste nlnuite, putnd fi simplu sau dublu nlnuite. n continuare se vor prezenta principalele operaii asupra listelor simplu nlnuite. Structura unui nod este urmtoarea: typedef struct tip_nod { int cheie; /* cmp neobligatoriu */ alte cmpuri de date utile; struct tip_nod urm; /* legtura spre urmtorul nod */ } TIP_NOD; Modelul listei simplu nlnuite este prezentat n fig. 2.1.

Fig. 2.1. Model de list simplu nlnuit Pointerii prim i ultim vor fi declarai astfel: TIP_NOD *prim, *ultim; 2.1 Crearea unei liste simplu nlnuite Crearea unei liste simplu nlnuite se va face astfel: a) Iniial lista este vid: prim = 0; ultim = 0; b) Se genereaz nodul de introdus: n=sizeof(TIP_NOD); p=(TIP_NOD *)malloc(n); /* rezervare spaiu de memorie n heap*/ citire date n nodul de adres p; c) Se fac legturile corespunztoare: p->urm = 0; /*nodul este ultimul n list */ if(ultim != 0) ultim->urm = p; /* lista nu este vid */ else prim = p; /* nodul p este primul introdus n list */ ultim=p;

2.2 Accesul la un nod al unei liste simplu nlnuite n funcie de cerine, nodurile listei pot fi accesate secvenial, extrgnd informaia util din ele. O problem mai deosebit este gsirea unui nod de o cheie dat i apoi extragerea informaiei din nodul respectiv. Cutarea nodului dup cheie se face liniar, el putnd fi prezent sau nu n list. O funcie de cutare a unui nod de cheie key va conine secvena de program de mai jos; ea returneaz adresa nodului respectiv n caz de gsire sau pointerul NULL n caz contrar: TIP_NOD *p; p=prim; while( p != 0 ) if (p->cheie == key) { /* s-a gsit nodul de cheie dat */ /* el are adresa p */ return p; } else p=p->urm; return 0; /* nu exist nod de cheie = key */ 2.3 Inserarea unui nod ntr-o list simplu nlnuit Nodul de inserat va fi generat ca la paragraful 2.1; se presupune c are pointerul p. Dac lista este vid, acest nod va fi singur n list: if (prim == 0) { prim=p; ultim=p; p->urm=0; } Dac lista nu este vid, inserarea se poate face astfel: a) naintea primului nod if(prim != 0) { p->urm = prim; prim = p; }

b) dup ultimul nod: if (ultim != 0) { p -> urm = 0; ultim -> urm = p; ultim = p; } c) naintea unui nod precizat printr-o cheie key: - se caut nodul de cheie key: TIP_NOD *q, *q1; q1=0; q=prim; while(q!=0) { if(q->cheie==key) break; q1=q; q=q->urm; } - se insereaz nodul de pointer p, fcnd legturile corespunztoare: if(q!=0) { /*nodul de cheie key are adresa q */ if (q==prim) { p->urm=prim; prim=p; } else { q1->urm=p; p->urm=q; } } d) dup un nod precizat printr-o cheie key: - se caut nodul avnd cheia key: TIP_NOD *q; q=prim; while(q!=0) { if(q->cheie==key) break; q=q->urm; }

- se insereaz nodul de adres p, fcnd legturile corespunztoare: if (q !=)0) { /* nodul de cheie key are adresa q */ p -> urm = q -> urm; q -> urm=p; if (q == ultim) ultim = p; } 2.4 tergerea unui nod dintr-o list simplu nlnuit La tergerea unui nod se vor avea n vedere urmtoarele probleme: lista poate fi vid, lista poate conine un singur nod sau lista poate conine mai multe noduri. De asemenea se poate cere tergerea primului nod, a ultimului nod sau a unui nod dat printr-o cheie key. a) tergerea primului nod TIP_NOD *p; if(prim!=0) { /* lista nu este vid */ p=prim; prim=prim->urm; elib_nod(p); /*eliberarea spaiului de memorie */ if(prim==0) ultim=0; /* lista a devenit vid */ }

b) tergerea ultimului nod TIP_NOD *q, *q1; q1=0; q=prim; if(q!=0) { /* lista nu este vid */ while(q!=ultim) { q1=q; q=q->urm; } if(q==prim) { prim=0; ultim=0; } else { q1->urm=0; ultim=q1; } elib_nod(q); } c) tergerea unui nod de cheie key TIP_NOD *q, *q1; /* cutare nod */ q1=0; q=prim; while (q!=0) { if(q->cheie == key) break; /* s-a gsit nodul */ q1=q; q=q->urm; } if(q != 0) { /* exist un nod de cheie key */ if (q == prim) { prim=prim_>urm; elib_nod(q); /*eliberare spaiu */ if( prim==0) ultim=0; } else { q1->urm=q->urm; if(q==ultim) ultim=q1; elib_nod(q); /* eliberare spaiu */ }

10

2.5 tergerea unei liste simplu nlnuite n acest caz, se terge n mod secvenial fiecare nod: TIP_NOD *p; while( prim != 0) { p=prim; prim=prim->ultim; elib_nod(p); /*eliberare spaiu de memorie */ } ultim=0; 2.6 Stive Stiva este o list simplu nlnuit bazat pe algoritmul LIFO (Last In First Out), adic ultimul nod introdus este primul scos. Modelul stivei, care va fi avut n vedere n continuare, este prezentat n fig.2.6.1.

Fig. 2.6.1. Model de stiv Fiind o structur particular a unei liste simplu nlnuite, operaiile principale asupra unei stive sunt: - push - pune un element pe stiv; funcia se realizeaz conform paragrafului 2.3.a., adic prin inserarea unui nod naintea primului; - pop - scoate elementul din vrful stivei; funcia se realizeaz conform paragrafului 2.4.a., adic prin tergerea primului nod; - clear - tergerea stivei; funcia se realizeaz conform paragrafului 2.5. n concluzie, accesul la o stiv se face numai pe la un capt al su.

11

2.7. Cozi Coada este o list simplu nlnuit, bazat pe algoritmul FIFO (First In First Out), adic primul element introdus este primul scos. Modelul cozii care va fi avut n vedere n consideraiile urmtoare, este prezentat n fig.2.7.1. Fig.2.7.1. Model de coad Deci coada are dou capete, pe la unul se introduce un element, iar de la celalalt capt se scoate un element. Operaiile importante sunt: - introducerea unui element n coad - funcia se realizeaz prin inserarea dup ultimul nod, conform celor prezentate introduce Se la paragraful un element 2.3.b.; nou - scoaterea unui element din coad funcia se realizeaz prin tergerea primului nod, conform celor prezentate la paragraful 2.4.a.; - tergerea cozii funcia se realizeaz conform paragrafului 2.5. 3. Mersul lucrrii 3.1.S se defineasc i s se implementeze funciile pentru structura de date typedef stuct { int lungime; struct TIP_NOD *inceput, *curent, *sfarit; } LISTA; avnd modelul din fig.3.1.

12

3.2.S se implementeze o list ca un tablou static ce conine pointeri la nodurile de informaie din heap, conform modelului din fig.3.2. 3.3. De la tastatur se citesc cuvinte. S se creeze o list simplu nlnuit ordonat alfabetic, care conine n noduri cuvintele distincte i frecvena lor de apariie. Se va afia coninutul listei n ordine alfabetic . 3.4. Se consider un depou de locomotive cu o singur intrare i cu o singur linie de cale ferat, care poate cuprinde oricte locomotive. S se scrie programul care realizeaz dispecerizarea locomotivelor din depou. ProgramulFig.3.1.Modelulcomenzi de problema n depou a unei prelucreaz listei pentru intrare 3.1 locomotive, de ieire din depou a unei locomotive i de afiare a locomotivelor din depou. 3.5. Aceeai problem 3.4, cu deosebirea c depoul are intrarea la un capt i ieirea la captul opus. 3.6. Sortarea topologic. Elementele unei mulimi M sunt notate cu litere mici din alfabet. Se citesc perechi de elemente x, y (x, y aparin mulimii M) cu semnificaia c elementul x precede elementulde listse afieze elementele mulimii M ntr-o Fig.3.2. Model y. S pentru problema 3.2. anumit ordine, nct pentru orice elemente x, y cu proprietatea c x precede pe y, elementul x s fie afiat naintea lui y. 3.7.S se scrie programul care creeaz dou liste ordonate cresctor dup o cheie numerica i apoi le interclaseaz. 3.8.S se conceap o stuctur dinamic eficient pentru reprezentarea matricelor rare. S se scrie operaii de calcul a sumei i produsului a dou matrice rare. Afisarea se va face in forma natural. 3.9.S se conceap o structur dinamic eficient pentru reprezentarea n memorie a polinoamelor. Se vor scrie funcii de calcul a sumei, diferenei, produsului i mpririi a dou polinoame. 3.10. Se citete de la tastatur o expresie postfixat corect sintactic. S se scrie programul de evaluare a sa. Expresia conine variabile formate dintr-o liter i operatorii binari +, -, *, /.

13

Lucrarea de laborator nr. 2.

LISTE CIRCULARE SIMPLU NLNUITE


1. Coninutul lucrrii n lucrare sunt prezentate principalele operaii asupra listelor circulare simplu nlnuite: crearea, inserarea unui nod, tergerea unui nod i tergerea listei. 2. Consideraii teoretice Lista circular simplu nlnuit este lista simplu nlnuit a crei ultim element este legat de primul element; adic ultim -> urm = prim. n cadrul listei circulare simplu nlnuite nu exist capete. Pentru gestionarea ei se va folosi un pointer ptr_nod, care adreseaz un nod oarecare al listei, mai precis ultimul introdus(fig.2.1.). Ca i la lista simplu nlnuit, principalele operaii sunt: crearea; accesul la un nod; inserarea unui nod; tergerea unui nod, tergerea listei.

Fig. 2.1 Modelul listei circulare simplu nlnuite

14

Structura unui nod este urmtoarea: typedef struct tip_nod { int cheie; /* nu este obligatoriu acest cmp */ cmpuri; struct tip_nod *urm; } TIP_NOD; 2.1. Crearea listei circulare simplu nlnuite Iniial lista este vid: ptr_nod = 0; Introducerea n list a cte unui nod se va face astfel: /* crearea nodului */ n = sizeof(TIP_NOD); p = (TIP_NOD *)malloc(n); /* dimensiunea nodului */ /* rezervarea memorie n heap */

citire date n nod la adresa p; if (ptr_nod = = 0) { /* lista este vid */ ptr_nod = p; ptr_nod -> urm = p; } else { /* lista nu este vid */ p -> urm = ptr_nod -> urm; ptr_nod -> urm = p; ptr_nod=p; /* ptr_nod pointeaz la ultimul nod inserat */ }

15

2.2. Accesul la un nod Nodurile pot fi accesate secvenial plecnd de la nodul de pointer ptr_nod: p=ptr_nod; if(p! = 0) do { /* lista nu este vid */ acceseaaz nodul i preia informaia; p = p -> urm; } while (p! = ptr_nod); sau cutnd un nod de cheie dat key; n acest caz o funcie care va returna pointerul la nodul gsit va conine urmtoarea secven de program: p = ptr_nod; if (p! = 0) do { /* lista nu este vid */ if ( p -> cheie == key) { /* s-a gsit nodul */ /* nodul are adresa p */ return p; } p = p -> urm; } while (p! = ptr_nod); return 0; 2.3. Inserarea unui nod Se pun urmtoarele probleme: inserarea naintea unui nod de cheie dat; inserarea dup un nod de cheie dat.

16

n ambele cazuri se caut nodul de cheie dat avnd adresa q; dac exist un astfel de nod ,se creeaz nodul de inserat de adres p i se fac legturile corespunztoare. a) Inserarea naintea unui nod de cheie dat

se caut nodul de cheie dat (adresa sa va fi q): TIP_NOD *p,*q,*q1; q = ptr_nod; do { q1 = q; q = q -> urm; if (q -> cheie = =key ) break; } while (q! = ptr_nod);

/* s-a gsit nodul */

se insereaz nodul de adres p; q1 -> urm = p; p -> urm = q; }

if (q -> cheie == key) {

b)

Inserarea dup un nod de cheie dat se caut nodul de cheie dat:

TIP_NOD *p,*q; q = ptr_nod; do { if (q -> cheie == key ) break; q = q -> urm; } while(q!=ptr_nod);

17

se insereaz nodul de adres p : p -> urm =q -> urm; q -> urm = p; }

if (q -> cheie == key) {

2.4. tergerea unui nod de cheie dat tergerea unui nod de cheie dat key se va face astfel: se caut nodul de cheie dat: q = ptr_nod; do { q1 = q; q = q -> urm; if (q -> cheie == key ) break; /* s-a gsit nodul */ } while (q! = ptr_nod); se terge nodul, cu meniunea c dac se terge nodul de pointer ptr_nod, atunci ptr_nod va pointa spre nodul precedent q1: if (q-> cheie == key) { if (q==q -> urm) ptr_nod==0; /* lista a devenit vid */ else { q1 -> urm = q -> urm; if (q == ptr_nod) ptr_nod = q1; } elib_nod(q); }

18

2.5.

tergerea listei tergerea listei circulare simplu nlnuite se va face astfel: p = ptr_nod; do { p1 =p; p = p -> urm; elib_nod(p1); } while (p! = ptr_nod); ptr_nod = 0; 3. Mersul lucrrii

3.1. S se defineasc i s se implementeze funciile pentru structura de date: struct LISTA_CIRC { int lungime; struct TIP_NOD *nceput; *curent; } avnd modelul din fig. 3.1. 3.2. De la tastatur se citete numrul n i numele a n copii. S se simuleze urmtorul joc: cei n copii stau ntr-un cerc. ncepnd cu un anumit copil, se numr copiii n sensul acelor de ceasornic. Fiecare al n-lea copil iese din cerc .Ctig ultimul copil rmas n joc. 3.3. S se implementeze un buffer circular, care conine nregistrri cu datele unui student i asupra cruia acioneaz principiul de sincronizare productor-consumator, care const n urmtoarele: a) nregistrrile sunt preluate n ordinea producerii lor; b) dac bufferul nu conine nregistrri, consumatorul este ntrziat pn cnd productorul depune o nregistrare; c) dac bufferul este plin, productorul este ntrziat pn cndFig.3.1. Modelulpreluatpentru problema 3.1. consumatorul a listei o nregistrare.

19

Lucrarea de laborator nr. 3.

LISTE DUBLU NLNUITE


1. Coninutul lucrrii n lucrare sunt prezentate principalele operaii asupra listelor dublu nlnuite: crearea, inserarea unui nod, tergerea unui nod, tergerea listei. 2. Consideraii teoretice Lista dublu nlnuit este lista dinamic ntre nodurile creia s-a definit o dubl relaie: de succesor si de predecesor. Modelul listei dublu nlnuite, pentru care se vor da explicaiile n continuare, este prezentat n figura 2.1. Tipul unui nod dintr-o list dublu nlnuit este definit astfel: typedef struct tip_nod { cheie; /* nu este obligatoriu */ date; struct tip_nod *urm; Fig. 2.1 Modelul listei circulare simplu nlnuite /* adresa urmtorului nod */ struct tip_nod * prec; /* adresa precedentului nod */ } TIP_NOD;

20

Ca i la lista simplu nlnuit, principalele operaii sunt: crearea; accesul la un nod; inserarea unui nod; tergerea unui nod, tergerea listei. Lista dublu nlnuit va fi gestionat prin pointerii prim i ultim: TIP_NOD *prim, *ultim; prim -> prec = 0; ultim -> urm = 0; 2.1. Crearea unei liste dublu nlnuite Iniial lista este vid: prim = 0; ultim = 0; Dup alocarea de memorie i citirea datelor n nod, introducerea nodului de pointer n list se va face astfel: if(prim= =0){ /* este primul nod n list */ prim = p; ultim = p; p -> urm = 0; p -> prec = 0; } else { /* lista nu este vid */ ultim -> urm = p; p -> prec = ultim; p -> urm = 0; p -> prec = ultim; ultim = p; }

21

2.2. Accesul la un nod Accesul la un nod se poate face: secvenial nainte (de la prim spre ultim): p = prim; while (p != 0) { vizitare nod de pointer p; p = p -> urm; } secvenial napoi ( de la ultim spre prim): p = ultim; while (p != 0) { vizitare nod de pointer p; p = p -> prec; } pe baza unei chei. Cutarea unui nod de cheie dat key se va face identic ca la lista simplu nlnuit (lucrarea 1, par. 2.2.).

2.3. Inserarea unui nod Inserarea unui nod ntr-o list dublu nlnuit se poate face astfel: naintea primului nod: if (prim == 0) { /* lista este vid */ prim = p; ultim = p; p -> urm = 0; p -> prec = 0; } else { /* lista nu este vid /* p -> urm =prim; p -> prec = 0; prim -> prec = p; prim = p; }

22

dup ultimul nod: if (prim == 0) { /* lista este vid */ prim = p; ultim = p; p -> urm = 0; p -> prec = 0; } else { /* lista nu este vid /* p -> urm =0; p -> prec = ultim; utim -> urm = p; ultim = p; } naintea unui nod de cheie dat key: Dup cutarea nodului de cheie key, presupunnd c acesta exist i are adresa q, nodul de adres p va fi inserat astfel: p -> prec = q -> prec; p -> urm = q; if (q -> prec != 0) q -> prec -> urm = p; q -> prec = p; if (q == prim) prim = p; dup un nod de cheie dat key: Dup cutarea nodului de cheie key, presupunnd c acesta exist i are adresa q, nodul de adres p va fi inserat astfel: p -> prec = q; p -> urm = q -> urm; if (q -> urm != 0) q -> urm -> prec = p; q -> urm = p; if (ultim == q) ultim = p;

23

2.4. tergerea unui nod Exist urmtoarele cazuri de tergere a unui nod din list: a) tergerea primului nod; acest lucru se poate face cu secvena de program: p = prim; prim = prim -> urm; /* se consider list nevid */ elib_nod(p); /* eliberarea nodului */ if (prim == 0) ultim = 0; /* lista a devenit vid */ else prim -> prec = 0; tergerea ultimului nod: p = ultim; ultim = ultim -> prec; /* se consider c lista nu este vid */ if (ultim == 0) prim = 0; /* lista a devenit vid */ else ultim -> urm = 0; elib_nod(p); /* tergerea nodului */
c) tergerea unui nod precizat printr-o cheie key. Presupunem c nodul de cheie key exist i are adresa p (rezult din cutarea sa):

if ((prim == p) && (ultim = =p)) { /* lista are un singur nod */ prim = 0;ultim = 0;/*lista devine vid*/ elib_nod(p);/*tergere nod* } else if(p == prim) { /* se terge primul nod */ prim = prim -> urm; prim -> prec =0; elib_nod(p); } else if (p == ultim) { /* se terge ultimul nod */ ultim = ultim -> prec; ultim -> urm = 0; elib_nod(p) } else { /* nodul de ters este diferit de capete */
24

p -> urm -> prec = p -> prec; p -> prec -> urm = p -> urm; elib_nod(p); } 2.5. tergerea listei tergerea ntregii liste se realizeaz tergnd nod cu nod astfel: TIP_NOD *p; while (prim != 0) { p = prim; prim = prim -> urm; elib_nod(p); } ultim = 0; 3. Mersul lucrrii

3.1. S se defineasc i s se implementeze funciile pentru structura de date: struct LISTA { int lungime; struct TIP_NOD *nceput, *curent, *sfrit; } avnd modelul din fig.3.1. 3.2. S se scrie funciile pentru realizarea operaiilor de creare, inserare, tergere pentru o list dublu nlnuit circular avnd modelele din fig.3.2. 3.3. De la tastatur se citesc n cuvinte ;s se creeze o list dublu nlnuit, care s conin n noduri cuvintele distincte i frecvena lor de apariie. Lista va fi ordonat alfabetic. Se vor afia cuvintele i frecvena lor de apariie a) n ordine alfabetic cresctoare i b) n ordine alfabetic descresctoare. Fig.3.1. Modelul listei pentru problema 3.1. 3.4. Folosind o list circular dublu nlnuit s se simuleze urmtorul joc: n copii, ale cror nume se citesc de la tastatur, stau n cerc. ncepnd cu un anumit copil (numele su se citete), se numr copiii n sensul acelor de ceasornic. Fiecare al m-lea copil (m se citete) iese din joc.

25

Fig. 3.2. Modelele de liste pentru problema

3.2.

Numrtoarea continu ncepnd cu urmtorul copil din cerc. Ctig jocul ultimul copil rmas n cerc.
3.5. Aceeai problem ca la 3.4., dar numrtoarea se face n sens contrar cu cel al acelor de ceasornic.

26

Lucrarea de laborator nr. 4.

ARBORI
1. Coninutul lucrrii n lucrare sunt prezentate operaiile de baz asupra arborilor binari, binari total echilibrai i arborilor oarecare. 2. Consideraii teoretice Arborele binar, foarte des ntlnit n aplicaii, este arborele n care orice nod are cel mult doi descendeni: fiul stng i fiul drept. 2.1. Construirea, traversarea i tergerea unui arbore binar. Construirea unui arbore binar se face citind n preordine din fiierul de intrare informaiile din nodurile arborelui. Subarborii vizi trebuie s fie notai cu un semn distinctiv. De exemplu pentru arborele din figura 2.1.1, notnd identificatorul pentru arborele vid cu * , introducerea identificatorilor nodurilor se va face astfel:

ABD*G***CE**F*H**
Fig. 2.1.1. Arbore binar.

Tipul unui nod se declar astfel:


27

typedef struct tip_nod { char ch; /* identificatorul nodului */ informaie; struct tip_nod *stg, *dr; } TIP_NOD; Construirea unui arbore binar se face conform funciei de construire, avnd urmtoarea structur: TIP_NOD *construire( ) { TIP_NOD *p; int n; char c; n=sizeof(TIP_NOD); /* citire caracter de identificare nod */ scanf(%c, c); if(c==*) return 0; else { /* construire nod de adres p */ p=(TIP_NOD *)malloc(n); /* introducere de informaie n nod */ p->ch=c; p->stg=construire( ); p->dr=construire( ); } return p; } Apelul funciei se va face astfel: rdcina = construire ( ) Traversarea unui arbore binar se poate face n cele 3 moduri cunoscute: preordine, inordine, postordine. n programul urmtor sunt implementate operaiile de construcie i traversare a unui arbore binar. Nodul conine numai identificatorul su. Afiarea nodurilor vizitate se face cu indentare.

28

/* Program de construire i afiare a arborilor binari */ #include <stdio.h> #include <conio.h> #include <alloc.h> typedef struct tip_nod{ int nr.; /*informaie */ struct tip_nod *stg,*dr; } TIP_NOD; TIP_NOD *rad; void preordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->nr); preordine(p->stg,nivel+1); preordine(p->dr,nivel+1); } } void inordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ inordine(p->stg,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->nr); inordine(p->dr,nivel+1); } } void postordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ postordine(p->stg,nivel+1); postordine(p->dr,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->nr); } }
29

TIP_NOD *constructie() { TIP_NOD *p; int inf,n; n=sizeof(TIP_NOD); printf("\nIntroduceti Inf.din nod inf="); scanf("%d",&inf); if(inf==0) return 0; else { p=(TIP_NOD *)malloc(n); p->nr=inf; p->stg=constructie(); p->dr=constructie(); } return p; } void main(void) { rad=constructie(); printf("\nVIZITAREA IN PREORDINE\n"); preordine(rad,0); getch(); printf("\nVIZITAREA IN INORDINE\n"); inordine(rad,0); getch(); printf("VIZITAREA IN POSTORDINE\n"); postordine(rad,0); getch(); } 2.7 Arbori binari total echilibrai Un arbore binar total echilibrat este un arbore binar care ndeplinete urmtoarea condiie: numrul nodurilor unui oricare subarbore stng difer cu cel mult 1 n plus fa de numrul nodurilor subarborelui corespunztor drept. Rezult c frunzele sale se afl pe ultimele dou niveluri. Algoritmul de construire a unui arbore binar total echilibrat cu n noduri, este urmtorul: a) un nod este rdcin;
30

se iau nstg = [n/2] noduri pentru arborele stng i se trece la construirea lui (pasul a); c) se iau cele ndr=n-nstg-1 noduri rmase pentru subarborele drept i se trece la construirea lui (pasul a). Pentru oricare nod exist relaia: ndr <= nstg <= ndr + 1 n programul urmtor este implementat acest algoritm pentru construirea unui arbore binar total echilibrat, citirea informaiei n noduri fcndu-se n preordine. #include <stdio.h> #include <conio.h> #include <alloc.h> /* ARBORI BINARI TOTAL ECHILIBRATI */ typedef struct tip_nod{ int nr;/*informaie */ struct tip_nod *stg,*dr; } TIP_NOD; TIP_NOD *rad; void preordine(TIP_NOD *p, int nivel) { int i; if(p!=0) { for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->nr); preordine(p->stg,nivel+1); preordine(p->dr,nivel+1); } } void inordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ inordine(p->stg,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->nr); inordine(p->dr,nivel+1); }
31

b)

} void postordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ postordine(p->stg,nivel+1); postordine(p->dr,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->nr); } } TIP_NOD *constructie(int nr_noduri) { TIP_NOD *p; int n_stg,n_dr; int inf,n; n=sizeof(TIP_NOD); if(nr_noduri==0) return 0; else { n_stg=nr_noduri/2; /*nr_noduri din subarborele stang */ n_dr=nr_noduri-n_stg-1; /*nr.noduri din subarborele drept */ p=(TIP_NOD *)malloc(n); printf("\nIntroduceti informatia din nod in preordine "); scanf("%d",&(p->nr)); p->stg=constructie(n_stg); p->dr=constructie(n_dr); } return p; } void main(void) { int nr_total_noduri; printf("\nNumarul total de noduri ="); scanf("%d",&nr_total_noduri);\ rad=constructie(nr_total_noduri);
32

printf("\nVIZITAREA IN PREORDINE\n"); preordine(rad,0); getch(); printf("\nVIZITAREA IN INORDINE\n"); inordine(rad,0); getch(); printf("VIZITAREA IN POSTORDINE\n"); postordine(rad,0); getch(); } 2.8 Arbori oarecare Arborele oarecare este un arbore a crui noduri au mai mult de doi descendeni. Un nod are urmtoarea structur: typedef struct tip_nod { informaie; int nr_fii; /*numr de fii */ struct tip_nod *adr_fii [maxfii]; /* adresele nodurilor fiu */ } TIP_NOD; Construirea arborelui se realizeaz astfel: pentru fiecare nod se citete informaia util i numrul de fii; nodurile citite n postordine i adresele sunt pstrate ntr-o stiv pn cnd apare nodul al crui fii sunt. n acest moment adresele fiilor sunt scoase din stiv, iar adresele lor sunt trecute n nodul tat, dup care adresa nodului tat este pus n stiv. n final singura adres n stiv va fi cea a rdcinii, arborele fiind construit. Traversarea arborelui pe orizontal (nivel dup nivel) se va face astfel: se utilizeaz o coad pentru pstrarea adreselor nodurilor ce urmeaz s fie prelucrate; iniial coada este vid; se introduce n coada adresa rdcinii;

33

se scoate pe rnd din coad adresa a cte unui nod, se prelucreaz informaia din nod, iar apoi se introduc adresele fiilor nodului respectiv. Se repet acest pas pn cnd coada devine vid. 3. Mersul lucrrii 3.1 Se citete de la tastatur o expresie matematic n form postfixat, sub forma unui ir de caractere. S se construiasc arborele corespunztor acestei expresii, fiecare nod coninnd un operator sau un operand. 3.2 S se tipreasc expresia de la punctul 3.1. n form postfixat i infixat. 3.3 S se evalueze expresia matematic de la punctul 3.1. 3.4 S se evalueze un arbore care conine n noduri constantele 0 i 1 i operatorii AND, OR, NOT. 3.5 S se scrie funcii de pretty-print (tiprire frumoas) a arborilor. 3.6 S se scrie funcii nerecursive pentru traversarea arborilor. 3.7 Arborele genealogic al unei persoane se reprezint astfel: numele persoanei este cheia nodului rdcin i pentru fiecare nod cheia descendentului stng este numele tatlui, iar a descendentului drept este numele mamei. Se citesc dou nume de la tastatur. Ce relaie de rudenie exist ntre cele dou persoane? Se presupune c o familie are doar un singur fiu. 3.8 S se scrie un program care transform un arbore binar ntr-o list dublu nlnuit. 3.9 S se scrie un program care s interschimbe subarborele drept cu cel stng pentru un nod dat. 3.10 S se scrie o funcie care determin nlimea unui arbore binar. 3.11 S se scrie o funcie care determin numrul de frunze ale unui arbore binar.

34

3.12 S se scrie o funcie care determin dac doi arbori binari sunt echivaleni (arborii binari sunt echivaleni dac sunt structural echivaleni i datele corespunztoare nodurilor sunt aceleai). 3.13 S se scrie un program de construire i traversare a unui arbore oarecare conform indicaiilor din lucrare (paragraful 2.3.).

35

Lucrarea de laborator nr. 5.

ARBORI BINARI DE CUTARE


1. Coninutul lucrrii n lucrare sunt prezentate principalele operaii asupra arborilor binari de cutare: inserare, cutare, tergere, traversare. De asemenea sunt prezentai arborii binari de cutare optimali. 2. Consideraii teoretice Arborii binari de cutare sunt des folosii pentru memorarea i regsirea rapid a unor informaii, pe baza unei chei. Fiecare nod al arborelui trebuie s conin o cheie distinct. Structura unui nod al unui arbore binar de cutare este urmtoarea: typedef struct tip_nod { tip cheie; informaii_utile; struct tip_nod *stg, *dr; } TIP_NOD; n cele ce urmeaz, rdcina arborelui se consider ca o variabil global: TIP_NOD *rad; Structura arborelui de cutare depinde de ordinea de inserare a nodurilor. 2.1.Inserarea ntr-un arbore binar de cutare. Construcia unui arbore binar de cutare se face prin inserarea a cte unui nod de cheie key. Algoritmul de inserare este urmtorul: a) Dac arborele este vid, se creeaz un nou nod care este rdcina, cheia avnd valoarea key, iar subarborii stng i drept fiind vizi.

36

b) Dac cheia rdcinii este egal cu key atunci inserarea nu se poate face ntruct exist deja un nod cu aceast cheie. c) Dac cheia key este mai mic dect cheia rdcinii, se reia algoritmul pentru subarborele stng (pasul a). d) Dac cheia key este mai mare dect cheia rdcinii, se reia algoritmul pentru subarborele drept (pasul a). Funcia nerecursiv de inserare va avea urmtorul algoritm: void inserare_nerecursiv (int key) { TIP_NOD *p, *q; int n; /* construcie nod p*/ n=sizeof (TIP_NOD); p=(TIP_NOD*)malloc(n); p->cheie=key; /* introducere informaie util n nodul p */ p->stg=0; p->dr=0; /* este nod funz */ if (rad==0) { /* arborele este vid */ rad=p; return; } /* arborele nefiind vid se caut nodul tat pentru nodul p */ q=rad; /* rad este rdcina arborelui variabil global */ for ( ; ; ) { if (key<q->cheie) { /* cutarea se face n subarborele stng */ if (q->stg==0) { /* inserare */ q->stg=p; return; } else q=q->stg; } else if (key>q->cheie) { /* cutarea se face n subarborele drept */ if (q->dr==0) { /* inserare */ q->dr=p;
37

return; } else q=q->dr; } else { /* cheie dubl */ /* scriere mesaj */ free (p); return; } } } 2.2 Cutarea unui nod de cheie dat key ntr-un arbore binar de cutare. Cutarea ntr-un arbore binar de cutare a unui nod de cheie dat se face dup un algoritm asemntor cu cel de inserare. Numrul de cutri optim ar fi dac arborele de cutare ar fi total echilibrat (numrul de comparaii maxim ar fi log 2 n unde n este numrul total de noduri). Cazul cel mai defavorabil n ceea ce privete cutarea este atunci cnd inserarea se face pentru nodurile avnd cheile ordonate cresctor sau descresctor. n acest caz, arborele degenereaz ntr-o list. Algoritmul de cutare este redat prin funcia urmtoare: TIP_NOD *cautare (TIP_NOD *rad, int key) /* funcia returneaz adresa nodului n caz de gsire sau 0 n caz c nu exist un nod de cheia key */ { TIP_NOD *p; if (rad==0) return 0; /* arborele este vid */ /* dac arborele nu este vid, cutarea ncepe din rdcina rad */ p=rad; while (p!=0) { if (p->cheie==key) return p; /* s-a gsit */
38

else if (key<p->cheie) p=p->stg; /*cutarea se face n subarb.stng */ else p=p->dr; /* cutarea se face n subarborele drept */ } return 0; /* nu exist nod de cheie key */ } Apelul de cutare este: p=cautare (rad, key); rad fiind pointerul spre rdcina arborelui. 2.3 tergerea unui nod de cheie dat ntr-un arbore binar de cutare n cazul tergerii unui nod, arborele trebuie s-i pstreze structura de arbore de cutare. La tergerea unui nod de cheie dat intervin urmtoarele cazuri: a) Nodul de ters este un nod frunz. n acest caz, n nodul tat, adresa nodului fiu de ters (stng sau drept) devine zero. b) Nodul de ters este un nod cu un singur descendent. n acest caz, n nodul tat, adresa nodului fiu de ters se nlocuiete cu adresa descendentului nodului fiu de ters. c) Nodul de ters este un nod cu doi descendeni. n acest caz, nodul de ters se nlocuiete cu nodul cel mai din stnga al subarborelui drept sau cu nodul cel mai din dreapta al subarborelui stng. Algoritmul de tergere a unui nod conine urmtoarele etape: cutarea nodului de cheie key i a nodului tat corespunztor; determinarea cazului n care se situeaz nodul de ters.

39

2.4 tergerea unui arbore binar de cutare tergerea unui arbore binar de cutare const n parcurgerea n postordine a arborelui i tergerea nod cu nod, conform funciei urmtoare: void stergere_arbore(TIP_NOD *rad) { if (rad !=0) { stergere_arbore (rad->stg); stergere_arbore (rad->dr); free (rad); } } 2.5 Traversarea unui arbore binar de cutare Ca orice arbore binar, un arbore binar de cutare poate fi traversat n cele trei moduri: n preordine, n inordine i n postordine conform funciilor de mai jos: void preordine (TIP_NOD *p) { if (p!=0) { extragere informaie din nodul p; preordine (p->stg); preordine (p->dr); } } void inordine (TIP_NOD *p) { if (p!=0) { inordine (p->stg); extragere informaie din p; inordine (p->dr); } } void postordine (TIP_NOD *p) {
40

if (p!=0) { postordine (p->stg); postordine (p->dr); extragere informaie din nodul p; } } Apelul acestor funcii se va face astfel: preordine(rad); inordine(rad); postordine(rad); 2.6 Arbori binari de cutare optimali Lungimea drumului de cutare a unui nod cu cheia x, ntr-un arbore binar de cutare, este nivelul hi al nodului n care se afl cheia cutat, n caz de succes sau 1 plus nivelul ultimului nod ntlnit pe drumul cutrii fr succes. Fie S ={ c1, c2, ... , cn } mulimea cheilor ce conduc la cutarea cu succes (c1 < c2 < ... < cn). Fie pi probabilitatea cutrii cheii ci (i=1,2,...,n). Dac notm cu C, mulimea cheilor posibile, atunci C S reprezint mulimea cheilor ce conduce la cutarea fr succes. Aceast mulime o partiionm n submulimile: k0 mulimea cheilor mai mici ca c1; kn mulimea cheilor mai mari ca cn; ki (i=1,2,...,n) mulimea cheilor n intervalul (ci, ci+1). Fie qi probabilitatea cutrii unei chei din mulimea ki. Cutarea pentru orice cheie din ki se face pe acelai drum; lungimea drumului de cutare va fi hi. Notm cu L costul arborelui, care reprezint lungimea medie de cutare: L=
i =1 n

p h + q h
i i j =0 j

' i

cu condiia:

41

p + q
i =1 i j =0

=1

Se numete arbore optimal, un arbore binar de cutare care pentru anumite valori pi, qi date realizeaz un cost minim. Arborii optimali de cutare nu sunt supui inserrilor i eliminrilor. Din punct de vedere al minimizrii funciei L, n loc de pi i qi se pot folosi frecvenele apariiei cutrilor respective n cazul unor date de test. Se noteaz cu Aij arborele optimal construit cu nodurile ci+1, ci+2, ..., cj. Greutatea arborelui Aij este:

q
q =q
ii

ij

k =i +1

p + q
k k =i

care se poate calcula astfel:


i

q =q
ij

i , j 1

p +q
j

pentru i = 1, 2, ..., n; pentru 0 i j n

Rezult c costul arborelui optimal Aij se va putea calcula astfel: pentru 0 i n cii = qii

c = q min (c
ij ij i< k j

i , k 1

+ c kj )

Fie rij valoarea lui k pentru care se obine minimul din relaia lui cij. Nodul cu cheia c[rij] va fi rdcina subarborelui optimal Aij, iar subarborii si vor fi Ai,k-1 i Akj. Calculul valorilor matricei C este de ordinul O(n3). S-a demonstrat c se poate reduce ordinul timpului de calcul la O(n2). Construirea se face cu ajutorul funciei urmtoare: TIP_NOD *constr_arbore_optimal(int i, int j) { int n; TIP_NOD *p; if(i==j) p=0; else { n=sizeof (TIP_NOD); p=(TIP_NOD*)malloc(n); p->stg=constr_arbore_optimal(i, r[i][j]-1); p->stg=constr_arbore_optimal[r[i][j]];
42

p->dr=constr_arbore_optimal(r[i][j], j); } return p; } 2.7 Exemple Primul program prezint toate funciile descrise n lucrare asupra unui arbore de cutare. Un nod conine drept informaie util numai cheia, care este un numr ntreg. Al doilea program conine funciile principale asupra unui arbore binar de cutare optimal. Exemplul nr.1 (arbori de cutare) #include <stdio.h> #include <conio.h> #include <alloc.h> typedef struct tip_nod{ int cheie;/*informatie */ struct tip_nod *stg,*dr; } TIP_NOD; TIP_NOD *rad; void preordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->cheie); preordine(p->stg,nivel+1); preordine(p->dr,nivel+1); } } void inordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ inordine(p->stg,nivel+1);

43

for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->cheie); inordine(p->dr,nivel+1); } } void postordine(TIP_NOD *p, int nivel) { int i; if (p!=0){ postordine(p->stg,nivel+1); postordine(p->dr,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("%2d\n",p->cheie); } } void inserare(int key) { TIP_NOD *p,*q; int n; n=sizeof(TIP_NOD); p=(TIP_NOD *)malloc(n); p->cheie=key; p->stg=0;p->dr=0; if(rad==0){ rad=p; return; } q=rad; for(;;) { if (key < q->cheie){ if(q->stg==0){ q->stg=p; return; } else q=q->stg; } else if (key > q->cheie) {
44

if(q->dr == 0) { q->dr=p; return; } else q=q->dr; } else { /* chei egale */ printf("\n Exista un nod de cheie = %d\n",key); /* eventuala prelucrare a nodului */ free(p); return; } } } TIP_NOD *inserare_rec(TIP_NOD *rad,int key) { TIP_NOD *p; int n; if (rad==0){ n=sizeof(TIP_NOD); p=(TIP_NOD *)malloc(n); p->cheie=key;p->stg=0;p->dr=0; return p; } else { if(key < rad->cheie) rad->stg=inserare_rec(rad->stg,key); else { if(key > rad->cheie) rad->dr=inserare_rec(rad->dr,key); else { /* cheie dubla */ printf("\n Exista un nod de cheie=%d\n",key); } } }; return rad; } TIP_NOD * cautare(TIP_NOD *rad, int key) {
45

TIP_NOD *p; if(rad==0) return 0;/*arborele este vid */ p=rad; while(p != 0) { if(p->cheie == key) return p;/* s-a gasit nodul */ else if(key < p->cheie) p=p->stg; else p=p->dr; } return 0; /* nu exista nod de cheie key */ } TIP_NOD *stergere_nod(TIP_NOD *rad,int key) { TIP_NOD *p,*tata_p;/* p este nodul de sters, iar tata_p este tatal lui */ TIP_NOD *nod_inlocuire,*tata_nod_inlocuire;/*nodul care il va inlocui pe p si tatal sau */ int directie; /*stg=1;dr=2*/ if(rad==0) return 0; /*arborele este vid */ p=rad; tata_p=0; /* cautare nod cu cheia key */ while((p!=0)&&(p->cheie!=key)) { if (key<p->cheie){ /*cautare in stanga */ tata_p=p; p=p->stg; directie=1; } else { /*cautare in dreapta */ tata_p=p; p=p->dr; directie=2; } } if(p==0){ printf("\n NU EXISTA NOD CU CHEIA=%d\n",key); return rad; }
46

/* s-a gasit nodul p de cheie key */ if(p->stg==0) nod_inlocuire=p->dr; /* nodul de sters p nu are fiu sting */ else if(p->dr==0) nod_inlocuire=p->stg; /*nodul de sters p nu are fiu drept*/ else { /* nodul de sters p are fiu stang si fiu drept */ tata_nod_inlocuire=p; nod_inlocuire=p->dr; /* se cauta in subarborele drept*/ while(nod_inlocuire->stg!=0) { tata_nod_inlocuire=nod_inlocuire; nod_inlocuire=nod_inlocuire->stg; } if(tata_nod_inlocuire!=p) { tata_nod_inlocuire->stg=nod_inlocuire->dr; nod_inlocuire->dr=p->dr; } nod_inlocuire->stg=p->stg; } free(p); printf("\nNodul de cheie=%d a fost sters!\n",key); if(tata_p==0) return nod_inlocuire; /*s-a sters chiar radacina initiala */ else { if (directie==1) tata_p->stg=nod_inlocuire; else tata_p->dr=nod_inlocuire; return rad; } } void stergere_arbore(TIP_NOD *rad) { if(rad!=0) { stergere_arbore(rad->stg); stergere_arbore(rad->dr); free(rad); } }
47

void main(void) { TIP_NOD *p; int i, n,key; char ch; printf("ALEGETI Inserare recursiva r/R sau nerecursiva alt caracter"); scanf("%c",&ch); printf("\nNumarul total de noduri="); scanf("%d",&n); rad=0; for(i=1;i<=n;i++) { printf("\nCheia nodului="); scanf("%d",&key); if((ch=='R')||(ch=='r')) rad=inserare_rec(rad,key); else inserare(key); } printf("\nVIZITAREA IN PREORDINE\n"); preordine(rad,0); getch(); printf("\nVIZITAREA IN INORDINE\n"); inordine(rad,0); getch(); printf("VIZITAREA IN POSTORDINE\n"); postordine(rad,0); getch(); fflush(stdin); printf("\n Doriti sa cautati un nod DA=D/d Nu= alt caracter :"); scanf("%c",&ch); while((ch=='D')||(ch=='d')) { printf("Cheia nodului cautat="); scanf("%d",&key); p=cautare(rad,key); if(p!=0) printf("Nodul exista si are adresa p\n"); else printf("Nu exista un nod de cheie data\n"); fflush(stdin);
48

printf("\n Doriti sa cautati un nod DA=D/d Nu= alt caracter : "); scanf("%c",&ch); } fflush(stdin); printf("\n Doriti sa sterget un nod DA=D/d Nu= alt caracter :"); scanf("%c",&ch); while((ch=='D')||(ch=='d')) { printf("Cheia nodului de sters="); scanf("%d",&key); rad=stergere_nod(rad,key); inordine(rad,0); fflush(stdin); printf("\n Doriti sa stergeti un nod DA=D/d Nu= alt caracter : "); scanf("%c",&ch); } printf("stergeti arborele creat ? da=d/D nu=alt caracter "); fflush(stdin); scanf("%c",&ch); if((ch=='D')||(ch=='d')) { stergere_arbore(rad); rad=0; printf("\nARBORELE ESTE STERS!!\n"); } getch(); }

Exemplul nr.2 (arbori optimali) #include <stdio.h> #include <conio.h> #include <alloc.h> #define nmax 25
49

typedef struct tip_nod { char cheie; tip_nod *stg,*dr; } TIP_NOD; char chei[nmax]; /* cheile c1,c2,...,cn */ int p[nmax];/* frecventa de cautare a cheilor */ int q[nmax]; /* frecventa de cautare intre chei */ int r[nmax][nmax]; /* radacinile subarborilor optimali */ void calcul(int nr,float *dr_med) { /* determina structura arborelui */ int c[nmax][nmax];/* costul subarborilor optimali */ int g[nmax][nmax]; /* greutatea arborilor */ int i,j,k,m,l; int x,min; /* calculul matricei greutate */ for(i=0;i<=nr;i++) { g[i][i]=q[i]; for(j=i+1;j<=nr;j++) g[i][j]=g[i][j-1]+p[j]+q[j]; } /* calculul matricei c */ for(i=0;i<=nr;i++) c[i][i]=g[i][i]; for(i=0;i<=nr-1;i++) { j=i+1; c[i][j]=c[i][i]+c[j][j]+g[i][j]; r[i][j]=j; } /*calcul c[i][l+i] */ for(l=2;l<=nr;l++) for(i=0;i<=nr-l;i++) { min=32000;
50

for(k=i+1;k<=l+i;k++) { x=c[i][k-1]+c[k][l+i]; if(x<min) { min=x; m=k; } } c[i][l+i]=min+g[i][l+i]; r[i][l+i]=m; } printf("\nMATRICEA G\n"); for(i=0;i<=nr;i++) { for(j=i;j<=nr;j++) printf("%d ",g[i][j]); printf("\n"); } getch(); printf("\nMATRICEA C\n"); for(i=0;i<=nr;i++) { for(j=i;j<=nr;j++) printf("%d ",c[i][j]); printf("\n"); } getch(); printf("\nMATRICEA R\n"); for(i=0;i<=nr;i++) { for(j=i;j<=nr;j++) printf("%d ",r[i][j]); printf("\n"); } getch(); printf("c[0][nr.]=%ld g[0][nr.]=%ld\n",c[0] [nr.],g[0][nr.]); getch(); *dr_med=c[0][nr.]/(float)g[0][nr.]; } TIP_NOD* constr_arbore(int i,int j)
51

/* construirea arborelui optimal */ { int n; TIP_NOD *p; if (i==j) p=0; else { n=sizeof(TIP_NOD); p=(TIP_NOD*)malloc(n); p->stg=constr_arbore(i,r[i][j]-1); p->cheie=chei[r[i][j]]; p->dr=constr_arbore(r[i][j],j); } return p; } void inordine(TIP_NOD *p,int nivel) { /* Afisare in inordine a arborelui */ int i; if(p!=0){ inordine(p->stg,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("%c\n",p->cheie); inordine(p->dr,nivel+1); } } void main(void) { TIP_NOD *radacina; int i; int n; /*n este numarul cheilor */ float drum_mediu; printf("\nNumarul cheilor="); scanf("%d",&n); /*Citirea cheilor si a frecventelor de cautare a lor*/ for(i=1;i<=n;i++) { printf("Cheia[%d]=",i);
52

chei[i]=getche(); printf(" frecventa="); scanf("%d",&p[i]); } /*Citirea frecventelor de cautare intre chei */ for(i=0;i<=n;i++) { printf("q[%d]=",i); scanf("%d",&q[i]); } calcul(n,&drum_mediu); printf("Drumul mediu=%6f\n",drum_mediu); getch(); radacina=constr_arbore(0,n); inordine(radacina,0); getch(); } 4. Mersul lucrrii 3.1 De la tastatur se citesc cuvinte ( iruri de caractere ). S se scrie un program care creeaz un arbore de cutare, care conine n noduri cuvintele i frecvena lor de apariie. S se afieze apoi cuvintele n ordine lexicografic cresctoare i frecvena lor de apariie. 3.2 S se implementeze operaia de interclasare a doi arbori de cutare.

3.3 S se verifice dac operaia de tergere a unui nod dintr-un arbore de cutare este comutativ ( tergerea nodurilor x i y se poate face n orice ordine). 3.4 Se consider dou liste liniare simplu nlnuite cu cmpurile de informaie util coninnd numere ntregi. S se construiasc o list care conine reuniunea celor dou liste i n care elementele sunt ordonate cresctor. Se va folosi o structur intermediar de tip arbore de cutare. Elementele comune vor apare a o singur dat.

53

3.5 Se consider un arbore de cutare care conine elemente cu informaia util de tip ir de caractere. S se scrie o funcie de cutare, inserare i tergere a irului de caractere permindu-se folosirea abloanelor, spre exemplu * pentru orice subir sau ? pentru orice caracter. 3.6 Informaiile pentru medicamentele unei farmacii sunt: nume medicament, pre, cantitate, data primirii, data expirrii. Evidena medicamentelor se ine cu un program care are drept structur de date un arbore de cutare dup nume medicament. S se scrie programul care execut urmtoarele operaii: creeaz arborele de cutare; caut un nod dup cmpul nume medicament i actualizeaz cmpurile de informaie; tiprete medicamentele n ordine lexicografic; elimin un nod identificat prin nume medicament; creeaz un arbore de cutare cu medicamentele care au data de expirare mai veche dect o dat specificat de la terminal. 3.7 Se va crea un arbore binar de cutare optimal care va avea n noduri cuvintele cheie folosite n limbajul C. Frecvenele pi i qi se vor da n funcie de folosirea cuvintelor cheie n programele exemplu din lucrare.

54

Lucrarea de laborator nr. 6

REPREZENTAREA I TRAVERSAREA GRAFURILOR


1. Coninutul lucrrii n lucrare sunt prezentate cteva noiuni legate de grafuri, modurile de reprezentare i traversare a lor. 2. Consideraii teoretice 2.1. Noiuni de baz Graful orientat sau digraful G =(V, E) este perechea format din mulimea V de vrfuri i mulimea E V V de arce. Un arc este o pereche ordonat de vrfuri (v, w), unde v este baza arcului, iar w este vrful arcului. In ali termeni se spune c w este adiacent lui v. O cale este o succesiune de vrfuri v[1],v[2],,v[k], astfel c exist arcele (v[1],v[2]), (v[2],v[3]),,(v[k-1],v[k]) n mulimea arcelor E. Lungimea cii este numrul de arce din cale. Prin convenie, calea de la un nod la el nsui are lungimea 0. O cale este simpl, dac toate vrfurile, cu excepia primului i ultimului sunt distincte ntre ele. Un ciclu este o cale de la un vrf la el nsui. Un graf orientat etichetat este un graf orientat n care fiecare arc i /sau vrf are o etichet asociat, care poate fi un nume, un cost sau o valoare de un tip oarecare. Un graf orientat este tare conex, daca oricare ar fi vrfurile v i w exist o cale de la v la w i una de la w la v. Un graf G =(V, E) este subgraf al lui G daca V V i E E. Se spune c subgraful indus de V V este G =(V, E (VV)). Un graf neorientat sau prescurtat graf G =(N, R) este perechea format din mulimea N de noduri i mulimea R de muchii. O muchie este o pereche neordonat (v, w)=(w, v) de noduri. Definiiile prezentate anterior rmn valabile i n cazul grafurilor neorientate.

55

2.2. Moduri de reprezentare Att grafurile orientate, ct i cele neorientate se reprezint frecvent sub dou forme: matricea de adiacene i listele de adiacene. Astfel, pentru graful orientat G =(V, E), unde V este mulimea vrfurilor V ={1,2,,n},matricea de adiacene A va fi definit astfel : 1 dac (i, j)E 0 dac (i, j)E

A[i][j]=

Matricea de adiacene etichetat A (sau matricea costurilor) va fi definit astfel : eticheta arcului (i, j) dac (i, j) E un simbol dac (i, j)E

A[i][j]=

Matricea de adiacene este simetric pentru grafuri neorientate i nesimetric pentru cele orientate. Matricea de adiacene este util cnd se testeaz frecvent prezena sau absena unui arc i este dezavantajoas cnd numrul de arce este mult mai mic dect n x n. Reprezentarea prin liste de adicene folosete mai bine memoria, dar cutarea arcelor este mai greoaie. n aceast reprezentare, pentru fiecare nod se pstreaz lista arcelor ctre nodurile adiacente. ntregul graf poate fi reprezentat printr-un tablou indexat dup noduri, fiecare intrare n tablou coninnd adresa listei nodurilor adiacente. Lista nodurilor adiacente poate fi dinamic sau static. Pentru graful din fig.2.2.1, sunt prezentate: - matricea de adiacene n fig.2.2.2.; - lista de adiacene dinamic n fig.2.2.3.; - lista de adiacene static n fig.2.2.4.

56

2.3.Explorarea n lrgime Explorarea n lrgime const n urmtoarele aciuni: se trece ntr-o coad vid nodul de pornire; se trece extrage din coad cte un nod care este prelucrat i se adaug toate nodurile adiacente lui neprelucrate. Se repet acest pas pn cnd coada devine vid.

LISTA

Fig. 2.2.3.Lista de adiacene dinamic.

Fig. 2.2.4.Lista de adiacene static.

57

Algoritmul este urmtorul: void explorare_largime(int s) /* s este nodul de pornire */ { int vizitate[NrMaxNoduri]; coada Q; int i,NrNoduri,v,w; for(i=0;i<NrNoduri;i++) vizitate[i]=0; /*iniializare vector cu zero*/ vizitate[s]=1;/*se viziteaz nodul s */ prelucreaz informaia din s; introducere nod s in coada Q; while(coada Q nu este vid) { extrage urmtorul nod v din coada Q; for(fiecare nod w adiacent lui v) if(nodul w nu a fost vizitat) { vizitate[w]=1; prelucreaz informaia din w; introducere nod w n coada Q; } } } 2.4.Explorarea in adncime La explorarea n adncime se marcheaz vizitarea nodului iniial, dup care se viziteaz n adncime, recursiv, fiecare nod adiacent. Dup vizitarea tuturor nodurilor ce pot fi atinse din nodul de start, parcurgerea se consider ncheiat. Dac rmn noduri nevizitate, se alege un nou nod i se repet procedeul de mai sus.

58

Algoritmul este urmtorul: void explorare_adancime(int s) /* s este nodul de pornire* / { int vizitate[NrMaxNoduri]; stiva ST; int NrNoduri,i,v,w; for(i=0;i<NrNoduri;i++) vizitate[i]=0; /*initializare vector cu zero*/ vizitate[s]=1; /*se incepe cu s */ prelucrare informatia din s; introducere s in stiva ST; while(stiva ST nu este vid) { v=coninutul nodului din vrful stivei; w=urmtorul nod adiacent lui v nevizitat; if(exist w) { vizitate[w]=1; prelucreaz informaia din w; pune pe stiva ST nodul w; } else pop(ST); /* se terge nodul v din vrful stivei ST */ } } 3.Mersul lucrrii 3.1. Pentru un graf orientat G =(V, E) i VV s se gseasc subgraful indus G =(V, E).Elementele din V i V se citesc. 3.2. S se scrie cte o funcie de construire pentru un graf G =(V, E), conform celor 3 reprezentri posibile. 3.3. Pentru un graf reprezentat prin matricea de adiacene, s se implementeze algoritmii de traversare prezentai n paragrafele 2.3. i 2.4.

59

3.4. S se scrie o funcie care s verifice dac exist o cale ntre dou noduri date (v, w) ale unui graf orientat G =(V, E). 3.5. Pentru un graf neorientat dat G =(N, R), s se scrie o funcie care s verifice dac graful este conex sau nu.

60

Lucrarea de laborator nr.7

ALGORITMI PENTRU PRELUCRAREA GRAFURILOR

1.Coninutul lucrrii In lucrare sunt prezentai algoritmii lui Dijkstra i Floyd pentru gsirea cilor de cost minim ntre dou noduri precizate, respectiv ntre oricare dou noduri ale unui graf i algoritmii lui Kruskal i Prim pentru gsirea arborelui de cost minim de acoperire a unui graf. 2.Consideraii teoretice 2.1.Cile de cost minim dintr-un vrf Se consider un graf orientat G =(V, E) etichetat, n care fiecare arc are ca etichet un numr nenegativ numit cost. Graful se reprezint n memorie prin matricea de adiacene etichetat, care se mai numete matricea costurilor. Fiind date dou vrfuri, unul surs i unul destinaie, se cere gsirea drumului de cost minim de la surs la destinaie. Algoritmul lui Dijkstra de rezolvare a acestei probleme const in urmtoarele: - se pstreaz o mulime S de vrfuri jV, pentru care exist cel puin un drum de la surs la j. Iniial S ={sursa}. - la fiecare pas, se adaug la S un vrf a crui distan fa de un vrf din S este minim. Pentru a nregistra cile minime de la surs la fiecare vrf se utilizeaz un tablou TATA, n care TATA[k] pstreaz vrful anterior lui k pe calea cea mai scurt. In descrierea algoritmului se fac urmtoarele notaii: - n numrul vrfurilor mulimii V; - mulimea S se reprezint prin vectorul caracteristic ( elementele sale sunt S[i]=1, dac iS i S[i]=0, dac iS; - vectorul DISTANTE de n elemente al distanelor minime de la surs la fiecare vrf;

61

- matricea de costuri COST de nxn elemente: COST[ i ][j]=c>0 dac ( i ,j)E, COST[i][j]=0 dac i =j si COST[i][j]=+ dac (i, j)E; - vectorul TATA. Algoritmul lui Dijkstra este urmtorul: #define NMAX #define INFINIT float DISTANTE[NMAX]; float COST[NMAX][NMAX]; int TATA[NMAX]; int S[NMAX]; void DIJKSTRA(int n,int sursa) { /* n este numrul de vrfuri;sursa este vrful surs */ int i ,j,k,pas; /*iniializri*/ for (i=1;i<=n;i++) { S[i]=0; DISTANTE[i]=COST[sursa][i]; if (DISTANTE[i]<INFINIT) TATA[i]=sursa; else TATA[i]=0; } /*introducere sursa in S*/ S[sursa]=1; TATA[sursa]=0; DISTANTE[sursa]=0; /*construire vectori DISTANTE si TATA */ for (pas=1;pas<=n-1;pas++) { gsete vrful k neselectat cu DISTANTE[k] minim; if (minimul anterior gsit==INFINIT) return; S[k]=1; /* se adaug k la mulimea S */ for (j=1;j<=n;j++) if ((S[j]=0) && (DISTANTE[k]+COST[k] [j]<DISTANTE[j])) { DISTANTE[j]=DISTANTE[k]+COST[k][j];

62

TATA[j]=k; } } } Vectorul TATA conine vrfurile accesibile din vrfurile sursa. El permite reconstruirea drumurilor de la vrful surs la oricare vrf accesibil. Pentru vrfurile inaccesibile din vrful sursa vom avea S[i]=0 i DISTANTE[i]=INFINIT. 2.2.Cile de cost minim din oricare vrf Algoritmul prezentat la 2.1. poate fi repetat din nodurile unui graf. Acest lucru permite calculul unui tablou al drumurilor minime ntre toate perechile de vrfuri ale grafului. In continuare se prezint un algoritm mai simplu, algoritmul lui Floyd. Algoritmul lui Floyd const n gsirea costurilor minime ntre oricare dou vrfuri i, jV. Aceste costuri minime se pstreaz n matricea A. Matricea A este iniial egal cu matricea costurilor. Calculul distanelor minime se face n n iteraii, n fiind numrul vrfurilor. La iteraia k, A[i][j] va avea ca valoare cea mai mic distan intre i si j pe ci care nu conin vrfuri peste k (exceptnd capetele i si j). Se utilizeaz formula urmtoare: Aij(k)= min (Aij(k-1),Aik(k-1)+Akj(k-1)). Deoarece Aik(k)=Aik(k-1) i Akj(k)=Akj(k-1) se poate utiliza o singur copie a matricii A. Algoritmul lui Floyd este urmtorul: #define NMAX float C[NMAX][NMAX]; /*matricea costurilor*/ float A[NMAX][NMAX]; void FLOYD(int n) { int i,j,k; for (i=1;i<=n;i++) for (j=1;j<=n;j++) A[i][j]=C[i][j];/*iniializare A*/ for (i=1;i<=n;i++) A[i][i]=0; /*iteraiile*/
63

for (k=1;k<=n;k++) for (i=1;i<=n;i++) //pentru toate liniile for (j=1;j<=n;j++) //pentru toate coloanele if (A[i][k]+A[k][j]<A[i][j]) A[i][j]=A[i][k]+A[k][j]; } Pentru a pstra cile minime, se utilizeaz un tablou adiional P, unde P[i][j] ine acel vrf k ce a condus la distana minim A[i][j]. Dac P[i][j]==0, atunci arcul (i, j) este calea minim ntre i si j. Pentru a afia vrfurile intermediare aflate pe calea cea mai scurt ntre i si j se poate proceda conform algoritmului urmtor: void CALE(int i, int j) { int k; if (k!=0){ CALE(i,k); scrie nodul k; CALE(k,j); } } 2.3.Arborele de acoperire de cost minim Fie G =(N, R) un graf neorientat conex. Fiecrei muchii (i, j)R i se asociaz un cost c[i][j]>0. Problema const n a determina un graf parial conex A = (N, T), astfel nct suma costurilor muchiilor din T s fie minim. Se observ imediat c acest graf parial este chiar arborele de acoperire. Algoritmul lui Prim const n urmtoarele: - se pornete cu o submulime W, format din nodul de plecare i mulimea T vid; - la fiecare iteraie, se selecteaz muchia (w, u) cu cel mai mic cost, wW i uN-W. Se adaug u la W i (w, u) la T. In final, W va conine toate nodurile din N, iar T va conine muchiile arborelui de acoperire minimal. void algoritm_PRIM(int n) {

64

W={1}; //se pleac din nodul 1 T={ }; //mulimea vid while (W!=N) { selecteaz muchia (w,u) de cost minim cu wW i uN-W; adaug u la W; adaug (u,w) la T; } } Un alt algoritm aparine lui Kruskal. In acest caz, muchiile sunt ordonate cresctor dup cost. Arborele de acoperire va conine n-1 muchii. La fiecare pas se alege muchia de cost minim care nu formeaz ciclu cu muchiile aflate deja n T. Acest algoritm are urmtoarea descriere: void algoritm_Kruskal(int n) { T={}; while (T nu este arbore de acoperire) { selecteaz muchia (w,u) de cost minim din R; terge (w,u) din R; if ( (w,u) nu creeaz un ciclu in T) adaug (w,u) la T; } } Problema mai dificil n algoritm const n verificarea dac o muchie creeaz ciclu in T. 3.Mersul lucrrii 3.1.S se implemeteze algoritmul lui Dijkstra de gsire a cilor de cost minim dintr-un vrf al unui graf orientat. Se va construi i afia arborele avnd ca rdcin vrful surs. Care este performanta algoritmului n ceea ce privete timpul de calcul?

65

3.2.S se implementeze algoritmul lui Floyd de gsire a cilor de cost minim din oricare vrf al unui graf neorientat. Se vor afia cile de cost minim ntre dou vrfuri, date de la tastatur. Care este performana algoritmului n ceea ce privete timpul de calcul? 3.3..S se implementeze algoritmul lui Prim de gsire a arborelui de acoperire a unui graf neorientat. 3.4.S se implementeze algoritmul lui Kruskal de gsire a arborelui de acoperire a unui graf neorientat. S se fac o comparaie n ceea ce privete timpul de calcul ntre algoritmul lui Kruskal i cel al lui Prim.

66

Lucrarea de laborator nr. 8 TABELE DE DISPERSIE 1. Coninutul lucrrii n lucrare sunt prezentate principalele operaii asupra unei tabele de dispersie: construirea tabelei de dispersie, inserarea unei nregistrri, cutarea unei nregistrri, afiarea nregistrrilor. De asemenea se fac cteva consideraii asupra alegerii funciei de dispersie. 2. Consideraii teoretice 2.1.Tipuri de tabele Tabelul este o colecie de elemente de acelai tip, identificabile prin chei. Elementele sale se mai numesc nregistrri. Tabelele pot fi : - fixe, cu un numr de nregistrri cunoscut dinainte i ordonate; - dinamice Tabelele dinamice pot fi organizate sub form de: - list dinamic simplu sau dublu nlnuit; - arbore de cutare; - tabele de dispersie. Din categoria tabelelor fixe face parte tabelul de cuvinte rezervate dintr-un limbaj de programare. Acesta este organizat ca un tablou de pointeri spre cuvintele rezervate, introduse n ordine alfabetic. Cutarea utilizat este cea binar. Tabelele dinamice organizate sub form de liste au dezavantajul cutrii liniare. Arborele de cutare reduce timpul de cutare. n cazul n care cheile sunt alfanumerice, comparaiile sunt mari consumatoare de timp. Pentru astfel de situaii, cele mai potrivite sunt tabelele de dispersie. 2.2.Funcia de dispersie ( hashing ) Funcia de dispersie este funcia care transform o cheie ntr-un numr natural numit cod de dispersie:
67

f: K -> H unde K este mulimea cheilor, iar H este o mulime de numere naturale. Funcia f nu este injectiv .Dou chei pentru care f(k1)=f(k2) se spune c intr n coliziune, iar nregistrrile respective se numesc sinonime. Asupra lui f se pun dou condiii: - valoarea ei pentru un k K s rezulte ct mai simplu i rapid; - s minimizeze numrul de coliziuni. Un exemplu de funcie de dispersie este urmtoarea: f(k)= (k) mod M unde (k) este o funcie care transform cheia ntr-un numr natural, iar M este un numr natural recomandat a fi prim. Funcia (k) se alege n funcie de natura cheilor. Dac ele sunt numerice, atunci (k)=k. n cazul cheilor alfanumerice, cea mai simpl funcie (k) este suma codurilor ASCII ale caracterelor din componena lor; ca urmare funcia f de calcul a dispersiei este urmtoarea: #define M int f(char *key) { int i,suma; suma=0; for(i=0;i<length(key);i++) suma=suma+*(key+i); return suma%M; }

68

2.3.Tabela de dispersie Rezolvarea coliziunilor se face astfel: toate nregistrrile pentru care cheile intr n coliziune sunt inserate ntr-o list simplu nlnuit. Vor exista astfel mai multe liste, fiecare coninnd nregistrri cu acelai cod de dispersie. Pointerii spre primul element din fiecare list se pstreaz ntr-un tablou, la indexul egal cu codul de dispersie .Ca urmare modelul unei tabele de dispersie este urmtorul:

Un nod al listei are structura urmtoare: typedef struct tip_nod { char *cheie; informaie struct tip_nod *urm; }TIP_NOD; Tabloul HT este declarat astfel: TIP_NOD *HT[M];

69

Iniial el conine pointerii nuli: for(i=0;i<M;i++) HT[i]=0; Cutarea ntr-o tabel de dispersie a unei nregistrri avnd pointerul key la cheia sa, se face astfel: - se calculeaz codul de dispersie: h=f(key); - se caut nregistrarea avnd pointerul key la cheia sa, din lista avnd pointerul spre primul nod HT[h]. Cutarea este liniar: p=HT(h); while(p!=0) { if(strcmp(key,p->cheie)==0) return p; p=p->urm; } return 0; Inserarea unei nregistrri ntr-o tabel de dispersie se face astfel: - se construiete nodul de pointer p, care va conine informaia util i pointerul la cheia nregistrrii: p=(TIP_NOD*)malloc(sizeof(TIP_NOD)); citire_nod(p); - se determin codul de dispersie al nregistrrii: h=f(p->cheie); - dac este prima nregistrare cu codul respectiv, adresa sa este depus n tabelul HT: if(HT[h]==0){ HT[h]=p; p->urm=0; }

70

n caz contrar se verific dac nu cumva mai exist o nregistrare cu cheia respectiv. n caz afirmativ se face o prelucrare a nregistrrii existente ( tergere, actualizare) sau este o eroare (cheie dubl ). Dac nu exist o nregistrare de cheia respectiv, se insereaz n list ca prim element nodul de adres p: q=cautare(p->cheie); if(q==o){ /* nu exista o nregistrare de cheia respectiva */ p->urm=HT[h]; HT[h]=p; } else prelucrare(p,q);/* cheie dubla */ Construirea tabelei de dispersie se face prin inserarea repetat a nodurilor. 2.4.Listarea tuturor nregistrrilor Listarea tuturor nregistrrilor pe coduri se face simplu, conform algoritmului urmtor: for(i=0;i<M;i++) { if(HT[i]!=0) { printf(\nInregistrri avand codul de dispersie=%d\n,i); p=HT[i]; while(p!=0){ afisare(p); p=p->urm; } } 3.Mersul lucrrii 3.1. Se va crea o tabel fix cu cuvintele rezervate din limbajul C. Se va scrie apoi o funcie de cutare binar a unui cuvnt n tabel.

71

3.2. S se implementeze algoritmii prezentai afereni unei tabele de dispersie. nregistrarea va conine datele aferente unui student. Cheia va fi numele i prenumele studentului. Scriei n plus fa de cele prezentate o funcie de tergere a unei nregistrri de cheie dat. 3.3. Scriei un program care s tipreasc identificatorii dintr-o tabel de dispersie n ordine alfabetic. 3.4. S se afieze frecvena de apariie a literelor dintr-un text utiliznd o tabel de dispersie.

72

Lucrarea de laborator nr. 9

METODE GENERALE DE ELABORARE A ALGORITMILOR (I)


1.Coninutul lucrrii n lucrare sunt prezentate principiile metodelor Greedy i backtracking, variantele lor de aplicare i exemple. 2 2.Consideraii teoretice 2.1.Metoda Greedy. Metoda Greedy se aplic urmtoarelor tipuri de probleme: Dintr-o mulime A de n elemente se cere determinarea unei submulimi B care s ndeplineasc anumite condiii pentru a fi acceptat. Numele metodei vine de la urmtorul fapt: se alege pe rnd cte un element din mulimea A i eventual se introduce n soluie. Se menioneaz faptul c o dat ce un element a fost ales el rmne n soluia final, iar dac un element a fost exclus, el nu va mai putea fi reconsiderat pentru includere n soluie. Metoda determin o singur soluie. Exist dou variante de rezolvare a unei probleme cu ajutorul metodei Greedy: a) Varianta I

Se pleac de la mulimea B vid. Se alege din mulimea A un element neales n paii precedeni. Se cerceteaz dac adugarea la soluia parial B conduce la o soluie posibil. n caz afirmativ se adaug elementul respectiv la B.

73

Descrierea variantei este urmtoarea: #define max ... GREEDY1(int A[max], int n, int B[max], int *k) /* A este mulimea de n elemente date; B este mulimea extras de k elemente */ { int x, v, i; *k = 0; /* Mulimea B este vid */ for(i = 0; i<n; i++) { ALEGE (A, n, i, x); /* se alege elementul x dintre elementele A[i], A[i+1], ... A[n1] i se aduce pe poziia i prin interschimbare */ POSIBIL (B, x, v); /* v=1 dac x prin adugare la B conduce la soluie posibil i v=0 n caz contrar */ if(v==1) ADAUGA(B, x, *k); /* se adaug x la B, k indicnd numrul de elemente din B */ } } n varianta I a metodei, funcia ALEGE stabilete criteriul care duce la soluia final. b) Varianta II Se stabilete de la nceput ordinea n care trebuie considerate elementele mulimii A. Apoi se ia pe rnd cte un element n ordinea stabilit i se verific dac prin adugare la soluia parial B anterior construit, se ajunge la o soluie posibil. n caz afirmativ se face adugarea.

74

Descrierea variantei este urmtoarea: #define max ... GREEDY2(int A[max], int n, int B[max], int *k) /* A este mulimea de n elemente date; B este mulimea extras de k elemente */ { int x, v, i; *k = 0; /* soluia vid */ PRELUCRARE(A, n); /* rearanjare vector A */ for(i = 0; i<n; i++) { x=A[i]; POSIBIL (B, x, v); /* v=1 dac prin adugarea lui x la B se ajunge la o soluie posibil i v=0 n caz contrar */ if(v==1) then ADAUGA(B, x, *k); /* se adaug x la mulimea B */ } } Dificultatea elaborrii funciei PRELUCRARE este identic cu cea a funciei ALEGE din varianta precedent. Exemplu: Determinarea arborelui de acoperire de cost minim prin algoritmul lui Prim. Problema a fost enunat n cadrul lucrrii nr.7 paragraful 2.3. Algoritmul const n urmtoarele: a) Iniial se ia arborele ce conine un singur vrf. S-a demonstrat c nu are importan cu care vrf se ncepe; ca urmare se ia vrful 1. Mulimea arcelor este vid. b) Se alege arcul de cost minim, care are un vrf n arborele deja construit, iar cellalt vrf nu aparine arborelui. Se repet n total acest pas de n-1 ori.

75

Pentru evitarea parcurgerii tuturor arcelor grafului la fiecare pas, se ia vectorul v avnd n componente definit astfel: 0 dac vrful i aparine arborelui deja construit U[i] = k dac vrful i nu aparine arborelui deja construit; k este vrful arborelui deja construit a. . muchia (i, k) este de cost minim. Iniial v[1]=0 i v[2]=v[3]=...=v[n]=1, adic iniial arborele este A =({1}, ). /*Algoritmul lui Prim */ #include <stdio.h> #include <conio.h> #define nmax 10 #define MAX 0x7fff void prim(int n,int c[nmax][nmax], int muchii[nmax][2],int *cost) /* n -numrul nodurilor; c - matricea costurilor; muchii-muchiile arborelui de acoperire de cost minim; cost-costul arborelui de acoperire de cost minim */ { int v[nmax]; /* v[i]=0 dac i aparine arborelui; v[i]=j dac i nu aparine arborelui. j este nodul din arbore a.i. muchia (i,j) este de cost minim */ int i,j,k,minim; *cost=0; v[1]=0; for(i=2;i<=n;i++) v[i]=1; /*arborele este ({1},{}) */ /* determinarea celor n-1 muchii */ for(i=1;i<=n-1;i++) { /*determinarea muchiei care se adaug arborelui */ minim=0x7fff; for(k=1;k<=n;k++) if(v[k]!=0) if(c[k][v[k]] < minim) { j=k;
76

minim=c[k][v[k]]; } muchii[i][0]=v[j]; muchii[i][1]=j; *cost=*cost+c[j][v[j]]; /*reactualizare vector v */ v[j]=0; for(k=1;k<=n;k++) if(v[k]!=0) if(c[k][v[k]]>c[k][j]) v[k]=j; } } void main() { int n;/*nr. nodurilor */ int c[nmax][nmax]; /*matricea costurilor */ int muchii[nmax][2];/*muchiile arborelui*/ int i,j,k,cost; printf("\nNr. nodurilor="); scanf("%d",&n); for(i=1;i<=n;i++) for(j=1;j<=n;j++) c[i][j]=MAX; /*citirea costurilor(numere ntregi) */ for(i=1;i<n;i++) { do { printf("\nIntroducei 0 pentru terminare sau nodul adiacent\n"); printf(" cu %2d > ca el =",i); scanf("%d",&j); if(j>0) { printf("\nCostul c[%d][%d]=",i,j); scanf("%d",&c[i][j]); c[j][i]=c[i][j]; /*matricea este simetrica */ } }
77

while(j>0); }; prim(n,c,muchii,&cost); printf("\nCOSTUL ARBORELUI = %d",cost); printf("\nMUCHIILE ARBORELUI COSTUL MUCHIEI\n"); for(i=1;i<=n-1;i++) printf(" %2d - %2d %10d\n",muchii[i][0],muchii[i][1], c[muchii[i][0]][muchii[i][1]]); getch(); } 2.8 Metoda backtracking Metoda backtracking se aplic algoritmilor pentru rezolvarea urmtoarelor tipuri de probleme: Fiind date n mulimi S1, S2, ... Sn, fiecare avnd un numr nrsi de elemente, se cere gsirea elementelor vectorului X =(x1, x2, ... xn) S=S1xS2xSn, astfel nct s fie ndeplinit o anumit relaie (x1, x2, ,xn) ntre elementele sale. Relaia (x1, x2, ,xn) se numete relaie intern, mulimea S=S1xS2xSn se numete spaiul soluiilor posibile, iar vectorul X se numete soluia rezultat. Metoda backtracking determin toate soluiile rezultat ale problemei. Dintre acestea se poate alege una care ndeplinete n plus o alt condiie. Metoda backtracking elimin generarea tuturor celor

nr s
i =1

posibiliti din spaiul soluiilor posibile. n acest scop la generarea vectorului X, se respect urmtoarele condiii: a) xk primete valori numai dac x1, x2, ... ,xk-1 au primit deja valori; b) dup ce se atribuie o valoare lui xk, se verific relaia numit de continuare `(x1, x2, ,xk), care stabilete situaia n care are sens s se treac la calculul lui xk+1. Nendeplinirea condiiei ` exprim faptul c oricum am alege xk+1, xk+2, ... ,xn nu se ajunge la soluia rezultat. n caz de nendeplinire a condiiei `(x1, x2, ,xk), se alege o nou valoare pentru xk Sk i se reia verificarea condiiei `. Dac mulimea de valori xk s-a epuizat, se revine la alegerea altei valori pentru xk-1 .a.m.d. Aceast micorare a lui k d numele metodei, ilustrnd faptul c atunci cnd nu se poate avansa se urmrete
78

napoi secvena curent din soluia posibil. ntre condiia intern i cea de continuare exist o strns legtur. Stabilirea optim a condiiilor de continuare reduce mult numrul de calcule. Algoritmul backtracking este redat sub form nerecursiv astfel: #define nmax ... backtracking_nerecursiv(int n) /* se consider globale mulimile Si i numrul lor de elemente nrsi */ { int x[nmax]; int k, v; k=1; while(k>0) { v=0; while ((mai exist o valoare Sk netestat) & (v==0)) { x[k]=; if ((x[1], x[2], ..., x[k]) este ndeplinit) v=1; } if (v==0) k=k-1; else if (k==n) afiare (x, n); /* afiarea sau eventual prelucrarea soluiei */ else k=k+1; } } Sub form recursiv, algoritmul backtracking poate fi redat astfel: #define nmax ... int x[nmax]; /* se consider globale n, mulimile Si i numrul lor de elemente nrsi */ backtracking_recursiv(int k) { int j; for (j=1;j<=nrsk;j++) {
79

x[k]=Sk[j]; /* al j-lea element din mullimea Sk */ if ((x[1], x[2], ..., x[k]) este ndeplinit) if (k<n) backtracking_recursiv(k+1); else afiare (x, n); /* afiarea sau eventual prelucrarea soluiei */ } } Apelul se face: backtracking_recursiv(1); Exemplu: Problema damelor de ah. Se cere gsirea tuturor soluiilor de aezare pe tabla de ah de n linii i n coloane a n dame, astfel nct ele s nu se atace. Se consider c ele se atac dac sunt pe aceeai linie, coloan sau diagonal. ntruct pe o linie se gsete o singur dam, soluia se prezint sub form de vector x =(x1, x2, ... ,xn), unde xi reprezint coloana pe care se afl dama n linia i. Condiiile de continuare sunt: a) dou dame nu se pot afla pe aceeai coloan, adic: X[ i ] < > X[ j] pentru i < > j ; b) dou dame nu se pot afla pe aceeai diagonal, adic: k - i < > X[ k ] X[ i ] pentru i = 1, 2, ... k - 1. Varianta nerecursiv este urmtoarea: #include <stdio.h> #include <conio.h> #include <stdlib.h> #define nmax 10 void dame_nerecursiv(int n) /* funcia gsete toate aezrile posibile pe o tabl de ah de n*n ptrate pentru ca n dame s nu se atace */ { int x[nmax]; int v; int i,j,k,nr_solutie;

80

nr_solutie=0; k=1;x[k]=0; while(k>0) { /*gsirea unei aezri corecte n linia k */ v=0; while((v==0)&(x[k]<=n-1)) { x[k]++; v=1;i=1; while((i<=k-1)&(v==1)) if((x[k]==x[i])|(abs(k-i)==abs(x[k]-x[i]))) v=0; else i++; } if(v==0) k=k-1; else { if(k==n){ /*afiarea tablei */ nr_solutie++; printf("\nSOLUTIA nr. %d\n",nr_solutie); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) if (x[i]==j) printf("1"); else printf("0"); printf("\n"); }; getch(); } else { k++; x[k]=0; }; } } } void main(void) { int n;
81

printf("\nOrdinul tablei de sah="); scanf("%d",&n); dame_nerecursiv(n); printf("\nSFARSIT"); } Varianta recursiv este urmtoarea: #include <stdio.h> #include <conio.h> #include <stdlib.h> #define nmax 10 /* Varianta recursiv a problemei damelor */ int n; /*dimensiunea (ordinul) tablei de ah */ int nr_solutie; int x[nmax]; int FI(int k) { /*testeaz condiiile de continuare */ int p; for(p=1;p<=k-1;p++) if((x[k]==x[p]) | (abs(k-p) == abs(x[k]-x[p]))) return 0; return 1; } void back_recursiv(int k) { int i,j,p; for(j=1;j<=n;j++) { x[k]=j; if(FI(k)==1) if(k<n) back_recursiv(k+1); else { /*tiprirea soluiei */ nr_solutie++; printf("\nSOLUTIA nr.%d\n",nr_solutie); for(i=1;i<=n;i++) { for(p=1;p<=n;p++)

82

if(x[i]==p) printf("1"); else printf("0"); printf("\n"); }; getch(); } } } void main(void) { printf("\nOrdinul tablei de sah n="); scanf("%d",&n); nr_solutie=0; back_recursiv(1); printf("\nSFARSIT\n"); } 3. Mersul lucrrii Utiliznd metoda Greedy s se rezolve urmtoarele probleme: 3.1 Se dau m vectori V1, V2, ... Vm, care conin n1, n2, ... nm elemente, ordonate cresctor dup o cheie. Se interclaseaz vectorii dai, obinndu-se un vector de lungime n1+n2+...+nm elemente, ordonate cresctor. Se tie c interclasarea a doi vectori care conin n1, respectiv n2 elemente necesit un timp proporional cu suma lungimilor lor. S se determine ordinea optim n care trebuie efectuat interclasarea tuturor vectorilor dai. 3.2 Problema rucsacului. Greutatea maxim care poate fi transportat cu un rucsac este G. Dndu-se n materiale, fiecare avnd greutatea m i costul C pe unitatea de greutate, s se gseasc ce cantitate din fiecare material s fie introdus n rucsac pentru ca s se obin ctigul maxim. Se vor deosebi dou cazuri: a) un material poate fi luat numai n ntregime; b) se poate lua o fraciune din material. 3.3 Problema activitilor. Exist o mulime S=1, 2, 3, ..., n de n activiti care doresc s foloseasc o aceeai resurs (de exemplu o sal de clas). Aceast resurs poate fi folosit de o singur activitate la un anumit moment de timp. Fiecare activitate i are un timp de pornire tpi i un timp de terminare

83

tfi (tpi < tfi). Dac este selectat activitatea i, ea se desfoar pe durata [tpi, tfi). Spunem c activitile i i j sunt compatibile dac duratele lor nu se intersecteaz. S se selecteze o mulime maximal de activiti mutual compatibile. Utiliznd metoda backtracking s se rezolve urmtoarele probleme: 3.4.Colorarea grafurilor. Fiind dat un graf neorientat G =(X, ) unde X este mulimea format din n noduri, iar este mulimea muchiilor i un numr de m culori, se cere s se determine toate colorrile posibile ale nodurilor grafului folosind cele m culori, astfel nct oricare dou noduri adiacente s fie colorate n mod diferit. 3.5.Problema ciclului hamiltonian. Se d un graf conex neorientat G =(X, ) prin matricea costurilor pozitive. Se cere s se determine ciclul hamiltonian de cost minim (ciclul hamiltonian este un ciclu care trece prin toate nodurile). 3.6. Fiind dat o matrice A de n n elemente numere naturale, s se determine cea mai mic sum de n elemente luate din linii diferite i coloane diferite. 3.7.Fiind date o tabl de ah de dimensiune n n ptrate i un cal plasat n ptratul din stnga sus al tablei, s se afieze toate posibilitile de mutare a calului astfel nct s treac o singur dat prin fiecare ptrat al tablei. 3.8.Un labirint este codificat printr-o matrice de n m elemente ale crui culoare sunt reprezentate prin elemente egale cu 1, situate n poziii consecutive pe o aceeai linie sau coloan, celelalte elemente fiind 0. O persoan se gsete n poziia (i, j) din interiorul labirintului. Se cere afiarea tuturor traseelor de ieire din labirint care nu trec de mai multe ori prin acelai loc. 3.9.Se consider o mulime format din n elemente numere ntregi. S se genereze toate submulimile acestei mulimi avnd proprietatea c suma elementelor lor este egal cu S.

84

3.10.Se dau dou mulimi de numere ntregi A i B. S se genereze toate funciile f : A B . Lucrarea de laborator nr. 10.

METODE GENERALE DE ELABORARE A ALGORITMILOR (II) 1. Coninutul lucrrii


n lucrare se prezint esena metodelor Branch and Bound (ramific i mrginete) i a metodei Divide et Impera. 2 2. Consideraii teoretice 2.1. Metoda Branch and Bound Metoda Branch and Bound este nrudit cu metoda backtracking, diferind ordinea de parcurgere a spaiului strilor i a modului de eliminare a subarborilor ce nu pot conduce la rezultat. Metoda Branch and Bound se aplic urmtoarelor tipuri de probleme: Se cunoate starea iniial s0 i starea final sf (starea rezultat). Din starea s0 se ajunge n starea sf printr-o serie de decizii. Ne intereseaz ca numrul strilor intermediare s fie minim. Presupunem c strile reprezint nodurile unui graf, iar arcele indic faptul c o decizie a transformat starea si n starea si+1. Se introduc restricii ca s nu existe mai multe noduri n graf cu aceeai stare, deci graful s nu fie infinit. Astfel graful se reduce la un arbore. Arborele se genereaz pn la prima apariie a nodului final. Exist posibilitatea parcurgerii grafului n adncime sau n lime, ns timpul de ajungere la rezultat este mare. O strategie superioar din punct de vedere al timpului de calcul se obine alegnd dintre descendenii vrfului curent pe cel mai aproape de starea final. Pentru a putea aprecia deprtarea fa de starea final, se va folosi o funcie de cost c definit pe mulimea vrfurilor din arbore. Avnd valorile acestei funcii, vom alege dintre vrfurile descendente ale vrfului curent pe cel cu cost minim. O astfel de parcurgere a grafului se numete Least Cost sau prescurtat LC.

85

Funcia c ideal pentru a msura distana de la vrf la vrful final este:


niv x 1 c ( x ) = + min c(y) y este vrf terminal subarborelui de rdcin } x

rezultat;

dac x este vrf terminal vrful rezultat dac x nu este vrf rezultat Funcia c definit mai sus nu este aplicabil, ntruct calculul ei presupune parcurgerea tuturor vrfurilor arborelui, de fapt urmrindu-se tocmai evitarea acestui lucru. Se observ c dac totui funcia c este calculat, atunci coborrea n arbore la vrful rezultat se face pe un drum deja format din vrfurile x ce au ataat o valoare c(x) egal cu c(rdcin). Neputnd lucra cu funcia c, atunci se definete o aproximare a sa cu una din urmtoarele dou variante: c( x ) = nivelul vrfului x + distana dintre starea curent i starea a) final; c( x ) = costul strii printe + distana dintre starea curent i starea b) final. Nivelul este numrul deciziilor prin care s-a ajuns la configuraia curent. Stabilirea funciei distan este specific fiecrei probleme. De exemplu, n cazul jocului PERSPICO (problema 3.1. din lucrare), distana este numrul de plcue care nu sunt la locul potrivit. Funcia care descrie metoda Branch and Bound este dat mai jos. Lista L conine vrfurile care memoreaz configuraia strilor. Pentru vrful i luat n considerare se memoreaz tatl su n vectorul TATA, permind ca odat ajuni n vrful rezultat s se poat reface drumul de la rdcin la vrful rezultat. RAD este pointerul la vrful ce conine starea iniial.

86

TIP_NOD *RAD; RAD=(TIP_NOD *)malloc(sizeof(TIP_NOD)); /* se depune configuraia iniial la adresa RAD */ void Branch_and_Bound() { TIP_NOD *i, *j; LISTA L; int iesire; i=RAD; LISTA_VIDA(L); for(;;) { while(mai exist vecini j ai lui i neprelucrai) if(j==vrf_rezultat) { afisare_drumul_de_la RAD la j; return; } else { ADAUG(j,L); TATA[j]=i; }; if(ESTE_VIDA(L)) { printf("Nu exista solutie\n"); return; } else i=ELEMENT(L); } } Funciile apelate au urmtoarea semnificaie: LISTAVID(L) iniializeaz lista L ca fiind vid; ESTEVID(L) returneaz 1 dac lista L este vid sau 0 n caz contrar; ELEMENT(L) este funcia ce returneaz un element al listei care are cel mai mic cost c , pentru a ne afla n cazul pacurgerii LC a arborelui strilor; ADAUG(j, L) adaug nodul j la lista L. Din descrierea funciei se observ c atunci cnd un vrf i din lista L devine vrf curent, sunt
87

generai toi descendenii si, acetia fiind pui n lista L. Unul din aceti descendeni va deveni la rndul su pe baza costului c vrf curent, pn cnd se ajunge la vrful rezultat (cel ce conine starea final). 2.9 Metoda Divide et Impera Metoda Divide et Impera const n mprirea repetat a unei probleme n dou sau mai multe probleme de acelai tip i apoi combinarea subproblemelor rezolvate, n final obinndu-se soluia problemei iniiale. Astfel, fie vectorul A =(a1, a2, ..., an), a crui elemente se prelucreaz. Metoda Divide et Impera este aplicabil dac pentru orice p, q, naturali, astfel nct 1 p < q n exist un m [ p + 1, q 1] nct prelucrarea secvenei a p , a p +1 ,..., a q se poate face prelucrnd secvenele

{a , a
p

p +1

,..., a m i

} {a , a
m +1

m+2

,..., a q i apoi prin combinarea rezultatelor

se obine prelucrarea dorit. Metoda Divide et Impera poate fi descris astfel: void DIVIDE_IMPERA(int p,int q,rezultat:alfa) /* p, q reprezinta indicii secvenei care se prelucreaz; alfa reprezint rezultatul */ { int eps, m; rezultat beta,gama; if(abs(q-p)<=eps) PRELUCRARE(p,q,alfa); else { DIVIDE(p,q,m); DIVIDE_IMPERA(p,m,beta); DIVIDE_IMPERA(m+1,q,gama); COMBINA(beta,gama,alfa); } } Apelul funciei Divide et Impera se face astfel: DIVIDE_IMPERA(1, n, alfa);

88

Variabilele i funciile din funcia DIVIDE_IMPERA au urmtoarele semnificaii: eps este lungimea maxim a unei secvene ap, ap+1, ...,aq pentru care prelucrarea se poate face direct; m este indicele intermediar n care secvena ap, ap+1, ...,aq este mprit n dou subsecvene de funcia DIVIDE; beta i gama reprezint rezultatele intermediare obinute n urma prelucrrii subsecvenelor (ap, ap+1, ...,am) i respectiv (am+1, am+2, ...,aq); alfa reprezint rezultatul combinrii rezultatelor intermediare beta i gama; DIVIDE mparte secvena (ap, ap+1, ...,aq) n dou subsecvene (ap, ap+1, ...,am) i (am+1, am+2, ...,aq); COMBIN combin rezultatele beta i gama ale prelucrrii subsecvenelor returnate de procedura DIVIDE, obinnd rezultatul alfa a prelucrrii secvenei iniiale. Exemplu. Sortarea prin interclasare a unui vector de n elemente: /*Program de sortare prin metoda divide et impera */ #include <stdio.h> #include <conio.h> #define nmax 100 int a[nmax]; /* vectorul de sortat */ void afisare(int n) /* afisarea vectorului */ { int i; printf("\n"); for(i=0;i<n;i++) { printf("%5d",a[i]); if(((i+1) % 10)==0)printf("\n"); } printf("\n"); } void comb(int inf,int med,int sup) { int i,j,k,l;

89

int b[nmax]; i=inf;j=med+1;k=inf; while((i<=med)&(j<=sup)) { if(a[i]<=a[j]) { b[k]=a[i]; i++; } else{ b[k]=a[j]; j++; } k++; } for(l=i;l<=med;l++) { /* au ramas elemente in stanga */ b[k]=a[l]; k++; } for(l=j;l<=sup;l++) { /* au ramas elemente in dreapta */ b[k]=a[l]; k++; } /* secventa intre inf si sup este sortata */ for(l=inf;l<=sup;l++) a[l]=b[l]; } void divide_impera(int inf,int sup) { int med; if(inf<sup) { med=(inf+sup) / 2; divide_impera(inf,med); divide_impera(med+1,sup); comb(inf,med,sup); } }
90

void main(void) { int i,n; printf("\nIntroduceti nr.elementelor n="); scanf("%d",&n); printf("\nIntroduceti elementele vectorului\n"); for(i=0;i<n;i++) { printf("a[%d]=",i); scanf("%d",&a[i]); } printf("\nVECTORUL NESORTAT\n"); afisare(n); divide_impera(0,n-1); printf("\nVECTORUL SORTAT\n"); 0 afisare(n); getch(); } 3. Mersul lucrrii Se vor rezolva urmtoarele probleme prin metoda Branch and Bound: 3.1 Jocul PERSPICO. 15 plcue ptrate sunt ncadrate ntr-un cadru de dimensiune 4x4, o poziie fiind liber. Orice plcu vecin cu aceast poziie liber poate fi mutat n locul ei. Cele 15 plcue sunt numerotate de la 1 la 15. Se ncepe dintr-o stare iniial, care corespunde unei distribuii oarecare a celor 15 plcue i a locului liber n cele 16 poziii posibile. Problema const n a trece, folosind mutri posibile, din starea iniial n starea final (fig. 3.1.1). 1 5 9 2 6 3 7 4 8 1 5 9 2 6 3 7 4 8

10 11

10 11 12

13 14 15 12
Exemplu de configuraie iniial

13 14 15
Configuraie final

91

Figura 3.1.1

3.2 Exist urmtorul joc: pe o linie de cale ferat se afl n vagoane unul lng altul, numerotate cu valori distincte din mulimea 1...n. O macara poate lua k vagoane de pe linie i le poate aeza n partea dreapt, la sfritul irului de vagoane, care apoi prin mpingere ajung din nou unul lng altul, n noua ordine creat dup operaia respectiv. Dndu-se ordinea iniial a vagoanelor, se cere s se determine (dac este posibil) numrul minim de operaii pe care trebuie s le efectueze macaraua pentru ca n final vagoanele s se afle n ordine cresctoare 1, 2, ..., n. 3.3 Pe malul unui ru se afl 2n btinai din care n sunt canibali. Acetia doresc s traverseze rul utiliznd o barc care poate transporta cel mult k persoane. Dac pe un mal sau n barc sunt mai muli canibali dect ceilali, atunci canibalii i vor mnca. Cum vor reui s treac toi pe malul opus fr s se mnnce i fr a apela la alte persoane. 3.4 Pe un pod se afl n capre care vin dintr-un sens, cu n capre care vin din sens opus. Acestea nu se pot ocoli, ns fiecare capr poate sri peste o singur capr din grupul opus i desigur poate avansa dac n faa sa este un spaiu liber. Cum reuesc aceste capre s traverseze podul doar prin cele dou micri posibile (avans i sritur). Se vor rezolva urmtoarele probleme Impera: prin metoda Divide et

3.5 Fiind dat un vector ce conine elemente de tip ntreg ordonate cresctor, s se scrie o funcie de cutare a unui element dat n vector, returnndu-se poziia sa. 3.6 Problema turnurilor din Hanoi. Se dau trei tije. Pe una dintre ele sunt aezate n discuri de mrimi diferite, discurile de diametre mai mici fiind aezate peste discurile cu diametre mai mari. Se cere s se mute aceste discuri pe o alt tij, utiliznd tija a treia ca intermediar, cu condiia mutrii a cte unui singur disc i fr a pune un disc de diametru mai mare peste unul cu diametru mai mic.

92

Lucrarea de laborator nr. 11.

METODE GENERALE DE ELABORARE A ALGORITMILOR (III)


1. Coninutul lucrrii

n lucrare sunt prezentate metoda programrii dinamice i metodele euristice. 2 2. Consideraii teoretice

2.1. Metoda programrii dinamice Metoda programrii dinamice se aplic pentru rezolvarea problemelor de optim, pentru care soluia este rezultatul unui ir de decizii secveniale, dependente de cele luate anterior i care ndeplinesc principiul optimalitii. Principiul optimalitii const n urmtoarele: Fie strile s0, s1, ..., sn, n care s0 este starea iniial i sn este starea final, obinute prin deciziile d1, d2, ..., dn (fig. 2.1.1).

Fig. 2.1.2 Stri

Dac di, di+1, ..., dj (1 i < j n) este un ir optim de decizii care transform starea si-1 n starea sj, trecnd prin strile intermediare si, si+1, ..., sj-1 i dac pentru oricare k [i, j-1] rezult c di, di+1, ..., dk i dk+1 dk+2, ..., dj sunt ambele iruri optime de decizii de trecere din starea si-1 n starea sk, respectiv din starea sk n starea si-1, atunci este satisfcut principiul optimalitii. Aplicarea metodei programrii dinamice se face astfel: se verific principiul optimalitii;

93

se scriu relaiile de recuren obinute din regulile de trecere dintr-o stare n alta i se rezolv. Drept exemplu se ia nmulirea optim a unui ir de matrici. R=A1*A2*...*An n care Ai (i=1,n) este de dimensiunile di*di+1. Matricea rezultat R va fi de dimensiunile di*dn+1. Se tie c la nmulirea matricelor Ai i Ai+1 se efectueaz di*di+1*di+2 operaii de nmulire. Dac matricele au dimensiuni diferite, numrul operaiilor de nmulire necesare obinerii matricei rezultat R depinde de ordinea efecturii produselor a cte dou matrice. Se cere gsirea ordinii de asociere pentru care numrul nmulirilor s fie minim. Rezolvarea acestei probleme se va face astfel: Fie Cij numrul minim de nmuliri de elemente pentru calculul produsului Ai*Ai+1*...*Aj pentru 1 i < j n. Se observ c: a) Cii=0 b) Ci,i+1= di*di+1*di+2 c) C1n este valoarea minim cutat. d) este verificat principiul optimalitii. Cij = min {Ci,k+Ck+1,j+di*dk+1*dj+1 1 k j } asocierile fiind de forma (Ai* Ai+1** Ak) * (Ak+1* Ak+2** Aj). Se calculeaz valorile Ci, i+d pentru fiecare nivel d, pn se ajunge la C1,n. Pentru a construi arborele binar care va descrie ordinea efecturii operaiilor, se va reine la fiecare pas indicele k care realizeaz minimul, adic modul de asociere a matricelor. Vrfurile arborelui vor conine limitele subirului de matrice care se asociaz; rdcina va conine (1,n), iar un subarbore care conine n rdcin (i, j) va avea descendeni pe (i, k) i (k+1, j), unde k este valoarea pentru care se realizeaz optimul cerut. n continuare este prezentat programul comentat de rezolvare a acestei probleme. #include <stdio.h> #include <conio.h> #include <alloc.h> #define nmax 10 typedef struct tip_nod{ long ind1,ind2; struct tip_nod *stg,*dr;

94

} TIP_NOD; /*Inmultirea optima a unui sir de matrici A1*A2*...*An */ /* de dimensiuni d1*d2,d2*d3,...,dn*d(n+1) */ void prod_matr(int n,long c[nmax][nmax],int d[nmax+1]) { int i,j,k,l,poz; long min,val; for(i=1;i<=n;i++) c[i][i]=0; for(l=1;l<=n-1;l++) for(i=1;i<=n-l;i++) { j=i+l; min=0x7fffffff; for(k=i;k<=j-1;k++) { val=c[i][k]+c[k+1][j]+ (long)d[i]*d[k+1]*d[j+1]; if(val<min) { min=val;poz=k; }; }; c[i][j]=min; c[j][i]=poz; } } TIP_NOD *constr_arbore(TIP_NOD *p,int i,int j,long c[nmax] [nmax]) { p=(TIP_NOD *)malloc(sizeof(TIP_NOD)); p->ind1=i;p->ind2=j; if(i<j) { p->stg=constr_arbore(p->stg,i,c[j][i],c); p->dr=constr_arbore(p->dr,c[j][i]+1,j,c); } else { p->stg=0; p->dr=0; }; return p;
95

} void postordine(TIP_NOD *p,int nivel) { int i; if(p!=0) { postordine(p->stg,nivel+1); postordine(p->dr,nivel+1); for(i=0;i<=nivel;i++) printf(" "); printf("(%ld,%ld)\n",p->ind1,p->ind2); } } void main(void) { int i,j,n; long c[nmax][nmax]; int d[nmax+1]; /* dimensiunile matricelor */ TIP_NOD *rad; printf("\nIntroduceti nr.matricelor n="); scanf("%d",&n); printf("\nIntroduceti dimensiunile matricelor\n"); for(i=1;i<=n+1;i++) { printf("\nDimensiunea d[%d]=",i); scanf("%d",&d[i]); }; prod_matr(n,c,d); /* Matricea c rezultata in urma calculelor */ printf("\nMATRICEA C\n"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) printf("%6ld",c[i][j]); printf("\n"); } printf("\nNR.MINIM DE INMULTIRI = %ld",c[1] [n]); getch(); rad=0;
96

rad=constr_arbore(rad,1,n,c); printf("\nARBORELE IN POSTORDINE\n"); postordine(rad,0); getch(); } 2.2. Metode euristice Prin algoritm euristic se va nelege un algoritm care furnizeaz o soluie aproximativ, nu neaprat optimal, dar care poate fi implementat uor i d rezultate n timp util, de ordin polinomial. Metodele euristice se aplic pentru rezolvarea unor probleme, la care nu se tie dac admit optim i care accept drept rezultate nu tocmai optimul. Una din idei, este descompunerea procesului de determinare a soluiei n mai multe procese succesive, crora li se caut soluia optimal. Idea nu conduce la obinerea n final n mod sigur a unei soluii optime, ntruct optimizarea local nu implic n general optimizarea global. n problemele practice se pot cuta mai multe soluii aproximative, din care s se aleag cea mai bun. Drept exemplu se prezint problema comis voiajorului: Se d un graf neorientat G =(X, ) n care toate nodurile sunt unite ntre ele printr-o muchie, creia i se asociaz un cost strict pozitiv. Se cere determinarea unui ciclu, care s nceap dintr-un nod i, s treac exact o dat prin toate nodurile i s se ntoarc n nodul iniial. Se cere ca ciclul gsit s aib un cost minim. Soluia optimal a problemei se gsete ntr-un timp de ordin exponenial prin metoda backtracking. Algoritmul euristic prezentat mai jos bazat pe metoda Greedy necesit un timp polinomial. Rezolvarea const n urmtoarele: Dac (v1, v2, ..., vk) este calea deja construit, atunci: dac (v1, v2, ..., vk) = = X, se adaug muchia (vk, v1) i ciclul este ncheiat; dac (v1, v2, ..., vk) X, se adaug muchia care leag vk de un vrf aparinnd lui x, dar neinclus n cale. Pentru c ciclul este o cale nchis, putem considera ca punct de plecare oricare nod. De aceea se pot alege nite noduri de start, se determin pentru fiecare ciclul corespunztor dup strategia descris i se reine ciclul de cost minim dintre ele. n continuare este prezentat programul corespunztor.
97

#include <stdio.h> #include <conio.h> #define nmax 10 /*Problema comis_voiajorului */ void comis_voiajor(int n,int c[nmax][nmax], int i,int ciclu[nmax+1],int *cost) /* n este nr.nodurilor;c este matricea costurilor; i este nodul de start;ciclu contine nodurile din ciclu; cost este costul ciclului */ { int p[nmax]; int k,v,j,vmin; int costmin; for(k=1;k<=n;k++) p[k]=0; *cost=0; p[i]=1;ciclu[1]=i; v=i; /*nodul curent */ for(k=1;k<n;k++) /* se adauga pe rand n-1 muchii */ { costmin=0x7fff; /*gasirea muchiei de cost minim care are nodul de origine v*/ for(j=1;j<=n;j++) if((p[j]==0)&&(c[v][j]<costmin)) { costmin=c[v][j]; vmin=j; } *cost=*cost+costmin; ciclu[k+1]=vmin; p[vmin]=1; v=vmin; } ciclu[n+1]=i; *cost=*cost+c[v][i]; } void main(void) {
98

int i,j,n; int cost_ciclu; int ciclu[nmax+1]; int c[nmax][nmax]; printf("\nNr.nodurilor grafului = "); scanf("%d",&n); for(i=1;i<=n;i++) for(j=1;j<=n;j++) c[i][j]=0x7fff; printf("\nIntroduceti costurile muchiilor care au ca origine"); printf("\n nodul i si celalalt nod mai mare ca i;daca nu mai sunt"); printf("\n astfel de noduri se introduce 0 \n"); for(i=1;i<=n-1;i++) { while(1) { printf("Nodul adiacent lui %d =",i); scanf("%d",&j); if(j!=0){ printf("Costul muchiei (%d, %d)=",i,j); scanf("%d",&c[i][j]); c[j][i]=c[i][j]; } else break; } } i=1; comis_voiajor(n,c,i,ciclu,&cost_ciclu); printf("\nCOSTUL CICLULUI =%d\n",cost_ciclu); printf("\nCICLUL="); for(i=1;i<=n+1;i++) printf("%3d",ciclu[i]); getch(); } 4. Mersul lucrrii

99

Se vor rezolva prin metoda programrii dinamice urmtoarele probleme: 3.1 Determinarea cilor de cost minim ntre oricare dou vrfuri ale unui graf orientat prin algoritmul lui Floyd. (problema 3.2 din lucrarea 7). 3.2 La un concurs de tir, inta este alctuit din cercuri concentrice, numerotate din exterior spre interior. Fiecrui sector determinat de dou cercuri succesive i este ataat o valoare strict pozitiv, reprezentnd punctajul primit de concurent n cazul lovirii acestui sector. S se determine numrul minim de lovituri pe care trebuie s le execute un concurent pentru a obine exact k puncte. 3.3 Se dau dou numere naturale A i B i un vector v care conine n numere naturale. S se determine dac se poate trece din A n B, tiind c singurele operaii permise sunt: a) Adunarea la A a oricte numere din vectorul v; b) Scderea din A a oricte numere din vectorul v; Fiecare numr poate fi adunat, respectiv sczut de mai multe ori. Dac rspunsul la ntrebare este afirmativ, se cere numrul minim de operaii prin care se poate trece din A n B. 3.4 Se dau n segmente aflate pe o dreapt. S se determine cardinalul maxim al unei submulimi de segmente care are proprietile: a) oricare dou numere din submulime nu se intersecteaz; b) submulimea conine primul element. 3.5 Pe o creang de mr, se afl n mere, fiecare caracterizat prin distana hi n cm de la pmnt pn la poziia n care se afl i prin greutatea sa gi n grame. Un culegtor dorete s culeag o cantitate exprimat n grame ct mai mare de mere. Dup ce este cules un mr ntreaga creang devine mai uoar i se ridic n sus cu x cm. Culegtorul ajunge doar la merele aflate la o nlime mai mic sau egal cu d cm. S se determine greutatea maxim de mere care poate fi culeas i ordinea n care sunt culese merele. Se citesc: n, x, d i (gi, hi) i=1...n. S se rezolve prin metode euristice urmtoarele probleme: 3.6 S se gseasc maximul unei funcii f(x) n intervalul [a, b].

100

3.7 Se d un graf neorientat cu n noduri. Se cere s se determine numrul minim de culori necesare pentru a colora nodurile grafului dat, astfel nct dou vrfuri legate printr-o muchie s fie colorate cu culori diferite. 3.8 Se d un graf neorientat cu n noduri. Se cere s se determine o submulime maxim de noduri cu proprietatea c oricare dou noduri din ea nu sunt legate printr-o muchie.

101

Lucrarea de laborator nr. 12.

ALGORITMI FUNDAMENTALI DE SORTARE


1. Coninutul lucrrii n lucrare sunt prezentai algoritmii de sortare prin numrare, prin inserare (direct i shellsort), prin interschimbare (metoda bulelor i quicksort), prin selecie i interclasare. 2. Consideraii teoretice Sortarea const n ordonarea cresctoare sau descresctoare a elementelor unui vector A =(a0, a1, ..., an-1). n practic, problema se ntlnete sub forma sortrii unor articole dup cheie, cheia fiind un cmp din articol. 2.1.Sortarea prin numrare Metoda sortrii prin numrare const n gsirea pentru fiecare element a[i], a numrului de elemente din vector, mai mici ca el. Numerele obinute sunt memorate ntr-un vector c; elementele vectorului de sortat a, sunt iniial atribuite vectorului b. Pe baza vectorului c, elementele lui b vor fi aranjate n vectorul a. Dezavantajul metodei const n utilizarea a doi vectori de lucru. Timpul de prelucrare este de ordinul O(n2). n programul prezentat la paragraful 2.6., funcia sort_numrare red algoritmul de mai sus. 2.2. Sortarea prin inserare Sortarea prin inserare const n urmtoarele: Fie secvena a0 < a1 < a2 ... < aj-1. Inserarea elementului aj n aceast secven const n compararea lui aj cu aj-1, aj-2 ... pn cnd se ajunge la ai < aj; se insereaz aj dup ai, cu meniunea c n prealabil elementele aj-1, aj-2, ..., ai+1 au fost deplasate spre dreapta cu o poziie. Metoda care procedeaz ntocmai se numete inserare direct.

102

Metoda inserrii binare const n cutarea binar a locului unde trebuie inserat elementul aj, avnd n vedere c secvena a0, a1, ..., aj-1 este ordonat cresctor. Tot din categoria inserrii face parte i metoda shell. Pentru a explica metoda se introduce noiunea de h-sortare. Numim h-sortare, sortarea prin inserare direct a urmtoarelor secvene: a0, ah, a2h, .... a1, a1+h, a1+2h, .... . . . ah, a2h, a3h, .... h este numit increment. Metoda shell const n alegerea unui numr de k incremeni h1 > h2 > h3 > ... > hk = 1 i de a face o h1 sortare, urmat de o h2 sortare . a. m. d., n final o 1 sortare. Performaele metodei sunt strns legate de alegerea incremenilor. n exemplul din paragraful 2.6., funcia sort_inserare_direct red algoritmul de inserare direct, iar funcia shell_sort red algoritmul metodei shell. Timpul de prelucrare n cadrul metodei de inserare direct este de ordinul O(n2), iar al metodei shell de ordinul O(n *lnn). De asemenea timpul de prelucrare n cadrul metodei de inserare prin cutare binar este de ordinul O(n *lnn). 2.3.Sortarea prin interschimbare Sortarea prin interschimbare const n modificri succesive de forma ai aj, pn cnd elementele vectorului apar n ordine cresctoare. Din aceast categorie fac parte metoda bulelor i metoda quicksort. Metoda bulelor const n compararea ai cu ai+1; dac ordinea e bun se compar ai+1 cu ai+2; dac ordinea nu e bun se interschimb ai cu ai+1 i apoi se compar ai+1 cu ai+2. Dup prima parcurgere a vectorului, pe ultima poziie ajunge elementul avnd valoarea cea mai mare, dup a doua parcurgere ajunge urmtorul element . a. m. d. Timpul de prelucrare este de ordinul O(n2). Sortarea rapid quicksort a fost creat de Hoare i folosete metoda Divide Et Impera.
103

Principiul metodei este urmtorul: se selecteaz un element din tablou numit pivot i se rearanjeaz vectorul n doi subvectori, astfel nct cel din stnga are toate elementele mai mici dect pivotul, iar cel din dreapta mai mare ca pivotul. Procedeul se reia n subtabloul din stnga i apoi n cel din dreapta . a. m. d. Procedeul se termin cnd se ajunge cu pivotul n extremitatea stng i respectiv dreapt a tabloului iniial. Timpul de prelucrare a metodei quicksort este de ordinul O(n* lnn). n exemplul de la paragraful 2.6., funcia sort_metoda_bulelor red algoritmul de sortri prin metoda bulelor, iar funciile quicksort i quick redau algoritmul sortrii prin metoda quicksort. 2.4.Sortarea prin selecie Sortarea prin selecie direct const n urmtoarele: se afl minimul a j dintre a0, a1, ..., an-1 i se aduce pe poziia zero n vector prin interschimbarea a0 cu aj; apoi procedeul se repet pentru a1, a2, ..., an-1 . a. m. d. Timpul de prelucrare este de ordinul O(n2). In exemplul de la paragraful 2.6., funcia sort_selecie red algoritmul de sortare prin metoda seleciei directe.

104

2.5.Sortarea prin interclasare Metoda sortrii prin interclasare a fost prezentat n cadrul lucrrii nr. 10, drept exemplificare a metodei de elaborare a algoritmilor Divide et Impera 2.6.Exemplu n programul de mai jos sunt implementai algoritmii de sortare prezentai n paragrafele 2.1. 2.5. #include <stdio.h> #include <conio.h> #define nmax 100 /* ALGORITMI DE SORTARE */ void citire_vector(int n,float a[nmax],float b[nmax]) /* CITIRE ELEMENTE VECTOR */ { int i; printf("\nIntroduceti elementele vectorului de sortat\n"); for(i=0;i<n;i++) { printf("a[%d]=",i); scanf("%f",&a[i]); b[i]=a[i]; } } void reconstituire(int n,float a[nmax],float b[nmax]) /* RECONSTITUIREA INITIALA A VECTORULUI a */ { int i; for(i=0;i<n;i++) a[i]=b[i]; }

105

void afisare(int n,float a[nmax]) /* AFISARE VECTOR */ { int i; for(i=0;i<n;i++) { printf("%8.2f",a[i]); if(((i+1) % 10)==0) printf("\n"); } } void sort_numarare(int n,float a[nmax]) /* SORTAREA PRIN NUMARARE */ { int i,j; float b[nmax]; int c[nmax]; for(i=0;i<n;i++) { c[i]=0; /* initializare vector de numarare */ b[i]=a[i]; /* copiere vector a in b */ } /* numarare */ for(j=1;j<n;j++) for(i=0;i<=j-1;i++) if(a[i]<a[j]) c[j]++; else c[i]++; /* rearanjare vector a */ for(i=0;i<n;i++) a[c[i]]=b[i]; } void sort_inserare_directa(int n,float a[nmax]) /* SORTARE PRIN INSERARE DIRECTA */ { int i,j; float x; for(j=1;j<n;j++) {
106

x=a[j]; i=j-1; while((i>=0) && (x<a[i])) { a[i+1]=a[i]; i=i-1; } a[i+1]=x; } } void shell_sort(int n,float a[nmax]) /* SORTAREA PRIN METODA SHELL */ { int i,j,incr; float x; incr=1; while(incr<n) incr=incr*3 + 1; while(incr>=1) { incr=incr / 3; for(i=incr;i<n;i++) { x=a[i];j=i; while(a[j-incr]>x) { a[j]=a[j-incr]; j=j-incr; if(j<incr) break; }; a[j]=x; } } } void sort_metoda_bulelor(int n,float a[nmax]) /* SORTAREA PRIN INTERSCHIMBARE-METODA BULELOR */ {
107

int i,j,gata; float x; j=0; do{ gata=1; j=j+1; for(i=0;i<n-j;i++) if(a[i]>a[i+1]){ gata=0; x=a[i];a[i]=a[i+1];a[i+1]=x; }; }while(gata==0); } void quick(int prim,int ultim,float a[nmax]) { int i,j; float pivot,x; i=prim;j=ultim; pivot=a[(prim + ultim) / 2]; do{ while(a[i]<pivot) i++; while(a[j]>pivot) j--; if(i<=j) { x=a[i];a[i]=a[j];a[j]=x; i++;j--; }; }while(i<=j); if(prim<j) quick(prim,j,a); if(i<ultim) quick(i,ultim,a); } void quicksort(int n,float a[nmax]) /* SORTAREA RAPIDA QUICKSORT */ { quick(0,n-1,a); } void sort_selectie(int n,float a[nmax])
108

/* SORTAREA PRIN SELECTIE */ { int i,j,poz; float x; for(i=0;i<n-1;i++) { x=a[i];poz=i; for(j=i+1;j<n;j++) if(a[j]<x) { x=a[j]; poz=j; } a[poz]=a[i];a[i]=x; } } void main(void) { int i,n; float a[nmax],b[nmax]; clrscr(); printf("\nIntroduceti nr.elementelor n="); scanf("%d",&n); citire_vector(n,a,b); printf("\nVECTORUL NESORTAT\n"); afisare(n,a); printf("\nVECTORUL SORTAT PRIN METODA NUMARARII\n"); sort_numarare(n,a); afisare(n,a); getch(); printf("\nVECTORUL SORTAT PRIN METODA INSERARII DIRECTE\n"); reconstituire(n,a,b); /* a devine vectorul nesortat initial */ sort_inserare_directa(n,a); afisare(n,a); getch();

109

printf("\nVECTORUL SORTAT PRIN METODA SHELL\n"); reconstituire(n,a,b); /* a devine vectorul nesortat initial */ shell_sort(n,a); afisare(n,a); getch(); printf("\nVECTORUL SORTAT PRIN METODA BULELOR\n"); reconstituire(n,a,b); /* a devine vectorul nesortat initial */ sort_metoda_bulelor(n,a); afisare(n,a); getch(); printf("\nVECTORUL SORTAT PRIN METODA QUICKSORT\n"); reconstituire(n,a,b); /* a devine vectorul nesortat initial */ quicksort(n,a); afisare(n,a); getch(); printf("\nVECTORUL SORTAT PRIN METODA SELECTIEI DIRECTE\n"); reconstituire(n,a,b); /* a devine vectorul nesortat initial */ sort_selectie(n,a); afisare(n,a); getch(); } 3. Mersul lucrrii 3.1. Fie tabloul de ntregi: 59174320 Ordonai, fr program, acest ir n ordinea cresctoare a elementelor, folosind cele trei metode elementare de sortare: inseria, metoda bulelor i selecia, artnd la fiecare pas al metodei care este noua configuraie a tabloului. Cte comparaii i cte mutri de elemente au avut loc pentru

110

fiecare metod? Care metod este cea mai potrivit pentru acest tablou de ntregi? Care aranjare a tabloului ar fi fost cea mai defavorabil? 3.2.Descriei algoritmul de sortare prin inserare, la care modificai cutarea liniar cu o cutare binar (n cadrul subtabloului din stnga elementului curent). Calculai i pentru acest nou algoritm (numit sortare prin inserie cu cutare binar) numrul de pai, numrul de comparaii i mutri ale elementelor din list pentru exemplul de la problema 3.1. Devine acest algoritm mai performant? 3.3.Pentru tabloul de elemente reale: -3.1, 0.1, 1.2, 5.7, -0.3, 6, aplicai metodele de ordonare shell-sort i quick-sort, pentru fiecare pas reprezentnd noua configuraie a tabloului. Numrai comparaiile i mutrile de elemente pe care le-ai efectuat. Care algoritm este mai eficient pentru acest tablou? 3.4.Analizai algoritmul de sortare quick-sort i nlocuii varianta prezentat ce folosete recursivitatea, cu o variant nerecursiv. Ce variabile trebuiesc iniializate, cu ce valori i unde are loc saltul n program pentru a se elimina apelul recursiv? 3.5.Care algoritm dintre cei prezentai are nevoie de spaiu de memorie cel mai mare? 3.6.Scriei un algoritm care combin algoritmii de sortare quick-sort pentru obinerea de partiii nesortate de lungime m, apoi utilizai inseria direct pentru terminarea sortrii. 3.7.Adaptai algoritmul quick-sort pentru a determina ntr-un ir de lungime n cu elemente ntregi, al m-lea mai mic element . 3.8.S se sorteze un numr de n elemente numerotate de la 1 la n caracterizate prin anumite relaii de preceden notate (j, k), avnd semnificaia c elementul j precede ca ordine elementul k. Sortarea trebuie s aib ca rezultat o list n care oricare element k este devansat de predecesorul su (sortarea topologic).

111

3.9.S se descrie algoritmul care realizeaz urmtoarele aciuni specifice unui examen de admitere: efectuarea calculului mediilor candidailor la examenul de admitere repartizarea celor admii dup opiuni (se consider c exist m opiuni, fiecare cu un numr dat de candidai admii) i afiarea listei. afiarea n ordinea descresctoare a mediilor a tuturor candidailor neadmii. Se consider c examenul const din dou probe, la medii egale (calculate cu dou zecimale, prin trunchiere), departajarea se face dup nota la prima prob (dac egalitatea se menine, se ia n considerare a doua), admindu-se depirea numrului de locuri n caz de egalitate i dup acest criteriu.

112

Bibliografie:

113