Lucrarea nr.7

IMPLEMENTAREA TDA MULTIME PRIN STRUCTURI DE DATE DE NIVEL SUPERIOR

1.IMPLEMENTAREA MULTIMILOR CU AJUTORUL ARBORILOR DE REGASIRE (TRIE TREES)

Arborii de regasire sunt structuri de date speciale care pot fi utilizate in reprezentarea multimilor de caractere sau a tipurilor de date care sunt siruri de obiecte de orice tip sau siruri de numere.

Un arbore de regasire permite implementarea simpla a operatorilor definiti asupra unei structuri de date de tip multime, ale carei elemente sunt siruri de caractere (cuvinte) (INSERTIE, SUPRIMARE, INITIALIZARE si TIPARIRE).

Utilizarea e eficienta atunci cand numarul de prefixe distincte ale tuturor cuvintelor din multime e mult mai mic decat numarul total de cuvinte.

Intr-un arbore de regasire, fiecare drum de la radacina spre un nod terminal, corespunde unui cuvant al multimii. Nodurile arborelui corespund prefixelor cuvintelor. Pentru delimitarea cuvintelor se foloseste caracterul '[' ( ord('[') = ord('Z') + 1 ).

Se pot face urmatoarele observatii:

  1. fiecare nod are cel mult 27 de fii ( literele din alfabet + '[');
  2. cele mai multe din noduri au mai putin de 27 de fii;
  3. un nod la care se ajunge printr-un ram etichetat cu '[', poate fi omis din structura ( nu are nici un fiu).

1.1. Structura de date "nod al unui arbore de regasire"

a)Implementarea bazata pe tablouri

Un nod al unui arbore de regasire poate fi privit ca o structura asociere cu domeniul multimea {'A'..'Z','['}={'A'..'['} si codomeniul, o multime de valori, apartinand tipului PointerNodArboreDeRegasire (deci o asociere definita pe multimea caracterelor cu valori in multimea pointerilor la noduri). Un arbore poate fi identificat printr-un pointer la radacina sa (sau prin nodul respectiv).

Daca p e un pointer, Nod o valoare a structurii NodArboreDeRegasire, c o.valoare a tipului char, asupra structurii NodArboreDeRegasire, se definesc, urmatorii operatori:

  1. Atribuie(Nod,c,p) - asociaza caracterului c din Nod, valoarea p;
  2. Valoare(Nod,c) - furnizeaza valoarea pointerului asociat caracterului c din Nod;
  3. NodNou(Nod,c) - face ca valoarea lui Nod pentru caracterul c, sa fie un pointer la un Nod nou;
  4. Initializeaza(Nod) - face ca nodul Nod sa indice asocierea nula.
Se pot folosi urmatoarele structuri de date:
	type PointerNodArboreDeRegasire =^NodArboreDeRegasire;
	     caractere='A'..'[';
             NodArboreDeRegasire=array[caractere] of PointerNodArboreDeRegasire;

Pentru caracterul '[' se adopta conventia : Nod['['] sa fie nil (cand nodul nu are fiu corespunzator lui '[') sau pointerul spre insusi Nod (cand nodul are fiu, indicand terminarea unui cuvant).

Exemplu: in figura 1. este reprezentat arborele trie care contine cuvintele AR, ARAR, ARAT, PI, PIAN. Fig.1. Arbore trie implementat cu tablouri

O implementare a operatorilor ar fi:

procedure Initializare(var Nod:NodArboreDeRegasire);
          var c:char;
          begin
               for c:='A' to '[' do Nod[c]:=nil
          end;

procedure Atribuie(var Nod:NodArboreDeRegasire; c:char;
                     p:PointerArboreDeRegasire);
     begin
          Nod[c]:=p
     end;

function Valoare(var Nod:NodArboreDeRegasire; c:char)
                     :PointerArboreDeRegasire;
     begin
          Valoare:=Nod[c]
     end;

procedure NodNou(var Nod:NodArboreDeRegasire; c:char);
     begin
           new(Nod[c]);
           Initializare(Nod[c]^)
     end;

Definind type ArboreDeRegasire=PointerNodArboreDeRegasire; se poate scrie procedura de adaugare a unui cuvant x in multimea cuvinte implementata printr-un arbore de regasire:
procedure Adauga(x:TipCuvint;var cuvinte:ArboreDeRegasire);
  var i:integer; {contorizeaza pozitia in cuvantul x}
      t:ArboreDeRegasire; {utilizat pentru a indica nodurile
        arborelui de regasire corespunzatoare prefixelor lui x}
  begin
      i:=1;
      t:=cuvinte;
      while x[i]<>'[' do
         begin
           if Valoare[t^,x[i])=nil then
            { nodul curent nu are fiu pentru x[i],
                   deci se creaza unul nou}
                 NodNou(t^,x[i]);
           t:=Valoare(t^,x[i]); {se trece la fiul lui t
              pentru  caracterul x[i], chiar daca a fost creat recent}
           i:=i+1
        end; {s-a ajuns la caracterul '[' in x, deci la
         sfarsitul lui}
      Atribuie(t^,'[',t); {se face o bucla pentru '[',
                            pentru a marca un nod terminal}
  end;

Arborele vid apare continand un singur nod, cu toti pointerii nil.

Pentru stergerea unui cuvant, se poate concepe o procedura recursiva, fiecare apel recursiv coborand cu cate un nivel in arborele de regasire.

Se coboara pana la nodul ce marcheaza sfarsitul cuvantului, avand intrarea corespunzatoare lui '[' diferita de nil . Prin punerea pe nil a acestei intrari, practic cuvantul e sters din dictionar.

Se mai poate verifica daca restul intrarilor din acest nod final sunt nil ( deci cuvantul sters nu e prefix pentru altele din dictionar ), caz in care se poate elibera spatiul ocupat de acest nod si se pune pe nil pointerul ce-l indica. La revenirea din recursivitate, se pot elibera spatiile ocupate de toate nodurile avand toti pointerii pe nil.

Configuratia arborelui trie nu depinde de ordinea in care se fac insertiile sau suprimarile.

Listarea cuvintelor unui arbore de regasire se face parcurgand cu o procedura recursiva nodurile, colectand la fiecare nivel, cate un caracter din cuvantul curent. Pentru fiecare nod, prelucrarea consta in: - daca e marcat sfarsitul unui cuvant ( elementul de indice '[' nu e nil ), se tipareste cuvantul curent ( tabloul in care s-au cules caracterele ) - pentru fiecare pointer corespunzator caracterelor 'A'..'Z', diferit de nil, se adauga respectivul caracter cuvantului curent si se apeleaza procedura recursiva de listare pentru descendent.

b)Implementarea cu ajutorul listelor

Se pot gasi diferite implementari, observand ca un nod de regasire este de fapt o asociere. Deoarece domeniul si codomeniul contin un numar restrans de elemente, apare potrivita reprezentarea bazata pe liste inlantuite.

Exemplu: in figura 2. este reprezentat arborele de regasire implementat cu ajutorul listelor si care contine cuvintele AR, ARAR, ARAT, PI, PIAN.

Fig.2. Arbore trie implementat cu liste

type NodArboreDeRegasire=^NodLista;
     NodLista=record
 	     domeniu:char;
	     valoare,urmator:NodArboreDeRegasire
               {indica prima celula a listei nodului fiu, respectiv   urmatoarea
                celula a listei nodului curent}
	     end;

O alta varianta ar fi ca sufixele ( resturile cuvintelor ce au un prefix comun) sa fie memorate in cate o lista inlantuita sau un ABO in scopul reducerii memoriei ocupate de arborele trie.

2.IMPLEMENTAREA MULtIMILOR CU AJUTORUL ARBORILOR B-BINARI (ARBORI 2-3)

Cum in cazul arborilor, performantele prelucrarii depind de inaltimea lor, e avantajos a se lucra cu arbori echilibrati : AVL, binari optimi sau binari B, insa algoritmii de prelucrare sunt foarte complecsi. in continuare se va prezenta implementarea folosind arborii B-binari, care sunt arbori-B de ordinul 1. Au toate nodurile terminale pe acelasi nivel, iar nodurile interioare au unul sau doua elemente, deci doi sau trei descendenti (se mai numesc arbori 2-3).

Se va utiliza o implementare diferita de cea prezentata la Lucrarea 5 referitoare la arborii B-binari.

Se presupune ca arborele reprezinta o multime de elemente peste care este definita o relatie de ordonare notata '<'. Elementele sunt plasate in nodurile terminale; daca un element x se gaseste la stanga unui element y, e valabila relatia x In fiecare nod interior al structurii de arbore 2-3 va fi memorata, pe prima pozitie, cheia celui mai mic descendent al celui de-al doilea fiu al nodului; daca nodul contine si un al treilea fiu, atunci pe pozitia urmatoare in nod se inregistreaza cheia celui mai mic descendent al acestui fiu ( prin cel mai mic descendent se intelege elementul terminal cu cheia cea mai mica in contextul considerat ).

Un arbore 2-3 cu h niveluri, contine intre 2h-1si 3h-1noduri terminale, deci arborele 2-3 de reprezentare a unei multimi de n elemente, are inaltimea intre 1+log3n si 1+log2n.

Cautarea unei chei intr-un arbore 2-3, se realizeaza cu un efort O(log n), prin particularizarea cautarii intr-un arbore B. Astfel, cautarea unei chei x se realizeaza simplu, parcurgand arborele de la radacina spre nodurile terminale. in fiecare nod parcurs, continand cheile (a,-) sau (a,b), se compara x cu a, daca x=a si nodul are numai doi fii, se trece la fiul al doilea, altfel se compara x cu b si daca x e mai mic decat b se trece la fiul al doilea al nodului, altfel la fiul al treilea. Elementul x exista in arbore daca si numai daca exista un nod terminal cu cheia x. Cautarea se poate opri la depistarea unei egalitati de genul x=a sau x=b in cadrul nodurilor interioare, daca se urmareste numai apartenenta, sau trebuie sa continue pana la depistarea nodului terminal, daca se doreste prelucrarea acestuia.

Implementarea acestei reprezentari se poate realiza in limbajul PASCAL, utilizand structura de tip articol cu variante:

type TipElement=record
	        cheie:real;
	        {alte campuri utile}
	      end;
      TipNod=(terminal, interior);
      PointerNodArbore2_3=^NodArbore2_3;
      NodArbore2_3=record
          case fel:TipNod of
		    terminal:(element:Tiplement);
 		    interior:(cheie_stang,cheie_drept:real;
                       fiu_unu,fiu_doi,fiu_trei:PointerNodArbore2_3)
		end;

	O multime poate fi definita ca un pointer la radacina arborelui 2-3
care o reprezinta.
  	type Multime=PointerNodArbore2_3;

2.1.Insertia in arborii 2-3

Insertia in arbori 2-3 se realizeaza in principiu ca cea in arbori B. Astfel, insertia unui nod cu cheia x incepe cu cautarea cheii x in arbore. Daca se ajunge la un nod apartinand nivelului situat chiar deasupra nivelului terminal si se constata ca fiii acestuia nu contin pe x, se procedeaza la insertie. Daca nodul are numai doi fii, se face x cel de-al treilea fiu al nodului, plasandu-l la locul potrivit si reajustand structura nodului, astfel incat aceasta sa reflecte noua situatie. Daca nodul considerat are trei fii, atunci adaugarea celui de-al patrulea fiu fiind imposibila, nodul se scindeaza in doua noduri, iar cei patru fii ai sai sunt redistribuiti: cei doi cu chei mai mici fostului nod, iar ceilalti doi fii cu chei mai mari, nodului nou aparut prin scindare. in continuare, nodul nou aparut trebuie inserat in structura de arbore. in urma acestui proces, scindarea se poate propaga spre nivelurile inferioare ale structurii, in cazul extrem, pana la radacina. Aceasta este de fapt singura posibilitate ca un arbore 2-3 sa creasca in inaltime.

Se va prezenta procedura Insertie care se apeleaza pentru radacina si procedura Insertie1 care parcurge arborele intr-o maniera recursiva pentru insertia elementului x. Se presupune ca arborele 2-3 nu este vid, nici cu un singur nod, cazuri care se trateaza simplu, separat.

Procedura Insertie1 returneaza pointerul la nodul nou creat, daca se creaza un astfel de nod, si cheia celui mai mic element al subarborelui determinat de acest nod. Acest lucru este realizat cu ajutorul parametrului Pnou, respectiv Mic, care se atribuie in situatia in care se returneaza un nod nou. Structura de principiu a procedurii Insertie1 este:

procedure Insertie1(Nod:PointerNodArbore2_3; x:TipElement;
	      var Pnou:PointerNodArbore2_3; var Mic:real)
 var Pprim,W:PointerNodArbore2_3; MicPrim:real;
 begin
    Pnou:=nil;
    if *Nod este terminal then
       if *x nu este elementul lui Nod then
           begin
                *creaza un nod nou indicat de Pnou;
                *atribuie pe x noului nod;
                Mic:=x.cheie
           end;
	else
         begin {Nod este un nod interior}
            *fie W acel fiu al lui Nod, caruia ii va  apartine x;
	        Insertie1(W,x,Prim;MicPrim);
	        if Pprim <> nil then
	            begin
	              * insereaza pointerul Pprim printre fiii  lui Nod in
                                         dreapta lui W;
	              if *Nod are patru fii then
	                  begin
	                   *creaza un nod nou indicat de Pnou;
	                       *asociaza acestui nod, fiii trei si patru ai lui Nod;
	                       *actualizeaza valorile lui cheie_stang si
                                                  cheie_drept  in Nod si in noul nod;
	                       *pozitioneaza pe Mic cu cea mai mica cheie
                                                apartinand fiilor noului nod
	                  end
            	  end
	    end
end;
Procedura Insertie, in cazul in care Insertie1 returneaza un nod nou, trebuie sa creeze o noua radacina.
procedure Insertie(x:TipElement; var M:Multime);
var Pprim:PointerNodArbore2_3;
            {pointer la noul nod returnat de Insertie1}
    MicPrim:real; {valoarea Mic a subarborelui determinat de Pprim}
    Temp:Multime; {utilizat pentru memorarea temporara a
                   valorii pointerului M}
begin
    {se verifica daca M este arbore vid sau cu un singur nod
     si se realizeaza insertia in consecinta}
    Insertie1(M,x,Pprim,MicPrim);
    if Pprim<>nil then
       begin {se creaza o noua radacina, fiii radacinii sint
               cei indicati de M, respectiv Pprim}
          Temp:=M;
          new(M);
          with M^ do
            begin
                 fel:=interior;
                 fiu_unu:=Temp;
                 fiu_doi:=Pprim;
                 fiu_trei:=nil;
	         cheie_stang:=Mic_prim
	    end
      end
end;

2.2. Suprimarea in arborii 2-3

Suprimarea in arborii 2-3 se realizeaza intr-o maniera similara cu cea in arborii B.

Astfel, fie n parintele nodului de suprimat si fie p parintele lui n, bunicul nodului de suprimat. Daca n are trei fii, suprimarea se face simplu. Daca in urma suprimarii, n ramane cu un singur fiu (subdepasire), arborele 2-3 trebuie reorganizat. Daca n este chiar nodul radacina, dupa suprimare, singurul lui fiu va deveni noua radacina. Daca p mai are un fiu situat la stanga sau la dreapta lui n si acest fiu are la randul sau trei fii, in cadrul unui proces numit "echilibrare", se va realiza "imprumutul" unui fiu, astfel incat n sa aiba doi fii. Daca insa fiul lui p adiacent lui n are numai doi fii, echilibrarea nu se mai poate realiza prin transfer si in consecinta se transfera singurul fiu al lui n unui frate adiacent si se suprima n. Daca in urma acestui proces, denumit "contopire", p ramane cu un singur fiu, se repeta procedura in mod recursiv, substituind pe n cu p, trecand astfel pe nivelul urmator al structurii arborelui. in caz extrem, procesul acesta se poate propaga pana la radacina, aceasta fiind practic singura cale de reducere a inaltimii arborelui.

Configuratia arborelui 2-3 depinde de ordinea in care se fac insertiile si suprimarile.

Exemplu: in figura 3.a. este prezentat arborele 2-3 continand cheile 1 3 5 7 9 11 13 15. Dupa insertia cheii 8, configuratia arborelui este cea din figura 3.b., iar dupa suprimarea cheii 5 in figura 3.c.