Вы находитесь на странице: 1из 881

Tejiendo algoritmos

Leandro Rabindranath Leon


lrleon@ula.ve

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.

Leandro Rabindranath Leon

Dedicatoria - A mis compa


neros estudiantes -

Nun a seras mas joven de lo que hoy eres;


los destinos tienen sus momentos:
se pa iente y a epta lo que pasa o te sobrepasa.
La libertad se ejer e en medida de la apa idad,
pero tu apa idad depende del Otro.
Ninguna obra tiene sentido sin el Otro:
mejorala y honra a quien te la re iba o ritique.
Ex elen ia es nun a asumir por ulminado el amino;
jamas erigirse en maestro absoluto:
es u ha a quien ha transitado por donde no has pasado.
Te ultivo el Otro, en una patria:
sele fraterna ...
... nun a uses tus ono imientos para trai ionarla.

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 a rmar 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

El primer medio es la palabra; sesgada, por supuesto, a mi mirada y experien ia subjetiva;


digo esto por ontraste al libro texto de este tiempo. Intento ser mas rti o que lo que la
razon y espa io tradi ionales lo permiten. Cuando hablo de rti a, no me re ero al tpi o
y ne esario analisis fsi o-matemati o que sobre un aspe to te ni o un libro texto realiza,
o a la individualista y absurda ostumbre de vituperar, sino a la a titud permanente de
dis utir aspe tos sobre el asunto de estudio que lo mejoren.
Hago un enfasis parti ular en algunas etimologas. En la medida de lo que me ha
sido posible, etimologizo terminos te ni os; mi inten ion, aparte de resistir al empobre imiento que la objetividad te nologi a ejer e sobre el lenguaje, es abrir aminos de sentido distintos al meramente te ni o. Tambien evado ons ientemente el uso de algunos
terminos; por ejemplo, salvo alguna ita textual, no uso \inventar", pues pienso que su
uso es privativo y ex luye a un ter ero de la posibilidad de que se le aparez an las mismas
ideas. En su lugar, empleo \des ubrir", ual signi a, \mostrar" y que, no solo aun no
ha sido menos abado (>aun?) por la utiliza ion te ni a, sino que posibilita la revela ion y
aprension de un ono imiento a un nivel de autenti idad que puede ser tan propio omo
el de ualquier autor.
Lo anterior no me ha impedido apelar al lenguaje te ni o tradi ional. En la mayora
de las o asiones planteo de ni iones formales, las uales, a menudo, tan solo requieren
matemati a elemental, presumiblemente forjada en el ba hillerato. En algunas es asas
situa iones se ne esitara matemati a mas avanzada. En los po os asos omplejos,
ono imientos basi os de matemati a dis reta son su ientes para apropiarse de las demostra iones.
C++

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

Repita mientras m > 0 y m < n


si a[m] < a[x] entonces
1 Es

imposible ser ompletamente oloquial en un lenguaje de programa ion.

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, a rmar 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 de enden 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

Los odigos fuentes fueron automati amente indentados on bcpp [3.


A lo largo de mi experien ia omo ense~nante, he intentado re ompensar a los des ubridores de errores y proponentes de mejoras on una pondera ion en su ali a ion. No veo
ningun impedimento a que un ter ero aplique lo mismo si este texto se usa omo material
instru ional. Si algun le tor fuera de mi r ulo de ense~nanza desea reportar algun error
o realizar alguna mejora, enton es le agradez o lo reporte al email:
lrleon@ula.ve

La do umenta ion de la bibliote a esta disponible en el enla e http://www.ing.ula.ve/aleph.



Esta fue es rita para pro esarse on el ex elente programa doxygen [7.
Programaci
on literaria

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
de ni 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 de nir 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 de nen 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

Di ho lo anterior, puede de irse que el sentido de un ejer i io en laboratorio es


permitirle al estudiante \ini iar" la on re ion de sus ono imientos. Para ello, en
laboratorio, pueden realizarse tres a tividades:
(a) Contempla ion de un problema omputa ional, junto on su solu ion, en el ual
se haga uso de alguna estru tura de datos o algoritmo de estudio.
El gua de laboratorio puede enun iar el problema y presentar la instrumenta ion de su solu ion. Luego, invitar al estudiante a revisar los fuentes
e inferir, antes de su eje u ion, las te ni as y estilos utilizados en la instrumenta ion.
(b) Contempla ion de la eje u ion del programa para diversas ombina iones de
entrada. Esta es la fase en la ual el aprendiz omienza a sentir on re ion;
es de ir, que los on eptos abstra tos deparen en solu iones on retas.
Durante esta a tividad puede ser re omendable la utiliza ion de un \depurador".
( ) Finalmente, resolu ion de una variante del problema a partir de la solu ion
primigenia. En este punto, es muy importante que se trabaje sobre el mismo
problema y fuentes, pero on alguna variante; una amplia ion para resolver
otras lases de entradas, por ejemplo.
3. Realiza ion de trabajos pra ti os: indudablemente, en el espritu del proverbio
itado, esta es la fase que se en aja el \ha er para entender".
>Como debe ser el proye to? Debe orresponderse on un problema original para y
on sentido al ontexto en donde se ense~ne; es de ir, formulado por y para usarse en
la omunidad en donde se reali e la ense~nanza de este urso. Por ejemplo, si aleda~no
al lugar de ense~nanza se pade en de problemas de tra o, enton es puede plantearse
un onjunto amplsimo de proye tos en torno a la simula ion del tra o y al estudio
de diversas te ni as para evitar la ongestion. Un proye to de este tipo requerira,
por ejemplo, la modeliza ion on grafos de las vas de ir ula ion, de me anismos de
ontrol omo los semaforos y de agentes ir ulantes omo los automoviles.
So riesgo de ser ex esivamente repetitivo, debo insistir en la importan ia de los ejer i ios.
Esto ya debe ser obvio desde la perspe tiva del estudiante, pues es el uni o medio de
ejer itarse. Al instru tor, por otra parte, le permite enrique er la ense~nanza uando se
plantean ejer i ios que ompletan o omplementan el ono imiento impartido; as omo,
mediante la orre ion, supervisar el nivel de sus estudiantes.
No puedo errar esta se ion sin expresar que, para m, la mas seria y justa manera de
evaluar un urso en torno al orpus de este texto es por apre ia ion de aten ion y situa ion
del aprendiz durante la le tura y mediante la elabora ion de un proye to omun para todos.
Tengo el deber de de ir que la peor, y posiblemente mas injusta, es mediante los examenes
tradi ionales de nuestro sistema es olar. En mu hos aspe tos, es pra ti amente azaroso
determinar el nivel de un aprendiz a traves del resultado de una prueba que se realiza en
aproximadamente dos horas. En esta materia, las virtudes a evaluar no son medibles en
un examen, sino a lo largo del urso y en la on re ion efe tiva de un proye to.
A la observa ion anterior se antepone una espe ie de doble drama. Tal pare e que
nuestra fragmenta ion moral no nos permite la mnima honestidad omo para que los

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 odi que.
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:

1. btreepic para dibujar arboles binarios.


2. ntreepic para dibujar arbores en ias y arboles en general.
3. graphpic para dibujar grafos.
El primer programa, btreepic, fue motivado a mi insatisfa ion on los dibujos de arboles
binarios realizados on programas espe iales tales omo Xfig y dia. A esto se auno la imposibilidad material de dibujar arboles enormes -de ientos o miles de nodos-. En virtud de
esto, soli ite un trabajo es olar al respe to uyos resultados, a pesar de satisfa erme es olarmente, distaron mu ho de serme su ientes. A raz de esa experien ia, de id por mi propia
uenta realizar btreepic. Cuando tuve que dibujar arboles generales, soli ite el mismo
tipo de programa; aqu apare io un estudiante ex elso, llamado Jose Brito, quien forjo una
primera version llamada xtreepic y que esta distribuida en ALEPH. Lamentablemente,
esta version operaba sobre una version de arboles que a la epo a de mi ne esidad y revision ya era adu a, por lo que prefer reha er enteramente el programa bajo el nombre
de ntreepic. Para la elabora ion del programa me fue muy util el hermoso texto sobre
dibujado de grafos de Kozo Sugiyama [17. Finalmente, uando me en ontre en la misma
ne esidad respe to a los grafos, aprove he la o asion para desarrollar asi enteramente el
orpus algortmi o en geometra omputa ional. El programa resultante, graphpic es aun
muy simple y evade toda la algortmi a vin ulada al dibujado automati o de grafos; de
he ho, las de isiones sobre dibujado son tomadas por el usuario; pero pudiera de ir que
graphpic tiene la virtud de operar enteramente sobre geometra omputa ional y que ello,
no solo valida este ampo, sino que abre futuros desarrollos.
En Marzo de 2008 ini ie la es ritura de la do umenta ion de la bibliote a. Me enfrente
a un problema: > omo integrar la do umenta ion en el fuente de este texto sin que ella
aparez a en el texto?. Requera es ribirla en el mismo sitio donde es rib este libro porque
de ese modo, bajo la misma do trina de noweb, una modi a ion de la bibliote a se podra
a tualizar rapidamente en la do umenta ion. De id enton es dise~nar un ltro de texto
que re ono iese los bloques de do umenta ion dentro del fuente noweb y los eliminase
de manera que no apare iesen en la salida L TEX. Tal ltro se denomina deldoxygen y
fue es rito on el generador de analizadores lexi ogra os flex y C++. Alguien dira que
pude haberlo he ho en treinta minutos on uno de los lenguajes de \s ripting" modernos
(perl, python, et .). Probablemente sea ierto si yo fuese maestro en alguno de aquellos
lenguajes, pero, en a~nadidura dejeseme repli ar on tres he hos. Primero, demore un par
de horas en es ribir deldoxygen porque tena quin e a~nos sin usar flex y no re ordaba
bien el lenguaje; no es pues mu ha la diferen ia. Segundo, mi ltro tiene mu has mas
posibilidades de ser orre to, pues fue espe i ado -no programado- bajo el formalismo
de las expresiones regulares y los automatas; en un lenguaje de s ripting, esta orre titud
tena que programarse y no espe i arse omo fue el aso. Finalmente, el ltro debe ser
onsiderablemente mas veloz que una ontraparte s ripting; yo dira, uando menos, dos
ordenes de magnitud.
Cuando me en ontraba en la edi ion nal de esta version (Marzo 2008), hube de realizar que el texto ontena (y aun ontiene) bloques noweb on pedazos de odigo sin mu ho
valor dida ti o; por ejemplo, repetir fun iones uyo sentido ya fue expli ado en otro lugar para otra lase de objeto. De id enton es dise~nar otro ltro de texto que re onoz a
delimitadores dentro del fuente noweb y que se invo ase antes de noweb. El ltro se deA

xi

nomina nobook, fue es rito tambien en flex y elimina, para la salida L TEX, los bloques
delimitados.
A

Cosas que faltan y sobran


Desde mu has perspe tivas, siempre un texto adole e de falta de algo. Aqu deseo referir
a lo que, \ reo", hubiera debido perfe tamente realizar y a lo que, \ on ertitud", no deb
de ha er.
Creo sin eramente que las estru turas de datos y algoritmos fundamentales estan presentes en este texto; aunque de seguro algun par dis repara. Por otra parte, material que a
mi jui io es de alto interes, que esta presente en la bibliote a ALEPH, no esta presente en
este texto. Los ejemplos mas notables de ello son las listas skip, ujos en grafo, oloreados,
aminos eulerianos y hamiltonianos, estru turas de grafos on urrentes, de agentes y de
simula ion, estru turas de grafos y sus algoritmos basados en olonias de hormigas y geometra omputa ional. Me hubiese gustado in luirlas en este libro, pero ya me sobrepaso
el momento y agote el lmite de espa io.
Aspe tos que no estan desarrollados en ALEPH y que seran dignos de in luirse son
los heaps de Fibona i, las familias de arboles dedi ados a sistemas de ar hivo y busqueda
en memoria se undaria (B+ y derivados) y los arboles quadtrees.
En este texto se apela al lenguaje de modelado UML para \fa ilitar la ompresion de
rela iones entre objetos". Creo sin eramente que UML tiene mu ho valor para omprender
sistemas omplejos, ya desarrollados, y un po o menos de valor, aunque apre iable, para
dise~narlos. Pero en lo que ata~ne a este texto y sus ursos derivados, no es de tras endente
utilidad.
En aras de la ompletitud, este texto ontiene toda implanta ion de estru tura de datos
y algoritmo que es presentado. Pero el sa ri io de esto es la longitud y un lastre de
odigo que, si bien es fundamental para la instrumenta ion, es de muy es aso valor para
la ense~nanza. Tengo la obliga ion de admitir, omo yerro, que sin este lastre este texto no
solo sera substan ialmente mas orto, sino mu hsimo mas legible y ameno de entender.
El ltro nobook ha mitigado un po o este problema, pero no lo ha resuelto.

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 simpli ed. 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.x g.org.

Contenido
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
1 Abstracci
on de datos

1.1 Espe i a iones de datos . . . . . . . . . . . . . . . . . . . . . . .


1.1.1 Tipo abstra to de dato . . . . . . . . . . . . . . . . . . . .
1.1.2 No ion de lase de objeto . . . . . . . . . . . . . . . . . .
1.1.3 Lo subjetivo de un objeto . . . . . . . . . . . . . . . . . .
1.1.4 Un ejemplo de TAD . . . . . . . . . . . . . . . . . . . . .
1.1.5 El lenguaje UML . . . . . . . . . . . . . . . . . . . . . . .
1.2 Heren ia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2.1 Tipos de heren ia . . . . . . . . . . . . . . . . . . . . . .
1.2.2 Multiheren ia . . . . . . . . . . . . . . . . . . . . . . . . .
1.2.3 Polimor smo . . . . . . . . . . . . . . . . . . . . . . . . .
1.2.3.1 Polimor smo de sobre arga . . . . . . . . . . . .
1.2.3.2 Polimor smo de heren ia . . . . . . . . . . . . .
1.2.3.3 Polimor smo de plantilla (tipos parametrizados)
1.2.3.4 Lo general y lo generi o . . . . . . . . . . . . . .
1.3 El problema fundamental de estru turas de datos . . . . . . . . .
1.3.1 Compara ion general entre laves . . . . . . . . . . . . . .
1.3.2 Opera iones para onjuntos ordenables . . . . . . . . . . .
1.3.3 Cir unstan ias del problema fundamental . . . . . . . . .
1.3.4 Presenta iones del problema fundamental . . . . . . . . .
1.4 Dise~no de datos y abstra iones . . . . . . . . . . . . . . . . . . .
1.4.1 Tipos de abstra ion . . . . . . . . . . . . . . . . . . . . .
1.4.2 El prin ipio n-a- n . . . . . . . . . . . . . . . . . . . . .
1.4.3 Indu ion y dedu ion . . . . . . . . . . . . . . . . . . . .
1.4.4 O ultamiento de informa ion . . . . . . . . . . . . . . . .
1.5 Notas y re omenda iones bibliogra as . . . . . . . . . . . . . . .
1.6 Ejer i ios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

2.1.1.4 Elimina ion por lave . . . . . . . . . . . . . . . .


2.1.2 Manejo de memoria para arreglos . . . . . . . . . . . . . . .
2.1.2.1 Arreglos en memoria estati a . . . . . . . . . . . .
2.1.2.2 Arreglos en pila . . . . . . . . . . . . . . . . . . .
2.1.2.3 Arreglos en memoria dinami a . . . . . . . . . . .
2.1.3 Arreglos de bits . . . . . . . . . . . . . . . . . . . . . . . .
2.1.4 Arreglos Dinami os . . . . . . . . . . . . . . . . . . . . . . .
2.1.5 El TAD DynArray<T> . . . . . . . . . . . . . . . . . . . . .
2.1.5.1 Estru tura de datos de DynArray<T> . . . . . . .
2.1.5.2 Manejo de memoria . . . . . . . . . . . . . . . . .
2.1.5.3 Espe i a ion e implanta ion de metodos publi os
2.1.5.4 A eso mediante operador [] . . . . . . . . . . . .
2.1.5.5 Uso del valor por omision . . . . . . . . . . . . . .
Arreglos multidimensionales . . . . . . . . . . . . . . . . . . . . . .
Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Listas Enlazadas . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Listas enlazadas y el prin ipio n a n . . . . . . . . . . . .
2.4.2 El TAD Slink (enla e simple) . . . . . . . . . . . . . . . .
2.4.3 El TAD Snode<T> (nodo simple) . . . . . . . . . . . . . .
2.4.4 El TAD Slist<T> (lista simplemente enlazada) . . . . . .
2.4.5 Iterador de Slist<T> . . . . . . . . . . . . . . . . . . . . .
2.4.6 El TAD DynSlist<T> . . . . . . . . . . . . . . . . . . . . .
2.4.7 El TAD Dlink (enla e doble) . . . . . . . . . . . . . . . . .
2.4.8 El TAD Dnode<T> (nodo doble) . . . . . . . . . . . . . . .
2.4.9 El TAD Dlist<T> (lista ir ular doblemente enlazada) . .
2.4.9.1 El problema de los destru tores virtuales . . . . .
2.4.9.2 Iterador de Dlist<T> . . . . . . . . . . . . . . . .
2.4.10 El TAD DynDlist<T> . . . . . . . . . . . . . . . . . . . . .
2.4.10.1 Iterador de DynDlist<T> . . . . . . . . . . . . . .
2.4.11 Apli a ion: aritmeti a de polinomios . . . . . . . . . . . . .
2.4.11.1 Implementa ion de polinomios . . . . . . . . . . .
Pilas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5.1 Representa iones de una pila en memoria . . . . . . . . . .
2.5.2 El TAD ArrayStack<T> (pila ve torizada) . . . . . . . . .
2.5.3 El TAD ListStack<T> (pila on listas enlazadas) . . . . .
2.5.4 El TAD DynListStack<T> . . . . . . . . . . . . . . . . . .
2.5.5 Apli a ion: un evaluador de expresiones aritmeti as in jas .
2.5.6 Pilas, llamadas a pro edimientos y re ursion . . . . . . . .
2.5.6.1 Consejos para la re ursion . . . . . . . . . . . . .
2.5.6.2 Elimina ion de la re ursion . . . . . . . . . . . . .
Colas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.6.1 Variantes de las olas . . . . . . . . . . . . . . . . . . . . .
2.6.2 Apli a iones de las olas . . . . . . . . . . . . . . . . . . . .
2.6.3 Representa iones en memoria de las olas . . . . . . . . . .
2.6.4 El TAD ArrayQueue<T> ( ola ve torizada) . . . . . . . . .
2.6.5 El TAD ListQueue<T> ( ola on listas enlazadas) . . . . .

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

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

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

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

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

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

2.6.6 El TAD DynListQueue<T> ( ola dinami a on listas


2.7 Estru turas de datos ombinadas - Multilistas . . . . . . . .
2.8 Notas bibliogra as . . . . . . . . . . . . . . . . . . . . . . .
2.9 Ejer i ios . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3 Crtica de algoritmos

xvii

enlazadas)
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .

3.1 Analisis de algoritmos . . . . . . . . . . . . . . . . . . . . . . .


3.1.1 Unidad o paso de eje u ion . . . . . . . . . . . . . . . .
3.1.2 A laratoria sobre los metodos de ordenamiento . . . . .
3.1.3 Ordenamiento por sele ion . . . . . . . . . . . . . . . .
3.1.4 Busqueda se uen ial . . . . . . . . . . . . . . . . . . . .
3.1.5 Busqueda de extremos . . . . . . . . . . . . . . . . . . .
3.1.6 Nota ion O . . . . . . . . . . . . . . . . . . . . . . . . .

3.1.6.1 Algebra
de O . . . . . . . . . . . . . . . . . . .
3.1.6.2 Analisis de algoritmos mediante O . . . . . . .
3.1.7 Ordenamiento por inser ion . . . . . . . . . . . . . . . .
3.1.8 Busqueda binaria . . . . . . . . . . . . . . . . . . . . . .
3.1.9 Errores de la nota ion O . . . . . . . . . . . . . . . . . .
3.1.10 Tipos de analisis . . . . . . . . . . . . . . . . . . . . . .
3.2 Algoritmos dividir/ ombinar . . . . . . . . . . . . . . . . . . .
3.2.1 Ordenamiento por mez la . . . . . . . . . . . . . . . . .
3.2.1.1 Mez la (merge) . . . . . . . . . . . . . . . . . .
3.2.1.2 Analisis del mergesort . . . . . . . . . . . . . .
3.2.1.3 Estabilidad del mergesort . . . . . . . . . . . .
3.2.1.4 Coste en espa io . . . . . . . . . . . . . . . . .
3.2.1.5 Ordenamiento por mez la de listas enlazadas .
3.2.2 Ordenamiento rapido (Qui ksort) . . . . . . . . . . . . .
3.2.2.1 Parti ion . . . . . . . . . . . . . . . . . . . . .
3.2.2.2 Analisis del qui ksort . . . . . . . . . . . . . .
3.2.2.3 Analisis de onsumo de espa io del qui ksort .
3.2.2.4 Sele ion del pivote . . . . . . . . . . . . . . .
3.2.2.5 Qui ksort sin re ursion . . . . . . . . . . . . .
3.2.2.6 Qui ksort sobre listas enlazadas . . . . . . . .
3.2.2.7 Mejoras al qui ksort . . . . . . . . . . . . . . .
3.2.2.8 Claves repetidas . . . . . . . . . . . . . . . . .
3.2.2.9 Qui ksort on urrente o paralelo . . . . . . . .
3.2.2.10 Busqueda aleatoria de lave . . . . . . . . . . .
3.2.2.11 Sele ion aleatoria sobre arreglos desordenados
3.3 Analisis amortizado . . . . . . . . . . . . . . . . . . . . . . . .
3.3.1 Analisis poten ial . . . . . . . . . . . . . . . . . . . . . .
3.3.2 Analisis ontable . . . . . . . . . . . . . . . . . . . . . .
3.3.3 Sele ion del poten ial o reditos . . . . . . . . . . . . .
3.4 Corre titud de algoritmos . . . . . . . . . . . . . . . . . . . . .
3.4.1 Planteamiento de una demostra ion de orre titud . . .
3.4.2 Tipos de errores . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

.
.
.
.
.

.
.
.
.
.

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

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

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

3.4.3 Preven ion y dete ion de errores . .


3.4.3.1 Dis iplina de programa ion
3.4.3.2 Analisis estati o . . . . . .
3.4.3.3 Analisis dinami o . . . . .
3.5 E a ia y e ien ia . . . . . . . . . . . . . .
3.5.1 La regla del 80-20 . . . . . . . . . .
3.5.2 >Cuando ata ar la e ien ia? . . . .
3.5.3 Maneras de mejorar la e ien ia . .
3.5.4 Per laje (pro ling) . . . . . . . . . .
3.5.5 Lo alidad de referen ia . . . . . . .
3.5.6 Tiempo de desarrollo . . . . . . . . .
3.6 Notas bibliogra as . . . . . . . . . . . . . .
3.7 Ejer i ios . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . .

4 Arboles

CONTENIDO

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

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

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

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

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

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

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

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

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

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

4.1 Con eptos basi os . . . . . . . . . . . . . . . . . . . . . . . .


4.2 Representa iones de un arbol . . . . . . . . . . . . . . . . . .
4.2.1 Conjuntos anidados . . . . . . . . . . . . . . . . . . .
4.2.2 Se uen ias parentizadas . . . . . . . . . . . . . . . . .
4.2.3 Indenta ion . . . . . . . . . . . . . . . . . . . . . . . .
4.2.4 Nota ion de Deway . . . . . . . . . . . . . . . . . . . .
4.3 Representa iones de arboles en memoria . . . . . . . . . . . .
4.3.1 Listas enlazadas . . . . . . . . . . . . . . . . . . . . .
4.3.2 Arreglos . . . . . . . . . . . . . . . . . . . . . . . . . .

4.4 Arboles
Binarios . . . . . . . . . . . . . . . . . . . . . . . . .
4.4.1 Representa ion en memoria de un arbol binario . . . .
4.4.2 Re orridos sobre arboles binarios . . . . . . . . . . . .
4.4.3 Un TAD generi o para arboles binarios . . . . . . . .
4.4.4 Contenedor de fun iones sobre arboles binarios . . . .
4.4.5 Re orridos re ursivos . . . . . . . . . . . . . . . . . . .
4.4.6 Re orridos no re ursivos . . . . . . . . . . . . . . . . .
4.4.7 Cal ulo de la ardinalidad . . . . . . . . . . . . . . . .
4.4.8 Cal ulo de la altura . . . . . . . . . . . . . . . . . . .
4.4.9 Copia de arboles binarios . . . . . . . . . . . . . . . .
4.4.10 Destru ion de arboles binarios . . . . . . . . . . . . .
4.4.11 Compara ion de arboles binarios . . . . . . . . . . . .
4.4.11.1 Similaridad . . . . . . . . . . . . . . . . . . .
4.4.11.2 Equivalen ia . . . . . . . . . . . . . . . . . .
4.4.12 Re orrido por niveles . . . . . . . . . . . . . . . . . . .
4.4.13 Constru ion de arboles binarios a partir de re orridos
4.4.14 Conjunto de nodos en un nivel . . . . . . . . . . . . .
4.4.15 Hilado de arboles binarios . . . . . . . . . . . . . . . .
4.4.16 Re orridos pseudo-hilados . . . . . . . . . . . . . . . .
4.4.17 Corresponden ia entre arboles binarios y m-rios . . .
4.5 Un TAD generi o para arboles . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

4.5.1 Observadores de Tree Node<T> . . . . . . . . . . . . . . . . . . .


4.5.2 Modi adores de Tree Node<T> . . . . . . . . . . . . . . . . . . .
4.5.3 Observadores de arboles . . . . . . . . . . . . . . . . . . . . . . . .
4.5.4 Re orridos sobre Tree Node<T> . . . . . . . . . . . . . . . . . . .
4.5.5 Destru ion de Tree Node<T> . . . . . . . . . . . . . . . . . . . .
4.5.6 Altura de un Tree Node<T> . . . . . . . . . . . . . . . . . . . . .
4.5.7 Busqueda por numero de Deway . . . . . . . . . . . . . . . . . . .
4.5.8 Busqueda y determina ion de numero de Deway . . . . . . . . . .
4.5.9 Corresponden ia entre Tree Node<T> y arboles binarios . . . . . .
Algunos on eptos matemati os de los arboles . . . . . . . . . . . . . . . .
4.6.1 Altura de un arbol . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.6.2 Longitud del amino interno/externo . . . . . . . . . . . . . . . . .

4.6.3 Arboles
ompletos . . . . . . . . . . . . . . . . . . . . . . . . . . .
Heaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7.1 Inser ion en un heap . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7.2 Elimina ion en un heap . . . . . . . . . . . . . . . . . . . . . . . .
4.7.3 Colas de prioridad . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7.3.1 Modi a ion de prioridad . . . . . . . . . . . . . . . . . .
4.7.4 Heapsort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7.5 Apli a iones de los heaps . . . . . . . . . . . . . . . . . . . . . . .
4.7.6 El TAD BinHeap<Key> . . . . . . . . . . . . . . . . . . . . . . . .
4.7.6.1 Inter ambio entre nodos . . . . . . . . . . . . . . . . . . .
4.7.6.2 Inser ion en BinHeap<Key> . . . . . . . . . . . . . . . .
4.7.6.3 Elimina ion del mnimo elemento de un BinHeap<Key> .
4.7.6.4 A tualiza ion en un heap . . . . . . . . . . . . . . . . . .
4.7.6.5 Elimina ion de ualquier elemento en un BinHeap<Key>
4.7.6.6 Destru ion de BinHeap<Key> . . . . . . . . . . . . . . .
4.7.6.7 Otras opera iones . . . . . . . . . . . . . . . . . . . . . .
Enumera ion y odigos de arboles . . . . . . . . . . . . . . . . . . . . . .
4.8.0.8 Codigos de un arbol binario . . . . . . . . . . . . . . . .
4.8.0.9 Codigos de Di k . . . . . . . . . . . . . . . . . . . . . . .
4.8.1 Numeros de Catalan . . . . . . . . . . . . . . . . . . . . . . . . . .

Arboles
binarios de busqueda . . . . . . . . . . . . . . . . . . . . . . . . .
4.9.1 Busqueda en un ABB . . . . . . . . . . . . . . . . . . . . . . . . .
4.9.1.1 Busqueda del menor y del mayor elemento de un ABB .
4.9.1.2 Busqueda del prede esor y su esor . . . . . . . . . . . . .
4.9.1.3 Busquedas espe iales sobre un ABB . . . . . . . . . . . .
4.9.2 El TAD BinTree<Key> . . . . . . . . . . . . . . . . . . . . . . . .
4.9.3 Inser ion en un ABB . . . . . . . . . . . . . . . . . . . . . . . . . .
4.9.4 Parti ion de un ABB por lave (split) . . . . . . . . . . . . . . . .
4.9.5 Union ex lusiva de ABB (join ex lusivo) . . . . . . . . . . . . . . .
4.9.6 Elimina ion en un ABB . . . . . . . . . . . . . . . . . . . . . . . .
4.9.7 Inser ion en raz de un ABB . . . . . . . . . . . . . . . . . . . . .
4.9.8 Union de ABB (join) . . . . . . . . . . . . . . . . . . . . . . . . . .
4.9.9 Analisis de los arboles binarios de busqueda . . . . . . . . . . . . .
El TAD DynMapTree<Tree, Key, Range, Compare> . . . . . . . . . . .

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

4.11 Extensiones a los arboles binarios . . . . . . . . . . . .


4.11.1 Sele ion por posi ion . . . . . . . . . . . . . .
4.11.2 Cal ulo de la posi ion in ja . . . . . . . . . . .
4.11.3 Inser ion por lave en arbol binario extendido .
4.11.4 Parti ion por lave . . . . . . . . . . . . . . . .
4.11.5 Inser ion en raz . . . . . . . . . . . . . . . . .
4.11.6 Parti ion por posi ion . . . . . . . . . . . . . .
4.11.7 Inser ion por posi ion . . . . . . . . . . . . . .
4.11.8 Union ex lusiva de arboles extendidos . . . . .
4.11.9 Elimina ion por lave en arboles extendidos . .
4.11.10 Elimina ion por posi ion en arboles extendidos
4.11.11 Desempe~no de las extensiones . . . . . . . . . .
4.12 Rota ion de arboles binarios . . . . . . . . . . . . . . .
4.12.1 Rota iones en arboles binarios extendidos . . .
4.13 Codigos de Hu man . . . . . . . . . . . . . . . . . . .
4.13.1 Un TAD para arboles de odigo . . . . . . . . .
4.13.2 De odi a ion . . . . . . . . . . . . . . . . . .
4.13.3 Algoritmo de Hu man . . . . . . . . . . . . . .
4.13.4 De ni ion de smbolos y fre uen ias . . . . . .
4.13.5 Codi a ion de texto . . . . . . . . . . . . . . .
4.13.6 Optima ion de Hu man . . . . . . . . . . . . .
4.13.6.1 Con lusion . . . . . . . . . . . . . . .

4.14 Arboles
estati os optimos . . . . . . . . . . . . . . . .
4.14.1 Objetivo del problema . . . . . . . . . . . . . .
4.14.2 Implanta ion del problema . . . . . . . . . . .
4.15 Notas bibliogra as . . . . . . . . . . . . . . . . . . . .
4.16 Ejer i ios . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

5.1 Manejo de olisiones . . . . . . . . . . . . . . . . . . . . . . .


5.1.1 La paradoja del umplea~nos . . . . . . . . . . . . . . .
5.1.2 Estrategias de manejo de olisiones . . . . . . . . . . .
5.1.3 En adenamiento . . . . . . . . . . . . . . . . . . . . .
5.1.3.1 En adenamiento separado . . . . . . . . . . .
5.1.3.2 Analisis del en adenamiento separado . . . .
5.1.3.3 En adenamiento errado . . . . . . . . . . .
5.1.3.4 Analisis informal del en adenamiento errado
5.1.4 Dire ionamiento abierto . . . . . . . . . . . . . . . .
5.1.4.1 Sondeo ideal (sondeo uniforme) . . . . . . .
5.1.4.2 Sondeo lineal . . . . . . . . . . . . . . . . . .
5.1.4.3 Analisis informal del sondeo lineal . . . . . .
5.1.4.4 Sondeo uadrati o . . . . . . . . . . . . . . .
5.1.4.5 Doble hash . . . . . . . . . . . . . . . . . . .
5.1.4.6 Analisis informal del doble hash . . . . . . .
5.1.5 Reajuste de dimension en una tabla hash . . . . . . .

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

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

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

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

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

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

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

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

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

5.1.6 Manejo dinami o de ubetas (TAD DynLhashTable<Key,


5.1.7 Tablas hash lineales . . . . . . . . . . . . . . . . . . . . .
5.1.7.1 Expansion/ ontra ion de una tabla hash lineal
5.1.7.2 Busqueda en LinearHashTable<Key> . . . . .
5.1.7.3 Inser ion en LinearHashTable<Key> . . . . . .
5.1.7.4 Elimina ion en LinearHashTable<Key> . . . .
5.1.7.5 Analisis de la dispersion lineal . . . . . . . . . .
5.2 Fun iones hash . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.1 Interfaz a la fun ion hash . . . . . . . . . . . . . . . . . .
5.2.2 Holgura de dispersion . . . . . . . . . . . . . . . . . . . .
5.2.3 Plegado o doblado de lave . . . . . . . . . . . . . . . . .
5.2.4 Heursti as de dispersion . . . . . . . . . . . . . . . . . .
5.2.4.1 Dispersion por division . . . . . . . . . . . . . .
5.2.4.2 Dispersion por multipli a ion . . . . . . . . . . .
5.2.5 Dispersion de adenas de ara teres . . . . . . . . . . . .
5.2.6 Dispersion universal . . . . . . . . . . . . . . . . . . . . .
5.2.7 Dispersion perfe ta . . . . . . . . . . . . . . . . . . . . . .
5.3 Otros usos de las tablas hash y de la dispersion . . . . . . . . . .
5.3.1 Identi a ion de adenas . . . . . . . . . . . . . . . . . .
5.3.2 Supertraza . . . . . . . . . . . . . . . . . . . . . . . . . .
5.3.3 Ca he (el TAD Hash Cache<Key,Data> ) . . . . . . . . .
5.4 Notas bibliogra as . . . . . . . . . . . . . . . . . . . . . . . . . .
5.5 Ejer i ios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6 Arboles
de b
usqueda equilibrados

6.1 Equilibrio de arboles . . . . . . . . . . . . . . . . . .



6.2 Arboles
aleatorizados . . . . . . . . . . . . . . . . . .
6.2.1 El TAD Rand Tree<Key> . . . . . . . . . . .
6.2.1.1 Inser ion en un ABBA . . . . . . .
6.2.1.2 Elimina ion en un ABBA . . . . . .
6.2.2 Analisis de los arboles aleatorizados . . . . .
6.3 Treaps . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3.1 El TAD Treap<Key> . . . . . . . . . . . . .
6.3.2 Inser ion en un treap . . . . . . . . . . . . .
6.3.3 Elimina ion en treap . . . . . . . . . . . . . .
6.3.4 Analisis de los treaps . . . . . . . . . . . . . .
6.3.5 Prioridades impl itas . . . . . . . . . . . . .

6.4 Arboles
AVL . . . . . . . . . . . . . . . . . . . . . .
6.4.1 El TAD Avl Tree<Key> . . . . . . . . . . .
6.4.1.1 Inser ion en un arbol AVL . . . . .
6.4.1.2 Elimina ion en un arbol AVL . . . .
6.4.2 Analisis de los arboles AVL . . . . . . . . . .

6.4.2.1 Arboles
de Fibona i . . . . . . . .
6.4.2.2 Demostra ion de la proposi ion 6.4

6.5 Arboles
rojo-negro . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

6.5.1 El TAD Rb Tree<Key> . . . . . . . . . . . . .


6.5.1.1 Inser ion en un arbol arbol rojo negro
6.5.1.2 Elimina ion en un arbol rojo negro .
6.5.2 Analisis de los arboles rojo-negro . . . . . . . .

6.6 Arboles
splay . . . . . . . . . . . . . . . . . . . . . . .
6.6.1 El TAD Splay Tree<Key> . . . . . . . . . . .
6.6.2 Analisis de los arboles splay . . . . . . . . . . .
6.7 Con lusion . . . . . . . . . . . . . . . . . . . . . . . .
6.8 Notas bibliogra as . . . . . . . . . . . . . . . . . . . .
6.9 Ejer i ios . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . .
7 Grafos

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

7.8.2 Algoritmo de Floyd-Warshall . . . . . . . . . . . . . .


7.8.2.1 Interfa es . . . . . . . . . . . . . . . . . . . .
7.8.2.2 El algoritmo de Floyd-Warshall . . . . . . .
7.8.2.3 El algoritmo de Floyd-Warshall . . . . . . .
7.8.2.4 Analisis del algoritmo de Floyd-Warshall . .
7.8.2.5 Corre titud del algoritmo de Floyd-Warshall
7.8.2.6 Re upera ion de aminos . . . . . . . . . . .
7.8.3 Algoritmo de Bellman-Ford . . . . . . . . . . . . . . .
7.8.3.1 Ini ializa ion del algoritmo de Bellman-Ford
7.8.3.2 Manejo del arbol abar ador . . . . . . . . . .
7.8.3.3 El algoritmo generi o Bellman-Ford . . . . .
7.8.3.4 Algoritmo mejorado de Bellman-Ford . . . .
7.8.3.5 Dete ion de i los negativos . . . . . . . . .
7.8.3.6 Constru ion del arbol abar ador . . . . . .
7.8.3.7 Analisis del algoritmo de Bellman-Ford . . .
7.8.3.8 Corre titud del algoritmo de Bellman-Ford .
7.8.4 Dis usion sobre los algoritmos de aminos mnimos . .
7.9 Notas bibliogra as . . . . . . . . . . . . . . . . . . . . . . . .
7.10 Ejer i ios . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Indice de identificadores

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

Cualquier programa que opere en un omputador puede dividirse en dos partes: la


se uen ia de instru iones de eje u ion y los datos. Las instru iones son resultado de
aquello que es ribimos omo odigo fuente. En o asiones, las instru iones pueden generarse durante la eje u ion del programa. Los datos representan el estado de al ulo en algun
momento del tiempo de eje u ion.
La palabra \dato" proviene del latn datum, que es el parti ipio pasado de
\do" (dar). Dato onnota, pues, algo que fue dado en el pasado y que nos interesa re ordar;
ese es el sentido de que sea memorizado. En el aso de la programa ion, un dato re uerda
una parte del estado de eje u ion del programa.
El mnimo nivel de organiza ion de un dato es su tipo. Un tipo de dato de ne un
onjunto ompuesto por todos los valores que puede adquirir una instan ia del dato.
Un tipo de dato puede onformarse por varios tipos de datos. En este aso, lo lasi amos en \dato estru turado" o \estru tura de datos". En algunos asos, los datos pueden
ser re ursivos (re urrentes ); es de ir, segun el tipo de dato, se re urren a s mismos o
entre ellos.
Algunos ejemplos en C++ podran dar luz de este asunto.
Para representar numeros enteros, se utiliza el tipo de dato int, el ual indi a que su
valor pertene e al onjunto Z. En este aso no se di e nada a er a de omo se representa
el tipo int en la memoria de un omputador; bien pudiera tratarse de 19, de 937 o de
2535301200456458802993406410049, entre in nitos valores posibles.
Para representar numeros reales, que pertene en a R, usamos el tipo float. Tipo mas
interesante que el anterior porque traslu e parte de su implanta ion en la memoria de un
omputador uando expresa, mediante el nombre float, que la representa ion del numero
es en punto otante; es de ir, esta estru turada en tres ampos de forma similar a la
siguiente:
2

00001000

Signo Exponente

001011110010110001001010

Parte fra ional

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 re ere 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.

1.1. Especificaciones de datos

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 de nir? >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 de nido, 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 de ne 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 de namoslo 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
de ni 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

Tipo abstracto de dato

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
de ne 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, de ne un orpus ognitivo, bastante abstra to por ierto, que nos permite de nir el nuevo tipo de dato.
Si nos remitimos a la de ni ion de tipo de dato, enton es, segun la matemati a, de nir un
tipo de dato estriba en de nir un onjunto de las dos maneras que tiene la matemati a: por
extension o por omprension. De nir un onjunto por extension es muy objetivo, pero,
tambien, muy arduo y, en mu hos asos, imposible; uando el onjunto es in nito, por
ejemplo. En el aso de la programa ion, un tipo de dato se de ne, 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 de nen el dominio del tipo .
4

3. Una interfaz de nida 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

y tipos de los parametros.


Especificaci
on sem
antica: des rip ion de lo que ha e la opera ion sobre el estado
del TAD.
En esta parte puede ser util indi ar axiomas, pre ondi iones y post ondi iones.

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.

fsi a del numero

1.

1.1. Especificaciones de datos

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 de ne 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();
};
De nes:
Complejo, never used.

Esta de ni 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 re ere 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.

1.1. Especificaciones de datos

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

Experien ias adquiridas en el desarrollo de programas de dibujado permiten modelizar un


TAD, llamado hFigure 7i, uyo n es generalizar opera iones inherentes al dibujado de
una gura sobre algun fondo de ontraste. Tal TAD es util para realizar programas que
hagan dibujos y una propuesta de de ni ion es omo sigue:
hFigure 7i

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
De nes:
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, de nir una abstra ion sin ono er
el para que de tal de ni 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 de ni 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 de nir 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, de nirla 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

Figure(const Point & point);


Figure(const Figure & figure);
Uses Figure 7.

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.

1.1. Especificaciones de datos

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 modi que 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;
De nes:
draw, used in hunk 15.
erase, used in hunk 15.
move, never used.
rotate, used in hunk 15.
scale, used in hunk 15.

Los nombres de metodos draw(), move() y erase() indi an laramente que es lo


que realizan . El metodo scale() ajusta el tama~no (es ala) de una gura segun un radio
dado. El tipo Ratio espe i a una magnitud de es ala que signi a la propor ion en que
la es ala se modi a; si este es menor que uno, enton es la gura se a hi a, de lo ontrario
se agranda. Finalmente, el metodo rotate() gira o rota la gura en el medio segun un
angulo de tipo Angle.
Los hModi adores de Figure 9bi representan opera iones generales sobre una gura.
Podemos dibujarla, moverla ha ia otro punto, borrarla, es alarla segun alguna magnitud,
o rotarla segun algun angulo en radianes uyo signo indi a el sentido de rota ion. En todos
los asos, tratamos on guras abstra tas, no on retas.
Al igual que on el tipo Point, en este estadio no es onveniente pensar en las implanta iones de los tipos Ratio y Angle. Solo basta on ono er su utiliza ion on objetos
de tipo Figure ir uns rita al n de dibujarlas.
Men ion parti ular mere en dos ali adores sinta ti os del C++. El primero lo onforma el pre jo reservado \virtual", el ual indi a que la opera ion puede implantarse
segun la parti ularidad de la gura; por ejemplo, un uadrado se dibuja diferente que un
r ulo. El segundo esta dado por el he ho de ini ializar la opera ion on el valor ero.
Esta es la sintaxis de C++ para de nir un \metodo virtual puro", el ual, a su vez, de ne
una lase abstra ta; o sea, abstra ion pura, sin ningun ara ter on reto, pues sino la
lase no sera abstra ta. Para aprehender esta observa ion, omen emos por preguntarnos
>a ual gura se re ere el TAD hFigure 7i?. La respuesta orre ta es que no lo sabemos,
pues se trata de una lase abstra ta, no de una on reta.
8

8A

ondi ion de que se onoz an los terminos ingleses.

10

Captulo 1. Abstracci
on de datos

Los metodos virtuales puros tienen que implantarse en \ lases derivadas" de la


lase Figure que on retan implanta iones parti ulares de una gura abstra ta. Por ejemplo, el metodo scale() de un triangulo se implanta en una lase Triangle derivada de
Figure.
El on epto de deriva ion sera estudiado on mas detalle en la se ion x 1.2.
1.1.5

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: "Uni ed 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

Figura 1.1: Diagrama UML de la lase Figure


La segunda se ion indi a los atributos y la ter era los metodos u opera iones. El
pre jo \-" en ada nombre de atributo u opera ion indi a que el miembro es ina esible;
mientras que el smbolo \+" indi a que es ompletamente a esible.
Un nombre de opera ion en letra ursiva indi a que la opera ion es virtual o polimorfa;
on epto que estudiaremos prontamente.
Los nombres de tipos de retorno y de parametros se separan de los nombres de fun ion
y de parametros on dos puntos (a la antigua, pero elegante, usanza del Pas al y Algol).
Los parametros tienen un pre jo ali ador que pueden tener valores in, out o inout para
espe i ar parametros de entrada, salida o entrada/salida respe tivamente.

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.

Figura 1.2: Jerarqua de lases espe ializadas de la lase Figure


La rela ion de heren ia se expresa en UML mediante una e ha ontigua que parte
desde la lase espe ializada ha ia la lase general. En el aso ejemplo, la lase general la
onforma el TAD hFigure 7i, mientras que las lases espe ializadas on retan las guras

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 de ne, 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 pre jo 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
de nimos que un preparador es, a la vez, estudiante y trabajador universitario.

Figura 1.3: Rela iones de lases de personas en una Universidad


Es posible tener rela iones de multiheren ia de interfaz por una parte, y de implanta ion por alguna otra.
1.2.3

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.

Hay tres lases de polimor smo: de sobre arga, de heren ia y de plantilla.

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 de nir 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);

El ompilador, a traves de la antidad de parametros, realiza la distin ion y de ide ual


de las tres fun iones es la que se debe invo ar. Por ejemplo, si el ompilador en uentra:
sumar(1, 2, 3);

Enton es este generara la llamada a sumar() on tres parametros.


Es posible, tambien, de nir sumar() para que \sume" otra lase de objetos. Por ejemplo:
const string sumar(const string & x, const string & y);

La suma de adenas puede interpretarse omo la on atena ion. El ompilador realiza la


distin ion respe to a las versiones aritmeti as mediante los tipos de los parametros.
En C++ se pueden sobre argar los operadores. Por ejemplo, podramos espe i ar la
on atena ion de adenas del siguiente modo:
const string operator + (const string & x, const string & y);

En este aso, puede es ribirse algo as omo:


string s1, s2;
...
string s3 = s1 + s2;

La sobre arga de operadores e, in lusive, la de fun iones, es un asunto polemi o porque


o ulta opera iones y puede ontravenir el sentido ultural del operador. Por ejemplo,
el odigo anterior tiene perfe to sentido ultural para la aritmeti a, pero quiza no para
la on atena ion. Cuando en una primera inspe ion un le tor lea la suma de adenas,
posiblemente el pensara que los operandos son numeri os, lo ual en el ultimo ejemplo no
es el aso. Por esa razon, es por lo general re omendable evitar la sobre arga.

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

El polimor smo de heren ia se de ne omo la posibilidad de de nir (no de sobre argar)


metodos virtuales del mismo nombre en las tres lases, invo ar al metodo desde la lase X y
determinar, en tiempo de eje u ion, ual es el metodo on reto segun sea la implanta ion
real de la lase X.
A los metodos Y::metodo() y Z::metodo() se les onnota omo \espe ializa iones"
del metodo en la lase base X::metodo().
Podra de irse que el polimor smo de heren ia es la sobre arga de metodos virtuales
entre las lases. Es importante resaltar que, en este aso, los prototipos de los metodos
virtuales tienen que ser identi os.
Re ordemos que el TAD hFigure 7i ontiene metodos virtuales, puros, que no se pueden
implantar. Si pretendiesemos dibujar un objeto de tipo hFigure 7i, se nos apare era la
pregunta: > ual gura?, pues el TAD hFigure 7i es una gura abstra ta, no on reta. Es
uando ono emos ual es la gura que tiene sentido indagar omo dibujarla.
La implanta ion de un metodo virtual puro se le delega a una lase derivada. Para
el aso del TAD hFigure 7i, sus hFiguras on retas 12i deben implantar sus metodos
virtuales puros. Por ejemplo, los metodos Square::draw() y Circle::draw() implantan
de manera diferente el dibujado de su orrespondiente gura. De este modo, uando se
opera sobre un objeto general de tipo hFigure 7i y se invo a a un metodo virtual, se
sele iona, en tiempo de eje u ion, el metodo de espe ializa ion segun la gura on reta
sobre la ual se este operando.
La virtud del polimor smo de heren ia es que permite es ribir programas generales
que manipulen \ guras" generales . Estos programas no requieren ono er las guras
on retas, pues operan en fun ion de metodos virtuales generales puros. Por ejemplo,
partes de programas para dibujar guras manipulan guras en abstra to, las dibujan, las
mueven, las rotan, et etera. A efe tos de e onomizar odigo, resulta util la posibilidad de
es ribir programas generales omo el del ejemplo siguiente:
hManejo general de Figure 15i
9

15

void release_left_button(const Action_Mode mode, Figure & fig)


{
switch (mode)
{
case Draw:
case Move:
9 De

nuevo, la redundan ia es adrede.

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

Polimorfismo de plantilla (tipos parametrizados)

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

Observemos que aunque la plantilla es la misma, o sea, es generi a, el odigo


generi o sequential search<int, are equals<int> >(...) es diferente al algoritmo
sequential search<string, are equals<string> >(...). El ompilador debe generar
dos odigos distintos; uno para arreglos de enteros (int) y el otro para arreglos de adenas (string).
Cuando se ejempli o la lase hFigure 7i, se indi o que su n es dibujarla en un medio
de ontraste. >De ual medio se habla: papel, pantalla de vdeo, televisor, holografa, ...?
El le tor a u ioso debe haberse per atado de que las implanta iones de la lase hFigure 7i
(Square, Triangle y Circle), tienen que asumir un medio en donde realizar las opera iones. Una manera de independizarse del medio de ontraste es ha er a la lase hFigure 7i
una plantilla uyo parametro sea, justamente, el medio de ontraste. La idea se ilustra en
el diagrama UML de la gura 1.4.
Medium_Type:Medium

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

En UML, los parametros plantilla de la lase se espe i an en un re tangulo punteado


situado en la esquina superior dere ha.
Un objeto de tipo hFigure 7i posee omo tipo parametrizado el medio en donde se
manipulan las guras. Puede ser ne esario que las espe ializa iones de hFigure 7i onoz an
el tipo on reto del medio de ontraste. Por esta razon, la version plantilla de hFigure 7i
exporta el tipo parametrizado bajo el nombre Medium Type. En C++ esta a ion se lleva a
abo mediante la siguiente de lara ion:
hObservadores de Figure 9ai+
(7) 9a
typedef Medium Medium_Type;

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

El problema fundamental de estructuras de datos

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.

1.3. El problema fundamental de estructuras de datos

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

Figura 1.5: Diagrama UML de una lase generi a onjunto (Set)

19

Set<T, Compare> puede modelizarse en C++ omo sigue:


hConjunto fundamental 19i

template <typename T, class Compare>


struct Set
{
void insert(const T & key);
T * search(const T & key);
void remove(const T & key);
const size_t size() const;
void swap(Set<T, Compare> & set);
void join(Set<T, Compare> * set);
void split(const T& key, Set<T, Compare> *& l, Set<T> *& r);
const int position(const T& key) const;
T * select(const int pos);
void split_pos(const int & pos, Set<T, Compare> *& l, Set<T, Compare> *& r);
};
Uses join 400a and select 411b.

En lneas generales, el problema fundamental se de ne omo el mantenimiento de un


onjunto Set<T, Compare> de elementos de tipo T on las opera iones basi as de inser ion,
busqueda, supresion y ardinalidad y uyas interfa es fundamentales son insert(),
search(), remove() y size(), respe tivamente.
Fre uentemente, los elementos del onjunto se denominan \ laves". De all el nombre
de parametro key en mu has de las interfa es de Set<T, Compare>.
swap(set) inter ambia todos los elementos de set on los de this. Seg
un la estru tura de datos on que se implante Set<T, Compare>, algunas ve es esta opera ion sera
muy rapida.
El metodo join(set) une this on el onjunto referen iado por el parametro set.
Despues de la opera ion, set deviene va o.
join() puede tener varia iones seg
un el tipo de onjunto y la estru tura de datos. Las

20

Captulo 1. Abstracci
on de datos

mas omunes son la on atena ion y la inter ep ion.


1.3.1

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

Operaciones para conjuntos ordenables

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

Circunstancias del problema fundamental

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

La ardinalidad de ide de entrada el tipo de memoria. Una ardinalidad muy grande


requerira memoria se undaria (dis o) o ter iaria (otros medios mas lentos) y esta de ision
afe ta radi almente el tipo de estru tura de datos.
Hay o asiones en que algunas laves son mas propensas a iertas opera iones que
otras. Por ejemplo, si las laves fuesen apellidos, enton es el saber que las letras \x"
o \y" son po o fre uentes, y que las vo ales son mas fre uentes, puede in idir en una
estru tura de datos que tienda a re uperar rapidamente laves que tengan, por ejemplo,
\a" omo segunda letra.
Segun el problema, algunas opera iones son mas probables que otras; in lusive, en
mu hos asos, no se utilizan todas las opera iones o la mayora de las a tividades sobre el
onjunto se on entran en una o po as opera iones. En estos asos, la estru tura de datos
puede dise~narse para optimizar la opera ion mas fre uente.
1.3.4

Presentaciones del problema fundamental

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

tada ha ia el on epto o abstra ion".


Una abstra ion orientada ha ia los datos es aquella uyo n esta en auzado por la organiza ion de los datos en el omputador. A la vez, tal organiza ion obede e a requerimientos
de desempe~no, ahorro de espa io o algun otro que ata~na al omputador, sistema operativo
u otros programas sistema. Este es el aso de mu has de las estru turas de datos que estudiaremos en este texto. Ejemplos de estas lases de orienta ion los onstituyen los arreglos,
las listas enlazadas y las diversas estru turas de arbol que seran estudiadas en este texto.
Mu hos problemas omputa ionales exhiben un patron de pro esamiento distintivo
y uniforme. En tales situa iones, puede ser muy onveniente disponer de una estru tura de datos que represente el orden o esquema de pro esamiento de los datos. En este
aso de imos que la estru tura esta orientada ha ia el ujo o al patron. Por ejemplo, si los
datos deben pro esarse segun el orden de apari ion en el sistema, enton es una disposi ion
de los datos en una se uen ia puede representar el orden de llegada. Tal estru tura se denomina \ ola" y sera estudiada en x 2.6. Notemos que en este aso no se piensa en la
organiza ion que los datos tengan en memoria, sino en el orden o patron en que estos se
pro esen.
Finalmente, el dise~no de una estru tura de datos puede fa ilitar la representa ion de
un on epto o abstra ion ono ida on miras a omprender el problema y, onsiguientemente, desenvolverse omodamente en su solu ion. En este aso, de imos que la estru tura de datos esta orientada ha ia el on epto. La idea es simpli ar al programador o a
los usuarios el entendimiento del problema y de su solu ion.
Hay mu hos aminos para resolver problemas. Cuentan que Mi hel Faraday, pre ursor
de la teora ele tromagneti a, no tena su iente forma ion matemati a para expli ar los
fenomenos ele tromagneti os de sus experimentos. La genialidad de Faraday lo ondujo a
rear sus propias abstra iones gra as, provenientes de sus observa iones experimentales,
a partir de las uales fundo y expli o el ele tromagnetismo. Al igual que Faraday, mu has
ve es reamos abstra iones que nos permiten omprender mejor un algoritmo. Estas abstra iones onforman estru turas de datos. Un ejemplo muy notable es el on epto de
grafo. Etimologi amente, el termino grafo proviene de \gra o", pues los grafos son expresados en terminos gra os. Sin embargo, en realidad, un grafo modeliza el on epto de
rela ion sobre el ual existe todo un orpus matemati o. A pesar del orpus y, quiza porque
este es in ompleto, los grafos ofre en una vision gra a de la rela ion matemati a on la
que es mas omoda trabajar. Esta es otra razon que justi a el dise~no de una estru tura
de dato: una manera de representar el problema en terminos mas sen illos.
Estos tres tipos de orienta ion de alguna forman lasi an el n o el \para que" se
dise~na o se sele iona una estru tura de datos. La lasi a ion no es exa ta ni ex luyente.
Una estru tura de datos puede en ajar a la vez, o en diferentes momentos, bajo todos o
ualquiera de los tipos de orienta ion. Pero determinar en fun ion de las ir unstan ias
ual es la orienta ion de una estru tura de datos, puede guiar al programador en su dise~no
o sele ion.
1.4.2

El principio fin-a-fin

Consideremos el TAD hFigure 7i y repitamos la pregunta fundamental: >para que sirve?


> ual es su nalidad? En la se ion x 1.1.4 se pretendio \generalizar opera iones inherentes
al dibujado de una gura sobre algun fondo de ontraste". Con este n de nido, las

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 de nido. 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 de nido 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

partir de esas parti ularidades, intentar de nir abstra iones.


En la programa ion a objetos, existen dos me anismos de generaliza ion: la heren ia
de lases y las plantillas. La heren ia on ierne a los datos, mientras que las plantillas al
odigo.
La heren ia se apli a para delinear generalidades y omportamientos omunes a una
ierta lase de objeto. Las lases derivadas lasi an y aportan parti ularidades de omportamiento general y niveles de abstra ion. Cuando se identi quen lases o TAD, busque
que es lo omun y eso llevelo a lo general a traves de lases bases o abstra tas.
Hay dos aspe tos a generalizar mediante la heren ia de lases. El primero lo omponen
los atributos; o sea las ara tersti as de un objeto dado. En el aso del TAD hFigure 7i, un
atributo general lo onforma el punto de referen ia omun a todas las guras parti ulares.
El segundo aspe to de generalidad es fun ional y ata~ne a las opera iones. En el aso del
TAD hFigure 7i, los metodos virtuales draw(), move(), et etera, generalizan opera iones
omunes a todas las guras.
Como ya lo indi amos, algunos algoritmos son sus eptibles de ser generi os bajo la
forma de plantilla. Consideremos el problema de ordenar una se uen ia de elementos que
sera tratado en el aptulo x 3. Notemos que el enun iado no men iona el tipo de elementos
a ordenar; solo espe i a que se trata de una se uen ia. Podemos ordenar apellidos, enteros
o elementos de ualquier otro tipo bajo el mismo esquema. Ordenar es independiente
del dato. En esta lase de problemas se pueden dise~nar un ordenamiento generi o uyo
parametro sera el tipo de elementos.
Es importante desta ar que un omportamiento generi o apare e despues de ono er
omportamientos on retos y no al ontrario. Ni siquiera uando se posea una amplia experien ia, no se debe programar odigo generi o sin antes haberlo veri ado exhaustivamente
on al menos un tipo de dato ono ido y on reto.
Para las dos te ni as de generaliza ion, heren ia y tipos parametrizados, el amino
omienza en lo on reto y se dirige ha ia lo abstra to, no al reves. Podemos de ir que este
es el estilo uando se dise~na y programa on sentido.
Conforme se gana experien ia on reta, un dise~nador puede onsiderar algunas generaliza iones a priori (no todas) sin aun ver las parti ularidades. Esto es dedu ion y es
posible despues de aprehender o dise~nar partes on retas de la solu ion.
La genialidad, uando o urre, es mirar en lo abstra to lo que puede devenir on reto. Ingenio signi a tener genio desde adentro (in). Genio que genera ideas, buenas,
por supuesto. Desde esta perspe tiva, ingeniera es, enton es, la pra ti a del in-genio;
pero, no se podra tener ingenio si siempre se exigiese permane er en lo on reto y se
supeditase a te ni as y metodos jos. Esta es la razon por la ual la intui ion nun a debe
ser des artada.
1.4.4

Ocultamiento de informaci
on

Un TAD solo espe i a el n de un dato y su interfaz. Cuando se a uerda un dise~no en


torno a un TAD, se a uerda una espe i a ion objetiva que no di e nada a er a de su
implanta ion. Esto es ono ido omo el prin ipio de o ultamiento de informa ion, el ual
onsiste en o ultar deliberadamente la implanta ion de un TAD, pues, omo ya lo dijimos,
esta onforma lo subjetivo, que, no solo es mu ho mas omplejo, sino que di ulta la
omuni a ion.

1.5. Notas y recomendaciones bibliogr


aficas

25

A ve es es bueno hablar de la implanta ion a nivel de interfaz. Por ejemplo, de ir


que hConjunto fundamental 19i esta implantado on arreglos ordenados propor iona una
idea a er a del desempe~no; se sabra, por ejemplo, que la busqueda es rapida, pero que la
inser ion y supresion son lentas. Notemos que en este aso no se de ne exa tamente omo
se implanta el TAD, sino se indi a, omo parte de la espe i a ion, un aspe to general de
la implanta ion.
Por tanto, la re omenda ion general de dise~no es que se o ulte lo mas que se pueda la
implanta ion. Pero, si por razones de desempe~no o de requerimientos resulta onveniente
estable er un tipo de implanta ion, enton es tratese esta en los terminos mas generi os
posibles.

1.5

Notas y recomendaciones bibliogr


aficas

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 polimor smo. 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 re namiento 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

ategora onsolidada de lase o odigo suele denominarsele \ omponente" o \patron". Un


repertorio bastante ri o de patrones generi os basi os puede en ontrarse en [4.
Si bien para dominar la programa ion se requieren algunos a~nos de experien ia omo
autor de programas, los textos de S ott Meyers [11, 12 onstituyen la mejor referen ia
para omprender, dominar y saber usar mu has de las idiosin rasias del C++.
La historia de UML [5 se remonta a OMT [14, un lenguaje gra o, pre ursor del
a tual UML, propuesto por James Rumbaugh, un ient o elebre de la programa ion
a objetos. El onsor io OMG (Obje t Management Group), en el ambito de los sistemas
distribuidos a objetos, tomo OMT omo base para desarrollar el a tual UML.

1.6

Ejercicios

1. Dise~ne e implante un TAD que represente numeros en punto otante y en el ual se


espe i que la pre ision; es de ir, el tama~no de la mantisa y del exponente.
2. Critique el TAD hComplejo 5i. >Que tan ompleto es? >Es orre ta su espe i a ion
semanti a? >Que problemas de dominio y resultados pueden o urrir?
3. Ample el TAD hComplejo 5i para manejar oordenadas polares. Dis uta donde
olo ar la amplia ion (en nuevos metodos, en una lase derivada, et etera)
4. Dise~ne e implante un TAD que represente numeros de pre ision arbitraria.
5. Revise fuentes de programas libres para dibujar omo Xfig y DIA e indague la jerarqua de lases on que ellos modelizan las guras.
6. Identi que los objetos fundamentales para la ondu ion de un automovil que se
en uentran en la abina. Para ada uno, establez a su n y las opera iones junto on
sus espe i a iones sinta ti a y semanti a.
7. Dise~ne indu tivamente una jerarqua de lases que represente veh ulos automotores.
Dibuje los diagramas UML.
8. Dise~ne dedu tivamente una jerarqua de lases que represente \viviendas". Dibuje
los diagramas UML.
9. Men ione tres o mas apli a iones en donde aparez a el problema fundamental de
las estru turas de datos. Para ada apli a ion, explique la forma en que apare e y
diserte brevemente a er a de omo se implantara.
10. Men ione tres o mas apli a iones en donde no aparez a el problema fundamental de
las estru turas de datos.
11. Men ione algunas estru turas de datos ono idas y dis uta su lasi a ion segun los
lineamientos expli ados en la se ion x 1.4.1.
12. Dado un onjunto S de nido por el tipo Set<T, Compare>, explique omo ono er
el elemento orrespondiente a la mediana en el sentido estadsti o.
13. Dado un onjunto S de nido por el tipo Set<T, Compare>, explique omo se programara, en fun ion de las primitivas de hConjunto fundamental 19i, la rutina:

1.6. Bibliografa

27

template <class __Set>


__Set extraer(__Set & set, int i, int j);

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.

li a iones - Consejo de Estudios de Postgrado, 2001.

[4 E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns. Addison-Wesley,


1995.
[5 Obje t Management Group. OMG Uni ed Modeling Language 2.0 Proposal, Revised submission to OMG RFPs ad/00-09-01 and ad/00-09-02, Version 0.671.
OMG, http://www.omg. om/uml/, 2002.
[6 D. H. H. Ingalls. Design prin iples behind smalltalk. BYTE, 6(8):286{298, August
1981.
[7 Ni olai M. Josuttis. The C++ Standard Library: a tutorial and referen e. pubAW, pub-AW:adr, 1999.
[8 Brian W. Kernighan and Dennis Rit hie. The C Programming Language, Se ond
Edition. Prenti e-Hall, 1988.
[9 Donald E. Knuth. Fundamental Algorithms, volume 1 of The Art of Computer
Programming. Addison-Wesley, Reading, MA, USA, third edition, 1997.
[10 Liskov and Zilles. Programming with abstra t data types. Sigplan Noti es, 9, 1974.
[11 S ott Meyers. E e tive C++. Addison Wesley, 1992.

28

Captulo 1. Abstracci
on de datos

[12 S ott Meyers. More E e tive C++. Addison Wesley, 1996.


[13 D. L. Parnas. A te hnique for software module spe i ation with examples. Communi ations of the Asso iation of Computing Ma hinery, 15(5):330{336, May 1972.
[14 James E. Rumbaugh. OMT: The obje t model. JOOP, 7(8):21{27, 1995.
[15 J. Saltzer, D. Reed, and D. Clark. End-to-end arguments in system design. ACM
Transa tions on Computer Systems, 2(4), 1984.
[16 Bjarne Stroustrup. The Design and Evolution of C++. Addison Wesley, Reading
Mass., 1994.
[17 Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, 3rd edition,
2000.
[18 Larry Tesler. The Smalltalk environment. Byte, 6(8):90{147, August 1981.

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 de ne 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

Hay varios aspe tos a onsiderar a la hora de operar sobre arreglos:


 Si los elementos est
an o no ordenados.
 Si se ono e el orden de inser ion/supresi
on de los elementos.
 Si se ono e on antela ion el n
umero de elementos.
 >Que tan iterativas ser
an las opera iones? Una alta intera tividad signi a que las in-

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

grande sea el elemento, menos bene ios se obtendran por el a he.

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": \A rma 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 su jo 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;
}
De nes:

binary search, used in hunks 33, 34b, and 754b.

binary search<T, Compare>() bus a una o urren ia del elemento x, de tipo


generi o T, dentro del rango omprendido entre l y r del arreglo ordenado a.

El prin ipio de la busqueda binaria es dividir el arreglo en dos partes iguales de


tama~no m = (l + r)/2 y, en aso de que a[m] no ontenga x, bus ar en alguna de las
mitades segun el orden de x respe to a a[m].
Notese que binary search<T, Compare>() retorna una posi ion valida aun si x no
se en uentra en el arreglo. Esto requiere que el liente, luego de la busqueda, ompare de
nuevo x on el valor de retorno para veri ar si x fue hallado o no. Aunque esto onlleva un
ligero oste, permite ono er la posi ion en donde se insertara x en el arreglo de manera
que este siga ordenado.
3 Abrevia i
on

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

Si el arreglo se mantiene desordenado, enton es la inser ion es rapidsima mediante


a~nadidura al nal de la se uen ia. De lo ontrario, la inser ion deviene mas ostosa, pues
se requiere bus ar en el arreglo la posi ion de inser ion, luego desplazar los elementos de la
se uen ia en una posi ion ha ia la dere ha y, nalmente, opiar el elemento. Tal algoritmo
se denomina \inser ion por apertura de bre ha\ y se instrumenta omo sigue:
hInser i
on por bre ha 33i
template <typename T, class Compare>
void insert_by_gap(T a[], const T & x, int & n)
{
int pos_gap = binary_search<T, Compare>(T, x, 0, n - 1);
for (int i = n; i > pos_gap; --i)
a[i] = a[i - 1];
a[pos_gap] = x;
++n;
}
De nes:

insert by gap, never used.


Uses binary search 32.

Este algoritmo se pi toriza del siguiente modo:


...

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

Compare>() o binary search<T, Compare>(), seg


un que el arreglo este o no ordenado. En

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;
}
De nes:

remove in unsorted array, never used.


Uses sequential search 193b.

remove in unsorted array<T, Equal>() tapa la bre ha on el u


ltimo elemento de la

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;
}
De nes:

remove in sorted array, never used.

Uses binary search 32.

La te ni a en uestion se pi toriza de la siguiente forma:


...

orden

...

...
pos gap

orden ...
++i

2.1. Arreglos

2.1.2

35

Manejo de memoria para arreglos

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

Arreglos en memoria est


atica

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

void foo(size_t n) { int array[n]; ...

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

Arreglos en memoria din


amica

Un arreglo en memoria dinami a es aquel apartado a traves del manejador de memoria.


En C++, por ejemplo, la siguiente instru ion reserva un arreglo de 1000 enteros:
4 En

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

int * array = new int [1000];

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;

Es importante resaltar que el operador delete [] debe imperativamente usar los


or hetes, pues esta es la manera de indi arle al ompilador que se libera un arreglo y
no un bloque de memoria. Se requiere de esta sintaxis porque es ne esario llamar a todos
los destru tores; uno por ada entrada del arreglo.
La memoria del arreglo debe liberarse apenas sea seguro que este no sera requerido.
El arreglo en memoria dinami a es la op ion de fa to para la mayora de las apli a iones
que usen arreglos de dimension mediana o grande. Delegue la veri a ion de rangos a
utilitarios de depura ion espe ializados tales omo dmalloc [20 o a valgrind [11.
2.1.3

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 a rmativa
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

que, omo se ve, representa un ahorro de memoria signi ativo.


Para manejar arreglos de bits, introdu iremos el TAD BitArray, el ual modeliza
ve tores de bits. Este TAD esta espe i ado e implantado en el ar hivo hbitArray.H 36i:
hbitArray.H 36i
class BitArray
{
private:

2.1. Arreglos

hMiembros

37

privados de BitArray 37ai

public:

ubli os de BitArray 39ai


hMiembros p
};
De nes:
BitArray, used in hunks 39, 40, 42, 426b, 428, 430, 431b, 437, 438a, and 766.
37a

Los atributos basi os de BitArray on iernen a su dimension:


hMiembros privados de BitArray 37ai
(36) 37b
h lase Byte 38ai
mutable size_t
mutable size_t

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

alma enar los bits requeridos.


La base del arreglo de bytes sera alma enada en:
hMiembros privados de BitArray 37ai+

(36) 37a 40a

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)

Figura 2.1: Diagrama UML del TAD BitArray


El \tru o" para realizar una implanta ion sen illa es disponer de un soporte que manipule los bits dentro de un byte. Di ho soporte esta dado por la sub lase BitArray::Byte,

38

38a

Captulo 2. Secuencias

la ual se de ne 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

de bit (never de ned)i

hConstru tor
};

de Byte 38 i

Basi amente, la sub- lase BitArray::Byte de ne 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

read_bit(const unsigned int & i) const

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 de nidos 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
}

~BitArray() { delete [] array_of_bytes; }


Uses BitArray 36.

39b

Para ono er la dimension del arreglo, se pueden emplear los metodos:


hMiembros p
ubli os de BitArray 39ai+
(36) 39a 40b
const size_t & get_len() const { return num_bits; }
const size_t & size() const { return

get_len(); }

size() es el nombre estandar de la bibliote a ALEPH y la estandar C++, pero get len()

se provee por razones de ompatibilidad.


El a eso a los elementos del arreglo se realiza mediante el operador []. Este operador
es ompli ado de espe i ar y de implantar porque este debe distinguir los a esos de
le tura de los def es ritura.
Dado el ndi e i de un bit, la posi ion byte index dentro de array of bytes se al ula
omo sigue:
$ %
byte index =

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

lase espe ial denominada \proxy", la ual se de ne omo sigue:


hMiembros privados de BitArray 37ai+
(36) 37b
class BitProxy
{
hmiembros privados

de BitProxy

40 i

public:
ubli os de BitProxy 40di
hmiembros p
};
De nes:
BitProxy, used in hunks 40 and 41.

40b

El operador [] de BitArray retorna un BitProxy, de manera tal omo sigue:


hMiembros p
ubli os de BitArray 39ai+
(36) 39b 41d
BitProxy operator [] (const size_t & i) const
{
return BitProxy(const_cast<BitArray&>(*this), i);
}

BitProxy operator [] (const size_t & i)


{
return BitProxy(*this, i);
}
Uses BitArray 36 and BitProxy 40a.

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

Tales atributos se ini ian en el onstru tor de BitProxy:


hmiembros p
ubli os de BitProxy 40di

(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.

Cuando se instan ia una lase BitProxy, es de ir, uando se invo a al operador []


de BitArray, el ampo byte ptr apunta al byte dentro de array of bytes que ontiene el bit que se desea a eder. bit index ontiene el ndi e del bit dentro del

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 de nir 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

La de ni ion anterior no trabajara on una expresion omo: array[40] = array[0],


pues la asigna ion toma un BitProxy omo operando dere ho y el ompilador no puede
onvertirlo a un int. Esto se resuelve de niendo el operador de asigna ion de un proxy a
un proxy:
hmiembros p
ubli os de BitProxy 40di+
(40a) 41b
BitProxy& operator = (const BitProxy & proxy)
{
byte_ptr->write_bit(bit_index, proxy.byte_ptr->read_bit(proxy.bit_index));

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
}

BitArray(const BitArray & array, const size_t & dim)


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])
{
// copiar contenido de array
const size_t n = Aleph::min(num_bytes, array.num_bytes);
for (int i = 0; i < n; ++i)
array_of_bytes[i] = array.array_of_bytes[i];
}
BitArray & operator = (const BitArray & array)
Exception_Prototypes(std::bad_alloc)
{
if (this == &array)
return *this;
// respaldar atributos por si ocurre excepci
on
Byte * old_array = array_of_bytes;
const size_t old_num_bits = num_bits;
const size_t old_num_bytes = num_bytes;
num_bits = array.num_bits;
num_bytes = array.num_bytes;
try
{
array_of_bytes = new Byte [num_bytes];

2.1. Arreglos

43

delete [] old_array;
h opiar

num bytes 43ai

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-de ni 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.

Notemos que mediante resize() no solo puede ambiarse la dimension para ha er el


arreglo mas grande, sino, tambien, para ha erlo mas peque~no. En este ultimo aso se
pierden los bits ex edentes.

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>

En esta se ion mostraremos toda la interfaz e implementa ion de un TAD, llamado


DynArray<T>, el ual modeliza un arreglo dinami o de elementos de tipo T.
DynArray<T> abstrae una \dimension a tual" on un valor ini ial espe i ado en
tiempo de onstru ion. Tal dimension a tual se ono e mediante el metodo size().
DynArray<T> garantiza un onsumo de memoria propor ional a la antidad de entradas es ritas del arreglo; es de ir, las entradas que no han sido es ritas no ne esariamente onsumen memoria. La memoria se reserva solo uando se es riben las entradas por
primera vez.
La dimension a tual se expande perezosa y automati amente uando se es ribe sobre
un ndi e que esta fuera de la dimension a tual. Si se referen ia un ndi e omo le tura
mayor o igual a la dimension a tual, enton es se genera la ex ep ion fuera de rango.
La dimension a tual puede ontraerse expl itamente mediante una opera ion llamada
cut(). La fun ion libera enton es toda la memoria o upada entre la nueva y anterior
dimension a tual.

2.1. Arreglos

45

45

DynArray<T> se espe i a en el ar hivo htpl dynArray.H 45i, el ual exporta la lase


parametrizada DynArray<T>:
htpl dynArray.H 45i
template <typename T>
class DynArray
{
private:
hmiembros

onstantes de DynArray<T> 47i

hmiembros

privados de DynArray<T> 49bi

public:
hmiembros
};

publi os de DynArray<T> 49 i

on onstantes DynArray<T> 57bi


hIni ia i
De nes:
DynArray, used in hunks 55{60, 66, 67, 310a, 383, 446{48, 504b, 753{56, 759a, 760, 762, 767b,
and 813 .
Es indispensable que exista el onstru tor por omision T::T(), as omo el destru tor T::~T(). La ausen ia de ualquiera de ellos se reporta omo error en tiempo de ompila ion.
Un DynArray<T> aso ia una dimension maxima. Un arreglo no puede expandirse
mas alla de aquella dimension. Por omision, la dimension maxima es 2 Giga entradas
(2 1024 1024 1024) .
7

2.1.5.1

Estructura de datos de DynArray<T>

La gura 2.2 (pagina 46) esquematiza la representa ion en memoria de un DynArray<T>,


la ual ilustra tres lases de omponentes:
Directorio: Arreglo de apuntadores a segmentos uya dimension es dir size.
Segmento: Arreglo de apuntadores a bloques uya dimension es seg size. Pueden existir
hasta dir size segmentos.
Bloque: Arreglo de elementos de tipo T uya dimension es block size. En total, pueden
existir hasta dir size seg size bloques. Los bloques alma enan los elementos
7 Sobre

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

.
.
.

.
.
.

.
.
.

.
.
.

Figura 2.2: Estru tura de datos de DynArray<T>


del arreglo. La dimension maxima del arreglo es dir size seg size block size
elementos.
El ara ter dinami o del arreglo reside en el he ho de que solo se utilizan los bloques y
segmentos que orresponden a elementos del arreglo que ya han sido es ritos o mediante
un metodo espe ial llamado touch().
Dado un ndi e i, el a eso impli a al ular la entrada en el dire torio, luego la entrada
en el segmento y, nalmente, la entrada en el bloque. Estos al ulos pueden realizarse
omo sigue:
Indice del segmento en el directorio: Cada segmento representa seg sizeblock size

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

Donde resto se de ne omo:

resto = i mod (seg size block size)

(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)

Sustituyendo (2.4) en (2.5):


pos in block = (i mod (seg size block size)) mod block size

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
De nes:

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;

Default Pow Block, used in hunk 57.


Default Pow Dir, used in hunks 56 and 57b.
Default Pow Seg, used in hunks 56 and 57b.
Max Bits Allowed, used in hunks 49b and 57b.
Max Dim Allowed, used in hunks 55{57.
Max Pow Block, used in hunk 57.

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;
De nes:
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)

seg size = 2pow seg


block size = 2pow block

Sustituyendo (2.7) y (2.8) en (2.2) tenemos:


pos in dir =
48b

2pow seg

i
i
= (pow seg+pow block)
pow
block
2
2

hmiembros onstantes de DynArray<T> 47i+


mutable size_t seg_plus_block_pow;
De nes:
seg plus block pow, used in hunks 50b, 55, 56, 59, and 60a.

(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)

Ahora debemos en ontrar una manera de al ular rapidamente el o iente y el resto de


una division entre una poten ia exa ta de 2. Para el aso del al ulo de la expresion (2.9), ya
sabemos que el o iente es el resultado de desplazar i ha ia la dere ha seg plus block pow
ve es. De esta manera, la expresion (2.9) resulta, mediante desplazamientos, en:
pos in dir = i seg plus block pow

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;
De nes:
mask seg plus block, used in hunks 50b, 55, 56, 59, and 60a.

2.1. Arreglos

49

mask seg plus block se al ula del siguiente modo:


mask seg plus block = 2seg plus block pow 1

(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

enmas ara los bits a la izquierda de seg plus block pow.


hmiembros onstantes de DynArray<T> 47i+

(45) 48 51a

mutable size_t dir_size;


// = 2^pow_dir
mutable size_t seg_size;
// = 2^pow_seg
mutable size_t block_size; // = 2^pow_block
De nes:
block size, used in hunks 49 , 53b, 55, 56, 58{60, and 65a.
dir size, used in hunks 49 , 52, 54{56, 59, and 60a.
seg size, used in hunks 49 , 52, 54{56, 59, 60a, and 63.
Uses pow block 48a, pow dir 48a, and pow seg 48a.

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;
}
De nes:

two raised, used in hunks 55 and 56.


Uses Max Bits Allowed 47.

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 veri quen 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; }
De nes:
get block size, never used.

50

Captulo 2. Secuencias

get dir size, never used.


get seg size, never used.
Uses block size 49a, dir size 49a, and seg size 49a.

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;
De nes:
mask block, used in hunks 50b, 55, 56, and 58{60.
mask seg, used in hunks 55, 56, 59, and 60a.

mask seg se denota por la siguiente expresion:


mask seg = 2pow seg 1 = seg size 1 ;

(2.14)

Lo que permite un al ulo para la posi ion en el bloque:


pos in seg = resto >> pow block

(2.15)

Donde resto es (2.13).


mask block se denota por:
mask block = 2pow block 1 = block size 1

(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;
}

size_t modulus_from_index_in_dir(const size_t & i) const


{
return (i & mask_seg_plus_block);
}
size_t index_in_seg(const size_t & i) const
{
return modulus_from_index_in_dir(i) >> pow_block;
}
size_t index_in_block(const size_t & i) const

2.1. Arreglos

51

{
return modulus_from_index_in_dir(i) & mask_block;
}
De nes:
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
De nes:
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

del dire torio, segmento y bloque.


hmiembros privados de DynArray<T> 49bi+

(45) 50b 52a

size_t current_dim;
size_t num_segs;
size_t num_blocks;
De nes:
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

de segmentos y de bloques que han sido reservados.


Los valores de algunos de los miembros anteriores pueden observarse mediante:
hmiembros p
ubli os de DynArray<T> 49 i+
(45) 49 54a
const size_t & size() const { return current_dim; }
const size_t & max_size() const { return max_dim; }
const size_t & get_num_blocks() const { return num_blocks; }
Uses current dim 51b, max dim 51a, and num blocks 51b.

52

Captulo 2. Secuencias

2.1.5.2

Manejo de memoria

En el manejo de memoria se deben onsiderar los siguientes aspe tos:


 El valor NULL indi a que una entrada de dire torio o segmento no tiene memoria

apartada. Esto impli a que uando un bloque es liberado, la entrada en el dire torio
debe ser restaurada al valor NULL.

 Los segmentos y bloques reservados o liberados deben ontabilizarse.

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;

dir representa el apuntador al dire torio.

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

hmiembros privados de DynArray<T> 49bi+


void fill_dir_to_null()
{
for (size_t i = 0; i < dir_size; ++i)
dir[i] = NULL;
}

(45) 52a 52

void fill_seg_to_null(T ** seg)


{
for (size_t i = 0; i < seg_size; ++i)
seg[i] = NULL;
}
De nes:
fill dir to null, used in hunk 52 .
fill seg to null, used in hunk 52 .
Uses dir size 49a and seg size 49a.

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

verbo \ini ializar" ya es parte \real" de la lengua espa~nola.

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;
}
De nes:
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

En el aso de ini ializar un bloque, debemos onsiderar la posibilidad de que el usuario


de DynArray<T> espe i que un valor ini ial. Para ello, guardaremos en los siguientes
ampos un valor ini ial de T de todas las entradas de un bloque:
hmiembros privados de DynArray<T> 49bi+
(45) 52 53b
T default_initial_value;

T * default_initial_value_ptr;
De nes:
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;
}
De nes:
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

Para espe i ar un valor por omision en la reserva ion de un bloque, generalmente


distinto al dado por T::T(), se usa la siguiente primitiva:
hmiembros p
ubli os de DynArray<T> 49 i+
(45) 51 55
void set_default_initial_value(const T & value)
{
default_initial_value
= value;
default_initial_value_ptr = &default_initial_value;
}
De nes:
set default initial value, used in hunk 760.
Uses default initial value 53a and default initial value ptr 53a.

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

hmiembros privados de DynArray<T> 49bi+


void release_segment(T **& seg)
{
delete [] seg;
seg = NULL;
--num_segs;
}

(45) 53b 54

void release_block(T *& block)


{
delete [] block;
block = NULL;
--num_blocks;
}
De nes:
release block, used in hunks 54 and 65d.
release segment, used in hunks 54 , 62, 65e, and 69 .
Uses num blocks 51b and num segs 51b.

54

Estos metodos se en argan de liberar onsistentemente un segmento o un bloque. Otra


manera de liberar involu ra un segmento on todos sus bloques, o todos los segmentos y
bloques del arreglo o el dire torio:
hmiembros privados de DynArray<T> 49bi+
(45) 54b 58a
void release_blocks_and_segment(T ** & seg)
{
for(size_t i = 0; i < seg_size ; ++i)
if (seg[i] != NULL)
release_block(seg[i]);

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;
}
De nes:

release all segments and blocks, used in hunk 64.


release blocks and segment, never used.
Uses current dim 51b, dir size 49a, release block 54b, release segment 54b, and seg size 49a.

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

El onstru tor puede generar la ex ep ion std::bad alloc si no se en uentra la


memoria mnima ne esaria o la ex ep ion std::length error si la maxima dimension resultante mediante las poten ias de dos dadas ex ede el maximo por
omision Max Dim Allowed. Eventualmente, las llamadas a two raises() pueden disparar
la ex ep ion std::overflow error.
El onstru tor por omision es muy similar pero plantea una sutileza a er a de la sele ion del tama~no del bloque:
hmiembros p
ubli os de DynArray<T> 49 i+
(45) 55 58b
DynArray(const size_t & dim = 10000)
throw (std::exception, std::bad_alloc,
std::length_error, std::overflow_error)
: pow_dir
( Default_Pow_Dir
pow_seg
( Default_Pow_Seg

on
hDetermina i

),
),

de la poten ia de 2 del bloque 57ai

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)

),
),
),
),
),
),
),
),
),
),
),

{
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 de nida
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)

Max Pow Block = 32 Default Pow Dir Default Pow Seg 1 = 21

Lo que ha e una maxima dimension dire ionable on el onstru tor por omision de:
2Default

57a

Pow Dir

2Default Pow Seg 221 = 24 26 221 = 536870912 ,

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;

template <typename T> const size_t DynArray<T>::Max_Pow_Block =


(Max_Bits_Allowed - Default_Pow_Dir - Default_Pow_Seg - 1);
Uses Default Pow Block 47, Default Pow Dir 47, Default Pow Seg 47, DynArray 45, Max Bits Allowed 47,
Max Dim Allowed 47, and Max Pow Block 47.

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)));
}
De nes:
next2Pow, used in hunk 57a.

58b

El destru tor esta de nido omo sigue:


hmiembros p
ubli os de DynArray<T> 49 i+

(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

void copy_array(const DynArray<T> & src_array)


{
for (int i = 0; i < src_array.current_dim; ++i)
if (src_array.exist(i))
(*this)[i] = src_array.access(i);
}
De nes:
copy array, used in hunk 59.
Uses access 60b, current dim 51b, DynArray 45, and exist 61a.

copy array() ha e uso de dos metodos p


ubli os que implantaremos mas adelante.
src array.exist(i) retorna true si la i-esima entrada existe en src array; false, de lo
ontrario. src array.access(i) retorna la i-esima entrada de src array sin veri ar que

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

size_t modulus_by_block_size(const size_t & number) const


{
return number & mask_block;
}
void
advance_block_index(size_t &
block_index,
size_t &
seg_index,
const size_t & len) const
{
if (block_index + len < block_size)
{
block_index += len;
return;
}
seg_index += divide_by_block_size(len);
block_index = modulus_by_block_size(len);
}
De nes:

advance block index, never used.


modulus by block size, never used.
Uses block size 49a, mask block 50a, and pow block 48a.

59

De nida 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);
}
De nes:
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.

El a eso a un elemento de un DynArray<T> puede realizarse dire tamente mediante:

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

const T & access(const size_t & i) const


{
return dir[index_in_dir(i)][index_in_seg(i)][index_in_block(i)];
}
De nes:
access, used in hunks 58 , 70, 753e, 756a, 760, and 761a.
Uses index in block 50b, index in dir 50b, and index in seg 50b.

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

const size_t pos_in_dir = index_in_dir(i);


if (dir[pos_in_dir] == NULL)
return false;
const size_t pos_in_seg = index_in_seg(i);
if (dir[pos_in_dir][pos_in_seg] == NULL)
return false;
return true;
}
De nes:
exist, used in hunks 58 , 70, 749a, 756a, 760, and 761a.
Uses index in dir 50b, index in seg 50b, pos in dir 67a, and pos in seg 67a.

61b

hveri ar rango 61bi


if (i > max_dim)
throw std::out_of_range ("index out of maximun range");
Uses max dim 51a.

(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)];
}
De nes:
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;
}
}
De nes:

reserve, never used.

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

des endentemente los bloques del segmento

hLiberar

el segmento

65ai

65ei

current_dim = new_dim; // actualiza nueva dimensi


on
}
De nes:
cut, used in hunks 59, 509a, 662{64, 758a, and 760.
Uses current dim 51b, index in dir 50b, index in seg 50b, and release all segments and blocks 54 .

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

El valor ini ial se de ne, enton es, del siguiente modo:


hLiberar des endentemente los bloques del segmento 65ai

(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 de ne, 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

Acceso mediante operador []

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

dato del proxy 67ai

public:
hM
etodos
};

66b

del proxy 67bi

Esta lase es el valor de retorno del operador [] en la lase DynArray<T>:


hmiembros p
ubli os de DynArray<T> 49 i+
(45) 64
Proxy operator [] (const size_t & i) const
throw (std::exception, std::bad_alloc, std::length_error,
std::invalid_argument)
{
if (i > max_dim)
throw std::out_of_range ("index out of maximun range");
return Proxy (const_cast<DynArray<T>&>(*this), i);
}
Proxy operator [] (const size_t & i)
throw (std::exception, std::length_error,
std::invalid_argument, std::bad_alloc)
{
if (i > max_dim)
throw std::out_of_range ("index out of maximun range");
return Proxy (const_cast<DynArray<T>&>(*this), i);
}
Uses DynArray 45 and max dim 51a.

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

se apartan. Puede generarse std::bad alloc si no hay memoria, o std::length error si


el ndi e i es mayor o igual que la maxima dimension.
La dimension se ajusta automati amente segun la mayor posi ion es rita. Si un a eso
de le tura a la i-esima entrada dete ta que el bloque no ha sido apartado, enton es se
genera la ex ep ion std::invalid argument.
El a eso de le tura a una entrada que no ha sido es rita no siempre ausa una ex ep ion, pues la entrada ya puede tener su bloque apartado; empero, en este aso el valor
de la le tura es indeterminado.
La idea del operador [] es retornar una lase Proxy en espera de ono er si el a eso
es de le tura o de es ritura.
Los miembros datos de Proxy mantienen la informa ion su iente para validar y efe tuar el a eso:
hMiembros dato del proxy 67ai
(66a)
size_t index;
size_t pos_in_dir;
size_t pos_in_seg;
size_t pos_in_block;
T**&
ref_seg;
T*
block;
DynArray&
array;
De nes:
pos in block, used in hunks 67 and 68.
pos in dir, used in hunks 61, 62, and 67b.
pos in seg, used in hunks 61, 62, 67b, and 69 .
ref seg, used in hunks 67b and 69.
Uses DynArray 45.

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

No se efe tua ninguna a ion uando se destruye un proxy.


Un Proxy onstruido esta listo para a eder a una posi ion dada. Segun el ontexto
de invo a ion del operador [], el ompilador determinara si el a eso es de le tura o de
es ritura. Cuando o urra una le tura, el ompilador intentara onvertir el Proxy al tipo
generi o T. Tal onversion se de ne del siguiente modo:
hM
etodos del proxy 67bi+
(66a) 67b 68b
operator T & ()
{
if (block == NULL)
throw std::invalid_argument("accessed entry has not been still written");

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

ontrario, se retorna el elemento referido.


La es ritura puede o urrir uando se efe tua una asigna ion, la ual se de ne para
un Proxy de las dos siguientes formas:
hM
etodos del proxy 67bi+
(66a) 68a
Proxy & operator = (const T & data)
{
hObtener o apartar el segmento y el bloque 69bi
hVeri ar

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

o apartar el segmento y el bloque 69bi


y a tualizar la dimension

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

o apartar el segmento y el bloque 69bi realiza dos pasos:

1. Obtener el segmento:
hObtener o apartar el segmento y el bloque 69bi
69b

(68b) 69

bool seg_was_allocated_in_current_call = false;

if (ref_seg == NULL) // hay segmento?


{
// No ==> apartarlo!
array.allocate_segment(ref_seg);
seg_was_allocated_in_current_call = true;
}
Uses allocate segment 52 and ref seg 67a.

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

if (block == NULL) // test if block is allocated


{
try // tratar apartar bloque
{
array.allocate_block(block);
ref_seg[pos_in_seg] = block;
}
catch (...) // Ocurre una falla en el apartado del bloque
{
if (seg_was_allocated_in_current_call)
array.release_segment(ref_seg);
throw;
}

}
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

Uso del valor por omisi


on

Hemos re al ado el he ho de que la memoria de un DynArray<T> se aparta perezosamente


en tiempo de es ritura. Tambien hemos desta ado que esta ara tersti a permite ahorrar
memoria en apli a iones que usen arreglos espar idos.
>Que su ede uando se a ede a una entrada que no ha sido es rita?. Para estudiar
esta uestion, supongamos un a eso de le tura a una i-esima entrada; ante este evento,
podemos plantear los siguientes es enarios:
1. La i-esima entrada ha sido previa y expl itamente es rita, en uyo aso se retornara
el ultimo valor es rito.
2. La entrada no ha sido previamente es rita. En esta situa ion pueden o urrir dos
osas:
(a) Que no se haya apartado un bloque para la i-esima entrada, en uyo aso se
trata de un error de programa ion (le tura de un valor que no ha sido es rito) el ual, en el mejor de los es enarios, generara una ex ep ion. Esta sera
la situa ion si el a eso se realiza mediante el operador []. Si el a eso se
realiza mediante access(), enton es, on suerte, o urrira la ex ep ion sistema
segmentation~fault. Por el ontrario, on mala suerte, no o urrira ninguna
ex ep ion, lo que tendera a o ultar el error.
(b) Que se s se haya apartado el bloque durante la es ritura de alguna entrada en
el mismo bloque de la i-esima entrada. En este aso, se retornara el valor que
haya es rito el onstru tor por omision T::T(). Notemos la posibilidad, no tan
ex ep ional, de que este onstru tor por omision no reali e ninguna es ritura,
en uyo aso el valor de retorno puede ser ualquiera, segun el estado del bloque.

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();

2.2. Arreglos multidimensionales

71

const long index_dynarray = i + j*n; // posici


on dentro de mat
if (not mat.exist(index))
return Zero_Value;
return mat.access(index);
}
Uses access 60b and exist 61a.

Lo guardado en la onstante index dynarray se orresponde on onsiderar a una matriz


omo un arreglo de arreglos . Si el predi ado not mat.exist(index) es ierto, enton es
no hay memoria apartada para el bloque que ontendra la entrada (i,j), razon por la
ual se retorna un valor por omision denominado en este aso Zero Value; de lo ontrario,
retornamos mat.access(index) pues el a eso esta garantizado si el ujo llega a esa lnea.
Puede presentarse la situa ion des rita en 2b, es de ir, mat.access(index) no
ha sido previamente es rito, pero not mat.exist(index) == true. En valor de retorno sera, enton es, el espe i ado por T::T() o el asignado en una llamada a
set default initial value().
set default initial value() permite espe i ar diversos valores ini iales para el
mismo tipo, lo que sera muy util para ampos algebrai os diferentes que manipulen el
mismo tipo de operando; las matri es de adya en ia en los grafos, por ejemplo.
10

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;

Asigna dato al j-esimo elemento de la i-esima la.


El ompilador genera automati amente el al ulo de la dire ion de a eso, el ual se
puede enun iar de la siguiente forma:
Dire ion del elemento = base + i m sizeof(T) + j sizeof(T)

(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

2.2 (pagina 71).

72

Captulo 2. Secuencias

Aleph::DynArray

 Default_Pow_Dir : const size_t


 Default_Pow_Seg : const size_t
 Default_Pow_Block : const size_t
 Max_Bits_Allowed : const size_t
 Max_Dim_Allowed : const size_t
 Max_Pow_Block : const size_t
 pow_dir : size_t
 pow_seg : size_t
 pow_block : size_t
 seg_plus_block_pow : size_t
 mask_seg_plus_block : size_t
 dir_size : size_t
 seg_size : size_t
 block_size : size_t
 max_dim : size_t
 mask_seg : size_t
 mask_block : size_t
 current_dim : size_t
 num_segs : size_t
 num_blocks : size_t
 dir : T***
 default_initial_value : T
 default_initial_value_ptr : T*
 two_raised(n : const size_t&) : const size_t
 index_in_dir(i : const size_t&) : const size_t
 modulus_from_index_in_dir(i : const size_t&) : const size_t
 index_in_seg(i : const size_t&) : const size_t
 index_in_block(i : const size_t&) : const size_t
 fill_dir_to_null()
 fill_seg_to_null(seg : T**)
 allocate_dir()
 allocate_segment(seg : T**&)
 allocate_block(block : T*&)
 release_segment(seg : T**&)
 release_block(block : T*&)
 release_blocks_and_segment(seg : T**&)
 release_all_segments_and_blocks()
 release_dir()
 next2Pow(number : const size_t&) : const size_t
 divide_by_block_size(number : const size_t&) : const size_t
 modulus_by_block_size(number : const size_t&) : const size_t
 advance_block_index(block_index : size_t&, seg_index : size_t&, len : const size_t&)
+ get_dir_size() : const size_t&
+ get_seg_size() : const size_t&
+ get_block_size() : const size_t&
+ size() : const size_t&
+ max_size() : const size_t&
+ get_num_blocks() : const size_t&
+ set_default_initial_value(value : const T&)
+ DynArray(_pow_dir : const size_t&, _pow_seg : const size_t&, _pow_block : const size_t&)
+ DynArray(dim : const size_t&)
+ ~ DynArray()
+ copy_array(src_array : const DynArray< T >&)
+ DynArray(array : const DynArray< T >&)
+ operator =(array : const DynArray< T >&) : DynArray< T >&
+ swap(array : DynArray< T >&)
+ access(i : const size_t&) : T&
+ exist(i : const size_t&) : const bool
+ touch(i : const size_t&) : T&
+ reserve(l : const size_t&, r : const size_t&)
+ cut(new_dim : const size_t&)
+ operator [ ](i : const size_t&) : const Proxy

Aleph::DynArray::Proxy

0..1

+array

Figura 2.3: Diagrama UML de la lase DynArray<T>

 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

permane en onstantes durante la vida de la matriz. Si bien esto no es tan problemati o,


pues se podra generar odigo para n y m variables, un ambio en alguna de las dimensiones
obliga a desplazar la mayora de los elementos.
Por las razones anteriores, es preferible tratar expl itamente a un arreglo multidimensional omo lo que es: una se uen ia de se uen ias. Bajo esta perspe tiva, una matriz
n m puede de lararse omo sigue:
T ** matriz = new T * [n]; // Aparta un arreglo de punteros a filas
for (int i = 0; i < n; ++i)
matriz[i] = new T [m]; // aparta la fila matriz[i]

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

Figura 2.4: Diagrama UML de la lase generi a Iterator<Set, T>

e0

e1

e2

...

ei

ei+1

...

en2

en1

Figura 2.5: Representa ion pi tori a de un interador sobre una se uen ia


Cuando se instan ia un iterador on un ontenedor Set<T>, este queda posi ionado en
el primer elemento de la se uen ia designado en el dibujo omo e0.
El elemento designado omo en no existe en la se uen ia, pero este logi amente denota que el iterador esta \desbordado" o \fuera de rango". La primitiva has current()
retorna false si el iterador se en uentra posi ionado en el elemento ti io en; true de
lo ontrario.
En algunas situa iones puede onvenir saber si el iterador esta posi ionado en el
primer o ultimo elemento. Si el iterador se en ontrase en el primer elemento, enton es
is in first() retornara true; la misma semanti a se apli ara on is in last() si el
iterador se en ontrase en el ultimo elemento.
El elemento a tual del iterador se onsulta mediante el metodo get current().
En el aso general, segun la implanta ion de Set<T>, el iterador genera la ex ep ion std::overflow error si se intenta a eder a un iterador desbordado.
Para salvaguardar el estado de un iterador pueden onstruirse iteradores sin que se
propor ione el onjunto. Obviamente, un iterador sin onjunto no es valido hasta que este
no se de na mediante alguna de las dos versiones del operador de asigna ion.
Para \avanzar" el iterador ha ia la \dere ha" o ha ia \adelante" se eje uta
el metodo next(). Analogamente, para retro ederlo ha ia la \izquierda" o ha-

en

2.4. Listas Enlazadas

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:

1. El primer elemento, t1, se a ede dire tamente en tiempo onstante.


2. Para a eder al elemento Ti, es ne esario a eder se uen ialmente los i 1
previos elementos < t1, t2, t3, . . . , ti1 >.

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

siempre es propor ional al numero de elementos; es de ir, puede expresarse omo


una fun ion f(n) = k n.

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

de vida de la apli a ion.

 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

Figura 2.6: Una lista simplemente enlazada

2.4. Listas Enlazadas

77

77

Cada elemento de la se uen ia se representa mediante una \ aja" dividida en dos


ampos: el elemento de la se uen ia y el apuntador a la proxima aja. Una aja se denomina
\nodo".
El punto de entrada a una lista es la dire ion del primer nodo, denominado head en
la gura 2.6.
En C o C++, un nodo puede espe i arse omo sigue:
hDe ni i
on de nodo simple 77i
struct Node
{
char
data;
Node * next;
};

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 re ere 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

Figura 2.7: Una lista doblemente enlazada


El osto en espa io de una lista doblemente enlazada se re ompensa on re es on su
versatilidad. Dado un elemento pertene iente a la lista, las opera iones pueden referir al
elemento prede esor o al su esor. Conse uentemente, la lista puede re orrerse en los dos
sentidos, de izquierda a dere ha o vi eversa.
Quiza la gran virtud de una lista doblemente enlazada es la apa idad de \autoelimina ion". Cualquier nodo posee todo el ontexto ne esario (prede esor y su esor) para
el mismo eliminarse de la lista.
En la gura 2.7 se usan dos puntos de entrada; uno por ada extremo. Es posible y
mas versatil si se ierran los enla es y se ha e a la lista \ ir ular" tal omo lo ilustra
la gura 2.8. En este aso, basta on mantener un puntero al primer nodo de la lista para
tener a eso a su otro extremo.

78

Captulo 2. Secuencias

first
A

Figura 2.8: Una lista doblemente enlazada ir ular


En a~nadidura, tal omo lo estudiaremos posteriormente, si se olo a omo abe era un
nodo adi ional, el ual no forma parte logi a de la se uen ia, enton es se gana en linealidad
de ujo, pues no hay que onsiderar los asos parti ulares uando la lista este o devenga
va a.
Las te ni as de ir ularidad y de nodo abe era tambien pueden apli arse a listas simplemente enlazadas; aunque la ganan ia en versatilidad no es tan notable omo on las
doblemente enlazadas.
2.4.1

Listas enlazadas y el principio fin a fin

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

2.4. 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 (enlace simple)

El TAD Slink, de nido 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

publi os de Slink 80bi

on de onversion de Slink
hfun i
# endif /* SLINK_H */

a lase 83i

De nes:

Slink, used in hunks 80, 81, 83, 84, and 89d.

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

Figura 2.10: Lista enlazada simple on Slink

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");

h opiar Slink 81ai

void reset()
{
next = this;
}
bool is_empty() const
{
return next == this;
}

2.4. Listas Enlazadas

81

Slink * get_next() const


{
return next;
}
Uses Slink 79.

81a

h opiar Slink 81ai


if (not link.is_empty())
throw std::invalid_argument ("link is not empty");

(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.

insert next() inserta el lazo p despues de this.


81

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-

lazada. Hay dos formas de ha erlo. La primera es por deriva ion:


class Window : public slink
{
...
};

En este aso podemos insertar dire tamente instan ias de Window despues de un nodo
parti ular first:

82

Captulo 2. Secuencias

Window * window_ptr = new Window (...);


...
first->insert_next(window_ptr);

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());

Como first->remove next() retorna un Slink, la onversion lo modi a para que se


trate omo de tipo Window, el ual es el aso por deriva ion.
La segunda forma para enlazar lases es por olo a ion de un Slink omo atributo
de Window:
class Window
{
...
Slink link;
};

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.

2.4. Listas Enlazadas

83

static Window * link_to_window(Slink *& link)


{
Window * ptr_zero
= 0;
size_t offset_link
= reinterpret_cast<char*>(&(ptr_zero->link));
char * address_result = reinterpret_cast<char*>(link) - offset_link;
return static_cast<Window *>(address_result);
}

83

No tenemos forma de ono er los nombres de los atributos Slink ni su antidad. No


podemos, pues, ofre er una rutina generi a que nos onvierta un Slink a la lase que
ontenga el Slink, pero s podemos ofre er un ma ro que prepare el terreno:
hfun i
on de onversion de Slink a lase 83i
(79)
# define SLINK_TO_TYPE(type_name, link_name) \
static type_name * slink_to_type(Slink * 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); \
}
De nes:
SLINK TO TYPE, never used.
Uses Slink 79.

El ma ro tiene dos parametros. type name es el nombre de la lase que alberga un


Slink. link name es el nombre de un atributo de tipo Slink ontenido dentro de la
lase type name.
Mediante el ma ro SLINK TO TYPE, el usuario dispone de una implanta ion de onversion de Slink a su lase. El ma ro se debe in luir dentro de la lase que utili e el Slink.
Por ejemplo, para la lase Window:
class Window
{
/* ... */
SLINK_TO_TYPE(Window, link);
};

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

El TAD Snode<T> (nodo simple)

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

de ne 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

privados de Snode<T> 84bi

public:
hmiembros p
ubli os de Snode<T> 84 i
};
De nes:
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

T& get_data() { return data; }

84d

Hay dos maneras de onstruir un Snode<T>:


hmiembros p
ubli os de Snode<T> 84 i+

(84a) 84 84e

Snode() { /* empty*/ }

Snode(const T & _data) : data(_data) { /* empty */ }


Uses Snode 84a.

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

La misma sobre arga debe realizarse para get next():


hmiembros p
ubli os de Snode<T> 84 i+

(84a) 84e
Snode * get_next() const { return static_cast<Snode*>(Slink::get_next()); }
Uses Slink 79 and Snode 84a.

2.4. Listas Enlazadas

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> (lista simplemente enlazada)

El TAD Slist<T> abstrae una lista simplemente enlazada ir ular denodos simples, la
ual se de ne en el ar hivo htpl slist.H 86ai, en el ual se de ne la lase en uestion:
htpl slist.H 86ai
template <typename T>
class Slist : public Snode<T>
{
public:
hde ni iones
hm
etodos

de Slist<T> 86bi

publi os de Slist<T> 86 i

hiterador de Slist<T> 87 i
};
De nes:
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:
hde ni iones de Slist<T> 86bi
(86a)
typedef Snode<T> Node;
Uses Snode 84a.

86

Esta de lara ion exporta el tipo Slist<T>::Node sinonimo de Snode<T>.


Slist<T> no tiene ning
un miembro dato expl ito. La instan ia heredada de
Snode<T> funge de nodo abe era de la lista. Al llamar al onstru tor:
hm
etodos publi os de Slist<T> 86 i
(86a) 86d
Slist() { /* empty */ }
Uses Slist 86a.

86d

se rea una lista va a uya abe era es this.


La uni a inser ion posible es al prin ipio de la lista tal omo sigue:
hm
etodos publi os de Slist<T> 86 i+
(86a) 86 86e
void insert_first(Node * node)
{
this->insert_next(node);
}

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.

2.4. Listas Enlazadas

87a

87b

87

hveri ar desborde negativo 87ai


if (this->is_empty())
throw std::underflow_error ("list is empty");

(86e 87b)

remove first() elimina el primer elemento de la lista y retorna su dire ion. Si la


lista esta va a, enton es se genera la ex ep ion std::underflow error.
El usuario de Slist<T> puede ono er la dire ion del primer nodo mediante:
hm
etodos publi os de Slist<T> 86 i+
(86a) 86e
Node * get_first() const Exception_Prototypes(std::underflow_error)
{
hveri ar desborde negativo 87ai
return this->get_next();
}

El resto de los elementos pueden a ederse a traves de la interfaz de Slist<T>::Node,


la ual es la misma de Snode<T>. El usuario puede efe tuar inser iones y supresiones de
la lista a traves de esta va.
2.4.5
87

Iterador de Slist<T>

Slist<T> exporta un iterador bajo la sub- lase:


hiterador de Slist<T> 87 i
class Iterator
{
private:
hmiembros privados

public:
hmiembros
};

87d

(86a)

de iterador de Slist<T> 87di

publi os de iterador de Slist<T> 87ei

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.

list es un apuntador a la lista enlazada sobre la ual se itera y current es el elemento

87e

a tual del iterador.


De resto, el iterador se de ne simplemente omo sigue:
hmiembros p
ubli os de iterador de Slist<T> 87ei

(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

hveri ar std::overflow error() 88ai


if (not this->has_current())
throw std::overflow_error ("");
Uses has current 103.

(87e)

El siguiente ejemplo visita todos los elementos de una lista:


for (typename Slist<int>::Iterator itor(list); itor.has_current(); itor.next())
// procesar itor.get_current()->get_data();

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 de ni 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
};
De nes:
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

2.4. Listas Enlazadas

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

nodo prede esor al elemento a tual.


Estamos listos para espe i ar la onstru ion:
hMiembros p
ubli os de DynSlist<T> 89bi

(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.

DynSlist<T> exporta un iterador uya espe i a ion es muy simple al ha erla


derivada de Slist<T>::Iterator.

2.4.7

90

El TAD Dlink (enlace doble)

En el mismo espritu de dise~no que el TAD Slink, el TAD Dlink de ne 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

2.4. Listas Enlazadas

class Dlink
{
hmiembros

91

protegidos de Dlink 91ai

public:
hmiembros
};

publi os de Dlink 91bi

on de Dlink a lase 100i


hma ros onversi
De nes:
Dlink, used in hunks 91, 92b, 94{103, 105, 106, 114{16, 190{92, 203, 204a, 212, 213a, 222, 226b, 228,
229, 323, 530{32, 535a, 651b, 655{57, 661a, 672 , 673a, 676, 677, 685b, and 690a.
A partir de este momento es muy importante puntualizar y distinguir los siguientes
nes:
1. El n de un Dlink es fungir de nodo abe era de una lista ir ular doblemente enlazada. En este sentido, las opera iones de la lase Dlink re eren a listas doblemente enlazadas, ir ulares, uyo nodo abe era es this.
2. El n de un Dlink* es fungir de apuntador a un nodo pertene iente a una lista ir ular doblemente enlazada.
91a

Dlink posee dos atributos protegidos:


hmiembros protegidos de Dlink 91ai

(90)

protected:
mutable Dlink * prev;
mutable Dlink * next;
Uses Dlink 90.

next y prev son apuntadores al su esor y prede esor de this.


Un nuevo Dlink siempre se rea on sus lazos apuntando a s mismo; es de ir, omo
91b

un nodo abe era uya lista esta va a:


hmiembros p
ubli os de Dlink 91bi

(90) 91

Dlink() : prev(this), next(this) { /* empty */ }


Uses Dlink 90.

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(); }

Dlink & operator = (const Dlink & l)


{
if (this == &l)
return *this;
if (not is_empty())
throw std::invalid_argument ("left list must be empty");
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);

2.4. Listas Enlazadas

93

Aleph::swap(next, link->next);
}
Uses Dlink 90.

this

swap

swap

swap

swap

link

Figura 2.12: Opera ion swap(link)


swap() es una gran opera ion, pues, aparte de que su tiempo de eje u ion es es-

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;
}

Un punto a desta ar de estas opera iones es que no requieren ontabilizar la antidad


de nodos de la lista.

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

La inser ion omo prede esor se denomina append() y se de ne omo sigue:


hmiembros p
ubli os de Dlink 91bi+
(90) 94a 94
void append(Dlink * node)
{
node->next = this;
node->prev = prev;
prev->next = node;
prev
= node;
}
Uses Dlink 90.
1: node->prev = this;

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()

2.4. Listas Enlazadas

95

{
return prev;
}
Uses Dlink 90.

this

head

(a) Antes de eje utar insert list(head)

this

head

(b) Despues de eje utar insert list(head)

Figura 2.14: Inser ion de lista dentro de una lista

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;

}
De nes:

append list, used in hunks 114a and 118b.


insert list, used in hunks 114a and 118b.
Uses Dlink 90.

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

void concat_list(Dlink * head)


{
if (head->is_empty())
return;
if (this->is_empty())
{
swap(head);
return;
}
prev->next
head->next->prev
prev
head->prev->next

=
=
=
=

head->next;
prev;
head->prev;
this;

head->reset();
}
De nes:
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.

2.4. Listas Enlazadas

97

concat list, used in hunks 212, 222a, 226b, 228 , and 509a.

Uses Dlink 90.

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();
}

Los pasos de del() se muestran en la gura 2.15.


1: prev->next = next;

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;
}
De nes:
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, used in hunks 99a, 106 , and 119a.

Uses Dlink 90.

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;

Dlink tmp_head; // cabecera temporal donde se guarda lista invertida


int counter = 0;
// recorrer toda la lista this, eliminar primero e insertar en tmp_head
while (not is_empty())
{
Dlink * current = remove_next(); // eliminar primero de la lista
tmp_head.insert(current); // insertarlo de primero en tmp_head
counter++;
}
// tmp_head contiene la lista invertida y this est
a vac
a ==> swap
swap(&tmp_head);
return counter;
}
De nes:
reverse list, never used.
Uses Dlink 90 and remove next 97b.

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 on able: 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

puede de ir que el segundo dupli a en velo idad al primero.

2.4. Listas Enlazadas

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;

while (not is_empty())


{
l.append(remove_next()); ++count;
if (is_empty())
break;
r.insert(remove_prev()); ++count;
}
return count;
}
De nes:

split list, used in hunks 116a and 213a.


Uses Dlink 90, remove next 97b, and remove prev 97b.

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

// quitar de this todo lo que est


a a partir de link
prev = link->prev;
link->prev->next = this;
link->prev = list;
list->prev->next = list;
}
De nes:

cut list, used in hunk 119b.


Uses Dlink 90.

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

Figura 2.16: Corte de una lista en un nodo dado

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); \
}

# define LINKNAME_TO_TYPE(type_name, link_name) \


inline static type_name * link_name##_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); \
}
De nes:
DLINK TO TYPE, never used.
LINKNAME TO TYPE, used in hunks 324a and 530 .
Uses Dlink 90.

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

2.4. Listas Enlazadas

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

publi os iterador Dlink::Iterator

101 i

Para mantener el estado del iterador requerimos dos miembros:


hatributos Dlink::Iterator 101bi

(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

Hay varias maneras de onstruir un iterador:


hmiembros p
ubli os iterador Dlink::Iterator 101 i

(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
}

itor) : head(itor.head), curr(itor.curr)

Iterator() : head(NULL), curr(NULL) { /* Empty */ }


Uses Dlink 90.

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

iterador sobre el ultimo elemento.


Existen situa iones espe iales en las uales se desea ini ializar un iterador a partir de
un elemento a tual ya ono ido. Para ello, se proveen las siguientes fun iones:
hmiembros p
ubli os iterador Dlink::Iterator 101 i+
(101a) 102b 103
void set(Dlink * new_curr)
{
curr = new_curr;
}

2.4. Listas Enlazadas

103

void reset(Dlink * new_head, Dlink * new_curr)


{
head = new_head;
curr = new_curr;
}
void reset(Dlink * new_head)
{
head = new_head;
curr = head->get_next();;
}
Uses Dlink 90.

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;
}

Dlink * get_current() const


{
if (not has_current())
throw std::overflow_error("Not element in list");
return curr;
}
bool is_in_first() const
{
return curr == head->next;
}
bool is_in_last() const
{
return curr == head->prev;
}
De nes:
get current, used in hunks 87e, 105, 112, 117{20, 125{28, 190b, 194a, 195b, 203, 253, 474a, 507b,
510a, 680 , 685b, 694b, 747, and 749b.
has current, used in hunks 87e, 88a, 104a, 105 , 118a, 120 , 125 , 127, 128, 190b, 194a, 195b, 203,
253, 473a, 474a, 500, 507b, 510a, 657a, 661, 666{68, 674, 675a, 681a, 685b, 687b, 695b, 698b, 702,
705, 706, 708{10, 712, 713, 715, 719a, 720, 724, 726 , 727, 738 , 740, 745{49, 757, 760, 765b, 775b,
787, 797 , 799a, 801 , 814 , 816, 818b, 821, and 822a.
is in first, never used.
is in last, never used.
Uses Dlink 90.

104

104a

Captulo 2. Secuencias

Para avanzar el iterador utilizamos las siguientes primitivas:


hmiembros p
ubli os iterador Dlink::Iterator 101 i+
(101a) 103 104b
void prev() throw(std::exception, std::underflow_error)
{
if (not has_current())
throw std::underflow_error("Not previous element in list");
curr = curr->get_prev();
}
void next() throw(std::exception, std::overflow_error)
{
if (not has_current())
throw std::overflow_error("Not next element in list");
curr = curr->get_next();
}
Uses has current 103.

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

2.4. Listas Enlazadas

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

forma que pre era.


En o asiones es ne esario ono er la lista sobre la ual se itera. Por ejemplo, onsidere
la siguiente primitiva lasi a de los ontenedores en la bibliote a estandar C++:
list.insert(iterator pos, iterator beg, iterator end)

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; }

bool verify(const Iterator & it) const { return head == it.head; }


Uses Dlink 90.

105

El primer verify() retorna true si el iterador referen ia a la lista l, mientras que el


segundo retorna true si el iterador referen ia a la misma lista que referen ia el iterador
it.
Como otro ejemplo de uso de Dlink::Iterator onsideremos el siguiente metodo:
hmiembros p
ubli os de Dlink 91bi+
(90) 101a
void remove_all_and_delete()
{
Dlink * node;
Iterator itor(this);

// recorrer y eliminar los nodos


while (itor.has_current())
{
node = itor.get_current();
itor.next();
node->del();
delete node;
}
}
Uses Dlink 90, get current 103, and has current 103.

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

El TAD Dnode<T> (nodo doble)

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 de ne 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
};
De nes:
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); }

Dnode<T> *& get_prev() { return reinterpret_cast<Dnode<T>*&> (prev); }


Dnode<T>* remove_prev() { return static_cast<Dnode<T>*>(Dlink::remove_prev()); }
Dnode<T>* remove_next() { return static_cast<Dnode<T>*>(Dlink::remove_next()); }
Uses Dlink 90, Dnode 106a, remove next 97b, and remove prev 97b.

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

Un punto sumamente importante es el tipo de dato de Dnode<T>; este se exporta


mediante la siguiente de lara ion:
hm
etodos publi os Dnode<T> 106 i+
(106a) 106d
typedef T dnode_type;

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:

2.4. Listas Enlazadas

107

typename Node::dnode_type data;

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

a la obten ion del elemento a tual.


Dlink

#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

Node:<template <class> class


T:class

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

El TAD Dlist<T> (lista circular doblemente enlazada)

En el siguiente nivel de manejo de listas doblemente enlazadas, en ontramos el tipo


Dlist<T>, el ual maneja una lista doblemente enlazada, ir ular, on nodo abe era,
de nodos de tipo Dnode<T>.
Dlist<T> se de ne e implanta en el ar hivo htpl dlist.H 108ai, uya estru tura es la
siguiente:
htpl dlist.H 108ai
on de nodos dobles 108bi
hDe ni i
hEnvoltorio
hLista

doble generi a 110ai

hListas

2.4.9.1

de nodos dobles 109i

dobles extremos 110bi


El problema de los destructores virtuales

En el mismo estilo que Slist<T>, la lase Dlist<T> debe exportar una sub lase
Dlist<T>::Node que de na 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:
hDe ni i
on de nodos dobles 108bi
(108a)
template <typename T>
class Dlist_Node : public Dnode<T>
{
public:
Dlist_Node() : Dnode<T>() { /* Empty */ }

2.4. Listas Enlazadas

109

Dlist_Node(const T & _data) : Dnode<T>(_data) { /* Empty */ }


};
template <typename T>
class Dlist_Node_Vtl : public Dlist_Node<T>
{
public:
Dlist_Node_Vtl() : Dlist_Node<T>() { /* Empty */ }
virtual ~Dlist_Node_Vtl() { /* Empty */ }
Dlist_Node_Vtl(const T & _data) : Dlist_Node<T>(_data) { /* Empty */ }
};
De nes:
Dlist Node, used in hunk 110b.
Dlist Node Vtl, used in hunk 110b.
Uses Dnode 106a.

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

Un algoritmo de ordenamiento requiere re orrer nodos de la lista. Esto se ha e a traves


de get next() y get prev(). Cualquier tipo de lase derivada de Dlist Node pueden utilizarse omo nodo. Sin embargo, puesto que get next() retorna un puntero a Dlist Node,
el ompilador re hazara todas las lneas donde se asigna el resultado get next() a un puntero de una lase derivada de Dlist Node.
Para evitar el problema anterior, requerimos que get next() y get prev() retornen
punteros a la lase derivada y no a la lase Dlist Node. Una solu ion es utilizar una lase
envoltoria de la lase nodo utilizada que efe tue las onversiones y retorne punteros del
tipo orre to. Tal lase tiene la siguiente espe i a ion:
hEnvoltorio de nodos dobles 109i
(108a)
template <template <class> class Node, typename T>
class MetaDlistNode : public Node<T>
{
public:
MetaDlistNode() { /* Empty */ }

MetaDlistNode(const T& _data) : Node<T>(_data) { /* Empty */ }

110

Captulo 2. Secuencias

MetaDlistNode(const MetaDlistNode<Node, T> & node) : Node<T>(node)


{ /* Empty */ }
MetaDlistNode<Node, T> *& get_next()
{
return reinterpret_cast<MetaDlistNode<Node, T> *&>(Node<T>::get_next());
}
MetaDlistNode<Node, T> *& get_prev()
{
return reinterpret_cast<MetaDlistNode<Node, T> *&>(Node<T>::get_prev());
}
};
De nes:
MetaDlistNode, used in hunks 110a and 111a.

110a

MetaDlistNode no tiene miembros dato ni fun iones virtuales. La lase se deriva


publi amente de una lase parametro Node<T> que debe ser des endiente de Dlist Node.
En esen ia, el rol de MetaDlistNode es garantizar que las fun iones get next()
y get prev() siempre retornen punteros del mismo tipo.
El manejo de listas se realiza, enton es, en fun ion de nodos de tipo MetaDlistNode:
hLista doble gen
eri a 110ai
(108a)
template <template <class> class Node_Type, typename T>
class GenDlist : public MetaDlistNode<Node_Type, T>
{
public:
ubli os GenDlist 111ai
hMiembros p
};
De nes:
GenDlist, used in hunks 110b and 112.
Uses MetaDlistNode 109.

110b

GenDlist<T> hereda parte de interfaz e implanta ion de Dnode<T>. En a~


nadidura, la
heren ia ha e que this sea el nodo abe era de la lista. Ahora on retamos dos espe ializa iones de GenDlist<T>:
hListas dobles extremos 110bi
(108a)
template <typename T>
class Dlist : public GenDlist<Dlist_Node, T>
{
public:
typedef typename GenDlist<Dlist_Node, T>::Node Node;

typedef typename GenDlist<Dlist_Node, T>::Iterator Iterator;


};
template <typename T>
class DlistVtl : public GenDlist<Dlist_Node_Vtl, T>
{
public:
typedef typename GenDlist<Dlist_Node_Vtl, T>::Node Node;
typedef typename GenDlist<Dlist_Node_Vtl, T>::Iterator Iterator ;

2.4. Listas Enlazadas

111

};
De nes:

Dlist, used in hunks 113{20.


DlistVtl, never used.
Uses Dlist Node 108b, Dlist Node Vtl 108b, and GenDlist 110a.

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

hveri ar Under ow 111 i


if (this->is_empty())
throw std::underflow_error ("List is empty");

(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
}

Iterator(GenDlist<Node_Type, T> & list, GenDlist<Node_Type, T> * curr)


: Dnode<T>::Iterator(&list, curr)
{
// Empty
}
Iterator() : Dnode<T>::Iterator() { /* empty */ }
Iterator(const Iterator & it) : Dnode<T>::Iterator(it) { /* Empty */ }
Iterator & operator = (const Iterator & it)
{
if (this == &it)
return *this;
Dnode<T>::Iterator::operator = (it);
return *this;
}
Node * get_current()
{
return static_cast<Node*>(Dnode<T>::Iterator::get_current());
}

2.4. Listas Enlazadas

113

Node * del() { return static_cast<Node*>(Dnode<T>::Iterator::del()); }


};
Uses Dnode 106a, GenDlist 110a, and get current 103.

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
};
De nes:
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

La onstru ion es muy simple, pues, a la ex ep ion del manejo de memoria y de la


ontabiliza ion del numero de elementos, todo esta implantado desde la lase Dlist<T>:
hm
etodos publi os DynDlist<T> 113bi+
(113a) 113b 113d
DynDlist() : num_elem(0) { /* Empty */ }
Uses DynDlist 113a.

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

void append(const T & _data) throw(std::exception, std::bad_alloc)


{
typename Dlist<T>::Node* ptr = new typename Dlist<T>::Node (_data);
Dlist<T>::append(ptr);
++num_elem;
}
Uses Dlist 110b.

114a

Las mismas opera iones se de ne 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();
}

T & get_last() throw(std::exception, std::underflow_error)


{
return Dlist<T>::get_last()->get_data();
}
Uses Dlist 110b.

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();

2.4. Listas Enlazadas

hobtener

115

dato y liberar ptr 115ai

T remove_last() throw(std::exception, std::underflow_error)


{
typename Dlist<T>::Node* ptr = Dlist<T>::remove_last();
hobtener dato y liberar ptr 115ai
}
Uses Dlist 110b.

115a

hobtener dato y liberar ptr 115ai


T retVal = ptr->get_data();
delete ptr;

(114 )

--num_elem;
return retVal;

remove first() suprime el primer elemento, remove last() el u


ltimo. A diferen ia

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

Esta primitiva debe ser invo ada por el destru tor:


hm
etodos publi os DynDlist<T> 113bi+

(113a) 115b 115d

~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

La lase guarda un apuntador a la lista on el proposito de mantener el ontador de


elementos; de esta manera se puede a tualizar el ontador uando se eje uten opera iones
sobre el iterador que modi quen la antidad de elementos.
El atributo pos re eja la posi ion ordinal dentro de la se uen ia del elemento a tual y
puede observarse mediante:
hm
etodos iterador de DynDlist<T> 116 i
(116b) 116d
const int & get_pos() const { return pos; }

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

2.4. Listas Enlazadas

117

const int & next()


{
Dlist<T>::Iterator::next();
pos++;
return pos;
}
const int & prev()
{
Dlist<T>::Iterator::prev();
pos--;
return pos;
}
Uses Dlist 110b.

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.

La inser ion y supresion mediante el iterador de Dlist<T> es dire ta porque


Dlist<T> maneja elementos de tipo Dnode<T> que poseen sus propias primitivas de

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;
}

void append(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()->append(node);
++list_ptr->num_elem;
}
Uses Dlist 110b and get current 103.

118

118a

118b

Captulo 2. Secuencias

hveri ar over ow 118ai


(117{19)
if (not this->has_current())
throw std::overflow_error ("DynDlist Iterator has not current");
Uses DynDlist 113a and has current 103.

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.

La supresion la vemos omo la supresion del elemento a tual:

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:

2.4. Listas Enlazadas

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 de nir 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

Del mismo modo, el onstru tor opia:


hm
etodos publi os DynDlist<T> 113bi+

(113a) 120a

DynDlist(const DynDlist & list) : Dlist<T>(list)


{
this->reset();
h opiar lista 120 i
}
Uses Dlist 110b and DynDlist 113a.

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 de ne a ontinua ion:
hpolinom.C 120di
# include <tpl_dynDlist.H>
class Polinomio
{

2.4. Listas Enlazadas

hMiembros
public:
hinterfaz
};

privados Polinomio 122ei

de Polinomio 121ai

hImplementa i
on

121a

121

de metodos Polinomio 124bi

Hay varias maneras de rear un polinomio, la primera es por omision:


hinterfaz de Polinomio 121ai
(120d) 121b
Polinomio();

121b

Este onstru tor rea el polinomio 0x0.


Para onstruir un polinomio ualquiera, el punto de partida sera un polinomio de un
solo termino:
hinterfaz de Polinomio 121ai+
(120d) 121a 121
Polinomio(const int& coef, const size_t & pot);

Este onstru tor instan ia el polinomio coef xpot; por ejemplo:


Polinomio p(20, 7);

121

Instan ia un polinomio on valor 20x7.


Si queremos onstruir un polinomio mas omplejo, enton es podemos sumar varios
terminos mediante el operador de suma:
hinterfaz de Polinomio 121ai+
(120d) 121b 121d
Polinomio operator + (const Polinomio&) const;

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

Por razones de e ien ia que dis utiremos posteriormente, es onveniente ofre er la


siguiente version de la suma:
hinterfaz de Polinomio 121ai+
(120d) 121 121e
Polinomio& operator += (const Polinomio&);

Mediante este operador, el polinomio anterior puede onstruirse omo sigue:


Polinomio p(20, 7);
p += Polinomio(3, 3) + Polinomio(1, 2) + Polinomio(20, 0);
121e

El produ to de dos polinomios se espe i a mediante el operador *:


hinterfaz de Polinomio 121ai+
(120d) 121d 121f
Polinomio operator * (const Polinomio&) const;

121f

Otras opera iones, que se delegan a ejer i ios, son:


hinterfaz de Polinomio 121ai+
Polinomio operator - (const Polinomio&) const;
Polinomio operator / (const Polinomio&) const;
Polinomio operator % (const Polinomio&) const;

(120d) 121e 122a

122

122a

Captulo 2. Secuencias

Estas opera iones orresponden a la substra ion, division y residuo.


Las apli a iones que utili en polinomios requeriran una forma de a eder a sus
terminos. Para ello, debemos proveer un onjunto mnimo de primitivas. Un atributo
esen ial de un polinomio es ono er la antidad de terminos:
hinterfaz de Polinomio 121ai+
(120d) 121f 122b
const size_t & get_num_terms() const;

122b

Requerimos ono er ada uno de los terminos, esto lo podemos ha er mediante:


hinterfaz de Polinomio 121ai+
(120d) 122a 122
size_t get_power(size_t i) const;

get power() retorna la poten ia orrespondiente al i-esimo termino diferente de ero

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

retorna el oe iente orrespondiente al i-esimo termino.


El grado del polinomio puede onsultarse mediante:
hinterfaz de Polinomio 121ai+

(120d) 122

const size_t & get_degree() const;

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;

2.4. Listas Enlazadas

4 6

123

2 3

-1 2

1 1

7 0

Figura 2.18: Representa ion de 4x6 + 2x3 x2 + x + 7 on listas enlazadas

size_t pot;
ermino 123bi
hMiembros T
};
De nes:
Termino, used in hunks 123{28.

123a

La lista enlazada ontiene, enton es, terminos:


hMiembros privados Polinomio 122ei+

(120d) 122e 123

DynDlist<Termino> terminos;
Uses DynDlist 113a and Termino 122e.

123b

Hay dos formas de onstruir un termino:


hMiembros T
ermino 123bi

(122e) 123d

Termino() : coef(0), pot(0) { /* empty */ }

Termino(int c, size_t p) : coef(c), pot(p) { /* empty */ }


Uses Termino 122e.

123

A nivel privado es importante onstruir un Polinomio a partir de un termino:


hMiembros privados Polinomio 122ei+
(120d) 123a 128
Polinomio(const Polinomio::Termino& termino)
{
terminos.append(termino);
}
Uses Termino 122e.

123d

La suma de terminos se de ne omo sigue:


hMiembros T
ermino 123bi+

Termino& operator += (const Termino& der)


{
I(pot == der.pot);
if (this == &der)
return *this;
coef += der.coef;
return *this;

}
Uses Termino 122e.

(122e) 123b 124a

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

Termino operator * (const Termino & der) const


{
return Termino(coef*der.coef, pot + der.pot);
}
Uses Termino 122e.

124b

De nida la onstru ion de un termino, podemos de nir la onstru ion de polinomios:


hImplementa i
on de metodos Polinomio 124bi
(120d) 124
Polinomio::Polinomio(const int & coef, const size_t & pot)
{
terminos.append(Termino(coef, pot));
}

Polinomio::Polinomio() { /* empty */ }
Uses Termino 122e.

Suma de polinomios

La suma de dos polinomios P1 =

Pn

i=0 cix

y P2 =

max(n,m)

P1 + P2 =

Pm

j=0 djx

se de ne 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

La suma es extremadamente simple porque subya e en la llamada al operador +=, el


ual posee la siguiente estru tura:
hImplementa i
on de metodos Polinomio 124bi+
(120d) 124 127
Polinomio & Polinomio::operator += (const Polinomio& der)
{
on de Polinomio::operator += 125ai
hImplanta i
return *this;
}

2.4. Listas Enlazadas

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.

La gura 2.19 ejempli a el polinomio 6x6+7x5x4+3x2+4x omo operando izquierdo,


y 8x8 + x6 2x2 + 2 omo operando dere ho.
itor izq

6x6

7x5

x4

3x2

8x8

x6

2x2

4x

itor der

Figura 2.19: Suma de polinomios

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

const size_t & der = itor_der.get_current().pot;


Uses get current 103.

izq y der deben pro esarse seg


un los siguientes asos:

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

ha sido pro esado. Finalmente, repetimos el pro edimiento.

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

2.4. Listas Enlazadas

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

La multipli a ion de dos polinomios P1 =


P1P2 =

n
X
i=0

cixi

Pn

i=0 cix

m
X
j=0

y P2 =

kjxj

Pm

j=0 djx

se de ne 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 if veri a que si alguno de los polinomios es nulo, enton es el produ to es nulo.


El for es exa tamente la version odi ada del algoritmo 2.1: ada termino del polinomio
izquierdo se multipli a enteramente por el polinomio dere ho y el resultado es a umulado en el polinomio result. El fragmento der.por termino(itor izq.get current()
orresponde a la multipli a ion de un polinomio por un termino; opera ion que de nimos
a ontinua ion:
hMiembros privados Polinomio 122ei+
(120d) 123
Polinomio multiplicado_por(const Termino& term) const
{
Polinomio result;
if (terminos.is_empty() or term.coef == 0)
return result;
DynDlist<Termino>::Iterator itor(const_cast<DynDlist<Termino>&>(terminos));
while (itor.has_current())
{
Termino new_term(itor.get_current().coef * term.coef,
itor.get_current().pot + term.pot);
result.terminos.append(new_term);
itor.next();
}
return result;
}
Uses DynDlist 113a, get current 103, has current 103, and Termino 122e.

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

Figura 2.20: Abstra ion de una pila


Gra amente, podemos ver una pila omo un re ipiente on una sola entrada tal
omo en la gura 2.20. Cuando se inserta un elemento, los elementos previamente insertados se \empujan" ha ia abajo. Por esa razon, la opera ion de inser ion se denomina
omunmente \push". El extremo del re ipiente, por donde entran y salen los elementos,
se denomina \tope", el ual es el uni o punto de manipula ion de la pila.
Alegori amente hablando, uando se a tiva una supresion, el resorte empuja los elementos ha ia arriba y el elemento situado en el tope \salta" ha ia afuera. Por esa razon,
a la supresion de una pila se le denomina omunmente \pop".
Esen ialmente, una pila se utiliza uando se requiere un ujo de pro esamiento de
elementos inverso a la se uen ia de observa ion o de entrada; es de ir, desde el mas re ientemente observado hasta el mas antiguamente observado. Cualquier patron de pro esamiento que visite elementos de un onjunto en este orden es sus eptible de implantarse
on una pila.
La vida real ofre e algunos ejemplos de la dis iplina pila. Cualquiera de nosotros habra
experien iado el refran popular que reza \los ultimos seran los primeros".
El lavado manual de platos en un fregadero domesti o sigue la dis iplina pila. Probablemente, los platos se \apilan" segun el orden de ulmina ion de la omida; es de ir,
el tope de la pila ontiene el plato de la ultima persona en ulminar su omida. Cuando
lavamos los platos, los enjabonamos y los apilamos en el segundo re ipiente del fregadero.
Posteriormente, enjuagamos los platos y los apilamos en el es urridero. Este ejemplo utiliza tres pilas y re eja un tipo de pro esamiento en el ual lo importante es ulminar
e azmente el trabajo y no el orden en que los platos son pro esados.
14 En

ingles LIFO (last in, rst out).

130

2.5.1

Captulo 2. Secuencias

Representaciones de una pila en memoria

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

Elemento mas antiguo

tope

Figura 2.21: Representa ion de una pila mediante un arreglo


En una lista enlazada requerimos mantener un apuntador al primer nodo. Puesto que
solo nos interesa el tope, la se uen ia de la lista esta invertida; es de ir, el primer nodo
de la lista alma ena el tope y el ultimo alma ena el mas antiguo. La gura 2.22 ilustra la
representa ion.
La sele ion de representa ion dependera de las ir unstan ias. En lo que ata~ne al espa io, quiza un fa tor onsiderable es la maxima antidad de elementos que pueda ontener
la pila, la ual depende de la apli a ion.
Las listas a arrean un sobre oste por elemento debido al apuntador adi ional. Si se
ono e la maxima antidad de elementos y la apli a ion la aprove ha, enton es esta laro
que el arreglo es mas ompa to que la lista.

Elemento mas antiguo

tope

Figura 2.22: Representa ion de una pila on listas enlazadas


Otro fa tor a evaluar es la varia ion maxima del tama~no de la pila. Podemos ono er el
maximo, pero si este es muy po o probable, enton es, en promedio, puede ser mas barato
en espa io una representa ion on listas que enfoque el tama~no promedio. La sele ion
se efe tuara onsiderando la desvia ion tpi a del tama~no de la pila. Sea el l el tama~no
promedio de la pila, l la desvia ion tpi a, lmax el tama~no maximo que puede al anzar
la pila, Ts el espa io o upado por un elemento de la pila y ps el espa io o upado por
apuntador, enton es, si
(l + l)(Ts + ps) lmaxTs ,

enton es, en promedio, la lista o upara menos espa io que el arreglo.


En lo que on ierne el tiempo de eje u ion, el arreglo es la representa ion mas rapida.
Puesto que los elementos del arreglo son se uen iales en memoria, la lo alidad espa ial y

2.5. Pilas

131

temporal es aprove hada y el a he del omputador logra su ometido. Este no es el aso


on las listas donde los nodos estaran en dire iones de memoria diferentes.
Por otra parte, uando se inserta o se suprime un elemento en una lista enlazada, puede
invo arse al manejador de memoria. El manejo de memoria impone un sobre oste adi ional
en una lista respe to al arreglo. La e ien ia del manejador de memoria para apartar un
bloque es muy variable y puede ser propor ional a la antidad de bloques apartada. La
misma onsidera ion es valida uando se libera un bloque.
2.5.2

131a

El TAD ArrayStack<T> (pila vectorizada)

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 de nen 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
};

publi os de ArrayStack<T> 132ai

template <typename T, const size_t dim = 100>


class FixedStack
{
hmiembros privados de pila ve torizada 131bi
public:
ubli os de FixedStack<T> (never de ned)i
hmiembros p
};
De nes:
ArrayStack, used in hunks 132a, 141, 151d, 301{4, and 612b.
FixedStack, used in hunks 221, 571a, and 592b.

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

array es un arreglo estati o de elementos de tipo T on dimension dim. dim es un parametro


de la plantilla que representa la dimension del arreglo y on un valor por omision de 100.
T:class
dim:size_t

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

Figura 2.23: Diagrama UML de la lase ArrayStack<T>

132a

head es el ndi e de la proxima entrada disponible. Tambien indi a la antidad de


elementos de la pila. head - 1 es el ndi e del elemento tope.
La onstru ion de una pila solo requiere ini ializar head:
hmiembros p
ubli os de ArrayStack<T> 132ai
(131a) 132b

ArrayStack() : head(0) { /* empty */ }


Uses ArrayStack 131a.

132b

Para insertar un elemento se usa el metodo push():


hmiembros p
ubli os de ArrayStack<T> 132ai+
(131a) 132a 132d

T & push(const T & data) throw(std::exception, std::overflow_error)


{
hpush data 132 i
}

132

132d

hpush data 132 i


array[head++] = data;
return array[head - 1];

(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

n elementos 133ai es muy simple on un arreglo; basta on in rementar head:

hapartar n elementos 133ai


head += n;
return array[head - 1];

(132d)

Para sa ar un elemento de la pila, utilizamos pop():


hmiembros p
ubli os de ArrayStack<T> 132ai+
(131a) 132d 133e
T pop() throw(std::exception, std::underflow_error)
{
hVeri ar Under ow 133 i
hpop data 133di
}

133

hVeri ar Under ow 133 i


if (head == 0)
throw std::underflow_error ("array stack underflow");

133d

hpop data 133di


return array[--head];

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

En algunos ontextos, a esta opera ion se le llama \multipop".


hliberar n elementos 133f i

(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

referen ia al tope 133hi

hretornar referen ia al tope 133hi


return array[head - 1];

(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

Una vez validado el rango de a eso, el elemento es a edido mediante:


hreferen ia al i-
esimo respe to a tope 134ai
(133i)
return array[head - i - 1];

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

Esta fa ilidad mere e ofre erse en una primitiva:


hmiembros p
ubli os de ArrayStack<T> 132ai+
(131a) 133i 134d
void empty() { hva iar pila 134bi }
Podemos onsultar un predi ado que nos diga si la pila esta o no va a:
hmiembros p
ubli os de ArrayStack<T> 132ai+
(131a) 134 134f
bool is_empty() const { hpila va a? 134ei }
hpila va a? 134ei
return head == 0;

(134d)

La antidad de elementos de la pila es dire tamente el valor de head:


hmiembros p
ubli os de ArrayStack<T> 132ai+
(131a) 134d
const size_t & size() const { return head; }

A nivel de interfaz, FixedStack<T> es identi a a ArrayStack<T>. Por razones de


ompatibilidad, es preferible de nir las mismas ex ep iones de ArrayStack<T> en las
primitivas de FixedStack<T>. De este modo, FixedStack<T> es apli able en ada sitio
donde se utili e ArrayStack<T>.
A nivel de implanta ion, FixedStack<T> es muy similar a ArrayStack<T>. La diferen ia prin ipal reside en que FixedStack<T> no efe tua veri a ion de desborde ni dispara ex ep iones. De resto, ada primitiva de FixedStack<T> es identi a a su par en
ArrayStack<T>.
2.5.3

134g

El TAD ListStack<T> (pila con listas enlazadas)

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 de ne 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
};
De nes:
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

Figura 2.24: Diagrama UML de las lases vin uladas a ListStack<T>


135a

ListStack<T> posee un atributo u


ni o:
hatributos de ListStack<T> 135ai

(134g)

size_t num_nodes;

135b

el ual ontabiliza la antidad de nodos que posee la pila.


ListStack<T> exporta dos sinonimos de nodo:
hm
etodos publi os de ListStack<T> 135bi

(134g) 135

typedef Snode<T> Node;


Uses Snode 84a.

135

A nivel de interfaz, las opera iones de ListStack<T> manejan \nodos" de la pila.


Item es un sinonimo ofre ido por ompatibilidad on versiones anteriores.
El onstru tor ListStack<T> solo se remite a ini iar el ontador de nodos:
hm
etodos publi os de ListStack<T> 135bi+
(134g) 135b 135d
ListStack() : num_nodes(0) { /* Empty */ }
Uses ListStack 134g.

135d

La opera ion push() se de ne en fun ion de un ListStack<T>::Node:


hm
etodos publi os de ListStack<T> 135bi+
(134g) 135 136a
void push(Node * node)
{
++num_nodes;
this->insert_next(node);
}

136

Captulo 2. Secuencias

push() no genera ninguna ex ep ion, pues su u


ni a responsabilidad es enlazar el nodo a
la lista. Puesto que this es la abe era, insert next() siempre insertara node al frente

136a

de la lista, el ual siempre se orresponde on el tope.


Suprimir el tope equivale a suprimir el nodo del frente de la lista. Esto es dire to segun
la interfaz de Snode<T>:
hm
etodos publi os de ListStack<T> 135bi+
(134g) 135d 136
Node * pop() throw(std::exception,std::underflow_error)
{
hveri a under ow 136bi
--num_nodes;
return this->remove_next();
}
Uses remove next 97b.

136b

136

hveri a under ow 136bi


if (this->is_empty())
throw std::underflow_error ("Stack underflow");

El tope puede onsultarse mediante:


hm
etodos publi os de ListStack<T> 135bi+

(136)

(134g) 136a

Node * top() const


{
hveri a under ow 136bi
return static_cast<Node*>(this->get_next());
}

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> de ne una pila implantada on listas enlazadas simples
y on manejo de memoria in luido. DynListStack<T> esta de nido 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
};
De nes:
DynListStack, used in hunks 137d and 230.
Uses ListStack 134g.

2.5. Pilas

137

DynListStack<T> alma ena datos del tipo generi o T. Al derivar p


ubli amente de
ListStack<T>, DynListStack<T> hereda gran parte de su implanta ion y de su interfaz.
El manejo de listas lo aporta, enton es, ListStack<T>. Las primitivas que no dependen
del tipo T, is empty() y size(), se heredan dire tamente. Las otras primitivas, que s
dependen de ListStack<T>, deben sobre argarse para que manipulen datos de tipo T.
137a

137b

Comenzamos por las primitivas mas sen illas:


hm
etodos de DynSlist<T> 137ai

(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.

push() retorna una referen ia al dato ontenido en el tope.


La labor de pop() es extraer el nodo de ListStack<T>, opiar el dato a retornar y
137

liberar la memoria. Esta ondu ta se espe i a omo sigue:


hm
etodos de DynSlist<T> 137ai+
(136d) 137b 137d
T pop() throw(std::exception, std::underflow_error)
{
typename ListStack<T>::Node* ptr = ListStack<T>::pop();
T retVal = ptr->get_data();
delete ptr;
return retVal;
}
Uses ListStack 134g.

Puesto

137d

que DynListStack<T> es responsable del manejo de memoria,


DynListStack<T>::DynListStack debe liberar toda la memoria. As pues, nos
aseguramos de va iar toda la pila en tiempo de destru ion:
hm
etodos de DynSlist<T> 137ai+
(136d) 137
virtual ~DynListStack()
{
while (not this->is_empty())
pop();
}

138

Captulo 2. Secuencias

Uses DynListStack 136d.

Notemos que is empty() pertene e a ListStack<T> mientras que pop() a DynListStack<T>.

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

 Pre ja: +xy


 Su ja: xy+
 In ja: x + y

La nota ion pre ja 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 pre ja y su ja 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 pre ja y su ja permiten expresar todas las
opera iones sin ne esidad de parentesis. Por ejemplo, la forma su ja de x (y + z) es
yz + x.
Para evaluar una expresion pre ja o su ja se utiliza una pila. El algoritmo para evaluar
expresiones su jas se des ribe omo sigue:
Algoritmo 2.2 (Evaluaci
on de expresi
on sufija) La entrada del algoritmo es una

se uen ia onteniendo una expresion in ja ompuesta por operadores y operandos


numeri os.
La salida es el valor orrespondiente al resultado nal.
El algoritmo utiliza una pila para guardar resultados par iales y una variable llamada
\ ha a tual" o simplemente la ha. La ha ontiene el smbolo, operando u operador,
que se esta pro esando.

Algoritmo

1. Repita mientras la ha no este al nal de la se uen ia.


(a) Si la ha es un operador = saque dos valores de la pila, efe tue la opera ion
dada por el operador ledo, y alma ene el resultado en la pila.
(b) De lo ontrario, si la ha es operando = metalo en la pila.
2. Cuando se al an e el nal de la se uen ia, el resultado alma enado en la pila es el
resultado de la expresion. Si la pila esta va a o esta ontiene mas de dos valores,
enton es la expresion su ja ontena algun error.
15 En

ingles es tradu ido omo post x. De all que algunos textos la denominen post ja.

2.5. Pilas

139a

139

Un algoritmo similar puede dedu irse para evaluar expresiones pre jas.
La nota ion su ja 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 in ja 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
in ja que sus equivalentes pre jo o su jo. Este he ho sugiere que manipular y evaluar
expresiones in jas 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 pre eren la nota ion
su ja.
Existe un algoritmo muy e iente para evaluar una expresion in ja, 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, de nimos 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 };
De nes:
Token Type, used in hunks 139b and 143a.

Value representa un operando, Operator representa uno de los operadores +, , , o /,


Lpar y Rpar representan los parentesis izquierdo y dere ho respe tivamente, End representa
el n de la adena y, nalmente, Error indi a que se en ontro un smbolo que no puede

139b

ser onsiderado ni operando ni operador.


Ahora podemos de nir una rutina que lea la adena y nos indique que tipo de ha se
esta leyendo:
hrutina le tora de has 139bi

const Token_Type lexer(char*& str, unsigned& len)


{
h uerpo de lexer 139 i
}
Uses Token Type 139a.

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.

lexer() retorna el tipo de ha que se ha dete tado. El parametro len se modi a

139

para indi ar la longitud en ara teres de la ha a tual en ontrada.


Los valores de los parametros se ini ian del siguiente modo:
h uerpo de lexer 139 i
(139b) 140b
str += len;
len = 1;
hignorar espa ios

en blan o 140ai
Por de ni 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 de ne
fa ilmente omo:
hignorar espa ios en blan o 140ai
(139 )
while (isblank(*str))
str++;

isblank() es una extension GNU de la bibliote a estandar del lenguaje C y retorna un

140b

valor diferente de ero si el ara ter de parametro orresponde a un blan o o tabulador.


Por simpli idad, nuestro evaluador solo a epta los operadores +, , y /, los uales
representan las opera iones aritmeti as lasi as. De la misma manera, los operandos solo
son numeros enteros. Salvo un operando, la longitud de una ha es uno. Veri amos,
pues, si el ara ter en ontrado orresponde a un operador:
h uerpo de lexer 139 i+
(139b) 139 140
switch (*str)
{
case (: return
case ): return
case +:
case -:
case *:
case /: return
case \0: return
}

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

diferente de ero si el ara ter de parametro orresponde a un dgito.


Si el ujo no retorna error, enton es ontabilizamos la antidad de dgitos por la dere ha
y retornamos el odigo de ha Value:
h uerpo de lexer 139 i+
(139b) 140
char* base = str + 1;
while (isdigit(*base++))
len++;
return Value;

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);
}
}
De nes:

precedence, used in hunk 143e.

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;
}
De nes:

str to token, used in hunk 143d.

str to token() extrae una opia del n


umero ontenido en token str.
141b

El algoritmo de evalua ion usara dos pilas omo sigue:


hpilas de evalua i
on 141bi

(143a)

ArrayStack<int> val_stack;
ArrayStack<char> op_stack;
Uses ArrayStack 131a.

141

El algoritmo realiza opera iones en lnea onforme analiza la adena de entrada. La


opera ion fundamental es sa ar dos operandos de la pila val stack y un operador de la
pila op stack, luego efe tuar la opera ion y, nalmente, alma enar el resultado de nuevo
en la pila val stack. Este patron se modulariza en la siguiente rutina:
hfun i
on de eje u ion de opera ion en pilas 141 i
void apply(ArrayStack<int>& val_stack, ArrayStack<char>& op_stack)
{
I(op_stack.size() > 0);
I(val_stack.size() >= 2);
const char the_operator = op_stack.pop();
const int right_operand = val_stack.pop();
const int left_operand
= val_stack.pop();

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);
}
De nes:
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 in ja. Nuestra rutina se llamara eval() y tendra la siguiente estru tura:
hfun i
on de evalua ion de expresion in ja 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");
}
}
}
De nes:
eval, never used.

input es la adena de ara teres que ontiene la expresion in ja.

2.5. Pilas

143a

eval() requiere siguientes variables:


hvariables de eval 143ai

hpilas de evalua i
on 141bi
Token_Type current_token;
unsigned token_len = 0;
Uses Token Type 139a.

143

(142)

current token es la ha a tual observada en la adena de entrada y token len es la


longitud de la ha en ara teres. Ini ialmente no hay ninguna ha, por lo que token len

143b

debe ser ero.


La pila de operadores requiere un entinela espe ial que sea de la menor pre eden ia
posible. Tal entinela es el valor `$` que se inserta ini ialmente en op stack:
hini ializa i
on de eval 143bi
(142)
op_stack.push($);

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

Cuando la ha es un operando, lo introdu imos en la pila de operandos:


hpro esar operando 143di
(142)
{

const int operand = atoi(str_to_token(input, token_len));


val_stack.push(operand);
break;
}
Uses str to token 141a.

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

while (precedence(op_stack.top()) >= precedence(*input))


apply(val_stack, op_stack);
hintrodu ir
break;

operador en op stack 144ai

}
Uses apply 141 and precedence 140e.

144a

144b

hintrodu ir operador en op stack 144ai


op_stack.push(*input);

(143e 144b)

Los parentesis en la expresiones in jas 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;

operador en op stack 144ai

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)
{

while (op_stack.top() not_eq ()


apply(val_stack, op_stack);
op_stack.pop(); /* saca el parentesis izquierdo */
break;
}
Uses apply 141 .

144d

El n de la entrada lo indi a lexer() uando retorna el valor End. En este momento,


si la expresion fue orre ta, nos resta evaluar las opera iones que se en uentran en la pila
de operandos, lo ual se de ne de la siguiente manera:
hpro esar n de entrada 144di
(142)
{

while (op_stack.top() not_eq $)


apply(val_stack, op_stack);
op_stack.pop(); // debe ser $
const int ret_val = val_stack.pop();
if ((val_stack.size() not_eq 0) or (op_stack.size() not_eq 0))
ERROR("Bad expression");
return ret_val;
}
Uses apply 141 .

2.5. Pilas

2.5.6

145

Pilas, llamadas a procedimientos y recursi


on

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?

 >Como ha e el ujo de ontrol para saltar a una fun i


on y regresar uando ulmina

la llamada a la fun ion?

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()

Generalmente, un push() equivale a restar al registro SP el tama~no de lo que se le


inserta. Simetri amente, un pop() equivale a una suma. Para las fun iones fct 2() y
fct 3(), el ompilador genera odigos similares a 2.5.1 y 2.5.2.
La informa ion pertinente a una llamada a pro edimiento que se guarda en la pila
sistema es ontigua. El bloque ompuesto por el valor de retorno, parametros, dire ion

2.5. Pilas

147

147

de retorno y variables lo ales se denomina \registro de a tiva ion".


El soporte eje utivo para las llamadas a pro edimientos se implanta on una pila
porque el programa uye a traves de las fun iones bajo una dis iplina UEPS. Una vez que
se al anza fct 3(), el programa debe regresar por el amino inverso a main() pasando
por fct 2() y fct 1().
Aunque la re ursion es una te ni a de programa ion bastante poderosa, es importante
aprehender que onlleva ostes importantes en espa io y tiempo. Como ejemplo, onsideremos la fun ion fa torial implantada re ursivamente:
hFa torial re ursivo 147i
int fact(const int & n)
{
if (n <= 1) return 1;
return n*fact(n-1);
}

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 de ni 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

Figura 2.25: Capa de la pila sistema on la llamada a fact(1)


La gura 2.25 ilustra el estado de la pila sistema uando se al anza la llamada a

fact(1). Cada vez que se efe t


ua una llamada re ursiva, se gasta en espa io de pila el

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

Consejos para la recursi


on

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 de ni ion matemati a es la siguiente:
Fib(n) =

148


1

si n 1
Fib(n 1) + Fib(n 2) si n > 1

La odi a ion re ursiva que al ula el i-esimo numero de Fibona i se desprende


de su de ni ion matemati a:
hFun i
on de Fibona i 148i
int fib(const int & n)
{
if (n <= 1)
return 1;

return fib(n - 1) + fib(n - 2);


}

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)

Figura 2.26: Llamadas a fib() para n = 5


Cuando se dise~na un algoritmo re ursivo, debemos er iorarnos de que no hayan al ulos
redundantes. Por lo general, la redundan ia requiere anotar los al ulos realizados en
alguna estru tura de datos espe ial.

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 de nirse 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

Podemos \suprimir" la re ursion si emulamos las llamadas re ursivas y los registros de


a tiva ion. Para ello debemos de nir una pila on los siguientes atributos:
1. Los valores de los parametros. En aso de que se trate de una fun ion, se in luye el
valor de retorno.
2. Los valores de las variables lo ales.
3. Una indi a ion que se~nale la dire ion de retorno. Cada valor de indi a ion debe
orresponder a un punto de retorno en el pro edimiento re ursivo.
Una vez de nida la pila, se modi a el pro edimiento re ursivo omo sigue:
1. Se a~nade a la fase de ini io la de lara ion e ini ializa ion de la pila.
2. Se olo an etiquetas de salto en los puntos de salida de las llamadas re ursivas.
3. A la ex ep ion de la fase de ini ializa ion, el uerpo de la fun ion se envuelve on
un lazo repetitivo uya ondi ion de parada es que la pila este va a.
4. Los puntos donde hay llamadas re ursivas se sustituyen por un push() de lo que
sera el registro de a tiva ion y un salto al ini io del lazo.
5. Los puntos donde el pro edimiento re ursivo retorne se sustituyen por un pop()
seguido de un switch que determina, segun la indi a ion de salto del registro de
a tiva ion, la dire ion de salto.
Por ejemplo, para el al ulo del i-esimo numero de Fibona i, modi amos la hFun ion

150

de Fibona i 148i del siguiente modo:

hVersi
on re ursiva mejorada
int fib(const int & n)
{
if (n <= 1) return 1;

de Fibona i

const int f1 = fib(n - 1);


const int f2 = fib(n - 2);
return f1 + f2;
}

150i

2.5. Pilas

151a

151

Esta forma fa ilita la elimina ion de la re ursion.


De nimos la siguiente estru tura para el registro de a tiva ion:
hRegistro a tiva i
on 151ai

(151 )

# define P1 1
# define P2 2

struct Activation_Record
{
int n;
int f1;
int result;
char return_point;
};
De nes:
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 de nimos en el ar hivo h b sta k.C 151 i uya de ni ion es la siguiente:
h b sta k.C 151 i
on 151ai
hRegistro a tiva i
ha eso

registro a tiva ion 151bi

int fib_st(const int & n)


{
h uerpo de b st 151di
}

151d

Ci~nendonos al metodo, lo primero es de nir el preambulo de la rutina:


h uerpo de b st 151di
(151 )
ArrayStack<Activation_Record> stack;

Activation_Record * caller_ar = &stack.pushn();


Activation_Record * current_ar = &stack.pushn();
NUM(current_ar) = n;
hemula i
on de re ursion 152ai
Uses Activation Record 151a and ArrayStack 131a.

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

current ar es el registro de a tiva ion de la llamada a tual.


Ahora emulamos el uerpo del pro edimiento re ursivo. Para ello, omenzamos por
olo ar el odigo mostrado en hVersion re ursiva mejorada de Fibona i 150i y luego
sustituimos por segmentos de odigo que emulan la re ursion:
hemula i
on de re ursion 152ai
(151d)
start:
h aso base
hllamada

de re ursion

152 i

a b(n - 1) 152di

p1: /* punto de retorno de fib(n - 1) */


hretorno desde b(n - 1) 153ai
hllamada

a b(n - 2) 153bi

p2: /* punto de retorno de fib(n - 2) */


hretorno desde b(n - 2) 153 i
return_from_fib:
hretornar desde b 153di

Distinguimos uatro etiquetas de salto del ujo de eje u ion:

 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);

Luego emulamos la llamada a fib(n - 2):


hllamada a b(n - 2) 153bi
153b

(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

a b(n - 2) 153bi es asi identi a a hllamada a b(n - 1)

ambia es que el valor del parametro n orresponde a n 2.

152di.

Lo uni o que

 Una vez al ulado fib(n - 2), <<retornar desde


fib>> saltara el ujo ha ia p2. En este momento, emulamos la asigna ion
int result = f1 + f2:
hretorno desde b(n - 2) 153 i
(152a)
153
RESULT(caller_ar) = F1(current_ar) + RESULT(current_ar);

 Finalmente, return from fib es el punto donde se emula la instru ion return del pro-

edimiento re ursivo. Tal emula ion onsiste en:


hretornar desde b 153di
153d

(152a)

stack.pop(); /* cae en el registro del invocante */


if (stack.size() == 1)
return RESULT(caller_ar);
caller_ar = &stack.top(1);
current_ar = &stack.top();
switch (RETURN_POINT(current_ar))
{
case P1: goto p1;
case P2: goto p2;
}

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 de nitivo 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

La rutina anterior exhibe un desempe~no inferior a su version re ursiva. De entrada,


el TAD ArrayStack<T> a~nade un sobre oste de valida ion de desborde que no tiene la
version re ursiva. Hay otras fuentes de sobre oste, la indi a ion de salto es una que no
tiene la version re ursiva.
Podemos efe tuar mejoras sobre la version no re ursiva. En primer lugar, podemos
eliminar el ampo f1 del registro de a tiva ion y utilizar result. De este modo disminuimos la antidad de pushes. Otra mejora es disminuir la antidad de observa iones a
la pila -instru ion top()-. La parte hllamada re ursiva a b 152bi podra de nirse omo:
caller_ar = current_ar;
++current_ar;
goto start;

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

Como ya lo hemos visto, eliminar la re ursion no es trivial. La experien ia indi a que


es muy dif il obtener una version no re ursiva que sea mas e iente que su equivalente
re ursivo. Por esa razon, la elimina ion de la re ursion de un algoritmo \naturalmente"
re ursivo solo debe ha erse si la parte re ursiva esta dentro del amino rti o de eje u ion
y es vital aumentar el desempe~no. En la mayora de las o asiones, es preferible trabajar
on la version re ursiva.
Existen situa iones en las que el tama~no de la pila del sistema es peque~no. En estos
asos, la re ursion es omprometedora, pues aumenta las posibilidades de desborde de
pila. Situa iones de este tipo son programas empotrados, de tiempo real o apli a iones
paralelas on varios ujos (threads) de eje u ion. En este aso, a n de proteger la pila del
sistema, es justi able -algunas ve es esen ial- disponer de una version no re ursiva que
maneje su propia pila.
En la pra ti a, el uso de la re ursion es una uestion de omodidad. Si los on eptos
inherentes al algoritmo son re ursivos, enton es es preferible trabajar en terminos re ursivos, pues hay mas seguridad de entendimiento. Si por razones de desempe~no o espa io se
ha e ne esario eliminar la re ursion, enton es es preferible formular on eptos iterativos,
en lugar de eliminar la re ursion de un algoritmo re ursivo. Un ejemplo notable es la
16 A

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

misma fun ion fa torial uya de ni ion iterativa es:


n! =

n
Y

i .

i=1

Esta de ni 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 de nirse omo:
1
Fib(n) =
5

"

!n
1+ 5

!n #
1 5
.
2

Un algoritmo basado en esta expresion no requiere ninguna itera ion ni re ursion.


En de nitiva, lo mas simple es a menudo lo idoneo. Si pensar re ursivamente es mas
simple, enton es transe por algoritmos re ursivos. Si la version re ursiva es muy ine iente, el fa torial o los numeros de Fibona i, por ejemplo, enton es busque solu iones
que disminuyan los al ulos repetidos o utili e on eptos iterativos que le onduz an ha ia
algoritmos iterativos.

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

Figura 2.27: Vision abstra ta de una ola


La gura 2.27 ilustra una vision abstra ta de una ola. Los elementos entran por el
extremo izquierdo mediante la opera ion put(). Del mismo modo, los elementos salen por
el extremo dere ho mediante la opera ion get(). Ambos extremos son observables.
El frente de una ola denota el elemento mas re ientemente insertado. El trasero denota
el menos re ientemente insertado. Esta no ion de re iente es fundamental en algunos
algoritmos y se denomina PEPS (Primero en Entrar, Primero en Salir) . Podemos de ir
que los elementos de la ola estan ordenados desde el mas joven hasta el mas antiguo. Este
patron ara teriza a los ontextos algortmi os en los uales una ola puede usarse.
Al igual que on las pilas, las olas son muy omunes en la vida real. En ontramos
ejemplos tpi os en las olas de espera de mu hos servi ios de nuestra so iedad. Cuando
vamos al ine debemos ha er una ola para omprar la entrada; igualmente, debemos ha er
otra ola para ingresar a la sala. Cuando vamos al ban o debemos ha er ola. Cuando
ondu imos un arro y arribamos a un semaforo se forma una ola.
17

17 En

ingles, FIFO (First In, First Out).

156

2.6.1

Captulo 2. Secuencias

Variantes de las colas

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

Aplicaciones de las colas

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

Representaciones en memoria de las colas

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

Figura 2.28: Cola implantada mediante un arreglo


Para insertar, opiamos el elemento e in rementamos la ola. Para eliminar, opiamos
el valor de retorno e in rementamos la abeza. La ola esta llena uando la antidad de
elementos es igual a la dimension del ve tor.
Notemos que la elimina ion de elementos puede dejar espa ios disponibles a la izquierda
del ndi e abeza. As pues, la inser ion y supresion deben onsiderar esta ir ularidad.
Esto puede manejarse mediante un if que veri que si el ndi e no se en uentra en el borde
del arreglo, o mediante la fun ion modulo.
Para las olas es preferible que la lista sea ir ular, pues podemos a eder al frente
y el trasero mediante un solo apuntador. Si se trata de una lista simplemente enlazada,
ordenamos los elementos desde el frente hasta el trasero y mantenemos un apuntador al
ultimo elemento ( gura 2.29). De este modo, podemos insertar y eliminar desde el puntero
a la ola.
rear

Figura 2.29: Cola implantada mediante una lista simple


Las listas doblemente enlazadas pueden ser una buena es ogen ia para las olas ordenadas segun el tiempo de a eso y para las di olas (x 2.6.1). La ventaja es que la supresion
de un elemento es dire ta on un nodo doble, mientras que en una lista simple habra que
mantener el puntero al prede esor.
2.6.4

157

El TAD ArrayQueue<T> (cola vectorizada)

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 de nen 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

de ola ve torizada 158ai


privados de ola ve torizada 160ei

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

privados de ola ve torizada 160ei

public:
hm
etodos publi os de FixedQueue<T> (never de ned)i
hobservadores de ola ve torizada 162hi
};
De nes:
ArrayQueue, used in hunks 160d and 308.
FixedQueue, never used.

158a

Ambas lases modelizan olas de elementos generi os de tipo T implantadas mediante


un arreglo estati o. ArrayQueue<T> veri a desbordes uando el arreglo esta va o o lleno.
Un desborde provo a una ex ep ion. FixedStack<T> no efe tua ninguna veri a ion ni
genera ninguna ex ep ion. La ausen ia de estas veri a iones onlleva una ligera ganan ia
en desempe~no.
La dimension del arreglo interno esta restringida a una poten ia exa ta de 2. El segundo
parametro plantilla, denominado two pow, representa la poten ia de 2 de la dimension del
arreglo.
hatributos de ola ve torizada 158ai
(157) 158b
const size_t two_pow;
const size_t dim;
T * array;

158b

El arreglo array ontiene los elementos de la ola. El ompilador re hazara T array[dim],


por lo que siempre debemos garantizar la invariante dim == 2two pow .
El frente de la ola esta dado por:
hatributos de ola ve torizada 158ai+
(157) 158a 159
size_t front_index; /* index of oldest inserted item */

front index es elndi e del elemento mas antiguo en la ola; es de ir, array[front index]
158

ontiene el frente de la ola:


hfrente de la ola 158 i

(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

Figura 2.30: Diagrama UML de la lase ArrayQueue<T>

array[front_index]

159

El trasero de la ola esta indi ado por:


hatributos de ola ve torizada 158ai+

(157) 158b 160a


size_t rear_index; /* index of next empty available cell */

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

Figura 2.31: Organiza ion del arreglo en ArrayQueue<T>


Los valores de front index y rear index se ini ian en ero. La gura 2.31 ilustra el
estado del arreglo y de los ndi es despues de insertar los primeros in o elementos en un
arreglo de 8 elementos.
Tanto para insertar, omo para eliminar, basta on in rementar el ndi e orrespondiente. Si queremos insertar, in rementamos rear index; si queremos eliminar, in rementamos front index.
Cuando alguno de los ndi es al anza la dimension del arreglo, lo ini iamos de nuevo
en ero; esto simula la ir ularidad. Para esto, basta olo ar un if despues del in remento
que veri que si se al anza la dimension; si tal es el aso, enton es reini iamos el ndi e en
ero. Podemos evitar el if si ada vez que in rementamos un ndi e al ulamos el modulo

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;

Es tentador al ular el numero de elementos mediante el valor absoluto de rear index


front index. Lamentablemente, tenemos una ambig
uedad, pues la resta es ero uando

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

de ola ve torizada 158ai son ini iados omo sigue:

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)

Estamos listos para espe i ar el onstru tor y destru tor:


hm
etodos publi os de ArrayQueue<T> 160di
(157) 161b
ArrayQueue(const size_t & tp = 8)
: hini ia i
on de atributos 160 i
{
array = new T [dim];
}

~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

Si se desea onsultar el trasero, es ne esario a eder a array[rear index - 1]. Puesto


que esta opera ion puede arrojar un desborde negativo, la aislamos en una sola fun ion
que nos garanti e una opera ion orre ta:
hm
etodos privados de ola ve torizada 160ei+
(157) 160e
T & rear_item(const size_t & i = 0)
{
return array[static_cast<size_t> ((rear_index - i - 1) & mask)];
}

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

La inser ion omo tal es omo sigue:


hput en ola ve torizada 161 i

(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

La primera parte veri a el desborde, la segunda se remite a in rementar rear index:


hputn en ola ve torizada 161ei
(161d)
increase_index(rear_index, n);
num_items += n;
return rear_item();

161f

161g

La supresion es asi simetri a a put():


hm
etodos publi os de ArrayQueue<T> 160di+

(157) 161d 162a


T get() throw(std::exception, std::underflow_error)
{
hget en ola ve torizada 161gi
}

donde la supresion onsiste en:


hget en ola ve torizada 161gi

num_items--;
T ret_val = hfrente de la ola 158 i;
increase_index(front_index);
return ret_val;

(161f)

162

162a

Captulo 2. Secuencias

La ontraparte de putn() es getn(); es de ir, liberar n elementos por el frente de la


ola:
hm
etodos publi os de ArrayQueue<T> 160di+
(157) 161f 162
T & getn(const size_t & n) throw(std::exception, std::underflow_error)
{
hgetn en ola ve torizada 162bi
}

162b

162

hgetn en ola ve torizada 162bi


num_items -= n;
increase_index(front_index, n);
return array[front_index];

La onsulta por el frente se realiza mediante:


hm
etodos publi os de ArrayQueue<T> 160di+

(157) 162a 162f


T & front(const size_t & i = 0) throw(std::exception, std::underflow_error)
{
hveri ar desborde negativo 162di
esimo elemento del frente 162ei
hretornar i-
}

162d

hveri ar desborde negativo 162di


if (i >= num_items)
throw std::underflow_error ("queue is empty");

162e

hretornar i-
esimo elemento del frente 162ei
return array[front_index + i];

162f

162g

162h

(162a)

(162)

(162 )

La onsulta por el trasero tambien es simetri a:


hm
etodos publi os de ArrayQueue<T> 160di+

(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);

del trasero 162gi

Finalmente, tenemos los lasi os observadores:


hobservadores de ola ve torizada 162hi

(162f)

(157)

const size_t & size() const { return num_items; }


bool is_empty() const { return num_items == 0; }
const size_t & capacity() const { return dim; }

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> (cola con listas enlazadas)

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
};
De nes:
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.

Quiza sorprendentemente, ListQueue<T> no deriva de Slist<T> o de Snode<T>.


Para omprender el por que, es menester onsiderar las diferentes alternativas de implanta ion mediante una lista simplemente enlazada, las uales se resumen omo:
1. Si los elementos estan ordenados desde la abeza hasta la ola, enton es no tenemos
problemas para insertar. Basta mantener un puntero a la ola y desde ella a tualizamos para que apunte al nuevo nodo. Para eliminar, mantenemos un puntero a la
abeza, el ual modi amos para que apunte a su su esor.
Con esta alternativa, usar un nodo abe era no ha e mu ha diferen ia. La uni a
posi ion del nodo abe era es entre la ola y la abeza y, por el orden, el nodo
abe era debe ser su esor de la abeza y prede esor de la ola . La idea de un nodo
abe era es fa ilitar el algoritmo. Este no es el aso porque, puesto que el nodo
abe era ira despues de la ola, habra que mantener el puntero a la ola para poder
insertar.
En de nitiva, debemos utilizar dos punteros, uno a la abeza y otro a la ola que
podemos manejar independientemente para el aso general. Tanto en la inser ion
omo en la elimina ion, debemos veri ar el aso uando la ola este o devenga
va a.
19

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

Figura 2.32: Diagrama UML de las lases vin uladas a ListQueue<T>


2. Si los elementos estan ordenados desde la abeza hasta la ola, enton es, manteniendo un puntero para la ola, podemos eliminar en O(1). Si la lista no es ir ular,
mantener un puntero ha ia la abeza es inutil, pues en una lista enlazada requerimos
el prede esor del nodo a eliminar. Pero si la lista es ir ular, la abeza es el su esor
de la ola, on lo que podemos efe tuar la supresion en O(1) mediante el mismo
puntero a la ola. La gura 2.33 muestra la disposi ion de una ola implantada mediante lazos simples de tipo Snode<T>. Esta disposi ion sera la que usaremos para
implantar ListQueue<T>.
rear ptr

Figura 2.33: Cola implantada mediante Snode<T>


En esta on gura ion, el nodo abe era tambien es inutil, pues este su edera a la
ola y pre edera a la abeza. Para eliminar, tendramos que avanzar hasta el nodo
abe era.
La on gura ion de la gura 2.33 es general solo uando la lista no esta va a. Es

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 on gura 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

es el numero de elementos que posee la ola:


hAtributos de ListQueue<T> 165ai+

(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

put() no dispara ninguna ex ep ion porque no hay manejo de memoria. Es imposible


que put() fra ase si el nodo fue orre tamente apartado.
La elimina ion es un po o mas ompli ada porque debe veri ar desborde negativo y
que la ola devenga va a:
hM
etodos publi os de ListQueue<T> 165 i+
(163a) 165d 166b
Node * get() throw(std::exception, std::underflow_error)
{
hveri ar desborde negativo 166ai
Node * ret_val = rear_ptr->remove_next();
num_nodes--;
if (num_nodes == 0)
rear_ptr = NULL;
return ret_val;
}
Uses remove next 97b.

166

166a

166b

Captulo 2. Secuencias

hveri ar desborde negativo 166ai


if (num_nodes == 0)
throw std::underflow_error("stack is empty");

(165e 166b)

La onsulta se de ne de dos formas:


hM
etodos publi os de ListQueue<T> 165 i+

(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;
}

front() retorna el nodo por el frente y rear() el nodo por la ola.

2.6.6

166

El TAD DynListQueue<T> (cola din


amica con listas enlazadas)

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)
{

2.7. Estructuras de datos combinadas - Multilistas

167

Node * ptr = ListQueue<T>::get();


T ret_val = ptr->get_data();
delete ptr;
return ret_val;
}
T & front() const throw(std::exception, std::underflow_error)
{
return ListQueue<T>::front()->get_data();
}
T & rear() const throw(std::exception, std::underflow_error)
{
return ListQueue<T>::rear()->get_data();
}
virtual ~DynListQueue()
{
while (not this->is_empty())
get();
}
};
De nes:
DynListQueue, used in hunks 702, 714, 720, 817, and 818a.
Uses ListQueue 163a.

2.7

Estructuras de datos combinadas - Multilistas

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 a rmativas.
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] re ere a una lista enlazada.
Tambien podramos de larar:
typedef int Arreglo[5];

168

Captulo 2. Secuencias

DynSlist<Arreglo> mat;

Lo que nos de ne una lista enlazada de arreglos de dimension 5.


Igualmente, podemos de larar:
DynSlist< DynSlist<int> > mat;

para de nir una lista de listas.


Los ejemplos anteriores pueden modelizarse a partir de TAD ono idos. Es posible que
tengamos que bajar de nivel y manejar dire tamente las listas. Como ejemplo, onsidere
una apli a ion que manipule matri es de muy gran es ala, del orden de millones de las y
olumnas. Tales apli a iones son omunes en problemas de programa ion lineal y entera
de la vida real. La es ala de estas matri es agota la memoria de ualquier omputador.
Esta laro que no tenemos espa io para alma enar una matriz de billones de elementos bajo la representa ion lasi a. Empero, aunque las matri es son enormes, la mayora
de las ve es estas poseen mu hsimos elementos nulos. Podemos, pues, apli ar el mismo
prin ipio que en los polinomios: solo guardamos los elementos diferentes de ero. Bajo este
lineamiento, imaginamos el siguiente tipo espe ial de nodo:

i j

dato

Puntero a la proxima olumna

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.

De esta manera, la matriz:

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

puede representarse omo en la gura 2.34.


Como vemos en la gura 2.34, tenemos 5 listas de las y 5 listas de olumnas. Cada
lista posee un nodo abe era, el ual, para mejorar la rapidez de a eso, onviene olo ar
en un arreglo. As pues, tendramos un arreglo de 5 abe eras para las las y otro de 5
abe eras para las olumnas.

2.7. Estructuras de datos combinadas - Multilistas

0 0

0 1

169

0 4

1 2

1 3

2 3

4 3

10

Figura 2.34: Una matriz espar ida representada mediante multilistas


Dado un nodo ualquiera, el elemento ha ia la dere ha indi a la proxima entrada en la
misma la on un valor diferente de 0; la olumna es averiguada por el ndi e de la olumna.
Del mismo modo, el elemento ha ia abajo indi a la proxima entrada en la misma olumna
on un valor diferente de ero; la la es averiguada por el ndi e de la la.
La representa ion de la gura 2.34 re ompensa on re es si el numero de entradas
nulas es muy grande. Por ejemplo, una matriz 104x104 o upara 400 106 de bytes en una
arquite tura de 32 bits. Si un 4% de los elementos son diferentes de ero, que representan
4 millones de elementos, la representa ion on multilistas o upara (4 + 4 + 4 + 4 + 4)
0, 04 1006 = 80 106 bytes.
La suma, resta y division de matri es son fa iles de programar on matri es basadas
en la estru tura de la gura 2.34. Para otras opera iones puede ser mas onveniente tener
lazos a la la y a la olumna prede esoras.
El algoritmo andido para multipli ar matri es n n es ubi o. Esto signi a que su
tiempo de eje u ion es propor ional a n3. Con millones de olumnas y las, no hay mu has
esperanzas de obtener un buen desempe~no si es ogemos la representa ion se uen ial. Sobre
matri es espar idas representadas on multilistas el tiempo es mu ho menor, pues las
entradas de las matri es son visitadas en el mismo orden de las listas y no hay ne esidad
de insertar eros en la matriz resultado. En de nitiva, el orden depende de la antidad de
elementos diferentes de ero.

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 in jas 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

1. >Por que la busqueda binaria no debe utilizarse para elementos ordenados en un


ar hivo alma enado en memoria se undaria?
2. Implante enteramente el problema fundamental de estru tura de datos estudiado
en x 1.3 mediante arreglos desordenados.
3. Implante enteramente el problema fundamental de estru tura de datos estudiado
en x 1.3 mediante arreglos ordenados.
4. Explique omo es el me anismo de a eso dire to a los elementos de una matriz.
5. En general, explique omo es el me anismo de a eso dire to a los elementos de un
arreglo n-dimensional.
6. Dise~ne e implante un TAD que modeli e arreglos n dimensionales.
7. Es riba una implanta ion lo mas e iente posible de la rutina DynArray<T>copy()
segun las indi a iones dadas en x 2.1.5.3 (pagina 59).
8. Es riba un algoritmo e iente que efe tue la inser ion ordenada en un DynArray<T>.
9. Es riba un algoritmo e iente que efe tue la elimina ion ordenada en un
DynArray<T>.
10. Suponga un pro eso on una memoria total disponible de M bytes y una maximo
permitido por una llamada a new de S bytes. Considere una arreglo dinami o de
elementos de tama~no T . Sele ione los tama~nos de dire torio, segmento y bloque que
maximi en la dimension de un DynArray<T>..
11. Es riba la sobre arga del metodo DynArray<T>::cut(l, r), el ual libera toda la
memoria entre [0..l - 1] y r+1..dim (dim) es la dimension a tual del arreglo.
12. La bibliote a ALEPH implanta un prototipo del TAD de la bibliote a stdc++ llamado vector<T>, el ual exporta un arreglo dinami o. El tipo en uestion se en uentra en el ar hivo Vector.H.

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 >

Asumiendo se uen ias implantadas on listas ir ulares doblemente enlazadas que


utilizan nodo abe era, es riba un algoritmo que implante rotar derecha(S, n) en
tiempo propor ional al tama~no del arreglo y sin usar espa io que rez a en fun ion
del arreglo. En otras palabras, no debe usar listas o arreglos.
19. Dada una adena de ara teres que ontiene palabras, suponga una opera ion de
justi a ion de la adena de ara teres a un maximo de n. Por justi ar, se entiende
que se oloquen todas las palabras posibles en exa tamente n ara teres inter alando
propor ionalmente blan os. Por ejemplo, la adena:
Esta es una prueba de justificaci
on

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 justi que 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

20. Considere n elementos enteros, alma enados en un arreglo, omprendidos entre 0 y


m, m n.
(a) Use el tipo BitArray para dise~nar un algoritmo que determine algun elemento
faltante.
(b) Suponga que m es su ientemente peque~no para que todos los numeros quepan
en memoria. Dise~ne un algoritmo que onstruya un arreglo que ontenga los
elementos faltantes.
( ) Suponga que n es muy grande, pero que el arreglo aun abe en memoria.
Suponga ondi iones muy restri tivas de memoria: no es posible usar un arreglo
de bits, ni en general ualquier otra estru tura de dato. Empero, s es posible
utilizar ar hivos en modo append; es de ir, solo se puede insertar al nal del
ar hivo.
i. Use el prin ipio de busqueda binaria para dise~nar un algoritmo que determine algun elemento faltante. (+++)
ii. Use el prin ipio de busqueda binaria para dise~nar un algoritmo que onstruya un ar hivo on los numeros faltantes. (+ -luego de resolver anterior-)
21. Considere la sele ion aleatoria de un elemento ontenido en un arreglo de n elementos enteros.
(a) Dise~ne un algoritmo que es oja aleatoriamente un elemento del arreglo.
(b) Suponga ahora que no se ono e n y que el nal del arreglo orresponde a un
elemento entinela on valor generi o FIN. Dise~ne un algoritmo e iente que
sele ione aleatoriamente un elemento del arreglo.
( ) Ahora asuma que los enteros estan alma enados en una lista simplemente enlazada. Dise~ne un algoritmo que efe tue una sola pasada y sele ione aleatoriamente un elemento de la lista. (++)
22. Extienda el TAD DynArray<T> para manejar matri es. Suponiendo que el TAD es
llamado DynMatrix<T>:
(a) >Puede implantarse este TAD a partir del TAD DynArray<T>?
(b) >Puede realizarse una implanta ion independiente mas e iente?
( ) >Como sera la forma y uales las fun iones de la lase proxy sobre el TAD
DynMatrix<T>?
23. Extienda el TAD DynArray<T> para manejar arreglos multidimensionales.
24. La mayora de los TAD que manejan listas no utilizan memoria. Ninguno de estos
TAD veri a si la dire ion de un objeto que inserta o elimina es valida. Dise~ne un
metodo que veri que si una dire ion de objeto es valida. (+)
25. En el mismo espritu de la pregunta anterior, dise~ne un metodo que, ademas de
veri ar si el puntero es valido, veri que que la dire ion orresponda a un objeto
del tipo esperado por la fun ion. (++)

174

Captulo 2. Secuencias

26. Espe i que 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 i que 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

43. Dado un tablero de ajedrez de 8 8 es aques, es riba un algoritmo que oloque o ho


reinas en el tablero de manera tal que ninguna reina pueda amenazar a ualquiera
de las otras. (++)
44. Resuelva el problema anterior de las o ho reinas para en ontrar todas las solu iones
esen ialmente diferentes. (+)
45. El problema del morral
Dado un objetivo O y una ole ion de pesos P = {p1, p2P
, . . . , pn, pi} N , se pide

determinar si existe un sele ion de pesos P P tal que pi P =).


Este problema es ono ido omo el problema del morral porque alegoriza el sele ionar uales argas llevar en un morral de manera tal que no se porte mas O
kilogramos.
Para el problema del morral:
(a) Es riba un algoritmo re ursivo.
(b) Elimine la re ursion del algoritmo anterior.
( ) Es riba un algoritmo iterativo que no utili e pila. (++)
46. Dada una ole ion de objetos S = {s1, s2, . . . , sn, si} on pesos P =
{p1, p2, . . . , pn, pi} R y valores V = {v1, v2, . . . , vn, vi} R, se pide en ontrar
P
S, que maximi e el valor total
un sub onjunto SP
sS vi restringido a una apa idad de pesos sS pi C; es de ir, que todos los objetos quepan en un morral
de apa idad C.
Dise~ne un algoritmo que resuelva esta variante del problema del morral. (++)
47. Es riba un algoritmo re ursivo que genere las n! permuta iones de n elementos
x1, x2, . . . , xn on un onsumo de espa io onstante, que no rez a seg
un n. (++)
48. La fun ion de A kerman A(m, n) esta de nida de la forma siguiente:

20 En

n + 1
A(m, n) = A(m 1, 1)

A(m 1, A(m, n 1))

m=0
m > 0, n = 0
m, n > 0

la jerga ajedre sti a, un es aque es uno de los uadros del tablero.

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>
de nida en x 2.1.5. (+)
52. Espe i que, 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. Modi que 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. Modi que 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.

71. Suponga la situa ion siguiente:


0

178

Captulo 2. Secuencias

(a) Explique un algoritmo que determine si la lista en uestion tiene o no un i lo


y, si existe un i lo, ual es el nodo de omienzo y ual es su longitud.
Dis uta el desempe~no de su metodo.
(b) Asuma que es imposible mantener alguna estru tura de dato o es ribir sobre
los nodos. Solo es posible algunas eldas de memoria.
i. Men ione algun es enario real donde estas restri iones se apli aran.
ii. Con las restri iones dadas, explique un metodo que o upe espa io onstante y que determine si existe un i lo, en ual nodo y de uanta longitud.
iii. >Cual es el desempe~no del metodo anterior?
72. Imagine la siguiente situa ion:
x

El problema onsiste en determinar ual es el nodo de interse ion, uantos nodos


pre eden a x en ada lista, y uantos nodos su eden a x.
(a) Suponiendo que se puede mar ar ualquier nodo, dise~ne un algoritmo que no
ontenga i los anidados.
(b) A partir de ahora asuma que no se puede mar ar algun nodo. Dise~ne un algoritmo andido basado en dos i los anidados. El algoritmo puede o upar espa io
propor ional al numero de nodos.
( ) Dise~ne un algoritmo basado en dos i los anidados que o upe espa io onstante.
(d) Dise~ne un algoritmo basado en i los simples, no anidados, que o upe espa io
onstante.
73. Considere la opera ion de parti ion de una lista en dos listas de igual tama~no. La
primera lista orresponde a los n/2 primeros elementos; la segunda a los n/2 elementos restantes. Es riba algoritmos basados en i los simples para parti ionar:
(a)
(b)
( )
(d)

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() de nidos 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

De este modo, 3710 = 1001012.


Implante, usando una pila, la rutina:
void imprime2(const int & n);

180

Captulo 2. Secuencias

La ual imprime el equivalente binario del parametro n.


85. Reali e un programa que evalue una expresion pre ja.
86. Reali e un programa que evalue una expresion su ja.
87. Expanda el evaluador de expresiones in jas para que efe tue opera iones de exponen ia ion, trigonometra y logaritmos.
88. Modi que el evaluador de expresiones in jas para que, en lugar de evaluar la
expresion, genere la expresion su ja. In luya las opera iones de exponen ia ion,
trigonometra y logaritmos.
89. Modi que el evaluador de expresiones in jas para que, en lugar de evaluar la
expresion, genere la expresion pre ja. In luya las opera iones de exponen ia ion,
trigonometra y logaritmos.
90. Dise~ne e implante un TAD que modeli e di olas implantadas on arreglos.
91. Dise~ne e implante un TAD que modeli e di olas implantadas on listas.
92. Dis uta las alternativas de dise~no de un TAD que modeli e matri es espar idas
representadas on multilistas. Explique omo se realizaran las opera iones de suma,
resta, multipli a ion e inversion.
93. Cal ule los ostes en espa io de una matriz representada mediante multilistas. Propor ione una expresion analti a en fun ion de la dimension de la matriz y del por entaje de elementos nulos.
94. Dise~ne e implante un TAD que modeli e matri es espar idas representadas on multilistas. Implante las opera iones de suma, resta, multipli a ion e inversion (++).

Bibliografa
[1 Alfred V. Aho, John E. Hop roft, and Je rey 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

[7 Donald E. Knuth. Fundamental Algorithms, volume 1 of The Art of Computer


Programming. Addison-Wesley, Reading, MA, USA, third edition, 1997.
[8 Harry R. Lewis and Larry Denenberg. Data Stru tures and Their Algorithms.
Harper Collins Publishers, New York, 1991.
[9 Zohar Manna and Ri hard Waldinger. The origin of the binary-sear h paradigm.
Te hni al Report Report, Stanford University, Stanford, CA, 1987.
[10 S ott meyer. More e e tive C++: 35 new ways to improve your programs and
designs. Addison-Wesley, 1996. ISBN 0-201-63371-X.
[11 Ni holas Nether ote and Julian Seward. Valgrind: A program supervision framework.
Ele tr. Notes Theor. Comput. S i, 89(2), 2003.
[12 Allen Newell and J. C. Shaw. Programming the logi theory ma hine. In Pro eedings
of the 1957 Western Joint Computer Conferen e, pages 230{240. IRE, 1957.
[13 Allen Newell, J. C. Shaw, and H. A. Simon. Empiri al explorations with the logi
theory ma hine. In Western Joint Computer Conferen e, volume 15, pages 218{
239, 1957.
[14 John K. Ousterhout. S ripting: Higher-level programming for the 21st entury. IEEE
Computer, 31(3):23{30, 1998.
[15 P.J.Maker and The Free Software Foundation. The GNU NANA homepage.
[16 M. Rozier, V. Abrossimov, F. Armand, I. Boule, M. Gien, M. Guillemont, F. Herrmann, C. Kaiser, S. Langlois, P. Leonard, and W. Neuhauser. CHORUS distributed
operating systems. USENIX Computing Systems, 1(4):305{370, 1988.
[17 Robert Sedgewi k. Algorithms in C: Parts 1{4: Fundamentals, data stru tures,
sorting, sear hing. Addison-Wesley, Reading, MA, USA, 1998.
[18 G. van Rossum and F. L. Drake, Jr., editors. An Introdu tion to Python. Network
Theory Ltd, September 2003.
[19 Larry Wall and Randal L. S hwartz. Programming Perl. O'Reilly & Asso iates, In .,
1990.
[20 Gray Watson. Dmallo - debug mallo library. Available on: http://dmalloc.com/.
[21 Niklaus Wirth. Algorithms + Data Stru tures = Programs. Prenti e-Hall Series in
Automati Computation. Prenti e-Hall, Upper Saddle River, NJ 07458, USA, 1976.

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

Captulo 3. Crtica de algoritmos

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 on gura ion de la entrada. Uno de los fa tores mas importantes de esta on gura 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 on gura 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 de nen 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

porque el tiempo de eje u ion puede tambien onsiderarse omo un re urso.

186

Captulo 3. Crtica de algoritmos

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.

on: Un algoritmo se instrumenta en alg


un
 Caractersticas de la codificaci

lenguaje de programa ion. Lenguajes diferentes que odi quen el mismo algoritmo
produ iran programas on dura iones diferentes.
En a~nadidura, los programadores tambien di eren 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

di eren 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

Unidad o paso de ejecuci


on

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

Captulo 3. Crtica de algoritmos

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

Aclaratoria sobre los m


etodos de ordenamiento

En este aptulo nos serviremos de algunos metodos de ordenamiento para ilustrar el


analisis de algoritmos, pues estos plantean \naturalmente" el tama~no de la entrada en
base a la antidad de elementos a ordenar. En a~nadidura, la permuta ion de la se uen ia
de entrada in ide en el desempe~no de la mayora de los metodos de ordenamiento. Por esta
razon, uando se analiza un metodo de ordenamiento, hay que onsiderar, por lo general
on riterios estadsti os, la forma de la permuta ion.
Aparte de fungir omo un buen veh ulo de ense~nanza para el analisis y dise~no de algoritmos, los metodos de ordenamiento son parte de los ono imientos esen iales de todo
buen programador. En efe to, el ordenamiento se usa en una amplia gama de problemas
mas omplejos. Por ejemplo, en grafos, ambito que estudiaremos en el aptulo x 7, a
ve es se requieren ordenar rela iones omo prepro esamiento a algunos algoritmos. Otro
ejemplo notable se en uentra en el \ as aron onvexo", problema de la geometra omputa ional que onsiste en en ontrar un polgono que \envuelva\ a un onjunto de puntos.
Un algoritmo simple y e iente para este problema requiere ordenar los puntos segun sus
oordenadas.
Los metodos de ordenamiento, as omo otros utilitarios rela ionados, residen en el
ar hivo htpl sort utils.H 189ai, uya estru tura general es la siguiente:

3.1. An
alisis de algoritmos

189a

189

htpl sort utils.H 189ai


extern const int Insertion_Threshold;
extern const int Not_Found;

etodos
hM
hM
etodos
3.1.3

de ordenamiento 189bi
de busqueda 205i

Ordenamiento por selecci


on

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

j desde i hasta n 1 para

en ontrar el menor elemento


i

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);
}
De nes:
selection sort, used in hunks 191{93.

190

190a

Captulo 3. Crtica de algoritmos

La busqueda del menor elemento se realiza se uen ialmente de la siguiente manera:


hSea min el ndi e menor entre i + 1 y n - 1 190ai
(189b)
for (min = i, j = i + 1; j < n; ++j)
if (Compare () (a[j], a[min]))
min = j;

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

Pasos de eje u ion

(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 a rmativa 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;
}
De nes:
search min, used in hunks 191 and 195a.
Uses Dlink 90, get current 103, and has current 103.

Es extremadamente importante notar que el metodo de ordenamiento re ibe el


tipo Dlink, desde el ual no se puede ono er generi amente el tipo dato que permite
realizar las ompara iones que ondu iran al ordenamiento. Justamente, este es el n de la
lase Compare, la ual debe implantar el operador bool Compare::operator () (Dlink

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;

while (not list.is_empty())


{
Dlink * min = search_min<Compare>(list); // menor de list
min->del(); // saque menor de list

192

Captulo 3. Crtica de algoritmos

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

la ual sera el riterio de ompara ion por omision:


hM
etodos de ordenamiento 189bi+

(189a) 191 192b

template <typename T, class Compare>


class Compare_Dnode
{
public:

bool operator () (Dlink * l1, Dlink * l2) const


{
Dnode<T> * n1 = static_cast<Dnode<T>*>(l1);
Dnode<T> * n2 = static_cast<Dnode<T>*>(l2);
return Compare () (n1->get_data(), n2->get_data());
}
};
De nes:
Compare Dnode, used in hunks 192b, 204b, 222 , and 229.
Uses Dlink 90 and Dnode 106a.

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;
}
De nes:

sequential search, used in hunks 16, 34a, and 194.

sequential search() bus a se uen ialmente en el arreglo a, entre los ndi es l y r, el


elemento x. Si la busqueda es exitosa, enton es se retorna el ndi e ontentivo del elemento;
de lo ontrario, se retorna 1.
sequential search() emplea omo riterio de igualdad el operador () de la lase
parametrizada Equal.

>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

Captulo 3. Crtica de algoritmos

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);

return ret != NULL ? &ret->get_data() : NULL;


}
Uses Dnode 106a, DynDlist 113a, and sequential search 193b.

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;

for (int i = l + 1; i <= r; i++)


if (Compare () (a[i], a[extreme_index])) // se ve un nuevo menor?
extreme_index = i; // s

return extreme_index;
}

3.1. An
alisis de algoritmos

195

De nes:

search extreme, used in hunk 195.

search extreme() bus a el elemento extremo seg


un el riterio de ompara ion
Compare. Sin embargo, on esta sintaxis no queda semanti amente laro que se trata de
195a

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);
}

template <typename T> inline


int search_max(T a[], const int & l, const int & r)
{
return search_extreme<T, Aleph::greater<T> > (a, l, r);
}
Uses search extreme 194 and search min 190b.

195b

>Cual es la diferen ia on la busqueda parti ular que estudiamos en la sub-se ion


pre edente? La estru tura es pra ti amente la misma: un lazo de r l = n pasos; la
diferen ia radi a en la deten ion. Para una permuta ion aleatoria, la busqueda parti ular
exitosa es propor ional a n en promedio, mientras que la busqueda de un extremo requiere,
siempre, examinar la totalidad de la se uen ia. Conse uentemente, search extreme() es
propor ional a n para todos los asos.
Para ompletar esta sub-se ion, debemos programar search extreme() para las listas
basadas en Dlink, lo que plante del siguiente modo:
hM
etodos de ordenamiento 189bi+
(189a) 195a 202a
template <typename T, class Compare> inline
Dnode<T> * search_extreme(Dnode<T> & list)
{
typename Dnode<T>::Iterator it(list);
Dnode<T> * curr_min

= it.get_current();

for (it.next(); it.has_current(); it.next())


{
Dnode<T> * curr = it.get_current();
if (Compare () (curr->get_data(), curr_min->get_data()))
curr_min = curr;
}
return curr_min;
}
Uses Dnode 106a, get current 103, has current 103, and search extreme 194 .

196

3.1.6

Captulo 3. Crtica de algoritmos

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 de ni 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.

Cuando de imos que T (n) = O(f(n)) estamos aproximando el omportamiento de T (n)


a una fun ion diferente y, para que tenga sentido pra ti o, mas sen illa. A rmemos, por
ejemplo, que T (n) = n2 + n + 1 = O(n2). Si esto es orre to, enton es c, n1 | T (n)
c(n2 + n + 1). Sele ionemos c = 1 e intentemos demostrar la a rma ion.
Para probar que n2 + n + 1 = O(n2) planteamos la ine ua ion n2 + n + 1 cn2, n
n1 = (1 c)n2 + n + 1 0. Lo que arroja omo ra es
n=

1 4(1 c)
.
2(1 c)

Si sele ionamos c = 7/4 tenemos omo ra es n0 = 6, n1 = 2. As pues, c = 7/4, n1 = 2


on rman que n2 + n + 1 = O(n2).
Demostrar que T (n) = O(f(n)) para alguna fun ion f(n) no impli a que no existan
otras fun iones que satisfagan la de ni ion. De he ho, es posible de ir que hay mejores
fun iones que otras. Por ejemplo, son demostrables las siguientes a rma iones:
n
X
i=1
n
X
i=1
n
X
i=1

i2 = O(n4)

(3.2)

i2 = O(n3)

(3.3)

i2 = O(n2)

(3.4)

pero la ultima a rma ion es la mas pre isa de las tres.


Puesto que hay dos in ognitas (c, n1) y una sola desigualdad, demostrar T (n) =
O(f(n)) puede ser dif il en algunos asos matemati os, pero, por lo general en el analisis
de algoritmos, la demostra ion es sen illa. En algunos asos, puede ser util pre jar un
valor de la onstante c y resolver:
lim T (n) c f(n) 0

(3.5)

La a rma 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 de ni ion.
Lo idoneo uando se a rme 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 de ni 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.

La idea de estudiar un algoritmo mediante opera iones O es obtener la aproxima ion


asintoti a mas er ana al tiempo de eje u ion. Podemos trabajar on una aproxima ion
y no on un resultado exa to porque lo que nos on ierne es la tenden ia de T (n) en el
algoritmo en s, sin onsiderar los detalles subjetivos ir unstan iales pertinentes al tipo de
hardware, ompilador, y otros fa tores previamente men ionados. Podemos, pues, asumir
que la onstante c abstrae las diferen ias ir unstan iales de eje u ion. Las diferen ias de
desempe~no dadas por otros fa tores representan diferentes onstantes uyo valor no nos
on ierne. En el mismo sentido, la forma exa ta de T (n) no es relevante, sino la tenden ia
asintoti a a partir de la onstante n1.
En algunos asos, pueden onvenirnos las de ni iones para los equivalentes inferiores
de O(f(n)) y o(f(n)); es de ir, asntotas que esten por debajo de T (n).
Definici
on 3.3 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 c, n1 | T (n) cf(n) , n n1.
Definici
on 3.4 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 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 de ni 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

Captulo 3. Crtica de algoritmos

cT (n)

g(n)
f(n)

T (n)

n1

n2

Asntotas

Identi quemos, 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 de ni 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 in nito, 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

Observemos primero la urva c2f(n), uya asntota es paralela a la de T (n). Un ierto


valor de onstante c2 ha e que T (n) = (f(n)). Ahora bien, bastara on multipli ar f(n)
por una onstante c1 > c2 para que T (n) = O(f(n)). Por tanto, T (n) = (f(n))
3.1.6.1

Algebra
de O

En el mundo de lo in nito, lo que ha e la diferen ia es la tenden ia que lleve la urva. Sumas


y produ tos de onstantes son des artables en el dominio de la nota ion O. Esto posibilita
un algebra sobre la nota ion O que simpli a onsiderablemente el analisis de un algoritmo.
Para ello, permtasenos enun iar las siguientes reglas basi as, uyas demostra iones se
delegan a ejer i ios:
O(f(n)) + O(g(n)) = O(max(f(n), g(n)))
c O(f(n)) = O(f(n))

O(O(f(n))) = O(f(n))

O(f(n)) O(g(n)) = O(f(n) g(n))

O(f(n) g(n)) = f(n) O(g(n))

T (n) = nk + nk1 + + n + c = O(nk)

logk n = O(n)

(3.6)
(3.7)
(3.8)
(3.9)
(3.10)
(3.11)
(3.12)

En o asiones tenemos que omparar fun iones on el objeto de determinar ual es la


mayor. A efe tos del rigor, o si la ompara ion es dif il de estable er, planteamos el radio
o razon entre las fun iones omo riterio de ompara ion y onse uente de ision. De este
modo:

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

Captulo 3. Crtica de algoritmos

++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

1. Instrucciones secuenciales: La eje u ion de dos instru iones se uen iales on


tiempo T1(n) = O(f1(n)), T2(n) = O(f2(n)) toman la suma T1(n) + T2(n), la ual,
por la regla (3.6) (de la suma), toma O(max(f1(n), f2(n))).
2. Instrucciones de decision: Asumamos, omo aso general, la siguiente estru tura:
if ( O(f1(n)) )
O(f2(n))
else
O(f3(n))

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

es la razon por la ual la instru ion dentro del if no se olo a indentada.

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

1000 10000 1000001e+06 1e+07 1e+08 1e+09


n
O(2n)
O(lg (n))
O(n lg (n))

O(n)
O(n2)
O(n3)

O( n)
O(1)

Figura 3.1: Curvas de lg(n), n, n, n lg (n), n2, n3, 2n a es alas logartmi as


La dura ion de un algoritmo se remite a identi ar sus se uen ias de eje u ion y \desenrollarlas" en una sola. Se uen ias de longitud ja; es de ir, pasos de eje u ion, son O(1).
Se uen ias de longitud propor ionalmente variable son O(n). Se uen ias de se uen ias de
longitud propor ionalmente variable son O(n2). Se uen ias de longitud uadrati amente
variable son O(n3). Siguiendo esta se uen ia de razonamiento, no es dif il intuir que el
onjunto de posibles fun iones des riptoras del tiempo de eje u ion de un algoritmo esta
ir uns rito a una peque~na familia, uyas urvas se ilustran en la gura 3.1.
En las sub-se iones subsiguientes estudiaremos una lase de algoritmo denominada
\dividir/ ombinar", la ual divide la entrada en dos partes aproximadamente iguales.
Dentro de esta lase de algoritmo, aquellos que trabajan sobre solo una de las partes
divididas dejando la otra sin pro esar son O(lg(n)); mientras que aquellos que requieren
pro esar lineal y enteramente las dos partes son O(n lg(n)).
Existe otra lase de problema uya solu ion, por lo general muy simple, onsiste en
al ular y analizar todas las permuta iones de se uen ias solu ion posibles. Cuando los elementos no estan repetidos, el numero de permuta iones de una se uen ia es n!. Cuando,
por el ontrario, s estan repetidos, es mn; donde m es la antidad de elementos y n
la longitud de la se uen ia. Ambas situa iones estan a otadas asintoti amente por la
fun ion f(n) = 2n, la ual no es polinomi a. Por esa razon, algoritmos uyo tiempo de
eje u ion es O(2n) son denominados no-polinomiales o, a ronimamente, NP.
No se sabe si para los problemas NP existen algoritmos orre tos on solu iones
polinomi as. En todo aso, s se aprehende, por tanto, se sabe que, hasta que no se demuestre lo ontrario, los problemas NP son \intratables" en el sentido de que, aun para una
peque~na es ala, no existen re ursos omputa ionales que permitan eje utar el algoritmo.
Por esa razon, los problemas NP son tildados de intratables o her uleos .
5

5 De

Her ules, el heroe mitologi o griego

202

Captulo 3. Crtica de algoritmos

3.1.7

Ordenamiento por inserci


on

Pongamos en pra ti a la teora de la nota ion O mediante el dise~no y analisis de desempe~no


de otro metodo de ordenamiento, denominado \de inser ion", uyo esquema general se
bosqueja del siguiente modo:
swap(tmp, a[j])
tmp = a[i]
j
A[j] >= A[i]
j

202a

Desorden

Orden relativo i

Primero, el elemento en la entrada a[i] se guarda en tmp. Ha ia la izquierda del ndi e i,


tenemos orden relativo; es de ir, los elementos estan ordenados mas no ne esariamente en
su posi ion de nitiva. Luego, el ndi e j retro ede a partir de i - 1 hasta en ontrar un
elemento menor o igual que tmp; en el nterin de la itera ion, ada elemento se va opiando
ha ia la dere ha de manera tal de ir dejando una bre ha. Cuando j esa de iterar, a[j]
tiene una bre ha donde olo amos el valor de a[i] memorizado en tmp.
Una implanta ion es omo sigue:
hM
etodos de ordenamiento 189bi+
(189a) 195b 203
template <typename T, class Compare>
void insertion_sort(T a[], const size_t & l, const size_t & r)
{
int i,j;
T tmp;
for (i = l; i <= r; ++i)
{
tmp = a[i]; // memorice a[i], pues ser
a sobre escrito
hAbra una bre ha a[j donde insertar tmp 202bi
a[j] = tmp; // inserte tmp en la brecha
}
}
De nes:

insertion sort, used in hunks 204 and 223.

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 so sti ados. En lo que sigue, asumiremos l = 0 y r
= n - 1
202b

hAbra una bre ha a[j donde insertar tmp 202bi


for (j = i; j > l and Compare() (tmp, a[j - 1]); --j)
a[j] = a[j - 1]; // desplazar hacia la derecha

(202a)

El i lo externo efe tua r l + 1 = n itera iones. Por lo tanto, este es O(n).


Para estudiar el i lo interno hAbra una bre ha a[j donde insertar tmp 202bi requerimos ono er el numero de ve es que este itera. Esta antidad depende del valor del predi ado j > l and Compare() (tmp, a[j - 1]), uyo resultado depende de la disposi ion
de los elementos a[l .. i - 1. En este sentido debemos onsiderar varias posibilidades:

3.1. An
alisis de algoritmos

203

1. Si el intervalo a[l .. i - 1] esta ordenado, enton es no se efe tua ninguna


repeti ion y el i lo es O(1). En este aso, el metodo es O(n) O(1) = O(n).
2. Si a[l .. i - 1] esta invertido; es de ir, sus elementos estan ordenados de mayor
a menor, enton es el i lo itera i l = i ve es, lo que nos da O(i). Puesto que i
depende de n, podemos de ir dire tamente que el i lo es O(n). Conse uentemente,
el metodo es O(n) O(n) = O(n2).
Las situa iones planteadas onsideran dos es enarios de eje u ion: el mejor aso, uando
el arreglo esta ordenado, y el peor, uando el arreglo esta ompletamente invertido. Si
la disposi ion del arreglo es aleatoria, enton es, ada uno de los es enarios tiene una
1
probabilidad de o urren ia de n!
; una muy baja probabilidad uando n es grande. As
pues, en un aso o en el otro, el analisis pe a de exageradamente optimista o pesimista;
on el onse uente peligro de ser irrealista al momento de expresar el desempe~no del
metodo. Para prede ir lo que su edera debemos onsiderar el aso esperado.
Si onsideramos la se uen ia a[l.. i - 1] omo una permuta ion aleatoria, enton es
el valor del ndi e de bre ha j tiene una probabilidad uniforme entre l e i 1. El numero
esperado de itera iones del i lo hAbra una bre ha a[j donde insertar tmp 202bi sera
la esperanza de una distribu ion uniforme dis reta entre l e i 1 y el i lo repetira
rl+1
ve es. Esto es O(n), lo que nos da omo on lusion de desempe~no del metodo
2
O(n) O(n) = O(n2); el mismo resultado del peor aso.
Comparemos el metodo de inser ion on el de sele ion. >Cual es el mejor? Si examinamos ligeramente el resultado, enton es podemos on luir, erroneamente, que son equivalentes -que lo son, asintoti amente-. Empero, el lazo interno del metodo de sele ion (hSea
min el ndi e menor entre i + 1 y n - 1 190ai) siempre itera n i + 1 ve es, independientemente de la permuta ion a[i+1 .. n -1]; mientras que la antidad de itera iones del
lazo interno del de inser ion (hAbra una bre ha a[j donde insertar tmp 202bi) depende
de la permuta ion a[l .. i - 1] . Para aprehender este he ho, es onveniente profundizar el analisis del metodo de inser ion para el aso promedio. Si aproximamos el numero
de repeti iones del lazo interno a 2i , enton es tenemos omo desempe~no:
6

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

Captulo 3. Crtica de algoritmos

void insert_sorted(Dlink & list, Dlink * p)


{
typename Dlink::Iterator it(list);
while (it.has_current() and Compare () (it.get_current(), p))
it.next();
if (it.has_current())
it.get_current()->append(p); // insertar a la derecha de current
else
list.append(p);
}
De nes:

insert sorted, used in hunk 204a.


Uses Dlink 90, get current 103, and has current 103.

insert sorted() inserta el nodo p en la lista ordenada list seg


un el riterio de ompara ion Compare. Con esta primitiva, el ordenamiento por inser ion queda on isamente
204a

de nido:
hM
etodos de ordenamiento 189bi+

(189a) 203 204b

template <class Compare>


void insertion_sort(Dlink & list)
{
Dlink aux;
while (not list.is_empty())
{
Dlink * p = list.remove_next();
insert_sorted<Compare>(aux, p);
}
list.swap(&aux);

}
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
}

El algoritmo bus a re ursivamente una o urren ia del elemento x en un arreglo que


esta ordenado entre l y r. Si l > r, enton es el rango de busqueda esta va o y tenemos
la ertitud de que el elemento x no se en uentra dentro del rango. De lo ontrario, inspe ionamos el elemento situado en el entro; si este ontiene el valor x, enton es m es el
resultado. Si no, de idimos, segun el orden de x, en ual de los dos subrangos debemos
bus ar: el izquierdo o el dere ho.
Asumamos que el rango [l..r] es de ardinalidad n.
Cuando n 0 el algoritmo termina en tiempo onstante O(1). Si, por el ontrario,
existen elementos dentro del rango, enton es pueden o urrir dos osas: o el elemento se
en uentra en el entro, o hay que bus ar re ursivamente en alguna de las parti iones.
Puesto que la parti ion o urre exa tamente por el entro, el resto del tiempo se onsume
sobre aproximadamente la mitad de la entrada. Planteamos, pues, la siguiente e ua ion
re urrente:

si n 0
O(1)
T (n) =
(3.14)
.
T (n/2) + O(1) si n 1

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

Captulo 3. Crtica de algoritmos

Lo interesante de la transforma ion es que expresa dire tamente la re urren ia omo


una sumatoria. En efe to, expandiendola tenemos:
T (2k) = T (2k1) + 1 = T (2k2) + 1 + 1
= T (200) + 1 + + 1 + 1 = k + 1

Ahora realizamos la transforma ion inversa:


T (n) = lg (n) + 1 ,

(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 de ni 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 de ni 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 de ni ion 3.1. En
otras palabras, a rmar 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

Dominio exa to de la nota i


on

Realidad

n1

La nota ion O es erteramente pre isa en el dominio de lo in nito (o de la eternidad),


pero este dominio puede ser muy abstra to y lejano de alguna realidad on reta. Por eso,
omo en todo lo objetivo, hay que andar on sumo uidado.
Costes muy grandes, inabordables, pero onstantes, pueden quedar ompletamente
o ultos por un analisis O. Lo mismo puede su eder on otros ostes asintoti amente inferiores pero onstantemente superiores. En a~nadidura, el o ultamiento total de la forma
de T (n), puede a arrear sorpresas si la eje u ion solo tiene sentido para es alas inferiores
a la ota asintoti a n1 de la de ni ion 3.1. Quiza lo peor, polti amente hablando, es que
una vez publi ado un analisis O, el ono imiento de aquellos ostes ya se~nalados tienden
a sepultarse para siempre.
3.1.10

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

Captulo 3. Crtica de algoritmos

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

1. Identificacion de un problema particular y su solucion


Esta parte identi a una solu ion parti ular del problema para un tama~no parti ular
de entrada n < nb. En esta fase, se requiere ono er omo resolver el problema para
la o urren ia de entrada parti ular.
A esta parte se le llama \base".
Asumamos que la solu ion base uesta T (nb). Puesto que nb es onstante, lo mas
probable es que T (nb) sea onstante, por lo que, la mayora de las ve es, el oste
de T (nb) = O(1), n nb.
2. Division
En esta parte se divide el problema en dos parti iones equitativas de tama~nos n1
n2 n
2.
Asumamos que la division uesta O(fd(n)).
3. Recursion
Esta parte onsiste en resolver re ursivamente ada parti ion y obtener solu iones s1(n1) y s2(n2).
Puesto que o urren llamadas re ursivas, el oste es des ono ido hasta tanto no se
onoz a el oste del aso base y de la ombina ion. Empero, a menudo el oste puede
expresarse re ursivamente omo T (n) = T (n1) + T (n2), pues las llamadas re ursivas
se orresponden on la misma fun ion de dura ion, pero para entradas menores.
4. Combinacion
En esta fase se ombinan las solu iones s1(n1) y s2(n2) resultantes de las llamadas
re ursivas en una solu ion de nitiva al problema.
Asumamos que la ombina ion uesta O(fc(n)).

7 En

ingles, tradi ionalmente se le denomina onquista.

3.2. Algoritmos dividir/combinar

209

Segun lo expresado, la dura ion de un algoritmo dividir/ ombinar se formula mediante


la siguiente re urren ia:

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

Ordenamiento por mezcla

Ejempli quemos 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);
}
De nes:
mergesort, used in hunk 689d.
Uses merge 211.

El algoritmo se orresponde exa tamente on el patron dividir/ ombinar. La fase de


ombina ion la realiza el pro edimiento merge(), el ual se en arga de \mez lar" dos
parti iones ordenadas. Antes de indagar el tiempo de eje u ion, requerimos ono er omo
se efe tua la mez la y analizar su desempe~no.
3.2.1.1

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

Captulo 3. Crtica de algoritmos

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

Es de ir, entre 0 y m opiamos el arreglo normalmente, mientras que entre m + 1 y r lo


invertimos. De esta forma, uando se efe tue la mez la, los mayores elementos fungiran de
entinelas naturales que nos ahorraran una segunda repeti ion junto on un if que de ida
sobre ual de las dos mitades hay que realizar el resto de la opia.
hCopia primera mitad arreglo 210ai
(211)
for (i = l; i <= m; i++)
b[i] = a[i];

210b

hCopia invertida segunda mitad arreglo 210bi


for (j = r, i = m + 1; i <= r; i++, j--)
b[i] = a[j];

(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

Lo ual se implanta del siguiente modo:


hmez lar mitades 210 i

for (k = l, i = l, j = r; k <= r; k++)


if (Compare() (b[i] < b[j]))
a[k] = b[i++];
else
a[k] = b[j--];

(211)

3.2. Algoritmos dividir/combinar

211

211

y que ompleta todo lo ne esario para programar la rutina merge():


hmez la de arreglos 211i
(209)
template <typename T, class Compare>
void merge(T a[], const int & l, const int & m, const int & r)
{
int i, j, k;
T b[r - l + 1];
hCopia primera mitad arreglo 210ai
hCopia invertida segunda mitad arreglo 210bi
hmez lar mitades 210 i

}
De nes:

merge, used in hunk 209.

El parametro m es el entro de la parti ion.


3.2.1.2

An
alisis del mergesort

Para analizar el mergesort() usamos la e ua ion (3.17) y planteamos:


T (n) =


O(1)

2T (n/2) + O(fc(n))

,n 1

,n > 1

(3.18)

Donde O(fc(n)) es la omplejidad de tiempo de la fase de ombina ion, o sea de la


fun ion merge(). Claramente, los bloques hCopia primera mitad de arreglo (never de ned)i y hCopia invertida segunda mitad del arreglo (never de ned)i onsumen n/2 pasos
de eje u ion, lo que los ha e O(n). El bloque hmez lar mitades 210 i onsume exa tamente n unidades de eje u ion lo que lo ha e tambien O(n). Por tanto, merge() onsume O(n) + O(n) + O(n) = O(n). De este modo, (3.18) se plantea omo:
T (n) =


O(1)

2T (n/2) + O(n)

,n 1

,n > 1

(3.19)

Esta re urren ia tambien puede resolverse asumiendo n = 2k = k = lg(n); o sea,


asumimos que n es una poten ia exa ta de 2. Para n > 1, (3.19) se plantea omo:
T (2k) = 2T (2k1) + O(2k) =
T (2k)
T (2k1)
=
+ O(1)
2k
2k1
T (2k2)
+ O(1) + O(1) = O(1) + + O(1) +O(1)
=
|
{z
}
2k2
k ve es

= O(k) + 1

(3.20)

Al regresar al plano n tenemos que (3.20) se plantea omo:


T (n)
= O(lg (n)) + O(1) =
n
T (n) = O(n lg (n)) + O(n) = O(n lg (n)) ;

(3.21)

212

Captulo 3. Crtica de algoritmos

para n = 2k. El ual esta asintoti amente a otado por O(n lg (n)). En el in nito 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

Estabilidad del mergesort

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 de ni 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

Ordenamiento por mezcla de listas enlazadas

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());

3.2. Algoritmos dividir/combinar

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
}
De nes:

mergesort, used in hunk 689d.


Uses Dlink 90 and split list 99a.

La bibliote a de ne 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)

Consideremos un arreglo desordenado a y la invo a ion pivot = partition(a, l, r), el


ual parti iona el arreglo en dos partes generales omo se pi toriza en la siguiente gura:
l

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 de nitiva 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
hDe ni i

214

Captulo 3. Crtica de algoritmos

template <typename T, class Compare>


void quicksort_rec(T a[], const int & l, const int & r)
{
if (l >= r) return;
const int pivot = partition <T, Compare> (a, l, r);
quicksort_rec <T, Compare> (a, l, pivot - 1);
quicksort_rec <T, Compare> (a, pivot + 1, r);
}
Uses partition 215 .

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 di ere 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)

O(fp(n)) + T (pivot l) + T (r pivot)

,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

while a[i] < pivot


havan e

i hasta primer elemento mayor que pivot 215ai

r
pivot

while a[j] > pivot


havan e

j hasta primer elemento menor que pivot 215bi

La gura representa el arreglo entre l y r. i es un ndi e por el lado izquierdo del


arreglo y j es por el lado dere ho. i se mueve ha ia la dere ha, lo que impli a que debe
in rementarse; mientras que j se mueve ha ia la izquierda, por lo que se debe de rementar.
8 En

la ita original Wirth emplea el termino \inventor".

3.2. Algoritmos dividir/combinar

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:
hDe ni 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);

template <typename T, class Compare>


int partition(T a[], const int & l, const int & r)
{
hSele ionar pivote 220bi
T pivot = a[r]; // elemento pivot
int i = l - 1, //
ndice del primer elemento a la izquierda mayor que pivot
j = r;
//
ndice del primer elemento a la derecha mayor que pivot
while (true)
{
havan e i
havan e j

hasta primer elemento mayor que pivot 215ai


hasta primer elemento menor que pivot 215bi

if (i >= j)
break;

216

Captulo 3. Crtica de algoritmos

// En este punto hay una inversi


on a[i] > a[pivot] > a[j]
Aleph::swap(a[i], a[j]); // Eliminar la inversi
on
}
Aleph::swap(a[i], a[r]);
return i;
}
De nes:
partition, used in hunks 213b, 218, 221, 223, 226a, and 227.
Uses select pivot 220a.

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

O(n) + T (pivot l) + T (r pivot ) , n > 1

(3.23)

Si suponemos una permuta ion aleatoria, enton es la esperanza sobre el punto de


parti ion se situa en l+r
on (3.23)
2 , o sea, en el entro del arreglo, por lo que la e ua i
puede aproximarse omo:
T (n) =


O(1)

O(n) + 2T (n/2)

,n 1

,n > 1

(3.24)

la ual es O(n lg(n)).


El desempe~no del qui ksort es O(n lg(n)) para el aso esperado y para el mejor aso.
El peor aso puede o urrir uando el pivote sea justamente el mayor de todos los
elementos entre i y j. En esta desafortunada situa ion, la parti ion izquierda es de longitud r l 1, mientras que la de la dere ha es 0. En otras palabras, no se logra dividir
efe tivamente. La e ua ion (3.23) se expresa omo:
T (n) =


O(1)

O(n) + T (n 1)

,n 1

,n > 1

(3.25)

3.2. Algoritmos dividir/combinar

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
de nitiva; 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

de nido 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 hDe ni ion de parti ion de
arreglo 215 i, el arreglo siempre se parti iona en:
quick sort rec<T, Compare>(a, r+1, r)

pivotquick sort rec<T, Compare>(a, l+1, r-1)

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

Captulo 3. Crtica de algoritmos

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)

donde n es la antidad de elementos. La sumatoria exa ta o urrira si n = 2k 1; o sea,


si n+1 es una poten ia exa ta de 2. En este aso, todas las parti iones o asionadas siempre
son exa tamente del mismo tama~no. La gura 3.2 pi toriza todas las llamadas re ursivas

3.2. Algoritmos dividir/combinar

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)

qs(10, 10) qs(12, 12) qs(14, 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 identi quemos 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

nombre del metodo ha sido abreviado por e onoma de espa io.

220

Captulo 3. Crtica de algoritmos

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

template <typename T, class Compare>


int select_pivot(T a[], const int & l, const int & r)
{
if (l - r <= 2)
return r;
const int m = (r + l) / 2; //
ndice del centro

int p = Compare () (a[l], a[m]) ? m : l; // p = max(a[l], a[m])


return Compare () (a[r], a[m]) ? r : p;
}
De nes:
select pivot, used in hunks 215 and 220b.

220b

// retornar min(a[r], a[m])

La rutina anterior permite ompletar el metodo de nitivo de parti ion de la siguiente


manera:
hSele ionar pivote 220bi
(215 )
const int p = select_pivot <T, Compare> (a, l, r);
Aleph::swap(a[p], a[r]);

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.

3.2. Algoritmos dividir/combinar

221

Uses select pivot 220a.

3.2.2.5

221

Quicksort sin recursi


on

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;

typedef typename Aleph::pair<int, int> Partition;


FixedStack<Partition, 32> stack;
stack.push(Partition(l, r));
while (stack.size() > 0)
{
const Partition p = stack.pop();
const int pivot = partition<T, Compare>(a, p.first, p.second);
if (pivot - p.first < p.second - pivot) // cu
al partici
on m
as peque~
na?
{
// partici
on izquierda m
as peque~
na
stack.push(Partition(pivot + 1, p.second));
stack.push(Partition(p.first, pivot - 1));
}
else
{
// partici
on derecha m
as peque~
na
stack.push(Partition(p.first, pivot - 1));
stack.push(Partition(pivot + 1, p.second));
}
}
}
De nes:

quicksort, used in hunks 222a, 757 , and 760.


Uses FixedStack 131a and partition 215 .

222

Captulo 3. Crtica de algoritmos

3.2.2.6

222a

Quicksort sobre listas enlazadas

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;

Dlink * pivot = list.remove_next();


Dlink smaller; // lista de los menores que pivot
Dlink bigger; // lista de los mayores que pivot
hparti ionar

lista 222bi

quicksort <Compare> (bigger);


quicksort <Compare> (smaller);
list.concat_list(&smaller);
list.append(pivot);
list.concat_list(&bigger);
}
Uses concat list 96, Dlink 90, quicksort 221 222 , and remove next 97b.

222b

La estru tura del algoritmo no es nada misteriosa, pues se orresponde on el esquema


del algoritmo: parti ionar la se uen ia en el patron smallerpivotbigger. La
lista smaller solo ontiene los elementos menores que pivot, mientras que la lista bigger
los mayores.
Ahora nos resta espe i ar la parti ion, la ual es onsiderablemente mas sen illa que
on los arreglos:
hparti ionar lista 222bi
(222a 226b)
while (not list.is_empty())
{
Dlink * p = list.remove_next();
if (Compare () (p, pivot))
smaller.append(p);
else
bigger.append(p);
}
Uses Dlink 90 and remove next 97b.

222

ALEPH exporta una espe ializa ion para el tipo Dnode<T>:


hM
etodos de ordenamiento 189bi+
(189a) 222a 223
template <typename T, class Compare>
void quicksort(Dnode<T> & list)
{

3.2. Algoritmos dividir/combinar

223

quicksort < Compare_Dnode<T, Compare> > (list);


}
template <typename T>
void quicksort(Dnode<T> & list)
{
quicksort < T, Aleph::less<T> > (list);
}
De nes:
quicksort, used in hunks 222a, 757 , and 760.
Uses Compare Dnode 192a and Dnode 106a.

La ual opera perfe tamente para Dlist<T> y DynDlist<T>.


3.2.2.7

223

Mejoras al quicksort

Revisemos el pro edimiento de parti ion hDe ni 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

Captulo 3. Crtica de algoritmos

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 .

El valor exa to de Insertion Threshold es relativo y depende de las ondi iones


materiales y operativas. Un valor entre 20 y 30 es su iente en el momento de reda ion
de este texto .
11

11 En

enero 2006, pro esadores i686 a 3 Ghz, on 2Mb de a he L2.

3.2. Algoritmos dividir/combinar

3.2.2.8

225

Claves repetidas

Aunque el algoritmo de parti ion hDe ni 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

Elementos iguales pivot

Elementos mayores 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

Quicksort concurrente o paralelo

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
de nitiva 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 inter eran 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

Cuando intentamos realizar el problema fundamental mediante arreglos, nos en ontramos


on una disyuntiva. Si ordenamos, enton es podemos emplear la e iente busqueda binaria,
pero tenemos una inser ion y supresion O(n). Por el ontrario, si mantenemos la se uen ia
desordenada, enton es tenemos una inser ion muy rapida O(1), pero una busqueda y
supresion lentas O(n). En la mayora de las o asiones, la razon de ser de resolver el

226

226a

Captulo 3. Crtica de algoritmos

problema fundamental es la busqueda, razon por la ual pudieramos ordenar de vez en


uando o uando estemos seguros de que no habran mas inser iones. Pero ello aun es
ostoso y limita la solu ion a un espe tro peque~no de situa iones.
Una alternativa elegante de busqueda, hbrida entre el orden y el desorden, que permite implantar el onjunto fundamental mediante se uen ias, se sirve de la parti ion del
qui ksort expli ada en x 3.2.2.1 (pagina 214). En efe to, podemos servirnos del metodo
desarrollado en hDe ni ion de parti ion de arreglo 215 i (x 3.2.2.1) para implantar la
busqueda sobre un arreglo desordenado:
hM
etodos de ordenamiento 189bi+
(189a) 223 226b
template <typename T, class Compare>
int random_search(T a[], const T & x, const int & l, const int & r)
{
if (l > r)
return Not_Found;
const int pivot = partition<T, Compare>(a, l, r);
if (Compare() (x, a[pivot]))
return random_search(a, l, pivot - 1);
else if (Compare() (a[pivot], x))
return random_search(a, pivot + 1, r);
return pivot; // elemento encontrado en el
ndice x
}
De nes:

random search, never used.


Uses partition 215 .

226b

La idea del algoritmo es parti ionar aleatoriamente el arreglo y obtener el punto de


parti ion pivot. Re ordemos que el pivote siempre se en uentra en su posi ion de orden
de nitiva. Por tanto, el orden de x respe to a pivot determina en ual de las dos parti iones
se ontinua re ursivamente la busqueda.
Por supuesto, segun el analisis de x 3.2.2.1 (pagina 214), el algoritmo re orre enteramente el arreglo. Razon por la ual es fa il ver que la busqueda general no podra apoyarse
enteramente en random search(). Pero s podra, empero, mantener una lista ordenada
de pivotes a partir de la ual realizar busquedas se uen iales que seran onsiderablemente
mas ortas que re orrer enteramente al arreglo.
El analisis formal de este algoritmo se delega omo ejer i io. La busqueda aleatorizada
es perfe tamente plausible on listas enlazadas. El prin ipio (y el algoritmo resultante) es
el mismo: parti ionar la lista segun el pivote y luego de idir en ual de las dos parti iones
ontinuar la busqueda:
hM
etodos de ordenamiento 189bi+
(189a) 226a 227
template <typename T, class Compare>
Dnode<T> * dlink_random_search(Dlink & list, const T & x)
{
if (list.is_empty())
return NULL;

Dnode<T> * pivot = static_cast<Dnode<T>*>(list.remove_next());


Dnode<T> item(x);

3.2. Algoritmos dividir/combinar

227

Dnode<T> * item_ptr = &item; // puntero a una celda que contiene a x


Dlink smaller; // lista de los menores que pivot
Dlink bigger; // lista de los mayores que pivot
hparti ionar

lista 222bi

Dnode<T> * ret_val = NULL;


if (Compare () (item_ptr, pivot))
ret_val = dlink_random_search <T, Compare> (smaller, x);
else if (Compare () (pivot, item_ptr))
ret_val = dlink_random_search <T, Compare> (bigger, x);
else
ret_val = pivot;
list.swap(&smaller);
list.append(pivot);
list.concat_list(&bigger);
return ret_val;
}
De nes:
dlink random search, never used.
Uses concat list 96, Dlink 90, Dnode 106a, and remove next 97b.

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

Captulo 3. Crtica de algoritmos

}
De nes:

random select, used in hunk 229.


Uses partition 215 .

Importante se~nalar que aqu el problema s se resuelve mas rapido on

random select() que mediante la mera b


usqueda se uen ial. En efe to, en este aso la
sele ion sera O(n) 2.
Es onveniente veri ar que el valor de i se ir uns riba al rango [l..r]. A efe tos de

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
de ne 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");

return __random_select(a, i, 0, n - 1);


}
De nes:
random select, used in hunk 229.

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();

if (Compare () (p, pivot))


{
smaller.append(p); ++smaller_count;
}
else
{
bigger.append(p); ++bigger_count;
}
}
Uses Dlink 90 and remove next 97b.

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

Dlink smaller; // lista de los menores que pivot


Dlink bigger; // lista de los mayores que pivot
size_t smaller_count = 0, // cantidad de elementos de smaller
bigger_count = 0; // cantidad de elementos de bigger
hparti ionar

lista y ontar 228bi

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;
}
De nes:
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

Captulo 3. Crtica de algoritmos

 Un TAD tiene varias opera iones uyos desempe~


nos pueden ser diferentes y que

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

template <typename T>


void push(DynListStack<T> & stack, const size_t & n)
{
if (n > stack.size())
throw std::overflow_error("Stack overflow");
for (int i = 0; i < n and stack.size() > 0; ++i)
stack.pop();

}
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

Nombre de opera ion

E ien ia peor aso

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 de nida 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 de ne 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

Diferen ia de poten ial:

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 de nir el oste total de n opera iones de la siguiente
14

14 De alguna vaga manera, el lenguaje ienti ista moderno a


un no ha logrado empobre er ompletamente

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 re riendo
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
pre jo (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

Captulo 3. Crtica de algoritmos

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

ti + (D1) + (D2) + + (Dn) (D0) + (D1) + + (Dn1)


ti + (Dn) (D0)

(3.28)

i=1

Para que el poten ial tenga su sentido, es menester de nir 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 a rmar 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 de namos : 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

Opera ion ti (Di) i = (Di) (Di1) b i = ti +i


push()
push()
push()
pop()
push()
popn(3)

1
1
1
1
1
3

no, quiza, en lo divino.

1
2
3
2
3
0

1
1
1
1
1
3

2
2
2
0
2
0

3.3. An
alisis amortizado

233

Al nal de un push(), el tama~no de la pila se in rementa en uno, por lo que siempre


se umple:
b
i = |{z}
1 + |{z}
1 ;
ti

si la i-esima opera ion es push().


Al nal de un popn(k), (Di) = Ni1 k donde Ni1 es la antidad de elementos antes
de efe tuar popn(). De este modo, luego de eje utado un popn(k), el oste amortizado
sera:
b
i = |{z}
k + Ni1 k Ni = 0 ;
|

{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)

La desigualdad es la misma que la expresada en (3.29), pero asumiendo que el


oste amortizado b i es onstante para ada opera ion (lo ual resulto el aso para el

234

Captulo 3. Crtica de algoritmos

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, on ar,
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 pre jo 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.

3.4. Correctitud de algoritmos

235

ellas amortizan o onsumen reditos entre s.


De una ierta forma, el analisis amortizado es subjetivo en el sentido de que delega en
la fun ion poten ial la subjetividades del TAD y de su estru tura de datos. Es, enton es,
durante el estudio y sele ion de la fun ion poten ial (o de los reditos por opera ion),
uando se tratan tales subjetividades.
Por lo general, la fun ion poten ial debe ponderar el estado de la estru tura de datos.
En el aso de una se uen ia, el tama~no es, a menudo, una buena es ogen ia.
Una manera de fa ilitar el analisis amortizado de una opera ion onsiste en dividirla
en etapas se uen iales, analizar ada una por separado y luego sumarlas. Supongamos que
una se uen ia de opera iones y una opera ion efe tuada en la i-esima vez on oste amortizado b i entre el estado Di1 y Di. Ahora supongamos que la fase Di1 Di puede
dividirse en las fases D1, D2, . . . Dk, de manera tal que Di1 = D0 y Di = Dk. Sea tj el
oste de pasar del estado Dj1 a Dj, enton es:
ti =

k
X

tj

j=1

Segun (3.27), el oste amortizado de ti puede de nirse omo:


k
X
j=1

b
j =
=

k
X

(tj +(Dj) (Dj1))

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

Cuando hablemos a er a de la orre titud de un algoritmo o programa, nos referimos


a que este logre el n para la ual fue destinado. Puesto de otro modo, la orre titud
on ierne a lo que al prin ipio de este aptulo denominamos \e a ia", lo ual, en el aso
de un programa, equivale a de ir que este no arroje malas respuestas. A menudo esto no
es sen illo de determinar porque no es hasta que el programa este operativo que se puede
veri ar si es o no orre to.
La e a ia que, re ordemos, no es lo mismo que la e ien ia, se juzga por ompletitud
y minimalidad. Por ompletitud de imos que el efe to, o sea el n, se al an e abalmente.
Por minimalidad de imos que no se efe tue algo adi ional al n; lo que en el lenguaje
te no rati o se denomina \efe to olateral"; expresion que omunmente indi a que la onse u ion del n a arrea efe tos adi ionales que fre uentemente son indeseables.

236

Captulo 3. Crtica de algoritmos

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

Planteamiento de una demostraci


on de correctitud

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 in nitos, 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 in nitos, 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

Esen ialmente, un error en un programa puede ometerse de las siguientes formas:


De dise
no : Premisas in orre tas, asumidas durante el dise~
no de un algoritmo, muy posi-

blemente onduz an a resultados in orre tos.

De prueba de correctitud del algoritmo : A ve es la orre titud de un algoritmo no

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.

3.4. Correctitud de algoritmos

237

2. Que alguna o mas de las pruebas este errada.


Posiblemente, los errores en la prueba de orre titud son los mas serios, pues indu en
lo que se denomina omo \falsa per ep ion"; el algoritmo podra o no estar orre to,
pero a rmamos, sin ertitud, que lo es.
De codificaci
on : Los algoritmos deben tradu irse a un lenguaje de programa ion. Esta

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).

De falla de programas de verificaci


on : Como men ionaremos posteriormente, para

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

Captulo 3. Crtica de algoritmos

Costo de correccin
de un error

Produccin
Pruebas

Codificacin

Diseo estructura
de cdigo

Tiempo de desarrollo de un sistema

Diseo arquitectural
Requerimientos

La gra a pi toriza las diferentes etapas del llamado \ i lo de desarrollo de software".


Durante las tres primeras fases es uando se toman de isiones de dise~no de algoritmos y
me anismos de sistema, mientras que durante las tres restantes es uando tpi amente se
dete tan los errores, siendo los de dise~no los mas ostosos. Podemos de ir que el oste de
un error y su orre ion es dire tamente propor ional al momento en que se se dete ta.
Consiguientemente, mientras mas tarde se dete te un error, mas impa to en ostes este
tiene.
De la re exion anterior se debe desprender un fuerte sentido de pre au ion que se
debe asumir durante las tres primeras etapas del i lo de desarrollo. El problema basi o, y
esta es la trampa a la que su umben mu hos ingenieros y programadores, es que, uando
se realiza un nuevo sistema y se toman de isiones de dise~no, no se ono en uales seran
sus bondades en e a ia y e ien ia. Ahora bien, mu has ve es se ae en la trampa de
tomar de isiones de dise~no sin ono er a priori sus bondades. Conse uentemente, mu has
ve es se en uentran errores de dise~no, que son de los mas ostosos, durante la fase de
instrumenta ion. >Como se puede disminuir este riesgo? La respuesta es realizando modelos
de dise~no y veri andolos antes de su implementa ion; omo se a ostumbra ha er en otras
ramas de la ingeniera.
3.4.3.1

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".

3.4. Correctitud de algoritmos

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

1. No usar goto, longjmp o re ursion dire ta o indire ta.


Salvo algunas ex ep iones parti ulares, la instru ion goto ha sido abolida desde
ha e unas uantas de adas porque va ontra la legibilidad de odigo. En a~nadidura,
los analizadores estati os tienen serias di ultades para interpretar que su ede on
un goto. Lo mismo o urre on el onjunto de instru iones longjmp de la bibliote a
estandar C.
Rutinas re ursivas, dire ta o indire tamente, son tambien muy dif iles de analizar
por analizadores estati os. En a~nadidura, la re ursion usa impl itamente la pila del
sistema, uya gestion es dif il de a otar.
Hay, por supuesto, ex ep iones. Muy en parti ular, en este texto, on la re ursion
dire ta. En ese sentido, podemos de ir que solo debe utilizar re ursion para fun iones uya orre titud haya sido demostrada y uyo onsumo de pila se garanti e a
estar a otado. En esos asos, la fun ion re ursiva puede substituirse por un tron o
o indi arse a un analizador que la fun ion en uestion es orre ta.
2. Siempre asegurar que todo lazo termine.
>Cuantas ve es no hemos eje utado un programa que no termina? En el aso general,
los programas no terminan - uando estan dise~nados para terminar- porque aen en
lazos eternos .
La regla onsiste, enton es, en eviden iar garanta de que ada lazo del programa (while, do-while, o for) termine. Cada vez que se programe un lazo
preguntese y respondase omo se asegura que este siempre termine.
La uni a ex ep ion a esta regla es uando se es ribe un lazo que jamas debe terminar.
Normalmente esto o urre en programas iterativos; por ejemplo, un servidor. En este
aso, la regla es asegurar que, en efe to, el lazo nun a termine.
17

3. No utilizar memoria dinami a despues de haber ini ializado el programa.


En el espritu de este texto, la regla es: independizar toda abstraccion del uso de
memoria din
amica. En otras palabras, ualquier algoritmo o estru tura de datos
debe on ebirse sin dependen ia en memoria dinami a.
17 A

menudo, la expresion es \lazo in nito". Sin embargo, en matemati a, as omo en otros dominios, el
adjetivo in nito 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

Captulo 3. Crtica de algoritmos

Por memoria dinami a entendemos llamadas a malloc()/free(), new/delete,


sbrk(), alloca() y, en general, a ualquier otra primitiva de manejo de memoria. Por ini ializa ion entendemos la primera fase de un programa en la ual se
de laran las estru turas de datos y demas lases de variables y se les asignan sus
valores primigenios.
La razon de ser de esta regla es doble. En primer lugar, la inmensa mayora de me anismos de administra ion de memoria no ofre en otas onstantes sobre el tiempo de
eje u ion. A medida que se gestiona mas memoria, mas fragmenta ion o urre en el
espa io de dire ionamiento, lo que degrada el desempe~no y la probabilidad de exito
de reserva ion. Conse uentemente, mientras mas dure un programa, mas probable
es que la reserva ion de un bloque de memoria falle. Esta fuente poten ial de falla
ha e imposible ofre er una garanta de orre titud (desde la perspe tiva de manejo
de memoria).
En segundo lugar, el manejo de memoria es origen de una gran propor ion de errores,
razon por la ual una forma de evadir este poten ial de error onsiste, simplemente,
en no usar memoria dinami a. Observemos que el uso de un \re ole tor de basura"
(garbage olle tor) no evade este prin ipio, sino que mas lo bien justi a, pues la
gestion de memoria basada sobre un re ole tor exhibe mu ha mas in ertidumbre y
tiene menos desempe~no que un manejador tradi ional.
Los errores de manejo de memoria seran brevemente tratados en x 3.4.3.3.
Programas dise~nados para apartar sus re ursos omputa ionales durante su ini ializa ion no solo son mas fa iles de asegurar orre titud, sino que tienden a exhibir
mas desempe~no.
En fun ion de esta regla, >nos esta vedado el uso de memoria dinami a? La respuesta es relativa. Notemos, en primer lugar, que los dise~nos de algoritmos y estru turas de datos que tratamos en este texto se a ogen, en su mayora, a esta regla.
De he ho, hasta el presente hemos apli ado el prin ipio n-a- n presentado en 1.4.2,
uya observan ia ondu e a primero abstraer sin onsiderar el manejo de memoria;
omo en efe to lo hemos he ho y haremos a traves de este texto. Los TAD dependientes de memoria ( on pre jo en nombre de lase \Dyn"), derivan de una version
independiente del manejo de memoria.
Existen problemas uyas solu iones son \largas" en dura ion y para los uales es
ineludible el manejo de memoria. En esta lase de problemas en ajan los grafos y
otras lases de apli a iones.
4. Maximo 60 lneas por rutina.
La vision y omprension humana de una rutina omo una unidad de programa se
remite a lo visible en una hoja de papel; o sea, aproximadamente 60 lneas. Rutinas
que ex edan esta longitud se fragmentan en varias hojas (o pantallas) y, por supuesto,
tienen mas probabilidad de fragmentar la omprension.
5. Mnimo dos invariantes (o asertos) por fun ion.
Una invariante es una ondi ion que siempre debera de umplirse. Su in umplimiento denotara, enton es, un error.

3.4. Correctitud de algoritmos

241

Las estadsti as industriales de prueba de programas indi ian o urren ia de error


entre 10 y 100 lneas de odigo. Por tanto, el uso de invariantes omo pre ondi iones,
post ondi iones e invariantes que se deben mantener en lazos, aunado a la regla
inmediatamente anterior, debe favore er la alidad del odigo por disminu ion de las
probabilidades de o urren ia de error y de aumento de su dete ion.
Las invariantes no afe tan el efe to de una rutina, aunque s su desempe~no. Por
esta razon, me anismos de invariantes pueden deshabilitarse o ha erse de alto
rendimiento. Mas adelante, en x 3.4.3.3, hablaremos sobre el uso de invariantes.
6. De lare e ini iali e objetos lo mas er a posible de su primer uso.
La orrup ion de datos es otra de las o urren ias prin ipales de error. >Cuantas ve es
no nos hemos en ontrado en la situa ion \misteriosa" en la que una variable no tiene
el valor esperado?
Una de las virtudes del o ultamiento de informa ion es que limita la orrup ion
al ambito de uso del dato. Si se trata de un TAD, bien realizado, por supuesto,
la orrup ion de uno de sus atributos solo puede deberse a uno de sus metodos.
Pare ido o urre a nivel de los objetos de uso interno de una rutina.
Por tanto, de larar un objeto lo mas er a posible de su primer uso favore e que el
objeto solo exista en su ambito de uso. En a~nadidura, ini ializa ion inmediata a la
de lara ion elimina la posibilidad de orrup ion entre la de lara ion y su primera
utiliza ion.
7. Siempre veri ar valor de retorno de una fun ion as omo sus parametros de entrada
y salida.
En palabras mas pre isas, ada punto invo ante de una fun ion debe veri ar pertenen ia al dominio del valor de retorno as omo de sus parametros de salida. Del
mismo modo, ada fun ion debe veri ar que los parametros re ibidos tambien esten
dentro de su dominio.
8. Minimi e el uso del prepro esador a in lusion de ar hivos y espe i a ion de ma ros
simples.
Aunque los prepro esadores son muy poderosos omo medios ad-ho para extender
el lenguaje, ellos no son ompiladores. Un prepro esador no veri a el sistema de
tipos y, en a~nadidura, puede generar instru iones que ompilan, que orrientemente
fun ionen, pero que estan totalmente o ultas a la mirada del programador.
Conse uentemente, se debe evitar la on atena ion de nombres, los parametros variables y los ma ros re ursivos. La ex ep ion de la regla es para aspe tos que no
pueda implantar el lenguaje, en uyo aso se debe usar parentesis por ada unidad
de expansion del ma ro.
Re ordemos que C y C++ tienen fun iones inline que son equivalentes en fun ionalidad y desempe~no a la mayora de los ma ros, on el valor a~nadido de que usan el
sistema de tipos del ompilador.
9. Restringa el uso de apuntadores a no mas de un nivel de dereferen ia.

242

Captulo 3. Crtica de algoritmos

Una dereferen ia es un a eso a lo que ontiene el puntero. En este sentido, la


regla estable e que no se pueden usar dos o mas dereferen ias; por ejemplo, algo
omo **ptr = ....
Para omenzar, mas de un nivel es ompli ado de omprender aun para los mas
virtuosos programadores. En segundo lugar, tambien es dif il de estudiar por los
analizadores estati os.
Por supuesto, hay asos en que la de lara ion de punteros a punteros es ne esaria, pero esto no ne esariamente a arrea viola ion de la regla. Para un ejemplo
de seguimiento de esta regla, vease la implanta ion de DynArray<T> presentada
en x 2.1.5.
10. Siempre ompile on todos los alertas del ompilador habilitados y use analizadores
de odigo.
La mayor parte de los ompiladores modernos efe tuan veri a iones de errores
tpi os. >Por que razon habran de des artarse esas veri a iones? La ruda respuesta es que mu hos programadores son mal riados y pre eren saltar un error o
alerta del ompilador on tal de proseguir, on mas probabilidad de error en el futuro, su proye to. El mismo razonamiento apli a para los analizadores estati os o
dinami os.
A ve es o urre lo que se denomina \falso positivo"; es de ir, un reporte o alerta de
un error que no existe. Por lo general, estos reportes o urren en ombina iones de
odigo intrin adas las uales, a efe tos de la laridad, es preferible modi arlas para
que el ompilador no arroje mensajes de alerta o de error.
A traves del devenir de la programa ion, se ha observado que tras la orre ion de
un error se tiende a en ontrar otro rela ionado o uno nuevo introdu ido por la misma
orre ion. Tambien se ha notado una resisten ia \natural", vi iosa, por parte del programador promedio, en mirar minu iosamente el origen del error. Tal a titud es vi iosa
porque no basta on orregir el error, sino, en aso de que detras del mismo subyaz a una
mala a titud, en ontrar en que onsiste tal a titud de modo que esta pueda enmendarse
y, onsiguientemente, no repetir la misma lase de error.
Tom Van Cle k, uno de los prin ipales desarrolladores de Multics [6, 28, quiza el hito
mas importante en sistemas operativos, se plantea tres preguntas [39 que un desarrollador
siempre debe ha erse ada vez que dete te un error y que lo en aminan a des ubrir la
a titud originaria de los errores.
Las preguntas actitudinarias de Van Cleck

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

3.4. Correctitud de algoritmos

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 pre jo \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 de nido. 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 signi que 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 di ere 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 on anza 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

Captulo 3. Crtica de algoritmos

legado de un largo devenir de ono imientos pra ti os omprobados. La ingeniera ivil


moderna tiene mas de un siglo y mu has de sus normas son mas que entenarias. Lo
mismo o urre en otros dominios ingenierles; en la o urren ia, la ele tri idad, ele troni a,
me ani a, qumi a, geologa, et etera. Las ingenieras a tuales son apa es de normalizar
representa iones objetivas de sus dise~nos, las uales se le transmiten a un ente instrumentista para su realiza ion. Por ejemplo, en la ingeniera ivil, se entregan los planos de la
onstru ion; en la ele tri a, los planos de la red ele tri a.
Lamentablemente, ojala podamos de ir \por ahora", el desarrollo de software no tiene
un devenir similar en historia y dura ion al de las otras ingenieras que permita normalizar ostumbres y objetizar dise~nos en formas analogas a los planos. Y para apturar
la diferen ia debemos a udir a otra etimologa foranea.
La palabra alemana \waren" signi a \art ulo", \ osa". De \waren" proviene \ware",
uya onnota ion, en ingles, es la misma que en aleman. En ingles \ware" su ja palabras
que representan lases de osas. As, por ejemplos, \silverware" onnota art ulos de plata
y \delftware" se~nala osas de alfarera.
Bajo la lnea de razonamiento anterior, \hardware" denota el onjunto de osas duras.
Si bien el termino es usado en una amplia variedad de ontextos rela ionados on ferretera
y utilera, el uso que nos ata~ne es para designar al onjunto de omponentes, duros, que
onforman un sistema omputa ional, y, por supuesto, a otra lase esen ialmente onstituyente de un sistema omputa ional, que no es dura, sino, mas bien, \blanda", ompletamente intangible, imper eptible a nuestros sentidos, y, sin embargo, muy on reta,
denominada \software" y que designa aquello que mentamos programa.
Ahora bien, su ede que la rea ion de software aun no se supedita a te ni as jas que
garanti en su manufa tura. La programa ion es, quien sabe sino para siempre, pero s, al
menos, por ahora, un arte que no puede substituirse por una norma, metodo o te ni a que
garanti e, omo ya o urre en otras ingenieras, el logro de un proye to. Al da de hoy, el
al an e de un proye to de programa ion subya e en la genialidad de sus obrantes.
Di ho lo anterior, estamos prestos para la a laratoria objeto de esta sub-se ion.
Por su ara ter blando, digamos que todava dependiente de la genialidad humana, en
la programa ion aun no se ha logrado, en un nivel equiparable al resto de las ingenieras,
\ordinarizar" los pro esos a un punto en que se dispongan de normas jas que regulen los
pro esos organiza ionales, sus metodos, sus planos de onstru ion y los roles organizativos
que permitan el logro de un proye to en una obra o, en su fra aso, estable er justamente
las responsabilidades.
Pues bien, existe una rama de la omputa ion, muy respetable, que intenta dilu idar las
normas estudiando las ostumbres. Esta rama es la Ingeniera del Software y uya madurez,
en la opinion de este reda tor, no ha tras endido mas alla de normas de odi a ion muy
apre iadas por odi adores autenti os. El resto de lo que llaman Ingeniera del Software
aun no se ha onsolidado omo Ingeniera en el buen sentido de lo ordinario y de lo
objetivo.
Las razones de lo anterior son diversas, pero nos onviene se~nalar las tres mas importantes. En primer lugar, omparado a otras ingenieras, a la de programa ion aun le
falta devenir. En segundo lugar, el espe tro de lasi a ion de obras de software no solo
es mu ho mayor que el de otras ingenieras, sino que, en onsona on la razon anterior,
20

21

20 \Hard" signi a \duro".


21 \Soft" signi a \blando".

3.4. Correctitud de algoritmos

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 de nir 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

Captulo 3. Crtica de algoritmos

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

Compilador y sistema de tipos

El sistema de tipos del lenguaje de programa ion, en onjun ion on el ompilador,


realizan un enorme trabajo de veri a ion de orre titud al validar la orresponden ias
del programa respe to al sistema de tipo del lenguaje. Como ejemplo trivial, pero notable,
onsideremos:
int x,
string s;
s = x; // Violaci
on de tipo. ERROR DE COMPILACI
ON

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

Resulta que la busqueda de errores en el odigo fuente de un programa se puede


automatizar para validar que este siga los estandares de odi a ion del lenguaje u otros
estable idos por el grupo trabajo. Quiza el ejemplo mas reputado de esto sea lint [17, un
analizador estati o de fuentes para el lenguaje C que ha devenido esen ial para el desarrollo
de programas en C, habida uenta de la debilidad del sistema de tipos de este lenguaje.
lint a
un es ampliamente usado.
Hay unas uantas herramientas, en su desafortunada mayora de uso omer ial, que realizan analisis estati o de orre titud de un programa. Lamentablemente, aun existen po as
herramientas libres que reali en un analisis estati o al mismo nivel que otras omer iales.
Podemos itar, empero, a LC-LINT, omplementario del elebre lint [17, y Flawfinder [2
y its4 [40 orientados a la busqueda de errores de seguridad.

3.4. Correctitud de algoritmos

247

Meta-compilaci
on

Meta- ompila ion onsiste en ofre er un ompilador extensible en el sentido de que el


usuario pueda a~nadir nuevas reglas de ompila ion segun el ono imiento apli ativo del
usuario. Esta idea tiene mu ho sentido porque el ompilador posee la maquinaria ne esaria
para la veri a ion automati a, pero no posee el ono imiento sobre el tipo de apli a ion.
Por el ontrario, el odi ador s posee aquel ono imiento, pero adole e de la falta de
aquella maquinaria. La idea se basa, por tanto, en el prin ipio n-a- n.
Por ejemplo, un usuario podra espe i ar que la asigna ion entre punteros esta prohibida y que para toda llamada al operador new sobre un puntero debe en ontrarse por
alguna parte otra llamada al operador delete. Del mismo modo, tal extension al ompilador podra permitir veri ar estati amente que aquel new o urra antes que el delete.
Se pueden aprove har, tambien, las propiedades transforma ionales del ompilador
para realizar a iones semanti as. Por ejemplo, que el ompilador implante la primera
regla de Holtzmann y despla e la gestion de memoria a la fase de ini ializa ion.
Reportes de Engler et al [7, 8 indi ian que la meta- ompila ion sera una de los prin ipales medios de veri a ion de orre titud en los a~nos venideros. Mediante esta te ni a,
Engler et al [7, 8 ya han en ontrado miles de errores en Linux, OpenBSD y mu hos y
omplejos sistemas omer iales.
Lamentablemente, los programas resultantes de las investiga iones de Engler et at [7, 8
no son de libre a eso.
Exploraci
on del espacio global de estado

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

Captulo 3. Crtica de algoritmos

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 in nito. 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 de nido. 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

predi ados uyos uanti adores onsideran al tiempo [31.


uyas transi iones solo ontienen predi ados logi os rela ionados
on un automata resultante del modelo [5, 22.

3.4. Correctitud de algoritmos

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 i que 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

Captulo 3. Crtica de algoritmos

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.

Si dise~naramos una primitiva para al ular la raz uadrada x de un numero real,


enton es es deseable olo ar omo pre ondi ion el predi ado x 0. La viola ion de esta

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 de nido.
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 on gurable. 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.

3.4. Correctitud de algoritmos

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 de nir 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

de lara ion de variable.

on) : Permite realizar un asigna ion a una variable


 void IS (Asignaci
de larada bajo ID().

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

Captulo 3. Crtica de algoritmos

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 re nadas 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

existen ia y que se de ne as:

for (inicio; condici


on; iteraci
on)
if (pred)
return true;
return false;

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

exa tamente una vez. Se puede interpretar de la siguiente manera:


bool seen = false;
for (inicio; condici
on; iteraci
on)
if (pred)
if (seen)
return false;
else
seen = true;

3.4. Correctitud de algoritmos

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

Una prueba es un experimento onsistente en eje utar el programa para veri ar si


fun iona orre tamente. A grosso modo, existen dos tipos de prueba: (1) de aja negra, en
la ual se estable en entradas y salidas orre tas a veri arse durante el experimento; y
(2) de aja blan a, en la ual se determinan entradas que veri quen que el ujo pase por
todas las partes del programa.
Por tradi ion, las pruebas, en parti ular las aja -negra, las realizan personas distintas
al odi ador llamadas \probadores" (testers).
La dis iplina impartida por Maguire [21 (ver x 3.4.3.1 (pagina 238)) ha e ostumbre
que un programador siempre reali e pruebas aja blan a para todo su odigo.
Existe toda una ien ia para probar programas uyo ambito y extension estan fuera
de al an e en este texto. Ademas de los dos autores anteriores, la mejor referen ia que se
este reda tor ono e para el arte de las pruebas se titula \The Art of Software Testing"
por G. J. Myers [25.
Generaci
on autom
atica de prueba

Engler et al han desarrollado una te ni a, primigeniamente basada en analisis estati o


del odigo fuente, onsistente en generar entradas al programa que eje utan todo los
aminos de eje u ion posibles del programa [44. Aparte de en ontrar las entradas que
ubren todos lo asos posibles, se generan entradas que someten al programa a maximos
esfuerzos segun desempe~no, ondi iones rti as de on urren ia, seguridad, et etera, que
han permitido en ontrar errores en manejadores de sistema de ar hivo en produ ion desde
ha e a~nos.
Manejo de memoria

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

Captulo 3. Crtica de algoritmos

Fugas de memoria (memory leaks)

Una \fuga de memoria" o, en ingles, un \memory leak" es un bloque que se reserva y


que jamas en la vida del programa se libera. Por ejemplo, si tenemos la siguiente ine iente,
inelegante e in orre ta version del swap() entre arreglos:
void array_swap(int a[], int b[], const size_t n)
{
int * tmp = new int [n];
for (int i = 0; i < n; ++i)
tmp[i] = a[i];
for (int i = 0; i < n; ++i)
a[i] = b[i];
for (int i = 0; i < n; ++i)
b[i] = tmp[i];
}

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];

Este odigo on ertitud es ribira un entero de mas en el arreglo tmp. En el mejor de


los asos, o urrira un \segmentation fault"; en el peor, el error pasara temporalmente desaper ibido hasta que tiempo despues (quiza mu ho despues) presente sntomas tambien
errati os en el sentido de que son variables, pudiendo ser, in lusive, de ndole \heisenbergiano" (heisenbug) .
La regla para evitar este error es algo vaga: siempre revise rigurosamente el a eso
mediante un apuntador. Por esa razon, son buenas ostumbres:
24

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.

3.4. Correctitud de algoritmos

255

1. Usar nombres de punteros que indiquen laramente que se trata de un puntero. El


me anismo de nombramiento favorito es su jar el nombre on ptr; de este modo,
una herramienta estilo grep puede en ontrar las o urren ias de uso de un apuntador y permitir al programador, o a una herramienta automatizada, analizar la
orre titud.
2. Aisle el uso de punteros omo arreglos en opera iones generi as, ergo reusables,
que on entren la veri a ion de orre titud en una sola por ion de odigo.
Un ejemplo representativo lo onstituyen las fun iones fill dir to null(),
release segment(), entre otras espe i adas en x 2.1.5.2 (pagina 52).
3. Evitar el uso de mas de un indire ionamiento por puntero. Por ejemplo:
**int_ptr = 5;

no estara permitido. La ex ep ion a esta regla es uando se traten arreglos omo


punteros y tengamos arreglos de arreglos, tales omo lo hi imos, por ejemplos para
el metodo access() de DynArray<T> (x 2.1.5.3 (pagina 61)) o on los arreglos
multi-dimensionales (x 2.2 (pagina 71)).
Direcci
on de devoluci
on inv
alida
Una invo a ion exitosa a malloc() o a new retorna un puntero al bloque reservado. El
me anismo de devolu ion, mediante free() o delete, debe re ibir exa tamente el mismo
valor de puntero que entrego malloc() o delete. Liberar una dire ion de memoria que no

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

Captulo 3. Crtica de algoritmos

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

Al liberar un bloque de memoria se revisa el valor de la reja, si este es diferente al de


ini io, enton es puede ser sntoma de que se ha es rito por fuera de un bloque.
Las veri a iones anteriores, en parti ular, las rejas, no garantizan dete ion para todos
los asos.
El enfoque de valgrind es algo similar a los asos anteriores pero no inter epta la
bibliote a en s. En su lugar, valgrind inter epta, sobre el eje utable, las llamadas rti as
a reserva ion de memoria (malloc(), free(), sbrk(), new, delete, ...) y, en a~nadidura,
inter epta los a esos a memoria y otras opera iones deli adas. Luego eje uta el programa
y, durante la eje u ion va veri ando y reportando los eventuales errores. valgrind ha
disminuido grandiosamente el tiempo de veri a ion en lo que on ierne al manejo de
memoria y otras lases de errores.
Bajo los dos enfoque presentados, el programa se eje uta onsiderablemente mas lento,
lo que en ir unstan ias espe iales puede ha er su uso ompli ado.
Depuradores

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 de ne 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.

3.5. Eficacia y eficiencia

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

Algoritmo 3.1 (Camino m


as corto seg
un la heurstica del vecino 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 >.

1. Sea S =< > la se uen ia nula


2. Sele ione un punto p P y a~nadalo a S
3. P {p}
4. Repita mientras P =
6
(a)
(b)
( )
(d)

Sele ione q P | q es el punto mas er ano a p


Con atene q a S.
P = P {q}
p=q

5. La se uen ia S es la solu ion


Este algoritmo es bastante simple, razon por la ual es muy fa il de omprender y
de realizar. Podemos lamar, tambien, que el algoritmo es muy e iente: una vez que
se on atena un punto a la se uen ia S, el punto nun a mas vuelve a onsiderarse. As
pues, el tiempo de eje u ion es O(n). Lamentablemente, el algoritmo no es orre to, y
para per atarnos de ello, onsideremos el siguiente ontraejemplo, omenzando desde el
punto 0:

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

Captulo 3. Crtica de algoritmos

-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:

3.5. Eficacia y eficiencia

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), (3, 4), (5, 5), (6, 6)}

{(1, 2), (3, 4), (5, 6)}


{(1, 2, 4, 3), (5, 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 =

3. Sea el onjunto de todas las permuta iones de (P)


4.
(a) Sea c la distan ia total obtenida de re orrer los puntos en el orden de la permuta ion
(b) Si c < distan ia =
i. distan ia = c
ii. resultado =
Todas los posibles aminos se onsideran y solo se es oge el de menor oste. El algoritmo
en s es simple, aunque no onsidera omo onstruir el onjunto de permuta iones, lo
ual no es una tarea muy simple. El algoritmo es, pues, orre to. Infelizmente, tal omo
se plantea, es tan ine iente que solo podra eje utarse en omputadores divinos. Para
aprehender esto anali emos la e ien ia.

260

Captulo 3. Crtica de algoritmos

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 on gura 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

La regla del 80-20

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?

3.5. Eficacia y eficiencia

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 a rma 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.

 Uso del programa omo un omponente de otro programa o sistema: tenemos un

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

Maneras de mejorar la eficiencia

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

Captulo 3. Crtica de algoritmos

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
\per lador" (\pro ler").
De manera elemental, un pro ler 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 pro ler ofre e dos gra os fundamentales:
1. El per l 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 pro ler 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 pro ler estadsti o es GNU gprof. Hay varios front-ends
gra os que versatilizan la visualiza ion de los per les arrojados por GNU gprof; siendo el
mas notable de ellos kprof. Valgrind tambien posee un ex elente pro ler estadsti o.

3.5. Eficacia y eficiencia

263

Otro enfoque de per laje, 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 pro ler 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

Captulo 3. Crtica de algoritmos

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 de nitivo 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 Za ropulo 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

1. Demuestre las a rma iones x 3.2, x 3.3 y x 3.4.


2. Demuestre todas las reglas algebrai as enun iadas en x 3.1.6.1.
3. Dise~ne un algoritmo de parti ion triple tal omo el expli ado enx 3.2.2.8.
4. Dise~ne el pro edimiento des rito en hmez la de arreglos 211i de modo tal que el
arreglo se undario solo onsuma la mitad del espa io. Es de ir,
 si el arreglo esta
entre [l .. r], enton es la dimension del arreglo b debe ser rl1
.
2

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

Captulo 3. Crtica de algoritmos

7. Anali e el tiempo de eje u ion del siguiente seudo programa:


void programa(int l, int r)
{
if (l >= r) return;
O(1)
int m = (l + r) / 2;
programa(l, m - 1);
programa(m + 1, r);
}

8. Dado el siguiente programa:


int programa(int l, int r, int A[])
{
if (r < l)
return 0;
for (int i = l; i < r; i++)
x = x + A[i];
int y=0;
y += x + programa(l, r - 1, A);
return y;
}

(a) Anali e el tiempo de eje u ion


(b) Para el arreglo A= [1,2,3,4,5,6. >Que valor devuelve la llamada programa(3,
5, A)?
9. Para ada uno de los siguientes programas, anali e el tiempo de eje u ion:
(a)
void f()
{
int i = 0;
for(int j = i; j > -1; j++);
}

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

Captulo 3. Crtica de algoritmos

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);

Ini ialmente, l1 y l2 estan va as. Al nal de la llamada se retorna el elemento


pivote de la parti ion; la lista list queda va a y l1 ontiene los elementos menores
que el pivote mientras que l2 los mayores.
19. Dise~ne e implante un qui ksort no re ursivo sobre listas dobles de tipo Dnode<T>.
20. Dise~ne e implante un qui ksort no re ursivo sobre listas dobles de tipo Snode<T>.
21. Dise~ne e implante un qui ksort sobre listas dobles on Dnode<T> que sele ione la
mediana entre algunos de los elementos onstantemente asequibles de la lista.
22. Es riba el qui ksort para listas doblemente enlazadas de manera tal que aleatori e
la sele ion del pivote.
23. Dise~ne e implante la siguiente primitiva
int select_pivot(T a[], const int & l, const int & r, const int& n)

La ual sortea al azar n ndi es entre l y r y sele iona el ndi e orrespondiente a


la mediana.
No veri que repiten ia entre los ndi es.
24. Modi que el qui ksort para listas enlazadas presentado en x 3.2.2.6 (pagina 222)
para que reali e una mejor sele ion del pivote.
25. >Estudie el onsumo de espa io en pila que o asiona el mergesort debido a las llamadas re ursivas?
26. Modi que el qui ksort para listas enlazadas presentado en x 3.2.2.6 (pagina 222)
para que minimi e el onsumo de espa io en sus llamadas re ursivas.
27. De los metodos de ordenamiento presentados en este aptulo, > ual sera el mejor
para ordenar listas enlazadas?
28. El mismo ejer i io que el anterior, pero esta vez no puede haber repiten ia entre los
ndi es sorteados.
29. Dise~ne e implante un algoritmo que en uentre el i-esimo menor elemento de una
lista simplemente enlazada desordenada (Snode<T>).
30. Dise~ne e implante un algoritmo que en uentre el i-esimo menor elemento de una
lista doblemente enlazada desordenada (Dnode<T>).

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 veri quen ontenedores
de la bibliote a ALEPH.
42. Para todos los TAD dise~nados hasta el presente, veri que 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 Flaw nder. 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

Captulo 3. Crtica de algoritmos

[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 Cli s, 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.

[29 Y. Perret. Fun tion he k. Available on: http://www710.univ-lyon1.fr/~yperret/fnccheck/profi


[30 P.J.Maker and The Free Software Foundation. The GNU NANA homepage.
[31 A. Pnueli. The temporal logi of programs. In fo s77, pages 46{57, 1977.
[32 David S. Rosenblum. Corre tion to \A pra ti al approa h to programming with
assertions". IEEE Transa tions on Software Engineering, 21(3):265, Mar h 1995.
[33 David S. Rosenblum. A pra ti al approa h to programming with assertions. IEEE
Transa tions on Software Engineering, 21(1):19{31, January 1995.
[34 Theo C. Ruys and Gerard J. Holzmann. Advan ed SPIN tutorial. In Susanne Graf
and Laurent Mounier, editors, Model Che king Software, 11th International SPIN
Workshop, Bar elona, Spain, April 1-3, 2004, Pro eedings, volume 2989 of Le ture Notes in Computer S ien e, pages 304{305. Springer, 2004.
[35 Robert Sedgewi k. Algorithms in C: Parts 1{4: Fundamentals, data stru tures,
sorting, sear hing. Addison-Wesley, Reading, MA, USA, 1998.
[36 Steven S. Skiena. The Algorithm Design Manual. Springer-Verlag, Berlin, Germany / Heidelberg, Germany / London, UK / et ., 1998.
[37 Ri hard Stallman and Roland H. Pes h. Debugging with GDB: the GNU sour elevel debugger. Free Software Foundation, In ., pub-FSF:adr, 4.09 for GDB version
4.9 edition, 1993. Previous edition published under title: The GDB manual. August
1993.
[38 Robert E. Tarjan. Amortized omputational omplexity. Sialgdis, 6:306{318, 1985.
[39 T. van Vle k. Three questions about ea h bug you nd. 1989.
[40 John Viega, J. T. Blo h, Tadayoshi Kohno, and Gary M Graw. ITS4: A stati vulnerability s anner for C and C++ ode. In 16th Annual Computer Se urity Appli ations Conferen e. ACM, De ember 2000.
[41 Gray Watson. Dmallo - debug mallo library. Available on: http://dmalloc.com/.
[42 Niklaus Wirth. Algorithms + Data Stru tures = Programs. Prenti e-Hall Series in
Automati Computation. Prenti e-Hall, Upper Saddle River, NJ 07458, USA, 1976.

272

Captulo 3. Crtica de algoritmos

[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. Za ropulo, 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 on ado, 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

Antes de abordar formalmente el on epto omputa ional de arbol, empapemosnos un


po o mas on la idea abstra ta de arbol en un ontexto mas otidiano.
En el entorno genealogi o de esta epo a, quiza sea el \arbol genealogi o", el ual
traza as enden ia, y uya representa ion general se ilustra en la gura 4.1, un ejemplar
representante ultural de la estru tura arbol y de su similaridad pi tori a y jerarqui a on
su parangon natural.
Bisabuelo

Bisabuela Bisabuelo

Abuelo paterno

Bisabuela Bisabuelo

Abuelo Materno

Bisabuela Bisabuelo

Abuelo materno

Pap
a

Bisabuela

Abuela materna

Mam
a

Hijo

Figura 4.1: Forma general de un arbol genealogi o


A diferen ia de su estereotipo natural, en el arbol genealogi o las semillas (los bisabuelos) se en uentran en las hojas, mientras que el ultimo des endiente en la raz. En
el mismo sentido genealogi o, aunque mas individualista, un patron seminal mas el on
el arbol natural es el \arbol dinasti o", el ual traza las des enden ia y uya estru tura
basi a, pero no general, se ilustra en la gura 4.2.
El arbol genealogi o general de la gura 4.1 es \binario" en el sentido de que ada
miembro tiene exa tamente dos an estros dire tos: el padre y la madre. En ambio, la
numeri idad del arbol dinasti o dependera del ara ter prol o de la familia.
En programa ion se maneja la abstra ion de arbol para representar jerarquas de
omputo, ordenes espe iales de datos o situa iones que ata~nan a los arboles ulturales
e, in lusive, naturales. De he ho, en el aptulo 1 introdujimos el on epto de \heren ia de lases" y su ara ter \genealogi o".
En omputa ion, las apli a iones del arbol son muy diversas y van desde la re upera ion
e iente de laves, hasta la resolu ion analti a de expresiones del al ulo formal. El arbol
fundamenta mu hos esquemas de re upera ion y re onstru ion de informa ion, tanto en
memoria primaria, omo en se undaria. Todo pro eso de tradu ion, por ejemplo, la ompila ion de algun lenguaje de programa ion, utiliza el arbol omo estru tura fundamental.
El al ulo formal automati o; es de ir, el al ulo de expresiones analti as, por ejemplo
lmites, derivadas, integrales, et etera, usa al arbol omo estru tura de representa ion de


Captulo 4. Arboles

276

Individuo

Primog
enito

Nieto 1

Bizn 1

Hijo 2

Nieto 2

Hijo 3

Nieto 3

Bizn 2 Bizn 3 Bizn 4

Bizn 5

Bizn 6

Nieto 4

Bizn 7 Bizn 8

Bizn 9

biz-biz

Figura 4.2: Forma basi a de un arbol dinasti o


las expresiones.
El arbol es una estru tura de dato ompleja y su estudio en profundidad esta fuera del
ambito de este texto. La labor de este aptulo sera, pues, formarnos on los lineamientos
basi os a efe tos de umplir los siguientes objetivos:
1. Cono er los on eptos basi os y homogeneizar terminologas.
2. Comprender los algoritmos fundamentales y preparar al le tor en el desarrollo de
algoritmos mas omplejos.
3. Prepararnos para el estudio de estru turas arbol, espe ializadas, que seran desarrolladas en otros aptulos de este texto.
Presentada la idea intuitiva y genealogi a del arbol, as omo su interes en la omputa ion, estamos prestos para ini iar formalmente nuestro estudio.

4.1

Conceptos b
asicos

Comen emos por una de ni ion matemati a:

Definici
on 4.1 (Arbol)
Un arbol T se de ne por un onjunto N = {n1, n2, . . . , nn} de

uno o mas nodos que satisfa en las siguientes ondi iones:


1. Existe un nodo espe ial designado raiz(T ) N .

2. El onjunto N {raiz(T )} puede parti ionarse en m arboles T 1, T 2, . . . , T m tal que:


{raiz(T )} T 1 T 2 T m = T

T1 T2 Tm =

Bizn 10

4.1. Conceptos b
asicos

277

Cada uno de los onjuntos T 1, T 2, . . . , T m son subarboles de T .


El ontenido de un nodo ni T se representa omo KEY(ni), el ual, a menudo, se le
llama \ lave".
La gura 4.3 ilustra un ejemplo donde la raz es el nodo etiquetado on la letra A y las
ra es de los subarboles de A estan etiquetados on B, C, D y E, respe tivamente. Notemos
que el arbol dibujado en la gura 4.3 esta invertido: la raz esta en la parte superior y las
hojas en la parte inferior.
Evidentemente, existe todo un argot genealogi o para des ribir un arbol. A un nodo
dire tamente one tado a la raz se le llama \hijo". A ex ep ion de la raz, todo nodo
tiene un padre. Dado un nodo ni, llamaremos padre(ni) al padre de ni e hijos(ni)
al onjunto formado por los hijos inmediatos de ni. Del mismo modo, dado un nodo n,
hijo(n, i) denota a la i-esima rama de n. Si hijo(n, i) = , enton es n no tiene una i-esima
rama.
A un nodo que no tenga hijos se le llama hoja, pues alegoriza la hoja de un arbol
en el sentido de que es una termina ion.
Aparte de la raz, al resto de los nodos se les llama descendientes del nodo raiz(T ).
A

Figura 4.3: Un arbol de ejemplo


Un arbol ontiene nodos y ar os. Un nodo aso ia un elemento de algun tipo generi o T .
Un ar o representa una rela ion entre los dos nodos involu rados.
Denominaremos T al onjunto in nito de todos los arboles posibles.
Una arborescencia se de ne omo un onjunto de ero o mas arboles disjuntos.
Un camino < n0, n1, . . . , nn1, nn > es una se uen ia orrespondiente a nodos inter one tados por ar os pertene ientes al arbol tal que hijo(n0) = n1 | hijo(n1) = n2 | |
hijo(nn1) = nn. Un amino se denota por Cns ,nt donde ns es el nodo ini io del amino
y nt es el nodo nal del amino.
La longitud del camino, denotada omo | Cns ,nt |, es la antidad de nodos que
ontiene el amino.


Captulo 4. Arboles

278

Los ancestros de ni, denotado omo an estros(ni), es el onjunto onformado por


los nodos pertene ientes al amino C (T),
(ni ); es de ir, la se uen ia de nodos prede esores desde la raz hasta ni.
Dados dos nodos ni, nj T , enton es, nj es an estro de ni si y solo si, nj
an estros(ni).
Algunos dis ursos utilizan una no ion de edad de nodo, la ual esta dada por su
posi ion dentro del amino de sus an estros. De este modo, dado un amino Cn0 ,nn =<
n0, n1, . . . , nn1, nn >, de imos que ni es mas viejo (o mayor) que ualquier ni+c
Cni+1 ,nn . Del mismo modo, de imos que ni es mas joven (o menor) que ualquier nic
Cn0 ,ni1 .
Dado un nodo ni Cn0 ,nn =< n0, n1, . . . , nn1, nn >, el onjunto hijos(ni) es llamado
la i-esima genera ion.
El grado de ni denotado omo grado(ni), es la antidad de subarboles que ontiene
el nodo ni.
El orden o grado de un arbol se de ne omo el numero maximo de ramas que
puede tener ualquier nodo del arbol.
Si el grado de un nodo es igual al orden del arbol, enton es de imos que el nodo esta
ompleto. Cuando un arbol posee mu hos nodos in ompletos y estos poseen muy po as
ramas, enton es de imos que el arbol es espar ido. Del mismo modo, uando hay mu hos
nodos ompletos de imos que el arbol es denso.
El nivel de un nodo ni, denotado omo nivel(ni), se de ne re ursivamente omo
sigue:

si ni = raiz(T )
0
nivel(ni) =
1 + nivel (padre(ni)) si ni > 0
raiz

padre

La altura de un nodo ni, denotada omo h(ni) se de ne omo el numero de nodos


menos uno que hay desde ni hasta una hoja situada en el mas alto nivel.
Tambien puede de nirse re ursivamente omo sigue:
h(ni) =

0
si ni es una hoja
1 + max(h(hijo1(ni)), h(hijo2(ni)), . . . , h(hijom(ni))) de lo ontrario

La altura de un arbol T se de ne omo la altura de raiz(T ); es de ir, h(raiz(T )).


La cardinalidad de un arbol T , denotada omo |T |, se de ne por el numero total
de nodos del arbol.
Ilustraremos algunos de estos on eptos mediante la gura 4.3. La raz es el nodo
etiquetado on A y esta situada en el nivel 0. El grado de raiz(T ) es 4, que tambien
orresponde al orden del arbol. Los hijos de A son B, C, D y E. padre(B) = padre(C) =
padre(D) = padre(E) = A. La altura de A(h(A)) es 5, que orresponde a la longitud
del amino mas largo desde A. Tal amino esta dado por CA,U = A, C, H, P, U o CA,V =
A, C, H, P, V | |CA,V | = 5.
Ahora onsideremos un nodo parti ular, por ejemplo H. Tenemos:
 grado(H) = 3
 padre(H) = C
 h(H) = 3 = |CH,U| = |CH,V |

Ejer tese on esta terminologa onsiderando ada nodo de la gura 4.3.

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

Figura 4.4: Conjuntos anidados


En esta representa ion la raz es un onjunto de nombre raiz(T ) on subarboles omo
sub onjuntos. Por ejemplo, la gura 4.4 ilustra el equivalente en onjuntos anidados del
arbol de la gura 4.3.
Esta representa ion puede fundamentarse sobre estru turas de datos espe ializadas en
onjuntos.
4.2.2

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 re namiento 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 in ja
Figura 4.5: A

3x4 +3x3 y2 +x2 1


4x2 z

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 pre ja; es de ir,
el nombre de la fun ion u opera ion \pre ja", \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 pre ja. 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 in ja 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 in ja omo:
(3*x^4 + 3*x^3*y^2 + x^2 - 1)/ (4*x^2*z)

La ual es la version resumida de la expresion in ja orrespondiente al arbol de la gura 4.5.


La expresion in ja es una representa ion lineal del arbol y requiere el uso de parentesis
para espe i ar el orden de apari ion de los nodos; exa tamente de la misma manera en
que debe ha erse on la pre eden ia en las expresiones algebrai as.

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, de nido 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)

El valor a~nadido de la nota ion de Deway es que se preserva ntegramente la forma


del arbol sin ne esidad de ajustarse a un orden de se uen ia. El arbol puede restaurarse
independientemente del orden en que se presenten los nodos.
Una representa ion basada en la nota ion de Deway puede ser muy util en algunas
situa iones donde la onstru ion del arbol sea altamente dinami a. Tambien puede ade uarse para la transmision remota de arboles muy grandes que no tengan abida en un
solo mensaje.

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

Cuyos ampos de interes son:


 R SIBLING: apuntador al nodo orrespondiente al hermano dere ho.
 L CHILD: apuntador a su hijo m
as a la izquierda.
/
*

+
*

*
3

^
x

3
4

^
x

^
3

-1

Figura 4.6: Representa ion on listas del arbol de la gura 4.5

4.3. Representaciones de
arboles en memoria

283

Los ampos R SIBLING onforman una lista simplemente enlazada de hermanos. La


abe era de esta lista es el nodo mas a la izquierda. La entrada a esta lista es el ampo
L CHILD del nodo padre.
Los ampos L CHILD onforman una lista simplemente enlazada de genera iones. Dado
un nodo x, L CHILD(x) es su hijo mas a la izquierda; L CHILD(L CHILD(x)) es el nieto
mas a la izquierda de x; L CHILD(L CHILD(L CHILD(x))) es el bisnieto mas a la izquierda
de x y as su esivamente para el resto de las genera iones.
A menudo, al hijo mas a la izquierda de un nodo se le tilda de \primogenito".
La gura 4.6 ilustra la representa ion on listas del arbol mostrado en la gura 4.5.
Si no hay mas hermanos o genera iones, enton es el apuntador orrespondiente se
mar a on el valor espe ial NULL.
Esta representa ion es ompa ta y exible, pues el orden y ardinalidad del arbol
pueden aumentarse o disminuirse dinami amente. El espa io o upado por ada nodo es
onstante e independiente del orden del arbol y del grado del nodo. El desperdi io en
nodos in ompletos (por las ramas no utilizadas) es peque~no: R SIBLING en el nodo mas a
la dere ha de ada lista de hermanos, y L CHILD en el ultimo des endiente de ada lista
de genera iones.
Para a eder a un nodo de la i-esima genera ion es ne esario a eder las i 1 genera iones anteriores. Para a eder al j-esimo hermano, es ne esario a eder los j 1 hermanos
pre edentes. Si el orden del arbol es ono ido, enton es el tiempo de a eso esta a otado
y por lo tanto puede onsiderarse onstante.
La versatilidad de la estru tura puede mejorarse si se utilizan listas doblemente enlazadas. Mu hos algoritmos que requieren regresar a sus an estros pueden bene iarse de
esta extension. Dado un nodo, una lista doblemente enlazada permite re uperar inmediatamente el padre. El enla e doble esta dado por el puntero al hijo mas a la izquierda y
el puntero al padre. Del mismo modo, la lista enlazada puede ser de los hermanos, en
uyo aso el enla e doble esta dado por los punteros al hermano izquierdo y dere ho,
respe tivamente.
4.3.2

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

Figura 4.7: Representa ion on arreglos del arbol de la gura 4.5


Una alternativa para este enfoque es utilizar un arreglo de pares <puntero a rama, ordinal de rama>. Este esquema utiliza un espa io por nodo propor ional al numero de
ramas y puede ganar espa io si el arbol es bastante espar ido. Si el numero de ramas es
sus eptible de ser muy grande, el arreglo puede estar ordenado por ordinal de rama y la
rama puede ser logartmi amente lo alizable mediante la busqueda binaria. Esto penaliza
a la inser ion y la elimina ion, pues hay que abrir y errar bre has en el arreglo.

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

nodos de nido re ursivamente omo sigue:

T=

denota el arbol va o

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

El ontenido de un nodo ni se denota omo KEY(ni).

(4.1)

Denominaremos B al onjunto in nito de todos los arboles binarios posibles.


La de ni ion de arbol binario es diferente de la de ni ion de arbol dada en x 4.1.
La distin ion la realiza la no ion de sentido de la rama. En la o urren ia, los arboles
binarios ilustrados en la gura 4.8 son diferentes bajo la de ni ion 4.2; empero, bajo la
de ni ion 4.1, ellos son iguales. Esta diferen ia de sentido es, pre isamente, la ara tersti a
que nos garantizara una orresponden ia unvo a entre un arbol binario y uno m-rio
ualquiera.


4.4. Arboles
Binarios

285

Figura 4.8: Dos arboles binarios diferentes


4.4.1

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

Figura 4.9: Representa ion en memoria de un arbol binario


Eventualmente, puede onvenir un ter er puntero por nodo orrespondiente al nodo
padre.
4.4.2

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

Figura 4.10: Expresion algebrai a representada on un arbol binario


Existen uatro patrones de se uen ias arquetpi as o \re orridos" para pro esar un
arbol binario:
1. Algoritmo 4.2 (Recorrido prefijo)
(a) Visite la raz.
(b) Visite la rama izquierda en pre jo.
( ) Visite la rama dere ha en pre jo.
2. Algoritmo 4.3 (Recorrido infijo)
(a) Visite la rama izquierda en in jo.
(b) Visite la raz.
( ) Visite la rama dere ha en in jo.
3. Algoritmo 4.4 (Recorrido sufijo)
(a) Visite la rama izquierda en su jo.
(b) Visite la rama dere ha en su jo.
( ) Visite la raz.
4. Algoritmo 4.5 (Recorrido por niveles)
(a) Repita desde i = 0 hasta la h(T ) 1 del arbol
 Imprima todos los nodos del nivel i ordenados de izquierda a dere ha.
Mu hos textos denominan los re orridos pre jo, in jo y su jo omo preorden, enorden
y postorden, respe tivamente; angli ismos de preorder, inorder y postorder.
G
S
B

X
A


Figura 4.11: Arbol
binario para ejempli ar re orridos


4.4. Arboles
Binarios

287

Apli ando las de ni iones sobre el arbol de la gura 4.11, tenemos el siguiente re orrido
pre jo:
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 in jo tenemos:
(4.3)

B S G A X Z

Primero visitamos la rama izquierda en in jo (B S), luego la raz (G) y ulminamos


on el subarbol dere ho (A X Z).
El re orrido su jo es:
B S A Z X G
(4.4)
Primero visitamos la rama izquierda en su jo (B S), luego la rama dere ha (A Z X)
y, por ultimo, la raz (G).
Finalmente, el re orrido por niveles es:
(4.5)

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 su jo es:
x 2 x 1 + + 2 y +

(4.6)

que es la expresion su ja de la expresion algebrai a (vea x 2.5.5).


El re orrido pre jo es:
+ + x2 + x1 2y

(4.7)

el ual puede evaluarse por la dere ha de forma analoga a la evalua ion de una expresion
in ja.
Ahora onsideremos un algoritmo de impresion del arbol in jo.
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 in jo, 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

Figura 4.12: Proye ion in ja de un arbol binario


6. Imprima ")".
Para el arbol de la gura 4.10, el algoritmo arroja la se uen ia:
((((x) (2)) + ((x) + (1))) + ((2) (y))) ;

(4.8)

la ual es una se uen ia in ja, 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 in jo. Asumamos que los nodos
estan su ientemente separados y que se proye ta una luz sobre la raz. El re orrido in jo
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 pre jo 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 in ja 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 pre jo.
La representa ion parentizada de x 4.2.2 es una version extendida del re orrido pre jo.
Del mismo modo, la representa ion onstruida por el algoritmo 4.6 es una version extendida del re orrido in jo. 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-

totipo de la fun ion que implantara el algoritmo es el siguiente:

template <class Node, typename T>


Node * build_tree(T preorder[], int inf_p, int sup_p,
T inorder[], int inf_i, int sup_i)
preorder e inorder son arreglos que ontienen los re orridos pre jo e in jo respe tivamente. El pre jo l denota el ndi e inferior en los arreglos donde omienzan los re orridos
y el pre jo r denota el ndi e superior donde terminan los re orridos.
La salida del algoritmo es el nodo raz del arbol orrespondiente a los re orridos pre jo
e in jo. Esta raz es el valor de retorno de la fun ion.

1. Si l p > r p (los ndi es se ruzan) = el re orrido no ontiene nodos y se retorna


el arbol va o.
2. Sea root = preorder[l p] la raz del arbol resultado.
3. Sea j el ndi e de la lave preorder[l p] dentro del arreglo inorder. Enton es,
sea i = j - l i. Este es el desplazamiento de la raz dentro del arreglo inorder.
4. LLINK(root) = build tree(preorder, l p + 1, l p + i, inorder, l i, l i +
i -1)

5. RLINK(root) = build tree(preorder, l p + i + 1, r p, inorder, l i + i +


1, r i)

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 pre jo es la raz del arbol, la ual bus amos en el re orrido in jo y obtenemos


Captulo 4. Arboles

290

el desplazamiento i. El ndi e i divide el re orrido in jo en dos partes: los elementos a


la izquierda que son l i .. i - 1 y que pertene en a la rama izquierda, mientras que
los elementos a la dere ha son i + 1 .. r i y que pertene en a la rama dere ha. Ambos
tipos de re orrido no avanzan a visitar la rama dere ha hasta tanto no se haya visitado
la totalidad del subarbol izquierdo y la raz. Por tanto, justo antes de omenzar a visitar
la rama dere ha, ambos re orridos han visitado la misma antidad de nodos (la rama
izquierda y la raz). Esto eviden ia que los re orridos pre jo e in jo de la rama dere ha
estan ontenidos en ambos arreglos entre l p + i + 1 y r p.
*
+

+
x

^
1


Figura 4.13: Arbol
de opera iones equivalente al de la gura 4.10
Con el re orrido in jo 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 pre jo o su jo.
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 pre ja o su ja, 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 su ja 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
su jas, 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 de nir
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

Desarrollaremos una solu ion basada en ma ros que se expanden a lases


parametrizadas. Los ma ros se de nen en el ar hivo htpl binNode.H 291ai, uya de ni ion
es omo sigue:
htpl binNode.H 291ai
# ifndef TPL_BINNODE_H
# define TPL_BINNODE_H

# include <ahDefs.H>
# include <ahAssert.H>
namespace Aleph {
hde ni i
on

nodo va o (never de ned)i

hma ros

internos de BinNode<Key> 294bi

hma ros

externos de BinNode<Key> 291bi

h lase BinNode<Key> 294ai


} // end namespace Aleph
# endif

291b

htpl

binNode.H 291ai exporta dos ma ros. El primero se de ne del siguiente modo:

hma ros externos de BinNode<Key> 291bi


# define DECLARE_BINNODE(Name, height, Control_Data)
INIT_CLASS_BINNODE(Name, height, Control_Data)
};

(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

ingles, entinela se es ribe \sentinel"; ( on \s")


Captulo 4. Arboles

292

\
template <typename Key> Name##Vtl<Key> * const Name##Vtl<Key>::NullPtr = NULL
De nes:
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 pre jo 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 de ne
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
De nes:
DECLARE BINNODE SENTINEL, used in hunks 409, 551a, 561, and 591.


Captulo 4. Arboles

294

Uses INIT CLASS BINNODE 294b and SentinelCtor.


294a

El nodo binario mas simple, llamado BinNode<Key>, se de ne en una sola lnea:


h lase BinNode<Key> 294ai
(291a)

DECLARE_BINNODE(BinNode, 1024, Empty_Node);


De nes:
BinNode, used in hunks 389a, 423, 424b, 427, 429{31, 433, 435, 436a, and 612a.
BinNodeVtl, used in hunks 389a and 612a.
Uses DECLARE BINNODE 291b and Empty Node.

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 de nida en la blibliote a.

Figura 4.14: Diagrama UML de la lase BinNode<Key>

294b

Los ma ros generan varios tipos de nodos binarios on mu ho odigo redundante.


Debemos, pues, fa torizar todo el odigo omun a las dos lases dentro de un solo ma ro:
hma ros internos de BinNode<Key> 294bi
(291a)
# define INIT_CLASS_BINNODE(Name, height, Control_Data)
template <typename Key>
class Name : public Control_Data
{
public:
static const size_t MaxHeight = height;
static Name * const NullPtr;

\
\
\
\
\
\
\
\
\


4.4. Arboles
Binarios

typedef Key key_type;

\
\
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);
}
De nes:
INIT CLASS BINNODE, used in hunks 291b and 293.

296

\
\
\
\
\
\
\
\
\
\
\

Para fa ilitar la legibilidad, se exportan los siguientes ma ros:


hma ros externos de BinNode<Key> 291bi+
(291a) 293

# define LLINK(p) ((p)->getL())


# define RLINK(p) ((p)->getR())
# define KEY(p)
((p)->get_key())
De nes:
LLINK, used in hunks 299{308, 310a, 311a, 317a, 338a, 339b, 343a, 364{67, 371a, 383{88, 391, 393,
395, 397{400, 411{21, 427f, 429{32, 448, 546b, 547, 552{54, 563, 565, 566, 570a, 572, 575, 578b, 579,
592 , 593, 595 , 597a, 598, 601, 602a, 604a, 610, 611, 613b, 616b, and 617.
RLINK, used in hunks 299{308, 310a, 311a, 317, 338a, 339b, 343a, 362b, 364{67, 371a, 383{88, 391,
393, 395, 397{400, 411{21, 427f, 429{32, 448, 546b, 547, 552{54, 563, 565, 566, 570a, 572, 575, 578b,
579, 592 , 593, 595 , 597, 598, 601{3, 610, 611, 613b, 616b, and 617.

Dado un nodo p, el a eso a la lave se realiza on KEY(p), el a eso a la rama izquierda


on LLINK(p) y el a eso a la rama dere ha on RLINK(p).
Uno de los atributos de un nodo binario mas importantes lo onforma el tipo de lave,
el ual es generi amente asequible mediante el nombre key type. Una fun ion generi a
puede ono er el tipo de lave de un nodo mediante la instru ion:
typename Node::key_type

La ual re ere 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. De nimos, 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

DECLARE_BINNODE(Nodo, 255, Altura);


SET_BINNODE_NULL_POINTER(NULL, Nodo);

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> de ne 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 de nidos 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 de na 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 de nirse 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

Contenedor de funciones sobre


arboles binarios

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 de ne 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 de nidos 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 (...) { ... }

Es de ir, el tipo parametrizado es un nodo binario generi o Node. El ontrato mnimo


para que la bibliote a opere es que Node exporte las fun iones de BinNode<Key> de nidas
en tpl binNode.H.
Puesto que la polu ion del espa io de nombramiento de los ma ros es mas deli ada
que la de nombres de fun iones y de variables, siempre \inde niremos" los ma ros al nal
de todo fuente; de esta forma evitaremos problemas on nombres de ma ros dupli ados.


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 in jo tendra el siguiente prototipo:
template <class Node> inline
int inOrderRec(Node * root, void (*visitFct)(Node*, int, int))

La fun ion inOrderRec() re orre en forma in ja el arbol binario on raz root y


retorna el numero de nodos visitados. Cada vez que se visita a un nodo, se efe tua una
llamada a la fun ion apuntada por visitFct, la ual debe orresponderse on el siguiente
prototipo:
template <class Node>
void visitFct(Node* node, int level, int position)
node es un puntero al nodo visitado, level es el nivel o la profundidad del nodo respe to
a la raz y position es su posi ion dentro del re orrido.
inOrderRec() es un \wrapper" a una fun ion re ursiva, estati a, en argada de efe tuar

299a

el re orrido, la ual se de ne dire tamente de la de ni ion:


hFun iones de BinNode Utils 299ai

(298) 299b

template <class Node> inline static


void __inorder_rec(Node *node, const int& level, int & position,
void (*visitFct)(Node*, int, int))
{
if (node == Node::NullPtr)
return;
__inorder_rec(static_cast<Node*>(LLINK(node)),
level + 1, position, visitFct);
(*visitFct)(node, level, position);
++position;
__inorder_rec(static_cast<Node*>(RLINK(node)),
level + 1, position, visitFct);

}
De nes:

inorder rec, used in hunk 299b.


Uses LLINK 296 and RLINK 296.

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

__inorder_rec(root, 0, position, visitFct);


return position;
}
De nes:
inOrderRec, never used.
Uses inorder rec 299a.

inOrderRec() retorna el n
umero de nodos que ontiene el arbol.

300

Los re orridos pre jo y su jo se de nen de manera analoga:


hFun iones de BinNode Utils 299ai+
(298) 299b 301
template <class Node> inline static
void __preorder_rec (Node *p, const int & level, int & position,
void (*visitFct)(Node*, int, int))
{
if (p == Node::NullPtr)
return;
(*visitFct)(p, level, position);
++position;
__preorder_rec(static_cast<Node*>(LLINK(p)),
level + 1, position, visitFct);
__preorder_rec(static_cast<Node*>(RLINK(p)),
level + 1, position, visitFct);
}
template <class Node> inline
int preOrderRec(Node * root, void (*visitFct)(Node*, int, int))
{
int position = 0;
__preorder_rec(root, 0, position, visitFct);
return position;
}
template <class Node> inline static
void __postorder_rec(Node *node, const int & level, int & position,
void (*visitFct)(Node*, int, int))
{
if (node == Node::NullPtr)
return;
__postorder_rec(static_cast<Node*>(LLINK(node)),
level + 1, position, visitFct);
__postorder_rec(static_cast<Node*>(RLINK(node)),
level + 1, position, visitFct);
(*visitFct)(node, level, position);
++position;


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;
}
De nes:
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

El estudio de los re orridos es uno de los mejores entrenamientos en algortmi a de arboles,


pues otros algoritmos obede en a patrones de un re orrido o a ombina iones de ellos.
Comen emos por la version mas simple del re orrido pre jo:
hFun iones de BinNode Utils 299ai+
(298) 300 302
template <class Node> inline
size_t simple_preOrderStack(Node * node, void (*visitFct)(Node *, int, int))
{
if (node == Node::NullPtr)
return 0;
ArrayStack<Node *, Node::MaxHeight> stack;
stack.push(node);
Node * p;
size_t count = 0;
while (not stack.is_empty())
{
p = stack.pop();
(*visitFct) (p, stack.size(), count++);
if (RLINK(p) != Node::NullPtr)
stack.push(RLINK(p));


Captulo 4. Arboles

302

if (LLINK(p) != Node::NullPtr)
stack.push(LLINK(p));
}
return count;
}
De nes:
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 de ni ion del re orrido pre jo, 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

return count; // fin


p = stack.pop(); // sacar para ir a rama derecha
}
}
}
De nes:

preOrderStack, never used.


Uses ArrayStack 131a, LLINK 296, and RLINK 296.

303

De una ierta manera, todo re orrido tiene algo de pre jo, pues todo arbol se a ede
a traves de las ra es de sus subarboles. Por tanto, es intuitivamente esperable que el
re orrido in jo tenga una estru tura similar al pre jo, pues la diferen ia esen ial es el
momento en que se visita la raz.
El re orrido pre jo visita la raz antes de ontinuar por la rama izquierda, mientras que
el in jo lo ha e despues de venir desde la rama izquierda. Di ho esto, podemos presentar
la version no re ursiva del re orrido in jo:
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

p = stack.pop(); // sacar para ir a rama derecha


}
}
}
De nes:

inOrderStack, never used.


Uses ArrayStack 131a, LLINK 296, and RLINK 296.

304a

El re orrido su jo es mas omplejo y ostoso porque al desempilar hay que veri ar


si se proviene desde la izquierda o la dere ha. Una manera de distinguir esta lase de
provenien ia onsiste en mar ar el nodo empilado on el sentido del re orrido. De este
modo, podemos de nir la siguiente pila:
hDe lara i
on de pila su ja 304ai
typedef Aleph::pair<Node*, char> Postorder_Pair;
ArrayStack<Postorder_Pair, Node::MaxHeight> stack;
Uses ArrayStack 131a.

El char de la pila guarda los valores siguientes:


 i: Indi a que se empilo antes de des ender por la rama izquierda.
 l: Indi a que se empilo de regreso de la rama izquierda.
 r: Indi a que se empilo de regreso de la rama dere ha. Es en este aso que se debe

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 de nirse re ursivamente omo:
|T | =

304b

si T =
| L(T )| + 1 + | R(T )| si T 6=
0

De esta de ni ion se deriva dire tamente el algoritmo:


hFun iones de BinNode Utils 299ai+
(298) 303 305a
template <class Node> inline
size_t compute_cardinality_rec(Node * node)
{
if (node == Node::NullPtr)
return 0;

return (compute_cardinality_rec(LLINK(node)) + 1 +
compute_cardinality_rec(RLINK(node)));
}
De nes:

compute cardinality rec, never used.


Uses LLINK 296 and RLINK 296.


4.4. Arboles
Binarios

4.4.8

305

C
alculo de la altura

Re ursivamente, la altura de un arbol binario T se de ne omo:


h(T ) =

305a

si T =
1 + max(h(L(T )), h(R(T ))) si T 6=

De este modo, dise~namos un algoritmo ompletamente reminis ente de la de ni ion:


hFun iones de BinNode Utils 299ai+
(298) 304b 305b
template <class Node> inline
size_t computeHeightRec(Node * node)
{
if (node == Node::NullPtr)
return 0;

const size_t left_height = computeHeightRec(LLINK(node));


const size_t right_height = computeHeightRec(RLINK(node));
return 1 + Aleph::max(left_height, right_height);
}
De nes:
computeHeightRec, used in hunks 431b and 570a.
Uses LLINK 296 and RLINK 296.

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 de nirse 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

La opia es un algoritmo fundamental para implantar el onstru tor opia y el operador


de asigna ion en tipos abstra tos que manipulen arboles binarios.
La version de nitiva debe manejar posibles ex ep iones en el manejo de memoria:
hFun iones de BinNode Utils 299ai+
(298) 305a 306
template <class Node> inline
Node * copyRec(Node * src_root) throw(std::exception, std::bad_alloc)
{
if (src_root == Node::NullPtr)
return (Node*) Node::NullPtr;


Captulo 4. Arboles

306

Node * tgt_root = new Node(*src_root);


try
{
LLINK(tgt_root) = copyRec<Node>(static_cast<Node*>(LLINK(src_root)));
RLINK(tgt_root) = copyRec<Node>(static_cast<Node*>(RLINK(src_root)));
}
catch (...)
{
if (LLINK(tgt_root) != Node::NullPtr)
destroyRec(LLINK(tgt_root)); // TODO: diff de Node*&
delete tgt_root;
throw;
}
return tgt_root;
}
De nes:
copyRec, used in hunk 406 .
Uses destroyRec 306, LLINK 296, and RLINK 296.

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;

destroyRec(static_cast <Node*> (LLINK(root)));


destroyRec(static_cast <Node*> (RLINK(root)));


4.4. Arboles
Binarios

307

delete root;
}
De nes:
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 su ja: 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)

Esta de ni ion sugiere el siguiente odigo re ursivo:


hFun iones de BinNode Utils 299ai+

R(T1) k R(T2)

(298) 306 307b

template <class Node> inline


bool areSimilar(Node * t1, Node * t2)
{
if (t1 == Node::NullPtr and t2 == Node::NullPtr)
return true;
if (t1 == Node::NullPtr or t2 == Node::NullPtr)
return false;
return (areSimilar(LLINK(t1), LLINK(t2)) and
areSimilar(RLINK(t1), RLINK(t2)));

}
De nes:

areSimilar, never used.


Uses LLINK 296 and RLINK 296.

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=

KEY(raiz(T1)) = KEY(raiz(T2)) L(T1) L(T2) R(T1) R(T2)

Es de ir, T1 y T2 son similares si y solo si y los ontenidos de ada nodo son exa tamente
los mismos. La de ni 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

bool areEquivalents(Node * t1, Node * t2)


{
if (t1 == Node::NullPtr and t2 == Node::NullPtr)
return true;
if (t1 == Node::NullPtr or t2 == Node::NullPtr)
return false;
if (not Equal () (KEY(t1), KEY(t2)))
return false;
return (areEquivalents<Node, Equal>(LLINK(t1), LLINK(t2)) and
areEquivalents<Node, Equal>(RLINK(t1), RLINK(t2)));
}
De nes:

areEquivalents, never used.


Uses LLINK 296 and RLINK 296.

4.4.12

308

Recorrido por niveles

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;

const size_t two_power =


static_cast<size_t>(std::log((float)queue_size)/std::log(2.0) + 1);
ArrayQueue<Aleph::pair<Node*, bool> > queue(two_power);
queue.put(root);
for (int pos = 0; not queue.is_empty(); pos++)
{
Aleph::pair<Node*, bool> pr = queue.get();
Node *& p = pr.first;
(*visitFct) (p, pos, pr.second);
if (LLINK(p) != Node::NullPtr)
queue.put(Aleph::pair <Node*, bool> (LLINK(p), true));
if (RLINK(p) != Node::NullPtr)
queue.put(Aleph::pair <Node*, bool> (RLINK(p), false));


4.4. Arboles
Binarios

309

}
}
De nes:

levelOrder, never used.


Uses ArrayQueue 157, LLINK 296, and RLINK 296.

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;
}

Node<Key> * root = new Node <Key> (preorder[l_p]); // crear la ra


z
if (r_p == l_p) // recorrido de longitud 1?
return root; // s
==> no hay infijo a buscar
hSea

i posi ion de preorder[l p en inorder[ 310bi

LLINK(root) = build_tree <Node, Key> (preorder, l_p + 1, l_p + i,


inorder, l_i, l_i + (i - 1));
RLINK(root) = build_tree <Node, Key> (preorder, l_p + i + 1, r_p,
inorder, l_i + i + 1, r_i);
return root;
}
De nes:
build tree, never used.
Uses DynArray 45, LLINK 296, and RLINK 296.

build tree() onstruye un arbol binario a partir de los re orridos pre jo e in jo.
preorder es un arreglo que ontiene el re orrido pre jo entre los ndi es l p y r p. Del
mismo modo, inorder es un arreglo que ontiene el re orrido in jo 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
in jo. 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

Conjunto de nodos en un nivel

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

template <class Node> inline static


void __compute_nodes_in_level(Node *
const int &
const int &
DynDlist<Node*> &
{
if (root == Node::NullPtr)
return;

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);
}
De nes:
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 pre jo y los nodos se guardan en la lista en
orden in jo. 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);
}
De nes:
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 a rma ion.
Asumiendo veraz la a rma 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 in jo.
 RLINK(p) apunta al nodo su esor in jo.

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 in jo. Para ello veamos un algoritmo que busque el su esor in jo.
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 in jo de p.

1. Si RLINK(p) es un hilo = retorne RLINK(p).


2. Si RLINK(p) es el valor entinela n de re orrido = retorne NULL.
3. p = RLINK(p)
4. Repita mientras que LLINK(p) no sea un hilo
 p = LLINK(p)


4.4. Arboles
Binarios

313

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

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

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

D
.
..
..
.
..
.
.
....
...
..
....
....
...
...
..
..
...
...
..
.
...
...
...
...
.
...
...
...
.
... ..
... ..
... ....
...

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

E
...
..
..
...
...
.
.
..
..
.
...
....
..
.
...
...
..
...
.....
...
....
..
....
..
..
...
..
.....
...
...
..
..
..
...
...
..
...
..
...
...
...
..
.
..
...
...
.
.
...
..
..
..
..
... ....
... ...
.......

H
..
..
..
...
...
..
.
...
....
.
.
.... ....
.... ...
.. ...
... ..
.. ...
... ..
....

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

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

I
..
..
.
..
...
.
...
..
....
.
...
...
....
...
....
...
...
..
.
...
...
...
..
..
...
...
...
.
... ...
.. ..
.. ...
.....

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

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

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

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

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

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

X
..
.
..
.
..
..
.
..
.
..
.
..
.
.
.
.
.
...
..
.
... .
.
.
.. ..
.
... .
.
.
.. .
... ...
.. ..
.......

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

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

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

Figura 4.16: Ejemplo de arbol hilado


5. Retorne p
La gran ventaja del hilado es la fa ilidad para re orrer el arbol. El re orrido in jo
requiere determinar el primer elemento del re orrido, luego en llamar su esivamente el
algoritmo 4.9 hasta llegar al ultimo elemento.
El hilado requiere distinguir si un apuntador es un hijo o un hilo. Aunque un bit
por puntero es su iente para espe i ar tal distin ion, los sistemas de memoria de los
omputadores modernos trabajan por bytes. Se requiere al menos un byte para utilizar
los dos bits ne esarios. Las maquinas CISC puras no restringen el tama~no de un bloque
de memoria; es posible, pues, apartar el byte extra. En maquinas RISC, los registros y
sus ampos deben estar alineados al tama~no de la palabra de memoria. En onse uen ia,
a~nadir un byte extra puede signi ar un in remento al tama~no del registro mayor a un
byte, pues habra que alinear el byte extra ha ia una dire ion que sea ade uada al tama~no
de la palabra.
Una te ni a para re ono er hijos de hilos onsiste en efe tuar una transforma ion,
inversible, que lleve el valor de un apuntador a un valor omprendido dentro de las zonas
externas al manejador de memoria. Cuando se examina un apuntador, se veri a si este se
en uentra dentro del rango valido; si tal es el aso, enton es se trata de un apuntador; de
lo ontrario, se trata de un hilo y, por supuesto, debe realizarse la transforma ion inversa
antes de referen iar el apuntador. Un obsta ulo a esta te ni a es la portabilidad entre
diferentes plataformas de hardware, sistema operativo, ompilador y lenguaje.
La transforma ion tradi ional onsiste en restarle a una dire ion un valor que asegure
que la resta estara dentro de la zona de datos o odigo o, por desbordamiento, dentro de
la zona de la pila. La resta es posible solo si el tama~no de la zona de memoria es menor o
igual a la suma de las otras zonas. A ve es, esto no es posible.

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


Captulo 4. Arboles

314

Nodo abe era


G
S
B

X
H

Figura 4.17: Representa ion en memoria de un arbol binario hilado

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);
}
De nes:
isThread, never used.

314b

hConvertir un apuntador en un hilo 314bi


template <class Node> inline
Node * makeThread(Node * p)
{
return (Node*) ( ( (long)p) | 1);
}
De nes:
makeThread, never used.

314

hConvertir un hilo en apuntador 314 i


template <class Node> inline
Node * makePointer(Node * p)
{
return (Node*) (((long) p) & -2);


4.4. Arboles
Binarios

315

}
De nes:

makePointer, never used.

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 in jo. El lazo dere ho apuntara a la raz. El hilo izquierdo del primer nodo in jo y
el lazo dere ho del ultimo nodo in jo 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".

Figura 4.18: Un arbol hilado de 80 nodos

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 a rmativa: los punteros nulos pueden
usarse omo hilos temporales.
Dado un arbol binario on raz r, ubiquemos ual es su prede esor in jo. Esto es
equivalente a determinar ual es el ultimo nodo que se visita en in jo 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

El prede esor de la raz de un arbol binario es el nodo mas a la dere ha de su subarbol


izquierdo.
Por simetra, el su esor de la raz de un arbol binario es el nodo mas a la izquierda de
su subarbol dere ho.
Dado un arbol binario de raz r, el re orrido in jo puede dise~narse on los siguientes
lineamientos:
1. Antes de des ender ha ia la rama izquierda, determine el prede esor rp.
2. Haga R(rp) = r; es de ir, oloque el puntero dere ho de rp a apuntar a la raz r.
3. rp es el ultimo nodo en in jo de la rama izquierda de r. Cuando lo visite, re upere r
mediante el valor de R(rp). Antes de visitar r, asegurese de restaurar R(rp) al valor
nulo.

316

Estamos en apa idad de abordar un algoritmo in jo 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

in jo hilado 317ai


4.4. Arboles
Binarios

317

}
De nes:

inOrderThreaded, never used.

p sera el nodo que se visita o un nodo de regreso para ir ha ia la dere ha.


q es la dire ion de un nodo sobre el ual existe un hilo temporal. r es un puntero al
padre de p.
317a

hre orrido in jo hilado 317ai


while (p != Node::NullPtr)
{
q = LLINK(p);

(316)

if (q == Node::NullPtr)
{ // No hay rama izq ==> visitar p
(*visitFct)(p);
r = p;
p = RLINK(p);
continue;
}
hsea

q el prede esor in jo de p 317bi

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
}

Uses LLINK 296 and RLINK 296.

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 in jo 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

Figura 4.19: Una arbores en ia


Este algoritmo produ e un arbol binario equivalente al m-rio dado. Puesto que la raz de un
arbol Tm no tiene hermanos, la raz del arbol binario resultante nun a tiene hijo dere ho.


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

Figura 4.20: Representa ion on listas de la arbores en ia de la gura 4.19


Hay una manera \visual" de interpretar el algoritmo 4.10, la ual es omo sigue:
1. En adene en una lista enlazada los hijos de ada familia.
2. Elimine los lazos verti ales ex epto el lazo que va ha ia el hijo mas a la izquierda.
Notemos que por ada lazo verti al eliminado, existe un lazo horizontal a~nadido.
La gura 4.20 ilustra el arbol binario equivalente a la arbores en ia de la gura 4.19
luego de eje utar la interpreta ion visual del algoritmo 4.10.
El arbol binario equivalente de la gura 4.20 es exa tamente la representa ion mediante listas enlazadas de la arbores en ia esquematizada en la gura 4.19. Dado un nodo
ualquiera en la representa ion on listas enlazadas, el apuntador ha ia la lista de hijos
funge de apuntador al subarbol izquierdo. Analogamente, el apuntador ha ia los hermanos
funge de apuntador ha ia el subarbol dere ho.
Si se siente in redulo, gire la gura 4.20 unos 45 en el sentido horario y estire un
poquitn los lazos. Debe ver un arbol on la forma de la gura 4.21.
Proposici
on 4.1 Sean T1 y T2 dos arboles ualesquiera. Sean Tb(T1) y Tb(T2) los arboles
binarios equivalentes de T1 y T2 onstruidos segun el algoritmo 4.10. Enton es,
T1, T2

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)

Podemos estru turar la demostra ion en tres asos generales:

1. Si |T1| 6= |T2| = |Tb(T1)| 6= |Tb(T2)| = Tb(T1) 6= Tb(T2).


2. Si orden(T1) 6= orden(T2). Supongamos que orden(T1) > orden(T2), enton es el arbol
binario Tb(T1) ontendra al menos una rama totalmente ha ia la dere ha uya longitud es mayor que ualquier rama totalmente orientada ha ia la dere ha de Tb(T2).
El razonamiento es similar si orden(T1) < orden(T2)
3. Si orden(T1) = orden(T2) |T1| 6= |T2|, enton es, para que T1 6= T2 debe al menos
existir nodos de diferen ia n1 T1 y otro n2 T2 en el ual orden(n1) 6= orden(n2).
En este punto, on ertitud, las ramas diferiran en las longitudes de las adenas de
des enden ia dere ha.


Puede demostrarse el equivalente inverso de la proposi ion 4.1; es de ir, la onversion


de un arbol m-rio arroja un uni o arbol binario. Esto ha e que la orresponden ia sea
inye tiva.
Puesto que T y Tb(T ) son equivalentes para todo T , las manipula iones que se ha en
sobre un arbol m-rio pueden perfe tamente realizarse sobre su equivalente binario.
Dada un arbores en ia, existen dos maneras elementales de re orrerlas:
 Recorrido prefijo

1. Visite la raz del primer arbol (el mas a la izquierda).


2. Re orra en pre jo los subarboles del primer arbol.
3. Re orra en pre jo los arboles restantes.


4.4. Arboles
Binarios

321

 Recorrido sufijo

1. Re orra en su jo los subarboles del primer arbol (el mas a la izquierda).


2. Visite la raz del primer arbol.
3. Re orra en su jo los arboles restantes.
Ahora al ulemos el re orrido pre jo de la arbores en ia de la gura 4.19:
A B F L MG C H O P U V Q D I J R E K S T

(4.10)

Este re orrido orresponde exa tamente al re orrido pre jo del arbol binario equivalente de la gura 4.21. Despues de todo, el re orrido pre jo es la manera topologi amente
natural de listar los nodos en un arbol.
El re orrido su jo 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)

el ual no orresponde al re orrido su jo del arbol binario equivalente ( gura 4.21). En su


lugar, la se uen ia (4.11) orresponde al re orrido in jo del arbol binario equivalente de
la gura 4.21. Esta equivalen ia de re orridos (su jo en una arbores en ia in jo en
su equivalente binario) es una ganan ia algortmi a, pues, omo ya se~nalamos, el re orrido
su jo es mas ostoso de implantar que los re orridos pre jo e in jo.
En resumen, dada una arbores en ia y el arbol binario equivalente al ulado segun el
algoritmo 4.10, tenemos las siguientes orresponden ias entre los re orridos:
 El re orrido pre jo de la arbores en ia orresponde al re orrido pre jo del arbol

binario equivalente.

 El re orrido su jo de la arbores en ia orresponde al re orrido in jo del arbol binario

equivalente.

En una arbores en ia, los re orridos in jo y por niveles no pueden de nirse on pre ision. Claramente, hay varias maneras de de nir el re orrido in jo sobre una arbores en ia, pues existen varias maneras de olo ar la raz entre sus subarboles. Del mismo modo,
hay varias formas de de nir 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 in jo en el arbol binario equivalente se orresponde on el re orrido su jo
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 su jo en la

arbores en ia.

 Un hilo izquierdo denota que el nodo es hoja en el arbol m-rio; esto se dedu e

dire tamente de la equivalen ia.


El nodo destino del hilo orresponde on el prede esor su jo en el arbol m-rio. Aqu
tenemos dos asos:
1. Si la hoja es el nodo mas a la izquierda en el arbol m-rio, que puede identi arse
en el equivalente binario porque es un hijo izquierdo, enton es el hilo apunta
un to primogenito en alguna genera ion. Notemos que puede tratarse de un to
dire to o de un to en genera iones anteriores, segun la profundidad de la hoja.
2. En el aso ontrario, si la hoja no es el mas a la izquierda en arbol m-rio,
enton es, el hilo apunta, siempre, a su hermano izquierdo.

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.

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

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

4.5. Un TAD gen


erico para
arboles

323

template <class T>


class Tree_Node
{
hAtributos de Tree Node<T> 323ai
hM
etodos

privados de Tree Node<T> 324ai

public:
hM
etodos
};

publi os de Tree Node<T> 323bi

etodos utilitarios de arboles 331 i


hM
De nes:
Tree Node, used in hunks 324{31 and 722{24.

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

El ual puede a ederse mediante:


hM
etodos publi os de Tree Node<T> 323bi

(322) 323d

T & get_key() { return get_data(); }


T & get_data() { return data; }
typedef T key_type;

323

Nuestra primera de ision de dise~no onsiste en es oger la representa ion en memoria.


Puesto que pretendemos versatilidad y generalidad, transaremos por la representa ion on
listas, pues esta, aunque mas lenta, ofre e mu ho mas dinamismo que su ontraparte on
arreglos. Consiguientemente, requerimos dos enla es dobles:
hAtributos de Tree Node<T> 323ai+
(322) 323a 324
Dlink child;
Dlink sibling;
De nes:
child, used in hunks 323, 324, 328b, 331 , 332b, 335a, 337, 338b, 366, 575b, and 598.
sibling, used in hunks 323, 324, 328, 339a, and 601b.
Uses Dlink 90.

child enlaza a los hijos mas a la izquierda y sibling enlaza a los hermanos. El a eso

323d

esta dado por los siguientes metodos:


hM
etodos publi os de Tree Node<T> 323bi+

Dlink * get_child_list() { return &child; }

Dlink * get_sibling_list() { return &sibling; }


De nes:
get child list, used in hunk 325b.
get sibling list, used in hunk 325b.
Uses child 323 , Dlink 90, and sibling 323 .

(322) 323b 325a


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);
De nes:
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 de nir 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()); }
De nes:
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, de nimos 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;

Flags() : is_root(1), is_leaf(1), is_leftmost(1), is_rightmost(1)


{
/* empty */
}
};
Flags flags;

4.5. Un TAD gen


erico para
arboles

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 de ne 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; }
De nes:
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

Por legibilidad, de nimos los siguientes ma ros:


hMa ros de Tree Node<T> 325bi
#
#
#
#

define
define
define
define

ISROOT(p)
ISLEAF(p)
ISLEFTMOST(p)
ISRIGHTMOST(p)

((p)->is_root())
((p)->is_leaf())
((p)->is_leftmost())
((p)->is_rightmost())

# define SIBLING_LIST(p) ((p)->get_sibling_list())


# define CHILD_LIST(p)
((p)->get_child_list())
# define LCHILD(p)
((p)->get_left_child())
# define RSIBLING(p)
((p)->get_right_sibling())
De nes:
CHILD, never used.
ISLEAF, never used.
ISLEFTMOST, used in hunk 327b.
ISRIGHTMOST, never used.
ISROOT, never used.
LCHILD, never used.
RSIBLING, never used.
SIBLING, never used.
Uses get child list 323d, get left child 326b, get right sibling 326a, get sibling list 323d,
is leaf 325a 431a, is leftmost 325a, is rightmost 325a, and is root 325a.

325

De nidos 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

Observadores de Tree Node<T>

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();
}
De nes:
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();

4.5. Un TAD gen


erico para
arboles

327

}
De nes:

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();

for (int j = 1; c != NULL and j < i; ++j)


c = c->get_right_sibling();
return c;
}
De nes:
get child, never used.
Uses get left child 326b, get right sibling 326a, and Tree Node 322.

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();
}
De nes:

get parent, used in hunk 328b.


Uses is root 325a, ISLEFTMOST 325b, left link 324b, Tree Node 322, and upper link 324b.

Los arboles pueden aso iarse en una arbores en ia


4.5.2

Modificadores de Tree Node<T>

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

La inser ion omo hermano dere ho se espe i a de la siguiente forma:


hM
etodos publi os de Tree Node<T> 323bi+
(322) 327b 328b
void insert_right_sibling(Tree_Node * p)
{
if (p == NULL)
return;
if (not is_root())
p->set_is_root(false);
p->set_is_leftmost(false);
Tree_Node * old_next_node = get_right_sibling();
if (old_next_node != NULL)
p->set_is_rightmost(false);
this->set_is_rightmost(false);
this->sibling.insert(SIBLING_LIST(p));
}
De nes:

insert right sibling, used in hunk 339a.


Uses get right sibling 326a, is root 325a, set is leftmost 325a, set is rightmost 325a,
set is root 325a, sibling 323 , and Tree Node 322.

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

4.5. Un TAD gen


erico para
arboles

329

if (parent != NULL)
parent->insert(p);
else
left_child->append(p);
}
this->set_is_leftmost(false);
this->sibling.append(SIBLING_LIST(p));
}
De nes:

insert left sibling, never used.


Uses child 323 , get left child 326b, get left sibling 326a, get parent 327b, is root 325a,
set is leftmost 325a, set is rightmost 325a, set is root 325a, sibling 323 , and Tree Node 322.

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));
}
}
De nes:

insert leftmost child, used in hunk 338b.


insert rightmost child, used in hunk 724.
Uses is leaf 325a 431a, left link 324b, lower link 324b, set is leaf 325a, set is leftmost 325a,
set is rightmost 325a, set is root 325a, and Tree Node 322.

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));
}
De nes:

insert tree to right, never used.


Uses get right tree 331a, is root 325a, set is leftmost 325a, set is rightmost 325a,
and Tree Node 322.

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. Un TAD gen


erico para
arboles

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();
}
De nes:

get left tree, never used.


get right tree, used in hunks 330 and 332a.
Uses is leftmost 325a, is rightmost 325a, left link 324b, right link 324b, and Tree Node 322.

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();
}
De nes:
get last tree, never used.
Uses is leftmost 325a, left link 324b, and Tree Node 322.

4.5.4

331

Recorridos sobre Tree Node<T>

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 pre jo 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);
}
De nes:

tree preorder traversal, used in hunk 332a.


Uses child 323 , get left child 326b, and get right sibling 326a.

332a

La rutina re orre re ursivamente, en pre jo, 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 pre jo 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);
}
De nes:

forest preorder traversal, never used.


tree preorder traversal, never used.
Uses tree preorder traversal 331 , get right tree 331a, and is root 325a.

332b

El re orrido su jo es muy similar y omprensible luego de estudiado el re orrido pre jo.


Su espe i a ion se presenta a ontinua ion:
hM
etodos utilitarios de arboles 331 i+
(322) 332a 333
template <class Node> static inline
void __tree_postorder_traversal(Node * node, const int & level,
const int & child_index,
void (*visitFct)(Node *, int, int))
{
Node * child = node->get_left_child();

4.5. Un TAD gen


erico para
arboles

333

for (int i = 0; child not_eq NULL; i++, child = child->get_right_sibling())


__tree_postorder_traversal(child, level + 1, i, visitFct);
(*visitFct)(node, level, child_index);
}
template <class Node> inline
void tree_postorder_traversal(Node * root,
void (*visitFct)(Node *, int, int))
{
__tree_postorder_traversal(root, 0, 0, visitFct);
}
template <class Node> inline
void forest_postorder_traversal(Node * root,
void (*visitFct)(Node *, int, int))
{
if (not root->is_leftmost())
throw std::domain_error("root is not the leftmost node of forest");
if (not root->is_root())
throw std::domain_error("root is not root");
for (/* nada */; root not_eq NULL; root = root->get_right_sibling())
__tree_postorder_traversal(root, 0, 0, visitFct);
}
De nes:

tree postorder traversal, never used.


forest postorder traversal, never used.
tree postorder traversal, never used.
Uses child 323 , get left child 326b, get right sibling 326a, is leftmost 325a, and is root 325a.

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
}
}
De nes:

destroy forest, never used.


destroy tree, never used.
Uses get left sibling 326a, get right child 326b, get right sibling 326a, is leftmost 325a,
and is root 325a.

4.5.6

334

Altura de un Tree Node<T>

Otra rutina util en mu hos ontextos la onstituye el al ulo de la altura de un


Tree Node<T>:
hM
etodos utilitarios de arboles 331 i+
(322) 333 335a
template <class Node>
size_t compute_height(Node * root)
{
size_t max_h = 0;
size_t temp_h;

for (Node * aux = root->get_left_child(); aux != NULL;


aux = aux->get_right_sibling())
if ((temp_h = compute_height(aux)) > max_h)
max_h = temp_h;
return max_h + 1;
}

4.5. Un TAD gen


erico para
arboles

335

Uses get left child 326b and get right sibling 326a.

4.5.7

335a

B
usqueda por n
umero de Deway

Planteemosnos la busqueda de un nodo en un arbol dado su numero de Deway (x 4.2.4).


En ese sentido, de nimos un amino de Deway omo una se uen ia de enteros guardada
en un arreglo de tipo int path[] y delimitada on un valor negativo que indi a el n de
la se uen ia. La primitiva fundamental de busqueda de Deway es la siguiente:
hM
etodos utilitarios de arboles 331 i+
(322) 334 335b
template <class Node> static inline
Node * __deway_search(Node *
node,
int
path [],
const int &
idx,
const size_t & size)
{
if (node == NULL)
return NULL;

if (idx > size)


throw std::out_of_range("index out of maximum range");
if (path[idx] < 0) // verifique si se ha alcanzado el nodo
return node;
// avance hasta el pr
oximo hijo path[0]
Node * child = node->get_left_child();
for (int i = 0; i < path[idx] and child != NULL; ++i)
child = child->get_right_sibling();
return __deway_search(child, path, idx + 1, size); // siga en pr
oximo nivel
}
De nes:
deway search, used in hunk 335b.
Uses child 323 , get left child 326b, and get right sibling 326a.

335b

La rutina retorna NULL si el nodo on el numero de Deway espe i ado en path[] no se


en uentra en el arbol; la dire ion de tal nodo, en aso ontrario.
La primitiva anterior es privada porque una version mas ompleta, que in luya
busqueda en una arbores en ia, puede implantarse tal omo sigue:
hM
etodos utilitarios de arboles 331 i+
(322) 335a 336a
template <class Node> inline
Node * deway_search(Node * root, int path [], const size_t & size)
{
for (int i = 0; root != NULL; i++, root = root->get_right_sibling())
if (path[0] == i)
return __deway_search(root, path, 1, size);

return NULL;
}
De nes:
deway search, never used.


Captulo 4. Arboles

336

Uses deway search 335a and get right sibling 326a.

4.5.8

B
usqueda y determinaci
on de n
umero de Deway

En mu hos ontextos , un problema importante onsiste en bus ar un nodo en el arbol y


enton es determinar su numero de Deway. Para ello, planteamos un re orrido pre jo del
arbol on un arreglo que ontenga el numero de Deway a tual del nodo visitado segun el
siguiente prototipo:
hM
etodos utilitarios de arboles 331 i+
(322) 335b 336b
8

336a

template <class Node, class Equal> inline static


Node * __search_deway(Node *
const typename Node::key_type &
const size_t &
int
const size_t &
size_t &
Uses search deway 337.

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.

4.5. Un TAD gen


erico para
arboles

337

}
De nes:

search deway, never used.


Uses search deway 337 and get right sibling 326a.

337

Nos resta por de nir la rutina que re orrera en pre jo 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;
}
De nes:

search deway, used in hunk 336.


Uses child 323 , get left child 326b, and get right sibling 326a.


Captulo 4. Arboles

338

4.5.9

Correspondencia entre Tree Node<T> y


arboles binarios

En esta sub-se ion implantaremos la orresponden ia entre arboles m-rios y binarios


expli ada mediante el algoritmo 4.10 presentado en x 4.4.17 .
El primer algoritmo que desarrollaremos sera el de la onversion de un arbol m-rio,
implantado mediante del TAD Tree Node<T>, ha ia un arbol binario implantado a traves
del TAD BinNode<Key>. Cuando de imos \implantado a traves del TAD ...", nos
referimos a que el arbol m-rio y binario se fundamentan en las lases Tree Node<T> y
BinNode<Key>, respe tivamente, pero no necesariamente son exactamente de estas
dos clases, sino que, pudieran ser, por ejemplo, lases derivadas.
La onversion de un arbol m-rio ha ia uno binario se expresa dire tamente segun las
pautas del algoritmo 4.10; es de ir, en el arbol binario, toda rama izquierda orresponde
a un hijo mas a la izquierda del arbol m-rio; mientras que toda rama dere ha orresponde
al siguiente hermano. Segun estos lineamientos, la implanta ion resultante es omo sigue:
hM
etodos utilitarios de arboles 331 i+
(322) 337 338b
9

338a

template <class TNode, class BNode>


BNode * forest_to_bin(TNode * root)
{
if (root == NULL)
return BNode::NullPtr;

BNode * result = new BNode (root->get_key());


LLINK(result) = forest_to_bin<TNode, BNode>(root->get_left_child());
RLINK(result) = forest_to_bin<TNode, BNode>(root->get_right_sibling());
return result;
}
De nes:
forest to bin, never used.
Uses get left child 326b, get right sibling 326a, LLINK 296, and RLINK 296.

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.

4.5. Un TAD gen


erico para
arboles

339

void insert_child(BNode * lnode, TNode * tree_node)


{
if (lnode == BNode::NullPtr)
return;
TNode * child = new TNode(KEY(lnode));
tree_node->insert_leftmost_child(child);
}
De nes:
insert child, used in hunk 339b.
Uses child 323 and insert leftmost child 329.

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);
}
De nes:
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);
}
De nes:
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

El parametro broot es la raz de un arbol binario fundamentado en el


TAD BinNode<Key>. El parametro troot es la raz de un arbol m-rio, fundamentado
en el TAD Tree Node<T>, equivalente a broot.
Finalmente, la interfaz publi a y de nitiva que rea la raz ini ial y dispara la onstru ion re ursiva, se de ne de la siguiente forma:
hM
etodos utilitarios de arboles 331 i+
(322) 339b
template <class TNode, class BNode> inline
TNode * bin_to_forest(BNode * broot)
{
if (broot == BNode::NullPtr)
return NULL;
TNode * troot = new TNode (KEY(broot));
bin_to_tree(broot, troot);
return troot;
}
De nes:
bin to forest, never used.
Uses bin to tree 339b.

4.6

Algunos conceptos matem


aticos de los
arboles

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)

4.6. Algunos conceptos matem


aticos de los
arboles

341

Demostraci
on
 h(T ) log m (n(m 1) + 1)

La altura de un arbol es mnima uando el numero de nodos ompletos es maximo.


Para omprender esto, onsideremos onstruir progresivamente un arbol m-rio evitando en lo posible aumentar su altura. A partir del arbol va o, solo podemos olo ar
el nodo raz. Luego, tenemos m espa ios disponibles en el nivel 1. Posteriormente,
ada uno de los m nodos del nivel 1 tambien tiene m eldas disponibles que estaran
en el nivel 2; es de ir m m eldas que pueden olo arse en el nivel 2. Continuando
esta lnea de razonamiento, es fa il observar que ada nivel i puede ontener a lo
sumo mi nodos. Por tanto, el numero de nodos de un arbol onstruido de esta forma
puede expresarse omo:
h(T)1

X
i=0

mi = m0 + m1 + m2 + + mh(T)1

(4.13)

Multipli ando (4.13) por m tenemos:


h(T)1

mn

X
i=0

= m1 + m2 + + mh(T)1 + mh(T)

(4.14)

Restando (4.14) menos (4.13), tenemos:


m n n mh(T) 1 =

n(m 1) + 1 mh(T) =

log m (n(m 1) + 1) h(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

Longitud del camino interno/externo

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
de nen omo los grado(ni) m subarboles faltantes.

En otras palabras, un nodo externo es todo puntero nulo. Conse uentemente, un


nodo interno es todo nodo pertene iente al arbol.

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

nodos externos es:

(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 de ne 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 de ne omo:
X
X

IPL(T ) =
nivel(ni) =
(4.16)
i | nivel (i)|
ni T
ni nodo interno

4.6. Algunos conceptos matem


aticos de los
arboles

343

Analogamente, la longitud del amino externo, denotada omo EPL(T ), se de ne omo:


EPL(T ) =

nivel(nx)

(4.17)

nx T
nx nodo externo

Para el arbol de la gura 4.23, la longitud del amino interno es


IPL(T ) = 0 + 1 + 1 + 2 + 2 + 2 + 2 + 3 + 3 + 3 = 19
y la longitud del amino externo es
EPL(T ) = 3 + 3 + 3 + 3 + 3 + 4 + 4 + 4 + 4 + 4 + 4 = 39

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;

return level + __internal_path_length(LLINK(p), level + 1) +


__internal_path_length(RLINK(p), level + 1);
}
De nes:

internal path length rec, never used.


Uses internal path length 343b, LLINK 296, and RLINK 296.

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);
}
De nes:
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

Para (4.18), es evidente que |T | es la suma de las ardinalidades de sus


subarboles mas su raz.
En uanto a (4.19), notemos que el nodo raz de T no tiene in iden ia en la longitud
del amino, pues su nivel es 0. Cuando al ulamos las longitudes de los aminos de los
subarboles de T , asumimos que los nodos estan a un nivel inferior; es de ir, ada nodo es
sumado en un nivel menos. Por ejemplo, la raz de T i, que en T esta en el nivel 1 se pondera
on 0 en lugar de 1; lo mismo su ede on el resto de los nodos. As pues, para obtener el
valor de IPL(T ), debemos sumar 1 a ada nodo onsiderado; es de ir, la antidad de nodos
|T | 1, pues la raz no uenta.
Finalmente, para (4.20), el razonamiento es exa tamente el mismo que para (4.19),
solo que el numero de nodos externos es, segun (4.15) (proposi ion 4.3), (m 1)|T | + 1
Demostraci
on

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

ierta para el aso base.

on es ierta para todo k y


 |Tk+1| = k + 1 En este aso, asumimos que la proposi i
pro edemos a veri ar si es ierta para k + 1.
Comenzamos por plantear la resta de la e ua ion (4.20) menos (4.19) en fun ion de
los resultados de la proposi ion 4.4 (e ua iones (4.20) y (4.19)):
EPL(Tk+1) IPL(Tk+1) =
=

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

Ahora apli amos la hipotesis indu tiva sobre el termino

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)

4.6. Algunos conceptos matem


aticos de los
arboles

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:

EPL(Tk+1) IPL(Tk+1) = (m 2)(IPL(Tk+1) k) + 2mk + m 2k


= (m 2) IPL(Tk+1) mk + 2k + 2mk + m 2k =
EPL(Tk+1) = (m 1) IPL(Tk+1) + m(k + 1)
(4.24)
que es la misma expresion de la proposi ion evaluada para k + 1 nodos

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)

Para demostrar IPL(T ) n(n+1)


, planteamos la longitud del amino del
2
arbol de mayor altura posible. Tal arbol es aquel uyos nodos, on ex ep ion de la raz y
la hoja, solo tienen una rama. La longitud del amino de este arbol esta dada por:
Demostraci
on

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 )

log m (n(m 1)) mlogm (n(m1)+1) + mn


m1

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

(n es la antidad de nodos en el ultimo nivel) (4.27)

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)

La solu ion nal de (4.29) se delega omo ejer i io.


Otra forma de resolver (4.27) y (4.29), es planteando una sumatoria distinta que involu re logaritmos:
IPL(T ) =

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 pre eren 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 veri que 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 de ni 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", re ere 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:

2. Orden: ni T = KEY(hijo1(ni)) < KEY(ni) KEY(hijo2(ni)) < KEY(ni)


KEY (hijom(ni)) < KEY (ni)
Segun el grado del arbol, un heap puede ser binario, trinario o de ualquier otro
orden. Habida uenta de la equivalen ia entre los arboles binarios y las arbores en ias
(ver x 4.4.17 (pagina 318)), en este texto solo onsideraremos los heaps binarios. Por tanto,
si se trata de un arbol binario, enton es la propiedad de orden puede expresarse omo:
ni T = KEY (L(n1)) < KEY (ni) KEY (R(n1)) < KEY (ni).
2
14

38

68

31

104
134

149

98
99

69

56
188

243

48
166

112

145

87
185

217

80
200

228

207

Figura 4.25: Un heap binario


La bondad de un heap se resume en dos ara tersti as:
1. El menor elemento de un onjunto de laves se en uentra, dire tamente, por onse uen ia de la propiedad de forma, en la raz del arbol; di ho de otro modo, la
onsulta del menor elemento es O(1).
2. Cualquier modi a ion del heap, entendamos una inser ion o elimina ion, uesta
O(lg (n)). Intuitivamente, esto es una onse uen ia de la propiedad de forma, la ual
garantiza que la altura este a otada a O(lg(n)).
La estru tura de datos preferida para implantar un heap es un arreglo que alma ene
el re orrido por niveles del arbol ompleto. Por ejemplo, el heap de la gura 4.25 puede
representarse on la siguiente se uen ia:
2 14 38 68 31 69 145 104 98 56 48 87 80 228 156 134 149 99 188 243 166 112 185 217 200 207
348

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;

re ere a la administra ion de memoria en un pro eso.

156

4.7. Heaps

349a

349

La raz, o sea, el menor elemento del onjunto, se en uentra en:


hRaz del heap 349ai
array[1];

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

static size_t u_index(const size_t & i)


{
return i >> 1; // divide i entre 2
}
De nes:
u index, never used.

2. Hijo izquierdo: segun (4.32):


hRutinas heap 349 i+
349d

(349f) 349 350

static size_t l_index(const size_t & i)


{
return i << 1; // multiplica i por 2
}
De nes:
l index, never used.

3. Hijo derecho: tambien segun (4.32):


hmiembros privados de ArrayHeap<Key> 348i+
349e

(349f) 349b 354

static size_t r_index(const size_t & i)


{
return (i << 1) + 1; // multiplica i por 2 y suma 1
}
De nes:
r index, never used.

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

privados de ArrayHeap<Key> 348i

public:
hmiembros p
ubli os
};
De nes:
ArrayHeap, never used.

de ArrayHeap<Key> 354bi

Esta lase parametrizada implanta un heap binario de elementos de tipo generi o T,


mediante un arreglo estati o y on riterio de ompara ion estable ido en la lase Compare.
4.7.1

Inserci
on en un heap

Consideremos un heap binario de tama~no n y la inser ion de un nuevo elemento x. Hay


un solo lugar, dentro del arbol, en el ual puede a~nadirse un nuevo elemento sin alterar la
propiedad de forma; tal lugar se pi toriza en la siguiente gura:

350

Garantizada la propiedad de forma, resta por veri ar on rmar 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
}
}
De nes:

sift up, never used.

La entrada de sift up() es un arreglo, ontentivo de un heap binario entre l y r 1,


on una eventual viola ion de la propiedad de orden en array[n]; la salida es un heap
binario, en forma y orden, entre l y r, on la eventual viola ion orregida. El pro eso se
ejempli a en la gura 4.26. Basi amente, sift up() se veri a si existe una viola ion
de orden entre ptr[i] y ptr[p]. Si tal es el aso, enton es inter ambia sus elementos y
as iende a pro esar el nivel inferior; si no, enton es, todo el arbol es un heap. El pro eso
ontinua hasta que el padre sea menor o se al an e la raz.

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

Figura 4.26: Opera ion sift up() sobre el valor de lave 38


Debemos prestar espe ial uidado al he ho de que intervalo del heap es [1 . . . n] y no
[0 . . . n 1], omo tradi ionalmente se manejan los arreglos en lenguaje C y C++. Conse uentemente, la dire ion ptr debe estar desplazada en una unidad.
El oste de sift up() se expresa por el siguiente lema:
Lema 4.1 Sea T un heap binario de n elementos on una viola ion eventual de orden
en array[n]. Enton es, la dura ion de sift up(array, 1, n) es O(lg(n)).

El tiempo de eje u ion de sift up() estara dominado por la antidad


de inter ambios que o asione el elemento violatorio. En este sentido, lo peor que puede
o urrir es que el elemento violatorio devenga la raz de T . Por tanto, la antidad maxima
de inter ambios es propor ional a la altura del arbol. Puesto que T es ompleto, y segun
se desprende de la proposi ion 4.2, su altura esta a otada por O(lg(n)) 
Demostraci
on

351

Mediante sift up(), es simplsimo a~nadir un nuevo elemento key al heap:


hA~
nadir elemento al heap 351i
(355 )

array[++num_items] = key; // colocar nuevo elemento


sift_up <T, Compare> (array, 1, num_items); // restaurar propiedad de orden

lo ual, por el lema 4.1, es O(lg(n)).


4.7.2

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

hRutinas heap 349 i+


(349f) 350 353b
template <typename T, class Compare> inline
void sift_down(T * ptr, const size_t & l, const size_t & r)
{
size_t i = l, c;
while (true)
{
c = l_index(i); //
ndice del hijo izquierdo (c = i/2)
if (c > r) // hay hijo izquierdo?

4.7. Heaps

353

return; // no ==> termine


if (c + 1 <= r) // hay hijo derecho?
if (Compare () (ptr[c + 1], ptr[c])) // s
==> escoja el menor
c++;
if (Compare () (ptr[i], ptr[c])) // Satisface propiedad de orden?
return; // s
==> termine
Aleph::swap(ptr[c], ptr[i]);
i = c;
}
}
De nes:

sift down, never used.

La rutina toma un heap entre [l + 1 . . . r] y restaura la propiedad de orden. Despues de


efe tuada la llamada, la se uen ia deviene un heap ompleto entre [l . . . r].
La dura ion de sift down() se revela en el siguiente lema:
Lema 4.2 Sea T un heap binario de n elementos on una viola ion eventual de orden en
array[1]. Enton es, la dura ion de sift down(array, 1, n) es O(lg(n)).

El tiempo de eje u ion de sift down() esta dominado por la antidad


de inter ambios que o asione el elemento violatorio. En este sentido, lo peor que puede
o urrir es que el elemento violatorio al an e el maximo nivel de T ; o sea, su altura. Por
tanto, la antidad maxima de inter ambios es propor ional a la altura del arbol. Como T
es ompleto y, segun la proposi ion 4.2, su altura esta a otada por O(lg(n)) 
Demostraci
on

353a

Con lo anterior en mente, podemos plantear la elimina ion del siguiente modo:
hEliminar raz en heap 353ai
(355 )

array[1] = array[num_items--]; // sobreescribir la ra


z con
ultimo elemento
sift_down <T, Compare> (array, 1, num_items); // Restaurar propiedad de orden

353b

la ual, segun el lema 4.2, es O(lg(n)).


La segunda manera de eliminar, mas general, es planteandonos la supresion de un elemento ualquiera. Para ello, simplemente apli amos el mismo prin ipio del primer tipo de
elimina ion: sobrees ribir el elemento que deseamos eliminar on el ultimo de la se uen ia.
La diferen ia on el primer metodo es que aqu pueden presentarse viola iones del orden
por el padre o por los hijos.
Llamaremos a la rutina general de supresion sift down up(), pues ella se remite solo
a esas a iones:
hRutinas heap 349 i+
(349f) 352 357
template <typename T, class Compare> inline
void sift_down_up(T * ptr,
const size_t & l, const size_t & i, const size_t & r)
{
sift_down <T, Compare> (ptr, i, r);
sift_up <T, Compare> (ptr, 1, i);
}


Captulo 4. Arboles

354

354a

La rutina re ibe un array[] ontentivo de un elemento posiblemente violador de la


propiedad de orden en la posi ion i. Primero veri a si hay viola ion entre sus hijos;
luego entre su padre.
Con la rutina anterior, podemos plantear la elimina ion del i-esimo elemento del heap:
hEliminar elemento i 354ai
array[i] = array[num_items--]; // sobreescribir i con
ultimo elemento
sift_down_up <T, Compare> (array, 1, i, n);

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

E ien ia peor aso


O(lg(n))
O(1)
O(lg(n))

Las olas de prioridad se modelizan dire tamente mediante el TAD ArrayHeap<Key>,


ya introdu ido, y el TAD BinHeap<Key>, que sera tratado en x 4.7.6. Hay dos maneras
de onstruir un ArrayHeap<Key>. La primera es on apartado automati o del arreglo
interno; la segunda, on un arreglo provisto expl itamente por el usuario:
hmiembros p
ubli os de ArrayHeap<Key> 354bi
(349f) 354d
ArrayHeap(const size_t & d = 1024)
: array(NULL), dim(d), num_items(0), array_allocated(false)
{
array = new T [d + 1];
array_allocated = true;
}

ArrayHeap(T * ptr, const size_t & d)


: array(ptr), dim(d), num_items(0), array_allocated(false)
{
// empty
}

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 de ne as:
hmiembros privados de ArrayHeap<Key> 348i+
(349f) 349e
mutable bool array_allocated;

354d

on esta bandera el destru tor sabe si se debe o no liberar la memoria:


hmiembros p
ubli os de ArrayHeap<Key> 354bi+
(349f) 354b 355a
virtual ~ArrayHeap()
{
if (array_allocated and array != NULL)
delete [] array;
}

4.7. Heaps

355a

355

La onsulta del menor es la opera ion mas simple:


hmiembros p
ubli os de ArrayHeap<Key> 354bi+

(349f) 354d 355


T & top() throw(std::exception, std::underflow_error)
{
hVeri ar heap va o 355bi
return array[1];
}

355b

355

hVeri ar heap va o 355bi


if (num_items == 0)
throw std::underflow_error("Heap is empty");

(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~

elemento al heap 351i

return array[num_items];
}
T getMin() throw(std::exception, std::underflow_error)
{
hVeri ar heap va o 355bi
T ret_val = array[1];
hEliminar

raz en heap 353ai

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

const size_t i = &data - array;


sift_down_up <T, Compare> (array, 1, i, num_items);
}

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

Consideremos ordenar un arreglo array[] de n elementos. Un metodo de ordenamiento,


estable , on rendimiento O(n lg(n)) para el peor aso, ual es mejor que el peor aso
del qui ksort, puede expli arse, on lo entendido hasta el presente, en dos fases:
11

1. Guarde en una ola de prioridad todos los elementos del arreglo.


2. Extraiga iterativamente de la ola y guarde el elemento extrado en la posi ion de
itera ion i. Puesto que la ola siempre retorna el menor elemento, al nal de esta
itera ion el arreglo estara ordenado.
356a

Este prin ipio puede realizarse del siguiente modo:


hHeapsort on ola de prioridad 356ai
ArrayHeap<T, n> heap;

for (int i = 0; i < n; ++i) // inserta elementos de array en heap


heap.insert(array[i]);
for (int i = 0; i < n; ++i) // extrae ordenadamente del heap y guarda en array
array[i] = heap.getMin();

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

template <class T, class Compare>


struct Inversed_Compare
{
bool operator () (const T & op1, const T & op2) const
{
return Compare () (op2, op1);
}
};

11 Revsese,

si es ne esario, en x 3.2.1.3 (pagina 212) el on epto de estabilidad de un metodo de ordenamiento.


12 Esta lase envoltoria est
a de nida en el ar hivo ahFunction.H.

4.7. Heaps

357

De nes:

Inversed Compare, never used.

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 de nitiva del mayor elemento
es n, planteamos la segunda fase del siguiente modo:
swap(ptr[1], ptr[i])

Heap entre 1 e i

Orden de nitivo
i

357b

357

la ual se implanta as:


hOrdenar a partir del heap 357bi

(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);
}

Grosso modo, se inter ambia array[1] on array[i], lo que ja el orden de nitivo en el


intervalo [i . . . n]. Este inter ambio es equivalente a que se hubiese eliminado del heap el
menor elemento; es de ir, el de la raz. As pues, sift down(array, 1, i - 1) restaura
el heap entre [1 . . . i 1].
Con lo anterior, podemos niquitar el metodo de ordenamiento:
hRutinas heap 349 i+
(349f) 353b 358
template <typename T, class Compare>
void heapsort(T * array, const size_t & n)
{
--array; //desplazar una posici
on hacia atr
as ==> array[1] == primer elemento


Captulo 4. Arboles

358

hConvertir

arreglo en un heap on sift up() 357ai

hOrdenar a partir
}
De nes:
heapsort, never used.

del heap 357bi

Existe una manera alterna y tan e az al bloque hConvertir arreglo en un heap


357ai, que logra, m
as e ientemente, en O(n), \heapi zar" el arreglo. Para
aproximarnos, onsideremos la primera pasada del heapsort de la siguiente forma:

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

O(lg (r) lg (l)) =

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

lo que nos arroja la siguiente nueva version mejorada del heapsort:


hRutinas heap 349 i+
(349f) 357

template <typename T, class Compare>


void faster_heapsort(T * array, const size_t & n)
{
--array; // desplazar una posici
on hacia atr
as ==> array[1] == primer elemento
hConvertir

hOrdenar
}
De nes:

arreglo en un heap on sift down() mejorado 358bi

a partir del heap 357bi

faster heapsort, never used.

4.7. Heaps

4.7.5

359

Aplicaciones de los heaps

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 de nida en el ar hivo:
htpl binHeap.H 360i
on de nodo de BinHeap<Key> 363i
hDe ni i
hDe ni i
on

de ma ros de BinHeap<Key> 364ai

4.7. Heaps

361

template <template <class> class NodeType,


typename Key,
class Compare = Aleph::less<Key> >
class GenBinHeap
{
public:
typedef NodeType<Key> Node;
private:
hAtributos

de BinHeap<Key> 362ai

hMiembros

privados de BinHeap<Key> 364 i

public:
hMiembros
};

publi os de BinHeap<Key> 362bi

template <class Key, typename Compare = Aleph::less<Key> >


class BinHeap
: public GenBinHeap<BinHeapNode, Key, Compare>
{
public:
typedef BinHeapNode<Key> Node;
};
template <class Key, typename Compare = Aleph::less<Key> >
class BinHeapVtl
: public GenBinHeap<BinHeapNodeVtl, Key, Compare>
{
public:
typedef BinHeapNodeVtl<Key> Node;
};
hInde ni i
on de ma ros
De nes:
BinHeap, never used.
BinHeapVtl, never used.
GenBinHeap, never used.

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-

pe ializa iones para manejar o no destru tores virtuales en los nodos.


Puesto que un heap binario es un arbol binario, se pensara que el tipo BinNode<Key>
basta para implementar un heap altamente dinami o; pero hay dos di ultades:
1. El TAD BinNode<Key> no tiene medio dire to de ono er el padre, el ual se requiere
en la opera iones sift up() y sift down().
Hay dos maneras de sortear este obsta ulo. La primera es mediante una pila que


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;

El ultimo atributo, num nodes, ontabiliza la ardinalidad del heap.


Un BinHeap<Key> se ini ializa y naliza as:
hMiembros p
ubli os de BinHeap<Key> 362bi
(360) 366

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

Figura 4.28: Representa ion del TAD BinHeap<Key>

virtual ~GenBinHeap() { /* Empty */ }


Uses RLINK 296.

363

Las onsidera iones de dise~no anteriores nos llevan a olo ar la siguiente informa ion
adi ional por nodo
hDe ni 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);
De nes:
BinHeapNode, never used.
BinHeapNode Data, never used.
Uses DECLARE BINNODE 291b and is leaf 325a 431a.

Esta lase nodo justi a de nir los ma ros siguientes a favor de la legibilidad de odigo:

364a

hDe ni 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())
De nes:
CTRL BITS, never used.
IS LEAF, never used.
IS LEFT, never used.
ULINK, never used.
Uses is leaf 325a 431a.

(360)

364b

hInde ni 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;

return ULINK(LLINK(p)) == RLINK(LLINK(p));


}
De nes:
is in list, never used.
Uses LLINK 296 and RLINK 296.

El segundo return veri a si p es el padre de last y este es su hijo izquierdo


13 Para

aprehender este aso, eje utese la rutina on p=6 en la gura 4.28.

13

4.7. Heaps

365a

365

Otro predi ado que requeriremos es ono er si un nodo tiene o no un hermano:


hMiembros privados de BinHeap<Key> 364 i+
(360) 364 365b

static bool has_sibling(Node * p)


{
return ULINK(p) != RLINK(p);
}
De nes:
has sibling, never used.
Uses RLINK 296.

Usamos este metodo porque opera aun si p es parte de la lista enlazada.


4.7.6.1

365b

Intercambio entre nodos

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 de ne 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

Uses child 323 , LLINK 296, RLINK 296, and S 794 .

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;
}
De nes:
remove last, never used.
Uses LLINK 296 and RLINK 296.

367b

Esta maquinaria nos permite de nir 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

Node * getMin() throw(std::exception, std::underflow_error)


{
if (root == NULL)
throw std::underflow_error ("Heap is empty");
Node *ret_val = root;
if (num_nodes == 1)
{
root = NULL;
ret_val->reset();
num_nodes = 0;
return ret_val;
}
swap_root_with_last();
remove_last();
sift_down(root);
ret_val->reset();
return ret_val;
}

4.7.6.4

368

Actualizaci
on en un heap

Al igual que on ArrayHeap<Key>, es plausible modi ar el valor de prioridad de un nodo


parti ular. En este aso, efe tuamos la misma orre ion:
hMiembros p
ubli os de BinHeap<Key> 362bi+
(360) 367b 369
void update(Node * p)
{
sift_down(p);
sift_up(p);
}

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,

existe un TAD, llamado TimeoutQueue, uyo n es modelizar un manejador de eventos


programados por el reloj del sistema. La implanta ion de TimeoutQueue ha e uso del TAD BinHeap<Key>
y ofre e, entre sus fun iones, la an ela ion de un evento previamente programado.

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 de nida, 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

Figura 4.29: Diagrama UML del TAD BinHeap<Key> y DynBinHeap<Key>

370

Cuando implantamos una version del problema fundamental basada en laves y no en


los nodos que la ontengan, una opera ion fundamental del TAD de base es la posibilidad
de liberar todos los elementos -y toda la memoria o upada- en una sola opera ion. Este es
el aso, por ejemplo, de las opera iones remove all and delete() (x 2.4.7 (pagina 90))
y destroyrec() (x 4.4.10 (pagina 306)) dise~nadas para las listas enlazadas y los arboles
binarios, respe tivamente.
En el aso de un heap dinami o omo BinHeap<Key>, o del destru tor del TAD
DynBinHeap<Key>, no es posible invo ar a la opera ion destroyRec(), pues a ausa
de la omplejidad del nodo de un BinHeap<Key> no se dispone del valor NullPtr, del
ual se sirve destroyRec() para re ono er las hojas.
Una tenta ion para tratar on este problema, es de ir, para eliminar todos los elementos
de BinHeap<Key>, es un i lo del siguiente estilo:
hEliminar todos los elementos del BinHeap<Key> 370i
while (not this->is_empty())
{
Node * p = getMin();
delete p;
}

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 su jo, re ursivo, que libere los

4.7. Heaps

371a

371

nodos del heap:


hMiembros privados de BinHeap<Key> 364 i+

(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 de ne odigo(T ) : B {a, b}
omo el re orrido pre jo del arbol extendido donde sus n nodos internos estan etiquetados
on a y sus externos on b.

Por ejemplo, para el arbol de la gura 4.30, odigo(T ) = aaaabbabbaabbbababb.


Estudiemos omo se ara teriza una palabra odigo(T ) {a, b} . De entrada, asumamos
que L {a, b} es el lenguaje que ara teriza a un odigo de un arbol. L es llamado lenguaje
de Lukasiewi z, en honor al matemati o pola o Jan Lukasiewi z.

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 de nido

por:

(4.34)

L = aLL b

En otras palabras, L = odigo(B).


La expresion (4.34) es una de ni ion re ursiva del onjunto L y signi a que una
palabra L es el resultado de on atenar a on dos palabras seguidas que pertenez an al
onjunto L o on la letra b. Lo mas interesante de este tipo de de ni ion es que hemos
de nido un onjunto in nito de palabras en una sola expresion re ursiva.
Demostraci
on
L odigo(B)

Para demostrar L = odigo(B) debemos demostrar L odigo(B) y

 aLL b odigo(B): Si T = , tenemos que odigo() = b aLL b. Si T 6= ,


enton es T puede expresarse omo T =< L(T ), raiz(T ), R(T ) >. Por la de ni ion
de odigo, odigo(< L(T ), raiz(T ), R(T ) >) = a odigo(L(T )) odigo(R(T ))
odigo(B) 
 odigo(B) aLL b: Sea w L. Si |w| = 1 = w = odigo () = b.

De lo ontrario, |w| > 1 = w = auv, u, v L; donde a orresponde a la raz de un


arbol T mientras que u = odigo(L(T )) y v = odigo(R(T ))


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 pre jo de w. Por ejemplo,
si w = abbba, enton es , a, ab, abb, abbb, abbba son pre jos de w .
En lo que respe ta a los pre jos, denotaremos:
15


u v si u es pre jo de v
u<v

si u es pre jo de v y u 6= v. En este aso, de imos que u es pre jo propio de v

Para el ejemplo anterior, a < abbba, pero abbba abbba.


Definici
on 4.8 (Conjunto prefijo) Un onjunto de palabras es pre jo si ninguna de
sus palabras es pre jo de alguna de las otras. Esto es, dado un lenguaje X, se di e que X es
un lenguaje pre jo u, v X, u y v no son pre jos entre si. Es de ir, u v = u = v.
Por ejemplo, X = {ab, baa, bab}.
15 El

smbolo denota la palabra va a.


Captulo 4. Arboles

374

Lema 4.3 Sea X un lenguaje pre jo y u, v, u , v X. Enton es, uv = u v = u = u y


v = v . En otros terminos, si u, v, u , v X | u 6= u , v 6= v = uv 6= u v .
Demostraci
on (por contradicci
on) Supongamos que X es un lenguaje pre jo; es

de ir, uv = u v = u 6= u o v 6= v . Para fa ilitar la omprension, superpongamos las


palabras uv y u v omo sigue:

En el diagrama se apre ia que u u. Podemos imaginar otras superposi iones y


suponer otros pre jos. En todo aso, es laro que o u u o v v . Sin embargo, puesto
que hemos di ho que X es un onjunto pre jo, es imposible que u sea pre jo de u. Puesto
que la nega ion del lema es falsa, el lema es ierto 
Este lema nos propor iona el argumento prin ipal para demostrar la proposi ion siguiente:
Proposici
on 4.8 (Huffman 1952 [9]) El lenguaje de Lukasiewi z es pre jo.
Demostraci
on (por contradicci
on inductiva sobre la longitud de una palabra)
Supongamos que el lenguaje de Lukasiewi z no es pre jo, enton es u, v, x L | ux =
v.
 |u| = 1: En este aso, u = b = x = = v = b, pero L no ontiene a lo que
impli a que L es pre jo para todas la palabras de longitud 1.
 Ahora asumimos que L es pre jo para todas las palabras u, v L de longitud nominal
y examinaremos si u, x L | ux = v. Es de ir, si existe una palabra de mayor
longitud que pueda ser ompuesta on una palabra pertene iente a L.

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

Esto nos arroja dos asos a examinar:


1. |u1| |v1|: En este aso, u y v se superponen de la forma siguiente:
u1

v1

u2

x
v2

La uni a posibilidad es que u1 = v1, pues de lo ontrario u1 sera pre jo de


v1, pero ello no es as por la hipotesis indu tiva. Por lo tanto, x =
/ L. El
planteamiento es, enton es, ontradi torio.
2. |v1| < |u1| < |u|: En este aso, u y v se superponen de la forma siguiente:

4.8. Enumeraci
on y c
odigos de
arboles

u1
v1

375

u2
v2

De nuevo, v1 no puede ser pre jo de u1, pues si no se ontradi e la hipotesis


indu tiva. Por lo tanto, v1 = u1 y x = / L.
Ambos asos, on |u1| 6= |v1| | |u1| < |u|, |v1| < |v|, ontradi en la hipotesis indu tiva.
Por lo tanto, la hipotesis es ierta

Proposici
on 4.9 T1, T2 B, T1 6= T2 odigo(T1) 6= odigo (T2).
Demostraci
on

La prueba se estru tura en dos partes:

 Inyectividad (=): Apli aremos indu ion sobre las ardinalidades de T1 y T2.

T1, T2 B, T1 6= T2 = odigo(T1) 6= odigo(T2).


Caso base: Si T1 = , T2 6= = odigo(T1) = b, odigo(T2) = auv, u, v L =
odigo(T1) 6= odigo(T2).
Hip
otesis inductiva: Si T1 6= , T2 6= y |T1| 6= |T2| = | odigo (T1)| 6=
| odigo (T2)| = odigo(T1) 6= odigo(T2).

Si |T1| = |T2| enton es:

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 pre jo}. Empero, por la proposi ion 4.8 sabemos que L es un lenguaje
pre jo. 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

 Suprayectividad: En este aso, debemos demostrar que:

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.

Si |w| = n + 1, la palabra w = auv satisfa e |auv| = 1 + |u| + |v|. Por la hipotesis


indu tiva, Tu B | odigo(Tu) = u y Tv B | odigo(Tv) = v tal que
| odigo(Tu)| + | odigo(Tv)| = n = |w| = 1 + |u| + |v| = n + 1 

Puesto que odigo(T ) : B L es inye tiva y supraye tiva, odigo(T ) : B L es


biye tiva. Uno de los teoremas fundamentales de la teora de onjuntos estable e que si
una fun ion f : A B es biye tiva enton es |A| = |B|. Por este teorema, podemos on luir
que |B| = |L|. Lo que demuestra que odigo(T1) 6= odigo(T2) = T1, T2 B, T1 6= T2 
Ahora veamos omo se ara teriza una palabra en L. Para ello, de namos:
|w|a = el n
umero de o urren ias de a en w

umero de o urren ias de b en w


|w|b = el n
Proposici
on 4.10
w L

(a) |w|b = 1 + |w|a


y
(b) u, v, |u| < |w| | w = uv = |u|a |u|b

(4.37)

Demostraci
on

(a) Sea T un arbol binario ualquiera on w = odigo(T ).. Por la de ni 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 de ni 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 pre jo w ualquiera de w, el ual puede tener alguna de las
siguientes on gura 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

Una alternativa para odi ar arboles, ompletamente equivalente a los odigos de la


se ion anterior, es etiquetar el arbol extendido de manera diferente: las ramas izquierdas
on a y las dere has on b tal omo el arbol de la gura 4.31.
a
a
a

b
b

a
b

b
a

a ba b a b

 rbol binario extendido: ramas izquierdas etiquetadas on a; ramas dere has


Figura 4.31: A
on b
Una palabra de Di k, denotada omo di k(T ) : B {a, b} es el re orrido pre jo de
un arbol etiquetado on sus ramas izquierdas en a y sus ramas dere has en b. Por ejemplo,
para el arbol de la gura 4.31
di k(T ) = aaaabbabbaabbbabab
Es fa il obtener una de ni ion re ursiva de una palabra de Di k. En primer lugar, de nimos di k() = . Para una arbol no va o, en su version extendida, T =<
L(T ), raiz(T ), R(T ) >, di k(< L(T ), raiz(T ), R(T ) >= a di k(L(T ))b di k(R(T )). As pues,
si D es el onjunto de todas las palabras de Di k, enton es
D = aDbD
Proposici
on 4.11 Sea D el lenguaje de todas las palabras de Di k, enton es
Db = L

En otras palabras, odigo(T ) = di k(T )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

Ahora retomemos el problema de ontar la antidad de arboles binarios que ontienen n1


nodos. Notemos que por la proposi ion 4.9, ontar el numero de arboles es equivalente a
ontar el numero de palabras de Lukasiewi z de longitud 2n + 1 o el numero de palabras
de Di k de longitud 2n.
Proposici
on 4.12 El n
umero total de arboles binarios diferentes on n nodos es:
 
1
2n
Cn =
(4.38)
.
n+1 n

Donde Cn es ono ido omo el n-esimo numero de Catalan, en honor al matemati o


belga Eugene Catalan.
Demostraci
on

re urrente:

Existe una demostra ion lasi a basada en plantear la siguiente e ua ion



1
Cn = Pn1
i=0

n=0
CiCni1 n > 0

Es de ir, olo amos el nodo raz y ontamos el numero de subarboles izquierdos y el


numero de subarboles dere hos. El arbol izquierdo puede omenzar desde el va o hasta
el que tiene n 1 nodos. Si un subarbol izquierdo tiene i nodos, enton es el dere ho tiene
ni1 nodos. Por la regla del produ to, tenemos CiCni1 arboles posibles uyo subarbol
izquierdo tiene i nodos. En lugar de resolver esta e ua ion re urrente, nuestra demostra ion
estara basada en una interpreta ion geometri a planteada por Kreher y Stinson [12.
Por la proposi ion 4.1, ontar el numero de arboles de n nodos es equivalente a ontar
el numero de arboles binarios de n nodos. Por la proposi ion 4.9, esto equivale a ontar
el numero de palabras de Lukasiewi z de longitud 2n + 1, ual, por la proposi ion 4.11, es
equivalente a ontar el numero de palabras de Di k de longitud 2n.
Existe una interpreta ion gra a de una palabra de Di k (o de Lukasiewi z) denominada \ amino de Catalan". Dada una palabra w = w1w2 . . . w2n+1 L Un amino de

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) =

p = (x, y) | wi w1w2 . . . w2n+1 L, y = i y


x = xi1 + 1

si wi = a
x = xi1 1 si wi = b

El amino de Catalan orrespondiente al odigo del arbol de la gura 4.31 se muestra en


la gura 4.32.
Segun la proposi ion 4.10, |u|a |u|b para todo u pre jo propio de w L, esto
impli a que todo amino de Catalan jamas traspasara el eje x antes del nal de la palabra.
Contar el numero de palabras de Di k de longitud 2n es equivalente a ontar el numero
de aminos de Catalan generales que pueden formarse on n aes y n bes tal que nun a
ru en el nivel x.
El numero total de aminos posibles de Catalan, que ru en o no el eje x, esta dado
por el numero de palabras que se pueden formar on n aes y n bes. Este onteo puede
visualizarse mediante la siguiente gura:
1 2 3 4 . . . 2n

a1 a2 . . . an

es de ir, el numero de ombina iones de 2n eldas enumeradas en n aes; la antidad de


bes son las eldas restantes. De este modo, on luimos que 2n
n es el total de aminos de
Catalan posibles que puede onforma una palabra ompuesta por el alfabeto {a, b} . De
este modo, podemos plantear:
Cn =


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 pre jo de w entre {(0, 0), . . . , (x , 1)}; es
de ir, el pre jo hasta el primer punto que este por debajo del eje x. De namos el amino
siguiente:
P (w) = P (u) (P(w) P (u)) ;

u es el omplemento de u; es de ir, la palabra orrespondiente a ambiar todas las letras


a por b y todas las letras b por a. P (u) se de ne omo:
P (w) = {(2 x, y) P(u) | 1 x x 1, (x, y) P(u)

Para el ejemplo anterior, aabbbaabbaab / D se parti iona enton es omo


bbaaa.aabbaab y tiene el siguiente amino de Catalan:

b b a a a a a b b a a b

Geometri amente, P (w) = P (uv) | u, que es el pre jo orrespondiente al amino


de Catalan P(uv) hasta el primer punto debajo del eje x, puede ser interpretado omo u
desplazado 2 unidades por debajo del eje x.

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

su ede P (w1), P (w2), P (w1) 6= P (w2) = P(w1) 6= P(w2).


Retomando (4.39), ontar el numero de aminos que ruzan el eje x es equivalente
a ontar el numero de aminos que parten desde (0, 2) y arriban a (0, 2n). Cualquier
amino de esta forma debe orresponder a n + 1 letras a y n 1 letras
b. Partiendo desde

2n
2, se requieren 2 letras a mas que la letra b. As pues, existen n+1 aminos que ruzan
el eje x. Sustituimos en (4.39):
Cn =

 

 
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.

pueden ser multipli ados aso iativamente


 El n
umero de maneras en que n numeros

1 6
on 2n parentesis. Por ejemplo, hay 4 3 = 5.
(n1(n2(n3n4)))

(n1((n2n3)n4)) ((n1n2)(n3n4))

((n1(n2n3))n4) (((n1n2)n3)n4)

Figura 4.33: Diferentes maneras de ortar un pentagono en 3 triangulos


 El n
umero de expresiones parentizadas on n parentesis izquierdos y n parentesis

dere hos que estan balan eadas.

 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

que pasan debajo de la diagonal.

 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

Figura 4.34: Diferentes aminos debajo de la diagonal en una malla 4 4

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 de ni ion.
El orden se desta a uando se observa el re orrido in jo. Para el arbol de la gura 4.35,
el re orrido in jo 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 de ni 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

Figura 4.35: Un arbol binario de busqueda

383

La propiedad de orden de un arbol binario de busqueda permite odi arlo on el


re orrido pre jo o su jo. Es de ir, podemos re onstruir ompletamente un ABB a partir
de su re orrido pre jo.
Sea n0, n1, . . . , n|T|1 el re orrido pre jo de un ABB T =< L(T ), raiz(T ), R(T ) >. El re orrido pre jo nos propor iona dire tamente raiz(T ) = n0 y raiz(L(T )) = n1. La propiedad
de orden nos garantiza que raiz(R(T )) sera el primer nodo en el re orrido pre jo que sea
mayor o igual que raiz(T ). As pues, apli amos este razonamiento re ursivo y obtenemos
el algoritmo siguiente:
hFun iones de BinNode Utils 299ai+
(298) 343b 384b
template <class Node> inline
Node *
preorder_to_binary_search_tree(DynArray<typename Node::key_type> & preorder,
const int & l, const int & r)
{
if (l > r)
return Node::NullPtr;
Node * root = new Node(preorder[l]);
if (l == r)
return root;
hSea first greater
LLINK(root) =

el primer nodo mayor que la raz 384ai

493


Captulo 4. Arboles

384

preorder_to_binary_search_tree<Node>(preorder, l + 1, first_greater - 1);


RLINK(root) =
preorder_to_binary_search_tree<Node>(preorder, first_greater, r);
return root;
}
De nes:

preorder to binary search tree, never used.


Uses DynArray 45, LLINK 296, and RLINK 296.

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;

while ( (first_greater <= r) and (preorder[first_greater] < preorder[l]))


++first_greater;

Este nodo parti iona el arreglo en dos partes. La primera omprendida entre l + 1
y right root - 1, orresponde al re orrido pre jo del subarbol izquierdo. La segunda
parte, omprendida entre right root y r, orresponde al re orrido pre jo 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;
}
De nes:

searchInBinTree, used in hunk 390b.


Uses LLINK 296 and RLINK 296.

//
//
//
//

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

Sabemos que el re orrido in jo de un ABB muestra la se uen ia ordenada. Por tanto, el


menor y el mayor de todos los elementos del onjunto estaran dados por el primer y ultimo
elementos del re orrido in jo, los uales se identi an pi tori amente, de manera general,
en la siguiente gura:
T

M
nimo

385

L(T )

R(T )
M
aximo

Para T ABB, el mnimo se en uentra en el nodo mas a la izquierda, mientras que el


maximo en el mas a la dere ha. De este ono imiento, se dedu en los siguientes algoritmos:
hFun iones de BinNode Utils 299ai+
(298) 384b 386
template <class Node>
Node * find_min(Node * root)
{
Node * ret_val = root;
while (LLINK(ret_val) != Node::NullPtr)
ret_val = static_cast<Node*>(LLINK(ret_val));
return ret_val;
}
template <class Node>
Node * find_max(Node * root)
{
Node * ret_val = root;
while (RLINK(ret_val) != Node::NullPtr)
ret_val = static_cast<Node*>(RLINK(ret_val));
return ret_val;
}
De nes:
find max, never used.
find min, never used.
Uses LLINK 296 and RLINK 296.


Captulo 4. Arboles

386

Ambas primitivas re iben un nodo raz de un ABB y retornan el mnimo y maximo,


respe tivamente.
Si el ABB esta equilibrado, enton es find min() y find max() tienen omplejidad
O(lg n).
4.9.1.2

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;
}
De nes:

find successor, never used.


Uses LLINK 296 and RLINK 296.

find successor() retorna el nodo su esor de p y su padre en el parametro pp. La llamada


debe realizarse sobre p y su padre.
Lo elegante de find successor() es que no se requiere un operador de ompara ion.
La topologa del arbol propor iona el resultado.
T

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;
}
De nes:

search parent, never used.


Uses LLINK 296 and RLINK 296.

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,

el ual debe ini ializarse en el padre de la raz.


Para estudiar el sentido del segundo tipo de busqueda, onsideremos el siguiente ABB
de ejemplo:
50
25
10

80
30

90


Captulo 4. Arboles

388

El arbol ontiene las laves 10, 25, 30, 50, 80, 90 entre un onjunto in nito 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..]

El uni o \espa io" por donde puede re er \naturalmente" un ABB es a traves de


sus nodos externos. Di ho de otro modo, que el va o que representa un nodo externo sea
o upado \fsi amente" por un nuevo nodo interno.
Luego de expresado lo anterior, adquiere sentido pensar una busqueda de un rango
que no esta en el arbol. O sea, bus ar los puntos por donde puede re er un ABB. Tal
busqueda se instrumenta de la siguiente forma:
hFun iones de BinNode Utils 299ai+
(298) 387 391
16

388

template <class Node, class Compare> inline


Node * search_rank_parent(Node * root, const typename Node::key_type & key)
{
Compare cmp;
Node *p = root;
while (true)
if (cmp(key, KEY(p)))
{
if (LLINK(p) == Node::NullPtr)
return p;
p = static_cast<Node*>(LLINK(p));
}
else if (cmp(KEY(p), key))
{
if (RLINK(p) == Node::NullPtr)
return p;
p = static_cast<Node*>(RLINK(p));
}
else
return p;

}
De nes:

search rank parent, never used.


Uses LLINK 296 and RLINK 296.
16 En

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>

Antes de introdu ir los algoritmos de inser ion y supresion, es onveniente introdu ir


nuestro TAD de base que modeliza un arbol binario de busqueda. Tal TAD se espe i a
en el ar hivo htpl binTree.H 389ai uya estru tura es la siguiente:
htpl binTree.H 389ai
template <template <typename> class NodeType, typename Key, class Compare>
class GenBinTree
{
ubli as BinTree<Key> 390ai
hDe ni iones p
private:
hmiembros dato de BinTree<Key> 389bi
hm
etodos privados de BinTree<Key> (never de ned)i
public:

etodos
hM
};

de BinTree<Key> 390bi

template <typename Key, class Compare = Aleph::less<Key> >


class BinTree : public GenBinTree<BinNode, Key, Compare>
{ /* empty */ };

template <typename Key, class Compare = Aleph::less<Key> >


class BinTreeVtl : public GenBinTree<BinNodeVtl, Key, Compare>
{ /* empty */ };
Uses BinNode 294a and BinNodeVtl 294a.

htpl

binTree.H 389ai de ne tres lases:

 GenBinTree<NodeType, Key, Compare> que es la lase generi a que implanta el

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

destru tores virtuales.

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

al usuario del siguiente modo:


hDe ni iones p
ubli as BinTree<Key> 390ai

(389a)

public:
typedef NodeType<Key> Node;
private:

El tipo sera BinTree<Key>::Node para nodos omunes o BinTreeVtl<Key>::Node para


nodos on destru tores virtuales.
NodeType:class
Key:class
Compare:class

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

GenBinTree<BinNode, Key, Compare>

GenBinTree<BinNodeVtl, Key, Compare>

Key:class
Compare:class

Key:class
Compare:class

BinTree

BinTreeVtl

Figura 4.37: Diagrama UML de las lases on ernientes a BinTree<Key>


390b

La busqueda se remite a invo ar a searchInBinTree():


hM
etodos de BinTree<Key> 390bi

(389a) 390

Node * search(const Key & key)


{
return searchInBinTree <Node, Compare>(root, key);
}
Uses searchInBinTree 384b.

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

(a) Antes de insertar 27

47
31

47

10
59

31
27

(b) Luego de insertar 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);

else if (Compare () (KEY(root), KEY(p))) // p > root?


// insertar en sub
arbol derecho
return insert_in_binary_search_tree <Node, Compare>
(static_cast<Node*&>(RLINK(root)), p);

59


Captulo 4. Arboles

392

return Node::NullPtr; // clave repetida ==> no hay inserci


on
}

De nes:

insert in binary search tree, used in hunks 392, 399, and 400a.

Uses LLINK 296 and RLINK 296.

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 in ja 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 > )

split key rec(T, k x , T < , T > )

KEY(T )

L(T )

T<

L(T )

R(T )

T<

T>

T>

=
( ) Situa ion ini ial

(d) Luego de split() en raz

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 de nitivo 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>

template <class Node, class Compare> inline


bool split_key_rec(Node * root,
const typename Node::key_type & key,
Node *& ts, Node *& tg)
{
if (root == Node::NullPtr)
{
// key no se encuentra en
arbol ==> split tendr
a
exito
ts = tg = Node::NullPtr;
return true;
}
if ( Compare() (key, KEY(root)) ) // key < KEY(root)?
{
if (SPLIT(static_cast<Node*&>(LLINK(root)), key,
ts, static_cast<Node*&>(LLINK(root))))
{
tg = root;
return true;
}
else
return false;
}
if ( Compare() (KEY(root), key) ) // key > KEY(root)?
{
if (SPLIT(static_cast<Node*&>(RLINK(root)), key,
static_cast<Node*&>(RLINK(root)), tg))
{
ts = root;
return true;
}
else
return false;
}
return false; // clave existe en
arbol ==> se deja intacto
}


Captulo 4. Arboles

394

# undef SPLIT
De nes:
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

split key rec(29, 19, ...)

split key rec(13, 19, ...)

13

45
split key rec(25, 19, ...)

25

30

77

split key rec(20, 19, ...)

12

split key rec(15, 19, ...)

split key rec(18, 19, ...)

20
15

27

53

91

22
18

Figura 4.38: Trazado de una lnea de division

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>)

Y la solu ion se pi toriza del siguiente modo:


T<
T>
L(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

}
De nes:

join exclusive, used in hunks 397a and 617.


Uses join 400a, LLINK 296, and RLINK 296.

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)

Si literalmente quitamos p, enton es debemos ha er \algo" on los tres ar os que quedan


\sueltos" si desapare e p: q p, p L(p) y p R(p). Adi ionalmente, para poder
modi ar orre tamente a q, debemos distinguir exa tamente si p es hijo izquierdo o
dere ho.
Se ono en dos maneras de tratar on la di ultad anterior:
1. Substituimos p por su nodo prede esor en L(p), o por su su esor en R(p).
Hay dos puntos fundamentales que debemos notar. Primero, tanto el prede esor omo
el su esor de p siempre son in ompletos. En el aso del prede esor, este esta dado
por el nodo mas a la izquierda de L(p), el ual tiene que ser in ompleto, pues sino
ontradira el he ho de que el prede esor sea el mas a la izquierda. El razonamiento
simetri o o urre on el su esor.
Puesto que el nodo a sustituir esta in ompleto, tenemos su iente espa io para enlazar las tres ramas si inter ambiamos el nodo substituto por p, pues, por ejemplo
en el prede esor, su rama izquierda faltante se substituye por L(p).
La segunda uestion a notar es que el prede esor y el su esor son los dos uni os posibles nodos in ompletos a inter ambiar, pues, de lo ontrario, se violara la propiedad
de orden de un ABB.
Un algoritmo \elegante" basado en este prin ipio es bastante laborioso, pues, para
efe tuar orre tamente los inter ambios, se requieren mantener punteros a p, a su
padre, al prede esor y al padre del prede esor. En a~nadidura, si el prede esor no
es hoja, hay que efe tuar un paso adi ional onsistente en \ orto- ir uitar" p en su
nueva posi ion on el arbol que este ontenga.


4.9. Arboles
binarios de b
usqueda

397

La \elegan ia" de la que hablamos en el parrafo anterior onsiste en no inter ambiar


los ontenidos de los nodos, pues se afe tara la oheren ia de eventuales estru turas
de datos que apunten a ellos.
Esta forma esta en desuso, pues la otra manera, que expli aremos inmediatamente, no solo es mu ho mas simple, sino mas e iente. De todos modos,
en x 6.4.1.2 (pagina 578) se muestra esta te ni a.
2. El arbol uya raz es p se sustituye por el arbol resultante de la union ex lusiva de
sus ramas. En fun ion de esto, se desprende el siguiente algoritmo:
397a
hFun iones de BinNode Utils 299ai+
(298) 395 398
# define REMOVE remove_from_search_binary_tree<Node, Compare>

template <class Node, class Compare> inline


Node * remove_from_search_binary_tree(Node *& root,
const typename Node::key_type & key)
{
if (root == Node::NullPtr)
return Node::NullPtr;
if (Compare () (key, KEY(root)))
return REMOVE(static_cast<Node*&>(LLINK(root)), key);
else if (Compare () (KEY(root), key))
return REMOVE(static_cast<Node*&>(RLINK(root)), key);
// root contiene la clave a borrar
Node * ret_val = root; // respaldar para retornar
root = join_exclusive(static_cast<Node*&>(LLINK(root)),
static_cast<Node*&>(RLINK(root)));
ret_val->reset();
return ret_val;
}
De nes:

remove from search binary tree, used in hunk 397b.


Uses join exclusive 395, LLINK 296, and RLINK 296.

El parametro root es la raz del ABB, el ual es por referen ia, de forma tal que se
modi que 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

Node * remove(const Key & key)


{
return remove_from_search_binary_tree<Node, Compare>(root, key);
}
Uses remove from search binary tree 397a.


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

const bool split = split_key_rec<Node, Compare>(root, KEY(p), l, r);


if (not split)
return Node::NullPtr;
LLINK(p) = l;
RLINK(p) = r;
root = p;
return root;
}

De nes:

insert root, used in hunk 400a.


Uses LLINK 296, RLINK 296, and split key rec 393.

La rutina retorna Node::NullPtr si el arbol ontiene un nodo on lave igual al nodo


de inser ion p.


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 pre jo 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;

Node * l = static_cast<Node*>(LLINK(t2)); // respaldos de ramas de t2


Node * r = static_cast<Node*>(RLINK(t2));
Node * ins_ret = insert_in_binary_search_tree <Node, Compare> (t1, t2);
if (ins_ret == Node::NullPtr)
// inserci
on fallida ==> elemento duplicado ==> insertar en dup
insert_in_binary_search_tree<Node, Compare>(dup, t2);
join_preorder(t1, l, dup);
join_preorder(t1, r, dup);
return t1;
}
De nes:
join preorder, never used.
Uses insert in binary search tree 391, LLINK 296, and RLINK 296.

Otra manera, quiza mas onsona on la re ursion inherente a un arbol binario, se


ilustra en el siguiente diagrama:
insert root

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

Con el prin ipio anterior laro, podemos dise~nar un algoritmo:


hFun iones de BinNode Utils 299ai+
(298) 399 419
template <class Node, class Compare> inline
Node * join(Node * t1, Node * t2, Node *& dup)
{
if (t1 == Node::NullPtr)
return t2;
if (t2 == Node::NullPtr)
return t1;
Node * l = static_cast<Node*>(LLINK(t1)); // respaldos de ramas de t1
Node * r = static_cast<Node*>(RLINK(t1));
t1->reset();
if (insert_root<Node, Compare>(t2, t1) == Node::NullPtr)
insert_in_binary_search_tree<Node, Compare>(dup, t1);
LLINK(t2) = join<Node, Compare>(l, static_cast<Node*>(LLINK(t2)), dup);
RLINK(t2) = join<Node, Compare>(r, static_cast<Node*>(RLINK(t2)), dup);
return t2;
}
template <class Node> inline
Node * join(Node * t1, Node * t2, Node *& dup)
{
return join<Node, Aleph::less<typename Node::key_type> >(t1, t2, dup);
}
De nes:
join, used in hunks 19, 395, 400b, 416b, and 418.
Uses insert in binary search tree 391, insert root 398, LLINK 296, and RLINK 296.

400b

Ahora in orporamos esta version al metodo orrespondiente en BinTree<Key>:


hM
etodos de BinTree<Key> 390bi+
(389a) 397b
void join(GenBinTree & tree, GenBinTree & dup)
{
root = Aleph::join<Node, Compare> (root, tree.root, dup);
tree.root = Node::NullPtr;
}
Uses join 400a.

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

(a) insert root()

(b) insert in search binary tree()

( ) join() de arboles (a) y (b)

Figura 4.40: Ejemplo de join() de arboles binarios


En lo que sigue de nuestro analisis, supondremos que el orden de inser ion de las laves
es aleatorio; es de ir, uniformemente distribuido. As, dado un arbol T , hay |T |! ordenes
2|T|
1
diferentes de inser ion que pueden produ ir C|T| = |T|+1
arboles binarios diferentes.
|T|
Ahora bien, es posible demostrar, preferiblemente por indu ion, que C|T| < |T |!, |T |. Lo
que impli a que algunos arboles se repetiran para permuta iones de inser ion diferentes.
Proposici
on 4.13 Sea T un arbol binario de b
usqueda de n nodos onstruido aleatoria-

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)

Donde S y U denotan los promedios de Sn y Un, respe tivamente; mientras que Hn es


el n-esimo numero armoni o .
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 de ni 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)

Sustituimos (4.21) en (4.40) (por la proposi ion 4.5):


S =
n

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)

Sustituimos (4.42) en (4.41):


S =
n

(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 de ni ion de promedio, podemos plantear una nueva e ua ion:
S

(U0 +1) + (U1 +1) + (U2 +1) + + (Un-1 +1)


n
n1
1X
= 1 +
Ui
n

i=0

(4.45)


4.9. Arboles
binarios de b
usqueda

403

Igualamos (4.45) on (4.43):


(n + 1) Un 2n
n

1 = 1

n1
1X
Ui
n

i=0

(n + 1) Un =

n1
X

(4.46)

2n

i=0

Evaluamos (4.46) para n 1 y obtenemos:


n Un-1 =

n2
X

(4.47)

2(n 1)

i=0

Restamos (4.46) menos (4.47):


(n + 1) Un

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

es el n-esimo numero armoni o. As pues:

1
i=1 i

U = 2(Hn+1 1) = 2Hn+1 2 2(ln (n + 1) ) 2 1, 386 lg (n + 1)  (4.49)


n

19

Ahora sustituimos (4.49) en (4.43):


S

(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

smbolo denota la onstante de Euler ara terizada por:


= lim (Hn ln n) 0, 5772156649 . . .
n

404

Captulo 4. Arboles

Figura 4.41: Un arbol binario de busqueda de 512 laves aleatorias


Nuestro analisis supone que no hay supresiones inter aladas entre las inser iones. Si las
supresiones son eje utadas despues de las inser iones hasta va iar el arbol, el desempe~no
aun es O(lg n), pues las premisas de la proposi ion 4.13 aun son iertas.
El analisis on supresiones inter aladas entre la inser iones es un problema extremadamente di ultoso que a la fe ha a tual no ha sido resuelto satisfa toriamente. La di ultad estriba en que la elimina ion efe tua una de ision que no es aleatoria uando en
la union ex lusiva es oge ual sera la raz del subarbol resultante. No se sabe exa tamente omo manejar las probabilidades de las permuta iones ante estos desplazamientos.
En a~nadidura, es importante notar que el algoritmo de supresion desarrollado en x 4.9.6
plantea una asimetra importante. Cuando se suprime un nodo ompleto y se efe tua
el join exclusive(), el nodo raz del arbol resultante es el subarbol izquierdo. Esta
de ision ausa que el numero de nodos por el lado izquierdo del punto de elimina ion
disminuya. Conse uentemente, la raz del arbol tiende a desplazarse ha ia la izquierda, lo
que rea un desequilibrio. Estudios empri os arrojan una longitud promedio del amino

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 DynMapTree<Tree, Key, Range, Compare>

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

4.10. El TAD DynMapTree<Tree, Key, Range, Compare>

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 de ne 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
De nes:

publi os de DynMapTree<Tree,

Key, Range, Compare> 406 i

dinami as derivadas de DynMapTree<Tree,

Key, Range, Compare> 408bi

DynMapTreey, never used.

DynMapTree<Tree, Key, Range, Compare> tiene uatro parametros lase. La lase


Tree representa el tipo de ABB on el ual se desea implantar el mapeo. Entre los tipos
posibles, entre los desarrollados en este texto, tenemos BinTree<Key>, Rand Tree<Key>,
Treap<Key>, Avl Tree<Key>, Rb Tree<Key> y Splay Tree<Key>. A la ex ep ion de
BinTree<Key>, el resto de las lases orresponde a ABB espe iales, on presta iones

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 de nido y usado
para el TAD BinNode<Key>.


Captulo 4. Arboles

406

406a

Finalmente, el ultimo parametro, onstituye la lase de ompara ion entre laves de


tipo Key.
Con
lo
anterior
en
mente,
podemos
plantear
los
atributos
de DynMapTree<Tree, Key, Range, Compare>:
hMiembros privados de DynMapTree<Tree, Key, Range, Compare> 406ai
(405) 406b
Tree<Key, Compare> tree;
size_t
num_nodes;

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 de nirse junto on el destru tor:
hMiembros p
ubli os de DynMapTree<Tree, Key, Range, Compare> 406 i
(405) 407a
DynMapTree() : num_nodes(0) { /* empty */ }

DynMapTree(DynMapTree & src_tree) : num_nodes(src_tree.num_nodes)


{
Node * src_root = static_cast<Node*>(src_tree.tree.getRoot());
try
{
tree.getRoot() = copyRec(src_root);
}
catch (...) // hubo falla
{
num_nodes = 0;
throw;

4.10. El TAD DynMapTree<Tree, Key, Range, Compare>

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;

(405) 406 407b

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

minar si hubo o no elimina ion.


Hay o asiones en las uales es deseable eliminar todos los elementos. Para ello, usamos
el metodo empty():
hMiembros p
ubli os de DynMapTree<Tree, Key, Range, Compare> 406 i+
(405) 407b 408a
void empty()
{
num_nodes = 0;
destroyRec(static_cast<Node*>(tree.getRoot()));


Captulo 4. Arboles

408

}
Uses destroyRec 306.

408a

Hay dos maneras de onsultar:


hMiembros p
ubli os de DynMapTree<Tree,

Key, Range, Compare> 406 i+


bool test_key(const Key & key)
{
Node * node = static_cast<Node*>(tree.search(key));

(405) 407

return node != NULL;


}
Range * test(const Key & key)
{
Node * node = static_cast<Node*>(tree.search(key));
return node != NULL ? &(node->get_data()) : NULL;
}

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 */ };
De nes:
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 in jo 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 in jo en O(lg(n)).
 Cono imiento de la posi i
on in ja dada una lave en O(lg(n)).

4.11. Extensiones a los


arboles binarios

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 de ni 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(n) = C(L(n)) + 1 + C(R(n)),

C() = 0

Un ABB puede ser extendido y lo de nimos omo sigue:

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.

El a ronimo ABBE re ere a un arbol binario de busqueda extendido.

409

La gura 4.42 ilustra un ABBE. El ampo superior es la lave y el inferior es la


ardinalidad del subarbol. En la gura, ada nodo tiene una etiqueta, que no forma parte
de la estru tura de datos, orrespondiente a su posi ion in ja.
Dado ni T | in ABBE, enton es, C(L(n)) nos da la posi ion in ja de ni respe to al
subarbol on raz ni. Este es el ono imiento que nos permitira a eder al arbol por su
posi ion in ja.
Estamos listos para dise~nar una infraestru tura de abstra iones y odigo que nos
maneje arboles extendidos. La primera fase onsiste en de nir la estru tura de un nodo
binario omponente de un ABBE, la ual se de ne en el ar hivo htpl binNodeXt.H 409i:
htpl binNodeXt.H 409i
class BinNodeXt_Data
{
size_t count; // cardinalidad del
arbol
public:
BinNodeXt_Data() : count(1) { /* empty */ }
BinNodeXt_Data(SentinelCtor) : count(0) { /* empty */ }
size_t & getCount() { return count; }
const size_t & size() const { return count; }
void reset() { count = 1; }
};
DECLARE_BINNODE_SENTINEL(BinNodeXt, 255, BinNodeXt_Data);
# define COUNT(p) ((p)->getCount())

asi as sobre BinNodeXt<Key> 411ai


hPrimitivas b
De nes:
BinNodeXt, never used.
Uses DECLARE BINNODE SENTINEL 293 and SentinelCtor.


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

Figura 4.42: Un arbol binario extendido


La ardinalidad se guarda en el atributo count, el ual tiene su observador y modi ador
de nidos en la parte publi a de la lase BinNodeXt<Key>.
En un ABBE es deseable utilizar un nodo entinela espe ial que represente el arbol
va o. La ventaja del entinela es que su ampo count siempre es ero, lo que evita la
ne esidad de veri ar si el nodo a edido es el nulo o no en aquellos algoritmos que
pregunten la ardinalidad. La ardinalidad de es 0, que es el mismo valor de la posi ion
in ja de un nodo in ompleto por la izquierda.
El nodo entinela es una instan ia estati a de BinNodeXt<Key> uyo espa io es reservado en el ma ro DECLARE BINNODE SENTINEL.

4.11. Extensiones a los


arboles binarios

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 in ja. 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)

select rec(R(r), i - COUNT(LLINK(r)) - 1)


L(T )

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
in jo 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;
}
De nes:
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 in ja; 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");

if (Compare () (key, KEY(r)))


return inorder_position(static_cast<Node*>(LLINK(r)), key, node);
else if (Compare () (KEY(r), key))
return inorder_position(static_cast<Node*>(RLINK(r)), key, node) +
COUNT(LLINK(r)) + 1;
else
return COUNT(LLINK(r));
}
De nes:

get inorder pos, never used.


Uses LLINK 296 and RLINK 296.

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 in jo.
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)

4.11. Extensiones a los


arboles binarios

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;
}
De nes:

insert by key xt, never used.


Uses LLINK 296 and RLINK 296.

La sintaxis y semanti a son las mismas que para los ABB.


4.11.4

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>

template <class Node, class Compare> inline


bool split_key_rec_xt(Node * root, const typename Node::key_type & key,
Node *& l, Node *& r)
{
if (root == Node::NullPtr)
{
l = r = Node::NullPtr;
return true;
}
if (Compare() (key, KEY(root)))
{
if (not SPLIT(LLINK(root), key, l, LLINK(root)))


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;
}
De nes:
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;

if (not split_key_rec_xt<Node, Compare>(root, KEY(p), LLINK(p), RLINK(p)))


return Node::NullPtr;
COUNT(p) = COUNT(LLINK(p)) + COUNT(RLINK(p)) + 1;
root = p;
return p;
}
De nes:

insert root xt, used in hunk 552a.


Uses LLINK 296, RLINK 296, and split key rec xt 413.

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 in jo. La parti ion resulta en dos
arboles Tl =< k1k2 . . . kl > y Tr =< ki . . . kn >.

4.11. Extensiones a los


arboles binarios

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);
}
}
De nes:

split pos rec, used in hunk 416a.


Uses LLINK 296 and RLINK 296.

La rutina dispara la ex ep ion std::out of range en aso de que i este fuera del rango
in jo del arbol.
4.11.7

Inserci
on por posici
on

Consideremos el re orrido in jo 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
in ja 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;
}
De nes:

insert by pos, never used.


Uses LLINK 296, RLINK 296, and split pos rec 415.

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

La union ex lusiva presentada en x 4.9.5 (pag. 394) es estru turalmente identi a en su


version para arboles extendidos. Lo uni o que debemos modi ar es la a tualiza ion de
los ontadores:
hPrimitivas b
asi as sobre BinNodeXt<Key> 411ai+
(409) 416a 417
template <class Node> inline
Node * join_exclusive_xt(Node *& ts, Node *& tg)
{
if (ts == Node::NullPtr)
return tg;
if (tg == Node::NullPtr)
return ts;
LLINK(tg) = join_exclusive_xt(RLINK(ts), LLINK(tg));
RLINK(ts) = tg;
// actualizaci
on de contadores
COUNT(tg) = COUNT(LLINK(tg)) + 1 + COUNT(RLINK(tg));
COUNT(ts) = COUNT(LLINK(ts)) + 1 + COUNT(RLINK(ts));
Node * ret_val = ts;
ts = tg = Node::NullPtr; // deben quedar vac
os despu
es del join
return ret_val;
}
De nes:

4.11. Extensiones a los


arboles binarios

417

join exclusive xt, used in hunks 417 and 418.


Uses join 400a, LLINK 296, and RLINK 296.

4.11.9

417

Eliminaci
on por clave en
arboles extendidos

Eliminar por lave en un arbol extendido tambien es muy similar a la estudiada


en x 4.9.6 (pag. 396); on la adi ion de que hay que a tualizar los ontadores en el amino
de busqueda:
hPrimitivas b
asi as sobre BinNodeXt<Key> 411ai+
(409) 416b 418
# define REMOVE remove_by_key_xt<Node, Compare>

template <class Node, class Compare> inline


Node * remove_by_key_xt(Node *& root, const typename Node::key_type & key)
{
if (root == Node::NullPtr)
return (Node*) Node::NullPtr; // clave no encontrada
Node * ret_val = Node::NullPtr;
if (Compare () (key, KEY(root)))
{
ret_val = REMOVE(static_cast<Node*&>(LLINK(root)), key);
if (ret_val != Node::NullPtr) // hubo eliminaci
on?
--COUNT(root); // S
==> actualizar contador
return ret_val;
}
else if (Compare () (KEY(root), key))
{
ret_val = REMOVE(static_cast<Node*&>(RLINK(root)), key);
if (ret_val != Node::NullPtr) // hubo eliminaci
on?
--COUNT(root); // S
==> actualizar contador
return ret_val;
}
// clave encontrada ==> eliminar
ret_val = root;
root = join_exclusive_xt(static_cast<Node*&>(LLINK(root)),
static_cast<Node*&>(RLINK(root)));
ret_val->reset();
return ret_val;
}
De nes:
remove by key xt, never used.
Uses join exclusive xt 416b, LLINK 296, RLINK 296, and S 794 .


Captulo 4. Arboles

418

4.11.10

418

Eliminaci
on por posici
on en
arboles extendidos

Si la posi ion in ja es valida, enton es, al igual que on la inser ion y la parti ion, la
elimina ion siempre tiene exito y se de ne 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)
{

if (COUNT(LLINK(root)) == pos) // posici


on encontrada?
{
// Si ==> guarde nodo y realice join exclusivo
Node * ret_val = root;
root = join_exclusive_xt(static_cast<Node*&>(LLINK(root)),
static_cast<Node*&>(RLINK(root)));
ret_val->reset();
return ret_val;
}
Node * ret_val; // guarda valor de retorno de llamada recursiva
if (pos < COUNT(LLINK(root)))
ret_val = remove_by_pos_xt(static_cast<Node*&>(LLINK(root)), pos);
else
ret_val = remove_by_pos_xt(static_cast<Node*&>(RLINK(root)),
pos - (COUNT(LLINK(root)) + 1));
if (ret_val != Node::NullPtr) // hubo eliminaci
on?
--COUNT(root); // Si ==> el
arbol con ra
z root perdi
o un nodo
return ret_val;
}
De nes:
remove by pos xt, never used.
Uses join 400a, join exclusive xt 416b, LLINK 296, and RLINK 296.

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 modi quen el
arbol seran O(lg(n)); este es el aso de la sele ion, determina ion del orden in jo 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 in jo del arbol 4.43(a) es (A)B, mientras que el del 4.43(b) es A(B).
B

(a)

(b)

Figura 4.43: Rota ion de un arbol binario


La opera ion de rota ion no afe ta la propiedad de orden de un ABB.
El arbol 4.43(b) se obtiene a partir del arbol 4.43(a) mediante una \rota ion ha ia la
dere ha" u \horaria" del nodo B. Analogamente, el arbol 4.43(a) se obtiene a partir del
arbol 4.43(b) mediante una \rota ion ha ia la izquierda" u \horaria" del nodo A.
Hay dos ara tersti as importantes de una rota ion que ejempli aremos on la
rota ion ha ia la dere ha:
1. El nodo A disminuye en un nivel, mientras que el nivel del nodo B del arbol original
aumenta en uno.
2. La rama disminuye enteramente en un nivel, mientras que la rama aumenta
enteramente en un nivel.

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;
}
De nes:

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.

rotate_to_right(p, pp) rota hacia la derecha el


arbol binario con
ra
z p y actualiza en pp, el padre de p, la rama que apunta al
arbol binario rotado resultante.

@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.

@return Arbol binario resultante de la rotaci


on.
*/
template <class Node> inline
Node * rotate_to_right(Node * p, Node * pp)
{
Node *q = static_cast<Node*>(LLINK(p));
LLINK(p) = RLINK(q);
RLINK(q) = p;
// actualizaci
on del padre
if (static_cast<Node*>(LLINK(pp)) == p)
LLINK(pp) = q;
else
RLINK(pp) = q;
return q;
}
Uses LLINK 296, RLINK 296, and rotate to right 419.

pp es el padre del nodo p.

Las versiones por la izquierda son simetri as.

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 de ni ion de arbol extendido, tenemos que:


1 + C() .
C(B) = C() + |{z}
1 + C() + |{z}
| {z }
| {z }
| {z }
| {z }
|B|

||

|{A}|

||

|{B}|

||

Despues de rotar se debe umplir:

C(A) = C() + |{z}


1 + C() + |{z}
1 + C()
| {z }
|A|

| {z }
||

|{B}|

| {z }
||

|{B}|

| {z }
||

4.13. C
odigos de Huffman

421

421

As pues, la primitiva de rota ion ha ia la dere ha sera:


hPrimitivas b
asi as sobre BinNodeXt<Key> 411ai+

(409) 418

template <class Node> inline


Node* rotate_to_right_xt(Node* p)
{
Node *q = static_cast<Node*>(LLINK(p));
LLINK(p) = RLINK(q);
RLINK(q) = p;
COUNT(p) -= 1 + COUNT(LLINK(q));
COUNT(q) += 1 + COUNT(RLINK(p));
return q;

}
De nes:

rotate to right xt, used in hunk 546b.


Uses LLINK 296 and RLINK 296.

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

unicode, extensible, de se uen ias de longitud es variable y on la pretension de servir a

todos los smbolos de todas las lenguas.


Designemos a omo el onjunto de todos los posibles smbolos que puede ontener
un texto. En este aso, podemos odi ar los || smbolos on lg || bits. A ordado el
odigo, ualquier agente (o programa) puede \interpretar" un texto.
20

20 He

aqu una objetiza ion de una a ion que en el ambito humano es muy subjetiva.


Captulo 4. Arboles

422

Si onsideramos a un texto parti ular Tx uyos smbolos se remiten a un onjunto


S , enton es podramos odi arlo on lg |S| lg || bits. Para ello, onstruimos

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 pre jo, en el sentido de la de ni ion 4.8 ; es de ir, que
ninguna se uen ia si de longitud i sea pre ja de alguna otra de ardinalidad superior. Si
es ogemos permuta iones de se uen ias pre jas, 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 pre jas.
Hay una mejora aun mas substan ial en usar una odi a ion pre ja: 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

arroja el siguiente mapeo :

a
e
i
b

000
001
010
011
10
11

El proposito de esta se ion es presentar me anismos para de odi ar se uen ias de


bits usando arboles de odigos y para onstruir estos ultimos \optimamente".
21 En

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
};
De nes:

publi os de odi ador 426f i

Huffman Encoder Engine, used in hunk 426f.

423b

Esta lase se en arga de onstruir un arbol de pre jos optimo y de odi ar textos en
fun ion del arbol anterior. Por optimo pretendemos de ir que un texto odi ado on los
pre jos 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
};
De nes:

publi os de de odi ador 427ai

Huffman Decoder Engine, used in hunk 427a.

423

Las lases y otras de ni iones se en uentran en el ar hivo hHu man.H 423 i, el ual
se estru tura del siguiente modo:
hHu man.H 423 i
hDe lara iones Hu man 424ai
hClase

odi adora 423ai

hClase

423d

de odi adora 423bi


Codi ar y de odi ar una se uen ia requiere de varias estru turas de datos. La
primera de ellas es el propio arbol de odigos, el ual se de ne del siguiente modo:
hmiembros privados de odi ador 423di
(423a) 426a
BinNode<string> * root;
Uses BinNode 294a.

423e

hmiembros privados de de odi ador 423ei


BinNode<string> * root;
Uses BinNode 294a.

(423b) 426e

root es, en ambas lases, la raz de un arbol de pre jos. 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

es root ontiene adenas (string) y no smbolos ASCII. La razon es que, en lugar de


smbolos puntuales, puede estable erse alguna fre uen ia de apari ion de frases enteras y
odi arse enteramente en una se uen ia de bits.
Ne esitamos una \tabla de smbolos" en la ual alma enemos sus fre uen ias de
apari ion y que nos permita onstruir un arbol de pre jos. El tipo de esta tabla se de ne
as:
hDe lara iones Hu man 424ai
(423 ) 424b
class Huffman_Node;

typedef DynMapTree<Treap_Vtl, string, Huffman_Node *> Symbol_Map;


De nes:
Symbol Map, used in hunk 426a.
Uses Huffman Node 424b and Treap Vtl 562d.

424b

La tabla mapea smbolos de tipo string a nodos de un heap que usaremos para
determinar optimamente los pre jos. 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 pre jos se onstruye a partir
de un heap del ual sus nodos son de tipo Huffman Node y se de nen de la siguiente forma:
hDe lara iones Hu man 424ai+
(423 ) 424a 425a
typedef BinNode< Aleph::pair<string, size_t> > Freq_Node;

4.13. C
odigos de Huffman

425

struct Huffman_Node : public BinHeap<size_t>::Node


{
BinNode<string> * bin_node;
Freq_Node * freq_node;
Huffman_Node() : BinHeap<size_t>::Node(0), bin_node(NULL), freq_node(NULL)
{
/* empty */
}
Huffman_Node(BinNode<string> * node)
: BinHeap<size_t>::Node(0), bin_node(node), freq_node(NULL)
{
/* empty */
}
~Huffman_Node() { /* No debe liberarse memoria de bin_node */ }
};
De nes:
Huffman Node, used in hunks 424a, 425b, 429, 433, 435, and 436a.
Uses bin node 429b and BinNode 294a.

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 pre jos.
2. freq node: que un nodo auxiliar usado para generar dibujos de arboles de pre jos .
22

425a

El tipo de heap se de ne de la siguiente manera:


hDe lara iones Hu man 424ai+

(423 ) 424b 425b

typedef BinHeap<size_t> Huffman_Heap;

425b

En fun ion de este tipo, de nimos las siguientes fun iones auxiliares:
hDe lara iones Hu man 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

parti ular los dibujados en este texto.


Captulo 4. Arboles

426

De nes:

get freq, used in hunk 429b.


increase freq, used in hunk 435.
set freq, used in hunk 429b.
Uses Huffman Node 424b and huffman node 429b.

426a

Ahora podemos de nir 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;
De nes:
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 de ni ion del
siguiente tipo:
hDe lara iones Hu man 424ai+
(423 ) 425b 431a
typedef DynMapTree<Treap_Vtl, string, BitArray> Code_Map;
Uses BitArray 36 and Treap Vtl 562d.

426

426d

hmiembros privados de odi ador 423di+


Code_Map code_map;
De nes:
code map, used in hunks 430 and 437.

(423a) 426a 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

su de ni ion oayuda a determinar uando ulmina una se uen ia de bits orrespondiente


a un texto odi ado. text len en la longitud del texto sin odi ar.
hmiembros privados de de odi ador 423ei+
(423b) 423e
string end_symbol;

426f

De nidas las estru turas de datos y otros atributos, estamos prestos para de nir 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:

publi os de de odi ador 427ai

427

(423b) 427

Huffman_Decoder_Engine(BinNode<string> * p, const string & end)


: root(p), end_symbol(end)
{
// empty
}
Uses BinNode 294a and Huffman Decoder Engine 423b.

427b

En ada una de las lases podemos ono er la raz del arbol de pre jos mediante el
observador:
hObservador de 
arbol de pre jos 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 pre jos 427bi

(423b) 427a 428

427d

hmiembros p
ubli os de odi ador 426f i+
hObservador de 
arbol de pre jos 427bi

(423a) 426f 429

4.13.2

427e

Decodificaci
on

Al a to que, objetivamente, hemos denominado \interpretar", lo podemos denotar omo


\de odi ar". De este modo, asumiendo una raz root de un arbol de pre jos y un puntero p, de la siguiente forma:
hDe odi a i
on 427ei
BinNode<string> * p = root;
Uses BinNode 294a.

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;

for (int i = 0; i < bit_stream_len; ++i)


{
const int bit = bit_stream.read_bit(i);
if (bit == 0)
p = LLINK(p);
else
p = RLINK(p);


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 pre jo 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

void decode(BitArray &


bit_stream,
const size_t & bit_stream_len,
ostream &
output)
{
hDe odi ar 427f i
}
De nes:
decode, never used.
Uses BitArray 36.

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

Dado un onjunto de n smbolos, queremos onstruir un arbol de odigos de exa tamente n


hojas. Ha iendo abstra ion de que estas hojas fungen de nodos externos, enton es, por la
proposi ion 4.3, ualquier arbol de n 1+ n = 2n 1 nodos, on ualquier permuta ion de
smbolos omo hojas onstituye
un arbol de odigos. Por la proposi ion 4.12, sabemos que

1 2(2n1)
existen C2n1 = 2n 2n1 diferentes arboles de odigo. Surgen, enton es, las preguntas:
> ual es oger? > omo onstruirlo?
Hu man [9 des ubrio un metodo para onstruir un arbol de pre jos el ual parte de
un onjunto ini iado on los smbolos y sus fre uen ias de apari ion. Es de ir, un onjunto
23 Notemos

que un pre jo guarda remembranza on el numero de Deway de la hoja.

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());

Huffman_Node * r_huffman_node = // nodo derecho


static_cast <Huffman_Node *> (heap.getMin());
De nes:
l huffman node, used in hunks 429 and 432a.
r huffman node, used in hunks 429 and 432a.
Uses Huffman Node 424b.

429b

Con estos dos nodos, se onstruye un sub-arbol de Hu man de la siguiente forma:


h rear un nuevo nodo de Hu man 429bi
(429 )
BinNode <string> * bin_node = new BinNode <string>;

Huffman_Node * huffman_node = new Huffman_Node (bin_node);


LLINK(bin_node) = l_huffman_node->bin_node;
RLINK(bin_node) = r_huffman_node->bin_node;
const size_t new_freq = get_freq(l_huffman_node) + get_freq(r_huffman_node);
Aleph::set_freq(huffman_node, new_freq);
De nes:
bin node, used in hunks 424b, 429 , and 432a.
huffman node, used in hunks 425b, 429 , 432a, 433, 435, and 436a.
Uses BinNode 294a, get freq 425b, Huffman Node 424b, l huffman node 429a, LLINK 296,
r huffman node 429a, RLINK 296, and set freq 425b 433.

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 Hu man y no de tipo Huffman Node.
La manera de nitiva para onstruir un arbol de pre jos 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

un nuevo nodo de Hu man

hConstruir

429bi

arbol auxiliar de fre uen ias

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;
}
De nes:

generate huffman tree, used in hunk 436b.


Uses bin node 429b, BinNode 294a, Huffman Node 424b, huffman node 429b, l huffman node 429a,
and r huffman node 429a.

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 Hu man.

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 Hu man. Las ra es de los sub-arboles se en uentran en el heap.

430

Una vez que se genera el arbol de Hu man, debemos, a efe tos de odi ar, generar el mapeo de smbolos a odigos pre jos. 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);
}
De nes:

build prefix encoding, never used.


Uses BinNode 294a, BitArray 36, code map 426 , is leaf 325a 431a, LLINK 296, and RLINK 296.

431a

hDe lara iones Hu man 424ai+


static inline bool is_leaf(BinNode<string> * p)
{
return LLINK(p) == NULL and RLINK(p) == NULL;
}
De nes:
is leaf, used in hunks 324{26, 329, 363, 364a, 427f, and 430.
Uses BinNode 294a, LLINK 296, and RLINK 296.

(423 ) 426b

La rutina se orresponde on un re orrido pre jo, re ursivo, del arbol de Hu man on


raz root. A medida que se va re orriendo, el pre jo de root se va manteniendo en el
arreglo de bits array y su longitud se mantiene en el parametro len. Cuando se al anza
una hoja, es de ir, uando ya tenemos el pre jo ompleto del smbolo, insertamos en el
mapeo code map la dupla <smbolo, array>.
build prefix encoding() es a tivada por la siguiente primitiva:
hmiembros privados de odi ador 423di+
(423a) 430 434
24

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 pre jo es una palabra de Di k (vease x 4.8.0.9 (pagina 377)).


Captulo 4. Arboles

432

build_prefix_encoding(root, array, 0);


}
De nes:
build prefix encoding, never used.
Uses BitArray 36, computeHeightRec 305a, and symbol map 426a.

432a

La onstru ion del arbol de fre uen ias, que es op ional, es similar a la del arbol de
pre jos 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;
}

El orden de manipula ion de la lase Huffman Encoder Engine es omo sigue:


1. De nir smbolos on sus fre uen ias.
2. Generar arbol de pre jos.
3. Codi ar textos.
En esta se ion hemos realizado la segunda fase. La primera sera expli ada en la siguiente
sub-se ion, mientras que la ter era sera expli ada en x 4.13.5.
4.13.4

433

Definici
on de smbolos y frecuencias

Antes de onstruir el arbol de pre jos 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 de nir 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

dupli idad del smbolo n de entrada 434bi

// 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();
}
De nes:


Captulo 4. Arboles

434

set freq, used in hunk 429b.


Uses BinNode 294a, Huffman Node 424b, huffman node 429b, S 794 , and symbol map 426a.

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 pre jos 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 de nir 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

hmiembros privados de odi ador 423di+


bool test_end(const string & str) const
{
if (end_symbol == "NO-END")
return false;

(423a) 431b 435

return end_symbol == str;


}
De nes:
test end, used in hunks 434b and 436a.

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:

static const size_t Max_Token_Size = 256;


public:
void read_input(char * input, const bool & with_freqs = false)
{
hVeri ar si 
arbol ha sido generado 434ai
char * curr_stream = input;
char curr_token[Max_Token_Size];
curr_token[1] = \0;
while (*curr_stream != \0)
{
curr_token[0] = *curr_stream++;

4.13. C
odigos de Huffman

435

update_freq(curr_token);
text_len++;
}

hGenerar

arbol 436bi

void read_input(ifstream & input, const bool & with_freqs = false)


{
arbol ha sido generado 434ai
hVeri ar si 
char curr_token[2];
curr_token[0] = curr_token[1] = \0;
while (not input.eof())
{
input.read(curr_token, 1);
update_freq(curr_token);
text_len++;
}
hGenerar

}
De nes:

arbol 436bi

read input, never used.


Uses update freq 435.

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

dupli idad del smbolo n de entrada 434bi

Huffman_Node * huffman_node = NULL;


Huffman_Node ** huffman_node_ptr = symbol_map.test(str);
if (huffman_node_ptr == NULL) // Fue definido previamente el s
mbolo?
{ // No ==> crear una entrada en symbol_map e insertarlo en heap
auto_ptr<BinNode<string> >
bin_node_auto ( new BinNode<string> (str) );
huffman_node = static_cast<Huffman_Node*>
(heap.insert(new Huffman_Node(bin_node_auto.get())));


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);
}
De nes:
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;
}
De nes:
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 Hu man, 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 de nido 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 pre jos 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 pre jos 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;
}
De nes:
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 pre jos, es muy sen illo: bus ar el smbolo
ledo en el mapeo y on atenar al arreglo de bits el pre jo. Tal tarea la realiza la primitiva


Captulo 4. Arboles

438

438a

privada append code(), la ual se instrumenta del siguiente modo:


hmiembros privados de odi ador 423di+
(423a) 435

static void append_code(BitArray & bit_stream, size_t & bit_stream_len,


const BitArray & symbol_code)
{
const size_t symbol_code_size = symbol_code.size();
if (bit_stream_len + symbol_code_size > bit_stream.size())
bit_stream.resize(5 * (bit_stream_len + symbol_code_size) / 4);
// concatenar los symbol_code_size bits en bit_stream
for (int i = 0; i < symbol_code_size; i++)
bit_stream[bit_stream_len++] = symbol_code[i];

}
De nes:

append code, used in hunk 437.


Uses BitArray 36 and resize.

438b

hVeri ar que exista 


arbol de pre jos 438bi
(437)
if (root == NULL)
throw std::domain_error("Huffman tree has not been generated");

Para el siguiente poema de Neruda:


Los Na imientos
Nun a re ordaremos haber muerto.
Tanta pa ien ia
para ser tuvimos
anotando
los numeros, los das,
los a~nos y los meses,
los abellos, las bo as que besamos,
y aquel minuto de morir
lo dejaremos sin anota ion:
se lo damos a otros de re uerdo
o simplemente al agua,
al agua, al aire, al tiempo.
Ni de na er tampo o
guardamos la memoria,
aunque importante y fres o fue ir na iendo;
y ahora no re uerdas un detalle,
no has guardado ni un ramo
de la primera luz.
Se sabe que na emos.
Se sabe que en la sala
o en el bosque
o en el tugurio del barrio pesquero
o en los a~naverales repitantes

hay un silen io enteramente extra~no,


un minuto solemne de madera
y una mujer se dispone a parir.
Se sabe que na imos.
Pero de la profunda sa udida
de no ser a existir, a tener manos,
a ver, a tener ojos,
a omer y llorar y derramarse
y amar y amar y sufrir y sufrir,
de aquella transi ion o es alofro
del ontenido ele tri o que asume
un uerpo mas omo una opa viva,
y de aquella mujer deshabitada,
la madre que all queda on su sangre
y su desgarradora plenitud
y su n y omienzo, y el desorden
que turba el pulso, el suelo, las frazadas,
hasta que todo se re oge y suma
un nudo mas el hilo de la vida,
nada, no quedo nada en tu memoria
del mar bravo que elevo una ola
y derribo del arbol una manzana os ura.
No tienes mas re uerdo que tu vida.
Pablo Neruda.

requiere 11056 bytes en ASCII y genera el siguiente arbol de Hu man

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

el ual lo omprime a 6035 bytes.


Optimaci
on de Huffman

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 a rmativamente 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 Hu man
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 de ne 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

Para el arbol de la gura 4.46-a tenemos:


wpl(T ) = 2 (7 + 6) + 3 (15 + 16 + 10 + 4) = 161

13
7
b
15


16
a

10
e

4
i

6
c
15



(a) Arbol
de odigos anterior

16
a

10
e

4
i

(b) Anterior sin hojas b y


Figura 4.46: Arboles
pre jos 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 Hu man", 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 pre jo onstruido
segun el algoritmo de Hu man (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 pre jos T es mnimo segun su longitud

ponderada del amino.

La prueba se onstruira por indu ion sobre la ardinalidad de T y se


valdra del siguiente lema:

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

wpl(T ) wpl(T ) w(n+)

(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

Hay tres posibles situa iones a onsiderar para n1, n2 T :

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))
|

el ual di ere exa tamente de wpl(T ) en w(n3).

{z

w(n3 )

2. n1 y n2 estan en el mismo nivel: asumamos s1 omo el hermano de n1 (el aso es


simetri o on n2). Enton es, podemos inter ambiar s1 on n2 y as deparar en el
aso anterior.
3. n1 y n2 estan en niveles distintos: al igual que en el punto anterior, asumamos s1
omo el hermano de n1. Del mismo modo, asumamos que nivel(n1) > nivel(n2).
Podemos pi torizar esta situa ion del siguiente modo:

n2

s1

p
s1

p
n1

n2

n1

inter ambiamos
n2

on

s1

Consideremos la in iden ia que tiene el subarbol s1 sobre wpl(T ), la ual puede


expresarse as:
X
(s1) =
w(nx) nivel (nx)
nx hoja(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 Hu man T :
1. Para |T | 2 el resultado es dire to.
2. Ahora asumamos que la proposi ion es ierta para todo arbol de Hu man y veri

quemos su vera idad para Tn+1; es de ir Tn+1


6= Tn+1 = wpl(Tn+1) < wpl(Tn+1
).
Sean n1 y n2 las dos primeras hojas que sele iona el algoritmo de Hu man. Ahora

onsideremos apli ar el lema 4.5 a Tn+1 y Tn+1


on los nodos n1 y n2 y produ ir dos

arboles Tn y Tn.
Puesto que n1 y n2 son los nodos on menos peso, estos son hermanos en el arbol
de Hu man. Por tanto wpl(Tn) = wpl(Tn+1) w(n1) w(n2) .

Para el arbol Tn sabemos, segun el lema 4.5, que wpl(Tn ) wpl(Tn+1


) (w(n1) +

|. 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 Hu man 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 Hu man. Sin embargo, aqu apare e un detalle interesante: para odi ar o de odi ar el texto no ha e
falta el arbol de Hu man. 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 pre jos.
Pero, > omo hara el de odi ador? La respuesta esta dada por la teora de pre jos 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 pre jo. Esto impli a que, dada una se uen ia de pre jos, 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

se eviden io en la primera parte de la demostra ion del lema 4.5.


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 de ni 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-

tud del amino interna ponderada se de ne omo:


Ci(T ) =

n
X

(nivel (ni) + 1)pi

(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 in jo 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 de ni ion 4.13, plantear Ci en fun ion de la
27
raz kl, del siguiente modo :
Ci(T ) = pl +

l1
X

(nivel (ki) + 1)pi +

i=1

n
X

(nivel (ki) + 1)pi

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

Objetivo del problema

Dado un arreglo ordenado K = [k1, k2, . . . , kn] de n laves y un arreglo paralelo


P = [p1, p2, . . . , pn] de probabilidades de a eso, el problema onsiste, re ursivamente
hablando, en determinar ual lave kl, entre las n posibles, debe olo arse omo raz del
arbol nal T . Una vez que se reali e esta sele ion, el resto de las laves que iran en L(T )
y R(T ) queda totalmente de nido, pues estas deben satisfa er la propiedad de orden de
un arbol binario. Los subarboles se onstruiran re ursivamente apli ando el mismo prin ipio sobre [k1, k2, . . . , kl1] y [p1, p2, . . . , pl1] para L(T ) y [kl+1, . . . , kn] y [pl+1, . . . , pn],
para R(T ).
P
Esto nos ondu e a una de ni ion re ursiva del osto: sea pi,j = jl=i pl y Ci,j el
osto total del arbol binario optimo ompuesto por las laves que van desde ki hasta kj.
Enton es:
j

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 re ere 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

Implantamos nuestro algoritmo en el ar hivo hopBinTree.H 445i uya estru tura es la


siguiente:
hopBinTree.H 445i
on ma ros opBinTree 446bi
hDe ni i
hPrototipos

fun iones auxiliares opBinTree

(never de ned)i

hFun iones auxiliares opBinTree 446di


template <class Node, typename Key>
Node * build_optimal_tree(Key keys[], double p[], const int & n)
{
hDe lara i
on de matri es internas 446ai
compute_optimal_costs(cost, p, n, tree);
return compute_tree<Node, Key> (keys, tree, n, 1, n);
}
hInde ni i
on
De nes:

ma ros opBinTree 446 i

build optimal tree, never used.


Uses compute tree 448 and cost 446a.

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));
De nes:
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

requeridos para onstruir el arbol optimo.


El a eso omo matriz a una entrada de un arreglo dinami o se de ne del siguiente
modo:
hDe ni i
on ma ros opBinTree 446bi
(445)
# define COST(i,j) (cost[(i)*(n+1) + (j)])
# define TREE(i,j) (tree[(i)*(n+1) + (j)])
De nes:
COST, used in hunks 446 and 447.
TREE, used in hunks 446{48.
Uses cost 446a.

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 de nitivo 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 inde nido al nal del ar hivo:
hInde ni i
on ma ros opBinTree 446 i
(445)
# undef COST
# undef TREE
Uses COST 446b and TREE 446b.

446d

Ahora de nimos 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
}
De nes:

ostes optimos 447ai

compute optimal cost, never used.


Uses cost 446a and DynArray 45.

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

for (i = 1; i <= n; ++i)


{
COST(i, i - 1) = 0;
TREE(i, i) = i;
}
Uses COST 446b and TREE 446b.

447a

El segundo bloque es el que al ula los ostes de nitivos. 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

sum p() efe t


ua la suma de las probabilidades entre j y k y se de ne omo sigue:
hFun iones auxiliares opBinTree 446di+
(445) 446d 447
static inline double sum_p(double p[], const int & i, const int & j)
{
double sum = 0.0;
for (int k = i - 1; k < j; ++k)
sum += p[k];
return sum;
}
De nes:
sum p, used in hunk 447a.

447

min index() retorna un ndi e l, omprendido entre j y k, tal que COST(j, l - 1) +


COST(l + 1, k) sea mnimo. Este valor se al ula mediante barrido dire to de l entre j y
k:
hFun iones auxiliares opBinTree 446di+
(445) 447b 448
static inline int min_index(DynArray <double>& cost,
const int & j, const int & k, const int & n)
{
int ret_val;
double min_sum = 1e32;
double sum;
for (int i = j; i <= k; ++i)
{
sum = COST(j, i - 1) + COST(i + 1, k);
if (sum < min_sum)


Captulo 4. Arboles

448

{
min_sum = sum;
ret_val = i;
}
}
return ret_val;
}
De nes:

min index, used in hunk 447a.


Uses COST 446b, cost 446a, and DynArray 45.

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)

4.15. Notas bibliogr


aficas

449

Node * root = new Node (keys[TREE(i, j) - 1]);


LLINK(root) = compute_tree <Node, Key>(keys, tree, n, i, TREE(i, j) - 1);
RLINK(root) = compute_tree <Node, Key>(keys, tree, n, TREE(i, j) + 1, j);
return root;
}
De nes:

compute tree, used in hunk 445.


Uses DynArray 45, LLINK 296, RLINK 296, and TREE 446b.

4.15

Notas bibliogr
aficas

Se pudiera de ir que la no ion matemati a de arbol y sus on eptos fundamentales son


resultado de varias eras y genera iones en la teora de onjuntos. El volumen 1 del \Art
of omputing programming" de Knuth [10, re opila los indi ios bibliogra os sobre los
orgenes matemati os de los arboles. Del mismo modo, Knuth es una de las mejores referen ias para ompletar las matemati as aso iadas a los arboles presentadas en este texto
y a otros tipos de arboles que no estudiamos. Desarrollos matemati os mas profundos
pueden en ontrarse en un texto formal sobre teora de grafos. En la o urren ia, re omendamos el lasi o de Harary, \Graph Theory" [6. Desarrollos re ientes pueden en ontrarse
en el texto \Combinatorial Algorithms" de Kreher y Stinson [12.
Los odigos de un arbol binario, el lenguaje de Lukasiewi z y la elebre nota ion pola a,
fueron estudiados por el matemati o pola o Jan Lukasiewi z [15. El orpus de ompresion
homonimamente llamado \de Hu man" fue des ubierto por David Hu man en 1952, en
aquel enton es \estudiante o ial". Interesantemente, Hu man jamas quiso patentar sus
des ubrimientos.
Para una revision y analisis de la teora de odigos de arbol binario, re omendamos la
obra de Berstel y Perrin [2.
Segun Knuth [11 los arboles binarios de busqueda fueron des ubiertos independientemente por varios investigadores durante la de ada de 1950. De nuevo, el le tor es remitido
a la obra de Knuth [11 para onsultar las referen ias exa tas.
Todos los libros de estru turas de datos onsagran varios aptulos a los arboles. En
ese sentido, ualquier otro texto sirve de referen ia adi ional. Sin embargo, en lo que a
arboles se re ere, ningun libro, in luido por supuesto el presente, puede onsiderarse ompleto. Un texto re iente, \Data Stru tures using C and C++" de Langsam, Augenstein
y Tanenbaum [13, exhibe amplias re exiones pra ti as y presenta apli a iones reales,
notablemente, la evalua ion de expresiones aritmeti as mediante arboles hilados.
Los arboles hilados fueron des ubiertos por A. J. Perlis y C. Thornton [16. Aunque
su uso es di ultoso, esta lase de arbol es usada en apli a iones estadsti as, editores de
textos y apli a iones gra as que manejen grandes volumenes de informa ion.
Los arboles on rango presentados en x 4.11 fueron desarrollados por este reda tor
para el presente trabajo omo instrumento dida ti o; on seguridad han sido desarrollados
previamente. Las ideas sobre el uso de rangos para la ve toriza ion de arboles es presentada
para una lase de arbol equilibrado denominado AVL por Crane [4. Aparentemente, el
trabajo de Crane es pionero en los algoritmos de on atena ion y parti ion.


Captulo 4. Arboles

450

Los odigos de Hu man son homonimos de David A. Hu man [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. Modi que 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

6. Dise~ne un algoritmo que uente el numero de nodos en un arbol n-ario representado


mediante listas enlazadas.
7. Dise~ne un algoritmo que re orra en pre jo un arbol representado on listas.
8. Dise~ne un algoritmo que re orra en su jo un arbol representado on listas.
9. Dise~ne un algoritmo que es riba la representa ion parentizada de un arbol.
10. Dise~ne un algoritmo que onvierta un arbol representado on listas enlazadas en
su equivalente representado on arreglos. Reali e dos respuestas: la primera onsiderando ono ido el orden del arbol, la segunda onsiderandolo des ono ido.
11. Con iba omo delimitar el re orrido pre jo para que ontenga la informa ion requerida para re onstruir el arbol original.
12. Con iba omo delimitar el re orrido su jo para que ontenga la informa ion requerida para re onstruir el arbol original.
13. Dise~ne un algoritmo que onstruya un arbol binario a partir de los re orridos in jo
y su jo.
14. Dise~ne un algoritmo que onstruya un arbol binario a partir de los re orridos pre jo
y su jo. (+)
15. Modi que el re orrido por niveles level order (x 4.4.12 (pagina 308)) para que use
una fun ion de visita que re iba el nivel y la posi ion dentro del re orrido.
16. Dise~ne un algoritmo no re ursivo que opie de arboles binarios. (+)
17. Dise~ne un algoritmo no re ursivo que destruya de arboles binarios. (+)
18. Dise~ne un algoritmo que re orra en pre jo un arbol binario hilado.
19. Dise~ne un algoritmo que re orra en su jo un arbol binario hilado.
20. Dise~ne un algoritmo que hile un arbol binario. La entrada es un arbol binario normal;
la salida es el mismo arbol, pero sus nodos estan hilados segun 4.4.15.
21. Dado un arbol binario, dise~ne un algoritmo que efe tue la opia hilada.
22. Suponga un arbol binario uyos nodos tienen un ampo adi ional llamado PLINK y
que denota el nodo padre. Dado un nodo ualquiera p, dise~ne algoritmos no re ursivos
y sin pila para:
(a) Determinar el nodo prede esor y,
(b) Determinar el nodo su esor.
23. Los re orridos pseudo-hilados no onsideran la posi ion del nodo en el re orrido ni
su nivel. Modi que las fun iones en uestion para que se in luyan estos parametros.
(++)
24. Dise~ne una rutina re ursiva que re orra por niveles un arbol binario y use espa io O(1). (+)


Captulo 4. Arboles

452

25. Dise~ne un algoritmo no re ursivo que efe tue el re orrido pre jo 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 su jo 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

Dibuje la arbores en ia.


28. De na e implante la similaridad entre dos arboles espe i ados mediante
el TAD Tree Node<T>.
29. De na e implante la equivalen ia entre dos arboles espe i ados mediante
el TAD Tree Node<T>.
30. Dise~ne un algoritmo re ursivo que implante la opia de un arbol espe i ado mediante el TAD Tree Node<T>.
31. Dise~ne un algoritmo no re ursivo que implante la opia de un arbol espe i ado
mediante el TAD Tree Node<T>.
32. Dise~ne un algoritmo no re ursivo que reali e la busqueda por numero de Deway.
33. Dibuje la arbores en ia equivalente al siguiente arbol binario:
9
6
4
1

21
7

16
8

10

18

13
3

22

11

17

24
19

14
12

23
20

15

34. Dibuje el arbol binario equivalente a la arbores en ia siguiente:

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

35. Complete la solu ion de la e ua ion (4.29). (++)


36. Complete la solu ion de la e ua ion (4.30). (+)
37. Sea C(ni) la ardinalidad del arbol binario uya raz es ni. Demuestre que:
IPL(T ) =

C(ni)

ni T|ni interno

38. Generali e la expresion anterior para arboles m-rios.


39. Es riba un algoritmo que transforme un arbol m-rio ompleto, representado on
listas, en un arreglo se uen ial.
40. Es riba un algoritmo que transforme un arbol binario ompleto en un arreglo se uen ial.
41. Es riba una version no re ursiva del algoritmo que al ula la altura de un arbol
binario.
42. Considere las siguientes lneas del algoritmo presentado en x 4.4.9 para la opia de
un arbol binario:
I(RLINK(tgt_root) == Node::NullPtr);
if (LLINK(tgt_root) != Node::NullPtr)
destroyRec(LLINK(tgt_root));
delete tgt_root;

(a) >Por que el aserto tiene sentido?


(b) El if y la destru ion del subarbol izquierdo son redundantes, >por que?
43. Es riba una version re ursiva del algoritmo de destru ion mostrado en x 4.4.10 que
libere los nodos en pre jo.
44. Es riba una version re ursiva del algoritmo de destru ion mostrado en x 4.4.10 que
libere los nodos en in jo.

30

31


Captulo 4. Arboles

454

45. Es riba una version no re ursiva, su jo, 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 su jo mediante pseudo-hilado (++).
48. Modi que el re orrido in jo pseudo-hilado para obtener un re orrido in jo inverso;
es de ir, el re orrido in jo 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 pre jo.
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 de nido
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 su jo.
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. Modi que 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 in jo 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 pre jos de arboles binarios de busqueda:
(a)
(b)
( )
(d)
(e)

1058 388 100 19 83 126


1156 3 185 119 285 225
868 527 240 11 230 562
898 206 173 151 136 43
112 60 80 1120 625 139

161 944
810 409
580 605
192 218
277 266

549
783
741
850
942

455 681 599 937 1186 1333.


420 1003 975 1351 1290
972 901 1450 1270 1164 1431
700 560 575 1091 1474 1325
909 981 967 1150 1439 1354

(a) Dibuje el arbol orrespondiente.


(b) Cal ule el IPL.
77. Dados los siguientes re orridos su jos de arboles binarios de busqueda:
(a)
(b)
( )
(d)
(e)

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

(a) Dibuje el arbol orrespondiente.


(b) Cal ule el IPL.
( ) Demuestre que la sumatoria planteada en (4.33) (x 4.7.4 (pagina 356)) es, en
efe to, O(n).
78. Considere un heap trinario; es de ir, un arbol trinario ompleto ( on la propiedad
triangular) tal omo:
1

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)

p, omo ya se dijo, es un nodo de un heap trinario. i es el ordinal del hijo; es de ir:

(a) i == 0: el hijo mas a la izquierda.


(b) i == 1: el hijo del entro.
( ) i == 2: el hijo mas a la dere ha.
Puede asumir, aunque eso no es esen ial al problema, que en ausen ia de un hijo
child() retorna NULL.
El objetivo de este problema es des ubrir un algoritmo que en uentre la se uen ia
de ordinales, parametros a child(), tales que a traves de llamadas su esivas se
en ontrara el ultimo elemento del heap trinario. Por ejemplo, para la gura anterior,
la se uen ia es 2, 0, 2, la ual, partiendo desde la raz 1 permite child(1, 2) = 4
child(4, 0) = 11 child(11, 2) = 34 las uales desembo an en el nodo
etiquetado on 34.
79. Generali e el problema anterior para heaps n-rios.
80. Explique omo puede ordenarse una lista doblemente enlazada mediante el heapsort
on onsumo de espa io O(1).
81. En base a la re exion del ejer i io anterior, implante el heapsort sobre listas de tipo
Dnode<T>.
82. Dise~ne un algoritmo que re orra una se uen ia de n elementos y en uentre los m n
menores elementos. Explique detalladamente omo se utiliza el heap para ontener
y mantener, exa tamente, m elementos a medida que se inspe iona la se uen ia.
83. Dise~ne un TAD DynArrayHeap<T> fundamentado en el TAD DynArray<T> (x 2.1.5 (pagina 44)).
Dis uta los tama~nos ade uados del dire torio, segmento y bloque. De ida un riterio
bueno para ortar el arreglo de manera tal que el onsumo de memoria sea mnimo.
84. Modi que el TAD BinHeap<Key> para que ada nodo pres inda del apuntador al
padre.
85. Modi que el TAD BinHeap<Key> para pres indir de la lista de hojas. Utili e permanentemente una se uen ia que albergue el numero de Deway de last, de manera
tal que ada modi a ion del heap la use para ubi ar los nodos a a tualizar.
86. Dise~ne un algoritmo que tome omo entrada el re orrido pre jo e in jo de un arbol
y retorne omo salida el re orrido por niveles. No se debe onstruir el arbol.
87. Dise~ne un algoritmo que re iba omo entrada el re orrido pre jo de un arbol binario
de busqueda y retorne su IPL. No se debe onstruir el arbol.
88. Dise~ne un algoritmo que re iba omo entrada el re orrido su jo de un arbol binario
de busqueda y retorne su IPL. No se debe onstruir el arbol.

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 in jo.
93. Para los siguientes pares de arboles representados on sus re orridos pre jos, onstruya el arbol produ to de la opera ion join().
(a)

 144 65 27 22 54 38 74 68 71 95 85 77 123 108 122

 5 138 46 21 33 70 55 125 86 78 76 72 109 129 143

(b)

 124 25 23 20 19 11 21 106 82 31 81 50 76 121 125

( )

 12 25 52 32 76 123 100 77 81 80 83 104 118 138 136

(d)

 40 13 11 4 19 24 122 66 64 43 54 74 77 115 146

(e)

 51 18 8 9 29 101 89 72 66 76 80 148 105 115 139

(f)

 123 47 45 15 33 112 97 96 80 107 105 109 121 122 149

 33 29 51 119 97 84 64 68 92 89 90 110 108 149 142

 62 37 31 9 26 46 61 131 110 84 69 105 116 129 143

 33 23 22 2 18 7 21 39 53 44 87 63 106 118 135

 5 128 85 54 23 45 71 57 123 103 91 95 119 126 140

 69 20 9 19 29 53 66 138 94 82 81 78 106 95 142

94. Dados 2 arboles binarios T1 y T2, se de ne la rela ion \es simetri o", denotada omo
T1 T2 omo:
T1 T2

T1 = T2 = = T1 T2

T1 6= T2 6= = raiz(T1) raiz(T2) L(T1) R(T2) L(T1) R(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. Modi que 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 in ja 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 in ja de la lave si esta se en uentra en el arbol,
o la posi ion in ja 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. Modi que 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. Modi que 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

arboles binarios diferentes que alma e-

132. Sea S una se uen ia odi ada segun el algoritmo de Hu man. Dise~ne un algoritmo
que lea la se uen ia, distinga los pre jos distintos y retorne el arbol de Hu man
orrespondiente a los pre jos. (+)
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

(b) 17 39 42 53 54 66 75 85 89 90 96 98 115 119 143


0.00047 0.00470 0.02194 0.06339 0.12678 0.18594 0.20660 0.17708 0.11806
0.06121 0.02449 0.00742 0.00165 0.00025 0.00002

( ) 6 8 12 40 59 62 65 69 90 103 109 118 128 134 138


0.00475 0.03052 0.09156 0.17004 0.21862 0.20613 0.14724 0.08113 0.03477
0.01159 0.00298 0.00058 0.00008 0.00001 0.00000

(d) 18 21 34 41 53 54 56 57 65 77 98 104 122 124 131


0.00000 0.00000 0.00001 0.00008 0.00058 0.00298 0.01159 0.03477 0.08113
0.14724 0.20613 0.21862 0.17004 0.09156 0.03052

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 de ne 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, de na la longitud del amino ponderada.
(b) Modi que 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

(a) Demuestre que si 1 j k n, enton es existen arboles optimos tal que


TREE(j,k - 1) TREE(j,k) TREE(j,k). Intuitivamente, esto quiere de ir
que a~nadir una nueva lave al extremo dere ho de la se uen ia no ausa que
la raz del arbol optimo se despla e ha ia la dere ha. Analogamente, su ede lo
mismo si la lave es insertada por el extremo izquierdo.
(b) Demuestre que la busqueda del subarbol optimo es restringida a TREE(j,k - 1)
TREE(j,k) TREE(j,k), enton es, para ada valor de i del algoritmo presentado en x 4.14, el for (j = ...) es O(n) y, por ende, el algoritmo global
es O(n2).
Ayuda: No se puede efe tuar la prueba si se bus a un lmite para el
for (j = ...) y enton es multipli ar por n. En su lugar, deben sumarse por
separado las longitudes de los rangos a bus ar.

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. Hu man. A method for the onstru tion of minimum redundan y odes. Pro .
I.R.E., 40:1098{1101, 1951.
[9 David A. Hu man. 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

Captulo 5. Tablas hash

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

Di ho lo anterior, podemos introdu ir formalmente el on epto de \Tabla hash" o de \tabla


de dispersion" :
3

Definici
on 5.1 (Tabla Hash) Sea un onjunto de laves K ir uns rito dentro de un
dominio K. Una tabla hash se de ne omo:

1. Un arreglo de tipo K de M entradas. Por lo general, |K| 6= M, aunque pudieran


oin idir.
2. Una fun ion h(k) : K [0, M 1] llamada \fun ion hash".
A un registro albergante de una lave en una tabla hash se le denomina cubeta o
su equivalente anglosajon bucket.
Mediante el arreglo y la fun ion h(k) : K [0, M 1] podemos usar una tabla hash
para implantar el problema fundamental de estru turas de datos. Sea A[M] un arreglo de
pares de tipo K {0, 1}; es de ir, A[i].key indi a la lave, mientras que A[i].busy indi a
si la entrada esta o upada o no. Enton es, grosso modo, las tres opera iones basi as se
realizan del siguiente modo:
Inserci
on de clave k:

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.

5.1. Manejo de colisiones

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 ri que el tiempo O(1) lamado para una tabla hash.

5.1

Manejo de colisiones

Como ya lo hemos se~nalado, si no se ono e el onjunto de laves que se insertaran en la


tabla, enton es son posibles las olisiones. >Que tan probables son esta? Una mirada a un
elebre problema nos indi ia su probabilidad.
5.1.1

La paradoja del cumplea


nos

Real-lizaremos la probabilidad de olision mediante un lasi o problema de la teora de


probabilidades. Supongamos un grupo de n personas, >que tan probable es que dos personas tengan la misma fe ha de umplea~nos?.
Los textos de probabilidades ofre en una solu ion pre isa a este problema. En aras de
la simpli idad y del ahorro de espa io, aqu nos abo aremos mediante una va mas orta,
pero mas impre isa.

468

Captulo 5. Tablas hash

Sea una xi,j una variable aleatoria de nida del siguiente modo:
xi,j =


0
1

si las personas i y j no umplen a~nos el mismo da


si las personas i y j umplen a~nos el mismo da

(5.1)

Ahora de namos la siguiente variable aleatoria:


X=

(5.2)

xi,j

i6=i

Cuya esperanza E(X), en virtud de las de ni iones planteadas, nos propor iona la antidad
esperada de pares de personas que umplen a~nos el mismo da.
Por de ni 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 de ni 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

No sorprendentemente, algunos in urren en el error de pensar que se trata de una


probabilidad ondi ional, lo ual no es el aso. Para aprehenderlo, imaginemosnos que nos
en ontramos en un grupo y nos preguntamos > uan probable es que tu umplas a~nos el
mismo da que yo? La respuesta es 1/365, pues, al yo ono er mi fe ha de umplea~nos
revela el su eso de interes probable entre todos los 365 posibles.
As pues:
 
n 1
n(n 1)
E(X) =
(5.6)
=
.
2 365

730

Lo ual, al plantear la desigualdad E(xi,j) 1 nos arroja omo ra es:

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. Manejo de colisiones

5.1.2

469

Estrategias de manejo de colisiones

Consideremos un onjunto generi o de laves olindantes Kc = {k1, k2, . . . , kn}; o sea,


ki, kj Kc = h(ki) = h(kj). Esen ialmente, existen dos te ni as para lidiar on onjuntos de olisiones:
Encadenamiento : las olisiones se enlazan en una o varias listas enlazadas fuera o dentro

de la misma tabla.

Direccionamiento abierto 4 : las olisiones se olo an dire tamente dentro de la propia

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

En su forma mas simple, el en adenamiento separado se implanta mediante una tabla


de punteros a listas simplemente enlazadas, uya representa ion puede visualizarse del
siguiente modo:
0

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

es la tradu ion literal del termino usado en ingles \open addresing".

470

470a

Captulo 5. Tablas hash

La gura muestra un onjunto de 12 laves ki donde i representa el orden de inser ion.


Cada entrada en el arreglo es un apuntador al primer elemento de la lista enlazada de
olisiones.
Todas las opera iones requieren al ular j = h(k) mod M, donde k es una lave; de
esta manera, la entrada tabla[j] es el puntero a la lista enlazada. Un valor de apuntador
nulo indi a que la lista esta va a; es de ir, que no hay ningun elemento aso iado al ndi e j
en el arreglo. De lo ontrario, existe una lista enlazada de laves olindantes en adenadas.
En el ejemplo la entrada 11 muestra dos laves k7, k4 uyo valor de fun ion hash arrojo el
mismo resultado (11).
En ALEPH este tipo de tabla esta implantado en el ar hivo htpl lhash.H 470ai, ual
implanta y exporta varias lases parametrizadas, de las uales, la mas importante es:
htpl lhash.H 470ai
template <typename Key, class BucketType>
class GenLhashTable
{
typedef BucketType Bucket;

on hash 472ai
hPrototipo de puntero a fun i
hMiembros dato de GenLhashTable 472bi
etodos
hM
hCubetas
De nes:

publi os de GenLhashTable 473ai

de LhashTable<Key> 470bi

GenLhashTable, used in hunk 471.

GenLhashTable<Key, BucketType> implanta todo el manejo generi o de la tabla hash


en la ual las ubetas son de tipo BucketType.
Cubetas

470b

La manera mas simple de manejar una ubeta a en adenar separadamente es mediante


el tipo Dnode<T> estudiado en x 2.4.8 (pagina 106):
hCubetas de LhashTable<Key> 470bi
(470a) 471
template <typename Key>
class LhashBucket : public Dnode<Key>
{
LhashBucket(const LhashBucket & bucket) : Dnode<Key>(bucket) { /* empty */ }
LhashBucket() { /* Empty */ }
LhashBucket(const Key & key) : Dnode<Key>(key) { /* Empty */ }
Key & get_key() { return this->get_data(); }
};
template <typename Key>
class LhashBucketVtl : public Dnode<Key>
{
LhashBucketVtl(const LhashBucketVtl & bucket) : Dnode<Key>(bucket)

5.1. Manejo de colisiones

471

{ /* empty */ }
LhashBucketVtl() { /* Empty */ }
virtual ~LhashBucketVtl() { /* Empty */ }
LhashBucketVtl(const Key & key) : Dnode<Key>(key) { /* Empty */ }
Key & get_key() { return this->get_data(); }
};
De nes:
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 de nida 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 de ne el operador
Record::operator==(const Record&) para que solo ompare la lave de tipo Key.
La tabla hash puede de nirse 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 de nen por espe ializa ion de
GenLhashTable:
471
hCubetas de LhashTable<Key> 470bi+
(470a) 470b
template <typename Key>
class LhashTable : public GenLhashTable<Key, LhashBucket<Key> >
{
};

template <typename Key>


class LhashTableVtl : public GenLhashTable<Key, LhashBucketVtl<Key> >
{
};
De nes:
LhashTable, used in hunks 501{3 and 529.
LhashTableVtl, never used.
Uses GenLhashTable 470a, LhashBucket 470b, and LhashBucketVtl 470b.

2. Se deriva una lase Derivada de ualquiera de las lases ubetas LhashBucket<Key> o


LhashBucketVtl<Key> en donde se oloquen los datos que se requieran aso iar al tipo
Key.
En este aso la tabla hash usara el operador de igualdad Key::operator==(const Key&).
La tabla hash se instan ia mediante LhashBucket<Key> o LhashBucketVtl<Key>, segun se
requiera o no destru tores virtuales. La inser ion y la elimina ion a eptan ubetas de tipo

472

Captulo 5. Tablas hash

Derivada, pues son ubetas por heren ia. La b


usqueda retorna una ubeta de la familia
LhashBucket<Key>, la ual debe onvertirse expl itamente5 a Derivada si se requiere
ono er la ubeta Derivada.
Atributos de LhashTable<Key>

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

El tipo LhashTable<Key> mantiene, pues, un puntero de este tipo:


hMiembros dato de GenLhashTable 472bi
(470a) 472
Hash_Fct hash_fct;

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 de ne de la siguiente manera:
hMiembros dato de GenLhashTable 472bi+
(470a) 472b 472d
typedef Dnode<Key> BucketList; // Tipo lista de cubetas

typedef typename Dnode<Key>::Iterator BucketItor; // Iterador de lista cubetas


typedef Dnode<Key> Node; // Sin
onimo de nodo
BucketList * table;
Uses BucketItor, BucketList, and Dnode 106a.

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 de nen 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

5 Mediante static cast<Derivada*>(cubeta).

5.1. Manejo de colisiones

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

Captulo 5. Tablas hash

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.

El desempe~no de esta rutina depende de la longitud de la lista table[i].


Eliminaci
on
474b

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());

bucket->del(); // eliminar de su lista de colisiones


if (next->is_empty()) // la lista devino vac
a?
--busy_slots_counter; // s
==> actualizar contador de listas ocupadas
--N;
return bucket;
}
Uses busy slots counter.

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.

5.1. Manejo de colisiones

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

de ubetas que se inspe ionan en una busqueda fallida es:


UN = =

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

Captulo 5. Tablas hash

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).

5.1. Manejo de colisiones

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 :

1. Si la busqueda es fallida, enton es el lema 5.2 apli a y la esperanza es:


= O(1)

(5.10)

2. Si la busqueda es exitosa, enton es el lema 5.3 apli a y la esperanza es:


1+

= 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).


Respe to al TAD LhashTable<Key>, la demostra ion solo ata~ne a la busqueda, pues,


por su fun ionalidad, en este TAD tanto la inser ion omo la elimina ion son O(1).
Una de las grandes ventajas del en adenamiento separado es que el numero de elementos N puede ser mayor que el tama~no de la tabla M. La inser ion no tiene, enton es,
porque preo uparse por el desborde, lo que nos propor iona exibilidad de ir unstan ias.
De los enfoques tradi ionales para manejar olisiones, este es el uni o que permite N > M
sin que se degrade el rendimiento esperado.
Notemos que el desempe~no del en adenamiento separado es una predi ion, >que tan
probable ella es?. Un indi io de respuesta nos las propor iona la varianza; la ual, es
fa ilmente al ulable de la prueba del lema 5.1 en la que determinamos que la longitud de
1
una lista de olisiones se distribuye binomialmente on parametro p = M
y N antidad
de ensayos. Por tanto, la varianza es:
var = 2 =



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

Al igual que en el en adenamiento separado, en esta estrategia las olisiones se en adenan


en listas enlazadas, pero la diferen ia estriba en que las ubetas residen en la propia tabla.

478

Captulo 5. Tablas hash

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

La busqueda es esen ialmente identi a a la del en adenamiento separado.

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

ingles, a la busqueda de una ubeta on status EMPTY se le denomina \probing". \Sondear" es el


termino que usaremos en astellano para referirnos a la palabra anterior.

5.1. Manejo de colisiones

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

Captulo 5. Tablas hash

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.

5.1. Manejo de colisiones

481

pare ido a un enfoque errado?.


2. En el en adenamiento errado y demas estrategias que oloquen olisiones dentro
de la misma tabla, el sondeo lineal se realiza sobre ubetas que estan ontiguas en
memoria, lo que aprove ha el a he del hardware. Por el ontrario, on el en adenamiento separado, las ubetas estan en dire iones de memoria dis ontinuas. Por
onsiguiente, para el mismo onjunto de olisiones, su inspe ion dentro de la propia
tabla tiende a ser onsiderablemente mas rapida que su inspe ion a lo largo de una
adena de ubetas dis ontinuas en la memoria, pues el primero aprove ha el a he y
el otro no.
5.1.4

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

Sondeo ideal (sondeo uniforme)

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

Captulo 5. Tablas hash

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

Enun iemos UN de la siguiente manera:


UN =

Suma de la antidad de sondeos de i ubetas en busqueda fallida


Total de on gura iones posibles de la tabla hash

i
N

(5.17)

a las N laves insertadas,


Si tenemos M ubetas numeradas entre 1 y M las repartimos
M
enton es el total de maneras posibles de ha erlo es N ; o sea, el total de on gura iones
posibles de la tabla hash (N).
Sea i la antidad de ubetas que se inspe ionan en una busqueda fallida on sondeo
ideal.
Para que se onsuman i sondeos, las primeras i 1 ubetas deben tener status BUSY,
mientras que el status de la ultima debe ser EMPTY. En i sondeos se revisan i 1 ubetas
que ontienen laves mientras que la i-esima ubeta sondeada esta va a. La antidad de
disposi iones en que se sondean i 1 laves es equivalente a la antidad de maneras de
Mi
distribuir las N i + 1 laves restantes en las M k ubetas restantes ; es de ir Ni+1
para una disposi ion de laves parti ular. El total posible es la suma para todos los valores
posibles de i:
8

 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)

pues de los k elementos ombinados siempre sobran n k.


uestion se se de ne omo

`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.

5.1. Manejo de colisiones

483

(A) es al ulable dire tamente por la propiedad de la sumatoria del ndi e superior:

(A) = (M 1)

M
MN

El uanto a (B), podemos absorber


(M N 1):

10

 
M
(por simetra)
= (m 1)
N

(5.19)

el fa tor (i 1) si multipli amos y dividimos por



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)

Sustituyendo (A) y (B) en (5.18):


 


M
M+1
i = (M 1)
(M N)
N
N
M!
(M + 1)!
= (M 1)
(M N)
(m N)!N!
(M N + 1)!N!
(M + 1)M!
M!
(M N)
= (M 1)
(m N)!N!
(M N + 1)(M N)!N!
 

 
M
M
M+1
MN
= (M + 1)
=
1
MN+1
MN+1 N
N

Al sustituir (5.21) y el valor N = m
N en (5.17) obtenemos (5.16) 

Conforme M, N , la expresion (5.16) tiende a:


1

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:

Lema 5.5 (Peterson 1957 [27]) Cantidad de cubetas inspeccionadas en una


b
usqueda exitosa sobre una tabla hash con resoluci
on de colisiones por direccionamiento y sondeo ideal
10

n
m

n n1
=
m m1

484

Captulo 5. Tablas hash

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

Bajo la misma aproxima ion M, N y la aproxima ion de los numeros


armoni os :
11

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).

5.1. Manejo de colisiones

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

publi os de OLHashTable<Key,Record> 486ai

};
}
De nes:

OLhashTable, never used.


Uses HashFctType.

OLHashTable<Key,Record> representa una tabla hash, on resolu ion de olisiones


abierta y sondeo lineal de ubetas, indizada por laves de tipo Key y que guarda registros
de tipo Record.

486

Captulo 5. Tablas hash

B
usqueda
486a

La busqueda de una lave se remite a:


hMiembros p
ubli os de OLHashTable<Key,Record> 486ai

(485) 486b

Record * search(const Key & key)


{
// Comenzar desde
ndice hash y sondear linealmente hasta
// encontrar una cubeta EMPTY
for (int i = (*hash_fct)(key) % M, c = 0; c < M and table[i].status != EMPTY;
++c, ++i)
if (table[i].status == BUSY) // Hay una clave en la cubeta?
if (table[i].key == key) // Comparar la clave
return &table[i].record;
return NULL; // No se encuentra la clave

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

5.1. Manejo de colisiones

487

La elimina ion es la opera ion polemi a de esta estrategia, pero es fundamentalmente


simple: (1) en ontrar la ubeta que ontiene la lave a eliminar y (2) mar arla on DELETED.
Las ubetas mar adas omo DELETED representan un oste adi ional a la busqueda,
pues estas deben inspe ionarse aunque no ontengan laves. Esto plantea un problema de
entropa a medida que aumenta la antidad de elimina iones pudiendo o urrir la desafortunada situa ion de tener que revisar todas las ubetas de la tabla en una busqueda fallida
aunque la tabla ontenga muy po os elementos. Conse uentemente, on dire ionamiento
abierto las elimina iones deben ser ex ep ionales.
Mejoras a la eliminaci
on

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);

int i = bucket - &table[0]; //


ndice de cubeta a eliminar; cubeta brecha
for (int j = (i + 1) % M; true; ++j)
switch (table[j].status)
{
case BUSY:
// Verificar si cubeta contiene una colisi
on.
if ((*hash_fct)(table[j].key) == bucket->key)
{
// contiene colisi
on ==> mover su contenido hacia table[i]
table[i].key
= table[j].key;
table[i].record = table[i].record;
table[i].status = BUSY;
table[j].status = DELETED; // esta cubeta deviene DELETED y
// eventualmente candidata a
// marcarse con EMPTY
i = j; // table[j] deviene cubeta brecha
}
break;
case DELETED: // en este caso, la cubeta i no puede marcarse con
// EMPTY porque si no rompe la cadena de sondeo lineal
i = j; // table[j] deviene cubeta brecha
break;

488

Captulo 5. Tablas hash

case EMPTY: // en este caso j ser


a la cubeta que detendr
a la b
usqueda,
// pero, puesto que i est
a m
as atr
as y no interrumpe la
// b
usqueda, la marcamos con EMPTY y terminamos
table[i].status = EMPTY;
N--;
return; // terminar
}
}
Uses record to bucket.

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

y el promedio para una busqueda exitosa es:

i=0

Demostraci
on

Por su omplejidad, la demostra ion es dejada en ejer i io. Consultese Sedgewi k y


Flajolet [28 y Knuth [19, 20 para los detalles

Corolario 5.1 Desempe
no esperado de una tabla hash con resoluci
on de colisiones por direccionamiento abierto y sondeo lineal

5.1. Manejo de colisiones

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

Captulo 5. Tablas hash

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

Figura 5.1: Cantidad de ubetas inspe ionadas en una busqueda fallida


5
Sondeo ideal 3
4.5En adenamiento separado
Sondeo lineal +
4

+
+
+
+
+
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

Figura 5.2: Cantidad de ubetas inspe ionadas en una busqueda exitosa


>Que es lo que degrada el sondeo lineal, tan notable en las gra as, respe to al sondeo
ideal? La respuesta es ono ida bajo el rotulo \agrupamiento primario". Es evidente que
ualquier lave olision h(k1) = i poten ia una olision h(k2) = i + 1. Conforme aumenta
la antidad de elementos, se forman adenas lineales de olisiones en torno a la posi ion i.
Sin embargo, en la pra ti a, estas adenas son tolerables hasta el por entaje de plenitud
expresado (80%), pues su ontiguidad en el arreglo aprove ha el a he del omputador .
Con nuestra implanta ion del en adenamiento separado tenemos una o upa ion en
memoria de:
SL = 2MSpST + 2NSpST .
(5.27)
12

12 Estos

por entajes pueden orroborarse (o refutarse) mediante cachegrind [25.

5.1. Manejo de colisiones

491

Donde Sp es el espa io o upado por un puntero y ST es el espa io o upado por la lave.


En el mismo sentido, on el dire ionamiento abierto tenemos una o upa ion de memoria de:
SO = M(ST + 1)
(5.28)
Claramente, SO < SL, lo que no signi a que no pueda redu irse el onsumo de memoria
del en adenamiento separado. Por ejemplo, podemos usar listas simples y una tabla de
puros punteros.
Las gra as 5.1 y 5.2 muestran ontundentemente la superioridad teori a del en adenamiento separado en uanto a la antidad de sondeos que se realizan para ualquier
busqueda. Pero, omo ya lo indi amos en x 5.1.3.4 (pagina 479), el sondeo lineal aprove ha
el a he del omputador, un plano de eje u ion varios ordenes de magnitud mas veloz
que el a eso fragmentado a la memoria, sin aprove har el a he, que requerira el en adenamiento separado. En a~nadidura, la inser ion e elimina ion requieren apelar al manejador
de memoria dinami a, mientras el dire ionamiento abierto no.
Cuando se atina la antidad esperada de laves a manejar, el dire ionamiento abierto
on sondeo lineal es, probable pero sorprendentemente, aun on los argumentos en mano,
el mejor enfoque para manejar una tabla hash. Las razones se pueden resumir en:
 Es muy simple de implantar, lo que impli a un bajo oste onstante.
 No utiliza el manejador de memoria dinami a.
 Aprove ha el a he del omputador.

>Cuando es impra ti o el sondeo lineal? Como desventajas podemos enun iar:


 El dire ionamiento abierto esta Supeditado a una estima i
on orre ta del numero

de elementos a manejar. Si esto falla, enton es el agrupamiento degrada onsiderablemente el desempe~no.


En o asiones no es posible disponer de una buena estima ion. En estos asos, as
omo si se desea una tabla hash general, es preferible el en adenamiento separado.

 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

El sondeo uadrati o es fundamentalmente simple. Si o urre una olision en la posi ion i,


enton es la proxima ubeta a sondear esta dada por i2 mod M. La idea de esta estrategia

492

Captulo 5. Tablas hash

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

5.1. Manejo de colisiones

493

De nes:

ODhashTable, used in hunk 494 .

En la pra ti a, ODhashTable<Key, Record> es en interfaz identi a a OLHashTable<Key,Record>,


la uni a diferen ia estriba en que el onstru tor requiere la segunda fun ion hash.
El problema de la eliminaci
on con el doble hash

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

Captulo 5. Tablas hash

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 de nir 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

Los tres primeros atributos se de nen de forma identi a a la ubeta de


OLHashTable<Key,Record>. El atributo probe type indi a si una lave alma enada en
la ubeta es resultante del primero (FIRST PROBE) o segundo (SECOND PROBE) sondeos o
del sondeo lineal (LINEAR PROBE) a partir de la ter era olision.
Notemos que status y probe type son ampos de bits (niblas) y que ambos onforman
un byte exa to.
El ultimo atributo, probe counter, indi a, tal omo lo a abamos de expli ar, la antidad de laves que olindan en una adena de olisiones que parte desde el primer sondeo
on la primera fun ion hash, hasta el ultimo sondeo on la segunda fun ion hash o on
alguna antidad de sondeos lineales. Por ejemplo, si una ubeta tiene probe counter ==
4, enton es esto indi a que esa ubeta esta en el amino de sondeo que omienza por
la primera fun ion hash, luego por la segunda fun ion hash, mas dos ubetas sondeadas
linealmente.
Los atributos de la propia tabla se de nen de la siguiente manera:
hMiembros privados de ODhashTable<Key, Record> 494ai+
(492) 494a 495b
Bucket *
Hash_Fct
Hash_Fct
size_t
size_t
size_t
size_t

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

los uales se ini ializan en el onstru tor de la siguiente manera:


hMiembros p
ubli os de ODhashTable<Key, Record> 494 i

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

5.1. Manejo de colisiones

495a

495

De las opera iones que requieren sondeos, la busqueda es la mas simple:


hMiembros p
ubli os de ODhashTable<Key, Record> 494 i+
494 496

Record * search(const Key & key)


{
int i = (*first_hash_fct)(key) % M; // primer sondeo con primera funci
on hash
if (table[i].status == BUCKET_EMPTY)
return NULL;
if (table[i].status == BUCKET_BUSY and table[i].key == key)
return &table[i].record;
i = (*second_hash_fct)(key) % M; // Segundo sondeo con segunda funci
on hash
if (table[i].status == BUCKET_BUSY and table[i].key == key)
return &table[i].record;
// Sondeo lineal a partir del
ndice dado por segunda funci
on hash
for (int count = 0; count < M and table[i].status != BUCKET_EMPTY; count++)
{
advance_index(i);
if (table[i].status == BUCKET_BUSY and table[i].key == key)
return &table[i].record;
}

return NULL; // sondeo no encontr


o la clave
}
Uses advance index.

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

Captulo 5. Tablas hash

return &bucket.record;
}
De nes:
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.

5.1. Manejo de colisiones

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--;

if (bucket->probe_counter == 0) // puede marcarse EMPTY sobre la cubeta?


{
bucket->status = BUCKET_EMPTY;
--deleted_entries_counter;
++empty_entries_counter;
}
}
De nes:

decrease probe counter, used in hunk 498.

497b

Del mismo modo, la ubeta a eliminar es \des-liberada" mediante a una rutina


simetri a al allocate bucket() empleado para la inser ion:
hMiembros privados de ODhashTable<Key, Record> 494ai+
(492) 497a
void deallocate_bucket(Bucket * bucket)
{
bucket->probe_counter--;

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;
}
De nes:

deallocate bucket, used in hunk 498.

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

Captulo 5. Tablas hash

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).

5.1. Manejo de colisiones

499

Si es ogemos, por seguridad, un 90% omo toleran ia de plenitud, enton es el 10-20%


de diferen ia respe to al sondeo lineal puede ofre ernos una ganan ia importante en espa io
que vara segun sea el tama~no de la lave y del registro. >Cual es la ganan ia en tiempo?
Abordar la pregunta anterior es deli ado porque el doble hash no solo es en tiempo
onstante mas ostoso que el sondeo lineal, sino que es bastante probable que el segundo
sondeo ause una ruptura del a he. As que, por su simpli idad y buen desempe~no general,
el sondeo lineal es la es ogen ia de fa to para onjuntos peque~nos y medianos en los uales
sea estimable el tama~no, o en situa iones de programa ion rapida tales omo el prototipeo
o la ontingen ia.
Pero la bondad del doble hash se apre ia uanto mayores sean M y N en los uales los
10 sondeos que toma la busqueda fallida y la inser ion, son notabilsimamente mejores que
los 63 sondeos que en promedio tomara la busqueda fallida si la tabla estuviese al 90%
de plenitud. En uanto al espa io, a mayores valores de M y N mayor es el desperdi io en
ubetas va as que requeriramos on el sondeo lineal.
Pasado el 90% de plenitud, el doble hash exhibe agrupamiento se undario y se degradan
sus opera iones. Es extremadamente importante garantizar esta ota.
En sntesis, los riterios que ondu en a es oger doble hash omo estrategia de sondeo
se resumen en:
1. Gran antidad de laves, lo que impli a que el valor a es oger de M es alto y el
desperdi io en ubetas deso upadas es mayor on el sondeo lineal.
2. Po o margen de desperdi io; por ejemplo, po a memoria.
Si se el estimado sobre N es pre iso y no muy grande, enton es el sondeo lineal es
la sele ion; si es N es grande, enton es doble hash. Si el estimado de N es impre iso,
enton es el en adenamiento separado es la mejor estrategia.
5.1.5

499

Reajuste de dimensi
on en una tabla hash

Luego de los analisis realizados, no debe ostarnos aprehender que, independientemente de


la estrategia para tratar on las olisiones, uanto mas peque~no sea menos probabilidad
de olision y, onse uentemente, mejor desempe~no de la estrategia. Por otra parte, aunque
sea o asional, la mala suerte existe y, eventualmente, o el onjunto de laves supera la
estima ion de N, o el mal azar ausa una antidad alta de olisiones. >Puede ha erse algo
al respe to?
Hay dos maneras de tratar on la mala suerte. La primera, basada en el en adenamiento
separado y que sera expli ada en x 5.1.7 (pagina 503), reubi a el arreglo y las ubetas para
garantizar una longitud maxima de ada lista de olisiones. La segunda, objeto de esta
sub-se ion, onsiste simplemente en apartar un nuevo arreglo un M mayor y reubi ar las
laves.
De este modo, el reajuste on el doble hash puede plantearse de la siguiente manera:
hMiembros p
ubli os de ODhashTable<Key, Record> 494 i+
498
const size_t & resize(const size_t & new_size)
throw(std::exception, std::bad_alloc, std::overflow_error, std::range_error)
{
Bucket * new_table = new Bucket [new_size]; // aparta nuevo arreglo

500

Captulo 5. Tablas hash

Bucket * old_table = table; // respaldar valores de la antigua tabla


const size_t old_M = M;
table
M
empty_entries_counter
deleted_entries_counter
N

=
=
=
=
=

new_table; // reiniciar tabla a nuevo arreglo


new_size;
M;
0;
0;

for (int i = 0; i < old_M; ++i) // recorrer antigua tabla y reinsertar


if (old_table[i].status == BUCKET_BUSY)
insert(old_table[i].key, old_table[i].record);
delete [] old_table;
return M;
}
Uses resize.

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

5.1. Manejo de colisiones

table
M
busy_slots_counter
N

=
=
=
=

501

new_table;
new_size;
0;
0;

// recorrer la antigua tabla y reinsertar las cubetas en la nueva tabla


for (int i = 0; i < old_size; ++i)
{
// recorrer las colisiones en old_table[i]
for (BucketItor it(old_table[i]); it.has_current(); /* Nothing */)
{
Bucket * node = // eliminar cubeta de la antigua tabla
static_cast<Bucket*>(it.del());
insert(node); // insertarla en la nueva
}
}
delete [] old_table; // liberar memoria antigua tabla
return M;
}
Uses BucketItor, BucketList, busy slots counter, has current 103, and resize.

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

typedef typename DynLhashTable<Key, Record>::Hash_Fct Hash_Fct;

502

Captulo 5. Tablas hash

hMiembros
};
De nes:

publi os de DynLhashTable<Key,

Record> 502bi

DynLhashTable, never used.


Uses LhashTable 471.

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 de ni ion del rango y al manejo de memoria.
La de ni 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;

DLBucket(const Key & key, const Record & _record)


: LhashTable<Key>::Bucket(key), record(_record)
{ /* Empty */ }
};
De nes:
DLBucket, used in hunks 502 and 503.
Uses LhashTable 471 and LhashTable<Key>::Bucket.

502b

Una vez de nida 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

El odigo es lineal porque la estrategia ya esta implantada en LhashTable<Key>.


La busqueda se remite a un wrapper a la busqueda de LhashTable<Key>:
hMiembros p
ubli os de DynLhashTable<Key, Record> 502bi+
(501) 502b 503b

Record * search(const Key & key)


{
DLBucket * bucket = static_cast<DLBucket*>(LhashTable<Key>::search(key));
return bucket != NULL ? &bucket->record : NULL;
}

5.1. Manejo de colisiones

503

Uses DLBucket 502a and LhashTable 471.

La elimina ion requiere transformar un registro a una ubeta:

503a

503b

hMiembros privados de DynLhashTable<Key, Record> 502ai+


(501) 502a
static DLBucket * record_to_bucket(Record * rec)
{
DLBucket * ret_val = 0;
size_t offset = reinterpret_cast<size_t>(&ret_val->record);
return reinterpret_cast<DLBucket*>(reinterpret_cast<size_t>(rec) offset);
}
Uses DLBucket 502a and record to bucket.

Con este metodo, la elimina ion tambien es muy simple:


hMiembros p
ubli os de DynLhashTable<Key, Record> 502bi+

(501) 502

void remove(Record * record)


{
DLBucket* bucket = record_to_bucket(record);
LhashTable<Key>::remove(bucket);

delete bucket;
}
Uses DLBucket 502a, LhashTable 471, and record to bucket.

La fa ilidad on se instrumenta el TAD DynLhashTable<Key, Record> indi ia, una


vez mas, la bondad del prin ipio n-a- n (x 1.4.2 (pagina 22)).
5.1.7

Tablas hash lineales

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

Captulo 5. Tablas hash

La tabla hash lineal esta implantada mediante el tipo LinearHashTable<Key> , el ual


se espe i a en el ar hivo htpl linHash.H 504ai, uya espe i a ion de base es la siguiente:
htpl linHash.H 504ai
template <typename Key, template <class> class BucketType>
class GenLinearHashTable
{
hMiembros privados de LinearHashTable<Key> 504bi
ubli os de LinearHashTable<Key> 510ai
hMiembros p
};

template <class Key>


class LinearHashTable : public GenLinearHashTable<Key, LhashBucket>
{
};
template <class Key>
class LinearHashTableVtl : public GenLinearHashTable<Key, LhashBucketVtl>
{
};
De nes:
GenLinearHashTable, never used.
LinearHashTable, never used.
LinearHashTableVtl, never used.
Uses LhashBucket 470b and LhashBucketVtl 470b.

504b

GenLinearHashTable<Key, BucketType es el tipo generi o que implanta la tabla hash


lineal, mientras que LinearHashTable<Key> y LinearHashTableVtl<Key> son los tipos
exportados para el uso que manejan ubetas sin y on destru tor virtual.
Las estru turas de las ubetas son exa tamente las mismas que las del tipo
LhashTable<Key> estudiado en x 5.1.3.1 (pagina 469).
LinearHashTable<Key> maneja los maneja los mismos atributos internos que
LhashTable<Key>:
hMiembros privados de LinearHashTable<Key> 504bi
(504a) 505a
DynArray<BucketList>
Hash_Fct
size_t
size_t
size_t

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.

La uni a y ligera diferen ia respe to al tipo LhashTable<Key> es el atributo table, ual


es, en este aso un arreglo dinami o.
5.1.7.1

Expansi
on/contracci
on de una tabla hash lineal

Siendo table un arreglo dinami o, su dimension se aumenta y se ontrae dinami amente


en tiempo O(1). Para ello, se manejan los siguientes atributos umbrales:

5.1. Manejo de colisiones

505a

505

hMiembros privados de LinearHashTable<Key> 504bi+


float upper_alpha; // factor de carga superior

(504a) 504b 505b

float lower_alpha; // factor de carga inferior

505b

LinearHashTable<Key> mantiene permanentemente el fa tor de arga = N/M.


Despues de una inser ion, se ompara on upper alpha; si upper alpha, enton es
la tabla se expande en una unidad y una antidad de ubetas, onstantemente a otada, se
reubi a. Simetri amente, si luego de una elimina ion lower alpha, enton es la tabla se
ontrae una unidad y, tambien, algunas ubetas se reubi an. El \tru o" de la dispersion
lineal estriba, pues, en la manera en que linealmente se expande y ontrae la tabla, as
omo en la forma en que se maneja la fun ion hash.
Los valores ini iales de upper alpha y lower alpha se espe i an durante la onstru ion de un objeto LinearHashTable<Key> , el ual exporta modi adores que permiten ajustar dinami amente los valores umbrales de arga.
El estado de expansion o ontra ion de la tabla, se mantiene mediante lo siguientes
dos atributos:
hMiembros privados de LinearHashTable<Key> 504bi+
(504a) 505a 505
size_t p; //
ndice de la lista que se particiona (o aumenta)
size_t l; // cantidad de veces que se ha duplicado la tabla

El arreglo siempre re e o de re e por su extremo dere ho. El ndi e p se referen ia de


dos maneras:
1. table[p + M] es la entrada del arreglo por donde este se expandira o ontraera,
segun sea la opera ion y el valor del fa tor de arga .
2. table[p] ontiene la lista de olisiones que se parti ionara si o urriese una expansion; es de ir, si o urre una expansion, enton es algunas ubetas de la lista
table[p] se pasan a la lista table[M + p]. Simetri amente, si o urre una ontra ion, enton es todas las ubetas de table[M + p] se trasladan a table[p].
Cuando o urre una expansion se in rementa el ndi e p. Simetri amente, se de rementa uando o urre una ontra ion.
0

M
2

M
2

M
2

(a) Estado ini ial

M
7

(b) Luego de la primera expansion


9

( ) Luego de 5 expansiones

M
4 5

10

(d) Luego de 6 expansiones(el valor de M se dupli a

Figura 5.3: Pro eso de expansion logi a de la tabla para M = 5

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

Captulo 5. Tablas hash

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

El pro eso de expansion se ilustra en la gura 5.3 para M = 5.

506b

En virtud de lo expli ado, al momento de expandir la tabla los valores de p, l, M],


[[MP y MM se a tualizan de la siguiente manera:
hA tualizar estado de expansi
on 506bi
(508)
++p;
++MP;
if (p == M) // (p == 2*M) debe duplicarse el tama~
no de la tabla?
{
// s
==> modificar el tama~
no de la tabla a 2*M
++l;
// Cantidad de veces que se ha duplicado la tabla aumenta
p = 0;
MP = M = MM; // se les asigna 2*M
MM = multiply_by_two(MM);
}

506

Simetri amente, la ontra ion de la tabla es el pro eso inverso:


hA tualizar estado de ontra i
on 506 i
(509b)

if (p == 0) // debe dividirse entre 2 el tama~


no de la tabla?
{
// s
==> actualizar tama~
no de la tabla a M/2
--l; // Cantidad de veces que se ha duplicado la tabla disminuye
MM = M; // se divide entre dos
M = divide_by_two(M);
p = M - 1;
}
else
// no ==> s
olo reducir
ndice p
--p;
--MP;

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

5.1. Manejo de colisiones

507a

507

la llamada a la fun ion hash de la siguiente manera:


hMiembros privados de LinearHashTable<Key> 504bi+

(504a) 506a 508

size_t call_hash_fct(const Key & key) const


{
const size_t hash = (*hash_fct)(key);
const size_t i = hash % M;
return i < p ? hash % MM : i;

}
De nes:

call hash fct, used in hunk 510.

Re ordemos que p es el ndi e de la lista de expansion/ ontra ion. En el rango [0, M 1]


la tabla ontiene ubetas asignadas por
(*hash f t)(key ) mod M

mientras que en el rango [M, M + p] las ubetas se asignan por


(*hash f t)(key ) mod 2M

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)

Figura 5.4: Ejemplo de parti ion/ ontra ion de una lista


507b

10

El pro eso de parti ion des rito puede instrumentarse de la siguiente forma:
hParti ionar lista 507bi
(508)

508

Captulo 5. Tablas hash

BucketList * src_list_ptr = table.test(p);


if (src_list_ptr != NULL) // table[p] est
a escrita?
if (not src_list_ptr->is_empty()) // table[p] no est
a vac
a
{
BucketList & tgt_list = table.touch(MP); // asegura memoria table[p+M]
// recorrer toda la lista de colisiones
for (BucketItor it(*src_list_ptr); it.has_current(); /* nothing */)
{
Bucket * bucket = static_cast<Bucket*>(it.get_current());
it.next(); // avance al siguiente elemento de la lista
const Key & key = bucket->get_key();
const int i = (*hash_fct)(key) % MM;
if (i == p) // pertenece esta clave a table[p]?
continue; // s
==> clave permanece en table[p]
// bucket no pertenece a table[p]] sino a table[p+m] ==>
// eliminar bucket de table[i] e insertarlo en table[p+m]
bucket->del();
tgt_list.append(bucket);
}
if (src_list_ptr->is_empty()) // table[p] qued
o vac
a?
--busy_slots_counter; // s
==> un slot vac
o
++busy_slots_counter; // uno nuevo por table[p+M]
}

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

if (alpha >= upper_alpha) // excedemos el factor de carga m


aximo?
return; // No, no hay necesidad de expandir.
hParti ionar

lista 507bi

hA tualizar estado de expansi


on 506bi
}
De nes:
expand, used in hunks 510b and 537b.

5.1. Manejo de colisiones

509a

509

La expansion solo se da a lugar si se ex ede el fa tor de arga.


El pro eso de ontra ion puede interpretarse a la inversa del de expansion: pasar
los elementos de la lista table[M + p] ha ia la lista table[p]; tambien esta ilustrado
en la gura 5.4 si se interpreta de dere ha a izquierda. En este aso, el pro eso -de
ontra ion- es mu ho mas rapido, pues no es ne esario re orrer ninguna de las listas, solo on atenarle table[M + p] a table[p], la ual la realiza una opera ion del
TAD Dlink (x 2.4.7 (pagina 90)):
hFusionar lista 509ai
(509b)
BucketList * src_list_ptr = table.test(MP);

if (src_list_ptr != NULL) // existe estrada para table[p+M]?


{
// s

if (not src_list_ptr->is_empty()) // est


a vac
a table[p+M]?
{
// no ==> fusionar las listas
BucketList & tgt_list = table.touch(p); // asegura escritura table[p]
tgt_list.concat_list(src_list_ptr);
--busy_slots_counter; // table[p+M] devino vac
a
}
table.cut(MP); // eventualmente liberar memoria de table[p+M]
}

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 de ne, enton es, de
la siguiente forma:
hMiembros privados de LinearHashTable<Key> 504bi+
(504a) 508
void contract()
{
const float alpha = (1.0*N)/MP;

if (alpha <= lower_alpha) // carga est


a por debajo de la m
nima?
return; // no ==> no hay necesidad de contraer
if (MP == len) // Est
a la tabla en el tama~
no original?
return; // s
==> no se puede contraer m
as
hA tualizar

estado de ontra ion 506 i

hFusionar lista 509ai


}
De nes:
contract, used in hunk 511.

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

Captulo 5. Tablas hash

hMiembros p
ubli os de LinearHashTable<Key> 510ai
Bucket * search(const Key & key)
{
const int i = call_hash_fct(key);

(504a) 510b

BucketList * list = table.test(i);


if (list == NULL) // Ha sido escrita alguna vez table[i]?
return NULL; // No ==> el elemento no se encuentra en la tabla
if (list->is_empty())
return NULL;
// buscar key en la lista de cubetas
for (BucketItor it(*list); it.has_current(); it.next())
{
Bucket * bucket = static_cast<Bucket*>(it.get_current());
if (key == bucket->get_key())
return bucket;
}
return NULL;
}

Uses BucketItor, BucketList, call hash fct 507a, get current 103, and has current 103.

La a~nadidura de esta busqueda respe to a los otros tipos de tabla hash es el he ho de


que, puesto que el arreglo dinami o aparta memoria en fun ion de la primera es ritura, es
probable que hayan entradas que no han sido apartadas.
5.1.7.3
510b

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());

(504a) 510a 511

BucketList & list = table.touch(i); // asegura memoria para table[i]


if (list.is_empty())
++busy_slots_counter;
list.append(bucket);
++N;
expand();
return bucket;
}
Uses BucketList, busy slots counter, call hash fct 507a, expand 508, and touch 62.

5.1. Manejo de colisiones

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)

y el promedio para una busqueda exitosa es:


1+

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 de nir las
siguientes fun iones:
u(y) = y ;
(5.31)

512

Captulo 5. Tablas hash

para el promedio de ubetas que se inspe iona en una busqueda infru tuosa y:
s(y) = 1 +

1
1
+
y M

(5.32)

para el el promedio de ubetas que se inspe iona en una busqueda exitosa.


Una mirada mas a u iosa, permite interpretar u(y) y s(y) en el mismo sentido uando
p = 0; es de ir, uando no ha o urrido ninguna expansion, la expansion a aba de dupli ar
el valor M (vease hA tualizar estado de expansion 506bi) o la ontra ion a aba de dividir
entre a M (vease hA tualizar estado de ontra ion 506 i).
Para tabla hash lineal de tama~no M, sus M listas de olisiones pueden distribuirse en:
 p primeras listas que han sido parti ionadas.
 M P listas que no han sido parti ionadas, y
 p listas resultantes de haber parti ionado las primeras p listas.

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 de nido, 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, de nido 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 de ne 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 de nimos 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,

5.1. Manejo de colisiones

513

segun (5.31), ual nos indi a la antidad de esperada de ubetas que se visitaran, U(x) se
de ne 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 de nimos 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 de ne 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)

En ambos tipos de busqueda, el mnimo esperado de ubetas revisadas o urre uando


la tabla no se ha expandido (x = 0) o uando la expansion ha dupli ado el tama~no de la
tabla (x = 1). De este modo, las otas inferiores estan de nidas por:
UN = U(0) =
SN = S(0) = 1 +

+
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

El oste a lo largo de un i lo de expansion, esta determinado por la proposi ion


siguiente.
Proposici
on 5.4 (Larson 1988 [22]) Costo promedio de b
usqueda en una tabla
hash lineal
Sea T una tabla hash lineal de longitud M on N elementos. Enton es, el oste prome-

dio de busqueda a lo largo de un i lo de expansion esta determinado por:


M

UN

13

12

(5.38)

para una busqueda fallida y:


M

SN

para una busqueda exitosa.

(13 + 12)M + 12
12M

(5.39)

514

Captulo 5. Tablas hash

Demostraci
on

El resultado sale dire tamente de integrar (5.36) y (5.37):


M

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.

5.2. Funciones hash

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

Replanteemos h(k) : K [0, M 1] en terminos omputa ionales bajo el siguiente \tipointerfaz":


template <typename Key> size_t hash_fct(const Key & key);

\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;

La \naturaleza" de Key no ne esariamente es numeri a, en uyo aso debemos en ontrar


una transforma ion de key ha ia el tipo size t. As que estable emos el siguiente proto olo
para el resultado de (*hash fct)(key):
1. Si Key no es numeri o, enton es transformarlo al tipo size t.
2. Asumiendo que key ya se en uentra en el dominio size t -por va dire ta o por
la transforma ion anterior-, \dispersar" key, lo mas que se pueda, en lo posible
on \emula ion aleatoria", ha ia el dominio size t. Esta dispersion es, en s, la
transforma ion.
Puesto que en ALEPH siempre ulminamos la determina ion de h(k) : K [0, M 1]
on la llamada (*hash fct)(key) mod M, esta opera ion, omo veremos prontamente, tambien puede ser parte de la dispersion.
5.2.2

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

Captulo 5. Tablas hash

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

Plegado o doblado de clave

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)];

char * ptr = (char*) key + 6; // comienzo del centro en 20 bytes


for (int i = 0; i < 4; ++i)
{
buf[i] = (ptr[0]>>4) | (ptr[1]>>4);
ptr += 2;
}
13 Estos
14 O del

son los valores ASCII para los smbolos latinos no a entuados.


tipo que se utili e omo rango segun sea el aso.

5.2. Funciones hash

517

En pos de una buena dispersion, en el dise~no de un buen plegado es onveniente


indagar la zona de la lave en la ual se presenta mas diversidad, pues esa es la zona on
mayor poten ial para emular la deseada aleatoriedad. Simetri amente, las zonas on menor
variedad son mas ausantes de repiten ias.
5.2.4

Heursticas de dispersi
on

En sntesis, para al ular h(k) : K [0, M 1] tenemos los siguientes requerimientos:


1. Que sea muy rapida, no solo O(1), sino que su tiempo onstante sea bajo.
2. Que tenga su iente holgura de dispersion.
3. Que los resultados ara teri en una distribu ion de probabilidad uniforme.
De estos tres requerimientos, el mas dif il es el ter ero, pues las le iones aprendidas en la
genera ion de numeros aleatorios [18 indi ian que es harto dif il, por no de ir imposible,
generar numeros aleatorios a partir de datos que no los son . Nadie, hasta el presente,
ha des ubierto una te ni a de dispersion general que emule a una distribu ion uniforme,
pero s se ono en ex elentes heursti as que, operadas sobre una representa ion numeri a,
emulan la aleatoriedad.
15

5.2.4.1

Dispersi
on por divisi
on

El metodo de dispersion mas popular es justamente el que hemos empleado a traves de


todos los tipos desarrollados:
h(k) = k mod M
(5.42)
Pero en este tipo de fun ion es rti a la sele ion del divisor; o sea, el valor de M, el ual,
a menudo, es el propio tama~no de la tabla .
Sabemos que ualquier entero k de n dgitos puede expresarse omo
16

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)

El resto de la division, k mod M, puede interpretarse omo: k mod M = k kk/M;


lo ual, segun (5.43), puede expresarse omo:

di b i
k mod M = k
M
 Pn1
n1
i
X
i
i=0 di b
di b
=
M
 Pn1
i=0

(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

Captulo 5. Tablas hash

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 de nir, simplemente, omo el resto de los restos. Desarrollamos la representa ion
segun k mod M = k k/M porque onsideramos que permite aprehender mejor.

5.2. Funciones hash

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 de ne una fun ion hash del siguiente modo:
h(k) = {k } M

(5.46)

donde es un fa tor de multipli a ion.


>Cual debe ser el valor del fa tor ?. Si deseamos emular la aleatoriedad, enton es
suena on sentido de ir que para favore erla la es ogen ia debe ser algo irra ional. Pues
bien, resulta que la matemati a nos des ubre una lase in nita de numeros llamados
\irra ionales" en el sentido de que no pueden expresarse omo una razon (fra ion) y, ya
he has las onsidera iones sobre la multipli idad de M en la sub-se ion anterior, sabemos
que las razones (fra iones) tienden a sesgar las laves.
Proposici
on 5.5 (Tur
an-1958) 18
Sea un numero irra ional y n un entero positivo. Enton es los n + 1 fra ionales
{}, {2}, {3}, . . . , {n} tienen a lo sumo tres longitudes distintas y el proximo fra ional
{(n + 1)} se aleja en una de las mayores longitudes.
Demostraci
on

Vease Knuth [19

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

18 Citado por Knuth [19.


19 En x 6.4.2.2 (p
agina 588) se expli a un po o mas el al ulo de este numero

y su rela ion on los numeros


de Fibona i. En x 6.8 (pagina 627) se realizan algunas referen ias histori as del uso de esta propor ion.

520

Captulo 5. Tablas hash

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:

5.2. Funciones hash

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

Captulo 5. Tablas hash

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

hHash del sistema sdbm [26 522bi


size_t hash(unsigned char * str)
{
size_t hash = 0;
int c;
while (c = *str++)
hash = c + (hash << 6) + (hash << 16) - hash;
return hash;
}

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

20 \Azar" proviene del


21 Notemos que puede

usada para insertar.

arabe \zahr" que signi a \dado", pero tambien \ ores".


o urrir h1 (k) = h2 (k), razon por la ual no se puede saber ual fue la fun ion

5.2. Funciones hash

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 de ni 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 =

|{hi, hj H | hi(k1) = hj(k2)}|


1

|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 de ni 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.

de los des ubrimientos.

524

Captulo 5. Tablas hash

5.3

Otros usos de las tablas hash y de la dispersi


on

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
hDe nir

en fp sub y fp urr huellas da tilares sub y de str[0 525i

multipli ando fa t de huella da tilar (never de ned)i

// recorrer la cadena str en b


usqueda de la sub-cadena sub
for (int i = 0; i < n - m + 1; ++i)
{
if (fp_sub == fp_curr) // coinciden las huellas digitales?
{
// s
==> verificar si &str[i] se corresponde con la sub-cadena
for (char * aux = str[i], int k = 0; k < m; ++k)
if (aux[k} != sub[k])
break; // falso positivo
return i; // str[i] contiene la cadena
}

hCal ular

huella da tilar fp urr para str[i+1 526ai

return -1; // sub-cadena no se encuentra


}

Como vemos, el algoritmo es estru turalmente simple y muy similar a la busqueda en


bruto de la sub- adena, la ual es O(n m). De esto se desprende observar que para que
el algoritmo de Rabin-Karp valga la pena respe to al de fuerza bruta se deban umplir
dos osas:
1. El al ulo del bloque hCal ular huella da tilar fp urr para str[i+1 526ai debe
ser rapido, O(1), preferiblemente.

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

5.3. Otros usos de las tablas hash y de la dispersi


on

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 de nida, 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)

La propiedad distributiva del modulo tambien se apli a al produ to; es de ir



(ik) mod M = (i mod M)(k mod M) mod M

(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

(stri mod M) B| str |1i mod M

(stri mod M) (B| str |1i mod M) mod M

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

tambien (5.45) y la nota a pie de la pagina 518.

526

Captulo 5. Tablas hash

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

hMultipli ando fa t de huella da tilar 526bi


const size_t fact = (size_t) pow(radix, m - 1); // radix^(m-1)

El metodo de \huella da tilar" se apli a una amplia antidad de situa iones. En


primer lugar, el algoritmo puede ha erse mas general para re ono er patrones bi o tridimensionales si los objetos en el plano o espa io se onsideran omo matri es o arreglos
tridimensionales, los uales, al n de uentas, deparan en se uen ias.
Una ganan ia de este algoritmo es que la longitud de la sub-se uen ia a bus ar no
in ide durante el barrido de la se uen ia en la que se bus a; esto ontrasta on otros
algoritmos que se enlente en a medida que la se uen ia bus ar es de mayor longitud.
El valor de M puede sele ionarse aleatoriamente, de manera tal que un hipoteti o
adversario no pueda ata ar el algoritmo olo ando omo una entrada una se uen ia a
bus ar que ause mu has olisiones.
Compartir libremente el ono imiento es una ondi ion esen ial para ser ser humano.
Adjudi arse el merito de otro no solo es explota ion del projimo, sino que va ontra la
esen ia de la vida en omunidad. El adjudi arse falsamente un des ubrimiento sin haberlo
he ho se le denomina \plagio". En el des ubrimiento de ideas, as omo en ualquier otro
obrar, se puede plagiar. Para lo es rito, y en general para toda produ ion representable
omo una se uen ia, puede modi arse el algoritmo de Rabin-Karp para veri ar automati amente si se ha ometido algun plagio. En este aso, se dise~na una fun ion hash
que modeli e la se uen ia sospe hosa de plagio.
5.3.2

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,

5.3. Otros usos de las tablas hash y de la dispersi


on

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 de nitivamente 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

Cache (el TAD Hash Cache<Key,Data> )

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

Captulo 5. Tablas hash

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

alma enamiento, en orden re iente a la velo idad y de re iente a la apa idad,


pueden lasi arse omo:
1.
2.
3.
4.

Memoria prin ipal.


Memoria se undaria (dis os duros).
Memoria ter iaria (CD-ROM, DVD, memorias ash, intas).
Memoria remota (disponible por red).

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

omplejos sus eptibles de repetirse. En este aso, podemos guardar en un a he


la se uen ia originaria del al ulo y su resultado. De este modo, si la dinami a del
programa requiere de nuevo el al ulo, este ya se en uentra disponible.
En esta situa ion suele usarse parte de la se uen ia originaria de al ulo omo lave
de una fun ion de dispersion.

5.3. Otros usos de las tablas hash y de la dispersi


on

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
};
De nes:
Hash Cache, used in hunk 533b.

529b

Hash Cache<Key,Data> implementa un a he indizado por laves de tipo Key, que


guarda datos de tipo Data y que esta implantado mediante una tabla hash.
Puesto que un a he esta destinado a ser nito y que sea lo mas rapido posible, el uso
de una tabla hash lineal no pare e a onsejable. He ha esta a ota ion, podemos de ir que
ualquier enfoque de resolu ion de olisiones (y sus tipos rela ionados) puede usarse omo
trasfondo de implanta ion. Por su simpli idad, usaremos el tipo LhashTable<Key>:
hMiembros privados de Hash Cache<Key,Data> 529bi
(529a) 530
LhashTable<Key> hash_table;
Uses LhashTable 471.

529

Esto impli a de nir una lase de ubeta, la ual se de ne del siguiente modo:
hEntrada de Hash Cache<Key,Data> 529 i
(529a)
class Cache_Entry : public LhashTable<Key>::Bucket
{
Data
data;
hMiembros
hMiembros

privados de entrada de Hash Cache<Key,Data>


publi os de entrada de Hash Cache<Key,Data>

530bi
530ai

530

Captulo 5. Tablas hash

}; // fin class Cache_Entry


De nes:
Cache Entry, used in hunks 530 and 533{37.
Uses LhashTable 471 and LhashTable<Key>::Bucket.

530a

El observador de la lave se hereda de LhashTable<Key>::Bucket, y el del dato se


de ne omo:
hMiembros p
ubli os de entrada de Hash Cache<Key,Data> 530ai
(529 )
Data & get_data() { return data; }

530b

Para determinar ual de los elementos ontenidos en el a he es el mas antiguo, las


ubetas de la tabla hash se ordenan en una ola desde la entrada mas antiguamente a edida hasta la menos re ientemente usada. En lugar de usar una ola separada, de nimos
el siguiente atributo interno a la entrada del a he:
hMiembros privados de entrada de Hash Cache<Key,Data> 530bi
(529 ) 530d
Dlink
De nes:

dlink_lru; // enlace a la cola lru

dlink lru, used in hunk 530.


Uses Dlink 90.

530

Si tenemos un puntero al ampo dlink lru, enton es el siguiente ma ro de Dlink


(x 2.4.7 (pagina 100)) genera una fun ion de onversion:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 529b 530e
LINKNAME_TO_TYPE(Cache_Entry, dlink_lru);
Uses Cache Entry 529 , dlink lru 530b, and LINKNAME TO TYPE 100.

530d

y un puntero al atributo dlink lru se obtiene mediante:


hMiembros privados de entrada de Hash Cache<Key,Data> 530bi+

(529 ) 530b 530f

Dlink* link_lru() { return &dlink_lru; }


Uses Dlink 90 and dlink lru 530b.

530e

\LRU" es a ronimo ingles de least re ently used, ual literalmente signi a


\menos re ientemente utilizado". En nuestro dis urso astellano emplearemos la frase
\mas antiguamente a edida". dlink lru es un doble enla e dentro de una lista doblemente enlazada ir ular, uyo estado se mantiene desde el objeto Hash Cache<Key,Data>
mediante los siguientes atributos:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 530 531
Dlink
size_t
De nes:

lru_list; // cabecera de la lista lru


num_lru; // numero de elementos en lista lru

lru list, used in hunks 533{35.


Uses Dlink 90.

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

5.3. Otros usos de las tablas hash y de la dispersi


on

531a

531

Cuando obtenemos la entrada mas antiguamente a edida, ne esitamos saber si esta o


no ontenida en la tabla hash. Para ello, mantenemos un atributo que indi a este estado:
hMiembros privados de entrada de Hash Cache<Key,Data> 530bi+
(529 ) 530f 531b
bool is_in_hash_table; // indica si la entrada est
a contenida dentro de
// la tabla hash

Los atributos de Cache Entry onforman la siguiente estru tura pi tori a:


dlink de hash

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 de nir 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

uyo estado es manejado en Hash Cache<Key,Data> mediante los atributos:


hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 530e 532
Dlink

inside_list; // lista de entradas (cubetas) ya apartadas y


// metidas en la tabla hash

size_t cache_size;

// tama~
no del cache; m
aximo n
umero de entradas que
// puede contener

De nes:

cache size, used in hunks 533b and 537b.


inside list, used in hunk 535b.
Uses Dlink 90.

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

Captulo 5. Tablas hash

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

Figura 5.6: Estado de un a he de tama~no 8 on tabla hash de tama~no 10. Se muestra el


arreglo de la tabla hash y el arreglo pre-apartado de ubetas. Los enla es de la tabla hash
son ontiguos, los de la lista de entradas que estan dentro de la tabla punteados y los de
la lista LRU trazados. La entrada on lave 375, que es olision en la tabla hash on la
lave 45, esta \tran ada"; por eso pertene e a la lista locked list.
3. locked list: Las entradas \tran adas" se sa an de lru list y se introdu en en una
ter era lista de nida as:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 531 533a
532
Dlink locked_list; // lista de entradas trancadas
size_t num_locked; // numero de elementos trancados
Uses Dlink 90.

El enla e dlink lru es ex luyente: o la ubeta esta en lru list o en locked list

5.3. Otros usos de las tablas hash y de la dispersi


on

533a

533

Eventualmente, aunque no debera de ser el aso, puede aumentarse el tama~no del


a he. Para ello, se aparta un nuevo arreglo de Cache Entry. Para poder liberarlos, los
bloques de arreglos de Cache Entry se enlazan en una lista que de nimos del siguiente
modo:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 532 533
typedef Dnode<Cache_Entry*> Chunk_Descriptor;

Chunk_Descriptor chunk_list;
De nes:
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 de ne la lista de bloques
(arreglos de Cache Entry). Estamos listos para de nir 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 de nen 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

Captulo 5. Tablas hash

{
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();
}
De nes:
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.

La misma lase de rutinas se de ne para la lista locked list.


El n de la ola lru list no es otro que instrumentar el orden segun el a eso. Hay
varias situa iones en la uales tiene que a tualizarse esta ola:
1. Cuando se inserta un nuevo elemento en el a he, obtenemos su Cache Entry
del frente de lru list, el ual ontiene al elemento mas antiguamente a edido
-re ordemos que todas las entradas Cache Entry se meten en esta ola durante la
onstru ion del a he-.
2. Cuando referen iamos a un elemento dentro del a he, debemos espe i ar que este
es el mas re ientemente a edido; para ello, eliminamos su Cache Entry a traves del
doble enla e dlink lru y lo re-insertamos en el trasero. Esto lo realiza la siguiente
rutina:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 533 534b
534a
void do_mru(Cache_Entry * cache_entry)
{
cache_entry->link_lru()->del(); // elimine de su posici
on actual
lru_list.insert(cache_entry->link_lru()); // col
oquelo en el trasero
}
De nes:
do mru, used in hunks 535a and 536a.
Uses Cache Entry 529 and lru list 530e.

do mru() ha e a cache entry la entrada mas re ientemente a edida.

3. Cuando eliminamos un elemento del a he lo olo amos en el frente, a ion que


instrumenta la siguiente rutina:
hMiembros privados de Hash Cache<Key,Data> 529bi+
(529a) 534a 535a
534b
void do_lru(Cache_Entry * cache_entry)
{
cache_entry->link_lru()->del(); // elimine de su posici
on actual
lru_list.append(cache_entry->link_lru()); // ins
ertelo en el frente
}
Uses Cache Entry 529 and lru list 530e.

do lru() ha e a cache entry la entrada mas antiguamente a edida.

5.3. Otros usos de las tablas hash y de la dispersi


on

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;
}
De nes:

get lru entry, used in hunk 535b.


remove entry from hash table, used in hunk 537a.
Uses Cache Entry 529 , Dlink 90, do mru 534a, and lru list 530e.

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

Captulo 5. Tablas hash

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

El a he mantiene un sub- onjunto de los ultimos cache size elementos a edidos on


la esperanza de que si se referen ian de nuevo enton es no se pague un oste alto por su
busqueda en el onjunto prin ipal. Para bus ar en el a he proveemos la siguiente rutina:
hMiembros p
ubli os de Hash Cache<Key,Data> 533bi+
(529a) 535b 536b

Cache_Entry * search(const Key & key)


{
// buscar en la tabla hash
Cache_Entry * cache_entry = static_cast<Cache_Entry*>(hash_table.search(key));
if (cache_entry != NULL) // fue encontrada la clave?
{
// s
==> hacerla la m
as recientemente usada
do_mru(cache_entry);
move_to_inside_front(cache_entry);
}

return cache_entry;
}
Uses Cache Entry 529 and do mru 534a.

536b

La busqueda \refres a la entrada en el a he" en el sentido de que la ha e la mas re ientemente a edida.


Para \tran ar" una entrada; es de ir, para asegurar que ella no sea sa ada del a he
uando o urra una inser ion, se emplea el siguiente metodo:
hMiembros p
ubli os de Hash Cache<Key,Data> 533bi+
(529a) 536a 536
void lock_entry(Cache_Entry * cache_entry)
throw(std::exception, std::runtime_error, std::domain_error)
{
if (cache_entry->is_locked())
throw std::runtime_error("Cache_Entry is already locked");

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

Analogamente, la entrada se puede \destran ar":


hMiembros p
ubli os de Hash Cache<Key,Data> 533bi+
void unlock_entry(Cache_Entry * cache_entry)
throw(std::exception, std::runtime_error)
{

(529a) 536b 537a

5.3. Otros usos de las tablas hash y de la dispersi


on

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

La elimina ion de un elemento en el a he puede pare er una opera ion redundante


e inne esaria, pues basta on tan solo no a eder mas a una entrada para que esta salga
del a he. Sin embargo, el ono imiento ertero de que una lave jamas sera a edida no
solo permite disponer su entrada para una inser ion, sino que poten ia a otras laves de
bene iarse del a he. Por eso, segun la ndole de la apli a ion, puede o no ser muy valiosa
la siguiente primitiva de elimina ion:
hMiembros p
ubli os de Hash Cache<Key,Data> 533bi+
(529a) 536 537b
void remove(Cache_Entry * cache_entry)
throw(std::exception, std::runtime_error, std::domain_error)
{
if (cache_entry->is_locked())
throw std::runtime_error("Cache_Entry is already locked");

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

void expand(const size_t & plus_size)


throw(std::exception, std::range_error, std::bad_alloc)

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

Captulo 5. Tablas hash

{
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

rapidamente una lave que la mera busqueda se uen ial.

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

1. Es riba un algoritmo de elimina ion de una tabla hash on olisiones separadamente


en adenadas en listas simplemente enlazadas.
2. La fun ion de elimina ion de los TAD LhashTable<Key> y DynLhashTable<Key, Record>
no veri a si el registro a eliminar pertene e a la tabla. Dis uta diferentes enfoques
para efe tuar esta veri a ion.
3. Demuestre la proposi ion 5.2. (+)
27 Pre
ambulo

de la pel ula \Mat h Point".

540

Captulo 5. Tablas hash

4. Explique detalladamente la onstru ion de la expresion (5.27).


5. Asumiendo en adenamiento separado on listas simples y una tabla de puros punteros, al ule el onsumo de espa io. >A partir de ual valor de y ST el dire ionamiento errado es menos ostoso en espa io?
6. Considere una tabla hash on resolu ion de olisiones por en adenamiento separado
( on listas enlazadas), un tama~no de tabla de M = 13 y una fun ion hash h(k) =
k mod M.
(a) Dibuje la tabla resultante de la siguiente se uen ia de inser ion
23, 13, 20, 5, 7, 2, 40, 50, 30, 45, 12, 21, 33, 34

(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

Captulo 5. Tablas hash

[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 re naron 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
de ni 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 de ni 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 in ja y lo suba hasta
la raz. Tal opera ion se de ne 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;
}
De nes:

6.1. Equilibrio de
arboles

547

select gotoup root, used in hunk 547.


Uses LLINK 296, RLINK 296, rotate to left xt, and rotate to right xt 421.

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;

root = select_gotoup_root(root, COUNT(root) / 2);


LLINK(root) = balance_tree(LLINK(root));
RLINK(root) = balance_tree(RLINK(root));
return root;
}
De nes:

balance tree, never used.


Uses LLINK 296, RLINK 296, and select gotoup root 546b.

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.

(a) Antes de balan ear

(b) Despues de balan ear

Figura 6.1: Ejemplo de equilibrado de un arbol aleatorio mediante balance()


Si el arbol original fue onstruido a partir de una se uen ia aleatoria, enton es su altura
tiende a O(lg n) y el desempe~no de select gotoup root() llamado desde balance tree()
es O(lg n). En este momento el arbol queda parti ionado en dos partes equitativas, ada


Captulo 6. Arboles
de b
usqueda equilibrados

548

una on un onsumo de tiempo propor ional a la mitad de la entrada. Podemos, pues,


plantear la siguiente e ua ion re urrente para el desempe~no de balance tree():
T (n) =


1

si n 1
.
2T (n/2) + O(lg n) si n > 1

Suponiendo que n es una poten ia exa ta de 2, realizamos la transforma ion n = 2k, lg n =


k. Esto nos plantea:
T (2k) = 2T (2k1) + k .

Dividiendo la e ua ion entre 2k, tenemos:


T (2k1)
k
+ k
k1
2
2
T (2k2) k 1
k
=
+ k1 + k
k2
2
2
2
k3
k
T (2 ) k 2 k 1
+ k2 + k1 + k
=
2k3
2
2
2
k
X
i
= 1+
=
2i

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

La solu ion de la re urren ia depende, pues, del valor de la sumatoria Sk =


ual puede resolverse por perturba ion:
Sk =
Sk +

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

A la vez, (6.2) depende de la solu ion de:


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

Multipli ando (6.3) por 2 tenemos:


2Qk = 1 +

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

uyo valor, al sustituirlo en (6.2) nos arroja:


Sk = 2

Sustituyendo (6.6) en (6.1) tenemos:




k+1
T (n) = n + n 2 k
2


lg n + 2
= n+n 2
n
= 3n lg n 2, n 1 ;

(6.7)

resultado que es O(n lg n).


balance tree() es, pues, O(n lg n), un tiempo ostoso si la fun ion se invo a fre uentemente. Hasta el presente, nadie ha des ubierto un algoritmo a eptable que modi que un
ABB y que garanti e una ondi ion de equilibrio fuerte. Debemos, pues, relajar las ondi iones de equilibrio y ha erlas menos restri tivas. Segun las te ni as algortmi as, podemos
plantear la siguiente lasi a ion de los me anismos de equilibrio ono idos:
 Equilibrio Probabilstico: Este enfoque onsiste en tomar de isiones aleatorias

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.

 Equilibrio garantizado: En este enfoque se plantean ondi iones de equilibrio,


menos restri tivas que la fuerte, que permiten modi a iones O(lg n) sobre un arbol
binario y que a otan la altura a O(lg n). Los metodos de este tipo ofre en una
garanta O(lg n) en todas las opera iones.

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

Un arbol binario de busqueda aleatorizado (ABBA) es un arbol binario de busqueda


extendido (ABBE en x 4.11) on opera iones espe iales que garantizan que el ABBA es
un arbol aleatorio. Re ordemos que un ABBE es un arbol en el que ada nodo alma ena
la ardinalidad del arbol del ual el es raz.
Notemos la distin ion que ha emos entre \aleatorio" y \aleatorizado". Informalmente,
un arbol aleatorio es equivalente a un arbol binario de busqueda onstruido a partir de
se uen ias de inser ion aleatorias; mientras que uno aleatorizado es uno tal que sus opera iones garantizan que este siempre sea aleatorio, independientemente del tipo de opera ion.

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.

 Si n > 0, enton es T es un ABBA si y s


olo si L(T ) y R(T ) son ABBA independientes,

P(| L(T )| = i | |T | = n) =

1
,
n

0 i < n, n > 0

(6.8)

El punto ru ial de la de ni ion, espe  amente la e ua ion (6.8), es que ualquiera


de las laves del arbol tiene exa tamente la misma probabilidad de ser la raz del arbol.
Esta propiedad es fundamental para el dise~no de los algoritmos de inser ion y elimina ion.
Para produ ir un ABB, ualquier lave a insertar debe tener alguna posibilidad de devenir
la raz del arbol, o la raz de alguno de sus subarboles. Del mismo modo, uando se elimina
una lave, ualquiera de las laves restantes debe tener posibilidades de devenir raz del
arbol o de sus subarboles.
6.2.1

550

El TAD Rand Tree<Key>

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

privados de Gen Rand Tree<Key> 551 i

public:
hmiembros
};

publi os de Gen Rand Tree<Key> 551bi

h lases
De nes:

publi as de Rand Tree<Key> 551di

Gen Rand Tree, used in hunk 551d.


6.2. Arboles
aleatorizados

551a

551

El primer paso para implantar Gen Rand Tree<Key> es de nir la estru tura del nodo
que onforma un ABBA:
htpl randNode.H 551ai

DECLARE_BINNODE_SENTINEL(RandNode, 80, BinNodeXt_Data);


De nes:
RandNode, used in hunk 551d.
RandNodeVtl, used in hunk 551d.
Uses DECLARE BINNODE SENTINEL 293.

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

gsl_rng * gsl_rng_object() { return r;}

551

A traves de este puntero, el usuario puede alterar el generador a su pleno riesgo.


Los miembros dato de Gen Rand Tree<Key>:
hmiembros privados de Gen Rand Tree<Key> 551 i
(550)
Node *
tree_root;
gsl_rng * r;

551d

tree root es un apuntador a la raz de arbol aleatorizado. r es un apuntador al objeto


de genera ion de numeros pseudo aleatorios de la bibliote a gsl.
Las lases que se exportan al usuario:
h lases p
ubli as de Rand Tree<Key> 551di
(550)
template <typename Key, class Compare = Aleph::less<Key> >
class Rand_Tree : public Gen_Rand_Tree<RandNode, Key, Compare>
{ /* empty */ };

template <typename Key, class Compare = Aleph::less<Key> >


class Rand_Tree_Vtl : public Gen_Rand_Tree<RandNodeVtl, Key, Compare>
{ /* empty */ };
De nes:
Rand Tree, never used.
Rand Tree Vtl, never used.
Uses Gen Rand Tree 550, RandNode 551a, and RandNodeVtl 551a.

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 de ni 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

rn aleatorio entre 0 y n 552bi;

if (rn == n) // Gana node el sorteo de ser ra


z?
return insert_root_xt<Node, Compare> (root, node); // s
, node ser
a ra
z
Node * result;
if (Compare() (KEY(node), KEY(root))) // insertar en
arbol izquierdo?
{
result = random_insert(LLINK(root), node); // inserta en rama izquierda
if (result != Node::NullPtr) // hubo inserci
on?
{
// si ==> actualizar rama y contadores
LLINK(root) = result;
++COUNT(root);
}
}
else if (Compare() (KEY(root), KEY(node)))// insertar en
arbol derecho?
{
result = random_insert(RLINK(root), node); // inserta en rama derecha
if (result != Node::NullPtr) // hubo inserci
on?
{
// si ==> actualizar rama y contadores
RLINK(root) = result;
++COUNT(root);
}
}
else
return Node::NullPtr; // clave duplicada ==> no hay inserci
on
return root;
}
De nes:
random insert, used in hunk 553a.
Uses insert root xt 414, LLINK 296, and RLINK 296.

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 de ni 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);

random insert() es una rutina p


ubli a, lo ual permite realizar inser iones aleatorias


6.2. Arboles
aleatorizados

553a

553

en un arbol on rangos del tipo BinNodeXt x 4.11 (pagina 408).


A traves de random insert() se implanta la rutina publi a de inser ion para el tipo
Gen Rand Tree<Key>, uya espe i a ion es omo sigue:
hmiembros p
ubli os de Gen Rand Tree<Key> 551bi+
(550) 552a 553b
Node * insert(Node * p)
{
Node * result = random_insert(tree_root, p);
if (result == Node::NullPtr)
return NULL;
tree_root = result;
return p;
}
Uses random insert 552a.

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

rn aleatorio entre 1 y m + n 554ai

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;
}
}
De nes:

random join, never used.


Uses LLINK 296 and RLINK 296.

554a

554b

hGenere rn aleatorio entre 1 y m + n 554ai


const size_t rn = 1 + gsl_rng_uniform_int(r, m + n);

(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;
}
De nes:
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);

return ret_val != Node::NullPtr ? ret_val : NULL;


}
Uses random remove 554b.

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 de ni 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.

Sea y = KEY(raiz(T )). Si x > y, enton es raiz(T<) = raiz(T ) y el arbol


T> y el subarbol dere ho R(T<) se al ulan re ursivamente on la llamada
split key rec xt(R(T ), x, T<, T>) Por premisa del lema, L(T ) es aleatorio e
independiente, pues T tambien lo es. Por la hipotesis indu tiva, los arboles R(T<)
y T> son aleatorios e independientes, pues ellos son el resultado de la llamada
split key rec xt(R(T ), x, T<, T>).
Del razonamiento anterior podemos on luir que que T> es aleatorio e independiente.
>Que a er a de T<? Sabemos que sus ramas izquierda y dere ha son aleatorias e
independientes. Nos falta, enton es, demostrar que la e ua ion 6.8 se satisfa e para
1
T<. Para ello debemos veri ar que z T< , P(raiz(T<) = z) = m
donde m = |T<|.
Esto es equivalente a plantear:
P(raiz(T<) = z | raiz(T ) < x) =

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

random split(R(T ), x, T< , T> )

T<
y

T
y y>x

L(T )

L(T )

R(T )

R(T<)

T>

=
(a) Antes de parti ionar

(b) Despues 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 de ni 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

que x sea raz de T ). As pues:


y T , P(raiz(T ) = y) =

1
n
|{z}

P(raiz(T)=y)

n
+ 1}
|n {z

1
n+1

P(raiz(T )=y)

En este aso x sera insertado en alguno de los subarboles de T y, por la hipotesis


indu tiva, el resultado sera un arbol aleatorio.
2. Si raiz(T ) = x, enton es x debe ganar el sorteo node become root; esto su ede
1
on probabilidad P(raiz(T ) = x) = n+1
, que es lo esperado segun el algoritmo.


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.

El orolario onlleva impli a iones importantes. En primer lugar, ualquiera sea el


orden de inser ion, siempre obtendremos un arbol aleatorio. En otras palabras, el arbol
resultante es equivalente a un ABB onstruido a partir de una se uen ia de inser ion
aleatoria. Conse uentemente, todos los resultados ono idos para un ABB son apli ables;
el mas importante de ellos es la proposi ion 4.13 que nos a ota la altura del arbol a O(lg n).
En uanto a la elimina ion, el primer paso para analizarla es estudiar la union ex lusiva
aleatoria, la ual se analiza en el lema siguiente:
Lema 6.2 (Martnez y Roura 1998 [9]) Sean T< y T> dos arboles aleatorios independientes tal que kl T<, kr T>, kl < kr; es de ir, todas las laves de T< son estri tamente menores que las laves de T>. Enton es, T = random join exclusive(T<, T>)

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:

1. Si m = 0 y n = 0, enton es random join exclusive(T<, T>) = , el ual es,


por de ni ion, aleatorio.
2. Si m = 0 o n = 0, enton es random join exclusive(T<, T>) retorna el arbol
no va o del par, el ual es, por premisa del lema, aleatorio. El lema es, pues,
ierto para m = 0 o n = 0.
 m > 0 y n > 0: Sea a = KEY (raiz(T>)), b = KEY (raiz(T>)) y T =
random join exclusive(T<, T>). Asumamos que a gana el sorteo efe tuado en
el random join exclusive() desarrollado en x 6.2.1.2, lo que impli a que a sera la
raz de T . Podemos bosquejar el arbol resultante del siguiente modo:

T = random join exclusive(T<, T>)


a
random join exclusive(R(T<), T>)

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 de ni 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:

1. Si raiz(T ) 6= x, enton es x es eliminado de uno de los subarboles de T y, por la


hipotesis indu tiva, esta elimina ion arroja un arbol aleatorio.
2. Si raiz(T ) = x, enton es remove(T , x) efe tua la llamada random join(L(T ), R(T ))
la ual, por el lema 6.2, es un arbol aleatorio. Podemos a rmar, enton es, que
L(T ) y R(T ) son arboles aleatorios independientes. Nos resta por omprobar
1
que y T , P(raiz(T ) = y) = n1
, lo ual se veri a omo sigue:
P(raiz(T ) = y) = P(raiz(T ) = y | raiz(T ) = x) P(raiz(T ) = x) +
P(raiz(T ) = y | raiz(T ) 6= x) P(raiz(T ) 6= x)
1
+
= P(raiz(random join(L(T ), R(T ))) = y)
n
n1
P(raiz(T ) = y | raiz(T ) 6= x)
n
1
1
1
n1
1
=

=

n1 n
n1
n
n1

De las proposi iones mostradas, podemos enun iar el siguiente orolario:


Corolario 6.2 Si T es un arbol aleatorizado ualquiera, enton es ualquier se uen ia de

inser iones y elimina iones produ e un arbol aleatorio.

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

Figura 6.2: Un arbol aleatorizado de 512 nodos


opera iones de inser ion y elimina ion depende de la altura, podemos on luir, tambien,
que en promedio la inser ion y la elimina ion son O(lg n).
Los arboles aleatorizados tienen dos ostes adi ionales respe to a los binarios lasi os.
En primer lugar, la estru tura de nodo de un ABBA requiere espa io adi ional para alma enar la ardinalidad. Esto puede representar un oste rti o uando existan restri iones de
espa io. En segundo lugar, un ABBA requiere opera iones adi ionales respe to a un ABB.
Durante la inser ion, es ne esario efe tuar una parti ion y a tualizar las ardinalidades.
Conse uentemente, para un orden de inser ion aleatorio, un ABB tiene mejor rendimiento
que un ABBA.
A pesar de las restri iones anteriores, un ABBA tiene la gran ventaja de eliminar el
peligro representado por el sesgo en la se uen ia de inser ion. Esta ventaja lo ha e una
estru tura idonea omo tabla de smbolos uando hay in ertidumbre sobre la se uen ia de
inser ion y es util ono er la se uen ia ordenada de los smbolos. En a~nadidura, el ontador
presente en ada nodo fa ilita perfe tamente el a eso por posi ion y sus opera iones
aso iadas tal omo se expli o en la se ion 4.11.

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 de ni ion:
Definici
on 6.3 (Treap) Sea T un arbol binario en ual ada nodo ni tiene dos ampos

a saber:

1. KEY(ni) K es la lave de busqueda, donde K es un onjunto ordenable ualquiera.


2. PRIO(ni) P es la prioridad del nodo, donde P es un onjunto ordenable ualquiera.
Enton es, T es un treap si y solo si:
 T ABB seg
un las laves K.
 ni T, PRIO(p) PRIO(L(p)) y PRIO(p) PRIO(R(p)) .


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>

El primer paso en el dise~no de un TAD que nos modeli e un treap es determinar la


estru tura del nodo que lo onforma. Hay dos maneras generales de implantar un treap:
re ursiva o iterativamente. Ambos enfoques son muy sen illos y omparten la estru tura
del nodo. Por esa razon, mantendremos la estru tura del nodo en un ar hivo separado
denominado htreapNode.H 561i, el ual se estru tura omo sigue:
htreapNode.H 561i
const long Max_Priority = ULONG_MAX; // Maximum priority
const long Min_Priority = 0; // Minimum priority
class TreapNode_Data
{
unsigned long priority;
public:
TreapNode_Data () : priority (Max_Priority) { /* empty */ }
TreapNode_Data (SentinelCtor) : priority (Max_Priority) { /* empty */ }
unsigned long & getPriority () { return priority; }
void reset () { priority = Max_Priority; }
};
DECLARE_BINNODE_SENTINEL(TreapNode, 80, TreapNode_Data);
# define PRIO(node)

( (node) -> getPriority () )

hVeri a i
on de treap (never de ned)i
De nes:
PRIO, used in hunks 563{65.
TreapNode, used in hunk 562d.
Uses DECLARE BINNODE SENTINEL 293 and SentinelCtor.

Aqu se de ne 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
De nes:

publi as de Gen Treap<Key> 562di

Gen Treap, used in hunk 562d.

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 de nido en el
ar hivo tpl ran array.H.
Antes de implantar los onstru tores, es ne esario de nir los atributos internos de
Gen Treap<Key>:
hMiembros de Gen Treap<Key> 562bi
(562a) 562
Node
Node *
Node *&

head;
head_ptr;
tree_root;

priority fct es un apuntador a la fun ion de genera ion de n


umeros aleatorios, head
es un nodo abe era, padre entinela de la raz, head ptr es un apuntador al nodo abe era
y tree root es una referen ia apuntador a la raz del treap.

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 */ };

template <typename Key, class Compare = Aleph::less<Key> >


class Treap_Vtl : public Gen_Treap<TreapNodeVtl, Key, Compare>
2 Gnu Scientific Library

[6.

6.3. Treaps

563

{ /* empty */ };
De nes:
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

Estamos preparados para implantar el algoritmo de inser ion:


hInser i
on en Gen Treap<Key> 563i
static Node * insert(Node * root, Node * p)
{
if (root == Node::NullPtr)
return p;

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 de ni 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 hasta que devenga hoja 565bi

p->reset();
return p;
}

565a

El primer bloque, hBus ar nodo a eliminar p 565ai, se en arga de ubi ar el nodo a


suprimir p y se de ne omo sigue:
hBus ar nodo a eliminar p 565ai
(564b)
Node ** pp = &RLINK(head_ptr);
Node * p = tree_root;
while (p != Node::NullPtr)
if (Compare() (key, KEY(p)))
{
pp = &LLINK(p);
p = LLINK(p);
}
else if (Compare() (KEY(p), key))
{
pp = &RLINK(p);
p = RLINK(p);
}
else
break;
Uses LLINK 296 and RLINK 296.

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.

Es el momento de realizar una observa ion interesante: la elimina ion de un nodo


o urre exa tamente en el mismo sitio donde este se hubiese insertado. Re ordemos que la

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

Figura 6.5: Un treap de 512 nodos


6.3.5

Prioridades implcitas

Los treaps son arboles aleatorios. Conse uentemente, el desempe~no de la busqueda es


equivalente a los arboles aleatorizados. En uanto al espa io, los ostes tambien son equivalentes, pues ambos arboles requieren alma enar un entero adi ional por nodo: en los
arboles aleatorizados es la ardinalidad; en los treaps es la prioridad.
Sorprendentemente, en un treap podemos obviar la prioridad si la substituimos por
una fun ion hash que transforme la lave a un valor de prioridad. De esta manera, la
prioridad no ne esita alma enarse, pues esta puede al ularse impl itamente ada vez que
se requiera. El treap o upara, enton es, un espa io equivalente al de un ABB.
Para que este enfoque sea e az, es ne esaria una buena fun ion hash. En este sentido,
toda la teora de fun iones hash impartida en x 5.2 (pagina 515) es apli able.

6.4

Arboles
AVL

Un arbol AVL, o un AAVL, es una lase espe ial de arbol binario de busqueda de nido
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 de nido 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

Figura 6.6: Un arbol AVL de 512 nodos

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 de ne 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

De nes:


Captulo 6. Arboles
de b
usqueda equilibrados

570

AvlNode, used in hunk 570b.


Uses DECLARE BINNODE 291b.

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

que es la suma de todos los tipos que ontiene el nodo.


Con nes de veri a ion, podemos de nir una fun ion que nos indague si un arbol
AVL satisfa e la ondi ion (6.9):
hutilitarios avl 570ai
(569)
template <class Node>
bool is_avl(Node* p)
{
if (p == Node::NullPtr)
return true;

if (DIFF(p) < -1 or DIFF(p) > 1)


return false;
const signed char hL = computeHeightRec(LLINK(p));
const signed char hR = computeHeightRec(RLINK(p));
if (not (DIFF(p) == hR - hL))
return false;
return is_avl(LLINK(p)) and is_avl(RLINK(p));
}
De nes:
is avl, never used.
Uses computeHeightRec 305a, LLINK 296, and RLINK 296.

6.4.1

570b

El TAD Avl Tree<Key>

El TAD Gen Avl Tree<Key> de ne una lase generi a de arbol AVLindependiente de que
el nodo sea virtual o no. La lase en uestion se de ne en el ar hivo htpl avl.H 570bi, que
se de ne 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

class Avl_Tree : public Gen_Avl_Tree<AvlNode, Key, Compare>


{ /* empty */ };
template <typename Key, class Compare = Aleph::less<Key> >
class Avl_Tree_Vtl : public Gen_Avl_Tree<AvlNodeVtl, Key, Compare>
{ /* empty */ };
} // end namespace Aleph
Uses AvlNode 569.

571a

Las lases Avl Tree Vtl<Key> y Avl Tree<Key> de nen 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;
De nes:
avl stack, used in hunks 571 , 572b, 576{79, and 582.
Uses FixedStack 131a.

avl stack siempre guardara el amino de b


usqueda desde la raz hasta el nodo insertado o

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; }

void clean_avl_stack() { avl_stack.popn (avl_stack.size() - 1); }


De nes:
avl stack empty, used in hunks 576 and 577b.
clean avl stack, used in hunks 572a, 576{78, and 582.
Uses avl stack 571a.

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;
}

Node *pp = search_and_stack_avl(KEY (p));


if (cmp (KEY(p), KEY(pp)))
LLINK (pp) = p;
else if (cmp (KEY(pp), KEY(p)))
RLINK(pp) = p;
else
{ // clave duplicada
clean_avl_stack();
return NULL;
}
restore_avl_after_insertion(KEY(p));
return p;
}
Uses clean avl stack 571 , LLINK 296, restore avl after insertion 576, RLINK 296,
and search and stack avl 572b.

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();
}
De nes:

search and stack avl, used in hunks 572a and 578a.


Uses avl stack 571a, LLINK 296, and RLINK 296.

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)

Figura 6.7: Primer aso de inser ion en un arbol AVL


La viola ion des rita en la gura 6.7(a) se orrige mediante una rota ion ha ia la
izquierda del nodo y, uyo resultado general se ilustra en la gura 6.7(b). Notemos que en
las dos guras -6.7(a) y 6.7(b)- el valor de altura global h + 2 permane e inta to. Por lo
tanto, si la as enden ia de T es AVL antes de la inser ion, tambien lo es despues. En otras
palabras, para el aso de desequilibrio de la gura 6.7(a), la orre ion resultante despues
de la rota ion produ e un arbol enteramente AVL.
El desequilibrio ilustrado en la gura 6.7(a) tiene un aso simetri o que o urre uando
la inser ion se produ e por la izquierda. Gra amente, el desequilibrio y su orre ion se


Captulo 6. Arboles
de b
usqueda equilibrados

574

ven igual que las guras 6.7(a) y 6.7(b) re ejadas en un espejo.


Los valores de los fa tores permiten identi ar la situa ion de desequilibrio mostrada en
la gura 6.7(a). Dado un nodo x on un desequilibrio, podemos ara terizar las siguientes
situa iones y sus orrespondientes a iones:
1. Si DIFF(x) == 2 and DIFF(y) == 1 = se efe tua una rota ion simple ha ia la
izquierda.
2. Si DIFF(x) == -2 and DIFF(y) == -1 = se efe tua una rota ion simple ha ia la
dere ha.
El segundo aso de viola ion de la ondi ion AVL se ilustra en la gura 6.8(a). El arbol
AVL T sufre un desequilibrio por la dere ha en el nodo x, ausado por un aumento de
altura del nodo z que estaba perfe tamente equilibrado. La inser ion puede o urrir por
ualquiera de los lados de z.
T
x
2

T
z
0

y
1

z
1

x
0

y
1

h
=
(a)

(b)

Figura 6.8: Segundo aso de inser ion en un arbol AVL


El desequilibrio se orrige mediante dos rota iones su esivas del nodo z. Primero, el
nodo z se rota ha ia la dere ha, luego ha ia la izquierda. La orre ion se ilustra en
la gura 6.8(b). Antes y despues de la orre ion, la altura global del arbol, h + 2, es la
misma. Conse uentemente, si la as enden ia de T es AVL, enton es, despues de la inser ion
y posterior orre ion, la as enden ia tambien es AVL.
El aso ilustrado en las guras 6.8(a) y 6.8(b) tiene su orrespondiente simetri o por
la izquierda, el ual es equivalente a re ejar las guras en un espejo.
La identi a ion de la situa ion de desequilibrio de la gura 6.8(a), junto on su orre ion, se ara teriza de la siguiente manera:
1. Si DIFF(x) == 2 and DIFF(y) == -1 = primero se rota el nodo z ha ia la dere ha
y luego ha ia la izquierda.
2. Si DIFF(x) == -2 and DIFF(y) == 1 = primero se rota el nodo z ha ia la
izquierda y luego ha ia la dere ha.


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
};
De nes:
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;
}
De nes:
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

static Node * restore_avl(Node * p, Node * pp)


{
Node ** link = LLINK(pp) == p ? &LLINK(pp) : &RLINK(pp);
switch (rotation_type(p))
{
case ROTATE_LEFT:
case ROTATE_RIGHT:
case DOUBLE_ROTATE_LEFT:
case DOUBLE_ROTATE_RIGHT:
default:
ERROR("Invalid rotation
break;
}

return
return
return
return

*link
*link
*link
*link

=
=
=
=

rotateLeft(p);
rotateRight(p);
doubleRotateLeft(p);
doubleRotateRight(p);

type");

return NULL;
}
De nes:

restore avl, used in hunks 577b and 582.


Uses DOUBLE ROTATE LEFT 575a, DOUBLE ROTATE RIGHT 575a, doubleRotateLeft, doubleRotateRight,
LLINK 296, RLINK 296, ROTATE LEFT 575a, ROTATE RIGHT 575a, rotateLeft, rotateRight,
and rotation type 575b.

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

fa tor del padre del nodo insertado 577ai

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

De nes:

restore avl after insertion, used in hunk 572a.


Uses avl stack 571a, avl stack empty 571 , and clean avl stack 571 .

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

an estros de pp 577bi revisa ada an estro gpp de pp y a tualiza su fa tor.

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();

// actualizar factores de equilibrio


if (key > KEY(gpp))
DIFF(gpp)++;
else
DIFF(gpp)--;
if (DIFF(gpp) == 0)
break; // no se necesita reajuste
else if (DIFF(gpp) == -2 or DIFF(gpp) == 2) // se viola condici
on AVL?
{
// se requiere reajuste
Node *ggpp = avl_stack.pop();
restore_avl(gpp, ggpp);
break;
}
}
while (not avl_stack_empty());
Uses avl stack 571a, avl stack empty 571 , and restore avl 575 .

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

pro edimiento se plantea del siguiente modo:


hmiembros p
ubli os de Gen Avl Tree<Key> 572ai+

(570b) 572a

Node * remove(const Key& key)


{
if (root == Node::NullPtr)
return NULL;

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 in jo, 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 in jo. 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) in jo:
hmiembros privados de Gen Avl Tree<Key> 571ai+
(570b) 576 582
Node* swapWithSuccessor(Node * p, Node *& pp)
{

// Referencia al tope de la pila, pues p ser


a intercambiado con el
// sucesor y la posici
on en la pila la ocupar
a el sucesor de p
Node *& ref_to_stack_top = avl_stack.top();
// encuentre el sucesor a la vez que actualiza la pila
Node *fSucc = p;
// padre del sucesor
Node *succ = RLINK(p); // b
usqueda comienza desde RLINK(p)
avl_stack.push(succ);
while (LLINK(succ) != Node::NullPtr) // descienda hasta el m
as a la izquierda
{
fSucc = succ;
succ = LLINK(succ);
avl_stack.push(succ);
}
// actualice antigua entrada de pila ocupada por p. Estas operaciones


Captulo 6. Arboles
de b
usqueda equilibrados

580

// son equivalentes a intercambiar el antiguo tope (antes de buscar


// sucesor) con el actual
ref_to_stack_top = succ;
avl_stack.top() = p;
// actualice el nuevo hijo de pp (el sucesor)
if (LLINK(pp) == p)
LLINK(pp) = succ;
else
RLINK(pp) = succ;
// intercambie las ramas izquierdas
LLINK(succ) = LLINK(p);
LLINK(p)
= Node::NullPtr;
// actualice ramas derechas
if (RLINK(p) == succ)
{
// sucesor es exactamente el hijo derecho de p
RLINK(p)
= RLINK(succ);
RLINK(succ) = p;
pp
= succ;
}
else
{
// sucesor es el descendiente m
as a la izquierda de RLINK(p)
Node *succr = RLINK(succ);
RLINK(succ) = RLINK(p);
LLINK(fSucc) = p;
RLINK(p)
= succr;
pp
= fSucc;
}
DIFF(succ) = DIFF(p); // intercambie factores de equilibrio
return succ;
}
Uses avl stack 571a, LLINK 296, and RLINK 296.

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)

Figura 6.9: Primer aso de elimina ion en un arbol AVL


T
x
2

T
z
0

y
1
h

h1

=
(a)

(b)

Figura 6.10: Segundo aso de elimina ion en un arbol AVL


La gura 6.10(a) muestra el segundo aso de elimina ion en un AAVL, el ual tambien
es similar al segundo aso de la inser ion mostrado en la gura 6.8(a). En la gura 6.10(a),
la rama sufre una perdida de altura debida a una elimina ion o a un ajuste de equilibrio.
A diferen ia de la gura 6.8(a), el nodo z puede estar perfe tamente equilibrado o sufrir
un ligero y no violatorio desequilibrio. En todo aso, la altura de z es h + 1. La orre ion
se presenta en la gura 6.10(b), que es la misma que para la inser ion presentada en la
gura 6.8(b): una rota ion doble, ruzada, del nodo z ha ia la dere ha y luego ha ia la
izquierda.
Similar al primer aso, el segundo tambien redu e la altura general del arbol T de h + 3
a h + 2. Conse uentemente, tambien es ne esario revisar la as enden ia de T en busqueda
de viola iones de la ondi ion AVL ausadas por el ajuste. Dado un nodo violatorio de la
ondi ion AVL, el segundo aso es unvo amente identi able: los signos del nodo violatorio
y su hijo son inversos. Puesto que esta es la misma situa ion que on el segundo aso de
viola ion en la inser ion, tambien podemos reutilizar la rutina rotation type().


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)

Figura 6.11: Ter er aso de elimina ion en un arbol AVL

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

Node* ppp = avl_stack.popn(3); // elimina de pila p, su padre y su abuelo


while (true)
{
// actualice factores de equilibrio
if (key >= KEY(pp))
DIFF(pp)--;
else
DIFF(pp)++;


6.4. Arboles
AVL

583

if (DIFF(pp) == -2 or DIFF(pp) == 2) // verifique una violaci


on AVL
pp = restore_avl(pp, ppp);
if (DIFF(pp) != 0 or pp == root)
break; // altura global del
arbol no ha cambiado ==> terminar
pp = ppp; // avance al pr
oximo ascendiente
ppp = avl_stack.pop();
}
clean_avl_stack();
}
De nes:

restore avl after deletion, used in hunk 578a.


Uses avl stack 571a, clean avl stack 571 , and restore avl 575 .

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-

dos, enton es:

lg (n + 1) h(T ) < 1.4404 lg (n + 2) 0.3277


Demostraci
on

(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

O(lg n). Despues, debemos retro eder en el amino de b


usqueda, veri ar si hay desequi-

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 de ne 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
in ja. 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 in ja, Fib(7 + 2) = 33 es la
antidad de nodos que pre eden al arbol izquierdo. Pero, por de ni 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 de ni 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 proposi ion es, enton es, ierta para todo k

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

 k > 0: Ahora asumimos que la proposi i


on es ierta para todo k y veri amos para
k + 1.

Sea Tk+1 el arbol de Fibona i de orden k + 1. Enton es, h(Tk+1) = 1 +


max(h(Tk), h(Tk1)). Apli ando la hipotesis indu tiva, max(h(Tk), h(Tk1)) =
h(Tk) = k, por lo que h(Tk+1) = 1 + k



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 de ni 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 de ni 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

La esen ia de la demostra ion reside en el he ho de que todos los nodos no hoja de un


arbol de Fibona i tienen fa tor de equilibrio 1. Esto se eviden ia en el lema que sigue:
Lema 6.4 Sea T es un arbol de Fibona i de ardinalidad n. Sea Ni el onjunto de todos
los nodos no hojas de T . Enton es, para n > 1:
ni T, ni Ni,

(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 in jo, 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 in jo,
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)

la ual, al dividir por c k, puede simpli arse a la legendaria \e ua ion divina":


2 1 = 0 ,

(6.17)

uyas ra es son:

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

De esta manera, la solu ion general es:


(6.21)

^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
^

De la e ua ion (6.22) tenemos:

(6.22)
(6.23)
(6.24)

c2 = c1

y sustituyendo (6.24) en (6.23):


c1 c1
^ = 1 = c1( )
^ = 1 = c1 =

1
1
=

^
5

(6.25)

Combinando (6.25) y (6.24) en (6.21):


1
5

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)

lo ual nos permite ompletar la demostra ion de la proposi ion 6.4.


Ahora retomamos la desigualdad (6.12):
n |Th| = Fib(h + 2) 1 =


h+2

>
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

numero de nodos negros.

 Nodo externo negro: Los nodos externos son negros. En otras palabras, todo

puntero nulo es negro.

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

Figura 6.14: Un arbol rojo-negro


La ondi ion negra justi a un nuevo on epto: la altura negra, la ual se de ne omo
sigue:
Definici
on 6.8 (Altura negra en un nodo rojo-negro) Sea T un arbol rojo-negro y
ni T un nodo ualquiera de T . Se de ne la \altura negra" de ni, denotada omo bh(ni),
omo el numero de nodos negros desde ni hasta ualquier nodo externo de T .

Notemos que la altura negra esta de nida 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

El primer paso ha ia el dise~no de un tipo abstra to de dato es espe i ar un nodo


rojo-negro:
hrbNode.H 591i
typedef unsigned char Color;

# define COLOR(p) ((p)->getColor())


# define RED
(0)
# define BLACK
(1)
class RbNode_Data
{
Color color; // RED o BLACK
RbNode_Data() : color(RED) { /* empty */ }
RbNode_Data(SentinelCtor) : color(BLACK) { /* empty */ }
Color& getColor() { return color; }
};
DECLARE_BINNODE_SENTINEL(RbNode, 128, RbNode_Data);

arboles rojo-negro (never de ned)i


hUtilitarios 
De nes:
RbNode, used in hunk 592a.
Uses DECLARE BINNODE SENTINEL 293 and SentinelCtor.
La lase RbNode esta dise~nada para umplir las ondi iones de base. El onstru tor
por omision siempre rea un nodo rojo. Un nuevo nodo rojo no altera la altura negra, por
onsiguiente, se preserva la ondi ion negra.
Intuitivamente podemos apre iar el equilibrio de un arbol rojo-negro: todo nodo esta
perfe tamente \negro"-equilibrado; es de ir, para todo nodo, la diferen ia de alturas negras
entre sus dos ramas es nula. De he ho, el arbol rojo-negro mostrado en la gura 6.15 esta
a eptablemente equilibrado.

Figura 6.15: Un arbol rojo-negro de 512 nodos

6.5.1

El TAD Rb Tree<Key>

EL TAD Gen Rb Tree<Key> de ne un arbol rojo-negro uyas opera iones prin ipales son
en fun ion de nodos rojo-negro de nidos 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
};

privados de Gen Rb Tree<Key> 592bi


publi os de Gen Rb Tree<Key> 593i

template <typename Key, class Compare = Aleph::less<Key> >


struct Rb_Tree : public Gen_Rb_Tree<RbNode, Key, Compare>
{ /* Empty */ };

template <typename Key, class Compare = Aleph::less<Key> >


struct Rb_Tree_Vtl : public Gen_Rb_Tree<RbNodeVtl, Key, Compare>
{ /* Empty */ };
De nes:
Gen Rb Tree, never used.
Rb Tree, never used.
Rb Tree Vtl, never used.
Uses RbNode 591.

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

else if (cmp (KEY(p), key))


p = RLINK(p);
else
return p;
}
while (p != Node::NullPtr);
return rb_stack.top();
}
De nes:
search and stack rb, used in hunks 593 and 598.
Uses LLINK 296 and RLINK 296.

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

if (hpp es negro 595di)


break; // el
arbol es rojo-negro ==> algoritmo termina
if (hp es hijo de la raz 595ei)
{
h olorear raz de negro 595f i
break; // ahora el
arbol es rojo-negro
}
hobtener

ppp (el abuelo de p) 595bi

hobtener

spp (el hermano de pp) 595 i

if (hto de p es rojo 595gi)


{
hinter ambiar olores entre los niveles 596ai
p = ppp;
continue; // avanzar a pr
oximo ancestro y verificar violaciones
}
hobtener

pppp (el bisabuelo de p) 596bi

hveri ar

spp negro on alinea ion ompleta 597ai

hveri ar

spp negro on alinea ion in ompleta 597bi

break; // aqu
el
arbol es rojo-negro
}
rb_stack.empty();
}
De nes:
fix red condition, used in hunk 593.

594b

El algoritmo toma omo entrada un nodo p, que es el nodo a insertar. La salida es un


arbol rojo-negro on el nodo p insertado. La rutina requiere algunas variables adi ionales
que se espe i an a ontinua ion:
hvariables de x red ondition 594bi
(594a)


6.5. Arboles
rojo-negro

595

Node *pp, // padre de p


*ppp, // abuelo de p
*spp; // t
o de p (hermano de pp)

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

Del mismo modo, el siguiente nodo en la pila sera ppp:


hobtener ppp (el abuelo de p) 595bi

(594a)

ppp = rb_stack.pop();

595

El al ulo del hermano de pp requiere a su padre ppp:


hobtener spp (el hermano de pp) 595 i

(594a)

spp = LLINK(ppp) == pp ? RLINK(ppp) : LLINK(ppp);


Uses LLINK 296 and RLINK 296.

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

hpp es negro 595di


COLOR(pp) == BLACK

(594a)

En este aso el arbol resultante es rojo-negro y el algoritmo puede terminar.


De lo ontrario pp es rojo y hay una viola ion de la ondi ion roja que debe ser
orregida.
Caso 2:
595e

hp

es hijo de la raz 595ei

(594a)

pp == root

root

root
pp
p

p
=

(a)

(b)

Figura 6.16: Colora ion de la raz


Esta situa ion se muestra en la gura 6.16 y se resuelve oloreando la raz de negro:
595f
h olorear raz de negro 595f i
(594a)
COLOR(pp) = BLACK;

El arbol resultante es rojo-negro y el algoritmo termina.


Caso 3:
595g

hto de p es rojo 595gi


COLOR(spp) == RED

(594a)


Captulo 6. Arboles
de b
usqueda equilibrados

596
ppp

ppp

pp

spp

pp

spp

pp

ppp

spp

pp

spp

ppp

Figura 6.17: Inter ambio de olores entre los niveles


En esta ir unstan ia se realiza un inter ambio de olores entre los niveles. Es de ir, el
abuelo p deviene rojo y sus dos hijos devienen negros. Los dos asos posibles se muestran
en la gura 6.17 y su solu ion es omo sigue:
hinter ambiar olores entre los niveles 596ai
(594a)
596a
COLOR(ppp) = RED;
COLOR(pp) = BLACK;
COLOR(spp) = BLACK;

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

hveri ar spp negro on alinea i


on ompleta 597ai
if (LLINK(pp) == p and LLINK(ppp) == pp)
{
rotate_to_right(ppp, pppp);
COLOR(pp) = BLACK;
}
else if (RLINK(pp) == p and RLINK(ppp) == pp)
{
rotate_to_left(ppp, pppp);
COLOR(pp) = BLACK;
}
Uses LLINK 296, RLINK 296, rotate to left, and rotate to right 419.

(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
{

spp negro on alinea ion in ompleta 597bi

(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

En prin ipio, la elimina ion es similar a la de un arbol binario estandar. Luego, si es


ne esario, se realizan los ajustes que restauren las ondi iones romati as.
hmiembros p
ubli os de Gen Rb Tree<Key> 593i+
(592a) 593
Node* remove(const Key& key)
{
if (root == Node::NullPtr)
return NULL;

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

Figura 6.20: Zona de familiares donantes de nodo rojo


600a

hmiembros privados de Gen Rb Tree<Key> 592bi+


void fix_black_condition(Node* p)
{
if (hp es rojo 600bi)
h olorear p de negro y terminar 601ai;
hvariables

de x bla k ondition 601bi

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

if (hnp es rojo 602bi)


hrotar np, olorear y

terminar 603ai;

if (hsnp es rojo 603bi)


hrotar doble snp, olorear
if (hpp es rojo 604bi)
hinter ambiar olores
}
}
De nes:

(592a) 594a

hdesplazar

y terminar 604ai;

de p y pp, luego terminar 604 i;

de it ha ia pp 604di

fix black condition, used in hunk 598.

600b

La entrada de la rutina es el nodo p on un de it en un nodo negro que debe ser


restaurado. La salida es un arbol rojo-negro. La rutina omienza por evaluar:
hp es rojo 600bi
(600a)
COLOR(p) == RED

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

h olorear p de negro y terminar 601ai


{
COLOR(p) = BLACK;
return;
}

(600a)

La situa ion junto on su orre ion se ilustran en la gura 6.21.


pq
RN

q
p

pq
RN

Nodo a eliminar

=
Figura 6.21: Elimina ion de un nodo negro on hijo rojo

601b

En el aso ontrario, el algoritmo entra en un i lo que bus a un nodo rojo en la zona


enmar ada en la gura 6.20 para intentar sustituir al nodo negro y as ompensar el de it.
Este i lo requiere las siguientes variables:
hvariables de x bla k ondition 601bi
(600a)

Node *pp = rb_stack.popn(2); // pops parent of deleted node


Node *sp;
// ps sibling
Node *np, *snp; // ps nephews
Uses sibling 323 .

601

El hermano del nodo de itario es al ulado segun:


h omputar hermano de p 601 i

(600a)

sp = LLINK(pp) == p ? RLINK(pp) : LLINK(pp);


Uses LLINK 296 and RLINK 296.

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

hhermano de p es rojo 601di


COLOR(sp) == RED

La situa ion y su orre ion se muestran en la gura 6.22.


601e
hrotar sp ha ia el lado de p e inter ambiar olores 601ei
{

Node *&ppp = rb_stack.top();


if (LLINK(pp) == p)
{
sp = LLINK(sp);
ppp = rotate_to_left(pp, ppp);

(600a)

(600a)


Captulo 6. Arboles
de b
usqueda equilibrados

602

sp
pp
p

pp
p

sp

Figura 6.22: Caso uando hermano de p es rojo

}
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
de nitivo 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

hnp es rojo 602bi


COLOR(np) == RED

(600a)

La situa ion se ilustra en la gura 6.23 y se identi a porque np esta alineado ha ia la


dere ha on sp. La solu ion onsiste en rotar np ha ia el lado de p y olorear de negro np


6.5. Arboles
rojo-negro

603

pp
RN

p
-

pp

sp

np

np

sp
RN

Figura 6.23: Caso uando np es rojo


y pp. Con esto ganamos el nodo negro pp en el amino ha ia p, lo que ompensa el de it:
603a
hrotar np, olorear y terminar 603ai
(600a)
{

Node *ppp = rb_stack.top();


if (RLINK(sp) == np)
rotate_to_left(pp, ppp);
else
rotate_to_right(pp, ppp);
COLOR(sp) = COLOR(pp);
COLOR(pp) = BLACK;
COLOR(np) = BLACK;
return;
}
Uses RLINK 296, rotate to left, and rotate to right 419.

Caso 3:
hsnp es rojo 603bi
COLOR(snp) == RED

603b

pp
RN

p
-

(600a)

sp

snp
RN

pp

snp

np

np

sp

Figura 6.24: aso uando snp es rojo


La situa ion se ilustra en la gura 6.24; es muy similar a hnp es rojo 602bi ex epto que no


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
{

Node * ppp = rb_stack.top();


if (LLINK(sp) == snp)
{
rotate_to_right(sp, pp);
rotate_to_left(pp, ppp);
}
else
{
rotate_to_left(sp, pp);
rotate_to_right(pp, ppp);
}
COLOR(snp) = COLOR(pp);
COLOR(pp) = BLACK;
return;
}
Uses LLINK 296, rotate to left, and rotate to right 419.

Caso 4:
604b

hpp es rojo 604bi


COLOR(pp) == RED

(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
=

Figura 6.25: Caso uando el padre de p es rojo


pp
p
snp

sp

p
np

pp
-

snp

sp
np

Figura 6.26: Desplazamiento del de it ha ia pp


El algoritmo itera si no se en uentran nodos rojos en la zona enmar ada en la
gura 6.20. La antidad maxima de itera iones depende de si hay o no nodos rojos en
las inmedia iones del amino de busqueda a la lave eliminada. En el peor de los asos,
puede ser ne esario subir hasta la raz. En esta situa ion, el de it o urre para todos los
aminos desde la raz hasta ualquier nodo nulo, por lo que no hay viola ion de la ondi ion
negra.
El amino mas largo se da uando la elimina ion o urre en una de las hojas mas
profundas en altura negra. Esto impli a que del lado opuesto a p el arbol debe estar
perfe tamente balan eado, pues si no la zona no sera ompletamente negra. El lema 6.8
nos mostrara que la altura de la zona ompletamente negra es h/2 para el peor arbol.
La proposi ion 6.9 nos mostrara que la altura maxima es 2 lg (n + 2) 2. As pues, la
elimina ion es O(lg n) para el peor aso.
6.5.2

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

El enfoque de la demostra ion es similar al utilizado en los arboles AVL


(proposi ion 6.4). Es de ir, vamos a determinar ual es el arbol rojo-negro de altura h on
el maximo numero de nodos. Para ello, debemos identi ar un arbol rojo-negro rti o.

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. L(T ) no tiene nodos rojos.


2. L(T ) es un arbol perfe tamente equilibrado y ompleto.
Demostraci
on

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

Figura 6.27: Forma de un arbol rojo-negro rti o


Este lema demuestra que la forma de un arbol rojo-negro rti o es la de la gura 6.27.
Es de ir, la rama de menor altura es enteramente negra y perfe tamente balan eada. Note
que el lema 6.7 tambien indi a que la forma del subarbol dere ho de la gura 6.27 es, en
terminos re ursivos, similar a la gura 6.27. Esta observa ion es muy importante porque
nos permitira al ular una ota a la antidad de nodos de nodos que tiene un arbol rti o
en fun ion de su altura.
Si T es rojo-negro, enton es bh(L(T )) = bh(R(T )). Si, ademas, T es rti o, enton es
bh(R(T )) = h(L(T )), pues, por el lema 6.7, L(T ) solo tiene nodos negros. Por el lema 6.6,
sabemos que h(R(T )) = h(T ) 1. Del mismo modo, tambien por el lema 6.6, si R(T ) es
rti o, enton es este debe tener la forma demostrada por el lema 6.7.
Lema 6.8 Sea T un arbol rojo-negro rti o de altura h(T ) = h. Enton es,
$ %
h
bh(T ) =
2
Demostraci
on (por inducci
on sobre la altura h)
 h = 0: en este aso 0/2 = 0 y la hip
otesis de base es valida.
 h > 0: ahora suponemos que el lema es valido para todo arbol Th rojo-negro rti o de
altura h y veri amos si aun lo es para el arbol Th+1 de altura h+1. La demostra ion
dependera del he ho de que h sea par o impar.


Captulo 6. Arboles
de b
usqueda equilibrados

608

1. Si h es impar = h + 1 es par. En este aso, si oloreamos de negro la raz de


Th+1 tenemos:
bh(Th+1) = bh(Th) + 1 =

$ %
h
h1
bh(Th+1) =
+ 1 =
=
2
2
$
%
h+1
h+1
=
bh(Th+1) =
2
2

El lema es valido para h impar.


2. Si h es impar = h + 1 es impar. En este aso, si oloreamos de rojo la raz de
Th+1 tenemos:
bh(Th+1) = bh(Th) =
bh(Th+1) =

h
=
2

h+1
2

El lema es valido para h, par o impar 


La demostra ion del lema 6.8 ha e aprensible el he ho de que el olor de la raz de un
arbol rojo-negro puede ser negro solamente si la altura del arbol es par.
El lema 6.8 permite ompletar la prueba del teorema porque ahora podemos al ular
una expresion que indique la antidad total de nodos de un arbol rojo negro rti o.
Si Th es un arbol rojo negro rti o de altura h, enton es
|Th| = 1 + | L(Th)| + | R(Th)|

(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)

|Th| = |Th2| + 2(h2)/2 + 2(h1)/2

(6.35)

la ual, al expandir |Th1|:

 Si h es par =

|Th| = |Th2| + 2(h2)/2 + 2(h2)/2 = |Th2| + 2h/2 =

|Th| =

h/2
X
i=1

2i = 2h/2+1 2

(6.36)


6.6. Arboles
splay

609

 Si h es impar =

|Th| = |Th2| + 2(h3)/2 + 2(h1)/2 =


|Th| = |Th2| + 21 . 2(h1)/2 + 2(h1)/2| =
|Th| = |Th2| + 3 . 2(h3)/2 =

(h3)/2

|Th| = |Th2| + 3 .

2i = 1 + 3 . 2(h3)/2 3 = 3 . 2(h3)/2 2

(6.37)

i=0

Para ompletar la demostra ion, sea n el numero de nodos de un arbol rojo-negro de


altura h. n debe ser mayor o igual al numero de nodos del arbol rojo-negro rti o de
altura h. Tomando el menor termino dere ho de las e ua iones (6.36) y (6.37) planteamos
n 2h/2+1 2 =

n + 2 2h/2+1 =
h
lg (n + 2)
+ 1 =
2
h 2 lg (n + 2) 2 

La maxima altura de un arbol rojo-negro es aproximadamente un 28 % mas grande


que la maxima altura de un arbol AVL (2 lg (n + 2) versus 1.4404 lg (n + 2)). As pues,
analti amente, el desempe~no de la busqueda debera ser superior en los arboles AVL que
en los arboles rojo-negro. Empero, esta diferen ia no es signi ativa. El desempe~no de
ambos arboles es muy similar.

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 a rmativa 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

Figura 6.28: Opera ion zig-zig

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 de ne
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;
}
De nes:
zig zig left, used in hunk 615.
zig zig right, used in hunk 615.
Uses LLINK 296 and RLINK 296.

C
A

Figura 6.29: Opera ion zig-zag

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 de ne 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);
}

static Node * zig_zag_left(Node* p)


{
RLINK(p) = rotate_to_right(RLINK(p));
return rotate_to_left(p);
}
De nes:
zig zag left, used in hunk 615.
zig zag right, used in hunk 615.


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

El TAD Splay Tree<Key>

El TAD Gen Splay Tree<Key> modeliza un arbol binario de busqueda en el que


ualquiera de las opera iones ausa un splaying del nodo a edido. El TAD se de ne
en el ar hivo htpl splay tree.H 612ai, el ual se de ne omo sigue:
htpl splay tree.H 612ai
template <template <typename> class NodeType, typename Key, class Compare>
class Gen_Splay_Tree
{
typedef NodeType<Key> Node;
hopera iones zig 610i
hmiembros
hmiembros
};

privados de Gen Splay Tree<Key> 612bi


publi os de Gen Splay Tree<Key> 616ai

template <typename Key, class Compare = Aleph::less<Key> >


struct Splay_Tree : public Gen_Splay_Tree<BinNode, Key, Compare>
{ /* Empty */ };
template <typename Key, class Compare = Aleph::less<Key> >
struct Splay_Tree_Vtl : public Gen_Splay_Tree<BinNodeVtl, Key, Compare>
{ /* Empty */ };
} // end namespace Aleph
# endif /* TPL_SPLAY_TREE_H */
Uses BinNode 294a and BinNodeVtl 294a.

612b

El TAD Gen Splay Tree<Key> no requiere de nir 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

4.43 (pagina 419).


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, de nimos 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;
}
}
De nes:
push in splay stack, used in hunks 613b, 615, and 616b.

La uni a responsabilidad de push in splay stack() es atrapar la ex ep ion


std::overflow error. Si esto o urre, lo u
ni o que se ha e es va iar la pila antes de

propagar la ex ep ion.
613b

push in splay stack() es utilizada ada vez que se efe t


ue una busqueda, la ual es
realizada por la rutina siguiente:
hmiembros privados de Gen Splay Tree<Key> 612bi+
(612a) 613a 614a
void search_and_push_in_splay_stack(const Key & key)
{
splay_stack.empty();
Node ** pp = &RLINK(head);
Node * p
= root;
while (p != NULL)
{
push_in_splay_stack(p, pp);
if (Compare()(key, KEY(p)))
{
pp = &LLINK(p);


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;
}
}
De nes:

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;
}
De nes:

zig type, used in hunk 615.

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 de nida 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);
}
}
De nes:

splay, used in hunks 616 and 617.


Uses push in splay stack 613a, rotate to left, rotate to right 419, zig type 614b, zig zag left 611,
zig zag right 611, zig zig left 610, and zig zig right 610.

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
de ne 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);

splay(); // Suba el nodo hasta la ra


z con splay
if (no_equals<Key, Compare>(KEY(root), key))
return NULL;
Node * p = root;
root = join_exclusive(LLINK(root), RLINK(root));
p->reset();
return p;
}
Uses join exclusive 395, LLINK 296, RLINK 296, search and push in splay stack 613b, and splay 615.

El algoritmo realiza la busqueda del nodo a eliminar y realiza un splay independientemente


de que la busqueda haya sido exitosa o no. Si la lave se en uentra en el arbol, enton es
la raz ontiene el nodo a eliminar y el arbol resultante es la on atena ion de sus ramas.
En aso ontrario, el algoritmo termina, pues no es ne esario realizar otra opera ion.
6.6.2

An
alisis de los
arboles splay

El analisis de un arbol splay es paradigmati o del analisis amortizado.


Todas las opera iones realizan una busqueda, uyo desempe~no depende de la profundidad del nodo, posteriormente, se realiza el splay del nodo, uyo desempe~no tambien
depende de la longitud del amino entre la raz y el nodo. La inser ion y la busqueda
tienen O(nivel (ni)) + O(nivel(ni)) = O(nivel(ni)), mientras que la elimina ion tiene
O(nivel (ni)) + O(nivel (ni)) + O(nivel (ni)) = O(nivel (ni)), siendo nivel (ni) el nivel del
nodo sujeto a la opera ion splay. Esto nos sugiere que la fun ion poten ial deba onsiderar
el nivel de los nodos omo parte importante del oste.
La longitud del amino interno, presentada en la de ni ion x 4.5 (pagina 342), nos
arroja una metri a que onsidera el nivel. Ahora bien, el ara ter de emula ion a la


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 de ne 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, de nido 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)

La demostra ion onsiste en al ular el oste amortizado para ada tipo


de opera ion que o urre durante un paso del splay. De este modo:

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 de nidos. 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)

3ri(k) 3ri1(k) + O(1) 

2. zig-zig:
c
Ti1 =

k
Ti =

b
c

En este aso tampo o ambian las ardinalidades de , , y , por lo que los


rangos permane en iguales entre Ti1 y Ti y se an elan mutuamente en el al ulo
del diferen ial poten ial. Por otra parte, el zig-zig es mas ostoso en dura ion que el
zig. En omplejidad de tiempo, se puede de ir que el zig-zig efe tua una rota ion mas,


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 de ne 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.

Por una propiedad ya ono ida, lg(x) + lg(y) = lg(xy). La urva


del logaritmo es monotonamente re iente y, dentro del dominio de nuestro interes,
exhibe la siguiente forma:
Demostraci
on

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

El valor del logaritmo es maximo uando el produ to xy es maximo. Dadas las


ondi iones de x e y, xy es maximo uando x = y = 1/2 = lg(xy) = 2 
Ahora expandimos lg(x) + lg(y):
Ci1(k)
Ci(c)
+ lg
Ci(k)
Ci(k)
= lg Ci1(k) lg Ci(k) + lg Ci(c) lg Ci(k)
= lg Ci1(k) + lg Ci(c) 2 lg Ci(k)

lg(x) + lg(y) = lg

= ri1(k) + ri(c) 2ri1(k)

En fun ion del lema 6.9, planteamos la siguiente desigualdad:


ri1(k) + ri(c) 2ri(k) 2 =

(6.46)

2ri(k) ri(k) ri1(k) 2 0

Como el resultado es positivo lo podemos sumar a la desigualdad (6.45):


b
i 2 + ri(k) + ri(c) 2ri1(k) + 2ri(k) ri(k) ri1(k) 2
3ri(k) 3ri1(k)

(6.47)

3ri(k) 3ri1(k) + O(1) 

3. zig-zag:
c
Ti1 =

Ti = a

El razonamiento en este aso es ompletamente similar al anterior hasta (6.44).


Observando que ri1(a) ri1(k), planteamos la siguiente desigualdad:
Sean:

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

Finalmente, sumando el lado izquierdo de (6.49) al dere ho de (6.48), tenemos:


b
i 2ri(k) 2ri1(k)

(6.50)

3ri(k) 3ri1(k) + O(1) 

Los tres asos posibles de un paso del splay estan a otados por 3ri(k) 3ri1(k) + O(1)

El lema de a eso es la base demostrativa de unos uantos teoremas sobre los


arboles splay, entre ellos el siguiente:
Proposici
on 6.11 El oste amortizado b
s de una opera ion splay() sobre un nodo k
pertene iente a un arbol splay es O(lg n).

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 de nirse omo:
Demostraci
on

b
s =

m
X
i=1

b
i .

Esta se uen ia de opera iones puede pi torizarse del siguiente modo:

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

En base a lo anterior, podemos de nir:


b
s =

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.

Figura 6.30: Un arbol splay de 512 nodos

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

Figura 6.31: Altura de diferentes ABB


de on anza, se muestran en las guras 6.31, 6.32, 6.33 y 6.34 respe tivamente . Las
gra as en uestion orresponden a medidas sobre el siguiente experimento:
5

1. Se genera una se uen ia aleatoria de laves de inser ion.


2. Para ada tipo de arbol se realiza 3 ve es la inser ion de la se uen ia anterior y, a
ada poten ia exa ta de 2, se efe tuan las medidas.
La menor dura ion medida entre las tres repeti iones se onsidera omo el tiempo
de dura ion.
Lo anterior se repitio 5 ve es; es de ir, se generaron 5 se uen ia aleatorias de inser ion.
Las gra as orresponden a los promedios.
La gura 6.31 ilustra las alturas de los seis tipos de ABB estudiados para se uen ias
de inser ion aleatorias. Esta medida es util porque nos da una idea del osto maximo
involu rado en la busqueda. En este sentido, la gura 6.31 eviden ia y on rma la teora
a er a de que los arboles rojo-negro y AVL son los que tienen menos altura. La sorpresa de
esta gra a es que, al menos para entradas aleatorias, los arboles rojo-negro no al anzan
la ota teori a 2 lg (n + 1) 2.
la longitud del amino interno nos indi a uan equilibrado esta el arbol. En este sentido la gura 6.32 eviden ia que la alidad de todos los arboles es equivalente, siendo
ligeramente favorable ha ia los arboles AVL y rojo-negro.
Las guras 6.31 y 6.32 es onden el osto de la opera ion splay para los arboles del
mismo nombre, pues el experimento no onsidera ni emula la lo alidad de referen ia. En
5 Los

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 on anza 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

Figura 6.32: IPL para diferentes ABB (en es ala logartmi a)


este sentido, pare e que si ualquiera de entre las laves tiene la misma probabilidad de
bus arse, enton es el arbol splay es el de mas pobre desempe~no. Pero esto es puede ser
fala ioso, pues la lo alidad de referen ia juega un rol fundamental en la mayora de las
apli a iones. En los arboles de altura a otada, los probabilsti os y los binarios lasi os,
las laves mas re ientes tienden a en ontrarse ha ia las hojas: mientras mas joven es una
lave, mayor es el tiempo esperado de busqueda. Esto no su ede on un arbol splay: las
laves re ientemente a edidas se en uentran er anas a la raz, por lo que el desempe~no
debera de ser onsiderablemente mejor para apli a iones on lo alidad de referen ia.
Las dura iones de inser ion y elimina ion para los arboles splay no se muestran porque
son tan notablemente superiores al resto que su visualiza ion obsta ulizara apre iar las
diferen ias entre las demas urvas.
Si bien los in o enfoques de equilibrio ofre en desempe~nos O(lg n) para las tres opera iones fundamentales, es ru ial observar que para la inser ion y elimina ion todos estos
enfoques de equilibrio a arrean ostes onstantes severamente mayores que los de un ABB
lasi o. Las guras 6.33 y 6.34 eviden ian dramati amente este aspe to: el ABB lasi o
es el mas rapido hasta 219 nodos para la inser ion y 214 nodos para la elimina ion. A
partir de estos valores, el ABB lasi o es ligeramente inferior pero asi tan bueno omo el
rojo-negro y el AVL. Para esta ultima observa ion no se debe olvidar que los ordenes de
inser ion son aleatorios.
En la sele ion de un arbol no se debe despre iar el uso de un ABB lasi o. En tal
sentido, un ABB de onsiderarse antes que ualesquiera de los metodos de equilibrio
desarrollados en este aptulo. La sele ion de un ABB debe onsiderar los siguientes
aspe tos:
1. La es ala en fun ion del numero de nodos es peque~na o mediana.
2. Las se uen ias de inser ion no deben estar onsiderablemente sesgadas, pues un ABB


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

Figura 6.33: Tiempo de inser ion para diferentes ABB


es O(lg n) para inser iones aleatorias. Si las se uen ias de inser ion son aleatorias,
enton es se puede onsiderar un ABB para grandes es alas.
3. Puesto que las elimina iones degradan un ABB, el numero de elimina iones debe ser
mu ho menor que el de inser iones.
4. El desempe~no O(lg n) es probabilsti o. Aun on inser iones aleatorias y ausen ia de
elimina iones, un ABB puede exhibir un aso desafortunado. Por lo tanto, un ABB
no debe utilizarse si existen requerimiento absolutos de desempe~no.
La idea subya ente de los enfoques aleatorizados es eliminar ualquier sesgo en la
inser ion o elimina ion mediante de isiones aleatorias que aleatorizan el arbol ha iendolo
equivalente a un ABB onstruido a partir de una se uen ia al azar. Conse uentemente, se
puede emplear un arbol aleatorizado o un treap uando se tema sesgo en la entrada. Puesto
que el desempe~no es probabilsti o, se debe garantizar la alidad del generador de numeros
pseudoaleatorios. Aun on un buen generador de numeros aleatorios, el desempe~no de un
enfoque aleatorizado es sus eptible al infortunio. As pues, los equilibrios probabilsti os
no deben usarse si se presentan restri iones fuertes de desempe~no.
Las guras 6.33 y 6.34 eviden ian al arbol aleatorizado omo uno de los mas ostosos.
De he ho, para la inser ion, se apre ia una degrada ion apre iable onforme aumenta el
numero de nodos. Degrada ion que no o urre on la otra alternativa aleatorizada: el treap.
En la opinion del autor, esto es expli able por las siguientes razones:
1. Los arboles aleatorios son mas sensibles a la alidad del generador de numeros aleatorios que los treaps. Los ABBA requieren numeros al azar entre ero y la ardinalidad del arbol; mientras que las prioridades de los treaps utilizan todo el espe tro
del generador de numeros aleatorios. Este he ho es importante de onsiderar porque
el rango restringido de los arboles aleatorios lo ha e mas sensible a fallas, sesgos y
demas imperfe iones del generador de numeros aleatorios.

6.8. Notas bibliogr


aficas

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

Figura 6.34: Tiempo de elimina ion para diferentes ABB


Otro aspe to a onsiderar es que el treap efe tua mu has menos llamadas al generador de numeros aleatorios que el ABBA. El treap genera un aleatorio una sola
vez por inser ion; mientras que el ABBA efe tua sorteos en la inser ion y en la
elimina ion. Ademas, el ABBA puede realizar varios sorteos en ada opera ion. La
antidad esperada de sorteos depende de la ardinalidad del arbol: a mayor antidad
de nodos, mayor el numero esperado de sorteos. Esta onsidera ion es esen ial, pues
mu hos generadores sufren de \fatiga" en el sentido de que su aleatoriedad disminuye
onforme aumenta el numero de llamadas.
2. Los algoritmos del treap son mas simples y tienen menos ostes onstantes que los
del ABBA.
La dis usion anterior eviden ia laramente que si existen restri iones insalvables de
desempe~no y si no hay espa io para la mala suerte, enton es la es ogen ia de fa to es
un arbol on altura a otada: un AVL o un rojo-negro. Si se desea una implementa ion
sen illa y que exhiba buen tiempo, enton es, en la opinion del autor, los treaps son la
mejor alternativa. Finalmente, si se tiene ono imiento de una buena lo alidad

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

El numero es muy importante en mu hos lugares de las matemati as. Es probable


que su uso provenga del mundo artsti o pues, desde tiempos inmemoriales, ha sido
onsiderado omo la mas bella propor ion entre dos diferentes tama~nos. Edsger W. Dijkstra expone los numeros de Fibona i y la se ion aurea orientada ha ia las ien ias
omputa ionales [5 .
A nales de 1960 investiga iones en re upera ion de laves en alma enamiento se undario ondujeron a una nueva y popular lase de arbol: el B, des ubierto por Bayer [4.
Esta lase de arbol genero una orriente de investiga ion que ondujo a diversas estru turas
de busqueda en memoria prin ipal, notablemente el arbol 2-3 des ubierto por Hop roft
y des rito por Aho, Hop roft y Ullman [2 y el arbol binario B simetri o que es un aso
parti ular del arbol B des ubierto por Bayer [3. Los arboles rojo-negro son un equivalente
binario de un tipo de arbol uaternario denominado por Bayer 2-3-4. Implantar arboles
2-3-4 en memoria es ompli ado porque en un nodo deben mantenerse laves ordenadas
y punteros a los hijos. Esta a laratoria es importante porque las demostra iones a er a la
garanta de balan e pueden realizarse dire tamente on los arboles 2-3-4.
En 1978, Guibas y Sedgewi k [7 aprove haron un isomor smo entre los arboles 2-3-4
y los arboles binarios de busqueda y enun iaron el arbol objeto de esta se ion: el arbol
rojo-negro. Aparte de que es mas fa il implantar un arbol rojo-negro que un arbol 2-3-4,
la de ni ion 6.7 estable e un enfoque ombinatorio, natural, para ver por que los arboles
rojo-negro son balan eados.
Los treaps fueron des ubiertos por Vuillemin en 1978 [17 y estudiados en profundidad
por Seidel y Aragon [14. Por su desempe~no y sen illez, esta lase de arbol es la sele ion
favorita de mu has apli a iones.
Mu has grandes ideas son simples y naturales a ondi ion de que ya hayan sido reveladas. Un notable ejemplo lo onstituyen los arboles aleatorizados des ubiertos por Martinez y Roura [9. Durante de adas los teori os bus aron una estru tura aleatoria natural
que resolviese la asimetra de la elimina ion de un ABB. A pesar del treap de Vuillemin [17
y de que Seidel y Aragon hayan demostrado su aleatoriedad y equivalen ia on el ABB [14,
el arbol aleatorizado es una estru tura natural que pondera probabilsti amente segun el
equilibrio fuerte de Wirth [18.
Desde prin ipios de la omputa ion, los dise~nadores de sistemas han utilizado felizmente heursti as auto-ajustantes que mueven elementos re ientemente a edidos a posi iones de rapido a eso. Desde el des ubrimiento de la inser ion por la raz [16, mu hos
programadores usaron este aspe to para aprove har la lo alidad de referen ia. Pero no fue
6

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 mar l 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

1. >Cuantos nodos tiene el arbol de la gura 6.1(a)?


2. Dise~ne la rutina select gotoup root() presentada en x 6.1 (pagina 546) en fun ion
del pro edimiento insert by pos xt(()) expli ado en x 4.11.7 (pagina 415).
3. Eje ute las siguientes se uen ias de inser ion en un arbol aleatorio.
(a)
(b)
( )
(d)
(e)

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, de nidos por sus re orridos pre jos, 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. Anali e uidadosamente el algoritmo de inser ion en un arbol aleatorizado y estime


la antidad esperada de sorteos junto on su varianza.
7. Anali e uidadosamente el algoritmo de elimina ion en un arbol aleatorizado y estime
la antidad esperada de sorteos junto on su varianza.
8. Como se ha men ionado, uno de los problemas de los arboles aleatorizados es que
se realizan mu hos sorteos que ha en que el generador de numeros aleatorios al an e su punto de fatiga mas rapidamente. Proponga un esquema que minimi e las
posibilidades de fatiga del generador de numeros aleatorios.
9. Dise~ne un algoritmo que genere un arbol de busqueda aleatorio; es de ir, que orresponda a la de ni ion de arbol aleatorio dada en x 6.2.
10. Reali e las pruebas estadsti as pertinentes para veri ar si el sorteo de inser ion en
1
la raz se distribuye on probabilidad p = n+1
.
11. Reali e las pruebas estadsti as pertinentes para veri ar si el sorteo para la sele ion
de la raz del algoritmo de on atena ion aleatoria se distribuye on probabilidad
1
p= m
, donde m es la antidad de nodos del arbol izquierdo.
12. Dise~ne un algoritmo de union de dos arboles aleatorios T1 y T2 tal que laves
pertene ientes a T1 pueden ser mayores que algunas laves pertene ientes a T2. El
algoritmo debe ser O(n lg(n)).
13. Dise~ne un algoritmo de union de dos arboles aleatorios T1 y T2 tal que laves
pertene ientes a T1 pueden ser mayores que algunas laves pertene ientes a T2. El
algoritmo debe ser O(n lg(n)).
14. Dise~ne un algoritmo que reali e la inser ion por posi ion en un arbol aleatorizado.
El arbol resultante debe ser aleatorio. Demuestre su respuesta. (+)
15. Dise~ne un algoritmo que reali e la elimina ion por posi ion en un arbol aleatorizado.
El arbol resultante debe ser aleatorio. Demuestre su respuesta. (+)

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)

49 97 82 142 187 85 14 123 194 129 190 114 152 109 66 33 99 37 86 73


11 24 126 10 17 85 100 194 29 97 167 18 48 80 12 186 6 3 84 74
14 171 41 120 172 11 123 15 71 74 176 148 149 31 104 184 83 182 81 47
130 176 8 142 182 129 125 153 0 122 13 190 148 184 30 84 16 1 58 39
4 161 3 120 71 145 68 140 191 134 29 116 118 21 104 108 190 159 158 148

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, de nidos por sus re orridos pre jos, 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, de nidos por sus re orridos pre jos, 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

34. Es riba un algoritmo que genere el arbol de Fibona i de orden k.


35. (De Moivre - 1718) Demuestre que

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
in jo 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, de nidos por sus re orridos pre jos, 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. identi que 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, de nidos por sus re orridos pre jos, 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 Je rey 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

[8 Donald E. Knuth. Seminumeri al Algorithms, volume 2 of The Art of Computer


Programming. Addison-Wesley, Reading, MA, USA, third edition, 1997.
[9 Conrado Martnez and Salvador Roura. Randomized binary sear h trees. Journal of
the ACM, 45(2):288{323, Mar h 1998.
[10 Makoto Matsumoto and Yoshiharu Kurita. Twisted GFSR generators. ACM Transa tions on Modeling and Computer Simulation, 2(3):179{194, July 1992.
[11 Makoto Matsumoto and Yoshiharu Kurita. Twisted GFSR generators II. ACM
Transa tions on Modeling and Computer Simulation, 4(3):254{266, July 1994.
[12 Makoto Matsumoto and Takuji Nishimura. The dynami reation of distributed
random number generators. In PPSC, 1999.
[13 Ian Parberry. Problems on algorithms. Prenti e-Hall, Englewood Cli s, NJ, 1995.
[14 R. Seidel and C. R. Aragon. Randomized sear h trees. Algorithmi a, 16(4/5):464{
497, O tober/November 1996.
[15 D.D. Sleator and Robert E Tarjan. Self-adjusting Binary Sear h Trees. Journal of
the ACM, (32):195{204, 1985.
[16 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.
[17 Jean Vuillemin. A data stru ture for manipulating priority queues. Communi ations
of the ACM, 21(4):309{315, April 1978.
[18 Niklaus Wirth. Algorithms + Data Stru tures = Programs. Prenti e-Hall Series in
Automati Computation. Prenti e-Hall, Upper Saddle River, NJ 07458, USA, 1976.

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 re ere 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},

de una manera diferente y, en general mas simple, que un diagrama sagital.


a

Figura 7.1: Ejemplo de rela ion expresada on un gra o


Figuras omo la 7.1 se denominan \grafos". En el ejemplo en uestion, se apre ian visualmente las onexiones entre los elementos del onjunto A. Los elementos
se dibujan en errados entre r ulos, mientras que las rela iones entre ellos se indi an mediantes lneas que one tan los r ulos. Es muy posible que al le tor le sea
mas sen illo apre iar la rela ion mediante la gura 7.1, que en su representa ion
matemati a 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)}, A = {a, b, c, d, e, f, g, h}.
En la jerga de grafos, los elementos de rela ion se denominan verti es o nodos, mientras
que las rela iones se llaman aristas o ar os. En el ejemplo de la gura 7.1 los verti es se
dibujan on r ulos etiquetados on los elementos del onjunto A = {a, b, c, d, e, f, g, h},
pero no existe ninguna restri ion sobre la forma visual que deba tener un nodo; podra
haberse dibujado on uadrados, triangulos o ualquier otra gura.
Tampo o existen restri iones sobre la manera u orden en que se deben olo ar los
nodos y los ar os. El grafo de la gura 7.2 representa exa tamente la misma rela ion
representada por la gura 7.1.
Por lo general, en matemati a la ambiguedad no es bien vista. Pero a ve es es, justamente, en la apa idad de expresar formas gra as diferentes para una misma rela ion,
en donde reside el poder de abstra ion y representa ion de un grafo. Un grafo es, pues,
una estru tura de datos orientada ha ia la representa ion gra a de las rela iones entre elementos de un mismo tipo. La razon que motiva a los programadores, ingenieros,
matemati os y demas pra ti antes a utilizar grafos, es la omodidad que onlleva para el
entendimiento el manejar abstra iones gra as. A abamos de presentar el para que es la
idea de grafo: una abstra ion general que pretende abar ar una vasta gama de problemas
en los uales se puedan expresar rela iones entre elementos de un mismo tipo y nos sea
mas omodo visualizarlas en un gra o.
Si bien la idea de grafo es visual en su sentido sensorial, su tratamiento tiene dos

7.1. Fundamentos

639

Figura 7.2: El grafo de la gura 7.1


perspe tivas de estudio que no lo son: la matemati a ombinatoria y la programa ion.
El on epto de grafo y sus problemas aso iados existen desde mu ho antes que la
programa ion. Puesto que la mayora de los problemas que modelizan los grafos requieren mu ho omputo, durante mu ho tiempo su interes y estudio estuvo reservado a los
matemati os. La perspe tiva matemati a tiende a investigar el grafo omo una rela ion;
es de ir, ombinatorialmente.
La otra perspe tiva del analisis la onstituye la programa ion de algoritmos sobre
grafos. Di hos algoritmos plantean desafos de labor de una es ala y omplejidad que
aun no ha sido abordada hasta el presente. Los algoritmos sobre grafos onjugan el uso de
diversas estru turas de datos y algoritmos. Por adelantar un ejemplo, el al ulo de aminos
optimos puede requerir algoritmos de ordenamiento y heaps binarios.

7.1

Fundamentos

Como intuitivamente ya se ha men ionado, un grafo G se de ne formalmente mediante


una dupla G =< V, A > ompuesta de dos onjuntos: V es el onjunto de verti es o nodos
y A es el onjunto de aristas o ar os . En el ejemplo de la gura 7.3, V se ompone por
las letras desde la A hasta la M, mientras que A lo omponen las onexiones enumeradas
desde el 1 hasta el 29.
La numera ion de los ar os de las guras 7.3 y 7.4 no representa magnitudes o tipos.
Segun las ara tersti as de la apli a ion, a los nodos y ar os pueden aso iarseles tipos de
datos. Por ejemplo, un \grafo vial" de arreteras guardara en sus nodos los nombres de
las iudades junto on informa ion tursti a y de servi ios basi os (esta iones de gasolina,
hoteles, et etera). Los ar os representaran las arreteras y alma enaran las distan ias
2

2 Por

lo general, los programadores usan el termino \nodo", mientras que los matemati os pre eren
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

Figura 7.3: Un grafo


entre las iudades junto on un ali ador que indi ase su alidad (autopista, arretera,
tro ha, et etera). A menudo, a esta lase de grafo, en parti ular a aquel uyos ar os
guardan antidades, se le denomina \grafo on pesos", porque las antidades se ponderan
y se utilizan en algoritmos; por ejemplo, para al ular la distan ia mas orta entre dos
iudades.
En el grafo de la gura 7.3, ualquier ar o rela iona a sus nodos en los dos sentidos. Por
ejemplo, el ar o 1 representa las rela iones A B y B A, respe tivamente. En terminos
matemati os, el ar o 1 representa (A, B), (B, A) A A. En palabras matemati as, la
rela ion representada por el grafo de la gura 7.3 es simetri a.
Cuando la rela ion no es simetri a, enton es el grafo es dirigido y a los ar os se les
olo an e has que indi an el sentido de la rela ion entre dos nodos. Un grafo dirigido
tambien se denomina digrafo. La gura 7.4 muestra un ejemplo. Notese que el ar o 1
dirigido desde el nodo A ha ia el nodo B representa (A, B) R. Del mismo modo, notese
que (B, A) / R se expresa por la ausen ia del ar o dirigido desde B ha ia A.
B

7
C

Figura 7.4: Un digrafo o grafo dirigido


El numero de ar os one tados a un nodo es llamado el grado del nodo. El
grado del grafo es el mayor grado entre todos sus nodos. Dado un nodo v, los nodos dire tamente one tados desde v a traves de sus ar os se llaman nodos adya entes. Por ejemplo,
en el grafo de la gura 7.4, los nodos adya entes a B son {C, D, E}. Los nodos desde los
uales se llega dire tamente a un nodo v del grafo se denominan nodos incidentes. Para
el ejemplo anterior, A es el uni o nodo in idente a B. Un ar o es incidente sobre el(los)
nodo(s) que el one ta. Esta jerga es apli able a un grafo no dirigido, pero puede omple-

7.1. Fundamentos

641

mentarse si a los numeros de ar os entrantes y salientes se les llaman grado de entrada


y grado de salida, respe tivamente.
Dado un ar o dirigido, a su nodo de origen se le denomina \adya ente" y al destino
\in idente".
Un camino P G de un grafo G se de ne omo una se uen ia inter alada de nodos
y ar os de G. Hay varias formas de expresar un amino, las uales dependen del tipo de
grafo y de la apli a ion. En el aso del grafo de la gura 7.3, la se uen ia P = A B J
H N L I indi a un amino desde el nodo A hasta el nodo I. Otra manera puede
ser dire tamente A, B, J, H, N, L, I, o mediante las etiquetas de los ar os: 1, 2, 3, 16, 6, 25;
esta ultima forma esta supeditada a que los ar os esten etiquetados; requisito que no se
ir uns ribe en la de ni ion formal de grafo. La longitud de un amino se ompone por
el numero de ar os que este ontenga; o sea, el numero de nodos menos uno.
Una de las apli a iones mas importantes de los grafos la onstituye la busqueda de
aminos segun alguna restri ion dada. Quiza el problema mas popular es la busqueda del
amino mnimo entre un par de nodos.
Un amino uyo primer y ultimo nodo sean iguales se denomina ciclo o circuito. Si
la longitud del i lo es uno, enton es al amino se le ali a de simple de bucle o
de lazo. El grafo de la gura 7.3 ontiene un solo i lo simple en el nodo J; es de ir, el
par (J, J).
Un grafo G =< V, A > se ali a de conexo si para todos los pares ordenados de nodos
(v1, v2) G existe un amino desde v1 hasta v2. Expresado de otra manera, un grafo es
onexo si para todo nodo del grafo existe un i lo que no sea simple.
Un grafo se di e que es total, completamente conexo o, simplemente, completo,
si ada nodo del grafo es adya ente al resto. El grado de un grafo ompleto es su numero
de nodos. La gura 7.5 ilustra el grafo ompleto de grado 5.
A

Figura 7.5: Un grafo ompleto de grado 5


El grafo complemento de un grafo G, que se denota G, es el grafo ompuesto por
los ar os que se requeriran para que G deviniese ompleto. Di ho de otro modo, el grafo
ompleto de grado |V| menos los ar os de G. La gura 7.6 ilustra un ejemplo. La union
de un grafo y su omplemento es el grafo ompleto de grado |V|. Algunos algoritmos
requieren al ular el grafo omplemento; otros requieren el grafo ompleto, el ual puede
al ularse si se tiene el omplemento mediante la union G G.
Las es alas en numero de nodos y ar os son los fa tores prin ipales que in iden en
el tiempo de eje u ion de un algoritmo sobre un grafo. En las expresiones de al ulos,
para fa ilitar el dis urso, las ardinalidades de los onjuntos V y A suelen representarse

642

Captulo 7. Grafos

(a)

(b)

Figura 7.6: Un grafo y su omplemento


dire tamente sin las barras.
2

B
1

J
7

H
8

14
9

D
13

12

11

10

17

E
16

15

18

17

Figura 7.7: Un multigrafo


A un grafo que tenga dos o mas ar os que one ten los mismos nodos se le denomina, en
parafrasis on los onjuntos, multigrafo. La gura 7.7 ilustra un ejemplo. Un multigrafo
puede ser dirigido, en uyo aso se le llama multidigrafo o multigrafo dirigido. Un ar o
redundante se tilda de paralelo.
Dado un grafo G =< V, A >, ualquier sub onjunto G =< V , A > | (V V)(A A |
a = (v1, v2) A = v1, v2 V ) se denomina subgrafo de G. La gura 7.8 ilustra dos
subgrafos del ilustrado en la gura 7.3. Cualquier subgrafo ompleto se denomina clique.
A un grafo que no es onexo se le llama inconexo. Un grafo in onexo G se ompone
de grafos onexos, los uales se denominan subgrafos conexos de G.
Un grafo onexo sin i los se denomina arbol. Menester notar que esta es una
interpreta ion diferente a la de arbol tratada en x 4.1, pues en este aso no es requisito
denotar un nodo raz; aunque nada impide ha erlo.
Un arbol abarcador de un grafo onexo G es un arbol ompuesto on ar os de G
que ontiene todos los nodos de G. La gura 7.9 ilustra un arbol abar ador del grafo
mostrado en la gura 7.3.
Algunos algoritmos requieren veri ar si el grafo es bipartido o en ontrarlo. Un
grafo es bipartido si este puede dividirse en dos onjuntos disjuntos de nodos B y C
3

3 En

ingles: spanning tree.

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)

Figura 7.8: Dos subgrafos del grafo en 7.3


B

H
15

16

L
6

10

17

E
13

25

18

I
27

20

21

Figura 7.9: Un arbol abar ador del mostrado en la gura 7.3


tal que los ar os de un onjunto one ten nodos de B on nodos de C y que no exista
onexion entre los nodos de un mismo onjunto. La gura 7.10 muestra un ejemplo.
Del sentido \visual" del on epto de grafo surge la idea de \ olora ion" si aso iamos
olores a los nodos o ar os. El problema de olorear nodos onsiste en \pintar" los nodos
de manera tal que dos nodos adya entes no tengan el mismo olor. En el mismo sentido,
olorear ar os onsiste en pintarlos de forma tal que dos ar os que in iden sobre el mismo
nodo tengan olores diferentes.
Dos grafos G1 =< V1, A1 > y G2 =< V2, A2 > son isomorfos si es posible ambiar de
las etiquetas de V1 por las de V2 tal que A1 = A2. La gura 7.11 muestra un ejemplo.
Un grafo es llamado planar si es posible dibujarlo en un plano sin que sus ar os se
ru en.
Los grafos representan una lase de problemas mas ompleja que ha e uso intensivo de
implanta iones del problema fundamental de estru tura de datos. La ndole de problemas
on grafos puede lasi arse en uatro tipos.
El primer tipo de problema se lasi a en de existencia. Dado un grafo y unos
requerimientos sobre este, >existe un grafo que satisfaga los requerimientos? Por ejemplo,
>es un grafo planar?
El segundo tipo se tipi a en de construccion. Una vez que se ono e la existen ia
de un grafo espe ial, enton es > omo onstruirlo?. Alguna ve es no es ne esario determinar
la existen ia porque esta se ono e de antemano; por ejemplos, para al ular el arbol abar ador o algun amino. Otras ve es s se onviene determinar la existen ia primero, antes

644

Captulo 7. Grafos

(a)
B

(b)

Figura 7.10: Un grafo bipartido


de abordar la onstru ion; por instan ia, el dibujado de un grafo planar.
El ter er tipo se tilda en de enumeracion o conteo. Una vez que se determina
la existen ia, enton es se desea ono er la antidad de solu iones. Por lo general, estos
problemas ata~nen mas al interes matemati o que apli ativo.
Finalmente, el ultimo tipo de problema se denomina de optimizacion y onsiste
en onstruir la mejor solu ion segun algun riterio de optima ion. En lo que sigue de este
texto se en ontraran mu hos ejemplos variando desde el arbol abar ador mnimo hasta la
onstru ion de un amino hamiltoniano.

7.2

Estructuras datos para representar grafos

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.

7.2. Estructuras datos para representar grafos

645

6
A

B
1

D
4

Figura 7.11: Dos grafos isomorfos

Figura 7.12: Un grafo


Si el tipo de dato aso iado a un nodo no es entero, enton es hay que implantar un
mapeo de nodos ha ia ndi es de la matriz. La manera mas simple de ha erlo es mediante
un arreglo de nodos uyos ndi es se orresponden on ndi es en la matriz. Si el arreglo esta
ordenado, enton es la busqueda binaria expli ada en x 3.1.8 en uentra muy e azmente
un ndi e dado un nodo.
Si los ar os no tienen algun tipo aso iado, enton es el TAD BitArray, estudiado
en x 2.1.3, puede utilizarse omo base de implanta ion de la matriz de adya en ia. Por
lo ontrario, si los ar os aso ian algun tipo, enton es ada entrada de la matriz podra
representar la instan ia del tipo que estara aso iada al ar o. En este aso, requerimos una
instan ia espe ial del tipo para indi ar la ausen ia del ar o.
Una matriz de adya en ia o upa O(V 2) eldas; osto alto aun para grafos de mediana
es ala. Por esta razon, resulta interesante el uso de estru turas de representa ion de matri es espar idas que no o upen espa io por entradas nulas; en nuestro aso, por ausen ia
de ar os.
Una primera manera de ahorrar memoria la onstituye onsiderar el tipo de grafo. Si

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

Figura 7.13: Matriz de adya en ia del grafo en la gura 7.12

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];

for (int i = 0; i < V; ++i)


matriz[i] = new T [V];

Este odigo puede modi arse para usar el tipo BitArray, lo que se deja omo ejer i io.
A

Figura 7.14: Arreglo de arreglos orrespondiente a la matriz de adya en ia de la gura 7.12


Hay algoritmos que se desempe~nan mejor on matri es de adya en ia que on su ontraparte que usa listas enlazadas. Algunos algoritmos on matri es de adya en ia requieren
otras matri es para mantener estado de sus al ulos, lo que in rementa el osto en memoria.
Aparte del aprove hamiento del tipo de grafo, otra forma de ahorrar espa io es usar
un arreglo espar ido del tipo DynArray<T> estudiado en x 2.1.5.

7.2. Estructuras datos para representar grafos

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

Figura 7.15: Listas de adya en ia del grafo en la gura 7.12


Cada nodo de una lista de adya en ia guarda la dire ion de memoria de un verti e en
el grafo orrespondiente a la existen ia de un ar o. Puesto que en un grafo los ar os son
bidire ionales, estos se re ejan dos ve es en la representa ion on listas de adya en ia; o
sea, una vez por ada nodo extremo de ada ar o. Por ejemplo, en el grafo de la gura 7.12,
el ar o A B apare e en las listas de adya en ia de los nodo A y B.
La dupli idad de ar os en las listas de adya en ia a arrea un sobre osto de espa io
uando los ar os aso ian algun tipo de dato, pues se dupli a informa ion. Por otra parte,
algunos algoritmos guardan estado de al ulo en los ar os, lo que requiere, en aso de
mantenerse la dupli idad men ionada, que el estado se a tuali e en dos dire iones de
memoria diferentes. Por esta razon, es preferible abstraer un ar o y guardar dire iones a
este en las listas de adya en ia, en lugar de guardar dire iones de nodos.
De esta manera, la lista de adya en ia deviene una lista de ar os. La distin ion del nodo
destino se realiza segun el nodo propietario de la lista. En el ejemplo de la gura 7.12 si nos
en ontramos en la lista de adya en ia del nodo B y en ontramos el ar o A B, enton es
sabemos que se trata del ar o on sentido B A.
Con el esquema anterior se puede mantener estado en los ar os sin ne esidad de lidiar
on las dupli idades de nodos. Este esquema tambien fun iona on digrafos a expensas de
un osto en memoria ligeramente superior que el de guardar los nodos.
Las listas de adya en ia tienen varias ventajas respe to a las matri es. La primera de
ellas, es que el oste en espa io es exa tamente propor ional al numero de ar os. Esto se
tradu e en ahorros de memoria uando el grafo es espar ido.
La segunda ventaja es que se fa ilita operar el grafo en fun ion de su ara ter gra o;
es de ir, en fun ion de nodos y ar os. Esto en general no su ede on un algoritmo que
4

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

Un TAD para grafos (List Graph<Node, Arc>)

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

para grafos 670di


ontrol 662i

hNodo

de grafo 651bi

hAr o

de grafo 657bi

hAr o-Nodo 676 i


hCamino

de grafo 690bi

hGrafos 649i

7.3. Un TAD para grafos (List Graph<Node, Arc>)

649

hDigrafos 650bi
hImplanta i
on

de List Graph<Node, Arc> 672di


Pudiera de irse que una de las prin ipales di ultades para modelizar un TAD de
grafos es el he ho de que este involu ra otras lases de objeto. Esen ialmente, un grafo
maneja nodos y ar os, los uales, en mu hos asos, hay que tratarlos omo una abstra ion
separada, aunque rela ionada, del propio grafo. Un aspe to esen ial para la ompresion es
la observan ia de que es en el momento en que se de ne el grafo, es de ir, uando este se
de lare y se ompile, que se ono eran ompletamente los tipos de nodos y ar os.
Debido a su alto dinamismo, List Graph<Node, Arc> onstituye el prin ipal TAD
pertinente a los grafos. Este TAD permite fa ilmente opiar, asignar y modi ar su
topologa; ara tersti as ne esarias para una amplia gama de algoritmos. Algoritmos
basados en matri es de adya en ia usaran una familia de TAD orientados a matri es:
Map Matrix Graph<GT>, Matrix Graph<GT> y Ady Mat<GT, Entry>, los uales seran
detalladamente desarrollados en x 7.6 (pagina 751).
En hNodo de grafo 651bi (x 7.3.3 (pagina 651)) se espe i a una lase Graph Node<Node Info,Arc Info
que modeliza un nodo pertene iente a un grafo representado mediante listas enlazadas.
Similarmente, en hAr o de grafo 657bi (x 7.3.4 (pagina 657)) se espe i a la
lase Graph Arc<Node Info, Arc Info>, uyo sentido es representar un ar o de un grafo
representado mediante listas enlazadas.
En hMa ros para grafos 670di (x 7.3.6 (pagina 670)) se de nen ma ros destinados a
fa ilitar la legibilidad de odigo.
En hAr o-Nodo 676 i (x 7.3.8.2 (pagina 676)) se de ne la representa ion de un nodo
de la lista de adya en ia de ar os que ontiene ada verti e .
En hGrafos 649i (x 7.3.1 (pagina 649)) se de ne el TAD fundamental List Graph<Node, Arc>,
el ual modeliza un grafo representado on listas enlazadas. List Graph<Node, Arc> requiere haber de nido los objetos Graph Node<Node Type> y Graph Arc<Arc Type> on
los uales se onstruira un grafo.
Analogamente, en hDigrafos 650bi (x 7.3.2 (pagina 650)) se modeliza el TAD
List Digraph<Node, Arc>, el ual es muy similar a List Graph<Node, Arc> salvo que
los ar os son dirigidos.
En hCamino de grafo 690bi (x 7.4 (pagina 690)) se de ne un TAD que modeliza un
amino sobre un grafo representado on listas.
5

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 de ne 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

problemas de ambiguedad, en este ontexto usamos \verti e" en lugar de nodo.

650

Captulo 7. Grafos

hIteradores de List Graph<Node, Arc> 655bi


};
De nes:
List Graph, used in hunks 650b, 655b, 661a, 669{76, 681, 682, 684{89, and 768b.

650a

Para poder usar un grafo List Graph<Node, Arc> es ne esario haber de nido 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 de nen 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;

Consideremos en siguiente fragmento de odigo el ual ejempli a el uso de estos tipos:


template <class GT>
void fct(GT & g)
{
typename GT::Arc * arc;
typename GT::Arc_Type dato;
// ...
}

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

Digrafos (List Digraph<Node, Arc>)

El TAD que modeliza digrafos se denomina List Digraph<Node, Arc>. Tanto a


nivel de estru tura de datos, omo a nivel de interfaz, el List Graph<Node, Arc>
tiene asi todo lo requerido para manejar digrafos. Por esa razon de niremos
List Digraph<Node, Arc> por heren ia p
ubli a de List Graph<Node, Arc> tal omo
se presenta a ontinua ion:
hDigrafos 650bi
(648)
template <typename Node_Info, typename Arc_Info>
struct List_Digraph : public List_Graph<Node_Info, Arc_Info>

7.3. Un TAD para grafos (List Graph<Node, Arc>)

651

{
List_Digraph();
List_Digraph(const List_Digraph & dg);
List_Digraph & operator = (List_Digraph & dg);
};
De nes:
List Digraph, used in hunks 671d and 689a.
Uses List Graph 649.

651a

La interfaz de List Digraph<Node, Arc> es identi a a la de List Graph<Node, Arc>;


de he ho, es, por deriva ion de lases, la misma. A nivel de implementa ion, la diferen ia
reside en que en List Graph<Node, Arc> se redunda el ar o, sin afe tar la oheren ia de
los algoritmos y on un ligero onsumo de espa io, para representar la bidire ionalidad
del ar o, mientras que en List Digraph<Node, Arc> solo se olo a el ar o en la lista de
adya en ia de su nodo adya ente.
Puede haber situa iones en las uales es ne esario determinar si un objeto de tipo
List Graph<Node, Arc> es o no un digrafo. Para ello, se provee la primitiva siguiente:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai
(649) 653a
bool is_digraph() const;
Uses is digraph.

la ual retorna true si el objeto es un digrafo; false de lo ontrario.


7.3.3

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
};
De nes:
Graph Node, used in hunk 652d.
Uses Arc Node 676 and Dlink 90.

hNodo

de grafo 651bi modeliza un nodo pertene iente a un grafo implantado mediante

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

De lara un nodo on atributo de tipo Ciudad.


Eventualmente, si no se requiere ningun atributo para un nodo o, si se pre ere olo arlo
en una deriva ion, enton es podemos espe i arlo on atributos va os de la siguiente
forma:
Graph_Node<Empty_Class> node;
Graph Node<Node Type> se de lara omo struct y no omo una lase. Esto sig-

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;

y que se observa y modi a mediante:


hMiembros de Graph Node<Node Type> 652ai+

(651b) 652a 652

Node_Info & get_info() { return 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;

Hay tres maneras de onstruir un nodo:


hMiembros de Graph Node<Node Type> 652ai+

(651b) 652 665b


Graph_Node() : num_arcs(0), counter(No_Visited), cookie(NULL) { /* empty */ }
Graph_Node(const Node_Info & info)
: node_info(info), num_arcs(0), counter(No_Visited), cookie(NULL)
{
/* empty */
}

Graph_Node(Graph_Node * node)
: node_info(node->get_info()), num_arcs(0), counter(No_Visited), cookie(NULL)
{
/* empty */
}
Uses Graph Node 651b.

7.3. Un TAD para grafos (List Graph<Node, Arc>)

653

Los atributos counter y cookie obraran sentido en las se iones subsiguientes.


7.3.3.1

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);

inline Node * insert_node(const Node_Type & node_info);


Uses insert node 682a.

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

Para eliminar un nodo de un grafo solo basta tener un puntero al mismo:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 653a 654a
inline void remove_node(Node * node);
Uses remove node 685a.

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

Acceso a los nodos de un grafo

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

primitiva basi a para obtener un nodo ualquiera del grafo es:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 653b 654b
inline Node * get_first_node();
Uses get first node 673 .

654b

La ual retorna un nodo ualquiera ontenido dentro del grafo.


get first node() es un punto ualquiera de a eso al grafo. No debe ha erse ninguna
onsidera ion a er a del nodo que retornara esta primitiva.
El numero de nodos que ontiene un List Graph<Node, Arc> puede ono erse mediante:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 654a 654
inline const size_t & get_num_nodes() const;

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

la ual invo a al operador == de la lase Node Type.


El segundo tipo de busqueda onsiste en veri ar si algun nodo parti ular es o no parte
del grafo:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 654d 655a
bool node_belong_to_graph(Node * node);

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

7.3. Un TAD para grafos (List Graph<Node, Arc>)

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.

la ual, usando el riterio de igualdad bool Equal::operator() (Node * curr, void *


ptr), bus a un nodo on ara tersti as espe i adas en el apuntador [ptr.

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();
};
De nes:
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);
}

Iterador de arcos de un nodo

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

de omputo sobre un grafo. Su espe i a ion es omo sigue:


hIteradores de List Graph<Node, Arc> 655bi+
(649) 655b 661a
class Node_Arc_Iterator : public Dlink::Iterator
{
hMiembros privados de iterador de Node 680bi

public:
hMiembros
};
De nes:

publi os de iterador de Node 656bi

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

La formas de de larar ( onstruir) un objeto Node Arc Iterator se expresan mediante


los siguientes onstru tores:
hMiembros p
ubli os de iterador de Node 656bi
(656a) 656
inline Node_Arc_Iterator();

inline Node_Arc_Iterator(Node * _src_node);


inline Node_Arc_Iterator(const Node_Arc_Iterator & it);
Uses Node Arc Iterator 656a.

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

7.3. Un TAD para grafos (List Graph<Node, Arc>)

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());
}

template <class Operation> void operate_on_nodes(void * ptr)


{
for (Node_Iterator itor(*this); itor.has_current(); itor.next())
Operation () (*this, itor.get_current_node(), ptr);
}
Uses has current 103, Node Iterator 655b, and operate on nodes 661 .

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

Un ar o de un grafo se de ne mediante el TAD Graph Arc<Arc Type> omo sigue:


hAr o de grafo 657bi
(648)
template <typename Arc_Info>
struct Graph_Arc : public Dlink
{
typedef Graph_Arc Arc;
typedef Arc_Info Arc_Type;
hAtributos de Graph Arc<Arc Type> 657 i
hM
etodos de Graph Arc<Arc Type> 657di
};
De nes:
Graph Arc, used in hunk 680a.
Uses Dlink 90.

Graph Arc<Arc Type> modeliza un ar o pertene iente a un grafo implantado mediante


listas de adya en ia. Al igual que Graph Node<Node Type>, Graph Arc<Arc Type> es una
lase parametrizada uyo tipo aso iado, Arc Info, onstituye la informa ion rela ionada
657

a ar o y se olo a omo atributo:


hAtributos de Graph Arc<Arc Type> 657 i
Arc_Info

657d

(657b) 665

arc_info;

A su vez, este dato se a ede mediante:


hM
etodos de Graph Arc<Arc Type> 657di

(657b) 676a

Arc_Info & get_info() { return arc_info; }

Si se desea, por ejemplo, un grafo de arreteras, enton es la siguiente lnea:

658

Captulo 7. Grafos

Arc<Carretera> * arco;

658a

De lara un apuntador a un ar o que usa omo atributo un tipo Carretera.


Un ar o one ta dos nodos llamados origen y destino que pueden a ederse de la
siguiente manera:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 657a 658b
inline Node * get_src_node(Arc * arc);
inline Node * get_tgt_node(Arc * arc);

Es momento de a larar dos osas en rela ion a los metodos sobre un ar o:


1. En el aso de un grafo, estos metodos no ne esariamente tienen sentido segun la
dire ion del ar o; simplemente onforman una manera de ono er los nodos que
este one ta. En el aso de un digrafo, por supuesto, get src node() retorna el
nodo adya ente y get tgt node() el in idente.
2. Ambas primitivas, as omo la mayora de las de a eso a un ar o, son parte de la
lase List Graph<Node, Arc> y no de la lase Graph Arc<Arc Type>. Esto impli a
que debe ono erse el grafo para poder invo ar a uno de estos metodos.

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;

La ual retorna true si node esta one tado al ar o arc.


7.3.4.1

658d

Inserci
on de arcos

Para de nir un ar o en un grafo deben haberse de nido e insertado en el grafo sus dos
nodos mediante las primitivas orrespondientes de nidas 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.

7.3. Un TAD para grafos (List Graph<Node, Arc>)

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.

el ual elimina del grafo el ar o arc y libera la memoria.


El metodo no realiza ninguna veri a ion de orre titud; por ejemplo, la elimina ion
de un ar o que no existe en el grafo.
7.3.4.3
659b

Acceso a los arcos de un grafo

Una manera primigenia de obtener un ar o ualquiera de un grafo la onforma el metodo:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 659a 659
inline Arc * get_first_arc();
Uses get first arc 673 .

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.

sort arcs<Compare>() ordena los ar os seg


un el riterio de ompara ion Compare, la ual
invo a al operador bool Compare::operator () (Arc * a1, Arc * 2). El programador

659d

es responsable de implantar el a eso al atributo del ar o que por el ual se ompara y de


la ompara ion misma. El ordenamiento se realiza en O(n lg (n)) on onsumo de espa io
O(1).
El numero de ar os de un grafo puede ono erse mediante:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 659 660a
inline const size_t & get_num_arcs() const;

660

660a

Captulo 7. Grafos

Y el numero de ar os que tiene un nodo mediante


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+

(649) 659d 660b


inline const size_t & get_num_arcs(Node * node) const;

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

template <class Equal>


Arc * search_arc(const Arc_Type & arc_info);

Arc * search_arc(const Arc_Type & arc_info);


Uses search arc 675a.

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.

(649) 660b 660d

Se retorna true si un ar o on dire ion arc pertene e al grafo; false de lo ontrario.


3. Existen ia de un ar o entre dos nodos: para ello se usa el siguiente metodo:
660d
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 660 660e
Arc * search_arc(Node * src_node, Node * tgt_node);
Uses search arc 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

que son sinonimos.

7.3. Un TAD para grafos (List Graph<Node, Arc>)

661

Iterador sobre los arcos de un grafo


661a

Los ar os de un grafo pueden observarse mediante el siguiente iterador:


hIteradores de List Graph<Node, Arc> 655bi+
(649) 656a
struct Arc_Iterator : public Dlink::Iterator
{
Arc_Iterator() { /* empty */ }
Arc_Iterator(List_Graph & _g);
Arc_Iterator(const Arc_Iterator & it);
Arc_Iterator & operator = (const Arc_Iterator & it);

Arc * get_current_arc();
};
De nes:
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());
}

template <class Operation> void operate_on_arcs(void * ptr)


{
for (Arc_Iterator itor(*this); itor.has_current(); itor.next())
Operation () (*this, itor.get_current_arc(), ptr);
}
De nes:
operate on arcs, used in hunks 670 and 695b.
Uses Arc Iterator 661a and has current 103.

661

La semanti a es similar a la de los nodos (ver x 7.3.3.3 (pagina 656)).


Es posible restringir la opera ion generi a a los ar os adya entes a un nodo. Para ello,
se tienen las primitivas siguientes:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 661b 665d
template <class Operation> void operate_on_arcs(Node * node)
{

662

Captulo 7. Grafos

for (Node_Arc_Iterator it(node); it.has_current(); it.next())


Operation () (*this, it.get_current_arc());
}
template <class Operation> void operate_on_arcs(Node * node, void * ptr)
{
for (Node_Arc_Iterator it(node); it.has_current(); it.next())
Operation () (*this, it.get_current_arc(), ptr);
}
De nes:
operate on arcs, used in hunks 670 and 695b.
operate on nodes, used in hunks 657a, 670a, 695b, 737a, 786, and 796.
Uses has current 103 and Node Arc Iterator 656a.

7.3.5

Atributos de control de nodos y arcos

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;

7.3. Un TAD para grafos (List Graph<Node, Arc>)

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 */ }

etodos bits ontrol 663i


hM
};
De nes:
Bit Fields, used in hunks 665 and 666b.
Uses cut 64, is acyclique 707, test cycle 705a, and test path 709b.
Bit Fields es un atributo que maneja omo mnimo 16 bits destinados a llevar estado

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");
}
}
De nes:
get bit, used in hunk 670d.
Uses cut 64, is acyclique 707, test cycle 705a, and test path 709b.

As omo tambien, eventualmente, puede modi arse a traves de:

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");
}
}
De nes:
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.

Los bits de ontrol pueden reini iarse:


hM
etodos bits ontrol 663i+

void reset(const int & bit) { set_bit(bit, 0); }


void reset()
{
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

=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=

0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;
0;

(662) 664a

7.3. Un TAD para grafos (List Graph<Node, Arc>)

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
};
De nes:
Graph Bits, never used.

Por ejemplo, el algoritmo de prueba de i lo invo ara:


control_bits.set_bit(Test_Cycle, 1);

665b

a efe tos de pintar una visita.


Cada nodo y ar o de un grafo ontiene bits de ontrol espe i ados omo sigue:
hMiembros de Graph Node<Node Type> 652ai+
(651b) 652d 666d
Bit_Fields control_bits;
Uses Bit Fields 662.

665

665d

hAtributos de Graph Arc<Arc Type> 657 i+


Bit_Fields control_bits;
Uses Bit Fields 662.

(657b) 657 666e

En un nodo los bits de ontrol pueden a ederse mediante:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 661 665e

inline Bit_Fields & get_control_bits(Node * node);


Uses Bit Fields 662.

Tambien pueden olo arse todos los bits en ero mediante:

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

O asignarse un bit en parti ular a traves de:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+

(649) 665e 666b


inline void set_bit(Node * node, const int & bit, const int & value);
Uses set bit 664a.

Analogamente, existen las mismas opera iones para un ar o:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 666a 666
inline Bit_Fields & get_control_bits(Arc * arc);

inline void reset_bit(Arc * arc, const int & bit);


inline void set_bit(Arc * arc, const int & bit, const int & value);
Uses Bit Fields 662 and set bit 664a.

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);
}

void reset_bit_arcs(const int & bit)


{
for (Arc_Iterator itor(*this); itor.has_current(); itor.next())
reset_bit(itor.get_current_arc(), bit);
}
De nes:
reset bit arcs, used in hunks 697b, 702, 705a, 707{9, 713, 715, 720, and 815a.
reset bit nodes, used in hunks 697b, 702, 705a, 707{9, 713, 715, 720, and 778 .
Uses Arc Iterator 661a, has current 103, and Node Iterator 655b.

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

El segundo nivel en manejo de estado para un nodo o ar o es un ontador entero. Por lo


general, este se usa para determinar la antidad de visitas o para mar ar \ olores". Su
de ni ion para un nodo es omo sigue:
hMiembros de Graph Node<Node Type> 652ai+
(651b) 665b 667
long counter;

666e

y para un ar o:
hAtributos de Graph Arc<Arc Type> 657 i+

(657b) 665 667d

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);

inline void reset_counter(Node * node);

7.3. Un TAD para grafos (List Graph<Node, Arc>)

667a

o para los ar os:


hMiembros p
ubli os de List Graph<Node,

Arc> 651ai+
inline long & get_counter(Arc * arc);

667

(649) 666f 667b

inline void reset_counter(Arc * arc);

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());
}
De nes:
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;

de Graph Arc<Arc Type> 657 i+


cookie;

(657b) 666e 675b

668

668a

Captulo 7. Grafos

Los ookies pueden a ederse mediante las siguientes primitivas:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 667b 668b
inline void *& get_cookie(Node * node);
inline void *& get_cookie(Arc * arc);

get cookie() retorna una referen ia on privilegios ompletos.


668b

Los ookies de todos los nodos o ar os pueden reini iarse mediante:


hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 668a 669a
void reset_cookie_nodes()
{
for (Node_Iterator itor(*this); itor.has_current(); itor.next())
itor.get_current_node().cookie = NULL;
}

void reset_cookie_arcs()
{
for (Arc_Iterator itor(*this); itor.has_current(); itor.next())
itor.get_current_arc().cookie = NULL;
}
De nes:
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>;

Cuando el usuario requiera a eder a la lista, puede ha erlo mediante un asting:


static_cast<DynDlist<float>*>(node->get_cookie()) ...

Por supuesto, no debe olvidarse de entregar memoria al sistema uando esta ya no se


requiera. Tal a ion se realiza omo sigue:
delete static_cast<DynDlist<float>*>(node->get_cookie())

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

7.3. Un TAD para grafos (List Graph<Node, Arc>)

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);

static void map_arcs(Arc * p, Arc * q);


Uses map arcs 686 and map nodes 686.

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

realizamos la siguiente llamada:


map_nodes(q, t);

Enton es, el mapeo resultante se puede interpretar omo p q t p.


El mapeo entre los ar os, realizado mediante map arcs(), tiene semanti a similar al
de los nodos.
Las primitivas de mapeo permiten mantener adenas de mapeos y onstituyen una
manera de simpli ar mu hos algoritmos. La situa ion mas omun ata~ne a las lases de
algoritmos que modi an el grafo para efe tuar sus al ulos; lo que a arrea perdida del
grafo original. Para evitar eso, realizamos una opia on mapeo y operamos sobre la opia.
Los ookies del grafo opia mantienen la imagen del grafo original. Por supuesto, esto
tambien requiere que se mapeen los ar os; lo que se realiza de la siguiente manera:
El uso de los ookies, aunque bastante exible, es muy deli ado porque no estan sometidos al sistema de tipos del ompilador. Por esa razon, es altamente re omendable asegurarse de que los ookies se a edan mediante fun iones que efe tuen la onversion de tipo.
7.3.5.4

669b

Reinicio de nodos y arcos

Todos los atributos de ontrol de un nodo o de un ar o, es de ir, los bits, el ontador y el


ookie, pueden reini iarse ompletamente mediante:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 669a 670a
inline void reset_node(Node * node);

inline void reset_arc(Arc * arc);


Uses reset arc and reset node.

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> ();
}
De nes:
reset nodes, used in hunks 718b and 726 .
Uses operate on nodes 661 .

670b

Analogamente, la misma te ni a se apli a para reini iar los ar os:


hMiembros privados de List Graph<Node, Arc> 669 i+
(649) 669 672
struct Reset_Arc
{
void operator () (List_Graph&, Arc * arc)
{
arc->control_bits.reset();
arc->counter = 0;
arc->cookie = NULL;
}
};
Uses List Graph 649.

670

hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
void reset_arcs()
{
operate_on_arcs <Reset_Arc> ();
}
De nes:
reset arcs, used in hunks 718b, 726 , and 737a.
Uses operate on arcs 661b 661 .

(649) 670a 671a

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

Macros de acceso a nodos y arcos

A efe tos de fa ilitar la legibilidad de odigo, aunque en detrimento del diagnosti o


sinta ti o del ompilador, la implanta ion de List Graph<Node, Arc> y algunos algoritmos usan los siguientes ma ros:
hMa ros para grafos 670di
(648)
#
#
#
#

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)

7.3. Un TAD para grafos (List Graph<Node, Arc>)

# define
# define
# define
# define
De nes:

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)

ARC COOKIE, used in hunk 686.


ARC COUNTER, used in hunk 744b.
ARCS LIST, never used.
IS ARC VISITED, used in hunks 698b, 702, 705, 706, 710, 712, 715, 719a, 720, 724, 727, 738 , 740, 748,

787b, and 799a.

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,

778, 787, 797a, 798b, 814 , 818a, and 822b.

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();

inline List_Graph(const List_Graph & g);


Uses List Graph 649.

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

inline List_Graph& operator = (List_Graph & g);


Uses List Graph 649.

671

En este aso, el grafo destino se destruye ompletamente y se opia el fuente g.


El destru tor:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 671b 671d
inline virtual ~List_Graph();
Uses List Graph 649.

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);

inline List_Graph & operator = (List_Digraph<Node, Arc> & g);


Uses List Digraph 650b and List Graph 649.

672

672a

Captulo 7. Grafos

En o asiones no tan ex ep ionales, se requiere, ademas de la opia del grafo, un mapeo


isomorfo entre las opias. En esta situa ion espe ial puede usarse el metodo siguiente:
hMiembros p
ubli os de List Graph<Node, Arc> 651ai+
(649) 671d 672b
void copy_graph(List_Graph & src_graph, const bool cookie_map = false);
Uses copy graph 687b and List Graph 649.

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

Dlink arc_list; // lista de arcos


size_t num_arcs; // cantidad de arcos
De nes:
arc list, used in hunks 673 , 677a, 682b, 685a, and 689d.
node list, used in hunks 673 , 681b, and 685b.
Uses Dlink 90.

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>

7.3. Un TAD para grafos (List Graph<Node, Arc>)

673

const size_t & List_Graph<Node, Arc>::get_num_nodes() const


{
return num_nodes;
}
template <typename Node, typename Arc>
const size_t & List_Graph<Node, Arc>::get_num_arcs() const
{
return num_arcs;
}
Uses List Graph 649.

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);
}

static Arc * dlink_to_arc(Dlink * p)


{
return static_cast<Arc*>(p);
}
De nes:
dlink to arc, used in hunks 673 and 690a.
dlink to node, used in hunks 673 and 685b.
Uses Dlink 90.

7.3.8.1

673b
673

Acceso a nodos y arcos

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());
}
De nes:
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 .

Tanto la busqueda de nodos, omo la de ar os, requieren iterar sobre la orrespondiente


lista (node list o arc list segun sea el aso). Por esa razon, nos onviene implementar
estos iteradores a efe tos de que las busquedas se reali en mediante ellos.
Iterador de nodos
La lase Node Iterador deriva publi amente de Dlink.
Iterador de arcos

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

Los diferentes estilos de busqueda de nodos y ar os se instrumentan a traves de algunos


de los dos iteradores presentados en las se iones previas. La implementa ion de una de
las primitivas ilustra el esquema general:
hImplanta i
on de List Graph<Node, Arc> 672di+
(648) 673 675a
template <typename Node, typename Arc>
template <class Equal>
Node * List_Graph<Node, Arc>::search_node
(const typename Node::Node_Type & node_info)
{
for (Node_Iterator itor(*this); itor.has_current(); itor.next())
{
Node * current_node = itor.get_current_node();
if (Equal() (current_node->get_info(), node_info))
return current_node;
}
return NULL;
}
Uses has current 103, List Graph 649, Node Iterator 655b, and search node.

A la ex ep ion de la espe ializa ion por omision, ada rutina se fundamenta en el


iterador sobre nodos del grafo Node Iterator.
B
usqueda de arcos

7.3. Un TAD para grafos (List Graph<Node, Arc>)

675a

675

La busqueda de un ar o dentro del grafo es reminis ente a la de un nodo: re orrer


mediante el iterador de ar os hasta satisfa er la busqueda o haberlos re orrido enteramente,
en uyo aso la busqueda es fallida. La implementa ion es omo sigue:
hImplanta i
on de List Graph<Node, Arc> 672di+
(648) 674 675
template <typename Node, typename Arc>
template <class Equal>
Arc *
List_Graph<Node, Arc>::search_arc(const typename Arc::Arc_Type & arc_info)
{
for (Arc_Iterator it(*this); it.has_current(); it.next())
{
Arc * current_arc = it.get_current_arc();
if (Equal() (current_arc->get_info(), arc_info))
return current_arc;
}
return NULL;
}
template <typename Node, typename Arc>
bool List_Graph<Node, Arc>::arc_belong_to_graph(Arc * arc)
{
for (Arc_Iterator it(*this); it.has_current(); it.next())
if (it.get_current_arc() == arc)
return true;
return false;
}
De nes:

arc belong to graph, used in hunk 660 .


search arc, used in hunks 660, 681a, 694a, and 805 .
Uses Arc Iterator 661a, has current 103, and List Graph 649.

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 de nir el iterador de ar os sobre un nodo.
7.3.8.2
675b

Un ar o one ta dos nodos llamados origen y destino, de larados de la siguiente manera:


hAtributos de Graph Arc<Arc Type> 657 i+
(657b) 667d 678b
void *
void *

675

Arcos

src_node; // nodo origen


tgt_node; // nodo destino

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

Y mediante ella realizar el metodo de List Graph<Node, Arc>:


hImplanta i
on de List Graph<Node, Arc> 672di+
(648) 675 681a

template <typename Node, typename Arc>


Node * List_Graph<Node, Arc>::get_connected_node(Arc * arc, Node * node)
{
return static_cast<Node*>(arc->get_connected_node(node));
}
De nes:
get connected node, used in hunks 658b, 676a, 693e, and 757d.
Uses List Graph 649.

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, de nimos el
tipo Arc Node, uya espe i a ion es omo sigue:
hAr o-Nodo 676 i
(648)
struct Arc_Node : public Dlink
{

7.3. Un TAD para grafos (List Graph<Node, Arc>)

677

void * arc;
Arc_Node() : arc(NULL) { /* empty */ }
Arc_Node(void * __arc) : arc(__arc) { /* empty */ }
};
De nes:
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

Puntero hacia el arco

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

static Arc_Node * dlink_to_arc_node(Dlink * p)


{
return static_cast<Arc_Node*>(p);
}
De nes:
dlink to arc node, used in hunks 680 and 685a.
Uses Arc Node 676 and Dlink 90.
9 T
engase

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

En resumen, manejamos tres tipos de listas ir ulares doblemente enlazadas: la lista


de todos los nodos del grafo, la lista de todos los ar os del grafo y la lista por nodo de sus
ar os. Tales listas son a edidas mediante la lase Dlink. Por esa razon, planteamos las
siguientes fun iones de onversion:
Si tenemos, por ejemplo un puntero a un objeto Node, podemos a eder al primero de
sus ar os en su lista de Arc Node:
Arc_Node * p = dlink_to_arc_node(node->arc_list.get_next());

El ual debe transformarse a un objeto de tipo Arc mediante:


Arc * arc = static_cast<Arc*>(p->arc);

678a

pues p->arc es de tipo void*. Para endulzar un po o esta onversion, la en apsulamos en


la siguiente primitiva:
hMiembros privados de List Graph<Node, Arc> 669 i+
(649) 677b 679
static Arc * void_to_arc(Arc_Node * arc_node)
{
return static_cast<Arc*>(arc_node->arc);
}
De nes:
void to arc, used in hunk 685a.
Uses Arc Node 676 .

Completemos ahora la modeliza ion de la estru tura de datos nal. Re ordemos


que un nodo tiene una lista arc list de elementos de tipo Arc Node enlazados mediantes punteros dobles Dlink. A su vez, un Arc Node tiene un puntero a un ar o de
tipo Graph Arc<Arc Type>. Esta on gura ion permite una inser ion O(1) a la vez que
no se redunda el ar o. Sin embargo, as las osas, la elimina ion de un ar o requerira
re orrer la lista de ar os del nodo destino para en ontrar su Arc Node y eliminarlo. Por
esta razon, olo aremos en Graph Arc<Arc Type> un doble puntero al Arc Node, uno al
del nodo origen, otro al del nodo destino. El Graph Arc<Arc Type> tendra, enton es, la
forma pi tori a siguiente:
src_arc_node

Dlink
enlace
en la lista
de arcos del
grafo

678b

counter
control_bits
cookie

tgt_arc_node

Lo que onlleva a las siguientes de lara iones para un ar o:


hAtributos de Graph Arc<Arc Type> 657 i+

(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 .

7.3. Un TAD para grafos (List Graph<Node, Arc>)

679

y uya on gura ion nos permite resolver dos problemas:


1. Al momento de eliminar un ar o en un grafo, requerimos eliminar el Arc Node de
las listas del nodo origen y destino respe tivamente. Esto se logra en O(1) mediante
el a eso a los ampos src arc node y tgt arc node.
2. Cuando se trate de un digrafo, el nodo destino del ar o (in idente) no ontiene un
Arc Node que apunte al ar o. Del mismo modo el ampo tgt arc node se mantiene
en NULL.

679

List Digraph<Node, Arc> hereda toda la interfaz de List Graph<Node, Arc> y


asi toda su implanta ion. En algunas opera iones, requeriremos determinar si se trata de
un grafo o un digrafo. Para ello, utilizaremos una bandera logi a:
hMiembros privados de List Graph<Node, Arc> 669 i+
(649) 678a 690a
protected:
bool digraph;
private:
De nes:
digraph, used in hunks 681a, 682b, 684, 688, 689, 778 , and 780.

El valor de digraph es true si se trata de un digrafo; false de lo ontrario.


B

Figura 7.16: Un grafo simple


Por ejemplo pi tori o, la gura 7.17 muestra el estado de la estru tura de datos para
el grafo de la gura 7.16.

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

Figura 7.17: Representa ion del grafo de la gura 7.16

680

680a

Captulo 7. Grafos

De nidos los ampo de un ar o, podemos implantar sus onstru tores:


hM
etodos de Graph Arc<Arc Type> 657di+
(657b) 676a

Graph_Arc()
: counter(No_Visited), cookie(NULL),
src_node(NULL), tgt_node(NULL), src_arc_node(NULL), tgt_arc_node(NULL)
{
/* empty */
}

Graph_Arc(const Arc_Info & info)


: arc_info(info),
counter(No_Visited), cookie(NULL),
src_node(NULL), tgt_node(NULL), src_arc_node(NULL), tgt_arc_node(NULL)
{
/* empty */
}
Graph_Arc(void * src, void * tgt, const Arc_Info & data)
: arc_info(data), counter(No_Visited), cookie(NULL),
src_node(src), tgt_node(tgt), src_arc_node(NULL), tgt_arc_node(NULL)
{
// empty
}
Uses Graph Arc 657b.

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

Como ya se dijo, un iterador de ar os a partir de un nodo se realiza a partir de

680b

Dlink::Iterator, pues Arc Node es por deriva ion de tipo Dlink.


Node Arc Iterator debe guardar el nodo desde el ual se itera:
hMiembros privados de iterador de Node 680bi

(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());
}
De nes:
get current arc node, never used.
Uses Arc Node 676 , dlink to arc node 677b, and get current 103.

7.3. Un TAD para grafos (List Graph<Node, Arc>)

681

B
usqueda de un arco dados dos nodos

681a

Con el iterador de ar os sobre un nodo, podemos implantar fa ilmente la busqueda de


un ar o que one te dos nodos. La idea es tomar uno de los nodos, preferiblemente el que
ontenga menos ar os, y desde all re orrer sus ar os hasta en ontrar el otro nodo o hasta
que no hayan mas ar os que re orrer, en uyo aso la busqueda es fallida.
hImplanta i
on de List Graph<Node, Arc> 672di+
(648) 676b 681b
template <typename Node, typename Arc>
Arc * List_Graph<Node, Arc>::search_arc(Node * src_node, Node * tgt_node)
{
Node_Arc_Iterator itor;
Node * searched_node;
// Seleccionar el nodo que contenga menor cantidad de arcos
if (digraph or src_node->num_arcs < tgt_node->num_arcs)
{
searched_node = tgt_node;
itor = Node_Arc_Iterator(src_node);
}
else
{
searched_node = src_node;
itor = Node_Arc_Iterator(tgt_node);
}
// recorrer lista de arcos del nodo seleccionado en b
usqueda de
// searched_node
for (/* nada */; itor.has_current(); itor.next())
if (itor.get_tgt_node() == searched_node)
return itor.get_current_arc();

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

template <typename Node, typename Arc>


Node * List_Graph<Node, Arc>::insert_node
(const typename Node::Node_Type & node_info)
{
return insert_node( new Node (node_info) );
}
De nes:
insert node, used in hunks 653a, 681b, 687b, 718{20, 727, 748, 749, 778, 787, 797a, 798b, and 822b.
Uses List Graph 649.

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

El algoritmo anterior se odi a omo sigue:


hImplanta i
on de List Graph<Node, Arc> 672di+

(648) 682a 684


template <typename Node, typename Arc> Arc *
List_Graph<Node, Arc>::insert_arc(Node *
src_node,
Node *
tgt_node,
const typename Arc::Arc_Type & arc_info)
{
// paso (1): apartar memoria para Arc e iniciar
auto_ptr<Arc> arc ( new Arc ); // apartar memoria para arco
arc->src_node
= src_node;
// Iniciar sus campos
arc->tgt_node
= tgt_node;
arc->get_info() = arc_info;
// paso (3) (parcial): apartar memoria para el Arc_Node del nodo
// origen src_node
auto_ptr<Arc_Node> src_arc_node ( new Arc_Node (arc.get()) );
// Paso (2): si es un grafo ==> apartar memoria para el Arc_Node

7.3. Un TAD para grafos (List Graph<Node, Arc>)

683

// del nodo destino tgt_node


if (not digraph) // si es digrafo ==> no hay que insertar en el otro nodo
{
// inserci
on en nodo destino
if (src_node == tgt_node) // verificar si se trata de un ciclo
arc->tgt_arc_node = src_arc_node.get(); /* este es un ciclo */
else
{
// apartar arco nodo para tgt_node
auto_ptr<Arc_Node> tgt_arc_node ( new Arc_Node (arc.get()) );
// inserci
on en lista de adyacencia de nodo destino tgt_node
arc->tgt_arc_node = tgt_arc_node.get();
tgt_node->arc_list.append(tgt_arc_node.get());
tgt_node->num_arcs++;
tgt_arc_node.release(); // desbloquear auto-puntero tgt_arc_node
}
}
// paso (3) (resto) inserci
on en lista de adyacencia de nodo
// origen src_node
arc->src_arc_node = src_arc_node.get();
src_node->arc_list.append(src_arc_node.get());
src_node->num_arcs++;
// Paso (4) insertar arco en lista de arcos del grafo
arc_list.append(arc.get());
++num_arcs;
// desbloquear auto-punteros y terminar
src_arc_node.release(); // desbloquear tgt_arc_node
return arc.release();
// desbloquear arc
}
De nes:
insert arc, used in hunks 658d, 687b, 719b, 720, 727, 748, 749b, 776b, 787b, 798b, and 822b.
Uses arc list 672 , Arc Node 676 , digraph 679, and List Graph 649.

La odi a ion es ligeramente diferente al algoritmo en astellano porque en ella se


onsidera la posibilidad de que o urra una falla de memoria. Para mantener el odigo lo
mas pare ido al algoritmo en astellano usamos auto-punteros . Por eso, la estrategia es
apartar los tres bloques de memoria que requerimos (el del Arc y los dos Arc Node) y, si
no ha o urrido ex ep ion, enton es podemos insertar en todas las listas sin riesgo de falla.
10

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

Los pasos anteriores se re ejan en el siguiente odigo:


hImplanta i
on de List Graph<Node, Arc> 672di+

(648) 682b 685a

template <typename Node, typename Arc>


void List_Graph<Node, Arc>::remove_arc(Arc * arc)
{
// paso (1): eliminaci
on del Arc_node del nodo origen src_node
Node * src_node = get_src_node(arc);
Arc_Node * src_arc_node = arc->src_arc_node;
src_arc_node->del(); // desenlaza src_node de la lista de nodos
src_node->num_arcs--; // decrementa contador de arcos de src_node
delete src_arc_node;
// entrega memoria
if (not digraph)
{
// eliminaci
on arco en nodo destino
Node * tgt_node = get_tgt_node(arc);
if (src_node != tgt_node) // verificar eliminaci
on de ciclo
{ // paso (2): eliminaci
on del Arc_node del nodo destino tgt_node
Arc_Node * tgt_arc_node = arc->tgt_arc_node;
tgt_arc_node->del();
tgt_node->num_arcs--;
delete tgt_arc_node;
}
}
// eliminaci
on de arco del grafo
arc->del(); // desenlazar arc de la lista de arcos del grafo
--num_arcs;
delete arc;

}
De nes:

remove arc, used in hunks 659a, 685a, and 776b.


Uses Arc Node 676 , digraph 679, and List Graph 649.

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

7.3. Un TAD para grafos (List Graph<Node, Arc>)

685a

685

basamos en la elimina ion del ar o implantada en la sub-se ion anterior:


hImplanta i
on de List Graph<Node, Arc> 672di+
(648) 684 685b

template <typename Node, typename Arc>


void List_Graph<Node, Arc>::remove_node(Node * node)
{
// Eliminar todos los arcos adyacentes a node
while (not node->arc_list.is_empty()) // repita mientras a
un hayan arcos
{
// obtener el Arc_Node
Arc_Node * arc_node = dlink_to_arc_node(node->arc_list.get_next());
Arc * arc = void_to_arc(arc_node); // obtener el arco
remove_arc(arc); // eliminarlo del grafo
}
// en este punto el nodo ya no tiene arcos
node->del(); // desenlazar nodo de lista de nodos del grafo
--num_nodes;

delete node;
}
De nes:
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());

// avanzar antes de borrar para que iterador permanezca consistente


node_itor.next();
remove_node(p); // eliminarlo del grafo
}
}
De nes:

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

Mapeo de nodos y arcos

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 on gura 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);

7.3. Un TAD para grafos (List Graph<Node, Arc>)

687

ARC_COOKIE(p) = q;
}
De nes:
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
re eren 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;
De nes:
mapping table, used in hunk 687b.

mapping table mapea nodos del grafo origen src graph a nodos del grafo destino this.
687b

El metodo de opia se espe i a omo sigue:


hImplanta i
on de List Graph<Node, Arc> 672di+

(648) 686 688


template <typename Node, typename Arc>
void List_Graph<Node, Arc>::copy_graph(List_Graph<Node, Arc> & src_graph,
const bool
cookie_map)
{
IG(not is_digraph() and src_graph.is_digraph(),
is_digraph() != src_graph.is_digraph());
verify_graphs(src_graph);
try
{
clear_graph(); // limpiar this antes de copiar

on
hDe lara i

de tabla mapeo 687ai

// Fase (1): recorrer todos los nodos de grafo src_graph e


// insertar una copia en this
for (Node_Iterator it(src_graph); it.has_current(); it.next())
{
Node * src_node = it.get_current_node();

688

Captulo 7. Grafos

auto_ptr<Node> tgt_node(new Node(src_node)); // hacer una copia


mapping_table.insert(src_node, tgt_node.get()); // insertar en mapeo
Node * tgt = tgt_node.release();
insert_node(tgt); // insertarla en grafo destino
if (cookie_map)
map_nodes(src_node, tgt);
}
// Fase (2): recorrer todos los arcos de src_graph: para cada
// uno, crear en this un arco que conecte a los nodos mapeados
// de mapping_table previamente insertados en la fase anterior
for (Arc_Iterator it(src_graph); it.has_current(); it.next())
{
Arc * src_arc = it.get_current_arc();
// obtener im
agenes de nodos en el grafo destino
Node * src_node = mapping_table[src_graph.get_src_node(src_arc)];
Node * tgt_node = mapping_table[src_graph.get_tgt_node(src_arc)];
// insertar arco en grafo destino
Arc * tgt_arc = insert_arc(src_node, tgt_node, src_arc->get_info());
if (cookie_map)
map_arcs(src_arc, tgt_arc);
}
}
catch (...)
{
// Si ocurre excepci
on se limpia this
clear_graph();
throw;
}
}
De nes:

copy graph, used in hunks 672a and 689b.


Uses Arc Iterator 661a, clear graph 685b, has current 103, insert arc 682b, insert node 682a,
is digraph, List Graph 649, map arcs 686, map nodes 686, mapping table 687a, Node Iterator 655b,
and verify graphs.

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()

7.3. Un TAD para grafos (List Graph<Node, Arc>)

689

: num_nodes(0), num_arcs(0), digraph(false)


{
/* empty */
}
Uses digraph 679 and List Graph 649.

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.

(648) 688 689b

Los onstru tores opia son muy sen illos una vez que hemos de nido 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

Finalmente, la destru ion de un List Graph<Node, Arc> tambien es simple si nos


apoyamos sobre clear graph():
hImplanta i
on de List Graph<Node, Arc> 672di+
(648) 689b 689d
template <typename Node, typename Arc>
List_Graph<Node, Arc>::~List_Graph()
{
clear_graph();
}
Uses clear graph 685b and List Graph 649.

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

mergesort < Cmp_Arc <Compare> > (arc_list);


}
De nes:
sort arcs, used in hunks 659 and 776a.
Uses arc list 672 , Cmp Arc 690a, List Graph 649, and mergesort 209 213a.

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);
}
};
De nes:

Cmp Arc, used in hunk 689d.


Uses Dlink 90 and dlink to arc 673a.

7.4

690b

TAD camino sobre un grafo (Path<GT>)

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 de ne 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
};
De nes:
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.

7.4. TAD camino sobre un grafo (Path<GT>)

691a

Path<GT> guarda la instan ia del grafo en un atributo:


hMiembros privados de path 691ai
GT *

691b

691

(690b) 691e

g;

el ual es onsultable mediante:


hMiembros p
ubli os de path 691bi

(690b) 691

GT & get_graph() { return *g; }


De nes:
get graph, never used.

691

En o asiones, es ne esario validar si un amino sobre un grafo re ere 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

retorna true si gr es el mismo grafo que maneja la instan ia de objeto Path<GT>.


La lase Path<GT> lee los tipos on ernientes a los nodos y ar os del grafo de la
siguiente manera:
hTipos de path 691di
(690b)
public:
typedef typename GT::Node_Type Node_Type;
typedef typename GT::Arc_Type Arc_Type;

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

Path_Desc(Node * _node = NULL, Arc * _arc = NULL) : node(_node), arc(_arc) {}


};
De nes:
Path Desc, used in hunks 692{94.

692

692a

Captulo 7. Grafos

De este modo, un amino se representa omo una lista de Path Desc:


hMiembros privados de path 691ai+
(690b) 691f

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 re ere a un determinado grafo:
hMiembros p
ubli os de path 691bi+
(690b) 691 692
12

692b

Path(GT & _g) : g(&_g) { /* empty */ }


Uses Path 690b.

692

El onstru tor ini ia un amino va o sobre el grafo g.


O asionalmente puede requerirse el onstru tor por omision:
hMiembros p
ubli os de path 691bi+
(690b) 692b 692d
Path() : g(NULL) { /* empty */ }
Uses Path 690b.

692d

el ual no tiene grafo aso iado.


Para omenzar a operar sobre un amino va o hay que indi ar ual es su nodo ini ial.
Esto se realiza mediante la siguiente primitiva:
hMiembros p
ubli os de path 691bi+
(690b) 692 692e
void init(Node * start_node)
{
list.append(Path_Desc(start_node));
}
Uses Path Desc 691f.

692e

donde start node es nodo ini io del amino.


Se puede ha er lo mismo en tiempo de onstru ion mediante:
hMiembros p
ubli os de path 691bi+
(690b) 692d 692f
Path(GT & _g, Node * start_node) : g(&_g)
{
init(start_node);
}
Uses Path 690b.

692f

Donde start node es el nodo ini io del amino.


Puesto que se permite de larar un Path<GT> sin espe i ar su grafo, debemos dise~nar
un esquema que permita espe i arlo, luego de de larado un Path<GT>. Para ello,
proveemos la siguiente primitiva:
hMiembros p
ubli os de path 691bi+
(690b) 692e 693a
void set_graph(GT & __g, Node * start_node = NULL)
{
clear_path();
g = &__g;
if (start_node == NULL)
return;
12 En

realidad la tradi ion \aun" no esta instituida.

7.4. TAD camino sobre un grafo (Path<GT>)

693

init(start_node);
}
De nes:
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

Tambien es posible preguntar si el amino esta o no va o:


hMiembros p
ubli os de path 691bi+
(690b) 693a 693
bool is_empty() const { return list.is_empty(); }

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

La ual va a la lista de Path Desc.


Es posible onstruir un amino opia de otro amino o asignarlo:
hMiembros p
ubli os de path 691bi+
(690b) 693 693e

Path(const Path & path) : g(path.g), list(path.list) { /* empty */ }


Path & operator = (const Path & path)
{
if (this == &path)
return *this;
clear_path();
g = path.g;
list = path.list;

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

throw std::domain_error("path is empty");


Path_Desc & last_path_desc = list.get_last(); // ultimo elemento de list
if (not g->node_belong_to_arc(arc, last_path_desc.node))
throw std::invalid_argument("last node in path does "
"not incide on arc");
last_path_desc.arc = arc;
list.append(Path_Desc(g->get_connected_node(arc, last_path_desc.node)));
}
Uses get connected node 676b and Path Desc 691f.

694a

Otra forma de a~nadir un omponente al amino es espe i ar el siguiente nodo en lugar


del ar o. Para ello, se provee el append() por nodo:
hMiembros p
ubli os de path 691bi+
(690b) 693e 694b
void append(Node * node)
{
if (list.is_empty())
{
init(node);
return;
}
Node * last_node = get_last_node();
Arc * arc = g->search_arc(last_node, node); // busque arco last_node-node
if (arc == NULL)
throw std::domain_error("There is no arc connecting node");
append(arc);
}
Uses search arc 675a.

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())

7.5. Recorridos sobre grafos

695

throw std::domain_error("Path with only a node (without any arc");


typename DynDlist<Path_Desc>::Iterator it(list);
it.reset_last();
it.prev();
return it.get_current().arc;
}
Uses DynDlist 113a, get current 103, get first arc 673 , get first node 673 , Path 690b,
and Path Desc 691f.

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

la ual borra el ultimo ar o (junto on su nodo) del amino.


Luego de onstruido un amino, puede requerirse re orrerlo. Para esto se provee un
iterador y la siguiente familia de fun iones de opera ion sobre los omponentes de un
amino:
hMiembros p
ubli os de path 691bi+
(690b) 695a
template <class Operation> void operate_on_nodes()
{
for (Iterator it(*this); it.has_current(); it.next())
Operation () (it.get_current_node());
}

template <class Operation> void operate_on_arcs()


{
for (Iterator it(*this); it.has_current(); it.next())
Operation () (it.get_current_arc());
}
Uses has current 103, operate on arcs 661b 661 , and operate on nodes 661 .

7.5

Recorridos sobre grafos

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

una u otra o ambas maneras de explorarlo.


La idea de los re orridos es similar a los de los arboles estudiados en x 4: un patron de
pro esamiento presente en mu hos algoritmos uyo estudio sirve de entrenamiento para el
dise~no de algoritmos.
Los re orridos y otras primitivas basadas en ellos se de nen en el ar hivo htpl graph utils.H 696i,
uya estru tura se presenta a ontinua ion:
htpl graph utils.H 696i
hRe orrido en profundidad 697ai
hRe orrido en amplitud 702i
hPrueba de one tividad 701i
usqueda de i lo 704i
hB
hPrueba de a i li idad 706i
hPrueba de amino 709ai
hComponentes in onexos 726ai

hArboles
abar adores 717i
hPuntos de orte 736i
hComparador de ar os 775ai
7.5.1

Recorrido en profundidad

En un re orrido en profundidad, el grafo se visita tratando de onformar el amino mas


largo posible sin ne esidad de retornar a un nodo ya visitado. Es un re orrido de ndole
re ursiva. Si dft(p) fuese la primitiva de re orrido sobre el nodo p, enton es el algoritmo
general sera omo sigue:
Algoritmo 7.1 (Recorrido en profundidad desde el nodo p ) El algoritmo es re ursivo on prototipo dft(p); donde p es el nodo a tual de visita.
Se usa una variable global, num nodes, uyo valor ini ial es ero, para se~nalar la an-

tidad de nodos visitados.

1. Si p esta pintado; es de ir, ya fue visitado, = retorne.


2. Pintar p; de ese modo, p es mar ado omo visitado.
3. num nodes++;
4. Si num nodes == |V| = todos los nodos del grafo han sido re orridos y el algoritmo
termina.
5. a ar os adya entes de p
(a) Sea a el ar o a tual adya ente de p.
(b) Sea q el nodo in idente desde p a traves del ar o a.
( ) Llamar a dft(q);
Se le di e en \profundidad" porque, dado el nodo p, el re orrido no revisa un nuevo
ar o p r hasta no haber re orrido ompletamente en profundidad el nodo q desde el
ar o a tual p q.
Mu has ve es un problema sobre grafos onsiste en bus ar un nodo on algunas ara tersti as que representan la solu ion. Por esta razon, a los re orridos sobre grafos tambien

7.5. Recorridos sobre grafos

697a

697

se le denomina, indistintamente, \busquedas". Emplearemos omo sinonimo, enton es, la


expresion \busqueda en profundidad" para referir al re orrido homonimo.
Veamos ahora omo odi ar el re orrido en profundidad. Para ello, examinemos, en
primer lugar el prototipo de la fun ion re ursiva que nos implantara el algoritmo 7.1:
hRe orrido en profundidad 697ai
(696) 697b
template <class GT> inline static bool
__depth_first_traversal(GT &
typename GT::Node *
typename GT::Arc *
bool

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

embebido en la fun ion.


>Cuando se llama por primera vez? >sobre ual nodo se arran a el re orrido? Las
respuestas las expresa la rutina de uso publi o siguiente:
hRe orrido en profundidad 697ai+
(696) 697a 698a
template <class GT> inline
size_t depth_first_traversal(GT &

g,

698

Captulo 7. Grafos

typename GT::Node * start_node,


bool (*visit)(GT & g,
typename GT::Node *,
typename GT::Arc *)
)
{
g.reset_bit_nodes(Depth_First); // reiniciar bit Depth_First de los nodos
g.reset_bit_arcs(Depth_First); // reiniciar bit Depth_First de los arcos
size_t counter = 0; // inicialmente no se ha visitado ning
un nodo
// iniciar la exploraci
on recursiva
__depth_first_traversal <GT> (g, start_node, NULL, visit, counter);
return counter;
}
Uses depth first traversal 698b, depth first traversal 698a, reset bit arcs 666 ,
and reset bit nodes 666 .

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 *)
)
{

return depth_first_traversal <GT> (g, g.get_first_node(), visit);


}
De nes:
depth first traversal, used in hunks 697b and 701.
Uses get first node 673 .

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,

7.5. Recorridos sobre grafos

699

bool

(*visit)(GT & g,
typename GT::Node *,
typename GT::Arc *),
count)

size_t &
{

if (IS_NODE_VISITED(node, Depth_First)) // Fue el nodo visitado?


return false; // S
==> retorne y siga explorando
NODE_BITS(node).set_bit(Depth_First, true); // marca nodo visitado
count++;
if (visit != NULL) // verifique si hay funci
on de visita
if ((*visit)(g, node, arc)) // s
, inv
oquela
return true;
// recorrer arcos de node y visitar recursivamente nodos
// conectados en profundidad
for (typename GT::Node_Arc_Iterator it(node); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (IS_ARC_VISITED(arc, Depth_First)) // Ya se pas
o por este arco?
continue; // s
==> ign
orelo y vea el siguiente
ARC_BITS(arc).set_bit(Depth_First, true); // marca arco actual visitado
if (__depth_first_traversal(g, it.get_tgt_node(), arc, visit, count))
return true;
}
return false; // retorne y siga explorando
}
De nes:
depth first traversal, used in hunk 697.
Uses has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d, Node Arc Iterator 656a,
NODE BITS 670d, S 794 , and set bit 664a.

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

Figura 7.18: Un grafo ejemplo

700

Captulo 7. Grafos

En ierto modo, el re orrido en profundidad es reminis ente al re orrido pre jo sobre


un arbol. De he ho, su interpreta ion omo arbol es muy util para estudiar el sentido de
la explora ion. La gura 7.19 ilustra dos interpreta iones de re orrido para el grafo de la
gura 7.18; un re orrido que omienza en el nodo A y otro en N. Una vez estable ido el
nodo de ini io, el orden de visita depende de la topologa del grafo y del orden en que se
presenten los ar os en las listas de adya en ia de ada nodo.
A
B
J

(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,

7.5. Recorridos sobre grafos

701

para el peor aso, O(V) + O(E).


El peor oste en espa io de la explora ion profundidad o urre si el grafo es fuertemente
onexo. Una indi a ion de esto se muestra mirando el arbol abar ador en profundidad de
la gura 7.19-A. Observemos que tiene una sola rama; lo que es equivalente a una lista
enlazada. En este aso, la profundidad re ursiva es propor ional a O(V).
Si el grafo no es onexo, enton es el re orrido ulmina sin haber visitado todos los
nodos; aquellos que sean visitados dependeran del nodo por el ual se ini ie la busqueda.
El re orrido que hemos presentado bus a nodos, por eso podemos detenerlo uando se
han visitado todos los nodos. En o asiones, la busqueda se plantea en fun ion de ar os,
en uyo aso el re orrido tiende a ser mas ostoso, pues la deten ion debe ha erse uando
se hayan visitado todos lo ar os.
7.5.2

701

Conectividad entre grafos

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;

return depth_first_traversal <GT> (g, NULL) == g.get_num_nodes();


}
De nes:
test connectivity, used in hunks 778 and 780.
Uses depth first traversal 698a and is digraph.

test connectivity retorna true si el grafo es onexo, false de lo ontrario.

La prueba sobre el numero de ar os vale on re es la pena porque toma tiempo O(1)


y puede ahorrar ostes lineales. Empero, esta simple prueba de one tividad no es orre ta
si tratamos on un multigrafo.
7.5.3

Recorrido en amplitud

Como lo hemos indi ado, el re orrido en profundidad emula al re orrido pre jo 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 su ja, 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

el re orrido en amplitud. En esta lase de busqueda no se pro esa un nodo de un nivel i


hasta que no se hayan pro esado todos los nodos del nivel anterior i 1; es de ir, una
espe ie de re orridos por niveles. El medio para guardar los nodos por niveles es, esen ialmente, el mismo que para los arboles: una ola. En nuestro aso, usamos una ola de
punteros a ar os.
A
B

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

La diferen ia esen ial entre la busqueda en amplitud y la de profundidad es que la de


amplitud explora los aminos mas ortos primero, mientras que la de profundidad tiende
a ir por los mas largos. Este he ho se indi ia perfe tamente ontrastando los arboles
de re orridos, los uales son mas \frondosos" para el re orrido en amplitud (ver guras
x 7.19 (p
agina 700) y x 7.20 (pagina 702)).
La primitiva no es re ursiva. Tiene la misma interfaz de parametros que su ontraparte
en profundidad y se instrumenta del siguiente modo:
hRe orrido en amplitud 702i
(696) 714d
template <class GT> inline size_t
breadth_first_traversal(GT &
g,
typename GT::Node * start,
bool (*visit)(GT &, typename GT::Node *,
typename GT::Arc *)
)
{
g.reset_bit_nodes(Breadth_First); // reiniciar bit Breadth_First de nodos
g.reset_bit_arcs(Breadth_First); // reiniciar bit Breadth_First de arcos
DynListQueue<typename GT::Arc*> q; // cola de arcos pendientes
// ingresar a la cola los arcos de nodo start
for (typename GT::Node_Arc_Iterator it(start); it.has_current(); it.next())
q.put(it.get_current_arc());
NODE_BITS(start).set_bit(Breadth_First, true); // marcar visitado start
size_t node_counter = 0; // contador de nodos visitados
// mientras queden arcos en la cola y no se hayan visitado todos
// los nodos
while (not q.is_empty() and node_counter < g.get_num_nodes())
{

7.5. Recorridos sobre grafos

703

typename GT::Arc * arc = q.get(); // extraiga arco m


as cercano de la cola
ARC_BITS(arc).set_bit(Breadth_First, true); // marcar arco
// obtener nodos conectados al arco
typename GT::Node * src = g.get_src_node(arc);
typename GT::Node * tgt = g.get_tgt_node(arc);
if (IS_NODE_VISITED(src,
IS_NODE_VISITED(tgt,
continue; // si alguno
// avanzamos

Breadth_First) and
Breadth_First))
de los nodos ha sido visitado entonces
al siguiente arco en la cola

typename GT::Node * visit_node = // seleccionar nodo a visitar


IS_NODE_VISITED(src, Breadth_First) ? tgt : src;
// vis
telo, m
arquelo y cu
entelo
if (visit not_eq NULL)
if ((*visit)(g, visit_node, arc))
break;
NODE_BITS(visit_node).set_bit(Breadth_First, true);
node_counter++;
// insertar en cola arcos del nodo reci
en visitado
for (typename GT::Node_Arc_Iterator it(visit_node);
it.has_current(); it.next())
{
typename GT::Arc * curr_arc = it.get_current_arc();
if (IS_ARC_VISITED(curr_arc, Breadth_First))
continue; // ya fue previamente introducido desde el otro nodo
// revise nodos del arcos para ver si han
if (IS_NODE_VISITED(g.get_src_node(curr_arc),
IS_NODE_VISITED(g.get_tgt_node(curr_arc),
continue; // nodos ya visitados ==> no vale

sido visitados
Breadth_First) and
Breadth_First))
la pena meter el arco

q.put(curr_arc);
}
}
return node_counter;
}
De nes:
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.

La busqueda en amplitud tiende a ser mas ostosa en tiempo y en espa io que la de


profundidad, pues el en olamiento mantiene ar os que no han sido visitados; lo que no

704

Captulo 7. Grafos

su ede on el empilamiento de la busqueda en profundidad que alma ena ar os visitados.


El peor aso de eje u ion de la busqueda en amplitud o urre uando es ne esario visitar
todos los ar os antes de haber visto todos los nodos; o sea, O(E), ual es el mismo de la
busqueda en profundidad.
Para analizar el oste en espa io es onveniente observar la dinami a de inser ion
y elimina ion de ar os en la ola. Cuando se sa a de la ola un ar o pertene iente al
nivel i (respe to al nodo de ini io), la ola puede ontener ar os del nivel i y del superior
i + 1. Si interpretamos el re orrido omo el re orrido por niveles de un arbol, enton es
podemos per atarnos de que a lo sumo se tendran E/2 ar os en la ola; lo que equivale a
un oste O(E), ual puede ser bastante mayor que el oste en espa io O(V) del re orrido
en profundidad.
>Cuando re orrer en profundidad o en amplitud? La respuesta depende de varios fa tores, de los uales el prin ipal es la presun ion de er ana que se tenga a er a del nodo
solu ion; onsiderando que al riterio de er ana in ide la topologa del grafo y su densidad. Si el nodo solu ion se presume er ano, enton es el riterio de amplitud tiende a
en ontrarlo mas rapido que el de profundidad.
Hay una observa ion determinante para la busqueda en amplitud de un nodo parti ular:
esta on ertitud en uentra el amino mas orto en antidad de ar os.
Por buena ostumbre, no generalidad, se tiende a realizar primero una busqueda en
profundidad para en ontrar una solu ion y, posteriormente, para a narla, se realiza una
en amplitud.
7.5.4

704

Prueba de ciclos

Planteemos el problema de determinar si existe o no un i lo a partir de un nodo parti ular


src node. En este aso, un riterio en profundidad pare e preferible al de amplitud porque
no gasta tiempo ni espa io en los ar os par iales que aun no son pro esados.
Una busqueda en profundidad par ial, es de ir, que no ne esariamente pretenda visitar
todos los nodos, sirve para dete tar si existe un i lo a partir de un nodo. La idea es explorar
re ursivamente hasta en ontrar el nodo de partida, en uyo aso la prueba es positiva, o
ulminar la busqueda, en uyo aso es negativa.
Nuestra prueba de i lo se apoya sobre la siguiente rutina 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);
Uses test cycle 705b.

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:

1. g: el grafo sobre el ual se realiza la prueba.


2. src node: el nodo para el ual se veri a si existe un i lo.
3. curr node: es el nodo de visita de la llamada re ursiva.
La rutina retorna true si desde curr node se al anzo el nodo ini ial src node; en este
aso, existe un i lo. Si se re orre entera y re ursivamente el omponente one tado a

7.5. Recorridos sobre grafos

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

La interfaz publi a se plantea y se instrumenta de la siguiente forma:


hB
usqueda de i lo 704i+
(696) 704 705b

template <class GT> inline


bool test_cycle(GT & g, typename GT::Node * src_node)
{
g.reset_bit_nodes(Test_Cycle); // reiniciar bit Test_Cycle para los nodos
g.reset_bit_arcs(Test_Cycle); // reiniciar bit Test_Cycle para los arcos
// explorar recursivamente a trav
es de los arcos adyacentes a src_node
for (typename GT::Node_Arc_Iterator it(src_node); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (IS_ARC_VISITED(arc, Test_Cycle))
continue;
ARC_BITS(arc).set_bit(Test_Cycle, true); // pintar arco
// se encuentra un ciclo por el arco arc?
if (__test_cycle <GT> (g, src_node, it.get_tgt_node()))
return true; // s
==> retornar true y detener la exploraci
on
}
// En este punto se han explorado todos los caminos desde src_node
// sin encontrar de nuevo a src_node ==> no existe ciclo
return false;

}
De nes:

test cycle, used in hunks 662{64.


test cycle 705b, has current 103, IS ARC VISITED 670d, Node Arc Iterator 656a,
reset bit arcs 666 , reset bit nodes 666 , and set bit 664a.

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

return false; // ya se visit


o este nodo
NODE_BITS(curr_node).set_bit(Test_Cycle, true); // marque nodo
// busque los caminos desde current_node a ver si llega a src_node
for (typename GT::Node_Arc_Iterator it(curr_node); it.has_current();
it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (IS_ARC_VISITED(arc, Test_Cycle)) // se pas
o por este arco?
continue; // s
==> ignorar y ver el pr
oximo
ARC_BITS(arc).set_bit(Test_Cycle, true); // marque arco
if (__test_cycle <GT> (g, src_node, it.get_tgt_node()))
return true; // ciclo encontrado desde el arco actual
}
// En este punto se han explorado todos los caminos desde curr_node
// sin encontrar src_node ==> no existe ciclo que pase por curr_node
return false;
}
De nes:

test cycle, used in hunks 704 and 705a.


Uses has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d, Node Arc Iterator 656a,
NODE BITS 670d, and set bit 664a.

7.5.5

706

Prueba de aciclicidad

Un grafo es a  li o si este no ontiene i los. Como mu hos algoritmos requieren veri ar


esta ondi ion sobre al ulos de grafos par iales, es importante que esta prueba se reali e
e ientemente.
Una variante del algoritmo anterior de prueba de i los puede utilizarse para veri ar
a i li idad sobre un grafo in onexo. Podemos invo ar a test cycle() para ada nodo del
grafo y, si todas las eje u iones arrojan omo resultado falso, enton es el grafo es a  li o.
El prin ipio de la prueba de a i li idad es que si durante la explora ion del grafo,
pasando por un ar o no visitado, vemos a un nodo ya visitado, enton es el grafo forzosamente tiene que tener un i lo (el que permite regresar al nodo visitado por un amino
diferente). En virtud de esta observa ion podemos plantear la siguiente rutina, privada,
re ursiva, de explora ion en profundidad:
hPrueba de a i li idad 706i
(696) 707
template <typename GT> inline static
bool __is_acyclique(GT & g, typename GT::Node * curr_node)
{
if (IS_NODE_VISITED(curr_node, Is_Acyclique)) // ha sido el nodo visitado?
return false; // s
==> se regresa desde arco no visitado ==> ciclo!
NODE_BITS(curr_node).set_bit(Is_Acyclique, true); // marcar nodo

7.5. Recorridos sobre grafos

707

// recorremos en profundidad curr_node


for (typename GT::Node_Arc_Iterator i(curr_node); i.has_current(); i.next())
{
typename GT::Arc * arc = i.get_current_arc();
if (IS_ARC_VISITED(arc, Is_Acyclique))
continue; // arco ya visitado, pase al siguiente
ARC_BITS(arc).set_bit(Is_Acyclique, true); // pintar arco
// se detect
o ciclo pasando por curr_node?
if (not __is_acyclique(g, i.get_tgt_node()))
return false; // s
==> detenemos ejecuci
on porque sabemos respuesta
}
// todos los arcos recorridos sin encontrar ciclo ==> el grafo es
// ac
clico pasando por curr_node
return true;
}
De nes:

is acyclique, used in hunks 707 and 708.


Uses has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d, Node Arc Iterator 656a,
NODE BITS 670d, and set bit 664a.

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

return __is_acyclique(g, start_node);


}
De nes:
is acyclique, used in hunks 662{64, 708, and 723b.
Uses is acyclique 706, is digraph, reset bit arcs 666 , and reset bit nodes 666 .

is acyclique() realiza la explora ion re ursiva en profundidad start node es el nodo

708

de ini io de la prueba, el ual debe pertene er a un omponente de grafo \sospe hoso" de


tener un i lo. La utilidad de la interfaz anterior es uando start node haya sido a~nadido
en algun al ulo y se requiera veri ar si la adi ion ausa o no un i lo. Esta rutina fun iona
para ualquier grafo onexo (sin importar el nodo de ini io) o para veri ar a i li idad
desde un nodo parti ular start node.
Si no se tiene ono imiento a er a de la one tividad del grafo, enton es es ne esario
inspe ionar los eventuales omponentes onexos del grafo; uestion que se puede realizar
del siguiente modo:
hPrueba de a i li idad 706i+
(696) 707
template <typename GT> inline
bool is_acyclique(GT & g)
{
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);
// recorrer todos los nodos
for (typename GT::Node_Iterator it(g); it.has_current(); it.next())
{
typename GT::Node * current_node = it.get_current_node();
if (IS_NODE_VISITED(current_node, Is_Acyclique))
continue; // nodo ya probado, avance al siguiente
// es ac
clico desde current_node?
if (not __is_acyclique(g, current_node))
return false; // s
==>
}
return true;
}
Uses is acyclique 706, has current 103, is acyclique 707, is digraph, IS NODE VISITED 670d,
Node Iterator 655b, reset bit arcs 666 , and reset bit nodes 666 .

Notese que despues de la primera llamada a is acyclique(g, curr node), algunos


nodos ya quedan mar ados on el valor Is Acyclique y que sobre estos no se debe repetir
la prueba de a i li idad.
Si bien la prueba por existen ia de i los es muy pare ida a la prueba de a i li idad,
vale la pena, por razones de desempe~no e identi a ion, realizarlas por separado y no en
el mismo algoritmo.

7.5. Recorridos sobre grafos

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;
}
De nes:

test path, used in hunks 662{64.


Uses test path 710, has current 103, is digraph, Node Arc Iterator 656a, reset bit arcs 666 ,
reset bit nodes 666 , and set bit 664a.

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

if (IS_NODE_VISITED(curr_node, Test_Path)) // Ya se visit


o curr_node?
return false; // s
, no explore
NODE_BITS(curr_node).set_bit(Test_Path, true); // pintar curr_node
// buscar recursivamente a trav
es de arcos de curr_node

7.5. Recorridos sobre grafos

711

for (typename GT::Node_Arc_Iterator i(curr_node); i.has_current(); i.next())


{
typename GT::Arc * arc = i.get_current_arc();
if (IS_ARC_VISITED(arc, Test_Path)) // Ya se vio este arco?
continue; // s
, ign
orelo y siga al siguiente
ARC_BITS(arc).set_bit(Test_Path, true); // pintar arco
// buscar camino por el arco actual arc
if (__test_path <GT> (g, i.get_tgt_node(), end_node))
return true;
}
// todos los arcos adyacente de curr_node fueron explorados sin
// encontrar a end_node ==> no existe camino pasando por curr_node
return false;
}
De nes:

test path, used in hunk 709.


Uses has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d, Node Arc Iterator 656a,
NODE BITS 670d, and set bit 664a.

Observemos que, en detrimento de la e ien ia, a traves de test path() podemos


en ontrar i los o lazos.
El tiempo de eje u ion de test path() es, en el peor aso, propor ional a la antidad
de ar os, pues este no iterara mas E ve es. test path() es, por tanto, O(E).
Con la representa ion matri ial, existe una manera de al ular las rela iones de one tividad entre todos los nodos de un grafo. Tal rela ion se llama \ lausura transitiva"
y se al ula mediante un algoritmo llamado de \Warshall" que sera presentado
en x 7.6.5 (pagina 769).
7.5.6.2

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.

find path depth first() en uentra y onstruye, mediante una b


usqueda en profundidad,
un amino entre start node y end node del grafos g. El amino resultante se guarda en el
parametro path. La rutina retorna true si, en efe to, se logra en ontrar un amino; false
de lo ontrario, en uyo aso el valor de path debe onsiderarse indeterminado.

712

712

Captulo 7. Grafos

find path depth first() es la interfaz p


ubli a, la ual se apoya sobre la rutina siguiente, que es la que realiza la explora ion re ursiva en profundidad:
hCamino de grafo 690bi+
(648) 711 713
template <class GT> inline static bool
__find_path_depth_first(GT&
g,
typename GT::Node * curr_node, //
typename GT::Arc * curr_arc, //
typename GT::Node * end_node, //
Path<GT> &
curr_path) //
{
if (curr_node == end_node) // se alcanz
o nodo final?
{
// s
, terminar a~
nadir el arco y terminar
curr_path.append(curr_arc);

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;
}
De nes:

find path depth first, used in hunk 713.


Uses has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d, Node Arc Iterator 656a,
NODE BITS 670d, Path 690b, and set bit 664a.

7.5. Recorridos sobre grafos

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

De nes:

find path depth first, used in hunks 711 and 801b.


Uses find path depth first 712, has current 103, IS NODE VISITED 670d, Node Arc Iterator 656a,
NODE BITS 670d, Path 690b, reset bit arcs 666 , reset bit nodes 666 , and set bit 664a.

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 de nitivamente 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)
{

7.5. Recorridos sobre grafos

715

auto_ptr<Path<GT> > path_auto;


if (path_ptr == NULL)
path_auto = // crear camino nuevo con origen en node
auto_ptr<Path<GT> > ( new Path<GT> (g, node) );
else
path_auto = // crear copia de camino *path_ptr
auto_ptr<Path<GT> > ( new Path<GT> (*path_ptr) );
path_auto->append(arc); // a~
nadir el nuevo arco
q.put(path_auto.get()); // insertar el nuevo camino en cola
path_auto.release();
}
De nes:
insert in queue, used in hunk 715.
Uses DynListQueue 166 and Path 690b.

insert in queue() rea un nuevo amino on el ar o a~


nadido arc y lo en ola en la
ola q. La rutina maneja una variable Path llamada path auto, on el ual, segun el valor
de path ptr, se pro esa de dos posibles formas:

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)

does not belong to graph");

path.clear_path(); // limpiamos cualquier cosa que est


e en path
g.reset_bit_nodes(Find_Path);
g.reset_bit_arcs(Find_Path);

on
hDe lara i

de ola de aminos

on
hDe lara i

de amino a tual 714 i

try

714ai

716

Captulo 7. Grafos

// insertar los caminos parciales con nodo inicio start


for (typename GT::Node_Arc_Iterator i(start); i.has_current(); i.next())
__insert_in_queue<GT>(g, q, start, i.get_current_arc());
NODE_BITS(start).set_bit(Find_Path, true); // m
arquelo visitado
while (not q.is_empty()) // repetir mientras haya arcos sin visitar
{
path_ptr = q.get(); // extraiga camino actual
typename GT::Arc * arc = path_ptr->get_last_arc();
ARC_BITS(arc).set_bit(Find_Path, true); // marcar arco
typename GT::Node * tgt = path_ptr->get_last_node();
// se visit
o el
ultimo nodo del camino?
if (IS_NODE_VISITED(tgt, Find_Path))
{
// s
==> el camino no conduce al nodo end ==> borrar camino
delete path_ptr;
continue;
}
if (tgt == end) // se encontr
o un camino?
{
// s
==> path_ptr contiene el camino buscado
path.swap(*path_ptr); // copiar resultado al par
ametro path
hva iar

ola y liberar aminos

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);

7.5. Recorridos sobre grafos

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

ola y liberar aminos

714bi

delete path_ptr;
return false;
}
De nes:

find path breadth first, never used.


insert in queue 714d, has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d,
Node Arc Iterator 656a, NODE BITS 670d, Path 690b, reset bit arcs 666 , reset bit nodes 666 ,
and set bit 664a.

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

en profundidad nodos adya entes a gnode 719ai

return true;
}
De nes:

718

Captulo 7. Grafos

find depth first spanning tree, never used.

find depth first spanning tree() maneja tres parametros:

1. g es un grafo onexo sobre el ual se requiere onstruir un arbol abar ador.


2. gnode es el nodo ini ial desde el ual se desea omenzar la onstru ion del
arbol abar ador.
3. tree es un grafo de salida donde se olo ara el arbol abar ador resultante.

718a

El valor de retorno es true si existe un arbol abar ador; false de lo ontrario.


En a~nadidura, find depth first spanning tree() mapea los nodos y ar os a traves
de los ookies. De este modo, el usuario puede ono er, dentro del grafo, uales nodos o
ar os pertene en al arbol abar ador.
Eventualmente, puede requerirse un arbol abar ador sin ne esidad de a ordar un
nodo ini ial. En tal aso, puede usarse la siguiente primitiva:

hArboles
(696) 717 719b
abar adores 717i+
template <class GT> inline
typename GT::Node * find_depth_first_spanning_tree(GT & g, GT & tree)
{
typename GT::Node * start_node = g.get_first_node();
if (not find_depth_first_spanning_tree(g, start_node, tree))
return NULL;
return start_node;
}
De nes:

find depth first spanning tree, never used.

Uses get first node 673 .

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();

tree.clear_graph(); // asegurar que


arbol destino est
e vac
o
NODE_BITS(gnode).set_bit(Spanning_Tree, true); // marcar gnode
// crear imagen de start_node en
arbol abarcador y mapearla
typename GT::Node * tnode = tree.insert_node(gnode->get_info());
GT::map_nodes(gnode, tnode);
Uses clear graph 685b, insert node 682a, map nodes 686, NODE BITS 670d, reset arcs 670 ,
reset nodes 670a, and set bit 664a.

7.5. Recorridos sobre grafos

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.

La estru tura es similar a la de otros algoritmos basados en el re orrido en profundidad.


La rutina re ursiva es find depth first spanning tree(), la ual a epta los siguientes
parametros en el orden presentado:
1. g: el grafo a re orrer.
2. arc tgt node: un nodo dentro de g que aun no ha sido visitado y que no tiene
imagen en tree.
3. garc: el ar o a tual de g uyo nodo origen es gnode. Notemos que el
ar o aun no tiene imagen en tree; este sera insertado al omienzo de
find depth first spanning tree().
4. gnode: un nodo de g, ya visitado, que tiene imagen en tree.
5. tree: el arbol abar ador que se esta onstruyendo.
6. tnode: la imagen de gnode en tree. Notemos que en este aso funge de nodo destino.
719b

La rutina re ursiva resultante tiene, enton es, la siguiente implanta ion:



hArboles abar adores 717i+
(696) 718a 720
template <class GT> inline static
bool __find_depth_first_spanning_tree(GT &
g,
typename GT::Node * gnode,
typename GT::Arc * garc,
GT &
tree,
typename GT::Node * tnode)
{
NODE_BITS(gnode).set_bit(Spanning_Tree, true); // marcar nodo
ARC_BITS(garc).set_bit(Spanning_Tree, true);
// marcar arco

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

en profundidad nodos adya entes a gnode 719ai

return false;
}
De nes:

find depth first spanning tree, used in hunk 719a.


Uses insert arc 682b, insert node 682a, map arcs 686, map nodes 686, NODE BITS 670d,
and set bit 664a.

De este algoritmo es fundamental entender su ondi ion de parada, ual se al anza


uando se han abar ado todos los nodos de g; es de ir, uando la antidad de nodos
de tree sea la misma que la de g. Tambien es importante notar que al evitar la a~nadidura
de un ar o o nodo ya visitado en tree, es imposible ausar un i lo; razon que garantiza
que tree sea, en efe to, un arbol.
7.5.9

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;

7.5. Recorridos sobre grafos

721

for (typename GT::Node_Arc_Iterator i(gnode); i.has_current(); i.next())


q.put(i.get_current_arc());
NODE_BITS(gnode).set_bit(Spanning_Tree, true); // marque visitado gnode
while (not q.is_empty()) // repita mientras queden arcos en la cola
{
typename GT::Arc * garc = q.get();
ARC_BITS(garc).set_bit(Spanning_Tree, true); // marque visitado garc
// obtenga nodos del arco en g garc
typename GT::Node * gsrc = g.get_src_node(garc);
typename GT::Node * gtgt = g.get_tgt_node(garc);
if (IS_NODE_VISITED(gsrc, Spanning_Tree) and
IS_NODE_VISITED(gtgt, Spanning_Tree))
continue; // los dos nodos del arco ya fueron visitados
// decidir cual es el nodo ya marcado y mapeado
if (IS_NODE_VISITED(gtgt, Spanning_Tree)) // es gtgt?
Aleph::swap(gsrc, gtgt); // s
, interc
ambielo con gsrc
typename GT::Node * tsrc = // obtener imagen de gsrc en tree
static_cast<typename GT::Node*>(NODE_COOKIE(gsrc));
NODE_BITS(gtgt).set_bit(Spanning_Tree, true); // marque gtgt visitado
// crear copia de gtgt, insertarlo en tree y mapearlo
auto_ptr<typename GT::Node> ttgt_auto (new typename GT::Node(gtgt));
tree.insert_node(ttgt_auto.get());
typename GT::Node * ttgt = ttgt_auto.release();
GT::map_nodes(gtgt, ttgt);
// insertar nuevo arco en tree y mapearlo
typename GT::Arc * tarc = tree.insert_arc(tsrc, ttgt, garc->get_info());
GT::map_arcs(garc, tarc);
// tree abarca al grafo g?
if (tree.get_num_nodes() == g.get_num_nodes())
break; // s
, terminar
// insertar en cola arcos de gtgt
for (typename GT::Node_Arc_Iterator i(gtgt); i.has_current(); i.next())
{
typename GT::Arc * current_arc = i.get_current_arc();
if (IS_ARC_VISITED(current_arc, Spanning_Tree))
continue; // ya fue previamente introducido desde el otro nodo
// revise nodos de arcos para ver si han sido visitados

722

Captulo 7. Grafos

if (IS_NODE_VISITED(g.get_src_node(current_arc), Spanning_Tree) and


IS_NODE_VISITED(g.get_tgt_node(current_arc), Spanning_Tree))
continue; // nodos ya visitados ==> no vale la pena meter el arco
q.put(current_arc);
}
}
}
De nes:

find breadth first spanning tree, never used.


Uses clear graph 685b, DynListQueue 166 , has current 103, insert arc 682b, insert node 682a,
IS ARC VISITED 670d, IS NODE VISITED 670d, map arcs 686, map nodes 686, Node Arc Iterator 656a,
NODE BITS 670d, NODE COOKIE 670d, reset bit arcs 666 , reset bit nodes 666 , and set bit 664a.

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.

7.5. Recorridos sobre grafos

723

En el operador () de esta lase se delega la onversion a traves de la siguiente


llamada:
void operator () (typename GT::Node * groot, Tree_Node<Key>* troot)

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

graph to tree node() se apoya sobre la siguiente rutina privada:


hConversi
on de List Graph<Node, Arc> a Tree Node<T> 722bi+
(722a) 722b 723b

template <typename GT, typename Key, class Convert> static void


__graph_to_tree_node(GT & g, typename GT::Node * groot, Tree_Node<Key> * troot);
Uses Tree Node 322.

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

Figura 7.21: Un grafo vial de iudades y distan ias

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)");

Tree_Node<Key> * troot = new Tree_Node<Key>; // apartar memoria para ra


z

724

Captulo 7. Grafos

Convert () (groot, troot); //convertir de groot y copiar a troot


// llamada recursiva que construye el Tree_Node<Key>
__graph_to_tree_node <GT, Key, Convert> (g, groot, troot);
return troot;
}
De nes:

graph to tree node, used in hunk 722b.


Uses is acyclique 707 and Tree Node 322.

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)
{

7.5. Recorridos sobre grafos

725

typedef typename GT::Node_Arc_Iterator Iterator;


typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
// recorrer arcos de groot y construir recursivamente
for (Iterator it(groot); it.has_current(); it.next())
{
Arc * arc = it.get_current_arc();
if (IS_ARC_VISITED(arc, Convert_Tree))
continue;
ARC_BITS(arc).set_bit(Convert_Tree, true); // marcar visitado arc
Node * gtgt = it.get_tgt_node();
Tree_Node<Key> * ttgt = new Tree_Node<Key>; // crear nuevo nodo
Convert () (gtgt, ttgt); // asignarle la clave
troot->insert_rightmost_child(ttgt); // insertarlo como hijo
__graph_to_tree_node <GT, Key, Convert> (g, gtgt, ttgt);
}
}
De nes:

graph to tree, never used.


Uses has current 103, insert rightmost child 329, IS ARC VISITED 670d, Node Arc Iterator 656a,
set bit 664a, and Tree Node 322.

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

Componentes inconexos de un grafo

Dado un grafo, se desea determinar la antidad de subgrafos in onexos que lo onforman.


A tales efe tos, onsideremos la siguiente primitiva:
hComponentes in onexos 726ai
(696) 726b
template <class GT> inline
void inconnected_components(GT & g, DynDlist<GT> & list);
Uses DynDlist 113a and inconnected components 726 .

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

build subgraph() re orre en profundidad el grafo g y onstruye un omponente sg seg


un
la one tividad del nodo que se este visitando. g src es el \nodo a tual" de g que se intenta
visitar. node count es un ontador sobre el numero de nodos visitados que permite dete tar

726

uando se ha inspe ionado todo el grafo.


build subgraph() es invo ado por inconnected components(), el ual re orre todos
los nodos del grafo y, para ada nodo curr node que no haya sido visitado, realiza una
llamada a build subgraph(), la ual onstruira el omponente in onexo orrespondiente
al re orrido en profundidad desde curr node. Para poder mapear todo el omponente
in onexo, la deten ion debe realizarse uando se hayan re orrido todos los ar os.
Bajo las re iente premisas, podemos enun iar el algoritmo de nitivo:
hComponentes in onexos 726ai+
(696) 726b 727
template <class GT> inline
void inconnected_components(GT & g, DynDlist <GT> & list)
{

7.5. Recorridos sobre grafos

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);
}
}
De nes:

inconnected components, used in hunk 726a.


Uses build subgraph 727, DynDlist 113a, has current 103, IS NODE VISITED 670d, Node Iterator 655b,
reset arcs 670 , and reset nodes 670a.

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

if (sg_src == NULL) // est


a mapeado g_src?
{
// No, cree imagen de g_src en el subgrafo sg y mapee
sg_src = sg.insert_node(g_src->get_info());
GT::map_nodes(g_src, sg_src);
}
// explore en profundidad a partir de g_src
for (typename GT::Node_Arc_Iterator i(g_src);
node_count < g.get_num_nodes() and i.has_current(); i.next())
{
typename GT::Arc * arc = i.get_current_arc();
if (IS_ARC_VISITED(arc, Build_Subtree))
continue; // avance a pr
oximo arco
ARC_BITS(arc).set_bit(Build_Subtree, true); // marque arc visitado
// ahora observe el nodo destino del arco
typename GT::Node * g_tgt = i.get_tgt_node();
typename GT::Node * sg_tgt =
static_cast<typename GT::Node *>(NODE_COOKIE(g_tgt));
if (sg_tgt == NULL) // est
a mapeado en sg?
{
// no, hay que mapearlo e insertarlo en el subgrafo sg
sg_tgt = sg.insert_node(g_tgt->get_info());
GT::map_nodes(g_tgt, sg_tgt);
}
// tenemos los nodos en el subgrafo, insertamos el arco
typename GT::Arc * sg_arc =
sg.insert_arc(sg_src, sg_tgt, arc->get_info());
GT::map_arcs(arc, sg_arc);
// continue recorriendo en profundidad y construyendo el subgrafo
build_subgraph(g, sg, g_tgt, node_count);
}
}
De nes:

build subgraph, used in hunk 726.


Uses has current 103, insert arc 682b, insert node 682a, IS ARC VISITED 670d, IS NODE VISITED 670d,
map arcs 686, map nodes 686, Node Arc Iterator 656a, NODE BITS 670d, NODE COOKIE 670d,
and set bit 664a.

7.5.12

Puntos de articulaci
on de un grafo

Dado un grafo onexo, un punto de articulacion, o de corte es un nodo tal que al


eliminarse divide al grafo en al menos dos subgrafos disjuntos. La gura 7.24 muestra un
ejemplo en el ual sus puntos de arti ula ion estan resaltados.
La determina ion de los puntos de arti ula ion es fundamental para la prueba de
planaridad y para algunos algoritmos de dibujado; a la vez que onstituye otra apli a ion

7.5. Recorridos sobre grafos

729

Figura 7.24: Un grafo y sus puntos de arti ula ion


mas de la busqueda en profundidad. Tambien, de ierto modo, revela los \puntos rti os"
de un grafo. Por ejemplo, en una red ele tri a, los puntos de arti ula ion indi an los puntos
que al fallar pueden omprometer la red; lo mismo apli a a la mayora de lases de redes:
de transporte, de paquetes de red, et etera.
En la dedu ion de un algoritmo que determine los nodos de orte, es menester ara terizar algunas de sus propiedades y sus rela iones on un arbol abar ador produ ido por
una busqueda en profundidad. Comen emos por la mas simple de todas, ara terizada por
el siguiente lema:
Lema 7.1 Si un nodo v es de orte, tal que divide un grafo G en dos omponentes G1 y
G2, respe tivamente, enton es, ada amino desde un nodo u en G1 ha ia un nodo w en
G2 debe, ne esariamente, ontener a v.
Demostraci
on Si el lema no fuese ierto, enton es sera ontradi torio que v fuese un
punto de orte, pues existira otro amino entre v y w que indi iara otra rama de one tividad. La gura 7.25 pi toriza esta situa ion.

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

tiene mas de un hijo.


Demostraci
on

730

Captulo 7. Grafos

Necesidad = (contradicci
on)

uni o hijo w:

Supongamos que v es un nodo de orte on un

G - {v}

.
.

Enton es, w es un arbol abar ador que abar a a los nodos restantes V {v}, lo que ontradi e la a rma ion de que v es un nodo de orte 
Suficiencia = Supongamos dos nodos u y w hijos de v:
v

Ninguno de los des endientes de u es an estro o des endiente de w en T ; analogamente,


lo mismo o urre para w. Puesto que T fue re orrido en profundidad, todos los nodos del
sub-arbol u son visitados antes que los nodos del sub-arbol w. Esto indi a que ningun
nodo de v esta one tado a un nodo de w por algun ar o de G, porque sino el re orrido en
profundidad los hubiese des ubierto. Lo que impli a que ada amino entre un nodo del
sub-arbol u ha ia un nodo del sub-arbol w ontiene al nodo raz v, el ual, por el lema 7.1,
es un punto de orte 
B

Figura 7.26: Un grafo on un punto de arti ula ion


De este lema, podemos enun iar un primer algoritmo:
Algoritmo 7.2 (Determina ion de los puntos de orte de un grafo (1ra version))
Entrada: Un grafo onexo G =< V, E >.
Salida: Un onjunto P on los nodos de orte de G.

1. P =

7.5. Recorridos sobre grafos

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

.....
.

......
.

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

Figura 7.28: Un grafo sin puntos de arti ula ion


El algoritmo 7.2 puede realizarse sin ne esidad de onstruir el arbol abar ador. Basta
on realizar un re orrido en profundidad tradi ional (mediante depth first traversal())

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 on rmamos 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

por un ar o no-abar ador.


En otras palabras, v es un nodo de orte si este ontiene un sub-arbol uyos ar os noabar adores no one ten a nodos an estros de v. Como ejemplos, onsidere las guras 7.27-a
y 7.27-b. En la gura 7.27-a ninguno de los ar os no-abar adores del sub-arbol de H on
raz I one ta a un an estro de H. En la gura 7.27-b, ninguno de los nodos del sub-arbol
uya raz es F se one ta on un ar o no-abar ador a un an estro de H.

7.5. Recorridos sobre grafos

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 de nido 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)

i. Sea A el onjunto de todos los an estros de ni en T .


ii. Sea D el onjunto de todos los des endientes de v en T .

734

Captulo 7. Grafos

iii. (x, y), x A, y D, (x, y) / G = P = P {ni}


En su version literal, el algoritmo anterior es realizable y de mejor desempe~no que
el 7.2. Empero, este requiere memoria para el arbol abar ador, explora ion a fuerza bruta,
tanto en el grafo y omo en el arbol, y, la determina ion de los onjuntos A y D de
an estros y des endientes, respe tivamente.
Un posible enfoque para mejorarlo es evadir la onstru ion del arbol abar ador y
usar el propio grafo. Puesto que la busqueda en profundidad mar a los ar os que re orre,
aquellos ar os que no fueron mar ados son los que no pertene en al arbol abar ador. De
este modo se puede evadir la onstru ion del arbol y utilizar el algoritmo 7.3. Sin embargo,
aun no se evade, ni la explora ion del grafo para determinar los ar os que no pertene en
al arbol abar ador, ni la onstru ion, impl ita o expli ita, de los onjuntos A y D.
Afortunadamente, dado uno nodo v y uno de sus hijos w, existe una manera indire ta
de determinar uales nodos pertene en al onjunto A de los an estros de v y al onjunto D
de los des endientes de w. Para ello, requerimos la siguiente de ni ion:
Definici
on 7.1 (N
umero df ) : Sea un grafo onexo G =< V, E > sobre el ual se realiza
un re orrido en profundidad. Sea v V un nodo. Enton es, df (v) se de ne omo el ordinal
de visita de un re orrido en profundidad en el ual df (v) = 0. En otras palabras, df (v)

denota el orden de visita en un re orrido en profundidad.

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

7.5. Recorridos sobre grafos

735

Ahora podemos plantear el lema 7.3 del siguiente modo:


Corolario 7.1 Sea T un arbol abar ador de profundidad de un grafo G uyos nodos estan

etiquetados segun el orden de visita. Enton es:

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

1. En este aso la prueba se desprende del lema 7.2 


2. Segun el lema 7.3, si v es un nodo de orte, enton es tiene que existir un hijo w uyos
des endientes no one tan a un an estro de v a traves de un ar o no-abar ador. De
la de ni ion del re orrido en profundidad, se desprende que para ualquier an estro u de v = df (u) < df(v). Por tanto, todos los ar os no-abar adores de algun
des endiente de un hijo w de v tienen que one tar a nodos uyos df sean menores
que df (v), pues sino el lema 7.3 no sera ierto

El orolario anterior nos propor iona una manera omputa ionalmente mas e iente
de determinar si un nodo de v T es o no de orte. Para ahorrarnos la onstru ion del
arbol abar ador, enun iaremos el siguiente on epto:
Definici
on 7.2 (N
umero low ) : Sea G =< V, E > un grafo onexo sobre el ual se
realiza un re orrido en profundidad. Sea v V un nodo. Enton es, low(v) se de ne omo
el mnimo df entre df (v) y los nodos one tados a v por aminos onformados por ar os

abar adores y no-abar adores.


Notese que en un arbol abar ador, los ar os abar adores son dirigidos, mientras que
los no-abar adores no.
Ejemplos se muestran en las guras 7.30 y 7.31; prestese aten ion a veri ar a que los
numeros low de ada nodo (ex epto la raz), se orresponden a la de ni ion.

Una forma alterna y, omputa ionalmente, mas onveniente de de nir low(v) es omo
sigue:
low(v) = mnimo valor entre

df(v)

df (x) | x ualquier nodo one tado a v por un ar o no-abar ador

low (w) | w un hijo de v


(7.1)

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 de ni 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);

7.5. Recorridos sobre grafos

737

}
template <class GT> inline static long & low(typename GT::Node * p)
{
return reinterpret_cast<long&>(NODE_COOKIE(p));
}
De nes:
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

template <typename GT>


struct Init_Low
{
void operator () (GT & g, typename GT::Node * node)
{
g.reset_counter(node); // inicializa df
low <GT> (node) = -1; // inicializa low
}
};
Uses df 736 and low 736.

737

El siguiente paso es de nir 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
hDe ni 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

re ursivamente las ramas de start 738 i

738

Captulo 7. Grafos

hVeri ar

si start es un nodo de orte 739ai

on
hImplanta i
De nes:

del re orrido re ursivo para los puntos de orte 740i

compute cut nodes, never used.

Uses DynDlist 113a.

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

template <typename GT>


void compute_cut_nodes(GT &
g,
DynDlist<typename GT::Node *> & list)
{
compute_cut_nodes(g, g.get_first_node(), list);
}
De nes:
compute cut nodes, never used.
Uses DynDlist 113a and get first node 673 .

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.

7.5. Recorridos sobre grafos

739

typename GT::Arc * arc = i.get_current_arc();


if (IS_ARC_VISITED(arc, Depth_First))
continue;
ARC_BITS(arc).set_bit(Depth_First, true);
__compute_cut_nodes(g, list, tgt, arc, current_df);
++call_counter;
}
Uses

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:
hDe ni i
on del re orrido re ursivo para los puntos de orte 739bi
(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);
Uses compute cut nodes 740 and DynDlist 113a.

uyos parametros se de nen omo sigue:


1. g: el grafo.
2. list: la lista de nodos de orte. compute cut nodes() a~nade ada punto de orte
en ontrado a esta lista.
3. p: el nodo a tualmente siendo visitado.
4. a: el ar o desde el ual se al anza a p. Este es, en el arbol abar ador de profundidad,
el ar o que one ta a su nodo padre.

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;

7.5. Recorridos sobre grafos

741

}
// p fue explorado recursivamente
if (p_is_cut_node)
{
NODE_BITS(p).set_bit(Cut, true);
list.append(p);
}
}
De nes:

compute cut nodes, used in hunks 738 and 739b.


Uses df 736, DynDlist 113a, has current 103, IS ARC VISITED 670d, IS NODE VISITED 670d, low 736,
Node Arc Iterator 656a, NODE BITS 670d, and set bit 664a.

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

Figura 7.32: Un grafo on varios puntos de orte


Puesto que para determinar si p es un nodo de orte es ne esario inspe ionar todos
sus ar os (abar adores y no-abar adores), la deten ion del re orrido debe ha erse uando

742

Captulo 7. Grafos

se hayan mirado todos los ar os y no todos los nodos.


Despues de una llamada re ursiva, se sabe que tgt es un hijo de p. Por esa razon, se
ompara low(tgt) df (p) para dete tar si p es un nodo de orte. El algoritmo propaga
el menor valor de low desde las hojas hasta sus an estros, pues la idea es memorizar si
existe un ar o dentro de una des enden ia que ru e o no ha ia una as enden ia.

....

....

....

....

....

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

Componentes conexos de los puntos de corte

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 de ni 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:

7.5. Recorridos sobre grafos

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".

Pi tori amente, un ar o de ru e se generaliza del siguiente modo:


u

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

grafo. El valor de retorno es la antidad de olores en ontrada; o sea, la antidad de


omponentes onexos o bloques.
paint subgraphs() asume que previamente se eje uto sobre el grafo la rutina compute cut nodes().
Luego de una llamada a paint subgraphs(), el grafo g deviene oloreado de la siguiente forma:
1. Componentes onexos alrededor de un nodo de orte son oloreados on un olor entre 0 y n; donde n es la antidad total de omponentes onexos que tiene el grafo. Por
\ oloreados", insistimos, queremos de ir que todos los nodos y ar os pertene ientes
a un olor i tienen su valor de ontador en i.

744

Captulo 7. Grafos

2. Los nodos de orte tienen olor 0.


3. Los ar os de orte tienen olor 0 y el bit Cut en true.
4. Los ar os de ru e son oloreados on el olor espe ial Cross Arc. Notemos que esta
lase de ar o no es, ni de orte ni pertene e a un omponente onexo.
El olor de un ar o de ru e se de ne omo:
744a
hPuntos de orte 736i+
(696) 738a 744b
const long Cross_Arc = -1;
De nes:
Cross Arc, used in hunks 744b and 746.

Para veri ar si un ar o es de ru e, emplearemos la primitiva siguiente:


hPuntos de orte 736i+
(696) 744a 745a
744b
template <typename GT> inline static
bool is_a_cross_arc(typename GT::Arc * a)
{
return ARC_COUNTER(a) == Cross_Arc;
}
De nes:
is a cross arc, used in hunk 749b.
Uses ARC COUNTER 670d and Cross Arc 744a.

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.

template <typename GT>


void map_subgraph(GT & g, GT & sg, const long & color);

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.

template <typename GT>


void map_cut_graph(GT &
DynDlist<typename GT::Node*> &
GT &
DynDlist<typename GT::Arc*> &

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.

7.5. Recorridos sobre grafos

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

Pintado de componentes conexos

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_arc <GT> (a, current_color);


if (is_node_painted <GT> (p)) // ya est
a el nodo pintado?
return; // S
, ign
orelo
paint_node <GT> (p, current_color);
// recorrer y pintar recursivamente el bloque a trav
es de arcos de p
for (typename GT::Node_Arc_Iterator it(p); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (is_arc_painted <GT> (arc))
continue;
__paint_subgraph <GT> (it.get_tgt_node(), arc, current_color);
}
}
Uses

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

template <typename GT> inline static


void __paint_subgraph(typename GT::Node * p, // Nodo de inicio
const long &
current_color)
{
paint_node <GT> (p, current_color);
for (typename GT::Node_Arc_Iterator it(p); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (is_arc_painted <GT> (arc))
continue;
// recorrer y pintar recursivamente el bloque a trav
es de arcos de p
__paint_subgraph <GT> (it.get_tgt_node(), arc, current_color);
}
}
De nes:

paint subgraph, used in hunks 745a and 746.


Uses has current 103, is arc painted, Node Arc Iterator 656a, and paint node.

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);

7.5. Recorridos sobre grafos

747

// cambiar color (siguiente arco que se visite pertenece a


// otro bloque
current_color++;
}
}
De nes:

paint from cut node, used in hunk 747.


paint subgraph 745b, Cross Arc 744a, has current 103, is a cut node, is node painted,
Node Arc Iterator 656a, paint arc, and set bit 664a.

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;
}
De nes:
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

Copia mapeada de componentes conexos

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
}
}
De nes:

map subgraph, used in hunk 749a.


Uses get color, has current 103, insert arc 682b, insert node 682a, IS ARC VISITED 670d,

7.5. Recorridos sobre grafos

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

modo, se distingue que un nodo o ar o on el olor de interes haya sido o no mapeado.


La rutina de interfaz, map subgraph(), bus a sobre el grafo g el primer nodo on olor
\color" y mapea el omponente onexo al subgrafo sg se espe i a omo sigue:
hPuntos de orte 736i+
(696) 748 749b
template <typename GT>
void map_subgraph(GT & g, GT & sg, const long & color)
{
sg.clear_graph();

// busque el primer nodo con el color


typename GT::Node * first = NULL;
for (typename GT::Node_Iterator it(g); it.has_current(); it.next())
if (get_color <GT> (it.get_current_node()) == color)
first = it.get_current_node();
if (first == NULL) // Encontr
o el color?
throw std::domain_error("Color does not exist in the graph");
// cree first, ins
ertelo en sg y map
eelo
auto_ptr<typename GT::Node> auto_tsrc ( new typename GT::Node(first) );
sg.insert_node(auto_tsrc.get());
GT::map_nodes(first, auto_tsrc.release());
NODE_BITS(first).set_bit(Build_Subtree, true); // m
arquelo visitado
try
{

// mapee recursivamente a partir de first


__map_subgraph(g, sg, first, color);

}
catch (...)
{
sg.clear_graph();
}
}
De nes:

map subgraph, never used.


Uses map subgraph 748, clear graph 685b, exist 61a, get color, has current 103, insert node 682a,
map nodes 686, NODE BITS 670d, Node Iterator 655b, and set bit 664a.

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

DynDlist<typename GT::Node*> & cut_node_list,


GT &
cut_graph,
DynDlist<typename GT::Arc*> & cross_arc_list)
{
cut_graph.clear_graph();
// recorra la lista de nodos de corte e ins
ertelos en sg
for (typename DynDlist<typename GT::Node*>::Iterator it(cut_node_list);
it.has_current(); it.next())
{
typename GT::Node * gp = it.get_current();
I (is_a_cut_node <GT> (gp));
auto_ptr<typename GT::Node> tp_auto ( new typename GT::Node(gp) );
cut_graph.insert_node(tp_auto.get());
GT::map_nodes(gp, tp_auto.release());
}
// Recorra los arcos. Inserte en sg los que no sean de corte, en
// cut_arc_list los que sean de corte y en cross_arc_list los que
// sean de cruce
for (typename GT::Arc_Iterator it(g); it.has_current(); it.next())
{
typename GT::Arc * garc = it.get_current_arc();
if (is_a_cross_arc <GT> (garc))
{
cross_arc_list.append(garc);
continue;
}
if (not is_an_cut_arc <GT> (garc))
continue;
typename GT::Node * src =
static_cast<typename GT::Node*>(NODE_COOKIE(g.get_src_node(garc)));
typename GT::Node * tgt =
static_cast<typename GT::Node*>(NODE_COOKIE(g.get_tgt_node(garc)));
typename GT::Arc * cut_arc =
cut_graph.insert_arc(src, tgt, garc->get_info());
GT::map_arcs(garc, cut_arc);
}
}
De nes:

7.6. Matrices de adyacencia

751

map cut graph, never used.


Uses Arc Iterator 661a, clear graph 685b, DynDlist 113a, get current 103, has current 103,
insert arc 682b, insert node 682a, is a cross arc 744b, is a cut node, is an cut arc,
map arcs 686, map nodes 686, and NODE COOKIE 670d.

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

Hasta el presente, hemos resuelto una antidad importante de problemas on el tipo


List Graph<Node, Arc>, basado en listas de adya en ia.
La representa ion on listas tiene la gran ventaja del dinamismo; podemos
versatilmente a~nadir y suprimir nodos y ar os. Con las matri es de adya en ia esto es
onsiderablemente mas dif il. Sin embargo, existe un amplio orpus de la teora de grafos,
as omo una extensa variedad de ir unstan ias de desempe~no en las uales puede ser
preferible usar la matriz de adya en ia omo representa ion de un grafo.
En esta se ion presentaremos los siguientes TAD vin ulados a las matri es de adya en ia:
1. Map Matrix Graph<GT>: modeliza una matriz de adya en ia \mapeo" de un grafo
de tipo GT basado sobre List Graph<Node, Arc>. Este TAD emplea omo objetos las lases Graph Node<Node Type> y Graph Arc<Arc Type> utilizadas para
List Graph<Node, Arc>.
El a eso a una entrada de la matriz retorna un Graph Arc<Arc Type> y se realiza
espe i ando los nodos de tipo Graph Node<Node Type>. Los valores posible son un
puntero a un Graph Arc<Arc Type> o NULL si no hay ar o.
Map Matrix Graph<GT> no esta destinada a al ulos; simplemente, onstituye el
primer paso para obtener una matriz de adya en ia.
2. Matrix Graph<GT>: modeliza una matriz de adya en ia de un List Graph<Node, Arc>
on los tipos aso iados omo atributos de las lases Graph Node<Node Type> y
Graph Arc<Arc Type>.
3. Ady Mat<GT, Entry>: modeliza una matriz de adya en ia en la ual el usuario
espe i a el tipo de dato que se desea omo entrada de la matriz.

751

4. Bit Mat Graph<GT>: el ual modeliza una matriz de adya en ia de bits.


Las tres lase anteriores se de nen en el ar hivo htpl matgraph.H 751i, el ual se
espe i a de la siguiente forma:
htpl matgraph.H 751i
hUtilitarios para matri es de adya en ia 753ei
hMap Matrix Graph<GT> 752ai
hMatrix Graph<GT> 758bi
hAdy Mat<GT, Entry> 761bi
hBit Mat Graph<GT> 767ai

752

Captulo 7. Grafos

7.6.1

El TAD Map Matrix Graph<GT>

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

hMap Matrix Graph<GT> 752ai


template <typename GT>
class Map_Matrix_Graph
{
hTipos p
ubli os de Map Matrix Graph<GT> (never de ned)i

(751)

private:
hMiembros

privados de Map Matrix Graph<GT> 753di

public:
hMiembros
};

publi os de Map Matrix Graph<GT> 752bi

on
hImplanta i

de Map Matrix Graph<GT> 754ai

Map Matrix Graph<GT> mapea un grafo de tipo List Graph<Node, Arc> a una ma-

752b

triz de adya en ia. Posee dos onstru tores:


hMiembros p
ubli os de Map Matrix Graph<GT> 752bi

(752a) 752

Map_Matrix_Graph(GT & g);

Map_Matrix_Graph(Map_Matrix_Graph & mat);

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;

(752a) 752b 753a

Retorna el apuntador a un nodo orrespondiente a la i-esima entrada de la matriz.


Por ejemplo, si tenemos la siguiente de lara ion:
Map_Matrix_Graph<Grafo> m(g);

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);

7.6. Matrices de adyacencia

753

Asigna al puntero a nodo p el vigesimo-primer nodo del grafo.


2.
hMiembros p
ubli os de Map Matrix Graph<GT> 752bi+
inline long operator () (Node * node) const;

753a

(752a) 752 753b

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;

Retorna el apuntador a ar o entre los nodos on ndi es dentro de la matriz i y j,


respe tivamente.
Los ndi es pueden obtenerse previamente mediante el operador (ptr), donde ptr
es un puntero a nodo.

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);
}
De nes:
get node, used in hunks 754a, 765b, and 768b.
Uses access 60b and DynArray 45.

754

754a

Captulo 7. Grafos

nodes es un arreglo de nodos e i es el ndi e de a eso. Podemos, pues, implantar nuestra


primera version del operador ():
hImplanta i
on de Map Matrix Graph<GT> 754ai
(752a) 754
template <class GT>
typename GT::Node * Map_Matrix_Graph<GT>::operator () (const long & i) const
{
return get_node <GT> (nodes, i);
}
Uses get node 753e.

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);
}
De nes:
index of node, used in hunks 754 , 756b, 757d, 760, 765b, and 768b.
Uses binary search 32 and DynArray 45.

754

nodes es un arreglo ordenado de punteros a nodos, n es la antidad de nodos y p es el


apuntador a bus ar.
index of node() es usada por la siguiente version del operador ():
hImplanta i
on de Map Matrix Graph<GT> 754ai+
(752a) 754a 756b
template <class GT>
long Map_Matrix_Graph<GT>::operator () (typename GT::Node * node) const
{
return index_of_node <GT> (nodes, num_nodes, node);
}
Uses index of node 754b.

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

el ual es a esible mediante:


hMiembros p
ubli os de Map Matrix Graph<GT> 752bi+

(752a) 753 755a

GT & get_list_graph() { return *lgraph; }

754f

La antidad de nodos del grafo; o sea, la dimension de la matriz, la alma enaremos en


una referen ia:
hMiembros privados de Map Matrix Graph<GT> 753di+
(752a) 754d 755b
mutable size_t num_nodes;

7.6. Matrices de adyacencia

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; }

La matriz de ar os tambien la representaremos on un DynArray<T> de dimension


n n; donde n =num nodes. No se vislumbra otra alternativa que no in urra en b
usqueda
lineal de los ar os en lgraph.
Re ordemos que el onsumo de memoria de DynArray<T> es propor ional a la antidad de entradas que se hayan es rito. Esto nos propor iona una gran ventaja en e onoma
de espa io. Habran, sin embargo, algunas entradas orrespondientes a NULL que se en ontraran en el arreglo dinami o. Por esa razon, no debemos de larar dire tamente algo
omo:
DynArray<Arc*> arcos(n*n);

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;

Mat_Entry(Arc * __arc = NULL) : arc(__arc) { /* empty */ }


};

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

Puesto que la matriz se implanta on un arreglo uni-dimensional, planteamos la siguiente primitiva:


hUtilitarios para matri es de adya en ia 753ei+
(751) 754b 756a
inline static long index_array(const long & i, const long & j, const long & n)
{
if (i >= n or j >= n)
throw std::out_of_range("Matrix index out of range");

return i + j*n;
}
De nes:
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

El a eso a una entrada de una matriz de adya en ia se realiza generi amente de la


siguiente forma:
hUtilitarios para matri es de adya en ia 753ei+
(751) 755d
template <typename Entry>
inline static Entry * read_matrix(const DynArray<Entry> & mat,
const long &
i,
const long &
j,
const long &
n)
{
const long index = index_array(i, j, n);
if (not mat.exist(index))
return NULL;
return &const_cast<Entry&>(mat.access(index));
}
De nes:
read matrix, used in hunk 756b.
Uses access 60b, DynArray 45, exist 61a, and index array 755d.

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.

7.6. Matrices de adyacencia

757

Una vez de nidos los a esos, nos resta por de nir 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

void copy_list_graph(GT & g);


Uses copy list graph 758a.

la ual opia grafo g representado on un List Graph<Node, Arc> a this, representado


on Map Matrix Graph<GT>. Esta rutina nos permite muy on isamente implantar los
onstru tores y la asigna ion; por ejemplo:
hImplanta i
on de Map Matrix Graph<GT> 754ai+
(752a) 756b 758a

757b

template <class GT>


Map_Matrix_Graph<GT>::Map_Matrix_Graph(GT & g)
: lgraph(&g), num_nodes(g.get_num_nodes())
{
copy_list_graph(g);
}
Uses copy list graph 758a.

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();

quicksort(nodes); // ordenar nodes de modo que se soporte la b


usqueda binaria
Uses has current 103, Node Iterator 655b, and quicksort 221 222 .

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();

nodes.cut(); // limpiar arreglos din


amicos de contenidos anteriores
mat.cut();
h opiar

nodos 757 i

h opiar
}
De nes:

ar os 757di

copy list graph, used in hunks 757, 767 , and 768a.

Uses cut 64.

7.6.2

El TAD Matrix Graph<GT>

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 de ne por la siguiente lase:
758b

hMatrix Graph<GT> 758bi


template <typename GT>
class Matrix_Graph
{
public:
typedef GT List_Graph_Type;
typedef typename GT::Node_Type Node_Type;

(751)

7.6. Matrices de adyacencia

759

typedef typename GT::Arc_Type Arc_Type;


typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
private:
hMiembros

privados de Matrix Graph<GT> 759ai

public:
hMiembros p
ubli os de Matrix Graph<GT> 759 i
};
De nes:
Matrix Graph, used in hunk 760.

759a

La diferen ia fundamental on Map Matrix Graph<GT> es que Matrix Graph<GT> es


una matriz independiente del List Graph<Node, Arc> original, sobre la ual se pueden
realizar modi a iones tanto a los valores de los nodos omo a los de los ar os. No es posible
obtener el List Graph<Node, Arc> a partir de un objeto de tipo Matrix Graph<GT>.
Matrix Graph<GT> debe usarse por aquellas apli a iones que solo deseen trabajar on
matri es de adya en ia.
Los atributos de Matrix Graph<GT> son muy similares a los de Map Matrix Graph<GT>:
hMiembros privados de Matrix Graph<GT> 759ai
(758b) 759b
DynArray<Node_Type> nodes;
DynArray<Arc_Type> arcs;
mutable size_t n;
Uses arcs 813 and DynArray 45.

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

mutable Arc_Type Null_Value;


De nes:
Null Value, used in hunks 759{63.

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

Si el a eso (i, j) a un Matrix Graph<GT> retorna Null value, enton es no existe


ar o entre el nodo i y j. Tanto la onstru ion omo la asigna ion se apoyan en una rutina
omun llamada copy(), la ual se sobre arga en dos versiones:
hMiembros privados de Matrix Graph<GT> 759ai+
(758b) 759b
void copy(Matrix_Graph & mat)
{
if (this == &mat)
return;

n
= mat.n;
// copiar atributos
Null_Value = mat.Null_value;
nodes.cut();
arcs.cut();

// limpiar memoria de arreglos din


amicos

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[]
{

7.6. Matrices de adyacencia

761

typename GT::Node * src = ptr[i];


nodes[i] = src->get_info();
for (typename GT::Node_Arc_Iterator it(src);
it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
typename GT::Node * tgt = it.get_tgt_node();
const long j = index_of_node<GT>(ptr, n, tgt);
const long mat_index = index_array(i, j, n);
arcs[mat_index] = arc->get_info();
}
}
}
Uses access 60b, arcs 813 , cut 64, DynArray 45, exist 61a, has current 103, index array 755d,
index of node 754b, Matrix Graph 758b, Node Arc Iterator 656a, Node Iterator 655b,
Null Value 759b, quicksort 221 222 , set default initial value 54a, and touch 62.

761a

Los a esos a los elementos de la matriz se realizan mediante el operador () de manera


similar al tipo estudiado en x 7.6.1 (pagina 752). En el a eso a una entrada matri ial
(i, j) se realiza la distin ion entre una le tura y es ritura. Ilustremos la manera en que
se maneja la memoria on el arreglo dinami o y los valores nulos mediante el siguiente
operador de a eso a una matriz onstante:
hMiembros p
ubli os de Matrix Graph<GT> 759 i+
(758b) 759
const Arc_Type & operator () (const long & i, const long & j) const
{
const long mat_index = index_array(i, j, n);
if (not arcs.exist(mat_index)) // Existe entrada?
return Null_Value; // No ==> no hay arco
return arcs.access(mat_index);
}
Uses access 60b, arcs 813 , exist 61a, index array 755d, and Null Value 759b.

7.6.3

El TAD Ady Mat<GT, Entry>

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

hAdy Mat<GT, Entry> 761bi


template <typename GT, typename __Entry_Type>
class Ady_Mat

(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

privados de Ady Mat<GT,

Entry> 762i

publi os de Ady Mat<GT,

Entry> 763ai

public:
hMiembros
};

on de metodos de Ady Mat<GT, Entry> 765bi


hImplanta i
De nes:
Ady Mat, used in hunks 763, 765b, and 803{6.
Ady Mat<GT, Entry> debe estar estri tamente ligado a un objeto List Graph<Node, Arc>.
El punto esen ial en la lase Ady Mat<GT, Entry> esta dado por el he ho de que el usuario

puede de idir el tipo de datos que albergaran las entradas de la matriz. Conse uentemente,

Ady Mat<GT, Entry> no ne esariamente representa una matriz de adya en ia.


Los atributos de Ady Mat<GT, Entry> son muy similares a los de las matri es ante762

riores:
hMiembros privados de
GT * lgraph;

Ady Mat<GT, Entry> 762i

(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

7.6. Matrices de adyacencia

763a

763

uando se invoque a mat.exist(index). Es por este medio que se dete ta si la entrada es


nula.
El resto de los atributos son observables:
hMiembros p
ubli os de Ady Mat<GT, Entry> 763ai
(761b) 763b
GT & get_list_graph() { return *lgraph; }

const Entry_Type & null_value() const { return Null_Value; }


const size_t & get_num_nodes() const { return num_nodes; }
Uses Null Value 759b.

763b

El uso de Null Value es el mismo que el de la lase Matrix Graph<GT>: de nir 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 de nido o, de nir 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 de nir 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

void set_null_value(const Entry_Type & null)


{
Null_Value = null;
}
Uses Null Value 759b.

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.

La ual invo a a Operation()(mat, arc, i, j, entry, ptr).


Como ejemplo, onsideremos ini ializar una matriz on los valores de distan ias guardados
en ada ar o. Suponiendo que la distan ia de un ar o es a esible mediante un metodo del
ar o llamado get distance(), la ini ializa ion de un ar o en la matriz puede espe i arse
de este modo:
struct Init_Arc
{
void operator () (AdyMat<Grafo) & mat,
Grafo::Arc *
arc,
int &
i,
int &
j,
Entry &
entry)
{
entry = arc->get_distance();
}
};

7.6. Matrices de adyacencia

765

De este modo, pueden ini iarse todas las entradas mediante:


mat.operate_all_arcs_list_graph <Init_Arc> ();

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();

template <class Operation> void operate_all_arcs_matrix(void * ptr);


Uses operate all arcs matrix 765b.

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

const long tgt_idx = // obtener


ndice del nodo destino
index_of_node<List_Graph_Type>(nodes, num_nodes,
arc->get_tgt_node());
Entry_Type & entry = // asegurar acceso a mat(src_idx, tgt_idx)
mat.touch(index_array(src_idx, tgt_idx, num_nodes));
Operation () (*this, arc, src_idx, tgt_idx, entry);
}
}
}
template <class GT, typename __Entry_Type>
template <class Operation>
void Ady_Mat<GT, __Entry_Type>::operate_all_arcs_matrix()
{
const long & n = num_nodes;
for (int s = 0; s < n; ++s)
{
Node * src_node = get_node<GT>(nodes, s);
for (int t = 0; t < n; ++t)
{
Node * tgt_node = get_node<GT>(nodes, t);
Entry_Type & entry = mat.touch(index_array(s, t, num_nodes));
Operation () (*this, src_node, tgt_node, s, t, entry);
}
}
}

De nes:

operate all arcs list graph, used in hunk 764.


operate all arcs matrix, used in hunks 765a and 806.
Uses Ady Mat 761b, get node 753e, has current 103, index array 755d, index of node 754b,
Node Arc Iterator 656a, Node Iterator 655b, and touch 62.

7.6.4

766

El TAD Bit Mat Graph<GT>

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 de ne de la siguiente manera:
hM
etodos privados de Bit Mat Graph<GT> 766i
(767a) 767b
BitArray bit_array;
Uses BitArray 36.

7.6. Matrices de adyacencia

767

Mientras que el Bit Mat Graph<GT> se espe i a mediante la siguiente lase:


767a

hBit Mat Graph<GT> 767ai


template <class GT>
class Bit_Mat_Graph
{
public:

(751)

typedef GT List_Graph_Type;
typedef typename GT::Node Node;
typedef typename GT::Arc Arc;
private:
hM
etodos

privados de Bit Mat Graph<GT> 766i

public:
hM
etodos
};
De nes:

publi os de Bit Mat Graph<GT> 767 i

Bit Mat Graph, used in hunks 767 and 770.

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

Hay uatro maneras de onstruir un Bit Mat Graph<GT>:


hM
etodos publi os de Bit Mat Graph<GT> 767 i
(767a) 768a
Bit_Mat_Graph() : lgraph(NULL) { /* empty */ }
Bit_Mat_Graph(GT & g) : lgraph(&g)
{
copy_list_graph(g);
}
Bit_Mat_Graph(const Bit_Mat_Graph & bitmat)
: bit_array(bitmat.bit_array), lgraph(bitmat.lgraph),
nodes(bitmat.nodes), n(bitmat.n)
{
// empty
}

768

Captulo 7. Grafos

Bit_Mat_Graph(const size_t & dim)


: bit_array(dim*dim), lgraph(NULL),
nodes(dim), n(dim)
{
/* empty */
}
Uses Bit Mat Graph 767a and copy list graph 758a.

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; }
De nes:
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

7.6. Matrices de adyacencia

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

Figura 7.34: Un grafo dirigido


Como enun iamos al prin ipio, un grafo es una manera de representar una rela ion
matemati a. En este sentido, una rela ion binaria E sobre un onjunto V (lo que onforma
el grafo < V, E >) es transitiva si x, y, z V, (x, y), (y, z) E. Un grafo transitivo es
todo aquel que representa a una rela ion transitiva.
Definici
on 7.5 (Clausura transitiva) La lausura transitiva de una rela ion R es la
rela ion R de nida por todo par (x, y) R tal que existe un amino entre x e y.
La lausura transitiva R de una rela ion R es la mnima rela ion que ontiene a R.

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

Figura 7.35: A~nadidura de ar o en Rk


La opera ion Rk1 Rk se realiza segun (7.2).
1
2
3
4
5
6
R0 = 7
8
9
10
11
12
13
14

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

Figura 7.36: Matriz de adya en ia de bits del grafo de la gura 7.34

770a

El al ulo de la lausura transitiva de un grafo representado mediante un objeto de


tipo List Graph<Node, Arc> se instrumenta en el ar hivo hwarshall.H 770ai espe i ado
de la siguiente forma:
hwarshall.H 770ai

template <class GT>


void warshall_compute_transitive_clausure(GT & g, Bit_Mat_Graph<GT> & mat)
{
hCal ular lausura transitiva 770bi
}
De nes:
compute transitive clausure, never used.
Uses Bit Mat Graph 767a.

g es un List Graph<Node, Arc> sobre el ual se desea al ular la lausura transitiva;


mientras que mat es el resultado de la lausura en uestion segun el algoritmo de Warshall.

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.

7.7. Arboles abarcadores mnimos

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

Nos resta implantar el resto del algoritmo segun (7.3):


hCal ular lausura transitiva 770bi+

(770a) 771a

const size_t & n = mat.get_num_nodes();

for (int k = 0; k < n; k++)


{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
mat(i, j) = mat_prev(i, j) or (mat_prev(i, k) and mat_prev(k, j));
mat_prev = mat;
}

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

Arboles abarcadores mnimos

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)

Figura 7.38: Un grafo y su arbol abar ador mnimo


de onstru ion o del oste de nan iamiento. En ambas situa iones, un arbol abar ador mnimo nos propor iona la red mnima, segun los terminos del modelo, que garantiza
ompleta one tividad.
Consideremos un grafo G =< V, E > y un arbol abar ador T =< V, E > | E E.
Puntuali emos algunas observa iones sobre el ara ter de arbol y minimalidad de T :
1. Si G ontiene i los, enton es, on ertitud, |E | < |E|.
2. Si T es mnimo, enton es:
X

peso(e) es mnima

(7.4)

eE

Si no existe ningun valor repetido de peso en G, enton es no existe ningun otro


onjunto E uya suma sea menor.
De lo ontrario, pueden existir uno o mas onjuntos de ar os E uya suma sea igual
a la de E , pero estos tambien son mnimos. Di ho de otro modo, un grafo G puede
tener varios arboles abar adores mnimos.
Un algoritmo que en uentre un arbol abar ador mnimo debe distinguir los ar os que
pertene en a E de los que no pertene en. Cuando onsideramos un ar o de G, > omo
sabemos si este pertene e o no al arbol abar ador? Para el abordaje de esta uestion
requeriremos la siguiente de ni ion:
Definici
on 7.6 (Corte de un grafo) Sea un grafo G. Un orte sobre G es una parti ion
de G en dos subgrafos disjuntos G1 y G2.
Los ar os eliminados de G, es de ir, aquellos que one tan nodos de G1 on nodos de
G2, se denominan \ar os de orte".

La situa ion en uestion se puede pi torizar as:

7.7. Arboles abarcadores mnimos

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

Sea a el ar o de ru e entre T1 y T2, el ual, omo ya lo hemos di ho, no es el mnimo de


los de ru e entre G1 y G2. Puesto que T1 y T2 son a  li os, podemos suprimir el ar o
de ru e a y substituirlo por el de ru e mnimo amin y as rear un arbol abar ador
T =< V, E >=< V, E {a} {a } >. Puesto que peso(amin) < peso(a) =
P
P
eE peso(e) <
eE peso(e). Lo que ontradi e la premisa ini ial de que T es el
arbol abar ador mnimo de G. La proposi ion es pues ierta

min

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.

1. Sea T =< V , E >=< , >.


2. Repita mientras T no abarque a G
(a) (*) Sele ione \ on riterio" un ar o e E y reali e E = E {e} (este es el paso
que vara segun el algoritmo).
(b) E = E e.
( ) Si T es  li o =
i. (**) Es oger el ar o que sea parte del i lo que tenga mayor peso.
ii. E = E {e}
Esta estru tura es esen ialmente la misma para los dos algoritmos que estudiaremos, mentados de Kruskal y de Prim, respe tivamente. La diferen ia entre ellos reside primordialmente en el paso 2a; es de ir, en la manera en que se es ogen los ar os para insertarlos en
T . Una diferen ia se undaria es omo se dete ta si T ya abar a a G.
Cuando la in lusion de un nuevo ar o en el arbol T ausa un i lo debe eliminarse el
ar o parte del i lo que tenga mayor peso. La proposi ion siguiente nos eviden ia que el
arbol abar ador resultante es mnimo.
Proposici
on 7.2 (Propiedad del ciclo - Sedgewick-2001 [9]) Sea G un grafo onexo
ualquiera y T su arbol abar ador mnimo. Sea G un grafo onstruido por a~nadidura de
un ar o e a G. Enton es, a~nadir e a T y eliminar el ar o de mayor peso en el i lo resulta
en un arbol abar ador T que es mnimo.
Demostraci
on
Si e es el ar o on mayor peso del i lo, enton es este no puede ser parte de T porque
sino T no sera mnimo (existira otro ar o de menor peso).
De lo ontrario, sea emax el ar o parte del i lo on mayor peso. Eliminar emax de T

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

Acceso a los pesos del grafo

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.

7.7. Arboles abarcadores mnimos

(b)

775

typename Distance<GT>::Distance Type operator () (typename GT::Arc *a): re-

torna el valor de peso ontenido en el ar o a.


( ) typename Distance<GT>::Max Distance: onstante estati a orrespondiente
al valor de distan ia maximo que un algoritmo onsiderara omo valor in nito.
(d) typename Distance<GT>::Zero Distance: onstante estati a orrespondiente
al elemento neutro de la suma. Tradi ionalmente, en la inmensa mayora de
asos, este sera el ero.
2. Compare<GT>: lase que realiza la ompara ion entre dos pesos y que debe exportar:

bool operator () (const typename Distance<GT>::Distance_Type & op1,


const typename Distance<GT>::Distance_Type & op2) const;

para implantar la rela ion \menor que".


3. Plus: lase de suma entre distan ias implantada mediante el operador:
typename Distance<GT>::Distance_Type
Plus::operator () (const typename Distance<GT>::Distance_Type & op1,
const typename Distance<GT>::Distance_Type & op2) const;

775a

Las lases Distance y Compare se onjugan bajo una lase espe ial de ompara ion de
ar os uya de ni 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) );
}
};
De nes:
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;

// recorrer todos los arcos y sumar su peso


for (typename GT::Arc_Iterator it(g); it.has_current(); it.next())
sum = Plus () (sum, Distance () (it.get_current_arc()));
return sum;
}

776

Captulo 7. Grafos

template <class GT, class Distance>


typename Distance::Distance_Type total_cost(GT & g)
{
typedef Aleph::less<typename Distance::Distance_Type> Less;
typedef Aleph::plus<typename Distance::Distance_Type> Plus;
return total_cost <GT, Distance, Less, Plus> (g);
}
De nes:
total cost, never used.
Uses Arc Iterator 661a and has current 103.

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

Es de ir, se invo a el metodo de ordenamiento de ar os planteado en x 7.3.8.11 (pagina 689).


Pro esar los ar os ordenadamente es la estrategia que en el aso del algoritmo de
Kruskal umple el rol del paso 2a del algoritmo generi o 7.4.
El pro eso para obtener el arbol abar ador mnimo, luego de tener los ar os ordenados
se delinea, a grandes rasgos, de la siguiente manera:
h al ular 
arbol abar ador mnimo segun Kruskal 776bi
(778 )
/* Recorrer arcos ordenados de g hasta que numero de arcos de tree
sea igual a numero de nodos de g */
for (typename GT::Arc_Iterator arc_itor(g);
tree.get_num_arcs() < g.get_num_nodes() - 1;
arc_itor.next())
{
// obtenga siguiente menor arco
typename GT::Arc * arc = arc_itor.get_current_arc();
hVeri ar

si nodo origen existe en tree 778ai

hVeri ar

si nodo destino existe en tree 778bi

// insertar arco en tree


typename GT::Arc * arc_in_tree =
tree.insert_arc(tree_src_node, tree_tgt_node, arc->get_info());
if (has_cycle(tree)) // verifique si nuevo arco causa un ciclo
{
// hay ciclo ==> hay que remover el arco
tree.remove_arc(arc_in_tree); // eliminar arco e ir a procesar
continue;
// siguiente arco
}

7.7. Arboles abarcadores mnimos

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

ual omentaremos el primero:


hVeri ar si nodo origen existe en tree 778ai

(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

if (not IS_NODE_VISITED(g_src_node, Aleph::Kruskal)) // est


a en tree?
{
// No, crearlo en tree, atarlo al cookie y marcar como visitado
NODE_BITS(g_src_node).set_bit(Aleph::Kruskal, true);
tree_src_node = tree.insert_node(g_src_node->get_info());
GT::map_nodes(g_src_node, tree_src_node);
}
else
tree_src_node = static_cast<typename GT::Node*>(NODE_COOKIE(g_src_node));
Uses insert node 682a, IS NODE VISITED 670d, map nodes 686, NODE BITS 670d, NODE COOKIE 670d,
and set bit 664a.

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

arbol abar ador mnimo segun Kruskal 776bi

7.7. Arboles abarcadores mnimos

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);
}
De nes:
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 on gura 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

Correctitud del algoritmo de Kruskal

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

en uentra un arbol abar ador mnimo.

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

Interfaces del algoritmo de Prim

El algoritmo se invo a mediante las siguientes interfa es:


hAlgoritmo de Prim 780i

(781a)

template <class GT, class Distance, class Compare> inline


void prim_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 conected");
tree.clear_graph();
hIni ializar

Prim 786ai

hDe lara i
on
hIni ializar
h al ular
}

de ola de prioridad 786ei

heap 787ai

arbol abar ador mnimo segun Prim 787bi

hDesini ializar

Prim 786 i

7.7. Arboles abarcadores mnimos

781

template <class GT, class Distance> inline


void prim_min_spanning_tree(GT & g, GT & tree)
{
prim_min_spanning_tree
<GT, Distance, Aleph::less<typename Distance::Distance_Type> > (g, tree);
}
De nes:
prim min spanning tree, never used.
Uses clear graph 685b, digraph 679, is digraph, and test connectivity 701.

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
hDe ni iones de Prim 785ai
hAlgoritmo

de Prim 780i

hInde ni iones

7.7.3.2

de Prim 786di

Heap exclusivo de arcos mnimos

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 de ni 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 de ni 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

Un heap tal omo el que a abamos de de nir es \naturalmente" implantable


mediante el TAD BinHeap<Key> estudiado en x 4.7 (pagina 347). Esta lase de
heap, que llamaremos ArcHeap<GT, Distance, Compare, Access Arc>, se de ne en el
ar hivo har heap.H 781bi:
har heap.H 781bi
# ifndef ARCHEAP_H
# define ARCHEAP_H

# 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
De nes:
ArcHeap, used in hunks 784{86, 794b, and 796e.
Uses Distance Compare 775a.

782

El ar hivo de ne 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

ha sido visitado y no pertene e al arbol abar ador.

1. Busque en el heap un ar o a tal que su nodo destino sea t.


2. Si a ha sido en ontrado =
(a) Si a < a = (segun lo que arroje la lase Distan e)
i. swap(a, a ) (inter ambie a on a )
(b) Elimine a del heap
3. Inserte a en el heap
Como se ve, debemos en ontrar una manera rapida de re uperar a de manera tal que
no omprometa el desempe~no del heap. Una primera alternativa es usar una tabla de
nodos destinos de ar os ontenidos en el heap, la ual pudiera instrumentarse mediante
una tabla hash o un arbol abar ador. Pero esto a arrea in ertidumbre, ostes adi ionales
o ultos que pueden deparar en O(lg(n)) y perdida de generalidad.

7.7. Arboles abarcadores mnimos

783

En virtud de lo anterior, aunque en detrimento de la laridad, anotaremos en ada


nodo del grafo, a traves del ookie, un puntero ha ia el nodo del heap dentro del ArcHeap.
Esto requiere forjar una onven ion de uso del ookie la ual puede realizarse de tres
maneras: (1) asun ion de un nombre uni o, (2) un puntero a una fun ion de a eso y
(3) una lase de a eso. Cada uno de los enfoques tiene sus ventajas y desventajas uya
dis usion dejamos a la re exion del le tor. En este dise~no transamos por la lase de a eso,
la ual la denominamos Access Heap Node y uyo operador () debe orresponderse on
el siguiente prototipo:
template <class GT, class Distance, class Compare> typename
BinHeap<typename GT::Arc*, Distance_Compare<GT, Distance, Compare> >::Node *&
Access_Heap_Node::operator () (typename GT::Node * p);

783

El operador retorna un puntero al nodo del heap que ontiene un ar o in luido


en el ArcHeap uyo nodo destino sea p, o NULL, si el heap no ontiene ningun
ar o on p omo nodo destino. Es responsabilidad del usuario de ArcHeap el que
Access Heap Node::operator () retorne NULL la primera vez en que se inserte un ar o
on un nodo destino. Es muy importante que se retorne una referen ia a un puntero y no
una opia; de modo tal que se pueda ambiar el valor del apuntador.
Dilu idada la te ni a, podemos implantar la inser ion segun el algoritmo 7.5 del siguiente modo:
hAtributos p
ubli os de Ar Heap 782i+
(781b) 782 784a
void put_arc(typename GT::Arc * arc, typename GT::Node * tgt)
{
Node *& heap_node = Access_Heap_Node () (tgt);

if (heap_node == NULL) // existe arco insertado en el heap?


{
// No ==> crear nuevo nodo para el heap y asignarle arco
heap_node = new Node;
heap_node->get_key() = arc;
this->insert(heap_node);
return;
}
//
//
//
//

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()

typename GT::Arc * arc_in_heap = heap_node->get_key();


// arco en el heap tiene distancia menor que el que se est
a mirando?
if (Distance_Compare <GT, Distance, Compare> () (arc_in_heap, arc))
return; // S
==> el antiguo arco permanece en el heap y el nuevo se ignora
// En este caso hay que sustituir el arco reci
en insertado y
// actualizar el heap con su distancia

784

Captulo 7. Grafos

heap_node->get_key() = arc; // cambia el arco


update(heap_node);
// actualiza el heap con el nuevo peso
}
De nes:

put arc, used in hunks 787, 797 , and 799a.


Uses Distance Compare 775a and S 794 .

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;
}
De nes:
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 on gurar 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, de nir la estru tura que

7.7. Arboles abarcadores mnimos

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:
hDe ni 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

Prim_Info() : tree_node(NULL), heap_node(NULL) { /* empty */ }


};
De nes:
Prim Info, used in hunks 785 and 786b.

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:
hDe ni iones de Prim 785ai+
(781a) 785a 785

# define PRIMINFO(p) static_cast<Prim_Info<GT>*>(NODE_COOKIE(p))


# define TREENODE(p) (PRIMINFO(p)->tree_node)
# define HEAPNODE(p) (PRIMINFO(p)->heap_node)
De nes:
HEAPNODE, used in hunks 785 , 786d, 794, and 795a.
Uses NODE COOKIE 670d and Prim Info 785a.

785

Ahora, segun los requerimientos del tipo ArcHeap<GT, Distance, Compare, Access Arc>,
debemos espe i ar el a eso al ampo heap node:
hDe ni 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;
}
};
De nes:
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:
hDe ni iones de Prim 785ai+
(781a) 785 786b
template <class GT>
struct Init_Prim_Info
{

786

Captulo 7. Grafos

void operator () (GT & g, typename GT::Node * p)


{
g.reset_bit(p, Aleph::Prim);
NODE_COOKIE(p) = new Prim_Info<GT>;
}
};
De nes:
Init Prim Info, used in hunk 786a.
Uses NODE COOKIE 670d and Prim Info 785a.

786a

786b

hIni ializar Prim 786ai


g.template operate_on_nodes <Init_Prim_Info <GT> > ();
Uses Init Prim Info 785d and operate on nodes 661 .

(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:
hDe ni 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;
}
};
De nes:
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

hInde ni iones de Prim 786di


# undef HEAPNODE
# undef TREENODE
# undef PRIMINFO
Uses HEAPNODE 785b.

(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 .

7.7. Arboles abarcadores mnimos

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

// guardar imagen de nodo en el


arbol abarcador
TREENODE(first) = tree.insert_node(first->get_info());
// meter en heap arcos iniciales del primer nodo
for (typename GT::Node_Arc_Iterator it(first); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
ARC_BITS(arc).set_bit(Aleph::Prim, true);
heap.put_arc(arc, it.get_tgt_node());
}
Uses get first node 673 , has current 103, insert node 682a, Node Arc Iterator 656a, NODE BITS 670d,
put arc 783, and set bit 664a.

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();

typename GT::Node * src = g.get_src_node(min_arc);


typename GT::Node * tgt = g.get_tgt_node(min_arc);
if (IS_NODE_VISITED(src, Aleph::Prim) and
IS_NODE_VISITED(tgt, Aleph::Prim))
continue; // Este arco cerrar
a un ciclo en el
arbol
typename GT::Node * tgt_node = // seleccione nodo no marcado
IS_NODE_VISITED(src, Aleph::Prim) ? tgt : src;
// inserte en
arbol, guarde node de
arbol y marque nodo como visitado
TREENODE(tgt_node) = tree.insert_node(tgt_node->get_info());
NODE_BITS(tgt_node).set_bit(Aleph::Prim, true);
// insertar arcos de tgt_node en el heap
for (typename GT::Node_Arc_Iterator it(tgt_node);

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).

7.7. Arboles abarcadores mnimos

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 de ni 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 de nitivo 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

Correctitud del algoritmo de Prim

Proposici
on 7.4 Sea G un grafo onexo ualquiera. Enton es el algoritmo de Prim en-

uentra un arbol abar ador mnimo.

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.

7.8. Caminos mnimos

791

La proposi ion es pues ierta para todos los nodos de grafo

7.8

Caminos mnimos

En grafos ponderados, el problema del amino mnimo onsiste en en ontrar un amino


entre un par de nodos tal que la suma total de todos los pesos sea mnima. Como ejemplos
podemos onsiderar:
1. Dadas iudades y distan ias entre arreteras, en ontrar el amino de menor distan ia
entre un par de iudades.
2. Dadas iudades y dura iones en horas de vuelo en avion y en es alas, segun ofertas y
disponibilidades de aerolneas, onseguir el itinerario que lleve de una iudad a otra
en el menor tiempo posible.
Si ambiamos dura iones por ostes e onomi os, enton es podemos plantear el en ontrar el itinerario e onomi amente mas barato.
3. Dada una red de omputadoras, se desea en ontrar el amino mas orto de nodos
intermedios por donde puedan ir ular los paquetes en el menor tiempo posible.
Analogamente, si se trata de una red telefoni a, enton es, a efe tos de prestar buen
servi io y, a la vez, de ahorrar ostes, se desea en ontrar el amino mas orto entre onmutadores intermediarios de tal manera que se mejoren parametros omo
laten ia de transmision (fundamental para la onversa ion) o antidad de llamadas
simultaneas que pueden efe tuarse en la red.
4. Un robot, en fun ion de sus me anismos de vision, debe plani ar un traye to desde
un punto a otro. El terreno y sus obsta ulos se modeliza mediante un grafo. En este
aso, para ahorrar tiempo y energa, se al ula el amino de mnima distan ia entre
los puntos.
Hay mu has mas situa iones en las que puede apare er este problema. Para que este
tenga sentido, el grafo debe ser onexo o que al menos exista un amino entre un par de
nodos; uestion ultima que podemos determinar mediante la rutina test path() estudiada
en x 7.5.6.1 (pagina 709).
Los tres algoritmos de aminos mnimos que estudiaremos en esta se ion operan para
digrafos. Solo el algoritmo de Dijkstra opera para grafos.
Una manera andida de abordar este problema es modi ar los algoritmos de busqueda
de aminos que hemos estudiado para que exploren todos lo aminos posibles entre el
par de nodos. La idea es que se ontabili en y se guarden todos lo aminos posibles entre
el par en uestion y se es oja el menor entre ellos. El problema de este enfoque es que
omputa ionalmente es intratable, pues la antidad de aminos posibles entre ualquier
par de nodos es ombinatoria.
Normalmente por razones de abstra ion, es plausible tener pesos negativos en un grafo.
En este sentido, en el estudio y al ulo de aminos mnimos hay un absurdo matemati o
16

16 Rutinas

find path depth first() en x 7.5.6.2 (p


agina 711) y find path breadth first()

en x 7.5.7 (pagina 714).

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 in nitamente
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
hDe ni i
hInforma i
on

por nodo en Dijkstra 794ai

hInforma i
on

por ar o en Dijkstra 796 i

hClase

apartadora de informa ion para nodo 795bi

hClase

liberadora de informa ion para nodo 795 i

hClase

apartadora de informa ion para ar o (never de ned)i

hClase

liberadora de informa ion para ar o (never de ned)i

hCompara i
on
hPrototipo

entre poten iales 796di

del algoritmo de Dijkstra 793i

hInde ni i
on

de ma ros para algoritmo de Dijkstra 795ai


Hay una gran reminis en ia entre el algoritmo de Dijkstra y el de Prim expresada por
el he ho de que ambos se basan en una explora ion de grafo segun las prioridades. La
diferen ia esen ial subya e en que el algoritmo de Dijkstra usa valores par iales tanto en
los nodos omo en los ar os.
Dado un nodo ini ial v0, ual representa el nodo origen de un amino mnimo, los
valores de los que hablamos se re ere el algoritmo de Dijkstra se lasi an de la siguiente
manera:
Para los nodos: Dado un nodo ualquiera vu, A (vi) representa la mnima distan ia
a umulada desde v0 hasta vi.

Ini ialmente, A (v0) = 0.


Para los arcos: Dado un ar o ualquiera e entre los adya entes a un nodo vi, Pot(e)
designa la \distan ia poten ial" a umulada a ubrir desde el nodo v0 hasta el nodo
destino de e.

7.8. Caminos mnimos

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, de nido 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 de ne 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
}
De nes:
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 de na el peso. Compare es una lase de ompara ion entre
tipos typename Distance::Distance Type. Por ultimo, Plus es una lase que realiza la

suma algebrai a de distan ias.

7.8.1.1

Distancia acumulada en los nodos

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 de nida 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
}
};
De nes:
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;
}
};
De nes:
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, de nimos los siguientes ma ros:
hDe ni 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))))

// Acceso al nodo del


arbol en el grafo
# define TREENODE(p) (DNI(p)->tree_node)

7.8. Caminos mnimos

795

# define ACC(p) (DNI(p)->dist) // Acceso a la distancia acumulada


# define HEAPNODE(p) (DNI(p)->heap_node)
De nes:
DNI, used in hunk 795.
S, used in hunks 366, 417, 433, 578b, 698b, 745a, and 783.
Uses Dijkstra Node Info 794a, HEAPNODE 785b, and NODE COOKIE 670d.

795a

795b

hInde ni 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, de nimos 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);

// apartar memoria informaci


on nodo (acumulado e imagen en
arbol)
NODE_COOKIE(p) = new Dijkstra_Node_Info <GT, Distance>;
}
};
De nes:

Initialize Node Dijkstra, never used.


Uses Dijkstra Node Info 794a and NODE COOKIE 670d.

795

Analogamente, la lase para des-ini ializar se de ne as:


hClase liberadora de informa i
on para nodo 795 i

(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));

delete aux; // liberar bloque


}
};
De nes:
Destroy Node Dijkstra, never used.
Uses Dijkstra Node Info 794a, DNI 794 , and map nodes 686.

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.
De nidas 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

hLiberar memoria para los nodos 796bi


(797b 799b)
g.template operate_on_nodes <Destroy_Dijkstra_Node <GT, Distance> > ();
Uses operate on nodes 661 .

En ambos bloques g es el grafo sobre el ual se desea al ular los aminos mas ortos.
7.8.1.2
796

Potencial en los arcos

La informa ion para un ar o es pare ida a la del nodo y se de ne de manera similar:


hInforma i
on por ar o en Dijkstra 796 i
(792)

template <class GT, class Distance>


struct Dijkstra_Arc_Info
{
typename GT::Arc *
tree_arc; // imagen en el
arbol abarcador
typename Distance::Distance_Type pot;
// potencial del arco
};
De nes:
Dijkstra Arc Info, never used.

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 de nir 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));
}
};
De nes:
Compare Arc Data, never used.
Uses POT.

796e

Esto nos permite de larar el heap;


hDe lara i
on de heap en Dijkstra 796ei

(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. Caminos mnimos

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

memoria para los nodos 796ai

hApartar

memoria para los ar os (never de ned)i

ACC(start_node) = Distance::Zero_Distance; // acumulado en start_node es cero


NODE_BITS(start_node).set_bit(Aleph::Dijkstra, true);
// inserte start_node en
arbol abarcador y an
otelo en registro
TREENODE(start_node) = tree.insert_node(start_node->get_info());
NODE_COOKIE(TREENODE(start_node)) = start_node;
Uses clear graph 685b, insert node 682a, NODE BITS 670d, NODE COOKIE 670d, and set bit 664a.

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 de ned)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
de ned)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
{

// bloque adicional para asegurar que el heap se destruya antes que


// los bloques almacenados en los cookies
hDe lara i
on de heap en Dijkstra 796ei
hMeter

ar os ini iales en heap 797 i

hCal ular 
arbol abar ador de Dijkstra 798ai
} // aqu
se destruye el heap
hLiberar

memoria para los ar os (never de ned)i

hLiberar

797

memoria para los nodos 796bi


Al igual que el re orrido en amplitud y el algoritmo de Prim, debemos meter los ar os
de ini io en el heap antes de ini iar el al ulo:
hMeter ar os ini iales en heap 797 i
(797b 799b)

for (typename GT::Node_Arc_Iterator it(start_node); it.has_current(); it.next())


{
typename GT::Arc * arc = it.get_current_arc();
ARC_BITS(arc).set_bit(Aleph::Dijkstra, true);

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

poten iales y meterlos en heap 799ai

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

hObtener y pro esar pr


oximo ar o on menor poten ial 798bi
(798a 801a)
typename GT::Arc * garc = heap.get_min_arc(); // obtener arco de menor potencial
// nodos conectados por el arco
typename GT::Node * gsrc = g.get_src_node(garc);
typename GT::Node * gtgt = g.get_tgt_node(garc);
// Est
an los dos nodos visitados?
if (IS_NODE_VISITED(gsrc, Aleph::Dijkstra) and
IS_NODE_VISITED(gtgt, Aleph::Dijkstra))
continue; // ambos visitados ==> insertar este arco causar
a ciclo
typename GT::Node * new_node = // seleccionar nodo no visitado
IS_NODE_VISITED(gsrc, Aleph::Dijkstra) ? gtgt : gsrc;
// insertar nuevo nodo en
arbol y guardarlo en registro de nodo
typename GT::Node * ttgt = tree.insert_node(new_node->get_info());
NODE_BITS(new_node).set_bit(Aleph::Dijkstra, true);
TREENODE(new_node) = ttgt;
typename GT::Arc * tarc = // insertar nuevo arco en tree y guardarlo en registro
tree.insert_arc(TREENODE(gsrc), TREENODE(gtgt), garc->get_info());
TREEARC(garc) = tarc;
Uses get min arc 784a, insert arc 682b, insert node 682a, IS NODE VISITED 670d,
NODE BITS 670d, set bit 664a, and TREEARC.

7.8. Caminos mnimos

799

2.
799a

hA tualizar poten iales y meterlos en heap 799ai


(798a 801a)
// actualizar distancia recorrida desde nodo inicial
ACC(new_node) = POT(garc);
const typename Distance::Distance_Type & acc = ACC(new_node);
// por cada arco calcular potencial e insertarlo en heap
for (typename GT::Node_Arc_Iterator it(new_node); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
if (IS_ARC_VISITED(arc, Aleph::Dijkstra))
continue;
ARC_BITS(arc).set_bit(Aleph::Dijkstra, true);
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
POT(arc) = Plus () (acc, ARC_DIST(arc)); // calcula potencial
heap.put_arc(arc, tgt);
}

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.

Es importante remar ar la bondad que aporta veri ar si el nodo destino del ar o ya ha


sido visitado. En efe to, esta veri a ion puede ahorramos una inser ion al heap, uyo
oste es O(lg V). Bajo esta misma lnea de observa ion, notemos que puede o urrir que
el destino de arc ya haya sido visitado, pues ya puede existir otra va insertada en el
heap que al an e el nodo destino. Por esa razon, es ne esario que en el bloque hObtener
y pro esar proximo ar o on menor poten ial 798bi se veri que visita de los dos nodos
del ar o.
El algoritmo de Dijkstra puede implantarse fa ilmente on matri es de adya en ia. La
estru tura es similar e, in lusive, puede guardarse en una matriz que ontenga todos los
pares de aminos mnimos. Esta te ni a se estudiara en x 7.8.2 (pagina 803).
7.8.1.4

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

algoritmo de Dijkstra 797ai


de heap en Dijkstra 796ei

ar os ini iales en heap 797 i

hCal ular

arbol abar ador de Dijkstra par ial 801ai

hBus ar

en tree amino entre start node y end node 801bi

hLiberar

memoria para los ar os (never de ned)i

hLiberar

memoria para los nodos 796bi

7.8. Caminos mnimos

801

De nes:

dijkstra min path, never used.

Uses Path 690b.

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

if (gtgt == end_node) // est


a end_node en
arbol abarcador?
break; // s
==> camino m
nimo ya est
a en
arbol abarcador

801b

hA tualizar

poten iales y meterlos en heap 799ai

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));
}

Uses has current 103 and Path 690b.

7.8.1.5

Correctitud del algoritmo de Dijkstra

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

Figura 7.42: Ejemplo de ar o negativo que ha e fallar al algoritmo de Dijkstra


fundamental para adjudi ar pertenen ia de un ar o al arbol abar ador mnimo on raz
en el nodo origen. Consideremos, por ontra-ejemplo, el grafo de la gura 7.42 y origen
en el nodo 0. El algoritmo de Dijkstra sele iona omo arbol abar ador los ar os 0 2
y 0 1, lo que ha e, falsamente, el ar o 0 2 omo el amino mnimo desde 0 hasta
2 on oste 3, uando el mnimo es 0 1 2 on oste 0.
Realizada la a laratoria anterior, podemos estable er la orre titud bajo la siguiente
proposi ion:
Proposici
on 7.5 Sea G un grafo onexo ualquiera. Enton es el algoritmo de Dijkstra
sobre un nodo de origen v en uentra un arbol abar ador ontentivo de todos los aminos
mnimos desde v.
Demostraci
on (por contracci
on inductiva sobre la cantidad de nodos V)
Suponga un grafo onexo G =< V, E >, un nodo v V raz de un arbol abar ador de
Dijkstra y un arbol abar ador T =< V, E >. El fundamento de la demostra ion estriba en
suponer que existe un ar o a / E .

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. Caminos mnimos

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 de ned)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 de ned)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

del algoritmo de Floyd-Warshall 804ai

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

hInterfa es del algoritmo de Floyd-Warshall 803bi


(803a) 804b
template <class GT, class 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);
Uses Ady Mat 761b and floyd all shortest paths 804a.

La rutina re ibe uatro parametros tipo:


(a) GT: el tipo de grafo (o digrafo).

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

La rutina anterior se implantara en el siguiente bloque:


hRutinas del algoritmo de Floyd-Warshall 804ai

(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

}
De nes:

de Floyd 808i

floyd all shortest paths, used in hunks 803b and 804b.

Uses Ady Mat 761b.

2.
804b

hInterfa es del algoritmo de Floyd-Warshall 803bi+


(803a) 803b
template <class GT, class Distance>
void floyd_all_shortest_paths
(GT &
g,
Ady_Mat<GT, typename Distance::Distance_Type> & dist,
Ady_Mat<GT, long> &
path)
{
typedef typename Distance::Distance_Type Dist_T;
floyd_all_shortest_paths
<GT, Distance, Aleph::less<Dist_T>, Aleph::plus<Dist_T> > (g, dist, path);
}
Uses Ady Mat 761b and floyd all shortest paths 804a.

7.8. Caminos mnimos

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)

find_min_path(p, src_index, tgt_index, path);


}
Uses find min path 811a and Path 690b.

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 =

Ci,j si existe ar o entre i y j

si no existe ar o entre i y j
si i = j

Ci,j es el peso del ar o entre el nodo i y j.


805

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
}
};
De nes:

Initialize Dist Floyd, used in hunk 806.


Uses Ady Mat 761b, cost 446a, and search arc 675a.

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

7.8. Caminos mnimos

807

typedef typename Distance::Distance_Type Dist_Type;


typedef Ady_Mat <GT, Dist_Type> Dist_Mat;
dist.
template operate_all_arcs_matrix <Initialize_Dist_Floyd <Dist_Mat, Distance> >
(&path);
Uses Ady Mat 761b, Initialize Dist Floyd 805 , and operate all arcs matrix 765b.

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

Tenemos un oste distk1


i,j previamente al ulado para ir desde el nodo i hasta el j. Ahora la
uestion onsiste en bus ar sendos aminos par iales ( i, k) y ( k, j), on distan ias par iales
k1
k1
k1
distk1
i,k y distk,j , respe tivamente, y evaluar si disti,k + distk,j es menos ostoso que
on se plantea del siguiente modo:
distk1
i,j . Formalmente, la de isi
distki,j = min

807b

distk1
i,j
k1
distk1
i,k + distk,j

La ual se modeliza de manera similar al algoritmo de Warshall:


hLazo del algoritmo de Floyd-Warshall 807bi
for (int k = 0; k < n; ++k)
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
if (dist(i, k) + dist(k, j) < dist(i, j))
path(i, j) = path(i, k);

(7.7)

808

808

Captulo 7. Grafos

La diferen ia esen ial on el algoritmo de Warshall es que el if de ide si existe un amino


de menor oste que pase por un amino on nodo intermedio k.
Hay una optima ion del algoritmo de Floyd-Warshall onsistente en observar que, si
la entrada distk1
a un amino mas orto que pase
i,j = , enton es, on ertitud, no habr
por el nodo intermedio k. En fun ion de esta observa ion, podemos plantear una variante
que ahorre las itera iones del lazo mas interno:
hAlgoritmo de Floyd 808i
(804a)
for (int k = 0; k < n; ++k)
for (int i = 0; i < n; ++i)
if (dist(i, k) < max) // es posible encontrar una distancia menor?
for (int j = 0; j < n; ++j)
{
// calcule nueva distancia pasando por k
Dist_Type new_dist = Plus () (dist(i, k), dist(k, j));
// dist(i, j) < dist(i, k) + dist(k, j)
if (Compare () (new_dist, dist(i, j)))
{
dist(i, j) = new_dist;
// actualice menor distancia
path(i, j) = path(i, k); // actualice nodo intermedio
}
}

Re ordemos que la Plus::operator()() implementa la suma, mientras que Compare::operator()()


la ompara ion.
La a tualiza ion de la matriz pathi,j sera expli ada en x 7.8.2.6 (pagina 810).
-2
3
D

B
2

-2

3
3

5
A

-1

-1

F
2

3
4

-1

1
C

-4

Figura 7.43: Un grafo on distan ias negativas


Para el grafo de la gura 7.43 el algoritmo al ula las siguientes matri es por itera ion:

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

7.8. Caminos mnimos

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 di ere 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

Correctitud del algoritmo de Floyd-Warshall

La orre titud del algoritmo de Floyd-Warshall debe sernos meridiana si hemos


aprehendido que el algoritmo de Warshall explora todos los aminos posibles entre
pares de nodos. Esto puede a eptarse omo orre to si asumimos que el algoritmo
de Warshall al ula la lausura transitiva de la rela ion binaria onformada por el
grafo (ver x 7.6.5 (pagina 769)) .
El algoritmo de Floyd-Warshall tambien explora todos los pares de aminos, lo ual se
orrobora apre iando la similitud entre las e ua iones (7.3) y (7.7). Cada itera ion interna
del algoritmo de Floyd-Warshall examina y ompara un nuevo oste entre un par de nodos.
Por ada par de nodos, el algoritmo de Floyd-Warshall explora todos los aminos posibles
y sele iona el menor de ellos. Por tanto, el algoritmo de Floyd-Warshall des ubre todos
los aminos mnimos entre pares de nodos.
17

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

7.6.5 (pagina 769) no se demostro que el algoritmo de Warshall es orre to.

7.8. Caminos mnimos

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);
}
}
De nes:

find min path, used in hunk 805.


Uses Path 690b and set graph 692f.

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

18 En realidad, pare e que hay otros autores. V


eanse las notas bibliogra as en
a laratorias al respe to.

7.9 (pagina 825) para

812

812a

Captulo 7. Grafos

El algoritmo es similar al Dijkstra en el sentido de que onstruye un arbol abar ador


de todos los aminos mnimos desde un ierto nodo origen. Aprove haremos este aso para
ilustrar otro modo de mantener el arbol abar ador onsistente en pintar los ar os que son
parte del arbol de aminos mnimos. Para ello, usaremos un bit, llamado Min, parte de los
bits de ontrol tratados en x 7.3.5.1 (pagina 662).
El algoritmo de Bellman-Ford reside en el ar hivo hBellman Ford.H 812ai on la siguiente estru tura general:
hBellman Ford.H 812ai
hDe ni i
on de ma ros del algoritmo de Bellman-Ford 814ai

on
hInforma i
hRutinas

de algoritmo de Bellman-Ford 812bi

hInde ni i
on

812b

por nodo en Bellman-Ford 813di


de ma ros del algoritmo de Bellman-Ford 814bi

hRutinas de algoritmo de Bellman-Ford 812bi


(812a)
template <class GT, class Distance, class Compare, class Plus> inline
bool 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

generi o de Bellman-Ford 816i

hDete i
on

de i los negativos 821i

hConstru i
on

hRutinas

arbol abar ador de algoritmo de Bellman-Ford 822ai

de ola del algoritmo de Bellman-Ford 818ai

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

de i los negativos 821i

hConstru i
on
}
De nes:

arbol abar ador de algoritmo de Bellman-Ford 822ai

bellman ford min spanning tree, never used.


q bellman ford min spanning tree, never used.

Esen ialmente, hay dos rutinas basadas en el algoritmo de Bellman-Ford. La rutina

bellman ford shortest paths() al ula el arbol abar ador de aminos mnimos desde el

7.8. Caminos mnimos

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

En fun ion de la interfaz de List Graph<Node, Arc>, el algoritmo de Bellman-Ford solo


opera on grafos dirigidos. Por esa razon, la primera instru ion que realizaremos es veri ar si tratamos o no on un digrafo:
hIni ializa i
on de algoritmo de Bellman-Ford 813ai
(812b) 813b
if (not g.is_digraph())
throw std::domain_error("Bellman-Ford algorithm only operates on digraphs");
Uses is digraph.

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;
De nes:
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)

template <class GT, class Distance>


struct Bellman_Ford_Node_Info
{
int
idx;
typename Distance::Distance_Type acum;
};
De nes:
Bellman Ford Node Info, used in hunk 814.

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:
hDe ni 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

hInde ni i
on de
# undef NI
# undef IDX
# undef ACU

ma ros del algoritmo de Bellman-Ford 814bi

(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
{

typename GT::Node_Iterator it(g);


for (int i = 0; it.has_current(); ++i, it.next())
{
typename GT::Node * p = it.get_current_node();
pred[i] = NULL;
arcs[i] = NULL;
g.reset_bit(p, Aleph::Min); // colocar bit en cero
// apartar memoria para cookie (
ndice y acumulado en distancia)
Bellman_Ford_Node_Info <GT, Distance> * ptr =
new Bellman_Ford_Node_Info <GT, Distance>;
NODE_BITS(p).set_bit(Min, false);
NODE_BITS(p).set_bit(Breadth_First, false);
NODE_COOKIE(p) = ptr;
// inicializar campos del cookie
ptr->idx = i;
ptr->acum = Distance::Max_Distance;
}
}
Uses arcs 813 , Bellman Ford Node Info 813d, has current 103, NODE BITS 670d, NODE COOKIE 670d,
Node Iterator 655b, pred 813 , and set bit 664a.

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;

7.8. Caminos mnimos

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

se representa pi tori amente en el arreglo pred de este modo:


Indi e en pred 0 1 2 3 4 5 6 7
Nodo apuntado 4 2 6 4 - 2 4 3
Esta gura representa los ndi es de los nodos en pred; pero re ordemos que en nuestra
implementa ion se guardan punteros a nodos.
La ventaja del arreglo es que si en el trans urso del al ulo se modi a una rama del
arbol, enton es basta on modi ar el padre del nodo.
7.8.3.3

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);

const typename Distance::Distance_Type & acum_src = ACU(src);


typename Distance::Distance_Type & acum_tgt = ACU(tgt);

816

Captulo 7. Grafos

const typename Distance::Distance_Type sum = Plus () (acum_src, dist);


if (Compare() (sum, acum_tgt))
{
const int & idx = IDX(tgt);
pred[idx] = src;
arcs[idx] = arc;
acum_tgt = sum;
}

Uses arcs 813 and pred 813 .


w
v, esta expresi
En terminos del ar o u
on es equivalente a:

 if A (v) > A (u) + w =

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)

// recorrer los arcos para evaluar si relajamiento


for (typename GT::Arc_Iterator it(g); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
typename GT::Node * src = g.get_src_node(arc);
typename GT::Node * tgt = g.get_tgt_node(arc);

hRelajar

ar o 815bi

Uses Arc Iterator 661a and has current 103.

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

Algoritmo mejorado de Bellman-Ford

El bloque hAlgoritmo generi o de Bellman-Ford 816i es simple; omo veremos despues,


es orre to, as omo fa ilmente adaptable a matri es de adya en ia. Podemos mejorarlo
si observamos que uando en una itera ion no se modi quen los a umulados de un par
de nodos one tados por un ar o, enton es, en la siguiente itera ion el ar o tampo o se

7.8. Caminos mnimos

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 de nitivo 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

Uses DynListQueue 166 and put in queue 818a.

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);
}
De nes:
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 inde nidamente, lo que a arreara que el algoritmo entre a un i lo in nito. 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);

7.8. Caminos mnimos

819

// recorrer y relajar arcos del nodo src


for (typename GT::Node_Arc_Iterator it(src);
it.has_current() and i < n; // haya arco y no m
as de V.E arcos
it.next(), ++i) // avanza a siguiente arco de nodo y cuenta arco
{
typename GT::Arc * arc = it.get_current_arc();
typename GT::Node * tgt = it.get_tgt_node();
const typename Distance::Distance_Type & acum_src = ACU(src);
const typename Distance::Distance_Type & w = Distance () (arc);
const typename Distance::Distance_Type sum_src = Plus () (acum_src, w);
typename Distance::Distance_Type & acum_tgt = ACU(tgt);
// relajar arco
if (Compare () (sum_src, acum_tgt)) // acum_src < acum_tgt
{
const int & idx = IDX(tgt);
pred[idx] = src;
arcs[idx] = arc;
acum_tgt = sum_src;
if (not is_in_queue <GT> (tgt))
put_in_queue <GT> (q, tgt);
}
}
}

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.

Hay varios omentarios que pueden ayudarnos a mejorar nuestra omprension:


1. El bit Breadth First, el ual es o ultado en las hRutinas de ola del algoritmo
de Bellman-Ford 818ai, evita que un nodo destino (tgt), uyo a umulado haya sido
modi ado en la itera ion a tual sea doblemente introdu ido en la ola. En efe to,
notemos que, por la va de un ar o que aun no haya sido pro esado, es posible
modi ar el a umulado de un nodo que se en uentre en la ola; bien sea de la
i-esima itera ion a tual o de alguna i 1-esima itera ion anterior.
2. El orden de la ola garantiza que, al menos en la primera itera ion (i = 0), el re orrido
es exa tamente uno de amplitud. Posteriormente, en una itera ion i, en fun ion de
los nodos que hayan sido metidos en la ola (solo aquellos uyo a umulado haya
sido modi ado), se pro esaran igual o menos ar os que en la itera ion 0. De aqu se
desprende la bondad de esta version del algoritmo: tiende a ser mu ho mas rapido
que hAlgoritmo generi o de Bellman-Ford 816i.
El orden de la ola asegura que se pro esen ar os one tados a nodos uyos a u-

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

Figura 7.45: Progreso del algoritmo de Bellman-Ford mejorado on raz en el nodo A.


Cada gura orresponde a E ar os inspe ionados, lo que, habida uenta de la antidad de
guras respe to a la gura 7.44, ofre e una idea de la a elera ion del algoritmo mejorado.
En a~nadidura, se ve omo el arbol abar ador se des ubre mas rapido que el algoritmo
anterior.
mulados hayan sido modi ados, lo que posibilita una relaja ion que disminuya el
a umulado. Por el ontrario, on el hAlgoritmo generi o de Bellman-Ford 816i
habran itera iones que on iernan a ar os uyos a umulados origen y destino son
in nitos y que, por tanto, no se modi aran a pesar de que el ar o sea revisado. Por
aqu tenemos otra idea a er a del porque el algoritmo basado en la ola tiende a ser
mas e iente que el generi o.
3. Un ar o a relajado uyo nodo destino ha sido metido en la ola no volvera a visitarse
hasta que todos los ar os adya entes a los nodos anteriores de la ola hayan sido
visitados. La itera ion 0 visita, on erteza, todos los ar os, pues los a umulados
de los nodos tienen valor in nito, lo que asegura su relaja ion. Un ar o a relajado
en la itera ion i debera esperar a la itera ion i + 1 para eventualmente volver a ser
relajado.
4. Si el grafo no ontiene i los negativos, enton es es seguro que la ola se va iara, pues,
uando el algoritmo estabili e los a umulados a sus valores de nitivos, es de ir, sus
ostes mnimos, no habran mas inser iones en la ola.
5. Si, por el ontrario, el grafo ontiene algun i lo negativo, enton es los a umulados

7.8. Caminos mnimos

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

Parafraseando a Sedgewi k, \los pesos negativos no son meramente una uriosidad

matemati a: al ontrario, estos extienden signi ativamente la apli abilidad de los


problemas de aminos mnimos omo modelos para resolver otros problemas" [9 .
19

821

Di ho en otros terminos, existen problemas sobre grafos -bastante importantes- que


plantean transformar los pesos de suerte tal que estos devengan negativos o la adi ion
de ar os \ ti ios" on pesos negativos. In lusive, desde la perspe tiva de la modeliza ion,
es posible imaginar ar os negativos. As pues, tratar on pesos negativos puede ser parte
de la realidad de un programador. Lo que s aparentemente no tiene sentido es la existen ia de un i lo negativo. Su dete ion, al menos on interes de depura ion o valida ion,
onstituye un problema.
Una vez agotadas las posibilidades de relaja ion de los ar os, gra ias a que el numero de
itera iones esta a otado, el siguiente paso, ual es una de las bondades de este algoritmo,
onsiste en veri ar la presen ia de i los negativos. En la version lasi a, la dete ion
de un i lo negativo se realiza si en la V -esima itera ion se relaja algun ar o. Para ello,
simplemente realizamos una itera ion mas:
hDete i
on de i los negativos 821i
(812b)
bool ret_val = true;

// recorrer todos los arcos en b


usqueda de un ciclo negativo
for (typename GT::Arc_Iterator it(g); it.has_current(); it.next())
{
typename GT::Arc * arc = it.get_current_arc();
typename GT::Node * src = g.get_src_node(arc);
typename GT::Node * tgt = g.get_tgt_node(arc);
const
const
const
const

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);

if (Compare() (sum, acum_tgt)) // lograr


a modificarse tgt?
{
ret_val = false; // se detect
o un ciclo negativo
break;
}
}
Uses Arc Iterator 661a and has current 103.

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

de este reda tor.

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;
}

Uses has current 103 and Node Iterator 655b.

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);
}

7.8. Caminos mnimos

823

typename GT::Node * gtgt = g.get_tgt_node(garc);


typename GT::Node * ttgt = NULL;
if (IS_NODE_VISITED(gtgt, Min))
ttgt = static_cast<typename GT::Node*>(NODE_COOKIE(gtgt));
else
{
NODE_BITS(gtgt).set_bit(Min, true); // marcar bit
delete NI(gtgt);
ttgt = tree.insert_node(gtgt->get_info());
GT::map_nodes(gtgt, ttgt);
}
typename GT::Arc * tarc = tree.insert_arc(tsrc, ttgt, garc->get_info());
GT::map_arcs(garc, tarc);
ARC_BITS(garc).set_bit(Min, true);
}
return true; // no hay ciclos negativos
Uses arcs 813 , insert arc 682b, insert node 682a, IS NODE VISITED 670d, map arcs 686, map nodes 686,
NODE BITS 670d, NODE COOKIE 670d, and set bit 664a.

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

Correctitud del algoritmo de Bellman-Ford

El algoritmo de Bellman-Ford basado en el bloque hAlgoritmo


generi o de Bellman-Ford 816i es orre to.

Proposici
on 7.6
20 Basada

20

en la prueba de Sedgewi k [9.

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 in nito y no hay ar os.

1. i = 0: Luego de la primera itera ion, v puede englobarse bajo dos asos:


w
v, enton es A (v) = w, el ual es el m
nimo amino posible
(a) Si existe un ar o u
ompuesto por un solo ar o.
(b) Si no existe ar o dire to entre u y v, enton es A (v) = y la antidad de
ar os involu rada es nula; lo que no viola la hipotesis indu tiva.

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

Es de ir, un amino de i o menos ar os desde u ha ia w y un ar o desde


w ha ia v. Por la hipotesis indu tiva, A (w) ontiene el oste mnimo desde
u ha ia w posible on i o menos ar os. Del bloque hAlgoritmo gen
eri o de
Bellman-Ford 816i vemos que la itera ion i + 1 observara todos los ar os de w
y sele ionara el ar o que va ha ia v a tualizando su A (v)


7.8.4

Discusi
on sobre los algoritmos de caminos mnimos

El problema de amino mnimo es uno de los problema resolubles mas importantes de


los grafos. Entre los algoritmos estudiados, y otros existentes, > uando y omo sele ionar
uno?. Ini iemos nuestra dis usion presentando la siguiente tabla re apitulatiba de tiempos
de eje u ion y espa io:
Algoritmo
Representa ion
Desempe~no Espa io
Dijkstra
Listas de adya en ia
O(E lg V)
O(V)
3
Floyd
Matri es de adya en ia
O(V )
O(V 2)
Bellman-Ford Listas de adya en ia
O(V E)
O(V)

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

7.9. Notas bibliogr


aficas

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 de ne 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 pre jo \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 pre jo 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 pre jo 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

1. >Cuanta es la antidad maxima de ar os que puede tener un grafo?


2. >Cuanta es la antidad maxima de ar os que puede tener un digrafo?
3. Para el siguiente grafo:
J

(a)
(b)
( )
(d)
(e)
(f)

Determine y dibuje un arbol de re orrido en profundidad a partir del nodo A.


Determine y dibuje un arbol de re orrido en amplitud a partir del nodo A.
Determine y dibuje un arbol abar ador a partir del nodo F.
En uentre el (o los ) amino(s) mas largo(s).
En uentre el grafo omplemento.
En uentre todos los liques o subgrafos ompletos.

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

7. >Como puede espe i arse un amino en un grafo o digrafo a traves de sus ar os


uando estos no esten etiquetados?
8. Plantee un algoritmo a fuerza bruta que veri que si dos grafos son o no isomorfos.
9. Plantee esquemas para representar una matriz de adya en ia orrespondiente a un
multigrafo.
10. Es riba el algoritmo de re orrido en profundidad sin re ursion.
11. Modi que el bloque hini ializar arreglo de listas (never de ned)i para que solo aparte
los elementos por arriba de la diagonal.
12. Modi que el bloque hIni ializar arreglo de listas (never de ned)i para usar el tipo
BitArray.
13. Dise~ne un TAD Ady Bit que represente una matriz de adya en ia de bits. Use un
arreglo dinami o DynArray<T> de arreglos de bits BitArray. Minimi e el onsumo
de memoria.
14. Dado un grafo < V, A > y sean sv y sa los tama~nos de los tipos alma enados en
los nodos y ar os, respe tivamente. Sea sp el tama~no de un apuntador. >Cual es la
rela ion entre la antidad de ar os respe to a la de nodos de modo tal que sea menos
o mas ostoso en espa io la representa ion on listas de adya en ia que on la de
matri es?
15. >En uales asos la veri a ion de existen ia de ar o on matri es de adya en ia es
O(lg (n))?
16. Determine los puntos de arti ula ion para el siguiente grafo:
J

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. Modi que el algoritmo de Floyd-Warshall para dete tar i los negativos. (+)
35. Suponga un grafo on i los negativos, es riba un algoritmo que identi que 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.

O jistem problemu minimalnm.

Spole nosti, 6:57{63, 1930. in Cze h.

Pra a Moravske Prrodove ke

[8 Dieter Jungni kel. Graphs, Networks and Algorithms. Springer, 2004.


[9 Robert Sedgewi k. Algorithms in C: Part 5: Graph algorithms. Addison-Wesley,
pub-AW:adr, 2002.
[10 R. E. Tarjan. Depth rst sear h and linear graph algorithms. SIAM J. Comput.,
1(2):146{160, June 1972.
[11 Stephen Warshall. A theorem on Boolean matri es. Journal of the ACM, 9(1):11{12,
January 1962.

Indice
al ulo de la posi ion in ja, 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
de ni 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

analisis de algoritmos, 185


analisis del manejo de olisiones por dire ionamiento abierto y sondeo lineal,
488
analisis del manejo de olisiones por en adenamiento errado, 479
analisis del manejo de olisiones por en adenamiento separado, 474
analisis del mergesort, 211
analisis del sondeo ideal, 481
analisis poten ial, 231{233
analisis amortizado
analisis ontable, 233{234
analisis poten ial, 231{233
sele ion de reditos, 234{235
sele ion de fun ion poten ial, 234{235
analisis de los arboles aleatorizados, 555{559
analisis de los arboles binarios de busqueda,
(400, 404
analisis de los arboles splay, 617{623
analisis de los treaps, 567
analisis del qui ksort, 216
an estros de un nodo, 277
apli a iones de las olas, 156
apuntadores y arreglo, 31
arbol
rojo-negro, 590
de Fibona i, 584
altura de un, 340
ompleto
representa ion on arreglo se uen ial,
347
de ni ion, 276
arbol AVL rti o, 586
arbol abar ador de amplitud, 720
arbol abar ador de profundidad, 717
arbol abar ador mnimo, 771{791
arbol binario
de ni ion, 284
re orrido in jo, 286
re orrido por niveles, 286
re orrido pre jo, 286
re orrido su jo, 286
arbol binario de busqueda
elimina ion, 396
inser ion, 391

INDICE

inser ion en raz, 398


supresion, (396, 397
arbol binario de busqueda aleatorio, 550
arbol binario de busqueda extendido
inser ion en raz, 414
arboles
an estros de un nodo, 277
AVL
analisis, 583{589
elimina ion, (577, 583
inser ion, (572, 577
binarios de busqueda, (382, 404
analisis, (400, 404
busqueda, (384, 389
busqueda de padre, 387
busqueda de rango, 387
busqueda del prede esor, 386
busqueda del su esor, 386
rti a, (400, 404
de ni ion, 382
inser ion, (391, 392
maximo, 385
mnimo, 385
supresion, (396, 397
equilibrio perfe to, 546
genera ion de un nodo, 278
grado de un nodo, 278
nodo binario
TAD BinNode<Key>, 290
representa ion en memoria, 282
rojo-negro, 590{609
analisis, 605{609
elimina ion, (598, 605
inser ion, (593, 598
splay, 609{623
arboles AVL, 568{589
analisis, 583{589
de ni ion, 568
elimina ion, (577, 583
inser ion, (572, 577
arboles aleatorizados, 550{559
analisis, 555{559
on atena ion, 553
elimina ion, 553
inser ion, 551
supresion, 553

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 in ja, 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

algoritmo de Dijkstra, 792{803


algoritmo de Floyd-Warshall, 803{811
dis usion, 824
ardinalidad
de un arbol, 278
identidad, 343
del nivel, 342
BinHeap<Key>
ardinalidad de un arbol binario, 304
a tualiza ion de un elemento, 368
asting
elimina ion, 367
Dlink a estru tura ontenida, 100
elimina ion de ualquier elemento, 368
Slink a estru tura ontenida, 83
elimina ion de todos los elementos, 369
Catalan
Inser ion, 366
numero de, 378{381
inter ambio entre nodos, 365
i los negativos
BinNode<Key>
algoritmo de Bellman-Ford, 821
onversion a Tree Node<T>, 338
lase
BinTree<Key>, 389
gestora, 66
BitArray, 36{43
intermediaria, 66
bits de ontrol, 662
proxy, 66
bu le, 641
lausura transitiva, 711, 769
busqueda
lique de un grafo, 642
sobre arreglos, 225
odigo
busqueda aleatoria
de Lukasiewi z, 372, 377
en arreglo, 225
odigo de un arbol binario, 372
en listas, 226
olas
busqueda binaria, 32, 205{206
apli a iones, 156
busqueda de aminos por amplitud, 714
mediante arreglos, 157
busqueda de aminos por profundidad, 709
mediante listas enlazadas, 163
busqueda de i los en un grafo, 704
mediante listas enlazadas dinami as,
odigos de Hu man, 421{442
166
odi a ion de texto, 436
representa iones en memoria, 156
de odi a ion, 427
olas de prioridad, 354
ingreso de fre uen ias, 433
a tualiza ion de un elemento, 355
optima ion, 439
ompara ion de arboles binarios, 307
a he, 263
Compare y Dlink, 190
amino
omplemento de un grafo, 641
busqueda en profundidad, 711
omponentes onexos de los puntos de orte,
entre nodos de un arbol, 277
742
longitud en un arbol, 277
Componentes in onexos de un grafo, 726
prueba de existen ia, 709, 769
omprension de datos, 421{442
simple, 641
on atena ion de arboles aleatorizados, 553
amino mas orto, 256
ondi ion negra, 590
amino de Catalan, 379
ondi ion roja, 590
amino de un grafo, 641
one tividad entre grafos, 701, 769
Camino mas orto, 259
onjunto, 21
aminos mnimos, 791{825
onjunto pre jo, 373
algoritmo de Bellman-Ford, 811{824
busqueda de nodos en un grafo
implementa ion, 674
busqueda en tabla hash lineal, 509
busqueda se uen ia, 193{194
Bellman-Ford
aminos mnimos, 811{824

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

dete ion de i los negativos, 821


Deway
busqueda en Tree Node<T>, 335
nota ion para arboles, 281
df, 734
diametro de un grafo, 825
Diagrama UML
DynArray<T>, 72
Hu man, 424
Listas doblemente enlazadas, 107
Listas simplemente enlazadas, 85
di olas, 156
digrafo, 640
Dijkstra
aminos mnimos, 792{803
Triple parti ion del qui ksort, 225
dipolos, 156
dispersion de adenas de ara teres, 521
dispersion por division, 517
dispersion por multipli a ion, 519
dispersion universal, 522
dividir ombinar, 208
oste en espa io, 217
dmallo , 255
DynSetTree, 405

edad de un nodo, 278


el problema del esta ionamiento, 466
elimina ion
en ABB, 396
arbol binario de busqueda, 396
elimina ion de la re ursion, 149
elimina ion de la re ursion ola, 149
ddd, 256
elimina ion de la re ursion por emula ion de
deadlo k, 248
los registros de a tiva ion, 150
DEBUG, 250
elimina ion en tabla hash lineal, 511
de alogo de Holtzmann, 239
elimina ion total de la re ursion, 154
dedu ion, 23
elimina ion en arbol arbol rojo-negro, (598,
delete, 36
605
depuradores, 256
desempe~no de los arboles binarios extendi- elimina ion en arbol aleatorizado, 553
elimina ion en treap, 564
dos, 418
elimina ion por lave en arboles binarios exdestroyRe , 306
tendidos, 417
destru ion
elimina ion por posi ion en arboles binarios
de Tree Node<T>, 333
destru ion de un arbol binario, 306
extendidos, 418
destru tores virtuales
Engler, 247
enla e doble, 90
problema, 108

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 in jas, 138
evalua ion de expresion su ja, 138
expansion de tabla hash lineal, 504
extensiones a los arboles binarios, 408
al ulo de la posi ion in ja, 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
aw nder, 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

arbol abar ador, 642


bipartido, 643
amino, 641
i lo, 641
lique, 642
omplemento de, 641
ompletamente onexo, 641
ompleto, 641
on pesos, 640
onexo, 641
dirigido, 640
in onexo, 642
nodos
iterador de ar os, 655
iterador de nodos, 655
subgrafo, 642
totalmente onexo, 641
grafos
olora ion, 643
de ni ion formal, 639
diametro, 825
Graph Node<Node Type>, 651
heap

BinHeap<Key>, 360

apli a iones, 359


olas de prioridad, 354
a tualiza ion de un elemento, 355
on arboles, 360
on listas enlazadas, 360
elimina ion, 351
inser ion, 350
heap de ar os mnimos, 781
heap ex lusivo, 781
heaps, 347
on arreglos, 348
heapsort, 356
heren ia, 11
tipos, 12
heren ia multiple, 13
heursti a
amino mas orto, 257
hilado de arboles binarios, (312, 318
holgura de dispersion de una fun ion hash,
515
Holtzmann, 239
Hu man

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

lista simplemente enlazada, 87


Iterador de ar os sobre un nodo
implanta ion, 680
iteradores, 73{75
its4, 246
Kruskal, 776
analisis, 779
orre titud, 779
lazo, 641
lema
de la uni idad de un treap, 560
lint, 246
listas
doblemente enlazadas
Dlink, 90
Dlist<T>, 108
Dnode<T>, 106
DynDlist<T>, 113
enla e doble, 90
iterador, 112
nodo doble, 106
doblemente enlazadas dinami as, 113
simplemente enlazadas
DynSlist<T>, 88
enla e simple, 79
iterador, 87
nodo simple, 83
Slink, 79, 83
Slist<T>, 86
listas de adya en ia, 647
en List Graph<Node, Arc>, 676
listas enlazadas, 75
para representar arboles, 282
List Graph<Node, Arc>, 648
asigna ion, 671
onstru ion, 671
destru ion, 671
implanta ion de la asigna ion, 688
implanta ion de la onstru ion, 688
implanta ion de la opia de grafos, 687
implanta ion de la elimina ion de ar os,
683
implanta ion de la elimina ion de nodos, 684

838

implanta ion de la inser ion de ar os,


682
implanta ion de la inser ion de nodos,
681
implanta ion de la limpieza de un grafo,
685
implanta ion del mapeo de nodos y ar os de un grafo, 686
livelo k, 248
lo alidad de referen ia, 260, 263
espa ial, 260, 263
temporal, 260, 263
longitud
del amino en arboles, 277
longitud del amino
de ni ion, 342
externo
formula en fun ion del amino interno, 344
identidades, 343
interno
lmites, 345
longitud del amino interna ponderada, 443
Lukasiewi z, 372

INDICE

meta- ompila ion, 247


metodo
etimologa, 8
mez la
metodo de ordenamiento, 209{213
modi ador, 9
multi onjunto, 21
multigrafo, 642
multiheren ia, 13
multilistas, 167
multimap, 21
multipop, 133
multiset, 21

numero de Catalan, 378{381


Numero de Deway
busqueda de lave y determina ion, 336
numeros de Fibona i, 148
nana, 249{253
new, 31, 36
nivel de un nodo en un arbol, 278
Node Arc Iterator, 680
nodo
edad de, 278
genera ion de, 278
metodo de division omo fun ion hash, 517
grado de un nodo en arbol, 278
metodo de multipli a ion omo fun ion nodo in idente, 640
hash, 519
nodo adya ente, 640
metodos virtual, 13
Nodo de un grafo, 651
ma ros para grafos, 670
nodo externo
map, 21
antidad en un arbol, 342
mapeo, 21
de ni ion, 342
ar os, 668
nodo simple, 83
nodos, 668
nota ion
mapeo de omponentes onexos, 747
de Deway, 281
mapping, 21
nota ion O, 196{207
matemati as sobre arboles, (340, 381
O
matri es, 71
de ni ion, 196
matriz
o, 197
al ulo de dire ion, 71
, 197
en Fortran, 73
, 197
Matriz de adya en ia, 644
, 197
matriz de adya en ia, 751
nota ion in ja, 138
memory leaks, 254
nota ion pre ja, 138
mergesort
nota ion su ja, 138
analisis, 211
numero df, 734
rti a, 211

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
polimor smo, 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
pro ling, 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
per laje, 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

qui ksort on urrente, 225


qui ksort paralelo, 225
Rabin-Karp
busqueda de sub- adenas, 524
rapido
metodo de ordenamiento, 229
realloc, 44
re orrido
in jo
sobre arbol binario, 286
por niveles
sobre arbol binario, 286, 308
pre jo
sobre arbores en ia, 320
sobre arbol binario, 286
su jo
sobre arbol binario, 286
sobre arbores en ia, 321
re orrido en amplitud, 701
re orrido en profundidad, 696
re orrido enorden, see re orrido in jo
re orrido postorden, see re orrido su jo
re orrido preorden, see re orrido pre jo
re orridos
sobre arbores en ias, 320, 331
re orridos pseudo-hilados sobre arboles binarios, 315
re orridos sobre arboles binarios, 285
re orridos sobre grafos, 695{751
re ursion
onsejos, 147
elimina ion, 149
emula ion de los registros de a tiva ion, 150
elimina ion total, 154
pilas, 145
re ursion ola
elimina ion, 149
registro de a tiva ion, 147
regla
de Pareto, 260
del 80-20, 260
rela ion
simetri a, 640
remove all and delete(), 105
representa ion de arboles

INDICE

onjuntos anidados, 279


indenta ion, 281
se uen ia parentizada, 279
representa ion de arboles on arreglos, 283
representa ion de arboles on listas enlazadas, 282
representa iones en memoria de una ola,
156
rota ion en arbol binarios, 419
rota ion en arboles binarios extendidos, 420
se uen ia parentizada para representar arboles,
279
Sedgewi k, 781
sele ion
metodo de ordenamiento, 189{193
sele ion
por posi ion en arbol binario, 411
por posi ion en arreglo, 227
sobre arreglos, 227
set, 21
sift down, 351
sift up, 350
similaridad de arboles binarios, 307
Skiena, Steven, 256
Slink, 79
sondeo
lineal, 484
sondeo ideal
analisis, 481
subgrafo de un grafo, 642
suma de polinomios, 124
supertraza, 249, 526
supresion en arbol aleatorizado, 553
supresion en arbol binario de busqueda,
(396, 397
supresion en treap, 564
tabla hash
holgura de dispersion, 515
interfaz de la fun ion hash, 515
plegado de lave en fun ion hash, 516
tablas hash
dispersion de adenas de ara teres, 521
dispersion universal, 522
fun iones, 515
fun iones hash, 515

INDICE

841

manejo de olisiones, 467


Treap<Key>, 561{567
dire ionamiento abierto, 481
Tree Node<T>, (322, 340
en adenamiento, 469
templates, 16
en adenamiento separado, 469
test de a i li idad, 706
manejo de olisiones por dire ionamiento , 197
abierto y sondeo lineal
thrashing, 537
analisis, 488
tipos de estru tura de datos, 21
re-dimensionamiento, 499
tipos de errores, 236
reajuste de dimension, 499
Tom Van Cle k, 242
supertraza, 526
Treap
tablas hash lineales, 503{515
de ni ion, 559
busqueda, 509
treaps, 559{568
elimina ion, 511
analisis, 567
expansion, 504, 509
elimina ion, 564
inser ion, 510
inser ion, 563
tablas hash perfe tas, 523
prioridades impl itas, 568
TAD
Tree Node<T>
Ady Mat<GT, Entry>, 761{766
busqueda por numero de Deway, 335
al ulo de altura, 334
Bit Mat Graph<GT>, 766{769
DynMapTree<Tree, Key, Range, Compare>, onversion a BinNode<Key>, 338
404{408
destru ion, 333
Map Matrix Graph<GT>, 752
modi adores, 327
Matrix Graph<GT>, 758{761
observadores, 326
ArrayStack<T>, 131{134
observadores de arbores en ias, 331
ArrayQueue<T>, 157{162
re orridos, 331
Avl Tree<Key>, 570{583
UML, 10
BinNode<Key>, (290, 298
unidad de eje u ion, 187
BinNodeXt<Key>, (409, 421
union de arboles binarios, 399
BinTree<Key>, (389, 400
uni
on de ABB, 399
Dlink, 90{106
union ex lusiva de arboles binarios, 394
Dlist<T>, 108{113
union ex lusiva de arboles binarios aleatoriDnode<T>, 106{107
zados, 553
DynArray<T>, 44{71
union ex lusiva de arboles binarios extendiDynDlist<T>, 113{120
dos, 416
DynListQueue<T>, 166{167
uni
o
n
ex lusiva
de ABB, 394
DynListStack<T>, 136{138
DynSlist<T>, 88{90
virtual, 13
listgraph, 648
ListQueue<T>, 163{166
Warshall, 711
ListStack<T>, 134{136
algoritmo para la lausura transitiva,
Rand Tree<Key>, 550{555
769
Rb Tree<Key>, 591{605
Wirth
Slink, 79{83
equilibrio perfe to, 546
Slist<T>, 86{88
Snode<T>, 83{84
Splay Tree<Key>, 612{617

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,

433, 435, 436a, 612a


bin node: 424b, 429b, 429 , 432a
BinNodeVtl: 294a, 389a, 612a
BinNodeXt: 409
bin to forest: 340
bin to tree: 339b, 340
BitArray: 36, 39a, 40b, 40d, 42, 426b, 428, 430, 431b, 437, 438a, 766
Bit Fields: 662, 665b, 665 , 665d, 666b
Bit Mat Graph: 767a, 767 , 770a, 770b
BitProxy: 40a, 40b, 40d, 41b, 41
block size: 49a, 49 , 53b, 55, 56, 58d, 59, 60a, 65a
breadth first traversal: 702
BucketItor: 472 , 473a, 474a, 500, 507b, 510a
BucketList: 472 , 500, 504b, 507b, 509a, 510a, 510b
bucket to index: 498
build optimal tree: 445
build prefix encoding: 430, 431b
build subgraph: 726b, 726 , 727
build tree: 310a
busy slots counter: 472d, 473a, 473b, 474b, 500, 504b, 507b, 509a, 510b, 511
Cache Entry: 529 , 530 , 533a, 533b, 533 , 534a, 534b, 535a, 535b, 536a, 536b, 536 ,
537a, 537b
cache size: 531 , 533b, 537b
call hash fct: 507a, 510a, 510b
CHILD: 325b
child: 323 , 323d, 324a, 324b, 328b, 331 , 332b, 335a, 337, 338b, 366, 575b, 598
child to Tree Node: 324a, 324b
chunk list: 533a, 533b, 537b
clean avl stack: 571 , 572a, 576, 577a, 578a, 578b, 582
clear graph: 672b, 685b, 687b, 689 , 718b, 720, 749a, 749b, 778 , 780, 797a, 813b
Cmp Arc: 689d, 690a
code map: 426 , 430, 437
Compare Arc Data: 796d
Compare Dnode: 192a, 192b, 204b, 222 , 229
Complejo: 5
compute cardinality rec: 304b
compute cut nodes: 737 , 738a
compute cut nodes: 738 , 739b, 740
computeHeightRec: 305a, 431b, 570a
compute nodes in level: 311b
compute nodes in level: 311a, 311b
compute optimal cost: 446d
compute transitive clausure: 770a
compute tree: 445, 448

845

concat list: 96, 212, 222a, 226b, 228 , 509a


contract: 509b, 511
copy array: 58 , 59
copy graph: 672a, 687b, 689b
copy list graph: 757a, 757b, 758a, 767 , 768a
copyRec: 305b, 406
COST: 446b, 446 , 446e, 447a, 447
cost: 445, 446a, 446b, 446d, 447a, 447 , 805
Cross Arc: 744a, 744b, 746
CTRL BITS: 364a
current dim: 51b, 51 , 54 , 55, 56, 58 , 59, 60a, 62, 63, 64, 69a
cut: 59, 64, 509a, 662, 663, 664a, 664b, 758a, 760
cut list: 99b, 119b
deallocate bucket: 497b, 498
DECLARE BINNODE: 291b, 294a, 363, 569
DECLARE BINNODE SENTINEL: 293, 409, 551a, 561, 591
decode: 428
decrease probe counter: 497a, 498
default initial value: 53a, 53b, 54a
default initial value ptr: 53a, 53b, 54a, 55, 56, 59
Default Pow Block: 47, 57a, 57b
Default Pow Dir: 47, 56, 57b
Default Pow Seg: 47, 56, 57b
depth first traversal: 697b, 698a, 701
depth first traversal: 697a, 697b, 698b
destroy forest: 333
Destroy Node Dijkstra: 795
destroyRec: 305b, 306, 406 , 407
destroy tree: 333
deway search: 335b
deway search: 335a, 335b
df: 736, 737b, 738b, 740
digraph: 679, 681a, 682b, 684, 688, 689a, 689b, 778 , 780
Dijkstra Arc Info: 796
Dijkstra Heap Info: 794b, 796e
dijkstra min path: 799b
dijkstra min spanning tree: 793
Dijkstra Node Info: 794a, 794 , 795b, 795
dir size: 49a, 49 , 52b, 52 , 54 , 55, 56, 59, 60a
Distance Compare: 775a, 776a, 781b, 782, 783
DLBucket: 502a, 502b, 502 , 503a, 503b
Dlink: 90, 91a, 91b, 91 , 92b, 94a, 94b, 94 , 95, 96, 97b, 98, 99a, 99b, 100, 101b, 101 ,

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

dlink random search: 226b


dlink random select: 228 , 229
dlink to arc: 673a, 673 , 690a
dlink to arc node: 677b, 680 , 685a
dlink to node: 673a, 673 , 685b
DLINK TO TYPE: 100
Dlist: 110b, 113a, 113d, 114b, 114 , 115b, 116b, 116d, 117a, 117b, 118b, 118 , 119a,

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

find min: 385


find min path: 805a, 805b, 811a
find path breadth first: 715
find path depth first: 711, 713, 801b
find path depth first: 712, 713
find succ and swap: 598
find successor: 386
fix black condition: 598, 600a
FixedQueue: 157
FixedStack: 131a, 221, 571a, 592b
fix red condition: 593, 594a
floyd all shortest paths: 803b, 804a, 804b
forest postorder traversal: 332b
forest preorder traversal: 332a
forest to bin: 338a
GenBinHeap: 360
GenDlist: 110a, 110b, 112
generate huffman tree: 429 , 436b
GenLhashTable: 470a, 471
GenLinearHashTable: 504a
Gen Rand Tree: 550, 551d
Gen Rb Tree: 592a
Gen Treap: 562a, 562d
get bit: 663, 670d
get block size: 49
get child: 327a
get child list: 323d, 325b
get color: 748, 749a
get connected node: 658b, 676a, 676b, 693e, 757d
get current: 87e, 103, 105a, 105 , 112, 117a, 117b, 118b, 118 , 119a, 119b, 120 , 125d,

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

node: 753e, 754a, 765b, 768b


parent: 327b, 328b
right child: 326b, 333
right sibling: 325b, 326a, 327a, 328a, 331 , 332b, 333, 334, 335a, 335b, 336b, 337,

338a, 339b

get right tree: 330, 331a, 332a


get seg size: 49
get sibling list: 323d, 325b
Graph Arc: 657b, 680a
Graph Bits: 665a
Graph Node: 651b, 652d
graph to tree: 724
graph to tree node: 722b, 723b
has current: 87e, 88a, 103, 104a, 105 , 118a, 120 , 125 , 127b, 127 , 128, 190b, 194a,

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

insert by pos: 416a


insert child: 338b, 339b
insert entry to lru list: 533b, 533 , 536 , 537b
insert in binary search tree: 391, 392, 399, 400a
insert in queue: 714d, 715
insertion sort: 202a, 204a, 204b, 223
insert leftmost child: 329, 338b
insert left sibling: 328b
insert list: 95, 114a, 118b
insert node: 653a, 681b, 682a, 687b, 718b, 719b, 720, 727, 748, 749a, 749b, 778a, 778b,

787a, 787b, 797a, 798b, 822b


insert rightmost child: 329, 724
insert right sibling: 328a, 339a
insert root: 398, 400a
insert root xt: 414, 552a
insert sibling: 339a, 339b
insert sorted: 203, 204a
insert tree to right: 330
inside list: 531 , 535b
internal path length: 343a, 343b
internal path length rec: 343a
Inversed Compare: 356b
is a cross arc: 744b, 749b
is a cut node: 745a, 746, 749b
is acyclique: 662, 663, 664a, 664b, 707, 708, 723b
is acyclique: 706, 707, 708
is an cut arc: 749b
is arc painted: 745a, 745b
IS ARC VISITED: 670d, 698b, 702, 705a, 705b, 706, 710, 712, 715, 719a, 720, 724, 727,
738 , 740, 748, 787b, 799a
is avl: 570a
is digraph: 651a, 687b, 701, 707, 708, 709b, 778 , 780, 813a
is in first: 103
is in last: 103
is in list: 364
is in queue: 818a, 818b
ISLEAF: 325b
is leaf: 324 , 325a, 325b, 326b, 329, 363, 364a, 427f, 430, 431a
IS LEAF: 364a
is leaf: 324 , 325a, 325b, 326b, 329, 363, 364a, 427f, 430, 431a
IS LEFT: 364a
ISLEFTMOST: 325b, 327b
is leftmost: 324 , 325a, 325b, 326a, 331a, 331b, 332b, 333
is node painted: 745a, 746
IS NODE VISITED: 670d, 698b, 702, 705b, 706, 708, 710, 712, 713, 715, 719a, 720, 726 ,
727, 738 , 740, 748, 778a, 778b, 787b, 798b, 799a, 818a, 822b

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

map subgraph: 748, 749a


mask block: 50a, 50b, 55, 56, 58d, 59, 60a
mask seg: 50a, 55, 56, 59, 60a
mask seg plus block: 48 , 50b, 55, 56, 59, 60a
Matrix Graph: 758b, 760
Max Bits Allowed: 47, 49b, 57b
max dim: 51a, 51 , 55, 56, 59, 60a, 61b, 63, 66b
Max Dim Allowed: 47, 55, 56, 57b
Max Pow Block: 47, 57a, 57b
merge: 209, 211
mergesort: 209, 213a, 689d
MetaDlistNode: 109, 110a, 111a
min index: 447a, 447
modulus by block size: 58d, 58d
modulus from index in dir: 50b
move: 9b
next2Pow: 57a, 58a
Node Arc Iterator: 656a, 656b, 656 , 661 , 681a, 698b, 702, 705a, 705b, 706, 709b, 710,

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

Path Desc: 691f, 692a, 692d, 693e, 694b


pos in block: 67a, 67b, 68a, 68b
pos in dir: 61a, 61 , 62, 67a, 67b
pos in seg: 61a, 61 , 62, 67a, 67b, 69
postOrderRec: 300
POT: 796d, 797 , 799a
pow block: 48a, 49a, 50b, 51a, 55, 56, 57a, 58d, 59, 60a
pow dir: 48a, 49a, 51a, 55, 56, 59, 60a
pow seg: 48a, 49a, 51a, 55, 56, 59, 60a
precedence: 140e, 143e
pred: 813 , 814 , 815b, 818b
preOrderRec: 300
preOrderStack: 302
preOrderStack aux: 301
preorder to binary search tree: 383
Prim Heap Info: 785 , 786e
Prim Info: 785a, 785b, 785d, 786b
prim min spanning tree: 780
PRIO: 561, 563, 564a, 565b
push in splay stack: 613a, 613b, 615, 616b
put arc: 783, 787a, 787b, 797 , 799a
put in queue: 817, 818a, 818b
q bellman ford min spanning tree: 812b
quicksort: 221, 222a, 222 , 757 , 760
RandNode: 551a, 551d
RandNodeVtl: 551a, 551d
random insert: 552a, 553a
random join: 553b
random remove: 554b, 555
random search: 226a
random select: 227, 228a, 229
Rand Tree: 551d
Rand Tree Vtl: 551d
RbNode: 591, 592a
Rb Tree: 592a
Rb Tree Vtl: 592a
read input: 434d
read matrix: 756a, 756b
record to bucket: 487, 498, 503a, 503b
ref seg: 67a, 67b, 69b, 69
release all segments and blocks: 54 , 64
release block: 54b, 54 , 65d
release blocks and segment: 54
release segment: 54b, 54 , 62, 65e, 69
remove arc: 659a, 684, 685a, 776b
remove by key xt: 417

853

remove
remove
remove
remove
remove
remove
remove
remove

by pos xt: 418


entry from hash table: 535a, 537a
entry from lru list: 533 , 536b
from search binary tree: 397a, 397b
in sorted array: 34b
in unsorted array: 34a
last: 367a
next: 81 , 84e, 86e, 89d, 97b, 98, 99a, 106 , 119a, 136a, 165e, 204a, 212, 222a,

222b, 226b, 228b, 228

remove node: 653b, 685a, 685b


remove prev: 97b, 99a, 106 , 119a
reserve: 63
reset arc: 669b
reset arcs: 670 , 718b, 726 , 737a
reset bit arcs: 666 , 697b, 702, 705a, 707, 708, 709b, 713, 715, 720, 815a
reset bit nodes: 666 , 697b, 702, 705a, 707, 708, 709b, 713, 715, 720, 778
reset cookie arcs: 668b
reset cookie nodes: 668b
reset counter arcs: 667b, 747
reset counter nodes: 667b, 747
reset node: 669b
reset nodes: 670a, 718b, 726
resize: 43b, 438a, 499, 500, 537b
restore avl: 575 , 577b, 582
restore avl after deletion: 578a, 582
restore avl after insertion: 572a, 576
reverse list: 98
r huffman node: 429a, 429b, 429 , 432a
right link: 324b, 326a, 331a
r index: 349e
RLINK: 296, 299a, 300, 301, 302, 303, 304b, 305a, 305b, 306, 307a, 307b, 308, 310a, 311a,

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

split key rec: 393, 394, 398


split key rec xt: 413, 414
split list: 99a, 116a, 213a
split pos rec: 415, 416a
str to token: 141a, 143d
sum p: 447a, 447b
Symbol Map: 424a, 426a
symbol map: 426a, 431b, 433, 435, 436a
Termino: 122e, 123a, 123b, 123 , 123d, 124a, 124b, 125b, 126a, 126b, 127b, 127 , 128
test connectivity: 701, 778 , 780
test cycle: 662, 663, 664a, 664b, 705a
test cycle: 704, 705a, 705b
test end: 434b, 434 , 436a
test path: 662, 663, 664a, 664b, 709b
test path: 709a, 709b, 710
Token Type: 139a, 139b, 143a
total cost: 775b
touch: 62, 507b, 509a, 510b, 760, 765b
Treap: 562d
TreapNode: 561, 562d
Treap Vtl: 424a, 426b, 562d
TREE: 446b, 446 , 446e, 447a, 448
TREEARC: 798b
Tree Node: 322, 324a, 324b, 325 , 326a, 326b, 327a, 327b, 328a, 328b, 329, 330, 331a,

331b, 722b, 723a, 723b, 724

tree postorder traversal: 332b


tree postorder traversal: 332b
tree preorder traversal: 332a
tree preorder traversal: 331 , 332a
two raised: 49b, 55, 56
u index: 349
ULINK: 364a
Uninit Prim Info: 786b, 786
update freq: 434d, 435
upper link: 324b, 327b
verify graphs: 687b
void to arc: 678a, 685a
zig type: 614b, 615
zig zag left: 611, 615
zig zag right: 611, 615
zig zig left: 610, 615
zig zig right: 610, 615

Вам также может понравиться