Академический Документы
Профессиональный Документы
Культура Документы
19 de mayo de 2008
Siendo miembro de una
omunidad humana, a
laro que ninguna idea presentada en
este trabajo se ha gestado fuera de la tierra del Otro. Todo lo que sea que
omo idea
me haya apare
ido, ha su
edido gra
ias a todo lo que a lo largo de mi existen
ia me han
entregado otros
ongeneres.
A
larado lo anterior, juro por mi honor que este trabajo es personal,
ito sistemati
amente, en la
on
iente medida de mi
ons
iente
apa
idad, otros trabajos que
he utilizado, men
iono oportunamente a otros obrantes que han
ooperado y mi trabajo
no entra~na ningun tipo de plagio.
Prefacio
Quisiera que este texto se
onsiderase
omo un \vademe
um" en el dise~no efe
tivo y en
la programa
ion avanzada de algoritmos y de estru
turas de datos que soporten programas
omputa
ionales. Vademe
um es una palabra latina
onstruida mediante el imperativo latn vade que
onnota \ven", \anda" o \
amina" y me
um que signi
a \
onmigo". Ense~nar, que proviene del latn \insignare", es un verbo
ompuesto de \in" (en)
y \signare" (se~na); la expresion aun se emplea
uando se se~nala
on el dedo una senda
a seguir. Un vademe
um pretende ser, pues, una gua a \
aminar" por una senda de
aprendizaje; en este
aso, la de programa
ion de
omputadoras.
El que nos se~nalen un
amino o nos a
ompa~nen en su transito, no impli
a, para nada, ni
que lo transitemos enteramente ni que arribemos a su destino. Tal vez esta es la primera
y mas esen
ial ense~nanza que
omo estudiantes debemos asumir. En el mismo espritu
primigenio, los maestros deben
omprender que tanto el
amino
omo el destino son,
desde
ada la vision del mundo individual, dinami
os, variables. Por eso, los maestros
siempre deben bus
ar distintas maneras de ense~nanza y mejorar las existente.
Confe
ione este texto para usarse
omo gua de en un
urso de programa
ion. En
ese sentido, sugiero que
ursos en torno a este texto se di
ten leyendolo y
omentandolo
rti
amente. No puedo armar que esta sea la mejor y mas
elere manera de ense~nar.
Podra de
irse, de he
ho, que a la mayora de los estudiantes -y
olegas- no les ha gustado,
pero tambien podra de
irse, responsablemente, que bajo este modo es
uando mas han
aprendido. Di
ho esto, tengo que expresar serias dudas a
er
a de la efe
tividad de este texto
omo gua de ense~nanza y aprendizaje; pero
reo seriamente que desde varias perspe
tivas
han habido y habran programadores a las
uales les enrique
era.
Modo y medios
Para fa
ilitar el transito de un
amino, en nuestro
aso de aprendizaje, se emplean medios
bajo algun modo. Permtaseme introdu
ir el modo bajo la idea expresada en este antiqusimo proverbio oriental:
\Cuando es
u
ho, olvido,
Cuando veo, re
uerdo,
Cuando hago, entiendo."
La apropia
ion efe
tiva de un
ono
imiento o
urre
uando este se \entiende". En un pro
eso de ense~nanza, esto se torna realidad
uando un aprendiz
onsuma la he
hura de
osas
ara
tersti
as de su pra
ti
a ha
iendolas el mismo. Di
ho de otra manera, el modo de
ense~nanza es mostrar enteramente
omo se realiza una estru
tura de dato y un algoritmo.
iii
iv
Bajo este modo, intentare guiar a un poten
ial le
tor y aprendiz por el
amino de la
pra
ti
a de la programa
ion avanzada. Para ello empleare algunos medios.
Palabra
Para representar los algoritmos y las espe
i
a
iones de estru
turas de datos se emplea el
lenguaje de programa
ion C++.
Esta de
ision no se tomo sin dignas obje
iones, las
uales, resumidamente, se pueden
lasi
ar en dos grupos. El primero
uestiona el eventual des
ono
imiento del le
tor sobre
el lenguaje C++. A esto debo repli
ar que, no solo C++ es uno de los lenguajes mas populares e importantes de la programa
ion de sistemas, sino que su sintaxis y semanti
a son
reminis
entes a la mayora de los otros lenguajes pro
edurales y a objetos. De he
ho, el
quorum a
tual de los lenguajes de programa
ion pro
edurales se inspira en C++ o en su
pre
ursor, el lenguaje C; por instan
ias, java, python, perl, C# y D.
El segundo tipo de obje
ion denun
ia que la
omprension se torna mas di
ultosa, a
la
ual repli
o
on dos argumentos. El primero es que los lenguajes de programa
ion se
pensaron, tambien, en un estilo \
oloquial" respe
to a la programa
ion. Consideremos,
por ejemplo, el siguiente seudo programa en un seudo lenguaje
astellano:
1
m = m/2;
de lo contrario
m = 2*m
fin si
Fin repita mientras
el
ual es equivalente a la tradu
ion literal, \
oloquial", del siguiente bloque en C++:
while (m > 0 and m < n)
{
if (a[m] < a[x])
m = m/2;
else
m = 2*m;
}
Aunque un ejemplo no basta para generalizar mi defensa sobre el uso del lenguaje C++, el
he
ho es que
ualquiera que
onsidere seriamente la programa
ion tendra que programar y
esto debera ha
erlo en un lenguaje de programa
ion el
ual, para bien o mal, fue pensado
en lengua inglesa. Por tanto, inevitablemente, un aprendiz hispano parlante debera programar en un lenguaje de programa
ion \anglizado". He
ha la a
ota
ion anterior, tambien
podemos de
ir que el programa en
astellano y su equivalente en C++ tienen el mismo
signi
ado. As pues, es dudoso,
uando menos, armar que la
omprension se torna mas
di
ultosa.
Una bondad que se atribuye al uso de un seudo lenguaje es que este es independiente de
la implementa
ion. En mi
riterio, esto es par
ialmente
orre
to. Por ejemplo, las plantillas
en C++ plantean un problema de \portatibilidad" en otros lenguajes que no las tienen.
Alguien pudiera adu
ir que el uso de plantillas son
rpti
as para el aprendiz. Pero esta
obje
ion solo tiene sentido si no se
omprende el
on
epto y n de una plantilla,
ual no es
otro que la generi
idad. Entendido esto, un programa
on plantillas es tan o mas generi
o
que uno realizado en un seudo lenguaje; y resulta que <este es el prin
ipal argumento de
quienes deenden la ense~nanza de programa
ion en un seudo lenguaje!. Por otra parte,
de todos modos, si se usase un seudo lenguaje, enton
es tambien habra que plantearse el
problema de tradu
ir a un lenguaje de programa
ion real . Mi segundo argumento estriba
en que, habida
uenta de la popularidad y trans
enden
ia del C++,
reo que es mas fa
il
tradu
ir un programa bien estru
turado en C++ ha
ia otro lenguaje, por ejemplo, java,
que uno realizado en un seudo lenguaje .
2
Biblioteca ALEPH
Este texto
ontiene, en s mismo, una implementa
ion
on
reta y
ompleta de una bibliote
a, libre, llamada ALEPH,
ontentiva de todas las estru
turas de datos y algoritmos
tratados en este texto
2 Una tradu
i
on
on mas esfuerzo si se trata de un seudo lenguaje en
astellano.
3 De he
ho, mu
hos textos de programa
i
on apare
en en diferentes edi
iones para
distintos lenguajes.
En mu
hos de estos
asos, el autor es
ribe los programas en un solo lenguaje y luego emplea tradu
tores
ha
ia el otro lenguaje. Tales son los
asos de Sedgewi
k o Weiss y sus series en C, C++ y java
vi
La le
tura asegura, al menos, la le
tura del fuente de la bibliote
a y revela aspe
tos
de su instrumenta
ion. Salvo errores aun no des
ubiertos o mejoras que
ualquiera desee
proponer, el le
tor tiene la posibilidad de apoyarse en una implanta
ion
on
reta que le
fa
ilite la apropia
ion de sus
ono
imientos.
Los
odigos fuentes de ALEPH estan disponibles en:
ftp:://ftp.ula.ve/pub/aleph
vi
En la elabora
ion de este texto se utilizo un sistema llamado noweb [16, 11, el
ual es
un sistema programado que genera este texto y los fuentes en C++ a partir de un solo
ar
hivo \fuente". El \fuente" entrelaza prosa expli
ativa
on \bloques de
odigo" de la
implanta
ion de la bibliote
a.
Este estilo de es
ritura de programas se denomina \programa
ion literaria" y fue propuesto por Knuth [12. La idea es presentar un estilo mas
oloquial para es
ribir programas
que el mero estilo deformado de un lenguaje de programa
ion, junto
on la ganan
ia de que
la do
umenta
ion y la ultima version del programa (presumiblemente
orre
ta) residan en
un solo fuente.
Aparte de
odigo en C++, los bloques pueden
ontener referen
ias a otros bloques. Una
deni
ion de bloque
omienza por su nombre entre parentesis angulares. Por ejemplo,
onsideremos determinar si un numero n es o no primo. Para ello, podemos denir el
siguiente bloque:
hCal
ular si n es primo vii
const int ra
z_de_n = static_cast<int>(ceil(sqrt(n)));
for (int i = 2; i < ra
z_de_n; ++i)
if (n % i == 0)
// n no es primo
// n es un n
umero primo
Los bloques se enumeran segun el numero de la pagina donde se denen por primera
vez. Si hay mas de un bloque en una pagina, enton
es a este se le a~nade una letra que, en
el orden alfabeti
o, se
orresponde
on su orden de apari
ion. Algunos bloques referen
ian
a otros bloques o variables.
vii
Los bloques estan es
ritos en un orden que \se
ree" es mejor para la
omprension que
el mero listado de
odigo. Agrade
ere toda
rti
a que se me pueda ha
er sobre el estilo
y orden de presenta
ion de una estru
tura de dato o algoritmo en programa
ion literaria,
pero le soli
ito al
rti
o un esfuerzo primigenio por
omparar el estilo noweb
on el listado
plano de
odigo y, enton
es, bajo esa
onsidera
ion, ejer
er su
rti
a.
Ejercicios
Cualquiera sea la pra
ti
a, no hay otra manera de que un pra
ti
ante
onsume sus
ono
imiento que no sea \ha
iendo pra
ti
a". Por mas esfuerzo que se reali
e por orientar
y ense~nar, el aprendiz debe, preferiblemente guiado por un maestro, ejer
itar por s solo
lo aprendido.
Hay tres maneras, que no deben ser ex
luyentes, de realizar lo anterior:
1. Resolu
ion de ejer
i
ios propuestos: en este sentido, al nal de
ada
aptulo se
presenta un
onjunto de ejer
i
ios destinados, primordialmente, a la resolu
ion en
solitario por parte del aprendiz.
Algunos ejer
i
ios son \teori
os" en el sentido de que no ne
esariamente requieren
es
ribir un programa
ompilable. Por supuesto, no esta prohibido sentarse frente al
omputador e intentar
on
retar el ejer
i
io mediante un programa. Otros ejer
i
ios
son pra
ti
os y
onsisten en extensiones a la bibliote
a. Estos ejer
i
ios son distinguibles de los teori
os porque enun
ian su resultado en terminos de un objeto de la
bibliote
a.
Los ejer
i
ios estan
lasi
ados segun una di
ultad subjetiva juzgada por m. No
es fa
il ponderar el tiempo de resolu
ion de un ejer
i
io, pues este depende del
estudiante, pero he aqu mi
lasi
a
ion:
(a) Ninguna
ruz denota a un ejer
i
io
onsiderado sen
illo. Los teori
os deberan
de resolverse en el orden de un minuto, mientras que los pra
ti
os en el de un
da.
(b) Una
ruz (+) expresa un tiempo estimado de una a dos horas en el
aso de un
ejer
i
io teori
o y de dos das en el pra
ti
o.
(
) Dos
ru
es (++) expresan ya una di
ultad mu
ho mayor. Por lo general, esta
lase de ejer
i
ios plantean al aprendiz un des
ubrimiento o revela
ion de un
\tru
o" o \te
ni
a" que, una vez revelada, debe redu
ir la
omplejidad del
ejer
i
io a ninguna
ruz.
El tiempo de un ejer
i
io teori
o debe ser al menos de un da, mientras que el
de uno pra
ti
o de tres (3) das.
(d) Tres
ru
es (+++) representan una ejer
i
io de elite, dif
il, aun, para un maestro y su tiempo de resolu
ion no esta delimitado.
2. Realiza
ion de ejer
i
ios guiados en laboratorio: uno de los grandes obsta
ulos que
plantea lo abstra
to al aprendiz -y uno dira que ello o
urre en
ualquier pra
ti
a- es
su
ondi
ion novi
ia le di
ulta a
eptar que lo que para el es en prin
ipio abstra
to
devendra, a traves del ejer
i
io,
on
reto.
viii
ix
aprendi
es reali
en los proye
tos por s mismos, ni nuestra
ultura nos propor
iona la
fuerza de
ara
ter ne
esaria para emprender la realiza
ion de un proye
to. Por otra parte,
omo ense~nantes, tambien en el
ontexto parti
ular de la Universidad de Los Andes, nos
hemos degradado; impartimos
ursos sin disponer de la autoridad pra
ti
a para ha
erlo
y otorgamos honores y ttulos a quienes
laramente no los mere
en. La razon por la
ual
tengo que in
luirme en este drama es que he fra
asado rotundamente en en
ontrar una
manera de resolver estos dramas y de ser justo; esto es, pre
isa y re
urrentemente, lo que
mas me merma mi autoridad
omo ense~nante.
Historia
Comen
e la elabora
ion de la bibliote
a ALEPH en mayo de 1998. En aquel enton
es me
onsagre a es
ribir
lases de objetos para representar listas (las jerarquas Slink y Dlink),
arboles binarios (las jerarquas BinNode<Key> y Avl Tree<Key>) y tablas hash (la
lase
LhashTable<Key>). En aquel enton
es, solo dise~
ne y
odique.
Partes de este texto
omenzaron a apare
er a mediados de 1999, luego de que algunos
estudiantes me observasen que les sera util leer algoritmos en
odigo y que estos fuesen
bien
omentados. Fue enton
es
uando apele a noweb,
uya magistral efe
tividad ya haba
ono
ido
uando, estudiando genera
ion dinami
a de
odigo, me fue oportunsimo leer el
libro de
ompiladores de Hanson y Fraser [9. En ese tiempo es
rib parte de lo que hoy
es el
aptulo 2,
on
erniente a se
uen
ias. A mediados del 2000
omen
e el
aptulo 4
sobre arboles y parte del 5, referente a las tablas hash. El
aptulo 4 ha tenido bastantes
modi
a
iones a su
ontenido original y a~nadiduras, o
urridas, en su mayor parte, en el
largo perodo
omprendido entre 2001 y 2004.
En noviembre del 2001 reda
te,
asi enteramente, lo que a
tualmente es el
aptulo 6
sobre equilibrio de arboles.
A mediados del 2004 ini
ie la extension de la bibliote
a ha
ia grafos; tema presentado
en el
aptulo 7. Las estru
turas y algoritmos en torno a este dominio han variado bastante
en forma y no ha sido, hasta agosto del 2007, en que han tomado una version estable. Creo
que en este dominio es donde este texto realiza sus prin
ipales aportes.
Desde febrero de 2006 me plantee el esfuerzo de revisar y uni
ar los
aptulos bajo
lo que hoy
onforma este texto. Es
rib enteramente los
aptulos 1, sobre abstra
ion de
datos, y el 3,
on
erniente a la
rti
a de algoritmos y estru
turas de datos.
El septiembre de 2007
ulmine el
aptulo 5 referente a las tablas hash.
Planteada la historia, se puede expli
ar, mas no, por supuesto, justi
ar, el
ara
ter
fragmentado del texto.
Puedo de
ir que los
aptulos 1, 3 y 6 ya se en
uentran en una forma a
eptable a mis
riterios y que no aspiro modi
arlos substan
ialmente en un futuro proximo.
Es muy posible que en otra edi
ion tenga que realizar algunas transforma
iones sobre
el
aptulo 2 que lo hagan mas legible y a
orde al estilo de la mayora del texto. Pienso,
sin embargo, que sobre su
ontenido y fondo no habran modi
a
iones signi
ativas.
Tambien es probable que sobre el
aptulo sobre arboles (4) deban realizarse algunas
peque~nas modi
a
iones.
La mayora de las guras de este libro fueron generadas automati
amente mediante
programas elaborados
on la propia bibliote
a ALEPH. En ese sentido, hay tres programas:
xi
nomina nobook, fue es
rito tambien en flex y elimina, para la salida L TEX, los bloques
delimitados.
A
Deudas
Subya
ente a la idea de autor se es
onde una de las mas grandes fala
ias y trampas de esta
tiempo: el dere
ho de autor y, mas alla y aberrante aun, la \propiedad industrial". Por
mas
ara
ter de primigenia que pueda tener
ualquier obra, esta se
ir
uns
ribe gra
ias a,
y para, una
ultura. La primera deuda, pues, que
ualquier individuo adquiere
on una
obra es ha
ia su
ultura, la
ual le entrega el trasfondo
ir
unstan
ial, en
ono
imientos
y sentimientos y que posibilitan e inspiran la obra en
uestion. En este mismo espritu,
una vez entregada la obra, la segunda deuda adquirida es ha
ia la
ultura que re
ibe y
re
ono
e la obra.
Parafraseando a Ortega y Gasset, uno es lo que es en la medida de sus
ir
unstan
ias,
pero,
ualesquiera sean, en estas, sin duda alguna, siempre se en
uentra la in
uen
ia
abrumadora del otro. Me siento, pues, en fran
a deuda ha
ia aquellos parti
ulares a los
que siento les debo lo que soy y, en lo parti
ular de este texto, ha
ia aquellos que in
idieron
muy dire
tamente en su elabora
ion.
xii
V
tor Bravo, Carlos Nava, Juan Lus Chaves y Juan Carlos Vargas fueron mis primeros
dis
pulos en esta y el area de sistemas distribuidos. V
tor instrumento la primera version
de la
lase LinearHashTable<Key> presentada en x 5.1.7. Carlos instrumento la primera
version de un sistema
omuni
a
ional de envergadura sustentado en el uso de ALEPH; el
sistema aun es operativo hoy en da. Juan Carlos instrumento los arboles AVL hilados y
on rangos, los
uales, si bien no estan presentes en este texto, su instrumenta
ion ayudo
a mejorar y depurar la
lase Avl Tree<Key>.
Andres Ar
ia fue un usuario intensivo de ALEPH durante la realiza
ion de su tesis de
maestra, lo que me permitio ver aspe
tos que luego in
idieron en extensiones y mejoras
de la bibliote
a.
Leonardo Zu~niga, Bladimir Contreras y Carlos A
osta realizaron la treepic, pre
ursor
de btreepic un programa para dibujar los arboles binarios de este libro. Jose Brito es
ribio
xtreepic, pre
ursor de ntreepic, usado para dibujar arboles y arbores
en
ias generales.
Jorge Redondo y Tomas Lopez realizaron las primeras pruebas de desempe~no sobre los
diversos arboles binarios de busqueda.
Jesus San
hez realizo parte de la implanta
ion par
ial de la bibliote
a estandar C++
bajo ALEPH. En pruebas de desempe~no tradi
ionales, la bibliote
a estandar bajo ALEPH
es de mejor desempe~no que la de GNU.
Juan Fuentes instrumento parte de y depuro la
lase generi
a de arbol Tree Node<T>.
Orlando Vi
u~na en
ontro errores importantes en los arboles y planteo algunas sugeren
ias
muy apre
iables sobre el estilo de implanta
ion.
Mabel Hernandez y Milton Vera implantaron la primera version de los algoritmos
geometri
os de plani
a
ion de movimiento de la bibliote
a ALEPH.
Jose Ruz implanto los fundamentos de las estru
turas de datos y algoritmos de geometra
omputa
ional.
Arstides Castillo, Derik Romero y mi persona instrumentamos las versiones base de
los grafos
on
urrentes, los grafos
on agentes y los grafos de simula
ion.
En la
onfe
ion de este texto se han empleado enteramente programas libres:
L TEX [13, TEX [18, noweb, BIBTEX [4, gnu make [5, imake [10, gnuplot [8, R [15,
Maxima [14, Xfig [20, dia [6, Umbrello [19, doxygen [7, bcpp [3, entre otros. Este
texto y los programas fueron editados
on gnu Emacs [2. Los programas fueron manejados
on todos los utilitarios GNU [1.
Algunas ve
es, al mirar algunas a
titudes en dis
pulos,
reo notarles algunas de mis
ense~nanzas; lo que evo
a la lejana posibilidad de mi impronta. Pero a ese tenor debo a
larar
que soy yo, mas bien, quien porta sus le
iones y, por tanto, quien les expresa gratitud.
Mis padres han
ontribuido, uno dira indire
tamente, pero, >quien sabe? a lo que
me atribuira
omo una sensibilidad parti
ular ha
ia la te
nologa. Mi amadsima madre,
Nelly, leyo enteramente este tras
rito.
Desde que na
en el mundo a
ademi
o, he observado que
asi todo autor de libro texto
te
ni
o expresa agrade
imientos a su familia (esposo(a) e hijo(a)(s)). En el trans
urso de
esta edi
ion me per
ate de que ello probablemente obedez
a a que a ellos, en mi
aso
on
des
arada e irresponsable negligen
ia, uno les olvida. Por razones muy ntimas, para nada
te
ni
as, no puedo medir ni expresar
omo y
uanto soy gra
ias a mi esposa, Magdiel, pero
s puedo
lamar que no sera nada sin ella. Sea pues
on y ha
ia ella, por su amor y su
perdon, mi mayor y prin
ipal deuda ... y gratitud.
A
0.0. Bibliografa
xiii
Bibliografa
[1 http://www.gnu.org.
[2 http://www.gnu.org/software/ema
s/.
[3 http://invisible-island.net/b
pp.
[4 http://www.
tan.org.
[5 http://www.
make.org.
[6 http://www.gnome.org/proje
ts/dia.
[7 http://www.doxygen.org.
[8 http://www.gnuplot.info.
[9 David R. Hanson and Christopher W. Fraser. A Retargetable C Compiler: Design
and Implementation. Addison Wesley, 1995.
[10 http://xorg.freedesktop.org.
[11 Andrew L. Johnson and Brad C. Johnson. Literate programming using noweb. Linux
Journal, pages 64{69, o
tober 1997.
[12 Donald E. Knuth. Literate programming. The Computer Journal, 27(2):97{111,
1984.
[13 http://www.
tan.org.
[14 http://maxima.sour
eforge.net.
[15 www.http://r-proje
t.org.
[16 Norman Ramsey. Literate programming simplied. IEEE Software, 11(5):97{105,
September 1994.
[17 Kozo Sugiyama. Graph Drawing and Appli
ations for Software Engineering and
Knowledge Engineers, volume 11 of Software Engineering and Knowledge Engineering. World S
einti
, 2002.
[18 http://tug.org/tetex/.
[19 http://www.kde.org.
[20 http://www.xg.org.
Contenido
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
1 Abstracci
on de datos
2 Secuencias
2.1 Arreglos . . . . . . . . . . . . . . . . . .
2.1.1 Opera
iones basi
as
on Arreglos
2.1.1.1 Aritmeti
a de punteros
2.1.1.2 Busqueda por
lave . .
2.1.1.3 Inser
ion por
lave . . .
xv
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
5
6
7
10
11
12
13
13
14
15
16
18
18
20
20
20
21
21
21
22
23
24
25
26
27
29
30
31
31
32
33
xvi
2.2
2.3
2.4
2.5
2.6
CONTENIDO
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
35
35
35
35
36
44
44
45
52
55
66
70
71
73
75
78
79
83
86
87
88
90
106
108
108
112
113
116
120
122
129
130
131
134
136
138
145
147
149
155
156
156
156
157
163
CONTENIDO
xvii
enlazadas)
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
166
167
170
171
180
183
185
187
188
189
193
194
196
199
199
202
205
206
207
208
209
209
211
212
212
212
213
214
216
217
219
221
222
223
225
225
225
227
229
231
233
234
235
236
236
xviii
4 Arboles
CONTENIDO
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
237
238
246
249
256
260
261
261
262
263
263
264
265
269
273
276
279
279
279
281
281
282
282
283
284
285
285
290
298
299
301
304
305
305
306
307
307
307
308
310
311
312
315
318
322
CONTENIDO
4.6
4.7
4.8
4.9
4.10
xix
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
326
327
331
331
333
334
335
336
338
340
340
342
345
347
350
351
354
355
356
359
360
365
366
367
368
368
369
372
372
372
377
378
382
384
385
386
387
389
391
392
394
396
398
399
400
404
xx
CONTENIDO
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5 Tablas hash
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
408
411
412
412
413
414
414
415
416
417
418
418
419
420
421
423
427
428
433
436
439
442
442
444
445
449
450
463
465
467
467
469
469
469
474
477
479
481
481
484
488
491
492
498
499
CONTENIDO
xxi
6 Arboles
de b
usqueda equilibrados
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Record> )501
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
503
504
509
510
511
511
515
515
515
516
517
517
519
521
522
523
524
524
526
527
538
539
541
545
546
550
550
551
553
555
559
561
563
564
567
568
568
570
572
577
583
584
588
590
xxii
CONTENIDO
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7.1 Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.2 Estru
turas datos para representar grafos . . . . . . . . . . . . .
7.2.1 Matri
es de adya
en
ia . . . . . . . . . . . . . . . . . . .
7.2.2 Listas de adya
en
ia . . . . . . . . . . . . . . . . . . . . .
7.3 Un TAD para grafos (List Graph<Node, Arc>) . . . . . . . . .
7.3.1 Grafos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3.2 Digrafos (List Digraph<Node, Arc>) . . . . . . . . . .
7.3.3 Nodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3.3.1 Inser
ion de nodos . . . . . . . . . . . . . . . . .
7.3.3.2 Elimina
ion de nodos . . . . . . . . . . . . . . .
7.3.3.3 A
eso a los nodos de un grafo . . . . . . . . . .
7.3.4 Ar
os . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3.4.1 Inser
ion de ar
os . . . . . . . . . . . . . . . . .
7.3.4.2 Elimina
ion de ar
os . . . . . . . . . . . . . . . .
7.3.4.3 A
eso a los ar
os de un grafo . . . . . . . . . .
7.3.5 Atributos de
ontrol de nodos y ar
os . . . . . . . . . . .
7.3.5.1 Bits de
ontrol . . . . . . . . . . . . . . . . . . .
7.3.5.2 Contadores . . . . . . . . . . . . . . . . . . . . .
7.3.5.3 Cookies . . . . . . . . . . . . . . . . . . . . . . .
7.3.5.4 Reini
io de nodos y ar
os . . . . . . . . . . . . .
7.3.6 Ma
ros de a
eso a nodos y ar
os . . . . . . . . . . . . . .
7.3.7 Constru
ion y destru
ion de List Graph<Node, Arc>
7.3.8 Implanta
ion de List Graph<Node, Arc> . . . . . . . .
7.3.8.1 A
eso a nodos y ar
os . . . . . . . . . . . . . .
7.3.8.2 Ar
os . . . . . . . . . . . . . . . . . . . . . . . .
7.3.8.3 Inser
ion de nodos . . . . . . . . . . . . . . . . .
7.3.8.4 Inser
ion de ar
os . . . . . . . . . . . . . . . . .
7.3.8.5 Elimina
ion de ar
os . . . . . . . . . . . . . . . .
7.3.8.6 Elimina
ion de nodos . . . . . . . . . . . . . . .
7.3.8.7 Limpieza de grafos . . . . . . . . . . . . . . . . .
7.3.8.8 Mapeo de nodos y ar
os . . . . . . . . . . . . . .
7.3.8.9 Copia de grafos . . . . . . . . . . . . . . . . . .
7.3.8.10 Constru
ion y asigna
ion de grafos . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
591
593
598
605
609
612
617
623
627
629
635
637
639
644
644
647
648
649
650
651
653
653
653
657
658
659
659
662
662
666
667
669
670
671
672
673
675
681
682
683
684
685
686
687
688
CONTENIDO
7.3.8.11 Ordenamiento de ar
os . . . . . . . . . . . . .
7.4 TAD
amino sobre un grafo (Path<GT>) . . . . . . . . . . . . .
7.5 Re
orridos sobre grafos . . . . . . . . . . . . . . . . . . . . . . .
7.5.1 Re
orrido en profundidad . . . . . . . . . . . . . . . . .
7.5.2 Cone
tividad entre grafos . . . . . . . . . . . . . . . . .
7.5.3 Re
orrido en amplitud . . . . . . . . . . . . . . . . . . .
7.5.4 Prueba de
i
los . . . . . . . . . . . . . . . . . . . . . .
7.5.5 Prueba de a
i
li
idad . . . . . . . . . . . . . . . . . . .
7.5.6 Busqueda de
aminos por profundidad . . . . . . . . . .
7.5.6.1 Prueba de existen
ia . . . . . . . . . . . . . . .
7.5.6.2 Busqueda de
amino entre dos nodos . . . . .
7.5.7 Busqueda de
aminos por amplitud . . . . . . . . . . . .
7.5.8 Arboles
abar
adores de profundidad . . . . . . . . . . .
7.5.9 Arboles abar
adores de amplitud . . . . . . . . . . . . .
7.5.10 Conversion de un arbol abar
ador a un Tree Node<T> .
7.5.11 Componentes in
onexos de un grafo . . . . . . . . . . .
7.5.12 Puntos de arti
ula
ion de un grafo . . . . . . . . . . . .
7.5.13 Componentes
onexos de los puntos de
orte . . . . . .
7.5.13.1 Pintado de
omponentes
onexos . . . . . . . .
7.5.13.2 Copia mapeada de
omponentes
onexos . . .
7.6 Matri
es de adya
en
ia . . . . . . . . . . . . . . . . . . . . . . .
7.6.1 El TAD Map Matrix Graph<GT> . . . . . . . . . . . . .
7.6.2 El TAD Matrix Graph<GT> . . . . . . . . . . . . . . .
7.6.3 El TAD Ady Mat<GT, Entry> . . . . . . . . . . . . . .
7.6.4 El TAD Bit Mat Graph<GT> . . . . . . . . . . . . . . .
7.6.5 Algoritmo de Warshall . . . . . . . . . . . . . . . . . . .
7.7 Arboles abar
adores mnimos . . . . . . . . . . . . . . . . . . .
7.7.1 A
eso a los pesos del grafo . . . . . . . . . . . . . . . .
7.7.2 Algoritmo de Kruskal . . . . . . . . . . . . . . . . . . .
7.7.2.1 Analisis del algoritmo de Kruskal . . . . . . .
7.7.2.2 Corre
titud del algoritmo de Kruskal . . . . .
7.7.3 Algoritmo de Prim . . . . . . . . . . . . . . . . . . . . .
7.7.3.1 Interfa
es del algoritmo de Prim . . . . . . . .
7.7.3.2 Heap ex
lusivo de ar
os mnimos . . . . . . . .
7.7.3.3 Ini
ializa
ion del algoritmo de Prim . . . . . .
7.7.3.4 El algoritmo de Prim . . . . . . . . . . . . . .
7.7.3.5 Analisis del algoritmo de Prim . . . . . . . . .
7.7.3.6 Corre
titud del algoritmo de Prim . . . . . . .
7.8 Caminos mnimos . . . . . . . . . . . . . . . . . . . . . . . . . .
7.8.1 Algoritmo de Dijkstra . . . . . . . . . . . . . . . . . . .
7.8.1.1 Distan
ia a
umulada en los nodos . . . . . . .
7.8.1.2 Poten
ial en los ar
os . . . . . . . . . . . . . .
7.8.1.3 El algoritmo de Dijkstra . . . . . . . . . . . .
7.8.1.4 Cal
ulo de un
amino mnimo . . . . . . . . .
7.8.1.5 Corre
titud del algoritmo de Dijkstra . . . . .
7.8.1.6 Analisis del algoritmo de Dijkstra . . . . . . .
xxiii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
689
690
695
696
701
701
704
706
709
709
711
714
717
720
722
726
728
742
745
747
751
752
758
761
766
769
771
774
776
779
779
780
780
781
784
787
788
790
791
792
793
796
797
799
801
803
xxiv
CONTENIDO
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
803
803
805
807
810
810
810
811
813
815
815
816
821
822
823
823
824
825
827
830
843
Captulo 1
Abstracci
on de datos
Este texto
on
ierne al dise~no e implanta
ion de estru
turas de datos y algoritmos que
instrumenten solu
iones a problemas mediante programas de
omputador.
Un algoritmo es una se
uen
ia nita de instru
iones que a
omete la
onse
u
ion de un
n. Usamos algoritmos en diversos
ontextos de la vida; por ejemplo,
uando preparamos
un plato de
omida segun alguna re
eta. La
ultura nos ha in
ul
ado algunos \algoritmos",
ulturales, no naturales , para desenvolvernos so
ialmente; por ejemplos, el algoritmo de
ondu
ir un automovil o el algoritmo para
ruzar una
alle. En ambos ejemplos, el n que
se plantea es arribar a un sitio.
Segun Knuth [9, el termino algoritmo proviene del nombre del an
estral
matemati
o al-Kkwarizm, de la Persia, parte del a
tual Iran, region del planeta muy amenazada de ser borrada del mapa. De al-Kkwarizm tambien proviene la palabra \algebra",
dominio des
ubierto por vez primera en el a
tual invadido y desbastado Iraq.
Para la
onse
u
ion de un n se emplean \medios". En el
aso de un plato, los medios
que se utilizan son los instrumentos de
o
ina e ingredientes; el
o
inero, quien puede
interpretarse tambien
omo un medio,
onjuga, en un orden espe
o, los ingredientes
mediante los instrumentos. La se
uen
ia de eje
u
ion, o sea, el algoritmo, es fundamental
para
onseguir el plato en
uestion. Una altera
ion del orden posiblemente a
arreara una
altera
ion sobre el sabor del plato.
Durante la prepara
ion de un plato, el
o
inero requiere per
ibir y re
ordar la se
uen
ia
de eje
u
ion. E l requiere, por ejemplo, sofrer algunos ali~nos antes de mez
larlos
on la
arne. Segun la experien
ia y la
omplejidad, es posible que el
o
inero lleve notas que
memori
en el estado de prepara
ion; por ejemplo, anotar la hora en que
omenzo a hornear.
En todo momento,
on notas o sin ellas, el
o
inero requiere tener
ons
ien
ia del estado
en el
ual se ubi
a la prepara
ion respe
to a su re
eta. Para ello, el se sirve de su memoria.
En el
aso de un programa, el
omputador funge de
o
inero, el
ual eje
uta elmente
las re
etas que se le propor
ionan. El
omputador es, enton
es, un eje
utor que organiza
y usa algunos medios para al
anzar la solu
ion de algun problema, segun alguna re
eta
llamada \programa" y que re
uerda estados de
al
ulos mediante una \memoria".
Aparte del CPU, quien funge de
o
inero, el
omputador se vale de un medio fundamental: la memoria. De por s, la memoria en bruto es una se
uen
ia de
eros y unos
uyo
sentido lo imparte el programador en forma de \datos".
1
1 En
natura.
estos tiempos esto no es tan obvio. In reblemente, gran parte de la ultura se onfunde on la
Captulo 1. Abstracci
on de datos
00001000
Signo Exponente
001011110010110001001010
El sentido de esta estru
tura es efe
tuar rapidamente sumas mediante ajuste del exponente
y de la parte fra
ional. Aunque no
ono
emos el tama~no de los
ampos anteriores, el
ono
imiento de la estru
tura y de la manera de manipularla nos alerta sobre el
elebre e
inevitable error de redondeo que o
urre
uando trabajamos
on aritmeti
a
otante.
Para ilustrar un dato re
urrente nos valdremos del tipo hElemento de se
uen
ia 2i,
uya espe
i
a
ion en C++ puede plantearse
omo sigue:
hElemento de se
uen
ia 2i
struct Elemento
{
int dato;
Elemento * siguiente_elemento;
};
hElemento de se
uen
ia 2i modeliza un elemento entero pertene
iente a una se
uen
ia.
Notemos que el atributo siguiente elemento se reere a un struct Elemento e indi
a
la dire
ion en memoria del siguiente elemento en la se
uen
ia.
2 En espa~
nol,
as
omo en la mayora de las lenguas roman
es, existe el termino \re
urrir", el
ual, segun
su raz latina re
urro,
onnota, \volver", \regresar". En ingles la raz es la misma, pero basada en re
ursus,
que signi
a \vuelta, retorno". En este texto se da preferen
ia a re
ursion en lugar de re
urren
ia.
Diversos intereses in
iden en el dise~no de una estru
tura de datos. Entre los mas
tpi
os podemos desta
ar: la
omprension y manipula
ion del programa por parte del
programador, el desempe~no y la adapta
ion a un algoritmo o
on
epto parti
ular. En el
ejemplo del tipo float, hay dos
ara
tersti
as de
isivas. La primera es que las opera
iones
aritmeti
as son muy rapidas. De he
ho, siempre toman tiempo
onstante e independiente
del valor parti
ular del dato. La segunda
ara
tersti
a
on
ierne al espa
io; es de
ir,
ada
dato en punto
otante siempre o
upa la misma
antidad de espa
io,
uestion que no
su
edera si usaramos aritmeti
a arbitraria.
Para los tipos de datos que a
abamos de ejempli
ar (int y float), disponemos de
un fondo
ultural, matemati
o y de programa
ion, que nos permite se~nalarlos y
omprenderlos sin ne
esidad de detallar minu
iosamente en que
onsisten. Sabemos, sin tener que
indi
arlo expl
itamente, que existen las opera
iones aritmeti
as tradi
ionales de suma,
resta, produ
to y division, as
omo que ha
en y
uales son sus resultados.
1.1
Especificaciones de datos
Supongamos que un
o
inero
onsumado desea es
ribir una de sus re
etas para divulgarla a otros
o
ineros. En esta situa
ion, segun el
orpus
ognitivo de su experien
ia, el
o
inero asume que sus le
tores poseen un lenguaje
omun que les fa
ilitara entender las
instru
iones de su re
eta. Por ejemplo, se requiere que el
o
inero y sus le
tores tengan
el mismo
on
epto de lo que es una olla.
En el mar
o de ese lenguaje
omun, la re
eta debe ser pre
isa; no debe
ontener
ambiguedades que bloqueen al eje
utante. Ademas, debe ser
ompleta en el sentido de que
el eje
utante prepare plenamente el plato en
uestion.
En la per
ep
ion del aprendiz de programa
ion existe una diferen
ia esen
ial entre elaborar un plato de
o
ina y eje
utar un programa. En
o
ina se opera sobre
osas
on
retas
para la per
ep
ion humana. En programa
ion el
omputador opera sobre datos
on
retos en su memoria, pero abstra
tos para nuestra per
ep
ion. Preguntemosnos, >existe un
numero? >existe un arreglo?. En nuestra mente, un arreglo
onstituye una abstra
ion
que no se
apta
on nuestra per
ep
ion sensorial. En el
omputador no tiene sentido la
abstra
ion arreglo, pues este no entiende lo que es un numero o arreglo. En el
aso de la
programa
ion, as
omo en otros dominios derivados de la matemati
a, un \numero" o un
\arreglo" son
on
eptos abstra
tos que
onforman un lenguaje
omun para
omuni
arlos
y permitir la
onstru
ion de mas
on
eptos.
Entre programadores, as
omo en el resto de las pra
ti
as, es muy importante disponer
de un
orpus
omun, sobre la manera de abstraer, que permita la
omuni
a
ion de manera
homogenea.
Consideremos una situa
ion en la que deseemos disponer de un nuevo tipo de dato.
Planteemosnos dos
lases de preguntas en el siguiente orden:
1. P1: >Cual es su n? o, di
ho de otra manera, >para que puede servir?
2. P2: >Como se puede denir? >que representa el dato?
La primera pregunta nos indi
a la
lase de problema para el
ual el tipo de dato se
ir
uns
ribe
omo parte de la solu
ion. Si esto no esta denido, enton
es no tiene ningun
sentido
onsiderar el tipo de dato. La segunda pregunta nos expresa que es el tipo de dato,
Captulo 1. Abstracci
on de datos
pero ese que-es depende del para que este se usa. Si tenemos
laro el n, enton
es un
dato se dene segun las opera
iones permisibles y sus resultados.
Por ejemplo, si nos en
ontramos en una situa
ion en la
ual requiramos
al
ulo de
variable
ompleja, enton
es es esen
ial tener un tipo de dato \numero
omplejo". LLamese
hComplejo 5i a este tipo y denamoslo
omo una suma cr + ci i | cr, ci R, donde el
oe
iente cr es llamado \parte real"y, ci \parte imaginaria"; este ultimo representa
una fra
ion del numero \imaginado" 1 . Al igual que
on los tipos anteriores, esta
deni
ion asume que el le
tor
uenta
on una
ultura matemati
a en la
ual tienen sentido
los numeros
omplejos.
Como opera
iones estable
emos la
onsulta de la parte real e imaginaria, respe
tivamente.
3
1.1.1
Hemos di
ho que un tipo de dato representa un
onjunto. Bajo la presun
ion de una base
ultural matemati
a, la
ual
omprende al
on
epto de
onjunto, el tipo hComplejo 5i se
dene en torno a la no
ion matemati
a de numero
omplejo que le brinda su
omprension.
Bajo ese lenguaje, el ejemplo anterior satisfa
e las preguntas P1 y P2, respe
tivamente.
Si no dispusieramos del
orpus matemati
o de numero
omplejo, enton
es nos sera muy
dif
il interpretar el sentido del tipo hComplejo 5i.
La matemati
a, siempre y
uando se haya pasado por su entrenamiento, dene un
orpus
ognitivo, bastante abstra
to por
ierto, que nos permite denir el nuevo tipo de dato.
Si nos remitimos a la deni
ion de tipo de dato, enton
es, segun la matemati
a, denir un
tipo de dato estriba en denir un
onjunto de las dos maneras que tiene la matemati
a: por
extension o por
omprension. Denir un
onjunto por extension es muy objetivo, pero,
tambien, muy arduo y, en mu
hos
asos, imposible;
uando el
onjunto es innito, por
ejemplo. En el
aso de la programa
ion, un tipo de dato se dene, paradoji
amente, por
omprension mediante una forma metodologi
a denominada: \tipo abstra
to de dato" o
\TAD", la
ual, en la version de este texto,
onsta de las siguientes partes:
1. Una des
rip
ion del n para el
ual se destina el tipo de dato.
2. Un
onjunto de axiomas y pre
ondi
iones que denen el dominio del tipo .
4
3. Una interfaz denida por todas las opera
iones posibles sobre el TAD en la
ual, por
ada opera
ion, se establez
an dos tipos de espe
i
a
iones:
Especificaci
on sint
actica: nombre de la opera
ion, tipo de resultado y nombres
Esen
ial desta
ar que,
ono
ido, entendido y a
eptado el n, adquieren
ompleto sentido las espe
i
a
iones sinta
ti
a y semanti
a de un TAD.
En este texto, nos valdremos del
on
epto de
lase de objeto para realizar parte de la
espe
i
a
ion.
3 Bajo la perspe
tiva matem
ati
a, no existe interpreta
ion
4 Dominio en el sentido de una fun
i
on matemati
a.
1.
1.1.2
Noci
on de clase de objeto
La palabra \objeto" proviene del latn obje
tum, palabra que, resumidamente, signi
aba
lo que es visible de una
osa, su
ara externa. En
ontraste, la palabra \sujeto", tambien
proveniente del latn subje
tum, signi
aba lo que esta debajo de la
osa, o
ulto, que le
es interno; di
ho de otro modo, invisible en aparien
ia .
En programa
ion, as
omo en otras ingenieras, lo objetivo, o sea, la
ara visible, se
denomina \interfaz"; de inter-faz ; es de
ir, lo que esta entre la
ara; lo que se ofre
e al
exterior.
En el
ontexto de la programa
ion, una \
lase de objeto", o simplemente \
lase",
es una representa
ion objetiva de un tipo abstra
to de dato que dene su espe
i
a
ion
sinta
ti
a. Por objetiva pretendemos de
ir que solo nos referimos a las partes externas,
visibles, que tendra un objeto pertene
iente a una
lase dada. En el
aso de un tipo de
dato, las \partes visibles" las
onforman el nombre del tipo, los nombres de las opera
iones,
los nombres de los parametros, los tipos de dato de los parametros y los resultados de las
opera
iones; es de
ir, su espe
i
a
ion sinta
ti
a.
Para satisfa
er la objetividad de la espe
i
a
ion sinta
ti
a, es ne
esario a
ordar un
lenguaje
omun entre los programadores, pues, de lo
ontrario, sera muy dif
il interpretar
la interfaz. Un automovil, por ejemplo, tiene una interfaz de uso
onsistente, entre otras
osas, de los pedales, el volante y el tablero. Para poder
ondu
irlo, se requiere que el
ondu
tor este entrenado en la utiliza
ion de esa interfaz. Del mismo modo, para que un
programador
omprenda una espe
i
a
ion sinta
ti
a, este debe entender el lenguaje en
que se espe
i
a la
lase o TAD. En el
aso de este texto, realizaremos espe
i
a
iones
sinta
ti
as de TAD en el lenguaje C++ o en diagramas de
lases UML.
En C++, el ejemplo del TAD hComplejo 5i podra modelizarse del siguiente modo:
hComplejo 5i
5
struct Complejo
{
Complejo(float r, float i);
Complejo(const Complejo & c);
float & obtenga_parte_real();
float & obtenga_parte_imag();
};
Denes:
Complejo, never used.
Esta deni
ion estable
e \objetivamente" la espe
i
a
ion sinta
ti
a del TAD hComplejo 5i,
la
ual, aunada al lenguaje y al
orpus matemati
o
ultural de la no
ion de numero
omplejo,
ompleta la espe
i
a
ion sinta
ti
a del TAD.
>Que su
edio
on la espe
i
a
ion semanti
a? >Esta
ompleta la espe
i
a
ion? Si
asumimos que el le
tor de la espe
i
a
ion del TAD hComplejo 5i
ono
e su matemati
a
inherente, enton
es los nombres de las opera
iones permiten
omprender dire
tamente que
ha
e
ada opera
ion sin ne
esidad de expli
itarlo. >Existe alguna duda sobre lo que ha
e la
opera
ion obtenga parte real())? La respuesta depende, entre otros fa
tores, del grado
de entendimiento matemati
o que tenga el
uestionante. Si el le
tor no
ono
e la no
ion
de numero
omplejo, enton
es se requerira una espe
i
a
ion semanti
a que le imparta lo
5 Estas a
laratorias etimol
ogi
as fueron des
ubiertas
6 Interfaz es un angli
ismo de interfa
e.
de [3.
Captulo 1. Abstracci
on de datos
que es un
omplejo.
Lo objetivo posibilita un a
uerdo
omun entre diferentes personas a
er
a de la interpreta
ion de un tipo de dato. Para que este a
uerdo o
urra, es ne
esario que las personas
en
uestion \vean" o interpreten homogeneamente al objeto. Conse
uentemente, la interpreta
ion de un TAD depende del grado en que sus interesados
ompartan el lenguaje
on
que se reali
e su espe
i
a
ion.
1.1.3
Lo subjetivo de un objeto
Si tratamos a un dato en terminos objetivos, enton
es, >en que
onsiste tratarlo en terminos
subjetivos? A grosso modo, la respuesta es que la subjetividad de un TAD se trata durante
su espe
i
a
ion semanti
a.
Existen, basi
amente, tres fuentes de subjetividad.
La vision, interpreta
ion y, en
onse
uen
ia, el sentido de un TAD, depende de la experien
ia y
ono
imiento que tenga la persona que utili
e el TAD. Pero esto es muy subjetivo,
pues
ada quien tiene su propia experien
ia, la
ual no debe tratarse objetivamente. La
primera fuente de subjetividad es, enton
es, la interpreta
ion del usuario del TAD a
er
a
de su sentido. Quienes hayan estudiado
abalmente los numeros
omplejos tendran una
ompresion del TAD hComplejo 5i mas homogenea que quienes no lo hayan estudiado.
Para estos ultimos puede ser ne
esario
omplementar la espe
i
a
ion.
En el
aso del TAD hComplejo 5i es muy
onveniente tener una traye
toria de estudios
matemati
os. >Puede un programador sin esta traye
toria manejar el TAD hComplejo 5i?
Enfrentar esta pregunta revela perspe
tivas
ontradi
torias.
En primer lugar, si el usuario del TAD hComplejo 5i a
epta la interfaz, enton
es, el
desarrollo de programas que usen numeros
omplejos permite ganar
omprension a
er
a
de la matemati
a
ompleja. Empero, este usuario, al no disponer del
orpus matemati
o
requerido, es mas propenso a utilizar la interfaz para un n diferente al que fue
on
ebido
el TAD hComplejo 5i; por ejemplo, para representar puntos en el plano
artesiano. Si bien
esto puede representar un ahorro de
odigo, tambien puede a
arrear grandes
onfusiones
entre los programadores y mantenedores.
La segunda fuente de subjetividad proviene del mismo dise~nador del TAD. El objeto
resultante depende tambien de la experien
ia del dise~nador. Personas diferentes tienden a
proponer interfa
es diferentes.
La ultima fuente de subjetividad se reere a la implanta
ion del TAD. Distintos programadores realizaran implanta
iones diferentes. Si bien esta es primariamente la subjetividad
que pretende es
onder un TAD, puede ser esen
ial
onsiderarla por dos razones: el tiempo
de implanta
ion y el tiempo de eje
u
ion.
El tiempo de desarrollo de un TAD puede ser tan extenso que
omprometa un proye
to.
Analogamente, el tiempo de eje
u
ion del programa resultante puede ser tan lento que haga
ne
esario repetir la implanta
ion del TAD. En
ualesquiera de estas situa
iones puede ser
onveniente indi
ar los aspe
tos generales de la implanta
ion.
En resumen, las fuentes de subjetividad se tipi
an
omo sigue:
1. Subjetividad de interpreta
ion del usuario.
2. Subjetividad de interpreta
ion del dise~nador.
3. Subjetividad de implanta
ion.
Por mas enfasis que se le haga a la orienta
ion a objetos, los programadores que se
ir
uns
riban en desarrollos
ooperativos deben estar
ons
ientes de estas subjetividades
al momento de realizar la espe
i
a
ion semanti
a de un TAD. La idea en la espe
i
a
ion semanti
a es, enton
es, ade
uarse a la expe
tativa
ognitiva del grupo de personas involu
radas en el desarrollo y uso de un TAD. Si aquel grupo, por instan
ia,
omprende la matemati
a
ompleja, enton
es no solo es inne
esario ahondar en una espe
i
a
ion semanti
a que explique la no
ion de numero
omplejo, sino que, tambien, puede
tornarse muy tedioso. En este
aso, la fuente de subjetividad solo es de implanta
ion, la
ual es, pre
isamente, la subjetividad que pretende o
ultar un TAD.
Esen
ialmente, por la razon anterior, la espe
i
a
ion semanti
a no es objetiva. Ahora
bien, >
omo realizar una espe
i
a
ion semanti
a efe
tiva? Es de
ir, que logre el efe
to de
a
eptarse entendida por un grupo de programadores. Respuesta resumida: mediante un
lenguaje ade
uado. Para disertar en torno a esta
uestion, es apropiado imaginar
omo
dos interlo
utores tratan la no
ion de lo objetivo y subjetivo a
er
a de una
osa de programa
ion y un TAD.
En el sentido en que lo hemos tratado, lo objetivo de la
osa programada es per
eptible a la vision
omun de los interlo
utores, mientras que lo subjetivo les esta o
ulto al
menos a la mirada de uno de ellos o ambos. Por \vision", el le
tor no debe asumir el
mero sentido sensorial, sino la
apa
idad de per
ibir una
osa o fenomeno mediante las
abstra
iones y
onstru
iones intele
tuales que la experien
ia de los interlo
utores les
permita. Como parte de la experien
ia, es
rti
o que los interlo
utores tengan destrezas
de programa
ion equiparables.
Supongamos que un interlo
utor A le presenta un TAD a otro B. Dos situa
iones
ini
iales son posibles: (1) B ve e interpreta el TAD de la misma manera que A y (2) B no
lo interpreta igual. En
ualquiera de los dos
asos, es esen
ial que A
onoz
a la opinion de B
para poder determinarse
ual de las dos situa
iones o
urre.
Cuando o
urre la primera situa
ion, enton
es los interlo
utores tienen una mirada
homogenea del TAD y la subjetividad que resta es de implanta
ion, la
ual, en el estadio
de dise~no,
asi siempre es bueno o
ultarla.
Si o
urre la segunda situa
ion, enton
es A y B deben homogeneizar la vision e interpreta
ion del TAD de forma que sus subjetividades de interpreta
ion devengan objetivas.
La uni
a manera hasta ahora
ono
ida de ha
er esto es a traves del dialogo. Es por esto
que el lenguaje es fundamental en la espe
i
a
ion de un TAD. Pero el lenguaje no es meramente unidire
ional. A no tiene ninguna forma de
orroborar si B
omparte su mirada
si no es
u
ha la interpreta
ion que tenga B a
er
a del TAD. Por tanto,
uando se dise~na
un nuevo TAD, es esen
ial que el dise~nador lo exponga ante los interesados e ini
ie un
pro
eso de dialogo que dure hasta que no hayan subjetividades de interpreta
ion y solo
resten las de implanta
ion.
1.1.4
Un ejemplo de TAD
Captulo 1. Abstracci
on de datos
struct Figure
{
hConstru
tores de Figure 8ai
hObservadores de Figure 9ai
hModi
adores de Figure 9bi
};
hFiguras
on
retas 12i
Denes:
Figure, used in
hunks 8, 12, and 15.
El TAD hFigure 7i modeliza una gura \general" que se dibujara en algun medio de
ontraste. Es \general" en el sentido de que solo abstraemos opera
iones generales sobre
una gura geometri
a
ualquiera; es de
ir, las opera
iones son \generales" porque operan
sobre
ualquier gura independientemente de su parti
ularidad. Por \
ualquier gura"
pretendemos expresar que su forma,
uadrati
a, triangular, et
etera, no nos importa; solo
nos interesa una gura
omo abstra
ion general para dibujar y la manera general de
operar sobre ella a traves de opera
iones generales
omunes a todas las guras existentes.
No se debe, y, es preferible asumir que no se puede, denir una abstra
ion sin
ono
er
el para que de tal deni
ion. Por esa razon,
uando dise~namos un TAD, debemos asegurarnos de tener
laro el n que perseguimos. En este sentido, en lo que
on
ierne al
TAD hFigure 7i, el n es dibujar guras en algun medio de
ontraste. Si este n no esta
laro, no tiene sentido hablar de guras y de sus opera
iones.
La espe
i
a
ion sinta
ti
a del TAD hFigure 7i esta dada por su deni
ion en C++.
Una opera
ion sobre una
lase se denomina \metodo", termino proveniente del latn
methodus, el
ual proviene del griego oo (meta - hodos). En este
aso \meta"
onnota un n [de un
amino y hodos signi
a
amino. \Metodo" quiere de
ir, pues, un
amino ha
ia el n; es de
ir, un
amino
on destino,
on sentido.
Para denir la semanti
a de
ada opera
ion, debemos denotar el TAD Point, pues lo
referen
ian algunas opera
iones del TAD hFigure 7i. Supeditado al n del TAD hFigure 7i,
un punto destina la ubi
a
ion de la gura al momento de su dibujado y no nos
onviene,
por ahora, denirla mas, pues no tenemos idea -y es por ahora tambien preferible no
tenerla- del medio en el
ual se dibujaran las guras; por ejemplos, un medio planar:
papel o pantalla; o un medio tridimensional: proye
tor tridimensional u holografa. Para
el primer tipo de medio el punto requiere dos
oordenadas, mientras que para el segundo
tres.
Hay dos formas de
onstruir una gura abstra
ta expresadas por los siguientes
onstru
tores:
hConstru
tores de Figure 8ai
(7) 8b
7
8a
8b
El primer
onstru
tor requiere un punto; el segundo
opia la gura a partir del punto
donde se en
uentre otra gura.
El destru
tor del TAD hFigure 7i debe ser virtual:
hConstru
tores de Figure 8ai+
(7) 8a
7 La
redundan ia es adrede.
virtual ~Figure();
Uses Figure 7.
9a
pues, de esa manera, se garantiza la invo
a
ion de
ualquier destru
tor aso
iado a una
gura parti
ular.
A un metodo que no altere o modique el estado del objeto suele llamarsele \observador". En este sentido, una gura tiene un solo observador:
hObservadores de Figure 9ai
(7) 17
const Point & get_point() const;
9b
el
ual \observa" su punto de referen
ia en el plano. En C++, el
ali
ador const sobre un
metodo le indi
a al
ompilador que el metodo no altera el estado del objeto.
A un metodo que altera o modi
a el estado de un objeto se le
lasi
a de \modi
ador"
o, a ve
es, de \a
tuador". Los a
tuadores de una gura son los siguientes:
hModi
adores de Figure 9bi
(7)
virtual void draw() = 0;
virtual void move(const Point & point) = 0;
virtual void erase() = 0;
virtual void scale(const Ratio & ratio) = 0;
virtual void rotate(const Angle &angle) = 0;
Denes:
draw, used in
hunk 15.
erase, used in
hunk 15.
move, never used.
rotate, used in
hunk 15.
scale, used in
hunk 15.
8A
10
Captulo 1. Abstracci
on de datos
El lenguaje UML
Hasta ahora nos hemos servido del lenguaje C++ para resolver la espe
i
a
ion sinta
ti
a.
En efe
to, en este lenguaje, y en otros orientados a objetos, la sintaxis del propio lenguaje
indi
ia todos los aspe
tos sinta
ti
os de interes y, si se es
ogen nombres ade
uados, fundamenta los semanti
os.
Hoy en da existen mu
hos lenguajes orientados a objetos,
uya presen
ia nos di
ulta uni
ar miradas en espe
i
a
iones y dise~nos. Para paliar este problema, desde
ha
e mas de una de
ada, un
onsor
io llamado OMG (Obje
t Management Group) intenta \homogeneizar" la manera de espe
i
ar TAD [5. Di
ho lenguaje se denomina
a
ronimamente UML: "Unied Modeling Language" y se sirve de un medio que no tienen
todos los lenguajes y que es
onsone
on la idea de lo objetivo: el gra
o. Un gra
o di
e
mas que mil palabras reza un antiguo proverbio, y UML lo honra
uando se requiere
observar diferentes TAD y sus rela
iones.
El TAD hFigure 7i puede modelizarse pi
tori
amente en UML
omo en la gura 1.1.
Un re
tangulo representa una
lase
on tres se
iones. El nombre de la
lase se en
uentra
en la se
ion superior. El ttulo en letra
ursiva indi
a que la
lase es abstra
ta.
Figure
-point: Point
+Figure(in point:Point)
+Figure(in figure:Figure)
+~Figure()
+get_point(): Point
+draw(): void
+move(in p:Point): void
+erase(): void
+scale(in ratio:Ratio): void
+rotate(in angle:Angle): void
1.2. Herencia
11
Un programa
omplejo
ontiene mu
hos tipos abstra
tos. Cuando esta
antidad es
grande,
ualquier lenguaje deviene
ompli
ado para mirar en unidad al sistema y dentro
de el observar sus tipos de datos e interrela
iones. La gran ventaja de UML es su
ara
ter
gra
o, el
ual fa
ilita observar, solo visual y objetivamente, los tipos abstra
tos en una
sola mirada.
En este texto usaremos UML solo para espe
i
ar diagramas de
lases e interrela
iones
entre ellas. UML es un lenguaje de modelado mu
ho mas ri
o y la experien
ia ha demostrado que, para ganar homogeneidad de interpreta
ion en un proye
to, este es mas
simple que un lenguaje de programa
ion.
1.2
Herencia
El TAD hFigure 7i modeliza una gura general que no indi
a nada a
er
a de su forma
on
reta. Sin embargo, al momento de dibujar una gura, se tiene que
on
retar y
ono
er
de
ual gura se trata. En la jerga a objetos, a \
on
retar" se le di
e \espe
ializar" y se
representa mediante una rela
ion llamada \heren
ia de
lase". En UML,
on
retar algunas
\guras" bajo un diagrama UML, resumido, ejemplariza el
on
epto de una forma que nos
permite visualizar las
lases y sus rela
iones en una espe
ie de \genealoga" o \taxonoma",
tal
omo se ilustra en la gura 1.2.
12
12
Captulo 1. Abstracci
on de datos
espe
as Triangle, Square y Circle, espe
ializa
iones de triangulo,
uadrado y
r
ulo,
respe
tivamente.
La heren
ia se dene, enton
es,
omo la propiedad que tiene una
lase de \heredar"
el ser de otra
lase .
A la
lase general se le llama \
lase base" o \fundamental", mientras que a la espe
ializada se le denomina \
lase derivada". En el ejemplo de las guras, el TAD hFigure 7i es
lase base de las
lases derivadas Triangle, Square y Circle.
De
imos, tambien, que la
lase derivada \hereda" de la
lase base en el sentido de que
hereda toda su interfaz publi
a. Un triangulo, por instan
ia, hereda el punto de referen
ia
atribuible a todas las guras generales; es de
ir, un objeto de tipo Triangle puede invo
ar
al metodo get point(), pues este fue heredado del TAD hFigure 7i.
En el diagrama UML de la gura 1.2 se apre
ia que las
lases derivadas poseen atributos
y metodos que no se en
uentran en la
lase base. Por ejemplo, la
lase Circle posee un
metodo llamado get ratio()
uya fun
ion es observar el radio de la
ir
unferen
ia que
representa una instan
ia de objeto de tipo Circle. El atributo \ratio" tiene sentido,
segun la geometra, para la
lase Circle, pero no lo tiene para las
lases Triagle y Square.
Ahora bien, las tres
lases derivadas de hFigure 7i
omparten el punto de referen
ia, pues
ellas son, por deriva
ion, de tipo hFigure 7i.
Lo anterior sugiere una interpreta
ion de la heren
ia quiza mas ri
a: la rela
ion \ser"; es
de
ir, la
lase derivada es, tambien, de
lase base. De este modo, objetos de tipo Triangle,
Square y Circle son, tambien, de tipo Figure.
La de
lara
ion en C++ de la rela
ion de heren
ia anterior es la siguiente:
hFiguras
on
retas 12i
(7)
struct Triangle : virtual public Figure { ... };
struct Square : public Figure { ... };
struct Circle : public Figure { ... };
Uses Figure 7.
La seudo espe
i
a
ion de la
lase Triangle indi
a que es una
lase abstra
ta. En
efe
to, notemos que, en el
aso de un triangulo, segun nuestra
ultura matemati
a, podemos espe
ializarlo aun mas segun las longitudes de sus lados.
Lo grandioso de la heren
ia es la posibilidad de expresar
on
eptos y abstra
iones
generales a traves de
lases bases para luego parti
ularizarlas mediante deriva
iones. Esto
ofre
e la posibilidad de dise~nar indu
tivamente, yendo desde lo parti
ular ha
ia lo general o, dedu
tivamente, yendo desde lo general ha
ia lo parti
ular. Di
ho de otro modo,
yendo desde lo
on
reto ha
ia lo abstra
to o, paradoji
amente, desde lo abstra
to ha
ia lo
on
reto.
1.2.1
Tipos de herencia
La heren
ia del ejemplo anterior se denomina \heren
ia publi
a", pues lo que es publi
o
de la
lase base tambien deviene publi
o en la
lase derivada.
Hay otro modo de heren
ia denominado \de implanta
ion" o \heren
ia privada".
Este modo expresa el he
ho de que la
lase base se usa para implantar la, o parte de
la,
lase derivada. En UML la heren
ia privada se representa mediante una
e
ha punteada, mientras que en C++ mediante el
ali
ador private
omo prejo al nombre de la
lase base.
1.2. Herencia
1.2.2
13
Multiherencia
En o
asiones, una
lase de objeto es, a la vez, de dos o mas
lases. En el mundo a objetos,
esto puede expresarse mediante una rela
ion de heren
ia multiple. Es de
ir, un objeto
puede heredar de dos o mas
lases base. La gura 1.3 muestra una rela
ion de
lases que
modeliza los a
tores de una Universidad. Aten
ion espe
ial mere
e la
lase Preparador.
En la vida real, un preparador es un estudiante ex
elso,
uya ex
elen
ia apre
ia la Universidad para la asisten
ia y mejora de sus
ursos. Como retribu
ion, la Universidad le
otorga al estudiante un estipendio. En los terminos del diagrama UML, Preparador hereda
de las
lases Estudiante y Trabajador Universitario, respe
tivamente. De este modo
denimos que un preparador es, a la vez, estudiante y trabajador universitario.
Polimorfismo
La palabra polimorfo proviene del griego o (\polys"), que signi
a \mu
ho";
mientras que \morfo" proviene de o (\morfe"), que signi
a \gura", \forma".
Polimorfo
onnota, pues, \mu
has guras", \mu
has formas". En la jerga a objetos,
polimorfismo es la propiedad de expresar varias funciones o procedimientos
diferentes o similares bajo el mismo nombre.
14
Captulo 1. Abstracci
on de datos
1.2.3.1
Polimorfismo de sobrecarga
Sobre
arga es la
apa
idad de un lenguaje a objetos para denir nombres iguales de fun
iones, pro
edimientos y metodos. Consideremos, por ejemplo, la fun
ion siguiente:
const int sumar(const int & x, const int & y);
uyo n es sumar dos enteros. sumar() puede \sobre
argarse" para que sume mas enteros:
const int sumar(const int & x, const int & y, const int & z);
const int sumar(const int & w, const int & x, const int & y, const int & z);
1.2. Herencia
1.2.3.2
15
Polimorfismo de herencia
Dada una rela
ion de heren
ia entre tres
lases X, Y y Z, expresada gra
amente de la
siguiente manera:
X
+mtodo(...): T
+mtodo(...): T
+mtodo(...): T
15
16
Captulo 1. Abstracci
on de datos
fig.draw(); break;
case Delete: fig.erase(); break;
case Scale: fig.scale(dif_mag_with_previous_click()); break;
case Rotate: fig.rotate(dif_angle_with_previous_click()); break;
...
}
}
Uses draw 9b, erase 9b, Figure 7, rotate 9b, and scale 9b.
la
ual sera invo
ada en
aso de que se dete
te que se suelta el boton izquierdo del raton
dentro de un \lienzo" abstra
to de dibujado.
release left button() no requiere
ono
er la gura
on
reta. Su
odigo es general y no se afe
ta por las modi
a
iones o a~nadiduras de las guras. Por ejemplo,
si release left button() opera sobre un
uadrado, enton
es se invo
aran a los metodos
virtuales \
on
retos" de la
lase Square; analogamente, o
urre
on un
r
ulo,
aso en el
ual se invo
aran los metodos de Circle.
1.2.3.3
16
Hay situa
iones en las
uales un problema y su solu
ion pueden espe
i
arse de forma independiente del (o los) tipo(s) de dato(s). Por ejemplo, el problema de bus
ar un elemento
en un
onjunto, y su solu
ion, son independientes del tipo de elementos. Si suponemos que
el
onjunto se representa mediante un arreglo, enton
es, una posible manera, generi
a, de
bus
ar un elemento es
omo sigue:
hB
usqueda dentro de un arreglo 16i
template <typename T, class Compare>
int sequential_search(T * a, const T& x, int l, int r)
{
for (int i = l; i <= r; i++)
if (are_equals<T, Compare> () (a[i], x))
return i;
return No_Index;
}
Uses sequential search 193b.
Esta rutina bus
a el elemento x dentro del rango
omprendido entre l y r del arreglo a.
Se retorna un ndi
e dentro del arreglo
orrespondiente a una entrada que
ontiene un
elemento igual a x, o el valor No Index (por lo general 1), si el arreglo no
ontiene x.
Aparte de los parametros pertinentes al
onjunto, el algoritmo
generi
o sequential search<T, Compare>() requiere dos tipos parametrizados: el
tipo de dato del
onjunto, denominado generi
amente T, y un tipo
omparador de igualdad llamado are equals<T, Compare>()
uyo uso sera abordado en x 1.3.1.
Fun
iones o metodos
omo sequential search<T, Compare>() se denominan \plantillas" . De
imos que sequential search<T, Compare>() es \generi
a" porque genera
una familia de fun
iones para
ada tipo existente en el
ual exista una
lase are equals().
En otras palabras, una plantilla automatiza la sobre
arga de la fun
ion o
lase para los
tipos involu
rados en la plantilla.
10
10 En
ingles, \template".
1.2. Herencia
17
Figure
-point: Point
+medium: Medium_Type
+Figure(in point:Point)
+Figure(in figure:Figure)
+~Figure()
+get_point(): Point
+draw(): void
+move(in p:Point): void
+erase(): void
+scale(in ratio:Ratio): void
+rotate(in angle:Angle): void
Figura 1.4: Diagrama UML de la lase Figure on el medio de ontraste omo parametro
17
De esta manera, una espe
ializa
ion puede instan
iar un objeto de tipo Medium Type
omo
se ilustra en el siguiente ejemplo:
void Square::draw()
{
/* .... */
Medium_Type m; // Instancia un objeto "medio de contraste"
18
Captulo 1. Abstracci
on de datos
/* .... */
medium.put_line(...);
}
El atributo tipo medium de la
lase Figure<Medium> le permite a
eso al mismo. La espe
ializa
ion Square::draw() dibujara lneas en el medio de
ontraste
orrespondientes
al respe
tivo
uadrado. La implanta
ion de Square::draw() deviene generi
a respe
to al
medio.
1.2.3.4
Lo general y lo gen
erico
Los terminos \general" y \generi
o" no solo se pare
en mu
ho lexi
amente, sino que, en
efe
to, tambien son muy similares semanti
a y etimologi
amente.
La raz de ambos terminos es el termino latino generalis, el
ual proviene de genero,
que signi
a engendrar,
rear. En esta epo
a, tanto \general"
omo \generi
o"
onnotan
lo que es
omun a una espe
ie. Generalis proviene a la vez del griego o (genus) que
en el lenguaje moderno
onnota \raza" y que en griego se refera a lo
omun. De \genus"
proviene una muy amplia variedad de terminos: genero, gen, geneti
a, gentili
io, generoso,
gente, genealoga, genio, genial, ingenio, ingeniera, et
etera.
En su
elebrsima y tras
endental \Metafsi
a", Aristoteles distingue el \genero"
omo
lo que le es esen
ialmente
omun a una espe
ie. Podemos de
ir, pues, que la jerga a objetos
esta, desde ha
e mas de 2500 a~nos, impregnada por esta idea.
En el
aso de la programa
ion a objetos, \general" identi
a
lases de objetos generales;
es de
ir, bases de otras
lases mas parti
ulares, individuales, o
lases que operan sobre la
generalidad. Por ejemplos, la
lase hFigure 7i es general a todas las guras, mientras que
la
lase are equals() representa la
ompara
ion general y generi
a entre objetos.
\Generi
o"
onnota lo que genera; o sea, en la orienta
ion a objetos, a las plantillas,
on
epto que re
ien a
abamos de presentar y ejempli
ar.
1.3
Existe una
lase de problema
uya o
urren
ia es tan ubi
ua en pra
ti
amente todos los
ambitos de la programa
ion que ya es posible generizarla en una sola
lase. Se trata del
onjunto. Puesto que en la mayora de los
asos los elementos son del mismo tipo, es posible objetizarlo en un TAD generi
o tal
omo lo ilustra el diagrama UML de la gura 1.5.
El diagrama en
uestion modeliza lo que se
ono
e
omo el \problema fundamental de estru
turas de datos". Las diferentes maneras de implantarlo y sus diversas
ir
unstan
ias
de apli
a
ion abar
an
asi todo el
orpus de este texto.
La
lase Set<T, Compare> modeliza un
onjunto generi
o de datos de tipo T,
on
riterio de
ompara
ion Compare,
uyo n es generalizar opera
iones sobre
onjuntos sin
que nos interese
omo estos se implantan.
Hay mu
has formas de implantar el tipo Set<T, Compare>. La de
ision depende de
sus
ir
unstan
ias de uso.
Conjuntos de datos que se
orrespondan
on el problema fundamental se denominan
\
ontenedores". Un
ontenedor es, pues, un
onjunto de elementos del mismo tipo.
19
Key_Type:T
Compare_Type:Compare
Set
+insert(in key:T): void
+search(in key:T): T *
+remove(in key:T): void
+size(): size_t
+swap(inout set:Set<T, Compare>): void
+join(in set:Set<T, Compare>): Set<T>
+split(in key:T,out l:Set<T, Compare>,out r:Set<T,
Compare>): void
+position(in key:T): int
+select(in pos:int): T*
+split_pos(in pos:int,out l:Set<T, Compare>,
out r:Set<T, Compare>): void
19
20
Captulo 1. Abstracci
on de datos
Comparaci
on general entre claves
Mu
has ve
es el tipo generi
o T es ordenable; es de
ir, las
laves pueden disponerse en una
se
uen
ia ordenada desde la menor hasta la mayor o vi
eversa. En esos
asos, apare
en el
resto de las opera
iones y la
lase de
ompara
ion Compare.
Compare es una
lase que implanta la
ompara
ion entre dos elementos de tipo T. Por lo
general, Compare implanta el operador rela
ional <. Con una
lase de este tipo, es posible
realizar el resto de los operadores rela
ionales. Por ejemplo, si tenemos dos
laves k1 y k2
y una
lase Compare<T>
uyo operador () implanta k1 < k2, enton
es el siguiente seudo
odigo ejempli
a todas las
ompara
iones posibles:
if (Compare() (k1, k2)) // k1 < k2?
// acci
on a ejecutar si k1 < k2
else if (Compare() (k2, k1)) // k2 < k1?
// acci
on a ejecutar si k2 < k1
else // Tienen que ser iguales
// acci
on a ejecutar si k1 == k2
1.3.2
Si el
onjunto es ordenable, enton
es este puede interpretarse
omo una se
uen
ia ordenada
S =< k0, k2, . . . , kn1 >, donde n es la
ardinalidad del
onjunto. En ese
aso, pueden
realizarse varias opera
iones sobre la se
uen
ia S.
El metodo split(key, l, r) \parti
iona" el
onjunto en dos sub
onjuntos
l =< k0, k1, . . . , ki > y r =< ki+1, ki+2, . . . , kn1 > seg
un la
lave key tal que l < key <
r. Es de
ir, l
ontiene las
laves menores que key y r las mayores. Despues de la opera
ion
this deviene va
o.
El metodo position(key) retorna la posi
ion de la
lave dentro de lo que sera la
se
uen
ia ordenada. Si key no se en
uentra en el
onjunto, enton
es se retorna un valor
invalido.
El metodo select(pos) retorna el elemento situado en la posi
ion pos segun el orden.
Si pos es mayor o igual a la
ardinalidad, enton
es se genera una ex
ep
ion.
Finalmente, el metodo split pos(pos, l, r) \parti
iona" el
onjunto en
l =< k0, k1, . . . , kpos1 > y r =< kpos, . . . , kn1 >. Una ex
ep
ion o
urrira si pos esta
fuera del rango.
1.3.3
Cualesquieran sean las situa
iones de utiliza
ion, los elementos de un
onjunto tienen que
guardarse en alguna
lase de memoria. La manera de representar en memoria tal
onjunto
requiere de una estru
tura de datos
uya forma depende de sus
ir
unstan
ias de uso.
Hay varios fa
tores que in
iden en el dise~no o es
ogen
ia de la estru
tura de datos, entre
los que
abe desta
ar los
ono
imientos que se tengan sobre la
ardinalidad, la distribu
ion
de referen
ia de los elementos, las opera
iones que se usaran y la fre
uen
ia
on que estas
se invo
aran.
1.4. Dise
no de datos y abstracciones
21
A nivel fun
ional existen varias interpreta
iones del tipo generi
o hConjunto fundamental 19i. Hay, en esen
ia, dos
onsidera
iones dignas de resaltarse.
La primera
onsidera
ion
on
ierne a la repiten
ia o no de los elementos. Cuando se
permite repetir los elementos de un
onjunto, enton
es a este se le denomina \multi
onjunto".
En la bibliote
a estandar C++, en adelante llamada stdc++, al
onjunto se le
ono
e
omo set<T, compare> mientras que al multi
onjunto
omo multiset<T, compare>.
La segunda
onsidera
ion es el alma
enamiento de pares ordenados de tipo (Key, Elem).
La idea es una tabla aso
iativa que re
upere una instan
ia elem Elem dada una
lave key Key. A esta
lase de
onjunto se le
ono
e
omo \mapeo" . Cuando las
laves pueden repetirse, enton
es al mapeo se le di
e \multimapeo".
En la bibliote
a estandar C++ al mapeo se le
ono
e
omo map<Key, Elem, compare>
mientras que al multimapeo
omo multimap<Key, Elem, Compare>. En los mapeos suele
implantarse el operador [] segun la
lave.
11
1.4
Dise
no de datos y abstracciones
El dise~no de abstra
iones y sus
onse
uentes TAD es un arte que se aprende
on la experien
ia. Adquirirla no tiene otra alternativa que enfrentarse responsablemente a problemas
reales de programa
ion. Por responsabilidad se entiende la a
titud honorable a responder
por los equvo
os, lo
ual no solo esta
ondi
ionado a la
ons
ien
ia que el pra
ti
ante
tenga a
er
a de su
ono
imiento, sino a su honestidad y fuerza de
ara
ter.
En lo que sigue de esta se
ion, se plantean algunas re
exiones que debe
onsiderar el
aprendiz para enfrentar mejor el aprendizaje del dise~no de datos y programa
ion.
1.4.1
Tipos de abstracci
on
Segun el interes que se tenga al momento de dise~nar una abstra
ion o estru
tura de datos,
esta puede
lasi
arse en \orientada ha
ia los datos", \orientada ha
ia el
ujo" u \orien11 Del
ingles \mapping",
uya
onnota
ion matemati
a signi
a fun
ion en el sentido de la teora de
onjuntos. Por otra parte, es importante desta
ar que el termino fue re
ientemente a
eptado por la RAE.
22
Captulo 1. Abstracci
on de datos
El principio fin-a-fin
1.4. Dise
no de datos y abstracciones
23
opera
iones del TAD hFigure 7i, dibujar, mover, et
etera, tienen sentido sin ne
esidad
de
ono
er
ual es la gura en
uestion. Pensemos, >que su
edera si no tuviesemos
laro
para que se usara el TAD hFigure 7i? La respuesta, no tan obvia en estos tiempos, es que
nos sera muy dif
il
omprenderlo. Si nuestro entendimiento no estuviese
laro, enton
es,
quiza,
ometeramos el error de intentar implantar el TAD hFigure 7i, el
ual,
omo ya se
men
iono, es abstra
to.
Ahora pensemos en
ual sera la forma de un TAD Figure si este estuviese destinado a
al
ulos geometri
os en los
uales, en lugar de dibujar, se
al
ulasen areas e interse
iones
entre guras. Para este n, las opera
iones del TAD hFigure 7i no tendran mu
ho sentido.
Un prin
ipio de dise~no de sistemas se
ono
e
omo \el prin
ipio n-a-n". Este
onsiste
en no espe
i
ar, menos dise~nar, mu
ho menos implantar, mas alla del n que se
onoz
a
y se a
uerde para el programa. Violar este prin
ipio puede
ostar esfuerzo vano, pues solo
es en los puntos nales del programa, o en sus usuarios nales, en que se tiene todo el
ono
imiento ne
esario para dise~nar un programa
on sentido [15.
En el ejemplo del TAD hComplejo 5i, tal
omo lo hemos tratado, >que
ono
emos
a
er
a de su n? Los numeros
omplejos tienen amplia apli
a
ion en
ien
ias y en ingeniera; razon por la
ual un programador pudiera verse tentado a enrique
er el TAD
on
metodos o
lases derivadas que fa
iliten su futura manipula
ion. Se pudiera, por ejemplo,
manejar
oordenadas polares. Sin embargo, ampliar el TAD hComplejo 5i no tiene sentido si no se tiene la
ertitud de que las
oordenadas polares seran usadas por los
lientes
eventuales del TAD hComplejo 5i.
La observa
ion anterior no se realiza para e
onomizar trabajo -una ganan
ia de
onsuno
on el prin
ipio n-a-n-, sino porque el interesado en un TAD hComplejo 5i extendido
on
oordenadas polares pudiera manejar otra interpreta
ion, en
uyo
aso, la extension
pudiera ser un estorbo.
Un TAD debe ser \mnimo" y \su
iente". Por mnimo pretendemos indi
ar que no
tiene mas de lo ne
esario. Por su
iente queremos de
ir que debe
ontener todo lo ne
esario
para destinarlo al n para el
ual fue denido. Estable
er estas barreras es relativo, pues
depende del n y de su interpreta
ion. De all, enton
es, el
ara
ter esen
ial que tiene,
para el exito de un proye
to, el que el n este
laramente denido y que los parti
ipantes
no solo lo tengan
laro, sino que esten
omprometidos
on el.
Quiza un aforismo de Saint-Exupery exprese mejor el sentido de minimalidad y su
ien
ia del prin
ipio n-a-n: \Pare
e que la perfe
ion se al
anza no
uando no hay
mas nada que a~nadir, sino
uando no hay mas nada que suprimir" .
12
1.4.3
Inducci
on y deducci
on
Indu
ion signi
a ir desde lo parti
ular ha
ia lo general; mientras que dedu
ion se~nala ir
desde lo general ha
ia lo parti
ular. Cuando se dise~nan abstra
iones, >por donde
omenzar?.
Es un prin
ipio
ono
ido en edu
a
ion y dise~no ir desde lo
on
reto ha
ia lo abstra
to.
En otras palabras, forjar abstra
iones a partir de la experien
ia
on
reta real. En ese
sentido,
uando no se tenga
ono
imiento ini
ial a
er
a de un problema dado, el pro
eso
de indaga
ion debe
omenzar a partir de fenomenos
on
retos del problema y, luego, a
12 Tradu
i
on
del autor de: \Il semble que la perfe
tion soit atteinte non quand il n'y a plus rien a
ajouter, mais quand il n'y a plus rien a retran
her". Saint-Exupery. \Terre des hommes".
24
Captulo 1. Abstracci
on de datos
Ocultamiento de informaci
on
25
1.5
La programa
ion orientada a objetos se remonta a nales de la de
ada de 1960 en que
apare
io el lenguaje Simula [2,
on los
on
eptos de
lase, heren
ia y polimorsmo. Hay
dos observa
iones histori
as muy importantes. La primera es que Simula se
ir
uns
ribio
en el dominio de la simula
ion y no de la programa
ion tradi
ional. La segunda es que
todos los
on
eptos modernos de la orienta
ion a objetos apare
ieron primero que la no
ion
matemati
a de tipo de dato abstra
to.
Simula no debe haberse tenido muy en
uenta en su epo
a porque trans
urrieron
algunas de
adas antes de que se le desempolvase y
onsiderase en el paradigma a
tual
de los objetos. Por el
ontrario, los
omputistas, que se
reen muy elites
os,
ono
en los
tipos abstra
tos de datos desde los trabajos de Liskov y Zilles [10 y el o
ultamiento de
informa
ion desde los trabajos de Parnas [13.
El lenguaje C++, veh
ulo de ense~nanza del presente texto, inspirado en los lenguajes C [8 y Smalltalk [6, 1, 18, fue
reado por Bjarne Stroustrup. La mejor referen
ia para
su aprendizaje es su propio texto The C++ Programming Languaje [17, el
ual, aparte
de que es posiblemente el mejor para
omprender el lenguaje, es un ex
elente tratado de
ingeniera de programa
ion, que no tiene nada que ver
on la geren
ia de proye
tos de
software, a
tividad ahora injusta y vulgarmente
ono
ida bajo el rotulo de ingeniera del
software.
Este texto no versa sobre la interfaz de la bibliote
a estandar C++, sino, mas bien, a
er
a
de su implanta
ion. Es util, sin embargo, estudiar la interfaz a efe
tos de no repetir trabajo
y de homogeneizar
riterios. Una ex
elente referen
ia sobre la bibliote
a estandar C++ la
onstituye el texto de Josuttis [7.
Un re
uento histori
o a
er
a del C++ y de la programa
ion a objetos puede en
ontrarse
en [16. La le
tura es muy interesante porque revela que las abstra
iones y
on
eptos
aso
iadas a los objetos son resultado del renamiento a traves de errores y fra
asos.
El prin
ipio n-a-n ha sido observado desde epo
as remotas, pero fue Guillermo de O
am,
uando enun
io su
elebre \navaja", la
ual reza \no multiplique los entes sin
ne
esidad", quien primero estable
io su importan
ia epistemologi
a. En la programa
ion,
el prin
ipio ha sido observado en grandes sistemas, siendo al respe
to emblemati
o el
art
ulo de Saltzer et al [15. Sobre este art
ulo es menester
omentar que Saltzer et al
orientan su art
ulo a sistemas distribuidos y no dire
tamente al dise~no de datos. Por otra
parte, el termino \n" a menudo se interpreta
omo \extremo" y no
omo un proposito.
A traves de la experien
ia se des
ubren datos generales y generos de
odigo. A una
26
Captulo 1. Abstracci
on de datos
1.6
Ejercicios
1.6. Bibliografa
27
la
ual extrae de S, y retorna en un nuevo
onjunto, todos los elementos que estan
entre las posi
iones i y j, respe
tivamente. Despues de la opera
ion, S
ontiene los
elementos entre los rangos [0..i 1] y [j + 1..n 1], donde n = |S|.
14. Asuma que el n de un sistema es la administra
ion de la es
olaridad de una
arrera
universitaria. Bajo este n, se desea disponer de bases de datos de estudiantes,
profesores,
arreras,
ursos, se
iones, salones, horarios y demas aspe
tos propios de
la administra
ion es
olar de una Universidad.
Plantee TAD generales, parti
ulares, parametrizados, que modeli
en las diversas abstra
iones que manejara el sistema. Dibuje los diagramas UML para todas las
lases
dise~nadas.
15. Re
onsidere el ejer
i
io anterior para
i
los es
olares de se
undaria y primaria.
Bibliografa
[1 Byte, editor. Spe
ial issue on Smalltalk, volume 6, August 1981.
[2 O. J. Dahl and K. Nygaard. SIMULA { an ALGOL-based simulation language. Communi
ations of the ACM, 9(9):671{682, September 1966.
Interpretando Organiza iones... Una Teora Sistemi oInterpertativa de Organiza iones. Universidad de Los Andes - Consejo de pub-
[3 Ramses Fuenmayor.
28
Captulo 1. Abstracci
on de datos
Captulo 2
Secuencias
Este
aptulo versa sobre se
uen
ias. En programa
ion, as
omo en otros ambitos de
nuestra
ultura, una se
uen
ia se dene
omo una su
esion de elementos de algun tipo.
Por su
esion entendemos que los elementos de la se
uen
ia puedan mirarse segun
ierto
orden de apari
ion o pro
esamiento; uno tras otro, de izquierda a dere
ha, de arriba ha
ia
abajo y otras
ombina
iones que mantengan el
ara
ter su
esivo subya
ente a la idea de
se
uen
ia.
En la vida
otidiana lidiamos
on se
uen
ias sin que
asi nun
a nos maravillemos de
sus
onse
uen
ias, las
uales se nos desapare
en en la unidad de la vida. En
astellano, y
en otras lenguas, leemos y es
ribimos de izquierda a dere
ha y desde arriba ha
ia abajo;
o sea, se
uen
ialmente. La mayora de las ve
es la le
tura o
urre sin que nuestro pensamiento intervenga y fragmente el sentido de lo que leemos. De
imos enton
es que leemos
udamente. Se
uen
ial situa
ion a ve
es su
ede en matemati
a, dominio donde solemos
operar al menos aso
iativamente, de izquierda a dere
ha y de algunos otros modos mas
reservados para los matemati
os ex
elsos.
No solo las se
uen
ias nos son ubi
uas, sino que mu
has ve
es estas tienen diferentes
perspe
tivas de mira o distintos modos y tiempos para presentarse e interpretarse. Consideremos, por ejemplo, el
aso de una pel
ula vista
omo una se
uen
ia de es
enas hilvanada
segun el sentido que el dire
tor nos pretenda transmitir de la historia. Para aproximarnos
a lo que
omo pel
ula se quiere presentar, debemos mirar la pel
ula se
uen
ialmente.
Un
orte en la se
uen
ia, o una permuta
ion entre sus es
enas puede volver a la pel
ula
ininteligible o, quiza \en el mejor de los
asos", ofre
er una interpreta
ion distinta. Por
supuesto, lo anterior no nos impide, en retrospe
tiva, luego de haber presen
iado enteramente la pel
ula, mirar algunas de sus partes para mejorar nuestra
omprension. Visto de
otro modo, una pel
ula
onsiste en una se
uen
ia de fotografas
uya su
esion re
onstruye
las es
enas
on impresion de realidad.
El
omputador no es
apa a la ubi
uidad de las se
uen
ias. No muy otrora, fue
lasi
ado de \maquina se
uen
ial", pues se remite a leer y eje
utar programas, los
uales no son
mas que se
uen
ias de instru
iones es
ritas en una \memoria". He aqu, pues, un indi
io
serio de que
ualquier abstra
ion de se
uen
ia es ampliamente usada en la
omputa
ion.
En este
aptulo estudiaremos las siguientes estru
turas de datos
ara
terizadas
omo
se
uen
ias:
Arreglos.
Listas enlazadas.
29
30
Captulo 2. Secuencias
Pilas.
Colas.
Cualquiera de estas estru
turas es ubi
ua por todas las
ien
ias
omputa
ionales y es
muy probable que sean parte del
amino
rti
o de eje
u
ion. Por esta razon, es deseable
ono
er las implanta
iones mas e
ientes posibles.
Los arreglos y listas enlazadas
onforman abstra
iones de datos, mientras que las pilas
y
olas son abstra
iones de
ujo. Una se
uen
ia de elementos del mismo tipo
onforma
un
onjunto; lo que sugiere de entrada que una se
uen
ia, vista
omo abstra
ion de dato,
puede implantar el problema fundamental de estru
tura de datos estudiado en el
aptulo 1.
Este sera el enfasis que le impartiremos a los arreglos y las listas enlazadas.
Otras situa
iones
omputa
ionales requieren un patron de pro
esamiento parti
ular.
Este es el sentido de las pilas y las
olas,
uyos nombres en
ierta forma abstraen el orden
de pro
esamiento.
2.1
Arreglos
Un arreglo de dimension dim es una se
uen
ia de n dim elementos del mismo tipo en la
ual el a
eso a
ada elemento es dire
to y
onsume un tiempo
onstante e independiente
de su posi
ion dentro de la se
uen
ia.
Por lo general, los elementos de un arreglo se organizan de forma dire
tamente
ontigua
en la memoria del
omputador, lo que propor
iona dos bondades que ha
en al arreglo muy
interesante para la programa
ion de sistemas:
1. Se puede a
eder dire
tamente a
ualquier elemento segun su posi
ion dentro de
la se
uen
ia. Por lo general, esto se implanta a nivel de
ompila
ion y se espe
i
a a nivel del lenguaje de programa
ion mediante el operador []. Por ejemplo, la
expresion en C++: a[15] = 9, asigna el entero 9 al de
imo sexto elemento del arreglo.
2. El
osto en espa
io es mnimo;
asi siempre, la
antidad de elementos.
Ninguna estru
tura de dato ofre
e mejor rendimiento en el a
eso por posi
ion. El
he
ho de que los elementos esten
ontiguos favore
e extraordinariamente a las apli
a
iones
que exhiban lo
alidad de referen
ia, pues es muy probable que los elementos
er
anos en
espa
io y tiempo se en
uentren en el
a
he .
La mayora de los lenguajes de programa
ion modernos ofre
en soporte para el manejo
de arreglos.
Consideremos un arreglo A[11] de elementos de tipo T
uyo tama~no en bytes es
sizeof(T) y que se pi
toriza del siguiente modo:
1
Base
0 1 2 3 4 5 6 7 8 9 10
T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10
El
ompilador aso
ia al arreglo la dire
ion del primer elemento llamada \base del arreglo",
uyo valor se determina segun el lugar donde o
urra la de
lara
ion o instan
ia
ion y el
1 En
este
ontexto un
a
he es una estru
tura espe
ial de dato, soportada por el hardware, la
ual, si se
usa
orre
tamente, a
elera
onsiderablemente la velo
idad de a
eso a la memoria.
2.1. Arreglos
31
modo de de
lara
ion. Cuando el
ompilador en
uentra un a
eso a la i-esima posi
ion,
este genera el siguiente
al
ulo de a
eso a la memoria:
Dire
ion del elemento = base + i sizeof(T)
(2.1)
La opera
ion asume que el primer ndi
e del arreglo es
ero, que es el
aso en los lenguajes
C y C++. Si el lenguaje permite que los ndi
es
omien
en en valores arbitrarios, enton
es
el
ompilador debe generar una opera
ion adi
ional antes de poder realizar el
al
ulo
anterior, lo que ha
e un po
o mas lento el a
eso.
En general, los
ompiladores no efe
tuan veri
a
ion de rango. Es de
ir, no se aseguran
de que el ndi
e de referen
ia al arreglo este dentro de los rangos
orre
tos. La razon es
que esta veri
a
ion le a~nade un
osto
onstante a
ada a
eso que puede ser importante.
Conse
uentemente, un a
eso fuera de rango es un grave error de programa
ion
uyos
sntomas pueden pasar desaper
ibidos durante algun tiempo. Este tipo de error es muy
dif
il de dete
tar; razon por la
ual es buena idea utilizar asertos de veri
a
ion de rangos
antes de referen
iar un arreglo.
2
2.1.1
Operaciones b
asicas con Arreglos
ser
iones, busquedas y elimina
iones pueden su
eder fre
uentemente y
on la misma
probabilidad.
El tama~
no y patron de a
eso de los elementos que alberga el arreglo. Mientras mas
2.1.1.1
Aritm
etica de punteros
Una de las grandes virtudes del lenguaje C, tras
endida al C++, es lo que se
ono
e
omo
\aritmeti
a de punteros". Tal
on
epto
onsiste en in
orporar al lenguaje opera
iones sobre punteros
uyos resultados
onsideran el tipo de dato al
ual se apunta. Para
lari
ar
este
on
epto, desarrollemos un ejemplo.
La instru
ion T * arreglo ptr = new [n] T; de
lara un puntero a una
elda de
memoria de tipo generi
o T. A la vez, se aparta memoria para albergar n
eldas
ontiguas
de tipo T; o sea, un arreglo de dimension n.
Normalmente, si arreglo ptr es la base de un arreglo, enton
es, para a
eder al i-esimo
elemento tendramos que efe
tuar un
al
ulo similar a (2.1). Ahora bien, el
al
ulo impli
ado en (2.1) lo genera automati
amente el
ompilador
uando, por ejemplo, es
ribimos: *(arreglo ptr + i) = 5;. El operando izquierdo de la asigna
ion se interpreta
2 El
D.R.A.E. expresa para \aserto": \Arma
ion de la
erteza de algo". A un aserto se le
ono
e mas
omo pre
ondi
ion o invariante.
En este texto se utilizan invo
a
iones a la primitiva I(predicado) de la bibliote
a nana [15.
32
Captulo 2. Secuencias
omo: a
eda al
ontenido de la dire
ion de memoria arreglo ptr mas i enteros. El
ompilador sabe que se trata del tipo int y no de otro porque el puntero fue de
larado
omo un puntero a entero. Con este
ono
imiento, el
ompilador impl
ita y transparentemente in
orpora el tama~no de un entero (sizeof(int)) en el
al
ulo del a
eso.
La expresion *(arreglo ptr + i) es exa
tamente equivalente a arreglo ptr[i],
la
ual se tradu
e: a
eda al i-esimo entero de la se
uen
ia
uya dire
ion base
es arreglo ptr. Quiza un problema de este enfoque es que es mas dif
il distinguir el
a
eso a un arreglo expl
itamente de
larado de un a
eso mediante un puntero. Por esta
razon, es
onveniente que el nombre del puntero re
eje su
ondi
ion. En el
aso ejemplo,
el sujo ptr denota que se trata de un apuntador.
3
2.1.1.2
32
B
usqueda por clave
Si los elementos estan ordenados, enton
es podemos usar un fabuloso algoritmo, llamado
\busqueda binaria\,
uya espe
i
a
ion generi
a es
omo sigue:
hB
usqueda binaria 32i
template <typename T, class Compare>
int binary_search(T a[], const T & x, const int & l, const int & r)
{
int m; //
ndice del medio
while (l <= r) // mientras los
ndices no se crucen
{
m = (l + r)/2;
if (Compare() (x, a[m])) // es x < a[m]?
r = m - 1;
else if (Compare() (a[m]), x) // es x > a[m]?
l = m + 1;
else
return m; // ==> a[m] == x ==> encontrado!
}
return m;
}
Denes:
en ingles de \pointer".
2.1. Arreglos
33
Como estudiaremos en la sub-se
ion x 3.1.8, el rendimiento de este algoritmo es propor
ional lg n. Si bien este
oste es mayor que el
onstante del a
eso por posi
ion, en la
pra
ti
a es bastante bueno.
Si los elementos no estan ordenados, enton
es puede efe
tuarse, a
osta del desempe~no, la busqueda se
uen
ial des
rita en x 1.2.3.3 bajo el nombre de fun
ion
sequential search<T, Compare>(). Si bien la itera
ion requerida por esta
lase de
busqueda puede requerir inspe
ionar todas las
eldas del arreglo, es a
eptable
omo
solu
ion para es
alas medianas.
2.1.1.3
33
Inserci
on por clave
orden
...
...
pos gap
orden ...
--i
La rutina asume que n es la
antidad de elementos que tiene la se
uen
ia y que este valor
no iguala o ex
ede la dimension del arreglo.
insert by gap<T, Compare>() toma el tiempo de b
usqueda, que es rapidsimo, mas
el tiempo de abrir la bre
ha que requiere la itera
ion y la
opia.
2.1.1.4
Eliminaci
on por clave
Con arreglos, la elimina
ion primero requiere
ono
er la posi
ion dentro del arreglo del
elemento a eliminar. Esto puede realizarse mediante la rutinas sequential search<T,
34
Captulo 2. Secuencias
34a
ambas situa
iones, el elemento eliminado deja un \hue
o" o \bre
ha" que debe \taparse".
Si el arreglo esta desordenado, enton
es la elimina
ion puede llevarse a
abo
omo
sigue:
hElimina
i
on en arreglo desordenado 34ai
template <typename T, class Equal>
void remove_in_unsorted_array(T a[], const T & x, int & n)
{
int pos_gap = sequential_search<T, Equal>(T, x, 0, n - 1);
if (a[pos_gap] != x)
// excepci
on: x no se encuentra en a[]
a[pos_gap] = a[n - 1];
--n;
}
Denes:
34b
se
uen
ia.
Si el arreglo esta ordenado, enton
es se requiere tapar la bre
ha mediante desplazamiento ha
ia la izquierda de todos los elementos que estan a la dere
ha de la bre
ha de la
siguiente forma:
hElimina
i
on en arreglo ordenado 34bi
template <typename T, class Equal>
void remove_in_sorted_array(T a[], const T & x, int & n)
{
int pos_gap = binary_search<T, Equal>(T, x, 0, n - 1);
if (a[pos_gap] != x)
// excepci
on: x no se encuentra en a[] ;
for (int i = pos_gap; i < n; ++i)
a[i] = a[i + 1];
--n;
}
Denes:
orden
...
...
pos gap
orden ...
++i
2.1. Arreglos
2.1.2
35
Segun el esquema en que se aparte la memoria para un arreglo, este se
lasi
a en estati
o
y dinami
o.
2.1.2.1
Este tipo de arreglo se de
lara global o estati
amente dentro de una fun
ion . Este arreglo
exige que se
onoz
a la dimension en tiempo de
ompila
ion. El arreglo o
upa todo su
espa
io durante toda la vida del programa y su dimension no puede
ambiarse.
En general, este tipo de arreglo debe usarse para guardar
onstantes que seran utilizadas a lo largo de toda (o la mayor parte de) la vida del programa. Tambien puede
utilizarse
omo deposito de valores iterativos; por ejemplo, una apli
a
ion numeri
a que
siempre reali
e opera
iones
on matri
es de dimension ja y
uyo valores
ambian en
fun
ion de la entrada del problema.
4
2.1.2.2
Arreglos en pila
Este tipo de arreglo es el que se de
lara dentro de una fun
ion . El espa
io de memoria
es apartado de la pila de eje
u
ion del programa. Puesto que la pila de eje
u
ion es vital
para el programa, se debe tener
uidado
on un desborde de pila
ausado por una ex
esiva
longitud del arreglo. Esto puede ser
rti
o en programas
on
urrentes multi-thread o en
ambientes que soporten
orrutinas.
La ventaja de este tipo de arreglo es doble. Primero el
ompilador no requiere
ono
er
su dimension para generar
ualquier
odigo de reserva
ion de memoria o de referen
ia
al arreglo. En
onse
uen
ia, es posible esperar hasta la eje
u
ion de la de
lara
ion para
ono
er su dimension. La siguiente se
ion de
odigo es fa
tible en
ompiladores GNU:
5
La segunda ventaja es que el espa
io o
upado por el arreglo se aparta automati
amente
en el momento de eje
u
ion de la de
lara
ion y se libera a la salida del ambito de la
de
lara
ion. En el ejemplo anterior, el arreglo es libera a la salida de la fun
ion foo().
A
ausa del riesgo de desborde de pila, el uso de este tipo de arreglo debe restringirse
a dimensiones peque~nas. Debe prestarse aten
ion esmerada al nivel de las llamadas a las
fun
iones, pues, a medida que se aniden las llamadas, se
onsume mas pila.
No use arreglos en pila dentro de fun
iones re
ursivas o dentro de fun
iones que seran
llamadas re
ursivamente.
2.1.2.3
C y en C++ , un arreglo estati
o se de
lara dentro de una fun
ion o un modulo pre
edido de la
palabra reservada static. En el
aso de un modulo, el arreglo solo es visible dentro del modulo y a partir
del punto de de
lara
ion. En el
aso de una fun
ion, el arreglo solo es visible dentro de la fun
ion.
5 Aten
i
on: esto no ne
esariamente es
ierto en otros lenguajes. En FORTRAN, por ejemplo, la mayora
de los arreglos son estati
os.
36
Captulo 2. Secuencias
El tama~no del arreglo solo esta limitado por la
antidad de memoria disponible. La dimension puede espe
i
arse
omo una variable.
Este tipo de arreglo exige que la memoria se libere
uando el arreglo ya no se requiera.
En el ejemplo anterior esto se realiza mediante:
delete [] array;
Arreglos de bits
Re
ordemos que un dato de tipo logi
o, bool en C++, solo puede tomar dos valores: true
o false. Para representar un bool solo se requiere un bit, pero, por razones de alinea
ion
de memoria, en C++ un bool es tradu
ido a un byte
uyo valor puede ser
ero (0) o uno (1).
Este desperdi
io de siete (7) bits es despre
iable en la inmensa mayora de las apli
a
iones,
pues la
antidad de datos logi
os es peque~na.
Ahora bien, >que su
ede si tenemos un arreglo de datos logi
os? Si la dimension del
arreglo es
onsiderable, enton
es podemos in
urrir en un desperdi
io de memoria importante. Pero, >existen apli
a
iones que requieran arreglos logi
os? La respuesta es armativa
y
abe de
ir que se presentan
on
ierta fre
uen
ia.
Un ejemplo es el manejo de paginas de memoria virtual efe
tuado por un sistema
operativo. Consideremos una memoria prin
ipal de 512 Mb y paginas de 4 Kb; un sistema
operativo debe mantener el estado de 5121024
= 131072 paginas. Si
ada estado se
4
representa
on un byte, enton
es se requieren 128 Kb de memoria para alma
enar el
estado de las 131072 paginas. Por
ada pagina, el sistema operativo maneja dos bits. El
primero indi
a si la pagina se en
uentra o no en memoria prin
ipal. El segundo indi
a si
una pagina ha sido o no modi
ada. Si se utilizan dos bits por pagina, se requieren
Kb
Kb
2 131072
= 32768 = 32Kb ;
8
36
2.1. Arreglos
hMiembros
37
public:
num_bits;
num_bytes;
num bits alma
ena la dimension en bits; mientras que num bytes alma
ena la dimension
del arreglo de bytes array of bytes. El valor de num bytes es mnimo y su
iente para
37b
Byte * array_of_bytes;
BitArray::Byte
BitArray::BitProxy
bit_index : size_t
byte_ptr : Byte*
+ BitProxy(array : BitArray&, i : const size_t&)
+ operator int()
+ operator =(value : const unsigned int&) : BitProxy&
+ operator =(proxy : const BitProxy&) : BitProxy&
0..1
+ b0 : unsigned int
+ b1 : unsigned int
+ b2 : unsigned int
+ b3 : unsigned int
+ b4 : unsigned int
byte_ptr
+ b5 : unsigned int
+ b6 : unsigned int
+ b7 : unsigned int
+ read_bit(i : const unsigned int&) : const unsigned int
+ write_bit(i : const unsigned int&, value : const unsigned int&)
BitArray
num_bits : size_t
num_bytes : size_t
array_of_bytes : Byte*
+ Exception_Prototypes( : std::bad_alloc)
+ ~ BitArray()
+ get_len() : const size_t
+ size() : const size_t
+ operator [ ](i : const size_t&) : const BitProxy
+ BitArray(array : const BitArray&)
+ BitArray(array : const BitArray&, size : const size_t&)
+ operator =(array : const BitArray&) : BitArray&
+ resize(new_dim : const size_t)
38
38a
Captulo 2. Secuencias
la
ual se dene as:
h
lase Byte 38ai
struct Byte
{
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
hLe
tura
b0
b1
b2
b3
b4
b5
b6
b7
(37a)
:
:
:
:
:
:
:
:
1;
1;
1;
1;
1;
1;
1;
1;
de bit 38bi
hEs ritura
hConstru
tor
};
de Byte 38 i
Basi
amente, la sub-
lase BitArray::Byte dene una mas
ara de 8 bits mediante
ampos bits . Esta mas
ara permite un a
eso sen
illo dado un ndi
e de bit. La le
tura
es, enton
es, implantada
omo sigue:
hLe
tura de bit 38bi
(38a)
6
38b
unsigned int
{
switch (i)
{
case 0 :
case 1 :
case 2 :
case 3 :
case 4 :
case 5 :
case 6 :
case 7 :
default:
}
}
38
b0 = value; break;
b1 = value; break;
b2 = value; break;
b3 = value; break;
b4 = value; break;
b5 = value; break;
b6 = value; break;
b7 = value; break;
throw std::out_of_range ("bit index greater than 7");
Donde i es el ndi
e del bit que se desea a
eder y value es el bit que se desea es
ribir.
Es muy util, a expensas de un po
o de tiempo, que
uando se aparte la memoria
para un objeto de tipo Byte todos sus bits esten ini
iados en
ero. Implantaremos esta
semanti
a en el
onstru
tor por omision:
hConstru
tor de Byte 38
i
(38a)
Byte()
: b0(0), b1(0), b2(0), b3(0), b4(0), b5(0), b6(0), b7(0)
{
// empty
}
6 Los
ampos bits fueron originalmente denidos para el lenguaje C y son parte del C++
2.1. Arreglos
39a
39
Este
onstru
tor garabtiza que todo arreglo de tipo Byte tenga todos sus bits en
ero.
Para de
larar un objeto de tipo BitArray basta
on espe
i
ar la dimension del arreglo.
El
onstru
tor y el destru
tor tienen, pues, la siguiente forma:
hMiembros p
ubli
os de BitArray 39ai
(36) 39b
BitArray(const size_t & dim = 256) Exception_Prototypes(std::bad_alloc) :
num_bits
(dim),
num_bytes
(num_bits/8 + (((num_bits % 8) != 0) ? 1 : 0)),
array_of_bytes (new Byte [num_bytes])
{
// empty
}
39b
get_len(); }
size() es el nombre estandar de la bibliote a ALEPH y la estandar C++, pero get len()
i
8
Su posi
ion bit index dentro del byte estara dada por:
bit index = i
mod 8 .
Consideremos la instru
ion i = array[40]; que leera el valor del bit
orrespondiente
a la entrada 40. En este
aso, la implanta
ion del operador [] debe bus
ar el bit 40 y
enton
es retornar una representa
ion entera -de tipo int- del bit a
edido. En este
aso,
el operador [] debe invo
ar a read bit().
Ahora
onsideremos la instru
ion array[40] = i; que es
ribira en la entrada 40 del
arreglo el valor
ontenido en la variable i. En este
aso, el a
eso debe lo
alizar el bit 40
en el arreglo y es
ribir, no leer, el valor de i. En este
aso, el operador [] debe invo
ar
a write bit().
La implanta
ion del operador [] requiere, enton
es, realizar opera
iones diferentes
segun el tipo de a
eso: le
tura o es
ritura. Esto no se puede realizar en la implanta
ion
de alguna de las de
lara
iones tradi
ionales del operador [], pero s se puede postergar
la distin
ion hasta algun momento en que sea posible
ono
er si el a
eso es de le
tura o
de es
ritura. Tal distin
ion se realiza mediante una
ombina
ion del operador [] y de una
40
40a
Captulo 2. Secuencias
de BitProxy
40 i
public:
ubli
os de BitProxy 40di
hmiembros p
};
Denes:
BitProxy, used in
hunks 40 and 41.
40b
40
El primer operador se invo
a para un BitArray
onstante (de
larado const), mientras
que el segundo para un BitArray que no es
onstante.
El operador de asigna
ion y el
onstru
tor
opia de BitProxy implantan la es
ritura,
mientras que el operador de
onversion de tipo implanta la le
tura.
Cualquiera sea el tipo de a
eso, previamente es ne
esario
al
ular el ndi
e del
byte dentro de array of bytes y el ndi
e del bit dentro del byte. Esta informa
ion la
guardamos en los siguientes atributos:
hmiembros privados de BitProxy 40
i
(40a)
const size_t bit_index;
Byte * byte_ptr;
40d
(40a) 41a
BitProxy(BitArray & array, const size_t & i) : bit_index(i % 8)
{
if (i >= array.num_bits)
throw std::out_of_range ("Bit array size too big");
const size_t byte_index = i/8;
byte_ptr = &array.array_of_bytes[byte_index];
}
Uses BitArray 36 and BitProxy 40a.
2.1. Arreglos
41a
41
byte *byte ptr. Si el ndi
e i esta fuera de rango, enton
es el
onstru
tor dispara la
ex
ep
ion std::out of range().
Notese que al momento de
onstru
ion de un BitProxy aun no se ha realizado el
a
eso, pero s se ha
al
ulado la dire
ion del byte donde se en
uentra el bit (byte ptr)
y se ha determinado
ual es el bit dentro de ese byte (bit index).
Segun el
ontexto de la expresion que invoque al operador [] el
ompilador distingue
si el a
eso es de le
tura o de es
ritura. Si es de le
tura, enton
es el
ompilador tratara de
onvertir el BitProxy a un entero resultante de la le
tura:
hmiembros p
ubli
os de BitProxy 40di+
(40a) 40d 41b
operator int () const
{
return byte_ptr->read_bit(bit_index);
}
41b
Cuando el
ompilador en
uentra una expresion
omo i = array[40], se bus
a un
onstru
tor de int
on parametro BitProxy. Sino, el
ompilador bus
a una asigna
ion
de BitProxy a int. Como no existe ninguno de estos operadores, el
ompilador
onvierte
el proxy a un int.
En la expresion array[40] = i, basta
on denir un operador de asigna
ion de proxy
a partir de un entero tal
omo sigue:
hmiembros p
ubli
os de BitProxy 40di+
(40a) 41a 41
BitProxy& operator = (const size_t & value)
{
byte_ptr->write_bit(bit_index, value);
return *this;
}
Uses BitProxy 40a.
41
return *this;
}
Uses BitProxy 40a.
41d
La
lase BitProxy permite manejar un arreglo de bits
asi exa
tamente de la misma
manera que se usan los arreglos. Sin embargo, esta te
ni
a ni garantiza que siempre sea
posible usar el operador [], ni es la de mas alto desempe~no. Por esa razon exportamos
dos opera
iones expli
itas de le
tura y de es
ritura:
hMiembros p
ubli
os de BitArray 39ai+
(36) 40b 42
int read_bit(const size_t & i) const
{
const int bit_index = i % 8;
42
Captulo 2. Secuencias
return array_of_bytes[i/8].read_bit(bit_index);
}
void write_bit(const size_t & i, const unsigned int & value) const
{
array_of_bytes[i/8].write_bit(i, value);
}
42
A efe
tos de mejor desempe~no, estas opera
iones no realizan veri
a
iones de
orre
titud.
En algunos
ontextos se requieren opera
iones rela
ionadas
on la
opia; es de
ir, el
onstru
tor
opia y la asigna
ion:
hMiembros p
ubli
os de BitArray 39ai+
(36) 41d 43b
BitArray(const BitArray & array) Exception_Prototypes(std::bad_alloc)
: num_bits
(array.num_bits),
num_bytes
(array.num_bytes),
array_of_bytes (new Byte [num_bytes])
{
h
opiar num bytes 43ai
}
2.1. Arreglos
43
delete [] old_array;
h
opiar
return *this;
}
catch (std::bad_alloc)
{
num_bits = old_num_bits;
num_bytes = old_num_bytes;
array_of_bytes = old_array;
throw;
}
}
Uses BitArray 36.
43a
Hay una version adi
ional del
onstru
tor
opia
on un parametro de mas: la
antidad
de bits que se desea
opiar. Por omision, se
opian todos los bits, pero el usuario puede
espe
i
ar esta
antidad si indi
a una
antidad de bits menor o igual que los bits de array.
h
opiar num bytes 43ai
(42)
for (int i = 0; i < num_bytes; ++i)
array_of_bytes[i] = array.array_of_bytes[i];
43b
Como arreglo, un BitArray esta sujeto a las mismas restri
iones aso
iadas, siendo el
prin
ipal obsta
ulo la posibilidad de que la dimension no sea su
iente para guardar los
bits requeridos. Por esa razon, plantearemos una opera
ion de reajuste,
onsistente en la
re-deni
ion de la dimension:
hMiembros p
ubli
os de BitArray 39ai+
(36) 42
void resize(const size_t & new_dim) Exception_Prototypes(std::bad_alloc)
{
const size_t new_num_bytes = new_dim/8 + (((new_dim % 8) != 0) ? 1 : 0);
Byte * new_array_of_bytes = new Byte [new_num_bytes];
for (int i = 0; i < new_num_bytes; ++i)
new_array_of_bytes[i] = array_of_bytes[i];
num_bits = new_dim;
num_bytes = new_num_bytes;
delete [] array_of_bytes;
array_of_bytes = new_array_of_bytes;
}
Uses resize.
44
2.1.4
Captulo 2. Secuencias
Arreglos Din
amicos
Un arreglo dinami
o es aquel en el
ual su dimension puede modi
arse dinami
amente.
Esto es muy util para apli
a
iones que se deben bene
iar del rapido a
eso por posi
ion,
pero que no
ono
en el numero de elementos que se pueden manejar. Por ejemplo, un
tipo espe
ial de re
upera
ion
lave-dire
ion, llamado hashing dinami
o, aumenta y
ontrae progresivamente la dimension de de un arreglo. Esta estru
tura se expli
a en la
se
ion x 5.1.7.
C y C++ no soportan arreglos dinami
os. Esto impli
a que los arreglos dinami
os deben
surtirse por un TAD en bibliote
a.
Ahora bien, >
omo implementar un arreglo dinami
o? La
andidez sugiere que se relo
ali
e el arreglo. Ini
ialmente, se ja una dimension y se aparta memoria para el arreglo.
Si la dimension es al
anzada, enton
es se determina una nueva dimension mayor y se
aparta un nuevo bloque de memoria. Despues, los elementos se
opian al nuevo bloque y,
nalmente, se libera el antiguo bloque.
Este enfoque ha sido utilizado
on exito en algunos sistemas importantes. La bibliote
a
estandar C ofre
e una primitiva, realloc(),
uya sintaxis es la siguiente:
void *realloc(void *ptr, size_t size);
La rutina
ambia el tama~no del bloque de memoria apuntado por ptr, el
ual debe haber
sido obtenido de llamadas previas a malloc(), calloc() o realloc(). El
ontenido de ptr
permane
era inmodi
ado al mnimo posible entre el antiguo y el nuevo tama~no size.
\Teori
amente", realloc() es \inteligente" y
apaz de apartar un bloque mas grande
sin
ambiar la dire
ion de memoria. Esto es interesante porque se evita la
opia de los
elementos del antiguo bloque ha
ia el nuevo bloque. Empero, en la pra
ti
a esto no es
su
iente porque no es una garanta. De he
ho, muy po
as implementa
iones de realloc()
ha
en un esfuerzo por veri
ar si hay memoria
ontigua disponible a ptr.
2.1.5
El TAD DynArray<T>
2.1. Arreglos
45
45
hmiembros
public:
hmiembros
};
publi os de DynArray<T> 49 i
2.1.5.1
una maquina de 32 bits, Pentium III, por ejemplo, un pro
eso dispone de un espa
io de dire
ionamiento de 232 . Como el espa
io de dire
ionamiento debe
ontener el
odigo del programa, sus datos
estati
os y sus datos dinami
os, la
antidad de espa
io disponible es menor que 232 . Ademas, el sistema
operativo reserva una parte del espa
io de dire
ionamiento para s mismo. Un arreglo de 2 Giga enteros
ortos (short) no
abra en un pro
eso. Por otra parte, se requerira al menos una memoria virtual de 4
Giga bytes para albergar este arreglo. Por estas razones,
reemos que 2 Giga entradas es su
iente para la
mayora de las situa
iones.
Lo anterior deja de ser valido en sistemas persistentes donde grandes arreglos son mapeados en dis
o y
en memoria y dinami
amente
argados a la demanda. Las arquite
turas de 64 bits o mas ha
en posible
espa
ios de dire
ionamiento persistentes de envergadura muy grande. Empero, la
onse
u
ion de un
sistema operativo, paradigma de la persisten
ia, es aun un tema muy inmaduro de investiga
ion.
46
Captulo 2. Secuencias
. . .
. . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Bloques
. block size
.
.
.
.
.
dir size
Segmento
. . .
seg size
.
.
.
Directorio
.
.
.
.
.
.
.
.
.
.
.
.
elementos del arreglo. El ndi
e en el dire
torio esta dado por la expresion siguiente:
pos in dir =
i
seg size block size
(2.2)
Utilizaremos el nombre de variable pos in dir
ada vez que requiramos alma
enar
un ndi
e en el dire
torio.
Indice del bloque en el segmento: El resto de la division (2.2), denotado
presa el numero de bloques que faltan para llegar al ndi
e i.
resto, ex-
Puesto que
ada bloque posee block size elementos, la posi
ion dentro del segmento
esta dada por:
resto
pos in seg =
(2.3)
block size
2.1. Arreglos
47
(2.4)
El nombre de variable pos in seg sera utilizado
ada vez que se desee memorizar
un ndi
e de segmento.
Indice del elemento en el bloque: El ndi
e del elemento dentro del bloque se obtiene
a traves del resto de la division (2.3). As pues, el resto de esta division es el numero
de elementos que faltan para llegar al i-esimo elemento dentro del bloque:
pos in block = resto mod block size
(2.5)
47
(2.6)
Estas opera
iones deben efe
tuarse siempre que se desee realizar un a
eso. Puesto
que
ada opera
ion toma un maximo de tiempo
onstante, se garantiza que el a
eso a un
arreglo dinami
o tome un maximo de tiempo que tambien es
onstante.
Para mejorar el tiempo de
al
ulo de (2.2), (2.3) y (2.6) los tama~nos de dire
torio, de
segmento y de bloque son poten
ias exa
tas de dos. De esta forma, la multipli
a
ion y la
division pueden realizarse
on simples desplazamientos.
DynArray<T> utiliza internamente un
onjunto de
onstantes omision
uando se
onstruya un DynArray<T> mediante el
onstru
tor va
o:
hmiembros
onstantes de DynArray<T> 47i
(45) 48a
static
static
static
static
static
static
Denes:
const
const
const
const
const
const
size_t
size_t
size_t
size_t
size_t
size_t
Default_Pow_Dir; /* 16
*/
Default_Pow_Seg; /* 64
*/
Default_Pow_Block; /* 4096 */
Max_Bits_Allowed;
Max_Dim_Allowed;
Max_Pow_Block;
Las primeras tres
onstantes, Default Pow Dir, Default Pow Seg y Default Pow Block
representan las poten
ias de dos por omision de los tama~nos del dire
torio, de los segmentos
y de los bloques.
La
onstante Max Bits Allowed representa la longitud en bits del tipo size t. Notese
que el valor de sizeof(size t) es dependiente del
ompilador o de la arquite
tura. Puesto
que los
al
ulos de los ndi
es de dire
torio, segmento y bloque se realizan mediante desplazamientos de bits, debemos
uidarnos de un desborde. Max Bits Allowed indi
a el
maximo numero de desplazamientos que se puede realizar sobre un dato de tipo size t
sin que este devenga el valor
ero.
La
onstante Max Dim Allowed representa la dimension maxima que puede tener un
arreglo dinami
o. Como ya se indi
o, esta dimension es de 2 Giga elementos.
48
48a
Captulo 2. Secuencias
A efe
tos de ganar tiempo de eje
u
ion, algunos
al
ulos se realizan en tiempo de
onstru
ion y jamas vuelven a modi
arse durante el tiempo de vida de una instan
ia
de DynArray<T>.
hmiembros
onstantes de DynArray<T> 47i+
(45) 47 48b
mutable size_t pow_dir;
mutable size_t pow_seg;
mutable size_t pow_block;
Denes:
pow block, used in
hunks 49{51 and 55{60.
pow dir, used in
hunks 49a, 51a, 55, 56, 59, and 60a.
pow seg, used in
hunks 49a, 51a, 55, 56, 59, and 60a.
Estas
onstantes alma
enan las poten
ias de 2 de las longitudes del dire
torio, segmento
y bloque, la
uales son 2pow dir , 2pow seg y 2pow block , respe
tivamente.
La dimension maxima resultante es 2pow dir 2pow seg 2pow block . Si este
al
ulo resulta
mayor que la maxima dimension permitida Max Dim Allowed, enton
es se genera la ex
ep
ion std::length error. Si no hay
apa
idad numeri
a para efe
tuar las opera
iones
mediante desplazamientos, enton
es se genera la ex
ep
ion std::overflow error.
Antes de
ontinuar, tenganse en
uenta las siguientes igualdades:
(2.7)
(2.8)
2pow seg
i
i
= (pow seg+pow block)
pow
block
2
2
(2.9)
(45) 48a 48
El valor 2(pow seg+pow block) se guarda previamente en seg plus block pow para futuros
al
ulos.
Sustituyendo (2.7) y (2.8) en (2.4), tenemos:
resto = i mod (2pow seg 2pow block ) = i mod 2(pow seg+pow block)
(2.10)
48
(2.11)
El resto esta dado por los seg plus block pow bits menos signi
ativos de i. Para
ono
er los seg plus block pow bits menos signi
ativos
onstruimos un numero
on los
seg plus block pow bits menos signi
ativos en uno y los restantes en
ero.
hmiembros
onstantes de DynArray<T> 47i+
(45) 48b 49a
mutable size_t mask_seg_plus_block;
Denes:
mask seg plus block, used in
hunks 50b, 55, 56, 59, and 60a.
2.1. Arreglos
49
(2.12)
En base binaria, 2seg plus block pow
ontiene puros
eros ex
epto un uno en la
posi
ion seg plus block pow. Como hay seg plus block pow
eros antes de llegar al
uni
o uno,
ada bit provo
a un presto que es por n pagado
uando se llega al bit en
seg plus block pow. As pues, este n
umero tiene los primeros seg plus block pow en
uno y los restantes en
ero. El resto en la expresion (2.10) puede
al
ularse
on un AND
logi
o
uyo operador en C++ es &:
resto = i AND mask seg plus block = i & mask seg plus block
(2.13)
Es
omun denotar a la variable mask seg plus block
omo una \mas
ara" porque
49a
(45) 48 51a
49b
Estas variables alma
enan las longitudes del dire
torio, segmento y bloque. Como son
poten
ias exa
tas de dos, ellas se
al
ulan mediante la siguiente fun
ion:
hmiembros privados de DynArray<T> 49bi
(45) 50a
static size_t two_raised(const size_t & n)
{
if (n >= Max_Bits_Allowed)
throw std::overflow_error ("number of bits exceeds de maximun allowed");
return 1 << n;
}
Denes:
two raised() al ula 2n desplazando n ve es a la unidad. Como todas las opera iones de
49
bits son
ompli
adas y sus
eptibles de error, siempre
olo
aremos asertos que veriquen si
la opera
ion realizada
on bits
orresponde a la opera
ion realizada en aritmeti
a
orriente.
El
liente tambien podra querer observar las longitudes del dire
torio, segmento y
bloques resultantes. Para ello, la interfaz de DynArray<T> propor
iona los siguientes observadores:
hmiembros p
ubli
os de DynArray<T> 49
i
(45) 51
const size_t & get_dir_size() const { return dir_size; }
const size_t & get_seg_size() const { return seg_size; }
const size_t & get_block_size() const { return block_size; }
Denes:
get block size, never used.
50
Captulo 2. Secuencias
50a
Estas fun
iones observan las longitudes del dire
torio, segmento y bloque, respe
tivamente.
hmiembros privados de DynArray<T> 49bi+
(45) 49b 50b
size_t mask_seg;
size_t mask_block;
Denes:
mask block, used in
hunks 50b, 55, 56, and 58{60.
mask seg, used in
hunks 55, 56, 59, and 60a.
(2.14)
(2.15)
(2.16)
Para
al
ular el ndi
e dentro de un bloque (2.5), hay que
al
ular el resto de dividir
entre block size. Para ello, utilizamos la mas
ara mask block. De esta forma, (2.6) se
plantea
omo sigue:
pos in block = (i & mask seg plus block) & mask block
50b
(2.17)
Ahora podemos implantar e
ientemente los a
esos al dire
torio, segmento y bloque
expresados. Para ello, aislaremos los
al
ulos en fun
iones separadas
on nombres que
indiquen
laramente los
al
ulos en
uestion:
hmiembros privados de DynArray<T> 49bi+
(45) 50a 51b
size_t index_in_dir(const size_t & i) const
{
return i >> seg_plus_block_pow;
}
2.1. Arreglos
51
{
return modulus_from_index_in_dir(i) & mask_block;
}
Denes:
index in block, used in
hunks 60{62 and 67b.
index in dir, used in
hunks 60{64 and 67b.
index in seg, used in
hunks 60{64 and 67b.
modulus from index in dir, never used.
Uses mask block 50a, mask seg plus block 48
, pow block 48a, and seg plus block pow 48b.
51a
index in dir()
al
ula el ndi
e dentro del dire
torio dado el ndi
e del arreglo i
mediante la expresion (2.11).
index in seg()
al
ula el ndi
e dentro del segmento dado el ndi
e del arreglo i segun la (2.15). Esta fun
ion requiere el valor resto que
orresponde
a modulus from index in dir()) segun (2.13).
index in block()
al
ula el ndi
e dentro del bloque dado el ndi
e i del arreglo
segun (2.17).
Todas estas fun
iones estan inundadas de asertos que nos
orroboran que nuestras
suposi
iones fueron
orre
tas y que los
al
ulos
on bits son equivalentes a los
al
ulos en
aritmeti
a normal.
Como las fun
iones son inline, la se
uen
ia interna de opera
iones se expone
ompletamente al optimizador del
ompilador, el
ual es
apaz de efe
tuar mu
has optimiza
iones,
por ejemplo, la de memorizar el resto de la division efe
tuada en el
al
ulo del ndi
e del
dire
torio.
hmiembros
onstantes de DynArray<T> 47i+
(45) 49a
mutable size_t max_dim;
// 2^(pow_dir + pow_seg + pow_block) - 1
Denes:
max dim, used in
hunks 51
, 55, 56, 59{61, 63, and 66b.
Uses pow block 48a, pow dir 48a, and pow seg 48a.
max dim alma
ena la maxima dimension que puede al
anzar el arreglo seg
un las longitudes
51b
size_t current_dim;
size_t num_segs;
size_t num_blocks;
Denes:
current dim, used in
hunks 51
, 54{56, 58{60, 62{64, and 69a.
num blocks, used in
hunks 51
, 53{56, 59, and 60a.
num segs, used in
hunks 52
, 54{56, 59, and 60a.
current dim alma ena la dimension a tual. num segs y num blocks ontabilizan el total
51
52
Captulo 2. Secuencias
2.1.5.2
Manejo de memoria
apartada. Esto impli
a que
uando un bloque es liberado, la entrada en el dire
torio
debe ser restaurada al valor NULL.
52a
La entrada a al dire
torios, segmento y bloque
omienza a partir del dire
torio, el
ual
se de
lara
omo sigue:
hmiembros privados de DynArray<T> 49bi+
(45) 51b 52b
T *** dir;
El manejo de memoria se realiza mediante metodos privados espe ializados que on entran los detalles de manejo de memoria en puntos uni os, sen illos de depurar y mantener:
52b
(45) 52a 52
fill dir to null() asegura que todas las entradas del dire torio sean nulas. El valor NULL
indi
a que la entrada del dire
torio no posee un segmento reservado. Analogamente,
un NULL en una entrada de un segmento indi
ara que la entrada no posee un bloque
reservado.
fill seg to null() ini
ializa todas la entradas del segmento apuntado por seg
al valor NULL. fill dir to null() se llama
uando se
rea el dire
torio y
fill seg to null()
uando se
rea un nuevo segmento.
hmiembros privados de DynArray<T> 49bi+
(45) 52b 53a
8
52
void allocate_dir()
{
try
{
dir = new T ** [dir_size];
fill_dir_to_null();
8 El
2.1. Arreglos
53
}
catch (...)
{
dir = NULL;
throw;
}
}
void allocate_segment(T **& seg)
{
seg = new T* [seg_size];
fill_seg_to_null(seg);
++num_segs;
}
Denes:
allocate dir, used in
hunks 55, 56, and 59.
allocate segment, used in
hunks 62, 63, and 69b.
Uses dir size 49a, fill dir to null 52b, fill seg to null 52b, num segs 51b, and seg size 49a.
allocate dir() reserva memoria para el dire
torio y ja todas sus entradas en NULL.
allocate segment() asigna al puntero seg la dire
ion de memoria de un nuevo segmento
uyas entradas estan ini
ializadas en NULL.
Las entradas del dire
torio y de los segmentos siempre deben ini
ializarse en NULL.
dir[i] == NULL indi
a que no se ha apartado un segmento; mientras que dir[i][j] ==
NULL indi
a que no se ha apartado el bloque.
53a
T * default_initial_value_ptr;
Denes:
default initial value, used in
hunks 53b and 54a.
default initial value ptr, used in
hunks 53{56 and 59.
53b
default initial value alma
ena el valor ini
ial, mientras que default initial value ptr
un puntero a la
elda anterior. Por omision, este puntero es NULL, de modo tal que, tambien
por omision, no se reali
e un ini
ializa
ion expl
ita, sino la impl
ita subya
ente al
onstru
tor por omision T::T(). Por esta razon, todos los
onstru
tores de DynArray<T>
ini
ializan default initial value ptr en NULL
hmiembros privados de DynArray<T> 49bi+
(45) 53a 54b
void allocate_block(T *& block)
{
block = new T [block_size];
++num_blocks;
if (default_initial_value_ptr == NULL)
return;
for (size_t i = 0; i < block_size; ++i)
54
Captulo 2. Secuencias
block[i] = default_initial_value;
}
Denes:
allocate block, used in
hunks 62, 63, and 69
.
Uses block size 49a, default initial value 53a, default initial value ptr 53a, and num blocks 51b.
allocate block() asigna al puntero block la dire
ion de memoria de un nuevo bloque.
Segun que se haya o no espe
i
ado un valor por omision, se re
orrera block y se le asignara
el valor default initial value; lo que exige la existen
ia del operador de asigna
ion T\&
operator = (const T&).
54a
set default initial value() tiene mu
ha utilidad algebrai
a para situa
iones en las
uales el elemento neutro es distinto al dado por el
onstru
tor por omision T::T().
54b
(45) 53b 54
54
2.1. Arreglos
55
release_segment(seg);
}
void release_all_segments_and_blocks()
{
for(size_t i = 0; i < dir_size ; ++i)
if (dir[i] != NULL)
release_blocks_and_segment(dir[i]);
current_dim = 0;
}
void release_dir()
{
release_all_segments_and_blocks();
delete [] dir;
dir = NULL;
current_dim = 0;
}
Denes:
2.1.5.3
55
Especificaci
on e implantaci
on de m
etodos p
ublicos
Comenzamos por implementar un
onstru
tor que re
ibe las poten
ias de dos del dire
torio, segmento y bloque:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 54a 56
DynArray(const size_t & _pow_dir,
const size_t & _pow_seg,
const size_t & _pow_block)
throw (std::exception, std::bad_alloc,
std::length_error, std::overflow_error)
: pow_dir
( _pow_dir
pow_seg
( _pow_seg
pow_block
( _pow_block
seg_plus_block_pow ( _pow_seg + _pow_block
mask_seg_plus_block ( two_raised(seg_plus_block_pow) - 1
dir_size
( two_raised(pow_dir)
seg_size
( two_raised(pow_seg)
block_size
( two_raised(pow_block)
max_dim
( two_raised(seg_plus_block_pow + pow_dir)
mask_seg
( seg_size - 1
mask_block
( block_size - 1
current_dim
( 0
num_segs
( 0
num_blocks
( 0
default_initial_value_ptr (NULL)
),
),
),
),
),
),
),
),
),
),
),
),
),
),
56
Captulo 2. Secuencias
{
if (max_dim > Max_Dim_Allowed)
throw std::length_error ("Dimension too large");
allocate_dir();
}
Uses allocate dir 52
, block size 49a, current dim 51b, default initial value ptr 53a,
dir size 49a, DynArray 45, mask block 50a, mask seg 50a, mask seg plus block 48
, max dim 51a,
Max Dim Allowed 47, num blocks 51b, num segs 51b, pow block 48a, pow dir 48a, pow seg 48a,
seg plus block pow 48b, seg size 49a, and two raised 49b.
56
on
hDetermina
i
),
),
),
),
),
),
),
),
),
),
),
),
),
{
if (max_dim > Max_Dim_Allowed)
throw std::length_error ("Dimension too large");
allocate_dir();
}
Uses allocate dir 52
, block size 49a, current dim 51b, default initial value ptr 53a,
Default Pow Dir 47, Default Pow Seg 47, dir size 49a, DynArray 45, mask block 50a, mask seg 50a,
mask seg plus block 48
, max dim 51a, Max Dim Allowed 47, num blocks 51b, num segs 51b,
pow block 48a, pow dir 48a, pow seg 48a, seg plus block pow 48b, seg size 49a, and two raised 49b.
La uni
a diferen
ia
on el
onstru
tor anterior es que este debe sele
ionar una longitud
de bloque. Mientras mas grande sea esta longitud, menor sera la
antidad de reserva
iones
2.1. Arreglos
57
de memoria
ausadas por la expansion del arreglo. Un tama~no de bloque muy peque~no
impli
ara que nuevos bloques tendran que apartarse muy a menudo. En a~nadidura, la
maxima dimension del arreglo sera muy limitada. Por esta razon debe existir un tama~no
del bloque mnimo que se expresa en la
onstante Default Pow Block, la
ual fue denida
en hmiembros
onstantes de DynArray<T> 47i.
Debemos privilegiar una longitud de bloque mas o menos grande. En prin
ipio, se sele
iona una o
tava parte de la dimension ini
ial espe
i
ada en el
onstru
tor. El uni
o
problema o
urre
uando el usuario sugiere una dimension ini
ial muy peque~na, lo que a
arreara un tama~no de bloque muy peque~no. Para evitar esto, sele
ionamos una poten
ia
de 2 de base
omo longitud mnima del bloque. Como se espe
i
o en hmiembros
onstantes de DynArray<T> 47i, tal poten
ia sera el valor de 12, equivalente a 4096 entradas,
lo que arroja una dimension maxima por omision de 24 + 26 + 212 = 222 = 4194304.
En una maquina de 32 bits, Max Bits Allowed = 32. Enton
es, restaran 32 22 1
poten
ias de dos de mas para un bloque antes de llegar a Max Bits Allowed. La maxima
poten
ia de dos del tama~no de un bloque sera:
(2.18)
Lo que ha
e una maxima dimension dire
ionable
on el
onstru
tor por omision de:
2Default
57a
Pow Dir
dimension mas que su
iente para la mayora de los arreglos que quepan en memoria.
La maxima poten
ia de dos del bloque es alma
enada en una
onstante
al
ulada
segun (2.18).
As pues, se debe garantizar que Default Pow Block pow block Max Pow Block.
Esta expresion se tradu
e del siguiente modo:
hDetermina
i
on de la poten
ia de 2 del bloque 57ai
(56)
pow_block
(std::min(std::max(next2Pow(dim/8), Default_Pow_Block),
Max_Pow_Block)
),
Uses Default Pow Block 47, Max Pow Block 47, next2Pow 58a, and pow block 48a.
57b
Este
al
ulo primero divide la dimension propuesta entre o
ho y luego lleva este resultado
a la proxima poten
ia de dos. Esta sera la poten
ia de dos del bloque a menos que sea
mas peque~na que Default Pow Block o mas grande que Max Pow Block.
hIni
ia
i
on
onstantes DynArray<T> 57bi
(45)
template
template
template
template
template
<typename
<typename
<typename
<typename
<typename
T>
T>
T>
T>
T>
const
const
const
const
const
size_t
size_t
size_t
size_t
size_t
DynArray<T>::Default_Pow_Dir
DynArray<T>::Default_Pow_Seg
DynArray<T>::Default_Pow_Block
DynArray<T>::Max_Bits_Allowed
DynArray<T>::Max_Dim_Allowed
=
=
=
=
=
4; /* 16
*/
6; /* 64
*/
12; /* 4096 */
8 * sizeof(size_t);
2*1024*1024*1024ul;
58
58a
Captulo 2. Secuencias
El
al
ulo de la proxima poten
ia de dos de un numero es realizado por la fun
ion
estati
a next2Pow:
hmiembros privados de DynArray<T> 49bi+
(45) 54
58d
static size_t next2Pow(const size_t & number)
{
return static_cast<size_t>(ceil(log(static_cast<float>(number))/log(2.0)));
}
Denes:
next2Pow, used in
hunk 57a.
58b
(45) 56 58
~DynArray()
{
release_dir();
}
Uses DynArray 45.
58
El
onstru
tor
opia y el operador de asigna
ion realizan una opera
ion
omun: las
reserva
iones de memoria y
opias del dire
torio, segmentos y bloques del arreglo destino,
sea por el
onstru
tor o por la asigna
ion. Por ello, en
apsulamos esta opera
ion en un
metodo privado
omun llamado copy array():
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 58b 59
58d
di
ha entrada exista; es de
ir, que tenga un segmento y un bloque apartados. No hay ne
esidad de veri
ar que haya memoria porque esto fue veri
ado
on src array.exist(i).
El operando izquierdo de la asigna
ion, (*this)[i] = ... aparta el segmento o el bloque
si se requiere.
Notemos que copy array() re
orre
ada posi
ion de los arreglos. Hay una manera
mu
ho mas e
iente, pero mas dif
il de implantar que se valga de la
opia dire
ta
de bloques. La di
ultad estriba
uando los arreglos tienen tama~nos de segmentos y
bloques diferentes. Tal implanta
ion se delega a ejer
i
io. Como ayuda, se provee el
metodo advance block index(), el
ual, dados los ndi
es del segmento y del bloque,
al
ula los ndi
es
orrespondientes a len bloques:
hmiembros privados de DynArray<T> 49bi+
(45) 58a 66a
size_t divide_by_block_size(const size_t & number) const
{
return number >> pow_block;
}
2.1. Arreglos
59
59
Denida la
opia entre arreglos, podemos implantar fa
ilmente el
onstru
tor
opia y el
operador de asigna
ion:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 58
60a
DynArray(const DynArray<T> & array)
throw (std::exception, std::bad_alloc)
: pow_dir
(array.pow_dir),
pow_seg
(array.pow_seg),
pow_block
(array.pow_block),
seg_plus_block_pow
(array.seg_plus_block_pow),
mask_seg_plus_block
(array.mask_seg_plus_block),
dir_size
(array.dir_size),
seg_size
(array.seg_size),
block_size
(array.block_size),
max_dim
(array.max_dim),
mask_seg
(array.mask_seg),
mask_block
(array.mask_block),
current_dim
(0),
num_segs
(0),
num_blocks
(0),
default_initial_value_ptr (NULL)
{
allocate_dir();
copy_array(array);
}
DynArray<T> & operator = (const DynArray<T> & array)
throw (std::exception, std::bad_alloc)
60
Captulo 2. Secuencias
{
if (this == &array)
return *this;
copy_array(array);
if (array.current_dim < current_dim)
cut(array.current_dim);
current_dim = array.current_dim;
return *this;
}
Uses allocate dir 52
, block size 49a, copy array 58
, current dim 51b, cut 64,
default initial value ptr 53a, dir size 49a, DynArray 45, mask block 50a, mask seg 50a,
mask seg plus block 48
, max dim 51a, num blocks 51b, num segs 51b, pow block 48a, pow dir 48a,
pow seg 48a, seg plus block pow 48b, and seg size 49a.
60a
El operador de asigna
ion utiliza el metodo cut(), el
ual se invo
a si la dimension del
arreglo origen (array) es menor que la del destino. cut() libera la memoria ex
edente.
La ultima fun
ion de esta se
ion es swap(),
uya implanta
ion es muy simple y su
eje
u
ion muy rapida:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 59 60b
void swap(DynArray<T> & array)
{
Aleph::swap(dir, array.dir);
Aleph::swap(pow_dir, array.pow_dir);
Aleph::swap(pow_seg, array.pow_seg);
Aleph::swap(pow_block, array.pow_block);
Aleph::swap(seg_plus_block_pow, array.seg_plus_block_pow);
Aleph::swap(mask_seg_plus_block, array.mask_seg_plus_block);
Aleph::swap(dir_size, array.dir_size);
Aleph::swap(seg_size, array.seg_size);
Aleph::swap(block_size, array.block_size);
Aleph::swap(mask_seg, array.mask_seg);
Aleph::swap(mask_block, array.mask_block);
Aleph::swap(max_dim, array.max_dim);
Aleph::swap(current_dim, array.current_dim);
Aleph::swap(num_segs, array.num_segs);
Aleph::swap(num_blocks, array.num_blocks);
}
Denes:
DynArray::swap, never used.
Uses block size 49a, current dim 51b, dir size 49a, DynArray 45, mask block 50a, mask seg 50a,
mask seg plus block 48
, max dim 51a, num blocks 51b, num segs 51b, pow block 48a, pow dir 48a,
pow seg 48a, seg plus block pow 48b, and seg size 49a.
60b
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 60a 61a
T & access(const size_t & i)
{
return dir[index_in_dir(i)][index_in_seg(i)][index_in_block(i)];
}
2.1. Arreglos
61
access() no veri a que la memoria del bloque y su segmento haya sido apartada. Este
61a
metodo debe usarse solo
uando se este absolutamente seguro de que se ha es
rito en la
posi
ion de a
eso i.
Para indagar si una entrada dada puede a
ederse sin error; es de
ir, que esta tenga
su bloque y su segmento, se provee:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 60b 61
bool exist(const size_t & i) const
throw (std::exception, std::out_of_range)
{
hveri
ar rango 61bi
61b
(61a 62)
exist() retorna true si la posi ion i tiene su bloque y segmento; false, de lo on-
61
trario.
En mu
has o
asiones, se requiere veri
ar existen
ia de una entrada y, si esta existe,
enton
es realizar el a
eso. Para evitar ahorrar el doble
al
ulo de ndi
es en dire
torio,
segmento y bloque, podemos exportar la primitiva siguiente:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 61a 62
T * test(const size_t & i)
{
const size_t pos_in_dir = index_in_dir(i);
if (dir[pos_in_dir] == NULL)
62
Captulo 2. Secuencias
return NULL;
const size_t pos_in_seg = index_in_seg(i);
if (dir[pos_in_dir][pos_in_seg] == NULL)
return NULL;
return &dir[index_in_dir(i)][index_in_seg(i)][index_in_block(i)];
}
Uses index in block 50b, index in dir 50b, index in seg 50b, pos in dir 67a, and pos in seg 67a.
62
Esta es la manera mas rapida posible de a
eder a una entrada del arreglo dinami
o.
Notemos, sin embargo, que no se realiza ninguna veri
a
ion. Si la entrada existe, enton
es
test() retorna un puntero a la entrada; NULL de lo
ontrario.
Para apartar expl
itamente el bloque y segmento de una posi
ion dentro del arreglo,
se provee:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 61
63
T & touch(const size_t & i)
throw (std::exception, std::bad_alloc, std::out_of_range)
{
hveri
ar rango 61bi
const size_t pos_in_dir = index_in_dir(i);
if (dir[pos_in_dir] == NULL)
allocate_segment(dir[pos_in_dir]);
const size_t pos_in_seg = index_in_seg(i);
if (dir[pos_in_dir][pos_in_seg] == NULL)
{
try
{
allocate_block(dir[pos_in_dir][pos_in_seg]);
}
catch (...)
{
release_segment(dir[pos_in_dir]);
throw;
}
}
if (i >= current_dim)
current_dim = i + 1;
return dir[pos_in_dir][pos_in_seg][index_in_block(i)];
}
Denes:
touch, used in
hunks 507b, 509a, 510b, 760, and 765b.
Uses allocate block 53b, allocate segment 52
, current dim 51b, index in block 50b,
index in dir 50b, index in seg 50b, pos in dir 67a, pos in seg 67a, and release segment 54b.
2.1. Arreglos
63
63
Tambien puede apartarse toda la memoria requerida para garantizar el a
eso dire
to
a un rango de posi
iones. Esto se realiza mediante el siguiente metodo:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 62 64
void reserve(const size_t & l, const size_t & r)
throw (std::exception, std::bad_alloc, std::domain_error, std::length_error)
{
if (l > r)
throw std::domain_error("invalid range");
if (r > max_dim)
throw std::length_error ("dimension too large");
const size_t first_seg = index_in_dir(l);
const size_t last_seg = index_in_dir(r);
const size_t first_block = index_in_seg(l);
const size_t last_block = index_in_seg(r);
try
{
for (size_t seg_idx = first_seg; seg_idx <= last_seg; ++seg_idx)
{
if (dir[seg_idx] == NULL)
allocate_segment(dir[seg_idx]);
size_t block_idx = (seg_idx == first_seg) ? first_block : 0;
const size_t final_block =
(seg_idx == last_seg) ? last_block : seg_size - 1;
while (block_idx <= final_block)
{
if (dir[seg_idx][block_idx] == NULL)
allocate_block(dir[seg_idx][block_idx]);
++block_idx;
}
} // end for (...)
if (r + 1 > current_dim)
current_dim = r + 1;
}
catch (...)
{
if (r + 1 > current_dim)
current_dim = r + 1;
throw;
}
}
Denes:
64
Captulo 2. Secuencias
Uses allocate block 53b, allocate segment 52
, current dim 51b, index in dir 50b, index in seg 50b,
max dim 51a, and seg size 49a.
reserve() aparta los bloques y segmentos que abar an el rango de posi iones entre l
64
y r.
De la misma manera en que se puede apartar memoria para asegurar a
eso a determinadas posi
iones, tambien es plausible indi
ar que se libere la memoria de un rango
de posi
iones que el usuario determine que no usara mas. El metodo cut() ajusta la dimension del arreglo a una dimension inferior llamada new dim y libera toda la memoria
entre new dim y la antigua dimension:
hmiembros p
ubli
os de DynArray<T> 49
i+
(45) 63 66b
void cut(const size_t & new_dim = 0)
throw (std::exception, std::domain_error)
{
if (new_dim > current_dim)
throw std::domain_error("new dimension is greater that current dimension");
if (new_dim == 0)
{
release_all_segments_and_blocks();
current_dim = 0;
return;
}
const size_t old_dim = current_dim; // guarda antigua dimensi
on
//
ndices cotas de bloques
const int idx_first_seg
= index_in_dir(old_dim - 1);
const int idx_first_block = index_in_seg(old_dim - 1);
//
ndices cotas de segmentos
const int idx_last_seg
= index_in_dir(new_dim - 1);
const int idx_last_block = index_in_seg(new_dim - 1);
// recorre descendentemente los segmentos
for (int idx_seg = index_in_dir(old_dim - 1); idx_seg >= idx_last_seg;
--idx_seg)
{
if (dir[idx_seg] == NULL) // hay un segmento?
continue;
hLiberar
hLiberar
el segmento
65ai
65ei
2.1. Arreglos
65
La libera
ion de bloques es algo deli
ada porque hay que manejar
asos parti
ulares
para el ultimo y primer bloque . idx block lleva el ndi
e del bloque en el segmento
a
tual idx seg. Para el valor ini
ial dentro del segmento, hay dos
asos posibles:
9
1. Si se trata del primer segmento, enton
es idx block
omienza en idx first block.
Este
aso o
urre una sola vez
uando idx seg == idx first seg.
2. idx block
omienza en block size 1. Este es el
aso
omun y se veri
a
uando idx seg != idx first seg.
65a
(64) 65d
// primer bloque a liberar
int idx_block = idx_seg == idx_first_seg ? idx_first_block : block_size - 1;
Uses block size 49a.
Tambien hay dos
asos posibles para el valor nal de idx block:
1. Si no nos en
ontramos en el ultimo segmento (idx last seg), enton
es el ultimo
bloque sera el de ndi
e 0. La
ondi
ion de parada sera, enton
es, la siguiente:
hSe est
e en bloque de un segmento general 65bi
(65d)
65b
(idx_seg > idx_last_seg and idx_block >= 0)
2. Si nos en
ontramos en el ultimo segmento, enton
es el ultimo bloque sera idx last block.
Esta
ondi
ion la dete
tamos mediante el predi
ado:
hSe est
e en bloque del ultimo segmento 65
i
(65d)
65
(idx_seg == idx_last_seg and idx_block > idx_last_block)
65d
El bu
le que libera des
endentemente el bloque del segmento a
tual se dene, enton
es,
omo sigue:
hLiberar des
endentemente los bloques del segmento 65ai+
(64) 65a
// Libera descendentemente los bloques reservados del segmento
while ( hSe est
e en bloque de un segmento general 65bi or
e en bloque del ultimo segmento 65
i )
hSe est
{
if (dir[idx_seg][idx_block] != NULL) // Hay un bloque aqu
?
release_block(dir[idx_seg][idx_block]);
--idx_block;
}
Uses release block 54b.
65e
El segmento solo debe liberarse si todos sus bloques lo han sido. Esto se veri
a si ha
re
orrido
ompletamente el segmento a
tual:
hLiberar el segmento 65ei
(64)
if (idx_block < 0)
release_segment(dir[idx_seg]);
Uses release segment 54b.
9 La
inversion es adrede porque la libera ion de bloques se efe tua des endentemente.
66
Captulo 2. Secuencias
2.1.5.4
66a
Hasta el presente, las interfa
es de a
eso a los elementos de un arreglo dinami
o estan
dise~nadas para efe
tuar el a
eso en tiempo
onstante de la manera mas rapida posible.
access() no veri
a si la memoria ha sido apartada, razon por la
ual el usuario debe
expl
itamente veri
arla a traves de exist() o apartarla mediante touch() o reserve().
Una forma mas simple y transparente de manipular un arreglo dinami
o, en detrimento
de un po
o de tiempo, es a traves del operador []. Ini
ialmente, el arreglo se
onstruye
on tan solo el dire
torio apartado. Paulatinamente, a medida que se es
riben datos en
las posi
iones, se apartan, perezosamente, los segmentos y bloques
orrespondientes a las
entradas que han sido es
ritas. De esta manera, la memoria o
upada por el DynArray<T>
es propor
ional a las entradas es
ritas y no a su dimension.
Para implantar esta fun
ionalidad, requerimos distinguir los a
esos de le
tura de
los de es
ritura. Para ello, usaremos una
lase mediadora Proxy, tal
omo se expli
o
en x 2.1.3 (pagina 40):
hmiembros privados de DynArray<T> 49bi+
(45) 58d
class Proxy
{
hMiembros
public:
hM
etodos
};
66b
Si se efe
tua un a
eso de le
tura fuera de la dimension a
tual, enton
es se genera std::length error. Si se realiza un a
eso de es
ritura, aun fuera de la dimension
a
tual, enton
es, se veri
a si existen el bloque y el segmento; si no es el
aso, enton
es estos
2.1. Arreglos
67a
67
67b
index es el ndi
e del elemento que el proxy a
ede. pos in dir, pos in seg
y pos in block son los ndi
es en el dire
torio, segmento y bloque
orrespondientes
a index. Estos valores se
al
ulan durante la
onstru
ion del proxy.
ref seg es una referen
ia a la entrada pos in dir del dire
torio. Debe ser una referen
ia porque esta es sus
eptible de modi
arse. Dada esta referen
ia, ref seg[pos in seg]
denota el apuntador al bloque y ref seg[pos in seg][pos in block] denota el elemento del arreglo
orrespondiente al ndi
e index. block es un apuntador igualado
a ref seg[pos in seg][pos in block]. array es una referen
ia al DynArray<T> utilizada para observar y modi
ar su estado.
hMiembros dato del proxy 67ai se ini
ian en el
onstru
tor del proxy
omo sigue:
hM
etodos del proxy 67bi
(66a) 68a
Proxy(DynArray<T>& _array, const size_t & i)
: index
( i
),
pos_in_dir ( _array.index_in_dir(index) ),
pos_in_seg ( _array.index_in_seg(index) ),
pos_in_block( _array.index_in_block(index) ),
ref_seg
( _array.dir[pos_in_dir]
),
block
( NULL
),
array
( _array
)
{
if (ref_seg != NULL)
block = ref_seg[pos_in_seg]; // ya existe un bloque para la entrada i
}
68
Captulo 2. Secuencias
Uses DynArray 45, index in block 50b, index in dir 50b, index in seg 50b, pos in block 67a,
pos in dir 67a, pos in seg 67a, and ref seg 67a.
i es el ndi
e del DynArray<T>array al
ual el proxy ha
e referen
ia. Las posi
iones se
al
ulan segun las fun
iones auxiliares de DynArray<T>. Si la referen
ia ref seg
ontiene
un apuntador valido, enton
es block se ini
ializa para apuntar al bloque.
68a
return block[pos_in_block];
}
Uses pos in block 67a.
block == NULL indi
a que no existe un bloque para el ndi
e index. Si no hay bloque,
enton
es la le
tura es invalida y se genera la ex
ep
ion std::invalid argument. De lo
68b
y a tualizar la dimension
69ai
block[pos_in_block] = data;
return
*this;
}
Proxy & operator = (const Proxy & proxy)
{
if (proxy.block == NULL) // operando derecho puede leer?
throw std::invalid_argument("right entry has not been still written");
if (&proxy == this)
return *this;
hObtener
hVeri
ar
69ai
block[pos_in_block] = proxy.block[proxy.pos_in_block];
2.1. Arreglos
69
return *this;
}
Uses pos in block 67a.
69a
Es de
ir, asigna
ion del tipo generi
o T a un proxy, por ejemplo array[i] = variable; o
asigna
ion de proxy a proxy, por ejemplo array[i] = array[j]. En el ultimo
aso se debe
veri
ar que la le
tura del operando dere
ho sea valida,
ual es el sentido del primer if.
De resto, las dos asigna
iones son muy similares y se remiten a hObtener o apartar
el segmento y el bloque 69bi en donde se es
ribira y luego a hVeri
ar y a
tualizar la
dimension 69ai.
La dimension a
tual solo se a
tualiza si se es
ribe en una posi
ion mayor que la dimension a
tual:
hVeri
ar y a
tualizar la dimensi
on 69ai
(68b)
if (index >= array.current_dim)
array.current_dim = index + 1;
Uses current dim 51b.
hObtener
1. Obtener el segmento:
hObtener o apartar el segmento y el bloque 69bi
69b
(68b) 69
seg was allocated in current call memoriza si se aparta memoria para el seg-
mento. Esto permite determinar si hay que liberar o no el segmento en
aso de que
o
urra una ex
ep
ion si se aparta el bloque.
69
2. Obtener bloque:
hObtener o apartar el segmento y el bloque 69bi+
(68b) 69b
}
Uses allocate block 53b, pos in seg 67a, ref seg 67a, and release segment 54b.
70
Captulo 2. Secuencias
Si o
urre una falla de memoria durante la reserva
ion del segmento, la ex
ep
ion std::bad alloc, generada por new y regenerada por allocate segment() o
allocate block(), puede dejarse sin
apturar. Si la falla de memoria o
urre durante
la reserva
ion del bloque, enton
es la ex
ep
ion std::bad alloc, generada por new, debe
apturarse para eventualmente poder liberar la memoria del segmento que fue apartada
por la llamada a
tual. Esta es la razon por la
ual hay un manejador de ex
ep
ion para la
reserva
ion del bloque.
2.1.5.5
70
El TAD DynArray<T> puede ser util para modelizar arreglos espar
idos; por ejemplo, una matriz espar
ida que
ontiene mu
hos
eros. En este
aso podemos usar un
DynArray<T> mat[n*n]. El a
eso a una entrada de la matriz puede realizarse mediante
el operador (i,j) o mediante el uso de una
lase proxy. Una seudo-implanta
ion sugerida
del a
eso de le
tura a un TAD Matriz<T> es
omo sigue:
ha
eso matriz 70i
template <typename T>
const T & Matriz<T>::operator () (const long & i, const long &j) const
{
const int & n = mat.size();
71
2.2
Arreglos multidimensionales
En una muy
ierta medida, los arreglos son reminis
entes a los ve
tores de la matemati
a.
En este sentido, es posible representar una matriz n m mediante un arreglo de n de arreglos de dimension m; es de
ir, una se
uen
ia de se
uen
ias.
En C++ una matriz de este tipo se de
lara del siguiente modo:
T matriz[n][m];
El lenguaje implanta el a
eso a una entrada de la matriz mediante
ombina
iones del
operador []. Por ejemplo, la expresion:
matriz[i][j] = dato;
(2.19)
En la de
lara
ion anterior, el
ompilador debe bus
ar un espa
io
ontiguo lo su
ientemente grande para albergar los n m elementos de la matriz. Conforme aumentan n y m,
disminuye la posibilidad de en
ontrar tal espa
io.
Por otra parte, en C++ la matriz anterior es estati
a en el sentido de que no es posible
ambiar su dimension. El
al
ulo (2.19) generado por el
ompilador asume que n y m
10 V
ease x
72
Captulo 2. Secuencias
Aleph::DynArray
Aleph::DynArray::Proxy
0..1
+array
index : size_t
pos_in_dir : size_t
pos_in_seg : size_t
pos_in_block : size_t
ref_seg : T**&
block : T*
array : DynArray&
+ Proxy(_array : DynArray< T >&, i : const si
+ operator T &()
+ operator =(data : const T&) : Proxy&
+ operator =(proxy : const Proxy&) : Proxy&
2.3. Iteradores
73
Este esquema es de entrada mas dinami
o que el anterior. Si se
ambia el numero de las n,
enton
es solo es ne
esario relo
alizar el arreglo matriz. Si, por el
ontrario, se modi
a el
numero de
olumnas, enton
es hay que relo
alizar
ada la matriz[i].
En C++, as
omo en la mayora de los lenguajes, una matriz se
onsidera
omo un
arreglo de n las de tama~no m. Sin embargo, tambien puede interpretarse al reves; es
de
ir,
omo un arreglo de m
olumnas de tama~no n. Este es el
aso del lenguaje de
programa
ion Fortran, intensivamente usado para
al
ulos numeri
os.
Aunque dis
utible, la disposi
ion espe
ial de las matri
es en Fortran no es un
apri
ho.
Su
ede que mu
has opera
iones matemati
as sobre matri
es exhiben un patron de a
eso
que favore
e la se
uen
ializa
ion por
olumnas en lugar de por las.
Cuenta habida de la popularidad del Fortran, los programadores de C y C++ deben
onsiderar variantes para los arreglos ade
uadas al Fortran, pues es bastante fa
tible utilizar
bibliote
as es
ritas en Fortran y vi
eversa.
2.3
Iteradores
En programa
ion, di
en que al n de
uentas no quedan sino se
uen
ias. Cualquiera sea
la estru
tura de datos, el \quid" de pro
esamiento siempre se remite a se
uen
ias. El
patron es tan fre
uente que mere
e un patron generi
o denominado Iterator<Set, T> y
espe
i
ado en el diagrama UML de la gura 2.4.
La
lase Iterator<Set, T> re
ibe dos tipos parametrizados Set<T> del tipo expli
ado
en x 1.3 (pagina 18) y T. Su n es fa
ilitar el pro
esamiento se
uen
ial de elementos en un
onjunto Set<T>.
Para usar un iterador se requiere un
onjunto o
ontenedor Set<T>. Hay dos formas
de instan
iarlo: mediante el
onstru
tor que re
iba el
onjunto o mediante el
onstru
tor
opia. En el primero, el iterador se posi
iona en el primer elemento de la se
uen
ia, mientras
que en el segundo en el elemento a
tual del iterador fuente de la
opia.
Pi
tori
amente, un
onjunto puede interpretarse
omo la siguiente se
uen
ia generi
a
de n elementos de algun tipo generi
o T:
74
Captulo 2. Secuencias
Set<Type>:template
T:class
Iterator
+Iterator(in ct:Set<T>)
+Iterator(in it:Iterator)
+Iterator()
+operator =(in ct:Set<T>)
+operator =(in it:Iterator)
+next(): void
+prev(): void
+has_current(): bool
+is_in_first(): bool
+is_in_last(): bool
+current(): T
+reset_first(): void
+reset_last(): void
+del(): void
+insert(in item:T): void
+append(in item:T): void
+set(in item:T): void
+position(): int
e0
e1
e2
...
ei
ei+1
...
en2
en1
en
75
ia \atras" se eje
uta prev(). Cualquiera de estos metodos puede generar las ex
ep
iones std::overflow error o std::underflow error si el iterador esta desbordado.
En ALEPH, as
omo en otros ambitos, un
onjunto
ontiene su
lase Iterator. Es
de
ir, Iterator es una sub
lase de la espe
ializa
ion de Set<T, Compare>.
El siguiente
odigo ilustra una busqueda generi
a en un
onjunto mediante un iterador:
template <template <class> class Set, typename T, class Compare>
T * search(Set<T> & set, const T & item)
{
for (typename Set<T>::Iterator it(set); it.has_current(); it.next())
if (are_equals<T, Compare>() (it.get_current(), item))
return &it.get_current();
return NULL;
}
A ve
es un iterador puede \reusarse". Para ello, es posible reini
iarlo para que este
apunte al primer o ultimo elemento del
onjunto. reset first() reini
ia el iterador al
primer elemento, mientras que reset last() lo reini
ia al ultimo (indi
ado
on en1 en
la gura x 2.5).
Las opera
iones que siguen son dependientes de la estru
tura de datos
on que se
implante el
onjunto.
El metodo del() elimina del
onjunto el elemento a
tual y avanza el iterador una
posi
ion ha
ia delante. Debe tenerse espe
ial
uidado
uando se inter
alen next() y del()
en una misma itera
ion.
El metodo insert() inserta un nuevo elemento en la se
uen
ia \a la dere
ha\ del
elemento a
tual del iterador sin avanzar. Analogamente, el metodo append() inserta ha
ia
la izquierda.
La primitiva set() posi
iona un iterador en el elemento item pertene
iente al
onjunto.
Por lo general, no se valida si item en efe
to es parte del
onjunto.
position() retorna la posi
ion del elemento a
tual dentro de la se
uen
ia.
Segun la implanta
ion de Set<T> el iterador puede enrique
erse
on otras primitivas.
Posiblemente la mas importante de ellas es el operador de a
eso [], el
ual retorna un
elemento del
onjunto segun su posi
ion dentro de la se
uen
ia.
2.4
Listas Enlazadas
Una lista es una se
uen
ia de longitud \variable" < t1, t2, t3, . . . , tn > de n elementos de
algun tipo T
on las siguientes propiedades:
Restricci
on de acceso:
76
Captulo 2. Secuencias
Esta restri
ion se impone sobre todas las opera
iones que requieran a
eder
ualquier posi
ion de la se
uen
ia. En
onse
uen
ia, el tiempo de eje
u
ion en el
a
eso, inser
ion y elimina
ion es dependiente de la posi
ion dentro de la lista.
Flexibilidad: Si se
ono
e la dire
ion de un elemento ti dentro de la se
uen
ia
< t1, t2, . . . , ti, ti+1, . . . , tn >, enton
es:
1. ti+1 puede eliminarse en tiempo
onstante. El estado despues de esta elimina
ion es < t1, t2, . . . , ti, . . . , tn >.
2. Un elemento
ualquiera tj puede insertarse despues de ti en tiempo
onstante.
El estado nal despues de la inser
ion es:
< t1, t2, . . . , ti1, ti, tj, ti+1, . . . , tn >
Espacio proporcional al tama
no: El espa
io o
upado por los elementos de la lista
Las listas son idoneas para apli
a
iones
on, o algunas de, las siguientes
ara
tersti
as:
Se requieren varios
onjuntos (quiza mu
hos) y su
antidad es variable a lo largo de
la apli a ion.
Las ardinalidades de los onjuntos son des ono idas y muy variables en el tiempo
El pro
esamiento es se
uen
ial; es de
ir, el pro
esamiento del i-esimo elemento se
realiza despues de pro
esar los (i 1) elementos previos.
Listas
on las
ara
tersti
as se~naladas forman parte de los lenguajes de programa
ion
fun
ionales, LISP, ML, CAML, por ejemplos; y de los modernos y potentes lenguajes de
\s
ripting"; en las o
urren
ias mas populares, perl y python. Lenguajes de mediano nivel,
tales
omo Pascal, C o C++, no poseen listas
omo parte del lenguaje, razon por la
ual
es
onveniente implantarlas general y generi
amente en bibliote
a.
A las listas se les tilda de \enlazadas" porque, a diferen
ia del arreglo,
ada elemento
de la se
uen
ia reside en una dire
ion de memoria que no es
ontigua. Para
ono
er
ual
es el siguiente elemento de la se
uen
ia, es ne
esario un \enla
e" al bloque de memoria
que lo alberga. La gura 2.6 muestra la
lasi
a representa
ion pi
tori
a de una lista simplemente enlazada
uyos elementos son las letras desde la A hasta la E.
head
A
77
77
Esta es la tpi
a de
lara
ion de un nodo pertene
iente a una lista enlazada. data alma
ena
la letra y next es el puntero al proximo elemento. Puesto que un Node se reere a s mismo
a traves de next, las listas enlazadas han sido
lasi
adas de \estru
turas re
urrentes".
La lista de la gura 2.6 se
ategoriza
omo \simplemente enlazada" porque sus nodos
poseen un solo enla
e ha
ia el siguiente nodo en la se
uen
ia. En detrimento de un poquito
mas de memoria, es posible utilizar un enla
e adi
ional al elemento prede
esor. En este
aso
la lista se
ategoriza
omo \doblemente enlazada" y se representa pi
tori
amente
omo en
la gura 2.7. A efe
tos de manejar el otro extremo, se requiere un puntero adi
ional al
ultimo elemento de la lista.
last
first
A
78
Captulo 2. Secuencias
first
A
Las opera
iones sobre listas enlazadas pueden separarse en los niveles jerarqui
os, segun
el tipo de opera
iones, ilustrados en el diagrama UML de las
lases que manipulan listas
en ALEPH.
Figura 2.9: Diagrama general UML de las lases para listas enlazadas
79
En el primer nivel el n es el manejo de apuntadores. Solo nos
on
iernen las opera
iones de enla
e de un nodo
on otro sin
onsiderar, ni el resto de los nodos, ni el tipo de
dato que estos
ontienen. En este nivel se en
uentran las
lases Slink y Dlink, la
uales
modelizan enla
es de nodos de listas simple y doblemente enlazadas, respe
tivamente. Las
opera
iones en este nivel son
ompletamente independientes del tipo de dato que alberguen
los nodos; lo que las ha
e generales para todas las
lases de listas.
En el segundo nivel el n es albergar un dato generi
o en el nodo que luego resulte
en el tipo de elemento de la se
uen
ia. En este nivel en
ontramos las
lases Snode<T>
y Dnode<T>, respe
tivamente. El n de estas
lases es manejar un dato generi
o T asequible
mediante la opera
ion get data(). Notese que por heren
ia publi
a un Snode<T> es un
Slink y un Dnode<T> es un Dlink.
En el ter
er nivel el n es manipular listas de nodos, sean simples de tipo Slist<T>,
o sean dobles de tipo Dlist<T>. Notese que en este nivel se habla de listas de nodos y no
de listas de elementos. Cada nodo de una lista requiere apartar un espa
io de memoria. En
lenguajes
omo C++ el manejo de memoria es deli
ado porque hay que asegurarse de liberar
ada bloque, en nuestro
aso,
ada nodo, que haya sido apartado una vez que se determina
que este ya no se usara mas. En a~nadidura, hay situa
iones en las
uales no es ne
esario
apartar o liberar memoria. Otra ventaja de este enfoque es que podemos eliminar un nodo
de un
onjunto e insertarlo en otro sin ne
esidad de liberar y luego apartar memoria. En
virtud de estas razones, el manejo por nodos permite espe
i
ar opera
iones sobre listas
sin
onsiderar el manejo de la memoria.
Finalmente, el ultimo nivel
orresponde al
on
epto tradi
ional de lista en el
ual
todo esta resuelto; in
luido el manejo de memoria. En este sentido, se exportan las
lases
DynSlist<T> y DynDlist<T>, la
uales modelizan listas implantadas mediante enla
es
simples y dobles, respe
tivamente
2.4.2
79
El TAD Slink, denido en el ar
hivo hslink.H 79i, representa un enla
e simple de una
lista simplemente enlazada
ir
ular
on nodo
abe
era.
hslink.H 79i exporta la
lase Slink:
hslink.H 79i
class Slink
{
hmiembros protegidos
public:
hmiembros
};
de Slink 80ai
on de
onversion de Slink
hfun
i
# endif /* SLINK_H */
a lase 83i
Denes:
Objetos que deban enlazarse en una lista simple deben tener un Slink
omo atributo
o ser Slink por heren
ia publi
a. La gura 2.10 muestra una lista de
uatro elementos
implantada mediante Slink.
80
Captulo 2. Secuencias
80a
Hay una diferen
ia inmediatamente apre
iable
on la gura 2.6:
ada lazo apunta al
lazo del siguiente nodo y no al nodo. Esto plantea una diferen
ia substan
ial
on las listas
enlazadas tradi
ionales donde el apuntador dire
iona al registro
ompleto.
Slink tiene un solo miembro dato y protegido:
hmiembros protegidos de Slink 80ai
(79)
protected:
Slink* next;
Uses Slink 79.
80b
La prote
ion permite que una
lase derivada de Slink utili
e dire
tamente el miembro dato next.
El TAD Slink es tan simple, que mu
hos de sus metodos
onllevan una sola lnea:
hmiembros p
ubli
os de Slink 80bi
(79) 81b
Slink() : next(this) { /* Empty */ }
Slink(const Slink & link)
{
h
opiar Slink 81ai
}
Slink & operator = (const Slink & link)
{
if (&link == this)
return *this;
if (not is_empty())
throw std::invalid_argument("link is not empty");
void reset()
{
next = this;
}
bool is_empty() const
{
return next == this;
}
81
81a
(80b)
next = this;
81b
El
onstru
tor Slink()
rea un lazo simple apuntando a s mismo. La
opia de un
Slink solo es permitida si el lazo no esta in
luido dentro de alguna lista; un Slink solo
puede
opiarse si este esta va
o.
reset() reini
ia un lazo ya existente para apuntar a s mismo.
is empty() retorna verdadero si el next apunta a s mismo. La idea de este metodo
es invo
arlo desde un nodo
abe
era.
get next() retorna el siguiente lazo atado a this.
La inser
ion es respe
to al su
esor y es
omo sigue:
hmiembros p
ubli
os de Slink 80bi+
(79) 80b 81
void insert_next(Slink * p)
{
p->next = next;
next = p;
}
Uses Slink 79.
Del mismo modo, la elimina
ion ata~ne al siguiente elemento y tambien es muy sen
illa:
hmiembros p
ubli
os de Slink 80bi+
(79) 81b
Slink * remove_next()
{
Slink * ret_val = next;
next = ret_val->next;
ret_val->reset();
return ret_val;
}
Uses remove next 97b and Slink 79.
remove next() suprime el siguiente elemento respe
to a this y retorna el lazo eliminado.
Supongamos que deseamos enlazar objetos de tipo Window en una lista simplemente en-
En este
aso podemos insertar dire
tamente instan
ias de Window despues de un nodo
parti
ular first:
82
Captulo 2. Secuencias
Esto es posible porque Window es, por deriva
ion, de tipo Slink. Puesto que las opera
iones
de la
lase Slink son en fun
ion de Slink , puede ser ne
esario efe
tuar una
onversion.
Por ejemplo:
11
window_ptr = static_cast<Window*>(first->remove_next());
Si first apunta al primer nodo de una lista, enton
es una instan
ia window ptr de
tipo Window puede insertarse despues de first del siguiente modo:
first->insert_next(&window_ptr->link);
El enfoque anterior tiene la ventaja de que ha
e posible enlazar objetos a varias listas;
o sea, un objeto puede ser parte de varias listas. Para ello, simplemente debemos de
larar
omo atributos tantos Slink
omo la
antidad de listas a las
uales pertene
era el objeto.
El ejemplo anterior, puede multipli
arse de la siguiente forma:
class Window
{
...
Slink link_1;
Slink link_2;
...
Slink link_n;
};
...
first->insert_next(&window_ptr->link_1);
first->insert_next(&window_ptr->link_2);
...
first->insert_next(&window_ptr->link_n);
Con esta te
ni
a no es posible realizar una
onversion por
ompilador, pues
ualquier
atributo de tipo Slink no es la propia
lase Window, sino parte de ella. Para obtener la
dire
ion
orre
ta a partir de un Slink son ne
esarios algunos
al
ulos en aritmeti
a de
apuntadores. Puesto que tales
al
ulos son muy sus
eptibles de error, es bastante deseable
en
apsularlos en una primitiva de la siguiente forma:
11 Redundan
ia
adrede.
83
83
Se genera un miembro estati
o que efe
tua el mismo trabajo que hfun
ion de
onversion
de Slink a
lase 83i.
Si bien el ma
ro es algo
ompli
ado, este es independiente de la posi
ion de Slink
en Window. La estru
tura del miembro estati
o es la misma para toda
lase que use un
Slink; solo hay que
ambiar Window y link por los nombres que el
liente es
oja.
2.4.3
Snode<T> es una lase parametrizada que abstrae un nodo pertene iente auna lista simplemente enlazada ir ular, on nodo abe era, uyos datos son de tipo T. Snode<T> se
84
84a
Captulo 2. Secuencias
dene e implanta en el ar
hivo htpl snode.H 84ai, el
ual posee la siguiente estru
tura:
htpl snode.H 84ai
template <typename T>
class Snode : public Slink
{
private:
hmiembros
public:
hmiembros p
ubli
os de Snode<T> 84
i
};
Denes:
Snode, used in
hunks 84, 86, 89a, 134g, 135b, 149, and 163b.
Uses Slink 79.
84b
Puesto que Snode<T> deriva de Slink, este es tambien un Slink y hereda, a ex
ep
ion
de algunos metodos que deben sobre
argarse, la mayor parte de los metodos de Slink.
Un Snode<T> tiene un uni
o atributo:
hmiembros privados de Snode<T> 84bi
(84a)
T data;
data ontiene el dato alma enado en el nodo. Este dato puede onsultarse mediante el
84
observador:
hmiembros p
ubli
os de Snode<T> 84
i
(84a) 84d
84d
(84a) 84 84e
Snode() { /* empty*/ }
84e
El
onstru
tor va
o
rea un nodo
on valor de dato indeterminado. El segundo
onstru
tor
rea un nodo
on el valor de dato data.
El metodo insert next() se hereda dire
tamente de Slink; lo que es permisible
porque un Snode<T> es tambien un Slink.
Por el
ontrario, debemos sobre
argar remove next(), pues esta retorna un Slink y
requerimos que se retorne un Snode<T>:
hmiembros p
ubli
os de Snode<T> 84
i+
(84a) 84d 84f
Snode * remove_next() { return static_cast<Snode*>(Slink::remove_next()); }
Uses remove next 97b, Slink 79, and Snode 84a.
84f
(84a) 84e
Snode * get_next() const { return static_cast<Snode*>(Slink::get_next()); }
Uses Slink 79 and Snode 84a.
85
class T:
ListStack
-num_nodes: size_t
Slink
+ListStack()
+push(node:Node*): void
+pop(): Node*
+top(): Node*
+isEmpty(): bool
+size(): size_t
+Slink()
+Slink(,link:const Slink)
+reset(): void
+isEmpty(): bool
+getNext(): Slink*
+insertNext(p:Slink*): void
+removeNext(): Slink*
class T:
DynListStack
class T:
+top(): T&
+push(,_data:const T&): T&
+pop(): T
+~DynListStack(): virtual
+isEmpty(): bool
+size(): int
Snode
+Snode()
+Snode(data:cons T&)
+removeNext(): Snode*
+getData(): T&
+getNext(): Snode*
class T
Slist::Iterator
-list: Slist *
-current: Node *
class T:
Slist
+Slist()
+insertFirst(node:Nodo*): void
+removeFirst(): Node*
+getFirst(): Node*
tiene un
+Iterator(_list:Slist&)
+has_current(): bool
+get_current(): Node*
+next(): void
+reset_first(): void
+operator =(node:Node*): Iterator&
class T:
DynSlist
-num_items: size_t
-current_pos: unsigned int
+DynSlist()
+operator [](i:unsigned int): T&
+get_num_items(): size_t
+insert(pos:unsigned int,data:const T&): void
+remove(pos:unsigned int): void
+~DynSlist()
class T:
DynSlist::Iterator
tiene un
+Iterator(list:DynSlist&)
+get_current(): T&
Figura 2.11: Diagrama general UML de las lases ALEPH para listas enlazadas simples
86
Captulo 2. Secuencias
2.4.4
86a
El TAD Slist<T> abstrae una lista simplemente enlazada
ir
ular denodos simples, la
ual se dene en el ar
hivo htpl slist.H 86ai, en el
ual se dene la
lase en
uestion:
htpl slist.H 86ai
template <typename T>
class Slist : public Snode<T>
{
public:
hdeni
iones
hm
etodos
de Slist<T> 86bi
publi os de Slist<T> 86 i
hiterador de Slist<T> 87
i
};
Denes:
Slist, used in
hunks 86{89.
Uses Snode 84a.
86b
La
lase Slist<T> modeliza una lista simplemente enlazada de nodos simples que
alma
enan datos de tipo T:
hdeni
iones de Slist<T> 86bi
(86a)
typedef Snode<T> Node;
Uses Snode 84a.
86
86d
insert first() inserta el nodo al prin ipio de la lista; es de ir, node deviene el primer
86e
elemento de la lista.
Del mismo modo, la uni
a forma de suprimir es por el prin
ipio de la lista:
hm
etodos publi
os de Slist<T> 86
i+
(86a) 86d 87b
Node * remove_first() throw(std::exception, std::underflow_error)
{
hveri
ar desborde negativo 87ai
return this->remove_next();
}
Uses remove next 97b.
87a
87b
87
(86e 87b)
Iterador de Slist<T>
public:
hmiembros
};
87d
(86a)
De alguna manera, el iterador debe poseer la informa
ion su
iente para poder re
orrer
la lista. Tal informa
ion esta
onstituida por:
hmiembros privados de iterador de Slist<T> 87di
(87
)
Slist * list;
Node * current;
Uses Slist 86a.
87e
(87
)
Iterator(Slist & _list) : list(&_list), current(list->get_first())
{
// Empty
}
bool has_current() const { return current != list; }
Node * get_current() throw(std::exception, std::overflow_error)
{
hveri
ar std::overflow error() 88ai
88
Captulo 2. Secuencias
return current;
}
void next() throw(std::exception, std::overflow_error)
{
hveri
ar std::overflow error() 88ai
current = current->get_next();
}
void reset_first() { current = list->get_next(); }
Uses get current 103, has current 103, and Slist 86a.
88a
(87e)
2.4.6
88b
El TAD DynSlist<T>
El TAD DynSlist<T> modeliza una lista de elementos de tipo T
uyo manejo de memoria
lo realiza internamente el propio TAD. Como tal, este TAD se a
er
a mas al
on
epto ideal
de lista enun
iado al prin
ipio del
aptulo. Su deni
ion e implanta
ion se en
uentran en
el ar
hivo htpl dynSlist.H 88bi,
uya estru
tura es la siguiente:
htpl dynSlist.H 88bi
template <typename T>
class DynSlist : public Slist<T>
{
private:
hMiembros privados de DynSlist<T> 88
i
public:
ubli
os de DynSlist<T> 89bi
hMiembros p
};
Denes:
DynSlist, used in
hunk 89.
Uses Slist 86a.
DynSlist<T> hereda parte de la interfaz e implanta ion de Slist<T>. this es, enton es, el nodo abe era de la lista. Adi ionalmente, DynSlist<T> ontabiliza la antidad
88
de elementos de la lista:
hMiembros privados de DynSlist<T> 88
i
size_t num_items;
(88b) 89a
89a
89
Antes de la apari
ion del patron de iterador presentado en x 2.3, las interfa
es a las
listas enlazadas manejaban el
on
epto de \
ursor". Un
ursor abstrae una posi
ion de la
lista dentro la se
uen
ia respe
to a la
ual se realizan sus opera
iones prin
ipales. A un
ursor se le llama tambien \posi
ion a
tual".
Para llevar el estado del
ursor se utilizan los siguientes atributos:
hMiembros privados de DynSlist<T> 88
i+
(88b) 88
89
int
current_pos;
Snode<T> * current_node;
Uses Snode 84a.
current pos es el ordinal del elemento a tual dentro de la se uen ia. current node es el
89b
89
(88b) 89d
DynSlist() : num_items(0), current_pos(0), current_node(this)
{
// Empty
}
Uses DynSlist 88b.
Antes de implantar las primitivas prin
ipales de DynSlist<T>, requerimos una rutina
de a
eso por posi
ion:
hMiembros privados de DynSlist<T> 88
i+
(88b) 89a
typename Slist<T>::Node * get_previous_to_pos(const int & pos)
{
if (pos > num_items)
throw std::out_of_range ("position out of range");
if (pos < current_pos) // hay que retroceder?
{ // Si, reinicie posici
on actual
current_pos = 0;
current_node = this;
}
// avanzar hasta nodo predecesor a pos
while (current_pos < pos)
{
current_node = current_node->get_next();
++current_pos;
}
return current_node;
}
Uses Slist 86a.
get previous to pos() retorna el nodo prede esor a la posi ion pos y deja current node
89d
posi
ionado en di
ho prede
esor. La memoriza
ion del prede
esor es indispensable para
los algoritmos de inser
ion y elimina
ion.
Los miembros publi
os restantes se espe
i
an
omo sigue:
hMiembros p
ubli
os de DynSlist<T> 89bi+
(88b) 89b
T & operator [] (const size_t & i)
90
Captulo 2. Secuencias
throw(std::exception, std::out_of_range)
{
return get_previous_to_pos(i)->get_next()->get_data();
}
size_t size() const { return num_items; }
void insert(const int & pos, const T & data)
throw(std::exception, std::bad_alloc, std::out_of_range)
{
// apartar nodo para nuevo elemento
typename Slist<T>::Node * node = new typename Slist<T>::Node (data);
// obtener nodo predecesor al nuevo elemento
typename Slist<T>::Node * prev = get_previous_to_pos(pos);
prev->insert_next(node);
++num_items;
}
void remove(const int & pos) throw(std::exception, std::range_error)
{
// obtener nodo predecesor al nuevo elemento
typename Slist<T>::Node * prev = get_previous_to_pos(pos);
typename Slist<T>::Node * node_to_delete = prev->remove_next();
delete node_to_delete;
--num_items;
}
~DynSlist()
{
// eliminar nodo por nodo hasta que la lista devenga vac
a
while (not this->is_empty())
delete this->remove_first(); // remove_first de la clase base Slink
}
Uses DynSlist 88b, remove next 97b, Slink 79, and Slist 86a.
2.4.7
90
En el mismo espritu de dise~no que el TAD Slink, el TAD Dlink dene un doble enla
e
ontenido en un nodo pertene
iente a una lista doblemente enlazada
ir
ular
on nodo
abe
era.
Dlink se espe
i
a e implanta en el ar
hivo hdlink.H 90i que se estru
tura del siguiente
modo:
hdlink.H 90i
class Dlink
{
hmiembros
91
public:
hmiembros
};
(90)
protected:
mutable Dlink * prev;
mutable Dlink * next;
Uses Dlink 90.
(90) 91
91
Eventualmente, aunque deli
ado, es
onveniente exportar interfa
es para
opias y asigna
iones:
hmiembros p
ubli
os de Dlink 91bi+
(90) 91b 92a
Dlink(const Dlink &) { reset(); }
92
Captulo 2. Secuencias
return *this;
}
Uses Dlink 90.
92a
En realidad las dos opera
iones no efe
tuan
opia; se exportan para satisfa
er opera
iones
on variables temporales usadas por el
ompilador. No es posible realizar
opia a este nivel,
porque no se maneja ningun me
anismo de asigna
ion de memoria ni se
ono
e el tipo de
dato que albergan los nodos.
Un doble enla
e puede \reini
iarse" mediante:
hmiembros p
ubli
os de Dlink 91bi+
(90) 91
92b
void reset()
{
next = prev = this;
}
reset() reini
ia una lista a que apunte a s mismo. Esto no es equivalente a eliminar
los nodos atados a this.
92b
Aunque no es posible asignar sobre una lista que
ontenga elementos, s es posible
inter
ambiar los
ontenidos de dos listas doblemente enlazadas en tiempo
onstante. Por
esta razon, se ofre
e la primitiva swap()
uya implanta
ion es
omo sigue:
hmiembros p
ubli
os de Dlink 91bi+
(90) 92a 93
void swap(Dlink * link)
{
if (is_empty() and link->is_empty())
return;
if (is_empty())
{
link->next->prev = this;
link->prev->next = this;
next = link->next;
prev = link->prev;
link->reset();
return;
}
if (link->is_empty())
{
next->prev = link;
prev->next = link;
link->next = next;
link->prev = prev;
reset();
return;
}
Aleph::swap(prev->next, link->prev->next);
Aleph::swap(next->prev, link->next->prev);
Aleph::swap(prev, link->prev);
93
Aleph::swap(next, link->next);
}
Uses Dlink 90.
this
swap
swap
swap
swap
link
93
pe
ta
ular, pues es
onstante, es general para todas las listas doblemente enlazadas
on
nodo
abe
era, sin importar ni el tipo de dato que se maneje, ni
omo se reserve la memoria. La gura 2.12 ilustra los nodos de las listas en los
uales se realizan los inter
ambios.
Asumiendo que this es el nodo
abe
era de una lista, podemos saber si la lista esta
va
a o no, si
ontiene un solo elemento o si su
ardinalidad es menor o igual a uno:
hmiembros p
ubli
os de Dlink 91bi+
(90) 92b 94a
bool is_empty() const
{
return this == next and this == prev;
}
bool is_unitarian() const
{
return this != next and next == prev;
}
bool is_unitarian_or_empty() const
{
return next == prev;
}
94
94a
Captulo 2. Secuencias
La gura 2.13 enumera los diferentes pasos involu
rados en la inser
ion, los
uales se
realizan en la rutina insert(), la
ual inserta a node
omo el su
esor de this:
hmiembros p
ubli
os de Dlink 91bi+
(90) 93 94b
void insert(Dlink * node)
{
node->prev = this;
node->next = next;
next->prev = node;
next
= node;
}
Uses Dlink 90.
94b
node
X
4: next = node;
2: node->next = next;
3: next->prev = node;
this
Figura 2.13: Inser ion en una lista doblemente enlazada implantada on Dlink
94
Puesto que la lista es
ir
ular, insert() desde el nodo
abe
era inserta un nodo al
prin
ipio de la lista. Analogamente, append() los inserta al nal.
Dada la dire
ion de un nodo, podemos a
eder a su su
esor y prede
esor mediante
los siguientes metodos:
hmiembros p
ubli
os de Dlink 91bi+
(90) 94b 95
Dlink *& get_next()
{
return next;
}
Dlink *& get_prev()
95
{
return prev;
}
Uses Dlink 90.
this
head
this
head
95
Existen
ir
unstan
ias en las
uales se requiere insertar una lista
ompleta dentro de
otra a partir de uno de sus nodos. Para ello, utilizamos las primitivas insert list()
y append list(). La gura 2.14 muestra el pro
eso de eje
u
ion de insert list(head).
Despues su eje
u
ion, la lista
uyo nodo
abe
era estaba apuntado por head deviene va
a,
pues sus nodos fueron in
luidos en this. Es muy importante notar que en este
aso this
no ne
esariamente es un nodo
abe
era, sino que puede ser
ualquier otro nodo. Las
implementa
iones son
omo sigue:
hmiembros p
ubli
os de Dlink 91bi+
(90) 94
96
void insert_list(Dlink * head)
{
if (head->is_empty())
return;
head->prev->next = next;
head->next->prev = this;
next->prev
= head->prev;
96
Captulo 2. Secuencias
next
head->reset();
= head->next;
}
void append_list(Dlink * head)
{
if (head->is_empty())
return;
head->next->prev
head->prev->next
prev->next
prev
head->reset();
=
=
=
=
prev;
this;
head->next;
head->prev;
}
Denes:
En algunos
ontextos, las opera
iones insert list() y append list() se
ono
en
omo \spli
e" .
Un
aso parti
ular, quiza mu
ho mas
omun que el spli
e, es la opera
ion de
on
atenar
listas concat list(head), la
ual
on
atena la lista
uyo nodo
abe
era es head
on this.
head deviene va
a despues de la opera
ion. En este
aso, s se asume que this es nodo
abe
era. Al respe
to, se plantea la siguiente implementa
ion:
hmiembros p
ubli
os de Dlink 91bi+
(90) 95 97a
12
96
=
=
=
=
head->next;
prev;
head->prev;
this;
head->reset();
}
Denes:
12 En
ingles, este termino se utiliza
uando se desea expresar que dos
osas se pegan, se juntan, por sus
extremos -una punta
on la otra-. En la opinion de este reda
tor, el equivalente
astellano mas proximo es
\enlazar",
uyo uso plantea una ambiguedad en la jerga de listas enlazadas. Por esa razon,
ontinuaremos
utilizando el termino en ingles.
97
concat list, used in hunks 212, 222a, 226b, 228 , and 509a.
97a
Dada la dire
ion de un nodo, hay varias maneras de invo
ar una elimina
ion. La mas
util de todas es la denominada \auto-elimina
ion", la efe
tua mediante del(). La rutina es
muy util porque permite que otras estru
turas de datos alma
enen referen
ias eliminables
a elementos de una lista enlazada. En otras palabras, un nodo
ualquiera puede suprimirse
a s mismo de la lista. Su implementa
ion es
omo sigue:
hmiembros p
ubli
os de Dlink 91bi+
(90) 96 97b
void del()
{
prev->next = next;
next->prev = prev;
reset();
}
3: reset();
2: next->prev = prev;
this
Figura 2.15: Auto-elimina ion en una lista doblemente enlazada implantada on Dlink
97b
Dado un node, hay otras dos maneras de eliminar: su prede
esor o su su
esor, las
uales
se implantan mediante los siguientes metodos:
hmiembros p
ubli
os de Dlink 91bi+
(90) 97a 98
Dlink * remove_prev()
{
Dlink* retValue = prev;
retValue->del();
return retValue;
}
Dlink * remove_next()
{
Dlink* retValue = next;
retValue->del();
return retValue;
}
Denes:
remove next, used in
hunks 81
, 84e, 86e, 89d, 98, 99a, 106
, 119a, 136a, 165e, 204a, 212, 222, 226b,
and 228.
98
Captulo 2. Secuencias
remove prev() suprime el prede esor respe to a this; remove next() suprime el su esor.
98
Estas primitivas son parti
ularmente utiles para el nodo
abe
era de la lista. Puesto que
la lista es
ir
ular, remove prev(), invo
ada desde el nodo
abe
era, suprime el ultimo
nodo de la lista; similarmente, remove next() suprime el primero.
Una apli
a
ion dire
ta de algunos de los metodos expli
ados se ejempli
a mediante
una rutina de inversion de nodos de la lista:
hmiembros p
ubli
os de Dlink 91bi+
(90) 97b 99a
int reverse_list()
{
if (is_empty())
return 0;
Aparte de invertir la lista, reverse list() aprove
ha el re
orrido para
ontar la
antidad de nodos;
antidad que retorna la fun
ion.
Dada una lista, >
omo partirla por el
entro en dos listas del mismo tama~no? Un
tru
o
onsiste en avanzar dos apuntadores. Por
ada itera
ion, un puntero avanza un paso,
mientras que el otro avanza dos . Cuando el segundo puntero se en
uentre al nal de la
lista, el primero se en
ontrara en el
entro; este es el punto de parti
ion. Este enfoque, es
el mas ade
uado para parti
ionar una lista simple, pero debe programarse
uidadosamente
los
asos extremos de
ero, uno o dos elementos.
Para listas doblemente enlazadas, existe un enfoque mu
ho mas simple y,
omo todo
lo simple, mas
onable: re
orrer la lista por los dos extremos. Por el lado izquierdo se
re
orre ha
ia la dere
ha; por el dere
ho ha
ia la izquierda. En
ada itera
ion, se elimina
13
13 Se
99a
99
de
ada extremo e insertar en
ada una de las listas resultado. Para ello, dise~namos el
metodo split list(l, r), el
ual re
ibe dos
abe
eras de listas va
as l y r, y parti
iona
a this por el
entro en dos partes, la izquierda en l y la dere
ha en r:
hmiembros p
ubli
os de Dlink 91bi+
(90) 98 99b
size_t split_list(Dlink & l, Dlink & r)
{
size_t count = 0;
99b
Dada una lista y uno de sus nodos, puede ser
onveniente parti
ionarla en un nodo
dado. Para ello, se provee la fun
ion cut list()
uya implanta
ion es la siguiente:
hmiembros p
ubli
os de Dlink 91bi+
(90) 99a 101a
void cut_list(Dlink * link, Dlink * list)
{
// enlazar list a list
list->prev = prev; //
ultimo nodo
list->next = link; // primer nodo que es link
El esquema de este algoritmo se ilustra en la gura 2.16. cut list() parti
iona this
en el nodo
uya dire
ion es link, el
ual debe pertene
er a la lista this. Los nodos a
la izquierda de link se preservan en this, mientras que los restantes, in
luido link, se
opian a list.
Los usos de Dlink son
asi los mismos que el de Slink desarrollado en la subse
ion x 2.4.2 (pagina 79). Podemos ha
er a una
lase ser un Dlink por deriva
ion publi
a,
o ha
erla parte de un nodo doble por de
lara
ion de un Dlink
omo atributo de la
lase.
La diferen
ia esen
ial respe
to a Slink es su versatilidad, expresada por la riqueza de
100
Captulo 2. Secuencias
link
this
list
100
las opera
iones que hemos estudiado, las
uales seran mas dif
iles de desarrollar que las
opera
iones de Slink.
Al igual que
on Slink, para situa
iones en que se usan registros que
ontengan Dlink,
se requieren ma
ros que generan fun
iones de
onversion de un Dlink ha
ia una
lase que
ontenga un atributo Dlink. En este sentido, hay dos posibilidades probables, aunque no
generales:
onversion ha
ia una
lase simple o
onversion ha
ia una
lase parametrizada.
Estas posibilidades se engloban en los siguientes ma
ros:
hma
ros
onversi
on de Dlink a
lase 100i
(90)
# define DLINK_TO_TYPE(type_name, link_name) \
inline static type_name * dlink_to_##type_name(Dlink * link) \
{ \
type_name * ptr_zero = 0; \
size_t offset_link
= reinterpret_cast<size_t>(&(ptr_zero->link_name)); \
char * address_type = reinterpret_cast<char*>(link) - offset_link; \
return reinterpret_cast<type_name *>(address_type); \
}
Todos los ma
ros generan una fun
ion de
onversion que
onvierte un puntero link de
tipo Dlink en un puntero a una
lase que
ontiene el Dlink.
El ma
ro DLINK TO TYPE genera una fun
ion general
on nombre dlink to type(). El
101
ma
ro LINKNAME TO TYPE re
ibe un parametro llamado link name, el
ual debera
orresponder exa
tamente
on el nombre del
ampo dentro de la
lase. La razon de ser de esta
fun
ion es que le permite al usuario distintos nombres de fun
iones para distintos nombres
de
ampos; esto es indispensable en los
asos en que la
lase
ontenga dos o mas enla
es
dobles. Los ma
ros DLINK TO TYPE y LINKNAME TO TYPE deben utilizarse dentro de la
lase
que use el tipo Dlink.
101a
Iterador de Dlink
Dlink exporta un iterador (ver x 2.3 (pagina 73))
uyo esquema se presenta
omo sigue:
hmiembros p
ubli
os de Dlink 91bi+
(90) 99b 105
class Iterator
{
private:
hatributos Dlink::Iterator 101bi
public:
hmiembros
};
101b
101 i
(101a)
Dlink * head;
Dlink * curr;
Uses Dlink 90.
head es el nodo abe era de la lista. curr es el nodo a tual del iterador.
101
(101a) 102a
Iterator(Dlink * head_ptr) : head(head_ptr), curr(head->get_next())
{
// Empty
}
Iterator(Dlink & _head) : head(&_head), curr(head->get_next())
{
// Empty
}
Iterator(Dlink * head_ptr, Dlink * curr_ptr) : head(head_ptr), curr(curr_ptr)
{
// Empty
}
Iterator(const Iterator &
{
// Empty
}
102
102a
Captulo 2. Secuencias
Los dos primeros
onstru
tores toman el nodo
abe
era de la lista sobre la
ual se iterara.
El primer
onstru
tor posi
iona el elemento a
tual sobre el primer elemento de la lista;
el segundo
olo
a el elemento a
tual sobre un nodo apuntado por curr ptr el
ual debe
pertene
er a la lista. Como se observa, no se realizan veri
a
iones de pertenen
ia sobre
este ultimo nodo.
El ter
er
onstru
tor es el de
opia.
El
uarto
onstru
tor
rea un iterador invalido que posteriormente puede usarse mediante asigna
ion:
hmiembros p
ubli
os iterador Dlink::Iterator 101
i+
(101a) 101
102b
Iterator & operator = (const Iterator & itor)
{
if (this == &itor)
return *this;
head = itor.head;
curr = itor.curr;
return *this;
}
Iterator & operator = (Dlink * head_ptr)
{
head = head_ptr;
curr = head->get_next();
return *this;
}
Uses Dlink 90.
102b
Un iterador puede reutilizarse, para ello se requieren fun
iones de reini
ia
ion:
hmiembros p
ubli
os iterador Dlink::Iterator 101
i+
(101a) 102a 102
void reset_first()
{
curr = head->get_next();
}
void reset_last()
{
curr = head->get_prev();
}
reset first() posi iona el iterador sobre el primer elemento. reset last() posi iona el
102
103
set()
olo
a el iterador a un nuevo nodo a
tual, mientras que las fun
iones reset()
olo
an el iterador a una nueva lista. En el
aso de un solo parametro, reset() es equivalente
a reset first() sobre la lista new head. Con dos parametros signi
a
olo
ar el iterador
103
sobre una nueva lista posi
ionada en un nodo a
tual espe
o. Estas fun
iones se
olo
an
por
ompatibilidad
on antiguo
odigo de ALEPH.
El elemento a
tual del iterador se a
ede y se veri
a mediante:
hmiembros p
ubli
os iterador Dlink::Iterator 101
i+
(101a) 102
104a
bool has_current() const
{
return curr != head;
}
104
104a
Captulo 2. Secuencias
104b
A menudo pueden
ombinarse iteradores en un for que semejan la itera
ion sobre
un arreglo. Como una lista no tiene a
eso dire
to, la
ondi
ion de itera
ion no debe ser
una
ompara
ion por posi
ion del tipo i < n. En una lista y, en general, para se
uen
ias
que se manipulen
on iteradores, la
ompara
ion debe ser entre iteradores. Puesto que
no se puede
ono
er la posi
ion dentro de la se
uen
ia para todas las situa
iones, no es
posible emplear los
omparadores rela
ionales <, <=, >, >=, pero s se puede
omparar
su igualdad o diferen
ia tal
omo sigue:
hmiembros p
ubli
os iterador Dlink::Iterator 101
i+
(101a) 104a 105a
bool operator == (const Iterator & it) const
{
return curr == it.curr;
}
bool operator != (const Iterator & it) const
{
return curr != it.curr;
}
Un estilo tradi
ional de uso de la
ompara
ion entre iteradores es
omo sigue:
for (Dlink::Iterator curr(list); curr != end; curr.next())
donde end es un iterador sobre la lista list apuntando al nal. Este es el estilo de la
bibliote
a estandar stdc++. end es equivalente a:
Dlink::Iterator end(list);
list.reset_last();
list.next();
Es posible insertar respe
to al elemento a
tual del iterador. Para ello, basta
on invo
ar
sobre el elemento a
tual
ualquiera de las primitivas de inser
ion insert() o append().
Existen situa
iones en las que se requiere eliminar el elemento a
tual del iterador. En
este
aso, no es posible efe
tuar del() sobre el nodo a
tual, pues podra perderse el estado
105a
105
del iterador. Para solventar esta situa
ion, la
lase Dlink::Iterator exporta una fun
ion
de elimina
ion sobre el nodo a
tual que deja al iterador en el nodo su
esor del eliminado:
hmiembros p
ubli
os iterador Dlink::Iterator 101
i+
(101a) 104b 105b
Dlink * del()
{
Dlink * current = get_current(); // obtener nodo actual
next(); // avanzar al siguiente nodo
current->del(); // eliminar de la lista antiguo nodo actual
return current;
}
Uses Dlink 90 and get current 103.
del() retorna el enla e eliminado de manera tal que el liente pueda disponer de el en la
105b
La eje
u
ion
orre
ta de esta primitiva requiere que el iterador pos referen
ie a list y no
a otro
ontenedor. Del mismo modo, tambien es indispensable que beg y end referen
ien al
mismo
ontenedor y que esta no sea list. Para efe
tuar esta veri
a
ion sin
omprometer
el en
apsulamiento de Dlink, se dise~nan las siguientes primitivas:
hmiembros p
ubli
os iterador Dlink::Iterator 101
i+
(101a) 105a
bool verify(Dlink * l) const { return head == l; }
105
106
Captulo 2. Secuencias
Este metodo elimina todos los nodos y asume que la memoria de
ada nodo fue asignada
mediante new.
2.4.8
106a
En un nivel superior respe
to a Dlink, el TAD Dnode<T> modeliza un nodopertene
iente a una lista doblemente enlazada
ir
ular. Dnode<T> se dene e implanta
en el ar
hivo htpl dnode.H 106ai
uya estru
tura es la siguiente:
htpl dnode.H 106ai
template <typename T> class Dnode : public Dlink
{
private:
hm
etodos privados Dnode<T> 106bi
public:
etodos publi
os Dnode<T> 106
i
hm
};
Denes:
Dnode, used in
hunks 106
, 108b, 112, 119a, 192{95, 204b, 222
, 226b, 229, 470b, 472
, and 533a.
Uses Dlink 90.
106b
Dnode<T> es un Dlink p
ubli
o por deriva
ion. Esen
ialmente, la uni
a diferen
ia reside
en que Dnode<T> alma
ena un elemento de tipo de T:
hm
etodos privados Dnode<T> 106bi
(106a)
mutable T data;
106
Los metodos de Dlink que retornen punteros Dlink* deben sobre
argarse para que
retornen punteros tipeados Dnode<T>*:
hm
etodos publi
os Dnode<T> 106
i
(106a) 106d
Dnode<T> *& get_next() { return reinterpret_cast<Dnode<T>*&>(next); }
106d
La uni
a fun
ion de estos metodos es efe
tuar la
onversion ha
ia Dnode<T>. El manejo
de los enla
es se implanta por el metodo de la
lase base Dlink.
El a
eso al dato se realiza por:
hm
etodos publi
os Dnode<T> 106
i+
(106a) 106
106e
T & get_data() { return data; }
106e
Esta de
lara
ion permite que programas generi
os
onoz
an el tipo de dato de
Dnode<T>. Por ejemplo, si se tiene un Dnode<T> bajo el tipo generi
o Node, enton
es un
fragmento de programa podra ser:
107
el
ual de
lara una variable de nombre data del tipo involu
rado en Node, el
ual es el
mismo tipo generi
o T.
Iterador de Dnode<T>
Dnode<T> exporta un iterador
uya semanti
a es identi
a a la de Dlink::Iterator .
Puesto que el estado y
ontrol del iterador ya esta implantado por Dlink::Iterator , la
espe
i
a
ion de Dnode<T>::Iterator solo se remite a la sobre
arga de los
onstru
tores y
#prev: Dlink *
#next: Dlink *
+Dlink()
+Dlink(link:const Dlink&)
+reset(): void
+isEmpty(): bool
+insert(node:Dlink*)
+append(node:Dlink*)
+get_next(): Dlink*
+del(): void
+remove_prev(): Dlink*
+remove_next(): Dlink*
Dlink::Iterator
+Iterator()
+Iterator(in head_ptr:Dlink*)
+Iterator(in head_ptr:Dlink*,in curr_ptr:Dlink*)
+Iterator(in Iterator:itor)
+operator =(in itor:Iterator): Iterator&
+operator =(in itor:Iterator): Iterator
+reset_first(): void
+reset_last(): void
+get_current(): Dlink*
+has_current(): bool
+prev(): void
+next(): void
+exporta
T:class
Dnode
MetaDlistNode
T:class
Node_Type:template <class> class
GenDlist
+get_next(): Dnode<T>*&
+get_prev(): Dnode<T>*&
+remove_prev(): Dnode<T>*&
+remove_next(): Dnode<T>*&
+Dnode()
+Dnode(_data:const T&)
+operator =(_data:const T&): Dnode&
+get_data(): T&
+exporta
T:class
Dnode::Iterator
+get_current(): Dnode<T>*
+del(): Dnode<T>
+exporta
+get_first(): Node*
+get_last(): Node*
+remove_first(): Node*
+remove_last(): Node*
T:class
GenDlist<T>::Iterator
+get_current(): Dnode<T>*
+del(): Dnode<T>
T:class
+con
GenDlist<Dlist_Node, T>
Especializacin
Dlist_Node
GenDlist<Dlist_Node_Vtl, T>
Especializacin
T:class
+con
Dlist_Node_Vtl
class T:
T:class
Dlist
T:class
DlistVtl
Con nodos virtuales
DynDlist
+DynDlist()
+insert(_data:const T&): void
+append(_data:const T&): void
+get_first(): T&
+get_last(): T&
+remove_first(): T
+remove_last(): T
+~DynDlist()
+exporta
T:class
DynDlist<T>::Iterator
+get_current(): T
+del()
+insert(in item:T)
+append(in item:T)
Figura 2.17: Diagrama general UML de las lases ALEPH para listas enlazadas dobles
108
2.4.9
108a
Captulo 2. Secuencias
hListas
2.4.9.1
En el mismo estilo que Slist<T>, la
lase Dlist<T> debe exportar una sub
lase
Dlist<T>::Node que dena los nodos de Dlist<T>. As mismo, un usuario de Dlist<T>
puede manejar nodos
omplejos mediante deriva
ion de Dlist<T>::Node; por ejemplo,
podra expresar lo siguiente:
class Window : public Dlist<int>::Node { /* ... */ };
108b
Del mismo modo que Slist<T>, el
liente puede utilizar instan
ias de Window en todas
las primitivas de Dlist<T>. Window puede ser un tipo
omplejo que referen
ie a otras
estru
turas de datos y que maneje memoria. Di
ha memoria sera liberada por el destru
tor
de Window.
Para que deriva
iones de Dlist<T>::Node puedan invo
ar el destru
tor de Window,
es indispensable que Dlist<T>::Node sea de
larado virtual.
Si Dlist<T>::Node fuese virtual, enton
es los destru
tores de instan
ias derivadas
de Dlist<T>::Node seran invo
ados y las semanti
as de los destru
tores en las
lases
derivadas seran respetadas. Por otra parte, para que Dlist<T>::Node sea virtual se
requiere un puntero al destru
tor de la
lase derivada, lo que impli
a un desperdi
io
en espa
io si no es ne
esario invo
ar el destru
tor de la
lase derivada. Como las listas
enlazadas pueden ser arbitrariamente grandes, un destru
tor virtual inne
esario puede
a
arrear un
oste en espa
io
onsiderable. Debemos, pues, en
ontrar una solu
ion que
le permita al usuario utilizar ambas alternativas segun su
onvenien
ia. Es de
ir, que el
de
ida si usa listas de nodos
on destru
tores virtuales o no.
Una solu
ion a este problema
onsiste en proveer dos
lases diferentes de nodos:
hDeni
i
on de nodos dobles 108bi
(108a)
template <typename T>
class Dlist_Node : public Dnode<T>
{
public:
Dlist_Node() : Dnode<T>() { /* Empty */ }
109
Si se requiere una lista
uyos nodos invoquen a los destru
tores de las
lases derivadas,
enton
es el
liente utiliza nodos de tipo Dlist Node Vtl. Del mismo modo, si el
liente
no requiere invo
ar a los destru
tores, enton
es este utiliza nodos de tipo Dlist Node
on
el
onsiguiente ahorro en espa
io.
Consideremos una primitiva de ordenamiento generi
a sobre listas enlazadas:
template <class Node, typename Key>
void ordenar(Node * head_list)
{
/* ... */
Node * node = head_list->get_next();
/* ... */
}
109
110
Captulo 2. Secuencias
110a
110b
111
};
Denes:
111a
Si el usuario requiere manejar nodos
on invo
a
ion a destru
tores de
lases derivadas de
los nodos, enton
es este utiliza la
lase DlistVtl<T>; de lo
ontrario utiliza Dlist<T>.
Dlist<T> y DlistVtl<T> son meras espe
ializa
iones de GenDlist<T>. Puesto que
hay deriva
ion publi
a, los metodos estan
ontenidos en GenDlist<T>.
GenDlist<T> utiliza nodos de tipo Node Type<T>. Esto se ha
e de manera simple
mediante:
hMiembros p
ubli
os GenDlist 111ai
(110a) 111b
typedef MetaDlistNode<Node_Type,T> Node;
Uses MetaDlistNode 109.
111b
Las fun
iones de GenDlist<T> se manejan, enton
es, en fun
ion de GenDlist<T>::Node.
Puesto que GenDlist<T> deriva publi
amente de Node Type<T>, GenDlist<T> posee
a traves de this un nodo
abe
era.
Hay dos maneras de insertar en GenDlist<T>: por el frente o por el nal. Los metodos
estan implantados por los miembros insert() y append() pertene
ientes a Dnode<T>.
Para la
onsulta, debemos instrumentar una parte en GenDlist<T> que reali
e la
onversion ha
ia GenDlist::Node. Di
ha
onsulta esta dada por las siguientes fun
iones:
hMiembros p
ubli
os GenDlist 111ai+
(110a) 111a 111d
Node * get_first() throw(std::exception, std::underflow_error)
{
hveri
ar Under
ow 111
i
return this->get_next();
}
Node * get_last() throw(std::exception, std::underflow_error)
{
hveri
ar Under
ow 111
i
return this->get_prev();
}
111
111d
(111)
Puesto que la elimina
ion retorna el nodo eliminado, esta tambien requiere
onversion
ha
ia GenDlist::Node:
hMiembros p
ubli
os GenDlist 111ai+
(110a) 111b 112
Node * remove_first() throw(std::exception, std::underflow_error)
{
hveri
ar Under
ow 111
i
Node* retValue = this->get_next();
retValue->del();
112
Captulo 2. Secuencias
return retValue;
}
Node* remove_last() throw(std::exception, std::underflow_error)
{
hveri
ar Under
ow 111
i
Node* retValue = this->get_prev();
retValue->del();
return retValue;
}
2.4.9.2
112
Iterador de Dlist<T>
Al igual que Dlink y Dnode<T>, GenDlist<T> exporta una sub
lase iterador GenDlist<T>::Iterator
uya implanta
ion se espe
i
a
omo sigue:
hMiembros p
ubli
os GenDlist 111ai+
(110a) 111d
class Iterator : public Node::Iterator
{
public:
Iterator(GenDlist<Node_Type, T> & list) : Dnode<T>::Iterator(&list)
{
// Empty
}
113
2.4.10
113a
El TAD DynDlist<T>
Las
lases Dnode<T> y Dlist<T> son su
ientemente versatiles
omo para realizar apli
a
iones
omplejas que requieran listas doblemente enlazadas. Sin embargo, estas
lases aun
requieren que el programador
omprenda
abalmente sus sutilezas de uso; en parti
ular,
el manejo de memoria.
En un ultimo nivel, dise~namos la
lase DynDlist<T>, la
ual modeliza una lista doblemente enlazada
ir
ular
on manejo interno de memoria. DynDlist<T> se espe
i
a e
implanta en el ar
hivo htpl dynDlist.H 113ai:
htpl dynDlist.H 113ai
template <typename T>
class DynDlist : public Dlist<T>
{
size_t num_elem;
public:
hm
etodos publi
os DynDlist<T> 113bi
};
Denes:
DynDlist, used in
hunks 113{16, 118{20, 123a, 125b, 127
, 128, 194b, 253, 311, 692a, 694b, 726,
737{40, 747, and 749b.
Uses Dlist 110b.
113b
La idea DynDlist<T> es que soporte toda la fun
ionalidad que arrastramos desde
Dlink sin ne
esidad de pensar en el manejo de apuntadores, nodos y memoria.
DynDlist<T> hereda p
ubli
amente su implanta
ion de la
lase Dlist<T> y una buena
parte de su interfaz. La responsabilidad esen
ial de DynDlist<T> es el manejo de memoria.
El uni
o atributo de DynDlist<T> es num elem, el
ual
ontabiliza la
antidad de
elementos que posee la lista y puede observarse mediante:
hm
etodos publi
os DynDlist<T> 113bi
(113a) 113
const size_t & size() const { return num_elem; }
113
113d
Al igual que en Dlist<T>, podemos insertar elementos por el prin
ipio o nal de la
lista:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 113
114a
void insert(const T & _data) throw(std::exception, std::bad_alloc)
{
typename Dlist<T>::Node* ptr = new typename Dlist<T>::Node (_data);
Dlist<T>::insert(ptr);
++num_elem;
}
114
Captulo 2. Secuencias
114a
Las mismas opera
iones se dene para listas; es de
ir, se puede
on
atenar una lista
entera tanto por el prin
ipio
omo por el nal:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 113d 114b
size_t insert_list(DynDlist & list)
{
Dlink::insert_list(&list);
num_elem += list.num_elem;
list.num_elem = 0;
return num_elem;
}
size_t append_list(DynDlist & list)
{
Dlink::append_list(&list);
num_elem += list.num_elem;
list.num_elem = 0;
return num_elem;
}
Uses append list 95, Dlink 90, DynDlist 113a, and insert list 95.
114b
El a
eso a una se
uen
ia DynDlist<T> se realiza por uno de sus extremos: el primer
o el ultimo elemento:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 114a 114
T & get_first() throw(std::exception, std::underflow_error)
{
return Dlist<T>::get_first()->get_data();
}
114
Ambos metodos retornan referen
ias al dato in
luido dentro de la lista. Esto signi
a que
el usuario puede modi
arlos dire
tamente.
La elimina
ion solo puede o
urrir por los extremos:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 114b 115b
T remove_first() throw(std::exception, std::underflow_error)
{
typename Dlist<T>::Node* ptr = Dlist<T>::remove_first();
hobtener
115
115a
(114 )
--num_elem;
return retVal;
115b
de la
onsulta, la elimina
ion retorna
opias de los valores, no referen
ias, pues el nodo
esa de existir despues de liberar la memoria o
upada por el nodo.
Es posible va
iar una lista; esto es: eliminar todos sus elementos:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 114
115
void empty()
{
while (not this->is_empty())
{
typename Dlist<T>::Node* ptr = Dlist<T>::remove_first();
delete ptr;
}
num_elem = 0;
}
Uses Dlist 110b.
115
~DynDlist()
{
empty();
}
Uses DynDlist 113a.
115d
Una primitiva esen
ial para el desempe~no y elegan
ia de algunos algoritmos es swap().
Esen
ialmente, todo el trabajo ya fue implantado desde la
lase Dlink, por lo que nuestra
primitiva se remite a tipear e invo
ar:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 115
116a
void swap(DynDlist & l)
{
Aleph::swap(num_elem, l.num_elem);
this->Dlink::swap(&l);
116
Captulo 2. Secuencias
}
Uses Dlink 90 and DynDlist 113a.
116a
Otra forma de modi
a
ion de una lista dinami
a es la parti
ion equitativa. Esto puede
realizarse desde el metodo Dlink::split list():
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 115d 116b
void split_list(DynDlist & l, DynDlist & r)
throw(std::exception, std::domain_error)
{
if ((not l.is_empty()) or (not r.is_empty()))
throw std::domain_error("lists are not empty");
Dlink::split_list(l, r);
l.num_elem = r.num_elem = num_elem/2;
if (num_elem % 2 == 1) // es num_elem impar?
l.num_elem++;
num_elem = 0;
}
Uses Dlink 90, DynDlist 113a, and split list 99a.
2.4.10.1
116b
Iterador de DynDlist<T>
Hasta el presente, la inser
ion, elimina
ion y otras opera
iones de modi
a
ion estan restringidas a los extremos de la lista. Si una apli
a
ion requiere modi
a
ion en posi
iones
diferentes, enton
es esta puede servirse de un iterador aunado a opera
iones espe
iales
sobre el elemento a
tual:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 116a 120a
class Iterator : public Dlist<T>::Iterator
{
DynDlist * list_ptr;
int pos;
public:
etodos iterador de DynDlist<T> 116
i
hm
};
Uses Dlist 110b and DynDlist 113a.
116
116d
. Para mantener
orre
tamente el estado de este valor, debemos sobre
argar las fun
iones de avan
e del iterador, de manera tal que a
tuali
en su valor:
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 116
117a
117
117a
El manejo de la posi
ion del elemento a
tual del iterador tambien requiere sobre
argar
los metodos reset first() y reset last().
has current() se hereda de la
lase base Dlink.
Dlist<T> trabaja en fun
ion de nodos y nuestro iterador debe trabajar en fun
ion de
elementos de tipo T. Por esa razon debemos sobre
argar get current():
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 116d 117b
T & get_current() { return Dlist<T>::Iterator::get_current()->get_data(); }
Uses Dlist 110b and get current 103.
117b
inser
ion y supresion. En nuestro
aso, no es posible realizar esto porque manejamos elementos generi
os de tipo T. As pues, requerimos que el iterador exporte fun
iones que
permitan insertar y eliminar.
La inser
ion la presentamos bajo las mismas formas que DynDlist<T>:
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 117a 118b
void insert(const T & _data)
throw(std::exception, std::overflow_error, std::bad_alloc)
{
hveri
ar over
ow 118ai
typename Dlist<T>::Node* node = new typename Dlist<T>::Node (_data);
Dlist<T>::Iterator::get_current()->insert(node);
++list_ptr->num_elem;
}
118
118a
118b
Captulo 2. Secuencias
Las otras maneras de insertar son listas enteras a partir del elemento a
tual:
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 117b 118
void insert_list(const DynDlist & list)
throw(std::exception, std::overflow_error)
{
hveri
ar over
ow 118ai
Dlist<T>::Iterator::get_current()->insert_list(&list);
list_ptr->num_elem += list.num_elem;
list.num_elem = 0;
}
void append_list(const DynDlist & list)
throw(std::exception, std::overflow_error)
{
hveri
ar over
ow 118ai
Dlist<T>::Iterator::get_current()->append_list(&list);
list_ptr->num_elem += list.num_elem;
list.num_elem = 0;
}
Uses append list 95, Dlist 110b, DynDlist 113a, get current 103, and insert list 95.
118
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 118b 119a
T del() throw(std::exception, std::overflow_error)
{
hveri
ar over
ow 118ai
typename Dlist<T>::Node* ptr = Dlist<T>::Iterator::get_current();
T ret_val = ptr->get_data();
Dlist<T>::Iterator::next();
ptr->del();
delete ptr;
--list_ptr->num_elem;
return ret_val;
}
Uses Dlist 110b and get current 103.
La opera
ion del() puede ser muy restri
tiva en el sentido de que avanza el iterador;
ademas, lo ha
e en un solo sentido. Nos
onviene, pues, ofre
er la primitivas de elimina
ion
\
ontextuales"; es de
ir, el prede
esor o su
esor del elemento a
tual:
119a
119
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 118
119b
T remove_prev() throw(std::exception, std::overflow_error)
{
hveri
ar over
ow 118ai
Dnode<T> * curr_ptr = Dlist<T>::Iterator::get_current();
Dnode<T> * ptr = curr_ptr->remove_prev();
T ret_val = ptr->get_data();
delete ptr;
--list_ptr->num_elem;
return ret_val;
}
T remove_next() throw(std::exception, std::overflow_error)
{
hveri
ar over
ow 118ai
Dnode<T> * curr_ptr = Dlist<T>::Iterator::get_current();
Dnode<T> * ptr = curr_ptr->remove_next();
T ret_val = ptr->get_data();
delete ptr;
--list_ptr->num_elem;
return ret_val;
}
Uses Dlist 110b, Dnode 106a, get current 103, remove next 97b, and remove prev 97b.
119b
La ultima opera
ion de modi
a
ion de una lista dinami
a mediante un iterador puede
ser muy util en algunos
asos; se trata del
orte de una lista a partir de la posi
ion a
tual
del iterador:
hm
etodos iterador de DynDlist<T> 116
i+
(116b) 119a
size_t cut_list(DynDlist & list)
{
hveri
ar over
ow 118ai
list_ptr->Dlist<T>::cut_list(Dlist<T>::Iterator::get_current(),
&list); // cortar lista
list.num_elem = list_ptr->num_elem - pos; // actualizar cardinalidad de list
list_ptr->num_elem -= pos; // actualizar cardinalidad de ptr_list.
return list.num_elem;
}
120
Captulo 2. Secuencias
Uses cut list 99b, Dlist 110b, DynDlist 113a, and get current 103.
120a
El iterador permite una manera
on
isa y expresiva de denir la
opia y asigna
ion de
listas:
hm
etodos publi
os DynDlist<T> 113bi+
(113a) 116b 120b
DynDlist<T> & operator = (const DynDlist & list)
{
if (this == &list)
return *this;
while (not this->is_empty())
this->remove_first();
h
opiar
lista 120 i
return *this;
}
Uses DynDlist 113a.
120b
(113a) 120a
120
La llamada this->reset() es indispensable para asegurar que la lista este logi
amente
va
a. Si no se realizase as, enton
es el valor de this, que funge de nodo
abe
era,
orrespondera a la
opia de list.
h
opiar lista 120
i
(120)
typename DynDlist<T>::Iterator itor(const_cast<DynDlist&>(list));
while (itor.has_current())
{
this->append(itor.get_current());
itor.next();
}
Uses DynDlist 113a, get current 103, and has current 103.
2.4.11
120d
Aplicaci
on: aritm
etica de polinomios
Consideremos un TAD que modeli
e polinomios y sus opera
iones basi
as. Tal TAD esta
realizado en el ar
hivo hpolinom.C 120di, el
ual se dene a
ontinua
ion:
hpolinom.C 120di
# include <tpl_dynDlist.H>
class Polinomio
{
hMiembros
public:
hinterfaz
};
de Polinomio 121ai
hImplementa
i
on
121a
121
121b
121
Por ejemplo, para
onstruir el polinomio 20x7 + 3x3 + x2 + 20, podemos efe
tuar:
Polinomio p(Polinomio(20, 7) + Polinomio(3, 3) +
Polinomio(1, 2) + Polinomio(20, 0));
121d
121f
122
122a
Captulo 2. Secuencias
122b
122
asumiendo que los terminos del polinomio estan ordenados desde la mayor hasta la menor
poten
ia. Del mismo modo, la opera
ion:
hinterfaz de Polinomio 121ai+
(120d) 122b 122d
const int & get_coef(size_t & i) const;
122d
(120d) 122
2.4.11.1
122e
Implementaci
on de polinomios
Una primera tenta
ion
omo estru
tura de dato para representar un polinomio es un
arreglo. Para un polinomio de grado n, se reserva un arreglo de dimension n + 1, el
ual
ontiene los
oe
ientes. No es ne
esario alma
enar la poten
ia, pues esta se representa
mediante el propio ndi
e del arreglo.
En aparien
ia, el arreglo ofre
e ventajas fenomenales; por ejemplo, la suma es dire
tamente la suma de ve
tores. Lamentablemente, el arreglo puede desperdi
iar una
antidad
de espa
io impresionante; por ejemplo, el polinomio 20x100 requiere un arreglo de 101
elementos para alma
enar un solo
oe
iente; un desperdi
io del 99%.
Los polinomios son muy diversos. La multipli
a
ion y division arrojan polinomios de
grados diferentes a sus operandos. En un ambiente donde se efe
tuen mu
has opera
iones,
es de esperar que dos polinomios tengan sus grados diferentes. En a~nadidura, mu
has ve
es
un polinomio es espar
ido; es de
ir, varios de sus
oe
ientes son nulos. Por estas razones,
una lista enlazada que solo alma
ene terminos diferentes de
ero es la estru
tura idonea
para representar un polinomio.
La gura 2.18 ilustra la representa
ion
on listas doblemente enlazadas del polinomio
4x6 + 2x3 1x2 + x + 7. La lista esta ordenada desde la mayor hasta la menor poten
ia. El
orden es esen
ial para fa
ilitar la e
ien
ia de los algoritmos.
Para representar un polinomio podemos utilizar el TAD DynDlist<T>, el
ual re
ibe
omo parametro un tipo que alberga los elementos a guardar en la lista; es de
ir, los pares
oe
iente-poten
ia:
hMiembros privados Polinomio 122ei
(120d) 123a
struct Termino
{
int
coef;
4 6
123
2 3
-1 2
1 1
7 0
size_t pot;
ermino 123bi
hMiembros T
};
Denes:
Termino, used in
hunks 123{28.
123a
DynDlist<Termino> terminos;
Uses DynDlist 113a and Termino 122e.
123b
(122e) 123d
123
123d
}
Uses Termino 122e.
124
124a
Captulo 2. Secuencias
La multipli
a
ion forzosamente debe produ
ir un nuevo termino, por esa razon debemos implantar estri
tamente el operador *:
hMiembros T
ermino 123bi+
(122e) 123d
124b
Polinomio::Polinomio() { /* empty */ }
Uses Termino 122e.
Suma de polinomios
Pn
i=0 cix
y P2 =
max(n,m)
P1 + P2 =
Pm
j=0 djx
se dene omo:
(ck + dk)xk .
k=0
124
Es fa
il obtener un algoritmo e
iente, pues las listas estan ordenadas por poten
ia. En este
sentido, es preferible implementar el operador +=, pues as nos ahorramos el
rear nuevos
terminos para el polinomio resultado. De este modo, el operador + podemos es
ribirlo en
fun
ion de +=
omo sigue:
hImplementa
i
on de metodos Polinomio 124bi+
(120d) 124b 124d
Polinomio Polinomio::operator + (const Polinomio & der) const
{
Polinomio ret_val(*this); // inicie valor de retorno en operando derecho
ret_val += der; // s
umele operando derecho
return ret_val;
}
124d
125a
125
Antes de efe
tuar la suma
omo tal, debemos veri
ar que ninguno de los dos polinomios
orresponda al elemento neutro:
hImplanta
i
on de Polinomio::operator += 125ai
(124d) 125b
if (der.terminos.is_empty())
return *this;
if (terminos.is_empty())
{
*this = der;
return *this;
}
125b
Si ambos polinomios no son nulos, enton
es debemos re
orrerlos. Para ello usamos un
iterador por
ada polinomio operando:
hImplanta
i
on de Polinomio::operator += 125ai+
(124d) 125a 125
DynDlist<Termino>::Iterator itor_izq(terminos);
DynDlist<Termino>::Iterator itor_der(const_cast<DynDlist<Termino>&>(der.terminos));
Uses DynDlist 113a and Termino 122e.
6x6
7x5
x4
3x2
8x8
x6
2x2
4x
itor der
125
Ahora re
orreremos las listas de terminos hasta que uno de los iteradores al
an
e su
n de lista:
hImplanta
i
on de Polinomio::operator += 125ai+
(124d) 125b 127b
while (itor_izq.has_current() and itor_der.has_current())
{
erminos a
tuales 125di
hPro
esar t
}
Uses has current 103.
125d
Sean izq y der las poten
ias de los terminos a
tuales de los iteradores izquierdo y
dere
ho respe
tivamente:
hPro
esar t
erminos a
tuales 125di
(125
) 126a
const size_t & izq = itor_izq.get_current().pot;
126
Captulo 2. Secuencias
1. Si la poten
ia del izq es menor que der, enton
es der estara presente en el resultado.
Como la lista esta ordenada, es seguro que this no
ontiene un termino
on la misma
poten
ia de der. Esta es la situa
ion
on los terminos 6x6 y 8x8 de los polinomios
izquierdo y dere
ho, respe
tivamente.
Debemos
rear un nuevo termino
opia de der e insertarlo en this (el polinomio
izquierdo). La
opia de der debe pre
eder a izq en this, pues el resultado debe
estar ordenado.
Las a
iones anteriores se tradu
en al siguiente
odigo:
126a
hPro
esar t
erminos a
tuales 125di+
(125
) 125d 126b
if (izq < der)
{
// insertar a la izquierda del actual de itor_izq
itor_izq.append(Termino(itor_der.get_current().coef, der));
itor_der.next(); // mirar el pr
oximo t
ermino de polinomio derecho
continue;
}
Uses get current 103 and Termino 122e.
append() sobre itor izq garantiza que el nuevo termino sea prede
esor del a
tual
izquierdo. Luego avanzamos el iterador dere
ho, pues el termino de poten
ia der ya
2. Si las poten
ias son iguales, enton
es debemos sumar los
oe
ientes, avanzar ambos
iteradores y repetir el pro
edimiento. Esta es la situa
ion
on los terminos 6x6 y x6
de los polinomios izquierdo y dere
ho respe
tivamente.
hPro
esar t
erminos a
tuales 125di+
(125
) 126a 127a
126b
if (izq == der)
{
// calcular coeficiente resultado
itor_izq.get_current() += itor_der.get_current(); // lama += de Termino
itor_der.next(); // avanzar al pr
oximo t
ermino del polinomio derecho
if (itor_izq.get_current().coef == 0) // verificar si suma anula el t
ermino
{
// s
, borrarlo del polinomio izquierdo (coeficiente cero)
itor_izq.del();
continue;
}
}
Uses get current 103 and Termino 122e.
3. En el ultimo
aso (izq > der), izq sera parte del resultado. Puesto que izq ya pertene
e
a this, simplemente avanzamos el iterador izquierdo y repetimos el pro
edimiento. Esta
127
es la situa
ion
on los terminos 7x5 y 2x2 de los polinomios izquierdo y dere
ho respe
tivamente.
127a
hPro
esar t
erminos a
tuales 125di+
(125
) 126b
itor_izq.next();
127b
El pro
eso iterativo anterior
ulmina
uando alguno de los iteradores al
anza el nal
de una de las listas. En este
aso queda por re
orrer una lista. Si la lista por re
orrer es
la izquierda, no debemos ha
er nada, pues sus elementos, que son parte del resultado, ya
estan in
luidos en this. Si, por el
ontrario, la lista que resta por re
orrer es la dere
ha,
enton
es debemos
opiar todos sus terminos restantes e insertarlos se
uen
ialmente en la
lista izquierda:
hImplanta
i
on de Polinomio::operator += 125ai+
(124d) 125
// recorre t
erminos restantes polinomio derecho y los copia en izquierdo
while (itor_der.has_current())
{
terminos.append(Termino(itor_der.get_current().coef, itor_der.get_current().pot));
itor_der.next();
}
Uses get current 103, has current 103, and Termino 122e.
Multiplicaci
on de polinomios
n
X
i=0
cixi
Pn
i=0 cix
m
X
j=0
y P2 =
kjxj
Pm
j=0 djx
se dene omo:
Esta expresion sugiere un algoritmo basado en suma de produ
tos par
iales, el
ual puede
resumirse
omo sigue:
Algoritmo 2.1 (Multiplicaci
on de dos polinomios)
P
Pm
j
j
La entrada son dos polinomios P1 = n
i=0 cix y P2 =
j=0 djx de grados n y m
respe
tivamente.
La salida es un polinomio R
orrespondiente al produ
to P1P2.
1. Instan
ie un polinomio nulo R.
2. Repita para i = 0 hasta n
(a) Sea R = cixi P2
(b) R = R + R
127
Ahora podemos
odi
ar el operador *
ompletamente reminis
ente del algoritmo 2.1:
hImplementa
i
on de metodos Polinomio 124bi+
(120d) 124d
Polinomio Polinomio::operator * (const Polinomio& der) const
{
Polinomio result;
128
Captulo 2. Secuencias
if (terminos.is_empty() or der.terminos.is_empty())
return result;
DynDlist<Termino>::Iterator
itor_izq(const_cast<DynDlist<Termino>&>(terminos));
while (itor_izq.has_current())
{
result += der.multiplicado_por(itor_izq.get_current());
itor_izq.next();
}
return result;
}
Uses DynDlist 113a, get current 103, has current 103, and Termino 122e.
128
El algoritmo es sen
illo. Se instan
ia un polinomio result
on valor ini
ial igual al polinomio nulo. Luego, por
ada termino del polinomio, se multipli
a por el termino operando
y el resultado se inserta ordenadamente en result.
2.5. Pilas
2.5
129
Pilas
Una pila es una abstra
ion de
ujo
onsistente de una se
uen
ia de elementos en la
ual
las opera
iones de inser
ion,
onsulta y elimina
ion se eje
utan por un solo extremo. La
propiedad esen
ial es que el ultimo elemento en insertarse siempre sera el primer elemento
en eliminarse. Tal propiedad se
ono
e
omo UEPS (Ultimo en Entrar, Primero en Salir) .
14
push
tope
pop
8
7
6
4
3
2
1
130
2.5.1
Captulo 2. Secuencias
Hay dos metodos para representar una pila: arreglos y listas enlazadas. En ambas representa
iones, los elementos se disponen se
uen
ialmente segun el mismo orden de la pila.
En un arreglo se requiere mantener el ndi
e al ultimo elemento. La primera entrada
alma
ena el elemento mas antiguo, mientras que el ndi
e referen
ia el tope mas uno. La
gura 2.21 ilustra una pila de 5 elementos
ontenida en un arreglo de 11 elementos.
0 1 2 3 4 5 6 7 8 9 10
T0 T1 T2 T3 T4
tope
tope
2.5. Pilas
131
131a
El desempe~no de una pila implantada mediante un arreglo es tan ex
elente que vale la
pena ha
er expl
ito el me
anismo de implanta
ion.
ArrayStack<T> dispara ex
ep
iones
uando se ex
ede la
apa
idad del arreglo o
uando se intenta a
eder el tope de una pila va
a. Disparar estas ex
ep
iones tiene
un ligero
osto en desempe~no que puede ser importante si la pila se utiliza muy a menudo.
Existen mu
has apli
a
iones en las
uales se puede
ono
er el tama~no maximo de la pila
y en las que es inne
esario
onsiderar el desborde. Por otra parte, un desborde negativo
es un error de programa
ion a veri
ar en las primeras etapas de un proye
to. Por esa
razon, el TAD FixedStack<T> modeliza las mismas primitivas que ArrayStack<T>
on
la diferen
ia de que no se efe
tuan veri
a
iones y no se disparan ex
ep
iones. De esta
manera, apli
a
iones que
onoz
an el maximo tama~no de pila se bene
ian de la ganan
ia
en velo
idad dada por la ausen
ia de veri
a
iones.
Pilas implantadas
on arreglos se denen en el ar
hivo htpl arraySta
k.H 131ai, el
ual
exporta dos tipos prin
ipales: ArrayStack<T> y FixedStack<T>:
htpl arraySta
k.H 131ai
template <typename T, const size_t dim = 100>
class ArrayStack
{
hmiembros privados de pila ve
torizada 131bi
public:
hmiembros
};
131b
Los dos tipos de datos poseen los mismos atributos; estos son:
hmiembros privados de pila ve
torizada 131bi
T
array[dim];
size_t head;
(131a)
132
Captulo 2. Secuencias
ArrayStack
-array[dim]: T
-head: int
+ArrayStack()
+push(data:const T&): T&
+pushn(n:int=1): T&
+pop(): T
+popn(n:int): T
+top(): T&
+top(i:int): T&
+empty(): void
+is_empty(): bool
+size(): size_t
132a
132b
132
132d
(132b)
En algunas apli
a
iones, se requiere apartar espa
io en la pila sin que aun se
onoz
an
los valores de inser
ion. Si bien esto puede lograrse mediante una serie
onse
utiva de
pushes de datos \va
os", es mas e
iente ofre
er una sola opera
ion. Tal opera
ion se
denomina pushn() y se implanta
omo sigue:
hmiembros p
ubli
os de ArrayStack<T> 132ai+
(131a) 132b 133b
T & pushn(const size_t & n = 1) throw(std::exception, std::overflow_error)
{
hapartar n elementos 133ai
}
A partir del tope, el usuario de ArrayStack<T> puede referen
iar a los n elementos
insertados que no han sido ini
ializados y as asignarles su valor
uando sea ne
esario.
2.5. Pilas
133a
133b
hapartar
133
(132d)
133
133d
133e
(133)
(133b)
Por razones de e
ien
ia, tambien es deseable ofre
er una primitiva que libere varios
elementos en una sola opera
ion:
hmiembros p
ubli
os de ArrayStack<T> 132ai+
(131a) 133b 133g
T popn(const int & n) throw(std::exception, std::underflow_error)
{
hliberar n elementos 133f i
}
133f
(133e)
head -= n;
return array[head];
133g
Hay varias formas de
onsultar la pila; todas se realizan respe
to al tope y retornan
una referen
ia a un elemento dentro de la pila. La primera forma es la del elemento en el
tope:
hmiembros p
ubli
os de ArrayStack<T> 132ai+
(131a) 133e 133i
T & top() throw(std::exception,std::underflow_error)
{
hVeri
ar Under
ow 133
i
}
133h
133i
hretornar
(133g)
Otro tipo de
onsulta, menos fre
uente pero posible en algunas apli
a
iones, es
ono
er
el i-esimo elemento respe
to al tope:
hmiembros p
ubli
os de ArrayStack<T> 132ai+
(131a) 133g 134
T & top(const int & i) throw(std::exception,std::underflow_error)
{
esimo respe
to a tope 134ai
hreferen
ia al i-
}
134
134a
Captulo 2. Secuencias
134b
En algunas o
asiones una pila es reutilizable a
ondi
ion de que esta sea previamente
va
iada. Va
iar la pila implantada
on un arreglo es una opera
ion extremadamente rapida:
hva
iar pila 134bi
(134
)
head = 0;
134
134d
134e
134f
(134d)
134g
El TAD ListStack<T> modeliza una pila implantada mediante listas enlazadas. Al
ono
er el tipo de implanta
ion, el usuario
ono
e los
ostes en espa
io y tiempo, as
omo las
ganan
ias en versatilidad impartidas por la \naturaleza" dinami
a de las listas enlazadas.
ListStack<T> se basa en el TAD Snode<T>, el
ual, en esen
ia,
ontiene todo lo ne
esario para manejar las listas. ListStack<T> se dene en el ar
hivo htpl listSta
k.H 134gi;
htpl listSta
k.H 134gi
template <typename T>
class ListStack : private Snode<T>
{
hatributos de ListStack<T> 135ai
public:
etodos publi
os de ListStack<T> 135bi
hm
};
Denes:
ListStack, used in
hunks 135{37.
Uses Snode 84a.
2.5. Pilas
135
Mediante deriva
ion de Snode<T>, this funge de nodo
abe
era de la lista
uyo primer
elemento siempre sera el nodo tope de la pila.
T:class
Snode
T:class
ListStack
-num_nodes: size_t
+ListStack()
+push(node:Node*): void
+pop(): Node*
+top(): Node*
+is_empty(): bool
+size(): size_t
T:class
DynListStack
+top(): T&
+push(_data:const T&): T&
+pop(): T
+~DynListStack(): virtual
(134g)
size_t num_nodes;
135b
(134g) 135
135
135d
136
Captulo 2. Secuencias
136a
136b
136
(136)
(134g) 136a
2.5.4
136d
El TAD DynListStack<T>
El TAD ListStack<T> maneja pilas en fun
ion de nodos de una lista enlazada. Esto
obliga al usuario a
ontrolar y veri
ar detalles del manejo de memoria. La responsabilidad
de ListStack<T> es el manejo de los nodos; la responsabilidad del
liente es apartar y
liberar los nodos. Como hemos aprendido en o
asiones anteriores, manejar los enla
es,
tipos y memoria puede realizarse bajo un solo TAD, derivado de TAD mas sen
illos.
El TAD DynListStack<T>
umple el
ometido men
ionado en el parrafo anterior. DynListStack<T> dene una pila implantada
on listas enlazadas simples
y
on manejo de memoria in
luido. DynListStack<T> esta denido en el ar
hivo
htpl dynListSta
k.H 136di:
htpl dynListSta
k.H 136di
template <typename T>
class DynListStack : public ListStack<T>
{
public:
etodos de DynSlist<T> 137ai
hm
};
Denes:
DynListStack, used in
hunks 137d and 230.
Uses ListStack 134g.
2.5. Pilas
137
137b
(136d) 137b
T & top() const throw(std::exception, std::underflow_error)
{
return ListStack<T>::top()->get_data();
}
Uses ListStack 134g.
top()
onsulta el nodo tope de la
lase base y retorna una referen
ia al dato.
push() debe insertar un dato de tipo T. Esto requiere, apartar el nodo,
opiarle el dato
e invo
ar al push() de ListStack<T>:
hm
etodos de DynSlist<T> 137ai+
(136d) 137a 137
T & push(const T & __data) throw(std::exception, std::bad_alloc)
{
typename ListStack<T>::Node *ptr = new typename ListStack<T>::Node (__data);
ListStack<T>::push(ptr);
return ptr->get_data();
}
Uses ListStack 134g.
Puesto
137d
138
Captulo 2. Secuencias
2.5.5
Aplicaci
on: un evaluador de expresiones aritm
eticas infijas
En el dominio de la matemati
a, hay varias maneras de representar una opera
ion binaria.
Cada una asume una
onven
ion a
er
a de la posi
ion del operador y los operandos. En
nota
ion prefija, el operador pre
ede a los operandos. En nota
ion sufija , el operador
su
ede a los operandos. En nota
ion infija, el operador se
olo
a entre los dos operandos.
Por ejemplo, podemos representar la suma de dos operandos x e y
omo sigue:
15
La nota
ion preja se aproxima a las matemati
as tradi
ionales. El resultado de evaluar
una fun
ion f
on dos operandos x e y se denota
omunmente
omo f(x, y). Del mismo
modo, la
omposi
ion fun
ional se denota
omo h(f(x, y).
Las nota
iones preja y suja permiten una
antidad arbitraria de operandos. Esta
es una gran ventaja para denotar fun
iones multivariables de ndole
ualquiera. Ademas,
uando hay varias opera
iones, las nota
iones preja y suja permiten expresar todas las
opera
iones sin ne
esidad de parentesis. Por ejemplo, la forma suja de x (y + z) es
yz + x.
Para evaluar una expresion preja o suja se utiliza una pila. El algoritmo para evaluar
expresiones sujas se des
ribe
omo sigue:
Algoritmo 2.2 (Evaluaci
on de expresi
on sufija) La entrada del algoritmo es una
Algoritmo
ingles es tradu ido omo postx. De all que algunos textos la denominen postja.
2.5. Pilas
139a
139
Un algoritmo similar puede dedu
irse para evaluar expresiones prejas.
La nota
ion suja junto
on el algoritmo 2.2 es
omun en algunas
al
uladoras de
bolsillo, antiguos pro
esadores y en algunos lenguajes de programa
ion.
Por supuesto, la nota
ion inja es la mas familiar, pero esta solo permite a lo sumo
dos operandos. En a~nadidura, la pre
eden
ia de las opera
iones puede requerir el uso de
parentesis. A
ausa de esto, expresiones
ompli
adas son normalmente mas largas en forma
inja que sus equivalentes prejo o sujo. Este he
ho sugiere que manipular y evaluar
expresiones injas es mas
ostoso en espa
io y en tiempo que en las otras dos nota
iones.
Posiblemente por esta razon, fabri
antes de
al
uladoras de mano preeren la nota
ion
suja.
Existe un algoritmo muy e
iente para evaluar una expresion inja, el
ual requiere
dos pilas. Una pila alma
ena operandos y resultados par
iales; la otra alma
ena operadores
y parentesis que representan la pre
eden
ia.
Antes de expli
ar el algoritmo, requerimos alguna maquinaria de base que efe
tue el
pro
esamiento de la
adena de
ara
teres y que nos indique si estamos en presen
ia de
un operando u operador. Para ello, denimos los posibles valores de \
has" que pueden
en
ontrarse en una
adena:
htipo de
ha 139ai
enum Token_Type { Value, Operator, Lpar, Rpar, End, Error };
Denes:
Token Type, used in
hunks 139b and 143a.
139b
lexer() inspe
iona se
uen
ialmente la
adena de
ara
teres
ontenida en str a partir
del
ara
ter str + len. Al nal de la llamada, str apunta al primer
ara
ter de la
ha.
El valor de str puede modi
arse, pues pueden haber espa
ios en blan
o que no deben
pro esarse.
139
en blan
o 140ai
Por deni
ion, la inspe
ion
omienza en str + len. Al ini
io, la longitud de la
ha que
se en
uentre sera al menos de una unidad.
140
140a
Captulo 2. Secuencias
Ini
iada la inspe
ion, la rutina debe ignorar los espa
ios en blan
os. Esto se dene
fa
ilmente
omo:
hignorar espa
ios en blan
o 140ai
(139
)
while (isblank(*str))
str++;
140b
140
Lpar;
Rpar;
Operator;
End;
Si el
ujo sale del switch, enton
es la uni
a posibilidad
orre
ta es en
ontrar un operando.
Nos
er
ioramos, enton
es, de que la
adena
orresponda a un operando:
h
uerpo de lexer 139
i+
(139b) 140b 140d
if (not isdigit(*str))
return Error;
isdigit() es una fun ion de la bibliote a estandar del lenguaje C y retorna un valor
140d
140e
Cuando lexer() retorna un operador, debemos tomar a
iones segun la pre
eden
ia del
operador. Para ello, elaboramos una fun
ion que, dado un operador, determine su pre
eden
ia:
hfun
i
on de pre
eden
ia 140ei
const unsigned precedence(const char & op) // $ < ( < +- < */
{
switch (op)
{
case $: return 0;
case (: return 1;
case +:
2.5. Pilas
141
case -: return 2;
case /:
case *: return 3;
default: ERROR("Invalid operator %c\n", op);
}
}
Denes:
precedence() tiene omo parametro un ara ter. Puesto que los operadores son de un
141a
solo
ara
ter, podemos identi
ar la o
urren
ia del operador mediante inspe
ion dire
ta
de la
adena.
Hay un operador
ti
io, $,
on la menor pre
eden
ia,
uyo rol se expli
ara mas adelante. El parentesis izquierdo se
onsidera un operador en el siguiente nivel de pre
eden
ia.
La suma y resta o
upan el proximo nivel de pre
eden
ia. Finalmente, los operadores
on
mas pre
eden
ia son el produ
to y la division.
Si lexer() retorna Value, enton
es ne
esitamos obtener una
adena de
ara
teres que
ontenga el numero. Tal fun
ion la realizamos del siguiente modo:
hfun
i
on
onvertir
ha a
adena 141ai
char * str_to_token(char * token_str, const unsigned & len)
{
static char buffer[256];
strncpy(buffer, token_str, len);
buffer[len] = \0;
return buffer;
}
Denes:
(143a)
ArrayStack<int> val_stack;
ArrayStack<char> op_stack;
Uses ArrayStack 131a.
141
142
Captulo 2. Secuencias
int result;
switch (the_operator)
{
case +: result = left_operand + right_operand; break;
case -: result = left_operand - right_operand; break;
case *: result = left_operand * right_operand; break;
case /: result = left_operand / right_operand; break;
default: ERROR("Operator %c invalid", the_operator);
}
val_stack.push(result);
}
Denes:
apply, used in
hunks 143 and 144.
Uses ArrayStack 131a.
142
apply() asume que el operando dere
ho fue insertado en la pila despues del izquierdo.
Esto es lo normal si la
adena de entrada se analiza de izquierda a dere
ha.
Ahora disponemos de las herramientas ne
esarias para desarrollar una rutina que evalue
una expresion inja. Nuestra rutina se llamara eval() y tendra la siguiente estru
tura:
hfun
i
on de evalua
ion de expresion inja 142i
const int eval(char* input)
{
hvariables de eval 143ai
on de eval 143bi
hini
ializa
i
while (true)
{
hleer pr
oxima
ha 143
i
switch (current_token)
{
case Value:
hpro
esar operando 143di
case Lpar:
entesis izquierdo 144bi
hpro
esar par
case Operator:
hpro
esar operador 143ei
case Rpar:
hpro
esar par
entesis dere
ho 144
i
case End:
hpro
esar n de entrada 144di
case Error:
default: ERROR("Bad token detected");
}
}
}
Denes:
eval, never used.
2.5. Pilas
143a
hpilas de evalua
i
on 141bi
Token_Type current_token;
unsigned token_len = 0;
Uses Token Type 139a.
143
(142)
143b
143
Despues de la ini
ializa
ion, la rutina
omienza a iterar. El
uerpo iterativo lee las
has presentes en la entrada y luego pro
esa la
ha segun su tipo. Leer la
ha
onsiste
simplemente en una invo
a
ion a lexer():
hleer pr
oxima
ha 143
i
(142)
current_token = lexer(input, token_len);
143d
143e
Previamente hay que efe
tuar la
onversion de la
adena de
ara
teres a su representa
ion
numeri
a.
Podemos introdu
ir el operador en la pila, pero antes podemos eje
utar todas las
opera
iones que estan en la pila de operadores
on mayor o igual pre
eden
ia que el
operador a
tual. Esta alternativa es preferible porque disminuye el
onsumo en espa
io de
las pilas.
Para ejempli
ar,
onsideremos la expresion 100 20 5 + 7 y supongamos que la
ha
a
tual es el operador de resta. En este momento, val stack
ontiene < 100 > y op stack
< $ > y no podemos efe
tuar ninguna opera
ion porque el operador
ti
io $ tiene menor
pre
eden
ia que el operador . Cuando en
ontramos el operador , tampo
o podemos
efe
tuar ninguna opera
ion porque el produ
to tiene mayor pre
eden
ia que la resta. Finalmente,
uando nos en
ontramos el operador +, val stack
ontiene < 100, 20, 5 > y
op stack < $, , >. Aqu efe
tuamos el produ
to, pues este tiene mayor pre
eden
ia que
la suma, lo que nos deja las pilas en los estados < 100, 100 > y < $, >, respe
tivamente.
Posteriormente, efe
tuamos la resta, pues esta tiene la misma pre
eden
ia que la suma. Al
termino de esta opera
ion introdu
imos el operador + en la pila lo que nos deja el estado
de las pilas en < 0 > y < $, + >.
La
ondu
ta anterior se modeliza, enton
es, bajo la siguiente forma:
hpro
esar operador 143ei
(142)
{
144
Captulo 2. Secuencias
}
Uses apply 141
and precedence 140e.
144a
144b
(143e 144b)
Los parentesis en la expresiones injas son utilizados para modi
ar la pre
eden
ia por
omision de los operadores. Cuando la
ha sea un parentesis izquierdo, lo introdu
imos en
la pila de operadores:
hpro
esar par
entesis izquierdo 144bi
(142)
{
hintrodu
ir
break;
144
El parentesis dere
ho indi
ara el nal de una expresion que debe ser evaluada. Cuando
en
ontremos el parentesis dere
ho, eje
utaremos todas las opera
iones entre los parentesis.
Para ello, llamamos su
esivamente apply() hasta que el parentesis izquierdo sea en
ontrado en el tope de la pila:
hpro
esar par
entesis dere
ho 144
i
(142)
{
144d
2.5. Pilas
2.5.6
145
Uno de los usos mas tras
endentales de la pila es la gestion de llamadas a pro
edimientos
de un programa. Consideremos el siguiente fragmento de
odigo:
int fct_1(int i) { /* ... */ }
int fct_2(int i)
{
int x;
x = fct_1(i);
...
}
int fct_3(int j)
{
int x;
x = fct_2(j);
...
}
void main()
{
int y;
y = fct_3(3);
...
}
Ahora examinemos los eventos involu
rados a la llamada a fct 3() desde main().
Cuando se al
anza la llamada a fct 3(), el
ujo de
ontrol salta ha
ia la dire
ion de
memoria de fct 3() y
omienza a eje
utarla. Posteriormente,
uando se al
anza la llamada
a fct 2(), y el
ujo de
ontrol salta de nuevo ha
ia la dire
ion de fct 2(). Finalmente,
se al
anza la fun
ion fct 1() y el
ujo salta de nuevo ha
ia la dire
ion de fct 1().
Cuando fct 1() naliza, el
ujo de programa re
orre el
amino inverso hasta main().
Planteemos, enton
es, las siguientes preguntas:
Las fun
iones fct 2() y fct 3() tienen parametros de nombre i y variables lo
ales
de nombre x. >Como se diferen
ian entre si los parametros y variables?
>Como se gestiona la memoria para los parametros y variables lo
ales de un pro
ed-
imiento?
Estos problemas son resueltos por el
ompilador, el sistema operativo y el hardware a traves
de una pila. La mayora de los pro
esadores manejan dos registros espe
iales
omunmente
denominados SB y SP. Estos registros manejan la denominada \pila del sistema". SB es la
146
Captulo 2. Secuencias
dire
ion base de una pila y SP es la dire
ion a
tual del tope de la pila. Cuando se ha
e
un push sobre la pila del sistema, SP es de
rementado. Del mismo modo,
uando se ha
e
un pop(), SP es in
rementado.
Cuando el
ompilador pro
esa una llamada a una fun
ion, por ejemplo, fct 1() desde
main(), se genera, antes de llamar a la fun
ion, la siguiente se
uen
ia de pseudo
odigo
sobre la pila sistema:
1. Un push() para el espa
io del resultado que retorna fct 1().
2. Varios pushes para los parametros de fct 1().
3. Un push() para la dire
ion a
tual del
ujo del programa.
4. Una instru
ion de salto ha
ia la dire
ion de la fun
ion fct 1(), el
ual lleva el
ujo de
ontrol ha
ia el ini
io de la fun
ion fct 1().
5. Una vez que fct 1() haya sido eje
utada, el
ujo de programa regresara al punto
posterior donde se efe
tuo el salto. Para ello, el
ompilador genera un pop() que
re
upera la dire
ion de retorno de la pila. Al regresar a main(), se efe
tuan varios
pops
orrespondientes a la
antidad de pushes que se hizo para pasar los parametros.
6. Finalmente, se ha
e un ultimo pop() que re
upera el resultado de fct 1().
Pseudo c
odigo 2.5.1: Estru
tura de
odigo generada para la llamada a fct 1()
El
ompilador tambien genera
odigo adi
ional al prin
ipio y al nal de la fun
ion
fct 1() bajo el siguiente esquema:
1. Al ini
io de la fun
ion se efe
tuan varios pushes propor
ionales al numero de variables lo
ales de fct 1().
2. El
odigo de la fun
ion se genera
on referen
ias a las variables lo
ales apartadas en
el punto anterior y los parametros apartados y
opiados en 2.5.1.
3. El resultado de la fun
ion tambien se guarda en el espa
io apartado en el punto 1
de 2.5.1.
4. Al nal de la fun
ion, el
ompilador a~nade una
antidad de pops igual a la
antidad
de pushes usada para las variables lo
ales. Estos pops liberan la memoria usada por
las variables lo
ales.
5. Se ha
e un pop() que re
upera la dire
ion de retorno al invo
ante de fct 1() y se
salta al punto 5 de 2.5.1.
Pseudo c
odigo 2.5.2: Estru
tura de
odigo generada para la llamada a la fct 1()
2.5. Pilas
147
147
La version re
urrente del fa
torial es un mal ejemplo del uso ine
iente de la re
ursion,
pues, aparte de ser extremadamente ine
iente, la
ontraparte iterativa es muy e
iente
y mas fa
il de implantar. Sin embargo, a efe
tos dida
ti
os, su
odi
a
ion re
urrente es
muy fa
il de
omprender porque re
eja identi
amente la deni
ion matemati
a.
SB
Resultado
4
fact(4)
Dire
i
on de retorno
Resultado
3
fact(3)
Dire
i
on de retorno
Resultado
2
fact(2)
Dire
i
on de retorno
Resultado
1
SP
fact(1)
Dire
i
on de retorno
resultado de la llamada re
ursiva, el parametro y la dire
ion de retorno. Del mismo modo,
por
ada llamada, se gasta en tiempo los tres pushes y los tres pops.
2.5.6.1
Si estamos dise~nando un algoritmo y nos es mas fa
il trabajar
on la re
ursion, enton
es,
deben tenerse en
uenta las siguientes
onsidera
iones:
1. Todo algoritmo re
ursivo debe tener un
aso base donde no o
urra ninguna llamada
re
ursiva.
148
Captulo 2. Secuencias
2. El algoritmo debe progresar y
onverger en tiempo nito a en
ontrar la solu
ion. Por
ada llamada re
ursiva, la entrada debe disminuir. Si este no es el
aso, hay bastantes
probabilidades de que el razonamiento subya
ente al algoritmo este errado.
3. Evite
al
ulos repetidos. Este es el prin
ipal peligro de la re
ursion. Un ejemplo
notable son los numeros de Fibona
i
uya deni
ion matemati
a es la siguiente:
Fib(n) =
148
1
si n 1
Fib(n 1) + Fib(n 2) si n > 1
Notemos que Fib(n 2) se
al
ula 2 ve
es. La primera
uando se llama expl
itamente a
Fib(n 2); la segunda dentro de la llamada a Fib(n 1). Esta dupli
idad de llamadas se
expande exponen
ialmente a medida que aumenta n. La gura 2.26 ilustra la
antidad de
llamadas a fib() para la peque~na es
ala n = 5.
fib(5)
fib(4)
fib(3)
fib(2)
fib(1)
fib(1)
fib(3)
fib(2)
fib(1)
fib(2)
fib(0)
fib(1)
fib(1)
fib(0)
fib(0)
2.5. Pilas
2.5.6.2
149
Eliminaci
on de la recursi
on
Hay tres maneras
ono
idas para eliminar la re
ursion, la
uales men
ionaremos en los
sub-parrafos subsiguientes.
Eliminaci
on de la recursi
on cola
Si un pro
edimiento P(x) tiene
omo ultima instru
ion una llamada a P(y), enton
es
es posible reemplazar P(y) por una asigna
ion x = y y un salto al ini
io del pro
edimiento.
149a
Por ejemplo, el re
orrido de los nodos de una lista enlazada puede denirse re
ursivamente
del siguiente modo:
hRe
orrido re
ursivo de lista enlazada 149ai
template <typename T>
void recorrer(Snode<T> * head, void (*visitar)(Snode<T>*))
{
if (head->is_empty())
return;
(*visitar)(head->get_next());
recorrer(head->get_next());
}
Uses Snode 84a.
149b
Este pro
edimiento puede transformarse mediante elimina
ion de la re
ursion
ola en:
hRe
orrido no-re
ursivo de lista enlazada 149bi
template <typename T>
void recorrer(Snode<T> * head, void (*visitar)(Snode<T>*))
{
start:
if (head->is_empty()) return;
(*visitar)(head->get_next());
head = head->get_next();
goto start;
}
Uses Snode 84a.
149
Mu
has ve
es es posible eliminar la re
ursion
ola aun si la ultima instru
ion no es una
llamada re
ursiva. Por ejemplo, la ultima instru
ion de la version re
ursiva del fa
torial
hFa
torial re
ursivo 147i no es la llamada a fact(n - 1), sino la llamada a return. En
este
aso podemos eliminar la re
ursion
ola y obtener la siguiente version:
hFa
torial no re
ursivo 149
i
int fact(const int & n)
{
int result = 1;
start:
if (n <= 1) return result;
150
Captulo 2. Secuencias
result *= n;
n--;
goto start;
}
Emulaci
on de los registros de activaci
on
150
hVersi
on re
ursiva mejorada
int fib(const int & n)
{
if (n <= 1) return 1;
de Fibona i
150i
2.5. Pilas
151a
151
(151 )
# define P1 1
# define P2 2
struct Activation_Record
{
int n;
int f1;
int result;
char return_point;
};
Denes:
Activation Record, used in
hunk 151d.
151b
Con miras a la
laridad, el a
eso a
ualquiera de los
ampos se efe
tua a traves de
alguno de los siguientes ma
ros:
ha
eso registro a
tiva
i
on 151bi
(151
)
#
#
#
#
151
define
define
define
define
NUM(p)
F1(p)
RESULT(p)
RETURN_POINT(p)
((p)->n)
((p)->f1)
((p)->result)
((p)->return_point)
La version no re
ursiva,
on pila, que
al
ula el i-esimo numero de Fibona
i, la denimos en el ar
hivo hb sta
k.C 151
i
uya deni
ion es la siguiente:
hb sta
k.C 151
i
on 151ai
hRegistro a
tiva
i
ha
eso
151d
A lo largo de la rutina, manejaremos dos apuntadores. caller ar representa el registro de a
tiva
ion del invo
ante. Requerimos este registro de a
tiva
ion porque debemos
guardar el resultado de la llamada a
tual que emulamos, el
ual se guarda en el registro
de a
tiva
ion del invo
ante y no del invo
ado.
152
152a
Captulo 2. Secuencias
de re ursion
152 i
a b(n - 1) 152di
a b(n - 2) 153bi
start es el punto donde se ini ia el metodo re ursivo. Cada vez que se emule una
152b
llamada re
ursiva, se a
tualizan los punteros a los registros de a
tiva
ion y se salta
ha
ia este punto.
hllamada re
ursiva a b 152bi
(152d 153b)
caller_ar = &stack.top(1);
current_ar = &stack.top();
goto start;
152
Este segmento de
odigo asume que un nuevo registro de a
tiva
ion ha sido previamente insertado en la pila. Por esa razon, a
tualizamos los apuntadores a los registros
de a
tiva
ion antes de saltar al ini
io del programa.
La primera lnea del programa re
ursivo pro
esa el
aso base, el
ual es muy similar
en la version no re
ursiva:
h
aso base de re
ursi
on 152
i
(152a)
if (NUM(current_ar) <= 1)
{
RESULT(caller_ar) = 1;
goto return_from_fib;
}
152d
Al igual que en la version re
ursiva, la
ondi
ion se evalua sobre el parametro n del
registro a
tual. Si
aemos al
aso base, enton
es
olo
amos el resultado en el registro
de a
tiva
ion del invo
ante y enviamos el
ujo de programa ha
ia la parte que emula
el retornar desde la fun
ion re
ursiva.
Si no se
ae en el pro
eso base, enton
es hay que emular la llamada afib(n - 1),
la
ual se realiza
omo sigue:
hllamada a b(n - 1) 152di
(152a)
2.5. Pilas
153
RETURN_POINT(current_ar) = P1;
NUM(&stack.pushn()) = NUM(current_ar) - 1; /* crea un registro de activaci
on */
hllamada re
ursiva a b 152bi
La primera lnea mar
a la dire
ion de retorno. La segunda aparta el registro de a
tiva
ion
de la nueva llamada e ini
ia su parametro n segun la llamada fib(n - 1).
Cuando se haya
al
ulado fib(n - 1), hretornar desde b 153di inspe
ionara el
valor RETURN VALUE del registro a
tual de a
tiva
ion y se per
atara de que hay que
saltar el
ujo ha
ia la etiqueta p1. En este momento debemos emular la asigna
ion
int f1 = fib(n - 1):
hretorno desde b(n - 1) 153ai
(152a)
153a
F1(current_ar) = RESULT(current_ar);
(152a)
NUM(&stack.pushn()) = NUM(current_ar) - 2; /* crea un registro de activaci
on */
RETURN_POINT(current_ar) = P2;
hllamada re
ursiva a b 152bi
hllamada
152di.
Lo uni o que
Finalmente, return from fib es el punto donde se emula la instru ion return del pro-
(152a)
Cada vez que se retorna de una fun
ion se elimina un registro de a
tiva
ion; ese es el
ometido del pop(). Si la pila
ontiene un registro, enton
es el
ujo esta terminando
la llamada ini
ial y el resultado denitivo se en
uentra en RESULT(caller ar). De lo
ontrario, se a
tualizan los apuntadores a los registros de a
tiva
ion y se determina donde
se debe retornar.
154
Captulo 2. Secuencias
Puesto que el registro del invo
ante pasara a ser el a
tual, no hay ne
esidad de observar la
pila. Puesto que la pila esta implantada
on un arreglo, el siguiente registro de a
tiva
ion
sera el ve
ino de current ar; por eso lo in
rementamos.
Una optimiza
ion nal
onsiste en
olo
ar dire
iones reales de salto en lugar de etiquetas. Este metodo eliminara el switch y el if de hretornar desde b 153di, pues el
ujo saltara dire
tamente a la dire
ion dada. Desafortunadamente, ni el lenguaje C ni
el C++ poseen me
anismos que permitan alma
enar dire
iones de salto . Tendramos,
enton
es, que programar algunas partes de la rutina en ensamblador.
16
Eliminaci
on total de la recursi
on
la ex
ep
ion del ensamblador, el autor no
ono
e ningun lenguaje que posea este me
anismo. Las
primitivas longjmp() y setjmp() de la bibliote
a estandar del lenguaje C permiten guardar dire
iones de
ujo y saltar a ellas. Lamentablemente, a nuestros efe
tos, sus
ostes en tiempo y en espa
io son superiores
que el guardar indi
a
iones.
2.6. Colas
155
n
Y
i .
i=1
Esta deni
ion nos
ondu
e a una version iterativa sin ningun razonamiento re
ursivo.
Algunas ve
es se puede en
ontrar una solu
ion analti
a. Por ejemplo,
omo demostraremos en x 6.4.2, el i-esimo numero de Fibona
i puede denirse
omo:
1
Fib(n) =
5
"
!n
1+ 5
!n #
1 5
.
2
2.6
Colas
Una
ola es una se
uen
ia
on dos extremos de a
eso. La inser
ion se efe
tua por un
extremo, la supresion por el otro. El extremo de inser
ion se denomina
omunmente \
ola"
o \trasero" o, en sus formas, sajonas \rear" o \tail". El extremo de supresion es llama
\
abeza", \frente" o, en sus formas sajonas, \front" o \head".
abeza
frente
front
ola
trasero
rear
put
Tn
Tn1 Tn2
T2
T1
get
17 En
156
2.6.1
Captulo 2. Secuencias
Una variante mas general del orden FIFO
onsiste en ver el orden desde el mas re
ientemente a
edido hasta el menos re
ientemente a
edido. El orden de inser
ion y extra
ion
es el mismo, pero hay una opera
ion adi
ional de a
eso o
onsulta. Cualquier elemento
de la
ola puede ser a
edido, pero este a
eso
ausa que el elemento se despla
e ha
ia
el trasero. De este modo, el frente es el elemento que tiene mas tiempo sin referen
iarse,
mientras que el trasero es el que tiene menos tiempo. Esta polti
a
obra importan
ia en
problemas que manipulan
onjuntos de datos de
ardinalidad nita. Cuando se ingresa un
nuevo elemento en un
onjunto lleno, hay que sele
ionar un elemento a suprimir. Si el
a
eso al
onjunto exhibe lo
alidad temporal, lo
ual su
ede en mu
hos
ontextos apli
ativos, enton
es el mejor elemento a suprimir es el menos re
ientemente utilizado. En una
ola que maneje el orden de a
eso, este elemento se en
uentra en el frente.
Otra variante importante es permitir la inser
ion y supresion por los dos extremos. Esta
variante es denominada \di
ola" o \dipolo" . En
ierta medida, una di
ola es una forma
general de pila y de
ola. Cualquiera de las dos estru
turas puede modelarse mediante una
di
ola. El TAD Dlist<T> (x 2.4.9) es un dipolo.
18
2.6.2
En general, los sistemas programados utilizan
olas para atender soli
itudes bajo la justa
dis
iplina FIFO. Por ejemplo, un servidor WEB puede en
olar soli
itudes de busqueda de
pagina segun el orden de llegada. Mu
hos sistemas de
ontrol de transa
iones en
olan las
soli
itudes de servi
ios; por ejemplo, transa
iones nan
ieras.
El rol fundamental del software de red es transmitir paquetes de informa
ion entre
los diversos nodos de una red. El programa en
argado de esta transmision es denomina
\enrutador" o \en
aminador". Generalmente, un enrutador pro
esa sus paquetes segun
una dis
iplina FIFO.
Los sistemas operativos tambien utilizan
olas para
ompartir re
ursos entre diversos
usuarios. Tradi
ionalmente, pro
esos que arriben a un sistema se insertan en una
ola. El
pro
eso en el frente se extrae y se eje
uta durante un tiempo llamado
uantum. Cuando
el
uantum expira, el pro
eso se inserta de nuevo en la
ola y el sistema operativo extrae
el siguiente pro
eso de la
ola.
Existe toda una rama de las matemati
as,
ono
ida
omo teora de
olas, la
ual
modeliza entidades abstra
tas que se en
olan para soli
itar algun servi
io. Aunque las
matemati
as arrojan resultados analti
os muy utiles, algunos modelos son tan
omplejos
que es ne
esario \simularlos". Tales sistemas de simula
ion usan intensivamente
olas.
2.6.3
Al igual que la pila, una
ola es una se
uen
ia de elementos de algun tipo. Como tal, hay
dos formas basi
as de representar una
ola:
on un arreglo o
on una lista.
La implanta
ion de la
ola
on un arreglo es similar a la pila. La diferen
ia estriba
en que debemos manejar los dos extremos; ergo, debemos mantener dos
ontadores: uno
para la
ola, otro para la
abeza. La gura 2.28 ilustra una
ola implantada mediante un
arreglo.
18 En
ingles, deque.
2.6. Colas
157
0 1 2 3 4 5 6 7 8 9 10
T0 T1 T2 T3 T4
ola
abeza
157
En esta se
ion desarrollaremos una implanta
ion de
olas mediante arreglos estati
os.
Implantaremos dos TAD muy similares: ArrayQueue<T> y FixedQueue<T>. Ambos tipos
son
asi identi
os salvo que FixedQueue<T> no efe
tua veri
a
iones de desborde.
Los tipos se denen en el ar
hivo htpl arrayQueue.H 157i de la siguiente manera:
htpl arrayQueue.H 157i
template <typename T>
class ArrayQueue
{
158
Captulo 2. Secuencias
private:
hatributos
etodos
hm
public:
hm
etodos publi
os de ArrayQueue<T> 160di
hobservadores de
ola ve
torizada 162hi
};
template <typename T>
class FixedQueue
{
private:
hatributos de
ola ve
torizada 158ai
hm
etodos
public:
hm
etodos publi
os de FixedQueue<T> (never dened)i
hobservadores de
ola ve
torizada 162hi
};
Denes:
ArrayQueue, used in
hunks 160d and 308.
FixedQueue, never used.
158a
158b
front index es elndi
e del elemento mas antiguo en la
ola; es de
ir, array[front index]
158
(161g)
2.6. Colas
159
T:class
ArrayQueue
+dim: const size_t
+array[1<<two_pow]: T
+front_index: size_t
+rear_index: size_t
+mask: const size_t
+num_items: size_t
-increase_ index(i:size_t&,inc:const size_t=1): void
+rear_item(i:size_t=0): T&
+ArrayQueue()
+put(item:const T&): T&
+putn(n:size_t): T&
+get(): T
+getn(n:size_t): T
+front(i:size_t=0): T&
+rear(i:size_t=0): T&
+size(): size_t
+is_empty(): bool
+dimension(): size_t
array[front_index]
159
rear index indi
a la posi
ion de la primera
elda disponible para insertar; es de
ir,
array[rear index - 1]
ontiene el elemento mas re
iente en la
ola.
0 1 2 3 4 5 6 7
T0 T1 T2 T3 T4
front
rear
160
160a
Captulo 2. Secuencias
de la dimension del arreglo. De este modo, el valor del ndi
e siempre estara
omprendido
entre 0 y dim 1. Este metodo linealiza el
odigo, pero es mas lento que
on el if. Es
para solventar este problema, que forzamos la dimension a que sea poten
ia exa
ta de 2,
pues, en este
aso, el modulo puede
al
ularse muy rapidamente a traves de un and logi
o.
Por tanto, uno de los atributos sera una mas
ara de bits para
al
ular el modulo:
hatributos de
ola ve
torizada 158ai+
(157) 159 160b
const size_t mask;
160b
la
ola esta llena. No hay otra alternativa, enton
es, que
ontabilizar dire
tamente la
antidad de elementos en un atributo:
hatributos de
ola ve
torizada 158ai+
(157) 160a
size_t num_items; /* current queues lenght */
160
160d
hatributos
hini
ia
i
on de atributos 160
i
(160d)
two_pow(tp), dim(1<<two_pow), array(NULL), front_index(0),
rear_index(0), mask(dim - 1), num_items(0)
~ArrayQueue()
{
if (array != NULL)
delete [] array;
}
Uses ArrayQueue 157.
160e
La opera
ion de in
rementar un ndi
e y luego
al
ular el modulo sera efe
tuada por los
modi
adores de la
ola. Es muy importante, pues, asegurarnos de ha
erla
orre
tamente.
Para ello, la modularizamos y la aislamos en una sola opera
ion:
hm
etodos privados de
ola ve
torizada 160ei
(157) 161a
void increase_index(size_t & i, const size_t & inc = 1) const
{
i += inc;
i &= mask;
}
increase index() in rementa i en inc unidades y al ula el modulo mediante la mas ara.
El metodo del modulo puede apli
arse tambien
on restas en lugar de sumas. Cuando
se de
rementa un entero sin signo
on valor
ero, el desborde negativo
ausa que el entero
adquiera el mayor valor. Puesto que la dimension es poten
ia de dos, el modulo dara
omo
resultado la dimension menos uno. Debemos asegurarnos, empero, de que las restas se
apliquen sobre enteros sin signo y, por supuesto, de que la aritmeti
a sea binaria.
2.6. Colas
161a
161
161b
hm
etodos publi
os de ArrayQueue<T> 160di+
(157) 160d 161d
T & put(const T & item) throw(std::exception,std::overflow_error)
{
hput en
ola ve
torizada 161
i
}
put() genera la ex ep ion std::overflow error si se intenta insertar en una ola llena.
161
(161b)
array[rear_index] = item;
T & ret_val = array[rear_index];
increase_index(rear_index);
num_items++;
return ret_val;
161d
En lugar de insertar un elemento, es posible apartar espa
io, bajo dis
iplina FIFO,
para n elementos. Esto es tan rapido a traves de un arreglo que vale la pena exportar la
fun
ionalidad en el siguiente metodo:
hm
etodos publi
os de ArrayQueue<T> 160di+
(157) 161b 161f
T & putn(const size_t & n) throw(std::exception, std::overflow_error)
{
hputn en
ola ve
torizada 161ei
}
161e
161f
161g
num_items--;
T ret_val = hfrente de la
ola 158
i;
increase_index(front_index);
return ret_val;
(161f)
162
162a
Captulo 2. Secuencias
162b
162
162d
162e
hretornar i-
esimo elemento del frente 162ei
return array[front_index + i];
162f
162g
162h
(162a)
(162)
(162 )
(157) 162
T & rear(const size_t & i = 0) throw(std::exception, std::underflow_error)
{
hveri
ar desborde negativo 162di
hretornar i-
esimo elemento del trasero 162gi
}
hretornar i-
esimo elemento
return rear_item(i);
(162f)
(157)
La implanta ion de FixedQueue<T> es muy similar a ArrayQueue<T>. La uni a diferen ia sera que FixedQueue<T> no veri a desbordes.
2.6. Colas
2.6.5
163a
163
El TAD ListQueue<T> modeliza una
ola implantada mediante una listasimplemente enlazada
ir
ular. ListQueue<T> esta espe
i
ado e implantado en el ar
hivo
htpl listQueue.H 163ai:
htpl listQueue.H 163ai
template <typename T>
class ListQueue
{
hNode de ListQueue<T> 163bi
hAtributos
de ListQueue<T> 165ai
public:
hM
etodos publi
os de ListQueue<T> 165
i
};
Denes:
ListQueue, used in
hunks 165
and 166
.
163b
Las opera
iones de ListQueue<T> se espe
i
an en fun
ion de nodos simples de tipo
ListQueue<T>::Node:
hNode de ListQueue<T> 163bi
(163a)
public:
typedef Snode<T> Node;
private:
Uses Snode 84a.
19 De imos
adrede \entre la
ola y la
abeza" y no, \entre la
abeza y la
ola", porque esta forma
orresponde al sentido de enla
e de los nodos.
164
Captulo 2. Secuencias
T:class
ListQueue
-rear_ptr: Node *
-num_nodes: size_t
+ListQueue()
+put(node:Node *): void
+get(): Node*
+front(): Node*
+rear(): Node*
+size(): size_t
+is_empty(): bool
T:class
DynListQueue
-Node: ListQueue<T>::Node
+put(data:const T&): T&
+get(): T
+front(): T&
+rear(): T&
+~DynListQueue(): virtual
2.6. Colas
165
inevitable, pues, tener que manejar dos
asos: uno general y uno
uando la
ola este
va
a.
En
on
lusion, utilizaremos una lista
ir
ular simple de nodos Snode<T> y mantendremos un solo puntero a la
ola.
165a
De
idida la
ongura
ion de la gura 2.33, podemos
omenzar a introdu
ir los atributos
de ListQueue<T>:
hAtributos de ListQueue<T> 165ai
(163a) 165b
Node * rear_ptr;
rear ptr sera el puntero a la
ola
ir
ular de elementos de tipo Snode<T>. El otro atributo
165b
(163a) 165a
size_t num_nodes;
165
Los valores anteriores requieren ser ini
iados por el
onstru
tor:
hM
etodos publi
os de ListQueue<T> 165
i
(163a) 165d
ListQueue() : rear_ptr(NULL), num_nodes(0) { /* empty */ }
Uses ListQueue 163a.
165d
Tenemos dos modos de saber si una
ola esta va
a: si rear ptr ==
NULL o si num nodes == 0.
La inser
ion posee la siguiente estru
tura:
hM
etodos publi
os de ListQueue<T> 165
i+
(163a) 165
165e
void put(Node * node)
{
if (num_nodes > 0)
rear_ptr->insert_next(node);
num_nodes++;
rear_ptr = node;
}
165e
166
166a
166b
Captulo 2. Secuencias
(165e 166b)
(163a) 165e
Node * front() const throw(std::exception, std::underflow_error)
{
hveri
ar desborde negativo 166ai
return rear_ptr->get_next();
}
Node * rear() const throw(std::exception, std::underflow_error)
{
hveri
ar desborde negativo 166ai
return rear_ptr;
}
2.6.6
166
El TAD DynListQueue<T> modeliza una
ola implantada a traves de una lista simplemente enlazada
ir
ular. DynListQueue<T> esta espe
i
ado e implantado en el ar
hivo
htpl dynListQueue.H 166
i.
DynListQueue<T> hereda parte de la interfaz e implanta
ion de ListQueue<T>. El
uni
o rol de DynListQueue<T> es manejar la memoria y manipular datos de un tipo
generi
o T en lugar de nodos de una lista enlazada. Puesto que este pro
eso ya lo hemos
realizado para las listas y las pilas,
olo
aremos en esta se
ion toda la implanta
ion dire
ta
de DynListQueue<T>:
htpl dynListQueue.H 166
i
template <typename T>
class DynListQueue : public ListQueue<T>
{
typedef typename ListQueue<T>::Node Node;
public:
T & put(const T & data) throw(std::exception, std::bad_alloc)
{
Node * ptr = new Node (data);
ListQueue<T>::put(ptr);
return ptr->get_data();
}
T get() throw(std::exception, std::underflow_error)
{
167
2.7
Consideremos una lista de \estudiantes" de un
urso de estru
tura de datos. Los nombres,
apellidos y
edula de identidad pueden representarse mediante
adenas de
ara
teres. Las
notas pueden representarse mediante enteros. Toda esta informa
ion la estru
turamos
en un registro y usamos un arreglo de registros para representar la lista. Esta manera de
organizar los datos, que posiblemente ya nos sea \natural", es un ejemplo de
ombina
iones
entre diversas estru
turas de datos.
Segun los requerimientos del problema, una se
uen
ia de elementos puede representarse
omo un arreglo o
omo una lista. Comunmente, una matriz se representa
omo un arreglo
de arreglos. >Podemos ha
er lo mismo
on listas? Es de
ir, >podemos tener una lista de
listas? o >arreglo de listas? o >una lista de arreglos? Las respuestas son armativas.
En lenguaje C, una matriz se representa mediante un arreglo de las, donde
ada la
es, tambien, un arreglo. As pues, podramos de
larar lo siguiente:
DynSlist<int> mat[5];
Aqu tenemos un arreglo de listas donde
ada mat[i] reere a una lista enlazada.
Tambien podramos de
larar:
typedef int Arreglo[5];
168
Captulo 2. Secuencias
DynSlist<Arreglo> mat;
i j
dato
Puntero a la proxima la
que nos representa una entrada de una matriz mat[i, j]
on los siguientes atributos:
Indi
e de la; es de
ir el valor de i.
Indi
e de
olumna; es de
ir el valor de j..
Valor del dato diferente de
ero
ontenido en mat[i, j].
Puntero al siguiente mat[i + x, j]
on valor diferente de
ero.
Puntero al siguiente mat[i, j + y]
on valor diferente de
ero.
1 5 0 0 10
0 0 1 2 0
0 ,
0 0 0 1
0 0 0 0
0
0 0 0 1
0
0 0
0 1
169
0 4
1 2
1 3
2 3
4 3
10
170
2.8
Captulo 2. Secuencias
Notas bibliogr
aficas
Los orgenes de las estru
turas basi
as se remontan a mediados de los a~nos 30 del siglo XX
durante los desarrollos de programas para los primeros
omputadores. Esta epo
a es tan
ar
ana a este reda
tor, que este no se siente autorizado para ofre
er un dis
urso histori
o
sobre el origen de las estru
turas de datos que representan se
uen
ias ni algunas de las
te
ni
as de busqueda tratadas en este
aptulo. Para un hermoso panorama de esta apasionante historia, es
rito por uno sus protagonistas, reiteramos re
omendar la se
ion histori
a
ontenida en el primer volumen de Knuth [7 (paginas 457-465).
Por su
ara
ter se
uen
ial \innato", la no
ion de arreglo fue manejada desde los
primeros programas de
omputador. En a~nadidura, el arreglo tiene su equivalente \ve
tor"en matemati
a. Por lo anterior, sera muy aventurado adjudi
ar alguna autora ex
lusiva. Empero,
abe se~nalar el Fortran [2
omo el primer lenguaje de programa
ion y el
primero en soportar arreglos a traves del propio lenguaje.
El enfoque de dise~no de la jerarqua de
lases Dlink esta primigeniamente inspirado
en utilitarios dise~nados para el mi
ro-nu
leo Chorus [16, sistema sobre el
ual este reda
tor realizo sus estudios do
torales. Algunas bibliote
as de programa
ion usan enfoques
similares, entre las
uales
abe desta
ar net.datastructures reportada en [5.
En el mismo sentido de lo arreglos, las listas enlazadas fueron des
ubiertas independiente y separadamente en los primigenios y ya ar
anos
ontextos
omputa
ionales. Sin embargo,
abe se~nalar al IPL (Information Pro
essing Languaje) [13,
omo el primer lenguaje
que in
orporo las listas a un lenguaje de programa
ion. La
lasi
a pi
toriza
ion
onsistente
de re
tangulos para representar bloques de memoria y de
e
has para los punteros apare
e
en un art
ulo de Newell y Shaw sobre programa
ion
on IPL [12.
Los arreglos, listas, pilas y
olas ya son parte de los lenguajes de muy alto nivel modernos; entre los que
abe se~nalar Perl [19 y Python [18. Estos lenguajes se
ir
uns
riben
en los lenguajes llamados de "s
ripting", termino madurado por John K. Ousterhout [14
en un
elebre y visionario art
ulo men
ionado en la se
ion bibliogra
a de este
aptulo.
La busqueda binaria es un prin
ipio de busqueda milenario, el
ual, segun Knuth [7,
se remonta hasta la misma Mesopotamia, pre
ursora de la
iviliza
ion y hoy en da miserablemente aniquilada en nombre de la propia
iviliza
ion. Consultese el trabajo histori
o
de Zohar Manna and Ri
hard Waldinger sobre los orgenes de la busqueda binaria [9.
Aparte sus ane
dotas histori
as [7 es una de las mas solidas referen
ias formales en
el
ampo de algoritmos y estru
turas de datos. Las estru
turas lineales son tratadas de
manera muy formal y
on apli
a
iones reales. La primera edi
ion fue reda
tada en una
epo
a en que el poder
omputa
ional de una maquina aun era muy bajo y el lenguaje
ensamblador era la uni
a alternativa seria para programar apli
a
iones de alto desempe~no.
Por esta razon, el texto de Knuth
ontiene una amplia gama de \tru
os" a utilizar
on
estru
turas lineales.
El
lasi
o y antiguo texto de Wirth [21, fue es
rito durante los a~nos 70 del siglo XX. A
pesar de su edad, hoy en da este texto
ontinua bastante vigente y presenta una exposi
ion
de las estru
turas lineales superior a la norma. Ademas, Wirth
onsagra un ex
elente y
ompleto
aptulo a la re
ursion.
El libro de Sedgewi
k [17, esta repleto de algoritmos
on estru
turas lineales; por
ejemplo, algoritmos de ordenamiento de listas. A nivel pra
ti
o, esta es, en nuestra opinion,
la mejor referen
ia. Cabe men
ionar, empero, que los algoritmos que usan estru
turas
2.9. Ejercicios
171
lineales estan distribuidos a lo largo del texto en
aptulos que no ne
esariamente tratan
de estru
turas lineales.
Lewis y Denenberg [8 presentan algunos tru
os interesantes
on listas enlazadas.
La te
ni
a de
lase intermediaria, utilizada por BitArray y DynArray<T> para distinguir los a
esos de le
tura de los de es
ritura la analiza intensivamente S
ott Meyer [10.
El problema de Jose es analti
amente tratado por Graham, Knuth y Patashnik [6.
La aritmeti
a de polinomios expli
ada en x 2.4.11 es ampliamente desarrollada por
Aho, Hop
roft y Ullman[1. Una alternativa puede en
ontrarse en el texto en
i
lopedi
o
de Cormen, Leiserson y Rivest [4.
El algoritmo de evalua
ion de expresiones injas desarrollado en x 2.5.5 fue tomado
de un texto sobre programa
ion de sistemas de Peter Calingaert [3. Este libro esta tan
magistral y
on
isamente es
rito que lo re
omendamos
on
re
es para una introdu
ion
al
ampo de la programa
ion de sistemas,
ompiladores, ensambladores y
argadores.
2.9
Ejercicios
172
Captulo 2. Secuencias
(a) Revise minu
iosamente la implanta
ion en Vector.H. Pruebela, busque errores,
eventualmente depurela y eventualmente mejore su e
ien
ia.
(b) Compare
rti
amente Vector.H
on otras implanta
iones de stdc++.
13. Es
riba un algoritmo e
iente que invierta un arreglo. Por ejemplo, al invertir
ABCDEFG se debe arrojar GFEDCBA.
14. Es
riba un algoritmo que elimine los elementos dupli
ados de un arreglo ordenado.
15. Es
riba un algoritmo que elimine los elementos dupli
ados de un arreglo desordenado.
16. Dado un arreglo desordenado de n 1 elementos enteros
omprendidos entre 1 y n,
dise~ne un algoritmo que no
ontenga
i
los anidados, o sea de una sola pasada, que
determine el elemento faltante. (+)
17. Considere la opera
ion de rota
ion de un arreglo. Por ejemplo, rotar 3 ve
es ha
ia la
izquierda el arreglo ABCDEFGHI arroja
omo resultado DEFGHIABC. Dise~ne un
algoritmo general, que no
ontenga
i
los anidados (solo
i
los de una sola pasada),
que rote m ve
es un arreglo de dimension n. (++)
18. Suponga una se
uen
ia S =< s1, s2, . . . sn >. La opera
ion void rotar derecha(S, n)
\rota\ a S n posi
iones ha
ia la dere
ha. Por ejemplo,
rotar derecha(< 1, 2, 3, 4, 5, 6, 7 >, 3) =< 5, 6, 7, 1, 2, 3, 4 >
tiene una longitud de 32
ara
teres. Si justi
a a 30
ara
teres, enton
es el resultado
es:
Esta
es
una
prueba
de
Dise~ne un algoritmo que justique una
adena de
ara
teres a un maximo n menor
que la longitud de la
adena. El algoritmo debe tener dos salidas: la
adena justi
ada
y la
adena extrada por la dere
ha.
2.9. Ejercicios
173
174
Captulo 2. Secuencias
26. Espe
ique
uales ayudas podra ofre
er un
ompilador para fa
ilitar la implanta
ion
generi
amente de la
onversion de un Slink al registro que lo in
luya. (+++)
27. Espe
ique
uales ayudas podra ofre
er el lenguaje de programa
ion para implantar
generi
amente la
onversion de un Slink al registro que lo in
luya (+++).
28. Es
riba un algoritmo que
uente el numero de nodos en una lista simplemente enlazada.
29. Estudie y dis
uta el dise~no de los TAD Slist<T> y DynSlist<T>. >De que formas
pueden extenderse o mejorarse? >Cuales son sus problemas? >Estan
ompletos?
30. Implante, en detrimento del tiempo de eje
u
ion, un TAD llamado Single Link que
tenga exa
tamente los mismos metodos que la
lase Dlink.
31. Es
riba un algoritmo que
uente el numero de o
urren
ias de un elemento x en una
lista simplemente enlazada.
32. Dado un apuntador ptr a un nodo de una lista enlazada, es
riba un algoritmo que
determine la posi
ion ordinal de ptr dentro de la lista. Dise~ne su algoritmo para que
fun
ione
on listas simples y dobles.
33. Dis
uta brevemente alternativas para implantar la substra
ion de polinomios.
34. Dis
uta brevemente
omo implantar los algoritmos de division y residuo de polinomios. Derive expresiones del tiempo de eje
u
ion.
35. Implante el TAD Polinomio, expli
ado el x 2.4.11 (pagina 120), mediante listas simplemente enlazadas. Use el TAD Snode<T> o Slink.
36. Considere dos listas de enteros doblemente enlazadas,
ir
ulares,
on nodo
abe
era,
denominadas l1 y l2, respe
tivamente.
Considere la opera
ion:
const bool similar(Dnode<int> & l1, Dnode<int> & l2)
La
ual retorna true si las listas
ontienen exa
tamente los mismos enteros; false,
en
aso
ontrario.
Es
riba algoritmos que implanten la opera
ion para las siguientes situa
iones:
(a) Las dos listas estan ordenadas del menor al mayor entero.
(b) Las listas no estan ordenadas.
37. Dise~ne, instrumente e in
orpore al TAD Slink las opera
iones del TAD
Dlink insert list(), append list() y concat list. (+)
38. Es
riba un algoritmo iterativo que
al
ule el i-esimo numero de Fibona
i.
39. Es
riba un algoritmo re
ursivo que
ompute el i-esimo numero de Fibona
i y que
no redunde
al
ulos (+).
2.9. Ejercicios
175
40. Es
riba una version del algoritmo
on pila desarrollado en 2.5.6.2 para
al
ular el
i-esimo n
umero de Fibona
i que no redunde en
al
ulos. (+)
41. Es
riba un algoritmo re
ursivo que
onvierta una
adena de
ara
teres que
ontiene
un entero a su representa
ion entera. En otras palabras, es
riba una rutina que
onvierta un entero alma
enado en un char* a un entero de tipo int.
42. En un tablero de ajedrez de n n es
aques , se situa un
aballo en las
oordenadas
x0, y0. Es
riba un algoritmo que en
uentre, si existe, un re
ubrimiento
ompleto del
tablero de n2 1 movimientos tal que
ada es
aque sea visitado exa
tamente una
vez. (++)
20
20 En
n + 1
A(m, n) = A(m 1, 1)
m=0
m > 0, n = 0
m, n > 0
176
Captulo 2. Secuencias
(a) Es
riba una rutina re
ursiva que
al
ule la fun
ion de A
kerman. >Cual es la
omplejidad de tiempo de la fun
ion? (+)
(b) Es
riba una fun
ion iterativa que
al
ule la fun
ion de A
kerman. >Cual es la
omplejidad de tiempo de la fun
ion? (+)
49. Sean A y B dos listas enlazadas ordenadas. Considere la \mez
la" ordenada de A y
B,
uyo resultado es una lista fusionada y ordenada de los nodos de A y B. Es
riba
un algoritmo que mez
le dos listas:
(a) Simplemente enlazadas.
(b) Doblemente enlazadas.
50. Implemente una bibliote
a que efe
tue todas las veri
a
iones de tipo en tiempo de
eje
u
ion tal
omo fue planteado en ejer
i
io 25. (++)
51. Implante el
onstru
tor
opia y el operador de asigna
ion para la
lase DynArray<T>
denida en x 2.1.5. (+)
52. Espe
ique, dise~ne e implante totalmente el TAD DynMatrix<T> men
ionado en el
problema 22.
53. Dise~ne e implante un TAD que maneje arreglos de bits y que utili
e el TAD
DynArray<T>.
54. Dise~ne e implante un TAD que maneje matri
es de bits.
55. Implante un TAD llamado DynArrayStack<T>, el
ual maneja pilas implantadas
on
arreglos dinami
os. El arreglo debe ser
ontrado dinami
amente.
56. Implante un TAD llamado DynArrayQueue<T>, el
ual maneja
olas implantadas
on
arreglos dinami
os. El arreglo debe ser
ontrado dinami
amente.
57. Implante la substra
ion de polinomios.
58. Implante la division de polinomios (++).
59. Implante la opera
ion residuo produ
to de la division de dos polinomios (++).
60. Modique el TAD Polinomio para que haga los terminos visibles al usuario y permita
a
ederlos e
azmente mediante un iterador.
61. Implante una fun
ion que
al
ule la derivada de un polinomio.
62. Implante una fun
ion que
al
ule la integral de un polinomio.
63. Modique el TAD polinomio para que utili
e
oe
ientes en punto
otante.
64. Implante una fun
ion que evalue un polinomio.
65. Es
riba un algoritmo de una sola pasada que en
uentre el menor elemento de una
lista enlazada.
2.9. Ejercicios
177
66. Dada una lista
ir
ular doblemente enlazada, es
riba un algoritmo que
oloque los
nodos que estan en posi
iones impares despues de los que estan en posi
iones pares.
Se debe preservar el orden relativo entre los nodos.
67. Considere la elimina
ion de los elementos repetidos de una lista. Dise~ne un algoritmo
que ltre los elementos repetidos de una lista. Reali
e para los siguientes
asos:
(a) La lista es simplemente enlazada.
(b) La lista es doblemente enlazada.
En ambos
asos, el resultado debe
omponerse de dos listas. Una
orrespondiente al
ltrado; la otra
orrespondiente a los elementos suprimidos.
68. Suponga dos listas simplemente enlazadas A y B y las opera
iones de union e interse
ion. Es
riba algoritmos que
al
ulen la union e interse
ion si:
(a) Las listas estan ordenadas.
(b) Las listas no estan ordenadas.
Anali
e el desempe~no de
ada uno de los algoritmos.
69. Suponga dos listas enlazadas A y B. Considere la opera
ion de fusion
uyo resultado
sera una sola lista
uyos primeros elementos
orresponden a la lista A seguida por
los elementos de la lista B. Es
riba un algoritmo de fusion para
ada uno de los
siguientes
asos:
(a)
(b)
(
)
(d)
Listas
Listas
Listas
Listas
simples no
ir
ulares.
simples
ir
ulares.
dobles no
ir
ulares.
dobles
ir
ulares.
70. Considere el problema de invertir los elementos de una lista en una sola pasada,
sin usar una estru
tura de dato adi
ional y sin
opiar los
ontenidos de los nodos.
Es
riba algoritmos para:
(a)
(b)
(
)
(d)
Una lista
Una lista
Una lista
Una lista
simplemente enlazada.
simplemente enlazada
ir
ular.
doblemente enlazada. Proponga al menos dos algoritmos.
doblemente enlazada
ir
ular. Proponga al menos dos algoritmos.
178
Captulo 2. Secuencias
Una lista
Una lista
Una lista
Una lista
simplemente enlazada.
simplemente enlazada
ir
ular.
doblemente enlazada.
doblemente enlazada
ir
ular.
74. Es
riba un pro
edimiento general, basado en
i
los simples, que efe
tue una mparti
ion. Es de
ir, despues de la opera
ion, la lista es partida en m listas de tama~no
n/m.
75. Es
riba un programa que mueva el menor elemento de una lista a la primera posi
ion.
76. Es
riba un programa que mueva el mayor elemento de una lista a la ultima posi
ion.
2.9. Ejercicios
179
77. En base a los dos ejer
i
ios previos, es
riba un programa que ordene una lista doblemente enlazada.
78. Dise~ne e implante un TAD que modeli
e una pila implantada
on arreglos dinami
os.
Dis
uta todas las op
iones, ventajas y desventajas de su dise~no.
79. Dise~ne e implante un TAD que modeli
e una
ola implantada
on arreglos dinami
os.
Dis
uta todas las op
iones, ventajas y desventajas de su dise~no.
80. Considere una lista
ir
ular de forma tal que la lista pueda re
orrerse e
azmente en
ambas dire
iones.
(a) Considere una fun
ion f(p1, p2) = p3 tal que f(p1, p3) = p2, f(p2, p3) =
p1, pi, pj, pk N , pi 6= pj 6= pk.
Dado un nodo
on un solo
ampo para una dire
ion de memoria, explique un
metodo que utili
e una fun
ion
omo la anterior para re
orrer la lista en los dos
sentidos aun
uando un nodo
ontiene un solo apuntador. (++)
(b) En
uentre opera
iones que puedan usarse para implantar la fun
ion f de la
pregunta anterior. (+)
(
) En base a las respuestas anteriores, dise~ne e implante un TAD que modeli
e
una lista
ir
ular re
orrible en ambos sentidos y que use un solo apuntador por
nodo. (++)
81. Implante los metodos pushn(), popn() y empty() denidos en ArrayStack<T>,
para la
lase ListStack<T>. >Por que no han sido implantados dire
tamente dentro
de la
lase?
82. Explique
omo implantar una pila mediante dos
olas.
83. Explique
omo implantar un
ola mediante dos pilas.
84. Para
onvertir un numero en base 10 se divide su
esivamente entre dos hasta que el
o
iente devenga en
ero y en
ada division se alma
ena el resto, el
ual es un dgito
del equivalente en base binaria.
Por ejemplo, 3710 puede
al
ularse en binario
omo:
37
18
9
4
2
1
mod 2
mod 2
mod 2
mod 2
mod 2
mod 2
= 1
= 0
= 1
= 0
= 0
= 1
180
Captulo 2. Secuencias
Bibliografa
[1 Alfred V. Aho, John E. Hop
roft, and Jerey D. Ullman. The Design and Analysis
of Computer Algorithms. Addison-Wesley, Reading, MA, USA, 1974.
[2 J. W. Ba
kus and W. P. Heising. FORTRAN. IEEE Transa
tions on Ele
troni
Computers, EC-13(4):382{385, 1964.
[3 Peter Calingaert. Assemblers,
ompilers, and program translation. Computer
S
ien
e Press, 1979.
[4 T. H. Cormen, C. E. Leiserson, and R. L. Rivest. Introdu
tion to Algorithms. MIT
Press, Cambridge, MA, USA, 1989.
[5 M. T. Goodri
h and R. Tamassia. Data Stru
tures and Algorithms in Java. John
Wiley & Sons, 1998.
[6 R. L. Graham, D. E. Knuth, and O. Patashnik. Con
rete Mathemati
s: A Foundation for Computer S
ien
e. Addison-Wesley Pub., 1994.
2.9. Bibliografa
181
Captulo 3
Crtica de algoritmos
En este
aptulo estudiaremos algunas te
ni
as de analisis y veri
a
ion de
orre
titud
uyo
n es
riti
ar algoritmos. Criti
ar proviene del griego o (
riti
os), que signi
a \
risis", el
ual, a su vez, deriva del verbo griego (
rinein) y
onnota romper, separar .
Criti
ar
onsiste, pues, en \separar", en \romper" la
osa de interes a efe
tos de estudiarla.
Pero, >para que la estudiamos? Tanto desde las perspe
tivas del obrante,
omo desde las
de los bene
iarios de la obra, la
rti
a se ha
e
on pretension de mejorar el bien.
Hay dos maneras de ver una
rti
a. Una se
on
entra en lo \interno" y ata~ne al pro
eso
de he
hura de la obra. Este tipo de
rti
a es mas propio entre los obrantes (pra
ti
antes)
que entre los bene
iarios que re
iben (usan) la obra.
A la otra fa
eta de la
rti
a se le denomina \externa" y ata~ne a la apre
ia
ion so
ial
de la obra; es de
ir,
omo el entorno o so
iedad apre
ia o despre
ia el bien de la obra. La
rti
a externa es fundamental y primaria respe
to a la interna, pues esta ata~ne al destino
de la obra.
La
rti
a externa se
on
entra en el que es la obra, lo que esta representa so
ialmente
omo bien; mientras que la
rti
a interna se
on
entra en el
omo se realiza la obra.
La
ien
ia moderna suele usar el termino \analisis" para referir a un estilo te
ni
o de estudio interno en la
ual la
osa de estudio se divide en partes para estudiarlas por separado.
\Analisis" proviene del griego (analisis), el
ual de
ierta forma tambien signi
a
\separar" ( y \soltar" () y que en su primera a
ep
ion el D.R.A.E.
onnota
omo \distinguir y separar las partes de un todo". Debemos a
larar vehementemente
que
riti
ar es mu
ho mas que analizar y, para aprehender ello, quiza lo mejor sea mirar el antonimo de analisis,
ual es \sntesis", proveniente del griego (sntesis)
que, D.R.A.E. dixit, signi
a \
omposi
ion de un todo por la reunion de sus partes".
Como debemos apre
iarlo, en este
ontexto
omponer programas, o sea, sintetizarlos, tiene
mas sentido para nosotros que des
omponerlos, es de
ir, que analizarlos. Surge, enton
es,
la siguiente pregunta, que dejaremos abierta:
uando programamos, >analizamos o sintetizamos o ambos? Planteado de otro modo: >
reamos programas a partir de un todo
que luego des
omponemos o a partir de partes que despues
omponemos, ambas
osas o
ninguna?
A una
rti
a suele
ali
arsela de \
onstru
tiva" o \destru
tiva". Hoy se aboga por que
la
rti
a sea
onstru
tiva y no destru
tiva. >Por que estos adjetivos? >Que se
onstruye
o destruye? Mas en parti
ular, >por que una
rti
a podra ser destru
tiva? Es
orre
to
1
1 No signi
a
2 Seg
un [20.
dividir.
183
184
de
ir que una obra se \
onstruye", sentido por el
ual una
rti
a, sobre todo,
uando
esta revela errores,
uyas asun
iones y enmiendas mejoran la obra, puede
onsiderarse
onstru
tiva. Aunque hemos di
ho que etimologi
amente el
riti
ar
onlleva \romper",
no debemos a
eptar que una
rti
a destruya literalmente a una obra; todo lo
ontrario,
mientras mas aguda y pre
isa sea una
rti
a, mas \
onstru
tiva" es, pues esta permite
mejorar la obra. Lo anterior es
orre
to aun, in
lusive, si una
rti
a revela que no tiene
sentido
onstruir la obra pues, al n y al
abo, este es un
ono
imiento pra
ti
o. >Como
es, enton
es, que una
rti
a puede
ali
arse de destru
tiva?
O
urre que la razon de la
rti
a de esta epo
a se
entra en el individuo, o sea en la
personalidad del obrante, quien es el
riti
ado, y del
riti
ante, en lugar de
on
entrarse
sobre la obra
omo un todo. En este mar
o,
uando el obrante de esta epo
a es
u
ha una
rti
a, es muy posible que, en lugar de
onsiderarla ha
ia el sentido de la obra, que es ella
y
omo esta se realiza, se
onfunda
on quien es el obrante y
omo es el
omo autor. Por
la mismas razones, a menudo, el
riti
ante in
urre en el mismo yerro:
on
entra su
rti
a
(si a
aso ahora se le puede tildar as) en la personalidad del obrante, es de
ir, en el autor,
en detrimento del sentido de la obra y su instrumenta
ion. En el mar
o hedonista, la
rti
a
es
onstru
tiva si se logra interpretar
omo elogio, mientras que es destru
tiva
uando se
entiende
omo repro
he. El problema es que al
onfundir el que se ha
e
on el quien ha
e,
o, el
omo se ha
e
on el
omo es quien lo ha
e, se pierde el valor esen
ial de la
rti
a,
ual no es otro que mejorar, perseguir la ex
elen
ia y, en ultima
onse
uen
ia, el bien en
el
ual tras
iende la obra.
Abo
aremos el objeto de estudio de este
aptulo desde dos perspe
tivas: la e
a
ia y
la e
ien
ia.
La e
a
ia
on
ierne a que se logre el efe
to; es de
ir, a que el algoritmo al
an
e el n
de resolver el problema para el
ual fue destinado. Esto es la \
orre
titud".
La e
ien
ia ata~ne a que tan bien un algoritmo o programa realiza su solu
ion; lo que
impli
a de
ir que, para poder hablar de e
ien
ia, previamente debe haberse sido e
az.
Podemos de
ir, enton
es, que la e
ien
ia ata~ne a
omo ser mas e
az.
Si un programa no es
orre
to, enton
es posiblemente no tenga sentido hablar de
e
ien
ia. Por eso, la
orre
titud prima a la e
ien
ia, pues de nada vale la ultima si no
se al
anza el efe
to; es de
ir, si no se resuelve el problema.
Para estudiar la e
ien
ia se realizan analisis. En este sentido
aben preguntas
omo:
>
uanto dura el programa? >
uantos re
ursos
omputa
ionales
onsume? El sentido
rti
o
sobre esta
lase de preguntas determinara la fa
tibilidad de un programa y las
ir
unstan
ias en que este pueda o deba usarse.
Durante un analisis, es muy posible que se revelen otras formas o variantes para realizar el algoritmo o programa; algunas de ellas lo mejoraran, otras lo empeoraran y otras
develaran algoritmos muy distintos. En a~nadidura, el entrenamiento en el analisis de algoritmos requiere su
ompresion
abal. Conse
uentemente, analizar algoritmos es una de
las mejores maneras de ganar
ono
imiento sobre su dise~no.
Cuando se tiene
laro el problema, un analisis puede indi
iar aspe
tos de
orre
titud.
Por ejemplo extremo, una dura
ion nula, que no tiene sentido
on
reto, pudiera indi
iar,
no solo un error en el analisis, sino en el algoritmo mismo.
En lo que sigue, enton
es, abordaremos dos temas prin
ipales: el analisis de algoritmos
y su
orre
titud. Ambos ata~nen a la
rti
a interna, la
ual, no debemos olvidar, no tiene
sentido sin una perspe
tiva de
rti
a externa.
3.1. An
alisis de algoritmos
3.1
185
An
alisis de algoritmos
Intuitivamente, hay dos metri
as fundamentales y elementales, las
uales, \en teora\,
permiten
uanti
ar la
alidad de un algoritmo:
1. La dura
ion de la eje
u
ion,
omunmente llamada \tiempo de eje
u
ion", la
ual
onsiste en medir el tiempo desde que se ini
ia el programa hasta que se
ulmina.
En este sentido, mientras un programa menos dure, mejor este es.
2. La
antidad de
onsumo de otros re
ursos ; en parti
ular, la memoria. Medimos los
bytes de memoria y de otros re
ursos que o
upa un programa durante su eje
u
ion.
Entre dos programas a
omparar, el que
onsuma menos re
ursos es el mejor.
3
Tanto la dura
ion,
omo el
onsumo de re
ursos, dependen de la
ongura
ion de la entrada. Uno de los fa
tores mas importantes de esta
ongura
ion, pero no el uni
o, es el
tama~no de la entrada, pues, mientras mayor es esta, posiblemente mayor es la
antidad de
datos a pro
esar y mayores dura
iones y
antidades de re
ursos hay que gastar. En este
orden de ideas, en lo que sigue, nos referiremos a T (n)
omo una fun
ion que des
ribe el
omportamiento de un algoritmo, segun su dura
ion o su
onsumo de re
ursos, al tama~no
de la entrada n.
El tama~no n es una parte de la
ongura
ion, la
ual, en s,
onsiste en el orden
o permuta
ion en que le apare
e la entrada al programa. Puesto que
ono
er todas las
permuta
iones posibles de una entrada es muy laborioso (y subjetivo), en el analisis de
un algoritmo se suelen emplear algunos estadsti
os, de los
uales los prin
ipales son el
promedio y el maximo.
La observa
iones anteriores suponen que el tama~no de la entrada puede expresarse en
fun
ion de una sola variable, lo
ual no siempre es el
aso. En algunas
ir
unstan
ias,
el tama~no, u otra
uanti
a
ion a
er
a de la entrada, se denen mediante una fun
ion
multivariable. Estos
asos son, por supuesto, mas
omplejos que el univariable.
Dados dos algoritmos A1 y A2, que resuelven el mismo problema, >
ual de los dos es
mejor? Objetiva e idoneamente hablando, podemos
al
ular a priori, o sea, sin
odi
ar y
sin eje
utar los algoritmos, las fun
iones de dura
ion Td1 (n) y Td2 (n) y las fun
iones de
onsumo de espa
io Te1 (n) y Te2 (n). Si tuviesemos estas fun
iones, enton
es tendramos
riterios bastante objetivos y pre
isos para de
idir. Por ejemplo, si Td exhibiese las formas maximas siguientes:
3 otros
186
Td1 (n)
Td2 (n)
n1
n2
enton
es sabramos que, para entradas mayores a n2 Td2 (n) es mejor que Td1 (n), pues esta
ultima es la que tiene mayor dura
ion.
El analisis anterior solo es posible si se pueden
ono
er a priori las formas de Td1 (n)
y Td2 (n). Lamentablemente, asumir tal posibilidad no es para nada realista, pues, para el
aso general, es extremadamente dif
il
al
ular a priori Td(n). Para aprehender el porque
de esta di
ultad es ne
esario
omprender que
al
ular Td(n) requiere
ontar la
antidad
de \instru
iones" que eje
utara la se
uen
ia nita denominada algoritmo.
Afortunadamente, no es
onveniente
ono
er
on pre
ision a Td(n), pues, a la postre, la
dura
ion real dependera de fa
tores subjetivos que modi
aran la forma exa
ta de Td(n).
En efe
to, Td(n) tendra formas diferentes segun las
ir
unstan
ias de eje
u
ion del algoritmo. Entre tales
ir
unstan
ias
abe desta
ar las siguientes
ara
tersti
as:
Caractersticas materiales del computador: Existen mu
has arquite
turas di-
ferentes de
omputadores. Algunas arquite
turas se prestan mejor para una
lase
de problemas que otras. Posiblemente, un algoritmo tendra dura
iones distintas en
arquite
turas distintas.
Aun entre arquite
turas homogeneas, existen diferen
ias dadas por la velo
idad del
reloj,
antidad de memoria, sistema operativo, numero de pro
esos que a
tualmente
albergue el
omputador, entre otros fa
tores que in
iden en la dura
ion de un programa.
lenguaje de programa
ion. Lenguajes diferentes que
odiquen el mismo algoritmo
produ
iran programas
on dura
iones diferentes.
En a~nadidura, los programadores tambien dieren en sus estilos de
odi
a
ion. Un
algoritmo implantado por dos programadores en un mismo lenguaje y eje
utado en
un solo
omputador probablemente exhibira diferen
ias en el tiempo de eje
u
ion.
Caractersticas del c
odigo generado por el compilador: Los
ompiladores
dieren en la tradu
ion del
odigo fuente al objeto. Es bastante probable que dos
ompiladores diferentes generen tradu
iones diferentes de un mismo programa y,
por tanto, di
hos programas exhibiran tiempos de eje
u
ion diferentes.
3.1. An
alisis de algoritmos
187
Estos fa
tores, entre otros mas, heterogeneos entre si, ha
en mas arduo, y, ademas,
impra
ti
o, no solo
ono
er la forma exa
ta de T (n), sino expresar el tiempo de eje
u
ion
de un programa en alguna unidad espe
a y pre
isa. Debemos, pues, indagar un
riterio
de analisis que eluda los fa
tores men
ionados.
3.1.1
187a
Como ya lo se~nalamos, analizar un algoritmo
onsiste en
ontar la
antidad de instru
iones o pasos de eje
u
ion que este eje
uta. >Que es un paso de eje
u
ion? En prin
ipio,
podemos de
ir que es la mnima instru
ion que se puede eje
utar. Esta no
ion estable
e
un grano mnimo que nos permite
uanti
ar la dura
ion de un algoritmo sin
onsiderar
el tiempo absoluto de eje
u
ion. >Que se
onsidera
omo una mnima instru
ion? Las
onsidera
iones anteriores re
ejan lo dif
il que es sentar este umbral. Por tal razon,
onsideraremos un paso de eje
u
ion
omo
ualquier se
uen
ia de eje
u
ion
uya dura
ion sea
onstante. Transamos por lo
onstante sin saber
ual es la dura
ion real.
Propi
iemos el entendimiento mediante algunos ejemplos. La siguiente instru
ion:
hEjemplo 1 paso de eje
u
i
on 187ai
(188)
x = y;
187b
se
onsidera un paso de eje
u
ion. Cada vez que el
ujo pase por esta instru
ion, su
eje
u
ion tendra una dura
ion maxima \
onstantemente a
otada". En el mismo sentido
hEjemplo 2 paso de eje
u
i
on 187bi
(188)
x = y + z;
187
uya dura
ion es mayor que hEjemplo 1 paso de eje
u
ion 187ai, tambien esta
onstantemente a
otada; por tanto, hEjemplo 2 paso de eje
u
ion 187bi tambien se
onsidera un
paso de eje
u
ion.
Una se
uen
ia de instru
iones tal
omo la siguiente:
hEjemplo 3 paso de eje
u
i
on 187
i
(188)
x = h;
if (x <
{
x =
a =
}
else
{
x =
a =
}
187d
y)
y + z;
b*c;
p + q + r;
b + c;
puede
onsiderarse mas
ostosa que hEjemplo 2 paso de eje
u
ion 187bi, sin embargo,
independientemente del valor del predi
ado y de lo larga que pueda ser la eje
u
ion de
ualquiera de los bloques, hEjemplo 3 paso de eje
u
ion 187
i es un paso de eje
u
ion,
pues este tiene una dura
ion que puede a
otarse por una
onstante.
El bloque repetitivo
hEjemplo 4 paso de eje
u
i
on 187di
(188)
for (i = 0; i < 100000; i++)
a[i] = b[i] + c[i];
188
188
tambien tiene una dura
ion
onstante y se
onsidera,
onse
uentemente, un paso de eje
u
ion.
Finalmente, la eje
u
ion se
uen
ial de todos los bloques anteriores
hUltimo ejemplo paso de eje
u
i
on 188i
hEjemplo 1 paso de eje
u
i
on 187ai
on 187bi
hEjemplo 2 paso de eje
u
i
on 187
i
hEjemplo 3 paso de eje
u
i
hEjemplo 4 paso de eje
u
i
on 187di
tambien es un paso de eje
u
ion, pues su dura
ion, a pesar de que
omprenda a la de todos
los bloques anteriores, tambien es
onstante.
A partir de este momento, una unidad de ejecucion se define como una secuencia
de instrucciones cuya duraci
on es constante. Esto
onlleva errores, pero re
ordemos
que el tiempo de eje
u
ion vara segun
ir
unstan
ias ajenas al algoritmo. Cualquiera
sea el tama~no y disposi
ion de la entrada, un
ujo de eje
u
ion siempre tendra dura
ion
onstante
ada vez que pase por alguna instru
ion se
uen
ial. En el mismo espritu de
razonamiento,
ualquier se
uen
ia de instru
iones
on dura
ion
onstante tambien se
onsidera una unidad de eje
u
ion. Consideremos algunos ejemplos.
Es importante desta
ar el sa
ri
io que impone nuestro
on
epto de unidad de eje
u
ion al
onsiderar
omo
onstante, lo que de he
ho es, en la mayora de los
asos, variable. El
on
epto equipara, por ejemplo, una se
uen
ia de diez instru
iones
on una de un
millon, pues en ambos
asos se
ono
en las
onstantes diez y un millon, respe
tivamente.
Sin embargo, esta
laro que muy probablemente la eje
u
ion de la primera se
uen
ia dure
105 ve
es menos que la segunda. Bajo la
ons
ien
ia del sa
ri
io anterior, la ventaja del
on
epto es que el
ara
ter
onstante no se pierde
on lo variable de la entrada. En palabras mas simples, se
uen
ias de eje
u
ion
uyas longitudes sean distintas pero
onstantes,
siempre tendran dura
iones
onstantes que no son afe
tadas por el tama~no de la entrada.
Esto ultimo es lo apre
iable del
on
epto.
3.1.2
3.1. An
alisis de algoritmos
189a
189
etodos
hM
hM
etodos
3.1.3
de ordenamiento 189bi
de busqueda 205i
Reali
emos nuestro primer analisis a traves de la no
ion de paso de eje
u
ion. Para ello
onsideremos el metodo de ordenamiento de sele
ion, el
ual puede plasmarse pi
tori
amente
omo sigue:
swap[a[i], a[j])
Desorden
Orden absoluto
189b
En esta
lase de diagrama, el re
tangulo prin
ipal representa el arreglo. Los sub-re
tangulos
internos representan pedazos del arreglo que
on
iernen a su estado de ordenamiento. Las
e
has, junto
on algun nombre de variable, indi
a un
ontador o iterador junto
on su
sentido de itera
ion.
Nuestra implanta
ion del ordenamiento por sele
ion se estru
tura del siguiente modo:
uya implanta
ion es:
hM
etodos de ordenamiento 189bi
(189a) 190b
template <typename T, class Compare>
void selection_sort(T a[], const size_t & n)
{
int i,j, min;
for (i = 0; i < n - 1; ++i)
{
hSea min el ndi
e menor entre i +
if (Compare () (a[min], a[i]))
Aleph::swap(a[min], a[i]);
}
1 y n - 1 190ai
}
template <typename T>
void selection_sort(T a[], const size_t & n)
{
selection_sort <T, Aleph::less<T> > (a, n);
}
Denes:
selection sort, used in
hunks 191{93.
190
190a
El mayor
oste del if in
luido en el bloque hSea min el ndi
e menor entre i + 1 y
n - 1 190ai es que el predi
ado sea
ierto. Si este fuese el
aso, la dura
ion del if aun
permane
e
onstantemente a
otada. Con
luimos, pues, que el bloque dentro del for toma
una unidad de eje
u
ion. Por lo tanto, el bloque hSea min el ndi
e menor entre i + 1
y n - 1 190ai eje
uta n i 1 pasos de eje
u
ion, el
ual puede aproximarse,
on un ligero
error, a n i. De este modo, el metodo toma:
T (n) =
n2
X
i=0
190b
ni=
n1
X
i=1
n1
X
i = (n 1)n
i=1
n(n 1)
n(n 1)
=
2
2
(3.1)
Esto nos revela que, independientemente de que tengamos un
omputador muy rapido
o muy lento, y demas idiosin
rasias presentadas en 3.1.1, el tiempo de eje
u
ion
re
e
uadrati
amente segun el numero de elementos que estemos ordenando. >Puede en
ontrarse algun metodo, objetivo, que
ara
teri
e el tiempo de eje
u
ion, sin
onsiderar los
aspe
tos relativos? La respuesta es armativa y la abordaremos en la siguiente sub-se
ion.
Exa
tamente el mismo algoritmo puede instrumentarse para ordenar listas doblemente
enlazadas
uyo nodo
abe
era sea de tipo Dlink. Pero para ello, es
onveniente instrumentar una rutina que busque el menor elemento de una lista enlazada:
hM
etodos de ordenamiento 189bi+
(189a) 189b 191
template <class Compare>
Dlink * search_min(Dlink & list)
{
typename Dlink::Iterator it(list);
Dlink * min = it.get_current();
for (it.next(); it.has_current(); it.next())
{
Dlink * curr = it.get_current();
if (Compare () (curr, min))
min = curr;
}
return min;
}
Denes:
search min, used in
hunks 191 and 195a.
Uses Dlink 90, get current 103, and has current 103.
3.1. An
alisis de algoritmos
191
*, Dlink *) y que se en
arga de
omparar dos nodos apuntados por variables Dlink*.
Podramos implantar el ordenamiento de listas a partir del tipo Dnode<T>, desde
ual
ya
ono
eramos un tipo de base. Pero esta de
ision ex
luira el ordenamiento de listas
basadas en otros usos de Dlink; por ejemplo, supongamos la siguiente estru
tura de nodo:
struct Nodo
{
Dlink enlace_lista_1;
Dlink enlace_lista_2;
Dlink enlace_lista_3;
D1 d1;
D2 d2;
D3 d3;
};
Esta
lase de nodo no podra ordenarse por una rutina generi
a de ordenamiento basada
en un Dnode<T>, pues el tipo ejemplo Nodo no es un Dnode<T>. En a~nadidura, Node
tiene tres enla
es que se en
adenan a listas diferentes. >
omo ordenamos generi
amente
segun
ualquiera de las tres listas? La respuesta
onsiste en delegar la
ompara
ion a bool
Compare::operator () (Dlink *, Dlink *). Por ejemplo, si deseamos ordenar la lista 1
segun el atributo d1, podemos ha
erlo
omo sigue:
struct Comparar_D1
{
bool operator () (Dlink * l1, Dlink* l2)
{
Nodo * n1 = Dlink::dlink_to_Node(l1);
Nodo * n2 = Dlink::dlink_to_Node(l2);
return n1->d1 < n2->d1;
}
}
191
Analogamente, podemos realizar
ompara
iones para los tipos D2 y D3 y as ordenar por
los enla
es enlace lista 2 y enlace lista 3.
Con lo anterior en mente, el ordenamiento por sele
ion se expli
a por s mismo:
hM
etodos de ordenamiento 189bi+
(189a) 190b 192a
template <class Compare>
void selection_sort(Dlink & list)
{
Dlink aux;
192
aux.append(min); // ins
ertelo ordenado en aux;
}
list.swap(&aux);
}
Uses Dlink 90, search min 190b, and selection sort 189b.
Esta version expresa mejor el prin
ipio del metodo: bus
ar el menor elemento e insertarlo
en una lista. La lista resultante estara ordenada. El metodo es el mismo que apli
amos
uando ordenamos barajas.
El usar:
bool Compare::operator () (Dlink *, Dlink *)
omo
riterio general de
ompara
ion pretende servir para todas las
ir
unstan
ias de
ordenamiento de listas enlazadas. Sin embargo, en la mayora de las
ir
unstan
ias, se
tratara de ordenar una lista de Dnode<T>, en la
ual s se
ono
e el tipo. Para endulzar
el asunto, dise~naremos la version generi
a:
bool Compare::operator () (Dnode<T> *, Dnode<T> *)
192a
192b
De este modo, podemos exportar una espe
ializa
ion de selection sort() que
onsidera
un
riterio de
ompara
ion de los
ontenidos de los nodos:
hM
etodos de ordenamiento 189bi+
(189a) 192a 193a
template <typename T, class Compare>
void selection_sort(Dnode<T> & list)
{
selection_sort < Compare_Dnode<T, Compare> > (list);
}
Uses Compare Dnode 192a, Dnode 106a, and selection sort 189b.
3.1. An
alisis de algoritmos
193a
193
Tambien podemos exportar una version por omision
uya
ompara
ion se fundamenta
en el operador <:
hM
etodos de ordenamiento 189bi+
(189a) 192b 193b
template <typename T>
void selection_sort(Dnode<T> & list)
{
selection_sort < T, Aleph::less<T> > (list);
}
Uses Dnode 106a and selection sort 189b.
Utilizaremos este enfoque en el resto de los metodos de ordenamiento sobre listas que
trataremos en este
aptulo.
3.1.4
193b
B
usqueda secuencial
Antes de pasar a estudiar un
orpus objetivo para analizar algoritmos, presentemos uno de
los aspe
tos que a menudo
ompli
an el analisis o sele
ion de un algoritmos: el azar. Para
ello, nos valdremos de uno los algoritmos mas simples, populares y utiles: la busqueda
se
uen
ial. Comen
emos por un arreglo, la
ual puede plasmarse, sin ne
esidad de expli
a
ion previa, de la siguiente manera:
hM
etodos de ordenamiento 189bi+
(189a) 193a 194a
template <typename T, class Equal> inline
int sequential_search(T a[], const T & x, const int & l, const int & r)
{
for (int i = l; i <= r; i++)
if (Equal () (a[i], x))
return i;
return -1;
}
Denes:
>Cuantos pasos de eje
u
ion
onsume este algoritmo? La sutileza de su analisis es que
la dura
ion de eje
u
ion depende de dos fa
tores: (1) la permuta
ion del arreglo a y (2)
del valor de busqueda x.
Veamoslo desde la perspe
tiva del valor de x. Si x no esta
ontenido en el arreglo,
enton
es, independientemente de la permuta
ion, el for se eje
utara
ompletamente, razon
por la
ual el algoritmo
onsume r l = n pasos de eje
u
ion. De lo
ontrario, enton
es,
la dura
ion dependera de la permuta
ion; espe
amente, de la posi
ion en donde se
en
uentre x dentro del arreglo. Si se en
uentra en la primera posi
ion, enton
es la dura
ion
es un paso de eje
u
ion. Si se se en
uentra en la ultima enton
es son n 1. >Cual es
oger?
No lo sabemos, pero s podemos tener una expe
tativa de lo que va a su
eder y esta la
expresa el
on
epto de esperanza estadsti
a.
194
194a
Si la permuta
ion es aleatoria, enton
es la esperanza sera por el
entro. La dura
ion
esperada sera l+r
2 .
Culminemos esta se
ion
on las versiones generi
as de la busqueda sobre listas:
hM
etodos de ordenamiento 189bi+
(189a) 193b 194b
template <typename T, class Equal> inline
Dnode<T> * sequential_search(Dnode<T> & list, const T & x)
{
for (typename Dnode<T>::Iterator it(list); it.has_current(); it.next())
{
Dnode<T> * curr = it.get_current();
if (Equal () (curr->get_data(), x))
return curr;
}
return NULL;
}
Uses Dnode 106a, get current 103, has current 103, and sequential search 193b.
194b
Es fa
il adaptar estas busquedas al TAD Dlist<T>. Tambien pueden usarse para el
tipo DynDlist<T>, pero habra que intervenir internamente. Por ello, vale la pena ofre
er
una interfaz de busqueda sobre DynDlist<T>:
hM
etodos de ordenamiento 189bi+
(189a) 194a 194
template <typename T, class Equal> inline
T * sequential_search(DynDlist<T> & list, const T & x)
{
Dnode<T> * ret =
sequential_search<T, Equal > (static_cast<Dnode<T>&>(list), x);
3.1.5
194
B
usqueda de extremos
Un
aso espe
ial de la busqueda es la determina
ion de un valor extremo de una se
uen
ia;
es de
ir, el valor mnimo o maximo. A traves de la
lase de
ompara
ion generi
a, esto
puede resolverse, para arreglos, del siguiente modo:
hM
etodos de ordenamiento 189bi+
(189a) 194b 195a
template <typename T, class Compare> inline
int search_extreme(T a[], const int & l, const int & r)
{
T extreme_index = l;
return extreme_index;
}
3.1. An
alisis de algoritmos
195
Denes:
bus
ar el mnimo o el maximo. Para
lari
ar el asunto, usaremos dos fun
iones:
hM
etodos de ordenamiento 189bi+
(189a) 194
195b
template <typename T> inline
int search_min(T a[], const int & l, const int & r)
{
return search_extreme<T, Aleph::less<T> > (a, l, r);
}
195b
= it.get_current();
196
3.1.6
Notaci
on O
La no
ion de paso de eje
u
ion nos permitio en
ontrar una aproxima
ion de Td(n) para el
ordenamiento por sele
ion. A
ulla, en la oquedad de las inmensidades de Td(n), podemos
en
ontrar un patron de
onstan
ia a
er
a de lo que a
a, en la eje
u
ion
on
reta de un
algoritmo, se mide
omo variable. En el horizonte de aquella lejana, tiene sentido la
siguiente deni
ion:
Definici
on 3.1 Nota
ion O: Sean dos fun
iones T (n) y F(n). Enton
es, se di
e que T (n)
es O(f(n)), denotado
omo T (n) = O(f(n)), si y solo si c, n1 | T (n) cf(n) , n n1.
1 4(1 c)
.
2(1 c)
i2 = O(n4)
(3.2)
i2 = O(n3)
(3.3)
i2 = O(n2)
(3.4)
(3.5)
La arma
ion T (n) = O(f(n)) solo tiene una dire
ion respe
to a la igualdad. No es
correcto decir O(f(n)) = T (n), pues no re
eja la desigualdad de la deni
ion.
Lo idoneo
uando se arme que T (n) = O(f(n)) es que f(n) a
ote asintoti
amente a
la fun
ion T (n). A nivel asintoti
o, la mejor aproxima
ion es que la asntota de f(n) sea
paralela a la de T (n). Hay
asos en que se puede demostrar que f(n) no es asintoti
amente
paralela a T (n) y esto se expresa mediante la siguiente deni
ion:
3.1. An
alisis de algoritmos
197
Definici
on 3.2 Nota
ion o: Sean dos fun
iones T (n) y F(n). Enton
es, se di
e que T (n)
es o(f(n)), denotado
omo T (n) = o(f(n)), si y solo si c, n1 | T (n) < cf(n) , n n1.
Para indi
ar que la aproxima
ion asintoti
a O(f(n)) es paralela a T (n) enun
iamos la
siguiente deni
ion:
Definici
on 3.5 Nota
ion : Sean dos fun
iones T (n) y F(n). Enton
es, se di
e que T (n)
es (f(n)), denotado
omo T (n) = (f(n)), si y solo si T (n) = O(f(n)) y T (n) = (f(n)).
Podemos de
ir, tambien, T (n) = (f(n)) T (n) = O(f(n)) y f(n) = O(T (n)).
La siguiente gura pi
toriza T (n) y algunas fun
iones que la a
otan asintoti
amente,
por abajo y por arriba:
198
cT (n)
g(n)
f(n)
T (n)
n1
n2
Asntotas
Identiquemos, en primer lugar, la fun
ion T (n), la
ual des
ribe hipoteti
amente el tiempo
de eje
u
ion de un algoritmo. En la gura, la fun
ion g(n) esta por en
ima de T (n) para
todo n n1; es de
ir, T (n) = o(g(n)). La deni
ion de o 3.2 nos permite multipli
ar T (n)
por alguna
onstante c de modo que T (n) sea mayor que g(n). Pero esto es irrelevante en
la oquedad de lo innito, pues la pendiente de g(n) (veanse sus puntos de
orte) es mayor
que la de T (n). Conse
uentemente, no importa
uanta sea la enormidad de la
onstante,
en alguna lejana, g(n) siempre sobrepasara a T (n).
El mismo estilo de razonamiento puede usarse para
onstatar T (n) = (f(n)). En el
horizonte de los grandes numeros, T (n) sobrepasara a f(n).
Ahora estamos preparados para
omprender el sentido del
on
epto de , el
ual puede
apre
iarse en la siguiente gura:
c 1 f(n)
T (n)
c 2 f(n)
Asntotas
n1
3.1. An
alisis de algoritmos
199
Algebra
de O
O(O(f(n))) = O(f(n))
logk n = O(n)
(3.6)
(3.7)
(3.8)
(3.9)
(3.10)
(3.11)
(3.12)
0 =
f(n)
= =
lim
n g(n)
c =
3.1.6.2
f(n) = o(g(n))
g(n) = o(f(n))
(3.13)
f(n) = (g(n))
An
alisis de algoritmos mediante O
Como ya harto lo hemos re
al
ado, en programa
ion al n de
uentas todo es se
uen
ia.
Analizar un algoritmo requiere, enton
es, o \desenrollar" la maxima se
uen
ia de eje
u
ion
posible, o \desenrollar" la se
uen
ia de eje
u
ion probable.
Para entender el sentido de \desenrollar",
onsideremos el siguiente bu
le:
for (int i = 0; i < 3; ++i)
printf("Prueba %d\n", i);
\Desenrollar" este bu
le equivale a es
ribir la eje
u
ion
omo una se
uen
ia:
int i = 0;
if (i < 3)
printf("Prueba %d\n", i);
++i;
if (i < 3)
printf("Prueba %d\n", i);
200
++i;
if (i < 3)
printf("Prueba %d\n", i);
++i;
if (i < 3) ;
Notemos que solo en el ultimo if el predi
ado i < 3 es falso, por lo que la impresion
siempre se eje
uta y, en este
aso parti
ular, el if
ompleto se
onsidera un paso de
eje
u
ion . Puesto que fue posible \desenrollar" el bu
le en 11 instru
iones de dura
ion
onstante, el bu
le en s es de dura
ion
onstante. Por lo tanto, el bu
le
onforma un paso
de eje
u
ion y su tiempo de dura
ion es O(1).
En el dominio O, el arte de analizar un algoritmo
onsiste en identi
ar se
uen
ias
de eje
u
ion que
omporten fun
iones de dura
ion diferentes y apli
ar reglas de
onteo;
sobre todo, las de la suma y del produ
to.
En el sentido de lo re
ien expli
ado, son apli
ables las siguientes reglas para los
lasi
os
bloques de programa
ion pro
edural:
4
En este
aso, el tiempo de eje
u
ion estara dado por O(f1(n))+max(O(f2(n)), O(f3(n))).
Aqu estamos
onsiderando lo peor; es de
ir, la eje
u
ion del bloque mas
ostoso.
Eventualmente, segun la distribu
ion de
ertitud del predi
ado del if, puede
onsiderarse la esperanza.
3. Instrucciones de repeticion: La eje
u
ion de una repeti
ion
on la siguiente estru
tura:
for ( O(f1(n)) )
O(f2(n))
se rige por la regla del produ
to. El tiempo de eje
u
ion esta, pues, determinado
por O(f1(n)) ve
es la eje
u
ion del bloque interno de
oste O(f2(n)); es de
ir,
O(f1(n)) O(f2(n)) = O(f1(n) f2(n)).
Para analizar una repeti
ion anidada deben estudiarse sus dependen
ias
on los
i
los externos. Existen diversas formas, las
uales no ne
esariamente
orresponden
on la estru
tura anterior. En lneas generales, el \tru
o" para analizar una repeti
ion
onsiste en averiguar la
antidad de ve
es que esta se efe
tua.
4 Esa
3.1. An
alisis de algoritmos
201
Por lo general, el analisis de una repeti
ion se realiza desde el bu
le mas interno
hasta el mas externo.
1e+08
1e+06
10000
100
1
1
10
100
O(n)
O(n2)
O(n3)
O( n)
O(1)
5 De
202
3.1.7
202a
Desorden
Orden relativo i
A diferen
ia del metodo anterior, la interfaz de insertion sort() toma los ndi
es
izquierdo y dere
ho entre los
uales se desea ordenar el arreglo. Esta interfaz sera util para
ombinar este metodo
on otros mas sosti
ados. En lo que sigue, asumiremos l = 0 y r
= n - 1
202b
(202a)
3.1. An
alisis de algoritmos
203
n1
X
i=1
203
i
n(n 1)
=
;
2
4
que es equivalente a la mitad de tiempo del metodo de sele
ion. Si bien las
urvas de
ambos metodos son
uadrati
as, la del de inser
ion es inferior a la del de sele
ion.
Por su simpli
idad de implanta
ion, su bajo
oste
onstante y su buen desempe~no para
permuta
iones
uasi-ordenadas (O(n)), el metodo de inser
ion es una ex
elente alternativa
para ordenar se
uen
ias peque~nas.
La version para listas es mas sen
illa, pues no se requieren manejar ndi
es; tan solo
re
orrer la lista e ir insertando ordenadamente en otra lista
ada nodo observado. Esta
a
tividad justi
a la presen
ia de la primitiva siguiente:
hM
etodos de ordenamiento 189bi+
(189a) 202a 204a
template <class Compare>
6 Note
que tiene sentido
omparar a[i+1 .. r]
on a[l .. i - 1] en
ada uno de los metodos. En
efe
to, por
ada permuta
ion a[i+1 .. r], existe otra
omplementaria a[l .. i - 1] que tiene la misma
probabilidad.
204
denido:
hM
etodos de ordenamiento 189bi+
}
Uses Dlink 90, insert sorted 203, insertion sort 202a, and remove next 97b.
204b
insert sorted() eje
uta una sola itera
ion de se
uen
ias
onstantes. La dura
ion de
la itera
ion dependera de la permuta
ion de la se
uen
ia list y del valor alma
enado
en el nodo p. Esta es la misma situa
ion que
on la version para arreglos. El tiempo
de insert sorted() es, enton
es, O(n) para el peor
aso y para el esperado. La inser
ion
es llamada desde otro bloque iterativo
uyo tiempo es,
on
ertitud, O(n), pues hay que
re
orrer toda la lista. As pues,
omo es de esperarse, el metodo es O(n2).
Nos resta exportar espe
ializa
iones por omision para el tipo Dnode<T>:
hM
etodos de ordenamiento 189bi+
(189a) 204a 209
template <typename T, class Compare>
void insertion_sort(Dnode<T> & list)
{
insertion_sort < Compare_Dnode<T, Compare> > (list);
}
Uses Compare Dnode 192a, Dnode 106a, and insertion sort 202a.
3.1. An
alisis de algoritmos
3.1.8
205
205
B
usqueda binaria
El analisis del metodo de inser
ion o de sele
ion a traves de opera
iones O es simple y
dire
to. En mu
has o
asiones, sobre todo en algoritmos re
ursivos, se debe plantear una
e
ua
ion de re
urren
ia. Ilustremos esta idea mediante la version re
ursiva de la busqueda
binaria sobre un arreglo ordenado:
hM
etodos de busqueda 205i
(189a)
template <typename T, class Compare>
int binary_search_rec(T a[], const T & x, const int & l, const int & r)
{
const int m = (l + r) / 2;
if (l > r)
return m;
if (Compare() (x, a[m]))
return binary_search_rec<T, Compare>(a, l, m - 1);
else if (Compare() (a[m], x))
return binary_search_rec<T, Compare>(a, m + 1, r);
return m; // encontrado
}
Notemos que existe un ligero error en la re
urren
ia: la llamada re
ursiva o
urre sobre T ((n 1)/2) y no sobre T (n/2)
omo se expresa en (3.14). Este error, que fa
ilita
manipular T (n), es despre
iable, pues un paso de eje
u
ion de menos no alterara el
omportamiento asintoti
o de T (n).
Efe
tuemos la transforma
ion n = 2k = k = lg(n); esto asume que n es una
poten
ia exa
ta de 2, lo
ual si mu
has ve
es no sera el
aso, s es valido en el dominio de
la nota
ion O. La re
urren
ia (3.14) se expresa, enton
es:
T (2k) =
O(1)
T (2k1)
si k = 0
.
+ 1 si k > 0
(3.15)
206
(3.16)
la
ual
laramente es O(lg(n)); una e
ien
ia espe
ta
ular. La busqueda binaria es, en el
peor
aso, O(lg(n)). Se requiere dupli
ar la entrada para que el algoritmo demore una
unidad de tiempo de mas.
3.1.9
Errores de la notaci
on O
Hay dos bondades bastante notables de la nota
ion O. En primer lugar, ella objetiza
la impre
ision. Podemos interpretar que las
onstantes de la deni
ion c, n1 absorben
las idiosin
rasias subjetivas que ya hemos se~nalado (tipo
omputador, ...). En segunda
instan
ia, la nota
ion permite estudiar fun
iones T (n) del tiempo de un algoritmo bajo
un metodo
onsiderablemente mas sen
illo que el
onteo y algebra tradi
ional.
Pero toda objetiza
ion a
arrea un pre
io, por
ierto bastante objetivo y general a
toda objetiza
ion,
ual es el o
ultamiento de aquello subjetivo que, no solo en mu
hos
asos puede ser importantsimo, sino serle esen
ialsimo, pues se arriesga ese bien supremo
llamado verdad.
Despues de analizar el metodo de inser
ion y
ompararlo
on el de sele
ion, podemos
resaltar que la nota
ion O solo arroja la forma de T (n) para entradas muy grandes. De
he
ho, la impre
ision de la nota
ion O dese
ha, entre otros aspe
tos, los que se resumen
en las siguientes observa
iones:
1. No se
onsideran los
ostes
onstantes del algoritmo: La propia deni
ion de O
des
arta las opera
iones que toman tiempo
onstante en O(1).
2. Los
ostes variables, pero inferiores a la
urva dominante, tampo
o se
onsideran.
Por ejemplo, supongamos un algoritmo
uadrati
o
on una fase ini
ial de prepro
esamiento O(n), luego una fase O(n2) donde se eje
uta el algoritmo en s y, nalmente,
una fase nal de postpro
esamiento O(n). En este
aso, el tiempo de eje
u
ion estara dado por O(n) + O(n2) + O(n) = O(n2). Ahora bien, el resultado nal O(n2)
o
ulta
ompletamente dos
ostes
onsiderables aso
iados al algoritmo: el pre y el
postpro
esamiento, los
uales, aun
uando son lineales, pueden ser
ostossimos si
onllevan una
onstante muy alta.
3. El
ara
ter real de T (n) se es
onde totalmente por la a
ota
ion O(f(n)) pues, en la
mayora de las ve
es, T (n) 6= f(n): en este sentido,
abe se~nalar que O(f(n)) solo tiene
sentido para entradas mayores que la
onstante n1 planteada en la deni
ion 3.1. En
otras palabras, armar que T (n) = O(f(n)) des
ono
e el
omportamiento de T (n)
antes de n1.
La siguiente gra
a se~nala,
artesianamente hablando, el dominio donde la nota
ion O
es
on,
ertitud, verdad:
3.1. An
alisis de algoritmos
207
Realidad
n1
Tipos de an
alisis
Como ya reiterativamente lo hemos di
ho, hay algoritmos que no solo dependen del tama~no
de la entrada, sino, tambien, de la forma en que se presente la entrada. El metodo de
inser
ion ofre
e un buen ejemplo.
Por lo general,
uando se analiza un algoritmo se
onsideran dos perspe
tivas. La
primera
onsiste en analizarlo para la entrada que
ause la peor eje
u
ion; la segunda para
la entrada esperada.
Casi siempre se requiere el analisis para la peor entrada, pues este plantea una
ota de
eje
u
ion. El peor
aso nos ofre
e una espe
ie de garanta a
er
a del tiempo de eje
u
ion,
pues
ualquier otra entrada diferente a la peor sera mejor. Esta garanta es apre
iable
uando se requieren algoritmos
on requerimientos de eje
u
ion duros, que no se pueden
exibilizar y en los
uales no se puede tener el lujo de tener una mala eje
u
ion.
El analisis para la entrada promedio plantea una predi
ion, una expe
tativa a
er
a
del tiempo de eje
u
ion mas probable. Se trata de una esperanza; es de
ir, de lo que se
espera que su
eda mu
has ve
es, pero no siempre. La propor
ion de ve
es que el algoritmo
se
omportara
omo lo esperado dependera de la varianza de la entrada. Basi
amente,
este tipo de analisis se puede plantear en dos situa
iones. La primera
uando la entrada
esperada sea bastante probable; es de
ir,
uando la varianza de la entrada sea peque~na.
La segunda situa
ion
uando se plantee
omo requerimiento satisfa
er al promedio de
asos sin importar algunas malas eje
u
iones
ausadas por una mala entrada. En ambas
situa
iones es importante
ono
er la varianza.
208
3.2
Algoritmos dividir/combinar
Una te
ni
a elegante para resolver un problema
onsiste en dividirlo en dos problemas
equitativos,
uyas
omplejidades, a menudo expresadas en fun
ion de la es
ala, son menores
que las del problema ini
ial. El pro
eso de division se repite re
urrentemente
on
ada una
de las partes hasta al
anzar
omplejidades parti
ulares
uya solu
ion sea
ono
ida. A partir
de este momento, se
ombina un par de solu
iones para en
ontrar la solu
ion al problema
general primigeniamente dividido. A la
ombina
ion de dos solu
iones para obtener la
solu
ion
ompleta se le denomina \
ombina
ion" o \
onquista" .
Para apli
ar esta te
ni
a se requiere (1) saber dividir el problema y (2) saber
ombinar.
Esta es la parte subjetivsima en el dise~no de un algoritmo dividir/
ombinar. Por
ontraste,
la estru
tura y analisis de esta
lase de algoritmos tiene un esquema muy objetivo.
Dado un problema P
on entrada de tama~no n, un algoritmo dividir/
ombinar,
uya
dura
ion se des
ribe por la fun
ion T (n) y que resuelve a P se estru
tura, de manera
general y objetiva, en las partes siguientes:
7
7 En
209
O(fb(n))
T (n) = O(fd(n)) +
| {z }
divisi
on
T (n1)
| {z }
primera parti
i
on
T (n2)
| {z }
segunda parti
i
on
+ O(fc(n))
| {z }
, n nb
, n > nb
(3.17)
ombina
i
on
Muy a menudo, la estru
tura de un algoritmo dividir/
ombinar se presta fa
ilmente
a su manejo
on
urrente; es de
ir, las parti
iones pueden tratarse por hilos o pro
esos
de eje
u
ion distintos. Si estos hilos son eje
utados por pro
esadores materiales distintos, enton
es los sub-problemas pueden resolverse simultaneamente y, de este modo, mas
rapidamente que un solo pro
esador. La velo
idad, por supuesto, depende de la
antidad
de pro
esadores que se disponga, de la ndole del algoritmo a paralelizar y de las virtudes
del dise~nador o programador que reali
e la paraleliza
ion.
3.2.1
209
Ejempliquemos el dise~no y analisis de un algoritmo dividir/
ombinar mediante un algoritmo de ordenamiento denominado de \mez
la", el
ual se des
ribe a
ontinua
ion:
hM
etodos de ordenamiento 189bi+
(189a) 204b 212
hmez
la de arreglos 211i
template <typename T, class Compare>
void mergesort(T a[], const int & l, const int & r)
{
if (l >= r) return;
const int m = (l + r)/2;
mergesort<T, Compare>(a, l, m);
mergesort<T, Compare>(a, m + 1, r);
merge<T, Compare>(a, l, m, r);
}
Denes:
mergesort, used in
hunk 689d.
Uses merge 211.
Mezcla (merge)
La mez
la asume dos se
uen
ias ordenadas, en los intervalos [l..m] y [m + 1..r]. Si
tuviesemos dos arreglos ordenados, enton
es podramos mez
larlos en un ter
ero por barrido. A
ada itera
ion,
omparamos el elemento a
tual de
ada arreglo,
opiamos el menor
de los dos al arreglo resultado y avanzamos el elemento a
tual en el arreglo que
ontena
el menor elemento.
210
En nuestra situa
ion, tenemos un arreglo parti
ionado en dos partes equitativas que
ya estan ordenadas. El pro
eso de mez
la
opiara el arreglo a ha
ia uno auxiliar que
llamaremos b. No es posible evadir el uso de un arreglo adi
ional.
Con miras a simpli
ar la mez
la, realizaremos la
opia del arreglo a ha
ia el segundo
arreglo b segun el siguiente esquema:
m
l
a
primera mitad arreglo 210ai hCopia invertida segunda mitad arreglo 210bi
r
a[m + 1..r]
a[l..m]
210a
a[m + 1..r]
a[l..m]
hCopia
210b
(211)
Una vez
opiadas las parti
iones en el arreglo b, pro
edemos a \mez
larlas" en el
arreglo a
omo sigue:
m
l
a[l..m]
b
i
r
a[m + 1..r]
j
min(b[i], b[j])
a
k
210
(211)
211
211
}
Denes:
An
alisis del mergesort
O(1)
2T (n/2) + O(fc(n))
,n 1
,n > 1
(3.18)
O(1)
2T (n/2) + O(n)
,n 1
,n > 1
(3.19)
= O(k) + 1
(3.20)
(3.21)
212
para n = 2k. El
ual esta asintoti
amente a
otado por O(n lg (n)). En el innito la multipli
idad de n no afe
ta el
omportamiento asintoti
o, pues esta no afe
ta la re
urren
ia.
En otros terminos, el que n sea o no poten
ia exa
ta de dos no
ambiara el he
ho de
que T (n) sea O(n lg(n)).
El metodo por mez
la es O(n lg(n)) para todos los
asos: mejor, peor y promedio. Notemos que el algoritmo en s no distingue la forma de la entrada; siempre
uesta O(n lg(n))
independientemente de la disposi
ion de los elementos en el arreglo.
3.2.1.3
Dada una tabla de registros, es de
ir, un arreglo
on
olumnas de tipos iguales o diferentes,
una \
lave primaria" es una que no se repite en una la. Por ejemplo, el numero uni
o de
identidad na
ional no esta dise~nado para repetirse. Una
lave se
undaria es una que puede
repetirse en dos o mas las; por ejemplos, los nombres y apellidos.
Bajo la deni
ion anterior, un metodo de ordenamiento se
ali
a de \estable" si, al
ordenar por
lave se
undaria una se
uen
ia de registros previamente ordenada por
lave
primaria, enton
es las
laves se
undarias repetidas se
onservan ordenadas segun la
lave
primaria.
En el sentido de lo anterior, el metodo mergesort es estable.
3.2.1.4
Coste en espacio
El
onsumo de memoria es otro
oste que siempre debe
onsiderarse al momento de analizar
un algoritmo. En este sentido, el ordenamiento de arreglos por mez
la es un buen ejemplo,
pues la rutina merge() requiere un arreglo propor
ional a n; lo que impli
a que el metodo
en
uestion requiere espa
io propor
ional a O(n). As pues, si se desea utilizar este metodo,
enton
es debe asegurarse el disponer del espa
io adi
ional requerido para su eje
u
ion.
3.2.1.5
212
El ordenamiento por mez
la fue
on
ebido muy otrora,
uando haba muy po
a memoria
y los medios de alma
enamiento se
undario eran
intas magneti
as lentsimas. En aquellos
tiempos, ordenar requera la \mez
la\ de varias
intas.
Aunque hoy en da disponemos de muy extensas memorias y de medios de alma
enamiento masivo no se
uen
iales, el ordenamiento por mez
la no es para nada obsoleto.
Todo lo
ontrario, es un metodo
on garanta O(n lg(n)) que permite ordenar efe
tivamente listas enlazadas y que, en a~nadidura, no requiere espa
io adi
ional. Tal mez
la se
espe
i
a
omo sigue:
hM
etodos de ordenamiento 189bi+
(189a) 209 213a
template <class Compare>
void merge_lists(Dlink & l1, Dlink & l2, Dlink & result)
{
while (not l1.is_empty() and not l2.is_empty())
if (Compare () (l1.get_next(), l2.get_next()))
result.append(l1.remove_next());
else
result.append(l2.remove_next());
213
if (l1.is_empty())
result.concat_list(&l2);
else
result.concat_list(&l1);
}
Uses concat list 96, Dlink 90, and remove next 97b.
213a
En
onsonan
ia
on su
ara
ter de se
uen
ia, el ordenamiento por mez
la de una lista
enlazada tiene exa
tamente la misma estru
tura que su version para arreglos.
hM
etodos de ordenamiento 189bi+
(189a) 212 213b
template <class Compare>
void mergesort(Dlink & list)
{
if (list.is_unitarian_or_empty())
return;
Dlink l, r;
list.split_list(l, r); // dividir en dos listas
mergesort <Compare> (l); // ordenar la primera
mergesort <Compare> (r); // ordenar la segunda
merge_lists <Compare> (l, r, list); // mezclarlas
}
Denes:
La bibliote
a dene espe
ializa
iones para las otras
lases de listas mediante la utiliza
ion de la
lase Compare Dnode.
3.2.2
Ordenamiento r
apido (Quicksort)
Todo menor o igual que a[pivot] pivote Todo mayor o igual que a[pivot]
pivot
213b
La eje
u
ion pivot = partition(a, l, r) efe
tua algunas opera
iones sobre el arreglo y retorna un ndi
e pivot tal que el arreglo satisfa
e la parti
ion segun el esquema
anterior. Es de
ir, despues de invo
ar partition(), el elemento a[pivot] se en
uentra
en su posi
ion denitiva dentro de lo que sera la se
uen
ia ordenada; o sea, la entrada
a[pivot]
orresponde al pivot-esimo menor elemento del arreglo. El arreglo ha sido dividido, segun a[pivot], en dos partes desordenadas, que pueden ordenarse re
ursiva e
independientemente
omo sigue:
hM
etodos de ordenamiento 189bi+
(189a) 213a 218
on de parti
ion de arreglo 215
i
hDeni
i
214
Este pro
edimiento es el mejor metodo de ordenamiento hasta hoy
ono
ido. \Su
rendimiento es tan espe
ta
ular que su des
ubridor , C.A.R. Hoare [9, lo bautizo \qui
ksort"" [42; o sea, ordenamiento rapido, o, abreviadamente, en
astellano,
8
\el rapido". La esen
ia del metodo subya
e en la manera de efe
tuar la parti
ion; pero
antes de estudiarla, es interesante fundamentar el analisis del algoritmo.
Conviene notar que la estru
tura dividir/
ombinar del qui
ksort diere ligeramente
de su tradi
ional orden objetivo. Las llamadas re
ursivas o
urren al nal de la rutina sin
que se note expl
itamente una fase de
ombina
ion. De he
ho, es en la propia parti
ion
del arreglo
uando o
urre la
ombina
ion, pues, una vez realizada la parti
ion,
ada parte
puede ordenarse, independientemente, por separado.
El tiempo de eje
u
ion se
ara
teriza por la siguiente e
ua
ion re
urrente:
T (n) =
O(1)
,n 1
,n > 1
(3.22)
donde O(fp(n))
ara
teriza la dura
ion de partition(). pivot es el ndi
e del elemento
en torno al
ual se realiza la parti
ion. Todo lo que esta a la izquierda del ndi
e pivot es
menor que a[pivot], mientras que lo que esta a su dere
ha es mayor.
3.2.2.1
Partici
on
La parti
ion es la parte mas deli
ada y
ompli
ada del qui
ksort. Asumamos un elemento
\pivot" que representara al elemento de parti
ion y que de alguna forma, que expli
aremos
mas adelante, se
olo
a en a[r]. Ini
iemos nuestro estudio mediante observa
ion de la
siguiente gura:
swap(a[i], a[j])
l
++i
r
pivot
215a
215
Los valores ini
iales de estos ndi
es se
olo
an en una posi
ion previa a su primer a
eso;
es de
ir, i se en l - 1 y j en r.
Luego de ini
iados los ndi
es, la rutina entra en un lazo
uyo
uerpo
onsiste en bus
ar
una inversion relativa al pivote; es de
ir, un par de elementos que esten invertidos en orden
respe
to al pivote. El primero de estos elementos se bus
a por la izquierda:
havan
e i hasta primer elemento mayor que pivot 215ai
(215
)
// avance mientras a[i] < a[pivot]
while (Compare() (a[++i], pivot)) { /* nothing to repeat here */ }
215b
En la instru
ion a[++i], la
arga de a[i] se realiza despues del in
remento. Por tanto,
el primer a
eso o
urre sobre a[l]. El desplazamiento de i jamas sobrepasara a r, pues el
pivote funge de
entinela y la
ompara
ion es por desigualdad estri
ta. Por tanto, i nun
a
a
edera un ndi
e fuera del rango [l . . . r].
La busqueda del elemento de inversion del lado dere
ho es similar, pero aqu no tenemos
un
entinela; por lo que debemos veri
ar que j no sobrepase a l:
havan
e j hasta primer elemento menor que pivot 215bi
(215
)
while (Compare() (pivot, a[--j])) // avance mientras a[pivot]< a[j]
if (j == l) // se alcanz
o el borde izquierdo?
break; // s
==> hay que terminar la iteraci
on
215
Si i y j no se
ruzan (i < j), enton
es existe una inversion que se
orrige inter
ambiando a[i]
on a[j].
Si, por el
ontrario, los ndi
es i y j se
ruzan (i >= j), el pro
eso de parti
ion
ulmina y el ndi
e i
onstituye el punto de parti
ion. En este momento, se inter
ambia
on el pivote e i divide al arreglo en dos partes; la izquierda
ontiene
laves menores o
iguales al pivote, mientras que la dere
ha
ontiene
laves mayores o iguales.
Es posible que el rango [l . . . i]
ontenga
laves iguales al pivote; esto su
ede si j
ruza
a i; es de
ir, si no se en
uentra una
lave del lado dere
ho que sea menor que el pivote e i
queda posi
ionado en una
lave igual al pivote.
Con las expli
a
iones anteriores apropiadas, estamos listos para mostrar la parti
ion:
hDeni
i
on de parti
ion de arreglo 215
i
(213b)
template <typename T, class Compare>
int select_pivot(T a[], const int & l, const int & r);
if (i >= j)
break;
216
La rutina toma
omo parametros el arreglo y los lmites izquierdo y dere
ho, denominados l y r, respe
tivamente.
La primera lnea del metodo es un paso fundamental en el desempe~no del qui
ksort y
onsiste en sele
ionar un elemento que funja de parti
ionador del arreglo. Tal elemento
se denomina \pivote". Por ahora, asumamos que sele
ionamos el ultimo elemento; es
de
ir a[r].
Existen otras formas de es
oger el elemento pivote que son mas
ostosas en tiempo
onstante, pero que mejoran la esperanza de dura
ion del ordenamiento. Cualquiera sea
el metodo, nos remitiremos a
olo
ar el pivote en el extremo dere
ho. De este modo,
una modi
a
ion del enfoque de sele
ion de pivote no afe
ta el resto del algoritmo de
parti
ion.
3.2.2.2
An
alisis del quicksort
El analisis del qui
ksort requiere resolver la e
ua
ion (3.22), la
ual, a su vez, requiere del
analisis de la parti
ion.
Cualquiera sea la disposi
ion del arreglo, el while mas externo rompe
uando se
ru
en
los ndi
es i y j. Para que esto su
eda tienen que o
urrir exa
tamente r l+ 1 in
rementos
y de
rementos. Por tanto, si n = r l + 1, enton
es el tiempo de eje
u
ion es O(n).
Sustituyendo este tiempo en la e
ua
ion (3.22) puede aproximarse a:
T (n) =
O(1)
,n 1
(3.23)
O(1)
O(n) + 2T (n/2)
,n 1
,n > 1
(3.24)
O(1)
O(n) + T (n 1)
,n 1
,n > 1
(3.25)
217
la
ual es O(n2). El qui
ksort deviene, en el peor
aso, \el lento" [42. >Cuan probable es
que a
aez
a lo peor? Al igual que en la vida, muy po
o. Para intuir este he
ho, planteemos
algunos \muy malos
asos".
Un muy mal
aso esta dado, paradoji
amente,
uando el arreglo esta ordenado. En esta
situa
ion, el pivote siempre es el mayor elemento, el
ual ya se en
uentra en su posi
ion
denitiva; la parti
ion izquierda es de longitud n 1 mientras que la dere
ha es nula. La
1
probabilidad de este infortunio es n!
, pues solo existe una posibilidad entre las n! posibles.
Un
aso ligeramente peor que el anterior se da
uando el arreglo esta ordenadamente
invertido. Esta disposi
ion
ausa una parti
ion izquierda nula y una dere
ha de longi1
tud n 1. La probabilidad es identi
a al
aso anterior: n!
.
>Que tan probable es tener un mal
aso? En estadsti
a, esta respuesta la da la varianza. Si la permuta
ion2es aleatoria, enton
es la parti
ion variara segun una distribu
ion
(rl+1) 1
; por lo que el punto de parti
ion se desviara dentro del rango
uniforme; es de
ir
2
q
denido por (n)121 , lo que arroja un error esperado de 29% aproximadamente. En otros
terminos, una espe
ie de mal
aso esperado de parti
ion es que esta sea de propor
ion 1/3;
propor
ion que aun divide el arreglo de manera efe
tiva. Ademas, esta \desventura\ tendra
que o
urrir
on la mayora de las parti
iones;
uestion altamente improbable si las permuta
iones se distribuyen segun una densidad uniforme.
2
3.2.2.3
An
alisis de consumo de espacio del quicksort
Los algoritmos re
ursivos
onllevan un
oste en espa
io debido a la pila. En este sentido,
un algoritmo dividir/
ombinar
onsume espa
io de pila
uando \memoriza" una de las
divisiones a efe
tos de pro
esar re
ursivamente la otra. De este modo,
onforme aumentan
las divisiones, mayor memoria se requiere.
El qui
ksort puede ser muy
ostoso en espa
io si o
urren malas parti
iones su
esivas.
Aprehendamos esto mirando el peor de todos los malos
asos, el
ual o
urre
uando el
arreglo esta inversamente ordenado. En esta situa
ion, segun hDeni
ion de parti
ion de
arreglo 215
i, el arreglo siempre se parti
iona en:
quick sort rec<T, Compare>(a, r+1, r)
Orden invertido
lo que, segun la implanta
ion de quicksort rec(),
ausa las llamadas re
ursivas (
olo
adas
on parametros reales) a quicksort rec(a, l, r-1) y quicksort rec(a, r+1,
r), justo en ese orden respe
tivo.
Notemos que este orden de llamadas empila la parti
ion mas peque~na mientras se
efe
tua la llamada re
ursiva sobre la parti
ion mas grande
uyo tama~no es r l 1. A
su vez, puesto que el resto del arreglo esta inversamente ordenado, la siguiente invo
a
ion
a quicksort rec(a, l, r - 1)
ausa de nuevo la peor parti
ion, por lo que se vuelve a
empilar la parti
ion va
a mientras se llama re
ursivamente a la parti
ion de tama~no rl
2. Este pro
eso de malas parti
iones
ontin
ua hasta que se ordene enteramente el arreglo.
El peor
aso del qui
ksort solo empila parti
iones va
as. Para pre
isar el
oste en
espa
io de estas llamadas vanas hay que
ontar la
antidad de ve
es que se llama re
ursivamente al qui
ksort, la
ual es, exa
tamente, el numero de elementos n; o sea, O(n). Un
muy mal
aso del qui
ksort puede, fa
ilmente,
ausar un desborde de pila.
218
218
Para evitar esto, debemos asegurarnos que la primera llamada re
ursiva se efe
tue
sobre la parti
ion mas peque~na, de modo tal que se empile menos, pues, a menor tama~no
de parti
ion, o
urrira una menor
antidad de llamadas re
ursivas. De esta manera, una
muy ligera varia
ion de quicksort rec(), que minimiza el
onsumo de pila, se enun
ia
omo sigue:
hM
etodos de ordenamiento 189bi+
(189a) 213b 220a
template <typename T, class Compare>
void quicksort_rec_min(T a[], const int & l, const int & r)
{
if (r <= l)
return;
const int pivot = partition<T, Compare>(a, l, r);
if (pivot - l < r - pivot) // cual es la partici
on m
as peque~
na?
{
// partici
on izquierda m
as peque~
na
quicksort_rec_min<T, Compare>(a, l, pivot - 1);
quicksort_rec_min<T, Compare>(a, pivot + 1, r);
}
else
{
// partici
on derecha m
as peque~
na
quicksort_rec_min<T, Compare>(a, pivot + 1, r);
quicksort_rec_min<T, Compare>(a, l, pivot - 1);
}
}
Uses partition 215
.
Esta version invo
a la re
ursion desde la menor hasta la mayor parti
ion, lo
ual
garantiza un
onsumo de pila mnimo. >De
uanto se trata este
onsumo? Para responder
la
uestion, debemos primero identi
ar
ual es el maximo tama~no que puede tomar una
menor parti
ion y asumir que ello o
urre re
ursivamente en
ada llamada re
ursiva. El
maximo de que hablamos se presenta
uando la parti
ion o
urre exa
tamente por el
entro,
el
ual, no sorprendentemente, es el mejor
aso de parti
ion en velo
idad de ordenamiento
y o
urre, a lo sumo, O(lg(n)) ve
es, que es la mayor propor
ion de ve
es que podemos
dividir al arreglo. Por tanto, quicksort rec min() tiene un
onsumo de espa
io O(lg(n)),
oste perfe
tamente manejable aun para pilas peque~nas.
El analisis pre
edente tambien nos da
uenta a
er
a del numero de invo
a
iones re
ursivas que o
urriran en el mejor
aso del qui
ksort; es de
ir,
uando todas las parti
iones
o
urran por el
entro. En este
aso,
ada llamada al qui
ksort o
asiona dos llamadas re
ursivas sobre parti
iones del mismo tama~no. Por tanto, el total de llamadas re
ursivas
que tomara el mejor
aso del qui
ksort estara dado por:
lg(n)
X
i
2i 2lg(n)+1 1 ;
(3.26)
219
que o
urren para n = 15 en una estru
tura denominada "arbol", la
ual sera estudiada en
el
aptulo 4.
qs(0, 14)
qs(0, 6)
qs(0, 2)
qs(0, 0)
qs(2, 2)
qs(8, 14)
qs(4, 6)
qs(4, 4)
qs(6, 6)
qs(8, 10)
qs(8, 8)
qs(12, 14)
Figura 3.2: Estru
tura de las parti
iones y llamadas re
ursivas para el mejor
aso del
qui
ksort y 15 elementos.
Prestemos aten
ion espe
ial al diagrama 3.2 e identiquemos la primera llamada qs(0,
14)9 . Esta llamada genera las llamadas qs(0, 6) y qs(8, 14). El algoritmo sele
iona qs(0, 6) para
ontinuar la re
ursion y empila qs(7, 14). A su vez, qs(0, 6)
empila qs[4, 6] y prosigue re
ursivamente sobre q(0, 2). Cuando se llega por primera
vez al
aso base, la pila
ontiene las llamadas qs(0, 14), qs(7, 14), qs(4, 6), qs(2,
2) pendientes por realizar.
La maxima
antidad de llamadas a empilar esta dada por la maxima profundidad
del arbol desde qs(0, 14) hasta
ualquier
aso base qs(i, i). Cualquiera sea el valor y
tama~no de la entrada, si se pro
esa primero la parti
ion mas peque~na, enton
es el
onsumo
en pila no sobrepasa O(lg(n)).
3.2.2.4
Selecci
on del pivote
Si la permuta
ion del arreglo es aleatoria, enton
es, la sele
ion del pivote por la dere
ha
del arreglo, tal
omo hasta el presente la hemos tratado, tambien es aleatoria. Por tanto,
para permuta
iones aleatorias, nuestra version del qui
ksort debe
omportarse O(n lg (n)),
en el
aso esperado. En a~nadidura, la varianza (29%) indi
ia que, en
aso de mala suerte,
el qui
ksort aun es rapido.
El razonamiento anterior presume que la permuta
ion se distribuye uniformemente;
uestion plausible e idonea en el mundo objetivo de la estadsti
a, pero que puede pe
ar
de
andida en el mundo real. Todo depende de \que" es lo que se ordena y \desde" donde
proviene la se
uen
ia. En la vida o
urre que los \que" y los \desde"
asi siempre son
sesgados.
Por otra parte, el peor
aso del qui
ksort es tan severamente
alamitoso que ninguna
apli
a
ion seria puede subya
er sobre el si el desempe~no es lo
rti
o.
Lo anterior justi
a
onsumir un po
o de tiempo
onstante para forzar, en la medida
posible, a que o
urran buenas parti
iones. La mejor manera de eliminar un sesgo es aleatorizar expl
itamente. La idea es forzar a que
ualquiera sea la permuta
ion, la sele
ion del
9 El
220
pivote obedez
a a un
riterio aleatorio, el
ual, tal
omo lo arguimos, probabilsti
amente
ause buenos
asos.
En este orden de ideas, una primera estrategia
onsistira en \aleatorizar" la parti
ion;
es de
ir, en sortear aleatoriamente la sele
ion del ndi
e del pivote. El ndi
e pivote sera,
enton
es, un numero aleatorio entre l y r. De este modo, la aleatoriedad ya no depende
de modo alguno del valor de la permuta
ion.
La te
ni
a anterior es buena, pero no evita la posibilidad de que el pivote aleatorio
ause una mala parti
ion. Una te
ni
a para disminuir esta posibilidad es sortear varios
ndi
es y sele
ionar
omo pivote el valor de la mediana. Esto
onlleva la di
ultad de
que hay que veri
ar que los sorteos no se repitan, lo que es algortmi
amente engorroso
y, en dura
ion
onstante,
ostoso. Por esa razon, es preferible no veri
ar repiten
ia y
simplemente sele
ionar la mediana, as o
urran repiten
ias. Resta por de
idir
uantos
sorteos se deben realizar. Mientras mayor sea esta
antidad, mayor posibilidad se tendra
de obtener un buen pivote, pero, tambien, se tendra mayor
onsumo de tiempo
onstante
y mayor impa
to en el desempe~no.
La aleatoriza
ion es un prin
ipio algortmi
o tendiente a disminuir la \mala suerte" o a
favore
er la \buena\. El prin
ipio ahora es
ono
ido bajo el rotulo de \algoritmos aleatorizados" y
onstituye toda una rama de la algortmi
a. En este texto se estudiaran diversas
lases de aleatoriza
ion.
Un esquema que no requiere generar ndi
es aleatorios es tomar los ndi
es extremos l, r
mas el ndi
e del
entro y es
oger las mediana de las tres entradas. Si bien,
omo siempre,
la aleatoriedad depende de la permuta
ion, tendra que haber permuta
iones sesgadas por
los tres lados para que o
urra un muy mal
aso; algo po
o probable de
on
ebir pero
sus
eptible de o
urrir o de ser adredemente provo
ado . En este sentido, la siguiente
rutina sele
iona el pivote segun lo re
ien expli
ado:
hM
etodos de ordenamiento 189bi+
(189a) 218 221
10
220a
220b
10 En
seguridad de sistemas, una te
ni
a de denega
ion de servi
io, llamada \ataque por
omplejidad
algortmi
a",
onsiste en explotar malos
asos del algoritmo. En este sentido, pudiera o
urrir una situa
ion
en la
ual deliberadamente se le de a un sistema malas parti
iones a efe
tos de
ausar su
olapso.
221
3.2.2.5
221
Cono
ido el patron de demanda de espa
io del qui
ksort, resulta atra
tivo, a efe
tos de aligerar la
arga por
al
ulo
onstante, intentar obtener una version no re
ursiva del metodo.
As mismo, el patron del algoritmo iterativo ya se
ono
e desde el dise~no de la version
iterativa de la busqueda binaria (x 2.1.1.2 (pagina 32)).
El qui
ksort debe usar una pila para re
ordar una parti
ion mientras ordena la otra.
Esta es la diferen
ia
on la busqueda binaria iterativa, la
ual no requiere una pila porque
no se requiere re
ordar la parti
ion que se des
arta despues de la division.
As pues, usaremos una pila de pares que
ontengan los ndi
es izquierdo y dere
ho
de la parti
ion. De resto,
ondi
ionada al estadio de una apropiada reda
ion y de un
responsable estudio, la rutina no debe plantear problemas de
omprension:
hM
etodos de ordenamiento 189bi+
(189a) 220a 222a
template <typename T, class Compare>
void quicksort(T a[], const int & l, const int & r)
{
if (r <= l)
return;
222
3.2.2.6
222a
De todos los algoritmos de ordenamiento, quiza el mas simple de expresarse sea el qui
ksort
sobre listas enlazadas. La siguiente rutina lo indi
ia:
hM
etodos de ordenamiento 189bi+
(189a) 221 222
template <class Compare>
void quicksort(Dlink & list)
{
if (list.is_unitarian_or_empty())
return;
lista 222bi
222b
222
223
223
Mejoras al quicksort
Revisemos el pro
edimiento de parti
ion hDeni
ion de parti
ion de arreglo 215
i
y los ordenamientos de sele
ion e inser
ion presentados en x 3.1.3 (pagina 189)
y x 3.1.7 (pagina 202), respe
tivamente. Una primera mirada indi
ia que estos metodos de
ordenamiento tienen menos instru
iones que el pro
edimiento de parti
ion del qui
ksort.
Una revision mas a
u
iosa revela que los metodos de ordenamiento, que son O(n2),
ontienen menos if, menos llamadas a Compare() y menos
ompara
iones de ndi
es que el
pro
edimiento de parti
ion.
Los
omentarios anteriores justi
an la observa
ion empri
a de que los metodos de
sele
ion e inser
ion son mas rapidos que el qui
ksort para arreglos peque~nos. >Como es
posible esto? Re
ordemos que la nota
ion O o
ulta los
ostes
onstantes y que esta solo
rige despues de un valor de entrada espe
o. He aqu, en el qui
ksort, un buen
aso de
aquellos
ostes que o
ulta la nota
ion O.
Una mejora substan
ial al qui
ksort
onsiste en
onmutar a otro metodo de ordenamiento para tama~nos de parti
ion peque~nos. Cuanto es lo mas grande que pueda ser
lo peque~no depende, basi
amente, del hardware y del
ompilador. Puesto que el metodo
de inser
ion exhibe mejores tiempos
onstantes que el de sele
ion, lo integraremos a la
siguiente version mejorada del qui
ksort:
hM
etodos de ordenamiento 189bi+
(189a) 222
226a
template <typename T, class Compare>
void quicksort_insertion(T a[], const int & l, const int & r)
{
if (r <= l) return;
const int pivot = partition<T, Compare>(a, l, r);
const int l_size = pivot - l; // tama~
no partici
on izquierda
const int r_size = r - pivot; // tama~
no partici
on derecha
bool left_done = false; // true si partici
on izquierda est
a ordenada
bool right_done = false; // true si partici
on derecha est
a ordenada
if (l_size <= Aleph::Insertion_Threshold) // partici
on izquierda peque~
na?
{
// s
==> ord
enela por inserci
on
insertion_sort<T, Compare>(a, l, pivot - 1);
224
left_done = true;
}
if (r_size <= Aleph::Insertion_Threshold) // partici
on derecha peque~
na?
{
// s
==> ord
enela por inserci
on
insertion_sort<T, Compare>(a, pivot + 1, r);
right_done = true;
}
if (left_done and right_done)
return; // ambas particiones ya est
an ordenadas por inserci
on
if (left_done) // partici
on izquierda ordenada por inserci
on?
{
// s
; s
olo resta ordenar recursivamente la partici
on derecha
quicksort_insertion<T, Compare>(a, pivot + 1, r);
return;
}
if (right_done) // partici
on derecha ordenada por inserci
on?
{
// s
; s
olo resta ordenar recursivamente la partici
on izquierda
quicksort_insertion<T, Compare>(a, l, pivot - 1);
return;
}
// En este punto, ambas particiones no fueron ordenadas por inserci
on
// Ordenar recursivamente de primero partici
on m
as peque~
na
if (l_size < r_size)
{
// partici
on izquierda m
as peque~
na
quicksort_insertion <T, Compare> (a, l, pivot - 1);
quicksort_insertion <T, Compare> (a, pivot + 1, r);
}
else
{
// partici
on derecha m
as peque~
na
quicksort_insertion <T, Compare> (a, pivot + 1, r);
quicksort_insertion <T, Compare> (a, l, pivot - 1);
}
}
Uses insertion sort 202a and partition 215
.
11 En
3.2.2.8
225
Claves repetidas
Aunque el algoritmo de parti
ion hDeni
ion de parti
ion de arreglo 215
i fun
iona
ade
uadamente para
laves repetidas, podra aprove
harse el pro
eso de parti
ion para
onsiderar repiten
ia y realizar una parti
ion
on la forma siguiente:
r
l
Elementos menores que pivot
A este tipo de parti
ion se le denomina \de la triple forma" o \bandera holandesa", en
honor a la na
ionalidad de Edsger W. Dijkstra, el primer hombre que se
ono
e haber
reportado el des
ubrimiento del metodo. La ventaja de este esquema es que todas las
laves repetidas quedan al
entro y las invo
a
iones re
ursivas se efe
tuan sobre
onjuntos
mas peque~nos que no
ontienen repiten
ias, lo que a
elera el ordenamiento.
La parti
ion debe modi
arse de manera tal que las repiten
ias se vayan
olo
ando por
los extremos en un esquema similar al siguiente:
r
l
igual
menor
p
desorden
i
mayor
j
igual
q
Luego,
uando los ndi
es se
ru
en, se realizan los inter
ambios ne
esarios para dejar el
arreglo triplemente parti
ionado.
Los detalles del algoritmo se dejan
omo ejer
i
io.
3.2.2.9
En el qui
ksort
on listas enlazadas es muy fa
ilmente aprensible su fa
ilidad de paraleliza
ion. En efe
to,
omo luego de la parti
ion el pivote ya se en
uentra en su posi
ion
denitiva dentro del orden absoluto, las parti
iones pueden ordenarse independientemente,
por separado, por distintos algoritmos, variantes y, en lo que
on
ierne al proposito de
esta observa
ion, por pro
esadores diferentes. Como manera mas familiar de aprehender el
asunto,
onsideremos un mazo de barajas a ordenar y tres personas, una para parti
ionar
y las dos restantes para ordenar. Mientras la primera realiza la parti
ion, las dos restantes
deben esperar; pero una vez que la parti
ion esta
ulminada, enton
es las personas en
argadas de ordenar pueden realizar el ordenamiento sin que intereran entre ellas y
on el
orden nal del mazo. Cuando las parti
iones esten ordenadas, el parti
ionador junta los
mazos,
on lo que tiene un mazo
ompletamente ordenado. La observa
ion es identi
a para
el ordenamiento
on arreglos, aunque un po
o mas dif
il de entender, dado el
ara
ter de
opia que se requiere para los arreglos.
3.2.2.10
B
usqueda aleatoria de clave
226
226a
226b
227
lista 222bi
Por e
onoma de
odigo, dlink random search() asume que se bus
a sobe una lista de
Dlink. Esto requiere que el usuario surta una
lase de
ompara
ion que
onsidere objetos de tipo Dlink y no Dnode<T>; lo que ha
e al asunto algo engorroso, pues el el
usuario debe
onvertir los punteros del tipo Dlink a Dnode<T>. Como ya lo expli
amos
en x 3.1.3 (pagina 192), la
lase generi
a Compare Dnode nos posibilita la implanta
ion de
random search() para objetos de tipo Dnode<T> y derivados.
3.2.2.11
227
Selecci
on aleatoria sobre arreglos desordenados
Bajo el mismo fundamento del algoritmo anterior, es posible implantar la sele
ion aleatoria; es de
ir, bus
ar el i-esimo menor elemento dentro de la se
uen
ia. Esto se lleva a
abo
de la siguiente manera:
hM
etodos de ordenamiento 189bi+
(189a) 226b 228a
template <typename T, class Compare> static inline
const T & __random_select(T a[], const int & i, const int & l, const int & r)
{
const int pivot = partition<T, Compare>(a, l, r);
if (i == pivot)
return a[i];
if (i < pivot)
return __random_select<T, Compare>(a, i, l, pivot - 1);
else
return __random_select<T, Compare>(a, i, pivot + 1, r);
228
}
Denes:
228a
ahorrar dura
ion de
al
ulo,
olo
amos la veri
a
ion una sola vez, dentro de la interfaz
publi
a, en lugar de ha
erlo a
ada llamada re
ursiva. De este modo, la rutina publi
a se
dene as:
hM
etodos de ordenamiento 189bi+
(189a) 227 228
template <typename T, class Compare>
const T & random_select(T a[], const int & i, const int & n)
{
if (i >= n)
throw std::out_of_range("index out of range");
228b
Para la sele
ion aleatoria
on listas enlazadas debemos a~nadir al pro
eso de parti
ion
la
ontabiliza
ion de las
ardinalidades de las dos parti
iones resultantes. Esto se puede
realizar de la siguiente manera:
hparti
ionar lista y
ontar 228bi
(228
)
while (not list.is_empty())
{
Dlink * p = list.remove_next();
228
hM
etodos de ordenamiento 189bi+
(189a) 228a 229
template <class Compare>
Dlink * dlink_random_select(Dlink & list, const size_t & i)
{
if (list.is_empty())
return NULL;
Dlink * pivot = list.remove_next();
3.3. An
alisis amortizado
229
if (i >= bigger_count)
throw std::out_of_range("index of selection greater than lists size");
Dlink * ret_val = NULL;
if (i == smaller_count)
ret_val = pivot;
else if (i < smaller_count)
ret_val = dlink_random_select <Compare> (smaller, i);
else
ret_val = dlink_random_select <Compare> (bigger, i - (smaller_count + 1));
list.concat_list(&smaller);
list.append(pivot);
list.concat_list(&bigger);
return ret_val;
}
Denes:
dlink random select, used in
hunk 229.
Uses concat list 96, Dlink 90, and remove next 97b.
229
Esta version no requiere dire
tamente el tipo T porque este no se emplea en el prototipo de
la fun
ion. Si lo requerimos, por supuesto, para espe
i
ar la sele
ion sobre Dnode<T>:
hM
etodos de ordenamiento 189bi+
(189a) 228
template <typename T, class Compare>
Dnode<T> * random_select(Dlink & list, const size_t & i)
{
return static_cast <Dnode<T>*>
(dlink_random_select < Compare_Dnode<T, Compare> > (list, i));
}
Uses Compare Dnode 192a, Dlink 90, dlink random select 228
, Dnode 106a, and random select 227 228a.
Basado en esta rutina, ALEPH exporta metodos espe
ializados sin la
lase Compare y
on
listas dinami
as de tipo DynDlist<T>.
3.3
An
alisis amortizado
Consideremos una situa
ion en la
ual queremos estudiar el desempe~no de un TAD o una
estru
tura de datos. La nota
ion O nos propor
iona un me
anismo sen
illo y objetivo
para analizar la tenden
ia de la
urva de dura
ion de un algoritmo. Sin embargo,
uando
analizamos el desempe~no de un TAD debemos
onsiderar otros aspe
tos adi
ionales:
230
pueden basarse en algoritmos distintos. Por tanto, analizar un TAD puede requerir
analizar distintos algoritmos.
Por lo general, un TAD subya e en una estru tura de datos. En este sentido, su
analisis requiere
onsiderar el estado a
tual de la estru
tura de datos, pues este muy
probablemente tenga rela
ion dire
ta
on el desempe~no de la opera
ion.
El estado de la estru tura de datos depende de la permuta ion de opera iones que se
su
eda sobre el TAD. Se
uen
ias de opera
ion distintas produ
iran tiempos distintos.
Al respe
to, >
uanto demorara efe
tuar n opera
iones sobre un TAD? >podemos
estable
er una
ota independiente del valor de la permuta
ion?
As las
osas, veamos los problemas del mero uso de la nota
ion O al analizar un
TAD. Para ello, examinemos el TAD DynListStack<T> (x 2.5.4 (pagina 136)) y una
se
uen
ia
ualquiera de opera
iones sobre este TAD; en parti
ular,
onsideremos las
se
uen
ias posibles que
omporten pushes y pops. Si rememoramos la implanta
ion
de DynListStack<T>, enton
es podemos aprehender que tanto push()
omo pop()
toman O(1). Por tanto,
ualquier se
uen
ia de n opera
iones sobre DynListStack<T>
toma nO(1) = O(n). A
abamos de realizar nuestro primer analisis sobre el
omportamiento de un TAD
omo un todo.
Ahora a~nadamos al TAD DynListStack<T> la opera
ion popn(n), la
ual extrae n
elementos de la pila . Tal opera
ion puede realizarse del siguiente modo:
hpushn para DynListStack<T> 230i
12
230
}
Uses DynListStack 136d.
Si la pila
ontiene n elementos, enton
es el
oste de popn() es O(n) para el peor
aso.
Supongamos que re
ibimos la version extendida del TAD DynListStack<T>
on las siguientes espe
i
a
iones de rendimiento basadas en el analisis tradi
ional :
13
push(item)
pop()
popn(n)
O(1)
O(1)
O(n)
>Cuanto
uesta una se
uen
ia de n opera
iones
onse
utivas sobre un objeto DynListStack<T>?
Para responder por el peor
aso, tomamos la opera
ion popn(), que es la mas
ostosa, y
12 Opera
i
on
que fue denida para el TAD ArrayStack<T> desarrollado en x 2.5.2 (pagina 131) y que
toma O(1).
13 El resto de las opera
iones size(), is empty() y top() se omiten porque tienen tiempo
onstante y
no realizan modi
a
iones sobre la estru
tura de datos.
3.3. An
alisis amortizado
231
al
ulamos el
oste de eje
utarla n ve
es
onse
utivas. A traves de este razonamiento, llegamos a la
on
lusion de que el
oste total es n O(n) = O(n2). Pero este razonamiento
no es justo, pues el desempe~no de popn() depende de la
antidad de elementos en la pila.
Una llamada a popn(n) toma O(n), pero, las siguientes tomaran O(1), <pues se eje
utan
sobre una pila va
a!.
El analisis anterior, mas subjetivo (o profundo), que
onsidera las
ir
unstan
ias de
invo
a
ion a popn(), nos revela que
ualquier se
uen
ia de n opera
iones es O(n).
Una te
ni
a de vanguardia que intenta
on
ierto exito objetizar el analisis de tipos
abstra
tos de datos se denomina \analisis amortizado". La idea es ponderar los
ostes de
opera
iones de un TAD a traves de un poten
ial abstra
to o de
reditos.
3.3.1
An
alisis potencial
En esta
lase de analisis debemos en
ontrar una \fun
ion poten
ial" : D R; donde
D representa la estru
tura de datos subya
ente al TAD y se dene
omo D = {D0}
{D1, D2, . . . , Dn}, donde D0 es el estado ini
ial de la estru
tura de datos y {D1, D2, . . . , Dn}
son los estados subse
uentes resultantes de una se
uen
ia de opera
iones sobre el TAD.
>De que manera puede usarse la fun
ion poten
ial para analizar un TAD? Para abordar
la pregunta, planteemos la siguiente idea de
oste amortizado para la i-esima opera
ion
sobre un TAD:
b
i = ti + (Di) (Di1)
(3.27)
|
{z
ti es el tiempo de eje
u
ion de la i-esima opera
ion, mientras que i es la \diferen
ia de poten
ial" entre la opera
ion a
tual y la anterior, la
ual representa el
onsumo o
la
ompensa
ion de energa segun que el diferen
ial sea positivo o negativo. En este
aso,
el poten
ial denota a una espe
ie de energa que se gasta o se gana entre una opera
ion y
otra .
El
oste amortizado modeliza el he
ho de que algunas opera
iones
uesten mas
que otras, pero, tambien, el que otras opera
iones aumenten el poten
ial de la estru
tura de datos de manera tal de que dispongan de energa
uando a
tuen.
La e
ua
ion (3.27) nos
ondu
e a denir el
oste total de n opera
iones de la siguiente
14
el sentido an
estral de la palabra \poten
ia", raz de \poten
ial",
ual es una no
ion manejada en esta
se
ion. Cuando de
imos que un estudiante tiene el poten
ial de ser brillante, no nos estamos reriendo
a la no
ion fsi
a de energa; no estamos di
iendo algo as
omo el tiene su
ientes
aloras. En este
aso, de
ir que tiene poten
ial expresa que el posee talentos que le permitiran devenir ex
elente, pero
no de
imos que lo es. En nuestro ejemplo el poten
ial es una
ondi
ion indispensable para la brillantez,
pero no una garanta. El pensamiento griego antiguo distingua los terminos (energeia) que
orresponde al lo que hoy
onnotamos
omo \energa". Los griegos usaban la palabra o (ergon) para
designar a la \fuerza para obrar" o a lo que hoy
onnotamos
omo a
tuar, a
ionar y
uyo parti
ipio
pasado es (ergeia); es de
ir, la fuerza empleada o el a
to. Energa proviene de la
omposi
ion del
prejo (en) que signi
a \dentro",
on (ergeia).
Por otra parte, el termino (dunamis) se
orresponde
on lo que hoy
onnotamos
omo \poten
ia",
uya tradu
ion latina es potentia.
Para los griegos energa
onstitua el a
to de la presen
ia y era
onsiderado estati
o, mientras que la
poten
ia era aquello que permita el a
to y que se
onsideraba dinami
o.
232
forma:
T(n) =
n
X
ti =
i=1
n
X
i=1
n
X
i=1
b=
C
n
X
i=1
b
i =
n
X
i=1
n
X
i=1
n
X
(b
i ((Di) (Di1)))
{z
}
|
i
b
i
ti +
n
X
(Di) +
i=1
n
X
n
X
(Di1) =
i=1
(Di)
i=1
n
X
(Di1)
i=1
(3.28)
i=1
Para que el poten
ial tenga su sentido, es menester denir de manera tal que, para
toda permuta
ion posible de opera
iones, (Dn) (D0); es de
ir, que la diferen
ia
poten
ial total entre el estado ini
ial de la estru
tura de datos y la ultima opera
ion
siempre sea positivo, pues si no, en algun momento la estru
tura de datos
are
era de
poten
ial, lo
ual pare
e no tener sentido en nuestro
ontexto terrenal . Podemos asumir
que (D0) 0, pues la idea de una estru
tura de datos es otorgar poten
ia a las futuras
opera
iones. Bajo esta asun
ion, (Dn) (D0) 0, lo que, en fun
ion de (3.28), nos
ondu
e a armar la desigualdad siguiente:
15
T(n) =
n
X
i=1
ti
n
X
i=1
b
i
(3.29)
b (n) a
ota suBajo las premisas planteadas, la desigualdad (3.29) nos demuestra que C
b (n) y los
periormente a T(n), razon por la
ual podemos trabajar
on seguridad mediante C
peores
asos de ti segun
ada una de las opera
iones del TAD y las posibles permuta
iones
de se
uen
ias de opera
ion.
Consideremos la version extendida del TAD DynListStack<T> y denamos : D
R
omo la
antidad de elementos que tiene la pila al termino de una opera
ion, por lo
que (D0) = 0. Estudiemos lo que o
urre
on los poten
iales mirando alguna se
uen
ia
de opera
ion parti
ular; por ejemplo, la mostrada por la tabla siguiente:
i
1
2
3
4
5
6
15 Mas
1
1
1
1
1
3
1
2
3
2
3
0
1
1
1
1
1
3
2
2
2
0
2
0
3.3. An
alisis amortizado
233
{z
ada vez que la i-esima opera
ion sea popn(k). Notemos que el
oste de pop() es equivalente a popn(1).
En sntesis, independientemente de la permuta
ion de la se
uen
ia de opera
ion, los
ostes amortizados del TAD DynListStack<T> son los siguientes:
Nombre de opera
ion b
push(item)
pop()
popn(n)
2
0
0
Ahora estudiemos el
oste total de n opera
iones
onse
utivas sobre el TAD DynListStack<T>.
Podemos dividir las opera
iones en n1 push() y n2 popn() (re
ordemos que pop()
popn(1)). Por tanto:
b (n) = O(n1) 2 + O(n2) 0 = O(n1) = O(n)
T(n) C
As pues, el
oste promedio por opera
ion, entre
ualquier permuta
ion de n opera
iones
esta dado por:
b (n)
O(n)
C
=
= O(1)
ti
n
3.3.2
An
alisis contable
En un analisis amortizado
ontable, a
ada opera
ion se le asigna una
antidad
onstante
llamada
redito, es
ogida
uidadosamente segun la naturaleza de la opera
ion, la
ual no
ne
esariamente se
orresponde
on el
oste real en dura
ion. Si el
oste en dura
ion de la
i-esima opera
ion es ti, y b
i su
oste
redito, enton
es requerimos satisfa
er:
b=
C
n
X
i=1
b
i T(n) =
n
X
ti
(3.30)
i=1
Para
ualquier se
uen
ia posible de n opera
iones sobre el TAD o estru
tura de datos. El
oste amortizado del TAD o estru
tura de datos para una se
uen
ia de n opera
iones es:
b=
C
Pn
i
i=1 b
n
(3.31)
234
TAD DynListStack<T>). Sin embargo, el razonamiento para la sele
ion del
redito de
ada opera
ion, aunque inspirado en la idea de poten
ial, esta prestado del argot mer
antil. La idea es que siempre deba haber
redito para
ostear
ualquier se
uen
ia de
opera
ion. En este sentido, los
reditos de
ada opera
ion deben sele
ionarse de modo tal
que \nan
ien"
on
reditos a otras.
De
ierta manera, la asigna
ion de
reditos puede interpretarse
omo una fun
ion poten
ial dis
reta y por partes segun la opera
ion.
Por ejemplo, asignemos al TAD extendido DynListStack<T> los siguientes
reditos
amortizados:
Nombre de opera
ion
Creditos
push(item)
pop()
popn(n)
2
0
0
Para brindarle sentido al razonamiento que nos
ondu
e a esta sele
ion de
reditos, debemos observar que no puede haber un pop() si antes no hubo un push(). De este modo,
podemos de
ir que
uando se efe
tua un push() se dejan dos (2)
reditos, uno
orrespondiente al
oste en dura
ion y otro dejado en prestamo o garanta para
ostear la dura
ion
de extra
ion del elemento
uando se efe
tue su pop(). Puesto que las opera
iones han
sido
osteadas por adelantado por el push(), pop() y popn() no tienen
reditos, pues sus
ostes han sido amortizados por el push().
Con los
ostes amortizados anteriores, podemos
al
ular una
ota para el
oste amortizado del TAD DynListStack<T>, el
ual no ex
edera de:
b
C
2n
= 2 = O(1)
n
El termino \
redito", proveniente del verbo latino \
redere", signi
a
reer,
onar,
dar fe en una persona. Tristemente, hoy en da, el mundo e
onomi
o emplea el termino
redito para referir a una
antidad de dinero que se otorga en prestamo,
omo si la presun
ion de buena fe fuese
uanti
able o, en a~nadidura, estuviese vin
ulada al poder
e
onomi
o .
El termino \amortizar" se usa en el mundo ban
ario y es desde all que proviene la
metafora
on este tipo de analisis. Curiosamente, \amortizar" proviene del fran
es, el
ual,
a su vez, proviene del latn medieval admortizare. El prejo ad adverbializa \proximidad\,
mientras que mortis signi
a \muerte". Amortizar signi
ara, enton
es, la proximidad
de la muerte, pero el termino se usa desde la Edad Media para
onnotar la proximidad de
la muerte de una deuda.
16
3.3.3
Selecci
on del potencial o cr
editos
Para que un analisis amortizado tenga sentido, es esen
ial en
ontrar una buena fun
ion
poten
ial o lograr interpretar las opera
iones en fun
ion de su trabajo y la manera en que
16 A
la fe
ha de la ultima revision de este es
rito, el premio Nobel de la Paz de 2006 ha sido
onferido al Profesor bengal Muhammad Yunus, fundador del Ban
o Grameen (ban
o rural). El ban
o
en
uestion
on
ede \mi
ro-
reditos" a prestatarios muy pobres materialmente, sin ninguna garanta o
\
redito" e
onomi
a, permitiendoles
onfrontar su pobreza material. No sorprendentemente, el 90% de
los prestatarios reintegran
ompletamente el prestamo, aunque, tambien, el 95% de los prestatarios son
mujeres.
235
k
X
tj
j=1
b
j =
=
k
X
j=1
k
X
tj + (Dk) (D0)
j=1
= ti +(Di) (Di1)
= b
i
Por tanto, el
oste amortizado de la opera
ion es la suma de los
ostes amortizados de sus
etapas.
Ademas de los
ono
imientos sobre la estru
tura de datos y de la te
ni
a de analisis,
en la busqueda y sele
ion de una fun
ion poten
ial intervienen la experien
ia, el arte y
la suerte.
3.4
Correctitud de algoritmos
236
El 4 de Julio de 1996 se lanzo por vez primera el
ohete europeo \Ariane 5". Inmediato
al lanzamiento, el
ohete empezo a desviarse de su traye
toria verti
al y debio destruirse 40
segundos despues. Las investiga
iones ulteriores revelaron que una rutina trataba numeros
en punto
otante
omo si ellos estuviesen representados en aritmeti
a entera. La version
anterior del
ohete, \Ariane 4" manejaba enteros y la rutina en
uestion no se veri
o
exhaustivamente para la nueva version del
ohete. La rutina era in
orre
ta [16, 10.
3.4.1
Una interpreta
ion de
orre
titud estriba en determinar si el programa arroja respuestas
orre
tas para todas las entradas posibles. En fun
ion de esto, puesto que en la mayora de
las o
asiones los valores posibles de entrada son o muy grandes o innitos, una prueba de
orre
titud requiere
lasi
ar la entrada en todos los sub
onjuntos posibles que
onforman
la entrada.
Lo anterior puede plantearse formalmente
omo sigue. Sea I el
onjunto dominio de
entrada de un programa y R el
onjunto resultado. Enton
es, una prueba de
orre
titud debe examinar el algoritmo y determinar en fun
ion del mismo (bifur
a
iones, lazos,
et
etera), los sub
onjuntos de entrada I = I1 I2 In, junto
on sus sub
onjuntos
de salida R = R1, R2, . . . , Rn. Notemos que los sub
onjuntos resultado pueden solaparse
entre s; o sea que los resultados no tienen por que ser ex
luyentes.
La prueba de
orre
titud
onsiste, enton
es, en veri
ar si para
ada sub
onjunto Ii el
algoritmo o programa arroja la salida Ri. Puesto que a menudo Ii y Ri son muy grandes
o innitos, la prueba se realiza
on los valores frontera de los sub
onjuntos.
Por ejemplo muy sen
illo, una inspe
ion mas profunda del ordenamiento por sele
ion
estudiado en x 3.1.3 revela que el bloque hSea min el ndi
e menor entre i + 1 y
n - 1 190ai siempre en
uentra el mnimo entre i+1 y n-1, pues todo el rango (i..n) es
inspe
ionado. Por tanto, hSea min el ndi
e menor entre i + 1 y n - 1 190ai es
orre
to.
Por otra parte, el for mas externo re
orre todo el arreglo entre [0..n 1); solo falta
el ultimo elemento del arreglo el
ual sera inspe
ionado por hSea min el ndi
e menor
entre i + 1 y n - 1 190ai.
Por tanto, todos los elementos del arreglo son inspe
ionados para determinar el
mnimo. El algoritmo pare
e ser, pues,
orre
to. El
onjunto de entrada fue I =
{Todas las permuta
iones posibles} y el de salida R = {Todas las permuta
iones ordenadas}.
3.4.2
Tipos de errores
es sen
illa de demostrar. En este sentido hay dos
lases generales de errores en la
prueba:
1. Que las instan
ias posibles de entrada no esten
ompletamente
ubiertas,
aso
en el
ual no se sabra si el algoritmo es
orre
to o no para la
lase de entrada
ausente.
237
fase se llama
odi
a
ion y durante ella se es
ogen las representa
iones de los datos
bajo la forma de estru
tura de datos, as
omo las representa
iones de las se
uen
ias
de eje
u
ion del algoritmo segun los modelos del lenguaje (while, for, if, et
etera).
evaluar
orre
titud se emplean diversos tipos de programas. En este sentido, una
uarta
lase de error puede indu
irse por el he
ho de que uno de los programas
usados para veri
ar
orre
titud sea in
orre
to.
Ante estas perspe
tivas de la
orre
titud, aunado al he
ho de que esta en s se aprehende
uando se eje
ute el programa y se resuelva el problema, un programador debe, permanentemente, asumir una a
titud de duda respe
to a la
orre
titud.
3.4.3
Prevenci
on y detecci
on de errores
Esen
ialmente, hay dos enfoques, que no son ex
luyentes, sino, mejor di
ho,
omplementarios, para lidiar
on la
orre
titud de un programa:
1. Preven
ion: >
omo evitar
ometer un error?
2. Dete
ion: >
omo dete
tarlo lo mas pronto posible?:
La preven
ion requiere una \a
titud"
rti
a por parte del programador en el sentido
de que aprenda de sus errores y busque maneras de no volver a
ometerlos.
La in
orre
titud siempre es
ostosa en el sentido de que de alguna manera a
arrea
perdidas; pero hay situa
iones en las
uales es demasiado
ostosa. Por ejemplo, una falla
en un programa
ontrolador eje
utandose en un
omputador industrial puede
ausar una
parada de planta que detiene la
adena produ
tiva; mas
ostoso es una falla en un programa
de mision
rti
a; un satelite, por ejemplo.
La dete
ion forzosamente requiere un analisis inspe
tivo del algoritmo o programa. Cuando el analisis se realiza sin eje
utar el programa en
uestion, se le denomina \estati
o". Analogamente,
uando el analisis se vale de la eje
u
ion del programa se
le llama \dinami
o". A la pregunta de >
ual es mejor? La respuesta es que
uanto antes
se dete
te un error menos
ostoso este es. Conse
uentemente, pare
e obligatorio efe
tuar
analisis estati
o antes de instrumentar un programa. Para aproximar el entendimiento de
este asunto,
onsideremos la siguiente gra
a:
238
Costo de correccin
de un error
Produccin
Pruebas
Codificacin
Diseo estructura
de cdigo
Diseo arquitectural
Requerimientos
Disciplina de programaci
on
Buenas son las
ostumbres, buenas deben ser las obras. Una
ostumbre es un habito
adquirido de
ono
imientos pra
ti
os,
omprobados, des
ubiertos durante he
huras de
buenas obras. En programa
ion,
omo en
ualquier otra pra
ti
a, los pra
ti
antes forjan
ostumbres, buenas
ostumbres, porque sino se les morira la pra
ti
a. Cuando una
ostumbre deviene
omun entre todos (o la mayor parte de) los pra
ti
antes, enton
es esta
deviene en \norma".
La observan
ia de una
ostumbre, mas aun de una norma, requiere, segun se sea aprendiz o maestro, de un esfuerzo
ons
iente de voluntad por re
ordar apli
ar la norma y de
una fuerza de
ara
ter por no transgredirla. A este esfuerzo y fuerza se le denomina \dis
iplina".
239
Hoy en da a las buenas
ostumbres se les llama \buenas pra
ti
as"; pero la redundan
ia de este
ali
ativo plantea una ambiguedad
on la no
ion de pra
ti
a, razon
por la
ual seguiremos usando\`
ostumbre" o,
uando esta sea objetiva entre los pra
ti
antes, \norma".
>Cuales son las
ostumbres de programa
ion que subya
en detras de programas
orre
tos? Numerosos, voluminosos e interesantes textos han sido es
ritos sobre este asunto.
No hay, pues, espa
io en esta sub-se
ion para presentar y dis
utir todo lo inherente a
la dis
iplina de programa
ion. Pero s podemos, remitirnos al siguiente de
alogo de Gerard Holtzmann [11 para la es
ritura de programas
orre
tos
rti
os.
Dec
alogo de Holtzmann
menudo, la expresion es \lazo innito". Sin embargo, en matemati
a, as
omo en otros dominios, el
adjetivo innito se usa en el sentido de propor
ion; sentido para el
ual, en la opinion de este subs
riptor,
no en
aja el tiempo. Para el tiempo en s existe el adjetivo \eterno".
240
241
242
1. >Se en
ontrara el error o la misma
lase de error en otra parte del programa?
Plantearse esta pregunta
onlleva estable
er un patron para el error y en fun
ion del
mismo bus
ar en el resto del programa otras apari
iones del patron. Los hallazgos
del patron son lugares probables de repiten
ia del error.
2. >Cuales otros errores estaran rela
ionados
on el en
ontrado o
on su
orre
ion?
Partiendo de la posibilidad de que la
orre
ion introduz
a un nuevo error, abordar
esta pregunta
onfronta plantear una
orre
ion e interrogar que su
edera una vez
243
que esta se reali
e. A su vez, esto pasa por demostrar rigurosamente que la
orre
ion
sea
orre
ta; o sea, que no
ontenga algun error.
3. >Que se debera ha
er para prevenir
ometer de nuevo el error?
Una vez respondidas las dos preguntas anteriores, se debe estable
er, en fun
ion del
patron en
ontrado, un proto
olo que evite repetir el
ometimiento del error.
Quiza esta sea la pregunta mas enrique
edora para el programador, pues su abordaje
plantea una ense~nanza.
Comentarios aclaratorios sobre la Ingeniera del Software
Para
ulminar esta larga sub-se
ion, permtasenos
omentar a
er
a de lo que hoy
onnota la expresion \Ingeniera del Software" o \Ingeniera de la programa
ion".
La palabra \ingeniera" resulta de la
omposi
ion del prejo \in",
ual signi
a \dentro" y de \genio" ,
ual
onnota, en esta
ir
unstan
ia, D.R.A.E. dixit: \
apa
idad mental extraordinaria para
rear o inventar
osas nuevas y admirables". Ingeniera
onsiste, enton
es, en usar el genio de la inteligen
ia que todos tenemos para instrumentar
medios que
onduz
an al n denido. En otras palabras, en ser
apaz de resolver problemas
on sentido, en los
uales se interrogue por la legitimidad y
onvenien
ia de sus nes.
Bajo la a
ep
ion anterior, la ingeniera es \extraordinaria"
uando se
rean obras innovadoras y buenas. De este modo, ser ingeniero es abordar un problema y aplicar
ono
imiento te
ni
o/te
nologi
o para resolverlo de manera autenti
a, sin que ne
esariamente ello signique ser primigenio u original, pues, en mu
has
ir
unstan
ias, aunque
ya se haya resuelto el problema en otras latitudes, en el lugar donde se formula no se
ono
e su solu
ion porque los
ono
imientos ne
esarios no estan disponibles o, simplemente, se presenta una varia
ion de un problema previamente resuelto que diere de sus
ante
edentes.
Desde tiempos inmemoriales, re
ordemos, por ejemplo, la torre de Babel, mu
hos
proye
tos ingenierles fra
asan. >Como evitar o minimizar la posibilidad de fra
aso? es
una pregunta tambien an
estral que ha
ondu
ido a formular metodos objetivos, no solo
en su sentido de
ara externa
omun a todos, sino en su
onstan
ia de apli
a
ion, que
maximi
en la posibilidad de exito de un proye
to ingenierl.
As, de este modo, tenemos en las ingenieras \normas"; o sea, costumbres
on
ara
ter de regla o ley que deben seguirse si se desea parti
ipar en un proye
to ingenierl
y su
onse
uente obra. En las obras
iviles, por ejemplo, se tienen normas mnimas para
todos los pro
esos organiza
ionales (
al
ulo, geren
ia, li
ita
ion, publi
idad, et
etera), as
omo normas para los metodos de
onstru
ion (mnima
antidad de a
ero, mnima
alidad de mez
la, et
etera) y, nalmente, normas y roles que deben
umplir los ads
ritos a
un proye
to de ingeniera
ivil. Notemos que la apari
ion y apli
a
ion de normas indi
ian
que una pra
ti
a ha devenido ordinaria, lo que
ontrasta
on el sentido extraordinario que
la etimologa y el D.R.A.E. le imparten a la ingeniera .
Tener y apli
ar normas en las ingenieras es muy bueno, pues propor
iona estabilidad
y
onanza a un proye
to. Destaquemos que las normas
onstituyen, muy justamente, el
18
19
18 Re
ordemos
19 \Ordinario"
la sub-se
ion x 1.2.3.4 que hablaba del termino \genus", raz etimologi
a de \genio".
onnota algo que se
ir
uns
ribe en el orden. Segun D.R.A.E. (primera a
ep
ion) signi
a
\Comun, regular y que su
ede habitualmente". La a
laratoria es ne
esaria porque, en algunos
ontextos,
\ordinario" se usa, erroneamente,
on dejos peyorativos.
244
21
245
aun le resta mu
ho por des
ubrir. Nuevas
lases de software apare
eran durante los tiempos venideros que obede
eran a metodologas que aun no se
ono
en; in
lusive, tambien
apare
eran nuevos tipos de hardware que requeriran
lases de software muy diferentes. Finalmente, la
rea
ion de software, aun el tradi
ional, sigue siendo un queha
er altamente
dependiente del ingenio humano.
As pues, antes de abordar la le
tura de un libro de Ingeniera de Software tome en
onsidera
ion que las
ostumbres pertinentes a las fases, etapas o itera
iones del llamado
i
lo de desarrollo de software aun no son normas. Hasta el presente, salvo para la
odi
a
ion, una metodologa de desarrollo es buena para una o, quiza, para algunas,
lase(s)
de software, pero no para todas. En a~nadidura,
omo ya lo hemos se~nalado, aun restan
nuevas y mu
has
lases de hardware y software por des
ubrir.
A la luz de esta re
exion, es menester, enton
es, preguntar >que es ingeniera del
software? Para
onfrontar esta pregunta, debemos primero denir dos a
tividades que se
distinguen en el resto de los
ontextos ingenierles: proye
to y obra.
En el
ontexto de la ingeniera, una obra es una
rea
ion te
nologi
a
on
reta,
onsumada y operativa que satisfa
e un n bueno; por ejemplo, en la ingeniera
ivil,
un puente. En el
aso de la programa
ion una obra es, enton
es, un programa
onsumado
y operativo; por ejemplo, un sistema de vota
ion en el
ual se automati
e efe
tiva y e
ientemente todo un pro
eso de ele
ion popular.
En ingeniera, un proye
to es un
onjunto de planes, demostrativos, a
er
a de
omo
se instrumenta una obra. En nuestro interes, un proye
to es el
onjunto de planos que se
deben seguir para realizar un sistema
omputa
ional. Notemos que para que un proye
to
pueda
ali
arse de bueno debe satisfa
er dos
osas:
1. Que el n sea bueno.
2. Que aporte eviden
ia
ontundente a
er
a de su fa
tibilidad, dura
ion,
ostes y demas
requerimientos.
Esta eviden
ia es importantsima por diversas razones. En primer lugar, permite
vislumbrar que tan instrumentable es una obra y espe
i
a
iones de
apa
idad para
los agentes
onstru
tores que seran
apa
es de realizarla. En segundo lugar, estable
e un baremo de responsabilidad entre el ingeniero, quien ha
e el proye
to, y el
onstru
tor, quien lo realiza.
En este estadio, por razones de
omuni
a
ion, la objetividad es grandemente apre
iada.
En una obra de ingeniera, la mayor parte del merito intele
tual, o sea, del ingenio, se
despliega en el proye
to y no en su
onstru
ion. Esen
ialmente, ingeniera es organizar
medios para lograr un n en el mar
o de una obra; es
on
ebir un proye
to de obra y
algunas ve
es supervisar su
onstru
ion. Por supuesto, nada impide que un ingeniero sea,
tambien,
onstru
tor, pero, insistimos la fase que requiere ingenio es el proye
to.
Entendidos los terminos anteriores, podemos en
ontrarle sentido a las metodologas de
ingeniera y a sus diferentes visiones de lo que es el
i
lo de vida de desarrollo del software.
Lamentablemente, la ingeniera del software aun adole
e del problema de que aun no
posee me
anismos objetivos y generales para eviden
iar la fa
tibilidad de un proye
to.
En a~nadidura, la ingeniera del software desde~na los po
os instrumentos que existen para
246
veri
ar la
orre
titud de un software: los modelos formales de veri
a
ion y de simula
ion. Conse
uentemente, el destino de la mayor parte de las obras de programa
ion se
ono
e durante la
onstru
ion y no, a priori, entre el nal del proye
to y el ini
io de la
onstru
ion.
3.4.3.2
An
alisis est
atico
Los errores de
ompila
ion han sido
ausa de ofus
a
ion y maldi
ion entre los programadores noveles y algunos avanzados. >Cuantos de nosotros alguna vez hemos permane
ido
bloqueados porque un programa no nos
ompila?
Una mirada mas
rti
a del problema anterior nos permite aprehender que, salvo un
error del
ompilador o del lenguaje de programa
ion,
uestion improbable pero fa
tible, un
error de
ompila
ion tiene la gran bondad de que nos fuerza a enfrentar el
ometimiento de
un error
omo programadores. En el ejemplo anterior, no tiene sentido asignar un entero
a una
adena de
ara
teres.
Por lo general los
ompiladores parametrizan sus modos de dete
ion y tratamiento
de errores. Consultese el manual del
ompilador GNU gcc para mas detalles. En todo
aso,
omo ya lo di
ta la de
ima regla de Holtzmann,
omplese
on toda la dete
ion de
errores y alertas habilitados. No se prosiga hasta que el
ompilador deje de arrojar alertas
y errores.
Analizadores externos
247
Meta-compilaci
on
Una te
ni
a grandiosa de veri
a
ion de
orre
titud requiere tradu
ir el programa a
una maquina de estado nito que represente los diferentes estados de los programas. Puesto
que no disponemos de espa
io para expli
ar formalmente que es un automata, lo intuiremos
pi
tori
amente mediante el siguiente y simplsimo automata, resultante del programa que
al
ula el menor entre dos numeros x, y:
0
not x<y
x<y
1
min=y
min=y
3
ret=min
4
Cada
r
ulo
orresponde a un \estado", mientras que
ada
e
ha es una \transi
ion"
entre dos estados. El
ambio de estado o
urre
uando se eje
uta la instru
ion aso
iada a
la transi
ion. El estado ini
ial se denota
on una
e
ha aislada que le llega, mientras que
uno nal
on otra
e
ha que le sale.
Una vez obtenido el (o los) automatas, un simulador se en
arga de explorar todas las
ombina
iones de estados posibles en la busqueda de errores sinta
ti
os y semanti
os. Un
error sinta
ti
o se
ataloga
omo un error general a
ualquier
lase de programa; por ejemplos, que un programa jamas eje
ute alguna por
ion de su
odigo, o que el programa entre
248
en un estado de bloqueo eterno en el
ual se espera por un evento que jamas o
urrira (deadlo
k), o que se entre en la eje
u
ion de un lazo innito. Un error semanti
o ata~ne al n del
programa y para espe
i
arlo se utiliza, por lo general, logi
a temporal o automatas de
predi
ados (o de Bu
hi) . Al pro
eso de veri
a
ion semanti
a mediante logi
a temporal (o
automatas de Bu
hi) se le denomina Verificacion de modelo (\Model Che
king").
As las
osas, la veri
a
ion por este metodo pare
e bastante simple. Pero, en la realidad, los automatas resultantes de un programa real son extremadamente grandes y no
siempre existe un metodo determinista para tradu
ir programas ha
ia automatas.
Este metodo de veri
a
ion es mu
ho mas interesante
uando se veri
an programas
on
urrentes; es de
ir, programas
ompuestos por varios programas que se eje
utan \simultaneamente". Pero aqu se requiere
onstruir un \automata global"
ompuesto por la
ombina
ion de los estados de todos los automatas.
Teori
amente, la explora
ion global del espa
io de estado es
apaz de dete
tar
ualquier
error sinta
ti
o y todos los semanti
os en fun
ion del n denido. En la pra
ti
a, empero,
se tiene el problema de que el espa
io de estado del automata global es explosivo O(2n).
Existen te
ni
as, basadas en desarrollos teori
os, que permiten lidiar
on este problema,
siendo las mas importantes las siguientes:
22
23
1. Algoritmos simboli
os: la idea
onsiste en substituir el automata, o parte de el, por
una formula logi
a. Por ejemplo, para el automata anterior, sera posible substituir
las transi
iones desde el estado 1 ha
ia el 4 por una transi
ion
uyo predi
ado sera
x < y or not x < y, ret=min pues el
ujo de eje
u
ion nun
a se detendra a
ausa de
las transi
iones suprimidas.
2. Redu
ion por orden par
ial: esta te
ni
a, que se deriva de algunos resultados
matemati
os,
onsiste, muy a grosso modo, en sustituir largas
adenas de transi
iones se
uen
iales, que no tienen bifur
a
iones, por una sola transi
ion. El orden
par
ial se apli
a dinami
a y re
ursivamente
uando se trata del automata global.
Por ejemplo, el automata:
0
tmp=x
1
x=y
2
y=tmp
3
puede substituirse por el siguiente
ontentivo de dos estados y una transi
ion
ompuesta por las tres instru
iones:
22 La l
ogi
a temporal es una logi
a de
23 Un aut
omata de predi
ados es uno
249
0
tmp=x
x=y
y=tmp
1
Un simulador puede dinami
amente dete
tar y efe
tuar esta
lase de redu
ion.
3. Abstra
ion: en simple, esta te
ni
a se basa en observar
onjuntos de estados y
\abstraer" su interfaz de manera general. El
onjunto
onsiderado se substituye por
un automata redu
ido
uyo rol es simular el resultado general.
Las te
ni
as anteriores permiten redu
ir, muy
onsiderablemente, el espa
io de estado. En a~nadidura, otra te
ni
a, llamada \supertraza" [15, que examinaremos en x 5.3.2,
basada en el uso de tablas hash sin dete
ion de
olision (ver
apitulo 5), permite redu
ir
todava mas el espa
io global de estado. Lo anterior, aunado al
ono
imiento de las
ien
ias
estadsti
as, indi
ia que es posible explorar \
asi todo" o, \todo" [43, el espa
io de estado
si se realizan su
ientes simula
iones aleatorias.
Quiza el exponente mas popular de esta
lase de veri
a
ion es spin/promela [13,
14, 12, 34. promela es un lenguaje de espe
i
a
ion de programas (no un lenguaje de
programa
ion) y spin es su simulador.
El enfoque requiere que el programa se espe
ique en promela, lo que a~nade un paso
adi
ional y una fuente poten
ial de error al trasladar la espe
i
a
ion en promela ha
ia el
lenguaje de programa
ion. La ne
esidad de este enfoque se debe a que aun no se
ono
e
generalmente
omo se tradu
e un programa en un lenguaje de programa
ion ha
ia un
automata de estado nito.
spin es libre desde 1991.
Re
ientemente se han des
ubierto te
ni
as efe
tivas para tradu
ir un programa realizado en un lenguaje de programa
ion a un automata segun la naturaleza de la apli
a
ion.
Esto permite automatizar el pro
eso de veri
a
ion sin ne
esidad de pasar por la fase de
modeliza
ion en otro lenguaje.
3.4.3.3
An
alisis din
amico
Sistema operativo
El hardware y el propio sistema operativo tienen medios para dete
tar errores durante
la eje
u
ion de programas. En el
aso del hardware, por ejemplo, puede dete
tarse una
division por
ero. En el
aso
ombinado de hardware y sistema operativo, puede dete
tarse
una viola
ion de memoria (segmentation fault), error tambien
ausante de desespera
iones
y maldi
iones en los programadores noveles, pero en realidad muy afortunado, pues indi
ia
el
ometimiento de un error y la asun
ion de su a
ion
orre
tiva.
Invariantes
Una invariante o aserto es un predi
ado logi
o que se
olo
a en algun punto de un
programa y
uya
ertitud veri
a si se
umplen las premisas de eje
u
ion supuestas por
el dise~nador del algoritmo o el programador.
250
La idea es bastante simple: el predi
ado se evalua solo
uando el
ujo de eje
u
ion pasa
por la invariante. En ese momento, se evalua el predi
ado y, si este es falso, enton
es se le
noti
a al programador mediante algun evento; por lo general, el aborto del programa. El
evento le permite aprehender que, (1) o las
ondi
iones requeridas para eje
utar el
ujo
en
ierto punto no se estan
umpliendo, o (2) el
ometio un error de razonamiento. El
ualquiera de los dos
asos, se esta en presen
ia de un error.
La manera mas simple de interpretar una invariante es en forma de pre
ondi
ion o
post
ondi
ion de una rutina. Notemos que esta idea puede apli
arse a
ualquier
ujo
de eje
u
ion; es de
ir, al prin
ipio de un
ujo, estable
emos pre
ondi
iones que deben
umplirse para que sea eje
utado
orre
tamente. Del mismo modo, luego de la eje
u
ion del
ujo, el estado habra
ambiado de tal forma que deben
umplirse algunas post
ondi
iones.
Cada pre
ondi
ion o post
ondi
ion no es otra
osa que una invariante.
Algunos ejemplos pueden
lari
ar la idea.
pre ondi ion eviden iara que en alguna parte, antes de invo ar a x, se ometio un
error. Del mismo modo, podramos
olo
ar
omo post
ondi
ion x2 = x,
uya viola
ion, a
ondi
ion de que la pre
ondi
ion no haya sido violada, probablemente indi
iara un error
en la instrumenta
ion de x.
Un eventual
uestionamiento a las invariantes es que estas
onsumen tiempo de eje
u
ion. Conse
uentemente, su uso ex
esivo puede impa
tar el tiempo de eje
u
ion. Notemos que este
oste, aunque
onstante, fa
ilmente puede tornarse oneroso si, por ejemplo,
esta
ontenido dentro de un lazo. Por esa razon, mu
hos programadores supeditan el uso
de invariantes al
odigo de desarrollo y no al
odigo de produ
ion. Por lo general, en C y
el C++, esto se automatiza mediante ma
ros
ondi
ionales. La manera tpi
a es
ompilar
invariantes si el ma
ro DEBUG esta denido.
En mu
has o
asiones es importante dejar las invariantes en
odigo de produ
ion, pues
arrojan indi
ios de o
urren
ia de error. En estas o
asiones, no se debe abortar el programa.
La bibliote
a estandar C
ontiene una fun
ion llamada assert(predicado),
uya
fun
ion es veri
ar una invariante. No obstante, esta primitiva es
ostosa en tiempo de
eje
u
ion, razon por la
ual su uso es
uestionable para
odigo produ
tivo.
Existen bibliote
as espe
ializadas en el uso de invariantes. Quiza una de las mas populares sea GNU nana [30,
uyas bondades, respe
to al assert() tradi
ional, pueden resumirse en:
1. Dise~nada para la e
ien
ia en dura
ion y espa
io. De he
ho, la veri
a
ion es tan
e
iente que mu
has ve
es no vale la pena eliminar invariantes en el
odigo de produ
ion.
2. La a
ion a tomar ante una viola
ion de invariante es
ongurable. Se puede, entre
otras
osas:
(a) Modi
ar la a
ion a tomar ante una viola
ion de invariante; por ejemplos,
abortarse, reini
iar, enviar a un depurador o, simplemente, reportar y
ontinuar.
(b) Habilitar o deshabilitar sele
tivamente la evalua
ion de invariantes, tanto durante la
ompila
ion
omo durante la eje
u
ion.
251
3. Enrique
ida
on otras
lases formales primitivas que permiten veri
a
iones de invariantes mas
omplejas.
4. Medi
ion pre
isa y portatil del tiempo de eje
u
ion.
Veamos algunas de las primitivas de GNU nana mas importantes para manejar invariantes:
Invariantes basadas en asertos : De esta
lase de invariantes, la prin
ipal es:
void I(pred)
La
ual veri
a que pred sea
ierto, en
uyo
aso negativo imprime un mensaje de
error y aborta.
Una forma
ondi
ional de evaluar una invariante es mediante:
void void IG (bool pred, bool cond)
La
ual evalua el predi
ado pred
omo invariante solo si el predi
ado cond es
ierto.
Esta forma permite denir
asos espe
iales.
A ve
es es ne
esario mantener estado previo, por lo general esto o
urre
on las
post
ondi
iones. En este
aso, son de interes las siguientes primitivas:
void ID (instrucci
on) : Permite eje
utar una instru
ion, normalmente, una
Aunque ID() e IS() son sinta
ti
amente similares, el primero debe usarse para
de
larar y el segundo para asignar, pues el
odigo de genera
ion e identi
a
ion
asume esta fun
ionalidad.
void ISG (instrucci
on, bool pred) : Efe
t
ua una asigna
ion
ondi
ional a
que el predi
ado pred sea
ierto.
251
Por ejemplo, un
odigo que
al
ula la raz
uadrada de un numero real podra
plantearse del siguiente modo:
hraz
uadrada 251i
float ra
z_cuadrada(const float & x)
{
I(x >= 0); // precondici
on exigiendo que x sea positivo
// c
alculo de la ra
z de x, el cual se guarda en ret_val
I(ret_val*ret_val == x); // Resultado debe ser igual a x
ID(float ret_plus = ret_val + 1); // Declara variable adicional
I(x < ret_plus*ret_plus); // y menor que el producto (ret_val+1)^2
}
252
Desde el lenguaje C es posible
olo
ar una se
uen
ia de instru
iones
omo una sola
separadas por el operador
oma. Esto es importante de resaltar en este
ontexto,
porque es lo que permite englobar una se
uen
ia
ompleta de instru
iones en alguna
primitiva de invariantes; por ejemplo:
ID(int i = 0, j = x + y);
Verificaci
on por cuantificadores de l
ogica de predicados : Derivado de la
elebre
\programa
ion por
ontratos" [24, GNU nana ofre
e veri
a
iones mas renadas bajo
la forma de uanti adores logi os. Entre los mas importantes podemos indi ar:
on,iteraci
on,pred) :
orrespondiente al
uanti bool A(inicio,condici
ador \para todo" (), el
ual se
orresponde
on:
for (inicio; condici
on; iteraci
on)
I(pred);
bool E (init,condici
on,next,pred) :
orrespondiente al
uanti
ador de
Es de
ir, es
ierto si al menos en una de las itera
iones se
umple el predi
ado.
on,next,pred) : Contador de o
urren
ias de un predi long C (init,condici
ado
onsistente en:
int count = 0;
for (inicio; condici
on; iteraci
on)
if (pred)
++count;
return count;
bool E1 (init,condici
on,next,pred) : Veri
a que el predi
ado se
umpla
253
return seen;
253
Como ejemplo,
onsideremos veri
ar la pertenen
ia ex
lusiva de un elemento espe
o a una lista enlazada:
hpertenen
ia a lista enlazada 253i
E1(typename DynDlist::iterator<int> it(l), it.has_current(), it.next(),
it.get_current() == x);
Uses DynDlist 113a, get current 103, and has current 103.
En este punto es menester se~nalar que GNU nana posee los mismos tipos de
uanti
adores
se~nalados para
ontenedores de la bibliote
a estandar C++.
GNU nana es una bibliote
a mu
ho mas ri
a, tanto en espe
i
a
ion de invariantes,
omo en otras fun
ionalidades. Lease detenidamente el manual [30 para mas informa
ion.
Pruebas
El manejo de memoria dinami
a es una fuente de error tan
omun que mere
e
onsagrar
esta sub-se
ion a
lasi
ar y des
ribir las
lases de error.
254
enton
es,
ada vez que se invoque a array swap() se dejara apartado, \para siempre", un
arreglo tmp de n elementos. Si array swap() se invo
a a menudo, enton
es el programa
olapsara en po
o tiempo por falta de memoria.
La regla para evitar este error es que todo bloque de memoria debe liberarse inmediatamente despues de que este ya no se requiera.
Acceso fuera de bloque
Menos fre
uente que una fuga de memoria, tambien
omun y, a menudo, mas grave,
un a
eso fuera de bloque
onsiste en la le
tura o es
ritura de una zona de memoria que
no se ha apartado. El
aso mas tpi
o es es
ribir justo despues del n de un bloque de
memoria reservado
on malloc() o new. Por ejemplo:
int * tmp = new int [n];
for (int i = 0; i <= n; ++i)
tmp[i] = a[i];
24 Por
el
elebre fsi
o aleman Werner Heisenberg quien des
ubrio el prin
ipio homonimo a
er
a de la
imposibilidad de observar el estado real de una part
ula atomi
a, pues la misma observa
ion
ambia el
estado.
255
haya sido retornada por el manejador de memoria es, pues, un serio error de programa
ion
que
ompromete el estado de la apli
a
ion y
uyas
onse
uen
ias son in
iertas y, por lo
general, permane
en indete
tables durante algun tiempo.
Entregar una dire
ion invalida al manejador de memoria es un error mas
omun de
lo que pudiera pensarse. Quiza el
aso mas fre
uente o
urre
on punteros sobre
lases
derivadas y algunas transforma
iones resultantes del \
asting". Otra posibilidad su
ede
uando se manejan multi-estru
turas; por ejemplo, una multi-lista.
Verificadores din
amicos de memoria
Los errores de memoria fueron un perenne dolor de
abeza en el pasado hasta que
apare
ieron programas, en su mayora en forma de bibliote
as, para dete
tarlos; siendo
los mas populares entre ellos electric~fence [1 y dmalloc [41. Con este esquema se
requiere ayuda del prepro
esador, la
ual no esta
ompletamente disponible en C++ y el
en
adenamiento de una bibliote
a, la
ual inter
epta las llamadas a malloc() y free().
Un nuevo y versatil programa de veri
a
ion ha apare
ido re
ientemente: valgrind [26,
27, el
ual no requiere bibliote
a sino que intera
tua sobre el eje
utable resultante.
Esen
ialmente, los me
anismos de dete
ion son los mismos. En primer lugar,
ada
dire
ion de bloque de memoria, junto
on su posi
ion exa
ta de reserva
ion, lnea y
nombre del ar
hivo, se anota en una tabla de manera tal que al nal del programa las
entradas en la tabla
orresponden a fugas de memoria. Del mismo modo, una entrega de
dire
ion invalida puede dete
tarse porque la dire
ion no se en
uentra en la tabla.
256
Cuando se soli
ita un bloque, se aparta un po
o mas de manera tal de
olo
ar \rejas",
las
uales son zonas
on valores espe
iales, a
ada lado del bloque, de po
a posibilidad de
es
ritura, y
uya pi
toriza
ion es
omo sigue:
ptr
A ve
es, un error no se dete
ta por los analizadores estati
os o dinami
os sino que se
ono
e su presen
ia porque el programa arroja resultados in
orre
tos. Su busqueda es un
arte que depende de la experien
ia, instinto e intui
ion del programador para en
ontrar
partes que ofrez
an pistas a
er
a del error. En este orden de ideas, una de las
lases
de programas mas utiles que existen son los depuradores, programas espe
ializados en
observar la eje
u
ion del programa y rela
ionarla al
odigo fuente.
Uno de los depuradores mas
elebres es el GNU gdb [37, junto
on algunos de sus
frontales (\front-ends"), tales
omo el ddd [46.
3.5
Eficacia y eficiencia
A estas alturas del dis
urso, nos debe ser
laro que la e
a
ia prima a la e
ien
ia. Al
nal de
uentas, >de que vale algo muy e
iente si no es
orre
to?. Pues bien, en algunos
problemas la e
a
ia puede
omprometer a la e
ien
ia y vi
eversa: si ha
e
orre
tamente, enton
es puede ser muy ine
iente y, posiblemente, inapli
able. Para aprehender
la
uestion, miremos un ejemplo magistral mostrativo de las vi
isitudes involu
radas en
la resolu
ion de un problema y los
ompromisos que a menudo se presentan entre
orre
titud (e
a
ia) y e
ien
ia. El dis
urso esta inspirado en Steven Skiena de su ex
elente
libro \The Algorithm Design Manual" [36.
Consideremos un
onjunto P = {p1, p2, p3, . . . , pn} de n puntos en el plano. Cada
punto se dene por sus
oordenadas
artesianas (x, y). El problema
onsiste, enton
es,
en en
ontrar un orden de visita entre todos los puntos de manera tal que la longitud del
re
orrido sea mnima.
Este problema es muy
omun en mu
hos
ontextos.
257
Hay varias maneras de abordar la solu
ion de este problema. Quiza la mas
andida sea
mediante la heursti
a del \ve
ino mas
er
ano": dado un punto pi, el proximo punto a
one
tar es el mas
er
ano a pi. Tal heursti
a
onlleva al algoritmo 3.1.
Figura 3.3: Una solu ion on la heursti a del ve ino mas er ano
1
-23
2
-6
3 4 5
-1 0
13
Con esta disposi
ion de puntos, el algoritmo 3.1, paradoji
amente, bus
a las se
uen
ias
mas largas y no las mas
ortas, las
uales son del siguiente modo:
258
-23
-6
-1 0
13
Consideremos otra heursti
a mas
ompleja: \el par de puntos mas
er
ano", la
ual
trabaja
on los puntos extremos de
aminos par
iales. Durante la eje
u
ion del algoritmo,
se tendra un
onjunto de
aminos par
iales. El progreso del algoritmo
onsiste en determinar y
one
tar el par de puntos extremos mas
er
anos de modo tal que la
onexion sea
un
amino y no se forme un
i
lo. Esta heursti
a
ondu
e al algoritmo 3.2.
Algoritmo 3.2 (Camino m
as corto seg
un la heurstica del par de puntos m
as cercano)
La entrada del algoritmo es el
onjunto P = {p1, p2, p3, . . . , pn}. La salida es una se
uen
ia S =< p1, p2, . . . , pn >.
El algoritmo utiliza un
onjunto S = {S1, S2, . . . , Sm} de
aminos par
iales.
Dado un par de
aminos par
iales p1 = (r, s), p2 = (t, v) la fun
ion dist(p1, p20
al
ula
la distan
ia mnima entre el par de puntos extremos de los
aminos p1 y p2. Del mismo
modo, la fun
ion
one
tar(p1, p2)
onstruye un
amino par
ial, produ
to de
one
tar p1
y p2, tal que la distan
ia de
onexion entre p1 y p2 es mnima.
1. distan ia =
2. S = P ; es de
ir, ini
ialmente, los
aminos par
iales estan
onformados por solo
puntos
3. Repita mientras |S| > 1
(a) (r, s), (t, v) S
i. Si dist((r, s), (t, v)) < distan
ia =
A. distan
ia = dist((r, s), (t, v))
B. p1 = (r, s)
C. p2 = (t, v)
Al
ulminar esta se
ion, p1 y p2 son los
aminos par
iales mas proximos.
(b) S = S p1 p2
(
) p =
one
tar(p1, p2)
(d) S = S {p}
4. Cone
te los puntos extremos de la se
uen
ia resultante en S . Esto
onforma el
i
lo
nal.
El algoritmo 3.2 trabaja
orre
tamente para todas disposi
iones de puntos que hemos
presentado. Para el ejemplo anterior, el algoritmo
omenzara
onstruyendo los
aminos
par
iales (3, 4), (3, 4, 5), (3, 4, 5, 6), (2, 3, 4, 5, 6), (2, 3, 4, 5, 6, 7) y (1, 2, 3, 4, 5, 6, 7).
El algoritmo 3.2 es mas
ompli
ado y menos e
iente que el 3.1, pero aun podemos
de
ir que aun es e
iente, pues solo se
onsideran los puntos extremos de
aminos par
iales;
los puntos in
luidos en el medio de
aminos par
iales jamas vuelven a
onsiderarse.
Empero, la heursti
a del par de puntos mas
er
ano tampo
o es
orre
ta. Consideremos la siguiente disposi
ion de puntos:
259
1+d
1d
1+d
Esta disposi
ion solo tiene dos tipos de distan
ias 1 d y 1 + d. Una posible evolu
ion del
onjunto S segun el algoritmo 3.2 sera mas o menos la siguiente:
S
{(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)}
{(1, 2), (3, 3), (4, 4), (5, 5), (6, 6)}
{(1, 2, 4, 3, 5, 6)
;}
p
uya distan
ia total es 3(1 d) + 2(1 + d) + (2(1 + d))2 + (1 d)2. Esta distan
ia es
mayor que la mnima: 4(1 + d) + 2(1 d),
ual es el permetro del re
tangulo
onformado
por los puntos. El algoritmo 3.2 no es, pues,
orre
to.
La uni
a solu
ion
orre
ta
ono
ida esta dada por el siguiente algoritmo:
Algoritmo 3.3 (Camino m
as corto entre un conjunto de puntos) La entrada del
algoritmo es el
onjunto P = {p1, p2, p3, . . . , pn}. La salida es una se
uen
ia S =<
p1, p2, . . . , pn >.
El algoritmo requiere
onstruir un
onjunto = {1, 2, . . . , m}
onformado por
todas las permuta
iones posibles de los puntos de P .
1. distan
ia =
2. resultado =
260
El numero de explora
iones que realiza el algoritmo 3.3 es igual a la
antidad de permuta
iones que existen entre los n puntos; o sea, O(2n), una e
ien
ia intratable aun para
po
os puntos. A ve
es, la
orre
titud y la e
ien
ia representan
ontradi
iones insalvables.
Aunque Skiena presenta este problema
omo un
ompromiso entre
orre
titud, e
ien
ia y fa
ilidad de programa
ion [36, lo
ual en
ierta medida es
orre
to, dis
reparemos
un po
o y plantearemos nuestros argumentos bajo las siguientes observa
iones:
1. La instan
ia del problema del vendedor viajero que estamos tratando es general en el
sentido de que se trata de resolverlo para todos lo
asos posibles. Sin embargo, en la
vida real,
asi siempre se dispone de mas informa
ion a
er
a de las
lases de entrada.
Por ejemplo, si se requiriese resolver el problema para plani
ar soldaduras sobre
una oblea ele
troni
a, enton
es se sabe que las
ongura
iones de puntos
onforman
polgonos monotonos y que para estos la heursti
a del ve
ino mas
er
ano arroja
solu
iones
orre
tas.
2. En otras o
asiones, la diferen
ia entre la solu
ion optima y una buena en
ontrada
heursti
amente no es muy notable. En estos
asos, el tiempo de desarrollo y eje
u
ion
de un algoritmo bueno puede ser menor que el de uno optimo.
En algoritmos y sistemas, as
omo en otros dominios de la ingeniera, se pronun
ia
un proverbio que reza que \lo mejor es enemigo de lo bueno".
De lo anterior se desprende que la le
ion sigue siendo que la e
a
ia prima a la
e
ien
ia.
3.5.1
En 1906 el ingeniero fran
o-italiano Vilfredo Pareto se~nalo que en Italia el 80% de la
riqueza se distribua \muy injustamente" entre el 20% de la pobla
ion. Su observa
ion fue
orroborada en algunas otras latitudes y
ontextos bajo \el prin
ipio de Pareto" .
En algunos
ontextos en los
uales se habla de e
ien
ia, el prin
ipio de Pareto ha
sido apli
ado para
ara
terizar una rela
ion entre re
ursos de
onsumo o produ
ion y los
agentes
onsumidores o produ
tores. Hay indi
a
iones empri
as de que el prin
ipio apli
a
a los sistemas
omputa
ionales. En efe
to, se ha observado, entre otras
osas, que:
25
1. El 80% de la memoria
onsumida por un programa es usada por el 20% del programa.
2. El 80% del tiempo de eje
u
ion es
onsumido por el 20% del programa.
3. El 80% de los a
esos a dis
o es realizado por el 20% del programa.
4. El 80% del esfuerzo de desarrollo de un sistema es realizado sobre el 20% del mismo.
En programa
ion, a este patron se le denomina la \regla del 80-20". La regla ha sido
solidamente
omprobada a lo largo de la historia de la programa
ion y a traves de diversos
sistemas: operativos, de bases de datos, y variadas apli
a
iones.
25 Desgra
iadamente
para algunos pueblos, el prin
ipio de Pareto ha tenido una apli
a
ion e
onomi
a, o,
mejor di
ho, e
onomi
ista, que ha permitido sugerirlo mas
omo un \prin
ipio natural" de toda e
onoma,
que
omo una eviden
ia de ine
ien
ia o injusti
ia so
ial.
Notese, sin embargo, que en lo que ata~ne al ser ser humano, las reglas e
onomi
as son
ulturales, no
naturales. Si tiene duda al respe
to, preguntese, por ejemplo, >es el dinero natural? >Es la no
ion o idea
de dinero un
ono
imiento innato, embebido en nuestros genes, desde antes de nuestro na
imiento?
261
La regla 80-20 no es una simple razon numeri
a o propor
ion. Se trata del valiossimo
ono
imiento que representa el saber que la e
ien
ia de un programa esta determinada
por una parte
orrespondiente al 20%. Por tanto,
uando se desea mejorar un programa;
o sea, ha
erlo mas e
iente, debemos ha
erlo sobre aquel 20% y no perdernos en mejorar,
futilmente, el restante 80%.
3.5.2
Cu
ando atacar la eficiencia?
Lo anterior refuerza la arma
ion de que la e
a
ia prima a la e
ien
ia, pues hasta que no
logremos el efe
to, no podemos identi
ar ese 20% del programa sobre el
ual deberamos
de
on
entrarnos si deseasemos mejorar la e
ien
ia. Mas aun, si elaboramos programas
e
a
es que satisfa
en las expe
tativas, >vale la pena mejorar su e
ien
ia? Para responder
esto debemos mejor preguntarnos: >si tenemos un programa e
az (
orre
to),
uando es
que hay que ata
ar la e
ien
ia? Hay varias
ondi
iones, siendo las mas
omunes las
siguientes:
El programa resultante no a
omete la solu
i
on en una dura
ion a
eptable para su
apli
a
ion: dise~namos un programa, lo
odi
amos,
omprobamos que es
orre
to,
pero
uando lo eje
utamos nos per
atamos de que su dura
ion de eje
u
ion no
umple
las expe
tativas.
Cambio en las ondi iones ambientales de uso del programa: tenemos un programa
orre
to que
umple las expe
tativas. Un da a
onte
e un
ambio, por ejemplo, se
aumenta la es
ala de entrada y el programa deja de ser e
iente.
programa
orre
to y e
iente y se nos plantea reusarlo
omo parte de otro programa.
Sin embargo, el
omponente en
uestion no tiene la su
iente e
ien
ia para asegurar
la e
ien
ia global del programa.
Si un programa dado en
aja en alguna de estas
ondi
iones, enton
es puede adverarse
la ne
esidad de ha
erlo mas e
iente. Si, al
ontrario, el programa satisfa
e todas las
expe
tativas, enton
es es preferible evitar el \efe
to del segundo sistema" [4, el
ual,
parafraseando a Brooks: \... is the most dangerous system a man ever designs" [4.
3.5.3
Una vez adverado que requerimos ha
er mas e
iente un programa, tenemos varias alternativas,
ombinables entre s, para mejorar la e
ien
ia; entre las mas usuales tenemos:
1. Identi
a
ion y mejoramiento del 20%:
uenta habida de la regla de Pareto, eso tiene
mu
ho sentido, pues ello bene
iara al 80% del programa.
2. Cambio de algoritmo: a ve
es, sobre todo debido a la es
ala de la entrada, es el propio
algoritmo la fuente de ine
ien
ia. En este
aso puede adverarse ne
esario dise~nar
un algoritmo mas e
iente
omputa
ionalmente. Por ejemplo, tal vez un pro
eso
rti
o de un sistema use algun metodo sen
illo de ordenamiento
uya
omplejidad
es O(n2); en este
aso, el metodo podra substituirse por alguno O(n lg n), el
ual
es
onsiderablemente mas e
iente.
262
3. Cambio en la a
titud de programa
ion: otra posibilidad de ine
ien
ia puede deberse
a una a
titud por parte de los programadores que introduz
a ine
ien
ia. A menudo,
esto o
urre entre programadores noveles.
Un ejemplo notable y real lo
onstituye el pase de parametros por valor en C++.
En efe
to, en este lenguaje, si no se tiene el
uidado ade
uado, el
oste del pase de
parametros por valor puede ser altsimo, pues
ada invo
a
ion a fun
ion requiere una
opia de
ada objeto parametro, la
ual, segun la ndole del objeto, puede
onsumir
bastante tiempo.
4. Cambio ambiental adrede: ha
e unos 20 a~nos,
uando un programador quera lograr
algunos efe
tos visuales espe
iales, aumentaba expl
itamente la velo
idad de reloj
y usaba una bolsa de hielo sobre el pro
esador para atenuar el
alor produ
ido.
Aquella limitada y efmera te
ni
a permita prede
ir que pro
esadores mas velo
es
-o adapta
iones fsi
as- daran solu
ion al problema.
Un problema de e
ien
ia puede ata
arse a \fuerza bruta" aumentando el poder
omputa
ional. Aparte de
ambios fsi
os, del estilo des
rito en el parrafo anterior,
hay varios mas loables. En primer lugar, puede substituirse el
ompilador por uno
que efe
tue optimiza
iones agresivas espe
iales. En segundo lugar, puede adquirirse
hardware espe
ializado o, simplemente, mas potente; velo
idad de pro
esador,
antidad de memoria,
antidad de dis
o, te
nologa de alma
enamiento, numero de
pro
esadores, et
etera.
Esta es, grosso modo, la
lasi
a
ion de las maneras objetivas en que se puede mejorar la
e
ien
ia. Lo demas, y lo que vendra, es de ndole subjetiva y parte del
orpus de estudio
de este texto.
3.5.4
Perfilaje (profiling)
Dado un programa del
ual se desea estudiar su e
ien
ia, >
uales son las partes del
programa que representan aquel 20%
rti
o? Aparte del
ono
imiento del programador,
en la determina
ion de esta respuesta, puede usarse una
lase espe
ial de programa llamado
\perlador" (\proler").
De manera elemental, un proler es un programa que,
o-asistido por el
ompilador,
muestrea la eje
u
ion de un programa y re
aba estadsti
as sobre la posi
ion de eje
u
ion
del programa. Por lo general, un proler ofre
e dos gra
os fundamentales:
1. El perl plano: el
ual muestra la dura
ion
onsumida por
ada rutina y la
antidad
de ve
es que fue invo
ada.
2. El grafo de llamadas: el
ual muestra, para
ada fun
ion,
uales fueron las fun
iones
que la llamaron y
uantas ve
es esto o
urrio.
Siendo este enfoque de ndole estadsti
a, la pre
ision del proler depende de la dura
ion
total del programa: a mayor dura
ion, mayor pre
ision. A pesar de este tipo de impre
ision,
este enfoque tiene la enorme ventaja de que no impa
ta demasiado sobre la dura
ion
permitiendole eje
utarse a velo
idades
er
anas al tiempo real.
El prin
ipal exponente del proler estadsti
o es GNU gprof. Hay varios front-ends
gra
os que versatilizan la visualiza
ion de los perles arrojados por GNU gprof; siendo el
mas notable de ellos kprof. Valgrind tambien posee un ex
elente proler estadsti
o.
263
Otro enfoque de perlaje, mas determinista,
onsiste en
olo
ar
ontadores dire
tos en
ada rutina y medidores de tiempo. El enfoque es mu
ho mas pre
iso que el estadsti
o
pero tiene el problema de que des-a
elera fuertemente el tiempo de eje
u
ion ha
iendolo
inapli
able para programas
on requerimientos de tiempo real. Un prototipo libre de esta
lase de proler es FunctionCheck [29.
3.5.5
Localidad de referencia
Por lo general, el a
eso a los datos por parte de un programa exhibe un patron repetitivo
denominado \lo
alidad de referen
ia". Cuando se a
ede a un dato, existe una alta probabilidad de que este sea a
edido de nuevo en un tiempo proximo. A este tipo de lo
alidad
de referen
ia se le
ali
a de \temporal". Un muy simple ejemplo es la instan
ia
ion de
una variable, la
ual, sobre todo si se siguen buenas
ostumbres de programa
ion, debera
a
ederse para le
tura o es
ritura en un tiempo muy proximo.
Otro patron
onsiste en a
eder a un dato
er
ano en memoria de otro re
ientemente
a
edido. A este
lase de lo
alidad de referen
ia se le
ali
a de \espa
ial" y, un buen
ejemplo, lo
onstituye el a
eso a ve
tores y matri
es.
La lo
alidad de referen
ia, aunado al
ono
imiento de la regla del 80-20, intuye un
me
anismo muy utilizado para mejorar el rendimiento: denominado \
a
he", verbo fran
es
que signi
a \es
onder" y que alegoriza la transparen
ia del me
anismo. Un
a
he es un
onjunto abstra
to, nito, sustentado en una estru
tura de datos, que se antepone a un
onjunto de datos mu
ho mayor y
uyo a
eso es notablemente mas rapido que el
onjunto
de datos. Cuando se a
ede por vez primera a un elemento del
onjunto, este se guarda en
el
a
he. Los siguientes a
esos se realizan sobre el
a
he, que es mu
ho mas rapido. Puesto
que el
a
he es nito, posiblemente este devenga lleno. En este
aso, se debe sele
ionar
una entrada del
a
he para eliminar de manera que pueda substituirse por la nueva. Por
lo general, se elimina la entrada que tenga mas tiempo sin usarse.
Ca
hes son tpi
amente usados en las plataformas de hardware para paliar la diferen
ia
de desempe~no que existe entre el CPU y la memoria. Por la misma razon los sistemas
operativos los usan para paliar el desfase entre la memoria y el dis
o. En ambas situa
iones,
suele guardarse en el
a
he, no solo el dato re
ien a
edido, el
ual
ubre la lo
alidad de
referen
ia temporal, sino, tambien, los datos adya
entes, de manera que tambien se
ubra
la lo
alidad espa
ial.
En x 5.3.3 estudiaremos un enfoque general de
a
he para usarse sobre
onjuntos en
memoria.
3.5.6
Tiempo de desarrollo
Los amantes de los grandiosos y modernos lenguajes de \s
ripting" tales
omo Perl,
Python y Ruby, los
uales fa
ilitan mu
hsimo el desarrollo rapido de prototipos y apli
a
iones, tienen el siguiente proverbio: \el tiempo humano es mas importante que el tiempo de CPU"
Con esto expresan un elemento de e
a
ia y e
ien
ia que a menudo es des
uidado e, in
lusive,
ompletamente despre
iado: el tiempo de desarrollo de un programa.
Modelos y prototipos son fundamentales en ingeniera, pero por una extra~na razon
po
os ingenieros de software suelen usarlos
uando se en
uentran ante dise~nos de software
26
26 Es u hado
por primera vez por este reda tor de la palabra de Fran is o Palm.
264
novedoso o que no tienen experien
ia en realizarlo. En todo
aso, pare
e absurdo elaborar
programas
uyo tiempo de desarrollo sea mu
ho mayor que la vida del programa en eje
u
ion o, in
lusive, lo
ual es pateti
amente mas
omun, que no sean e
ientes o, mu
ho
peor, ine
a
es.
Segun las
onsidera
iones anteriores, son re
omendables las siguientes ultimas re
omenda
iones tendientes a a
elerar el tiempo de desarrollo:
1. Prime la simpli
idad de dise~no por el desempe~no, pues,
omo reiteradamente lo
hemos se~nalado, la e
a
ia prima a la e
ien
ia. Por tanto, piense en optimiza
ion
solo si se ha asegurado el n y se requiere mas e
ien
ia.
2. Invierta todo el tiempo ne
esario en garantizar
orre
titud de los dise~nos antes de
pro
eder a la
odi
a
ion.
En virtud de esta re
omenda
ion, use modelos y verifquelos exhaustivamente. Si
existen espe
i
a
iones de e
ien
ia, enton
es verifquelas en los modelos.
\Prototipee" rapidamente prototipos de los modelos ya veri
ados y
onvaldelos
on
las
ondi
iones esperadas de eje
u
ion. En este sentido, es perfe
tamente plausible
usar lenguajes que permitan desarrollar efe
tivamente prototipos e, in
lusive, si la
e
ien
ia es satisfa
toria, basar el desarrollo denitivo en el lenguaje de prototipeado.
3.6
Notas bibliogr
aficas
El ordenamiento,
omo obrar del hombre, se en
uentra en toda
ultura y epo
a humana.
Pudiera de
irse que el metodo de sele
ion es de \autora humana\, pues no se en
uentra
el nombre de algun individuo que se atribuya para s mismo su autora.
Los historiadores de la
omputa
ion atribuyen el ordenamiento por mez
la a
John von Neumann; una de las mentes mas geniales del siglo XX. El metodo de inser
ion
se le atribuye a Konrad Zuse, a quien tambien se le atribuye el des
ubrimiento del
omputador digital .
El qui
ksort fue des
ubierto por Charles Anthony Ri
hard Hoare en 1961 [9. Desde
enton
es hasta el presente, el metodo ha sido intensivamente estudiado. Knuth [19 ofre
e
un muy detallado estudio as
omo un ex
elente re
uento histori
o del qui
ksort y demas
metodos de ordenamiento.
Robert Sedgewi
k, bajo la tutora de Knuth,
onsagro su tesis do
toral al estudio del
qui
ksort. Como tal, Sedgewi
k [35 es una ex
elente referen
ia para el analisis del metodo
y sus variantes apli
ativas.
La nota
ion O, fundamento esen
ial del analisis de algoritmos, fue originalmente
on
ebida por el matemati
o aleman Paul Ba
hmann en 1894 [3.
Segun Tarjan [38, el analisis amortizado fue desarrollado por M. R. Brown,
Robert E. Tarjan, S. Huddleston y K. Mehlhorn [38. En su art
ulo, Tarjan atribuye el
des
ubrimiento del metodo poten
ial a Daniel D. Sleator mientras que el
ali
ativo \amortizado" se le atribuye a Tarjan y Sleator [38.
La idea de veri
a
ion de
orre
titud basada en invariantes ha sido popular desde el
mismo ini
io de la programa
ion. Sin embargo, al respe
to vale la pena desta
ar el trabajo
de David Rosenblum [33, 32.
27
27 Zuse
on
ibi
o su
omputador en la epo
a de la Alemania nazi. Quiza por ello, el no ha sido muy
men
ionado por la historia.
3.7. Ejercicios
265
La espe
i
a
ion y veri
a
ion automati
a de programas fue ini
iada a nales de los
a~nos 60; pero no fue hasta nal de la de
ada del 70 que se tuvo
laro,
on los trabajos
de Zaropulo et al [45, que el futuro de la veri
a
ion de sistemas se hara formal y
automati
amente.
Si usted deviene un programador, enton
es, quiza, algun da emprendera la dire
ion
de una obra
ompleja en un sistema
omputa
ional que requiera la parti
ipa
ion de mu
hos
programadores. En ese enton
es, requerira dominar las buenas
ostumbres y normalizarlas
entre los programadores. En ese momento debera
ono
er muy bien a
er
a de la Ingeniera
del Software. En el nterin, primero domine el ser programador y
omien
e por leer el
primer libro, en tiempo y en ex
elen
ia, de Ingeniera del Software: \The Mythi
al ManMonth: Essays on Software Engineering" de Fredri
k P. Brooks [4.
El
ampo sobre dis
iplina y buenas
ostumbres de programa
ion tendientes a produ
ir
programas
orre
tos es muy vasto. No hay, pues, su
iente espa
io en este texto para
dis
ernir al respe
to. Sin embargo, ante la posibilidad de fragmenta
ion del le
tor ante
la extensa variedad de fuentes, es esen
ial responsabilidad en esta sub-se
ion men
ionar
expl
itamente los prin
ipales textos de referen
ia:
1. \Writing Solid Code" [21 de Steve Maguire versa sobre
omo es
ribir programas
que no
ontengan errores. Aunque parez
a muy osado, Maguire ense~na una dis
iplina
que fuerza
odi
ar programas
orre
tos. Para resumir, Maguire labra un
amino en
torno a las tres preguntas de Van Cle
k.
2. \The Pra
ti
e of Programming" [18 de Kernighan y Pike es un libro sobre las
buenas
ostumbres de programa
ion, es
rito
on mu
ha autoridad, pues sus autores
son pra
ti
antes involu
rados en ex
elsas obras de software.
Finalmente, \Code Complete" [23 de Steve M
Connell es un tratado en
i
lopedi
o
sobre buenas
ostumbres en programa
ion.
3.7
Ejercicios
5. Dise~ne un algoritmo generi
o, que se sirva del patron generi
o Iterator, que ordene
por sele
ion una se
uen
ia. Dise~ne el algoritmo de forma tal que sea lo mas e
iente
posible en arreglos y listas doblemente enlazadas.
6. La mez
la de dos se
uen
ias ordenadas usada en el metodo de ordenamiento por
mez
la requiere
opiar todos los elementos de los arreglos a mez
lar en un arreglo
auxiliar. Dise~ne un algoritmo que disminuya el espa
io adi
ional a la mitad.
266
3.7. Ejercicios
267
(b)
void g(int n)
{
for (int j = 2n; j < lg n; j /= 2)
i++;
}
(
)
void h(int n)
{
for (int x = 1; x < n; x += 2)
printf("impar: %d", x);
}
(d)
void f(int N)
{
int n = 1;
do
{
n *= 2;
for (z = 0; z < n; z++)
printf("%d\n", z);
}
while (n < N);
}
10. Cal
ule el tiempo de eje
u
ion del algoritmo de multipli
a
ion de polinomios desarrollado en la sub-se
ion 120.
11. Elimine la re
ursion
ola del metodo quicksort rec().
12. Reali
e un estudio estadsti
o a
er
a de los tama~nos de parti
iones a partir de las
uales el ordenamiento por inser
ion es mas rapido que el qui
ksort.
13. Implante el ordenamiento por sele
ion
on listas simplemente enlazadas del tipo
Snode<T>.
14. Implante el ordenamiento por inser
ion
on listas simplemente enlazadas del tipo
Snode<T>.
15. Implante el ordenamiento por mez
la
on listas simplemente enlazadas del tipo
Snode<T>.
16. Implante un qui
ksort iterativo que ordene listas simplemente enlazadas del tipo
Snode<T>.
268
17. Implante el qui
ksort
on listas simplemente enlazadas del tipo Snode<T>.
18. Es
riba un algoritmo que parti
ione una lista simplemente enlazada
ir
ular
on
nodo
abe
era del tipo Snode<T>. El prototipo es el siguiente:
Snode<T> * partir(Snode<T> & list, Snode<T> & l1, Snode<T> & l2);
3.7. Bibliografa
269
31. Dise~ne e implante el esquema de triple parti
ionamiento expli
ado en x 3.2.2.8 para
manejar
laves repetidas.
32. Anali
e rigurosamente, para los
asos esperado y pero, el desempe~no de la primitiva
random search().
33. Es
riba el random search() para listas simplemente enlazadas de tipo Slink.
34. Anali
e rigurosamente, para los
asos esperado y pero, el desempe~no de la primitiva
random select().
35. Implante el random search()
on listas simples de tipo Snode<T>.
36. Implante el random select()
on listas simples de tipo Snode<T>.
37. Implante el
onjunto fundamental
on arreglos desordenados basado en las primitivas random search() y random select().
38. Implante el
onjunto fundamental
on listas simples de tipo Snode<T> basado en
las primitivas random search() y random select().
39. Implante el
onjunto fundamental
on listas simples de tipo Dnode<T> basado en
las primitivas random search() y random select().
40. Estudie la e
ien
ia del algoritmo 3.2.
41. Extienda GNU nana para que maneje los
uanti
adores que veriquen
ontenedores
de la bibliote
a ALEPH.
42. Para todos los TAD dise~nados hasta el presente, verique la apli
a
ion de las reglas
de Holtzmann [11 presentadas en x 3.4.3.1.
Bibliografa
[1 Ele
tri
fen
e. Available on: http://www.pf-lug.de/projekte/haya/efence.php.
[2 Flawnder. Available on: http://www.dwheeler.com/flawfinder/.
[3 Paul Ba
hmann. Die Analytis
he Zahlentheorie. Zahlentheorie, pt. 2. B. G. Teubner, Liepzig, 1894.
[4 Fredri
k P. Brooks. The Mythi
al Man-Month: Essays on Software Engineering,
20th Anniversary Edition. Addison Wesley, Reading, Mass., se
ond edition, 1995.
[5 J. Bu
hi. Weak se
ond-order logi
and nite automata. Z. Math. Logik Grundlagen
Math., 5:66{92, 1960.
[6 F. J. Corbato, J. H. Saltzer, and C. T. Clingen. Multi
s { the rst seven years. In
Spring Joint Computer Conferen
e, pages 571{583. AFIPS Press, May 1972.
[7 Dawson Engler, David Yu Chen, Seth Hallem, Andy Chou, and Benjamin Chelf. Bugs
as deviant behavior: A general approa
h to inferring errors in systems
ode, August 09
2001.
270
[8 Dawson R. Engler, Benjamin Chelf, Andy Chou, and Seth Hallem. Che
king system
rules using system-spe
i
, programmer-written
ompiler extensions. In Pro
eedings
of the 7th Symposium on Operating System Design and Implementation, pages
1{16, 2000.
[9 C. A. R. Hoare. Algorithms 64: Qui
ksort. Communi
ations of the ACM, 4(7):321,
1961.
[10 Per Holager, Jean-Mar
Jezequel, and Bertrand Meyer. Letters: Pla
ing the blame
for Ariane 5. Computer, 30(5):6, 8, May 1997.
[11 Holtzmann. The power of 10: Rules for developing safety-
riti
al
ode. COMPUTER:
IEEE Computer, 39, 2006.
[12 G. J. Holzmann. The Spin Model Che
ker, Primer and Referen
e Manual.
Addison-Wesley, Reading, Massa
husetts, 2003.
[13 Gerard J. Holzmann. Design and Validation of Computer Proto
ols. Prenti
e Hall,
Englewood Clis, NJ, 1991.
[14 Gerard J. Holzmann. The model
he
ker spin. IEEE Transa
tions on Software
Engineering, 23(5):279{295, May 1997.
[15 Gerard J. Holzmann. An analysis of bitstate hashing. Formal Methods in System
Design, 13(3):289{307, 1998.
[16 Jean-Mar
Jezequel and Bertrand Meyer. Obje
t te
hnology: Design by
ontra
t: The
lessons of Ariane. Computer, 30(1):129{130, January 1997. See also letters [10.
[17 S. C. Johnson. LINT : A C program
he
ker. In UNIX Programmer Manual. BELL
Labs., 7th edition, 1979.
[18 Brian W. Kernighan and Rob Pike. The Pra
ti
e of Programming. Addison-Wesley,
Reading, MA, 1999.
[19 Donald E. Knuth. Sorting and Sear
hing, volume 3 of The Art of Computer
Programming. Addison-Wesley, Reading, MA, USA, se
ond edition, 1998.
[20 Alasdair Ma
Intyre, editor. After Virtue. University of Notre Dame Press, 1984.
[21 Steve Maguire. Writing solid
ode: Mi
rosoft's te
hniques for developing bug-free
C programs. MICROSOFT, 1993.
[22 Z. Manna and A. Pnueli. Spe
i
ation and veri
ation of
on
urrent programs by
V-automata. In Conferen
e re
ord of the 14th ACM Symposium on Prin
iples of
Programming Languages (POPL), pages 1{12, 1987.
[23 Steve M
Connell. Code Complete. Mi
rosoft Press, Redmond, WA., 1993.
[24 Bertrand Meyer. Design by
ontra
t,
omponents and debugging. JOOP, 11(8):75{
79, 1999.
[25 G. J. Myers. The Art of Software Testing. John Wiley & Sons, 1978.
3.7. Bibliografa
271
[26 Ni
holas Nether
ote and Julian Seward. Valgrind: A program supervision framework.
Ele
tr. Notes Theor. Comput. S
i, 89(2), 2003.
[27 Ni
holas Nether
ote and Julian Seward. Valgrind: A program supervision framework.
Ele
tr. Notes Theor. Comput. S
i, 89(2), 2003.
[28 A Managerial View of the Multi
s System Development. F. J.
orbato and C. T.
lingen. In Conferen
e on Resear
h Dire
tions in Software Te
hnology, Providen
e,
Rhode Island, O
tober 1977.
272
[43 Pierre Wolper and Denis Leroy. Reliable hashing without
ollosion dete
tion. In
Costas Cour
oubetis, editor, Computer Aided Veri
ation, 5th International Conferen
e, CAV '93, Elounda, Gree
e, June 28 - July 1, 1993, Pro
eedings, volume
697 of Le
ture Notes in Computer S
ien
e, pages 59{70. Springer, 1993.
[44 Junfeng Yang, Can Sar, and Dawson Engler. Xplode: a lightweight, general system
for nding serious storage system errors. In Pro
eedings of the 7th Symposium on
Operating System Design and Implementation, 2006.
[45 P. Zaropulo, C. West, H. Rudin, D. Cowan, and D. Brand. Towards analysing
and synthesizing proto
ols. IEEE Transa
tions on Communi
ation, Comm28(4):6512013661, 1980.
[46 Andreas Zeller and Dorothea Lutkehaus. DDD - A free graphi
al front-end for UNIX
debuggers. SIGPLAN Noti
es, 31(1):22{27, 1996.
Captulo 4
Arboles
Aunque a
tualmente
on tenden
ia al olvido, desde ha
e mu
ho tiempo, a eso que le
brinda identidad y sentido a una
osa se le llama \esen
ia" o \quid". Desde y durante
edades inmemoriales, an
estrales y lejanas, se
ree que el quid de algo se estudia a partir
de su
ontexto de origen. Lo que ahora vulgarmente llamamos futuro, an
estralmente se
le denominaba destino; o sea, ha
ia donde se va. Cuando se quera saber que era (desde
donde viene) y que devendra (ha
ia donde va) una
osa, se estudiaba y se
riti
aba su
\genealoga"; es de
ir, su origen y devenir a partir de su genesis.
El termino \genealoga", que
ontiene a \genus" , proviene del latn genealogia, el
ual proviene a su vez del griego y signi
a el estudio del
amino desde
origen (genesis) hasta el presente. Hoy en da la genealoga tiende a estudiarse
omo una
se
uen
ia de eventos respe
to a un movimiento externo y ajeno [a los eventos llamado
tiempo. A este tipo de estudio se le llama, por lo general, \Historia".
Hubo epo
as, sin embargo, en que algunos genealogistas sentan que el quid de una
osa
no se remita a una mera y uni
a se
uen
ia de eventos, sino a diversas y variables se
uen
ias
que
onvergan en el presente y proseguan ha
ia un destino. Aquellos genealogistas se
per
ataron de que
onvivan
on testigos que haban a
ompa~nado a sus as
endientes y que
les sobreviviran a ellos y sus des
endientes. As mismo, se fas
inaron por el he
ho de que las
propias formas de aquellos testigos indi
iaban su devenir y edad. Al genero de aquel testigo
hoy se le mienta bajo el nombre de \arbol" y, a la ex
ep
ion de la
ultura te
nologi
a de
esta epo
a, en las restantes el arbol ha
ausado tanto asombro, a traves de las edades y
latitudes, que sido al extremo sujeto de venera
ion.
>Por que asombraba? Aparte de su monumentalidad y longevidad, haban varias razones. Una de las prin
ipales, de gran interes para nuestro
ontexto, era su estru
tura
generi
a, as
omo su evolu
ion. Un arbol
omienza en el mundo
omo una semillita que
lentamente enraiza y
re
e mirando y bus
ando la luz en una trama jerarqui
a que traza su
edad, su genealoga y que transige imaginar su destino. Aunque por lo general a un arbol
lo miramos
omo a un arbol; es de
ir,
omo un todo, en o
asiones miramos sus partes:
raz, tron
o, ramas, hojas,
ores, .... Mientras mas
er
a se esta de la raz, mas antigua es
la region del arbol que miramos; analogamente, mientras mas alta o amplia esta es, mas
joven es la parte respe
to a otras inferiores o mas internas. Este patron estru
tural, que no
se
orresponde
on una mera se
uen
ia, apare
e en una numersima variedad de
ontextos
de nuestra vida de los
uales no es
apa la
omputa
ion.
1
1 V
ease y re
uerdese x 1.2.3.4.
2 O quiz
a, mejor di
ho, de la \anti-
ultura".
273
Captulo 4. Arboles
274
A los a
tos y he
hos humanos les llamamos \gestos", pues estos \gestan". Ser humano
solo tiene sentido
on los otros seres humanos. Mas humanos somos en la medida en que
mas nos hayamos rela
ionado
on los otros. De alguna manera podramos de
ir que
omo
humanos nos distinguimos entre nosotros porque los gestos que re
ibimos de y gestamos
para el otro son de alguna manera diferentes. Un antiguo y hermoso proverbio hindu
reza: \un gesto genera una a
titud, una a
titud genera un
ara
ter y un
ara
ter
genera un destino". Si genealizamos nuestra identidad, o la de alguien otro, podemos
pensar en el
ara
ter y ha
er memoria en las a
titudes y gestos que
ondujeron aquellas
a
titudes. Indagando en un
ara
ter tenemos idea a
er
a de
omo se proye
ta el destino.
No en balde Hera
lito, a quien debe llamarsele \El Luminoso" y no el os
uro , de
a que
\el
ara
ter de un hombre es su destino". Como un hipoteti
o y muy redu
ido ejemplo,
onsideremos la risa, la
ual, en
ierta forma, gesta alegra y que, a su vez, gesta un
ara
ter amistoso. La amistad,
uando se
ir
uns
ribe dentro de una autenti
a no
ion
de bien, gesta un destino enmar
ado en la buena vida subya
ente a la idea de bien que
en amistad se
omparta. La vida humana esta plena de mu
hos mas gestos, la sonrisa,
el llanto, el grito, el asombro, ...; as
omo de sus
onse
uentes a
titudes, la pi
arda, la
tristeza, el enojo, la
uriosidad, .... Cara
teres emergen de aquellas a
titudes; el amistoso,
el astuto, el deprimido, el des
onado, el perseverante, .... Dependiendo del devenir y de la
suerte, esos
ara
teres
onjugados pueden destinar en un padre, un polti
o, un poeta, un
guerrero, un losofo, .... Todo este dis
urso puede sintetizarse bajo el siguiente diagrama:
3
risa
sonrisa
llanto
grito
asombro
alegr
a
picard
a
tristeza
enojo
curiosidad
amistoso
astuto
deprimido
desconfiado
perseverante
Padre
Pol
tico
Poeta
Guerrero
Fil
osofo
Destino
En los gestos, los nuestros y los del otro, se gestan nuestros destinos.
3 De genealoga.
4 Seg
un \Aletheia"
de Heidegger.
275
Bisabuela Bisabuelo
Abuelo paterno
Bisabuela Bisabuelo
Abuelo Materno
Bisabuela Bisabuelo
Abuelo materno
Pap
a
Bisabuela
Abuela materna
Mam
a
Hijo
Captulo 4. Arboles
276
Individuo
Primog
enito
Nieto 1
Bizn 1
Hijo 2
Nieto 2
Hijo 3
Nieto 3
Bizn 5
Bizn 6
Nieto 4
Bizn 7 Bizn 8
Bizn 9
biz-biz
4.1
Conceptos b
asicos
Definici
on 4.1 (Arbol)
Un arbol T se dene por un
onjunto N = {n1, n2, . . . , nn} de
T1 T2 Tm =
Bizn 10
4.1. Conceptos b
asicos
277
Captulo 4. Arboles
278
padre
0
si ni es una hoja
1 + max(h(hijo1(ni)), h(hijo2(ni)), . . . , h(hijom(ni))) de lo
ontrario
4.2. Representaciones de un
arbol
4.2
279
Representaciones de un
arbol
Tradi
ionalmente, estos arboles abstra
tos se dibujan al reves de los naturales,
omo en la
gura 4.3. Es de
ir, la raz en la parte superior y sus des
endientes en las partes inferiores.
Esta representa
ion es idonea en la mayora de las situa
iones, pero algunos problemas se
prestan para otras representa
iones mas
onvenientes.
4.2.1
Conjuntos anidados
B F L M
K S
C H
J
G
U V
P
Q
O
I
Secuencias parentizadas
Los
onjuntos anidados ignoran el orden de apari
ion de los subarboles, el
ual en algunas
situa
iones puede ser importante. Si se desea mantener este orden, un renamiento
onsiste
en representar los
onjuntos entre parentesis. En este
aso, la aso
iatividad representa el
orden de apari
ion de los subarboles. Para el arbol de las guras 4.3 y 4.4, se tiene la
siguiente representa
ion parentizada:
(A(B(F(L)(M))(G))(C(H(P(U)(V))(O)(Q)))(E(K(S)(T )))(D(J(R))(I)))
Esta representa
ion tiene la ventaja de que es lineal y re
eja
on exa
titud la topologa
del arbol.
Consideremos la evalua
ion automati
a de expresiones algebrai
as. Como ejemplo,
asumamos la expresion algebrai
a
3x4 + 3x3y2 + x2 1
.
4x2z
Un
ompilador podra tradu
ir esta expresion al arbol de la gura 4.5, el
ual debe
evaluarse de manera \as
endente"; o sea, desde las hojas hasta la raz. Este orden re
eja
Captulo 4. Arboles
280
rbol de la expresion inja
Figura 4.5: A
elmente las posibles y validas maneras en que la expresion puede evaluarse sin que se
pierda su sentido algebrai
o.
En matemati
a, la
omposi
ion de fun
iones suele es
ribirse de manera preja; es de
ir,
el nombre de la fun
ion u opera
ion \preja", \ante
ede", a los operandos. Por ejemplo,
f(x, y) representa una fun
ion f
on dos operandos. O
urre que la se
uen
ia parentizada de
un arbol de expresiones es preja. En la o
urren
ia, el arbol de la gura 4.5 se representa
omo:
(/(+(*(3) (^ (x)(4)) ) (*(3) (^ (x)(3)) (^ (y)(2)) ) (^ (x)(2)) ( -1 )) (* (4) (^ (x)(2) ) (z) ) )
La linealidad de la representa
ion parentizada puede utilizarse para la transmision de
arboles en una red. El sitio origen de la
omuni
a
ion obtiene una representa
ion parentizada que se embala en un mensaje. Al re
ibir el arbol parentizado, el sitio destino realiza
la transforma
ion inversa -desembalaje- y obtiene una
opia exa
ta del arbol.
En la mayora de los lenguajes de programa
ion, en artilugios ele
troni
os de
al
ulo
y, en general, en la ingeniera, la expresiones se
olo
an en forma inja y los parentesis se
olo
an en donde queramos modi
ar la pre
eden
ia de los operadores. De este modo, la
expresion anterior podra es
ribirse en forma inja
omo:
(3*x^4 + 3*x^3*y^2 + x^2 - 1)/ (4*x^2*z)
4.2. Representaciones de un
arbol
4.2.3
281
Indentaci
on
Una manera
onveniente y barata para dibujar arboles peque~nos, en la
ual no se pierde
la estru
tura,
onsiste en indentar por nivel. La idea se expli
a mediante el siguiente
algoritmo:
Algoritmo 4.1 (Dibujo de un
arbol en modo texto) La entrada del algoritmo es un
arbol en su representa
ion gra
a
lasi
a. La salida es la representa
ion indentada del arbol.
El algoritmo tiene un parametro llamado s, denido
omo el numero de espa
ios en
blan
o de separa
ion horizontal.
El algoritmo es re
ursivo
on prototipo void dibujar(Node * x). La primera llamada
debe eje
utarse
on la raz p.
1. Imprimir p.
2. y hijos(x)
(a) Indentar s espa
ios ha
ia la dere
ha.
(b) dibujar(y);
(
) Indentar s espa
ios ha
ia la izquierda.
3. Si x es una hoja =
(a) Salte de lnea.
El arbol de las guras 4.3 y 4.4 tiene la siguiente representa
ion indentada:
A
G
H
L
M
O
P
U
V
Q
D
E
4.2.4
I
J
K
R
S
T
Notaci
on de Deway
La estru
tura
ion y
ontenido de este texto en
aptulos, se
iones, et
etera, se
orresponde
on una arbores
en
ia en la
ual los
aptulos son arboles disjuntos. Las se
iones de un
aptulo son los subarboles de la raz y, a la vez, las sub-se
iones son los subarboles de una
se
ion. Esta estru
tura
ion obede
e a una jerarqua segun el area de estudio. Pues bien,
el enfoque utilizado para enumerar
ada se
ion
onstituye una manera de representar un
arbol. El metodo en
uestion se denomina notacion decimal de Deway, por analoga
a una nota
ion similar usada para
lasi
ar libros en bibliote
as. Basi
amente, la nota
ion
de Deway es una manera de enumerar e identi
ar unvo
amente
ada nodo del arbol.
Captulo 4. Arboles
282
El arbol de las guras 4.3 y 4.4 puede representarse mediante los siguientes numeros de
Deway:
(1 : A), (1.1 : B), (1.2 : C), (1.3 : D), (1.4 : E), (1.1.1 : F), (1.1.2 : G),
(1.2.1 : H), (1.3.1 : I), (1.3.2 : J), (1.4.1 : K), (1.1.1.1 : L), (1.1.1.2 : M),
(1.2.1.1 : O), (1.2.1.2 : P), (1.2.1.3 : Q), (1.3.2.1 : R), (1.4.1.1 : S),
(1.4.1.2 : T ), (1.2.1.2.1 : U), (1.2.1.2.2 : V)
4.3
Representaciones de
arboles en memoria
Basi
amente, existen dos maneras de representar un arbol en memoria: por arreglos y por
listas enlazadas. Ambas representan un intermedio entre velo
idad y espa
io.
4.3.1
Listas enlazadas
En esta representa
ion,
ada nodo se estru
tura segun la forma siguiente:
R SIBLING
clave
L CHILD
+
*
*
3
^
x
3
4
^
x
^
3
-1
4.3. Representaciones de
arboles en memoria
283
Arreglos
Dado un arbol de orden m,
ada nodo
ontiene el espa
io para el dato y un arreglo mdimensional de apuntadores a sus m subarboles. El valor NULL indi
a la ausen
ia de la
rama
orrespondiente.
Esta representa
ion tiende a utilizar mas espa
io que la listas enlazadas. El desperdi
io
de memoria en nodos in
ompletos puede ser importante si el arbol es muy espar
ido. En
la o
urren
ia, en el arbol de orden 4 dibujado en la gura 4.7, solo un nodo esta
ompleto;
onse
uentemente, el resto de los nodos in
ompletos desperdi
ia al menos una
elda en
punteros nulos.
La representa
ion es estati
a en el sentido de que el orden del arbol no es fa
il de
modi
ar. Este enfoque no es
onveniente para
onstruir un arbol de orden des
ono
ido.
En mu
hos
asos, es
onveniente efe
tuar la
onstru
ion
on la representa
ion de listas
enlazadas y luego
onvertirlo a la representa
ion ve
torizada.
El a
eso a una genera
ion es equivalente al de las listas enlazadas, pero el a
eso a
ualquiera de las ramas es dire
to; esta
ara
tersti
a es la que ha
e a esta representa
ion
mas rapida que su
ontraparte
on listas enlazadas.
Captulo 4. Arboles
284
^
x
^
x
-1
^
3
Arboles
Binarios
4.4
Ahora pro ederemos a estudiar una lase espe ial de arbol denominada \arbol binario". El arbol binario es la base on eptual de una amplia gama de algoritmos y estru turas de datos.
Definici
on 4.2 (Arbol
binario) Un arbol binario T es un
onjunto nito de
ero o mas
T=
es el nodo raz
n
< L(T ), n, R(T ) > de lo
ontrario; donde:
L(T ) es el subarbol binario izquierdo
R(T )
es el subarbol binario dere
ho
(4.1)
4.4. Arboles
Binarios
285
Representaci
on en memoria de un
arbol binario
Los arboles binarios se representan en memoria
on arreglos. Sin embargo, puesto que
la dimension del arreglo es dos, la representa
ion es pra
ti
amente la misma que
on
listas enlazadas. La gura 4.9 ilustra un ejemplo.
G
S
B
X
H
Recorridos sobre
arboles binarios
Los arboles y los arboles binarios representan ordenes jerarqui
os. Pero re
ordemos que el
omputador solo pro
esa se
uen
ias; es de
ir, no puede distinguir, en el mismo sentido de
nuestra per
ep
ion, que un arbol es un arbol.
Consideremos el problema de
ontar el numero de nodos que posee un arbol binario.
Para es
alas peque~nas,
omo seres humanos no estamos restringidos por la topologa del
arbol. De he
ho, podemos borrar todas las
onexiones entre los ar
os y aun somos
apa
es
de
ontar los nodos. Consideremos ahora la evalua
ion de la expresion (x2 + x + 1)2y, la
ual puede representarse mediante un arbol binario similar al de la gura 4.10. Podemos
omenzar la evalua
ion sobre
ualquiera de los tres subarboles que poseen hojas. Por
ejemplo, podramos evaluar x2, luego 2y, luego x + 1 y as, su
esivamente. Notemos que
este orden de evalua
ion no
onsidera distan
ias entre ramas; por ejemplo, la que hay entre
la rama x2 y 2y.
La vision de los seres humanos permite improvisar diversas se
uen
ias de re
orrido
o pro
esamiento sobre el arbol. En un
omputador, empero,
on la representa
ion de
nodo binario dada, la vision de
omputador esta restringida por las siguientes
ondi
iones:
1. El primer nodo observable es la raz. Conse
uentemente, si deseamos mirar algun
subarbol en parti
ular, enton
es se debe re
orrer, se
uen
ialmente,
ada genera
ion
des
endiente desde la raz.
2. Desde un nodo
ualquiera solo se pueden ver sus dos hijos mediante los
ampos L(T )
y R(T ).
Captulo 4. Arboles
286
*
+
^
x
+
2
X
A
Figura 4.11: Arbol
binario para ejempli
ar re
orridos
4.4. Arboles
Binarios
287
Apli
ando las deni
iones sobre el arbol de la gura 4.11, tenemos el siguiente re
orrido
prejo:
G S B X A Z
(4.2)
Primero visitamos la raz (G), luego el subarbol izquierdo (S B) y luego el subarbol
dere
ho (X A Z).
Para el re
orrido injo tenemos:
(4.3)
B S G A X Z
G S X B A Z
A menudo, la se
uen
ia del re
orrido
ara
teriza la topologa del arbol. En efe
to, los
re
orridos estan ntimamente rela
ionados
on mu
hos de los algoritmos sobre arboles y
ara
terizan algo a
er
a de su forma. Consideremos el arbol de expresiones algebrai
as
mostrado en la gura 4.10. Su re
orrido sujo es:
x 2 x 1 + + 2 y +
(4.6)
(4.7)
el
ual puede evaluarse por la dere
ha de forma analoga a la evalua
ion de una expresion
inja.
Ahora
onsideremos un algoritmo de impresion del arbol injo.
Algoritmo 4.6 (Impresi
on infija, parentizada, de un
arbol binario) La entrada del
algoritmo es la raz p de un arbol binario. La salida es el re
orrido injo, parentizado (vease x 4.2.2).
El algoritmo es re
ursivo
on prototipo void imprimir(Node *nodo); donde nodo es
la raz del arbol a imprimir. La primera llamada debe eje
utarse
on la raz.
1. Si nodo == NULL termine.
2. Imprima " (".
3. imprimir(LLINK(nodo));
4. Imprima el smbolo
ontenido en nodo.
5. imprimir(RLINK(nodo));
Captulo 4. Arboles
288
^
x
x ^ 2
x + 1
(4.8)
la
ual es una se
uen
ia inja, valida, de la expresion algebrai
a. En otras palabras, el algoritmo 4.6 obtiene una representa
ion parentizada del arbol diferente a la representa
ion
dada en x 4.2.2. Esta representa
ion tambien
onserva la forma del arbol y permite re
onstruir su forma original.
Hay una forma ingeniosa de interpretar el re
orrido injo. Asumamos que los nodos
estan su
ientemente separados y que se proye
ta una luz sobre la raz. El re
orrido injo
se proye
tara en un plano situado debajo del arbol. La gura 4.12 esquematiza la idea.
Consideremos ahora un problema inverso: dado algun re
orrido
ualquiera, >
omo
obtener el arbol? La respuesta o
ial es que no es posible. La respuesta o
iosa es que todo
depende del tipo de arbol binario. De alguna manera, un re
orrido, aunado al
ono
imiento
a
er
a del tipo del arbol,
onlleva su
iente informa
ion sobre su topologa. En la o
urren
ia, el re
orrido prejo permite identi
ar inmediatamente la raz y la raz del subarbol
izquierdo. Pero esta informa
ion por s sola no es su
iente para re
onstruir el arbol original. Se requiere algo mas.
Existen varios metodos para obtener la forma original. Todos requieren informa
ion
adi
ional. Un primer metodo es a~nadir delimitadores al re
orrido; la representa
ion parentizada expli
ada en x 4.2.2 es un ejemplo; el algoritmo 4.6 de impresion inja parentizada
es otro ejemplo.
>Cual es la diferen
ia entre las representa
iones parentizadas de x 4.2.2 y la dada por
el algoritmo 4.6? Para responder, observemos la representa
ion parentizada del arbol de
la gura 4.10:
((+((x)(2))(+(x)(1)))((2)(y)))
(4.9)
Al eliminar los parentesis, la expresion resultante es identi
a a la expresion (4.7); es de
ir,
es el re
orrido prejo.
La representa
ion parentizada de x 4.2.2 es una version extendida del re
orrido prejo.
Del mismo modo, la representa
ion
onstruida por el algoritmo 4.6 es una version extendida del re
orrido injo. Ambas representa
iones
ontienen informa
ion su
iente para
re
onstruir el arbol original.
4.4. Arboles
Binarios
289
Otra manera alternativa de re
onstruir el arbol original
onsiste en
ombinar la informa
ion de dos re
orridos diferentes sobre el mismo arbol. El algoritmo 4.7 nos ofre
e un
ejemplo.
Algoritmo 4.7 (C
alculo del
arbol a partir de recorridos prefijo e infijo) El pro-
6. Retorne root
La
onstru
ion del arbol puede pi
torizarse de la siguiente manera:
l_p
r_p
prefix k
infix
R_p
L_p
L_i
l_i
R_i
r_i
k
build_tree
build_tree
L_p
R_p
L_i
R_i
La logi
a del algoritmo es simple. Debe estar
laro que el primer elemento de un
re
orrido prejo es la raz del arbol, la
ual bus
amos en el re
orrido injo y obtenemos
Captulo 4. Arboles
290
+
x
^
1
Figura 4.13: Arbol
de opera
iones equivalente al de la gura 4.10
Con el re
orrido injo normal, sin parentesis, no es posible
onstruir algun arbol porque
es imposible
ono
er la pre
eden
ia.
Mu
hos tipos de arboles pueden
onstruirse
on tan solo el re
orrido prejo o sujo.
Existen
asos en los
uales no es ne
esario obtener un arbol identi
o al original, pero
s equivalente a efe
tos de la abstra
ion. Un ejemplo notable esta dado por los arboles
de expresiones algebrai
as; por ejemplo, el arbol de la gura 4.13 es diferente en forma
pero equivalente en opera
ion al arbol de la gura 4.10. A pesar de las ambiguedades, es
muy importante aprehender que, dada una expresion preja o suja, es posible
onstruir
automati
amente un arbol que represente
orre
tamente la evalua
ion de la expresion.
Esto es muy importante porque de alguna manera la expresion suja posee su
iente
informa
ion para
onstruir un arbol, ergo, para resolver el problema. En otras palabras,
en el
aso de las expresiones algebrai
as, podramos optar por
onstruir el arbol que
efe
tue la evalua
ion de izquierda a dere
ha. A efe
tos del problema, la solu
ion esta dada
y la ambiguedad no es importante. As pues, siempre podramos trabajar
on expresiones
sujas, una forma e
az y muy
ompa
ta de alma
enar expresiones algebrai
as.
4.4.3
Un TAD gen
erico para
arboles binarios
Antes de abordar algoritmos que nos entrenen en el re
orrido de arboles, estable
eremos
un TAD sobre el
ual fundamentaremos la mayora de las estru
turas de arbol binario de
este texto.
Modelizaremos e implantaremos un me
anismo generi
o para
onstruir \nodos binarios". A lo largo de este texto desarrollaremos diferentes estru
turas de arbol binario. La
mayora de ellas siempre manejaran tres atributos basi
os: una
lave, un puntero a la rama
izquierda y un puntero a la rama dere
ha. Segun el tipo de arbol, el nodo puede
ontener alguna informa
ion adi
ional de
ontrol. Debemos, enton
es, en
ontrar una forma de denir
generi
amente nodos binarios de
ualquier ndole bajo los siguientes requerimientos:
4.4. Arboles
Binarios
291
1. Soporte para atributos generales: Dado un nodo binario, debe poder a
ederse
a la
lave y a sus ramas des
endientes. Estos atributos son
omunes a todos las
lases
de nodos binarios posibles.
2. Especificacion de atributos opcionales: Eventualmente, debe posibilitarse el
de
larar atributos propios de una
lase parti
ular de nodo binario.
3. Soporte opcional para destructores virtuales: El usuario puede trabajar op
ionalmente
on nodos que posean destru
tores virtuales.
4. Soporte para nodos centinelas especiales : Normalmente, el arbol va
o se
se~nala mediante el valor espe
ial NULL. Para
iertas estru
turas de arbol, es
onveniente que este arbol va
o se represente mediante una instan
ia parti
ular de
nodo.
5
291a
# include <ahDefs.H>
# include <ahAssert.H>
namespace Aleph {
hdeni
i
on
hma ros
hma ros
291b
htpl
(291a) 293
\
\
\
\
template <typename Key> Name<Key> * const Name<Key>::NullPtr = NULL; \
\
INIT_CLASS_BINNODE(Name##Vtl, height, Control_Data)
\
virtual ~Name##Vtl() { /* empty */ }
\
};
\
5 En
Captulo 4. Arboles
292
\
template <typename Key> Name##Vtl<Key> * const Name##Vtl<Key>::NullPtr = NULL
Denes:
DECLARE BINNODE, used in
hunks 294a, 363, and 569.
Uses INIT CLASS BINNODE 294b.
DECLARE BINNODE() genera dos
lases parametrizadas que representan nodos binarios
pertene
ientes a alguna
lase de arbol binario. El parametro Name es el nombre prejo de
las
lases que se desean generar. El ma
ro se expande a dos
lases
asi identi
as: Name
y NameVtl. La uni
a diferen
ia es que NameVtl posee un destru
tor virtual.
El parametro height representa un estimado de la altura maxima que puede al
anzar
el arbol binario. Mu
hos algoritmos sobre arboles binarios son re
ursivos, por lo que el
onsumo de espa
io en pila es un fa
tor a
onsiderar. En este sentido, el atributo height ofre
e
un aproximado de la altura maxima del arbol binario,
uyo valor sirve a los algoritmos a
tomar previsiones a
er
a del
onsumo en pila.
Finalmente, el parametro Control Data es una
lase que representa informa
ion de
ontrol del nodo binario pertinente a una espe
ializa
ion, la
ual vara segun el tipo de
arbol binario que se maneje. Por ejemplo, los arboles rojo-negro, que estudiaremos en x 6.5
alma
enan un
olor. Destaquemos que Control data no esta destinada a usarse por el
usuario nal
Mu
hos tipos de arboles binarios se utilizan para
onstruir mapeos entre
laves de
algun dominio y elementos de algun rango. En esta situa
ion, lo que se requiere es
que
ada nodo alma
ene un elemento del rango dado. Puesto que nuestra pretension es
generi
a, no podemos usar Control Data, pues si no invalidaramos, por ejemplo, un TAD
arbol rojo generi
o.
Una manera de realizar un mapeo es mediante heren
ia de interfaz. Supongamos que
requerimos un mapeo mediante arboles rojo-negro. Por ejemplo, un mapeo entre numeros
de
edula y apellidos, puede espe
i
arse del siguiente modo:
RbNode<int>
-key: int
-lLink: RbNode *
-rlink: RbNode *
+RbNode(k:const int &)
+RbNode(node:const RbNode &)
+RbNode()
+reset(): void
+get_key(): int&
+getL(): RbNode*&
+getR(): RbNode*&
Persona
+apellidos: string
+apellidos(): string
+modificar_apellidos(in a:string): void
En este
aso, implantamos un mapeo \jo" en el sentido de que este
ontendra pares de
tipo [int,string].
Si deseamos un mapeo generi
o, el
ual es mas
ompleto, enton
es la
lase derivada
debe ser una plantilla, lo
ual, en el
aso de nuestro ejemplo, puede espe
i
arse de la
siguiente manera:
4.4. Arboles
Binarios
293
RbNode<Key>
-key: Key
-lLink: RbNode<Key> *
-rlink: RbNode<Key> *
+RbNode(k:const Key &)
+RbNode(node:const RbNode<Key> &)
+RbNode()
+reset(): void
+get_key(): Key&
+getL(): RbNode<Key>*&
+getR(): RbNode<Key>*&
key:class Key
data:Data
Mapppig
+apellidos: string
+apellidos(): string
+modificar_apellidos(in a:string): void
293
Los arboles requieren un valor espe
ial para representar el arbol va
o. Tal valor
se alma
ena en el atributo estati
o Name<T>::NullPtr, el
ual debe ini
ializarse
expl
itamente al valor de arbol va
o. Por omision, DECLARE BINNODE() asume que el
arbol va
o es el valor NULL.
Algunas ve
es es
onveniente que NullPtr apunte a un nodo
entinela parti
ular. En
estas situa
iones debemos usar el ma
ro DECLARE BINNODE SENTINEL(), el
ual se dene
del siguiente modo:
hma
ros externos de BinNode<Key> 291bi+
(291a) 291b 296
# define DECLARE_BINNODE_SENTINEL(Name, height, Control_Data)
\
INIT_CLASS_BINNODE(Name, height, Control_Data)
\
Name(SentinelCtor) :
\
Control_Data(sentinelCtor), lLink(NullPtr), rLink(NullPtr) {}\
static Name sentinel_node;
\
};
\
\
template <typename Key>
Name<Key> Name<Key>::sentinel_node(sentinelCtor);
\
\
template <typename Key>
Name<Key> * const Name<Key>::NullPtr = &Name<Key>::sentinel_node;\
\
INIT_CLASS_BINNODE(Name##Vtl, height, Control_Data)
\
virtual ~Name##Vtl() { /* empty */ }
\
private:
\
Name##Vtl(SentinelCtor) :
\
Control_Data(sentinelCtor), lLink(NullPtr), rLink(NullPtr) {}\
static Name##Vtl sentinel_node;
\
};
\
\
template <typename Key>
Name##Vtl<Key> Name##Vtl<Key>::sentinel_node(sentinelCtor);
\
\
template <typename Key>
Name##Vtl<Key> * const Name##Vtl<Key>::NullPtr =
\
&Name##Vtl<Key>::sentinel_node
Denes:
DECLARE BINNODE SENTINEL, used in
hunks 409, 551a, 561, and 591.
Captulo 4. Arboles
294
La
lase BinNode<Key> espe
i
a el mas simple nodo pertene
iente a un arbol binario.
Sin
ono
er el tipo de arbol binario que se trate, su altura es bastante variable y depende
del orden en que las
laves sean insertadas. Por esta razon, el tama~no de una pila que
opere sobre un arbol binario debe ser lo su
ientemente grande para que permita alturas
elevadas. Como es muy dif
il determinar la maxima altura que podra al
anzar un arbol
binario general, la jamos en un valor grande, pero advertimos que esto no elimina la
posibilidad de un desborde de pila.
BinNode<Key> no tiene atributos de
ontrol espe
iales; por eso,
olo
amos la
lase
va
a Aleph::Empty Node, la
ual esta denida en la blibliote
a.
294b
\
\
\
\
\
\
\
\
\
4.4. Arboles
Binarios
\
\
private:
\
\
Key
key;
\
Name * lLink;
\
Name * rLink;
\
\
public:
\
\
Key & get_key() { return key; }
\
\
const Key & get_key() const { return key; }
\
\
Name *& getL() { return lLink; }
\
\
Name *& getR() { return rLink; }
\
\
Name(const Key& k) : key(k), lLink(NullPtr), rLink(NullPtr) \
{
\
/* Empty */
\
}
\
\
Name(const Control_Data & control_data, const Key & k) :
\
Control_Data(control_data),
\
key(k), lLink(NullPtr), rLink(NullPtr)
\
{
\
/* Empty */
\
}
\
\
Name(const Name & node) :
\
Control_Data(node),
\
key(node.key), lLink(NullPtr), rLink(NullPtr)
\
{
\
/* Empty */
\
}
\
\
Name(const Control_Data & control_data) :
\
Control_Data(control_data),
\
lLink(NullPtr), rLink(NullPtr)
\
{
\
/* Empty */
\
}
\
\
Name() : lLink(NullPtr), rLink(NullPtr)
\
{
\
/* Empty */
\
}
\
\
void reset()
\
{
\
295
Captulo 4. Arboles
296
Control_Data::reset();
rLink = lLink = NullPtr;
}
static Name * key_to_node(Key & __key)
{
Name * node_zero = 0;
size_t offset = (size_t) &(node_zero->key);
char * addr = &__key;
return (Name*) (addr - offset);
}
Denes:
INIT CLASS BINNODE, used in
hunks 291b and 293.
296
\
\
\
\
\
\
\
\
\
\
\
La
ual reere al tipo de
lave del nodo. Mediante esta interfaz, se puede dise~nar
odigo
generi
o, sin ne
esidad de espe
i
ar, ni de
ono
er expl
itamente, el tipo de
lave que
alberga el nodo.
Supongamos que deseamos un nodo que guarde
omo atributo de
ontrol la altura del
nodo. Denimos, enton
es, una
lase que
ontiene tal atributo y nos valemos de los ma
ros
de la siguiente forma:
class Altura
{
private:
size_t altura;
public:
Altura() { /* Empty */ }
Altura(size_t a) : altura(a) { /* empty */ }
size_t dar_altura() const { return altura; }
};
4.4. Arboles
Binarios
297
El ma
ro se expande a dos
lases plantilla denominadas Nodo<Key> y NodoVtl<Key>, respe
tivamente. Las
lases son
asi identi
as, salvo que NodoVtl<Key> dene un destru
tor
virtual.
La estru
tura de Nodo<Key> es la siguiente:
template <typename Key, size_t _MaxHeight = 255>
class Nodo : public Altura
{
public:
static Nodo * NullPtr;
static const size_t MaxHeight = _MaxHeight;
typedef Key key_type;
private:
Key key;
Nodo * lLink;
Nodo * rLink;
public:
Key& get_key() { return key; }
Nodo*& getL() { return lLink; }
Nodo*& getR() { return rLink; }
Nodo(const Key& k) : key(k), lLink(NullPtr), rLink(NullPtr) { }
Nodo(const Altura & control_data, const Key& k) :
Altura(control_data), key(k), lLink(NullPtr), rLink(NullPtr) { }
Nodo(const Altura & control_data) :
Altura(control_data), lLink(NullPtr), rLink(NullPtr) { }
Nodo() : lLink(NullPtr), rLink(NullPtr) { }
Nodo(EmptyCtor) { }
void reset() { rLink = lLink = NullPtr; }
};
template <typename Key, size_t _MaxHeight>
BinNode<Key, _MaxHeight> * BinNode<Key, _MaxHeight>::NullPtr = NULL;
Captulo 4. Arboles
298
La
lases Nodo<Key> y NodoVtl<Key> representan familias de nodos binarios
on atributos de
ontrol denidos en la
lase Altura y
on
lave generi
a Key. El a
eso a la
lave esta dado por get key() y los a
esos a las ramas izquierda y dere
ha por getL() y
getR(), respe
tivamente.
Nodo<Key> hereda p
ubli
amente de Altura. Por tanto, un Nodo<Key> es, tambien, de
tipo altura. De este modo, Nodo<Key> tiene a
eso a toda la interfaz publi
a de Altura.
Hay
in
o maneras de
onstruir un Nodo<Key> representadas por los
in
o
onstru
tores generados los
uales, a ex
ep
ion del quinto, se expli
an por s solos. Si se requiere un nodo
entinela, este se instan
ia mediante el quinto
onstru
tor, el
ual requiere
que Node::Node(SentinelCtor) se dena en la
lase Altura.
La plantilla Nodo<Key> tiene dos atributos estati
os. Node::NullPtr es el apuntador al arbol va
o. En la mayora de las situa
iones, este puntero tendra el valor
nulo; en otras o
asiones, Node::NullPtr dire
ionara al nodo
entinela. El valor de
Node::NullPtr debe denirse expl
itamente por el invo
ante de DECLARE BINNODE mediante el ma
ro SET BINNODE NULL POINTER. Este ma
ro toma
omo parametros el nombre
de la
lase nodo y el puntero al nodo
entinela.
El segundo atributo estati
o
orresponde a un valor estimado de altura maxima, que
puede ser requerido por algunos algoritmos para determinar el tama~no de sus pilas.
Un Nodo<Key> puede reutilizarse. Por ejemplo, podramos extraerlo de un arbol e
insertarlo en otro. En este
aso, el estado del nodo debe ser reini
iado. Para ello se provee
el metodo reset().
4.4.4
298
Mu
has opera
iones sobre arboles binarios se en
apsulan en una bibliote
a espe
ial que
ontendra algoritmos tpi
os sobre arboles binarios y que se dene en el
ar
hivo htpl binNodeUtils.H 298i:
htpl binNodeUtils.H 298i
hFun
iones de BinNode Utils 299ai
Esta bibliote
a esta dise~nada para trabajar
on arboles binarios de
ualquier ndole.
Contiene algoritmos tpi
os; por ejemplo, eje
u
ion de re
orridos, dupli
a
ion de arboles,
et
etera.
Como ya lo hemos observado, algunos algoritmos requieren la utiliza
ion de pilas. Con
miras al desempe~no, es
onveniente que las pilas se implanten
on un arreglo; por esa razon,
in
luimos los prototipos de manejo de pilas ve
torizadas denidos en tpl arrayStack.H.
La mayor parte de las fun
iones de htpl binNodeUtils.H 298i son plantillas
on la forma
general siguiente:
template <class Node> funci
on (...) { ... }
4.4. Arboles
Binarios
4.4.5
299
Recorridos recursivos
La rutina de re
orrido se remite a su fun
ion basi
a: re
orrer. As pues, una rutina de
re
orrido injo tendra el siguiente prototipo:
template <class Node> inline
int inOrderRec(Node * root, void (*visitFct)(Node*, int, int))
299a
(298) 299b
}
Denes:
299b
El nivel level se in
rementa en
ada llamada, mientras que la position en
ada visita.
Notemos que position es un parametro por referen
ia, pues este requiere a
tualizarse en
ada visita.
Para que la rutina sea
onsistente, la llamada ini
ial debe realizarse
on valores
de level y position iguales a
ero. Este es el trabajo de inOrderRec():
hFun
iones de BinNode Utils 299ai+
(298) 299a 300
template <class Node> inline
int inOrderRec(Node * root, void (*visitFct)(Node*, int, int))
{
int position = 0;
Captulo 4. Arboles
300
inOrderRec() retorna el n
umero de nodos que
ontiene el arbol.
300
4.4. Arboles
Binarios
301
}
template <class Node> inline
int postOrderRec(Node * root, void (*visitFct)(Node*, int, int))
{
int position = 0;
__postorder_rec(root, 0, position, visitFct);
return position;
}
Denes:
postOrderRec, never used.
preOrderRec, never used.
Uses LLINK 296 and RLINK 296.
Todas las primitivas de re
orrido de htpl binNodeUtils.H 298i tienen
omo primer
parametro la raz del arbol binario. El segundo parametro es una fun
ion de visita del
nodo que provee el
liente de la bibliote
a. La fun
ion sera invo
ada durante la visita
a
orde a su re
orrido.
Notemos que la
ondi
ion de parada de la re
ursion es el visitar un nodo nulo.
4.4.6
301
Recorridos no recursivos
Captulo 4. Arboles
302
if (LLINK(p) != Node::NullPtr)
stack.push(LLINK(p));
}
return count;
}
Denes:
preOrderStack aux, never used.
Uses ArrayStack 131a, LLINK 296, and RLINK 296.
302
El patron iterativo del algoritmo es simple: visite, introduz
a el arbol dere
ho, luego el
izquierdo y saque de la pila. Puesto que el arbol izquierdo fue introdu
ido despues del
dere
ho, el izquierdo sera el primero a extraerse y visitarse.
Observemos que el nivel del nodo se
orresponde
on el tama~no a
tual de la pila
Ahora bien, segun la deni
ion del re
orrido prejo, el arbol izquierdo se visita inmediatamente despues de visitar el nodo. Podemos, enton
es, mejorar el algoritmo si, en
lugar de guardar en pila el arbol dere
ho, guardamos su padre y asumimos que todo nodo
extrado de la pila ya fue visitado. Este patron
ondu
e al algoritmo siguiente:
hFun
iones de BinNode Utils 299ai+
(298) 301 303
template <class Node> inline
size_t preOrderStack(Node * node, void (*visitFct)(Node *, int, int))
{
if (node == Node::NullPtr)
return 0;
ArrayStack<Node *, Node::MaxHeight> stack;
Node *p = node;
size_t count = 0;
while (true)
{
(*visitFct)(p, stack.size(), count++);
if (LLINK(p) != Node::NullPtr)
{
stack.push(p); // push porque p y RLINK(p) faltan por visitarse
p = LLINK(p); // avanzar a la izquierda
continue; // ir a visitar ra
z rama izquierda
}
while (true)
{
if (RLINK(p) != Node::NullPtr)
{
p = RLINK(p); // avanzar a la derecha
break;
// ir a visitar ra
z rama derecha
}
if (stack.is_empty())
4.4. Arboles
Binarios
303
303
De una
ierta manera, todo re
orrido tiene algo de prejo, pues todo arbol se a
ede
a traves de las ra
es de sus subarboles. Por tanto, es intuitivamente esperable que el
re
orrido injo tenga una estru
tura similar al prejo, pues la diferen
ia esen
ial es el
momento en que se visita la raz.
El re
orrido prejo visita la raz antes de
ontinuar por la rama izquierda, mientras que
el injo lo ha
e despues de venir desde la rama izquierda. Di
ho esto, podemos presentar
la version no re
ursiva del re
orrido injo:
hFun
iones de BinNode Utils 299ai+
(298) 302 304b
template <class Node> inline
size_t inOrderStack(Node * node, void (*visitFct)(Node *, int, int))
{
if (node == Node::NullPtr)
return 0;
ArrayStack<Node *, Node::MaxHeight> stack;
Node *p = node;
size_t count = 0;
while (true)
{
if (LLINK(p) != Node::NullPtr)
{
stack.push(p); // push porque p y RLINK(p) faltan por visitarse
p = LLINK(p); // avanzar a la izquierda
continue; // continuar bajando por la rama izquierda
}
while (true)
{
(*visitFct)(p, stack.size(), count++);
if (RLINK(p) != Node::NullPtr)
{
p = RLINK(p); // avanzar a la derecha
break;
// ir a visitar ra
z rama derecha
}
if (stack.is_empty())
return count;
Captulo 4. Arboles
304
304a
visitar el nodo.
Vease el fuente de la bibliote
a para mayores detalles sobre la instrumenta
ion de este
algoritmo.
4.4.7
C
alculo de la cardinalidad
Probablemente, la apli
a
ion mas sen
illa de los re
orridos es
al
ular la
ardinalidad de
un arbol binario, la
ual puede denirse re
ursivamente
omo:
|T | =
304b
si T =
| L(T )| + 1 + | R(T )| si T 6=
0
return (compute_cardinality_rec(LLINK(node)) + 1 +
compute_cardinality_rec(RLINK(node)));
}
Denes:
4.4. Arboles
Binarios
4.4.8
305
C
alculo de la altura
305a
si T =
1 + max(h(L(T )), h(R(T ))) si T 6=
4.4.9
Copia de
arboles binarios
Dado un arbol binario T , \
opiarlo"
onsiste en obtener un arbol binario T
uya forma
y
ontenido se
orresponda exa
tamente
on T . Este algoritmo puede denirse re
ursivamente
omo sigue.
Algoritmo 4.8 El prototipo es Node * copy(T * src root). El parametro src root es
la raz del arbol binario que se desea
opiar. El valor de retorno es una
opia del arbol
binario
on raz src root.
1. Sea tgt root una
opia del nodo raz src root.
2. LLINK(tgt root) = copy(LLINK(src root));
3. RLINK(tgt root) = copy(RLINK(src root));
4. Retorne tgt root (Esa es la raz del arbol binario
orrespondiente a la
opia)
305b
Captulo 4. Arboles
306
Este
odigo es importante de
omentar por dos razones. La primera es que ilustra el
manejo de ex
ep
iones
on la re
ursion. Cualquiera sea el
aso, si o
urre una ex
ep
ion
no sera posible
opiar el arbol, por lo que debemos propagarla al invo
ante, pero, antes
de ha
erlo, debemos liberar toda la memoria par
ial que se haya apartado.
La segunda razon es el manejo re
ursivo de tipos. Notemos que copyRec()
opia
arboles generi
os
on nodos de tipo Node, el
ual podra derivar de una
lase de nodo
de arbol binario. Por esa razon, la llamada re
ursiva copyRec<Node>(LLINK(src root))
debe espe
i
ar expl
itamente el tipo Node, pues sino, en el
aso de que se trate de una
lase derivada, el
ompilador invo
ara copyRec()
on la
lase base a la
ual
orrespondan LLINK(tgt root) y a RLINK(tgt root). Si la
lase Node es distinta a la
lase que
retornan LLINK(tgt root) y RLINK(tgt root), enton
es o
urrira un
on
i
to de tipos.
4.4.10
306
Destrucci
on de
arboles binarios
Cualquier TAD que manipule arboles binarios debe estar en
apa
idad de destruir todo el
arbol. Es de
ir, de invo
ar el destru
tor y liberar la memoria o
upada por
ada nodo del
arbol. Esto se realiza de la siguiente forma:
hFun
iones de BinNode Utils 299ai+
(298) 305b 307a
template <class Node> inline
void destroyRec(Node * root)
{
if (root == Node::NullPtr)
return;
4.4. Arboles
Binarios
307
delete root;
}
Denes:
destroyRec, used in
hunks 305b, 406
, and 407
.
Uses LLINK 296 and RLINK 296.
>Que tipo de re
orrido exhibe este algoritmo? Claramente, la forma es suja: la raz
se libera luego de liberar las dos ramas. Otras variantes re
ursivas de este algoritmo, que
se ajusten a los demas re
orridos, son posibles y delegadas en ejer
i
ios.
4.4.11
Comparaci
on de
arboles binarios
A ve
es es ne
esario
omparar dos arboles binarios. Basi
amente, hay dos
riterios que
presentaremos a
ontinua
ion.
4.4.11.1
Similaridad
Dados dos arboles binarios T1 y T2, se di
e que T1 es similar a T2, y se denota
omo:
T1 k T2
307a
T1 = T2 =
T1 6= T2 6=
L(T1) k L(T2)
R(T1) k R(T2)
}
Denes:
4.4.11.2
Equivalencia
Dados dos arboles binarios T1 y T2, se di
e que T1 es equivalente a T2, y se denota
omo:
T1 T2
307b
T1 = T2 =
T1 6= T2 6=
Es de
ir, T1 y T2 son similares si y solo si y los
ontenidos de
ada nodo son exa
tamente
los mismos. La deni
ion de equivalen
ia nos arroja la siguiente implanta
ion re
ursiva:
hFun
iones de BinNode Utils 299ai+
(298) 307a 308
template <class Node, class Equal> inline
Captulo 4. Arboles
308
4.4.12
308
En esta
lase de re
orrido una
ola des
ribe dire
tamente el orden en que se deben pro
esar
los nodos:
hFun
iones de BinNode Utils 299ai+
(298) 307b 310a
template <class Node> inline
void levelOrder(Node *
root,
void
(*visitFct)(Node*, int, bool),
const size_t & queue_size)
{
if (root == Node::NullPtr)
return;
4.4. Arboles
Binarios
309
}
}
Denes:
Es posible, en detrimento de mu
ho mas tiempo, mas no del espa
io, dise~nar un algoritmo
re
ursivo para re
orrer por niveles. Esta version se delega en ejer
i
io.
La fun
ion de visita tiene el siguiente prototipo:
(*visitFct)(Node* p, int pos, bool is_left)
p es el nodo visitado, pos es la posi
ion del nodo visitado dentro del re
orrido y is left
es un valor logi
o
uyo valor es true si el nodo es izquierdo. Una de las apli
a
iones tpi
as
del re
orrido por niveles es para el dibujado de arboles, en
uyo
aso es util saber si p es
o no un hijo izquierdo.
La logi
a del algoritmo es aprove
har la propiedad FIFO de la
ola para garantizar el
orden de visita requerido. Esta
laro que el primer nodo a visitar es la raz, por lo que la
metemos en la
ola y
omenzamos. Cada vez que se visite un nodo, todos sus
ompa~neros
de nivel o genera
ion deben ser los siguientes a extraer de la
ola. Esto se garantiza porque
ada vez que visitamos un nodo introdu
imos en la
ola su hijo izquierdo y luego su hijo
dere
ho. Estos hijos van a estar en la
ola despues de los an
estros de su nivel anterior
y despues de sus
ompa~neros de nivel que esten del lado izquierdo. En la gura 4.15,
los proximos nodos en la
ola son los hermanos dere
hos del a
tual; estos nodos fueron
introdu
idos
uando se estaba en el nivel uno. Despues vienen los nodos del nivel tres que
fueron introdu
idos
uando se pro
esaron los hermanos izquierdos del nodo a
tual.
a tual
Trasero
Frente
Figura 4.15: Estado de la
ola
uando se pro
esa el nodo indi
ado
on
e
ha
Este re
orrido es el mas
ostoso de los
uatro, pues se puede requerir alma
enar
mu
hsimos nodos en la
ola. Si las hojas del arbol tienden a estar en el mismo nivel,
enton
es, por
ada nivel que se des
ienda, se requerira alma
enar el doble de la
antidad
de nodos hasta llegar al nivel donde se en
uentren las hojas.
Supongamos que la
antidad de nodos de
ada nivel es poten
ia exa
ta de dos; o sea,
ada nivel del arbol esta lleno. Enton
es, asumiendo altura
ono
ida h, podemos expresar
P
h1 nodos alma
enados en la
i
la
antidad de nodos n
omo h1
i=0 2 . Es de
ir, tendremos 2
ola
uando nos toque pro
esar el ultimo nivel.
Puesto que la forma de un arbol puede ser bastante arbitraria, la primitiva
levelOrder() re
ibe
omo parametro el tama~
no maximo de la
ola y delega al
liente
el estimar un tama~no ade
uado segun su apli
a
ion.
Captulo 4. Arboles
310
4.4.13
310a
Construcci
on de
arboles binarios a partir de recorridos
Podemos
odi
ar el algoritmo 4.7 presentado en x 4.4.2. Para ello, planteamos la siguiente
rutina:
hFun
iones de BinNode Utils 299ai+
(298) 308 311a
template <template <class> class Node, typename Key> inline
Node<Key> * build_tree(DynArray<Key> & preorder,
const int & l_p, const int & r_p,
DynArray<Key> & inorder,
const int & l_i, const int & r_i)
{
if (l_p > r_p) // est
a vac
o el recorrido?
{ // s
, retornar
arbol vac
o
return Node <Key> ::NullPtr;
}
build tree()
onstruye un arbol binario a partir de los re
orridos prejo e injo.
preorder es un arreglo que
ontiene el re
orrido prejo entre los ndi
es l p y r p. Del
mismo modo, inorder es un arreglo que
ontiene el re
orrido injo entre los ndi
es l i
y r i. La rutina retorna la raz del nuevo arbol binario.
310b
La dete
ion de arbol va
o se realiza
uando los re
orridos son va
os; es de
ir,
uando
los ndi
es de los re
orridos se
ruzan.
La parte mas deli
ada es bus
ar la posi
ion relativa de la raz dentro del re
orrido
injo. Esto lo realizamos
uidadosamente mediante inspe
ion se
uen
ial:
hSea i posi
i
on de preorder[l p en inorder[ 310bi
(310a)
int i = 0;
for (int j = l_i; j <= r_i; ++j)
if (inorder[j] == preorder[l_p])
{
i = j - l_i;
break;
}
4.4. Arboles
Binarios
4.4.14
311
En algunas situa
iones, es ne
esario
ono
er
uales son los nodos de un arbol que se
en
uentran en un determinado nivel i;. por ejemplo,
uando se dibujan arboles, a efe
tos
de ajustar las posi
iones de los nodos en nivel determinado.
La siguiente rutina, re
orre re
ursivamente el arbol
on raz root y guarda en
la lista level list los nodos que se en
uentren en el nivel level. El parametro
current level denota el nivel del nodo que se esta visitando :
hFun
iones de BinNode Utils 299ai+
(298) 310a 311b
6
311a
root,
level,
current_level,
level_list)
if (level == current_level)
{
level_list.append(root);
return; // no vale la pena descender m
as
}
__compute_nodes_in_level(LLINK(root), level, current_level + 1, level_list);
__compute_nodes_in_level(RLINK(root), level, current_level + 1, level_list);
}
Denes:
compute nodes in level, used in
hunk 311b.
Uses DynDlist 113a, LLINK 296, and RLINK 296.
311b
Como debe apre
iarse, el arbol se re
orre en prejo y los nodos se guardan en la lista en
orden injo. Menester desta
ar que, a diferen
ia del re
orrido por niveles, no es ne
esario
usar una
ola.
La rutina anterior debe llamarse por la que fungira de interfaz, la
ual, se espe
i
a
de la siguiente forma:
hFun
iones de BinNode Utils 299ai+
(298) 311a 316
template <class Node> inline
void compute_nodes_in_level(Node *
root,
const int &
level,
DynDlist<Node*>& list)
{
__compute_nodes_in_level(root, level, 0, list);
}
Denes:
compute nodes in level, never used.
Uses compute nodes in level 311a and DynDlist 113a.
6 Equivale
a la profundidad re ursiva.
Captulo 4. Arboles
312
4.4.15
Hilado de
arboles binarios
Es fa
il dedu
ir que un arbol binario de n nodos
onsume 2n apuntadores. Pero quiza
no sea tan fa
il aprehender que para
ualquier arbol binario la
antidad total de punteros
on el valor NULL siempre es mayor que la
antidad de punteros asignados. Mas
adelante (proposi
ion 4.3) demostraremos la vera
idad de esta arma
ion.
Asumiendo veraz la arma
ion anterior, algunos han
uestionado el desperdi
io de espa
io
ausado por los punteros nulos. A tenor del ahorro, pueden
onsiderarse las siguientes
perspe
tivas:
1. Podemos tener tres tipos de nodos:
ompleto, in
ompleto y hoja. Cada nodo o
upara
el espa
io exa
to segun el tipo de nodo. Por supuesto,
ada nodo tendra que tener
un
ampo adi
ional que indique su tipo.
Esta solu
ion es una alternativa importante y apelable si hay requerimientos
rti
os
de espa
io. Ella adole
e, sin embargo, de estatismo en el sentido de que si el arbol
ambia dinami
amente, enton
es el tipo de nodo puede
ambiar. Tambien, las opera
iones sobre estos arboles son mas
omplejas porque estas deben distinguir los tipos
de nodos.
2. Utilizar los apuntadores nulos para alma
enar informa
ion adi
ional. Aqu surge la
pregunta: >
ual informa
ion? >para que?
Perlis y Thornton [16 des
ubrieron un uso ingenioso de los punteros nulos. El metodo
onsiste en reemplazar punteros nulos por \hilos" que vayan ha
ia otras partes del arbol.
La idea es fa
ilitar el re
orrido y,
onse
uentemente, los algoritmos.
Dado un nodo p, in
ompleto u hoja, el prin
ipio esta dado por las dos reglas siguientes:
LLINK(p) apunta al nodo prede
esor injo.
RLINK(p) apunta al nodo su
esor injo.
La gura 4.16 ilustra el \hilado" de un arbol. Los hilos son representados mediante
lneas
ortadas.
Ahora estudiemos
omo debe realizarse el re
orrido injo. Para ello veamos un algoritmo que busque el su
esor injo.
Algoritmo 4.9 (B
usqueda del sucesor en un
arbol hilado) La entrada del algoritmo es un apuntador p a un nodo. La salida es el su
esor injo de p.
4.4. Arboles
Binarios
313
.
.
..
.
...
.
.
..
.
.
.
.
..
.
.
.
..
.
.
...
.
.. .
.
. .
.
.
... .
.
... .
.
..
... ..
.. ...
......
..
..
..
...
..
.
.
.
.
..
.
..
.
.
.
.
..
.
.
... .
.
.. .
.
.
... .
.
.
... .
.
... ...
.. ..
......
..
...
...
...
..
...
..
...
...
...
..
.
..
.. ....
... ...
... ..
... ..
... ....
.....
D
.
..
..
.
..
.
.
....
...
..
....
....
...
...
..
..
...
...
..
.
...
...
...
...
.
...
...
...
.
... ..
... ..
... ....
...
.
.
.
..
.
... .
.
.
.
.
.
.
..
.. ...
.
.
..
.
.
.
. .
.
..
.
.
.
.
.
.. .
.
.
..
.
.
.
.. .
.
.
.
.
.
..
..
.
.
.
..
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
..
..
..
.
.
.
...
.
..
.
..
.
.
...
..
.
..
.
.
.
.
... .....
.
.
.
..
.
... ...
.
.
.
.
..
... .. .
.
.
.
... ... .
..
.
..... .
.
..
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
..
..
.
.
.
.
.
..
.
..
.
.
.
.
..
.
.
.
.
.
..
..
.
.
..
.
.
.
..
.
.
...
..
.
.
...
...
.
.
..
.
.
...
.
..
.
...
.
.
.
...
.
.
...
...
.
.
.
.. ....
.
... ...
.
.
.
........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
..
..
..
..
..
.
.
...
..
...
..
..
..
.
...
..
..
..
... ...
... ...
......
E
...
..
..
...
...
.
.
..
..
.
...
....
..
.
...
...
..
...
.....
...
....
..
....
..
..
...
..
.....
...
...
..
..
..
...
...
..
...
..
...
...
...
..
.
..
...
...
.
.
...
..
..
..
..
... ....
... ...
.......
H
..
..
..
...
...
..
.
...
....
.
.
.... ....
.... ...
.. ...
... ..
.. ...
... ..
....
...
...
...
..
...
..
...
..
...
..
...
..
.
....
..
...
..
...
.
..
.
...
.
.
..
..
...
.
.
...
.
..
..
.. ...
... ..
.......
..
...
...
...
..
..
...
...
...
..
.
... ....
.. ...
... ...
.. ..
.. ..
.. ..
.......
I
..
..
.
..
...
.
...
..
....
.
...
...
....
...
....
...
...
..
.
...
...
...
..
..
...
...
...
.
... ...
.. ..
.. ...
.....
.
.
..
.
..
..
.
..
.
.
.
.
..
.
.
..
.
.
.
...
.
.. .
.
. .
.
.
... .
.
.
... .
... ...
.. ...
......
..
.
.
..
..
.. .
..
..
.
..
.. ..
..
.
.
.
..
.
.
.
.
..
.
.
.
.
.. .
.
.
..
.
.
... .
.
.
.. .
.
.
.
.
.
.
..
.
. ..
.
..
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
. ..
.
.
.
.
.
.
...
.
. ..
.
.
..
.
.
.
..
.
..
..
.
...
...
..
.
..
.
.
.
.
.
. .. .
.
.. ..
.
.
.
. ........
.
.
.
...
..
.
...
.
.
.
..
...
..
...
..
...
..
..
..
... ...
..........
.
..
.
.
..
..
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
..
..
.
...
..
..
...
...
...
...
.
... ...
.. ..
.........
.
.
.
..
..
...
.
..
..
.
.
..
.
.
..
.
...
.
.
... .
.
.
.. .
.
... .
..
.. ..
... ..
.. ..
......
.
.
.
..
...
..
.
..
..
.
.
..
.
.
.
..
...
.
.
.
... .
.
.
.. .
.
... ..
.
.. ..
... .
.. ...
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.. ...
.
.
.
..
...
.
..
... ....
......
...
X
..
.
..
.
..
..
.
..
.
..
.
..
.
.
.
.
.
...
..
.
... .
.
.
.. ..
.
... .
.
.
.. .
... ...
.. ..
.......
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.
..
.
.
..
..
.
...
.
..
... ...
.. ....
.....
...
...
...
...
...
..
...
..
...
..
...
..
...
...
..
...
...
.
...
...
...
...
..
...
.
...
.
..
..
.
... ...
.. ...
... ..
.....
..
..
..
..
....
..
...
....
...
..
..
... ....
... ...
... ...
.
.. .
... ..
.......
...
...
...
..
...
..
..
..
..
...
...
..
...
...
..
..
...
....
..
...
....
..
..
...
.
.
...
..
... ...
.. ...
... ..
......
Captulo 4. Arboles
314
X
H
314a
Algunas antiguas maquinas CISC tenan el bit mas signi
ativo reservado para el
signo. Como los apuntadores utilizaban inne
esariamente este signo, pues las dire
iones
negativas no tienen sentido, este bit poda utilizarse para denotar si el apuntador era o no
un hilo. Las maquinas modernas son RISC, y las po
as CISC que existen tienen mu
has
fun
ionalidades RISC. Hoy en da, la alinea
ion de las dire
iones de memoria es una de
las fun
ionalidades
omunes entre los dos tipos de maquina. La alinea
ion impli
a que las
dire
iones de memoria, -los valores de los apuntadores- siempre estan alineados al tama~no
de la palabra de memoria. En la mayora de arquite
turas modernas, la longitud de la
palabra en bits siempre es poten
ia exa
ta de dos; es de
ir, puede expresarse
omo 2n, que
n
es igual a 2n 28 bytes. Esto impli
a que todo apuntador valido siempre tendra los n 3
bits menos signi
ativos
olo
ados en
ero. Estos bits pueden utilizarse para alma
enar
informa
ion adi
ional; en la o
urren
ia, el bit menos signi
ativo de un apuntador puede
fungir
omo indi
ador que se~nale si el apuntador es o no un hilo.
Hay varias maneras de manipular el bit menos signi
ativo de un apuntador. Quiza la
mas sen
illa es mediante opera
iones logi
as sobre el apuntador
omo sigue.
hDeterminar si un apuntador es un hilo 314ai
template <class Node> inline
bool isThread(Node * p)
{
return (Node*) (((long) p) & 1);
}
Denes:
isThread, never used.
314b
314
4.4. Arboles
Binarios
315
}
Denes:
De alguna manera, el hilado plantea una analoga equivalente a la diferen
ia entre listas
enlazadas
ir
ulares y listas enlazadas no
ir
ulares. En este sentido, un arbol hilado se
pare
e a una lista doblemente enlazada. Al igual que en las listas
ir
ulares, es bastante
deseable utilizar un nodo
abe
era. El lazo izquierdo del nodo
abe
era apuntara al primer
nodo injo. El lazo dere
ho apuntara a la raz. El hilo izquierdo del primer nodo injo y
el lazo dere
ho del ultimo nodo injo apuntaran al nodo
abe
era.
La gura 4.17 ilustra un ejemplo de la representa
ion en memoria de un arbol hilado
utilizando un nodo
abe
era. Para notar la reminis
en
ia
on una lista enlazada, tome el
arbol por los nodos B y Z y \estire".
4.4.16
Recorridos pseudo-hilados
Si bien la representa
ion hilada de arboles tiene sus bondades sobre el rendimiento de
los re
orridos, el hilado es mas dif
il de implantar que la representa
ion tradi
ional. Si
optasemos por la representa
ion tradi
ional, >existira aun alguna forma de aprove
har
los punteros nulos desperdi
iados? La respuesta es armativa: los punteros nulos pueden
usarse
omo hilos temporales.
Dado un arbol binario
on raz r, ubiquemos
ual es su prede
esor injo. Esto es
equivalente a determinar
ual es el ultimo nodo que se visita en injo de su rama izquierda.
Podemos determinar tres
asos:
Si L(r) = = r no tiene prede
esor.
Si L(r) 6= = tenemos dos
asos:
Captulo 4. Arboles
316
1. Si R(L(r)) = = L(r) es prede
esor. Si L(r) no tiene rama dere
ha, enton
es
L(r) es el prede
esor de r.
...
..
...
..
...
...
..
...
...
..
.
.
..
...
...
.
.
.
.
...
.
.
...
.
..
...
..
..
..
....
.
.
....
.
.
....
.
...... ....
...
....
....
Predecesor L(r)
R(r)
L(L(r))
2. Si R(L(r)) 6= = el prede
esor es el des
endiente mas a la dere
ha de L(r).
r
L(r)
R(r)
L(L(r))
R(L(r)) Predecesor
316
Estamos en
apa
idad de abordar un algoritmo injo que use hilos par
iales y
uya
forma general sera la siguiente:
hFun
iones de BinNode Utils 299ai+
(298) 311b 343a
template <class Node> inline
void inOrderThreaded(Node * root, void (*visitFct)(Node*))
{
if (root == Node::NullPtr)
return;
Node *p = root, *r = Node::NullPtr, *q;
hre
orrido
injo hilado 317ai
4.4. Arboles
Binarios
317
}
Denes:
(316)
if (q == Node::NullPtr)
{ // No hay rama izq ==> visitar p
(*visitFct)(p);
r = p;
p = RLINK(p);
continue;
}
hsea
if (q != r) // tiene p un predecesor?
{ // si ==> dejar un hilo para luego subir a visitar p
RLINK(q) = p; // Aqu
se coloca el hilo
p = LLINK(p); // Seguir bajando por la izquierda
continue;
}
(*visitFct)(p);
RLINK (q) = Node::NullPtr; // Borrar hilo
r = p;
p = RLINK(p); // avanzar a la rama derecha
}
317b
El me
anismo del algoritmo no es trivial. Para apre
iar
ompletamente su fun
ionamiento, es ne
esaria una eje
u
ion manual, la
ual se delega al le
tor.
hsea q el prede
esor injo de p 317bi
(317a)
// avanzar hacia el nodo m
as a la derecha de la rama izquierda
while (q != r and RLINK(q) != Node::NullPtr)
q = RLINK(q);
Uses RLINK 296.
El hilado par
ial es muy importante porque puede usarse en algoritmos sobre arboles en
los
uales sea deli
ado utilizar pila o re
ursion, pues nos ahorra espa
io en pila, lo que nos
Captulo 4. Arboles
318
garantiza un re
orrido seguro, sin preo
upa
ion por desborde de pila. Consiguientemente,
el hilado debe ser la op
ion a es
oger si la altura del arbol es des
ono
ida.
Hay, sin embargo, tres problemas
on el hilado. El primero es que la algortmi
a es
mas
ompli
ada. El segundo lo
onstituye el eventual
onsumo de tiempo requerido para
en
ontrar el nodo prede
esor antes de bajar un nivel a la izquierda. Finalmente, el ultimo
problema es que los algoritmos que utili
en hilado par
ial no son reentrantes; es de
ir, no
pueden eje
utarse
on
urrentemente sobre el mismo arbol.
4.4.17
Correspondencia entre
arboles binarios y m-rios
Existe un metodo general para representar una arbores
en
ia
omo un arbol binario y
vi
eversa. El pro
edimiento se expli
a en el siguiente algoritmo:
Algoritmo 4.10 (Conversi
on de un
arbol m-rio a uno binario equivalente)
La entrada es un arbol m-rio Tm T .
La salida es un arbol binario T B equivalente a Tm
1. T = .
2. ni Tm aplique las siguientes reglas:
(a) El hijo mas a la izquierda de ni en Tm es el hijo izquierdo de ni T .
(b) El hermano inmediatamente a la dere
ha de ni en Tm es el hijo dere
ho de ni
en T .
A
4.4. Arboles
Binarios
319
Esta observa
ion nos
ondu
e a extender el algoritmo 4.10 para que maneje arbores
en
ias,
lo
ual
onsiste, simplemente, en
onsiderar las ra
es de los arboles de la arbores
en
ia
omo hermanos de una primera raz
ti
ia. En este
aso se debe adoptar un
riterio para
determinar el orden en que los arboles de la arbores
en
ia se pro
esan y que
onsiste en
mirarlos de izquierda a dere
ha.
A
T1 6= T2 = Tb(T1) 6= Tb(T2)
Captulo 4. Arboles
320
A
B
F
L
C
G
H
O
J
R
K
S
P
U
T
Q
Figura 4.21: Arbol
binario equivalente al arbol de la gura 4.19
Demostraci
on (informal)
4.4. Arboles
Binarios
321
Recorrido sufijo
(4.10)
Este re
orrido
orresponde exa
tamente al re
orrido prejo del arbol binario equivalente de la gura 4.21. Despues de todo, el re
orrido prejo es la manera topologi
amente
natural de listar los nodos en un arbol.
El re
orrido sujo de la arbores
en
ia de la gura 4.19 es:
L M F G B O U V P Q H C AI R J D S T K E ,
(4.11)
binario equivalente.
equivalente.
En una arbores
en
ia, los re
orridos injo y por niveles no pueden denirse
on pre
ision. Claramente, hay varias maneras de denir el re
orrido injo sobre una arbores
en
ia, pues existen varias maneras de
olo
ar la raz entre sus subarboles. Del mismo modo,
hay varias formas de denir el re
orrido por niveles en una arbores
en
ia.
La equivalen
ia entre arbores
en
ias y arboles binarios es de suma importan
ia
pra
ti
a. Esen
ialmente, la equivalen
ia signi
a que
ualquier problema representado
on
arbores
en
ias puede representarse y resolverse en el plano de los arboles binarios; el enfoque inverso tambien es posible. Como dise~nadores y programadores, podemos es
oger
la representa
ion mas ade
uada en fun
ion de bondades tales
omo la
omprension del
problema, la e
ien
ia de eje
u
ion, la simpli
idad de la solu
ion y la reutiliza
ion de
algoritmos y de
odigos existentes.
El hilado del arbol binario equivalente puede ser util en
iertas
lases de problemas. La
gura 4.22 muestra el arbol binario hilado equivalente a la arbores
en
ia de la gura 4.19.
El dibujar esta representa
ion
on listas nos permitira aprehender y
orroborar el signi
ado de los hilos y su utiliza
ion para regresar sobre los an
estros. Para ello, re
ordemos
que el re
orrido injo en el arbol binario equivalente se
orresponde
on el re
orrido sujo
en la arbores
en
ia. Por lo tanto, a la ex
ep
ion de los nodos mas a la izquierda y mas a la
dere
ha del arbol binario equivalente, podemos
on
luir
on las siguientes observa
iones
generales sobre los hilos y su sentido respe
to al arbol m-rio:
Captulo 4. Arboles
322
A
B
F
L
..
..
.
..
.
..
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
...
.
.
.
...
.
.
.
..
...
..
.. ...
... ..
... ...
....
.
..
..
.
..
..
...
.
..
.
.
..
.
.
..
.
..
.
..
.
..
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
...
..
.
.
...
..
.
...
..
.
.
...
..
..
... ....
...........
..
....
..
...
.
..
...
....
....
..
..
...
...
..
.
..
... ...
... ..
... ...
.......
... ..
.. ..
... ...
... ...
... ..
.. ...
... ..
.. ...
... ..
.. ...
... .
.. ..
.. ..
..
... ..
... ..
... ....
... ..
.. ...
... ..
...
.. ....
...
.
.
.
... ...
...
...
... ..
.. ..
...
.. ..
..
... .... ....
.......... ..
...
.....
.. ..
...
.. ..
... ....
..
.
...
..
.
...
...
.
....
.
..
..
...
...
... ....
.
... ....
.
......
...
...
..
..
....
..
.....
...
...
.
..
....
...
..
..
..
..
...
...
..
...
.
.
.
.. ..
... ....
.......
P
U
..
...
...
.
....
....
..
...
.
.
....
...
...
...
...
..
... ....
... ...
... ..
.......
..
..
.
..
..
..
.
.
..
.
..
.
..
.
.
..
.
..
.
..
.
.
..
.
.
..
.
.
.
.
.
.
.
.
..
...
.
.
...
..
.
.
.
.
...
.
...
..
..
...
... ....
.... ...
.......
..
.
..
..
..
.
..
.
.
..
.
.
.
.
.
.
..
.
.
..
.
.
.
...
.
..
.
...
..
... ....
... ....
.......
.
..
.
..
..
.
..
.
..
.
.
..
.
..
.
.
..
..
.
..
.
..
.
..
.
..
.
.
.
..
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
..
...
.
.
..
..
...
..
..
..
..
...
...
..
...
.
... ....
.......
..
..
.
..
..
..
..
.
.
..
.
..
.
..
.
.
..
.
.
...
.
.
..
.
.
.
...
.
.
... ..
... ...
.. ..
........
..
..
.
..
..
..
.
..
..
..
..
...
..
.
..
.
..
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
...
.
.
..
.
...
..
...
.
..
...
... ....
... ..
.........
..
..
.
..
..
.
.
.
..
.
..
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
...
..
...
..
..
..
...
...
..
..
.
..
.
.. ...
... ...
......
D
I
.
..
.
..
.
.
...
.
.
.
..
..
.
..
..
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
..
...
.
..
...
.
..
..
.
...
...
.
.
...
... .....
... ....
.....
..
..
.
..
..
.
.
..
.
..
.
.
..
..
.
.
.
...
.
..
..
.
.
..
.
...
.
..
.
... .
.. ....
... ..
.....
...
.
.
..
.
.. ..
.
.. ..
.
.. .
.
.
.
..
.
.. .
...
..
.. .
...
..
.
.. .
.
.
..
.
.
.
.. .
.
.
.
.
.. .
.
.
.
.
.. ..
.
.
.
.
..
..
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
...
.
..
..
...
.
.
.
..
.
.
..
.
...
.
.
.
..
.. .
.
.
... .
...
.. .... .
.
.
.... ... .
....... .
.
..
.
.
..
.
..
..
..
.
..
...
...
..
...
...
.
...
...
...
...
..
... ....
.........
K
..
.
..
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
..
.
.
..
..
.
...
..
.
.
... ..
... ...
......
..
Figura 4.22: Arbol
binario, hilado, equivalente al arbol de la gura 4.19
Un hilo dere
ho apunta, siempre, a su padre, pues este es el su
esor sujo en la
arbores en ia.
Un hilo izquierdo denota que el nodo es hoja en el arbol m-rio; esto se dedu e
4.5
Un TAD gen
erico para
arboles
Los arboles binarios, que aun no hemos estudiado
abalmente, nos permiten resolver una
amplia variedad de problemas. No obstante, en algunas o
asiones puede preferirse un arbol
de algun orden dado. En este sentido, esta se
ion tratara sobre el dise~no e implanta
ion
de un TAD que modeli
e un arbol.
El TAD en
uestion se en
uentra en el ar
hivo htpl tree node.H 322i ,
uya estru
tura
general es
omo sigue:
htpl tree node.H 322i
7
322
7 En
la elabora
ion de este TAD se
onto
on la valiosa ayuda del para la epo
a ex
elso estudiante de
maestra Jose Brito.
...
..
...
..
....
..
..
..
...
..
...
..
...
..
...
...
..
...
....
.
...
.
.
...
...
.
.
...
.
.
.
..
..
...
.
.
..
... ...
..........
...
...
..
..
...
.
...
..
...
..
...
..
...
..
..
...
...
.
... ...
... ...
.. ..
........
323
public:
hM
etodos
};
323a
El TAD Tree Node<T> modeliza un nodo de un arbol general el
ual guarda un atributo generi
o de tipo T espe
i
ado de la siguiente forma:
hAtributos de Tree Node<T> 323ai
(322) 323
T data;
323b
(322) 323d
323
child enlaza a los hijos mas a la izquierda y sibling enlaza a los hermanos. El a eso
323d
Captulo 4. Arboles
324
324a
Estos metodos son publi
os a efe
tos de que otros algoritmos, eventualmente, puedan
realizar modi
a
iones sobre el arbol.
Mu
has ve
es, luego de obtener un puntero Dlink a o desde child o sibling, ne
esitaremos
onvertirlo a un Tree Node<T>. Para ello, nos servimos de los ma
ros ofre
idos
por la
lase Dlink (x 2.4.7):
hM
etodos privados de Tree Node<T> 324ai
(322) 324b
LINKNAME_TO_TYPE(Tree_Node, child);
LINKNAME_TO_TYPE(Tree_Node, sibling);
Denes:
child to Tree Node, used in
hunk 324b.
sibling to Tree Node, used in
hunk 324b.
Uses child 323
, LINKNAME TO TYPE 100, sibling 323
, and Tree Node 322.
324b
A traves de este par de metodos podemos denir el a
eso a los nodos del entorno de
un Tree Node<T>:
hM
etodos privados de Tree Node<T> 324ai+
(322) 324a
Tree_Node * upper_link() { return child_to_Tree_Node(child.get_prev()); }
Tree_Node * lower_link() { return child_to_Tree_Node(child.get_next()); }
Tree_Node * left_link() { return sibling_to_Tree_Node(sibling.get_prev()); }
Tree_Node * right_link() { return sibling_to_Tree_Node(sibling.get_next()); }
Denes:
left link, used in
hunks 326, 327b, 329, and 331.
lower link, used in
hunks 326b and 329.
right link, used in
hunks 326a and 331a.
upper link, used in
hunk 327b.
Uses child 323
, child to Tree Node 324a, sibling 323
, sibling to Tree Node 324a, and Tree Node 322.
324
Es de
ir, el padre y el hijo, a traves de upper link() y lower link(), en
aso de que
se trate de un nodo que sea el mas a la izquierda, y los hermanos izquierdo y dere
ho,
left link() y right link(), si se trata de
ualquier otro nodo diferente al primogenito.
Si bien las listas son
ir
ulares, ellas no tienen nodo
abe
era. Por esta razon, debemos
estar muy pendientes de
ual es el extremo de
ada lista. En este sentido, \mar
aremos"
ada nodo
on banderas que nos indi
aran el tipo de nodo segun que este sea extremo de
alguna lista. Para ello, denimos los siguientes bits:
hAtributos de Tree Node<T> 323ai+
(322) 323
struct Flags
{
unsigned int
unsigned int
unsigned int
unsigned int
is_root
is_leaf
is_leftmost
is_rightmost
:
:
:
:
1;
1;
1;
1;
325
Uses is leaf 325a 431a, is leftmost 325a, is rightmost 325a, and is root 325a.
325a
La observa
ion y modi
a
ion de estas banderas dene los siguientes metodos sobre un
nodo:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 323d 325
bool is_root() const { return flags.is_root; }
bool is_leaf() const { return flags.is_leaf; }
bool is_leftmost() const { return flags.is_leftmost; }
bool is_rightmost() const { return flags.is_rightmost; }
void set_is_root(const bool & value) { flags.is_root = value; }
void set_is_leaf(const bool & value) { flags.is_leaf = value; }
void set_is_leftmost(const bool & value) { flags.is_leftmost = value; }
void set_is_rightmost(const bool & value) { flags.is_rightmost = value; }
Denes:
is leaf, used in
hunks 324{26, 329, 363, 364a, 427f, and 430.
is leftmost, used in
hunks 324{26 and 331{33.
is rightmost, used in
hunks 324{26 and 331a.
is root, used in
hunks 324
, 325b, 327, 328, 330, 332, and 333.
set is leaf, used in
hunk 329.
set is leftmost, used in
hunks 328{30.
set is rightmost, used in
hunks 328{30.
set is root, used in
hunks 328 and 329.
325b
define
define
define
define
ISROOT(p)
ISLEAF(p)
ISLEFTMOST(p)
ISRIGHTMOST(p)
((p)->is_root())
((p)->is_leaf())
((p)->is_leftmost())
((p)->is_rightmost())
325
Denidos todos los atributos de Tree Node<T>, espe
i
amos su
onstru
ion:
publi
os de Tree Node<T> 323bi+
(322) 325a 326a
hM
etodos
Captulo 4. Arboles
326
Tree_Node() { /* empty */ }
Tree_Node(const T & __data) : data(__data) { /* empty */ }
Uses Tree Node 322.
4.5.1
326a
Aparte de las banderas y el dato generi
o, desde un Tree Node<T> pueden observarse
sus nodos adya
entes; es de
ir, su padre, su hijo mas a la izquierda y sus hermanos.
Comen
emos por los hermanos, los
uales son los observadores mas simples:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 325
326b
Tree_Node * get_left_sibling()
{
if (is_leftmost())
return NULL;
return left_link();
}
Tree_Node * get_right_sibling()
{
if (is_rightmost())
return NULL;
return right_link();
}
Denes:
get left sibling, used in
hunks 328b and 333.
get right sibling, used in
hunks 325b, 327a, 328a, and 331{39.
Uses is leftmost 325a, is rightmost 325a, left link 324b, right link 324b, and Tree Node 322.
326b
Desde un nodo, se puede a
eder a sus hijos extremos: el mas a la izquierda y el mas
a la dere
ha:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 326a 327a
Tree_Node * get_left_child()
{
if (is_leaf())
return NULL;
return lower_link();
}
Tree_Node * get_right_child()
{
if (is_leaf())
return NULL;
Tree_Node * left_child = lower_link();
return left_child->left_link();
327
}
Denes:
get left child, used in
hunks 325b, 327a, 328b, 331
, 332b, 334, 335a, and 337{39.
get right child, used in
hunk 333.
Uses is leaf 325a 431a, left link 324b, lower link 324b, and Tree Node 322.
327a
Ambos metodos pueden referir al mismo nodo en
aso de que this tenga un solo hijo o
este va
o.
En
aso de que el grado del nodo sea mayor que dos, el resto de los nodos puede a
ederse mediante get left sibling() y get right sibling(). A efe
tos de la versatilidad,
puede
onvenirnos el a
eso segun el ordinal:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 326b 327b
Tree_Node * get_child(const int & i)
{
Tree_Node * c = get_left_child();
327b
El metodo retorna NULL si index es mayor o igual que el grado del nodo.
Finalmente, el ultimo observador
on
ierne al nodo padre:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 327a 328a
Tree_Node * get_parent()
{
if (is_root())
return NULL;
Tree_Node * p = this;
while (not ISLEFTMOST(p)) // retroceda hasta ser el nodo m
as a la izquierda
p = p->left_link();
return p->upper_link();
}
Denes:
Cuando se
rea un nuevo nodo se asume raz de un arbol. Hay dos tipos de inser
ion:
omo
hermano y
omo hijo. Segun el tipo de inser
ion, el nodo puede dejar de ser raz.
Hay dos formas de insertar
omo hermano: a la izquierda y a la dere
ha. Si el nodo
hermano es raz, enton
es el nodo insertado sigue siendo raz. En este
aso, tratamos
on
una arbores
en
ia asequible mediante los hermanos. En
aso
ontrario, el nodo insertado
omparte el mismo padre.
Captulo 4. Arboles
328
328a
328b
La inser
ion
omo hermano izquierdo es un po
o mas
ompli
ada porque p puede
devenir el hijo mas a la izquierda; en
uyo
aso, el antiguo mas izquierda debe sa
arse de
la lista de hijos child y sustituirse por p.
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 328a 329
void insert_left_sibling(Tree_Node * p)
{
if (p == NULL)
return;
if (not this->is_root())
p->set_is_root(false);
p->set_is_rightmost(false);
Tree_Node * old_next_node = this->get_left_sibling();
if (old_next_node != NULL)
p->set_is_leftmost(false);
else if (not this->child.is_empty()) // verifique si p devendr
a en m
as a
// la izquierda
{
// this es el m
as a la izquierda ==> p pasar
a a ser el primog
enito
Tree_Node * parent = this->get_parent();
Tree_Node * left_child = this->get_left_child();
CHILD_LIST(this)->del(); // sacar this de lista de hijos
// ahora meter a p en la lista de hijos
329
if (parent != NULL)
parent->insert(p);
else
left_child->append(p);
}
this->set_is_leftmost(false);
this->sibling.append(SIBLING_LIST(p));
}
Denes:
329
Ambas primitivas, insert right sibling() e insert left sibling() insertan por la
izquierda y dere
ha y requieren que el nodo a insertar este va
o.
El segundo tipo de inser
ion; es de
ir,
omo hijo, puede realizarse
omo el hijo mas a
la izquierda o el mas a la dere
ha. Las siguientes dos primitivas a
ometen esas tareas:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 328b 330
void insert_leftmost_child(Tree_Node * p)
{
if (p == NULL)
return;
p->set_is_root(false);
if (this->is_leaf())
{
this->set_is_leaf(false);
CHILD_LIST(this)->insert(CHILD_LIST(p));
}
else
{
Tree_Node * old_left_child_node = this->lower_link();
old_left_child_node->set_is_leftmost(false);
p->set_is_rightmost(false);
CHILD_LIST(old_left_child_node)->del();
CHILD_LIST(this)->insert(CHILD_LIST(p));
SIBLING_LIST(old_left_child_node)->append(SIBLING_LIST(p));
}
}
void insert_rightmost_child(Tree_Node * p)
{
if (p == NULL)
return;
p->set_is_root(false);
if (this->is_leaf())
Captulo 4. Arboles
330
{
this->set_is_leaf(false);
CHILD_LIST(this)->insert(CHILD_LIST(p));
}
else
{
Tree_Node * old_right_child_node = this->lower_link()->left_link();
old_right_child_node->set_is_rightmost(false);
p->set_is_leftmost(false);
SIBLING_LIST(old_right_child_node)->insert(SIBLING_LIST(p));
}
}
Denes:
330
Hay una serie de pre
ondi
iones que debe
umplir un nodo para que pueda insertarse
onsistentemente en un arbol, las
uales son, esen
ialmente, que el nodo ya no este atado
a ningun arbol mediante alguna de las primitivas de inser
ion. Ese es el rol de los asertos
olo
ados en las previas
uatro primitivas.
Aunque eventualmente es posible
onstruir un arbores
en
ia mediante algunas de estas
lases de inser
ion, es preferible
onstruir arboles y luego en
adenarlos en un arbores
en
ia
mediante el siguiente metodo:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 329 331a
void insert_tree_to_right(Tree_Node * tree)
{
if (tree == NULL)
return;
if (not this->is_root())
throw std::domain_error("\"this\" is not root");
tree->set_is_leftmost(false);
Tree_Node * old_next_tree = this->get_right_tree();
if (old_next_tree != NULL)
tree->set_is_rightmost(false);
this->set_is_rightmost(false);
SIBLING_LIST(this)->insert(SIBLING_LIST(tree));
}
Denes:
El parametro tree debe imperativamente ser un arbol; es de
ir, tree tiene que ser una
raz; de lo
ontrario se
onsidera un error.
4.5.3
331a
331
Observadores de
arboles
Arbores
en
ias
onstruidas mediante insert tree to right() puede
onsultarse a traves
de los siguientes metodos:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 330 331b
Tree_Node * get_left_tree()
{
if (is_leftmost())
return NULL;
return left_link();
}
Tree_Node * get_right_tree()
{
if (is_rightmost())
return NULL;
return right_link();
}
Denes:
331b
Estos metodos retornan el arbol situado a la izquierda o dere
ha de this, respe
tivamente.
Eventualmente, solo desde el primer arbol de la arbores
en
ia; o sea, desde el mas a
la izquierda, puede
onsultarse el arbol mas a la dere
ha de la arbores
en
ia mediante el
metodo siguiente:
hM
etodos publi
os de Tree Node<T> 323bi+
(322) 331a
Tree_Node * get_last_tree()
{
if (not is_leftmost())
throw
std::range_error("\"this\" is not the leftmost tree in the forest");
return left_link();
}
Denes:
get last tree, never used.
Uses is leftmost 325a, left link 324b, and Tree Node 322.
4.5.4
331
El primer y esen
ial ejer
i
io del TAD re
ien expli
ado es el desarrollo de los re
orridos.
Consideremos, en primer lugar, el re
orrido prejo re
ursivo de un arbol:
hM
etodos utilitarios de arboles 331
i
(322) 332a
template <class Node> static inline
void __tree_preorder_traversal(Node * root, const int & level,
const int & child_index,
void (*visitFct)(Node *, int, int))
Captulo 4. Arboles
332
{
(*visitFct)(root, level, child_index);
Node * child = root->get_left_child();
for (int i = 0; child != NULL;
i++, child = child->get_right_sibling())
__tree_preorder_traversal(child, level + 1, i, visitFct);
}
Denes:
332a
La rutina re
orre re
ursivamente, en prejo, el arbol
on raz root. En
ada visita, se
invo
a a la fun
ion apuntada por (*visitFct),
uyos parametros son el nodo visitado, su
nivel dentro del arbol y su ordinal
omo hijo respe
to a su padre.
tree preorder traversal() no esta destinada
omo interfaz p
ubli
a. En su lugar,
dise~namos dos primitivas publi
as de re
orrido prejo que llaman a la version estati
a,
una para un arbol y otra para una arbores
en
ia:
hM
etodos utilitarios de arboles 331
i+
(322) 331
332b
template <class Node> inline
void tree_preorder_traversal(Node * root, void (*visitFct)(Node *, int, int))
{
if (not root->is_root())
throw std::domain_error("root is not root");
__tree_preorder_traversal(root, 0, 0, visitFct);
}
template <class Node> inline
void forest_preorder_traversal(Node * root,
void (*visitFct)(Node *, int, int))
{
if (not root->is_root())
throw std::domain_error("root is not root");
for (/* nada */; root != NULL; root = root->get_right_tree())
__tree_preorder_traversal(root, 0, 0, visitFct);
}
Denes:
332b
333
4.5.5
333
Destrucci
on de Tree Node<T>
Existen alguna rutinas que podran
onformar una bibliote
a de utilitarios para arboles.
La mayor parte de ellas se delegan a ejer
i
ios. Empero, para ha
er al TAD Tree Node<T>,
mnimamente operativo, nos es esen
ial la destru
ion,
uya espe
i
a
ion es
omo sigue:
hM
etodos utilitarios de arboles 331
i+
(322) 332b 334
template <class Node> inline
void destroy_tree(Node * root)
{
// recorrer los sub
arboles de derecha a izquierda
for (Node * p = root->get_right_child(); p != NULL; /* nada */)
{
Node * to_delete = p;
// respaldar sub
arbol a borrar p
p = p->get_left_sibling(); // Avanzar p a hermano izquierdo
SIBLING_LIST(to_delete)->del(); // eliminar to_delete de lista hermanos
destroy_tree(to_delete); // eliminar recursivamente
arbol
}
// Si to_delete es el m
as a la izquierda ==> sacarlo de lista de hijos
Captulo 4. Arboles
334
if (root->is_leftmost())
CHILD_LIST(root)->del();
delete root;
}
template <class Node> inline
void destroy_forest(Node * root)
{
if (not root->is_leftmost())
throw std::domain_error("root is not the leftmost tree of forest");
if (not root->is_root())
throw std::domain_error("root is not root");
// recorre los
arboles de izquierda a derecha
while (root != NULL)
{
Node * to_delete = root; // respalda ra
z
root = root->get_right_sibling(); // avanza a siguiente
arbol
SIBLING_LIST(to_delete)->del(); // elimine
arbol de lista de
arboles
destroy_tree(to_delete); // Borre el
arbol
}
}
Denes:
4.5.6
334
335
Uses get left child 326b and get right sibling 326a.
4.5.7
335a
B
usqueda por n
umero de Deway
335b
return NULL;
}
Denes:
deway search, never used.
Captulo 4. Arboles
336
4.5.8
B
usqueda y determinaci
on de n
umero de Deway
336a
336b
root,
key,
current_level,
deway [],
size,
n);
search deway() bus
a re
ursivamente en el arbol
uya raz es root la
lave key. A
ada
llamada re
ursiva, se in
rementa el nivel current level, el
ual se usa para a
tualizar el
numero de deway alma
enado en el arreglo deway y
uya
apa
idad es size. El parametro n
tiene sentido si se en
uentra la
lave y alma
ena la longitud del numero de Deway.
La rutina anterior es ini
iada por la siguiente interfaz publi
a:
hM
etodos utilitarios de arboles 331
i+
(322) 336a 337
template <class Node, class Equal> inline
Node * search_deway(Node *
root,
const typename Node::key_type & key,
int
deway [],
const size_t &
size,
size_t &
n)
{
n = 1; // valor inicial de longitud de n
umero de Deway
if (size < n)
throw std::overflow_error("there is no enough space for deway array");
// recorrer los
arboles de la arborescencia
for (int i = 0; root != NULL; i++, root = root->get_right_sibling())
{
deway[0] = i;
Node * result =
__search_deway <Node, Equal> (root, key, 0, deway, size, n);
if (result != NULL)
return result;
}
return NULL;
8 En
parti ular para programas que requieran omo entrada el numero de Deway de un nodo.
337
}
Denes:
337
Nos resta por denir la rutina que re
orrera en prejo el arbol a la busqueda de la
lave:
hM
etodos utilitarios de arboles 331
i+
(322) 336b 338a
template <class Node, class Equal> inline static
Node * __search_deway(Node *
root,
const typename Node::key_type & key,
const size_t &
current_level,
int
deway [],
const size_t &
size,
size_t &
n)
{
if (current_level >= size)
throw std::overflow_error("there is no enough space for deway array");
if (root == NULL)
return NULL;
if (Equal () (KEY(root), key))
{
n = current_level + 1; // longitud del arreglo deway
return root;
}
Node * child = root->get_left_child();
for (int i = 0; child != NULL; i++, child = child->get_right_sibling())
{
deway[current_level + 1] = i;
Node * result = __search_deway <Node, Equal>
(child, key, current_level + 1, deway, size, n);
if (result!= NULL)
return result;
}
return NULL;
}
Denes:
Captulo 4. Arboles
338
4.5.9
338a
La rutina maneja dos tipos generi
os. El primero, TNode, es un nodo m-rio basado en el
TAD Tree Node<T>. El segundo tipo es BNode, el
ual representa el nodo binario y debe
estar basado en BinNode<Key>. La veri
a
ion de estos tipos se realiza impl
itamente
en tiempo de instan
ia
ion de la plantilla
uando se
ompilan las tres penultimas lneas y
se veri
a la existen
ia de los metodos que se invo
an.
La
onversion de un arbol binario ha
ia uno m-rio es un po
o mas laboriosa; razon por
la
ual nos
onviene des
omponerla en rutinas espe
as y separadas que efe
tuen a
iones
on
retas y modularizadas. En este sentido, hay dos a
iones para un Tree Node<T>:
1. Inser
ion de primogenito:
omo ya lo sabemos, en un arbol binario una rama
izquierda representa un primogenito en el arbol m-rio equivalente. Cuando en un
nodo binario miramos su hijo izquierdo lo insertamos en el arbol m-rio
omo primogenito. De este modo, planteamos la primitiva siguiente:
hM
etodos utilitarios de arboles 331
i+
(322) 338a 339a
338b
template <class TNode, class BNode> inline static
9 En
el dise~no e implanta
ion de las fun
iones expli
adas en esta sub-se
ion es menester se~nalar la
parti
ipa
ion fundamental de Juan Fuentes.
339
El parametro lnode es un nodo binario que es hijo izquierdo. El parametro tree node
es un nodo m-rio equivalente al nodo binario padre de lnode.
2. Inser
ion de hermano: analogamente,
uando en el nodo binario miramos su
hijo dere
ho lo insertamos en el arbol m-rio
omo su hermano dere
ho. Esto
ondu
e
a la siguiente primitiva:
hM
etodos utilitarios de arboles 331
i+
(322) 338b 339b
339a
template <class TNode, class BNode> inline static
void insert_sibling(BNode * rnode, TNode * tree_node)
{
if (rnode == BNode::NullPtr)
return;
TNode * sibling = new TNode(KEY(rnode));
tree_node->insert_right_sibling(sibling);
}
Denes:
insert sibling, used in
hunk 339b.
Uses insert right sibling 328a and sibling 323
.
El parametro rnode es un nodo binario que es hijo dere
ho. El parametro tree node
es un nodo m-rio equivalente al nodo binario padre de rnode.
339b
Ahora estamos listos para implantar el algoritmo que
al
ule el arbol m-rio equivalente
a uno binario:
hM
etodos utilitarios de arboles 331
i+
(322) 339a 340
template <class TNode, class BNode> inline static
void bin_to_tree(BNode * broot, TNode * troot)
{
if (broot == BNode::NullPtr)
return;
insert_child(LLINK(broot), troot);
TNode * left_child = troot->get_left_child();
bin_to_tree(LLINK(broot), left_child);
insert_sibling(RLINK(broot), troot);
TNode * right_sibling = troot->get_right_sibling();
Captulo 4. Arboles
340
bin_to_tree(RLINK(broot), right_sibling);
}
Denes:
bin to tree, used in
hunk 340.
Uses get left child 326b, get right sibling 326a, insert child 338b, insert sibling 339a,
LLINK 296, and RLINK 296.
340
4.6
Los arboles se en
uentran entre las estru
turas mas interesantes y
omplejas de la
matemati
a
ombinatoria. Un analisis riguroso de sus propiedades matemati
as esta fuera
del
ontexto de este texto. Requerimos, sin embargo, de un
orpus matemati
o, mnimo,
que nos permita abordar el analisis de las diferentes estru
turas arbol que luego estudiaremos.
Esen
ialmente, lo que nos
on
ierne es
ono
er que tanto un arbol se pare
e a un
arbol, pues, para algunas situa
iones
omputa
ionales, mientras mas un arbol sea un arbol,
enton
es mas e
iente es su utiliza
ion. Notemos que una lista de elementos en
aja en el
on
epto de arbol; empero, obviamente si los arboles tuviesen topologas similares a las
listas, enton
es sera preferible utilizar listas.
4.6.1
Altura de un
arbol
Un primer indi
ador a
er
a de que tan bueno puede ser un arbol es
ono
er las alturas
posibles en fun
ion del numero de nodos, para ello es util estudiar la siguiente proposi
ion.
Proposici
on 4.2 Sea T un arbol m-rio
on n nodos. Enton
es:
log m (n(m 1) + 1) h(T ) n
(4.12)
341
Demostraci
on
h(T ) log m (n(m 1) + 1)
X
i=0
mi = m0 + m1 + m2 + + mh(T)1
(4.13)
mn
X
i=0
= m1 + m2 + + mh(T)1 + mh(T)
(4.14)
n(m 1) + 1 mh(T) =
h(T ) n
Para este
aso, seguimos la lnea de razonamiento inversa al punto anterior; es de
ir,
onstruimos progresivamente un arbol m-rio tratando siempre de aumentar su altura.
Tal arbol es aquel que tiene exa
tamente n niveles y un nodo por nivel. Pues, este
arbol tiene altura n
Esta proposi
ion revela
otas para el mas
orto y largo
amino desde la raz hasta una
hoja en fun
ion de la
ardinalidad del arbol. Suponiendo n nodos, el arbol mas \
orto"
posible tiene altura h(T ) = log m (n(m 1) + 1), mientras que el mas alto h(T ) = n.
Los arboles
on altura mnima son de gran importan
ia
omputa
ional, pues ellos
garantizan la mayor e
ien
ia de mu
hos de los algoritmos sobre arboles. No obstante, a la
fe
ha a
tual, existen po
os esquemas en los
uales se pueda restringir la altura, dinami
a
y e
azmente, para que esta sea mnima. As pues, mu
has ve
es es ne
esario mantener
arboles
uya altura no sea mnima. La
alidad de esta ultima
lase de arboles se mide
en que tanto estos arboles se a
er
an a los de altura mnima y no realmente por el valor
de su altura. Requerimos, enton
es, una medida adi
ional que nos indique que tan bueno
es el arbol. El mar
o
on
eptual de esta medida se denomina \longitud del
amino interno/externo", y se enun
iara en la sub-se
ion siguiente.
Captulo 4. Arboles
342
4.6.2
un nodo
Definici
on 4.3 (Nodo externo) Sea T un arbol m-rio
on n nodos y sea ni alg
in
ompleto de T tal que grado(ni) = m < m. Enton
es, los nodos externos de ni se
denen
omo los grado(ni) m subarboles faltantes.
A menudo es
onveniente y, hasta mas fa
il, analizar un arbol
on una version espe
ial
en que se muestren todos sus nodos externos. Tradi
ionalmente, los nodos internos se
representan
on
r
ulos y los externos
on lneas horizontales. La gura 4.23 ilustra la
version extendida de un arbol binario.
Figura 4.23: Arbol
binario extendido
Por tradi
ion, en expresiones algebrai
as, un nodo interno se denota
omo ni y uno
externo
omo nx.
Proposici
on 4.3 Sea T un arbol m-rio
on n nodos internos. Enton
es, el n
umero de
(4.15)
(m 1) n + 1
Demostraci
on El n
umero de nodos externos es exa
tamente igual al numero de
subarboles nulos; es de
ir, al numero de apuntadores nulos.
Es
laro que existen mn apuntadores. De los n nodos, n1 tienen padre (la raz no).
Existen, enton
es, n 1 ramas o apuntadores diferentes de nulo, por lo que el numero de
nodos externos estara dado por el total de apuntadores menos la
antidad de apuntadores
no nulos. Esto es:
mn (n 1) = (m 1)n + 1
Por la proposi
ion 4.3, para un arbol binario de n nodos, existen 2n apuntadores, n1
apuntadores a nodos internos y n + 1 nodos externos.
Definici
on 4.4 (Cardinalidad del nivel) Sea T un arbol m-rio, enton
es la
ardinalidad del nivel i, denotada |nivel(i)|, se dene
omo el numero de nodos internos en el
nivel i.
Definici
on 4.5 (Longitud del camino interno/externo) Sea T un arbol m-rio
on n
nodos. La longitud del
amino interno del arbol T , denotada
omo IPL(T ), se dene
omo:
X
X
IPL(T ) =
nivel(ni) =
(4.16)
i | nivel (i)|
ni T
ni nodo interno
343
nivel(nx)
(4.17)
nx T
nx nodo externo
343a
El
al
ulo de la longitud del
amino es fundamental para el analisis empri
o de desempe~no de algoritmos basados en arboles binarios de busqueda; una
lase de arbol binario
espe
ial que estudiaremos mas adelante. Esto justi
a dise~nar una primitiva que
al
ule
el IPL:
hFun
iones de BinNode Utils 299ai+
(298) 316 343b
template <class Node> inline static
size_t __internal_path_length(Node * p, const size_t & level)
{
if (p == Node::NullPtr)
return 0;
343b
El parametro level indi
a el nivel a
tual del nodo visitado y tambien debe ini
ializarse
en
ero. La interfaz publi
a internal path length rec() funge pues de \wrapper" que
eje
ute la llamada ini
ial:
hFun
iones de BinNode Utils 299ai+
(298) 343a 383
template <class Node> inline
size_t internal_path_length(Node * p)
{
return __internal_path_length(p, 0);
}
Denes:
internal path length, used in
hunk 343a.
Proposici
on 4.4 Sea T un arbol m-rio, enton
es:
|T | =
m
X
|T i| + 1
(4.18)
IPL(T i) + |T | 1
(4.19)
EPL(T i)
(4.20)
i=1
IPL(T ) =
EPL(T ) =
m
X
i=1
m
X
i=1
(m 1)|T | + 1
Captulo 4. Arboles
344
Las identidades planteadas por esta proposi
ion son los fundamentos de mu
has de las
demostra
iones involu
radas en los arboles.
Proposici
on 4.5 Sea T un arbol m-rio. Enton
es:
EPL(T ) = (m 1) IPL(T ) + m |T |
(4.21)
Demostraci
on (por inducci
on sobre la cardinalidad de T ) Asumiremos que k
representa la
ardinalidad de un arbol Tk.
|Tk| = 0 En este
aso, EPL(T0) = (m 1) IPL(T0) + m |T0| = 0. La proposi
i
on es
m
X
i=1
m
X
i
EPL(Tk+1
) + (m 1)|Tk+1| + 1
i
EPL(Tk+1
) + (m 1)(k + 1) + 1
m
X
i
IPL(Tk+1
) + |Tk+1| 1
i=1
m
X
i
IPL(Tk+1
) (k + 1) + 1
i=1
i=1
m
X
i
) + mk + m 2k
EPL(Tk+1
i=1
m
X
i
)
IPL(Tk+1
(4.22)
i=1
Pm
i
i=1 EPL(Tk+1):
m
m
X
X
i
i
i
EPL(Tk+1) IPL(Tk+1) =
(m 1) IPL(Tk+1) + m|Tk+1| + mk + m 2k
)
IPL(Tk+1
i=1
= (m 1)
= (m 2)
m
X
i=1
m
X
i=1
i
)+m
IPL(Tk+1
m
X
i
| + mk + m 2k
|Tk+1
i=1
i
) + 2mk + m 2k
IPL(Tk+1
i=1
m
X
i
)
IPL(Tk+1
i=1
(4.23)
345
m
i
i
Notemos que, por (4.18), m
i=1 |Tk+1| = (k+1)1 = k. La sumatoria
i=1 IPL(Tk+1)
puede reemplazarse por IPL(Tk+1) k, que es el resultado de despejar la sumatoria
de (4.19) evaluada en Tk+1:
Esta proposi
ion nos ayudara a estudiar los lmites rela
ionados
on la longitud del
amino, los
uales se estable
en en la siguiente proposi
ion.
Proposici
on 4.6 Sea T un arbol m-rio de n nodos. Enton
es:
log m (n(m 1)) mlogm (n(m1)+1) + mn
n(n 1)
IPL(T )
m1
2
(4.25)
IPL(T ) =
n
X
i=
i=0
n(n 1)
2
Para demostrar la
ota inferior planteamos la longitud del
amino externo del arbol de
altura mnima h en fun
ion de la proposi
ion 4.5 (pag. 344):
EPL(T ) hmh = (m 1) IPL(T ) + mn =
IPL(T )
hmh + mn
m1
(4.26)
Por la proposi
ion 4.2 (pag. 340),
ono
emos el valor mnimo de h. Sustituimos, pues,
la parte izquierda de (4.12) en (4.26):
IPL(T )
Varias
lases de arboles no a
otan la altura, pero son, en forma, buenos. El IPL es una
metri
a que permite
omparar arboles entre s y determinar que tan bueno es uno respe
to
al otro.
De
imos que un nodo esta \lleno"
uando este
ontiene todos sus hijos. Por el
ontrario,
de
imos que un nodo esta \in
ompleto"
uando le falta al menos un hijo.
4.6.3
Arboles
completos
Consideremos la manera de
onstruir un arbol m-rio de n nodos
uya longitud del
amino
sea mnima. Para
onstruir tal arbol, debemos
olo
ar la mayor
antidad de nodos en los
niveles inferiores; no debemos
olo
ar nodos en un nivel superior hasta que no se hayan
ompletado todos los niveles prede
esores.
Captulo 4. Arboles
346
Siguiendo esta lnea de razonamiento, podemos ver que solo un nodo, la raz, puede
estar a distan
ia 0 de la raz. A lo sumo m nodos pueden estar a distan
ia 1 de la raz.
A lo sumo m m = m2 nodos pueden estar a distan
ia 2. En general, a lo sumo m
m m . . . m = mi nodos pueden estar en el i-esimo nivel. As pues, la longitud del
amino interno de este arbol sera la suma de los primeros n terminos de las series:
0, |1, 1,{z. . . 1}, |2, 2,{z. . . 2}, |3, 3,{z
. . . , 3}, |4, 4,{z
. . . , 4}, . . . ,
m
m2
ve es
m3
ve es
m4
ve es
h1
h1
, mh1
|m
{z, , m }
antidad de nodos en el u
ltimo nivel
Sea T un arbol de n nodos, de altura h,
uyos nodos estan a las mnimas distan
ias de
la raz. Podemos, enton
es, denotar una expresion de base para la longitud del
amino:
IPL(T ) =
h2
X
i mi + (h 1)n
i=0
i
Para
ontar el numero de nodos en el ultimo nivel razonamos
omo sigue. h1
i=0 m
sera el total de nodos si el ultimo nivel estuviese
ompletamente
lleno. Ahora bien, si el
P
i n nodos para
ompletar
m
ultimo nivel no estuviese
ompleto, enton
es faltaran h1
i=0
el nivel. Ademas, si el ultimo nivel estuviese
ompleto, enton
es habran mh1 nodos en
el ultimo nivel. Sea n el numero de nodos en el ultimo nivel, enton
es:
n = mh1
h1
X
mi n
i=0
= mh1
h1
X
mi + n
(4.28)
i=0
es de
ir, el maximo numero de nodos en el ultimo nivel menos los nodos que faltan para
ompletar el nivel.
Al resolver las sumatorias involu
radas, tenemos:
!
h1
(h 1)mh1 (h 2)mh m
m
IPL(T ) =
+n
+ (h 1) mh1
(m 1)2
m1
(4.29)
n
X
i=1
log m i;
(4.30)
uya solu
ion tambien se delega en ejer
i
io y que puede resolverse por des
omposi
ion de
la sumatoria en dos partes.
Los arboles
ompletos, que son de longitud de
amino mnima, son muy importantes,
pues representan la mayor e
ien
ia posible sobre mu
hos algoritmos.
Una propiedad muy interesante de los arboles
ompletos esta dada por el he
ho de que
ellos pueden representarse se
uen
ialmente en memoria mediante un arreglo. La gura 4.24
ilustra un arbol trinario
ompleto donde
ada nodo esta etiquetado
on su respe
tivo ndi
e
dentro de un arreglo se
uen
ial. La disposi
ion de los ndi
es
orresponde a un re
orrido
por niveles.
Para distinguir esta representa
ion de la expli
ada en x 4.3.2, la denotaremos
omo
\representa
ion mediante arreglo se
uen
ial".
4.7. Heaps
347
10
14 15 16
17 18 19
20 21 22
23 24 25
26 27 28
29 30 31
11 12 13
32 33 34
Figura 4.24: Arbol
trinario
ompleto; nodos enumerados segun posi
ion en arreglo se
uen
ial
Sea i el ndi
e de un nodo ni dentro de un arbol de n nodos representado
on un
arreglo. Sea T (i, j) el j-esimo subarbol de la raz de un subarbol
on ndi
e i (los ndi
es
omienzan en 1). Enton
es:
padre(ni) =
m+i2
m
&
T (i, j) = m(i 1) + 1 + j
i1
m
'
(4.31)
(4.32)
Este par de formulas fun
ionan para
ualquier orden de arbol. Por sus ventajas, la inmensa mayora de autores y
odi
adores preeren esta representa
ion para algoritmos que
manejen arboles
ompletos, notablemente los heaps binarios, una estru
tura muy versatil
que permite, entre otras
osas, ordenar e
ientemente e implantar
olas de prioridad.
Dado un ndi
e i de nodo valido, >de que manera podra un algoritmo dete
tar que el
a
eso al padre o a un hijo de i es valido? Si i = 1 enton
es estamos en el nodo raz. En este
aso, para que un algoritmo sobre otras representa
iones sea portatil a esta representa
ion,
padre(i) debe retornar nulo. Para realizar esto, basta
on un test que verique si i = 1.
Del mismo modo, si T (i, j) > |T |, enton
es el nodo i no tiene un j-esimo nodo.
4.7
Heaps
Comen
emos esta se
ion desde lo abstra
to introdu
iendo la deni
ion de la estru
tura de datos de esta se
ion:
Definici
on 4.6 (Heap) Un \heap"10 T es un arbol
on las dos siguientes propiedades:
10 En
aras de la
ompresion, preservaremos el termino en su voz anglosajona, sin tradu
ion. Cabe
men
ionar, ademas, que el termino \heap" plantea algunas ambiguedades. Al respe
to, es menester ha
er
dos a
laratorias:
1. En ingles, el termino \heap" tiene varias a
ep
iones, pero, en nuestro
aso,
onnota un \monton",
\bulto" o \pila" de
osas. Por ejemplo, \a heap of dirty
lothes", reere a una pila de ropa su
ia.
2. En las
ien
ias
omputa
ionales \heap" se ha utilizado para
onnotar dos
osas. La primera
on
ierne
a una espe
ie de arbol binario, la
ual es el sujeto de estudio de la presente se
ion. La segunda
Captulo 4. Arboles
348
1. Forma: el arbol es
ompleto, lo que impli
a que, pi
tori
amente, exhibe la siguiente
forma:
38
68
31
104
134
149
98
99
69
56
188
243
48
166
112
145
87
185
217
80
200
228
207
la
ual se alma
ena en un arreglo array[] espe
i
ado del siguiente modo:
hmiembros privados de ArrayHeap<Key> 348i
(349f) 349b
T * array;
156
4.7. Heaps
349a
349
349b
Notemos que es array[1] y no array[0]; es por ello que el arreglo se aparta para dim + 1.
Para manejar esta estru
tura de datos, debemos espe
i
ar la dimension del arreglo y
ontar la
antidad de elementos del heap mediante:
hmiembros privados de ArrayHeap<Key> 348i+
(349f) 348 349e
const size_t dim;
size_t
num_items;
Dado un ndi
e i, los a
esos al padre y los hijos se espe
i
an de las siguientes formas:
1. Padre: en este
aso nos basamos en (4.31):
hRutinas heap 349
i
349
(349f) 349d
349f
Estos
al
ulos revelan una ventaja del heap binario respe
to a
ualquiera de otro orden: los
produ
tos y divisiones pueden realizarse rapidamente mediante desplazamientos de bits,
los
uales son substan
ialmente mas rapidos que los produ
tos y divisiones que ofre
e el
pro
esador o una bibliote
a.
Los heaps binarios implantados mediante arreglos se espe
i
an en el ar
hivo
htpl arrayHeap.H 349f i,
uya espe
i
a
ion general es
omo sigue:
htpl arrayHeap.H 349f i
hRutinas heap 349
i
template <typename T, class Compare = Aleph::less<T> >
class ArrayHeap
Captulo 4. Arboles
350
hmiembros
public:
hmiembros p
ubli
os
};
Denes:
ArrayHeap, never used.
de ArrayHeap<Key> 354bi
Inserci
on en un heap
350
Garantizada la propiedad de forma, resta por veri
ar
onrmar y, eventualmente restaurar, la de orden. Para ello, nos valdremos de la rutina:
hRutinas heap 349
i+
(349f) 349d 352
template <typename T, class Compare> inline
void sift_up(T * ptr, const size_t & l, const size_t & r)
{
size_t i, p;
for (i = r; i > l; i = p)
{
p = u_index(i); //
ndice del padre (c = i/2)
if (Compare () (ptr[p], ptr[i])) // satisface propiedad orden?
return; // si, todo el arreglo es un heap
Aleph::swap(ptr[p], ptr[i]); // intercambie y restaure en nivel p
}
}
Denes:
4.7. Heaps
351
33
33
59
118
143
61
108
123
135
59
39
66
169
118
79
100 113
81
256 198 145 138 170 152 182 227 190 144 120
38
(a)
39
143
61
108
123
135
123
66
256 198 145 138 170 152 182 227 190 144 120
( )
59
79
38
169
81
(b)
39
108
135
100 113
33
59
143
38
256 198 145 138 170 152 182 227 190 144 120
33
118
66
169
79
61
100 113
81
38
108
118
143
135
123
79
39
169
66
61
256 198 145 138 170 152 182 227 190 144 120
(d)
100 113
81
351
Eliminaci
on en un heap
La elimina
ion puede plantearse de dos maneras: (1) elimina
ion de la raz y (2) elimina
ion
de
ualquier elemento. En realidad, el
aso (2) es general y
omprende al (1), pero, en favor
de la simpli
idad, trataremos el primero por separado.
Consideremos, pues, la elimina
ion del menor elemento de un heap; es de
ir, su raz.
Desde la perspe
tiva de forma de un heap, eliminar la raz equivale, pi
tori
amente, a lo
siguiente:
Captulo 4. Arboles
352
min
x
lo que, morfologi
amente, no es un heap. Hay, de nuevo, una manera de restaurar la forma,
la
ual equivale, pi
tori
amente, a lo siguiente:
x
x
es de
ir, el ultimo elemento sobrees
ribe a la raz. Alegori
amente, el heap se
orta por el
ultimo nivel del arbol y,
on ese \pedazo", \se rellena el hue
o" dejado por la raz.
El nuevo elemento raz muy probablemente violara la propiedad de orden, la
ual puede
restaurarse mediante el pro
edimiento sift down(), en
argado de inter
ambiar elementos
del heap hasta que la propiedad de orden sea restaurada.
sift down() es ligeramente mas
omplejo que sift up(), pues hay que mirar los dos
hijos del eventual elemento violador y es
oger para inter
ambiar el menor de ellos. De este
modo, se garantiza la propiedad de orden. El pro
eso
ontinua en el siguiente nivel hasta
que los dos hijos sean mayores o se al
an
e el ultimo nivel.
81
38
59
38
108
118
135
143
79
39
169
123
61
66
59
256 198 145 138 170 152 182 227 190 144 120
(a)
100 113
81
81
108
118
135
143
169
123
(b)
38
59
143
59
39
108
135
123
81
169
66
256 198 145 138 170 152 182 227 190 144 120
( )
61 100 113
66
256 198 145 138 170 152 182 227 190 144 120
38
118
79
39
79
61 100 113
39
108
118
143
135
123
61
169
66
79
81 100 113
256 198 145 138 170 152 182 227 190 144 120
(d)
Figura 4.27: Opera
ion sift down() sobre la raz 33 en el heap resultante de la gura 4.26
352
4.7. Heaps
353
353a
Con lo anterior en mente, podemos plantear la elimina
ion del siguiente modo:
hEliminar raz en heap 353ai
(355
)
353b
Captulo 4. Arboles
354
354a
4.7.3
Colas de prioridad
Existe una amplsima gama de situa
iones en las
uales dinami
amente se requiere manejar
un
onjunto de
laves y
ono
er el menor elemento en
ualquier momento. Para tal tipo
de
ir
unstan
ia, se emplea un TAD
ono
ido
omo \
ola de prioridad".
En opera
ion y desempe~no, la
ola de prioridad se resume en la tabla siguiente:
Nombre de opera
ion
insert(item)
top()
remove(n)
354b
354
El primer
onstru
tor aparta memoria para un arreglo de dimension d. El segundo
onstru
tor re
ibe la dire
ion base de un arreglo, previamente apartado, de dimension d.
La bandera array allocated indi
a si se aparto o no el arreglo y se dene as:
hmiembros privados de ArrayHeap<Key> 348i+
(349f) 349e
mutable bool array_allocated;
354d
4.7. Heaps
355a
355
355b
355
(355)
La inser
ion y elimina
ion tambien son muy sen
illas, dado que todo el trabajo ya ha
sido realizado:
hmiembros p
ubli
os de ArrayHeap<Key> 354bi+
(349f) 355a 355d
T & insert(const T & key) throw(std::exception, std::overflow_error)
{
if (num_items >= dim)
throw std::overflow_error("Heap out of capacity");
nadir
hA~
return array[num_items];
}
T getMin() throw(std::exception, std::underflow_error)
{
hVeri
ar heap va
o 355bi
T ret_val = array[1];
hEliminar
return ret_val;
}
Re
ordemos que, segun se desprende de los lemas 4.1 y 4.2, los bloques hA~
nadir elemento
al heap 351i y hEliminar raz en heap 353ai son O(lg(n)). Puesto que el resto del entorno
de insert() y getMin() es O(1), enton
es estas opera
iones son O(lg(n)).
4.7.3.1
355d
Modificaci
on de prioridad
Dentro del mantenimiento de un
onjunto,
on una prioridad aso
iada a sus elementos, es
plausible modi
ar el valor de prioridad de un elemento parti
ular. Como esta opera
ion
no altera topologi
amente al arbol, la propiedad de forma esta garantizada. Lo uni
o que
debemos
er
iorar es que el orden se preserve, lo que se realiza mediante la siguiente
opera
ion:
hmiembros p
ubli
os de ArrayHeap<Key> 354bi+
(349f) 355
void update(T & data)
{
Captulo 4. Arboles
356
La rutina asume que el valor de prioridad de la i-esima entrada del arreglo ha sido modi
ada y restaura la propiedad de orden.
4.7.4
Heapsort
Cada pasada demora O(n) O(lg(n)) = O(n lg(n)). Por tanto, el pro
edimiento es
O(n lg (n)) + O(n lg (n)) = O(n lg (n)), independientemente de la permuta
ion del arreglo.
Sin embargo, el uso del heap requiere alma
enar todos los elementos del arreglo, por lo
que el
onsumo de espa
io es O(n);
ara
tersti
a indeseable en mu
has
ir
unstan
ias.
Si hemos
omprendido bien que un heap puede representarse
on un arreglo, enton
es
no es dif
il aprehender que podemos utilizar el propio arreglo que pretendemos ordenar
para guardar el heap. Para ello, es ne
esario invertir la
ondi
ion de orden de modo tal que
la raz alma
ene el mayor entre todos los elementos. Esto se realiza fa
ilmente mediante
una
lase envoltoria que reali
e la inversion :
hCompara
i
on invertida 356bi
12
356b
11 Revsese,
4.7. Heaps
357
Denes:
De este modo, la primera fase puede plantearse, pi
tori
amente, de la siguiente manera:
Heap entre 1 e i 1
Desorden
i
357a
la
ual re
orre el arreglo desde 2 hasta n, eje
utando, a
ada itera
ion i, sift up <T,
Inversed Compare<T, Compare>> (arreglo, i); es de
ir,
orrigiendo la eventual viola
ion que a
arreara insertar un nuevo elemento en la posi
ion i. La primera fase puede
odi
arse, enton
es,
omo sigue:
hConvertir arreglo en un heap
on sift up() 357ai
(357
)
for (int i = 2; i <= n; ++i)
sift_up <T, Aleph::Inversed_Compare<T, Compare> > (array, 1, i);
La idea es
omenzar desde el heap
omprendido entre [1 . . . 1] y luego, a
ada itera
ion i,
se asume una inser
ion en la posi
ion i. Al arreglo entre [1 . . . i]] se le restaura la propiedad
de heap mediante sift up(array, 1, i).
En el bloque hConvertir arreglo en un heap
on sift up() 357ai no debe olvidarse
que array debe estar desplazado en uno, pues, re
ordemos, el arreglo en realidad
omienza
en el ndi
e 0, pero sift up() y sift down() asumen que se
omienza en 1.
Al nal de la primera fase, todo el arreglo es un heap
uyo maximo elemento se en
uentra en la raz. A partir del he
ho de que la posi
ion denitiva del mayor elemento
es n, planteamos la segunda fase del siguiente modo:
swap(ptr[1], ptr[i])
Heap entre 1 e i
Orden denitivo
i
357b
357
(357
358
)
for (int i = n; i > 1; --i)
{
Aleph::swap(array[1], array[i]); // colocar en la ra
z i-
esimo item
sift_down <T, Aleph::Inversed_Compare<T, Compare> > (array, 1, i - 1);
}
Captulo 4. Arboles
358
hConvertir
hOrdenar a partir
}
Denes:
heapsort, never used.
on sift up()
Desorden
Heap entre i + 1 y n
i
358a
o sea, un barrido desde la ultima posi
ion array[n] hasta la primera array[1]. Podemos
realizar esto mediante sift down():
hConvertir arreglo en un heap
on sift down() 358ai
for (int i = n - 1; i > 1; --i)
sift_down <T, Aleph::Inversed_Compare<T, Compare> > (array, i, n);
El analisis de este bloque es un po
o mas sutil. Cada llamada a sift down()
uesta
O(lg(r) lg (l)), lo que nos da:
n1
X
i=1
358b
n1
X
i=1
r
O lg
l
= O(n) ;
(4.33)
oste que es mejor que el de O(n lg(n)) aso
iado al bloque hConvertir arreglo en un heap
on sift up() 357ai.
Hay, aun, una mejora demas si aprehendemos el he
ho de que, para l > n2 , array[l..n]
es,
on
ertitud, un heap en propiedad de orden, pues, para l > n2 = 2l > n; es de
ir,
no hay des
endientes. Por tanto, la velo
idad de hConvertir arreglo en un heap
on
sift down() 358ai, puede dupli
arse de la siguiente manera:
hConvertir arreglo en un heap
on sift down() mejorado 358bi
(358
)
for (int i = n/2; i >= 1; --i)
sift_down <T, Aleph::Inversed_Compare<T, Compare> > (array, i, n);
358
hOrdenar
}
Denes:
4.7. Heaps
4.7.5
359
Los heaps son muy importantes en situa
iones en las
uales se requiera mantener y
ono
er
rapidamente los valores lmites de un
onjunto segun algun
riterio de orden; es de
ir,
su(s) menor(es) elemento(s) o su(s) mayor(es). Hay abundantes o
asiones en que esto es
ne
esario. A
ontinua
ion, estable
eremos una lista in
ompleta de las que
reemos son las
mas populares:
1. Aritmetica flotante: para la pre
ision de punto
otante, es
rti
o el orden en que
se reali
en las opera
iones, el
ual,
asi nun
a ne
esariamente,
orresponde
on el
expresado por el programador. Por ejemplo, la suma pierde pre
ision si un numero
muy peque~no es sumado (o multipli
ando) a uno grande. Un esquema de alta pre
ision mantiene en un heap los operandos, de manera tal que la suma privilegia los
operandos peque~nos.
2. Simulacion a eventos discretos: mu
has situa
iones de la vida real se modelizan y
estudian mediante \eventos". Grosso modo, un evento es una representa
ion objetiva
de algun su
eso de interes dentro de la situa
ion que se desea estudiar. Como objeto,
un evento tiene dos atributos fundamentales:
(a) Tiempo de o
urren
ia te.
(b) A
ion modi
ante sobre el estado del sistema, la
ual se divide en la modi
a
ion de las variables del modelo y, sobre todo, en la genera
ion de nuevos
eventos en el futuro.
Los sistemas de simula
ion mantienen un tiempo simulado
orrespondiente al tiempo
en que se eje
uta la simula
ion del evento a
tual. Ini
ialmente, se programan algunos
eventos ini
iales a eje
utarse en algunos tiempos jos. Por ejemplo,
onsideremos tres
eventos ini
iales a eje
utarse en tres tiempos:
e1
e2
e3
El tiempo de simula
ion
omienza en el tiempo te1 , que es
uando o
urrira e1. Los
eventos se
olo
an en una lista ordenada por tiempo. El simulador, sistemati
amente,
extrae de aquella lista el proximo evento en el tiempo. Esto es lo maravilloso de un
simulador a eventos dis
retos, pues el tiempo se simula mediante desplazamientos
dis
retos entre los eventos, y no
ontinuamente
omo a
ae
e en la vida real o en un
modelo abstra
to
ontinuo.
Ahora bien,
omo ya lo expresamos,
ada evento genera nuevos eventos,
uyos tiempos de o
urren
ia se
ono
en durante la genera
ion. Supongamos, que e1 genera
{e11 , e12 , e13 },
uyos tiempos de o
urren
ia se pi
torizan as:
e1 e13 e12 e2
e11
e3
Observemos que los nuevos eventos e13 , e12 o
urriran antes que e2; esto quiere de
ir
que el proximo evento a simular es e13 y no e2,
ual era en que estaba programado
Captulo 4. Arboles
360
antes de simular e1. El simulador se per
ata de esto porque la lista esta abstra
tamente ordenada por tiempo. Mantener una se
uen
ia ordenada por tiempo no solo
es
ostoso en dura
ion, sino inutil, pues lo que en realidad se requiere es
ono
er el
proximo evento mas inmediato en tiempo; es de
ir, el menor.
La lista de eventos de un simulador se implementa mediante un heap. El simulador extrae del heap el menor evento en el tiempo. El heap permite, tambien,
simular e
ientemente la
an
ela
ion de un evento. Para ello, se usa la opera
ion sift down up().
3. Codigos de Huffman: seran estudiados en x 4.13 (pagina 421).
4. B
usqueda: En
ontrar los m menores elementos de un
onjunto de
ardinalidad n m; por ejemplo,
ono
er los 1000 menores elementos entre un
onjunto
de 109
laves. En este
aso, se debe usar un \heap nito"; es de
ir, uno
uya
apa
idad este limitada a m. A medida que el heap se va a
tualizando, se debe
ono
er
el mayor elemento entre los m alma
enados, de manera tal que este sea substituido
por el nuevo menor en
ontrado.
5. Procesamiento ordenado: en general, toda situa
ion en la
ual se requiera pro
esar de primero el menor elemento, es muy sus
eptible de
onsiderar un heap.
4.7.6
360
El TAD BinHeap<Key>
Hay situa
iones algortmi
as, sobre todo aquellas que aspiran generalidad, en las
uales no
se tiene idea exa
ta de la
antidad de elementos que se pro
esaran. Este des
ono
imiento,
aunado a la es
ala, puede
ompli
ar y
omprometer
onsiderablemente el uso de un arreglo, pues, al menos nuestra implanta
ion ArrayHeap<Key>, requiere que
onoz
amos su
dimension. Si la dimension es muy grande y se usan varios heaps (lo
ual puede ser el
aso en algoritmos sobre grafos), enton
es se puede in
urrir en un desperdi
io de memoria
importante. Si la dimension es peque~na, enton
es se puede sa
ri
ar pro
esamiento de alta
es
ala. En estos tipos de situa
iones se debe
onsiderar un heap altamente dinami
o que
onsuma memoria exa
tamente propor
ional a la
antidad de elementos que maneja.
Una alternativa, delegada en ejer
i
io, tanto en estudio
omo en dise~no e implanta
ion,
es utilizar arreglos dinami
os del tipo DynArray<T> desarrollado en x 2.1.5 (pagina 44).
Esta variante, aunque mas
exible que el mero arreglo estati
o, tambien puede in
urrir en
los mismos problemas, pues se tiene un desperdi
io debido a las entradas no usadas del
dire
torio, segmento y bloque.
Una desventaja de implementar un heap mediante un arreglo es que, puesto que los
elementos se mueven, no se pueden mantener apuntadores sobre ellos. Este in
onveniente
puede paliarse si en el ArrayHeap<Key> guardamos punteros tipeados a los datos y programamos la
lase de
ompara
ion para que opere sobre punteros tipeados. Pero esto
onsume espa
io y dura
ion adi
ionales debido al apuntador extra y a su dereferen
ia.
En esa se
ion desarrollaremos una solu
ion basada en nodos binarios del
tipo BinNode<Key> y denida en el ar
hivo:
htpl binHeap.H 360i
on de nodo de BinHeap<Key> 363i
hDeni
i
hDeni
i
on
4.7. Heaps
361
de BinHeap<Key> 362ai
hMiembros
public:
hMiembros
};
de BinHeap<Key> 364bi
GenBinHeap<NodeType, Key, Compare implanta un heap binario mediante nodos binarios de tipo NodeType. BinHeap<Key, Compare> y BinHeapVtl<Key, Compare> son es-
Captulo 4. Arboles
362
guarde el
amino de a
eso desde la raz hasta el nodo sobre el
ual se eje
uta sift up() o sift down(). La segunda
onsiste en mantener, por
ada nodo,
un puntero adi
ional al padre.
El primer enfoque tiene la ventaja de que
onsume menos espa
io que el segundo,
pero el manejo de la pila desde la raz hasta el nodo de opera
ion
onsume un
tiempo O(lg(n)).
2. Cuando o
urre una inser
ion o elimina
ion, es ne
esario a
eder al padre del nodo
mas a la dere
ha del ultimo nivel, pues puede requerirse a
tualizar sus enla
es.
Sea last el nodo mas a la dere
ha del ultimo nivel. >Como
ono
er este nodo y su
padre? Per
atemosnos de que last y su padre pueden
ambiar
ada vez que o
urre
una modi
a
ion sobre heap.
Un enfoque para determinar last
onsiste en dividir su
esivamente el valor de
ardinalidad del heap. Si la
ardinalidad es n, enton
es n mod 2 nos indi
a si last
es hijo izquierdo o dere
ho. Su
esivamente, n mod 4 nos di
e si el padre de last
es des
endiente izquierdo o dere
ho. El pro
edimiento se repite hasta al
anzar la
raz; es de
ir, hasta que n/i = 1. En este momento, tenemos el numero de Deway,
invertido, de last y, a partir de el, toda su as
enden
ia.
El
al
ulo anterior es O(lg(n)), tanto en espa
io
omo en dura
ion. Pudiera pensarse que el mantener permanentemente la se
uen
ia de Deway ha
ia last, ahorra
tiempo, pero, en el peor
aso, puede ser ne
esario a
tualizarla hasta la raz y esto
uesta O(lg(n)).
362a
En nuestro dise~no transaremos por la rapidez. Para sortear el primer obsta
ulo,
usaremos un puntero al padre, el
ual posibilita todo el
ontexto para las opera
iones
de inter
ambio entre niveles requeridas por sift up() y sift down().
Para evitar el segundo obsta
ulo, aprove
haremos los n + 1 punteros nulos del heap
para mantener una lista, doblemente enlazada,
ir
ular, de los nodos borde de los ultimos
niveles; es de
ir, una lista
onformada por los nodos hojas y, eventualmente, el nodo de
padre de last, el
ual es in
ompleto
uando last es hijo izquierdo. Una instan
ia de esta
estru
tura de datos puede pi
torizarse
omo en la gura 4.28. last siempre apunta al
nodo mas a la dere
ha del ultimo nivel. head es un nodo
abe
era,
entinela, que permite
tratar de manera general el inter
ambio entre la raz y alguno de sus dos hijos. root es
un puntero referen
ia, permanente, a la raz, que es hija dere
ha de head. Requerimos,
enton
es, los siguientes atributos para el TAD BinHeap<Key>:
hAtributos de BinHeap<Key> 362ai
(360)
Node
Node *
Node *&
Node *
size_t
362b
head_node;
head;
root;
last;
num_nodes;
GenBinHeap()
: head(&head_node), root(RLINK(head)), last(&head_node), num_nodes(0)
{ /* empty */ }
4.7. Heaps
363
head
1
root
10
11
12
last
363
Las
onsidera
iones de dise~no anteriores nos llevan a
olo
ar la siguiente informa
ion
adi
ional por nodo
hDeni
i
on de nodo de BinHeap<Key> 363i
(360)
class BinHeapNode_Data
{
struct Control_Fields // Definici
on de banderas de control
{
int is_leaf : 4; // true si el nodo es hoja
int is_left : 4; // true si el nodo es hijo izquierdo
};
BinHeapNode_Data * pLink; // puntero al padre
Control_Fields control_fields;
public:
BinHeapNode_Data() : pLink(NULL)
{
control_fields.is_leaf = true;
control_fields.is_left = true;
}
BinHeapNode_Data *& getU() { return pLink; }
Control_Fields & get_control_fields() { return control_fields; }
void reset()
Captulo 4. Arboles
364
{
control_fields.is_leaf = true;
control_fields.is_left = true;
}
};
DECLARE_BINNODE(BinHeapNode, 64, BinHeapNode_Data);
Denes:
BinHeapNode, never used.
BinHeapNode Data, never used.
Uses DECLARE BINNODE 291b and is leaf 325a 431a.
Esta lase nodo justi a denir los ma ros siguientes a favor de la legibilidad de odigo:
364a
hDeni
i
on de ma
ros de BinHeap<Key> 364ai
# define PREV(p)
(p->getL())
# define NEXT(p)
(p->getR())
# define ULINK(p)
reinterpret_cast<Node*&>((p)->getU())
# define IS_LEAF(p)
((p)->get_control_fields().is_leaf)
# define IS_LEFT(p)
((p)->get_control_fields().is_left)
# define CTRL_BITS(p) ((p)->get_control_fields())
Denes:
CTRL BITS, never used.
IS LEAF, never used.
IS LEFT, never used.
ULINK, never used.
Uses is leaf 325a 431a.
(360)
364b
hIndeni
i
on de ma
ros
# undef PREV
# undef NEXT
# undef ULINK
# undef IS_LEAF
# undef IS_LEFT
# undef CTRL_BITS
(360)
364
de BinHeap<Key> 364bi
Si IS LEAF(p) == true, enton
es NEXT(p) y PREV(p)
ontienen el su
esor y prede
esor de la lista de hojas. Si, por el
ontrario, IS LEAF(p) == false, enton
es los mismos
ampos se usan
omo LLINK(p) y RLINK(p).
Mediante el
ampo IS LEAF(p) podemos determinar si p pertene
e o no a la lista
enlazada:
hMiembros privados de BinHeap<Key> 364
i
(360) 365a
static bool is_in_list(Node * p)
{
if (IS_LEAF(p))
return true;
13
4.7. Heaps
365a
365
365b
Quiza la rutina esen
ial de nuestra implanta
ion es la que inter
ambia dos nodos de nivel.
Llamaremos a esta rutina swap with parent(p), y su mision es inter
ambiar el nodo p
on su padre. Esta es la rutina mas
ompli
ada de la implanta
ion porque el inter
ambio
debe distinguir los
asos parti
ulares en los
uales p no tiene abuelo (
uando solo hay dos
o tres nodos) y el
aso en que p este en ultimo nivel, situa
ion en la
ual p es parte de la
lista.
La rutina swap with parent() es bastante
ompli
ada y no se muestra en este texto
(ella puede examinarse en la bibliote
a). swap with parent() opera independientemente
de la situa
ion de p dentro del heap. Podemos, pues, emplearla para efe
tuar los inter
ambios requeridos por sift up() y sift down(), la
uales, una vez realizada la opera
ion swap with parent(), son mu
ho mas simples que su
ontraparte
on arreglos:
hMiembros privados de BinHeap<Key> 364
i+
(360) 365a 367a
virtual void sift_up(Node * p)
{
while (p != root and Compare() (KEY(p), KEY(ULINK(p))))
swap_with_parent(p);
}
virtual void sift_down(Node * p)
{
Node *cp; // guarda el menor hijo de p
while (not IS_LEAF(p))
{
cp = LLINK(p);
if (has_sibling(cp))
if (Compare() (KEY(RLINK(p)), KEY(LLINK(p))))
cp = RLINK(p);
if (Compare() (KEY(p), KEY(cp)))
return;
swap_with_parent(cp);
}
}
Uses LLINK 296 and RLINK 296.
Captulo 4. Arboles
366
4.7.6.2
366
Inserci
on en BinHeap<Key>
La rutina sift up() nos permite espe
i
ar la primera rutina publi
a de BinHeap<Key>:
la inser
ion, la
ual se dene del siguiente modo:
hMiembros p
ubli
os de BinHeap<Key> 362bi+
(360) 362b 367b
Node * insert(Node * p)
{
if (root == NULL) // heap est
a vac
o?
{ // S
, inicialice
root
= p;
LLINK(p)
= RLINK(p) = p;
ULINK(p)
= head;
IS_LEAF(p) = true;
IS_LEFT(p) = false; /* root is right child of header node */
last
= root;
num_nodes = 1;
return p;
}
// inserci
on general
Node * pp = RLINK(last); // padre de actual last
LLINK(p) = last;
ULINK(p) = pp;
if (IS_LEFT(last))
{ // p ser
a hijo derecho
IS_LEFT(p)
= false;
RLINK(p)
= RLINK(pp);
LLINK(RLINK(pp)) = p;
RLINK(pp)
= p;
}
else
{ // p ser
a hijo izquierdo
IS_LEFT(p) = true;
RLINK(p)
= pp;
IS_LEAF(pp) = false; // si p es izquierdo ==> pp era hoja
LLINK(pp)
= p;
}
RLINK(last) = p;
last
= p;
num_nodes++;
sift_up(last);
return p;
}
4.7. Heaps
367
4.7.6.3
Eliminaci
on del mnimo elemento de un BinHeap<Key>
Para la elimina
ion por la raz, apli
amos la misma estrategia expli
ada en x 4.7.2 (pagina 351):
inter
ambiar la raz por last y luego \
ortar" por last. Para ello, nos valemos en primer
lugar de una rutina swap root with last(),
uya mision es inter
ambiar la raz
on last
y que no se muestra en este texto (ella tambien puede ser examinada en la bibliote
a).
Pi
tori
amente, swap root with last(), a nivel de los nodos, no de sus
ontenidos,
realiza la siguiente opera
ion:
root
last
367a
Falta \
ortar" el nodo mas a la dere
ha del ultimo nivel, lo
ual se realiza as:
hMiembros privados de BinHeap<Key> 364
i+
(360) 365b 371a
Node *
{
Node
Node
Node
remove_last()
* ret_val = last;
* pp
= ULINK(last);
* new_last = LLINK(last);
if (IS_LEFT(last))
{
IS_LEAF(pp) = true;
LLINK(pp)
= new_last;
}
else
{
RLINK(pp)
= RLINK(last);
LLINK(RLINK(last)) = pp;
}
RLINK(LLINK(last)) = pp;
last = new_last;
num_nodes--;
ret_val->reset();
return ret_val;
}
Denes:
remove last, never used.
Uses LLINK 296 and RLINK 296.
367b
Esta maquinaria nos permite denir getMin() exa
tamente
omo expli
ado
en x 4.7.2 (pagina 351); es de
ir, inter
ambiar root
on last, luego
ortar por last:
hMiembros p
ubli
os de BinHeap<Key> 362bi+
(360) 366 368
Captulo 4. Arboles
368
4.7.6.4
368
Actualizaci
on en un heap
4.7.6.5
Eliminaci
on de cualquier elemento en un BinHeap<Key>
Quiza una de las opera
iones que mas
obre sentido
on este tipo de implanta
ion de heap
es la de eliminar, arbitrariamente,
ualquier elemento, dada la dire
ion del nodo. Un ejemplo de esta situa
ion es la
an
ela
ion de un evento, tanto en un sistema de simula
ion a
eventos dis
retos,
omo en un manejador de eventos en tiempo real . En este
aso, teniendo un apuntador al evento,
ual puede, por ejemplo, derivar de BinHeap<Time>::Node,
puede eliminarse del heap
ualquier elementos.
Es
onveniente realizar que en la implanta
ion del heap
on arreglos, esta opera
ion
requiere un
ierre de bre
ha que es O(n).
14
14 En ALEPH,
4.7. Heaps
369
369
Para eliminar node se opera pare
ido a su
ontraparte ArrayHeap<Key>: (1) se desenlaza last del arbol y se respalda en una variable p; esto se realiza
on la opera
ion,
ya denida, remove last(). Luego, (2) se substituye el nodo a eliminar node por p y,
nalmente, se eje
uta sift down() y sift up() para ajustar la propiedad de orden.
El paso (2) de la opera
ion anterior se vale la opera
ion replace node(Node *
node, Node * new node),
uyo rol es reemplazar dentro del arbol binario el nodo node
por new node. Todo el
ontexto ne
esario esta dado, pues
ada nodo
ontiene un puntero
a su padre. Esta opera
ion puede
onsultarse en la bibliote
a.
Ahora, solo nos resta remitirnos a la te
ni
a expli
ada para implantar la elimina
ion
de un nodo node:
hMiembros p
ubli
os de BinHeap<Key> 362bi+
(360) 368 371b
Node * remove(Node * node) throw(std::exception, std::underflow_error)
{
if (root == NULL)
throw std::underflow_error ("Heap is empty");
if (node == root)
return getMin();
if (node == last)
return remove_last();
Node * p = remove_last();
if (node == last)
{
remove_last();
insert(p);
return node;
}
replace_node(node, p);
update(p);
node->reset();
return node;
}
4.7.6.6
Destrucci
on de BinHeap<Key>
Al igual que
on otros enfoques de solu
ion del problema fundamental que hemos estudiado hasta el presente, en lugar de manejar dire
tamente las
laves del
onjunto, el TAD
BinHeap<Key> maneja los nodos que las
ontienen. Del mismo modo, en fun
ion del prin
ipio n-a-n, otro TAD, en nuestro
aso DynBinHeap<Key>, se en
arga de manejar las
laves sin que el usuario tenga por que pensar en los nodos
omo objetos de manipula
ion
del heap.
Captulo 4. Arboles
370
GenBinHeap
NodeType
Key
Compare
head_node : Node
head : Node*
root : Node*&
last : Node*
num_nodes : size_t
is_in_list(p : Node*) : bool
has_sibling(p : Node*) : bool
swap_with_parent(p : Node*)
sift_up(p : Node*)
sift_down(p : Node*)
swap_root_with_last()
remove_last() : Node*
replace_node(node : Node*, new_node : Node*)
+ GenBinHeap()
+ ~ GenBinHeap()
+ insert(p : Node*) : Node*
+ update(p : Node*)
+ size() : const size_t&
+ is_empty() : bool
# advance_left(p : Node*) : Node*
# advance_right(p : Node*) : Node*
# verify_heap(p : Node*) : bool
+ verify_heap() : bool
+ remove_all_and_delete()
Key
Compare
BinHeapVtl
Key
Compare
BinHeap
T
Compare
DynBinHeap
+ empty()
+ ~ DynBinHeap()
+ insert(item : ) : T
+ getMin() : T
370
Este bloque
umple la fun
ion, pero a expensas de un
oste en tiempo que puede devenir
importante, pues es O(n lg(n)), que es mayor que O(n), el tiempo que nos lleva el barrido
de los elementos y su
orrespondiente elimina
ion. Desde esta perspe
tiva se nos revela
ne
esario proveer desde el TAD BinHeap<Key> una primitiva que elimine todos los elementos en O(n). En tal sentido, haremos un re
orrido sujo, re
ursivo, que libere los
4.7. Heaps
371a
371
(360) 367a
static void __postorder_delete(Node * p, Node * incomplete_node)
{
if (IS_LEAF(p))
{
delete p;
return;
}
__postorder_delete(LLINK(p), incomplete_node);
if (p != incomplete_node)
__postorder_delete(RLINK(p), incomplete_node);
delete p;
}
Uses LLINK 296 and RLINK 296.
371b
Puesto que en la estru
tura de datos se
ombina la estru
tura arbol binario
on lista
doblemente enlazada, se debe tener espe
ial
uidado
uando el ultimo nodo (last), sea
izquierdo; solo en este
aso p sera un nodo de un solo hijo, por lo que la llamada ha
ia la
dere
ha sera in
orre
ta y
ausante de una doble elimina
ion.
Esta rutina se invo
a por la siguiente interfaz publi
a:
hMiembros p
ubli
os de BinHeap<Key> 362bi+
(360) 369 372
void remove_all_and_delete()
{
if (root == NULL)
return;
if (num_nodes <= 3)
{
while (not this->is_empty())
delete getMin();
return;
}
if (IS_LEFT(last))
__postorder_delete(root, ULINK(last));
else
__postorder_delete(root, NULL);
// reiniciar como si se hubiese llamado a constructor
root = NULL;
last = &head_node;
num_nodes = 0;
}
Dado que el heap es un arbol
ompleto, el riesgo de desborde de pila debido a la re
usion
es mnimo.
Captulo 4. Arboles
372
La rutina remove all and delete() es invo
ada por el destru
tor del TAD DynBinHeap<Key>,
el
ual,
omo es de suponerse, es una extension de BinHeap<Key> orientada a manejar
laves. De este modo, la destru
ion es O(n).
4.7.6.7
Otras operaciones
BinHeap<Key> tiene otras opera iones mu ho mas simples de implantar y entender que
372
las anteriores.
Para
onsultar el menor elemento, se apela top():
hMiembros p
ubli
os de BinHeap<Key> 362bi+
(360) 371b
Node * top() const throw(std::exception, std::underflow_error)
{
if (root == NULL)
throw std::underflow_error ("Heap is empty");
return root;
}
4.8
Enumeraci
on y c
odigos de
arboles
Abordemos un problema
ombinatorio que, si bien ata~ne a los arboles, rela
iona a mu
hos otros problemas
ombinatorios: >
uantos arboles diferentes pueden
onstruirse
on n
nodos? Por ejemplo, estos son todos los posibles arboles de 4 nodos:
Re
ordemos que existe una
orresponden
ia unvo
a entre los arboles y los arboles binarios (x 4.4.17 (pagina 318), x 4.1 (pagina 319)). Dado un arbol
ualquiera de n nodos,
enton
es, segun x 4.4.17, la raz de su equivalente binario no tiene rama dere
ha. Restan, enton
es, n1 nodos para
ombinar en la rama izquierda. Por lo tanto, el numero de posibles
arboles que se pueden
onstruir
on n nodos es equivalente al numero de arboles binarios
que se pueden
onstruir
on n 1 nodos. Podemos resolver este problema de
onteo en el
ontexto mas familiar y, quiza mas simple, de los arboles binarios.
4.8.0.8
C
odigos de un
arbol binario
Comen
emos por enun
iar el
on
epto de
odigo de un arbol binario. Para ello dibujemos
un arbol extendido -
on sus nodos externos- tal
omo el de la gura 4.30.
Definici
on 4.7 Sea un arbol binario T de n nodos. Se dene
odigo(T ) : B {a, b}
omo el re
orrido prejo del arbol extendido donde sus n nodos internos estan etiquetados
on a y sus externos
on b.
4.8. Enumeraci
on y c
odigos de
arboles
373
a
a
a
a
a
a
b
b
bbbbbb
Figura 4.30: Un arbol binario extendido: nodos internos etiquetados
on a; nodos externos
on b
Proposici
on 4.7 El lenguaje de Lukasiewi
z, de todos los
odigos posibles esta denido
por:
(4.34)
L = aLL b
Cuando tenemos una palabra w = uv; es de
ir, que puede
omponerse de la
on
atena
ion de otras dos palabras u y v, enton
es a u se le di
e que es prejo de w. Por ejemplo,
si w = abbba, enton
es , a, ab, abb, abbb, abbba son prejos de w .
En lo que respe
ta a los prejos, denotaremos:
15
u v si u es prejo de v
u<v
Captulo 4. Arboles
374
Sean u1, u2, v1, vx, x L | |u1| < |u| , |u2| < |u| , |u2| < |u| , |v1| < |v| , |v2| < |v|.
Reali
emos las siguientes des
omposi
iones:
u = au1u2
v = av1v2
Y plateamos:
au1u2x = av1v2 = u1u2x = v1v2
v1
u2
x
v2
4.8. Enumeraci
on y c
odigos de
arboles
u1
v1
375
u2
v2
Inyectividad (=): Apli aremos indu ion sobre las ardinalidades de T1 y T2.
w1 =
odigo(T1) = au1v1
w2 =
odigo(T2) = au2v2
u1 =
odigo(L(T1))
v1 =
odigo(R(T1))
u2 =
odigo(L(T2))
v2 =
odigo(R(T2))
Ahora que la proposi
ion es
ierta para dos arboles de una misma
ardinalidad
nominal y probamos si la proposi
ion es
ierta para arboles de
ardinalidad
mayor. Por la hipotesis indu
tiva, tenemos que o u1 6= u2 o v1 6= v2. Debemos
responder si:
au1v1 6= au2v2
(4.35)
Para ello, planteemos el siguiente lema:
Lema 4.4 Sean u1, u2, v1, v2 L. Si u1 6= u2 o v1 6= v2, enton
es u1v1 6= u2v2.
Demostraci
on La demostra
ion se deriva del lema 4.3. Si negamos el
lema 4.3, tenemos que u1 6= u2 o v1 6= v2 = u1v1 6= u2v2
on u1, u2, v1, v2
{Lenguaje prejo}. Empero, por la proposi
ion 4.8 sabemos que L es un lenguaje
prejo. As pues, el lema es
ierto.
El lema 4.4 nos garantiza que (4.35) es
ierto para arboles binarios de
ardinalidad mayor, lo que impli
a que
odigo(T ) : B L es inye
tiva.
La inye
tividad prueba T1, T2 B, T1 6= T2 =
odigo(T1) 6=
odigo(T2)
Captulo 4. Arboles
376
w L T B | odigo(T ) = w
(4.36)
La demostra
ion es por indu
ion sobre la longitud de w. La hipotesis indu
tiva es
(4.36).
|w| = 1 =
odigo() = b = (4.36) es
ierto para |w| = 1.
Ahora asumimos que (4.36) es
ierto para todo |w| = n y veri
amos si la
hipotesis es satisfe
ha para |w| = n + 1.
(4.37)
Demostraci
on
(a) Sea T un arbol binario
ualquiera
on w =
odigo(T ).. Por la deni
ion de
odigo,
sabemos que |w|a = |T |; es de
ir, el numero de nodos internos. Por la proposi
ion 4.3,
sabemos que T extendido tiene 2|T |+1 nodos; es de
ir n nodos internos y n+1 nodos
externos. As pues, |w| = 2|T | + 1 = 2|w|a + 1 = |w|a + |w|b = |w|b = |w|a + 1
(b) (indu
ion sobre |w|)
|w| = 1 = w = b = u = = |u|a = 0 |u|b = 0. (b) es
ierto para el
aso
base.
Sea |w| = n + 1 y asumamos (b)
ierto para todo |w| < n + 1. Por deni
ion,
w tiene que estar
ompuesto
omo w = auv; El smbolo a por la hipoteti
a
raz de un arbol binario y u y v los
odigos de las rama izquierda y dere
ha,
respe
tivamente.
Consideremos un prejo w
ualquiera de w, el
ual puede tener alguna de las
siguientes
ongura
iones:
4.8. Enumeraci
on y c
odigos de
arboles
w a
Caso 1: w a
Caso 2: w a
377
u
u
u
v
v
En el
aso 1, |u | < |u|, por lo que la hipotesis indu
tiva es
ierta entre u y u.
Por lo tanto, |u |a |u |b y, obviamente, |au | |u |.
En el
aso 2, |v | < |v|, por lo que la hipotesis indu
tiva es
ierta entre v y
v; lo que permite determinar que |uv |a |uv |b y, en
onse
uen
ia, |auv |a
|auv |b
Esta proposi
ion nos da una manera e
ientsima de veri
ar si una se
uen
ia de bits
es una palabra Lukasiewi
z; o sea, si una palabra se
orresponde
on el
odigo de un
arbol binario.
4.8.0.9
C
odigos de Dick
b
b
a
b
b
a
a ba b a b
Captulo 4. Arboles
378
Demostraci
on
D = aDbD
on
atenamos la e
ua
ion
on b =
Db = (aDbD )b =
Db b
Db |{z}
Db = a |{z}
|{z}
L
segun (4.34)
Estamos en
apa
idad de formular un nuevo problema
ombinatorio: >
uantas expresiones que utili
en n parentesis izquierdos y n parentesis dere
hos estan balan
eadas? Por
ejemplo, ((()())()), es una expresion balan
eada, mientras que ()(((()()))())) no lo es -falta
un parentesis izquierdo-.
La respuesta esta dada por la
antidad de palabras de Di
k de longitud 2n. En efe
to,
si
onsideramos el
ara
ter a
omo el parentesis izquierdo y el b
omo el dere
ho, una
palabra de Di
k representa una expresion
on parentesis bien formada.
4.8.1
N
umeros de Catalan
re urrente:
n=0
CiCni1 n > 0
4.8. Enumeraci
on y c
odigos de
arboles
379
a a a a b b a b b a a b b b a b a b b
Figura 4.32: Camino de Catalan del arbol de la gura 4.31
Catalan es formado por una se
uen
ia de puntos en el plano:
P(w) =
x = xi1 + 1
si wi = a
x = xi1 1 si wi = b
a1 a2 . . . an
2n
antidad de
aminos que
ruzan el eje x
n
(4.39)
Requerimos, pues,
ontar la
antidad de
aminos que
ruzan el eje x y que terminan
en el eje x,
orrespondiente a una palabra w / D | |w|a = |w|b. Sea
P = {(0, 0), (1, y1), . . . , (2n 1, y2n1), (2n, 0)} ,
un onjunto de puntos que representa un amino de Catalan que ruza el eje x orrespondiente a una palabra w / D. Por ejemplo, el siguiente amino de Catalan:
Captulo 4. Arboles
380
a a b b b a a b b a a b
orresponde a la palabra aabbbaabbaab / D,
Sea p = (x , 1) P el primer punto de P por debajo del eje x, enton
es podemos
des
omponer:
P(w) = P(uv) = P(u) P(v) = {(0, 0), (1, y1), . . . , (x , 1)} (P(w) P(u))
Para todo P(w) = uv, w / D, u
orresponde al prejo de w entre {(0, 0), . . . , (x , 1)}; es
de
ir, el prejo hasta el primer punto que este por debajo del eje x. Denamos el
amino
siguiente:
P (w) = P (u) (P(w) P (u)) ;
b b a a a a a b b a a b
4.8. Enumeraci
on y c
odigos de
arboles
381
La rela
ion P(w) P (w) | w / D es una fun
ion biye
tiva. Geometri
amente,
P(w1), P(w2), P(w1) 6= P(w2) = P (w1) 6= P (w2) tendra una imagen u
ni
a. Lo mismo
2n
2n
1
2n
=
n
n+1
n+1 n
Los numeros de Catalan tienen una importan
ia tras
endental en la
ombinatoria, pues
existen mu
has situa
iones de
onteo identi
adas mediante esta
lase de numero. Entre
alguna de las apli
a
iones tenemos:
La
antidad de maneras en que un polgono de n + 2 lados puede ser
ortado en n
triangulos. Por ejemplo, hay 14 63 = 5 diferentes maneras de
ortar un pentagono en
triangulos.
(n1((n2n3)n4)) ((n1n2)(n3n4))
((n1(n2n3))n4) (((n1n2)n3)n4)
El n
umero de arboles binarios de n nodos.
El n
umero de arboles de n + 1 nodos.
El n
umero de
aminos de longitud 2n en una malla n n que estan por debajo de
la diagonal. Por ejemplo, para una malla 4 4, hay 15 84 = 14
aminos diferentes
El n
umero de palabras de Di
k de longitud 2n o el numero de palabras Lukasiewi
z
de longitud 2n + 1.
El n
umero de
aminos en el plano,
onformados por 2n puntos, que parten desde
(0, 0) y terminan en (2n, 0) que no
ruzan el eje x.
Captulo 4. Arboles
382
4.9
Arboles
binarios de b
usqueda
Supongamos que 1300 millones de personas estan sus
ritas al servi
io telefoni
o. Dado el
nombre de un subs
riptor, >
omo averiguar su numero telefoni
o? Si los registros de aquellas 1300 millones de personas estan ordenados alfabeti
amente en un ar
hivo, enton
es la
busqueda binaria nos permitira en
ontrar el numero de
elular de Zhang Shunniean Cheung Wu en a lo sumo lg 1300000000 = 31 intentos.
La busqueda binaria exige que la se
uen
ia de datos este ordenada y esto es dif
il de
garantizar, pues es de esperar que a menudo aparez
an nuevos subs
riptores y desaparez
an otros. A
tualizar en lnea una se
uen
ia ordenada de numeros telefoni
os sera muy
ine
iente, pues la inser
ion y supresion en una se
uen
ia ordenada es O(n).
Afortunadamente, si bien no podemos mantener sin
ostes importantes una se
uen
ia
ordenada, s podemos simularla mediante un arbol binario que emule la busqueda binaria
y que nos ofrez
a ordenamiento y rapidez para las opera
iones de inser
ion, busqueda y
supresion.
Definici
on 4.9 (Arbol
binario de b
usqueda) Sea un arbol binario T =< L(T ), raiz(T ), R(T ) >,
enton
es, T es un arbol binario de busqueda (ABB) si y solo si:
ni L(T ) = KEY (ni) < KEY (raiz(T ))
y
T ABB
ni R(T ) = KEY(n1) > KEY (raiz(T ))
Esta regla se
ono
e
omo la propiedad o
ondi
ion de orden de un arbol binario de
busqueda.
Para disminuir la longitud del dis
urso, en mu
hos
ontextos utilizaremos \ABB" para
denotar un \arbol binario de busqueda".
Dado un nodo
ualquiera, todos los nodos en el subarbol izquierdo deben tener
laves
menores, mientras que todos los nodos en el subarbol dere
ho deben tener
laves mayores.
El arbol de la gura 4.35 satisfa
e la deni
ion.
El orden se desta
a
uando se observa el re
orrido injo. Para el arbol de la gura 4.35,
el re
orrido injo es: 48 59 110 121 140 156 187 214 225 226 229 233 253 261
285 288 324 328 345 359 371 386 423 432 442 446 451 462 465 493 . Este orden
es dedu
ible de la deni
ion: visite los nodos a la izquierda, los
uales son menores a la
raz, luego visite la raz y, nalmente, visite los nodos a la dere
ha, que son mayores que
la raz.
4.9. Arboles
binarios de b
usqueda
383
225
48
288
187
156
253
214
140
229
226
233
345
285
261
324
328
386
359
110
59
371
121
432
423
442
446
465
462
451
383
493
Captulo 4. Arboles
384
384a
right root es el primer nodo, de izquierda a dere
ha, que sea mayor que la raz
(preorder[inf]); este nodo es la raz del subarbol dere
ho. Para lo
alizarlo, re
orremos
linealmente el arreglo:
hSea first greater el primer nodo mayor que la raz 384ai
(383)
int first_greater = l + 1;
Este nodo parti
iona el arreglo en dos partes. La primera
omprendida entre l + 1
y right root - 1,
orresponde al re
orrido prejo del subarbol izquierdo. La segunda
parte,
omprendida entre right root y r,
orresponde al re
orrido prejo del subarbol
dere
ho.
4.9.1
384b
B
usqueda en un ABB
Consideremos T ABB y una
lave k a bus
arse en el arbol. Para ha
erlo, inspe
ionamos KEY(T ), si k = KEY(T ), enton
es k ha sido en
ontrado. De lo
ontrario,
omparamos k < KEY(T ); si el predi
ado es
ierto, enton
es bus
amos en el subarbol izquierdo;
de lo
ontrario, bus
amos en el subarbol dere
ho. Si durante este pro
eso nos en
ontramos
on un subarbol va
o, enton
es
on
luimos que k no se en
uentra dentro del arbol.
El siguiente algoritmo re
eja este pro
eso:
hFun
iones de BinNode Utils 299ai+
(298) 383 385
template <class Node, class Compare> inline
Node * searchInBinTree(Node * root, const typename Node::key_type & key)
{
Node * p = root;
while (p != Node::NullPtr)
if (Compare () (key, KEY(p)))
p = static_cast<Node*>(LLINK(p));
else if (Compare() (KEY(p), key))
p = static_cast<Node*>(RLINK(p));
else
break; // se encontr
o!
return p;
}
Denes:
//
//
//
//
Est
a en rama izquierda?
baje a rama izquierda
Est
a en rama derecha?
baje a rama derecha
4.9. Arboles
binarios de b
usqueda
385
Esta primitiva bus
a en el arbol un nodo
on valor de
lave key y retorna su dire
ion
si la
lave se en
ontro o Node::NullPtr de lo
ontrario.
searchInBinTree() se utiliza en la mayora de TAD fundamentados en ABB.
El desempe~no de este algoritmo depende de
uan equilibrado este el arbol. Si este tiende
a estar
ompleto, enton
es su altura tiende a lg |T |. De este modo, si garantizamos que el
arbol sea equilibrado, enton
es la busqueda en un arbol binario no requerira mas de lg |T |
ompara
iones; exa
tamente la misma
alidad de servi
io de la busqueda binaria. El tru
o
es, pues, lograr que las inser
iones y supresiones del arbol lo mantengan en equilibrio.
4.9.1.1
B
usqueda del menor y del mayor elemento de un ABB
M
nimo
385
L(T )
R(T )
M
aximo
Captulo 4. Arboles
386
386
B
usqueda del predecesor y sucesor
Dado T ABB, >
ual es el su
esor de KEY(T )? Por la propiedad de orden de un ABB,
sabemos que este su
esor, si existe, se en
uentra en el nodo mas a la izquierda de la rama
dere
ha y la
ondi
ion de existen
ia es, justamente, que exista una rama dere
ha. De lo
anterior, se dedu
e el algoritmo siguiente:
hFun
iones de BinNode Utils 299ai+
(298) 385 387
template <class Node> inline
Node * find_successor(Node* p, Node *& pp)
{
pp = p;
p = static_cast<Node*>(RLINK(p));
while (LLINK(p) != Node::NullPtr)
{
pp = p;
p = static_cast<Node*>(LLINK(p));
}
return p;
}
Denes:
L(T )
R(T )
Predecesor Sucesor
Figura 4.36: Ubi
a
iones generales del nodo prede
esor y su
esor de la raz de un arbol T
La busqueda del prede
esor es analoga.
4.9. Arboles
binarios de b
usqueda
4.9.1.3
387
387
B
usquedas especiales sobre un ABB
En algunas no tan ex
ep
ionales o
asiones se requiere \en
ontrar" un nodo que, en lugar
de
orresponderse
on la
lave, de alguna forma guarde otra rela
ion
on ella. En esta
situa
ion en
ajan dos tipos de busqueda: la del padre de una
lave pertene
iente al arbol
y la del que sera el padre de una
lave no pertene
iente.
La primera busqueda se expresa del siguiente modo:
hFun
iones de BinNode Utils 299ai+
(298) 386 388
template <class Node, class Compare> inline
Node * search_parent(Node *
root,
const typename Node::key_type & key,
Node *&
parent)
{
Compare cmp;
Node * p = root;
while (true)
if (cmp(key, KEY(p)))
{
if (LLINK(p) == Node::NullPtr)
return p;
parent = p;
p = static_cast<Node*>(LLINK(p));
}
else if (cmp(KEY(p), key))
{
if (RLINK(p) == Node::NullPtr)
return p;
parent = p;
p = static_cast<Node*>(RLINK(p));
}
else
return p;
}
Denes:
search parent() bus
a la
lave key en el ABB
uya raz es root. La rutina retorna el
nodo que
ontiene a key y su nodo padre se guarda en el parametro por referen
ia parent,
80
30
90
Captulo 4. Arboles
388
El arbol
ontiene las
laves 10, 25, 30, 50, 80, 90 entre un
onjunto innito de posibles valores. Si el arbol
re
iese \naturalmente"; es de
ir, solo por sus puntas, >en donde
olo
ar
nuevas
laves?. La respuesta se eviden
ia en el arbol siguiente:
50
25
10
[..9] [11..24]
80
30
[26..29]
[31..49]
[51..79]
90
[81..89]
[91..]
388
}
Denes:
este parrafo las no
iones de \espa
io", \natural" y \fsi
o" se enun
ian en el sentido de la
ien
ia
fsi
a moderna. Se usan
omillas y enfasis itali
o porque tales ideas que, si bien tambien son abstra
tas
en nuestro mundo
otidiano, son aun mas abstra
tas en el dis
urso de la programa
ion de
omputadores;
maxime
uando el
on
epto de arbol binario es,
omo tal, pura abstra
ion.
4.9. Arboles
binarios de b
usqueda
389
La rutina bus
a la
lave key en arbol
on raz root y retorna el nodo que sera padre
de una
lave key.
4.9.2
389a
El TAD BinTree<Key>
etodos
hM
};
de BinTree<Key> 390bi
htpl
problema fundamental de estru
turas de datos mediante un ABB. Esta
lase solo
esta destinada a implantar y no es de uso del
liente.
17
BinTree<Key> que es una
lase parametrizada que implanta el problema fundamental a traves de un arbol binario de busqueda de nodos binarios
on
laves de tipo Key.
Esta
lase es implantada dire
tamente por deriva
ion publi
a de GenBinTree. As
pues, su interfaz es exa
tamente la misma que GenBinTree.
BinTreeVtl<Key> que es equivalente al BinTree<Key> salvo que los nodos poseen
389b
Para fa
ilitar los algoritmos, todos los tipos abstra
tos basados en arboles binarios de
busqueda utilizaran un nodo
abe
era:
hmiembros dato de BinTree<Key> 389bi
(389a)
17 Re
ordar x
1.3.
Captulo 4. Arboles
390
Node
headNode;
Node * head;
Node *& root;
headNode representa el nodo
abe
era, head es la dire
ion de la
abe
era y root es una
referen
ia al lazo dere
ho de head. En otros terminos, RLINK(head) == root
Las primitivas de GenBinTree manipulan nodos binarios
uya espe
i
a
ion se exporta
390a
(389a)
public:
typedef NodeType<Key> Node;
private:
GenBinTree
+GenBinTree()
+~GenBinTree(): virtual
+getRoot(): Node*&
-insert(p:Node*): Node *
+search(key:const Key&): Node *
+remove(key:const Key&): Node*
+verifyBin(): bool
+reset(): void
+split(in k:Key,inout l:Node *,inout r:Node *): void
+join(inout tree:GenBinTree&,inout dup:GenBinTree&): void
Key:class
Compare:class
Key:class
Compare:class
BinTree
BinTreeVtl
(389a) 390
390
Un observador esen
ial, sobre todo para poder liberar la memoria o
upada por el
arbol binario, es la
onsulta de la raz:
hM
etodos de BinTree<Key> 390bi+
(389a) 390b 392
Node*& getRoot() { return root; }
4.9. Arboles
binarios de b
usqueda
4.9.3
391
Inserci
on en un ABB
La inser
ion obede
e al \
re
imiento natural de un arbol"; es de
ir, por las puntas u
hojas. Dado un nodo a insertar p, debemos en
ontrar un nodo externo para sustituirlo
por p de manera tal que no se viole la propiedad de orden de un ABB. Asumiendo que
las
laves no se repiten,
ondi
ion que desde ahora imponemos al TAD BinTree<Key>,
existe un solo nodo externo para sustituir por p. Por ejemplo,
onsideremos insertar un
nodo que
ontenga valor de
lave 27:
23
23
15
15
10
47
31
47
10
59
31
27
El \ex-nodo-externo", hijo izquierdo del 31, fue sustituido por uno interno
on valor
27, el
ual, a traves de sus dos nodos externos, abre dos \bre
has" en el arbol. La primera
esta dada por el lazo izquierdo del 27 y puede albergar
laves entre [24..26], mientras que
la segunda bre
ha la representa el lazo dere
ho y podra albergar
laves entre [28..30].
391
El ejer
i
io anterior nos indi
ia que para efe
tuar una inser
ion es menester bus
ar
el nodo externo a substituir. Ahora bien, a efe
tos de poder \enlazar" el nuevo nodo,
se requiere obtener un puntero al que sera el padre del nodo a insertar -en el ejemplo,
un puntero al nodo
on
lave 31-. Si bien esta a
ion puede instrumentarse mediante la
rutina search rank parent() estudiada en x 4.9.1.3, es preferible, a efe
tos de la simpli
idad, \memorizar" este puntero realizando una busqueda re
ursiva y luego insertando el
nuevo nodo. Este es el enfoque del siguiente algoritmo:
hFun
iones de BinNode Utils 299ai+
(298) 388 393
template <class Node, class Compare> inline
Node * insert_in_binary_search_tree(Node *& root, Node * p)
{
if (root == Node::NullPtr) // Inserci
on en
arbol vac
o?
return root = p;
if (Compare () (KEY(p), KEY(root)))
// p < root?
// insertar en sub
arbol izquierdo
return insert_in_binary_search_tree <Node, Compare>
(static_cast<Node*&>(LLINK(root)), p);
59
Captulo 4. Arboles
392
Denes:
insert in binary search tree, used in hunks 392, 399, and 400a.
392
La rutina inserta el nodo p en el ABB
on raz root y retorna la raz del arbol binario
resultante en
aso de que la inser
ion tenga exito; es de
ir, si la
lave
ontenida en p no
se en
uentra en el arbol. De lo
ontrario, puesto que no se permiten
laves dupli
adas, se
retorna Node::NullPtr.
Notemos que el parametro root se pasa por referen
ia, pues, en el
aso base de la
re
ursion, se modi
a la raz.
Mediante la anterior rutina generi
a, la implanta
ion del metodo insert() de
BinTree<Key> es mera
uestion de forma:
hM
etodos de BinTree<Key> 390bi+
(389a) 390
394
Node * insert(Node *p)
{
return insert_in_binary_search_tree<Node, Compare>(root, p);
}
Uses insert in binary search tree 391.
4.9.4
Partici
on de un ABB por clave (split)
En esta opera
ion, un arbol T
on se
uen
ia inja generi
a Si =< k1, k2, k3, . . . , kn > se
parti
iona segun una
lave kx en dos arboles T< y T> tal que Si< =< k1, k2, k3, . . . , ... > |
ki T<, ki < kx y Si> =< . . . , kn > | ki T>, ki > kx.
La primitiva que nos realiza la parti
ion se denomina split key rec(T, kx, T<, T>);
donde T es el arbol a parti
ionar, kx es la
lave de parti
ion y T< y T> son los arboles
resultantes de la parti
ion.
El prin
ipio re
ursivo de la parti
ion es simple y se pi
toriza en el diagrama siguiente:
split key rec(T, k x , T < , T > )
KEY(T )
if k x >
KEY(T )
split key rec(R(T), k x , L(T), T > )
KEY(T )
L(T )
T<
L(T )
R(T )
T<
T>
T>
=
(
) Situa
ion ini
ial
Llamemos kx a la
lave de parti
ion y supongamos kx > KEY(T )). En este
aso, se
efe
tua una llamada re
ursiva split key rec(R(T ), kx, T< , T> ) sobre R(T ), la
ual
4.9. Arboles
binarios de b
usqueda
393
393
arroja dos arboles T< y T> produ
to de parti
ionar R(T )
on kx. El resultado denitivo es
. La otra parti
i
T< =< L(T ), T, L(T ) ) >; donde L(T ) = T<
on, T>, es dire
tamente el arbol
T> = T>.
Si kx < KEY(T )) el algoritmo es simetri
amente identi
o.
El
aso base de la re
ursion es la parti
ion sobre el arbol va
o, la
ual arroja dos
arboles va
os.
Con lo anterior, el algoritmo resultante debera ser
omprensible sin di
ultades:
hFun
iones de BinNode Utils 299ai+
(298) 391 395
# define SPLIT split_key_rec<Node, Compare>
Captulo 4. Arboles
394
# undef SPLIT
Denes:
split key rec, used in
hunks 394 and 398.
Uses LLINK 296 and RLINK 296.
split key rec() tiene dos parametros de entrada: la raz del arbol a parti
ionar root
y la
lave de parti
ion key. Los parametros de salida son dos arboles l y r. Despues de la
llamada, l
ontiene la
laves menores que key y r
ontiene las mayores o iguales.
Si la
lave no se en
uentra en el arbol, enton
es split key rec() retorna true para
indi
ar que el arbol fue parti
ionado. De lo
ontrario, se deja el arbol inta
to y se retorna false.
19
29
13
45
split key rec(25, 19, ...)
25
30
77
12
20
15
27
53
91
22
18
394
Pi
tori
amente, la parti
ion puede interpretarse
omo el trazado de una lnea divisoria
segun la
lave de parti
ion. La gura 4.38 ilustra tal lnea
on
lave 19 y se indi
an
las llamadas re
ursivas de la parti
ion. Desde el nodo raz 29, se determina que debemos
parti
ionar la rama izquierda de raz 13, pues 19 es menor que 29. split key rec(13, 19,
l, r) retorna la parti
ion del subarbol
on raz 13. El parametro l es el arbol T< resultado
de split key rec (29, 19, l, r), mientras que el arbol T< sera < r, 29, R(29) >.
Con la rutina anterior, ya estamos listos para dise~nar la version de la parti
ion para el
TAD BinTree<Key>:
hM
etodos de BinTree<Key> 390bi+
(389a) 392 397b
bool split(const Key & key, GenBinTree & l, GenBinTree & r)
{
return split_key_rec<Node, Compare>(root, key, l.root, r.root);
}
Uses split key rec 393.
4.9.5
Uni
on exclusiva de ABB (join exclusivo)
Consideremos dos arboles T< y T> resultantes de split key rec() y la opera
ion inversa:
\pegar" T< y T> en un arbol T . Re
ordemos que en este
aso, T< y T> son ex
luyentes.
Llamemos a esta opera
ion join exclusive(T<, T>), la
ual retorna el arbol
orrespondiente de unir T<
on T>. Cali
amos la opera
ion de \ex
lusiva" porque sabemos que los
rangos de
laves no se solapan y que T< T> = , lo
ual es un
ono
imiento muy valioso,
4.9. Arboles
binarios de b
usqueda
395
pues nos permite realizar un algoritmo parti
ular para este
aso
on mejor desempe~no que
el de la union general si los intervalos se solapan o los elementos se
omparten.
La siguiente gura ilustra los
omponentes generales de T< y T> que requerimos identi
ar para dise~nar join exclusive(T<, T>):
join(T<,T>)
T<
L(T<)
T>
R(T<)
L(T>)
R(T>)
join(R(T<),L(T>))
R(T>)
R(T<)
395
L(T>)
El
aso base del algoritmo es que alguno de los dos arboles sea va
o.
Lo anterior nos
onlleva al siguiente algoritmo:
hFun
iones de BinNode Utils 299ai+
(298) 393 397a
template <class Node> inline
Node * join_exclusive(Node *& ts, Node *& tg)
{
if (ts == Node::NullPtr)
return tg;
if (tg == Node::NullPtr)
return ts;
LLINK(tg) = join_exclusive(RLINK(ts), LLINK(tg));
RLINK(ts) = tg;
Node * ret_val = ts;
ts = tg = Node::NullPtr; // deben quedar vac
os despu
es del join
return ret_val;
Captulo 4. Arboles
396
}
Denes:
Esta rutina nos sera de suma utilidad para simpli
ar
onsiderablemente el algoritmo de
elimina
ion de un ABB.
4.9.6
Eliminaci
on en un ABB
Esen
ialmente, la elimina
ion por
lave en un ABB exhibe dos fases: bus
ar el nodo que
ontenga la
lave y luego quitar el nodo del arbol. Pero hay una di
ultad \topologi
a"
para \quitar" el nodo. Consideremos un nodo p a \quitarse" de un ABB y el siguiente
diagrama:
q
p
L(p)
R(p)
4.9. Arboles
binarios de b
usqueda
397
El parametro root es la raz del ABB, el
ual es por referen
ia, de forma tal que se
modique la raz
uando se en
uentre la
lave. El segundo parametro es la
lave misma a
eliminar.
397b
Al igual que
on la inser
ion, el metodo de elimina
ion de BinTree<Key> deviene muy
simple:
hM
etodos de BinTree<Key> 390bi+
(389a) 394 400b
Captulo 4. Arboles
398
4.9.7
398
Inserci
on en raz de un ABB
En el algoritmo anterior de inser
ion en ABB, desarrollado en x 4.9.3 (pagina 391), el arbol
re
e por los nodos externos. Algunas ve
es es preferible que el nuevo nodo devenga raz
del arbol.
La te
ni
a anterior favore
e la lo
alidad de referen
ia en el sentido de que los nodos
er
anos a la raz son los re
ientemente insertados.
Insertar un nuevo nodo
omo raz es muy fa
il luego de que se dispone de la parti
ion split key rec() desarrollada en x 4.9.4 (pagina 392). Todo lo que hay que ha
er
es parti
ionar segun la
lave de inser
ion y luego atarle a esta
lave los arboles resultantes
de la parti
ion:
hFun
iones de BinNode Utils 299ai+
(298) 397a 399
template <class Node, class Compare> inline
Node * insert_root(Node *& root, Node * p)
{
Node * l = Node::NullPtr,
* r = Node::NullPtr; // para guardar resultados del split
Denes:
Figura 4.39: Arbol
resultante de 512 inser
iones aleatorias en la raz
4.9. Arboles
binarios de b
usqueda
4.9.8
399
399
Uni
on de ABB (join)
Consideremos T1, T2 ABB y la opera
ion de union T1 T2. En nuestra interfaz, la union
se denominara join(T1, T2, d), la
ual retorna un ABB produ
to de unir las
laves de
T1
on las de T2. Puesto que hemos a
ordado no tener
laves repetidas, las eventuales repiten
ias entre T1 y T2 se guardaran en un ABB auxiliar llamado d, el
ual es un parametro
por referen
ia a join(). Debemos prever esto porque los arboles a unir pueden haberse
onstruido independientemente
Una primera forma de implantar el join() es re
orrer en prejo un arbol, por ejemplo T2, e ir insertando sus
laves en el otro arbol T1. Esta observa
ion nos
ondu
e al
siguiente algoritmo:
hFun
iones de BinNode Utils 299ai+
(298) 398 400a
template <class Node, class Compare> inline
Node * join_preorder(Node * t1, Node * t2, Node *& dup)
{
if (t2 == Node::NullPtr)
return t1;
T1
T1
T2
L(T1 )
R(T1 )
L(T1 )
R(T1 )
join(L(T1 ),L(T2 )
L(T2 )
R(T2 )
join(R(T1 ),R(T2 )
Tomamos la raz de uno de los arboles y la insertamos
omo raz del otro. En el
aso de
la gura tomamos la raz del arbol T1 y la insertamos en T2. Luego de insertar raiz(T1),
las ramas sueltas L(T1) y R(T1) se unen re
ursivamente
on las ramas izquierda y dere
ha
de T2; o sea, L(T2) = L(T1) L(T1) y R(T2) = R(T1) R(T2).
Captulo 4. Arboles
400
400a
400b
4.9.9
An
alisis de los
arboles binarios de b
usqueda
Todas las opera
iones estudiadas sobre un ABB deben realizar busquedas; su tiempo de
eje
u
ion, depende, por tanto, del tiempo de busqueda, el
ual esta a
otado por la altura
del arbol. Conse
uentemente, lo tras
endental en el analisis es
ono
er
ual sera el nivel
promedio de un nodo.
4.9. Arboles
binarios de b
usqueda
401
mente segun una se
uen
ia de inser
ion aleatoria. No hay supresiones. Sean:
Sn el n
umero de nodos visitados durante una busqueda exitosa, y
Un el n
umero de nodos visitados durante una busqueda fallida.
Enton
es:
S
!
1
= 2 1+
Hn
n
= 2Hn+1
1.386 lg n
1.386 lg (n + 1)
= O(lg n)y
= O(lg n)
18
18 H
Pn
1
i=1 i
Captulo 4. Arboles
402
Demostraci
on El n
umero de nodos visitados en una busqueda exitosa es la longitud
del
amino desde raiz(T ) hasta el nodo en
ontrado. Si ni es el nodo en
ontrado, enton
es
|C (T),ni | = nivel(ni) + 1, pues el nivel
omienza desde
ero. Por deni
ion de promedio,
tenemos:
raiz
1 X
(nivel (ni) + 1)
n
ni T
1 X
nivel(ni)
n
ni T
1
IPL(T )
n
(4.40)
EPL(T ) 2n
(4.41)
Ahora planteamos una e
ua
ion similar para una busqueda infru
tuosa. Puesto que
el nodo no se en
uentra en el arbol, la busqueda des
endera hasta un nodo externo. El
numero de nodos visitados en una busqueda infru
tuosa es, pues, equivalente a la longitud
del
amino desde la raz hasta el nodo externo. Podemos, enton
es, plantear el promedio
de longitudes entre todos los
aminos desde la raz hasta un nodo externo. Esto es:
U
1
n+1
nx
nivel(nx)
nx
nodo externo
EPL(T )
=
n+1
EPL(T ) = (n + 1) Un
(4.42)
(n + 1) Un 2n
n
(4.43)
La e
ua
ion (4.43) nos plantea las dos in
ognitas que intentamos en
ontrar. Para poder
resolverla requerimos una segunda e
ua
ion, independiente de (4.43), que tambien nos
rela
ione S y U ,
Supongamos que la permuta
ion de inser
ion es n0n1n2 . . . nn2nn1. Notemos que la
antidad de nodos que se visitan
uando se bus
a exitosamente el nodo ni es equivalente
a uno mas el numero de nodos que se visitaron durante la busqueda infru
tuosa que se
realizo durante la inser
ion de ni. Esto solo es
orre
to si se asume que no hay supresiones.
La ausen
ia de supresiones garantiza que los nodos nun
a
ambian de lugar en el arbol.
Podemos, enton
es, de
ir que:
S = U +1
(4.44)
n
i-1
Ahora, por la misma deni
ion de promedio, podemos plantear una nueva e
ua
ion:
S
i=0
(4.45)
4.9. Arboles
binarios de b
usqueda
403
1 = 1
n1
1X
Ui
n
i=0
(n + 1) Un =
n1
X
(4.46)
2n
i=0
n2
X
(4.47)
2(n 1)
i=0
n Un-1 = Un-1
= Un-1 +
2
n+1
(4.48)
Lo que da una e
ua
ion re
urrente de U que podemos resolverla por expansion su
esiva
hasta el ultimo termino U = 0:
n
2
n+1
2
2
Un-2 + +
n n+1
2
2
2
Un-3 +
+ +
n1 n n+1
2
2
2
2
+ +
U1 + + +
3
n1 n n+1
2 2
2
2
2
U0 + + + +
+ +
2 3
n 1 n n +!
1
= Un-1 +
=
=
=
=
= 2
La serie
Pn
1 1
1
1
1
+ + +
+ +
2 3
n1 n n+1
1
i=1 i
19
(n + 1)2(Hn+1 1) 2n
+ 1
n
n+1
=
(2(Hn+1 1)) 1
n
!
1
n+1
Hn +
1
1
= 2
n
n+1
=
= 2
19 El
n+1
Hn 3 2 ln n 1, 386 lg n
n
404
Captulo 4. Arboles
tendiente a O( n).
La proposi
ion 4.13 nos demuestra que, si no se inter
alan supresiones
on inser
iones,
el desempe~no esperado de la busqueda y la inser
ion es O(lg n), lo
ual ha
e a un arbol binario una alternativa bastante a
eptable para implantar tablas de smbolos en situa
iones
donde el orden de inser
ion sea aleatorio y el numero de supresiones inter
aladas sea
relativamente peque~no; por ejemplo, la tabla de smbolos de un
ompilador. Lamentablemente, existen situa
iones donde el orden de inser
ion podra ser sesgado. Por ejemplo,
los apellidos en
astellano estan sesgados; probablemente, un apellido
omo Leon sera mas
fre
uente que Knuth.
4.10
EL TAD BinTree<Key>, as
omo otros TAD para ABB que estudiaremos en el
aptulo
x 6, efe
t
uan todas sus opera
iones en fun
ion de \nodos" y no en fun
ion del tipo de
lave. Las razones para esto se fundamentan en el prin
ipio n a n y ya han sido adu
idas
anteriormente.
>Como se implanta un
onjunto de
laves mantenido por alguna
lase de ABB? Di
ho
de otra manera, >
omo se resuelve el problema fundamental mediante un ABB?
ALEPH implanta el
onjunto fundamental
on arboles binarios de b
usqueda de dos
405
405
maneras. La primera es mediante el tipo DynSetTree<Tree,Key,Compare, el
ual instrumenta un
onjunto de
laves de tipo Key implantado mediante alguna
lase de arbol
binario de busqueda Tree<Key> y
iterio de
ompara
ion Compare. . La segunda manera
es mediante una
lase de mapeo llamada DynMapTree<Tree, Key, Range, Compare>,
uya diferen
ia
on DynSetTree es que la ultima guarda pares; o sea, maneja un
onjunto
\rango" del mapeo o fun
ion.
Todos los TAD fundamentados en ABB de este texto exhiben la misma interfaz que el TAD BinTree<Key>. Por esa razon, DynSetTree<Tree,Key,Compare y
DynMapTree<Tree, Key, Range, Compare> pueden instrumentar
ualquier
lase de
onjunto implementado mediante arboles binarios de busqueda.
En esta se
ion desarrollaremos el mas
omplejo de los TAD men
ionados, la
lase DynMapTree<Tree, Key, Range, Compare>. Su fun
ion es implantar un
onjunto de
laves o un mapeo de
laves en un dominio ha
ia elementos en un
onjunto \rango" mediante una
lase de ABB. El TAD en
uestion se dene en el
ar
hivo htpl dynMapTree.H 405i:
htpl dynMapTree.H 405i
template <
template <typename /* Key */, class /* Compare */> class Tree,
typename Key,
typename Range,
class Compare = Aleph::less<Key>
>
class DynMapTree
{
hMiembros privados de DynMapTree<Tree, Key, Range, Compare> 406ai
public:
hMiembros
};
hClases
Denes:
publi os de DynMapTree<Tree,
propias segun el tipo de arbol, que esen
ialmente implantan las mismas opera
iones que
BinTree<Key>. De este modo, el usuario de DynMapTree<Tree, Key, Range, Compare>
sele
iona, segun sus
riterios de
ualidad y requerimiento, la
lase de ABB que desea usar.
El parametro
lase Key representa el tipo de dato
orrespondiente al dominio del
mapeo.
El parametro Range representa el tipo de dato del rango del mapeo. Si lo que se
desea es meramente mantener un
onjunto de
laves, enton
es este parametro se debe
orresponder
on una
lase va
a; por ejemplo, Empty Node, el
ual fue denido y usado
para el TAD BinNode<Key>.
Captulo 4. Arboles
406
406a
tree es una instan
ia de ABB
on
lave Key y num nodes es la
antidad de nodos que
ontiene el arbol tree. Esta
antidad se
ontabiliza en las primitivas
de DynMapTree<Tree, Key, Range, Compare> que inserten o eliminen nodos y es ob-
406b
servable.
El TAD BinTree<Key> y demas TAD rela
ionados, operan sobre nodos que alma
enan una
lave generi
a de tipo Key. Ahora bien, para el mapeo requerimos de un dato
de mas de tipo Range, el
ual se espe
i
a por deriva
ion del tipo generi
o Tree<Key,
Compare>::Node:
hMiembros privados de DynMapTree<Tree, Key, Range, Compare> 406ai+
(405) 406a
struct Node : public Tree<Key, Compare>::Node
{
friend class DynMapTree<Tree, Key, Range, Compare>;
Range data;
Node() { /* Empty */ }
Node(const Key & _key) : Tree<Key, Compare>::Node(_key) { /* empty */ }
Range & get_data() { return data; }
};
DynMapTree<Tree, Key, Range, Compare> opera internamente
on nodos del tipo Node,
los
uales se ordenan por
lave de tipo Key, a nivel del arbol binario que se utili
e, pero
ada nodo
ontiene un par de tipo (Key, Range).
406
Los
onstru
tores por omision y de
opia pueden enton
es denirse junto
on el destru
tor:
hMiembros p
ubli
os de DynMapTree<Tree, Key, Range, Compare> 406
i
(405) 407a
DynMapTree() : num_nodes(0) { /* empty */ }
407
}
}
virtual ~DynMapTree()
{
if (num_nodes > 0)
destroyRec(tree.getRoot());
}
Uses copyRec 305b and destroyRec 306.
Para insertar:
407a
hMiembros p
ubli
os de DynMapTree<Tree, Key, Range, Compare> 406
i+
size_t insert(const Key & key, const Range & data)
{
Node * node = new Node (key);
node->data = data;
if (tree.insert(node) == NULL)
{
delete node;
return num_nodes;
}
return ++num_nodes;
}
407b
La rutina retorna la
antidad de nodos que tiene el arbol. Puesto que no se permiten
elementos repetidos, esta
antidad puede
otejarse para saber si o
urrio o no la inser
ion.
Para la elimina
ion, solo basta la
lave, y esta se realiza mediante el siguiente metodo:
hMiembros p
ubli
os de DynMapTree<Tree, Key, Range, Compare> 406
i+
(405) 407a 407
size_t remove(const Key & key)
{
Node * node = static_cast<Node*>(tree.remove(key));
if (node == NULL)
return num_nodes;
delete node;
return --num_nodes;
}
remove() retorna la antidad de elementos, lo que permite, al igual que insert(), deter-
407
Captulo 4. Arboles
408
}
Uses destroyRec 306.
408a
(405) 407
408b
La primera, test key() se destina para
onjuntos que solo
ontengan
laves, mientras que
la segunda, test(), para mapeos.
Otra manera de insertar y bus
ar elementos es mediante el operador [], el
ual \indiza"
laves y retorna imagenes dentro del rango. Por supuesto, este esquema solo tiene sentido
si se trata de un mapeo y no de un
onjunto de
laves.
Del TAD DynMapTree<Tree, Key, Range, Compare> podemos espe
ializar diversas
lases segun el tipo de arbol binario de busqueda:
hClases din
ami
as derivadas de DynMapTree<Tree, Key, Range, Compare> 408bi
(405)
template <typename Key, typename Type, class Compare = Aleph::less<Key> >
class DynMapBinTree : public DynMapTree<BinTree, Key, Type, Compare>
{ /* Empty */ };
Denes:
DynMapBinTree, never used.
4.11
Extensiones a los
arboles binarios
EL TAD BinTree<Key> y sus derivados (ver x 6) son idoneos para realizar una solu
ion
al problema fundamental
on las opera
iones de inser
ion, busqueda y elimina
ion
en O(lg(n)). Esto es un gran paso respe
to a un arreglo donde la inser
ion y la elimina
ion son O(n). Sin embargo, un arreglo ordenado nos ofre
e la alternativa de bus
ar
el i-esimo menor elemento en O(1) mientras que en un ABB esta opera
ion requiere un
re
orrido injo que es O(n).
Existe una estru
tura de dato, basada en un ABB, que nos permite mantener un
onjunto de
laves
on las siguientes opera
iones y sus tiempos:
Inser
i
on, busqueda y elimina
ion de una
lave en O(lg(n)).
A
eso al i-esimo elemento del re
orrido injo en O(lg(n)).
Cono
imiento de la posi
i
on inja dada una
lave en O(lg(n)).
409
El prin ipio fundamental de la estru tura de datos subya e en alma enar la ardinalidad del arbol en ada nodo. Para ello, nos valdremos de la siguiente deni ion:
Definici
on 4.10 (Arbol
con rangos) Un arbol binario
on rangos es un arbol binario
on un
ampo adi
ional en
ada nodo, denotado C(n), que alma
ena la
ardinalidad y el
ual satisfa e:
n T,
C() = 0
Definici
on 4.11 (Arbol
binario de b
usqueda extendido) Un arbol binario de b
usqueda
extendido T es un arbol binario de busqueda
on rangos.
409
Captulo 4. Arboles
410
25
265
30
6
69
25
29
298
4
4
47
6
2
35
4
0
4
2
9
92
18
5
55
1
3
41
1
28
296
3
7
84
2
13
141
15
8
91
1
26
273
2
12
108
3
1
17
1
18
183
11
11
104
2
10
98
1
27
284
1
17
173
4
24
261
6
15
168
3
14
144
1
16
172
1
22
227
5
19
196
3
23
243
1
21
200
2
20
198
1
4.11.1
411
Selecci
on por posici
on
Dado T ABBE
on raz r, se desea en
ontrar el i-esimo elemento en la posi
ion inja. La
estru
tura de datos, aunada al uso del nodo
entinela, ofre
e una solu
ion
ompletamente
general; es de
ir, sin ningun
aso parti
ular, la
ual se pi
toriza del siguiente modo:
if i < C(L(r))
T
C(r) select rec(r, i)
if i > C(L(r)) + 1
select rec(L(r), i)
C(L(r))
411a
R(T )
C(R(r))
Men
ion parti
ular mere
e la llamada re
ursiva por la dere
ha. Generalmente hablando,
uando bus
amos por la dere
ha, lo ha
emos relativamente sobre un arbol que
ontiene C(R(r)) nodos, pero la posi
ion i de la llamada original ata~ne a un arbol
on C(r)
nodos. Debemos pues,
ompensar la llamada
on la
antidad de nodos que el re
orrido
injo deja
uando se va por la dere
ha. Esto es: C(L(r)) del subarbol izquierdo mas la raz.
Con lo anteriormente expuesto
omprendido, el algoritmo resultante debe ser sen
illo:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai
(409) 411b
template <class Node> inline
Node * select_rec(Node * r, const size_t & i)
throw(std::exception, std::out_of_range)
{
if (i == COUNT(LLINK(r)))
return r;
if (i < COUNT(LLINK(r)))
return select_rec(static_cast<Node*>(LLINK(r)), i);
return
select_rec(static_cast<Node*>(RLINK(r)), i - COUNT(LLINK(r)) - 1);
}
Uses LLINK 296 and RLINK 296.
411b
La version iterativa tambien es muy sen
illa, mas segura y mu
ho mas e
iente:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 411a 412a
template <class Node> inline
Node * select(Node * r, const size_t & pos)
throw(std::exception, std::out_of_range)
{
size_t i = pos;
while (i != COUNT(LLINK(r)))
{
if (i < COUNT(LLINK(r)))
r = static_cast<Node*>(LLINK(r));
Captulo 4. Arboles
412
else
{
i -= COUNT(LLINK(r)) + 1;
r = static_cast<Node*>(RLINK(r));
}
}
return r;
}
Denes:
select, used in
hunk 19.
Uses LLINK 296 and RLINK 296.
4.11.2
412a
C
alculo de la posici
on infija
Dada una
lave existente en el ABBE, deseamos
al
ular su posi
ion inja; es de
ir, su
orden dentro del
onjunto de
laves. Este problema se resuelve re
ursivamente del siguiente
modo:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 411b 412b
template <class Node, class Compare> inline
size_t inorder_position(Node *
r,
const typename Node::key_type & key,
Node *&
node)
{
if (r == Node::NullPtr)
throw std::domain_error("Key not found");
La rutina re
ibe
omo entrada la raz del arbol r y la
lave key. El ter
er parametro,
node, es de salida y es un nodo que
ontiene la
lave key; este resultado tiene sentido solo si
la
lave fue en
ontrada. El valor de retorno es la posi
ion de key dentro del re
orrido injo.
En
aso de que key no se en
uentre en el arbol, enton
es se retorna un valor negativo.
4.11.3
412b
Inserci
on por clave en
arbol binario extendido
La inser
ion por
lave es identi
a a la de un ABB presentada enx 4.9.3, salvo que ne
esitamos a
tualizar los
ontadores:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 412a 413
template <class Node, class Compare> inline
Node * insert_by_key_xt(Node *& r, Node * p)
413
{
if (r == Node::NullPtr)
return r = p;
Node * q;
if (Compare () (KEY(p), KEY(r)))
{
q = insert_by_key_xt<Node, Compare>(static_cast<Node*&>(LLINK(r)), p);
if (q != Node::NullPtr)
++COUNT(r);
}
else if (Compare ()(KEY(r), KEY(p)))
{
q = insert_by_key_xt<Node, Compare>(static_cast<Node*&>(RLINK(r)), p);
if (q != Node::NullPtr)
++COUNT(r);
}
else
return (Node*) Node::NullPtr; // clave duplicada
return q;
}
Denes:
413
Partici
on por clave
La parti
ion por
lave expli
ada en x 4.9.4 (pagina 392) puede implantarse
on la misma
estru
tura para un ABBE:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 412b 414
# define SPLIT split_key_rec_xt<Node, Compare>
Captulo 4. Arboles
414
return false;
r = root;
COUNT(r) -= COUNT(l);
}
else if (Compare() (KEY(root), key))
{
if (not SPLIT(RLINK(root), key, RLINK(root), r))
return false;
l = root;
COUNT(l) -= COUNT(r);
}
else
return false; // clave duplicada
return true;
}
Denes:
split key rec xt, used in
hunk 414.
Uses LLINK 296 and RLINK 296.
4.11.5
414
Inserci
on en raz
Con la fun
ion anterior, podemos implantar la inser
ion en la raz bajo el mismo esquema
que la desarrollada en x 4.9.7 (pagina 398):
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 413 415
template <class Node, class Compare> inline
Node * insert_root_xt(Node *& root, Node * p)
{
if (root == Node::NullPtr)
return p;
4.11.6
Partici
on por posici
on
Esta opera
ion se pare
e a la parti
ion por
lave,
on la ex
ep
ion de que el \punto"
de parti
ion es respe
to a una posi
ion i del re
orrido injo. La parti
ion resulta en dos
arboles Tl =< k1k2 . . . kl > y Tr =< ki . . . kn >.
415
415
Salvo que i este fuera de rango; es de
ir, i C(T ), el algoritmo siempre tiene exito.
Para esta situa
ion, dise~namos el siguiente algoritmo reminis
ente a la parti
ion re
ursiva por
lave:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 414 416a
template <class Node> inline
void split_pos_rec(Node * r, const size_t & i, Node *& ts, Node *& tg)
{
if (i == COUNT(r)) // Es la
ultima posici
on (que est
a vac
a)?
{
ts = r;
tg = Node::NullPtr;
return;
}
if (i == COUNT(LLINK(r))) // se alcanz
o la posici
on de partici
on?
{
ts = LLINK(r);
tg = r;
LLINK(tg) = Node::NullPtr;
COUNT(tg) -= COUNT(ts);
return;
}
if (i < COUNT(LLINK(r)))
{
split_pos_rec(static_cast<Node*>(LLINK(r)), i, ts,
static_cast<Node*&>(LLINK(r)));
tg = r;
COUNT(r) -= COUNT(ts);
}
else
{
split_pos_rec(static_cast<Node*>(RLINK(r)), i - (COUNT(LLINK(r)) + 1),
static_cast<Node*&>(RLINK(r)), tg);
ts = r;
COUNT(r) -= COUNT(tg);
}
}
Denes:
La rutina dispara la ex
ep
ion std::out of range en
aso de que i este fuera del rango
injo del arbol.
4.11.7
Inserci
on por posici
on
Consideremos el re
orrido injo k0, k1, k2, . . . , ki1, ki, ki+1, . . . , kn1 . Cuando insertamos
kp en la i-esima posi
ion, el re
orrido resultante es k0, k1, k2, . . . , ki1, kp, ki, ki+1, . . . , kn1 ;
Captulo 4. Arboles
416
416a
es de
ir, a partir de la posi
ion i, los elementos se desplazan ha
ia la dere
ha; la posi
ion
inja kp es i, su prede
esor es ki1 y su su
esor es ki. El algoritmo que proponemos es
muy sen
illo si nos inspiramos del de inser
ion en la raz presentado en x 4.9.7 (pag. 398):
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 415 416b
template <class Node> inline
void insert_by_pos_xt(Node *& r, Node * p, const size_t & pos)
{
split_pos_rec(r, pos, static_cast<Node*&>(LLINK(p)),
static_cast<Node*&>(RLINK(p)));
COUNT(p) = COUNT(LLINK(p)) + 1 + COUNT(RLINK(p));
r = p;
}
Denes:
Salvo que pos este fuera de rango; o sea que sea igual o sobrepase la
antidad de elementos,
la inser
ion siempre tendra exito.
Notemos que la inser
ion por posi
ion puede violar la
ondi
ion de orden de un ABB.
4.11.8
416b
Uni
on exclusiva de
arboles extendidos
417
4.11.9
417
Eliminaci
on por clave en
arboles extendidos
Captulo 4. Arboles
418
4.11.10
418
Eliminaci
on por posici
on en
arboles extendidos
Si la posi
ion inja es valida, enton
es, al igual que
on la inser
ion y la parti
ion, la
elimina
ion siempre tiene exito y se dene
omo sigue:
hPrimitivas b
asi
as sobre BinNodeXt<Key> 411ai+
(409) 417 421
template <class Node> inline
Node * remove_by_pos_xt(Node *& root, const size_t & pos)
{
4.11.11
Desempe
no de las extensiones
Segun la proposi
ion 4.13 (pag. 401), una se
uen
ia de inser
ion aleatoria produ
e un
arbol que en promedio esta equilibrado. Conse
uentemente, si
onstruimos un ABBE por
inser
ion de
laves aleatorias, enton
es los desempe~nos de fun
iones que no modiquen el
arbol seran O(lg(n)); este es el
aso de la sele
ion, determina
ion del orden injo y de la
inser
ion por
lave.
En el
aptulo 6 estudiaremos diversas te
ni
as para mantener equilibrados arboles
binarios de busqueda y garantizar O(lg(n)) en todas las opera
iones. Con
ualquiera de
estas te
ni
as, siempre es posible mantener los rangos.
4.12. Rotaci
on de
arboles binarios
4.12
419
Rotaci
on de
arboles binarios
La gura 4.43 muestra dos ABB equivalentes segun la propiedad de orden. Cada uno de
ellos puede obtenerse a partir del otro a traves de una transforma
ion llamada \rota
ion".
El re
orrido injo del arbol 4.43(a) es (A)B, mientras que el del 4.43(b) es A(B).
B
(a)
(b)
419
Ahora veamos
omo implantar la rota
ion ha
ia la dere
ha. Sea p la raz del arbol a
rotar, enton
es:
hFun
iones de BinNode Utils 299ai+
(298) 400a 420
template <class Node> inline
Node * rotate_to_right(Node * p)
{
Node * q = static_cast<Node*>(LLINK(p));
LLINK(p) = RLINK(q);
RLINK(q) = p;
return q;
}
Denes:
rotate to right, used in
hunks 420, 563, 565b, 597, 601e, 603a, 604a, 611, and 615.
Uses LLINK 296 and RLINK 296.
rotate to right() retorna la nueva raz del arbol luego de la rota
ion. Con este valor
de retorno puede a
tualizarse el padre del arbol resultante. Por lo general, esta primitiva
se utiliza en algoritmos re
ursivos que invo
an rota
iones de subarboles.
Captulo 4. Arboles
420
420
Para algoritmos iterativos, es mas
onveniente utilizar una version de la rota
ion que
efe
tue la a
tualiza
ion del padre. Para ello, debemos pasar el padre del subarbol que sera
rotado:
hFun
iones de BinNode Utils 299ai+
(298) 419
/* Rotaci
on a la derecha con actualizaci
on del padre.
@param[in] p ra
z del
arbol a rotar.
@param[in,out] pp padre de p. Luego de la operaci
on, la rama de
pp que apunta a p es actualizada al
arbol binario resultante de
la rotaci
on.
4.12.1
Rotaciones en
arboles binarios extendidos
Con arboles extendidos hay que tener
uidado de ajustar los
ontadores. Retomando el
aso general de rota
ion ilustrado en la gura 4.43, tenemos un arbol binario (A)B
que deseamos rotar. Por la deni
ion de arbol extendido, tenemos que:
1 + C() .
C(B) = C() + |{z}
1 + C() + |{z}
| {z }
| {z }
| {z }
| {z }
|B|
||
|{A}|
||
|{B}|
||
| {z }
||
|{B}|
| {z }
||
|{B}|
| {z }
||
4.13. C
odigos de Huffman
421
421
(409) 418
}
Denes:
4.13
C
odigos de Huffman
La representa
ion de este texto
onsiste en una serie de smbolos latinos, arabes
os, griegos y otros mas
onvenidos desde mundos de los fsi
os y matemati
os. En papel,
on
el ade
uado formato, los smbolos
onforman una se
uen
ia visualmente legible por un
hispano hablante.
A la a
ion de redu
ir el espa
io o
upado por un texto (o se
uen
ia) se le denomina
\
omprimir". En la elabora
ion de un texto, hay un
ompromiso entre la \
ompresion"
y la legibilidad. En aras de la legibilidad, se sa
ri
a la
ompresion, y as es
omo debe
ser. Si, por ejemplo, la letra es muy peque~na, usamos menos espa
io, papel, por ejemplo,
pero el texto se ha
e menos legible. Por el
ontrario, si la letra es muy grande, enton
es
requerimos mas espa
io, ergo, mas papel o pantalla.
En la representa
ion
iberneti
a de los smbolos de un texto se usa un \
odigo" de
bits. En nuestras
ir
unstan
ias, un
odigo es un a
uerdo de representa
ion de smbolos
mediante
ombina
iones de bits. Un ejemplo notable lo
onstituye el
odigo ASCII el
ual
mapea se
uen
ias de siete bits a un smbolo; he, en la siguiente tabla, algunas de sus
o
urren
ias:
Smbolo
A
b
f
Valor Binario
Valor de imal
1000001
1100010
1111011
65
98
123
Hay otros sistemas de odi a ion de ara teres, de los uales uno muy notable es el
20 He
aqu una objetiza ion de una a ion que en el ambito humano es muy subjetiva.
Captulo 4. Arboles
422
una tabla mapeo de smbolos a se
uen
ias de bits y nos servimos de ella para \interpretar"
el texto. Tenemos aqu una primera forma de
ompresion. Sin embargo, este metodo tiene
la eventual desventaja de que el texto debe leerse por anti
ipado a efe
tos de
onstruir la
tabla. Esto puede ser problemati
o en algunas
ir
unstan
ias, la le
tura de un ar
hivo por
ejemplo, y prohibitivo en otras, la transmision de un texto.
Otros in
onveniente del enfoque anterior es que
ada smbolo o
upa la misma
antidad de bits, independientemente de su fre
uen
ia de apari
ion en el texto. Si usasemos
se
uen
ias de longitud variable, enton
es podramos es
oger se
uen
ias muy
ortas para
smbolos muy fre
uentes y dejar las mas largas para smbolos de rara apari
ion. La idea
es sele
ionar los smbolos de un bit, luego los de dos, y as su
esivamente segun
uan
fre
uente sea la apari
ion del smbolo en el texto. Por ejemplo, el blan
o, denotado, para
distinguirlo,
omo \", y la \a", que son muy fre
uentes, podran ser la se
uen
ias 0 y
1, respe
tivamente; mientras que la \e", \i", \b" y \
" podran denotarse
omo 00, 01,
10 y 11. Pudieramos
ontinuar
on se
uen
ias mas largas hasta abar
ar
ompletamente el
onjunto S. Pero as, en bruto, este enfoque plantea una ambiguedad insalvable expresada,
por instan
ia parti
ular en la se
uen
ia 001, en la in
apa
idad de distinguir si se trata de
\a" o \ea" o \i".
La ambiguedad anterior se solventa si, en detrimento de la
antidad de
ombina
iones
posibles de bits, usamos un
odigo prejo, en el sentido de la deni
ion 4.8 ; es de
ir, que
ninguna se
uen
ia si de longitud i sea preja de alguna otra de
ardinalidad superior. Si
es
ogemos permuta
iones de se
uen
ias prejas, enton
es podemos, sin problema alguno,
distinguirlas e interpretarlas en un texto. Mas aun, podemos utilizar se
uen
ias de Di
k,
las
uales,
omo
orolario de las proposi
iones 4.8 y 4.11, son prejas.
Hay una mejora aun mas substan
ial en usar una
odi
a
ion preja: podemos mapear en O(1), en lnea
on la le
tura del texto, su de
odi
a
ion. Para ello, usamos un
arbol binario
uyas hojas
odi
an los smbolos de S. Por ejemplo, para S = {, a, e, i, b, c},
ualquier arbol binario de 6 hojas
odi
a a S; en la o
urren
ia, el arbol siguiente:
21
b
Smbolo Codigo
a
e
i
b
000
001
010
011
10
11
la se ion
4.8.0.8 (pagina 372) se usaron los smbolos \a" por \0" y \b" por \1".
4.13. C
odigos de Huffman
4.13.1
423a
423
Un TAD para
arboles de c
odigo
En el
ontexto de la
odi
a
ion siempre podemos distinguir dos agentes: uno
odi
ador,
que toma un texto y lo
omprime, y otro de
odi
ador que toma el texto
omprimido y
lo de
odi
a a la se
uen
ia original.
Para
odi
ar usaremos la siguiente
lase:
hClase
odi
adora 423ai
(423
)
class Huffman_Encoder_Engine
{
hmiembros privados de
odi
ador 423di
hmiembros
};
Denes:
423b
Esta
lase se en
arga de
onstruir un arbol de prejos optimo y de
odi
ar textos en
fun
ion del arbol anterior. Por optimo pretendemos de
ir que un texto
odi
ado
on los
prejos del arbol optimo o
upa el menos espa
io posible.
Para de
odi
ar usaremos la siguiente
lase:
hClase de
odi
adora 423bi
(423
)
class Huffman_Decoder_Engine
{
hmiembros privados de de
odi
ador 423ei
hmiembros
};
Denes:
423
Las
lases y otras deni
iones se en
uentran en el ar
hivo hHuman.H 423
i, el
ual
se estru
tura del siguiente modo:
hHuman.H 423
i
hDe
lara
iones Human 424ai
hClase
hClase
423d
423e
(423b) 426e
root es, en ambas
lases, la raz de un arbol de prejos. Posteriormente, detallaremos
un algoritmo para
onstruir este arbol de manera optima (x 4.13.3 (pagina 428)). Del
mismo modo, tambien plantearemos un
riterio de e
ien
ia y demostraremos su optima
ion (x 4.13.6 (pagina 439)). En el nterin, hay que se~nalar que el arbol
uya raz
Captulo 4. Arboles
424
Aleph::Huffman_Encoder_Engine
root : BinNode< string >*
heap : Huffman_Heap
symbol_map : Symbol_Map
code_map : Code_Map
freq_root : Freq_Node*
end_symbol : string
text_len : size_t
Max_Token_Size : const size_t
build_prefix_encoding(root : BinNode< string >*, array : BitArray&, len : const size_t&)
build_encoding_map()
test_end(str : const string&) : const bool
update_freq(str : const string&)
append_code(bit_stream : BitArray&, bit_stream_len : size_t&, symbol_code : const BitArray&)
+ Huffman_Encoder_Engine()
+ get_root() : BinNode< string >*
+ generate_huffman_tree(with_freqs : const bool&) : BinNode< string >*
+ get_freq_root() : Freq_Node*
+ set_freq(str : const string&, freq : const size_t)
+ read_input(input : char*, with_freqs : const bool&)
+ read_input(input : ifstream&, with_freqs : const bool&)
+ set_end_of_stream(str : const string&)
+ encode(input : char*, bit_stream : BitArray&) : const size_t
+ encode(input : ifstream&, bit_stream : BitArray&) : const size_t
Aleph::Huffman_Decoder_Engine
root : BinNode< string >*
end_symbol : string
+ Huffman_Decoder_Engine(p : BinNode< string >*, end : const string&)
+ get_root() : BinNode< string >*
+ decode(bit_stream : BitArray&, bit_stream_len : const size_t&, output : ostream&)
Figura 4.44: Diagrama UML de las lases odi adora y de odi adora
424a
424b
La tabla mapea smbolos de tipo string a nodos de un heap que usaremos para
determinar optimamente los prejos. El parametro Treap Vtl es una
lase espe
ial de
arbol binario de busqueda llamado Treap, el
ual sera tratado en x 6.3 (pagina 559). Si
tenemos alguna di
ultad en a
eptar el tipo Treap Vtl, enton
es podemos utilizar,
on la
misma interfaz y, probablemente
on desempe~no similar, el tipo BinTreeVtl previamente
estudiado en x 4.9.2 (pagina 389).
Por razones que expli
aremos prontamente, un arbol de prejos se
onstruye a partir
de un heap del
ual sus nodos son de tipo Huffman Node y se denen de la siguiente forma:
hDe
lara
iones Human 424ai+
(423
) 424a 425a
typedef BinNode< Aleph::pair<string, size_t> > Freq_Node;
4.13. C
odigos de Huffman
425
Grosso modo, Huffman Node es un nodo binario de un heap
uya
lave, a
edida mediante get key(), es la fre
uen
ia de apari
ion o estadsti
a de un smbolo. El nodo en
uestion
ontiene dos apuntadores a dos nodos binarios:
1. bin node: que es un nodo pertene
iente a un arbol de prejos.
2. freq node: que un nodo auxiliar usado para generar dibujos de arboles de prejos .
22
425a
425b
En fun
ion de este tipo, denimos las siguientes fun
iones auxiliares:
hDe
lara
iones Human 424ai+
(423
) 425a 426b
static inline const size_t & get_freq(Huffman_Node * huffman_node)
{
return huffman_node->get_key();
}
static inline void increase_freq(Huffman_Node * huffman_node)
{
huffman_node->get_key()++;
}
static inline void set_freq(Huffman_Node * huffman_node, const size_t & freq)
{
huffman_node->get_key() = freq;
}
22 En
Captulo 4. Arboles
426
Denes:
426a
Ahora podemos denir el heap dentro del
odi
ador y su tabla de smbolos:
hmiembros privados de
odi
ador 423di+
(423a) 423d 426
Huffman_Heap heap;
Symbol_Map
symbol_map;
Denes:
symbol map, used in
hunks 431b, 433, 435, and 436a.
Uses Symbol Map 424a.
426b
Por ejemplo, symbol map["a"].get key() retorna la fre
uen
ia aso
iada al smbolo "a".
La ultima estru
tura de datos es la tabla de
odigos, la
ual requiere la deni
ion del
siguiente tipo:
hDe
lara
iones Human 424ai+
(423
) 425b 431a
typedef DynMapTree<Treap_Vtl, string, BitArray> Code_Map;
Uses BitArray 36 and Treap Vtl 562d.
426
426d
Esta tabla se utiliza para
odi
ar un texto. La idea es leer se
uen
ialmente el texto y
en
ontrar en la tabla de
odigos el
orrespondiente
odigo a afe
tos de generar el texto
odi
ado. Al igual que
on la tabla de smbolos, la de
odigos se implementa
on un
Treap (x 6.3 (pagina 559)).
Hay otros atributos adi
ionales que usaremos en la
lase Huffman Encoder Engine:
hmiembros privados de
odi
ador 423di+
(423a) 426
430
Freq_Node * freq_root;
string end_symbol;
size_t text_len;
freq root es la raz del arbol auxiliar de fre
uen
ias. end symbol, que tambien se utiliza
en Huffman Decoder Engine, es un smbolo espe
ial que denota la naliza
ion de un texto;
426e
426f
Denidas las estru
turas de datos y otros atributos, estamos prestos para denir los
onstru
tores:
hmiembros p
ubli
os de
odi
ador 426f i
(423a) 427d
public:
Huffman_Encoder_Engine()
: root(NULL), freq_root(NULL), end_symbol("NO-END"), text_len(0)
{
// empty
}
Uses Huffman Encoder Engine 423a.
4.13. C
odigos de Huffman
427a
hmiembros
public:
427
(423b) 427
427b
En
ada una de las
lases podemos
ono
er la raz del arbol de prejos mediante el
observador:
hObservador de
arbol de prejos 427bi
(427)
BinNode<string> * get_root()
{
if (root == NULL)
throw std::domain_error("Huffman tree has not been generated");
return root;
}
Uses BinNode 294a.
427
hmiembros p
ubli
os de de
odi
ador 427ai+
hObservador de
arbol de prejos 427bi
427d
hmiembros p
ubli
os de
odi
ador 426f i+
hObservador de
arbol de prejos 427bi
4.13.2
427e
Decodificaci
on
427f
enton
es, para de
odi
ar una se
uen
ia bit stream de bit stream len bits, pro
edemos
omo sigue:
hDe
odi
ar 427f i
(428)
BinNode<string> * p = root;
Captulo 4. Arboles
428
if (p == NULL)
throw std::domain_error("Invalid bits sequence");
if (is_leaf(p))
{
const string & symbol = p->get_key();
if (symbol == end_symbol)
break;
output << symbol;
p = root;
}
}
Uses BinNode 294a, is leaf 325a 431a, LLINK 296, and RLINK 296.
La idea es partir desde la raz del arbol y, segun el valor de bit ledo, des
ender ha
ia la
izquierda o dere
ha hasta deparar en alguna hoja . En ese enton
es, el prejo ledo se
orresponde
on un smbolo.
La a
ion anterior le es en
argada a la siguiente rutina:
hmiembros p
ubli
os de de
odi
ador 427ai+
(423b) 427
23
428
As pues, para de
odi
ar una se
uen
ia de bits, el usuario debe instan
iar un objeto
de tipo Huffman Decoder Engine,
uyo
onstru
tor re
ibe la raz del arbol
odigo y el
smbolo
onsiderado
omo n de la entrada. Luego, se invo
a al metodo decode(), el
ual
arroja su salida al parametro output. Cono
ido el arbol, el pro
eso de de
odi
a
ion es
relativamente sen
illo.
4.13.3
Algoritmo de Huffman
4.13. C
odigos de Huffman
429a
429
de nodos del tipo Huffman Node ya des
rito. Llamemos a este
onjunto heap, el
ual se
implanta mediante un heap.
Para
onstruir un nuevo nodo huffman node, primero hay que sele
ionar los dos nodos
on menores fre
uen
ias de la siguiente forma:
hsele
ionar los dos nodos de menor fre
uen
ia 429ai
(429
)
Huffman_Node * l_huffman_node = // nodo izquierdo
static_cast <Huffman_Node *> (heap.getMin());
429b
429
El nuevo nodo se
onstruye
omo padre de los dos menores
ontenidos en heap. Es
importante desta
ar que el nuevo sub-arbol es de tipo BinNode<string>, que es el tipo
del arbol de Human y no de tipo Huffman Node.
La manera denitiva para
onstruir un arbol de prejos se realiza as:
hmiembros p
ubli
os de
odi
ador 426f i+
(423a) 427d 432b
BinNode<string> * generate_huffman_tree(const bool & with_freqs = false)
{
while (heap.size() > 1)
{
hsele
ionar los dos nodos de menor fre
uen
ia 429ai
h
rear
hConstruir
429bi
delete l_huffman_node;
delete r_huffman_node;
heap.insert(huffman_node);
432ai
Captulo 4. Arboles
430
}
// Al salir del while, queda en el heap un solo nodo que contiene
// la ra
z del
arbol de prefijos
Huffman_Node * huffman_root = static_cast <Huffman_Node *> (heap.getMin());
root = huffman_root->bin_node;
if (with_freqs)
freq_root = huffman_root->freq_node;
delete huffman_root;
build_encoding_map(); // construir mapeo de c
odigos
return root;
}
Denes:
El parametro with freqs indi
a si se
onstruye o no un arbol adi
ional
uyas etiquetas
son las fre
uen
ias. Este arbol se usa en este texto para ilustrar los arboles de Human.
10
4
i
6
c
7
b
10
15
16
4
=
10
25
10
4
=
6
b
10
15
16
25
15
16
17
7
b
10
10
e
4
=
6
b
17
4
=
6
c
16
10
15
16
58
33
15
7
b
25
17
7
b
10
10
e
4
=
6
b
33
15
16
17
7
b
10
e
Figura 4.45: Constru ion de un arbol de Human. Las ra es de los sub-arboles se en uentran en el heap.
430
Una vez que se genera el arbol de Human, debemos, a efe
tos de
odi
ar, generar el mapeo de smbolos a
odigos prejos. Esta tarea la realiza la primitiva
build prefix encoding(), la
ual se instrumenta de la siguiente forma:
hmiembros privados de
odi
ador 423di+
(423a) 426d 431b
void build_prefix_encoding(BinNode<string> * root,
4.13. C
odigos de Huffman
431
BitArray &
const size_t &
array,
len)
{
if (is_leaf(root))
{
string & str = root->get_key();
code_map.insert(str, BitArray(array, len));
return;
}
// ir hacia la izquierda
array[len] = 0;
build_prefix_encoding(LLINK(root), array, len + 1);
// ir hacia la derecha
array[len] = 1;
build_prefix_encoding(RLINK(root), array, len + 1);
}
Denes:
431a
(423 ) 426b
431b
void build_encoding_map()
{
if (root == NULL)
throw domain_error("Huffman encoding tree has not been generated");
const size_t h = computeHeightRec(root);
BitArray array(h * symbol_map.size());
symbol_map.empty();
24 Vale
la pena re ordar que este prejo es una palabra de Di k (vease x 4.8.0.9 (pagina 377)).
Captulo 4. Arboles
432
432a
La
onstru
ion del arbol de fre
uen
ias, que es op
ional, es similar a la del arbol de
prejos y se espe
i
a de la siguiente manera:
hConstruir
arbol auxiliar de fre
uen
ias 432ai
(429
)
if (with_freqs)
{
Freq_Node *& l_freq_node = l_huffman_node->freq_node;
if (l_freq_node == NULL)
{
l_freq_node
= new Freq_Node;
l_freq_node->get_key().first =
l_huffman_node->bin_node->get_key();
l_freq_node->get_key().second = l_huffman_node->get_key();
}
Freq_Node *& r_freq_node = r_huffman_node->freq_node;
if (r_freq_node == NULL)
{
r_freq_node
= new Freq_Node;
r_freq_node->get_key().first =
r_huffman_node->bin_node->get_key();
r_freq_node->get_key().second = r_huffman_node->get_key();
}
const string str = gnu::autosprintf ("%d", new_freq);
Freq_Node *& freq_node
freq_node
freq_node->get_key().first
freq_node->get_key().second
LLINK(freq_node)
RLINK(freq_node)
=
=
=
=
=
=
huffman_node->freq_node;
new Freq_Node;
str;
huffman_node->get_key();
l_freq_node;
r_freq_node;
Uses bin node 429b, huffman node 429b, l huffman node 429a, LLINK 296, r huffman node 429a,
and RLINK 296.
432b
Basi
amente, este arbol guarda en
ada nodo un string
ontentivo del smbolo y de
su fre
uen
ia (los nodos internos
ontienen la
adena va
a). Un programa llamado
write huffman se en
arga de generar la entrada a btreepic.
Dentro de Huffman Encoder Engine podemos observar la raz del arbol de fre
uen
ia
mediante:
hmiembros p
ubli
os de
odi
ador 426f i+
(423a) 429
433
Freq_Node * get_freq_root()
{
4.13. C
odigos de Huffman
433
if (freq_root == NULL)
throw std::domain_error("Huffman tree has not been generated");
return freq_root;
}
433
Definici
on de smbolos y frecuencias
Antes de
onstruir el arbol de prejos segun el algoritmo anterior, debemos indi
ar los
smbolos y sus respe
tivas fre
uen
ias. Hay dos maneras de ha
erlo. La primera
onsiste
en espe
i
ar dire
tamente el smbolo y su
orrespondiente fre
uen
ia, lo
ual se ha
e
mediante set freq(str, freq); donde str es el smbolo a denir y freq su fre
uen
ia
aso
iada. El metodo en
uestion se espe
i
a
omo sigue:
hmiembros p
ubli
os de
odi
ador 426f i+
(423a) 432b 434d
void set_freq(const string & str, const size_t & freq)
{
hVeri
ar si
arbol ha sido generado 434ai
hVeri
ar
// Buscar s
mbolo str
Huffman_Node ** huffman_node_ptr = symbol_map.test(str);
if (huffman_node_ptr != NULL) // ya fue definido?
throw std::domain_error // S
==> esto es un error!
(gnu::autosprintf("Frequency for symbol %s has already set",
str.c_str()));
auto_ptr<BinNode<string> > bin_node_auto ( new BinNode<string> (str) );
Huffman_Node * huffman_node = new Huffman_Node(bin_node_auto.get());
Aleph::set_freq(huffman_node, freq);
heap.insert(huffman_node);
symbol_map.insert(str, huffman_node);
bin_node_auto.release();
}
Denes:
Captulo 4. Arboles
434
434a
434b
hVeri
ar si
arbol ha sido generado 434ai
(433{35)
if (root != NULL)
throw domain_error("Huffman encoding tree has already been generated");
Si el arbol de prejos ya ha sido generado, enton
es no se puede ingresar un nuevo smbolo
o a
tualizar su fre
uen
ia.
Por lo general, es
onveniente denir un smbolo espe
ial que nos denota el nal de
la entrada. Tal smbolo solo debe apare
er una vez y esta pre
ondi
ion la veri
amos
mediante:
hVeri
ar dupli
idad del smbolo n de entrada 434bi
(433 435)
if (test_end(str))
throw domain_error("End symbol has already been inserted");
Uses test end 434
.
434
434d
La otra forma de indi
ar fre
uen
ias es a partir de un texto, identi
ar sus smbolos
y
ontabilizar sus fre
uen
ias de apari
ion. Esto se ha
e mediante los metodos denominados read input(input, with freqs). En ambas versiones, input es el texto a partir del
ual se desea
ontabilizar las fre
uen
ias y with freqs es un parametro op
ional que indi
a
si desea o no
onstruir el arbol adi
ional de fre
uen
ias. Hay dos versiones de read input()
una lee una
adena de
ara
teres C y la otra un stream C++.
hmiembros p
ubli
os de
odi
ador 426f i+
(423a) 433 436a
private:
4.13. C
odigos de Huffman
435
update_freq(curr_token);
text_len++;
}
hGenerar
arbol 436bi
}
Denes:
arbol 436bi
435
La dos versiones de read input() subya
en sobre la rutina update freq(curr token),
la
ual se remite a bus
ar curr token en el mapeo de smbolos,
rear una nueva entrada
si no esta mapeado, in
rementar en uno la fre
uen
ia y a
tualizar el heap. Se instrumenta
as:
hmiembros privados de
odi
ador 423di+
(423a) 434
438a
void update_freq(const string & str)
{
arbol ha sido generado 434ai
hVeri
ar si
hVeri
ar
Captulo 4. Arboles
436
symbol_map.insert(str, huffman_node);
bin_node_auto.release();
}
else
huffman_node = *huffman_node_ptr; // Ya definido, recuperarlo
increase_freq(huffman_node);
heap.update(huffman_node);
}
Denes:
update freq, used in
hunk 434d.
Uses BinNode 294a, Huffman Node 424b, huffman node 429b, increase freq 425b, and symbol map 426a.
436a
El termino de la entrada debe indi
arse mediante el metodo espe
ial siguiente:
hmiembros p
ubli
os de
odi
ador 426f i+
(423a) 434d 437
void set_end_of_stream(const string & str)
{
if (test_end(str))
throw domain_error("End symbol has already been inserted");
if (root != NULL)
throw domain_error("Huffman encoding tree has already been generated");
auto_ptr<BinNode<string> > bin_node_auto ( new BinNode<string> (str) );
Huffman_Node * huffman_node = static_cast<Huffman_Node*>
(heap.insert(new Huffman_Node(bin_node_auto.get())));
symbol_map.insert(str, huffman_node);
bin_node_auto.release();
end_symbol = str;
}
Denes:
set end of stream, used in
hunk 436b.
Uses BinNode 294a, Huffman Node 424b, huffman node 429b, symbol map 426a, and test end 434
.
436b
Puesto que el n de los metodos read input() es mirar las fre
uen
ias y
onstruir la
entrada, estos
ulminan
on la genera
ion del arbol de
odigos:
hGenerar
arbol 436bi
(434d)
set_end_of_stream("");
generate_huffman_tree(with_freqs);
Uses generate huffman tree 429
and set end of stream 436a.
4.13.5
Codificaci
on de texto
Una vez
onstruido un arbol de Human, podemos
odi
ar
ualquier texto mediante
ualquiera de los metodos encode(input, bit stream). input es el texto a
odi
ar y
4.13. C
odigos de Huffman
437
437
bit stream es un BitArray del tipo denido en x 2.1.3 (pagina 36). Tenemos dos versiones,
una para
adenas de
ara
teres y otras para un stream C++.
hmiembros p
ubli
os de
odi
ador 426f i+
(423a) 436a
size_t encode(char * input, BitArray & bit_stream)
{
hVeri
ar que exista
arbol de prejos 438bi
char * curr_stream = input;
char curr_token[Max_Token_Size];
curr_token[1] = \0;
size_t bit_stream_len = 0;
while (*curr_stream != \0)
{
curr_token[0] = *curr_stream++;
append_code(bit_stream, bit_stream_len, code_map[curr_token]);
}
append_code(bit_stream, bit_stream_len, code_map[""]);
return bit_stream_len;
}
size_t encode(ifstream & input, BitArray & bit_stream)
{
arbol de prejos 438bi
hVeri
ar que exista
char curr_token[2];
curr_token[0] = curr_token[1] = \0;
size_t bit_stream_len = 0;
while (not input.eof())
{
input.read(curr_token, 1);
append_code(bit_stream, bit_stream_len, code_map[curr_token]);
}
append_code(bit_stream, bit_stream_len, code_map[""]);
return bit_stream_len;
}
Denes:
encode, never used.
Uses append code 438a, BitArray 36, and code map 426
.
El segundo parametro de encode() es el arreglo de bits que
ontiene el texto
odi
ado.
El pro
eso, una vez generado el mapeo
on los prejos, es muy sen
illo: bus
ar el smbolo
ledo en el mapeo y
on
atenar al arreglo de bits el prejo. Tal tarea la realiza la primitiva
Captulo 4. Arboles
438
438a
}
Denes:
438b
4.13. C
odigos de Huffman
439
1382
558
273
135
66
u
824
285
372
138 138
e a
69
147
72
34
c
35
17
8
4
2
9
4
18
q
175
75 83
s r
35
t
37
18
197
92
96
o
46
m
46
19
y
22
9 9
v .
10
2 2
x P
1 1 1 1
T u
e
;
452
3
n
~
24
,
12
5 5
N o
209
243
101
115
48 53
i d
56 59
\n l
128
62
30
15
b
7
3 4
S a
32
15
7
h
4 4
j z
1
L
Esta es la primera vez en este texto en que men
ionamos \optima
ion" y otros terminos
aso
iados. Habida
uenta de su importan
ia en las ingenieras, vale pues la pena un in
iso
lologi
o para advertir sobre su uso.
Segun D.R.A.E., \optima
ion" signi
a \a
ion y efe
to de optimar". El verbo \optimar" -u \optimizar"-
onnota, D.R.A.E. dixit, \bus
ar la mejor manera de realizar una
a
tividad". Ahora bien, en el estri
to sentido de universalidad, >existe una mejor manera
de realizar una a
tividad?. Responder armativamente y dar
uenta sobre la supuesta
mejor manera es muy deli
ado. Si para un
ierto asunto asumimos y mostramos una optima
ion, enton
es, en el asunto en
uestion, jamas regresaramos a mejorarlo; >para que si
el \optimo" ya es el mejor?. Lo que es mejor es relativo a las
ir
unstan
ias, y estas ata~nen
al momento, al lugar, pero, sobre todo, a quienes. Asumir optima
ion sin
onsidera
ion
ir
unstan
ial
onlleva peligro para las partes del
ontexto que no fueron sopesadas.
Quiza una fuerte eviden
ia
ultural del desden que a menudo
onlleva una \optima
ion" se en
uentra en su raz etimologi
a. En latn \optmos"
onnotaba \aristo
rati
o", pertene
iente a los \optmates", quienes eran los miembros del senado romano,
los pre
ursores de los oligar
as de nuestra epo
a. Tal pare
e que en la antigua Roma los
que gozaban de plenos dere
hos, quienes tena mayor
apa
idad de ele
ion, o sea, optaatus"), eran los nobles.
ban (\opt
Realizada la a
laratoria anterior, podemos preguntarnos >es un arbol de Human
optimo? Para
onfrontar la pregunta, debemos plantearnos un
riterio que nos pondere el
oste de un smbolo en fun
ion de su fre
uen
ia de apari
ion y que nos
onduz
a a una
forma general y uni
a de des
ubrir el mejor arbol de
odigos.
Definici
on 4.12 (Longitud ponderada del camino) Sea un arbol binario T en el
ual
a
ada hoja ne T se le asigna un
oste o pondera
ion w. Enton
es, la longitud ponderada
del
amino, denotada
omo wpl(T ), se dene
omo:
X
(4.50)
wpl(T )
nivel(ne) w(ne)
ne es
hoja
16
8
0 1
:
4.13.6
66
n
8 8
f g
16
p
Captulo 4. Arboles
440
13
7
b
15
16
a
10
e
4
i
6
c
15
(a) Arbol
de
odigos anterior
16
a
10
e
4
i
Figura 4.46: Arboles
prejos ejemplo
Cuanto menor altura tenga una hoja, menor es su in
iden
ia sobre el wpl. La idea es
onstruir un arbol de
odigos
uyo wpl(T ) sea mnimo; este es el
riterio de optima
ion. Tal
arbol se denomina \de Human", en honor a su des
ubridor, quien des
ubrio la siguiente
proposi
ion:
Proposici
on 4.14 (Optimaci
on de Huffman - Huffman [8] 1951) Sea N = {n1, n2, . . . , nn}
un
onjunto de nodos
on pesos w(n1), w(n2), . . . , w(nn). Sea T un arbol prejo
onstruido
segun el algoritmo de Human (x 4.13.3 (pagina 428)). Enton
es, T 6= T = wpl(T )
wpl(T ).
Lo anterior equivale a de
ir que el arbol de prejos T es mnimo segun su longitud
Demostraci
on.
Lema 4.5 Sea T un arbol
on pesos en sus hojas. Sean n1 y n2 la dos hojas de T
on peso
mnimo. Consideremos un arbol T tal que este
ontenga las mismas hojas que T ex
epto
que las hojas n1 y n2 se substituyen por una hoja n+
on w(n+) = w(n1) + w(n2).
Enton
es, es posible
onstruir un arbol T tal que :
25
(4.51)
Por ejemplo, la gura 4.46-b ilustra un T posible en el
ual se le suprimen las hojas b y
c. En este
aso, wpl(T ) = 13 + 3 (15 + 16 + 10 + 4) = 148
25 Es
esen
ial distinguir la
ondi
ion de posibilidad en el enun
iado del lema; es de
ir, puede en
ontrarse
una disposi
ion topologi
a
on los nodos restantes que satisfaga la desigualdad.
4.13. C
odigos de Huffman
Demostraci
on
441
1. n1 y n2 son hermanos: en este
aso, los suprimimos y
olo
amos en su padre n3, que
deviene en hoja, w(n3) = w(n1) + w(n2). En este arbol, n3 esta en un nivel inferior
i 1, por lo que
wpl(T ) = wpl(T ) i(w(n1)+w(n2)) + (i1)(w(n1)+w(n2)) = wpl(T )(w(n1) + w(n2))
|
{z
w(n3 )
n2
s1
p
s1
p
n1
n2
n1
inter
ambiamos
n2
on
s1
Cuando realizamos el inter
ambio entre s1 y n2, el valor de (s1) disminuye a (s1),
pues nivel(s1) = nivel(n1) en T . Por tanto
wpl(T ) = wpl(T ) + nivel(n1) w(n2) (s1) + (s1 ) wpl(T )
El
aso en que son iguales es
uando s1 es una hoja.
Ahora,
uando efe
tuamos la fusion de n1 y n2 en n+, regresamos a la situa
ion del
primer
aso:
wpl(T ) = wpl(T )+(nivel (n1)1)(w(n1)+w(s))(s1)+(s1 ) wpl(T )w(n+)
El
aso
uando nivel(n1) < nivel(n2) es simetri
o
Captulo 4. Arboles
442
Este lema es el fundamento mostrativo que nos permitira eviden
iar que para
ualquier
arbol T T = wpl(T ) wpl(T ).
Retomemos la demostra
ion de la proposi
ion y planteemos indu
ion sobre la
ardinalidad del arbol de Human T :
1. Para |T | 2 el resultado es dire
to.
2. Ahora asumamos que la proposi
ion es
ierta para todo arbol de Human y veri
arboles Tn y Tn.
Puesto que n1 y n2 son los nodos
on menos peso, estos son hermanos en el arbol
de Human. Por tanto wpl(Tn) = wpl(Tn+1) w(n1) w(n2) .
|. Por
w(n2)). Ahora bien, aunque quiza parez
a obvio, |Tn| = |Tn| < |Tn+1| = |Tn+1
tanto, podemos apli
ar la hipotesis indu
tiva para
on
luir que wpl(Tn) wpl(Tn )
26
4.13.6.1
Conclusi
on
Re
apitulemos el uso del algoritmo de Human mediante la dos
lases que a
abamos de
presentar. El ente
odi
ador se vale de una instan
ia de Huffman Encoder Engine, mientras que el de
odi
ador de su
ontraparte de Huffman Decoder Engine. Obviamente,
ambas instan
ias deben ponerse de a
uerdo en usar el mismo arbol de Human. Sin embargo, aqu apare
e un detalle interesante: para
odi
ar o de
odi
ar el texto no ha
e
falta el arbol de Human. Esto es evidente en el
odi
ador, pues, este se remite a leer los
smbolos del texto y, a traves del mapeo de
odigos, a emitir los
orrespondientes prejos.
Pero, >
omo hara el de
odi
ador? La respuesta esta dada por la teora de prejos impartida en x 4.8.0.8 (pagina 372). En efe
to, un texto
odi
ado es una se
uen
ia de palabras
de Di
k (x 4.8.0.9 (pagina 377)), el
ual,
omo
orolario de la proposi
ion x 4.8 (pagina
374), es prejo. Esto impli
a que, dada una se
uen
ia de prejos, podemos re
ono
erlos
entre s. La te
ni
a en
uestion se delega a ejer
i
io.
4.14
Arboles
est
aticos
optimos
Los arboles binarios son dise~nados para emular la busqueda binaria. Su bondad prin
ipal
es que permiten inser
iones y supresiones en O(lg(n)). Ahora bien, si el
onjunto de
laves
fuese estati
o; es de
ir, no o
urre inser
iones ni supresiones, >es, por ejemplo, un arbol
binario de altura mnima e
iente?
Consideremos disponer las
laves en un arreglo ordenado y efe
tuar la busqueda binaria.
Con este enfoque, tenemos un arbol de longitud de
amino mnimo. Ademas, ahorramos
espa
io, pues no requerimos utilizar punteros. Como punto nal, debemos remar
ar que
el arreglo aprove
ha los
a
hes del
omputador.
26 Esto
4.14. Arboles
est
aticos
optimos
443
La busqueda binaria asume que la fre
uen
ia de a
eso es equitativa para todas las
laves y esto no es
ierto en algunos
asos. Algunas
laves se a
eden mas que otras. Hay
situa
iones en que la fre
uen
ia estadsti
a de a
eso puede
ono
erse antes de
onstruir
el arbol. En estos
asos, un
riterio de optimalidad
onsiste en minimizar la
antidad total
esperada de busquedas. Intuitivamente, esta minimiza
ion se logra
olo
ando los nodos de
a
eso mas fre
uente
er
anos a la raz. El
on
epto que nos permitira en
ontrar arboles
optimos subya
e en la deni
ion siguiente:
Definici
on 4.13 (Longitud del camino interna ponderada) Sea un arbol T de n
P
nodos. Sea P = {pi | pi es el peso del nodo ni T | i pi = 1}. Enton
es, la Longi-
n
X
(4.52)
i=1
n
X
nivel(ni) pi
i=1
z
y
x
y
y
z
( )
(d)
z
z
(e)
(f)
(g)
Figura 4.47: Arboles
binarios de busqueda de tres nodos
Dado un
onjunto de n
laves
on sus probabilidades de a
eso, el problema
onsiste
en en
ontrar un arbol de n nodos
uyo Ci sea mnimo. Di
ho de otro modo, >
ual, entre
todos los posibles arboles de n
laves, tiene el Ci mnimo? Como ejemplo,
onsideremos
el
onjunto
de nodos {X, Y, Z}
on pesos {1/9, 3/9, 5/9}. Segun la proposi
ion 4.12 existen
1 6
maneras
de disponer tres nodos en un arbol binario de busqueda, las
uales se
=
5
4 3
ilustran en la gura 4.47. Las longitudes de
amino ponderadas para
ada arbol son:
(a)
Ci
(b)
Ci
(c)
Ci
(d)
Ci
(e)
Ci
1
3 5
14
= 3 +2 + =
9
9 9
9
3
5
21
1
= 1 +2 +3 =
9
9
9
9
1 3
5
15
= 2 + +2 =
9 9
9
9
1
3
5
20
=
+3 +2 =
9
9
9
9
1
3 5
16
= 2 +3 + =
9
9 9
9
Esto arroja al arbol (a)
omo el optimo; resultado sorprendente y revelador de que lo
optimo no ne
esariamente es lo tradi
ionalmente bueno, pues, en nuestro ejemplo, el arbol
optimo es el mas desequilibrado segun los
riterios tradi
ionales.
Captulo 4. Arboles
444
El ejemplo anterior nos da una idea a
er
a de las
omplejidades inherentes a un algoritmo. Podramos generar todos los arboles de busqueda posibles
on n
laves y enton
es
medir las longitudes de
amino ponderadas para sele
ionar la menor. Obviamente, esta
te
ni
a es muy
ostosa en tiempo y espa
io.
El primer paso ha
ia la
onse
u
ion de un algoritmo es aprehender que si un arbol es
optimo, enton
es sus dos ramas tambien lo son. Formalizamos este he
ho en la siguiente
proposi
ion:
Proposici
on 4.15 Sea T B un arbol binario de b
usqueda optimo. Enton
es, L(T ) y
R(T ) son optimos.
Demostraci
on (Por
ontradi
ion):
Sea k1, k2, . . . , kl, . . . , kn el re
orrido injo de un T ABB general de n nodos
on
raz en
lave kl. De este modo, L(T )
ontiene las
laves {k1, k2, . . . , kl1} y R(T ) las
laves
{kl+1, . . . , kn}. Podemos, enton
es, seg
un la deni
ion 4.13, plantear Ci en fun
ion de la
27
raz kl, del siguiente modo :
Ci(T ) = pl +
l1
X
i=1
n
X
i=l+1
= pl + Ci(L(T )) +
l1
X
pi + Ci(R(T )) +
i=1
n
X
n
X
pi
i=l+1
pi + Ci(L(T )) + Ci(R(T ))
(4.53)
i=1
Supongamos que el arbol optimo
ontiene un L(T ) o R(T ) que no es optimo. En este
aso, segun (4.53), Ci(T ) aumentara de valor, lo que
ontradi
e la suposi
ion de que T es
optimo. La proposi
ion es, pues, verdadera
4.14.1
Ci,j = min
l=i
pi,l1(1 + Ci,l1) +
{z
}
|
arbol izquierdo
27 Re ordemos
kl
pl
|{z}
omo ra
z
+ pl+1,j(1 + Cl1,j)
{z
}
|
arbol dere
ho
1
pi,j
(4.54)
que un nivel en L(T ) o R(T ) esta desfasado en uno respe
to a T . Por esa razon,
(nivel(ki )pi , en donde nivel(ki ) se reere a T , es Ci (L(T )); el razonamiento es el mismo
para Ci (R(T ))
Pn
i=l+1
4.14. Arboles
est
aticos
optimos
445
El pro
edimiento es exponen
ial si lo apli
amos dire
tamente. No obstante, los arboles
optimos deben satisfa
er la propiedad de orden y, en este sentido, existen a lo sumo n(n+1)
2
diferentes maneras de distribuir el
onjunto K en un arbol binario. Podemos, enton
es,
dise~nar un algoritmo as
endente que
omien
e por
onstruir todos los n1 arboles optimos
de 2 nodos. A partir de este resultado,
onstruimos los n 2 arboles optimos de 3 nodos
y as su
esivamente.
Los
ostes de los subarboles optimos se alma
enan en una matriz COST[] n n, donde
ada entrada COST(i,j) representa el
osto del arbol optimo entre i y j. Cal
ulamos
iterativamente esta matriz por diagonales hasta llegar a la diagonal COST(1,n), la
ual
ontiene el
osto optimo del arbol de n nodos.
El algoritmo toma
omo entrada dos arreglos de dimension n, donde n representa el
numero de
laves. Al primero lo llamamos keys[] y
ontiene las
laves. Al segundo lo
llamamos [p] y
ontiene las fre
uen
ias de a
eso para
ada
lave.
El algoritmo requiere otra matriz TREE[] que alma
ene las ra
es de los arboles optimos.
Dado un
osto optimo COST(i,j), TREE(i,j)
ontiene el ndi
e de la raz del arbol binario
optimo dentro del arreglo keys.
4.14.2
445
Implantaci
on del problema
(never dened)i
El ar
hivo exporta una fun
ion prin
ipal llamada build optimal tree(). La entrada
de la fun
ion es un arreglo ordenado de
laves keys[], un arreglo paralelo de probabilidades
de a
eso por
ada i-esima
lave y la dimension n de los respe
tivos arreglos. La salida es
la raz de un arbol binario optimo
onforme a las probabilidades dadas.
Las
laves y probabilidades se
olo
an en arreglos estati
os. A
ausa de la gran es
ala,
las matri
es internas son mas dif
iles de mantener estati
as. Por esa razon, y, en a~nadidura,
para no limitarnos por la fragmenta
ion de memoria, usaremos arreglos dinami
os:
Captulo 4. Arboles
446
446a
hDe
lara
i
on de matri
es internas 446ai
DynArray <int> tree((n + 1)*(n + 1));
DynArray <double> cost((n + 1)*(n + 1));
Denes:
cost, used in
hunks 445{47 and 805
.
Uses DynArray 45.
(445)
tree[] es una matriz que alma
ena ndi
es al parametro arreglo keys de la fun
ion
build optimal tree(). cost[] es una matriz que alma
ena
ostes de los arboles par
iales
446b
446
TREE(i,j) alma
ena el ndi
e a la raz de un arbol optimo par
ial que
ontiene las
laves
entre i y j. Al nal, la raz del arbol optimo denitivo sera keys[TREE(1, n)], la raz del
arbol izquierdo keys[TREE(1, TREE(1,n) - 1)], et
etera.
COST(i,j) alma
ena el
osto del arbol binario
on
laves entre keys[i] y keys[j].
Como es la
ostumbre, todo ma
ro debe ser indenido al nal del ar
hivo:
hIndeni
i
on ma
ros opBinTree 446
i
(445)
# undef COST
# undef TREE
Uses COST 446b and TREE 446b.
446d
Ahora denimos la fun
ion compute optimal cost(), la
ual se estru
tura en dos
bloques:
hFun
iones auxiliares opBinTree 446di
(445) 447b
static inline
void compute_optimal_costs(DynArray <double> & cost,
double
p[],
const int &
n,
DynArray <int>
& tree)
{
hIni
iar estru
turas internas 446ei
h
onstruir
}
Denes:
compute optimal cost()
al
ula las matri
es de
ostes COST[] y los ndi
es de las ra
es
de
ada subarbol optimo en TREE[]. El primer bloque hIni
iar estru
turas internas 446ei,
446e
olo
a los valores ini
iales en las respe
tivas matri
es:
hIni
iar estru
turas internas 446ei
int i, j, k;
(446d)
4.14. Arboles
est
aticos
optimos
447
447a
El segundo bloque es el que
al
ula los
ostes denitivos. Primero
on los subarboles
de 2 nodos, luego
on los de 3, y as su
esivamente hasta obtener el valor de COST(0, n-1)
y la raz resultante en TREE(0, n-1):
h
onstruir
ostes
optimos 447ai
(446d)
for (i = 0; i
for (j = 1;
{
k
TREE(j,
COST(j,
< n; ++i)
j <= n - i; ++j)
= j + i;
k) = min_index(cost, j, k, n);
k) = sum_p(p, j, k) +
COST(j, TREE(j, k) - 1) +
COST(TREE(j, k) + 1, k);
Uses COST 446b, cost 446a, min index 447
, sum p 447b, and TREE 446b.
447b
447
Captulo 4. Arboles
448
{
min_sum = sum;
ret_val = i;
}
}
return ret_val;
}
Denes:
De la forma planteada, min index() es O(n),
uya llamada o
urre dentro de dos lazos
O(n). Lo anterior impli
a que nuestro algoritmo es O(n3) un tiempo mu
ho mejor que
el exponen
ial que produ
ira el algoritmo a fuerza bruta, pero aun
uestionable para
valores de n muy grandes. La busqueda del ndi
e mnimo
al
ulado en min index()
puede mejorarse si logramos demostrar que el subarbol mnimo se en
uentra en el rango
TREE(j, k - 1) TREE(j, k) TREE(j + 1, k). En este
aso, puede demostrarse que
el algoritmo es O(n2), lo que se delega
omo ejer
i
io.
54
38
29
66
42
63
24
69
71
98
108
Figura 4.48: Arbol
binario optimo de 14
laves distribuidas binomialmente
on p = 0, 5
448
Nos resta
onstruir el arbol a partir de la matriz TREE. El ndi
e de la raz es TREE(1,n),
el de su subarbol izquierdo es TREE(1, TREE(1,n)) y as su
esivamente. Estamos listos,
pues, para dise~nar la rutina:
hFun
iones auxiliares opBinTree 446di+
(445) 447
template<class Node, typename Key>
Node * compute_tree(Key
DynArray <int>&
const int
int
int
{
if (i > j)
return Node::NullPtr;
static inline
keys[],
tree,
n,
i,
j)
449
4.15
Notas bibliogr
aficas
Captulo 4. Arboles
450
Los
odigos de Human son homonimos de David A. Human [9 quien los des
ubrio
durante la realiza
ion de sus tesis do
toral.
Los heaps, junto
on el heapsort, fueron presentados por primera vez por
J. W. J. Williams [19 y R. W. Floyd [5.
En la opinion de este reda
tor, la mas autenti
a y magistral expli
a
ion sobre heaps la
ofre
e Bentley en \Programming Pearls" [1. La expli
a
ion es tan ex
elsa que desde ha
e
mu
hos a~nos (1988)
uando este reda
tor estudio los heaps, este no puede desprenderse de
su esquema de presenta
ion de los heaps. Sirva el presente parrafo
omo un re
ono
imiento
y a la vez
omo una
ita.
Cormer, Leiserson y Rivest [3 presentan desarrollos alternativos de arboles extendidos
para otra
lase de arbol equilibrado llamado rojo-negro.
El ahora
lasi
o de Sedgewi
k, \Algorithms in C: Parts 1{4" [17, se
ara
teriza por
exponer mu
hos algoritmos, de forma muy
on
isa pero a ve
es in
ompleta y en detrimento
del formalismo. A pesar de ello, el Sedgewi
k es una de las mejores referen
ias para indagar
algun algoritmo espe
o.
El volumen 3 del \Art of
omputer programming" de Knuth [11
ontiene un analisis
matemati
o bastante exhaustivo de los arboles binarios de busqueda y de los arboles
optimos.
El \Introdu
tion to Algorithms" de Cormen, Leiserson y Rivest [3,
onsagra un
aptulo a las extensiones sobre arboles binarios. Aunque este
aptulo se basa en una
lase
parti
ular de arbol llamado \rojo-negro" (ver x 6.5), las extensiones propuestas pueden
usarse sobre pra
ti
amente
ualquier
lase de arbol binario de busqueda.
El algoritmo de inser
ion en la raz presentado en x 4.9.7 fue des
ubierto por Stepheson
y reportado en \A method for
onstru
ting binary sear
h trees by making insertions
at the root" [18. Este trabajo mar
o un hito sobre los arboles binarios, pues dio pie a
nuevas ideas y usos, notablemente, los algoritmos de
on
atena
ion y parti
ion presentados
en x 4.9.8 y x 4.11.6 y los arboles aleatorizados que seran presentados en x 6.2.
Los arboles binarios de busqueda optimos fueron propuestos y estudiados por
Hu y Tu
ker en 1971 [7.
4.16
Ejercicios
1. Modique el algoritmo 4.1 para que imprima un arbol arbitrariamente grande que
no quepa en el medio de impresion (pantalla o impresora) (+)
2. Dise~ne un algoritmo que tome
omo entrada una se
uen
ia desordenada (una lista o
un ve
tor, po
o importa), de pares <Valor nodo, Numero de Deway>, y retorne el
arbol representado
on listas enlazadas y
on arreglos.
3. Dise~ne un algoritmo que tome de entrada un arbol
ualquiera y retorne una se
uen
ia
ordenada de numeros de Deway. Considere los dos tipos de representa
ion: listas
enlazadas y arreglos.
4. Dis
uta y dise~ne una estru
tura de dato orientada ha
ia la nota
ion de Deway. Dis
uta algunos
ontextos en los
uales su dise~no sera justi
ado.
5. Dise~ne un algoritmo que busque una
lave en un arbol n-ario representado mediante
listas enlazadas. Dis
uta sobre las diferentes estrategias de busqueda.
4.16. Ejercicios
451
Captulo 4. Arboles
452
25. Dise~ne un algoritmo no re
ursivo que efe
tue el re
orrido prejo sobre un arbores
en
ia espe
i
ada mediante el TAD Tree Node<T>.
26. Dise~ne un algoritmo no re
ursivo que efe
tue el re
orrido sujo sobre un arbores
en
ia espe
i
ada mediante el TAD Tree Node<T>.
27. Considere los siguientes re
orridos sobre una arbores
en
ia:
Prefijo : 4 2 1 3 18 16 12 8 5 6 7 9 10 11 14 13 15 17 19 20
Sufijo : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21
7
16
8
10
18
13
3
22
11
17
24
19
14
12
23
20
15
25
4.16. Ejercicios
453
15
11
14
12
13
10
17
16
18
26
19
21
22
23
24
20
28
27
29
25
C(ni)
ni T|ni interno
30
31
Captulo 4. Arboles
454
45. Es
riba una version no re
ursiva, sujo, del algoritmo de destru
ion mostrado en
x 4.4.10 que, en
aso de que o
urra un desborde de pila, deje el arbol en un estado
onsistente.
46. Es
riba un programa que re
orra un arbol binario por niveles de dere
ha a izquierda.
47. Implante el re
orrido sujo mediante pseudo-hilado (++).
48. Modique el re
orrido injo pseudo-hilado para obtener un re
orrido injo inverso;
es de
ir, el re
orrido injo de dere
ha a izquierda (+).
49. Dise~ne un algoritmo que re
iba
omo entrada el numero de Deway de un nodo
ualquiera y el arbol binario donde reside el nodo y retorne el nodo en
uestion.
50. Dise~ne un algoritmo que tome de entrada un arbol binario y
onstruya un
onjunto
ompuesto por todos los nodos representados en numeros de Deway.
51. En base a lo planteado al ini
io de x 4.8 (pagina 372),
al
ule el numero de arbores
en
ias posibles que se pueden plantear
on n nodos.
52. Dise~ne un algoritmo que determine si una se
uen
ia de bits
orresponde a una palabra de Lukasiewi
z.
53. Dado un arbol binario, dise~ne un algoritmo que genere la palabra de Lukasiewi
z
orrespondiente.
54. Demuestre que el lenguaje de Di
k es prejo.
55. Dise~ne un algoritmo que imprima el re
orrido por niveles inverso de un arbol binario.
Es de
ir, primero se listan de izquierda a dere
ha los nodos del ultimo nivel; luego
los del penultimo y as su
esivamente hasta llegar a la raz.
56. Dado un arbol binario
ualquiera, el re
orrido
omplemento por niveles es denido
omo el listado de los nodos que faltan por nivel a partir de la raz hasta el nodo de
mayor altura. Por ejemplo, para el arbol de la gura 4.49, el re
orrido
omplemento
es 7, 8, 11, 14, 15; es de
ir, los nodos que faltaran para que el arbol este
ompleto
hasta su altura.
1
3
2
4
8
5
9
10
6
11
12
7
13
14
15
Figura 4.49: Arbol
binario ejemplo
(a) Dise~ne un algoritmo que
al
ule el re
orrido
omplemento de un arbol binario.
Asuma que los nodos estan etiquetados por nivel a partir de la posi
ion
ero.
(+)
4.16. Ejercicios
455
(b) Dise~ne un algoritmo que genere un arbol
omplemento. Esto es, un arbol
uyas
hojas en
ajen
on los nodos faltantes del arbol para que el arbol este
ompleto.
(+)
57. Dise~ne un algoritmo que \pode" un arbol binario; es de
ir, que elimine la mnima
antidad de nodos para volverlo un arbol
ompleto.
58. Dise~ne un algoritmo que en
uentre el mas largo
amino por la dere
ha
ontenido en
un arbol binario.
59. Dise~ne un algoritmo que
onstruya un arbol binario a partir de una palabra de
Lukasiewi
z. (+)
60. Dise~ne un algoritmo que genere un arbol binario a de n nodos donde n es un valor
onstante (+).
61. Dise~ne un algoritmo que determine si una expresion algebrai
a tiene sus parentesis
balan
eados. (+)
62. Dise~ne un algoritmo que genere un arbol binario aleatorio de n nodos. La
antidad n
es un parametro del algoritmo y todos los arboles deben tener la misma probabilidad.
(++)
63. Dise~ne un algoritmo que
onstruya un arbol binario de busqueda a partir de su
re
orrido sujo.
64. Dado un arbol binario de busqueda, dise~ne un algoritmo que
onstruya una se
uen
ia
de inser
ion que reproduz
a un arbol binario equivalente.
65. Dise~ne e implante un algoritmo que liste los rangos de
laves que no se en
uentran
en el arbol.
66. Dise~ne un nodo binario que, ademas de la
lave, tenga un enla
e doble Dlink. Modique las rutinas de inser
ion y elimina
ion en arbol binario de busqueda para que
el
ampo Dlink funja de hilos expl
itos. El re
orrido de la lista debe
orresponder
al re
orrido injo en el arbol binario.
67. Dise~ne una version iterativa, sin usar pila, del pro
edimiento insert in binary search tree().
68. Dise~ne una version iterativa, sin usar pila, del pro
edimiento remove from search binary tree().
69. Dise~ne una version iterativa, si utilizar pila, del pro
edimiento split key rec().
70. Dibuje los arboles binarios de busqueda
orrespondientes a la parti
ion de siguiente
arbol segun la
lave 250:
Captulo 4. Arboles
456
244
122
279
41
5
4
203
49
156
36
66
163
65
63
214
73
159
266
255
245
270
293
291
297
264
189
72
54
71. Es
riba una version de la elimina
ion de un arbol binario de busqueda que,
uando
se trate de suprimir un nodo
ompleto, es
oja al azar
ual sera el nodo a sele
ionar
omo raz del join exclusive() >Cuales seran las ventajas de este enfoque?
72. Dise~ne una version iterativa, sin usar pila, del pro
edimiento remove from binary tree().
Ayuda: revise los fundamentos expli
ados en x 4.9.6-1.
73. Demuestre que si no hay
laves dupli
adas, enton
es join() y join preorder() son
equivalentes. Es de
ir, produ
en exa
tamente el mismo arbol.
74. Dibuje el arbol binario resultante para las siguientes se
uen
ias de inser
ion:
(a)
(b)
(
)
(d)
(e)
(f)
220 1400 315 534 1223 1339 329 514 1207 1019 182 146 1317 435 646
1207 726 520 1038 10 331 316 31 1203 1277 480 755 1422 1231 574
170 1247 893 411 378 1371 105 186 906 689 747 115 858 453 1079
97 401 815 54 99 583 102 654 155 725 256 737 1193 354 854
649 858 699 148 57 986 1438 1194 824 996 1119 434 15 226 758
346 787 891 1405 1111 1204 1181 496 314 1014 529 1087 481 1146 1343
75. Dibuje el arbol binario resultante para las siguientes se
uen
ias de inser
ion en la
raz:
(a)
(b)
(
)
(d)
271 1450 171 93 907 123 1341 329 714 231 1079 628 1270 1214 320
990 505 834 119 329 47 1283 1303 468 970 697 239 596 794 1215
1484 145 320 1025 609 932 270 1115 846 1211 1287 1095 855 1059 1343
201 607 1137 1099 1197 203 840 741 526 929 1370 190 996 205 715
4.16. Ejercicios
457
(e) 420 78 221 1475 1155 1200 1218 1257 1061 1053 1124 53 665 743 1238
(f) 429 1074 459 437 1041 979 675 1127 213 664 715 1158 117 844
76. Dados los siguientes re
orridos prejos de arboles binarios de busqueda:
(a)
(b)
(
)
(d)
(e)
161 944
810 409
580 605
192 218
277 266
549
783
741
850
942
123 478 408 947 1009 831 303 1250 1363 1352 1206 1487 1369 1043 230
15 116 512 1014 342 1049 1085 1028 1267 1294 1288 1117 1468 1340 47
48 84 408 141 898 815 798 619 488 26 1013 1341 1410 1142 1032
230 318 315 109 433 368 367 558 576 461 327 1193 801 1288 733
30 83 193 456 389 495 1056 1001 565 1275 1495 1422 558 521 103
10
14 15 16
17 18 19
20 21 22
23 24 25
26 27 28
29 30 31
11 12 13
32 33 34
Y, ademas,
on la propiedad de orden; o sea que para
ada
lave del arbol, las
laves
des
endientes siempre son mayores.
Considere la siguiente opera
ion sobre un nodo
ualquiera p de un heap trinario:
Captulo 4. Arboles
458
Node *
child(Node * p, int i)
4.16. Ejercicios
459
89. Dise~ne un algoritmo que
uente la
antidad de nodos en el i-esimo nivel de un arbol
binario. (+)
90. Dise~ne un algoritmo que
uente la
antidad de nodos in
ompletos no hojas de un
arbol binario.
91. Dise~ne un algoritmo que
uente la
antidad de nodos llenos de un arbol binario.
92. Suponga que
ada nodo posee un
ampo adi
ional next. Es
riba un algoritmo que
enla
e todos nodos del arbol segun el re
orrido injo.
93. Para los siguientes pares de arboles representados
on sus re
orridos prejos,
onstruya el arbol produ
to de la opera
ion join().
(a)
(b)
( )
(d)
(e)
(f)
94. Dados 2 arboles binarios T1 y T2, se dene la rela
ion \es simetri
o", denotada
omo
T1 T2
omo:
T1 T2
T1 = T2 = = T1 T2
Dise~ne un algoritmo que tome
omo entrada dos arboles y determine si son
simetri
os.
95. Dise~ne e implante una version iterativa, sin usar pila, del algoritmo inorder position().
96. Modique la primitiva insert in pos() expli
ada en x 4.11.3 para que valide que
la
lave del nodo a insertar no viole la propiedad de orden de un ABB.
97. Dise~ne un algoritmo re
ursivo que
al
ule la posi
ion inja de una
lave del arbol.
98. Dado un arbol binario de busqueda
on rangos y una
lave de de busqueda, dise~ne
un algoritmo que retorne la posi
ion inja de la
lave si esta se en
uentra en el arbol,
o la posi
ion inja que tomara la
lave despues de la inser
ion.
99. Implante un algoritmo re
ursivo que reali
e la union general de dos ABBE.
Captulo 4. Arboles
460
100. Implante un algoritmo iterativo, que no use pila, que reali
e la union general de dos
ABBE.
101. Dise~ne un TAD basado en arboles
on rango que modele arreglos e
ientes.
102. Dise~ne una version iterativa, sin usar pila, del algoritmo split key rec() y que deje
el arbol inta
to si la
lave ya se en
uentra en el arbol. (++)
103. Dise~ne una version iterativa de la fun
ion split pos rec() expli
ada en x 4.11.6.
104. Dise~ne una rutina que busque una
lave en un arbol binario y, en
aso de en
ontrarla,
rote el nodo hasta la raz.
105. Demuestre que el arbol binario produ
ido por insert in binary search tree()
on la se
uen
ia de inser
ion S =< k1, k2, . . . , kn > es exa
tamente igual al produ
ido
por insert root() sobre la se
uen
ia invertida S =< kn, kn1, . . . , k1 >.
106. Dise~ne un algoritmo re
ursivo, basado en rota
iones, que reali
e la inser
ion por la
raz en un ABB. El algoritmo debe bus
ar el nodo externo que albergara al nuevo
nodo y despues rotar re
ursivamente a su padre hasta que este devenga en raz.
107. (Sugerido por Andres Arcia) Explique la opera
ion que realiza la siguiente
rutina:
Node * program(Node * root)
{
if (root == NullPtr)
return NullPtr;
while (RLINK(root) != NullPtr)
root = rotate_to_left(RLINK(root));
program(LLINK(root));
return root;
}
108. Dise~ne una rutina que sele
ione un nodo en la i-esima posi
ion y lo rote el nodo
hasta la raz.
109. Demuestre que las primitivas insert root rec() y insert root() siempre produ
en arboles equivalentes.
110. Dado un arbol binario de busqueda y el algoritmo de inser
ion presentado en x 4.9.3,
al
ule la varianza sobre el numero de nodos visitados en una busqueda. (++)
111. >Por que el valor de Node::MaxHeight = 80 es ade
uado para arboles binarios de
busqueda?
4.16. Ejercicios
461
112. Dado un arbol binario de busqueda y el algoritmo de inser
ion en la raz presentado
en x 4.9.7,
al
ule las esperanzas y varianzas de las
antidades de nodos visitados en
busquedas exitosas e infru
tuosas. (++)
113. Dado un ABBE, dise~ne un algoritmo e
iente que extraiga un rango de
laves entre
i y j, j > i. (++)
114. Dados dos arboles ABBE A1 y A2, dise~ne un algoritmo e
iente que inserte A1 a
partir de la posi
ion i de A2.
115. Considere la primitiva remove by pos() expli
ada en x 4.11.9. Dise~ne una version
basada en elimina
ion de la raz y
on
atena
ion de los subarboles restantes.
116. Modique los algoritmos de inser
ion, elimina
ion,
on
atena
ion y parti
ion para
que fun
ionen en arboles binarios de busqueda extendidos.
117. Dise~ne un algoritmo iterativo e
iente que efe
tue la
on
atena
ion de dos arboles
binarios. (+)
118. Dise~ne un algoritmo re
ursivo e
iente que efe
tue la union de dos arboles binarios.
119. Dise~ne un algoritmo iterativo e
iente que efe
tue la union de dos arboles binarios.
(+)
120. Dise~ne un algoritmo re
ursivo e
iente que efe
tue la interse
ion de dos arboles
binarios.
121. Dise~ne un algoritmo iterativo e
iente que efe
tue la interse
ion de dos arboles
binarios. (+)
122. Dise~ne un algoritmo re
ursivo e
iente que efe
tue la diferen
ia de dos arboles binarios T1 yT2. Esto es, T1 T2 debe retornar el arbol binario T1 ex
epto aquellas
laves
que se en
uentran en T2.
123. Es
riba un programa que inserte las n
laves de manera que el arbol quede equilibrado. (+)
124. En
uentre una expresion analti
a que genere la su
esion de inser
iones para que el
arbol quede equilibrado (++).
Ayuda: Considere n = 2k; es de
ir, n es una poten
ia exa
ta de dos.
125. Reali
e un algoritmo que efe
tue la union de dos arboles binarios de busqueda extendidos. El arbol resultante no debe tener
laves repetidas.
126. Reali
e un algoritmo que efe
tue la interse
ion de dos arboles binarios de busqueda
extendidos. El arbol resultante no debe tener
laves repetidas.
127. Modique las rutinas insert root rec() e insert root() presentadas en 4.9.7 para
que manejen arboles binarios extendidos.
128. Efe
tue medidas de rendimiento para todos los re
orridos implantados en
BinNode Utils. De un reporte detallado de sus experimentos que se~
nale por
ada
version de re
orrido el numero de nodos y el tiempo total de re
orrido.
Captulo 4. Arboles
462
129. Dise~ne e implante un tipo abstra
to de dato que represente un arbol binario que
o
upe espa
io mnimo. Es de
ir, la implanta
ion debera manejar los tres tipos de
nodos se~nalados en x 4.4.15-1:
ompleto, in
ompleto y hoja.
130. Dise~ne un TAD de uso general que modele arboles n-rios basados en el TAD
DynArray<T>.
131. Dadas n
laves, demuestre que existen
nan las n
laves.
n(n1 )
2
132. Sea S una se
uen
ia
odi
ada segun el algoritmo de Human. Dise~ne un algoritmo
que lea la se
uen
ia, distinga los prejos distintos y retorne el arbol de Human
orrespondiente a los prejos. (+)
133. >Cual es la equivalen
ia entre una palabra de Di
k y un numero de Deway?
134. Cal
ule el arbol optimo para los siguientes
onjuntos de
laves y probabilidades:
(a) 10 17 30 31 63 68 74 80 88 93 99 103 114 143 14
0.00003 0.00046 0.00320 0.01389 0.04166 0.09164 0.15274 0.19638 0.19638
0.15274 0.09164 0.04166 0.01389 0.00320 0.00046
135. Cal
ule las distribu
iones de probabilidad para
ada uno de los arboles de la pregunta
anterior.
136. Los arboles estati
os optimos presentados en x 4.14 asumen que las
laves a bus
ar
siempre estan presentes. Considere el problema de
onstruir un arbol optimo donde
es posible efe
tuar busquedas infru
tuosas. Es este
aso, se dene un arreglo adi
ional
q de dimension n + 1 donde
ada qi representa la probabilidad de que una
lave de
P
Pn
busqueda este
omprendida entre ki y ki+1 tal que n
i=1 pi +
i=0 qi = 1.
(a) Para esta varia
ion del problema, dena la longitud del
amino ponderada.
(b) Modique el algoritmo presentado en x 4.14 para que se
onsideren busquedas
infru
tuosas.
Ayuda: Considere que la busqueda infru
tuosa es realizada ha
ia un nodo externo. (+)
137. (Tomado y traducido de Lewis-Denenberg [14]) Segun los terminos presentados en x 4.14, sea TREE(j,k), 1 j k n, la raz del arbol binario optimo para
el
onjunto de
laves kj, . . . , kk.
4.16. Bibliografa
463
Bibliografa
[1 J. L. Bentley. Programming Pearls. Addison-Wesley, 1986.
[2 J. Berstel and D. Perrin. Theory of Codes. A
ademi
Press, New York, 1985.
[3 T. H. Cormen, C. E. Leiserson, and R. L. Rivest. Introdu
tion to Algorithms. MIT
Press, Cambridge, MA, USA, 1989.
[4 C. A. Crane. Linear lists and priority queues as balan
ed binary trees. PhD thesis,
Computer S
ien
e Department, Stanford University, 1972.
[5 R. W. Floyd. Algorithm 245 : Treesort 3. Comm. ACM, 7:701, 1964.
[6 Frank Harary. Graph Theory. Addison-Wesley, Reading, MA, 1969.
[7 T. C. Hu and A. C. Tu
ker. Optimal
omputer sear
h trees and variable length
alphabeti
odes. SIAM J. Applied Math., 21:514{532, 1971.
[8 D. A. Human. A method for the
onstru
tion of minimum redundan
y
odes. Pro
.
I.R.E., 40:1098{1101, 1951.
[9 David A. Human. A method for the
onstru
tion of minimum-redundan
y
odes.
Pro
eedings of the IRE, 40(9):1098{1101, September 1952.
[10 Donald E. Knuth. Fundamental Algorithms, volume 1 of The Art of Computer
Programming. Addison-Wesley, Reading, MA, USA, third edition, 1997.
[11 Donald E. Knuth. Sorting and Sear
hing, volume 3 of The Art of Computer
Programming. Addison-Wesley, Reading, MA, USA, se
ond edition, 1998.
[12 Donald L. Kreher and Douglas R. Stinson. Combinatorial Algorithms. CRC Press,
1999. ISBN 0-8493-3988-X.
[13 Yedidyah Langsam, Moshe Augenstein, and Aaron M. Tenenbaum. Data stru
tures
using C and C++. Prenti
e-Hall, Upper Saddle River, NJ 07458, USA, 1996.
464
Captulo 4. Arboles
[14 Harry R. Lewis and Larry Denenberg. Data Stru
tures and Their Algorithms.
Harper Collins Publishers, New York, 1991.
[15 J. Lukasiewi
z. O zna
zeniu i potrezeba
h logiki matematy
znej (on the importan
e
and needs of mathemati
logi
). Nauka Polska, 10:604{620, 1929.
[16 A. J. Perlis and C. Thornton. Symbol manipulation by threaded lists. Communi
ations of the ACM, 3(4):195{204, April 1960.
[17 Robert Sedgewi
k. Algorithms in C: Parts 1{4: Fundamentals, data stru
tures,
sorting, sear
hing. Addison-Wesley, Reading, MA, USA, 1998.
[18 C. J. Stephenson. A method for
onstru
ting binary sear
h trees by making insertions at the root. International Journal of Computer and Information S
ien
es,
9(1):15{29, February 1980.
[19 J. W. J. Williams. Algorithm 232 : HEAPSORT. Comm. ACM, 7:347{348, 1964.
Captulo 5
Tablas hash
La te
nologa ha transformado la faz del mundo fsi
o,
omun e indispensable para la vida,
que
omprende a esa totalidad llamada planeta. Pudieramos de
ir que hasta ha
e no menos
de un siglo, toda persona aso
iaba lo natural a la naturaleza; es de
ir, al mundo lleno de
aire, tierra y agua y en el
ual se distinguan los tres grandes reinos: mineral, vegetal y
animal, en el
ual apenas se notaban par
elas de lo humano. Hoy en da, pra
ti
amente
no hay espa
io del planeta que no este permeado por lo humano, a tal extremo que para
mu
hos una estru
tura de
on
reto,
on la que
onvive desde el na
imiento, les pare
e
ompletamente natural.
Una mirada aerea sobre la faz terrestre nos propor
iona una buena idea a
er
a de
uanto la te
nologa ha transformado el mundo. La vista esta delineada por polgonos
on
sus lneas regulares, a pesar de que estos partieron de abstra
iones matemati
as, que no
son naturales. La perspe
tiva aerea de la faz terrenal nos revela parte de los profundsimos
ambios que esta ha sufrido al observar en la tierra polgonos y lneas perfe
tas.
De los diverssimos objetos te
nologi
os de hoy en da, uno no tan moderno, pero que ha
ontribuido de
isivamente a ese
ambio del mundo, lo en
arna el automovil. Detengamonos
un breve momento y meditemos
omo sera el mundo si no existiese este artefa
to.
El automovil, que hoy nos es \natural", es un medio de transporte esen
ial para nuestra
forma de vida. Y, sin embargo, es impresionante
uan alienante nos puede ser .
De manera un po
o simplista, podemos de
ir que
uando un automovil no esta en
movimiento este o
upa un espa
io. Toda a
tividad humana que
uente
on el automovil
parti
ular
omo medio de transporte, en detrimento del transporte publi
o, debe
onsiderar el esta
ionamiento. Los
entros
omer
iales de hoy da, i
onos de un tipo so
ialmente
parti
ular de \buena vida", indi
adores del
onsumismo desenfrenado de esta epo
a y
ara
tersti
a pe
uliar de un estrato so
ial, son buenos ejemplos.
Un problema de interes
uando se dise~na una obra
ivil en un ambiente donde no exista
transporte publi
o -
uestion que debera de ser muy ex
ep
ional-
onsiste en interrogar por
la
antidad de super
ie para el esta
ionamiento. Un primer paso
onsiste en estimar la
antidad esperada de automoviles y as de
idir la
apa
idad en lugares del esta
ionamiento.
Grosso modo, un esta
ionamiento puede pi
torizarse del siguiente modo:
1
11
6
M2
.......................................
M2 M1
1 Y la transforma
i
on que este le ha impartido a una
iudad
omo Merida, bastante representativa de
lo \natural", es un buen indi
ador.
465
466
Generi
amente, el esta
ionamiento tiene M puestos posibles numerados se
uen
ialmente.
Los automoviles arriban se
uen
ialmente y se esta
ionan en el primer puesto disponible. El
ono
imiento
onsolidado re
omienda que tal
apa
idad arroje un por
entaje de plenitud
entre el 75% y 90%. Es de
ir, segun lo esperado, debe haber un sobrante entre el 25 % y
10 %.
>Que tiene que ver la situa
ion del esta
ionamiento
on los algoritmos y estru
turas de datos? Consideremos un
onjunto generi
o K de
laves, dentro de un posible
dominio K, y un esta
ionamiento
on
apa
idad M que albergara las
laves. Ahora
onsideremos la existen
ia de una fun
ion inye
tiva h(k) : K [0, M 1], la
ual puede
pi
torizarse as:
2
[0..M 1]
0
i
1
2
M1
M1
M1
Definici
on 5.1 (Tabla Hash) Sea un
onjunto de
laves K
ir
uns
rito dentro de un
dominio K. Una tabla hash se dene
omo:
1. i = h(k) mod M
2. A[i].key = k
3. A[i].busy = 1
2 h : D R
se di
e que es inye
tiva x, y D = h(x) 6= h(y). Una fun
ion inye
tiva tambien
llamada \uno a uno".
3 De por s, esta ha sido la de
isi
on de algunos tradu
tores al
astellano. En el
aso
on
reto, la tradu
ion
astellana [3 de \Data Stru
tures and Algorithms de Aho, Hop
roft y Ullman" [2.
467
B
usqueda de clave k:
1. i = h(k) mod M
2. if (A[i].busy == 1) return true; else return false;
Eliminaci
on de clave k:
1. i = h(k) mod M
2. A[i].busy = 0
Notemos que ninguna de las opera
iones
ontiene itera
iones. Por tanto, el
oste de las
tres opera
iones depende dire
tamente del
oste de h(k) : K [0, M 1]. Conse
uentemente, si h(k) : K [0, M 1] es O(1), enton
es las tres opera
iones son O(1). He aqu,
pues, la grandeza de esta estru
tura de datos.
Si h(k) : K [0, M 1] no es inye
tiva, enton
es es posible que a dos
laves distintas
ki, kj K se la h(k) : K [0, M 1] les asigne el mismo valor; o sea, h(ki) = h(kj). En
este
aso, de
imos que existe una colision. La mayora de las ve
es no
ono
emos
on
exa
titud el
onjunto de
laves K K, lo
ual ha
e posible una
olision.
As las
osas, para que una tabla hash
on una fun
ion h(k) : K [0, M 1] no inye
tiva satisfaga opera
iones en O(1), esta debe
ontemplar dos requerimientos esen
iales:
1. El
omputo de la fun
ion h(k) : K [0, M 1] debe realizarse en O(1),
on un muy
po
o
oste
onstante y su
omportamiento debe emular a una distribu
ion dis
reta
uniforme entre [0, M 1].
Puesto que pueden haber
olisiones, estas deben redu
irse al mnimo posible; este es
el sentido de que h(k) : K [0, M 1] emule la aleatoriedad.
2. Por la misma posibilidad de tener
olisiones, aunque fuese mnima, se debe tener una
estrategia para manejarlas; es de
ir, se debe de
idir, sistemati
amente, que ha
er
on
aquellas nuevas
laves que
olinden
on otras ya presentes en la tabla. La estrategia
debe ser tal que no sa
rique el tiempo O(1)
lamado para una tabla hash.
5.1
Manejo de colisiones
468
Sea una xi,j una variable aleatoria denida del siguiente modo:
xi,j =
0
1
(5.1)
(5.2)
xi,j
i6=i
Cuya esperanza E(X), en virtud de las deni
iones planteadas, nos propor
iona la
antidad
esperada de pares de personas que
umplen a~nos el mismo da.
Por deni
ion:
X
xi,j ;
E(X) = E
(5.3)
i6=i
la
ual, si todas las variables xi,j son independientes entre s, puede plantearse
omo:
E(X) =
E(xi,j) .
(5.4)
i6=i
Lo que nos plantea la ne
esidad de
al
ular E(xi,j), la
ual es, por deni
ion, as:
E(xi,j) = 0 P(xi,j = 0) + 1 P(xi,j = 1) = P(xi,j = 1) .
(5.5)
La
uestion requiere, pues,
al
ular P(xi,j = 1); es de
ir, la probabilidad de que dos personas parti
ulares
umplan a~nos el mismo da y esta, a la in
redulidad de algunos, es:
P(xi,j = 1) =
1
.
365
730
2921 1
2921 + 1
n1 =
, n2 =
27, 523138 .
2
2
Esto impli
a que para un grupo de 28 personas es esperado en
ontrar un par
on la misma
fe
ha de
umplea~nos; para el doble de personas (n = 56, E(X) = 5655
730 = 4.2192), podemos
esperar
uatro pares de personas
on el mismo
umplea~nos.
Para un su
eso en aparien
ia aleatorio e independiente
omo lo es la fe
ha de
na
imiento, la probabilidad de
olision es bastante alta, lo que indi
ia la ne
esidad de
prepararse a su eventualidad. En lo que sigue, dis
utiremos varias estrategias para alma
enar
olisiones en una tabla hash.
5.1.2
469
de la misma tabla.
tabla.
Cada una de estas te
ni
as tiene sus ventajas y desventajas y se ade
ua segun el
ontexto
de uso.
5.1.3
Encadenamiento
La idea fundamental del en
adenamiento es que estas
olisiones se en
uentran en una lista
simple o doblemente enlazada. Cuando los nodos de la lista se guardan fuera de la tabla,
de
imos que se trata de en
adenamiento separado. En el sentido inverso,
uando los propios
nodos de la lista son parte de la tabla, de
imos que se trata de en
adenamiento
errado.
5.1.3.1
Encadenamiento separado
k10
k1
1
2
k8
3
4
5
k9
k5
k2
6
7
8
k3
9
10
k7
k4
k11
k6
11
M2
M1
4 Esta
470
470a
on hash 472ai
hPrototipo de puntero a fun
i
hMiembros dato de GenLhashTable 472bi
etodos
hM
hCubetas
Denes:
de LhashTable<Key> 470bi
470b
471
{ /* empty */ }
LhashBucketVtl() { /* Empty */ }
virtual ~LhashBucketVtl() { /* Empty */ }
LhashBucketVtl(const Key & key) : Dnode<Key>(key) { /* Empty */ }
Key & get_key() { return this->get_data(); }
};
Denes:
LhashBucket, used in
hunks 471 and 504a.
LhashBucketVtl, used in
hunks 471 and 504a.
Uses Dnode 106a.
El empleo del tipo Dnode<T> nos fa
ilita grandemente las opera
iones; en parti
ular
la de elimina
ion, la
ual en una lista simple requerira mantener un puntero al elemento
prede
esor.
La uni
a distin
ion entre una
lase y la otra es la presen
ia del destru
tor virtual.
Hay dos maneras de aso
iar otros datos a la
lave de tipo Key denida en la
ubeta:
1. Se
rea un registro, por ejemplo de tipo Record, que
ontenga todos los
datos de interes, in
luida la
lave de tipo Key. Despues se de
lara una
ubeta (LhashBucket<Record> o LhashBucketVtl<Record>, y se dene el operador
Record::operator==(const Record&) para que solo
ompare la
lave de tipo Key.
La tabla hash puede denirse mediante
ualquiera de los tres tipos GenLhashTable,
LhashTable o LhashTableVtl. Los dos u
ltimos tipos se
orresponden
on tablas que
manejan
ubetas sin o
on destru
tores virtuales y se denen por espe
ializa
ion de
GenLhashTable:
471
hCubetas de LhashTable<Key> 470bi+
(470a) 470b
template <typename Key>
class LhashTable : public GenLhashTable<Key, LhashBucket<Key> >
{
};
472
472a
Obviamente, un atributo esen
ial, del
ual hablaremos mas adelante, es la fun
ion hash,
que transforma una
lave de tipo Key a un natural. Tal fun
ion debe tener el prototipo
siguiente:
hPrototipo de puntero a fun
i
on hash 472ai
(470a)
typedef size_t (*Hash_Fct)(const Key &);
472b
472
La \mision" de este tipo es manejar de forma generi
a la fun
ion hash. Todo
onstru
tor
de tabla hash requiere esta fun
ion.
El segundo atributo es la tabla misma; es de
ir, al arreglo de punteros a
ubetas, el
ual se dene de la siguiente manera:
hMiembros dato de GenLhashTable 472bi+
(470a) 472b 472d
typedef Dnode<Key> BucketList; // Tipo lista de cubetas
472d
Hay un po
o de \azu
ar sinta
ti
a" que fa
ilita las de
lara
iones. En a~nadidura, hay una
de
lara
ion de un iterador, la
ual permite re
orrer una lista de
olisiones.
El resto de los atributos se denen
omo sigue:
hMiembros dato de GenLhashTable 472bi+
(470a) 472
size_t M; // Tama~
no de la tabla
size_t N; // N
umero de elementos que tiene la tabla
size_t busy_slots_counter; // Cantidad de entradas del arreglo ocupadas
bool remove_all_buckets; // Indica si se deben liberar las cubetas
// cuando se llame al destructor
Uses busy slots counter.
El atributo busy slots counter
uenta la
antidad de entradas del arreglo tableque
estan va
as. Es una informa
ion muy importante para
ono
er el nivel de dispersion de la
fun
ion hash. Si N < M4, enton
es el radio
nos da idea de la efe
tividad
N
de la fun
ion hash en terminos de dispersion. Un valor peque~no indi
a que hay mu
has
olisiones.
El usuario de LhashTable<Key> tiene la responsabilidad de apartar la memoria para
las
ubetas, as
omo, eventualmente, la de liberarlas. Dis
utimos la razon de ser de esto
busy slots
ounter
473a
473
uando hablamos del prin
ipio n-a-n (x 1.4.2 (pagina 22)) y lo hemos apli
ado a los largo
de los distintos TAD que hemos dise~nado. En ese sentido, es bastante util tener una rutina
equivalente a remove all and delete() de Dlink (x 2.4.7 (pagina 105)) o destroyRec()
de los arboles binarios (x 4.4.10 (pagina 306)). As pues, el atributo remove all buckets
le indi
a al destru
tor de LhashTable<Key> que debe liberar toda la memoria.
Si se desea va
iar la tabla, enton
es puede invo
arse al metodo empty():
hM
etodos publi
os de GenLhashTable 473ai
(470a) 473b
void empty()
{
for (int i = 0; i < M; ++i)
for (BucketItor itor(table[i]); itor.has_current(); /* nothing */)
delete static_cast<Bucket*>(itor.del());
busy_slots_counter = 0;
N
= 0;
}
Uses BucketItor, busy slots counter, and has current 103.
Inserci
on
473b
Un asunto de interes es el lugar de la lista en que debe insertarse una nueva
olision: al
prin
ipio, al nal u ordenar la lista. Cualquiera de estas variantes no
ausa gran reper
usion
en el desempe~no, pero si es simplemente enlazada, enton
es la preferen
ia es por el frente,
de modo que la inser
ion sea lo mas \
onstantemente" rapida. Si la lista es doble, enton
es
que se inserte al prin
ipio o nal es indiferente en tiempo.
Otro fa
tor de interes es
onsiderar o no repiten
ia de
laves. A diferen
ia de los
arboles binarios de busqueda, en una tabla hash es preferible no veri
ar repiten
ia porque
sino se requiere una busqueda en la lista de
olisiones
ada vez que se efe
tue una inser
ion.
Ahora podemos espe
i
ar la inser
ion en LhashTable<Key>, la
ual se realiza, muy
sen
illamente, as:
hM
etodos publi
os de GenLhashTable 473ai+
(470a) 473a 474a
Bucket * insert(Bucket * bucket)
{
const size_t i = (*hash_fct)(bucket->get_key()) % M;
if (table[i].is_empty()) // est
a vac
a la lista table[i]?
++busy_slots_counter; // s
==> actualizar contador de ocupaci
on
table[i].append(bucket); // insertar cubeta al final
++N;
return bucket;
}
Uses busy slots counter.
Si la llamada a (*hash fct)(key) es O(1), enton es insert() es, on ertitud determinista, O(1).
474
474a
B
usqueda La b
usqueda
onsiste en
al
ular la entrada de la tabla mediante la fun
ion
hash y luego en re
orrer la i-esima lista hasta que se en
uentre k o hasta que se llegue al
nal, en
uyo
aso se trata de una busqueda fallida:
hM
etodos publi
os de GenLhashTable 473ai+
(470a) 473b 474b
Bucket * search(const Key & key) const
{
const size_t i = (*hash_fct)(key) % M;
if (table[i].is_empty())
return NULL;
for (BucketItor it(table[i]); it.has_current(); it.next())
{
Bucket * bucket = static_cast<Bucket*>(it.get_current());
if (bucket->get_key() == key)
return bucket;
}
return NULL;
}
Uses BucketItor, get current 103, and has current 103.
La elimina
ion es dire
ta una vez que se
ono
e la dire
ion de la
ubeta:
hM
etodos publi
os de GenLhashTable 473ai+
(470a) 474a 500
Bucket * remove(Bucket * bucket)
{
Bucket * next = // guarda pr
oxima colisi
on
static_cast<Bucket*>(bucket->get_next());
5.1.3.2
An
alisis del encadenamiento separado
Por simpli
idad
rti
a, asumiremos una tabla
onsistente de un arreglo de nodos
abe
era
a listas de
olisiones
ir
ulares y doblemente enlazadas del tipo Dnode<T> estudiado
en x 2.4.8 (pagina 106). Bajo el mismo espritu, tambien asumiremos que la inser
ion
siempre se realiza al nal de la lista.
475
El desempe~no de la inser
ion es dire
tamente O(1) mediante la opera
ion append()
en la
abe
era que arroje la fun
ion hash.
El desempe~no de la elimina
ion depende del desempe~no de la busqueda. A su vez, el
desempe~no de la busqueda, exitosa o fallida, depende de la longitud de la lista. Por tanto,
el desempe~no esperado estara dominado por la longitud esperada de la lista.
En lo que sigue de nuestro analisis, M denota la longitud del arreglo interno y N el
numero de elementos que
ontiene la tabla. = N/M indi
a el fa
tor de
arga de la tabla;
es de
ir, que tan llena esta la tabla. Con esta terminologa de base, podemos introdu
ir
nuestro lema fundamental:
Lema 5.1 Longitud esperada de una lista de colisiones
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash de longitud M
on resolu
ion de
olisiones por en
adenamiento separado y que
ontiene N elementos. Sea |li| el numero de
nodos de la i-esima lista en T [i]. Enton
es, la longitud esperada de li es:
li =
N
=
M
(5.7)
Demostraci
on
1
Puesto que h(k) : K [0, M 1] se distribuye uniformemente, P(h(k) = i) = M
para
una
lave
ualquiera k K.
Sea P(k li) la probabilidad de que la
lave sea albergada en la i-esima lista
orrespondiente a la entrada T [i]. Claramente, segun la observa
ion del parrafo anterior,
1
P(k li) = P(h(k) = i) = M
. As pues, tenemos N
laves uniformemente repartidas en M
1
listas. Cada lista li se
orresponde
on una distribu
ion binomial
on parametro p = M
y
1
N
antidad de ensayos. Por tanto, la media es N M
De este lema se derivan los dos siguientes que
ontabilizan la
antidad de
ubetas que
se visitan en una busqueda fallida y exitosa, respe
tivamente.
Lema 5.2 Cantidad de cubetas inspeccionadas en una b
usqueda fallida sobre
una tabla hash con resoluci
on de colisiones por encadenamiento separado
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash de longitud M
on resolu
ion de
olisiones por en
adenamiento separado y que
ontiene N elementos. Enton
es, el numero
N
M
(5.8)
Demostraci
on
Sea k una
lave que no esta dentro de la tabla. El primer paso de la busqueda es
obtener li = T [h(k)]. De este modo, el numero de
ubetas a visitar se
orresponde
on el
numero de nodos de li, pues es ne
esario re
orrer enteramente a li para estar seguros de
que k no se en
uentra en la tabla. Por el lema 5.1, sabemos que li =
Lema 5.3 Cantidad de cubetas inspeccionadas en una b
usqueda exitosa sobre
una tabla hash con resoluci
on de colisiones por encadenamiento separado
476
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla de longitud M
on resolu
ion de
olisiones
por en
adenamiento separado y que
ontiene N elementos. Sea k K una
lave
ualquiera.
Enton
es, el numero esperado de nodos que se visitan en una busqueda exitosa es:
SN = 1 +
2
2M
(5.9)
Demostraci
on
Sea K = {k0, k1, . . . , kN1}, el
onjunto de
laves presentes en la tabla, donde i representa el orden de inser
ion; es de
ir k0 es la primera
lave insertada, mientras que kN1
es la ultima.
A efe
tos de la simpli
idad, asumiremos que no se permiten
laves dupli
adas, lo que
requiere una busqueda se
uen
ial sobre una lista
uando se realiza una inser
ion.
Sea pi la probabilidad de que se busque la
lave presente ki.
Sea lki el numero de elementos que pre
eden a ki en su lista de
olisiones. Enton
es
E(lki ) =
N1
X
pilki .
i=0
Asumiendo que
ualquier
lave tiene igual probabilidad de ser bus
ada, enton
es pi = 1/N.
Despues de insertar la
lave ki, hay i elementos en la tabla, lo que, segun el lema 5.1, ha
e
que la longitud de la lista donde reside ki sea lki = i/M. O sea que para en
ontrar ki
i
hay que re
orrer M
ubetas prede
esoras, pues ki se inserto al nal de la lista y no se
i
permiten dupli
ados. Esto
ontabiliza un total de M
+ 1, pues hay que a
eder a la
ubeta
que
ontiene ki.
De este modo, el numero de esperado de
ubetas a examinar sera
E(lki ) =
=
i
+1
pi
M
i=0
N1
1 X i
+1
N
M
N1
X
i=0
=
=
=
1
N
!
N1
N1
X
1 X
1
i+
M
i=0
N1
+1
2M
+1
2
2M
i=0
Con estos dos re
ientes lemas, estamos listos para enun
iar y demostrar la proposi
ion
siguiente:
Proposici
on 5.1 Desempe
no esperado de una tabla hash con resoluci
on de colisiones por encadenamiento separado
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla de longitud M
on resolu
ion de
olisiones por en
adenamiento separado y que
ontiene un maximo a
otado de N elementos.
Enton
es, el desempe~no esperado de las opera
iones de inser
ion, busqueda y elimina
ion
es O() = O(1).
477
Demostraci
on
El punto
ru
ial de la demostra
ion es aprehender que tanto M
omo N son valores
onstantemente a
otados. Por a
otado entendemos que, si bien N puede ser mayor que M,
estos valores estan a
otados maximos
ono
idos, razon por la
ual tambien esta a
otado
a un valor maximo
onstante. A
larado esto, podemos separar
ada una de las opera
iones:
Inserci
on : En este
aso se realiza una b
usqueda fallida, la
ual, segun el lema 5.2, espera
inspe
ionar = O(1)
ubetas.
B
usqueda :
(5.10)
= O(1)
2
2M
(5.11)
Eliminaci
on : En este
aso se realiza una b
usqueda exitosa, la
ual en el item anterior
se demostro a ser O(1).
N
1
1
=
M
M
M
(5.12)
Cuanto mayor sea M, mas aproximada es la varianza al valor = li. Mientras mayor sea
la
antidad de elementos, mayor tiende a ser la varianza. Sin embargo, esta observa
ion
desde~na el radio y, justamente, el proposito de la ultima expresion es re
ejar el he
ho
de que, a mayor radio, o sea, a mayor plenitud, mayor es la varianza.
5.1.3.3
Encadenamiento cerrado
478
Cuando o
urre una
olision, se \sondea" linealmente la tabla en busqueda de una
ubeta
va
a. La tabla ejemplo de la sub-se
ion anterior,
on las mismas listas de
olisiones, se
vislumbra as:
6
k1
k10
k8
k11
4
5
k2
k5
k9
k3
9
10
k4
11
k7
M2
M 1 k6
Notemos la
olision {k6, k11}, la
ual requirio
ontinuar el sondeo por el prin
ipio de la tabla
hasta al
anzar la
uarta entrada de la tabla; es de
ir, el sondeo lineal debe ser
ir
ular.
El sondeo requiere una forma de distinguir si una
ubeta esta o no va
a. La manera
generi
a tradi
ional es usar un bit
on valor BUSY para indi
ar o
upa
ion, o, EMPTY para
indi
ar disponibilidad.
B
usqueda
Eliminaci
on
La elimina
ion, que requiere la busqueda, se remite a mar
ar la entrada que
ontiene
el elemento a eliminar
omo Empty y, en
aso de que despues hayan
olisiones, se debe
\
orto-
ir
uitar" el enla
e del elemento prede
esor para que apunte al elemento su
esor.
Un
aso espe
ialmente parti
ular o
urre
uando se elimina el primer elemento dentro de una lista de
olisiones. En esta situa
ion, el \
orto-
ir
uito" no se puede realizar
dire
tamente, pues no se tiene una
olision que pre
eda al elemento eliminado. Para resolver esto, el segundo elemento en la lista se mueve ha
ia la posi
ion de elimina
ion y, a
partir de esta posi
ion, se realiza el \
orto-
ir
uito" ha
ia el ter
er elemento
olindante.
Para un hipoteti
o
onjunto de
olisiones {ki, kj, kk, kl}, la situa
ion se puede interpretar
pi
tori
amente as:
6 En
479
ki
kj
kj
kk
kk
kl
kl
Puesto que
on esta te
ni
a los elementos pueden moverse de su posi
ion original de inser
ion, este esquema de resolu
ion de
olisiones no permite mantener punteros a elementos
del
onjunto alma
enado en la tabla; situa
ion indeseable en algunos
asos.
Inserci
on
La inser ion es la opera ion mas ompleja en este esquema y se realiza omo sigue:
1. i = h(k) mod M
2. Si A[i].busy == EMPTY =
(a) A[i].key = k
(b) A[i].busy = BUSY
(
) Terminar.
3. De lo
ontrario =
(a) Re
orrer la lista enlazada hasta el ultimo elemento; sea k su posi
ion en la tabla.
(b) A partir de k bus
ar linealmente en la tabla la primera entrada
on valor EMPTY;
sea i el ndi
e de esta entrada.
(
) A[i].key = k
(d) A[i].busy = BUSY
(e) A[j].next = A[i]
5.1.3.4
An
alisis informal del encadenamiento cerrado
Debe sernos fa
il asumir que si M , enton
es el analisis es aproximado al en
adenamiento separado. Esta suposi
ion que, algo irrealista, nos permite aprehender que, para
M N, seg
un los lemas (5.2) y (5.3), el analisis puede aproximarse a:
1
+1
2
2M
UN
var
M
SN
(5.13)
(5.14)
(5.15)
480
En la sub-se
ion x 5.1.4.3 (pagina 488) presentaremos un analisis mas formal que es
apli
able a esta estrategia.
Mientras menor sea la
arga , mas pre
isa y realista es la aproxima
ion. >Hasta
que valor de se
umple la aproxima
ion? El analisis de otra te
ni
a, expli
ado en
x 5.1.4.5 (pagina 492), nos permite vislumbrar que la aproxima
ion es buena hasta un
fa
tor de
arga = 0, 7; es de
ir, un por
entaje de o
upa
ion del 70%.
Lo anterior nos revela que
uando se desea instrumentar el problema fundamental
on una tabla hash y desempe~no esperado O(1), enton
es debemos estimar la
antidad
esperada de elementos que vamos a guardar y, en fun
ion de la estima
ion, es
oger M
de modo que nos satisfaga el valor de para el
ual los resultados son buenos, mas un
margen o fa
tor de error. Si, por ejemplo, esperamos 1000 elementos, enton
es es
ogemos
M = 1465, lo
ual
ubre el 70% men
ionado, mas un 5%
omo fa
tor de error ingenierl .
Una mejora para este enfoque
onsiste en emplear un area
ontigua de la tabla ex
lusivamente. En este
aso la tabla tiene un tama~no de M + C entradas. Cuando o
urre una
olision, enton
es el sondeo lineal se realiza entre el rango [M..C). Resultados empri
os
reportados por [23 indi
ian que un valor C 1.14M es su
iente para tener
adenas de
olisiones dos elementos
omo tama~no esperado. Esto tiene sentido a la luz de la paradoja
del
umplea~nos, la
ual patenta lo bastante probable que es una
olision de dos elementos; pero un analisis para
olisiones de tres o mas elementos nos indi
ia mu
ha menos
probabilidad.
A este estadio de tratamiento de las tablas hash, probablemente algun le
tor haya observado,
omo desventaja de este enfoque, la limita
ion que se impone sobre el maximo
numero de elementos que podemos guardar en una tabla hash
on resolu
ion de
olisiones
por en
adenamiento
errado. Bajo la misma lnea de pensamiento, tambien se pudiera
observar,
omo ventaja del en
adenamiento separado, que este permite guardar mas elementos que el valor de M. >
ual es, enton
es, el sentido de esta te
ni
a?
El asombro y la sorpresa ha embargado a mu
hos estudiantes, in
lusive, a algunos
estudiosos,
uando se realiza que,
onsiderando
orre
tamente los fa
tores involu
rados,
en parti
ular, el valor esperado de N, los enfoques que resuelven
olisiones dentro de
la misma tabla tienden a ser de mejor desempe~no que el en
adenamiento separado. La
aprension de esta realidad se fa
ilita mediante las siguientes observa
iones:
7
1. En el en
adenamiento separado las
ubetas se apartan y liberan a traves del manejador de memoria. El manejo de memoria dinami
a, tanto para reservarla
omo para
liberarla, no solo toma un tiempo de mas respe
to a un enfoque de resolu
ion de
olisiones que las
oloque dentro de la misma tabla, sino que su desempe~no es variable
segun la
arga del manejador,
arga que, a su vez, depende de
ondi
iones tales
omo
fre
uen
ia de reserva
ion y liberado y la
antidad a
tual de bloques reservados.
Para el en
adenamiento separado, el apartado afe
ta a la opera
ion de inser
ion,
mientras que la libera
ion a la elimina
ion. Por el
ontrario,
on el en
adenamiento
errado y demas estrategias que
olo
an las
olisiones dentro de la misma tabla, no
se usa el manejador de memoria.
Alguien pudiera sugerir tener pre-apartado un
onjunto de
ubetas, pero, >no es esto
7 En
ingeniera, suele usarse el termino \fa
tor de seguridad" para expresar un sobre-
al
ulo en
onsidera
ion a la in
ertidumbre del modelo, errores de
al
ulo en sus diversas fases y fa
etas, y errores en las
argas esperadas.
481
Direccionamiento abierto
Como ya lo indi
amos, en el dire
ionamiento abierto toda
olision se resuelve dentro de
la tabla; sin apuntador ha
ia la
olision.
Fundamentalmente, las
eldas
ontienen registros
on el valor de
lave y una mar
a,
denominada \status",
on tres posibles valores:
EMPTY la
elda esta disponible para alma
enar una
lave.
BUSY la
elda esta o
upada por una
lave.
DELETED la
elda
ontiene una entrada que en alg
un momento
ontuvo una
lave y
que luego fue borrada. Mas adelante veremos por que es ne esaria esta mar a.
Una
ubeta tabla[i] puede estar o
upada
omo resultado dire
to de la fun
ion hash
o porque es una
olision y fue es
ogida por alguna te
ni
a de sondeo. Cualquiera de los dos
asos se distingue mediante
omproba
ion del predi
ado h(tabla[i]) == i. Si el predi
ado
es
ierto, enton
es la
lave no fue una
olision.
5.1.4.1
Puesto que las
olisiones deben estar dentro de la propia tabla, la estrategia se remite
en
ontrar una
ubeta
uyo valor sea distinto a EMPTY. Puede de
irse que la estrategia de
resolu
ion de
olisiones
on dispersion
errada (dire
ionamiento abierto) es una estrategia
de sondeo.
>Cual es, enton
es, la estrategia de sondeo ideal? Se \presume" que es aquella que
inspe
iona aleatoriamente las
ubetas; es de
ir,
uando o
urre una
olision, la proxima
ubeta a sondear en donde
olo
ar la
olision debe sele
ionarse aleatoriamente en el
intervalo [0, M1]. Tpi
amente, esta suposi
ion es
ono
ida
omo \hashing o dispersion
no de
ualquier
universal" y se le di
e ideal porque idealiza lo que sera el mejor desempe~
estrategia
errada de manejo de
olisiones.
Lema 5.4 (Peterson 1957 [27]) Cantidad de cubetas inspeccionadas en una
b
usqueda fallida sobre una tabla hash con resoluci
on de colisiones por direccionamiento y sondeo ideal
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash de longitud M
on resolu
ion de
482
olisiones por dire
ionamiento abierto y que
ontiene N elementos. Sea k K una
lave
ualquiera. Enton
es, el promedio de
ubetas que se visitan en una busqueda fallida es:
UN =
M+1
MN+1
(5.16)
Demostraci
on
i
N
(5.17)
X
M
M
X
Mi
Mi
=
(Por simetra nk = n n k )
i
=
i
Ni+1
MN1
i=1
i=1
M1
M2
1
0
=
+2
+ + (M 1)
+M
MN1
MN1
MN1
MN1
M1
M1
M1
X
X
X
i
i
i
=
(M i)
=M
i
MN1
MN1
MN1
i=0
i=0
i=0
Ahora, a efe
tos de apli
ar la propiedad de la sumatoria del ndi
e superior de los
oe
ientes binomiales , vamos a expresar el fa
tor i
omo i + 1 1:
9
M1
X
i
i
(i + 1 1)
= M
MN1
MN1
i=0
i=0
M1
M1
X
X
i
i
(i + 1)
= (M 1)
MN1
MN1
i=0
|i=0
{z
}
{z
}
|
M1
X
(A)
8 Re
u
erdese que
9 La propiedad en
(5.18)
(B)
`n
k
n
nk
n
X
k=0
k
m
n+1
m+1
Una ex
elente referen
ia sobre opera
iones
on
oe
ientes binomiales lo
onstituye el texto \Con
rete
Mathemati
s" [14.
483
(A) es
al
ulable dire
tamente por la propiedad de la sumatoria del ndi
e superior:
(A) = (M 1)
M
MN
10
M
(por simetra)
= (m 1)
N
(5.19)
M1
i
MN X
(i + 1)
(B) =
MN1
MN
i=0
M1
X
i+1
M+1
= (M N)
= (M N)
MN
MN+1
i=0
M+1
(por simetra)
= (M N)
N
(5.20)
UN
lim
M,N
MN+1
= (1 )1
M+1
(5.21)
(5.22)
N
, lo que en la realidad puede ser pre
iso, pues suele usarse
El resultado aproxima M+1
una
ubeta mas que el valor previsto de M que funja de
entinela para detener el sondeo
en todos los
asos.
Intuitivamente, la probabilidad de que la inser
ion o
urra en el primer sondeo (el
dire
to resultante de la fun
ion hash) es . Del mismo modo, la probabilidad de requerirse
dos sondeos, es 2. En n, generalmente, la probabilidad de requerirse k sondeos ideales
es k.
La
antidad de sondeos que se realizan en una busqueda exitosa esta expresada por el
lema siguiente:
n
m
n n1
=
m m1
484
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash de longitud M
on resolu
ion de
olisiones por dire
ionamiento abierto y que
ontiene N elementos. Sea k K una
lave
ualquiera. Enton
es, el promedio de
ubetas que se visitan en una busqueda exitosa es:
SN =
M+1
HM+1 HMN+1
N
(5.23)
Demostraci
on
Sea Si el promedio de
ubetas que se sondean en una busqueda exitosa sobre una tabla
on i elementos.
El fundamento de la demostra
ion es expresar una busqueda sobre una tabla que
ontiene i elementos en fun
ion de la busqueda fallida que hubo de realizarse para insertar
el i-esimo elemento; o sea Si = Ui1.
El promedio SN puede expresarse, en terminos de (5.16), de la siguiente manera:
Si =
=
=
N1
1 X
Ui
N
1
N
i=0
N1
X
i=0
N1
M+1 X
M+1
1
=
Mi+1
N
Mi+1
i=0
M+1
HM+1 HMN+1
N
lim
M,N
Si =
=
=
lim
M,N
lim
M,N
M+1
ln (M + 1) ln (M N + 1)
N
1
M+1
ln
MN+1
1
1
ln
1
(5.24)
Como ya lo hemos indi
ado, el sondeo ideal es lo que se presume arrojara el mejor
desempe~no. En la pra
ti
a es irreal porque, aparte de que es demasiado dif
il generar
sondeos aleatorios e independientes para
ualquier
onjunto de
laves, es posible, aunque
muy improbable en la medida en que M sea mas grande, que el sondeo ideal no en
uentre
una
ubeta disponible. El sondeo ideal y las
otas expresadas por (5.22) y (5.24) nos
propor
ionan una referen
ia de desempe~no para otros enfoques de sondeo que estudiaremos
inmediatamente.
5.1.4.2
Sondeo lineal
El sondeo lineal es exa
to al del en
adenamiento
errado: si o
urre una
olision, enton
es
la proxima
elda a sondear sera la siguiente disponible,
uya mar
a sea distinta de EMPTY,
resultante de la inspe
ion se
uen
ial a partir del ndi
e dado por la fun
ion hash. La tabla
ejemplo que hemos mostrado para las dos estrategias anteriores se pi
toriza as:
11 H
ln n + O(1).
485
k1
k10
k8
B
E
k11
k2
k5
k9
k3
10
k4
11
k7
485
M2
M 1 k6
Notemos que la distribu
ion de las
laves es identi
a a la del en
adenamiento separado,
pues son resultantes del mismo tipo de sondeo.
ALEPH tiene un TAD, denominado OLHashTable<Key,Record>, que implanta una
tabla hash
on resolu
ion de
olisiones por dire
ionamiento abierto y sondeo lineal. El
TAD en
uestion se exporta en el ar
hivo <<tpl olhash.H:
htpl olhash.H 485i
namespace Aleph {
template <typename Key, typename Record>
class OLhashTable
{
public:
typedef size_t (*HashFctType)(const Key &);
hMiembros
};
}
Denes:
486
B
usqueda
486a
(485) 486b
Observemos que el lazo se detiene
uando se sondea una
elda
uyo status sea EMPTY.
Una
elda
on status igual a BUSY se revisa para veri
ar si
ontiene la
lave de busqueda.
Una
elda
on status DELETED no
ontiene
lave, pero
omo esta en el
amino de una
olision, hay que
ontinuar el sondeo sobre la siguiente
ubeta.
Inserci
on
486b
Esen
ialmente, la inser
ion se remite a en
ontrar la primera
ubeta, sea resultante
dire
ta de la fun
ion hash o del sondeo lineal,
on status distinto a BUSY. Esto se plantea
mediante el siguiente metodo:
hMiembros p
ubli
os de OLHashTable<Key,Record> 486ai+
(485) 486a 487
Record * insert(const Key & key, const Record & record)
throw(std::exception, std::overflow_error)
{
if (N >= M)
throw std::overflow_error("Hash table is full");
int i = (*hash_fct)(key) % M;
// Sondear linealmente las cubetas disponibles
while (table[i].status != BUSY)
i = (i + 1) % M;
// i contiene una celda con status DELETED o EMPTY
table[i].key
= key;
table[i].record = record;
table[i].status = BUSY;
N++;
return &table[i].record;
}
Eliminaci
on
487
487
La entropa
ausada por la a
umula
ion de
ubetas
on status DELETED puede
ontrarrestarse mediante una te
ni
a que
ierre la bre
ha dejada por la
ubeta eliminada dentro
de la
adena de
olisiones. De este modo, la ultima
ubeta de la
adena puede mar
arse
on
el valor EMPTY. Puesto que la elimina
ion requiere una busqueda, la interfaz para eliminar
se plantea en fun
ion de un puntero al registro que se desea eliminar; de este modo, el
usuario puede, eventualmente, ahorrar la repeti
ion del trabajo de busqueda. Di
ho esto,
no debe resultar dif
il
omprender la rutina de elimina
ion:
hMiembros p
ubli
os de OLHashTable<Key,Record> 486ai+
(485) 486b
void remove(Record * record)
{
Bucket * bucket = record_to_bucket(record);
488
Esta rutina
ierra la bre
ha y mar
a la ultima
ubeta de la
adena de
olisiones
omo
EMPTY.
Aunque esta \mejora" mejora el pro
eso de busqueda, esta ralentiza el de elimina
ion.
Si las elimina
iones son ex
ep
ionales, enton
es es preferible utilizar el muy simple algoritmo de mar
ar la
ubeta
on DELETED y de
rementar N.
La mejora plantea el mismo problema de movimiento de
ubetas que tiene el en
adenamiento
errado. Conse
uentemente, tampo
o se pueden mantener punteros a elementos
de la tabla.
5.1.4.3
An
alisis informal del sondeo lineal
El analisis que presentaremos en esta sub-se
ion esta basado en los resultados presentados
por Sedgewi
k y Flajolet [28 y se fundamentan en la siguiente proposi
ion,
ual fue el
primer algoritmo que Knuth analizo exitosamente:
Proposici
on 5.2 (Knuth 1962 [19]) Cantidad de cubetas accedidas en direccionamiento abierto y sondeo lineal
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash de longitud M
on resolu
ion de
olisiones por dire
ionamiento abierto y sondeo lineal. Sea k K una
lave
ualquiera.
Enton es, el promedio de ubetas que se visitan en una busqueda fallida es:
N1
1 1X
(N 1)!
1
1
1
UN = +
=
1+
+O
;
i
2 2
M (N i 1)!
2
1
N
(5.25)
N1
1 1X
1
1
N!
1
SN = +
=
i i
+O
1+
.
2
2 2
M (N i)!
2
(1 )
N
(5.26)
i=0
i=0
Demostraci
on
489
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla de longitud M
on resolu
ion de
olisiones
por dire
ionamiento abierto y sondeo lineal. Enton
es, el desempe~no esperado de las
opera
iones de inser
ion, busqueda y elimina
ion es O() = O(1).
Demostraci
on
M esta a
otado a un valor
onstante. Puesto que la tabla es
errada, N <= M, lo que
impli
a que es
onstante. En virtud de este he
ho, las expresiones (5.25) y (5.26) son
onstantes.
La inser
ion esta determinada por la busqueda fallida y, puesto que (5.25) es
onstante,
el desempe~no esperado tambien es
onstante. Ergo, la inser
ion es O(1).
La busqueda fallida en
aja en la misma situa
ion que la inser
ion. La busqueda exitosa
depende de (5.26), la
ual tambien es
onstante.
La elimina
ion
on el TAD OLHashTable<Key,Record> es determinsti
amente
onstante. Cualquier otro TAD basado en el sondeo lineal que requiera una busqueda en
aja
en los
asos anteriores
Ahora que hemos mostrado la proposi
ion que nos
ara
teriza el desempe~no del sondeo lineal, meditemos a
er
a de lo que ella nos revela. La guras 5.1 y 5.2 ilustran los desempe~nos para del en
adenamiento separado, sondeo lineal y sondeo ideal para la busqueda
fallida y exitosa, respe
tivamente.
El sondeo lineal se degrada para la busqueda exitosa
uando la tabla se en
uentra en un
80% de plenitud; a partir de all, la pendiente deviene importante y la
antidad de
ubetas
que se inspe
ionan aumenta notablemente. Antes del 80% la
antidad de inspe
iones es
en promedio menor que tres, lo
ual es a
eptabilsimo habida
uenta del
a
he.
En
uanto a la busqueda fallida, el asunto empeora antes para el sondeo lineal. A partir
del 60% se inspe
ionan en promedio
uatro
ubetas en una busqueda fallida, lo que es
equivalente a de
ir que se sondean
uatro
ubetas en una inser
ion. Tenemos, pues, un
margen entre el 60% y el 80% en el
ual el sondeo lineal es en promedio a
eptable. >Cual es
el por
entaje de plenitud en que podemos trabajar
on el sondeo lineal de manera segura?
La pregunta anterior requiere
uestionar >
ual, entre las dos
urvas, es la mas representativa? o quiza mejor, di
ho, >
ual entre las dos busquedas es la mas importante?
Por lo general la busqueda o
onsulta de lo que ya esta en un
onjunto es mas fre
uente
que la busqueda fallida. En este sentido, la gra
a 5.2 es la mas importante. Bajo este
riterio y el
ono
imiento empri
o, podemos de
ir que el sondeo lineal es abordable hasta
un maximo entre el 70 y 80% de plenitud. >Que tanto
uesta el 30-20% de deso
upa
ion?
Una perspe
tiva interesante para abordar esta pregunta nos la propor
iona
omparar el
espa
io o
upado por el en
adenamiento separado.
490
5
Sondeo ideal 3
4.5En
adenamiento separado +
Sondeo lineal
4
+
3
+
3
+
3
+
3
+
3
+
3
3
+
3
+
3
3.5
+
3
+
+
33
3
+
3
3
+
3
3
++
3
3
+
3
++
2.5
33
3
3
++ 3
3
+
+ 3
3
33
+++ 3
3
+
3
2
+
3
3
++ 3
3
+++3
333
3
3
++3
+
3
+
3
+
3
+3
3
+3
3
1.5
+3
3
+3
+3
+3
+3
+3
+3
+3
+
+
3
+
3
+
3
+
3
3
+
3
+
3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
13
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
+
+
+
+
+
3
+
3.5
3
+
+
3
+
3
3
+
3
+
3
3
++
3
+
3
2.5
+
3
3
++
3
33
+++
3
+
3
3
+
3
2
3
++
3
33
3
3
++++ 3
3
+
3
+
3
3
+
+
3
+ 3
3
3
3
3
+++3
3
++3
+3
333
+3
3
1.5
+3
+3
33
+3
3
+3
+
3
+
+
+
+
+
3
+
3
+
3
+3
3
3
+3
3
+3
3
+3
3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
+3
1 +3
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
12 Estos
491
La elimina
i
on en el sondeo lineal
onlleva eventuales in
onvenientes. El algoritmo de
elimina
ion que desarrollamos tiene la desventaja de mover (por
opia) los
ontenidos
de las
ubetas. No podemos, pues, mantener punteros a los elementos de una tabla
on sondeo lineal.
Si apelamos a la elimina
ion mas simple, es de
ir, solo mar
ar la
ubeta
on
DELETED, enton
es no podemos permitir mu
has elimina
iones, pues estas degradan
la busqueda.
>Es posible superar el sondeo lineal? La
uestion equivale a en
ontrar una forma de
evitar el agrupamiento primario. Abordajes a esta pregunta en las sub-se
iones subsiguientes.
5.1.4.4
Sondeo cuadr
atico
492
es evitar el agrupamiento primario y la forma
ion de
adenas lineales de
olisiones que son
las que degradan el desempe~no en el sondeo lineal.
Pero esta estrategia
onlleva el problema de que es dif
il garantizar que todas las
ubetas sean sondeadas, pues i2 mod M no ne
esariamente
ubre todo el rango [0, M).
Cuando la tabla al
anza el 50% de plenitud no hay garanta absoluta de que el sondeo
uadrati
o en
uentre una
ubeta disponible.
Weiss [30 demuestra que es posible en
ontrar tal
ubeta si M es primo. En a~nadidura,
on M primo y M = 4k + 3 el sondeo
uadrati
o inspe
iona toda la tabla.
El sondeo
uadrati
o impide la forma
ion de
adenas lineales de
olision, pero no emula
el sondeo ideal porque la opera
ion i2 ni es aleatoria ni simula un
omportamiento aleatorio. Conse
uentemente,
on el sondeo
uadrati
o se presenta el \agrupamiento se
undario"
y la forma
ion de
adenas en torno a las
olisiones i2 mod M. Tiene la desventaja, ademas,
de ser mas
omplejo de implantar y de imponer restri
ion a la sele
ion de M a un numero
primo, algo que no siempre es posible.
5.1.4.5
492
Doble hash
>Es posible instrumentar el sondeo ideal? Consideremos disponer de M distintas fun
iones
hash {h1(k), h2(k), . . . , hM(k)}
on
omportamientos aleatorios e independientes: En esta
situa
ion, podemos sondear idealmente si invo
amos primigeniamente a h1(k) y, si hay
olision, enton
es sondeamos las
ubetas en el orden h2(k), h3(k), . . . , hM(k) hasta en
ontrar una
ubeta disponible. Este es
enario es perfe
tamente fa
tible, pero ya debe sernos
evidente su di
ultad, no solo en
uanto a la es
ala de M, sino en
uanto su generalidad
respe
to al tama~no M, as
omo el tipo de
lave para el
ual se instrumente la familia de
fun
iones hash.
A pesar de las
onsidera
iones anteriores, existe una ex
elente y relativamente barata
manera de emular el sondeo ideal
onsistente en utilizar dos fun
iones hash para los primer
y segundo sondeos, respe
tivamente. Si las dos sondeos aleatorizados
ausan
olision, enton
es usamos sondeo lineal. La idea fundamental es emular el sondeo ideal para la primera
olision. Intuitivamente, si re
ordamos la paradoja del
umplea~nos, esto tiene sentido
porque la probabilidad de triple o mas
olision de
re
e exponen
ialmente (3, 4, . . . y as
su
esivamente).
Eventualmente, es perfe
tamente realizable, aunque en tiempo
onstante mas
ostoso,
un triple y, en general, un m-hash.
En ALEPH, una tabla hash
on resolu
ion de
olisiones por dire
ionamiento abierto
y doble fun
ion hash es exportada por el TAD ODhashTable<Key, Record>, el
ual se
espe
i
a en el ar
hivo htpl odhash.H 492i:
htpl odhash.H 492i
template <typename Key, typename Record>
class ODhashTable
{
public:
typedef size_t (*Hash_Fct)(const Key &);
hMiembros
privados de ODhashTable<Key,
Record> 494ai
493
Denes:
Cuando estudiamos el sondeo lineal, expli
amos el problema que plantea al desempe~no
el he
ho de que las
ubetas deban mar
arse
on el valor DELETED. En aquel enton
es
desarrollamos una te
ni
a reminis
ente a \la
erradura de bre
ha", que apli
amos al
TAD OLHashTable<Key,Record>, que nos permite paulatinamente ir eliminando el status DELETED. Con el doble hash no es posible esta te
ni
a porque pueden existir
laves
tales que (h1(k) = h2(k), lo que puede a
arrear una situa
ion en la que sea imposible
determinar si la
lave
olisiono en el primer o segundo sondeo.
En general este problema es el mismo si usamos tres o mas fun
iones hash o, si llegase
el
aso, sondeo ideal.
En a~nadidura, el metodo de
errar bre
has de
ubetas
on status DELETED que empleamos en la elimina
ion de OLHashTable<Key,Record> mueve los
ontenidos de las
ubetas; movimiento indeseable en algunas situa
iones.
Si usamos doble hash, >
omo, enton
es, tratamos
on la elimina
ion? Hay dos enfoques,
des
ubiertos por primera vez por dos investigadores japoneses [15, que no mueven las
ubetas y que a su vez eliminan la entropa
ausada por las
ubetas DELETED:
1. Se mantiene la
uenta de las
ubetas
on status DELETED. Cada
ierto nivel de
arga,
por ejemplo, un 30% de
ubetas
on valor DELETED, todas las
ubetas
on valor
DELETED se mar
an
on valor EMPTY. Luego, se re
orre enteramente la tabla y se
examina
uales
ubetas
on status BUSY se en
uentran en una
adena de sondeo tal
que se requiera mar
ar
ubetas intermedias
on status DELETED.
Esta te
ni
a tiene el in
onveniente de que
onsume tiempo O(M), lo
ual es bastante notable respe
to al desempe~no esperado de O(1). Ademas, es dif
il posibilitar
eje
u
ion de las demas opera
iones mientras se efe
tua esta \limpieza".
Eventualmente, en menos
abo de tener que mover algunas
ubetas, aquellas
ubetas
olindantes
on status BUSY, que sean produ
to del sondeo lineal, pueden
re-lo
alizarse mediante simple re-inser
ion.
2. La otra te
ni
a, \en lnea" y
on dura
ion
onstante, es mantener un
ontador de sondeos en
ada
ubeta que denominaremos probe counter. Ini
ialmente,
ada
ubeta
tiene status EMPTY y
ontador en
ero. Conforme o
urran inser
iones y las
ubetas
esten en una
adena de sondeo, se in
rementan los
ontadores de las
ubetas
omponentes de la
adena. Analogamente,
uando o
urren elimina
iones, se de
rementan
las
ubetas entre el primer sondeo y la
ubeta eliminada. Cuando el
ontador de una
ubeta
on status DELETED deviene
ero, enton
es, puesto que la
ubeta no rompe
ninguna
adena de sondeo, esta puede mar
arse
on seguridad
omo EMPTY.
Atributos de ODhashTable<Key, Record>
494
494a
En fun
ion de la te
ni
a de elimina
ion que re
ien hemos presentado y sele
ionado,
lo primero que debemos ha
er es denir la estru
tura de la
ubeta:
hMiembros privados de ODhashTable<Key, Record> 494ai
(492) 494b
struct Bucket
{
Key
Record
unsigned
unsigned
//
//
//
//
//
unsigned short probe_counter; //
};
494b
clave
registro
status EMPTY, DELETED o BUSY
sondeo: FIRST_PROBE SECOND_PROBE o
LINEAR_PROBE
contador de sondeos
494
key;
record;
status
: 4;
probe_type : 4;
table;
first_hash_fct;
second_hash_fct;
M;
N;
deleted_entries_counter;
empty_entries_counter;
//
//
//
//
//
//
//
arreglo de cubetas
primera funci
on hash
segunda funci
on hash
tama~
no de la tabla
n
umero de cubetas ocupadas
n
umero de cubetas DELETED
n
umero de cubetas EMPTY
495a
ODhashTable(Hash_Fct
__first_hash_fct,
Hash_Fct
__second_hash_fct,
const size_t & len) throw (std::exception, std::bad_alloc)
: table(new Bucket[len]),
first_hash_fct(__first_hash_fct), second_hash_fct(__second_hash_fct),
M(len), N(0), deleted_entries_counter(0), empty_entries_counter(M)
{ /* empty */ }
Uses ODhashTable 492.
B
usqueda
495a
495
Inserci
on
495b
Para mejorar la
ompresion del algoritmo de inser
ion,
ondi
ion esen
ial para la legibilidad y
orre
titud, en
apsularemos la reserva
ion de la
ubeta en la siguiente rutina:
hMiembros privados de ODhashTable<Key, Record> 494ai+
(492) 494b 497a
Record* allocate_bucket(Bucket &
bucket,
const unsigned char & probe_type,
const Key &
key,
const Record &
record)
{
++N;
if (bucket.status == BUCKET_EMPTY)
--empty_entries_counter;
else
--deleted_entries_counter;
bucket.key
= key;
bucket.record
= record;
bucket.status
= BUCKET_BUSY;
bucket.probe_type = probe_type;
bucket.probe_counter++;
496
return &bucket.record;
}
Denes:
allocate bucket, used in
hunk 496.
496
La rutina re
ibe una
ubeta en la
ual se desea insertar por
opia el par (key,record)
on sondeo de tipo probe type (FIRST PROBE, SECOND PROBE o LINEAR PROBE).
La rutina anterior permite
on
entrar la inser
ion en el asunto interes de este estudio:
la manera de sondear
on dos fun
iones hash y luego lineal si o
urren tres o mas
olisiones. Basi
amente, independientemente de su status, a
ada
ubeta sondeada durante la
inser
ion, debe in
rementarsele su
ontador de
olisiones:
hMiembros p
ubli
os de ODhashTable<Key, Record> 494
i+
495a 498
Record* insert(const Key & key, const Record & record)
throw (std::exception, std::overflow_error)
{
if (N >= M)
throw std::overflow_error("Hash table is full");
// primer sondeo con la primera funci
on hash
int i = (*first_hash_fct)(key) % M;
if (table[i].status != BUCKET_BUSY) // cubeta disponible?
// s
==> m
arquela como primer sondeo y termine
return allocate_bucket(table[i], FIRST_PROBE, key, record);
// por esta cubeta se sondear
a una colisi
on ==> incrementar contador
table[i].probe_counter++;
// segundo sondeo con segunda funci
on hash
i = (*second_hash_fct)(key) % M;
if (table[i].status != BUCKET_BUSY) // cubeta disponible?
// s
==> m
arquela como segundo sondeo y termine
return allocate_bucket(table[i], SECOND_PROBE, key, record);
do // sondear linealmente a partir de i (
ndice de segundo sondeo) e
// ir incrementado contador en cada cubeta sondeada
{
// por esta cubeta se sondear
a una colisi
on ==> incrementar contador
table[i].probe_counter++;
advance_index(i);
}
while (table[i].status == BUCKET_BUSY); // detener cuando se sondee
// una cubeta DELETED o EMPTY
// marcar cubeta como sondeo lineal y terminar
return allocate_bucket(table[i], LINEAR_PROBE, key, record);
}
Uses advance index and allocate bucket 495b.
497
Eliminaci
on
497a
La elimina
ion es la opera
ion mas deli
ada de este TAD. E sta es, en lo que
on
ierne
al sondeo, inversa a la inser
ion: los
ontadores de las
ubetas sondeadas deben de
rementarse. Pero, en a~nadidura, es durante esta opera
ion que se mar
an
omo EMPTY las
ubetas
uyos
ontadores sean
ero.
As pues, a efe
tos de expresar la elimina
ion solo en terminos del sondeo, el de
remento
del
ontador de una
ubeta y su eventual mar
ado
omo EMPTY se realiza mediante la
siguiente rutina:
hMiembros privados de ODhashTable<Key, Record> 494ai+
(492) 495b 497b
void decrease_probe_counter(Bucket * bucket)
{
bucket->probe_counter--;
497b
if (bucket->probe_counter == 0)
{
bucket->status
= BUCKET_EMPTY;
bucket->probe_type = NO_PROBED;
++empty_entries_counter;
}
else
{
bucket->status = BUCKET_DELETED;
++deleted_entries_counter;
}
--N;
}
Denes:
Al igual que en las tablas hash anteriores, la elimina
ion re
ibe un puntero al registro,
lo que ha
e mas simple el algoritmo, pues ahorra
odigo para la busqueda de la
ubeta.
Esto, aunado a las dos rutinas anteriores, nos permitira dise~nar una elimina
ion que solo
se
on
entre en de
rementar los
ontadores entre el primer sondeo y la
ubeta a eliminar.
498
498
De aqu, pues, la utilidad del atributo probe type, pues este nos permite determinar en
que punto de la
adena de sondeo se en
uentra la
ubeta:
hMiembros p
ubli
os de ODhashTable<Key, Record> 494
i+
496 499
void remove(Record * record)
throw(std::exception, std::domain_error, std::invalid_argument)
{
Bucket * bucket = record_to_bucket(record);
if (bucket->probe_type != FIRST_PROBE)
{
const int i_fst_probe = (*first_hash_fct)(bucket->key) % M;
decrease_probe_counter(&table[i_fst_probe]);
if (bucket->probe_type == LINEAR_PROBE)
{ // la cubeta fue apartada durante un sonde lineal ==>
// decrementar primer y segundo sondeo y luego, a partir del
//
ndice del segundo sondeo, decrementar todas la cubetas
// hasta llegar a la que vamos a eliminar
const int i_snd_probe = ((*second_hash_fct)(bucket->key) % M);
decrease_probe_counter(&table[i_snd_probe]);
int i
= i_snd_probe;
const int last_index = bucket_to_index(bucket);
for (advance_index(i); i != last_index; advance_index(i))
decrease_probe_counter(&table[i]);
}
}
deallocate_bucket(bucket);
}
Uses advance index, bucket to index, deallocate bucket 497b, decrease probe counter 497a,
and record to bucket.
5.1.4.6
An
alisis informal del doble hash
Como ya lo hemos reiterado, el doble hash emula el sondeo ideal. Observa
iones
empri
as [19 indi
ian que esta emula
ion es bastante
ertera. As pues, en la pra
ti
a, el
omportamiento del doble hash es equiparable al del sondeo ideal y las
urvas mostradas
en las guras 5.1 (pag. 490) y 5.2 (pag. 490) para el sondeo ideal se
orresponden
on el
doble hash.
Al 90% de plenitud, el doble hash realiza 10 sondeos para una busqueda fallida y, muy
importante, 2,5 sondeos para una busqueda exitosa. Estas
otas, expresadas en fun
ion
de e independientes del valor de M, ha
en que el desempe~no esperado de la busqueda,
exitosa o fallida, sea
onstante. Conse
uentemente, la inser
ion y la busqueda
on el doble
hash son O(1) hasta un 90% de plenitud. Despues de este umbral, el desempe~no puede
degradarse a O(M).
499
499
Reajuste de dimensi
on en una tabla hash
500
=
=
=
=
=
500
La opera
ion es
on
eptual, estru
turalmente muy sen
illa e identi
a para el sondeo
lineal. El
riterio para invo
arla lo determina el que el valor de supere o se aproxime
al umbral en que se predi
e un buen desempe~no. En el
aso del doble hash, pudieramos
invo
ar el reajuste
uando nos aproximemos al 90% de plenitud.
Es plausible reajustar segun una plenitud de desperdi
io de
ubetas; esta estrategia
tiene bastante sentido si las inser
iones
esan y la tabla se usa prin
ipalmente para la
busqueda. En este
aso, pudieramos reajustar la tabla a un M inferior de manera que
disminuyamos el desperdi
io en
ubetas y respetemos el umbral de desempe~no.
Hay dos
uestionamientos a esta opera
ion. El primero de ellos es su
oste O(M) +
O(M ); dura
ion en la
ual sera prohibitivo eje
utar
ualquier otra opera
ion. Este eventual problema se mitiga, mas no se evita del todo,
ontabilizando algun tiempo de o
io;
es de
ir, una vez que devenga
er
ana al umbral, enton
es esperamos por un tiempo prudente a ver si no o
urren opera
iones; si expira el tiempo de espera, enton
es pro
edemos
a reajustar bajo la esperanza de que no interrumpamos las otras opera
iones.
El segundo
uestionamiento, valido solo para las tablas
erradas, es que se mueven los
ontenidos de las
ubetas. Con el en
adenamiento separado, no tenemos este problema.
El reajuste en el en
adenamiento separado tiene dos ventajas. La primera, ya men
ionada, es la posibilidad de N > M. La segunda es que es simple tomar previsones para
permitir la inser
ion durante el pro
eso de reajuste.
hM
etodos publi
os de GenLhashTable 473ai+
(470a) 474b
size_t resize(const size_t & new_size) throw(std::exception, std::bad_alloc)
{
if (new_size == M)
return M;
BucketList * new_table = new BucketList [new_size];
BucketList * old_table = table; // guardar estado de la tabla actual
const size_t old_size = M;
// reiniciar la tabla como vac
a al arreglo new_table
table
M
busy_slots_counter
N
=
=
=
=
501
new_table;
new_size;
0;
0;
5.1.6
501
Manejo din
amico de cubetas (TAD DynLhashTable<Key, Record> )
>Cual es la manera mas general de exportar un TAD basado en una tabla hash?
Se tratara de un mapeo entre
laves y registros,
uya interfaz y uso son bastante similares a los mapeos que realizamos
on arboles binarios de busqueda (tipo
DynMapTree<Tree, Key, Range, Compare>- x 4.10 (pagina 404)).
Los tipos de tablas hash OLHashTable<Key,Record> y ODhashTable<Key, Record>
satisfa
en este requerimiento;
on la salvedad de que estos limitan la
antidad de
laves
al valor de M sele
ionado en
onstru
ion. El TAD LhashTable<Key> no tiene este
problema, pero este ni maneja registros dire
tamente ni trata
on la memoria dinami
a.
Hay una alternativa que, en detrimento del tiempo
onstante, desarrollaremos
en x 5.1.7 (pagina 503). Por los momentos, en esta se
ion extenderemos el tipo
LhashTable<Key> para que maneje un rango aso
iado a el
onjunto de
laves y trate
on la
memoria dinami
a. El tipo en
uestion lo denominamos DynLhashTable<Key, Record>
, el
ual modeliza una tabla hash,
on resolu
ion de
olisiones en
adenada, que aso
ia
elementos de tipo key
on registros de tipo Record y que se espe
i
a en el ar
hivo
htpl dynLhash.H 501i
uya estru
tura general es
omo sigue:
htpl dynLhash.H 501i
template <typename Key, typename Record>
class DynLhashTable : public LhashTable<Key>
{
hMiembros privados de DynLhashTable<Key, Record> 502ai
502
hMiembros
};
Denes:
publi os de DynLhashTable<Key,
Record> 502bi
502a
La tabla hash puede a
ederse
omo un arreglo donde los ndi
es son
laves
de tipo key y los
ontenidos del arreglo son registros de tipo Record. Para ello,
DynLhashTable<Key, Record> exporta sobre
argas al operador [].
Como se ve, DynLhashTable<Key, Record> hereda, tanto parte de su interfaz
omo
parte de su implanta
ion, de LhashTable<Key>. Metodos
omo los observadores y el
reajuste ya estan implantados por LhashTable<Key>. Lo mismo se puede de
ir para el
manejo de la estrategia. La labor de DynLhashTable<Key, Record> se remite, enton
es,
a la deni
ion del rango y al manejo de memoria.
La deni
ion del rango se realiza mediante una
ubeta derivada de LhashTable<Key>::Bucket:
hMiembros privados de DynLhashTable<Key, Record> 502ai
(501) 503a
struct DLBucket : public LhashTable<Key>::Bucket
{
Record record;
502b
Una vez denida la
ubeta, las opera
iones prin
ipales son
on
eptualmente sen
illas,
pues el manejo de la estrategia ya esta instrumentado en LhashTable<Key>. La inser
ion
es, pues,
omo sigue:
hMiembros p
ubli
os de DynLhashTable<Key, Record> 502bi
(501) 502
Record * insert(const Key & key, const Record & record)
throw (std::exception, std::bad_alloc)
{
DLBucket * bucket = new DLBucket (key, record);
LhashTable<Key>::insert(bucket);
return &bucket->record;
}
Uses DLBucket 502a and LhashTable 471.
502
503
503a
503b
(501) 502
delete bucket;
}
Uses DLBucket 502a, LhashTable 471, and record to bucket.
Una
ondi
ion sobre la
ual se sustentan las predi
iones de desempe~no que nos ofre
en las
diversas estrategias para tratar
on las
olisiones es que el fa
tor de
arga no ex
eda el
umbral para el
ual se
umple la predi
ion. Cuanto mayor sea la
arga de una tabla hash,
independientemente de la estrategia que usemos para tratar
on las
olisiones, mayor sera
la probabilidad de que se degrade el desempe~no.
Sabemos que el fa
tor de
arga puede disminuirse si reajustamos la tabla a un mayor
valor de M y reubi
amos las
ubetas. Pero,
omo ya lo sabemos, este reajuste tiene un
pre
io O(M)+O(M ). La estru
tura que trataremos en esta sub-se
ion ata~ne a la
uestion
>puede irse aumentado el arreglo dinami
amente? Intuitivamente, debemos saber que esto
es posible si usamos un arreglo dinami
o del tipo estudiado en x 2.1.5 (pagina 44). Por
este lado, no tenemos
ostes importantes -mayores que O(1)- que pagar por el he
ho de
relo
alizar la tabla. El asunto se relega a en
ontrar una manera de
ambiar dinami
amente
el valor de M.
Al enfoque de tabla hash que estudiaremos e instrumentaremos en esta sub-se
ion,
ual realiza la gestion de dinami
a de M, se le denomina \dispersion lineal" (linear hashing).
La te
ni
a fue des
ubierta por vez primera para alma
enamiento se
undario[24 y este estudio esta fuertemente basado en la ex
elsa y magistral exposi
ion de Larson [22.
El enfoque utiliza un arreglo dinami
o DynArray<T>. El valor de M vara
dinami
amente en fun
ion de . De este modo las predi
iones de desempe~no siempre
se
umplen sin ne
esidad de pagar por el reajuste.
504
504a
504b
table;
hash_fct; // puntero a funci
on hash
M; // Tama~
no de la tabla
N; // N
umero de elementos que tiene la tabla
busy_slots_counter; // Cantidad de entradas del
// arreglo ocupadas
bool
remove_all_buckets; // Indica si se deben liberar
// las cubetas cuando se
// llame al destructor
Uses BucketList, busy slots counter, and DynArray 45.
Expansi
on/contracci
on de una tabla hash lineal
505a
505
505b
M
2
M
2
M
2
M
7
( ) Luego de 5 expansiones
M
4 5
10
505
Para poder
al
ular el fa
tor de
arga, debemos
ono
er en todo momento el tama~no
logi
o a
tual de la tabla (M ); este valor lo mantendremos en el siguiente atributo:
hMiembros privados de LinearHashTable<Key> 504bi+
(504a) 505b 506a
size_t MP; // guarda el valor p + M
506
506a
En a~nadidura, este atributo nos pone a dire
ta disposi
ion el
al
ulo p + M.
Cuando p = 2M, el tama~no logi
o de la tabla se dupli
a y se reini
ian M = 2M,
p = 0 y MP = M. En todo momento 0 p 2lM 1. La fun
ion del atributo l es,
enton
es, a
otar la
ontra
ion al valor original de M que haya sido espe
i
ado en
onstru
ion.
En
ada expansion/
ontra
ion hay que estar pendiente del predi
ado p == 2M. Para
ganar un po
o de tiempo de
al
ulo, el produ
to 2M se guarda en el atributo MM =
2M:
hMiembros privados de LinearHashTable<Key> 504bi+
(504a) 505
507a
size_t MM; // producto 2*M
506b
506
El enfoque dinami
o de expansion/
ontra
ion plantea un problema
on la determina
ion del ndi
e en la tabla y el valor que arroje la fun
ion hash. Dada una
lave k,
tradi
ionalmente el ndi
e en la tabla se determina mediante:
h(k) mod M ;
pero este valor abar
a el rango de entradas [0, M 1] y deja por fuera el rango de entradas
expandidas [M, M + p]. El problema se resuelve mediante el valor de p y la modi
ando
507a
507
}
Denes:
Cuando p = 2M, enton
es todo el rango [0, 2M 1] puede ser
ubierto
on el modulo de
2M.
Cuando o
urre una expansion la lista de
olisiones
uyo ndi
e en la tabla es p
es parti
ionada. Se re
orre enteramente la lista de
olisiones table[p] y se invo
a a
call hash fct() para
ada
lave. Aquellas
laves
uyo ndi
e es el mismo de su entrada
(p) permane
en en la lista, mientras que aquellas
uyo ndi
e es distinto (M + p) son
movidas ha
ia table[M + p]. La gura 5.4 ilustra los estados antes y despues de una
expansion.
p=1
70
206
10
p=2
266
11
M=5 2
1676
86
70
206
11
M=5 2
3
13
77
99
94
14
13
77
99
94
14
435
15
35
435
15
35
266
1676
86
(a)
(b)
10
El pro
eso de parti
ion des
rito puede instrumentarse de la siguiente forma:
hParti
ionar lista 507bi
(508)
508
Uses BucketItor, BucketList, busy slots counter, get current 103, has current 103, and touch 62.
508
El bloque
onsidera la posibilidad de que la lista table[p] este va
a o, in
lusive, que
jamas haya sido referen
iada.
Entendidas (se presume) las vi
isitudes de la expansion, podemos es
ribir una rutina
que la reali
e y que sea invo
ada
ada vez que o
urre una inser
ion:
hMiembros privados de LinearHashTable<Key> 504bi+
(504a) 507a 509b
void expand()
{
const float alpha = 1.0*N/MP; // calculamos la carga actual
lista 507bi
509a
509
Uses BucketList, busy slots counter, concat list 96, cut 64, and touch 62.
509b
La rutina de
ontra
ion,
ual se invo
a
on
ada elimina
ion, se dene, enton
es, de
la siguiente forma:
hMiembros privados de LinearHashTable<Key> 504bi+
(504a) 508
void contract()
{
const float alpha = (1.0*N)/MP;
Lo novedoso de una tabla hash lineal es el pro
eso de expansion/
ontra
ion. De resto,
estru
turalmente, las opera
iones son identi
as.
5.1.7.2
B
usqueda en LinearHashTable<Key>
La busqueda, omo es tradi ion de una tabla hash, es la opera ion mas simple:
510
510a
hMiembros p
ubli
os de LinearHashTable<Key> 510ai
Bucket * search(const Key & key)
{
const int i = call_hash_fct(key);
(504a) 510b
Uses BucketItor, BucketList, call hash fct 507a, get current 103, and has current 103.
Inserci
on en LinearHashTable<Key>
hMiembros p
ubli
os de LinearHashTable<Key> 510ai+
Bucket* insert(Bucket * bucket)
{
const int i = call_hash_fct(bucket->get_key());
5.1.7.4
511
511
Eliminaci
on en LinearHashTable<Key>
hMiembros p
ubli
os de LinearHashTable<Key> 510ai+
(504a) 510b
Bucket * remove(Bucket * bucket)
{
Bucket * next = static_cast<Bucket*>(bucket->get_next());
bucket->del(); // elimine de lista de colisiones
if (next->is_empty()) // lista de colisiones qued
o vac
a?
--busy_slots_counter; // s
==> un slot vac
o
--N;
contract();
return bucket;
}
Uses busy slots counter and contract 509b.
5.1.7.5
An
alisis de la dispersi
on lineal
Antes de enun
iar la proposi
ion fundamental, es menester notar que, salvo las expansiones
y
ontra
iones, una tabla hash lineal es similar a una tabla hash tradi
ional
on resolu
ion
de
olisiones por en
adenamiento separado. Como tal, no nos debe ser extra~no analizar la
dispersion lineal a partir del analisis realizado
on el en
adenamiento separado.
Proposici
on 5.3 (Larson 1988 [22]) Cantidad de cubetas revisadas en una
b
usqueda sobre una tabla hash lineal
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash lineal de longitud M
on N ele-
mentos. Enton
es, el promedio de
ubetas que se visitan en una busqueda fallida es:
UN
9
+ ;
16
(5.29)
1
1
1
9
+
SN 1 +
+
M
M 16
(5.30)
Demostraci
on
Sea l la longitud de
ada una las listas de
olisiones en el en
adenamiento separado.
Segun el lema 5.1 (e
ua
ion (5.7), pag. 475), l = N/M.
Los lemas 5.2 (e
ua
ion (5.8), pag. 475) y 5.3 (e
ua
ion (5.9), pag. 476), que estudiamos
on el en
adenamiento separado, plantean los promedios
uando no o
urren expansiones
o
ontra
iones. En este sentido, asumiendo un fa
tor de
arga y, podemos denir las
siguientes fun
iones:
u(y) = y ;
(5.31)
512
para el promedio de
ubetas que se inspe
iona en una busqueda infru
tuosa y:
s(y) = 1 +
1
1
+
y M
(5.32)
La situa
ion se puede pi
torizar de manera parti
ular (p = 2), as
omo tambien de manera
general, as:
p+M
...
M
Las listas que no han sido parti
ionadas
ontienen, en promedio, l
ubetas.
Bajo la suposi
ion de aleatoriedad para la fun
ion hash, una lista de longitud promedio
l se parti
iona en dos listas de tama~
no equitativo l/2.
En fun
ion de lo denido, el total de
ubetas se reparte enton
es en:
N=
l
p
2
|{z}
parti ionadas
+ (M p)l +
| {z }
no parti
ionadas
l
p
2
|{z}
= pl + (M p)l = Ml
(5.33)
resultantes de parti
i
on
El fa
tor de
arga de una tabla hash lineal esta, enton
es, denido por:
=
Ml
p+M
= l =
p+M
M
(5.34)
Sea x = p/M; o sea, la propor
ion de listas que han sido parti
ionadas. De este modo,
(5.34) se dene
omo:
l = (1 + x)
(5.35)
De (5.35) proviene la \linealidad": la
antidad esperada de
ubetas en una lista que
no ha sido parti
ionada aumenta linealmente de ha
ia 2.
Cualquier busqueda, exitosa o fallida, tiene probabilidad x de realizarse sobre una lista
parti
ionada o resultante de una parti
ion. Del mismo modo, hay una probabilidad (1 x)
de que la busqueda depare en una lista que no ha sido parti
ionada.
Si denimos U(x)
omo la
antidad esperada de
ubetas a visitar en una busqueda
fallida
uando una propor
ion x de las listas de la tabla han sido parti
ionadas, enton
es,
513
segun (5.31),
ual nos indi
a la
antidad de esperada de
ubetas que se visitaran, U(x) se
dene
omo:
(1 + x)
+ (1 x) u((1 + x))
U(x) = x u(l/2) + (1 x) u(l) = x u
2
=
(5.36)
(2 + x x2)
2
De la misma manera, si denimos S(x)
omo la
antidad esperada de
ubetas a visitar
en una busqueda exitosa
uando una propor
ion x de las listas de la tabla han sido parti-
ionadas, enton
es, segun (5.32),
ual nos indi
a la
antidad de esperada de
ubetas que
se visitaran, S(x) se dene
omo:
(1 + x)
S(x) = x s(l/2) + (1 x) s(l) = x s
+ (1 x) s((1 + x))
2
1
(1 + x)
(1 + x)
1
= x 1+
+
+
+ (1 x) 1 +
M
22
M
2
1
+ (2 + x x2)
= 1+
M 4
(5.37)
+
M 2
Notemos,
omo
abra esperarse, que estas son las mismas
otas que para el en
adenamiento separado sin expansion/
ontra
ion.
Analogamente, el maximo esperado de
ubetas revisadas o
urre
uando x = 1/2 =
p = M/2, pues (5.36) y (5.37) son maximos
uando x x2 es maximo. Esto equivale a
de
ir que M/2 listas han sido parti
ionadas en M/2 listas adi
ionales, mientras que restan
M/2 listas sin parti
ionar. As pues:
UN = U(0) =
SN = S(0) = 1 +
9
1
+
M 16
UN
13
12
(5.38)
SN
(13 + 12)M + 12
12M
(5.39)
514
Demostraci
on
UN
Z1
0
y:
M
SN
Z1
0
1+
(2 + x x2) dx
2
+ (2 + x x2) dx
M 4
(5.40)
(5.41)
As pues, en este tipo de tabla, el fa
tor de
arga siempre permane
e
onstantemente
a
otado, lo que posibilita enun
iar el
orolario siguiente.
Corolario 5.2 Desempe
no de las operaciones en una tabla hash lineal
Sea h(k) : K [0, M 1] una fun
ion hash O(1) que distribuye uniformemente los
elementos de K ha
ia [0, M 1]. Sea T una tabla hash lineal de longitud M
on N elementos y fa
tor de
arga l alpha alpha. Enton
es, el desempe~no esperado de las
opera
iones de inser
ion, busqueda y elimina
ion es O(1).
Demostraci
on
La inser
ion requiere una busqueda fallida, la
ual, segun (5.40), requiere re
orrer, en
promedio, 13
on es
12
ubetas. Puesto que es
onstante, (5.40) es
onstante y la inser
i
O(1). Lo mismo o
urre para la b
usqueda fallida.
Para una busqueda exitosa se re
orren, segun (5.41), (13+12)M+12
ubetas,
antidad
12M
que tambien es
onstante, pues es
onstante. La busqueda exitosa es, por tanto, O(1).
Si la elimina
ion es
omo la dise~namos, enton
es esta es O(1) de forma determinista.
Si se basa en la busqueda, enton
es esta
ara
terizada por la busqueda exitosa, la
ual ya
demostramos es O(1)
La gran bondad de este tipo de tabla es que no hay ne
esidad de eje
utar el
ostoso
reajuste para mantener el fa
tor de
arga al valor que que ofrez
a el desempe~no esperado; la
arga es dinami
amente ajustada en tiempo
onstante. Independientemente, de la
antidad de elementos, la
arga siempre estara a
otada y, por tanto, el desempe~no sera
\
onstantemente esperado". >Es, pues, este enfoque idoneo para todas las situa
iones en
que se requiera una tabla hash? Por supuesto que no; por varias razones.
En primer lugar, a
ausa del arreglo dinami
o, por mas que nos hayamos esforzado en
una eje
u
ion de alto desempe~no
uando dise~namos el tipo DynArray<T>, este a
arrea
ostes
onstantes mayores que el del arreglo
ontiguo tradi
ional que empleamos
on el
en
adenamiento separado. As pues, para situa
iones en las
uales se estime
orre
tamente
el valor de N, la dispersion lineal es el enfoque mas lento de todos los que hemos estudiado.
Di
ho de otro modo, si se esta en
apa
idad de estimar
orre
tamente N, aun
on un
ierto
margen de in
ertidumbre, enton
es es preferible adoptar alguno de los enfoques que ya
hemos estudiado en fun
ion de las
onsidera
iones he
has en x 5.1.4.6. Los argumentos son
simples: simpli
idad y mejor desempe~no.
Surge enton
es la pregunta >
uando debemos usar dispersion lineal?. Hay dos es
enarios
fundamentales:
1. Cuando no se
onoz
a la estima
ion de N,
ual es la situa
ion
uando se manejan
onjuntos de manera general.
515
2. Cuando, aunque se
onoz
a una estima
ion maxima de N, este
u
tue fre
uentemente entre valores extremos.
En sntesis, la dispersion lineal tiene la doble bondad de exhibir desempe~no esperado
de O(1)
on un
onsumo de memoria propor
ional a la
antidad de elementos que se
manejan. Debe ser la op
ion
uando se traten
onjuntos generales; por ejemplo, los mapeos
de lenguajes
omo python o perl o la propuesta al estandar C++ de mapeo hash, llamada
hash map.
5.2
Funciones hash
Todos los resultados analti
os de desempe~no sobre los esquemas de resolu
ion de
olisiones asumen que h(k) : K [0, M 1] es O(1) y que esta emula a una distribu
ion de
probabilidad uniforme. Sin estas premisas, ninguno de aquellos resultados es veraz. Es obviamente esen
ial, enton
es, que la fun
ion hash
umpla estos requisitos. Pero hay mu
ho
mas que estos meros requisitos
uando se dise~na una fun
ion hash.
5.2.1
Interfaz a la funci
on hash
\Idealmente", la fun
ion toma una
lave generi
a de tipo Key y la transforma a un entero
entre 0 y el mas grande entero que pueda representarse en el tipo size t. Re
ordemos que
el ndi
e dentro de la tabla se
al
ula mediante:
(*hash_fct)(key) mod M;
Holgura de dispersi
on
El mayor entero posible esta supeditado al mayor valor que se pueda representar
on el tipo
size t. A la
antidad posible de distintos valores que puede arrojar h(k) : K [0, M 1]
516
se le denomina \holgura de dispersion"; mientras mayor sea la
antidad de numeros distintos que pueda generar la fun
ion hash, mayor es su holgura de dispersion. As pues,
en el nuestro
ontexto, un requerimiento de la fun
ion hash es tener la mayor holgura de
dispersion posible.
En programa
ion, al n de
uentas, todo tipo de dato puede representarse
omo una
se
uen
ia de bits. Cualquiera sea la ndole del dominio K, ki, kj K, ki 6= kj =
ki y kj tendran se
uen
ias de bits distintas. En este sentido, es bastante deseable que
h(k) : K [0, M 1]
onsidere todos, los bits de una
lave, pues de esta manera se
fa
ilita mejor el repartir K en el espe
tro size t y, por tanto, se aumenta la
apa
idad de
dispersion. Pero no basta
on
onsiderar todos los bits. Para
omprender ello, examinemos
una tpi
a situa
ion del mundo real de programa
ion.
Consideremos
omo
laves simples
adenas de
ara
teres (char*
) y a h(k)
omo la
P
suma de los
ara
teres de la
adena. h(k) en
aja en el rango [0, n
a
i si]. Si size t est
32
representado
on 32 bits, el valor maximo a representar
on size t es 2 = 4294967296
Pn
s
.
Si
tenemos
laves alfabeti
as
uya longitud maxima es de 30
ara
teres, enton
es
i i
on valores de
ara
teres entre [65, 90][61, 122] , el rango de h(k) es [6130, 122122] =
[1950, 3660]; esto impli
a que dejamos de lado 232 3660+1950 valores posibles que podra
tomar h(k representado
on size t; si M > 1710, enton
es,
on
ertitud, M 11710
entradas jamas seran a
edidas y
ualquiera sea la te
ni
a que lidie
on las
olisiones
ompletamente vana. Una tabla hash
on esa fun
ion puede llamarse una \tabla mash".
13
5.2.3
Considerar todos los bits de una
lave esta supeditado al tama~no del tipo size t , el
ual esta determinado por las
ara
tersti
as de hardware y el
ompilador. Tambien, si
efe
tuamos opera
iones aritmeti
as
omo la ejempli
ada en la sub-se
ion pre
edente,
podemos tener un desborde.
A la te
ni
a tradi
ional para
onfrontar este problema se le
ono
e bajo el parti
ipio
\plegado" o \doblado". Plegar una
lave
onsiste en se
ionar la
lave en pedazos mas
peque~nos y
ombinarlos
on sumas, oes ex
lusivos o desplazamientos.
Una
lave de n bytes debe plegarse en sizeof(size t) bytes. Segun la ndole de
la
lave podemos sele
ionar pedazos de, por ejemplo, n/sizeof(size t) en un orden
es
ogido para evadir algun parti
ular sesgo. Por ejemplo, si tenemos una
lave de 20
bytes, y sizeof(size t) == 4, enton
es podemos
onsiderar las niblas mas signi
ativas
de los 8 bytes
entrales:
hPlegado de una
adena key de n bytes 516i
14
516
char buf[sizeof(size_t)];
517
Heursticas de dispersi
on
5.2.4.1
Dispersi
on por divisi
on
k=
n1
X
i=0
di b i
donde
ada di es un dgito y b es la base del sistema numeri
o. En este sentido, la division
entera k/M puede expresarse
omo:
Pn1
i
k
i=0 di b
=
M
M
(5.43)
(5.44)
i=0
15 En
16 En
tabla.
programa
ion, de
ir \imposibilidad" es muy fuerte, pues lo virtual da espa
io para todo lo posible.
lo que sigue, debemos estar pendientes del he
ho de que M no ne
esariamente es el tama~no de la
518
Esta expresion nos permite ver que los sumandos menos signi
ativos, aquellos tales que
di ni < M, siempre
onforman parte de k mod M, pues estos no son, en terminos
literales, \enteramente divisibles" por M. As pues, (5.44) puede expresarse re
ursivamente
omo:
X
k mod M =
i|di
di b +
bi <M
di b +
i|di bi <M
i|di
di b
bi M
i|di bi M
mod M
!
di b i
mod M mod M (; 5.45)
M
o sea, todos los sumandos de k que no son enteramente divisibles por M mas el resto de
la suma de los restos .
Al primer sumando de (5.45) lo llamaremos \lo indivisible" y al segundo
\el resto de restos".
Esta interpreta
ion del resto nos fa
ilita aprehender la tras
enden
ia que tiene la ade
uada sele
ion del divisor M sobre la
onsidera
ion de dgitos de la se
uen
ia que expresa
a k. De (5.44) se puede ver que los uni
os dgitos que se
ontabilizaran
on seguridad son
los del
omponente indivisible. El resto de restos depende de la multipli
idad respe
to a
M. Seg
un sea el valor de M, la opera
ion modulo tomara en
uenta o no algunos dgitos de
k. Un ejemplo puede ayudar a aquellos que a
un no lo han realizado. Si tenemos M = 100,
enton
es el resto solo esta determinado por lo indivisible, pues los restos restantes son
nulos y
on ellos el resto de restos. Con M = bw no importa
ual sea el valor de k, el resto
solo
onsidera los w ultimos dgitos de k. Si, por ejemplo, los ultimos dgitos de k estan
sesgados a estar entre 20 y 30 y M = 100, enton
es h(k) estara sesgado a ese rango; lo que
derrumba todas las expe
tativas de desempe~no, pues h(k) distara mu
ho de ser aleatoria.
Tampo
o basta M 6= 2w, pues pudiera existir algun patron de multipli
idad en los
sumandos di bi. Por ejemplo, 75312 mod 150 = 12 = 75312 mod 100;
uando miramos
que 150 = 10 5 3, no solo vemos una multipli
idad
omo para
uando M = 100, sino
que,
omo
ada sumando del resto de restos es multiplo de 5 la in
iden
ia de un dgito es
su multipli
idad por 3; en terminos mas simples,
ada dgito tiene probabilidad 1/3 de no
ser
onsiderado. En a~nadidura, la suma de restos tambien tiene buena probabilidad de ser
multiplo de tres.
Las
onsidera
iones sobre la multipli
idad que hemos realizado son validas para
ualquier base numeri
a; en parti
ular la binaria.
Cuando sele
ionamos el metodo de division
omo fun
ion hash, debemos estar sumamente pendientes de esta multipli
idad, pues el azar puede jugarnos en
ontra. Si no
disponemos de tiempo o animo para estudiar la multipli
idad, enton
es podemos sele
ionar a M
omo un numero primo, lo que asegura que ninguno de los sumandos de k
tenga alguna rela
ion de multipli
idad.
El metodo de division aunado a una sele
ion de M,
omo divisor y a la vez
omo
tama~no de la tabla, es el metodo de dispersion mas popular. El enfoque de ALEPH
17
17 Es
de notar que
k mod M =
n1
X
i=0
(di b ) mod M
i
mod M ;
o sea, el resto se puede denir, simplemente,
omo el resto de los restos. Desarrollamos la representa
ion
segun k mod M = k k/M porque
onsideramos que permite aprehender mejor.
519
onlleva dire
tamente este metodo, aunque no la sele
ion de M
omo primo,
ual se
delega al usuario del TAD. En s, este metodo es bastante sen
illo y rapido, pero no se
ade
ua a todas las
ir
unstan
ias; por ejemplo, si lo usamos dire
tamente, no siempre es
fa
il, en fun
ion de tama~no original o del reajuste, asegurar que M sea primo.
5.2.4.2
Dispersi
on por multiplicaci
on
Este metodo tiene la ventaja de que puede adaptarse para
ualquier tama~no de tabla.
Definici
on 5.2 (Fraccional de un n
umero real) Sea x un n
umero real positivo, el
fra
ional de x, denotado
omo {x}, es la parte fra
ional de x. Por ejemplo, {1, 6252345} =
0, 6252345.
El metodo de multipli
a
ion dene una fun
ion hash del siguiente modo:
h(k) = {k } M
(5.46)
El
ono
imiento que nos aporta este teorema se tradu
e en la siguientes observa
iones:
1. {k} y {(k + 1)} pueden estar separados por tres distan
ias posibles y,
2. {k} y {(k + 1)} distan en entre s entre las dos mayores longitudes. Esta separa
ion
es lo que emula la aleatoriedad.
Knuth [19 sugiere que
1
=
^ =
51
=
2
es una buena sele
ion en mu
hos
asos y denomina a esta dispersion \de Fibona
i".
Pero, >por que es bueno este numero?
^ es el re
pro
o de lo que se
ono
e
omo la
\propor
ion aurea" o el \radio divino", pues, desde tiempos inmemoriales, se di
e que
es la propor
ion
on que los dioses forjaron al mundo . La propor
ion , que,
omo
vemos, es irra
ional, lo que indi
ara, quiza, que hay algo de ra
ional en lo divino, no solo
19
520
es ubi
ua en la naturaleza, sino que ha
omprobado ser bastante buena en una amplia
gama de dominios: musi
a, arquite
tura, ingenieras, et
etera. Vale la pena
onstatar la
distribu
ion multipli
ativa,
on unos po
os valores de k, a traves de la siguiente gra
a:
1
0.9
0.8
0.7
0.6
3
3
3
3
{k}
^
0.5
3
3
0.4
0.2
0.1
0.3
3
3
3
3
03
k
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
En la gra
a
onstatamos que
laves se
uen
iales distan en la dispersion: por ejemplo,
h(5) = 0 y h(6) = 4, pero, ademas, la aparien
ia de la propia gra
a bien pudiera ser la
de una distribu
ion uniforme.
Ya vemos, pues, que la dispersion por multipli
a
ion reparte los produ
tos
on su
iente distan
ia, los inter
ala sin algun orden aparente y es independiente del valor de M.
En a~nadidura, este metodo tiene la ventaja de tender a ser mas rapido que el de division,
pues, es mas rapido multipli
ar que dividir. Hay, ademas, un \tru
o" para multipli
ar
rapidamente, que nos ahorra el
oste de apelar a la aritmeti
a en punto
otante.
Sea w la longitud en bits de la
lave y es
ojamos M = 2z. Es de resaltar, en primer
lugar,
omo M = 2z, h(k) puede representarse
on z bits.
Sea = k representado en w bits. El produ
to entero k -no en punto
otantetiene una longitud de 2w bits dividida en dos palabras de w bits p0 y p1 tales que
k = p1k + p2{k}. Pi
tori
amente, podemos mirar el asunto de la siguiente manera:
w bits
k
p1 = k
= 2w
h(k)
p0 = {k}
z bits
De este modo, los z bits mas signi
ativos de p0 representan el valor de h(k); h(k) se
obtiene
on desplazar z bits ha
ia la izquierda a p0 o
on un \and" aritmeti
o entre p0 y
la palabra
on forma: 11
. . . 1} 00
. . . 0} . En grandiosa a~
nadidura, si nos aseguramos de que
| {z
| {z
z ve
es wz ve
es
sea impar, enton
es este metodo garantiza que la palabra p0 se distribuya uniformemente
tal
omo lo eviden
ia el siguiente teorema:
521
Proposici
on 5.6 (Lewis-Denemberg 1991 [23]) (Aleatoriedad de {k})
Sea = 2z tal que es impar. Sea K el
onjunto de
laves, el
ual esta representado
on palabras de w bits. Sea k = p1k + p2{k} | k K. Enton
es, para
ualquier par
de
laves distintas k1, k2 K:
k1 mod 2w 6= k2 mod 2w .
Di
ho de otro modo:
p0 {k2} 6= p0 {k2} .
|{z}
|{z}
k1
k2
Demostraci
on
Asumamos k1 < k2 y sea Dk = k2 k1. El argumento de la prueba estriba en demostrar
que (k2 mod 2w) (k1 mod 2w) = (k2 k1) mod 2w = Dk mod 2w 6= 0; lo
ual tiene
sentido porque Dk 6= 0 y el modulo exhibe la propiedad distributiva.
P
Pw1
i
i
Sean = w1
i=0 i2 y Dk =
i=0 di2 . El produ
to Dk puede expresarse
omo:
Dk =
w1
X
i=0
i2
w1
X
di2
i=0
020 + 121 + + w12w1 d020 + d121 + + dw12w1
!
2w2
i
X X
jdij 2i
=
(5.47)
i=0
j=0
Sea dp el primer bit menos signi
ativo de Dk
uyo valor es 1; de este modo dp1 =
dp2 = = d0 = 0. Debe sernos
laro que la sumatoria interna de (5.47) es nula para
i < p. Ahora bien, para i = p
p
X
jdpj = 0dp ,
j=0
pues los sumandos restantes son nulos dado que dpj = 0 para j < p. De este modo, el bit
p de Dk es 1, lo que impli
a que Dk 6= 0 y k1 mod 2w 6= k2 mod 2w para todo k1 6= k2
El teorema anterior impli
a que todas las
laves posibles de w bits se distribuyen
uniformemente en la palabra de p0 = {k}. Por supuesto, habran
olisiones entre los z bits
mas signi
ativos que
onforman a h(k).
Esta te
ni
a se ade
ua muy bien para una tabla hash lineal en la
ual el tama~no M se
dupli
a. Ademas, no es dif
il adaptarla a palabras de bits de longitud arbitraria.
5.2.5
Dispersi
on de cadenas de caracteres
Hay una vasta
antidad de situa
iones en que la indiza
ion se realiza por una
adena de
ara
teres. Es, pues, importante meditar sobre la dispersion de
adenas, aparte de que,
omo lo ejempli
amos en x 5.2.2 (pagina 515), es relativamente fa
il
onstruir una pesima
fun
ion hash. Si no se dispone del tiempo y animo, enton
es es
onveniente transar por bien
ono
idas y e
ientes fun
iones hash. Las siguientes, son publi
as y de buen desempe~no:
522
1.
hHash de Berstein [5 522ai
size_t hash(unsigned char * str)
{
size_t hash = 5381;
int c;
522a
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
Las razones sobre la es
ogen
ia del valor ini
ial 5381 y el numero \magi
o" 33 aun
no han sido des
ubiertas.
2.
522b
5.2.6
Dispersi
on universal
Por muy buena que sea una fun
ion de dispersion, esta puede deparar en una serie de
olisiones y,
onse
uentemente, en degradar seriamente el desempe~no de una tabla hash. La
mala suerte, improbable en el azar pero posible, no ne
esariamente podra ser la
ausante
de una serie de
olisiones. Supongamos, por ejemplo, que se tiene
ono
imiento exa
to de
la fun
ion hash que se emplea en alguna determinada apli
a
ion. Pudiera o
urrir que ese
ono
imiento se utili
e para generar inser
iones
olindantes que degraden el desempe~no.
Curiosamente, tal
omo lo estudiamos
on el qui
ksort (x 3.2.2.4 (pagina 219)), una
manera de evitar la mala suerte, as
omo tambien en este
aso, la mali
ia, es mediante
aleatoriza
ion; lo que quiza equivalga a de
ir que la mala suerte se
ombate
on el azar .
Una simple manera de aleatorizar es usar dos fun
iones de dispersion h1(k) y h2(k).
Cuando o
urra una inser
ion, se sele
iona al azar entre h1 y h2 la fun
ion hash que se
empleara. Cuando se requiera bus
ar, se
omienza
on
ualquiera de las fun
iones. Si la
lave se en
uentra, enton
es aquella es la fun
ion
on que pudo haberse insertado ; de lo
ontrario, se prueba
on la otra fun
ion.
20
21
523
Obviamente esta te
ni
a enlente
e los algoritmos, pero
ombate algun mal sesgo en la
se
uen
ia de inser
ion de
laves.
En el mismo espritu de aleatoriza
ion, en lugar de usar y es
oger simultaneamente dos
o mas fun
iones de dispersion, podramos sele
ionar una al azar
ada vez que la tabla devenga va
a. Esta ta
ti
a no degrada en lo absoluto el desempe~no y rompe
ompletamente
algun sesgo desafortunado o mali
ioso.
La siguiente deni
ion estable
e un requerimiento que ha
e a un
onjunto de fun
iones
de dispersion \a
eptables" :
22
Definici
on 5.3 (Familia universal de funciones hash)
Sea H = {h0(k), h2(k), . . . , hW1(k)} una
ole
ion de fun
iones de dispersion desde
un dominio K al rango [0, M). Se di
e que H es universal si y solo si:
k1, k2 K =
|H|
M
Di
ho de otro modo, el maximo permitido de
olisiones que pueden o
urrir
on dos pares
de
laves es a lo sumo H/M.
Ha sido demostrado que para una familia universal de
ardinalidad |H| el desempe~no es
ompletamente equiparable en todas las estrategias de resolu
ion que hemos estudiado .
Igualmente, se ha demostrado que para K = {0, 1, . . . , M 1} | |K| = L y
23
24
enton es:
hi,j(k) = (ik + b) mod L mod M
H = {hi,j(k) | 1 i L 1 j L}
(5.48)
(5.49)
Tenemos, pues, un metodo para disponer de L fun
iones hash y realizar aleatoriza
ion.
5.2.7
Dispersi
on perfecta
Hay o
asiones en que el
onjunto de
laves a indizar es nito y
ono
ido. En este
aso, es
posible dise~nar una fun
ion hash biye
tiva y
ompleta,
on e
ien
ia de
omputo O(1),
que mapee todo el
onjunto de
laves ha
ia el rango [0, M). En este
aso, el tiempo de
a
eso esta garantizado a ser O(1) y no se requiere manejar
olisiones (la fun
ion hash es
biye
tiva).
A una fun
ion hash
omo la des
rita se le denomina \perfe
ta" y existen variadas
te
ni
as y programas
onsumados para generar la fun
ion en
uestion.
Si bien las situa
iones en que se
ono
e todo el
onjunto de
laves son o
asionales,
no son ex
ep
ionales. Consideremos, por ejemplo, un di
ionario grande a ser grabado
estati
amente en un DVD. En este
aso, vale perfe
tamente la pena
argar en memoria
una fun
ion hash perfe
ta que mapee la palabra ha
ia la ubi
a
ion fsi
a en el DVD.
En GNU existe un ex
elente y general programa, llamado gperf [1, para generar fun
iones hash perfe
tas de
ualquier tipo de
lave.
22 La
onnotamos de a
eptables porque la deni
i
on se realizo a posteriori
23 Puede
onsultarse Cormen, Leiserson y Rivest [8 o Knuth [19.
24 Cons
ultese Lewis-Denenberg [23 para detalles.
524
5.3
5.3.1
524
Identificaci
on de cadenas
Segun la bondad de una fun
ion de dispersion, esta puede utilizarse para re
ono
er,
on
probabilidad de atino de, digamos, en fun
ion de su
alidad, una sub-se
uen
ia dentro
de otra. La idea fundamental es que, para dos se
uen
ias s1 6= s2 la probabilidad de que
h(s1) = h(s2) es bastante baja si la fun
ion hash es buena.
Este es el prin
ipio de algoritmos de busqueda de patrones inspirados en un
elebre
algoritmo llamado de \Rabin-Karp" [17, en honor a las primeras personas que lo des
ubrieron. Fue originalmente
on
ebido para la busqueda de sub-
adenas de
ara
teres.
La idea es
al
ular previamente una \huella da
tilar" (\ngerprint") en el siguiente
ontexto algortmi
o C/C++:
hAlgoritmo
andido de Rabin-Karp 524i
int rabin_karp_search(char * str, char * sub)
{
// guardar longitudes de las cadenas involucradas
const size_t m = strlen(sub);
const size_t n = strlen(str);
hCal
ular
hDenir
hCal ular
2. La antidad de falsos positivos debe ser peque~na, pues su o urren ia a arrea omparar los m ara teres de la sub- adena. Conse uentemente, en la medida en que se
525
dete
tan mas falsos positivo, el algoritmo de Rabin-Karp tendera ha
ia O(n m);
simetri
amente, menos falsos positivos haran al algoritmo O(n).
El
al
ulo de la huela da
tilar no es otra
osa que una fun
ion hash denida, sobre una
adena de
ara
teres generi
a str de la siguiente manera:
!
| str |1
h(str) =
X
i=0
stri B|
str |1i
mod M ;
(5.50)
donde B es la base del sistema de
odi
a
ion (radix) de un smbolo pertene
iente a una
adena de
ara
teres y stri es el i-esimo smbolo de la se
uen
ia str.
Es menester re
ordar, de x 5.2.4.1 (pagina 517), la propiedad distributiva que nos
indi
a que el resto de la suma es igual al resto de la suma de los restos ; o sea
25
(i + k) mod M = (i mod M) + (k mod M) mod M
(5.51)
(5.52)
Este
ara
ter distributivo del modulo porque nos permite
al
ular (5.50) en fun
ion de los
modulos:
h(str) =
| str |1
stri B
i=0
!
| str |1
| str |1i
mod M
i=0
| str |1
mod M
i=0
525
mod M
!
mod M
mod M
!
mod M ;
(5.53)
lo
ual es sumamente grandioso, pues nos evita preo
uparnos por el eventual desborde
numeri
o de las sumas y poten
ias.
Rabin y Karp [17 demuestran
omo h(str) dispersa muy bien
onforme el valor de M
se sele
iona
omo un numero primo.
Con el
ono
imiento de (5.53), podemos
odi
ar el bloque ini
ial:
hCal
ular en fp sub y fp
urr huellas da
tilares sub y de str[0 525i
(524)
const size_t radix = 256; // radix de un s
mbolo
size_t fp_sub = 0;
size_t fp_curr = 0;
for (int i = 0; i < m; ++i)
{
fp_sub = (radix*fp_sub + sub[i]) % M;
fp_sub = (radix*fp_curr + str[i]) % M;
}
25 V
ease
526
El bloque toma O(m). La huella da
tilar pf sub solo se
al
ula una vez, pero la de la
sub-
adena de str en la posi
ion i debe
al
ularse n i ve
es. Si usaramos el mismo
pro
edimiento que para el bloque hCal
ular en fp sub y fp
urr huellas da
tilares sub y
de str[0 525i, enton
es el algoritmo de Rabin-Karp sera O(n m)
on un
oste
onstante
mayor que el algoritmo a fuerza bruta. El tru
o del algoritmo reside en que la huella digital
para la sub-
adena stri+1 de longitud m, que resumiremos
omo h(stri+1), puede
al
ularse
en tiempo independiente de m en fun
ion del valor previo de h(stri); todo lo que debemos
ha
er es restarle a h(stri+1) el primer sumando de h(stri), pues este ya no es parte de
la huella digital, y a~nadirle el nuevo sumando para stri+1. De esto se dedu
e
omputar
h(stri+1) seg
un:
h(stri+1) = B h(stri) + stri+m Bm stri mod M ;
526a
(5.54)
esta te
ni
a es llamada \dispersion enrollada" (\hash rolled"). Bajo estos terminos, el
valor de fp curr para la i-esima itera
ion se
al
ula as:
hCal
ular huella da
tilar fp
urr para str[i+1 526ai
(524)
fp_curr = (radix*(fp_curr - str[i]*fact) + str[i + m]) % M;
526b
Supertraza
La
omputa
ion esta llena de problemas que manejan
onjuntos de datos muy grandes;
mu
hos de ellos \intratables" en el sentido de que la
antidad de datos es exponen
ial.
Hay o
asiones en las
uales un dato o resultado debe guardarse a efe
tos de no repetir su
al
ulo y, sobre todo, de no repetir los datos que su
eden al re
ien des
ubierto.
Consideremos una se
uen
ia generi
a de datos < d1 d2 d3 dn >
tal que d2 depende del
al
ulo de d1 y as se
uen
ial y generi
amente para todo dato di+1,
527
que requiere
al
ular di. La idea de guardar
ada dato di en una tabla hash es que, segun
la ndole del problema, puede o
urrir que el
al
ulo de un dato di aparez
a repetido; si este
es el
aso, enton
es, el
al
ulo de toda la se
uen
ia sub-siguiente a di (di+1 di+1
di+k >) se repite de nuevo.
Tomemos
omo ejemplo un algoritmo que a fuerza bruta des
ubra maneras de
olo
ar
o
ho reinas en un tablero de ajedrez sin que estas se amena
en. Es de
ir,
omenzamos por
olo
ar una reina en el es
aque a1 y proseguimos re
ursivamente a
olo
ar la siguiente
en la se
uen
ia de es
aques de b sin que esta amena
e a a1. Comenzando por b1 vemos
que la reina amenaza a la puesta en a1; lo mismo o
urre si la
olo
amos en b2. Estas dos
ombina
iones, que denitivamente no
ondu
en a la solu
ion, son
onvenientes guardarlas
a efe
tos de que, en un tablero de 9 9 es
aques no repitamos el
al
ulo en vano. Para
ello, la se
uen
ia explorada se guarda en una tabla.
Aunque el problema anterior puede resolverse e
ientemente sin ne
esidad de una
tabla, este ilustra dos aspe
tos de interes: (1) la idea de guardar estado de
al
ulo para
evitar repeti
ion y (2) el he
ho de que
onforme aumenta la dimension del tablero y la
antidad de reinas aumenta la
antidad de estados. Para un tablero de dimension n muy
grande, la
antidad de estados puede ser inabordable.
En el domino de la explora
ion de grafos dinami
os de estado, se des
ubrio una muy
interesante te
ni
a llamada \supertraza" [16. En lugar de guardar de guardar enteramente
el estado de
al
ulo, se dise~na una fun
ion de dispersion y se mar
a en una tabla de bits
su apari
ion. Este mar
aje aumenta
onsiderablemente la
apa
idad de alma
enamiento,
pues solo se requiere un bit por estado, lo que aumenta la
apa
idad de tratar
on la es
ala
del problema. Sin embargo, no hay manera determinista de saber si un hash alma
enado
en la tabla
orresponde al estado a
tual de
al
ulo o se trata de otro estado que
ausa
olision. En otras palabras, es imposible garantizar que todos los estados se
ubran.
Imposibilidad en garantizar no ex
luye a su probabilidad. La in
ertidumbre anterior se
ata
a eje
utando la te
ni
a
on distintas fun
iones hash de manera de ofre
er una buena
expe
tativa de
ubrir todos los estados posibles.
5.3.3
Uno de los prin
ipales usos de las tablas de dispersion es lainstrumenta
ion de
a
hes.
Un
a
he es una tabla aso
iativa, nita pero de a
eso rapido, que se
olo
a entre un
programa y un
onjunto de datos. Para que se re
ompense la utiliza
ion de un
a
he, este
debe ser mu
ho mas rapido que el simple a
eso al
onjunto de datos. Pi
tori
amente, el
asunto puede interpretarse del siguiente modo:
Programa
a
velocidad
Vp
Cache a
velocidad
Vc
Medio contentivo
de datos
a velocidad
Vd
528
En una situa
ion algortmi
a, el programa se eje
uta a una velo
idad vp y a
ede datos
de un
onjunto que opera a velo
idad vd. El
a
he, opera a velo
idad vc | vp < vc < vd.
De este modo,
uando se desea bus
ar un dato, se revisa primero el
a
he y, si se en
uentra, enton
es se re
upera el dato sin ne
esidad de pagar el
oste que a
arrea a
ederlo a
velo
idad vd. La idea se sustenta en la regla 80-20 men
ionada en x 3.5.1 (pagina 260).
La palabra \
a
he" proviene del verbo galo \
a
her" que signi
a es
onder y ha
e algo
de alusion porque a ve
es este se maneja trasparentemente; es de
ir, el programa a
ede
a los datos asumiendo que se en
uentran en un medio esperado sin
ono
imiento a
er
a
de la existen
ia del
a
he; esta es la situa
ion, por ejemplo, en hardware. Los fran
eses
tambien le llaman \ante-memoria" (\antememoire"), sobre todo para el hardware.
Hay una vasta
antidad de
ir
unstan
ias en que el uso de un
a
he ha
e la diferen
ia
entre un muy alto y pobre desempe~no. La situa
iones tpi
as se pueden resumir
omo
sigue:
Medio de almacenamiento de datos distinto a memoria principal Los niveles de
Cuando los datos que a
eda un programa, el
ual se maneja en memoria prin
ipal,
se en
uentren en un nivel de memoria mas lento, enton
es un
a
he entre la memoria
prin
ipal y el medio de alma
enamiento puede a
elerar sustan
ialmente la eje
u
ion.
Estructuras de datos est
aticas Por razones de diversa ndole, puede o
urrir que haya
que emplear una estru
tura de datos que no es ade
uada a
ierto estilo de re
upera
ion.
Por ejemplo, supongamos que mantenemos en un arbol binario de busqueda un
onjunto de registros. El arbol se indiza por alguna
lave prin
ipal numeri
a; por instan
ia un numero de
edula, lo que permite una re
upera
ion esperada de O(lg(n))
para
ualquier registro. Con menos fre
uen
ia, se requiere re
uperar por alguna
lave
alfabeti
a se
undaria; un apellido,
omo
lasi
o arquetipo.
En este
aso, puede ser muy
ostoso, y probablemente inne
esario, indizar mediante
un segundo arbol binario. En su lugar, podemos guardar en un
a
he, indizado
por apellido, los registros mas re
ientemente
onsultados, sean por
edula o por
apellido, de modo tal que eventualmente nos ahorremos un busqueda se
uen
ia sobre
el
onjunto de
laves.
C
alculos que puedan repetirse En mu
has o
asiones, se requiere realizar
al
ulos
529a
529
Lampson [21 ha
e una ex
elente exposi
ion al respe
to desde la perspe
tiva del dise~no de
sistemas.
Una
ara
tersti
a muy importante de un
a
he es que es nito; es de
ir, tiene una
apa
idad maxima,
onsiderablemente menor que la del
onjunto objeto de la gestion.
Cuando se requiere insertar un dato en un
a
he lleno, enton
es hay que sele
ionar una
de las datos
ontenidos y substituirlo por el nuevo dato. >Cual debe sele
ionarse? Si
es
ogemos un dato que sera referen
iado en un futuro
er
ano, enton
es, en lo que
on
ierne
al dato sa
ado, perdemos la bondad del
a
he. La lo
alidad de referen
ia sugiere sele
ionar
el dato mas antiguo.
Probablemente la tabla hash es la estru
tura de datos idonea para implantar un
a
he
en memoria prin
ipal. Esen
ialmente por una razon: es en promedio el esquema de re
upera
ion mas rapido que se
ono
e. Un
a
he es una ayuda al desempe~no, pero su bondad
depende del patron de a
eso. No es, pues,
rti
o el tener un desempe~no garantizado, pues
si este fuese el
aso, enton
es no deberamos de usar un
a
he. En a~nadidura, la fun
ion
hash puede representar parte de lo que se pretenda guardar. Como ejemplos tenemos
los usos des
ritos en las dos sub-se
iones anteriores y el dispersar
al
ulos que puedan
repetirse.
ALEPH
ontiene un TAD, llamado Hash Cache<Key,Data>
uya espe
i
a
ion esta
ontenida en el ar
hivo htpl hash
a
he.H 529ai:
htpl hash
a
he.H 529ai
template <class Key, class Data>
class Hash_Cache
{
hEntrada de Hash Cache<Key,Data> 529
i
hMiembros privados de Hash Cache<Key,Data> 529bi
ubli
os de Hash Cache<Key,Data> 533bi
hMiembros p
};
Denes:
Hash Cache, used in
hunk 533b.
529b
529
Esto impli
a denir una
lase de
ubeta, la
ual se dene del siguiente modo:
hEntrada de Hash Cache<Key,Data> 529
i
(529a)
class Cache_Entry : public LhashTable<Key>::Bucket
{
Data
data;
hMiembros
hMiembros
530bi
530ai
530
530a
530b
530
530d
530e
530f
Por mas antigua que pueda ser una entrada en el
a
he, hay situa
iones en las
uales
se requiera que esta no se substituya
uando el
a
he este lleno e ingrese una nueva
entrada. Este tipo de entrada se mar
a
omo \tran
ada" (locked) y se identi
a mediante
el siguiente atributo:
hMiembros privados de entrada de Hash Cache<Key,Data> 530bi+
(529
) 530d 531a
bool locked; // indica si la entrada est
a trancada
531a
531
key
data
locked
is in hash table
dlink inside
dlink lru
531b
Son tres dobles enla
es: el de la
ubeta de la tabla hash (dlink de hash), dlink lru que
es el de la
ola ordenada por tiempo de a
eso y dlink inside que es el de la lista de
ubetas que estan dentro de la tabla hash.
A esta altura de la expli
a
ion quiza alguien ya haya planteado
omo obje
ion el usar el
en
adenamiento separado en lugar de un enfoque
errado. Si utilizasemos el sondeo lineal,
enton
es tendramos el problema de que las entradas se pueden mover y ello impide que
otra estru
tura de dato apunte a un objeto Cache Entry. Con el sondeo por doble hash
tenemos el problema de tener que denir dos fun
iones hash y un po
o mas de tiempo a
gastar.
As pues, haremos que el TAD Hash Cache<Key,Data> no aparte ni libere memoria
para ninguna entrada cache entry; estas se apartan en un arreglo
ontiguo en tiempo de
onstru
ion. De este modo, aprove
hamos el propio
a
he del
omputador y no desperdi
iamos tiempo en llamadas al manejador de memoria
Ini
ialmente, estas entradas estan en
oladas bajo el siguiente enla
e:
hMiembros privados de entrada de Hash Cache<Key,Data> 530bi+
(529
) 531a
Dlink
dlink_inside; // enlace a la lista de entradas que
Uses Dlink 90.
531
size_t cache_size;
// tama~
no del cache; m
aximo n
umero de entradas que
// puede contener
Denes:
Los enla
es a las
olas de un Cache Entry se manejan del siguiente modo:
1. lru list:
ola de entradas ordenadas desde la mas antiguamente a
edida hasta la
mas re
ientemente a
edida. Ini
ialmente, el
a
he esta va
o y todas las entradas
pre-apartadas enlazadas por esta
ola.
2. inside list: lista de entradas insertadas en la tabla hash. Ini
ialmente, esta lista
esta va
a, pues el
a
he no
ontiene ningun elemento. Las entradas en esta lista
532
cache-size
.....
lru list
inside list
Figura 5.5: Estado ini
ial del arreglo
ontiguo de
ubetas. Todas las entradas pertene
en
a la lista lru.
tambien estan ordenadas desde la mas antiguamente a
edida hasta la mas re
ientemente a
edida. Notemos, sin embargo, que puesto esta
ola solo
ontiene entradas
logi
amente pertene
ientes al
a
he, su estado no ne
esariamente es el mismo que el
de la
ola lru list, la
ual ordena entradas que pertene
en o no al
a
he.
La uni
a fun
ion de la lista inside list es proveer un iterador simple sobre los
elementos del
a
he.
Si la tabla esta llena, enton
es inside list deviene va
a.
45
lru list
inside list
0
1
2
3
false
true
false
false
false 2
false
375
3
true
true
4
4
false
false
58
false
true
8
9
locked list
false 6
false
479
false 7
true
El enla e dlink lru es ex luyente: o la ubeta esta en lru list o en locked list
533a
533
Chunk_Descriptor chunk_list;
Denes:
chunk list, used in
hunks 533b and 537b.
Uses Cache Entry 529
and Dnode 106a.
533b
chunk list es el u
ltimo atributo de Hash Cache<Key,Data> y dene la lista de bloques
(arreglos de Cache Entry). Estamos listos para denir el
onstru
tor:
hMiembros p
ubli
os de Hash Cache<Key,Data> 533bi
(529a) 535b
Hash_Cache(size_t
(*hash_fct)(const Key&),
const size_t & __hash_size,
const size_t & __cache_size)
throw (std::exception, std::bad_alloc)
: hash_table(hash_fct, __hash_size, false),
num_lru(0), cache_size(__cache_size), num_locked(0)
{
// apartar entradas del cache
Cache_Entry * entries_array = new Cache_Entry [cache_size];
try
{ // apartar el descriptor del arreglo
std::auto_ptr<Chunk_Descriptor>
chunk_descriptor (new Chunk_Descriptor (entries_array));
chunk_list.insert(chunk_descriptor.get());
// insertar todos los Cache_Entry en la lista lru
for (int i = 0; i < cache_size; i++)
insert_entry_to_lru_list(&entries_array[i]);
chunk_descriptor.release();
}
catch (...)
{
delete [] entries_array;
throw;
}
}
Uses Cache Entry 529
, cache size 531
, chunk list 533a, Hash Cache 529a,
and insert entry to lru list 533
.
533
observemos que el
onstru
tor apela a un metodo interno insert entry to lru list(),
el
ual, junto
on otros metodos privados en torno a lru list, se denen del siguiente
modo:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 533a 534a
void insert_entry_to_lru_list(Cache_Entry * cache_entry)
534
{
num_lru++;
lru_list.insert(cache_entry->link_lru());
}
void remove_entry_from_lru_list(Cache_Entry * cache_entry)
{
num_lru--;
cache_entry->link_lru()->del();
}
Denes:
insert entry to lru list, used in
hunks 533b, 536
, and 537b.
remove entry from lru list, used in
hunk 536b.
Uses Cache Entry 529
and lru list 530e.
535a
535
Ahora estamos prestos para mostrar una rutina
rti
a, la
ual sele
iona la entrada
mas antigua (
ontenida o no en el
a
he) y la ha
e la mas re
ientemente a
edida:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 534b
void remove_entry_from_hash_table(Cache_Entry * cache_entry)
{
cache_entry->link_inside()->del();
hash_table.remove(cache_entry);
cache_entry->is_in_hash_table = false;
do_lru(cache_entry);
}
Cache_Entry * get_lru_entry()
{
if (lru_list.is_empty())
// existe una entrada disponible?
throw std::underflow_error("All entries are locked"); // no ==> excepci
on!
// obtenga la entrada m
as antigua -la menos recientemente accedida
Dlink * lru_entry_link = lru_list.get_prev();
Cache_Entry * cache_entry = dlink_lru_to_Cache_Entry(lru_entry_link);
// si cache_entry est
a contenida en la tabla hay que eliminarlo
if (cache_entry->is_in_hash_table)
remove_entry_from_hash_table(cache_entry);
do_mru(cache_entry); // la entrada deviene la m
as recientemente accedida
return cache_entry;
}
Denes:
remove entry from hash table() elimina cache entry de la tabla hash y la torna
omo
la entrada mas antiguamente a
edida. get lru entry() es la rutina
rti
a que sele
iona
535b
la entrada mas antiguamente a
edida y que se invo
a
uando se inserta un nuevo elemento
en el
a
he.
Expli
ado esto, podemos mostrar la inser
ion:
hMiembros p
ubli
os de Hash Cache<Key,Data> 533bi+
(529a) 533b 536a
Cache_Entry * insert(const Key & key, const Data & data)
{
Cache_Entry * cache_entry = get_lru_entry(); // obtener entrada m
as antigua
// escribirle el par
cache_entry->get_key() = key;
cache_entry->get_data() = data;
inside_list.insert(cache_entry->link_inside()); // ins
ertela en inside_list
// insertarla en la tabla hash
536
hash_table.insert(cache_entry);
cache_entry->is_in_hash_table = true;
return cache_entry;
}
Uses Cache Entry 529
, get lru entry 535a, and inside list 531
.
536a
return cache_entry;
}
Uses Cache Entry 529
and do mru 534a.
536b
if (not cache_entry->is_in_table())
throw std::domain_error("Cache_Entry is not in the cache");
remove_entry_from_lru_list(cache_entry);
insert_entry_to_locked_list(cache_entry);
cache_entry->lock();
}
Uses Cache Entry 529
and remove entry from lru list 533
.
536
537
if (not cache_entry->is_locked())
throw std::runtime_error("Cache_Entry is not locked");
remove_entry_from_locked_list(cache_entry);
insert_entry_to_lru_list(cache_entry);
cache_entry->unlock();
}
Uses Cache Entry 529
and insert entry to lru list 533
.
537a
if (not cache_entry->is_in_table())
throw std::domain_error("Cache_Entry is not in the cache");
remove_entry_from_hash_table(cache_entry);
}
Uses Cache Entry 529
and remove entry from hash table 535a.
La sele
ion de un buen tama~no para de
a
he puede ser
rti
a para el desempe~no
global de un sistema. Una
apa
idad insu
iente puede ha
er que las inser
iones reempla
en a entradas que seran referen
iadas. Si esto o
urre, enton
es la busqueda siempre sera
vana y el desempe~no del a
eso global se degradara a un punto peor que si no se tuviese
el
a
he. A este fenomeno se le denomina \thrashing" y puede ser sumamente
ostoso si
el
a
he su emplea en el
amino
rti
o de un sistema; lo
ual suele ser el
aso.
As pues,
uando se opta por el empleo de un
a
he, se debe, en la mayor medida
posible, disponer de una estima
ion pre
isa de la rela
ion de referen
ia al
onjunto. En
esto puede ser muy importante la rela
ion 80-20 (x 3.5.1 (pagina 260)), pero,
uenta habida
del
oste que puede a
arrear un
ambio o error en la estima
ion, es re
omendable que el
tama~no del
a
he pueda ajustarse en tiempo de eje
u
ion. La idea es que si durante la
eje
u
ion se revela ne
esario ha
er un ajuste, enton
es este sea posible, lo
ual dista de
ser un enfoque dinami
o
omo el de una tabla hash lineal. En virtud de esto, dise~namos
la siguiente rutina de expansion:
hMiembros p
ubli
os de Hash Cache<Key,Data> 533bi+
(529a) 537a
26
537b
26 La tradu
i
on literal de este termino es dif
il porque pensamos que no existe un equivalente dire
to
astellano. En el
ontexto del
a
he, \thrashing" alegoriza \
agelar", pues, por lo general,
uando un
a
he
entra en este estado a
arrea una penalidad demasiado severa sobre el sistema que lo usa.
538
{
if (plus_size == 0)
throw std::range_error ("bad plus_size");
const size_t new_cache_size = cache_size + plus_size;
Cache_Entry * entries_array = new Cache_Entry [plus_size];
try
{
// apartar el descriptor
std::auto_ptr<Chunk_Descriptor>
chunk_descriptor (new Chunk_Descriptor (entries_array));
// Calcular el nuevo tama~
no de la tabla hash y relocalizar sus
// entradas
const float curr_hash_ratio = 1.0*cache_size/hash_table.capacity();
const size_t new_hash_capacity = new_cache_size/curr_hash_ratio;
hash_table.resize(new_hash_capacity);
// Meter nuevas entradas del cache en la lista lru
for (int i = 0; i < plus_size; i++)
insert_entry_to_lru_list(&entries_array[i]);
chunk_list.insert(chunk_descriptor.release());
cache_size = new_cache_size;
}
catch (...)
{
delete [] entries_array;
throw;
}
}
Uses Cache Entry 529
, cache size 531
, chunk list 533a, expand 508, insert entry to lru list 533
,
and resize.
5.4
Notas bibliogr
aficas
En su ex
elso libro sobre programa
ion, van der Linden[29 men
iona, matafori
amente,
que si a el le to
ase estar en una isla desierta y le diesen a es
oger una sola estru
tura de datos, enton
es esta sera la tabla hash. >por que un programador virtuoso
onsidera tan vital esta estru
tura? Quiza porque el no sea uno no sea uno de los seres
tpi
amente dominados por una idea determinista del mundo.
En lo
on
reto de la programa
ion, podemos de
ir que la dispersion es una te
ni
a
ompletamente basada en la probabilidad; es de
ir,
rudamente hablando, en eso que
an
estralmente llamamos la suerte o azar, y que hoy se mira
on desden. Como sera de
extra~na esta lia por el determinismo que muy po
os programadores se per
atan de que
emplear una buena fun
ion de dispersion para bus
ar en un arreglo, sin preo
uparse de
las
olisiones ni de mar
ar las
eldas, tiene mu
has mejores probabilidades de en
ontrar
5.5. Ejercicios
539
El hombre que dijo: "preferira ser afortunado que bueno" tena una profunda
perspe
tiva de la vida. La gente teme re
ono
er que una gran parte de la vida depende
de la suerte. Da miedo pensar en todo lo tanto sobre lo que no tenemos
ontrol..." .
27
Esta
ita de una pel
ula de Woody Allen nos indi
ia un po
o el desden que en esta
epo
a tenemos ha
ia el azar. Ha
e unos 2500 a~nos Demo
rito, quiza uno de los primero
que pensaba en la idea a
tual de atomo, de
a que en el azar y la espontaneidad hay
ons
ien
ia.
Maquiavelo vio en la suerte un fa
tor determinante para el destino de los pueblos.
Alegorizaba la vida
omo un ro
uyo
uir
otidiano la suerte poda
ambiar; una inunda
ion, por ejemplo. Bajo ese sentido alegori
o, el re
omendaba prepararse para
uando la
suerte tornase y lo alegorizaba
on la
onstru
ion de un dique. En la vida moderna esto
puede tradu
irse al ahorro. En la vida de la programa
ion a una estrategia de resolu
ion
de
olisiones.
Segun Knuth la idea de dispersion fue des
ubierta independientemente por primera
vez por H. P. Luhn [19 y Amdahl et al [4 en 1953. El ultimo art
ulo men
iona el sondeo
lineal.
El en
adenamiento separado y el metodo de division apare
e primero des
ubierto por
Dumey [9.
El sondeo lineal fue sugerido por Ershov [10,
uyo analisis, extremadamente di
ultoso,
fue des
ubierto por primera vez por Knuth en 1962,
omo el mismo lo expli
a en una nota
a pie de pagina de [19 pag. 536.
El modelo de sondeo ideal, extremadamente util
omo mar
o de analisis de las te
ni
as
de sondeo, fue introdu
ido por Peterson en 1957 [27.
La paradoja del
umplea~nos es un problema
lasi
o de la teora de probabilidades.
Una presenta
ion rigurosa puede en
ontrarse en el
lasi
o Feller [11. Esta paradoja fue
generalizada en la
elebre fun
ion Q de Ramanujan [13. Un analisis de su apli
a
ion en
la programa
ion puede en
ontrarse en [12.
La te
ni
a presentada en este texto para eliminar las
eldas mar
adas DELETED
on el
doble hash fue presentada por [15. Una implanta
ion
on
reta, aparte de la aqu expuesta
no es
ono
ida publi
amente.
La dispersion universal fue des
ubierta en 1977 por Carter y Wegman [6.
La dispersion perfe
ta apare
e a nales de los setenta. Un art
ulo paradigmati
o es
de Ci
helli [7.
5.5
Ejercicios
540
(b) Asumiendo que las
laves estan entre 1 y 100, que no se repiten y que
ada
lave tiene la misma probabilidad de bus
arse,
al
ule:
i. Probabilidad de que una
lave se en
uentre en la tabla.
ii. Probabilidad de que una
lave se en
uentre en la tabla y que se requiera
re
orrer dos (2) o mas nodos durante su busqueda.
7. Asuma una tabla hash
on resolu
ion de
olisiones
on en
adenamiento separado.
Asuma una
antidad esperada de
laves de 105
on una desvia
ion de 104. Sugiera un
tama~no de tabla tal que el fa
tor de
arga no ex
eda del 97 %. Cal
ule la longitud
esperada de
ada lista de
olision li y su desvia
ion tpi
a.
8. La estru
tura de
ubeta utilizada por el TAD ODhashTable<Key, Record> utiliza
un
ampo espe
ial denominado probe type. Deduz
a la manera en que se puede
determinar en tiempo de eje
u
ion la informa
ion aportada por este
ampo. Di
ho
de otro, >
omo puede pres
indirse de este
ampo?
9. En el TAD ODhashTable<Key, Record>, deduz
a una manera de pres
indir de los
ampos de la
ubeta status y probe type. (+)
10. Dada una tabla hash
on resolu
ion de
olisiones por dire
ionamiento abierto, deduz
a un algoritmo general de dos fases que reduz
a al mnimo la
antidad de
ubetas
on estado DELETED. El algoritmo debe poder ser apli
ado para
ualquier estrategia
de resolu
ion por dire
ionamiento abierto.
La primera fase
onsiste en examinar la tabla y mar
ar
on EMPTY todas las entradas
on valor DELETED. La segunda fase estudia las
ubetas
on valor BUSY y determina
uales
ubetas
on estado EMPTY deben ser
ambiadas al estado DELETED de manera
que la
lave pueda ser lo
alizada.
No esta permitido mover los registros
ontenidos en las
ubetas. (+)
11. Enun
ie las ventajas y desventajas del algoritmo enun
iado en la pregunta anterior.
12. Cal
ule una expresion similar a (5.27) que
ompare el
oste en espa
io del TAD
ODhashTable<Key, Record>
on el de OLHashTable<Key,Record>.
13. Haga un analisis
omparativo entre los TAD DynLhashTable<Key, Record> y
ODhashTable<Key, Record>. Enun
ie ventajas, desventajas, similitudes y diferen
ias.
5.5. Bibliografa
541
14. Dada una tabla hash lineal,
al
ule la
antidad esperada de llamadas a la fun
ion
hash
ada vez que o
urre una inser
ion o busqueda. (+)
15. La implanta
ion de DynLhashTable<Key, Record> hereda de LhashTable<Key>
la implanta
ion y parte de su interfaz. Puesto que DynLhashTable<Key, Record>
es derivada publi
amente de LhashTable<Key>, hay metodos de LhashTable<Key>,
que no son parte de la interfaz de DynLhashTable<Key, Record> , que pueden
llamarse desde una instan
ia de DynLhashTable<Key, Record> . Por ejemplo, el
usuario podra invo
ar la inser
ion de LhashTable<Key> desde un objeto de tipo
DynLhashTable<Key, Record> :
DynHashTable<int, Record> table;
LhashTable<int>::Bucket bucket = new LhashTable<int>::Bucket (key);
table.insert(bucket);
Explique un me
anismo para evitar que esto o
urra. Es de
ir, para impedir o,
en el peor de los
asos, para dete
tar que el usuario llamo a la inser
ion de
LhashTable<Key> y reportar un error en tiempo de eje
u
ion.
16. Considere el problema de la existen
ia de un objeto. Esto es, dado un puntero a un
objeto de tipo, digamos Object, se desea veri
ar si la dire
ion de memoria apunta
efe
tivamente a un objeto de ese tipo.
Dis
uta todas las alternativas posibles para dise~nar un sistema general de veri
a
ion
de existen
ia de objetos.
17. Implante las
lases LhashTable<Key> y LhashTableVtl<Key> para que utili
en
listas simplemente enlazadas en lugar de doblemente enlazadas.
18. Implante las
lases LhashTable<Key> y LhashTableVtl<Key> para que utili
en listas enlazadas ordenadas. Reali
e un estudio
omparativo
on la implanta
ion tradi
ional expli
ada en x 5.1.3.1.
19. Cal
ule la probabilidad de que la opera
ion k mod 150 solo tome en
uenta los dos
dgitos menos signi
ativos.
Bibliografa
[1 http://www.gnu.org/software/gperf/.
[2 Aho, Hop
roft, and Ullman. Data Stru
tures and Algorithms. Addison-Wesley,
Reading, 1983.
[3 Aho, Hop
roft, and Ullman. Estru
turas de datos y algoritmos. Addison-Wesley
Iberoameri
ana, 1988. Tradu
i?n de Am?ri
o Vargas Villaz?n y Jorge Lozano
Moreno.
542
[4 Gene M. Amdahl, Elaine M. Boehme, N. Ro
hester, and Arthur L. Samuel. ??? The
year is un
ertain (???). Amdahl originated the idea of open addressing with linear
probing, whi
h was later independently redis
overed and published [10., 1953.
[5 Daniel J. Bernstein. Pagina web de daniel j. bernstein.
[6 Carter and Wegman. Universal
lasses of hash fun
tions. In STOC: ACM Symposium on Theory of Computing (STOC), 1977.
[7 R.J. Ci
helli. Minimal perfe
t hash fun
tions made simple. Communi
ations of the
ACM, 23(1):17{19, January 1980.
[8 T. H. Cormen, C. E. Leiserson, and R. L. Rivest. Introdu
tion to Algorithms. MIT
Press, Cambridge, MA, USA, 1989.
[9 Arnold I. Dumey. Indexing for rapid random a
ess memory systems. Computers and
Automation, 5(12):6{9, De
ember 1956. First paper in open literature on hashing.
First use of hashing by taking the modulus of division by a prime number. Mentions
haining for
ollision handling, but not open addressing. See [10 for the latter.
[10 A. P. Ershov. ??? Doklady Adak. Nauk SSSR, 118(??):427{430, 1958. Redis
overy
and rst publi
ation of linear open addressing. See [4, 9.
[11 W. Feller. An Introdu
tion to Probability Theory and its Appli
ations. John
Wiley, pub-JW:adr, 1950. See the dis
ussion of the birthday paradox in Se
tion 2.3.
[12 P. Flajolet, D. Gardy, and L. Thimonier. Birthday paradox,
oupon
olle
tors,
a
hing
algorithms and self-organizing sear
h. Te
hni
al Report STAN-CS-87-1176, Department of Computer S
ien
e, Stanford University, 1987, August.
[13 Philippe Flajolet, P. J. Grabner, P. Kirs
henhofer, and H. Prodinger. On ramanujan's
Q-fun
tion. Te
hni
al Report RR-1760, Inria, Institut National de Re
her
he en
Informatique et en Automatique.
[14 R. L. Graham, D. E. Knuth, and O. Patashnik. Con
rete Mathemati
s: A Foundation for Computer S
ien
e. Addison-Wesley Pub., 1994.
[15 Takao Gunji and Eii
hi Goto. Studies on hashing | 1. a
omparison of hashing
algorithms with key deletion. Journal of the Information Pro
essing So
iety of
Japan, 3(1):1{12, ???? 1980.
[16 Gerard J. Holzmann. An analysis of bitstate hashing. Formal Methods in System
Design, 13(3):289{307, 1998.
[17 Karp and Rabin. E
ient randomized pattern-mat
hing algorithms. IBMJRD: IBM
Journal of Resear
h and Development, 31, 1987.
[18 Donald E. Knuth. Seminumeri
al Algorithms, volume 2 of The Art of Computer
Programming. Addison-Wesley, Reading, MA, USA, third edition, 1997.
[19 Donald E. Knuth. Sorting and Sear
hing, volume 3 of The Art of Computer
Programming. Addison-Wesley, Reading, MA, USA, se
ond edition, 1998.
5.5. Bibliografa
543
[20 Donald E. Knuth. Sele
ted Papers on Analysis of Algorithms. CSLI Publi
ations,
Stanford, CA, USA, 2000.
[21 Butler W. Lampson. Hints for
omputer system design. IEEE Software, 1(1):11{28,
January 1984.
[22 Larson. Dynami
hash tables. CACM: Communi
ations of the ACM, 31, 1988.
[23 Harry R. Lewis and Larry Denenberg. Data Stru
tures and Their Algorithms.
Harper Collins Publishers, New York, 1991.
[24 Witold Litwin. Linear hashing: A new algorithm for les and tables addressing. In
ICOD, pages 260{276, 1980.
[25 Ni
holas Nether
ote and Julian Seward. Valgrind: A program supervision framework.
Ele
tr. Notes Theor. Comput. S
i, 89(2), 2003.
[26 Ozan Yigit oznexus.yorku.
a. Pagina web del proye
to sdbm.
[27 W. W. Peterson. Addressing for random a
ess storage. IBM Journal of Resear
h
and Development, 1(2), April 1957.
[28 Robert Sedgewi
k and Philippe Flajolet. An Introdu
tion to the Analysis of Algorithms. Addison-Wesley Publishing Company, 1996.
[29 Peter van der Linden. Expert C Programming - Deep C Se
rets. SunSoft Press -A
Prenti
e Hall Tittle-, 1994. ISBN 0-13-177429-8.
[30 M. A. Weiss. Data Stru
tures and Algorithm Analysis in C. Addison Wesley, 1997.
\D S and A A" [not in C, Benjamin Cummings, '92.
Captulo 6
Arboles
de b
usqueda equilibrados
Quiza alguna vez la no
ion de equilibrio se nos haya apare
ido bajo una idea an
estralmente inmemorable y
uyo aspe
to visual es el siguiente:
La imagen pi
toriza una artefa
to antiqusimo,
on tenden
ia a
tual al desuso, pero aun
vigente en algunos es
asos
ontextos,
uya nalidad es pesar; o sea, medir pesos. La idea
es
olo
ar en uno de los platillos lo que se desea pesar, mientras que en el otro se ponen
pesos de referen
ia a
tualmente llamados \plomadas". Cuando los platillos al
anzaban
igual altura se de
a que haba \qulbrum"; de \aequus", que signi
a \igual" y \lbra"
que era el nombre romano de la plomada y aun vigente
omo una medida real de peso o
valor e
onomi
o.
El artefa
to en
uestion, hoga~no se le denomina balanza, pero anta~no los romanos
tambien lo mentaron
omo \libra", pues estos renaron el me
anismo de tal forma que el
brazo de la balanza poda desplazarse y sustituir algunas plomadas de referen
ia. Desde
enton
es, la balanza o libra ha simbolizado situa
iones en las
uales se anhela alguna
espe
ie de igualdad o equilibrio; en parti
ular, ha sido el smbolo de la justi
ia, quiza la
virtud mas dif
il de
ultivar.
En el ambito de los arboles binarios, el equilibrio se pi
toriza bajo la ya bien
ono
ida
imagen siguiente:
T
L(T )
R(T )
Las ramas del arbol T tienen pesos equivalentes; es de
ir,
ontienen las mismas
antidades
de nodos. T estara, enton
es, en equilibrio o equilibrado. Re
ursivamente, si todos los
nodos de T estan equilibrados, enton
es la altura de T tiende a O(lg(n)), lo
ual ha
e
545
Captulo 6. Arboles
de b
usqueda equilibrados
546
que el rendimiento de las opera
iones sobre un arbol sea tambien O(lg(n)). Al igual que
o
urre
on las virtudes, los arboles abstra
tos son buenos en la medida en que estos esten
equilibrados.
6.1
Equilibrio de
arboles
En una primera instan
ia, podemos intentar al
anzar un equilibrio basado en la siguiente
deni
ion:
Definici
on 6.1 (Equilibrio fuerte de Wirth [18]) Un arbol binario T es equilibrado
ni T , | | L(ni)| | R(ni)| | 1
546a
546b
Bajo esta deni
ion, podemos dise~nar un primer algoritmo que nos equilibre un arbol.
Nuestro algoritmo utiliza los arboles extendidos (ABBE) expli
ados en x 4.11. Las opera
iones requeridas las in
luimos en el ar
hivo htpl balan
eXt.H 546ai
uya estru
tura es
omo sigue:
htpl balan
eXt.H 546ai
hRutinas de equilibrio 546bi
Una de las ventajas de un ABBE es la opera
ion de sele
ion (x 4.11.1 (pagina 411)).
Mediante esta opera
ion, podramos sele
ionar la
lave del
entro y ha
erla raz del arbol.
El numero de nodos en ambos lados diferira a lo sumo en uno. Si apli
amos este pro
edimiento re
ursivamente obtenemos un arbol equilibrado segun Wirth.
Requerimos una fun
ion que sele
ione un nodo segun su posi
ion inja y lo suba hasta
la raz. Tal opera
ion se dene
omo sigue:
hRutinas de equilibrio 546bi
(546a) 547
template <class Node> inline
Node * select_gotoup_root(Node * root, const size_t & i)
{
if (i >= COUNT(root))
throw std::out_of_range ("");
if (i == COUNT(LLINK(root)))
return root;
if (i < COUNT(LLINK(root)))
{
LLINK(root) = select_gotoup_root(LLINK(root), i);
root = rotate_to_right_xt(root);
}
else
{
RLINK(root) =
select_gotoup_root(RLINK(root), i - COUNT(LLINK(root)) - 1);
root = rotate_to_left_xt(root);
}
return root;
}
Denes:
6.1. Equilibrio de
arboles
547
select gotoup root() sele
iona el i-esimo nodo del arbol
on raz root y lo sube hasta la
raz. Si tomamos un arbol de n nodos, sele
ionamos el nodo
orrespondiente a la posi
ion
n/2 y lo subimos hasta la raz, enton
es, a nivel del nodo n/2, la diferen
ia de nodos
547
entre su subarbol izquierdo y dere
ho es a lo sumo uno. Si apli
amos el mismo prin
ipio
re
ursivamente, dedu
imos el algoritmo siguiente;
hRutinas de equilibrio 546bi+
(546a) 546b
template <class Node> inline
Node * balance_tree(Node * root)
{
if (COUNT(root) <= 1)
return root;
select gotoup root() puede realizarse mediante la parti
ion por posi
ion expli
ada
en x 4.11.7 (pagina 415). Esto es delegado a ejer
i
io.
Captulo 6. Arboles
de b
usqueda equilibrados
548
1
si n 1
.
2T (n/2) + O(lg n) si n > 1
T (2k)
2k
i=1
k
X
i
= 1+
=
2i
T (n)
n
i=1
k
X
i
T (n) = n + n
.
2i
(6.1)
i=1
k+1
2k+1
Pk
i
i=1 2i ,
la
k
X
i
=
2i
i=1
k
X
i=0
k
X
i+1
2i+1
k
X
1
=
+
2i+1
2i+1
|i=0{z } i=0
Sk
2
Sk
2
k
X
1
k+1
=
k+1 .
2i+1
2
|i=0{z }
(6.2)
Qk
k
X
1
1
1
1
= + 2 + + k+1 .
i+1
2
2 2
2
i=0
(6.3)
6.1. Equilibrio de
arboles
549
1
1
1
+ 2 + + k .
2 2
2
(6.4)
Ahora restamos (6.4) menos (6.3) lo que nos da
omo solu
ion de (6.3):
1
;
2k+1
(6.5)
k+1
k+2
1
k =2 k
k
2
2
2
(6.6)
Qk = 1
(6.7)
tendientes a restable
er el equilibrio. Los arboles obtenidos, ofre
en un tiempo esperado de O(lg n) para todas las opera
iones.
De esta
lase de equilibrio estudiaremos dos estru
turas: los arboles aleatorizados y
los treaps.
Bajo este tipo de equilibrio estudiaremos dos estru
turas: los arboles AVL y los
arboles rojo-negros.
Equilibrio amortizado: Este enfoque onsiste en eje utar opera iones espe iales
durante la eje
u
ion de
ualquier opera
ion de manera tal que el arbol tienda a estar
equilibrado. Bajo esta
on
ep
ion, se garantiza un
oste amortizado de O(p lg n)
para p opera
iones su
esivas sobre el arbol.
En este grupo, la uni
a te
ni
a amortizada
ono
ida para equilibrar arboles es el
desplegado (splay), empleada en los arboles splay.
Captulo 6. Arboles
de b
usqueda equilibrados
550
Arboles
aleatorizados
6.2
Definici
on 6.2 (Arbol
binario de b
usqueda aleatorio (ABBA)) Sea T un arbol
binario de busqueda
on
ardinalidad |T | = n.
Si n = 0, enton
es T = es un ABBA.
P(| L(T )| = i | |T | = n) =
1
,
n
0 i < n, n > 0
(6.8)
550
Antes de expli
ar los algoritmos, requerimos algunas bases para un TAD que nos modele
un ABBA. Tal TAD se denomina Gen Rand Tree<Key> y se en
ontrara en el ar
hivo
htpl rand tree.H 550i
uya estru
tura fundamental es la siguiente:
htpl rand tree.H 550i
template <template <typename> class NodeType, typename Key, class Compare>
class Gen_Rand_Tree
{
public:
typedef NodeType<Key> Node;
private:
hmiembros
public:
hmiembros
};
h
lases
Denes:
6.2. Arboles
aleatorizados
551a
551
El primer paso para implantar Gen Rand Tree<Key> es denir la estru
tura del nodo
que
onforma un ABBA:
htpl randNode.H 551ai
Internamente, el TAD Gen Rand Tree<Key> trabaja
on un nodo
entinela espe
ial
omo nodo nulo. El
entinela ama
ena el valor
ero
omo
ardinalidad del arbol. El metodo
publi
o remove() o
ulta la raz del arbol y utiliza el valor tradi
ional NULL.
La
lase Gen Rand Tree<Key> requiere un generador de numeros pseudo aleatorios.
Para ello, nos valemos de la bibliote
a gsl , usaremos el generador \tornado" (\Twisted"),
ono
ido a
tualmente
omo el mejor generador
ono
ido de numeros seudo-aleatorios [11,
10, 12. Un puntero al objeto gsl, generador de numeros aleatorios, puede obtenerse mediante:
hmiembros p
ubli
os de Gen Rand Tree<Key> 551bi
(550) 552a
1
551b
551
551d
6.2.1.1
Inserci
on en un ABBA
La idea basi
a del algoritmo de inser
ion es efe
tuar de
isiones aleatorias, en fun
ion de
la deni
ion 6.2, que determinen si el nodo a insertar deviene o no raz y, de ese modo,
1 Gnu Scientific Library
[6.
Captulo 6. Arboles
de b
usqueda equilibrados
552
552a
garanti
en que el arbol binario resultante sea aleatorio. Para ello, presentamos el algoritmo
siguiente:
hmiembros p
ubli
os de Gen Rand Tree<Key> 551bi+
(550) 551b 553a
Node * random_insert(Node * root, Node * node)
{
if (root == Node::NullPtr)
return node;
const long & n = COUNT(root);
hGenere
552b
El punto
entral de la inser
ion es el sorteo efe
tuado en hGenere rn aleatorio entre 0
y n 552bi, el
ual, en
onsona
on la deni
ion 6.2, de
ide si el nodo a insertar devendra
raz o no.
hGenere rn aleatorio entre 0 y n 552bi
(552a)
const size_t rn = gsl_rng_uniform_int(r, n + 1);
6.2. Arboles
aleatorizados
553a
553
6.2.1.2
553b
Eliminaci
on en un ABBA
En la elimina
ion
lasi
a en un arbol binario de busqueda, la de
ision de es
oger la raz resultante de la union ex
lusiva siempre es la raz del sub-arbol
izquierdo (x 4.11.8 (pagina 416)). Esto introdu
e un sesgo en el equilibrio probabilsti
o en
ontra de la aleatoriedad. Sortear uniformemente
ual de las ra
es -izquierda o dere
hadebe reemplazarse no fun
iona, pues no se pondera la
antidad de nodos que tenga
ada
rama.
El tru
o para que la union ex
lusiva produz
a un arbol aleatorio es sortear, en fun
ion
de las
ardinalidades,
ual de las ra
es devendra la raz del resultado. Esta idea se plasma
en el algoritmo siguiente:
hmiembros p
ubli
os de Gen Rand Tree<Key> 551bi+
(550) 553a 554b
Node * random_join_exclusive(Node * tl, Node * tr)
{
if (tl == Node::NullPtr)
return tr;
if (tr == Node::NullPtr)
return tl;
const size_t & m = COUNT(tl);
const size_t & n = COUNT(tr);
hGenere
if (rn <= m)
{
// rama izquierda gana sorteo
COUNT(tl) += COUNT(tr);
RLINK(tl) = random_join_exclusive(RLINK(tl), tr);
return tl;
}
else
Captulo 6. Arboles
de b
usqueda equilibrados
554
{
COUNT(tr) += COUNT(tl);
LLINK(tr) = random_join_exclusive(tl, LLINK(tr));
return tr;
}
}
Denes:
554a
554b
(553b)
Una vez realizada la union ex
lusiva aleatorizada, la elimina
ion aleatoria en arbol
binario de busqueda
on rangos es estru
turalmente identi
a a la de un ABB:
hmiembros p
ubli
os de Gen Rand Tree<Key> 551bi+
(550) 553b 555
Node * random_remove(Node *& root, const Key & key)
{
if (root == Node::NullPtr)
return Node::NullPtr;
Node * ret_val;
if (Compare() (key, KEY(root)))
{
ret_val = random_remove(LLINK(root), key);
if (ret_val != Node::NullPtr)
COUNT(root)--;
return ret_val;
}
else if (Compare() (KEY(root), key))
{
ret_val = random_remove(RLINK(root), key);
if (ret_val != Node::NullPtr)
COUNT(root)--;
return ret_val;
}
// clave encontrada
ret_val = root;
root = random_join_exclusive(LLINK(root), RLINK(root));
ret_val->reset();
return ret_val;
}
Denes:
random remove, used in
hunk 555.
Uses LLINK 296 and RLINK 296.
6.2. Arboles
aleatorizados
555
555
La logi
a del algoritmo es des
ender re
ursivamente hasta el nodo a eliminar. Una vez
en
ontrado este nodo, se efe
tua una
on
atena
ion \aleatoria" entre la rama izquierda y
la dere
ha, la
ual es el resultado de la elimina
ion.
Culminamos esta se
ion
on la estru
tura de la elimina
ion para uso del
liente:
hmiembros p
ubli
os de Gen Rand Tree<Key> 551bi+
(550) 554b
Node * remove(const Key & key)
{
Node * ret_val = random_remove(tree_root, key);
6.2.2
An
alisis de los
arboles aleatorizados
La idea
entral en este analisis es demostrar que los algoritmos de inser
ion 6.2.1.1 y de
elimina
ion 6.2.1.2, produ
en arboles aleatorios en el sentido de la deni
ion 6.2.
Lema 6.1 (Martnez y Roura 1998 [9]) Sean T< y T> los arboles binarios de
busqueda produ
idos por la llamada split key rec xt(T , x, T<, T>) implantada
segun el algoritmo expli
ado en x 4.11.4 (pagina 413). Si T es un arbol aleatorio, enton
es,
T< y T> son arboles aleatorios independientes.
Demostraci
on (Por inducci
on sobre n = |T |)
n = 0: Si T = , enton
es split key rec xt(T , x, T<, T>) produ
e T< = T> = .
El lema es, pues,
ierto para n = 0.
n > 0: Ahora asumimos que el lema es
ierto para todo n y veri
amos si el lema
es
ierto para n + 1.
1/n
1
P(raiz(T ) = z raiz(T ) < x)
=
=
P(raiz(T ) < x)
m/n
m
Simetri
amente, el mismo razonamiento se apli
a si x < y, inter
ambiando los roles
de T< y T>, respe
tivamente
Captulo 6. Arboles
de b
usqueda equilibrados
556
T<
y
T
y y>x
L(T )
L(T )
R(T )
R(T<)
T>
=
(a) Antes de parti
ionar
El lema 6.1 es util porque la parti
ion es una opera
ion interna de la inser
ion,
uya validez
esta demostrada por la proposi
ion siguiente:
Proposici
on 6.1 (Martnez y Roura 1998 [9])
Sea T un arbol aleatorio y sea p un nodo a insertar
on
lave k. Enton
es, la llamada
insert(T , p) implantada en x 6.2.1.1 produ
e un arbol aleatorio.
Demostraci
on (Por inducci
on sobre n = |T |)
n = 0: Si T = , enton
es insert(, p) = p. Los subarboles L(p) y R(p) son va
os,
los
uales, por deni
ion, son aleatorios. Sobre p se veri
a que P(raiz(p) = k) =
1
1 = |p|
. As pues, insert(, p) = p es un arbol aleatorio y el lema es
ierto para
n = 0.
n > 0: Ahora asumimos que el lema es
ierto para todo n y veri
amos si lo es para
n + 1. Sea T =insert(T , p), sea x = KEY (p) e y = raiz(T ). Antes de insertar
1
x, P(raiz(T ) = y) = n
, pues T es aleatorio.
A partir de estas suposi
iones, podemos distinguir dos
asos despues de la inser
ion:
(1) que y
ontinue
omo raz de T o (2) que x sea la raz de T :
1. Si raiz(T ) = y, enton
es x debe perder en el sorteo node become root realizado
por el algoritmo de inser
ion. La probabilidad de que node become root sea
n
1
falso es n+1
(el
omplemento probabilsti
o de n+1
, que es la probabilidad de
1
n
|{z}
P(raiz(T)=y)
n
+ 1}
|n {z
1
n+1
P(raiz(T )=y)
6.2. Arboles
aleatorizados
557
Por otra parte, T = < T<, x, T> >, donde T< y T> son los arboles resultantes de
random join(T , x, T<, T>) los
uales son, por el lema 6.1, aleatorios
Del resultado anterior podemos estable
er el
orolario siguiente:
Corolario 6.1 Sea K = {k1, k2, . . . , kn} un
onjunto de
laves
ualquiera. Sea P =
xi1 , . . . , xin
ualquier permuta
ion de las
laves en K. Enton
es, el arbol obtenido despues
de insertar las
laves en el orden de la permuta
ion P, segun el algoritmo desarrollado
en x 6.2.1.1, es un arbol aleatorio.
es un arbol aleatorio.
Demostraci
on (por inducci
on sobre m = |T<| y n = |T>|)
m = 0 o n = 0: Bajo este predi
ado, tenemos dos
asos:
L(T<)
T>
558
Captulo 6. Arboles
de b
usqueda equilibrados
Por la estru
tura del algoritmo y el resultado del sorteo, la rama izquierda de T es
L(T ), mientras que la dere
ha es la llamada re
ursiva random join exclusive(R(T<), T>).
Ahora bien, L(T ) es un arbol aleatorio pues, por premisa del lema, T tambien lo es.
Igualmente, por la hipotesis indu
tiva, T> tambien es aleatorio. L(T ) y T> son independientes, pues L(T ) y R(T ) son, por premisa del lema, independientes y T> =
random join exclusive(R(T<), T>) es, por la hipotesis indu
tiva, independiente.
Habiendo demostrado que las ramas de T son aleatorias e independientes, solo nos
1
resta demostrar que x T< , P(raiz(T ) = x) = m+n
. Esto es:
P(raiz(T ) = x) = P(raiz(L(T )) = x) P(l is winner) =
m
1
1
=
m m+n
m+n
Con este lema, estamos en
apa
idad de estudiar la elimina
ion, la
ual es analizada
en la proposi
ion siguiente:
Proposici
on 6.2 (Martnez y Roura 1998 [9]) Sea T un arbol aleatorio y x una
lave
ontenida en T . Sea la llamada a remove(T , x) y sea T el arbol aleatorio resultante
despues de la llamada a remove(). Enton
es, T es un arbol aleatorio.
Demostraci
on (por inducci
on sobre n = |T |)
Si n = 1, enton
es T = , el
ual es, por deni
ion, aleatorio.
Si n > 1, enton
es asumimos que el lema es
ierto para todo |T | < n y veri
amos si
tambien es
ierto para n. Aqu podemos separar dos
asos:
=
n1 n
n1
n
n1
Segun el
orolario anterior,
ualquiera sea la se
uen
ia de inser
ion,
on elimina
iones
inter
aladas arbitrarias, el arbol resultante siempre es aleatorio. Podemos, pues,
on
luir
que en promedio la altura del arbol aleatorizado es O(lg n). Puesto que el tiempo de las
6.3. Treaps
559
6.3
Treaps
Ahora estudiaremos otro enfoque de aleatoriza
ion radi
almente diferente a un ABBA: la
estru
tura treap. Un treap es un ABB
on un
ampo adi
ional en
ada nodo denominado
prioridad segun la siguiente deni
ion:
Definici
on 6.3 (Treap) Sea T un arbol binario en
ual
ada nodo ni tiene dos
ampos
a saber:
Captulo 6. Arboles
de b
usqueda equilibrados
560
La segunda propiedad es denomina rela
ion del an
estro. Si las prioridades son
uni
as, es de
ir, si estas no se repiten, enton
es el treap es denominado \puro".
La primera propiedad
orresponde a la de orden de un ABB; la segunda a la de orden
de un heap. De all, pues, el nombre \treap": tree y heap.
48
443
11
519
92
450
6
545
3
769
22
544
10
731
9
736
13
720
12
782
17
724
62
587
33
589
93
686
54
589
39
636
41
764
73
635
60
623
57
781
100
692
68
693
65
723
72
818
81
637
80
640
74
705
97
753
88
746
84
758
Figura 6.3: Ejemplo de treap. Campos superiores representan las
laves; inferiores prioridades
Podemos
ara
terizar un treap
omo una se
uen
ia de pares (
lave, prioridad)
orrespondiente a los
ontenidos de
ada nodo. Por ejemplo, el treap de la gura 6.3 puede
ara
terizarse
omo: (3, 769), (6, 545), (9, 736), (10, 731), (11, 519), (12, 782), (13, 720),
(17, 724), (22, 544), (33, 589), (39, 636), (41, 764), (48, 443), (54, 589), (57, 781), (60, 623),
(62, 587), (65, 723), (68, 693), (72, 818), (73, 635), (74, 705), (80, 640), (81, 637), (84, 758),
(88, 746), (92, 450), (93, 686), (97, 753), (100, 692)
Un treap tiene una propiedad muy interesante
ara
terizada por el siguiente lema:
Lema 6.3 (Lema de la unicidad del treap (Seidel - Aragon 1996) [14]) Sea K =
{k1, k2, . . . kn} un
onjunto de
laves y P = {p1, p2, . . . pn} un
onjunto de prioridades.
Enton
es, el
onjunto {(k1, p1), (k2, p2), . . . (kn, pn), } K P tiene un uni
o treap.
Demostraci
on (Por inducci
on sobre n)
n = 1: En este
aso existe un u
ni
o treap
onformado por el par. El lema es
ierto
para n = 1.
n > 1: Ahora asumimos que el lema es
ierto para todo n y veri
amos su vera
idad
para n + 1. Puesto que las prioridades son uni
as, el treap de n + 1 nodos solo puede
6.3. Treaps
561
tener una raz
uya prioridad es la menor de todas. Sea (ki, pi) el nodo raz del
treap. Una vez determinada la raz del treap y a
ausa de la propiedad de orden de
un ABB, el
onjunto de nodos de las ramas izquierda y dere
ha queda determinado en
K< = {(k1, p1), . . . , (Ki1, pi1)} y K> = {(ki+1, pi+1), . . . , (kn, pn)}, respe
tivamente.
Por la hipotesis indu
tiva, K< y K>,
uyas
ardinalidades son inferiores a n+1, tienen
treaps uni
os. El lema es, pues,
ierto para todo n
>En que
onsiste la aleatoriedad de un treap? La respuesta se en
uentra en la sele
ion
de la prioridad. Cuando se
rea un nuevo nodo, se sele
iona la prioridad aleatoriamente, se
inserta el nodo segun el algoritmo
lasi
o de inser
ion en ABB y las viola
iones eventuales
a la rela
ion del an
estro se
orrigen mediante rota
iones.
6.3.1
561
El TAD Treap<Key>
hVeri
a
i
on de treap (never dened)i
Denes:
PRIO, used in
hunks 563{65.
TreapNode, used in
hunk 562d.
Uses DECLARE BINNODE SENTINEL 293 and SentinelCtor.
Aqu se dene el nodo binario TreapNode<Key> que alma
ena una prioridad a
edida
mediante el metodo getPriority() o el ma
ro PRIO. Notemos que el ar
hivo exporta
Captulo 6. Arboles
de b
usqueda equilibrados
562
562a
dos
onstantes Min Priority y Max Priority que
orresponden a los valores mnimo y
maximo que puede tener una prioridad.
Un TreapNode<Key> tiene un valor de Node::NullPtr apuntado a un
entinela
on
prioridad maxima. Este nodo funge de
entinela para detener rota
iones des
endentes
ha
ia hojas del arbol.
Nuestra implanta
ion del treap requiere de un nodo
abe
era padre de la raz. Tal nodo
tambien funge de
entinela para detener rota
iones as
endentes ha
ia la raz.
Ahora pro
edemos a desarrollar el TAD Treap<Key>:
htpl treap.H 562ai
template <template <typename> class NodeType, typename Key, class Compare>
class Gen_Treap
{
hMiembros de Gen Treap<Key> 562bi
};
hClases
Denes:
562b
Al igual que el TAD Rand Tree<Key>, Gen Treap<Key> usa por omision el generador
de numeros aleatorios implantado por Donald Knuth, expli
ado en [8 y denido en el
ar
hivo tpl ran array.H.
Antes de implantar los
onstru
tores, es ne
esario denir los atributos internos de
Gen Treap<Key>:
hMiembros de Gen Treap<Key> 562bi
(562a) 562
Node
Node *
Node *&
head;
head_ptr;
tree_root;
Un treap requiere una fun
ion que genere numeros aleatorios
orrespondientes a las
prioridades. Para ello, nos valemos de la bibliote
a gsl , usaremos el generador \tornado" (\Twisted"),
ono
ido a
tualmente
omo el mejor generador
ono
ido de numeros
seudo-aleatorios [11, 10, 12:
hMiembros de Gen Treap<Key> 562bi+
(562a) 562b 564a
2
562
gsl_rng * r;
562d
La
lase Gen Treap<Key> no es para uso dire
to del
liente, pues su rol es implantar
un treap generi
o que sea independiente de que sus nodos sean virtuales o no. El
liente
debe usar alguna de las siguientes
lases:
hClases p
ubli
as de Gen Treap<Key> 562di
(562a)
template <typename Key, class Compare = Aleph::less<Key> >
class Treap : public Gen_Treap<TreapNode, Key, Compare>
{ /* empty */ };
[6.
6.3. Treaps
563
{ /* empty */ };
Denes:
Treap, never used.
Treap Vtl, used in
hunks 424a and 426b.
Uses Gen Treap 562a and TreapNode 561.
6.3.2
Inserci
on en un treap
Con
eptualmente, la inser
ion de un nodo p puede
ara
terizarse en dos prin
ipales partes:
1. Efe
tuar una inser
ion
omo en ABB; es de
ir, substituir el nodo externo por p.
2. Rotar p, de forma que este suba de nivel, hasta que su prioridad no viole la rela
ion
an
estral.
563
Node * insertion_result;
if (Compare() (KEY(p), KEY(root)))
{
insertion_result = insert(LLINK(root), p);
if (insertion_result == Node::NullPtr)
return Node::NullPtr;
LLINK(root) = insertion_result;
if (PRIO(insertion_result) < PRIO(root))
return rotate_to_right(root);
else
return root;
}
else if (Compare() (KEY(root), KEY(p)))
{
insertion_result = insert(RLINK(root), p);
if (insertion_result == Node::NullPtr)
return Node::NullPtr;
RLINK(root) = insertion_result;
if (PRIO(insertion_result) < PRIO(root))
return rotate_to_left(root);
else
return root;
}
Captulo 6. Arboles
de b
usqueda equilibrados
564
return Node::NullPtr;
}
Uses LLINK 296, PRIO 561, RLINK 296, rotate to left, and rotate to right 419.
insert() efe
t
ua una busqueda re
ursiva de la
lave
ontenida en el nodo p. Si se en
uentra
un nodo
on tal
lave, enton
es se retorna Node::NullPtr ; este es el indi
ativo en la
564a
se
uen
ia de llamadas re
ursivas que se~nala que no debe realizarse la inser
ion y se dete
ta
mediante el predi
ado insertion result == Node::NullPtr.
Si, por el
ontrario, no se en
uentra la
lave, enton
es la re
ursion se detiene en el nodo
externo donde ini
ialmente se inserta el nodo. La se
uen
ia re
ursiva de retornos veri
a
si hay una viola
ion de la rela
ion an
estral, la
ual eventualmente es
orregida
on una
rota
ion.
La version anterior de insert() es un miembro privado, estati
o,
uyo rol es realizar
la inser
ion y
orregir las viola
iones de la rela
ion an
estral. La sele
ion aleatoria de la
prioridad y la invo
a
ion ini
ial al metodo re
ursivo son realizados por la version publi
a
de insert()
uya deni
ion es
omo sigue:
hMiembros de Gen Treap<Key> 562bi+
(562a) 562
564b
Node * insert(Node * p)
{
I(p != Node::NullPtr);
PRIO(p) = gsl_rng_get(r);
Node * result = insert(tree_root, p);
if (result == Node::NullPtr)
return NULL;
tree_root = result;
return p;
}
Uses PRIO 561.
6.3.3
564b
Eliminaci
on en treap
La elimina
ion es
on
eptualmente mas simple que la
lasi
a en un ABB. Primero se bus
a
el nodo
on la
lave de interes. Una vez ubi
ado, el nodo se rota de manera que su hijo de
menor prioridad suba de nivel. El pro
eso
ontinua hasta que el nodo a eliminar devenga
hoja; en ese momento el nodo se substituye por Node::NullPtr .
Sorprendentemente, una version iterativa de la elimina
ion es fa
ilmente realizable, la
ual se estru
tura en dos partes que se presentan
omo sigue:
hMiembros de Gen Treap<Key> 562bi+
(562a) 564a
Node * remove(const Key & key)
{
hBus
ar nodo a eliminar p 565ai
if (p == Node::NullPtr)
return NULL; // clave no fue encontrada
6.3. Treaps
hRotar
565
p->reset();
return p;
}
565a
565b
Este bloque de
lara dos variables. La variable p guarda la dire
ion del nodo a suprimir.
Puesto que en la segunda fase el nodo p sera rotado hasta que devenga hoja, es ne
esario,
en prin
ipio, guardar la dire
ion del nodo padre de tal que manera este se a
tuali
e en
ada rota
ion. Como el algoritmo es iterativo, es preferible guardar la dire
ion de la
elda
dentro del nodo padre que apunta a p; tal apuntador es mantiene en la variable pp.
El segundo bloque, hRotar p hasta que devenga hoja 565bi se espe
i
a del siguiente
modo:
hRotar p hasta que devenga hoja 565bi
(564b)
while (hp no sea hoja 566i)
if (PRIO(LLINK(p)) < PRIO(RLINK(p)))
{
*pp = rotate_to_right(p);
pp = &RLINK(*pp);
}
else
{
*pp = rotate_to_left(p);
pp = &LLINK(*pp);
}
*pp = Node::NullPtr;
Uses LLINK 296, PRIO 561, RLINK 296, rotate to left, and rotate to right 419.
Captulo 6. Arboles
de b
usqueda equilibrados
566
5
8
5
8
1
70
1
70
38
26
0
24
12
28
12
28
0
24
8
38
38
26
39
30
8
38
28
54
28
54
40
36
39
30
21
69
21
69
64
97
22
89
40
36
22
89
64
42
43
81
43
81
42
42
42
97
(a)
(b)
5
8
5
8
1
70
0
24
12
28
1
70
8
38
39
30
38
26
0
24
39
30
28
54
64
42
21
69
43
81
( )
8
38
40
36
28
54
22
89
12
28
21
69
40
36
38
26
64
42
22
89
42
97
43
81
42
97
(d)
Figura 6.4: Ejemplo de elimina
ion de la
lave 38 en un treap. La posi
ion nal del 38
antes de suprimirlo es la misma que la posi
ion ini
ial de inser
ion.
566
La ultima lnea es la que nalmente elimina el nodo p al asignarle a la
elda del nodo que
apunta a p el nodo externo Node::NullPtr .
El predi
ado hp no sea hoja 566i
onsiste en veri
ar si los subarboles de p son externos.
Esto se realiza de la siguiente manera:
hp no sea hoja 566i
(565b)
not (LLINK(p) == Node::NullPtr and RLINK(p) == Node::NullPtr)
Uses LLINK 296 and RLINK 296.
6.3. Treaps
567
inser
ion
omienza por
olo
ar el nuevo nodo
omo hoja. Si de
idimos eliminar una
lave
y luego insertarla, la hoja del nodo de inser
ion es exa
tamente la misma que
uando fue
eliminada. Un ejemplo de esta situa
ion se ilustra
on el nodo 38 de la gura 6.4.
Intuitivamente este fenomeno es eviden
iado por el lema de la uni
idad del treap
(lema 6.3). Para que el treap sea uni
o a traves de todas las modi
a
iones, la inser
ion y
elimina
ion siempre deben ver el mismo treap.
6.3.4
An
alisis de los treaps
En analisis de los treaps requiere estable
er una propiedad muy aso
iada a la aleatoriedad:
la propiedad de ausen
ia de memoria en las de
isiones aleatorias. Tal propiedad esta dada
por la siguiente proposi
ion:
Proposici
on 6.3 Un treap T es un arbol binario aleatorio.
En otras palabras, un treap es equivalente a un ABB
onstruido a partir de una se
uen
ia de inser
ion aleatoria.
Demostraci
on
Puesto que las prioridades son sele
ionadas aleatoria e independientemente, podemos
asumir que todas las prioridades son es
ogidas antes de que se ini
ie la se
uen
ia de
inser
ion.
Sea K = {k1, k2, . . . , kn} una se
uen
ia de inser
ion
ualquiera y sea P = {p1, p2, . . . , pn}
un
onjunto de prioridades sele
ionadas aleatoria e independientemente que son asignadas
a las
laves K tal que
ada par (ki, pi)
ompondra un nodo del treap.
Por el lema 6.3, sabemos que dadas las
laves y sus prioridades el treap es uni
o. Esto
impli
a que el orden de inser
ion no afe
ta el treap resultante. De este modo, sin perdida
de generalidad, podemos asumir una se
uen
ia de inser
ion SABB que va desde la menor
hasta la mayor prioridad de la
ual podemos realizar las siguientes observa
iones:
1. Respe
to al
onjunto de
laves K, la probabilidad de que SABB sea el valor de una
1
permuta
ion espe
a es |K|!
, pues el orden de la permuta
ion esta dado por el
orden de las prioridades que son asignadas aleatoriamente a
ada
lave. Por tanto,
la se
uen
ia SABB ordenada por prioridad es aleatoria, pues las prioridades fueron
es
ogidas aleatoria e independientemente.
2. SABB no
ausa rota
iones en el treap, pues
ada nodo es insertado
omo hoja y,
puesto que fue insertado por prioridad as
endente, la rela
ion an
estral jamas es
violada. De lo anterior, podemos
on
luir que la inser
ion de SABB o
urre de la
misma manera que en un ABB.
Puesto que SABB es una se
uen
ia aleatoria y que la inser
ion o
urre
omo en un
ABB, podemos
on
luir que un treap es equivalente a un ABB
onstruido a partir de una
se
uen
ia de inser
ion aleatoria
La proposi
ion anterior nos garantiza que el tiempo esperado para la busqueda en un
treap es O(lg n). >Que a
er
a de las inser
iones y supresiones? Observemos que ambas
opera
iones estan dominadas por la altura del arbol y que estas resultan en treaps. La
antidad maxima de rota
iones tambien es fun
ion de la altura. De esta manera, podemos
on
luir que las opera
iones de inser
ion, busqueda y elimina
ion son O(lg n).
Captulo 6. Arboles
de b
usqueda equilibrados
568
Prioridades implcitas
6.4
Arboles
AVL
Un arbol AVL, o un AAVL, es una
lase espe
ial de arbol binario de busqueda denido
omo sigue:
Definici
on 6.4 (Arbol
AVL) Sea T un arbol binario de b
usqueda. Se di
e que T es AVL
si y solo si:
ni T,
1 h(L(ni)) h(R(ni)) 1
(6.9)
Es de
ir, para todo nodo, la diferen
ia de altura entre la rama izquierda y dere
ha no debe
ex
eder de la unidad.
Para futuros dis
ursos, la diferen
ia de altura de un nodo ni se denomina (ni) =
h(R(ni)) h(L(ni)). En
ontextos de
odigo, (ni) es denota
omo DIFF(n).
La
ondi
ion (6.9) se denomina
ondi
ion AVL y es menos fuerte que el equilibrio
fuerte de Wirth denido en x 6.1. La idea es perder un po
o de equilibrio en aras de
algoritmos mas e
ientes que garanti
en la
ondi
ion AVL. Mas adelante demostraremos
que la altura de un AAVL esta logartmi
amente a
otada. La ganan
ia estriba, enton
es,
en lograr algoritmos de modi
a
ion logartmi
os. Esto es fa
tible si alma
enamos en
ada
nodo informa
ion sobre el equilibrio.
Se
ono
en dos formas de implantar un AAVL. La primera, que es la mas popular y
e
iente, pero tambien la mas dif
il,
onsiste en alma
enar en
ada nodo la diferen
ia de
6.4. Arboles
AVL
569
569
alturas. Para ello solo se requieren dos bits, los
uales, bajo los requerimientos de alinea
ion
de palabra impuestos por las arquite
turas modernas, deben extenderse hasta la longitud
de la palabra de la arquite
tura.
La segunda implanta
ion
onsiste en alma
enar en
ada nodo su altura. Puesto que el
arbol es logartmi
amente a
otado, un byte es su
iente. Esta alternativa
ondu
e a algoritmos mas sen
illos, pero menos e
iente, pues,
uando se modi
a el arbol, se requieren
a
tualizar mas nodos
on su altura.
En este texto adoptaremos el primer estilo de implanta
ion; es de
ir, alma
enaremos la
diferen
ia de alturas en
ada nodo. La estru
tura de un nodo AVL se dene en el ar
hivo
havlNode.H 569i, el
ual tiene la estru
tura siguiente:
havlNode.H 569i
# ifndef AVLNODE_H
# define AVLNODE_H
# include <tpl_binNode.H>
class AvlNode_Data
{
private:
signed char diff; // diferencia de altura
public:
AvlNode_Data() : diff(0) { /* EMPTY */ }
signed char & getDiff() { return diff; }
void reset() { diff = 0; }
};
DECLARE_BINNODE(AvlNode, 40, AvlNode_Data);
# define DIFF(p) ((p)->getDiff())
hutilitarios
avl 570ai
# endif // AVLNODE_H
Denes:
Captulo 6. Arboles
de b
usqueda equilibrados
570
Como se apre
ia, usamos un byte signado para alma
enar la diferen
ia de alturas, el
ual
denominaremos diferen
ia o fa
tor. Como ya lo se~nalamos, este byte puede extenderse
segun la alinea
ion de palabra del
omputador. Por ejemplo, si la
lave del nodo o
upa 4
bytes, la informa
ion adi
ional 10 bytes y tenemos una arquite
tura de 32 bits,
ada nodo
o
upa 16 bytes y no
4 + 10 +
1
|{z}
= 15 ,
sizeof(signed har)
570a
6.4.1
570b
El TAD Gen Avl Tree<Key> dene una
lase generi
a de arbol AVLindependiente de que
el nodo sea virtual o no. La
lase en
uestion se dene en el ar
hivo htpl avl.H 570bi, que
se dene a
ontinua
ion:
htpl avl.H 570bi
template <template <typename> class NodeType, typename Key, class Compare>
class Gen_Avl_Tree
{
typedef NodeType<Key> Node;
hmiembros privados de Gen Avl Tree<Key> 571ai
ubli
os de Gen Avl Tree<Key> 572ai
hmiembros p
};
template <typename Key, class Compare = Aleph::less<Key> >
6.4. Arboles
AVL
571
571a
Las
lases Avl Tree Vtl<Key> y Avl Tree<Key> denen arboles que manejan nodos
virtuales y normales respe
tivamente.
Por lo general, los algoritmos sobre arboles son naturalmente expresables en forma
re
ursiva. Ex
ep
ionalmente, los arboles AVL no en
ajan en esta
ategora. Hasta la fe
ha
de impresion de este texto, nadie ha reportado una implanta
ion de arbol AVL re
ursiva
y e
iente. Las implanta
iones re
ursivas que se
ono
en estan basadas en alma
enar
en
ada nodo su altura y a
tualizarla re
ursivamente en
ada modi
a
ion. En nuestro
aso, insistiremos en una implanta
ion e
iente, por lo
ual utilizaremos una pila que se
espe
i
a
omo sigue:
hmiembros privados de Gen Avl Tree<Key> 571ai
(570b) 571b
FixedStack<Node *, Node::MaxHeight> avl_stack;
Denes:
avl stack, used in
hunks 571
, 572b, 576{79, and 582.
Uses FixedStack 131a.
571b
eliminado. Puesto que la altura de un AAVL esta a
otada, la pila no requiere veri
a
iones
de desborde.
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 571a 571
Node
Node *
Node *&
Compare
head_node;
head_ptr;
root;
cmp;
head node es un nodo auxiliar que sera padre del nodo raz. head ptr es un apuntador
571
al nodo auxiliar. El uso del nodo
abe
era es muy util para generalizar las rota
iones. Si
no usasemos esta
abe
era, enton
es la rota
ion de la raz debera tratarse separadamente.
head ptr siempre estara insertado en la pila. A efe
tos algortmi
os, se
onsiderara que
la pila esta va
a
uando esta solo
ontenga el nodo
abe
era. Planteamos, enton
es, las
siguientes fun
iones:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 571b 572b
bool avl_stack_empty() { return avl_stack.top() == head_ptr; }
avl stack empty() retorna
ierto si avl stack esta logi
amente va
a. clean avl stack()
limpia la pila; es de
ir, extrae todos sus elementos.
Captulo 6. Arboles
de b
usqueda equilibrados
572
Por
onven
ion, la raz root sera la hija dere
ha del nodo
abe
era head ptr. Notemos
que root es una referen
ia a RLINK (head ptr) y no un puntero. Este ardid nos permite
trabajar dire
tamente
on RLINK (head ptr) mediante el nombre root.
6.4.1.1
572a
Inserci
on en un
arbol AVL
Consideremos un AAVL en el
ual se realiza una inser
ion
lasi
a en un ABB; es de
ir,
el nodo se inserta
omo hoja en el lugar o
upado por el nodo externo resultado de la
busqueda. Tal inser
ion la realiza el algoritmo siguiente:
hmiembros p
ubli
os de Gen Avl Tree<Key> 572ai
(570b) 578a
Node * insert(Node * p)
{
if (root == Node::NullPtr)
{
root = p;
return p;
}
572b
La fun
ion search and stack avl() bus
a la
lave
ontenida en p y a la vez guarda todo
el
amino de busqueda en la pila avl stack:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 571
575a
Node * search_and_stack_avl(const Key &
{
Node * p = root;
key)
do // desciende en b
usqueda de key y empila el camino de b
usqueda
{
avl_stack.push(p);
if (cmp(key, KEY(p)))
p = LLINK(p);
else if (cmp(KEY(p), key))
6.4. Arboles
AVL
573
p = RLINK(p);
else
return p;
}
while (p != Node::NullPtr);
return avl_stack.top();
}
Denes:
Si KEY(p) ya se en
uentra en el arbol, enton
es search and stack avl() retorna el nodo
que
ontiene la
lave. La dupli
a
ion se dete
ta en el primer if. En
aso
ontrario, pp es
el padre del nodo a insertar p. El nodo p se inserta fsi
amente y se a
tualiza el fa
tor de
balan
e de pp.
Restauraci
on de la condici
on AVL
Despues de la inser
ion fsi
a, se veri
a si o
urre una viola
ion de la
ondi
ion AVL, la
ual se
ara
teriza en dos
asos generales. El primero de ellos se ilustra en la gura 6.7(a).
Ini
ialmente, el nodo y se en
uentra perfe
tamente equilibrado, mientras que su padre
x esta ligeramente desequilibrado ha
ia la dere
ha. O
urre, enton
es, la inser
ion de un
valor mayor que y, el
ual ha
e que la rama aumente de altura. El nodo x sufre un
desequilibrio que viola la
ondi
ion AVL.
T
x
2
T
y
0
y
1
x
0
h+1
h+1
=
(a)
(b)
Captulo 6. Arboles
de b
usqueda equilibrados
574
T
z
0
y
1
z
1
x
0
y
1
h
=
(a)
(b)
6.4. Arboles
AVL
575a
575
Los
asos presentados en las guras 6.7(a) y 6.8(a), junto
on sus equivalentes
simetri
os,
ara
terizan situa
iones de viola
ion de la
ondi
ion AVL que o
urren durante
la inser
ion. Clasi
aremos las a
iones de
orre
ion en el siguiente rango:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 572b 575b
enum Rotation_Type
{
ROTATE_LEFT,
ROTATE_RIGHT,
DOUBLE_ROTATE_LEFT,
DOUBLE_ROTATE_RIGHT
};
Denes:
DOUBLE ROTATE LEFT, used in
hunk 575.
DOUBLE ROTATE RIGHT, used in
hunk 575.
ROTATE LEFT, used in
hunk 575.
ROTATE RIGHT, used in
hunk 575.
575b
Este tipo de valor es devuelto por una rutina que examina un nodo desequilibrado y
determina, en fun
ion del tipo de desequilibrio,
ual
lase de rota
ion debe ser apli
ada:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 575a 575
static Rotation_Type rotation_type(Node * p)
{
Node * pc; // ps child
if (DIFF(p) == 2) // hacia la izquierda
{
pc = RLINK(p);
if (DIFF(pc) == 1 or DIFF(pc) == 0)
return ROTATE_LEFT;
return DOUBLE_ROTATE_LEFT;
}
pc = LLINK(p);
if (DIFF(pc) == -1 or DIFF(pc) == 0)
return ROTATE_RIGHT;
return DOUBLE_ROTATE_RIGHT;
}
Denes:
rotation type, used in
hunk 575
.
Uses child 323
, DOUBLE ROTATE LEFT 575a, DOUBLE ROTATE RIGHT 575a, LLINK 296, RLINK 296,
ROTATE LEFT 575a, and ROTATE RIGHT 575a.
575
rotation type() es llamada por una fun
ion de uso general que restaura la
ondi
ion
AVL. Su estru
tura general
orresponde a veri
ar los
uatro tipos de rota
ion en
fun
ion del nodo violatorio de la
ondi
ion AVL y del fa
tor de su hijo. Notemos que
rotation type() efe
t
ua una veri
a
ion adi
ional, DIFF(pc) == 0,
orrespondiente a
otro
aso que se solo se presenta en la elimina
ion y que expli
aremos posteriormente.
Lo anterior propor
iona toda la utilera requerida para restaurar la
ondi
ion AVL en
ualquiera de los
asos de inser
ion:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 575b 576
Captulo 6. Arboles
de b
usqueda equilibrados
576
return
return
return
return
*link
*link
*link
*link
=
=
=
=
rotateLeft(p);
rotateRight(p);
doubleRotateLeft(p);
doubleRotateRight(p);
type");
return NULL;
}
Denes:
576
El metodo restore avl() toma dos nodos
omo parametros. El primero, p, es el nodo que
sufre el desequilibrio; el segundo, pp, es el padre de p. En fun
ion del tipo de desequilibrio
determinado por rotation type(), restore avl() emprende las a
iones ne
esarias para
restaurar la
ondi
ion AVL.
Hasta el presente, hemos desarrollado una serie de rutinas en
argadas de
orregir una
viola
ion de la
ondi
ion AVL en un nodo. Nos resta determinar
ual es el nodo que sufre
el desequilibrio. Para la inser
ion, basta
on memorizar el ultimo nodo desequilibrado en
el
amino de busqueda, pues este es el uni
o sus
eptible de sufrir un desequilibrio. Ubi
ado
este nodo, se a
tualiza su fa
tor y se veri
a si se requiere emprender a
iones
orre
tivas
on el metodo restore avl().
Ahora solo nos resta una rutina que busque dentro de la pila el nodo desequilibrado,
a
tuali
e el fa
tor y, eventualmente, efe
tue la
orre
ion:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 575
579
void restore_avl_after_insertion(const Key & key)
{
Node *pp = avl_stack.pop(); // padre del nodo insertado
hAjustar
if (avl_stack_empty())
return; // pp es ra
z
Node *gpp; // padre de pp
hA
tualizar
an estros de pp 577bi
clean_avl_stack();
}
6.4. Arboles
AVL
577
Denes:
577a
El bloque hAjustar fa
tor del padre del nodo insertado 577ai extrae el primer nodo en
la pila, a
tualiza su fa
tor y veri
a si este no es la raz:
hAjustar fa
tor del padre del nodo insertado 577ai
(576)
if (key > KEY(pp)) // ajuste el factor del padre del nodo insertado
DIFF(pp)++;
else
DIFF(pp)--;
if (DIFF(pp) == 0)
{
// en este caso, altura del ascendiente de pp no aumenta
clean_avl_stack();
return;
}
Uses clean avl stack 571
.
hA tualizar
577b
Si el fa
tor de algun gpp es
ero, enton
es la altura de gpp permane
e igual y, puesto que
la as
enden
ia de gpp es AVL, el resto del arbol es AVL y el algoritmo termina. En
aso
ontrario, se revisa si hay una viola
ion de la
ondi
ion AVL, la
ual se
orrige. En
este ultimo
aso el algoritmo tambien termina, pues sabemos que en la inser
ion el arbol
ompleto es AVL luego de efe
tuar la primera
orre
ion:
hA
tualizar an
estros de pp 577bi
(576)
do
// buscar nodo con factor igual a 0
{
gpp = avl_stack.pop();
6.4.1.2
Eliminaci
on en un
arbol AVL
Estru
turalmente, la elimina
ion se divide en tres pasos: (1) bus
ar el nodo a eliminar, (2)
eliminarlo y (3) revisar la
ondi
ion AVL y eventualmente restaurarla. En ese sentido, el
Captulo 6. Arboles
de b
usqueda equilibrados
578
578a
(570b) 572a
Node * p = search_and_stack_avl(key);
if (no_equals<Key, Compare> (KEY(p), key))
{
// clave no fue encontrada
clean_avl_stack();
return NULL;
}
hElimina
i
on
fsi a de p 578bi
restore_avl_after_deletion(removed_key);
return p;
}
Uses clean avl stack 571
, restore avl after deletion 582, and search and stack avl 572b.
Eliminaci
on fsica del nodo
on fsi
a de p 578bi se fundamenta en el esquema alternativo de elimina
ion
hElimina
i
expli
ado en x 4.9.6-1 (pagina 396), el
ual, re
ordemos,
onsiste en garantizar que el nodo
578b
a eliminar sea in
ompleto y, en el
aso de que sea
ompleto, enton
es sustituirlo por el
su
esor o el prede
esor. En nuestro
aso, optamos por el su
esor y planteamos el siguiente
algoritmo:
hElimina
i
on fsi
a de p 578bi
(578a)
Key removed_key = KEY(p);
Node * pp = avl_stack.top(1); // obtener padre de p
while (true)
{
if (LLINK(p) == Node::NullPtr) // Est
a incompleto por la izquierda?
{
// S
, ate a pp el hijo de p
if (LLINK(pp) == p)
LLINK(pp) = RLINK(p);
else
RLINK(pp) = RLINK(p);
break;
}
if (RLINK(p) == Node::NullPtr) // Est
a incompleto por la izquierda?
{
// S
, ate a pp el hijo de p
if (LLINK(pp) == p)
LLINK(pp) = LLINK(p);
6.4. Arboles
AVL
579
else
RLINK(pp) = LLINK(p);
break;
}
// en este punto, p es un nodo completo ==> intercambiar por sucesor
Node * succ = swapWithSuccessor(p, pp);
removed_key = KEY(succ);
}
p->reset();
if (pp == head_ptr) // verifique si se elimin
o la ra
z
{
// En este caso, los factores quedan inalterados y no se viola
// la condici
on AVL
clean_avl_stack();
return p;
}
Uses avl stack 571a, clean avl stack 571 , LLINK 296, RLINK 296, and S 794 .
579
Si o
urre un inter
ambio del nodo a eliminar
on su su
esor injo, enton
es el nodo
eliminado no sirve para a
tualizar el fa
tor del nodo su
esor, pues existe una viola
ion
temporal de la propiedad de orden de un ABB. Por esta razon, la variable removed key
alma
ena la
lave segun la
ual se a
tualizaran los fa
tores en el
amino de busqueda.
Como su nombre lo expresa, la rutina swapNodeWithSuccessor() inter
ambia p
on
su su
esor injo. A la ex
ep
ion de los aspe
tos pertinentes a los arboles AVL, la siguiente
implanta
ion es estru
turalmente identi
a para la elimina
ion en toda
lase de arbol binario de busqueda que subyaz
a en el inter
ambio del nodo eliminado
on su su
esor (o
prde
esor) injo:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 576 582
Node* swapWithSuccessor(Node * p, Node *& pp)
{
Captulo 6. Arboles
de b
usqueda equilibrados
580
Restauraci
on de la condici
on AVL
La gura 6.9(a) ilustra el primer
aso de desequilibrio
ausado por una elimina
ion.
La gura supone una elimina
ion en la rama que le
ausa una perdida de altura. La
situa
ion es identi
a a la gura 6.7(a) y su
orre
ion similar; es de
ir, una rota
ion ha
ia
la izquierda
uyo resultado, identi
o a la gura 6.7(b), se presenta en la gura 6.9(b).
Dado el nodo en el
ual o
urre la viola
ion, la identi
a
ion del primer
aso es identi
a
a la de la inser
ion; es de
ir, fa
tores
on el mismo signo y una unidad de diferen
ia.
Para este
aso, enton
es, podemos reutilizar la rutina rotation type(). Sin embargo,
a diferen
ia de la inser
ion, el arbol T sufre una perdida general de altura. En efe
to,
antes de la elimina
ion, el arbol T tiene altura general h + 3, mientras que despues de
eliminar la altura general es h + 2. Conse
uentemente, la as
enden
ia de T puede sufrir
un desequilibrio violatorio de la
ondi
ion AVL.
6.4. Arboles
AVL
581
T
x
2
y
1
h+1
y
0
T
x
0
h+1
h+1
=
(a)
(b)
T
z
0
y
1
h
h1
=
(a)
(b)
Captulo 6. Arboles
de b
usqueda equilibrados
582
El ter
er y ultimo
aso de viola
ion
ausado por una elimina
ion se ilustra en la
gura 6.11(a). Aqu la rama pierde altura debido a una elimina
ion o a un ajuste
previo. Este
aso y su solu
ion son pare
idos al primer
aso ilustrado en las guras 6.9(a)
y 6.9(a), respe
tivamente. La diferen
ia estriba en que el nodo y esta equilibrado. Este
aso tambien es unvo
amente identi
able: el hijo del nodo violatorio esta equilibrado y
fue
onsiderada, pero hasta ahora no expli
ada, en el dise~no de la rutina rotation type().
La
orre
ion del ter
er
aso
onsiste en una rota
ion simple ha
ia la izquierda que
arroja
omo solu
ion la situa
ion des
rita en la gura 6.11(b). Debido a que y esta ini
ialmente equilibrado, la altura general de T no
ambia. Por lo tanto, si la as
enden
ia de T
es AVL, el arbol resultante de la
orre
ion tambien lo es y la elimina
ion puede
ulminar
ante este
aso. T
T
x
2
y
1
y
0
x
1
=
(a)
(b)
582
Cada uno de los tres
asos, tiene sus equivalentes simetri
os fa
ilmente interpretables
imaginandolos re
ejados en un espejo.
De manera general, despues de la elimina
ion fsi
a, debemos retro
eder en el
amino
de busqueda, a
tualizar
ada fa
tor y revisarlo para ver si existe alguna viola
ion. Si
se en
uentra un fa
tor que represente una viola
ion, enton
es debemos
orregirla segun
los
asos presentados. Si la
orre
ion
orresponde al ter
er
aso, enton
es el algoritmo
termina. De lo
ontrario, debemos repetir el pro
edimiento hasta en
ontrar un fa
tor que
no represente una viola
ion o hasta en
ontrarnos
on la raz. Esta
ondu
ta se expresa en
la siguiente fun
ion:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 579
void restore_avl_after_deletion(const Key & key)
{
Node* pp = avl_stack.top(1); // padre de p
6.4. Arboles
AVL
583
6.4.2
An
alisis de los
arboles AVL
Comenzaremos nuestro analisis por la presenta
ion de la siguiente proposi
ion y la dis
usion de sus
onse
uen
ias:
Proposici
on 6.4 (Adelson Velsky - Landis, 1962 [1]) Sea T un arbol AVL
on n no-
(6.10)
Ver x 6.4.2.2
La demostra
ion es algo di
ultosa y se desarrollara en detalle en las se
iones subsiguientes. Pero las
onse
uen
ias de la proposi
ion para el analisis de las opera
iones de
inser
ion y elimina
ion son mu
ho mas evidentes.
La primera observa
ion que
onlleva la proposi
ion 6.10 es que la altura maxima de un
arbol AVL es O(lg n). No importa
uanto
rez
a el arbol, el aumento de la altura siempre
es logartmi
o.
Respe
to al
oste
onstante tenemos, en el peor de los
asos, una altura un 44% mayor
que la de un arbol perfe
tamente equilibrado. En las situa
iones restantes, la altura es
menor y el tiempo de busqueda es mejor. Ahora bien, >que tan
ostoso es este 44%? Si
re
ordamos los 1300 millones de
hinos
onsiderados en en x 4.9, el nombre de Zhang Shunniean Cheung Wu puede ser lo
alizado en a lo sumo 1.44 lg 1300000000 = 44 intentos, 13
mas que
on un arbol perfe
tamente equilibrado y esto solo si somos muy desafortunados.
La
ota impuesta por la proposi
ion 6.10 ata~ne dire
tamente a la busqueda. >Que
a
er
a de la inser
ion y elimina
ion? La inser
ion requiere una busqueda que por la
proposi
ion 6.10 es O(lg n). Luego, se retro
ede en el
amino de busqueda para veri
ar si hay algun desequilibrio. Este retro
eso no toma mas de tres nodos, lo que lo ha
e,
pues,
onstantemente a
otado. Si se en
uentra un desequilibrio, enton
es este se
orrige
en, a lo sumo, dos rota
iones, las
uales tambien estan
onstantemente a
otadas. Podemos
on
luir, enton
es, que la inser
ion es O(lg n) + O(1) + O(1) = O(lg n).
Captulo 6. Arboles
de b
usqueda equilibrados
584
El primer paso de la elimina ion es una busqueda, la ual ya on luimos que toma
librio y, si es el
aso,
orregirlo. Re
ordemos que una
orre
ion de desequilibrio puede
ausar otro desequilibrio en la as
enden
ia del nodo
orregido. En el peor de los
asos,
la primera
orre
ion puede
ausar una
adena de
orre
iones su
esivas que puede llegar hasta la raz. Puesto que la altura es O(lg n) la
adena de
orre
iones es a lo sumo
O(lg n) si esta llegase hasta la raz. La elimina
ion es, pues, O(lg n) + O(lg n) = O(lg n);
mas
ostosa que la inser
ion pero tambien O(lg n).
En
on
lusion, todas las opera
iones de un arbol AVL son O(lg n) para el peor
aso.
Si se requieren garantas de desempe~no y se dispone de una implementa
ion, los arboles
AVL son una buena es
ogen
ia.
6.4.2.1
Arboles
de Fibonacci
Para demostrar la proposi
ion 6.10, es
onveniente estudiar una
lase espe
ial de arbol
denominado de \Fibona
i".
Definici
on 6.5 (Arbol
de Fibonacci) Un arbol de Fibona
i de orden k, denominado
Tk, se dene re
ursivamente
omo:
si k = 0
Tk =
h, 0, i
hT , Fib(k + 1) 1, T
k1
si k = 1
k2 + Fib(k + 1)i si k 2
El ultimo termino, hTk1, Fib(k + 1) 1, Tk2 + Fib(k + 1)i, debe interpretarse
uidadosamente. La rama izquierda, Tk1 es el arbol de Fibona
i de orden k 1. La raz de Tk esta
etiquetada
on Fib(k + 1) 1 donde Fib(i) es el i-esimo numero de Fibona
i. La rama
dere
ha es el arbol de Fibona
i de orden k 2 pero
on todos sus nodos sumados en
Fib(k + 1).
La gura 6.12 muestra el arbol de Fibona
i para k = 8. La rama izquierda es el arbol
T7. A su vez, la rama izquierda de T7 es el arbol T6 quien a su vez tiene de rama izquierda
a T5 y as su
esivamente. La rama dere
ha de Tk tiene exa
tamente la misma forma que
Tk1, pero
ada nodo es in
rementado en Fib(k + 1). Por ejemplo, en la gura 6.12 se
puede veri
ar que la rama dere
ha prin
ipal tiene exa
tamente la misma forma que T6,
ex
epto que los valores de
ada nodo son in
rementados en Fib(9) = 34.
De la gura 6.12 se observa que la etiqueta de
ada nodo es exa
tamente su posi
ion
inja. Esta propiedad nos permite intuir la
ardinalidad de un arbol de Fibona
i. La
raz del arbol de Fibona
i de orden 8 mostrado en la gura 6.12 es Fib(8 + 1) 1 = 33.
Puesto que los nodos estan etiquetados
on su posi
ion inja, Fib(7 + 2) = 33 es la
antidad de nodos que pre
eden al arbol izquierdo. Pero, por deni
ion, L(T8) = T7, por
lo que podemos dedu
ir que |T7| = Fib(7 + 2) 1. Podemos observar diferentes arboles de
Fibona
i y
orroborar esta intui
ion mediante la siguiente proposi
ion:
Proposici
on 6.5 El n
umero de nodos de un arbol de Fibona
i de orden k es Fib(k +
2) 1.
6.4. Arboles
AVL
585
33
20
46
12
28
17
10
2
1
6
3
9 11
8
15
25
19
14 16 18
13
41
23
31
27
22 24 26
38
30 32
29
36
44
40
35 37 39
21
51
49
43 45
42
48 50 52
47
34
Figura 6.12: Arbol
de Fibona
i de orden 8
Demostraci
on (por inducci
on sobre k)
k = 0: En este
aso, |T0| = Fib(0) = 0. La proposi
i
on es
ierta para k = 0.
k > 0: Ahora asumimos que la proposi
i
on es
ierta para todo k y veri
amos si aun
lo es para k + 1.
Por deni
ion:
|Tk+1| = |Tk| + 1 + |Tk1|
(6.11)
es de
ir, el numero de nodos de la rama izquierda mas la raz mas el numero de nodos
de la rama dere
ha. Apli
ando la hipotesis indu
tiva a la e
ua
ion (6.11), tenemos:
|Tk+1| = Fib(k + 1) 1 + 1 + Fib(k + 1) 1
= Fib(k + 2) + Fib(k + 1) 1
= Fib(k + 3) 1
La altura de un arbol de Fibona
i tambien es fa
ilmente sugerible por observa
ion.
En el
aso del arbol de orden 8 mostrado en la gura 6.12 es 8; es de
ir, el orden. Esta
intui
ion tambien es fa
ilmente demostrable por indu
ion.
Proposici
on 6.6 La altura del arbol de Fibona
i de orden k es k.
Demostraci
on (por inducci
on sobre k)
k = 0: Este
aso es dire
to pues el arbol va
o tiene altura nula.
53
586
Captulo 6. Arboles
de b
usqueda equilibrados
Figura 6.13: Arbol
de Fibona
i de orden 13
La proposi
ion anterior es fundamental para demostrar que un arbol de Fibona
i es
AVL,
uestion que responde la proposi
ion que sigue:
Proposici
on 6.7 Un arbol de Fibona
i es AVL.
Demostraci
on
Sea Tk el arbol de Fibona
i de orden k. Construiremos la demostra
ion por indu
ion
sobre k:
k = 0: Este
aso es dire
to pues el arbol va
o es AVL.
k > 0: Ahora asumimos que la proposi
i
on es
ierta para todo k y veri
amos para
Tk+1.
Para que Tk+1 sea AVL, la diferen
ia de alturas de todos sus nodos no debe ex
eder
de uno. Por deni
ion, las ramas de Tk+1 son arboles de Fibona
i, los
uales, por
la hipotesis indu
tiva, son AVL. As pues, el uni
o nodo en que hay que veri
ar
la
ondi
ion AVL es en la raz de Tk+1. La diferen
ia de alturas de raiz(Tk+1) esta
dada por (raiz(Tk+1)) = h(R(Tk+1)) h(L(Tk+1)) = h(Tk1) h(Tk) la
ual, por la
proposi
ion 6.6, es k 1 k = 1. Puesto que todos los nodos de Tk+1 satisfa
en la
ondi
ion AVL, Tk+1 es AVL y la proposi
ion es
ierta para todo k
Cualquier arbol de Fibona
i es AVL, pero, desde esta perspe
tiva, los arboles de
Fibona
i nos deparan una sorpresa:
ara
terizan arboles AVL de altura maxima. Para
apre
iar esta
uestion, requerimos la siguiente deni
ion:
Definici
on 6.6 (Arbol
AVL crtico) Sea T un arbol AVL de altura h. Se di
e que T es
rti o si y solo si, al quitarle una hoja, el arbol deja de ser AVL o pierde altura.
Proposici
on 6.8 Un arbol de Fibona
i es un arbol AVL
rti
o.
6.4. Arboles
AVL
587
Demostraci
on
(ni) = 1
Demostraci
on (por inducci
on sobre n = |T |)
n = 2: En este
aso se trata de T2 y (T2) = h(R(T2)) h(L(T2)) = h(T0) h(T1) =
0 1 = 1. El lema es
ierto para k = 2.
n > 2: Ahora asumimos que el lema es
ierto para todo k y veri
amos si a
un lo es
para k + 1.
Los nodos no hojas de Tk+1 son su raz y los nodos no hoja de su ramas izquierda
Tk y dere
ha Tk1, respe
tivamente. Por la hipotesis indu
tiva, los nodos no hoja de
Tk y Tk1 satisfa
en el lema. Por lo tanto, el u
ni
o nodo a veri
ar es raiz(Tk+1),
uya diferen
ia de alturas esta dada por (raiz(Tk+1)) = h(R(Tk+1)) h(L(Tk+1)) =
h(Tk1) h(Tk) = k 1 k = 1. El lema es pues
ierto para todo k
Con este lema, estamos prestos para demostrar la proposi
ion por redu
ion al absurdo.
Para ello negamos la proposi
ion, es de
ir, asumimos que existe un arbol de Fibona
i T ,
de altura h, que no es
rti
o. Si este es el
aso, enton
es podemos suprimir una hoja de
T de manera tal que su altura sea h y el arbol a
un sea AVL.
Sea ne T una hoja
ualquiera de T y sea np T el padre de ne. Por el lema 6.4,
sabemos que np tiene fa
tor de altura 1. Si eliminamos ne, podemos vislumbrar dos
asos:
1. Si ne es hijo dere
ho, enton
es,
omo (np) = 1, el arbol deja de ser AVL.
2. Si ne es hijo izquierdo, enton
es, puesto que (np) = 1, np pierde una unidad en
altura y deviene perfe
tamente equilibrado. Ahora bien, por el lema 6.4, todos los
nodos no hoja de T tienen fa
tor 1. Aqu debemos
onsiderar dos
asos:
(a) Si la hoja ne es el primer nodo en el re
orrido injo, enton
es T pierde altura,
lo que
ontradi
e el he
ho de que T no es
rti
o.
(b) En
aso
ontrario, si la hoja ne es diferente al primer nodo en el re
orrido injo,
enton
es la perdida de altura de np impli
a que algun nodo en su as
enden
ia
tendra una viola
ion de la
ondi
ion AVL, lo que
ontradi
e el he
ho de que T
es AVL.
Puesto que es imposible que T sea un arbol de Fibona
i no
rti
o,
on
luimos que T
tiene que ser
rti
o
Estudiados los arboles de Fibona
i, tenemos todas las herramientas requeridas para
la demostra
ion de la proposi
ion 6.4.
Captulo 6. Arboles
de b
usqueda equilibrados
588
6.4.2.2
Demostraci
on de la proposici
on 6.4
La altura mnima de un arbol AVL es la misma que para
ualquier arbol binario, la
ual
fue demostrada en la proposi
ion 4.2. Por tanto, nos
on
entraremos en estudiar la altura
maxima que puede al
anzar un arbol AVL.
Estamos interesados en en
ontrar una manera de disponer n nodos en un arbol AVL
de manera tal que su altura sea maxima. En otras palabras, debemos disponer los n nodos
en un arbol
rti
o. Como sabemos de la proposi
ion 6.8, un arbol de Fibona
i es
rti
o.
Del mismo modo, por la proposi
ion 6.7, un arbol de Fibona
i es AVL. Con seguridad,
podemos bus
ar la
ota de la altura maxima a traves de un arbol de Fibona
i.
Dada una altura nominal h, por la proposi
ion 6.6, el arbol de Fibona
i Th, AVL y
rti
o, tiene, segun la proposi
ion 6.5, Fib(h + 2) 1 nodos. As pues, planteamos:
n |Th| = Fib(h + 2) 1
(6.12)
Cualquier arbol AVL de altura h tiene que tener igual o mas nodos que |Th|, pues
Th es
rti
o. De este modo, lo u
ni
o que nos resta es en
ontrar una expresion que nos
denote el n-esimonumero de Fibona
i y despejar h de la desigualdad (6.12). A partir del
ono
imiento de que Fib(0) = 0, Fib(0) = 1, debemos resolver la e
ua
ion de re
urren
ia:
Fib(k) = Fib(k 1) + Fib(k 2) =
Fib(k + 2) = Fib(k + 1) + Fib(k) =
(6.13)
(6.14)
La e
ua
ion (6.14) es una e
ua
ion re
urrente homogenea de segundo orden. Se denomina de esta manera porque es reminis
ente a una e
ua
ion diferen
ial homogenea de
segundo orden. De he
ho, las e
ua
iones de re
urren
ias a menudo se denominan \e
ua
iones de diferen
ias", por su similitudes
on las e
ua
iones diferen
iales. En este sentido,
la e
ua
ion (6.14) es reminis
ente a una e
ua
ion diferen
ial lineal homogenea de segundo
orden
on
oe
ientes
onstantes; es de
ir, de forma y + a1 y_ + a2 y = 0.
Ahora podemos resolver la e
ua
ion re
urrente (6.14) mediante un metodo analogo de
resolu
ion de una e
ua
ion diferen
ial. Para ello, al igual que
on una e
ua
ion diferen
ial
homogenea de segundo orden, apli
amos la transforma
ion siguiente:
Fib(k) = c k, c, 6= 0, k 2 .
(6.15)
Sustituimos (6.15) en (6.14), lo que propor
iona la e
ua
ion
ara
tersti
a siguiente:
c k+2 = c k+1 + c k ,
(6.16)
(6.17)
1 5
.
=
2
^
uyos valores son:
Estos numeros son legendarios, el numero y
1+ 5
1.61803 ,
=
2
1 5
0.61803
^ =
2
(6.18)
(6.19)
(6.20)
6.4. Arboles
AVL
589
^k
Fib(k) = c1 k + c2
Para en
ontrar los valores de c1 y c2, ha
emos lo mismo que
on una e
ua
ion diferen
ial: planteamos un sistema de e
ua
iones
on valores
ono
idos de Fib(k). En este
aso,
utilizamos Fib(0) = 0 y Fib(1) = 1:
^ 0 = c1 + c2
Fib(0) = 0 = c1 0 + c2
Fib(1) = 1 = c1 1 + c2
^ 1 = c1 + c2
^
(6.22)
(6.23)
(6.24)
c2 = c1
1
1
=
^
5
(6.25)
1
5
Fib(k) = k
^k =
k
^k
(6.26)
^ < 1, el termino
^ k deviene exponen
ialmente muy peque~
no
onforme
Puesto que
re
e k, ergo,
^ es despre
iable en (6.26)para valores de k muy grandes. Conse
uentemente, Fib(k) es bastante
er
ano a k/ 5
onforme
re
e k. As pues, podemos tener
una expresion mas simple para
al
ular el k-esimo numero de Fibona
i:
k
redondeado al entero mas proximo
5
Fib(k) =
(6.27)
>
redondeado al entero mas
er
ano 1 =
5
h+2
n >
2
5
(6.28)
(6.29)
(6.30)
Notemos que la e
ua
ion (6.27) estable
e que la fun
ion redondeo al entero mas
er
ano
debe apli
arse. Para despejar h de la ine
ua
ion (6.29), sera ne
esario
ono
er la inversa
de la fun
ion de redondeo. Por esa razon, restamos una unidad del lado dere
ho de la
ine
ua
ion (6.29) y, a partir de all, expresamos una desigualdad estri
ta:
h+2 <
h+2 <
h <
h <
h <
5 (n + 2) =
log [ 5 (n + 2)] =
log (n + 2) + log ( 5) 2 =
lg (n + 2)
0.3277 =
lg
1.4404 lg (n + 1) 0.3277
Captulo 6. Arboles
de b
usqueda equilibrados
590
6.5
Arboles
rojo-negro
Ahora estudiaremos una
lase de arbol binario
uyo esquema de balan
e tambien es garantizado. El esquema es \
romati
o" en el sentido de que los nodos son \
oloreados" de rojo o
negro, respe
tivamente. Las reglas de
olora
ion propor
ionan argumentos
ombinatorios,
en fun
ion de las posibles
ombina
iones de
olores, que garantizan un equilibrio.
Definici
on 6.7 (Arbol
rojo-negro) Un arbol rojo-negro, o ABRN, es un arbol binario
de busqueda,
on un atributo adi
ional en
ada uno de sus nodos denominado \
olor",
que satisfa
e las siguientes
ondi
iones denominadas \
romati
as":
on crom
atica primaria: Un nodo es rojo o negro.
Condici
Condici
on roja: Si un nodo es rojo, enton
es sus dos hijos deben ser negros.
Esto garantiza que dos nodos rojos nun
a pueden estar
ontiguos en el
amino de
busqueda.
Condici
on negra: Para todo nodo, el
amino hasta una hoja
ontiene el mismo
Nodo externo negro: Los nodos externos son negros. En otras palabras, todo
La gura 6.14 ilustra un ejemplo de un arbol rojo-negro en la
ual los nodos rojos
estan sombreados.
45
27
14
9
5
3
30
18
13 16 21
8 10
77
28
59
34
52
31 35
48 54
70
94
80
78 84
96
98
40
Notemos que la altura negra esta denida para
ualquier
amino desde raiz(T ). Esto
es una
onse
uen
ia dire
ta de la
ondi
ion negra que nos garantiza que esta altura es la
misma independientemente de la hoja que se
onsidere.
6.5. Arboles
rojo-negro
591
591
6.5.1
El TAD Rb Tree<Key>
EL TAD Gen Rb Tree<Key> dene un arbol rojo-negro
uyas opera
iones prin
ipales son
en fun
ion de nodos rojo-negro denidos anteriormente. La estru
tura prin
ipal es
omo
sigue:
Captulo 6. Arboles
de b
usqueda equilibrados
592
592a
htpl
rb tree.H
592ai
template <template <typename> class NodeType, typename Key, class Compare>
class Gen_Rb_Tree
{
typedef NodeType<Key> Node;
hmiembros
hmiembros
};
592b
Los fundamentos de implanta
ion son muy similares a los demas arboles binarios estudiados: un nodo
abe
era, uno
entinela y dilu
ida
ion de uso o no de la re
ursividad.
Los algoritmos son mas sen
illos que los de un arbol AVL, pero no lo su
iente
omo para
permitir una implanta
ion naturalmente re
ursiva.
Una de las de
isiones que tomamos a priori es realizar una implanta
ion iterativa
asistida de una pila, la
ual justi
a los siguientes atributos:
hmiembros privados de Gen Rb Tree<Key> 592bi
(592a) 592
Node
head_node;
Node *
head;
Node *&
root;
FixedStack<Node*, Node::MaxHeight> rb_stack;
Compare
cmp;
Uses FixedStack 131a.
592
La inser
ion y elimina
ion requieren empilar el
amino de busqueda. Esta a
ion es
realizada por una rutina espe
a:
hmiembros privados de Gen Rb Tree<Key> 592bi+
(592a) 592b 594a
Node * search_and_stack_rb(const Key & key)
{
Node * p = root;
rb_stack.push(head);
do
{
rb_stack.push(p);
if (cmp (key, KEY(p)))
p = LLINK(p);
6.5. Arboles
rojo-negro
593
6.5.1.1
593
Inserci
on en un
arbol
arbol rojo negro
En prin
ipio, la inser
ion es exa
tamente igual a la inser
ion en un arbol binario estandar.
Luego, si es ne
esario, se efe
tuan los ajustes para preservar el balan
e:
hmiembros p
ubli
os de Gen Rb Tree<Key> 593i
(592a) 598
Node * insert(Node * p)
{
if (root == Node::NullPtr) // inserci
on en
arbol vac
o
{
root = p;
return p;
}
Node * q = search_and_stack_rb(KEY(p));
if (cmp (KEY(p), KEY(q)))
LLINK(q) = p;
else if (cmp (KEY(q), KEY(p)))
RLINK(q) = p;
else
{
rb_stack.empty();
return NULL;
// clave duplicada
}
fix_red_condition(p);
return p;
}
Uses fix red condition 594a, LLINK 296, RLINK 296, and search and stack rb 592
.
La rutina implanta la inser
ion fsi
a
lasi
a. fix red condition() veri
a viola
iones de
las
ondi
iones
romati
as y las
orrige si es el
aso.
En el
aso de la inser
ion, el tru
o es insertar un nodo rojo, pues este no altera la
ondi
ion negra, ergo, el balan
e de alturas negras. Empero, la inser
ion de un nodo rojo
puede
ausar una viola
ion de la
ondi
ion roja si el padre del nodo insertado es rojo. Si el
Captulo 6. Arboles
de b
usqueda equilibrados
594
594a
padre del nodo insertado es negro, enton
es el arbol resultante es rojo-negro y el algoritmo
termina. De lo
ontrario, es ne
esario restable
er la
ondi
ion roja sin alterar la
ondi
ion
negra.
La estru
tura de fix red condition() es
omo sigue:
hmiembros privados de Gen Rb Tree<Key> 592bi+
(592a) 592
600a
void fix_red_condition(Node *p)
{
hvariables de x red
ondition 594bi
while (p != root)
{
hobtener pp (el
padre de p) 595ai
hobtener
hveri ar
hveri ar
break; // aqu
el
arbol es rojo-negro
}
rb_stack.empty();
}
Denes:
fix red condition, used in
hunk 593.
594b
6.5. Arboles
rojo-negro
595
595a
En prin
ipio, p es el nodo insertado. Como algunas
orre
iones pueden
ausar otras
viola
iones sobre la as
enden
ia de p, nos referiremos a p
omo al \nodo
rti
o".
En lo que sigue, pp es el padre del nodo
rti
o, ppp su abuelo y spp el hermano de pp
-o el to de p-. El a
eso a pp esta dado por el tope de la pila:
hobtener pp (el padre de p) 595ai
(594a)
pp = rb_stack.pop();
595b
(594a)
ppp = rb_stack.pop();
595
(594a)
Ahora podemos
ara
terizar las veri
a
iones y eventuales viola
iones de la
ondi
ion
romati
a, las
uales deben ser evaluadas estri
tamente en el orden indi
ado:
Caso 1:
595d
(594a)
hp
(594a)
pp == root
root
root
pp
p
p
=
(a)
(b)
(594a)
Captulo 6. Arboles
de b
usqueda equilibrados
596
ppp
ppp
pp
spp
pp
spp
pp
ppp
spp
pp
spp
ppp
Si luego de este ajuste el padre de ppp es negro, enton
es el arbol resultante es rojo-negro
y el algoritmo termina. De lo
ontrario, se ne
esita repetir el pro
edimiento para ppp. Por
esa razon, despues de hinter
ambiar
olores entre los niveles 596ai, eje
utamos p = ppp,
lo que ha
e que ambos
asos son evaluados
uando el lazo repita.
Caso 4:
Finalmente, el ultimo
aso general se presenta
uando el to de p es negro. La solu
ion
onsiste en realizar rota
iones que transformen el problema en uno de los anteriores. Las
rota
iones requieren el nodo bisabuelo de p, el
ual se denomina pppp y se obtiene mediante:
596b
hobtener pppp (el bisabuelo de p) 596bi
(594a)
Node *pppp = rb_stack.pop();
Bajo los lineamientos anteriores, tenemos dos
lases de situa
iones. La primera, ilustrada en
la gura 6.18, se dete
ta porque p, pp y ppp estan
ompletamente alineados. La
orre
ion
onsiste en efe
tuar una rota
ion simple de pp ha
ia el lado de spp; esto ha
e que pp
devenga la raz del subarbol. Por ultimo, se inter
ambian los
olores de pp y ppp.
6.5. Arboles
rojo-negro
597a
597
(594a)
El primer if veri
a una alinea
ion
ompleta ha
ia la izquierda mientras que el segundo
ha
ia la dere
ha.
ppp
pp
pp
spp
ppp
spp
Figura 6.18: Rota
ion y
olora
ion para restaurar
ondi
ion roja
La segunda situa
ion, ilustrada en la gura 6.19, se presenta
uando p, pp y ppp no estan
ompletamente alineados. La solu
ion
onsiste en una doble rota
ion
ruzada de p ha
ia
el lado de spp. En este
aso, p deviene la raz del subarbol y los
olores de p y de ppp son
inter
ambiados.
ppp
p
pp
spp
pp
ppp
spp
Figura 6.19: Doble rota
ion de p y
olora
ion para restaurar
ondi
ion roja
597b
hveri
ar
else
{
(594a)
Captulo 6. Arboles
de b
usqueda equilibrados
598
if (RLINK(pp) == p)
{
rotate_to_left(pp, ppp);
rotate_to_right(ppp, pppp);
}
else
{
rotate_to_right(pp, ppp);
rotate_to_left(ppp, pppp);
}
COLOR(p) = BLACK;
}
COLOR(ppp) = RED;
Uses RLINK 296, rotate to left, and rotate to right 419.
En las dos situa
iones anteriores, el arbol resultante es rojo-negro y el algoritmo termina.
Algunas itera
iones pueden o
urrir
uando se
ae en el
aso hto de p es rojo 595gi,
pudiendo ser ne
esario subir hasta la raz. En esta situa
ion, la eventual viola
ion de la
ondi
ion roja sube dos niveles. As pues, el numero maximo de itera
iones esta a
otado
por la altura del arbol dividida entre dos (h/2).
6.5.1.2
598
Eliminaci
on en un
arbol rojo negro
Node *q = search_and_stack_rb(key);
if (no_equals<Key, Compare> (KEY(q), key))
{
rb_stack.empty();
return NULL;
}
Node* pq = rb_stack.top(1); /* qs parent */
Node *p; /* pqs child after q has been deleted */
repeat:
if (LLINK(q) == Node::NullPtr) // by pass to the left side
{
if (LLINK(pq) == q)
p = LLINK(pq) = RLINK(q);
else
p = RLINK(pq) = RLINK(q);
6.5. Arboles
rojo-negro
599
goto end;
}
if (RLINK(q) == Node::NullPtr) // by pass to the right side
{
if (LLINK(pq) == q)
p = LLINK(pq) = LLINK(q);
else
p = RLINK(pq) = LLINK(q);
goto end;
}
find_succ_and_swap(q, pq);
goto repeat;
end:
if (COLOR(q) == BLACK)
fix_black_condition(p);
q->reset();
rb_stack.empty();
return q;
}
Uses child 323
, find succ and swap, fix black condition 600a, LLINK 296, RLINK 296,
and search and stack rb 592
.
La rutina bus
a la
lave a la vez que empila el
amino de busqueda. El nodo a eliminar
es llamado q y su padre pq. El while implanta la elimina
ion fsi
a del nodo, la
ual, a
estas alturas del estudio, debe ser
ompresible para el le
tor. Despues de la elimina
ion
fsi
a, existe el riesgo de viola
ion de la
ondi
ion negra, pues el nodo eliminado puede
ser sido negro, lo que provo
a una perdida en altura negra. En adelante, llamaremos p al
nodo el
ual puede tener un de
it en nodos negros.
find succ and swap() debe ser familiar al le
tor. El u
ni
o punto a re
ordar es que
si se elimina un nodo
ompleto, enton
es el su
esor -o prede
esor- que se sustituye debe
heredar el mismo
olor del nodo eliminado. De esta manera, la viola
ion eventual o
urre
sobre un nodo que
on
erteza es in
ompleto.
La elimina
ion estandar en un ABB siempre se remite a la elimina
ion de un nodo
uyo
padre sea in
ompleto. La idea es efe
tuar un
orto-
ir
uito -x 4.9.6- ha
ia el hijo de q a
ual
denominamos p. Si q es rojo, enton
es el arbol resultante es rojo-negro. El problema surge
uando q es negro, pues se viola la
ondi
ion negra. En este
aso, diremos que existe un
de
it de un nodo negro en el
amino desde el padre del nodo eliminado hasta p. De manera
general, la estrategia de restaura
ion de la
ondi
ion negra es en
ontrar un nodo rojo en
las inmedia
iones de p de manera tal
omo se indi
a en la gura 6.20. Si se en
uentra tal
nodo, enton
es este puede pasarse ha
ia el lado del de
it y
olorearlo de negro para as
ompensar el de
it. El trabajo general es realizado por la rutina fix black condition()
uya estru
tura general es la siguiente:
Captulo 6. Arboles
de b
usqueda equilibrados
600
pp
p
sp
snp
np
while (p != root)
{
h
omputar hermano
if (hhermano de
hrotar sp ha
ia
h
omputar
de p 601 i
p es rojo 601di)
el lado de p e inter
ambiar
olores 601ei
sobrinos de p 602ai
terminar 603ai;
(592a) 594a
hdesplazar
y terminar 604ai;
de it ha ia pp 604di
600b
Este es el
aso mas simple y se resuelve
oloreando p de negro para as
ompensar el
de
it:
6.5. Arboles
rojo-negro
601a
601
(600a)
q
p
pq
RN
Nodo a eliminar
=
Figura 6.21: Elimina
ion de un nodo negro
on hijo rojo
601b
601
(600a)
Notemos que no es posible h
omputar sobrinos de p 602ai durante la misma opera
ion
de h
omputar hermano de p 601
i, pues la opera
ion posterior hrotar sp ha
ia el lado
de p e inter
ambiar
olores 601ei puede
ambiar los sobrinos.
Ahora el algoritmo itera y evalua los siguientes
asos:
Caso 1:
601d
(600a)
(600a)
Captulo 6. Arboles
de b
usqueda equilibrados
602
sp
pp
p
pp
p
sp
}
else
{
sp = RLINK(sp);
ppp = rotate_to_right(pp, ppp);
}
COLOR(ppp) = BLACK;
COLOR(pp) = RED;
}
Uses LLINK 296, RLINK 296, rotate to left, and rotate to right 419.
Aunque esto ha
e des
ender el nodo de
itario un nivel, se garantiza que el nuevo hermano
de p sera negro, lo
ual,
omo
omprenderemos mas adelante, garantiza que el ajuste
denitivo se efe
tuara en la proxima itera
ion.
Si el hermano de p no es rojo, enton
es hay que revisar sus sobrinos, pero antes, se ne
esita
al
ularlos de la siguiente manera:
h
omputar sobrinos de p 602ai
(600a)
602a
if (LLINK(pp) == p)
{
np = RLINK(sp);
snp = LLINK(sp);
}
else
{
np = LLINK(sp);
snp = RLINK(sp);
}
Uses LLINK 296 and RLINK 296.
Caso 2:
602b
(600a)
6.5. Arboles
rojo-negro
603
pp
RN
p
-
pp
sp
np
np
sp
RN
Caso 3:
hsnp es rojo 603bi
COLOR(snp) == RED
603b
pp
RN
p
-
(600a)
sp
snp
RN
pp
snp
np
np
sp
Captulo 6. Arboles
de b
usqueda equilibrados
604
hay alinea
ion
ompleta ha
ia la dere
ha. La solu
ion tambien es similar: rotar snp dos
ve
es ha
ia el lado de p y luego
olorear de negro pp y sp. Al igual que el
aso anterior, la
ganan
ia del nodo negro pp en el
amino ha
ia p
ompensa el de
it:
hrotar doble snp,
olorear y terminar 604ai
(600a)
604a
{
Caso 4:
604b
(600a)
La situa
ion se muestra en la gura 6.25 y se resuelve inter
ambiando los
olores de pp
on
sp. Despues de este ajuste, el
amino ha
ia p gana un nodo negro a traves de su padre,
mientras que el
amino ha
ia sp permane
e
on la misma
antidad de nodos negros. Este
ajuste solo puede realizarse si se han evaluado los
asos anteriores, pues se podra violar
la
ondi
ion roja si alguno de los sobrinos de p fuese rojo:
604
hinter
ambiar
olores de p y pp, luego terminar 604
i
(600a)
{
COLOR(pp) = BLACK;
COLOR(sp) = RED;
return;
}
Caso 5: Si los asos anteriores fallan, enton es no hay nodos rojos en la zona enmar ada en
la gura 6.20. Ante esta situa
ion, la uni
a salida es desplazar el de
it ha
ia el padre de
p y luego repetir el pro
edimiento anterior
on p = pp. El desplazamiento, ilustrado en
la gura 6.26,
onsiste simplemente en
olorear sp de rojo. Con esto eliminamos un nodo
negro a partir de sp y trasladamos el de
it ha
ia pp:
hdesplazar d
e
it ha
ia pp 604di
(600a)
604d
COLOR(sp) = RED;
p
= pp;
pp
= rb_stack.pop();
6.5. Arboles
rojo-negro
605
pp
p
snp
pp
sp
p
np
sp
np
snp
=
sp
p
np
pp
-
snp
sp
np
An
alisis de los
arboles rojo-negro
Comen
emos por re
ordar que la
ondi
ion negra garantiza, por si misma, un balan
e
a nivel de nodos negros. Si solo se
uentan los nodos negros, enton
es, para
ada nodo,
la diferen
ia de alturas entre las dos ramas es
ero. Bajo la altura negra, el arbol esta
perfe
tamente equilibrado. El analisis se enfo
a, enton
es, en estudiar el impa
to de los
nodos rojos sobre la altura global del arbol.
Por la
ondi
ion roja, la altura debe expresarse en fun
ion de nodos negros, pues no
pueden haber dos nodos rojos
onse
utivos. El numero maximo de nodos rojos involu
rados
no puede ex
eder, pues, al numero de nodos negros mas uno. De manera intuitiva podemos
de
ir que en el peor
aso la altura maxima de un arbol rojo negro esta determinada por la
longitud maxima en nodos negros multipli
ada por dos. Es de
ir, asumimos que el
amino
de la rama mas larga esta repleto de nodos rojos inter
alados entre nodos negros.
Con estas observa
iones en mente, estamos listos para enun
iar la proposi
ion siguiente.
606
Captulo 6. Arboles
de b
usqueda equilibrados
Proposici
on 6.9 (Guibas y Sedgewi
k - 1978) Sea T un arbol rojo-negro
on n nodos y
altura h. Enton
es:
h 2 lg (n + 1) 2
(6.31)
Demostraci
on
Definici
on 6.9 (Arbol
rojo-negro crtico) Sea T un arbol rojo-negro de altura h(T ).
Se di
e que T es
rti
o si y solo si al quitarle una hoja el arbol deja de ser rojo-negro o
pierde altura.
Al igual que
on los arboles AVL, la
ardinalidad de un arbol rojo-negro
rti
o es
mnima a la altura h(T ); es de
ir, si T es
rti
o, no es posible tener un arbol a la misma
altura
on menor
antidad de nodos.
Ahora pro
edemos a estudiar
uantos nodos tiene un arbol
rti
o en fun
ion de su
altura. Primero
omen
emos por observar las diferen
ias de altura en los nodos de un arbol
rti
o.
Lema 6.5 Sea T un arbol rojo-negro
rti
o de altura h(T ) > 1
on ramas izquierda L(T )
y dere
ha R(T ) respe
tivamente. Enton
es h(L(T )) 6= h(R(T )).
Demostraci
on (por reducci
on al absurdo)
Supongamos que existe un arbol rojo-negro
rti
o
on h(L(T )) = h(R(T )). Puesto
que h(T ) > 1, podemos reemplazar
ualquiera de las ramas por otro arbol rojo-negro mas
peque~no en altura y
ardinalidad, lo que arrojara un arbol rojo-negro de
ardinalidad
inferior. Esto
ausa una
ontradi
ion, pues hemos di
ho que T es
rti
o. El lema es pues
ierto
Ahora veamos
uantos subarboles
rti
os existen dentro de un arbol rojo-negro
rti
o.
Lema 6.6 Sea T un arbol rojo-negro
rti
o de altura h(T ) > 1. Enton
es, h(R(T )) =
h(T ) 1 = R(T ) es
rti
o y por lo tanto mnimo.
La situa
ion simetri
a, es de
ir, si h(L(T )) = h(T ) 1, es equivalente.
Demostraci
on (por reducci
on al absurdo)
Supongamos que T es
rti
o y que R(T )) no es
rti
o. Enton
es, R(T ) podra ser
reemplazado por un subarbol T , h(T ) = h(T ) 1 tal que |T | < | R(T )|. Esto impli
ara
que T tiene menos nodos a la misma altura, lo
ual es una
ontradi
ion
on la suposi
ion
de que T es
rti
o
Con este lema, podemos estudiar la
omposi
ion
romati
a de
ada uno de los
subarboles de un arbol rojo-negro
rti
o.
Lema 6.7 Sea T un arbol rojo-negro
rti
o de altura h(T ) > 1. Enton
es, h(R(T )) =
h(T ) 1 =:
6.5. Arboles
rojo-negro
607
1. Si L(T ) tiene un nodo rojo, enton
es este nodo puede ser suprimido y sustituido por
el arbol va
o. El arbol resultante es rojo-negro pero tiene menos nodos, lo que viola
la suposi
ion de que T es
rti
o.
2. Puesto que L(T ) solo
ontiene nodos negros, la uni
a forma de preservar la
ondi
ion
negra es que L(T ) sea perfe
tamente equilibrado y
ompleto
h/2
h1
Captulo 6. Arboles
de b
usqueda equilibrados
608
$ %
h
h1
bh(Th+1) =
+ 1 =
=
2
2
$
%
h+1
h+1
=
bh(Th+1) =
2
2
h
=
2
h+1
2
(6.32)
Por los lemas 6.7 y 6.8, sabemos que L(Th) solo tiene nodos negros, por ende
| L(Th)| = 2(h1)/2 1
(6.33)
Por otra parte, por el lema 6.6, sabemos que R(Th) es
rti
o y que su altura es h 1.
Sustituimos (6.33) en (6.32) y obtenemos una e
ua
ion re
urrente fa
ilmente resoluble:
|Th| = |Th1| + 2(h1)/2
(6.34)
(6.35)
Si h es par =
|Th| =
h/2
X
i=1
2i = 2h/2+1 2
(6.36)
6.6. Arboles
splay
609
Si h es impar =
(h3)/2
|Th| = |Th2| + 3 .
2i = 1 + 3 . 2(h3)/2 3 = 3 . 2(h3)/2 2
(6.37)
i=0
n + 2 2h/2+1 =
h
lg (n + 2)
+ 1 =
2
h 2 lg (n + 2) 2
6.6
Arboles
splay
Los arboles aleatorizados y los treaps basan sus equilibrios sobre la toma de de
isiones
aleatorias. En promedio, la altura de estos arboles deviene logartmi
a;
on probabilidades
muy es
azas de tener un mal
aso. Los arboles AVL y rojo-negro basan sus equilibrios en
reglas espe
iales que se mantienen en tiempos logartmi
os. En esta
lase de arboles la
altura maxima y, por ende, el tiempo de a
eso, estan logartmi
amente a
otados. Existe
una ter
era alternativa de equilibrio basada en efe
tuar modi
a
iones estru
turales sobre
el arbol que \tiendan" a volverlo equilibrado. Tal alternativa es el objeto de estudios de
esta se
ion: los arboles splay.
Consideremos las tres opera
iones
lasi
as sobre una tabla de smbolos y un arreglo
que alma
ena los elementos desordenadamente. Como vimos en x 2.1.1, en un arreglo las
tres opera
iones son O(n). Ahora
onsideremos una modi
a
ion sobre la implanta
ion
tradi
ional:
ada vez que se realiza un a
eso, sea por busqueda, inser
ion o elimina
ion,
el elemento es
olo
ado en la ultima primera posi
ion. Bajo esta regla, la inser
ion y
elimina
ion devienen O(1) para todos los
asos y la busqueda \O(n)" para el peor
aso. Si
la apli
a
ion exhibe lo
alidad de referen
ia, enton
es la busqueda tiende a ser O(1), pues
lo elementos re
ientemente a
edidos se en
uentran en las primeras posi
iones del arreglo.
Captulo 6. Arboles
de b
usqueda equilibrados
610
>Que tan buena es esta te
ni
a? Los analisis formales y empri
os demuestran que es muy
buena en mu
hos
asos. Cuando no hay lo
alidad de referen
ia, la busqueda es O(n) para
los
asos promedio y peor, un desempe~no a
eptable hasta es
alas medianas.
>Se puede apli
ar la te
ni
a anterior para los arboles binarios de busqueda? La respuesta es armativa y la primera tenta
ion es mover el nodo re
ientemente a
edido hasta
la raz. La inser
ion
onsistira en insertar en la raz segun los algoritmos estudiados
en x 4.9.7 (pagina 398). Del mismo modo, podramos dise~nar una rutina similar al select()
estudiado en x 4.11.1 que busque una
lave y mediante rota
iones su
esivas suba el nodo
en
ontrado hasta la raz. Al igual que
on los arreglos, esta te
ni
a ha probado tener un desempe~no bueno para apli
a
iones que exhiban lo
alidad de referen
ia. Lamentablemente,
puesto que la te
ni
a no realiza ningun ajuste de equilibrio sobre el arbol, este probablemente no sera muy equilibrado. Pero aun si los a
esos son sesgados ha
ia un extremo del
orden de las
laves, el arbol puede devenir severamente desequilibrado.
Sleator y Tarjan [15 des
ubrieron una te
ni
a de ajuste que garantiza que el
osto
promedio de n opera
iones es O(lg n). Su te
ni
a es denominada \splaying" y los arboles
resultantes son denominados \arboles splay" Basi
amente, el \splaying"
onsiste en llevar
un nodo a
edido hasta la raz mediante opera
iones espe
iales que favore
eran futuros
a
esos.
3
p
C
A
B
B
p
C
610
La primera de las opera
iones se denomina zig-zig y se ilustra en la gura 6.28. Vista de
izquierda a dere
ha, la opera
ion se denomina zig zig right, mientras que en el sentido
ontrario se denomina zig zig left. La opera
ion sube el nodo A tres niveles y se dene
omo sigue:
hopera
iones zig 610i
(612a) 611
static Node * zig_zig_right(Node* p)
{
Node * q = LLINK(p);
Node * r = LLINK(q);
LLINK(p) = RLINK(q);
LLINK(q) = RLINK(r);
RLINK(q) = p;
3 En
astellano no existe un termino que ade
uadamente re
eje la palabra \splay". En
ontramos en
\plegado" y en \desplegado" las aproxima
iones mas representativas, pero, a nuestro jui
io, \arboles desplegados" o \arboles desplegados" no expresan los mismos signi
ados que \splay trees". Por esa razon,
preferimos utilizar el termino original.
6.6. Arboles
splay
611
RLINK(r) = q;
return r;
}
static Node * zig_zig_left(Node* p)
{
Node * q = RLINK(p);
Node * r = RLINK(q);
RLINK(p) = LLINK(q);
RLINK(q) = LLINK(r);
LLINK(q) = p;
LLINK(r) = q;
return r;
}
Denes:
zig zig left, used in
hunk 615.
zig zig right, used in
hunk 615.
Uses LLINK 296 and RLINK 296.
C
A
611
La segunda opera
ion se denomina zig-zag y se ilustra en la gura 6.29. Esta opera
ion
es equivalente a una doble rota
ion. Al igual que el zig-zig, el nodo r sube tres niveles y
se dene
omo sigue:
hopera
iones zig 610i+
(612a) 610
static Node * zig_zag_right(Node* p)
{
LLINK(p) = rotate_to_left(LLINK(p));
return rotate_to_right(p);
}
Captulo 6. Arboles
de b
usqueda equilibrados
612
Uses LLINK 296, RLINK 296, rotate to left, and rotate to right 419.
La ultima opera
ion, que se denomina \zig",
onsiste en efe
tuar una rota
ion simple y
solo se realiza si el nodo que devendra raz se en
uentra en el nivel 1; es de
ir, exa
tamente
un nivel inferior a la raz .
Dado un nodo x en un arbol binario de busqueda, el \splaying" del nodo x
onsiste en
subirlo hasta la raz mediante las opera
iones zig, zig-zig y zig-zag. Los analisis posteriores
demostraran que si esto se realiza por
ada a
eso, enton
es el
osto promedio de n a
esos
es O(lg n).
4
6.6.1
612a
612b
El TAD Gen Splay Tree<Key> no requiere denir una nueva
lase de nodo binario,
pues no es ne
esario alma
enar informa
ion de estado en
ada nodo.
Las opera
iones zig-zig y zig-zag involu
ran 4 nodos que son examinados bajo dos
perspe
tivas. Primero, requerimos determinar
ual de las seis opera
iones posibles debe
eje
utarse. Segundo, debemos eje
utar la opera
ion en
uestion. El splaying de un nodo
involu
ra, enton
es, a todos los nodos del
amino de busqueda desde la raz hasta el nodo
donde se realiza el splaying. Por tanto, usamos una pila que alma
ene el
amino ntegro
de busqueda:
hmiembros privados de Gen Splay Tree<Key> 612bi
(612a) 613a
struct Node_Desc
4 V
ease x
6.6. Arboles
splay
613
{
Node * node;
Node ** node_parent;
};
ArrayStack<Node_Desc, Node::MaxHeight> splay_stack;
Uses ArrayStack 131a.
613a
Cada entrada de la pila registros de tipo Node Desc, el
ual a su vez guarda dos valores:
el nodo parte del
amino de busqueda y un doble apuntador a su padre. El puntero doble
permite efe
tuar las rota
iones sin ne
esidad de preguntar de que lado, izquierdo o dere
ho,
se en
uentra el padre.
La a
ion de splay tiende a equilibrar el arbol, pero no ex
luye un
aso muy desafortunado. Eventualmente, splay stack puede tener un desborde que debe ser manejado. Por
esa razon, denimos un metodo espe
ial que inserta un nodo en la pila y que veri
a el
desborde:
hmiembros privados de Gen Splay Tree<Key> 612bi+
(612a) 612b 613b
void push_in_splay_stack(Node * p, Node ** pp)
{
try
{
splay_stack.push(Node_Desc(p, pp));
}
catch (std::overflow_error)
{
splay_stack.empty();
throw;
}
}
Denes:
push in splay stack, used in
hunks 613b, 615, and 616b.
propagar la ex
ep
ion.
613b
Captulo 6. Arboles
de b
usqueda equilibrados
614
p = LLINK(p);
}
else if (Compare()(KEY(p), key))
{
pp = &RLINK(p);
p = RLINK(p);
}
else
return;
}
}
Denes:
search and push in splay stack, used in
hunks 616 and 617.
Uses LLINK 296, push in splay stack 613a, and RLINK 296.
614a
search and push in splay stack() es una rutina muy importante la
ual es es indispensable
omprender. Basi
amente, la rutina bus
a un nodo
on
lave key y empila en
amino de busqueda independientemente de que se en
uentre o no la
lave. Si la busqueda
es exitosa, enton
es el tope de la pila
ontiene el nodo en
ontrado. De lo
ontrario, el tope
ontiene el ultimo nodo antes del externo que determino el n de la busqueda. La rutina
sirve, pues, para las tres opera
iones basi
as: inser
ion, busqueda y elimina
ion:
Los atributos restantes ya son familiares al le
tor:
hmiembros privados de Gen Splay Tree<Key> 612bi+
(612a) 613b 614b
Node head_node;
Node *head;
Node *&root;
614b
La esen
ia de la implanta
ion subya
e en una rutina splay() que realiza el splaying del
nodo situado en el tope de la pila. splay() requiere determinar el tipo opera
ion eje
utar
segun la alinea
ion de
amino de busqueda. Esto es realizado por la siguiente rutina:
hmiembros privados de Gen Splay Tree<Key> 612bi+
(612a) 614a 615
enum Zig_Type { ZIG_LEFT, ZIG_RIGHT, ZIG_ZAG_LEFT, ZIG_ZAG_RIGHT,
ZIG_ZIG_LEFT, ZIG_ZIG_RIGHT };
Zig_Type zig_type(Node *& p, Node **& pp)
{
Node_Desc first = splay_stack.pop();
Node_Desc second = splay_stack.pop();
Zig_Type ret_val =
Compare()(KEY(second.node), KEY(first.node)) ? ZIG_LEFT : ZIG_RIGHT;
if (splay_stack.is_empty())
{ /*
ultima etapa y es un zig */
p = second.node;
pp = second.node_parent;
return ret_val;
}
Node_Desc third = splay_stack.pop();
6.6. Arboles
splay
615
pp = third.node_parent;
p = third.node;
if (ret_val == ZIG_LEFT)
return Compare()(KEY(third.node), KEY(second.node))
? ZIG_ZIG_LEFT : ZIG_ZAG_RIGHT;
return Compare()(KEY(second.node), KEY(third.node))
? ZIG_ZIG_RIGHT : ZIG_ZAG_LEFT;
}
Denes:
615
zig type() retorna el tipo de opera
ion que debe realizarse sobre el nodo en el tope
de la pila. Adi
ionalmente, la rutina devuelve los valores p y pp
orrespondientes al ultimo
nodo en la
adena de la opera
ion y su padre. Estamos listos para la rutina splay(), la
ual es muy sen
illa una vez denida la maquinaria ne
esaria:
hmiembros privados de Gen Splay Tree<Key> 612bi+
(612a) 614b
void splay()
{
if (splay_stack.is_empty() or splay_stack.top().node == root)
return;
Node **pp, *p;
while (true)
{
switch (zig_type(p, pp))
{
case ZIG_LEFT:
*pp
case ZIG_RIGHT:
*pp
case ZIG_ZIG_LEFT: *pp
case ZIG_ZIG_RIGHT: *pp
case ZIG_ZAG_LEFT: *pp
case ZIG_ZAG_RIGHT: *pp
default: ERROR("Invalid
}
= rotate_to_left(p); break;
= rotate_to_right(p); break;
= zig_zig_left(p); break;
= zig_zig_right(p); break;
= zig_zag_left(p); break;
= zig_zag_right(p); break;
rotation type");
if (splay_stack.is_empty())
break;
push_in_splay_stack(p, pp);
}
}
Denes:
splay() es la rutina fundamental del TAD Gen Splay Tree<Key>. Ella asume que se
efe
tuo una llamada previa a search and push in splay stack() y que el tope de la pila
ontiene el nodo sobre el
ual se desea realizar el splay. Cada vez que se eje
uta
ualquier
opera
ion de inser
ion, busqueda o elimina
ion, se debe realizar un splay del arbol. La
Captulo 6. Arboles
de b
usqueda equilibrados
616
616a
idea de esta opera
ion es amortizar los
asos desafortunados y equilibrar el arbol.
De las tres opera
iones fundamentales, la mas simple es la de busqueda, la
ual se
dene
omo sigue:
hmiembros p
ubli
os de Gen Splay Tree<Key> 616ai
(612a) 616b
Node * search(const Key & key)
{
if (root == NULL)
return NULL;
search_and_push_in_splay_stack(key);
splay();
if (are_equals<Key, Compare>(key, KEY(root)))
return root;
return NULL;
}
Uses search and push in splay stack 613b and splay 615.
616b
La busqueda es muy sen
illa porque toda la infraestru
tura se en
uentra implantada por
los metodos privados search and push in splay stack() y splay(). Note que el splay
siempre se realiza, independientemente de que la busqueda sea infru
tuosa o no.
La inser
ion es ligeramente mas
ompli
ada pero, al igual que
on la busqueda, todo
el trabajo es implantado por search and push in splay stack() y splay():
hmiembros p
ubli
os de Gen Splay Tree<Key> 616ai+
(612a) 616a 617
Node * insert(Node * p)
{
if (root == NULL)
{
root = p;
return root;
}
search_and_push_in_splay_stack(KEY(p));
Node * pp = splay_stack.top().node;
if (Compare()(KEY(p), KEY(pp)))
{
LLINK(pp) = p;
push_in_splay_stack(p, &LLINK(pp));
}
else if (Compare()(KEY(pp), KEY(p)))
{
RLINK(pp) = p;
push_in_splay_stack(p, &RLINK(pp));
}
else
{ // clave duplicada
splay();
6.6. Arboles
splay
617
return NULL;
}
splay();
return root;
}
Uses LLINK 296, push in splay stack 613a, RLINK 296, search and push in splay stack 613b,
and splay 615.
617
Para la elimina
ion se tienen dos alternativas: la elimina
ion
lasi
a que ante un nodo
ompleto sele
iona el su
esor o prede
esor, o mediante la opera
ion join() desarrollada
en x 4.9.8. Por ser la mas sen
illa y posiblemente la mas e
az, es
ogemos la segunda
alternativa:
hmiembros p
ubli
os de Gen Splay Tree<Key> 616ai+
(612a) 616b
Node* remove(const Key& key)
{
search_and_push_in_splay_stack(key);
An
alisis de los
arboles splay
Captulo 6. Arboles
de b
usqueda equilibrados
618
busqueda binaria, y las
in
o
lases anteriores de arboles binarios que hemos estudiado,
sugieren que
onsideremos al logaritmo en la fun
ion poten
ial que nos pondere el nivel.
Definici
on 6.10 (Rango de un
arbol) Sea un arbol binario T de n nodos. Sea C(ni)
la
antidad de nodos del sub-arbol
on raz en ni. Enton
es, el \rango de T ", denotado
omo r(T ), se dene
omo:
r(T ) = lg C(T )
(6.38)
Sleator y Tarjan [15 sele
ionaron una fun
ion poten
ial basada en el rango de un
arbol del siguiente modo:
(T )
=
ni
r(ni)
ni T
es interno
=
ni
(6.39)
lg C(ni)
ni T
es interno
En
ierta forma, este poten
ial es reminis
ente al IPL(T ) en el sentido de que de alguna
forma pondera
uan equilibrado esta T . La diferen
ia esta
onsiste en que (T ) a
otado
por O(n lg (n)), mientras que el IPL(T ) por O(n2).
Para un arbol
ompleto el rango es peque~no, por ejemplo, para:
T=
(T ) = lg (9) + lg (5) + 2 lg (3) + 5 lg (1) 8, 66
mientras que
on un arbol mas desequilibrado el rango es mayor; por ejemplo, para:
T=
(T ) = lg (9) + lg (8) + lg (7) + lg (4) + 2 lg (2) + 3 lg (1) 17, 62
Mientras mas equilibrado este un arbol, menor es su poten
ial; analogamente, mientras
menos equilibrado, mayor este es.
Proposici
on 6.10 (Lema de a
eso a un arbol splay - Sleator y Tarjan 1985 [15)
Sea T un arbol splay y k T una
lave. Sean Ci1(k) y Ci(k) las
ardinalidades del
sub-arbol
uya raz es k antes y despues de un paso de un splay de un total de m pasos.
Sean ri1(k) y ri(k) los rangos de k antes y despues de un paso del splay de un total de m
pasos. Enton
es, el
oste amortizado en el i-esimo paso, denido
omo b
i, esta a
otado
omo:
b
i
3ri(k) 3ri1(k)
si i < m
3ri(k) 3ri1(k) + O(1) si i = m
(6.40)
Demostraci
on
b
i = ti +(Ti) i1(Ti1)
(6.41)
Donde Ti1 y Ti son los arboles antes y despues de un paso en el splay entre los tres
posibles tipos denidos. Sea k el nodo que subira de nivel en uno de los pasos del splay,
enton
es:
6.6. Arboles
splay
619
A efe
tos de esta demostra
ion, plantearemos una presenta
ion menos pre
isa del
lema de a
eso
b
i 3ri(k) 3ri1(k) + O(1) , 1 i m
(6.42)
En lo que sigue, trataremos los tres posibles pasos que o
urren en un splay:
1. zig:
b
Ti1 =
Ti =
En este
aso, la dura
ion
onstante equivale a una rota
ion, razon por la
ual asumimos ti = 1. El zig no altera las
ardinalidades de las ramas , y , por lo que
sus rangos se
an
elan en la diferen
ia poten
ial. Por lo tanto, la diferen
ia poten
ial
solo
ontiene a los rangos de los nodos k y b. D este modo:
b
i = 1 + ri(k) + ri(b) ri1(k) ri1(b)
Notemos, por observa
ion de la gura del zig, que Ci1(b) = Ci(k), por lo que
ri(k) ri1(b) = 0, lo que nos deja:
b
i = 1 + ri(b) ri1(k)
Del mismo modo, ri1(b) ri(k), por lo que podemos plantear la desigualdad
siguiente:
b
i 1 + ri(k) ri1(k)
(6.43)
2. zig-zig:
c
Ti1 =
k
Ti =
b
c
Captulo 6. Arboles
de b
usqueda equilibrados
620
la
ual es propor
ional a O(2); o sea el doble del zig. Por lo tanto, el
oste amortizado
se dene
omo:
b
i = 2 + ri(k) + ri(b) + ri(c) ri1(k) ri1(b) ri1(c)
En este
aso, Ci1(c) = Ci(k), lo que ha
e que ri1(c) ri(k) = 0, por lo que la
expresion anterior deviene en:
b
i = 2 + ri(b) + ri(c) ri1(k) ri1(b)
(6.44)
b
i 2 + ri(k) + ri(c) 2ri1(k)
(6.45)
Igual que para el zig, ri1(b) ri1(k) y ri(k) ri(b). Substituimos en la e
ua
ion
anterior y la ha
emos una desigualdad:
Sean:
Ci1(k)
Ci(c)
, y=
Ci(k)
Ci(k)
Puesto que Ci(k) > Ci1(k) y Ci(k) > Ci(c), enton
es x > 0 e y > 0. En a~nadidura,
x + y < 1 lo
ual se eviden
ia expresando las
ardinalidades en fun
ion de sus subarboles:
1 + C() + C()1 + C() + C()
2 + C() + C() + C() + C()
x+y=
=
3 + C() + C() + C() + C()
3 + C() + C() + C() + C()
x=
>Como se a
otara lg(x) + lg(y)? o, di
ho de otro modo, >
ual sera el maximo valor
que tomara lg(x) + lg(y)? La respuesta se trata en el siguiente lema:
Lema 6.9 Sean x, y R | x > 0, y > 0, x + y 1, enton
es, lg (x) + lg(y) 2.
lg(n)
0.5
0
-0.5
-1
-1.5
-2
-2.5
-3
0.25
0.5
0.75
1
n
1.25
1.5
1.75
6.6. Arboles
splay
621
lg(x) + lg(y) = lg
(6.46)
(6.47)
3. zig-zag:
c
Ti1 =
Ti = a
b
i 2 + ri(a) + ri(c) 2ri1(k)
(6.48)
Ci(a)
Ci(c)
, y=
Ci(k)
Ci(k)
En este momento debe ser
laro que x + y < 1, razon por la
ual, apli
ando el
lema 6.9 tenemos que:
x=
lg x + lg y 2 =
C (a)
C (ca)
2 =
lg i + lg i
Ci(k)
Ci(k)
2ri(k) ri(a) ri(c) 2 0
(6.49)
Captulo 6. Arboles
de b
usqueda equilibrados
622
(6.50)
Los tres asos posibles de un paso del splay estan a otados por 3ri(k) 3ri1(k) + O(1)
La opera
ion splay puede imaginarse
omo una se
uen
ia de pasos zig
segun la orienta
ion del nodo
uyo
osto amortizado total puede denirse
omo:
Demostraci
on
b
s =
m
X
i=1
b
i .
m
.
.
.
i+1
k
i
.
.
.
1
El nodo k sube ha
ia la raz mediante m 1 opera
iones zig-zig o zig-zag y una m-esima
opera
ion que podra ser
ualquiera de las tres posibles. Mas pre
isamente sabemos, por
el lema de a
eso, que los m 1 primeros pasos estan a
otados por 3ri(k) 3ri1(k), pues
este valor a
ota tanto al zig-zig
omo al zig-zag; mientras que la ultima opera
ion pudiera
ser
ualquiera de las tres, razon por la
ual es ne
esario a
otarla al maximo; es de
ir,
a 3rm(k) 3rm1(k) + 1.
6.7. Conclusi
on
623
m1
X
3ri(k) 3ri1(k) + 3rm(k) 3rm1(k) + 1
i=1
m1
X
i=1
3ri(k)
m1
X
i=1
3ri1(k)
+ 3rm(k) 3rm1(k) + 1
3r1(k) + 3r2(k) + + 3rm1(k) 3r0(k) + 3r1(k) + + 3rm2(k) + 3rm(k) 3rm1(k) +
= 3rm(k) 3r0(k) + 1
3rm(k) + 1 = 1 + lg n = O(lg n)
Como
orolario a la proposi
ion 6.11 podemos enun
iar que p opera
iones sobre
un arbol splay tienen
oste amortizado de O(p lg n). En efe
to,
omo ya lo expresamos
anteriormente, la fun
ion poten
ial
onsidera la
alidad de equilibrio del arbol, la
ual
uenta para el tiempo de busqueda que realiza una opera
ion de un arbol splay.
6.7
Conclusi
on
Este
aptulo trato el equilibrio de arboles binarios de busqueda. La idea subya
ente es
minimizar la
antidad de nodos a inspe
ionar en una busqueda.
A tenor de lo anterior, estudiamos varias
lases de equilibrio junto
on sus arboles
orrespondientes. En primer lugar,
onsideramos el equilibrio fuerte de Wirth y desarrollamos
un algoritmo O(n lg n) que equilibra un ABB
ualquiera. El desarrollo de este algoritmo
nos permitio per
atarnos de las vi
isitudes y di
ultades inherentes al equilibrio.
El resto del
aptulo se
onsagro a estudiar
in
o
lases de arboles equilibrados: aleatorizados, treaps, AVL, rojo-negro y splay. Cada uno
on sus ventajas y desventajas a
onsiderar segun la apli
a
ion.
El subs
riptor del presente texto ha llevado un estudio empri
o sobre los diferentes
tipos de arboles. Los resultados, sin rigor estadsti
o, mu
ho menos sin expresar intervalos
Captulo 6. Arboles
de b
usqueda equilibrados
624
70
AVL
Rojo-negro
Aleatorizado
Treap
Splay
Binario
Altura mnima
60
50
A
l
t
u
r
a
40
3
+
2
30
20
3
3
+
+
2
3
2
+
3
+
3
2
+
3
2
+
3
2
+
0
10
2
2
2
2
2
2
2
2
+
+ 3
2 2
+ 3
3
+ 3
3
+
2
2
+ 3
+ 3
3
+ 3
3
+
+
2
+ 3
2
3
3
+ 3
3
+ +
3
+
10
n=2
15
20
estudios aun estan en etapa de veri
a
ion y valida
ion estadsti
a. Las medidas donde se expresa
tiempo
orresponden a los valores mas
er
anos al
entro del intervalo de
onanza y fueron realizados
sobre una Intel Pentium III a 800 Mhz.
Medidas sobre arquite
turas diferentes -Spar
y MIPS-, estudios sobre sesgos para veri
ar bondades del
splay, otras metri
as -numero de rota
iones, por ejemplo- y analisis mas exhaustivos aun estan pendientes
y es un
ampo fertil para trabajo.
6.7. Conclusi
on
625
1.07374e+09
AVL
Rojonegro
Aleatorizado
Treap
Binario
Splay
IPL mnimo
3.35544e+07
1.04858e+06
IPL
32768
1024
32
0.03125
0
10
15
20
2**k
Captulo 6. Arboles
de b
usqueda equilibrados
626
16
AVL
Rojo-negro
Aleatorizado
Treap
Binario
14
12
3
+
2
10
se
s
2
2
2
2
2
2 2
2
2
3
+
2
3
2
3
2
3
2
3
2
2 2
2
3
3
3
+
+
+
+ 3
+
3
+
3
+
3
3
+
+
2
3
3
+ +
2
3
+
3
+
3
+
3
+
3
+
3
+ +
2
10
n=2
15
20
AVL
Rojo-negro
Aleatorizado
Treap
Binario
7
6
se
s
627
3
+
2
5
4
3
2
3
+
2
3
+
3
+
2
3
+
2
3
2
+
2
3
+
3
3
2
3 2 +
3
+
3
2
3 3
+
+
3
2 +
2
2 +
+
2 +
2
10
n=2
2
2
2
3
+
2
3
2
+
3
+
2
3
3 +
+
2
3
2
+
3
+
2
3
+
3
15
20
6.8
Notas bibliogr
aficas
La historia de los enfoques de equilibrio de un arbol
omienza por los arboles binarios
de busqueda
lasi
os. A prin
ipio de 1960, los investigadores ya saban que las promesas
de desempe~no de un ABB solo eran vigentes a
ondi
ion de tener se
uen
ias de inser
ion
aleatorias y de no o
urrir ex
esivas elimina
iones.
En 1962 se publi
aron los arboles AVL por Adelson Velsky y Landis [1. Hasta re
ientemente, estos arboles permane
ieron la es
ogen
ia de fa
to en la implanta
ion de tablas de
smbolos en memoria prin
ipal. Aun hoy en da, esta
lase de arbol se usa fre
uentemente
y
onforma el estandar de mu
has apli
a
iones.
Captulo 6. Arboles
de b
usqueda equilibrados
628
6 Esta propor
i
on esta onminis
entemente presente a lo largo de toda la naturaleza, la anatoma humana,
in
lusive. Como ejemplos vanidosos, el ombligo es
onsiderado por los es
ultores
omo la division natural
de todo el
uerpo. Pues bien, la distan
ia desde el suelo al ombligo, dividida entre la distan
ia desde el
ombligo hasta la
abeza es . Del mismo modo, exa
tamente la misma propor
ion es preservada entre la
abeza y la quijada donde esta ubi
ado en la punta de la nariz. Todas las partes del
uerpo humano
respetan esta propor
ion.
La propor
ion en
uestion es que, al dividir un segmento en dos partes, digamos x e y, se satisfaga la
x
siguiente e
ua
ion x+y
on = yx se tiene la e
ua
ion divina: 2 1 = 0.
= yx . Si se realiza la transforma
i
Puesto que la mayora somos
reyentes, en honor a Dios,
reador del Universo -y del Hombre in
lusive es llamado el n
umero divino, el numero de Dios, el radio de oro o, artsti
amente, la se
ion aurea, pues
pare
e que fue la propor
ion que Dios utilizo para
rear nuestro mundo.
El nombre es en honor a Fidias,
onsiderado el mas grande es
ultor de la antigua Gre
ia, autor de la
estatua de Zeus en el monte Olimpo, realizada de marl y oro, aproximadamente en el a~no 450 A.C. Esta
obra, fue in
luida por el poeta Antipater de Sidon, en su
elebre lista de las siete maravillas del mundo
antiguo.
6.9. Ejercicios
629
hasta 1985,
uando Sleator y Tarjan publi
aron los arboles splay [15, que se dispuso de
un arbol logartmi
o en el sentido amortizado. En a~nadidura, este fue uno de los primeros
estudios
onvin
entes sobre el analisis amortizado.
6.9
Ejercicios
127
143
148
108
123
193 30 71 158 185 40 12 35 94 116 110 139 57 81 95 181 114 112 171
118 34 111 125 61 101 45 126 38 12 41 199 54 21 166 136 154 188 174
176 182 108 141 44 173 128 102 65 139 192 37 188 116 88 165 162 9 91
67 109 76 83 175 140 136 35 168 62 181 100 112 197 92 1 173 180 33
193 13 15 50 115 160 25 45 136 79 3 1 14 178 108 62 57 101 75
Para todas las se
uen
ias, asuma la siguiente se
uen
ia de numeros aleatorios entre
1 y 100:
43 19 46 43 6 28 11 5 10 35 4 38 23 48 3 17 30 31 41 12 35 32 44 47 40 46 3 44 32 19
9 39 38 19 28 12 13 18 36 11 50 9 33 44 6 8 29 6 48 13 30 21 16 48 3 14 39 9 1 31 23
48 40 46 34 30 43 3 40 4 15 36 40 41 30 26 44 23 34 50 33 10 49 22 9 37 32 47 26 19
40 39 12 10 5 10 26 36 49 24 21 28 1 48 24 7 14 39 38 42 45 20 5 40 5 31 20 9 50 7
35 13 7 13 19 50 35 45 9 39 3 34 32 17 45 19 41 40 32 8 24 9 17 48 12 44 24 7 33 25
4. Para los siguientes arboles aleatorios, denidos por sus re
orridos prejos, eje
ute
las se
uen
ias de elimina
ion dadas:
(a) Prefijo: 46 11 18 151 102 83 55 70 61 63 79 103 125 124 140 148 173 162 160
181
Secuencia de eliminaci
on: 125 148 18 11 79 124 140 181 102 173
(b) Prefijo: 195 193 105 40 33 5 31 22 50 61 68 62 76 159 151 146 112 137 165 199
Secuencia de eliminaci
on: 31 68 165 22 159 105 112 61 199 151
(
) Prefijo: 132 48 37 28 12 9 95 76 55 70 82 106 102 97 153 135 195 171 165 177
Secuencia de eliminaci
on: 165 177 171 9 76 28 195 48 55 37
(d) Prefijo: 96 89 59 28 21 13 46 30 83 87 174 147 132 109 101 99 162 150 157 19
Secuencia de eliminaci
on: 28 132 13 99 101 150 87 174 157 96
(e) Prefijo: 190 63 43 37 13 3 28 38 55 51 184 177 90 124 122 168 138 162 187 196
Secuencia de eliminaci
on: 90 37 124 190 51 28 122 168 3 13
Para todos los ejer
i
ios asuma la siguiente se
uen
ia de numeros aleatorios entre 1
y 100:
Captulo 6. Arboles
de b
usqueda equilibrados
630
1 83 9 84 64 46 100 65 7 43 20 63 26 61 100 95 95 60 75 50 19 51 21 29 90 69 80 93
71 52 34 40 34 29 67 63 39 57 93 70 31 82 67 93 47 12 99 67 4 59 75 41 93 93 21 93
52 49 50 93 100 39 58 6 95 68 45 63 10 13 4 100 85 1 3 70 77 43 94 35 8 56 71 69 96
80 30 30 85 59 68 35 29 34 97 15 29 44 79 71 26 47 12 67 94 27 30 80 39 79 32 3 49
65 66 89 45 66 93 68 69 81 85 99 86 44 18 80 50 19 69 90 96 35 12 1 72 21 26 27 29
55 52 62 50 19 50 46 68 71
5. Estudie el desempe~no del siguiente algoritmo para eliminar en un arbol aleatorizado.
Si el nodo a eliminar esta in
ompleto, enton
es se efe
tua la elimina
ion tradi
ional.
Si, por el
ontrario, el nodo esta
ompleto, enton
es se realiza un sorteo para es
oger
ual de los nodos, el prede
esor o el su
esor, sera inter
ambiado. El prede
esor debe
1
de ser sele
ionado, mientras que el su
esor debe tener
tener una probabilidad | (T)|
1
una probabilidad de | (T)| . Pruebe o desapruebe que el arbol resultante es aleatorio.
(+)
L
6.9. Ejercicios
631
16. Dise~ne un TAD equivalente a Gen Rand Tree<Key> en el
ual todas las opera
iones
no sean re
ursivas.
17. Eje
ute las siguientes se
uen
ias de inser
ion en un treap:
(a)
(b)
(
)
(d)
(e)
Para todas los ejer
i
ios, use la siguiente se
uen
ia de numeros aleatorios:
33 81 23 16 75 60 14 89 93 99 97 66 38 34 47 49 52 47 21 40
18. Para los siguientes treaps, denidos por sus re
orridos prejos, eje
ute las se
uen
ias
de elimina
ion dadas:
(a) Prefijo: 148 22 16 8 94 51 41 25 30 66 63 128 124 116 113 111 190 173 186 175
Secuencia de eliminaci
on: 157 23 85 165 118 111 70 184 186 19
(b) Prefijo: 86 58 15 29 32 41 47 83 164 129 90 87 102 126 114 106 136 196 174
192
Secuencia de eliminaci
on: 100 31 190 158 84 105 149 10 52 32
(
) Prefijo: 54 89 34 137 180 44 133 112 71 51 7 39 41 84 161 15 69 113 93 59
Secuencia de eliminaci
on: 133 5 51 155 77 178 6 166 186 115
(d) Prefijo: 140 66 38 8 25 15 46 76 110 79 102 83 129 161 145 151 156 155 164
194
Secuencia de eliminaci
on: 22 36 73 63 115 140 123 45 123 26
(e) Prefijo: 122 113 42 22 7 26 62 49 97 172 133 127 153 137 151 167 156 158 168
198
Secuencia de eliminaci
on: 199 49 70 62 150 7 26 118 158 75
19. Dise~ne un algoritmo que
onstruya un treap dados sendos arreglos
on
laves y
prioridades. El algoritmo no puede usar las primitivas del treap.
20. Dise~ne un TAD equivalente a Gen Treap<Key> en el
ual todas las opera
iones no
sean re
ursivas.
21. Dise~ne un algoritmo O(n lg(n)) que efe
tue la union de dos treaps.
22. Dise~ne un algoritmo O(n lg(n)) que efe
tue la interse
ion de dos treaps.
23. Dise~ne un algoritmo O(n lg(n)) que efe
tue la parti
ion (split) de dos treaps.
24. Considere un treap en el
ual la prioridad esta dada por el numero de a
esos. A
mayor numero de a
esos, menor es la prioridad. De este modo el treap devendra
auto ajustante en el sentido de que las
laves re
ientemente a
edidas se en
ontraran
en la raz. Estudie y
ompare este metodo
on la sele
ion aleatoria de prioridades.
632
Captulo 6. Arboles
de b
usqueda equilibrados
25. Eje
ute las siguientes se
uen
ias de inser
ion en un arbol avl:
(a) 235 206 185 156 194 108 137 207 141 93 148 231 75 144 6 239 2 138 30 187 58
244 0 83 55
(b) 207 76 4 226 216 83 129 221 230 81 29 46 201 208 212 56 168 38 78 87 138 215
66 122 154
(
) 247 168 113 141 1 176 52 179 223 106 122 181 100 118 81 21 27 41 228 197 2
123 22 244 212
(d) 144 77 246 193 213 230 82 44 114 195 148 88 231 0 163 109 228 66 28 12 132
18 135 79 74
(e) 39 234 1 242 172 156 108 30 170 241 232 3 140 5 180 100 133 60 24 139 129 19
235 118 236
26. Para los siguientes arboles AVL, denidos por sus re
orridos prejos, eje
ute las
se
uen
ias de elimina
ion dadas:
(a) Prefijo: 136 103 62 22 19 12 55 45 86 70 99 123 121 126 134 181 152 142 147
158 193 189 205 199 235
Secuencia de eliminaci
on: 99 123 121 181 134 193 12 22 45 205 62 189
(b) Prefijo: 128 41 28 21 8 24 29 93 54 49 84 117 94 207 158 146 143 157 182 190
225 211 213 244 235
Secuencia de eliminaci
on: 41 54 29 211 28 235 128 84 24 182 146 8
(
) Prefijo: 110 49 29 11 1 21 39 91 80 77 102 97 191 173 156 151 172 184 181 219
210 209 227 226 230
Secuencia de eliminaci
on: 80 21 49 226 181 39 11 91 29 172 184 97
(d) Prefijo: 151 47 44 7 1 35 46 115 93 71 94 140 126 218 203 184 183 201 192 213
207 232 231 227 243
Secuencia de eliminaci
on: 184 227 213 207 46 47 151 44 231 115 94 7
(e) Prefijo: 172 131 89 69 27 110 106 116 155 141 137 152 158 194 185 176 193
190 222 201 217 244 236 231 245
Secuencia de eliminaci
on: 185 131 201 176 69 194 190 172 217 236 152 158
27. Si
ada nodo de un arbol AVL alma
ena su altura, >
uantos bits se requieren?
28. Cal
ule la
omplejidad de tiempo de la fun
ion is avl().
29. Implante enteramente un TAD que modele arboles AVL en el
ual
ada nodo guarde
su altura. (+)
30. Dise~ne un algoritmo O(n lg(n)) que efe
tue la union de dos arboles AVL.
31. Dise~ne un algoritmo O(n lg(n)) que efe
tue la parti
ion (split) de dos arboles AVL.
32. Dise~ne un algoritmo O(n lg(n)) que efe
tue la interse
ion de dos arboles AVL.
33. Es
riba un algoritmo que determine si un arbol binario es de Fibona
i. Cal
ule su
omplejidad de tiempo.
6.9. Ejercicios
633
Fib(n + 1) 1 + 5
lim
=
.
n
Fib(n)
2
Donde Fib(i) es el i-esimo numero de Fibona
i. (8 puntos)
36. Obtenga una expresion que denote el numero de hojas de un arbol de Fibona
i.
37. Demuestre que la inser
ion en un arbol AVL
ausa a lo mas una rota
ion doble.
38. Demuestre que
lim
Fib(n + 1)
= .
Fib(n)
39. Explique y desarrolle un metodo para re
onstruir un arbol AVL dado el re
orrido
injo de las
laves y los valores de diferen
ia de altura de
ada nodo.
40. Demuestre que para todo arbol AVL, existe una
olora
ion rojo-negro. (+)
41. Dise~ne un algoritmo que examine un arbol binario
ualquiera y determine si es
oloreable.
42. Dise~ne un algoritmo que
oloree de rojo-negro un arbol
ualquiera que haya pasado
el test del ejer
i
io anterior.
43. Eje
ute las siguientes se
uen
ias de inser
ion en un arbol rojo-negro:
(a) 14 52 107 169 66 44 15 116 68 196 51 177 79 171 218 59 9 197 125 84 89 1 213
195 163
(b) 110 199 147 180 79 10 127 240 0 38 104 195 207 49 89 9 153 191 101 54 206 181
(
) 152 15 36 31 124 239 57 90 86 26 129 169 91 133 137 42 99 32 35 102 200 220
95 80
(d) 228 226 97 175 185 161 21 240 227 237 194 74 84 90 193 27 72 51 192 142 55
229 167
(e) 197 25 107 0 62 114 124 221 122 179 105 128 78 29 58 41 208 217 46 158 213
32 66 246 152
44. Para los siguientes arboles AVL, denidos por sus re
orridos prejos, eje
ute las
se
uen
ias de elimina
ion dadas:
(a) Prefijo: 195 143 90 8 0 1 83 58 125 107 156 148 153 188 187 186 193 264 218
217 211 232 225 238 267 265 277 270 278
Secuencia de eliminaci
on: 8 265 193 148 225 107 195 58 0 218 186 125 270
217 277
(b) Prefijo: 160 89 35 20 1 26 32 77 133 95 149 135 159 282 213 175 167 178 190
262 240 269 289 285 297 294 299
634
Captulo 6. Arboles
de b
usqueda equilibrados
Secuencia de eliminaci
on: 262 285 160 269 159 32 240 282 133 299 213 294
26 178 167
(
) Prefijo: 129 62 38 12 6 31 42 84 73 107 93 128 241 157 148 140 143 150 195
173 227 229 276 244 247 291 281 296
Secuencia de eliminaci
on: 195 291 84 42 31 12 281 173 157 129 241 140 276
107
(d) Prefijo: 156 67 23 13 10 22 53 30 64 115 73 89 138 136 151 272 185 177 169
178 229 192 247 292 278 291 294 299
Secuencia de eliminaci
on: 247 229 272 278 294 13 292 177 136 192 73 151
138 64 89
(e) Prefijo: 106 46 39 21 3 37 40 41 69 55 53 90 70 93 92 229 165 128 125 110 130
159 183 174 224 196 293 238 260 297
Secuencia de eliminaci
on: 159 125 130 55 37 183 297 293 165 41 3 46 196
92 21
45. (Tomado y tradu
ido de Parberry [13) Dibuje un arbol AVL para el
ual la elimina
ion de un nodo requiera dos rota
iones dobles. Dibuje el arbol. identique el
nodo a eliminar y explique porque dos rota
iones son ne
esarias y su
ientes. (+)
46. (Tomado y tradu
ido de Parberry [13) Demuestre que n N existe un arbol AVl
para el
ual la elimina
ion de un nodo requiere exa
tamente n rota
iones. Indique
omo se
onstruye tal arbol, indique de manera general el nodo a eliminar y explique
porque n rota
iones son ne
esarias.
47. Dise~ne un algoritmo O(n lg(n)) que efe
tue la union de dos arboles rojo-negro.
48. Dise~ne un algoritmo O(n lg(n)) que efe
tue la parti
ion (split) de dos arboles rojonegro.
49. Dise~ne un algoritmo O(n lg(n)) que efe
tue la interse
ion de dos arboles rojo-negro.
50. Eje
ute las siguientes se
uen
ias de inser
ion en un arbol splay:
(a) 173 220 204 95 138 208 247 264 154 7 244 167 283 188 187 97 12 168 238 197
166 253 270 291 186 160 59 60 250 117
(b) 6 37 144 285 105 259 80 65 106 205 32 141 24 150 3 30 64 7 57 16 187 101 293
20 73 14 147 93 79 153
(
) 139 200 91 141 37 237 221 63 33 214 224 297 21 274 195 278 243 100 196 256
148 252 163 206 3 77 8 276 99 281
(d) 112 129 96 71 237 86 79 45 255 226 36 64 225 240 167 276 7 239 37 232 115 175
264 138 22 33 299 229 161 111
(e) 271 181 204 288 121 128 105 254 81 162 247 35 56 117 115 174 253 255 208 61
239 236 66 161 145 70 137 258 148 107
51. Para los siguientes arboles AVL, denidos por sus re
orridos prejos, eje
ute las
se
uen
ias de elimina
ion dadas:
6.9. Bibliografa
635
(a) Prefijo: 77 74 18 6 64 21 38 72 286 82 83 88 227 205 98 130 196 147 284 275
271 236 230 265 240 259 270 291 295 298
Secuencia de eliminaci
on: 286 298 295 196 83 227 77 64 147 236 38 88 205
6 21
(b) Prefijo: 277 275 10 3 157 156 59 40 47 93 64 78 87 150 234 160 231 190 162
165 186 209 225 211 218 255 270 290 291 293
Secuencia de eliminaci
on: 10 40 47 255 186 162 87 150 93 157 160 231 277
290 293
(
) Prefijo: 216 214 188 186 171 130 122 23 16 10 7 67 98 92 75 90 83 113 166 159
165 202 200 213 234 262 251 266 269 292
Secuencia de eliminaci
on: 266 213 165 122 75 7 83 166 23 269 292 186 16
67 216
(d) Prefijo: 64 60 55 10 6 15 26 19 30 67 72 247 245 166 93 90 159 143 131 117
173 238 195 181 248 252 261 253 256 284
Secuencia de eliminaci
on: 15 10 90 26 60 166 238 67 248 64 173 256 117 72
6
(e) Prefijo: 60 27 20 15 12 5 266 182 73 136 89 134 97 90 105 115 168 241 222 213
190 188 184 198 199 211 259 251 286 293
Secuencia de eliminaci
on: 198 27 60 266 15 293 89 20 241 115 213 5 73 136
105
52. Dibuje el arbol AVL resultante de eliminar el nodo 53 del arbol de la gura 6.12.
53. Dise~ne un algoritmo que efe
tue la parti
ion de un arbol splay. Demuestre que la
altura del arbol resultante es logartmi
a.
Bibliografa
[1 G. M. Adel'son-Vel'skii and E. M. Landis. An algorithm for the organization of
information. Soviet Mathemati
s Doklady, 3:1259{1263, 1962.
[2 Alfred V. Aho, John E. Hop
roft, and Jerey D. Ullman. The Design and Analysis
of Computer Algorithms. Addison-Wesley, Reading, MA, USA, 1974.
[3 Rudolf Bayer. Symmetri
binary B-trees: Data stru
ture and maintenan
e algorithms.
A
ta Informati
a, 1(4):290{306, November 1972.
[4 Rudolf Bayer and Edward M. M
Creight. Organization and maintenan
e of large
ordered indi
es. A
ta Informati
a, 1:173{189, 1972.
[5 E. W. Dijkstra. In honour of bona
i. In F. L. Bauer and M. Broy, editors, Program
Constru
tion, volume 69 of Le
ture Notes in Computer S
ien
e, pages 49{50.
Springer Verlag, 1978.
[6 http://www.gnu.org/software/gsl/.
[7 Guibas and Sedgewi
k. A di
hromati
framework for balan
ed trees. In FOCS: IEEE
Symposium on Foundations of Computer S
ien
e (FOCS), pages 8{21, 1978.
636
Captulo 6. Arboles
de b
usqueda equilibrados
Captulo 7
Grafos
De nuestros sentidos sensoriales, pare
e que la vista es el que o
upa la mayor parte de
nuestra per
ep
ion. Nuestra memoria y re
uerdo estan impregnados en gran medida de
imagenes visuales. En mu
has o
asiones preferimos tratar
on imagenes porque nos son
mas
on
retas a nuestra per
ep
ion y entendimiento que los
on
eptos abstra
tos. Cuando
lidiamos
on lo puramente abstra
to, es muy posible que aso
iemos una imagen visual.
Cuando es
u
hamos \tres", por ejemplo, es posible que se nos aparez
a una imagen rela
ionada a una tripli
idad o al dgito \3".
En la
onstru
ion del
ono
imiento humano suelen utilizarse imagenes arti
iales para
expresar mejor ideas y
on
eptos, o para sintetizarlas en una imagen que englobe y resuma
los asuntos de interes. Las imagenes de este tipo son llamadas
omunmente \gra
os". Los
gra
os
artesianos; es de
ir,
urvas matemati
as dibujadas en el eje
artesiano de
oordenadas, permiten mirar ampliamente el
omportamiento de una fun
ion;
omportamiento
que puede estar o
ulto en la mera formula. Esta
lase de gra
o es arti
ial en el sentido
de que no se en
uentra naturalmente; o sea, no se
orresponde
on una imagen per
ibida
en el mundo natural.
Existe una amplia variedad de problemas en la
ual se deben expresar rela
iones entre
osas de algun tipo. Quiza un buen ejemplo para este tiempo lo
onstituya un mapa vial
que represente una red de
arreteras entre
iudades. En la jerga
omputa
ional, a este tipo
de gra
o se le denomina formalmente grafo.
El termino \grafo" proviene del griego o (grafos), el
ual signi
a es
ritura. La
es
ritura es expresion visual de eso tan maravilloso y misterioso
omo lo es el lenguaje.
Cuando leemos, no requerimos la presen
ia del es
ritor , sino la vision y entrenamiento
omo le
tor. Los gra
os
onstituyen una de las primeras formas humanas de representar
ono
imiento y la
omputa
ion y programa
ion no es
apan a la riqueza expresiva de lo
gra
o.
En terminos matemati
os, una rela
ion es un sub
onjunto (R A B) de algun
produ
to
artesiano entre dos
onjuntos A y B. En esta se
ion nos interesan rela
iones entre
osas de un mismo tipo, por lo que los
onjuntos origen y destino
son los mismos y la rela
ion,
ali
ada de \binaria", se reere al produ
to
artesiano A A. Hay varios esquemas gra
os para expresar una rela
ion binaria. En
matemati
a es fre
uente utilizar los diagramas sagitales, pero estos son engorrosos
para las rela
iones binarias. En lo que
on
ierne este estudio, existe una repre1
1 Aunque pueden perderse algunas emo iones e interpreta iones involu radas uando se es u ha dire tamente al le tor.
637
638
Captulo 7. Grafos
senta
ion gra
a que permite mirar y entender mejor una rela
ion. La gura 7.1 ilustra la
rela
ion R = {(a, b), (a, c), (a, d), (b, a), (b, c), (b, d), (b, f), (b, e), (c, a), (c, b), (c, g), (c, f),
(c, h), (d, a), (d, b), (d, e), (g, c), (g, h), (f, c), (f, b), (f, h)} AA, A = {a, b, c, d, e, f, g, h},
7.1. Fundamentos
639
7.1
Fundamentos
2 Por
lo general, los programadores usan el termino \nodo", mientras que los matemati
os preeren
el termino \verti
e". Del mismo modo, los programadores utilizan el vo
ablo \ar
o" mientras que los
matemati
os se in
linan por usar \arista\. Puesto que este texto es mas para programadores, se usaran en
preferen
ia los terminos nodo y arco, respe
tivamente.
640
Captulo 7. Grafos
29
M
5
28
16
L
6
14
10
15
17
25
N
24
13
12
11
18
18
22
23
I
27
20
21
26
7
C
7.1. Fundamentos
641
642
Captulo 7. Grafos
(a)
(b)
B
1
J
7
H
8
14
9
D
13
12
11
10
17
E
16
15
18
17
3 En
7.1. Fundamentos
643
M
5
28
L
6
14
10
16
17
25
N
24
13
12
11
18
22
23
I
27
20
26
(a)
(b)
H
15
16
L
6
10
17
E
13
25
18
I
27
20
21
644
Captulo 7. Grafos
(a)
B
(b)
7.2
Esen
ialmente, existen dos esquemas para representar grafos en la memoria de un
omputador: matri
es o listas enlazadas.
7.2.1
Matrices de adyacencia
Puesto que un grafo representa visualmente una rela
ion, las matri
es pare
en ser la estru
tura \natural". De he
ho, estas tienen una tradi
ion de uso exitosa en la matemati
a
para expresar y manipular rela
iones binarias y,
onse
uentemente, grafos.
Una matriz que represente a un grafo se denomina de adyacencia. Se trata de
una matriz
uadrada M[V V]
uyas entradas M(i, j) representan la
onexion entre el
nodo i y j. La mnima informa
ion ne
esaria en
ada entrada es un bit que indique la
existen
ia o no del ar
o. El grafo de la gura 7.12 se representa mediante la matriz mostrada
en la gura 7.13. Un
ero indi
a la ausen
ia de ar
o, mientras que un uno su presen
ia.
645
6
A
B
1
D
4
646
Captulo 7. Grafos
A
0
1
1
1
0
0
B C D E F G
1 1 1 0 1 1
0 1 0 1 1 1
1 0 1 1 0 0
0 1 0 1 0 0
1 1 1 0 1 0
1 0 0 1 0 1
1 1 0 0 0 1 0
A
B
C
D
E
F
G
646
la matriz no representa a un digrafo, enton
es basta
on solo guardar los elementos sobre
o debajo de la diagonal. Del mismo modo, si el grafo no
ontiene lazos, enton
es no es
ne
esario
onsiderar la diagonal.
Una matriz tradi
ional requiere un bloque de memoria
ontiguo propor
ional a O(V 2).
A medida que aumente V , es mas dif
il para un manejador de memoria en
ontrar un
bloque
ontiguo. Un esquema fun
ionalmente equivalente,
onsiste en apartar un arreglo de arreglos
uya representa
ion pi
tori
a se muestra en la gura 7.14. En ese
aso, el
manejador de memoria requiere apartar V bloques
ontiguos de tama~no V , lo
ual es mas
fa
il que uno solo de V V . En C++, una matriz de adya
en
ia de ar
os del tipo T se
de
lara e ini
ia del siguiente modo:
hini
ializar arreglo de arreglos 646i
T ** matriz = new T * [V];
Este
odigo puede modi
arse para usar el tipo BitArray, lo que se deja
omo ejer
i
io.
A
647
Una matriz de adya
en
ia requiere
ono
er a priori el numero de nodos. Por esta razon,
esta representa
ion no es
onveniente para situa
iones en las
uales al grafo se le inserten
y eliminen nodos o ar
os dinami
amente.
7.2.2
Listas de adyacencia
Dado un nodo v pertene
iente a un grafo, su lista de adya
en
ia se
ompone por los nodos
a los
uales v esta
one
tado mediante ar
os. De este modo, para representar un grafo en
memoria, se utilizan listas de adya
en
ia para
ada nodo del grafo. La gura 7.15 ilustra
un ejemplo.
A
4 Se
usa \verti e" en lugar de \nodo" para distinguirlo de nodo en la lista de adya en ia.
648
Captulo 7. Grafos
use una matriz de adya
en
ia el
ual probablemente tiene sentido matemati
o, pero no el
gra
o que se~nalamos al prin
ipio del
aptulo.
La ultima ventaja que apre
iamos en la representa
ion
on listas es su
exibilidad para
el manejo de memoria y dinamismo. Puesto que se aparta memoria en fun
ion de nodos
y ar
os, es mas simple para el manejador de memoria obtener bloques; aun en es
enarios
en que haya po
a memoria o esta este muy fragmentada. Por otro lado, por su
ara
ter
de listas, esta representa
ion es mu
ho mas dinami
a para opera
iones inter
aladas de
inser
ion y elimina
ion; sobre todo si las listas son doblemente enlazadas.
Como desventaja podemos identi
ar en esen
ia un problema; la veri
a
ion de existen
ia de un ar
o es en el promedio o peor de los
asos O(V), pues es ne
esaria una busqueda
en una lista de adya
en
ia. Con una matriz, en
ambio, esta veri
a
ion es O(1) o O(lg(n)).
Con una matriz de adya
en
ia, los ar
os apare
en en el orden dado por sus las
(o
olumnas). En el ejemplo de la gura 7.12, los ar
os ha
ia el nodo D siempre apare
en
primero que los que van ha
ia el nodo E. Con listas de adya
en
ia, el orden de apari
ion
de los ar
os y, por tanto, su orden de pro
esamiento es relativo a su posi
ion dentro de la
lista de adya
en
ia. A la vez, el orden de apari
ion depende del orden de inser
ion en el
grafo. Aparte de la suerte, por lo general esto no pare
e tener impli
a
iones de desempe~no.
7.3
648
En esta se
ion presentaremos el dise~no e implanta
ion del TAD List Graph<Node, Arc>,
el
ual pretende modelizar grafos generales que puedan usarse para la mayora de
lases
de apli
a
iones
ono
idas sobre grafos.
A pesar de que en su sentido visual un grafo pare
e ser muy general, esta abstra
ion
de
on
epto se usa para resolver problemas muy distintos. Conse
uentemente, en
ontrar
un patron general que permita modelizar un TAD que fun
ione para todas las
lases de
problemas sobre grafos es algo dif
il. Ademas,
omo ya debe ser bien sabido, luego de
insistir en el prin
ipio n-a-n (x 1.4.2 (pagina 22)), la generalidad tiene sus
ostes en el
sentido de que algunas apli
a
iones no requieren la maquinaria desplegada para tratar
on
todos los
asos posibles.
El
odigo pertinente a los TAD fundamentales sobre grafos se espe
i
a en el
ar
hivo htpl graph.H 648i,
uya estru
tura es
omo sigue:
htpl graph.H 648i
hMa
ros
hBits de
hNodo
de grafo 651bi
hAr o
de grafo 657bi
de grafo 690bi
hGrafos 649i
649
hDigrafos 650bi
hImplanta
i
on
7.3.1
649
Grafos
Un List Graph<Node, Arc> es una
lase que modeliza un grafo implementado
on listas
de adya
en
ia. Sus parametros tipo son el node y el ar
o, y se dene del siguiente modo:
hGrafos 649i
(648)
template <typename __Graph_Node, typename __Graph_Arc>
class List_Graph
{
hTipos de List Graph<Node, Arc> 650ai
hMiembros privados de List Graph<Node, Arc> 669
i
ubli
os de List Graph<Node, Arc> 651ai
hMiembros p
5 Por
650
Captulo 7. Grafos
650a
Para poder usar un grafo List Graph<Node, Arc> es ne
esario haber denido los tipos
de nodos y de ar
os,
uestion a la que nos abo
aremos en las proximas sub-se
iones.
List Graph<Node, Arc> sera ampliamente utilizado para la programa
ion generi
a;
es de
ir, para programar algoritmos que fun
ionen para
lases generales de grafos.
En la programa
ion generi
a es a menudo ne
esario
ono
er los subtipos de
List Graph<Node, Arc>, los
uales se denen a
ontinua
ion
hTipos de List Graph<Node, Arc> 650ai
(649)
public:
typedef
typedef
typedef
typedef
__Graph_Node
__Graph_Arc
typename Node::Node_Type
typename Arc::Arc_Type
Node;
Arc;
Node_Type;
Arc_Type;
La fun
ion es generi
a y re
ibe un List Graph<Node, Arc> llamado GT. La primera lnea
de
lara un puntero a un ar
o; mientras que la siguiente de
lara una variable del mismo
tipo que el atributo aso
iado al ar
o. Notemos que este
odigo es
ompletamente generi
o
en el sentido de que fun
iona para
ualquier
lase de
ombina
ion de tipos de nodos y
ar
os del grafo.
En lo que sigue de las siguientes sub-se
iones, men
ionaremos opera
iones sobre
List Graph<Node, Arc> a nivel de interfaz y no de implanta
ion.
7.3.2
650b
651
{
List_Digraph();
List_Digraph(const List_Digraph & dg);
List_Digraph & operator = (List_Digraph & dg);
};
Denes:
List Digraph, used in
hunks 671d and 689a.
Uses List Graph 649.
651a
651b
Nodos
Para representar a un nodo de un grafo, se utiliza el TAD Graph Node<Node Type>,
uya
espe
i
a
ion general es
omo sigue:
hNodo de grafo 651bi
(648)
template <typename Node_Info>
struct Graph_Node : public Dlink
{
typedef Graph_Node Node;
typedef Node_Info Node_Type;
friend class Arc_Node;
hMiembros de Graph Node<Node Type> 652ai
};
Denes:
Graph Node, used in
hunk 652d.
Uses Arc Node 676
and Dlink 90.
hNodo
listas de adya
en
ia. Un Graph Node<Node Type> es una
lase plantilla
uyo parametro es
el tipo de objeto aso
iado al nodo y que se denomina Node Info. Si se desea, por ejemplo,
un grafo de
iudades, enton
es la lnea siguiente:
Graph_Node<Ciudad> * nodo;
652
Captulo 7. Grafos
652a
ni
a que todos sus miembros son a
esibles por omision, lo
ual pudiera interpretarse
omo una
ontraven
ion al prin
ipio de o
ultamiento de informa
ion. Sin embargo, el
tipo Graph Node<Node Type> esta destinado para utilizarse desde el grafo y no dire
tamente desde un puntero o referen
ia a Graph Node<Node Type>. Estas
onsidera
iones tambien se apli
an para el tipo Graph Arc<Arc Type> (x 7.3.4 (pagina 657)).
Graph Node<Node Type> y Graph Arc<Arc Type> se de
laran struct para que
las
lases List Graph<Node, Arc> y List Digraph<Node, Arc> puedan manipularlas sin restri
iones, pero debe insistirse en que Graph Node<Node Type> y
Graph Arc<Arc Type> no deben usarse dire
tamente, sino a traves de una instan
ia de
lase List Graph<Node, Arc>.
El atributo de un nodo se guarda en el miembro dato node info, el
ual se de
lara
omo sigue:
hMiembros de Graph Node<Node Type> 652ai
(651b) 652b
Node_Info
652b
node_info;
652
Dentro de un List Graph<Node, Arc>,
ada nodo pertene
e a una lista
ir
ular doblemente enlazada de nodos. El enla
e dentro de esa lista es this mediante heren
ia publi
a
de Dlink.
La
antidad de ar
os salientes de hNodo de grafo 651bi se alma
ena en un atributo:
hMiembros de Graph Node<Node Type> 652ai+
(651b) 652b 652d
size_t
652d
num_arcs;
Graph_Node(Graph_Node * node)
: node_info(node->get_info()), num_arcs(0), counter(No_Visited), cookie(NULL)
{
/* empty */
}
Uses Graph Node 651b.
653
653a
Inserci
on de nodos
Para la
omprension de esta sub-se
ion y de las sub-siguientes es ne
esario aprehender que
un grafo List Graph<Node, Arc> usa objetos de tipo List Graph<Node, Arc>::Node
y no estri
tamente de tipo Graph Node<Node Type>.
Aunque
sin
duda
debera
basarse
en
Graph Node<Node Type>,
List Graph<Node, Arc>::Node podra ser una deriva
ion. Por esta razon, a efe
tos de
la
ompletitud de tipos, es ne
esario insistir en que en un List Graph<Node, Arc> el
nodo es de tipo List Graph<Node, Arc>::Node.
He
ha la a
laratoria anterior podemos hablar de la inser
ion de un nodo en un
List Graph<Node, Arc>. Hay dos opera
iones:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 651a 653b
inline Node * insert_node(Node * node);
La primera version toma un nodo ya
reado, es de
ir,
uya
onstru
ion ya fue realizada, y lo inserta dentro del grafo. La segunda aparta la memoria para un objeto de tipo
List Graph<Node, Arc>::Node, le asigna el atributo node info y luego lo inserta dentro
del grafo.
Si no hay memoria su
iente para
rear el nodo, enton
es se genera la ex
ep
ion
estandar bad alloc.
A efe
tos de no ralentizar el desempe~no dinami
o de List Graph<Node, Arc>,
insert node() no realiza ninguna valida
ion de
orre
titud; por ejemplo, la doble inser
ion.
7.3.3.2
653b
Eliminaci
on de nodos
remove node() elimina del grafo el nodo node junto a todos sus ar
os in
identes y adya
entes. Toda la memoria o
upada por el nodo y sus ar
os es liberada.
Bajo el mismo espritu de no gastar
i
los en valida
ion, remove node() no realiza
veri
a
ion de
orre
titud; por ejemplo, la elimina
ion de un nodo que no pertenez
a al
grafo .
6
7.3.3.3
Si bien las opera
iones de inser
ion devuelven el puntero al nodo insertado, lo que permite
\re
ordarlo", puede requerirse a
eso a los nodos desde un List Graph<Node, Arc>. La
6 Las primeras versiones de List Graph<Node, Arc>
ontenan invariantes que en
ierto modo permitan
validar su uso. No obstante, se eliminaron porque la degrada
ion de desempe~no para algoritmos prototpi
os
era tan severa que impeda una
odi
a
ion produ
tiva.
654
654a
Captulo 7. Grafos
654b
B
usqueda de nodos
654
Dado un grafo, puede plantearse la busqueda de algun nodo que reuna algunas
ara
tersti
as. List Graph<Node, Arc> ofre
e tres formas de busqueda, se
uen
iales, es de
ir
que su
oste es O(n) (|V| = n); por lo que no se re
omienda su uso si la busqueda es muy
fre
uente.
Es menester se~nalar que si la apli
a
ion requiere busquedas fre
uentes, enton
es es
preferible indizar los nodos en alguna estru
tura de datos espe
ial; una tabla hash o alguna
lase de arbol binario de busqueda.
El primer tipo de busqueda esta dado por:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 654b 654d
template <class Equal>
Node * search_node(const Node_Type & node_info);
Uses search node.
654d
En este
aso, se realiza una busqueda se
uen
ial
on
riterio de igualdad Equal sobre los
atributos del nodo. La
lase Equal permite estable
er algun
riterio sele
tivo sobre alguna
parte de los atributos. Si se desean
omparar enteramente los atributos, enton
es se puede
usar la siguiente version por omision:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 654
654e
Node * search_node(const Node_Type & node_info);
Uses search node.
654e
node belong to graph() retorna true si node es parte del grafo; false de lo ontrario.
A ve
es se requiere bus
ar un nodo
on
ara
tersti
as que no son parte de sus atributos tpi
os (
lase Node::Node Type o Node Info). Por ejemplo, algun
al
ulo alma
enado
en el
ookie o alguna informa
ion
olo
ada por deriva
ion de Graph Node<Node Type>.
Puesto que no hay manera de
ono
er a priori estas
lases de
ir
unstan
ias, la uni
a forma
que se nos o
urre para espe
i
ar las
ara
tersti
as de busqueda es a traves de un puntero
655a
655
opa
o. As pues, nuestra ultima
lase de busqueda se realiza de la siguiente forma:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 654e 657a
template <class Equal>
Node * search_node(void * ptr);
Uses search node.
655b
Iterador de nodos
Dado un List Graph<Node, Arc> hay una manera de re
orrer todos sus nodos. Para
ello, List Graph<Node, Arc> exporta la siguiente sub
lase:
hIteradores de List Graph<Node, Arc> 655bi
(649) 656a
struct Node_Iterator : public Dlink::Iterator
{
Node_Iterator() { /* empty */ }
Node_Iterator(List_Graph & _g);
Node_Iterator(const Node_Iterator & it);
Node_Iterator & operator = (const Node_Iterator & it);
Node * get_current_node();
};
Denes:
Node Iterator, used in
hunks 657a, 666{68, 674, 687b, 708, 726
, 749a, 757, 760, 765b, 814
, and 822a.
Uses Dlink 90 and List Graph 649.
La opera
ion get current node() retorna el nodo a
tual en donde se en
uentra el iterador.
Node Iterator re
orre todos los nodos del grafo independientemente de sus rela
iones
de
one
tividad. Puesto que deriva de Dlink::Iterator, las opera
iones de desplazamiento y ubi
a
ion son las mismas: next(), prev() y has current().
El orden de visita de Node Iterator debe
onsiderarse indeterminado.
La siguiente fun
ion ejempli
a el re
orrido de todos los nodos del grafo y la impresion,
para
ada uno de ellos, del numero de ar
os:
template <class GT>
void recorrer(GT & g)
{
for (typename GT::Node_Iterator it(g); it.has_current(); it.next())
print("%d\n", it.get_current_node()->num_arcs);
}
Existe una lase de iterador el ual re orre los ar os adya entes de un nodo. Posiblemente este es el iterador mas popular e importante, pues es la base de ualquier re orrido
656
656a
Captulo 7. Grafos
public:
hMiembros
};
Denes:
Node Arc Iterator, used in hunks 656, 661 , 681a, 698b, 702, 705, 706, 709b, 710, 712, 713, 715, 719a,
720, 724, 727, 738
, 740, 745, 746, 748, 757d, 760, 765b, 787, 797
, 799a, and 818b.
Uses Dlink 90.
656b
656
Interesante observar que el objeto aso
iado a un Node Arc Iterator es un nodo
pertene
iente a un grafo y no el grafo mismo.
Node Arc Iterator requiere el operador de asigna
ion, de modo tal que se puedan
opiar estados temporales de itera
ion. Para ello requerimos el operador =:
hMiembros p
ubli
os de iterador de Node 656bi+
(656a) 656b 656d
inline Node_Arc_Iterator & operator = (const Node_Arc_Iterator & it);
Uses Node Arc Iterator 656a.
656d
Puesto que Node Arc Iterator es, por deriva
ion, del tipo Dlink::Iterator, este
ontiene todos sus metodos aso
iados (next(), prev(), has current(), et
etera). El objeto
a
tual del iterador se a
ede mediante:
hMiembros p
ubli
os de iterador de Node 656bi+
(656a) 656
680
inline Arc * get_current_arc();
inline Node * get_tgt_node();
get current arc() retorna el ar o a tual (de tipo Arc), mientras que get tgt node()
retorna el nodo destino del ar
o a
tual (el nodo origen es el aquel sobre el
ual se itera).
get tgt node() es muy u
til porque en este
aso s se distingue el nodo destino; es de
ir,
aquel que esta
one
tado al extremo del ar
o a
tual del nodo origen, sobre el
ual se esta
iterando.
Node Arc Iterator es el prin
ipal me
anismo para manipular grafos implantados mediante listas enlazadas. Es importante desta
ar que los ar
os de un nodo se visitan en
un orden indeterminado, el
ual, debe
onsiderarse aleatorio por
ualquier apli
a
ion que
haga uso de Node Arc Iterator.
Operaciones gen
ericas sobre nodos
657a
657
En o
asiones se requiere realizar una opera
ion espe
a sobre todos los nodos;
por ejemplo,
olo
ar algun valor ini
ial de atributo. Para tales efe
tos se proveen las
siguientes rutinas, las
uales, por su sen
illez, se implantan dire
tamente:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 655a 658a
template <class Operation> void operate_on_nodes()
{
for (Node_Iterator itor(*this); itor.has_current(); itor.next())
Operation () (*this, itor.get_current_node());
}
Como se ve, sendas rutinas re
orren
ada nodo del grafo g de tipo generi
o GT y
efe
tuan la opera
ion Operation () () sobre
ada nodo. La segunda version permite
pasar parametros a traves del puntero ptr.
7.3.4
657b
Arcos
657d
(657b) 665
arc_info;
(657b) 676a
658
Captulo 7. Grafos
Arc<Carretera> * arco;
658a
658b
Algo que s tiene sentido es
ono
er
ual es el nodo que
one
ta un ar
o dado un nodo
origen. Para esto, se provee la siguiente primitiva:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 658a 658
inline Node * get_connected_node(Arc * arc, Node * node);
Uses get connected node 676b.
658
La
ual siempre retorna el nodo
one
tado a node, por el ar
o arc independientemente de
que sea un grafo o digrafo.
O
asionalmente, puede ser ne
esario indagar si un nodo esta o no rela
ionado
on otro
nodo a traves de un ar
o. Para ello, se propor
iona la primitiva siguiente:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 658b 658d
inline bool node_belong_to_arc(Arc * arc, Node * node) const;
658d
Inserci
on de arcos
Para denir un ar
o en un grafo deben haberse denido e insertado en el grafo sus dos
nodos mediante las primitivas
orrespondientes denidas en x 7.3.3.1 (pagina 653). Con
las dire
iones de los nodos, puede invo
arse la siguiente primitiva:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 658
659a
inline Arc * insert_arc(Node *
src_node,
Node *
tgt_node,
const typename Arc::Arc_Type & arc_info);
Uses insert arc 682b.
659
src node y tgt node son los nodos que rela iona el ar o. Si se trata de un grafo, enton es
el orden entre los nodos no tiene importan
ia. Si, por el
ontrario, se trata de un digrafo,
enton
es src node es el nodo adya
ente y tgt node el in
idente. arc info es el valor de
atributo del ar
o
on que se desea
rear el ar
o.
insert arc() aparta la memoria requerida para el ar
o,
onstruye el ar
o para los
valores espe
i
ados por los parametros, lo inserta en el grafo y retorna la dire
ion de
memoria del nuevo ar
o. Si no hay su
iente memoria, se genera la ex
ep
ion bad alloc.
En el
aso de que List Graph<Node, Arc> opere
on una version derivada de
Graph Arc<Arc Type>, el usuario debe ini
iar los atributos de la
lase derivada; bien
sea inmediatamente despues de la inser
ion, o por espe
i
a
ion en los
onstru
tores de
la
lase derivada. Por ejemplo, sea X Arc una
lase derivada de Graph Arc<Arc Type>. Si
List Graph<Node, Arc> opera
on ar
os de tipo X Arc, enton
es,
uando se inserta el
ar
o, el usuario debe tomar el puntero al ar
o re
ien
reado y asignar los atributos extendidos por deriva
ion de X Arc. Tambien, el usuario puede espe
i
ar en los
onstru
tores
X Arc::X Arc valores por omision.
7.3.4.2
659a
Eliminaci
on de arcos
Para eliminar un ar
o, solo se requiere
ono
er su dire
ion y enton
es valerse del metodo
siguiente:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 658d 659b
inline void remove_arc(Arc * arc);
Uses remove arc 684.
659
el
ual retorna un ar
o
ualquiera del grafo. No deben ha
erse suposi
iones a
er
a de
ual
ar
o sera retornado. Sin embargo, los ar
os dentro del grafo pueden ordenarse mediante:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 659b 659d
template <class Compare> inline void sort_arcs();
Uses sort arcs 689d.
659d
660
660a
Captulo 7. Grafos
B
usqueda de arcos
Para los ar os existen uatro tipos de busqueda, los uales se enun ian a ontinua ion:
1. Busqueda por atributo Arc::Arc Type o Arc Info . en este
aso podemos usar:
660b
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 660a 660
7
La primera version usa el
riterio de
ompara
ion Equal, lo que permite dis
ernir
alguna parte de los atributos en la busqueda. La segunda version invo
a dire
tamente
al operador bool Node Type::operator == (const Node Type & info).
2. Prueba de pertenen
ia: realizada por:
660
hMiembros p
ubli
os de List Graph<Node,
Arc> 651ai+
bool arc_belong_to_graph(Arc * arc);
Uses arc belong to graph 675a.
El
ual retorna la dire
ion de un ar
o en
aso de existir alguno entre los nodos
src node y tgt node.
La primitiva fun
iona para digrafos.
En
aso de tratarse de un multigrafo (o multidigrafo) y haber mas de un ar
o entre
los nodos involu
rados, se retorna
ualquiera entre los redundantes sin
onsidera
ion
de algun
riterio espe
o.
4. Busqueda espe
ializada: este es el
aso
uando en la busqueda se desea distinguir
algun dato que no es parte de los atributos del ar
o (Arc Type).
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 660d 661b
660e
template <class Equal>
Arc * search_arc(void * ptr);
Uses search arc 675a.
La rutina retorna la dire
ion del ar
o que satisfa
e igualdad dada por bool
Equal::operator () (Arc * arc, void *ptr), o NULL en
aso de que se hayan
re
orrido todos los ar
os sin satisfa
er el
riterio de igualdad.
Todas las busquedas son O(n) (n = |E|) para el peor
aso (busqueda fallida).
7 Re ordemos
661
Arc * get_current_arc();
};
Denes:
Arc Iterator, used in
hunks 661b, 666{68, 675a, 687b, 749b, 775b, 776b, 816, and 821.
Uses Dlink 90 and List Graph 649.
Al igual que
on los iteradores de este texto, Arc Iterator
ontiene las fun
iones tpi
as
next(), prev() y has current() (por deriva
ion de Dlink::Iterator). La obten
ion del
ar
o de visita a
tual se realiza mediante get current arc().
Si los ar
os no han sido ordenados, enton
es el orden de visita es indeterminado; de lo
ontrario, el iterador visitara los ar
os segun el orden estable
ido en la ultima invo
a
ion
a sort arcs().
Operaciones gen
ericas sobre arcos
661b
Al igual que para los nodos, es posible efe
tuar opera
iones generi
as sobre todos los
nodos del grafo. Para ello, se proveen las siguientes opera
iones:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 660e 661
template <class Operation> void operate_on_arcs()
{
for (Arc_Iterator itor(*this); itor.has_current(); itor.next())
Operation () (*this, itor.get_current_arc());
}
661
662
Captulo 7. Grafos
7.3.5
En mu
hos
asos, los algoritmos sobre grafos requieren mantener un estado de
al
ulo en
sus nodos y ar
os. Quiza el
aso mas
omun sea \mar
ar" o \pintar"
omo visitado un
nodo o un ar
o.
En este dise~no
ontemplamos tres maneras de llevar estado, tanto en los nodos
omo
en los ar
os: bits de
ontrol,
ontadores y \
ookies". Cada
lase de estado se guarda
dire
tamente en el nodo o en el ar
o.
7.3.5.1
662
Bits de control
Para efe
tos de \pintar" nodos y ar
os solo se requiere de un bit. Podramos mantener un
bit por
ada nodo y ar
o en un valor
ero y, al pro
esarlo segun el interes del algoritmo,
pintarlo
on uno. El problema de esta te
ni
a es que los algoritmos que usen el bit devienen
no-reentrantes. Por ejemplo, una prueba de
one
tividad puede inspe
ionar los nodos a
traves de los ar
os y pintarlos a medida que los visita. El grafo sera
onexo si se visitan
todos los nodos; es de
ir, si al nal de la itera
ion todos los nodos estan pintados. Ahora
bien, otro algoritmo que invoque pruebas de
one
tividad no puede usar el mismo bit usado
por la prueba de
one
tividad para pintar sus nodos, pues
omprometera la
onsisten
ia
del test de
one
tividad.
Para tratar
on la situa
ion anterior usaremos varios bits
lasi
ados segun el algoritmo
stos se espe
i
an
omo sigue:
que los utili
e. E
hBits de
ontrol 662i
(648)
hN
umero de bit 665ai
struct Bit_Fields
{
unsigned int depth_first
unsigned int breadth_first
unsigned int test_cycle
unsigned int is_acyclique
unsigned int test_path
unsigned int find_path
unsigned int kruskal
unsigned int prim
unsigned int dijkstra
:
:
:
:
:
:
:
:
:
1;
1;
1;
1;
1;
1;
1;
1;
1;
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
int
int
int
int
int
int
int
euler
hamilton
spanning_tree
build_subtree
convert_tree
cut
min
:
:
:
:
:
:
:
663
1;
1;
1;
1;
1;
1;
1;
Bit_Fields()
: depth_first(0), breadth_first(0), test_cycle(0), find_path(0),
kruskal(0), prim(0), dijkstra(0), euler(0), hamilton(0),
spanning_tree(0), build_subtree(0), convert_tree(0), cut(0), min(0)
{ /* empty */ }
663
de visita.
Cualquier bit de
ontrol puede
onsultarse mediante:
hM
etodos bits
ontrol 663i
(662) 664a
bool get_bit(const int & bit) const throw(std::exception, std::out_of_range)
{
switch (bit)
{
case Aleph::Depth_First: return depth_first;
case Aleph::Breadth_First: return breadth_first;
case Aleph::Test_Cycle:
return test_cycle;
case Aleph::Is_Acyclique: return is_acyclique;
case Aleph::Test_Path:
return test_path;
case Aleph::Find_Path:
return find_path;
case Aleph::Kruskal:
return kruskal;
case Aleph::Prim:
return prim;
case Aleph::Dijkstra:
return dijkstra;
case Aleph::Euler:
return euler;
case Aleph::Hamilton:
return hamilton;
case Aleph::Spanning_Tree: return spanning_tree;
case Aleph::Build_Subtree: return build_subtree;
case Aleph::Convert_Tree: return convert_tree;
case Aleph::Cut:
return cut;
case Aleph::Min:
return min;
default:
throw std::out_of_range("bit number out of range");
}
}
Denes:
get bit, used in
hunk 670d.
Uses cut 64, is acyclique 707, test cycle 705a, and test path 709b.
664
664a
664b
Captulo 7. Grafos
hM
etodos bits
ontrol 663i+
(662) 663 664b
void set_bit(const int & bit, const int & value)
{
switch (bit)
{
case Aleph::Depth_First: depth_first
= value; break;
case Aleph::Breadth_First: breadth_first = value; break;
case Aleph::Test_Cycle:
test_cycle
= value; break;
case Aleph::Is_Acyclique: is_acyclique = value; break;
case Aleph::Test_Path:
test_path
= value; break;
case Aleph::Find_Path:
find_path
= value; break;
case Aleph::Kruskal:
kruskal
= value; break;
case Aleph::Prim:
prim
= value; break;
case Aleph::Dijkstra:
dijkstra
= value; break;
case Aleph::Euler:
euler
= value; break;
case Aleph::Hamilton:
hamilton
= value; break;
case Aleph::Spanning_Tree: spanning_tree = value; break;
case Aleph::Build_Subtree: build_subtree = value; break;
case Aleph::Convert_Tree: convert_tree = value; break;
case Aleph::Cut:
cut
= value; break;
case Aleph::Min:
min
= value; break;
default:
throw std::out_of_range("bit number out of range");
}
}
Denes:
set bit, used in
hunks 664b, 666, 698b, 702, 705, 706, 709b, 710, 712, 713, 715, 718{20, 724, 727,
738{40, 746, 748, 749a, 778, 787, 797{99, 814
, 818a, and 822b.
Uses cut 64, is acyclique 707, test cycle 705a, and test path 709b.
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
(662) 664a
665
}
Uses cut 64, is acyclique 707, set bit 664a, test cycle 705a, and test path 709b.
Los bits de
ontrol estan enumerados \magi
amente" segun su n pretendido por
otros algoritmos:
hN
umero de bit 665ai
(662)
8
665a
enum Graph_Bits
{
Depth_First,
Breadth_First,
Test_Cycle,
Is_Acyclique,
Test_Path,
Find_Path,
Kruskal,
Prim,
Dijkstra,
Euler,
Hamilton,
Spanning_Tree,
Build_Subtree,
Convert_Tree,
Cut,
Min,
Num_Bits_Graph
};
Denes:
Graph Bits, never used.
665b
665
665d
665e
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 665d 666a
inline void reset_bit(Node * node, const int & bit);
8 En
la jerga de programa ion, un numero magi o es uno que es nombrado on un identi ador.
666
666a
666b
Captulo 7. Grafos
666
Se pueden reini
iar los bits de
ontrol de todos los nodos o ar
os de un grafo mediante:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 666b 666f
void reset_bit_nodes(const int & bit)
{
for (Node_Iterator itor(*this); itor.has_current(); itor.next())
reset_bit(itor.get_current_node(), bit);
}
Los
uales
olo
an en
ero, sea para los nodos o ar
os, al \bit-esimo" bit de
ontrol.
7.3.5.2
666d
Contadores
666e
y para un ar
o:
hAtributos de Graph Arc<Arc Type> 657
i+
long counter;
666f
El
ontador puede a
ederse o reini
iarse (
olo
arse en
ero) para los nodos:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 666
667a
inline long & get_counter(Node * node);
667a
Arc> 651ai+
inline long & get_counter(Arc * arc);
667
667b
Igualmente podemos reini
iar los
ontadores (
olo
arlos en
ero) de todos los nodos o
ar
os:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 667a 668a
void reset_counter_nodes()
{
for (Node_Iterator itor(*this); itor.has_current(); itor.next())
reset_counter(itor.get_current_node());
}
void reset_counter_arcs()
{
for (Arc_Iterator itor(*this); itor.has_current(); itor.next())
reset_counter(itor.get_current_arc());
}
Denes:
reset counter arcs, used in
hunk 747.
reset counter nodes, used in
hunk 747.
Uses Arc Iterator 661a, has current 103, and Node Iterator 655b.
7.3.5.3
667
Existen mu
hos algoritmos que requieren aso
iar estado de
al
ulo de una manera muy
parti
ular al tipo de algoritmo y para los
uales los bits de
ontrol y
ontadores no son su
ientes. La busqueda del
amino mas
orto segun Dijkstra, por ejemplo, ne
esita guardar
ierta informa
ion temporal en los ar
os. La forma y uso de este estado es parti
ular al
algoritmo de Dijkstra y posiblemente no sirve para otros algoritmos.
Requerimos, enton
es, de una forma mas amplia de aso
iar estado generi
o. Puesto que
en mu
has o
asiones el estado es temporal; es de
ir, solo se requiere durante la eje
u
ion
del algoritmo, no es
onveniente parametrizarlo en una plantilla que se le pase al nodo o
ar
o
uando estos se instan
ien.
Una forma generi
a, pero deli
ada, para aso
iar generi
amente datos a los nodos es
traves de un cookie. El termino
ookie proviene de la programa
ion de sistemas y
onnota a un parametro opa
o que se le trasmite a una fun
ion. En nuestro
aso, un
ookie es un puntero opa
o que se le aso
ia a un nodo o a un ar
o.
As pues, los nodos y los ar
os poseen un atributo cookie,
uya espe
i
a
ion es la
siguiente:
hMiembros de Graph Node<Node Type> 652ai+
(651b) 666d 677a
void *
667d
Cookies
hAtributos
void *
cookie;
668
668a
Captulo 7. Grafos
void reset_cookie_arcs()
{
for (Arc_Iterator itor(*this); itor.has_current(); itor.next())
itor.get_current_arc().cookie = NULL;
}
Denes:
reset cookie arcs, never used.
reset cookie nodes, never used.
Uses Arc Iterator 661a, has current 103, and Node Iterator 655b.
Supongamos, por ejemplo, que ne
esitamos guardar en
ada nodo una lista dinami
a
de valores reales. Si tenemos un apuntador a un nodo, denominado node, enton
es una
forma de ha
erlo es
omo sigue:
node->get_cookie() = new DynDlist<float>;
Si el usuario requiere un estado estru
turado, enton
es este utiliza una estru
tura;
struct o class seg
un sea el
aso.
Mapeo entre nodos y arcos
Un uso importante del cookie es implantar el mapeo entre grafos homomorfos o iso-
morfos. En
iertos algoritmos, en la o
urren
ia el
al
ulo del arbol abar
ador, se debe
al
ular un grafo adi
ional
orrespondiente al arbol abar
ador. Para identi
ar dentro del
arbol abar
ador
ual es su nodo en el grafo original, el cookie alma
ena la imagen del
669a
669
nodo dentro del arbol y vi
eversa. A este tipo de algoritmos les puede ser muy util las
siguientes primitivas
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 668b 669b
static void map_nodes(Node * p, Node * q);
map nodes() mapea entre s los nodos p y q, respe
tivamente. Si ya existe un mapeo
previo; o sea si p >get cookie()
ontiene una dire
ion, enton
es map nodes() realiza un
mapeo
ompuesto. Por ejemplo, supongamos que p y q estan mapeados entre s y que
669b
669
Tambien pueden reini
iarse todos los atributos de
ontrol de todos los nodos o
ar
os del grafo. En este sentido podemos ilustrar un uso
on
reto de los metodos
operate on nodes() y operate on arcs().
Basi
amente, lo que deseamos es re
orrer
ada nodo e invo
ar reset() para
ada atributo de
ontrol. Esta a
tividad se puede espe
i
ar mediante la siguiente
lase opera
ion:
hMiembros privados de List Graph<Node, Arc> 669
i
(649) 670b
struct Reset_Node
{
void operator () (List_Graph&, Node * node)
{
node->control_bits.reset();
node->counter = 0;
670
Captulo 7. Grafos
node->cookie = NULL;
}
};
Uses List Graph 649.
670a
Ahora simplemente es
ribimos la reini
ia
ion de los nodos mediante invo
a
ion a
operate on nodes()
on Reset Node
omo opera
ion:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 669b 670
void reset_nodes()
{
operate_on_nodes <Reset_Node> ();
}
Denes:
reset nodes, used in
hunks 718b and 726
.
Uses operate on nodes 661
.
670b
670
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
void reset_arcs()
{
operate_on_arcs <Reset_Arc> ();
}
Denes:
reset arcs, used in
hunks 718b, 726
, and 737a.
Uses operate on arcs 661b 661
.
Es tpi
o, antes de realizar un
al
ulo que involu
re los atributos de
ontrol, tanto
de nodos
omo de ar
os, invo
ar a reset nodes() y reset arcs() para \limpiarlos" de
valores utilizados por
al
ulos previos.
7.3.6
670d
define
define
define
define
NODE_BITS(p)
((p)->control_bits)
NODE_COUNTER(p)
((p)->counter)
IS_NODE_VISITED(p, bit) (NODE_BITS(p).get_bit(bit))
NODE_COOKIE(p)
((p)->cookie)
# define
# define
# define
# define
Denes:
671
ARC_COUNTER(p)
((p)->counter)
ARC_BITS(p)
((p)->control_bits)
IS_ARC_VISITED(p, bit) (ARC_BITS(p).get_bit(bit))
ARC_COOKIE(p)
((p)->cookie)
IS NODE VISITED, used in hunks 698b, 702, 705b, 706, 708, 710, 712, 713, 715, 719a, 720, 726 , 727,
738 , 740, 748, 778, 787b, 798b, 799a, 818a, and 822b.
NODE BITS, used in hunks 698b, 702, 705b, 706, 710, 712, 713, 715, 718{20, 727, 738{40, 748, 749a,
NODE COOKIE, used in
hunks 686, 720, 727, 736, 748, 749b, 778, 785, 794
, 795b, 797a, 814, and 822b.
NODE COUNTER, used in
hunk 736.
NUM ARCS, never used.
Uses get bit 663.
7.3.7
671a
Construcci
on y destrucci
on de List Graph<Node, Arc>
Basi
amente, un grafo puede
onstruirse por omision o por
opia desde otro grafo:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 670
671b
inline List_Graph();
671b
El primer
onstru
tor
rea un grafo va
o (sin nodos y ar
os); el segundo realiza una
opia
de grafo g.
Es posible asignarle a un grafo otro grafo:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 671a 671
671
671d
elimina del grafo todos los nodos y ar
os que este
ontiene. Toda la memoria es liberada.
Se puede
onstruir un grafo a partir de un digrafo, as
omo tambien asignarle a un
grafo un digrafo. En ese
aso, los ar
os dirigidos son sustituidos por ar
os bidire
ionales:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 671
672a
inline List_Graph(List_Digraph<Node, Arc> & g);
672
672a
Captulo 7. Grafos
672b
el
ual, en
aso de que el parametro cookie map sea
ierto, realiza un mapeo biye
tivo
entre los dos grafos a traves de los
ookies de sus nodos y ar
os. Al nal de la
opia, el
puntero cookie de
ada nodo
ontendra la imagen en el grafo
opia; igual se apli
a para
los ar
os.
La
opia mapeada es muy util para la realiza
ion de mu
hos algoritmos. Por ejemplo,
para
al
ular el
omplemento de un grafo, podemos, en primer lugar,
opiar el grafo,
luego insertamos sobre la
opia los ar
os ne
esarios para volverlo
ompletamente
onexo
y, nalmente, eliminamos de la
opia aquellos ar
os que hayan sido mapeados.
No todas las ve
es es
onveniente mapear las
opias mediante los
ookies. La generalidad del
aso la representa toda situa
ion en la
ual el grafo a
opiar ya
ontenga datos en
los
ookies. Esta es la razon por la
ual el mapeo entre los
ookies es op
ional a la
opia.
Es posible \borrar" expl
itamente un grafo; es de
ir, eliminar todos sus nodos (y
ar
os). Para ello se usa el siguiente metodo:
hMiembros p
ubli
os de List Graph<Node, Arc> 651ai+
(649) 672a
inline void clear_graph();
Uses clear graph 685b.
Esta primitiva es muy valiosa
uando se usan grafos temporales
orrespondientes a
al
ulos
intermedios.
7.3.8
672
Implantaci
on de List Graph<Node, Arc>
En la sub-se
ion anterior expli
amos la interfaz de los TAD List Graph<Node, Arc>
y List Digraph<Node, Arc>. En esta sub-se
ion expli
aremos su implanta
ion. Nos
on
entraremos en List Graph<Node, Arc> porque, a nivel de implanta
ion, es en este
tipo en donde pra
ti
amente se realiza.
List Graph<Node, Arc> maneja dos listas
ir
ulares doblemente enlazadas: una lista
de sus nodos y otra de sus ar
os. Cada lista aso
ia un
ontador de sus elementos:
hMiembros privados de List Graph<Node, Arc> 669
i+
(649) 670b 673a
Dlink node_list; // lista de nodos
size_t num_nodes; // cantidad de nodos
num nodes y num arcs ontabilizan las antidades totales de nodos y ar os que tiene
672d
el grafo. Estos atributos se a
tualizan en la primitivas de inser
ion y elimina
ion de nodos
y ar
os. Los observadores, pues, se implementan muy simplemente de la siguiente forma:
hImplanta
i
on de List Graph<Node, Arc> 672di
(648) 673
template <typename Node, typename Arc>
673
673a
node list y arc list son las
abe
eras, de tipo Dlink, de las listas de nodos y ar
os, respe
tivamente, que
ontiene el grafo. Re
ordemos que las
lases Graph Node<Node Type>
y Graph Arc<Arc Type> derivan de Dlink. La base Dlink, pues,
onforma en
ada
lase,
el enla
e doble de la listas node list y arc list.
Como node list y arc list son de tipo Dlink, planteamos las siguientes fun
iones
de
onversion de Dlink a Graph Node<Node Type> y a Graph Arc<Arc Type>:
hMiembros privados de List Graph<Node, Arc> 669
i+
(649) 672
673b
static Node * dlink_to_node(Dlink * p)
{
return static_cast<Node*>(p);
}
7.3.8.1
673b
673
Con estas
onversiones ya podemos
odi
ar legiblemente los metodos basi
os de a
eso
a nodos y ar
os:
hMiembros privados de List Graph<Node, Arc> 669
i+
(649) 673a 677b
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 672d 674
template <typename Node, typename Arc>
Node * List_Graph<Node, Arc>::get_first_node()
{
if (num_nodes == 0)
throw std::range_error("Graph has not nodes");
return dlink_to_node(node_list.get_next());
}
template <typename Node, typename Arc>
Arc * List_Graph<Node, Arc>::get_first_arc()
674
Captulo 7. Grafos
{
if (get_num_arcs() == 0)
throw std::range_error("Graph has not arcs");
return dlink_to_arc(arc_list.get_next());
}
Denes:
get first arc, used in
hunks 659b and 694b.
get first node, used in
hunks 654a, 694b, 698a, 718a, 738a, and 787a.
Uses arc list 672
, arcs 813
, dlink to arc 673a, dlink to node 673a, List Graph 649,
and node list 672
.
La misma
onsidera
ion que para el iterador sobre los nodos apli
a sobre el iterador
sobre los ar
os, pues estos tambien se fundamentan en Dlink
B
usqueda de nodos
674
675a
675
Nos resta por implementar la busqueda de un ar
o que
one
te a dos nodos, tarea a la
ual nos abo
aremos despues de denir el iterador de ar
os sobre un nodo.
7.3.8.2
675b
675
Arcos
Estos atributos son de tipo void* porque desde un Graph Arc<Arc Type> no se puede
ono
er el tipo exa
to de Graph Node<Node Type>; este se
ono
era
uando se instan
ie el grafo. La implanta
ion del a
eso a estos atributos se realiza desde la
lase
List Graph<Node, Arc> de la siguiente manera:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 675a 676b
template <typename Node, typename Arc>
Node * List_Graph<Node, Arc>::get_src_node(Arc * arc)
{
676
Captulo 7. Grafos
return static_cast<Node*>(arc->src_node);
}
template <typename Node, typename Arc>
Node * List_Graph<Node, Arc>::get_tgt_node(Arc * arc)
{
return static_cast<Node*>(arc->tgt_node);
}
template <typename Node, typename Arc>
bool List_Graph<Node, Arc>::node_belong_to_arc(Arc * arc,
Node * node) const
{
return node == arc->src_node or node == arc->tgt_node;
}
Uses List Graph 649.
676a
Como debe apre
iarse, el uni
o rol de estas fun
iones es la
onversion de tipo desde
void* ha
ia Node*
Para la implanta
ion de get connected node() podemos dise~nar una rutina
homonima, parte de Graph Arc<Arc Type>, que nos sirva de intermediaria:
hM
etodos de Graph Arc<Arc Type> 657di+
(657b) 657d 680a
void * get_connected_node(void * node)
{
return src_node == node ? tgt_node : src_node;
}
Uses get connected node 676b.
676b
Listas de adyacencia
676
La no
ion tradi
ional de lista de adya
en
ia sugiere que un nodo
ontenga una lista de
nodos adya
entes junto
on los atributos aso
iados al ar
o. El problema de este enfoque
es que, para un grafo (no un digrafo) debemos dupli
ar exa
tamente aquella informa
ion.
Esta redundan
ia a
arrea el problema de que, por ejemplo, si se requiere modi
ar el
ar
o, enton
es debemos entrar por los dos nodos (el origen y destino) y realizar dos modi
a
iones. Por esa razon, para evadir el problema de
oheren
ia anterior, denimos el
tipo Arc Node,
uya espe
i
a
ion es
omo sigue:
hAr
o-Nodo 676
i
(648)
struct Arc_Node : public Dlink
{
677
void * arc;
Arc_Node() : arc(NULL) { /* empty */ }
Arc_Node(void * __arc) : arc(__arc) { /* empty */ }
};
Denes:
Arc Node, used in
hunks 651b, 677, 678, 680
, 682b, 684, and 685a.
Uses Dlink 90.
677a
Arc Node deriva de Dlink, por lo que este es parte de una lista doblemente enlazada. El
ampo arc es un puntero a un Graph Arc<Arc Type> (o familiar de el por deriva
ion).
De este modo, la lista de adya
en
ia de un nodo se de
lara
omo sigue:
hMiembros de Graph Node<Node Type> 652ai+
(651b) 667
Dlink arc_list;
Uses arc list 672
and Dlink 90.
Esto termina de
onformar la estru
tura de datos de un nodo o verti
e de grafo, la
ual
se representa pi
tori
amente del modo siguiente:
Dlink arc_list (lista de arcos del nodo)
counter
num_arcs
counter
Dlink
control_bits
cookie
Enlace
del nodo en la lista de nodos del grafo
Mientras que la estru
tura de un Arc Node se des
ribe pi
tori
amente de la siguiente
forma:
Dlink
void*
enlace
del Arc_Node
en la lista arc_list de un nodo
De esta manera, si se altera un ar
o, la modi
a
ion se realiza sobre una sola instan
ia
de tipo Graph Arc<Arc Type> y el estado del ar
o se mantiene
oherente para los dos
nodos
one
tados.
La lista de adya
en
ia de un nodo es de tipo Dlink, pero sus nodos son de tipo
Arc Node. Debemos, pues, disponer de la
onversion:
hMiembros privados de List Graph<Node, Arc> 669
i+
(649) 673b 678a
9
677b
uidado
on la ambiguedad. En esta frase nos referimos a los nodos de una lista enlazada y
no a los de un grafo.
678
Captulo 7. Grafos
678a
Dlink
enlace
en la lista
de arcos del
grafo
678b
counter
control_bits
cookie
tgt_arc_node
(657b) 675b
Arc_Node * src_arc_node; // puntero al Arc_Node del nodo fuente
Arc_Node * tgt_arc_node; // puntero al Arc_Node del nodo destino
Uses Arc Node 676
.
679
679
counter
num_arcs=2
counter
control_bits
cookie
A
counter
control_bits
cookie
counter
num_arcs=2
counter
control_bits
cookie
Arc_Nodes
counter
control_bits
cookie
counter
control_bits
cookie
counter
num_arcs=2
counter
control_bits
cookie
3
Arcos
Nodos
680
680a
Captulo 7. Grafos
Graph_Arc()
: counter(No_Visited), cookie(NULL),
src_node(NULL), tgt_node(NULL), src_arc_node(NULL), tgt_arc_node(NULL)
{
/* empty */
}
Estos
onstru
tores son para uso de List Graph<Node, Arc> y no del usuario, pues desde
la interfaz de List Graph<Node, Arc> se pueden
rear y eliminar ar
os.
Iterador de arcos sobre un nodo
680b
(656a)
Node * src_node;
680
Puesto que Node Arc Iterator es por deriva
ion de tipo Dlink::Iterator, este
tiene todos los metodos aso
iados (next(), prev(), has current(), et
etera). El metodo
Dlink::Iterator::get current() no puede usarse dire
tamente porque este retorna un
Dlink y ne
esitamos
ono
er en prin
ipio el Arc Node. Por tanto, dise~
namos esta
onversion:
hMiembros p
ubli
os de iterador de Node 656bi+
(656a) 656d
Arc_Node * get_current_arc_node()
{
return dlink_to_arc_node(get_current());
}
Denes:
get current arc node, never used.
Uses Arc Node 676
, dlink to arc node 677b, and get current 103.
681
B
usqueda de un arco dados dos nodos
681a
return NULL;
}
Uses digraph 679, has current 103, List Graph 649, Node Arc Iterator 656a, and search arc 675a.
7.3.8.3
681b
Inserci
on de nodos
El primer tipo de inser
ion asume que la memoria para el nodo ya ha sido apartada y se
instrumenta de la siguiente manera:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 681a 682a
template <typename Node, typename Arc>
Node * List_Graph<Node, Arc>::insert_node(Node * node)
{
++num_nodes;
node_list.append(node);
return node;
}
Uses insert node 682a, List Graph 649, and node list 672
.
682
682a
Captulo 7. Grafos
El segundo tipo extiende el trabajo anterior para apartar la memoria para el nodo y
olo
arle la informa
ion dada de la forma siguiente:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 681b 682b
7.3.8.4
Inserci
on de arcos
Para insertar un ar
o deben
ono
erse los nodos que este rela
iona, los
uales deben haberse
insertado previamente en el grafo. Esen
ialmente, la inser
ion de un ar
o se resume en los
siguientes pasos:
1. Apartar memoria para el ar
o (de tipo Arc) y asignarle sus datos.
2. Si la inser
ion o
urre en un grafo (no un digrafo), enton
es apartar memoria para
un objeto de tipo Arc Node
orrespondiente al nodo destino tgt node,
olo
arlo a
apuntar ha
ia el ar
o
reado en (1) e insertarlo en la lista de adya
en
ia de tgt node.
En este punto debemos estar pendientes de que el ar
o a insertar no represente un
i
lo y as, de ese modo, no dupli
ar el Arc Node.
3. Apartar memoria para un objeto de tipo Arc Node
orrespondiente al nodo origen
src node,
olo
arlo a apuntar ha
ia el ar
o
reado en (1) e insertarlo en la lista de
adya
en
ia de src node
4. Insertar el ar
o en la lista de ar
os del grafo.
682b
683
7.3.8.5
Eliminaci
on de arcos
La elimina
ion de un ar
o es mas sen
illa porque no hay puntos eventuales de ex
ep
ion .
El pro
edimiento se resume en los siguientes pasos:
11
10 Un
auto-puntero es una
lase de objeto apuntador que maneja la libera
ion automati
a de memoria
uando se invo
a al destru
tor. De este modo, se ahorra la in
lusion de un manejador de ex
ep
iones que
prevea una falla de memoria y que requiera \limpiar" estado intermedio. Los auto-punteros se liberan
uando se eje
uta el metodo release(); en este
aso, el destru
tor no efe
tuara el delete.
11 Salvo que se trate de un error del usuario, por ejemplo, elimina
i
on doble o un bug en la implanta
ion.
684
Captulo 7. Grafos
1. Eliminar de las lista de adya
en
ia del nodo origen el Arc Node y liberar su memoria.
El a
eso a este Arc Node se da por el atributo arc->src arc node.
2. Si se trata de un grafo (no un digrafo), enton
es eliminar de las lista de adya
en
ia
del nodo destino el Arc Node y liberar su memoria.
El a
eso a este Arc Node se da por el atributo arc->tgt arc node.
En este paso hay que prestar aten
ion a que el ar
o sea un
i
lo, en
uyo
aso no
hay que ni eliminar de la lista de adya
en
ia ni liberar la memoria; ambas a
iones
ya fueron he
has en el paso anterior.
3. Finalmente, eliminar el ar
o de la lista de ar
os del grafo y liberar su memoria.
684
}
Denes:
7.3.8.6
Eliminaci
on de nodos
La elimina
ion de un nodo puede pare
er
ompli
ada ante el requerimiento de que deben
eliminarse todos sus ar
os in
identes y adya
ente. Sin embargo, resulta sen
illa si nos
685a
685
delete node;
}
Denes:
remove node, used in
hunks 653b and 685b.
Uses arc list 672
, Arc Node 676
, dlink to arc node 677b, List Graph 649, remove arc 684,
and void to arc 678a.
7.3.8.7
685b
Limpieza de grafos
remove node() nos ha
e la mayor parte del trabajo ne
esario para limpiar un grafo (opera
ion clear graph()), pues esta se remite a eliminar todos los nodos del grafo:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 685a 686
template <typename Node, typename Arc>
void List_Graph<Node, Arc>::clear_graph()
{
// recorrer cada nodo del grafo a trav
es de la lista de nodos
for (Dlink::Iterator node_itor(&node_list); node_itor.has_current(); )
{
// obtener nodo a borrar
Node * p = dlink_to_node(node_itor.get_current());
clear graph, used in hunks 672b, 687b, 689 , 718b, 720, 749, 778 , 780, 797a, and 813b.
686
Captulo 7. Grafos
Uses Dlink 90, dlink to node 673a, get current 103, has current 103, List Graph 649, node list 672
,
and remove node 685a.
7.3.8.8
Re
ordemos que estipulamos el mapeo de nodos y de ar
os a traves de los
ookies. Con
entremosnos en map node(), el
ual mapea los
ookies de dos nodos y preserva la
omposi
ion de mapeos en el sentido de que si los
ookies son distintos de NULL, enton
es el
mapeo se propaga
omo si los
ookies fuesen una lista enlazada
ir
ular. Ini
ialmente, si
no hay mapeo, enton
es node map(p, q)
onstruye la siguiente
ongura
ion de enla
es:
q
Luego, si o
urre otra llamada node map(q,r), enton
es los enla
es devienen en:
p
686
Claramente, los mapeos deben
omportarse
omo si tratase de una inser
ion en una
lista
ir
ular, simplemente enlazada, sin nodo
abe
era. Lo que nos
ondu
e a:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 685b 687b
template <typename Node, typename Arc>
void List_Graph<Node, Arc>::map_nodes(Node * p, Node * q)
{
if (NODE_COOKIE(p) == NULL)
{
NODE_COOKIE(p) = q;
NODE_COOKIE(q) = p;
return;
}
NODE_COOKIE(q) = NODE_COOKIE(p);
NODE_COOKIE(p) = q;
}
template <typename Node, typename Arc>
void List_Graph<Node, Arc>::map_arcs(Arc * p, Arc * q)
{
if (ARC_COOKIE(p) == NULL)
{
ARC_COOKIE(p) = q;
ARC_COOKIE(q) = p;
return;
}
ARC_COOKIE(q) = ARC_COOKIE(p);
687
ARC_COOKIE(p) = q;
}
Denes:
map arcs, used in
hunks 669a, 687b, 719b, 720, 727, 748, 749b, 776b, 787b, and 822b.
map nodes, used in
hunks 669a, 687b, 718{20, 727, 748, 749, 778, 786b, 795
, and 822b.
Uses ARC COOKIE 670d, List Graph 649, and NODE COOKIE 670d.
7.3.8.9
687a
Copia de grafos
De manera general, la
opia puede separarse en dos fases: (1)
opia de nodos y (2)
opia
de ar
os. Copiar nodos es relativamente fa
il: por
ada nodo del grafo origen se
rea una
opia y se inserta en el grafo destino.
La
opia de ar
os es similar: por
ada ar
o del grafo origen se
rea una
opia y se
inserta en el grafo destino. Re
ordemos que para insertar un ar
o hay que espe
i
ar
sus nodos. Cuando inspe
ionamos un ar
o del grafo origen, los nodos que examinamos
reeren al grafo origen, pero el ar
o a insertar en el grafo destino requiere nodos respe
to
al grafo destino y no respe
to al origen. Se nos presenta enton
es el siguiente problema:
dado un nodo del grafo origen, >
omo
ono
er su equivalente en el grafo
opia destino?
Una manera de ha
erlo sera mediante un mapeo de nodos entre los dos grafos a traves
de sus
ookies, pero la semanti
a de la interfaz copy graph() puede impedir este tipo
de mapeo (
uando el parametro cookie map == false). Por esa razon, realizaremos el
mapeo mediante una tabla instrumentada
on un arbol AVL:
hDe
lara
i
on de tabla mapeo 687ai
(687b)
DynMapAvlTree<Node*, Node*> mapping_table;
Denes:
mapping table, used in
hunk 687b.
mapping table mapea nodos del grafo origen src graph a nodos del grafo destino this.
687b
on
hDe
lara
i
688
Captulo 7. Grafos
7.3.8.10
688
Construcci
on y asignaci
on de grafos
La
onstru
ion por omision de un List Graph<Node, Arc> es dire
ta una vez que
ono
emos todos los atributos:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 687b 689a
template <typename Node, typename Arc>
List_Graph<Node, Arc>::List_Graph()
689
El onstru tor por omision de un List Digraph<Node, Arc> debe modi ar la bandera
digraph:
689a
689b
hImplanta
i
on de List Graph<Node, Arc> 672di+
template <typename Node, typename Arc>
List_Digraph<Node, Arc>::List_Digraph()
{
List_Graph<Node, Arc>::digraph = true;
}
Uses digraph 679, List Digraph 650b, and List Graph 649.
Los
onstru
tores
opia son muy sen
illos una vez que hemos denido la
opia entre
grafos. Para realizar esta sen
illez, basta
on una implanta
ion:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 689a 689
template <typename Node, typename Arc>
List_Graph<Node, Arc>::List_Graph(const List_Graph<Node, Arc> & g)
: num_nodes(0), num_arcs(0), digraph(false)
{
copy_graph(const_cast<List_Graph<Node, Arc> &>(g));
}
Uses copy graph 687b, digraph 679, and List Graph 649.
689
7.3.8.11
689d
Ordenamiento de arcos
Los ar
os estan
ontenidos en la lista doblemente enlazada arc list. Para ordenarlos, nos
valdremos del mergesort implementado en x 3.2.1.5 (pagina 212). Apelamos al mergesort
por varias razones. En primer lugar, a diferen
ia, por instan
ia, del qui
ksort, el desempe~no
O(n lg (n)) del mergesort esta garantizado y no supeditado a la permuta
ion. En segundo
lugar, el
onsumo de espa
io del mergesort es a lo sumo O(lg(n)), a diferen
ia del O(n)
que es el mal
aso de
onsumo para el qui
ksort.
As pues, el ordenamiento de los ar
os se remite a:
hImplanta
i
on de List Graph<Node, Arc> 672di+
(648) 689
template <typename Node, typename Arc>
template <class Compare>
void List_Graph<Node, Arc>::sort_arcs()
{
690
Captulo 7. Grafos
690a
sort arcs() re
ibe la
lase de
ompara
ion Compare sobre punteros a ar
os de tipo
Arc; esta
lase, que la debe proveer el usuario interesado en ordenar los ar
os, realiza
la
ompara
ion bool operator () (Arc *, Arc *). Pero el mergesort sobre listas enlazadas invo
a a bool operator () (Dlink *, Dlink *) (x 3.2.1.5 (pagina 212)), para
omparar. Debemos, pues, es
ribir la
lase de
ompara
ion para mergesort(), denominada Cmp Arc, que
onvierta los punteros de tipo Dlink a Arc* e invoque la
ompara
ion
Compare. Esto se instrumenta del siguiente modo:
hMiembros privados de List Graph<Node, Arc> 669
i+
(649) 679
template <class Cmp>
struct Cmp_Arc
{
bool operator () (Dlink * d1, Dlink * d2) const
{
Arc * arc1 = dlink_to_arc(d1); // convertir dlink d1 de arco a Arc
Arc * arc2 = dlink_to_arc(d2); // convertir dlink d2 de arco a Arc
// al tener dos arcos invocamos a Cmp
return Cmp () (arc1, arc2);
}
};
Denes:
7.4
690b
La busqueda de
aminos es una de las a
tividades mas
omunes en la manipula
ion sobre
un grafo. Por esto vale la pena un TAD que modeli
e
aminos y fa
ilite su
onstru
ion.
El TAD Path<GT> modeliza un
amino sobre un List Graph<Node, Arc>, su estru
tura general se dene
omo sigue:
hCamino de grafo 690bi
(648) 711
template <typename GT>
class Path
{
hTipos de path 691di
hMiembros privados de path 691ai
ubli
os de path 691bi
hMiembros p
};
Denes:
Path, used in
hunks 692{94, 711{15, 799b, 801, 805, and 811a.
El parametro tipo GT debe ser de tipo List Graph<Node, Arc>, List Digraph<Node, Arc>
o de alguna
lase des
endiente.
691a
691b
691
(690b) 691e
g;
(690b) 691
691
En o
asiones, es ne
esario validar si un
amino sobre un grafo reere a un grafo parti
ular. El metodo:
hMiembros p
ubli
os de path 691bi+
(690b) 691b 692b
public:
bool inside_graph(GT & gr) const { return g == &gr; }
691d
691e
Lo mismo se apli
a para los punteros a nodos y ar
os;
on la ex
ep
ion de que estos se
manejan de manera privada:
hMiembros privados de path 691ai+
(690b) 691a 691f
private:
typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
691f
Nuestra manera de representar un
amino es mediante los ar
os que lo
omponen. Esta forma es independiente de idiosin
rasias tales
omo que se trate un
multigrafo, un digrafo, et
etera. Puesto que en List Graph<Node, Arc> y en su
derivado List Digraph<Node, Arc>, un ar
o no expresa la dire
ion, es ne
esario indi
ar
ual es el nodo origen. De este modo, el otro nodo del ar
o sera el su
esor en el
amino.
La observa
ion anterior sugiere la siguiente representa
ion de estru
tura de datos de
ada punto del
amino:
hMiembros privados de path 691ai+
(690b) 691e 692a
struct Path_Desc
{
Node * node; // nodo origen
Arc * arc; // arco adyacente
692
692a
Captulo 7. Grafos
DynDlist<Path_Desc> list;
Uses DynDlist 113a and Path Desc 691f.
La forma \tradi
ional" de de
larar un
amino es uno va
o que reere a un determinado grafo:
hMiembros p
ubli
os de path 691bi+
(690b) 691
692
12
692b
692
692d
692e
692f
693
init(start_node);
}
Denes:
set graph, used in
hunk 811a.
693a
La longitud de un
amino es el numero de ar
os que lo
omponen. En nuestra estru
tura de datos, esta informa
ion esta dada por el numero de elementos del atributo list:
hMiembros p
ubli
os de path 691bi+
(690b) 692f 693b
const long & size() const { return list.size(); }
693b
693
La opera
ion de \limpieza" de un
amino
onsiste en borrar todos sus nodos y ar
os.
Esto se realiza
on la opera
ion siguiente:
hMiembros p
ubli
os de path 691bi+
(690b) 693b 693d
void clear_path()
{
while (not list.is_empty())
list.remove_first();
}
693d
return *this;
}
Uses Path 690b.
693e
La asigna
ion \limpia" el
amino destino antes de
opiar el
amino origen path.
list.get last()->arc debe estar, siempre, en NULL. De este modo, list.get first()->node
es el primer nodo del
amino mientras que list.get last()->node el ultimo. El re
orrido del
amino se distingue entre los ar
os mediante el
ampo node de
ada Path Desc
de list.
Luego de tener un
amino ini
iado; es de
ir,
on su primer nodo, la
onstru
ion del
amino se remite a a~nadirle los ar
os. Para ello, se dispone de la siguiente opera
ion:
hMiembros p
ubli
os de path 691bi+
(690b) 693d 694a
void append(Arc * arc)
{
if (list.is_empty())
694
Captulo 7. Grafos
694a
694b
La rutina es deli
ada porque puede a
arrear ambiguedades sobre multigrafos y multidigrafos.
Lo
omun en la
onstru
ion de
aminos es por el extremo denominado \ultimo nodo".
Sin embargo, o
asionalmente es posible extender el
amino por el primer nodo; es de
ir,
a~nadir un nuevo nodo o ar
o tal que este devenga el primero. Esto se realiza mediante las
ontrapartes insert().
Los extremos de un
amino se
onsultan mediante las primitivas siguientes:
hMiembros p
ubli
os de path 691bi+
(690b) 694a 695a
Node * get_first_node() { return list.get_first().node; }
Node * get_last_node() { return list.get_last().node; }
Arc * get_first_arc() { return list.get_first().arc; }
Arc * get_last_arc()
{
if (list.is_unitarian())
695
695a
De estas rutinas, quiza la que mere
e una expli
a
ion es get last arc(). Re
ordemos
primero que un DynDlist<T> solo puede a
ederse por sus extremos. Si se desea un
elemento diferente, enton
es es ne
esario re
orrer la se
uen
ia a traves de un iterador.
Ahora bien, get last arc() debe retornar el ultimo ar
o del
amino, pero este no se
en
uentra en el ultimo Path Desc de list, sino en el penultimo. He all la razon por la
ual el iterador se posi
iona en el ultimo Path Desc y luego retro
ede una posi
ion. De
esta forma, desde el penultimo Path Desc se a
ede al ultimo ar
o.
Mu
hos algoritmos, sobre todo los que efe
tuan retro
eso (ba
ktraking),
onstruyen
aminos
uyos extremos nales deben ser borrados. Por esta razon, es indispensable la
siguiente primitiva:
hMiembros p
ubli
os de path 691bi+
(690b) 694b 695b
void remove_last_node()
{
list.remove_last();
list.get_last().arc = NULL;
}
695b
7.5
Se han identi ado dos patrones arquetpi os de explora ion, llamados busqueda en profundidad y en amplitud, respe tivamente. La mayora de los algoritmos sobre grafos exhibe
696
696
Captulo 7. Grafos
Recorrido en profundidad
697a
697
size_t &
Uses depth first traversal 698b.
g,
node,
arc,
(*visit)(GT & g,
typename GT::Node *,
typename GT::Arc *),
count);
Menester se~nalar que esta no es la interfaz al usuario, sino la rutina re
ursiva que implanta
el algoritmo 7.1.
depth first traversal<GT>() opera sobre un objeto derivado de List Graph<Node, Arc>,
el
ual esta expresado por el parametro tipo GT. Los parametros de la fun
ion se des
riben
as:
1. g: el grafo sobre el
ual se realiza el re
orrido.
2. node: el nodo que se esta visitando.
3. arc: el ar
o desde el
ual se llega al nodo node.
4. (*visit)(): puntero a la fun
ion de visita. Si este puntero es distinto de nulo,
enton
es la fun
ion de visita se invo
a la primera vez que se ve un nodo.
La fun
ion de visita tiene los siguientes parametros:
(a) g: el grafo que se esta visitando.
(b) n: puntero al nodo visitado.
(
) a: puntero al ar
o que
ondu
e al nodo visitado n.
(*visit)() retorna un valor logi
o. Si es true enton
es se asume que la b
usqueda ha
terminado; de lo
ontrario, la busqueda prosigue hasta que (*visit)() retorne true
o hasta que se hayan visitado todos los ar
os y nodos del grafo segun su
one
tividad.
5. node counter: parametro por referen
ia que
ontabiliza la
antidad de nodos visitados.
depth first traversal() re
orre re
ursivamente el grafo a partir del nodo node. El
valor de retorno de (*visit)() permite detener la busqueda segun algun
riterio espe
o
697b
g,
698
Captulo 7. Grafos
698a
la
ual explora en profundidad el grafo g a partir del nodo start node y retorna la
antidad
de nodos visitados. Si se espe
i
a una fun
ion de visita, enton
es esta se invo
a
ada vez
que durante la explora
ion se des
ubra un nodo.
Hay otra version que arran
a la visita sobre un nodo
ualquiera del grafo (el que arroja
get first node()):
hRe
orrido en profundidad 697ai+
(696) 697b 698b
template <class GT> inline
size_t depth_first_traversal(GT & g,
bool (*visit)(GT &, typename GT::Node *,
typename GT::Arc *)
)
{
698b
Antes de implantar depth first traversal() es
onveniente realizar algunas a
laratorias que nos ayudaran a
omprender el
odigo de este y mu
hos de los algoritmos sobre
grafos de este texto.
En primer lugar, a
ordemos la manera de mar
ar los nodos, la
ual sera mediante
un bit de
ontrol; en nuestro
aso, el bit Depth First. En segundo lugar, a efe
tos de
a
elerar el re
orrido, vamos a mar
ar, tambien, los ar
os vistos. Esto nos ahorra llamadas
re
ursivas que van a
aer sobre nodos previamente visitados desde otro
amino. He
has
las a
laratorias pertinentes, podemos presentar la rutina re
ursiva:
hRe
orrido en profundidad 697ai+
(696) 698a
template <class GT> inline static bool
__depth_first_traversal(GT &
g,
typename GT::Node * node,
typename GT::Arc * arc,
699
bool
(*visit)(GT & g,
typename GT::Node *,
typename GT::Arc *),
count)
size_t &
{
Notese que la
ondi
ion de itera
ion del for
onsidera la deten
ion si ya se han visitado
todos los nodos.
B
L
D
I
C
700
Captulo 7. Grafos
(a)
Ini
io en A
(b) Ini io en N
Figura 7.19: Arboles
abar
adores de profundidad del grafo de la gura 7.18
Algoritmos basados en el re
orrido en profundidad se usan
uando se estima que el nodo
solu
ion esta \lejano" del ini
io. Notemos que los arboles de profundidad de la gura 7.19
son \largos y delgados",
on po
as ramas. El primero es plano porque a partir de A es
posible visitar enteramente el grafo sin ne
esidad de regresar a un nodo ya visitado. En el
segundo arbol vemos un regreso al nodo G.
Si el grafo es
onexo, enton
es la busqueda en profundidad requiere a lo sumo O(V) +
O(E) = O(max(V, E)) pasos para ini
ializar los bits de los nodos y ar
os. Posteriormente,
la rutina
omienza en el primer nodo del grafo y a lo sumo llama re
ursivamente V ve
es
a depth first traversal(). Durante
ada llamada re
ursiva se re
orre, enteramente,
la lista de adya
en
ia del nodo de visita; lo que impli
a que, en el peor de los
asos, se
visitaran todos los ar
os. Por tanto, la busqueda en profundidad
on listas enlazadas es,
701
701
Una de las apli
a
iones mas simples de la busqueda en profundidad es la prueba de
one
tividad de un grafo. La idea basi
a es re
orrer el grafo y veri
ar si fueron visitados todos
los nodos, en
uyo
aso podemos
on
luir
on
ertitud que el grafo es
onexo.
Hay una
onsidera
ion adi
ional que ha
e nuestro algoritmo y
onsiste en revisar la
antidad de ar
os, la
ual, si es menor al numero de nodos menos uno, enton
es podemos
on
luir que el grafo es in
onexo y ahorrarnos el re
orrido.
La rutina resultante se implanta, enton
es, de la siguiente manera:
hPrueba de
one
tividad 701i
(696)
template <class GT> inline
bool test_connectivity(GT & g)
{
// un grafo con menos arcos que nodos no puede ser conexo
if (not g.is_digraph() and g.get_num_arcs() < g.get_num_nodes() - 1)
return false;
Recorrido en amplitud
Como lo hemos indi
ado, el re
orrido en profundidad emula al re
orrido prejo sobre
un arbol: trate de ir lo mas profundo en nivel antes de regresar a pro
esar ar
os aun
no re
orridos. No tiene mu
ho sentido una variante suja, pues los ar
os apare
en en un
orden arbitrario y eventualmente
ambiante sin que el grafo pierda su sentido topologi
o;
ontrario a lo que su
ede en un arbol.
Otra forma de pro
esar el re
orrido sobre un grafo, que privilegia los nodos mas
er
anos sobre los mas lejanos y que, segun la ndole del grafo, puede ser mas
onveniente, es
702
Captulo 7. Grafos
D
H
F
E
G
L
I
(a) Ini
io en A
K
A
(b) Ini io en N
Figura 7.20: Arboles
abar
adores de amplitud del grafo de la gura 7.18
702
703
Breadth_First) and
Breadth_First))
de los nodos ha sido visitado entonces
al siguiente arco en la cola
sido visitados
Breadth_First) and
Breadth_First))
la pena meter el arco
q.put(curr_arc);
}
}
return node_counter;
}
Denes:
breadth first traversal, never used.
Uses DynListQueue 166
, has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d,
Node Arc Iterator 656a, NODE BITS 670d, reset bit arcs 666
, reset bit nodes 666
,
and set bit 664a.
704
Captulo 7. Grafos
704
Prueba de ciclos
test cycle() es de uso privado, realiza la explora
ion re
ursiva en profundidad por el
nodo curr node en busqueda del nodo src node y maneja tres parametros:
705
curr node, enton
es podemos
on
luir que pasando por curr node no es posible al
anzar
a src node y que por lo tanto por esta va no hay
i
lo. En este
aso retornamos false.
705a
}
Denes:
Uses
test cycle() retorna true si existe un i lo desde src node; false de lo ontrario.
705b
Un punto esen
ial en este algoritmo es que, a diferen
ia de lo que planteamos en las
sub-se
iones anteriores
on
ernientes a las busquedas, no debemos detenernos
uando
hayamos visitado todos los nodos, pues algun
i
lo pudiera en
ontrarse por algun ar
o
que no haya sido visitado. No tenemos en este
aso otra alternativa que explorar todos los
ar
os, razon por la
ual el algoritmo es O(E) para el peor
aso en que no exista
i
lo.
Di
ho lo anterior, estamos listos para implantar la explora
ion re
ursiva:
hB
usqueda de
i
lo 704i+
(696) 705a
template <class GT> inline static
bool __test_cycle(GT &
g,
typename GT::Node * src_node,
typename GT::Node * curr_node)
{
if (src_node == curr_node) // verificar si se alcanza nodo origen
return true; // ciclo detectado
if (IS_NODE_VISITED(curr_node, Test_Cycle))
706
Captulo 7. Grafos
7.5.5
706
Prueba de aciclicidad
707
707
is acyclique() realiza una prueba de a
i
li
idad a partir del nodo curr node. Se
retorna true si hay a
i
li
idad; false de lo
ontrario.
La deten
ion se realiza
uando se dete
ta un
i
lo; en este
aso is acyclique()
retorna false. El que no se en
uentre
i
lo por algun ar
o adya
ente de curr node no
impli
a que no exista alguno por otro de sus ar
os. Solo
uando se hayan explorado todos
los ar
os de curr node sin en
ontrar
i
lo es que podemos
on
luir que, pasando por
curr node el grafo es a
li
o.
Si un subgrafo de un grafo
onexo es
li
o, enton
es, sin importar desde
ual nodo se
ini
ie, una busqueda en profundidad debe dete
tar un
i
lo por en
uentro de un nodo ya
mar
ado. Si eso no o
urre, es de
ir, si no podemos regresar al nodo origen de la busqueda,
enton
es,
on toda
ertitud, el subgrafo es a
li
o. Si eso su
ede para todos los nodos
onexos, enton
es el grafo es a
li
o.
Hay una observa
ion
ru
ial en el
aso de un grafo
onexo (no un digrafo, multigrafo
o multidigrafo): si la
antidad de ar
os es menor que la de nodos, enton
es el grafo no
puede
ontener ningun
i
lo. Este he
ho nos a
ota la prueba de a
i
li
idad a O(V). La
prueba puede ini
iarse desde
ualquier nodo y la siguiente version publi
a de la prueba es
perfe
tamente apli
able:
hPrueba de a
i
li
idad 706i+
(696) 706 708
template <typename GT> inline
bool is_acyclique(GT & g, typename GT::Node * start_node)
{
if (not g.is_digraph() and g.get_num_arcs() >= g.get_num_nodes())
return false;
g.reset_bit_arcs(Is_Acyclique);
g.reset_bit_nodes(Is_Acyclique);
708
Captulo 7. Grafos
708
7.5.6
709
B
usqueda de caminos por profundidad
La busqueda de
aminos es quiza el problema mas
omun y
riti
o de los grafos. Hay
diversas variantes, entre la
uales tenemos:
1. Existen
ia: dados dos nodos, >existe un
amino entre ellos?
2. Determina
ion de un
amino: en
ontrar un
amino de un nodo origen ha
ia uno
destino.
3. Camino mnimo: determinar el
amino de menor
oste entre dos nodos.
4. Camino euleriano: un
amino euleriano o, simplemente, un \euleriano", es un
amino
que pasa por todos los ar
os sin repetir ninguno de ellos.
Aqu tenemos, tambien, diversa distin
iones: la existen
ia, la determina
ion de un
euleriano y la busqueda de uno de
oste mnimo.
5. Camino hamiltoniano: un
amino hamiltoniano o, simplemente, un \hamiltoniano",
es un
amino que pasa por todos los nodos si repetir ninguno de ellos.
En esta sub-se
ion solo desarrollaremos prueba de existen
ia y determina
ion de
aminos. En la se
ion x 7.8 (pagina 791) estudiaremos varios algoritmos para determinar
el
amino de
oste mnimo.
7.5.6.1
709a
Prueba de existencia
Dado un par de nodos >
omo determinar si existe un
amino entre ellos? Llamemos
test path() a la rutina re
ursiva, que explora en profundidad el grafo, en b
usqueda
de un nodo n de
amino end node y
uyo prototipo se enun
ia a
ontinua
ion:
hPrueba de
amino 709ai
(696) 709b
template <class GT> inline static
bool __test_path(GT&
g,
typename GT::Node * curr_node,
typename GT::Node * end_node);
Uses test path 710.
709b
// nodo procedencia
// arco destino
g es el grafo. curr node es el nodo a
tual que se esta visitando y end node es el nodo
destino del
amino. Si se en
uentra a end node, enton
es existe el
amino pasando por
curr node y la rutina retorna true. Si se exploran todas las vas por curr node sin
en
ontrar a end node, enton
es se
on
luye que pasando por curr node no existe
amino
a end node y la rutina retorna false.
Para este problema, el
ono
imiento que tengamos sobre la
one
tividad del grafo nos
ayuda un po
o. El grafo puede ser in
onexo y aun as existir
amino entre los nodos
onsiderados. As las
osas, en esta situa
ion no tenemos mas alternativa que explorar el
grafo. Pero si el grafo es
onexo (y no es digrafo), enton
es
on
ertitud existe un
amino
entre
ualquiera de sus nodos y no requerimos explorarlo.
Veamos ahora
omo se apli
a la rutina anterior para la prueba de existen
ia de
amino
entre un nodo ini
ial start node y otro nal end node:
hPrueba de
amino 709ai+
(696) 709a 710
template <class GT> inline
710
Captulo 7. Grafos
bool test_path(GT&
g,
typename GT::Node * start_node,
typename GT::Node * end_node)
{
// si el grafo es conexo ==> existe camino
if (not g.is_digraph() and g.get_num_arcs() >= g.get_num_nodes())
return true;
g.reset_bit_nodes(Test_Path); // reiniciar bit Test_Path para nodos y arcos
g.reset_bit_arcs(Test_Path);
// buscar recursivamente caminos por arcos adyacentes a start_node
for (typename GT::Node_Arc_Iterator i(start_node); i.has_current(); i.next())
{
typename GT::Arc * arc = i.get_current_arc();
ARC_BITS(arc).set_bit(Test_Path, true); // marcar arco
// existe camino hasta end_node pasando por arc?
if (__test_path(g, i.get_tgt_node(), end_node))
return true; // s
, retornar
}
// todos los arcos de start_node han sido explorados sin encontrar un
// camino hasta end_node ==> no existe camino
return false;
}
Denes:
710
test path() retorna true si existe un
amino entre start node y end node; false de
lo
ontrario.
La prueba de
amino par
ial; es de
ir, determinar si hay un
amino desde un nodo
intermedio curr node hasta uno nal end node se implementa segun el patron de busqueda
en profundidad:
hPrueba de
amino 709ai+
(696) 709b
template <class GT> inline static
bool __test_path(GT &
g,
typename GT::Node * curr_node, // nodo procedencia
typename GT::Node * end_node) // arco destino
{
if (curr_node == end_node) // se encontr
o end_node?
return true; // s
==> existe camino; retornar true
711
711
B
usqueda de camino entre dos nodos
En otras o
asiones, a
ondi
ion por supuesto de su existen
ia, lo que se desea es en
ontrar
y
onstruir un
amino
ualquiera entre dos nodos. Para ello, dise~naremos una primitiva,
muy similar a la de la sub-se
ion anterior,
uyo n sea obtener un objeto de tipo Path
ontentivo de un
amino entre dos nodos y
on la interfaz siguiente:
hCamino de grafo 690bi+
(648) 690b 712
template <class GT> inline
bool find_path_depth_first(GT&
g,
typename GT::Node * start_node,
typename GT::Node * end_node,
Path<GT> &
path);
Uses find path depth first 713 and Path 690b.
712
712
Captulo 7. Grafos
nodo actual
arco procedencia
nodo destino
camino actual
return true;
}
if (IS_NODE_VISITED(curr_node, Find_Path)) // No ha sido visitado?
return false; // s
==> desde
el no hay camino
curr_path.append(curr_arc); // a~
nadir curr_arc al camino
NODE_BITS(curr_node).set_bit(Find_Path, true); // marcar nodo
// buscar recursivamente a trav
es de arcos de curr_node
for (typename GT::Node_Arc_Iterator i(curr_node); i.has_current(); i.next())
{
typename GT::Arc * next_arc = i.get_current_arc();
if (IS_ARC_VISITED(next_arc, Find_Path))
continue; // ya se pas
o por este arco
ARC_BITS(next_arc).set_bit(Find_Path, true); // marcar arco
typename GT::Node * next_node = i.get_tgt_node();
// continuamos exploraci
on en profundidad desde next_node
if (__find_path_depth_first <GT> (g, next_node, next_arc,
end_node, curr_path))
return true; // se encontr
o camino, terminar retornando true
}
// no se encontr
o camino por curr_arc ==> eliminarlo de path
curr_path.remove_last_node();
return false;
}
Denes:
713
713
La estru
tura es la misma que la de las diferentes explora
iones en profundidad que
hemos presentado. La diferen
ia estriba en que antes de visitar re
ursivamente los ar
os
de curr node, lo a~nadimos al
amino. Si re
orremos todos los ar
os de curr node sin
en
ontrar un
amino ha
ia end node, enton
es sa
amos a curr node del
amino. Dada la
ndole re
ursiva del algoritmo, path funge
omo pila. Por tanto, el maximo tama~no de
path podra al
anzar O(E).
find path depth first() retorna true si se logra
onstruir un
amino ha
ia end node proviniendo desde current node. Hay dos parametros adi
ionales respe
to
a test path(): el ar
o a
tual current arc y el
amino path. path es un parametro de
salida que
ontiene el valor del
amino en
ontrado. current arc es un ar
o
uyo nodo
origen es current node que se requiere porque en la
lase Path se inserta por ar
os y no
por nodos.
Nos resta implantar la primitiva publi
a:
hCamino de grafo 690bi+
(648) 712
template <class GT> inline
bool find_path_depth_first(GT&
typename GT::Node *
typename GT::Node *
Path<GT> &
{
if (not path.inside_graph(g))
throw std::invalid_argument("Path does not
g,
start_node,
end_node,
path)
belong to graph");
path.clear_path();
// limpiamos path
path.init(start_node); // insertamos nodo origen
g.reset_bit_nodes(Find_Path);
g.reset_bit_arcs(Find_Path);
NODE_BITS(start_node).set_bit(Find_Path, true); // marcar start_node visitado
// explorar recursivamente cada arco de start_node
for (typename GT::Node_Arc_Iterator i(start_node); i.has_current(); i.next())
{
typename GT::Arc * arc = i.get_current_arc();
// marcar arco como visitado
ARC_BITS(arc).set_bit(Find_Path, true);
typename GT::Node * next_node = i.get_tgt_node();
if (IS_NODE_VISITED(next_node, Find_Path))
continue;
if (__find_path_depth_first(g, next_node, arc, end_node, path))
return true;
}
return false;
}
714
Captulo 7. Grafos
Denes:
Cuando se desea
onstruir (o determinar existen
ia de) un
amino
ualquiera entre dos
nodos, es preferible valerse, en una primera instan
ia, de una explora
ion en profundidad.
Luego, si se requiere uno mas
orto en ar
os, o que
onsuma menos memoria, enton
es
puede ha
erse por amplitud. Otra situa
ion en la
ual es denitivamente preferible la
explora
ion en profundidad es
uando se busque un
amino sobre un grafo a
li
o o un
arbol.
7.5.7
714a
B
usqueda de caminos por amplitud
El n de esta sub-se
ion es
onstruir un
amino por amplitud entre dos nodos start
y end. Al
omenzar por start, exploramos y empilamos todos sus ar
os en busqueda
de end. Si no hemos hallado end, enton
es
ontinuamos por todos los
aminos de longitud
dos. Este pro
eso
ontinua hasta en
ontrar el
amino en
uestion.
Para realizar la estrategia anterior, la
ola debe guardar
aminos par
iales desde el
nodo de ini
io hasta el del ultimo nivel que se haya explorado; lo que se espe
i
a del
siguiente modo:
hDe
lara
i
on de
ola de
aminos 714ai
(715)
DynListQueue<Path<GT>*> q; // cola de caminos parciales
Uses DynListQueue 166
and Path 690b.
714b
Notese que es una
ola de punteros a
aminos, lo que impli
a que la memoria de estos
debe apartarse y, sobre todo,
uando se va
e la
ola, liberarse. Como se trata de punteros
Path* el destru
tor de la
ola no liberara la memoria o
upada por los
aminos, lo que
requiere la siguiente a
ion expl
ita para liberar la
ola:
hva
iar
ola y liberar
aminos 714bi
(715)
while (not q.is_empty())
delete q.get();
714
Esta a
ion debe ha
erse al nal del pro
edimiento de la busqueda, se haya en
ontrado o
no un
amino, o si o
urre una ex
ep
ion.
Para manejar el
amino a
tual de explora
ion, manejaremos la siguiente variable:
hDe
lara
i
on de
amino a
tual 714
i
(715)
Path<GT> * path_ptr = NULL;
Uses Path 690b.
714d
La inser
ion en la
ola requiere (1) apartar memoria para el
amino y (2) la propia
a
ion de insertar en la
ola. Puesto que hay manejo de memoria en ambas opera
iones,
usaremos una rutina
on auto-punteros:
hRe
orrido en amplitud 702i+
(696) 702 715
template <class GT> inline static
void __insert_in_queue(GT &
g,
DynListQueue<Path<GT> *> & q,
typename GT::Node *
node,
typename GT::Arc *
arc,
Path<GT> *
path_ptr = NULL)
{
715
1. Si path ptr == NULL enton
es path auto es un nuevo
amino
on nodo de ini
io node.
2. Si path ptr != NULL enton
es el
amino
ontenido en *path ptr se
opia a
path auto.
715
En
ualquiera de los dos
asos, el ar
o arc se le a~nade a path auto y este se inserta en la
ola.
Con lo anterior, podemos dise~nar la siguiente busqueda de
amino en amplitud:
hRe
orrido en amplitud 702i+
(696) 714d
template <class GT> inline
bool find_path_breadth_first(GT& g,
typename
typename
Path<GT>
{
if (not path.inside_graph(g))
throw std::invalid_argument("Path
GT::Node * start,
GT::Node * end,
&
path)
on
hDe
lara
i
de ola de aminos
on
hDe
lara
i
try
714ai
716
Captulo 7. Grafos
714bi
delete path_ptr;
return true;
}
NODE_BITS(tgt).set_bit(Find_Path, true); // marcar
ultimo nodo camino
// insertar en cola arcos del nodo reci
en visitado
for (typename GT::Node_Arc_Iterator i(tgt); i.has_current(); i.next())
{
typename GT::Arc * curr_arc = i.get_current_arc();
if (IS_ARC_VISITED(curr_arc, Find_Path)) // se visit
o arco?
continue; // s
==> avanzar al siguiente (ya fue visto)
// revise nodos del arco para ver si han sido visitados
if (IS_NODE_VISITED(g.get_src_node(curr_arc), Find_Path) and
IS_NODE_VISITED(g.get_tgt_node(curr_arc), Find_Path))
continue; // nodos ya visitados ==> no vale la pena meter arco
__insert_in_queue<GT>(g, q, NULL, curr_arc, path_ptr);
717
}
delete path_ptr; // borrar camino extra
do de la cola
} // fin while (not q.is_empty())
}
catch (...) // hay excepci
on
{
on
hva
iar
ola y liberar
aminos 714bi // antes de propagar excepci
delete path_ptr;
throw; // propagar la excepci
on
}
hva
iar
714bi
delete path_ptr;
return false;
}
Denes:
Uses
Se debe prestar mu
ha aten
ion a liberar los
aminos que sean vistos y dese
hados; bien sea
porque el nodo ya haya sido visitado, o bien sea porque path ptr ya haya sido
opiado.
Como se debe apre
iar, find path breadth first()
onsume mu
ha mas memoria
que su
ontraparte en profundidad. Aparte de que la
ola tiende a
re
er mas que una
pila,
ual es el
aso en el re
orrido en profundidad, la
ola guarda
aminos enteros desde
el nodo de ini
io.
7.5.8
717
Arboles
abarcadores de profundidad
El objeto de esta sub-se
ion es desarrollar un algoritmo que, a partir de un nodo de ini
io,
explore en profundidad un grafo y
onstruya otro grafo
orrespondiente a un arbol abar
ador. Tal rutina tiene la siguiente forma general:
hArboles
(696) 718a
abar
adores 717i
template <class GT> inline
bool find_depth_first_spanning_tree(GT &
g,
typename GT::Node * gnode,
GT &
tree)
{
hIni
ializar
onstru
i
on arbol abar
ador 718bi
hRe
orrer
return true;
}
Denes:
718
Captulo 7. Grafos
718a
718b
En este
aso, el valor de retorno sera el nodo dentro de g (no en tree) que sera raz del
arbol abar
ador, o NULL si g no es un arbol. Si se desea
ono
er la raz en tree, puede
bus
arse a partir del dato
ontenido mediante tree.search node().
Antes de
omenzar la explora
ion re
ursiva de g, es ne
esario limpiar sus bits de
ontrol
y asegurarse de que tree este va
o:
hIni
ializar
onstru
i
on arbol abar
ador 718bi
(717)
g.reset_nodes();
g.reset_arcs();
719a
719
Usamos el bit Spanning Tree para pintar un nodo o ar
o que haya sido visitado.
Con lo anterior en mente, podemos plantear el re
orrido en profundidad a partir
de gnode:
hRe
orrer en profundidad nodos adya
entes a gnode 719ai
(717 719b)
for (typename GT::Node_Arc_Iterator i(gnode); i.has_current(); i.next())
{
typename GT::Arc * arc = i.get_current_arc();
if (IS_ARC_VISITED(arc, Spanning_Tree))
continue;
typename GT::Node * arc_tgt_node = i.get_tgt_node();
if (IS_NODE_VISITED(arc_tgt_node, Spanning_Tree))
continue; // nodo destino tambi
en ya ha sido visitado desde otro arco
if (__find_depth_first_spanning_tree(g, arc_tgt_node, arc, tree, tnode))
return false; // ya el
arbol est
a calculado
}
Uses
find depth first spanning tree 719b, has current 103, IS ARC VISITED 670d,
IS NODE VISITED 670d, and Node Arc Iterator 656a.
720
Captulo 7. Grafos
// insertar tgt_node en
arbol y mapearlo
typename GT::Node * tree_tgt_node = tree.insert_node(gnode->get_info());
GT::map_nodes(gnode, tree_tgt_node);
// insertar arc en
arbol y mapearlo
typename GT::Arc * tarc =
tree.insert_arc(tnode, tree_tgt_node, garc->get_info());
GT::map_arcs(garc, tarc);
tnode = tree_tgt_node; // esto hace que el bloque de recorrido sea el mismo
if (tree.get_num_nodes() == g.get_num_nodes()) // se ha abarcado el grafo?
return true; // tree ya contiene el
arbol abarcador
hRe
orrer
return false;
}
Denes:
720
Arboles
abarcadores de amplitud
En el mismo sentido que para un arbol abar
ador en profundidad, podemos plantear
una rutina que nos extraiga del grafo el arbol abar
ador
orrespondiente al re
orrido en
amplitud desde un nodo dado. Tal rutina se realiza de la siguiente manera:
abar
adores 717i+
hArboles
(696) 719b
template <class GT> inline
void find_breadth_first_spanning_tree(GT &
g,
typename GT::Node * gnode,
GT &
tree)
{
g.reset_bit_nodes(Spanning_Tree);
g.reset_bit_arcs(Spanning_Tree);
// inicialice
arbol e inserte copia mapeada de gnode
tree.clear_graph();
auto_ptr<typename GT::Node> tnode_auto (new typename GT::Node(gnode));
tree.insert_node(tnode_auto.get());
GT::map_nodes(gnode, tnode_auto.release());
// recorra arcos de gnode e ins
ertelos en cola
DynListQueue<typename GT::Arc*> q;
721
722
Captulo 7. Grafos
Puesto que de la
ola se obtienen ar
os de los
uales un solo nodo esta
ontenido en
tree y, ademas, tree es permanentemente
onexo, enton
es es imposible que la in
lusion
de un nuevo ar
o
ause un
i
lo. tree es, por tanto, un arbol abar
ador.
7.5.10
722a
722b
Conversi
on de un
arbol abarcador a un Tree Node<T>
En x 4.5 (pagina 322) desarrollamos el TAD Tree Node<T>, el
ual modeliza un arbol
m-rio; mientras que en la sub-se
iones pre
edentes a
abamos de desarrollar algoritmos
para
al
ular arboles abar
adores
orrespondiente a los re
orridos en profundidad y amplitud de un grafo. En esta sub-se
ion desarrollaremos un algoritmo que nos
onvierte
un arbol abar
ador de tipo List Graph<Node, Arc> a un arbol de tipo Tree Node<T>.
Aparte de su valor dida
ti
o, esta rutina nos permitira apelar al dibujado automati
o
de arboles m-rios y as esquematizar, a lo largo de este texto, diferentes arboles que se
des
ubren en mu
hos de los algoritmos que existen sobre grafos.
Nuestro algoritmo de
onversion residira en el ar
hivo hgraph to tree.H 722ai, el
ual
tiene la siguiente estru
tura:
hgraph to tree.H 722ai
on de List Graph<Node, Arc> a Tree Node<T> 722bi
hConversi
La
onversion se invo
a a traves de la siguiente interfaz:
hConversi
on de List Graph<Node, Arc> a Tree Node<T> 722bi
(722a) 723a
template <typename GT, typename Key, class Convert> static
Tree_Node<Key> * graph_to_tree_node(GT & g, typename GT::Node * groot);
Uses graph to tree node 723b and Tree Node 322.
La
ual retorna un arbol de tipo Tree Node<Key>*
orrespondiente al arbol abar
adorg
on raz en groot .
graph to tree node() tiene tres parametros tipo:
13
1. GT: El tipo de List Graph<Node, Arc>, que
ontiene un arbol abar
ador, y que se
desea
onvertir a Tree Node<T>.
2. Key: La
lave que se alma
enara en el Tree Node<T>.
3. Convert: Una
lase de transforma
ion desde typename GT::Node* ha
ia Tree Node<Key>*.
13 Los
arboles dibujados en este texto son pro
esados
on una rutina llamada generate tree() residente
en el ar
hivo generate tree.H, la
ual genera un arbol de entrada al programa de dibujado ntreepic.
723
Cada vez que se
rea y se mapea un nodo de tipo groot de tipo typename GT::Node
*, se invo
a a:
Convert () (groot, troot);
La
ual extrae alguna informa
ion de groot, la
onvierte al tipo Key y asigna este
resultado a troot->get key().
723a
la
ual se en
arga de re
orrer re
ursivamente el arbol abar
adorg a partir del nodo groot
y mantiene en troot el equivalente de tipo Tree Node<Key>.
Coro
Paraguiapos
82
254
10
310
Caracas
55
252
199
Maracaibo
Maracay
166
139
49
Maturn
130
315
Valencia
251
435
San Felipe
295
Valera
69
San Cristobal
La Fra
86
120
36
86
Carora
San Carlos
79
38
El Cantn
150
200
261
173
Mrida
Sacramento
Rubio
Ciudad Bolivar
Barquisimeto
167
El Viga
241
102
113
22
Pto Ordaz
100
107
59
San Antonio
171
130
220
Santa Barbara
252
48
El Tigre
San Juan
222
Machiques
526
180
Barinas
94
Guanare
547
San Fernando
Caparo
723b
Cuman
Pto La Cruz
Barcelona
109
Mediante graph to tree node() podemos implantar la rutina prin
ipal de la siguiente manera:
hConversi
on de List Graph<Node, Arc> a Tree Node<T> 722bi+
(722a) 723a 724
template <typename GT, typename Key, class Convert> inline
Tree_Node<Key> * graph_to_tree_node(GT & g, typename GT::Node * groot)
{
if (not Aleph::is_acyclique(g))
throw std::domain_error("Graph is not a tree (not acyclique)");
724
Captulo 7. Grafos
Merida
El Vigia
La Fria
Machiques
Maracaibo
Coro
Sta Barbara
Paraguaipos
Valencia
Barquisimeto
Carora
Guanare
Valera
Barinas
Sn Antonio
Caparo
Sn Fernando
Rubio
Sn Juan
Sn Cristobal
Caracas
Smento
Barcelona
Maracay
El Canton
Pto La Cruz
Sn Felipe
Cumana
Sn Carlos
Maturin
Pto Ordaz
Cd Bolivar
El Tigre
Figura 7.22: Arbol
abar
ador en profundidad del grafo de la gura x 7.21 (pagina 723)
on
nodo de ini
io \Merida"
724
graph to tree() ini
ializa la raz Tree Node<Key>. El resto del trabajo lo realiza la
explora
ion re
ursiva de groot implantada por graph to tree():
hConversi
on de List Graph<Node, Arc> a Tree Node<T> 722bi+
(722a) 723b
template <typename GT, typename Key, typename Convert> static
void __graph_to_tree_node(GT &
g,
typename GT::Node * groot,
Tree_Node<Key> *
troot)
{
725
Merida
El Vigia
La Fria
Sta Barbara
Machiques Sn Cristobal
Sn Antonio
Valera
Carora
Barquisimeto
Smento
Valencia
El Canton
Maracay
Barinas
Maracaibo
Coro
Sn Felipe
Paraguaipos
Caparo
Guanare
Sn Fernando
Rubio
Sn Carlos
Sn Juan
Caracas
El T
Barcelona Cd Bo
Pto La Cruz Pto
Cumana
rbol abar
ador en amplitud del grafo de la gura x 7.21 (pagina 723)
on
Figura 7.23: A
nodo de ini
io \Merida"
Es su
iente
on solo mar
ar los ar
os que ya se han visitado, pues,
omo g es un
arbol, no hay posibilidad de visitar al nodo desde otro ar
o. El bit de mar
a se denomina Convert Tree.
Mat
726
7.5.11
726a
Captulo 7. Grafos
inconnected components() re
ibe un grafo g y una lista va
a list. Luego de la eje
u
ion de la primitiva, list
ontiene los subgrafos mapeos de g
orrespondientes a sus
omponentes in
onexos. Si g es
onexo, enton
es list
ontiene un solo grafo.
726b
Hay varias maneras de aprove
har el re
orrido en profundidad para resolver este problema, algunas mas e
ientes y otras mas simples de implantar. En todos los
asos, la idea
basi
a es re
orrer los nodos del grafo segun que el nodo examinado se haya visitado o no.
Un algoritmo simple
onsiste en realizar dos pasadas. La primera re
orre los ar
os por
profundidad y pinta los nodos segun la
one
tividad. Al nal de esta pasada, los nodos y
ar
os quedan pintados segun el
olor del
omponente in
onexo desde el
ual se ini
io su
re
orrido en profundidad. Durante esta pasada se insertan los nodos, mas no los ar
os, en
los subgrafos resultados.
Posteriormente, en una segunda pasada, se re
orren los ar
os y estos, segun su
olor,
se insertan en su
orrespondiente subgrafo resultado. La primera pasada del algoritmo
toma O(E), mientras que la segunda O(E). Por tanto, el algoritmo es O(E).
Se puede lograr un algoritmo mas e
iente que ahorre la segunda pasada si se
onstruye
dire
tamente el subgrafo durante el re
orrido en profundidad, en lugar de postergarlo para
la segunda pasada. Este es el enfoque que emplearemos.
Consideremos la primitiva siguiente:
hComponentes in
onexos 726ai+
(696) 726a 726
template <class GT> inline
void build_subgraph(GT &
g,
GT &
sg,
typename GT::Node * g_src,
size_t &
node_count);
Uses build subgraph 727.
//
//
//
//
grafo original
componente inconexo
nodo actual en g
contador de nodos
726
727
g.reset_nodes();
g.reset_arcs();
size_t count = 0; // contador de nodos visitados
// Recorrer todos los nodos de g
for (typename GT::Node_Iterator i(g);
count < g.get_num_nodes() and i.has_current(); i.next())
{
typename GT::Node * curr_node = i.get_current_node();
if (IS_NODE_VISITED(curr_node, Build_Subtree))
continue;; // nodo ya fue visitado en otro recorrido, avance al pr
oximo
// crear subgrafo donde se colocar
a el componente inconexo que se
// descubrir
a a partir de curr_node
list.append(GT()); // crea subgrafo y lo inserta en lista
GT & subgraph = list.get_last(); // obtiene una referencia al grafo
// reci
en creado e insertado en list
// construir subgrafo conexo a partir de curr_node
build_subgraph(g, subgraph, curr_node, count);
}
}
Denes:
727
Ahora podemos implantar build subgraph(), la
ual se remite a explorar en profundidad desde un nodo g src a la busqueda de todos los ar
os y nodos asequibles desde
g src y que
onformaran un
omponente
onexo del grafo:
hComponentes in
onexos 726ai+
(696) 726
template <class GT> inline
void build_subgraph(GT &
g,
// grafo original
GT &
sg,
// subgrafo componente inconexo
typename GT::Node * g_src, // nodo actual en g
size_t &
node_count)
{
if (sg.get_num_nodes() != 0)
throw std::domain_error("sg is not empty");
if (IS_NODE_VISITED(g_src, Build_Subtree))
return;
NODE_BITS(g_src).set_bit(Build_Subtree, true); // marcar g_src visitado
++node_count;
typename GT::Node * sg_src = // obtener imagen de g_src en sg
static_cast<typename GT::Node *>(NODE_COOKIE(g_src));
728
Captulo 7. Grafos
7.5.12
Puntos de articulaci
on de un grafo
729
Figura 7.25: Apari
ion de un punto de
orte en un
amino entre dos bloques de un grafo
La propiedad anterior es importante para
ara
terizar una rela
ion que apare
e entre
un nodo de
orte
uando este es raz de un arbol abar
ador de profundidad. En este
sentido, el lema siguiente es el primer fundamento del algoritmo de
al
ulo de nodos de
orte:
Lema 7.2 Sea T el arbol abar
ador de profundidad de un grafo G =< V, E > generado a
partir de un nodo v. Enton
es, v es un nodo de
orte de un grafo G si y solo si la raz v
730
Captulo 7. Grafos
Necesidad = (contradicci
on)
uni o hijo w:
G - {v}
.
.
Enton
es, w es un arbol abar
ador que abar
a a los nodos restantes V {v}, lo que
ontradi
e la arma
ion de que v es un nodo de
orte
Suficiencia = Supongamos dos nodos u y w hijos de v:
v
1. P =
.....
........
......
............
....... ....
..... .....
..
.... .....
... ....
.........
... ...
........
......
... ..
.. .
....... .... ....
......
.
......
...
... ...
.... ..
.
........ .....
.
... ...... ......
...... ......
...
..............
...
..............
.
....
...
.
....
.
.....
.
.....
.
......
.
.....
.
......
.
A
C
G
F
.........
........
.......
......
.....
...
..
...
..
...
.....
.....
......
........
.........
.........
........
....
.
.
.
......
....
...
...
...
.
...
.....
.....
......
........
.........
H
I
L
J
K
.....
.
.....
.
...
.
....
.
...
.
...
.
...
.
.....
...
.
.....
.
.... ...
... ...
... ...
.....
...
..
....
..
.....
..
.. .
. ...
..
. ...
......
.........
... .
........ .. .....
...... ...
. .....
.......... ...... ..
...
.
...
..........
. .
...
.........
..
..
..
.
.
.
.
...
.
.
......
.......
..
.........
......
.
731
......
J
I
.
.....
.
......
.
......
.
.....
.....
....
.........
........
...
.......
... ......
.
.
.
. ...
. ..
....
..
.........
....
........
.
.......
........
..
......
..... ... ..... ..
....
... ...
........
.. .
.........
... ...
...
........
.
...
... .
.
......... .....
..
.
.
.
.
.
.
...... ......
....
.
.
...
.....
......
.
...
.......
..
......
...
.
........
...
.
...
.....
.....
......
........
.........
(a)
Comienzo
en D
F
B
A
C
G
D
.
..
...
.
..
.
...
..
...
....
..
..
.
.....
. ..
.
...
...
.
......
.....
.
.
......
... ...
.
...
...... ..
.
...
.........
.
...
.. ....
... ...
.
.
. .
.
... .
.
. .
..
... .
.....
...
......
...
.....
.
..
..
.....
.
.
..
... ...
...
... ...
... ...
...
.
.
.. ...
. .
........
..
...... ..
.
.
.
.
.
.
.
..
.......
......
.......
.....
.....
.
..
......
..
.......
..
......
..
....
... .. ...
. . ..
.... ... .
.
....... ...
.
...... ...
. ..
.... ..
.. .
..... ...
. ...
.. .....
. ..
...
.
... ........
.. ...
..
..
...
.
...
..
L
.
......
.
......
.
......
.
.....
.
....
.
...
.........
........
...
.......
... ......
.
.
.
. ...
. ..
..
...
..
....
.
........
.
... ......
......
...
........
.
...
.........
...
........
.
... .
........... .....
.
...... ......
.
...
.....
.
...
......
...
.
..
..
...
.....
.....
......
........
.........
(b) Comienzo en J
F
B
A
C
G
D
.
..
...
.
..
.
...
.....
..
.
...
....
.
.
..
.
.....
. ...
...
...
......
.....
.
.
.
......
... ...
.
...
...... ...
.
...
.........
.
...
... .....
... ...
.
.
. .
.
... .
.
. .
..
... .
....
...
..
.
....
..
....
...
....
...
.
..
..
... ...
.
... ...
..
.
.
.
.
.
. .
..
. .
.. ...
. .
..... ...
.
...... ...
. ..
.... ..
.......
......
.......
.....
...
.
...
....
...
...
...
..
..
....
..
.........
......
.
.
.......
......
.....
...
...
...
.
...
....
.....
......
I
L
J
........
.........
.........
........
.......
......
.....
...
..
..
..
...
.
.
.
.
.
....
.
......
........
........
( ) Comienzo en H
Figura 7.27: Arboles
abar
adores del grafo de la gura 7.26 junto
on los ar
os noabar
adores. Tanto en la gura (a)
omo (b), ningun des
endiente del nodo H tiene un
ar
o no-abar
ador que
one
te a un an
estro de H. En la gura (
), H es raz y
ontiene
dos ramas, lo que satisfa
e el lema 7.2.
2. v V
(a) Sea T el arbol abar
ador de profundidad a partir del nodo v.
(b) Si v tiene mas de un hijo = P = P {v}
El lazo externo
laramente requiere O(V). La
omplejidad sera, enton
es, O(V) ve
es lo
que demore
ada re
orrido en profundidad. Por tanto, el peor desempe~no de este algoritmo
es O(V 2) o O(V E) segun la densidad del grafo.
B
732
Captulo 7. Grafos
y prestar aten
ion al lazo externo que ini
ia la explora
ion a partir de un nodo v. Si la
llamada depth first traversal() sobre el primer ar
o de v no
ubre todos los nodos,
enton
es v
omo raz del arbol abar
ador en profundidad tiene mas de una rama y es,
por tanto, un nodo de
orte.
.....
........
......
...........
...... .....
.
..... .....
..
.... .....
... ....
........
... ...
........
....
... ..
. ...
..... ... .....
... ..
.......
......
... ...
... ...
.... ..
... ... ...
.
... ...... ......
... ...... ........ .
...... ......
...
............
.
.....
...
.
.....
.....
.
.....
.
......
.
..
.....
.
......
.
.....
.
.....
.....
....
.
.
.
.
..
...
...
..
........
..
.
........
...
.......
... ... ......
..
......
.. ..
.. ...
...
...
...
.....
.....
.....
.....
...... . ....
........ .. .
...............
........
........
.....
.
.
.....
.
.....
....
..
..
..
...
.....
.....
......
........
.........
D
B
A
C
G
F
H
I
L
J
.....
.
.....
.
.....
.
...
..
....
.
...
.
...
.
......
...
.
.....
...
.
.....
.
... ...
. ..
... .
... ...
....
.
...
..
.....
.....
..
... .
. ..
... ..
. ..
.....
.
.........
... .
........ .. ....
.... ...
. ......
........... ...... ..
...
.
...
...........
..
...
.........
..
.........
........
..
.......
...
.
.
.
........ .
.......
.
...
......
...
........
..
........
...
...
.
.
.
..
....
.
......
........
......
............
........
.......
......
.....
...
...
...
..
...
.
.
..
....
.
......
.......
..
.........
......
.
......
.
K
(a)
Comienzo
en D
J
I
..
......
. .
..... ...
.
.
.
.
.....
.
...
.....
.
...
. ........
.
.
.......... ..
.
..... .. .
.
.
.
.
. ..
. ......
.
.
.
.
........
.. ....
.......
..
....
... ..
...
.
.
. ...
.......
..
... ...
... ......
...... ... ..
...
......
.
... .....
...
. .. .... ......... ..
.... . .
.
.
.
.
...
........
..
..... ..... ...
........
.
.
.
.
....
......
.
...
.
..... ...
........
.... . .......
.... .......... .
..... ........
.
.
.......
......
.......
... ..
..
..
.....
.....
......
........
.........
.
.....
B
A
C
G
.
....
..
....
....
.. .
.
.. ..
. .
.
.. ...
. ..
..... . ...
. ..
... .....
... ..
. . ...
... .... ... .
. . ...
.... .. ... .. .
. .
... ...
..... ..
.
.. ..
... ..
.....
.
... ..
... ....
...
......
... .....
.
... ....
.. ... ...
. . .. .. ...
. .
... . .
... ... ...
..
.
.. .
... .. ..
. ..
.
... .
... .. ...
.
... . ...
....
.
...
...
... .
.
....
.
...
....
......
.
.
.
...
....
.....
.
.
...
....
....
....
...
......
...
....
....
.
...
.....
......
.... ...
...
.... .. .
...
.. ...
...
.. ..
.... ...
...
........
...
.
..........
...
.. ......
.
..
.
.
. . ..
...
.
.
...... ..
...
...
......
.
.
.
.....
...
......
.
...
.
.
.
.
.
.
..
.
.
.
...
..
.
.
..
....
(b) Comienzo en J
.
....
.
.....
.
....
.
...
........
.
. .
...
...... ...
.
.
..
.
.....
.
.
.
.
.....
...
.
.
..
...
... ....
.
....
... ...
. .... .
.
.......... ..
.
.
.. ..
..... ... ..
.
.
.
..
.... . ......
.
.
.
... . ...
.. ...
......
....
..
......
.. ..
....
......
.
. ...
... ....
.
.
... .
... ..........
. ..
... ... ...... .......
.
.
... ... .. ...... .........
.
..
.... ...... ...
...
...
..............
..
.
...... ........... ...
........
..
......
....
......
......
...
...
.
..... ..
. .
.
.
.
.... ...
.
.
...... ....
...
.......
..
H
F
B
A
C
G
..
..
.
...
...
..
..
....
.. .....
.
.. ...
... ....
..
... ....
...
..
.. ....
.. ...
..
... ......
. ..
.. .....
.. .....
.. ......
.. .. ..
.. ....
.. .. ..
.
.. .. ..
.. ... ..
.
.. .. ..
.
.... ..
..... ..
..
....... .
...... ...
. .
.....
.. ..
... ..
... .
.. ..
... ..
..... ..
. .
.............
.
.
..... ..
.
.....
.
.
...
.
.
...
....
.
....
.
.
...
..
..
.
.
.
.
...
...
...
...
..
....
......
......
.......
........
L
I
J
.........
..
.......
......
.....
.....
...
..
..
..
..
.
....
.
......
.......
........
.........
( ) Comienzo en H
Figura 7.29: Arboles
abar
adores del grafo de la gura 7.28 junto
on los ar
os noabar
adores. Aqu
onrmamos que no existen puntos de
orte, pues, en todas las guras,
para todo nodo no raz v, siempre hay un ar
o no-abar
ador por debajo que
one
ta a un
nodo an
estro de v.
>Pueden
al
ularse los puntos de
orte
on una sola explora
ion en profundidad del
grafo? Nos aproximaremos a la respuesta mediante el siguiente lema:
Lema 7.3 Sea G =< V, E > un grafo. Sea T =< V, E >, E E un arbol abar
ador de
profundidad de G. Sea E E el
onjunto de \ar
os no-abar
adores", dados por los ar
os
que estan en E pero no en E .
Enton
es, un nodo v G, que no sea raz de T , es un nodo de
orte si y solo si v tiene
un hijo w en T tal que ningun des
endiente de w esta
one
tado a un an
estro de v en T
733
Por el
ontrario, en la familia de guras 7.29 vemos que todo sub-arbol
ontiene un
ar
o no-abar
ador que
one
ta a un an
estro de la raz.
Demostraci
on
Necesidad = (por contradicci
on): Supongamos la situa
ion
ontraria: existe
un nodo
orte v tal que un des
endiente w tiene un ar
o en G que
one
ta a un an
estro
u de v. Tal situa
ion puede pi
torizarse del siguiente modo:
u
Ancestro
arco en G
Nodo de
corte
Descendiente
La pi
toriza
ion revela la
ontradi
ion. En primer lugar existe un
amino desde u ha
ia
w pasando por v, pues este esta denido por el arbol abar
ador. Sin embargo, tambien
existira un
amino desde u ha
ia w sin pasar por v. La uni
a forma en que o
urra esto es
que u y v pertenez
an al mismo
omponente in
onexo que
ausara la supresion de v. Pero
esto es una
ontradi
ion, pues si pertene
iesen al mismo
omponente in
onexo, enton
es
no existira un
amino entre u y w pasando por v
Suponga que v tiene un hijo w tal que ningun des
endiente de w
esta
one
tado a un an
estro de v por un ar
o de G que no pertenez
a a T . Enton
es,
ada
amino en G desde w hasta el nodo raz de T debe pasar por v, lo que indi
a que v es un
nodo de
orte
Suficiencia =:
Ahora estamos en
apa
idad de enun
iar otro algoritmo para determinar los puntos de
orte.
Algoritmo 7.3 (Determina
ion de los puntos de
orte de un Grafo (2da version))
Entrada: Un grafo
onexo G.
Salida: Un
onjunto P
on los nodos de
orte de G.
1. P =
2. Es
oja un nodo
ualquiera de G =< V, E > y a partir de el genere un arbol abar
ador
en profundidad T .
3. Si r = raiz(T ) tiene mas de un hijo = P = P {r}.
4. ni T | ni 6= raiz(T )
(a) v = hijo(ni)
734
Captulo 7. Grafos
D,0,-
....
.........
.....
.............
....... ...
.
.
.
.
.
.
.
.. .
.... .....
... ....
.........
... ....
........
.
.
......
. ..
..
..... ... ....
..... ...
.
.........
.. ...
... ...
......... .....
.
... ...... ......
...... ......
...
..............
.
...
..............
.
....
....
.
.....
.....
.
.....
.
......
.
.....
.
.....
.
.....
.
...
.
....
...
.
...
.
...
.
.....
...
.
.....
.
.... ...
... ...
. .
... .
.
.
...
..
....
..
.
...
.
.
.....
.
.....
.
... ..
.
.
.
.
.. ..
.
.........
...
........ .. . ....
...... ...
. .....
.......... ...... ..
...
.
.
.
...
........
..
..
.........
..
..
...
....
.
.....
......
........
.........
B,1,0
......
.
......
.
......
A,2,0
J,0,-
C,3,0
I,1,0
G,4,0
H,2,0
F,5,0
H,6,4
.........
........
.......
.....
.....
...
..
.
...
..
...
.....
.....
......
........
.........
.........
........
.
......
.
.
.
..
....
.
...
...
...
..
..
.....
.....
......
........
.........
I,7,6
.........
........
.......
......
.....
...
...
...
..
...
....
.
.....
......
......
..
.........
L,8,6
J,9,7
K,10,8
(a)
Comienzo
en D
..
.....
..
.......
..
......
.. .
....
... .. ...
. .. ..
... .. .
.
....... ...
.
...... ...
... ....
... .
.... ...
.
...... ..
.
... .
... .........
.. ...
...
.
...
.
..
..
.
.....
.
.....
......
F,3,2
.
......
.
....
.
...
.........
........
...
.......
... .....
..........
.......
.
..
....
.
........
.
... ......
......
...
........
....
.........
...
........
.
... .
.......... .....
.
.
.
.
.
.....
..
...
.
.
.....
.
....
......
...
.
..
..
...
.....
.....
......
........
.........
..
..
.
..
.
...
..
..
....
..
..
.
.....
.
. ..
....
...
......
.. ...
.
.
......
.. ...
.
...
..... ...
.
.
...
.........
.
.
...
.. ....
... ...
. .
...
... .
..
... ..
....
...
..
....
...
......
.
..
.....
. ..
..
.
..
. .
.
... ..
...
. ..
.
... ...
...
. ...
.
.
. .
. ..
... ...
.. ...
... ..
. ....
........ ..
...
.......
.......
.....
.....
.
B,4,2
A,5,2
C,6,2
G,7,2
D,8,3
(b) Comienzo en J
H,0,-
L,9,0
.
.....
.
.....
.
......
F,1,0
.
......
.
....
.
...
.........
.
........
...
.......
... .....
.... .....
..
......
..
....
.
........
.
... ......
......
...
........
.
...
.........
...
........
.
... .
.......... .....
.
.
.
.
.
.....
..
.....
.....
.
...
......
...
.
..
..
...
.....
.....
......
........
.........
K,10,0
.
.
...
.
...
.
...
.
...
....
..
..
.
.....
.
. .
.... .
....
......
.....
.
.
......
... ...
.
...
...... ...
.
...
.........
.
.
.
...
.. ....
... ...
. .
...
... .
.
.
.
.
... ..
.
....
...
..
....
...
....
.
.
.
..
....
.
..
..
.. ..
. .
.
... ...
..
. .
.
... ...
...
. .
... ..
. .
.... ...
.
..
.... ..
. ....
..... ..
....
.......
.....
.
.
.
.
.
....
.....
.
B,2,0
A,3,0
C,4,0
...
.
...
...
...
...
...
...
..
I,7,0
...
.
..
.........
........
.
.
.....
.........
........
.......
......
.....
...
...
..
...
...
...
.
.....
......
.......
.........
L,8,0
.....
.....
...
..
.
...
.
...
.....
.....
......
........
.........
J,9,7
K,10,8
G,5,0
D,6,1
( ) Comienzo en H
Figura 7.30: Arboles
abar
adores del grafo de la gura 7.26 junto
on sus numeros df, low
y ar
os no-abar
adores
735
1. La raz de T es un nodo de
orte si y solo si esta tiene mas de un hijo. Este
aso esta
ilustrado en la gura 7.30-
on el nodo de
orte H.
2. Un nodo v, no raz de T , es de
orte si y solo si v tiene un hijo w tal que ningun
des
endiente de w esta
one
tado por un ar
o no-abar
ador a un nodo
uyo df sea
menor que df (v).
Pi
tori
amente, el asunto se ilustra en las guras 7.30-a y 7.30-b. En 7.30-a, el
nodo H, que es de
orte, tiene una sola rama
on raz I y se
onstata que ninguno
de los des
endientes u {L, J, K} de I se
one
ta por un ar
o no-abar
ador a otro
nodo u
uyo df(u) > df (H) = 6. Del mismo modo, para la gura 7.30-b, podemos
ver que, para la rama
uya raz es F, absolutamente ninguno de sus des
endientes
u {B, A, C, G, D} tienen df(v) > df (H) = 2.
Demostraci
on
Una forma alterna y,
omputa
ionalmente, mas
onveniente de denir low(v) es
omo
sigue:
low(v) = mnimo valor entre
df(v)
736
Captulo 7. Grafos
Informalmente, la prueba es muy sen
illa: si v es una hoja en el arbol abar
ador, enton
es la
ter
era posibilidad queda ex
luida y las dos primeras se
orresponden
on la deni
ion 7.2.
De lo
ontrario, en \terminos re
ursivos", low(v) memoriza el mnimo df de todos sus
des
endientes.
La forma alterna nos da una formula
ompleta para determinar los puntos de
orte de
un grafo en una sola explora
ion, sin utilizar un arbol abar
ador de profundidad adi
ional,
que se resume en el siguiente
orolario:
Corolario 7.2 Sea T un arbol abar
ador de profundidad de un grafo G =< V, E >. Sea v
un nodo que no es la raz de T . Enton
es, v es un nodo de
orte si y solo v tiene un hijo w
tal que low(w) df (v).
Demostraci
on Se desprende dire
tamente del segundo punto del
orolario 7.1. Si
low(w) df (v) enton
es ningun des
endiente de w se
one
ta a un an
estro de v
D,0,-
....
.........
.....
...........
..... .....
.
. .
..... .....
..... .....
.... ...
.........
... ...
........
......
... ..
.. .
...... .. ....
... ...
..
........
.. ...
.... ....
.
........ .....
.
... ...... ......
...... ......
...
..............
.
...
.............
.
.....
...
.
.....
.....
.
.....
.
.......
.
......
.
.....
.
.....
.
.....
.....
...
.
.
.
...
.
..
.
..
..
........
...
.......
...
.......
... .. ......
...
.....
.. ..
... ....
..
.
...
....
.
.....
.....
.....
...... . .....
........ . .
...............
.........
........
....
.
.
.
....
.
.
.....
...
..
....
.
...
.....
.....
......
........
.........
.....
.
.....
.
.....
.
....
...
.
...
.
...
.
......
...
.
.
.....
...
.
.....
.
... ...
. ..
... .
. .
... ..
......
...
.
.
.....
..
.....
.
.
.....
.
... ..
. ...
......
.........
... .
........ ... ....
..........
.. ....
........ ...... ..
...
. ..
...
...........
.
...
.
.
.
.
.
.
....
..
.........
........
..
.......
...
.
.
...........
......
.
...... ....
.......
...
..
...
.........
..
...
.
.
...
.
.
.
.
.
......
.......
..
......
............
........
.......
......
.....
...
..
..
..
...
.
...
.
.
.
.
.
......
........
.........
B,1,0
......
.
.......
A,2,0
J,0,-
C,3,0
I,1,0
G,4,0
F,5,0
H,6,4
I,7,4
L,8,4
J,9,7
K,10,8
(a)
Comienzo
en D
..
......
... .
..
.. ..
. .
.. ...
. ..
.
........ ..
. ... ..
.
.. . . ..
. . .. ..
.. . ..
... .. ...
. . ...
.... ... .. .. .
.
... ....
..... ..
... ..
... ..
.
.
........
... ..
....
... ...
... .....
..
... ....
... ... ...
.. ...
. .
... . ... .. ..
..
.. .. ...
... .
... ... ..
.
.....
... .. ...
.
.
...
....
... ..
...
.
... .
.
......
.
...
....
.....
.
...
.....
....
.
.
...
.
...
...
...
...
......
...
.....
....
.
...
.....
.....
...
.... ...
..... . .
...
.. ....
..
. .
...
.... ....
.......
...
.
.........
....
.
...
.
.
. . ..
..
.
.
.
. .. ...
...
.
.
...... ..
..
...
......
.
.
.
.....
..
.
.
.....
.
...
.
.
.
..
.
..
.
.
....
..
.
.
.
..
....
.
.
.....
.
...
.
.
...
........
.. .
...
..... ...
.
.
.
..
.....
.
.
.
.
.....
...
.
.
...
....
...
. ..
...
.. . .
. .......
.
.
........... ..
.. ..
.... .. ..
.
.
.
. ..
. .. ......
.
..
.
.
.
... .....
.
.....
.. ....
...
..
.
.....
... ..
..
.
......
.
.. ...
... .....
.
.
... .
... ..........
. ..
... .... ...... .......
..
.
... ... ... ....... .........
. . .. ..
..
.... ....
. .....
.
...
.............
..
... .. ........... ...
.........
. ..
. .....
.....
......
......
..
.
.
..... ...
. ..
.
.
..... ..
.. ..
.
.
.
.
.
.. .
.
........
..
..
..
..
..
...
..
..
.
...
.. ....
.. .
. .....
.. .
.
.. .....
. ...
.
... .....
. ....
... ......
.. ...
.
.. ...
.. .....
.. ......
... ....
.
.. ....
..... ..
.
...... ..
.
..... ..
..
..... .
..
.... ..
. .. .
... .
. . ..
..... .
.
...... ...
.
.....
.. ..
.... .
... ..
... .
.. ..
..... ..
. .
............
.
.
.... ..
. . .......
.....
.
.
.
....
....
.
....
...
...
.
.
..
.
..
.
..
.
..
...
...
...
.....
......
......
.......
........
F,1,0
G,5,0
D,6,1
G,6,0
L,8,0
(b) Comienzo en J
H,0,-
C,4,0
A,4,0
H,9,1
.
.....
A,3,0
B,3,0
C,5,0
.
....
B,2,0
F,2,0
.
.......
. .
...... ..
.
.
.
.
.....
.
...
.....
...
....
. ........
.
.
.
.
.
.
.
.
.
.
.
. .
.
....... .. ..
... ......
.
.
.
.
.
........
.
... ....
......
...
...
.. ..
....
.
.
.. ...
.........
.
.
... .
... ......
...... ... ..
...
.....
.
... ......
...
. . ... ......... .
.... .
.
..
.
.......
...
.
.. .
... . ..... ...
.
......
.
.
..
....
......
.
...
...
..... ...
.........
... . ........
..... ..........
.. ........
.
.
.
.
........
.........
......
.. ..
...
...
.....
.....
......
........
.........
D,7,2
.
.....
K,10,0
L,7,0
.........
........
......
.....
.....
...
...
..
..
..
.
...
.
.
......
.......
.......
.........
I,8,0
J,9,7
K,10,7
( ) Comienzo en H
Figura 7.31: Arboles
abar
adores del grafo de la gura 7.28 junto
on sus numeros df, low
y ar
os no-abar
adores.
736
Para ha
er mas
ompresible la instrumenta
ion del algoritmo que determina los puntos
de
orte, planteamos las siguientes primitivas:
hPuntos de
orte 736i
(696) 737b
template <class GT> inline static long & df(typename GT::Node * p)
{
return NODE_COUNTER(p);
737
}
template <class GT> inline static long & low(typename GT::Node * p)
{
return reinterpret_cast<long&>(NODE_COOKIE(p));
}
Denes:
df, used in
hunks 737b, 738b, and 740.
low, used in
hunks 737b and 740.
Uses NODE COOKIE 670d and NODE COUNTER 670d.
737a
Las
uales retornan los valores de df (p) y low(p), respe
tivamente. Como podemos apre
iar, el valor del
ontador de
ada nodo se usa para alma
enar df, mientras que el del
puntero
ookie para alma
enar low(p). Esto, por supuesto, impide el
al
ulo de los puntos
de
orte si los
ookies de los nodos estan siendo utilizados.
Antes de
omenzar el
al
ulo, deben ini
ializarse los nodos y ar
os del grafo. Ello se
realiza de la siguiente forma:
hIni
ializar nodos y ar
os 737ai
(738b)
g.template operate_on_nodes <Init_Low <GT> > ();
g.reset_arcs();
Uses operate on nodes 661
and reset arcs 670
.
737b
Donde la ini
ializa
ion de un nodo esta instrumentada por la
lase Init Low,
uya espe
i
a
ion es
omo sigue:
hPuntos de
orte 736i+
(696) 736 737
737
El siguiente paso es denir la interfaz, llamada compute cut nodes(g, start, list),
uyos parametros son el grafo, el nodo ini
io de busqueda y una lista sobre la
ual se
alma
enan sus puntos de arti
ula
ion:
hPuntos de
orte 736i+
(696) 737b 738a
on del re
orrido re
ursivo para los puntos de
orte 739bi
hDeni
i
template <typename GT>
void compute_cut_nodes(GT &
g,
typename GT::Node *
start,
DynDlist<typename GT::Node *> & list)
{
on de nodos de
orte 738bi
hIni
ializar dete
i
hExplorar
738
Captulo 7. Grafos
hVeri ar
on
hImplanta
i
Denes:
Para el
al
ulo de los puntos de arti
ula
ion no es estri
tamente ne
esario espe
i
ar
un nodo de ini
io de
al
ulo . Por eso, exportamos la siguiente espe
ializa
ion:
hPuntos de
orte 736i+
(696) 737
744a
14
738a
738b
La rutina tiene tres fases
uyas fun
iones estan
laramente enun
iadas. Aparte de
reini
iar los ar
os y nodos, requerimos el
ontador global de visitas que determinara el df
de
ada nodo, el nodo ini
ial por donde
omenzar a bus
ar y un
ontador de llamadas a
la explora
ion re
ursiva, la
ual sera implantada por compute cut nodes():
hIni
ializar dete
i
on de nodos de
orte 738bi
(737
)
hIni
ializar nodos y ar
os 737ai
long current_df = 0; // contador global de visitas
NODE_BITS(start).set_bit(Depth_First, true); // pintar visitado start
df <GT> (start) = current_df++;
int call_counter = 0; // contador de llamadas recursivas
Uses df 736, NODE BITS 670d, and set bit 664a.
738
call counter
uenta la
antidad de llamadas realizadas a compute cut node(), lo
ual
luego permitira determinar si start es o no un nodo de
orte.
La fase siguiente es la explora
ion re
ursiva a todos los nodos
one
tados por start:
hExplorar re
ursivamente las ramas de start 738
i
(737
)
// Recorra los arcos de start mientras g no haya sido abarcado
for (typename GT::Node_Arc_Iterator i(start);
i.has_current() and current_df < g.get_num_nodes();
i.next())
{
typename GT::Node * tgt = i.get_tgt_node();
if (IS_NODE_VISITED(tgt, Depth_First))
continue;
14 En
este texto, ha sido ne
esario para poder generar las distintas guras
on los ar
os no-abar
adores
y numeros df y low.
739
compute cut nodes 740, has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d,
Node Arc Iterator 656a, and set bit 664a.
739a
Segun el primer punto del
orolario 7.1, start es un nodo de
orte si el arbol abar
ador de
profundidad
on raz start tiene mas de un hijo. En lo terminos de este algoritmo, esto esta
determinado por la
antidad de ve
es que se explore re
ursivamente un nodo
one
tado
a start; di
ho de otro modo, la
antidad de ve
es que se llame a compute cut nodes().
Si se llama una sola vez, enton
es el grafo es
ompletamente abar
ado desde el primer ar
o
visto desde start y por lo tanto start no puede ser un nodo de
orte. De lo
ontrario,
sin importar el valor exa
to de call counter, start es de
orte.
Lo anterior se expresa, enton
es, del siguiente modo:
hVeri
ar si start es un nodo de
orte 739ai
(737
)
if (call_counter > 1) // es la ra
z un punto de articulaci
on?
{
NODE_BITS(start).set_bit(Cut, true);
list.append(start);
}
Uses NODE BITS 670d and set bit 664a.
739b
Cada vez que se dete
te un nodo de
orte, este se mar
ara
on el bit Cut, el
ual, luego,
nos servira para mar
ar los
omponentes
onexos aso
iados los puntos de
orte.
La interfaz de la explora
ion re
ursiva, compute cut nodes(), se expresa en el siguiente bloque:
hDeni
i
on del re
orrido re
ursivo para los puntos de
orte 739bi
(737
)
740
Captulo 7. Grafos
5. curr df: el valor a
tual del
ontador de visitas que sera asignado al nodo siendo
visitado
omo su valor df.
740
La implanta
ion de compute cut nodes() a~nade la re
ursion, el
al
ulo de los valores low de
ada nodo y la distin
ion de que el nodo visitado sea o no una hoja dentro del
eventual arbol abar
ador de profundidad:
hImplanta
i
on del re
orrido re
ursivo para los puntos de
orte 740i
(737
)
template <typename GT> inline static
void __compute_cut_nodes(GT &
g,
DynDlist<typename GT::Node *> & list,
typename GT::Node *
p,
typename GT::Arc *
a,
long &
curr_df)
{
NODE_BITS(p).set_bit(Depth_First, true); // pinte p como visitado
low <GT> (p) = df <GT> (p) = curr_df++; // as
gnele df
// Recorrer recursivamente arcos de p mientras g no haya sido abarcado
bool p_is_cut_node = false;
for (typename GT::Node_Arc_Iterator i(p); i.has_current(); i.next())
{
typename GT::Arc * arc = i.get_current_arc();
if (arc == a) // es el arco del padre?
continue; // s
==> avance al siguiente arco
typename GT::Node * tgt = i.get_tgt_node(); // nodo destino de arc
if (IS_NODE_VISITED(tgt, Depth_First))
{
if (not IS_ARC_VISITED(arc, Depth_First)) // es arco no-abarcador?
if (df <GT> (tgt) < low <GT> (p)) // s
, verificar valor de low
low <GT> (p) = df <GT> (tgt); // actualizar low(p)
continue;
}
if (IS_ARC_VISITED(arc, Depth_First)) // arco ya visitado?
continue; // s
==> avance al siguiente
ARC_BITS(arc).set_bit(Depth_First, true); // marque arco
__compute_cut_nodes(g, list, tgt, arc, curr_df);
if (low <GT> (tgt) < low <GT> (p)) // verificar hijo de p - low(tgt)
low <GT> (p) = low <GT> (tgt); // actualizar low(p)
// detecci
on de nodo de corte
if (low <GT> (tgt) >= df <GT> (p) and df <GT> (tgt) != 0)
p_is_cut_node = true;
741
}
// p fue explorado recursivamente
if (p_is_cut_node)
{
NODE_BITS(p).set_bit(Cut, true);
list.append(p);
}
}
Denes:
La rutina aprove
ha el propio re
orrido sobre los ar
os para
al
ular low(p) exa
tamente
segun la formula (7.1). Ini
ialmente, low(p) = df (p); luego, durante la inspe
ion de los
ar
os de p, se determina si se trata del ar
o padre, de un ar
o no-abar
ador o de un hijo.
Cada mirada sobre un ar
o hijo o uno no-abar
ador veri
a si hay un valor menor para
olo
arle low(p).
Es muy importante re
al
ar la forma en que el algoritmo distingue las tres
lases
de ar
os. Los ar
os no-abar
adores se identi
an porque no son mar
ados
on el
bit Depth First. El ar
o padre se distingue mediante el parametro a. Finalmente, los
ar
os del arbol abar
ador s son mar
ados justo antes de la llamada re
ursiva.
19
18
20
16
15
17
14
28
21
13
10
26
23
1
22
11
27
24
25
12
3
742
Captulo 7. Grafos
....
....
....
....
....
1,0,-
....
....
...
....
.....
......
......
.
....
....
....
....
......
.
.
.....
.
.. .......
...
.....
..
......
.
......
.
.
......
.
.
.
......
.
.....
.
.
.
......
.
.
.
......
.
.
.
.....
..
.
.
.....
..
..
.
.....
...
.
.
.
.
...
.
.
.
.
.
.
...
.
.
.
..
.
...
.
..
.
.
.
.
.
..
.
.
..
.
.
.
..
.
.
.
..
.
..
.
.
.
..
2,1,0
7,6,6
5,2,0
8,7,6
15,14,13
23,21,0
4,3,0
10,8,6
18,15,13
22,22,0 28,23,21
6,4,0
9,9,6
19,16,13
24,24,21
3,5,0
13,10,6
20,17,13 16,19,13
11,11,6
17,18,13
...
...
..
.
..
..
..
..
.
...
..
.
...
...
...
...
...
...
.
..
..
.
..
.
.
.
..
...
...
...
...
...
.
...
..
....
...
..
.
.
... . ..
.
.
....
...
.... ..
.... ...
...
...
...
...
...
...
.
...
.. ...
...
...
...
... ...
...
..
.
....
..
.
.....
.
....
...
.
...
... ...
.
...
..... ..
...
.
........
.
..
.
.
.
.
.
....
.
........
..
...
... ....
.
.
......
.
..
..
...
..
......
...
..... ...
...
......
. ... ..
..
.
...
.
..
......
..
.. ....
..
..
.
.
.
.....
..
.. .. ...
...
.
.
......
... ..
....
.....
.
...
....
..
.
.....
....
....
..
.. ...
.
.
.
.
.
.
.
....
...
... ..
..
..
.
....
... ..
..
..
..
......
......
..
....
..
......
...
......
...
....
.
..
..
......
......
......
......
......
.....
14,13,13
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
..
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
..
.
.
.
.
..
.
.
12,12,6
21,20,0
.
..
.
.
.
.
..
..
.
.
..
.
..
.
..
.
.
..
.
.
.
.
.
..
.
.
..
.
..
.
.
..
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
..
..
.
.
.
.
...
.
.
.
...
...
...
.
..
..
. ...
... ...
.
....
....
... ...
... ...
........
.
...
...
...
...
...
...
..
.
.
..
.
..
.
..
...
...
...
27,25,23
...
...
...
..
.
..
..
.
..
...
...
.
..
...
...
25,26,24
26,27,25
Figura 7.33: Arbol
abar
ador de profundidad del grafo de la gura 7.32 junto
on sus df
y low.
7.5.13
Una vez que hemos
al
ulado los puntos de
orte, puede ser deseable
ono
er
uales son
sus componentes conexos o, tambien, bloques; es de
ir, los subgrafos resultantes
de suprimir los puntos de
orte.
Un primer y muy simple algoritmo para determinar los
omponentes
onexos
onsiste
en tomar una
opia mapeada del grafo y sobre este eliminar sus nodos de
orte. Los
subgrafos resultantes son dire
tamente los
omponentes
onexos del grafo. El problema de
este algoritmo es que puede ser
ostoso en espa
io si el grafo es muy grande.
En a~nadidura, puede ser interesante, no solo tener los
omponentes
onexos sino el resto
del grafo original; de modo tal que, pudieramos re
onstruirlo a partir de sus
omponentes
onexos, puntos de
orte y ar
os que
ontengan al menos un punto de
orte. Es momento
de introdu
ir dos nuevas deni
iones.
Definici
on 7.3 (Arco de corte) Sea un grafo G =< V, E > y dos nodos de
orte u, v
tales que estos
onforman un ar
o a = (u, v) E. Enton
es, el ar
o (u, v) es denominado
\de
orte".
Di
ho de otro modo, un ar
o de
orte es aquel
onformado por dos nodos de
orte.
Pi
tori
amente, un ar
o de
orte se generaliza del siguiente modo:
743
G1
G2
Por ejemplo, los ar
os (1, 7) y (1, 14) del grafo de la gura 7.32 son de
orte.
Definici
on 7.4 (Arco de cruce) Sea un grafo G =< V, E > y un ar
o a E tal que
a = (u, v) esta
ompuesto por un nodo de
orte u y por un nodo v pertene
iente a alg
un
omponente
onexo. Enton
es, el ar
o a = (u, v) es llamado \de
ru
e".
G2
Por ejemplo, los ar
os (1, 2), (1, 21) y (1, 22) del grafo de la gura 7.32 son, entre otros
mas, de
ru
e.
La distin
ion de estas
lases de ar
os nos servira para invertir algun parti
ionamiento
del grafo segun sus nodos de
orte. Aparte de los
omponentes
onexos y los nodos de
orte, requerimos los ar
os de
orte y de
ru
e para poder re
onstruir el grafo original.
Obtendremos los
omponentes
onexos en dos fases. La primera fase se en
arga de
\pintar" los
omponentes
onexos, los nodos y ar
os de
orte y los ar
os de
ru
e
on
diferentes
olores de modo tal que en la fase siguiente se les distinga. Para esta fase
emplearemos la rutina paint subgraphs().
Dada una lista de nodos de
orte
al
ulada mediante compute cut nodes(),
paint subgraphs() pinta los
omponentes
onexos del grafo seg
un los
ortes. Esta te
ni
a,
mas
ompleja, pero
on menor
onsumo de espa
io,
onsiste en \pintar" el grafo a
partir de un punto de
orte e ir
ambiando los
olores segun se regrese al punto de
partida o se al
an
e otro punto de
orte. Esto presume que las mar
as a~nadidas (el
bit Cut) por compute cut nodes() aun siguen presentes. Llamaremos a nuestra primitiva basi
a paint subgraphs(), la
ual tiene la siguiente interfaz:
template <typename GT>
long paint_subgraphs(GT &
g,
const DynDlist<typename GT::Node*> & cut_node_list);
g es un grafo
on sus nodos de
orte
al
ulados y guardados en la lista cut node list;
mientras que cut arc list es un parametro de salida que retorna los ar
os de
orte del
744
Captulo 7. Grafos
La segunda fase
onsiste en mapear el grafo previamente pintado segun alguno de los
olores empleados en la primera fase. Para esta fase se emplean dos primitivas:
1.
Se utiliza para obtener los
omponentes
onexos del grafo segun sus puntos de arti
ula
ion.
g es un grafo
on sus nodos de
orte previamente
al
ulados y mar
ado mediante compute cut nodes(). sg es el grafo en donde se desea obtener una
opia mapeada del
omponente
onexo de
olor color previamente pintado mediante paint subgraphs().
Notemos que debemos realizar tantas llamadas sobre subgrafos sg distintos
omo
omponentes
onexos tenga g. O sea, la
antidad de
olores,
ual es el valor de
retorno de paint subgraphs().
2.
g,
cut_node_list,
cut_graph,
cross_arc_list);
Obtiene el \grafo de
orte"; es de
ir, el grafo
ompuesto por todos los nodos y ar
os
de
orte.
745
g es un grafo
on sus nodos de
orte previamente
al
ulados y mar
ado mediante compute cut nodes(). cut node list es la lista de nodos de
orte tambien
obtenida luego de llamar a compute cut nodes(). cut graph es el grafo donde se
desea una
opia mapeada del grafo de
orte. Finalmente, cross arc list es la lista
de ar
os de
ru
e en g, los
uales no pertene
en ni a los
omponentes
onexos, ni al
grafo de
orte.
7.5.13.1
745a
Grosso modo, la te
ni
a de pintado
onsiste en ini
iar un re
orrido en profundidad desde
un punto de
orte
on un
olor ini
ial 0. Cuando el re
orrido re
ursivo al
an
e un nodo
de
orte, que puede ser el mismo de partida, enton
es se realiza un
ambio (in
remento)
de
olor. Durante el re
orrido de un
omponente, tanto los nodos
omo los ar
os son
oloreados
on el
olor a
tual. Una vez que todos los ar
os hayan sido re
orridos sin
ruzar
un punto de
orte, enton
es el grafo estara
oloreado segun los
omponentes aso
iados a
sus puntos de arti
ula
ion.
hPuntos de
orte 736i+
(696) 744b 745b
template <typename GT> inline static
void __paint_subgraph(typename GT::Node *
typename GT::Arc *
const long &
{
if (is_a_cut_node <GT> (p)) // es un
return; // S
, ign
orelo
p, // Nodo actual
a, // Arco que conduce a p
current_color)
nodo de corte?
paint subgraph 745b, has current 103, is a cut node, is arc painted, is node painted,
Node Arc Iterator 656a, paint arc, paint node, and S 794
.
745b
Esta version asume que p se al
anza desde un nodo ya pintado
on current color. El
ini
io, o sea, el primer nodo que se entra a pintar y que esta
one
tado a un ar
o de
ru
e,
se debe pintar mediante la siguiente version:
hPuntos de
orte 736i+
(696) 745a 746
746
Captulo 7. Grafos
746
Esta version ini
ia la
olora
ion de un
omponente
onexo al
ual se le llega desde un
nodo de
orte visto por la siguiente rutina:
hPuntos de
orte 736i+
(696) 745b 747
template <typename GT> inline static
void __paint_from_cut_node(typename GT::Node *
p, // un nodo de corte
long &
current_color)
{
// pintar recursivamente con distintos colores los bloques conectados a p
for (typename GT::Node_Arc_Iterator it(p); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
typename GT::Node * tgt_node = it.get_tgt_node();
if (is_a_cut_node <GT> (tgt_node)) // es un arco de corte?
{
ARC_BITS(arc).set_bit(Cut, true); // marque arco como de corte
continue; // avance a pr
oximo arco
}
else
{
paint_arc <GT> (arc, Cross_Arc); // marque arco como de cruce
if (is_node_painted <GT> (tgt_node)) // pintado el nodo destino?
continue; // s
, avanzar entonces al pr
oximo arco
}
// pintar recursivamente con current_color el nodo conectado a arc
// (que no es ni de corte ni de cruce)
__paint_subgraph <GT> (tgt_node, current_color);
747
Uses
747
Como se ve, paint from cut node() toma un nodo de
orte y se en
arga de invo
ar
a paint subgraph() sobre el ar
o a
tual o a pintar el ar
o de
orte.
Finalmente, no resta implementar la rutina prin
ipal:
hPuntos de
orte 736i+
(696) 746 748
template <typename GT> inline
long paint_subgraphs(GT &
g,
const DynDlist<typename GT::Node*> & cut_node_list)
{
g.reset_counter_nodes();
g.reset_counter_arcs();
long current_color = 1;
// Recorrer cada nodo de corte y pintar sus bloques
for (typename DynDlist<typename GT::Node*>::Iterator i(cut_node_list);
i.has_current(); i.next())
__paint_from_cut_node <GT> (i.get_current(), current_color);
return current_color;
}
Denes:
paint subgraphs, never used.
Uses paint from cut node 746, DynDlist 113a, get current 103, has current 103,
reset counter arcs 667b, and reset counter nodes 667b.
Despues de
al
ular los puntos de
orte y tenerlos en cut node list, el usuario invo
a
a paint subgraphs() para pintar
on distintos
olores los bloques del grafo. Cada bloque
puede distinguirse mediante su
olor. Los ar
os de
ru
e se distinguen a traves del
olor
espe
ial Cross Arc. Los nodos y ar
os de
orte se distinguen mediante el bit Cut. Hasta
el presente, no se ha realizado una
opia adi
ional del grafo.
7.5.13.2
En algunas
ir
unstan
ias, es
onveniente
opiar los distintos bloques de un grafo. Por
ejemplo, los algoritmos de dibujado o de dete
ion de planaridad simpli
an sus
al
ulos
trabajando sobre los bloques. Puesto que a menudo es ne
esario modi
ar los bloques, es
preferible ha
erlo sobre
opias que sobre el grafo original.
El pro
eso de identi
a
ion,
opia y mapeo a un subgrafo, dado un
olor es simple una
vez que se tienen pintados los
omponentes
onexos: bus
ar y en
ontrar el primer nodo
on el
olor de interes y, a partir de el, realizar una explora
ion en profundidad que visite,
opie y mapee los nodos y ar
os
on el mismo
olor.
748
748
Captulo 7. Grafos
Sobre un nodo del grafo gsrc,
on el
olor de interes \color", ya
opiado y mapeado
a un subgrafo sg, el resto del mapeo se
ompleta, re
ursivamente, de la siguiente manera:
hPuntos de
orte 736i+
(696) 747 749a
template <typename GT> inline static
void __map_subgraph(GT &
g,
// grafo con ptos de corte
GT &
sg,
// subgrafo donde se copia bloque
typename GT::Node * gsrc, // nodo de g ya copiado en sg
const long &
color)
{
typename GT::Node * tsrc = // obtener imagen de gsrc en sg
static_cast<typename GT::Node*>(NODE_COOKIE(gsrc));
// recorrer arcos de gsrc y a~
nadir a sg aquellos con el color de inter
es
for (typename GT::Node_Arc_Iterator i(gsrc); i.has_current(); i.next())
{
typename GT::Arc * garc = i.get_current_arc();
if (get_color <GT> (garc) != color or IS_ARC_VISITED(garc, Build_Subtree))
continue; // arco es de otro color o ya est
a visitado
ARC_BITS(garc).set_bit(Build_Subtree, true); // marque arco
typename GT::Node * gtgt = i.get_tgt_node(); // nodo destino de garc
// obtener imagen de gtgt en sg
typename GT::Node * ttgt = NULL;
if (IS_NODE_VISITED(gtgt, Build_Subtree)) // gtgt ya est
a en sg?
ttgt = static_cast<typename GT::Node*>(NODE_COOKIE(gtgt)); // s
else
{
// gtgt no est
a en sg ==> copiarlo y mapearlo
auto_ptr<typename GT::Node>
ttgt_auto ( new typename GT::Node (gtgt) );
sg.insert_node(ttgt_auto.get());
GT::map_nodes(gtgt, ttgt_auto.get());
NODE_BITS(gtgt).set_bit(Build_Subtree, true); // marque gtgt
ttgt = ttgt_auto.release();
}
// copiar y mapear arco en sg
typename GT::Arc * tarc = sg.insert_arc(tsrc, ttgt, garc->get_info());
GT::map_arcs(garc, tarc);
__map_subgraph(g, sg, gtgt, color); // proseguir recursivamente con gtgt
}
}
Denes:
749
IS NODE VISITED 670d, map arcs 686, map nodes 686, Node Arc Iterator 656a, NODE BITS 670d,
NODE COOKIE 670d, and set bit 664a.
map subgraph() mar a los nodos y ar os visitados on el bit Build Subtree; de este
749a
}
catch (...)
{
sg.clear_graph();
}
}
Denes:
749b
Nos resta por
onstruir el \grafo de
orte" y guardar los ar
os de
ru
e. Para ello,
on una pasada sobre la lista de nodos de
orte
reamos y mapeamos sus imagenes en el
grafo de
orte cut graph. Despues, efe
tuamos un barrido sobre todos los ar
os. Los que
estan pintados los ignoramos, pues pertene
en a los
omponentes
onexos. Los ar
os de
orte los
opiamos e insertamos en cut graph mientras que los de
ru
e los insertamos en
la lista cross arc list. Un detalle esen
ial es per
atarse de que, a la ex
ep
ion de los
omponentes
onexos y del grafo de
orte, los ar
os de
ru
e no son
opiados.
hPuntos de
orte 736i+
(696) 749a
template <typename GT>
void map_cut_graph(GT &
g,
750
Captulo 7. Grafos
751
El grafo cut graph es in
onexo y sus
omponentes pueden hallarse mediante build subgraph()
implantada en hComponentes in
onexos 726ai (x 7.5.11 (pagina 726))
7.6
Matrices de adyacencia
751
752
Captulo 7. Grafos
7.6.1
El n de este TAD es disponer de una representa
ion matri
ial de un grafo espe
i
ado
on el TAD List Graph<Node, Arc>. Se espe
i
a
omo sigue:
752a
(751)
private:
hMiembros
public:
hMiembros
};
on
hImplanta
i
Map Matrix Graph<GT> mapea un grafo de tipo List Graph<Node, Arc> a una ma-
752b
(752a) 752
El primer
onstru
tor toma un List Graph<Node, Arc> de tipo GT y
onstruye una
matriz de adya
en
ia mapeo. El segundo es el
onstru
tor
opia.
Un
ambio en la
antidad de ar
os o nodos del List Graph<Node, Arc>no se refleja
en un mapeo Map Matrix Graph<GT>. No obstante, se debe prestar espe
ial
uidado
on
el ambito de existen
ia del List Graph<Node, Arc>, el
ual debe destruirse despues de
todo objeto Map Matrix Graph<GT> que le este rela
ionado.
El a
eso a un elemento de la matriz se realiza mediante el operador () (no
on el
operador tradi
ional []). Hay
uatro presenta
iones:
1.
752
hMiembros p
ubli
os de Map Matrix Graph<GT> 752bi+
inline Node * operator () (const long & i) const;
Donde g es un objeto de tipo Grafo derivado de List Graph<Node, Arc>. Enton
es,
la siguiente opera
ion:
Grafo::Node * p = m(20);
753
753a
Retorna el ndi
e o posi
ion dentro de la matriz del nodo
on dire
ion node.
3.
hMiembros p
ubli
os de Map Matrix Graph<GT> 752bi+
(752a) 753a 753
inline Arc * operator () (Node * src_node, Node * tgt_node) const;
753b
Retorna el apuntador a ar
o entre los nodos src node y tgt node. Si no existe tal
ar
o, enton
es se retorna NULL.
4.
753
hMiembros p
ubli
os de Map Matrix Graph<GT> 752bi+
(752a) 753b 754e
inline Arc * operator () (const long & i, const long & j) const;
753d
La
ara
tersti
a mas apre
iada de una matriz de adya
en
ia es el a
eso dire
to a
los nodos y ar
os; lo que permite una prueba de ar
o O(1). En nuestra implanta
ion
debemos asegurar ese desempe~no. Es por eso que basaremos nuestra implanta
ion en el
tipo DynArray<T> expli
ado x 2.1.5 (pagina 44). En primer lugar, tendremos un arreglo
de nodos espe
i
ado de la siguiente manera:
hMiembros privados de Map Matrix Graph<GT> 753di
(752a) 754d
DynArray<Node*> nodes;
Uses DynArray 45.
753e
De esta forma, todo a
eso a nodes[i] retorna en O(1) el apuntador a nodo. Como tambien
usaremos este estilo en las
lases Matrix Graph<GT> y Ady Mat<GT, Entry>, no es muy
onveniente en
apsular el a
eso en una rutina privada:
hUtilitarios para matri
es de adya
en
ia 753ei
(751) 754b
template <typename GT> inline static
typename GT::Node * get_node(DynArray<typename GT::Node *> nodes,
const long &
i)
{
if (i >= nodes.size())
throw std::out_of_range("Index of node out of range");
return nodes.access(i);
}
Denes:
get node, used in
hunks 754a, 765b, and 768b.
Uses access 60b and DynArray 45.
754
754a
Captulo 7. Grafos
754b
A efe
tos de
al
ular lo mas rapidamente posible el ndi
e de un nodo dado un puntero,
el arreglo nodes estara ordenado por las dire
iones de sus punteros. De este modo, el
al
ulo de un ndi
e puede resolverse generi
amente en O(lg(n)) mediante la busqueda
binaria:
hUtilitarios para matri
es de adya
en
ia 753ei+
(751) 753e 755d
template <typename GT>
inline static long index_of_node(DynArray<typename GT::Node *> nodes,
const long &
n,
typename GT::Node *
p)
{
return Aleph::binary_search<typename GT::Node*>(nodes, p, 0, n - 1);
}
Denes:
index of node, used in
hunks 754
, 756b, 757d, 760, 765b, and 768b.
Uses binary search 32 and DynArray 45.
754
754d
Antes de implantar el resto de los metodos de la
lase Map Matrix Graph<GT>, debemos espe
i
ar el resto de sus miembros dato. En primer lugar, puede requerirse el propio
List Graph<Node, Arc>:
hMiembros privados de Map Matrix Graph<GT> 753di+
(752a) 753d 754f
GT * lgraph;
754e
754f
755a
755
la
ual sera una referen
ia dire
ta al List Graph<Node, Arc>
ontenido en lgraph. Este
atributo es observable a traves de:
hMiembros p
ubli
os de Map Matrix Graph<GT> 752bi+
(752a) 754e 757a
const size_t & get_num_nodes() const { return num_nodes; }
755b
pues habra que es
ribir expl
itamente los ar
os no existentes
on el valor NULL, lo que
que a
arreara es
ribir en las n n entradas y perder el eventual ahorro de espa
io.
Para paliar el problema anterior, usaremos el siguiente tipo intermedio:
hMiembros privados de Map Matrix Graph<GT> 753di+
(752a) 754f 755
struct Mat_Entry
{
Arc * arc;
755
As pues, las entradas que
ontengan memoria, pero que no hayan sido es
ritas,
ontendran
el valor NULL previamente asignado por el
onstru
tor por omision. La matriz de adya
en
ia
se de
lara, enton
es,
omo sigue:
hMiembros privados de Map Matrix Graph<GT> 753di+
(752a) 755b
DynArray<Mat_Entry> mat;
Uses DynArray 45.
755d
return i + j*n;
}
Denes:
index array, used in
hunks 756a, 760, 761a, and 765b.
La ual retorna el ndi e del a eso (i,j) dentro de un DynArray<T> usado para implantar una matriz n*n.
756
756a
Captulo 7. Grafos
756b
La rutina lee la entrada (i,j) en el DynArray<T>mat de tipo Entry que implanta una
matrix n*n.
Con lo anterior podemos implantar el resto de los operadores de a
eso:
hImplanta
i
on de Map Matrix Graph<GT> 754ai+
(752a) 754
757b
template <class GT>
typename GT::Arc * Map_Matrix_Graph<GT>::operator () (const long & i,
const long & j) const
{
Mat_Entry * mat_entry = read_matrix<Mat_Entry>(mat, i, j, num_nodes);
if (mat_entry == NULL)
return NULL;
return mat_entry->arc;
}
template <class GT>
typename GT::Arc * Map_Matrix_Graph<GT>::operator () (Node * src_node,
Node * tgt_node) const
{
Mat_Entry * mat_entry =
read_matrix<Mat_Entry>(mat,
index_of_node<GT>(nodes, num_nodes, src_node),
index_of_node<GT>(nodes, num_nodes, tgt_node),
num_nodes);
if (mat_entry == NULL)
return NULL;
return mat_entry->arc;
}
Uses index of node 754b and read matrix 756a.
757
Una vez denidos los a
esos, nos resta por denir los
onstru
tores y la asigna
ion.
Para ello, nos
onviene la siguiente rutina de
opia:
hMiembros p
ubli
os de Map Matrix Graph<GT> 752bi+
(752a) 755a
757a
757b
Para
ulminar
on esta
lase, debemos instrumentar copy list graph(), el
ual se
remite, basi
amente, a dos fases:
1.
757
h opiar
nodos 757
i
(758a)
// copiar los nodos de g hacia el arreglo nodes[]
int i = 0;
for (typename GT::Node_Iterator it(g); it.has_current(); it.next(), ++i)
nodes[i] = it.get_current_node();
2.
757d
h
opiar ar
os 757di
(758a)
// Para todos los arcos de g
for (typename GT::Node_Iterator nit(g); nit.has_current(); nit.next())
{
Node * src = nit.get_current_node();
const long src_idx = index_of_node<GT>(nodes, num_nodes, src);
// para cada arco de src escribirlo en la matriz mat
for (typename GT::Node_Arc_Iterator ait(src); ait.has_current(); ait.next())
{
Arc * arc = ait.get_current_arc();
Node * tgt = g.get_connected_node(arc, src);
const long tgt_idx = index_of_node<GT>(nodes, num_nodes, tgt);
write_matrix<Mat_Entry>(mat, src_idx, tgt_idx, num_nodes, arc);
}
758
Captulo 7. Grafos
}
Uses get connected node 676b, has current 103, index of node 754b, Node Arc Iterator 656a,
and Node Iterator 655b.
758a
Notemos el uso de write matrix(), la
ual generi
amente instrumenta la es
ritura en una
entrada de una matriz implementada
on DynArray<T>.
Entendidas las fases, la
opia es bastante simple:
hImplanta
i
on de Map Matrix Graph<GT> 754ai+
(752a) 757b
template <class GT>
void Map_Matrix_Graph<GT>::copy_list_graph(GT & g)
{
// copiar atributos
lgraph
= &g;
num_nodes = g.get_num_nodes();
nodos 757 i
h
opiar
}
Denes:
ar os 757di
7.6.2
El TAD Map Matrix Graph<GT> re
ientemente estudiado, solo sirve para obtener
una representa
ion matri
ial en fun
ion de objetos de tipo Graph Node<Node Type>
y Graph Arc<Arc Type>. Una modi
a
ion sobre una entrada matri
ial (i, j) de
Map Matrix Graph<GT> redunda en una modi
a
ion dire
ta sobre el ar
o de tipo
Graph Arc<Arc Type>.
A ve
es lo que se requiere es una matriz
opia de un List Graph<Node, Arc> o un
Map Matrix Graph<GT> que se pueda modi
ar sin alterar el grafo original (insistimos,
de tipo List Graph<Node, Arc>). El proposito de Matrix Graph<GT> es, pues, obtener
una matriz de adya
en
ia
uyos
ontenidos sean los
ontenidos de los ar
os de un grafo
originalmente representado
on List Graph<Node, Arc>.
Matrix Graph<GT> se dene por la siguiente
lase:
758b
(751)
759
public:
hMiembros p
ubli
os de Matrix Graph<GT> 759
i
};
Denes:
Matrix Graph, used in
hunk 760.
759a
n es la
antidad de nodos del grafo; o sea, la dimension de la matriz y del arreglo de nodos.
El a
eso a una entrada (i) de un objeto Matrix Graph<GT> retorna un objeto de tipo
typename GT::Node Type. Analogamente, el a
eso a una entrada (i, j) retorna un objeto
de tipo typename GT::Arc Type.
Con la matriz arcs, se nos plantea un peque~no in
onveniente para ahorrar el espa
io
759b
o
upado por las entradas nulas; es de
ir, por las entradas en las
uales no hayan ar
os.
Para ello, nos serviremos del siguiente atributo adi
ional:
hMiembros privados de Matrix Graph<GT> 759ai+
(758b) 759a 760
759
El
ual representa lo que se
onsidera la ausen
ia de ar
o. Este valor espe
ial puede
observarse mediante:
hMiembros p
ubli
os de Matrix Graph<GT> 759
i
(758b) 761a
const Arc_Type & null_value() const { return Null_Value; }
Uses Null Value 759b.
760
760
Captulo 7. Grafos
n
= mat.n;
// copiar atributos
Null_Value = mat.Null_value;
nodes.cut();
arcs.cut();
arcs.set_default_initial_value(Null_Value);
// recorrer la matriz origen y copiarla a nodes[] y arcs[]
for (int i = 0; i < n; ++i)
{
nodes[i] = mat.nodes[i];
for (int j = 0; j < n; ++j)
{
const long mat_index = index_array(i, j, n);
if (not arcs.exist(mat_index))
continue;
arcs.touch(mat_index) = mat.arcs.access(mat_index);
}
}
}
void copy(GT & g)
{
n = g.get_num_nodes();
DynArray<typename GT::Node *> ptr; // arreglo temporal
int i = 0;
for (typename GT::Node_Iterator it(g); it.has_current(); it.next(), ++i)
ptr[i] = it.get_current_node();
quicksort(ptr); // ordenar por si se requiere relaci
on con Map_Matrix_Graph
arcs.set_default_initial_value(Null_Value);
for (i = 0; i < n; ++i) // coloca contenidos de ptr[] en nodes[]
{
761
761a
7.6.3
En mu
has
ir
unstan
ias se requiere una matriz homomorfa a una de adya
en
ia que
ontenga valores distintos y, muy probablemente, de tipos diferentes a los del grafo. Por ejemplo, pudiera ne
esitarse una matriz
ontentiva de dura
iones de traye
tos entre ar
os.
Para la situa
ion anterior se tiene el tipo Ady Mat<GT, Entry>, el
ual modeliza una
matriz elementos de tipo Entry Type y espe
i
ada de la siguiente forma:
761b
(751)
762
Captulo 7. Grafos
{
public:
typedef GT List_Graph_Type;
typedef __Entry_Type Entry_Type;
typedef typename GT::Node_Type Node_Type;
typedef typename GT::Arc_Type Arc_Type;
typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
private:
hMiembros
Entry> 762i
Entry> 763ai
public:
hMiembros
};
puede de idir el tipo de datos que albergaran las entradas de la matriz. Conse uentemente,
riores:
hMiembros privados de
GT * lgraph;
(761b)
DynArray<Node*> nodes;
DynArray<Entry_Type> mat;
mutable size_t num_nodes;
mutable Entry_Type Null_Value;
Uses DynArray 45 and Null Value 759b.
Los a
esos a un objeto Ady Mat<GT, Entry> son similares a los de Map Matrix Graph<GT>.
Los nodos pueden referirse por punteros a nodos en un List Graph<Node, Arc> o por
sus ndi
es enteros respe
to a la matriz.
El
onsumo de memoria de la matriz es propor
ional a la
antidad de entradas que
hayan sido es
ritas. Una entrada (i, j) que no haya sido es
rita puede retornar false
763a
763
763b
El uso de Null Value es el mismo que el de la
lase Matrix Graph<GT>: denir el
ero
en la matriz y permitir un ahorro de espa
io por los ar
os ausentes.
Puesto que el tipo de dato que
ontiene la matriz es independiente del objeto
List Graph<Node, Arc>, no hay ne
esidad de o
upar espa
io a priori para el
ontenido
de la matriz. Por eso tiene sentido el siguiente
onstru
tor:
hMiembros p
ubli
os de Ady Mat<GT, Entry> 763ai+
(761b) 763a 763
Ady_Mat(GT & g) : lgraph(&g), num_nodes(lgraph->get_num_nodes())
{
copy_nodes(g);
}
Uses Ady Mat 761b.
763
En este
aso la matriz no o
upa memoria ini
ialmente; esta se apartara a medida que
las entradas se vayan es
ribiendo. Sin embargo, si se opta por
onstruir de esta forma un
objeto Ady Mat<GT, Entry>, enton
es el usuario debe, el mismo, en
argarse de es
ribir
todas las entradas de la matriz (a traves del operador (i, j)), pues el valor Null Value
no se ha denido o, denir Null Value antes de realizar
ualquier es
ritura. El TAD no
veri
a este orden.
Hay dos maneras de ahorrar memoria por ausen
ia de ar
os o porque la apli
a
ion
maneje algun elemento nulo, la primera
onsiste en denir Null Value en tiempo de
onstru
ion mediante:
hMiembros p
ubli
os de Ady Mat<GT, Entry> 763ai+
(761b) 763b 763d
Ady_Mat(GT & g, const Entry_Type & null)
: lgraph(&g), num_nodes(lgraph->get_num_nodes()), Null_Value(null)
{
copy_nodes(*lgraph);
}
Uses Ady Mat 761b and Null Value 759b.
763d
En este
aso, Entry Type debe ser el mismo tipo que GT::Arc Type. Un error de
ompila
ion debe generarse si no es as. La segunda manera es mediante la siguiente primitiva:
hMiembros p
ubli
os de Ady Mat<GT, Entry> 763ai+
(761b) 763
764a
El tipo Ady Mat<GT, Entry> esta destinado para guardar datos de
ualquier tipo;
sobre todo para
al
ulos temporales requeridos por algoritmos de grafos dise~nados
764
Captulo 7. Grafos
para matri
es de adya
en
ia. Como tal, es muy
omun ini
ializar matri
es de tipo
Ady Mat<GT, Entry>. Para esta tarea, se proveen dos
lases de interfa
es:
1.
764a
hMiembros p
ubli
os de Ady Mat<GT, Entry> 763ai+
(761b) 763d 764b
template <class Operation> void operate_all_arcs_list_graph();
Uses operate all arcs list graph 765b.
Esta primitiva eje
uta la opera
ion Operation) sobre
ada entrada de la matriz que
ontenga un ar
o en List Graph<Node, Arc>.
Notemos que la opera
ion no se invo
a para ar
os ausentes.
El formato de la
lase Operation() es Operation()(mat, arc, i, j, entry),
uyos
parametros se des
riben as:
(a) mat: es una referen
ia al objeto Ady Mat<GT, Entry> sobre el
ual se esta
realizando la opera
ion.
(b) arc: es un apuntador al ar
o dentro del List Graph<Node, Arc> aso
iado a
la matriz.
(
) i: es el ndi
e del nodo origen en la matriz.
(d) j: es el ndi
e del nodo destino en la matriz.
(e) entry: es una referen
ia al
ontenido de la entrada (i, j) en la matriz.
Eventualmente, si se requiere transmitir algun parametro adi
ional a la opera
ion, enton
es
puede usarse la siguiente version:
764b
hMiembros p
ubli
os de Ady Mat<GT, Entry> 763ai+
(761b) 764a 765a
template <class Operation> void operate_all_arcs_list_graph(void * ptr);
Uses operate all arcs list graph 765b.
765
Puesto que solo se ini
ializan entradas para ar
os dentro del List Graph<Node, Arc>
aso
iado, aquellas entradas en donde no haya ar
o
ontienen el valor Null Value.
2. La segunda interfaz esta basada en ndi
es de Ady Mat<GT, Entry>:
hMiembros p
ubli
os de Ady Mat<GT, Entry> 763ai+
(761b) 764b
765a
template <class Operation> void operate_all_arcs_matrix();
El esquema es similar al anterior, salvo que, en este
aso, se re
orren todas las entradas y,
por tanto, se aparta la memoria para todas. Conse
uentemente, el
onsumo de espa
io es
O(n2).
El esquema de parametros de la
lase Operation() es el siguiente:
(a) mat: referen
ia a la matriz.
(b) src node: puntero al nodo origen dentro del List Graph<Node, Arc> aso
iado.
(
) tgt node: puntero al nodo destino dentro del List Graph<Node, Arc> aso
iado.
(d) s: ndi
e del nodo origen.
(e) t: ndi
e del nodo destino.
(f) entry: referen
ia a entrada dentro de la matriz.
(g) ptr (op
ional segun la interfaz): puntero opa
o.
765b
Clari
ada la interfaz, podemos mostrar algunas de las implementa
iones de los
metodos anteriores:
hImplanta
i
on de metodos de Ady Mat<GT, Entry> 765bi
(761b)
template <class GT, typename __Entry_Type>
template <class Operation>
void Ady_Mat<GT, __Entry_Type>::operate_all_arcs_list_graph()
{
// recorrer todos los nodos del grafo
for (typename GT::Node_Iterator nit(*lgraph); nit.has_current(); nit.next())
{
Node * src = nit.get_current_node();
const long src_idx = // obtener
ndice del nodo origen
index_of_node<List_Graph_Type>(nodes, num_nodes, src);
// recorrer todos los arcos del nodo origen
for (typename GT::Node_Arc_Iterator at(src); at.has_current(); at.next())
{
Arc * arc = at.get_current_arc();
766
Captulo 7. Grafos
Denes:
7.6.4
766
La ultima
lase de matriz se denomina Bit Mat Graph<GT> y representa una muy simple
matriz de adya
en
ia de bits. Un valor uno indi
a presen
ia de ar
o; uno nulo, ausen
ia. Por supuesto, el tipo en
uestion esta basado en el TAD BitArray desarrollado
en x 2.1.3 (pagina 36) y se dene de la siguiente manera:
hM
etodos privados de Bit Mat Graph<GT> 766i
(767a) 767b
BitArray bit_array;
Uses BitArray 36.
767
(751)
typedef GT List_Graph_Type;
typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
private:
hM
etodos
public:
hM
etodos
};
Denes:
767b
Un Bit Mat Graph<GT> representa una matriz de adya
en
ia,
uyos bits solo
expresan presen
ia o ausen
ia de ar
o dentro de un grafo representando
on un
List Graph<Node, Arc>. Bit Mat Graph<GT> no sirve para manejar multigrafos o multidigrafos.
Bit Mat Graph<GT> mantiene un apuntador al List Graph<Node, Arc> aso
iado,
un arreglo dinami
o de nodos que permitira
al
ular rapidamente el ndi
e mediante la
busqueda binaria y la
antidad de nodos:
hM
etodos privados de Bit Mat Graph<GT> 766i+
(767a) 766
GT *
lgraph;
DynArray<typename GT::Node*> nodes;
mutable size_t
n;
Uses DynArray 45.
767
768
Captulo 7. Grafos
768a
El
uarto
onstru
tor no requiere un List Graph<Node, Arc> porque esta destinado para
de
larar matri
es de bits a usarse para
al
ulos par
iales. Esto, por supuesto, impide el
a
eso por dire
iones de nodos en un List Graph<Node, Arc>.
Eventualmente, se puede espe
i
ar el List Graph<Node, Arc> por separado, as
omo tambien
onsultarlo:
hM
etodos publi
os de Bit Mat Graph<GT> 767
i+
(767a) 767
768b
void set_list_graph(GT & g)
{
lgraph = &g;
copy_list_graph(g);
}
GT * get_list_graph() { return lgraph; }
Denes:
set list graph, used in
hunk 771a.
Uses copy list graph 758a.
768b
Finalmente, debemos implantar las
uatro formas de a
eso, dos de las
uales pueden
instrumentarse dire
tamente de forma similar a las
lases matri
es que ya hemos tratado:
hM
etodos publi
os de Bit Mat Graph<GT> 767
i+
(767a) 768a
Node * operator () (const long & i) const
{
if (lgraph == NULL)
throw std::domain_error("There is no a List_Graph object");
return Aleph::get_node<GT>(nodes, i);
}
long operator () (Node * node) const
{
if (lgraph == NULL)
throw std::domain_error("There is no a List_Graph object");
return index_of_node<GT>(nodes, n, node);
}
Uses get node 753e, index of node 754b, and List Graph 649.
La implanta
ion del a
eso a una entrada matri
ial (i, j) es mas
ompleja que las anteriores, pues el
ompilador y la mayora de hardware no manejan bits
omo una unidad. El
ompilador maneja palabras
ompuestas por bits. No podemos, pues, implantar el operador (i, j) para que retorne un bit. Tampo
o nos sirve que el operador (i, j) nos retorne una
769
palabra de tipo int, pues el a
eso podra ser de es
ritura. Debemos, enton
es, distinguir
los a
esos de es
ritura de los de le
tura y, para ello, usaremos una
lase Proxy en el mismo
estilo que otros tipos ya estudiados.
7.6.5
Algoritmo de Warshall
Ilustraremos nuestra primera utiliza
ion de una matriz de adya
en
ia para implantar un
elebre algoritmo que
al
ula todas las rela
iones de
one
tividad de un grafo a traves de
los distintos
aminos. El algoritmo en
uestion tiene mas sentido sobre grafos dirigidos y
es
ono
ido
omo \de Warshall" [11 en honor a su des
ubridor.
02
04
03
01
10
06
09
07
05
11
12
08
13
14
Si una rela
ion R se representa matri
ialmente, enton
es, la matriz
orrespondiente a
la rela
ion R representa todas las rela
iones posibles de
one
tividad entre
ada par de
nodos. Una entrada R(i,j) 6= 0 indi
a que existe un
amino entre los nodos i y j. Cal
ular
R nos permite, pues, implementar una prueba de existen
ia de
amino entre
ualquier
par de nodos.
El algoritmo de Warshall para
al
ular la
lausura transitiva se sirve de la
onstru
ion
de rela
iones entre
aminos de longitud i + 1. La idea es
onstruir una se
uen
ia de rela
iones intermedias (o digrafos) R0, R1, R2, . . . , Rn. En
ada itera
ion que
al
ula Rk, se
examinan todos los tros (vk, vi, vj) de nodos de Rk1. Si la transitoriedad vi vj vk
existe en Rk1, enton
es se a~nade el ar
o vi vk a Rk
Por supuesto, si dentro de un trio (vk, vi, vj) en Rk1 ya existe un ar
o vi vk,
enton
es la entrada Rki,j = 1. La formula resultante es, enton
es:
i1
i1
i1
)
Rk,j
(Ri,k
Rii,j = Ri,j
(7.2)
El pro
eso se eje
uta n ve
es hasta llegar a los
aminos de longitud n. Lo que nos arroja
R = Rn =
n
Y
k=1
Rk1 Rk
(7.3)
770
Captulo 7. Grafos
=
(a) Rk1
(b) Rk
1
0
0
0
1
0
0
0
0
0
0
0
0
0
0
2
0
0
0
0
0
0
0
0
0
0
0
0
0
0
3
1
0
0
0
0
0
0
0
0
0
0
0
0
0
4
0
1
0
0
0
0
1
0
0
0
0
0
0
0
5
0
0
1
0
0
0
0
0
0
0
0
0
0
0
6
0
0
1
1
0
0
0
0
0
0
0
0
0
0
7
0
0
0
0
1
0
0
0
0
0
0
0
0
0
8
0
0
0
0
0
0
1
0
0
0
0
0
0
0
9
0
0
0
0
0
0
1
0
0
0
0
0
0
0
10
0
0
0
0
0
1
0
0
1
0
0
0
0
0
11
0
0
0
0
0
0
0
0
0
0
0
1
0
0
12
0
0
0
0
0
0
0
1
1
0
0
0
0
1
13
0
0
0
0
0
0
0
0
0
1
1
0
0
0
14
0
0
0
0
0
0
0
0
0
0
0
0
1
0
770a
770b
A efe
tos de ahorro de espa
io, podemos dise~nar un algoritmo que solo use dos matri
es
de manera tal de no redundar el espa
io de las n matri
es. Para ello, denominamos a mat
omo la matriz de bits Rk en la itera
ion k y a mat prev
omo Rk1, el
ual hay que
ini
iar R0:
hCal
ular
lausura transitiva 770bi
(770a) 771a
Bit_Mat_Graph<GT> mat_prev(g);
Uses Bit Mat Graph 767a.
771a
771
Antes de ini
iar
ualquier
al
ulo, debemos asegurar de que mat este aso
iado al grafo g:
hCal
ular
lausura transitiva 770bi+
(770a) 770b 771b
if (mat.get_list_graph() != &g)
mat.set_list_graph(g);
Uses set list graph 768a.
771b
(770a) 771a
1
2
3
4
5
6
R = 7
8
9
10
11
12
13
14
1
1
1
1
1
1
0
1
0
0
0
0
0
0
0
2
0
0
0
0
0
0
0
0
0
0
0
0
0
0
3
1
1
1
1
1
0
1
0
0
0
0
0
0
0
4
1
1
1
1
1
0
1
0
0
0
0
0
0
0
5
1
1
1
1
1
0
1
0
0
0
0
0
0
0
6
1
1
1
1
1
0
1
0
0
0
0
0
0
0
7
1
1
1
1
1
0
1
0
0
0
0
0
0
0
8
1
1
1
1
1
0
1
0
0
0
0
0
0
0
9
1
1
1
1
1
0
1
0
0
0
0
0
0
0
10
1
1
1
1
1
1
1
0
1
0
0
0
0
0
11
1
1
1
1
1
1
1
1
1
1
1
1
1
1
12
1
1
1
1
1
1
1
1
1
1
1
1
1
1
13
1
1
1
1
1
1
1
1
1
1
1
1
1
1
14
1
1
1
1
1
1
1
1
1
1
1
1
1
1
Figura 7.37: Matriz de adya
en
ia de la
lausura transitiva del grafo del la gura 7.34
(pag. 769.
7.7
Dado un grafo
onexo G =< V, E >
on distan
ias (o pesos), un arbol abar
ador mnimo
es un arbol abar
ador de G tal que la suma total de sus distan
ias es mnima.
Los arboles abar
adores tienen muy utiles apli
a
iones, de las
uales
abe men
ionar
algunas pra
ti
as. Supongamos, por ejemplo, la plani
a
ion de un sistema ferroviario na
ional, o de una red de transmision ele
tri
a para el pas. Ambos proye
tos
onllevan
ostes
altsimos en terminos de trabajo, dura
ion y nan
iamiento. En estas situa
iones se puede
modelizar un grafo de todas las alternativas de fa
tibilidad segun los terminos men
ionados. El grafo mantendra las posibles vas en fun
ion de la geografa, o de la dura
ion
772
Captulo 7. Grafos
(a)
(b)
peso(e) es mnima
(7.4)
eE
773
G
.
.
.
G1
G2
arcos de corte
Si en esta gura imaginamos un arbol abar
ador, enton
es, nos debe ser obvio que solo uno
entre los ar
os de
ru
e pertene
e al arbol abar
ador mnimo, pues, de lo
ontrario, habra
un
i
lo y no podramos de
ir que es un arbol abar
ador. >
ual entre todos los ar
os
de
ru
e pertene
e al arbol abar
ador mnimo? La respuesta nos la ofre
e la siguiente
proposi
ion.
Proposici
on 7.1 (Propiedad del corte - Sedgewick-2001 [9]) Sea G =< V, E > un
grafo y T =< V, E > | E E, un arbol abar
ador mnimo de G. Sea < G1, G2 > un
orte
ualquiera de G. Enton
es el ar
o de
ru
e mnimo entre los posibles de
ru
e entre G1 y
G2 pertene
e al arbol abar
ador mnimo.
Demostraci
on (por contradicci
on)
Supongamos un
orte
ualquiera < G1, G2 > y la existen
ia de un arbol abar
ador mnimo T =< V, E >
uyo ar
o de
ru
e no es el mnimo.
Sean T1 y T2 los
omponentes de T segun el
orte. Pi
tori
emos el asunto as:
T
.
.
.
T1
T2
arcos de corte
Los algoritmos de
al
ulo de arbol abar
ador mnimo que trataremos
onstruyen progresivamente un arbol T por a~nadidura progresiva de ar
os. Cada vez que se a~nade un
nuevo ar
o al arbol T se veri
a si se
ausa un
i
lo. Si as es, enton
es el ar
o re
ien
a~nadido se elimina del arbol; de lo
ontrario, este pertene
e al arbol abar
ador mnimo.
La diferen
ia entre los algoritmos es la manera en que se es
ogen los ar
os del grafo para
irlos in
orporando a T . De este modo, podemos vislumbrar el siguiente patron general de
un algoritmo de
al
ulo de arbol abar
ador mnimo:
774
Captulo 7. Grafos
Algoritmo 7.4 (Patron general de
al
ulo de arbol abar
ador mnimo) Entrada: un grafo
on pesos
onexo G =< V, E >.
Salida: Un arbol abar
ador mnimo T =< V, E > de G.
lo
orta en dos arboles disjuntos los
uales, segun la proposi
ion 7.1 estan
one
tados en
T por el mnimo ar
o de
ru
e. T es, por tanto, mnimo
7.7.1
Si pretendemos elaborar algoritmos generales para
al
ular arboles abar
adores mnimos,
es de
ir, que operen para
ualquier
lase de pesos, enton
es debemos separar todo lo que
ata~na a los pesos del algoritmo. En tal sentido, usaremos dos
lases que seran parametros
tipos de un algoritmo para
al
ular el arbol abar
ador mnimo:
1. Distance<GT>:
lase de a
eso al peso de un ar
o, la
ual debe exportar los siguientes
atributos:
(a) Distance<GT>::Distance Type: el tipo de dato que representa un peso en un
ar
o.
(b)
775
775a
Las
lases Distance y Compare se
onjugan bajo una
lase espe
ial de
ompara
ion de
ar
os
uya deni
ion es
omo sigue:
hComparador de ar
os 775ai
(696) 775b
template <class GT, class Distance, class Compare>
struct Distance_Compare
{
bool operator () (typename GT::Arc * a1, typename GT::Arc * a2) const
{
return Compare () (Distance() (a1) , Distance() (a2) );
}
};
Denes:
Distance Compare, used in
hunks 776a and 781{83.
775b
Una primera forma de
onjugar estas
lases es implantando el
al
ulo del
oste de un
grafo; es de
ir, la suma de los pesos de todos sus ar
os. Para ello, requerimos una
uarta
lase que implante la suma de pesos:
hComparador de ar
os 775ai+
(696) 775a
template <class GT, class Distance, class Compare, class Plus>
typename Distance::Distance_Type total_cost(GT & g)
{
typename Distance::Distance_Type sum = Distance::Zero_Distance;
776
Captulo 7. Grafos
7.7.2
776a
Algoritmo de Kruskal
El algoritmo de Kruskal sele
iona los ar
os a insertar en T segun su peso, desde del
menor al mayor. Para ello, los ar
os se ordenan previamente, de manera tal que, iterando
sobre ellos (ver x 7.3.8.1 (pagina 674)), estos aparez
an ordenadamente. Este ordenamiento
previo lo realizamos del siguiente modo:
hordenar ar
os 776ai
(778
)
g.template sort_arcs<Distance_Compare<GT, Distance, Compare> >();
Uses Distance Compare 775a and sort arcs 689d.
776b
hVeri ar
777
GT::map_arcs(arc, arc_in_tree);
}
Uses Arc Iterator 661a, has cycle, insert arc 682b, map arcs 686, and remove arc 684.
El
i
lo mira los ar
os del grafo desde el menor hasta el mayor. Cada ar
o que es mirado se
inserta en tree y, si este no
ausa un
i
lo, enton
es este es parte del arbol abar
ador. El
pro
eso
ontinua hasta que la
antidad de ar
os del arbol sea exa
tamente la
antidad de
nodos del grafo menos uno, lo
ual es la indi
a
ion de que el arbol ya abar
a
ompletamente
a los nodos del grafo sin perder su
ondi
ion de arbol -
ualquier otro ar
o que se inserte
en tree
ausara un
i
lo-.
Una gran bondad del algoritmo de Kruskal, aprensible mediante inspe
ion de sus
bloques prin
ipales, es que puede modi
arse
on relativa fa
ilidad para que opere
on
urrentemente. En primer lugar,
omo su
intamente expli
amos
on el qui
ksort
en x 3.2.2.9 (pagina 225), podemos a
elerarlo substan
ialmente si lo modi
amos para
usar varios pro
esadores materiales. Mas dif
il, pero
omprobadamente posible, es modi
ar el bloque h
al
ular arbol abar
ador mnimo segun Kruskal 776bi para que maneje
los bloques internos hVeri
ar si nodo origen existe en tree 778ai y hVeri
ar si nodo
destino existe en tree 778bi en pro
esadores diferentes.
L
10
12
10
F
9
8
15
3
H
10
12
10
7
4
10
12
10
2
3
8
15
3
H
F
9
10
12
10
15
1
10
12
10
3
I
2
3
2
C
7
4
2
1
1
3
8
15
N
3
F
9
4
A
15
1
H
7
2
C
(e)
3
I
2
3
K
2
3
4
1
3
5
4
7
4
2
C
K
2
(d)
N
3
7
4
(
)
8
(b)
3
N
3
2
C
(a)
3
I
7
B
2
D
7
4
2
1
5
4
K
2
10
12
10
F
9
4
A
8
15
1
3
3
H
7
B
2
D
P
7
4
2
1
2
3
5
4
K
2
(f)
Figura 7.39: Progreso del algoritmo de Kruskal (
ada gura
orresponde a tres itera
iones)
Por requerimientos de interfaz para insertar un ar
o en tree es ne
esario que sus nodos
ya hayan sido previamente insertados. Este es el rol basi
o de los bloques hVeri
ar si
nodo origen existe en tree 778ai y hVeri
ar si nodo destino existe en tree 778bi, del
778
778a
Captulo 7. Grafos
(776b)
typename GT::Node * g_src_node = g.get_src_node(arc); // nodo origen en g
typename GT::Node * tree_src_node; // nodo origen en tree
778b
Como se ve, el bloque tiene dos
ujos: o el nodo ya esta insertado y mapeado en tree
(
ujo del else), o hay que
rearlo, mar
arlo y mapearlo. El otro bloque es similar pero
on el otro extremo del ar
o:
hVeri
ar si nodo destino existe en tree 778bi
(776b)
typename GT::Node * g_tgt_node = g.get_tgt_node(arc);
typename GT::Node * tree_tgt_node; // Nodo destino en
arbol abarcador
if (not IS_NODE_VISITED(g_tgt_node, Aleph::Kruskal))
{
// No, crearlo en tree, atarlo al cookie y marcar como visitado
NODE_BITS(g_tgt_node).set_bit(Aleph::Kruskal, true);
tree_tgt_node = tree.insert_node(g_tgt_node->get_info());
GT::map_nodes(g_tgt_node, tree_tgt_node);
}
else
tree_tgt_node = static_cast<typename GT::Node*>(NODE_COOKIE(g_tgt_node));
Uses insert node 682a, IS NODE VISITED 670d, map nodes 686, NODE BITS 670d, NODE COOKIE 670d,
and set bit 664a.
778
Con todas las estru
turas anteriores, podemos espe
i
ar la rutina de interfaz:
hAlgoritmo de Kruskal 778
i
(779)
template <class GT, class Distance, class Compare> inline
void kruskal_min_spanning_tree(GT & g, GT & tree)
{
if (g.is_digraph())
throw std::domain_error("g is a digraph");
if (not test_connectivity(g))
throw std::invalid_argument("Input graph is not connected");
g.reset_bit_nodes(Aleph::Kruskal); // limpiar bits de marcado
tree.clear_graph(); // limpia grafo destino
hordenar
ar os 776ai
h al ular
779
}
template <class GT, class Distance> inline
void kruskal_min_spanning_tree(GT & g, GT & tree)
{
typedef typename Distance::Distance_Type DT;
kruskal_min_spanning_tree<GT, Distance, Aleph::less<DT> > (g, tree);
}
Denes:
kruskal min spanning tree, never used.
Uses clear graph 685b, digraph 679, is digraph, reset bit nodes 666
, and test connectivity 701.
779
Al nal de la llamada, tree
ontiene un arbol abar
ador
uyos cookies, tanto de
nodos
omo de ar
os, estan mapeados al grafo g y vi
eversa. Las distan
ias de los ar
os
son de tipo Distance::Distance Type y a
edidas por esta
lase. Las
ompara
iones entre
distan
ia las realiza la
lase Compare.
La implanta
ion
ompleta reside en el ar
hivo hKruskal.H 779i:
hKruskal.H 779i
hAlgoritmo de Kruskal 778
i
7.7.2.1
An
alisis del algoritmo de Kruskal
El tiempo de eje
u
ion estara dado por la suma de los bloques hordenar ar
os 776ai mas
h
al
ular
arbol abar
ador mnimo segun Kruskal 776bi.
Sabemos, por analisis previos, que la mejor manera de ordenar una lista es O(n lg (n)),
por lo que el bloque hordenar ar
os 776ai exhibe O(E lg E), esperado o determinista segun
el metodo de ordenamiento.
Para el bloque h
al
ular arbol abar
ador mnimo segun Kruskal 776bi asumimos,
omo peor
aso, que la
ongura
ion de ar
os es tal que estos deben revisarse
ompletamente. El while requerira, pues, O(E) itera
iones. Dentro del while se eje
utan
onstantemente dos opera
iones: la inser
ion del ar
o en tree y la prueba de a
i
li
idad has cycle(). La inser
ion de un ar
o es O(1), mientras que la invo
a
ion a
has cycle() (ver x 7.5.5 (pagina 706)) es O(V), pues, por su
ondi
ion de arbol o de grafo,
par
ialmente in
onexo, en tree E V . El bloque h
al
ular arbol abar
ador mnimo
segun Kruskal 776bi
onlleva, enton
es, O(E) O(V) = O(E V) en el peor de los
asos.
El algoritmo de Kruskal
onsume, para el peor
aso, O(E lg E) + O(E V) =
O(max(E lg E, E V), lo
ual lo ha
e atra
tivo para grafos espar
idos.
7.7.2.2
Para eviden
iar la
orre
titud, es de
ir, si el algoritmo en efe
to en
uentra un arbol abar
ador mnimo, nos valdremos de la siguiente proposi
ion:
Proposici
on 7.3 Sea G un grafo
onexo
ualquiera. Enton
es el algoritmo de Kruskal
Demostraci
on (por inducci
on sobre V)
780
Captulo 7. Grafos
1. V 2: la prueba es inmediata.
2. V > 2: Asumimos que la proposi
ion es
ierta para todo n y veri
aremos su validez
para n + 1.
Por la hipotesis indu
tiva, el algoritmo mantiene una arbores
en
ia de arboles abar
adores mnimos. Cuando se toma un ar
o a n + 1 pueden o
urrir dos situa
iones:
(a) Que se
ree un
i
lo, en
uyo
aso, puesto que los ar
os fueron vistos ordenadamente, a es el ar
o
on mayor peso dentro del
i
lo
ausado. De este modo, al
eliminarlo del arbol, segun la propiedad del
i
lo (prop. x 7.2), la arbores
en
ia
se preserva y esta es de arboles abar
adores mnimos.
(b) No se
rea un
i
lo, en
uyo
aso la adi
ion de a une a dos
omponentes in
onexos. Ahora bien, puesto que a es visto ordenadamente, a es el mnimo ar
o
de
ru
e entre estos dos
omponentes y, por la propiedad de
orte (prop. x 7.1),
el
omponente resultante es un arbol abar
ador mnimo
La proposi
ion es
ierta para todo grafo
7.7.3
Algoritmo de Prim
El segundo algoritmo para
onstruir un arbol abar
ador se basa en una modi
a
ion del
re
orrido en amplitud, de manera tal que se ponderen los pesos de los ar
os.
7.7.3.1
780
(781a)
Prim 786ai
hDe
lara
i
on
hIni
ializar
h
al
ular
}
heap 787ai
hDesini ializar
Prim 786 i
781
781a
Fun
ionalmente, estas interfa
es son exa
tas a las del algoritmo de Kruskal.
La implanta
ion
ompleta del algoritmo de Prim subya
e en el ar
hivo hPrim.H 781ai,
el
ual se estru
tura
omo sigue:
hPrim.H 781ai
hDeni
iones de Prim 785ai
hAlgoritmo
de Prim 780i
hIndeni iones
7.7.3.2
de Prim 786di
Como vimos en x 7.5.3 (pagina 701), el
oste en espa
io debido a la
ola puede al
anzar O(E). En la pra
ti
a, este es basi
amente el mismo
onsumo que a
arreara el
heap (hDe
lara
ion de
ola de prioridad 786ei) si usasemos meramente este enfoque.
Si el grafo es denso, el
oste puede ser O(V 2), un
oste altsimo. As las
osas, dada la
importan
ia del
al
ulo de arboles abar
adores mnimos
omo problema fundamental de
grafos, es momento de introdu
ir una te
ni
a presentada por Sedgewi
k [9 que redu
e
substan
ialmente el tama~no del heap a no mas de V entradas; lo que impli
a que el
oste
en tiempo se redu
e a O(lg V) y el de espa
io a O(V), una ganan
ia fenomenal en tiempo
y, sobre todo, en espa
io. El
on
epto que nos guiara ha
ia la
omprension de esta te
ni
a
subya
e en la siguiente deni
ion:
Definici
on 7.7 (Heap exlcusivo de arcos mnimos) Un heap ex
lusivo de ar
os
mnimos es un heap que guarda ar
os de un grafo ordenados por su peso de manera
tal que, en ningun momento, pueden existir dos ar
os
on el mismo nodo destino.
Ci~nendonos a la deni
ion, para un grafo G =< V, E >, es imposible que el heap
ontenga mas de V 1 ar
os, pues si no habran dos ar
os
on el mismo nodo destino.
Por su
ara
ter de heap, un heap ex
lusivo garantiza que la extra
ion de un ar
o se
orrespondera
on el de peso mnimo entre todos los in
luidos en el
onjunto.
781b
# include <tpl_binHeap.H>
782
Captulo 7. Grafos
# include <tpl_graph_utils.H>
template <class GT, class Distance, class Compare, class Access_Heap_Node>
struct ArcHeap :
public BinHeap<typename GT::Arc*, Distance_Compare<GT, Distance, Compare> >
{
hAtributos p
ubli
os de Ar
Heap 782i
};
# endif // ARCHEAP_H
Denes:
ArcHeap, used in
hunks 784{86, 794b, and 796e.
Uses Distance Compare 775a.
782
El ar
hivo dene la
lase ArcHeap<GT, Distance, Compare, Access Arc>, la
ual implementa un heap ex
lusivo de ar
os mnimos, pertene
ientes a un grafo de tipo GT,
on
a
eso a los pesos de los ar
os mediante la
lase Distance y
ompara
ion entre las distan
ias a traves de la
lase Compare. El sentido del parametro tipo Access Arc sera expli
ado
prontamente.
Como se puede observar de la de
lara
ion de ArcHeap<GT, Distance, Compare, Access Arc>,
esta hereda la implementa
ion de BinHeap<Key>, pues el orden de salida es uno de los
requerimientos prin
ipales
uando se extraiga algun ar
o. De este modo, el BinHeap<Key>
manejara nodos del siguiente tipo:
hAtributos p
ubli
os de Ar
Heap 782i
(781b) 783
typedef typename
BinHeap<typename GT::Arc*, Distance_Compare<GT, Distance, Compare> >::Node Node;
Uses Distance Compare 775a.
Lo que debemos implantar, enton
es, es la opera
ion de inser
ion de un ar
o, la
ual
puede resumirse bajo el siguiente algoritmo:
Algoritmo 7.5 (Inser
ion generi
a en un heap ex
lusivo)
La entrada es un ar
o a que rela
iona dos nodos s y t, origen y destino, respe
tivamente.
s es un nodo que ya pertene
e a un arbol abar
ador y ha sido, por tanto, visitado. t no
783
783
En este caso existen dos arcos con el mismo nodo destino ==> hay
que seleccionar el menor arco y descartar el mayor. No hay necesidad de
apartar un nuevo nodo para el heap, pues si el arco cambia,
entonces se ajusta la prioridad mediante update()
784
Captulo 7. Grafos
784a
Para un grafo G =< V, E >, el
oste de una inser
ion de ar
os es O(lg V), pues la
substitu
ion de un ar
o a
ota el heap a un maximo de V 1 elementos.
La elimina
ion es tan deli
ada
omo la inser
ion, pues, aparte del manejo de memoria,
hay que asegurar que el ar
o eliminado no se rela
ione
on su nodo destino:
hAtributos p
ubli
os de Ar
Heap 782i+
(781b) 783 784b
typename GT::Arc * get_min_arc()
{
Node * heap_node = this->getMin(); // saque del heap
typename GT::Arc * arc = heap_node->get_key();
// seleccionar nodo que retorne la clase de acceso
typename GT::Node * p = static_cast<typename GT::Node*>(arc->src_node);
if (Access_Heap_Node () (p) != heap_node)
p = static_cast<typename GT::Node*>(arc->tgt_node);
Access_Heap_Node () (p) = NULL;
delete heap_node;
return arc;
}
Denes:
get min arc, used in
hunks 787b and 798b.
784b
Puesto que el heap ya esta a
otado un maximo de V 1 ar
os, la elimina
ion es O(lg V).
Finalmente, debemos
ongurar el destru
tor para que se liberen rapidamente todos
los elementos del heap:
hAtributos p
ubli
os de Ar
Heap 782i+
(781b) 784a
~ArcHeap()
{
this->remove_all_and_delete();
}
Uses ArcHeap 781b.
7.7.3.3
Inicializaci
on del algoritmo de Prim
En lugar de usar una
ola tradi
ional, el algoritmo de Prim emplea una
ola de prioridad
de manera que el pro
esamiento de los ar
os sea ordenado segun el peso. Para asegurar
un
oste por el heap de no mas de O(V) en espa
io y O(lg V) en tiempo, usaremos un
heap ex
lusivo. Lo primero que debemos ha
er es, enton
es, denir la estru
tura que
785a
785
ontendra el
ookie de un nodo, de modo tal que podamos usar el heap ex
lusivo dise~nado
en la sub-se
ion pre
edente:
hDeni
iones de Prim 785ai
(781a) 785b
template <class GT> struct Prim_Info
{
typename GT::Node * tree_node; // imagen del nodo en el
arbol abarcador
void *
heap_node; // puntero dentro del heap exclusivo
785b
El
ampo tree node guarda la imagen del nodo dentro del arbol abar
ador, mientras que
heap node la dire
ion dentro del heap ex
lusivo que guarda el mnimo ar
o al nodo en
uestion
omo destino. heap node se de
lara
omo void* debido a que tenemos diferentes
ru
es de tipos involu
rados. El
onstru
tor asegura que automati
amente los
ampos se
ini
ian en NULL
uando se aparte la memoria.
Dado un puntero a nodo p, el a
eso a estos
ampos se fa
ilita mediante los siguientes
ma
ros:
hDeni
iones de Prim 785ai+
(781a) 785a 785
785
Ahora, segun los requerimientos del tipo ArcHeap<GT, Distance, Compare, Access Arc>,
debemos espe
i
ar el a
eso al
ampo heap node:
hDeni
iones de Prim 785ai+
(781a) 785b 785d
template <class GT, class Distance, class Compare>
struct Prim_Heap_Info
{
typedef typename ArcHeap<GT, Distance, Compare, Prim_Heap_Info>::Node Node;
Node *& operator () (typename GT::Node * p)
{
void *& heap_node = HEAPNODE(p);
return (Node*&) heap_node;
}
};
Denes:
Prim Heap Info, used in
hunk 786e.
Uses ArcHeap 781b and HEAPNODE 785b.
785d
Segun lo anterior, la ini
ializa
ion del
ookie del nodo se realiza mediante:
hDeni
iones de Prim 785ai+
(781a) 785
786b
template <class GT>
struct Init_Prim_Info
{
786
Captulo 7. Grafos
786a
786b
(780)
Despues que el algoritmo de Prim
al
ula el arbol abar
ador, se debe liberar la memoria
o
upada por los
ookies. Pero antes de eso, se deben mapear los nodos mediante las
imagenes en el arbol abar
ador que estan
ontenidas en el
ampo tree node:
hDeni
iones de Prim 785ai+
(781a) 785d
template <class GT>
struct Uninit_Prim_Info
{
void operator () (GT &, typename GT::Node * p)
{
Prim_Info<GT> * aux = PRIMINFO(p);
GT::map_nodes(p, TREENODE(p));
delete aux;
}
};
Denes:
Uninit Prim Info, used in
hunk 786
.
Uses map nodes 686 and Prim Info 785a.
786
Observemos que Uninit Prim Info()() no libera memoria rela
ionada
on el heap, pues
esto es tarea del destru
tor del heap.
hDesini
ializar Prim 786
i
(780)
g.template operate_on_nodes <Uninit_Prim_Info <GT> > ();
Uses operate on nodes 661
and Uninit Prim Info 786b.
786d
786e
(781a)
Lo que resta de la des-ini
ializa
ion del algoritmo de Prim
on
ierne al heap ex
lusivo.
En primer lugar su de
lara
ion:
hDe
lara
i
on de
ola de prioridad 786ei
(780)
typedef Prim_Heap_Info<GT, Distance, Compare> Acc_Heap;
ArcHeap<GT, Distance, Compare, Acc_Heap> heap;
Uses ArcHeap 781b and Prim Heap Info 785
.
787a
787
En todo momento, la opera
ion heap.get min arc() nos arroja el ar
o
on el menor peso
entre todos los in
luidos dentro del heap. Esta es, hasta los momentos, la uni
a diferen
ia
on la
ola tradi
ional empleada en el re
orrido en amplitud. A
otado esto, la estru
tura del
algoritmo de Prim es fundamentalmente la misma que la
onstru
ion del arbol abar
ador
en amplitud estudiado en x 7.5.9 (pagina 720).
En segundo lugar, la inser
ion de los ar
os adya
entes al nodo de ini
io:
hIni
ializar heap 787ai
(780)
// inicializar la ra
z con el primer nodo del grafo
typename GT::Node * first = g.get_first_node();
NODE_BITS(first).set_bit(Aleph::Prim, true); // marcarlo visitado
7.7.3.4
787b
El algoritmo de Prim
Ahora estamos listos para
onstruir iterativamente el arbol abar
ador en el orden estable
ido por la
ola, lo
ual se realiza de la siguiente manera:
h
al
ular
arbol abar
ador mnimo segun Prim 787bi
(780)
// mientras tree no abarque a g
while (tree.get_num_nodes() < g.get_num_nodes())
{
// obtenga siguiente menor arco
typename GT::Arc * min_arc = heap.get_min_arc();
788
Captulo 7. Grafos
it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (IS_ARC_VISITED(arc, Aleph::Prim))
continue;
ARC_BITS(arc).set_bit(Aleph::Prim, true); // marcar arco
typename GT::Node * tgt = it.get_tgt_node();
if (IS_NODE_VISITED(tgt, Aleph::Dijkstra))
continue; // nodo visitado ==> causar
a ciclo ==> no se requiere
// insertar en heap
heap.put_arc(arc, tgt);
}
// inserte nuevo arco en tree
typename GT::Arc * tree_arc =
tree.insert_arc(TREENODE(src), TREENODE(tgt), min_arc->get_info());
GT::map_arcs(min_arc, tree_arc); // mapear arcos
}
Uses get min arc 784a, has current 103, insert arc 682b, insert node 682a, IS ARC VISITED 670d,
IS NODE VISITED 670d, map arcs 686, Node Arc Iterator 656a, NODE BITS 670d, put arc 783,
and set bit 664a.
Como veremos posteriormente, el arbol abar
ador resultante de este pro
eso es mnimo.
Hay una diferen
ia
ru
ial en la manera en que el algoritmo de Prim trata
on la
propiedad del
i
lo (7.2 pag. 774) y que nos permite evadir una prueba de a
i
li
idad.
Puesto que el arbol abar
ador tree siempre es
onexo, podemos, por simple inspe
ion
de visita de los nodos de un ar
o pro
esado, determinar si la in
lusion del nuevo ar
o en
tree
ausara un
i
lo. Esen
ialmente, si ambos nodos ya han sido visitados, enton
es,
on
ompleta seguridad, sabemos que si insertamos el ar
o, enton
es este
ausara un
i
lo;
de lo
ontrario, podemos insertar el ar
o pro
esado
on
ertitud de que este forma parte
del arbol abar
ador mnimo. Esta es la fun
ion del if inmediato a la extra
ion del ar
o
de la
ola de prioridad. Con el algoritmo de Kruskal no podemos ha
er esta
onsidera
ion
porque tree es in
onexo.
7.7.3.5
An
alisis del algoritmo de Prim
El analisis
ompleto del algoritmo de Prim requiere
ontabilizar sus bloques prin
ipales:
hIni
ializar Prim 786ai, hIni
ializar heap 787ai, h
al
ular
arbol abar
ador mnimo segun
Prim 787bi y hDesini
ializar Prim 786
i.
Los bloques hIni
ializar Prim 786ai y hDesini
ializar Prim 786
i efe
tuan barridos
sobre los nodos del grafo. Ambos son, por tanto, O(V).
El bloque hIni
ializar heap 787ai re
orre los ar
os del nodo origen. En el muy extremo
peor
aso en el que el nodo este
one
tado al resto de los nodos, hIni
ializar heap 787ai
es O(V).
10
12
10
F
9
8
15
3
H
10
12
10
7
4
10
12
10
2
3
789
8
15
3
H
F
9
10
12
10
15
1
10
12
10
3
I
2
3
2
C
7
4
2
1
1
3
8
15
N
3
F
9
4
A
15
1
H
7
3
I
2
3
2
C
(e)
K
2
3
4
1
3
5
4
7
4
2
C
K
2
(d)
N
3
7
4
(
)
8
(b)
3
N
3
2
C
(a)
3
I
7
B
2
D
7
4
2
1
5
4
K
2
10
12
10
F
9
4
A
8
15
1
3
3
H
7
B
2
C
2
D
P
7
4
2
1
2
3
5
4
K
2
(f)
Figura 7.40: Progreso del algoritmo de Prim (
ada gura
orresponde a tres itera
iones)
Los tres bloques anteriores son, en
onjunto, O(V).
En
uanto al bloque h
al
ular arbol abar
ador mnimo segun Prim 787bi, este es un
po
o mas sutil de analizar porque la
antidad de itera
iones del while depende de lo que
su
eda
on el for interno, el
ual, a su vez, tampo
o tiene una
antidad de repeti
iones
exa
ta. Si examinamos
on
uidado el bloque, enton
es podemos realizar que debemos de
indagar dos
antidades:
1. El numero de ve
es que se a
ede el heap ex
lusivo, el
ual nos propor
iona la
omplejidad del while
ombinado
on el for.
Bajo el supuesto de que el grafo es
onexo, es
laro que todos los nodos deben ser
mirados por el algoritmo, pues este se ini
ia desde el primer ar
o del nodo origen y
ulmina hasta que todos los nodos esten abar
ados, lo
ual es la
ondi
ion de parada
del while. Cada nuevo nodo inspe
ionado a
arrea su in
lusion en el arbol abar
ador y la
onse
uente inser
ion de todos los ar
os no visitados adya
entes al nodo
re
ien pro
esado. Cuando o
urre la inser
ion del ultimo nodo, ya todos los ar
os del
grafo han sido visitados y,
omo estos son mar
ados, se sa
an del heap una sola vez.
Vemos, pues, que se realizan a lo sumo E inser
iones de ar
o en el heap. En
uanto a
la
antidad de extra
iones, debemos notar que, puesto que tratamos
on un heap ex
lusivo, este sa
a ar
os durante la inser
ion en un
oste O(1). O
urren, enton
es, a lo
15
15 Re ordemos
que en este algoritmo el arbol abar
ador siempre es
onexo, lo que asegura que no se
reara un
i
lo
uando se inserte en el arbol un nodo no visitado.
790
Captulo 7. Grafos
sumo V extra
iones de ar
os; lo que nos arroja una
omplejidad de O(V +E) = O(E)
para la
antidad de repeti
iones que realizan el while y el for
ombinados.
Si en lugar de un heap ex
lusivo tratasemos
on un heap tradi
ional, enton
es realizaramos E extra
iones; tendramos un algoritmo mas lento pero
on la misma
omplejidad iterativa.
2. El tama~no maximo o promedio del heap, el
ual nos propor
iona el
oste de operar
on el. Puesto que usamos un heap ex
lusivo, es seguro, de lo que aprehendimos
en la deni
ion 7.7 (x 7.7.3.2 (pagina 781)), que su tama~no no ex
ede de V 1
elementos, lo que nos arroja un
oste de opera
ion de O(lg V) por
ada la inser
ion
o elimina
ion.
El
oste de una opera
ion sobre el heap ex
lusivo es, pues, O(lg V).
De las
antidades re
ientemente
al
uladas, podemos
on
luir que el
oste del bloque
h
al
ular
arbol abar
ador mnimo segun Prim 787bi es O(E) O(lg V) = O(E lg V).
Los
uatro bloques involu
rados
ontabilizan O(V) + O(E lg V) = O(E lg V), que es
el desempe~no denitivo en tiempo del algoritmo de Prim.
Es importantsimo desta
ar que si usasemos un heap tradi
ional para guardar los ar
os,
enton
es el
oste de una opera
ion sobre el heap estara a
otado por O(lg E), el
ual a
otara
al algoritmo de Prim a un desempe~no de O(E lg E), un
oste substan
ialmente mayor al
que nos propor
iona el heap ex
lusivo. Analogamente, tendramos un
oste en espa
io de
O(E) versus el O(V). El uso de un heap ex
lusivo se re
ompensa
on
re
es, tanto en
tiempo
omo en espa
io.
El algoritmo de Prim es el metodo de preferen
ia para grafos densos.
7.7.3.6
Proposici
on 7.4 Sea G un grafo
onexo
ualquiera. Enton
es el algoritmo de Prim en-
Demostraci
on (por inducci
on sobre el n
umero de nodos)
1. V < 2: En este
aso, el heap retorna el menor ar
o del nodo ini
ial y
onstruye un
arbol par
ial de dos nodos el
ual es,
on
ertitud mnimo.
2. V > 2: ahora asumimos la proposi
ion
ierta para un arbol abar
ador mnimo par
ial.
Una observa
ion
ru
ial para esta demostra
ion es aprehender que el heap siempre
nos da el menor ar
o de entre los restantes (lo mismo o
urre
on el algoritmo de
Kruskal). Cuando se extrae un menor ar
o, existen dos posibilidades:
(a) Que el ar
o forme un
i
lo, en
uyo
aso, por la propiedad del
i
lo, este es el
mayor entre todos los ar
os que
onforman el
i
lo y, por la hipotesis indu
tiva,
el arbol abar
ador es mnimo.
(b) Que el ar
o no forme un
i
lo, en
uyo
aso, planteando un
orte entre el
arbol abar
ador par
ial y un grafo de un solo nodo, el ar
o es el mnimo entre
los posibles de
ru
e (fue extrado del heap) y,
omo lo sabemos de la propiedad
de
ru
e, el nuevo arbol es mnimo.
791
7.8
Caminos mnimos
16 Rutinas
792
Captulo 7. Grafos
y, posiblemente fsi
o, del
ual debemos tener espe
ial
uidado. Se trata de los
i
los
negativos. Son absurdos porque su existen
ia en el grafo plantea un
i
lo que innitamente
re
orrido sera mnimo.
7.8.1
792
Algoritmo de Dijkstra
En 1959, Edsger W. Dijkstra, posiblemente uno de los
ient
os en
ien
ias
omputa
ionales mas grandiosos de todos los tiempos, hizo publi
o un algoritmo por el des
ubierto
para en
ontrar todos los
aminos mas
ortos entre un nodo de ini
io y el resto de los del
grafo.
El algoritmo de Dijkstra puede emplearse para grafos o digrafos. Es bastante ade
uado
para grafos eu
lidianos.
Todo lo pertinente al
al
ulo de
aminos mnimos segun el algoritmo de Dijkstra subya
e en el ar
hivo hDijkstra.H 792i, el
ual se estru
tura de la siguiente manera:
hDijkstra.H 792i
on de ma
ros para algoritmo de Dijkstra 794
i
hDeni
i
hInforma
i
on
hInforma
i
on
hClase
hClase
hClase
hClase
hCompara
i
on
hPrototipo
hIndeni
i
on
793
Si d(e) designa la distan
ia (o peso) del ar
o e, enton
es, dado un nodo vi:
Pot(e) = A
(vi) + d(e)
(7.5)
Esta opera
ion se realiza para
ada ar
o de un nodo de transito durante la eje
u
ion
del algoritmo.
Ini
ialmente, para el nodo de ini
io v0, e adya
ente a v0, Pot(e) = d(e), pues la
distan
ia a
umulada desde v0 es nula (A
(v0) = 0).
El algoritmo de Dijkstra utiliza un heap ex
lusivo que guarda los poten
iales
de los ar
os que el algoritmo ha visto. Por su
ara
ter ex
lusivo estudiado
en x 7.7.3.2 (pagina 781), las opera
iones sobre el heap no ex
eden de O(lg V) en
tiempo y de O(V) en espa
io.
793
Grosso modo, denido un heap ex
lusivo segun poten
iales de ar
os, el algoritmo de Dijkstra es estru
turalmente igual al de Prim. Sin embargo, el algoritmo de Dijkstra requiere
un po
o mas de
uidado, pues maneja informa
ion adi
ional tanto para los nodos
omo
para los ar
os. Por esa razon
onsagraremos las siguientes dos sub-se
iones a espe
i
ar
todo lo pertinente a la distan
ia a
umulada y el poten
ial.
El prototipo de nuestra implementa
ion del algoritmo de Dijkstra se dene del siguiente
modo:
hPrototipo del algoritmo de Dijkstra 793i
(792) 799b
template <class GT, class Distance, class Compare, class Plus> inline
void dijkstra_min_spanning_tree(GT &
g,
typename GT::Node * start_node,
GT &
tree)
{
halgoritmo de Dijkstra 797bi
}
Denes:
dijkstra min spanning tree, never used.
g es el grafo (o digrafo) sobre el
ual se desea
al
ular un arbol abar
ador de distan
ias
mnimas. start node es el nodo ini
ial, el
ual puede
onsiderarse raz del arbol abar
ador. Finalmente, tree es el arbol abar
ador de
aminos mnimos
on ini
io (o raz) en
start node. Luego de la eje
u
ion, g y tree estan mapeados mediante sus
ookies.
En
uanto a los tipos parametrizados, GT es el tipo del grafo y del arbol abar
ador
de distan
ias mnimas. Distance es una
lase que a
ede a la distan
ia alma
enada en el
ar
o segun la ndole
on que se dena el peso. Compare es una
lase de
ompara
ion entre
tipos typename Distance::Distance Type. Por ultimo, Plus es una
lase que realiza la
7.8.1.1
La distan
ia a
umulada puede guardarse mediante el
ookie del nodo. Puesto que el sistema
algebrai
o
on que se representen las distan
ias no ne
esariamente podra ser numeri
o o podra tratarse de una representa
ion numeri
a no tradi
ional; aritmeti
a arbitraria,
por ejemplo-, debemos apartar un bloque de memoria en donde alma
enar el a
umulado
794
794a
Captulo 7. Grafos
A
(vi). Guardaremos, pues, por
ada nodo, la informa
ion denida por el siguiente registro:
hInforma
i
on por nodo en Dijkstra 794ai
(792) 794b
template <class GT, class Distance>
struct Dijkstra_Node_Info
{
typename GT::Node * tree_node; // Imagen del nodo en el
arbol abarcador
typename Distance::Distance_Type dist; // distancia acumulada
void *
heap_node;
Dijkstra_Node_Info()
: tree_node(NULL), dist(Distance::Zero_Distance), heap_node(NULL)
{
// empty
}
};
Denes:
Dijkstra Node Info, used in
hunks 794 and 795.
tree node es,
omo su
omentario lo indi
a, la imagen del nodo en el arbol abar
ador
de
aminos mnimos. dist es el a
umulado A
(vi) desde start node. heap node es un
794b
puntero a un nodo dentro del heap ex
lusivo ordenado por poten
iales.
En
onsona
on la interfaz de ArcHeap, debemos espe
i
ar la
lase de a
eso al
ampo
heap node:
hInforma
i
on por nodo en Dijkstra 794ai+
(792) 794a
template <class GT, class Distance, class Compare>
struct Dijkstra_Heap_Info
{
typedef
typename ArcHeap<GT, Distance, Compare, Dijkstra_Heap_Info>::Node Node;
Node *& operator () (typename GT::Node * p)
{
void *& heap_node = HEAPNODE(p);
return (Node*&) heap_node;
}
};
Denes:
Dijkstra Heap Info, used in
hunk 796e.
Uses ArcHeap 781b and HEAPNODE 785b.
794
Para ha
er mas sen
illa la manipula
ion de los
ampos anteriores, denimos los siguientes ma
ros:
hDeni
i
on de ma
ros para algoritmo de Dijkstra 794
i
(792)
// conversi
on de cookie a Node_Info
# define DNI(p) (static_cast<Dijkstra_Node_Info<GT, Distance>*>(NODE_COOKIE((p))))
795
795a
795b
hIndeni
i
on de ma
ros para algoritmo
# undef DNI
# undef TREENODE
# undef ACC
# undef HEAPNODE
Uses DNI 794
and HEAPNODE 785b.
de Dijkstra 795ai
(792)
El tipo List Graph<Node, Arc> dispone de toda la maquinaria para ini
ializar y
des-ini
ializar los nodos. Segun ello, denimos la ini
ializa
ion de la siguiente manera:
hClase apartadora de informa
i
on para nodo 795bi
(792)
template <class GT, class Distance>
struct Initialize_Dijkstra_Node
{
void operator () (GT & g, typename GT::Node * p)
{
g.reset_bit(p, Aleph::Dijkstra);
795
(792)
template <class GT, class Distance>
struct Destroy_Dijkstra_Node
{
void operator () (GT &, typename GT::Node * p)
{
Dijkstra_Node_Info <GT, Distance> * aux = DNI(p); // guardar bloque liberar
GT::map_nodes (p, TREENODE(p));
Puesto que el
ookie se utiliza para guardar estado temporal de
al
ulo, este queda
invalidado, durante el
al
ulo, para guardar la imagen del nodo en el arbol abar
ador. Por
796
796a
Captulo 7. Grafos
esa razon, el mapeo del nodo se realiza al nal del
al
ulo, durante la libera
ion de la
informa
ion Dijkstra Node Info. Lo mismo o
urrira
on los ar
os y el poten
ial.
Denidas estas
lases, la ini
ializa
ion y des-ini
ializa
ion se realizan, a traves de la
maquinaria de List Graph<Node, Arc>, de la siguientes formas:
hApartar memoria para los nodos 796ai
(797a)
g.template operate_on_nodes <Initialize_Dijkstra_Node <GT, Distance> > ();
Uses operate on nodes 661
.
796b
En ambos bloques g es el grafo sobre el
ual se desea
al
ular los
aminos mas
ortos.
7.8.1.2
796
796d
El manejo de esta estru
tura mediante ma
ros, su ini
ializa
ion y libera
ion se realiza
de manera similar a
on los nodos.
Antes de denir el heap debemos espe
i
ar la manera en que se
omparan los poten
iales:
hCompara
i
on entre poten
iales 796di
(792)
template <class GT, class Compare, class Distance>
struct Compare_Arc_Data
{
bool operator () (typename GT::Arc * a1, typename GT::Arc * a2) const
{
return Compare() (POT(a1), POT(a2));
}
};
Denes:
Compare Arc Data, never used.
Uses POT.
796e
(797b 799b)
typedef Dijkstra_Heap_Info<GT, Distance, Compare> Acc_Heap;
ArcHeap<GT, Distance, Compare, Acc_Heap> heap;
Uses ArcHeap 781b and Dijkstra Heap Info 794b.
7.8.1.3
797a
797
El algoritmo de Dijkstra
El algoritmo tiene dos fases: una de ini
ializa
ion otra de
al
ulo. La ini
ializa
ion aparta
la memoria adi
ional para la distan
ia a
umulada y los poten
iales:
hIni
ializar algoritmo de Dijkstra 797ai
(797b 799b)
tree.clear_graph(); // limpiar
arbol abarcador destino
hApartar
hApartar
797b
Con el heap y el manejo de la memoria debemos ser muy
uidadosos. El heap guarda
ar
os, los
uales a su vez mantienen los poten
iales apartados en hApartar memoria para
los ar
os (never dened)i. En este sentido, es muy importante que el heap se destruya antes
de la libera
ion de memoria eje
utada en el bloque hLiberar memoria para los ar
os (never
dened)i. Si o
urre al rev
es, enton
es el heap referen
iara
ookies que ya fueron liberados.
Para asegurar esto,
rearemos un bloque interno donde se de
larara el heap. De ese modo,
la estru
tura general del algoritmo de Dijkstra queda
omo sigue:
halgoritmo de Dijkstra 797bi
(793)
hIni
ializar algoritmo de Dijkstra 797ai
{
hCal
ular
arbol abar
ador de Dijkstra 798ai
} // aqu
se destruye el heap
hLiberar
hLiberar
797
798
Captulo 7. Grafos
POT(arc) = ARC_DIST(arc);
heap.put_arc(arc, it.get_tgt_node());
}
Uses ARC DIST, has current 103, Node Arc Iterator 656a, POT, put arc 783, and set bit 664a.
Una vez que los poten iales ini iales estan en el heap y el valor de distan ia nula en
798a
start node, se ini
ia la itera
ion para
al
ular el arbol abar
ador de Dijkstra,
uyas ramas
son
aminos mnimos desde la raz start node:
hCal
ular
arbol abar
ador de Dijkstra 798ai
(797b)
// mientras tree no abarque a g
while (tree.get_num_nodes() < g.get_num_nodes())
{
oximo ar
o
on menor poten
ial 798bi
hObtener y pro
esar pr
}
hA tualizar
El bloque repetitivo
esa
uando tree abar
a a g, lo
ual se dete
ta
uando tree al
anza
la misma
antidad de nodos. Al igual que el algoritmo de Prim, el de Dijkstra mantiene
a tree
onexo, lo que permite dete
tar
i
los por simple mar
ado de los nodos visitados,
sin ne
esidad de una prueba de a
i
li
idad. Por modularidad, es muy
onveniente dividir
el
al
ulo interno del lazo en dos partes estru
turalmente identi
as a la del re
orrido en
amplitud y el algoritmo de Prim:
1.
798b
799
2.
799a
Uses ARC DIST, has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d,
Node Arc Iterator 656a, POT, put arc 783, and set bit 664a.
799b
C
alculo de un camino mnimo
Como ya lo hemos se~nalado, el algoritmo anterior
al
ula un arbol abar
ador
on raz en
el nodo origen y
uyas ramas representan
aminos mnimos desde el origen a
ualquier
otro nodo. Si lo que se pretende es
ono
er un
amino mnimo entre dos nodos, enton
es
podemos ahorrar tiempo de eje
u
ion si detenemos el
al
ulo del arbol abar
ador una vez
que se al
an
e el nodo destino. Esto lo podemos plantear de la siguiente manera:
hPrototipo del algoritmo de Dijkstra 793i+
(792) 793
template <class GT, class Distance, class Compare, class Plus> inline
800
Captulo 7. Grafos
10
12
10
F
9
8
15
3
H
10
12
10
7
4
10
12
10
2
3
8
15
3
H
F
9
10
12
10
15
1
10
12
10
3
I
2
3
2
C
7
4
2
1
1
3
8
15
N
3
F
9
4
A
15
1
H
7
4
3
I
2
3
2
C
K
2
3
4
1
3
5
4
7
4
2
C
K
2
(d)
N
3
7
4
(
)
8
(b)
3
N
3
2
C
(a)
3
I
7
B
2
D
7
4
2
1
5
4
10
12
10
2
E
4
A
8
15
1
3
3
H
7
B
(e)
2
C
2
D
P
7
4
2
1
2
3
5
4
K
2
(f)
Figura 7.41: Progreso del algoritmo de Dijkstra
on raz en el nodo a (
ada gura
orresponde a tres itera
iones)
void dijkstra_min_path(GT &
g,
typename GT::Node * start_node,
typename GT::Node * end_node,
Path<GT> &
min_path)
{
GT tree; //
arbol abarcador temporal
hIni
ializar
{
on
hDe
lara
i
hMeter
hCal ular
hBus ar
hLiberar
hLiberar
801
Denes:
801a
La primera parte del algoritmo es muy similar al bloque halgoritmo de Dijkstra 797bi.
La uni
a diferen
ia esta dada por el bloque hCal
ular arbol abar
ador de Dijkstra par
ial 801ai
uya estru
tura es pare
ida al bloque hCal
ular arbol abar
ador de Dijkstra 798ai, salvo que, para ahorrar tiempo de eje
u
ion, el
al
ulo del arbol abar
ador se
detiene
uando se mira el nodo destino end node, o sea,
uando gtgt == end node:
hCal
ular
arbol abar
ador de Dijkstra par
ial 801ai
(799b)
// mientras tree no abarque a g
while (tree.get_num_nodes() < g.get_num_nodes())
{
hObtener y pro
esar pr
oximo ar
o
on menor poten
ial 798bi
801b
hA tualizar
Al termino de este bloque, tree
ontiene un arbol abar
ador, probablemente par
ial, que
ontiene un
amino mnimo desde start node hasta end node. Lo primero que ha
emos,
pues, es en
ontrar ese
amino mnimo:
hBus
ar en tree
amino entre start node y end node 801bi
(799b) 801
Path<GT> tree_min_path(tree, TREENODE(start_node));
find_path_depth_first(tree, TREENODE(start_node), TREENODE(end_node),
tree_min_path);
Uses find path depth first 713 and Path 690b.
801
tree min path
ontiene el
amino mnimo en tree, pero lo que deseamos es el
amino
mnimo en g]. As pues, la siguiente fase
consiste en copiar, siguiendo el mapeo, el camino [[tree min path al parametro
min path:
hBus
ar en tree
amino entre start node y end node 801bi+
(799b) 801b
min_path.clear_path();
min_path.init(start_node);
typename Path<GT>::Iterator it(tree_min_path);
for (it.next(); it.has_current(); it.next())
{
typename GT::Node * node = it.get_current_node();
min_path.append(GRAPHNODE(node));
}
7.8.1.5
La primera
osa que debemos aprehender del algoritmo de Dijkstra es que este no opera
orre
tamente para ar
os negativos. El algoritmo, que opera sobre el arbol abar
ador
onexo, asume que la distan
ia total desde el nodo origen no de
re
e; esta premisa es
802
Captulo 7. Grafos
2
3
0
-2
2
1. V = 2: En este
aso, el arbol abar
ador desde el nodo origen tiene un solo ar
o el
ual es el mnimo segun el algoritmo. En este
aso, a / E es una
ontradi
ion, pues
el heap garantiza que el primer ar
o que se pro
esa es el mnimo adya
ente de v.
2. Ahora suponemos que la proposi
ion es
ierta para todo V n y
onstatamos si lo
es para V = n + 1.
Sea v V el nodo origen del arbol abar
ador y w un nodo destino que en la itera
ion
n no se en
uentra en el arbol abar
ador T . Si asumimos que w es el u
ltimo nodo a
in
luir en T , enton
es, luego de la itera
ion n + 1, asumamos que a / E | a es
mnimo entre v y w.
Si w es el ultimo nodo a in
luir en T , enton
es el heap
ontiene ar
os que, o
onforman
i
los o
one
tan a w. Los ar
os que
onforman
i
los no solo no pertene
en a T ,
sino que no pueden ser mnimos pues un
i
lo aumenta el
oste. As pues, solo nos
interesan los ar
os en el heap del tipo x w que
one
tan a un nodo x V
on w.
Puesto que el heap esta ordenado por los poten
iales desde w hasta x, el algoritmo de
Dijkstra sa
ara el ar
o
on menor
oste total desde v. Ahora bien, la existen
ia de un
ar
o mnimo a / E es una
ontradi
ion, pues, el ultimo ar
o x w
orresponde
a un
oste total mnimo entre v y w
7.8.1.6
803
An
alisis del algoritmo de Dijkstra
El analisis es pra
ti
amente similar al del algoritmo de Prim, pues las estru
turas de ambos
algoritmos son las mismas.
Los bloques de ini
ializa
ion hApartar memoria para los nodos 796ai y hApartar
memoria para los ar
os (never dened)i
onsumen O(V)+O(E), lo
ual, puesto que el grafo
es
onexo, puede
onsiderarse O(E). Lo mismo o
urre para los bloques nales hLiberar
memoria para los ar
os (never dened)i y hLiberar memoria para los nodos 796bi.
As pues, el analisis se
entra en
ontabilizar las itera
iones del bloque hCal
ular
arbol abar
ador de Dijkstra 798ai,
uyo analisis es identi
o al del algoritmo de Prim,
pues la estru
tura es la misma y el algoritmo de Dijkstra tambien usa el heap ex
lusivo.
El bloque hCal
ular arbol abar
ador de Dijkstra 798ai tiene una
omplejidad en tiempo
de O(E lg V).
7.8.2
803a
Algoritmo de Floyd-Warshall
El algoritmo de Floyd-Warshall
al
ula todos los
aminos mnimos entre pares de nodos.
Es una variante del algoritmo de Warshall para
al
ular la
lausura transitiva estudiado
en x 7.6.5 (pagina 769). Como tal, opera sobre matri
es de adya
en
ia y posee la virtud
de operar
on pesos negativos, pero es in
apaz de dete
tar los absurdos
i
los negativos.
El algoritmo fue reportado des
ubierto por primera vez por Floyd [3, pero, puesto que
estru
turalmente es similar al de Warshall, suele llamarsele de \Floyd-Warshall".
La espe
i
a
ion del algoritmo se en
uentra en el ar
hivo hFloyd.H 803ai,
uya espe
i
a
ion general es
omo sigue:
hFloyd.H 803ai
hInterfa
es del algoritmo de Floyd-Warshall 803bi
hIni
ializa
i
on
hRutinas
7.8.2.1
de matri es 805 i
Interfaces
Como ya lo hemos di
ho, el algoritmo de Floyd-Warshall opera sobre matri
es de adya
en
ia. A efe
tos de fa
ilitar la
ompresion, antes de desarrollar el algoritmo, nos
onviene
mostrar y expli
ar las interfa
es de las primitivas pertinentes:
1.
803b
804
Captulo 7. Grafos
(b) Distance: es la
lase que maneja las distan
ias. Minimalmente, este debe exportar los atributos siguientes expli
ados en x 7.7.1 (pagina 774):
i. Distance Type. ar
os.
ii. Max Distance.
iii. Zero Distance
iv. operator () (typename GT::Arc *).
(
) Compare:
lase de
ompara
ion entre distan
ias.
(d) Plus:
lase de suma entre distan
ias.
Los parametros de la rutina son:
(a) g: el grafo o digrafo representado mediante una variante de List Graph<Node, Arc>.
(b) dist: matriz de
ostes mnimos entre pares de nodos. Cada entrada disti,j
ontiene el
oste mnimo entre los nodos de ndi
es en la matriz de adya
en
ia i y
j respe
tivamente.
(
) path: matriz de re
upera
ion de
aminos. Cada entrada pathi,j
ontiene el nodo
k que pasa por el
amino mnimo entre i y j. Inspe
iones su
esivas de pathk,j
hasta que pathk,j = j permiten re
uperar sin busqueda el
amino mnimo entre
el par < i, j >.
804a
(803a)
template <class GT, typename Distance, class Compare, class Plus>
void floyd_all_shortest_paths
(GT &
g,
Ady_Mat<GT, typename Distance::Distance_Type> & dist,
Ady_Mat<GT, long> &
path)
{
hIni
ializar algoritmo de Floyd-Warshall 806i
hAlgoritmo
}
Denes:
de Floyd 808i
2.
804b
805
Esta version maneja por omision la rela
ion \menor que" y la suma tradi
ional.
3.
805a
hRe
upera
i
on de
aminos 805ai
(811b)
template <class Mat>
void find_min_path(Mat &
const long &
const long &
Path<typename Mat::List_Graph_Type> &
Uses find min path 811a and Path 690b.
805b
p,
src_index,
tgt_index,
path);
Dada una matriz de
aminos mnimos
al
ulada mediante floyd all shortest paths(),
la rutina
onstruye un
amino mnimo entre los pares de nodos
on ndi
es src index y
tgt index.
4.
805b
hRe
upera
i
on de
aminos 805ai+
(811b) 805a
template <class Mat>
void find_min_path(Mat &
typename Mat::Node *
typename Mat::Node *
Path<typename Mat::List_Graph_Type> &
{
const long src_index = p(src_node);
const long tgt_index = p(tgt_node);
811a
p,
src_node,
tgt_node,
path)
Similar a la anterior, salvo que los nodos se espe
i
an dire
tamente mediante punteros
en la representa
ion
on listas enlazadas.
7.8.2.2
El algoritmo de Floyd-Warshall
La estru
tura del algoritmo es similar al del algoritmo de Warshall: tres lazos que exploran,
omparan y sele
ionan los
aminos de longitud 1, 2 hasta
ubrir el numero de nodos.
La idea basi
a
onsiste en
onsiderar nodos intermedios de
aminos mas
ortos bajo
el mismo prin
ipio del de Warshall. Para ello, el algoritmo utiliza una matriz disti,j de
distan
ias n n
uyos valores ini
iales obede
en a lo siguiente:
dist0i,j =
si no existe ar
o entre i y j
si i = j
Dado un grafo, esta ini
ializa
ion la realizamos mediante la siguiente
lase:
hIni
ializa
i
on de matri
es 805
i
(803a)
template <class AM, class Distance>
struct Initialize_Dist_Floyd
(7.6)
806
Captulo 7. Grafos
{
typedef typename AM::List_Graph_Type::Arc_Type Arc_Type;
typedef typename AM::List_Graph_Type GT;
typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
typedef typename Distance::Distance_Type Distance_Type;
// inicializaci
on de cada entrada de la matriz
void operator () (AM &
mat,
Node *
src,
Node *
tgt,
const long &
i,
const long &
j,
Distance_Type & entry,
void *
p)
{
Ady_Mat <typename AM::List_Graph_Type, long> & path =
* reinterpret_cast <Ady_Mat <typename AM::List_Graph_Type, long> *> (p);
if (i == j) // es diagonal de la matriz?
{
// s
==> la distancia es cero
entry = Distance::Zero_Distance;
path(i, j) = j;
return;
}
GT & g = mat.get_list_graph();
// buscar arco en la representaci
on con listas enlazadas
Arc * arc = g.search_arc(src, tgt);
if (arc == NULL) // ausencia de arco?
{
// s
==> se coloca coste infinito
entry = Distance::Max_Distance;
return;
}
// existe arco ==> extraiga distancia
entry = Distance () (arc); // la coloca en cost(i, j)
path(i, j) = j; // coloca camino directo
}
};
Denes:
806
Este operador es invo
ado en la fase del ini
ializa
ion del algoritmo de Floyd-Warshall:
hIni
ializar algoritmo de Floyd-Warshall 806i
(804a) 807a
807
807a
lo que deja a las matri
es en los estados ini
iales segun (7.6).
El resto de la fase de ini
ializa
ion es jar algunas
onstantes de manera de fa
ilitar el
trabajo de optimiza
ion del
ompilador:
hIni
ializar algoritmo de Floyd-Warshall 806i+
(804a) 806
const Dist_Type & max = Distance::Max_Distance;
const long & n = g.get_num_nodes();
7.8.2.3
El algoritmo de Floyd-Warshall
La estru
tura del algoritmo de Floyd-Warshall es muy similar al del de Warshall: tres lazos
anidados que exploran
aminos posibles y de
iden el mnimo. En una itera
ion k,
ada
entrada distki,j
ontiene el
oste mnimo para ir del nodo i ha
ia el nodo j
on un ar
o de
longitud k. Pi
tori
amente, en la k-esima itera
ion, la
uestion se puede plantear de la
siguiente manera:
k
distk1
i,k
i
distk1
k,j
distk1
i,j
807b
distk1
i,j
k1
distk1
i,k + distk,j
(7.7)
808
808
Captulo 7. Grafos
B
2
-2
3
3
5
A
-1
-1
F
2
3
4
-1
1
C
-4
D0
A
B
C
D
E
F
G
H
I
2
0
5
1
2
3
2
0
2
0
1
0
1
4
2
0
3
P0
A
B
C
D
E
F
G
H
I
A
A
A
A
A
A
A
A
A
A
B
B
B
A
A
A
A
A
A
A
C
A
A
C
A
A
C
A
A
A
D
A
D
A
D
A
D
D
D
A
E
A
A
E
A
E
E
A
A
A
F
F
F
A
F
A
F
F
A
A
G
A
A
A
A
G
A
G
G
G
H
A
A
A
H
A
A
H
H
H
I
A
A
A
A
I
A
A
A
I
D1
D2
D3
D4
D5
D6
D7
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
809
0
1
2
0
3
5
1
6
2
2
3
2
5
3
6
0
3
1
4
2
0
2
0
1
0
1
4
2
0
3
0
1
2
0
3
5
3
6
0
3
1
4
2
2
3
2
2
3
2
0
2
0
2
0
1
0
1
0
1
4
2
0
3
2
0
3
5
3
6
0
3
1
4
2
9
7
10
4
2
3
2
0
2
0
1
0
0
1
4
2
2
0
3
2
0
3
5
3
6
0
3
1
4
2
9
7
10
4
2
3
2
0
2
0
1
0
2
4
0
1
4
I
A
A
A
A
I
A
A
A
I
A
B
C
D
E
F
G
H
I
A
A
A
A
A
A
A
A
A
A
B
B
B
A
A
A
A
A
A
A
C
A
A
C
A
A
C
A
A
A
D
B
D
A
D
A
D
D
D
A
E
A
A
E
A
E
E
A
A
A
F
B
F
A
F
A
F
F
A
A
G
A
A
A
A
G
A
G
G
G
H
A
A
A
H
A
A
H
H
H
I
A
A
A
A
I
A
A
A
I
P2
A
B
C
D
E
F
G
H
I
A
A
A
A
A
A
C
A
A
A
B
B
B
A
A
A
C
A
A
A
C
A
A
C
A
A
C
A
A
A
D
B
D
A
D
A
D
D
D
A
E
A
A
E
A
E
E
A
A
A
F
B
F
A
F
A
F
F
A
A
G
A
A
A
A
G
A
G
G
G
H
A
A
A
H
A
A
H
H
H
I
A
A
A
A
I
A
A
A
I
P3
A
B
C
D
E
F
G
H
I
A
A
A
A
A
A
C
A
A
A
B
B
B
A
A
A
C
A
A
A
C
A
A
C
A
A
C
A
A
A
D
B
D
A
D
A
D
D
D
A
E
A
A
E
A
E
E
A
A
A
F
B
F
A
F
A
F
F
D
A
G
A
A
A
A
G
A
G
G
G
H
B
D
A
H
A
D
H
H
H
I
A
A
A
A
I
A
A
A
I
P4
A
B
C
D
E
F
G
H
I
A
A
A
A
A
A
C
A
A
A
B
B
B
A
A
A
C
A
A
A
C
A
A
C
A
A
C
A
A
A
D
B
D
A
D
A
D
D
D
A
E
A
A
E
A
E
E
A
A
A
F
B
F
A
F
A
F
F
D
A
G
A
A
E
A
G
E
G
G
G
H
B
D
A
H
A
D
H
H
H
I
A
A
E
A
I
E
A
A
I
A
B
C
D
E
F
G
H
I
A
A
F
A
F
A
C
F
D
A
B
B
B
A
F
A
C
F
D
A
C
B
F
C
F
A
C
F
D
A
D
B
F
A
D
A
D
F
D
A
E
B
F
E
F
E
E
F
D
A
F
B
F
A
F
A
F
F
D
A
G
B
F
E
F
G
E
G
G
G
H
B
F
A
H
A
D
F
H
H
I
B
F
E
F
I
E
F
D
I
2
0
2
2
0
3
P5
0
1
1
2
2
0
3
4
2
0
0
1
1
1
2
0
3
1
4
2
0
1
0
2
1
2
1
2
1
2
3
2
0
1
0
7
5
6
6
2
4
0
1
4
5
3
6
4
5
3
4
4
0
2
1
2
3
1
2
2
2
0
1
0
0
0
1
1
2
1
0
1
2
3
2
0
3
4
3
2
1
0
5
2
0
0
1
0
1
2
3
2
1
1
2
0
1
2
3
4
1
5
3
4
4
0
2
1
0
5
3
1
4
2
1
0
1
2
3
7
5
6
6
2
4
0
1
4
5
3
6
4
3
2
1
0
3
3
1
2
2
2
0
1
2
0
H
A
A
A
H
A
A
H
H
H
G
A
A
A
A
G
A
G
G
G
F
F
F
A
F
A
F
F
A
A
E
A
A
E
A
E
E
A
A
A
D
A
D
A
D
A
D
D
D
A
C
A
A
C
A
A
C
A
A
A
2
0
3
0
1
4
B
B
B
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
2
0
3
P1
A
B
C
D
E
F
G
H
I
2
1
0
3
P6
P7
A
B
C
D
E
F
G
H
I
A
A
F
A
F
G
C
F
G
G
B
B
B
A
F
G
C
F
G
G
C
B
F
C
F
G
C
F
G
G
D
B
F
A
D
G
D
F
G
G
E
B
F
E
F
E
E
F
G
G
F
B
F
A
F
G
F
F
G
G
G
B
F
E
F
G
E
G
G
G
H
B
F
A
H
G
D
F
H
H
I
B
F
E
F
I
E
F
G
I
810
D8
D9
Captulo 7. Grafos
A
B
C
D
E
F
G
H
I
A
B
C
D
E
F
G
H
I
7.8.2.4
0
1
1
2
1
0
1
2
1
2
0
3
4
3
2
1
0
3
2
0
0
1
0
1
2
3
0
1
1
2
0
1
2
3
4
1
5
3
4
4
0
2
1
0
3
3
1
4
2
1
0
1
2
1
4
2
5
3
2
1
0
1
2
5
3
6
4
3
2
1
0
3
3
1
2
2
2
0
1
2
0
0
1
1
2
1
0
1
2
1
2
0
3
4
1
2
1
0
3
2
0
0
1
2
1
2
3
0
1
1
1
0
3
2
3
4
1
5
3
4
4
0
2
1
0
3
3
1
3
2
1
0
1
2
1
4
2
4
3
0
1
0
1
2
5
3
5
4
1
2
1
0
3
3
1
2
2
2
0
1
2
0
P8
P9
A
B
C
D
E
F
G
H
I
A
A
F
A
F
G
C
F
G
H
B
B
B
A
F
G
C
F
G
H
C
B
F
C
F
G
C
F
G
H
D
B
F
A
D
G
D
F
G
H
E
B
F
E
F
E
E
F
G
H
F
B
F
A
F
G
F
F
G
H
G
B
F
A
H
G
D
G
G
H
H
B
F
A
H
G
D
F
H
H
I
B
F
E
F
I
E
F
G
I
A
B
C
D
E
F
G
H
I
A
A
F
A
F
I
C
F
G
H
B
B
B
A
F
I
C
F
G
H
C
B
F
C
F
I
C
F
G
H
D
B
F
E
D
I
D
F
G
H
E
B
F
E
F
E
E
F
G
H
F
B
F
E
F
I
F
F
G
H
G
B
F
E
H
I
D
G
G
H
H
B
F
E
H
I
D
F
H
H
I
B
F
E
F
I
E
F
G
I
An
alisis del algoritmo de Floyd-Warshall
Para estudiar el desempe~no del bloque hIni
ializar algoritmo de Floyd-Warshall 806i,
debemos
onsiderar la estru
tura de la rutina operate all arcs list graph() des
rita
en x 7.6.3 (pagina 766) y veri
ar que esta eje
uta O(V 2) itera
iones, pues en realidad se re
orre toda la matriz. Por
ada itera
ion, se invo
a al operador () de la
lase
Initialize Dist Floyd, la
ual eje
uta una b
usqueda lineal search arc(), la
ual es
2
O(E). La ini
ializa
ion
onsume, enton
es, O(V ) O(E) = O(V 2 E).
El bloque hAlgoritmo de Floyd 808i es O(V 3).
El algoritmo presentado es, enton
es, O(V 2 E), resultado que diere del tradi
ional
O(V 3) aso
iado al algoritmo de Floyd-Warshall. La razon es simple, el analisis tradi
ional
asume que el grafo ya esta
olo
ado en una matriz de adya
en
ia.
7.8.2.5
7.8.2.6
Recuperaci
on de caminos
La matriz disti,j
al
ulada por el algoritmo de Floyd-Warshall
ontiene los
ostes mnimos
entre
ada par de nodos i y j. El n de la matriz pathi,j es guardar el nodo k que permite
al algoritmo de Floyd-Warshall en
ontrar el mnimo valor de disti,j.
17 En
la sub-se ion
811a
811
El pro
edimiento find min path(), que re
upera y
olo
a en path el
amino mas
orto, entre los nodos
uyos ndi
es origen y destino en la matriz de adya
en
ia p son
src index y tgt index, se instrumenta del siguiente modo:
hRe
upera
i
on de
aminos 805ai+
(811b) 805b
template <class Mat>
void find_min_path(Mat &
const long &
const long &
Path<typename Mat::List_Graph_Type> &
{
typedef typename Mat::List_Graph_Type GT;
p,
src_idx,
tgt_idx,
path)
GT & g = p.get_list_graph();
typename GT::Node * src = p(src_idx);
path.set_graph(g, src);
for (int i = src_idx, j; j != tgt_idx; i = j)
{
j = p(i, tgt_idx);
typename GT::Node * tgt = p(j);
path.append(tgt);
}
}
Denes:
811b
Puesto que puede haber otros algoritmos que mantengan matri
es de
aminos similares,
este algoritmo lo in
luimos en el siguiente ar
hivo:
hmat path.H 811bi
# ifndef MAT_PATH_H
# define MAT_PATH_H
# include <tpl_matgraph.H>
hRe
upera
i
on
de aminos
805ai
# endif // MAT_PATH_H
7.8.3
Algoritmo de Bellman-Ford
El algoritmo de Bellman-Ford [1, 4, llamado as en honor a sus primeros des
ubridores
ono
idos , independientes entre s, en
uentra todos los
aminos mnimos desde un nodo
dado. El algoritmo opera
on
i
los negativos y tiene la bondad de permitir dete
ion de
i
los negativos.
18
812
812a
Captulo 7. Grafos
on
hInforma
i
hRutinas
hIndeni
i
on
812b
hAlgoritmo
hDete
i
on
hConstru
i
on
hRutinas
template <class GT, class Distance, class Compare, class Plus> inline
bool q_bellman_ford_min_spanning_tree(GT &
g,
typename GT::Node * start_node,
GT &
tree)
{
on de algoritmo de Bellman-Ford 813ai
hIni
ializa
i
hAlgoritmo
de Bellman-Ford 817i
hDete
i
on
hConstru
i
on
}
Denes:
bellman ford shortest paths() al ula el arbol abar ador de aminos mnimos desde el
813
nodo origen start node segun el algoritmo
lasi
o. Como ya es el habito, el parametro
tipo GT representa el grafo, Distance la
lase extra
tora de los pesos
ontenidos en
los ar
os, Compare la
lase de
ompara
ion entre pesos y Plus la
lase que realiza
la suma entre pesos. Si existe el arbol abar
ador de
aminos mnimo, enton
es la
rutina retorna true; de lo
ontrario, existe un
i
lo negativo y se retorna false. La
version q bellman ford shortest paths() es una version mejorada que usa internamente
una
ola de nodos, lo que a
arrea mayor
onsumo de espa
io pero tiende a a
elerar el substan
ialmente al algoritmo.
7.8.3.1
813a
Inicializaci
on del algoritmo de Bellman-Ford
813b
Del mismo modo,
omo parte de la valida
ion, debemos asegurarnos de que el grafo tree
este va
o:
hIni
ializa
i
on de algoritmo de Bellman-Ford 813ai+
(812b) 813a 813
tree.clear_graph(); // limpiar
arbol abarcador destino
Uses clear graph 685b.
813
El algoritmo utiliza dos arreglos temporales, uno de nodos prede
esores parte de los
aminos mnimos y otro de los ar
os:
hIni
ializa
i
on de algoritmo de Bellman-Ford 813ai+
(812b) 813b 814
DynArray<typename GT::Node*> pred;
DynArray<typename GT::Arc*> arcs;
Denes:
arcs, used in
hunks 673
, 759{61, 814
, 815b, 818b, and 822b.
pred, used in
hunks 814
, 815b, and 818b.
Uses DynArray 45.
813d
Estos arreglos
onforman una representa
ion del arbol abar
ador que nos sera mas ade
uada para manejarlo dinami
amente y que expli
aremos en x 7.8.3.2 (pagina 815).
Por
ada nodo, se mantiene la siguiente informa
ion:
hInforma
i
on por nodo en Bellman-Ford 813di
(812a)
idx es el ndi
e dentro del arreglo preds, lo que permite un a
eso O(1) al nodo prede
esor en el
amino mnimo par
ial. acum es la distan
ia par
ial a
umulada desde el nodo
origen start node,
uyo valor ini
ial es Distance::Zero Distance para el nodo origen,
y Distance::Max Distance para el resto de los nodos.
814
814a
Captulo 7. Grafos
El a
eso a los
ampos de esta estru
tura se endulza mediante los siguientes ma
ros:
hDeni
i
on de ma
ros del algoritmo de Bellman-Ford 814ai
(812a)
# define NI(p) (static_cast<Bellman_Ford_Node_Info<GT, Distance>*>(NODE_COOKIE(p)))
# define IDX(p) (NI(p)->idx)
# define ACU(p) (NI(p)->acum)
Uses Bellman Ford Node Info 813d and NODE COOKIE 670d.
814b
814
hIndeni
i
on de
# undef NI
# undef IDX
# undef ACU
(812a)
A efe
tos de ahorro de tiempo, en lugar de ini
ializar los
ookies de los nodos mediante
el metodo operate on nodes(), lo haremos
on un lazo que permita, en una sola pasada,
ini
ializar el ndi
e idx y los arreglos temporales:
hIni
ializa
i
on de algoritmo de Bellman-Ford 813ai+
(812b) 813
814d
{
814d
El bit Breadth First se utilizara para distinguir nodos que se hayan insertado en una
ola.
Usamos Breadth First porque el algoritmo de Bellman-Ford re
orre el grafo en amplitud,
lo que mostraremos en x 7.8.3.4 (pagina 816).
El uni
o nodo
on valor a
umulado de distan
ia
ono
ido al prin
ipio es start node:
hIni
ializa
i
on de algoritmo de Bellman-Ford 813ai+
(812b) 814
815a
ACU(start_node) = Distance::Zero_Distance;
815a
815
Los ar
os no manejan informa
ion adi
ional. Su ini
ializa
ion solo
onsiste en limpiar
los bits de
ontrol:
hIni
ializa
i
on de algoritmo de Bellman-Ford 813ai+
(812b) 814d
g.reset_bit_arcs(Min);
Uses reset bit arcs 666
.
7.8.3.2
Manejo del
arbol abarcador
En el algoritmo de Bellman-Ford, a diferen
ia del de Dijkstra, las ramas del arbol abar
ador se modi
an durante el
al
ulo. Es por esa razon, prin
ipalmente, que usamos el
arreglo pred para representar el arbol. Antes de
ontinuar nuestro desarrollo del algoritmo,
debemos estudiar
omo se interpreta el arbol abar
ador en terminos del arreglo.
Supongamos un nodo p
uyo ndi
e en el arreglo pred es k. La entrada pred[i] indi
a
que el nodo
orrespondiente al ndi
e i tiene al valor pred[i]
omo nodo prede
esor en
el arbol abar
ador de
aminos mnimos desde el nodo start node.
Por ejemplo, el arbol:
4
815b
El algoritmo gen
erico Bellman-Ford
El prin
ipio fundamental del algoritmo de Bellman-Ford es el \relaja
ion de ar
o". Dado
tgt node, su relaja
i
un ar
o src nodeDistance()(arc)
on
onsiste en veri
ar si es posible
disminuir el a
umulado del nodo destino tgt node de la siguiente manera:
hRelajar ar
o 815bi
(816)
const typename Distance::Distance_Type & dist =
Distance () (arc);
816
Captulo 7. Grafos
pred[v] = u
A
(v) = A
(u) + w
816
La redu
ion
onsiste, enton
es, en tratar de disminuir el peso del nodo tgt node;
notemos que tal disminu
ion solo o
urre si el predi
ado A
(v) = A
(u) + w es
ierto, en
uyo
aso, el arreglo pred es a
tualizado.
Grosso modo, el
al
ulo del arbol abar
ador
onsiste en revisar todos los ar
os y redu
irlos. Si la representa
ion del grafo fuese matri
ial, enton
es esto se realiza del siguiente
modo:
hAlgoritmo gen
eri
o de Bellman-Ford 816i
(812b)
for (int i = 0, n = g.get_num_nodes() - 1; i < n; ++i)
hRelajar
ar o 815bi
De este
odigo podemos notar sin di
ultad que se eje
uta en O(V E) ve
es.
Si
ada nodo aso
ia su propio arreglo pred, enton
es la superposi
ion de los arreglos
onforma la matriz de
aminos path; exa
tamente en el mismo estilo que en el algoritmo
de Floyd-Warshall.
7.8.3.4
817
D
1
1
-1
H
-2
-1
1
1
2
F
0
3
1
2
1
-1
-2
-2 4
-1
-1
-2
3
3
-1
1 5
(d) i=3
-1
1 2
1
2
-2 4
-2
-1
3
3
G
3
-2 4
-1
0
4
-2
(f) i=5
D
2
2
1 2
(g) i=6
-2
-1
G
3
-1
-1
-2
G
3
-1
-1
-2
-1
C
2
-2 4
-1
I
1 2
1
4
E
-1
4
C
-1
-2
1 2
3
1 2
1
-2
2
-1
-1
-1
4
I
(e) i=4
-1
-2
1 2
0
4
-2 4
1 2
1
-1
-1
C
2
4
-2
-2
( ) i=2
-1
-2
1 5
-1
-1
-2
-1
0
4
-2 4
-1
1 2
1
-1
F
3
3
4
(b) i=1
-1
-1
-2
H
-2
1 2
1
-1
1 2
1
2
-1
I
(a) i=0
5
3
-2
-2
-1
-1
2
2
E
1 2
(h) i=7
-2
I
0
C
2
E
1 2
(i) i=8
-2
I
0
Figura 7.44: Progreso del algoritmo de Bellman-Ford on raz en el nodo A. Observemos que el arbol abar ador denitivo permane e invariante a partir del la quinta itera ion (i = 5).
817
relajara. Tambien debemos notar,
omo en efe
to se puede observar en la gura 7.44, que
en las primeras itera
iones pueden haber nodos
uyos a
umulados permane
en invariantes;
tenden
ia que se poten
ia a medida que el grafo es mas grande o mas espar
ido.
Por tanto, una optima
ion al algoritmo
onsiste en pro
esar solo aquellos ar
os
uyos
a
umulados de su nodo destino hayan sidos modi
ados y, a la vez, asegurar que no se
repita el pro
esamiento de un ar
o sin que previamente se hayan pro
esado el resto de
los ar
os. Esta
ondu
ta de
ujo podemos modelizarla mediante la estru
tura de
ujo
destinada para ello: una
ola que alma
ena los nodos
uyo a
umulado sea modi
ado,
uya de
lara
ion es
omo sigue:
hAlgoritmo de Bellman-Ford 817i
(812b) 818b
DynListQueue<typename GT::Node*> q;
put_in_queue <GT> (q, start_node);
818
Captulo 7. Grafos
818a
La
ola
ontiene el primer nodo
uyo a
umulado ha sido modi
ado y es
ono
ido: el nodo
origen. Re
ordemos que el bit Breadth First se ini
ializo
uando se aparto la memoria
para el atributo Bellman Ford Node Info (x 7.8.3.1 (pagina 813)).
La rutina put in queue() en
ola el nodo y le mar
a el bit Breadth First, la
ual es
parte del siguiente tro de rutinas en torno a la
ola:
hRutinas de
ola del algoritmo de Bellman-Ford 818ai
(812b)
template <class GT> inline static
void put_in_queue(DynListQueue<typename GT::Node*> & q, typename GT::Node * p)
{
q.put(p);
NODE_BITS(p).set_bit(Breadth_First, true);
}
template <class GT> inline static
typename GT::Node * get_from_queue(DynListQueue<typename GT::Node*> & q)
{
typename GT::Node * p = q.get();
NODE_BITS(p).set_bit(Breadth_First, false);
return p;
}
template <class GT> inline static
bool is_in_queue(typename GT::Node * p)
{
return IS_NODE_VISITED(p, Breadth_First);
}
Denes:
get from queue, used in
hunk 818b.
is in queue, used in
hunk 818b.
put in queue, used in
hunks 817 and 818b.
Uses DynListQueue 166
, IS NODE VISITED 670d, NODE BITS 670d, and set bit 664a.
818b
Un aspe
to esen
ial en este algoritmo es la
ondi
ion de deten
ion, pues si el grafo
ontiene un
i
lo negativo, enton
es los nodos que lo
onforman son modi
ados indenidamente, lo que a
arreara que el algoritmo entre a un
i
lo innito. Debemos, enton
es,
en
ontrar una manera de detener el algoritmo en
aso de que este
ontenga
i
los negativos.
El numero total de repeti
iones que eje
uta el bloque hAlgoritmo generi
o de
Bellman-Ford 816i es exa
tamente V E ve
es. Esta
antidad es importante porque nos
a
ota la maxima
antidad de ve
es que debera de iterar la version
on
ola, dise~nada para
una representa
ion
on listas de adya
en
ia tal
omo List Digraph<Node, Arc>. De este
modo, el algoritmo mejorado resultante se
onforma
omo sigue:
hAlgoritmo de Bellman-Ford 817i+
(812b) 817
for (int i = 0, n = (g.get_num_nodes() - 1)*g.get_num_arcs();
(not q.is_empty()) and i < n; // cola no vac
a y no m
as de V.E arcos
/* nothing */)
{
typename GT::Node * src = get_from_queue <GT> (q);
819
Uses arcs 813
, get from queue 818a, has current 103, is in queue 818a, Node Arc Iterator 656a,
pred 813
, and put in queue 818a.
820
Captulo 7. Grafos
D
1
H
-2
1
2
-1
3
1
-1
G
1
2
1
-1
-1
-2
1
3
2
1
-2
-1
(a)
2
-2
1
3
(b)
2
1
2
-2
D
1
G
1
-1
-2
-2
1
-1
-1
-1
-1
G
1
3
3
-2
-1
-1
-2
-1
E
1
G
1
3
3
-1
3
4
-1
A
0
-1
H
-2
1
2
( )
-2
I
1
C
2
E
1
(d)
-2
I
0
821
de los nodos parte del
i
lo siempre seran relajados, por lo que el algoritmo no se
detendra por la
ola va
a. En este
aso, el
ontador i de ar
os visitados al
anzara
el valor (v 1) E, lo que detendra el algoritmo.
7.8.3.5
Detecci
on de ciclos negativos
821
typename
typename
typename
typename
Distance::Distance_Type
Distance::Distance_Type
Distance::Distance_Type
Distance::Distance_Type
& dist
& acum_src
& acum_tgt
sum
=
=
=
=
Distance () (arc);
ACU(src);
ACU(tgt);
Plus ()(acum_src, dist);
Observemos que el bloque opera
orre
tamente para las dos versiones del algoritmo.
Si existen
i
los, enton
es un problema que puede ser importante
onsiste en determinar
uales son. El bloque hDete
ion de
i
los negativos 821i puede extenderse para que,
19 Tradu
i
on
822
Captulo 7. Grafos
en lugar de detenerse
uando se en
uentre el primer nodo
uyo a
umulado sera modi
ado, se guarden los nodos y ar
os en una lista, la
ual sera la base para
onstruir un
subgrafo que nos permita bus
ar y determinar exa
tamente los
i
los.
Otra alternativa para dete
tar
i
los es a~nadir al grafo un nodo \arti
ial" x
one
tado
a
ada nodo
on distan
ia nula; luego, a partir de el, eje
utar el algoritmo de Bellman-Ford.
7.8.3.6
822a
Construcci
on del
arbol abarcador
Si se dete
to un
i
lo, enton
es no tiene sentido
onstruir el arbol abar
ador, pero s
debemos liberar la memoria;
hConstru
i
on arbol abar
ador de algoritmo de Bellman-Ford 822ai
(812b) 822b
if (not ret_val) // hay ciclos negativos?
{
// liberar memoria de los cookies de los nodos
for (typename GT::Node_Iterator it(g); it.has_current(); it.next())
delete NI(it.get_current_node());
return false;
}
822b
Si no hay existen
ia de
i
los negativos, enton
es tiene sentido
onstruir el arbol abar
ador; lo que puede realizarse en una sola pasada re
orriendo el arreglo arcs e insertando
los nodos y ar
os a la vez que son mapeados. Tambien aprove
haremos esta pasada para
liberar la memoria o
upada por los
ookies; empero, aqu tenemos el problema de ambiguedad de determinar si el
ookie alma
ena un mapeo o un puntero a una estru
tura
Bellman Ford Node Info; para esto nos valemos del bit Min: si un nodo esta mar
ado
on Min, enton
es su
ookie mapea al nodo del arbol abar
ador; de lo
ontrario, el nodo no
esta en el arbol abar
ador y el
ookie
ontiene un puntero a un Bellman Ford Node Info;
hConstru
i
on arbol abar
ador de algoritmo de Bellman-Ford 822ai+
(812b) 822a
for (int i = 0; i < g.get_num_nodes(); ++i)
{
typename GT::Arc * garc = arcs[i];
if (garc == NULL)
continue;
typename GT::Node * gsrc = g.get_src_node(garc);
typename GT::Node * tsrc = NULL;
if (IS_NODE_VISITED(gsrc, Min))
tsrc = static_cast<typename GT::Node*>(NODE_COOKIE(gsrc));
else
{
NODE_BITS(gsrc).set_bit(Min, true); // marcar bit
delete NI(gsrc);
tsrc = tree.insert_node(gsrc->get_info());
GT::map_nodes(gsrc, tsrc);
}
823
La gran ventaja de
onstruir el arbol abar
ador es que podemos servirnos de la rutina
find path depth first() estudiada en x 7.5.6.2 (pagina 711) para en
ontrar un
amino
mnimo desde start node y
ualquier otro nodo.
7.8.3.7
An
alisis del algoritmo de Bellman-Ford
Analizar el algoritmo de Bellman-Ford requiere analizar sus
uatros bloque prin
ipales:
hIni
ializa
i
on de algoritmo de Bellman-Ford 813ai, hAlgoritmo generi
o de BellmanFord 816i, hDete
ion de
i
los negativos 821i y hConstru
ion arbol abar
ador de
algoritmo de Bellman-Ford 822ai.
El bloque hIni
ializa
ion de algoritmo de Bellman-Ford 813ai toma las ini
ializa
iones de los nodos y de los ar
os; lo que arroja una
omplejidad de O(V + E).
El bloque hAlgoritmo generi
o de Bellman-Ford 816i itera tal
omo lo miramos
en x 7.8.3.3 (pagina 815), V E) ve
es. Mientras que hDete
ion de
i
los negativos 821i
exa
tamente O(E). Esto impli
a que los dos bloques se pueden aproximar a O(V E).
Finalmente, hConstru
ion arbol abar
ador de algoritmo de Bellman-Ford 822ai
toma O(V) ve
es.
As pues, el algoritmo toma O(V +E)+O(V E)+O(V) = O(V E). La version hAlgoritmo
de Bellman-Ford 817i basada en la
ola tiene la misma
omplejidad, aunado a un
oste
adi
ional de espa
io de O(V) pero, en la pra
ti
a, tiende a ser mu
ho mas e
iente que la
version generi
a.
7.8.3.8
Proposici
on 7.6
20 Basada
20
824
Captulo 7. Grafos
Demostraci
on (por inducci
on sobre la i-
esima iteraci
on)
Sean u V el nodo origen (start node en el algoritmo). Sea v V un nodo
ualquiera.
La hipotesis indu
tiva es que luego de la i-esima itera
ion el valor A
(v) es menor o
igual al
amino mas
orto
ompuesto por i o menos ar
os.
Notemos que la proposi
ion de la premisa es
orre
ta si no existe
amino desde u ha
ia
v, en
uyo
aso el
oste es innito y no hay ar
os.
2. i > 0: Ahora asumimos que la hipotesis indu
tiva es
ierta para todo i y veri
amos
si aun lo es para i + 1. Podemos distinguir dos
asos entre
aminos desde u ha
ia v:
(a) Existe un
amino mnimo p
ompuesto por i o menos ar
os: en este
aso, puesto
que p es mnimo, toda relaja
ion ha
ia v no afe
tara el valor A
(v).
(b) En el
aso
ontrario, existe un
amino p desde u ha
ia v,
ompuesto por i o
menos ar
os, que es el mas
orto posible entre todos los
aminos posibles desde
u ha
ia v de longitud i+1. Este
amino puede estru
turarse del siguiente modo:
i nodos
u
7.8.4
Discusi
on sobre los algoritmos de caminos mnimos
Hay varios
riterios para ata
ar esa pregunta. Uno primero esta determinado por el
he
ho de que el grafo sea o no dirigido. Si se trata de un grafo sin pesos negativos, enton
es,
debido a su mejor rendimiento, el algoritmo de Dijkstra es la es
ogen
ia de fa
to. Los
825
grafos ponderados \naturales" no tienen pesos negativos; por \natural" entendemos que
el grafo modeliza dire
tamente una situa
ion de la vida real y no es una
onse
uen
ia de
alguna transforma
ion matemati
a. Por ejemplo, en
ualquier grafo eu
lidiano, es de
ir,
uno que represente distan
ias eu
lidianas, o en un grafo temporal, o sea uno que modeli
e
dura
iones, el algoritmo de Dijkstra es la mejor op
ion para
al
ular el
amino mnimo
entre un par de nodos.
La existen
ia de pesos negativos des
arta de plano el algoritmo de Dijkstra. En esta
situa
ion, el algoritmo de Bellman-Ford es la preferen
ia, pues no solo exhibe mejor tiempo,
sino que dete
ta
i
los negativos; aunque esto ultimo es mas una
onsidera
ion de valida
ion que de realidad matemati
a, no se paga una
oste signi
ativo por la veri
a
ion.
Planteadas las re
exiones anteriores, nos apare
e, enton
es, la siguiente pregunta:
>
uando usar el algoritmo de Floyd-Warshall? Una primera respuesta
onsiste en de
ir:
uando se requieran
al
ular todos los pares de
aminos mnimos. A tales efe
tos,
onviene
omparar los otros algoritmos presentados para todos lo pares de
aminos mnimos:
Algoritmo
Desempe~no
Espa
io
Dijkstra
O(V E lg V) O(V 2) + O(V)
Floyd
O(V 3)
O(V 2)
Bellman-Ford
O(V E)
O(V 2) + O(V)
Para grafos densos en los que E V 2 el algoritmo de Dijkstra es
ostoso y nos queda el de
Bellman-Ford
omo el uni
o
ompetitivo en tiempo, mas no en espa
io. As las
osas, el
algoritmo de Floyd-Warshall es la es
ogen
ia
uando requiramos
al
ular todos los pares
de
aminos mnimos y el grafo sea denso; mientras que el algoritmo de Bellman-Ford lo es
uando el grafo sea espar
ido.
Pero la pregunta y re
exion previas no tienen sentido sin la pregunta a
er
a de >
uando
se requieren
al
ular todos los pares de
aminos mnimos? Grosso modo, existen dos situa
iones:
uando se requiera disponer del mnimo entre
ualquier par de nodos o
uando se
requiera
ono
er el diametro de una red.
En grafos, el diametro se dene
omo el
amino simple mas largo. La respuesta a
esta interrogante la propor
iona el mas largo
amino entre un par de nodos, dato que
se
orresponde
on el maximo valor de la matriz de
ostes arrojada por el algoritmo de
Floyd-Warshall o por el algoritmo de Bellman-Ford en su version para todos los
aminos
mnimos.
7.9
Notas bibliogr
aficas
\Pre-sen
ia"`signi
a \esen
ia al frente". El termino proviene del prejo \pre", que
onnota \delante" y \esen
ia", termino metafsi
o que, muy grosso modo, designa a un ser
o una
osa; o sea, a eso que se le di
e \ente". \Presen
ia" signi
a, enton
es, lo que esta
delante de un ser, de una
osa, de un ente. De este termino derivan la forma verbal \presentar",
ual signi
a poner al frente y la adjetiva, \presente",
ual atribuye presen
ia en
algun lugar y tiempo espe
os.
Bajo esta lnea de pensamiento, >que signi
ara \re-presentar"? El prejo latino \re"
onnota \repetir", pero el uso
otidiano de re-presentar no signi
a que se repite un
presentar. >Por que, enton
es, se le
onnota una repiten
ia? >que es lo que se repite?
826
Captulo 7. Grafos
Cuando re
ordamos alguna
osa que nos haya sido presentada o, re
ursivamente, representada, no ne
esariamente tenemos al frente la
osa en
uestion y, sin embargo, aprehendemos mu
ho de ella en su ausen
ia , a un punto tal que mu
has ve
es podemos
manejarla dis
ursivamente sin su presen
ia. As la
osas, \representar" signi
a, segun
la primera a
ep
ion del D.R.A.E., \ha
er presente algo
on palabras o guras que la
imagina
ion retiene".
Esta a
laratoria es muy importante para poder enfatizar dos
osas. En primer lugar,
un grafo puede
onformar una representa
ion, gra
amente orientada, de una realidad
on
reta, o sea, algo presente, de la vida real; o una representa
ion de una abstra
ion u
otra representa
ion. En segundo lugar, la a
laratoria nos permite indi
iar que los grafos
son inherentes al
ono
imiento humano y que,
omo tal, estan presentes desde tiempos
inmemoriales; pra
ti
amente desde los albores del ser humano. No sera justo, enton
es,
atribuir autora sobre la idea de grafo, siendo esta parte inmemorial de la
ultura humana.
Ahora bien, sin pretension de demerito, la historia de los grafos se remonta a un
elebre
art
ulo es
rito por el matemati
o suizo Leonard Euler y su estudio sobre un problema
itadino
ono
ido
omo los \Puentes de Konigsberg". Por esta
iudad pasa el ro Pregel
del
ual existen dos islas enmar
adas en el permetro de la
iudad. Desde las riberas ha
ia
las islas y entre ellas se
uentan siete puentes distribuidos de la siguiente manera
21
Los
itadinos se preguntaban si haba alguna manera de re
orrer todos los puentes exa
tamente una vez; es de
ir, sin pasar dos ve
es por el mismo puente.
Si sa
amos la idea de \grafo" del
ontexto es
olar y la pensamos en el
otidiano,
probablemente aprehendamos que su sentido es
ompletamente \natural" al pensamiento
humano. Antes de en
ontrarnos
on los grafos
omo
on
epto, fuera del ambito de la
ien
ia moderna, >quien entre nosotros no ha mirado y apropiado un mapa? >No es un
polgono
on sus verti
es y aristas un grafo?.
Euler modelizo el asunto mediante el siguiente grafo:
, y di
tamino la manera general de resolver el problema hoy
ono
ida bajo el rotulo de
\
i
lo euleriano". Desde enton
es, a Euler lo
onsideran
omo el pre
ursor de la teora de
grafos o, mas justamente, de la topologa.
Los re
orrido arquetpi
os sobre grafos, tal
omo se maneja
on los arboles, fueron
reportados por primera vez por Robert. E. Tarjan [10.
la pena desta
ar que \ausen
ia" proviene del absentia y que el prejo latino \ab" es privativo.
De este modo, \ausen
ia"
onnota \no esen
ia".
21 Vale
7.10. Ejercicios
827
El problema del arbol abar
ador mnimo se
ono
e desde los a~nos 20 del siglo XX.
Boruvka reporto una primera solu
ion en 1926 [2 y, pra
ti
amente, el hoy
ono
ido algoritmo de Prim fue des
ubierto tambien y varias de
adas antes por Jarnik [7. Los arboles
abar
adores no son pues un legado de la investiga
ion de opera
iones.
Un
lasi
o en teora de grafos, aunque ya adole
ente de falta de des
ubrimientos importantes, es el texto de Harary [6. A la fe
ha de esta publi
a
ion, el mejor texto teori
o
sobre grafos es el texto de Jungni
kel [8.
Un ex
elente texto, entre lo teori
o, lo pra
ti
o y los instrumental lo
onstituye el libro
de Gross y Yellen.
En el
riterio de este reda
tor, el mejor texto instrumental (algortmi
o) es el texto de
Alan Gibbons [5.
7.10
Ejercicios
(a)
(b)
(
)
(d)
(e)
(f)
4. Plantee desventajas de los diagramas de sagitales para expresar rela
iones binarias.
Para
ada planteamiento, establez
a la diferen
ia
on un grafo.
5. Dise~ne la primitiva test cycle(), presentada en x 7.5.4 (pagina 704), basada en una
explora
ion en amplitud.
6. Dise~ne la primitiva test cycle(g, node, len) la
ual retorna true si existe un
i
lo en el nodo node
uya longitud sea exa
tamente len ar
os.
828
Captulo 7. Grafos
17. En fun
ion del TAD List Graph<Node, Arc>,
onstruya un algoritmo que
al
ule
el
omplemento de un grafo.
18. Construya una version del metodo sort arcs() que no use un arreglo dinami
o
temporal, sino que ordene dire
tamente la lista de ar
os.
19. En la rutina copy graph() (x 7.3.8.9 (pagina 687)), >
uales son los in
onvenientes
de implantar el mapeo
on una tabla hash?
20. Implante el re
orrido en profundidad para que explore todos los ar
os.
7.10. Ejercicios
829
21. Implante la busqueda en profundidad para la representa
ion
on matri
es de adya
en
ia.
22. Implante el re
orrido en amplitud para que explore todos los ar
os.
23. Implante la busqueda en amplitud para la representa
ion
on matri
es de adya
en
ia.
24. Explique
omo se modeliza un laberinto mediante un grafo. Es
riba un algoritmo
para en
ontrar la salida dado un punto dentro del laberinto.
25. Es
riba un algoritmo que determine el grado de un grafo.
26. Implante un
al
ulo de arbol abar
ador basado en un re
orrido en amplitud. Explique
las diferen
ias del arbol resultante respe
to al algoritmo basado en busqueda en
profundidad desarrollado en x 7.5.8 (pagina 717).
27. Implante una rutina que
onvierta un arbol abar
ador alma
enado en un objeto de
tipo List Graph<Node, Arc> a un arbol de
lase Tree Node<T>. Estudie minu
iosamente
omo realizar la
onversion de los tipos aso
iados a los nodos y ar
os.
28. Dise~ne un algoritmo que
al
ule los puntos de
orte y durante el mismo re
orrido
oloree los
omponentes
onexos aso
iados a los puntos de
orte. (+)
29. Suponga que posee el grafo de
orte (obtenido a traves de map cut graph()) y los
bloques (obtenidos
on llamadas a map subgraph()
on los diferentes
olores dados
por compute cut nodes()). Dise~ne un algoritmo que
al
ule el grafo original sin
apelar a los
ookies.
30. Desarrolle una version del TAD Bit Mat Graph<GT> que maneje estri
tamente bits
y que se fundamente en el tipo DynArray<T>, de manera tal que mu
has entradas
de valor
ero no o
upen memoria.
31. Dado un grafo G y una lista l de nodos, dise~ne un algoritmo
on prototipo;
template <class GT>
void cut(GT & g, const DynDlist<typename GT:Node*> & l, GT & g1, GT & g2);
el
ual retorna un
orte del grafo tal que g1
ontiene los nodos de la lista l y g2 los
restantes.
32. Es
riba el algoritmo de Dijkstra para matri
es de adya
en
ia. Asegurese de que,
omo en el algoritmo de Floyd, se
al
ulen todos los pares de
aminos mas
ortos.
33. Es
riba el algoritmo de Bellman-Ford para matri
es de adya
en
ia.
34. Modique el algoritmo de Floyd-Warshall para dete
tar
i
los negativos. (+)
35. Suponga un grafo
on
i
los negativos, es
riba un algoritmo que identique los
i
los
y
onstruya una lista
aminos
orrespondiente a los
i
los en
uestion. (+)
Ayuda: vea x 7.8.3.5 (pagina 821).
830
Captulo 7. Grafos
36. Como es bien sabido, el algoritmo de Dijkstra no opera para ar
os
on distan
ias
negativas. Una te
ni
a para apli
ar el algoritmo de Dijkstra a un grafo
on distan
ias
negativas
onsistira en bus
ar la mnima distan
ia del grafo y, enton
es sumarle el
negado a todos los ar
os del grafo. De este modo, el grafo solo
ontendra distan
ias
positivas.
Demuestre que esta te
ni
a no es valida y que los
aminos mnimos sobre el grafo
transformado pueden ser distintos a los del grafo original. (+)
37. Segun lo estudiado sobre el heap ex
lusivo en x 7.7.3.2 (pagina 781), dise~ne una
estrategia general de re
orrido en amplitud
uyo
onsumo en espa
io no ex
eda
de O(V). (+)
Bibliografa
[1 Ri
hard Bellman. On a routing problem. Quarterly of Applied Mathemati
s,
16(1):87{90, 1958.
[2 O. Boruvka.
_
O jistem problemu minimalnm. Pra
e Moravske Prrodovede
ke
Spole
nosti, 3:37{58, 1926. In Cze
h.
[3 R. Floyd. Algorithm 97: Shortest path. Commun. ACM, 5(6):345, 1962.
[4 Lestor R. Ford, Jr. and D. R. Fulkerson. Flows in Networks. Prin
eton University
Press, 1962.
[5 Alan Gibbons. Algorithmi
Graph Theory. Cambridge University Press, Cambridge,
1985.
[6 Frank Harary. Graph Theory. Addison-Wesley, Reading, MA, 1969.
[7 V. Jarnk.
Indice
al
ulo de la posi
ion inja, 412
arbol abar
ador de un grafo, 642
desempe~no, 418
arbol binario
elimina
ion por
lave, 417
nodo
ompleto, 345
elimina
ion por posi
ion, 418
nodo in
ompleto, 345
inser
ion por posi
ion, 415
nodo lleno, 345
inser
ion en raz, 414
arbol binario de busqueda
inser
ion por
lave, 412
busqueda, 384
parti
ion, 413
deni
ion, 382
parti
ion por posi
ion, 414
al
ulo de nodos pertene
ientes a un nivel,
sele
ion, 411
311
split, 413
remove all and delete(), 105
union ex
lusiva, 416
estru
tura de datos
a
eso fuera de bloque, 254
orientada ha
ia el
on
epto, 22
a
tuador, 9
orientada ha
ia el
ujo, 22
aleatoriza
ion, 220
orientadas ha
ia los datos, 22
algebra de O, 199
atributos, 472
ubetas, 470
algoritmo
inser
ion, 473
de
ompara
ion de arbol binario, 307
80-20 regla, 260
de destru
ion de arbol binario, 306
de Kruskal, 776{780
ABB
de Prim, 780{791
busqueda, 384
re
ursivo de altura de un arbol binario,
busquedas espe
iales, 387
305
on
atena
ion, 399
re
ursivo de
ardinalidad de un arbol bi
on
atena
ion ex
lusiva, 394
nario, 304
elimina
ion, 396
Algoritmo de Rabin-Karp, 524
inser
ion, 391
Algoritmo de Warshall para la
lausura
inser
ion en raz, 398
transitiva, 769
join, 399
algoritmos aleatorios, 220
join ex
lusivo, 394
altura
parti
ion, 392
de un arbol, 278
prede
esor, 386
de un nodo, 278
split, 392
en Tree Node<T>, 334
su
esor, 386
altura de un arbol, 340
union, 399
altura de un arbol binario, 305
union ex
lusivo, 394
altura negra en un arbol rojo-negro, 590
ABBA
analisis amortizado, 229
union ex
lusiva, 553
analisis
ontable, 233{234
ABBE
831
832
INDICE
INDICE
treap, 564
arboles binarios
al
ulo de la altura, 305
al
ulo de la
ardinalidad, 304
ompara
ion, 307
destru
ion, 306
equivalen
ia, 307
extensiones
al
ulo de la posi
ion inja, 412
inser
ion por
lave, 412
sele
ion, 411
hilados, (312, 318
join, 399
join ex
lusivo, 394
parti
ion, 392
por posi
ion, 414
parti
ion por
lave, 413
re
orridos no re
ursivos, 301
rota
ion, 419
similaridad, 307
split, 392
union, 399
union ex
lusiva, 394
arboles binarios aleatorizados
union ex
lusiva, 553
arboles binarios
on rangos, 409
arboles binarios de busqueda, (382
analisis, (400, 404
rti
a, (400, 404
extendidos, 409
arboles binarios extendido
split, 413
arboles binarios extendidos, (408, )418
elimina
ion por
lave, 417
elimina
ion por posi
ion, 418
inser
ion
por posi
ion, 415
rota
ion, 420
union ex
lusiva, 416
arboles de Fibona
i, 584{587
altura, 585
ardinalidad, 584
arboles equilibrados, 546{549
arboles estati
os optimos, (442, 449
arboles splay, 609{623
analisis, 617{623
833
arboles treaps
analisis, 567
arboles binarios de busqueda, 404
arboles
ompletos
representa
ion en memoria, 345
arbores
en
ia, 277
re
orridos, 320, 331
TAD Tree Node<T>, 331
ar
o in
idente, 640
ar
o paralelo, 642
ar
os de un grafo
implanta
ion, 675
aritmeti
a de polinomios
uso de listas enlazadas, 120
aritmeti
a de punteros, 31
arreglo
en memoria dinami
a, 35
busqueda binaria, 32
busqueda por
lave, 32
al
ulo de dire
ion, 31
on
eptos generales, 30
de bits, 36{43
dinami
o, 44{71
elimina
ion por
lave, 33
en memoria estati
a, 35
en pila, 35
inser
ion por
lave, 33
multidimensional, 71
para representar arboles, 283
asertos, 249
LhashTable<Key>, 472
atributos de
ontrol de nodos y ar
os de un
grafo, 662
automata, 247
AVL
arboles, 568{589
busqueda
de ar
os en grafo, 660
de nodos en grafo, 654
se
uen
ial, 16
busqueda binaria, 32
busqueda de ar
os en un grafo
implementa
ion, 674
busqueda de extremos, 194
busqueda de maximo, 194
busqueda de mnimo, 194
834
INDICE
INDICE
onjuntos
representa
ion de arboles, 279
ontra
ion de tabla hash lineal, 504
onversion
Dlink a estru
tura
ontenida, 100
Slink a estru
tura
ontenida, 83
onversion de un arbol abar
ador a un
Tree Node<T>, 722
orre
titud
busqueda, 237
dete
ion, 237
planteamiento del problema, 236
orresponden
ia entre arboles binarios y mrios, 318
orresponden
ia entre BinNode<Key> y
Tree Node<T>, 338
orresponden
ia entre Tree Node<T> y
BinNode<Key>, 338
oste en espa
io de algoritmos dividir
ombinar, 217
ostumbres de
orre
titud
de
alogo de Holtzmann, 239
Preguntas a
titudinarias de Van Cle
k,
242
rti
a de algoritmos, 185
rti
a del mergesort, 211
rti
a de los arboles binarios de busqueda,
(400, 404
rti
a del qui
ksort, 216
LhashTable<Key>, 470
umplea~nos, 467
835
INDICE
836
enla
e simple, 79
enumera
ion de arboles, 372
equilibrio perfe
to de Wirth, 546
equivalen
ia
de arboles binarios, 307
errores de memoria
a
eso fuera de bloque, 254
fuga, 254
estabilidad
en ordenamiento, 212
mergesort, 212
esta
ionamiento, 466
Estru
tura de datos
problema fundamental, 18{21
evalua
ion de expresiones injas, 138
evalua
ion de expresion suja, 138
expansion de tabla hash lineal, 504
extensiones a los arboles binarios, 408
al
ulo de la posi
ion inja, 412
extensiones a los arboles binarios smbolos
desempe~no, 418
extensiones a los arbol binario smbolos
inser
ion por
lave, 412
extensiones a los ABB
sele
ion, 411
Fibona
i, 148
n, 22
awnder, 246
Floyd-Warshall
aminos mnimos, 803{811
Fortran, 73
fugas de memoria, 254
fun
ion de Fibona
i, 148
fun
ion hash
holgura de dispersion, 515
metodo de division, 517
metodo de multipli
a
ion, 519
fun
iones hash, 515
gdb, 256
generi
o, 18
genera
ion de un nodo, 278
general, 18
grado
de un grafo, 640
grafo
BinHeap<Key>, 360
INDICE
odigos, 421{442
odi
a
ion de texto, 436
de
odi
a
ion, 427
ingreso de fre
uen
ias, 433
optima
ion, 439
indenta
ion
representa
ion de arboles, 281
indu
ion, 23
ingeniera de la programa
ion, 245
ingeniera del software, 243, 245
LhashTable<Key>, 473
inser
ion en arbol binario de busqueda, 391
inser
ion en tabla hash
on resolu
ion de
olisiones por en
adenamiento separado, 473
inser
ion en tabla hash lineal, 510
inser
ion
metodo de ordenamiento, 202, 204
inser
ion en arbol aleatorizado, 551
inser
ion en arbol arbol rojo-negro, (593,
598
inser
ion en raz
arbol binario de busqueda, 398
en binario de busqueda extendido, 414
inser
ion en treap, 563
inser
ion por
lave en arbol binario extendido, 412
inser
ion por posi
ion de arboles binarios
extendidos, 415
inser
ionen raz
en ABB, 398
en ABBE, 414
interfaz de la fun
ion hash, 515
invariantes, 249
Iterador
de ar
os de un grafo
implanta
ion, 674
de ar
os de un nodo de grafo, 680
de nodos de un grafo
implanta
ion, 674
lista doblemente enlazada, 112
sobre Dlink, 101
sobre Dnode<T>, 107
iterador
de ar
os de un nodo de grafo, 655
de nodos sobre un grafo, 655
837
838
INDICE
INDICE
839
re
ursion, 145
pintado de
omponentes
onexos, 745
pivote
sele
ion en el qui
ksort, 219
plantillas, 16
plegado de
lave en fun
ion hash, 516
polimorsmo, 13
de heren
ia, 15
de sobre
arga, 14
polinomios
implanta
ion
on listas enlazadas, 120
suma, 124
Preguntas de Van Cle
k, 242
Prim, 780
analisis, 788
orre
titud, 790
prin
ipio
de Pareto, 260
del 80-20, 260
prin
ipio n-a-n, 22
prioridades impl
itas en treaps, 568
problema de destru
tores virtuales, 108
problema fundamental de estru
turas de
datos, 18
proling, 262
propiedades matemati
as de los arboles,
(340, 381
paradoja
proxy, 66
umplea~nos, 467
prueba de a
i
li
idad, 706
Pareto Vilfredo, 260
prueba de
one
tividad, 701
parti
ion del qui
ksort, 214
prueba de existen
ia de
amino, 709, 769
parti
ion de arboles binarios, 392
punteros y arreglo, 31
parti
ion de ABB, 392
punto de arti
ula
ion, 728
parti
ion de ABBE, 413
punto de
orte, 728
parti
ion por
lave de arboles binarios expintado de
omponentes
onexos, 745
tendidos, 413
puntos de
orte
parti
ion por posi
ion de arboles binarios,
omponentes
onexos, 742
414
mapeo de
omponentes
onexos, 747
paso de eje
u
ion, 187
qui
ksort, 213{229
perlaje, 262
analisis, 216
pila
laves repetidas, 225
representa
iones en memoria, 130
on listas enlazadas, 222
pilas
rti
a, 216
on arreglos, 131
sele
ion del pivote, 219
on listas enlazadas, 134
sin re
ursion, 221
on listas enlazadas dinami
as, 136
triple parti
ion de Dijkstra, 225
llamadas a pro
edimientos, 145
O, 196{207
Algebra
de O, 199
analisis de algoritmos mediante O, 199{
204
errores, 206{207
o, 197
observador, 9
o
ultamiento de informa
ion, 24
, 197
, 197
operador
delete, 36
new, 31, 36
orden
de un arbol, 278
ordenamiento
estabilidad, 212
inser
ion, 202, 204
mez
la, 209{213
sele
ion, 189{193
ordenamiento de ar
os sobre un grafo, 689
Ordenamiento de listas enlazadas, 190
orienta
ion
ha
ia el
on
epto, 22
ha
ia el
ujo, 22
ha
ia los datos, 22
840
INDICE
INDICE
841
Indice de identificadores
access: 58
, 60b, 70, 753e, 756a, 760, 761a
Activation Record: 151a, 151d
advance block index: 58d
advance index: 495a, 496, 498
Ady Mat: 761b, 763b, 763
, 765b, 803b, 804a, 804b, 805
, 806
allocate block: 53b, 62, 63, 69
allocate bucket: 495b, 496
allocate dir: 52
, 55, 56, 59
allocate segment: 52
, 62, 63, 69b
append code: 437, 438a
append list: 95, 114a, 118b
apply: 141
, 143e, 144
, 144d
arc belong to graph: 660
, 675a
ARC COOKIE: 670d, 686
ARC COUNTER: 670d, 744b
ARC DIST: 797
, 799a
ArcHeap: 781b, 784b, 785
, 786e, 794b, 796e
Arc Iterator: 661a, 661b, 666
, 667b, 668b, 675a, 687b, 749b, 775b, 776b, 816, 821
arc list: 672
, 673
, 677a, 682b, 685a, 689d
Arc Node: 651b, 676
, 677b, 678a, 678b, 680
, 682b, 684, 685a
arcs: 673
, 759a, 760, 761a, 813
, 814
, 815b, 818b, 822b
ARCS LIST: 670d
areEquivalents: 307b
areSimilar: 307a
ArrayHeap: 349f
ArrayQueue: 157, 160d, 308
ArrayStack: 131a, 132a, 141b, 141
, 151d, 301, 302, 303, 304a, 612b
AvlNode: 569, 570b
avl stack: 571a, 571
, 572b, 576, 577b, 578b, 579, 582
avl stack empty: 571
, 576, 577b
balance tree: 547
bellman ford min spanning tree: 812b
Bellman Ford Node Info: 813d, 814a, 814
binary search: 32, 33, 34b, 754b
BinHeap: 360
BinHeapNode: 363
BinHeapNode Data: 363
843
844
Captulo .
Indice de identificadores
BinHeapVtl: 360
BinNode: 294a, 389a, 423d, 423e, 424b, 427a, 427b, 427e, 427f, 429b, 429
, 430, 431a,
845
102a, 102
, 103, 105a, 105b, 105
, 106a, 106
, 114a, 115d, 116a, 190b, 191, 192a, 203,
204a, 212, 213a, 222a, 222b, 226b, 228b, 228
, 229, 323
, 323d, 530b, 530d, 530e, 531b,
531
, 532, 535a, 651b, 655b, 656a, 657b, 661a, 672
, 673a, 676
, 677a, 677b, 685b, 690a
dlink lru: 530b, 530
, 530d
846
Captulo .
Indice de identificadores
119b, 120b
Dlist Node: 108b, 110b
Dlist Node Vtl: 108b, 110b
DlistVtl: 110b
DNI: 794
, 795a, 795
Dnode: 106a, 106
, 108b, 112, 119a, 192a, 192b, 193a, 194a, 194b, 195b, 204b, 222
,
226b, 229, 470b, 472
, 533a
do mru: 534a, 535a, 536a
doubleRotateLeft: 575
DOUBLE ROTATE LEFT: 575a, 575b, 575
doubleRotateRight: 575
DOUBLE ROTATE RIGHT: 575a, 575b, 575
draw: 9b, 15
DynArray: 45, 55, 56, 57b, 58b, 58
, 59, 60a, 66b, 67a, 67b, 310a, 383, 446a, 446d, 447
,
448, 504b, 753d, 753e, 754b, 755
, 756a, 759a, 760, 762, 767b, 813
DynArray::swap: 60a
DynDlist: 113a, 113
, 114a, 115
, 115d, 116a, 116b, 118a, 118b, 119b, 120a, 120b, 120
,
123a, 125b, 127
, 128, 194b, 253, 311a, 311b, 692a, 694b, 726a, 726
, 737
, 738a, 739b,
740, 747, 749b
DynLhashTable: 501
DynListQueue: 166
, 702, 714a, 714d, 720, 817, 818a
DynListStack: 136d, 137d, 230
DynMapBinTree: 408b
DynMapTreey: 405
DynSlist: 88b, 89b, 89d
Empty Node: 294a
encode: 437
erase: 9b, 15
eval: 142
exist: 58
, 61a, 70, 749a, 756a, 760, 761a
expand: 508, 510b, 537b
faster heapsort: 358
Figure: 7, 8a, 8b, 12, 15
fill dir to null: 52b, 52
fill seg to null: 52b, 52
find breadth first spanning tree: 720
find depth first spanning tree: 717, 718a
find depth first spanning tree: 719a, 719b
find max: 385
847
126a, 126b, 127b, 127
, 128, 190b, 194a, 195b, 203, 253, 474a, 507b, 510a, 680
, 685b,
694b, 747, 749b
get current arc node: 680
get dir size: 49
get first arc: 659b, 673
, 694b
get first node: 654a, 673
, 694b, 698a, 718a, 738a, 787a
get freq: 425b, 429b
get from queue: 818a, 818b
get graph: 691b
get inorder pos: 412a
get last tree: 331b
get left child: 325b, 326b, 327a, 328b, 331
, 332b, 334, 335a, 337, 338a, 339b
get left sibling: 326a, 328b, 333
get left tree: 331a
get lru entry: 535a, 535b
get min arc: 784a, 787b, 798b
848
Captulo .
Indice de identificadores
get
get
get
get
338a, 339b
195b, 203, 253, 473a, 474a, 500, 507b, 510a, 657a, 661b, 661
, 666
, 667b, 668b, 674,
675a, 681a, 685b, 687b, 695b, 698b, 702, 705a, 705b, 706, 708, 709b, 710, 712, 713, 715,
719a, 720, 724, 726
, 727, 738
, 740, 745a, 745b, 746, 747, 748, 749a, 749b, 757
, 757d,
760, 765b, 775b, 787a, 787b, 797
, 799a, 801
, 814
, 816, 818b, 821, 822a
has cycle: 776b
Hash Cache: 529a, 533b
HashFctType: 485
has sibling: 365a
HEAPNODE: 785b, 785
, 786d, 794b, 794
, 795a
heapsort: 357
Huffman Decoder Engine: 423b, 427a
Huffman Encoder Engine: 423a, 426f
Huffman Node: 424a, 424b, 425b, 429a, 429b, 429
, 433, 435, 436a
huffman node: 425b, 429b, 429
, 432a, 433, 435, 436a
inconnected components: 726a, 726
increase freq: 425b, 435
index array: 755d, 756a, 760, 761a, 765b
index in block: 50b, 60b, 61
, 62, 67b
index in dir: 50b, 60b, 61a, 61
, 62, 63, 64, 67b
index in seg: 50b, 60b, 61a, 61
, 62, 63, 64, 67b
index of node: 754b, 754
, 756b, 757d, 760, 765b, 768b
INIT CLASS BINNODE: 291b, 293, 294b
Initialize Dist Floyd: 805
, 806
Initialize Node Dijkstra: 795b
Init Prim Info: 785d, 786a
inOrderRec: 299b
inorder rec: 299a, 299b
inOrderStack: 303
inOrderThreaded: 316
insert arc: 658d, 682b, 687b, 719b, 720, 727, 748, 749b, 776b, 787b, 798b, 822b
insert by gap: 33
insert by key xt: 412b
849
850
Captulo .
Indice de identificadores
ISRIGHTMOST: 325b
is rightmost: 324
, 325a, 325b, 326a, 331a
ISROOT: 325b
is root: 324
, 325a, 325b, 327b, 328a, 328b, 330, 332a, 332b, 333
isThread: 314a
join: 19, 395, 400a, 400b, 416b, 418
join exclusive: 395, 397a, 617
join exclusive xt: 416b, 417, 418
join preorder: 399
kruskal min spanning tree: 778
LCHILD: 325b
left link: 324b, 326a, 326b, 327b, 329, 331a, 331b
levelOrder: 308
LhashBucket: 470b, 471, 504a
LhashBucketVtl: 470b, 471, 504a
LhashTable: 471, 501, 502a, 502b, 502
, 503b, 529b, 529
LhashTable<Key>::Bucket: 502a, 529
LhashTableVtl: 471
l huffman node: 429a, 429b, 429
, 432a
l index: 349d
LinearHashTable: 504a
LinearHashTableVtl: 504a
LINKNAME TO TYPE: 100, 324a, 530
List Digraph: 650b, 671d, 689a
List Graph: 649, 650b, 655b, 661a, 669
, 670b, 671a, 671b, 671
, 671d, 672a, 672d, 673
,
674, 675a, 675
, 676b, 681a, 681b, 682a, 682b, 684, 685a, 685b, 686, 687b, 688, 689a,
689b, 689
, 689d, 768b
ListQueue: 163a, 165
, 166
ListStack: 134g, 135
, 136d, 137a, 137b, 137
LLINK: 296, 299a, 300, 301, 302, 303, 304b, 305a, 305b, 306, 307a, 307b, 308, 310a, 311a,
317a, 338a, 339b, 343a, 364
, 365b, 366, 367a, 371a, 383, 384b, 385, 386, 387, 388, 391,
393, 395, 397a, 398, 399, 400a, 411a, 411b, 412a, 412b, 413, 414, 415, 416a, 416b, 417,
418, 419, 420, 421, 427f, 429b, 430, 431a, 432a, 448, 546b, 547, 552a, 553b, 554b, 563,
565a, 565b, 566, 570a, 572a, 572b, 575b, 575
, 578b, 579, 592
, 593, 595
, 597a, 598,
601
, 601e, 602a, 604a, 610, 611, 613b, 616b, 617
low: 736, 737b, 740
lower link: 324b, 326b, 329
lru list: 530e, 533
, 534a, 534b, 535a
makePointer: 314
makeThread: 314b
map arcs: 669a, 686, 687b, 719b, 720, 727, 748, 749b, 776b, 787b, 822b
map cut graph: 749b
map nodes: 669a, 686, 687b, 718b, 719b, 720, 727, 748, 749a, 749b, 778a, 778b, 786b,
795
, 822b
mapping table: 687a, 687b
map subgraph: 749a
851
712, 713, 715, 719a, 720, 724, 727, 738
, 740, 745a, 745b, 746, 748, 757d, 760, 765b,
787a, 787b, 797
, 799a, 818b
NODE BITS: 670d, 698b, 702, 705b, 706, 710, 712, 713, 715, 718b, 719b, 720, 727, 738b,
739a, 740, 748, 749a, 778a, 778b, 787a, 787b, 797a, 798b, 814
, 818a, 822b
NODE COOKIE: 670d, 686, 720, 727, 736, 748, 749b, 778a, 778b, 785b, 785d, 794
, 795b,
797a, 814a, 814
, 822b
NODE COUNTER: 670d, 736
Node Iterator: 655b, 657a, 666
, 667b, 668b, 674, 687b, 708, 726
, 749a, 757
, 757d,
760, 765b, 814
, 822a
node list: 672
, 673
, 681b, 685b
Null Value: 759b, 759
, 760, 761a, 762, 763a, 763
, 763d
NUM ARCS: 670d
num blocks: 51b, 51
, 53b, 54b, 55, 56, 59, 60a
num segs: 51b, 52
, 54b, 55, 56, 59, 60a
ODhashTable: 492, 494
OLhashTable: 485
operate all arcs list graph: 764a, 764b, 765b
operate all arcs matrix: 765a, 765b, 806
operate on arcs: 661b, 661
, 670
, 695b
operate on nodes: 657a, 661
, 670a, 695b, 737a, 786a, 786
, 796a, 796b
paint arc: 745a, 746
paint from cut node: 746, 747
paint node: 745a, 745b
paint subgraph: 745a, 745b, 746
paint subgraphs: 747
partition: 213b, 215
, 218, 221, 223, 226a, 227
Path: 690b, 692b, 692
, 692e, 693d, 694b, 711, 712, 713, 714a, 714
, 714d, 715, 799b,
801b, 801
, 805a, 805b, 811a
852
Captulo .
Indice de identificadores
853
remove
remove
remove
remove
remove
remove
remove
remove
317a, 317b, 338a, 339b, 343a, 362b, 364
, 365a, 365b, 366, 367a, 371a, 383, 384b, 385,
386, 387, 388, 391, 393, 395, 397a, 398, 399, 400a, 411a, 411b, 412a, 412b, 413, 414,
415, 416a, 416b, 417, 418, 419, 420, 421, 427f, 429b, 430, 431a, 432a, 448, 546b, 547,
552a, 553b, 554b, 563, 565a, 565b, 566, 570a, 572a, 572b, 575b, 575
, 578b, 579, 592
,
593, 595
, 597a, 597b, 598, 601
, 601e, 602a, 603a, 610, 611, 613b, 616b, 617
rotate: 9b, 15
rotateLeft: 575
ROTATE LEFT: 575a, 575b, 575
rotateRight: 575
ROTATE RIGHT: 575a, 575b, 575
rotate to left: 563, 565b, 597a, 597b, 601e, 603a, 604a, 611, 615
rotate to left xt: 546b
rotate to right: 419, 420, 563, 565b, 597a, 597b, 601e, 603a, 604a, 611, 615
rotate to right xt: 421, 546b
rotation type: 575b, 575
854
Captulo .
Indice de identificadores
RSIBLING: 325b
S: 366, 417, 433, 578b, 698b, 745a, 783, 794
scale: 9b, 15
search and push in splay stack: 613b, 616a, 616b, 617
search and stack avl: 572a, 572b, 578a
search and stack rb: 592
, 593, 598
search arc: 660b, 660d, 660e, 675a, 681a, 694a, 805
search deway: 336b
search deway: 336a, 336b, 337
search extreme: 194
, 195a, 195b
searchInBinTree: 384b, 390b
search min: 190b, 191, 195a
search node: 654
, 654d, 655a, 674
search parent: 387
search rank parent: 388
seg plus block pow: 48b, 50b, 55, 56, 59, 60a
seg size: 49a, 49
, 52b, 52
, 54
, 55, 56, 59, 60a, 63
select: 19, 411b
select gotoup root: 546b, 547
selection sort: 189b, 191, 192b, 193a
select pivot: 215
, 220a, 220b
SentinelCtor: 293, 409, 561, 591
sequential search: 16, 34a, 193b, 194a, 194b
set bit: 664a, 664b, 666a, 666b, 698b, 702, 705a, 705b, 706, 709b, 710, 712, 713, 715,
718b, 719b, 720, 724, 727, 738b, 738
, 739a, 740, 746, 748, 749a, 778a, 778b, 787a,
787b, 797a, 797
, 798b, 799a, 814
, 818a, 822b
set default initial value: 54a, 760
set end of stream: 436a, 436b
set freq: 425b, 429b, 433
set graph: 692f, 811a
set is leaf: 325a, 329
set is leftmost: 325a, 328a, 328b, 329, 330
set is rightmost: 325a, 328a, 328b, 329, 330
set is root: 325a, 328a, 328b, 329
set list graph: 768a, 771a
SIBLING: 325b
sibling: 323
, 323d, 324a, 324b, 328a, 328b, 339a, 601b
sibling to Tree Node: 324a, 324b
sift down: 352
sift up: 350
Slink: 79, 80a, 80b, 81b, 81
, 83, 84a, 84e, 84f, 89d
SLINK TO TYPE: 83
Slist: 86a, 86
, 87d, 87e, 88b, 89
, 89d
Snode: 84a, 84d, 84e, 84f, 86a, 86b, 89a, 134g, 135b, 149a, 149b, 163b
sort arcs: 659
, 689d, 776a
splay: 615, 616a, 616b, 617
855