Documente Academic
Documente Profesional
Documente Cultură
2. Stive
2.1 Alocarea secventiala
3. Cozi
3.1 Alocarea secventiala
4. Liste circulare
7. Arbori
7.1 Reprezentare
7.2 Traversare
8. Arbori binari
8.1 Notiuni generale
8.2 Reprezentare
8.3 Traversare
12.2 Quicksort
Acest material este destinat studentilor anului II, invatamant la distanta. Modul de
prezentare are n vedere particularitile nvmntului la distan, la care studiul
individual este determinant.
Obiective specifice disciplinei Algoritmi si structuri de date sunt:
Cunoasterea principalelor structuri de date liniare si neliniare folosite in
informatica;
Asimilarea metodelor de analiza a eficientei unui algoritm;
Cunoasterea principalilor algoritmi de sortare si cautare;
Implementarea algorimilor si a structurilor de date invatate in limbajul C.
Materialul a fost elaborat astfel incat algoritmii prezentati pot fi implementati in
orice limbaj de programare. Pentru a face o alegere, limbajul de programare folosit in
aplicatii va fi limbajul C. Este foarte important ca parcurgerea materialului sa se faca
in ordinea modulelor incluse (Modulul 0 Modulul VI). Fiecare modul contine, dupa
fiecare lectie, pe langa prezentarea notiunilor teoretice, teme propuse de laborator
care faciliteaza o intelegere mai rapida a materialului inclus. Asa cum se mentioneza
in programa analitica, studentii vor avea de elaborat un proiect individual obligatoriu.
Tematica acestui proiect se gaseste de asemenea in materialul inclus. Pe langa aceste
teme si proiect, materialul contine si o serie de probleme propuse repartizate pe
module ce testeaza cunoasterea notiunilor teoretice de catre student. Mentionam ca
aceste probleme nu sunt ordonate dupa gradul lor de dificultate.
Structura modulelor este urmatoarea:
Modulul 0. Alocare dinamica in limbajul C
Modulul I. Structuri de date liniare
Modulul II. Structuri de date neliniare
Modulul III. Analiza algoritmilor
Modulul IV. Algoritmi de sortare
Modulul V. Algoritmi de cautare
Mentionam ca Modulul 0 contine, de fapt, o recapitulare a notiunilor de pointeri si
alocare dinamica in limbajul C, recapitulare peste care se poate trece in cazul in care
limbajul de programare ales pentru implementarea algoritmilor prezentati in modulele
urmatoare este altul decat limbajul C.
Materialul inclus contine, pe langa acest cuvant inainte, continutul fiecarui modul
in fisiere separate, cuprinsul, tematica proiectului si lista de probleme propuse. Pentru
orice nelmuriri fa de acest material v rugm s contactai tutorele de disciplin
care are datoria s v ajute oferindu-v toate explicaiile necesare.
In speranta ca organizarea si prezentarea materialului va fi pe placul
dumneavoastra, va uram MULT SUCCES!
1
spatiul alocat si NULL daca nu este spatiu suficient in memorie sau daca n = 0 sau
dimensiune = 0.
Exemplu:
float *p;
p = calloc(10, sizeof(float) );
Prototipul functiei realloc este:
void *realloc(void *p, size_t dimensiune);
Functia realloc modifica marimea unui spatiu pentru date deja existente. p este un
pointer catre o zona de memorie deja alocata. Daca p este NULL functia realloc
functioneaza ca si malloc, alocand un bloc de atatia octeti cati sunt precizati de
dimensiune. Daca p nu este NULL atunci p trebuie sa fie un pointer returnat de un
apel anterior al uneia din functiile malloc, calloc sau realloc. Parametrul dimensiune
determina noua marime in octeti a blocului de memorie alocat. Acest proces are loc
fara sa se stearga datele existente. Daca zona de memorie existenta nu poate fi extinsa
fara sa se suprapuna cu alte date, intreaga zona este copiata la o alta locatie iar zona
veche este eliberata. Functia returneaza un pointer la noua zona de memorie alocata
sau NULL.
Exemplu:
alocdinam.C
#include<stdio.h>
#include<stdlib.h>
main()
{
char *pc;
pc = malloc(50 * sizeof(char));
if(pc == NULL)
printf("Insuficient spatiu");
else
{
printf("Bloc de 50 de caractere alocat cu succes \n");
realloc(pc , 100 * sizeof(char));
if(pc == NULL) printf("Insucces realloc");
else printf("Bloc de 100 de caractere alocat cu succes\n");
}
}
Functia free elibereaza blocul de memorie indicat de pointerul p, unde p trebuie sa fie
un pointer returnat de un apel anterior al uneia din functiile malloc, calloc sau realloc.
Functia free nu poate fi utilizata pentru eliberarea memoriei alocate de variabile in
momentul compilarii. Dupa apelarea functiei free blocul eliberat este disponibil
pentru alocare. Un pointer NULL ca argument este ignorat.
2
Exemplu:
free.C
#include<stdio.h>
#include<stdlib.h>
main( )
{
char *pc, *p, c;
int i;
pc = malloc(32);
p = pc;
De multe ori dorim sa grupam mai multe date de tipuri diferite sau de acelasi
tip sub acelasi nume deoarece din punct de vedere logic ele reprezinta aceeasi entitate.
De exemplu, un punct in plan este reprezentat prin doua numere reale, numere care de
fapt descriu o singura entitate. Deci, un program va fi mai usor de inteles si de scris,
daca pentru descrierea punctului, in acel program, definim o singura entitate ce
grupeaza doua variabile de tip double decat daca lucram in program cu doua entitati
separate de tip double corespunzatoare coordonatelor carteziene ale punctului. Un alt
exemplu: In general, pentru afisarea notelor studentilor ne sunt necesare urmatoarele
informatii despre fiecare student: numele, prenumele si notele la examene. Putem
grupa cele trei caracteristici (doua de tip sir de caractere si unul de tip sir de intregi)
sub acelasi nume, ele reprezentand, in ansamblu, aceeasi entitate.
Gruparea mai multor elemente se face, in C, prin definirea unui nou tip de
date, un tip de date care este definit de programator (deci nu este predefinit) si care se
numeste tipul struct. Descrierea unui punct in plan se va face atunci in felul urmator:
struct punct
{
double x;
double y;
};
3
iar o variabila de acest tip va putea fi declarata
struct punct p1;
si va avea de fapt doua componente la care se poate face referire in felul urmator:
coordonata x va fi p1.x iar coordonata y va fi p1.y. Dar sa vedem cum se defineste
acest tip struct in general:
struct nume_tip
{
tip_membru1 var_membru1;
tip_membru2 var_membru2;
... nu uitati ;
};
in care struct este cuvant cheie (keyword), nume_tip este numele pe care
programatorul il da tipului de date nou creat, tip_membru1 var_membru1;
tip_membru2 var_membru2; ... sunt declaratiile variabilelor componentelor tipului de
date, cele care sunt grupate impreuna si sunt tratate ca si o singura entitate.
var_membru1, var_membru2, ... se numesc numele membrilor tipului de date.
Exemplu:
struct info_student
{
char *nume;
char *prenume;
int note[10];
};
descrie un nou tip struct pentru reprezentarea informatiei unui student.
Declararea variabilelor de tipul nou creat (de tip struct), variabile care se mai
numesc si structuri, se poate face in felul urmator:
struct nume_tip nume_var_structura;
si deci o variabila de tip info_student poate fi declarata:
struct info_student student;
nume_tip este optional deci putem defini tipuri struct fara nume. In acest caz
declararea variabilelor se va face in momentul definirii tipului struct, ca de exemplu:
struct
{
double x;
double y;
double z;
} origine, pct;
In acest caz origine si pct sunt doua variabile de tip structura.
Putem declara variabile de tip structura in acelasi timp cu definirea tipului de
date chiar si atunci cand se precizeaza numele tipului:
struct nume_tip
{
tip_membru1 var_membru1;
tip_membru2 var_membru2;
...
} nume_var1, nume_var2;
Definitia unui tip struct este de obicei plasata in afara oricarei functii (ca si
declararea variabilelor globale), caz in care definitia este globala si va fi valabila in
fragmentul de program ce urmeaza definitiei.
4
Dupa declararea variabilei de tip struct, variabilele membru ale acesteia se pot
accesa in felul urmator:
nume_var_structura.var_membru
unde . este operatorul punct ce are precedenta cea mai mare si se asociaza de la stanga
la dreapta. Aceste variabile reprezinta si se comporta ca orice alte variabile de tipul
tip_membru corespunzator.
Exemple: Cu definitiile de mai sus, putem scrie
student.nume si are tipul char*
origine.x si are tipul double
pct.y si are tipul double
student.note si are tipul sir de 10 intregi. Elementele sirului pot fi referite in
felul urmator: student.note[0], student.note[1], ..., student.note[9].
De asemenea, putem tipari coordonatele punctului p1 (variabila de tip struct punct):
printf("(%6.2f,%6.2f)", p1.x, p1.y);
sau initializa variabila origine de tipul struct fara nume:
origine.x = 0.0;
origine.y = 0.0;
origine.z = 0.0;
sau putem prelucra informatia despre numele unui student (folosind variabila student
de tip info_student):
char s[50];
strcpy(s, student.nume);
strcat(s," ");
strcat(s, student.prenume);
puts(s);
care va tipari numele studentului urmat de un spatiu si de prenume.
Numele unui membru al unei structuri precum si numele unui tip struct pot fi
aceleasi cu numele unei variabile simple deoarece nu exista posibilitatea unui conflict,
al unei confuzii. Deci putem avea, in acelasi program, declaratiile de mai sus alaturi
de declaratiile:
char *nume;
int x;
int *z;
double punct;
Mai mult, putem defini doua structuri diferite cu unul sau mai multe nume de membri
comune. Sunt permise ambele definitii
struct struct
{ {
int a; char *a;
double b; int x;
} str1; } structura1;
deoarece cei doi membri vor fi accesati numai prin intermediul variabilelor carora
apartin: str1.a, si respectiv, structura1.a.
Structurile pot fi initializate in momentul in care sunt declarate, prin folosirea
unei liste de valori initiale, cate o valoare pentru fiecare membru in ordinea aparitiei
in lista care defineste tipul struct.
Exemplu:
struct punct p = {0, 0}, q = {1.5, 2.5};
5
dar valorile membrilor structurii nu pot fi modificate mai tarziu in program cu
instructiunea:
p = {3, 2}; incorect
O declaratie de tip struct care nu este urmata de o lista de variabile (deci este
definit numai tipul de date), nu aloca nici un spatiu de memorie. Acest tip de
declaratie descrie numai forma (sablonul) unei structuri. Alocarea memoriei se va face
abia cand se vor defini variabilele de tip struct. Pentru a afla cat spatiu de memorie
este necesar pentru alocarea unei variabile de acel tip, putem folosi operatorul sizeof.
In orice caz, numarul de octeti necesari unui tip struct va fi cel putin egal cu suma
numarului de octeti alocati fiecarui membru al structurii.
Exemplu:
sizeof(struct info_student) va arata 16 (octeti)
sizeof(struct punct) va arata 48 (octeti)
Membrii unei structuri pot avea orice tip, inclusiv un tip struct. De exemplu,
putem reprezenta un dreptunghi pe ecranul monitorului precizand coltul din stanga
sus, lungimea si latimea:
struct dreptunghi
{
struct punct sus;
double lungime;
double latime;
} d1;
Accesarea componentelor structurii d1 se face:
d1. sus de tipul punct si deci avand doua componente (d1.sus).x si (d1.sus).y
(aici parantezele se pot elimina deoarece operatorul . se asociaza de la stanga la
dreapta),
d1.lungime de tip double
d1.latime de tip double.
Observatie: Nu putem declara un membru cu acelasi tip ca si tipul struct caruia ii
apartine, dar putem declara un membru ca pointer la variabile de tipul struct caruia ii
apartine. In acest caz tipul struct trebuie sa aiba nume. (In acest fel se pot crea listele
inlantuite).
Exemplu:
struct nod
{
int x;
struct nod *next;
};
Variabilele de tip structura pot fi argumentele unei functii sau putem scrie
functii care returneaza structuri.
6
Exemplu: Putem defini o structura pentru reprezentarea unei zile calendaristice:
struct data
{
int zi;
int luna;
int an;
};
si apoi, date fiind doua zile calendaristice, putem scrie o functie care returneaza 1
daca prima zi este inaintea celei de a doua, 0 daca sunt identice si 1 daca a doua zi
este inaintea primei. Prezentam aici intregul program. Programul citeste de la tastatura
informatiile celor doua zile calendaristice. Programul contine, pe langa functia mai
sus amintita, o functie de citire si una de scriere. Functia de citire este o functie care
returneaza o variabila de tip struct data, iar functia de scriere are ca argument o
variabila de tip struct data.
struct_data.C
#include <stdio.h>
struct data
{
int zi;
int luna;
int an;
};
main()
{
int rezultat;
char c;
struct data ziua1, ziua2;
puts("Programul urmator compara doua zile calendaristice");
do
{
ziua1 = citeste();
ziua2 = citeste();
temp.zi = zi;
temp.luna = luna;
temp.an = an;
return temp;
}
int semn(int n)
{
if (n > 0) return 1;
else if (n < 0 ) return -1;
else return 0;
}
8
(*pzi).an. Parantezele sunt necesare deoarece precedenta operatorului . este mai mare
decat a operatorului unar *.
Mai exista si o alta alternativa pentru accessul la componentele unei structuri pointate
de un pointer, si anume folosirea operatorului ->: daca avem declaratia struct
nume_tip p; atunci (*p).membru face accessul la o componenta a structurii si este
echivalent cu p ->membru. Pentru exemplul de mai sus, pzi -> zi, pzi -> luna, pzi ->
an sunt componentele structurii pointate de pzi.
Daca se doreste folosirea pointerilor la structuri, programul de mai sus
struct_data.C ar trebui modificat in felul urmator:
se modifica functia scrie:
void scrie (struct data *d)
{
printf("%2d/%2d/%4d", d->zi, d->luna, d->an);
}
se modifica functia mai_inainte:
int mai_inainte(struct data *ziua1, struct data *ziua2)
{
int temp1, temp2;
temp1 = semn(ziua2->an - ziua1->an);
if(temp1) return temp1;
else
{
temp2 = semn(ziua2->luna - ziua1->luna);
if(temp2) return temp2;
else return semn(ziua2->zi - ziua1->zi);
}
}
se modifica apelul functiilor de mai sus in functia main:
rezultat = mai_inainte(&ziua1, &ziua2) ;
scrie (&ziua1);
scrie (&ziua2);
De asemenea, limbajul C permite redenumirea unor tipuri sau numirea unor tipuri
predefinite sau create de programator. De exemplu, daca nu ne place sa folosim int ca
nume pentru tipul de date intregi, putem redenumi acest tip de exemplu Intreg, fara ca
sa existe modificari de tipuri sau de date, in felul urmator:
typedef int Intreg;
si daca vrem sa declaram variabile o facem in felul urmator:
intreg i, n; care este echivalent cu int i, n;
Typedef nu introduce tipuri noi ci introduce doar nume noi, sinonime pentru tipuri
existente. Putem da si urmatoarea definitie
typedef char * String;
insemnand ca String este numele pe care il dam tipului pointer la caractere. Numele
nou dat unui tip poate fi orice identificator permis in C, dar conventional se folosesc
siruri de caractere alfanumerice ce incep cu litera mare.
Sintaxa declaratiei typedef este:
typedef declaratia_tipului sinonim;
Typedef este foarte folositor atunci cand tipul de date este complicat si se doreste o
versiune mult mai simplificata, nu in context ci doar in scriere. Cu declaratia
typedef struct punct
{
9
double x;
double y;
} Pct;
vom putea defini variabile de tipul struct punct mult mai simplu: Pct origine; care este
echivalent cu struct punct origine;. Declaratia typedef Pct Triunghi[3]; da numele
Triunghi tipului de date: sir de 3 elemente de tip Pct, iar Triunghi ABC; este
echivalent cu struct punct ABC[3];. Declaratia typedef struct punct TRIUNGHI[3]; da
numele TRIUNGHI tipului de date: sir de 3 elemente de tip struct punct, iar
TRIUNGHI abc; este echivalent cu struct punct abc[3];
10
Modulul I. Structuri de date liniare
1
Algoritm: Presupunem 0 k n. Daca n = N atunci se produce OVERFLOW
adica spatiul alocat listei este ocupat in totalitate si ca nici o alta inserare nu este
posibila. In caz contrar se muta elementele sirului x[k], ..., x[n-1], cate un bloc de
memorie spre dreapta, incepand cu ultimul. Se introduce elementul nou in pozitia k.
Se actualizeaza numarul de elemente al sirului. Mai precis, algoritmul se scrie:
if n = N then OVERFLOW
else for i = n-1, k, -1
x[i+1] = x[i]
endfor
endif
x[k] = elem_nou
n = n +1
eliminarea elementului de indice k din lista.
Algoritm: Daca n = 0 atunci se produce UNDERFLOW adica lista este vida si
deci eliminarea unui element din lista nu este posibila. Presupunem 0 k n-1. Se
salveaza informatia continuta de elementul de indice k pentru cazul in care se doreste
prelucrarea ei. Se muta elementele sirului x[k+1], ..., x[n-1], cate un bloc de memorie
spre stanga, incepand cu x[k+1]. Se actualizeaza numarul de elemente al sirului. Mai
precis, algoritmul se scrie:
if n = 0 then UNDERFLOW
else elem_sters = x[k]
for i = k, n-2,1
x[i] = x[i+1]
endfor
endif
n = n 1
2
elementul urmator). Datorita reprezentarii structurate, elementele unei astfel de liste
se mai numesc si noduri.
In C, putem defini tipul de date asociat unui nod astfel:
struct nod
{
T info;
struct nod *link;
};
typedef struct nod NOD;
unde T este presupus definit anterior (eventual printr-o definitie typedef).
HEAD
info link info link ... info NULL
HEAD
... NULL
p
info nou
3
o la sfarsitul listei:
Algoritm: Folosim variabilele info_nou ce contine valoarea informatiei
nodului ce trebuie introdus in lista, p pointer la un nod si iter care initial
pointeaza la primul nod al listei si va parcurge lista pana cand acesta va pointa
la ultimul nod din lista..
Aloca memorie pentru un nod nou. Returneaza p, un pointer la noul nod.
if p NULL then
p -> link = NULL
p -> info = info_nou
iter = HEAD
while (iter NULL and iter -> link NULL)
iter = iter -> link
endwhile
if iter = NULL then HEAD = p
else iter ->link =p
endif
else OVERFLOW
endif
HEAD
... NULL
iter
p
info nou NULL
o dupa un nod dat:
Algoritm: Folosim variabilele info_nou ce contine valoarea informatiei
nodului ce trebuie introdus in lista si p pointer la un nod si inainte pointer la
nodul dupa care se doreste introducerea noului nod.
Aloca memorie pentru un nod nou. Returneaza p, un pointer la noul nod.
if p NULL then
p -> link = inainte ->link
inainte -> link =p
p -> info = info_nou
else OVERFLOW
endif
... ...
inainte
p info nou
4
iter = iter -> link
endwhile
if iter = NULL then tipareste mesajul "Eroare. Lista este vida. UNDERFLOW."
else recupereaza informatia nodului de sters sterge->info
iter ->link = sterge -> link
endif
... ...
iter sterge
5
Modulul I. Structuri de date liniare
Capitolul 2. Stive
Stiva este o lista liniara in care inserarile si stergerile din lista se pot face
numai pe la un capat al listei, numit varful stivei. Singurul element care poate fi
accesat la un moment dat este cel din varful stivei. Se poate face o analogie intre o
stiva folosita in programare si o stiva de carti. Adaugarea unei carti se poate face
numai in varful stivei de carti, peste cartile existente si singura carte ce poate fi
accesata, eventual eliminata din stiva este cea din varful stivei.
inserare stergere
...
Stivele se mai numesc si liste push-down sau LIFO (last in/first out) deoarece
primul element care este extras din stiva este ultimul introdus in stiva.
Vom nota inserarea unui element a intr-o stiva S: a S, iar stergerea unui
element a dintr-o stiva S: S a. Cele doua operatii se mai numesc si push respectiv
pop.
1
2.2 Alocarea inlantuita
p info_nou
.............
top
NULL
.......
NULL
2
3
Modulul I. Structuri de date liniare
Capitolul 3. Cozi
O coada este o lista liniara in care stergerea si accesarea unui element se pot
face numai pe la un capat al cozii, numit front, iar inserarea se face la celalalt capat al
cozii, numit rear. Se poate face o analogie intre o coada folosita in programare si, de
exemplu, o coada pentru tiparirea mai multor fisiere text. Pentru tiparirea unui nou
fisier va trebui sa asteptam pana cand toate fisierele sunt tiparite in ordinea in care
comenzile de tiparire au fost efectuate.
stergere
front
...
rear
inserare
Cozile se mai numesc si liste FIFO (first in/first out) deoarece primul element
care este extras din coada este primul introdus.
Vom nota inserarea unui element a intr-o coada C: a C, iar stergerea unui
element a dintr-o coada C: C a.
1
else elem_sters = x[F]
F = F -1
endif
front
.............
...........
rear
NULL
rear
info_nou
p NULL
2
3