Sunteți pe pagina 1din 9

STRUCTURI DE DATE SI ALGORITMI - LABORATOR 3

---- Stive şi Cozi ---

Aşa cum s-a menţionat în laboratorul precedent, listele înlănţuite şi în particular cele
simplu înlănţuite sunt printre cele mai importante structuri de date nu numai prin perspectiva
faptului că sunt ele însele ades utilizate dar şi datorită faptului că sunt parte din implementarea
altor structuri cu o largă aplicabilitate precum stivele, cozile, tabelele hash, etc.

O stivă, la nivel fundamental, este o structură de date abstractizată ce acumulează


datele într-o colecţie înlănţuită şi permite doar două operaţii asupra acestora: adugare (Push) şi
extragere (Pop) - Fig.1. În plus, cele două operaţii afectează doar un singur capăt al structurii,
cunoscut adesea sub denumirea de "vârf-ul stivei". Uneori se mai convine şi asupra
implementării unei a treia operaţii, peek, ce are rolul de a întoarce valoarea informaţiei stocate
în vârful stivei fără a o extrage din aceasta. Din această perspectivă stivele mai sunt cunoscute
şi drept structuri LIFO - Last In, First Out, denumirea fiind cât se poate de clară în ceea ce
priveşte funcţionarea structurii: un nou element adăugat (ultimul din colecţie) este întotdeauna
primul care va fi extras.

Figura 1 - Structura de date de tip stiva, reprezentarea generică fără specificarea unui mod de
implementare.

Stivele sunt utilizate în multe aplicaţii, cu implementări atât software cât şi hardware,
precum organizarea memoriei statice asociate cu un proces (i.e. variabilele alocate 'static' sunt
stocate într-o structură de tip stivă), reţinerea adreselor de întoarcere la instrucţiunea
ulterioară unui apel de funcţie (în memoria de program), implementarea maşinilor stivă
(sisteme de calcul reale sau emulate a căror instrucţiuni sunt reţiunte şi executate cu ajutorul
stivelor). De asemenea sunt parte integrantă din diferiţi algoritmi precum algoritmul
backtracking, parcurgerea în adâncime a grafurilor, algoritmul de rezolvare a problemei
turnurilor din Hanoi, etc.

Stivele pot fi implementate cu ajutorul tablourilor sau listelor simplu înlanţuite, aşa cum
a fost menţionat şi mai devreme. Dacă prima variantă oferă o viteză de execuţie crescută este
totuşi limitată de dimensiunea prealocată pe care o are tabloul. În cazul în care dimensiunea
datelor ce trebuie stocate depăşeşte dimensiunea stivei, trebuie făcută o realocare de
memorie, copierea datelor în noua structură, update-ul pointerului de vârf al stivei şi eliberarea
zonei vechi de memorie. O altă abordare ar sugera suprascrierea înregistrărilor cele mai vechi
din stivă însă această abordare de natură circulară ar complica de asemenea implementarea şi
ar putea fi chiar dăunătoare anumitor aplicaţii. Aşadar pentru flexibilitate şi uşurinţă în
construirea unei soluţii la o problemă dată (dar în detrimentul vitezei de execuţie), se preferă
varianta utilizării listelor simplu înlănţuite. În acest caz implementarea este similară cu cea de la
laboratorul precedent, programatorului nu-i rămâne decât să constrângă operaţiile ce se
realizează asupra listelor la cerinţele celor două operaţii fundamentale ce se pot realiza asupra
stivelor.

Pentru început reamintim că o celulă / nod dintr-o listă este reprezentată în cadrul
memoriei calculatorului cu ajutorul unui struct ce defineşte un tip de date impus de
programator (în contextul aplicaţiei pe care o dezvoltă). Aşadar şi o înregistrare dintr-o stivă va
fi reţinută cu ajutorul unei variabile de tipul struct. Se exemplifică mai jos pentru o problemă în
care se cere implementarea unei stive ce reţine valori întregi:

struct My_Stack
{
int info; // informatia retinuta
My_Stack * next; // pointer catre urmatorul element din stiva
};

Continuând exemplul de mai sus, cele două operaţii fundamentale Push / Pop se pot
implementa aşa cum se va vedea mai jos. Trebuie înţeles că în memorie stiva va fi tot o
structură înlănţuită terminată cu un element de semnalizare (NULL) doar că extragerea /
inserarea de noduri va fi permisă doar pe la un singur capăt.

void Push(My_Stack ** pTop, int inputInfo)


{
My_Stack * pTmp = new My_Stack; // aloc memorie pentru un element nou
pTmp -> info = inputInfo; // scriu informatia in el
pTmp -> next = *pTop; // fac legatura catre varful stivei
*pTop = pTmp; // actualizez varful stivei. Celula adaugata a devenit
// noul varf.
}

My_Stack * Pop(My_Stack ** pTop)


{
My_Stack * pTmp = *pTop; // retin varful stivei

if(!pTmp) // daca nu exista intorc NULL


return NULL;

*pTop = (*pTop) -> next; // daca exista, deplasez varful si intorc vechiul varf
pTmp -> next = NULL; // (optional) desprind efectiv nodul extras
return pTmp; // intorc un pointer catre acesta
}

Figura 2 - schema de principiu pentru adăugarea unui nou nod in stivă. Stiva este reprezentată cu
ajutorul unei liste simplu înlănţuite.
Figura 3 - schema de principiu pentru extragerea unui nod din stivă. Stiva este reprezentată cu ajutorul
unei liste simplu înlănţuite

În cazul în care se doreşte şi implementarea peek-ului ("a trage cu ochilul") se poate utiliza
codul de mai jos:

int Peek(My_Stack ** pTop)


{
if(!*pTop) // daca stiva nu are nici un el
return MY_ERR; // un cod de eroare definit de dezvoltator

return (*pTop) -> info; // intorc informatia din vf. stivei dar nu sterg nodul
}

Exerciţii:

1) Cu ajustorul unei liste simplu înlănţuite, să se implementeze o structură de tip stivă


conţinând valori numerice de tip întreg cu valori citite de la tastatură. Să se afiseze toate
valorile din stivă cu ajutorul unei funcţii adiţionale DisplayData care primeşte un pointer la
vârful stivei, parcurge toate elementele şi la fiecare pas al parcurgerii afisează informaţia
stocată. Folosind apeluri succesive ale funcţiei Pop să se şteargă toate elementele stivei.

2) Folosind o structură de tip stivă să se realizeze conversia unui număr din format zecimal în
format binar. Să se afiseze rezultatul folosind apeluri succesive ale funcţiei Pop.

3) Folosind o stivă de caractere, verificaţi dacă un cuvânt citit de la tastatură este un palindrom.
(un cuvânt consideră a fi palindrom dacă citit de la ambele capete reprezintă acelaşi lucru).

Cozile sunt structuri de date înlănţuite, asemănătoare listelor (şi chiar implementate cu
ajutorul acestora) însă, precum stivele, sunt supuse la anumite constrângeri cu privire la accesul
spre date respectiv scrierea de informaţii noi: Într-o coadă este permisă scrierea informaţiei
doar după ultima înregistrare iar extragerea informaţiei se face doar de la primul element. Din
acest considerent o coadă mai este cunoscută şi sub denumirea de FIFO - First In, First Out.

Figura 4 - Schema de principiu pentru o coadă implementată cu ajutorul unei liste simplu
înlănţuite şi a unei variabile struct adiţională ce memorează pointerii către capetele acesteia.

Cozile sunt adesea utilizate în programare atât de sine stătătoare în implementări care
prezintă diferite modalităţi de deservire ale unor cereri venite spre o entitate ce oferă un
serviciu sau ca parte integrantă din alţi algoritmi precum A*, parcurgerea în lăţime a grafurilor
(Breadh-First Search), etc.

Sunt cel mai adesea implementate cu ajutorul listelor simplu înlănţuite cu ajutorul unei
modificări menite să optimizeze adăugarea de noi date după cum vom vedea imediat. Un nod
informaţional dintr-o coadă este implementat precum în cazul listelor simplu înlănţuite cu
ajutorul unui struct. Dar accesul efectiv la o coadă nu se va face prin intermediul unui pointer
de tipul acestui struct ci se va utiliza o nouă structură adiţională ce reţine pointeri către primul
şi ultimul element din coadă în ideea de a avea acces imediat la capete şi a realiza rapid
operaţiile de extragere şi mai ales inserţie, fără a fi nevoie de o parcurgere a tuturor
elementelor (precum în cazul listelor simplu înlănţuite) pentru a ajunge la elementul după care
se face inserţia.

Exemplificăm mai jos pentru o structură de tip coadă care reţine valori numerice întregi.
Trebuie remarcat în plus că vârful cozii este considerat a fi nodul cel mai apropiat de NULL
terminal în timp ce ultimul element din coadă ar fi echivalent cu primul element din lista simplu
înlănţuită - vezi Fig. 4.

// struct-ul asociat cu un element din coada


struct My_Nod
{
int info;
My_Nod * next;
};

// struct-ul de acces spre elementele din coada.


// Acesta retine pointeri catre primul si ultimul element din coada
struct My_Queue
{
My_Nod * primul;
My_Nod * ultimul;
};

//functia de adaugare a unui element in coada


void AddNod(My_Queue ** queue, int info_nou)
{
My_Nod * tmp;
//caz particular: coada nu exista
if(!*queue)
{
*queue = new My_Queue;
(*queue)->primul = (*queue)->ultimul = new My_Nod;
(*queue)->primul->next = NULL; //conventia de reprezentare a cozii
(*queue)->primul->info = info_nou;

return;
}

//caz general: pas 1 rezerv mem pentru un nou nod si inscriu datele
tmp = new My_Nod;
tmp->next = NULL;
tmp->info = info_nou;

// pas 2: fac legatura in coada


(*queue)->primul->next = tmp;

//pas 3: deplasez pointerul de varf pe noul element


(*queue)->primul = tmp;
}

Figura 5 - schema de principiu pentru adăugarea unui nod într-o structură de tip coadă implementată cu
ajutorul unei liste simplu înlănţuite

// functia de extragere a unui element din coada


My_Nod * GetNod(My_Queue ** queue)

My_Nod * tmp;

//caz particular: coada nu exista


if(!*queue)
return NULL;
//caz particular: coada este vida, sterg pointerii de acces si marchez coada cu NULL
if(!(*queue)->ultimul)
{
delete *queue;
*queue = NULL;
return NULL;
}

// caz general: pas 1 - extrag nodul ...


tmp = (*queue)->ultimul;

// pas 2: ... si fac update spre zonele la care indica pointerii de access
(*queue)->ultimul = (*queue)->ultimul->next;

// pas 3 (optional): rup efectiv legatura nodului extras cu restul cozii


tmp->next = NULL;

return tmp;
}

Figura 6 - schema de principiu pentru extragerea unui nod dintr-o structură de date de tip coadă
implementată cu ajutorul listelor simplu înlănţuite.

Exerciţii:

1) Să se implementeze o structură de tip coadă conţinând informaţii numerice de tip întreg. Să


se citească informaţia de la tastatură şi apoi să se scrie o funcţie recursivă care afişează
conţinutul cozii. Funcţia primeşte drept parametru un pointer către o structură de tip
My_Queue şi nu întoarce date. Prin apeluri repetate la funcţia GetNod, să se şteargă fiecare
element al cozii.

2) În următoarea secvenţă, o literă înseamnă AddNod şi un asterisk înseamnă GetNod. Să se


afişeze rezultatul fiecărui apel al funcţiei GetNod când se procesează următoarea secvenţă de
valori prin intermediul unei structuri de tip coadă:

123*4*321***23***43*2***1*

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