laborator sda

170
CUPRINS PREFAŢĂ................................... .......................................... .... Pag. 2 LISTE SIMPLU ÎNLĂNŢUITE................................ ............. Pag. 5 LISTE CIRCULARE SIMPLU ÎNLĂNŢUITE.................... Pag. 15 LISTE DUBLU ÎNLĂNŢUITE................................ .............. Pag. 22 ARBORI.................................... .......................................... ..... Pag. 30 ARBORI BINARI DE CĂUTARE................................... ....... Pag. 39

Upload: alexa808

Post on 25-Jun-2015

536 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Laborator SDA

CUPRINS

PREFAŢĂ.................................................................................Pag. 2

LISTE SIMPLU ÎNLĂNŢUITE.............................................Pag. 5

LISTE CIRCULARE SIMPLU ÎNLĂNŢUITE....................Pag. 15

LISTE DUBLU ÎNLĂNŢUITE..............................................Pag. 22

ARBORI...................................................................................Pag. 30

ARBORI BINARI DE CĂUTARE..........................................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

Page 2: Laborator SDA

P R E F A Ţ Ă

3

Page 3: Laborator SDA

4

Page 4: Laborator SDA

Lucrarea de laborator nr. 1.

LISTE SIMPLU ÎNLĂNŢUITE

1. Conţinutul lucrării

În lucrare sunt prezentate operaţiile importante asupra listelor simplu înlănţuite şi particularităţile stivelor şi cozilor.

2. Consideraţii teoretice

Lista este o mulţime finită şi ordonată de elemente de acelaşi 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 înlănţuite, putând fi simplu sau dublu înlănţuite.

În continuare se vor prezenta principalele operaţii asupra listelor simplu înlănţuite.

Structura unui nod este următoarea:

typedef struct tip_nod {int cheie; /* câmp neobligatoriu */alte câmpuri de date utile;struct tip_nod urm; /* legătura spre

următorul nod */} TIP_NOD;

Modelul listei simplu înlănţuite este prezentat în fig. 2.1.

5

Page 5: Laborator SDA

Fig. 2.1. Model de listă simplu înlănţuită

Pointerii prim şi ultim vor fi declaraţi astfel:

TIP_NOD *prim, *ultim;

2.1 Crearea unei liste simplu înlănţuite

Crearea unei liste simplu înlănţuite se va face astfel:a) Iniţial lista este vidă:

prim = 0; ultim = 0;

b) Se generează nodul de introdus:

n=sizeof(TIP_NOD);p=(TIP_NOD *)malloc(n); /* rezervare spaţiu de

memorie în heap*/citire date în nodul de adresă p;

c) Se fac legăturile corespunzătoare:

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;

6

Page 6: Laborator SDA

2.2 Accesul la un nod al unei liste simplu înlănţuite

În funcţie de cerinţe, nodurile listei pot fi accesate secvenţial, extrăgând informaţia utilă din ele. O problemă mai deosebită este găsirea unui nod de o cheie dată şi apoi extragerea informaţiei din nodul respectiv. Căutarea nodului după cheie se face liniar, el putând fi prezent sau nu în listă.

O funcţie de căutare a unui nod de cheie “key” va conţine secvenţa de program de mai jos; ea returnează adresa nodului respectiv în caz de găsire sau pointerul NULL în caz contrar:

TIP_NOD *p;p=prim;while( p != 0 ) if (p->cheie == key)

{/* s-a găsit 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 înlănţuită

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;

}

7

Page 7: Laborator SDA

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, făcând legăturile corespunzătoare:

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 având cheia “key”:

TIP_NOD *q;q=prim;while(q!=0) {

if(q->cheie==key) break;q=q->urm;

}

8

Page 8: Laborator SDA

- se inserează nodul de adresă p, făcând legăturile corespunzătoare:

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 înlănţuită

La ştergerea unui nod se vor avea în vedere următoarele probleme: lista poate fi vidă, lista poate conţine un singur nod sau lista poate conţine 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 spaţiului de memorie */if(prim==0) ultim=0;

/* lista a devenit vidă */}

9

Page 9: Laborator SDA

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;/* căutare nod */q1=0; q=prim;while (q!=0)

{if(q->cheie == key) break; /* s-a găsit 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 spaţiu */if( prim==0) ultim=0;

}else {

q1->urm=q->urm;if(q==ultim) ultim=q1;elib_nod(q); /* eliberare spaţiu */

}

10

Page 10: Laborator SDA

2.5 Ştergerea unei liste simplu înlănţuite

În acest caz, se şterge în mod secvenţial fiecare nod:

TIP_NOD *p;while( prim != 0) {

p=prim; prim=prim->ultim;elib_nod(p); /*eliberare spaţiu de memorie */

}ultim=0;

2.6 Stive

Stiva este o listă simplu înlănţuită 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 înlănţuite, operaţiile principale asupra unei stive sunt:

- push - pune un element pe stivă; funcţia se realizează conform paragrafului 2.3.a., adică prin inserarea unui nod înaintea primului;

- pop - scoate elementul din vârful stivei; funcţia se realizează conform paragrafului 2.4.a., adică prin ştergerea primului nod;

- clear - ştergerea stivei; funcţia se realizează conform paragrafului 2.5.

În concluzie, accesul la o stivă se face numai pe la un capăt al său.

11

Page 11: Laborator SDA

2.7. Cozi

Coada este o listă simplu înlănţuită, bazată pe algoritmul FIFO (First In First Out), adică primul element introdus este primul scos. Modelul cozii care va fi avut în vedere în consideraţiileurmătoare, 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 capăt se scoate un element.

Operaţiile importante sunt:- introducerea unui element în coadă - funcţia se realizează prin inserarea după ultimul nod, conform celor prezentate la paragraful 2.3.b.;- scoaterea unui element din coadă – funcţia se realizează prin ştergerea primului nod, conform celor prezentate la paragraful 2.4.a.;- ştergerea cozii – funcţia se realizează conform paragrafului 2.5.

3. Mersul lucrării

3.1.Să se definească şi să se implementeze funcţiile pentru structura de date

typedef stuct { int lungime;struct TIP_NOD *inceput, *curent, *sfarşit;

} LISTA;

având modelul din fig.3.1.

12

Se introduce un element

nou

Page 12: Laborator SDA

3.2.Să se implementeze o listă ca un tablou static ce conţine pointeri la nodurile de informaţie din heap, conform modelului din fig.3.2.

3.3. De la tastatură se citesc cuvinte. Să se creeze o listă simplu înlănţuită ordonată alfabetic, care conţine în noduri cuvintele distincte şi frecvenţa lor de apariţie. Se va afişa conţinutul 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 oricâte locomotive. Să se scrie programul care realizează dispecerizarea locomotivelor din depou.

Programul prelucrează comenzi de intrare în depou a unei locomotive, de ieşire din depou a unei locomotive şi de afişare a locomotivelor din depou.

3.5. Aceeaşi problemă 3.4, cu deosebirea că depoul are intrarea la un capăt şi ieşirea la capătul opus.

3.6. Sortarea topologică.Elementele unei mulţimi M sunt notate cu litere mici din alfabet.

Se citesc perechi de elemente x, y (x, y aparţin mulţimii M) cu semnificaţia

13

Fig.3.1.Modelul listei pentru problema 3.1

Fig.3.2. Model de listă pentru problema 3.2.

Page 13: Laborator SDA

că elementul x precede elementul y. Să se afişeze elementele mulţimii M într-o anumită ordine, încât pentru orice elemente x, y cu proprietatea că x precede pe y, elementul x să fie afişat înaintea lui y.

3.7.Să se scrie programul care creează două liste ordonate crescător după o cheie numerica şi apoi le interclasează.

3.8.Să se conceapă o stuctură dinamică eficientă pentru reprezentarea matricelor rare. Să se scrie operaţii 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 funcţii de calcul a sumei, diferenţei, produsului şi împărţirii a două polinoame.

3.10. Se citeşte de la tastatură o expresie postfixată corectă sintactic. Să se scrie programul de evaluare a sa. Expresia conţine variabile formate dintr-o literă şi operatorii binari +, -, *, /.

14

Page 14: Laborator SDA

Lucrarea de laborator nr. 2.

LISTE CIRCULARE SIMPLU ÎNLĂNŢUITE

1. Conţinutul lucrării

În lucrare sunt prezentate principalele operaţii asupra listelor circulare simplu înlănţuite: crearea, inserarea unui nod, ştergerea unui nod şi ştergerea listei.

2. Consideraţii teoretice

Lista circulară simplu înlănţuită este lista simplu înlănţuită a cărei ultim element este legat de primul element; adică ultim -> urm = prim.

În cadrul listei circulare simplu înlănţuite 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 înlănţuită, principalele operaţii sunt:

crearea; accesul la un nod; inserarea unui nod; ştergerea unui nod, ştergerea listei.

15

Fig. 2.1 Modelul listei circulare simplu înlănţuite

Page 15: Laborator SDA

Structura unui nod este următoarea:

typedef struct tip_nod {int cheie; /* nu este obligatoriu acest câmp */câmpuri;struct tip_nod *urm;

} TIP_NOD;

2.1. Crearea listei circulare simplu înlănţuite

Iniţial lista este vidă:

ptr_nod = 0;

Introducerea în listă a câte unui nod se va face astfel:

/* crearea nodului */n = sizeof(TIP_NOD); /* dimensiunea nodului */p = (TIP_NOD *)malloc(n); /* 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 */}

16

Page 16: Laborator SDA

2.2. Accesul la un nod

Nodurile pot fi accesate secvenţial plecând de la nodul de pointer ptr_nod:

p=ptr_nod;if(p! = 0) /* lista nu este vidă */

do {acceseaază nodul şi preia informaţia;p = p -> urm;

}while (p! = ptr_nod);

sau căutând un nod de cheie dată key; în acest caz o funcţie care va returna pointerul la nodul găsit va conţine următoarea secvenţă de program:

p = ptr_nod;if (p! = 0) /* lista nu este vidă */

do {if ( p -> cheie == key)

{/* s-a găsit nodul *//* nodul are adresa p */

return p; }

p = p -> urm;}

while (p! = ptr_nod);return 0;

2.3. Inserarea unui nod

Se pun următoarele probleme: inserarea înaintea unui nod de cheie dată; inserarea după un nod de cheie dată.

17

Page 17: Laborator SDA

În ambele cazuri se caută nodul de cheie dată având adresa q; dacă există un astfel de nod ,se creează nodul de inserat de adresă p şi se fac legăturile corespunzătoare.

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; /* s-a găsit nodul */

}while (q! = ptr_nod);

se inserează nodul de adresă p;

if (q -> cheie == key) {q1 -> urm = p; p -> urm = q;

}

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);

18

Page 18: Laborator SDA

se inserează nodul de adresă p :

if (q -> cheie == key) {p -> urm =q -> urm;q -> urm = p;

}

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 găsit nodul */

}while (q! = ptr_nod);

se şterge nodul, cu menţiunea 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);

}

19

Page 19: Laborator SDA

2.5. Ştergerea listei

Ştergerea listei circulare simplu înlănţuite 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 lucrării

3.1. Să se definească şi să se implementeze funcţiile pentru structura de date:

struct LISTA_CIRC {int lungime;struct TIP_NOD *început; *curent;

}

având modelul din fig. 3.1.

3.2. De la tastatură se citeşte numărul n şi numele a n copii. Să se simuleze următorul joc: cei n copii stau într-un cerc. Începând cu un anumit

20

Fig.3.1. Modelul listei pentru problema 3.1.

Page 20: Laborator SDA

copil, se numără copiii în sensul acelor de ceasornic. Fiecare al n-lea copil iese din cerc .Câştigă ultimul copil rămas în joc.

3.3. Să se implementeze un buffer circular, care conţine înregistrări cu datele unui student şi asupra căruia acţionează principiul de sincronizare producător-consumator, care constă în următoarele:

a) înregistrările sunt preluate în ordinea producerii lor; b) dacă bufferul nu conţine înregistrări, consumatorul este

întârziat până când producătorul depune o înregistrare; c) dacă bufferul este plin, producătorul este întârziat până

când consumatorul a preluat o înregistrare.

21

Page 21: Laborator SDA

Lucrarea de laborator nr. 3.

LISTE DUBLU ÎNLĂNŢUITE

1. Conţinutul lucrării

În lucrare sunt prezentate principalele operaţii asupra listelor dublu înlănţuite: crearea, inserarea unui nod, ştergerea unui nod, ştergerea listei.

2. Consideraţii teoretice

Lista dublu înlănţuită este lista dinamică între nodurile căreia s-a definit o dublă relaţie: de succesor si de predecesor.

Modelul listei dublu înlănţuite, pentru care se vor da explicaţiile în

continuare, este prezentat în figura 2.1. Tipul unui nod dintr-o listă dublu înlănţuită este definit astfel:

typedef struct tip_nod {cheie; /* nu este obligatoriu */date;struct tip_nod *urm; /* adresa următorului nod */struct tip_nod * prec; /* adresa precedentului nod */

} TIP_NOD;

22

Fig. 2.1 Modelul listei circulare simplu înlănţuite

Page 22: Laborator SDA

Ca şi la lista simplu înlănţuită, principalele operaţii sunt: crearea; accesul la un nod; inserarea unui nod; ştergerea unui nod, ştergerea listei.

Lista dublu înlănţuită va fi gestionată prin pointerii prim şi ultim:

TIP_NOD *prim, *ultim;prim -> prec = 0;ultim -> urm = 0;

2.1. Crearea unei liste dublu înlănţuite

Iniţial 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;

}

23

Page 23: Laborator SDA

2.2. Accesul la un nod

Accesul la un nod se poate face: secvenţial înainte (de la „prim” spre „ultim”):

p = prim;while (p != 0) {

vizitare nod de pointer p; p = p -> urm;

}

secvenţial înapoi ( de la „ultim” spre „prim”):

p = ultim;while (p != 0) {

vizitare nod de pointer p; p = p -> prec;}

pe baza unei chei. Căutarea unui nod de cheie dată key se va face identic ca la lista simplu înlănţuită (lucrarea 1, par. 2.2.).

2.3. Inserarea unui nod

Inserarea unui nod într-o listă dublu înlănţuită se poate face astfel:

a) î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;

}

24

Page 24: Laborator SDA

b) 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;}

c) înaintea unui nod de cheie dată key:După căutarea nodului de cheie key, presupunând 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;

d) după un nod de cheie dată key:După căutarea nodului de cheie key, presupunând 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;

25

Page 25: Laborator SDA

2.4. Ştergerea unui nod

Există următoarele cazuri de ştergere a unui nod din listă:

a) ştergerea primului nod; acest lucru se poate face cu secvenţa 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;

b) ş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 căutarea 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 */

26

Page 26: Laborator SDA

p -> urm -> prec = p -> prec;p -> prec -> urm = p -> urm;elib_nod(p);

}

2.5. Ştergerea listei

Ştergerea întregii liste se realizează ştergând nod cu nod astfel:

TIP_NOD *p;while (prim != 0) {

p = prim; prim = prim -> urm;elib_nod(p);

}ultim = 0;

3. Mersul lucrării

3.1. Să se definească şi să se implementeze funcţiile pentru structura de date:

struct LISTA {int lungime;struct TIP_NOD *început, *curent, *sfârşit;

}având modelul din fig.3.1.

27

Fig. 3.1. Modelul listei pentru problema 3.1.

Page 27: Laborator SDA

3.2. Să se scrie funcţiile pentru realizarea operaţiilor de creare, inserare, ştergere pentru o listă dublu înlănţuită circulară având modelele din fig.3.2.

3.3. De la tastatură se citesc n cuvinte ;să se creeze o listă dublu înlănţuită, care să conţină în noduri cuvintele distincte şi frecvenţa lor de apariţie. Lista va fi ordonată alfabetic. Se vor afişa cuvintele şi frecvenţa lor de apariţie a) în ordine alfabetică crescătoare şi b) în ordine alfabetică descrescătoare.

3.4. Folosind o listă circulară dublu înlănţuită să se simuleze

următorul joc: n copii, ale căror nume se citesc de la tastatură, stau în cerc. Începând cu un anumit copil (numele său se citeşte), se numără copiii în sensul acelor de ceasornic. Fiecare al m-lea copil (m se citeşte) iese din joc. Numărătoarea continuă începând cu următorul copil din cerc. Câştigă jocul ultimul copil rămas în cerc.

3.5. Aceeaşi problemă ca la 3.4., dar numărătoarea se face în sens contrar cu cel al acelor de ceasornic.

28

Fig. 3.2. Modelele de liste pentru problema 3.2.

Page 28: Laborator SDA

Lucrarea de laborator nr. 4.

ARBORI

1. Conţinutul lucrării

În lucrare sunt prezentate operaţiile de bază asupra arborilor binari, binari total echilibraţi şi arborilor oarecare.

2. Consideraţii teoretice

Arborele binar, foarte des întâlnit în aplicaţii, este arborele în care orice nod are cel mult doi descendenţi: fiul stâng şi fiul drept.

Construirea, traversarea şi ştergerea unui arbore binar.

Construirea unui arbore binar se face citind în preordine din fişierul de intrare informaţiile din nodurile arborelui. Subarborii vizi trebuie să fie notaţi cu un semn distinctiv. De exemplu pentru arborele din figura 2.1.1, notând identificatorul pentru arborele vid cu * , introducerea identificatorilor nodurilor se va face astfel:

A B D * G * * * C E * * F * H * *

Fig. 2.1.1. Arbore binar.

Tipul unui nod se declară astfel:

29

Page 29: Laborator SDA

typedef struct tip_nod {char ch; /* identificatorul nodului */informaţie;struct tip_nod *stg, *dr;

} TIP_NOD;

Construirea unui arbore binar se face conform funcţiei de construire, având următoarea 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 informaţie în nod */p->ch=c;p->stg=construire( );p->dr=construire( );

}return p;

}

Apelul funcţiei se va face astfel:

rădăcina = construire ( )

Traversarea unui arbore binar se poate face în cele 3 moduri cunoscute: preordine, inordine, postordine.

În programul următor sunt implementate operaţiile de construcţie şi traversare a unui arbore binar. Nodul conţine numai identificatorul său. Afişarea nodurilor vizitate se face cu indentare.

30

Page 30: Laborator SDA

/* Program de construire şi afişare a arborilor binari */#include <stdio.h>#include <conio.h>#include <alloc.h>typedef struct tip_nod{

int nr.; /*informaţie */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);

}}

31

Page 31: Laborator SDA

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 echilibraţi

Un arbore binar total echilibrat este un arbore binar care îndeplineşte următoarea condiţie: numărul nodurilor unui oricare subarbore stâng diferă cu cel mult 1 în plus faţă de numărul nodurilor subarborelui corespunzător drept. Rezultă că frunzele sale se află pe ultimele două niveluri.

Algoritmul de construire a unui arbore binar total echilibrat cu n noduri, este următorul:

a) un nod este rădăcină;

32

Page 32: Laborator SDA

b) se iau nstg = [n/2] noduri pentru arborele stâng şi se trece la construirea lui (pasul a);

c) se iau cele ndr=n-nstg-1 noduri rămase pentru subarborele drept şi se trece la construirea lui (pasul a).

Pentru oricare nod există relaţia:ndr <= nstg <= ndr + 1

În programul următor este implementat acest algoritm pentru construirea unui arbore binar total echilibrat, citirea informaţiei în noduri făcându-se în preordine.

#include <stdio.h>#include <conio.h>#include <alloc.h>/* ARBORI BINARI TOTAL ECHILIBRATI */

typedef struct tip_nod{int nr;/*informaţie */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);

}

33

Page 33: Laborator SDA

} 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);

34

Page 34: Laborator SDA

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 cărui noduri au mai mult de doi descendenţi.

Un nod are următoarea structură:

typedef struct tip_nod {informaţie;int nr_fii; /*număr de fii */struct tip_nod *adr_fii [maxfii];

/* adresele nodurilor fiu */ } TIP_NOD;

Construirea arborelui se realizează astfel: pentru fiecare nod se citeşte informaţia utilă şi numărul de fii; nodurile citite în postordine şi adresele sunt păstrate într-o stivă până

când apare nodul al cărui 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 rădăcinii, arborele fiind construit.

Traversarea arborelui pe orizontală (nivel după nivel) se va face astfel:

se utilizează o coadă pentru păstrarea adreselor nodurilor ce urmează să fie prelucrate;

iniţial coada este vidă; se introduce în coada adresa rădăcinii; se scoate pe rând din coadă adresa a câte unui nod, se prelucrează

informaţia din nod, iar apoi se introduc adresele fiilor nodului respectiv. Se repetă acest pas până când coada devine vidă.

35

Page 35: Laborator SDA

3. Mersul lucrării

3.1 Se citeşte de la tastatură o expresie matematică în formă postfixată, sub forma unui şir de caractere. Să se construiască arborele corespunzător acestei expresii, fiecare nod conţinând un operator sau un operand.

3.2 Să se tipărească 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 conţine în noduri constantele 0 şi 1 şi operatorii AND, OR, NOT.

3.5 Să se scrie funcţii de pretty-print (tipărire frumoasă) a arborilor.

3.6 Să se scrie funcţii nerecursive pentru traversarea arborilor.

3.7 Arborele genealogic al unei persoane se reprezintă astfel: numele persoanei este cheia nodului rădăcină şi pentru fiecare nod cheia descendentului stâng este numele tatălui, iar a descendentului drept este numele mamei. Se citesc două nume de la tastatură. Ce relaţie 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 înlănţuită.

3.9 Să se scrie un program care să interschimbe subarborele drept cu cel stâng pentru un nod dat.

3.10 Să se scrie o funcţie care determină înălţimea unui arbore binar.3.11 Să se scrie o funcţie care determină numărul de frunze ale unui arbore

binar.

3.12 Să se scrie o funcţie care determină dacă doi arbori binari sunt echivalenţi (arborii binari sunt echivalenţi dacă sunt structural echivalenţi şi datele corespunzătoare nodurilor sunt aceleaşi).

36

Page 36: Laborator SDA

3.13 Să se scrie un program de construire şi traversare a unui arbore oarecare conform indicaţiilor din lucrare (paragraful 2.3.).

37

Page 37: Laborator SDA

Lucrarea de laborator nr. 5.

ARBORI BINARI DE CĂUTARE

1. Conţinutul lucrării

În lucrare sunt prezentate principalele operaţii asupra arborilor binari de căutare: inserare, căutare, ştergere, traversare. De asemenea sunt prezentaţi arborii binari de căutare optimali.

2. Consideraţii teoretice

Arborii binari de căutare sunt des folosiţi pentru memorarea şi regăsirea rapidă a unor informaţii, pe baza unei chei. Fiecare nod al arborelui trebuie să conţină o cheie distinctă.

Structura unui nod al unui arbore binar de căutare este următoarea:

typedef struct tip_nod { tip cheie; informaţii_utile; struct tip_nod *stg, *dr;

} TIP_NOD;

În cele ce urmează, rădăcina arborelui se consideră ca o variabilă

globală:

TIP_NOD *rad;

Structura arborelui de căutare depinde de ordinea de inserare a

nodurilor.

2.1. Inserarea într-un arbore binar de căutare.

Construcţia unui arbore binar de căutare se face prin inserarea a câte unui nod de cheie key. Algoritmul de inserare este următorul:

a) Dacă arborele este vid, se creează un nou nod care este rădăcina, cheia având valoarea key, iar subarborii stâng şi drept fiind vizi.

38

Page 38: Laborator SDA

b) Dacă cheia rădăcinii este egală cu key atunci inserarea nu se poate face întrucât există deja un nod cu această cheie.

c) Dacă cheia key este mai mică decât cheia rădăcinii, se reia algoritmul pentru subarborele stâng (pasul a).

d) Dacă cheia key este mai mare decât cheia rădăcinii, se reia algoritmul pentru subarborele drept (pasul a).

Funcţia nerecursivă de inserare va avea următorul algoritm:

void inserare_nerecursivă (int key){

TIP_NOD *p, *q;int n;/* construcţie nod p*/n=sizeof (TIP_NOD);p=(TIP_NOD*)malloc(n);p->cheie=key;/* introducere informaţie 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 rădăcina arborelui variabilă globală */for ( ; ; )

{if (key<q->cheie)

{ /* căutarea se face în subarborele stâng */if (q->stg==0) { /* inserare */

q->stg=p;return;

}else q=q->stg;

}else if (key>q->cheie)

{ /* căutarea se face în subarborele drept */if (q->dr==0) { /* inserare */

q->dr=p;

39

Page 39: Laborator SDA

return;}

else q=q->dr;}

else { /* cheie dublă */ /* scriere mesaj */ free (p); return;

}}

}

2.2 Căutarea unui nod de cheie dată key într-un arbore binar de căutare.

Căutarea într-un arbore binar de căutare a unui nod de cheie dată se face după un algoritm asemănător cu cel de inserare.

Numărul de căutări optim ar fi dacă arborele de căutare ar fi total echilibrat (numărul de comparaţii maxim ar fi log 2 n – unde n este numărul total de noduri).

Cazul cel mai defavorabil în ceea ce priveşte căutarea este atunci când inserarea se face pentru nodurile având cheile ordonate crescător sau descrescător. În acest caz, arborele degenerează într-o listă.

Algoritmul de căutare este redat prin funcţia următoare:

TIP_NOD *cautare (TIP_NOD *rad, int key)/* funcţia returnează adresa nodului în caz de găsire 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, căutarea începe din rădăcina rad */ p=rad; while (p!=0)

{ if (p->cheie==key) return p; /* s-a

găsit */

40

Page 40: Laborator SDA

else if (key<p->cheie) p=p->stg; /*căutarea se face în subarb.stâng */

else p=p->dr; /* căutarea se face în subarborele drept */

} return 0; /* nu există nod de cheie key */

}

Apelul de căutare este:

p=cautare (rad, key);

rad fiind pointerul spre rădăcina arborelui.

2.3 Ştergerea unui nod de cheie dată într-un arbore binar de

căutare

În cazul ştergerii unui nod, arborele trebuie să-şi păstreze structura de arbore de căutare.

La ştergerea unui nod de cheie dată intervin următoarele cazuri:a) Nodul de şters este un nod frunză. În acest caz, în nodul tată,

adresa nodului fiu de şters (stâng 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 înlocuieşte cu adresa descendentului nodului fiu de şters.

c) Nodul de şters este un nod cu doi descendenţi. În acest caz, nodul de şters se înlocuieşte cu nodul cel mai din stânga al subarborelui drept sau cu nodul cel mai din dreapta al subarborelui stâng.

Algoritmul de ştergere a unui nod conţine următoarele etape: căutarea nodului de cheie key şi a nodului tată corespunzător; determinarea cazului în care se situează nodul de şters.

41

Page 41: Laborator SDA

2.4 Ştergerea unui arbore binar de căutare

Ştergerea unui arbore binar de căutare constă în parcurgerea în postordine a arborelui şi ştergerea nod cu nod, conform funcţiei următoare:

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 căutare

Ca orice arbore binar, un arbore binar de căutare poate fi traversat în cele trei moduri: în preordine, în inordine şi în postordine conform funcţiilor de mai jos:

void preordine (TIP_NOD *p){

if (p!=0) {extragere informaţie din nodul p;preordine (p->stg);preordine (p->dr);

} }

void inordine (TIP_NOD *p){

if (p!=0) {inordine (p->stg);extragere informaţie din p;inordine (p->dr);

} }

void postordine (TIP_NOD *p) {

42

Page 42: Laborator SDA

if (p!=0) { postordine (p->stg); postordine (p->dr); extragere informaţie din nodul p;

} }

Apelul acestor funcţii se va face astfel:preordine(rad);inordine(rad);postordine(rad);

2.6 Arbori binari de căutare optimali

Lungimea drumului de căutare a unui nod cu cheia x, într-un arbore binar de căutare, este nivelul hi al nodului în care se află cheia căutată, în caz de succes sau 1 plus nivelul ultimului nod întâlnit pe drumul căutării fără succes.

Fie S ={ c1, c2, ... , cn } mulţimea cheilor ce conduc la căutarea cu succes (c1 < c2 < ... < cn).

Fie pi probabilitatea căutării cheii ci (i=1,2,...,n).Dacă notăm cu C, mulţimea cheilor posibile, atunci C S reprezintă

mulţimea cheilor ce conduce la căutarea fără succes. Această mulţime o partiţionăm în submulţimile:

k0 – mulţimea cheilor mai mici ca c1;kn – mulţimea cheilor mai mari ca cn;ki (i=1,2,...,n) – mulţimea cheilor în intervalul (ci, ci+1).

Fie qi probabilitatea căutării unei chei din mulţimea ki.Căutarea pentru orice cheie din ki se face pe acelaşi drum; lungimea

drumului de căutare va fi h’i.

Notăm cu L costul arborelui, care reprezintă lungimea medie de căutare:

cu condiţia:

43

hqhp i

n

jj

n

iii

L'

01

Page 43: Laborator SDA

qqiii

Se numeşte arbore optimal, un arbore binar de căutare care pentru anumite valori pi, qi date realizează un cost minim.

Arborii optimali de căutare nu sunt supuşi inserărilor şi eliminărilor.Din punct de vedere al minimizării funcţiei L, în loc de p i şi qi se pot

folosi frecvenţele apariţiei căutărilor 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:

care se poate calcula astfel:

Rezultă că costul arborelui optimal Aij se va putea calcula astfel:

Fie rij valoarea lui k pentru care se obţine minimul din relaţia lui c ij. Nodul cu cheia c[rij] va fi rădăcina subarborelui optimal Aij, iar subarborii săi 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 funcţiei următoare:

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]];

44

101

n

jj

n

ii

qp

j

ikk

j

ikkij

qpq1

n; ..., 2, 1,ipentru nji0pentru qpqq

jjjiij

1,

qc iiii

)(1,min ccqc kjki

jkiijij

ni0pentru

Page 44: Laborator SDA

p->dr=constr_arbore_optimal(r[i][j], j);}

return p;}

2.7 Exemple

Primul program prezintă toate funcţiile descrise în lucrare asupra unui arbore de căutare. Un nod conţine drept informaţie utilă numai cheia, care este un număr întreg.

Al doilea program conţine funcţiile principale asupra unui arbore

binar de căutare optimal.

Exemplul nr.1 (arbori de căutare)

#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);

45

Page 45: Laborator SDA

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) {

46

Page 46: Laborator SDA

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){

47

Page 47: Laborator SDA

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;

}

48

Page 48: Laborator SDA

/* 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); }

}

49

Page 49: Laborator SDA

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);

50

Page 50: Laborator SDA

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

51

Page 51: Laborator SDA

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;

52

Page 52: Laborator SDA

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)

53

Page 53: Laborator SDA

/* 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);

54

Page 54: Laborator SDA

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 lucrării

3.1 De la tastatură se citesc cuvinte ( şiruri de caractere ). Să se scrie un program care creează un arbore de căutare, care conţine în noduri cuvintele şi frecvenţa lor de apariţie. Să se afişeze apoi cuvintele în ordine lexicografică crescătoare şi frecvenţa lor de apariţie.

3.2 Să se implementeze operaţia de interclasare a doi arbori de căutare.

3.3 Să se verifice dacă operaţia de ştergere a unui nod dintr-un arbore de căutare este comutativă ( ştergerea nodurilor x şi y se poate face în orice ordine).

3.4 Se consideră două liste liniare simplu înlănţuite cu câmpurile de informaţie utilă conţinând numere întregi. Să se construiască o listă care conţine reuniunea celor două liste şi în care elementele sunt ordonate crescător. Se va folosi o structură intermediară de tip arbore de căutare. Elementele comune vor apare a o singură dată.

55

Page 55: Laborator SDA

3.5 Se consideră un arbore de căutare care conţine elemente cu informaţia utilă de tip şir de caractere. Să se scrie o funcţie de căutare, inserare şi ştergere a şirului de caractere permiţându-se folosirea şabloanelor, spre exemplu * pentru orice subşir sau ? pentru orice caracter.

3.6 Informaţiile pentru medicamentele unei farmacii sunt: nume medicament, preţ, cantitate, data primirii, data expirării.

Evidenţa medicamentelor se ţine cu un program care are drept structură de date un arbore de căutare după nume medicament. Să se scrie programul care execută următoarele operaţii:

creează arborele de căutare; caută un nod după câmpul nume medicament şi

actualizează câmpurile de informaţie; tipăreşte medicamentele în ordine lexicografică; elimină un nod identificat prin nume medicament; creează un arbore de căutare cu medicamentele care au

data de expirare mai veche decât o dată specificată de la terminal.

3.7 Se va crea un arbore binar de căutare optimal care va avea în noduri cuvintele cheie folosite în limbajul C. Frecvenţele pi şi qi se vor da în funcţie de folosirea cuvintelor cheie în programele exemplu din lucrare.

56

Page 56: Laborator SDA

Lucrarea de laborator nr. 6

REPREZENTAREA ŞI TRAVERSAREA GRAFURILOR

1. Conţinutul lucrării

În lucrare sunt prezentate câteva noţiuni legate de grafuri, modurile de reprezentare şi traversare a lor.

2. Consideraţii teoretice

Noţiuni de bază

Graful orientat sau digraful G =(V, E) este perechea formată din mulţimea V de vârfuri şi mulţimea E V V de arce. Un arc este o pereche ordonată de vârfuri (v, w), unde v este baza arcului, iar w este vârful arcului. In alţi termeni se spune că w este adiacent lui v.

O cale este o succesiune de vârfuri v[1],v[2],…,v[k], astfel că există arcele (v[1],v[2]), (v[2],v[3]),…,(v[k-1],v[k]) în mulţimea arcelor E. Lungimea căii este numărul de arce din cale. Prin convenţie, calea de la un nod la el însuşi are lungimea 0.

O cale este simplă, dacă toate vârfurile, cu excepţia primului şi ultimului sunt distincte între ele.

Un ciclu este o cale de la un vârf la el însuşi.Un graf orientat etichetat este un graf orientat în care fiecare arc şi

/sau vârf 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 vârfurile 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 (V’V’)).

Un graf neorientat sau prescurtat graf G =(N, R) este perechea formată din mulţimea N de noduri şi mulţimea R de muchii. O muchie este o pereche neordonată (v, w)=(w, v) de noduri.

Definiţiile prezentate anterior rămân valabile şi în cazul grafurilor neorientate.

57

Page 57: Laborator SDA

Moduri de reprezentare

Atât grafurile orientate, cât şi cele neorientate se reprezintă frecvent sub două forme: matricea de adiacenţe şi listele de adiacenţe.

Astfel, pentru graful orientat G =(V, E), unde V este mulţimea vârfurilor V ={1,2,…,n},matricea de adiacenţe A va fi definită astfel :

Matricea de adiacenţe etichetată A (sau matricea costurilor) va fi definită astfel :

Matricea de adiacenţe este simetrică pentru grafuri neorientate şi nesimetrică pentru cele orientate.

Matricea de adiacenţe este utilă când se testează frecvent prezenţa sau absenţa unui arc şi este dezavantajoasă când numărul de arce este mult mai mic decât n x n.

Reprezentarea prin liste de adicenţe foloseşte mai bine memoria, dar căutarea arcelor este mai greoaie. În această reprezentare, pentru fiecare nod se păstrează lista arcelor către nodurile adiacente. Întregul graf poate fi reprezentat printr-un tablou indexat după noduri, fiecare intrare în tablou conţinând adresa listei nodurilor adiacente. Lista nodurilor adiacente poate fi dinamică sau statică. Pentru graful din fig.2.2.1, sunt prezentate:

- matricea de adiacenţe în fig.2.2.2.;- lista de adiacenţe dinamică în fig.2.2.3.; - lista de adiacenţe statică în fig.2.2.4.

58

Page 58: Laborator SDA

2.3.Explorarea în lărgime

Explorarea în lărgime constă în următoarele acţiuni: se trece într-o coadă vidă nodul de pornire; se trece extrage din coadă câte un nod care este prelucrat şi

se adaugă toate nodurile adiacente lui neprelucrate. Se repetă acest pas până când coada devine vidă.

59

LISTA

Fig. 2.2.3.Lista de adiacenţe dinamică. Fig. 2.2.4.Lista de adiacenţe statică.

Page 59: Laborator SDA

Algoritmul este următorul:

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; /*iniţializare vector cu zero*/ vizitate[s]=1;/*se vizitează nodul s */ prelucrează informaţia din s; introducere nod s in coada Q; while(coada Q nu este vidă) { extrage următorul nod v din coada Q; for(fiecare nod w adiacent lui v) if(nodul w nu a fost vizitat) { vizitate[w]=1; prelucrează informaţia din w; introducere nod w în coada Q; } } }

2.4.Explorarea in adâncime

La explorarea în adâncime se marchează vizitarea nodului iniţial, după care se vizitează în adâncime, recursiv, fiecare nod adiacent. După vizitarea tuturor nodurilor ce pot fi atinse din nodul de start, parcurgerea se consideră încheiată. Dacă rămân noduri nevizitate, se alege un nou nod şi se repetă procedeul de mai sus.

60

Page 60: Laborator SDA

Algoritmul este următorul:

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=conţinutul nodului din vârful stivei; w=următorul nod adiacent lui v nevizitat; if(există w) { vizitate[w]=1; prelucrează informaţia din w; pune pe stiva ST nodul w; } else pop(ST); /* se şterge nodul v din vârful stivei ST */ } }

3.Mersul lucrării

3.1. Pentru un graf orientat G =(V, E) şi V’V să se găsească subgraful indus G’ =(V’, E’).Elementele din V şi V’ se citesc.

3.2. Să se scrie câte o funcţie de construire pentru un graf G =(V, E), conform celor 3 reprezentări posibile.

3.3. Pentru un graf reprezentat prin matricea de adiacenţe, să se implementeze algoritmii de traversare prezentaţi în paragrafele 2.3. şi 2.4.

61

Page 61: Laborator SDA

3.4. Să se scrie o funcţie 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 funcţie care să verifice dacă graful este conex sau nu.

62

Page 62: Laborator SDA

Lucrarea de laborator nr.7

ALGORITMI PENTRU PRELUCRAREA GRAFURILOR

1.Conţinutul lucrării

In lucrare sunt prezentaţi algoritmii lui Dijkstra şi Floyd pentru găsirea căilor de cost minim între două noduri precizate, respectiv între oricare două noduri ale unui graf şi algoritmii lui Kruskal şi Prim pentru găsirea arborelui de cost minim de acoperire a unui graf.

2.Consideraţii teoretice

2.1.Căile de cost minim dintr-un vârf

Se consideră un graf orientat G =(V, E) etichetat, în care fiecare arc are ca etichetă un număr nenegativ numit cost. Graful se reprezintă în memorie prin matricea de adiacenţe etichetată, care se mai numeşte matricea costurilor.

Fiind date două vârfuri, unul sursă şi unul destinaţie, se cere găsirea drumului de cost minim de la sursă la destinaţie. Algoritmul lui Dijkstra de rezolvare a acestei probleme constă in următoarele:

- se păstrează o mulţime S de vârfuri jV, pentru care există cel puţin un drum de la sursă la j. Iniţial S ={sursa}.

- la fiecare pas, se adaugă la S un vârf a cărui distanţă faţă de un vârf din S este minimă.

Pentru a înregistra căile minime de la sursă la fiecare vârf se utilizează un tablou TATA, în care TATA[k] păstrează vârful anterior lui k pe calea cea mai scurtă.

In descrierea algoritmului se fac următoarele notaţii:- n numărul vârfurilor mulţimii V;- mulţimea 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 distanţelor minime de la sursă la fiecare vârf;

63

Page 63: Laborator SDA

- 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 următorul:

#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 numărul de vârfuri;sursa este vârful sursă */ int i ,j,k,pas; /*iniţializări*/ 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++) {

găseşte vârful k neselectat cu DISTANTE[k] minim; if (minimul anterior găsit==INFINIT) return; S[k]=1; /* se adaugă k la mulţimea 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];

64

Page 64: Laborator SDA

TATA[j]=k; }

} }

Vectorul TATA conţine vârfurile accesibile din vârfurile sursa. El permite reconstruirea drumurilor de la vârful sursă la oricare vârf accesibil. Pentru vârfurile inaccesibile din vârful sursa vom avea S[i]=0 şi DISTANTE[i]=INFINIT.

2.2.Căile de cost minim din oricare vârf

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 vârfuri ale grafului. In continuare se prezintă un algoritm mai simplu, algoritmul lui Floyd.

Algoritmul lui Floyd constă în găsirea costurilor minime între oricare două vârfuri i, jV. Aceste costuri minime se păstrează în matricea A. Matricea A este iniţial egală cu matricea costurilor. Calculul distanţelor minime se face în n iteraţii, n fiind numărul vârfurilor. La iteraţia k, A[i][j] va avea ca valoare cea mai mică distanţă intre i si j pe căi care nu conţin vârfuri peste k (exceptând capetele i si j). Se utilizează formula următoare:

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 următorul:

#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];/*iniţializare A*/

for (i=1;i<=n;i++) A[i][i]=0; /*iteraţiile*/

65

Page 65: Laborator SDA

for (k=1;k<=n;k++) for (i=1;i<=n;i++) //pentru toate liniile

for (j=1;j<=n;j++) //pentru toate coloaneleif (A[i][k]+A[k][j]<A[i][j])

A[i][j]=A[i][k]+A[k][j];}

Pentru a păstra căile minime, se utilizează un tablou adiţional P, unde P[i][j] ţine acel vârf k ce a condus la distanţa minimă A[i][j]. Dacă P[i][j]==0, atunci arcul (i, j) este calea minimă între i si j.

Pentru a afişa vârfurile intermediare aflate pe calea cea mai scurtă între i si j se poate proceda conform algoritmului următor:

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. Fiecărei muchii (i, j)R i se asociază un cost c[i][j]>0. Problema constă în a determina un graf parţial conex A = (N, T), astfel încât suma costurilor muchiilor din T să fie minimă. Se observă imediat că acest graf parţial este chiar arborele de acoperire.

Algoritmul lui Prim constă în următoarele:- se porneşte cu o submulţime W, formată din nodul de plecare şi mulţimea T vidă;- la fiecare iteraţie, 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 conţine toate nodurile din N, iar T va conţine muchiile arborelui de acoperire minimal.

void algoritm_PRIM(int n) {

W={1}; //se pleacă din nodul 1

66

Page 66: Laborator SDA

T={ }; //mulţimea 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 aparţine lui Kruskal. In acest caz, muchiile sunt ordonate crescător după cost. Arborele de acoperire va conţine 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 următoarea 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 lucrării

3.1.Să se implemeteze algoritmul lui Dijkstra de găsire a căilor de cost minim dintr-un vârf al unui graf orientat. Se va construi şi afişa arborele având ca rădăcină vârful sursă. Care este performanta algoritmului în ceea ce priveşte timpul de calcul?

67

Page 67: Laborator SDA

3.2.Să se implementeze algoritmul lui Floyd de găsire a căilor de cost minim din oricare vârf al unui graf neorientat. Se vor afişa căile de cost minim între două vârfuri, date de la tastatură. Care este performanţa algoritmului în ceea ce priveşte timpul de calcul?

3.3..Să se implementeze algoritmul lui Prim de găsire a arborelui de acoperire a unui graf neorientat.

3.4.Să se implementeze algoritmul lui Kruskal de găsire a arborelui de acoperire a unui graf neorientat. Să se facă o comparaţie în ceea ce priveşte timpul de calcul între algoritmul lui Kruskal şi cel al lui Prim.

68

Page 68: Laborator SDA

Lucrarea de laborator nr. 8

TABELE DE DISPERSIE

1. Conţinutul lucrării

În lucrare sunt prezentate principalele operaţii asupra unei tabele de dispersie: construirea tabelei de dispersie, inserarea unei înregistrări, căutarea unei înregistrări, afişarea înregistrărilor. De asemenea se fac câteva consideraţii asupra alegerii funcţiei de dispersie.

2. Consideraţii teoretice

2.1.Tipuri de tabele

Tabelul este o colecţie de elemente de acelaşi tip, identificabile prin chei. Elementele sale se mai numesc înregistrări.

Tabelele pot fi :- fixe, cu un număr de înregistrări cunoscut dinainte şi ordonate; - dinamice

Tabelele dinamice pot fi organizate sub formă de:- listă dinamică simplu sau dublu înlănţuită;- arbore de căutare;- 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ă. Căutarea utilizată este cea binară.

Tabelele dinamice organizate sub formă de liste au dezavantajul căutării liniare. Arborele de căutare reduce timpul de căutare. În cazul în care cheile sunt alfanumerice, comparaţiile sunt mari consumatoare de timp. Pentru astfel de situaţii, cele mai potrivite sunt tabelele de dispersie.

2.2.Funcţia de dispersie ( hashing )

Funcţia de dispersie este funcţia care transformă o cheie într-un număr natural numit cod de dispersie:

69

Page 69: Laborator SDA

f: K -> H unde K este mulţimea cheilor, iar H este o mulţime de numere naturale.

Funcţia f nu este injectivă .Două chei pentru care f(k1)=f(k2) se spune că intră în coliziune, iar înregistrările respective se numesc sinonime.

Asupra lui f se pun două condiţii:- valoarea ei pentru un k K să rezulte cât mai simplu şi rapid;- să minimizeze numărul de coliziuni.

Un exemplu de funcţie de dispersie este următoarea:f(k)=(k) mod M

unde (k) este o funcţie care transformă cheia într-un număr natural, iar M este un număr natural recomandat a fi prim.

Funcţia (k) se alege în funcţie de natura cheilor. Dacă ele sunt numerice, atunci (k)=k. În cazul cheilor alfanumerice, cea mai simplă funcţie (k) este suma codurilor ASCII ale caracterelor din componenţa lor; ca urmare funcţia f de calcul a dispersiei este următoarea:

#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;}

70

Page 70: Laborator SDA

2.3.Tabela de dispersie

Rezolvarea coliziunilor se face astfel: toate înregistrările pentru care cheile intră în coliziune sunt inserate într-o listă simplu înlănţuită. Vor exista astfel mai multe liste, fiecare conţinând înregistrări cu acelaşi cod de dispersie. Pointerii spre primul element din fiecare listă se păstrează într-un tablou, la indexul egal cu codul de dispersie .Ca urmare modelul unei tabele de dispersie este următorul:

Un nod al listei are structura următoare:

typedef struct tip_nod {char *cheie;informaţiestruct tip_nod *urm;

}TIP_NOD;

Tabloul HT este declarat astfel:

TIP_NOD *HT[M];

71

Page 71: Laborator SDA

Iniţial el conţine pointerii nuli:

for(i=0;i<M;i++)HT[i]=0;

Căutarea într-o tabelă de dispersie a unei înregistrări având pointerul key la cheia sa, se face astfel:- se calculează codul de dispersie:

h=f(key);

- se caută înregistrarea având pointerul key la cheia sa, din lista având pointerul spre primul nod HT[h].Căutarea este liniară:

p=HT(h);while(p!=0) {

if(strcmp(key,p->cheie)==0) return p;p=p->urm;

}return 0;

Inserarea unei înregistrări într-o tabelă de dispersie se face astfel:- se construieşte nodul de pointer p, care va conţine informaţia utilă şi pointerul la cheia înregistrării:

p=(TIP_NOD*)malloc(sizeof(TIP_NOD));citire_nod(p);

- se determină codul de dispersie al înregistrării:

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;

}

72

Page 72: Laborator SDA

în caz contrar se verifică dacă nu cumva mai există o înregistrare cu cheia respectivă. În caz afirmativ se face o prelucrare a înregistrării 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 înregistrărilor

Listarea tuturor înregistrărilor pe coduri se face simplu, conform algoritmului următor:

for(i=0;i<M;i++) {

if(HT[i]!=0){

printf(“\nInregistrări avand codul de dispersie=%d\n”,i);p=HT[i];while(p!=0){

afisare(p);p=p->urm;

} }

3.Mersul lucrării

3.1. Se va crea o tabelă fixă cu cuvintele rezervate din limbajul C. Se va scrie apoi o funcţie de căutare binară a unui cuvânt în tabelă.

73

Page 73: Laborator SDA

3.2. Să se implementeze algoritmii prezentaţi aferenţi unei tabele de dispersie. Înregistrarea va conţine datele aferente unui student. Cheia va fi numele şi prenumele studentului. Scrieţi în plusfaţă de cele prezentate o funcţie de ştergere a unei înregistrări de cheie dată.

3.3. Scrieţi un program care să tipărească identificatorii dintr-o tabelă de dispersie în ordine alfabetică.

3.4. Să se afişeze frecvenţa de apariţie a literelor dintr-un text utilizând o tabelă de dispersie.

74

Page 74: Laborator SDA

Lucrarea de laborator nr. 9

METODE GENERALE DE ELABORARE A ALGORITMILOR (I)

1.Conţinutul lucrării

În lucrare sunt prezentate principiile metodelor Greedy şi backtracking, variantele lor de aplicare şi exemple.

2.Consideraţii teoretice

2.1.Metoda Greedy.

Metoda Greedy se aplică următoarelor tipuri de probleme:Dintr-o mulţime A de n elemente se cere determinarea unei

submulţimi B care să îndeplinească anumite condiţii pentru a fi acceptată.Numele metodei vine de la următorul fapt: se alege pe rând câte un

element din mulţimea A şi eventual se introduce în soluţie.Se menţionează faptul că o dată ce un element a fost ales el rămâne

în soluţia finală, iar dacă un element a fost exclus, el nu va mai putea fi reconsiderat pentru includere în soluţie.

Metoda determină o singură soluţie.Există două variante de rezolvare a unei probleme cu ajutorul

metodei Greedy:

a) Varianta I

Se pleacă de la mulţimea B vidă. Se alege din mulţimea A un element neales în paşii precedenţi. Se cercetează dacă adăugarea la soluţia parţială B conduce la o soluţie posibilă. În caz afirmativ se adaugă elementul respectiv la B.

75

Page 75: Laborator SDA

Descrierea variantei este următoarea:

#define max ...GREEDY1(int A[max], int n, int B[max], int *k)/* A este mulţimea de n elemente date;

B este mulţimea extrasă de k elemente */{ int x, v, i;

*k = 0; /* Mulţimea 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[n-1] şi se aduce pe poziţia i prin interschimbare */POSIBIL (B, x, v);/* v=1 dacă x prin adăugare la B conduce la soluţie posibilă şi

v=0 în caz contrar */ if(v==1) ADAUGA(B, x, *k);

/* se adaugă x la B, k indicând numărul de elemente din B */

} }

În varianta I a metodei, funcţia ALEGE stabileşte criteriul care duce la soluţia finală.

b) Varianta IISe stabileşte de la început ordinea în care trebuie considerate

elementele mulţimii A. Apoi se ia pe rând câte un element în ordinea stabilită şi se verifică dacă prin adăugare la soluţia parţială B anterior construită, se ajunge la o soluţie posibilă. În caz afirmativ se face adăugarea.

76

Page 76: Laborator SDA

Descrierea variantei este următoarea:

#define max ...GREEDY2(int A[max], int n, int B[max], int *k)/* A este mulţimea de n elemente date;

B este mulţimea extrasă de k elemente */{ int x, v, i;

*k = 0; /* soluţia vidă */PRELUCRARE(A, n); /* rearanjare vector A */

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

x=A[i];POSIBIL (B, x, v);/* v=1 dacă prin adăugarea lui x la B se ajunge la o soluţie posibilă şi

v=0 în caz contrar */ if(v==1) then ADAUGA(B, x, *k);

/* se adaugă x la mulţimea B */}

}

Dificultatea elaborării funcţiei PRELUCRARE este identică cu cea a funcţiei ALEGE din varianta precedentă.

Exemplu: Determinarea arborelui de acoperire de cost minim prin algoritmul lui Prim.

Problema a fost enunţată în cadrul lucrării nr.7 paragraful 2.3. Algoritmul constă în următoarele:

a) Iniţial se ia arborele ce conţine un singur vârf. S-a demonstrat că nu are importanţă cu care vârf se începe; ca urmare se ia vârful 1. Mulţimea arcelor este vidă.

b) Se alege arcul de cost minim, care are un vârf în arborele deja construit, iar celălalt vârf nu aparţine arborelui. Se repetă în total acest pas de n-1 ori.

77

Page 77: Laborator SDA

k

0 U[i]

Pentru evitarea parcurgerii tuturor arcelor grafului la fiecare pas, se ia vectorul v având n componente definit astfel:

dacă vârful i aparţine arborelui deja construit

dacă vârful i nu aparţine arborelui deja construit; k este vârful arborelui deja construit a. î. muchia (i, k) este de cost minim.

Iniţial v[1]=0 şi v[2]=v[3]=...=v[n]=1, adică iniţial 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 -numărul 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 aparţine arborelui;

v[i]=j dacă i nu aparţine 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;

78

Page 78: Laborator SDA

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("\nIntroduceţi 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 */ }

}

79

Page 79: Laborator SDA

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 următoarelor tipuri de probleme:

Fiind date n mulţimi S1, S2, ... Sn, fiecare având un număr nrsi de elemente, se cere găsirea elementelor vectorului X =(x1, x2, ... xn) Є S=S1xS2x…Sn, astfel încât să fie îndeplinită o anumită relaţie φ(x1, x2, … ,xn) între elementele sale.

Relaţia φ(x1, x2, … ,xn) se numeşte relaţie internă, mulţimea S=S1xS2x…Sn se numeşte spaţiul soluţiilor posibile, iar vectorul X se numeşte soluţia rezultat.

Metoda backtracking determină toate soluţiile rezultat ale problemei. Dintre acestea se poate alege una care îndeplineşte în plus o altă condiţie.

Metoda backtracking elimină generarea tuturor celor

posibilităţi din spaţiul soluţiilor posibile. În acest scop la generarea vectorului X, se respectă următoarele condiţii:

a) xk primeşte valori numai dacă x1, x2, ... ,xk-1 au primit deja valori;

b) după ce se atribuie o valoare lui xk, se verifică relaţia numită de continuare φ`(x1, x2, … ,xk), care stabileşte situaţia în care are sens să se treacă la calculul lui xk+1. Neîndeplinirea condiţiei φ` exprimă faptul că oricum am alege xk+1, xk+2, ... ,xn nu se ajunge la soluţia rezultat. În caz de neîndeplinire a condiţiei φ`(x1, x2, … ,xk), se alege o nouă valoare pentru xk

Є Sk şi se reia verificarea condiţiei φ`. Dacă mulţimea de valori xk s-a epuizat, se revine la alegerea altei valori pentru xk-1 ş.a.m.d. Această micşorare a lui k dă numele metodei, ilustrând faptul că atunci când nu se

80

Page 80: Laborator SDA

poate avansa se urmăreşte înapoi secvenţa curentă din soluţia posibilă. Între condiţia internă şi cea de continuare există o strânsă legătură. Stabilirea optimă a condiţiilor de continuare reduce mult numărul de calcule.

Algoritmul backtracking este redat sub formă nerecursivă astfel:

#define nmax ...backtracking_nerecursiv(int n)/* se consideră globale mulţimile Si şi numărul 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) afişare (x, n); /* afişarea sau eventual prelucrarea soluţiei */

else k=k+1; }

}

Sub formă recursivă, algoritmul backtracking poate fi redat astfel:

#define nmax ...int x[nmax];

/* se consideră globale n, mulţimile Si şi numărul lor de elemente nrsi */backtracking_recursiv(int k){

int j;for (j=1;j<=nrsk;j++)

{

81

Page 81: Laborator SDA

x[k]=Sk[j]; /* al j-lea element din mullţimea Sk */if (φ(x[1], x[2], ..., x[k]) este îndeplinită)

if (k<n) backtracking_recursiv(k+1);else afişare (x, n); /* afişarea sau eventual prelucrarea soluţiei */

} }

Apelul se face:

backtracking_recursiv(1);

Exemplu: Problema damelor de şah.Se cere găsirea tuturor soluţiilor de aşezare pe tabla de şah de n linii

şi n coloane a n dame, astfel încât ele să nu se atace. Se consideră că ele se atacă dacă sunt pe aceeaşi linie, coloană sau diagonală.

Întrucât pe o linie se găseşte o singură damă, soluţia se prezintă sub formă de vectorx =(x1, x2, ... ,xn), unde xi reprezintă coloana pe care se află dama în linia i.

Condiţiile de continuare sunt:a) două dame nu se pot afla pe aceeaşi coloană, adică:

b) două dame nu se pot afla pe aceeaşi diagonală, adică:

Varianta nerecursivă este următoarea:

#include <stdio.h>#include <conio.h>#include <stdlib.h>#define nmax 10void dame_nerecursiv(int n)/* funcţia găseşte toate aşezările posibile pe o tablăde şah de n*n pătrate pentru ca n dame să nu se atace */{int x[nmax];int v;int i,j,k,nr_solutie;

nr_solutie=0;

82

; j ipentru jX iX

Page 82: Laborator SDA

k=1;x[k]=0;while(k>0){/*găsirea unei aşezări 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){/*afişarea 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;printf("\nOrdinul tablei de sah=");

83

Page 83: Laborator SDA

scanf("%d",&n);dame_nerecursiv(n);printf("\nSFARSIT");

}

Varianta recursivă este următoarea:

#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ă condiţiile 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 { /*tipărirea soluţiei */ nr_solutie++; printf("\nSOLUTIA nr.%d\n",nr_solutie); for(i=1;i<=n;i++)

{ for(p=1;p<=n;p++) if(x[i]==p) printf("1");

84

Page 84: Laborator SDA

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 lucrării

Utilizând metoda Greedy să se rezolve următoarele probleme:3.1 Se dau m vectori V1, V2, ... Vm, care conţin n1, n2, ... nm elemente,

ordonate crescător după o cheie. Se interclasează vectorii daţi, obţinându-se un vector de lungime n1+n2+...+nm elemente, ordonate crescător. Se ştie că interclasarea a doi vectori care conţin n1, respectiv n2 elemente necesită un timp proporţional cu suma lungimilor lor. Să se determine ordinea optimă în care trebuie efectuată interclasarea tuturor vectorilor daţi.

3.2 Problema rucsacului. Greutatea maximă care poate fi transportată cu un rucsac este G. Dându-se n materiale, fiecare având greutatea m şi costul C pe unitatea de greutate, să se găsească ce cantitate din fiecare material să fie introdus în rucsac pentru ca să se obţină câştigul maxim. Se vor deosebi două cazuri:

a) un material poate fi luat numai în întregime;b) se poate lua o fracţiune din material.

3.3 Problema activităţilor. Există o mulţime S=1, 2, 3, ..., n de n activităţi care doresc să folosească o aceeaşi 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 tfi (tpi < tfi). Dacă este selectată activitatea i, ea se desfăşoară pe

85

Page 85: Laborator SDA

durata [tpi, tfi). Spunem că activităţile i şi j sunt compatibile dacă duratele lor nu se intersectează. Să se selecteze o mulţime maximală de activităţi mutual compatibile.

Utilizând metoda backtracking să se rezolve următoarele probleme:

3.4.Colorarea grafurilor. Fiind dat un graf neorientat G =(X, Γ) unde X este mulţimea formată din n noduri, iar Γ este mulţimea muchiilor şi un număr de m culori, se cere să se determine toate colorările posibile ale nodurilor grafului folosind cele m culori, astfel încât 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 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 pătrate şi un cal plasat în pătratul din stânga sus al tablei, să se afişeze toate posibilităţile de mutare a calului astfel încât să treacă o singură dată prin fiecare pătrat al tablei.

3.8.Un labirint este codificat printr-o matrice de elemente ale cărui culoare sunt reprezentate prin elemente egale cu 1, situate în poziţii consecutive pe o aceeaşi linie sau coloană, celelalte elemente fiind 0. O persoană se găseşte în poziţia (i, j) din interiorul labirintului. Se cere afişarea tuturor traseelor de ieşire din labirint care nu trec de mai multe ori prin acelaşi loc.

3.9.Se consideră o mulţime formată din n elemente numere întregi. Să se genereze toate submulţimile acestei mulţimi având proprietatea că suma elementelor lor este egală cu S.

3.10.Se dau două mulţimi de numere întregi A şi B. Să se genereze toate funcţiile .

Lucrarea de laborator nr. 10.

86

Page 86: Laborator SDA

METODE GENERALE DE ELABORARE A ALGORITMILOR (II)

1. Conţinutul lucrării

În lucrare se prezintă esenţa metodelor “Branch and Bound” (ramifică şi mărgineşte) şi a metodei “Divide et Impera”.

2. Consideraţii teoretice

2.1. Metoda “Branch and Bound”

Metoda “Branch and Bound” este înrudită cu metoda backtracking, diferind ordinea de parcurgere a spaţiului stărilor şi a modului de eliminare a subarborilor ce nu pot conduce la rezultat.

Metoda “Branch and Bound” se aplică următoarelor tipuri de probleme: Se cunoaşte starea iniţială s0 şi starea finală sf (starea rezultat). Din starea s0 se ajunge în starea sf printr-o serie de decizii. Ne interesează ca numărul stărilor intermediare să fie minim.

Presupunem că stările reprezintă nodurile unui graf, iar arcele indică faptul că o decizie a transformat starea s i în starea si+1. Se introduc restricţii ca să nu existe mai multe noduri în graf cu aceeaşi stare, deci graful să nu fie infinit. Astfel graful se reduce la un arbore. Arborele se generează până la prima apariţie a nodului final.

Există posibilitatea parcurgerii grafului în adâncime sau în lăţime, însă timpul de ajungere la rezultat este mare. O strategie superioară din punct de vedere al timpului de calcul se obţine alegând dintre descendenţii vârfului curent pe cel mai aproape de starea finală. Pentru a putea aprecia depărtarea faţă de starea finală, se va folosi o funcţie de cost c definită pe mulţimea vârfurilor din arbore. Având valorile acestei funcţii, vom alege dintre vârfurile descendente ale vârfului curent pe cel cu cost minim. O astfel de parcurgere a grafului se numeşte “Least Cost” sau prescurtat “LC”.

Funcţia c ideală pentru a măsura distanţa de la vârf la vârful final este:

87

Page 87: Laborator SDA

x de uisubarborel

terminal vârfestey c(y)min

1

)(

rădăcină

nivx

xc

rezultat;

dacă x este vârf terminal vârful rezultat

dacă x nu este vârf rezultat

Funcţia c definită mai sus nu este aplicabilă, întrucât calculul ei presupune parcurgerea tuturor vârfurilor arborelui, de fapt urmărindu-se tocmai evitarea acestui lucru.

Se observă că dacă totuşi funcţia c este calculată, atunci coborârea în arbore la vârful rezultat se face pe un drum deja format din vârfurile x ce au ataşată o valoare c(x) egală cu c(rădăcină).

Neputând lucra cu funcţia c, atunci se defineşte o aproximare a sa cu una din următoarele două variante:

a) nivelul vârfului x + distanţa dintre starea curentă şi starea finală;

b) costul stării părinte + distanţa dintre starea curentă şi starea finală.

Nivelul este numărul deciziilor prin care s-a ajuns la configuraţia curentă.

Stabilirea funcţiei “distanţă” este specifică fiecărei probleme. De exemplu, în cazul jocului PERSPICO (problema 3.1. din lucrare), distanţa este numărul de plăcuţe care nu sunt la locul potrivit.

Funcţia care descrie metoda “Branch and Bound” este dată mai jos. Lista L conţine vârfurile care memorează configuraţia stărilor. Pentru vârful i luat în considerare se memorează tatăl său în vectorul TATA, permiţând ca odată ajunşi în vârful rezultat să se poată reface drumul de la rădăcină la vârful rezultat.

RAD este pointerul la vârful ce conţine starea iniţială.

TIP_NOD *RAD;RAD=(TIP_NOD *)malloc(sizeof(TIP_NOD));/* se depune configuraţia iniţială la adresa RAD */void Branch_and_Bound()

88

Page 88: Laborator SDA

{TIP_NOD *i, *j;LISTA L;int iesire;i=RAD;LISTA_VIDA(L);for(;;)

{while(mai există vecini j ai lui i neprelucraţi)

if(j==vârf_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);}

}

Funcţiile apelate au următoarea semnificaţie:LISTAVIDĂ(L) – iniţializează lista L ca fiind vidă;ESTEVIDĂ(L) –– returnează 1 dacă lista L este vidă sau 0 în

caz contrar;ELEMENT(L) –– este funcţia ce returnează un element al

listei care are cel mai mic cost , pentru a ne afla în cazul pacurgerii LC a arborelui stărilor;

ADAUG(j, L) ––– adaugă nodul j la lista L. Din descrierea funcţiei se observă că atunci când un vârf i din lista L devine vârf curent, sunt generaţi toţi descendenţii săi, aceştia fiind puşi în lista L. Unul din aceşti descendenţi va deveni la rândul său pe baza costului vârf curent, până când se ajunge la vârful rezultat (cel ce conţine starea finală).

89

Page 89: Laborator SDA

2.9 Metoda “Divide et Impera”

Metoda “Divide et Impera” constă în împărţirea repetată a unei probleme în două sau mai multe probleme de acelaşi tip şi apoi combinarea subproblemelor rezolvate, în final obţinându-se soluţia problemei iniţiale.

Astfel, fie vectorul A =(a1, a2, ..., an), a cărui elemente se prelucrează. Metoda “Divide et Impera” este aplicabilă dacă pentru orice p, q, naturali, astfel încât există un încât prelucrarea

secvenţei se poate face prelucrând secvenţele

şi şi apoi prin combinarea

rezultatelor se obţine prelucrarea dorită.Metoda “Divide et Impera” poate fi descrisă astfel:

void DIVIDE_IMPERA(int p,int q,rezultat:alfa)/* p, q reprezinta indicii secvenţei 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 funcţiei “Divide et Impera” se face astfel:

DIVIDE_IMPERA(1, n, alfa);

Variabilele şi funcţiile din funcţia DIVIDE_IMPERA au următoarele semnificaţii:

90

Page 90: Laborator SDA

eps – este lungimea maximă a unei secvenţe ap, ap+1, ...,aq

pentru care prelucrarea se poate face direct;m – este indicele intermediar în care secvenţa ap, ap+1, ...,aq

este împărţită în două subsecvenţe de funcţia DIVIDE;beta şi gama – reprezintă rezultatele intermediare obţinute în

urma prelucrării subsecvenţelor (ap, ap+1, ...,am) şi respectiv (am+1, am+2, ...,aq);alfa – reprezintă rezultatul combinării rezultatelor

intermediare beta şi gama;DIVIDE – împarte secvenţa (ap, ap+1, ...,aq) în două

subsecvenţe (ap, ap+1, ...,am) şi(am+1, am+2, ...,aq);

COMBINĂ – combină rezultatele beta şi gama ale prelucrării subsecvenţelor returnate de procedura DIVIDE, obţinând rezultatul alfa a prelucrării secvenţei iniţiale.

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 100int 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; int b[nmax]; i=inf;j=med+1;k=inf;

91

Page 91: Laborator SDA

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); } }void main(void){

92

Page 92: Laborator SDA

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 lucrării

Se vor rezolva următoarele probleme prin metoda “Branch and Bound”:

3.1 Jocul PERSPICO. 15 plăcuţe pătrate sunt încadrate într-un cadru de dimensiune 4x4, o poziţie fiind liberă. Orice plăcuţă vecină cu această poziţie liberă poate fi mutată în locul ei. Cele 15 plăcuţe sunt numerotate de la 1 la 15. Se începe dintr-o stare iniţială, care corespunde unei distribuţii oarecare a celor 15 plăcuţe şi a locului liber în cele 16 poziţii posibile. Problema constă în a trece, folosind mutări posibile, din starea iniţială în starea finală (fig. 3.1.1).

Exemplu de configuraţie iniţială

Configuraţie finală

Figura 3.1.1

1 3 4

5 2 7 8

9 6 10 11

13 14 15 12

1 2 3 4

5 6 7 8

9 10 11 12

13 14 15

93

Page 93: Laborator SDA

3.2 Există următorul joc: pe o linie de cale ferată se află n vagoane unul lângă altul, numerotate cu valori distincte din mulţimea 1...n. O macara poate lua k vagoane de pe linie şi le poate aşeza în partea dreaptă, la sfârşitul şirului de vagoane, care apoi prin împingere ajung din nou unul lângă altul, în noua ordine creată după operaţia respectivă.

Dându-se ordinea iniţială a vagoanelor, se cere să se determine (dacă este posibil) numărul minim de operaţii pe care trebuie să le efectueze macaraua pentru ca în final vagoanele să se afle în ordine crescătoare 1, 2, ..., n .

3.3 Pe malul unui râu se află 2n băştinaşi din care n sunt canibali. Aceştia doresc să traverseze râul utilizând o barcă care poate transporta cel mult k persoane. Dacă pe un mal sau în barcă sunt mai mulţi canibali decât ceilalţi, atunci canibalii îi vor mânca. Cum vor reuşi să treacă toţi pe malul opus fără să se mănânce şi fără 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 sări peste o singură capră din grupul opus şi desigur poate avansa dacă în faţa sa este un spaţiu liber.

Cum reuşesc aceste capre să traverseze podul doar prin cele două mişcări posibile (avans şi săritură).

Se vor rezolva următoarele probleme prin metoda “Divide et Impera”:

3.5 Fiind dat un vector ce conţine elemente de tip întreg ordonate crescător, să se scrie o funcţie de căutare a unui element dat în vector, returnându-se poziţia sa.

3.6 Problema turnurilor din Hanoi. Se dau trei tije. Pe una dintre ele sunt aşezate n discuri de mărimi diferite, discurile de diametre mai mici fiind aşezate peste discurile cu diametre mai mari. Se cere să se mute aceste discuri pe o altă tijă, utilizând tija a treia ca intermediar, cu condiţia mutării a câte unui singur disc şi fără a pune un disc de diametru mai mare peste unul cu diametru mai mic.

94

Page 94: Laborator SDA

Lucrarea de laborator nr. 11.

METODE GENERALE DE ELABORARE A ALGORITMILOR (III)

1. Conţinutul lucrării

În lucrare sunt prezentate metoda programării dinamice şi metodele

euristice.

2. Consideraţii teoretice

2.1. Metoda programării dinamice

Metoda programării dinamice se aplică pentru rezolvarea problemelor de optim, pentru care soluţia este rezultatul unui şir de decizii secvenţiale, dependente de cele luate anterior şi care îndeplinesc principiul optimalităţii.

Principiul optimalităţii constă în următoarele:Fie stările s0, s1, ..., sn, în care s0 este starea iniţială şi sn este starea

finală, obţinute prin deciziile d1, d2, ..., dn (fig. 2.1.1).

Dacă di, di+1, ..., dj (1 i < j n) este un şir optim de decizii care transformă starea si-1 în starea sj, trecând prin stările 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 satisfăcut principiul optimalităţii.

Aplicarea metodei programării dinamice se face astfel: se verifică principiul optimalităţii; se scriu relaţiile de recurenţă obţinute din regulile de

trecere dintr-o stare în alta şi se rezolvă.

95

Fig. 2.1.2 Stări

Page 95: Laborator SDA

Drept exemplu se ia înmulţirea 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 înmulţirea matricelor Ai şi Ai+1 se efectuează di*di+1*di+2 operaţii de înmulţire. Dacă matricele au dimensiuni diferite, numărul operaţiilor de înmulţire necesare obţinerii matricei rezultat R depinde de ordinea efectuării produselor a câte două matrice. Se cere găsirea ordinii de asociere pentru care numărul înmulţirilor să fie minim.

Rezolvarea acestei probleme se va face astfel:Fie Cij numărul minim de înmulţiri de elemente pentru calculul

produsului Ai*Ai+1*...*Aj pentru 1 i < j n.Se observă că:

a) Cii=0b) Ci,i+1= di*di+1*di+2

c) C1n este valoarea minimă căutată.d) este verificat principiul optimalităţii.

Cij = min {Ci,k+Ck+1,j+di*dk+1*dj+11 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, până se ajunge la C1,n. Pentru a construi arborele binar care va descrie ordinea efectuării operaţiilor, se va reţine la fiecare pas indicele k care realizează minimul, adică modul de asociere a matricelor. Vârfurile arborelui vor conţine limitele subşirului de matrice care se asociază; rădăcina va conţine (1,n), iar un subarbore care conţine în rădăcină (i, j) va avea descendenţi 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; } TIP_NOD;

/*Inmultirea optima a unui sir de matrici A1*A2*...*An */ /* de dimensiuni d1*d2,d2*d3,...,dn*d(n+1) */

96

Page 96: Laborator SDA

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; } void postordine(TIP_NOD *p,int nivel) {

97

Page 97: Laborator SDA

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; rad=constr_arbore(rad,1,n,c); printf("\nARBORELE IN POSTORDINE\n"); postordine(rad,0);

98

Page 98: Laborator SDA

getch(); }

2.2. Metode euristice

Prin algoritm euristic se va înţelege un algoritm care furnizează o soluţie aproximativă, nu neapărat optimală, dar care poate fi implementată uşor ş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 soluţiei în mai multe procese succesive, cărora li se caută soluţia optimală. Idea nu conduce la obţinerea în final în mod sigur a unei soluţii optime, întrucât optimizarea locală nu implică în general optimizarea globală.

În problemele practice se pot căuta mai multe soluţii 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, căreia 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 iniţial. Se cere ca ciclul găsit să aibă un cost minim.

Soluţia optimală a problemei se găseşte într-un timp de ordin exponenţial prin metoda backtracking.

Algoritmul euristic prezentat mai jos bazat pe metoda Greedy necesită un timp polinomial. Rezolvarea constă în următoarele:

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 vârf aparţinând 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 nişte noduri de start, se determină pentru fiecare ciclul corespunzător după strategia descrisă şi se reţine ciclul de cost minim dintre ele.

În continuare este prezentat programul corespunzător. #include <stdio.h>

99

Page 99: Laborator SDA

#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 arenodul 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) { int i,j,n;

100

Page 100: Laborator SDA

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 lucrării

Se vor rezolva prin metoda programării dinamice următoarele probleme:

101

Page 101: Laborator SDA

3.1 Determinarea căilor de cost minim între oricare două vârfuri ale unui graf orientat prin algoritmul lui Floyd. (problema 3.2 din lucrarea 7).

3.2 La un concurs de tir, ţinta este alcătuită din cercuri concentrice, numerotate din exterior spre interior. Fiecărui sector determinat de două cercuri succesive îi este ataşată o valoare strict pozitivă, reprezentând punctajul primit de concurent în cazul lovirii acestui sector.

Să se determine numărul minim de lovituri pe care trebuie să le execute un concurent pentru a obţine exact k puncte.

3.3 Se dau două numere naturale A şi B şi un vector v care conţine n numere naturale. Să se determine dacă se poate trece din A în B, ştiind că singurele operaţii permise sunt:

a) Adunarea la A a oricâte numere din vectorul v;b) Scăderea din A a oricâte numere din vectorul v;Fiecare număr poate fi adunat, respectiv scăzut de mai multe ori.Dacă răspunsul la întrebare este afirmativ, se cere numărul

minim de operaţii 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 submulţimi de segmente care are proprietăţile:

a) oricare două numere din submulţime nu se intersectează;b) submulţimea conţine primul element.

3.5 Pe o creangă de măr, se află n mere, fiecare caracterizat prin distanţa hi în cm de la pământ până la poziţia în care se află şi prin greutatea sa gi în grame. Un culegător doreşte să culeagă o cantitate exprimată în grame cât mai mare de mere. După ce este cules un măr întreaga creangă devine mai uşoară şi se ridică în sus cu x cm. Culegătorul ajunge doar la merele aflate la o înălţime 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 următoarele probleme:

3.6 Să se găsească maximul unei funcţii f(x) în intervalul [a, b].

102

Page 102: Laborator SDA

3.7 Se dă un graf neorientat cu n noduri. Se cere să se determine numărul minim de culori necesare pentru a colora nodurile grafului dat, astfel încât două vârfuri 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 submulţime maximă de noduri cu proprietatea că oricare două noduri din ea nu sunt legate printr-o muchie.

103

Page 103: Laborator SDA

Lucrarea de laborator nr. 12.

ALGORITMI FUNDAMENTALI DE SORTARE

1. Conţinutul lucrării

În lucrare sunt prezentaţi algoritmii de sortare prin numărare, prin inserare (directă şi shellsort), prin interschimbare (metoda bulelor şi quicksort), prin selecţie şi interclasare.

2. Consideraţii teoretice

Sortarea constă în ordonarea crescătoare sau descrescătoare a elementelor unui vector A =(a0, a1, ..., an-1). În practică, problema se întâlneşte sub forma sortării unor articole după cheie, cheia fiind un câmp din articol.

2.1. Sortarea prin numărare

Metoda sortării prin numărare constă în găsirea pentru fiecare element a[i], a numărului de elemente din vector, mai mici ca el. Numerele obţinute sunt memorate într-un vector c; elementele vectorului de sortat a, sunt iniţial 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., funcţia sort_numărare redă algoritmul de mai sus.

2.2. Sortarea prin inserare

Sortarea prin inserare constă în următoarele:Fie secvenţa a0 < a1 < a2 ... < aj-1.

Inserarea elementului aj în această secvenţă constă în compararea lui aj

cu aj-1, aj-2 ... până când se ajunge la ai < aj; se inserează aj după ai, cu menţiunea că în prealabil elementele aj-1, aj-2, ..., ai+1 au fost deplasate spre dreapta cu o poziţie. Metoda care procedează întocmai se numeşte inserare directă.

104

Page 104: Laborator SDA

Metoda inserării binare constă în căutarea binară a locului unde trebuie inserat elementul aj, având în vedere că secvenţa a0, a1, ..., aj-1 este ordonată crescător.

Tot din categoria inserării face parte şi metoda shell. Pentru a explica metoda se introduce noţiunea de h-sortare. Numim h-sortare, sortarea prin inserare directă a următoarelor secvenţe:

a0, ah, a2h, ....a1, a1+h, a1+2h, .......ah, a2h, a3h, ....

h este numit increment.Metoda shell constă în alegerea unui număr de k incremenţi

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.

Performaţele metodei sunt strâns legate de alegerea incremenţilor.În exemplul din paragraful 2.6., funcţia sort_inserare_directă redă

algoritmul de inserare directă, iar funcţia 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 căutare binară este de ordinul O(n *lnn).

2.3. Sortarea prin interschimbare

Sortarea prin interschimbare constă în modificări succesive de forma ai aj, până când elementele vectorului apar în ordine crescătoare.

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 poziţie ajunge elementul având valoarea cea mai mare, după a doua parcurgere ajunge următorul element ş. a. m. d.

Timpul de prelucrare este de ordinul O(n2).Sortarea rapidă quicksort a fost creată de Hoare şi foloseşte metoda

„Divide Et Impera”.

105

Page 105: Laborator SDA

Principiul metodei este următorul: se selectează un element din tablou numit pivot şi se rearanjează vectorul în doi subvectori, astfel încât cel din stânga are toate elementele mai mici decât pivotul, iar cel din dreapta mai mare ca pivotul. Procedeul se reia în subtabloul din stânga şi apoi în cel din dreapta ş. a. m. d. Procedeul se termină când se ajunge cu pivotul în extremitatea stângă şi respectiv dreaptă a tabloului iniţial.

Timpul de prelucrare a metodei quicksort este de ordinul O(n* lnn).În exemplul de la paragraful 2.6., funcţia sort_metoda_bulelor redă

algoritmul de sortări prin metoda bulelor, iar funcţiile quicksort şi quick redau algoritmul sortării prin metoda quicksort.

2.4. Sortarea prin selecţie

Sortarea prin selecţie directă constă în următoarele: se află minimul a j

dintre a0, a1, ..., an-1 şi se aduce pe poziţia 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., funcţia sort_selecţie redă algoritmul

de sortare prin metoda selecţiei directe.

106

Page 106: Laborator SDA

2.5. Sortarea prin interclasare

Metoda sortării prin interclasare a fost prezentată în cadrul lucrării nr.

10, drept exemplificare a metodei de elaborare a algoritmilor „Divide et

Impera”

2.6. Exemplu

În programul de mai jos sunt implementaţi algoritmii de sortare prezentaţi î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];}

107

Page 107: Laborator SDA

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++) {

108

Page 108: Laborator SDA

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 */ {

109

Page 109: Laborator SDA

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])

110

Page 110: Laborator SDA

/* 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();

111

Page 111: Laborator SDA

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 lucrării

3.1. Fie tabloul de întregi:5 9 1 7 4 3 2 0

Ordonaţi, fără program, acest şir în ordinea crescătoare a elementelor, folosind cele trei metode ‘elementare’ de sortare: inserţia, metoda bulelor şi selecţia, arătând la fiecare pas al metodei care este noua configuraţie a tabloului. Câte comparaţii şi câte mutări de elemente au avut

112

Page 112: Laborator SDA

loc pentru 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. Descrieţi algoritmul de sortare prin inserare, la care modificaţi căutarea liniară cu o căutare binară (în cadrul subtabloului din stânga elementului curent). Calculaţi şi pentru acest nou algoritm (numit sortare prin inserţie cu căutare binară) numărul de paşi, numărul de comparaţii şi mutări 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,

aplicaţi metodele de ordonare shell-sort şi quick-sort, pentru fiecare pas reprezentând noua configuraţie a tabloului. Număraţi comparaţiile şi mutările de elemente pe care le-aţi efectuat. Care algoritm este mai eficient pentru acest tablou?

3.4. Analizaţi algoritmul de sortare quick-sort şi înlocuiţi varianta prezentată ce foloseşte recursivitatea, cu o variantă nerecursivă. Ce variabile trebuiesc iniţializate, cu ce valori şi unde are loc saltul în program pentru a se elimina apelul recursiv?

3.5. Care algoritm dintre cei prezentaţi are nevoie de spaţiu de memorie cel mai mare?

3.6. Scrieţi un algoritm care combină algoritmii de sortare quick-sort pentru obţinerea de partiţii nesortate de lungime m, apoi utilizaţi inserţia directă pentru terminarea sortării.

3.7. Adaptaţi 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 număr de n elemente numerotate de la 1 la n caracterizate prin anumite relaţii de precedenţă notate (j, k), având semnificaţia 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 său (sortarea topologică).

113

Page 113: Laborator SDA

3.9. Să se descrie algoritmul care realizează următoarele acţiuni specifice unui examen de admitere:

efectuarea calculului mediilor candidaţilor la examenul de admitere

repartizarea celor admişi după opţiuni (se consideră că există m opţiuni, fiecare cu un număr dat de candidaţi admişi) şi afişarea listei.

afişarea în ordinea descrescătoare a mediilor a tuturor candidaţilor neadmişi.

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 menţine, se ia în considerare a doua), admiţându-se depăşirea numărului de locuri în caz de egalitate şi după acest criteriu.

114

Page 114: Laborator SDA

Bibliografie:

115