Issuu on Google+

Arbres de cerca Juan Manuel Dodero Beardo P06/05001/00581 Mòdul 7


© FUOC • P06/05001/00581 • Mòdul 7

Arbres de cerca

Índex

Introducció ..............................................................................................

5

Objectius ...................................................................................................

6

1. Els arbres de cerca .............................................................................

7

1.1. Arbres binaris de cerca (ABC) .........................................................

9

1.2. Arbres multicamí de cerca .............................................................. 10 2. Implementació de col·leccions ordenades amb arbres binaris de cerca ........................................................... 12 2.1. Arbres binaris de cerca .................................................................... 12 2.1.1. Cerca en un ABC ................................................................. 13 2.1.2. Inserció en un ABC ............................................................. 14 2.1.3. Esborrament en un ABC ...................................................... 16 2.1.4. Complexitat en les cerques ................................................. 20 2.2. Arbres binaris equilibrats ................................................................ 21 2.2.1. Algorisme d’equilibratge perfecte ....................................... 23 2.2.2. Quan és rendible l’equilibri perfecte ................................... 24 2.2.3. Equilibri en alçària .............................................................. 25 2.3. Arbres AVL ...................................................................................... 25 2.3.1. Arbres de Fibonacci ............................................................. 26 2.3.2. Factor d’equilibri ................................................................. 27 2.3.3. Inserció en un arbre AVL .................................................... 27 2.3.4. Esborrament en un arbre AVL ............................................. 31 2.3.5. Implementació en la biblioteca de TAD ............................. 33 2.4. Implementació de diccionaris amb arbres binaris de cerca ........... 34 2.5. Implementació de conjunts amb arbres binaris de cerca ............... 36 2.6. Exemple d’aplicació: implementació d’un paginador ................... 38 3. Arbres multicamí i arbres B ........................................................... 41 3.1. Estructura d’un arbre B ................................................................... 43 3.2. Inserció en un arbre B ..................................................................... 44 3.3. Esborrament en un arbre B ............................................................. 44 4. Els arbres de cerca a la Java Collections Framework .............. 47 Resum ........................................................................................................ 48 Exercicis d’autoavaluació .................................................................... 49 Solucionari ............................................................................................... 51 Glossari ..................................................................................................... 52


© FUOC • P06/05001/00581 • Mòdul 7

Bibliografia .............................................................................................. 52 Annex ........................................................................................................ 53

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

5

Introducció

En aquest mòdul es presenten els arbres binaris de cerca com a estructures enllaçades que milloren l’eficiència de les cerques en col·leccions ordenades d’elements. També es mostren els arbres multicamí de cerca, que s’utilitzen quan cal emmagatzemar grans col·leccions ordenades d’elements en dispositius de memòria secundària com ara discos.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

6

Objectius

En els materials didàctics corresponents a aquest mòdul es troben els elements indispensables per a assolir els objectius següents: 1. Saber implementar col·leccions ordenades d’elements amb arbres binaris de cerca. 2. Comprendre la utilitat dels arbres binaris de cerca per a millorar l’eficiència en les cerques. 3. Conèixer els arbres multicamí i comprendre’n la utilitat per a millorar l’eficiència de l’emmagatzemament de col·leccions ordenades d’elements en dispositius de memòria secundària.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

7

Arbres de cerca

1. Els arbres de cerca

En mòduls anteriors s’han tractat algunes estructures de dades que serveixen per a millorar l’eficiència de les operacions de cerca en una col·lecció d’elements. Per exemple, les implementacions del TAD Diccionari basades en taules de dispersió permeten assolir un cost asimptòtic molt bo en les operacions de

Vegeu el TAD Diccionari en el mòdul “El TAD Taula” d’aquesta assignatura.

cerca sobre elements individuals. Això no obstant, quan es tracta de llistar tots els elements d’una taula seguint un ordre determinat, el comportament de les taules de dispersió no és tan bo com seria desitjable. Per això es plantegen estructures de dades alternatives que, mantenint una complexitat acceptable per a les cerques individuals, millorin l’eficiència de les operacions de llistat sobre una part considerable dels elements de la col·lecció. En aquest mòdul, s’estudiaran els arbres de cerca com una solució alternativa a aquests problemes i a d’altres. Els arbres de cerca són una variant dels arbres ja estudiats. Els arbres són estructures de dades que reflecteixen relacions jeràrquiques entre els seus elements. Així com les llistes són lineals, se sol parlar dels arbres com estructures multidimensionals, ja que el nombre de relacions jeràrquiques entre cada element i els seus descendents en l’arbre es poden veure com les diferents dimensions d’una taula. De fet, qualsevol arbre es pot expressar en forma de taula multidimensional, encara que en forma de taula sigui més difícil de dibuixar. Una col·lecció estructurada de dades que es pot representar fàcilment en forma d’arbre és el conjunt de creuaments d’una competició esportiva per eliminatòries, com la figura 1. Aquesta estructura també es pot representar en forma de taula, encara que en aquest cas és més complicat de dibuixar i resulta menys clar. Figura 1. Conjunt d’eliminatòries d’una competició esportiva representades en forma d’arbre

L’aprofitament dels arbres com a estructures de dades jeràrquiques s’estudia en el mòdul “Arbres” d’aquesta assignatura.


© FUOC • P06/05001/00581 • Mòdul 7

8

Una segona utilitat dels arbres és la de guardar col·leccions lineals d’elements ordenats. Aquest segon propòsit és el motiu de la nova classe d’arbres que s’estudiaran en aquest mòdul. En aquest cas es parla d’arbres de cerca, justament perquè la seva comesa és millorar l’eficiència de les operacions de cerca que es pot assolir amb estructures lineals enllaçades.

Un arbre de cerca és un arbre normal que té els elements ordenats segons un o més criteris, els quals constitueixen la clau d’ordenació.

L’ordre dels elements emmagatzemats en els nodes d’un arbre de cerca es defineix de la manera següent: • Tots els elements situats per sota i a l’esquerra d’un altre han de tenir una clau d’ordenació més petita que aquest. • Tots els elements situats per sota i a la dreta d’un altre han de tenir una clau d’ordenació més gran que aquest. Les relacions jeràrquiques entre els elements de l’arbre s’aprofiten per a representar l’ordre dels elements, tal com il·lustra l’exemple de la figura 2, que reflecteix un arbre binari de cerca que utilitza nombres enters com a clau d’ordenació. En l’arbre esmentat, tots els nodes situats per sota i a l’esquerra del node 6 són nombres més petits (nodes 2, 4 i 5); tots els nodes situats per sota i a la dreta del node 6 són nombres més grans (nodes 7, 9 i 11); aquesta mateixa condició es pot comprovar per als nodes 4, 7 i 11. L’arbre està ordenat i, per tant, és un arbre de cerca. Figura 2. Exemple d’arbre binari de cerca

Així, és fàcil saber quins són més petits i quins són més grans a partir d’un element qualsevol, i es pot restringir considerablement l’espai de cerca sobre una

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

9

Arbres de cerca

col·lecció d’elements ordenats, de manera semblant a com fa la cerca binària o cerca dicotòmica.

Els arbres de cerca serveixen per a guardar estructures lineals d’elements ordenats i millorar la complexitat de les cerques. Les cerques en un arbre es realitzaran comparant la clau buscada amb les claus dels elements emmagatzemats en cada node, començant des de l’arrel i baixant per l’arbre. Depenent de si la clau buscada és més gran o més petita que la trobada en cada node, es tria la branca de l’arbre que s’utilitza per a baixar en la cerca.

Complexitat asimptòtica La millora en l’eficiència que es produeix en els arbres de cerca no sempre es tradueix en una millora de la complexitat asimptòtica. Per a assegurar això, l’arbre ha de tenir una certa forma equilibrada que s’estudiarà més endavant en aquest mòdul.

Hi ha molts tipus d’arbres de cerca, però, en general, es poden classificar en dos grans grups segons el grau de l’arbre: els arbres binaris de cerca i els arbres multicamí de cerca.

En la resta del mòdul s’estudiaran aquests dos tipus d’arbres de cerca separadament, i l’equilibri en arbres binaris de cerca, un problema fonamental que justifica la utilitat dels arbres com a estructures eficients per a fer cerques.

1.1. Arbres binaris de cerca (ABC)

En un arbre binari de cerca (ABC), cada node té dos fills com a màxim (és a dir, el seu grau és dos) i, a més, es compleix el següent: 1) El subarbre esquerre de qualsevol node només pot contenir nodes amb claus més petites, o bé estarà buit. 2) El subarbre dret de qualsevol node només pot contenir nodes amb

Sobre repetició de claus En principi, els ABC no estan pensats per a emmagatzemar elements repetits. Tanmateix, es poden definir variants d’un ABC per a manejar claus repetides. Un mètode típic consisteix a associar a cada node una llista de col·lisions que guardi els elements amb clau repetida.

claus més grans, o bé estarà buit.

Els elements d’un ABC estan ordenats d’acord amb una clau d’ordenació, que pot ser qualsevol atribut de l’element. La clau d’ordenació pot estar constituïda per un o més atributs, com ja sabem. En un arbre binari de cerca, en principi, no hi pot haver elements de clau repetida, llevat que s’ampliï l’estructura de l’arbre amb estructures addicionals que donin suport a aquesta circumstància.

L’exemple de la figura 3 mostra la diferència entre un arbre binari normal i un altre de cerca. L’arbre de la figura 3a no és un arbre de cerca perquè, entre altres raons, el node 7 té clau més gran que el node 2, però està situat a la seva esquerra. A més a mes, el 6 és més petit que el 7 i està situat a la seva dreta, de manera que no compleix les condicions d’ordenació.

Vegeu clau d’ordenació en el mòdul “El TAD Taula” d’aquesta assignatura.


© FUOC • P06/05001/00581 • Mòdul 7

10

Arbres de cerca

Figura 3. Exemples d’arbre binari i d’arbre binari de cerca

D’altra banda, l’arbre de la figura 3b sí que és un ABC, perquè tots els nodes tenen per sota i a la seva esquerra elements més petits que ells, i per sota i a la seva dreta elements més grans. Per a comprovar-ho, simplement cal generar el recorregut en inordre de l’arbre: • Recorregut en inordre de l’arbre 3a: {2, 7, 5, 6, 11, 2, 5, 4, 9}

Arbres binari i binari de cerca Com que els elements que formen l’arbre a no estan ordenats, aquest arbre no és de cerca. Com que els elements que formen l’arbre b estan ordenats, aquest arbre és de cerca.

• Recorregut en inordre de l’arbre 3b:{2, 4, 5, 6, 7, 9, 11} Com a resultat de recórrer un ABC en inordre s’obté una col·lecció ordenada dels elements de l’arbre.

1.2. Arbres multicamí de cerca Els tipus de recorregut d’un arbre es van estudiar en el mòdul “Arbres”.

Els arbres multicamí de cerca són arbres de cerca amb grau superior a 2. Cada un dels seus nodes serveix per a emmagatzemar un conjunt ordenat de claus, en comptes d’una de sola, com passa amb els arbres binaris. De cada node, surten diverses branques i formen subarbres.

Aquests subarbres tenen les claus ordenades com s’indica en la figura 4. Figura 4. Criteri d’ordenació dels nodes d’un arbre multicamí de cerca


© FUOC • P06/05001/00581 • Mòdul 7

11

En la figura 4, es mostra un node qualsevol d’un arbre multicamí amb cabuda per a 5 claus, amb nom des de k1 fins a k5. Els nodes del seu primer subarbre per l’esquerra guardaran només claus més petites que k1, els del segon fill per l’esquerra guardaran claus situades entre k1 i k2, i així successivament fins a arribar al fill de l’extrem dret, que guarda les claus més grans que k5.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

12

Arbres de cerca

2. Implementació de col·leccions ordenades amb arbres binaris de cerca

Els arbres binaris de cerca serveixen per a emmagatzemar col·leccions ordenades d’elements i millorar la complexitat de l’operació de cerca en una llista ordenada. Per tant, per a estudiar la utilitat dels arbres binaris de cerca, se’ls

Una altra possibilitat dels arbres binaris de cerca és proporcionar una implementació addicional per als diccionaris estudiats en el mòdul “El TAD Taula” d’aquesta assignatura.

ha d’utilitzar com a implementació d’algun TAD que representi una col·lecció ordenada d’elements (per exemple, una llista ordenada per una clau determinada). En aquest apartat, s’estudiaran els arbres binaris de cerca com a contenidors de qualsevol col·lecció d’elements ordenats per una clau determinada. En aquest cas, no hi ha diferència entre la implementació d’una llista ordenada i la d’un diccionari, excepte pel tipus de dades de la clau. Per tant, en la resta de l’apartat es parlarà de col·leccions ordenades, independentment del seu tipus concret.

2.1. Arbres binaris de cerca Suposem que es vol guardar la col·lecció ordenada de nombres enters següents {2, 6, 7, 9, 10, 12} en forma de l’arbre de la figura 5. Figura 5. Un exemple d’arbre de cerca amb complexitat propera a O(log n)

Quin és el cost de buscar un element qualsevol en aquest arbre? Depèn de la mida màxima del camí de cerca. El camí de cerca és la seqüència de nodes recorreguts durant una cerca en un ABC. La cerca començarà comparant l’element buscat amb l’arrel i descendint cap a les fulles. Després de cada comparació, progressem per la branca de l’esquerra o per la de la dreta, però mai per totes dues alhora. La cerca s’aturarà quan trobem l’element, o bé si arribem a una fulla i l’element buscat no és a l’arbre.

Intuïtivament, es pot comprovar que com a màxim caldrà fer 3 operacions de comparació (és a dir, la mida màxima del camí de cerca) per a trobar qualsevol

La complexitat de les cerques La complexitat de les cerques en col·leccions ordenades acostuma variar entre O(log n) i O(n). S’aconsegueix una complexitat O(log n) quan la implementació de la col·lecció permet realitzar cerques binàries, com és el cas dels vectors. Tanmateix, en les implementacions de llistes enllaçades, la millor complexitat que es pot assolir és de O(n). Els ABC intenten millorar aquesta complexitat.


© FUOC • P06/05001/00581 • Mòdul 7

13

element en l’arbre de l’exemple. Atès que un arbre binari d’aquesta alçada pot contenir com a màxim n = 7 nodes, la complexitat serà aproximadament log n.

Tanmateix, aquest cost no s’assoleix en totes les situacions, sinó que depèn de la “forma” de l’arbre. Per exemple, l’arbre de la figura 6 conté els mateixos elements que el de la figura 5, però fer una cerca pot necessitar fins a 5 comparacions (és a dir, la mida màxima del camí de cerca). En aquest cas, la complexitat és més propera a O(n) que a O(log n).

Figura 6. Un exemple d’arbre de cerca, amb els mateixos elements de la figura 5, però amb una complexitat més gran, propera a O(n)

En resum, per a garantir que els ABC assoleixin la millora desitjada en la complexitat de les cerques, cal exigir a l’arbre certa forma “equilibrada”. En un arbre de n nodes que tingui una forma equilibrada semblant a la de l’arbre de la figura 5, la complexitat de les cerques serà aproximadament O(log n).

A continuació, es descriuran les operacions del TAD ArbreBinariCerca més importants: la cerca, la inserció i l’eliminació. Finalment, es farà una breu disquisició sobre la complexitat de les cerques en un ABC.

2.1.1. Cerca en un ABC La cerca en un ABC consisteix a comparar l’element buscat amb els nodes de l’arbre, començant per l’arrel i descendint cap a les fulles. Després de cada comparació, s’avança per la branca esquerra o per la dreta, però mai per totes dues. La cerca s’aturarà quan es trobi l’element, o bé quan s’arribi a una fulla sense haver-lo trobat (l’element buscat no és a l’arbre).

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

14

Arbres de cerca

L’algorisme de cerca de l’element buscat en l’arbre que té l’arrel en posicio es pot descriure de la manera següent: 1) Mentre la posició no sigui nul·la: a) Comparar l’element en Posicio i l’element buscat. • Si tots dos són iguals, l’element en Posicio és l’element buscat i la cerca s’atura. • Si l’element en Posicio és més petit, s’ha d’actualitzar Posicio perquè apunti al seu fill de la dreta. • Si l’element Posicio és més gran, actualitzar Posicio perquè apunti al seu fill de l’esquerra. b) Continuar la cerca. 2) Si l’algorisme ha acabat perquè Posicio és nul·la, l’element no es troba en l’arbre i la cerca ha estat infructuosa. La cerca progressa per un dels dos costats de la posició de partida, però no per tots dos. En cas que l’element buscat sigui més petit que l’arrel de l’esmentada posició, sabem que no estarà en el subarbre dret, així que podem descartar tots els seus elements i només hem de buscar en l’esquerre; passa el mateix amb el subarbre esquerre si l’element buscat és més gran que l’arrel. L’algorisme de l’operació de cerca, abstret de l’operació consultar disponible en la implementació del TAD ArbreBinariCercaEncadenat, és el següent: uoc.ei.tads.ArbreBinariCercaEncadenat

Elem consultar(Posicio pare, Elem buscat) { boolean trobat = false; int comp; while ( pare!=null && !trobat ) { comp = comparar(buscat, pare.getElem()); trobat = comp==0; if (comp <0) pare = fillEsquerre(pare); else if (comp >0) pare = fillDret(pare); } return trobat ? pare.getElem() : null; }

2.1.2. Inserció en un ABC La inserció ha de garantir que l’arbre resultant continuï essent un ABC, és a dir, que els seus nodes continuïn ordenats. Per a això, la inserció comença bus-

Cerca en un ABC De manera similar a com es feia en la cerca binària o dicotòmica sobre un vector ordenat, en la cerca en un ABC també som capaços de descartar una part dels elements per delimitar l’espai de cerca. La diferència rau en el fet que en la cerca binària s’usen índexs sencers que delimiten l’espai de cerca en el vector i, en canvi, en un ABC s’accedeix als fills esquerre i dret per descartar els subarbres on és segur que no es troba l’element.


© FUOC • P06/05001/00581 • Mòdul 7

15

Arbres de cerca

cant la posició en què s’ha d’inserir el nou element. Si la posició en què s’ha d’inserir ja està ocupada per un altre element amb la mateixa clau, aquest serà substituït. En canvi, si la posició en què cal inserir està buida, l’element s’hi afegirà com una fulla nova. Primer cal buscar la posició en què s’ha d’inserir el nou element. Si l’arbre està buit, l’element es col·loca en la seva arrel. Si no està buit, es baixa per l’arbre “visitant” els seus fills i comparant-los amb el nou element, i es decideix per a cada node visitat en quin lloc cal inserir. Hi ha tres casos: • Si el nou element és igual al del node visitat, se substitueix el contingut del node pel nou element i la inserció acaba. • Si el nou element és més petit que el del node visitat, es baixa per l’esquerra. • En canvi, si el nou element és més gran, es baixa per la seva dreta. La cerca s’atura si s’arriba al primer cas o si s’assoleix un node buit que serà just la posició en què cal inserir el nou element. Una vegada trobada la posició, simplement s’insereix l’element com un node nou i l’algorisme s’acaba.

Repetició d’elements Tal com ja hem comentat, els ABC no estan dissenyats per a emmagatzemar elements repetits. Per això, en cas d’igualtat de l’element per inserir amb algun element de l’arbre, s’opta per substituir aquest darrer. Tanmateix, és possible canviar aquest comportament i fer que el nou element s’acumuli en una llista de col·lisions associada al node, i així poder emmagatzemar les repeticions.

L’algorisme de l’operació d’inserció, abstret de l’operació inserir disponible en la implementació del TAD ArbreBinariCercaEncadenat, és el següent:

Posicio<E> inserir (Posició<E> pare, E elem) { if (pare == null) return super.afegir(pare, elem); Posicio<E> node = null; int comp = comparar(elem, pare.getElem()); if (comp == 0) { super.substituir(pare, elem); node = pare; } else if (comp <0) { if (fillEsquerre(pare) == null) node = super.inserirFillDret(pare, elem); else node = inserir(fillEsquerre(pare), elem); } else { if (fillDret(pare) == null) node = super.inserirFillDret(pare, elem); else node = inserir(fillDret(pare), elem); } equilibrar(pare); return node; }


© FUOC • P06/05001/00581 • Mòdul 7

16

En realitat, la inserció no representa més que una operació de cerca seguida de la substitució d’un node, o bé la creació del nou node i la integració amb l’arbre.

De moment, no ens hem preocupat de l’equilibri dels ABC. Tanmateix, observeu quan cal equilibrar l’arbre després de cada inserció: en la crida a l’operació equilibrar(pare). L’operació d’inserció pot provocar l’aparició d’un nou node en l’arbre. Si es produeixen moltes insercions i totes van a parar a la mateixa zona de l’arbre, és possible que creixin més unes branques que altres i l’arbre s’acabi desequilibrant. Recordeu que és desitjable mantenir l’arbre equilibrat perquè les cerques es mantinguin amb una complexitat més propera a la logarítmica que a la lineal. Més endavant estudiarem les operacions d’equilibratge, que seran necessàries després d’inserir o d’esborrar elements d’un ABC.

Exemple En l’exemple de la figura 7, s’insereix l’element de clau 16 en l’arbre de l’esquerra per a obtenir l’arbre de la dreta. El procés es realitza de forma similar a la cerca. Com que l’element 16 no es troba en l’arbre, s’insereix com a fill esquerre de l’últim element visitat, que és el 18, perquè és més petit. Figura 7. Exemple d’inserció en un ABC

2.1.3. Supressió en un ABC

La supressió d’un element en un ABC consisteix a buscar la posició del pare del node ocupat per l’element esmentat, i a continuació eliminar del pare la referència al node trobat. Aquesta manera de procedir té una excepció: quan el node que s’esborra és l’arrel, a més cal actualitzar la referència a aquesta en l’arbre.

Per a descriure més detalladament l’operació de supressió, es dividirà en dues fases. En la primera fase, es buscarà el node de l’element que s’esborra; i en la

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

17

segona, s’eliminarà o es substituirà el node trobat. Durant aquestes dues fases, cal tenir en compte algunes qüestions:

a) La supressió no sempre s’acaba amb una simple eliminació del node contenidor de l’element trobat, perquè pot passar que el node esmentat ocupi una posició intermèdia en l’arbre, i la seva simple eliminació també eliminaria tots els nodes que en pengen.

b) La supressió d’un node, a qui realment afecta és al pare del node esmentat, ja que una vegada trobat el node que s’elimina, cal actualitzar la referència que el seu pare en té.

c) Quan s’actualitza l’esmentada referència del pare del node esborrat, cal conèixer si el node esborrat és el seu fill esquerre o el dret, ja que el pare té dues referències i cal saber quina s’ha d’actualitzar. Això és fàcil d’esbrinar si es té en compte que l’arbre és de cerca i, per tant, els seus elements han de ser ordenats. Llavors, només farà falta comparar l’element esborrat amb la clau del pare, i se sabrà si aquest ocupava el node a la seva esquerra (la clau de l’element és més petit que la del pare) o a la seva dreta (la clau és més gran).

1) Fase de cerca

L’algorisme de supressió d’un element comença realitzant una operació de cerca de l’element en l’arbre. Si l’arbre està buit, no és possible esborrar cap node i, en conseqüència, l’operació de supressió finalitza. Si l’arbre no està buit, cal baixar per l’arbre visitant cada node per una de les dues branques. S’avança per la branca esquerra o la dreta segons resulti de comparar l’element que s’esborra amb la clau de l’arrel. Per a això, hi ha tres casos:

a) Si l’element que s’esborra és igual al del node visitat, s’atura la cerca i es guarda la posició del pare del node esmentat. b) Si l’element que s’esborra és més petit que el del node visitat, es baixa per la seva esquerra. c) En canvi, si l’element que s’esborra és més gran, es baixa per la seva dreta. La cerca s’atura si s’assoleix el primer cas, o bé s’arriba a un node buit en algun punt del descens per l’arbre. 2) Fase d’eliminació Si en la cerca de la fase anterior s’assoleix un node buit, és que l’element que es vol esborrar no es troba en l’arbre, i la supressió acaba infructuosament. En

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

18

canvi, si la cerca s’atura en una posició no nul·la, aleshores és necessari esborrar el node que ocupa aquella posició no nul·la. Això afectarà el pare del node, i es poden produir tres casos, que esmentem a continuació de menys a més complexitat:

a) Que el node no tingui cap fill (és a dir, és una fulla): llavors simplement s’elimina la referència al node que guarda el pare.

b) Que el node només tingui un fill (és totalmente indiferent que sigui el node esquerre o el node dret, però no tots dos alhora): llavors s’actualitza la referència en el pare perquè aquest guardi la del “nét”, i se salti el fill que es vol eliminar.

c) Que el node tingui dos fills no buits. Aquest és el cas més complicat, ja que no es pot escollir simplement un dels dos fills com a substitut, com en el cas anterior (pot ser que el substitut tingui també dos fills i no tindríem lloc per a col·locar-los). En aquest cas, cal buscar el substitut ideal. Si es pensa que l’ABC guarda una col·lecció ordenada d’elements, hi ha dos substituts possibles: l’element immediatament anterior o el posterior en l’ordre esmentat. En conseqüència, en aquest cas la supressió ha d’avançar cap a la subfase que consisteix a localitzar l’esmentat substitut amb qui reemplaçar el node esborrat.

3) Subfase de cerca de substitut

La cerca de substitut té dues possibilitats:

a) Buscar l’element immediatament anterior, que ocuparà la posició de l’element més gran del subarbre esquerre.

b) Buscar l’element immediatament posterior, que ocuparà la posició de l’element més petit del subarbre dret.

Qualsevol de les dues possibilitats és vàlida, però només se’n pot triar una. Per exemple, si s’opta per la primera opció, cal buscar l’element més gran del subarbre esquerre. Aquesta cerca consisteix simplement a baixar per la branca dreta de tots els descendents, fins que es trobi un node sense fill dret. Una vegada trobat, el node esmentat serà el substitut, i només faltarà que reemplaci el node que es vol esborrar.

L’algorisme de l’operació de supressió, abstret de l’operació esborrar disponible en la implementació del TAD ArbreBinariCercaEncadenat, és el que es mostra a continuació:

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

19

Arbres de cerca

E esborrar ( Posicio<E> on, Posicio<E> pare, E elem ) {

E elemEsborrat=null; if (pare == null) return null; int comp = comparar(elem, pare.getElem()); if (comp <0) elemEsborrat = esborrar(pare, fillEsquerre(pare), elem); else if (comp >0) elemEsborrat = esborrar(pare, fillDret(pare), elem); else { elemEsborrat = pare.getElem(); if (esFulla(pare)) super.esborrar(on, pare); else if ((fillEsquerre(pare)!=null) && (fillDret(pare)==null) ) substituir(on, pare, fillEsquerre(pare)); else if ((fillEsquerre(pare)==null) && (fillDret(pare)!=null) ) substituir(on, pare, fillDret(pare)); else { Posicio<E> mesPetit = fillDret(pare); while (fillEsquerre(mesPetit)!=null) mesPetit=fillEsquerre(mesPetit); intercanviar(pare, mesPetit); esborrar(pare, fillDret(pare), mesPetit.getElem()); } } equilibrar(on); return elemEsborrat; }

Com amb l’operació d’inserció, la supressió pot provocar un desequilibri en l’arbre que caldrà arreglar. Per a això, s’ha situat oportunament la crida a equilibrar una vegada acabada la tasca d’esborrar. Aquest mètode es defineix en subclasses d’ArbreBinariCercaEncadenat, on s’afegeix al comportament d’arbre de cerca el d’arbre equilibrat.

Exemple En l’arbre de la figura 8a s’ha esborrat l’element 12, que constituïa un node fulla, i s’ha obtingut l’arbre de la figura 8b; posteriorment, sobre l’arbre de la figura 8b s’ha eliminat l’element 10 (que a més era l’arrel). En aquest cas hi ha dues possibilitats: substituir-lo pel 8 (figura 8c) o substituir-lo pel 13 (figura 8d). En tots dos casos resultarien ABC diferents, però igualment vàlids.

Vegeu l’equilibratge d’arbres en el subapartat 2.2 d’aquest mòdul didàctic.


© FUOC • P06/05001/00581 • Mòdul 7

20

Arbres de cerca

Figura 8. Exemples de supressions en un ABC

2.1.4. Complexitat en les cerques Aquest mòdul ha començat afirmant que els ABC serveixen per a guardar col·leccions ordenades d’elements, però millorant l’eficiència de les cerques. Això no sempre es tradueix en una millora de la complexitat asimptòtica de les operacions de cerca. A continuació, se’n veurà el perquè. En mesurar la complexitat asimptòtica d’una cerca en un ABC que conté n nodes, es pot observar que, en general, no fa falta recórrer-los tots. En cada node que es recorre, cal decidir si es progressa per la branca esquerra o per la dreta, i així es descarten molts dels nodes. Com més nodes es descartin, més eficient serà la cerca. Llavors, la pregunta és: quants nodes es descarten? Estudiem-ne dos casos separadament: 1) En el millor dels casos, en cada comparació es descartarà la meitat dels nodes que queden per processar. En aquest cas, l’arbre tindria una forma equili-

Recordeu que per a la mesura de la complexitat asimptòtica es té en compte el pitjor dels casos (vegeu el mòdul “Complexitat algorísmica”) d’aquesta assignatura.


© FUOC • P06/05001/00581 • Mòdul 7

21

brada similar a la de la figura 5. La complexitat seria de O(log n) perquè només seria necessari fer tantes operacions de comparació com nombre de nivells tingués l’arbre. 2) El pitjor dels casos es produeix quan cal recórrer tots els elements de l’arbre, és a dir, quan ha degenerat en una cosa semblant a una llista, com és el cas de la figura 6. En aquest cas, la complexitat seria O(n), és a dir, no es millora gens respecte a la cerca en una llista enllaçada normal. Observeu que un arbre pot arribar a tenir una forma més semblant al millor o al pitjor dels casos segons l’ordre en què s’insereixin els seus elements, seguint l’algorisme vist en l’apartat anterior. Per exemple, si sobre un arbre buit arriba

Arbres de cerca

Complexitat del millor i del pitjor dels casos Si h és l’alçària d’un ABC i n és el nombre de nodes que conté, es pot demostrar que n = 2h +1 −1, és a dir h = log2(n + 1) − 1. Per tant, el millor dels casos (sense tenir en compte la posició del node cercat) seria quan h = log2 n. I per això la complexitat en el millor dels casos és O(log n). En canvi, el pitjor dels casos es produirà per al valor més gran que h pot prendre, és a dir, h = n, i s’obtindrà una complexitat de O(n).

una seqüència d’insercions en l’ordre {9, 6, 2, 10, 7, 12}, s’obté l’arbre de la figura 5. En canvi, si arriba la seqüència {10, 12, 9, 7, 6, 2}, s’obté l’arbre de la figura 6. Atesa l’aleatorietat amb què poden arribar els elements que s’insereixen en l’arbre, no és possible assegurar amb certesa si tindrem una situació semblant a un extrem o a l’altre. Per aquest motiu, cal pensar en el pitjor dels casos. Per tant, per a assegurar que la complexitat asimptòtica de les cerques és O(log n), cal procurar que l’estructura de l’ABC s’assembli tant com sigui possible a la de la figura 5, és a dir, mantenir l’arbre equilibrat després de cada operació d’inserció o de supressió. En el subapartat següent es parlarà amb més detall de la qüestió de l’equilibratge en ABC, que afecta especialment la implementació de les operacions inserir i esborrar vistes anteriorment.

2.2. Arbres binaris equilibrats

Un arbre binari està equilibrat quan, en termes generals, tots els seus nodes estan equitativament repartits al llarg de les branques. Gràficament, un arbre equilibrat manté una certa simetria en la mida de totes les branques, per a qualsevol alçada de l’arbre. Dit d’una altra manera, les longituds de les branques esquerra i dreta de qualsevol node no són gaire diferents.

L’equilibri en un ABC és una qualitat desitjable perquè la complexitat asimptòtica de les cerques es vegi realment millorada. Mantenir un arbre sempre equilibrat és una tasca costosa, ja que cal assegurar-se que continua estant equilibrat després de cada inserció i de cada supressió. Estudi de Wirth En l’obra Algoritmos + estructuras de datos = Programas (1976), N. Wirth va demostrar analíticament que la longitud mitjana del camí de cerca en un ABC aleatori és 1,386 vegades la del camí de cerca en un de perfectament equilibrat.

Recordeu que la mesura de la complexitat que proposa la notació O(·) es pren en el cas pitjor.


© FUOC • P06/05001/00581 • Mòdul 7

22

Dit d’una altra manera, la cerca en un ABC aleatori exigeix una mitjana de 39% més de comparacions que en un ABC perfectament equilibrat. La millora pot ser fins i tot més gran en el cas més desfavorable en què un ABC sense equilibrar degenera en una llista.

A l’hora d’equilibrar un arbre, hi ha diverses possibilitats. El millor cas és l’anomenat equilibri perfecte. Però també hi ha altres nivells d’equilibri que, sense ser perfectes, proporcionen una eficiència acceptable i són menys costosos d’implementar. A continuació, es descriuran dos d’aquests tipus d’equilibri: l’equilibri perfecte i l’equilibri en alçària.

Es diu que un ABC està perfectament equilibrat si, per a cada node, el nombre de nodes dels seus subarbres esquerre i dret difereix com a màxim en una unitat.

La figura 9 mostra alguns exemples d’arbres perfectament equilibrats i arbres sense equilibri perfecte.

Figura 9. Exemples d’arbres binaris equilibrats i sense equilibrar

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

23

2.2.1. Algorisme d’equilibratge perfecte

L’algorisme per a equilibrar un arbre binari consisteix a desplaçar la meitat dels nodes que sobren d’un costat cap a l’altre costat d’un node. Cal aplicar l’operació a tots els subarbres, començant per l’arrel i seguint cap a les fulles.

Figura 10. Exemple del procés d’equilibratge perfecte d’un arbre no equilibrat

Per a descriure l’operació equilibrar, ens basarem en unes altres tres operacions auxiliars definides sobre els nodes de l’arbre: • NodeArbre.desplaçarDreta(int quants): desplaça tants nodes com indiqui el valor de quants cap al subarbre dret del node. • NodeArbre.desplaçarEsquerra(int quants): desplaça tants nodes com indiqui el valor de quants cap al subarbre esquerre del node.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

24

• NodeArbre.equilibrar(): equilibra un node, és a dir, fa tants desplaçaments de nodes a esquerra i/o dreta com siguin necessaris perquè quedi equilibrat el subarbre que penja del node. En primer lloc, es veurà mitjançant un exemple en què consisteix l’operació d’equilibratge, i després la passarem a descriure més detalladament. En la figura 10, es mostra un exemple de com s’aconsegueix l’equilibri perfecte d’un arbre. Inicialment, l’arbre no està equilibrat perquè la distribució de les claus filles de l’arrel està descompensada (n’hi ha 5 a l’esquerra i 1 a la dreta). El procés comença intentant resoldre aquest primer desequilibri, per la qual cosa cal traspassar dues o tres claus a la dreta. Només se’n traslladen dues perquè és menys costós; aquestes seran les claus 10 i 12, perquè són les més grans per l’esquerra. La clau 10 quedarà com a nova arrel. A continuació, cal aplicar recursivament el criteri d’equilibri perfecte als subarbres fills de l’arbre resultant, prenent com a bases les claus 6 i 20, respectivament. El procés acaba quan es desplacen la clau 5 (la més gran de les més petites que 6) en el subarbre de l’esquerra i la clau 15 (la més gran de les més petites que 20) en el subarbre de la dreta. En cada desplaçament d’un node cap al subarbre dret, s’ha traslladat el node de clau més gran del subarbre esquerre, i aquest ha passat a ocupar l’arrel del nou subarbre. Alhora, l’antiga arrel del subarbre es col·loca al lloc que li correspon segons la seva clau en el subarbre dret. La col·locació de l’arrel en la seva nova ubicació es realitza de forma semblant a l’operació normal d’inserció d’un node en un ABC. Encara que en l’exemple anterior només han fet falta desplaçaments a la dreta per a equilibrar l’arbre, també es poden produir cap a l’esquerra. En aquest cas, l’operació és simètrica: en cada desplaçament a l’esquerra es traslladarà el node més petit del subarbre dret, i aquest ocuparà l’arrel del nou subarbre i farà baixar l’antiga arrel per l’esquerra.

2.2.2. Quan és rendible l’equilibri perfecte L’equilibri perfecte és una condició bastant costosa d’exigir, ja que encara que millora l’eficiència en les cerques, empitjora les actualitzacions. Cal aplicar l’algorisme d’equilibratge cada vegada que s’insereix o s’elimina algun node en l’arbre. I l’algorisme d’equilibratge ni és trivial ni té complexitat constant. Per tant, la millora que suposa mantenir un arbre equilibrat no es considera prou bona excepte si es dóna algun dels casos següents: • Quan el cas més desfavorable es presenta amb assiduïtat. Per exemple, quan els nodes que es volen inserir en l’arbre arriben en seqüències ordenades d’una certa longitud. Això provocaria que l’arbre quedés sovint molt degenerat. • Quan el nombre de cerques és molt gran en relació amb el nombre d’actualitzacions. En aquest cas, el cost d’equilibratge addicional imposat per les insercions i supressions no és tan alt perquè aquestes operacions són poc freqüents.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

25

2.2.3. Equilibri en alçària L’equilibri perfecte és una condició bastant estricta que cal exigir a un ABC per a aconseguir una complexitat en les cerques de O(log n). Quan el nombre d’actualitzacions és molt gran en comparació amb les cerques, caldrà executar l’algorisme d’equilibratge perfecte moltes vegades. En aquestes situacions, la complexitat de O(log n) que té cada inserció o supressió empitjora, ja que s’hi ha d’afegir la complexitat de l’operació d’equilibratge, que és O(n). Tanmateix, hi ha condicions més laxes d’equilibri que es poden exigir a un ABC per a aconseguir la millora pretesa d’eficiència en les cerques, però sense empitjorar gaire per això les operacions d’actualització. Un d’aquests casos és l’anomenat equilibri en alçària.

Un arbre està equilibrat en alçària quan, per a cada un dels seus nodes, les alçàries dels seus subarbres esquerre i dret difereixen com a màxim en una unitat.

Exemple L’arbre de la figura 11 està equilibrat en alçària, ja que la diferència d’alçàries entre les branques esquerra i dreta no supera la unitat en cap subarbre. Figura 11. Arbre equilibrat en alçària

Quan un arbre està equilibrat en alçària, la longitud del camí de cerca és més petit fins i tot en el cas pitjor, perquè la condició d’equilibri imposa certes restriccions al creixement desordenat de l’arbre i evita fer-lo degenerar en una llista.

2.3. Arbres AVL Els arbres AVL són un tipus particular d’arbres binaris de cerca equilibrats en alçària. La definició d’equilibri en alçària es deu a G. M. Adelson-Velskii i E. M. Landis, en honor dels quals aquests arbres reben el nom d’arbres AVL.

Arbres de cerca


26

© FUOC • P06/05001/00581 • Mòdul 7

Arbres de cerca

Un arbre AVL és un arbre binari de cerca en el qual les alçàries dels subarbres de qualsevol node difereixen com a màxim en una unitat.

L’avantatge principal que s’obté dels arbres AVL és que les operacions d’equi-

Observació Fixeu-vos que la diferència màxima entre els subarbres esquerre i dret en un AVL és en alçàries, no en nombre de nodes, que pot ser més gran.

libratge són més senzilles i, sobretot, eficients. La mateixa estructura dels arbres AVL fa que la longitud mitjana del camí de cerca sigui similar a la dels arbres perfectament equilibrats. La comprovació de per què la complexitat de la cerca en un AVL és inferior a O(n) es farà sobre el tipus d’arbre AVL més desequilibrat que es pot definir: els anomenats arbres de Fibonacci.

2.3.1. Arbres de Fibonacci L’arbre de Fibonacci és un tipus d’arbre AVL, el nom del qual prové de la manera com es construeixen, similar als coneguts nombres de Fibonacci. La definició d’arbre de Fibonacci és la següent:

a

1) L’arbre buit és un arbre de Fibonacci d’alçària 0 i es denota per A0.

Arbres de Fibonacci Els arbres de Fibonacci són els AVL més desequilibrats que hi ha perquè, exceptuant les fulles, tots els altres nodes presenten un desequilibri exacte d’una unitat (bé a l’esquerra o bé a la dreta).

2) Un únic node és un arbre de Fibonacci d’alçària 1 i es denota per A1. 3) Si Ah-1 i Ah-2 són arbres de Fibonacci d’alçàries h − 1 i h − 2, respectivament, llavors l’arbre Ah resultant d’annexar Ah-1 i Ah-2 com a fills esquerre i dret d’un node arrel és arbre de Fibonacci d’alçària h. 4) No hi ha més arbres de Fibonacci que els construïts com indiquen les regles anteriors. La figura 12 mostra els arbres de Fibonacci de diverses alçàries, des de 0 fins a 4. Fixeu-vos que l’arbre d’alçària h es construeix annexant els dos arbres d’alçària h −1 i h − 2 sota una arrel comuna. Això impedeix que l’arbre pugui créixer gaire més per qualsevol branca esquerra que per la dreta: la diferència d’alçària entre elles sempre serà com a màxim una unitat. La complexitat de les cerques en un arbre AVL dependrà de l’alçària màxima que pugui assolir, és a dir, O(h), en què h és l’alçària de l’arbre. En els arbres de Fibonacci, es pot demostrar (Rosen, 2004, cap. 3) que el nombre de nodes n i l’alçària de l’arbre estan relacionats mitjançant la inequació següent: n ≥ 2h+1 − 1 Prenent logaritmes en tots dos costats de la desigualtat: log n ≥ h +1

Figura 12. Arbres de Fibonacci de diverses alçàries


© FUOC • P06/05001/00581 • Mòdul 7

27

Arbres de cerca

Per tant, la complexitat de les cerques en un arbre de Fibonacci serà O(log n). Com que els arbres de Fibonacci són els AVL més desequilibrats que existeixen, la classe general dels arbres AVL no farà sinó millorar aquesta complexitat.

2.3.2. Factor d’equilibri L’únic element afegit a l’estructura de dades d’un arbre AVL respecte a altres ABC és la mesura del factor d’equilibri. Tots els nodes d’un arbre AVL tenen un camp sencer que mesura el factor d’equilibri. El factor d’equilibri d’un node és la diferència entre les alçàries dels subarbres dret i esquerre: Fe(n) = h(fillEsquerre(n)) − h(fillDret(n))

El factor d’equilibri de qualsevol node d’un AVL és un valor sencer comprès entre −1 i +1. Quan el valor del factor d’equilibri se surti d’aquest rang, caldrà realitzar alguna operació addicional d’equilibratge. L’avantatge dels AVL rau en el fet que el cost d’aquesta operació d’equilibratge és constant i no depèn de la mida de l’arbre. El factor d’equilibri d’un node s’ha d’actualitzar amb cada inserció o supressió que es faci en algun dels seus subarbres. Això es realitzarà en punts concrets dels algorismes d’inserció i supressió, com es veurà més endavant.

2.3.3. Inserció en un arbre AVL L’operació d’inserció en un arbre AVL comença amb el mateix algorisme que en els ABC regulars. Quan es decideix el lloc en què es produeix la inserció, cal actualitzar el factor d’equilibri del pare que l’acull i comprovar si el seu factor d’equilibri se surt dels límits permissibles: • Si se’n surt, es realitza una operació de rotació que torni a equilibrar l’arbre i l’operació acaba. • Si no se’n surt, es propaga la variació del factor d’equilibri cap a dalt en la jerarquia de l’arbre. Aquesta propagació acaba quan es detecta i s’arregla un desequilibri, o bé quan s’assoleix l’arrel de l’arbre. Sens dubte, si l’arbre estava buit i el node inserit es converteix en arrel, també cal actualitzar la referència corresponent a l’arrel de l’arbre.

La inserció en un arbre AVL té dues fases, que es veuran seguidament per separat: l’actualització del factor d’equilibri dels nodes afectats i les rotacions (si s’escau) per a resoldre desequilibris provocats en la fase anterior.

Mesura del factor d’equilibri Per conveni, el factor d’equilibri es defineix com la diferència entre les alçàries dels subarbres dret i esquerre, encara que es podria haver elegit la mesura contrària. Si el factor d’equilibri d’un node val −1, el subarbre esquerre serà un nivell més gran que el dret; si val +1, el subarbre dret serà un nivell més alt; i si val 0, tots dos subarbres tindran la mateixa alçària.


© FUOC • P06/05001/00581 • Mòdul 7

28

Arbres de cerca

1) Actualització del factor d’equilibri Quan el factor d’equilibri d’un node se surt dels límits, estarem davant d’un dels casos següents, en què caldrà procedir d’una manera diferent: a) Inserció en un node amb Fe = 0. En aquest cas s’insereix el nou node, s’actualitza l’Fe del pare i es propaga la variació cap als seus ascendents en l’arbre. Figura 13. Exemple d’inserció en un node amb Fe = 0

b) Inserció en el subarbre més petit d’un node amb Fe ≠ 0. En aquest cas s’insereix el nou node i s’actualitza l’Fe del pare. Figura 14. Exemple d’inserció en el subarbre més petit d’un node amb Fe ≠ 0

c) Inserció en el subarbre més gran d’un node amb Fe ≠ 0. En aquest cas s’insereix el nou node, s’actualitza l’Fe del pare i es propaga la variació cap als ascendents, fins que es detecta un node amb un factor d’equilibri que se surt dels límits permesos. Figura 15. Exemple d’inserció en el subarbre més gran d’un node amb Fe ≠ 0

En l’exemple de la figura 15, el resultat d’aquesta primera fase d’actualització no és un arbre AVL. Això és així perquè l’operació encara no ha acabat (falta realitzar la fase de rotacions que veurem a continuació). Una vegada detectat un desequilibri (cas 3), cal realitzar una operació de rotació sobre els nodes de l’arbre.

Inserció per la dreta En els tres casos anteriors, s’ha representat la inserció d’un fill per l’esquerra. La inserció per la dreta és anàloga.


© FUOC • P06/05001/00581 • Mòdul 7

29

Arbres de cerca

Rotacions en un arbre AVL Quan calgui realitzar alguna rotació, el node en què hàgim detectat el desequilibri, l’anomenarem node pivot. El node pivot és l’antecedent més proper del node inserit que tingui Fe ≠ 0. Es poden aplicar quatre tipus de rotacions. El nom de les rotacions ve donat pel camí inicial a seguir per a arribar des del pivot fins al nou node inserit (E = esquerra; D = dreta), segons es baixa en l’arbre. La figura 16 representa gràficament aquestes rotacions. Figura 16. Tipus de rotacions necessàries per a equilibrar l’arbre

Les línies discontínues horitzontals representen els nivells de profunditat de l’arbre; els cercles són nodes individuals i els quadrats són subarbres (poden representar més d’un node); el node ombrejat representa el lloc de la inserció del nou node.

Els nodes afectats per una rotació simple són el pivot (P) i l’arrel del més gran subarbre fill (F) del pivot. En una rotació doble es veuen afectats aquests mateixos nodes i, a més, l’arrel del subarbre nét més gran (N) del node F. Tant en rotacions simples com en dobles també es veu afectat el pare del pivot, que no s’ha representat en la figura 16 per a una major claredat. La figura 17 mostra exemples d’insercions en un arbre AVL, en què es produeixen rotacions simples E-E (figura 17) i D-D i rotacions dobles E-D i D-E.


© FUOC • P06/05001/00581 • Mòdul 7

30

Figura 17. Exemples d’insercions amb rotació

Llegenda de la figura 17 S’ha marcat el valor del Fe = ±1 en els nodes en què es produeixen desequilibris, just abans de l’operació (inserció) que ho provoca. Es podria pensar que aquest factor assoliria valors de ±2 després de cada inserció. Realment, el valor que assoleixi el factor d’equilibri d’aquests nodes és una manera interna de detectar el desequilibri, però l’operació d’inserció més la rotació corresponent s’han d’executar com un tot indivisible, sense deixar que l’Fe assoleixi valors de ±2.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

31

2.3.4. Supressió en un arbre AVL

La supressió en un arbre AVL comença per la cerca del node que es vol esborrar. Com en els ABC, si es troba en un node fulla, s’elimina. Si el node és intermedi, cal substituir-lo pel més petit del subarbre esquerre (o el més gran del dret).

Una vegada eliminat el node, cal recalcular el factor d’equilibri del pare del node esborrat o mogut. Si no s’ha provocat un desequilibri, només cal ajustar alguns factors d’equilibri. En canvi, si s’ha produit un desequilibri, també caldrà reequilibrar l’arbre mitjançant les rotacions introduïdes per al cas de la inserció.

L’eliminació d’un node d’un AVL pot provocar un desequilibri més greu que la inserció. En el cas de la inserció, una simple rotació pot arreglar el desequilibri. En la supressió, en canvi, pot ser necessari realitzar més d’una rotació. Per a detectar-ho, cal analitzar els nodes ascendents, seguint el camí que va des del node esborrat fins a l’arrel de l’arbre. En l’anàlisi de cada un d’aquests nodes es poden distingir els casos següents:

1) El node analitzat té Fe = 0. Llavors s’ajusta el factor d’equilibri a +1 o –1, segons de quina branca s’hagi esborrat el node, i no és necessari seguir l’anàlisi. La figura 18 mostra la supressió del node 85 en l’arbre de l’esquerra. En primer lloc, s’elimina el node com si es tractés d’un ABC normal. Una vegada desenganxat el node 85 del seu pare, s’analitzen els nodes ascendents de baix a dalt, començant pel 84. Com que aquest té Fe = 0 (cas 1), simplement s’ajusta el seu nou valor Fe = –1 i no fa falta seguir l’anàlisi.

Figura 18. Exemple d’esborrament en un AVL

2) El node analitzat té Fe ≠ 0 i el node esborrat pertanyia al subarbre més gran. En aquest cas, s’ajusta el factor d’equilibri a zero i cal continuar analitzant els antecessors. Per exemple, en la figura 19, si s’esborra el node 75, en el camí d’anàlisi i actualització dels factors d’equilibri primers es troba el node 45, amb el seu Fe no nul. Una vegada ajustat i posat a Fe = 0, és necessari seguir l’anàlisi i actualització d’Fe, perquè l’escurçament que s’ha produït en la branca més llarga del node 45 pot influir en l’Fe de nodes situats més amunt en l’arbre, com passa amb el 40 i el 36, els Fe dels quals cal actualitzar: Fe(40) = 0 i Fe(36) = –1.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

32

Figura 19. Exemple d’esborrament en un AVL (cas 2)

3) El node analitzat té Fe ≠ 0 i el node esborrat pertanyia al subarbre més petit. Ara cal aplicar una rotació i ajustar el factor d’equilibri, com s’indica a continuació:

a) L’arrel del subarbre més gran té Fe = 0, es realitza una rotació simple (que dependrà del Fe del node analitzat: si Fe > 0, la rotació serà D-D; i si Fe < 0, la rotació serà E-E). Després s’ajusta el factor d’equilibri del node analitzat i es deté l’anàlisi. En l’arbre de la figura 20, s’ha esborrat el node 38. Com que el factor d’equilibri del node analitzat és Fe(40) = +1, s’ha esborrat per la branca més curta, i com que l’arrel de la branca més llarga té Fe(48) = 0, cal aplicar el cas 3a. La rotació aplicada és la D-D, perquè el Fe del node analitzat és positiu.

Figura 20. Exemple d’esborrament en un AVL (cas 3a)

b) El node analitzat i l’arrel del subarbre més gran tenen el mateix Fe, es realitza una rotació simple, s’ajusta el factor d’equilibri d’aquest últim, però l’anàlisi prossegueix cap als antecessors del node analitzat (si els Fe són positius, la rotació serà D-D, i si són negatius, la rotació serà E-E). Per exemple, sobre l’arbre de la figura 21 s’esborrarà el node 38. Llavors s’analitza el node 40, que té un Fe no nul. A més, s’ha esborrat per la branca més curta, i la branca més llarga és Fe(45) = Fe(40) = +1. Per tant, cal aplicar el cas 3b i realitzar una rotació D-D simple. Una diferència notable respecte al cas 3a és que ara l’anàlisi no es deté, sinó que la variació del factor d’equilibri es continua propagant cap amunt en

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

33

l’arbre. Aquest és el motiu pel qual el Fe del node 36 finalment valdrà –1, després de l’aplicació del cas 1 en la propagació. Figura 21. Exemple d’esborrament en un AVL (cas 3b)

c) El node analitzat i l’arrel del subarbre més gran tenen Fe de diferent valor, es realitza una rotació doble (E-D o D-E, depenent dels factors d’equilibri de tots dos), s’ajusten els seus factors d’equilibri i s’analitzen els antecessors. En l’arbre de l’esquerra de la figura 22 s’esborra el node de clau 6. L’anàlisi comença pel node 12. Tant en el node 12 com el 3 es reprodueix el cas 2. Per això, després d’actualitzar els seus Fe, cal continuar analitzant cap amunt. El següent que s’analitza és el node 13 (amb un Fe, +1). Si considerem que el Fe del node 40 és –1, ens trobem en el cas 3c. Per tant, cal realitzar una rotació doble D-E, i el resultat és l’arbre de la dreta de la figura. Figura 22. Exemple d’esborrament en un arbre AVL (cas 3c)

Observeu que, en tots els casos, el criteri per a decidir si cal continuar analitzant els nodes del recorregut fins a l’arrel és que es modifiqui l’alçària del subarbre que surt del node analitzat. Si l’alçària no es modifica, no s’ha de continuar analitzant els antecessors restants.

2.3.5. Implementació en la biblioteca de TAD Els AVL no proposen un TAD nou respecte als arbres binaris de cerca, ja que no amplien el conjunt d’operacions de maneig que aquests últims permeten. Un arbre AVL és una implementació particular d’arbre binari de cerca equili-

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

34

Arbres de cerca

brat, que simplifica la manera de mantenir l’equilibri després de cada operació d’inserció i supressió. Els arbres AVL de la biblioteca de TAD estan representats per la classe ArbreAVL, que hereta la mateixa interfície de la classe ArbreBinariCercaEncadenat, i redefineix només la implementació de certes operacions de la superclasse: en particular, la inserció i la supressió. Afortunadament, la classe ArbreBinariCercaEncadenat s’ha dissenyat tenint en compte aquest factor, per la qual cosa s’ha concentrat en el mètode protegit equilibrar() tota la implementació d’operacions d’equilibri. D’aquesta manera, la classe ArbreBinariCercaEncadenat delega en la seva subclasse

Patró de disseny Aquesta manera en què la superclasse delega l’operació d’equilibri cap a les seves subclasses és un patró de disseny molt comú, conegut com a mètode plantilla (en anglès, template method) (Gamma i altres, 2002).

ArbreAVL perquè proporcioni la implementació adequada al mètode equilibrar. D’altra banda, la classe ArbreAVL proporciona, a més, una implementació d’un arbre AVL en què altres classes contenidores (per exemple, Diccionari i Conjunt) deleguin la seva implementació basada en arbres AVL. En el cas dels diccionaris, això es fa per delegació, amb la classe DiccionariAVLImpl. Per a implementar el TAD Conjunt la classe ConjuntAVLImpl proporciona el comportament dels conjunts i delega en ArbreAVL la gestió de l’arbre.

2.4. Implementació de diccionaris amb arbres binaris de cerca A continuació es veurà com s’usa un arbre binari de cerca AVL per a allotjar la col·lecció d’elements del TAD Diccionari. El TAD Diccionari es pot implementar amb diversos tipus de contenidors. En destaquen els arbres binaris de cerca i les taules de dispersió. A continuació, es descriu una implementació del TAD Diccionari basada en un arbre AVL, que després compararem amb la implementació basada en taules de dispersió. col·lecció DiccionariAVLImpl < C,E> implementa Diccionari<C,E> és • constructor() O(1) – Crea una instància de l’arbre AVL que implementa el diccionari. • void nombreElems() O(1) – Torna el nombre d’elements de l’arbre. • boolean estaBuit() O(1) – Comprova si el nombre d’elements és zero. • void inserir(<C> clau, <E> elem) O(log n) – Insereix en l’arbre un nou node format per la parella de clau i element. • <E> consultar (<C> clau>) O(log n) – Busca la clau en l’arbre. – Si troba la clau, torna l’element associat a la clau en el mateix node. – Si no troba la clau, torna ‘nul’. • <E> esborrar (<C> clau) O(log n) – Busca la clau en l’arbre.

El TAD Diccionari es va estudiar conjuntament amb les taules de dispersió en el mòdul “El TAD Taula” d’aquesta assignatura.


© FUOC • P06/05001/00581 • Mòdul 7

35

– Si troba la clau, esborra de l’arbre el node que la conté, i torna l’element associat. – Si la clau no és a l’arbre, torna ‘null’. • boolean hiEs(<C> clau) O(log n) – Comprova si la clau és a l’arbre mitjançant una crida a consultar. • iterador<C> claus () O(n) – Torna un iterador per recórrer en inordre totes les claus guardades en l’arbre. • iterador<E> elements () O(n) – Torna un iterador per recórrer en inordre tots els elements guardats en l’arbre. Magnituds del cost: • n: Nombre d’elements de l’arbre De la classe DiccionariAVLImpl de la biblioteca de TAD, destaca el codi següent, que mostra com s’implementa el diccionari basant-se en la delegació sobre un arbre AVL: uoc.ei.tads.DiccionariAVLImpl public class DiccionariAVLImpl<C,E> implements Diccionari<C,E> { protected ArbreAVL<ClauValor<C,E>> avl; public DiccionariAVLImpl() { avl = new ArbreAVL<ClauValor<C,E>>(); } public int nombreElems() { return avl.nombreElems(); } public boolean estaBuit() { return avl.estaBuit(); } public void inserir(C clau, E elem) { avl.inserir( new ClauValor<C,E>(clau, elem)); } public boolean hiEsta(C clau) { return avl.consultar(clau) != null; } public E consultar(C clau) { E result=null; ClauValor<C,E> aux = new ClauValor<C,E>(clau,null); ClauValor<C,E> clauValor = avl.consultar(aux); if (clauValor!=null) result=clauValor.getValor(); return result; }

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

36

Arbres de cerca

public E esborrar(C clau) { E result=null; ClauValor<C,E> aux=new ClauValor<C,E>(clave,null); ClauValor<C,E> clauValor = avl.esborrar(aux); if (clauValor!=null) result=clauValor.getValor(); return result; } }

Com es pot veure, la implementació de Diccionari delega la implementació de la col·lecció ordenada d’elements en l’atribut privat anomenat avl i el tipus ArbreAVL. La delegació consisteix en el fet que les peticions que arriben al diccionari i que tenen a veure amb l’emmagatzemament dels seus elements es

La tècnica de delegació s’explica amb més detall en l’assignatura Programació orientada a objectes.

transformen en crides a mètodes de l’objecte delegat, és a dir, l’atribut avl. Això es pot veure en el fet que gairebé tots els mètodes de DiccionariAVLImpl s’implementen com a crides a mètodes d’ArbreAVL. Per exemple, els mètodes inserir, consultar i esborrar de DiccionariAVLImpl només han de fer una crida al mètode del mateix nom de l’ArbreAVL delegat. En la biblioteca de TAD, la classe DiccionariAVLImpl proporciona una implementació alternativa a TaulaDispersió. En triar una d’aquestes dues implementacions, cal tenir en compte l’ús més freqüent que es donarà al diccionari. Per exemple, l’elecció no serà la mateixa si és bastant comuna la necessitat de recórrer tots els elements dins d’un rang de claus, o si simplement es necessita el diccionari per a fer cerques esporàdiques de claus. El comportament de les taules de dispersió no és tan bo com seria desitjable per a l’operació de recórrer tots els elements d’un rang de claus. Tanmateix, en un arbre de cerca, totes les claus del rang mantenen una proximitat de referències en l’arbre que el fan especialment valuós per a aquest tipus d’operacions.

2.5. Implementació de conjunts amb arbres binaris de cerca Una altra possible aplicació dels arbres AVL consisteix a dotar d’implementació la interfície Conjunt. Els conjunts abstractes estudiats fins al moment responen al TAD ConjuntAbstracte, la implementació del qual a la biblioteca de TAD la proporciona la classe ConjuntAbstracte. Aquesta classe confia en una sèrie de mètodes abstractes que rep de la interfície Conjunt, i per als quals no proporciona cap implementació. En particular, aquests mètodes són: • void inserir(E elem) • boolean hiEs(E elem) • E esborrar(E elem)

Aquestes raons són les mateixes que s’esgrimeixen per a la solució de l’exemple d’aplicació del subapartat 2.6. d’aquest mòdul didàctic.


37

© FUOC • P06/05001/00581 • Mòdul 7

Arbres de cerca

La classe ConjuntAVLImpl proporciona la implementació final desitjada per a aquests mètodes, basant-se en un arbre AVL privat que guarda ordenats els elements del conjunt. Encara que la interfície de TAD Conjunt que presenta aquest cas no permet l’accés a les característiques d’ordenació que l’arbre de cerca permetria, les cerques es realitzaran internament d’una manera més òptima. A continuació s’especifica la classe ConjuntAVLImpl i es detallen les parts més destacades de la implementació esmentada. col·lecció ConjuntAVLImpl <E> estén ConjuntAbstracte<E> és • constructor() O(1) – Crea una instància de l’arbre AVL que implementa el conjunt. • void nombreElems() O(1) – Torna el nombre d’elements. • boolean estaBuit() O(1) – Comprova si el nombre d’elements és zero. • void inserir(<E> elem) O(log n) – Insereix en l’arbre un nou node per a l’element. • <E> esborrar (<E> elem) O(log n) – Esborra i torna l’element esborrat de l’arbre. • boolean hiEs(<E> Elem) O(log n) – Comprova si l’element és a l’arbre. • iterador<E> elements() O(n) – Torna un iterador per recórrer en inordre tots els elements de l’arbre. Magnituds del cost: • n: Nombre d’elements de l’arbre uoc.ei.tads.ConjuntAVLImpl public class ConjuntAVLImpl<E> extends ConjuntAbstracte<E> { protected ArbreAVL<E> avl; public ConjuntAVLImpl() { avl = new ArbreAVL<E>(); } public int nombreElems() { return avl.nombreElems(); } public boolean estaBuit() { return avl.estaBuit(); }

Adaptador La manera en què ArbreAVL proporciona la implementació d’un conjunt, adaptant la seva pròpia interfície (que queda oculta) a la de ConjuntAbstracte, és un patró de disseny conegut com a Adaptador (adapter) (Gamma i altres, 2002).


© FUOC • P06/05001/00581 • Mòdul 7

38

public void inserir(E elem) { avl.inserir(elem); } public boolean hiEs(E elem) { return avl.consultar(elem) != null; } public E esborrar(E elem) { return avl.esborrar(elem); } }

2.6. Exemple d’aplicació: implementació d’un paginador En un gran nombre d’aplicacions web es mostren llistats molt llargs de registres que s’agrupen en trossos de mida fixa, de manera que aquests trossos càpiguen en la pantalla i s’hi pugui navegar amb més comoditat mitjançant una sèrie de controls com els que es mostren en la figura 26. Figura 23. Exemple d’un paginador de registres procedents d’una base de dades de pel·lícules

No es mostra tota la llista d’un cop, sinó només un tros. El llistat s’acompanya de controls per a avançar i retrocedir entre trossos.

La manera de desplegar les dades en pantalla “a trossos” es coneix com un paginador. Sovint, un paginador es pot veure com un TAD Diccionari<C,E> que ordena els registres segons una clau determinada. Per exemple, les pel·lícules de la figura 23 podrien estar ordenades per un codi de pel·lícula; la clau C és el codi i l’element E serà el registre amb totes les dades de la pel·lícula. Fixeu-vos que, en aquest exemple, no se solen fer cerques esporàdiques per clau en el diccionari que conté totes les pel·lícules. Més aviat, se solen fer recorreguts en blocs de k elements, tants com càpiguen en la pantalla. En aquest cas, una implementació basada en arbres AVL serà més convenient que aquelles basades en taules de dispersió.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

39

Quines operacions necessitarem per a implementar el paginador de l’exemple? Sens dubte, només tindrem en compte allò que tingui a veure amb la gestió de les dades necessària per al recorregut per tota la col·lecció, i ens oblidarem de qüestions relatives a la interfície d’usuari: • Una operació constructora del paginador. • Una operació per a inserir elements en el paginador. • Una operació paginar(inicial,final) que obté el rang d’elements del diccionari comprès entre el parell de claus inicial i final. L’operació per a paginar tornarà la col·lecció d’elements del diccionari compresos entre dues claus. Això és semblant al que fan els mètodes per al recorregut d’un TAD, que en la biblioteca de TAD estan implementats com a iteradors. Per tant, l’operació paginar la implementarem de manera similar al mètode elements() del TAD Diccionari. La diferència és que aquest últim torna un iterador que recorre tots els elements del diccionari, i el mètode paginar(inicial,final) tornarà un iterador que actua només sobre els elements compresos entre la clau inicial i la clau final. A continuació, s’ofereix part d’una possible implementació del paginador. La interfície Paginador és implementada per la classe PaginadorImpl, que al seu torn delega en DiccionariEstes la implementació del recorregut parcial sobre els elements del rang. uoc.ei.exemples.modul7.Paginador ... public interface Paginador<C,E> { void inserir(C clau,E element); Iterador<E> paginar(C clauInicial, C clauFinal); }

uoc.ei.ejemplos.modulo7.PaginadorImpl ... public class PaginadorImpl<C,E> implements Paginador<C,E> { private DiccionariEstes<C,E> diccionariPagines; public PaginadorImpl() { diccionariPagines = new DiccionariEstes<C,E>(); } public Iterador<E> paginar(C clauInicial,C clauFinal) { return diccionariPagines.elements(clauInicial,clauFinal); } }

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

40

Arbres de cerca

uoc.ei.exemples.modul7.DiccionariEstes ... public class DiccionariEstes<C,E> extends DiccionariAVLImpl<C,E> { public Iterador<E> elements(C clauInicial,C clauFinal) { Recorregut<ClauValor<C,E>> rang= new RecorregutRang<C,E>(avl.recorregutInordre(),clauInicial,clauFinal); return new IteradorRecorregutValorsImpl<C,E>(rang); } }

uoc.ei.exemples.modul7.RecorregutRang ... public class RecorregutRang<C,E> implements Recorregut<ClauValor<C,E>> { private Recorregut<ClauValor<C,E>> recorregutGlobal; private C clauInicial,clauFinal; private Posicio<ClauValor<C,E>> seguent; public RecorregutRang(Recorregut<ClauValor<C,E>> r,C clauInicial,C clauFinal) { this.recorregutGlobal=r; this.clauInicial=clauInicial; this.clauFinal=clauFinal; seguent=null; } public boolean hihaSeguent() { if (seguent!=null) return true; while (recorregutGlobal.hihaSeguent() && seguent==null) { Posicio<ClauValor<C,E>>aux=recorregutGlobal.seguent(); if (esClauValida(aux.getElem().getClau())) seguent=aux; } return Seguent!=null; } public Posicio<ClauValor<C,E>> seguent()throws ExcepcioPosicioInvalida { Posicio<ClauValor<C,E>> aux=seguent; seguent=null; return aux; } protected boolean esClauValida(C clau) { return ((Comparable<C>)clauInicial).compareTo(clau)<=0 && ((Comparable<C>)clau).compareTo(clauFinal)<=0; } }


© FUOC • P06/05001/00581 • Mòdul 7

41

3. Arbres multicamí i arbres B

Un arbre de cerca serveix per a guardar col·leccions ordenades d’elements. Aquesta capacitat dels arbres de cerca també té la seva utilitat en aplicacions amb exigències d’emmagatzemament massiu, com les bases de dades o els sistemes de fitxers. En aquests casos, els arbres solen ser tan extremadament grans que no caben complets en la memòria principal, per la qual cosa s’emmagatzemen en disc o en algun altre dispositiu de memòria secundària. L’accés a disc és considerablement més lent que l’accés a la memòria principal. Per aquest motiu, és molt convenient reduir el nombre d’accessos a disc a l’hora de buscar un element.

Les bases de dades i els sistemes de fitxers solen emprar índexs per a accedir més ràpidament als elements emmagatzemats. Els índexs se solen construir emprant com a clau un subconjunt d’atributs de la col·lecció d’elements (per exemple, els noms dels fitxers, o les claus de les taules d’una base de dades). Els arbres de cerca multicamí estan pensats per a construir aquests índexs de manera que s’aprofitin els avantatges dels arbres de cerca alhora que es minimitza el nombre d’accessos al disc.

En un arbre de cerca multicamí, els nodes s’emmagatzemen en memòria secundària o disc. Aquests nodes contenen tant valors de l’índex com els propis elements d’informació indexada. Si ens despreocupéssim de la ubicació dels nodes en el disc, cada accés a un node exigiria un accés a disc, que és una operació relativament costosa.

Els discos estan organitzats en blocs d’una certa mida (normalment 2k per a valors de k > 9) el contingut total dels quals és llegit amb un sol accés a disc. Tanmateix, per a emmagatzemar quantitats d’informació de mida més gran, es necessitaran diversos blocs, que no han de ser necessàriament contigus i, per tant, exigiran més d’un accés al disc. L’avantatge dels arbres multicamí no és reduir el nombre d’operacions, sinó reduir el nombre d’accessos a disc, de la manera següent:

• En cada node de l’arbre s’agrupa un conjunt d’elements de dades. • Aquestes dades se solen llegir seqüencialment, si bé no en la seva totalitat, sí en subseqüències ordenades d’una mida determinada. • Per a aprofitar la forma seqüencial dels accessos, les dades s’emmagatzemen contigües al disc. Per tant, amb un únic accés al disc tindrem en memòria tots els elements d’un node.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

42

• Quan més endavant s’hagi d’accedir a l’element següent de la seqüència, si aquest era en el mateix node de l’arbre, estarà en memòria, per la qual cosa ja no serà necessari cap altre accés a disc. Com a conseqüència, encara que el nombre d’operacions en un arbre multicamí serà semblant al d’un ABC, el nombre d’accessos a disc serà molt més petit. Exemple Suposem que es vol emmagatzemar una col·lecció ordenada de 220 elements en un arbre binari de cerca situat en un disc organitzat en blocs amb cabuda per a 100 nodes. Encara que l’arbre estigui equilibrat, a causa de la seva mida, en el pitjor dels casos es necessitarien 20 accessos a disc per a recuperar un element (el pitjor dels casos es dóna quan l’element se situa en el nivell més profund de la jerarquia i cap parell de nodes dels recorreguts per a arribar-hi no comparteixen un mateix bloc del disc). Això és perquè amb un ABC, el nombre d’accessos a disc és igual que el nombre d’elements consultats, és a dir, 20 en el pitjor dels casos. Si aconseguim agrupar els nodes de l’ABC en blocs o pàgines de nodes, com mostra la figura 24, el nombre d’accessos a disc serà de log100 210 ∼ 1,5. Això vol dir que, amb un màxim de dos accessos a disc, podem assolir qualsevol node. Figura 24. Un arbre binari de cerca organitzat en blocs

El tipus d’arbre de cerca multicamí més comú és l’anomenat arbre B. Un arbre B pot guardar en els seus nodes més d’un element, i cada node pot tenir més de dos fills. A més, per definició, són arbres equilibrats en alçària, i això queda assegurat per la forma en què s’actualitzen.

Aquestes propietats permeten que l’arbre emmagatzemi grans quantitats d’informació sense créixer gaire en alçària. Com que el nombre màxim d’accessos a disc depèn de l’alçària de l’arbre, els accessos a memòria secundària seran molt eficients i l’equilibri de l’arbre no podrà degenerar creixent per alguna branca més que per d’altres.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

43

Un arbre B d’ordre n és aquell que té les característiques següents:

Arbres de cerca

a

1) Cada node (excepte l’arrel) té com a mínim n/2 elements. 2) Cada node té com a màxim n elements. 3) Si un node té m elements, o bé és una fulla, o bé té m + 1 fills.

L’arrel d’un arbre B L’arrel d’un arbre B ha de poder guardar un nombre d’elements per sota del mínim permès, ja que inicialment o bé l’arbre és buit, o bé no hi ha prou nodes, no tan sols per a fer-lo créixer, sinó tampoc per a omplir l’arrel.

4) Els elements d’un mateix node estan ordenats linealment. 5) Els nodes de l’arbre estan ordenats com un arbre de cerca normal (és a dir, els que guarden elements més petits a l’esquerra i els més grans a la dreta). 6) Totes les fulles es troben en el mateix nivell en l’arbre. La figura 25 presenta un arbre B d’ordre 4 en què s’observen les característiques de la definició.

Ordre d’un arbre B Alguns autors defineixen l’ordre d’un arbre B com el nombre màxim d’elements d’un node. D’altres el defineixen com la meitat de la capacitat màxima d’elements d’un node. Nosaltres utilitzem la primera definició.

Figura 25. Arbre B d’ordre 4

Les operacions bàsiques sobre un arbre B són la inserció i la supressió d’elements. El fet de ser un arbre multicamí redueix considerablement l’espai de cerca respecte a un ABC equivalent (és a dir, aquell que contingui els mateixos elements), ja que això fa que disminueixi la profunditat màxima assequible. A

Arbres B d’ordre parell Per a arbres B d’ordre parell, s’assegura una ocupació mínima en cada node (excepte l’arrel) del 50%.

més, l’estructura de l’arbre i la manera en què es realitzen la inserció i la supressió asseguren que l’arbre està sempre equilibrat en alçària. Tanmateix, el nombre d’accessos en un arbre B és similar al d’un ABC equivalent, ja que, si bé es visiten menys nodes, per a cada node es fan diverses comparacions (en el pitjor dels casos, amb tots els elements agrupats en el node).

3.1. Estructura d’un arbre B L’estructura de dades bàsica d’un arbre B d’ordre n és la d’un node ocupat per m claus, reproduïda gràficament en la seqüència ordenada de la figura 26. En l’estructura esmentada es compleix que n/2 ≤ m ≤ n.

Sentit dels arbres B Els arbres B només tenen sentit per a ordres grans (és a dir, una mida suficient dels blocs de disc). Tanmateix, en els exemples presentats a continuació s’empraran ordres més petits per motius de claredat.


© FUOC • P06/05001/00581 • Mòdul 7

44

Figura 26. Estructura d’un node d’un arbre B

En la figura 26, ∀i = 1, ..., m es compleix que ki < ki+1, i pi és una referència a un node amb claus més grans que ki i més petites que ki+1. Aquesta seqüència correspon a un TAD Llista ordenada, encara que la implementació més comuna és amb una matriu (array) estàtica, ja que l’ordre de l’arbre no sol canviar.

3.2. Inserció en un arbre B

La inserció comença amb la col·locació de la clau en el lloc que li correspon, baixant per l’arbre i comparant amb les claus de cada node. Una vegada trobat el node que li toca, si hi ha lloc, simplement s’insereix en la col·lecció ordenada de claus. En canvi, si s’excedeix el nombre de claus permeses, cal dividir el node en dos com indica la figura 27, i promocionar cap al node pare la clau central (la que ocupa la mitjana de la seqüència de claus del node juntament amb la nova clau).

Figura 27. Esquema de la inserció en un arbre B

La clau que puja cap al pare pot donar lloc a una nova divisió, i aquesta situació es pot repetir fins a arribar a l’arrel. Per això se sol dir que els arbres B creixen per l’arrel, al contrari que els ABC, que ho fan per les fulles.

3.3. Supressió en un arbre B En esborrar una clau es poden donar els casos següents: 1) La clau és en un node fulla. Llavors, cal desplaçar a l’esquerra la resta de les claus del node.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

45

2) La clau no està en un node fulla. Llavors hi ha dues alternatives: a) Substituir la clau per l’element més gran del node apuntat per la referència a l’anterior (vegeu la figura 28 per a un exemple d’aquesta alternativa). b) Substituir la clau per l’element més petit del node apuntat per la referència al següent. Figura 28. Exemple de supresssió d’una clau en un arbre B

Pot passar que, en esborrar un node, el node corresponent quedi subocupat. En aquesta circumstància, cal procedir com en el cas 3. 3) Si el node en què s’esborra l’element queda desocupat i no és l’arrel, caldrà reorganitzar l’arbre. Llavors hi ha dues possibilitats: a) Equilibratge: s’afegeixen elements d’un dels dos nodes veïns (esquerre o dret) que tingui prou claus per sobre del mínim, de manera que es redistribueixin de manera equitativa els elements de tots dos nodes, tal com indica la figura 29. Primer considerem l’esquerre i, si no té prou claus, el dret (vegeu com a exemple la figura 30). Figura 29. Esquema de l’equilibratge de nodes en esborrar una clau en un arbre B

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

46

Figura 30. Exemple d’equilibratge en esborrar en un arbre B

b) Fusió: si no es pot equilibrar ni amb l’esquerre ni amb el dret perquè cap dels nodes veïns té elements de sobra per a deixar, llavors s’unirà amb un dels nodes veïns (considerant primer l’esquerre i, si no, el dret), i afegirà la clau que els enllaça en el node pare (vegeu un exemple en la figura 32). Figura 31. Esquema de la fusió de nodes en esborrar una clau en un arbre B

Figura 32. Exemple de fusió de nodes en esborrar en un arbre B

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

47

Arbres de cerca

4. Els arbres de cerca a la Java Collections Framework

La implementació que la Java Collections Framework (JCF) proporciona per als arbres de cerca no és directament visible i pública. La decisió dels dissenyadors de la JCF va ser oferir només les interfícies “útils” dels TAD que aquests arbres implementen: Diccionari i Conjunt. Els esmentats TAD apareixen implementats en classes com la TreeSet i la TreeMap: • TreeMap és una implementació del TAD Diccionari, que internament guarda els seus elements ordenats en un arbre binari de cerca. • TreeSet és una implementació del TAD Conjunt, basada en una instància privada de TreeMap que allotja els elements del conjunt. D’altra banda, la classe TreeSet implementa la interfície SortedSet, que assegura que els elements del conjunt seran recorreguts de forma ordenada. Els arbres binaris de cerca usats per la JCF són una varietat coneguda com a arbres vermell-i-negres. Aquesta implementació proporciona una complexitat de O(log n) per a les operacions de cerca (containsKey i get), inserció (put) i supressió (remove).

Web recomanat Es pot trobar una prova de l’eficiència d’aquests arbres en la Wikipedia: http://en.wikipedia.org/wiki/ Red-black_tree


© FUOC • P06/05001/00581 • Mòdul 7

48

Resum

En aquest mòdul s’han estudiat els arbres binaris de cerca i diverses implementacions que asseguren que les cerques en col·leccions enllaçades d’elements ordenats es poden fer de forma eficient O(log n). Per això s’ha vist el concepte d’equilibri i dues de les seves variants: l’equilibri perfecte i l’equilibri en alçària. Com a exemple d’arbres equilibrats en alçària s’han estudiat els arbres AVL i les seves operacions d’inserció i supressió. També s’han estudiat els arbres B, útils per a emmagatzemar grans col·leccions ordenades d’elements en un disc. D’aquests arbres, se n’ha vist l’estructura i la manera com es realitzen les operacions d’inserció i supressió.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

49

Exercicis d’autoavaluació 1. En un arbre binari de cerca inicialment buit s’insereixen els valors de clau de l’1 al n, en ordre creixent. Posteriorment, es realitzen operacions de cerca sobre l’arbre resultant. Quin és el nombre d’operacions de comparació en el pitjor dels casos? Per a quins valors de clau passa? I si l’arbre està equilibrat? Empleneu la taula següent per a mesurar intuïtivament la complexitat de les operacions de cerca en un cas i en l’altre: n

Nombre de comparacions en el pitjor dels casos (arbre no equilibrat)

Nombre de comparacions en el pitjor dels casos (arbre equilibrat)

3 7 15 31 2. Esborreu el node de clau 5 en l’arbre AVL de la figura següent:

3. A partir del següent arbre AVL, esborreu el node de clau 10 i indiqueu les operacions de rotació necessàries i els factors d’equilibri abans i després de cada rotació.

4. Si definim el grau de desequilibri d’un arbre AVL com el nombre de nodes el factor d’equilibri dels quals és diferent de zero, reflexioneu i raoneu sobre el perquè de l’afirmació següent: “Els arbres de Fibonacci són arbres AVL amb el grau màxim de desequilibri possible”. 5. Inseriu en un arbre AVL inicialment buit les següents claus i en aquest mateix ordre: 3, 8, 9, 2, 1, 5, 4, 6, 10 i 7. Indiqueu les rotacions i els canvis de factor d’equilibri en cada operació. Posteriorment, extraieu els mateixos valors en l’ordre invers en què es van inserir. 6. Inseriu en un arbre B d’ordre 4, inicialment buit, les claus següents i en aquest mateix ordre: 20, 40, 10, 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 i 25. Sobre l’arbre resultant, esborreu la seqüència de claus següent: 25, 45, 24, 38, 32, 8, 27, 46, 13, 42, 5, 22, 18, 26, 7, 35 i 15. Indiqueu a cada pas l’estat inicial i final de l’arbre.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

50

7. Els arbres 2-3 són un tipus d’arbre de cerca no binari en el qual cada node té dos (node-2) o tres fills (node-3). Un node-2 conté un element i dos enllaços fills que, com en els arbres binaris de cerca, apunten cap a elements més petits (els del fill esquerre) i més grans (els del fill dret). Els nodes-2 poden no tenir fills, però no en poden tenir només un. Un node3 guarda exactament dos elements i tres fills (o bé cap). Si el node-3 té fills, el fill esquerre conté elements més petits que el primer element del pare; el fill central conté elements més grans que el primer element del pare, però més petits que el segon element; i el fill dret conté elements més grans que el segon element del pare. La propietat més interessant dels arbres 2-3 és que, per construcció, tots els nodes fulla són en un mateix nivell, per la qual cosa no és necessari fer operacions d'equilibratge addicionals, sempre que es respecti la definició de l’estructura citada. Programeu una implementació alternativa DicionariArbre23Impl per al TAD Diccionari. 8. En el paginador dissenyat en l’apartat 2.6, podríem haver optat per definir operacions per navegar per la col·lecció de dades, com avançar(k), que avança i torna els següents k elements del diccionari, i anàlogament retrocedir(k). Implementeu aquestes operacions i amplieu en conseqüència la interfície de la classe DiccionariEstes. 9. La solució implementada per al paginador del subapartat 2.6 recorre tots els elements de l’arbre AVL, per a posteriorment filtrar aquells que estan compresos entre les claus inicial i final demanades pel mètode paginar. Una solució més idònia evitaria haver de recórrer tots els elements, filtrant directament els sol·licitats en un sol recorregut, a mesura que es construeix l’iterador retornat pel mètode recorregutInordre() de la classe ArbreAVL. Modifiqueu la solució proposada perquè optimitzi la implementació del mètode paginar de la manera descrita. (Nota: la classe ArbreAVL es pot ampliar amb algun mètode auxiliar per al recorregut que tingui en compte el rang de claus que es pot recórrer.)

Arbres de cerca


51

© FUOC • P06/05001/00581 • Mòdul 7

Arbres de cerca

Solucionari 1. n

Nombre de comparacions en el pitjor dels casos (arbre no equilibrat)

Nombre de comparacions en el pitjor dels casos (arbre equilibrat)

3

3

2

7

7

3

15

15

4

31

31

5

Intuïtivament, es pot calcular el nombre de comparacions T(n) en el pitjor dels casos: – per a un arbre no equilibrat, T(n) = n; per tant, la seva complexitat és O(n) – per a un arbre equilibrat és T(n) = log2 n + 1; per tant, la seva complexitat és O(log n) 2. L’anàlisi comença pel node 10, antic pare del node 5 esborrat. En el node 10 es dóna el cas 2 de la supressió en un AVL, per la qual cosa s’anul·la la seva Fe i cal continuar analitzant. El següent és el node 84 (que tenia un Fe de +1), que s’ha esborrat per la branca més curta. Per tant, ens trobem en el cas 3. Per a esbrinar quina rotació cal realitzar, cal considerar el Fe del node 86, que té el mateix valor que el del 10 (tots dos són +1). Per tant, estem en el cas 3b i cal fer una rotació simple (D-D en aquest cas, perquè tots dos Fe són iguals a +1). El resultat és l’arbre que veiem al marge: 3. Són necessàries dues rotacions consecutives: primer, una rotació D-D per a equilibrar el subarbre amb arrel en la clau 50; i, després, una rotació D-E sobre el node 62, per a equilibrar l’arbre resultant de la primera rotació.


© FUOC • P06/05001/00581 • Mòdul 7

52

4. Els arbres de Fibonacci, per la manera de construir-se, són arbres AVL que no tenen cap node amb factor d’equilibri diferent de zero, per la qual cosa són arbres AVL amb el grau de desequilibri més gran possible.

Glossari arbre AVL m Tipus d’arbre binari de cerca en el qual les alçàries dels subarbres de qualsevol node difereixen com a màxim en una unitat. arbre B m Tipus d’arbre multicamí de cerca que limita el nombre de claus de cada node i assegura que l’arbre es mantingui sempre equilibrat en alçària. arbre binari de cerca m Arbre binari emprat per a emmagatzemar col·leccions ordenades d’elements enllaçats de manera que s’agilitin les operacions de cerca. sigla ABC ABC m Vegeu arbre binari de cerca. equilibri en alçària m Equilibri que manté un ABC quan, per a cada node, les alçàries dels seus subarbres esquerre i dret difereixen com a màxim en una unitat. equilibri perfecte m Equilibri que manté un ABC quan, per a cada node, el nombre de nodes dels seus subarbres esquerre i dret difereix com a màxim en una unitat. factor d’equilibri m Diferència entre les alçàries dels subarbres dret i esquerre d’un node.

Bibliografia Bibliografia bàsica Aho, A. V.; Hopcroft, J. E.; Ullman, J. D. (1988). Estructuras de datos y algoritmos. Buenos Aires: Addison Wesley Iberoamericana. Bayer, R.; McCreight, E. (1972). “Organization and maintenance of large ordered indexes”. Acta informatica (núm. 1, pàg. 173-189). Cormen, T. H.; Leiserson, C. E.; Rivest. R. L. (1996). Introduction to algorithms. Cambridge / Nova York / etc.: The MIT Press. Gamma, E.; Helm, R.; Johnson, R.; Vlissides, J. (2002). Patrones de diseño. AddisonWesley. Goodrich, M. T.; Tamassia, R. (2003). Data structures and algorithms in Java (3a. ed.). John Wiley & Sons. Preiss, B. R. (1999). Data structures and algorithms with object-oriented design patterns in Java. John Wiley & Sons. Rosen, K. (2004). Matemática discreta y sus aplicaciones (5a. ed.). McGraw-Hill. Weiss, M. A. (1995). Estructuras de datos y algoritmos. Upper Saddle River: Addison Wesley. Wirth, N. (1999): Algoritmos + Estructuras de datos = Programas (2a. ed.). Dossat.

Arbres de cerca


© FUOC • P06/05001/00581 • Mòdul 7

53

Arbres de cerca

Annex

Per a saber-ne més Els arbres vermell-i-negres són un tipus d’arbre binari de cerca que afegeixen un atribut a cada node per indicar-ne el color (vermell o negre) i imposen certes restriccions a la forma en què s'encadenen nodes vermells i negres. Es pot trobar una definició més precisa d’aquests arbres en l’obra de Goodrich i Tamassia (2003). El que és interessant dels arbres vermell-inegres és que asseguren una alçària màxima de 2 × log(n + 1) per a un arbre de n nodes (cf. Cormen i altres, 1996). Això fa que aquests arbres siguin bons arbres binaris de cerca. D’altra banda, els arbres 2-3 són una manera d’aprofitar un arbre B per a implementar arbres binaris de cerca. Els arbres 2-3 van ser inventats per J. E. Hopcroft fins i tot abans que Bayer i McCreight (1972) fessin el mateix amb els arbres B. Un arbre 2-3 pot guardar un màxim de dues claus en cada node. Quan s’intenta inserir-ne una tercera, el node es divideix com es feia en els arbres B. A més, les insercions s’esdevenen sempre en les fulles, mai en els nodes intermedis. D’aquesta manera, s’aconsegueix, com en els arbres B, que l’arbre estigui sempre equilibrat en alçària, la qual cosa facilita posar un límit al desequilibri que desmillorava la complexitat de les cerques.

També es pot demostrar que l’alçària d’un arbre 2-3 és més petita o igual que log n (cf. Aho i altres, 1988). Quant als arbres multicamí, hi ha algunes variants dels arbres B que pretenen millorar dos aspectes. D’una banda, els arbres B* aprofiten millor l’espai en disc ocupat per un arbre B, així asseguren una ocupació mínima de cada node de les dues terceres parts dels seus elements. Això ho aconsegueixen realitzant d’una manera diferent l’operació d’inserció, que en aquest cas inclou una redistribució o una divisió de nodes. D’altra banda, els arbres B+ milloren l’operació de recorregut, gràcies al fet que enllacen tots els nodes fulla entre ells i converteixen els nodes intermedis en índexs que guarden duplicats de les claus de les fulles. Una ocupació mínima del 66% només es compleix per a arbres d’ordre superior a quatre. Això és perquè cada clau (que és indivisible) representa en un node d’ordre més petit que quatre un percentatge molt elevat de la seva capacitat (almenys el 25%) que s’acumula en un node a costa del veí. Però aquest no és el cas comú, ja que els arbres B* estan pensats per a ordres grans, tantes claus com càpiguen en un sector d’un disc (els sectors de disc tenen mides a partir de 29 bytes).



M7