Sunteți pe pagina 1din 18

11.

STIVE I COZI
11.1 Consideraii privind structurilor de date de tip stiv i coad
O stiv, stack n limba englez, este o structur de tip LIFO (Last In First Out =ultimul intrat primul ieit) i este un caz particular al listei liniare n care toate inserrile (depunerile n englez push) i tergerile (sau extragerile -n englez pop) (n general orice acces) sunt fcute la unul din capetele listei, numit vrful stivei. Acest nod poate fi citit, poate fi ters sau n faa lui se poate insera un nou nod care devine cap de stiv.

push

pop

Figura 11.1 Mecanismul de stiv Pentru nelegerea mecanismului unei stive, se poate folosi reprezentarea manevrrii ntr-un depou a vagoanelor de cale ferat sau a unui vraf de farfurii din care putem depune i extrage doar n i din vrful vrafului. Stivele sunt legate i de algoritmii recursivi, la care salvarea variabilelor dintr-o funcie care se apeleaz recursiv se face avnd la baz un mecanism de tip stiv. O stiv poate fi implementat ca o list liniar pentru care operaiile de acces (inserare, tergere, accesare element) sunt restricionate astfel: inserarea se face doar n faa primului element al listei, capul listei; accesarea respectiv tergerea acioneaz doar asupra primului element al listei. O stiv poate fi implementat folosind o structur de date static sau dinamic.

n abordarea static, stiva poate fi organizat pe un spaiu de memorare de tip tablou unidimensional. n abordarea dinamic, implementarea unei stive se poate face folosind o structur de tip list liniar simplu nlnuit n care inserarea se va face tot timpul n capul listei iar tergerea de asemenea se va face n capul listei. O coad (n englez queue) este o structur de tip FIFO (First In First Out = Primul venit primul servit), n care toate inserrile se fac la un capt al ei (numit capul cozii) iar tergerile (extragerile) (n general orice acces) se fac la cellalt capt (numit sfritul cozii). n cazul cozii, avem nevoie de doi pointeri, unul ctre primul element al cozii (capul cozii), iar altul ctre ultimul su element (sfritul cozii). Exist i o variant de coad circular, n care elementele sunt legate n cerc, iar cei doi pointeri, indicnd capul i sfritul cozii, sunt undeva pe acest cerc.

push

pop

Figura 11.2 Mecanismul de coad Se face o analogie cu o cale ferat pe un singur sens sau cu obinuita coad la un ghieu oarecare la care primul venit este i primul servit. Pentru gestiunea unei cozi sunt necesari doi indicatori: - un indicator care indic primul element ce urmeaz a fi scos; - un indicator care indic unde va fi pus urmtorul element adugat la coad. ntr-o abordare static, o coad poate fi implementat folosind un spaiu de memorare de tip tablou unidimensional. n acest caz, adugri i tergeri repetate n coad deplaseaz coninutul cozii la dreapta, fa de nceputul vectorului. Pentru evitarea acestei deplasri, o soluie este ca fiecare operaie de extragere din coad s fie acompaniat de deplasarea la stnga a coninutului cozii cu o poziie. n acest caz, primul element care urmeaz s fie eliminat din coad va fi ntotdeauna pe prima poziie, indicatorul care s indice elementul ce urmeaz s fie scos pierzndu-i utilitatea. Dezavantajul acestei soluii l reprezint necesitatea deplasrii ntregului set de elemente din coad rmase cu o poziie. ntr-o abordare dinamic, o coad poate fi implementat folosind o list liniar simplu nlnuit la care operaiile de acces sunt restricionate corespunztor. Exist dou abordri: -una n care adugarea se face tot timpul la nceputul listei liniare simplu nlnuite iar extragerea se face de la sfritul listei -cea de a doua, n care adugarea se face la sfritul listei iar extragerea se face din capul listei. Pentru implementarea unei cozi, vom folosi aspectele tratate n capitolul de liste liniare.

11.2 Caracteristicile structurilor de date de tip stiv


Caracteristicile unei stive sunt puse n eviden prin comparaie cu structura de date de tip list: - n timp ce pentru gestionarea listei se vehiculeaz cu variabile de tip Tp, care stocheaz adresa componentei (Z1, 1) a listei, componenta numit cap de list, n cazul stivei este memorat adresa ultimei componente (Z'n, 'n), numit vrful stivei; - n timp ce cont cont(n) = NULL n cazul listei, cont ('1) = NULL n cazul stivei; - n timp ce succ (Zj) = Zj+1 n cazul listei, n cazul stivei succ(Zj)=Zj-1; - n timp ce pred (Zj) = Zj-1 n cazul listei, n cazul stivei pred(Zj)=Zj+1; - n tip ce parcurgerea n cazul listei este de la (Z1, 1) spre (Zn, m), n cazul stivei, parcurgerea este de la (Z'n, 'n) spre ( Z'1, '1 ); - n timp ce disciplina lucrului cu elementele listei este primul venit primul servit (FIFO), n cazul stivei regula de vehiculare a elementelor, este ultimul venit primul servit (LIFO). Se observ c stiva este o list invers. Totui, multe dintre tehnicile de rezolvare a problemelor vehiculeaz stive i nu liste, datorit disciplinei de gestionare a elementelor. Regulile de creare i accesare la nivel de stiv, prevd c fiecrui element care se adaug, i se aloc o zon de memorie i acest element devine vrf al stivei. Exist procese n care informaiile se stocheaz n ordinea opus momentului prelucrrii, iar odat folosit, aceasta devine necesar. Stocnd informaiile ntr-o stiv, dup utilizarea informaiilor coninute n vrful stivei, zona de memorie alocat este eliberat (dealocat), vrful stivei mutndu-se cu o component spre baza stivei. Baza stivei este considerat elementul (Z'1, '1). n funcie de cerinele de prelucrare, se obine o fluctuaie a poziiei vrfului stivei, ceea ce creeaz o imagine mai bun pentru dinamica prelucrrii datelor. Astfel, stivele stau la baza gestionrii adreselor parametrilor ce se transmit n funcii, implementrii mecanismelor de recursivitate din limbaje de programare. n general, ideea de stiv sugereaz prelucrri care au un caracter simetric. De exemplu, se consider o prelucrare complet privind: A. ncrcarea componentelor C1 pentru disponibilizarea de resurse ale unui sistem de calcul; B. ncrcarea componentei C2 care este destinat lansrii n execuie a programului utilizatorului; C. efectuarea deschiderii de fiiere n programul utilizatorului C3; D. prelucrri de date i afiri de rezultate C4; E. nchiderea fiierelor; F. ieirea din programul utilizatorului sub supravegherea componentei C3; G. ieirea din lucru sub componenta C2;

H. dezactivarea resurselor sistemului de calcul prin componenta C1. Se construiete stiva din figura 11.3.
vrf de stiv adr (C4) (Z'4, '4)

adr (C3)

(Z'3, '3)

adr (C2)

(Z'2, '2)

adr (C1) NULL

(Z'1, '1) baza activ

Figura 11.3 Structura unei stive Dup parcurgerea pasului D, vrfului stivei coboar la (Z'3, '3) i componenta (Z'4, '4) este dealocat, deci distrus. Folosind informaiile stocate n pasul C, se efectueaz tergerea fiierelor (pasul E). Vrful stivei se coboar la componenta (Z'2, '2), iar componenta (Z'3, '3) este eliberat.
vrf de stiv e

b a NULL

baza activ

Figura 11.4 Structura unei stive pentru inversarea caracterelor unui ir Se execut pasul F, care este simetricul pasului C i apoi vrful coboar spre (Z'1, '1) i se activeaz componentele C1 pentru eliberarea resurselor pas simetric pasului A.

Astfel, de exemplu, dac dorim s inversm poziia elementelor unui ir {a, b, c, d, e}, prin crearea unei stive care s conin aceste elemente, cu modelul grafic din figura 11.4 i prin apelarea unei funcii de parcurgere cu imprimarea elementelor identificate, se obine exact irul { e, d, c, b, a }. n cazul calculrii lui n!, se folosete formula de recuren: P(n) = P(n-1) * n (11.1)

cu P(1) = 1, se creeaz o stiv care conine expresiile ce sunt evaluate cu valorile descresctoare ale lui n. Se construiete stiva care are n informaia util, de la baz spre vrf: n, n 1, n 2, , 3, 2, 1. Valoarea iniial a funciei recursive P(1) = 1, permite prin parcurgerea stivei de la vrf spre baz, obinerea rnd pe rnd a rezultatelor: P(2), P(3),,P(n 2), P(n 1 ), P(n) i ntlnindu-se NULL la baza stivei, procesul se ncheie.

11.3 Operaii de baz cu stive


tergerea unei stive, revine la dealocarea componentelor care formeaz stiva. Se realizeaz prin parcurgerea integral a stivei, cu dealocarea fiecrui vrf. Adugarea unui element n stiv, se face de regul cu mutarea poziiei vrfului stivei pe componenta adugat. tergerea unui element din stiv, se efectueaz de regul asupra elementului din vrful stivei, aceasta cobornd pe componenta ce l precede. Parcurgerea stivei, se efectueaz de la vrf ctre baz. Operaiile de inserare, de concatenare, de sortare a stivelor, nu difer mult de aceleai operaii din interiorul unei liste, dar frecvena lor de utilizare este mult mai redus. Programul urmtor, exemplific unele dintre operaiile care se execut cu structuri de date de tip stiv.
#include <iostream.h> #include <malloc.h> class elementstiva { public: int info; elementstiva *prec; elementstiva() { prec=NULL; } }; class stiva {

public: elementstiva *vs; //varful stivei stiva() { vs=NULL; } void sterg() { elementstiva* aux=vs; if(vs == NULL) cout<<"\n Stiva este vida!"; else { aux = vs->prec; free(vs); while(aux !=NULL) { vs=aux; aux=aux->prec; delete vs; } vs = NULL; } }// void tipar() { elementstiva* aux; if(vs == NULL) cout<<"\n Stiva este vida!"; else { aux = vs; while(aux !=NULL) { cout<<aux->info; aux=aux->prec; } } } void inserare(int el) { elementstiva *aux; aux = new elementstiva(); aux->info = el; aux->prec = vs; vs = aux; }// int extrag() { elementstiva* aux; int el; if (vs == NULL) return -1; else

{ aux = vs->prec; el = vs->info; delete vs; vs = aux; return el; } } }; void main() { stiva st; char opt; int x,el; do { cout<<"\n Optiuni de lucru cu stiva "; cout<<"\n P - Tiparire stiva"; cout<<"\n A - Adaugare element n stiva"; cout<<"\n E - Extragere element din stiva"; cout<<"\n T - Terminare lucru"; cin>>opt; switch(opt) { case 'a': cout<<"element:";cin>>el; st.inserare(el); break; case 'e': { x = st.extrag(); if (x==-1) cout<<"\n Stiva este vida!"; else cout<<"\n Element extras"<<x; } break; case 's': st.sterg(); break; case 'p': st.tipar(); break; default: cout<<"\n Nu exista optiunea!"; } }while (opt!='t'); }

Pe baza meniului afiat prin program, se activeaz funciile n orice ordine, iar situaia de stiv vid, este marcat n mod corespunztor. Crearea stivei se realizeaz prin adugri succesive, care presupun alocri dinamice de

memorie pentru elementele componente. Procedura de tergere elibereaz toat zona de memorie alocat stivei, iar funcia de extragere, elibereaz zona de memorie ocupat de vrful stivei i iniializeaz cu adresa noului vrf al stivei, variabila VS.

11.4 Evaluarea expresiilor matematice cu ajutorul stivei i cozii


Pentru evaluarea expresiilor matematice exist diferii algoritmi. Unul dintre acetia folosete ca structur principal de date stiva. Acest algoritm presupune rearanjarea expresiei ntr-o anumit form astfel nct ordinea operaiilor s fie clar i evaluarea s necesite o singur parcurgere a expresiei. Pentru aceasta se poate folosi forma polonez, inventat de matematicianul de origine polonez Jan Lukasiewicz. Acesta presupune scrierea operatorilor naintea operanzilor. Aceast form mai are o variant numit scrierea polonez invers n care operatorii sunt scrii n urma operanzilor. Tabelul nr. 11.1 Forme ale scrierii unei expresii matematice Expresia matematic (scriere infixat)
4 + 5 4 + 5 * 5 4 * 2 + 3 4 + 2 + 3 4 * (2 + 3)

Expresia n forma polonez (scriere prefixat)


+ 4 5 + 4 * 5 5 + * 4 2 3 + + 4 2 3 * 4 + 2 3

Expresia n forma polonez invers (scriere postfixat)


4 5 + 4 5 5 * + 4 2 * 3 + 4 2 + 3 + 4 2 3 + *

Dup cum se vede din tabelul 11.1, ordinea operanzilor nu se schimb, ei gsindu-se n aceeai ordine ca n expresia matematic. Forma polonez invers are avantaje fa de scrierea prefixat sau infixat: - ordinea n care se efectueaz operaiile este clar; - parantezele nu mai sunt necesare; - evalurile sunt uor de efectuat cu ajutorul calculatorului. Un algoritm de transformare din expresie matematic n scriere postfixat a fost dezvoltat de ctre Edsger Dijkstra (algoritmul macazului al lui Dijkstra Dijkstra Shunting Algorithm). Acest algoritm utilizeaz o stiv n care sunt pstrai operatorii i din care sunt eliminai i transferai n scrierea postfixat. Fiecare operator are atribuit o ierarhie dup cum este prezentat n tabelul 11.2.

Tabelul nr. 11.2 Ierarhia operatorilor Operator


( [ { ) ] } + * /

Ierarhie
1 2 3 4

Algoritmul este: se iniializeaz stiva i scrierea postfixat; att timp ct nu s-a ajuns la sfritul expresiei matematice: - se citete urmtorul element din expresie; - dac este valoare se adaug n scrierea postfixat; - dac este ( se introduce n stiv; - dac este ) se transfer elemente din stiv n scrierea postfixat pn la (; - altfel: att timp ct ierarhia operatorului din vrful stivei este mai mare ierarhia operatorului curent, se trece elementul din vrful stivei n scrierea postfixat; se introduce operatorul curent n stiv. se trec toi operatorii rmai pe stiv n scrierea postfixat. Avnd expresia sub aceast form, se face evaluarea ei. Algoritmul de evaluare folosete tot o stiv pe care sunt pstrai de aceast dat operanzii. Algoritmul este: se iniializeaz stiva; att timp ct nu s-a ajuns la sfritul scrierii postfixate: - se citete urmtorul element; - dac este valoare se depune pe stiv; - altfel (este operator): se extrage din stiv elementul y; se extrage din stiv elementul x; se efectueaz operaia x operator y; se depune rezultatul pe stiv; ultima valoare care se afl pe stiv este rezultatul expresiei. De exemplu, pentru expresia n scriere postfixat: 4 8 2 3 * - 2 / +, algoritmul se execut ca n figura 11.5:

Elem entul citit 4 8

Stiv a 4 8 4 2 8 4 3 2 8 4 6 8 4 2 4 2 2 4 1 4 5

Figura 11.5 Execuia algoritmului Programul care realizeaz evaluarea expresiilor matematice prin algoritmii prezentai a fost realizat folosind programarea orientat pe obiecte. Astfel exist o clas numit stiva care are rolul de a implementa operaiile cu stiva necesare att la generarea expresiei n scriere postfixat ct i la evaluarea expresiei scris n aceast form. Acest obiect are la baz clasa deque (double ended que coad cu 2 capete list dublu nlnuit) care se gsete n biblioteca standard de C++. Fiind nevoie de dou tipuri de stiv (una care pstreaz operanzi i una care pstreaz valori reale) s-a construit o clas stiv template. Pentru pstrarea formei poloneze inverse se utilizeaz o structur de date numit coad care este implementat tot printr-un obiect care are la baz clasa deque. n cadrul acestei clase sunt implementate funcii care ajut la programarea algoritmului. Exist funcii care verific dac primul element din coad reprezint o valoare (eNumar()), extrag primul element ca valoare (getNumar()). Mai exist o clas fisier care are rolul de a parcurge fiierul n care se afl expresie aritmetic ce trebuie evaluat. Codul surs al programului este:

#pragma warning(disable:4786) #include <deque> #include <iostream> #include <string> using namespace std; #include #include #include #include "ierarhie.h" "stiva.h" "fisier.h" "coada.h"

/////////////////////Variabile globale fisier f; stiva <char> OPER; stiva <double> EVAL; coada POSTFIX; void eroare(const char * text,int nr){ cout<<"\n"<<text<<"\n"; exit(nr); } void scrierePOSTFIX(string &op) { char opc; if(op=="") return; int ierOp=ierarhie(op); //daca e valoare, se trece direct in scrierea postfixata if(!ierOp) POSTFIX.adauga(op); else { opc=op[0]; switch(ierOp) { //daca e paranteza deschisa, se introduce pe stiva case PD: //([{ OPER.push(opc); break; //daca e paranteza inchisa, se extrag toate elementele //de pe stiva pana la intalnirea unei paranteze deschise case PI: //)]} while(ierarhie(OPER.top())!=PD) POSTFIX.adauga(OPER.pop()); OPER.pop(); break; //daca e alt operator, se extrag elemente de pe stiva atat //timp cat stiva nu este goala si ierarhia operatorului //din varful stivei este mai mare sau egala decat ierahia //operatorului curent //la sfarsit operatorul curent se depune pe stiva default: while((!OPER.eGoala())&&ierarhie(OPER.top())>=ierOp)

POSTFIX.adauga(OPER.pop()); OPER.push(opc); break; } } } void evalPOSTFIX() { string op; char opc; double t1, t2, rez; //atat timp cat nu s-a ajuns la sfarsitul expresiei postfixate while(!POSTFIX.eGoala()) { //daca elementul curent este numar acesta se depune pe stiva while(POSTFIX.eNumar()) EVAL.push(POSTFIX.getNumar()); //se extrag 2 valori de pe stiva t2=EVAL.pop(); t1=EVAL.pop(); op=POSTFIX.extrage(); opc=op[0]; //se efectueaza operatia dintre cele 2 valori switch(opc) { case '+': rez=t1+t2; break; case '-': rez=t1-t2; break; case '*': rez=t1*t2; break; case '/': rez=t1/t2; break; default: eroare("Operator necunoscut!", 1); } //rezultatul operatiei se depune pe stiva EVAL.push(rez); } } void main(int argc, char* argv[]) { //se primeste ca parametru numele fisierului in care se //gaseste expresia matematica ce se doreste a fi evaluata string numefis; if(argc!=2)

{ cout<<"Specificati scrierea postfixata!"; exit(1); } numefis=argv[1]; f.open(numefis.c_str());

numele

unui

fisier

pentru

care

doriti

if(f.bad()) { cout<<"Fisierul "<<numefis<<" nu exista.\n"; exit(2); } cout<<"Lucrez cu fisierul "<<numefis<<".\n";

//parcurge fisierul primit ca parametru string op; while(!f.eof()) { op=f.citeste(); scrierePOSTFIX(op); } f.close(); //se extrag toti operatorii ramasi pe stiva while(!OPER.eGoala()) POSTFIX.adauga(OPER.pop()); //afiseaza expresia din fisier in forma postfixata cout<<"\n\nExpresia in scriere postfixata\n"; for(int i=0;i<POSTFIX.size();i++) cout<<POSTFIX[i]<<" "; //efectueaza calculul expresiei evalPOSTFIX(); //afiseaza valoarea expresiei (ultima valoare ramasa pe stiva) cout<<"\nValoarea expresiei este: "<<EVAL.pop()<<"\n"; } //ierarhie.h - informatii despre ierarhia operatorilor #define PD 1 #define PI 2 #define PLUSMINUS 3 #define INMIMP 4 int ierarhie(char c) { switch (c) { case '+': case '-': return PLUSMINUS;

break; case '*': case '/': return break; case '(': case '[': case '{': return break; case ')': case ']': case '}': return break; default: return break; } };

INMIMP;

PD;

PI;

0;

int ierarhie(string & s) { char c; c=s[0]; return ierarhie(c); }; /////coada.h - Clasa coada void eroare(const char * text,int nr); class coada{ private: //implementarea cozii cu o clasa template double ended que din //C++ Standard Template Library deque <string> s; public: //verifica daca coada e goala bool eGoala() { return s.empty(); };

//adauga un element in coada void adauga(const string & str) { s.push_back(str); };

//adauga un element in coada void adauga(char c) { string str;

str=c; s.push_back(str); }; //intoarce si extrage primul element din coada string extrage() { string t; if(!s.empty()) { t=s.front(); s.pop_front(); } else eroare("Eroare de sintaxa.",2); return t; }; //verifica daca primul element din coada reprezinta un numar bool eNumar() { char *stop; string st=s.front(); strtod(st.c_str(), &stop); return (*stop) == 0; }; //extrage elementul din coada ca numar double getNumar() { char *stop; double v; string st; st=s.front(); s.pop_front(); v=strtod(st.c_str(), &stop); return v; }; //intoarce a i-lea element din coada (0 - primul element) string operator [] (unsigned int i) { if((i>=0) && (i<s.size())) return s[i]; eroare("Eroare de sintaxa.",4); return ""; }; //intoarce numarul de elemente din coada int size() { return s.size(); }; }; /////stiva.h - Clasa template stiva void eroare(const char * text,int nr);

template <class T> class stiva{ private: //implementarea stivei cu o clasa template double ended que din //C++ Standard Template Library deque <T> s; public: //verifica daca stiva e goala bool eGoala() { return s.empty(); }; //introduce un element in stiva void push(T str) { s.push_back(str); }; //intoarce si extrage elementul din stiva T pop() { T t; if(!s.empty()) { t=s.back(); s.pop_back(); } else eroare("Eroare de sintaxa.",5); return t; }; //intoarce elementul din stiva T top() { if (!s.empty()) return s.back(); eroare("Eroare de sintaxa.",6); return NULL; }; }; /////fisier.h - clasa fisier #include <string> #include <fstream> using namespace std; class fisier { public: //citeste o noua secventa de caractere din fisierul de date string citeste(); //deschide fisierul

void open(const char * numefis); //verifica daca fisierul a fost deschis cu succes bool bad(); //verifica daca s-a ajuns la sfarsitul fisierului bool eof(); //inchide fisierul deschis void close(); private: // fisierul din care se citesc datele ifstream fis; // verifica daca parametrul primit este separator sau operator bool isSeparator(string s); // verifica daca parametrul primit este separator sau operator bool isSeparator(char c); //scoate spatiile albe din datele de intrare (tab, spatiu, sfarsit de linie) void eatWhite(); };

/////fisier.cpp - clasa fisier #include "fisier.h" // verifica daca parametrul primit este separator sau operator bool fisier::isSeparator(string s) { char c=s[0]; return (c=='+') || (c=='-') || (c=='*') || (c=='/') || (c=='(') || (c=='[') || (c=='{') || (c==')') || (c==']') || (c=='}') || (c=='\n') || (c=='\t') || (c==' '); }; // verifica daca parametrul primit este separator sau operator bool fisier::isSeparator(char c) { return (c=='+') || (c=='-') || (c=='*') || (c=='/') || (c=='(') || (c=='[') || (c=='{') || (c==')') || (c==']') || (c=='}') || (c=='\n') || (c=='\t') || (c==' '); } //scoate spatiile albe din datele de intrare (tab, spatiu, sfarsit de linie) void fisier::eatWhite() { while((fis.peek()==' ') || (fis.peek()=='\n') || (fis.peek()=='\t')) fis.get(); } //citeste o noua secventa de caractere din fisierul de date string fisier::citeste() { string rez="";

rez=fis.get(); if(!isSeparator(rez)) while(!isSeparator(fis.peek()) && !fis.eof()) rez+=fis.get(); eatWhite(); return rez; } //deschide fisierul void fisier::open(const char * numefis) { fis.open(numefis); eatWhite(); } //verifica daca fisierul a fost deschis cu succes bool fisier::bad() { return fis.bad(); } //verifica daca s-a ajuns la sfarsitul fisierului bool fisier::eof() { return fis.eof(); } //inchide fisierul deschis void fisier::close() { fis.close(); }

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