|
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 - 2024 | Trimite document | Harta site | Adauga in favorite |
|