F.morrau - Structuri de Date

Embed Size (px)

Citation preview

  • 8/2/2019 F.morrau - Structuri de Date

    1/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 1

    CUPRINS

    1. STRUCTURI DE DATE SI TIPURI DE DATE ABSTRACTE1.1 Structuri de date fundamentale ....................................................... 31.2 Clasificri ale structurilor de date ................................................... 3

    1.3Tipuri abstracte de date ..................................................................... 41.4Eficienta structurilor de date ............................................................. 6

    2. STRUCTURI DE DATE N LIMBAJUL C2.1 Implementarea operatiilor cu structuri de date 92.2 Utilizarea de tipuri generice .. 112.3 Utilizarea de pointeri generici 132.4 Structuri si functii recursive 16

    3. VECTORI3.1 Vectori 243.2 Vectori ordonati . 253.3 Vectori alocati dinamic .. 273.4 Aplicatie: Componente conexe .. 293.5 Vectori multidimensionali 313.6 Vectori de biti 32

    4. LISTE CU LEGTURI4.1 Liste nlntuite .. 354.2 Colectii de liste . 394.3 Liste nlntuite ordonate 424.4 Variante de liste nlntuite . 444.5 Liste dublu-nlntuite . 474.6 Comparatie ntre vectori si liste 484.7 Combinatii de liste si vectori . 514.8 Tipul abstract list (secvent) .. . 544.9 Liste Skip ... 564.10 Liste neliniare .. 59

    5. MULTIMI SI DICTIONARE5.1 Tipul abstract Multime 625.2 Aplicatie: Acoperire optim cu multimi . 635.3 Tipul Colectie de multimi disjuncte 645.4 Tipul abstract Dictionar .. 665.5 Implementare dictionar prin tabel de dispersie .. 68

    5.6 Aplicatie: Compresia LZW 71

    6. STIVE SI COZI6.1 Liste stiv .. .756.2 Aplicatie: Evaluare expresii . .. 77

    6.3 Eliminarea recursivittii folosind o stiv . .. 826.4 Liste coad ..846.5 Tipul Coad cu prioritti . . . 896.6 Vectori heap . . 91

  • 8/2/2019 F.morrau - Structuri de Date

    2/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date2

    7. ARBORI7.1 Structuri arborescente . . 967.2 Arbori binari neordonati .. . 977.3 Traversarea arborilor binari 997.4 Arbori binari pentru expresii 1047.5 Arbori Huffman .. 1067.6 Arbori multici 1107.7 Alte structuri de arbore .. 115

    8. ARBORI DE CAUTARE8.1 Arbori binari de cutare .. 1218.2 Arbori binari echilibrati .. 1248.3 Arbori Splay si Treap . 1278.4 Arbori AVL 1318.5 Arbori RB si AA 1368.6 Arbori 2-3 ... 138

    9. STRUCTURI DE GRAF9.1 Grafuri ca structuri de date . 1429.2 Reprezentarea grafurilor prin alte structuri 1439.3 Metode de explorare a grafurilor 1479.4 Sortare topologic .. 1509.5 Aplicatii ale explorrii n adncime .. 1529.6 Drumuri minime n grafuri 1579.7 Arbori de acoperire de cost minim. 1609.8 Grafuri virtuale .. 164

    10. STRUCTURI DE DATE EXTERNE10.1 Specificul datelor pe suport extern .. 170

    10.2 Sortare extern 17110.3 Indexarea datelor 17210.4 Arbori B . 173

    11. STRUCTURI DE DATE N LIMBAJUL C++11.1 Avantajele utilizrii limbajului C++ .. 17911.2 Clase si obiecte n C++ .. 18011.3 Clase sablon (template) n C++ .. 18611.4 Clase container din biblioteca STL 18911.5 Utilizarea claselor STL n aplicatii . 19211.6 Definirea de noi clase container .. 194

  • 8/2/2019 F.morrau - Structuri de Date

    3/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 3

    Capitolul 1

    STRUCTURI DE DATE SI TIPURI DE DATE ABSTRACTE

    1.1 STRUCTURI DE DATE FUNDAMENTALE

    Gruparea unor date sub un singur nume a fost necesar nc de la nceputurile programriicalculatoarelor. Prima structur de date folosit a fost structura de vector (tabel), utilizat n operatiilede sortare (de ordonare) a colectiilor si prezent n primele limbaje de programare pentru aplicatiinumerice (Fortran si Basic).

    Un vector este o colectie de date de acelasi tip, n care elementele colectiei sunt identificate prinindici ce reprezint pozitia relativ a fiecrui element n vector.

    La nceput se puteau declara si utiliza numai vectori cu dimensiuni fixe, stabilite la scriereaprogramului si care nu mai puteau fi modificate la executie.

    Introducerea tipurilor pointer si alocrii dinamice de memorie n limbajele Pascal si C a permisutilizarea de vectori cu dimensiuni stabilite si/sau modificate n cursul executiei programelor.

    Gruparea mai multor date, de tipuri diferite, ntr-o singur entitate, numit articol (record) nPascal sau structur n C a permis definirea unor noi tipuri de date de ctre programatori siutilizarea unor date dispersate n memorie, dar legate prin pointeri : liste nlntuite, arbori si altele.Astfel de colectii se pot extinde dinamic pe msura necesittilor si permit un timp mai scurt pentruanumite operatii, cum ar fi operatia de eliminare a unei valori dintr-o colectie.

    Limbajul C asigur structurile de date fundamentale (vectori, pointeri, structuri ) si posibilitateacombinrii acestora n noi tipuri de date, care pot primi si nume sugestive prin declaratia typedef.

    Dintr-o perspectiv independent de limbajele de programare se pot considera ca structuri de datefundamentale vectorii, listele nlntuite si arborii, fiecare cu diferite variante de implementare. Altestructuri de date se pot reprezenta prin combinatii de vectori, liste nlntuite si arbori. De exemplu, untabel de dispersie (Hash table) este realizat de obicei ca un vector de pointeri la liste nlntuite (liste

    de elemente sinonime). Un graf se reprezint deseori printr-un vector de pointeri la liste nlntuite

    (liste de adiacente), sau printr-o matrice (un vector de vectori n C).

    1.2 CLASIFICRI ALE STRUCTURILOR DE DATE

    O structur de date este caracterizat prin relatiile dintre elementele colectiei si prin operatiileposibile cu aceast colectie. Literatura de specialitate actual identific mai multe feluri de colectii(structuri de date), care pot fi clasificate dup cteva criterii.

    Un criteriu de clasificare foloseste relatiile dintre elementele colectiei:- Colectii liniare (secvente, liste), n care fiecare element are un singur succesor si un singur

    predecesor;- Colectii arborescente (ierarhice), n care un element poate avea mai multi succesori (fii), dar un

    singur predecesor (printe);- Colectii neliniare generale, n care relatiile dintre elemente au forma unui graf general (un element

    poate avea mai multi succesori si mai multi predecesori).Un alt criteriu grupeaz diferitele colectii dup rolul pe care l au n aplicatii si dup operatiile

    asociate colectiei, indiferent de reprezentarea n memorie, folosind notiunea de tip abstract de date:- Structuri de cutare (multimi si dictionare abstracte);- Structuri de pstrare temporar a datelor (containere, liste, stive, cozi s.a.)

    Un alt criteriu poate fi modul de reprezentare a relatiilor dintre elementele colectiei:- Implicit, prin dispunerea lor n memorie (vectori de valori, vectori de biti, heap);- Explicit, prin adrese de legtur (pointeri).

    Dup numrul de aplicatii n care se folosesc putem distinge ntre:- Structuri de date de uz general ;- Structuri de date specializate pentru anumite aplicatii (geometrice, cu imagini).

  • 8/2/2019 F.morrau - Structuri de Date

    4/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date4

    Organizarea datelor pe suport extern ( a fisierelor si bazelor de date) prezint asemnri dar sidiferente fat de organizarea datelor n memoria intern, datorit particularittilor de acces la discurifat de accesul la memoria intern.

    Un fisier secvential corespunde oarecum unui vector, un fisier de proprietti este n fond undictionar si astfel de paralele pot continua. Pe suport extern nu se folosesc pointeri, dar se pot folosiadrese relative n fisiere (ca numr de octeti fat de nceputul fisierului), ca n cazul fisierelor index.

    Ideea unor date dispersate dar legate prin pointeri, folosit la liste si arbori, se foloseste mai rarpentru fisiere disc pentru c ar necesita acces la articole neadiacente (dispersate n fisier), operatii careconsum timp pe suport extern. Totusi, anumite structuri arborescente se folosesc si pe disc, dar eletin seama de specificul suportului: arborii B sunt arbori echilibrati cu un numr mic de noduri si cunumr mare de date n fiecare nod, astfel ca s se fac ct mai putine citiri de pe disc.

    Salvarea unor structuri de date interne cu pointeri ntr-un fisier disc se numeste serializare, pentruc n fisier se scriu numai date (ntr-o ordine prestabilit), nu si pointeri (care au valabilitate numai pedurata executiei unui program). La ncrcarea n memorie a datelor din fisier se poate reconstrui ostructur cu pointeri (n general alti pointeri la o alt executie a unui program ce foloseste aceste date).

    Tot pe suport extern se practic si memorarea unor colectii de date fr o structur intern (datenestructurate), cum ar fi unele fisiere multimedia, mesaje transmise prin e-mail, documente, rapoarte

    s.a. Astfel de fisiere se citesc integral si secvential, fr a necesita operatii de cutare n fisier.

    1.3 TIPURI ABSTRACTE DE DATE

    Un tip abstract de date este definit numai prin operatiile asociate (prin modul de utilizare), frreferire la modul concret de implementare (cu elemente consecutive sau cu pointeri sau alte detalii dememorare).

    Pentru programele nebanale este util o abordare n (cel putin) dou etape:- o etap de conceptie (de proiectare), care include alegerea tipurilor abstracte de date si algoritmilor

    necesari;- o etap de implementare (de codificare), care include alegerea structurilor concrete de date, scrierea

    de cod si folosirea unor functii de bibliotec.

    In faza de proiectare nu trebuie stabilite structuri fizice de date, iar aplicatia trebuie gndit ntipuri abstracte de date. Putem decide c avem nevoie de un dictionar si nu de un tabel de dispersie,putem alege o coad cu prioritti abstract si nu un vector heap sau un arbore ordonat, s.a.m.d.

    In faza de implementare putem decide ce implementri alegem pentru tipurile abstracte decise nfaza de proiectare. Ideea este de a separa interfata (modul de utilizare) de implementarea unui anumittip de colectie. In felul acesta se reduc dependentele dintre diferite prti ale unui program si sefaciliteaz modificrile care devin necesare dup intrarea aplicatiei n exploatare.

    Conceptul de tip abstract de date are un corespondent direct n limbajele orientate pe obiecte, sianume o clas abstract sau o interfat. In limbajul C putem folosi acelasi nume pentru tipul abstractsi aceleasi nume de functii; nlocuirea unei implementri cu alta poate nsemna un alt fisier antet (cudefinirea tipului) si o alt bibliotec de functii, dar fr modificarea aplicatiei care foloseste tipulabstract. Un tip de date abstract poate fi implementat prin mai multe structuri fizice de date.

    Trebuie spus c nu exist un set de operatii unanim acceptate pentru fiecare tip abstract de date, iaraceste diferente sunt uneori mari, ca n cazul tipului abstract "list" (asa cum se pot vedea comparndbibliotecile de clase din C++ si din Java ).

    Ca exemplu de abordare a unei probleme n termeni de tipuri abstracte de date vom consideraverificarea formal a unui fisier XML n sensul utilizrii corecte a marcajelor (tags). Exemplele careurmeaz ilustreaz o utilizare corect si apoi o utilizare incorect a marcajelor:

    POPAION 8.50

  • 8/2/2019 F.morrau - Structuri de Date

    5/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 5

    POPA ION 8.50

    Pentru simplificare am eliminat marcajele singulare, de forma .Algoritmul de verificare a unui fisier XML dac este corect format foloseste tipul abstract stiv

    (stack) astfel: pune ntr-o stiv fiecare marcaj de nceput (, ,...), iar la citirea unuimarcaj de sfrsit (, ,...) verific dac n vrful stivei este marcajul de nceput perechesi l scoate din stiv :

    initializare stivarepet pn la sfrsit de fisier

    extrage urmtorul marcajdaca marcaj de nceput

    pune marcaj n stivdac marcaj de sfrsit

    dac n varful stivei este perechea luiscoate marcajul din vrful stivei

    altfeleroare de utilizare marcaje

    daca stiva nu e goaleroare de utilizare marcaje

    In aceast faz nu ne intereseaz dac stiva este realizat ca vector sau ca list nlntuit, dac eacontine pointeri generici sau de un tip particular.

    Un alt exemplu este tipul abstract multime, definit ca o colectie de valori distincte si avnd caoperatie specific verificarea apartenentei unei valori la o multime (deci o cutare n multime dupvaloare ). In plus, exist operatii generale cu orice colectie : initializare, adugare element la ocolectie, eliminare element din colectie, afisare sau parcurgere colectie, s.a. Multimile se potimplementa prin vectori de valori, vectori de biti, liste nlntuite, arbori binari si tabele de dispersie(hash).

    In prezent sunt recunoscute cteva tipuri abstracte de date, definite prin operatiile specifice si

    modul de utilizare: multimi, colectii de multimi disjuncte, liste generale, liste particulare (stive,cozi),cozi ordonate (cu prioritti), dictionare. Diferitele variante de arbori si de grafuri sunt uneori si eleconsiderate ca tipuri abstracte.

    Aceste tipuri abstracte pot fi implementate prin cteva structuri fizice de date sau combinatii alelor: vectori extensibili dinamic, liste nlntuite, matrice, arbori binari, arbori oarecare, vectori "heap",fiecare cu variante.

    Conceperea unui program cu tipuri abstracte de date permite modificarea implementrii colectieiabstracte (din motive de performant, de obicei), fr modificarea restului aplicatiei.

    Ca exemplu de utilizare a tipului abstract dictionar vom considera problema determinriifrecventei de aparitie a cuvintelor ntr-un text. Un dictionar este o colectie de perechi cheie-valoare, ncare cheile sunt unice (distincte). In exemplul nostru cheile sunt siruri (cuvinte), iar valorile asociatesunt numere ntregi ce arat de cte ori apare fiecare cuvnt n fisier.

    Aplicatia poate fi descris astfel:

    initializare dictionarrepet pn la sfrsit de fisier

    extrage urmtorul cuvantdac cuvantul exist n dictionar

    aduna 1 la numrul de aparitiialtfel

    pune in dictionar cuvant cu numr de aparitii 1afisare dictionar

    Implementarea dictionarului de cuvinte se poate face printr-un tabel hash dac fisierele sunt foartemari si sunt necesare multe cutri, sau printr-un arbore binar de cutare echilibrat dac se cere

  • 8/2/2019 F.morrau - Structuri de Date

    6/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date6

    afisarea sa numai n ordinea cheilor, sau printr-un vector (sau doi vectori) dac se cere afisarea saordonat si dup valori (dup numrul de aparitii al fiecrui cuvnt).

    Existenta unor biblioteci de clase predefinite pentru colectii de date reduce problemaimplementrii structurilor de date la alegerea claselor celor mai adecvate pentru aplicatia respectiv siconduce la programe compacte si fiabile. "Adecvare" se refer aici la performantele cerute si laparticularittile aplicatiei: dac se cere mentinerea colectiei n ordine, dac se fac multe cutari, daceste o colectie static sau volatil, etc.

    1.4 EFICIENTA STRUCTURILOR DE DATE

    Unul din argumentele pentru studiul structurilor de date este acela c alegerea unei structurinepotrivite de date poate influenta negativ eficienta unor algoritmi, sau c alegerea unei structuriadecvate poate reduce memoria ocupat si timpul de executie a unor aplicatii care folosesc intenscolectii de date.

    Un bun exemplu este cel al structurilor de date folosite atunci cnd sunt necesare cutri frecvente ntr-o colectie de date dup continut (dup chei de cutare); cutarea ntr-un vector sau ntr-o listnlntuit este ineficient pentru un volum mare de date si astfel au aprut tabele de dispersie (hash

    table), arbori de cutare echilibrati, arbori B si alte structuri optimizate pentru operatii de cutare.Alt exemplu este cel al algoritmilor folositi pentru determinarea unui arbore de acoperire de costminim al unui graf cu costuri, care au o complexitate ce depinde de structurile de date folosite.

    Influenta alegerii structurii de date asupra timpului de executie a unui program st si la bazaintroducerii tipurilor abstracte de date: un program care foloseste tipuri abstracte poate fi mai usormodificat prin alegerea unei alte implementri a tipului abstract folosit, pentru mbunttireaperformantelor.

    Problema alegerii unei structuri de date eficiente pentru un tip abstract nu are o solutie unic, desiexist anumite recomandri generale n acest sens. Sunt mai multi factori care pot influenta aceastalegere si care depind de aplicatia concret.

    Astfel, o structur de cutare poate sau nu s pstreze si o anumit ordine ntre elementelecolectiei, ordine cronologic sau ordine determinat de valorile memorate. Dac nu conteaz ordinea

    atunci un tabel de dispersie (hash) este alegerea potrivit, dac ordinea valoric este importantatunci un arbore binar cu autoechilibrare este o alegere mai bun, iar dac trebuie pstrat ordinea deintroducere n colectie, atunci un tabel hash completat cu o list coad este mai bun.

    In general un timp mai bun se poate obtine cu pretul unui consum suplimentar de memorie; unpointer n plus la fiecare element dintr-o list sau dintr-un arbore poate reduce durata anumitoroperatii si/sau poate simplifica programarea lor.

    Frecventa fiecrui tip de operatie poate influenta de asemenea alegerea structurii de date; dacoperatiile de stergere a unor elemente din colectie sunt rare sau lipsesc, atunci un vector estepreferabil unei liste nlntuite, de exemplu. Pentru grafuri, alegerea ntre o matrice de adiacente si ocolectie de liste de adiacente tine seama de frecventa anumitor operatii cu graful respectiv; deexemplu, obtinerea grafului transpus sau a grafului dual se face mai repede cu o matrice de adiacente.

    In fine, dimensiunea colectiei poate influenta alegerea structurii adecvate: o structur cu pointeri(liste de adiacente pentru grafuri, de exemplu) este bun pentru o colectie cu numr relativ mic deelemente si care se modific frecvent, iar o structur cu adrese succesive (o matrice de adiacente, deexemplu) poate fi preferabil pentru un numr mare de elemente.

    Eficienta unei anumite structuri este determinat de doi factori: memoria ocupat si timpul necesarpentru operatiile frecvente. Mai des se foloseste termenul de complexitate, cu variantelecomplexitate temporal si complexitate spatial.

    Operatiile asociate unei structuri de date sunt algoritmi, mai simpli sau mai complicati, iarcomplexitatea lor temporal este estimat prin notatia O(f(n)) care exprim rata de crestere a timpuluide executie n raport cu dimensiunea n a colectiei pentru cazul cel mai nefavorabil.

    Complexitatea temporal a unui algoritm se estimeaz de obicei prin timpul maxim necesar ncazul cel mai nefavorabil, dar se poate tine seama si de timpul mediu si/sau de timpul minim necesar.

    Pentru un algoritm de sortare n ordine cresctoare, de exemplu, cazul cel mai defavorabil este ca

  • 8/2/2019 F.morrau - Structuri de Date

    7/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 7

    datele s fie ordonate descresctor (sau cresctor pentru metoda quicksort). Cazul mediu este alunui vector de numere generate aleator, iar cazul minim al unui vector deja ordonat.

    In general, un algoritm care se comport mai bine n cazul cel mai nefavorabil se comport maibine si n cazul mediu, dar exist si exceptii de la aceast regul cum este algoritmul de sortare rapidQuickSort, care este cel mai bun pentru cazul mediu (ordine oarecare n lista initial), dar se poatecomporta slab pentru cazul cel mai nefavorabil (functie si de modul de alegere a elementului pivot).

    Pentru a simplifica compararea eficientei algoritmilor se apreciaz volumul datelor de intrareprintr-un singur numr ntreg N, desi nu orice problem poate fi complet caracterizat de un singurnumr. De exemplu, n problemele cu grafuri conteaz att numrul de noduri din graf ct si numrulde arce din graf, dar uneori se consider doar numrul arcelor ca dimensiune a grafului (pentru celemai multe aplicatii reale numrul de arce este mai mare ca numrul nodurilor).

    O alt simplificare folosit n estimarea complexittii unui algoritm consider c toate operatiile deprelucrare au aceeasi durat si c putem numra operatii necesare pentru obtinerea rezultatului fr sne intereseze natura acelor operatii. Parte din aceast simplificare este si aceea c toate dateleprelucrate se afl n memoria intern si c necesit acelasi timp de acces.

    Fiecare algoritm poate fi caracterizat printr-o functie ce exprim timpul de rulare n raport cudimensiunea n a problemei; aceste functii sunt mai greu de exprimat printr-o formul si de aceea se

    lucreaz cu limite superioare si inferioare pentru ele.Se spune c un algoritm are complexitatea de ordinul lui f(n) si se noteaz O(f(n)) dac timpul deexecutie pentru n date de intrare T(n) este mrginit superior de functia f(n) astfel:

    T(n) = O(f(n)) dac T(n) n0unde k este o constant a crei important scade pe msur ce n creste.

    Relatia anterioar spune c rata de crestere a timpului de executie a unui algoritm T(n) n raport cudimensiunea n a problemei este inferioar ratei de crestere a functiei f(n). De exemplu, un algoritm decomplexitate O(n) este un algoritm al crui timp de executie creste liniar (direct proportional) cuvaloarea lui n.

    Majoritatea algoritmilor utilizati au complexitate polinomial, deci f(n) = nk. Un algoritm liniar arecomplexitate O(n), un algoritm ptratic are complexitate O(n2), un algoritm cubic are ordinul O(n3)s.a.m.d.

    Diferenta n timpul de executie dintre algoritmii de diferite complexitti este cu att mai mare cuct n este mai mare. Tabelul urmtor arat cum creste timpul de executie n raport cu dimensiuneaproblemei pentru cteva tipuri de algoritmi.

    n O(log(n)) O(n) O(n*log(n)) O(n2) O(n3) O(2n)10 2.3 10 23 100 1000 10e320 3.0 20 60 400 8000 10e630 3.4 30 102 900 27000 10e940 3.7 40 147 1600 64000 10e1250 3.9 50 195 2500 125000 10e15

    Complexitatea unui algoritm este deci echivalent cu rata de crestere a timpului de executie n

    raport cu dimensiunea problemei.Algoritmii O(n) si O(n log(n)) sunt aplicabili si pentru n de ordinul 109. Algoritmii O(n2) devin

    nepracticabili pentru n >105, algoritmii O(n!) nu pot fi folositi pentru n > 20, iar algoritmii O(2 n) suntinaplicabili pentru n >40.

    Cei mai buni algoritmi sunt cei logaritmici, indiferent de baza logaritmului.Dac durata unei operatii nu depinde de dimensiunea colectiei, atunci se spune c acea operatie are

    complexitatea O(1); exemple sunt operatiile de introducere sau de scoatere din stiv, care opereaz lavrful stivei si nu depind de adncimea stivei. Un timp constant are si operatia de apartenent a unuielement la o multime realizat ca un vector de biti, deoarece se face un calcul pentru determinareapozitiei elementului cutat si o citire a unui bit (nu este o cutare prin comparatii repetate).

    Operatiile de cutare secvential ntr-un vector neordonat sau ntr-o list nlntuit au o duratproportional cu lungimea listei, deci complexitate O(n) sau liniar.

  • 8/2/2019 F.morrau - Structuri de Date

    8/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date8

    Cutarea binar ntr-un vector ordonat si cutarea ntr-un arbore binar ordonat au o complexitatelogaritmic de ordinul O(log2n), deoarece la fiecare pas reduce numrul de elemente cercetate la

    jumtate. Operatiile cu vectori heap si cu liste skip au si ele complexitate logaritmic (logaritm de 2).Cu ct dimensiunea colectiei n este mai mare, cu att este mai mare cstigul obtinut prin cutarelogaritmic n raport cu cutarea liniar.

    Cutarea ntr-un arbore ordonat are o durat proportional cu nltimea arborelui, iar nltimea esteminim n cazul unui arbore echilibrat si are valoarea log2n , unde n este numrul de noduri dinarbore. Deci complexitatea operatiei de cutare ntr-un arbore binar ordonat si echilibrat estelogaritmic n raport cu numrul de noduri (cu dimensiunea colectiei).

    Anumite structuri de date au ca specific existenta unor operatii de durat mare dar care se executrelativ rar: extinderea unui vector, restructurarea unui arbore, s.a. Dac am lua durata acestor operatiidrept cazul defavorabil si am nsuma pe toate operatiile am obtine rezultate gresite pentrucomplexitatea algoritmilor de adugare elemente la colectie. Pentru astfel de cazuri devine importantanaliza amortizat a complexittii unor secvente de operatii, care nu este neaprat egal cu sumacomplexittilor operatiilor din secvent. Un exemplu simplu de analiz amortizat este costuladugrii unui nou element la sfrsitul unui vector care se extinde dinamic.

    Fie C capacitatea momentan a unui vector dinamic. Dac numrul de elemente din vector N este

    mai mic ca C atunci operatia de adugare nu depinde de N si are complexitatea O(1). Dac N este egalcu C atunci devine necesar extinderea vectorului prin copierea celor C elemente la noua adresobtinut. In caz c se face o extindere cu un singur element, la fiecare adugare este necesar copiereaelementelor existente n vector, deci costul unei operatii de adugare este O(N).

    Dac extinderea vectorului se va face prin dublarea capacittii sale atunci copierea celor Celemente se va face numai dup nc C/2 adugri la vectorul de capacitate C/2. Deci durata medie aC/2 operatii de adugare este de ordinul 3C/2, adic O(C). In acest caz, cnd timpul total a O(N)operatii este de ordinul O(N) vom spune c timpul amortizat al unei singure operatii este O(1). Altfelspus, durata total a unei secvente de N operatii este proportional cu N si deci fiecare operatie esteO(1).

    Aceast metod de analiz amortizat se numeste metoda agregat pentru c se calculeaz un costagregat pe o secvent de operatii si se raporteaz la numrul de operatii din secvent.

    Prin extensie se vorbeste chiar de structuri de date amortizate, pentru care costul mare al unoroperatii cu frecvent mic se amortizeaz pe durata celorlalte operatii. Este vorba de structuri carese reorganizeaz din cnd n cnd, cum ar fi tabele de dispersie (reorganizate atunci cnd listele decoliziuni devin prea lungi), anumite variante de heap (Fibonacci, binomial), arbori scapegoat(reorganizati cnd devin prea dezechilibrati) , arbori Splay (reorganizati numai cnd elementul accesatnu este deja n rdcin), arbori 2-3 si arbori B (reorganizati cnd un nod este plin dar mai trebuieadugat o valoare la acel nod), s.a.

    Diferentele dintre costul mediu si costul amortizat al unor operatii pe o structur de date provin dinurmtoarele observatii:- Costul mediu se calculeaz ca medie pe diferite intrri (date) si presupune c durata unei operatii

    (de adugare de exemplu) nu depinde de operatiile anterioare;- Costul amortizat se calculeaz ca medie pe o secvent de operatii succesive cu aceleasi date, iar

    durata unei operatii depinde de operatiile anterioare.

  • 8/2/2019 F.morrau - Structuri de Date

    9/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 9

    Capitolul 2

    PROGRAMAREA STRUCTURILOR DE DATE IN C

    2.1IMPLEMENTAREA OPERATIILOR CU STRUCTURI DE DATE

    Operatiile cu anumite structuri de date sunt usor de programat si de aceea pot fi rescrise naplicatiile care le folosesc, pentru a tine seama de tipul datelor sau de alte particularitti ale aplicatiei.Din aceast categorie fac parte vectori, matrice, stive, cozi, liste nlntuite simple si chiar arboribinari fr reechilibrare.

    Pentru alte structuri operatiile asociate pot fi destul de complexe, astfel c este preferabil s gsimo bibliotec sau surse care pot fi adaptate rapid la specificul aplicatiei. Din aceast categorie fac partearborii binari cu autoechilibrare, tabele de dispersie, liste cu acces direct (skip list), arbori B, s.a.

    Biblioteci generale de functii pentru operatii cu principalele structuri de date exist numai pentrulimbajele orientate pe obiecte (C++, C#, Java). Pot fi gsite ns si biblioteci C specializate cum esteLEDA pentru operatii cu grafuri.

    Limbajul de programare folosit n descrierea si/sau n implementarea operatiilor cu colectii de datepoate influenta mult claritatea descrierii si lungimea programelor. Diferenta cea mai important este

    ntre limbajele procedurale (Pascal si C) si limbajele orientate pe obiecte (C++ si Java).Limbajul folosit n acest text este C dar unele exemple folosesc parametri de tip referint n functii

    (declarati cu "tip &"), care sunt un mprumut din limbajul C++.Uilizarea tipului referint permite simplificarea definirii si utilizrii functiilor care modific

    continutul unei structuri de date, definite printr-un tip structur. In C, o functie nu poate modificavaloarea unui argument de tip structur dect dac primeste adresa variabilei ce se modific, printr-unargument de un tip pointer. Exemplul urmtor foloseste o structur care reuneste un vector sidimensiunea sa, iar functiile utilizeaz parametri de tip pointer.

    #define M 100 // dimensiune maxima vectoritypedef struct { // definire tip Vector

    int vec[M];int dim; // dimensiune efectiva vector

    } Vector;// operatii cu vectori

    void initV (Vector * pv) { // initializare vector

    pv dim=0;}void addV ( Vector * pv, int x) { // adaugare la un vector

    pv vec[pv dim]=x;

    pv dim ++;

    }

    void printV ( Vector v) { // afisare vectorfor (int i=0; i< v.dim;i++)

    printf ("%d ", v.vec[i]);printf("\n");

    }int main() { // utilizare operatii cu vectoriint x; Vector v;initV ( &v); // initializare vectorwhile (scanf("%d",&x) != EOF)

    addV ( &v,x); // adaugari repetateprintV (v); // afisare vector

    }

  • 8/2/2019 F.morrau - Structuri de Date

    10/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date10

    Pentru o utilizare uniform a functiilor si pentru eficient am putea folosi argumente pointer sipentru functiile care nu modific vectorul (de ex. printV).

    In C++ si n unele variante de C se pot folosi parametri de tip referint, care simplific multdefinirea si utilizarea de functii cu parametri modificabili. Un parametru formal referint se declarfolosind caracterul & ntre tipul si numele parametrului. In interiorul functiei parametrul referint sefoloseste la fel ca un parametru de acelasi tip (transmis prin valoare). Parametrul efectiv care va

    nlocui un parametru formal referint poate fi orice nume de variabil (de un tip identic saucompatibil). Exemple de functii din programul anterior cu parametri referint:

    void initV (Vector & v) {v.dim=0;

    }void addV ( Vector & v, int x) {

    v.vec[v.dim]=x; v.dim ++;}void main() { // utilizare functii cu parametri referintaint x; Vector v;initV ( v);

    while (scanf("%d",&x) != EOF)addV ( v,x);

    printV (v);}

    In continuare vom folosi parametri de tip referint pentru functiile care trebuie s modifice valorileacestor parametri. In felul acesta utilizarea functiilor este uniform, indiferent dac ele modific saunu variabila colectie primit ca argument.

    In cazul vectorilor sunt posibile si alte solutii care s evite functii cu argumente modificabile (deex. memorarea lungimii la nceputul unui vector de numere), dar vom prefera solutiile generalaplicabile oricrei colectii de date.

    O alt alegere trebuie fcut pentru functiile care au ca rezultat un element dintr-o colectie: functia

    poate avea ca rezultat valoarea elementului sau poate fi de tip void iar valoarea s fie transmis nafar printr-un argument de tip referint sau pointer. Pentru o functie care furnizeaz elementul (de untip T) dintr-o pozitie dat a unui vector, avem de ales ntre urmtoarele variante:

    T get ( Vector & v, int k); // rezultat obiectul din pozitia kvoid get (Vector& v, int k, T & x); // extrage din pozitia k a lui v in xint get (Vector& v, int k, T & x); // rezultat cod de eroare

    unde T este un tip specific aplicatiei, definit cu "typedef".Alegerea ntre prima si ultima variant este oarecum subiectiv si influentat de limbajul utilizat.O alternativ la functiile cu parametri modificabili este utilizarea de variabile externe (globale)

    pentru colectiile de date si scoaterea acestor colectii din lista de argumente a subprogramelor care

    opereaz cu colectia. Solutia este posibil deseori deoarece multe aplicatii folosesc o singur colectiede un anumit tip (o singur stiv, un singur graf) si ea se ntlneste n textele mai simple desprestructuri de date. Astfel de functii nu pot fi reutilizate n aplicatii diferite si nu pot fi introduse nbiblioteci de subprograme, dar variabilele externe simplific programarea si fac mai eficiente functiilerecursive (cu mai putini parametri de pus pe stiv la fiecare apel).

    Exemplu de utilizare a unui vector ca variabil extern:

    Vector a; // variabila externavoid initV() {

    a.dim=0;}void addV (int x) { // adaugare la vectorul a

    a.vec[a.dim++]=x;}

  • 8/2/2019 F.morrau - Structuri de Date

    11/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 11

    // utilizare operatii cu un vectorvoid main() {int x; initV (); // initializare vector awhile (scanf("%d",&x) != EOF)

    addV (x); // adauga la vectorul aprintV (); // afisare vector a

    }

    Functiile de mai sus pot fi folosite numai ntr-un program care lucreaz cu un singur vector,declarat ca variabil extern cu numele "a". Dac programul foloseste mai multi vectori, functiileanterioare nu mai pot fi folosite. In general se recomand ca toate datele necesare unui subprogram sitoate rezultatele s fie transmise prin argumente sau prin numele functiei.

    Majoritatea subprogramelor care realizeaz operatii cu o structur de date se pot termina anormal,fie din cauza unor argumente cu valori incorecte, fie din cauza strii colectiei; de exemplu, ncercareade adugare a unui nou element la un vector plin. In absenta unui mecanism de tratare a exceptiilorprogram (cum sunt cele din Java si C++), solutiile de raportare a acestei conditii de ctre unsubprogram sunt :- Terminarea ntregului program dup afisarea unui mesaj, cu sau fr utilizarea lui "assert" (pentru

    erori grave dar putin probabile) . Exemplu:

    // extragere element dintr-un vectorT get ( Vector & v, int k) {

    assert ( k >=0 && k =v.dim ) // daca eroare la indicele kreturn -1;

    x=v.vec[k];return k;

    } // utilizare

    .. .if ( get(v,k,x) < 0) {

    printf(indice gresit n fct. get \n); exit(1);}

    2.2 UTILIZAREA DE TIPURI GENERICE

    O colectie poate contine valori numerice de diferite tipuri si lungimi sau siruri de caractere sau altetipuri agregat (structuri), sau pointeri (adrese). Se doreste ca operatiile cu un anumit tip de colectie spoat fi scrise ca functii generale, adaptabile pentru fiecare tip de date ce poate face parte din colectie.

    Limbajele orientate pe obiecte au rezolvat aceast problem, fie prin utilizarea de tipuri generice,neprecizate (clase template), fie prin utilizarea unui tip obiect foarte general pentru elementele unei

    colectii, tip din care pot fi derivate orice alte tipuri de date memorate n colectie (tipul "Object" nJava).

    Realizarea unei colectii generice n limbajul C se poate face n dou moduri:1) Prin utilizarea de tipuri generice (neprecizate) pentru elementele colectiei n subprogramele ce

    realizeaz operatii cu colectia. Pentru a folosi astfel de functii ele trebuie adaptate la tipul de datecerut de o aplicatie. Adaptarea se face partial de ctre compilator (prin macro-substitutie) si partial de

    ctre programator (care trebuie s dispun de forma surs pentru aceste subprograme).

  • 8/2/2019 F.morrau - Structuri de Date

    12/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date12

    2) Prin utilizarea unor colectii de pointeri la un tip neprecizat (void * ) si a unor argumente de acesttip n subprograme, urmnd ca nlocuirea cu un alt tip de pointer (la date specifice aplicatiei) s sefac la executie. Utilizarea unor astfel de functii este mai dificil, dar utilizatorul nu trebuie sintervin n textul surs al subprogramelor si are mai mult flexibilitate n adaptarea colectiilor ladiverse tipuri de date.

    Primul exemplu arat cum se defineste un vector cu componente de un tip T neprecizat n functii,dar precizat n programul care foloseste multimea :

    // multimi de elemente de tipul T#define M 1000 // dimensiune maxima vectortypedef int T ; // tip componente multimetypedef struct {

    T v[M]; // vector cu date de tipul Tint dim; // dimensiune vector

    } Vector;// operatii cu un vector de obiecte

    void initV (Vector & a ) { // initializare vectora.dim=0;

    }void addV ( Vector & a, T x) { // adauga pe x la vectorul aassert (a.n < M); // verifica daca mai este loc in vectora.v [a.n++] = x;

    }int findV ( Vector a, T x) { // cauta pe x in vectorul a

    int j;for ( j=0; j < a.dim;j++)

    if( x == a.v[j] )return j; // gasit in pozitia j

    return -1; // negasit}

    Functiile anterioare sunt corecte numai dac tipul T este un tip numeric pentru c operatiile decomparare la egalitate si de atribuire depind n general de tipul datelor. Operatiile de citire-scriere adatelor depind de asemenea de tipul T , dar ele fac parte n general din programul de aplicatie.

    Pentru operatiile de atribuire si comparare avem dou posibilitti:a) Definirea unor operatori generalizati, modificati prin macro-substitutie :

    #define EQ(a,b) ( a==b) // equals#define LT(a,b) (a < b) // less than

    Exemplu de functie care foloseste acesti operatori:

    int findV ( Vector a, T x) { // cauta pe x in vectorul a

    int j;for ( j=0; j < a.dim;j++)if( EQ (x, a.v[j]) ) // comparatie la egalitate

    return j; // gasit in pozitia jreturn -1; // negasit

    }

    Pentru o multime de siruri de caractere trebuie operate urmtoarele modificri n secventeleanterioare :

    #define EQ(a,b) ( strcmp(a,b)==0) // equals#define LT(a,b) (strcmp(a,b) < 0) // less thantypedef char * T ;

  • 8/2/2019 F.morrau - Structuri de Date

    13/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 13

    b) Transmiterea functiilor de comparare, atribuire, s.a ca argumente la functiile care le folosesc (fra impune anumite nume acestor functii). Exemplu:

    typedef char * T;typedef int (*Fcmp) ( T a, T b) ;

    int findV ( Vector a, T x, Fcmp cmp) { // cauta pe x in vectorul aint j;for ( j=0; j < a.dim;j++)

    if ( cmp (x, a.v[j] ) ==0 ) // comparatie la egalitatereturn j; // gasit in pozitia j

    return -1; // negasit}

    In cazul structurilor de date cu elemente legate prin pointeri (liste si arbori) mai exist o solutie descriere a functiilor care realizeaz operatii cu acele structuri astfel ca ele s nu depind de tipul datelormemorate: crearea nodurilor de list sau de arbore se face n afara functiilor generale (n programul deaplicatie), iar functiile de insertie si de stergere primesc un pointer la nodul de adugat sau de sters si

    nu valoarea ce trebuie adugat sau eliminat. Aceast solutie nu este adecvat structurilor folositepentru cutarea dup valoare (multimi, dictionare).Uneori tipul datelor folosite de o aplicatie este un tip agregat (o structur C): o dat calendaristic

    ce grupeaz numere pentru zi, lun, an , descrierea unui arc dintr-un graf pentru care se memoreaznumerele nodurilor si costul arcului, s.a. Problema care se pune este dac tipul T este chiar tipulstructur sau este un tip pointer la acea structur. Ca si n cazul sirurilor de caractere este preferabil sse manipuleze n programe pointeri (adrese de structuri) si nu structuri. In plus, atribuirea ntrepointeri se face la fel ca si atribuirea ntre numere (cu operatorul '=').

    In concluzie, tipul neprecizat T al elementelor unei colectii este de obicei fie un tip numeric, fie untip pointer (inclusiv de tip void * ). Avantajul principal al acestei solutii este simplitateaprogramelor, dar ea nu se poate aplica pentru colectii de colectii (un vector de liste, de exemplu) sinici pentru colectii neomogene.

    2.3 UTILIZAREA DE POINTERI GENERICI

    O a doua solutie pentru o colectie generic este o colectie de pointeri la orice tip (void *), care vorfi nlocuiti cu pointeri la datele folosite n fiecare aplicatie. Si n acest caz functia de compararetrebuie transmis ca argument functiilor de insertie sau de cutare n colectie. Exemplu de vectorgeneric cu pointeri:

    #define M 100 // dimens maxima vectortypedef void * Ptr; // pointer la un tip neprecizattypedef int (* fcmp) (Ptr,Ptr); // tip functie de comparare

    typedef void (* fprnt) (Ptr); // tip functie de afisare

    typedef struct { // tipul vectorPtr v[M]; // un vector de pointeriint dim; // nr elem in vector

    } Vector;void initV (Vector & a) { // initializare vector

    a.dim = 0;}//afisare date de la adresele continute in vector

    void printV ( Vector a, fprnt print ) {int i;for(i=0;i

  • 8/2/2019 F.morrau - Structuri de Date

    14/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date14

    printf ("\n");}// adaugare la sfirsitul unui vector de pointeri

    void addV ( Vector & a, Ptr p) {assert (a.dim < M);a.v [a.dim ++] = p;

    }// cautare in vector de pointeri

    int findV ( Vector v, Ptr p, fcmp cmp) {int i;for (i=0;i

  • 8/2/2019 F.morrau - Structuri de Date

    15/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 15

    creare a unui graf ca vector de vectori de pointeri la ntregi (liste de noduri vecine pentru fiecare noddin graf):

    void initV (Vector* & pv) { // initializare vectorpv=(Vector*)malloc(sizeof(Vector));

    pv n = 0;

    }// adaugare la sfirsitul unui vector de pointeri

    void addV ( Vector* & pv, Ptr p) {

    asser t (pv n < MAX);

    pv v[pv n ++] = p;}

    // afisare date reunite n vector de orice pointerivoid printV ( Vector* pv, Fprnt print) {

    int i;

    for(i=0;i

  • 8/2/2019 F.morrau - Structuri de Date

    16/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date16

    if ( a.comp(a.v[i],p)==0)return 1;

    return 0;}

    Generalitatea programelor C cu structuri de date vine n conflict cu simplitatea si usurinta de

    ntelegere; de aceea exemplele care urmeaz sacrific generalitatea n favoarea simplittii, pentru cscopul lor principal este acela de a ilustra algoritmi. Din acelasi motiv multe manuale folosesc unpseudo-cod pentru descrierea algoritmilor si nu un limbaj de programare.

    2.4 STRUCTURI DE DATE SI FUNCTII RECURSIVE

    Un subprogram recursiv este un subprogram care se apeleaz pe el nsusi, o dat sau de mai multeori. Orice subprogram recursiv poate fi rescris si nerecursiv, iterativ, prin repetarea explicit aoperatiilor executate la fiecare apel recursiv. O functie recursiv realizeaz repetarea unor operatii fra folosi instructiuni de ciclare.

    In anumite situatii exprimarea recursiv este mai natural si mai compact dect forma

    nerecursiv. Este cazul operatiilor cu arbori binari si al altor algoritmi de tip divide et impera (demprtire n subprobleme).In alte cazuri, exprimarea iterativ este mai natural si mai eficient ca timp si ca memorie folosit,

    fiind aproape exclusiv folosit: calcule de sume sau de produse, operatii de cutare, operatii cu listenlntuite, etc. In plus, functiile recursive cu mai multi parametri pot fi inutilizabile pentru un numrmare de apeluri recursive, acolo unde mrimea stivei implicite (folosit de compilator) este limitat.

    Cele mai simple functii recursive corespund unor relatii de recurent de forma f(n)= rec(f(n-1))unde n este un parametru al functiei recursive. La fiecare nou apel valoarea parametrului n sediminueaz, pn cnd n ajunge 0 (sau 1), iar valoarea f(0) se calculeaz direct si simplu.

    Un alt mod de a interpreta relatia de recurent anterioar este acela c se reduce (succesiv)rezolvarea unei probleme de dimensiune n la rezolvarea unei probleme de dimensiune n-1, pn cndreducerea dimensiunii problemei nu mai este posibil.

    Functiile recursive au cel putin un argument, a crui valoare se modific de la un apel la altul sicare este verificat pentru oprirea procesului recursiv.Orice subprogram recursiv trebuie s contin o instructiune "if" (de obicei la nceput ), care s

    verifice conditia de oprire a procesului recursiv. In caz contrar se ajunge la un proces recursiv ce tindela infinit si se opreste numai prin umplerea stivei.

    Structurile de date liniare si arborescente se pot defini recursiv astfel:- O list de N elemente este format dintr-un element si o (sub)list de N-1 elemente;- Un arbore binar este format dintr-un nod rdcin si cel mult doi (sub)arbori binari;- Un arbore multici este format dintr-un nod rdcin si mai multi (sub)arbori multici.

    Aceste definitii recursive conduc la functii recursive care reduc o anumit operatie cu o list sau cuun arbore la una sau mai multe operatii cu sublista sau subarborii din componenta sa, ca n exempleleurmtoare pentru numrarea elementelor dintr-o list sau dintr-un arbore:

    int count ( struct nod * list) { // numara elementele unei listeif (list==NULL) // daca lista vida

    return 0;else // daca lista nu e vida

    return 1+ count(list next); // un apel recursiv}// numara nodurile unui arbore binar

    int count ( struct tnod * r) {if (r==NULL) // daca arbore vid

    return 0;else // daca arbore nevid

    return 1+ count(r left) + count(r right); // doua apeluri recursive}

  • 8/2/2019 F.morrau - Structuri de Date

    17/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 17

    In cazul structurilor liniare functiile recursive nu aduc nici un avantaj fat de variantele iterativeale acelorasi functii, dar pentru arbori functiile recursive sunt mai compacte si chiar mai usor de

    nteles dect variantele iterative (mai ales atunci cnd este necesar o stiv pentru eliminarearecursivittii). Totusi, ideea folosit n cazul structurilor liniare se aplic si n alte cazuri de functiirecursive (calcule de sume si produse, de exemplu): se reduce rezolvarea unei probleme dedimensiune N la rezolvarea unei probleme similare de dimensiune N-1, n mod repetat, pn cnd seajunge la o problem de dimensiune 0 sau 1, care are o solutie evident.

    Exemplele urmtoare arat c este important locul unde se face apelul recursiv:

    void print1 (int a[],int n) { // afisare vector in ordine inversa -recursivif (n > 0) {

    printf ("%d ",a[n-1]);print1 (a,n-1);

    }}void print2 (int a[],int n) { // afisare vector in ordine directa - recursivif (n > 0) {

    print2 (a,n-1);

    printf ("%d ",a[n-1]);}

    }

    Ideia reducerii la dou subprobleme de acelasi tip, de la functiile recursive cu arbori, poate fifolosit si pentru anumite operatii cu vectori sau cu liste liniare. In exemplele urmtoare se determinvaloarea maxim dintr-un vector de ntregi, cu unul si respectiv cu dou apeluri recursive:

    // maxim dintre doua numere (functie auxiliar)int max2 (int a, int b) {return a>b? a:b;

    }// maxim din vector - recursiv bazat pe recurentaint maxim (int a[], int n) {if (n==1)

    return a[0];else

    return max2 (maxim (a,n-1),a[n-1]);}// maxim din vector - recursiv prin injumatatireint maxim1 (int a[], int i, int j) {int m;if ( i==j )

    return a[i];m= (i+j)/2;return max2 (maxim1(a,i,m), maxim1(a,m+1,j));

    }

    Exemple de cutare secvential a unei valori ntr-un vector neordonat:

    // cautare in vector - recursiv (ultima aparitie)int last (int b, int a[], int n) {if (n

  • 8/2/2019 F.morrau - Structuri de Date

    18/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date18

    // cautare in vector - recursiv (prima aparitie)int first1 (int b, int a[], int i, int j) {if (i>j)

    return -1;if (b==a[i])

    return i;

    return first1(b,a,i+1,j);}

    Metoda mprtirii n dou subprobleme de dimensiuni apropiate (numit divide et impera)aplicat unor operatii cu vectori necesit dou argumente (indici initial si final care definesc fiecaredin subvectori) si nu doar dimensiunea vectorului. Situatia functiei max din exemplul anterior se

    mai ntlneste la cutarea binar ntr-un vector ordonat si la ordonarea unui vector prin metodelequicksort si mergesort. Diferenta dintre functia recursiv care foloseste metoda divide et imperasi functia nerecursiv poate fi eliminat printr-o functie auxiliar:

    // determina maximul dintr-un vector a de n numereint maxim (int a[], int n) {

    return maxim1(a,0,n-1);}// cauta prima apritie a lui b intr-un vector a de n numereint first (int b, int a[], int n) {return first1(b,a,0,n-1);

    }

    Cutarea binar ntr-un vector ordonat mparte succesiv vectorul n dou prti egale, comparvaloarea cutat cu valoarea median, stabileste care din cei doi subvectori poate contine valoareacutat si deci va fi mprtit n continuare. Timpul unei cutri binare ntr-un vector ordonat de nelemente este de ordinul log2(n) fat de O(n) pentru cutarea secvential (singura posibil ntr-unvector neordonat). Exemplu de functie recursiv pentru cutare binar:

    // cutare binar, recursiv a lui b ntre a[i] si a[j]int caut(int b, int a[], int i, int j) {int m;if ( i > j)return -1; // b negsit n a

    m=(i+j)/2; // m= indice median intre i si jif (a[m]==b)

    return m; // b gasit in pozitia melse // daca b != a[m]

    if (b < a[m]) // daca b in prima jumatatereturn caut (b,a,i,m-1); // cauta intre i si m-1

    else // daca b in a doua jumatatereturn caut (b,a,m+1,j); // cauta intre m+1 si

    }

    Varianta iterativ a cutrii binare foloseste un ciclu de njumttire repetat:

    int caut (int b, int a[], int i, int j) {int m;while (i < j ) { // repeta cat timp i

  • 8/2/2019 F.morrau - Structuri de Date

    19/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 19

    elsej=m-1;

    }return -1; // -1 daca b negasit

    }

    Sortarea rapid (quicksort) mparte repetat vectorul n dou partitii, una cu valori mai mici si altacu valori mai mari ca un element pivot, pn cnd fiecare partitie se reduce la un singur element.Indicii i si j delimiteaz subvectorul care trebuie ordonat la un apel al functiei qsort:

    void qsort (int a[], int i, int j) {int m;if (i < j) {

    m=pivot(a,i,j); // determina limita m dintre partitiiqsort(a,i,m); // ordoneaza prima partitieqsort (a,m+1,j); // ordoneaza a doua partitie

    }}

    Indicele m este pozitia elementului pivot, astfel ca a[i]m. De observat c nu se compar elemente vecine din vector (ca n alte metode), ci secompar un element a[p] din prima partitie cu un element a[q] din a doua partitie si deci se aduc mairepede valorile mici la nceputul vectorului si valorile mari la sfrsitul vectorului.

    int pivot (int a[], int p, int q) {int x,t;x=a[(p+q)/2]; // x = element pivotwhile ( p < q) {

    while (a[q]> x) q--;while (a[p] < x) p++;if (p

  • 8/2/2019 F.morrau - Structuri de Date

    20/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date20

    - Se reduc cele dou apeluri la un singur apel recursiv;- Se foloseste o stiv pentru a elimina apelul recursiv neterminal.

    // qsort cu un apel recursivvoid qsort (int a[], int i, int j) {int m;

    while (i < j) { // se ordoneaz alternativ fiecare partitiem=pivot(a, i, j); // indice element pivotqsort(a, i, m); // ordonare partitiei=m+1; m=j; // modifica parametri de apel

    }}

    Cea mai simpl structur de stiv este un vector cu adugare si extragere numai de la sfrsit (vrfulstivei este ultimul element din vector). In stiv se vor pune argumentele functiei care se modific de laun apel la altul:

    void qsort (int a[], int i, int j) {

    int m; int st[500],sp;sp=0;st[sp++]=i; st[sp++]=j; // pune i si j pe stivawhile (sp>=0) {if (i < j) {

    m=pivot(a, i, j);st[sp++]=i; st[sp++]=m; // pune argumente pe stivai=m+1; m=j; // modifica argumente de apel

    }else { // refacere argumente pentru revenire

    j=st[--sp]; i=st[--sp];}

    }

    }

    O functie cu dou apeluri recursive genereaz un arbore binar de apeluri. Un exemplu este calcululnumrului n din sirul Fibonacci F(n) pe baza relatiei de recurent:

    F(n) = F(n-2)+F(n-1) si primele 2 numere din sir F(0)=F(1)=1;Relatia de recurent poate fi transpus imediat ntr-o functie recursiv:

    int F(int n) {if ( n < 2) return 1;return F(n-2)+F(n-1);

    }

    Utilizarea acestei functii este foarte ineficient, iar timpul de calcul creste exponential n raport cun. Explicatia este aceea c se repet rezolvarea unor subprobleme, iar numrul de apeluri al functieicreste rapid pe msur ce n creste. Arborele de apeluri pentru F(6) va fi:

    F(6)

    F(4) F(5)

    F(2) F(3) F(3) F(4)

    F(1) F(2) F(1) F(2) F(2) F(3)

  • 8/2/2019 F.morrau - Structuri de Date

    21/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 21

    Desi n este mic si nu am mai figurat unele apeluri (cu argumente 1 si 0), se observ cum se repetapeluri ale functiei recursive pentru a recalcula aceleasi valori n mod repetat.

    Ideea programrii dinamice este de a nlocui functia recursiv cu o functie care s completezevectorul cu rezultate ale subproblemelor mai mici ( n acest caz, numere din sirul Fibonaci):

    int F(int n) {int k, f[100]={0}; // un vector ( n < 100) initializat cu zerourif[0]=f[1]=1; // initializari in vectorfor ( k=2;k

  • 8/2/2019 F.morrau - Structuri de Date

    22/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date22

    for (j=1;j

  • 8/2/2019 F.morrau - Structuri de Date

    23/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 23

    }

    Functia recursiv care urmeaz rezolv problema pentru n discuri si 3 tije:

    // muta n discuri de pe a pe b folosind si tvoid muta ( int n, int a, int b, int t) {

    if (n==1) // daca a ramas un singur discmutadisc (1,a,b); // se muta direct de pe a pe b

    else { // daca sunt mai multe discuri pe amuta (n-1,a,t,b); // muta n-1 discuri de pe a pe tmutadisc (n,a,b); // muta discul n (de diametru maxim) de pe a pe bmuta (n-1,t,b,a); // muta n-1 discuri de pe t pe b

    }}

    Solutiile nerecursive pornesc de la analiza problemei si observarea unor proprietti ale strilor princare se trece; de exemplu, s-a artat c se poate repeta de (2n1) ori functia mutadisc recalculnd lafiecare pas numrul tijei surs si al tijei destinatie (numrul discului nu conteaz deoarece nu se poate

    muta dect discul din vrful stivei de pe tija surs):int main(){

    int n=4, i; // n = numar de discurifor (i=1; i < (1

  • 8/2/2019 F.morrau - Structuri de Date

    24/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date24

    Capitolul 3

    VECTORI

    3.1 VECTORI

    Structura de vector (array) este foarte folosit datorit avantajelor sale:- Nu trebuie memorate dect datele necesare aplicatiei (nu si adrese de legtur);- Este posibil accesul direct (aleator) la orice element dintr-un vector prin indici;- Programarea operatiilor cu vectori este foarte simpl.- Cutarea ntr-un vector ordonat este foarte eficient, prin cutare binar.

    Dezavantajul unui vector cu dimensiune constant rezult din necesitatea unei estimri adimensiunii sale la scrierea programului. Pentru un vector alocat si realocat dinamic poate apare ofragmentare a memoriei dinamice rezultate din realocri repetate pentru extinderea vectorului. Deasemenea, eliminarea de elemente dintr-un vector compact poate necesita deplasarea elementelor din

    vector.Prin vectori se reprezint si anumite cazuri particulare de liste nlntuite sau de arbori pentrureducerea memoriei ocupate si timpului de prelucrare.

    Ca tipuri de vectori putem mentiona:- Vectori cu dimensiune fix (constant);- Vectori extensibili ( realocabili dinamic);- Vectori de biti (la care un element ocup un bit);- Vectori heap (care reprezint compact un arbore binar particular);- Vectori ca tabele de dispersie.

    De obicei un vector este completat n ordinea cresctoare a indicilor, fie prin adugare la sfrsit anoilor elemente, fie prin insertie ntre alte elemente existente, pentru a mentine ordinea n vector.

    Exist si exceptii de la cazul uzual al vectorilor cu elemente consecutive : vectori cu interval

    (buffer gap) si tabele de dispersie (hash tables).Un buffer gap este folosit n procesoarele de texte; textul din memorie este mprtit n dousiruri pstrate ntr-un vector (buffer cu text) dar separate ntre ele printr-un interval plasat n pozitiacurent de editare a textului. In felul acesta se evit mutarea unor siruri lungi de caractere n memoriela modificarea textului; insertia de noi caractere n pozitia curent mreste secventa de la nceputulvectorului si reduce intervalul, iar stergerea de caractere din pozitia curent mreste intervalul dintrecaracterele aflate nainte si respectiv dup pozitia curent.

    Mutarea cursorului necesit mutarea unor caractere dintr-un sir n cellalt, dar numai ca urmare aunei operatii de modificare n noua pozitie a cursorului.

    Caracterele sterse sau inserate sunt de fapt memorate ntr-un alt vector, pentru a se puteareconstitui un text modificat din greseal (operatia undo de anulare a unor operatii si de revenire lao stare anterioar).

    Vectorii cu dimensiune constant, fixat la scrierea programului, se folosesc n unele situatiiparticulare cnd limita colectiei este cunoscut si relativ mic sau cnd se doreste simplificareaprogramelor, pentru a facilita ntelegerea lor. Alte situatii pot fi cea a unui vector de constante sau decuvinte cheie, cu numr cunoscut de valori.

    Vectori cu dimensiune fix se folosesc si ca zone tampon la citirea sau scrierea n fisiere text sau nalte fluxuri de date.

    Vectorul folosit ntr-un tabel de dispersie are o dimensiune constant (preferabil, un numr prim)din cauza modului n care este folosit (se va vedea ulterior).

    Un fisier binar cu articole de lungime fix poate fi privit ca un vector, deoarece are aceleasiavantaje si dezavantaje, iar operatiile sunt similare: adugare la sfrsit de fisier, cutare secvential nfisier, acces direct la un articol prin indice (pozitie relativ n fisier), sortare fisier atunci cnd estenevoie, s.a. La fel ca ntr-un vector, operatiile de insertie si de stergere de articole consum timp sitrebuie evitate sau amnate pe ct posibil.

  • 8/2/2019 F.morrau - Structuri de Date

    25/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 25

    3.2 VECTORI ORDONATI

    Un vector ordonat reduce timpul anumitor operatii, cum ar fi: cutarea unei valori date, verificareaunicittii elementelor, gsirea perechii celei mai apropiate, calculul frecventei de aparitie a fiecreivalori distincte s.a.

    Un vector ordonat poate fi folosit si drept coad cu prioritti, dac nu se mai fac adugri deelemente la coad, pentru c valoarea minim (sau maxim) se afl la una din extremittile vectorului,de unde se poate scoate fr alte operatii auxiliare.

    Mentinerea unui vector n ordine dup fiecare operatie de adugare sau de stergere nu esteeficient si nici nu este necesar de multe ori; atunci cnd avem nevoie de o colectie dinamicpermanent ordonat vom folosi un arbore binar sau o list nlntuit ordonat. Ordonarea vectorilor seface atunci cnd este necesar, de exemplu pentru afisarea elementelor sortate dup o anumit cheie.

    Pe de alt parte, operatia de sortare este eficient numai pe vectori; nu se sorteaz liste nlntuitesau arbori neordonati sau tabele de dispersie.

    Sunt cunoscuti mai multi algoritmi de sortare, care difer att prin modul de lucru ct si prinperformantele lor. Cei mai simpli si ineficienti algoritmi de sortare au o complexitate de ordinulO(n*n), iar cei mai buni algoritmi de sortare necesit pentru cazul mediu un timp de ordinul

    O(n*log2n), unde n este dimensiunea vectorului.Uneori ne intereseaz un algoritm de sortare stabil, care ptreaz ordinea initial a valoriloregale din vectorul sortat. Mai multi algoritmi nu sunt stabili.

    De obicei ne intereseaz algoritmii de sortare pe loc, care nu necesit memorie suplimentar,desi exist ctiva algoritmi foarte buni care nu sunt de acest tip: sortare prin interclasare si sortare prindistributie pe compartimente.

    Algoritmii de sortare pe loc a unui vector se bazeaz pe compararea de elemente din vector,urmat eventual de schimbarea ntre ele a elementelor comparate pentru a respecta conditia ca oriceelement s fie mai mare ca cele precedente si mai mic ca cele care-i urmeaz.

    Vom nota cu T tipul elementelor din vector, tip care suport comparatia prin operatori ailimbajului (deci un tip numeric). In cazul altor tipuri (structuri, siruri) se vor nlocui operatorii decomparatie (si de atribuire) cu functii pentru aceste operatii.

    Vom defini mai nti o functie care schimb ntre ele elementele din dou pozitii date ale unuivector:

    void swap (T a[ ], int i, int j) { // interschimb a[i] cu a[j]T b=a[i]; a[i]=a[j]; a[j]=b;

    }

    Vom prezenta aici ctiva algoritmi usor de programat, chiar dac nu au cele mai bune performante.Sortarea prin metoda bulelor (Bubble Sort) compar mereu elemente vecine; dup ce se compar

    toate perechile vecine (de la prima ctre ultima) se coboar valoarea maxim la sfrsitul vectorului.La urmtoarele parcurgeri se reduce treptat dimensiunea vectorului, prin eliminarea valorilor finale(deja sortate). Dac se compar perechile de elemente vecine de la ultima ctre prima, atunci se aduce

    n prima pozitie valoarea minim, si apoi se modific indicele de nceput. Una din variantele posibilede implementare a acestei metode este functia urmtoare:

    void bubbleSort(T a[ ], int n) { // sortare prin metoda bulelorint i, k;for (i = 0; i < n; i++) { // i este indicele primului element comparatfor (k = n-1; k > i; k--) // comparatie incepe cu ultima pereche (n-1,n-2)

    if (a[k-1] > a[k]) // daca nu se respecta ordinea crescatoareswap(a,k,k-1); // schimba intre ele elemente vecine

    }}

    Timpul de sortare prin metoda bulelor este proportional cu ptratul dimensiunii vectorului(complexitatea algoritmului este de ordinul n*n).

  • 8/2/2019 F.morrau - Structuri de Date

    26/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date26

    Sortarea prin selectie determin n mod repetat elementul minim dintre toate care urmeaz unuielement a[i] si l aduce n pozitia i, dup care creste pe i.

    void selSort( T a[ ], int n) { // sortare prin selectieint i, j, m; // m = indice element minim dintre i,i+1,..nfor (i = 0; i < n-1; i++) { // in poz. i se aduce min (a[i+1],..[a[n])

    m = i; // considera ca minim este a[i]for (j = i+1; j < n; j++) // compara minim partial cu a[j] (j > i)

    if ( a[j] < a[m] ) // a[m] este elementul minimm = j;

    swap(a,i,m); // se aduce minim din poz. m in pozitia i}

    }

    Sortarea prin selectie are si ea complexitatea O(n*n), dar n medie este mai rapid dect sortareaprin metoda bulelor (constanta care nmulteste pe n*n este mai mic).

    Sortarea prin insertie consider vectorul format dintr-o partitie sortat (la nceput de exemplu) si opartitie nesortat; la fiecare pas se alege un element din partitia nesortat si se insereaz n locul

    corespunztor din partitia sortat, dup deplasarea n jos a unor elemente pentru a crea loc de insertie.void insSort (T a[ ], int n) {int i,j; T x;for (i=1;i 100 pasii folositi vorfi 13,4 si 1.

    void shellSort(T a[ ], int n) {int h, i, j; T t;

    // calcul increment maximh = 1;if (n < 14) h = 1;else if ( n > 29524) h = 3280;else {

    while (h < n) h = 3*h + 1;h /= 3; h /= 3;

    }// sortare prin insertie cu increment h variabil

    while (h > 0) {

    for (i = h; i < n; i++) {

  • 8/2/2019 F.morrau - Structuri de Date

    27/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 27

    t = a[i];for (j = i-h; j >= 0 && a[j]> t; j -= h)

    a[j+h] = a[j];a[j+h] = t;

    }h /= 3; // urmatorul increment

    }}

    3.3 VECTORI ALOCATI DINAMIC

    Putem distinge dou situatii de alocare dinamic pentru vectori:- Dimensiunea vectorului este cunoscut de program naintea valorilor ce trebuie memorate n vector

    si nu se mai modific pe durata executiei programului; n acest caz este suficient o alocare initial dememorie pentru vector (malloc).- Dimensiunea vectorului nu este cunoscut de la nceput sau numrul de elemente poate creste pe

    msur ce programul evolueaz; n acest caz este necesar extinderea dinamic a tabloului (se

    apeleaz repetat "realloc").In limbajul C utilizarea unui vector alocat dinamic este similar utilizrii unui vector cu

    dimensiune constant, cu diferenta c ultimul nu poate fi realocat dinamic. Functia "realloc"simplific extinderea (realocarea) unui vector dinamic cu pstrarea datelor memorate. Exemplu deordonare a unui vector de numere folosind un vector alocat dinamic.

    // comparatie de ntregi - pentru qsortint intcmp (const void * p1, const void * p2) {

    return *(int*)p1 - *(int*)p2;}

    // citire - sortare - afisarevoid main () {

    int * vec, n, i; // vec = adresa vector// citire vectorprintf ("dimens. vector= "); scanf ("%d", &n);vec= (int*) malloc (n*sizeof(int));for (i=0;i

  • 8/2/2019 F.morrau - Structuri de Date

    28/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date28

    char * * tab; // tabel de pointeri la cuvinteint i, n, nmax=INC; // nc= numar de cuvinte in listan=0;tab = (char**)malloc(nmax*sizeof(char*)); // alocare initiala ptr vectorwhile (scanf ("%s",cuv) > 0) { // citeste un cuvant

    pc =strdup(cuv); // aloca memorie ptr cuvant

    if (find (tab,n,pc) < 0) { // daca nu exista dejaif (n ==nmax) { // daca vector plin

    nmax = nmax+INC; // mareste capacitate vectortab =(char**)realloc(tab,nmax*sizeof(char*)); // realocare

    }tab[n++]=pc; // adauga la vector adresa cuvant

    }}

    }

    Functia "realloc" primeste ca argumente adresa vectorului ce trebuie extins si noua sa dimensiunesi are ca rezultat o alt adres pentru vector, unde s-au copiat automat si datele de la vechea adres.Aceast functie este apelat atunci cnd se cere adugarea de noi elemente la un vector plin (n carenu mai exist pozitii libere).

    Utilizarea functiei "realloc" necesit memorarea urmtoarelor informatii despre vectorul ce va fiextins: adres vector, dimensiunea alocat (maxim) si dimensiunea efectiv. Cnd dimensiuneaefectiv ajunge egal cu dimensiunea maxim, atunci devine necesar extinderea vectorului.Extinderea se poate face cu o valoare constant sau prin dublarea dimensiunii curente sau dup altmetod.

    Exemplul urmtor arat cum se pot ncapsula n cteva functii operatiile cu un vector alocat si apoiextins dinamic, fr ca alocarea si realocarea s fie vizibile pentru programul care foloseste acestesubprograme.

    #define INC 100 // increment de exindere vectortypedef int T; // tip componente vectortypedef struct {

    T * vec; // adresa vector (alocat dinamic)int dim, max; // dimensiune efectiva si maxima

    } Vector; // initializare vector v

    void initV (Vector & v) {v.vec= (T *) malloc (INC*sizeof(T));v.max=INC; v.dim=0;

    }// adaugare obiect x la vectorul v

    void addV ( Vector & v, T x) {if (v.dim == v.max) {

    v.max += INC; // extindere vector cu o valoare fixav.vec=(T*) realloc (v.vec, (v.max)*sizeof(T));

    }v.vec[v.dim]=x; v.dim ++;

    }

    Exemplu de program care genereaz si afiseaz un vector de numere:

    void main() {T x; Vector v;initV ( v);while (scanf("%d",&x) == 1)

    addV ( v,x);

    printV (v);

  • 8/2/2019 F.morrau - Structuri de Date

    29/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 29

    }

    Timpul necesar pentru cutarea ntr-un vector neordonat este de ordinul O(n), deci proportional cudimensiunea vectorului. Intr-un vector ordonat timpul de cutare este de ordinul O(lg n). Adugarea lasfrsitul unui vector este imediat ( are ordinul O(1)) iar eliminarea dintr-un vector compact necesitmutarea n medie a n/2 elemente, deci este de ordinul O(n).

    3.4 APLICATIE :COMPONENTE CONEXE

    Aplicatia poate fi formulat cel putin n dou moduri si a condus la aparitia unui tip abstract dedate, numit colectie de multimi disjuncte (Disjoint Sets).

    Fiind dat o multime de valori (de orice tip) si o serie de relatii de echivalent ntre perechi devalori din multime, se cere s se afiseze clasele de echivalent formate cu ele. Dac sunt n valori,atunci numrul claselor de echivalent poate fi ntre 1 si n, inclusiv.

    Exemplu de date initiale (relatii de echivalent):30 ~ 60 / 50 ~ 70 / 10 ~ 30 / 20 ~ 50 / 40 ~ 80 / 10 ~ 60 /

    Rezultatul (clasele de echivalenta) : {10,30,60}, {20,50,70}, {40,80}

    O alt formulare a problemei cere afisarea componentelor conexe dintr-un graf neorientat definitprintr-o list de muchii. Fiecare muchie corespunde unei relatii de echivalent ntre vrfurile unite demuchie, iar o component conex este un subgraf (o clas de noduri ) n care exist o cale ntre oricaredou vrfuri. Exemplu:

    1

    8 2

    7 3

    6 4

    5

    In cazul particular al componentelor conexe dintr-un graf, este suficient un singur vector cls,unde cls[k] este componenta n care se afl vrful k.

    In cazul mai general al claselor de echivalent ce pot contine elemente de orice tip (numereoarecare sau siruri ce reprezint nume), mai este necesar si un vector cu valorile elementelor. Pentruexemplul anterior cei doi vectori pot arta n final astfel (numerele de clase pot fi diferite):

    val 10 20 30 40 50 60 70 80

    cls 1 2 1 3 2 1 2 3

    Vectorii val, cls si dimensiunea lor se reunesc ntr-un tip de date numit colectie de multimidisjuncte, pentru c fiecare clas de echivalent este o multime, iar aceste multimi sunt disjuncte

    ntre ele.

    typedef struct {int val[40], cls[40]; // vector de valori si de claseint n; // dimensiune vectori

    } ds;

    Pentru memorarea unor date agregate ntr-un vector avem dou posibilitti:

  • 8/2/2019 F.morrau - Structuri de Date

    30/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date30

    - Mai multi vectori paraleli, cu aceeasi dimensiune; cte un vector pentru fiecare cmp din structur(ca n exemplul anterior).

    - Un singur vector de structuri:

    typedef struct { // o pereche valoare-clasint val; int cls;

    } entry;typedef struct {

    entry a [40]; // vector de perechi valoare-clasint n; // dimensiune vector

    } ds;

    S-au stabilit urmtoarele operatii specifice tipului abstract Disjoint Sets:- Initializare colectie (initDS)- Gsirea multimii (clasei) care contine o valoare dat (findDS)- Reunire multimi (clase) ce contin dou valori date (unifDS)- Afisare colectie de multimi (printDS)

    La citirea unei perechi de valori (unei relatii de echivalent) se stabileste pentru cele dou valori

    echivalente acelasi numr de clas, egal cu cel mai mic dintre cele dou (pentru a mentine ordinea nfiecare clas).

    Dac valorile sunt chiar numerele 1,2,3...8 atunci evolutia vectorului de clase dup fiecare perechede valori citit va fi

    claseinitial 1 2 3 4 5 6 7 8

    dupa 3-6 1 2 3 4 5 3 7 8dupa 5-7 1 2 3 4 5 3 5 8dupa 1-3 1 2 1 4 5 1 5 8dupa 2-5 1 2 1 4 2 1 2 8dupa 4-8 1 2 1 4 2 1 2 4dupa 1-6 1 2 1 4 2 1 2 4 (nu se mai modific nimic)

    Urmeaz un exemplu de implementare cu un singur vector a tipului Colectie de multimi

    disjuncte si utilizarea sa n problema afisrii componentelor conexe.

    typedef struct {int cls[40]; // vector cu numere de multimiint n; // dimensiune vector

    } ds;// determina multimea in care se afla x

    int find ( ds c, int x) {return c.cls[x];

    }// reunire multimi ce contin valorile x si y

    void unif ( ds & c,int x,int y) {int i,cy;cy=c.cls[y];

    for (i=1;i

  • 8/2/2019 F.morrau - Structuri de Date

    31/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 31

    printf("%d ",j);m++;

    }if (m) // daca exista valori in multimea iprintf("\n"); // se trece pe alta linie

    }

    }// initializare multimi din colectie

    void initDS (ds & c, int n) {int i;c.n=n;for (i=1;i 0) // citeste muchii x-y

    unif(c,x,y); // reuneste componentele lui x si yprintDS(c); // afisare componente conexe

    }

    In aceast implementare operatia find are un timp constant O(1), dar operatia de reuniune este deordinul O(n). Vom arta ulterior (la discutia despre multimi) o alt implementare, mai performant,dar tot prin vectori a colectiei de multimi disjuncte.

    3.5VECTORI MULTIDIMENSIONALI (MATRICE)

    O matrice bidimensional poate fi memorat n cteva moduri:

    - Ca un vector de vectori. Exemplu :char a[20][20]; // a[i] este un vector- Ca un vector de pointeri la vectori. Exemplu:

    char* a[20]; // sau char ** a;- Ca un singur vector ce contine elementele matricei, fie n ordinea liniilor, fie n ordinea coloanelor.

    Matricele alocate dinamic sunt vectori de pointeri la liniile matricei.Pentru comparatie vom folosi o functie care ordoneaz un vector de nume (de siruri) si functii de

    citire si afisare a numelor memorate si ordonate.Prima form (vector de vectori) este cea clasic, posibil n toate limbajele de programare, si are

    avantajul simplittii si clarittii operatiilor de prelucrare.De remarcat c numrul de coloane al matricei transmise ca argument trebuie s fie o constant,

    aceeasi pentru toate functiile care lucreaz cu matricea.

    #define M 30 // nr maxim de caractere intr-un sir// ordonare sirurivoid sort ( char vs[][M], int n) {int i,j; char tmp[M];for (j=1;j

  • 8/2/2019 F.morrau - Structuri de Date

    32/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date32

    // citire siruri in matriceint citmat ( char vs[][M] ) {

    int i=0;printf ("lista de siruri: \n");while ( scanf ("%s", vs[i])==1 )

    i++;

    return i; // numar de siruri citite}// afisare matrice cu sirurivoid scrmat (char vs[][M],int n) {

    int i;for (i=0;i

  • 8/2/2019 F.morrau - Structuri de Date

    33/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 33

    typedef long Set; // multimi cu max 32 de elemente cu valori intre 0 si 31void initS ( Set & a) { // initializare multimea=0;

    }void addS (Set & a, int x) { // adauga element la multime

    a= a | (1L

  • 8/2/2019 F.morrau - Structuri de Date

    34/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date34

    // elementul [i][j] din matrice graf primeste valoarea val (0 sau 1)void setbit (graf & g, int i, int j, int val) {int nb = g.n*(i-1) +j; // nr bit in matriceint no = nb/8 +1; // nr octet in matriceint nbo = nb % 8; // nr bit in octetul noint b=0x80;

    int mask = (b >> nbo); // masca selectare bit nbo din octetul noif (val)g.b[no] |= mask;

    elseg.b[no] &= ~mask;

    }// valoare element [i][j] din matrice grafint getbit (graf g, int i, int j ) {int nb = g.n*(i-1) +j; // nr bit in matriceint no = nb/8 +1; // nr octet in matriceint nbo = nb % 8; // nr bit in octetul noint b=0x80;int mask= (b >>nbo);

    return mask==( g.b[no] & mask);}// citire date si creare matrice graf

    void citgraf (graf & g ) {int no,i,j;printf("nr. noduri: "); scanf("%d",&g.n);no = g.n*g.n/8 + 1; // nr de octeti necesarifor (i=0;i

  • 8/2/2019 F.morrau - Structuri de Date

    35/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 35

    Capitolul 4

    LISTE CU LEGTURI

    4.1 LISTE NLNTUITE

    O list nlntuit ("Linked List") este o colectie de elemente, alocate dinamic, dispersate nmemorie, dar legate ntre ele prin pointeri, ca ntr-un lant. O list nlntuit este o structur dinamic,flexibil, care se poate extinde continuu, fr ca utilizatorul s fie preocupat de posibilitatea depsiriiunei dimensiuni estimate initial (singura limit este mrimea zonei "heap" din care se solicitmemorie).

    Vom folosi aici cuvntul "list" pentru o list liniar, n care fiecare element are un singur succesorsi un singur predecesor.

    Intr-o list nlntuit simpl fiecare element al listei contine adresa elementului urmtor din list.Ultimul element poate contine ca adres de legtur fie constanta NULL (un pointer ctre nicieri),fie adresa primului element din list ( dac este o list circular ), fie adresa unui element terminatorcu o valoare special.

    Adresa primului element din list este memorat ntr-o variabil pointer cu nume (alocat lacompilare) si numit cap de list ("list head").

    cap val leg val leg val leg

    Este posibil ca variabila cap de list s fie tot o structur si nu un pointer:

    val val val

    cap leg leg leg

    Un element din list (numit si nod de list) este de un tip structur si are (cel putin) dou cmpuri:un cmp de date (sau mai multe) si un cmp de legtur. Exemplu:

    typedef int T; // orice tip numerictypedef struct nod {

    T val ; // cmp de datestruct nod *leg ; // cmp de legtur

    } Nod;

    Continutul si tipul cmpului de date depind de informatiile memorate n list si deci de aplicatiacare o foloseste. Toate functiile care urmeaz sunt direct aplicabile dac tipul de date nedefinit T esteun tip numeric (aritmetic).

    Tipul List poate fi definit ca un tip pointer sau ca un tip structur:

    typedef Nod* List; // list ca pointertypedef Nod List; // list ca structur

    O list nlntuit este complet caracterizat de variabila "cap de list", care contine adresa primuluinod (sau a ultimului nod, ntr-o list circular). Variabila care defineste o list este de obicei ovariabil pointer, dar poate fi si o variabil structur.

    Operatiile uzuale cu o list nlntuit sunt :- Initializare list ( a variabilei cap de list ): initL (List &)- Adugarea unui nou element la o list: addL (List&, T)

    0

  • 8/2/2019 F.morrau - Structuri de Date

    36/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date36

    - Eliminarea unui element dintr-o list: delL (List&, T)- Cutarea unei valori date ntr-o list: findL (List, T)- Test de list vid: emptyL(List)- Determinarea dimensiunii listei: sizeL (List)- Parcurgerea tuturor nodurilor din list (traversare list).

    Accesul la elementele unei liste cu legturi este strict secvential, pornind de la primul element sitrecnd prin toate nodurile precedente celui cutat, sau pornind din elementul "curent" al listei, dacse memoreaz si adresa elementului curent al listei.

    Pentru parcurgere se foloseste o variabil cursor, de tip pointer ctre nod, care se initializeaz cuadresa cap de list; pentru a avansa la urmtorul element din list se foloseste adresa din cmpul delegtur al nodului curent:

    Nod *p, *prim;p = prim; // adresa primului element.. .

    p = p leg; / / avans la urmatorul nod

    Exemplu de afisare a unei liste nlntuite definite prin adresa primului nod:

    void printL ( Nod* lst) {while (lst != NULL) { // repeta cat timp exista ceva la adresa lst

    printf ("%d ",lst val); // afisare date din nodul de la adresa lst

    lst=lst leg; // avans la nodul urmator din l ista}

    }

    Cutarea secvential a unei valori date ntr-o list este asemntoare operatiei de afisare, dar areca rezultat adresa nodului ce contine valoarea cutat .

    // cutare ntr-o list neordonat

    Nod* findL (Nod* lst, T x) {whi le ( lst!=NULL && x != lst val)

    lst = lst leg;return lst; // NULL dac x negsit

    }}

    Functiile de adugare, stergere si initializare a listei modific adresa primului element (nod) dinlist; dac lista este definit printr-un pointer atunci functiile primesc un pointer si modific (uneori)acest pointer. Daca lista este definit printr-o variabil structur atunci functiile modific structura, casi n cazul stivei vector.

    In varianta listelor cu element santinel nu se mai modific variabila cap de list deoarece contine

    mereu adresa elementului santinel, creat la initializare.Operatia de initializare a unei liste stabileste adresa de nceput a listei, fie ca NULL pentru listefr santinel, fie ca adres a elementului santinel.

    Crearea unui nou element de list necesit alocarea de memorie, prin functia malloc n C sau prinoperatorul new n C++. Verificarea rezultatului cererii de alocare (NULL, dac alocare imposibil) sepoate face printr-o instructiune if sau prin functia assert, dar va fi omis n continuare. Exemplude alocare:

    nou = (Nod*) malloc( sizeof(Nod)); // sau nou = new Nod;assert (nou != NULL); // se include

    Adugarea unui element la o list nlntuit se poate face:

    - Mereu la nceputul listei;

  • 8/2/2019 F.morrau - Structuri de Date

    37/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 37

    - Mereu la sfrsitul listei;- Intr-o pozitie determinat de valoarea noului element.

    Dac ordinea datelor din list este indiferent pentru aplicatie, atunci cel mai simplu este caadugarea s se fac numai la nceputul listei. In acest caz lista este de fapt o stiv iar afisareavalorilor din list se face n ordine invers introducerii n list.Exemplu de creare si afisare a unei liste nlntuite, cu adugare la nceput de list:

    typedef Nod* List; // ptr a permite redefinirea tipului "List"void main () {

    List lst; int x; Nod * nou; // nou=adresa element noulst=NULL; // initializare lista vidawhile (scanf("%d",&x) > 0) {

    nou=(Nod*)malloc(sizeof(Nod)); // aloca memorie

    nou val=x; nou leg=lst ; / / completare e lementlst=nou; // noul element este primul

    }while (lst != NULL) { // afisare lista

    printf("%d ",lst val); // in ordine inversa celei de adaugare

    lst=lst leg;}

    }

    Operatiile elementare cu liste se scriu ca functii, pentru a fi reutilizate n diferite aplicatii. Pentrucomparatie vom ilustra trei dintre posibilittile de programare a acestor functii pentru liste stiv, cuadugare si eliminare de la nceput.

    Prima variant este pentru o list definit printr-o variabil structur, de tip Nod:

    void initS ( Nod * s) { // initializare stiva (s=var. cap de lista)

    s leg = NULL;}

    // pune in stiva un elementvoid push (Nod * s, int x) {Nod * nou = (Nod*)malloc(sizeof(Nod));

    nou val = x; nou leg = s leg;

    s leg = nou;}

    // scoate din stiva un elementint pop (Nod * s) {Nod * p; int rez;

    p = s leg; // adresa primului element

    rez = p val; // valoare din varful stivei

    s leg = p leg; // adresa element urmatorfree (p) ;

    return rez;}// utilizareint main () {Nod st; int x;initS(&st);for (x=1;x

  • 8/2/2019 F.morrau - Structuri de Date

    38/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date38

    void initS ( Nod ** sp) {*sp = NULL;

    }// pune in stiva un element

    void push (Nod ** sp, int x) {Nod * nou = (Nod*)malloc(sizeof(Nod));

    nou val = x; nou leg = *sp;*sp = nou;

    }// scoate din stiva un element

    int pop (Nod ** sp) {Nod * p; int rez;

    rez = (*sp) val;p = (*sp) leg;free (*sp) ;*sp = p;return rez;

    }// utilizare

    int main () {Nod* st; int x;initS(&st);for (x=1;x

  • 8/2/2019 F.morrau - Structuri de Date

    39/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 39

    Structura de list nlntuit poate fi definit ca o structur recursiv: o list este format dintr-unelement urmat de o alt list, eventual vid. Acest punct de vedere poate conduce la functii recursivepentru operatii cu liste, dar fr nici un avantaj fat de functiile iterative. Exemplu de afisare recursiva unei liste:

    void printL ( Nod* lst) {

    if (lst != NULL) { // daca (sub)lista nu e vidprintf ("%d ",lst val); // afisarea primului element

    printL (lst leg); // afisare sublist de dup primul element}

    }

    4.2COLECTII DE LISTE

    Listele sunt preferate vectorilor atunci cnd aplicatia foloseste mai multe liste de lungimi foartevariabile si impredictibile, deoarece asigur o utilizare mai bun a memoriei. Reunirea adreselor de

    nceput ale listelor ntr-o colectie de pointeri se face fie printr-un vector de pointeri la liste, fie printr-olist nlntuit de pointeri sau printr-un arbore ce contine n noduri pointeri la liste.

    Mentionm cteva aplicatii clasice care folosesc colectii de liste:- Sortarea pe compartimente (Radix Sort sau Bin Sort);- O colectie de multimi disjuncte, n care fiecare multime este o list;- Un graf reprezentat prin liste de adiacente (liste cu vecinii fiecrui nod);- Un dictionar cu valori multiple, n care fiecare cheie are asociat o list de valori;- Un tabel de dispersie (Hashtable) realizat ca vector de liste de coliziuni;

    O colectie liniar de liste se reprezint printr-un vector de pointeri atunci cnd este necesar unacces direct la o list printr-un indice (grafuri, sortare pe ranguri, tabele hash) sau printr-o list depointeri atunci cnd numrul de liste variaz n limite largi si se poate modifica dinamic (ca ntr-undictionar cu valori multiple).

    In continuare se prezint succint sortarea dup ranguri (pe compartimente), metod care mparte

    valorile de sortat n mai multe compartmente, crora le corespund tot attea liste nlntuite.Sortarea unui vector de n numere (cu maxim d cifre zecimale fiecare) se face n d treceri: la fiecaretrecere k se distribuie cele n numere n 10 compartimente (liste) dup valoarea cifrei din pozitia k(k=1 pentru cifra din dreapta), si apoi se reunesc listele n vectorul de n numere (n care ordinea semodific dup fiecare trecere).

    Algoritmul poate fi descris astfel:

    repet pentru k de la 1 la d // pentru fiecare ranginitializare vector de liste trepet pentru i de la 1 la n // distribuie elem. din x in 10 liste

    extrage in c cifra din pozitia k a lui x[i]adaug x[i] la lista t[c]

    repet pentru j de la 0 la 9 // reunire liste in vectorul x

    adaug toat lista j la vectorul x

  • 8/2/2019 F.morrau - Structuri de Date

    40/197

    ------------------------------------------------------------------------- Florian Moraru: Structuri de Date40

    Exemplu cu n=9 si d=3 :

    Initial Trecerea 1 Trecerea 2 Trecerea 3

    Vector cifra liste vector cifra liste vector cifra liste vector459 0 472 0 432 0 177

    254 1 432 1 534 1 177 239472 2 472,432 254 2 239 2 239,254 254534 3 534 3 432,534,239 649 3 432649 4 254,534,654 654 4 649 254 4 432,459,472 459239 5 177 5 254,654,459 654 5 534 472432 6 459 6 459 6 649,654 534654 7 177 649 7 472,177 472 7 649177 8 239 8 177 8 654

    9 459,649,239 9 9

    Cifra din pozitia k a unui numr y se obtine cu relatia: c = (y / pow(10,k-1)) % 10;Adugarea de elemente la o list (n faza de distribuire) se face mereu la sfrsitul listei, dar

    extragerea din liste (n faza de colectare a listelor) se face mereu de la nceputul listelor, ceea ce faceca fiecare list s se comporte ca o coad.

    Pentru ordonare de cuvinte formate din litere numrul de compartimente va fi numrul de literedistincte (26 dac nu conteaz diferenta dintre litere mari si mici).

    Functia urmtoare implementeaz algoritmul de sortare pe ranguri:

    void radsort (int x[ ], int n) {int div=1; // divizor (puteri ale lui 10)int i,k,c,d=5; // d= nr maxim de cifre in numerele sortateList t [10]; // vector de pointeri la liste

    // repartizare valori din x in listele tfor (k=1; k

  • 8/2/2019 F.morrau - Structuri de Date

    41/197

    Florian Moraru: Structuri de Date ------------------------------------------------------------------------- 41

    Init