0% encontró este documento útil (0 votos)
155 vistas232 páginas

Orientación A Objetos - Compressed PDF

Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
155 vistas232 páginas

Orientación A Objetos - Compressed PDF

Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 232

OBJETIVOS

• Entender en qué consiste el proceso de abstracción.


• Describir la técnica de la abstracción de datos para el diseño de
estructuras de datos.
• Comprender qué es una estructura de datos.
• Entender el diseño de una estructura de datos a través de una
especificación lógica.
• Ubicar los niveles de abstracción al programar.
• Identificar los beneficios de utilizar la abstracción de datos como
estrategia de trabajo en el desarrollo de software.

e
Abstracción de datos

¿Qué es una abstracción?

En términos simples, una abstracción es un proceso mental, mediante el


cual se extraen los rasgos esenciales de algo para representarlos por
medio de un lenguaje gráfico o escrito. Puesto que es un proceso
mental, la abstracción es una acción subjetiva y creativa, esto es, de-
• pende del contexto psicológico de la persona que la realiza.
Por ejemplo, se dice que algunas de las pinturas de Picasso son abs­
tractas, no porque sean difíciles de entender, sino porque a través de
ciertos trazos simples, y de acuerdo con su criterio mental, el pintor
plasmó los rasgos esenciales de ese algo real que trataba de represen- /' >**""*
tar por medio de un lenguaje gráfico. / /
En la vida cotidiana, continuamente se hacen abstracciones, por / /
ejemplo cuando describimos a una persona: "Reconocerán al profe- / /
sor Garay por su enorme nariz, su barba y sus anteojos; además, es /
muy alto, y tiene un carácter muy amigable". Cuando hacemos un /
mapa para describir cómo se llega al rancho donde será el día de
campo, extraemos los rasgos importantes del camino: "Al llegar al \
cruce donde está una piedra gigantesca, das vuelta a la derecha y verás \
un árbol lleno de flores rojas; ahí encontrarás la entrada al rancho.'1

¿Por qué es importante la abstracción? \ X.

Aunque la gente realiza cotidianamente el proceso de abstracción, debe


convertirse en una habilidad para quien estudie una carrera relaciona­
da con la computación. La capacidad de modelar una realidad por
medio de herramientas computacionales requiere necesariamente de
hacer continuas abstracciones, por lo que es vital conocer metodologías
que desarrollen esta habilidad.

¿Qué es la abstracción de datos?

La abstracción de datos es una técnica o metodología que permite


diseñar estructuras de datos. Consiste, básicamente, en representar
bajo ciertos lincamientos de formato las características esenciales de
una estructura de datos. Este proceso de diseño se olvida de los detalles
específicos de implementación de los datos razón por la cual se trata
de una abstracción. ,

#
8
¿En que consiste la especificación lógica de un TDfl?

¿Qué es una estructura de datos?


Cualquier colección o grupo de datos organizados de tal forma que ten­
gan asociados un conjunto de operaciones para poder manipularlos, se
dice que conforma una estructura de datos.
Por ejemplo, cualquier lenguaje de alto nivel provee típicamente de
tipos de datos estructurados o estructuras de datos predefinidas, como
los arreglos o los registros. Un arreglo es un conjunto de datos, todos
"•.»„, del mismo tipo, con una organización lineal y con métodos claros de
* \ acceso a través de sus subíndices. Las operaciones tradicionales sobre
"^\ *\ los arreglos incluyen la comparación, la asignación, la escritura, etc.
^\ En un nivel más bajo, podría verse a los números enteros como estruc-
\ \ turas de datos: se comr>onen de un enmo de dígitos y tienen asociadas
\ \ operaciones como sumar, restar y multiplicar, entre otras.

¿Qué es un Tipo de Dato Abstracto (TDA)?


i

/ La técnica de la abstracción de datos establece que al diseñar una


nueva estructura de datos, ésta pasa a ser un Tipo de Dato Abstracto
(TDA), que podrá implementarse en cualquier lenguaje y aplicarse en
cualquier concepto.
Por lo tanto, al usar la metodología de la abstracción de datos se
diseñarán TDA, siguiendo los lincamientos para hacer una especifica­
ción lógica o abstracta del mismo TDA.

¿En qué consiste la especificación lógica de un TDA?

La especificación lógica de un TDA es un documento en el que se •


plasma la abstracción realizada al diseñar una estructura de datos.
Dicho documento pasará a ser el mapa o plano mediante el cual se
construirá (implementará) la estructura de datos y en el que se defini­
rán claramente las reglas en las que podrá usarse (aplicarse) el TDA.
Aunque existe una fundamentación matemática para esta técnica,
no es el objetivo de este libro profundizar en ella, por lo que se utilizará
el lenguaje natural para describir la especificación de un TDA.
El documento de la especificación lógica de un TDA consiste de los
siguientes cuatro puntos:

. @
Abstracción de datos

1. Elementos que conformarán la estructura de datos.


En este punto se describe el tipo de los datos individuales que guar­
dará la estructura. Por ejemplo, números enteros, caracteres, fechas,
registros con los datos de un empleado, etcétera.

2. Tipo de organización en que se guardarán los elementos.


» Existen solamente cuatro tipos de organización para los datos en la
estructura, la cual deberá tener alguna de las siguientes organizaciones:
• Lineal: Si hay una relación de uno a uno entre los elementos.
• Jerárquica: Si hay una relación de uno a muchos entre los ,.--'"''
elementos. / ^~~~
• Red: Si hay una relación de muchos a muchos entre los elementos.
• Sin relación: Si no hay relaciones entre los elementos. /
i /
i /

Las relaciones entre los elementos se dan, por ejemplo, cuando existe /
algún motivo para que un elemento esté antes o después que otro í /
(figura 1.1).

f Estructuras Estructura Estructura Estructura y, \


lineales jerárquica de red sin relaciones \ \

¿ <Í
A\ S$P* Vo ° 7
*, Relación de uno a uno Relación de uno a varios Relación de muchos a muchos No hay relación J

Figura 1.1, Tipos de estructuras u organizaciones de datos.

3. Dominio de la estructura.
Este punto es opcional, y en él se describirá la capacidad de la es­
tructura en cuanto al rango posible de datos por guardar.

4. Descripción de las operaciones de la estructura,


Cada operación relacionada con la estructura debe describirse con
los siguientes puntos:
•Nombre de la operación.
• Descripción breve de su utilidad.
• Datos de entrada a la operación.

Q •
Ejemplo

. Datos que genera como salida la operación.


• Precondicion: Condición que deberá cumplirse antes de utilizar la
operación para que se realice sin problemas.
. Postcondición Condición en que queda el TDA después de ejecutar
la operación.

¿£ Ejemplo

" ^ \ "X ^ í ESPECIFICACIÓN LÓGICA DEL TIPO DE DATO ABSTRACTO


>v \ (TDA) CADENA

\ \ Elementos: todos los caracteres alfabéticos (letras mayúsculas y mi-


\ \ núsculas), caracteres numéricos y caracteres especiales.

'i Estructura: hay unarelaciónlineal entre los caracteres.

/ / Dominio: existen entre 0 y 80 caracteres en cada valor del TDA


/ i CADENA. El dominio serán todas aquellas secuencias de caracteres
/ / que cumplan con las reglas.
J^ / OPERACIONES

BORRAINICIO
..-"'*' UTILIDAD: Sirve para eliminar el primer carácter de una cadena.
ENTRADA: Cadena S sobre la que se desea eliminar el primer
carácter.
SALIDA: El carácter más a la izquierda de la cadena S y la
cadena S modificada.
PRECONDICION: La cantidad de caracteres es mayor que cero. •
POSTCONDICIÓN: La cadena S tiene todos los caracteres, menos el
primero.

AGREGAFINAL
UTILIDAD: Sirve para agregar un carácter al final de una cadena.
ENTRADA: Cadena S y el carácter L, que se añadirá a la
cadena S.
SALIDA: Cadena S modificada.
PRECONDICION: La cantidad de caracteres en S es menor que 80.
POSTCONDICIÓN: La cadena S tiene el carácter L que queda al
extremo derecho de la cadena.

I )
• —Q
Abstraccton de datos

( >
VACÍA
UTILIDAD: Sirve para verificar si una cadena está vacía o no.
ENTRADA: Cadena S que se verificará.
SALIDA: VERDADERO si la cadena S no tiene caracteres,
FALSO en caso contrario.
PRECONDICIÓN: Ninguna
• POSTCONDICIÓN: Ninguna (pues la cadena S no se modifica).

LLENA ..--'"
UTILIDAD: Sirve para verificar si una cadena está llena o no.
ENTRADA: cadena S que será verificada. y ^
SALIDA: VERDADERO si la cadena S contiene ya 80 /
caracteres, FALSO en caso contrario. / /
PRECONDICIÓN: Ninguna / /
POSTCONDICIÓN: Ninguna (pues la cadena S no se modifica).

INVIERTE
UTILIDAD: Sirve para invertir el orden de los caracteres en una
cadena. \
ENTRADA: Cadena S a la que se desea invertir el orden de los
caracteres. \ \
SALIDA: Cadena S modificada. \
PRECONDICIÓN: Ninguna
POSTCONDICIÓN: La secuencia de caracteres en la cadena S se \ \ .
invierte, de forma que el primer carácter toma el
lugar del último, el segundo el del penúltimo y así \ .
sucesivamente.
V J

Definir claramente la especificación lógica del TDA facilita posterior­


mente la construcción y aplicación de la estructura de datos.

¿Cuáles son los niveles de abstracción de datos?

Al aplicar la abstracción de datos se pueden definir tres niveles de


trabajo:

1. El nivel lógico o abstracto, que correspondería a la especificación


lógica del TDA que se describió anteriormente. En este nivel se define
abstractamente la estructura de datos y las operaciones relaciona­
das con ella. La descripción que se obtenga en este nivel debe ser

Q •
¿Qué es la independencia de datos y el ocultamiento de información?

independiente del lenguaje de programación en el que se implemen-


tará o usará la estructura.
2. El nivel físico o de implementación. En este nivel se decide el len­
guaje de programación en que se implementará la estructura, qué
üpos de datos ya definidos servirán para representarla y, finalmente,
bajo estas consideraciones, se implementa como un módulo a cada
una de las operaciones del TDA. Este nivel toma el diseño que se ha
realizado a nivel lógico y, siguiendo al pie de la letra las especifica-
—-.. ciones de cada operación, construye la estructura que posteriormente
**\ se usará en el nivel aplicación.
""^^ \ 3. En el nivel aplicación o de uso el programador usará el TDA para
N. \ resolver determinada aplicación. El uso del TDA se limita a llamar
\ \ las operaciones sobre la estructura que se requiera cuidando siem-
\ \ pre de cumplir con las reglas de cada operación especificadas en el
\ \ nivel lógico.

¿Qué es la independencia de datos y el ocultamiento


de información?

/ / De lo descrito en la sección anterior se observa que hay una indepen-


jS / dencia bien marcada entre el nivel físico y el nivel de aplicación del
+#* y' TDA, con el nivel lógico como intermediario (figura 1.2). Esto signi-
,..--***' fica que quien implementa el TDA no debe estar influenciado por la
aplicación que tendrá la estructura y quien use la estructura no tiene
porqué saber cómo se implementaron sus operaciones.
Entonces, se dice que la forma en que se almacenan los datos en la
estructura es independiente de su aplicación y que para el usuario pro­
gramador permanece oculto cómo se implementaron las operaciones
del TDA. Esto, sin lugar a dudas, simplifica la labor del usuario del
TDA, pues se olvida de detalles de programación al basar su trabajo
sólo del diseño lógico del propio TDA. Adicionalmente, el implementa-
dor del TDA, podrá hacer cambios o mejoras a su implementación, res­
petando la especificación lógica y sin afectar en lo más mínimo las
aplicaciones desarrolladas.

• 0
Abstracción de datos

¿Cómo distinguir los niveles de abstracción?

Una analogía podría hacerse al comparar este proceso con el que se rea­
liza al construir una casa (figura 1.2). Primero se trabaja en el nivel lógi­
co al pedirle a un arquitecto que diseñe el plano; el arquitecto hace una
• abstracción y plasma en el papel los rasgos principales de la casa por
construir. Además, especifica claramente medidas y condiciones de cons­
trucción y de uso. Una vez aprobado el diseño, se procede en el nivel
físico, que correspondería a la construcción. Los albañiles seguirán
paso a paso las especificaciones dadas en el plano (diseño en nivel lógi­
co) y no tienen por que preguntar para ciué se utilizará cada uno de los -'' /
espacios construidos. Además, utilizarán el material más apropiado /
para cada situación. Una vez que la construcción esté terminada, He- /
gará su dueño para habitarla dándole el uso correspondiente a cada ■ /
espacio (nivel aplicación) El dueño al utilizar la casa no necesita sa- / /
ber cómo se hizo, con qué materiales o quiénes la construyeron- sin I
embargo, le será útil conocer algunas medidas o condicionen de
construcción que se representan en el plano (nivel lógico) i \
» \

f l I \ \ \
Nivel físico -*-■ Nivel lógico Nivel de aplicación V \ ^

v"^^" 1 —h J
Figura 1.2. Relación entre los niveles de abstracción.

De la misma forma, cuando se desea aplicar la abstracción de datos


en el desarrollo de software que requiere de una estructura de datos, se
debe comenzar con el diseño a nivel lógico del TDA, continuar con su
implementación y finalmente con su uso en el desarrollo de la aplicación
correspondiente En este proceso podrán intervenir tres personas: el di­
señador del TDA, un programador a nivel físico y un programador a
nivel aplicación. Cuando el mismo programador trabaje en los niveles

ñ •
Ejemplo

físico y de aplicación, debe tener en cuenta la regla de no invadir los ni­


veles, respetando el nivel lógico que es intermediario.

¿Qué ventajas ofrece utilizar la técnica de abstracción


de datos?
Seguir la regla de los tres niveles de abstracción redunda en un mejor
"^X, desarrollo de software. La técnica obliga a diseñar modularmente y,
^ *X como consecuencia, se tiene una implementación más clara, documen-
"\. \ tada y es fácil darle mantenimiento. Adicionalmente, gracias a la inde-
\ \ pendencia de datos y al ocultamiento de información, se pueden crear
\ \ paquetes como unidades de software reutilizable, con lo que se obtienen
\ \ estructuras de datos genéricas.
\ \ Quien utiliza un TDA se limita a llamar las operaciones, cuidando
i solamente de cumplir con las especificaciones del diseño lógico, lo
que facilita y hace más rápido el desarrollo de aplicaciones, pues no es
/ / necesario manejar los detalles físicos de la estructura.
/ i La abstracción de datos es uno de los principios que fundamentan
/ / la programación orientada a objetos, y se describirá en el siguiente
/ / capítulo.
f r
y t

"""'% Ejemplo

r
En cierta aplicación científica se requiere calcular el factorial de un 1
número entero positivo. La función factorial aplicada sobre un núme­
ro es la multiplicación de todos los números, desde el 1 hasta el valor •
del número correspondiente ( n! = 1 * 2 * 3 * ... * (n-1) * n ).

Solución obvia en C+ + :

int factorial = 1;
for(intj = n;j>0;j-)
factorial = factorial* j ;

Sin embargo, es importante notar que:

• El factorial de un número crece en forma exponencial:


1! = I, 2! = 2, 3! = 6. 4! = 24, 5! = 120, 6! = 720, 7! = 5040, 8! = 40320 ...
V J
• 0
Rbstraccion de datos

*• La capacidad de los tipos de dato enteros en C++ está limitada. i


El tipo unsigned long, en cuatro bytes, soporta como valor máximo
4,294,967,295 lo cual permitiría obtener sólo hasta el factorial de 12.
Por lo tanto, se requiere de un tipo de dato con una capacidad para al­
macenar a cualquier número entero, sin importar qué tan grande sea;
• ya que C++ no provee de un tipo estándar para este caso, se puede di­
señar un TDA para utilizar números enteros grandes, el cual a su vez
l será una estructura de datos. j

* y
* s

íg^ Diseño del TDA numeróte (nivel lógico) /

ESPECIFICACIÓN LÓGICA: \ I
- Elementos: un numeróte se compone de dígitos.
-Tipo de organización: los dígitos se organizan de manera lineal. \
- Dominio: se pretende que un numeróte pueda contener cualquier \
cantidad de dígitos bajo cualquier combinación. Sin embargo, se \
puede limitar a una capacidad máxima de mil dígitos. \
- Operaciones: las mismas que posee el tipo de dato entero.

OPERACIONES

SUMA
UTILIDAD: sirve para sumar dos numeróles.
ENTRADAS: dos numerotes
SALIDAS: un numeróte que guarda la suma de los dos
numerotes de entrada.
PRECONDICIÓN: Ninguna
POSTCONDICIÓN: El numeróte de salida contiene la suma
aritmética de los dos numerotes de entrada.

DESPLIEGA
UTILIDAD: Sirve para desplegar en pantalla un
numeróte.
ENTRADAS: Numeróte a desplegar.
SALIDAS: Ninguna (observa en pantalla)
PRECONDICIÓN: Ninguna
POSTCONDICIÓN: Ninguna

V )

Q •
Ejercicios

^Con base en este diseño lógico, se puede pasar a la implementación d e n


TDA (nivel físico) para posteriormente utilizarlo (nivel de aplicación)
y resolver el problema factorial eficientemente.
Este ejemplo será retomado en el siguiente capítulo para analizar
ylos niveles físico y de aplicación. j

CONCLUSIONES
\ , En la evolución del desarrollo de software, se han descubierto una gran
""**\ \ cantidad de estructuras de datos, con propósitos firmes y claros, que no
\^ \ pierden actualidad y forman parte de las bases para cualquier futuro
\ \ desarrollador de software. El propósito de este material, es mostrar ca-
\ \ da una de estas estructuras en sus tres niveles de abstracción, por lo que
\ \ los capítulos posteriores, se vuelven el mejor ejemplo de las ideas mos-
\ \ tradas en este capítulo. Deben ser de especial interés los capítulos de
stnngs, pilas y filas.
i : Sin embargo, la metodología aquí presentada puede ser utilizada
/ / para el diseño de nuevas estructuras de acuerdo con las necesidades de
I j aplicaciones y ajustando el punto de referencia de los niveles de abs-
/ / tracción. Además, la comprensión de estas ideas facilitará el entendi-
/ / miento de la filosofía de la programación orientada a objetos.

i ( EJERCICIOS ) &
V s
m
1. Defina los siguientes conceptos:

* Abstracción
* Abstracción de datos
* Especificación lógica
* Nivel de abstracción
* Independencia de niveles
* Precondición
* Postcondición
* Módulo
* Abstracción modular

• 0I
Rbstracción de datos

2. Describa situaciones de la vida cotidiana en las que se realicen abs­


tracciones.

3. Recuerde cómo comprendió el concepto de una matriz (arreglo bi-


dimensional) en un lenguaje de programación. Si se considera la
matriz como un TDA, defina su especificación lógica y comente las
• características de su nivel físico y algunas de sus posibles aplica­
ciones.

4. Explique cómo es que un registro en Pascal (o struct en C), cumple


con las características de una estructura de datos. /* ^ ^ ~
t f

5. El tipo de dato string, puede verse como un tipo de dato abstracto / /


(TDA), para el cual pudiéramos diseñar su especificación lógica. /
Sin embargo, el propio lenguaje C o C++, provee este tipo de dato /
a través de las bibliotecas strtng.h y/o cstring.h. \

a) ¿Cuál es la principal diferencia entre el manejo de los strings como


objetos y el manejo de los strings como arreglos? Explique breve- \ \
mente las diferencias e indique en qué nivel de abstracción se \ \
encuentran esas diferencias. Justifique su respuesta. \ \
b) Las siguientes tres operaciones típicas se pueden realizar sobre un \
TDA string: \ \ .

Copiar un string en otro (o asignar un string a otro). ^ X


Concatenar o unir dos strings.
Comparar dos strings verificando si son iguales o diferentes.

• Describa cada operación bajo el formato de la especificación ló­


gica de una operación, según el funcionamiento de las funciones
predefinidas que provee el lenguaje para los strings como arre­
glos. Indique, además, el nombre de la función.
• Analice si las funciones predefinidas en el lenguaje, correspon­
dientes a estas operaciones para los strings como objetos, tienen
alguna diferencia con respecto a la especificación lógica que
obtuvo en el punto anterior. En caso de que haya alguna dife­
rencia, explíquela indicando claramente en qué operación y cuál es
el cambio en la especificación lógica. Indique, además, cuál es el
nombre de la función (método).
c) A continuación se muestran tres secciones de código en lenguaje
C++, que al ejecutarse realizan exactamente la misma función:

0 •
flutoeualuación

CÓDIG01 CÓDIGO 2 CÓDIGO 3


«include <string.h> char cadenal[501 cadena2[50]; #include<cstríng.h>
charcadañal[50], cadena2[50]; string cadenal, cadena2;
for (intj = 0; cadenal® .'= W j + + )
strcpy(cadena2, cadenal); cadena2¡S¡ = cadena1{¡}; cadena2 = cadañal;
cadena2[¡] = W;

^ ^ \ • Identifique cuál es la función que se está realizando con estos códigos.


>. • Clasifique los códigos de mayor a menor abstracción.
\ \ • Identifique en qué nivel de abstracción se está trabajando en cada
\ \ uno de los códigos.

\ \ 6. Diseñe las operaciones aritméticas y relaciónales para el TDA nu­


meróte descrito en este capítulo.

/ t

/ i

-V r— ñuTOEUñLuncióNC\ f
■ i
i.— — _ . - _ — — — -- — — — - . _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ j

Conteste: verdadero o falso

1. La abstracción de datos es una metodología para diseñar sistemas


computacionales. __ .

2. La especificación lógica de un TDA determina qué tipo de herra­


mientas de un lenguaje computacional se utilizarán para implemen-
tar el TDA.

3. Si una operación de un TDA no tiene precondiciones, significa


que quien use la operación en el nivel de aplicación no tendrá que
preocuparse por hacer validaciones especiales, antes de llamar a la
operación, para que ésta funcione correctamente.

• 0
Abstracción de datos

4. La postcondición en la definición lógica de una operación de un


TDA, le dará información a quien la implementa en el nivel físi­
co de lo que debe codificar.. . .

5. La implementación de un TDA puede modificarse sin afectar los


programas de aplicación que ya se hayan implementado.

t /
t /
t /

v \
*i \\
t \

Q •
Capítulo J ~

OtlElUPDÍ i OWIOS I

OBJETIVOS
• Entender los principios básicos de la programación orientada a
objetos.
• Repasar y comprender la terminología de la programación orientada
a objetos: objeto, clase, mensaje, método, herencia, polimorfismo,
encapsulamiento.
• Identificar las ventajas y desventajas de la programación orientada
a objetos.
• Relacionar los conceptos de la abstracción de datos con los de la
programación orientada a objetos.
• Ejemplificar la implementación del nivel físico de un TDA utilizan­
do la programación orientada a objetos en el lenguaje de
programación C++.

0 I
Programación orientada a objetos

¿Qué es la programación orientada a objetos?

El término de programación orientada a objetos se puso de moda en la


década de 1990, aunque sus orígenes se remontan a los años 70 con los
lenguajes Simula 7 y Smalltalk. A grandes rasgos, la programación
orientada a objetos podría definirse como una filosofía para programar
• encapsulando datos y código para formar objetos, que interactúan para
obtener los resultados esperados.
A diferencia de la programación tradicional (procedural), en la pro­
gramación orientada a objetos sólo se puede accesar a los datos que
conforman un objeto a través del código asociado al propio objeto. yS*
Asimismo, la programación orientada a objetos posee los mecanismos
de herencia y polimorfismo como características que dan mayor poder
y flexibilidad a la programación. / /
i i
t I
* í
i /
i

¿Qué es un objeto?
t
* V
* \
En la programación orientada a objetos, un objeto es un paquete que \ \
contiene datos y el código en forma de subrutinas que operarán sobre
los datos del propio objeto. Los datos que guarda un objeto se llaman \
atributos del objeto, mientras que a las subrutinas que operan sobre
los datos se les conoce como métodos. Los objetos de un programa
se comunican enviándose mensajes, y así se generan los resultados ^--„
correspondientes.
Para los fines de este libro, hay una relación muy estrecha entre la
abstracción de datos y la programación orientada a objetos, por lo que
cualquier estructura de datos, vista como un TDA, se considera como
un objeto en la programación orientada a objetos. La especificación
lógica de un TDA determina claramente cuáles son las operaciones váli­
das en la estructura de datos, por lo que de ahí se obtendrán directa­
mente los métodos del objeto; los atributos del objeto se determinarán
con base en la representación física que se le quiera dar al TDA.

0 •
¿Que son la herencia y el polimorfismo?

¿Qué ventajas ofrece implementar las estructuras de


datos con la programación orientada a objetos?

Puesto que la programación orientada a objetos se apoya en la idea


de la abstracción de datos, están implícitos los beneficios de la técnica
que se mencionó en el capítulo anterior. Adicionalmente, se obliga al
programador a separar claramente la construcción a nivel físico de la
estructura de datos de la aplicación que se le dará. Las estructuras de
N ^ datos implementadas como objetos pueden guardarse como unidades
s. \ de software que pueden emplearse en diferentes aplicaciones. Además,
\ \ los mecanismos de herencia y polimorfismo permiten generar nuevas
\ \ estructuras de las ya existentes.
\ i
\ i

¿Qué son la herencia y el polimorfismo?

La herencia es el proceso mediante el cual un objeto se define adqui-


/ / riendo las propiedades de otro, es decir, hereda los atributos y méto-
/ / dos de un objeto superior. Por ejemplo, el objeto león hereda todas las
/ / propiedades del objeto mamífero, que a su vez hereda las propiedades
/ del objeto vertebrado, que su vez hereda los atributos del objeto am-
Jr / mal. Si no existiera el mecanismo de herencia, cada objeto tendría que
definir sus características; al haber herencia, cada objeto define sólo
..--'**' los atributos adicionales y específicos, y no los comunes al objeto de
quien hereda.
El polimorfismo es un mecanismo mediante el cual se puede lo­
grar que una misma operación se realice de diferentes formas, según
los objetos sobre los que se aplica. Por ejemplo, el símbolo +, tradi-
cionalmente usado para sumar dos números, puede redefinirse por
polimorfismo para que sirva también para concatenar dos strings.
Aunque estas ideas son muy importantes dentro de la filosofía de
la programación orientada a objetos, se mencionarán sólo dentro de las
estructuras de datos que lo justifiquen.

• Q
Programación orientada a objetos

¿Qué es una clase?

La mayoría de los lenguajes de.programación orientada a objetos utili­


zan el término clase para describir a los objetos de un mismo tipo. Nor­
malmente, la definición de una clase es la declaración de un tipo de
9 dato que involucra la especificación de los atributos y los métodos
de un objeto. Cuando se declare una variable del tipo de dato de la
clase, se creará un objeto o una instancia de objeto que ya contiene
físicamente a los atributos y métodos asociados.

¿Cómo se usan los objetos? /


i f

Los objetos sólo pueden utilizarse enviándoles mensajes para que reac- /
cionen con alguna acción que repercuta en el estado de un programa.
Puesto que los datos que contiene un objeto sólo pueden utilizarse a
través de los métodos del mismo, los mensajes en sí son los nombres
de los métodos asociados. \
El envío de mensajes a los objetos y entre los objetos es lo que dará
al final el resultado esperado de un programa. \
% \_
* x

H Ejemplo \

f?x\ un sistema bancario, las cuentas de cheques de los clientes pueden\


considerarse objetos. Todos ellos pertenecerían a una clase llamada
CUENTA_DE_CHEQUES que define, para cada instancia, los siguien­
tes datos:

- Número de la cuenta
- Nombre del cliente
- Sucursal donde se abrió la cuenta
l - Saldo de la cuenta J

0 •
Ejemplo

Entre los métodos que pudieran asociarse a los objetos de la clase


CUENTA_DE_CHEQUES pueden mencionarse:

• ALTA_DE_CUENTA. Este método se ejecutaría al crear un objeto


de la clase CUENTA_DE_CHEQUES, e implica solicitar de teclado
los datos correspondientes para guardarlos en los atributos del
objeto y colocar el valor de cero en el saldo actual.

• DEPOSITO. Este método agrega la cantidad especificada al saldo


de la cuenta.

• RETIRO. Este método quita al saldo de la cuenta la cantidad espe-


cificada, validando que sea posible retirar esa cantidad.

• MUESTRA_SALDO. Este método muestra en pantalla el saldo de


la cuenta correspondiente.

Dada la definición anterior, los objetos de tipo CUENTA_DE_CHE-


QUES, responderán a mensajes con los nombres: ALTA_DE_CUEN-
TA, DEPÓSITO, RETIRO, MUESTRA_SALDO, que corresponden
a los nombres de los métodos. Los objetos responderán a los mensa-
jes, ejecutando el código asociado a los métodos correspondientes
(figura 2.1).

r~ Clase: Cuenta de cheques


Atributos
Métodos
Nombre del cliente Alta
Número de cuenta Depósito
Sucursal Retiro
Saldo disponible Muestra

Instancia de la clase

Mensajes Objeto: MLcuenta


Contiene a los atributos definidos B o bj e t o responderá
enlaciase. 1 a |os m e n s a j e s e ; e c u t a n [ j D
La ejecución de los métodos afee- / ...
taré sus atributos. / los métodos correspondientes.

Figura 2.1. Relación entre los términos CLASE, OBJETO, MENSAJE


y MÉTODO.

fl
Programación orientada a objetos

Aunque en la actualidad existen diversos lenguajes orientados a obje­


tos, algunos más puros que otros, este material utilizará objetos para
representar estructuras de datos en el lenguaje C++.
Dadas las características de este material, se deja al estudiante la pro-
fundización en el estudio de este tema y de los lenguajes. Consulte la
bibliografía sugerida.

Sintaxis para utilizar objetos en C++


Definición de clases: .-

class nombre /
{ II Lista de atributos, con la sintaxis de la
II declaración tradicional de variables en C /

// Lista de métodos, usando la sintaxis de la


II declaración tradicional defunciones en C
h \ \
Definición de objetos: \ \
v \

--> Declarar variables del tipo de la clase. \

Envío de mensajes:

variable_objeto . nombre _del_mensaje

Ü Ejemplo
^¿Recuerda la necesidad del TDA numeróte en el ejemplo del cálculo^
del factorial en el capítulo anterior? Ahora procederemos a analizar
cómo podría ser su implementación en el nivel físico.

¿Cómo se puede representar en memoria el TDA numeróte!


Una primera idea, fácil de conceptualizar, es utilizar un arreglo que
sirva para guardar mil dígitos (en forma de caracteres). Para poder
controlar cuántos de estos mil espacios del arreglo realmente está
empleando un numeróte, se requiere un indicador de la cantidad de dí-
Eritos que contiene el numeróte Éstos pueden imolementarse como los
j a m b a o s de un objeto que representari- un nunemte. )

Q •
Ejemplo

(m TDA numeróte en lenguaje C++ se programaría así: ^

class Numeróte
{ prívate:
char digitos[1000];
int cantidad;
public:
II Métodos que programan las operaciones
" \ >;

*X. \ Es muy importante notar que el nombre de la clase es un nuevo tipo de


\ \ dato.

\ \ ¿Cómo se implementarán las operaciones?


\ i
V i

\ \ El diseño lógico determina la abstracción modular para cada una de las


j operaciones. Sin embargo, dado que el TDA numeróte es un objeto,
I ¡ conviene programar las operaciones como un método de la clase
/ i (función miembro). Además, puesto que un objeto numeróte está invo-
/ / lucrado en la implementación de la operación, no será necesario mane-
/ / jarlo como parámetro.
y / La programación de la operación despliega, sena:

/ void Numeróte::despliega()
--"'*** {for(intj = 0; j<cantidad; j++)
cout« dígitosljj;
}

Es importante notar cómo la función no requiere el parámetro del •


numeróte, pues al ser un método de la clase, se trabaja automática­
mente sobre los atributos del objeto involucrado.
La operación suma no representa estrictamente un comportamiento de
los numeróles, sino que puede verse como una aplicación, por lo que
puede implementarse como una función libre y para facilitar su im­
plementación se puede hacer amiga de la clase:

friend Numeróte suma (Numeróte NI, Numeróte N2);

Por otro lado, utilizando las características del lenguaje C++, se puede
aprovechar el concepto de sobrecarga de operadores para dar un nom­
b r e más práctico a las operaciones: j

. Q
Programación orientada a objetos

f friendNumeróte operator+ (Numeróte NI, Numeróte N2); \


friend ostream& operator« (ostream &os, Numeróte N);

De esta manera, la aplicación final del TDA numeróte podría imple-


mentarse así:

Numeróte factorial;
factorial = 1;
for (int j = n; j > 0; j~)
factorial = factorial */;

Este código supone que se tienen definidas las operaciones de asigna­


ción y multiplicación de numeróles y enteros. /
Es importante observar que la implementación de la aplicación es
independiente de la forma en que se implemento el nivel físico del
y TDA numeróte. ¡

( EJERCICIOS ) & A

1. Defina los siguientes conceptos:


• Clase
• Objeto
• Método
• Mensaje
• Polimorfismo
• Herencia
• Sobrecarga de operador

2. Implemente el objeto CUENTA_BANCARIAen lenguaje C++. En­


víe mensajes para depositar y retirar dinero, mostrando el estado de
cuenta después de cada operación.

3. Tome la especificación lógica del TDA CADENA del capítulo ante­


rior e implemente el TDA como un objeto en cualquier lenguaje.

g «
Ejercicios

4. A continuación se muestra la declaración de una clase en lenguaje


C++, que es útil para representar a un punto del plano coordenado:

cíes* Punto
{ public:
Puntof); II Método constructor que inidaliza en la coordenada (0,0)
void Lee(); II Método que lee del teclado el valor de la coordenada (x,y)
void Despliega! ); II Método que despliega en pantalla la coordenada (x,y) del
llpunto
--„, Jloat Distancia íPunto P); II Este método calcula la distancia del punto que
'""Ss // recibe el mensaje al punto P.

^ \ . \ prívate:
\ \ Jloat x, y; II Estos atributos guardan los valores de la coordenada <x,y)
\ \ }; II que corresponden a un punto en el plano coordenado.
\ l
V i
\ l

\ \ a) Escriba la implementación del método Distancia, cuyo encabe-


\ \ zado ya está definido en la clase Punto. Recuerde que la distan­
cia entre dos puntos (x,, y() y (x2, y2), se calcula con la siguiente
I • fórmula:
/ r

/ i

/ /' Distancia = VT-^V + (yry^


X t
^^ *
y ' b) El triángulo es una figura geométrica que puede definirse como
...-■"'* un objeto que contiene tres vértices, donde cada vértice es un
punto en el plano coordenado. Utilizando la clase Punto, se
puede definir una clase para los triángulos de la siguiente ma­
nera:

deas Triángulo
{ public:
II Aquí se encuentran los encabezados de los métodos asociados a un triángulo,
prívale:
Punto vi, v2, v3; II Estos atributos corresponden n los tres vértices del triángulo
};

Basándose en estas características y en el método que implemento en


el inciso anterior, describa la implementación de una función miembro
(método) de la clase Triángulo que regrese como valores de salida las
longitudes de los tres lados del triángulo. Se puede suponer que los valo­
res de los vértices ya están validados y siempre corresponden a los
vértices de un triángulo.

• Q
Programación orientada a objetos

c) El polígono es una figura geométrica que puede tener tres o más


lados y que también puede definirse con base en los vértices que
contenga.
Si se considera que se usa un arreglo de puntos para definir un po­
lígono de 'N' vértices (N > 3), el arreglo contendría los valores de
las coordenadas de los vértices ordenados de tal forma que, al unir
# el primer punto en el arreglo con el segundo se forma un lado del
polígono, al unir el segundo con el tercero se forma otro lado, y así
sucesivamente, hasta formar el último lado con el último punto y el
primer punto del arreglo.
Utilice la declaración typedef Punto PoligonofNJ; para implemento
una función libre que sirva para calcular el perímetro de un polígono
considerado como parámetro de entrada. Considere que el arreglo
ya se cargó con los datos de un polígono, y que siempre tiene 'N'
vértices. /
Utilice la clase Punto y el método Distancia definidos en el inciso
a. Recuerde que el perímetro de un polígono es la suma de las
longitudes de sus lados.
v \
i \

r-
1
ñuTOEunLuncióNq,. \
' \ ^ ^
i i \

1. Características esenciales de los lenguajes de programación orienta­


dos a objetos:

a) Recursividad b) Encapsulamiento
c) Funcionalidad d) Todas las anteriores
e) Ninguna de las anteriores

2. Conteste verdadero o falso. Un método es la petición que se hace a


un objeto para que éste reaccione con cierto comportamiento:

Q •
Rutneuaiuacion

3. Valor resultante de evaluar la siguiente expresión en lenguaje C++:


8+3%2

a) cero b) 8 c) 1
d) 7 e) 9

4. Si se considera que la variable n ya tiene un valor mayor que cero,


¿qué valor se obtiene en la variable r después de ejecutar el esta-
tuto/or?

"ss. \ r = l;
\ \ for (intj= l;j<=n;j++) r *=j;

\ \ a) r guarda el valor de la siguiente multiplicación: 1*2 * 3 * ... * (n-¡) *

\ \ b) r guarda la multiplicación de n por n, n veces (n* n* n *....* n)


\ c)/guarda el valor de r*./
d) r guarda el valor de 1 * n
I j e) r guarda el valor de /

/ / 5. Conteste verdadero o falso. Una función que contiene tres paráme-


/ r

/ / tros por referencia (utilizan el operador &) y dos parámetros por


y / valor, genera como salida al menos tres valores:
..-■''' 6. Dada la siguiente declaración, se puede afirmar que:

class Fecha
{ public:
Fechaf);
Fecha(iia a. int m. int di; U inivializa ¡a fecha con valores dados
void despliega (); II despliega en pantalla una fecha *
int cantidad_diasJranscurridos(); //calcula cantidad de días transcurridos desde
111/11)900
prívale:
int Año, Mes. Dia;};
Fecha fl.fld997, 8,20);

a)/7 es un objeto de la clase b) Fecha es un objeto de la clase


Fecha fl
c)f2 es un método de la clase d) Fecha es un método de la
Fecha clase fl
e)/7 es un mensaje de la clase
Fecha

• a
Programación orientada a objetos

7. ¿Cuál de las siguientes instrucciones envía correctamente un


mensaje a algunos de los objetos definidos en la declaración de la
pregunta anterior?

'd)fl.despliega();
b)f2(1997J ,12);
• c)Fecha¿I();
d)fl.mes;
e)cantidad_dias_transcurridoí!(f2); ,..--—

8. Según la declaración de la pregunta 6, ¿cuántas veces se ejecuta el


método constructor? / /
f
*t //
/

a) ninguna vez, pues no se ha utilizado / /


b) sólo una vez /
c) dos veces
d) tres veces
e) tantas veces como se haya llamado al método en el programa
*
i
\ \
i \

9. Según la declaración de la pregunta 6, ¿cuántos objetos estarán


involucrados cuando el programador utilice el método >v
cantidad_díasjranscumdos,l

a) ningún objeto
b) sólo un objeto
c) dos objetos
d) tres objetos
e) tantos objetos como se requieran

10. Tomando como base la declaración de la pregunta 6, y suponiendo


que la siguiente instrucción es válida:

ififl <J2) cout « "La fecha es posterior"

ñ •
Rutoeualuacion

¿Qué declaración tendría que incluirse en la definición de la clase


para que la instrucción anterior funcionara correctamente?

a) friend Fecha operator < (Fecha fl, Fecha f2);


b)friend char operawr < (Fecha fl, Fecha¡2);
c) friend Fecha operator < (Fecha f);
á)friend char operator < ();
e) friend char operator < (Fecha f);

^v^ *\
V *
\ *

/ I
i I
M i
fI i

y /

• o
Capítulo^*
• I

DATOS
OBJETIVOS
• Entender qué es la representación física de una estructura de datos.
• Describir las características que distinguen a la representación por posiciones,
y a la representación por ligas o encadenamientos.
• Definir qué es un apuntador y cuál es su aplicación en el contexto de un lenguaje
de programación.
• Distinguir la diferencia entre una dirección de memoria física o real y una dirección
lógica o relativa (arreglos).
• Entender cuáles son las diferencias entre el uso de la memoria estática y la memoria
dinámica.
• Comprender las ventajas de utilizar la memoria dinámica en las aplicaciones de
software.
• Conocer las operaciones para crear y liberar espacios de memoria dinámica.
• Aplicar correctamente las regias para utilizar a la memoria dinámica.
• Aplicar los conceptos de apuntadores y memoria dinámica para trabajar con objetos
dinámicos y objetos con atributos dinámicos.
• Ejemplificar los conceptos con la programación del nivel físico de un TDA utilizando
la programación orientada a objetos y los atributos dinámicos.

0
Representación de estructuras de datos

¿Qué es la representación de una estructura de datos?

Básicamente, puede entenderse como el punto de conexión entre la es­


pecificación lógica de un TDA y su implementación en un lenguaje de
programación particular. Dentro del proceso de abstracción de datos, la
0 representación de una estructura corresponde al inicio del segundo
nivel de abstracción, es decir, el nivel físico. Esta etapa construye un
esquema de cómo se almacenarán los elementos de la estructura de datos
en la memoria, de tal forma que se logre su óptimo aprovechamiento.
* ^^
¿Qué tipos de representaciones existen para una / /
estructura de datos? / /
>t
*
//
I

Independientemente de las facilidades de implementación que ofrez- ( /


can los lenguajes de programación, una estructura de datos (TDA) puede
representarse de dos formas:

• por posiciones (almacenamiento contiguo).

• por ligas (almacenamiento disperso). \


* ^s.
* ^w

¿Cómo funciona la representación de una estructura


de datos por posiciones?

En este tipo de representación, el lugar físico donde se almacena un


elemento, determina automáticamente su posición relativa en la estruc­
tura de datos. Básicamente, se puede pensar en ella como un espacio
de almacenamiento contiguo donde cada lugar sirve para almacenar un
elemento, de tal forma que si un elemento está almacenado en el lugar
K, tendrá la K-ésima posición dentro de la estructura y, obviamente, el
elemento K + 1 estará ubicado exactamente después del K y se alma­
cenará en el lugar K + 1 del espacio de almacenamiento. Esta represen­
tación se ejemplifica en la figura 3.1.

Q •
¿Qué herramientas existen típicamente en la mayoría de los lenguajes de programación
para desarrollar una representación por posiciones?

1 aíemento 1
2 elemento 2
— o ~
o
K elemento K
K+1 elemento K+1

0
"--.. 0
%
V N elemento N

* \ \ ^ )
\ \ Figura 3.1. Representación por posiciones (almacenamiento contiguo}
\ *

\ \ Como ya se mencionó anteriormente, una de las principales caracterís­


ticas de este tipo de representación es que la dirección (posición) de un
I elemento depende directamente del lugar físico donde se almacenó.
/ / Además, se establece implícitamente una relación de linealidad entre
/ / los elementos almacenados, ya que, del elemento l (que está en la
/ / posición 1), sigue el elemento 2 (que está en la posición 2), y del 2 sigue
/ / el elemento 3; y, obviamente, antes del elemento 3 está el 2 y así suce-
y^ / sivamente.

¿Qué herramientas existen típicamente en la mayoría


de los lenguajes de programación para desarrollar una
representación por posiciones?

Casi todos los lenguajes de programación proveen al programador de
herramientas útiles para representar estructuras de datos en forma
contigua.
Las herramientas empleadas para representaciones contiguas que, a
su vez, resultan ser estructuras de datos implícitas del lenguaje, son:

Arreglos de elementos uniformes: permiten almacenar un conjunto de


elementos del mismo tipo, ya sea simple (como enteros, strings, etc.) o
compuestos (registros con elementos de diferentes tipos). Figura. 3.2.

• a
Representación de estructuras de datos

1 elemento 1
2 ' "elemento 2
3 elemento 3

0
o
r
• N elemento N

y ' y
Figura 3.2. Arreglo de elementos uniformes

r
* ^^
JT
* y^

Registros de elementos de diversos tipos: permiten definir estructu­


ras que conjuntan valores de diferentes tipos, almacenándolos como si
fueran una unidad (figura 3.3). /

í r —A i
campo 1
r—— ' 1 1 ' , \
campo 1 campo 2 \
campo 4 . ^.
campo 2 campo 3 campo 3 \
' ' ' campo 4 \ \
/ \ \
x y \ \ ^
Figura 3.3. Registro de elementos de diversos tipos.

Archivos secuenciales: al igual que los arreglos, esta estructura permi­


te almacenar un conjunto de elementos, aunque lo hace en la memoria
secundaria.

¿Qué ventajas y desventajas tiene la representación


por posiciones?

Una de las principales ventajas de utilizar representación contigua es


su fácil imple-mentación, además de su rapidez de recorrido, debido a
que la dirección de cada elemento está implícita en su posición física.
Sin embargo, este tipo de representaciones tiene grandes desventa­
jas para realizar manipulaciones sobre la estructura principalmente
cuando se desea agregar un nuevo elemento en algún lugar que no sea

Q •
¿Cómo funciona la representación de una estructura de datos por ligas?

el último. Esto se debe a que, dado que los elementos están almacena­
dos en forma contigua, no existe un espacio físico disponible entre
ellos; de tal forma que se tendría que "generar" un nuevo espacio. La
única forma de hacerlo es desplazar hacia abajo los elementos que
están después de la posición de inserción deseada, logrando con esto
que se genere un espacio para poder agregar un nuevo elemento.
De manera similar, borrar un elemento que no sea el último deja un
hueco no permitido entre los elementos; si no se elimina ese espacio,
ya no se podría usar la posición física del elemento como su referencia
de dirección. La única manera de solucionar este problema es despla-
--- zar una posición hacia arriba los elementos que están después del
^v \ elemento borrado.
\ i

¿Cómo funciona la representación de una estructura


de datos por ligas?

/ j En este tipo de representación la ubicación física de un elemento no de-


/ / termina la posición relativa que tiene dentro de la estructura de datos. El
/ / almacenamiento en este tipo de representación se realiza en forma dis-
y / persa, es decir, dos elementos contiguos en la estructura de datos no
^ ^ /' necesariamente deben estar almacenados físicamente en posiciones
•**■" contiguas dentro del espacio de almacenamiento, como se muestra en
---"*"' la figura 3.4.

/ , — \
1 elemento K #
2

O
o
K
K+1 elemento 1
O
0
N elemento K+1

v '
Figura 3.4. Almacenamiento disperso.
/

- o
Representación de estructuras de datos

Como ya se mencionó, en este tipo de representación la ubicación físi­


ca de un elemento no determina su posición relativa en la estructura.
Por ello es necesario que cada elemento almacene la dirección física
donde se ubica el elemento que le sigue en la estructura, de tal forma
que se pueda mantener una relación de dónde está cada uno de los ele­
mentos. Gráficamente, esta relación se aprecia en lafigura3.5.

( = ^ ^
1 elemento K dirK+1
2 y'''

1 / /
K+1 elemento 1 dirg ! /
— \ " í /

" .'' /
O í
I

N e¡ementoK+1 dirK+2 i
V ' '
Figura 3.5. Representación por ligas.
J \ \ \ \
^ \
i \

¿Qué ventajas y desventajas tiene la representación \ \ ^


por ligas?

Una de las principales desventajas de este tipo de representación es


que cada elemento debe "recordar" dónde está alguno de los elemen­
tos (comúnmente se puede pensar en el "siguiente"). Esto es, cada
hueco en el espacio de almacenamiento es un elemento compuesto de
dos datos: el elemento en sí y la dirección de algún otro. Además, se
requiere de un "administrador" de los espacios disponibles.
Esto implica que, necesariamente, las operaciones que se realicen
en este tipo de representación requieren más tiempo de procesamien­
to que las equivalentes en el almacenamiento contiguo debido a que
el acceso no es directo, ya que para accesar el /-ésimo elemento (que no
necesariamente está en la t-ésima posición), se deben accesar secuen-
cialmente todos los elementos que se encuentran antes que él.
Estas desventajas pierden importancia cuando se les compara con
las ventajas de este tipo de representación.

6 •
¿Que es un apuntador?

Una de ellas es que dicha representación no es exclusiva de las es­


tructuras que mantienen una organización lineal; es decir, también se
puede utilizar para estructuras más generales, que requieren de una
organización no-lineal.
Otra ventaja es la posibilidad de agregar y eliminar elementos de la
estructura sin tener que desplazar los elementos que ya estaban en ella
(tal y como ocurre en el almacenamiento contiguo).
Asimismo, una de las ventajas principales es que la representación
--..._ por ligas permite un uso más eficiente del espacio de almacenamiento,
ya que se puede hacer crecer y decrecer la estructura según se requiera,
^ ^ \ lo que no es posible hacer con la representación contigua.

\ i

¿Qué herramientas brindan la mayoría de los


lenguajes de programación para desarrollar
una representación por ligas?
i

/ / Básicamente, en la mayoría de los lenguajes de programación no


/ / existen herramientas predefinidas para soportar una representación
/ / por ligas; por lo tanto, es el programador quien implementa las es-
/ / tructuras. La construcción de este tipo de representaciones se apoya
y / en el concepto de apuntador (tipo de dato capaz de almacenar una
- ^ y' dirección de memoria).
„--'**' Una de las estructuras de datos lineales que utiliza este tipo de re­
presentación es la lista encadenada que, de hecho, se considera la
abstracción de la representación por ligas para una estructura de datos
lineal y, por ello, se emplea como una opción para representar otras
estructuras de datos lineales más especializadas. La descripción del
TDA lista encadenada se explicará en el siguiente capítulo. Por ahora, *
es importante conocer las herramientas auxiliares para trabajar efi­
cientemente con una representación por ligas.

¿Qué es un apuntador?
Un apuntador se define como un tipo de dato capaz de almacenar una
dirección de memoria. Se considera como referencia indirecta a un ele­
mento.
Los apuntadores pueden ser un tipo de dato especial dentro del len­
guaje de programación en cuyo caso, guardarán direcciones de memoria
real; o bien, el programador puede simular el funcionamiento de un

• a
Representación de estructuras de datos

apuntador, por ejemplo, cuando una variable de tipo entero guarda la


posición (subíndice) de un dato en un arreglo. Para efectos de este ca­
pítulo, es importante conocer cómo se trabaja con los apuntadores que
proveen algunos lenguajes como tipo de dato.
La figura 3.6 muestra la diferencia entre una variable tradicional y
una de tipo apuntador.

f ^
n 5 VAR1 ,-**' ^ * ^
VAR1 / /S
n+1 m VAR2 —r e- m /
/ /
i /
I i
l i

m bzl J ÍH3 \\
Figura 3.6. Diferencia entre una variable tradicional VAR1 y un apuntador VAR2.

¿Para qué sirve un apuntador?

No todos los lenguajes de programación permiten el uso de apuntadores,


aunque la mayoría lo hace. Sus principales aplicaciones están relacio­
nadas con el manejo de la memoria dinámica (concepto que se explica­
rá más adelante) y en lenguajes como C y C++; además, se emplean
para permitir el paso de los parámetros por referencia a los módulos.

@ — — ~ — •
¿Cómo se utiliza un apuntador en lenguaje C/C++?

¿Cómo se declara un apuntador en lenguaje C/C++?

La declaración de un apuntador se realiza de la siguiente forma:

tipo_de_dato *nombre jxpuntador;

Por ejemplo:

**¥\ int *p;


* \ char *c;

\ \ En este ejemplo, la variable p almacenará direcciones de elementos


\ \ enteros, mientras que c guardará la dirección de valores carácter.
\ \ La definición de un apuntador puede incluir cualquier tipo de dato, ya
\ \ sea simple o estructurado. Observe que la diferencia entre una variable
1 \ tradicional y una variable apuntador es el asterisco que acompaña la
declaración de dicha variable.

¿Cómo se utiliza un apuntador en lenguaje C/C++?

/ / En lenguaje C/C++ hay dos operadores muy importantes para la mani-


y ^ / pulación de apuntadores: el operador de indireccion, que se identifica
con el símbolo * y el operador de dirección, que se identifica con el
„..---''', símbolo &.

El operador de indireccion se emplea para obtener el valor almace­


nado en la dirección que guarda la variable apuntador; es decir, accesa
el contenido y con él se puede realizar cualquier operación permitida #
para ese tipo de valor. Se utiliza de la siguiente forma:
*Var_apuntador

El operador de dirección se usa para obtener la dirección de alguna


variable. Dicha dirección puede almacenarse en un apuntador capaz de
guardar direcciones del mismo tipo de dato que la variable, en esta forma:
&Variable.

Observe que el operador de indireccion sólo se puede emplear con


variables apuntador, mientras que el operador de dirección puede em­
plearse con cualquier tipo de variables.

• Q
Representación de estructuras de datos

Así, si se tiene la declaración y las operaciones:

ittt a, b, *p, *q; p = &a;


a = 3;
q = p;
h = % +2;

se tiene que:
• El apuntador p almacena la dirección de la variable a.
• La variable a guarda el valor de 3.
• El apuntador q guardará la misma dirección que almacena el apun­
tador p.
• La variable b toma el contenido apuntado por la dirección alma­
cenada en q (un 3) y le suma un 2, por lo tanto b tomará el valor
de 5. /
• Después de estas operaciones, se accesa el mismo espacio de me­
moria con a, *p y *q.
i
i i
i \
► \

¿Qué es la memoria dinámica? \ \


i \

Muchos lenguajes de programación permiten manejar dos tipos de al­


macenamiento de datos en la memoria principal: el almacenamiento
estático (o automático) y el almacenamiento dinámico.
La memoria estática es la que se maneja tradicionalmente. Sus
principales características son:

• Se define explícitamente al declarar una variable, ya sea global o


local.
• El compilador genera automáticamente el espacio de memoria.
• Se mantiene fija durante toda la vida de la variable (independien­
temente de que se utilice o no).

La memoria dinámica permite crear y destruir espacios de memoria,


según indicaciones explícitas del programador durante la ejecución del
programa. Sus principales características son:

• Utiliza una parte de la memoria principal denominada heap.


• Apoya el uso eficiente de la memoria durante la ejecución.
• Requiere apuntadores que almacenen direcciones de memoria
real, dado que éstas se asignan dinámicamente.

ñ — •
¿Cómo se pueden crear y liberar espacios de memoria dinámica?

¿Cómo se pueden crear y liberar espacios de memoria


dinámica?
Los espacios de memoria dinámica se crean y se destruyen, a petición
explícita del programador, mediante operaciones especiales de cada
tipo de lenguaje. En el lenguaje C se cuenta con las operaciones malloe
y free. En C++ se utilizan los operadores new y delete.

El operador new se utiliza de la siguiente forma:

apuntador = new tipo_de_dato;


N
\ \ La ejecución de un new hace una petición al sistema operativo para
\ \ que se le otorgue un espacio de memoria del heap del tamaño asocia-
\ \ do al tipo de dato indicado. Si el espacio de memoria está disponible,
\ \ la operación regresa la dirección real del espacio de memoria que se
\ ; otorga al programador (por eso, se asigna a una variable apuntador el
resultado del new). Si no hubiera espacio en el heap, se regresa el valor
I de cero (NULL). El apuntador toma el valor de la dirección y apunta a
/ /' ese nuevo espacio, que estará separado por el sistema operativo hasta
/ / que sea liberado.
/ El operador delete se utiliza de la siguiente forma:

^ ^ delete apuntador;

La ejecución del delete provoca que se libere el espacio direcciona-


do por el apuntador, dejándolo con un valor indefinido. Esto quiere
decir que, al espacio referenciado por el apuntador, el sistema operativo
lo considerará como memoria disponible en el heap y, por lo tanto, ya
no se podrá accesar.

Por ejemplo, si se tiene la siguiente declaración de variables: inta, *p\


se podría asignar un espacio dinámico al apuntador^ de la manera si­
guiente: p e new int;

Para accesar el espacio dinámico apuntado por/» y asignarle el valor


que guarda la variable a, se ejecutaría la siguiente instrucción:

*p = a;

Cuando ya no se requiera utilizar el espacio dinámico apuntado por


p, se deberá realizar la instrucción: delete p;

• U
Representación de estructuras de datos

¿Cuáles son las reglas para programar con apuntadores


y memoria dinámica?

Al programar, estas herramientas brindan un potencial muy importan­


te para manejar eficientemente la memoria con diversas estructuras de
datos. Sin embargo, manejar directamente la memoria tiene sus riesgos
y es muy importante que los programadores cumplan con ciertas reglas
que eviten que los programas que utilizan apuntadores y memoria diná­
mica fallen en su ejecución.
Estas reglas se han agrupado en el siguiente "Decálogo para progra­
mar correctamente utilizando memoria dinámica":

1. Por cada vez que se ejecute un new, deberá ejecutarse un delete


antes de terminar la ejecución de un programa.

La memoria dinámica no es global ni local; proviene de un área


de memoria, heap, que administra el sistema operativo. Por lo tan­
to, el sistema operativo reserva los espacios de memoria dinámica
que se solicitan hasta que se indica su liberación, sin importar si el
programa en cuestión terminó o no su ejecución. \

2. Un delete actúa liberando el espacio de memoria apuntado, inde­


pendientemente de que existan más apuntadores en el mismo
espacio.

Cuando se libera un espacio dinámico, todos los apuntadores con


referencia hacia el mismo guardan un valor indefinido (basura), y
ya no es posible accesar ningún dato a través de ellos.

3. Un apuntador local a un módulo se destruye al terminar su eje­


cución, sin importar a qué espacio de memoria hace referencia.

Se deberá cuidar que los apuntadores locales no dejen "volando"


espacios de memoria dinámica cuando se termine la ejecución del
módulo al que pertenecen, a menos que el espacio solicitado vaya a
eliminarse del módulo (como resultado o parámetro de salida).

4. Basura y nada son diferentes.

El valor nada es válido para un apuntador y representa "no apun­


tar a algún elemento en memoria"; el valor basura es cualquier

Q •
¿Cuáles son las reglas para programar con apuntadores y memoria dinámica?

valor indefinido. En el lenguaje C++, la constante NULL repre­


senta el valor de nada en los apuntadores.

5. Hacer un delete con un apuntador que no hace referencia a un


espacio de memoria dinámica provoca fallas de ejecución.

La operación delete sólo actúa con espacios de memoria solici­


tados al sistema operativo con un new. Si un apuntador con valor de
—-.._ nada (NULL), está indefinido, o bien, apunta a un espacio de me­
moria estática, la operación delete sobre este apuntador podrá
"^s. X provocar fallas al ejecutar el programa.

\ \ 6. Una referencia a través de un apuntador, cuyo valor sea NULL,


\ \ provocará fallas en la ejecución del programa.
\ <

\ Cuando un apuntador señala a nada, no es posible hacer una in-


dirección (con el operador *), pues no se hace referencia a ningún
elemento. Normalmente, se cancela la ejecución del programa.

I 7. Al asignar un valor a un apuntador con la operación new, el


/ / apuntador perderá su valor anterior, independientemente de
/ qué esté señalando.
*^ t
JT *

La operación new genera el valor de la dirección del espacio de


memoria dinámica recién creado, para asignarlo a un apuntador. Si
el apuntador tiene un valor indefinido o valor de nada (NULL), no
habrá ningún problema al crear esta referencia, pero si el apuntador
ya referencia a otro espacio de memoria dinámica, deberá evitarse
usar new con ese apuntador, o bien, referenciar previamente el es- #
pacio ya creado con otro apuntador, para no dejarlo "volando".

8. No siempre se tiene que realizar un new para utilizar un apun­


tador.

Los apuntadores por sí mismos son espacios de memoria que


guardan como dato una dirección. Una forma de asignarles valor es
a través de un new, sin embargo también permiten la asignación di­
recta del valor de otro apuntador o de la constante nada (NULL).
Aquí se aplican las reglas de congruencia entre tipos de variables.

9. Los valores de los apuntadores se pueden comparar para verifi­


car si señalan lo mismo o no.

• M
Representación de estructuras de datos

Los operadores de comparación de igualdad y desigualdad ( » y


!=) se pueden aplicar sobre variables apuntador, para verificar sus
referencias. Aquí también se aplican las reglas de congruencia entre
tipos de variables.

10. A un dato referenciado por un apuntador se le pueden aplicar


• todas las operaciones válidas para el tipo de dato.

Para hacer referencia al dato apuntado, siempre se utiliza el ope­


rador de indirección (*). Cuando el elemento apuntado es un objeto,
se puede emplear el operador -> para el envío de mensajes, de
acuerdo con la siguiente equivalencia: /
(*ap_obj).mewaje = apjvbj->mensaje. / /
t /

¿Cuál es la relación entre los apuntadores y los , /


arreglos en lenguaje C/C++? /
El nombre de un arreglo es, por sí mismo, un apuntador al primer ele­
mento del arreglo. Por lo tanto, si se declaró un arreglo de 100 enteros
de la siguiente forma: int a[100]\ se puede decir que a es un apuntador \
al arreglo y que *a accesa al elemento afOJ. \ \
Gracias a que en el lenguaje C existe la "aritmética de apuntadores",
los arreglos pueden manejarse con apuntadores con la siguiente equi­
valencia:

arreglofjjes equivalente a *(arreglo+j)


¿¿arreglo!]] es equivalente a (arreglo-i-])

Por lo tanto: arreglofO] es igual a ^arreglo y &arreglo[0] es igual


a arreglo.

Por otro lado, con las facilidades de la memoria dinámica se pueden


crear arreglos dinámicos del tamaño que se requiera en tiempo de eje­
cución. La forma de hacerlo en C++ implica una variación al formato
del new y del delete, como se muestra en el siguiente ejemplo:

Tipo *armgfoi Use define sólo el apuntador


arreglo = new Tipo[tamaño]; II aquí se crea el arreglo dinámicamente
IIEI acceso puede ser como un arreglo tradicional: arreglofsubfndicej
deletef / arreglo: II para liberar el espacio dinámico del arreglo

9 •
¿Cómo se pueden crear objetos dinámicos?

^ Ejemplo

^ A R R E G L O ESTÁTICO ARREGLO DINÁMICO ^

intdatos[200!; int *datos, tam;


cin » tam;
jar (j=0; j<200; j++) datos = new int[tamj;
cin » datos[j}; for(j=0;j<tatn; j++)
"**%., ■•■ cin »datos[jJ;

\ \ Nota: observe que la definición de un arreglo


\ \ dinámico permite el uso de una variable para
\ \ su tamaño.
\ i
\ i

\ \v /

¿Cómo se pueden crear objetos dinámicos?

y ^ El concepto de la memoria dinámica en los objetos puede utilizarse de


^ ^ dos maneras: creando dinámicamente objetos cuando se requieran, o
_..-*-'" con objetos cuyos atributos se crean dinámicamente. En ambos casos
se aplican las reglas mencionadas anteriormente. Incluso, se pueden
llegar a utilizar objetos dinámicos que contengan atributos dinámicos.

Para trabajar con un objeto que se crea dinámicamente, se aplican


los formatos ya conocidos considerando que el nombre de la clase de los •
objetos es, por sí mismo, un tipo de dato. Así, si se tiene definida la cla-
í,e Ejemplo, la forma de definir un objeto dinámico de dicha clase sería:

Ejemplo ap;
ap = new Ejemplo;

En caso de que la clase Ejemplo tenga un método constructor con


parámetros, la forma de crear un objeto parametrizado sería:

ap = new Ejemplo(argumentos);

•—— ——————Q
Representación de estructuras de datos

Para accesar los atributos o métodos públicos de un objeto dinámi­


co, el operador de indirección se utilizaría asi' sobre el apuntador:
(*ap).mensaje. Sin embargo, para este caso, el lenguaje provee de
un operador que simplifica esta escritura, siendo equivalente a:
ap->mensaje. El operador -> se puede emplear siempre que se haga
referencia de acceso a un objeto dinámico por medio de un apuntador.
• Por otro lado, los objetos pueden contener atributos dinámicos, lo
que permite la creación de objetos con espacios de memoria variables,
según las necesidades de ejecución. Para este caso, se esperaría que el
método constructor haga el new correspondiente y que el método des­
tructor libere la memoria del atributo con un delete. En este caso, ad­
quiere sentido la presencia de los métodos destructores en la definición
de una clase. /
i /

H Ejemplo / /

f ^ I
class Ejemplo
{ prívate: \
int *ap; II atributo que apuntará a un espacio dinámico de memoria \
pubiic:
Ejemplo(int tam) { ap = new int[tam]; } llgenera un espacio
//dinámico para enteros

-Ejemplo!) { deleteU ap; } /¡libera el espacio generado por el


¡¡constructor
h

\ /

Ahora, se ejemplificarán las diferentes combinaciones que pueden


ocurrir entre memoria estática y memoria dinámica.

1. Objetos estáticos con atributos estáticos.


Supongamos que tenemos la siguiente definición de una clase:

class Ejemplo
{pubiic:
int a;
h

o •
Ejemplo

y se declara un objeto:

Ejemplo obj;

obj es un objeto estático con un atributo estático, que si se quiere


accesar se haría con la expresión obj.a
Obviamente, lo único que se le puede asignar al atributo a es
un valor entero.

\ . 2. Objetos estáticos con atributos dinámicos


^. \ , Ahora supongamos que la definición de la clase contiene como
X. \ atributo a un apuntador:
\ s
\ I
\ I

\ \ class Ejemplo
\ \ {public:
\ int *a;
};

I j y se declara un objeto:
/ / Ejemplo obj;
/ / obj es un objeto estático con un atributo apuntador que puede di-
y / reccionar a un espacio dinámico, que si se quiere accesar se haría
con la expresión obj.a y, obviamente, lo único que se puede asig-
,,.-''' nar al apuntador a es una dirección.
Una forma de asignar un espacio de memoria dinámico al apun­
tador a sería: obj.a = new int; y la forma de referenciar este espa­
cio dinámico para asignarle un valor sería: *(obj.a) = valor, que
es equivalente a *obj.a = 8; dado que el operador punto (.) tiene
prioridad sobre el operador de indirección (*). *

3. Objetos dinámicos con atributos estáticos


Ahora, supongamos que nuevamente se tiene la definición de la
clase con un atributo estático:

class Ejemplo
{ public:
int a;
};

pero se declara un objeto dinámico a través de un apuntador:

Ejemplo *ap = new Ejemplo;

e
Representación de estructuras de datos

La forma de accesar el atributo a del objeto apuntado por ap se­


ría con la expresión ap->a.
Obviamente, lo único que se puede asignar al atributo a es un valor
entero, a través de una expresión como:

ap->a=valor;

4. Objetos dinámicos con atributos dinámicos


Finalmente, supongamos que tenemos el caso anterior, pero con un
atributo dinámico:

class Ejemplo
{public: / /
int *a; / /
};

pero se declara un objeto dinámico a través de un apuntador:

Ejemplo *ap=new Ejemplo; \


i \

donde ap es un apuntador a un objeto dinámico que tiene un atributo


que se puede direccionar a un espacio dinámico, que si se desea ac­
cesar se haría con la expresión ap->a y lo único que se puede asignar
al apuntador a es una dirección.
Una forma de asignarle un espacio de memoria dinámico al apun­
tador a sería:
ap->a=new int;
y la manera de referenciar este espacio dinámico para asignarle un
valor sería:
*(ap->a)=valor;
que es equivalente a
*ap->a=8;
dado que el operador ñecha(->) tiene prioridad sobre el operador de
indirección (*).

Estos conceptos se utilizarán y contextualizarán en una aplicación


práctica (listas encadenadas) y se explicarán en el siguiente capítulo.

8 ^

¿Qué debe cuidarse al pasar como parámetros objetos dinámicos en lenguaje C / C * ?

¿Qué debe cuidarse al pasar como parámetros objetos


dinámicos en lenguaje C/C++?
Analicemos qué pasa cuando trabajamos con objetos estáticos y se tiene
una declaración típica de parámetros por valor:
void Ejemplo (Objeto obj);
Cuando se hace la llamada Ejemplo(x), automáticamente se crea el
--... objeto obj, que tiene en sus atributos una copia de los atributos de JC
\ t (figura 3.7).

\ \ f 7=^ ^=^ >


\ \ /At^utosN /AtributtwX |
ob
\ \ ■ estéticos \ / estáticos \ '

\ \ L_^___^ )
Figura 3.7.

/ / Ahora analicemos (figura 3.8), bajo esta misma declaración de


/ / parámetros, qué pasa cuando el objeto tiene atributos creados dinámi-
y / camente, es decir, el objeto tiene atributos que son apuntadores que
"^ /' direccionan espacios creados dinámicamente.

( /~x /—X A
Atributos \ / Atrihutos \
x ' dinámicos '. Bstáticos | obj

Figura 3.8.

Como se puede observar, la copia automática en el parámetro obj es


sólo del valor del apuntador, por lo que el espacio dinámico estaría
siendo apuntado por los dos objetos al mismo tiempo.
El problema surge cuando el objeto obj deja de existir (al terminar
la ejecución del módulo Ejemplo), y se ejecuta automáticamente el

• 0
Representación de estructuras de datos

método destructor sobre obj, por lo que afecta irremediablemente al


objeto x. Esto se aprecia en la siguiente figura (3.9):

Atributos Atributos i
x dinámicos estáticos \ 0 bj

T
^ — ■ ■ ^ ^ « ■ 1 — - ^ _ „ - -

Figura 3.9.

La solución a este problema es manejar el parámetro como una


referencia, pues, en este caso, al momento de llamar al módulo el pa­
rámetro no será una copia, sino una referencia (apuntador) al objeto
que actúa como argumento. Esto se aprecia en la figura 3.10:

Atributos
dinámicos

V ^ *** ) \ \
Figura 3.10.

Ya que desde el punto de vista del diseño es importante que el pará­


metro actúe sólo como entrada, el lenguaje provee el declarativo const
para que la referencia no se modifique bajo ninguna circunstancia.
De esta manera, cuando termina la ejecución del módulo, el paráme­
tro formal obj, se destruye, pero, al ser sólo un apuntador, no afecta
al objeto referenciado, pues no se ejecuta el método destructor involu­
crado.
En este caso, el encabezado del módulo sena así:

void Ejemplo (const Objeto &obj);

Por lo tanto, cuando se desee emplear un objeto con atributos diná­


micos como parámetro de entrada a un módulo, deberá utilizarse el
declarativo const, declarando el parámetro por referencia.

Algunos detalles importantes en la programación, que utiliza herra­


mientas del lenguaje C++ se pueden encontrar en el Anexo A.

Q •
Ejercicios

i ( EJERCICIOS > &

1. Suponga la siguiente definición de clases y variables en lenguaje


C++:

**"\ ctass Ejeml class Ejeml


{ prívate: { prívate:
" \ . int a, b , c; char x, *y;
X public: public:
\ Ejeml(){}; Ejem2()
\ \ EjemKint x){ a = b = c = x;}; { x = 0;
\ \ voíd despliega (} v = newckar/5/; } ;
\ \ {cout « a « b « c; } ; ~Ejem2()
}; { detete[] y;};
);

I j Ejeml objl, "-API, *AP2; Ejeml ob}2, ohj3, *AP3;


/ t

/ t

y / / Responda las siguientes preguntas:

„---'' a) ¿Cuántas veces se ejecutaron métodos constructores, según la de­

claración anterior?

b) ¿Cuál es el valor de los atributos de los objetos objl y obj21

c) ¿Para qué objetos y cuándo se ejecutará un método destructor? *


d) Escriba la instrucción para crear un objeto apuntado por API, ini-
cializando sus atributos con 1.

e) Escriba la instrucción para que el objeto creado en el inciso d) sea


apuntado también por API.

f) Escriba la instrucción para crear un objeto apuntado por AP3.


¿Qué valores tienen los atributos del objeto?

g) Escriba la instrucción para que el apuntador API señale al obje­


to objl. ¿Qué sucede con el objeto que señalaba antes AP11

• 0
Representación de estructuras de datos

h) Escriba la instrucción que envía el mensaje de despliegue al ob­


jeto apuntado por AP2.

i) Escriba la instrucción que, suponiendo que se permite el acceso a


los atributos, sirve para comparar el atributo x del objeto obj2 y
del objeto apuntado por AP3.

j) Escriba la instrucción que sirve para comparar a los apuntadores
API y AP2.

k) Escriba la instrucción para liberar (destruir) el objeto apuntado


por AP2.
* /
* /
1) Escriba la instrucción para crear un nuevo objeto apuntado por
AP2
m) Escriba la instrucción para hacer que API apunte al objeto al
cual señala AP2. ¿Qué sucede con el objeto apuntado por API
anteriormente?
» \
n) Escriba la instrucción para liberar el objeto apuntado por API.
¿Qué sucede con AP21
\ .
o) Escriba la instrucción que, suponiendo que se permite el acceso
a los atributos, sirva para inicializar con espacios en blanco los
cinco caracteres del atributo y del objeto obj'3.

p) Escriba la instrucción que, suponiendo que se permite el acceso


a los atributos, sirva para inicializar con espacios en blanco los
cinco caracteres del atributo y del objeto apuntado por AP3.

r HUTOEUHLUflCIÓN < \ =
" i
■ i
L _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ — _ _ _ _ _ J

Para las preguntas 1 a 3, considere la siguiente definición de la clase


de números complejos:

Q .
flutoeualuacrón

class Complejo
{ public:
ComplejoO;
Complejo(floatx,floaty);
float Parte_Real ();
voidescribeO;
void lee();
prívate:
float real, imaginario; } ;

^ \ \ Complejo numl, numl (2,0), *num3, *num4, arregloflO];

\ \ 1. ¿Cuál de las siguientes instrucciones envía correctamente un men-


\ \ saje a alguno de los objetos definidos en la declaración?
\ a) escribe(numl);
\ b) num3.lee();
c) num2.Complejo(l, 3);
J ¡ d) num3->Complejo;
I ¡ e) (*num3).Parte^Real();

/ / 2. ¿Cuál de las siguientes opciones crea correctamente un objeto de la


/ / clase Complejo?

/ a) Complejo *num5(l, 0);


b) Complejo num6=new Complejo (3,1);
c) numl=new Complejo;
d) num3=new Complejo (4, 0);
e) Ninguna de las anteriores.

3. Suponiendo que se pudieran accesar los atributos privados de la cla­
se, ¿cuál de las siguientes asignaciones es válida?

si) numl.real =*num3.real;


b) arreglo->real=num3->real;
c) *numl.real=num2.real
d)arregto[2]->real=(*num3).real;
e) num3->real = arreglofSJ.real

. 0
Representación de estructuras de datos

4. Si se supone que P y Q son variables apuntador que apuntan al mis­


mo tipo de dato, y que aún no se han inicializado, ¿cuál de los seg­
mentos siguientes de código está libre de fallas en su ejecución?
Nota: considere que dejar nodos "volando" (sin referenciarlos) es también una
falla de ejecución.

a) if(P ==Q)P= new Tipo; b) Q = new Tipo;


else Q = new Tipo; P =NULL;
cout « *P « *Q; if(P== Q) P=new Tipo;
else delete Q;

c)P = new Tipo; d) delete P; e) P = new Tipo;


Q = new Tipo; delete Q; Q = P;
if(P != Q)P=Q; P = new Tipo; delete P;
delete Q; Q = new Tipo; delete Q;

5. Considere el siguiente fragmento de código en C++:


i \

* \
float a = 2.0, *b, *c;

b= &a;
c=newfloat;
*c = *b;
a= *c+/.5;
cout «*b «*c;

¿Qué valores se desplegarán en pantalla al ejecutarlo?

a) 2.0 2.0
b)3.5 2.0
c) dirección de a y dirección dada en el new.
d) dirección de a y 3.5
e)3.5 3.5

Q •
Capítulo Jn\

OBJETIVOS

• Entender qué es una lista encadenada.


• Describir el TDA lista encadenada.
• Comprender cómo se puede representar una lista encadenada, tanto
en memoria estática como en memoria dinámica.
• Describir la forma en que se pueden implementar las operaciones de
inserción de un elemento, eliminación de un elemento y desplegado
de los elementos de una lista encadenada.
• Identificar las características, ventajas y desventajas de una lista en­
cadenada circular.
• Identificar las características, ventajas y desventajas de una lista do­
blemente encadenada y una lista doblemente encadenada circular.

0I
Listas Encadenadas

¿Qué es una lista encadenada?

La lista encadenada es una estructura de datos que tiene una organiza­


ción lineal y se caracteriza porque cada uno de sus elementos tiene que
indicar dónde se encuentra el siguiente elemento de la lista (figura 4.1);
9 por lo tanto, es una abstracción de la representación por ligas para una
estructura de datos lineal.
Una lista encadenada es un TDA que se convierte en una opción
para representar otras estructuras de datos o TDA.

v ) \[
Figura 4.1.

Cada elemento de la lista se guarda en un nodo que contiene la


información y el valor que indica en dónde se encuentra el siguiente
dato en la lista.
^ \,

¿Cuál es la especificación lógica del TDA lista


encadenada?

La especificación lógica para el TDA lista encadenada es:

*j ELEMENTOS: los elementos de una lista se denominan nodos, que almacenan da­
tos simples o estructurados.

ESTRUCTURA: la estructura de tina lista es lineal, esto es, existe un elemento llama­
do "el primero" y otro llamado "el último"; cada elemento tiene un
tínico sucesor y antecesor; cada uno de los elementos tiene asignada
una única posición en la lista, esto es, la posición del primer elemen­
to es la 1 la de su sucesor es 2 etc Si la lista no está vacía, sus
elementos tendrán las posiciones 1.2..,,*, donde N es la cantidad
de elementos de la
lisa
V )

0 •
¿Cuál es la especificación lógica del TDfl lista encadenada?

( \
Sólo se muestran algunas operaciones generales que pueden ser
útiles para cualquier aplicación.

METER INICIOLISTA
ENTRADA: " la lista y el nuevo elemento
SALIDA: la lista con el nuevo elemento al inicio
PRECONDICIÓN: ninguna
POSTCONDICIÓN: la lista contiene el elemento nuevo al inicio

«^ ' \ METER_FINAL_LISTA
" ^ \ \ ENTRADA: la lista y el nuevo elemento
X \ SALIDA: la lista con el nuevo elemento al final
\ \ PRECONDICIÓN: ninguna
\ \ POSTCONDICIÓN: la lista contiene al elemento nuevo al final

\ \ METER EN ORDEN ESPECIAL LISTA


\ ENTRADA: " la Tista y el nuevo elemento
SALIDA: la lista con el nuevo elemento en el lugar que le corresponde
PRECONDICIÓN: ninguna
/ | POSTCONDICIÓN: la lista contiene al elemento nuevo en el orden que le
/ j corresponde

/ / SACAR_INICIO_LISTA
/ ENTRADA: la lista a la que se va a sacar el elemento
/ / SALIDA: la lista con un elemento menos y, en viejo, el valor que se sacó
^ / PRECONDICIÓN: la lista no está vacía
y*' POSTCONDICIÓN: la lista contiene un elemento menos (el del inicio)

SACARFTNAL LISTA
ENTRADA: la lista de la que se va a sacar el elemento
SALIDA: la lista con un elemento menos y, en viejo, el valor que se sacó
PRECONDICIÓN: la lista no está vacía
POSTCONDICIÓN: la lista contiene un elemento menos (el del final)

SACAR_EN_ORDEN_ESPECIAL_LISTA
ENTRADA: ~ la lista de la quese va a sacar el elemento y el elemento que se
desea sacar
SALIDA: la lista con un elemento menos
PRECONDICIÓN: la lista no está vacía y contiene al elemento que se quiere sacar.
POSTCONDICIÓN: la lista contiene un elemento menos y corresponde al que se sacó

BUSCAR
ENTRADA: la lista y el elemento por buscar
SALIDA: la lista como estaba y un valor de verdadero si encontró el
elemento en la lista o falso si el elemento no se encuentra en
la lista
PRECONDICIÓN: ninguna
POSTCONDICIÓN: ninguna

V )

• -
Listas Encadenadas

¿Cómo se utilizan la memoria estática y la dinámica para


representar el TDA lista encadenada?
Una lista encadenada está formada por un conjunto de nodos ligados
entre sí. Se denomina nodo de lista encadenada al espacio de memoria
que almacena un elemento de la lista y la dirección donde se encuen-
a tra el siguiente elemento de la estructura (figura 4.2).

r { , . ~
a
N| ..----
Valor del rección
elemento del siguiente
elemento X

\ ) / /
Figura 4.2. Esquema de nodo para una lista encadenada.

La lista encadenada puede representarse en memoria estática utili­


zando un arreglo de nodos o, más común, con memoria dinámica utilizan­
do nodos encadenados a través de apuntadores. \
Cualquiera que sea la representación seleccionada debe existir un
apuntador principal, externo a la estructura, que almacene la dirección
del primer elemento de la lista, ya que, a partir de él, se encadena el
resto de los elementos (cada uno guarda la dirección de su sucesor).
La figura 4.3 muestra el esquema de una lista encadenada en memo­
ria estática y la figura 4.4 el equivalente en memoria dinámica.

f Posición del 1
Posición Elemento siguiente elemento

1 R 7
~~2~
~3~
~4~ ~Q~ ~~6~~
5
6 Z 1
~7 ~ u '. ninguna
8

Posición del primer elemento = 4

Figura 4.3. Lista encadenada con memoria estática.

@ •
¿Cómo se puede implementar una lista encadenada en lenguaje C++?

f"= \
v , 3
Figura 4.4. Lista encadenada con memoria dinámica.

Cualquier operación que se efectúe sobre una lista encadenada


debe cuidar el correcto encadenamiento de los elementos, ya que un
\ apuntador mal colocado provocará pérdidas en la lista y afectará la
N. \ integridad de la estructura.

¿Cómo se puede implementar una lista encadenada


en lenguaje C++?

/ '} La implementación de una lista encadenada requiere primero de un


/ / tipo de dato que represente a un nodo que, a su vez, permita agrupar el
/ / valor almacenado y la dirección del siguiente elemento.
/ / Las opciones para esta representación, que ofrece el lenguaje C++,
/ / son a través de un objeto o un registro (struct).
** Dado que un nodo sirve para formar una lista, es recomendable que
en cualquier representación seleccionada para el nodo, se pueda tener
acceso a los elementos que lo conforman; es decir, los atributos deben
ser públicos.
A continuación se muestran las dos opciones de representación:

classNodo struct Nodo


{public: {
tipoinfo info; tipoinfo info;
Nodo* % Nodo* sig;
Noda() { sig = NULL; } Nodo() { sig = NULL;}
Nodo (tipoinfo dato ) Nodo (tipoinfo dato )
{ info = dato; sig = NULL; } { info = dato; sig - NULL; }
}: h

Una vez definido el tipo de dato de los nodos, la representación de


la lista encadenada se especifica con un apuntador al inicio de la lista,
como se muestra a continuación:

• 6
Listas Encadenadas

class Lista
{prívate:
Nodo* inicio;
public:
Lista() {inicio = NULL;}
~Lista() {//instrucciones para liberar los nodos de la lista }
• // otros métodos, según el diseño
};

¿Cuáles son las situaciones típicas de implementación


en una lista encadenada? / / *
/
Para visualizar las situaciones más comunes de implementación de una
lista encadenada, se analizarán diversos ejemplos. /
/

^ Ejemplo ¡
r \ \ \
Ejemplo 1 Agregar un nodo inmediatamente después del nodo
apuntado por P en la lista de la figura 4.5:

(meto

Figura 4.5.

En este caso, se puede observar que la secuencia de pasos será:

1. Crear un nuevo nodo con un apuntador auxiliar.


2. Encadenar el nuevo nodo al siguiente nodo de P.
3. Apuntar el siguiente de P al nuevo nodo.

Esto se puede visualizar como en la figura 4.6:


V J

@ •
Ejemplo

Ü Ejemplo (continúa)

( ' ^
inicio — J B—|
2

^ \ \ iP
\ ^ \ Figura 4.6.

\ Es muy importante que el paso 2 se realice antes que el paso 3, pues


\ \ de lo contrario, se perdería la referencia de todos los nodos que seguían
al nodo apuntado por P.
\ Í Las instrucciones equivalentes a este análisis, en lenguaje C/C++,
serían:

/ 1. Nodo* Q = new Nodo;


I j 2. Q->sig = P->sig;
/ 3. P->sig = Q;

^ / Ejemplo 2. Eliminar el nodo que está después del nodo apuntado por
,.--''' P en la figura 4.7.

inicio

Figura. 4.7.

Aquí, se puede observar que la secuencia de pasos será:

1. Colocar un apuntador auxiliar en el nodo que se va a borrar.


2. Reacomodar el encadenamiento haciendo un puente del nodo
apuntado por P al que ahora será su siguiente.
3. Liberar el nodo que se desea borrar.

Esto se puede visualizar en la figura 4.8:

• 0
Listas E n t r e n a d a s

^ E j e m p l o (continúa)

í inicio 1
\
Q
3

• | |-j*- —*| \z *" ■H IT—H |/_


p
\ / * 2
Figura 4.8.

Al igual que en el caso anterior, es importante que se siga la secuen-


cia mencionada; si no es así, se perdería la referencia al nodo que se
quiere borrar.
Las instrucciones equivalentes a este análisis, en lenguaje C/C++,
serían:
\. Nodo *Q = P-> sig;
2. P -> sig = Q -> sig ; II o también P ->sig = P ->sig -> sig ;
3.delete Q;
Ejemplo 3. Desplegar la información contenida en una lista.
Puesto que se requiere recorrer todos los nodos de la lista desple-
gándolos, será importante utilizar un apuntador auxiliar que permita
hacer de la siguiente forma los movimientos sobre la lista:
1. Colocar un apuntador auxiliar al inicio de la lista.
2. Desplegar la información del nodo.
3. Mover el apuntador auxiliar hacia el siguiente nodo.
4. Repetir los pasos 2 y 3 hasta que se termine la lista.
Estos pasos se pueden traducir, en términos del lenguaje C/C++, de
la siguiente manera:
Nodo *p;
p = inicio ;
while (p .'= NUIL)
{ coui « p -> info ;
p =p -> sig ;
}
Esta estructura de ciclo que recorre todos los nodos puede ser útil
para otras aplicaciones donde se requiera visitar todos los nodos de la
lista.

Ejemplo 4. Eliminar el nodo señalado por el apuntador P en la lista de


la figura 4.9.

O •
¿Cuales son las uariantes de una lista encadenada?

^r E j e m p l o (continúa)

inicio

^-N. Figura 4.9.

X ¿Se puede eliminar ejecutando sólo la instrucción delete P$ Obvia-


\ mente si', pero esta acción dejaría "volando" el resto de los nodos de la
lista. Es importante observar que, primero, se debe "hacer el puente"
del nodo anterior al apuntado por P, al siguiente nodo del señalado por
P. Sin embargo, para llegar al nodo anterior al apuntado por P, se re­
querirá de un apuntador auxiliar que recorra la lista hasta llegar a ese
nodo y, posteriormente, realizar las instrucciones ya conocidas. El cd-
/ digo que realiza esta acción es:
/ i

/ / Nodo *q;
/ / q = inicio;
whüe (q->sig!=P) II este ciclo recorre al apuntador q hasta que esté en el nodo
q = q->sig; II anterior al que se desea borrar
.,.*''' q->sig=P->sig; II esta instrucción "hace el puente"
delete P;

Los casos que hemos analizado representan todas las situaciones tí­
picas con las que se puede enfrentar un programador al trabajar con
una lista; si se comprenden, es fácil extrapolar las situaciones con di- •
ferentes variantes.

¿Cuáles son las variantes de una lista encadenada?


Si se consideran las características de una lista encadenada es posible
generar diversas variantes de gran utilidad. Entre las más comunes se
encuentran:

• Listas encadenadas circulares


• Listas doblemente encadenadas
• Listas doblemente encadenadas circulares
• Listas con múltiples encadenamientos

• 0
Listas Encadenadas

¿Cómo es una lista encadenada circular?


La lista encadenada circular es una ligera variante de la lista encadena­
da lineal; se obtiene al considerar el "primer" nodo de la lista como el su­
cesor del último nodo, almacenando la dirección del primer elemento en
el campo de dirección del último, en lugar de almacenar la dirección nu-
0 la. Al realizar esta modificación se genera automáticamente un círculo,
donde realmente ya no existe ni el primero ni un último elemento.

Características generales de una lista circular


• Es necesario mantener un apuntador general a la estructura (Lista),
aunque ya no apunte obligatoriamente al primer elemento.
• La dirección nula no existe en la lista, excepto cuando la lista está /
vacía.
• Si la lista contiene un solo elemento, el campo de dirección
apuntará a ese mismo nodo.
• Como la lista es un círculo, es posible llegar a cualquier nodo de la
lista a partir de cualquiera de sus nodos, lo que es imposible en una
lista lineal (figura 4.10).

( l
Lista
^ \\ \ \

■ ' ' ' ' ' ' ' ^ " " * * - » .

v. J
Figura 4.10. Esquema de una lista encadenada circular.

A continuación se muestra el código en lenguaje C++ de las instruc­


ciones que sirven para desplegar la información de una lista circular:

if (inicio != NULL)
{ P = inicio ;
do
{ coitt « P -> info ;
P~P-> sig ;
} while (P != inicio) }

Observe la importancia de validar el caso extremo de la lista vacía, y


de controlar con un ciclo do...while el desplegado de la información.

Q •
¿Como es una lista doblemente encadenada?

¿Cómo es una lista doblemente encadenada?

Estas listas presentan una variación más sustancial que la anterior. Este
tipo de listas se utilizan cuando la aplicación sobre una lista encadena­
da debe recorrer la lista en ambos sentidos, algo imposible de realizar
en una lista encadenada lineal y, aunque es posible hacerlo en una
lista circular, resulta muy ineficiente.

*-,., Características generales de una lista doblemente encadenada


• Se requiere mantener un apuntador general a la estructura (Lista), a
"^^ pesar de que los nodos conocen a su predecesor y a su sucesor. No
N^ es necesario que lista apunte al primer elemento de la lista, ya que
\ siempre se podrá llegar a él (figura 4.11).
\ \ • Cada nodo requiere almacenar, además del elemento, dos direccio­
nes: la del predecesor y la del sucesor.
\ • El movimiento de apuntadores puede hacerse en ambas direcciones.

/ I (
/ / Üsta
^
y / Lrn^m3-~Em-íE0
^ / \ /
„.*•'' Figura 4.11. Esquema de una üsta doblemente encadenada.

Los nodos de una lista doblemente encadenada se definirían de la


siguiente manera en el lenguaje C++:

class Nodo
[public:
tipoinfo info;
Nodo *sig, *ant;
Nodo() { sig = ant = NÜLL; J
}■>

La eliminación del nodo señalado por el apuntador P en una lista


doblemente encadenada, requerirá de las siguientes instrucciones en
lenguaje C++:

P~>ant->sig = P->sig;
P->sig->am = P->mt;
delete P;

• £
Listas Encadenadas

La inserción de un nodo después del nodo señalado por el apunta­


dor P en una lista doblemente encadenada, requerirá de las siguientes
instrucciones en lenguaje C++:

Nodo *nuevo = new Nodo;


nuevo->sig = P->sig;
• nuevo->ant = P->ant.
P->sig->ant = nuevo;
P->sig = nuevo;

¿Cómo es una lista doblemente encadenada circular? / /*"

En este tipo de listas, cada nodo almacena la dirección de su sucesor y /


de su predecesor. No existe un inicio ni un final. /
t f

Características generales de una lista circular doblemente enca­


denada circular
• Se requiere mantener un apuntador general a la estructura (Lista),
a pesar de que los nodos conocen a su predecesor y a su sucesor. \
Este apuntador puede guardar la dirección de cualquier nodo de
la lista (figura 4.12). \ \
• Cada nodo requiere almacenar, además del elemento, dos direc­
ciones (la del predecesor y la del sucesor).
• La dirección nula no existe en la lista, excepto cuando está vacía.
• Si la lista contiene un elemento, el campo de dirección apuntará a
ese mismo nodo.
• El movimiento de apuntadores puede hacerse en ambas direcciones.

( ' i r—i 1 Lista


^1

V )
Figura 4.12. Esquema de una lista doblemente encadenada circulan

6 •
Ejercicios

¿Cómo es una lista con múltiples encadenamientos?

Esta variante permite mezclar listas que contienen diferentes elemen­


tos. Lo anterior se logra cuando los nodos almacenan la dirección de
otra lista (figura 4.13). Estas mezclas pueden llegar a ser tan comple­
jas como la aplicación lo requiera.

ü!M
( f- 1

/ / L™ )
/ /' Figura 4.13. Esquema de una lista con múltiples encadenamientos.

( EJERCICIOS ) #
i •

Resuelva cada uno de los siguientes ejercicios sobre listas encadena­


das. Considere que cada uno debe programarse como si fuera un mé­
todo de la clase Lista. Para esto, tome en cuenta la definición de la cla­
se Lista expücada previamente. Desarrolle los métodos en C++.

1. Escriba la implementación de un método llamado: MIEMBRO_DE,


el cual recibe un dato y verifique si existe en la lista. El método re­
gresará un 1 si el elemento se encuentra dentro de la hsta o un 0, en
caso contrario.

• Q
Listas Encadenadas

2. Escriba la implementación de un método llamado: ÍNDICE, el cual


recibe un dato y regresa la posición donde se ubica la primera ocu­
rrencia de dicho dato. Considere que el primer elemento de la lista
se encuentra en la posición 1 y así sucesivamente. En caso de no en­
contrarse el dato, el método deberá regresar un 0.

• 3. Escriba la implementación de un método llamado CUENTA_DATOS,


el cual recibe un dato y genera como resultado la cantidad de veces
que existe el dato en la lista. El método no modifica la lista y se debe
considerar que la lista puede o no tener elementos.

4. Escriba la implementación de un método llamado COPIA, que sirva


para generar como salida una copia de la lista. El método, al ejecu­
tarse, debe generar nuevos nodos para la lista que será una copia.

5. Escriba la implementación de un método denominado IMPARES, /


que elimina los nodos que se encuentren en posiciones impares de
la lista; es decir, borrará al primer elemento, al tercero, al quinto y
así sucesivamente. Se puede suponer que la lista no está vacía. \

6. A continuación se describe un método para la clase lista. Implemén- \


telo en C++. \ \
t \ .
> N i

MEZCLA
FUNCIONAMIENTO: une, en forma alterna, los elementos de dos listas. \ . ^
ENTRADA: dos listas encadenadas, Ll y L2.
SALIDA: la lista Ll contiene los elementos de las dos listas,
acomodados de manera alterna.
PRECONDICIÓN: las listas existen y no están vacías.
POSTCONDICIÓN: la lista L2 queda vacía y la Ll queda con los elementos
en forma alterna.

7. Escriba la implementación de un método llamado INVIERTE, que


invertirá el orden original de los elementos en la lista, de tal forma
que el último elemento será ahora el primero, el penúltimo será el
segundo, y así hasta que el primero sea el último. Considere que la
lista no está vacía y que no se construirá una nueva, sólo se inverti­
rá el orden de los elementos de la lista original.

8. A continuación se describe un método para la clase Lista. Implemén-


telo en C++.

fj •
Hutoeualuaciort

SPLIT
FUNCIONAMIENTO: divicie los elementos de una lista, de tal forma, que se
convierte en dos nuevas listas. Los elementos de la pri­
mera lista serán los que se encontraban en las posiciones
1, 3, 5 ... de la lista original y la segunda tendrá los de­
más elementos. No crea dos nuevas listas, sólo divide la
original.
ENTRADA: una lista encadenada (L).
SALIDA: dos listas (IMPARES y PARES).
PRECONDICIÓN: la lista existe.
"**-*, POSTCONDICIÓN: la lista Lqueda sin elementos. Las listas PARES e IMPA-
*\, RES contienen a los elementos de la lista original, de
^\ \ acuerdo con su posición.
^^ \
^v *
X %

\ \ r—nuTOEURLuncióN <\ =
i ■ I
: i i
1 L _ _ _ _ _ - — — — - - _ — — _ _ - — _ _ _ _ _ _ _ _ _ _ _ _ . . _ _ _ — - J

/ / 1. Suponga que se tiene la siguiente lista encadenada (representada en


/ / un arreglo de nodos):
/ *
X *
y/ / INFO SIG
• ^ y' [11 | ADRIANA" 7~

.-''' [2] CARLOS 3


m omet. ^~0~ „*.«„«*,,
[41 FRANCISCO 1 extemo a la c™=4
[51 " JORGE 6 '
[61 CLAUDIA 8
[71 ALEX 5_
[B] ROGEUO 3 •

Si se elimina de la lista el nodo que contiene a ALEX como infor- I


mación, ¿cuál de los enunciados es verdadero? 1
a) El nodo que guarda a ADRIANA b) El nodo que guarda a FRANCISCO
ahora tendrá como valor en SIG la ahora tendrá como valor en SIG la
posición 5. posición 7.

c) El nodo que guarda a ALEX ahora d) El nodo que guarda a ADRIANA


tendrá como valor en SIG la posi- ahora tendrá como valor en SIG la
ción 1. posición 7.

e) El nodo que guarda a JORGE ahora


tendrá como valor en SIG la posi-

.— a
Listas Encadenadas

2. ¿Cuál de los códigos une dos listas encadenadas que ya tienen valores
para formar una sola lista? Utilice la siguiente declaración:

struct Nodo
{int valor; Nodo *sig:}:
Nodo *aux.
*lista!, *lista2; ¡I donde ¡istal v lista2 son apuntadores ai primer nodo de cada lista.

a) aux = lista/; b) aux = lista!;


while (aux != NULL) while (aux->sig != NULL)
aux = aux->sig; aux = aux->sig;
aux->sig = lista!; aux->sig = lista!;

c) aux = listal; d) aux = lista!;


while (aux != lista!) while (aux->sig != NULL)
aux = aux->sig; aux = aux~>sig;
aux = lista!; aux->sig = lista!;

e) Ninguna de las anteriores

3. Dada la lista doblemente encadenada circular, ¿cuál es el dato que


se despliega en pantalla al ejecutar la siguiente instrucción?

cout« lista~>sig->ant->ant->info;

Usta

a) A b) B c) C d) D e) E

Q •
CapítulolJ
• H m

OBJETIVOS
• Definir qué es un string y cómo se diseña bajo las reglas de la abstrac­
ción de datos.
• Conocer algunas de las opciones de representación para el TDA
string, analizando sus ventajas y desventajas.
Strings

¿Qué es un string y por qué es importante su análisis?

La palabra string hace referencia a una cadena de caracteres. Su estu­


dio cobra importancia cuando se toma en consideración que la mayor
parte de la información que se maneja actualmente en casi cualquier
área del conocimiento, puede verse como una secuencia de caracteres.
• El nombre de una persona, el contenido de un libro, la información
presentada en este texto pueden ser vistas como cadenas de caracteres
o strings; por lo tanto, es relevante cuestionar cómo almacenarlos efi­
cientemente y, todavía más, que operaciones pueden hacerse sobre los
strings. Esto nos lleva a pensar que un string debe definirse como una
estructura de datos y por ello es conveniente que se diseñe inicialmcnte /
como un TDA. / /

¿De qué forma conceptualizar un string como un /


TDA?

Dada la importancia de los strings, es conveniente considerarlos como


una estructura de datos que, como se sabe, contendrá el conjunto de
operaciones más relevantes por realizar. Partiendo de esto, el primer
paso sería definir el TDA que representa la estructura que se desea
diseñar.
Se pueden diseñar muchos TDA que definan un string considerando
qué operaciones se tomarán como básicas para el funcionamiento de
dicha estructura y cuál será el conjunto de caracteres que se aceptarán
como parte de él.
Una posible definición para el TDA de un string sería la siguiente:

^¿£ i TDA STRING


ELEMENTOS: conjunto de letras (mayúsculas y minúsculas), caracteres
especiales y dígitos.
ESTRUCTURA: dado que el string se considera una cadena, se puede pensar
que se establece una relación lineal entre sus elementos.
DOMINIO: suponiendo que los strings pueden almacenar entre 0 y 80 ele­
mentos. entonces su dominio será el conjunto de todas las
cadenas que se puedan formar con combinaciones de los ele­
mentos y que cumplan con la regla de longitud.

V _ )

0 .
¿Cómo se puede representar el TDfl del string?

í ^
Algunas de las operaciones más importantes para strings pudieran
diseñarse de la siguiente forma:
IN1CIALIZAR
ENTRADA: espacio de memoria para un string
SALIDA: el string S es nulo (está vacío)
PRECONDICION: ninguna
POSTCONDICIÓN: el string S es nulo

INSERTAR_CARÁCTER_AL_FINAL
"~^^ \ ENTRADA: string S y la letra L que se insertará al final de S
^ \ \ SALIDA: string S modificado
\ \ PRECONDICION: el número de letras en S es menor que 80
\ \ POSTCONDICIÓN: el string S tiene una letra más en el extremo derecho

\ \ BORRAR_CARÁCTERJNICIAL
\ ENTRADA: string S al que se le borrará la primera letra.
SALIDA: string S modificado
PRECONDICION: la cantidad de letras en S es mayor o igual que 1.
/ POSTCONDICIÓN: el string S tiene una letra menos en el extremo
/ izquierdo.
/ t

/ / CONCATENAR
/ / ENTRADA: strings SI y S2
y / / SALIDA: string SI modificado
^ ^ / PRECONDICION: La suma de la longitud de S1 y S2 es menor que 80
,,--'' POSTCONDICIÓN: A string SI se le agregan todos los caracteres de S2
,..--''' (en el extremo derecho); S2 no se modifica

BUSCAR_SUBSTRING
ENTRADA: strings SI y S2
SALIDA: verdadero si S2 está contenida en S1, falso en caso
contrario •
PRECONDICION: si y s2 existen
POSTCONDICIÓN: ninguna

V )

¿Cómo se puede representar el TDA del string?


Hay diversas posibilidades para representar una cadena de caracteres.
Se puede almacenar en la memoria contigua, como sería el caso de un
arreglo; también podría utilizarse almacenamiento no contiguo, como
las listas encadenadas. Incluso en estas dos posibilidades se pueden
generar una gran cantidad de variantes.

• 0
Strings

¿Cuáles son las formas más comunes de representación


con almacenamiento contiguo?
Generalmente, la representación en memoria contigua se basa en el uso
de arreglos de caracteres. Los más utilizados son:

• Forma 1: uso de un carácter centinela


En este tipo de representación, los caracteres que conforman el string
se almacenan en un arreglo de caracteres con longitud predefinida. El
string actual se delimita con un carácter especial (que no forma parte
del conjunto de elementos del string) colocado al final de los caracteres
que están almacenados en él. Obviamente, en este tipo de repre­
sentación se debe considerar que el centinela es un carácter que ocu­
pará un espacio; por lo tanto, se debe tomar en cuenta al calcular la lon- /
gitud máxima que tendrá el string. / /
La principal desventaja de este tipo de representación es que se
desconoce la longitud actual de un string, elemento que se requiere
para realizar gran cantidad de operaciones. Cuando esto ocurre, este
valor se debe calcular continuamente. Por ejemplo, en el lenguaje C,
los strings están delimitados por el carácter nulo, representado por la
secuencia '\0'. En la figura 5.1 se muestra gráficamente este tipo de
representación.

H OÍL AT
V.
carácter centinela
V [enCes\0] j

Figura 5.1.

Forma 2: almacenamiento de la longitud actual de un string


En este tipo de representación, también los caracteres se almacenan en
un arreglo de extensión predefinida. En este modelo la longitud actual
del string se almacena en la primera posición del arreglo (generalmente
la posición 0), que no forma parte del string, aunque comparte la
misma definición de datos.
La principal desventaja de este tipo de representación es que,
debido a que la longitud se almacena en una posición de tipo carácter,
la longitud máxima del string se restringe (a 255 caracteres).
Este tipo de representación se emplea para almacenar los strings en
el lenguaje Pascal. Gráficamente, este tipo de representación se mues­
tra en la figura 5.2.

o •
¿Cuáles son las formas más comunes de representación con almacenamiento no contiguo?

r 0 i máx

4 H 0 . L A - - - - -

realmente almacena el
L carácter con ASCII4 j

Figura 5.2.

¿Cuáles son las formas más comunes de representación


con almacenamiento no contiguo?

\ \ Cuando se emplea almacenamiento no contiguo, generalmente se usan


\ \ listas encadenadas debido a la relación lineal que se establece entre los
\ \ elementos que conforman el string. Las más comunes, dentro de las va-
\ riantes que se pueden generar con este formato, son:

Forma 1: lista encadenada de nodos con un carácter por nodo


/ / En este modelo se genera una lista encadenada donde cada uno de los
/ / nodos almacena únicamente a uno de los caracteres del string y la di-
/ / rección donde se encuentra el siguiente nodo de la lista (figura 5.3).
^ / / La principal ventaja de este tipo de representación es que se pue­
den generar strings de longitud "infinita" y aprovechar fácilmente
,,■'' las operaciones definidas, sobre una lista encadenada para implantar las
operaciones propias del string. Sin embargo, este modelo presenta una
fuerte desventaja: desperdicia mucha memoria (hasta 80%) debido al
almacenamiento de tantas direcciones de nodos que es indispensable
guardar

string

-^r~n"
U ~~Li o nLÍ~L L j A11/
V.
Figura 5.3.
^ ^ ^ ^ ^ J

• Q
Strings

Forma 2: lista encadenada de nodos con N caracteres por nodo


En este tipo de representación se genera una lista encadenada donde
cada uno de los nodos almacena N caracteres del string (N > 1) y la di­
rección donde se encuentra el siguente nodo de la lista.
La principal ventaja de este tipo de representación es que se pueden
generar strings de longitud "infinita" y se disminuye un poco el despea
• dicio de memoria generado con la representación anterior. Sin embargo,
este modelo tiene una gran desventaja: la implemcntación de sus ope­
raciones es más compleja, ya que la información contenida en el string
está almacenada en forma de substrings en diferentes nodos de una lis­
ta encadenada, lo que dificulta el movimiento de caracteres dentro del
string. S
Dependiendo del valor que se establezca para N, gráficamente este
modelo se muestra en la figura 5.4, al suponer un valor de N = 6:

strinc

v
— _J . ,.. -^ \
Figura 5.4.

i ( EJERCICIOS ) &

1. La especificación del TDA string realizada por un diseñador contie­


ne la siguiente descripción de operaciones, que se han considerado
como básicas o primitivas:

CREAR
ENTRADA: espacio en memoria que se quiere trabajar como siring
SALIDA: espacio de memoria inicializado como string
PRECONDICIÓN: ninguna
POSTCONDICIÓN: string inicializado como string nulo (sin caracteres)
-->: esta operación también puede emplearse para limpiar un string que ya
contiene caracteres al volverlo string nulo

Que exista el string S, es una precondición para las siguientes opera­


ciones:

hA 9
Ejercicios

LONGITUD
ENTRADA: string S del que se desea conocer su longitud
SALIDA: canüdad de caracteres que contiene el string S
PRECONDICIÓN: ninguna
POSTCONDICIÓN: ninguna

ÜAMECAR
ENTRADAS: String S donde se desea conocer cierto carácter
Posición J del carácter que se desea conocer
SALIDA: carácter que se encuentra en la posición J del string S
*"--^ PRECONDICIÓN: el string S no puede ser nulo y J debe ser un valor entre 1
***. y la longitud de S
■^^ POSTCONDICIÓN: ninguna

\ \ AÑADE_CAR
\ \ ENTRADAS: string S donde se desea añadir un carácter y el carácter C
\ \ añadir.
\ \ SALIDA: string S modificado
\ PRECONDICIÓN: la longitud del string S debe ser menor a la longitud
1 i máxima posible.
POSTCONDICIÓN: el string S contiene todos los caracteres originales en el
/ mismo orden y a la derecha de S se añadió el carácter C.
I P

/ / BORRA_CAR
/ / ENTRADA: string S donde se desea borrar un carácter
/ SALIDAS: carácter C que es el primer carácter del string S y el string
y / S modificado
PRECONDICIÓN: el string S no es nulo
/ POSTCONDICIÓN: el String S contiene todos los caracteres originales.
..--**' excepto el que ocupaba la primera posición. Los
caracteres se reacomodan en el mismo orden, de tal forma
que el segundo ahora es el primero, el tercero el segundo
y así sucesivamente

Por otro lado, el implementador del TDA, al conocer esta especi­


ficación, propone como opciones de representación de un string las
siguientes:
Forma 1: lista encadenada en memoria dinámica, donde cada nodo
guarda un carácter.
Forma 2: lista encadenada en memoria dinámica, donde cada nodo
guarda más de un carácter.
Forma 3: arreglo unidimensional en memoria estática, donde el pri­
mer espacio de arreglo guarda la longitud real del string
almacenado.
Forma 4: vector de concatenaciones de strings y tabla de apuntado­
res en memoria estática.

• — -
Strings

Considere la información anterior para responder cada uno de los si­


guientes incisos:

a) Si se supone que el implementador decidió emplear la representa­


ción de la Forma 1, escriba la implementación completa en lengua­
je C de la operación AÑADE_CAR siguiendo los detalles mostra-
• dos en la especificación y tomando en cuenta la siguiente definición
de tipos:

typedef char tipoinfo


typedef struct nodo
{ tipoinfo car;
struct nodo *sig; } tiponodo;
typedef tiponodo *apnodo;
i /
t f

b) Si se supone que el implementador decidió utilizar la representación


de la Forma 2 con 10 caracteres por nodo, reescriba la definición del
tiponodo (mostrada en el inciso a) para que sirva en esta represen­
tación. \
c) Basándose en la representación del inciso anterior (Forma 2), escri­
ba la implantación completa, en lenguaje C, de la operación
CREAR siguiendo los detalles mostrados en la especificación.
(suponga que si el apuntador al string (lista) no es igual a nulo, el
string tiene caracteres que deben eliminarse).
d) Si se supone que el implementador decidió usar la representación de
la Forma 3, escriba la implementación en lenguaje C de la opera­
ción BORRA_CAR siguiendo los detalles mostrados en la especifi­
cación del TDA y con la siguiente definición de tipos:
typedef char string(256];
e) Adicionalmente a las operaciones básicas del TDA string, el diseña­
dor supuso otras operaciones generales que pueden implementarse
con base en las cinco operaciones básicas.
A continuación se muestra, de manera incompleta, la especifica­
ción de tres de estas operaciones generales. Complete los puntos
que hacen falta en la especificación de las operaciones.

CONCATENAR
ENTRADA: dos strings SI y S2 que se desean unir
SALIDA: string S1 modificado
PRECONDICIÓN:
POSTCONDICIÓN: el string S I contiene los caracteres originales seguidos de
los caracteres del string S2. el cual no se modifica

A #
Ejercicios

COPIAR
ENTRADA: dos strings S1 y S2 (S2 se copiará en S1)
SALIDA: string SI modificado (igual a S2).
PRECONDICIÓN: ninguna
POSTCONDICIÓN:

REMOVER
ENTRADAS: string S, posición P desde donde se desea remover y
cantidad N de caracteres a remover
SALIDA:
PRECONDICIÓN: _ _ ^ _ ^ .
\ POSTCONDICIÓN: el string S contiene los caracteres originales, excepto los
"*"*•«•*. \ N caracteres que se encontraban a partir de la posición P
de S. Se recorrieron los caracteres necesarios en S

\ \ f) Basándose en la representación de la Forma 1 (inciso a) y los enca-


\ \ bezados de los módulos de las operaciones básicas que se muestran
\ a continuación, escriba la implementación en lenguaje C de la ope­
ración CONCATENAR (cuya especificación se obtuvo en el inciso
anterior), haciendo uso del nivel aplicación de las cinco operaciones
/ / básicas.
I *
J i void crear (apnodo *str);
/ / int longitud (apnodo str);
^ / / tipoinfo dame_car (apnodo str, intj);
^ ^ ./ void añade_car (apnodo *str, tipoinfo car);
^■■'' tipoinfo borra_car (apnodo *str);

g) Si se supone que el implementador decidió utilizar la representación


de la Forma 4, y en determinado momento la estructura de datos en
esta representación contiene la información que se muestra en la si­
guiente gráfica: *

• e
Strings

TABLA DE
STRINGS

I '" i—I— VECTOR DE CONCATENACIONES


S1-> 4 12 - ¡ l i l i ! r T - r ■ 1 i I i r
L A S U E R T E
S2-> 1B 0 "1 I I l i l i l í I 1 I 1 | 1I
En 21 22 2?, ?A 25 26 27 sé aa 30 31 :IS 33 M 3b...
S3-> 1 3

• S4-> IB 4
S5-> ~22 EP Apuntador al siguiente espacio disponible en el
vector de concatenaciones = 28 ,--"'"

Apuntador Longitud del ¿f


al veaor de string /
concatenaciones •' /
*t
t
//
i
i /

Muestre, a través de un esquema, cuál sería el estado de la estructura


de datos después de ejecutar eficientemente la operación COPIAR,
dada la especificación del inciso y suponiendo que se desea copiar el
string S5 en el string S3. \ \
\ \
i \

: - n u T O E U H L u n c i ó N Q^ = V

1. Considere la siguiente definición para un string:


typedefcharstring[256];
stringS;

Si este espacio de almacenamiento contiguo fuera a utilizarse bajo la


representación de: Almacenamiento de la longitud actual en el mismo
string; y al string 5 se le insertara la frase: ANTiTALAVALATINA ((u­
ya longitud es 15 caracteres), ¿cuál de los siguientes incisos describe
el contenido de la posición O de S?

a) SfO] almacena el número b) SLOJ almacena la letra A.


entero 15
c) S[0] almacena un carácter d) S[01 almacena l y S[l]
cuyo código ASCII es 15 almacena un 5.
e) Ninguna de las anteriores

0 •
Rutoelaluación

2. Considerando la definición para un string empleada en el problema


anterior, analice los siguientes fragmentos de código y seleccione
aquel que implemente la operación de ELIMINAR el último carácter
del string S.

a)for (i=j;i==S[0];i++); b) S[0}~;


S[iJ=";

"--. ü)S[S[0J]= "; d)for(i=S[0];i== };i~)


* \ S[0]-; Sfi]=S[i+l];
^ \ \ S[OJ=S[0J-];
>v \ e) Cualquiera de las anteriores es
\ \ una implementación válida
\ l

\ 3. Suponiendo que se tienen 2 strings que han sido representados en


\ \ listas encadenadas en las que cada nodo guarda un carácter, ¿cuál de
los siguientes fragmentos de código representa la implementación
I : de la operación CONCATENAR dos strings? Tome en cuenta lo si-
/ I guiente: al string SI se le concatenará el string S2 y los apuntadores
/ / apS1 y apS2 están direccionando al lnicio de cada uno de estos strings.
/ i

/ /
/ t

y^ / a) aux=aPSl b) aux=aPSI
^ / whüe(aux>=NULL) while(aux->sig!=NULL)
y / aux=aux->sig; aux=aux->sig;
aux->sig^apS2; aux->sig=apS2;

c) aux=aPSl; d) aux^apS2;
while(aux!=apS2) while(aux->sig!=NULL)
aux=aux->sig; aux=aux->sig; •
aux=apS2; aux->sig^apS2;

^Ninguna de las anteriores.

. 0
Capítulo j r l

OBJETIVOS
• Explicar en qué consisten las estructuras de datos pila y fila (Cola), y
cuáles son sus aplicaciones principales.
•Diseñar los TDA pila y fila.
• Describir las diferentes opciones para representar los TDA pila y fila,
analizando ventajas y desventajas.
• Conocer la forma en que se maneja un arreglo circular y cómo se
aplica en la representación de una fila.
• Integrar los conceptos de abstracción de datos, programación orien­
tada a objetos y listas encadenadas en memoria dinámica, en el con­
texto de los TDA pila y fila.

Q
Pilas y rilas

¿Qué es una pila?

Una pila es un conjunto ordenado de objetos los cuales pueden obte­


nerse (uno a la vez) siguiendo un orden especial: el último que entró
en la pila es el primero en salir (figura 6.1). Así, en una cafetería de au­
toservicio se tienen apiladas las charolas que emplearán los clientes y
• cada uno toma la que se encuentra hasta arriba (en el tope) y la última
charola que se toma es precisamente la primera que se colocó en la pila.
De la misma manera, se puede tener una pila de libros, una pila de
ladrillos, una bolsa de pan de caja, etcétera.

/ /
V -* tope
í /

Ü S ^- fondo
"• \
> \
Figura 6.1.

¿Qué es la estructura de datos pila?


Esta última idea de obtener el elemento del fondo de la pila, a través
del acceso de los elementos que están por encima de él, sirve para ha­
cer la definición abstracta de la estructura de datos pila. Esto es una
estructura de datos lineal de objetos ordenados de forma tal que éstos
se obtienen por un solo lado de la estructura siguiendo un sistema
UEPS (Último en Entrar, Primero en Salir) o en inglés LIFO {Last fnput,
First Ouipul).
La pila es una estructura finita. Los elementos pueden identificarse
por la posición en que están: el último elemento de la pila se encuen­
tra en el tope, enseguida está el otro, y así sucesivamente. Para llegar
al objeto del fondo, es necesario obtener primero los que le anteceden.
Por consiguiente, siempre se deberá tener en cuenta donde se encuen­
tra el último elemento añadido, es decir, el tope de la pila.

0 •
¿Qué es la estructura de datos fila?

¿Qué es una fila (cola)?


Diariamente vivimos las filas. En los supermercados, en los teatros, en
los bancos, hacemos filas con otras personas que esperan ser atendidas
(figura 6.2). Existen modelos de líneas de espera que estudian el com­
portamiento de las filas tomando en cuenta factores como llegadas de
clientes, cantidad de servidores, tiempo de llegada entre clientes, tiem­
po necesario para servicio, etc. Sin embargo, aquí se hará una breve
■<.,„, explicación del concepto de fila o cola (queue en inglés) desde el punto
* \ de vista de las estructuras de datos.

\A t ^

Jj l 1|}
^ ^ / Figura 6.2.

¿Qué es la estructura de datos fila?

Aunque el estudio de la fila se hará desde el punto de vista de las es­


tructuras de datos, sigue manteniéndose el concepto de atender al pri- •
mero en la fila. Con base en esto, se puede definir una fila como una
estructura de datos cuyos elementos se manejan bajo la filosofía
(PEPS) primero en entrar, primero en salir o en inglés FIFO (First In-
put, First Ouput).
La fila es una estructura finita y lineal donde los elementos pueden
ser de cualquier tipo que se requiera. Su dominio lo forman aquellos
elementos que puedan llegar a almacenarse en la estructura, y se obtie­
nen por dos partes de la estructura: una llamada frente que señala dónde
se encuentra el siguiente elemento por atender en la fila, y otra llama­
da fina/, que significa el lugar del último que llegó. Todo esto indica
que los elementos se extraen por el frente de la fila y se agregan por
el final.

• II
Pilas y filas

¿Cuál es la relación entre pilas y filas?


Se puede observar, con base en lo explicado, que ambas estructuras
comparten muchas características, pero, en particular, ambas son es­
tructuras útiles cuando se requiere administrar el orden de entrada de
los datos que se guardarán en la estructura; es evidente que ambas re-
• querirán de operaciones que sirvan para insertar y sacar datos, pero la
diferencia se da precisamente en la forma en que se insertan y extraen
los datos. Dadas las similitudes, y considerando las diferencias, hemos
decidido explicar paralelamente estas estructuras de datos.

¿Cuáles son las aplicaciones de las pilas y filas? / /""

Cualquier aplicación que requiera administrar el orden de entrada de un


conjunto de datos requerirá alguna de estas estructuras. /
Una pila será útil cuando la aplicación requiera de un orden inverso al
orden de entrada original de los datos. Un ejemplo típico del uso de una
pila lo podemos ver en el manejo de las llamadas a módulos o subruti-
nas en un programa (razón por la que un stock está siempre presente en
la ejecución de cualquier programa, aunque sea implícito para el pro- \
gramador) Otro ejemplo del uso de una pila se da en el proceso de con- *< \
versión de expresiones a diferentes formatos (situación que se requiere
en la implantación de un compilador). \.
Una fila será útil cuando la aplicación requiera del orden estricto de
entrada de los datos. Los ejemplos del uso de una fila tienen que ver con
buffers de memoria donde se guardan temporalmente datos, por ejem­
plo, el buffer de teclado o el de impresión en un sistema computacio-
nal. Por otro lado, cualquier simulación por medio de un programa de
una situación de la vida real en la que se forman filas de personas u
objetos, requerirá esta estructura de datos.

¿Cuál es la especificación lógica para los TDA pila y fila?


Para ambas estructuras se pueden generalizar los siguientes datos de
la especificación lógica:

^Si ELEMENTOS: el tipo de los elementos depende de la aplicación de la pila o


la fila.
ESTRUCTURA: lineal
DOMINIO: la pila o la fila tendrá capacidad almacenar cualquier cantidad
de elementos, según su representación lo permita.
V J

o •
¿Cuál es la especificación lógica para los TDH pila y fila?

f >
El diseño de las operaciones es lo que realmente distingue a los
TDA pila y fila. Sin embargo, las operaciones que se diseñarán
serán las mismas para ambos.
Adicionalmente, se podrá observar que puede haber diversos di­
seños para las operaciones, dependiendo del enfoque del diseñador
lógico. En este caso, se presentan dos opciones de diseño que per­
miten ejemplificar y razonar el impacto en la abstracción de datos.

Diseño 1 para las operaciones del TDA pila

METER (PUSH)
\ \ UTILIDAD: agrega un elemento a una pila.
\ \ ENTRADAS: la pila a la que se va a agregar el elemento y el elemen-
\ \ to que se agregará.
\ SALIDAS: la pila con un elemento más.
\ PRECONDICIÓN: la pila está creada y no está llena.
POSTCONDICIÓN: la pila queda con un elemento adicional, agregado por el
extremo del tope de la pila.

/ SACAR (POP)
/ / UTILIDAD: elimina el último elemento que se agregó a una pila.
/ / ENTRADAS: la pila a la que se va a quitar un elemento.
/ / SALIDAS: la pila con un elemento menos y el elemento que se
^ S / eliminó.
PRECONDICIÓN: la pila está creada y no está vacía.
POSTCONDICIÓN: la pila queda con un elemento menos, eliminado por el
extremo del tope de la pila.

V, _ J
Ya que en estas operaciones se ha puesto como precondición la verificación del es­
tado de la pila (vacía o llena), es necesario diseñar operaciones que permitan al usuario •
de la pila hacer validaciones. Por lo tanto, se diseñan estas operaciones:

í VACÍA ^
UTILIDAD: verifica si una determinada pila está vacía o no.
ENTRADAS: la pila que se va a verificar.
SALIDAS: valor booleano que indique si la pila está vacía o no.
PRECONDICIÓN: la pila por verificar existe.
POSTCONDICIÓN: ninguna, la estructura no se modifica.
V J

• Q
T K O S L] 1 M Q 3

( \
LLENA
UTILIDAD: verifica si una pila determinada se encuentra llena o no.
ENTRADAS: la pila que se va a verificar.
SALIDAS: valor booleano que indique si la pila está llena o no.
PRECONDICIÓN: la pila por verificar existe.
POSTCONDICIÓN: ninguna, la estructura no se modifica.

• V _ )
Si se analiza un poco, el TDA fila contendría en forma equivalente las mismas
operaciones, pero considerando en las postcondiciones los cambios de acuerdo con el
manejo del orden de entrada en una fila. A continuación, se detalla este mismo diseño
para un TDA fila.

í >/ /
Diseño 1 para las operaciones del TDA fila
METER (INSERT)
UTILIDAD: agrega un elemento a una fila.
ENTRADAS: la fila a la que se agregará el elemento y el elemento por
agregar. \
SALIDAS: la fila con un elemento adicional. \ \
PRECONDICIÓN: la fila está creada y no está llena.
POSTCONDICIÓN: la fila queda con un elemento adicional, agregado por el
extremo del final de la fila. \ \ ^

SACAR (REMOVE)
UTILIDAD: elimina el último elemento que se agregó a una fila.
ENTRADAS: la fila a la que se va a quitar un elemento.
SALIDAS: la fila con un elemento menos y el elemento que se
eliminó.
PRECONDICIÓN: la fila está creada y no está vacía.
POSTCONDICIÓN: la fila queda con un elemento menos, eliminado por el
extremo del frente de la fila.

VACIA
UTILIDAD: verifica si una determinada fila se encuentra vacía o no.
ENTRADAS: la fila que se va a verificar.
SALIDAS: valor booleano que indique si la fila está vacía o no.
PRECONDICIÓN: la fila por verificar existe.
POSTCONDICIÓN: ninguna, la estructura no se modifica.

LLENA
UTILIDAD: verifica si una fila determinada se encuentra llena o no.
ENTRADAS: la fila que se va a verificar.

V __ )

@ •
¿Cuál es la especificación lógica para los TBfl pila y fila?

C >
SALIDAS: valor booleano que indique si la fila está llena o no.
PRECONDICIÓN: la fila por verificar existe.
POSTCONDICIÓN: ninguna, la estructura no se modifica.

V )
Es importante darse cuenta en este momento que, sin saber cómo
se implementará físicamente la pila o la fila, se pueden diseñar las
operaciones en un nivel abstracto.
"-... Adelantando un poco el nivel físico o de implementación, se pue­
de observar que la especificación de las operaciones puede determinar
* \ \ la interfaz que tendrán los módulos de las operaciones.
\ Por ejemplo, si el TDA pila fuera a ser implementado con este di-
\ \ seño en el lenguaje C++, es posible bosquejar el diseño de la clase
\ \ pila de la siguiente manera:
\ \ class Pila
{ prívate:
i // atributos según representación física del TDA
/ I puhlic:
i ¡ Pila(); // constructor que micializa como vacía
/ / void Meter (Tipoelem dato);
/ / Tipoelem Sacar ();
y ^ / char Llenaf);
char Vacia();
.---'"' ~Fila(); //destructor si se requiere
h
Ahora bien, si se eliminan las precondiciones que indican que las
estructuras no deben estar vacías o llenas, éstas condiciones se pueden
validar físicamente en las operaciones METER y SACAR, lo que nos •
lleva a plantear otro diseño lógico, cuya consecuencia es proveer ma­
yor comodidad al usuario de los TDA, y ya no serán necesarias las
operaciones LLENA y VACÍA.
A continuación se detalla este diseño sólo para el TDA pila, del
cual se deduce fácilmente el TDA fila.

Diseño 2 para las operaciones del TDA pila

METER (PUSH)
UTILIDAD: agrega un elemento a una pila si no está llena.
ENTRADAS: la pila a la que se va a agregar el elemento y el elemento que
se agregará.

V )
• @
Pilas y filas

t >
SALIDAS: la pila con un elemento adicional, si se pudo agregar, o la
pila como estaba. Además, la operación genera como sa­
lida un valor booleano que indica si se pudo o no agregar
el elemento.
PRECONDICIÓN: ninguna.
POSTCONDICIÓN: la pila queda con un elemento adicional por el extremo
del tope si se pudo agregar o se queda como estaba por
encontrarse llena.
SACAR (POP)
UTILIDAD: elimina el último elemento que se agregó a una pila, si es
que no está vacía.
ENTRADAS: la pila a la que se va a quitar un elemento.
SALIDAS: la pila con un elemento menos, si se pudo quitar y el ele­
mento eliminado o la pila como estaba. Además, la ope­
ración que se realiza genera como salida un valor boolea­
no que indica si se pudo o no eliminar el elemento.
PRECONDICIÓN: ninguna.
POSTCONDICIÓN: la pila queda con un elemento menos, si se pudo quitar
(eliminado por el tope) o queda como estaba en caso de
encontrarse vacía.

V ) \ \
^ Ejemplo \ X.

' C a s o de aplicación
Este es el momento para ilustrar el concepto y los beneficios de la abs­
tracción de datos, en la que se debe cumplir la independencia entre los
niveles de aplicación e implantación (físico).
El problema consiste en verificar si una frase es o no palíndromo,
esto es, si se lee igual de izquierda a derecha, que de derecha a izquier­
da. Por ejemplo: AN1TALAVALATINA.
Aunque la solución de este problema puede obtenerse de muchas
maneras, se puede observar que una pila y una fila pueden ser útiles
planteando el siguiente algoritmo:

1. Cada letra que se lea de la frase se deberá meter en una pila y en una
fila.
2. Saque un dato de la pila y de la fila. Si son iguales, repita el paso 2.
Si son diferentes, termine el proceso indicando que no es palíndromo.
3. Si la pila y la fila se vaciaron, es palíndromo.

Q •
Ejemplo

^ Ejemplo (continúa)

Observe que, sin haber implantado los TDA pila y fila, se puede de­
sarrollar una aplicación solamente conociendo la interfaz de las opera­
ciones en el lenguaje correspondiente. Esto se demostrará también en
términos de la implantación en un lenguaje de programación y, por lo
tanto, supondremos que se han planteado las siguientes definiciones en
C++:

N
Implementación parcial del TDA Pila, según el Implementación parcial del TDA Fila, según el

diseño lógico 2 diseño lógico 1


class Pifa class Fila
{ prívóte: { prívate:
//atributos, según representación del TDA //atributos, según representación del TDA
public: pubiic:
Pílaf); //constructor que inicializa como vacía Fila(); // constructor que inicializa como vacía
char Meter {Tipoelem dato); void Meter ¡Tipoelem dato);
char Sacar (Ttpoelem& dato): Tipoelem Sacar <);
~Pila(); //destructor char Llena();
I; char VaciaQ;
~Fila();//destructor
/ ^
La implementación de la aplicación se puede realizar de la siguien­
te manera, sin necesidad de conocer cómo están implementados los
TDA, lo que cumple con la independencia de niveles durante la abs­
tracción de datos:

char patináronte 0
i
Pila P;
Fila F;
Tipoelem dato;
cin.get(dato);
while (dato .'= \n')
/ PMeter(dato);
FMeter(dato);
cm.get(dato); }
while ( PSacar(dato))
if(FSacar() != dato)
return 0;
return 1;
} j

. e
¿Cómo se puede representar físicamente el TDA pila?

Las herramientas que se han presentado en los capítulos anteriores nos


permiten observar que la representación para una pila se puede hacer
con las estructuras lineales típicas de un lenguaje de programación. En
^ este caso, es indispensable distinguir entre la opción de utilizar memo­
ria estática a través de un arreglo unidimensional, o bien de utilizar me­
moria dinámica a través de una lista encadenada. Sin embargo, en
estas opciones hay gran variedad de formas de representación e im-
plementación de las operaciones, por lo que ejemplificaremos las dos
situaciones más típicas de implementación y se dejarán como ejercicio
otras posibilidades. / /
* /
Implementación utilizando memoria estática /
i

Se utilizará un arreglo unidimensional en el que se guardarán los datos


a partir de la posición inicial del mismo. La posición de inicio del arre­
glo actuará como fondo de la pila y se requerirá de un apuntador que
indique la posición donde se encuentra el tope de la pila (último ele­
mento añadido). \
Estos datos se pueden encapsular como atributos de un objeto en
caso de que se esté utilizando un lenguaje como C++, quedando de
la siguiente manera la definición de la clase, según el diseño lógico 2:
*-
class Pila
{ prívate:
tipoelem datosfMAX]; //arreglo para guardar los datos
int tope; //apuntador al último dato añadido
publie:
Pila(); //consrructor que inicializa como vacía
char Meter (Tipoelem dato);
char Sacar (Tipoelem* dato);
//NO se requiere destructor.
u

Q •
¿Como se puede representar físicamente el TDft pila?

Las operaciones se implementarán de la siguiente manera:

Pila::Pila()
{ tope=-J; ///este valor indica que no hay datos en la Pila

char Pila:.-Meter (tipoelem nuevo)


{ ifitope » MAX-l) //si el apuntador está en la última posición, ya
//no hay espacio
return 0;
else
""v. { tope++; //se apunta a un nuevo lugar disponible en el arreglo
. "N datos/tope] = nuevo; //y se copia el nuevo dato
^ \ \ return 1;
X 1
\ I
char Pila::Sacar (tipoelem & valor)
( if(tope == -1)
\ i return 0;
else
( valor = datosltope];
/ tope- ■ 1/ aunaue el valor queda en el arréelo lógicamente ya no
j i //está en la Pila
/ return1;//y su lugar puede ser ocupado en una próxima inserción.
/ / }
/ / }

Implementación utilizando memoria dinámica

Se utilizará una lista encadenada en la que se guardarán los datos en


cada nodo. Por eficiencia, conviene considerar el inicio de la lista como
el tope de la pila; por lo tanto, sólo se requiere un apuntador al inicio
de la lista para representar la pila. Este apuntador se puede encapsular *
como atributo de un objeto en el caso de que se esté utilizando un len­
guaje como C++, quedando de la siguiente manera la definición de la
clase, según el diseño lógico 2:
class Pila
I prívate:
Nodo *tope; // el apuntador externo manejará el tope
public:
Pila(); // constructor que inicializa como vacia
char Meter (Tipoelem dato);
char Sacar (Tipoelem& dato);
~Pila(); //destructor necesario
I;

• 0
Pilas y filas

Las operaciones se implementarían de ta siguiente manera:

Pila::Pila()
( tope = NULL; }

Pila::~Püa()
# í Nodo *aux;
aux = tope;
while (tope != NULL)
I tope = aux->sig;
delete aux;
aux = tope; }
}
i /
i /

char Pila::Meter (lipoelem nuevo)


¡ Nodo *aux = new Nodo;
if(aux == NULL)
return 0;
else
I aux'>info = nuevo;
aux->sig = tope;
tope = aux;
return 1; } \^
I
char Pila::Sacar (tipoelem &valor)
{ Nodo *aux = tope;
if(aux == NULL)
return 0;
else
( valor = aux->info;
tope = aux->sig;
delete aux;
return 1;}
}

n #
¿Como se puede representar físicamente el TDR fila?

¿Cómo se puede representar físicamente el TDA fila?

Al igual que en la pila, resulta evidente imaginar la representación de


la fila por medio de las estructuras lineales que provee el lenguaje, tan­
to en memoria estática como en memoria dinámica. Sin embargo, en
el caso de la fila, se harán algunas consideraciones interesantes.

Implementación utilizando memoria estática

" \ Para este caso es importante analizar diversas opciones de representa-


X \ ción e implantación de las operaciones. A continuación, se describen
\^ haciendo énfasis en cómo las desventajas propias de una representa-
\ \ ción pueden ser superadas.
\ i
\ i

\ \ Forma 1: recorriendo información en el arreglo

Se puede representar una fila en un arreglo unidimensional. Los ele-


j mentos se colocan a partir de la primera posición hasta la posición má-
/ / xima. Al momento de sacar un elemento, los restantes se recorren una
/ / posición hacia adelante en el arreglo, de tal manera que el frente de la
/ / fila siempre está dado por la primera posición del arreglo. Así, única-
y / mente se requiere un apuntador que indique donde se encuentra el fi-
^ ^ / nal de la fila. Aunque esta es una representación sencilla que facilita la
implantación de las operaciones también es evidente que una gran
desventaja se da con el corrimiento de los elementos cada vez que se
saca uno de la fila lo que repercute en la eficiencia cuando la fila
guarda una cantidad significativa de datos.

Forma 2: arreglo lineal con dos apuntadores •

Se puede hacer una variante de la forma anterior de representar la fila


en un arreglo unidimensional, pero con dos apuntadores: uno para el
frente y otro para el final. En esta representación los elementos no se
recorren cada vez que se saca un elemento de la fda, sino que el apun­
tador al frente se mueve hacia adelante en el arreglo, cada vez que se
saca un dato. Así, esta representación elimina el problema de los corri­
mientos, pero podría llegar al caso extremo en que se desperdicien lo­
calidades de memoria, pues cuando el indicador del final de la fila
esté en la posición máxima del arreglo, ya no podrán agregarse ele­
mentos a la fila, aunque se tengan lugares disponibles.

• Q
Pilas y filas

Forma 3: arreglo circular con dos apuntadores

Una tercera representación para una fila puede hacerse utilizando un


arreglo unidimensional donde los elementos se incorporan de manera
que, al sacar uno, no es necesario recorrer los restantes en la fila y
cuando el final de la fila esté en la posición máxima del arreglo y éste
• aún tenga localidades, pueda agregarse un nuevo elemento a la fila.
Esto se logra manteniendo dos apuntadores: uno para el frente y otro
para el final, y tomando en cuenta que después de la posición máxima
del arreglo sigue la primera posición, es decir, el arreglo se comporta
como un anillo o en forma circular
Por ejemplo, para el caso que se muestra en la figura 6.3, el último
elemento insertado en la fila es la T, y el primer elemento que entró
(o el siguiente en salir) es la Y.

C 5 ± 0 ^\
00 ***£* f^al = 0

v M ^ ^ ^ ^ MJ -*- frente = 1 \

3 a X
v J x
Figura 6.3. Fila en arreglo circular.
Es importante observar que esta representación no tiene las desven­
tajas de las anteriores, pues no se requiere recorrer datos ni se desperdi­
cia espacio en el arreglo al considerar todas las localidades disponibles
en él. Sin embargo, puesto que un arreglo en la memoria no es física­
mente circular, el implementador de las operaciones tendrá que contro­
lar esta característica.
Antes de conocer el código de implementación para esta representa­
ción, es importante considerar que los dos apuntadores (frente y final)
siempre se incrementarán dentro del arreglo. Esto quiere decir que, pa­
ra agregar un dato, se incrementa el final, y para sacar, se incrementa el
frente. En la acción de incrementar se tendrá que considerar la circula-
ridad del arreglo y se puede deducir que el incremento se validará con
una estructura de decisión que verifique si se está en la última posición
del arreglo para mandar a la posición inicial, o si no, incrementar
normalmente.
Sin embargo, aquí vale la pena presentar un truco de piogramación
que permite hacer más eficiente la acción de "incrementar circularmen-
te". Esto se logra con el operador que permite obtener el residuo de una
^ división entera. Para el caso del lenguaje C++ (o C), la equivalencia
Q #
¿Cómo se puede representar físicamente el TOA fila?

sería la de la figura 6.4, y se puede comprobar (donde MAX es el tama­


ño del arreglo):

f íf [ap==MAX-1)
i ^
ap"0, ap = [ap+1]%MAX:
ap++;

V '. )
Figura 6.4.

^ X, Por lo tanto, la implementación final del TDA fila, con esta represen-
^ \ \ tación y con el diseño lógico 1, sería la siguiente:
\ \ class Fila
\ \ {prívate:
\ \ tipoelem datos[MAX];
\ int Frente, Final;
public:
I \ FilaO { Frente = Final = -1; j
/ / void Meter (tipoelem dato);
I j tipoelem Sacar ();
/ / char Llenaf);
/ / char Vacia();

void Fila:-.Meter(tipoelem nuevo)


í
Fina! = (Final + 1) % MAX;
datos[Final] = nuevo;
if (Frente == -1) Frente = 0;
} •
tipoelem Filar.Sacar ()
í
tipoelem valor = datos[Frente];
if(Frente == Final)
Frente a Final = -1;
else
Frente = (Frente + I) % MAX;
return valor;
}
char Vacia ()
{return (Frente == -I); )
char Llena ( )
{ return ((Final + I) % MAX) == Frente ); } ^

• 0
Pilas y filas

Implementación utilizando memoria dinámica

Al igual que en el caso de la pila, se utilizará una lista encadenada en la


que cada nodo de la lista guarde un dato de la fila. Sin embargo, a dife­
rencia de la pila, se requiere tener dos apuntadores en ambos extremos
de la lista para accesar a ellos: uno para el frente y otro para el final
• (figura 6.5). También es importante analizar en qué orden estarán estos
apuntadores: ¿frente está al inicio de la lista y final en el último nodo?
o ¿final en el primer nodo y frente en el último'' Aunque podría parecer
indistinto, esto repercute en la eficiencia de las operaciones. Por la for­
ma en que se construye la lista, lo más conveniente será que el frente
esté al inicio de la lista y final en el último nodo.
t f
t /

' frente final >

Figura 6.5. ^ \

A continuación, se muestra la implementación con esta representación


y en el diseño 2 del TDA fila:

class Fila
{ prívate:
Nodo *Frente, *Final;
public:
FÜaQ; //constructor que inicializa como vacía
char Meter (tipoelem dato);
char Sacar (tipoelem* dato);
~Fiia(); //destructor necesario
h

Q #
¿Cómo se puede representar físicamente el TDH fila?

Fiia::Fila()
{ Frente = Final = NULL;}

Fila::~Fila()
{ Nodo *aux = Frente;
while (Frente != NULL)
{Frente = aux->sig;
delete aux;
aux = Frente; }
\ ;

N. char Fila::Méter (tipoelem nuevo)


\ \ { Nodo *aux = newNodo;
\ if (aux == NULL) returnO;
\ \ else
\ (aux->info = nuevo;
if (Final == NULL) Frente = aux;
\ else Final->sig = aux;
I i Final = aux;
I '; return l; }
/ >' i
^/ /' char Fila::Sacar (tipoelem *valor)
y' { Nodo *aux = Frente;
.--''' if(aux == NULL) return 0;
else
[valor = aux->info;
Frente = aux->sif>;
delete aux; •
if (Frente == NULL) Final = NULL;
return 1;}
}

. g
Pilas y filas

( EJERCICIOS ) ^

1. Para cada una de las siguientes formas de representación física de


una pila, implemente las operaciones según la especificación lógica
del diseño 1 para el TDA pila, suponiendo que cada operación será
un método de la clase que representa a las pilas. /**

a) Memoria estática: se utiliza un arreglo unidimensional y un


apuntador TOPE que indique el lugar del arreglo donde estará el
siguiente elemento por añadir en la pila. Los elementos se guar- /
dan desde la primera posición hasta la última del arreglo.
b) Memoria estática: se utiliza un arreglo unidimensional y un
apuntador TOPE que indique la posición del último elemento aña- \ \
dido a la pila. Los elementos se guardan comenzando en la última \
posición hasta la primera del arreglo. \
c) Memoria estática: se utiliza un arreglo unidimensional en que la \
primera posición indicará dónde estará el siguiente elemento por Nv
añadir en la pila. Los elementos se guardan desde la segunda
posición del arreglo hasta la última. Obviamente, esta repre­
sentación sólo sirve para pilas que guardan números enteros.
d) Memoria dinámica: se utiliza una lista encadenada circular con
un apuntador externo. El tope de la pila estará en el siguiente no­
do del nodo señalado por el apuntador externo.
e) Memoria dinámica: se utiliza una lista encadenada circular con
un apuntador externo. Aunque no es lo más eficiente, el tope de la
pila estará en el nodo señalado por el apuntador externo.
0 Memoria dinámica: se utiliza una lista doblemente encadenada
con un apuntador externo, que hace referencia al último nodo de
la lista. El tope de la pila estará en el nodo señalado por el apun­
tador externo.
g) Memoria dinámica: se utiliza una lista doblemente encadenada
circular con un apuntador externo. El tope de la pila estará en el
nodo señalado por el apuntador extemo.
h) Memoria dinámica: se utiliza una lista doblemente encadenada
circular con un apuntador externo. El tope de la pila estará en el
nodo anterior al nodo señalado por el apuntador externo.

0 •
Ejercicios

2. Resuelva los mismos casos del ejercicio anterior, pero basándose en


la especificación lógica del diseño 2 del TDA pila.

3. Un nuevo diseño de las operaciones en la especificación lógica del


TDA pila se muestra a continuación:

METER --> igual al diseño 2

' \ SACAR
^-. \ ENTRADA: la pila de donde se desea sacar
\^ \ SALIDA: la pila modificada
\ \ PRECONDICIÓN: la pila existe y no está vacía
\ \ POSTCONDICIÓN: la pila tiene un elemento menos eliminado por el extremo
\ '. del tope

\ \ OBSERVA
: ENTRADA: la pila de donde se desea observar
SALIDA: el valor del elemento que está en el tope de la pila
¡' PRECONDIOÓN: la pila existe y no está vacía
/ / POSTCONDICIÓN: ninguna

/ / VACÍA --> igual ai diseño 1

y
y Con estas especificaciones, responda los siguientes incisos:

a) ¿Por qué se justifica el diseño de la operación OBSERVA y VA­


CÍA?, ¿qué ventajas se tienen en el nivel de aplicación?, ¿por qué
no se justifica el diseño de la operación LLENA (igual al diseño 1)?
b) Implemente las operaciones del nuevo diseño (SACAR y OBSER- *
VA) en cada una de las formas de representación que se indicaron
en el ejercicio 1.
c) Resuelva el problema del palíndromo, en el nivel de aplicación,
utilizando ahora este diseño lógico.
d) Suponiendo que la operación OBSERVA se añade al diseño 1 y al
diseño 2, ¿cómo sería su implementación en cada diseño, conside­
rando que se implementará al nivel de aplicación del resto de las
operaciones del TDA pila?

4. A continuación se muestra una serie de casos los cuales deberán re­


solverse implementando una función libre en C++ en el nivel de
aplicación del TDA pila, según el diseño 2:

• S
Pilas y filas

a) Realice un módulo que sirva para invertir el orden de los elemen­


tos de una pila.
b) Desarrolle un módulo que sirva para obtener una copia de una pi­
la.
c) Realice un módulo que sirva para contar cuántos elementos tiene
una pila.
• d) Obtenga un módulo que sirva para sacar el elemento del fondo de
una pila.
e) Realice un módulo que sirva para reemplazar por un nuevo valor
las ocurrencias de cierto valor en una pila.
5. ¿Cómo se podrían implementar dos pilas en un mismo arreglo? Si
se supone que una pila crecerá de la primera posición hacia adelan­
te y que otra crecerá de la última posición hacia atrás en el arreglo, /
y que cada una tiene un apuntador (TOPE1 y TOPE2) que indica la
posición del último elemento añadido en cada pila, ¿cuál es la con­
dición para detectar que las pilas están llenas? Implemente las ope­
raciones de METER y SACAR para cada una de las pilas, según el
diseño lógico 2. \
6. Una nueva representación en memoria estática para el TDA fila, se
describe a continuación: \
Se utiliza un arreglo unidimensional en forma circular, con un
apuntador al frente y otro al final de la fila. La fila crece, a partir de
la primera posición del arreglo, en forma circular. El apuntador
FINAL señalará el último elemento añadido y el apuntador FREN­
TE siempre apuntará a un lugar reservado, que nunca guardará un
elemento, pero siempre estará una posición antes del siguiente ele­
mento en salir de la fila.
Con esta representación, implemente las operaciones del TDA
fila, según el diseño lógico 2. ¿Qué ventajas se obtienen con respec­
to a la representación en que no se utilizaba el lugar reservado?

7. Para cada una de las siguientes formas de representación física de


una fila en memoria dinámica implemente las operaciones según la
especificación lógica del diseño 2 para el TDA fila, suponiendo que
cada operación será un método de la clase que representa a las filas.

a) Lista encadenada circular, donde el apuntador externo de la lista


señala el final de la fila, y el siguiente nodo del que representa el
final es el frente.
b) Lista doblemente encadenada con dos apuntadores externos, uno
al primer nodo en la lista y otro al último. El frente de la fila será el
^ último nodo de la lista; el final de la fila será el primer nodo.

Q — •
Ejercicios

c) Lista doblemente encadenada circular con un apuntador externo


que señala el final de la fila y el nodo anterior representa el
frente.
d) Lista doblemente encadenada circular con un apuntador externo
que señala el frente de la fila, y el nodo siguiente representa el
final.

8. Suponga que la operación OBSERVA, que se diseñó en el ejercicio


--^. 3, también es útil para el TDA fila, y se añade al diseño 1 y al 2.
* \ ¿Cómo sería su implementación en cada diseño, considerando que
^ ^ \ se implementará en el nivel de aplicación del resto de las operacio-
^ \ \ nes del TDA fila?

\ \ 9. A continuación se muestra una serie de casos que deberán resolver-


\ \ se implementando una función libre en C++ y trabajando en el nivel
\ de aplicación del TDA fila según el diseño 2.

, : a) Realice un módulo que sirva para copiar una fila.


/ / b) Obtenga un módulo que sirva para contar los elementos de una
/ / fila.
/ / c) Realice un módulo que sirva para reemplazar las ocurrencias de
/ cierto valor, en una fila, por un nuevo valor.
// / d) Desarrolle un módulo que sirva para reorganizar los valores guar­
dados en una fila de números, de tal forma que primero queden los
_^-'** valores negativos (en el orden que habían llegado a la fila) y des­
pués los positivos (en el mismo orden).

10. ¿Cómo se podrían implementar dos filas en un mismo arreglo? Su­


poniendo que una fila crecerá de la primera posición hacia adelan- #
te, que la otra fila crecerá de la última posición hacia atrás en el
arreglo cada una tiene un apuntador (FINAL1 y FINAL2) que in­
dica la posición del último elemento añadido en cada fila y que el
frente de las filas siempre estará en la primera y última posición
del arreglo, ¿cuál es la condición para detectar que las filas estén
llenas? Implemente las operaciones de METER y SACAR para ca­
da una de las filas, según el diseño lógico 2.

11. A continuación se muestra el estado de una lista encadenada circu­


lar en memoria estática, controlada por el apuntador externo llama­
do LISTA. Los espacios disponibles en el arreglo se controlan por
medio de otra lista encadenada (no circular), manejada como pila a
través del apuntador extemo llamado DISPONIBLES. El valor
NULL se representa por el 0 (cero)

• 0
Pilas y íilas

[1] MANUEL 7
[2] ~ 3
[3] 5 Lista = 4
[4] FRANCISCO 1
Disponibles = 8
Í5] 6
[B] 0_
[7] CÉSAR 4
[8] 2
"~^~^ »** ^

I /

Para los incisos siguientes, muestre el estado del arreglo y de los apun­
tadores, después de ejecutar la operación correspondiente. Maneje las
operaciones de filas y pilas sobre una lista circular de la manera más efi­
ciente (evitando recorrer nodos). Para el primer inciso, tome el estado
original del arreglo; para los otros incisos, el estado del arreglo re­
sultante del inciso anterior. \ \

a) Inserte el dato DANIEL en la lista, considerando que es la repre- \ \


sentación de una pila.
b) Inserte el dato MARÍA en la lista, considerando que es la repre­
sentación de una fila.
c) Saque un dato de la lista, considerando que es la representación de
una pila.
d) Saque un dato de la lista, considerando que es la representación
de una fila.
12. Repita el ejercicio anterior considerando que la lista es doblemente
encadenada circular, y que la lista de disponibles sigue maneján­
dose igual (como pila) sólo a través del campo SIG. Considere que
para la pila el tope es el nodo apuntado por LISTA, y que para la
fila el frente es el nodo señalado por LISTA y el final es el nodo
anterior al apuntado por LISTA.
13. ¿Cómo se puede implementar una fila al nivel de aplicación de las
pilas? Considere que la definición de la clase fila es:
class Fila
{ prívate:
Pila datos;
public:
//métodos tradicionales, según el diseño lógico
h
ni ^
Rutoeualuación

Esto significa que tendrá que simularse una fila por medio de una
pila y que la implementadón de los métodos de la clase fila se realizará
en el nivel de aplicación de las pilas.
Escriba la implementación de los métodos de la clase fila, con­
siderando esta representación y el diseño lógico 2.

14. ¿Cómo se puede implementar una pila al nivel de aplicación de las


filas? Realice lo equivalente al ejercicio anterior, pero ahora considere
que se implementará una pila utilizando el nivel de aplicación de las filas.

^X \ r flUTOEUHLUflCION C\ z
\ \ ' '
\ \ I I

1. Se implemento una fila en un arreglo de cinco posiciones, utilizan-


/ / do dos apuntadores (FRENTE y FINAL) y considerando que el arreg-
/ / lo es circular. Suponiendo que el arreglo se define con los subíndices
/ / 1 a 5 , y que FRENTE apunta al siguiente elemento en salir y FINAL
/ / apunta al último elemento añadido, ¿cuál es el estado de la fila si el
y / apuntador FRENTE vale 4 y el FINAL vale 3?

..-•'"'' a) La fila está llena


b) La fila está vacía
c) La fila tiene dos elementos
d) La fila tiene un elemento
e) La fila tiene tres elementos

2. La operación OBSERVA se diseñó para el TDA pila de la siguiente


forma:

Entrada: la pila de donde se desea observar


Salida: el valor próximo a sacar de la pila
Precondición: la estructura de datos está creada y tiene elementos
Postcondición: ninguna

Suponiendo que la operación se implantó como un método para la


clase pila de la siguiente forma:

• 0
Pilas y filas

TipoelemObsenaO { retum datos[tope+l]; }

se puede decir que:

a) La pila está implementada en un arreglo en que el apuntador TOPE


señala al último elemento añadido y donde la pila crece a partir de
# la primera posición.
b) La pila está implementada en un arreglo en que el apuntador
TOPE señala al último elemento añadido y donde la pila crece a
partir de la última posición.
c) La pila está implementada en un arreglo en que el apuntador TOPE
señala al siguiente lugar disponible en el arreglo y donde la pila
crece a partir de la primera posición. /
d) La pila está implementada en un arreglo en que el apuntador TOPE
señala al siguiente lugar disponible en el arreglo y donde la pila
crece a partir de la última posición.
e) Ninguna de las anteriores.

3. Dada la siguiente lista encadenada circular:

Lista \

i 1 1 r—i 1 i 1 1 i 1 1 '-
^ R -i». S --». T -I -»■ *~ M —
I 1 1 I L _l L I J I I

¿cuál de los siguientes enunciados es verdadero, suponiendo que se


ha representado una pila o una fila en esta lista de la manera más
eficiente?

a) Para una pila, el dato 'M' es el siguiente elemento en salir.


b) Para una fila, el dato 'M" es el siguiente elemento en salir.
c) Para una pila, el dato 'R' será el último elemento en salir.
d) Para una fila, el dato 'M' será el último elemento en salir.
e) Para una fila, el dato 'R' es el último dato que entró.

9 •
Capítulo j f p
KMTlMDt ' ^
DATOS LinCrLfS PAPJ
Lfl EÜSQUÜDO DE * ^
nomnoón
OBJETIVOS
• Comprender el concepto de búsqueda y su importancia en múltiples apli­
caciones computacionales.
• Describir el algoritmo de la búsqueda secuencial.
• Describir el algoritmo de la búsqueda binaria, distinguiendo claramente sus
ventajas.
• Implantar los algoritmos de búsqueda en estructuras lineales que utilizan
los dos tipos de representación (por posiciones y por ligas).
• Entender las ventajas y desventajas de los algoritmos de búsqueda, según
la representación de la estructura y en conjunto con las operaciones de
inserción y eliminación de elementos.

0
Estructuras de datos lineales para la búsqueda de información

¿Por qué es importante la búsqueda de información?

Uno de los propósitos de la computadora es que sirva de medio para el


almacenamiento de grandes volúmenes de información, lo que implica
la existencia de mecanismos para accesar eficientemente a esta última.
Los mecanismos de acceso más comunes requieren realizar una bús-
* queda, de ahí que este concepto, aunado al de almacenar la informa­
ción en una estructura de datos eficiente, es de suma importancia en el
ámbito computadonal.

¿Cómo realizar eficientemente una búsqueda? , /'"

Imagine que tiene que buscar un teléfono en el directorio. ¿Qué pasa- /


ría si el directorio no tuviera la información ordenada? Lo más seguro
es que tardaría días, semanas o meses buscando. Podemos deducir que /
un requisito importante para que el proceso de búsqueda se realice en
forma eficiente es que la información esté ordenada, por lo que se re- I
quiere plantear una representación y un método de acceso para que la \
información se almacene ordenadamente y se facilite el proceso de
búsqueda. \ \
Para ordenar la información es necesario realizar el ordenamiento
según uno (o varios) de los campos que conforman cada registro de in- \
formación. El campo que determina el ordenamiento se denomina
llave, ya que distingue de manera única a cada registro.
La llave puede ser simple, si se forma con un solo campo (como la
matrícula de un alumno en la universidad ) o compuesta, si se forma por
varios campos (nombre, apellido y dirección de una persona), por lo
que en una estructura de datos para la búsqueda de información no se
guardará información repetida.

¿Qué opciones de representación en memoria existen para


una estructura de datos para la búsqueda de información?

Si se desea utilizar una estructura temporal y limitada, para obtener


accesos rápidos, se deberá pensar en memoria principal.
Si se desea utilizar una estructura permanente e ilimitada para alma­
cenar grandes volúmenes de información, se deberá pensar en memo­
ria secundaria. Esto lleva a ligar el concepto con lo que. en realidad, es
la implementación de un sistema de base de datos.

@ •
¿Cómo se puede ímplementar el proceso de búsqueda en una estructura lineal?

Posibles representaciones lineales en memoria principal.


En memoria estática se puede utilizar un arreglo que guarde la infor­
mación en forma ordenada. Esta representación, típicamente, se conoce
con el nombre de tabla.
En memoria dinámica se puede utilizar una lista encadenada que
guarde ordenadamente la información. Esta representación, general­
mente, se llama lista ordenada.
En ambos casos, y analizando estas representaciones como un TDA,
-^ las operaciones básicas necesarias para la estructura serían: inserción,
S
NN eliminación, actualización y consulta de un elemento. Se puede obser-
N ^ \ var que estas operaciones requieren realizar búsquedas para ubicar la
\ \ información en la que se va a trabajar, por lo que la búsqueda se vuelve
\ \ un proceso fundamental en estos TDA.
\ i
\ i
\\ \\

¿Cómo se puede Ímplementar el proceso de


búsqueda en una estructura lineal?
/ / Hay dos algoritmos básicos para Ímplementar este proceso:

/ / La búsqueda secuencial es el algoritmo más obvio y el que tiene una


y / / implantación intuitiva. Consiste en comparar a partir del primer ele-
*<>^ / mento de la estructura, secuencialmente, hasta que el elemento busca-
..--'' do se encuentra, o un elemento mayor al buscado (en cuyo caso se
puede dejar de buscar, pues los elementos restantes son mayores al
estar ordenada la información, y es evidente que el elemento por buscar
no existe).
Este algoritmo no es apropiado cuando se tiene una tabla con mu­
cha información, ya que, en el peor caso, este algoritmo requiere •
realizar n comparaciones (donde n es la cantidad de elementos).

La búsqueda binaria consiste en dividir sucesivamente la estructura


en mitades, descartando del proceso de búsqueda la mitad en que no
se puede encontrar el elemento que se busca. El proceso parte la es­
tructura en mitades cada vez más pequeñas, lo que asegura que tarde
o temprano se encuentre el elemento buscado o bien, se decide que el
elemento no se encontró.
Este algoritmo tiene un comportamiento eficiente si lo compara­
mos con la búsqueda secuencial. Está comprobado matemáticamente
que, para este algoritmo, el peor caso requiere realizar log2 n + 1
comparaciones (donde n es la cantidad de elementos en la estructura).

• Q
Estructuras de datos lineales para la búsqueda de información

Por lo tanto, para grandes cantidades de información, este algoritmo


es más apropiado.

Algoritmo de búsqueda binaria


• Encontrar el elemento de la mitad de la tabla.
• Si su valor es igual a la llave buscada: se termina y se reporta que
9 hubo éxito en la búsqueda.
• Si no, se toma la mitad de la tabla apropiada según el valor buscado
y se repite este proceso hasta llegar a un segmento de tabla que con­
tenga sólo un elemento.
• Si el último elemento verificado en la tabla no es igual al valor bus­
cado, se termina y se reporta z\ fracaso en la búsqueda.

¿Qué ventajas y desventajas ofrece la representación / /


de una tabla en memoria estática? /

La principal ventaja es que el algoritmo de la búsqueda binaria se pue­


de implementar fácilmente, considerando que las mitades se pueden
calcular con base en los índices de un arreglo, por lo tanto, el proceso \
de búsqueda es eficiente. \
Sin embargo, ya que los algoritmos de búsqueda presuponen que la \
información está compactada (no hay huecos en el arreglo), el proceso
para insertar y borrar información en la tabla se vuelve ineficiente.
Analizando la implementación bajo estas consideraciones, la inser­
ción de un elemento implica hacer un hueco en el arreglo, recorriendo
todos los elementos que se encuentren, a partir de esa posición, una po­
sición hacia abajo. Por otro lado, la eliminación de un elemento, de la
misma manera, para no dejar un hueco al borrar el elemento, implica
recorrer todos los elementos una posición hacia arriba (figura 7.1).

' Problema de inserción Problema de eliminación 1


Gráficamente: :nsertar r8 Gráficamente: eliminar 21

15 JS
_ £i ^-^- si

"...:*: as ^ ^ : sa
fc" se - :=: 38

V I : I : )
Figura 7.1.

Q •
Ejercicios

¿Qué ventajas y desventajas ofrece la representación


de una lista ordenada en memoria dinámica?

Definitivamente, el problema de los corrimientos de información, que


apareció como una desventaja en una tabla, no es una característica
propia de una estructura que utiliza encadenamientos, por lo que, com­
parativamente, la eficiencia en las operaciones de inserción y elimina­
ción de elementos es una ventaja de esta representación.
**-**., Sin embargo, la única estrategia de búsqueda que se puede aplicar
\ sobre esta representación es la búsqueda secuencial, debido a que la in-
"Ss\ \ formación está almacenada en forma dispersa. El algoritmo de la bús-
\ \ queda binaria, aunque se puede implantar en esta representación, se
\ \ volvería ineficiente, pues el cálculo de las mitades en una lista impli-
\ \ caría recorrer sus elementos desde el inicio.
\ \ Por lo tanto, se puede concluir que ninguna de las dos propuestas de
\ i representación lineal cumplen, al mismo tiempo, con las condiciones
de eficiencia (inserción, eliminación y búsqueda). Esto nos lleva a su-
:
/ ; poner que se necesita una estructura de datos diferente que cumpla con
/ / ambas características, como se estudiará en el siguiente capítulo.
/ r

( EJERCICIOS ) $

1. Defina los siguientes conceptos:


• Tabla
• Búsqueda secuencial
• Búsqueda binaria

2. Suponga que se tiene una estructura tipo tabla, implementada en un


arreglo unidimensional, en la que los objetos se guardan en orden
de mayor a menor, según la llave.

A dicha estructura se insertaron los objetos correspondientes a las


siguientes llaves (en el orden que se muestran):
78 45 61 5 98 90 34 13

• Q
Estructuras de datos lineales para la búsqueda de información

Responda las siguientes preguntas considerando que un corrimiento es


el movimiento de un objeto de la tabla, de una posición a otra dentro
del arreglo:

a) ¿Cuántos corrimientos de objetos ocurrieron en total al momento de


insertar los objetos en el orden mostrado anteriormente?
• b) Utilizando las mismas llaves, escriba una secuencia para hacer que
las inserciones a la tabla generen la mayor cantidad de corrimien­
tos. ,.---
c) Con las mismas llaves, escriba una secuencia para hacer que las in­
serciones a la tabla generen la menor cantidad de corrimientos.
d) ¿Cuántos corrimientos de objetos ocurrirán en total si se elimina de
la tabla la siguiente secuencia de objetos con las siguientes llaves: /
5 78 98? / /
e) En general, ¿cuál es la máxima cantidad de corrimientos que se pue- /
den tener al insertar n objetos (todos con diferente llave)?

3. El algoritmo de búsqueda binaria se ha implementado para trabajar


sobre una tabla basado en el siguiente pseudocódigo, considerando \
que ARRIBA, ABAJO y ACTUAL son apuntadores que guardarán
posiciones del arreglo. \

arriba = 0; \ \ .
abajo = posición del último elemento en la tabla +1; "\ ^""^
mientras (arriba-abajo >l)y no se haya encontrado el elemento buscado hacer:
S actual = el cociente de dividir (arriba+abajo) entre 2;
si el elemento buscado < elemento de la posición actual entonces:
abajo = actual
si no
si el elemento buscado > elemento de la posición actual entonces:
arriba == actual
si no
ya se encontró el elemento l

Basándose en este algoritmo y suponiendo que la tabla se llenó con


los siguientes datos, ordenándolos de menor a mayor alfabéticamente:

TIM DOT EVA ROY KIM GUY AMY JON ANN JIM
KAY RON JAN

responda las siguientes preguntas, considerando que cada iteración del


ciclo corresponde a lo que se contabilizará como una comparación de
datos:

Q •
Ejercicios

a) ¿Cuántas comparaciones se realizarán para encontrar el nombre de


JON?
b) ¿Cuál es la cantidad máxima de comparaciones que se realizarían
para encontrar un dato siguiendo el algoritmo de búsqueda binaría?,
y ¿siguiendo el algoritmo de la búsqueda secuencial?
c) Especifique, para cada nombre, cuántas comparaciones requeriría su
búsqueda aplicando el algoritmo de búsqueda binaria.

--... 4. Dada la siguiente lista encadenada en memoria estática, que se ma-


\ nejará como lista ordenada, muestre el estado del arreglo y los
^ \ \ apuntadores, después de ejecutar las instrucciones que se muestran
^v \ en cada inciso: (Para el inciso a tomar las condiciones iniciales;
\ \ para el resto de los incisos, tomar la lista resultante del inciso an-
\ \ terior.) Considere que 0 = NULL y que los nodos disponibles se
\ \ administran en una lista encadenada manejada como pila e identifi-
\ cada por el apuntador externo llamado DISPONIBLES.

/ / INFO SIG

/ 1 2
/ / 2 3
/ / / 3 4
4
--'''' 5
.---''' 5 6

6 7
7 D
LISTA = 0
DISPONIBLES = 1

a) Insertar a GABRIEL
b) Insertar a GUILLERMO
c) Insertar a ALEJANDRO
d) Insertar a LUIS
e) Dar de baja a GUILLERMO
f) Insertar a ROBERTO
g) Dar de baja a GABRIEL

• 0
Estructuras de datos lineales para la búsqueda de información

í H U T O E U ñ L U H C I Ó N Q, =
1
!
■ i
■._ — _ _ _ _ _ _ _ — _ _ _ — _ — _ _ _ _ . _ _ _ — _ _ _ _ _ _ _ _ _ _ _ j

Para las preguntas 1 a 3, se utiiiza la lista circular en memoria estáttca


• que se muestra a continuación, suponiendo que 0 = NULL y que la lista
de disponibles se maneja como pila. Considere cada pregunta en forma
independiente, es decir, para cada pregunta considere el estado inicial
del arreglo.

INFO SIG /
i /

[1] MANUEL 7 /

[2] 3

[3] 5

[4] FRANCISCO 1

[5| 6 \

[6] 0 \

[71 CÉSAR 4

[8] 2 \ ^ _

1. Si la lista fuera a ser manejada como una lista ordenada (tabla) y se


insertara en ella el valor de GABRIELA, ¿,cuál de los siguientes enun­
ciados es verdadero?

a) El dato GABRIELA estaría en la posición 8 y señalaría la posición


4 (lista = 4).
b) El dato GABRIELA estaría en la posición 4 y señalaría la posición
7 (lista = 4).
c) El dato GABRIELA estaría en la posición 8 y señalaría la posición
4 (lista = 8).
d) El dato GABRIELA estaría en la posición 4 y señalaría la posición
8 (lista = 4).
e) El dato GABRIELA estaría en la posición 8 y señalaría la posición
7 (Hsta = 8).

0 •
Rutoeualuacíón

2. Si la lista es manejada como fila, y se ejecutaron un conjunto de


instrucciones que la dejaron vacía, ¿cuál es el valor del apuntador
DISPONIBLES, después de insertar un nuevo dato con el valor de
ENRIQUE?

a) 4 b) 7 c) l d) 0 e) 2

3. Si la lista fuera a ser manejada como una lista ordenada (tabla) y se


insertaran en ella valores hasta llenarla, ¿cuál es el número máximo de
comparaciones de datos que se harían al buscar un valor de la lista,
aplicando el algoritmo de la búsqueda secuencial?

a) I b) 2 c) 3 d) 4 e) 8
\ i
\\ *t
\ \

J t
/ t
/ t
/ i

* í|
Capítuloi Q

T teous DDIOHOS
DE BÚSQUEDA
OBJETIVOS
• Definir las características de las estructuras de datos jerárquicas.
• Describir la terminología de las estructuras de datos tipo árbol: nodo
raíz nodo hijo nodo padre ancestros descendientes nodo hoja sub-
árbol, altura y niveles.
• Describir el diseño lógico del TDA árbol binario de búsqueda (ABB),
incluyendo las operaciones de búsqueda, inserción y eliminación de
un elemento.
• Implantar el TDA ABB, comprendiendo el movimiento del apunta­
dor en la ruta de búsqueda.
• Describir la forma en que se realizan los recorridos sobre un árbol
binario.
• Distinguir las ventajas de aplicar la recursividad en la implementación
de rutinas relacionadas con árboles binarios.

"
Estructuras jerárquicas y arboles buíanos de búsqueda

¿Qué es una estructura jerárquica?


La organización de los datos en una estructura en forma jerárquica o
de niveles, es una nueva opción para representar estructuras de datos,
comúnmente denominada árboles (figura 8.1). Su característica prin­
cipal es que mantienen una relación de uno a muchos (lat) entre sus
• elementos.

/ Ejemplos de jerarquías

c3 =" =
'= ° / /
' /
Esta estructura pudiera representar:
V Un árbol genealógico, un organigrama, los directorios de un sistema operativo, etc... j

Figura 8.1. Estructura jerárquica o árbol.

Terminología básica en las estructuras jerárquicas \


o árboles \
\ \
* \
• Nodo raíz: es el primer elemento de un árbol binario; un árbol binario
sólo tiene un nodo raíz.
• Nodo padre: son los nodos que tienen al menos un hijo (derecho y/o
izquierdo).
• Hijo derecho: nodo que se encuentra al lado derecho de otro nodo.
• Hijo izquierdo: nodo que está al lado izquierdo de otro nodo.
• Nodo hoja: nodos que no tienen hijos. (Un nodo de un árbol binario
puede tener ninguno, uno o dos hijos.)
• Nodo hermano: nodos que tienen un mismo padre.
• Ancestros: nodo padre de un nodo o el padre de algún nodo ancestro.
(El nodo raíz es un ancestro de todos los nodos del árbol.)
• Nodo descendiente: el hijo de un nodo o el hijo de otro descendien­
te de ese nodo. (Todos los nodos del árbol son descendientes del
nodo raíz.)
• Subárbol izquierdo: todos los descendientes por la izquierda de un
nodo forman un subárbol izquierdo, cuya raíz es el hijo izquierdo de
ese nodo.
• Subárbol derecho: todos los descendientes por la derecha de un nodo
forman un subárbol derecho, cuya raíz es el hijo derecho de ese nodo.
• Nivel de un nodo: distancia desde la raíz. La raíz está en el nivel ce­
ro. Cantidad de nodos por los que se tiene que pasar para llegar a un
nodo. (El número máximo de nodos en el nivel n es 2n.)

0 •
¿Que es un árbol binario de búsqueda IflBB)?

c El nodo raíz es el 5.
>
*■ El hijo derecho de 5 es 8. El hijo derecho de 6 es 7
5 El hijo izquierdo de 5 es 3. El hijo izquierdo de 8 es B.
El nodo padre de 2 y 4 es 3. El nodo padre de 7 es 6.
Los nodos hojas son 1,4, 7 y 10.
^ 8 Los hermanos son (2 y 4], (6 y 9).
Los ancestros de 7 son 6, 8 y 5.
2 4 6 g Los descendientes de 8 son 6, 7, 9 y 10.
El subárbol derecho de 5 es el que tiene raíz 8.
7 1Q El subárbol izquierdo de 5 es el que tiene raíz 3.
El nodo del nivel cero es 5.
-^ Los nodos del nivel 1 son 3 y 8.
Los nodos del nivel 2 son 2, 4, 6 y 9.
V Los nodos del nivel 3 son 1,7 y 10. J

Figura 8 . 2 . Ejemplo de los elementos de un árbol binario.


\
\
\
*

¿Cómo se relaciona el problema de la búsqueda


con los árboles?

Si se recuerda, en el capítulo anterior se concluyó que la manera más


eficiente de realizar una búsqueda en una estructura lineal es con el al­
goritmo de la búsqueda binaria aplicado en una tabla de memoria está­
tica. Sin embargo, esta estructura presentaba la desventaja de no ser
eficiente para la inserción y eliminación de elementos. Por otro lado,
una lista encadenada ordenada tenía un mejor comportamiento en las
inserciones y las bajas de elementos, pero no en el algoritmo de la bús­
queda binaria. Ante esta disyuntiva, contar con las estructuras jerárqui­
cas (árboles), representa una opción para conjuntar las características
positivas de estas estructuras lineales La propuesta es el TDA Árbol •
Binario de Búsqueda, que se describe a continuación.

¿Qué es un Árbol Binario de Búsqueda (ABB)?

Un ABB es una estructura de datos que guarda información no repeti­


da para administrar eficientemente la búsqueda de los propios datos.
Pertenece al conjunto de estructuras jerárquicas, restringiendo la rela­
ción de uno a dos como máximo y cumpliendo con un ordenamiento
de tal forma que, para cada elemento del ABB, los elementos menores
estarán a su izquierda y los mayores a su derecha (figura 8.3).

• 0
Estructuras jerárquicas y arboles binarios de búsqueda

3 9 1 3 1 5 9 1 2

3 7 3

2 1 6 / - * " '

6 9. ^ ¡§j ¡g> / / "

5 7 4 5 7

/ /

Árboles binarios de búsqueda Árboles que no son binarios


de búsqueda

V | )\ \
Figura 8.3. Ejemplos de árboles.
* \
i \

Especificación lógica del TDA ABB

S f J
>
^5 ELEMENTOS: los elementos de un ABB se identifican como nodos. Cada uno
de ellos contiene un dato (simple o estructurado) único en el
árbol.
ESTRUCTURA: un ABB posee una estructura jerárquica (a excepción del árbol
vacío). Sólo hay un nodo raíz; los demás forman dos subár-
boles disjuntos. Cada nodo, excepto el raíz, tiene un único
padre y no tiene hijos o tiene uno o dos hijos. El hijo izquier­
do siempre tendrá un valor menor y es la raíz del subárbol
izquierdo y el derecho siempre tendrá un valor mayor y es la
raíz del subárbol derecho.

V )

fia 0
Especificación lógica del TOfl RBB

( ^
OPERACIONES

CREAR
UTILIDAD: crea o inicializa un árbol.
ENTRADAS: el espacio de memoria donde se creará el árbol.
SALIDAS: tí árbol inicializado.
PRECONDICIÓN: ninguna
POSTCONDICIÓN: el árbol está inicializado (sin elementos).

BUSCAR
X UTILIDAD: busca un elemento dentro del árbol ABB.
"""""-•v ENTRADAS: el árbol ABB donde va a buscar y el elemento
N. (valor) por localizar.
\ \ SALIDAS: regresa falso si valor no se encuentra en el árbol o regresa
\ verdadero si encontró valor en el árbol y un apuntador P
\ almacena la posición dentro del árbol donde está valor.
\ PRECONDICIÓN: existe el árbol ABB.
POSTCONDICIÓN: ninguna.

, INSERTAR
UTILIDAD: inserta un nuevo elemento dentro del árbol ABB.
/ ENTRADAS: el árbol ABB donde va a insertar el elemento y el
/ / elemento nuevo.
/ / SALIDAS: el árbol tiene un nuevo elemento (nuevo) insertado como
/ / hoja en la posición que le correspondía, según su valor.
y^ PRECONDICIÓN: el árbol ABB existe y el elemento nuevo no está en él.
POSTCONDICIÓN: el árbol ABB contiene al elemento nuevo insertado como
,.-■'' una hoja.

BORRAR
UTILIDAD: elimina un elemento del árbol.
ENTRADAS: el árbol ABB de donde se va a eliminar el elemento y el
elemento (dato) a borrar.
SALIDAS: regresa falso si dato no se encuentra en el árbol; regresa •
verdadero si encontró dato en el árbol y lo pudo
eliminar, en cuyo caso, regresa el árbol modificado.
PRECONDICIÓN: el árbol ABB existe y el elemento dato se encuentra en
dicho árbol.
POSTCONDICIÓN: el árbol ABB contiene un elemento menos (dato).

^ )

• 0
Fstructuras jerárquicas y árboles binarios de búsqueda

RECORRER
UTILIDAD: despliega los elementos almacenados en el árbol.
ENTRADAS: el árbol ABB a desplegar y el orden en que se
desplegarán los elementos.
SALIDAS: cada nodo en el árbol se procesa exactamente una vez.
El orden en que se procesan los nodos
• depende del valor de orden. Si orden es:
PREORDEN: Cada nodo se procesa antes que cualquiera de los nodos
existentes en sus subárboles.
INORDEN: cada nodo se procesa exactamente después de procesar
todos los nodos de su subárbol izquierdo, pero antes de
procesar los de su subárbol derecho.
POSTORDEN: Cada nodo es procesado después de que se procesaron
los nodos existentes en ambos subárboles.
PRECONDICIÓN: existe el árbol ABB.
POSTCONDICIÓN: ninguna.

V ) : I
¿Cómo se realiza la búsqueda en un ABB? \ \
> \\
i
\\
La idea básica del algorimo de búsqueda consiste en comparar la in-
formación del nodo con la del valor buscado; si no son iguales, el
apuntador de búsqueda se mueve a la izquierda o a la derecha del valor
buscado, según sea menor o mayor, comenzando por la raíz y hasta
que se encuentre o no el valor.
El algoritmo se puede describir con los siguientes pasos:

1. Coloque un apuntador auxiliar en la raíz del árbol.


2. Mientras no se haya encontrado el valor que se busca y el apunta­
dor auxiliar no esté vacío (fuera del árbol):
Verifique si la información del nodo señalado por el apuntador au-
xilar es mayor, menor o igual al nodo buscado.
- Si es mayor, mueva el apuntador auxiliar al nodo hijo derecho; si
es menor, mueva el apuntador auxiliar al nodo hijo izquierdo. Si son
iguales, ha encontrado el nodo y el apuntador auxiliar lo señala
(figura 8.4.).

o •
¿Cómo se realiza la inserción de un elemento en un fiBB ?

( ~* >
flL. / ia ,? ia 12
2i
7 21 ^ "^y pC / i
4 9 IB 23 & O 13 ^ 4 9 16 v 23 4 9 18 35

2 a 13 19 2 (J @ 19 2 S 13 19 ¿? 2 8 .13 19

¡ÉÍÍITOI

>> , . . . . ^
Figura 8.4. Movimiento de apuntadores para buscar el elemento 13.

¿Por qué es eficiente la búsqueda en un ABB?


\ ■

El ABB es una consecuencia directa del algoritmo de búsqueda bina­


ria sobre una estructura lineal y, por lo tanto, tiene todos sus beneficios.
Si un ABB tiene distribuidos sus elementos en forma balanceada, se
obtendrá el mayor beneficio, pues se harían las mismas comparaciones
que en una búsqueda binaria sobre un arreglo. El peor caso de una
/ búsqueda en un ABB está determinado por la altura del árbol y, por lo
/ tanto, entre menor altura tenga el ABB, es decir, entre más balanceado
y / esté, se obtendrán mejores resultados.

¿Cómo se realiza la inserción de un elemento en un ABB?

Todo nuevo nodo se insertará como nodo hoja en el ABB, en el lugar


que le corresponda según el proceso de búsqueda (figura 8.5).

¡r \
^ Buscar
J
V1& *" el 15 12 13
J 2i 7 0
4 2 4
<& $9 3 0 ¿? $[ 23 9 18 ^8
2 & <$ 19 2 ($ © 19 2 3' 13 19
X . Puesto que no existe.
13
se detecta el nodo padre
de! 15 en la inserción

V
Rgura 8.5. Inserción de un elemento en un ABB.
I )

. 6
Estructuras jerárquicas y arboles binarios de búsqueda

El ejemplo anterior ilustra la manera en que se insertaría el elemen­


to 15 en un ABB; es decir, el algoritmo busca al nodo cantidato a padre
del nuevo valor y realiza la inserción correspondiente mediante los si­
guientes pasos:

1. Se crea un nuevo nodo por medio de un apuntador auxiliar 1. Se


• llena con la información que se va a insertar en el árbol y se colocan
sus apuntadores como nodo hoja.
2. Se coloca un apuntador auxiliar 2 en la raíz del árbol y un apunta­
dor auxiliar 3 en vacío. El apuntador auxiliar 3 siempre señalará al no­
do padre del nodo al que señala el apuntador auxiliar 2.
3. Mientras el apuntador auxiliar 2 no sea vacío (fuera del árbol) se rea­
liza lo siguiente:
t /
i

• Se coloca el apuntador auxiliar 3 en el nodo que marca el apunta­


dor auxiliar 2.
• Mueva el apuntador auxiliar 2 al nodo hijo izquierdo si la informa­
ción que se va a insertar es menor a la información del nodo que
señala el apuntador auxiliar 2; en caso contrario, debe moverse a la
derecha (pues la información por insertar es mayor).

Al salir del ciclo el apuntador auxiliar 2 señalará vacío, pero el


apuntador auxiliar 3 estará en el nodo que será el padre del nuevo.
4. Verifique si el apuntador auxiliar 3 es vacío, en cuyo caso, el nue­
vo nodo será el primero en el árbol y el apuntador raíz tendrá que
señalarlo.
Si el apuntador auxiliar 3 no es vacío, entonces estará señalando al
padre del nuevo nodo. Se debe verificar si la información del nuevo
nodo es menor a la del marcado por el apuntador auxiliar 3, en cuyo
caso deberá encadenarse el nuevo nodo como un hijo izquierdo del se­
ñalado por el apuntador auxiliar 3. Si la información no es menor, en­
tonces será mayor y tendrá que encadenarse como hijo derecho.

¿Cómo se realiza la eliminación de un elemento en un ABB?

La acción de borrar un nodo puede enfrentarse con alguno de los si­


guientes casos:

1. El nodo que se va a borrar es una hoja. Puesto que no tiene hijos, su


nodo padre apuntará ahora a vacío (figura 8.6).
2. El nodo por borrar tiene sólo un hijo. En este caso, el padre del que
se va a borrar puede apuntar directamente al nodo hijo del que se
eliminará (figura 8.7.)

g •
¿Cómo se realiza la eliminación de un elemento en un flBB?

5 s 19
J& © 0 / ©

Se busca el elemento El padre del nodo borrado


cambia su liga a NULL
'"■--.. v y
Figura 8.6. Eliminación de un nodo hoja: se elimina el 8.

Se busca el elemento

19
I ® 9 « « J& &
9 2
/ 0 $ ~& <¡* El padre del nodo
/ borrado cambia su
y apuntador al hijo del
S / nodo borrado

^ y \
Figura 8.7. Eliminación de un nodo con un hijo: se elimina el 21.
)

3. El nodo por borrar tiene dos hijos. Puesto que el padre del que se va
a eliminar no puede heredar dos apuntadores, se busca un valor sus­
tituto del valor por borrar y el nodo no se borra físicamente. Se
puede escoger como sustituto al predecesor del que se eliminará (el
valor mayor de todos los valores menores; o bien, de los nodos del
subárbol izquierdo el de más a la derecha), y se elimina físicamente
el nodo donde se encuentre (figura 8.8). Se puede asegurar que la
baja física del nodo sustituto cae en alguno de los dos primeros casos.
De igual manera, se puede considerar al sucesor como valor sustituto.

• @
Estructuras jerárquicas y arboles binarios de búsqueda

Buscar el elemento Buscar el sustituto Baja utilizando


el predecesor

7 21 7 21 7 21

4 9 16 4 9 16 4 8 16

9 8 13 19 a B 13 19 a 13 19

PREDECESOR SUCESOR

Figura B.B. Eliminación de un nodo con dos hijos: se elimina el 12.

El algoritmo de esta operación está compuesto de dos partes. La


primera se encarga de localizar el nodo por borrar y la segunda borra
el nodo encontrado.

Primera parte (localizar el nodo por borrar)


1. Se coloca un apuntador auxiliar 1 en la raíz del árbol y un apunta­
dor auxiliar 2 en vacío. El apuntador auxiliar 2 siempre señalará al
nodo padre del que señala el apuntador auxiliar 1.
2. Mientras no se haya encontrado el nodo por borrar:

• Se verifica si la información del nodo señalado por el apuntador


auxiliar 1 es la que se desea borrar, en caso de que no sea: a) )s
coloca el apuntador auxiliar 2 en el nodo que señala el apuntador
auxiliar 1 y b) se mueve el apuntador auxiilar I 1l nodo oijo iz­
quierdo si la información que se va a borrar es menor a la infor­
mación del nodo que señala el apuntador auxiliar 1; en caso
contrario, debe moverse a la derecha.
Al salir del ciclo, el apuntador auxiliar 1 estará señalando al nodo
por borrar y el apuntador auxiliar 2 al nodo padre.

Segunda parte (eliminar el nodo correspondiente)


1. Se coloca un apuntador temporal en el nodo que debe borrar.
2. Se verifica si el nodo por borrar es hoja o tiene sólo un hijo, en cuyo
caso se desencadenará del árbol para darlo de baja.
Nodo hoja: Si el nodo apuntado por el auxiliar 1 no ttene hijo izquier­
do ni derecho, debe modificarse el apuntador que lo conecta con su
padre (a través del auxiliar 2) de tal forma que apunte hacia vacío
Nodo con un hijo derecho', si el nodo apuntado por el auxiliar 1 no

Q •
¿Qué uentajas ofrece un HBB sobre el algoritmo de la búsqueda binaria?

tiene hijo izquierdo, pero sí derecho, se modifica el apuntador que lo


conecta con su padre (a través del auxiliar 2) de tal forma que señale
al hijo derecho.
Nodo con un hijo izquierdo: si el nodo apuntado por el auxiliar 1 no
tiene hijo derecho, pero sí hijo izquierdo, debe modificarse el apunta­
dor que lo conecta con su padre (a través del auxiliar 2) de tal forma
que señale al nodo hijo izquierdo.
Nodo con dos hijos: si el nodo tiene dos hijos se procederá a localizar
su sustituto buscando a su predecesor de la siguiente forma:

• Se coloca el apuntador temporal en el hijo izquierdo del nodo


señalado por el apuntador auxiliar 1.
• Se mueve el apuntador temporal hacia la derecha lo más posible,
es decir, justo en el nodo antes de que el movimiento a la derecha
lo saque del árbol. El nodo al que se llegue será el nodo sustituto
y se puede asegurar que es nodo hoja o que tiene sólo un hijo
izquierdo.
• Se copia la información del nodo sustituto en el marcado por el
apuntador auxiliar 1.
/ • Se desencadena el nodo señalado por el apuntador temporal de la
/ misma forma en que se hace para un nodo hoja o uno con hijo iz-
/ quierdo. En este caso, para el movimiento de apuntadores, se ten-
^/ drá que evaluar si el nodo marcado por el apuntador temporal es la
raíz del subárbol izquierdo del nodo señalado por el apuntador
..-*'**' auxiliar 1 o es de un nivel inferror.

3. Se libera el nodo señalado por el apuntador temporal.

¿Qué ventajas ofrece un ABB sobre el algoritmo •


de la búsqueda binaria?

Aunque la eficiencia de una búsqueda en un ABB es igual a la del


algoritmo de la búsqueda binaria en una estructura lineal, el ABB ofre­
ce adicionalmente la ventaja de su representación por medio de ligas.
La mejor representación para un ABB será utilizando memoria di­
námica obteniendo así sus beneficios, pues el algoritmo de búsqueda
binaria pierde su eficiencia si se trata de aplicar en una lista encadena­
da ordenada.

• ñ
Estructuras jerárquicas y arboles binarios de búsqueda

¿Qué desventajas tiene un ABB?

La principal desventaja de un ABB es la forma en que se realizan las


inserciones y eliminaciones de elementos. El orden de inserción y eli­
minación determina la forma en que se balancea el árbol y, por lo tan­
to, repercute en las búsquedas posteriores. En el peor de los casos, un
• ABB puede degenerar en una lista sobre la que se aplicará una búsque­
da secuencia!. Se puede hacer la prueba insertando en el árbol vacío
una secuencia ordenada de datos y comprobando que el árbol que se
forma es uno en que sólo se encadenan los nodos por medio de un sólo
apuntador y se degenera en una lista. Obviamente, en este caso el al­
goritmo de búsqueda en el ABB se comportará como una búsqueda
secuencial. La forma de atacar este problema se analizará en capítulos
posteriores al conocer otras estructuras.

¿Qué aplicaciones tiene un ABB? /

Un ABB será útil en cualquier aplicación en la que se requiera admi­


nistrar un grupo ordenado de datos en memoria principal con el obje­
tivo básico de buscar de manera eficiente cualquier dato. Se recomienda
que la aplicación tenga al grupo de datos bien definido desde un
principio y que no requiera de muchas altas y bajas para mantener el
árbol lo más balanceado posible.

¿Cómo se puede representar físicamente un ABB?


La forma de representar un ABB está obligada a utilizar ligas, ya sea
en memoria dinámica (preferentemente) o estática.
La base de esta representación estará en un nodo que contenga la in­
formación y los apuntadores a los subárboles izquierdo y derecho. El
ABB estará controlado mediante un apuntador principal al nodo raíz
del árbol (figura 8.9).

f Raíz >L

8
KEH HH3—-■ S I S
6 T

Figura 8.9. Representación de un ABB en memoria, con nodos que contienen


apuntadores a sus hijos izquierdos y derechos. El control del árbol está en el
^^ apuntador principal raíz

Q •
¿De qué manera se implementan las operaciones y/o aplicaciones sobre arboles binarios?

Declaración de tipos para un ABB en lenguaje C:

typedef struct nodo


{ tipoinfo info;
struct nodo *izq, *der; j tiponodo;

typedef üponodo *tipoarboI;

Declaración de tipos para un ABB en lenguaje C++:

* * \ \ class NodoÁrbol
\. (public:
\ \ tipoinfo info;
\ NodoÁrbol *izq, *der;

\ \ NodoArbolO {izq=der=NULL;}
NodoArbol(tipoinfo dato)
I {info=dato; izq=der=NULL;}

/
I / A i

/ / class ABB
/ / {prívate:
NodoÁrbol *raiz;
public:
..-"''* ABB() {raiz=NULL;}
~ABB() //se requiere un destructor
//otros métodos...
U

¿De qué manera se implementan las operaciones


y/o aplicaciones sobre árboles binarios?
La implementación de las operaciones sobre árboles binarios requiere
observar previamente qué tipo de solución conviene. Cuando lo que se
quiere programar requiere visitar sólo un camino lineal dentro del ár­
bol, una solución iterativa que maneje apuntadores será suficiente;
pero cuando se deba implementar una operación en la que se necesite
visitar todos los nodos del árbol, será conveniente hacer uso de la re-
cursividad como estrategia de solución. La recursividad, en este caso,
se presta como estrategia, pues los árboles binarios son una estructura
que se puede definir recursivamente, hablando de los subárboles izquier­
do y derecho.

• Q
istruLturas jerárquicas y arboles binanos de búsqueda

Algoritmo iterativo de la búsqueda

NodoABB *p=raiz;
while (p!=NULL)
I if(p->info s s valor)
' return(p);
• else
p=(p->info > valor? p->izq: p->der):
I
return(NULL);

Algoritmo iterativo de la inserción

NodoABB *NuevoNodo = new Nodo Árbol (valor);


NodoABB *actual = raíz, "anterior = NULL;
while (actual !=NULL)
(anterior=actuat;
actuaHactual->info>valor? actual->lzq: actual->der);j
if(anteríor==NULL)
raiz=NuevoNodo;
else
if(anterior->info > valor)
anterior->izq=NuevoNodo;
else
anterior->der=NuevoNodo;
~~-

Rutina recursiva para desplegar la información de un árbol binario

vaid despliega ( NodoABB* raiz)


I
if( raiz != NULL)
( ,
cout « Raiz->info; El orden de estas instrucciones
despliega( Raiz->izq); determina el tipo de recorrido
despliegai Raiz->der); en el árbol
i

/
I

y *
¿En qué consisten los recorridos en un árbol binario?

¿En qué consisten los recorridos en un árbol binario?


La acción de recorrer una estructura de datos es muy importante, pues
permite hacer algo en todos los elementos de la estructura (por ejem­
plo, desplegarlos en pantalla). Esto es relativamente simple en una
estructura lineal, pues sólo se recorre desde el principio hasta el final.
Pero ahora, en una estructura jerárquica como los árboles binarios, hay
diferentes formas de realizar estos recorridos.
Los algoritmos de los recorridos de un árbol binario se pueden plan-
\ N tear recesivamente de la siguiente forma (se entiende que "visitar" es
* ^ \ la acción por aplicar al nodo durante el recorrido). Asimismo, se obser-
^X \ va que un recorrido se aplica sobre cualquier árbol binario, sin importar
\ \ si es o no de búsqueda (figura 8.10).
\ i

\ \ Preorden: útil para reconstruir un ABB


1. Visite el nodo raíz del árbol.
• 2. Recorra en preorden el subárbol izquierdo del nodo raíz.
i 3. Recorra en preorden el subárbol derecho del nodo raíz.

/ / Inorden: útil para desplegar en orden la información de un ABB


/ / 1. Recorra en inorden el subárbol izquierdo del nodo raíz.
/ 2. Visite el nodo raíz del árbol.
. / / 3. Recorra en inorden el subárbol derecho del nodo raíz.

Postorden: útil para implementar el destructor de un árbol binario (eli­


minación de todos los nodos)
1. Recorra en postorden el subárbol izquierdo del nodo raíz.
2. Recorra en postorden el subárbol derecho del nodo raíz.
3. Visite el nodo raíz del árbol. #

Además de estos recorridos tradicionales, se tienen los recorridos


conversos, en los que el orden de recorrido se invierte a derecha-iz­
quierda, en vez de izquierda-derecha.
Finalmente, existe un recorrido llamado nivel por nivel, en el que
los nodos del árbol se visitan por niveles a partir del 0, y de izquierda
a derecha. Este algoritmo puede plantearse iterativamente, utilizando
una fila de la siguiente manera:

1. Inserte el apuntador al nodo raíz a una fila.


2. Mientras la fila no se vacíe:

• Saque el apuntador de la fila y procese el nodo señalado.


• Inserte en la fila los apuntadores de los hijos del nodo procesado
(si éstos existen). ^

• 6
Estructuras jerárquicas y árboles binarios de búsqueda

^ Ejemplo

f Preorden: = </&?$+^\
0^ Inorden: /&<? = +$
-A Postorden: &/?< + $ =
Preorden converso: =$+<?/&
<* ^ ^ Inorden converso: $+=?<&/
& Postorden converso: +$?&/<=
Nivel por nivel: =<$/? +&
V / /' /-
Figura 8.10. */ / /
r /
t /
i /

i ( EJERCICIOS 1 ® / /

l. Dado el siguiente árbol binario de búsqueda codificado (cada sím­


bolo de un nodo corresponde a un valor numérico), responda cada \
uno de los siguientes incisos: \ >.
a) ¿Qué símbolo representa el valor
0 numérico más grande en el árbol?
b) ¿Qué símbolo representa el valor
S
numérico más pequeño en el árbol?
?
<¿ «? c) ¿Qué símbolo representa el valor medio
^ en el árbol?
d) ¿Cuáles son los ancestros del nodo que
contiene el símbolo "?"?
e) ¿Cuántas comparaciones se requerirán para encontrar el símbolo
& en el árbol?
f) ¿Cuántos nodos como máximo podrían existir en el árbol si su
altura fuera igual a 4?
g) ¿Cuál es el símbolo cuyo valor numérico asociado es inmediata­
mente mayor al valor numérico del símbolo = ?
h) Si el símbolo % representa la suma de los valores asociados a los
símbolos $ y /, muestre con un dibujo cómo quedaría el árbol al
insertar el símbolo %.

Q •
Ejercicios

2. A continuación se muestra una serie de casos que deberán resolver­


se implementando una función libre en C++, y trabajando iterati­
vamente (no recesivamente) en el nivel físico del árbol binario de
búsqueda. Considere, para todos los casos, que el módulo recibirá
el árbol a través de un apuntador a su nodo raíz.

a) Realice un módulo que sirva para encontrar el número de nivel


en que se encuentra un dato en el ABB.
-"-.., b) Realice un módulo que, dado un elemento del árbol, obtenga el
\ N elemento que es su abuelo.
^ \ \ c) Realice un módulo que, dado un elemento del árbol, despliegue
^V \ los elementos que son sus ancestros.
\ \ d) Realice un módulo que, dados los valores de dos elementos de un
\ \ ABB, encuentre cuáles son los nodos ancestros comunes a am-
\ \ bos. Los ancestros se deberán desplegar del más antiguo al más
\ \ joven.
e) Realice un módulo que sirva para desplegar los valores de los no-
/ dos primos del nodo, cuyo valor se especificará como entrada al
/ / módulo.
/ i

/ / 3. Se tiene el siguiente ABB almacenado en un arreglo de memoria es-


/ / tática. Los nodos disponibles se manejan como una pila en una lista
^ / / doblemente encadenada a través de los campos izq y der. Tome en
~** y'' cuenta que 0 = NULL y que el apuntador disponible señala el tope
...--'"*" de la pila.

ÁRBOL IKFO gapER a) Muestre a través de un esquema el árbol que


[1] IQtoaca Isla] está representado en este arreglo.
[2J Nuevo Lean 0 0 •
[3] Yucatán _ 0 0 ^ p ^ ^ ^ ^ fc ^ siguientes p u m o s mues.

[5] oü^go " g 1 tre el estado del arreglo y de los apuntadores,


[6] , J3_j*_ después de ejecutar las operaciones que se in-
m T S N Í ÍD~7~ dican. Además, muestre el esquema del árbol
[9] r^ahuii'r5~"'o~"o" correspondiente. Para todos los puntos tome
siempre las condiciones iniciales (originales)
DISPONIBLES^ 6 del árb0\ Si es necesario, emplee la estrategia
del "menor de los mayores" al dar de baja un
nodo.

0 Inserte en el árbol el elemento Sonora.


II) Elimine del árbol original el elemento Zacatecas.
I'I'I) Elimine del árbol original el elemento que se encuentra en la
raíz. _

• g
Estructuras jerárquicas y árboles binarios de búsqueda

Nota: los siguientes problemas se pueden aplicar en cualquier árbol bi­


nario, sin importar si es de búsqueda o no.

4. Escriba una función recursiva que sirva para obtener la altura de un


árbol binario. La altura de un árbol binario se define como la distan­
cia entre el nodo raíz y el nodo hoja más alejado de la raíz, o bien,
• la cantidad de niveles que tiene el árbol. La función recibirá como
entrada el apuntador al nodo raíz del árbol.

5. Escriba una función que sirva para obtener la anchura de un árbol


binario, que se define como la cantidad máxima de nodos que se en­
cuentran en algún nivel del árbol. La función recibirá como entra­
da el apuntador al nodo raíz del árbol. Para este caso, se recomienda
apoyarse en la práctica del recorrido nivel por nivel.

6. Modifique la implementación del recorrido nivel por nivel, para des­


plegar los elementos de un árbol binario, de tal forma que para cada
nivel del árbol se muestren los elementos que hay en el nivel, de iz­
quierda a derecha. La función recibirá como entrada el apuntador al
nodo raíz del árbol. '\ \
% \

7. Realice la implementación de una función que sirva para desplegar


en pantalla la información que guardan las hojas de un árbol, de iz­
quierda a derecha. Describa también la rutina equivalente que lo
haga de derecha a izquierda.

8. Para cada uno de los siguientes incisos, dados los recorridos, cons­
truya el árbol binario correspondiente:

a) Inorden: ? / - X + % * * # & 3 l ¿ @

Postorden: ? ? - / X % * & X 3 ¿ $ @ #

b)Preorden: 3 1 9 4 7

Postorden: 7 4 9 1 3

9. ¿Qué tipo de recorrido sobre un árbol binario realiza la siguiente


función?

Nota: suponga que la clase pila está parametrizada y que se imple-


menta según las necesidades de esta aplicación.

6 •
Rutoeuaiuación

vote recorrido (NodoArbol *Raiz)


{ P¡la<NodoÁrbol*>P;
Nodo Árbol* aux m Raíz;
do
{ while (aux t= NUIL)
{ cout « aux->info;
Pila.Meter( aux);
aux = aux->izq; }
if(Pila.Sacarf aux))
" \ aux = aux->der;
" \ . \ } while (aux != NULL) // ( / Pila.Vacia());

\ \ r— HUTOEUflLUnCIÓN < \ =
\ \ I I
\ 1 I

í Para las preguntas 1, 2 y 3 utilice el árbol binario de búsqueda codifi-


/ cado que se muestra en la siguiente figura:

1. Si los símbolos representan la codificación de los valores del 1 al 20,


¿cuál es el valor asociado al símbolo +?

a) 5 b)8 c)9 d) 13 e) 15

2. Si se quisiera dar de baja la raíz del árbol, ¿cuál de los siguientes


símbolos es candidato para sustituirlo?

a)R b)- c)0 d)T e) Z

• 0
Estructuras jerárquicas y árboles binarios de búsqueda

3. Si el símbolo X representa un valor menor que todos los valores del


árbol, ¿en qué posición se insertaría?

a) A la izquierda del nodo que contiene el símbolo L.


b) A la derecha del nodo que contiene al símbolo O.
c) A la izquierda del nodo que contiene al símbolo Z.
• d) A la izquierda del nodo que contiene al símbolo - .
e) A la derecha del nodo que contiene al símbolo L.

INFO IZQ DER ,.-''' ^ .

4. La siguiente figura muestra la re- 1| | 4 | 0 S


presentación de un árbol binario 2 " —¡¡¿^f~ ~~6 rj~ /
de búsqueda en memoria estática. 3 ¡- H5—5- /
¿Cuál es el valor del apuntador ! ¡ /
4
RAÍZ después de eliminar el valor __3 0_ í /
5 AMÉRICA
TIGRES del árbol? | 0 0
Suponga que 0 = NULL y que la 6 " MONTERREY 5 0.
lista de disponibles se maneja co- 7 i Q~ \ \
mo pila a través del campo izq. I ¡ 1 \
RAÍZ ■ 2 DISPONIBLES = 7 \ \

a) 5 b) 6 c) l d) 0 e) 2

5. Los recorridos conversos son una vanante de los recorridos tradi­


cionales sobre un árbol binario. Se basan en la misma idea de los
recorridos tradicionales, sólo que en vez de visitar primero el subár-
bol izquierdo y luego el derecho, se visita primero el subárbol dere­
cho y después el izquierdo.
Para el siguiente árbol binario, indique cuál de los siguientes recor­
ridos es correcto:

A
a) Preorden converso: A$#+°*R3@X9
^ b) Postorden: °+ #$RX9@3*A
3 R # c) Inorden converso: $+ °#AR*X@93
d) Todos los anteriores
e) Ninguno de los anteriores

@ •
Rutoetialuación

6. ¿Qué desplegará en pantalla la ejecución del módulo misterio, si se


le envía como entrada el árbol que se muestra?

void Misterio (NodoÁrbol *Raiz);


{ PilaP; FilaF; NodoÁrbol *X;
X = Raíz;
A if (X /= NULL)
{ FMeter(X);
0 'Z while (F.Sacar(X))
""'^ ut< 0 \ w {P.Meter(X);
' \ \^ O if(X->der >=NULL FMeter(X->der);
^ \ \ €l £¿ if(X->izq != NULL F.Meter(X->izq);
N. \ ^ }; '

: a ) U * Z M 8 P R E + C5
/ b)5C + E R P 8 M Z * U
/ / c)C 5 + R E M 8 P * Z U
/ / d) U Z * P 8 M E R + 5 C
/ / e)U*MR + C 5 Z 8 P E

7. ¿Cuál es el módulo recursivo para contar la cantidad de valores


,.,---*'' negativos que hay en un árbol binario?

a) int Cuenta (NodoÁrbol *Raiz);


{ if(Raiz==NULL)
return 0;
else •
if(Raiz->info <0)
return I;
else
return Cuenta(Raiz->izq)+Cuenta(Raiz->der);
}

• 0
Estructuras jerárquicas y árboles binarios de búsqueda

b) int Cuenta (NodoÁrbol *Raiz);


{ if(Raiz==NULL)
return 0;
else
if(Raiz->info>0)
return 1 ;
• else
return Cuenta(Raiz->izq)+Cuenta(Raiz->der);
} ..."-
»**
c) int Cuenta (NodoÁrbol *Raiz); / ' s**~
{ if(Raiz==NULL) / X
return 0; / /
else I /
if (Raiz->info < 0)
return( 1 +Cuenta(Raiz->izq)+Cuenta(Raiz->der));
else
return 0;
\i \\
i \

d) int Cuenta (NodoArbol *Raiz); \


{ if(Raiz==NULL) \ ^
return 0; \ ^
else
if(Raiz->info > 0) '■--..._
return(Cuenta(Raiz->izq)+Cuenta(Raiz->der));
else
return(¡ + Cuenta(Raiz->izq)+Cuenta(Raiz->der));
}

e) Ninguno de los anteriores.

0 •
Capítulo Q

OBJETIVOS

• Entender la importancia que tiene el balanceo en un ABB.


• Describir las características de los árboles AVL: balanceo basado en
alturas de subárboles.
• Definir qué es el Factor de Balance (FB) de un nodo en un árbol AVL.
• Explicar la forma en que se realiza una rotación simple y una rotación
doble en un árbol AVL, describiendo el movimiento de apuntadores.
• Describir los algoritmos para las operaciones de inserción y elimi­
nación de elementos en un árbol AVL.
• Implantación del nivel físico de un TDA árbol AVL utilizando la pro­
gramación orientada a objetos.

Q
Arboles HUÍ

¿Por qué es importante el balanceo en un árbol de búsqueda?


La manera en que los elementos estén distribuidos en un árbol de bús­
queda determinará su altura y, en consecuencia, la cantidad de compa­
raciones a realizar al buscar un elemento (eficiencia). Lo ideal seria
que el árbol tuviera sus elementos distribuidos en forma equilibrada o
balanceada, consiguiendo así la mayor eficiencia que ofrece la búsqueda
binaria.

¿Cuál es el número máximo de comparaciones que se , ^ ..--


realizan en el peor caso de búsqueda en un ABB? / /"'
* /
* /
Análisis general de la eficiencia de búsqueda en un ABB

La cantidad máxima de comparaciones al realizar una búsqueda en un


ABB está determinada por la altura del árbol.
Si un ABB degenera en una lista, se tiene un árbol cuya altura es
igual a la cantidad de nodos en el árbol y el peor caso corresponderá a
realizar tantas comparaciones como nodos tenga el árbol.
Sin embargo, ¿qué pasa si el árbol está balanceado! Si la altura de
un ABB determina la cantidad máxima de comparaciones en una bús­
queda, lo ideal seria tener la altura mínima que puede tener un ABB
para n elementos. La altura mínima en un ABB con n elementos se dará
en la medida en que cada nivel del árbol esté integrado a su máxima
capacidad.
Al analizar cual es la cantidad máxima de nodos que puede tener
cada nivel en un ABB, en la figura 9.1 se puede observar que:

' ( Nivel 0 -> 1 elemento


1
Nivel 2 -> 2 elementos
Nivel 3 -> 4 elementos
Nivel 4 -> 8 elementos
\ \ / \ Nivel 5 -> 16 elementos
Oó o o o oo o
V )
Figura 9.1.
En general, se tiene que el número máximo de nodos en el nivel k en
un árbol binario es 2*
Con base en esto, se puede encontrar la cantidad total de elemen­
tos que puede guardar un árbol binario de altura k, es decir, que tiene
k niveles (figura 9.2):

o •
¿Qué es un árbol balanceado?

Altura O -> 1 elemento


X ? M u r a 2 -> 3 elementos

Figura 9.2.

'---... Por lo tanto, se tiene que el número máximo de nodos en un árbol bi-
**\ nario de altura k es de: 2 M .
X \ Ahora bien, si la altura de un ABB determina el número máximo de
\ \ comparaciones al buscar un elemento, ¿cuántas comparaciones se
\ \ harán al buscar un elemento en un ABB ideal que tiene n elementos?
\ \ De acuerdo con el análisis que hemos hecho, la respuesta se obtiene
\ al encontrar el valor de k, dado el valor de n para la fórmula:
\ \ n = 2*-l.
Por lo tanto, despejando k, aplicando las leyes de los logaritmos, se
/ '} obtiene
/ / k = log2(» + 1).
/ Entonces, la cantidad máxima de comparaciones a realizar en un
/ ABB ideal de n elementos es:
y / log2(n+1)
En este caso, se puede comprobar que la fórmula coincide con la
„--'* de la búsqueda binaria en un arreglo, si se complementa con la fun­
ción techo:
riog 2 («+ i)i.
Lo anterior nos lleva a concluir que, para que el peor caso de una
búsqueda en un ABB se cumpla con la mayor eficiencia posible, el #
ABB debe estar balanceado.
Sin embargo, las operaciones de un ABB no aseguran que se cum­
pla el balanceo, por lo que se necesita una mejora en las operaciones,
sin demeritar su eficiencia. Hasta ahora, no se ha encontrado la forma
eficiente de mantener un ABB totalmente balanceado.

¿Qué es un árbol balanceado?


Se considera que un árbol binario está balanceado cuando todos sus ni­
veles, excepto el último, están integrados a la máxima capacidad de
nodos.
Las investigaciones respecto a esta estructura de datos no han logra­
do encontrar una técnica eficiente para manejar árboles de búsqueda
completamente balanceados; las propuestas han llegado sólo a presen­
tar árboles parcialmente balanceados, sin repercutir en la eficiencia de ^

• ~ Q
Arboles flt/L

las operaciones de inserción y eliminación de nodos. La más común y


usada de las técnicas es la de los árboles AVL.

¿Qué es un árbol AVL?


Un árbol AVL es un árbol binario de búsqueda que trata de mantener-
#
se lo más balanceado posible, conforme se realizan operaciones de in­
serción y eliminación. Fueron propuestos en 1962 por los matemáticos
rusos Adelson-Velskii y Landis, de donde surge su nombre. Su contri­
bución principal consistió en presentar algoritmos eficientes de inser- ^
ción y eliminación de elementos considerando un balanceo en el árbol /' /""'
que, a su vez, repercute en la eficiencia de las búsquedas. /
Formalmente, en los árboles AVL se debe cumplir el hecho de que
para cualquier nodo del árbol, la diferencia entre las alturas de sus
subarboles no exceda una unidad (figura 9.3). /

o o ?\ tC \

V^ ÁRBOLES AVL ARBOLES QUE NO SON AVL /

Figura 9.3. Ejemplos de árboles AVL. El contenido de un árbol AVL debe cumplir con las re­
glas de un ABB,

¿Qué es el Factor de Balance (FB) de un nodo?

Los nodos de un árbol AVL guardan un valor entre 1 y - 1 , lo que se


conoce como Factor de Balance (FB), y representa la diferencia en­
tre las alturas de sus subarboles. Un FB igual a cero en un nodo sig­
nifica que las alturas de sus subarboles son iguales; un FB positivo
significa que el subárbol derecho es más grande que el izquierdo, y
un FB negativo que el subárbol izquierdo es más grande que el derecho
(figura 94).

o •
¿Cómo se realiza el proceso de inserción de un elemento en un árbol RllL?

( > ^ ^

\ \ V )
\ \ Figura 9.4. Árbol AVL con sus factores de balance en cada nodo.

¿Cuál es la especificación lógica del TDA árbol AVL?


; La especificación del TDA árbol AVL es idéntica a la del TDA ABB, y
/ sólo se considera un cambio en las postcondiciones de las operaciones
/ / de INSERTAR y BORRAR para obtener un árbol que cumpla con las
restricciones de altura ya explicadas.

^ y ¿Cómo se realiza el proceso de inserción de un elemento


, - en un árbol AVL?
Inicialmente, el proceso de inserción de un elemento en un árbol AVL
es idéntico al de un ABB: se busca la posición en el árbol en que el
nuevo elemento quede como un nodo hoja (puesto que el nuevo nodo
es hoja, tendrá un FB igual a cero).
Una vez hecha la inserción como si fuera un ABB, se deberá verifi­
car si afecta el balanceo del árbol, según las reglas de los AVL. El
mejor de los casos será cuando el nuevo nodo no provoque un desba­
lanceo, implicando sólo la modificación de los FB de los ancestros al
nuevo nodo. El otro caso será cuando ocurra un desbalanceo que obli­
gue a hacer movimientos de apuntadores y de FB para balancearlo.
La forma de detectar algorítmicamente en qué caso se hará o no
un balanceo en el AVL, se basa en la búsqueda de un nodo pivote. Un
nodo pivote es aquel que tiene un FB diferente de cero y es el más cer­
cano de los ancestros del nodo recién insertado. Basados en este
concepto, se pueden detectar los siguientes casos:

• 0
Arboles flUL

1. El árbol AVL carece de nodo pivote. Esto significa que todos los an­
cestros del nuevo nodo tienen un FB igual a cero (figura 9.5). En este
caso, el nuevo nodo no desbalancea el árbol y sólo se tendrán que
ajustar los valores de los FB de todos los ancestros, volviéndose
positivos o negativos, según el valor del nuevo elemento.

Al hacer la inserción j ,_--


sólo se ajustan los FB ,,--''

0 *- + ,^| «** j^^~

o >o oQ / /
i ' * ' /
oO oO o Os o oO +^ oO ' /

N>
Nuevo nodo
o
Nuevo nodo j
/ /
;

Figura 9.5. No existe nodo pivote al hacer la inserción. \


i \
* V
1 \

2. El árbol AVL tiene nodo pivote y el nuevo se ha insertado en el su-


bárbol más pequeño del pivote (figura 9.6). En este caso tampoco
habrá desbalanceo, pues se igualan las alturas de los dos subárboles
del nodo pivote, y sólo se tendrán que ajustar los FB de los ancestros
que están a partir del nodo pivote, volviéndose positivos o negati­
vos según el valor del nuevo elemento.

Al hacer- la inserción
sólo se aiustan los FB

0 - ~y (""N Nuevo pivotB 0 £j O*-*^

y +
y\ +
\~
+
°o °o 0 oO oo ° o o o
oO O o O oo
Nuevo nodo Nuevo nodo

l
Figura 9.6. Existe nodo pivote y la inserción se realiza sobre el subárbol
)
más pequeño del nodo pivote.

0 •
¿Cómo se realiza el balanceo en un árbol flUI?

3. El árbol AVL tiene nodo pivote y en el subárbol más grande de éste


se inserta el nuevo nodo. En este caso se desbalancea el árbol a par­
tir del nodo pivote y tendrá que realizarse un balanceo.

¿Cómo se realiza el balanceo en un árbol AVL?


Adelson-Velskii y Landis detectaron que, ante un problema de desbalan­
ceo, todos los casos podían resolverse aplicando uno de los cuatro esque-
—-.. mas sencillos de balanceo; a estos esquemas los llamaron rotaciones
" \ (por la forma en que se mueven los nodos), y consisten en modificar los
"""""-N. \ apuntadores de ciertos nodos, según el esquema, junto con algunos FB.
^v \ Lo más valioso de esta propuesta es que el balanceo afecta sólo los
\ \ nodos que forman parte del subárbol, cuya raíz es el nodo pivote, dejan-
\ \ do intactos los nodos del resto del árbol.

\ \ El esquema de la rotación simple, que puede ser izquierda (RSI) o dere­


cha (RSD) implica el movimiento de tres apuntadores (figura 9.7). El es-
i : quema de la rotación doble, que también puede ser izquierda (RDI) o
/ / derecha (RDD), implica el movimiento de cinco apuntadores (figura 9.8).

A—i ^
^ S ( Resto del árbol ) ( Resto del árbol )

.__,-"*''' (~\ ROTACIÓN SIMPLE (~\

+
C^\ Nodo pivote j T O

A © °& A
X~\^ Ajustar FB en
I \ h f\ A A / \ ruta de búsqueda

l\ A-A A-A ^
/■W'\ r~\
h\l'\o Nuevo nodo
y Nuera nodo J

Figura 9.7. Esquema de la rotación simple izquierda. Los triángulos representan subárboles de cualquier altura (la
misma para todos). La rotación hace un balanceo a partir del nodo pivote y el resto del árbol no se afecta. Obser­
ve que el valor A es mayor que B y hay congruencia después de la rotación. La imagen de la RSD se obtiene
viendo este esquema a través de un espejo

. 0
Arboles HUL

r ,— =) , , >
Resto del árbol } ( Resto del árbol
T —— ■ 1
ROTACIÓN DOBLE

B Nodo pivote fe )v

/ \ ° Cc^ \ —*" A \

L Nuevo nodo

Figura 9.8. Esquema de la rotación doble izquierda. Los triángulos 1 y 4 representan subárboles de cualquier altu­
ra; tas triángulos 2 y 3 son subárboles con una altura menor en un nivel, con respecto a I y 4. La rotación hace un
balanceo a partir del nodo pivote; el resto del árbol no se ve afectado. Observe que el valor C es mayor que B y me­
nor que A, por lo que hay congruencia después de la rotación. El esquema de RDD se obtiene viendo este esquema
a través de un espejo.

Algoritmo general para la inserción de un nodo en un árbol AVL


(vea ejemplo página 147).

1. Inserte el nodo como en un ABB (rutina de inserción ya conocida).


2. Busque el nodo pivote. Coloque los apuntadores Pl, P2. P3 y P4,
donde:
Pl = apuntador al nodo padre del nodo pivote.
P2 = apuntador al nodo pivote.
P3 = apuntador al nodo hijo del nodo pivote, que es la raíz del
subárbol más grande.
P4 = apuntador al nodo hijo del nodo apuntado por P3. que sigue en
la ruta de búsqueda del nuevo nodo.
3. Si no existe nodo pivote (P2 apunta a vacío), entonces modifique los
FB desde la raíz hasta el nuevo nodo (rutina que modifica los FB).
Si el nuevo nodo se insertó en el subárbol más pequeño del nodo
pivote, entonces modifique los FB desde el nodo pivote hasta el nue­
vo (rutina que modifica los FB).
Si no es así, verifique el tipo de rotación:
Si es rotación simple, entonces

e
-> Modificar apuntadores y FB (rutina que realiza la rotación

simple).

*
¿Cómo se realiza un balanceo en un árbol fllíL?

Si no,
-> Modificar apuntadores y FB (rutina que realiza la rotación
doble).

Algoritmo para la rotación simple


Suponga que los apuntadores Pl, P2 y P3 ya están colocados según los
esquemas:
1. Si P l n o apunta a vacío
\ , Si la información del nuevo nodo es menor que la información
- \ X apuntada por Pl:
\ , \ Hijo izquierdo de P l = P 3
\ \ si no
\ \ Hijo derecho de Pl =P3
\ \ si no
\ \ P3 es la nueva raíz del árbol.
2. Si la información del nuevo nodo es menor que la información apun­
tada por P2:
/ Hijo izquierdo de P2 = hijo derecho de P3
/ / Hijo derecho de P3 = P2
/ / Modificar el FB desde el hijo izquierdo de P3 hasta el padre del
/ / nuevo nodo.
^ ^ si no
...-**" Hijo derecho de P2 = Hijo izquierdo de P3
---'"" Hijo izquierdo de P3 = P2
Modificar FB desde el hijo derecho de P3 hasta el padre del
nuevo nodo.
3. El FB del nodo señalado por P2 = 0.

Algoritmo para la rotación doble


1.SiPl no apunta a vacío
Si la información del nuevo nodo es menor que la informa­
ción apuntada por Pl:
Hijo izquierdo de Pl = P4
si no
Hijo derecho de Pl = P4
si no
P4 es la nueva raíz del árbol.
2. Si la información del nuevo nodo es menor que la información
apuntada por P2:
Hijo derecho de P3 = hijo izquierdo de P4
Hijo izquierdo de P2 = hijo derecho de P4 ^

• — 3
Arboles HUL

Hijo izquierdo de P4 = P3
Hijo derecho de P4 = P2
-> ** seguir en 3.4
si no
Hijo izquierdo de P3 = hijo derecho de P4
Hijo derecho de P2 = hijo izquierdo de P4
• Hijo derecho de P4 = P3
Hijo izquierdo de P4 = P2
-> ** ira 3.b .-—
y'"
3.a Si la información del nuevo nodo es menor que la información de P4:
modificar FB desde el hijo derecho de P3 hasta el padre del
nuevo nodo
modificar FB del nodo señalado por P2 (ahora vale +1)
si no /
Si la información del nuevo nodo es mayor a la información de P4:
modificar FB desde el hijo izquierdo de P2 hasta el padre
del nuevo nodo
modificar FB del nodo señalado por P3 (ahora vale -1)
modificar FB del nodo señalado por P2 (ahora vale 0)

3.b Si la información del nuevo nodo es mayor que la información de P4:


modificar FB desde el hijo izquierdo de P3 hasta el padre del
nuevo nodo
modificar FB del nodo señalado por P2 (ahora vale -1)
si no
Si la información del nuevo nodo es menor que la información
deP4:
modificar FB desde el hijo derecho de P2 hasta el padre
del nuevo nodo
modificar FB del nodo señalado por P3 (ahora vale +1)
modificar FB del nodo señalado por P2 (ahora vale 0)

e •
Ejemplo

^ Ejemplo

/Inserción del elemento 52 sobre el árbol AVL: "^

* \ o0 - es ggi o °C>
X. *
^^ *
\ \ La inserción inicial se realiza como en un ABB:

+
0 Jtot, ■ &. oo

/ /
>^ /
^ El nodo pivote es el 45 y la inserción provoca una rotación simple a la
izquierda, que genera el siguiente árbol:

Para el siguiente árbol AVL:

• Q
Rrboles RUI

^J Ejemplo (continúa)

• La inserción de los siguientes elementos no provoca rotaciones: 12,


19,28,33,45,51,75.
• La inserción de los siguientes elementos provoca rotación simple
^ izquierda: 100, 65,68.
■ La inserción de los siguientes elementos provoca rotación simple
derecha: 5, 16.
• La inserción del siguiente elemento provoca rotación doble iz­
quierda: 8. / ^ *
• La inserción de los siguientes elementos provoca rotación doble
derecha: 36, 57, 59, 95. / /

¿Cómo se elimina un nodo de un árbol AVL? / /


Para eliminar un nodo de un árbol AVL se aplica inicialmente el algo­
ritmo ya conocido para dar de baja un nodo en un ABB normal. Vea los
ejemplos de las páginas 152 y 153 (figuras 9.14 a 9.17).
Físicamente, este algoritmo sólo da de baja nodos que tienen un \ \
hijo o ninguno. Para el caso en que el nodo tiene dos hijos, se hace una \
sustitución con algún nodo que no tenga hijos o que tenga uno, apli­
cando el método de "el mayor de los menores", o el de "el menor de
los mayores", como se vio en el capítulo anterior.
Una vez dado de baja el nodo correspondiente, pueden ocurrir dos
situaciones:
1. El nodo que se borró no provocó un desbalanceo en el árbol; en este
caso, sólo se ajustan algunos factores de balance.
2. El nodo que se borró provocó un desbalanceo en el árbol; en este caso,
tendrán que ajustarse algunos apuntadores a través de rotaciones y
algunos factores de balance.
A diferencia del procedimiento de inserción de un nodo, ahora no se
utilizará un nodo pivote, pues una baja puede ocasionar un desbalan­
ceo total en el árbol y más de una rotación.
Al dar de baja un nodo se tendrá que analizar el balanceo de todos
los ancestros de la ruta de búsqueda, desde el padre del nodo borrado,
hasta el nodo raíz del árbol. Esto se debe a que pueden modificarse las
alturas de los subárboles por el balanceo.
La forma de detectar algorítmicamente en qué caso se hará o no un
balanceo en el AVL, después de eliminar un elemento, se puede resu­
mir en los siguientes casos:
Sea P2 un apuntador del nodo por analizar en el recorrido, desde el
padre del nodo borrado hasta la raíz. Sea Pl un apuntador al padre
del nodo señalado por P2.

0
Casos

C a s o s (figura 9.13)

1. El nodo señalado por P2 tiene un FB = 0 y no hay desbalanceo. En es­


te caso se ajusta el FB a + o - , según el nodo eliminado; ya que la al­
tura del árbol señalado por P2 no se modifica, no es necesario seguir
analizando los nodos del recorrido.
2. El nodo señalado por P2 tiene un FB o 0. El nodo que se borró
pertenecía al subárbol más grande y no hay desbalanceo (temporal­
mente).
* \ En este caso el FB del nodo señalado por P2 es igual a cero, y puesto
s ^ \ que la altura del árbol señalado por P2 se modificó (se redujo), es ne-
>v \ cesario mover P2 al siguiente nodo ancestro y volver a analizar en qué
\ \ caso se está.
\ \ 3. El nodo señalado por P2 tiene un FB o 0. El nodo que se borró per-
\ \ tenecía al subárbol más pequeño, por lo que hay desbalanceo y es
\ \ necesario aplicar una rotación y ajustar FB.
Sea P3 el apuntador al subárbol más grande del nodo señalado por
I P2.
/ i 3.1 El FB del nodo señalado por P3 es igual a 0.
I ¡ Se realiza una rotación simple y se ajusta el FB del nodo apunta-
/ / do por P3.
/ / Puesto que la altura del árbol apuntado ahora por P3 no cambió,
¿r / no es necesario seguir analizando los nodos ancestros.
3.2 El FB del nodo señalado por P2 es igual al FB del nodo apuntado
..---**' por P3. Se realiza una rotación simple y se ajusta el FB del nodo
señalado por P3. Puesto que la altura del árbol señalado ahora por
P3 se redujo, es necesario seguir analizando los nodos ancestros,
por lo que P2 apunta al nuevo nodo y se vuelve a analizar en qué
caso se está ubicado.
3.3 El FB del nodo señalado por P2 es contrario al FB del nodo apun­
tado por P3. Se realiza una rotación doble, donde P4 es el apunta­
dor al subárbol más grande del nodo señalado por P3, y se ajusta
el FB de los nodos correspondientes. Puesto que la altura se modi­
fica, se analizan los nodos ancestros.

• Q
Arboles mu

1 +
CAS0 1 A ° AS
Altura
sin cambio

/ 1 2 / 1\ / 2 \
í = i A_A '—* A_A
A> ° .----■
CASO 2 J&-- A^V
Altura
—*■ reducida x*"

CASO 3.1
,; +
■ A A 0\ "
Q
B AJ
¿ A
+

A
A B Altura

sin cambio

CASO 3.2 (V, °

/ ^TAV °/B£ \
/^ Altura
/ ^ / \ reducida

^¿AÁ\ AAAAAA
+
CASO 3.3 X

~X¿1+ * ÍT AV>a+
G p Altura
\ Z A ^A reducida

A AA A A AA A
/ \ /\ /\ M / \ M /a\ M
í=sá=i tutu
v y
^^ Figura 9.13. Esquemas de rotación para eliminar un elemento.
Casos

Algoritmo general para dar de baja un nodo en un árbol AVL


(vea ejemplo páginas 151 y 152)

1. Borrar el nodo como si fuera un ABB normal.


* A esta rutina hay que agregarle el que vaya guardando en una
pila los apuntadores a los nodos en el recorrido desde la raíz
hasta el padre del nodo que físicamente se borró.

2. Mientras haya apuntadores en la pila:


2.1.Sacar apuntador de la pila y ajusfado a P2
^ \ 2.2.Ajustar Pl (Observar el tope de la pila)
\ \ 2.3.Si el FB del nodo apuntado por P2 = 0, entonces:
\ \ Si la información del nodo real borrado es menor o igual a
\ la información de P2
-> Cambiar el FB del nodo apuntado por P2 a +1.
\ si no
-> Cambiar el FB del nodo apuntado por P2 a -1. (Caso 1)
Salir del ciclo.
/ / Si no
/ Si el FB del nodo apuntado por P2 es +1 y la información
/ / del nodo real borrado es mayor a la información de P2
/ ó el FB del nodo apuntado por P2 es -1 y la información del
^/ nodo real borrado es menor o igual a la información de P2,
,--' entonces:
..,--"'' -> El FB de el nodo apuntado por P2 = 0 (Caso 2)
si no
Ajustar P3 a la raíz del subárbol más grande de P2
Si el FB del nodo apuntado por P3 = 0
-> Aplicar una rotación simple (Caso 3.1) #
Salir del ciclo
Sino
Si el FB del nodo apuntado por P3 es igual al FB
del nodo apuntado por P2
-> Aplicar una rotación simple (Caso 3.2)
si no
Ajustar P4 y
Aplicar una rotación doble (Caso 3.3)

• 0
Rrboles miI

% Ejemplo

'Eliminación del elemento 10 de un árbol AVL: i

tr@ / /
t /

t /
I /

La eliminación física del nodo 10 provoca un desbalanceo en el |


nodo 15:

\ \V
B balanceo se realiza por medio de una rotación doble izquierda; el
árbol queda en la siguiente forma:

l )

I .
Ejemplo

^ Ejemplo

/ E n el siguiente árbol AVL: ^\

I ! - La eliminación de los siguientes elementos no provoca rotaciones:


/ / 17,18,58,60,66.
/ / • La eliminación del siguiente elemento provoca sólo una rotación sim-
/ / ple izquierda: 52.
. / / • La eliminación del siguiente elemento provoca sólo una rotación sim­
ple derecha: 25.
...--''* - La eliminación de los siguientes elementos provoca sólo una rotación
doble izquierda: 7, 10.
• La eliminación del siguiente elemento provoca sólo una rotación
doble derecha: 42.
• La eliminación del 80 provoca una rotación doble derecha en el m
nivel del nodo 70 y, posteriormente, una rotación simple derecha
en el nivel del 50.
V y

• @
Arboles RUI

¿Cómo se puede representar el TDA árbol AVL en memoria?

Los nodos del árbol tendrán que contener, además de su información,


un atributo que permita conocer el estado de las alturas de sus subár-
boles; adicionalmente ese atributo puede contener el factor de balance.
Otra estrategia sería almacenar la altura del árbol, cuya raíz es el nodo
en cuestión.

Definición de tipos en lenguaje C:


y'
typedef struct nodo
{tipoinfo info;
char FB; lio altura del subarbol
struct nodo *izq, *der;} tiponodo;
typedef tiponodo *üpoarbol;

Definición de tipos en lenguaje C++:

class NodoAVL
{ public:
tipoinfo info; \
char FB; lio altura del subarbol \
NodoAVL *izq,*der; } ; \ ^

class AVL
i prívate:
NodoAVL *raiz;
public:
i ¡métodos correspondientes);

Formas de implementación de las operaciones en un AVL


• Algoritmos iterativos basados en factores de balance (como se anali­
zó anteriormente).
• Algoritmos recursivos basados en alturas de subárboles.

Consideraciones importantes sobre los algoritmos iterativos basa­


dos en FB
• Inserción: el proceso de insertar como un ABB debe aprovechar la
búsqueda para localizar al nodo pivote.
• Eliminación: se requiere una pila para guardar todos los ancestros del
nodo eliminado y así permitir el análisis de balanceo.

Q •
¿Cómo se puede representar el TDR árbol RUI en memoria?

Consideraciones importantes sobre los algoritmos recursivos basa­


dos en alturas

• Eliminación: la recursividad implícitamente facilita el análisis de los


ancestros del nodo eliminado.
• Inserción: no hay búsqueda de nodo pivote debido a que la recursivi­
dad hace también un análisis de todos los ancestros (aunque no sea
necesario).

Implementación general de la rotación simple izquierda


\ . en lenguaje C

\ // P es un apuntador al nodo pivote en la inserción


\ IIQes un apuntador ai hijo del nodo pivote correspondiente
\ NodoAVL *hijo = P->der;
if(P->info > Q->info)
Q->der= hijo;
else
I Q->izq = hijo;
I P->der = hijo->izq;
/ hijo->izq = P;
/ II ajustar FB o alturas, según implementación.

,.-'" Implementación general de la rotación doble derecha

// P es un apuntador al nodo pivote en la inserción


IIQ es un apuntador al hijo del nodo pivote correspondiente
NodoAVL *hijo = P->Ízq; *nieto = hijo->der;
if(P->info > Q->info) #
Q->der= nieto;
else
Q->izq = nieto;
hijo->der = nieto->izq;
P->izq = nieto->der;
nieto->der = P;
nieto->izq = hijo;
II ajustar FB o alturas, según implementación.

Implementación en C para árboles AVL

void insertaAVL (tipoarbol *raiz, tipoinfo nuevo)


{tipoarbol aux, ant, pl, p2, p3, p4, nvo;
(continúa)

• Q
Árboles BUL

/* CREAR EL NUEVO NODO */


nvo - mal loe isizeaf |'tipanado));
nvo->info = nuevo; nvo->izq = nva->der = NULL;
nvo->FB = 0;
/* INICIALIZACIÓN DE APUNTADORES PARA BUSCAR DONDE SE
INSERTARÁ EL NUEVO NODO Y DONDE QUEDAN AJUSTADOS EL
NODO PIVOTE (P2) Y SU PADRE (P l) */
aux = *raiz;
anl=pl =¡>2=NULL;
i* CICLO PARA AJUSTAR APUNTADORES */ ,.- "
while (aux ¡m NULL) y'''
{ if(am->FB !- 0) {p2 = aux; p! = ant} /* AJUSTAR NODO PIVOTE */ X**^*
ant - aux: / S^
if (nuevo < aux->info) aux = aiíx->izq; / /
eise aux - aax->der; ! /

l * INSERCIÓN DEL NUEVO NODO COMO HOJA EN EL LUGAR / /


ADECUADO */
if(ant =- NULL) { *raiz = nvu: return;}
else if(nuevo < ant->info) am->izq = nvo; \ \
else ant->der — nvo: \ \
y \

\ \
/* VERIFICACIÓN DE CASOS SEGÚN EL NODO PIVOTE */ \ \
if (p2 == NULL) modiftca_FB(*raiz. nuevo): /* SI NO EXISTE NODO \
PIVOTE */ \ N.
else *\ ^^^^_
if(((p2->FB==l)&&tnuevo<p2->info))¡¡((p2->FB== })&& (nueva>p2- " \
>info)Jmodifica_FB(p2, nuevo); f* INSERCIÓN EN SUBÁRBOL "'---._
PEQUEÑO DEL PIVOTE */
else
/* CASOS DE ROTACIÓN */
/* P3 SE AJUSTA AL SUBÁRBOL MÁS GRANDE DE P2 Y COMPARAN-
DO EL VALOR DEL NUEVO NODO CON RESPECTO A P3 SE DECIDE
SI ES UNA ROTACIÓN SIMPLE O DOBLE */
if (p2->FB = = ¡) { p3 = p2->der;
if (nueva > p3->infa) rotacion_simple(raiz, pl,
p2,p3, nuevo);
else
{ p4 = pS->izq; rotación_dobie{raíz, p¡, p2, p3,
p4, nuevo):}
}
else { p3 = p2->izq;
if (nuevo < p3->infa) rotación _simple(raiz, pl,
p2, p3, nuevo):
else
{p4 = p3->der: rotación_dable(raiz.p¡.p2,p3,p4.
nueva);}
i

Q •
Ejercicios

i ( EJERCICIOS ) &
O
1. Realice la implementación de una rutina que sirva para verificar si
un ABB es un árbol AVL.

^ ^ \ 2. Realice la implementación de una rutina útil para modificar los fac-


^ \ \ tores de balance de los nodos que se encuentran en determinada ruta
\ \ de búsqueda, al hacer la inserción de un nuevo nodo en un árbol
\ \ AVL. La rutina recibirá como entrada el apuntador al nodo a partir
\ \ del cual se desea modificar los factores de balance y el apuntador al
\ \ nodo insertado.

3. Realice la implementación de un módulo que se encargue de reali-


/ / zar el movimiento de apuntadores, de acuerdo con la rotación simple
/ / y otro que efectúe el movimiento de apuntadores de acuerdo con la
/ / rotación doble. Se puede suponer que estos módulos recibirán el apun-
/ / tador al nodo pivote de la rotación y el apuntador al padre del nodo
y ^ / pivote.

,.--''' 4. En el árbol que se muestra a continuación los círculos representan


los nodos ya existentes en el árbol y los rectángulos indican algunos
posibles nodos hoja por insertar en el árbol.

• [fl
Arboles RUÍ

a) Coloque en cada rectángulo del esquema alguna de las siguientes


claves, según la acción que se tenga que realizar si ocurriera la in-
serción de esa hoja sobre el árbol original:

OK, si la inserción no provoca un desbalanceo


RSI, si la inserción provoca un desbalanceo y se requiere una
• rotación simple izquierda.
RSD, si la inserción provoca un desbalanceo y se requiere una
rotación simple derecha.
RDI, si la inserción provoca un desbalanceo y se requiere una
rotación doble izquierda.
RDD, si la inserción provoca un desbalanceo y se requiere una
rotación doble derecha. / /

b) Muestre por medio de esquemas los cambios en el árbol AVL origi-


nal (sin ninguno de los rectángulos) si se eliminara el nodo que tie¬ /
ne el .valor de 70. Si es necesario, emplee el método de sustitución
del "menor de los mayores".

5. Se sabe que, como mínimo, se requiere una secuencia de siete ele-


mentos para que al insertarse en un árbol AVL vacío produzcan una
rotación de cada uno de los cuatro tipos posibles (RSD, RSI. RDD,
RDI). A continuación se muestra un esquema con los cambios que
ocurren en el árbol AVL conforme se insertan los elementos.

O 0
Inserte Inserte Inserte

5
cuart0
primer segundo tercer elemento , T M
elemento elemento elemento quinto elemento

i
°o—"rijo ^ v >
Inserte Inserte
sexto elemento séptimo elemento

Q •
Ejercicios

a) Indique en el esquema los tipos de rotación que ocurren en cada


caso y el lugar en que se coloca el apuntador al nodo pivote.
b) Si la secuencia por insertar fueran los números 1 a 7, ¿en qué or­
den se deberán insertar esos números para que ocurran los ajustes
que se muestran en el esquema?

6. Dado el siguiente árbol AVL codificado (los símbolos en el árbol


tienen asociado un valor con el que se respeta la filosofía de orde­
namiento de un ABB):

\ & \ & \

/ t

/ / a) Muestre por medio de esquemas los cambios en el árbol si se da de


/ / baja el nodo raíz siguiendo:
>^ / • El método de sustitución del "menor de los mayores"
• El método de sustitución del "mayor de los menores"
,...."'*' b)Indique claramente en los esquemas qué tipos de rotaciones ocurren.

7. Observe el siguiente árbol AVL codificado (los símbolos en los nodos


representan un valor):

A continuación se muestra un conjunto de símbolos nuevos y se in­


dica la relación que guardan con los símbolos que ya se encuentran
en el árbol:

S, con un valor mayor al símbolo ? y menor al R

• 8
Arboles MJL

A, con un valor menor al valor asociado al símbolo %


+, con un valor mayor al símbolo R y menor al $
M, con un valor menor al símbolo + y mayor al R
&, con un valor mayor al símbolo M y menor al +

a) Si se inserta la siguiente secuencia de símbolos en el árbol origi-


• nal: A, S, +, & muestre con esquemas los cambios que ocurren
en el árbol, al hacer las inserciones, indicando los tipos de rota­
ción. ^"'
b) Muestre a través de un esquema el estado del árbol original, des­
pués de dar de baja el símbolo del nodo raíz utilizando: /'' ^

• El método de sustitución del "mayor de los menores"


• El método de sustitución del "menor de los mayores"
J /
** //
8. En los árboles AVL, el proceso de rotación doble recibe ese nombre
debido a que se efectúan implícitamente dos rotaciones simples.
Conteste, con base en lo anterior y en el siguiente árbol AVL (repre­
sentado en memoria estática): \
<
\■ \\
\

1 2 3 4 5 6 7 9 9 10

1 1 1 i 1 1 1 1 1 i — 1 '• ^^"-^
INFO 5 - 7 13 15 9 - 17 11 3

' RAÍZ = 4
IZQ 10 0 0 1 0 3 2 5 0 0
DISP = 7

D E F I 6 0 0 a 0 9 0 0 a O
L I L I l L I I I I

a) Sabiendo que si se inserta el elemento con valor 16 se provoca


una rotación doble derecha. Muestre cómo quedaría el árbol si se
simulara el proceso de rotación doble a partir de rotaciones sim­
ples (izquierda y/o derecha). Muestre los dos esquemas de las
rotaciones simples.
b) Mencione un elemento que al insertarse sobre el árbol original
provoque una rotación doble izquierda y muestre cómo quedaría
el árbol después de la inserción (también debe simular el proce­
so de rotación doble). Muestre los dos esquemas de las rotaciones
simples

8 •
Hutoeuauuación

í HUTOEURLUflCIÓN C\ -
i !
k _ _ _ _ _ . _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ . _ j

1. A continuación se muestra un árbol AVL representado en memoria


estática (los nodos disponibles se encadenan como pila a través del
"-*-., campo izquierdo):

~ ^ \ \ INFO IZQ DER

1
\ \ 5 0

\ 2 CONDORES 0 ' 0

\ 3 0 0

• 4 BORREGOS 6 2
5
I 3 ' 0~
6
/ ;'' ÁGULASBLANCAS 0 7
7
/ / AZTECAS 0 0

/ / RAÍZ = 4 DISPONIBLES = 1

Si se inserta ÁGUILAS REALES en este árbol, ¿cuál de los


siguientes enunciados es verdadero?

a) El valor del apuntador RAÍZ es 7.


b) Aztecas apunta por la derecha a ÁGUILAS REALES y por la
izquierda a ÁGUILAS BLANCAS. •
c) El elemento AZTECAS tiene como padre al elemento ÁGUILAS
REALES.
d) El valor del apuntador DISPONIBLES es 3.
e) La inserción de ÁGUILAS REALES no provoca desbalanceo.

2. Si se considera el árbol AVL original de la pregunta anterior, ¿qué


ocurre en el árbol cuando se da de baja al elemento CÓNDORES?

a) El valor del apuntador RAÍZ sigue siendo 4.


b) AZTECAS apunta, por la derecha a BORREGOS y por la izquierda
a ÁGUILAS BLANCAS.
c) El elemento AZTECAS tiene como padre al elemento ÁGUILAS
BLANCAS.

. 0
Hrboles RUL

d) El valor del apuntador DISPONIBLES es 1.


e) La eliminación de CÓNDORES no provoca desbalanceo.

Para las preguntas 3, 4 y 5 utilice el árbol AVL codificado que se


muestra a continuación:

3. ¿Cuál de las siguientes operaciones no provoca que en el árbol se


realicen rotaciones? \ \

a) Insertar un valor menor al del símbolo Y y mayor al símbolo - .


b) Eliminar el símbolo del nodo raíz.
c) Insertar un valor mayor al del símbolo $ y menor al símbolo M.
d) Eliminar el nodo con el símbolo L.
e) Eliminar el símbolo Y.

4. ¿Cuál de los siguientes elementos no provoca rotación cuando se


elimina del árbol (independientemente de la metodología de susti­
tución que se utilice)?
a) O b) / c) R d) W e) X

5. ¿Qué tipo de rotación ocurrirá al insertar un elemento que es mayor


a / y menor a L?

a)RSD b)RDD c) RSI


d) RDI e) No hay rotación

y •
Capítulo t-z
• ^H II
auros ^

OBJETIVOS

• Describir las caracteristicas de las estructuras de datos de tipo red


o grafos.
• Definir la terminologia de grafos: nodos, arcos, trayectoria,
trayectoria simple, ciclo, subgrafo.
• Identificar los diferentes tipos de grafo: dirigido, no-dirigido,
ponderado.
• Describir el diseño logico del TDA grafo, incluyendo las operaciones
de insertion y eliminatión de un nodo y de un arco.
• Explicar las diferentes formas de representanon para un grafo.
• Describir los recorridos de un grafo: anchura y profundidad.
• Explicar en que consiste el arbol del camino mas corto y arbol de
extension minima para un grafo, y la importancia de esta aplicacion.
• Describir el algoritmo de la cerradura transitiva para un grafo.

3
Grafos

¿Qué es un grafo?
El grafo es una red de elementos conectados entre sí. En la actualidad
se le considera una de las herramientas más utilizadas para modelar
problemas reales, ya que, generalmente, algunos componentes de estos
problemas interactúan. lo que fácilmente puede representarse en un
grafo.
El grafo podría explicarse como la red de carreteras de un país
(figura 13.1), que tiene un conjunto de ciudades conectadas a través
de caminos. No todas las poblaciones tienen la misma cantidad de vías,
y no necesariamente se puede llegar directamente de una ciudad a otra.

C /Piedras Negras >


V T ^
K ^ f \
^
ChiHuahua ■
' r^SZ '—N-.—. _ TV

(ciuSd'Dhregem ) ^ L - J , - - ^ "> \ ~^~~~^r A


V J \ J ^ J 1 ^ X _ _ UorxSovSí ' \

(Monterrey) V^^ " ^ ^ _ - J¡L„

l A\ J
Torreón ^V^ Uopi«rty

Figura 13.1. Forma en que se vería gráficamente esta aplicación.

El grafo se considera como la estructura más general dentro de la


clasificación tradicional de estructuras de datos, ya que es la generali­
zación de una estructura jerárquica, la cual, a su vez/resulta ser la
generalización de la estructura lineal. Cada elemento del grafo mantie­
ne una relación de N:M con respecto a los demás elementos.

Terminología utilizada en grafos

• Grafo: conjunto de nodos y arcos o de vértices y aristas.


• Nodo: elemento básico de información de un grafo.
• Arco: liga que une dos nodos del grafo.
• Nodos adyacentes: dos nodos son adyacentes si hay un arco que los
conecte.
• Camino: secuencia de nodos, en la que cada par de nodos son adya­
centes.
• Camino simple: es un camino en el que todos los nodos contenidos
son diferentes, con la posible excepción del primero y el último, que
podrían ser el mismo.
• Grafo no dirigido: los arcos en el grafo no tienen una dirección par­
ticular. es decir, son bidireccionales. Por ejemplo: si los nodos A y B
son adyacentes, es igual ir de A a B que de B a A.

g _ •
¿Cual es la especificación lógica del TDfi grafo?

• Grafo dirigido (digrafo): los arcos en el grafo tienen una dirección


asociada. El primer elemento del arco es el origen y el segundo es
considerado el destino. Por ejemplo: el arco A B es diferente del
arco B A.
• Grafo ponderado: cada arco del grafo tiene asociado un peso o va­
lor. Generalmente el peso está relacionado con costos, distancias y
similares.
• Ciclo: en un grafo dirigido, el ciclo es un camino donde el nodo de
inicio y el nodo de terminación son el mismo.
• Grafo conectado: un grafo no dirigido es conectado si hay un cami­
no de cualquier nodo hacia cualquier otro en el grafo.
• Digrafo fuertemente conectado: un grafo dirigido se considera
fuertemente conectado si hay un camino desde cualquier nodo hacia
cualquier otro.
• Digrafo débilmente conectado: un grafo dirigido se considera
débilmente conectado si su grafo no dirigido correspondiente está co­
nectado, pero no hay caminos para llegar de cualquier nodo hacia
cualquier otro.
• Grado de un grafo: es el máximo grado de sus nodos, donde éste se
define como la cantidad de arcos que inciden en ese nodo. En el caso
de digrafos se distingue entre el grado_entrada y el grado_salida. El
primero define la cantidad de arcos en los que el nodo es el destino
y el segundo es la cantidad de arcos donde es el origen.

¿Cuál es la especificación lógica del TDA grafo?

Si se considera que el grafo es la estructura de datos más general que


existe, al igual que en el resto de las estructuras, es posible generar la
especificación lógica que lo define como un TDA. Dicha especifi- #
cación, considerando sólo un subconjunto de las posibles operaciones
por aplicar, quedaría como:

Sg /" \
ELEMENTOS: un grafo consiste de nodos y arcos. En general, cada nodo
puede contener más de un valor, sin embargo, el caso par­
ticular es que almacene una única llave, de tal forma que
por ésta se identifique a cada elemento tipoinfo.
ESTRUCTURA: posee una estructura de red. Cada arco establece una relación
uno-a-uno entre dos nodos. Un par de nodos puede estar
conectado por un único arco, pero cada nodo puede conec­
tarse a cualquier conjunto de nodos.
V '. /

• Q
Grafos

( \
CREARGRAFO
ENTRADA: Ninguna
SALIDA: El grafo GRAFO inicializado.
PRECONDICIÓN: Ninguna.
POSTCONDICIÓN: El grafo queda inicializado.

BUSCARNODO
ENTRADA: El grafo GRAFO y el elemento por buscar NODO
SALIDA: VERDADERO si el elemento NODO está en el grafo
GRAFO y un apuntador P almacena la posición de ese
elemento en el grafo; FALSO en caso contrario.
PRECONDICIÓN: El grafo GRAFO existe.
POSTCONDICIÓN: Ninguna.

BUSCARARCO
ENTRADA: El grafo GRAFO y el arco por buscar (NODOl, NOD02).
SALIDA: VERDADERO si el arco formado por NODOl,
NOD02 existe en el grafo GRAFO; FALSO si no.
PRECONDICIÓN: El grafo GRAFO existe.
POSTCONDICIÓN: Ninguna.

INSERTARNODO
ENTRADA: El grafo GRAFO y el elemento por insertar NODO
NUEVO.
SALIDA: El grafo GRAFO contiene un elemento más.
PRECONDICIÓN: El grafo GRAFO existe y el elemento NODO NUEVO
no existe en el grafo.
POSTCONDICIÓN: El grafo GRAFO contiene un elemento más.

INSERTARARCO
ENTRADA: El grafo GRAFO y el arco por insertar (NODOl,
NOD02)
SALIDA: El grafo GRAFO contiene un arco más.
PRECONDICIÓN: El grafo GRAFO existe, los elementos NODOl,
NOD02 existen en el grafo, y el arco definido por
NODOl, NOD02 no existe en el grafo.
POSTCONDICIÓN: El grafo GRAFO contiene un arco más.

BORRARARCO
ENTRADA: El grafo GRAFO y el arco por borrar (NODOl,
NOD02).
SALIDA: El grafo GRAFO tiene un arco menos.
PRECONDICIÓN: El grafo GRAFO existe y el arco definido por NODO1,
NOD02 existe en el grafo.
POSTCONDICIÓN: El grafo GRAFO tiene un arco menos.

V )

% *
¿Cómo se puede representar el TDR Grafo?

í >
BORRAR NODO
ENTRADA: El grafo GRAFO y el elemento a borrar NODOVIEJO.
SALIDA: El grafo GRAFO tiene un elemento menos.
PRECONDICIÓN: El grafo GRAFO existe y el elemento NODOVIEJO existe
en el grafo y no tiene conexiones (arcos definidos).
POSTCONDICIÓN: El grafo GRAFO tiene un elemento menos.
V _ ) !
¿Cómo se puede representar el TDA Grafo?
Hay diferentes formas de representar un grafo; entre las más comunes
están:

Matriz de adyacencias
Una de las representaciones más sencillas de un grafo es la denomi­
nada matriz de adyacencias, que consiste básicamente en un arre­
glo bidimensional de tamaño n X n, donde n es la máxima cantidad
de nodos en el grafo. Cada casilla de la matriz será llenada con ver­
dadero o falso, según exista o no un arco que conecte los dos nodos
involucrados (figura 13.2). Para el caso de los grafos no dirigidos, la
matriz será simétrica; sin embargo, esto no ocurre en los digrafos,
donde se debe considerar la dirección explícita de cada uno de los
arcos.
Para el caso de los grafos ponderados, la matriz puede llenarse con
el peso asociado a cada uno de los arcos.
La principal ventaja de este tipo de representación es su simplici­
dad, ya que resulta relativamente fácil realizar cada una de las opera­
ciones descritas para un grafo. Desafortunadamente, esta representa­
ción tiene una fuerte desventaja: está limitada a un número máximo de
nodos en el grafo, lo que provoca que, en cierto momento, sea imposi­
ble almacenar más información o que, por el contrario, si se supone un
número máximo muy grande, se desperdicie una gran cantidad de me­
moria. Para el caso de un grafo no dirigido el desperdicio sería mayor.
ya que, al ser simétrica la matriz, la información está duplicada.

A B C D E '
Q¡L jQ. A F F V V I V "
\^ B F F V V F
pxrfV_— -© C V V F~ F F
J * / D V V F F V
E V F F V F

V )

. Figura 13.2. Representación del grafo en matriz de adyacencias.

o
0^
Grafos

Lista de adyacencias
En esta representación, generalmente dinámica, se trata de evitar el
problema de restricción de espacio. Básicamente consiste en definir
una lista encadenada de nodos y, para cada uno de ellos, encadenar una
lista con los nodos adyacentes. La idea es similar a la de la matriz; sin
embargo, no define las casillas de los nodos que no tienen un arco
común, ahorrando un poco de espacio.
En este tipo de representación se desperdicia mucho espacio cuan­
do se almacena un grafo no dirigido ya que, al igual que en la matriz
de adyacencias, la información de cada arco está duplicada; pues hay
un arco de un nodo a otro y viceversa. Sin embargo, esta representa­
ción es la más utilizada cuando se tiene que almacenar un grafo disper­
so (sparse graph), donde la cantidad de arcos es mucho menor que el
cuadrado del número de nodos (existen pocas conexiones).
Este tipo de representación se muestra en la figura 13.3:

f NODOS A

B B J-*-HTT~H o l/|

T
"D]V| A I W B I -W E 1/1

Figura 13.3. Representación del grafo en lista de adyacencias.

Lista de arcos
Una tercera alternativa de representación, usada preferentemente cuan­
do se tienen digrafos es la lista de arcos. En esta representación se
mantiene una lista de nodos (al igual que en la lista de adyacencias)
pero, en lugar de almacenar los nodos vecinos, se mantiene una lista de
arcos para cada nodo, en la que se tiene la información necesaria para
determinar cuál nodo es el primer elemento cid arco (origen) y cuál es
el seoundn MestinrO Fs una rpnrpspnt'irión Hnndp s;p minimiza el dPS-
oerdfcio i ^ o ^ ^ ^ ^ d o d e c ^ o S oara su ma

ycaaa^
figura iT*

Q •
¿Cómo se puede recorrer un grafo?

USTA DE NODOS LISTA DE ARCOS ^ '


Apuntador al siguiente
i ^ A p u n t a d o r a l siguiente , ^arco donde NODO
Klnrvl — arco donde NODO | -f es ori9en
" ^ v es origen [_N0DO1-N0D02 "

~ \ .Apuntador al siguiente
* Apuntador al siguiente * arc D d m d e N 0 D a
¥ arco donde NODO DC H „ c r in r l
Apuntador al es desuno
V siguiente nodo J

Figura 13.4.

A continuación, figura 13.5 se muestra un grafo dirigido y su corres­


pondiente representación en lista de arcos:

NOOOS

A
r —-. A A

BüáaZT' í
Figura 1 3 . 5 . Representación del grafo en lista de arcos.

¿Cómo se puede recorrer un grafo?

Al igual que en los árboles, los grafos requieren algoritmos especiali­


zados para desplegar la información contenida en ellos. Básicamente
se manejan dos algoritmos que sirven para recorrer un grafo, y lo ha­
cen siguiendo un orden diferente, dependiendo de la representación
seleccionada para el grafo. Dado el grado de complejidad de la represen­
tación de grafos, estos algortimos se apoyan en estructuras de datos au­
xiliares; en este caso, el primeros de ellos, denominado primero en
anchura (breadth first figura 13.6), utiliza una fila; y el segundo, lla­
mado primero en profundidad (depth first figura 13.7), emplea una pila.
El algoritmo de recorrido de grafos breadth first es el siguiente:

. @
Gratos

Primero en anchura (breadth first)


- Inicializar todos los nodos del grafo en el estado: en espera.
- Para cada nodo del grafo:
- Si el estado es en espera, entonces:
- Insertar el nodo en una fila (cambiar su estado a listo).
- Mientras la fila de nodos listos no esté vacía:
- Sacar un nodo de la fila y procesarlo.
- Cambiar su estado a procesado.
- Meter en la fila todos los nodos vecinos del nodo
recién sacado cuyo estado sea en espera. Cambiar el
estado de estos nodos a listo.

Nota: el orden del recorrido de los nodos vecinos depende de cómo es­
té representado el grafo.

^ Ejemplo

/ S i se supone que los elementos del grafo están almacenados en orden 1


alfabético, el recorrido primero en anchura, partiendo del nodo A, en
el siguiente grafo sería:
EJEMPLO DE BREADTH FIRST

ABEHCGIJDF
Figura 13.6.
v J

g •
¿Cuáles son algunas aplicaciones importantes de gratos?

El algoritmo de recorrido de grafos depthfirst es el siguiente:

Primero en profundidad (depthfirst)


- Inicializar todos los nodos del grafo en el estado en espera.
- Para cada nodo del grafo:
- Si el estado es en espera, entonces:
- Insertar el nodo en una pila (cambiar su estado a listo).
- Mientras la pila de nodos listos no esté vacía:
- Sacar un nodo de la pila y procesarlo.
- Cambiar su estado a procesado.
- Meter en la pila todos los nodos vecinos del nodo
recién sacado cuyo estado sea en espera o listo.
Cambiar el estado de estos nodos a listo.
* Si el nodo ya existe en la pila (con estado de listo) se deja sólo el que
llegó al último (el más reciente).

^t Ejemplo

fs\ se supone que los elementos del grafo están almacenados en order?\
alfabético, el recorrido primero en profundidad, partiendo del nodo A
sería:
EJEMPLO DE DEPTH FIRST

- iú Figura 13.7 A B C D E G J F H I Salida

¿Cuáles son algunas aplicaciones importantes de


grafos?
,

Particularmente, los grafos ponderados tienen diversas aplicaciones,


entre las más comunes están el árbol de extensión mínima y el árbol
de camino más corto; ambas son de suma importancia en aplicaciones
donde el factor económico o la distancia son primordiales, ya que ayu­
dan a obtener el costo mínimo de conexión para un grafo y la distancia #fe

• — 6
Gratos

más corta (o el costo más pequeño) de un nodo del grafo hacia el res­
to de los nodos. Estas dos aplicaciones son vitales en actividades como
la instalación de redes computacionales, optimización de rutas y algo­
ritmos de transporte.
A continuación se presentan los algoritmos correspondientes a estas
dos aplicaciones:

Árbol de extensión mínima [AEM] (minimal spanning tree)


El AEM de un grafo x es un subgrafo conectado que contiene todos los
nodos del grafo x y los arcos cuya suma de ponderaciones es la míni­
ma posible (figura 13.8).

Algoritmo para crear el AEM de un grafo con el algoritmo de E.W.


Dijkstra:
• Crear el grafo vacío AEM.
• Insertar cualquier nodo vértice .v del grafo en el AEM.
• Insertar todos los nodos vértices vecinos del nodo vértice .v, en
una cola priorizada. El valor del arco que une al nodo vértice x
con su vecino es su prioridad; la mayor prioridad es el valor menor.
• Mientras la cola priorizada no esté vacía y no se hayan agregado
todos los nodos al AEM:
- Sacar un nodo vértice de la cola priorizada.
- Si el nodo vértice no está en el AEM, agregarlo con su co­
rrespondiente arco y meter todos sus vecinos, que no estén
en el AEM, en la cola priorizada.

^ Ejemplo

fs\ se supone el siguiente grafo, el árbol de extensión mínima que le \


corresponde, independientemente del nodo que se elija inicialmente,
sería:
EJEMPLO A.E.M

2 fiJ 2
A B EM A B

9
B
« * c B

D E D E
7

7 0 D c E F/ D/ D Colaphorizada
fe 12 10 8 9 3 * - »

^ ^ l Figura 13.8. J

y •
!

Árbol de extensión mínima con el algoritmo de Kruskal


En 1956, J. B. Kruskal ideó un algoritmo para encontrar el árbol de ex­
tensión mínima para un grafo conectado. En esencia, el algoritmo es si­
milar al mostrado anteriormente, pero trabaja con base en los arcos
existentes en el grafo. Para ir conformando el árbol, se apoya en una
cola priorizada que le permite ir eligiendo los arcos de menor peso.

Algoritmo de Kruskal para crear el AEM de un grafo:


• Crear un árbol vacío AEM.
• Crear una cola priorizada vacía.
• Encontrar el arco (i, j) que tenga el menor peso dentro del grafo.
• Insertar ese arco, junto con los nodos i yj en el árbol AEM.
• Para cualquier vértice k distinto de y:
• Si hay un arco (i, k), insertarlo en la cola priorizada.
• Para cualquier vértice K distinto de í:
• Si existe un arco (/, k), insertarlo en la cola priorizada.
• Mientras la cantidad de nodos en el AEM sea menor que la canti­
dad de nodos vértices del grafo:
• Sacar de la cola priorizada el arco (a, b) con el menor peso.
• Si el vértice b no existe en el AEM.
• Insertar el vértice by el arco (a, b) en el AEM.
• Para cada vértice e, de los arcos conformados por
(fr, c)
• Si el vértice c, aún no existe en el AEM:
• Meter el arco ií>, c) en la cola priorizada.

Es fácil observar que el algoritmo es similar al mostrado anterior­


mente; pero se basa en el uso de arcos.

Árbol del camino más corto a partir de un nodo [ACMC]


El ACMC (figura 13.9) a partir del nodo x de un grafo w, es un subgrafo
conectado que contiene todos los nodos vértices del grafo w y los arcos
necesarios para minimizar la suma de las ponderaciones, de los arcos en
el camino del nodox a cualquier otro nodo vértice del grafo (el nodo x es
la raíz del árbol).
Para obtener el ACMC de un grafo, a partir de un nodo, se utiliza el
mismo algoritmo de Dijkstra que obtiene el AEM, pero las prioridades
se forman acumulando las ponderaciones de los arcos, desde el nodo
inicial hasta el nodo analizado.

. o
brafos

U Ejemplo

/"" EJEMPLO CMC "\

— <a> ®———#
K
/*£_ ^a D — (^ -*- CMC
7
Cola Priorizada

Etí EJS EBÍ ECA ECB ECD EDA EE¡¿


á 7 9 15 11 9 17 i /

V. Figura 13.9. J

Cerradura transitiva en un digrafo

Otra aplicación muy importante de los grafos consiste en determinar si


para dos nodos cualesquiera de un digrafo hay una trayectoria que los
una (figura 13.10). Esta operación es muy útil en problemas de ruteo y
conectividad.
El cálculo de la cerradura transitiva se basa en el siguiente concepto:
Si hay una trayectoria entre los nodos n/-ny, y una trayectoria entre
los nodos ny-nk, entonces existe una trayectoria desde n i hasta n fe.
Existen varios algoritmos que permiten calcular la cerradura. Sin
embargo, uno de los más comunes es el de Warshall, que trabaja con la
representación del grafo en una matriz de adyacencias. Una versión de
este alogaritmo es:

Si se supone la existencia de un grafo con n nodos vértices:


• Para cada una de las columnas de la matriz 0')
• Para cada uno de sus renglones (I)
• Si la casilla A\j, i] es verdadera (hay conexión entre el
nodo i y el nodo» entonces:
• Para cada una de las columnas de la matriz den­
tro del renglón i
• Si la casilla AL/, k] es verdadera (hay cone­
xión entre el nodo i y el nodo k) entonces:
• La casilla A\j, k] es verdadera (exis­
te una trayectoria desdey hasta k).

6 •
Ejercicios

<|| Ejemplo

^Ejemplo de cerradura 0 *•& '


transitiva i

<L I
Matriz original Cerradura transitiva
| 1 2 3 4 1 2 3 4

1 0 1 0 0 1 1 1 1 1
2 0 0 0 1 2 1 1 1 1
3 1 D 0 0 3 1 1 1 1
4 0 0 1 G 4 1 1 1 1

\^ Figura 13.10. J

i ( EJERCICIOS ") & ¡


> x
u
1. ¿Cuál sería la representación del siguiente grafo por medio de estos
tres elementos: una matriz de adyacencias, una lista de adyacencias
y una lista de arcos?

EM
2. Trace un grafo dirigido que corresponda a la siguiente relación: x es­
tá relacionada con y si x-y es divisible entre 3 (considere que x y y
son los enteros del 1 hasta el 12)) Para el grafo obtenido:

a) Indique si el grafo contiene ciclos y cuáles son.


b) Muestre todas las trayectorias simples posibles de 1 a 12.

3. Suponga que G es un grafo no dirigido con m nodos v„ v,, .... vm y


n arcos ar a2, .... an . La matriz de incidencias de G es la matriz de
m x fl, donde: A

• 0
Gratos

• el elemento (ij) de la matriz = 1 ss el nodo v, perrenece al arco a-


• el elemento (/, j) de la matriz = 0 si el nodo vt no pertenece al
arco a i
Encuentre la matriz de incidencias para el grafo del problema 1.

4. Otra manera de representar un grafo en memoria consiste en mane­


jar la lista de nodos vértice y la lista de adyacencias en memoria
estática (en un arreglo). Esta forma es útil cuando el lenguaje en que
se desea implementar la estructura del grafo no contiene las opcio­
nes de memoria dinámica. La representación consiste básicamente
en tener dos arreglos:
• Un arreglo para la lista de nodos vértice, donde cada elemento del
arreglo contendrá la información del nodo vértice, un apuntador al
siguiente nodo vértice en la lista y un apuntador al primer nodo en
la lista de adyacencias.
• Un arreglo para las listas de adyacencias, donde cada elemento del
arreglo contendrá un apuntador a la posición en la lista de nodos
vértice, donde está el nodo vértice terminal del arco, y un apunta­
dor al siguiente nodo de la lista de adyacencias.
Para manejar esta estructura, además, se requiere un apuntador
general al grafo (GRAFO) y dos apuntadores generales a la lista de
nodos disponibles en cada uno de los arreglos (DISPI y DISP2). La
lista de nodos disponibles se maneja como pila. Sobre la lista de
nodos vértices y las listas de adyacencias se insertará como pila
y para eliminar se maneja el elemento deseado.
a) A continuación se muestran los datos'de un grafo (G) que está
guardado en memoria bajo esta representación. Dibuje el grafo
correspondiente.

1 2 3 4 5 6 7 8
INFO A C E D E GRAFO = 6

SG
I ^J^J^X^l-I- D!SP1=5 LISTADENODOS
ADY 6 1 ID" T 9

1 2 3 4 5 6 7 8 9 10
OEST r g a 1 "4 | 3 | 3 6 | 3
SE ~~5~ ~7 j j o " D [ D i O ~0~ ~4~ " a " DISP2 = 3 LISTA DE ADYACENCIAS
■ I 1 ^ — 1 — ^ ■ 1 1 I p I 1 , J. , 1 ,1 I

b) Encuentre los cambios que ocurren en la representación de este


mismo grafo si se elimina el arco c-e y se inserta el arco d-e.

0- .
flutoeualuación

c) Encuentre los cambios que ocurren en la representación del gra-


fo G mostrado en la figura 13.12 si se inserta el nodo/y los arcos
e-fyf-d.
d) Encuentre los cambios que ocurren en la representación del gra­
fo G mostrado en la figura 13.12 si se elimina el nodo b.
5. Obtenga los recorridos primero en anchura (bf) y primero en pro­
fundidad idf) de cada uno de los siguientes grafos, a partir del nodo A:
Suponga que se guardan en orden alfabético.

a) b)

c) d)

6. Para cada uno de los grafos del problema anterior, encuentre:


a) El árbol de extensión mínima.
b) El árbol del camino más corto para el nodo £ , para C, etcétera.

7. Obtenga la cerradura transitiva para la matriz de adyacencias corres­


pondiente al grafodel problema 1.

; AUTOEUflLUnCIÓNC\ -
i !
b - - - - - - - - - - - _ _ - _ - _ - _ _ _ _ _ _ _ - _ — _ j

1. Para un grafo no dirigido que tiene cinco nodos vértice, ¿cuántos


arcos habrá en el grafo si todos los nodos vértices se relacionan unos
con otros?

a) 10 b)20 c)25 d) 9 e) 15

Para las preguntas 2 y 3, tome el grafo que se muestra, suponiendo que


la información se almacena en orden alfabético.

• Q
Graíos

2. ¿Cuál de las siguientes opciones representa un recorrido válido


para el grafo, partiendo del nodo H?

a)HGJDIEFCAB b)HCABFEGIDJ

c)HCFGAIDEBJ d)HCFAIGDJBE

e) Ninguna de las anteriores

3. ¿Cuál de los siguientes valores representa el costo mínimo de cone­


xión para el grafo?

a) 22 b) 27

c) 31 d)33

e) Ninguno de los anteriores.

4. ¿Cuál de los árboles de la página siguiente representa el árbol del


camino más corto para A?

1 —7 DV
. 1 2

C - 1 - E

0 •
flutoeualuación

a). b).

o o o a

^1\|/* Ni—i \
\ \
\ *
6 ó 6— *
\ i

e). Ninguno de los anteriores.

/ j
/ i
/ t
J i
/ i

• 0
OBJETIVOS
• Definir que es un heap.
• Entender la forma en que se puede representar un heap en memoria.
• Describir el diseno logico del TDA heap, incluyendo las operacio­
nes de creation de un heap, insertion y elimination de elementos
del heap.
• Comprender las aplicaciones de un heap utilizado como cola priori-
zada.
• Describir el algoritmo de construction de un heap a partir de una lis­
ta de elementos.

Q
Heaps

¿Qué es un árbol heap?


Puede considerse que es un árbol binario con ciertas restricciones, aun­
que tiene ventajas considerables en su implementación.
Básicamente, un heap es un árbol binario casi completo. Se le con­
sidera casi completo ya que está lleno en todos sus niveles, excepto,
probablemente, el último que se va completando de izquierda a dere­
cha. La diferencia entre un árbol binario y un heap es que en el segun­
do cada nodo tiene un valor menor o igual (o bien mayor o igual) que
el valor asociado a cada uno de sus hijos, lo que se conoce como condi­
ción del heap. En la figura 12.1 se muestra un ejemplo de árbol heap:

( A "I
5 8

4 r | 6

V J
Figura 12.1

Las aplicaciones más comunes de esta estructura de datos se rela­


cionan con el manejo de colas priorizadas y el ordenamiento de infor­
mación.

¿Cómo diseñar el TDA que corresponde al árbol


heap?
En el diseño del TDA para un heap se debe considerar que los elemen­
tos que se almacenarán deben poseer la propiedad de ordenamiento, es
decir, tener una relación de mayor que y menor que. Además, se debe
considerar que la estructura del heap es una jerarquía en la que se res­
petan sus propias condiciones. De acuerdo con esto, se puede generar
la siguiente especificación lógica del TDA heap:

*íp ELEMENTOS: los elementos de un árbol heap son nodos. Cada uno contiene un
dato (simple o estructurado tipoinfo) único en el árbol.

ESTRUCTURA: un árbol heap posee una estructura jerárquica (a excepción del ár­
bol vacío). Cada nodo contiene un elemento de un conjunto orde­
nado de valores; los nodos forman un árbol binario completo, de tal
forma que cada padre contiene un elemento de mayor prioridad que
^

Q •
¿Como se puede representar el TDfl heap?

sus dos hijos (la prioridad se establece con relación a los va­
lores almacenados, es decir, a menor valor, mayor prioridad, o a
mayor valor, menor prioridad). La relación de la prioridad se
puede establecer como mayor o mayor-igual.

OPERACIONES

CREARHEAP
ENTRADA: Ninguna
SALIDA: El árbol heap creado.
PRECONDICIÓN: Ninguna
POSTCONDICIÓN: El árbol heap creado sin elementos.

CONVIERTEHEAP
ENTRADA: Una lista de N elementos.
SALIDA: El árbol heap de N elementos creado con base en la
lista dada.
PRECONDICIÓN: Ninguna
POSTCONDICIÓN: El árbol heap de N elementos creado.

INSERTAR
ENTRADA: Árbol heap sobre el que se realizará la inserción y el
elemento a insertar nuevo.
SALIDA: El árbol heap contiene un nuevo elemento nuevo.
PRECONDICIÓN: El árbol heap existe.
POSTCONDICIÓN: El árbol heap tiene un nuevo elemento colocado de
acuerdo con su prioridad.

BORRAR
ENTRADA: Árbol heap sobre el que se realizará la baja.
SALIDA: El árbol heap condene un elemento menos (se eliminó
el elemento de mayor prioridad la raíz del heap).
PRECONDICIÓN: El árbol heap existe.
POSTCONDICIÓN: El árbol heap tiene un elemento menos.

V )
¿Cómo se puede representar el TDA heap?

Considerando las características expuestas sobre los árboles heap


(como que se le considera un árbol binario casi completo), su represen­
tación más tradicional es un arreglo de elementos, es decir, esta estruc­
tura no necesita representarse como un árbol, ya que fácilmente puede
almacenarse en un arreglo (figura 12.2).

• 0
Heaps

í 1 2 3 4 5 6 7 B 9 10
rr- P T 1—■ r T T 1 j r———

V - ■ - ■' ■■ — 1 ' —' ' "" — ?L ■■ ' J

Figura 12.2.

Si se considera que el primer elemento del arreglo (heap[l]) es la raíz.


el segundo almacena al hijo izquierdo de la raíz, el tercero al hijo de­
recho y, sabiendo que es un árbol completo hasta el penúltimo nivel y
que el último se llena de izquierda a derecha, se puede llegar a concluir
que para el /-ésimo elemento del heap (heap[/]) sus hijos izquierdo y
derecho, si existen, estarán ubicados en las posiciones 2*/ y 2*/ + I.
respectivamente.
Además, las condiciones del heap, con respecto al orden de los va­
lores, se satisfacen de acuerdo con:
heap[í] < heap[2*/J y heap[i'l < heap[2*/ + l],
o bien con relación de >, dependiendo de cómo se manejarán las prio­
ridades en el heap.
En la figura 12.3 se muestra un ejemplo donde un árbol heap es
representado con un arreglo de valores donde se respetan las condi­
ciones del heap:

C ^ ^
/ N. »> 1 2 3 4 5 E 7 8 9 10

JK JK l*H 8 l 4 l 1 l a H a l I H
l!
Figura 12.3.
)
Con lo anterior se pudiera pensar que cualquier arreglo de valores se
puede transformar en un heap para aprovechar todas las facilidades que
ofrece este tipo de estructura de datos.

¿Cómo generar un árbol heap a partir de un arreglo


de elementos?
El proceso de creación de un árbol heap a partir de un arreglo arbitra­
rio de elementos consiste en los siguientes pasos:

1. Todos los elementos del arreglo que corresponderían a las hojas del
árbol heap cumplen con sus propias condiciones. Dado que es un
árbol casi completo, entonces se cumple que la cantidad de elemen­
tos que son hojas del heap es N/2 o N/2 + 1, dependiendo de si existe
• una cantidad par o impar de elementos en el arreglo.
¿Como generar un árbol heap a partir de un arreglo de elementos?

2. De aquí en adelante, se irá agregando al heap un nuevo elemento ca­


da vez, partiendo del último nodo del árbol que sí tiene hijos.
Si se considera que un nodo tiene hijos, entonces, para que sea
un heap, necesariamente debe cumplir con las condiciones conoci­
das: el valor del nodo será siempre menor que el valor almacenado
en sus hijos; si no es así, entonces debe intercambiarse el valor del
nodo padre con el valor más pequeño de sus hijos. Se repiten estos
intercambios a lo largo de toda la descendencia de ese nodo, hasta
que se logren reacomodar hacia abajo todos los nodos descendien­
tes del nodo que se está insertando en el heap.
3. Se repite el paso anterior hasta que se incluyan todos los elementos
del arreglo dentro del heap. En ese momento, se considera que el
arreglo de valores ya se ha convertido en un árbol heap.

Es importante aclarar que, para un mismo conjunto de elementos, se


pueden obtener diferentes árboles heap, dependiendo del ordenamien­
to que inicialmente tuvieran los elementos en el arreglo.
A continuación se muestran los algoritmos relacionados con el pro­
ceso de creación de un árbol heap.
Algoritmo general para construir un heap dada una lista L

Sea n la cantidad de elementos:


1. Apuntador = n div 2 (sólo se analizan nodos que no son hojas)
2. Mientras apuntador > = 1:
- Reacomodar dato señalado por apuntador
(Rutina acomoda_abajo)
- Apuntador = apuntador -1
Algoritmo para la rutina acomoda_abajo

Sea AP el apuntador al elemento por acomodar:

l.Aux=AP
2. Hijos = 2*AP
(hijos señala al hijo izquierdo del nodo apuntado por AP)
3. Mientras haya hijos de aux (hijos <= maxlista) y alguno de ellos sea
mayor:
- Encontrar el hijo mayor de aux (hijomay)
- Si hijomay > aux
-> intercambiar valores de aux e hijomay
aux = hijomay
hijos = 2*aux
si no
salir del ciclo. Jfe

• Q
Heaps

fS Ejemplo
r N
>
A continuación se muestra, paso a paso, la construcción de un árbol
heap a partir de una secuencia de valores que no necesariamente repre­
sentan un heap.
1. Localice, dentro del arreglo (figura 12.4), los elementos que esta­
rían como hojas del árbol heap, que se ubican en la segunda mitad
del arreglo.

5 jg> ^ S 5 17 19 21 28 ... I
@ ^ 0 1 2 3 4 5 6 ...

Figura 12.4.

2. Tome cada uno de los elementos de la primera mitad del arreglo y


compárelo con los que representen a sus hijos en el arreglo. Si
hay necesidad de cambiarlos, se intercambian al llegar al último
nodo de su descendencia (figura 12.5).

X^\ \~ ~~^ heap


5 17
i á I 5 I 17 ' 19 [ai 128 '... '
\ r L_J L , i _ 1 . r . ji,
W © @ 1 2 3 4 5 6 ...

t
hea
_ ^ P heap

8 8 5 |2ET|T9~[BI]77 | T T j 8 21 28 19 5 17 | . . . "

1 2 3 4 5 6 ... 1 3 3 4 5 6 . . .

í í
heap

I y
SB 21 8 19 5 17

1 2 3 4 5 6 ...
A
I
Figura 12.5.

o •
¿Cómo se utiliza una estructura heap en una cola priorizada?

f heap A

w/^m. m¿ I S a l 21 117 19 5 8 I ...


1 2 3 4 5 6 ...
f
Figura 12.5. (continúa)

¿Cuáles son las aplicaciones más importantes para


la estructura de datos heap?

Un árbol heap es una de las estructuras de datos más apropiadas para


representar el concepto de colas priorizadas.
Básicamente, una cola priorizada es una estructura de datos donde
cada uno de los elementos tiene asociada una prioridad y el orden de sa­
lida de éstos depende de su prioridad, es decir, a mayor prioridad más
rápidamente saldrá de la estructura y sólo en el caso de tener dos o
más elementos con la misma prioridad se considerará que el primero
en salir debe ser el elemento que llegó nrimero (como en una fda).

¿Cómo se utiliza una estructura heap en una cola


priorizada?

Si se considera que cada uno de los elementos en un heap tiene asocia­


da una prioridad que depende de su valor (a menor valor, mayor prio­
ridad), entonces el elemento de mayor prioridad siempre estará en la
raíz del árbol y los elementos que le siguen en estas características
estarán cerca de él.
En este caso, cuando se desee sacar al elemento de mayor prioridad,
bastará con dar de baja la raíz del heap y la manera más sencilla de ha­
cerlo es eliminarla y sustituirla con la última hoja (la que se encuentra
más a la derecha en el último nivel). Una vez hecho, se deberá aco­
modar hacia abajo este sustituto, para que ahora la raíz del árbol sea
el elemento con la siguiente mayor prioridad. Este reacomodo corres­
ponde al último paso del proceso de creación de un heap (donde ya
sólo falta acomodar a la raíz).
Cuando se desee insertar un nuevo elemento en la cola priorizada,
lo más sencillo será agregarlo como la última hoja del árbol, es decir,

• Q
Heaps

en la primera posición libre del último nivel. En seguida, ese elemen­


to se deberá acomodar arriba, es decir, comparar con su padre y, si tie­
ne mayor prioridad, se realizará un intercambio, lo que se hará hasta
que se encuentre con un padre que tenga mayor prioridad que él. De
esta forma se asegura que el nuevo elemento quedará en la posición
que le corresponde, de acuerdo con la prioridad que tiene y la priori­
dad de los elementos en esa rama ascendente del árbol.

U Ejemplo

f
A continuación se muestra un ejemplo de cómo eliminar el elemento^
de mayor prioridad en el heap (su raíz) y cómo añadirle nuevos ele­
mentos, de forma que se conserve como una cola priorizada (figuras
12.6, 12.7 y 12.8).

Eliminación de la raíz
I"28I 21 I 17 I loT~5~] 8 I ...
i 1 1 1 1 1 i 1
ES 1 2 3 4 5 6.,.

0 5 8
fiel 21 I 17 I 1 9 | 5 I 8 I ... I
I I 1 I I !—_!
1 2 3 4 5 B ...
8 21 17 19 5 ^y¿ ...
1 2 3 4 5 6 ...

8 l 21 17 T19 T 5 T ...
1 9 3 4 5...

8 21 | 17 19 5 I ...
1 1 1—_J—_! 1
1 2 3 4 5 ...

211 8 l 17 p ü I 5 I I ...
1 2 3 4 5 ...

21| 19 I 7 l 8 I 5 I I ...
l i l i l í

1 2 3 4 5 . . .

Figura 12.6. ,
V y

o •
Ejemplo

( ^

Inserción de un nuevo elemento (suponga que es el árbol heap original).

J0(- %& |aB|ai|17|iB|5 | 8 J 7 7 |


1 2 3 4 5 6 ...
Figura 12.7

Se inserta el elemento con valor 20 al heap.

* " %
I
/ ° \ / \ ~2B~ 21 [ 17 19 5 a so I... I
" 1 2 3 4 5 6 7 B ...

28 i 21 117 I 19 I 5 I 8 I 20 I 1777
I I I L_l I I I ¡'
1 2 3 4 5 6 7 8 ...

28 21 20 19 5 8 17

1 2 3 4 5 6 7 8 ...

28 21 20 19 5 8 17 7T1

1 2 3 4 5 6 7 8 ...

Figura 12.8.

V )

• fj
Heaps

¿Cómo se utiliza un heap para ordenar un arreglo de valores


en forma ascendente o descendente?

Otra de las aplicaciones de los árboles heap es el ordenamiento de un con-


junto de valores, aplicación que recibe el nombre de heapsort y se basa
en las operaciones definidas para el TDA heap (ejemplo y figura 12.9).
El algoritmo de heapsort construye inicialmente un heap para el
conjunto de valores por ordenar. Posteriormente, inicia un proceso re-
petitivo en el que intercambia el valor de la raíz (elemento menor) con
el valor de la última hoja del heap, disminuye su tamaño y lo recons-
truye, reacomodando hacia abajo al elemento que quedó temporalmen-
te como la raíz del árbol.
Este proceso se repite hasta que sólo queda un elemento en el heap
y al final del arreglo ya están ordenados los demás elementos del con-
junto original.
A pesar de no ser un algoritmo muy popular, definitivamente es uno
de los métodos de ordenamiento más eficientes que existen.

Ejemplo

JN
A continuación se muestra un ejemplo del comportamiento del algorit-
mo de ordenamiento heapsort.
8 5 4 3 9 6 2

8 5 4 3 9 6 2

8 5 6 3 9 4 2

8 9 6 3 5 4 2

8 9 6 3 5 4 2

HEAP 9 8 6 3 5 4 2

2 8 6 3 5 4 I 9
HEAP 8 5 6 3 2 4 9

4 5 6 3 2

HEAP 6 5 4 3 2 8 9

Figu ra 12.9.
J
Ejercicios

2 5 4 3 6 8 9|

HEAP 5 3 4 2 í 6 '8 9
2 3 4 [~5 6 8~ 9

HEAP 4 3 2 I 5 6 ~8 9

2 3 I 4 ~~5~ 6 8~ 9
HEAP 2 3 r^ 5 ¡j 8 9
' —

2 3 I 4 5 6 ~~8 9
-
HEAP 3 2 [~~4 5 6 8 9

2 |3 4 ~5 B - "8 9

2 j3 4 5 6 8 ^9

Termina [s""-^ 4 5 B 8 9~

, Figura 12.9. (continúa) j

( EJERCICIOS \ ^

o
1. Se tiene un heap de 10 elementos con base en prioridades; cada ele­
mento tiene una prioridad única, es decir, existen prioridades del 1
al 10. Si la prioridad 10 es la mayor y la 1 es la menor, ¿qué posi­
ciones del arreglo que representa el heap puede ocupar el elemento
con la prioridad 2?

2. Se tiene un heap de 10 elementos con base en prioridades. Cada ele­


mento tiene una prioridad única, es decir, existen prioridades del 1
al 10. Si la prioridad 10 es la mayor y la 1 es la menor, ¿qué posi­
ciones del arreglo que representa el heap puede ocupar el elemento
con la prioridad 9?

3. Realice la implementacion en lenguaje C++ de las siguientes ruti­


nas: rutina que, dada una lista de valores, construya un heap; rutina
que, dado un heap, inserte un nuevo elemento y rutina que, dado un
heap, elimine el elemento con mayor prioridad.
• |J
Heaps

4. El aeropuerto de la ciudad de Nueva York es uno de los más transi­


tados del mundo. En sus pistas aterrizan y despegan cientos de
aviones cada hora.
Para evitar accidentes, hay un controlador de vuelos para cada
una de las pistas del aeropuerto. El trabajo del controlador consiste
en decidir cuál de los aviones utilizará la pista en un momento de­
terminado (ya sea para aterrizar o para despegar). Para tomar la
decisión de manera justa se maneja una cola priorizada de la que se
toma el avión con mayor prioridad para asignarle la pista. Las polí­
ticas del aeropuerto establecen los siguientes tipos de vuelos:

1 Vuelo transoceánico 2 Vuelo continental


3 Vuelo nacional 4 Vuelo regional

donde la prioridad más alta corresponde al vuelo transoceánico y la


más baja al regional. Además, cuando en la cola hay varios aviones
con la misma prioridad, se le da preferencia al avión que haya soli­
citado la pista primero (más temprano).
Cuando la pista trabaja normalmente, este esquema minimiza el
tiempo promedio de espera de cada vuelo (según estadísticas obte­
nidas por el personal del aeropuerto).
El 1 de octubre a las 10:00:00 horas, ,a pista administrada por el
controlador Smith está desocupada y no hay vuelos en la lista de es­
pera. En ese instante se detecta un problema (algún avión derramó
aceite) por lo que la pista se cierra para limpieza. Los aviones a los
que se les asigne ésta, tendrán que esperar a que se abra nuevamente
(tabla 12.1).

Durante el lapso que permanece cerrada la pista (aproximada­


mente cinco minutos), los siguientes aviones se reportaron listos
para utilizarla:

Vuelo Tipo de vuelo Hora de solicitud I Tipo de solicitud

NY- Búfalo Regional 10:01 :20 Despegar


Los Ángeles-NY i Nacional 10 : 01 : 4 0 Aterrizar
NY-Pittsburgh Regional 10:02:05 Despegar
París-NY Transoceánico 10: 0 2 : 3 5 Aterrizar
NYMéxico, DF Continental 1 0 : 03-: 5 5 Despegar
BuenosA¡res-NY ' Continental 10 : 0 4 : 1 2 Aterrizar
DenverWY Nacional 10:04:25 Aterrizar
Moscú-NY Transoceánico 10:04:57 Aterrizar

Tabla 12.1

Q •
Ejercicios

a) Muestre, a través de un esquema, cómo va creciendo la cola prio-


rizada conforme van llegando las solicitudes de uso de pista.
b) Si a las 10:05:02 la pista entra en funcionamiento, muestre cómo
se modifica la cola priorizada cuando ésta se asigna al vuelo con
mayor prioridad.

5. Una computadora multiusuario requiere que el sistema operativo


que controla sus recursos administre óptimamente la asignación de
jobs (programas listos para ejecutarse) al CPU. Generalmente, esto
se logra empleando una cola priorizada donde siempre se asigna al
CPU el job con mayor prioridad.
La computadora de una institución educativa maneja los si-
guientes tipos de programas:
Jobs--> tipo A: administrativos
tipo B: programas de maestros
tipo C: programas de alumnos
Cuando los jobs de tipo A están en la cola priorizada, prioridad
sobre los de tipo B, los que a su vez la tienen sobre los del tipo C.
Además, cuando hay varios jobs del mismo tipo se toma en cuenta
la hora en que llegaron a la cola. Cuanto más temprano haya llega-
do el job. tiene mayor prioridad.
Suponiendo que la cola inicialmente está vacía y que en deter-
minado lapso llegaron ocho jobs, como se muestra en la siguiente
tabla 12.2:

Tipo Hora Tipo Hora

c 10:50:53 A 10:55:45
A 10:52:03 A 10:58:12
C 10:53:25 C 1V.Or.22

Tabla 12.2

a) Muestre, a través de esquemas, cómo va creciendo la cola priori-


zada al ir llegando los jobs correspondientes.
b) Muestre cómo se modifica la cola priorizada si a las 11:05:00 el
CPU atiende al siguiente job en la cola. (En esta hora, la cola con-
tiene todos los jobs que llegaron anteriormente.)

6. Muestre los cambios que ocurren en la siguiente lista si se quiere or-


denar descendentemente por el método heap sort: 70 60 50 40
30 20 10. Indique cuántos intercambios de elementos ocurrieron en
total al formar el heap inicial y cuántos al aplicar el ordenamiento.

• Q
Heaps

7. Dada una lista de cuatro elementos en forma descendente, ¿cuántos


intercambios de elementos en el arreglo ocurrirían si la misma lista
se desea ordenar en forma descendente por el método heapsort?

8. Dada una lista de cuatro elementos ordenados en forma descenden­


te, ¿cuántos intercambios de elementos en el arreglo ocurrirían si la
misma lista se desea ordenar en forma ascendente por el método
heapsort?

9. Escriba la implementación en lenguaje C++ de una rutina que sirva


para ordenar por el método heapsort un arreglo de números enteros.
Se puede suponer que ya están implementadas las siguientes ruti­
nas: crearjieap, acomoda_abajo y acomoda_arriba.
¿De qué rutinas depende que el ordenamiento se haga en for­
ma ascendente o descendente?

í nUTOEUHLUñCIÓNQ, =
' !
i i

1. Considere la siguiente cola priorizada: 84 39 56 21 39 42 21 21


¿Cómo se modifica la cola priorizada después de insertar al elemen­
to 81?

a)84 39 56 21 39 42 21 21 81
b)84 39 81 21 39 42 56 21 21
c)84 81 56 39 39 42 21 21 21
d)84 81 56 21 39 42 21 21 39
e) Ninguna de las anteriores.

2. Dado el siguiente árbol heap:

m
70 <3> (Sí ^

la lista que lo representa es:


a) Frente --> 90 80 40 70 50 60 10 20 30 <-- Final
b) Final --> 10 60 30 20 50 70 40 80 90 <-- Frente
c) Final - > 10 60 70 50 80 20 30 40 90 <~ Frente
d) Frente-> 90 40 80 30 20 50 70 10 60 <-- Final

0 e) Ninguna de las anteriores.



Capítulo J- j

OBJETIVOS
■ Definir en qué consiste una estructura de datos sin relación o conjunto.
• Describir las características primordiales de los conjuntos ordinales y es-
tructurados.
• Explicar las formas de representación para un TDA conjunto.
• Describir en qué consiste la técnica hashing, como otra opción para imple-
mentar la búsqueda eficiente.
- Definir qué es una función hashing y cuáles son las alternativas para su di-
seño eficiente.
• Entender la importancia de contar con una estrategia para el manejo de ele-
mentos colisionados.
- Describir las diversas estrategias para el manejo de colisiones.

B
Hashing

¿Qué es un conjunto?
Considerando la definición matemática típica, un conjunto es. simple­
mente una colección de objetos. Los objetos no mantienen ninguna
relación aparente entre ellos (como descendiente de..., anterior a...) y
tampoco están obligados a compartir atributos comunes, es decir, no
^ necesitan pertenecer a la misma clase; sencillamente están agrupados
en una misma estructura.
En el área computacional la noción de conjunto se restringe, ya que
se define como una agrupación finita de objetos, con características
comunes, que no presentan relación alguna entre sí. Esto quiere decir
que los elementos que pertenecen a un conjunto deberán compartir al
menos el atributo de tener el mismo tipo de datos. / /
De esto último se deduce que, en términos computacionales, se
pueden tener dos clases de conjuntos: con elementos atómicos o y con
elementos estructurados.

¿Qué es un conjunto de elementos atómicos?


Básicamente, el término atómico, aplicado al concepto de tipos de
datos, define los elementos simples, indivisibles y enumerables. En los
lenguajes de programación se hace referencia a los tipos de datos bási­
cos como enteros, caracteres, etc., generalmente conocidos como
valores ordinales.
A estos conjuntos se les pueden aplicar las operaciones que se rea­
lizan en conjuntos matemáticos, tales como intersección, pertenencia o
diferencia.
Dado que, como se mencionó anteriormente, un conjunto de ele­
mentos atómicos posee un grupo de valores y una serie de operaciones
posibles de aplicar en él, entonces se puede decir que se tiene una
estructura de datos denominada conjunto de elementos atómicos.

¿Qué es un conjunto de elementos estructurados?


Cuando se utiliza el término elemento estructurado se hace referen­
cia a los formados por más de un valor, es decir, los que pueden
dividirse en varios componentes.
Al trabajar con conjuntos de elementos estructurados se pre­
supone que todos los miembros pertenecen a la misma clase y que cada
uno de ellos tendrá un componente que lo distinguirá de manera única
del resto de los elementos. A este componente se le denomina llave o
atributo primario

@ •
¿Cómo se puede representar un conjunto de elementos estructurados?

Este conjunto de elementos, al igual que el anterior, posee un grupo


de operaciones válidas; sin embargo, a diferencia de las operaciones en
conjuntos atómicos, no son similares a las definidas para conjuntos
matemáticos. Las operaciones en los estructurados dependen directa­
mente de la llave de los elementos, ya que no hay relación definida
paradlos.
Para realizar cualquier operación sobre este tipo de conjuntos es
necesario que se haga referencia a un valor particular de la llave, de lo
contrario, no tendría sentido.
" \ Básicamente, en un conjunto de valores estructurados se pueden
. \ realizar las siguientes operaciones:

\ \ BuscarLlave, Insertar Elemento, Borrar Elemento, Modificar


\ *
\ i

\ V Considerando que un conjunto de elementos estructurados está con-


\ formado por un grupo de valores y una serie de operaciones válidas
para aplicar sobre sí mismo, se puede concluir que, al igual que el con-
I junto atómico, los de elementos estructurados son una estructura de
/ datos. Por lo tanto, también es posible definir la especificación lógi-
/ / ca requerida para el diseño del TDA correspondiente al conjunto
/ / estructurado.

.-'-'"" ¿Como se puede representar un conjunto de elementos


.. estructurados?
Cualquier tipo de representación que se pueda realizar en conjuntos de­
be respetar su característica principal: la no relación entre sus elemen­
tos. Dado que se hace referencia a cada uno de los elementos a través
de su llave, se puede pensar en almacenarlos secuencialmente y hacer •
una búsqueda secuencial. También se podrían guardar ordenadamen­
te con respecto al valor de la llave e implantar en ellas una búsqueda
binaria.
Estas opciones de representación son buenas, aunque pueden provo­
car un pobre desempeño de las operaciones cuando el conjunto tiene
varios elementos.
Hay una opción de representación para conjuntos que trata de evi­
tar los problemas que se presentan con la búsqueda secuencial y la
binaria. En esa representación los elementos se guardan de manera
esparcida dentro de un espacio de almacenamiento (comúnmente
llamado tabla) y posteriormente se intenta localizar cada uno de los
elementos del conjunto a través de un acceso directo. Esto quiere decir
que con una sola comparación se puede determinar si una llave en
particular se encuentra o no en el conjunto.

• 9
Hashing

En ocasiones, cuando se manejan llaves numéricas, se podría pen­


sar en asociar cada posición del espacio de almacenamiento a una llave
específica (como si fuera el subíndice de un arreglo).
Por ejemplo, si las llaves de los elementos fueran números del 1 al
10 y la tabla estuviera diseñada para guardar diez elementos, se pudie­
ra asociar la primera posición al elemento de llave 1. la dos al de llave
• 2, etcétera.
Lo anterior generalmente es imposible porque las llaves no siempre
son numéricas y, aunque lo fueran, tal vez sus valores serían demasia­
do grandes y el espacio de almacenamiento pequeño, y no habría una
correspondencia uno a uno. Por ejemplo, cuando las llaves de los ele­
mentos son números de seis cifras y la tabla es de 100 posiciones. /
Cuando se presentan situaciones así, el acceso se puede efectuar a
través de una técnica denominada hashing. / /

¿Cómo trabaja la técnica hashing?

Básicamente, hashing sirve para mapear todos los posibles valores de \


las llaves dentro del espacio de almacenamiento (denominado tabla
de Hash). Si en la mayoría de las ocasiones no se puede establecer una
correspondencia uno a uno entre los valores de la llave y las posiciones
de la tabla, la técnica hashing convertirá cada valor de la llave en una ^ ^ ^
dirección válida dentro de la tabla, aplicando a la llave una función de
conversión:

f{Uave) = posición jabla (dirección basel

Las funciones utilizadas para hacer la conversión reciben el nombre de


funciones hashing. Si se lograra diseñar una función que, para cada
uno de los posibles valores de la llave generara posiciones únicas den­
tro de la tabla, se estaría cumpliendo cabalmente con la idea de lograr
un acceso directo a cada elemento almacenado en ella. Este tipo de
funciones se llaman funciones hashing perfectas, aunque es práctica­
mente imposible obtenerlas. Esto quiere decir que, por lo general, una
función de conversión puede llegar a generar la misma posición en la
tabla para dos llaves diferentes, provocando con esto una colisión que,
obviamente, debe resolverse de una u otra forma.

8 •
¿Como diseñar una función hashing?

¿Cómo diseñar una función hashing?


Es muy fácil deducir que uno de los puntos principales en el uso de la téc­
nica hashing es el diseño adecuado de la función que transforme la llave
del objeto en una posición válida dentro de la tabla.
A través del tiempo se han diseñado diversas funciones y se han crea­
do metodologías para su diseño; así. no existe una metodología que
genere "la mejor" función hashing para la transformación de llaves, ya
que, en la mayoría de las ocasiones, la función depende de las caracterís-
**\ ticas de la llave en una aplicación particular. Sin embargo, independien-
-^ \ temente de la forma en que se haga el diseño de esta función, se espera
X \ que cumpla con las siguientes propiedades:

\ \ • Ejecución rápida (haga conversiones sencillas)


\ \ • Distribución uniforme de posiciones dentro de la tabla
\ • Direccione todo el espacio de almacenamiento (que use toda la tabla)

Es evidente que la función de transformación de la llave debe ser com-


/ • pletamente confiable, esto es, siempre debe generar la misma dirección
/ / para una misma llave en la tabla. Esto quiere decir que el cálculo de
/ / la dirección es totalmente independiente de la operación que se vaya a
/ / realizar sobre el elemento. Es lo mismo de dar de alta la llave que buscar-
; / / la o querer borrarla, pues la dirección generada será siempre la misma.

...--''*' A continuación se definen algunas de las metodologías más empleadas


en el diseño de funciones hashing:

Selección de dígitos
Consiste, básicamente, en seleccionar algunos de los dígitos que confor­
man la llave y con ellos generar un índice para el espacio de almacena- *
miento. Por ejemplo:

H(d,d2d3d4d5d6d7d8(d9) = d3d5d7 donde 0<= d, <= 9

Esto serviría para accesar un espacio de mil elementos (000-999).

Residuales (división)
Esta función consiste, básicamente, en utilizar como índice el residuo de
dividir la llave con el tamaño máximo de almacenamiento, esto es:

H(llave) = llave MOD n donde : 0<= H(llave) <= n-\

• Q
Hashing

Esta es una de las técnicas más comunes debido a su sencillez. Sin em­
bargo, para mejorar su eficiencia (de la distribución uniforme de índices)
se sugiere que el tamaño de espacio de almacenamiento sea un número
primo (para evitar que varias llaves obtengan el mismo residuo y, por
tanto, el mismo índice).

• Cuadrática (multiplicación)
Consiste en elevar al cuadrado el valor de la llave y, del resultado,
seleccionar los dígitos centrales (los más involucrados en las opera­
ciones de multiplicación y suma); la cantidad de dígitos depende
exclusivamente del rango de valores válidos para el índice. Dicha
función se representa como:
H(llave) = dígitos_centrales (llave2)
Por ejemplo:

H((d|d2d,d4d?)2) = w ? donde 0<= r, <= 9


por lo tanto.
000<= H <= 999

Folding (plegamiento)
Consiste, básicamente, en agrupar algunos de los dígitos que conforman
la llave y aplicarles algunas operaciones que permitan generar un índice
para el espacio de almacenamiento. Por ejemplo:

H ( d ^ d ^ d , ) = d, + d2 + 4 + d4+ d5
donde (k=d,<= 9 y ( k = H < = 4 5

Esta técnica puede emplearse en combinación con cualquiera de los otros


métodos.

¿Cómo se resuelven las colisiones en la técnica de hashing?


Como se mencionó, una colisión se presenta cuando la función que
convierte la llave en una posición dentro de la tabla, genera la misma
dirección para dos valores de llave diferentes.
A fin de resolver estas colisiones se han diseñado diversas metodo­
logías; la mayoría son fáciles de implantar y algunas son muy eficientes.
Dependiendo de la forma en que se busca una posición nueva para
el elemento colisionado, a partir de la dirección base, y según se ma­
neje el espacio de almacenamiento, estas metodologías o pruebas se
dividen en dos grandes grupos:

8 •
¿Cómo funciona la metodología de direccionamiento abierto?

• Direccionamiento abierto
• Encadenamiento

¿Cómo funciona la metodología de direccionamiento abierto?

Las pruebas de direccionamiento abierto se caracterizan por utilizar


únicamente el espacio definido en la tabla, la cual se considera circu­
lar, para manejar los elementos colisionados; es decir, cuando un ele­
mento provoca una colisión se almacena en otra posición del mismo
espacio definido para la tabla, hacia arriba o hacia abajo de la dirección
base original (que es: dir_base=hash(llave\.
La diferencia principal entre las diversas pruebas que se agrupan en
la metodología de dirección abierta consiste en la forma en que buscan
la nueva posición donde quedará el elemento colisionado. Evidente­
mente se espera que, aplicando siempre la misma prueba de solución
de colisiones, una llave genere el mismo patrón de búsqueda dentro de
la tabla, porque, de lo contrario, la prueba sería inestable y sin posibi­
lidades de aplicación real.
Dentro de las pruebas más comunes que agrupa el concepto de di­
rección abierta se encuentran:

• Prueba lineal
• Prueba cuadrática
• Prueba aleatoria
• Prueba doble hashing

Independientemente de qué prueba se aplique, los métodos de direc­


ción abierta necesitan manejar una bandera de estado para cada una de
las casillas de la tabla; cada bandera servirá para saber si la casilla es- •
tá vacía, tiene un elemento almacenado o tuvo un elemento (es decir,
se borró). Esto se hace para controlar el proceso de búsqueda de un ele­
mento que provoca colisión.
Además, el tipo de pregunta que se hace en estos métodos depende
de la operación particular que se vaya a realizar sobre el elemento, ya
que si se piensa insertarlo se preguntará por un espacio vacío; si se quie­
re buscar si existe o no, se compararán los elementos hasta encontrarlo
o, en su defecto, llegar a un espacio que esté vacío.

. o
Hashing

¿Cómo funciona la prueba lineal?


U idea básica de la prueba lineal es buscar secuencialmente una posi­
ción disponible a partir de la dirección original generada por la función
hashing.
Esta es la prueba más fácil de implantar, pero puede llegar a generar
_ una búsqueda secuencial, perdiéndose las características benéficas de
la técnica hashing.
El algoritmo típico para la prueba lineal es:

1. Calcular la dirección base que le corresponde a la llave (dir_base).


2. Accesar la posición y comparar el contenido de la tabla contra la
llave del elemento.
3. Si la casilla está ocupada y su contenido es diferente al elemento,
se debe buscar una nueva posición por calcular. Esto se debe
hacer de la siguiente manera: nueva Jir = dirbase + j , donde;
es un contador del número de ocasiones en que se lia registrado
una colisión en esa búsqueda, lo que genera una búsqueda
secuencial a partir de la dirección base.
4. Si la nueva dirección no es igual a la dirección base, se regresa al
punto número 2. Si son iguales, significa que se ha completado
una vuelta a la tabla y. por lo tanto, la búsqueda fracasó.

^ Ejemplo
/^Para ilustrar el comportamiento de la prueba lineal cuando se utiliza un^
tamaño de paso N=l, suponga que sobre un arreglo de 11 posiciones
se insertará la secuencia de llaves (figura 11.1): 7, 17,6, 33, utilizan­
do como función hashing: H(llave)=llave moa 11. La secuencia de
movimientos sería:

—r~~i—i—i—i—i—i—i—i—i—
7
I I I I 1 I I | I I
0 1 2 3 4 5 6 7 8 9 10

17 7
l l l l i l l I l l I
0 1 2 3 4 5 6 7 8 9 10

17 7

I i II M I i i I
G 1 2 3 4 5 6 7 B 3 10

Q •
Ejemplo

U Ejemplo (continúa) '


' ^-—■ >

17 7

0 1 2 3 4 5 6 7 8 9 10

i i i i i i ITTI i |
17 7

0 1 2 3 4 5 6 7 8 9 10

— i — r — i — i — r — i — i — i — i — i —
17 7 o
1 , 1 1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 10

i n I I I I I I I I !
33 17 7 6

0 1 2 3 4 5 6 7 3 9 10
Suponga que en el estado actual de la tabla se decide agregar un par de
nuevos elementos: 28 y 52 (figura 11.2). Siguiendo el mismo proceso
de solución de colisiones se llegaría al siguiente estado de la tabla :
. . . 1 . 1 1 . 1—
33 17 7 6 28 52 \
— l — ! 1 — , — I — , 1
0 1 2 3 4 5 6 7 8 9 10
¿Cómo se modificaría el estado actual de la tabla si se decidiera añadir
el elemento 76 (figura 11.3)? La solución de esta colisión requeriría los
siguientes pasos:
—1 1 1 1 1 1 1 1 1
33 171 7 6 28 52 •
I I 1 I I I . 1 I I ,
0 1 2 3 4 5 8 7 8 9 10

33 17 7 6 28 52

' 0 1 2 3 4 5 6 7 B 9 ' 1 0

33 I 17 7 6 ] 28 52 '

0 1 2 3 4 5 6 7 8 9 10

—i—r~i—T~T—i—r~r—i—i—i
33 76 17 7 6 28 52

[ i : i l l
■ '0■■■2■3■4 -'- - - -'- ^L_0
Hashlng

¿Cómo funciona la prueba cuadrática?


La prueba cuadrática funciona de la misma manera que la lineal; la
única diferencia es la forma de calcular la nueva posición. En esta
prueba se supone que la probabilidad de colisión disminuye si la
búsqueda no es lineal. Aquí, la nueva posición se calcula con un
0 desplazamiento igual al valor cuadrático del contador de colisiones.
Esto es. la nueva dirección quedaría como:

nueva Jir = dir_base + p.

Por lo tanto, la diferencia con respecto a la prueba lineal es que en


lugar de ir sumando 1, 2, 3... a la dirección base, se sumará 1, 4, 9,...,
sin olvidar que la tabla es un círculo.
El algoritmo para la prueba cuadrática es exactamente igual que el
de la lineal, únicamente cambia la forma de calcular la nueva posición.
Esta prueba tiene un pequeño inconveniente; en ocasiones no com­
para contra posiciones vacías y el criterio para detectar una búsqueda
fallida (cuando ya se le dio toda la vuelta a la tabla) es mucho más
complejo de establecer que en la prueba lineal a razón de esto es sim­
ple, ya que no hace una búsqueda completa en todas las posiciones de
la tabla y probablemente, al "darle la vuelta" a ésta, se desfase con
respecto a la posición base original, lo que provoca la posibilidad de
quedar ciclado.

¿Cómo funciona la prueba aleatoria?


Esta prueba es una de las más difíciles de implementar ya que, como
su nombre lo indica, genera desplazamientos aleatorios de un elemen­
to a partir de su dirección base, por lo que las nuevas posiciones para
el elemento colisionado se distribuirán de forma aleatoria. Obviamen­
te, siempre debe producirse la misma secuencia aleatoria para la mis­
ma llave, es decir, debe considerarse la llave como la semilla a partir
de la que se generará una secuencia aleatoria.
Esta prueba tiene exactamente los mismos problemas que la prue­
ba cuadrática en cuanto a no comparar todas las casillas y a no tener
un buen criterio de búsqueda fallida.

¿Cómo funciona la prueba doble hashing?


La prueba doble hashing, también denominada rehashing, genera las
nuevas posiciones por consultar a partir del propio valor de la llave.
Esto es, en lugar de calcular desplazamientos lineales o cuadráticos,

3 •
¿Como funciona la metodología de Encadenamiento?

genera desplazamientos que dependerán enteramente del valor de la


llave con la que se está trabajando. Para lograrlo, aplica una segunda
función hashing a la llave y utiliza su resultado para determinar el "ta­
maño de paso" que producirá la llave en cuestión.
La forma en que esta prueba determina la nueva posición por com­
parar es:

nuevajir = áir_base + fnuevoJiashíllave)

donde dir_base es la posición generada por la función hashing original,


/ es el contador de colisiones para esa búsqueda y nuevojiash (llave)
se comporta como una constante para la llave en particular y determi­
na el tamaño de salto o longitud de desplazamiento que tendrá la bús­
queda.
Al igual que las pruebas anteriores es fácil de implementar, aunque
puede tener problemas con el criterio para detener la búsqueda e inclu­
so caer en un ciclo "infinito" y, por tanto, dejar sin verificar el resto de
posiciones de la tabla.
El algoritmo típico para la prueba doble hashing es:

1. Calcular la dirección base que corresponde a la llave (dir_base).


2. Accesar la posición y comparar el contenido de la tabla con la lla­
ve del elemento.
3. Si la casilla está ocupada y su contenido es diferente al elemento,
se debe buscar una nueva posición a calcular. Esto se hará de la
siguiente manera:

nuevajir = dirjase + j*nuevoJash(llave).

4. Se regresa al punto número 2 y se repite el proceso hasta que se


encuentra una posición disponible, se detecta que el proceso ha en­
trado en un ciclo infinito o que ya se recorrió totalmente la tabla.

¿Cómo funciona la metodología de Encadenamiento?


La idea básica de la metodología de encadenamiento es ligar todos los
elementos cuyas llaves generan la misma dirección base. Esta liga pue­
de hacerse dentro del mismo espacio de la tabla o bien, con una por­
ción de espacio externa.
Las dos formas más comunes en esta metodología son:

• Método de encadenamiento externo


• Método de encadenamiento de coalisiones

• 0
Hashing

¿Cómo funciona el método de encadenamiento externo?

Cuando se utiliza esta técnica, cada casilla de la tabla de hashing se con­


sidera como la cabeza de una lista que mantiene ligados todos los ele­
mentos cuya llave generó la misma dirección base. La lista de elementos
siempre es un espacio adicional a la tabla. Se pueden diseñar diversas
• estructuras para representar esta lista; las más comunes son: lista enca­
denada, pila, lista ordenada e, incluso, ABB.
A continuación (figura 11.4) se muestra una gráfica con diferentes
representaciones para este método:

( * ^ / / ^
1ra i r-' / /
9 2 / /
3 | 3 : /
4 4 _

5 ^□>n>n>a »_f |
6 6 V' \
7 7 \
8 8 \ \

l 1Ü 10 ) \ X
Rgura 11.4.
El algoritmo para este método es sumamente sencillo;

1. Se calcula la dirección base del elemento.


2. Se analiza en forma secuencia! o en forma binaria, según la repre­
sentación seleccionada, hasta encontrar el elemento o, en su defec­
to, determinar que no está dado de alta en la tabla.

Las principales ventajas de este método son:

• Se puede almacenar una cantidad muy grande de elementos (se­


gún restricciones de configuración).
• No hay necesidad de recalcular direcciones.
• Es fácil de implantar.

Por otro lado, las desventajas son:

• Se debe definir y manipular un espacio de memoria adicional a la


tabla que originalmente se diseñó.
• Si la función de conversión diseñada no es eficiente, se corre el

0 riesgo de degenerar en una búsqueda casi secuencial,


¿Cómo funciona el método de encadenamiento de coalistones?

¿Cómo funciona el método de encadenamiento de coalisiones?


Este método, al igual que el anterior, forma una íísta de elementos co-
lisionados; sin embargo, utiliza la misma tabla para almacenarlos.
Con este método se pueden definir diversas representaciones, con­
siderando que la información se almacenará únicamente en el espacio
de memoria diseñado para la tabla.
La más común de estas representaciones agrega un campo adicional
a la tabla (para guardar la dirección del siguiente elemento en la cade-
**\ na) y la divide en dos segmentos: el área normal y el área de colisio-
«^ X, nes, donde se espera que ésta tenga un tamaño aproximado de 15% del
\ \ espacio total y se emplea para almacenar las listas de los elementos
\v \ colisionados.
\ \ En la figura 11.5 se muestra de cómo se vería una tabla de hashing
\ \ con este método:
\ \ f Área de * \
Área ™rrnBl colisiones

J ,' 0 1 2 3 4 5 6 7 8 9 10 11

/ / V I i l I, I, I i J J
^ ^ / Figura 11.5.
,..*'*' El algoritmo con el que trabaja este método es el siguiente:

1. Calcular la dirección base del elemento.


2. Accesar la tabla en esa posición.
3. Si la casilla está vacía, el elemento no se encuentra, por lo que
pudiera insertarse (dependiendo de la operación a realizar). Si la •
casilla está ocupada, verifique la dirección almacenada como si­
guiente elemento de la lista. Si la dirección no es nula, se debe
rastrear la lista hasta encontrar al elemento o llegar a una direc­
ción nula, en cuyo caso se tendría una búsqueda fallida y la posi­
bilidad de insertarlo, para lo cual se debe utilizar el primer espacio
disponible en el área de colisiones almacenando la dirección otor­
gada, dentro de la casilla, que anteriormente se había consultado.

La principal ventaja de este método es que no hay necesidad de


recalcular direcciones, sólo es cuestión de seguir el encadenamiento ge­
nerado a través de los elementos colisionados en una misma dirección.
Su principal desventaja es que las operaciones posibles por aplicar
sobre los elementos, especialmente la eliminación, se vuelven mucho
más complejas, ya que se debe mantener actualizada la lista de elemen­

•— tos colisionados en una misma dirección base.

s
0\
Hashing

( EJERCICIOS ) &

DISEÑO DE FUNCIONES HASHING

1. Se desea diseñar una función hashing para trabajar con llaves de tres
letras (L,L2L3).
Al aplicar la función se deberá generar un valor entre 0 y n - 1,
donde n es el tamaño de la tabla.
Al insertar la secuencia de llaves: PAL, LAP, PAM, MAP, PAT,
PET, SET, SAT, TAT, BAT. ¿cuál de las siguientes funciones has­
hing es la mejor para los valores de «= 11, 13, 17 y 19?

a) H O ^ L X , ) = (ORD(L,) + ORD(L 2 ) + ORD(L,)) M O D N


b) H(L,L2L,) = (ORD(L,) * ORD(U,)) M O D N
c) H(L,L,L,) - ((ORD(L,) M O D N ) + (ORD(L 2 ) M O D N ) +
(ORD{L,)) M O D N)) M O D N
d) H(L,L2L,) - ORD(L,) M O D N

(Suponga que la función ORD obtiene el código ASCII de la letra y


que éstos son: A = 65, B = 66, T = 84, etcétera.)

2. Una línea aérea desea automatizar mediante un sistema computacio-


nal sus reservaciones de vuelos. El ingeniero en sistemas encargado
del proyecto ha decidido que el manejo de la información se im­
plantará utilizando una técnica hashing; además, después de un bre­
ve análisis se planea trabajar con el siguiente formato en la llave de
los objetos:
La llave consta de cinco dígitos (d|d2d3d4d5), donde d ^ corres­
ponde a una clave que identifica la ciudad de origen, d3d4 a una cla­
ve que identifica la ciudad destino y d5 a una clave que identifica el
día de la semana y la hora del vuelo. Cada dígito puede tener un
valor entre 0 y 9.
Por ejemplo:

La llave 06541 representa el vuelo de México a Monterrey el lunes


a las 7:00 a.m.

6 •
Ejercicios

La llave 06542 representa el vuelo de México a Monterrey el lunes


alas3:00p.m.
La llave 54067 representa el vuelo de Monterrey a México el vier­
nes a las 8:30 p.m.

Se sabe que el tamaño de la tabla donde se guardarán los objetos


es de 100 (TAMTABLA = 100).
Para las siguientes funciones hashing, indique qué método se uti­
lizó y si es una buena función. Justifique su respuesta.

a) H(d,d2d3d4d5) = d,d2 + d3d4 + d50


b) H(d,d2d3d4d5) = d2d3d4 MOD TAMTABLA * 2
c) H(llave) = r,r2 donde r, = d,d2 DIV TAMTABLA yr2 = d3d4d,
DIV TAMTABLA
d) H(d,d2d3d4ds) = d2d4ds MOD (TAMTABLA + 1)
e) H(llave) = (llave)2 MOD TAMTABLA

Si usted fuera el encargado del proyecto, ¿cuál de las funciones


hashing escogería en la programación del sistema?

3. En determinado sistema computacional se requiere almacenar infor­


mación acerca de automóviles. Se ha decidido manejar la técnica
hashing para administrar la información y se sabe que el número de
placa será la llave de acceso a la tabla. El número de placa tiene el
formato: X , X ^ D | D d ^ , donde X,X2X3 son tres letras mayúsculas
y D ^ D 3 son tres dígitos; en general, la primera letra (X,) corres­
ponde al estado en donde se expidió la placa. Se sabe también que
el sistema almacenará información de 500 automóviles como máximo.
Para cada uno de los métodos de construcción de funciones has­
hing, diseñe una buena y una mala función hashing para esta apli­
cación. Justifique y explique su respuesta.

4. Si en determinada aplicación se decide emplear la fecha de naci­


miento de una persona como llave para insertar en una tabla de has­
hing de 101 posiciones, ¿cuál de las siguientes funciones de hashing
es la mejor?

a) (valor del día + valor del mes*30) MOD 101


b) (valor del año) MOD 101
c) (valor del día + valor del mes + valor del año) MOD 100
d) (valor del día + valor del mes + valor del año) MOD 101
e) Todas las anteriores.

• 6
Hashing

MANEJO DE COLISIONES

5. Cuando se usan los métodos de dirección abierta como estrategia pa­


ra el manejo de colisiones, es necesario manejar un estatus en cada
elemento de la tabla para indicar si la localidad está vacía, ocupada o
borrada. El estatus es necesario para controlar las eliminaciones, las
• inserciones y las búsquedas de los elementos (recuerde que en esta
estrategia es necesario comparar con una localidad vacía para detener
la búsqueda).
Una idea para evitar el uso del estatus en los elementos es mane­
jar las bajas de la siguiente forma:

Cuando se quiera borrar un elemento se debe:


1. Buscar el elemento en la tabla como se hace tradicionalmente (se
supone que está en la posición X).
2. Buscar el último elemento en el cluster del elemento borrado, es
decir, el elemento que está justo antes de la primera posición va­
cía en la búsqueda (se supone que está en la posición Y).
3. Intercambiar los elementos de las posiciones X y Y.
4. Borrar el elemento de la posición Y (dejar la localidad vacía).

Aunque esta idea parece buena, tiene algunos inconvenientes. Expli­


que claramente para qué casos funcionaría este método sin problemas
e indique qué tipo de agolpamiento impide que funcione en otros.

6. Una tabla de hashing de 100 posiciones está completamente llena y


todas las llaves han llegado a la tabla por medio de una función has­
hing perfecta. ¿Cuál es el promedio de comparaciones para determi­
nar que una llave existe en la tabla?

7. Si en una tabla de diez posiciones se han insertado tres llaves en la


primera, segunda y décima posición, ¿cuál es el promedio de com­
paraciones para determinar que una llave no existe en la tabla, si se
usó la prueba lineal con c = 1 como estrategia para manejar las
colisiones?

8. Para una tabla de n posiciones, que contiene n - 1 llaves, ¿cuál se­


ría la cantidad máxima de comparaciones para encontrar una llave?
y ¿cuál la mínima?

9. Se tiene una tabla hashing con 27 posiciones y se usó la función


H(llave) = H(did2<b) = di + d2+ di. Si se usa la prueba cuadrática

6 •
Ejercicios

para manejar las colisiones, ¿cuáles serían las siguientes tres posi­
ciones de la tabla que se verificarían si se insertara la llave 678 y la
posición 21 está ocupada?

10. Se tiene una tabla hashing con 27 posiciones y se usó la función


H(llave) = H(did2d3) = di + d2+ di. Si se usa la prueba cuadrática
para manejar las colisiones ¿cuáles serían las siguientes tres posi­
ciones de la tabla que se verificarían si se insertara la llave 578 y la
posición 20 está ocupada?

11. Sobre una tabla hashing de 11 posiciones se aplica la siguiente fun­


ción:

H(llave) = H(did2d3d4d5d6) = ABS (dafc- did2) MOD 11

y se usa como estrategia de manejo de colisiones la prueba lineal


de los métodos de dirección abierta.
Responda cada uno de los siguientes incisos:

a) Muestre la tabla resultante al insertar las siguientes llaves:


343434, 859187, 123403, 730062, 503762, 644554, 648056
b) ¿Cuántas colisiones en total ocurrieron al insertar las llaves del in­
ciso anterior?
c) ¿Cuántas comparaciones se requieren para encontrar la llave
648056?
d) ¿Cuántas comparaciones se requieren para saber que la llave
143012 no existe en la tabla?
e) ¿Cuántas comparaciones en promedio se requieren para saber que
una llave no existe en la tabla?
f) Mencione de qué tipo es la siguiente función hashing y por qué no
sería buena para aplicarla sobre las llaves y la tabla que se indi­
can en este problema:
H(d1d2d^d5df,) = d, + d2 + d, + d4 + d5 + d 6
g) Conteste verdadero o falso :

• Si el tamaño de la tabla aumenta a 15 posiciones y se trabaja con


la misma función hashing, existirían menos colisiones al inser­
tar las mismas llaves.
• Una función hashing perfecta sobre las llaves que se muestran
en el inciso a) reduciría a cero la cantidad de comparaciones
para encontrar una llave.

• a
Hashinq

• El orden en que se insertan las llaves influye en las posiciones que


ocuparán en la tabla.
• La estrategia de manejo de colisiones que se usa en el algoritmo
de inserción de llaves en una tabla debe ser la misma estrategia
que usa el algoritmo de búsqueda de llaves en la tabla. .

• 12. Sobre una tabla de hashing se aplica la siguiente función hashing:


H(llave) = H(did2d3d4d5d6)= dads MOD 10, y se usa como estrategia
de manejo de colisiones la prueba lineal (método de direccionamiento
abierto con c = 1). Después de hacer algunas inserciones, la tabla con­
tiene las siguientes llaves:

1
2 143012
3 198911
4 153926
5 152621
6 520851
7 -
8 191972
9 335872
10 151090
11 190890
12 150588
13 232371

Con base en la información de esta tabla, responda cada una de las


siguientes preguntas:
(Nota: considere sólo las comparaciones con llaves y con vacío.)

a) ¿Cuáles son las llaves que se insertaron sin problemas de colisiones?


b) ¿En qué orden se insertaron las llaves para las que la función dio el
valor de 7?
c) ¿Cuántas comparaciones se requieren, en promedio, para encontrar
una llave de la tabla?
d) ¿Cuántas comparaciones se requieren, en promedio, para determinar
que una llave no existe en la tabla?
e) ¿Cuántas colisiones ocurrirían al insertar la llave 154889?
f) ¿Qué tipo de agrupamiento provoca que al insertarse la llave 123456
ésta colisione?
g) Si se eliminara de la tabla la llave 150588, ¿cuántas comparaciones
serían necesarias para determinar que esta llave ya no existe en la

|—±- .
Ejercicios

h) Si se eliminará de la tabla la llave 153926, ¿cuántas comparaciones


serían necesarias para encontrar la llave 152621?
i) Si se diera de baja de la tabla la llave 335872, ¿en qué posición se
guardaría la llave 432178?
j) Reescriba la función hashing, de forma que sea más eficiente.

13. Sobre una tabla hashing de 14 posiciones se utiliza la función


Hl(llave) = llave MOD 10 y se manejan las colisiones con la estrategia
doble hashing con una función H2(llave) = (llave MOD 3) + 1.

a) Muestre, por medio de un esquema, cuál es el estatus de la tabla


después de insertar las siguientes llaves:
27, 12, 15.7,35.40,75,82,99.
b) ¿Cuál es el factor de carga en la tabla?
c) ¿Cuántas comparaciones se requieren para encontrar la llave 75?
d) ¿Cuántas comparaciones se requieren, en promedio, para determinar
que una llave existe en la tabla?
e) ¿Cuántas comparaciones se requieren, en promedio, para determinar
que una llave no existe en la tabla si se sabe que Hl(llave) = 5?

14. Conteste verdadero o falso:

a) Los agolpamientos (primario y secundario) se pueden eliminar por


completo en los métodos de dirección abierta para el manejo de
colisiones. .
b) Al insertar un elemento en una tabla de hashing se buscará la primer
posición con estatus de vacía o borrada, según el método de direc­
ción abierta para manejar las colisiones
c) Una función hashing perfecta elimina por completo los agrupamien-
tos en una tabla de hashing.
d) El tamaño de la tabla de hashing influye en el diseño de una buena
función hashing
e) El método de reasignación aleatoria disminuye al máximo el agru-
pamiento primario. .
f) Los agrupamientos (primario y secundario) se pueden eliminar por
completo con los métodos de encadenamiento para el manejo de
colisiones. _ _
g) La estrategia de manejo de colisiones influye en el diseño de una
buena función hashing
h) El método de reasignación aleatoria elimina el agrupamiento secun­
dario. .

• Q
Hashing

15. Realice la programación en lenguaje C de la rutina correspondien­


te a la especificación de la siguiente operación utilizada por una
aplicación de la técnica hashing.

REHASH1NC,
UTILIDAD: Sirve para obtener la posición de la tabla donde se insertará un
objeto, una vez que se sabe que colisionó.
ENTRADA: Tabla de hashing, la posición i donde colisionó inicialmente el
objeto y la llave del objeto.
SALIDA: Posición i de la tabla que está desocupada, en donde se puede
insertar el objeto.
PRECONDICIÓN: La tabla de hashing no está llena y el objeto no existe en la
tabla.
POSTCONDICIÓN: Ninguna.

El método por utilizar será el doble hashing (o rehashing) usado como


segunda función para el cálculo de la constante c : H(llave) = H(d,d2d3)
= r3r4, donde r3 y r4 son los dígitos tomados de: llave2 = ipyytftl»
Escriba la definición de tipos necesaria para trabajar con la tabla de
hashing y para programar esta rutina.

16. Dada una tabla de hashing con capacidad de 150 elementos y en la


cual se usa la función hashing H(llave) = H
(didafod*) = ABS(did2 - dbcU), responda:

a) Si al insertar la llave X, la función hashing dio como resultado la


localidad 45 de la tabla, mencione tres valores posibles para
la llave X.
b) ¿Qué tipo de función es la de hashing? Explique si la función es
una buena función de hashing o no, justificando la respuesta.
c) Si al insertar la llave 1234, la localidad 22 estaba ocupada, men­
cione cuáles son las siguientes tres localidades de la tabla que
se verificarán mientras sigan existiendo colisiones, según la fi­
losofía de cada uno de los siguientes métodos para manejo de
colisiones:
i) Prueba lineal
ii) Prueba cuadrática
iii) Doble hashing con H2(d,d^d,d4) = d,d,d,d4 MOD 100 + 1

@ •
Rutoetialuacion

• flUTOEUHLUflCIÓN <\ =

i i
h _ _ _ _ _ _ _ _ — _ _ _ _ _ _ _ — _ _ _ _ _ J

1. Sobre una tabla de hashing de cinco posiciones inicialmente vacía


se han insertado las siguientes llaves en el orden que se muestran:
13, 8, 24, 10, 3. Si la función hashing empleada es: h(llave) = llave
mod 5 y se usó la estrategia de la prueba lineal para el manejo de
las colisiones, ¿cuál es el elemento que se guardaría en la posición
número dos de la tabla?

a) 3 b)8 c)10 d) 13 e)24

2. Si las llaves del problema anterior se hubieran insertado con la mis­


ma función hashing, pero utilizando el encadenamiento extemo como
estrategia de manejo de colisiones en la tabla de cinco posiciones,
¿cuántos elementos conforman la lista más grande de la tabla?

a) 1 b) 2 c) 3 d) 4 e) 5

3. Se tiene una tabla de hashing con 28 posiciones y en la cual se usó


la función hashing H(Uave) = H(d|d2d3)=di+d2+d3. Si se usa el do­
ble hashing como estrategia para el manejo de colisiones con la fun­
ción H2(llave) = d|+d„ ¿cuáles serían las siguientes tres posiciones
de la tabla que se verificarían si se inserta la llave 845 y la posición
17 está ocupada?

a) 30, 43, 56 b) 2,15,0 c) 18, 19,20

d) 18,21,26 e)2,0, 11

4. Sobre una tabla de 10 posiciones (0...9) se han insertado seis llaves


cuya función hashing dio siempre como resultado la misma posi­
ción en la tabla. ¿Cuál es el promedio de comparaciones necesarias
para determinar que una llave no existe en la tabla, si se usó la prue­
ba lineal para el manejo de las colisiones y la función hashing arro­
ja posiciones de toda la tabla?

a) 3 1 / 1 0 b)21/6 c)31/6

d)21/10 e)16/10

• 0
Hashing

5. Sobre un conjunto de llaves se aplicó la función hashing: H(llave) =


(Id/) MOD 11 y se utilizó como estrategia de manejo de colisiones
el método de la prueba lineal.
Sobre la tabla de 11 posiciones se insertaron siete llaves, como
se muestra en seguida:

0 1 2 3 4 5 6 7 8 9 ID

787 103 933 899 ! 475 676 045

I I i i . I I

Indique cuál de las siguientes aseveraciones es falsa:

a) La llave 899 colisionó al momento de insertarse.


b) Un posible orden en que las llaves se insertaron es: 103, 475,
787, 045, 933, 676, 899.
c) La inserción de la llave 676 no provocó colisiones.
d) La búsqueda de la llave 103 requiere sólo de una comparación.
e) Ninguna de las anteriores.

6 •
Capítulo^CI

OBJETIVOS

• Describir las características principales de los árboles B: orden del ár­


bol y balanceo total.
• Explicar la representación física de un árbol B en el contexto de
memoria secundaria.
• Describir los algoritmos para las operaciones de inserción y elimina­
ción de elementos en un árbol B.
• Conocer algunas de las variaciones sobre los árboles B: árbol B*, árbol
B+ y árbol 2-3.
• Analizar el comportamiento de un árbol B con diferentes órdenes.

6
Arboles B

¿Qué es un árbol B?
Un árbol B es un tipo especial de árbol con determinadas propiedades
que lo hacen útil para guardar y accesar eficientemente a grandes can­
tidades de información en memoria secundaria. Bayer y McCreight de­
sarrollaron en 1970 la idea de los árboles B, motivados por encontrar
» una nueva técnica para administrar grandes volúmenes de información.
Al igual que en un ABB, en un árbol B la búsqueda de un elemen­
to requiere del recorrido de un camino, desde la raíz del árbol hacia al­
guna de las hojas; estos árboles están completamente balanceados, por
lo que se garantiza eficiencia en los algoritmos de búsqueda, inserción
y eliminación. Sin embargo, el proceso de inserción y baja de elemen- S
tos varía ligeramente respecto a un árbol binario tradicional.
A diferencia del árbol binario balanceado (AVL), los árboles B pue­
den guardar en sus nodos más de un elemento y tener más de dos hi­
jos, por lo que son árboles multicamino. Esta propiedad permite que
se almacenen grandes cantidades de información sin que la altura del
árbol sea muy grande, lo que optimiza los algoritmos de acceso en
memoria secundaria.
Una definición general del árbol B es la siguiente: \

Un árbol B de orden n es aquel que:


1. Todas las hojas en el árbol están al mismo nivel.
2. Cada nodo contiene entre n y 2n elementos (excepto la raíz que
tiene entre 1 y 2o).
3. Si un nodo tiene m elementos, el nodo contendrá 0 o m + 1 hijos.
4. Los elementos de un nodo del árbol están ordenados linealmente
en el nodo.
5. Los elementos del árbol B están organizados siguiendo las propie­
dades de un ABB, es decir, los elementos menores a la izquierda
y las mayores a la derecha del nodo original (figura 10.1).

( 7^ ^
. 10 15 20 (30 35 40 4 5 ;

f í ^ Í T V 111314 ) f 21 22 23 ) ( 26 27 2 8 ) ) (3^7~3~^~9" 46 47 48 4S9

c 16 18 19 ) (3T32 3334) ( 4142 4 4 )

^ . )
Figura 10.1. Ejemplo de un árbol B. Este árbol puede ser de orden 2 o de orden
3, sin romper ninguna de las reglas.

@ •
¿Cómo se realiza el proceso de inserción en un árbol B?

Nota importante: algunos autores definen el orden de un árbol B según


su capacidad máxima de elementos o de hijos en los nodos. Para estos
casos, la definición se adaptaría a esas condiciones, pero el concepto
sigue siendo el mismo. Para efectos de este capítulo, el orden del árbol
B se considerará tal como se definió.

Puesto que el árbol B es una estructura para guardarse en memoria


secundaria, los nodos de estos árboles pueden almacenarse como una
--.._ página en disco. Las aplicaciones reales de árboles B manejan órdenes
^ \ de 100 o más elementos; sin embargo, para ilustrar su funcionamiento
^ ^ \ se manejarán árboles B con un orden pequeño.

\ ¿Cómo se realiza el proceso de inserción en un árbol B?

\ \ El algoritmo general para dar de alta un nuevo elemento en un árbol B


\ de orden n se muestra enseguida:

I • Busque el nodo hoja donde se debe insertar el nuevo elemento.


/ / Sobre este nodo pueden ocurrir los siguientes casos:

/ / 1. El nodo no está lleno, es decir, tiene menos de 2fl elementos; y


/ / tiene capacidad para guardar más.
y f / -> Debe insertarse el nuevo elemento en el nodo y terminar con
el proceso de inserción.

2. El nodo está lleno, es decir, tiene 2« elementos; por lo que no tie­


ne capacidad de guardar más.
-> Debe hacerse una división del nodo de la siguiente forma:
Dado que se tienen 2fl + 1 elementos: #
* Se crea un nuevo nodo y recibe a los fí elementos más grandes.
* Los n elementos más pequeños quedan en el nodo donde ocu­
rrió el desborde.
* El valor medio pasa a ser el padre de los dos nodos menciona­
dos anteriormente.
El proceso de inserción continúa. Se inserta el valor medio
(padre) en el nodo correspondiente, verificando de nuevo en qué
caso se está ubicado.

3. El nodo está lleno y es la raíz del árbol.


->Debe hacerse una división del nodo de la siguiente forma:
Dado que se tienen 2n + 1 elementos:
* Se crea un nuevo nodo y recibe a los n elementos más grandes.
* Los « elementos más pequeños quedan en el nodo donde ocu­
rrió el desborde. A

• 0
HrbolesB

* Se crea un nuevo nodo, que toma como único elemento el valor me-
Rrbo,e
dio y este nodo pasa a ser la nueva raíz del árbol.
Termina el proceso de inserción.

A través del siguiente ejemplo, se ilustra el funcionamiento general


del algoritmo de inserción.

^c Ejemplo

Suponga que se tiene el árbol B de orden 2 (figura 10.2). La canti-


dad máxima de elementos que puede tener un nodo es cuatro, el núme-
ro mínimo es dos (excepto la raíz) y la cantidad de hijos que tendrá un
nodo depende de la cantidad de elementos del nodo, pero podrá variar
entre 0, 2, 3,4o5hijos.

aa 3B, 43 )

( 10, SO, 25 32. 'JA 40.42 44.50.56 ■


'

Al insertar el elemento 58 sobre el árbol se cae en el caso 1 (figura


10.3), puesto que el nodo tiene espacio para recibir un elemento más.
El árbol queda así:

^ 30.38,43 ~*)

( 10. 2 0 , 2 5 ^ 32,34 ) 40.43 44, 50. 56,58

AI insertar el elemento 60 sobre el árbol se cae en el caso 2 (figura


10.4), por lo que ocurre un desborde en el nodo y existe la necesidad
de una división. El árbol queda así:

30.3a, 43. 56 ~ )

ÍO. PO S5 35,34 ~ 40.42 ) ( f ^ i o " ""la,60 )

Vj )

6 •
¿Cómo es el proceso para eliminar un elemento en el árbol B?

H E j e m p l o (continúa)

í Después de insertar los elementos 52,54 y 46 (figura 10.5), el árbol^


tiene que crecer en un nivel (caso 3) y queda de la siguiente forma:

^—-C 43 |.3~~—^-
( 3033 ") f SO, 56 )

C magas) ( 33,34 ) (' 40.48 ) ( 44,« ) ( sas* ) ( sáeo )


"\ \X
V
\ %
>
s
v J
\ \ Observaciones importantes sobre la inserción
\ \ * El árbol siempre se resiste a crecer. Se trata de distribuir los elemen-
\ tos en los nodos ya existentes.
\ ■
' * Cuando el árbol aumenta en altura, sólo se agrega una nueva raíz. El
árbol siempre permanece balanceado y crece de abajo hacia arriba.
/ i

¿Cómo es el proceso para eliminar un elemento en


el árbol B?
/ /
jf í

^ El algoritmo general para dar de baja un elemento del árbol B de or­


den n considera casos parecidos a los de la inserción.
Al igual que en los otros tipos de árboles de búsqueda, la elimina­
ción siempre se dará en un nodo hoja; cuando el elemento no está di­
rectamente allí, se busca quién lo sustituya aplicando alguna de las
metodologías de sustitución ya conocidas: el "elemento mayor de los
menores" (del subárbol izquierdo, el nodo de más a la derecha, el úl- *
timo elemento en la lista) o el elemento "menor de los mayores" (del
subárbol derecho el nodo más a la izquierda, el primer elemento en
la lista).
Una vez que se ha encontrado el nodo hoja donde se borrará un
elemento (sea el elemento real o el sustituto), se procede de acuerdo
con lo siguiente:
l. El nodo que contiene el elemento por eliminar posee más del mí­
nimo de elementos. En este caso, se elimina el elemento del nodo
y el proceso de eliminación termina (figura 10.6).

• 0
Arboles B

( 10 15 aO ) (3035 40 4 ^

(TzÚ) ( 11 13 1 4 ) j ^ g l ^ 3 3 ^ ( 86 27 28 j | ' 3 6 37 3839) ' í46 47 48 49)

( 16 18 19 ) f31 32 33 34) 41 42 44 )

Figura 10.6. La eliminación de cualquier elemento del árbol, si es de orden 2, no


afecta la cantidad mínima de elementos que puede tener un nodo.

2. El nodo que contiene el elemento por eliminar posee exactamente el


mínimo de elementos. En este caso se buscará otro elemento que
pueda sustituir al que se dará de baja y, si no existe un sustituto, se
deberá unir dos nodos. /
Cuando alguno de los hermanos adyacentes al nodo que se dará de /
baja tiene más del mínimo de elementos, el elemento padre del nodo
con el elemento por borrar lo sustituirá cuando sea borrado. Un
elemento (el mayor o el menor, según el caso) del nodo hermano
adyacente con más del mínimo de elementos pasa a ser el nuevo
elemento padre y el proceso de eliminación termina (figura 10.7) \

S F=^ N "^ \ .
/ 101530) ■ 3035 4045 **»,

(" 1 B34,) ( 1lrtSt4 ) ( SI ES! 23 ) ( ES 57 38 ) l'36 37 3839) 1 ( « 4 7 48 49)

( 161813 ) (313233 34) ( 41 43(44)')

. Baja los elementos 13 y y4

/ 4 15 ¿O > (3035 40 48}

( 1E3 ) ( 10 11 14 ) ( 2123 24 ) ( 262728 ) ( 36 37 38 39 47 484*ÍT)

> ( 161813 ) (¿rd^M) (~44245J j

Figura 10.7. Si el árbol es de orden 3, la eliminación de los elementos 13 y 44 provo­


ca un ajuste en el árbol, de tal forma que algún nodo hermano adyacente con más
del mínimo de elementos transfiere un elemento para no desbalancear el árfad.

Si no existiera algún nodo hermano adyacente con más del mínimo de


elementos se procederá de la siguiente manera: el nodo donde ocurre
la eliminación y un hermano adyacente se unen, junto con el elemen­
to padre que desaparece del nodo padre. Si este último queda con más

Q_ #
¿Cómo es el proceso para eliminar un elemento en el árbol B?

del mínimo de elementos termina el proceso (figura 10.8). Si no, se


analiza la eliminación en el nodo padre aplicando los casos que ya
se han explicado (figura 10.9).

( \

( 4 15 30 ) (3013540«I

<\Hx^ _-i~fiT2r>--_
( 133 ) ( 10 11 14 ) i ( gl 3334 ) C 3B 37 3B ) ¡ ( 36 37 3838) ( 47(43)49 )

" \ ( 16 IB 19 ) (3138 3334) ( 414345)


~~"^\ *», ir Bfl(e el elemento 48

\ \ ( 4 15 30 > ( 30 35 40 ")

\ ', ( 13 3 ) ( 1011 14 ) ( 8133 34 ) ( 36 37 38 ) (36373839)

\ 'í ( IB 18 IB ) (313333 34) ( 41 43 45 46 47 49 )

f Figura 10.8. Si el árbol es de orden 3, la eliminación del elemento 4 8 provoca la


/ i unión del nodo donde se encontraba con su nodo hermano adyacente, eliminando
/ / a su elemento padre del nodo padre, sin afectar el resto del árbol.
/ r
/ t

^ ^
y / ( -' ( 41530 \
--o ('3035 40 431
>
_„„#-* (fjje3 ) ( 101114 ) ( 8133 34 ) ( 38 3 7 3 8 ) ( 3 6 3 7 3 6 3 3 ) I ( 474849 )

( IB 18 19 ) ( 3 1 33 3334) ( 41 43 45 )

, ñoratí* ™ « s 1

( 15 3 0 3 5 ) í" 30 3540 \

( 8 341011tt) ( ai 3334 ) ( 3 1 33 33 34) ( 3 6 37 38 39) ('474849 )

( IBiaiB ) ( 363738 ) ( 41 43 45 )

V )
Figura 10.9 Si el árbol es de orden 3, la eliminación del elemento 1 provoca la
unión del nodo donde se encontraba con su hermano adyacente y, además, la
transferencia de un elemento al nodo padre, para completar su cantidad mínima
de elementos. Observe que se movió un nodo de un hermano a otro.

3. El nodo que contiene el elemento por eliminar es la raíz del árbol y


posee sólo un elemento. En este caso, el nodo desaparece y la nue­
va raíz del árbol será el nodo resultado de la unión de los dos hijos,
que dependían de la raíz anterior (figura 10.10).

• Q
Hrboles V,

_£_ 155088 ) ( 3540W ) „

(23410líft) ( giaa24 j (31383334) (38373839j 47484a

16 IB 19 ) 26 27 es ( 41 4245^)

A - B¡*a ei elemento 23

_X T3Í3O30354048)~^

r^T 18 IB IB 'l / ' O ( 4142 45 )


v :
'' ( 3132 33 34) ■ v y
,-* ^

( 234101114 ) r J- -, (-„„48 ^ /'' y^

^ ■ ^ / /

Figura 10.10. Si el árbol es de orden 3, la eliminación del elemento 23 provoca


la disminución de la altura del árbol, ya que se unen los nodos del nivel inferior y
los nodos padres, generando una nueva raíz. /

¿Cuál es el análisis general de comportamiento de


los árboles B? , \
1 \

El análisis de la eficiencia de un árbol B siempre ha sido un importan- \


te tema de estudio. La capacidad de almacenamiento de los nodos, en
conjunto con la cantidad probable de elementos por insertar, determi­
nan la eficiencia de la búsqueda (altura). Por lo tanto, es importante
conocer cómo se afecta la eficiencia al hacer variaciones en estos
parámetros.
Primero se analiza cómo se comporta el árbol B cuando se utilizan
los nodos a su mínima capacidad:

• ¿Cuántos elementos mínimo se guardan en el nivel 0?


1; por lo tanto, hay 2 hijos.
• ¿Cuántos elementos mínimo se guardan en el nivel 1 ?
n en cada nodo, 2n en total; por lo tanto, hay 2(/i + 1) hijos.
• ¿Cuántos elementos mínimo se guardan en el nivel 2?
n en cada nodo, 2n(n + 1) en total; por lo tanto, hay
2(n+ !)(«+ 1) hijos.
• ¿Cuántos elementos mínimo se guardan en el nivel 3?
n en cada nodo, 2n(n + l)2 en total; por lo tanto, hay
2(w+ l)2(n + 1) hijos.
• En general, ¿cuántos elementos mínimo se guardan en el nivel k?
n en cada nodo, 2n(/?+l)*-' en total; por lo tanto, hay 2(/?+l)*
hijos, si no es el último nivel.

0 •
¿Que uentajas ofrece un árbol B?

Con estas fórmulas, se pueden resolver preguntas como:

¿Cuál es el número mínimo de elementos o de nodos que tiene un


árbol B de cierta altura hl
¿Cuál es la altura máxima que tiene un árbol con cualquier cantidad
de elementos?

Ahora, se analiza cómo se comporta el árbol B cuando se utilizan


--,. los nodos a su máxima capacidad:

\ \ • ¿Cuántos elementos máximo se guardan en el nivel 0?


>v \ 2/i, por lo tanto, hay 2» + 1 hijos.
\ • ¿Cuántos elementos máximo se guardan en el nivel 1?
\ 27 en cada nodo, 2n(2n + 1) en total; por lo tanto, hay
\ \ (2n+ l)(2/? + 1) hijos.
\ \ • ¿Cuántos elementos máximo se guardan en el nivel 2?
27 en cada nodo, 2«(2/i+l)2 en total; por lo tanto, hay
I (2/7+l)2(2«+1) hijos.
/ / • ¿Cuántos elementos máximo se guardan en el nivel 3?
/ 2/i en cada nodo, 2n(2n + l)3 en total; por lo tanto, hay
/ (2/7+ l)3(2/7+ 1) hijos.
• En general, ¿cuántos elementos máximo se guardan en el nivel *?
^ / I7 en cada nodo, 2n{2? + 1)* en total; por lo tanto, hay
„.--*'' (2/? + l)*+l hijos, si no es el último nivel.

Con estas fórmulas se pueden resolver preguntas como:

¿Cuál es el número máximo de elementos o de nodos que tiene un


árbol B de cierta altura h? •
¿Cuál es la altura mínima que tiene un árbol con cualquier cantidad
de elementos?

¿Qué ventajas ofrece un árbol B?

Puesto que los nodos de un árbol B están en memoria secundaria, se


busca minimizar los accesos que se hagan a disco porque los tiempos
de ejecución son muy costosos. La altura que tenga un árbol B deter­
minará cuántos accesos al disco, en el peor de los casos, se tendrían
que realizar en alguna operación sobre el árbol.

. 0
Arboles B

La tabla de la figura 10.11 muestra la altura correspondiente al árbol


B para diversos órdenes de un árbol y diversas cantidades de elementos:

f — =T~^
ORDEN NUMERO DE ELEMENTOS
104 105 i D 6 1D? 108 109

16 4 5 5 6 7 8
32 3 4 4 5 6 6 .---""
64 3 3 4 4 5 5 ,-**'*
L ■ f-c . | . — 1 . —j- —,. — » ■■ I / * j S ^

v y / y<
Figura 1011 /

Estos resultados demuestran el beneficio de almacenar datos en /


un árbol B. Por ejemplo, para almacenar un billón de elementos en un
árbol B de orden 16, la altura del árbol cuando mucho será de 8 (ocho
accesos a disco): sin embargo, un ABB requiere una altura de 29 nive­
les para guardar 999 999 999 elementos en el caso ideal y con las impli­
caciones correspondientes a su implantación en memoria secundaria. \ \
Si los nodos se ven como páginas de memoria secundaria (disco),
esta estructura es útil en la administración del nivel físico de una ba­
se de datos. Existen algunas variantes sobre estos árboles, como los
árboles B*. B+ y B+ link, que actualmente se emplean en la imple-
mentación de manejadores de bases de datos.

¿Cómo se representa físicamente un árbol B?

Para un árbol de orden n, los nodos deben contener:

• Un arreglo de 2n posiciones para guardar elementos.


• Un arreglo de 2n + 1 posiciones para guardar apuntadores a hijos.
• Un apuntador al nodo padre.
• Un valor que indique la cantidad de elementos que guarda el nodo.

Los apuntadores en los nodos del árbol B serán direcciones de dis­


co si se implementa en memoria secundaria. Las búsquedas de un
elemento en el nodo se realizan en memoria principal, una vez que se
ha cargado del disco. En caso de ser un árbol B con un orden peque­
ño se puede aplicar una búsqueda secuencial; para un orden grande
será recomendable una búsqueda binaria.
La programación de los algoritmos de las operaciones de inserción
y eliminación de elementos quedan fuera del alcance de este libro.

N •
¿Qué es un árbol B ?

¿Qué es un árbol B*?


Un árbol B* es una variante del B, también propuesta por Bayer y Mc-
Creight. Ésta busca optimizar el uso de la memoria en un nodo al tra­
tar de mantenerlo a 2/3 de su capacidad máxima. Los algoritmos de
inserción y baja se modifican tratando de cumplir con esta nueva ca­
racterística; sin embargo, consumen mayor tiempo de ejecución por
la cantidad de consideraciones que se le deben tener. Por ejemplo, en la
inserción se evitará la división de un nodo, a menos que los nodos her-
\ manos adyacentes también estén llenos (figura 10.12).

\ \ ( 24aBI0ia ~) ( aoai esS384ss ) f"ai ¿ 3 3 3 4 3 5 3 6 )

Inserain del
' Hüsmsfito 2 8

/
/ ./ v
/ / (-5469 1012) ( 80 BIES 83") ( 85883031 )
'
"( 33 34 35 3¿P)

Figura 10.12. Inserción de un elemento en un árbol B* de orden 3. Observe la


y
^ S / distribución de los elementos de dos nodos llenos en tres nodos [los dos ya exis-
tentesy el nuevo).

Ventajas de un árbol B*
• Mejor aprovechamiento del espacio interno de cada nodo en el árbol.
• En promedio, menor altura que un árbol B, por lo que genera mejores •
búsquedas.
Desventaja de un árbol B*
• Los algoritmos de inserción y eliminación son más complejos que en
un árbol B, por lo que se genera sobrecarga en el proceso.

¿Qué es un árbol B+?

Los árboles B+ surgen como una variante de los árboles B, para hacer
más eficiente su uso ante las necesidades de implementación de un ma-
nejador de bases de datos. La característica principal de estos árboles
es que contienen a todos los elementos del árbol en el nivel de los no­
dos hoja Los niveles superiores contienen un mapa de llaves que
permite hacer las búsquedas aleatorias con la eficiencia ya conocida.

• Q
Rrboles B

Adicionalmente, los nodos hoja pueden ligarse en una lista lineal,


permitiendo accesos secuenciales eficientes a los datos (figura 10.13).

( 7^ ^
Z
Mapa de llaves \^
(para realizar

Encadenamiento lineal Todos los objetos / X


para hacer accesos "completos" están en el / /
i secuenciales nivel inferior del árbol J /

Figura 10.13. Esquema general de un árbol B+. /

Ventajas de un árbol B+

• Permite el acceso secuencial a la información para facilitar los des­


plegados en orden. \
• Los nodos intermedios del árbol sólo contienen la información de las
llaves. Algunas veces se puede cargar completo a la memoria principal.

Desventajas de un árbol B+

• Hay duplicidad de datos en memoria.


• Cualquier búsqueda debe llegar siempre hasta las hojas para accesar
a cada elemento completo (no sólo a su llave).

¿Qué es un árbol 2-3 o árbol B binario (BB tree)?

Un árbol 2-3 es un árbol B de orden 1. Su nombre se deriva del he­


cho de que los nodos bajo este nombre sólo pueden tener dos o tres
hijos. Manejar un árbol B de un orden tan pequeño no tiene sentido,
por lo que su ventaja radica en su uso en memoria principal como
árboles balanceados. Estos árboles representan otra alternativa de re-
presentación para un árbol balanceado en memoria principal con las
ventajas ya observadas. ,

6 •
Ejercicios

La característica principal de los árboles 2-3 es su forma de repre­


sentación. Los nodos no serán de un árbol B, sino de un ABB agregán­
dole un campo que indique si el apuntador derecho señala a un nodo
hijo o a un hermano (figura 10.14).

- \ \ vI ^ ])
\ V Figura 10.14. Árbol 2-3 en que los nodos guardan un elemento, pero simulan al
\ \ árbol B utilizando el apuntador derecho para señalar a un hermano o hijo.

Los árboles 2-3 representan una opción para manejar un árbol par-
/ i cialmente balanceado en memoria principal; además de los árboles
/ / AVL, aunque no son tan populares.
/ j
/ i
/ i

i ( EJERCICIOS ) &
% /

o
1. Dado el siguiente árbol B de orden 2: •

, < - - ^ ^ ,
(3035 j g0 5BB370)

(JO SO 85) ( 3 8 3 4 ) ( 4 0 41 ) JÍ446 4748) ( 55 54 4) ( 5 6 60 ) ( 8 5 69 ) ((7 72 73)

a) Muestre en un esquema el árbol resultante, después de insertar la


llave 49.
b) Del árbol resultante, desarrolle un esquema del árbol después de
suprimir la llave 34.

• 0
Arboles B

2. Responda las siguientes preguntas, suponiendo que se tiene un árbol


B de orden 25:

a) ¿Cuál es el número máximo de hijos que puede tener un nodo de


este árbol?
b) ¿Cuál es la cantidad mínima de hijos que puede tener un nodo de
• este árbol (sin considerar la raíz ni las hojas)?
c) Si la raíz del árbol tiene hijos, ¿cuál es la cantidad mínima de hi­
jos de la raíz?
d) ¿Cuál es el número de elementos que contiene un nodo no hoja
que tiene 30 hijos?
e) ¿Cuántos elementos como máximo puede tener el árbol si consta
de tres niveles?
f) ¿Cuántos elementos como mínimo puede tener el árbol si consta
de dos niveles?
g) ¿Cuál es la altura máxima del árbol si contiene 2000 elementos?

3. Si un árbol B de orden k tiene todos sus nodos a su máxima capaci­


dad y se sabe que existen .v niveles:
\
a) ¿Cuántos elementos guarda cada nodo?
b) ¿Cuántos niveles tendrá el árbol después de hacer la inserción de
un elemento?
c) ¿Cuántos nodos nuevos existen en el árbol después de hacer la in­
serción de un elemento?

4. Se tiene el siguiente árbol B de orden 4:

5 10 15 20 ( 3 0 35 40 45 i

( i 534 ) ( 1 1 12 13 14^) ( 2 1 22 23 24 26 27 28 29 36 37 38 33 . 4 6 47 48 4 8

( S7 89 ~) ( i B 171B19^) 31 32 33 34 ¡ 4142 43 44

a) Muestre a través de un esquema el estado del árbol al eliminar el


elemento 25.
b) Si este árbol tuviera una altura de tres y se sabe que todos sus no­
dos están llenos a su máxima capacidad y, además, guardan la
secuencia de elementos desde 1 hasta k, responda:

0 •
Ejercicios

• ¿Cuál es el valor de /t?


• ¿Cuántos elementos guardaría el nodo raíz si se inserta el
elemento jt+1?
• ¿Cuántos elementos guardaría el nodo raíz si se elimina el
elemento 1?
• ¿Cuántos hijos tendría el nodo raíz si en su lista se elimina
el primer elemento?
• ¿En qué nivel del árbol se encuentra el elemento 9?

5. A continuación se muestra un esquema con el contenido de un archi­


vo que guarda los nodos de un árbol B de orden 3. Cada nodo guar­
dado en la posición de un archivo contiene, además de la información
\ tradicional, un apuntador al nodo padre para facilitar la implanta-
\ \ ción de las operaciones. Considere que 0 equivale a nuil y que el
\ nodo raíz del árbol está en la posición 1 del archivo.
A i
i i
i

. 1 2 3 4 5 6 0 1 9 3 4 5 6
f
I 6 0 [ l o | S0| 3 0 , 4 0 ' 5 0 | 6 0 2| al 4| 5 6J 7 I B
/ / CANTTELEM PADRE ELEM HIJOS
_
/ _ 1 Í 3 4 5 6 ~~0 i 1 3 4 5 6~~
6
/ r^i rn \' i2¡31 ~ i * 1 ° i °i °i °i °i °i °i °
/ / CANT.ELEM PADRE ELEM ^ ^ HUOS
; / / 1 S 3 4 5 6~~ ~~0 1 2 3 4 5 ÉT~
■^ / ^ ral PTJ 1111 igj 13| 14J 15| - ] ¡ o| O] O, O I O| O | O "
--''' CANT.ELEM PADRE ELEM ~~ HÜDS
.---''
1 2 3 4 5 6 0 1 2 3 4 5 6
PH n i I21J25Í27 2B\ - I -] 01 DI D 0 0 I 01 O
CANTJELEM PADRE ELEM HUOS
~J 2 3 4 5 6 ~~0 í 2 3 4 5 6
G p l rri 31 32 34 35 37 38 0 | 0 | 0 | Ol Ol OJ O
CANTJELEM PADRE ELEM HIJOS

C 1 2 3 4 5 6 0 1 2 3 4 5 6
Q [Tí [TI J4BJ47l48l - I - I -J ' Ol Ol OJ Ol OÍ Ol OJ
CANT_ELEM PADRE ELEM HIJOS

-, 1 2 3 4 5 6 0 1 2 3 4 5 6
/ Ríl [~T| "53|55| 571 - I * I - I ~0 0 O O O O 0
CANT.ELEM PADRE ELEM HIJOS
Q 1 2 3 4 5 6 0 1 2 3 4 5 6
H H JB4JBBJ B9J - I • j • I j Ol Ol 0¡ OJ o l OJ Og
CANTJELEM PADRE ELEM HUOS

_^ — o
Arboles S

Responda cada uno de los siguientes incisos:

a) Muestre, por medio de un esquema, el árbol B que se guarda


en la estructura.
b) Mencione un elemento que, al insertarse en el árbol, provoque
que aumente su altura.
• c) Mencione un elemento que, al insertarse en el árbol, provoque que
el nodo raíz tenga un hijo más.
d) Mencione un elemento que, al eliminarse del árbol, provoque
la unión de dos nodos.
e) Si se elimina la siguiente secuencia de elementos en orden: 10,
20, 50. 60, ¿cómo quedaría la estructura en memoria si se
aplica la filosofía del método de sustitución de "el menor de
los mayores"? /
f) Si el árbol guarda 20 elementos, ¿cuántos nodos mínimo se re- /
quieren para almacenarlos?
g) ¿Cuántos nodos hoja máximo habría si el árbol tuviera k niveles?

6. ¿Cuál es la cantidad mínima de elementos que, al insertarse en un


árbol B de orden 5, provocan que el árbol tenga tres niveles?

7. ¿Cuál es la cantidad máximo de niveles que puede tener un árbol


B de orden 8 que contiene 500 elementos? ¿Cuál es la cantidad
mínima?
* \
8. Se tiene un árbol B de orden x con una altura w que guarda la se­
cuencia de llaves desde 1 hasta fe. Todos los nodos contienen la
máxima capacidad de elementos. ¿Cuál es la altura del árbol al
eliminar el elemento 1?

9. Se tiene un árbol B de orden x con una altura w que guarda la se­


cuencia de llaves desde 1 hasta k. Todos los nodos contienen la
máxima capacidad de elementos. ¿Cuál es el valor de k si se sabe
que x = 5 y w = 3?

10. Se tiene un árbol B de orden x con una altura w que guarda la se­
cuencia de llaves desde 1 hasta k. Todos los nodos contienen la
mínima capacidad de elementos. ¿Cuántas llaves mínimo es nece­
sario eliminar para que el árbol tenga una altura w - 1?
11. Se tiene un árbol B de orden x con una altura w que guarda la se­
cuencia de llaves desde 1 hasta k. Todos los nodos contienen la
mínima capacidad de elementos. ¿Cuántas llaves mínimo es nece­
sario insertar para que el árbol tenga la altura w + 1 ?

■yj 0
Ejercicios

12. Se tiene un árbol B de orden % con una altura w que guarda la se­
cuencia de llaves desde 1 hasta k. ¿Cuál es el rango de valores
que puede tomar k si se sabe que w = 3 y x = 1 ?

13. Se tiene m árbol B de orden x con una altura w que guarda la se­
cuencia de llaves desde 1 hasta *. Todos los nodos contienen la
mínima capacidad de elementos. ¿Cuál es la altura del árbol al
eliminar el elemento 1?

**X, 14. Se tiene un árbol B de orden x con una altura w que guarda la se-
^\ *\ cuencia de llaves desde 1 hasta k. Todos los nodos contienen la
^X \ mínima capacidad de elementos. ¿Cuál es el valor de k si se sabe
\ \ que x ss 5 y w = 3?
\ i
\ %
\ \ 15. Se tiene un árbol B de orden x con una altura w que guarda la se-
\ \ cuencia de llaves desde 1 hasta k. Todos los nodos contienen la
máxima capacidad de elementos. ¿Cuántas llaves mínimo es ne-
í cesario eliminar para que el árbol tenga una altura w - 1?
/ / 16. Se tiene un árbol B de orden x con una altura w que guarda la se-
/ / cuencia de llaves desde 1 hasta k. Todos los nodos contienen la
/ / máxima capacidad de elementos. ¿Cuántas llaves mínimo es ne-
/ / cesario insertar para que el árbol tenga la altura w + 1 ?

17. Coloque una V o una F, según sean verdaderas o falsas las aseve-
„.---''' raciones que se mencionan a continuación:
a) En un árbol B*, el proceso de inserción que se realiza en un
nodo hoja que está a 2/3 de su capacidad es idéntico al que se
realizaría en un árbol B.
b) En un árbol B*, la inserción de un elemento en un nodo lleno •
provoca siempre la creación de otro nodo.
c) Los nodos de un árbol 2-3 tienen capacidad para guardar dos
elementos y los apuntadores para tres hijos, incluyendo un
campo booleano que indica si un apuntador es hijo o hermano.
d) Todo árbol 2-3 es un árbol B* de orden 1.
e) En un árbol B+ de altura m, la llave x puede existir, a lo más,
en m nodos (cada nodo en un nivel diferente del árbol).
f) Los árboles B tienen una mayor resistencia a crecer que los
árboles B*.
g) En cuanto a la forma del árbol, todo árbol B es un árbol B* y
viceversa.
h) El proceso de insertar un elemento en un nodo que no está lleno
de un árbol B* es diferente al proceso del mismo caso en un

. _ 0
Arboles B

18. Sobre un árbol B* de orden 2 vacío se inserta la siguiente secuen-


cia de llaves: 43, 21, 77. 58, 63, 15, 37, 41, 72, 39, 70, 26, 8, 30.
Muestre paso a paso cómo se va modificando el árbol B* confor-
me se llevan a cabo las inserciones. ¿Cómo quedaría el árbol si
éste fuera de orden 3?

• 19. Si un árbol B+ tiene una altura x, ¿en cuántos niveles máximo del
árbol puede estar contenida la llave /? ¿En cuántos niveles míni-
mo del árbol puede estar contenida la llave /?

20. Sobre un árbol B+ de orden 2 vacío se inserta la siguiente secuen-


cia de llaves: 43. 21. 77, 58, 63, 15, 37, 41, 72, 39, 70, 26,8,30.
Muestre paso a paso cómo se va modificando el árbol B+ confor-
me se llevan a cabo las inserciones, si se considera que la copia
de cada elemento que no es hoja se almacena en:
a) su descendiente derecho b) su descendiente izquierdo

21.Sobre un árbol 2-3 se hizo la inserción de las siguientes llaves en


el orden mostrado: 1, 3, 4, 2, 5, 6. ¿Cuáles de los nodos en me-
moria tendrían su apuntador derecho a un hermano y no a un hijo?
*
\Y
\
22. Sobre un árbol 2-3 vacío se inserta la siguiente secuencia de llaves:
1, 2, 4, 9, 5, 3, 8, 6. 7. Muestre paso a paso cómo se va modifi-
cando el árbol conforme se llevan a cabo las inserciones.
23. Responda los incisos considerando el árbol B de orden 1 codifi-
cado que se muestra a continuación:

a) Si el árbol está implementado en memoria secundaria, ¿cuántos ac-


cesos al disco tendrían que hacerse máximo al realizar la búsqueda
de un elemento?

o *
Rutoeualuación

b) ¿Cuántas comparaciones de elementos tendrían que hacerse, máxi­


mo, al realizar la búsqueda de un elemento que sí existe en el árbol?
c) ¿Cuántos elementos mínimo es necesario insertar en el árbol para
que tenga una altura de cinco niveles?
d) Conteste verdadero o falso: la baja del elemento a provocaría la
eliminación física de un nodo en el árbol, independientemente de
la filosofía de sustitución que se utilice.
e) ¿Cuántos elementos máximo es posible eliminar, sin que se modifi­
que la cantidad de nodos en el árbol?
0 ¿Cuántos elementos máximo puede guardar el árbol conservando la
^ ^ \ misma altura?
X. \ g) Si los valores codificados representan la secuencia: 1, 2, 3,...,
\ \ « - 1, n\ ¿cuál es el símbolo asociado en el árbol a «, y cuál es su valor?
\ \ h) Si el símbolo # representa un valor mayor al símbolo O y menor al
\ \ símbolo &, ¿cuántos nodos nuevos existirían en el árbol al insertar
el símbolo #?
i) Muestre a través de un esquema el estado del árbol al eliminar el ele­
mento con el símbolo 7.
j) Si el árbol se representara en memoria como un árbol 2-3, mencione
/ / todos los elementos que tendrían su apuntador derecho señalando a
/ / un hermano.
/ k) Si los mismos elementos del árbol B mostrado fueran insertados en
. / / un árbol B+ de orden 7, ¿cuántos elementos estarían contenidos
en los nodos hoja del árbol B+?
_...-■ 1) Si el árbol fuera un B*, ¿cuántos nuevos nodos existirían en el árbol
al insertar un valor menor al del símbolo MI

■ nuTOEunLuncióN Q , .
i i
i i
■._____ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ J

1. Dado un árbol B de orden 100 con una altura de siete niveles cuyos
nodos contienen la mínima capacidad de elementos, ¿cuántos ele­
mentos tendrá el nodo raíz después de eliminar un elemento?

a) 100 elementos
b) 99 elementos
c) 200 elementos
d) 1 elemento
e) Ninguna de las anteriores

. 0
Arboles B

Para las preguntas 2 y 3, considere el siguiente árbol B:

^ 5 1015gQJ ( M 35 40 45

# ( 1 2 3 4 ) [ ( l T l 3 l O ] ( l ? ^ r ) (láZir) | (3637 38 39) 46 47 48 49


( 6789 ) f16relT) (3135 33 34) ( 41 42 44 )

2. ¿Cuántos nodos nuevos habrá en el árbol si se inserta un valor ma­


yor a 49? / S

a) Si el orden es 3, habría dos nuevos nodos / /


b) Si el orden es 2, habría dos nuevos nodos. / /
c) Si el orden es 2, no habría nuevos nodos
d) Si el orden es 3, habría tres nuevos nodos.
e) Si el orden es 2, habría un nuevo nodo. \

3. Si el orden del árbol es 3, ¿cuál de los enunciados es verdadero si se


elimina el elemento de la raíz? \ \

a) El árbol tendrá una altura de dos niveles después de realizar la eli­


minación.
b) Si se utiliza la sustitución del "menor de los mayores", el árbol
tendrá un nodo menos.
c) Si se utiliza la sustitución del "mayor de los menores", el árbol
tendrá un nodo menos.
d) Si se utiliza la sustitución del "mayor de los menores", se elimina­
rán dos nodos del árbol.
e) Si se utiliza la sustitución del "menor de los mayores", se elimi­
narán dos nodos del árbol.

4. Dado un árbol B de orden x, con altura w que guarda la secuencia de


llaves desde 1 hasta L y se sabe que sus nodos contienen la mínima
capacidad de elementos, ¿cuál es el rango de valores de las llaves
guardadas en el nodo hoja de más a la izquierda en el árbol?

a) de 1 ak
b) de 1a x
c) de 1 a 2k
d) de 1 a 2x

& e)de1ajc/2

También podría gustarte