![]() ![]()  | 
| Politica de confidentialitate | 
| 
 | 
|  •  domnisoara hus • legume • istoria unui galban • metanol • recapitulare • profitul • caract • comentariu liric • radiolocatia • praslea cel voinic si merele da aur  | |
| Implementarea modelelor de date in C++ | ||||||
  | 
  ||||||
| 
	 
  Programele pentru calculatoare utilizeaza de obicei tabele de informatii care 
  presupun anumite relatii structurale intre datele care le compun. Aceste 
  relatii structurale diferentiaza mai multe modele de organizare a datelor, dintre 
  care cele mai importante sunt: liste, arbori, multimi. Toate aceste modele se 
  reprezinta in programare prin colectii de date care, in programarea 
  orientata pe obiecte, se implementeaza prin clase (tipuri definite de utilizator). 
   s2s18sj Fiecare programator isi poate defini propriile implementari ale modelelor de date, sau poate sa foloseasca unele tipuri definite in bibliotecile sistemului, pe care le modifica in mod corespunzator cerintelor aplicatiei. In ambele situatii, insa, este necesara cunoasterea modului de organizare interna a unor astfel de modele de date, precum si posibilitatile de reprezentare si de exploatare a acestora prin intermediul claselor de colectii, a claselor container si a claselor template. Reprezentarea colectiilor de date prin intermediul claselor derivate si al claselor template vor fi prezentate in sectiunile 5 si, respectiv, 7. 3.1 Liste liniare O lista liniara este o secventa finita de elemente de un tip dat. Secventa 
  in care apar elementele este definitorie. De exemplu, lista (a1, a2,……an 
  ) este diferita de lista (a2, a1,…..an). De asemenea, in lista este 
  posibil sa existe elemente cu acceasi valoare (elemente duplicat, ceea ce in 
  multimi nu este admis). Lungimea listei este data de numarul de elemente in 
  lista. Modelul de lista include si lista vida, care are zero elemente. Daca 
  lista nu este vida, atunci ea contine un element de inceput, numit capul 
  listei (head), iar restul elementelor se numeste coada listei (tail). Elementele 
  listei pot fi identificate prin pozitia lor in lista, un index i, care 
  poate avea valori 1 ? i ? n , unde n este lungimea listei. De multe ori, (mai 
  ales in implementarile in limbajul C), elementele listei se indexeaza 
  de la 0 la n-1, asa cum sunt indexate datele in tablourile din C. • Operatia de inserare consta in adaugarea unui nou element x unei 
  liste L. Pozitia in care se poate insera noul element este oricare pozitie 
  in lista: inaintea primului element (in aceasta situatie se 
  modifica capul listei), dupa ultimul element, sau intre oricare doua elemente. 
  Este admisa inserarea unui nou element care are aceeasi valoare cu un alt element 
  component al listei.  3.1.1 Liste ordonate Operatia de sortare intr-o lista presupune ca elementele listei apatin 
  unei multimi in care se poate defini o relatie de ordine liniara. Fie 
  o lista: • pentru fiecare doua elemente a si b ale multimii S este indeplinita 
  una din conditiile: a < b, a = b sau a > b; Simbolul “ < ” semnifica “precede”. Un exemplu de 
  multime care satisface conditiile de ordine liniara este multimea numerelor 
  intregi. Un alt exemplu este multimea literelor alfabetului Latin. Intr-o 
  secventa S = As1, s2, ….snS compusa din elemente care apartin unei multimi 
  ordonabile liniar, rangul k al unui element si este definit ca numarul de elmente 
  in S care preced elementul si plus 1.  In programarea orientata pe obiecte elementele unei liste sunt de un tip dat, predefinit sau definit de utilizator (clasa). Pentru tipurile de date fundamentale ale limbajului C++, relatia de precedenta “ < “ este definita printr-un operator de relatie. Pentru tipurile definite de utilizator, obiectele (instante ale unei clasei) dintr-o lista pot fi ordonate daca pentru clasa respectiva se defineste operatia de precedenta. In general, aceasta operatie se defineste prin supraincarcarea operatorul de relatie “ < ”. 3.1.2 Stive si cozi Pe baza modelului de date lista liniara sunt definite alte tipuri de date abstracte, cum sunt stiva (stack) si coada (queue), prin restrictionarea modului de executie a operatiilor asupra listei. Stiva este o lista in care toate operatiile (de inserare si de extragere) au loc printr-un singur capat al listei, care se numeste varful stivei (top) (Fig. 3.1-a). Datorita faptului ca operatiile in stiva se executa printr-un singur capat, aceste operatii se desfasoara intotdeauna in ordinea ultimul inserat, primul extras (last-in, first-out - LIFO), de unde si numele LIFO sub care este frecvent intalnit acest tip de date. Coada este o lista in care operatiile de inserare si extragere au loc prin capete diferite ale listei. Operatia de inserare se executa la sfarsitul cozii (spate, rear), in timp ce extragerea se executa din inceputul (fata, front) cozii (Fig. 3.1-b). Temenul de lista FIFO, frecvent folosit pentru coada, provine din acest mod de executie: primul intrat, primul extras (first-in, first-out, FIFO). Fig. 3.1 Doua tipuri de liste: stiva (a) si coada(b) Modelul de date abstract lista se poate reprezenta, in principal, prin doua structuri (tipuri) de date: tablou de elemente (vector) si lista inlantuita. 3.1.3 Implementarea listelor liniare prin tablouri In aceasta implementare elementele listei sunt memorate intr-un 
  tablo, ceea ce inseamna ca elementele vecine in lista ocupa locatii 
  vecine in memorie. Implementarea prin tablouri a listelor beneficiaza 
  de acces rapid la un element intr-o pozitie data. In schimb, inserarea 
  sau stergerea unui element poate necesita operatii costisitoare de deplasare 
  a unor elemnte pentru a face loc unuia nou (la inserare) sau pentru a le realipi, 
  la stergerea unui element.  Fig. 3.2 Implementarea ca vector a unei liste liniare. In abordarea orientata pe obiecte, o lista de elemente de tipul T implementata ca vector se poate reprezenta printr-o clasa care, in general, memoreaza vectorul de date de tipul respectiv T, dimensiunea vectorului d si numarul n de elemente ale listei. Functiile membre ale clasei modeleaza operatiile impuse de destinatia listei. De exemplu, pentru o clasa care reprezinta o stiva, sunt necesare operatiile de inserare (push) si de extragere (pop). Aceasta a fost modul de implementare din Exemplul 2.5 din sectiunea precedenta: 
  clasa IntStack implementeaza o stiva de numere intregi, reprezentata printr-un 
  tablou (vector) alocat static in memorie, de dimensiune (MAX_SIZE) care 
  este o constanta in program. In implementarea prin vector a stivei, 
  operatiile de inserare si de extragere a elementelor stivei (push() si pop()) 
  se executa fata de ultimul element al listei care reprezinta stiva: se insereaza 
  un element dupa ultimul element memorat (cu indice maxim in lista) si 
  se extrage ultimul element memorat. Aceasta pozitie de varf a stivei este 
  memorata in clasa IntStack printr-un indice (tos). Intr-o versiune 
  similara de stiva (cea prezentata in subsectiunea 7.1 ), in clasa 
  template TStack varful stivei este memorat printr-un pointer (top), dar 
  aceasta diferenta este nesemnificativa. Implementarea unei cozi se poate face folosind un vector de elemente, dar pentru 
  accesul la ambele capete ale listei, asa cum este necesar in structora 
  FIFO, se aranjeaza astfel ca vectorul sa fie parcurs circular.  Initial coada este goala, numarul de elemente ocupate in vector (n) este egal cu zero, iar indicii de inserare si de extragere sunt de asemenea initializati cu valoarea 0. Un element se insereaza in vector in pozitia (indicele) writer, dupa care acesta este incrementat circular: writer = (writer+1)%d; si de asemenea este incrementat numarul n de elemente ale listei. Daca lista nu este vida (daca n > 0) se poate extrage un element din vector de la adresa reader, dupa care acest indice este incrementat circular: reader = (reader+1)%d; iar numarul de elemente n este decrementat. Operatia de inserare trebuie sa fie permisa numai daca mai exista pozitii libere in vector (daca n < d). Operatia de extragere trebuie sa fie permisa numai daca exista cel putin un element in vector (n > 0). Listele, impreuna cu ale modele de date (cum sunt multimile sau arborii) sunt in general denumite si colectii de date, deoarece contin mai multe elemente de acelasi tip, cu o anumite relatii intre ele. 3.1.4 Implementarea colectiilor de date prin liste inlantuite Principalul dezavantaj al listelor inlantuite il reprezinta timpul mare si variabil de cautare al unui element dat in lista, dar chiar si asa, listele inlantuite sunt frecvent utilizate in programare. Reprezentarea listelor inlantuite este posibila in orice limbaj 
  procedural, prin definirea structurilor compuse din campurile corespunzatoare 
  (elementul listei si pointerii la noduri vecine).  O reprezentare mai completa a unei liste inlantuite se poate face prin doua tipuri de date (clase): o clasa reprezinta un nod al listei si o alta clasa reprezinta lista insasi; in subsectiunile care urmeaza sunt descrise astfel de reprezentari ale listelor simplu si dublu inlantuite de numere intregi. 3.1.4.1 Lista simplu inlantuita de numere intregi  Se definesc doua clase pentru reprezentarea unei liste simplu inlantuite 
  de numere intregi: o clasa IntSListNode pentru reprezentarea unui nod 
  al listei si o clasa IntSList, pentru reprezentarea listei. In clasa IntSListNode 
  este memorat elementul listei (un numar intreg, variabila int v) si pointerul 
  link la nodul urmator. In clasa IntSList este memorat pointerul la primul 
  nod al listei (first), pointerul la ultimul nod al listei (last) si numarul 
  de elemente ale listei (count). Memorarea ultimului nod al listei (last) si 
  al numarului de elemente (count) nu sunt strict necesare, deoarece ele pot fi 
  calculate de fiecare data pornind de la primul nod. Dar aceste operatii parcurg 
  secvential intreaga lista, ceea ce reprezinta un consum de timp de care 
  poate fi considerabil pentru listele de dimensiuni mari si de aceea memorarea 
  acestor variabile in clasa IntSList maresc eficienta de executie a operatiilor 
  cu listele simplu inlantuite. class IntSListNodeA // nod in lista simplu inlantuita int v; // elementul listei, nr. intreg Constructorul de initializare al clasei IntSListNode construieste un nod al 
  listei in care elementul primeste valoarea transmisa ca argument si pointerul 
  de inlantuire nul (0). inline IntSList::IntSList(int x)A Constructorul de initializare cu doua argumente creaza o lista inlantuita in care elementele (pe care le creaza in memoria heap) sunt in ordinea din vectorul de numere intregi primit ca argument. IntSList::IntSList(int* p, int n)A first = 0; for (int i=0;i<n;i++)A  Se mai poate defini un constructor avand ca argument o referinta (const) la un obiect din clasa IntArray, care contine un vector de numere intregi (Exercitiul 3.6): IntSList::IntSList(const IntArray& array)A int n = array.GetSize(); first = 0; for (int i=0;i<n;i++)A  IntSList::IntSList(IntSList& r)A first = 0; last = 0; count = 0; Destructorul clasei IntSListNode nu are de executat nici-o operatie utila, alta decat sa afiseze un mesaj care sa permita ulterior observarea compotarii acestei clase. Destructorul clasei IntSList parcurge lista inlantuita de noduri si le sterge succesiv din memorie folosind operatorul delete: IIntSListNode()A  cout << "Destructor nod v = " << v <<endl; void IntSList::Display()A Fie urmatoarea functie fslist1(): void fslist1()A int valai = A1,2,3,4,5S; Obiectul list1 de clasa IntSList care se creaza la executia acestei functii este reprezentat in Fig. 3.4. Fig. 3.4 Obiect (lista simpla inlantuita de numere intregi) din clasa IntSetList Mesajele afisate la executia functiei fslist1() sunt: 1 2 3 4 5 Operatiile de dictionar implementate pentru aceasta lista permit adaugarea unui nou element la inceputul sau sfarsitul listei (AddHead() si AddTail()) si extragerea (citire valoare si eliminare) unui element din capul sau coada listei (RemoveHead() si RemoveTail()). In aceste operatii de adaugare si stergere element nu trateaza cazurile de eroare (eroare de alocare a unui nod nou la inserare sau eroarea de lista vida la extragere). Aceste situatii se vor trata ca exceptii (in sectiunea 8). void IntSList::AddHead(int x)A Alte functii membre ale clasei IntSList sunt functii de aflare a elementelor de inceput si de sfarsit ale liste, GetHead() si GetTail() si functia Lookup() care returneaza numarul de elemente cu o valoare data aflate in lista. Definitiile acestor functii sunt lasate ca un exercitiu simplu. Lista simplu inlantuita definita prin clasele IntSListNode si IntSList se poate folosi pentru a implementa o stiva de numere intregi. Functia AddHead() este echivalenta operatiei push, iar functia RemoveHead() este echivalenta operatiei pop. Functia fslist2() evidentiaza acest lucru: void fslist2()A S De acemenea, aceasta lista se poate folosi pentru implementarea unei cozi de numere intregi. Functia AddTail() este echivalenta operatiei de inserare, iar functia RemoveHead() este echivalenta operatiei de extragere. De exemplu: void fslist3()A 3.1.4.2 Lista dublu inlatuita de numere intregi Pentru implementarea unei liste dublu inlantuite de numere intregi se folosesc doua clase: clasa IntDListNode, care reprezinta un nod al listei si clasa IntDList, care reprezinta lista dublu inlantuita. In clasa IntDListNode este memorat elementul listei (un numar intreg, variabila int v), pointerul next la nodul urmator si pointerul prev la nodul precedent. In clasa IntDList este memorat pointerul la primul nod al listei (first), pointerul la ultimul nod al listei (last) si numarul de elemente ale listei (count). Pentru fiecare clasa s-au definit constructori, destructor si functii membre care implementeaza operatii asupra listei. Clasa IntDList este declarata friend in clasa IntDListNode, pentru ca aceasta din urma sa poata accesa datele protejate ale clasei IntDList. class IntDListNodeA unsigned int v; Constructorul cu un argument de tip intreg al clasei IntDList se defineste astfel: inline IntDList::IntDList(int x)A Ceilalti constructori se pot implementa mai simplu folosind una din functiile de inserare a unui element, AddHead() sau AddTail(). La inserarea unui element intr-o lista dublu inlantuita trebuie sa fie refacute legaturile intre nodul nou introdus si nodurile existente pentru ambele directii de inlantuire. In Fig. 3.5 sunt reprezentate modificarile de pointeri la inserarea unui element nou in fata primului element al listei (functia AddHead()). Definitiile functiilor de inserare elemente AddHead() si AddTail() sunt urmatoarele: void IntDList::AddHead(int x)A Cazul particular al listei vide este tratat separat. Ceilalti constructori, destructorii si functiile acestor clase sunt asemanatoare cu cele ale claselor IntSListNode si IntSList si sunt propuse ca un exercitiu simplu (Exercitiul 3.3). Fie functia: void fdlist1()A int vai = A1,2,3,4,5S; In aceasta functie se creaza lista dublu inlantuita de numere intregi list1, cu valori ale elementelor luate din vectorul v. Elementele listei sunt afisate in ordine directa (incepand cu primul element al listei) si in ordine inversa (incepand cu ultimul element al listei). La iesirea din functia fdlist1() obiectul list1 este distrus, iar la distrugerea acestuia sunt apelati pe rand destructorii nodurilor componente. Mesajele afisate la executia acestei functii sunt urmatoarele: 1 2 3 4 5  Lista dublu inlantuita implementata prin clasele IntDListNode si IntDList poate fi folosita ca stiva de numere intregi, (operatiile push si pop utilizand functiile AddHead() si, respectiv, RemoveHead()), sau ca o coada de numere intregi (operatiile de inserare si extragere utilizand functiile AddTail() si, respectiv, RemoveHead()). In functia fdlist2() sunt exemplificate ambele modalitati de functionare. void fdlist2()A  /* Functionarea ca o coada a listei */ Avantajul principal al listei dublu inlantuite fata de lista simplu inlantuita 
  este acela ca permite accesul la fel de rapid din ambele capete ale listei, 
  fiind prin aceasta mult mai eficienta decat lista simplu inlantuita. 
  De exemplu, implementarea functiei RemoveTail() din clasa IntSList (lista simplu 
  inlantuita) este excesiv de ineficienta, datorita parcurgerii intregii 
  liste pana la atingerea penultimului nod, al carui link trebuie trecut 
  in 0. De aceea, este putin probabil ca o lista simplu inlantuita 
  sa fie folosita in programe care necesita astfel de operatii. Listele de elemente (implementate prin tablouri sau liste inlantuite), ca si alte modele de date (cum sunt arborii sau multimile) sunt in general reprezentate prin colectii de obiecte de un anumit tip. Exemplele prezentate pana acum au permis crearea unor colectii de obiecte de un singur tip (numere intregi). Pentru implementarea unor colectii de un tip de date oarecare se pot folosi mai multe metode: • Se foloseste ca element al colectiei un pointer generic (void*) care 
  este convertit in pointer la tipul de date dorit. Un astfel de exemplu 
  va fi prezentat pentru structurile de tip arbore binar in subsectiunea 
  urmatoare. In colectiile definite in aceasta sectiune, inspectarea elementelor 
  listelor s-a executat intr-un mod simplu, prin definirea unor functii 
  de afisare (display()) care afiseaza la consola elementele colectiei exact in 
  ordinea in care ele apar in lista.  3.2.1 Definitii Un arbore (tree) este un model de date care permite reprezentarea structurilor 
  ierarhice si constitue una dintre cele mai importante structuri neliniar ce 
  intervin in algoritmii pentru calculatoare.  • Un nod este distinct si este se numeste radacina (root). Un nod poate avea zero sau mai multi fii, dar orice nod, cu exeptia radacinii, 
  are un parinte. Nodurile care sunt fii ale aceluisi parinte se numesc frati. In Fig. 3.6 este reprezentat grafic un arbore, conform acestei definitii recursive. Din definitia recursiva a arborelui rezulta ca fiecare nod al unui arbore este radacina unui subarbore. Numarul subarborilor unui nod se numeste gradul nodului. Nodurile de grad zero se numesc noduri terminale sau noduri frunza. Nodurile care nu sunt terminale sau radacina se numesc noduri interioare sau intermediare. Nivelul unui nod se defineste fata de radacina: radacina are nivelul egal cu 0; fiii radacinii au nivelul egal cu 1; in general, nivelul unui nod este cu o unitate mai mare decat nivelul parintelui sau.Un arbore etichetat (labeled) este un arbore in care oricarui nod ii este asociata o valoare (eticheta). Eticheta unui nod este informatia asociata cu acesta si poate fi un simplu numar sau o informatie complexa, de exemplu, un intreg document sau fisier. Daca ordinea relativa a subarborilor T1, T2,…Tm este importanta, se spune ca arborele este ordonat. Pentru un arbore ordonat cu m ? 2 , are sens sa fie numit T1 primul subarbore, T2 al doilea subarbore, etc. Arborii ordonati se mai numesc si arbori plani. Aceasta ordonare a arborilor se face, in general, in functie de etichetele nodurilor, de aceea pentru arborii ordonati etichetele nodurilor trebuie sa fie elemente ale unei multimi ordonabile liniar, la fel ca si in cazul listelor ordonate. 3.2.2 Reprezentarea arborilor Arborii se pot reprezenta prin diferite tipuri de structuri de date, iar alegerea 
  uneia dintre ele depinde de cerintele programului de aplicatie. La modul general, 
  fiecare nod se reprezinta prin doua campuri: un camp care contine 
  eticheta (informatia din nod) si un camp in care se memoreza o lista 
  de pointeri (sau oricare alt fel de informatie de legatura) ai tuturor nodurilor 
  fii. La randul ei, lista de pointeri poate fi reprezentata intr-unul 
  din modurile obisnuite de reprezentare a listelor: prin vectori sau liste inlantuite. 
   3.2.3 Arbori binari Un arbore binar este un arbore in care fiecare nod are cel mult doi fii, numiti fiul din stanga si fiul din dreapta. Fiecare nod dintr-un arbore binar se reprezinta de obicei printr-un camp al informatiei (eticheta) si doi pointeri catre noduri, pointerul catre nodul fiu din stanga si pointerul catre nodul fiu din dreapta. Daca unul sau amandoi fiii unui nod lipsesc, pointerii corespunzatori au valoarea zero. In Fig. 3.7 este reprezentat un arbore binar in care fiecare nod este descris printr-o structura care contine informatia (eticheta) si cei doi pointeri. Parcurgerea unui arbore binar se poate realiza in trei moduri: parcurgerea in preordine, in inordine sau in postordine. Metodele de parcurgere sunt definite recursiv: un arbore binar vid este parcurs fara sa se intreprinda nimic; altfel, parcurgerea se executa astfel: Parcurgerea in preordine Parcurgerea in inordine Parcurgerea in 
  postordine Un arbore binar ordonat (sau arbore binar de cautare -; binary search 
  tree) este un arbore binar etichetat in care se respecta urmatoarea proprietate 
  pentru fiecare nod n: etichetele tuturor nodurilor subarborelui stang 
  al nodului n au valori mai mici decat eticheta nodului n; etichetele tuturor 
  nodurilor subarborelui drept a lui n sunt mai mari decat eticheta nodului 
  n. 3.2.4 Implementarea unui arbore binar ordonat Pentru implementarea unui arbore binar ordonat se definesc doua clase: clasa 
  TNode, care reprezinta un nod in arbore si clasa BTree, care reprezinta 
  arborele insusi. Deoarece se intentioneaza implementarea unui arbore ordonat 
  pentru orice tip de date, informatia (eticheta) din fiecare nod este reprezentata 
  printr-un pointer generic in clasa TNode (pointerul d de tip PDate, cu 
  definitia typedef void* Pdate;). Relatia de precedenta pentru compararea etichetelor 
  nodurilor este definita printr-o functie al carui pointer este transmis ca argument 
  la constructia arborelui si memorata ca data membra in clasa BTree (pointerul 
  COMPARE). De asemenea, functia care se executa la vizitarea unui nod este transmisa 
  printr-un pointer ca argument la constructia arborelui si memorata in 
  clasa BTree (pointerul EXECUTE). Astfel functii pecizate de utilizator pentru 
  a fi executate in derularea unui program oarecare se numesc functii callback. typedef void* Pdate; typedef int (*COMPARE) (Pdate d1, Pdate d2); typedef void (*EXECUTE) (Pdate d); class infoA int label; public: info() A S info(int x) A label = x; S int get() A return label; S  class TNodeA public: class BTreeA Constructorul clasei TNode creaza un nod care contine informatia data ca argument 
  prin pointerul la aceasta si cu cei doi pointeri la nodurile fii (left si right) 
  de valoare zero. Constructorul clasei BTree construieste un arbore vid (cu pointerul 
  la radacina root de valoare zero) si memoreza in obiectul creat pointerii 
  la functiile de comparatie si de executie primiti ca argumente.  • daca T este arbore vid, se inlocuieste T printr-un nod nou creat 
  care primeste informatia x;  Functia publica insert() a clasei BTree implementeaza acest algoritm de inserare folosind o functie private a clasei, functia insert1() astfel: TNode* BTree::insert1(TNode *root, TNode *r, Pdate data)A if(!r)A count++; r = new TNode(data); if(!root) return r; if(f(data, root->d) < 0) root->left = r; else root->right = r; return r; Functia de parcurgere in inordine a clasei BTree este: void BTree::inorder1(TNode *root)A if(!root) return;  inorder1(root->left); if(root->d) g(root->d); inorder1(root->right); Daca un arbore a fost creat ordonat, (folosind functia insert()), atunci functia de parcurgere inorder() afiseaza etichetele nodurilor ordonate. De exemplu: void ft1()A La executia acestei functii se genereaza 10 obiecte din clasa info fiecare 
  dintre acestea continand ca informatie un numar intreg generat in 
  mod aleator. Fiecare obiect creat este inserat in arborele binar ordonat 
  tree, folosind functia insert(). La parcurgerea in inordine a acestui 
  arbore se afiseaza numerele ordonate.  Aceasta reprezentare a unui arbore binar sortat, chiar daca permite generalizarea tipului de informatie din nodul arborelui prin utilizarea unui pointer generic, este destul de incomoda (necesita conversii explicite de la pointerul la tipul de date dorit la pointer generic si invers) si este nesigura tocmai datorita faptului ca prin conversiile explicite se ocoleste mecanismul de verificare a tipului datelor specific limbajelor orientate pe obiecte. Mai mult, sunt necesare functii callback pentru definirea relatiei de precedenta sau al prelucrarilor necesare pentru fiecare tip de date. Daca pentru aplicatii mici se pot accepta si astfel de implementari, pentru programe de dimensiuni mari este recomandata folosirea unor clase de colectie “sigure”, in care diversitatea tipurilor de date din care se compun colectiile respective se obtine prin derivare sau prin clase template, nu prin conversii explicite de pointeri. Astfel de reprezentari ale colectiilor de date prin clase de colectii sigure sunt prezentate in sectiunea 5 (clase derivate) si sectiunea 7 (clase template).   Cea mai obisnuita definitie a multimii (set) este aceea prin a specifica daca un obiect apartine sau nu unei multimi date, adica: • x ? S, inseamna ca x apartine multimii S. Aceste particularitati deosebesc multimile de liste, in care ordinea elementelor este esentiala si pot exista elemente duplicat. Pentru reprezentarea in programe a multimilor se utilizeaza diferite 
  tipuri de colectii de date (vectori, liste, arbori), cu conditia ca la operatia 
  de inserare a unui nou element sa se verifice daca mai exita un element identic 
  cu noul element si nu se mai insereaza elementul daca el exista deja in 
  multimea data. Acest lucru inseamna ca la inserare trebuie sa fie testate 
  toate elementelor multimii, pentru a nu se permite multipliciatea elementelor 
  in lista. O alta operatie care se defineste intr-o multime este 
  operatia de cautare a unui element. Fiind data o multime S, trebuie aflat daca 
  un element oarecare x apartine sau nu multimii.  3.3.1 Implementarea prin liste a multimilor O multime, reprezentata ca o lista ordonata de elemente, se poate implementa 
  ca un vector (tablou unidimensional), sau ca o lista inlantuita.   class IntSetA int *set; int count; // numar de elemente int size; // dimensiune multime public: Constructorul clasei IntSet aloca un vector de dimensiune data ca argument 
  in memoria libera si destructorul dezaloca acest spatiu de memorie. int IntSet::insert(int t)A int i = count; if (count >= size)A cout << "Depasire capacitate\n"; return 1; Operatia de aflare daca un membru apartine multimii foloseste un algoritm de cautare binara, la fel ca si operatia de stergere: int IntSet::lookup(int t)constA int l = 0; int u = count -1; Pentru parcurgerea elementelor multimii in scopul de a fi utilizate (de 
  exemplu, afisate la consola) se defineste un mecanism care itereaza prin multimea 
  data in ordinea dorita. Cel mai simplu mod de a defini un iterator este 
  prin intermediul unor functii publice ale clasei IntSet, care stabileste punctul 
  de pornire al iteratiei si ordinea in care sunt parcurse elementele multimii. 
  Un astfel de mecanism ascunde utilizatorului modul de organizare interna a datelor 
  clasei IntSet, si poate fi utilizat si daca se modifica modul de implementare 
  a multimii, de exemplu se poate folosi o lista inlantuita sau arbore de 
  cautare binara. void fset1()A  int pos; // variabila de control iteratie set.start(pos); O alta modalitate de a defini un iterator pentru multimi din clasa IntSet este prin definirea unei clase diferite care memoreaza si actualizeaza pozitia de parcurgere a unei multime date. O astfel de clasa, clasa SetIter se declara clasa friend in clasa IntSet si se defineste in felul urmator: class SetIterA Un iterator se construieste pentru un obiect din clasa IntSet, al carui pointer 
  il primeste ca argument al constructorului si-l memoreaza in variabila 
  protejata s. La constructie, sau la apelul functiei start() se initializeaza 
  pozitia de inceput a parcurgerii, pos = 0. Functia membra next() avanseaza 
  cu o pozitie in multime; functia elem() returneaza valoarea elementului 
  din pozitia curenta a iteratiei.  void fset2()A SetIter iter(&set); // iterator pt. set  cout << iter.elem() << " "; set.remove(0);  iter.start(); cout << iter.elem() << " "; Exemplul de mai sus, care foloseste un vector pentru reprezentarea listei ordonate a elementelor multimii, a ilustrat in principal modul de reprezentare a multimilor, dar nu este cea mai eficienta reprezentare a acestora. Chiar daca operatia de cautare este rapida (se poate executa cautare binara, care este in ordinul lui log n, pentru n elemente ale multimii -; in O(log n) ), operatiile de inserare si de stergere sunt foarte ineficiente, datorita necesitatii deplasarii unor elemente pentru mentinerea ordonarii. De aceea, cel mai frecvent, multimile se implementeaza prin liste inlantuite, in care elementele se mentin de asemenea ordonate. Operatia de cautare a unui element nu se mai poate efectua prin cautare binara (deoarece nu exista posibilitatea de a accesa noduri interne ale listei fara ca aceasta sa fie parcursa), de aceea se foloseste cautarea secventiala, care este in ordinul lui n pentru n elemente ale listei (in O(n)). In schimb, operatiile de inserare si stergere a unui element sunt nult mai eficiente deoarece nu necesita deplasari pentru mentinerea ordonarii listei. 3.3.2 Implementarea prin arbori binari ordonati a multimilor Un arbore binar ordonat poate fi folosit pentru a implementa o multime de elemente de un anumit tip (predefinit sau definit de utilizator). Intr-o astfel de reprezentare, operatiile de inserare, stergere si cautare a unui element intr-o multime sunt executate eficient (in ordinul lui log n pentru n elemente ale multimii). In schimb, operatiile cu multimi (reuniunea, intersectia, diferenta) pot sa nu fie la fel de eficiente datorita apelurilor recursive ale functiei de parcurgere in ordine crescatoare a elementelor (inorder()). Pentru implementarea multimilor se mai pot folosi si alte structuri de date, 
  cum sunt vectorii caracteristici, vectorii asociativi sau tabele de dispersie 
  (hash table). Alegerea celei mai adecvate metode de reprezentare a unei multimi 
  depinde, evident, de cerintele aplicatiei in care este folosita aceasta. 
    | 
  ||||||
	
  | 
||||||
	
  | 
||||||
| Copyright© 2005 - 2025 | Trimite document | Harta site | Adauga in favorite | 
	||||||
| 
 |