You are on page 1of 60

El TAD Taula

Xavier Franch i Gutiérrez

PID_00161972
Mòdul 6
© FUOC • PID_00161972 • Mòdul 6 El TAD Taula

Índex

Introducció .............................................................................................. 5

Objectius ................................................................................................... 8

1. Presentació del TAD Taula ............................................................. 9

2. Implementació per dispersió del TAD Taula ............................. 12


2.1. Funcions de dispersió ..................................................................... 17
2.1.1. Funció de dispersió sobre cadenes de caràcters .................. 18
2.1.2. Funcions de restricció d’un enter a un interval .................. 21
2.2. Organitzacions de dispersió ............................................................ 21
2.2.1. Organitzacions de dispersió encadenades:
la dispersió encadenada oberta ........................................... 22
2.2.2. Implementació de la dispersió encadenada oberta
en la biblioteca de TAD de l’assignatura ............................. 23
2.2.3. Organitzacions de dispersió seqüencials:
l’adreçament obert .............................................................. 26
2.2.4. El problema del sobreeiximent ........................................... 30

3. El TAD Conjunt .................................................................................. 34

4. Exemple d’aplicació: una taula de símbols


per a un llenguatge modular ......................................................... 38

5. Les taules i els conjunts a la Java Collections Framework ..... 45


5.1. Interfícies per a les taules i els conjunts en la JCF ......................... 45
5.2. Implementacions per a les taules i els conjunts en la JCF ............. 46

Resum ........................................................................................................ 50

Activitats .................................................................................................. 51

Exercicis d’autoavaluació .................................................................... 51

Solucionari ............................................................................................... 53

Glossari ..................................................................................................... 58

Bibliografia .............................................................................................. 59
© FUOC • PID_00161972 • Mòdul 6 5 El TAD Taula

Introducció

Una vegada estudiats els tipus abstractes de dades (TAD) de les seqüències i els
arbres, podem considerar que disposem de diversos mètodes d’organització
d’una col·lecció de dades que hi permeten accedir en un ordre fixat pel TAD
mateix, sia seqüencial, sia jeràrquic. Això no obstant, molt freqüentment ne-
cessitem organitzar en els nostres programes col·leccions de dades, normal-
ment anomenades valors, a les quals s’ha d’accedir a partir d’una altra dada,
anomenada normalment clau o identificador. El resultat és un nou TAD ano-
menat Taula, pel fet que gràficament es pot representar com una taula, amb
una primera columna per a la clau, i una o més columnes per a representar les
parts d’un valor.

Taula 1. Exemple de taula. Ocupació per despatxos d’un edifici d’oficines


Despatx Ocupant Extensió

100 Joan Pi 1423

101 Roser Grau 1424

102 Pep Solé 1425

108 Ramon Ruiz 1471

110 Maite Torres 1472

... ... ...

Un altre terme que s’usa sovint per a anomenar aquest TAD és el de diccionari,
pel fet que en un diccionari cerquem el significat (valor) d’una paraula (clau).

Figura 1. Entrades de diccionari

El TAD Taula es va començar a utilitzar fa molts anys i ha aparegut, i segueix


apareixent, en un gran nombre de contextos. Dos exemples clàssics i primige-
nis són les taules de símbols dels compiladors i les taules de mapatge de pàgi-
nes dels sistemes operatius. Il·lustrem el primer d’aquests exemples.

Una taula de símbols és una estructura que existeix en els traductors dels llen-
guatges de programació (compiladors i intèrprets) que associa a cada identifi-
cador d’un programa tota aquella informació d’interès per a la generació de
codi: categoria (si és un identificador d’objecte, de mètode, de classe, etc.), ti-
pus (si s’escau, que pot ser enter, real, string, etc.), direcció de memòria assig-
nada, nombre de bytes que ocupa, etc. El traductor crea una nova entrada a la
© FUOC • PID_00161972 • Mòdul 6 6 El TAD Taula

taula quan troba per primera vegada l’identificador; a partir d’aquest moment,
pot consultar la taula cada vegada que troba l’identificador en l’anàlisi del pro-
grama (per exemple, en fer l’anàlisi semàntica de la correcció d’una expressió);
quan acaba el tractament corresponent en l’àmbit de definició de l’identifica-
dor, l’esborra de la taula. Podem dir que la taula de símbols és un objecte que
pertany al TAD Taula tal que les claus són els identificadors i els valors associ-
ats, la resta d’informació.

Taula 2. Exemple de taula de símbols per a un programa Java


Nom Categoria Tipus Direcció

... ... ... ...

afegirAbansDe Mètode - 148FH

node Paràmetre Posicio<E> 3112H

elem Paràmetre E 3116H

nouNode Variable Posicio<E> 3120H

ant Variable NodeEncadenat<E> 3124H

public Posicio<E> afegirAbansDe(Posicio<E> node, E elem)


{
Posicio<E> nouNode;
NodeEncadenat<E> ant = anterior((NodeEncadenat<E>)node);
if (ant==darrer)
nouNode=afegirAlPrincipi(elem);
else
nouNode=afegirDespresDe(ant,elem);
return nouNode;
}

La utilitat i la necessitat de les taules

La necessitat de disposar de taules sorgeix contínuament en els nostres propis programes.


A continuació n’oferim dos exemples:

1) En una aplicació informàtica per a una acadèmia, es volen guardar les dades personals
dels seus estudiants: DNI, nom, edat, domicili, etc. En aquest cas, es pot definir una taula
d’estudiants en què el valor de cada estudiant són les seves dades personals, mentre que
la clau és el DNI, atès que no pot haver-hi dos estudiants amb el mateix DNI.

2) En una aplicació web per a una agència de viatges, es volen enregistrar tots els aero-
ports internacionals del món amb les seves dades: situació, capacitat, etc. Els aeroports
s’identifiquen per un codi de tres lletres (per exemple, el de Barcelona a Catalunya és
BCN, el de Rio de Janeiro a Brasil és GIG, etc.). Podem tenir una taula d’aeroports en què
el valor de cada un siguin les dades abans esmentades (situació, etc.) i la clau sigui el codi
de tres lletres.

En resum, el TAD Taula és un tipus que organitza una col·lecció de dades de


manera que cadascuna és accessible mitjançant una clau que la identifica uní-
vocament (no hi poden haver dues dades amb la mateixa clau). Sobre aquest
TAD, tindrem les típiques operacions d’inserció, supressió i consulta. Les dues
últimes operacions tenen com a referència la clau de la dada que es vol supri-
© FUOC • PID_00161972 • Mòdul 6 7 El TAD Taula

mir o consultar. La dificultat més gran en la definició de taules rau en la im-


plementació, atès que no és gens trivial assolir l’equilibri entre l’eficiència
temporal de les operacions i l’eficiència espacial de la representació.

El mòdul està dividit en cinc apartats que presenten els conceptes següents:

1) En el primer apartat, es presenten les operacions del TAD, que es definei-


xen usant el concepte matemàtic de funció. També es proporciona la interfície
Java que s’utilitza a la biblioteca de TAD de l’assignatura, i la seva situació dins
de la jerarquia de TAD. A més, s’esmenten possibles variants del concepte bà-
sic de taula.

2) En el segon apartat, s’estudia el mètode d’implementació habitual del TAD,


la implementació per dispersió. Aquesta estratègia es basa en l’elecció de dos
elements relacionats: un algoritme matemàtic capaç de transformar la clau en
un nombre que permeti indexar un vector, i una estratègia capaç de gestionar
l’emmagatzematge de les entrades de la taula.

3) En el tercer apartat, es mostra un exemple d’ús de les taules, l’exemple de


les taules de símbols tot just esmentades. Donem les operacions que definim
sobre les taules de símbols, i raonem de quina manera una taula implementa-
da amb dispersió és útil per a implementar una taula de símbols.

4) En el quart apartat ens adrecem a una variant de les taules, que en realitat
és un cas particular, en què la clau coincideix amb la dada a emmagatzemar.
El resultat és el TAD Conjunt. Redefinirem les operacions per a aquest cas i veu-
rem com canvien els conceptes explicats per a les taules.

5) En el cinquè apartat, mostrarem les facilitats que dóna la Java Collections


Framework (JCF) oferta en el JDK per a implementar els conceptes vistos al
llarg del mòdul.
© FUOC • PID_00161972 • Mòdul 6 8 El TAD Taula

Objectius

En els materials didàctics d’aquest mòdul trobarem els continguts i les eines
indispensables per a assolir els objectius següents:

1. Entendre la importància de les taules i dels conjunts com a mètodes d’accés


directe a un domini d’elements.

2. Conèixer els conceptes bàsics de la tècnica de dispersió com a implemen-


tació eficient de l’accés directe.

3. Aprendre alguna implementació bàsica de la tècnica de dispersió i presen-


tar breument alguna variant, entre elles les anomenades variants dinàmi-
ques.

4. Identificar en quines situacions pot ser útil una taula o un conjunt imple-
mentats amb tècniques de dispersió.
© FUOC • PID_00161972 • Mòdul 6 9 El TAD Taula

1. Presentació del TAD Taula

El TAD Taula és un tipus per a organitzar una col·lecció de dades anomena-


des valors. Els valors de la taula estan associats a una informació anomenada
clau o identificador que els identifica unívocament, és a dir, no hi poden ha-
ver dos valors dins la taula associats a la mateixa clau. Aquesta clau serà nor-
malment un únic atribut, usualment un string o un enter (per exemple, en
una taula de persones, la clau pot ser el DNI), si bé també es pot donar el cas
que la clau estigui composta de diversos atributs (per exemple, en una taula
dels llogaters d’una ciutat, la clau pot ser la composició del nom de la ciutat,
el carrer, el número i el pis). La clau pot formar part dels valors (per exemple,
si els valors són persones i la clau és el DNI) o no (per exemple, si la clau és
l’ISBN d’un llibre i, els valors, la llista de persones que l’han tingut en préstec
en una biblioteca).

Gràficament, dibuixem les taules com indica el seu nom, és a dir, en forma ta-
bular, tals que cada fila conté un parell clau-valor i només hi ha dues colum-
nes, una per a les claus i l’altra per als valors. Aquests parells s’anomenen
entrades de la taula. Si la clau forma part del valor, per convenció, no repetirem
la clau a la part del valor. En la introducció hem mostrat un exemple d’aquesta
representació gràfica.

El model subjacent de les taules són les funcions matemàtiques. Una funció f
és un cos matemàtic que associa valors que pertanyen a dos conjunts A i B, i
es denota per f: A → B. Aquestes associacions són úniques: per cada element
de A hi ha, com a molt, un element de B associat.

Diem que A és el domini de la funció, mentre que de B se’n diu co-


domini o abast. Denominem abast real de la funció, –denotat per
ab(f)– al subconjunt de B associat a algun element de A en la funció
i, simètricament, domini real de la funció –denotat per dom(f)– al
subconjunt de A que conté aquells elements que tenen un element
associat de B.

El domini de la funció correspon al conjunt potencial de claus de la taula; el


domini real, al conjunt de claus que han estat inserides a la taula; l’abast, al
conjunt potencial de valors de la taula; i l’abast real, al conjunt de valors que
han estat inserits a la taula. Per extensió, podem parlar del domini o abast (reals
o no) d’una taula, entenent el domini o abast de la funció que modelitza la
taula.
© FUOC • PID_00161972 • Mòdul 6 10 El TAD Taula

Figura 2. Expressió dels conceptes matemàtics subjacents a la taula 1

Les operacions que caracteritzen el TAD Taula són aquelles que possibiliten
l’accés als valors usant la clau. A continuació mostrem la seva signatura i definició
com a especificació informal de la pre- i post-condició. Denominem diccionari a la
interfície corresponent seguint la nomenclatura que utilitza el llenguatge Java en
la seva biblioteca JCF.

Figura 3. Interfície del TAD Taula

C: domini de la funció; E: abast de la funció

col·lecció Diccionari<C, E> esten Contenidor<E> és

constructor()
Crea una taula.
@pre Cert.
@post Retorna una taula buida.

E esborrar(C clau)
Esborra una entrada de la taula.
@pre Cert.
@post No hi ha cap entrada amb la clau donada a la taula i retorna
com a resultat el valor associat a la clau esborrada (o ‘nul’, si la clau
no era a la taula).

void afegir(C clau, E elem)


Afegeix una entrada (parell clau-valor) a la taula.
@pre Cert.
@post El valor passa a estar associat amb la clau.
© FUOC • PID_00161972 • Mòdul 6 11 El TAD Taula

Cal destacar quin és el comportament de l’operació si ja existeix una entrada


a la taula amb a com a clau. El resultat és que es trenca l’associació amb el valor
associat i la clau es passa a associar amb la nova dada b. Una altra opció, que
donaria lloc a una especificació diferent, seria no efectuar aquesta substitució Vegeu el punt a de l’exercici
d’autoavaluació 1 d’aquest mòdul.
o, fins i tot, no permetre aquest cas.

E consultar(C clau)
Obté el valor associat a una clau.
@pre Cert.
@post Obté el valor associat a la clau; retorna ‘nul’ si la clau no és a la taula.

Notem que el resultat no és un objecte vàlid en cas que la clau no existeixi; pot
ser útil, doncs, comprovar-ne prèviament l’existència mitjançant hiEs. Fins i tot,
podríem requerir explícitament que la clau ha d’existir (vegeu l’exercici 1c).

boolean hiEs(C clau)


Comprova si una clau és a la taula.
@pre Cert
@post Retorna ‘cert’ si la clau és a la taula i ‘fals’ altrament.

Aquesta operació és útil simplement per a validar l’existència o no d’una clau,


per això esdevé quasi imprescindible per a raonar sobre l’existència o absència
de les claus a la taula.

A més d’aquestes operacions, el TAD Taula ofereix les operacions que hereta
dels contenidors: una operació per a saber el nombre d’entrades de la taula
(nombreElements), una per a saber si la taula és buida (estaBuit), una operació
de clonació (clone) i un iterador per a obtenir-ne els elements (elements).
Aquesta operació retorna un iterador sobre les entrades de la taula en un ordre
no determinat. A més, el TAD Taula ofereix un segon iterador, claus, que re-
torna les claus de la taula també en ordre aleatori. Sobre aquests iteradors po-
dem aplicar les operacions habituals per a obtenir-ne els elements.

Figura 4. Les taules: hereves dels contenidors


© FUOC • PID_00161972 • Mòdul 6 12 El TAD Taula

2. Implementació per dispersió del TAD Taula

Ens adrecem en aquest apartat a la problemàtica de la implementació del TAD


Taula. Òbviament, amb els coneixements actuals, som capaços de construir
implementacions satisfactòries funcionalment (és a dir, que es comporten cor-
rectament), però amb problemes d’eficiència.

Estudiants d’una acadèmia

En una aplicació informàtica per a una acadèmia, es volen guardar les dades personals
dels estudiants: DNI, nom, edat, sexe i domicili. L’acadèmia té prop de 2.000 estudiants.
Atès l’ús que se’n vol fer (per exemple, esborrar i consultar els estudiants a partir del seu
DNI), decidim definir una taula d’estudiants en què la clau és el seu DNI (som, doncs, en
el cas que la clau forma part del valor). Ara volem implementar la taula. Podem constatar
que:

• Si usem una estructura lineal encadenada, com ara una llista dinàmica, per a organit-
zar els estudiants, les operacions d’inserció, supressió i consulta tenen un cost lineal
sobre el nombre d’estudiants, independentment de si l’estructura està ordenada o no
(en el cas de la inserció, perquè cal comprovar si ja existeix alguna entrada amb la clau
donada).

Figura 5. Representació mitjançant una llista encadenada dels estudiants


d’una acadèmia

• Si usem un vector gestionat seqüencialment, també tenim un cost lineal per a les ope-
racions d’inserció, supressió i consulta si els elements estan desordenats. En cas d’or-
denar el vector, les consultes passen a tenir un cost logarítmic prou bo, però les
operacions d’inserció i supressió segueixen tenint un cost lineal. En certes condicions,
aquesta ineficiència pot ser admissible, però en d’altres pot ser que no.

Figura 6. Representació mitjançant un vector desordenat dels estudiants


38.254.100 12.345.600 43.692.100

Joan Pep Anna

19 H 25 H 23 D

c/Pa 23 c/Pi 14 c/Po 44

Figura 7. Representació mitjançant un vector ordenat dels estudiants


12.345.600 38.254.100 43.692.100

Pep Joan Anna

25 H 19 H 23 D

c/Pi 14 c/Pa 23 c/Po 44

• Si usem un vector amb accés directe, aprofitant que els índexs són nombres naturals,
obtenim un cost constant en les operacions d’inserció, supressió i consulta, però mal-
baratem molt l’espai, ja que cal reservar tant espai com DNI hi pugui haver. Normal-
ment, aquesta situació serà inadmissible. A més, els recorreguts en resulten perjudicats:
per a obtenir l’element següent d’un altre, cal recórrer el vector fins a trobar la següent
© FUOC • PID_00161972 • Mòdul 6 13 El TAD Taula

posició lliure. Com a conseqüència, el cost del recorregut és lineal, però no sobre el
nombre d’elements de la taula (és a dir, la dimensió del domini real de la taula), sinó
sobre el nombre de claus possibles (és a dir, la dimensió del domini de la taula), que
normalment serà un factor molt més gran.

Figura 8. Representació mitjançant un vector indexat per a DNI

Suposem que el menor DNI possible és el 10.000.000 i el major el 49.999.999.

Aeroports internacionals

En una aplicació web per a una agència de viatges, es volen enregistrar tots els aeroports
internacionals del món amb les seves dades: situació, capacitat, etc. Els aeroports s’iden-
tifiquen per un codi de tres lletres (per exemple, el de Barcelona a Catalunya és BCN, el
de Rio de Janeiro a Brasil és GIG, etc.). Es requereix que hi hagi una funcionalitat per a
obtenir les dades d’un aeroport a partir del seu codi. La situació és similar a l’anterior, fins
i tot més restrictiva, ja que en aquest cas generalment no és possible accedir a un vector
usant el codi sense aplicar-hi alguna transformació.

Podem concloure que:

1) Les implementacions per vector amb accés directe optimitzen el cost de les
operacions d’accés individual als elements (inserció, supressió i consulta) i
perjudiquen el cost dels recorreguts per iterador. L’eficiència espacial en resul-
ta molt perjudicada perquè l’espai que ocupa l’estructura és fix i molt gran, li-
neal sobre el nombre de claus que existeixen en el domini, ja que cada clau té
una posició reservada. Habitualment, el nombre de claus emmagatzemades en
la taula és molt més petit que aquesta dimensió. Per acabar, cal considerar que
hi ha casos en què la clau no és un índex vàlid de vector (per exemple, clau
string o claus multiatribut), de manera que cal fer una manipulació prèvia.

2) Les implementacions encadenades milloren l’eficiència espacial des del


punt de vista asimptòtic, ja que ara és variable, lineal sobre el nombre de claus
emmagatzemades. Com a conseqüència, el cost dels recorreguts per iterador
millora. Ara bé, el cost de les operacions d’accés directe queda també lineal so-
bre aquest factor.

3) Les implementacions seqüencials exigeixen fer conjuctures pel que fa al


nombre de claus que s’emmagatzemen a la taula, la qual cosa no sempre és
possible. Si es pot esbrinar aquest valor, llavors l’eficiència espacial segueix
sent lineal sobre el nombre de claus emmagatzemades i el cost dels recorreguts
per iteradors també té aquest ordre. El cost de les operacions d’inserció i su-
pressió sempre és lineal sobre aquest factor. El cost de les operacions de con-
sulta (consultar i hiEs) és lineal sobre aquest factor si el vector està desordenat,
i logarítmic sobre aquest factor si està ordenat. En aquest segon cas, les actua-
litzacions (insercions i supressions) exigeixen moure elements.
© FUOC • PID_00161972 • Mòdul 6 14 El TAD Taula

Taula 3. Comparativa d’eficiència de les organitzacions esmentades


Operació Representació

Vector amb accés directe Implementació encadenada Implementació seqüencial

Inserció O(1) O(nombre real d’elements guardats) O(nombre real d’elements guardats)

Supressió O(1) O(nombre real d’elements guardats) O(nombre real d’elements guardats)
O(nombre real d’elements guardats)
Consulta O(1) O(nombre real d’elements guardats)
O(log nombre real d’elements guardats)

Iterador O(nombre màxim d’elements) O(nombre real d’elements guardats) O(nombre real d’elements guardats)

Espai O(nombre màxim d’elements) O(nombre real d’elements guardats) O(nombre real d’elements guardats)

Exigeix fer conjectures sobre el nombre


Altres Problemes per claus no enteres –
real d’elements guardats.

Per totes aquestes particularitats, no és habitual que les implementacions es- Taules amb pocs elements
mentades siguin admissibles com a implementació del TAD Taula. Per això
Una excepció remarcable és
cerquem altres possibilitats. En concret, sembla interessant la implementació quan la taula té pocs elements,
unes poques desenes. En
per vector amb accés directe sempre que trobem solució als dos problemes aquest cas, es considera que
una representació encadenada
identificats: la possibilitat que la clau no permeti indexar el vector directa- o seqüencial és admissible.
ment i el malbaratament d’espai. Per al primer problema, necessitem ser capa-
ços de convertir qualsevol clau en un enter que sí que puguem utilitzar per a
indexar el vector. Per al segon problema, hem de dimensionar el vector amb
aproximadament tantes posicions com claus esperem a la taula.

Un examen més detallat del segon exemple ens pot ajudar a generar algunes
idees bàsiques per a gestionar aquesta situació. És obvi que el codi d’aeroport
es pot transformar de manera senzilla en un enter mitjançant la fórmula ade-
quada. Per exemple, un hipotètic aeroport amb codi AAA podria tenir assigna-
da la posició 0 del vector, el de codi AAB la posició 1, AAC la 2, [...], AAZ la 25,
ABA la 26, etc., fins a l’hipotètic ZZZ que tindria la posició 17.575 (hi ha
17.576 codis des del AAA fins al ZZZ usant les 26 lletres de l’alfabet anglès).

Taula 4. Comparativa entre el codi hexadecimal i el codi dels aeroports


Hexadecimal Decimal Codi aeroport Decimal

0000H 0 AAA 0
0001H 1 AAB 1
0002H 2 AAC 2
... ... ... ...
0009H 0 ABA 27
000AH 10 ... ...
Hexadecimal Decimal Codi aeroport Decimal

000BH 11 BAA 676


... ... ... ...
000FH 15 BCN 741
0010H 16 ... ...
... ... GIG 4.270
... ...
ZZZ 17.575

Segons aquesta regla, els codis “vertaders” (és a dir, que identifiquen aeroports
existents) tindrien assignats enters: per exemple, BCN tindria assignat l’enter 741
© FUOC • PID_00161972 • Mòdul 6 15 El TAD Taula

mentre que GIG tindria assignat l’enter 4.270. Aquesta fórmula es pot expressar
matemàticament si ens adonem que el codi d’aeroport és com un nombre en
base 26, usant les lletres de l’alfabet anglès com a dígits. Ho podem entendre mi-
llor si recordem com s’escriuen els nombres hexadecimals (en base 16): quan
s’acaben els dígits del 0 al 9, usem les lletres de la A a la F. Doncs bé, la idea que
proposem aquí és la mateixa.

Sigui COD el codi de l’aeroport format per tres lletres, COD = COD1COD2COD3
(per exemple, en el cas de BCN, tenim que COD1 = ‘B’, COD2 = ‘C’, COD3 = ‘N’).
Definim la transformació de COD en un enter, TRANSF(COD), com:

TRANSF(COD) = ord(COD1) × 262 + ord(COD2) × 261 + ord(COD3) × 260 =


= ord(COD1) × 676 + ord(COD2) × 26 + ord(COD3)

En què ord és una funció que transforma una lletra de l’alfabet anglès (que té 26
lletres diferents) en un nombre del 0 al 25: ord(A) = 0, ord(B) = 1, ..., ord(Z) = 25.
Aquesta fórmula no és més que la conversió d’un nombre en base 26 expressat
amb lletres en un nombre decimal.

L’existència d’aquesta fórmula permet que un programa informàtic pugui ara


indexar un vector usant el codi de l’aeroport. Si definim un vector d’aeroports
com fem a continuació, podem accedir a aquest vector a partir d’un codi d’ae-
roport COD usant l’expressió A[TRANSF(COD)]:

T_aeroport[] A;
A = new T_aeroport[17576];

Fins ara hem solucionat el primer problema, però no el segon: ens trobem amb
el mateix malbaratament d’espai que hem esmentat en l’exemple dels estudi-
ants de l’acadèmia. Si considerem que hi ha uns 1.000 aeroports per emmagat-
zemar en l’aplicació, resulta que hi ha unes 16.576 posicions buides en el
vector. Aquest malbaratament es considerarà normalment intolerable. Com
podem evitar-lo?

La idea és que si hi ha aproximadament 1.000 aeroports, hem de dimensionar


el vector amb 1.000 posicions (aproximadament també) i així ajustar l’espai
emprat a l’espai requerit. Imaginem que fem, doncs:

A = new T_aeroport[1000];

Clarament des del codi AAA (que va a parar a la posició 0) fins al BML (que va
a parar a la posició 999) no hi ha cap problema; però, què passa a partir del
codi BMM, que hauria d’anar a la posició 1.000 que ja no existeix? Una mane-
© FUOC • PID_00161972 • Mòdul 6 16 El TAD Taula

ra matemàticament senzilla és modificar la fórmula i afegir una operació de


mòdul al final:

TRANSF(COD) = (ord(COD1) × 676 + ord(COD2) × 26 + ord(COD3)) mòdul 1.000

Així, ens assegurem que TRANSF no pot retornar cap valor més gran que 999
(que és la posició més gran del vector), i a més ja tenim resposta a la pregunta:
el codi BMM té assignada la posició 0, el BMN la 1, etc.

En aquests moments tenim un mitjà per a transformar codis en enters i per


a restringir aquests enters a les posicions del vector, que està ajustat a la di-
mensió requerida. Però apareix de manera òbvia un nou problema: què passa
si volem emmagatzemar dos aeroports, AE1 i AE2, que tinguin assignat un
codi del mateix valor (és a dir, van a parar a la mateixa posició del vector)?
És el cas, per exemple, dels codis AAA i BMM, que ja hem dit que van a parar
a la posició 0, dit en altres paraules, TRANSF(AAA) = TRANSF(BMM) = 0. Com
que en una posició del vector només pot anar un aeroport, en cas que vul-
guem emmagatzemar aquests dos aeroports, un d’ells l’hem d’emmagatze-
mar en un altre lloc. Com? Per exemple, podem emmagatzemar-lo en la
primera posició lliure a partir de la 0, o bé podem fer que de la posició 0 pen-
gi una llista en què guardem tots els aeroports que van a parar a aquesta po-
sició, com s’il·lustra a la figura 9.

Figura 9. Tractament de les col·lisions amb llista de sinònims

Aquest exemple ens ha servit per a introduir l’estratègia d’implementació de


les taules que presentem en aquest apartat, que es basa en les idees presentades
en l’exemple:

• Existència d’una funció, anomenada funció de dispersió, per a transformar


la clau en un enter dins d’un interval. De l’enter associat a la clau en diem
valor de dispersió de la clau.
© FUOC • PID_00161972 • Mòdul 6 17 El TAD Taula

• Existència d’un vector indexat per aquesta funció.

• Definició d’una política de resolució dels conflictes de col·lisió de dos ele-


ments que tenen el mateix valor de funció de dispersió. D’aquesta política
se’n diu organització de dispersió.

Figura 10. Taula resum sobre l’organització de les taules per dispersió
En la resta de l’apartat estudiem
les funcions i les organitzacions
de dispersió.

2.1. Funcions de dispersió

Com ha quedat dit, l’objectiu de les funcions de dispersió és assignar un enter


(anomenat valor de dispersió) a cadascuna de les claus que formen el domini de
la taula.

Per a ser més precisos, donat el domini A de les claus i donat un enter r, ||A|| representa el nombre
d’elements de A; r << ||A|| significa
que complirà r < ||A|| (i, normalment, r << ||A||) una funció de dispersió que r és molt més petit que ||A||.
h és de la forma h: A → [0, r – 1]. Per a cada clau a ∈ A, h(a) representa
la posició del vector a partir de la qual se cercarà a aplicant la política
pròpia de l’organització de dispersió elegida.

A l’hora de dissenyar les funcions de dispersió, ens regirem per les propietats
següents:

1) La funció de dispersió haurà de distribuir uniformement el domini de les


claus dins de l’interval [0, r – 1] per evitar que hi hagi claus perjudicades a
priori a l’hora d’efectuar cerques. Per exemple, si el domini de la funció té
10.000 claus i s’agafa r = 1.000, idealment s’hauria de complir que per cada
valor x de l’interval [0, r – 1] hi ha 10 claus del domini amb un valor de dis-
persió igual a x.
© FUOC • PID_00161972 • Mòdul 6 18 El TAD Taula

2) La funció de dispersió haurà de ser realment aleatòria per a evitar que, en


el cas que les claus que formin el domini real tinguin un patró comú, i hi hagi
més col·lisions del compte. Per exemple, imaginem que les claus són cognoms
espanyols de persones. Ja sabem que existeixen molts cognoms acabats en “-ez”.
Una mala funció de dispersió podria provocar que, per culpa d’aquesta similitud,
tots aquests cognoms anessin a parar a unes poques posicions del vector, amb les
col·lisions inherents a aquest fet.

3) La funció de dispersió haurà de ser ràpida de calcular per a evitar que s’alen-
teixi l’accés a la taula. Així, les funcions que per a assegurar que l’accés sigui
aleatori comencin a fer productes, divisions, arrels quadrades, etc., provocaran
problemes d’eficiència temporal, i no oblidem que un dels objectius de la dis-
persió és justament assegurar una bona eficiència temporal.

Estudiem una mica més la primera propietat. Si bé existeixen diverses funci-


ons que realment ens asseguren aquesta uniformitat, totes ho fan sobre el do-
mini de la taula, i no sobre el domini real. Això vol dir que, per molt ben
dissenyada que estigui la funció, podem tenir la mala sort que el subconjunt
de claus que s’emmagatzemen en la taula provoquin moltíssimes més col·lisi-
ons que les previstes. Per exemple, considerem una funció de dispersió que
transforma cadenes de lletres minúscules en enters del 0 al 25, i el valor de dis-
persió de la clau el determina la primera lletra: la ‘a’ té el 0, la ‘b’ té l’1, etc.
Aquesta funció distribueix uniformement, en teoria, el domini de les esmen-
tades cadenes, però podem imaginar-nos fàcilment molts exemples de domi-
nis reals que presenten mala distribució. Aquest factor d’incertesa és un
element característic de la implementació de les taules per dispersió que no Els arbres de cerca s’estudien en el
mòdul “Arbres de cerca” d’aquesta
assignatura.
apareix en d’altres implementacions (per exemple, els arbres de cerca).

A la resta de l’apartat, estudiarem una funció de dispersió particular que es pot


aplicar amb èxit a un gran nombre de casos. Aquesta funció està composta de
dues que s’apliquen l’una sobre el resultat de l’altra:

1) Primer, s’aplica una funció que transforma una cadena de caràcters en un


enter.

2) Després, se n’aplica una altra que restringeix un enter a un interval [0, r – 1].

Òbviament, si la clau és un enter, directament s’hi aplica la segona funció. En


qualsevol altre cas (per exemple, claus multiatribut) caldrà efectuar modifica-
cions apropiades sobre aquest cas general (vegeu l’exercici 5).

2.1.1. Funció de dispersió sobre cadenes de caràcters

Sigui C el domini dels caràcters de la màquina (UNICODE, ASCII, etc.). Ens in-
teressa trobar una bona funció de dispersió de la forma h: C* → Z, on C* repre-
© FUOC • PID_00161972 • Mòdul 6 19 El TAD Taula

senta les cadenes d’elements de C i Z representa els nombres enters. Aquesta


funció serà una combinació de la interpretació individual dels caràcters de la
cadena com a nombres.

Si bé podríem pensar a assignar a cada caràcter de C un nombre que fos sempre


el mateix, independentment de l’aplicació concreta, cal tenir en compte que
la majoria de les vegades les claus no usaran tots els caràcters de C. Per exem-
ple, imaginem el cas habitual que les cadenes de caràcters representen noms
(de persones, de llocs, etc.); és evident que hi ha alguns caràcters vàlids del
codi usat per la màquina que no hi apareixeran mai (com ara ‘&’, ‘%’, etc.), per
la qual cosa la seva codificació numèrica no s’usarà mai. Per exemple, si el codi
de la màquina és ASCII estàndard (que té valors dins l’interval [0, 127]) i en els
noms només apareixen lletres i alguns caràcters especials més (com ara “.” i “-”),
les cadenes estaran formades només per un conjunt de 54 caràcters del codi
(suposant que es distingeixen majúscules de minúscules); és a dir, més de la
meitat del codi estarà desaprofitat, i per això els valors generats no seran equi-
probables (encara més, hi pot haver diversos valors inabastables).

Per a evitar una primera pèrdua d’uniformitat és convenient dur a terme una
traducció de codis mitjançant una funció conv: C → [1, b], essent b el nombre
diferent de caràcters que poden formar part de les claus, de manera que la funció
de conversió h es defineix finalment com a h(a) = h’(CONV(a)), (essent CONV
l’extensió de conv a tots els caràcters de la clau, i h’ una funció h’: [1, b]* → Z).
Aquesta conversió serà més o menys laboriosa segons la distribució dels caràc-
ters dins del codi usat per la màquina. Per exemple, si el codi ho permet (per
exemple, ASCII, però no UNICODE), aquesta conversió es pot implementar
amb un vector V indexat amb el tipus de dades dels caràcters, com V[c] = conv(c)
en aquells caràcters que formen part de la clau, i V[c] = 0 per a la resta.

Ara ja estem en condicions de definir la funció de dispersió, que anomenarem


suma ponderada. A partir de la clau a = c1...cn, la funció de suma ponderada apli-
cada sobre a, abreujadament SP(a), combina els valors conv(ck) i els assigna un
pes segons la posició que ocupin dins de la cadena:

SP(a) = Σk: 1 ≤ k ≤ n: conv(ck) × bk–1

Podríem dir que la funció SP interpreta les cadenes com nombres en base b
(amb alguns matisos irrellevants).

Exemple

Sigui a = “HOLA”, b = 26 i una funció conv que assigna valors enters a partir de l’1 a les
lletres majúscules. Llavors:

SP(a) = conv(‘H’) + conv(‘O’) × 26 + conv(‘L’) × 676 + conv(‘A’) × 17.576 =


= 8 + 390 + 8.112 + 17.576 = 26.186

Per al bon comportament de la funció, és fonamental multiplicar el valor de cada caràcter


per un factor que prové de la seva posició. Si no hi aparegués, la influència de cada caràc-
© FUOC • PID_00161972 • Mòdul 6 20 El TAD Taula

ter en el resultat seria independent del lloc en què aparegués, i això incrementaria amb
molta probabilitat el nombre de col·lisions.

La forma donada de SP presenta un parell d’inconvenients que convé destacar:

1) És força ineficient a causa dels càlculs reiterats de la potència i del pro-


ducte. La potència es pot estalviar si introduïm un vector T auxiliar tal, que
T[k] = bk–1 s’inicialitzi una única vegada a l’inici del programa que usa la taula
i que generalment sigui prou petit dins del context de les dades del programa
per a negligir l’espai que ocupa.

2) Es pot produir un sobreeiximent en el càlcul que cal detectar abans que re-
alment tingui lloc. Per exemple, si b val 26, en una màquina que usi 32 bits
per a representar els enters, es pot produir sobreeiximent a partir del vuitè ca-
ràcter. Una solució consisteix a ignorar els bits que es van generant fora de la
dimensió de la paraula del computador, però aquesta estratègia ignora certa
quantitat d’informació, per la qual cosa redueix l’aleatorietat de la clau; a més,
no sempre és possible i/o eficient detectar i ignorar aquest sobreeiximent.

Una alternativa és partir la clau d’entrada en m trossos de dimensió k bits (si


la divisió m div k té residu, en realitat el darrer fragment serà més petit), aplicar
llavors la fórmula de la suma ponderada sobre cadascun d’aquests trossos i
combinar els resultats parcials mitjançant sumes. En la figura 11 es mostra un
exemple de divisió d’una clau de 22 caràcters en 6 fragments, els cinc primers
de 4 caràcters, i l’últim de 2 caràcters.

Figura 11. Exemple de partició de la clau ESTERNOCLEIDOMASTOIDEO

La dimensió k ha d’assegurar que el càlcul de cada tros per separat no provoqui


sobreeiximent, i tampoc la seva suma final. És a dir, k és l’enter més gran que
verifica la relació següent:

m × (Σj: 1 ≤ j ≤ k: bj) ≤ maxent i k × m ≥ Longitud màxima de les claus

tenint en compte que maxent és l’enter més gran representable en la màquina


(que, per les propietats de la representació binària dels nombres enters en els
ordinadors, ha de ser una potència de 2 menys 1) i que cada factor és el valor
màxim que pot prendre cada caràcter una vegada ponderat.
© FUOC • PID_00161972 • Mòdul 6 21 El TAD Taula

En l’exemple anterior, maxent ha de tenir un valor de, com a mínim, 4.194.303,


ja que si donem valors a l’expressió, obtenim 6 × (Σj: 1 ≤ j ≤ 4: 26j) = 2.851.368,
i la menor potència de 2 més gran que aquesta quantitat és 4.194.304.

2.1.2. Funcions de restricció d’un enter a un interval

Sigui r el nombre esperat de claus de la taula, i consegüentment [0, r – 1] l’in- Bibliografia


terval dels enters que s’usarà per a indexar un vector que formi part de la re- recomanada

presentació de la taula. L’objectiu és ara trobar una bona funció de dispersió Podeu trobar una
panoràmica de les funcions
de la forma h: Z → [0, r – 1], de manera que es puguin transformar enters grans de restricció d’un enter en un
interval en l’obra següent:
(ja siguin obtinguts mitjançant la funció de la suma ponderada, ja siguin di-
X. Franch (2001). Estructuras
rectament les claus) en índexs vàlids. En els textos del món de les estructures de datos: especificación, diseño
e implementación (4a. ed.).
de dades, existeixen moltes propostes, i aquí veurem la més simple, que es Barcelona: Edicions UPC
(Politext, núm. 30).
comporta bé en la majoria dels casos, la funció del mòdul. Disponible en línia a:
http://www.edicionsupc.es

A partir d’un enter x, la funció del mòdul, denotada per MOD, es defi-
neix com MOD(x) = x mod r.

Aquesta funció és efectivament uniforme, relativament ràpida de calcular i Bibliografia


recomanada
simple. Si les claus de la taula es distribueixen uniformement, el valor de r no
Podeu trobar les
és crític, perquè l’entrada ja és suficientment aleatòria. Altrament, el valor r es- justificacions d’aquests
devé crític per a una bona distribució. Així, per exemple, una potència de 2 valors en el llibre ja clàssic:
D. E. Knuth (1998). Sorting
afavoreix l’eficiència, però la distribució resultant és defectuosa perquè sim- and searching (2a. ed.).
Reading: Addison-Wesley.
plement agafaria els log2r bits menys significatius de la clau numèrica. Tampoc
no és bo que r sigui parell, perquè la paritat de z es conservaria en el resultat;
ni que sigui un múltiple de 3, perquè dos valors z i z’ que només es diferenci- El valor i la funció
hashcode
essin en la posició de dos bytes tindrien el mateix valor. En general, cal evitar
els r tals que (bs ± w) mod r = 0 per a s i w petits, perquè un mòdul amb aquest La biblioteca de TAD de l’assig-
natura aplica la funció del mò-
valor tendeix a ser una superposició dels bytes de a, i el que és millor és un r dul al valor hashcode de la clau.
El valor hashcode és l’aplicació
primer tal que bs mod r ≠ ±w. En la pràctica, sembla que n’hi ha prou que r no de la funció hashcode de Java
sobre la clau.
tingui divisors més petits que 20.

2.2. Organitzacions de dispersió

Com ja s’ha dit, les organitzacions de dispersió s’ocupen de resoldre les col·li-
sions que es produeixen durant la vida d’una taula de dispersió. Hi ha diverses
estratègies de dispersió que, com és habitual, podem classificar en encadena-
des i en seqüencials. Començarem estudiant una variant de les primeres, la
dispersió encadenada oberta –potser la implementació més intuïtiva del
concepte de dispersió–, i després passarem a l’organització seqüencial típica,
l’adreçament obert. Sigui quina sigui l’organització de dispersió adoptada, re-
© FUOC • PID_00161972 • Mòdul 6 22 El TAD Taula

cordem que el nostre objectiu és assolir una complexitat d’ordre constant,


O(1), en les operacions d’accés individual a la taula.

2.2.1. Organitzacions de dispersió encadenades:


la dispersió encadenada oberta

Segurament la manera més senzilla de resoldre les col·lisions consisteix a or-


ganitzar llistes amb les claus sinònimes que les provoquen. Per motius d’efici-
ència obvis, aquestes llistes s’hauran d’organitzar mitjançant encadenaments
i, per això, donen lloc a representacions encadenades. És l’anomenada taula de
dispersió encadenada oberta. Depenent de com distribuïm les llistes, obtenim di-
verses variants de la mateixa idea.

Si realment associem una estructura individual a cadascuna de les llistes, ens


veiem abocats a representar-les mitjançant memòria dinàmica, perquè, altra-
ment, la suma d’espai malbaratat en les diferents llistes podria esdevenir inac-
ceptable. És més, podria ser el cas que s’omplís una de les llistes de sinònims i
no n’hi cabessin més, llavors es produiria un error i d’altres quedarien buides.

La figura 12 mostra l’esquema de les taules de dispersió encadenades obertes.


Disposem d’un vector de r posicions, indexat de 0 a r – 1. És a dir, a partir d’una
clau, hi aplicarem una funció de dispersió i el resultat ens permetrà indexar
aquest vector. De cada posició k del vector, en penja una llista amb un node
per a cada entrada de la taula de manera que la funció de dispersió aplicada
sobre la clau d’aquesta entrada és igual a k. En la taula dibuixada a la figura 12,
això significa que totes les claus a1, ... , as tenen un valor de dispersió igual a 0.

Figura 12. Exemple de taula de dispersió encadenada oberta

En aquest gràfic, es mostra la llista corresponent als s sinònims que hi ha per a claus amb valor de dispersió igual a 0.
© FUOC • PID_00161972 • Mòdul 6 23 El TAD Taula

2.2.2. Implementació de la dispersió encadenada oberta


en la biblioteca de TAD de l’assignatura

Tot seguit es presenta la implementació seguida en la biblioteca de TAD de


l’assignatura per a la dispersió encadenada oberta de les taules. En l’estructura
de dades, destaquem que les llistes realment contenen claus sinònimes corres-
ponents al valor de dispersió de la que pengen, i que no hi ha més d’una cel·la
per a una clau. També citem la inexistència d’elements fantasmes perquè n’hi
hauria massa; en conseqüència, l’algoritme de supressió ha de distingir un cas
especial. Les insercions es fan per l’inici per a simplificar (tot i que és necessari
recórrer la llista per controlar el cas en què ja hi hagi una cel·la amb aquella
clau). La supressió, en recórrer la llista de sinònims, manté actualitzat un
apuntador a l’element anterior dins del recorregut per esborrar l’element de
manera eficient.

Les operacions tenen la complexitat següent: O(r) pel que fa a la creació de la


taula (cal crear les r llistes buides), i complexitat lineal sobre la longitud de les
llistes per a la resta. Si la longitud de les llistes és curta, aquest factor lineal és
pràcticament tan bo com si fos constant, tal com és el nostre objectiu. Per ai-
xò, cal que r sigui aproximadament igual al nombre esperat de claus, i que la
funció de dispersió realment distribueixi bé, de manera que el més probable
sigui que la majoria de llistes quedin molt curtes i de longitud semblant. En
aquests supòsits, i per a simplificar, el cost de les operacions d’accés directe es
considerarà O(1).

La complexitat espacial és O(r + k), essent k el nombre d’entrades de la taula.


En el cas normal, quan el nombre d’elements emmagatzemats en la taula sigui
el nombre esperat, tindrem que r ≈ k, i el cost espacial es pot escriure com O(r).

Per a assegurar que realment el nombre d’elements emmagatzemats en la taula


no ultrapassa les previsions, en la representació es pot incorporar un compta-
dor d’elements a la taula, de forma que el càlcul de la taxa d’ocupació sigui im-
mediat. Aquest comptador d’elements és útil quan no s’està segur de si la taula
ha estat dimensionada correctament i, per tant, de si hi ha el perill que s’hi
insereixin massa dades; si hi ha massa dades, la longitud de les llistes pot es-
devenir massa gran i fer perillar l’hipotètic cost constant de les operacions
d’accés individual a la taula. En inserir nous elements, cal comprovar prèvia-
ment que la taxa no ultrapassi una fita màxima determinada. Si la ultrapassés,
hi hauria una situació coneguda amb el nom de sobreeiximent. Més endavant Vegeu el tractament del
sobreeiximent en el subapartat
2.2.4 d’aquest mòdul didàctic.
presentem una organització per tractar el problema del sobreeiximent.

col·lecció TaulaDispersio<C, E> implementa Diccionari<C, E> és

• constructor() O(MIDA_TAULA_PER_DEFECTE)
– Crida al constructor amb MIDA_TAULA_PER_DEFECTE com a nombre
d’elements màxim (O(MIDA_TAULA_PER_DEFECTE)).
© FUOC • PID_00161972 • Mòdul 6 24 El TAD Taula

• constructor(int r) O(r)
– Crea el vector d’elements (O(r)).

• void afegir(C clau, E elem) O(k)


– Calcula el valor de dispersió de la clau (O(1)).
– Si la posició del vector índex està buida, crea una llista de sinònims i inse-
reix l’entrada a l’inici (O(1)).
– Altrament, cerca la clau a la llista de sinònims: (O(k))
• Si la troba, substitueix el valor associat (O(1)).
• Si no, insereix la clau a l’inici de la llista (O(1)).

• E esborrar(C clau) O(k)


– Calcula el valor de dispersió de la clau (O(1)).
– Si la llista de sinònims associada a la posició del vector índex no està buida:
(O(1))
• cerca la clau a la llista de sinònims: (O(k))
• Si la troba, l’esborra (O(1)).
• Si era l’únic element, esborra la llista (O(1)).

• E consultar(C clau) O(k)


– Calcula el valor de dispersió de la clau (O(1)).
– Altrament, cerca la clau en la llista de sinònims (O(k)).
– Si la troba, retorna el valor associat, si no, retorna ‘nul’ (O(1)).

• boolean hiEs(C clau) O(k)


– Efectua una crida per a consultar i comprova si el resultat està ‘nul’ (O(1)).

• int nombreElements() O(1)


– Consulta el nombre d’elements (O(1)).

• boolean estaBuit() O(1)


– Comprova si el nombre d’elements és 0 (O(1)).

• iterador<C> claus() O(r + n)


– Crea un iterador per recórrer les claus de la taula (O(r + n)).

• iterador<E> elements() O(r + n)


– Crea un iterador per recórrer els elements de la taula (O(r + n))

Magnituds del cost:

• MIDA_TAULA_PER_DEFECTE: valor màxim de dispersió per defecte


• r: valor màxim de dispersió; r = taula.length
• k: longitud esperada de la llista de sinònims; idealment, O(k) = O(1)
• n: nombre d’entrades (parells clau-valor) de la taula
© FUOC • PID_00161972 • Mòdul 6 25 El TAD Taula

uoc.ei.tads.TaulaDispersio

package uoc.ei.tads;

public class TaulaDispersio<C,E> implements Diccionari<C,E> {


public static final int MIDA_TAULA_PER_DEFECTE = 256;
protected int n = 0;
protected LlistaEncadenada<ClauValor<C,E>>[] taula;

...

public void afegir(C clau, E elem) {


ClauValor<C,E> kv = new ClauValor<C,E>(clau, elem);
int index = calcularIndexTaula(clau);
LlistaEncadenada<ClauValor<C,E>> sinonims=taula[index];
if ( sinonims == null )
creaLlistaDeSinonims(index,kv);
else {
Posicio<ClauValor<C,E>> posicio=cercarClauEnSinonims(sinonims,clau);
if (posicio!=null)
sinonims.reemplacar(posicio,kv);
else {
sinonims.afegirAlPrincipi(kv);
n++;
}
}
}

public E consultar(C clau) {


E objecteCercat=null;
int index = calcularIndexTaula(clau);
LlistaEncadenada<ClauValor<C,E>> sinonims=taula[index];
Posicio<ClauValor<C,E>> posicioElement=cercarClauEnSinonims(sinonims,clau);
if (posicioElement!=null) {
ClauValor<C,E> kv=posicioElement.getElem();
objecteCercat=kv.getValor();
}
return objecteCercat;
}

...

protected Posicio<ClauValor<C,E>> cercarClauEnSinonims


(LlistaEncadenada<ClauValor<C,E>> sinonims,C clau) {
Recorregut<ClauValor<C,E>> recorregutSinonims=sinonims.posicions();
Posicio<ClauValor<C,E>> posicio=null;
boolean trobat=false;
© FUOC • PID_00161972 • Mòdul 6 26 El TAD Taula

while (recorregutSinonims.hiHaSeguent() && !trobat) {


posicio=recorregutSinonims.seguent();
ClauValor<C,E> kvPosicio=posicio.getElem();
trobat=clau.equals(kvPosicio.getClau());
}
return posicio;
}
...
}

2.2.3. Organitzacions de dispersió seqüencials:


l’adreçament obert

En l’organització seqüencial de les taules per adreçament obert, es disposa


d’un vector d’exactament r posicions. En no haver-hi encadenaments en les
seves posicions, el mètode seguit consisteix a associar a cada clau una se-
qüència de posicions que determina a quina posició va a parar. Concreta-
ment, en inserir el parell (a, b) dins de la taula (a és la clau, b és el valor) de
manera que h(a) = p0, se segueix una seqüència p0, ..., pr–1 associada a a fins que:

1) Es troba una posició p tal que la seva clau és a. Se substitueix la seva infor-
mació associada per b.

2) Es troba una posició ps que és buida. S’hi col·loca el parell (a, b). Si s = 0,
vol dir que la clau ocupa la posició que li correspon. Si s > 0, de la clau a se’n
diu invasora (perquè ocupa un lloc que no és el seu).

3) S’exhaureix la seqüència sense trobar cap posició que compleixi alguna de


les dues condicions anteriors. Això significa que la taula està plena i no s’hi
pot inserir el parell.

La seqüència de posicions associada a la clau, anomenada camí, sempre és fixa


per a cada clau, la qual cosa assegura l’èxit de la gestió de la taula. Notem que
el camí es pot entendre com una mena de cadena d’encadenaments lògics, ja
que també determina un ordre. Del procés de generació dels valors del camí
en direm redispersió, i de cada accés a la taula mitjançant un valor del camí se’n
diu assaig. La redispersió s’assoleix mitjançant una col·lecció {hk} de funcions
de redispersió tal que, si l’aplicació de hk(a) no porta al resultat esperat (per
exemple, si no porta a la clau que s’està buscant), llavors s’aplica hk+1(a). La
funció de dispersió h que existeix sempre en les organitzacions de dispersió fa
en aquesta definició el paper de h0.

La figura 13 il·lustra el funcionament bàsic d’aquest tipus d’organització mit-


jançant una seqüència d’insercions, algunes de les quals produeixen col·lisi-
© FUOC • PID_00161972 • Mòdul 6 27 El TAD Taula

ons. Suposem que la funció de dispersió h porta l’element a la posició del seu
subíndex. Suposem també que quan una posició es troba ocupada, s’explora
la següent, i així successivament.

Figura 13. Exemple de taula de dispersió per adreçament obert

LL significa posició lliure.

Aquest tipus d’organització presenta una complicació addicional: la gestió de la


supressió d’elements. Quan una clau k col·lideix i finalment es diposita a la posi-
ció determinada per un valor hj(k), és perquè totes les posicions determinades pels
valors hi(k), per a tot i < j, estan ocupades; en suprimir una clau que ocupa la po-
sició hs(k), per a algun s < j, no es pot simplement marcar aquesta posició com a
lliure, perquè una cerca posterior de k dins la taula seguint l’estratègia abans es-
mentada fracassaria. En conseqüència, a més de les posicions ocupades o lliures,
es distingeix un tercer estat que identifica les posicions esborrades, les quals es
tracten com a lliures en cercar lloc per a inserir un nou parell, i com a ocupades
en cercar una clau. En la figura 14 es mostra un exemple d’evolució d’una taula
d’adreçament obert amb insercions i supressions.

Ara, l’únic que queda per determinar són les estratègies de redispersió que cal
utilitzar en l’organització vista. Podem trobar en la literatura diverses propos-
tes. Totes compleixen les propietats següents:

1) El camí associat a una clau és sempre fix.


2) El camí d’una clau és una permutació (sense repeticions) de les r posicions
de la taula.
3) Els camins de dues claus diferents han d’assemblar-se el mínim possible.
© FUOC • PID_00161972 • Mòdul 6 28 El TAD Taula

Figura 14. Exemple de supressions de taula de dispersió per adreçament obert

La darrera propietat vol dir que, si bé és inevitable que dues claus compartei-
xin part dels seus camins, cal evitar que l’encavalcament sigui total a partir
d’un punt determinat. En cas contrari, a la que es produís una primera col·li-
sió, se’n produirien moltes més; eventualment, cada assaig provocaria una
nova col·lisió. Aquest fenomen s’anomena apinyament i és present en diferents
graus en totes les estratègies; el que passa és que, en algunes, el grau d’apinya-
ment és prou petit per a negligir-lo.

El primer mètode de redispersió que considerem és la redispersió lineal. Es ca-


racteritza perquè la distància entre dues funcions consecutives de la família de
funcions de redispersió és constant:

hi(a) = (h(a) + k × i) mod r

Veiem, doncs, que la redispersió lineal es basa en l’existència d’una funció de


dispersió “primària”, h, que s’usa com a punt de partida. L’aplicació del residu
de la divisió és necessària per a mantenir els valors de dispersió generats dins
dels límits de la taula.

El valor concret de k és irrellevant, i per això s’acostuma a prendre k = 1. A


més, aquesta elecció assegura que amb r assajos s’accedeixi a les r posicions
diferents del vector, de manera que si hi ha alguna posició buida, finalment
es troba.

La redispersió lineal presenta un alt grau d’apinyament: quan es produeix una


col·lisió, aquesta es repeteix una i altra vegada. A més, la probabilitat d’ocupa-
© FUOC • PID_00161972 • Mòdul 6 29 El TAD Taula

ció d’una posició de la taula en la inserció següent depèn directament del


nombre de posicions consecutives ocupades que hi ha immediatament abans.
Dit en altres paraules, una zona de la taula que contingui moltes posicions
ocupades consecutives té més probabilitats de créixer, la qual cosa alenteix el
temps d’accés a les claus que s’hi van afegint i que possiblement s’hauran in-
serit després de diversos assajos.

Figura 15. Representació gràfica del fenomen d’apinyament

P representa la probabilitat d’ocupació d’una posició buida en la següent inserció.

Tanmateix, la redispersió lineal té una propietat interessant: la supressió no


obliga a degenerar la taula, com fèiem en la implementació tot just vista, sinó
que es poden moure diversos elements per a evitar espais buits. Concretament,
en esborrar l’element que ocupa la posició pi dins una cadena p1, ... , pn, i < n,
es poden moure els elements kj que ocupen les posicions pj, i < j = n, a posicions
ps, s < j, sempre que es compleixi que s = t, essent pt = h(kj). És a dir que, en
esborrar un element, s’examina la seva cadena des de la posició que hi ocupa fins
al final, i es mouen cap endavant tants elements com sigui possible. En concret,
un element es pot moure sempre que la posició de destinació no sigui anterior al
seu valor de dispersió. La figura 16 mostra un exemple d’evolució d’una taula
amb adreçament obert i redispersió lineal amb moviment a les supressions.

Aquesta tècnica no evita només la degeneració espacial de la taula, sinó que


apropa els elements al seu valor de dispersió i; a més, segons la distribució dels
elements, es poden partir les cadenes en dues. Ara bé, la conveniència del mo-
viment d’elements s’ha d’estudiar acuradament perquè pot portar problemes
d’ineficiència.

L’alternativa més popular a la redispersió lineal és l’anomenada redispersió


doble. El nom prové de la seva definició a partir de dues funcions de dispersió,
la primària h i la secundària g:

hi(a) = (h(a) + g(a) × i) mod r

És a dir, si comparem amb la redispersió lineal, simplement en canvia l’incre-


ment constant per un increment variable. Si h i g són independents entre si,
se soluciona el problema de l’apinyament: la ocurrència d’una col·lisió no im-
© FUOC • PID_00161972 • Mòdul 6 30 El TAD Taula

plica col·lisions futures més que ocasionalment (amb les mateixes probabili-
tats que si no n’hi haguessin hagut anteriorment). De fet, el mètode de
redispersió doble s’apropa molt als resultats teòricament més bons, sempre
que h i g hagin estat correctament dissenyades.

Figura 16. Taula d’adreçament obert amb moviments a les supressions

Tradicionalment, la forma concreta de g depèn de l’elecció de h. Així, per


exemple, suposant que les claus siguin enteres, si h(a) = a mod r podem agafar
g(a) = 1 + (a mod (r – 2)); idealment, r i r – 2 haurien de ser nombres primers
(per exemple, 1.021 i 1.019). Una altra possibilitat és prendre una g que no
obligui a calcular noves divisions, i poder aprofitar els càlculs efectuats anteri-
orment amb h, com ara h(a) = a mod r i g(a) = 1 + a div r (cal forçar el resultat
perquè sigui diferent de zero).

Per acabar, notem que, a diferència del mètode lineal, no es poden moure ele-
ments perquè en esborrar-ne, no se sap trivialment de quines cadenes formen
part.

2.2.4. El problema del sobreeiximent

Com ja hem esmentat i queda clar a partir de les organitzacions presentades,


la utilització de les organitzacions per dispersió exigeix fer conjectures pel que
fa a un valor r que determina l’interval de valors de dispersió de les claus de la
© FUOC • PID_00161972 • Mòdul 6 31 El TAD Taula

taula i que s’usa per dimensionar un vector. A més, des del punt de vista del
rendiment de la taula, el valor r té una importància cabdal:

• En el cas de l’organització encadenada oberta, l’eficiència temporal de


les operacions d’accés individual és funció de la longitud de les llistes de
sinònims, i aquesta longitud depèn de dos factors: la qualitat de la fun-
ció de dispersió –referida a la capacitat per a distribuir uniformement les
claus a l’interval [0, r – 1]– i la taxa d’ocupació de la taula –definida com
el quocient entre el nombre d’entrades de la taula i r. Hi ha estudis que
mostren que el rendiment de la taula pot començar a degenerar si la taxa
d’ocupació ultrapassa el valor d’1,0.

• En el cas de l’organització per adreçament obert, els perills són els matei-
xos: una funció de dispersió que distribueixi malament i una taxa d’ocupa-
ció elevada de la taula. Cal veure, a més, que hi ha una restricció física
insalvable: no hi poden cabre més d’r claus, de manera que la taxa d’ocu-
pació mai no serà superior a 1. Però de fet, hi ha resultats que mostren que
el rendiment d’aquest tipus d’organització comença a ser dolent a partir
d’una taxa d’ocupació del 0,6; per això, normalment el valor r es calcula
per excés respecte del nombre de claus esperades.

Com a conclusió, les organitzacions per dispersió vistes són arriscades si


no tenim una idea clara del nombre d’entrades de la taula. Una possibilitat
és estudiar altres alternatives, com ara els arbres de cerca que es presenten
en un altre mòdul. Una altra possibilitat és utilitzar una variant que sigui
capaç de fer créixer la taula si s’ultrapassa la taxa d’ocupació màxima
(diem que es produeix sobreeiximent) i obtenir les anomenades organit-
zacions de dispersió dinàmiques.

La primera opció òbvia per a solucionar el problema és redimensionar el vec- Vegeu el concepte de cost
amortitzat en el mòdul
tor d’r posicions fins al nou valor màxim de dispersió. És una solució molt sim- “Contenidors seqüencials”
d’aquesta assignatura.
ple. A primera vista pot semblar molt ineficient, perquè cal reinserir totes les
entrades de la taula, i això té un cost lineal. Però aquí hem d’analitzar el cost
amortitzat (que s’ha introduït en el mòdul d’estructures seqüencials): si cal re-
inserir N entrades en el nou vector, el nombre mínim d’insercions que haurem
efectuat en la taula és N, cadascuna amb una eficiència O(1), de manera que el
cost que arrosseguem és O(N), i el cost O(N) de la reinserció queda compensat.
La figura següent mostra un exemple de redimensionament passant d’una tau-
la de 5 elements a una taula de 10 elements. La funció de dispersió es defineix * Aquesta és la solució
implementada a la JCF.
com el valor del subíndex de la clau mòdul la dimensió del vector.*

La solució alternativa consisteix a estendre selectivament només una part de


la taula per acomodar-la a un nou valor més gran. Hi ha diverses maneres
d’implementar aquesta idea segons quina part de la taula s’estén i amb quina
freqüència, i aquí ens centrem en l’organització de dispersió extensible.
© FUOC • PID_00161972 • Mòdul 6 32 El TAD Taula

En aquesta estratègia, es disposa d’una taula índex que, com a informació, conté
apuntadors a zones de memòria que són les que realment contenen les entrades
de la taula organitzades mitjançant qualsevol de les organitzacions de dispersió
vistes. Aquest vector índex s’indexa usant els s bits de l’esquerra de la clau. La
posició indexada del vector es correspon a la interpretació decimal d’aquests s
bits. Com a resultat, la dimensió del vector és 2s, una magnitud considerable-
ment menor que r. La figura 17 mostra un exemple amb s = 2; mostrem el con-
tingut d’una de les taules suposant que les claus a tenen els seus tres bits de més
a l’esquerra igual a 010, i les b, igual a 011.

Figura 17. Organitzacions de dispersió dinàmiques

En inserir un nou parell clau-valor a la taula, primer s’indexa el vector índex


usant la interpretació binària dels s bits de l’esquerra de la clau, i s’obté la taula
associada a aquesta posició j del vector índex. A continuació, s’aplica la funció
de dispersió escollida sobre la clau i se n’obté el valor de dispersió m, que s’uti-
litza per a accedir a la taula de dispersió obtinguda prèviament. Si es troba la
clau, s’actualitza el valor associat. Si no es troba, s’insereix seguint l’estratègia
de dispersió escollida, a menys que la inserció provoqui sobreeiximent, és a
dir, que s’ultrapassi una taxa d’ocupació màxima predeterminada.

En cas de sobreeiximent, immediatament es duplica la dimensió del vector ín-


dex, però ara es consideren, no pas els s bits de l’esquerra, sinó els s + 1. Les
noves posicions 2j i 2j + 1 passen a apuntar a dues taules noves que es repar-
teixen les entrades de la taula que anteriorment estava associada a la posició j,
segons sigui el valor de dispersió d’aquestes claus en els s+1 bits de més a l’es-
querra, mentre que la resta de posicions 2k i 2k + 1 (k ≠ j), apunten a la taula
© FUOC • PID_00161972 • Mòdul 6 33 El TAD Taula

anteriorment associada a la posició k. Com a conseqüència, només es tornen


a ubicar les entrades corresponents a una de les taules. En la figura 18, se su-
posa que la posició j que pateix el sobreeiximent és j = 1, i per això és la taula
associada la que es desdobla. La clau la inserció de la qual ha provocat el so-
breeiximent és a2.

Figura 18. Desdoblament de la taula per sobreeiximent

Suposem ara que una altra posició i, i ≠ j arriba a la condició de sobreeiximent.


En aquest cas, repetiríem el procés però sense duplicar la dimensió del vector ín-
dex, atès que la taula associada a la posició i estava associada tant a la posició i
com a la i + 1 (si i era parell; altrament, tant a la i com a la i – 1). En altres parau-
les, la dimensió del vector índex augmenta només quan totes les claus de la tau-
la que arriben al sobreeiximent tenen els s + 1 bits iguals a la representació
binària de la posició corresponent.

La supressió provoca el funcionament simètric en la taula. Si s’esborra una en-


trada de la taula i, com a resultat, la suma de les taxes d’ocupació de dues tau-
les que corresponen a dues posicions 2j i 2j + 1 passa a estar per sota de la taxa
d’ocupació màxima predeterminada, aquestes taules es fusionen (és a dir, les
seves entrades es reinsereixen en una nova taula) i les dues posicions corres-
ponents en la taula índex passen a apuntar a la mateixa taula.
© FUOC • PID_00161972 • Mòdul 6 34 El TAD Taula

3. El TAD Conjunt

Una evolució del concepte taula és el de conjunt, en què la clau i el valor que
s’emmagatzemen en la taula coincideixen. Exemples clàssics són conjunts de
strings o d’enters. Si bé el model matemàtic subjacent i la interfície varien lleu-
gerament, les estratègies d’implementació són idèntiques al cas de les taules,
adaptades a la peculiaritat esmentada.

El model subjacent del TAD Conjunt són els conjunts de les matemàtiques. Un
conjunt C d’elements d’un domini A conté elements de A sense repeticions.
Denotem per P(A) tots els possibles conjunts que es poden formar amb ele-
ments d’A. Les operacions bàsiques són anàlogues al cas de les taules i, a més,
es presenten operacions d’unió, intersecció i diferència. A continuació mos-
trem la seva signatura i especificació pre-post.

Figura 19. Interfície del TAD Conjunt

col·lecció Conjunt<E> esten Contenidor<E> és

constructor()
Crea un conjunt.
@pre Cert.
@post Retorna un conjunt buit.

Elements ja inserits
void afegir(E elem)
Per la definició de Conjunt, si
Afegeix un element al conjunt. l’element inserit ja era en el
conjunt, l’operació no té efec-
@pre Cert. te, ja que els conjunts no tenen
@post L’element pertany al conjunt. elements repetits.

E esborrar(E elem) Elements no trobats


Esborra un element del conjunt.
L’operació d’esborrar no
@pre Cert. farà cap acció si l’element
no és al conjunt.
@post L’element no pertany al conjunt.
© FUOC • PID_00161972 • Mòdul 6 35 El TAD Taula

boolean hiEs(E elem)


Comprova si un element és al conjunt.
@pre Cert.
@post Retorna ‘cert’ si l’element pertany al conjunt i ‘fals’ altrament.

A més d’aquestes operacions, el TAD Conjunt ofereix les operacions que hereta
dels contenidors: una operació per a saber el nombre d’entrades de la taula
(nombreElements), una per a saber si la taula és buida (estaBuit), una operació
de clonació (clone) i un iterador per a obtenir-ne els elements (elements).
Aquesta operació retorna un iterador sobre les dades de la taula en un ordre
aleatori. Sobre aquest iterador, podem aplicar les operacions habituals per a
obtenir els elements.

Figura 20

La implementació dels conjunts en la biblioteca de l’assignatura es presenta tot


seguit. Les operacions que són anàlogues a les taules simplement deleguen en
les operacions de taules corresponents, aplicades sobre un atribut privat (td) que
és una instància de les taules en què el tipus de les claus i els valors són el mateix.
Les operacions d’unió, intersecció i diferència s’implementen directament en
una classe abstracta, ConjuntAbstracte (de fet, la implementació dels conjunts
per mitjà de taules hereta d’aquesta classe intermèdia), usant iteradors per a ob-
tenir els elements del conjunt passat com a paràmetre i tractant-los en conse-
qüència. L’estudi d’eficiència de les operacions és similar al de les taules, i en el
cas de la unió, la intersecció i la diferència és O(n × k), ja que per cada element
obtingut cal fer un accés directe de cost O(k) a la taula de dispersió (que normal-
ment suposarem que és O(1) si les llistes de sinònims són curtes).

col·lecció ConjuntTaulaImpl<E> esten ConjuntAbstracte<E> és

• constructor() O(MIDA_TAULA_PER_DEFECTE)
– Crida al constructor amb MIDA_TAULA_PER_DEFECTE com a nombre
d’elements màxim (O(MIDA_TAULA_PER_DEFECTE)).
© FUOC • PID_00161972 • Mòdul 6 36 El TAD Taula

• constructor(int r) O(r)
– Crea la taula de dispersió (O(r)).

• void afegir(E elem) O(k)


– Afegeix a la taula de dispersió (O(k)).

• void esborrar(E elem) O(k)


– Esborra de la taula de dispersió (O(k)).

• boolean hiEs(E elem) O(k)


– Mira si l’element és a la taula de dispersió (O(k)).

• int nombreElements() O(1)


– Consulta el nombre d’elements (O(1)).

• boolean estaBuit() O(1)


– Comprova si el nombre d’elements és 0 (O(1)).

• iterador<E> elements() O(r + n)


– Crea un iterador per recórrer els elements de la taula (O(r + n)).

col·lecció ConjuntAbstracte<E> implementa Conjunt<E> és

• void unio(Conjunt<E> cjt) O(n × k)


– Construeix un iterador amb els elements de cjt (O(1)).
– Afegeix al conjunt tot element de l’iterador (O(n × k)).

• void intersecció(Conjunt<E> cjt) O(n × k)


– Construeix un iterador amb els elements del conjunt (this) (O(1)).
– Construeix una llista auxiliar amb tots els elements de l’iterador que no
són a cjt (O(n × k)).
– Recorre la llista esborrant, un a un, tots els elements de la llista auxiliar
(O(n × k)).

• void diferencia(Conjunt<E> cjt) O(n × k)


– Construeix un iterador amb els elements de cjt (O(n)).
– Esborra del conjunt tot element de l’iterador (O(n × k)).

Magnituds del cost:

– MIDA_TAULA_PER_DEFECTE: valor màxim de dispersió per defecte.


– r: valor màxim de dispersió; r = taula.length.
– k: longitud esperada de la llista de sinònims; idealment, O(k) = O(1).
– n: nombre d’entrades (parells clau-valor) de la taula.
© FUOC • PID_00161972 • Mòdul 6 37 El TAD Taula

uoc.ei.tads.ConjuntAbstracte

package uoc.ei.tads;
public abstract class ConjuntAbstracte<E> implements Conjunt<E> {

public void unio(Conjunt<E> conj) {


Iterador<E> iter = conj.elements();
while (iter.hiHaSeguent())
afegir(iter.seguent());
}
...
}

uoc.ei.tads.ConjuntTaulaImpl

package uoc.ei.tads;
public class ConjuntTaulaImpl<E> extends ConjuntAbstracte<E> {
protected TaulaDispersio<E,E> td;
...
public void afegir(E elem) { td.afegir(elem, elem); }
public boolean hiEs(E elem) {
return td.consultar(elem) != null; }
public E consultar(E elem) {
return td.consultar(elem); }
public E esborrar(E clau) {
return td.esborrar(clau); }
public Iterador<E> elements() {
return td.elements(); }
}
© FUOC • PID_00161972 • Mòdul 6 38 El TAD Taula

4. Exemple d’aplicació: una taula de símbols


per a un llenguatge modular

Durant el procés de traducció d’un programa escrit en un llenguatge de pro-


gramació d’alt nivell, és necessari construir una estructura de dades que associï
a cada identificador que hi apareix un cert nombre de característiques, com
ara la categoria (variable, paràmetre, funció, tipus, etc.), l’adreça física a la me-
mòria, la mida que ocupa a la memòria, etc. Aquesta estructura és dinàmica
perquè els identificadors s’hi insereixen i se n’esborren a mesura que es neces-
sita. Ja hem comentat a l’inici del mòdul que aquesta estructura s’anomena
taula de símbols. Diversos tipus de llenguatges poden exigir taules que presen-
tin característiques lleugerament diferents.

En aquest apartat veurem la implementació de les taules de símbols per als


anomenats llenguatges de blocs, com ara Pascal o C, que són llenguatges que de-
fineixen com a àmbit d’existència dels seus identificadors els diferents blocs
que formen el programa principal. Un bloc correspon a la idea de subprogra-
ma (funcions, procediments, mètodes, etc.). Com que els blocs es poden im-
bricar, el llenguatge ha de definir exactament les regles de visibilitat dels
identificadors, de manera que el traductor sàpiga resoldre les possibles ambi-
güitats que apareguin en el procés de traducció del bloc.

Normalment, les dues regles de visibilitat dels identificadors principals


són:

1) No es poden declarar dos identificadors amb el mateix nom dins


d’un mateix bloc.

2) Una referència a un identificador requereix consultar els blocs imbri-


cats de dins cap enfora, a partir del bloc en curs.

En la figura 13, es presenta un programa escrit en el llenguatge de programació


Pascal i una taula de símbols tal com està en el moment que el traductor ha
arribat al punt writeln(c). Com a característiques associades, hi ha la categoria
de l’identificador, el tipus (si escau) i la dimensió. La ratlla gruixuda delimita
els dos blocs existents, el programa principal PP i el bloc corresponent al pro-
cediment Q en curs. En aquesta situació, una consulta de l’identificador a re-
torna la descripció de la variable local a, que oculta la variable del programa
a; mentre que una consulta de b retorna la descripció de la variable del progra-
ma b. La variable del programa a tornarà a ser visible en el moment que el
compilador acabi el procés de traducció del bloc Q.
© FUOC • PID_00161972 • Mòdul 6 39 El TAD Taula

Figura 21

Clarament, una taula de símbols és una mena de taula de dispersió amb la petita
complicació del tractament dels blocs. Fixem-nos que la taula de la figura 13 no
es pot considerar com una taula de dispersió, perquè les claus (en aquest cas,
els identificadors del programa) hi poden aparèixer més d’una vegada. Una
solució simple seria considerar que cada entrada de la taula té, no pas unes
característiques úniques, sinó una seqüència de característiques en què cada ele-
ment identifica, a més, el bloc al qual està associat, tal com mostra la figura 22.
En realitat la seqüència és una pila, ja que se segueix l’estratègia “el darrer que
hi entra és el primer que en surt”.

Figura 22. Representació de la taula de símbols mitjançant una taula de dispersió

PP vol dir programa principal.


© FUOC • PID_00161972 • Mòdul 6 40 El TAD Taula

Per a reflexionar sobre l’escaiença d’aquesta representació, analitzem quines


són les operacions necessàries sobre la taula de símbols:

• L’operació constructora crea una taula de símbols buida i defineix el pro-


grama principal com a bloc en curs.
• L’operació entra() enregistra que el traductor comença la traducció d’un
nou bloc.
• L’operació surt() enregistra que el traductor ha acabat la traducció d’un
bloc. El nou bloc en curs passa a ser aquell que conté el bloc de què se surt.
• L’operació declara(id, categoria, tipus, dimensio) enregistra l’existència d’un
identificador amb unes característiques associades dins del bloc en curs.
• L’operació consulta(id) retorna les característiques de l’identificador id cor-
responents a la seva declaració dins del bloc més proper al bloc en curs.

uoc.ei.exemples.modul6.TaulaSimbols

...
public interface TaulaSimbols {

void entra();
void surt();
void declara(String id, Categoria categoria,
Tipus tipus, int dimensio);
PropietatsSimbol consulta(String id);

Observem que, per a obtenir la taula de símbols de l’exemple, la seqüència


d’operacions que s’aplica sobre la taula un cop creada és:

uoc.ei.exemples.modul6.ProvaTaulaSimbols

...
TaulaSimbols ts=creaTaulaSimbols();
ts.declara(“P”,Categoria.PROGRAM,
Tipus.NO_DEFINIT, 10230);
ts.declara(“a”,Categoria.VAR,Tipus.INT, 1024);
ts.declara(“b”,Categoria.VAR,Tipus.INT, 1026);
ts.declara(“Q”,Categoria.PROC,
Tipus.NO_DEFINIT, 10500);
ts.entra();
ts.declara(“c”,Categoria.PARAM,Tipus.INT, 1028);
ts.declara(“a”,Categoria.VAR,Tipus.REAL, 1030);
ts.declara(“d”,Categoria.VAR,Tipus.REAL, 1032);
ts.surt();
© FUOC • PID_00161972 • Mòdul 6 41 El TAD Taula

La representació mitjançant dispersió que acabem de veure presenta un incon-


venient: quan se surt d’un bloc, és necessari recórrer tota la taula i comprovar
per a cada identificador si hi ha una entrada al bloc del qual se surt, en el qual
cas cal esborrar-la de la seqüència (i també l’identificador si la seqüència queda
buida).

Tanmateix, com que normalment el nombre d’entrades no serà gaire gran, no


ho considerem un inconvenient greu i, per això, considerem satisfactòria l’es-
tratègia. La taula 5 ens mostra un resum del cost de cada operació, on N denota
el nombre màxim d’identificadors que hi pot haver en la taula en un moment
donat, i B indica el nombre màxim de blocs imbricats que hi pot haver.

Taula 5. Representació de la taula de símbols


mitjançant una taula de dispersió

Operació Cost

creació O(N)

entra O(1)

surt O(N)

declara O(1)

consulta O(1)

Per a trobar la solució, caldria donar forma tant a la funció com a l’organitza-
ció de la taula de dispersió. Elegim la funció de suma ponderada per convertir
l’identificador en enter, i la funció de mòdul per restringir l’enter a un àmbit.
Pel que fa a la primera, cal determinar el nombre de caràcters diferents vàlids;
pel que fa a la segona, cal determinar l’estratègia i el nombre esperat d’identi-
ficadors que pot emmagatzemar la taula en un moment donat.

Si suposem que les 26 lletres de l’alfabet anglès (majúscules i minúscules són


equivalents), els 10 dígits i el caràcter de subratllat són caràcters vàlids, tenim
un total de 37 caràcters diferents. Com a estratègia, elegim la més simple pos-
sible, la dispersió encadenada oberta. Pel que fa al màxim, difícilment ens cal-
dran més de 1.000 entrades a la taula, per la qual cosa elegim com a dimensió
de la taula un nombre primer proper a 1.000, per exemple, 1.023.

Per acabar, constatem que com que un identificador pot tenir un gran
nombre de caràcters (fins a 80 en moltes implementacions del traductor),
es pot produir sobreeiximent amb molta facilitat, i apliquem la tècnica ex- Vegeu la tècnica per a evitar el
sobreeiximent explicada al final del
subapartat 2.1.1 d’aquest mòdul.
plicada per evitar el sobreeiximent. En aquest cas, com b = 37 i suposem
que maxent = 231 – 1, el resultat és que la clau s’ha de partir en trossos de 5 bits.
Amb totes aquestes dades, ja podem completar la codificació en Java de la
taula de símbols.
© FUOC • PID_00161972 • Mòdul 6 42 El TAD Taula

uoc.ei.exemples.modul6.TaulaSimbolsImpl

...

public class TaulaSimbolsImpl implements TaulaSimbols {


private int nombreBlocsImbricats;
private TaulaDispersioSimbols taula;

public TaulaSimbolsImpl() {
nombreBlocsImbricats = 0;
taula = new TaulaDispersioSimbols();
}

public void entra() {


nombreBlocsImbricats++;
}

public void surt() {


Llista<String> idsAEliminar = new LlistaEncadenada<String>();
Iterador<String> ids = taula.claus();
while (ids.hiHaSeguent()) {
String id = ids.seguent();
Pila<PropietatsSimbol> pila = taula.consultar(id);
PropietatsSimbol props = pila.cim();
if (props.getBloc()==nombreBlocsImbricats) {
pila.desempilar();
if (pila.estaBuit())
idsAEliminar.afegirAlFinal(id);
}
}
ids = idsAEliminar.elements();
while (ids.hiHaSeguent()) {
String id = ids.seguent();
taula.esborrar(id);
}
}

public void declara(String id, Categoria categoria,Tipus tipus,int dimensio) {


id = id.toLowerCase();
PropietatsSimbol props = new PropietatsSimbol(nombreBlocsImbricats,
categoria,tipus,dimensio);
Pila<PropietatsSimbol> pila = taula.consultar(id);
if (pila == null) {
pila = new PilaEncadenadaImpl<PropietatsSimbol>();
taula.afegir(id, pila);
}
pila.empilar(props);
}
© FUOC • PID_00161972 • Mòdul 6 43 El TAD Taula

public PropietatsSimbol consulta(String id) {


id = id.toLowerCase();
Pila<PropietatsSimbol> pila = taula.consultar(id);
if (pila == null)
return null;
return pila.cim();
}
}

uoc.ei.exemples.modul6.TaulaDispersioSimbols

...
class TaulaDispersioSimbols extends TaulaDispersio<String,Pila<PropietatsSimbol>> {
private static final int MIDA_TAULA = 1023;

public TaulaDispersioSimbols() {
super(MIDA_TAULA);
}

protected int calcularIndexTaula(String clau) {


int hash = 0;
for(int i = 0;i<clau.length();i++)
hash+= ordinal(clau.charAt(i));
hash%= MIDA_TAULA;
return hash;
}

private int ordinal(char c) {


if (c==‘_’) return 0;
if (Character.isDigit(c)) return 1+c-’0’;
return 11+c-’a’;
}
}

uoc.ei.exemples.modul6.PropietatsSimbol

public class PropietatsSimbol {

public enum Categoria { PROGRAM, PROC, PARAM, VAR }


public enum Tipus { NO_DEFINIT, BOOLEAN, INT, REAL, CHAR }
© FUOC • PID_00161972 • Mòdul 6 44 El TAD Taula

private Categoria categoria;


private Tipus tipus;
private int dimensio;
private int bloc;

public PropietatsSimbol(int bloc,Categoria categoria,int dimensio) {


this.bloc = bloc;
this.categoria = categoria;
this.tipus = Tipus.NO_DEFINIT;
this.dimensio = dimensio;
}

public PropietatsSimbol(int bloc,Categoria categoria,Tipus tipus, int dimensio) {


this.bloc = bloc;
this.categoria = categoria;
this.tipus = tipus;
this.dimensio = dimensio;
}

public int getBloc() { return bloc; }


public Categoria getCategoria() { return categoria; }
public Tipus getTipus() { return tipus; }
public int getDimensio() { return dimensio; }
}
© FUOC • PID_00161972 • Mòdul 6 45 El TAD Taula

5. Les taules i els conjunts a la Java Collections


Framework

Atès que les taules i els conjunts són dos TAD de gran tradició, les biblioteques
d’estructures de dades més difoses els inclouen. Aquest és el cas de la Java Co-
llections Framewok (JCF), que ofereix tant interfícies com implementacions.

5.1. Interfícies per a les taules i els conjunts a la JCF

La JCF ofereix interfícies tant per a les taules com per als conjunts. La interfície
dels conjunts, Set, és hereva de Collection, i remarcablement només presenta
aquestes operacions heretades. Les operacions es poden classificar en tres grans
tipus:

1) Operacions bàsiques, que són les pròpies del TAD: add, remove, contains,
size, isEmpty i iterator.

2) Operacions massives, que bàsicament estenen les anteriors sobre grups


d’elements: addAll, removeAll, containsAll (rep contenidors com a paràmetres),
equals (compara el conjunt amb un objecte) i clear (per a destruir el contingut
del conjunt).

3) Operacions sobre vectors per a transferir el contingut d’un conjunt a un


vector: toArray.

Figura 23. Jerarquia d’interfícies i implementacions per als conjunts de la JCF


© FUOC • PID_00161972 • Mòdul 6 46 El TAD Taula

Pel que fa a la interfície de les taules, Map, no tenim aquesta relació d’herència
i presenta com a operacions:

1) Operacions bàsiques: put, remove, get, containsKey, containsValue, size i isEmpty.

2) Operacions massives: putAll (anàloga a addAll), equals i clear.

3) Operacions sobre col·leccions, que donen una aparença diferent a la taula:


keySet, que retorna les claus de la taula en forma de conjunt; values, que retor-
na els valors en forma de col·lecció (no pot ser un conjunt perquè hi poden
haver dades repetides), i entrySet, que retorna el conjunt d’entrades (parells
clau-valor) de la taula. Sobre aquests conjunts i col·leccions, es poden aplicar
posteriorment les operacions de recorregut amb iteradors.

Figura 24. Jerarquia d’interfícies i implementacions per a les taules de la JCF

5.2. Implementacions per a les taules i els conjunts a la JCF

La JCF ofereix dues implementacions tant per a les taules com per als conjunts. Vegeu les implementacions per a
taules i conjunts de la JCF per
L’una usa dispersió de forma similar a la presentada en aquest mòdul, i l’altra estructures arborescents en el mòdul
“Arbres de cerca” d’aquesta assignatura.
usa estructures arborescents. Les implementacions per dispersió s’anomenen
HashMap i HashSet, i implementen la interfície corresponent (Map o Set) i tam-
bé la Cloneable. Les jerarquies de les figures 23 i 24 mostren també l’existència
d’algunes classes abstractes, AbstractSet i AbstractMap, que s’usen per a imple-
mentar l’esquelet de les autèntiques implementacions, i de codificar tot allò
que no depèn de l’estratègia d’implementació.

És interessant destacar dos aspectes sobre la implementació: la interfície de les


operacions constructores i la definició de la funció de dispersió.
© FUOC • PID_00161972 • Mòdul 6 47 El TAD Taula

Les operacions constructores tenen dos paràmetres, tal com queda palès a la
seva interfície:

HashMap(int initialCapacity, float loadFactor)

El primer paràmetre indica la dimensió inicial de la taula (és a dir, la magnitud


que hem denotat r al llarg del mòdul), mentre que el segon paràmetre representa
el valor màxim permès de la taxa d’ocupació de la taula. Ja hem vist la importàn-
cia d’aquestes dues magnituds. Si s’ultrapassa la taxa màxima d’ocupació especi-
ficada, es redimensiona la taula al doble de posicions i es tornen a inserir tots els
elements amb la nova funció de dispersió adaptada a aquest canvi; és a dir, la JCF
no ofereix cap mena d’estratègia avançada de la dispersió dinàmica.

La classe Object ofereix un mètode anomenat hashCode que retorna un enter


per a qualsevol tipus d’objecte; l’enter no està restringit a cap interval. El con-
tracte d’aquesta operació assegura que el valor retornat per a un objecte sem-
pre serà el mateix. No s’indica com es calcula aquest valor, si bé normalment
el que es fa és convertir l’adreça de l’objecte en un enter. En principi, el con-
tracte també estableix que dos objectes que són iguals (comparats amb equals)
tenen el mateix hashCode, però això no sempre és així. Les implementacions
HashMap i HashSet criden a aquesta operació.

El mètode hashCode es pot redefinir utilitzant qualsevol estratègia com ara la


suma ponderada vista en aquest mòdul. De fet, el mateix Java ofereix dues re-
definicions a les classes String i Integer. A la classe String, s’usa la suma ponde-
rada amb l’única particularitat que la magnitud b que actua com a base de la
potència és sempre igual a 31, no al nombre de caràcters que poden aparèixer
a la clau; la funció conv es basa en el codi UNICODE. És interessant remarcar
que aquesta versió de funció de dispersió no controla el sobreeiximent en els
càlculs. Fins i tot podem obtenir valors negatius, de manera que calgui aplicar-
hi el valor absolut per a estar completament segurs que compleixen les premis-
ses de l’estratègia de dispersió. En la classe Integer, el valor de dispersió retornat
és el valor mateix de l’enter.

uoc.ei.exemples.modul6.jcf.TaulaSimbolsImpl

...
public class TaulaSimbolsImpl implements TaulaSimbols {
private int nombreBlocsImbricats;
private Map<String,Stack<PropietatsSimbol>> taula;

public TaulaSimbolsImpl() {
nombreBlocsImbricats=0;
taula=new TaulaDispersioSimbols();
}

public void entra() {


nombreBlocsImbricats++;
}
© FUOC • PID_00161972 • Mòdul 6 48 El TAD Taula

public void surt() {


Collection<String> idsAEliminar = new ArrayList<String>();
Iterator<String> ids=taula.keySet().iterator();
while (ids.hasNext()) {
String id=ids.next();
Stack<PropietatsSimbol> pila=taula.get(id);
PropietatsSimbol props=pila.peek();
if (props.getBloc()==nombreBlocsImbricats) {
pila.pop();
if (pila.empty())
idsAEliminar.add(id);
}
}
ids=idsAEliminar.iterator();
while (ids.hasNext()) {
String id=ids.next();
taula.remove(id);
}
}

public void declara(String id, Categoria categoria,Tipus tipus,int dimensio) {


id=id.toLowerCase();
PropietatsSimbol props = new PropietatsSimbol(nombreBlocsImbricats,
categoria,tipus,dimensio);
Stack<PropietatsSimbol> pila=taula.get(id);
if (pila==null) {
pila=new Stack<PropietatsSimbol>();
taula.put(id,pila);
}
pila.push(props);
}

public PropietatsSimbol consulta(String id) {


id=id.toLowerCase();
Stack<PropietatsSimbol> pila=taula.get(id);
if (pila==null)
return null;
return pila.peek();
}
}
© FUOC • PID_00161972 • Mòdul 6 49 El TAD Taula

uoc.ei.exemples.modul6.TaulaDispersioSimbols

...
class TaulaDispersioSimbols extends HashMap<String,Stack<PropietatsSimbol>> {
private static final int MIDA_TAULA = 1023;

public TaulaDispersioSimbols() {
super(MIDA_TAULA);
}
}
© FUOC • PID_00161972 • Mòdul 6 50 El TAD Taula

Resum

És indubtable que la representació de les taules mitjançant implementacions


per dispersió és molt útil en l’àmbit de les estructures de dades, perquè obte-
nim un cost asimptòtic molt bo en les operacions d’accés individuals. De fet,
mercès a la dispersió, hem generalitzat el concepte de vector propi de la majoria
de llenguatges de programació imperatius i orientats a objectes, i l’hem dotat
d’una flexibilitat més gran pel que fa a la dimensió i el tipus dels índexs. Això
no obstant, la dispersió presenta algunes característiques que, en alguns con-
textos, es poden considerar negatives:

1) El bon comportament de la dispersió depèn del bon comportament de la


funció de dispersió. Recordem que, per molt acurat que sigui el disseny d’aques-
ta funció, sempre pot passar que es comporti malament per al subconjunt con-
cret de claus a emmagatzemar en la taula. En resum, les taules de dispersió tenen
un cert component aleatori en el seu funcionament.

2) S’ha de determinar el nombre aproximat d’elements que s’espera guardar


en la taula, fins i tot quan no se’n té la més remota idea. Si la taula queda pe-
tita, cal ampliar-la, redefinir la funció de dispersió i reinserir els elements, o bé
usar mètodes més complexos com la redispersió; si queda gran, hi haurà espai
desaprofitat.

3) Cal efectuar una anàlisi per a decidir quina funció de dispersió i quina or-
ganització de dispersió són les adients per al context d’ús.

4) Si es vol ampliar el concepte bàsic de taula per a efectuar altres menes


d’operacions, podem tenir problemes. Un exemple clàssic és la necessitat de
llistar els elements de la taula seguint un ordre determinat, com ara l’ordre al-
fabètic.

Aquests inconvenients justifiquen que cerquem alguna altra estratègia d’im-


plementació de taules que els solucioni, fins i tot sacrificant alguna caracterís-
tica positiva de la dispersió, com ara el temps constant en les operacions.
Sobretot, ens interessa la possibilitat d’enriquir el model de les taules amb una En el mòdul “Arbres de cerca”, es
dóna una alternativa que aborda
aquests temes.
operació de llistat ordenat, una situació en què la dispersió es comporta mala-
ment.
© FUOC • PID_00161972 • Mòdul 6 51 El TAD Taula

Activitats

1. Doneu una especificació pre-post formal de les operacions sobre taules presentades en
l’apartat 1 d’aquest mòdul basada en el model matemàtic de funció. Com a exemple,
mostrem l’especificació de l’operació d’afegir:

t: Taula, a, b: Object
{ Pre: t = t0 ∧ a = a0 ∧ b = b0 }
t.afegir(a, b)
{ Post:
dom(t) = dom(t0) ∪ {a0} ∧ -- la clau a passa a formar part del domini real de t
t(a0) = b0 ∧ -- la clau a té b com a valor associat dins t
∀x: x∈dom(t) ∧ x ≠ a0: t(x) = t0(x)} -- la resta d’entrades de t segueixen com abans

2. Cerqueu altres propostes de funcions de dispersió en els textos d’estructures de dades i


comenteu-los breument.

3. Cerqueu altres propostes d’organitzacions de dispersió existents en els textos d’estructu-


res de dades i comenteu-les breument.

4. Doneu una especificació pre-post formal de les operacions sobre conjunts presentades en
l’apartat 3 d’aquest mòdul.

Exercicis d’autoavaluació

1. A partir de l’especificació de les taules de l’apartat 1:


a) Doneu l’especificació d’afegir per als casos en què si la clau ja és a la taula, no se’n can-
via el valor associat. Doneu-la també per al cas en què no es permet aquesta situació.
b) Doneu l’especificació d’esborrar quan s’exigeix que la clau per a esborrar sigui dins de
la taula.
c) Doneu l’especificació de consultar quan s’exigeix que la clau consultada és dins de la
taula.

2. Proposeu altres operacions per a les taules. Raoneu per què podrien ser interessants, pre-
feriblement donant-ne un context d’ús.

3. Apliqueu la funció de dispersió de la suma ponderada sobre les cadenes UOC, BOTIFLER i
CAPSIGRANY, suposant que els caràcters vàlids són les lletres majúscules de l’alfabet anglès.

4. Siguin unes claus que representen noms de ciutat (en majúscules i poden aparèixer gui-
ons, punts i espais en blanc) amb un màxim de 35 caràcters, i sigui un ordinador que re-
presenta els nombres enters amb 32 bits en la codificació de complement a 2. Volem
adaptar la funció de suma ponderada amb control de sobreeiximent a aquests supòsits.
Determineu el valor de maxent, b, k i m que defineixen aquesta funció.

5. Descriviu breument algunes funcions de dispersió adequades per als casos següents de
claus multiatribut:
a) nom i cognoms;
b) codi de caixa i número de compte;
c) districte postal, carrer i número.

6. La funció conv es defineix sobre l’abast [1, b]. Per què no es pot definir sobre l’abast [0, b − 1]?

7. Una alternativa a la implementació de les taules de dispersió obertes consisteix a no con-


trolar les possibles repeticions de clau en inserir un nou parell a la taula. Per això, simple-
ment es crea la nova entrada com a primer element de la llista de sinònims. Raoneu com
aquesta estratègia afecta el funcionament de la resta d’operacions i l’eficiència tant tem-
poral com espacial. Quan és avantatjós i quan no ho és fer servir aquesta variant?

8. Sigui un conjunt implementat amb una taula de dispersió d’adreçament obert de 7 posi-
cions en què les dades són nombres enters. Suposeu que la funció de dispersió és simple-
ment h(x) = x mod 7. Simuleu el comportament de la taula en efectuar la seqüència
d’operacions següent: inserció del 7, inserció del 17, inserció del 0, supressió del 7, inser-
ció del 22, inserció del 13, inserció del 20, supressió del 0. Considereu les tres alternatives
de redispersió següents:
a) lineal amb k = 1 i sense moviments a la supressió.
b) lineal amb k = 1 i moviments a la supressió.
c) doble amb g(x) = 1 + (x mod 5).
© FUOC • PID_00161972 • Mòdul 6 52 El TAD Taula

9. Sigui un conjunt implementat amb una taula de dispersió encadenada oberta de 7 posi-
cions, en què les dades són nombres enters. Suposeu que la funció de dispersió és simple-
ment h(x) = x mod 7. Suposeu que s’insereixen els enters següents en aquest ordre: 123,
49, 1, 22, 18, 23 i 100. Suposeu que el factor de càrrega permès a la taula és del 100%, de
manera que en inserir l’element 50 es produeix un sobreeiximent que es vol solucionar
doblant la dimensió de la taula. Mostreu la taula de dispersió resultant amb una nova tau-
la de 15 elements i funció de dispersió h(x) = x mod 15.

10. Volem implementar un diccionari amb operacions d’accés directe per paraula i recorre-
gut alfabètic. Suposeu que l’espai que ocupa cada entrada del diccionari (que conté la pa-
raula i la seva definició) és de X bits, un valor fix. Suposeu que el nombre esperat
d’entrades és N. Descriviu l’estructura de dades i les operacions necessàries per a oferir
una bona eficiència en els operacions d’accés directe per paraula. Determineu el cost tem-
poral asimptòtic de les diferents operacions del diccionari i l’espai usat (en bits i com a
funció de X i N), suposant que els enters i les adreces de memòria ocupen 32 bits.

11. Es vol implementar un dipòsit d’adreces web per a un navegador. El dipòsit consta
d’adreces URL exclusivament. Implementeu-lo mitjançant un conjunt. Feu-ne dues ver-
sions, amb la JCF i amb la biblioteca de classes de l’assignatura.

12. Es vol generar una aplicació de gestió dels alumnes i les assignatures d’una universitat. A
aquest efecte, els alumnes s’identifiquen pel seu DNI i se’n coneix el nom i cognoms,
l’adreça postal, l’adreça electrònica i l’edat. De les assignatures se’n sap el codi identifica-
dor, el nom i el nombre de crèdits. Es demana que implementeu aquestes dues estructu-
res com a taules de dispersió (les podeu encapsular mitjançant un nou TAD) i que feu un
programet de prova. A continuació, esteneu la implementació (i el TAD definit) per a in-
cloure-hi una operació per a matricular alumnes d’assignatures.
© FUOC • PID_00161972 • Mòdul 6 53 El TAD Taula

Solucionari

Activitats

1. En les precondicions, bàsicament es fixen els valors inicials dels objectes, és a dir, els va-
lors que tenen els objectes abans de l’execució de l’operació i totes aquelles condicions
que hagin de complir; mentre que les postcondicions mostren el canvi de valor d’aquests
objectes i referencien els valors inicials que apareixen en la precondició:

t: Taula, a, b: Object
{ Pre: t = t0 ∧ a = a0 }
b = t.esborrar(a)
{ Post:
dom(t) = dom(t0) – {a0} ∧ -- la clau a deixa de formar part del domini real de t
∀x: x∈dom(t): t(x) = t0(x) ∧ -- la resta d’entrades de t segueixen com abans
a0∈dom(t0) ⇒ b = t0(a0) ∧ -- si hi havia una entrada dins t amb clau a, es guarda a b el seu
valor associat
a0∉dom(t0) ⇒ b = null } -- altrament, b acaba valent null

t: Taula, a: Object; s: Boolean


{ Pre: t = t0 ∧ a = a0 }
s = t.hiEs(a)
{ Post: s = (a0∈dom(t0)) } -- retorna cert si a pertany al domini real de t, fals altrament

t: Taula, a, b: Object
{ Pre: t = t0 ∧ a = a0 }
b = t.consultar(a)
{ Post: a0∈dom(t0) ⇒ b = t0(a0) ∧ -- si hi havia una entrada dins t amb clau a, es guarda a b el
seu valor associat
a0∉dom(t0) ⇒ b = null } -- altrament, b acaba valent null

4.
c: Conjunt, x: Object
{ Pre: c = c0 ∧ x = x0 }
c.afegir(x)
{ Post: c = c0 ∪ {x0} }

c: Conjunt, x: Object
{ Pre: c = c0 ∧ x = x0 }
c.esborrar(x)
{ Post: c = c0 - {x0} }

c: Conjunt, x: Object, s: Boolean


{ Pre: c = c0 ∧ x = x0 }
s = c.hiEs(x)
{ Post: s = (x0∈c0) }

Exercicis d’autoavaluació

1. Donem també l’especificació formal per si heu fet l’activitat 1.


a) Primera variant

t: Taula, a, b: Object
{ Pre: no n’hi ha }
t.afegir(a, b)
{ Post: si la clau no era dins la taula t, el valor b passa a estar associat amb la clau a dins
de la taula t }

t: Taula, a, b: Object
{ Pre: t = t0 ∧ a = a0 ∧ b = b0 }
t.afegir(a, b)
{ Post: dom(t) = dom(t0) ∪ {a0} ∧ -- la clau a passa a formar part del domini real de t
a0∉dom(t0) ⇒ t(a0) = b0 ∧ -- la clau a té b com a valor associat dins t, si a no era dins t
∀x: x∈dom(t) ∧ x ≠ a0: t(x) = t0(x) } -- la resta d’entrades de t segueixen com abans

Segona variant:

t: Taula, a, b: Object
{ Pre: la clau a no és dins t }
t.afegir(a, b)
{ Post: el valor b passa a estar associat amb la clau a dins de la taula t }
© FUOC • PID_00161972 • Mòdul 6 54 El TAD Taula

t: Taula, a, b: Object
{ Pre: t = t0 ∧ a = a0 ∧ b = b0 ∧ a0∉dom(t0) }
t.afegir(a, b)
{ Post: dom(t) = dom(t0) ∪ {a0} ∧ -- la clau a passa a formar part del domini real de t
t(a0) = b0 ∧ -- la clau a té b com a valor associat dins t
∀x: x∈dom(t) ∧ x ≠ a0: t(x) = t0(x) } -- la resta d’entrades de t segueixen com abans

b)
t: Taula, a: Object
{ Pre: la clau a és dins t }
t.esborrar(a)
{ Post: elimina l’entrada amb clau a de la taula t }

t: Taula, a: Object
{ Pre: t = t0 ∧ a = a0 ∧ a0∈dom(t0) }
t.esborrar(a)
{ Post: dom(t) = dom(t0) – {a0} ∧ -- la clau a deixa de formar part del domini real de t
∀x: x ∈ dom(t): t(x) = t0(x) } -- la resta d’entrades de t segueixen com abans

c)
t: Taula, a, b: Object
{ Pre: la clau a és dins t }
b = t.consultar(a)
{ Post: retorna el valor associat a la clau a dins de la taula t }

t: Taula, a, b: Object
{ Pre: t = t0 ∧ a = a0 ∧ a0∈dom(t0) }
b = t.consultar(a)
{ Post: b = t0(a0) }

2. Podem trobar diverses propostes. Per exemple, si pensem en l’aplicació de les taules com a
taules de símbols, dues operacions pràctiques podrien ser obrirAmbit i tancarAmbit. La pri-
mera enregistraria que, durant el procés de traducció, s’entra en un nou àmbit dins del pro-
grama, és a dir, una nova classe, un nou mètode, etc. En obrir un nou àmbit, és lícit declarar
nous objectes que estiguin identificats per noms utilitzats en àmbits anteriors. En tancar
l’àmbit, haurien de desaparèixer de cop tots aquells identificadors de la taula introduïts en
l’àmbit.
Més genèricament, altres operacions que de vegades resulten útils són els recorreguts or-
denats. Ja hem dit que els recorreguts definits en el TAD de les taules obtenen els ele-
ments en un ordre no determinat, la qual cosa de vegades no és convenient. Hi ha força
contextos en què pot interessar trobar els elements ordenats alfabèticament (per nom,
per DNI, etc.) o numèricament.
També és necessari, ocasionalment, fusionar dues taules, és a dir, ajuntar les entrades de les
dues taules per a formar-ne una de nova. Òbviament, es requereix que els dominis i abastos
de les dues taules siguin els mateixos. L’especificació ha de determinar ben clarament què
passa quan una mateixa clau apareix en les dues taules i associada a un valor diferent en
cadascuna.

3. Mostrem els càlculs en la taula següent. Hi ha 10 columnes, una per cada caràcter del nom
(UOC en té 3; BOTIFLER, 8; i CAPSIGRANY, 10). Per cada cadena, mostrem: una fila per als
caràcters que la formen, la seva conversió amb la funció conv, la potència de 26 correspo-
nent, i el producte de cada sumand. El resultat SP es mostra en la darrera columna, SP(a).

k 1 2 3 4 5 6 7 8 9 10 SP(a)

a U O C

conv 21 15 3

bk-1 1 26 676

Σk 21 390 2028 2439

a B O T I F L E R

conv 2 15 20 9 6 12 5 18

bk-1 1 26 676 17576 456976 11881376 308915776 8031810176

Σk 2 390 13520 158184 2741856 142576512 1544578880 144572583168 146262652512

a C A P S I G R A N Y

conv 3 1 16 19 9 7 18 1 14 25

bk-1 1 26 676 17576 456976 11881376 308915776 8031810176 208827064576 5429503678976

Σk 3 26 10816 333944 4112784 83169632 5560483968 8031810176 2923578904064 135737591974400 138674850799810


© FUOC • PID_00161972 • Mòdul 6 55 El TAD Taula

4. Si considerem les 26 lletres majúscules i els tres caràcters addicionals, tenim b = 29.
El valor maxent és igual a 231 – 1 = 2147483647 (a partir de les característiques de la codificació
per complement a 2). Per als altres dos valors, hem de calcular-los a partir de la fórmula:

m × (Σj: 1 ≤ j ≤ k: bj) ≤ maxent

Anem donant valors amb una taula fins que ens passem:

k m k×m Σj: 1 ≤ j ≤ k: 29j m × (Σj: 1 ≤ j ≤ k: 29j)


2 18 36 870 15.660
3 12 36 25.259 303.108
4 9 36 732.540 6.592.860
5 7 35 21.243.689 148.705.823
6 6 36 616.067.010 3.696.402.060

És a dir, el k més gran és k = 5, la qual cosa significa que hem de partir la clau en, com a
mínim, 7 trossos (quan té la longitud màxima de 35 caràcters).

5.
a) En el primer cas, simplement concatenem el nom i els cognoms per obtenir una ca-
dena de caràcters sobre la qual apliquem la funció habitual (suma ponderada seguida
del’operació mòdul).
b) Si considerem que totes dues són nombres enters, tenim diverses opcions. Per exem-
ple, podem transformar els dos enters per separat i sumar-ne els resultats. També podem
formar un enter per la composició dels dos enters i aplicar el mòdul sobre el resultat.
També, podem considerar el número de compte com a cadena de caràcters, que aleshores
transformaríem en enter amb la suma ponderada i actuaríem com en el cas anterior. Fi-
nalment, tots dos camps es poden considerar caràcters i estaríem en la situació a.
c) Una vegada més, tenim diverses opcions. Podem considerar el districte postal com un
nombre o com una cadena de caràcters. En el segon cas, el podem combinar amb el carrer
per a tenir una única cadena a la qual aplicar la suma ponderada i combinar el resultat
amb el nombre seguint qualsevol de les opcions vistes en l’apartat b.
En els casos b i c, una alternativa diferent seria considerar dos nivells de tractament de les
dades. Per exemple, en el cas b podem tenir una primera estructura (que potser no caldria
que fos una taula de dispersió, perquè el nombre diferent de codis de bancs i caixes no és ex-
cessivament gran) que permeti a cada element (que representi un banc o caixa) tenir associ-
ada una taula de dispersió que emmagatzemi els comptes del banc o caixa, tal com mostra la
figura 25. En el cas c es pot fer un raonament similar aplicat als codis postals.

Figura 25. Representació jeràrquica de la taula

6. Perquè en aquest cas hi hauria claus que tindrien el mateix valor de suma ponderada SP.
En concret, si imaginem per un moment que, efectivament, conv es defineix sobre l’abast
[0, b – 1] i que les claus estan formades per lletres minúscules, és a dir b = 26, veurem que
SP(‘a’) = SP(‘aa’):

SP(‘a’) = conv(‘a’) = 0
SP(‘aa’) = conv(‘a’) + conv(‘a’) × 26 = 0
© FUOC • PID_00161972 • Mòdul 6 56 El TAD Taula

7. L’operació d’esborrar ha de recórrer tota la llista de sinònims, ja que la clau que identifica
l’entrada a esborrar pot estar repetida i limitar-nos a esborrar la primera entrada que tro-
bem no és suficient. Les operacions de consulta i comprovació d’existència funcionen
igual. Remarquem que l’operació de consulta ha d’esbrinar el valor de la primera entrada
que trobem, que és l’última inserció feta per la clau. Les operacions d’iteració són més
complicades, perquè cal evitar visitar les entrades corresponents a claus que han canviat
de valor associat per insercions posteriors. L’eficiència de totes les operacions –tret de la
inserció– empitjora perquè ara les llistes no tenen un nombre d’elements proporcional a
les claus existents, sinó a les insercions efectuades. Per la mateixa raó, l’eficiència espacial
és pitjor, lineal sobre el nombre d’insercions efectuades a la taula. Per això, aquesta estra-
tègia és especialment dolenta quan les taules s’actualitzen amb molta freqüència i, en
particular, quan una clau es modifica moltes vegades mentre està inserida dins de la tau-
la. En canvi, és bona quan una clau no es modifica mai, atès el context d’ús de la taula.
Per exemple, imagineu-vos una taula que guardés tots els alumnes graduats de la UOC.
Com normalment aquests alumnes, un cop inserits a la taula, no es modificarien mai,
aquesta estratègia seria millor que l’habitual.

8.
a)

9.
© FUOC • PID_00161972 • Mòdul 6 57 El TAD Taula

10. El diccionari es pot considerar un conjunt de paraules. L’estructura de dades és una taula
de dispersió que podem implementar de manera encadenada oberta. Les operacions són
les habituals: inserir una paraula al diccionari, esborrar-l’en quan ha quedat obsoleta, mi-
rar si existeix i obtenir-ne la definició. Totes aquestes operacions són O(1) suposant el
bon comportament de la funció de dispersió. L’espai usat d’una taula de dispersió enca-
denada oberta es calcula de la manera següent:

E(diccionari) = E(vector índex) + E(llistes de sinònims)


= 32N + (X + 32) · N --el vector índex té un apuntador a cada posició,
mentre que cada element de la llista té l’entrada
i un altre apuntador
= (X + 64) · N bits

11.
uoc.ei.exercicis.modul6.exercici11.DipositURLImpl

...
public class DipositURLImpl implements DipositURL {
private Conjunt<String> diposit;

public DipositURLImpl() {
diposit = new ConjuntAVLImpl<String>();
}

public void afegir(String url) {diposit.afegir(url); }


public boolean hiEs(String url) {return diposit.hiEs(url); }
public boolean esborrar(String url){ return diposit.esborrar(url)!= null; }
public Iterador<String> elements() {return diposit.elements(); }
}

uoc.ei.exercicis.modul6.exercici11.DipositURLJCLImpl

...
public class DipositURLJCFImpl implements DipositURL {
private Set<String> diposit;

public DipositURLJCFImpl() {
diposit = new HashSet<String>();
}

public void afegir(String url) {diposit.add(url); }


public boolean hiEs(String url) { return diposit.contains(url); }
public boolean esborrar(String url) {return diposit.remove(url); }
public Iterator<String> elements() {return diposit.iterator(); }
}

12.
uoc.ei.exercicis.modul6.exercici12.TaulaAlumnes

...
public class TaulaAlumnes extends TaulaDispersio<String,Alumne> {
public TaulaAlumnes() { super(1023); }
}

uoc.ei.exercicis.modul6.exercici12.TaulaAssignatures

...
public class TaulaAssignatures extends TaulaDispersio<String,Assignatura> {
public TaulaAssignatures() { super(257); }
}
© FUOC • PID_00161972 • Mòdul 6 58 El TAD Taula

uoc.ei.exercicis.modul6.exercici12.CursAcademicImpl

...
public class CursAcademicImpl implements CursAcademic {
TaulaAlumnes alumnes;
TaulaAssignatures assignatures;

public CursAcademicImpl() {
alumnes = new TaulaAlumnes();
assignatures = new TaulaAssignatures();
}

public void nouAlumne(Alumne alumne) {


alumnes.afegir(alumne.getDNI(),alumne);
}

public void novaAssignatura(Assignatura assignatura) {


assignatures.afegir(assignatura.getCodi(),assignatura);
}

public void matricular(String dniAlumne, int codiAssignatura) {


Alumne alumne = alumnes.consultar(dniAlumne);
Assignatura assignatura = assignatures.consultar(codiAssignatura);
alumne.afegirAssignatura(assignatura);
}

public Iterador<Assignatura> assignatures(String dniAlumne) {


Alumne alumne = alumnes.consultar(dniAlumne);
return alumne.assignatures();
}
}

Glossari

abast d’una funció m Vegeu funció.

adreçament obert m Mètode d’organització seqüencial d’una taula de dispersió, caracte-


ritzat per l’ús d’una funció de redispersió per a distribuir els elements en un vector.

apinyament m Fenomen present en les taules d’adreçament obert, consistent en la diferèn-


cia en la probabilitat d’ocupació de les posicions del vector.

camí d’una clau m Seqüència de valors de dispersió generada per una funció de redispersió
aplicada reiteradament sobre una clau.

clau f Vegeu funció.

clau invasora f En una taula de dispersió d’adreçament obert, clau que ocupa una posició
del vector diferent del seu valor de dispersió.

clau sinònima f Clau que té el mateix valor de dispersió que una altra.

col·lisió f Inserció d’una clau a en una taula en què ja existeix una altra clau amb el mateix
valor de dispersió que a.

dispersió f Noció general d’implementació de les taules que utilitza una funció de dispersió que
transforma les claus en valors de dispersió, i resol les col·lisions mitjançant una taula de dispersió.

domini d’una funció m Vegeu funció.

domini real d’una funció m Conjunt de claus del domini d’una funció per a les quals
existeix un valor associat.

funció f Relació establerta entre dos dominis de dades A i B tal que a cada element de A li
correspon com a molt un element de B. Les funcions de A a B es denoten per A → B. De A,
se’n diu domini de la funció, i de B se’n diu abast de la funció. Dels elements de A se’n diu
claus, i dels de B, valors.
© FUOC • PID_00161972 • Mòdul 6 59 El TAD Taula

funció de dispersió f Funció que transforma una clau en un valor de dispersió.

funció de dispersió primària f Vegeu funció de redispersió.

funció de dispersió secundària f Vegeu redispersió doble.

funció del mòdul f Funció de restricció d’un enter a un interval basat en l’aplicació de la
divisió entera.

funció de redispersió f Família de funcions de dispersió definides a partir del valor de dis-
persió retornat per una funció de dispersió primària.

índex m Vegeu funció.

informació f Vegeu funció.

organització de dispersió f Vegeu taula de dispersió.

redispersió f Noció general d’implementació de les taules d’adreçament obert usant una
funció de redispersió que transforma les claus en valors de dispersió.

redispersió doble f Categoria de funció de redispersió en què la família de funcions no de-


pèn tan sols de la funció de dispersió primària, sinó també d’una funció de dispersió secun-
dària.

redispersió lineal f Categoria de funció de redispersió en què la família de funcions depèn


exclusivament de la funció de dispersió primària.

sobreeiximent m Ocupació d’una taula de dispersió per sobre de la seva capacitat.

suma ponderada f Funció de transformació d’una cadena de caràcters en un enter basada


en la combinació de les transformacions individuals dels seus caràcters.

taula f Denominació del concepte de funció matemàtica en el context de les estructures de


dades.

taula de dispersió f Mètode de resolució de les col·lisions que es produeixen en una taula
de dispersió.

taula oberta f Mètode d’organització encadenada d’una taula de dispersió, caracteritzat per
l’ús d’un vector indexat pels valors de dispersió, de les posicions del qual pengen llistes de
claus sinònimes del valor de dispersió corresponent.

valor Vegeu funció.

valor de dispersió m Enter generat per les funcions de dispersió que serveix de mitjà d’ac-
cés a les taules de dispersió.

Bibliografia

Aho, A.; Hopcroft, J.; Ullman, J. (1998). Estructuras de datos y algoritmos. Wilmington:
Addison-Wesley Iberoamericana.

Cormen, T. H.; Leiserson, C. E.; Rivest, R. L.; Stein, C. (2001). Introduction to algorithms
(2a. ed.). MIT Press.

Franch, X. (1999). Estructures de dades: especificació, disseny i implementació (4a. ed.).


Barcelona: Edicions UPC. Disponible en línia a:
www.edicionsupc.es

Weiss, M. A. (1995). Estructuras de datos y algoritmos. Upper Saddle River: Addison-Wesley


Iberoamericana.

You might also like