Sunteți pe pagina 1din 24

Cuprins

Modulul 0. Alocare dinamica in limbajul C


0. Pointeri si alocare dinamica. Tipul de date struct
0.1 Pointeri si alocare dinamica

0.2 Tipul struct

Modulul I. Structuri de date liniare


1. Liste liniare
1.1 Alocarea secventiala

1.2 Alocarea inlantuita

2. Stive
2.1 Alocarea secventiala

2.2 Alocarea inlantuita

3. Cozi
3.1 Alocarea secventiala

3.2 Alocarea inlantuita

4. Liste circulare

5. Liste dublu inlantuite

Modulul II. Structuri de date neliniare


6. Grafuri
6.1 Notiuni generale

6.2 Reprezentarea grafurilor

7. Arbori
7.1 Reprezentare

7.2 Traversare

8. Arbori binari
8.1 Notiuni generale

8.2 Reprezentare

8.3 Traversare

Modulul III. Analiza algoritmilor


9. Analiza eficientei algoritmilor

Modulul IV. Algoritmi de sortare


10. Sortare prin numarare

11. Sortare prin inserare


12. Sortare prin interschimbare
12.1 Bublesort

12.2 Quicksort

13. Sortare prin selectie


14. Mergesort

Modulul V. Algoritmi de cautare


15. Cautare secventiala
16. Cautare binara

17. Arbori binari de cautare.


17.1 Cautare in arbori binari de cautare

17.2 Inserare si stergere arbori binari de cautare


Lector univ. dr. DANIELA JOITA

Algoritmi si structuri de date

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!

Coordonator disciplin: Lector univ. dr. Daniela Joita


Tutor: Lector univ. dr. Daniela Joita
Modulul 0. Alocare dinamica in limbajul C

Capitolul 0. Pointeri si alocare dinamica. Tipul de date struct

0.1 Pointeri si alocare dinamica

O problema care apare in momentul declararii unui sir era aceea ca


dimensiunea sirului trebuia sa fie constanta, iar daca doream sa rulam programul
pentru siruri de dimensiuni diferite atunci trebuia sa definim o dimensiune suficient de
mare a sirului si in program sa folosim numai un numar de elemente ale sirului, numar
introdus de utilizator la fiecare rulare a programului, celelalte elemente nefiind
folosite si deci ocupand inutil un spatiu de memorie. Pentru rezolvarea acestei
probleme exista o modalitate de alocare a memoriei ce se numeste alocare dinamica,
adica alocare a memoriei in momentul rularii, permitand programului sa isi ajusteze
capacitatea de memorare a datelor in functie de necesitatile utilizatorului.
In C sunt disponibile patru functii care pot aloca atat spatiu cat este dorit, pot
modifica dimensiunea unui sir existent sau pot elibera zone de memorie alocate
anterior. Aceste functii sunt:
malloc
calloc
realloc
free
Toate apartin bibliotecii stdlib.h.
Prototipul functiei malloc este:
void *malloc(size_t dimensiune);
malloc rezerva un numar de octeti specificat de catre parametrul dimensiune, in locatii
de memorie consecutive returnand un pointer la primul octet sau pointerul NULL
(adica pointerul 0, care nu puncteaza nimic) daca nu poate aloca memoria solicitata.
Functia returneaza un pointer de tip void adica in momentul in care acest pointer este
atribuit unui pointer de un anumit tip atunci valoarea pointerului returnat de catre
functia malloc se va transforma in tipul corespunzator. size_t corespunde tipului
intreg fara semn, fiind tipul rezultatului pe care il returneaza operatorul sizeof. Acest
tip este definit in stddef.h.
Exemplu:
int *p;
p = malloc(5 * sizeof (int) );
malloc va aloca 5*4=20 de octeti (am presupus ca dimensiunea int este de patru
octeti) va returna un pointer la primul octet si este transformat in pointer de tip int
memorat in pointerul p de tip int.
Deci p este de fapt un pointer la un sir de 5 variabile int. Putem initializa elementele
sirului, putem efectua orice prelucrare a elementelor sirului ca si cum ele ar fi fost
alocate in momentul alocarii.
Prototipul functiei calloc este:
void *calloc(size_t n, size_t dimensiune);
Functia calloc aloca spatiu de memorare pentru n elemente, fiecare de marimea
dimensiune. Fiecare element este initializat cu zero. Functia returneaza un pointer la

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

Prototipul functiei free este:

void free(void *p);

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;

for(c = 'A'; c <= 'Z'; c ++)


{
*p = c;
p++;
}
for(i = 0; i < 32; i++)
printf("%c ", *(pc + i));
puts("");
free(pc);
for(i = 0; i < 32; i++)
printf("%c ", *(pc + i));
}

0.2 Tipul struct

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

ci va trebui sa folosim cate o instructiune de atribuire pentru fiecare variabila


membru:
p.x = 3; p.y = 2;
Putem copia o structura in alta structura (daca sunt de acelasi tip):
p = q;
in schimb nu putem compara doua structuri cu operatorul ==:
p == q
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;
};

struct data citeste();


void scrie (struct data d);
int mai_inainte(struct data ziua1, struct data ziua2);
int semn(int n);

main()
{
int rezultat;
char c;
struct data ziua1, ziua2;
puts("Programul urmator compara doua zile calendaristice");
do
{
ziua1 = citeste();
ziua2 = citeste();

rezultat = mai_inainte(ziua1, ziua2) ;


scrie (ziua1);
if( rezultat == 1 )
printf(" este inaintea ");
else if( rezultat ==0 )
printf(" este identica cu ");
else if( rezultat == -1)
printf (" este dupa ");
else printf(" eroare ");
scrie (ziua2);
puts("");
fflush(stdin);
puts("Doriti repetarea acestui program? (Y/N)");
7
c = getchar();
} while (c=='Y' || c=='y');
}
struct data citeste()
{
int zi, luna, an;
char c1, c2;
struct data temp;
puts("Introduceti elementele zilei calendaristice in formatul \
zi/luna/an");
scanf("%d%c%d%c%d", &zi, &c1, &luna, &c2, &an);

temp.zi = zi;
temp.luna = luna;
temp.an = an;
return temp;
}

void scrie (struct data d)


{
printf("%2d/%2d/%4d", d.zi, d.luna, d.an);
}

int semn(int n)
{
if (n > 0) return 1;
else if (n < 0 ) return -1;
else return 0;
}

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);
}
}
In general, cand o functie are drept parametru o structura este mai eficient sa fie
transmis un pointer la structura decat structura insasi, deoarece structura ar fi
transmisa prin valoare si deci ar fi necesare blocuri de memorie pentru doua structuri,
unul in functia apelanta si altul in functia apelata. Pointerii la structuri se definesc ca
pointerii la orice alt tip de variabile: struct nume_tip *p;
Exemplu: struct data pzi; defineste un pointer pzi la variabile de tip struct data, pzi
este structura de tip data, iar componentele structurii sunt (*pzi).zi, (*pzi).luna si

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

Lab 0 1. Folosind alocare dinamica, creati un sir de intregi de


dimensiune ce este introdusa de la tastatura si apoi
folosind programarea structurata (folosind functii pentru
executarea a partilor importante), sortati sirul, tipariti
elementele in noua ordine si eliberati memoria alocata
acestui sir.
2. Folosind tipul struct, dati o definitie pentru un cont
bancar ce contine informatiile: nr de cont, balanta, suma
minima in cont. Scrieti un program care simuleaza o
operatiune bancara: extragere sau depunere de bani in
cont. Se vor afisa informatiile contului inainte si dupa
efectuarea operatiunii.

10
Modulul I. Structuri de date liniare

Capitolul 1. Liste liniare

In viata de fiecare zi construim liste ca o modalitate de a pune lucrurile in


ordine: lista studentilor unei grupe, lista numerelor din cartea de telefon, programul
TV, lista de cumparaturi.
O lista liniara este un set finit de elemente ordonate liniar, adica exista un
element considerat primul in lista, un element considerat ultimul si pentru orice alt
element din lista exista un element precedent si un element urmator.
Principalele operatii cu liste sunt:
accesarea sau modificarea unui element din lista
inserarea unui element in lista
eliminarea unui element din lista
Modul in care aceste operatii sunt efectuate depinde de modul in care sunt
reprezentate listele. Exista doua feluri de reprezentari: secventiala si inlantuita.

1.1 Alocarea secventiala

In alocarea secventiala o lista se reprezinta ca un sir in care elementele sunt


memorate in locatii consecutive de memorie. In C, o astfel de lista este, de fapt, un sir
care se defineste, in general:
nume_tip nume_sir[nr_elemente];
unde nume_tip este tipul elementelor sirului, nume_sir este numele sirului (listei), iar
nr_elemente este o constanta ce reprezinta numarul maxim posibil de elemente ale
sirului. In C, se poate face referire la elementele sirului prin intermediul indicelui.
Primul element in lista are indice 0 si se noteaza nume_sir[0], ultimul este
nume_sir[nr_elemente 1], iar un element de indice k, 1 k nr_elemente - 2 este
nume_sir[k] si are ca precedent elementul nume_sir[k - 1] si este urmat de elementul
nume_sir[k + 1]. Elementele sirului sunt memorate astfel incat:
adresa lui nume_sir[k] = adresa lui nume_sir[0] + k *sizeof (nume_tip)
unde sizeof(nume_tip) returneaza numarul de octeti necesari memorarii unei singure
variabile de tipul nume_tip.
In cele ce urmeaza, presupunem ca avem un sir definit
T x[N];
unde N este o constanta suficient de mare, T este un tip de date definit anterior
(eventual printr-o definitie de tipul typedef), iar n este numarul de elemente din lista,
n N.
x[0] x[1] x[2] x[N]
... ... ...

Operatiile cu liste prezentate mai sus, se pot descrie astfel:


accesarea/ modificarea unui element se face prin intermediul indicelui elementului
inserarea unui element se poate face intr-o pozitie data, dupa sau inaintea unui
element dat. Prezentam in continuare inserarea unui element numit elem_nou intr-
o pozitie data k, deoarece celelalte se reduc la aceasta.

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

Scrieti un program care implementeaza algoritmii de mai sus


Lab 1
pentru o lista liniara alocata secvential.

1.2 Alocarea inlantuita

De multe ori, memoria nu este ocupata la rand, zone de memorie libere


alternand cu zone de memorie ocupate. Alocarea secventiala a unei liste se poate face
daca exista blocuri de memorie suficient de mari pentru a o memora. Daca, de
exemplu, vrem sa alocam spatiu pentru o lista cu M elemente, dar nu exista nici un
bloc de memorie de marime M, desi exista trei blocuri de memorie de marime M/2, ar
trebui sa gasim o modalitate de reorganizare a memoriei, altfel lista nu poate fi creata
sau sa gasim o alta metoda de reprezentare a listei in care elementele sa nu mai fie
memorate in locatii consecutive. Acesta din urma este cazul alocarii inlantuite.
In alocarea inlantuita, pentru a pastra ordinea listei va trebui sa stim care este
primul element al listei, al doilea etc. Pentru aceasta vom folosi un pointer la primul
element al listei (numit pointerul listei si notat HEAD), iar pentru a sti care este
urmatorul element in lista fiecare element va trebui sa contina (sa memoreze) pe langa
informatia corespunzatoare lui si un pointer la elementul urmator in lista. Elementele
nemaifiind memorate in locatii consecutive, acesti pointeri ajuta la reconstituirea
listei. Ultimul element in lista va avea drept componenta pointerul NULL. Din acest
motiv, fiecare element va fi de tip structura, continand doua campuri: info (folosit
pentru memorarea informatiei corespunzatoare elementului) si link (un pointer la

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

Acestea fiind date, operatiile cu liste se pot descrie astfel:


accesarea/ modificarea unui element ce contine o valoare data k, k de tip T.
Algoritm: Folosim variabilele gasit de tip boolean, gasit = false daca elementul cu
valoarea k nu a fost gasit in lista si true in caz contrar si iter de tip pointer la un
nod care initial pointeaza la primul nod al listei si va parcurge lista pana cand
nodul dorit este gasit sau pana cand se ajunge la ultimul nod al listei, in caz
contrar.
gasit = false
iter = HEAD
while (not gasit and iter NULL)
if iter -> info = k then gasit = true
else iter = iter -> link
endif
endwhile
if gasit then acceseaza nodul pointat de iter
else afiseaza mesajul "Nu exista nici un nod cu informatia data."
endif
inserarea unui nou nod
o la inceputul listei:
Algoritm: Folosim variabilele info_nou ce contine valoarea informatiei
nodului ce trebuie introdus in lista si p pointer la un nod.
Aloca memorie pentru un nod nou. Returneaza p, un pointer la noul nod.
if p NULL then
p -> link = HEAD
p -> info = info_nou
HEAD = p
else OVERFLOW
endif

HEAD
... NULL

p
info nou

Observatie: Algoritmul functioneaza chiar daca lista este nula si se poate


folosi pentru crearea unei liste prin introducerea pe rand, unul cate unul, a elementelor
listei.

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

eliminarea unui nod dat din lista:


Algoritm: Folosim variabilele sterge si iter de tip pointer, sterge este pointer la
nodul care trebuie eliminat din lista si iter care initial pointeaza la primul nod al
listei si va parcurge lista pana cand acesta va pointa la nodul dinaintea nodului de
sters din lista.
iter = HEAD
while (iter NULL and iter -> link sterge)

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

Scrieti un program care implementeaza algoritmii de mai sus


Lab 2
pentru o lista liniara alocata inlantuit.

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.

2.1 Alocarea secventiala

In alocarea secventiala, presupunand ca stiva este definita


T x[N];
unde N este o constanta suficient de mare, T este un tip de date definit anterior
(eventual printr-o definitie de tipul typedef), iar n este numarul de elemente din stiva,
n N, iar elementul din varful stivei este considerat elementul x[n-1],
x[0] x[1] x[2] x[n -1]
... ... ...

atunci operatia de inserare elem_nou S se poate descrie astfel:


if n = N then OVERFLOW
else n = n + 1
x[n-1] = elem_nou
endif

iar operatia de stergere S elem_sters se poate descrie astfel:


if n = 0 then UNDERFLOW
else elem_sters = x[n-1]
n=n1
endif

1
2.2 Alocarea inlantuita

In alocarea inlantuita, folosim aceeasi structura ca si in cazul listei liniare


struct nod
{
T info;
struct nod *link;
};
typedef struct nod NOD;
unde T este presupus definit anterior (eventual printr-o definitie typedef) si notam cu
top pointerul stivei (adica pointerul care pointeaza la elementul din varful stivei).
NOD *top;
Cu aceste notatii, operatiile cu stive se pot descrie astfel:

inserarea unui nod nou: stergerea/accesarea unui nod:


Algoritm: Folosim variabilele info_nou ce Algoritm:
contine valoarea informatiei nodului ce if top = NULL then UNDERFLOW
trebuie introdus in lista si p pointer la un else elem_sters = top -> info
top = top -> link
nod. endif
Aloca memorie pentru un nod nou.
Returneaza p, un pointer la noul nod.
if p NULL then
p -> link = top top
info
p -> info = info_nou
top = p
else OVERFLOW
endif

p info_nou

.............

top

NULL

.......

NULL

Scrieti un program care implementeaza algoritmii de mai sus


Lab 3
pentru o stiva.

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.

3.1 Alocarea secventiala

In alocarea secventiala, presupunand ca avem o coada definita


T x[N];
unde N este o constanta suficient de mare, T este un tip de date definit anterior
(eventual printr-o definitie de tipul typedef), iar elementele cozii sunt in ordine x[R],
x[R+1], ..., x[F], cu indicii 0 R F N 1,
x[R] x[R+1] x[F]
... ... ...

atunci operatia de inserare elem_nou C se poate descrie astfel:


if R = 0 and F = N - 1 then OVERFLOW
else if R > 0 then x[R 1] = elem_nou
R = R 1
else for i = F, R, -1
x[i+1] = x[i]
endfor
x[0] = elem_nou
F = F +1
endif
endif
iar operatia de stergere C elem_sters se poate descrie astfel:
if R > F then UNDERFLOW

1
else elem_sters = x[F]
F = F -1
endif

3.2 Alocarea inlantuita

In alocarea inlantuita, folosim aceeasi structura ca si in cazul listei liniare


struct nod
{
T info;
struct nod *link;
};
typedef struct nod NOD;
unde T este presupus definit anterior (eventual printr-o definitie typedef) si notam cu
front, si respectiv rear, pointerul la primul nod al cozii, si respectiv la ultimul nod.
NOD *front, rear;
Cu aceste notatii, operatiile cu cozi se pot descrie astfel:

inserarea unui nod nou: stergerea/accesarea unui nod:


Algoritm: Folosim variabilele info_nou Algoritm:
ce contine valoarea informatiei nodului if front = NULL then UNDERFLOW
ce trebuie introdus in lista si p pointer la else elem_sters = front -> info
front = front -> link
un nod. endif
Aloca memorie pentru un nod nou.
Returneaza p, un pointer la noul nod.
if p NULL then
p -> link = NULL front
info
p -> info = info_nou
rear -> link = p
rear = p
else OVERFLOW
endif

front
.............

...........
rear
NULL
rear

info_nou
p NULL

Scrieti un program care implementeaza algoritmii de mai sus


Lab 4
pentru o coada.

2
3

S-ar putea să vă placă și